คอนเทนเนอร์ Docker กว่าร้อยตัว: กิจวัตรตรวจสถานะรายเดือนของเรา
การรันคอนเทนเนอร์ Docker กว่าร้อยตัวในสภาพแวดล้อมใช้งานจริงไม่ใช่เรื่องแปลก หากเพิ่มบริการทีละน้อยมาหลายปี Supabase ที่ติดตั้งและดูแลเองมีสิบสามคอนเทนเนอร์แล้ว เพิ่ม frontend ของเว็บ บริการ API หลายตัว ตัวประมวลผลงานเบื้องหลัง ระบบเฝ้าติดตาม และรวมบันทึกเหตุการณ์ จำนวนก็โตเร็วกว่าแผนส่วนใหญ่คาด สิ่งที่ยากไม่ใช่นำคอนเทนเนอร์ขึ้นใช้ แต่ดูแลต่อเนื่อง
เอกสารส่วนใหญ่บอกวิธีทำให้คอนเทนเนอร์ทำงาน แต่แทบไม่บอกสิ่งที่เกิดหกเดือนต่อมาเมื่อดิสก์เต็มวันอาทิตย์ พบว่าหนึ่งในสามไม่มีนโยบายเริ่มใหม่ หรือใบรับรอง SSL หมดอายุเงียบ ๆ เพราะปิดเสียงแจ้งเตือน บทความนี้บันทึกกิจวัตรรายเดือนสำหรับเซิร์ฟเวอร์ใช้งานจริงที่มีคอนเทนเนอร์กว่าร้อยตัว
เริ่มต้นด้วยดิสก์
ดิสก์เต็มเป็นสาเหตุขัดข้องเฉียบพลันที่พบบ่อยที่สุดใน Docker ที่ทำงานมานาน ข้อมูลสะสมในรูปแบบที่เครื่องมือระบบมาตรฐานไม่แสดง df -h บอกการใช้ระบบไฟล์ แต่ไม่บอกว่า Docker เก็บชั้นข้อมูลคอนเทนเนอร์ที่หยุด อิมเมจไม่มีแท็ก และแคชการสร้างรวมกันห้าสิบกิกะไบต์จากการนำระบบขึ้นใช้ซ้ำตลอดหกเดือน
เริ่มด้วย docker system df ซึ่งแยกการใช้ดิสก์เป็นอิมเมจ คอนเทนเนอร์ โวลุมในเครื่อง และแคชการสร้าง ผลมักน่าประหลาดใจ เราเคยเห็นแคชการสร้างมากกว่าชั้นข้อมูลคอนเทนเนอร์ที่กำลังทำงานรวมกัน เพราะการสร้างจาก CI สะสมหลายเดือนโดยไม่ล้าง
ค่าที่สำคัญที่สุดคือคอลัมน์พื้นที่ที่เรียกคืนได้ ก่อนแก้ต้องกำหนดค่าอ้างอิงว่ามีเท่าไรและเปลี่ยนอย่างไรเดือนต่อเดือน หากเพิ่มขึ้น ต้องล้างข้อมูลที่ไม่ได้ใช้อย่างเข้มขึ้นหรือบ่อยขึ้น
ล้างตามลำดับนี้ เริ่มจากโวลุมแบบไม่ระบุชื่อที่ไม่มีคอนเทนเนอร์ใดอ้างอิง ตั้งแต่ Docker 23.0 เป็นต้นมา docker volume prune จะไม่ลบโวลุมแบบมีชื่อที่ไม่ได้ใช้งาน เว้นแต่จะเพิ่ม --all ก่อนเพิ่มแฟล็กนี้ ให้ตรวจสอบว่าไม่มีโวลุมที่ไม่ได้เชื่อมกับคอนเทนเนอร์เก็บข้อมูลที่ยังต้องใช้ไว้:
docker volume prune -f
จากนั้นลบอิมเมจไม่มีแท็กที่คอนเทนเนอร์ทั้งที่ทำงานและหยุดไม่ได้อ้างอิง:
docker image prune -f
สุดท้าย หากยืนยันแล้วว่าสร้างใหม่ทั้งหมดได้ภายในช่วงกู้คืนที่กำหนด จึงลบอิมเมจที่ไม่ได้ใช้ทั้งหมดที่เก่ากว่าเจ็ดวัน:
docker image prune -a --filter "until=168h" -f
ตัวเลือก -a ลบอิมเมจไม่ได้ใช้ทั้งหมด ไม่ใช่เฉพาะที่ไม่มีแท็ก เราเรียกเมื่อยืนยันแล้วว่าทุกบริการสร้างใหม่จากทะเบียนอิมเมจได้ภายในเวลากู้คืนที่ยอมรับ ต้องตรวจสอบก่อนคำสั่ง ไม่ใช่หลัง
นโยบายเริ่มทำงานใหม่
นโยบายเริ่มทำงานใหม่กำหนดผลเมื่อคอนเทนเนอร์จบโดยไม่คาดคิด หรือ Docker daemon เริ่มใหม่หลังรีสตาร์ทเครื่อง คู่มือมักกล่าวสั้น ๆ แต่ตั้งผิดอาจทำให้บริการหยุดเงียบสองสัปดาห์โดยไม่มีคนพบ เพราะการแจ้งเตือนชี้ผิดปลายทาง
Docker มีสี่นโยบาย no เป็นค่าเริ่มต้น ไม่เริ่มคอนเทนเนอร์ใหม่ always เริ่มใหม่เมื่อหยุด ไม่ว่ารหัสจบใด แต่หลังสั่ง docker stop ด้วยมือ จะกลับมาทำงานเมื่อ daemon เริ่มใหม่หรือมีการเริ่มคอนเทนเนอร์อีกครั้งเท่านั้น unless-stopped คล้าย always แต่เคารพการหยุดด้วยมือ หากเรียก docker stop ก่อนรีสตาร์ทเครื่อง จะยังหยุดอยู่หลังเครื่องกลับมา on-failure[:max-retries] เริ่มใหม่เฉพาะรหัสจบไม่ใช่ศูนย์ และเลือกจำกัดจำนวนลองใหม่ได้
บริการเว็บและตัวประมวลผลงาน API ที่ไม่เก็บสถานะใช้ unless-stopped เพื่อให้คอนเทนเนอร์ที่ตั้งใจหยุดระหว่างบำรุงรักษายังหยุดหลังรีสตาร์ทเครื่อง ไม่กลับมาเอง ส่วน always จะกลับมาทำงานเมื่อ daemon เริ่มใหม่ครั้งถัดไป แม้เราจะตั้งใจหยุดไว้ก็ตาม
คอนเทนเนอร์ย้ายโครงสร้างฐานข้อมูลหรืองานตั้งต้นครั้งเดียวควรใช้ no งานย้ายที่ล้มเหลวไม่ควรวนซ้ำ on-failure:3 เหมาะกับการลองสั้น ๆ เมื่อระบบพึ่งพาไม่พร้อมชั่วคราว เช่น ตัวอ่านคิวภายนอกรอโบรกเกอร์ แต่ไม่ควรรันไม่จำกัด
ทุกเดือนตรวจคอนเทนเนอร์ทั้งหมดด้วยคำสั่งเดียว:
docker inspect --format '{{.Name}} {{.HostConfig.RestartPolicy.Name}}' $(docker ps -aq)
คอนเทนเนอร์ที่ใช้ no แต่ไม่ได้ตั้งใจให้เป็นงานครั้งเดียวต้องทบทวน มักเป็นบริการที่เริ่มด้วย docker run ระหว่างเหตุขัดข้อง แล้วไม่ได้เพิ่มใน Compose พร้อมนโยบายเริ่มใหม่ที่เหมาะสม
การหมุนเวียนไฟล์บันทึก
ไดรเวอร์บันทึกเริ่มต้นของ Docker คือ json-file ซึ่งไม่จำกัดขนาดโดยค่าเริ่มต้น คอนเทนเนอร์ที่ส่งบันทึกในอัตราไม่สูงก็สร้างได้หลายร้อยกิกะไบต์ในหลายเดือน เป็นสาเหตุดิสก์เต็มที่พบจริงบนเซิร์ฟเวอร์ที่ไม่ได้วางแผนจัดการบันทึก
แก้โดยตั้งนโยบายส่วนกลางใน /etc/docker/daemon.json:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "100m",
"max-file": "5"
}
}
จำกัดบันทึกต่อคอนเทนเนอร์เป็นห้าไฟล์ ไฟล์ละหนึ่งร้อยเมกะไบต์ รวมสูงสุดห้าร้อยเมกะไบต์ ต้องเริ่ม Docker daemon ใหม่ และต้อง สร้างคอนเทนเนอร์ใหม่ ไม่ใช่เพียงเริ่มใหม่ เพื่อให้ค่าบันทึกมีผล
การตั้งค่า daemon.json ใช้เฉพาะคอนเทนเนอร์ที่สร้างหลังเปลี่ยน คอนเทนเนอร์เดิมเก็บการตั้งค่าเดิมต่อไป ข้อผิดพลาดที่พบมากที่สุดคือกำหนดนโยบายแล้วเริ่ม daemon ใหม่และคิดว่าทุกตัวใช้ตามแล้ว เราตรวจการตั้งค่าบันทึกแยกแต่ละตัวทุกเดือน:
docker inspect --format '{{.Name}} {{.HostConfig.LogConfig}}' $(docker ps -q)
ตัวที่ไม่มีขีดจำกัดชัดเจนจะสร้างใหม่ด้วยค่าปัจจุบันในช่วงบำรุงรักษาถัดไป ลำดับสำคัญ บริการเก็บสถานะต้องรักษาโวลุมข้อมูล และบริการที่พึ่งกันต้องเริ่มตามลำดับถูก
วันหมดอายุใบรับรอง SSL
ใบรับรอง SSL หมดอายุได้ ระบบเฝ้าติดตามอัตโนมัติจับได้ส่วนใหญ่ แต่ก็ตั้งผิด แจ้งเตือนมากจนคนละเลย หรือล้มเหลวเงียบพร้อมบริการที่ตรวจได้ กิจวัตรรายเดือนจึงตรวจด้วยมืออย่างเป็นอิสระจากระบบอัตโนมัติ
ตรวจใบรับรองตรงสำหรับแต่ละโดเมนสาธารณะ:
echo | openssl s_client -connect domain.com:443 -servername domain.com 2>/dev/null | openssl x509 -noout -enddate
คำสั่งแสดงวัน notAfter ใบรับรองที่หมดในสามสิบวันเข้าคิวต่ออายุทันที ไม่ว่าแดชบอร์ดใดบอกอย่างไร การตรวจมือเป็นมาตรการสำรอง
ระบบใบรับรองที่เราดูแลเองสำหรับบริการภายในหลายตัวต้องตรวจใบรับรองระดับกลางแยกจากปลายทาง หากระดับกลางหมดอายุ การตรวจทั้งสายโซ่ล้มเหลวแม้ปลายทางยังใช้ได้ อาการสังเกตยากกว่าใบปลายทางหมด เบราว์เซอร์กับไคลเอนต์อาจให้ข้อผิดพลาดสับสน แทนข้อความ “ใบรับรองหมดอายุ” ที่คาด
เรามีสคริปต์เชลล์วนรายการโดเมน ใช้ openssl ดึงวันหมดอายุ เตือนเมื่อเหลือสามสิบวัน และเตือนระดับวิกฤตเมื่อเหลือเจ็ดวัน รันด้วย cron และรันมือทุกเดือนเพื่อยืนยันผล Cron อีกทาง งาน Cron ล้มเหลวเงียบบ่อยกว่าที่คิด
ขีดจำกัดทรัพยากรคอนเทนเนอร์
หากไม่จำกัดหน่วยความจำ คอนเทนเนอร์ผิดปกติอาจใช้ RAM เครื่องหมดจน OOM killer ของเคอร์เนลหยุดกระบวนการที่ไม่เกี่ยว หากไม่จำกัด CPU กระบวนการหลุดควบคุมอาจแย่งทรัพยากรจากเพื่อนบ้านจนล้มเหลวต่อเนื่อง ทั้งสองไม่ใช่กรณีหายาก
ตรวจขีดจำกัดทรัพยากรของคอนเทนเนอร์ที่ทำงานทั้งหมดทุกเดือน:
docker stats --no-stream --format "table {{.Name}} {{.CPUPerc}} {{.MemUsage}}"
คอลัมน์หน่วยความจำแสดงปริมาณที่ใช้และขีดจำกัดร่วมกัน คอนเทนเนอร์ที่ไม่ได้จำกัดหน่วยความจำจะแสดงหน่วยความจำทั้งหมดของเครื่องโฮสต์เป็นขีดจำกัด ส่วน 0B / 0B จะแสดงเฉพาะคอนเทนเนอร์ที่ไม่ได้ทำงาน เราทบทวนทุกตัว สำหรับบริการ HTTP ไม่เก็บสถานะ เริ่มที่สองถึงสี่เท่าของหน่วยความจำที่สังเกตว่าใช้งาน เป้าหมายไม่ใช่ค่าพอดีเป๊ะ แต่กันการโตไม่จำกัดจนบริการร่วมเครื่องล่ม
ดูเปอร์เซ็นต์ CPU ด้วย ตัวที่ใกล้หนึ่งร้อยเปอร์เซ็นต์ต่อเนื่อง (ใน docker stats ค่า 100% เท่ากับการใช้หนึ่งแกนเต็ม จึงอาจแสดงค่าสูงกว่านี้บนเครื่องหลายแกนได้)อาจมีกระบวนการหลุดควบคุมหรือทรัพยากรไม่พอต่องาน ต้องตรวจทั้งสองกรณีก่อนเดือนถัดไป
ตรวจการอัปเดตอิมเมจ
อิมเมจฐานได้รับแพตช์ความปลอดภัยไม่สม่ำเสมอ รุ่นที่ใหม่เมื่อหกเดือนก่อนอาจมี nginx หรือ PostgreSQL ที่รู้ว่ามีช่องโหว่ เราไม่ดึงและนำทุกตัวขึ้นใช้ใหม่อัตโนมัติทุกเดือน เพราะเสี่ยงกว่าเดิม แต่เทียบสิ่งที่รันกับรุ่นปัจจุบัน
บริการที่ระบุรุ่นอิมเมจตายตัวต้องเทียบกับบันทึกการเปลี่ยนแปลงผู้พัฒนาต้นทางทุกเดือน ส่วนแท็กที่ไม่ผูกกับรุ่น เช่น latest หรือ 16-alpine ให้ดึงและเทียบค่าไดเจสต์อิมเมจ หากเปลี่ยนต้องทบทวนก่อนนำขึ้นใช้
วินัยสำคัญกว่าคือเลิกแท็กที่ไม่ผูกกับรุ่น บริการที่เปลี่ยนโดยไม่รู้ตัวเพราะ latest ชี้รุ่นหลักใหม่และเกิดการเปลี่ยนที่ไม่เข้ากัน วินิจฉัยยากกว่ารุ่นเก่าที่รู้แน่นอน ระบุรุ่นตายตัวแล้วอัปเดตอย่างตั้งใจ
วินัย
การตรวจรายเดือนครอบคลุมหกด้าน: ดิสก์กับล้างข้อมูล นโยบายเริ่มใหม่ หมุนเวียนบันทึก ใบรับรอง SSL หมดอายุ ขีดจำกัดทรัพยากร และความเป็นปัจจุบันอิมเมจ รวมใช้ไม่เกินเก้าสิบนาทีบนเซิร์ฟเวอร์ที่มีเอกสารดี คุณค่าคือทำตามกำหนดก่อนระบบเสีย ไม่ใช่หลังเสีย
ระบบใช้งานจริงเสื่อมทีละน้อย ดิสก์กับบันทึกสะสม ใบรับรองใกล้หมด อิมเมจล้าสมัย ไม่มีการเตือนจนเกินเกณฑ์ การตรวจรายเดือนเปลี่ยนงานดูแลจากตอบสนองเหตุเป็นงานคาดการณ์ได้
บทความที่เกี่ยวข้อง
- สร้างคลังข้อมูล Amazon ด้วย FastAPI และ TimescaleDB — วินัยเดียวกันด้านโครงสร้างพื้นฐานที่ใช้กับชั้นข้อมูล
- ดำเนินงานในระดับใหญ่ด้วยทีมเล็ก: จัดการโครงการหลายสิบรายการ — วิธีที่เราจัดโครงสร้างการดูแลรักษาซ้ำ ๆ ในกลุ่มโครงการขนาดใหญ่