Docker Image Layers และ Build Cache
บทนี้จะพาคุณเข้าใจเชิงลึกว่า Layer และ Build Cache ทำงานอย่างไร ทำไมลำดับคำสั่งใน Dockerfile จึงมีผลต่อความเร็ว build และวิธีจัดการ cache/ภาพค้างอย่างปลอดภัย
ภาพจำ: image คือชั้นข้อมูลแบบ immutable ที่นำมาซ้อนกัน และ container จะมี writable layer เพิ่มด้านบนตอน runtime
ภาพรวมบทนี้
ถ้าจัดลำดับ layer ดี คุณจะ build เร็วขึ้น ประหยัด disk และลดเวลารอใน CI ได้แบบเห็นผล
ภาพจำ: แก้ไฟล์ที่กระทบ layer ท้าย ๆ จะรักษา cache ของ dependency layer ได้ดีที่สุด
ส่วนที่ 1
Step 1: Layer คืออะไร และ Union Filesystem (overlay2) ทำงานอย่างไร
ใน Docker แต่ละคำสั่งสำคัญใน Dockerfile เช่น FROM, RUN, COPY จะสร้าง layer ใหม่ที่เป็น read-only และ immutable เมื่อรัน container Docker จะเพิ่ม writable layer ด้านบนสุดแล้วใช้ copy-on-write: ไฟล์ที่แก้จะถูกคัดลอกขึ้น writable layer ก่อน โดยไม่แตะ layer เดิม ด้านล่างทั้งหมด
ดูคำสั่งแต่ละบรรทัดเป็นชั้นข้อมูลที่แยกกันชัดเจน
- Layer ของ image เป็น read-only และแชร์ข้าม image/container ได้
- Container มี writable layer ของตัวเองด้านบนสุด
- overlay2 รวมหลาย layer ให้มองเป็น filesystem เดียวตอน runtime
ส่วนที่ 2
Step 2: ทำไม Layer ถึงสำคัญ (Layer Sharing)
Layer sharing ทำให้หลาย image ใช้ layer เดียวกันได้ เช่น image จำนวนมากที่ใช้ base `ubuntu:22.04` จะ reuse base layer เดิมในเครื่องเดียวกัน จึงไม่ต้องดาวน์โหลดซ้ำทุกครั้ง ช่วยประหยัดทั้ง disk และ network
| สถานการณ์ | ถ้าไม่มี Layer Sharing | เมื่อมี Layer Sharing |
|---|---|---|
| มี 10 image ใช้ ubuntu:22.04 เหมือนกัน | เก็บ base ซ้ำ 10 ชุด | เก็บ base ชุดเดียวแล้วแชร์ร่วมกัน |
| เครื่องใหม่ pull image กลุ่มเดียวกัน | download ซ้ำทุก image | download เฉพาะ layer ที่ยังไม่มี |
| CI runner build หลายโปรเจกต์ | ใช้ disk สูงและช้า | cache อุ่นขึ้นและ build เร็วขึ้น |
- base image ที่ใช้ร่วมกันคือจุดประหยัดที่ใหญ่ที่สุด
- ทีมควรกำหนด base image strategy ให้สอดคล้องกัน
ส่วนที่ 3
Step 3: Build Cache คืออะไร และ Cache Invalidation เกิดเมื่อไร
Docker จะเก็บผลลัพธ์ของแต่ละ layer ระหว่าง build ถ้าคำสั่งและบริบทของ layer นั้นเหมือนเดิม Docker จะใช้ cache ทันที (cache hit) ถ้ามีอะไรเปลี่ยนในเงื่อนไขที่ใช้สร้าง layer จะเกิด cache miss และ build ใหม่ตั้งแต่ layer นั้นลงไป
Build ครั้งแรกจะสร้างทุก layer ส่วนครั้งถัดไปจะเห็น CACHED ในหลายขั้นตอน
สังเกตว่าเมื่อแก้ source code เฉพาะบางส่วน Docker จะ rebuild ตั้งแต่ COPY ที่เกี่ยวข้องลงไป
| เหตุการณ์ | ผลต่อ cache |
|---|---|
| เปลี่ยนคำสั่ง RUN ใน Dockerfile | invalidate layer นั้นและทุก layer ถัดไป |
| เปลี่ยนไฟล์ที่ถูก COPY ในขั้นหนึ่ง | invalidate ตั้งแต่ COPY ขั้นนั้นลงไป |
| ไม่เปลี่ยน Dockerfile และไฟล์ที่เกี่ยวข้อง | cache hit เกือบทั้งหมด |
ส่วนที่ 4
Step 4: Best Practices การจัดเรียง Layer (Dockerfile ดี vs ไม่ดี)
หลักสำคัญคือวางสิ่งที่เปลี่ยนน้อยไว้ก่อน (เพื่อให้ cache อยู่ได้นาน) และวางสิ่งที่เปลี่ยนบ่อยไว้ท้าย ๆ โดยเฉพาะ source code วิธีนี้ช่วยลดเวลารอ build อย่างชัดเจน
แยก COPY lockfile ก่อนเพื่อให้ dependency layer cache ได้นานขึ้น
COPY ทั้งโปรเจกต์ก่อน install dependency ทำให้แก้ไฟล์เล็กน้อยก็ต้องติดตั้งใหม่
| สถานการณ์ | Dockerfile ไม่ดี | Dockerfile ที่ดี |
|---|---|---|
| แก้ไฟล์ src 1 ไฟล์ | npm ci รันใหม่ | ข้าม npm ci ได้ (cache hit) |
| เวลารวม build รอบถัดไป | สูงกว่า | ต่ำกว่าชัดเจน |
ส่วนที่ 5
Step 5: การจัดการ Cache ขั้นสูง
บางสถานการณ์ต้องบังคับไม่ใช้ cache หรือดึง cache จาก image ก่อนหน้า และสำหรับ BuildKit เราสามารถใช้ cache mounts ช่วยลดเวลางานที่ต้องดาวน์โหลดซ้ำ เช่น package manager cache
ใช้แต่ละตัวตามเป้าหมาย: บังคับ build ใหม่, reuse cache จาก image เดิม, หรือเร่งความเร็ว dependency download
ใช้ --no-cache เมื่อสงสัยว่า cache เดิมทำให้ผลลัพธ์ผิด
ใช้ --cache-from ใน CI เพื่อดึง cache จาก image ก่อนหน้า
ใช้ BuildKit cache mounts เพื่อลดเวลางาน download dependency ซ้ำ
ส่วนที่ 6
Step 6: Dangling Images คืออะไร และลบอย่างปลอดภัย
Dangling images คือ image ที่ไม่มี tag อ้างอิงแล้ว (มักเห็นเป็น `<none>:<none>`) เกิดจากการ build/re-tag หลายรอบ ถ้าไม่ลบจะกิน disk เพิ่มเรื่อย ๆ แต่ต้องระวังคำสั่ง prune เพราะอาจลบทรัพยากรที่ยังต้องใช้
เริ่มจาก prune แบบแคบก่อน แล้วค่อยขยายเป็น system prune เมื่อมั่นใจผลกระทบ
- ก่อน prune ควรตรวจ `docker images` และ `docker ps -a` ก่อนเสมอ
- ใน production/runner สำคัญ ควรมีนโยบาย retention และ backup ที่ชัดเจน