วิธีลบไฟล์ Docker เพื่อเพิ่มพื้นที่ว่างบน VPS อย่างปลอดภัย
VPS พื้นที่เต็มเพราะ Docker ใช่หรือไม่ เรียนรู้วิธีตรวจสอบว่า images, containers, build cache หรือ volumes ส่วนใดที่กินพื้นที่ พร้อมคำสั่ง prune ที่ถูกต้องโดยไม่ให้ข้อมูลสำคัญสูญหาย
ตรวจสอบว่าสิ่งใดกำลังใช้พื้นที่ดิสก์ก่อนที่คุณจะลบข้อมูลใดๆ
Docker ใช้พื้นที่ดิสก์บน VPS ใน 4 ส่วนหลัก ได้แก่ images, containers ที่หยุดทำงาน, build cache และ local volumes ให้รัน docker system df ก่อนเพื่อดูว่าส่วนใดที่ใช้พื้นที่มากที่สุด จากนั้นจึงรันคำสั่ง prune ที่เฉพาะเจาะจงเพื่อล้างข้อมูลนั้น ลำดับมีความสำคัญเพราะคำสั่งสุดท้ายในคู่มือนี้คือ docker volume prune -a ซึ่งจะลบข้อมูลอย่างถาวรและไม่สามารถกู้คืนได้
ให้เริ่มตรวจสอบจากระบบไฟล์ (filesystem) ไม่ใช่จาก Docker
df -h /
sudo du -xh --max-depth=1 /var/lib/docker | sort -hdf จะบอกคุณว่าสถานการณ์รุนแรงเพียงใด ส่วน du จะบอกว่าพื้นที่หายไปที่ไหน แฟล็ก -x จะช่วยให้ du ทำงานอยู่บนระบบไฟล์เดียว เพื่อไม่ให้มันติดตาม mount ไปยัง volume อื่นและนับข้อมูลซ้ำซ้อน มี 5 ไดเรกทอรีที่สำคัญในส่วนนี้: overlay2 เก็บเลเยอร์ของ image และ container, volumes เก็บข้อมูลของ volume, containers เก็บ metadata ของ container และไฟล์ log, buildkit เก็บ build cache และ image เก็บ metadata ของเลเยอร์
ข้อควรทราบเกี่ยวกับ sudo และ shell wildcards ซึ่งมักทำให้เสียเวลามาก: /var/lib/docker เป็นกรรมสิทธิ์ของ root และผู้ใช้ทั่วไปไม่สามารถอ่านได้ ดังนั้น ls /var/lib/docker จึงส่งค่ากลับเป็น Permission denied คำสั่งอย่าง sudo du -sh /var/lib/docker/* จะล้มเหลวเช่นกัน เพราะ shell ของคุณจะขยาย * ก่อนที่ sudo จะทำงาน และ shell ของคุณไม่มีสิทธิ์อ่านไดเรกทอรีนั้น ทุกคำสั่งด้านล่างจึงใช้ find หรือ --max-depth แทนการใช้ wildcard ด้วยเหตุผลนี้
ตอนนี้มาดูมุมมองจากฝั่ง Docker
docker system dfTYPE TOTAL ACTIVE SIZE RECLAIMABLE
Images 24 6 12.4GB 8.91GB (71%)
Containers 31 6 1.42GB 1.39GB (97%)
Local Volumes 14 5 6.03GB 4.11GB (68%)
Build Cache 212 0 9.87GB 9.87GBตัวเลขเหล่านั้นมาจากเครื่องเดียวและไม่ได้บ่งบอกถึงสถานะของเครื่องคุณ ให้พิจารณารูปแบบแทน TOTAL นับจำนวนออบเจกต์, ACTIVE นับจำนวนที่กำลังใช้งานอยู่ และ RECLAIMABLE คือค่าประมาณการของ Docker ว่าการรัน prune จะคืนพื้นที่ได้เท่าใดจากแถวนั้น
มี 2 สิ่งเกี่ยวกับ RECLAIMABLE ที่มักทำให้ผู้ใช้เข้าใจผิด: มันนับเลเยอร์ของ image ที่ใช้ร่วมกันซ้ำตามจำนวน image ที่เรียกใช้ ดังนั้นแถวของ image มักจะแสดงตัวเลขที่สูงกว่าความเป็นจริง และมันไม่เคยรวมไฟล์ log ของ container เพราะ Docker ไม่ถือว่าไฟล์ log เป็นออบเจกต์ที่สามารถล้างข้อมูลได้ เมื่อ du รายงานขนาดไดเรกทอรีที่ใหญ่กว่าที่ docker system df แจ้งไว้ สาเหตุมาจากไฟล์ log ซึ่งจะมีเนื้อหาอธิบายในส่วนถัดไปด้านล่าง
เพิ่ม -v เพื่อดูรายละเอียดแยกตามออบเจกต์
docker system df -vคำสั่งนี้จะแยกสรุปข้อมูลออกเป็นส่วนๆ ตามประเภทของออบเจกต์ ส่วนของ image จะเพิ่มคอลัมน์ SHARED SIZE และ UNIQUE SIZE เพื่อให้คุณเห็นว่า image แต่ละตัวใช้พื้นที่จริงเท่าใด ส่วนของ volume จะเพิ่มจำนวน LINKS ซึ่งคือจำนวน container ที่เชื่อมต่อกับ volume นั้น โปรดจำ LINKS ไว้ให้ดี เพราะค่า 0 คือเกณฑ์ตัดสินว่าคำสั่ง prune volume จะทำงานกับ volume นั้นหรือไม่
Dangling images กับ unused images
คำสองคำนี้ดูเหมือนจะใช้แทนกันได้ แต่ความจริงแล้วไม่ใช่ ตัวกรอง (filter) ทำงานต่างกันเพราะวัตถุที่จัดการนั้นต่างกัน
Dangling image คืออิมเมจที่ไม่มีแท็ก (tag) มันจะแสดงเป็น <none> ในคำสั่ง docker images คุณจะสร้างอิมเมจประเภทนี้ขึ้นมาทุกครั้งที่มีการ rebuild: เมื่อ docker build -t myapp:latest . ย้ายแท็ก myapp:latest ไปยังอิมเมจใหม่ อิมเมจเก่าจะยังคงเก็บเลเยอร์ทั้งหมดไว้แต่สูญเสียชื่อไป ไม่มีสิ่งใดอ้างอิงถึงมัน และไม่มีกระบวนการใดล้างข้อมูลนี้ออกโดยอัตโนมัติ
Unused image คืออิมเมจใดๆ ก็ตาม ไม่ว่าจะติดแท็กหรือไม่ ที่ไม่มีคอนเทนเนอร์ใดใช้งานอยู่ในขณะนี้ ตัวอย่างเช่น postgres:16 ที่คุณดึงมาเมื่อเดือนที่แล้วและไม่ได้รันอยู่ในปัจจุบัน ถือเป็น unused image และมันไม่ใช่ dangling image
docker image prune # dangling images only
docker image prune -a # every image no container refers toคำสั่งที่สองจะถามยืนยันก่อนเสมอ
WARNING! This will remove all images without at least one container associated to them.
Are you sure you want to continue? [y/N]โปรดอ่านข้อความแจ้งเตือนนั้นให้ละเอียด คำว่า "Associated to them" หมายถึงวัตถุคอนเทนเนอร์ที่มีอยู่จริง ไม่ว่าจะกำลังรันอยู่หรือหยุดทำงานไปแล้ว หากคุณรันคำสั่ง docker compose down คอนเทนเนอร์เหล่านั้นจะถูกลบออกไป ทำให้อิมเมจทั้งหมดที่บริการเหล่านั้นเคยใช้กลายเป็น unused และคำสั่ง -a จะลบอิมเมจเหล่านั้นทิ้งทั้งหมด ข้อมูลที่ลบไปสามารถกู้คืนได้เสมอ แต่การรัน docker compose up -d ในครั้งถัดไปจะทำให้ระบบต้องดึงหรือสร้างอิมเมจใหม่ทั้งหมด ซึ่งสิ้นเปลืองทั้งแบนด์วิดท์และเวลาในการ build บน VPS ขนาดเล็ก นี่คือเหตุผลเชิงปฏิบัติที่คุณควรทราบว่า docker compose down ลบอะไรออกไปบ้างและ stop ทิ้งอะไรไว้ให้รันอยู่ ก่อนที่จะทำการ prune ข้อมูลใดๆ
ตัวกรอง (filter) ช่วยกันไม่ให้อิมเมจที่เพิ่งใช้งานไม่นานถูกลบออกไป
docker image prune -a --filter "until=240h"คำสั่งนี้จะลบ unused image ที่ถูกสร้างขึ้นนานกว่า 240 ชั่วโมง (10 วัน) ที่ผ่านมา และเก็บอิมเมจที่ใหม่กว่าไว้ ค่า until รองรับรูปแบบ Go duration string เช่น 240h หรือการระบุเวลาแบบเจาะจง (absolute timestamp) เช่น 2026-08-01T00:00:00
Build cache คืออะไรและเหตุใดจึงขยายขนาดโดยไม่มีขีดจำกัด
BuildKit เป็นเครื่องมือสร้างอิมเมจที่ Docker ใช้เป็นค่าเริ่มต้นสำหรับ docker build และ docker compose build ตั้งแต่ Docker Engine 23.0 เป็นต้นมา มันจะเก็บแคชผลลัพธ์ของทุกขั้นตอนในทุก Dockerfile ที่ทำงาน และจัดเก็บแคชนั้นไว้ใน /var/lib/docker/buildkit แคชนี้คือเหตุผลที่การสร้างอิมเมจครั้งที่สองของคุณเสร็จสิ้นภายในเวลาไม่กี่วินาที ดังนั้นมันจึงทำหน้าที่ของมันได้ถูกต้อง ปัญหาคือไม่มีกลไกใดที่ลบรายการเก่าออกโดยอัตโนมัติ หากคุณสร้างอิมเมจเดิมซ้ำ 50 ครั้งโดยมีขั้นตอน COPY ที่เปลี่ยนแปลงไปในแต่ละครั้ง คุณก็จะเก็บชุดเลเยอร์ไว้ถึง 50 ชุด
Build cache เป็นสิ่งที่มองไม่เห็นสำหรับ docker image prune มันเป็นออบเจกต์ประเภทแยกต่างหากที่มีคำสั่งจัดการเฉพาะของมัน
docker builder prune # dangling cache
docker builder prune -a # all unused cache
docker builder prune --filter until=168h # cache untouched for 7 daysคำสั่งเหล่านี้ไม่มีผลกระทบต่ออิมเมจหรือข้อมูลของคุณ ต้นทุนเพียงอย่างเดียวของการล้าง build cache คือการสร้างอิมเมจครั้งถัดไปจะทำงานช้าลงหนึ่งครั้ง บน VPS ที่มีการสร้างอิมเมจใหม่เป็นประจำ Build Cache มักจะเป็นแถวที่ใหญ่ที่สุดใน docker system df ซึ่งทำให้มันเป็นสิ่งที่ใหญ่ที่สุดที่คุณสามารถลบได้อย่างปลอดภัยที่สุด
คำสั่ง prune เรียงลำดับจากปลอดภัยไปจนถึงทำลายข้อมูล
ให้ไล่ทำตามรายการนี้และหยุดทันทีที่ df -h / กลับมามีสถานะปกติ ทุกคำสั่งจะแสดงบรรทัด Total reclaimed space: เมื่อทำงานเสร็จสิ้น
docker container pruneจะลบคอนเทนเนอร์ที่หยุดทำงานแล้ว เลเยอร์ที่เขียนได้ (writable layers) ของคอนเทนเนอร์เหล่านั้นจะถูกลบไปด้วย ดังนั้นข้อมูลใดๆ ที่คอนเทนเนอร์เขียนไว้นอก volume จะถูกลบหายไปทั้งหมด แต่ volume จะไม่ได้รับผลกระทบdocker image pruneจะลบเฉพาะ image ที่ไม่มีการใช้งาน (dangling images) เท่านั้น นี่เป็นคำสั่งจัดการ image ที่ปลอดภัยที่สุดdocker builder pruneจะลบ build cache ที่ไม่มีการใช้งาน ผลกระทบคือการ build ครั้งถัดไปจะช้าลงหนึ่งครั้งdocker image prune -aจะลบทุก image ที่ไม่มีคอนเทนเนอร์ใดอ้างอิงถึง ผลกระทบคือคุณต้องทำการ pull หรือ build ใหม่ในภายหลังdocker system pruneจะทำงานเหมือนสามข้อแรกพร้อมกัน และเพิ่มการลบ network ที่ไม่ได้ใช้งานเข้าไปด้วยdocker volume pruneจะลบ anonymous volume ที่ไม่ได้ใช้งานdocker volume prune -aจะลบ volume ที่ไม่ได้ใช้งานทั้งหมดรวมถึง volume ที่ตั้งชื่อไว้ (named volumes) นี่คือคำสั่งที่จะทำให้ฐานข้อมูลถูกลบหายไป
docker system prune จะแสดงขอบเขตการทำงานของมันก่อนที่จะเริ่มประมวลผล
WARNING! This will remove:
- all stopped containers
- all networks not used by at least one container
- all dangling images
- unused build cache
Are you sure you want to continue? [y/N]รายการข้างต้นจงใจละเว้นเรื่อง volume ไว้ การเพิ่ม --volumes จะนำ anonymous volume กลับเข้ามาอยู่ในขอบเขตการทำงาน ส่วนการเพิ่ม -a จะขยายขอบเขตขั้นตอนการจัดการ image จากเดิมที่ลบเฉพาะ dangling images ให้กลายเป็นการลบทุก image ที่ไม่ได้ใช้งาน การรัน docker system prune -a --volumes -f แบบเต็มรูปแบบบนโฮสต์ที่ใช้งานจริง (production) คือสาเหตุที่ทำให้ผู้คนสูญเสียข้อมูลในขณะที่พยายามเพิ่มพื้นที่ว่างในดิสก์
เหตุใดการทำ pruning volume จึงลบฐานข้อมูลของคุณ
นี่คือส่วนที่คุณควรอ่านซ้ำสองรอบ
Volume จะถูกนับว่าไม่ได้ใช้งานเมื่อไม่มีคอนเทนเนอร์ใดเชื่อมต่ออยู่ นี่คือเกณฑ์การตัดสินเพียงอย่างเดียว Docker ไม่ได้ตรวจสอบว่า volume นั้นว่างเปล่าหรือไม่, ไฟล์ compose ยังคงประกาศใช้งานอยู่หรือไม่ หรือมันเป็นสำเนาชุดเดียวของฐานข้อมูลที่คุณมีอยู่หรือไม่ LINKS 0 ใน docker system df -v หมายถึงสิ่งที่สามารถลบได้ (prunable) และไม่มีความหมายอื่นแอบแฝง
ลองพิจารณาการกระทำปกติสองอย่างที่เกิดขึ้นต่อเนื่องกัน คุณรัน docker compose down เพื่อรีสตาร์ท stack อย่างสะอาดหมดจด คำสั่งนี้จะลบคอนเทนเนอร์ออกแต่ยังคงเก็บ named volume ไว้ ซึ่งเป็นไปตามเอกสารระบุไว้ทุกประการ ในตอนนี้ Postgres volume ของคุณจึงไม่ได้เชื่อมต่อกับสิ่งใด สิบนาทีต่อมาคุณรัน docker volume prune -a เพื่อคืนพื้นที่ว่าง และฐานข้อมูลของคุณก็หายไป ทั้งสองคำสั่งทำงานได้อย่างถูกต้องตามหน้าที่ของมัน แต่ลำดับการทำงานนี้ได้ทำลายข้อมูลของคุณไปแล้ว
ตั้งแต่ Docker Engine 23.0 (API version 1.42) เป็นต้นมา คำสั่งพื้นฐานนี้มีขอบเขตการทำงานที่แคบลงกว่าเดิม
WARNING! This will remove anonymous local volumes not used by at least one container.Anonymous volume คือ volume ที่ Docker สร้างขึ้นให้คุณ โดยปกติเกิดจากการที่ image ประกาศ VOLUME ไว้แล้วคุณไม่ได้ตั้งชื่อให้มัน ข้อมูลเหล่านี้มักเป็นข้อมูลที่คุณไม่ได้ต้องการเก็บรักษาไว้ ส่วน named volume ซึ่งเป็นประเภทที่คุณระบุไว้ในไฟล์ compose จะถูกลบก็ต่อเมื่อคุณเพิ่มแฟล็ก -a เท่านั้น Docker เวอร์ชันเก่าจะลบทั้งสองประเภทด้วยคำสั่งพื้นฐาน ดังนั้นอย่าเชื่อใจความเคยชินที่สร้างมาจากเครื่องที่คุณอัปเกรดไปแล้ว ความแตกต่างนี้จะชัดเจนขึ้นเมื่อคุณทราบว่า named volume แตกต่างจาก bind mount อย่างไร เพราะ bind mount ไม่ใช่ Docker volume และไม่มีคำสั่ง prune ใดที่จะไปแตะต้องมันได้
ตรวจสอบให้ดีก่อนลบ คุณสามารถแทนที่ myapp_pgdata ด้วยชื่อ volume ที่คุณกำลังตรวจสอบอยู่
docker volume ls -f dangling=true
docker volume inspect myapp_pgdata
sudo ls -la /var/lib/docker/volumes/myapp_pgdata/_dataตัวกรอง dangling=true บน volume หมายถึง volume ที่ไม่มีการอ้างอิง ไม่ได้หมายถึง volume ที่ว่างเปล่า การแสดงรายการ _data จะช่วยให้คุณเห็นสิ่งที่อยู่ภายในจริงๆ หากคุณพบไดเรกทอรี pgdata หรือ mysql อยู่ในนั้น ให้หยุดและสำรองข้อมูลก่อนที่จะดำเนินการใดๆ ต่อไป การทำลายข้อมูลในลักษณะเดียวกันสามารถเกิดขึ้นได้ผ่าน docker compose down -v ซึ่งจะลบทุก volume ที่ไฟล์ compose ประกาศไว้โดยไม่ถามคุณก่อน
Volume คือสิ่งเดียวบน Docker host ที่การ rebuild ไม่สามารถสร้างขึ้นใหม่ได้ นี่คือเหตุผลที่ข้อมูลใน volume ควรถูกเก็บไว้ใน การสำรองข้อมูลด้วย restic ที่ทำงานอยู่นอกเซิร์ฟเวอร์ ซึ่งเป็นที่ที่การพิมพ์แฟล็กผิดพลาดไม่สามารถส่งผลกระทบไปถึงได้
เมื่อไม่มีสิ่งใดถูกลบออก: ไฟล์ log ของ container
คุณได้ลบทุกอย่างที่ทำได้แล้ว docker system df แสดงว่าแทบไม่มีพื้นที่ให้เรียกคืน แต่ดิสก์ยังคงเต็มอยู่ ให้ตรวจสอบไฟล์ log
sudo find /var/lib/docker/containers -name '*-json.log' -exec du -h {} + | sort -h | tail -10ทุก container จะเขียน standard output และ standard error ลงในไฟล์ JSON ภายใต้ /var/lib/docker/containers/ ในการติดตั้งแบบปกติ max-size จะไม่ได้ถูกตั้งค่าไว้ ซึ่งหมายความว่าไม่มีการจำกัดขนาด ดังนั้น container ตัวเดียวที่ติดอยู่ใน crash loop อาจเขียนข้อมูลจนเต็มพาร์ทิชันได้ ไม่มีคำสั่ง prune ใดที่ลบไฟล์เหล่านี้ได้ เนื่องจาก container ที่สร้างไฟล์เหล่านั้นยังคงทำงานอยู่ ซึ่งตามนิยามแล้วถือว่าไม่สามารถลบได้
ห้ามลบไฟล์ดังกล่าว การรัน rm บนไฟล์ log ที่กำลังเปิดใช้งานอยู่จะไม่ช่วยเพิ่มพื้นที่ว่าง เพราะ Docker daemon ยังคงถือ file descriptor ที่เปิดค้างไว้อยู่ และ kernel จะยังคงจัดสรร block เหล่านั้นไว้จนกว่า handle ดังกล่าวจะถูกปิด df จะไม่ขยับเลย ให้ใช้วิธี truncate แทน ซึ่งจะยังคง inode เดิมไว้และช่วยให้ daemon เขียนข้อมูลต่อไปได้
sudo find /var/lib/docker/containers -name '*-json.log' -exec truncate -s 0 {} +
df -h /นั่นเป็นเพียงการแก้ไขชั่วคราว docker logs สำหรับ container เหล่านั้นจะแสดงผลเป็นค่าว่าง และไฟล์จะเริ่มขยายขนาดขึ้นอีกครั้งทันที การแก้ไขที่แท้จริงคือการทำ rotation ซึ่งจะอธิบายในส่วนถัดไป
วัดผลก่อนและหลังทุกครั้ง
อย่าคาดเดาผลลัพธ์จากการสั่ง prune ให้ทำการวัดค่าก่อน สั่งคำสั่งหนึ่งครั้ง แล้วจึงวัดค่าอีกครั้ง
df -h /
docker system df
docker builder prune -f --filter until=168h
docker image prune -f
docker system df
df -h /เปรียบเทียบผลลัพธ์จาก df ทั้งสองครั้ง ตัวเลขนี้เป็นค่าเดียวที่ใช้ตัดสินว่าเซิร์ฟเวอร์ของคุณจะยังคงให้บริการต่อไปได้หรือไม่ จากนั้น docker system df จะบอกคุณว่าแถวข้อมูลใดที่ถูกย้ายจริง และการสั่ง prune แต่ละครั้งจะแสดงค่า Total reclaimed space: ของตัวเองออกมา
หาก df ไม่มีการเปลี่ยนแปลงแต่ docker system df ระบุว่ามีการคืนพื้นที่ว่าง แสดงว่ามี file handle ที่เปิดค้างไว้กำลังถือครองบล็อกข้อมูลที่ถูกลบไปแล้ว ซึ่งเป็นปัญหาเดียวกับปัญหาไฟล์ log ที่กล่าวถึงข้างต้น หากทั้งสองค่ามีการเปลี่ยนแปลงแต่ดิสก์กลับเต็มอีกครั้งภายในหนึ่งวัน แสดงว่าคุณกำลังประสบปัญหาการเติบโตของข้อมูลมากกว่าปัญหาการทำความสะอาด ซึ่งวิธีแก้ไขคือการทำ rotation ร่วมกับการตั้งตารางงาน (scheduled job)
วิธีป้องกันไม่ให้ดิสก์เต็มอีกครั้ง
จำกัดขนาดของ log สร้างหรือแก้ไขไฟล์ /etc/docker/daemon.json
{
"log-driver": "json-file",
"log-opts": {
"max-size": "10m",
"max-file": "3"
}
}การตั้งค่านี้จะจำกัดขนาด log ของแต่ละคอนเทนเนอร์ไว้ที่ 30 MB ค่าทุกค่าภายใต้ log-opts จะต้องอยู่ในรูปแบบสตริง รวมถึงค่าที่เป็นตัวเลขด้วย ให้ตรวจสอบว่าไฟล์มีรูปแบบที่ถูกต้องก่อนเริ่มการทำงานใหม่ เพราะหากไฟล์ daemon.json มีรูปแบบผิดพลาด จะทำให้ daemon ไม่สามารถเริ่มทำงานได้และส่งผลให้คอนเทนเนอร์ทั้งหมดหยุดทำงานตามไปด้วย
python3 -m json.tool /etc/docker/daemon.json
sudo systemctl restart docker
docker info | grep -i 'logging driver'ตอนนี้ docker info ควรแสดงผลเป็น Logging Driver: json-file โดยขีดจำกัดเหล่านี้จะปรากฏในส่วน LogConfig ของ docker inspect สำหรับคอนเทนเนอร์ที่สร้างขึ้นหลังจากเริ่มการทำงานใหม่ ซึ่งเป็นรายละเอียดที่สำคัญ: การตั้งค่านี้จะมีผลกับคอนเทนเนอร์ใหม่เท่านั้น คอนเทนเนอร์ที่มีอยู่เดิมจะยังคงใช้การตั้งค่าที่ถูกกำหนดไว้ตอนสร้าง ดังนั้นคุณต้องสร้างคอนเทนเนอร์เหล่านั้นขึ้นมาใหม่
docker compose up -d --force-recreateคุณสามารถกำหนดขีดจำกัดเดียวกันนี้แยกตามบริการในไฟล์ compose ได้ ซึ่งเป็นทางเลือกที่ดีกว่าในกรณีที่มีบริการที่สร้าง log จำนวนมากและต้องการกำหนดค่าเฉพาะตัว
services:
app:
logging:
driver: json-file
options:
max-size: "10m"
max-file: "3"กำหนดเวลาการล้างข้อมูลแบบจำกัดขอบเขต ให้ทำเป็นรายสัปดาห์ โดยเน้นไปที่ dangling images และ build cache เก่า ห้ามใส่คำสั่ง -a หรือ --volumes ลงในงานที่ตั้งเวลาไว้โดยเด็ดขาด เพราะหากงานดังกล่าวทำงานในขณะที่ stack กำลังหยุดทำงาน ระบบจะลบ image ของ stack นั้นทิ้ง และด้วยคำสั่ง --volumes ระบบจะเริ่มทำงานโดยใช้ข้อมูลของคุณทันที
sudo tee /etc/cron.weekly/docker-prune >/dev/null <<'EOF'
#!/bin/sh
docker image prune -f
docker builder prune -f --filter until=168h
EOF
sudo chmod +x /etc/cron.weekly/docker-prune
sudo /etc/cron.weekly/docker-pruneบรรทัดสุดท้ายนั้นเป็นการรันสคริปต์ด้วยตนเองหนึ่งครั้งเพื่อให้คุณเห็นผลลัพธ์ก่อนที่จะปล่อยให้มันทำงานโดยอัตโนมัติ ไฟล์ดังกล่าวจะต้องสามารถ execute ได้ และชื่อไฟล์ต้องไม่มีจุด เพราะ run-parts จะข้ามไฟล์ที่ไม่สามารถ execute ได้และไฟล์ที่มีนามสกุลทั้งหมด
ตั้งการแจ้งเตือนเมื่อพื้นที่ว่างเหลือน้อย การล้างข้อมูลหลังจากดิสก์เต็มแล้วถือเป็นการกู้คืนระบบ แต่การแจ้งเตือนที่ระดับ 80 เปอร์เซ็นต์ถือเป็นการป้องกัน
usage=$(df --output=pcent / | tr -dc '0-9')
[ "$usage" -ge 80 ] && echo "root filesystem at ${usage} percent full"ให้นำคำสั่งนั้นไปใส่ใน cron ร่วมกับระบบแจ้งเตือนที่คุณใช้งานอยู่ พื้นที่ว่างเป็นเพียงครึ่งหนึ่งของภาพรวมเท่านั้น ดังนั้นควรใช้งานการแจ้งเตือนนี้ควบคู่ไปกับ การตรวจสอบสุขภาพของดิสก์บน VPS เพราะทั้งดิสก์ที่กำลังจะเสียและดิสก์ที่เต็มต่างก็ทำให้คอนเทนเนอร์ของคุณหยุดทำงาน แต่ทั้งสองกรณีต้องการวิธีแก้ไขที่แตกต่างกัน
เนื้อหาทั้งหมดข้างต้นตั้งอยู่บนสมมติฐานว่าเป็นการติดตั้งแบบมาตรฐานที่มี data root อยู่ที่ /var/lib/docker หากคุณย้ายตำแหน่งด้วยคีย์ data-root ในไฟล์ daemon.json ให้ใช้ path ของคุณแทนในทุกคำสั่ง การวางโครงสร้างให้ถูกต้องตั้งแต่ตอนติดตั้งบนเครื่องใหม่เป็นส่วนหนึ่งของ การตั้งค่า Docker บน VPS และการตัดสินใจเรื่องนี้ก่อนที่จะมีคอนเทนเนอร์ขนาด 40 GB อยู่ใน partition ที่ไม่ถูกต้องนั้นทำได้ง่ายกว่ามาก
FAQ
docker system prune จะลบ volume ของฉันหรือไม่?
ไม่ คำสั่งปกติจะลบเฉพาะคอนเทนเนอร์ที่หยุดทำงาน, เน็ตเวิร์กที่ไม่ได้ใช้งาน, อิมเมจที่ไม่มีแท็ก (dangling images) และแคชการ build ที่ไม่ได้ใช้งาน โดยจะมีข้อความยืนยันแสดงรายการสิ่งที่จะถูกลบให้ทราบ Volume จะถูกนำมาพิจารณาก็ต่อเมื่อคุณเพิ่ม --volumes เข้าไป และตั้งแต่ Docker Engine 23.0 เป็นต้นมา แฟล็กดังกล่าวจะครอบคลุมเฉพาะ anonymous volumes เท่านั้น ไม่รวมถึง named volumes ซึ่ง named volumes จะถูกลบด้วย docker volume prune -a และ docker compose down -v เท่านั้น คุณควรระมัดระวังการใช้คำสั่งทั้งสองนี้
ทำไมดิสก์ยังเต็มอยู่หลังจากรัน docker prune?
มีสาเหตุหลักสองประการ ประการแรกคือไฟล์ log ของคอนเทนเนอร์ภายใต้ /var/lib/docker/containers/ ซึ่งคำสั่ง prune ไม่ได้เข้าไปจัดการและไฟล์เหล่านี้จะขยายขนาดขึ้นเรื่อยๆ จนกว่าคุณจะตั้งค่า max-size ประการที่สองคือไฟล์ที่ถูกลบไปแล้วแต่ยังมีโพรเซสเปิดใช้งานอยู่ หากคุณลบ log ด้วย rm ในขณะที่คอนเทนเนอร์ยังทำงานอยู่ daemon จะยังคงถือ file descriptor นั้นไว้และ kernel จะไม่คืนพื้นที่บล็อกข้อมูล ทำให้ df รายงานว่าพื้นที่ว่างไม่เปลี่ยนแปลง ให้เปรียบเทียบผลลัพธ์ระหว่าง sudo du -xh --max-depth=1 /var/lib/docker กับ docker system df เพื่อตรวจสอบว่าคุณกำลังเจอปัญหาในกรณีใด
docker image prune กับ docker image prune -a ต่างกันอย่างไร?
คำสั่งปกติจะลบเฉพาะ dangling images เท่านั้น ซึ่งหมายถึงอิมเมจที่ไม่มีแท็กกำกับ (มักเกิดจากการ build ใหม่) ส่วนรูปแบบ -a จะลบทุกอิมเมจที่ไม่มีคอนเทนเนอร์ใดใช้งานอยู่ รวมถึงอิมเมจที่มีแท็กที่คุณดึงมา (pull) ด้วยตัวเอง หลังจากรัน docker compose down คอนเทนเนอร์จะหายไป ดังนั้น -a จะลบอิมเมจของ stack นั้นออกไปด้วย ข้อมูลจะไม่สูญหายถาวรเพราะการเริ่มทำงานครั้งถัดไปจะดึงหรือ build อิมเมจเหล่านั้นใหม่ แต่หากการเชื่อมต่อเครือข่ายช้าอาจต้องใช้เวลารอนาน
ฉันจะป้องกันไม่ให้ Docker logs ทำให้ดิสก์เต็มได้อย่างไร?
ให้ตั้งค่า max-size และ max-file ภายใต้ log-opts ในไฟล์ /etc/docker/daemon.json จากนั้นรีสตาร์ท daemon ด้วย sudo systemctl restart docker การตั้งค่านี้จะมีผลเฉพาะกับคอนเทนเนอร์ที่สร้างขึ้นใหม่หลังจากรีสตาร์ทเท่านั้น ดังนั้นคุณต้องสร้างคอนเทนเนอร์ที่กำลังทำงานอยู่ขึ้นมาใหม่ด้วย docker compose up -d --force-recreate คุณสามารถตั้งค่าสองตัวเลือกนี้แยกตามบริการในไฟล์ compose ภายใต้คีย์ logging ซึ่งเป็นวิธีที่เหมาะสมหากมีบริการใดบริการหนึ่งสร้าง log มากกว่าบริการอื่นอย่างเห็นได้ชัด
การรัน docker system prune ใน cron job ปลอดภัยหรือไม่?
คำสั่ง docker system prune -f แบบปกติมีความปลอดภัยบนโฮสต์ที่ทุก stack ทำงานอยู่ตลอดเวลา แต่เนื่องจากมันจะลบคอนเทนเนอร์ที่หยุดทำงานไปแล้ว มันจึงอาจลบคอนเทนเนอร์ที่คุณตั้งใจหยุดไว้เพื่อรอเริ่มใหม่ในภายหลังได้ งานที่ปลอดภัยกว่าสำหรับการตั้งเวลาคือการใช้ docker image prune -f ร่วมกับ docker builder prune -f --filter until=168h ซึ่งจะช่วยคืนพื้นที่ในส่วนที่ขยายขนาดเร็วที่สุดและไม่ส่งผลกระทบต่อ volume ห้ามตั้งเวลาการทำงานของ -a หรือ --volumes โดยเด็ดขาด