ข้าม​ไป​ยัง​เนื้อหา

เติม​คอม​เมน​ต์​บรรทัด​เดียว​ใน .dockerignore แล้ว​เสีย layer เท่ากับ​แก้ source ทั้ง file

คุณ​เปิด .dockerignore แล้ว​เติม​บรรทัด​เดียว​เข้าไป เป็น​คอม​เมน​ต์ ไม่​ได้​ตัด file ไหน​เพิ่ม

docker build รอบ​ถัด​ไป​เหลือ​ขั้น​ที่​ใช้​ของ​เก่า​ได้ 2 ขั้น จาก 6 ขั้น

ซึ่ง​เท่ากับ​ตอน​ที่​คุณ​แก้ source หนึ่ง​ไบต์​เป๊ะ

หน้า​นี้​เอา Dockerfile สอง​ใบ​ที่​ต่าง​กัน​แค่​ลำดับ​ของ COPY ไป​เจอ​การ​แก้ 7 แบบ แล้ว​นับ​ว่า​เหลือ​กี่​ขั้น​ที่ BuildKit บอก​ว่า CACHED

ธี​สิ​ส​ของ​หน้า​นี้​อยู่​ตรง​นี้ — สิ่ง​ที่​ตัดสิน​ว่า cache หลุด​หรือ​ไม่ ไม่ใช่​ว่า​คุณ​แก้​อะไร แต่​เป็น​ว่า​สิ่ง​ที่​คุณ​แก้ อยู่​ใน​ขอบเขต​ที่​ขั้น​นั้น​มอง​เห็น​หรือ​เปล่า และ​ขอบเขต​นั้น​กว้าง​กว่า ที่​คน​ส่วน​ใหญ่​คิด มัน​รวม file ที่​ไม่มี​ใคร​อ่าน และ​รวม​ตัว .dockerignore เอง

ใบ​หนึ่ง​คัด​ลอก​ทุก​อย่าง​ก่อน อีก​ใบ​คัด​ลอก​ประกาศ dependency ก่อน

naive
FROM alpine:3.22
WORKDIR /app
COPY marker.txt .
COPY . .
RUN cat deps.txt > /app/.deps-installed
RUN cat src/main.txt > /app/.built
ordered
FROM alpine:3.22
WORKDIR /app
COPY marker.txt .
COPY deps.txt .
RUN cat deps.txt > /app/.deps-installed
COPY . .
RUN cat src/main.txt > /app/.built

RUN สอง​บรรทัด​ใน​นี้​ไม่​ได้​ลง package จริง มัน​แค่​อ่าน file แล้ว​เขียน​ผล​ลง image เพราะ​สิ่ง​ที่​หน้า​นี้​วัด​คือ ขั้น​ไหน​ถูก​ทำ​ใหม่ ไม่ใช่​ว่า​ขั้น​นั้น​ใช้​เวลา​เท่าไร

marker.txt ไม่​ได้​อยู่​ใน​เรื่อง มัน​เป็น​ตัว​แยก cache ของ​แต่ละ​ทรง​ออก​จาก​กัน เหตุผล​อยู่​ใน​หัวข้อ​สุดท้าย และ​มัน​เป็น​กับ​ดัก​ที่​กิน​เวลา​ไป​หนึ่ง​รอบ

วัด​จาก​อะไร

ทุก​ค่า​ใน​หน้า​นี้​มา​จาก scripts/docker-cache/spike.py ที่​สั่ง docker build จริง บน Docker 29.4.0 กับ base image alpine:3.22

แต่ละ​ทรง build สอง​ครั้ง · ครั้ง​แรก --no-cache เพื่อ​ให้​จุด​ตั้ง​ต้น​เท่า​กัน แล้ว​แก้​หนึ่ง​อย่าง แล้ว build อีก​ครั้ง

หน่วย​ของ​หลัก​ฐาน​ทั้ง​หน้า​คือ​จำนวน​ขั้น​ที่ BuildKit รายงาน​ว่า CACHED จำนวน​ขั้น​ทั้งหมด และ​ค่า boolean ว่า layer เป็น​ชุด​เดิม​ไหม ไม่มี​หน่วย​เวลา​สัก​ช่อง · probe รัน​ชุด​เดิม 2 รอบ​ต่อ​การ​เรียก​หนึ่ง​ครั้ง และ​แดง​ถ้า​มี​ค่า​ไหน​ขยับ

เปลี่ยน​เวลา​แก้ไข​ของ file ที่​ถูก COPY โดย​ไม่​แตะ​เนื้อ​ใน​สัก​ไบต์

การ​แก้naive · ขั้น​ที่ CACHEDordered · ขั้น​ที่ CACHED
ไม่​แก้​อะไร​เลย5 จาก 66 จาก 7
touch อย่าง​เดียว5 จาก 66 จาก 7

สอง​แถว​นี้​ให้​เลข​เดียวกัน และ layer ที่​ได้​เป็น​ชุด​เดิม​ทั้ง​คู่

BuildKit คิด cache key ของ COPY จาก​เนื้อ​ใน​ของ file ไม่​ได้​คิด​จาก​เวลา​แก้ไข · การ touch เพื่อ​บังคับ​ให้ build ใหม่ จึง​ไม่​ทำ​อะไร​เลย

ขั้น​ที่​ไม่​เคย​ขึ้น​ว่า CACHED คือ​ขั้น FROM ซึ่ง​เป็นการ​ดึง base image ทุก​แถว​ใน​หน้า​นี้​จึง​เทียบ​กับ​เพดาน​ที่ 6 ลบ​หนึ่ง สำหรับ naive

แก้ source หนึ่ง​ไบต์ แล้ว​ลำดับ​ของ COPY ตัดสิน​ว่า​เหลือ​กี่​ขั้น

หัวข้อ​ที่​มีชื่อ​ว่า “แก้ source หนึ่ง​ไบต์ แล้ว​ลำดับ​ของ COPY ตัดสิน​ว่า​เหลือ​กี่​ขั้น”

Dockerfile สอง​ใบ​นี้​สร้าง image ที่​ทำงาน​เหมือน​กัน แต่​ราคา​ของ​การ​แก้​ต่าง​กัน

การ​แก้naiveordered
แก้ source หนึ่ง​ไบต์2 จาก 64 จาก 7
เติม​หนึ่ง​บรรทัด​ใน deps.txt2 จาก 62 จาก 7

แถว​แรก​คือ​เหตุผล​ทั้งหมด​ที่​คน​แยก COPY ออก​เป็น​สอง​ครั้ง · ordered เก็บ​ขั้น​ที่​ลง dependency ไว้​ได้ ส่วน naive ทิ้ง​มัน​ทุก​ครั้ง​ที่​แก้ source

แถว​ที่​สอง​คือ​ราคา​ที่ ordered ไม่​ได้​ลด · เมื่อ​ประกาศ dependency เปลี่ยน​เอง ทั้ง​สอง​ใบ​เหลือ​เท่า​กัน เพราะ​ขั้น​ที่​ลง dependency ต้อง​ถูก​ทำ​ใหม่​จริง ๆ

ลำดับ​ของ COPY ไม่​ได้​ทำให้ cache ดี​ขึ้น มัน​ย้าย​ว่า​อะไร​จะ​พัง และ​มัน​คุ้ม​เพราะ source เปลี่ยน​บ่อย​กว่า​ประกาศ dependency มาก

file ที่​ไม่มี​ขั้น​ไหน​อ่าน ก็​ทำให้ cache หลุด​เท่า​กัน

หัวข้อ​ที่​มีชื่อ​ว่า “file ที่​ไม่มี​ขั้น​ไหน​อ่าน ก็​ทำให้ cache หลุด​เท่า​กัน”

เพิ่ม NOTES.md เข้า​มา​หนึ่ง​ใบ ไม่มี RUN บรรทัด​ไหน​แตะ​มัน​เลย

การ​แก้naiveordered
แก้ source หนึ่ง​ไบต์24
เพิ่ม file ที่​ไม่มี​ใคร​อ่าน24
เพิ่ม file นั้น โดย​มี .dockerignore ตัด​ไว้56

สอง​แถว​แรก​ให้​เลข​เดียวกัน · COPY . . ไม่รู้​ว่า​ใคร​อ่าน file ไหน มัน​เห็น​แค่​ว่า ชุด file ที่​ต้อง​คัด​ลอก​เปลี่ยน​ไป

แถว​ที่​สาม​คือ​ทาง​แก้ · .dockerignore ตัด file นั้น​ออก​จาก​สิ่ง​ที่ COPY . . มอง​เห็น cache จึง​รอด​ทั้ง​ใบ และ layer ที่​ได้​เป็น​ชุด​เดิม

นี่​คือ​เหตุผล​ที่ .dockerignore เป็น​เรื่อง​ของ​ความเร็ว ไม่ใช่​แค่​เรื่อง​ขนาด image

เติม​บรรทัด​ที่​ขึ้น​ต้น​ด้วย # ซึ่ง​ไม่​ตัด file ไหน​เพิ่ม​สัก​ใบ

การ​แก้naiveordered
แก้ source หนึ่ง​ไบต์24
เติม​คอม​เมน​ต์​ใน .dockerignore24

สอง​แถว​นี้​ให้​เลข​เดียวกัน​ทั้ง​สอง Dockerfile

ชุด file ที่​ถูก​คัด​ลอก​ไม่​เปลี่ยน​แม้แต่​ใบ​เดียว สิ่ง​ที่​เปลี่ยน​คือ​เนื้อ​ใน​ของ .dockerignore ซึ่ง​เข้าไป​อยู่​ใน cache key ของ​ขั้น​ที่​คัด​ลอก​ทั้ง context

ผู้​เขียน probe ทำนาย​ไว้​ก่อน​วัด​ว่า cache จะ​ไม่​หลุด และ​คำ​ทำนาย​นั้น​ผิด · คำ​ทำนาย​อยู่​ที่ scripts/docker-cache/README-predictions.md พร้อม​ข้อ​ที่​ถูก​หัก​ล้าง

ผล​ใน​ทาง​ปฏิบัติ​คือ การ​จัด​ระเบียบ .dockerignore มี​ราคา และ​มัน​เป็น​ราคา​เดียว​กับ การ​แก้ code ไม่ใช่​ราคา​ของ​การ​แก้​คอม​เมน​ต์

build แรก​ของ​ทุก​ทรง​ใน​หน้า​นี้​ใช้​ธง​นั้น และ​มัน​ยัง​เหลือ​หนึ่ง​ขั้น​ที่ CACHED

ทรงขั้น​ทั้งหมดขั้น​ที่ CACHED ตอน​ใส่ --no-cache
naive61
ordered71

ขั้น​นั้น​คือ WORKDIR /app ซึ่ง​ไม่​ได้​แตะ file สัก​ใบ มัน​เปลี่ยน​แค่ metadata ของ image

เรื่อง​เดียวกัน​นี้​เป็น​กับ​ดัก​ที่​กิน​เวลา​ของ probe ไป​หนึ่ง​รอบ · ร่าง​แรก​ใช้ LABEL ที่​ต่าง​กัน​ต่อ​ทรง เพื่อ​แยก cache ของ​แต่ละ​ทรง​ออก​จาก​กัน

มัน​แยก​ไม่​ได้​เลย เพราะ​ขั้น​ของ metadata ไม่​ได้​เปลี่ยน cache key ของ​ขั้น​ที่​แตะ file ข้าง​หลัง​มัน · และ cache ของ BuildKit อยู่​ข้าม​การ​รัน ผล​ที่​ได้​จึง​ขึ้น​กับ​ว่า​รอบ​ก่อน build อะไร​ไว้ ไม่​ได้​ขึ้น​กับ​สิ่ง​ที่​แก้

รูป​ร่าง​ของ​ความ​พัง​นี้ อ่าน​เหมือน​ผลลัพธ์​ที่​ถูก​ต้อง

ตอน​ที่​วิธี​วัด​ยัง​พัง ทุก​ทรง​พิมพ์​ตัวเลข​เดียวกัน​หมด

ซึ่ง​อ่าน​ได้​ว่า “การ​แก้​แบบ​ไหน​ก็​ให้​ผล​เท่า​กัน” ซึ่ง​เป็น​ข้อ​สรุป​ที่​ฟัง​ขึ้น แทนที่​จะ​อ่าน​ว่า “ตัว​แยก​ไม่​ทำงาน”

ตัว​แยก​ที่​ใช้ได้​จริง​ต้อง​เป็น file ที่​ถูก COPY และ​มี​ค่า​ไม่​ซ้ำ​ต่อ​การ​วัด​หนึ่ง​ครั้ง · ไม่ใช่​การ​ไป​ลบ cache ของ​เครื่อง​ผู้​อ่าน

ขั้น​ที่​ยัง​ใช้​ของ​เก่า​ได้ หลัง​การ​แก้​แต่ละ​แบบ

การ​แก้naive · จาก 6 ขั้นordered · จาก 7 ขั้น
ไม่​แก้​อะไร​เลย56
touch source โดย​ไม่​แตะ​เนื้อ​ใน56
แก้ source หนึ่ง​ไบต์24
เติม​หนึ่ง​บรรทัด​ใน deps.txt22
เพิ่ม file ที่​ไม่มี​ขั้น​ไหน​อ่าน24
เพิ่ม file นั้น โดย​มี .dockerignore ตัด​ไว้56
เติม​คอม​เมน​ต์​ใน .dockerignore24
สิ่ง​ที่​ควร​จำ​จาก​หน้า​นี้

หนึ่ง touch ไม่​ทำให้ cache หลุด · BuildKit ดู​เนื้อ​ใน​ของ file ไม่​ได้​ดู​เวลา​แก้ไข

สอง ลำดับ​ของ COPY ไม่​ได้​ทำให้ cache ดี​ขึ้น มัน​ย้าย​ว่า​อะไร​จะ​พัง และ​ย้าย​ไป​อยู่​กับ​สิ่ง​ที่​เปลี่ยน​ไม่​บ่อย

สาม COPY . . เห็น​ทุก file ที่​ไม่​ได้​ถูก​ตัด รวม file ที่​ไม่มี​ขั้น​ไหน​อ่าน

สี่ .dockerignore ปิด​รู​นั้น แต่​การ​แก้ตัว​มัน​เอง ราคา​เท่ากับ​การ​แก้ code

เช็กความเข้าใจ

ข้อ 1 / 3

สั่ง touch ใส่ file ที่ถูก COPY เข้า image โดยไม่แก้เนื้อในเลย แล้ว docker build ใหม่ ผลเป็นอย่างไร

ทุก​ค่า​ใน​หน้า​นี้​สร้าง​ใหม่​ได้​จาก​คำ​สั่ง​สอง​บรรทัด

Terminal window
scripts/docker-cache/up.sh # ดึง base image ที่ตรึงไว้ · ต้องใช้เน็ตรอบแรก
npm run verify:docker-cache # วัด 14 ทรง แล้วเทียบกับข้ออ้างทุกข้อ

probe สร้าง​แต่ image แล้ว​ลบ​ทุก​ใบ​ที่​มัน​สร้าง​เอง มัน​ไม่​สร้าง container สัก​ตัว และ ไม่​ลบ cache ของ​เครื่อง​คุณ ทุก​ทรง​แยก​กัน​ด้วย​ค่า​ไม่​ซ้ำ​ใน marker.txt

probe นี้​อยู่​นอก CI โดย​ตั้งใจ เพราะ​มัน​ต้อง​มี Docker · ราย​ละเอียด​อยู่​ใน CLAUDE.md หัวข้อ​รายการ​ที่​กัน​ออก​จาก CI

รหัส​ออก 1 แปล​ว่า​ข้อ​อ้าง​ใน​หน้า​นี้​ผิด ส่วน​รหัส​ออก 2 แปล​ว่า​วัด​ไม่​ได้ สอง​อย่าง​นี้​ไม่​เหมือน​กัน