Docker (LV.2)
การวาง structure ของ file และ folder ร่วมกับ source code
สมมติว่าเรามี source code NodeJS เราจะวาง Dockerfile, .dockerignore และ docker-compose ดังนี้
ตัวอย่างไฟล์ content ดังนี้
app.js
package.json
package-lock.json ปกติจะเอาขึ้น git ด้วย แต่ไม่ได้ใส่มาในตัวอย่างนี้ เนื่องจากมีขนาดใหญ่ และมีโอกาสเปลี่ยนแปลงในอนาคต ให้ gen มาด้วยการสั่ง npm install ในเครื่องของตนเอง
libs/math.js
test/math.test.js
Dockerfile
จากตัวอย่าง Dockerfile เราจะสร้าง /app ไว้ แล้ว copy package.json และ package-lock.json เข้าไปก่อน คำสั่ง npm ci จะทำการติดตั้ง lib ทั้งหมดจาก config ใน package-lock.json ซึ่งจะการันตีว่า lib version ตรงกับที่ developer ใช้งานในเครื่องของตนเองแน่นอน หลังจากติดตั้ง dependency ทั้งหมด จะทำการ copy source code อื่นๆตามหลังไป ข้อดีของการแยก layer เช่นนี้ ทำให้เวลาเปลี่ยน source code แต่ไม่เปลี่ยน dependency จะไม่เกิดการ build dependency layer ทำให้ pipeline เสร็จไว
.dockerignore
.dockerignore เป็นไฟล์ที่ทำหน้าที่คล้าย .gitignore ใน git มีอยู่เพื่อป้องกันการ copy file ที่ไม่จำเป็นและอาจจะมี sensitive data ไม่ให้หลุดเข้าไปใน Docker image
ไฟล์หลักๆที่เราต้องป้องกันคือ README.md, .git และ .env ส่วนที่เหลืออื่นๆเป็นการลดเพื่อไม่ copy ส่วนไม่จำเป็นเข้าไปใน image
Step ข้างต้นนี้เพียงพอที่จะ build container image ได้แล้ว แต่หากเราต้องการ build และ test ในเครื่องตนเองก็สามารถทำได้โดยเขียน docker-compose.yml เพิ่ม
docker-compose.yml
จากตัวอย่างจะเป็นการบอก docker-compose ให้สร้าง image ชื่อ myapp:localtest โดย build image จาก Dockerfile ที่อยู่ใน current context directory และ bind port 3000 ของ host เข้ากับ port 3000 ของ container
หลังจากเตรียมไฟล์เรียบร้อย สามารถสั่ง docker-compose เพื่อ start ขึ้นมาทำงานได้ ดังนี้
หากต้องการ force rebuild image อีกครั้ง เวลามีการเปลี่ยน version ให้ใช้คำสั่งดังนี้
การทำ Multi-stage build
เป็นเทคนิคใน Dockerfile ที่ช่วยลดขนาดของ Docker image และทำให้การ build มีประสิทธิภาพมากขึ้น โดยการแยกขั้นตอนการ build ออกเป็นหลาย ๆ ขั้น (stages) เพื่อที่จะนำเฉพาะผลลัพธ์ที่ต้องการมาใช้งานใน final image โดยไม่ต้องเก็บทุกอย่างที่ใช้ในการ build เอาไว้ใน image สุดท้าย
ประโยชน์อย่างนึงที่เห็นได้ชัดเจน เช่นการแยก stage แรกซึ่งจะติด build tools และ application code อยู่ใน layer ออกจากอีก stage ซึ่งจะ copy มาแค่ artifact ที่ compile เสร็จแล้วมาแทน ทำให้ container image light-weight และ secure ขึ้น
ตัวอย่าง Dockerfile
code ในส่วน EOF คือการเขียน content และใส่ลงไฟล์ชื่อ main.go
ปกติจะไม่ใส่ใน Dockerfile เพียงแต่ตัวอย่างนี้ทำเพื่อให้สร้างไฟล์อย่างง่าย โดยผู้เรียนไม่ต้องมาเตรียมไฟล์แยกไว้เท่านั้น
ทำการ build image ด้วยคำสั่งดังนี้
จากตัวอย่าง จะเห็น FROM
มากกว่า 1 บรรทัดในไฟล์ ซึ่งส่วนนี้เป็นตัวระบุจุดเริ่มของแต่ละ stage และบอกให้ทราบว่าใช้ base image อะไร
stage แรกจะทำการ compile main.go ออกมาเป็นไฟล์ /bin/hello
stage ที่สองจะใช้ base จาก scratch
image แล้ว copy file จาก stage แรก (stage id 0) มา
ผลลัพธ์จากการ build จะเอา stage สุดท้ายมาเป็น container image และติด tag ชื่อ hello
เนื่องจากการใช้ stage id มีโอกาสผิดพลาดได้ง่าย และยากต่อการเข้าใจ ส่วนมากจึงนิยมใช้การกำหนดชื่อ build stage แทน ดังตัวอย่างต่อไปนี้
Docker build cache
Docker จะเก็บ cache ของแต่ละขั้นตอนการ build ใน Dockerfile ไว้ และจะมีการตรวจสอบว่ามีการเปลี่ยนแปลงหรือไม่ ดังนั้นหากมีขั้นตอนใด ๆ ที่ไม่เปลี่ยนแปลง Docker จะใช้ผลลัพธ์จาก cache ที่เก็บไว้แทนการรันขั้นตอนนั้นใหม่ ส่งผลให้การ build ทำงานเร็วขึ้น
ดังนั้นการที่เราเข้าใจการทำงานของ Docker build cache ก็จะทำให้เราสามารถ optimize ระยะเวลาที่ต้องใช้ในการ build แต่ละครั้งได้
ตัวอย่าง Dockerfile แรก
ตัวอย่าง Dockerfile สอง
จากตัวอย่าง Dockerfile ที่สองจะ copy ไปแค่ package.json
ก่อน และสั่ง npm install
แล้วจึง copy code nodejs ที่เหลือ
หาก content ใน package.json
ไม่มีการเปลี่ยนแปลง ก็จะทำให้สามารถใช้ cache จาก layer COPY package.json .
และ RUN npm install
ได้ ซึ่งจะช่วยให้ไม่ต้องดาวน์โหลด npm package จาก internet มาในทุกครั้งที่ build และลดระยะเวลาการ build ได้อย่างมาก
ในขณะที่ Dockerfile แบบแรก จะไม่สามารถ reuse layer RUN npm install
ได้ เนื่องจาก code application เปลี่ยนทุกครั้ง ทำให้ layer COPY . .
และ layer ถัดๆไป ไม่สามารถ reuse cache ได้
Docker buildx, BuildKit และ cache backend
BuildKit เป็น backend หรือ engine ที่พัฒนาโดย Docker สำหรับการ build image ซึ่งมีประสิทธิภาพและความยืดหยุ่นที่ดีกว่า engine เดิมของ Docker Build เป้าหมายหลักของ BuildKit คือเพิ่มความเร็วและประสิทธิภาพในการ build image รวมถึงการปรับปรุงการจัดการ caching, multi-stage builds, และการรองรับการ build ข้ามแพลตฟอร์ม (multi-platform builds) ได้ดีขึ้น
Docker Buildx เป็น CLI extension ของ Docker ที่ขยายความสามารถในการ build โดยใช้ BuildKit เป็น backend เพื่อให้ผู้ใช้เข้าถึงฟีเจอร์ขั้นสูงของ BuildKit ได้ง่ายขึ้น โดยเฉพาะการสร้าง multi-platform image, การจัดการ cache ที่ซับซ้อน, และการ build image ข้ามระบบได้ง่ายขึ้น
จากเนื้อหาก่อนหน้า เราทราบกันแล้วว่า Docker สามารถใช้ build cache เพื่อเพิ่มประสิทธิภาพการ build ได้ ซึ่งจากตัวอย่างจะเป็นการ build บน local machine และใช้ local backend cache
ปัญหาคือเมื่อเรานำ Dockerfile เดียวกันไป build บนเครื่องอื่นที่ไม่เคย build image นี้มาก่อน เราจะไม่สามารถ reuse cache ได้ เพราะโดย default จะใช้ local backend cache BuildKit และ Buildx จึงจะเข้ามาแก้ไขในส่วนนี้ โดยทำให้เราสามารถไปใช้ external backend cache ได้
Buildx รองรับ cache storage backend ดังต่อไปนี้
inline เก็บ cache ไว้ที่เดียวกับ output image
registry เก็บ cache ไว้เป็นอีก image หนึ่งและ push ขึ้น registry เป็นคนละที่กับ output image
local เขียน cache ใน local directory บน filesystem ของเครื่องตัวเอง
gha เก็บ cache ที่ github action cache
s3 upload build cache ขึ้น AWS S3
azblob upload build cache ขึ้น Azure Blob Storage
เราสามารถระบุ cache ได้โดยการใส่ --cache-to
และ --cache-from
ตัวอย่างการใช้ใน CI/CD แบบง่ายๆ
จะทำการ build โดยใช้ cache จาก image tag latest
บน remote registry ถ้ามี เพื่อ build เป็น image tag v2
และ tag latest
แล้ว push ขึ้นไปที่ remote registry
Last updated
Was this helpful?