สำหรับใครที่ใช้ Docker อาจจะพอรู้ประโยชน์หลายๆอย่างจากการใช้ Docker ในกระบวนการทำงานแล้วก็กำลังสนุกกับมันอยู่ แต่ก็น่าจะมีหลายๆคนที่พอได้ใช้มาสักระยะหนึ่งก็เริ่มเจอจุดที่ไม่ค่อยสะดวก อย่างเช่นขั้นตอนการสร้าง Docker image ที่เราจะมาพูดถึงกันในบทความนี้
เขียนโดย
Developer
@ borntoDev
Docker Image คืออะไร ?
ก่อนอื่นมาทบทวนแบบสั้นๆกันสักนิดหนึ่ง Docker image เป็นเหมือนหัวใจหลักของ Docker คือการใช้เป็นแม่แบบของ container อธิบายก็คือ เรามีโค้ดที่เขียนขึ้นมาเองไม่ว่าจะเขียนด้วยภาษาอะไรก็ตาม เวลาที่เราอยากทำให้โปรแกรมของเรารันแบบ Docker ได้ ก็จะมีขึ้นตอนคร่าวๆคือ
code ที่เขียน -> แปลงร่างด้วย Dockerfile -> ได้เป็น Docker image -> เอาไปรันในรูปแบบ container
จะเห็นว่าการที่เราจะแปลงโค้ดที่เราเขียนให้กลายเป็น Docker image ได้จะต้องมี Dockerfile ที่ใช้สำหรับแปลงร่าง ซึ่งจุดนี้นี่เองที่อาจจะมีคนประสบปัญหา
ทำไมการสร้าง Docker image ถึงช้า ?
การสร้าง Docker image นั้นเป็นงานปกติที่ผู้ใช้ Docker ทุกคนต้องทำกันเป็นประจำอยู่แล้ว โดยคำศัพท์ที่ใช้เรียกกระบวนการนี้ก็ตรงตัวเลยคือคำว่า “build” โดย “ขั้นตอน” ในการ build นั้นเราจะเขียนเอาไว้ในไฟล์ที่ชื่อว่า “Dockerfile”
โดยเจ้า Dockerfile นี้จะมีขั้นตอนตั้งแต่การ copy ไฟล์ของเรา ติดตั้ง library หรือเครื่องมือที่จำเป็นต่างๆ ไปจนถึงการรันโค้ดของเรา ด้วยความที่มันรวมกระบวนการหลายๆอย่างเอาไว้ ทำให้ยิ่งโค้ดของเราเยอะขึ้น ก็ยิ่งต้องใช้เวลานานมากขึ้นเวลาที่ build นับว่าเป็นจุดที่หลีกเลี่ยงไม่ได้จริงๆในการใช้ Docker
การทำงานของ Dockerfile
อย่างแรกเลยเราต้องรู้ก่อนว่าการ build Docker image นั้นมันทำงานยังไง โดยเราจะใช้ตัวอย่างเป็น calculator app ที่เขียนด้วย React จาก ahfarmer/calculator: Simple calculator built with React (github.com) ลองเอามาทำเป็น Docker image กัน
มาเริ่มจากการสร้างไฟล์ชื่อว่า “Dockerfile” เอาไว้ที่ชั้นนอกสุดของ project
# 1) เลือก base image FROM node:14-alpine # 2) กำหนด directory ที่จะทำงาน WORKDIR /app # 3) copy ไฟล์ใน /src ทั้งหมด ไปไว้ใน image COPY ./src /app/src # 4) copy ไฟล์ package.json ไปไว้ใน image COPY package.json ./ # 5) ติดตั้ง dependencies RUN npm install # 6) เปิด port 3000 ให้เข้าถึงได้ EXPOSE 3000 # 7) run react app CMD [ "npm", "start" ]
จะเห็นว่ามีอยู่ทั้งหมด 7 คำสั่ง พอเรา build Dockerfile นี้ด้วยคำสั่ง docker build -t calculator . จะเห็นว่า Docker แสดงขั้นตอนออกมาทั้งหมด 5 ขั้นตอน ซึ่งก็คือ คำสั่งที่ 1) – 5) เพราะว่า 6) กับ 7) นั้นเป็นแค่การกำหนดค่าให้กับ image เท่านั้น
ถ้าหากเราลอง build อีกรอบนึงโดยที่ไม่ได้แก้โค้ดอะไรเลย จะสังเกตได้ว่าขั้นตอนที่ Docker แสดงให้ดูนั้นมีบางสิ่งเปลี่ยนไป
ในขั้นตอน [1/5] จะไม่ได้ดาวน์โหลดไฟล์มาใหม่จาก Docker repository เพราะว่ามี base image เก็บอยู่ในเครื่องของเราแล้ว
ส่วนขั้นตอนที่ 2 – 5 จะเห็นว่ามีคำว่า CACHED อยู่ข้างหน้า ซึ่งหมายความว่าขั้นตอนเหล่านี้ Docker ไม่ได้ทำงานใหม่ แต่ว่าเรียกใช้ cache ที่มีอยู่มาแทน ซึ่งจุดนี้นี่แหละที่เราจะมาปรับให้ Dockerfile ของเราสามารถ build ได้เร็วขึ้น
การทำงานของ cache
ก่อนอื่นมาแวะดูการทำงานของ Docker build กันสักนิด ในการ build นั้น Docker จะมองคำสั่งแต่ละคำสั่งเป็น layer โดยแต่ละ layer จะซ้อนทับต่อจากชั้นก่อนหน้าไปเรื่อยๆ อย่างใน Dockerfile ที่เราใช้อยู่ก็มี 5 layer
เรารู้แล้วว่า Docker มีการใช้ cache ในขั้นตอนของการสร้าง image ทีนี้เรามาจำลองการ build หลังจากที่โค้ดมีการเปลี่ยนแปลงกัน ในที่นี้จะเปลี่ยน background-color: #858694; เป็น background-color: yellow; ในไฟล์ src/component/Display.css ซึ่งจะทำให้ layer ที่ 3 ก็คือ COPY ./src /app/src มีการเปลี่ยนแปลงไป ในขั้นตอนนี้ Docker ก็จะไม่ได้ใช้ cache อีกต่อไป โดยผลลัพธ์น่าจะออกเป้นแบบด้านล่างนี้
แต่ว่าพอลอง build แล้วเราจะพบว่า Docker ไม่ได้ทำงานเหมือนรูปด้านบน
จะเห็นว่าขั้นตอนที่ 2 นั้นยังมาจาก cache อยู่ ส่วน 3 – 5 นั้นไม่ได้มาจาก cache
สาเหตุที่เป็นแบบนี้มาจากการทำงานของ cache ที่มีเงื่อนไขก็คือ
-
layer ก่อนๆต้องเหมือนเดิม โดยมองภาพรวมเป็นก้อนเดียว ไม่ใช่เพียงแค่ layer ที่อยู่ติดกัน หรือก็คือต้องไม่มีการเปลี่ยนแปลงใดๆเลยกับ layer ทุกชั้นก่อนหน้า
-
layer นั้นๆต้องไม่เปลี่ยนแปลง
จากเงื่อนไขการใช้ cache นี้ทำให้การทำงานจริงๆ เป็นแบบด้านล่างก็คือ ถึงแม้จะเปลี่ยนเพียงแค่ layer 3 แต่ว่า 4 กับ 5 ก็ถือว่าถูกเปลี่ยนแปลงไปด้วย เนื่องจาก layer 3 มีการเปลี่ยนแปลงนั่นเอง
แค่สลับชีวิตก็เปลี่ยน
จากเรื่องการทำงานของ cache เราจะได้หลักการทำให้ Docker build เร็วที่สุดได้ด้วยการเรียงลำดับคำสั่งใหม่ โดยคำสั่งที่ไม่ค่อยเปลี่ยนแปลงก็ใส่ไว้เป็น layer แรกๆเท่าที่จะทำได้ เพื่อให้ Docker ใช้ cache ให้ได้มากที่สุด ทำให้ Dockerfile ของเราออกมาหน้าตาแบบด้านล่างนี้
FROM node:14-alpine WORKDIR /app COPY package.json ./ RUN npm install COPY ./src /app/src EXPOSE 3000 CMD [ "npm", "start" ]
โดยเราจะทำการ build หนึ่งรอบก่อนเพื่อให้ Docker ได้มี cache สำหรับ Dockerfile แบบใหม่
จากนั้นจะลองแก้โค้ดจุดเดิม คราวนี้เปลี่ยนเป็น background-color: red; แล้วก็ลอง build ดูอีกครั้งหนึ่ง
จะพบว่าตอนนี้ 2 – 4 นั้นเป็นการใช้ cache เรียบร้อยแล้ว มีเพียงคำสั่งที่ 5 เท่านั้นที่มีการเปลี่ยนแปลง
สรุปได้ว่า
เราสามารถเขียน Dockerfile โดยจัดเรียงคำสั่งต่างเพื่อทำให้ Docker ใช้ cache ได้อย่างมีประสิทธิภาพได้ ซึ่งอาจจะลดเวลาในการ build image ไปได้เยอะมากๆ
อย่างในวันนี้ Dockerfile เวอร์ชันแรก ใช้เวลาในการ build หลังจากมีการเลี่ยนแปลงโค้ด 88.8 วินาที แต่ในเวอร์ชันที่สองใช้เวลาเพียง 3.4 วินาที
ที่เร็วขึ้นมากขนาดนี้ก็เพราะส่วนที่ใช้เวลานานที่สุดก็คือ npm install ซึ่งพอเราทำให้ Docker ใช้ cache ได้ ก็เลยไม่ต้องเสียเวลาในขั้นตอนนี้นั่นเอง
สรุปของสรุปอีกทีก็คือเราต้องมีความเข้าใจขั้นตอนต่างๆใน Dockerfile ของเรา เพื่อที่จะเรียงลำดับคำสั่งให้ใช้ cache ได้อย่างมีประสิทธิภาพและยังคงทำงานได้ถูกต้อง ถ้าใครได้อ่านบทความนี้แล้วก็ลองกลับไปดู Dockerfile ของตัวเองกันได้ครับ หวังว่าทุกๆคนจะได้มีคุณภาพชีวิตที่ดีครับ