tva
← Insights

Docker containers กว่าร้อยตัว: กิจวัตรตรวจสอบสุขภาพรายเดือนของเรา

การรัน Docker containers กว่าร้อยตัวใน production ไม่ใช่เรื่องแปลกหากคุณสร้างบริการขึ้นมาทีละน้อยในหลายปี Supabase stack ที่โฮสต์เองนั้นมี containers ถึงสิบสามตัว เพิ่ม web frontend, API services หลายตัว, background workers, monitoring stack, log aggregation และตัวเลขจะพุ่งขึ้นเร็วกว่าแผนส่วนใหญ่คาดไว้ สิ่งที่ยากไม่ใช่การ deploy containers เหล่านี้ แต่เป็นการดูแลรักษา

เอกสารส่วนใหญ่ครอบคลุมการทำให้ containers รัน แต่น้อยมากที่ครอบคลุมสิ่งที่เกิดขึ้นหกเดือนต่อมาเมื่อดิสก์เต็มในวันอาทิตย์ หรือเมื่อคุณพบว่า containers หนึ่งในสามไม่มี restart policy หรือเมื่อ SSL certificate หมดอายุเงียบๆ เพราะ monitoring alert ถูก mute บทความนี้บันทึกกิจวัตรรายเดือนของเราสำหรับเซิร์ฟเวอร์ production ที่มี containers กว่าร้อยตัว

เริ่มต้นด้วยดิสก์

ดิสก์คือโหมดความล้มเหลวเฉียบพลันที่พบบ่อยที่สุดใน Docker environments ที่รันมานาน Docker สะสมข้อมูลในรูปแบบที่เครื่องมือระบบมาตรฐานมองไม่เห็น การรัน df -h แสดงการใช้งาน filesystem แต่ไม่บอกว่า Docker กักเก็บ container layers ที่หยุดทำงานห้าสิบกิกะไบต์ dangling images และ build cache จากการ deploy ซ้ำๆ หกเดือน

จุดเริ่มต้นที่ถูกต้องคือ docker system df ซึ่งแบ่งการใช้ดิสก์ตาม images, containers, local volumes และ build cache output มักน่าแปลกใจ เราเคยเห็นเซิร์ฟเวอร์ที่ build cache เพียงอย่างเดียวเกินกว่า container layers ที่รันทั้งหมดรวมกัน สะสมอย่างเงียบๆ จากการ build ที่ CI ทริกเกอร์เดือนแล้วเดือนเล่าโดยไม่เคยทำความสะอาด

แต่ในความเป็นจริง ตัวเลขที่สำคัญที่สุดคือคอลัมน์ reclaimable ก่อนแตะอะไรเราสร้าง baseline: พื้นที่ที่ reclaimable ได้ในปัจจุบันคือเท่าไร และแนวโน้มเป็นอย่างไรเมื่อเปรียบเทียบรายเดือน หาก reclaimable space กำลังเพิ่มขึ้น ตารางการ pruning ต้องเพิ่มความเข้มข้นหรือความถี่ขึ้น

ลำดับการทำความสะอาดของเรารันตามลำดับนี้ ก่อนอื่น volumes ที่ไม่ได้ attach กับ container ใดๆ:

docker volume prune -f

จากนั้น dangling images ซึ่งเป็น layers ที่ไม่มี tag และไม่ได้อ้างอิงโดย container ที่รันหรือหยุดทำงานใดๆ:

docker image prune -f

และสุดท้าย หากเรายืนยันแล้วว่าการ rebuild เต็มรูปแบบสามารถทำได้ภายใน recovery window ของเรา images ที่ไม่ได้ใช้ทั้งหมดที่เก่ากว่าเจ็ดวัน:

docker image prune -a --filter "until=168h" -f

flag -a ลบ images ที่ไม่ได้ใช้ทั้งหมด ไม่เฉพาะ dangling ones เรารันสิ่งนี้เฉพาะหลังจากตรวจสอบแล้วว่าบริการทั้งหมดสามารถ rebuild จาก registry ภายใน acceptable recovery time การตรวจสอบนั้นเกิดขึ้นก่อนคำสั่ง ไม่ใช่หลังจากนั้น

Restart Policies

Restart policies กำหนดสิ่งที่เกิดขึ้นเมื่อ container ออกโดยไม่คาดคิดหรือเมื่อ Docker daemon รีสตาร์ทหลังจาก host reboot คู่มือการ deploy ส่วนใหญ่กล่าวถึงสิ่งนี้สั้นๆ แต่ในความเป็นจริง restart policy ที่ตั้งค่าผิดคือวิธีที่คุณลงเอยกับบริการ ที่หยุดทำงานเงียบๆ สองสัปดาห์และไม่มีใครสังเกต

Docker มี restart policies สี่แบบ no คือค่าเริ่มต้น: container ไม่รีสตาร์ทภายใต้สถานการณ์ใดๆ always รีสตาร์ท container เมื่อใดก็ตามที่หยุดทำงาน รวมถึงเมื่อ daemon รีสตาร์ท โดยไม่คำนึงถึง exit code unless-stopped ทำงานเหมือน always แต่เคารพการหยุดทำงานอย่างชัดเจน หากคุณรัน docker stop ก่อน reboot container จะยังคงหยุดทำงานหลัง reboot on-failure[:max-retries] รีสตาร์ทเฉพาะกับ exit code ที่ไม่ใช่ศูนย์ โดยมีขีดจำกัดการลองซ้ำที่เป็นตัวเลือก

สำหรับ web services และ API workers แบบ stateless เราใช้ unless-stopped หากเราหยุด container โดยตั้งใจระหว่าง maintenance window มันควรยังคงหยุดทำงานหลัง reboot ถัดไปแทนที่จะกลับมาโดยไม่คาดคิด

สำหรับ database migration containers หรือ one-shot initialization jobs policy ที่ถูกต้องคือ no migration ที่ล้มเหลวไม่ควรวนซ้ำ on-failure:3 เหมาะสำหรับ containers ที่ควรลองซ้ำสั้นๆ กับ dependency ที่อาจไม่พร้อมชั่วคราว แต่ไม่ควรรันไม่จำกัด

การตรวจสอบรายเดือนของเรารันคำสั่งเดียวกับ containers ทั้งหมด:

docker inspect --format '{{.Name}} {{.HostConfig.RestartPolicy.Name}}' $(docker ps -aq)

containers ใดที่มี policy no ที่ไม่ใช่ one-shot job ที่ตั้งใจไว้จะถูกตรวจสอบ ในกรณีส่วนใหญ่หมายความว่าบริการถูกเริ่มด้วย docker run ระหว่างเหตุการณ์ และไม่เคยถูกเพิ่มอย่างเป็นทางการใน compose configuration ด้วย restart policy ที่เหมาะสม

Log Rotation

Docker logging driver เริ่มต้นคือ json-file โดยค่าเริ่มต้นไม่มีขีดจำกัดขนาด container ที่ส่ง log อย่างต่อเนื่องสามารถสร้างหลายร้อยกิกะไบต์ในหลายเดือน นี่ไม่ใช่ความกังวลในทางทฤษฎี แต่เป็นหนึ่งในสาเหตุที่พบบ่อยกว่าของการสิ้นสุดดิสก์บนเซิร์ฟเวอร์ production ที่ตั้งค่าโดยไม่มีการจัดการ log อย่างมีเป้าหมาย

การแก้ไขคือ global policy ใน /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "100m",
    "max-file": "5"
  }
}

สิ่งนี้จำกัด log ของแต่ละ container ที่ไฟล์ห้าไฟล์ขนาดหนึ่งร้อยเมกะไบต์ต่อไฟล์ สูงสุดห้าร้อยเมกะไบต์ต่อ container Docker daemon ต้องรีสตาร์ทหลังการเปลี่ยนแปลงนี้ และที่สำคัญ containers ต้องถูก สร้างใหม่ ไม่ใช่แค่รีสตาร์ท เพื่อให้การตั้งค่า log ใหม่มีผล

แต่ในความเป็นจริง การตั้งค่า daemon.json ใช้เฉพาะกับ containers ที่สร้างหลังการเปลี่ยนแปลง containers ที่มีอยู่เดิมจะเก็บ log configuration ดั้งเดิมของตัวเองไว้ตลอดไป นี่คือความผิดพลาดที่พบบ่อยที่สุดที่เราพบ: policy ถูกตั้ง daemon รีสตาร์ท และสมมติว่า containers ทั้งหมดปฏิบัติตามแล้ว แต่ไม่ใช่ การตรวจสอบรายเดือนของเราตรวจสอบ log configuration ต่อ container:

docker inspect --format '{{.Name}} {{.HostConfig.LogConfig}}' $(docker ps -q)

Containers ที่ไม่มีขีดจำกัดขนาดชัดเจนจะถูกสร้างใหม่ด้วย configuration ที่อัปเดตในระหว่าง maintenance window ถัดไป ลำดับการสร้างใหม่มีความสำคัญ บริการ stateful ต้องให้ data volumes ของตัวเองคงอยู่ และบริการที่ขึ้นต่อกันต้องขึ้นมาในลำดับที่ถูกต้อง

การหมดอายุ SSL Certificate

SSL certificates หมดอายุ การ monitoring อัตโนมัติจับกรณีส่วนใหญ่ แต่การ monitoring อัตโนมัติก็ถูกตั้งค่าผิด สร้าง alert fatigue หรือล้มเหลวเงียบๆ ร่วมกับบริการที่ถูกตรวจสอบ กิจวัตรรายเดือนของเรารวมถึงการตรวจสอบด้วยตนเองที่เป็นอิสระจากระบบอัตโนมัติใดๆ

สำหรับแต่ละ domain ที่หันหน้าสู่สาธารณะ เราตรวจสอบ certificate โดยตรง:

echo | openssl s_client -connect domain.com:443 -servername domain.com 2>/dev/null   | openssl x509 -noout -enddate

สิ่งนี้ output วันที่ notAfter สิ่งใดที่หมดอายุภายในสามสิบวันจะเข้าสู่คิวการต่ออายุทันที โดยไม่คำนึงถึงสิ่งที่ monitoring dashboard ใดๆ บอก การตรวจสอบด้วยตนเองเป็น backstop

สำหรับ certificate infrastructure ที่จัดการเอง เราตรวจสอบ intermediate certificates แยกจาก leaf certificates certificate ระดับกลางที่หมดอายุทำให้การตรวจสอบ full chain ล้มเหลวแม้เมื่อ leaf certificate เองยังมีอายุ โหมดความล้มเหลวนี้มองเห็นได้น้อยกว่า leaf ที่หมดอายุ: browsers และ clients อาจรายงานข้อผิดพลาดที่สับสน

เราดูแล shell script ที่วนซ้ำตามรายการ domains, ดึงวันหมดอายุผ่าน openssl และพิมพ์คำเตือนสำหรับสิ่งใดที่อยู่ในสามสิบวันและ critical alert สำหรับสิ่งใดที่อยู่ในเจ็ดวัน script นี้รันเป็น cron job แต่เราก็รันด้วยตนเองระหว่างการตรวจสอบรายเดือน Cron jobs ล้มเหลวเงียบๆ บ่อยกว่าที่คนส่วนใหญ่คาด

Container Resource Limits

หากไม่มีขีดจำกัด memory container ที่ทำงานผิดปกติสามารถหมด RAM ของ host และทริกเกอร์ OOM killer ของ kernel กับ processes ที่ไม่เกี่ยวข้อง หากไม่มีขีดจำกัด CPU process ที่หลุดควบคุมสามารถทำให้ containers เพื่อนบ้านอดอาหาร นานพอที่จะทำให้เกิดความล้มเหลวแบบลูกโซ่ ทั้งสองกรณีนี้ไม่ใช่ edge case หายาก

การตรวจสอบรายเดือนตรวจสอบ resource limits บน containers ที่รันทั้งหมด:

docker stats --no-stream --format "table {{.Name}}	{{.CPUPerc}}	{{.MemUsage}}	{{.MemLimit}}"

Containers ที่แสดง 0B / 0B ในคอลัมน์ memory limit ไม่มีข้อจำกัดที่ตั้งไว้ เราตรวจสอบแต่ละรายการและกำหนดขีดจำกัดที่เหมาะสม สำหรับ HTTP services แบบ stateless ขีดจำกัด memory สองถึงสี่เท่าของ working set ที่สังเกตคือจุดเริ่มต้นที่สมเหตุสมผล เป้าหมายไม่ใช่ความแม่นยำ แต่เพื่อป้องกันการเติบโตไม่จำกัดจากทำให้บริการที่อยู่ร่วมหยุดทำงาน

เรายังดูเปอร์เซ็นต์ CPU ระหว่างการตรวจสอบ stats container ที่ CPU ใกล้ร้อยเปอร์เซ็นต์สม่ำเสมอบน multi-core host แนะนำถึงกระบวนการที่หลุดควบคุมหรือ container ที่มีทรัพยากรไม่เพียงพอสำหรับ workload ทั้งสองสภาวะต้องการการสืบสวนก่อนเดือนถัดไป

การตรวจสอบอัปเดต Image

Base images ได้รับ security patches ตามกำหนดการไม่สม่ำเสมอ container ที่รัน image ซึ่งเป็นปัจจุบันเมื่อหกเดือนก่อนอาจรันกับ nginx หรือ PostgreSQL เวอร์ชันที่มีช่องโหว่ที่รู้จัก เราไม่ดึงและ redeploy ทุก container โดยอัตโนมัติทุกเดือน เพราะสร้างความเสี่ยงมากกว่าที่บรรเทา แต่เราตรวจสอบว่ารันอยู่กับสิ่งที่เป็นปัจจุบัน

แนวทางปฏิบัติ: สำหรับแต่ละบริการที่มี image เวอร์ชันที่ pin ไว้ เราตรวจสอบเวอร์ชันที่ pin กับ upstream changelog เดือนละครั้ง สำหรับบริการที่ใช้ floating tag เช่น latest หรือ 16-alpine เราดึงและ diff image digest เพื่อพิจารณาว่ามีอะไรเปลี่ยนแปลง หากเปลี่ยนแปลง เราตรวจสอบสิ่งที่เปลี่ยนก่อน deploy

แต่ในความเป็นจริง วินัยที่สำคัญกว่าคือการเลิกใช้ floating tags บริการที่ redeploy เงียบๆ พร้อม breaking change เพราะ latest tag ชี้ไปยัง major version ใหม่ เป็นปัญหาที่วินิจฉัยยากกว่าบริการที่รัน image เก่าที่รู้จัก Pin เวอร์ชัน แล้วอัปเดตอย่างตั้งใจ

วินัย

รวมกัน การตรวจสอบสุขภาพรายเดือนครอบคลุมหกพื้นที่: การใช้ดิสก์และการ pruning, การตรวจสอบ restart policy, การตั้งค่า log rotation, การหมดอายุ SSL certificate, resource limits ของ container และความทันสมัยของ image งานเหล่านี้ไม่ต้องการเกินเก้าสิบนาทีโดยรวมบนเซิร์ฟเวอร์ที่มีเอกสารดี คุณค่าไม่ได้อยู่ที่การตรวจสอบแต่ละรายการ แต่อยู่ที่การทำในตารางที่แน่นอน ก่อนที่อะไรจะเสียหาย ไม่ใช่หลังจากนั้น

ระบบ production เสื่อมสมรรถนะทีละน้อย ดิสก์สะสม Logs เติบโต Certificates แก่ Images ตกหลัง กระบวนการเหล่านี้ไม่สร้าง alert จนกว่าจะถึง threshold การตรวจสอบรายเดือนเปลี่ยนภาระงานดูแลรักษาจากการตอบสนองเป็นคาดเดาได้ ซึ่งเป็นท่าทีปฏิบัติการที่แตกต่างโดยสิ้นเชิง


ข้อมูลเชิงลึกที่เกี่ยวข้อง

บทความที่เกี่ยวข้อง