Go: Docker Multistage Build
สวัสดีชาว Gopher ทุกท่าน ในปัจจุบันการใช้ Container สำหรับพัฒนาแอปอย่าง Docker เป็นที่นิยมอย่างมาก เนื่องจากช่วยในการจัดการ environment และ library ให้เหมือนกันได้ไม่ว่าจะอยู่ที่ไหนก็ตาม
อย่างไรก็ดี ปัญหาการ "บวม" (Docker image bloating) หรือก็คือ image มีขนาดใหญ่เกินความจำเป็นนั้น สามารถเกิดขึ้นได้ถ้าไม่จัดการให้ดี ทางออกอื่นๆ ที่ช่วยได้ เช่น
- ใช้
.dockerignore
เพื่อไม่ให้เพิ่มไฟล์ที่ไม่ต้องการ - เลือกใช้ Distroless/Minimal base image เพื่อลดขนาดให้ได้มากที่สุด
ต้องเข้าใจว่าส่วนที่ใช้ทำในตอน build แอปพลิเคชันนั้น พอเสร็จแล้วมักจะไม่ถูกนำมาใช้ต่อตอนที่แอปกำลังรัน ถ้าเรานำทั้งหมดมาใช้ก็จะทำให้บวมได้ ในบทความนี้จะนำเสนออีกวิธีที่จะแยกส่วนที่เป็น build tool ออก ให้เหลือเฉพาะส่วนที่จำเป็นต่อการรันแอปนั่นเอง เรียกว่า Multi-stage builds
การใช้ Multi-stage builds จะช่วยลดขนาดของ container ให้เล็กลงได้ ด้วยการแตกขั้นตอนออกเป็นหลายๆ stage ย่อย แต่ละ stage จะส่งผลลัพธ์ต่อไปยัง stage ถัดไป
Demo
เราสามารถเขียน Go application ด้วย Gin framework ดังนี้
package main
import "github.com/gin-gonic/gin"
func main()
ในการเปรียบเทียบเพื่อให้เห็นภาพชัด จะทดสอบการ build ทั้งสองรูปแบบ
ใช้ Dockerfile ในการ build แบบปกติ
FROM golang:1.23-alpine
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY *.go ./
RUN CGO_ENABLED=0 GOOS=linux go build -o /api
EXPOSE 8080
CMD [ "/api" ]
จากนั้นลอง build ด้วยคำสั่ง docker build -t my-go-app .
ใช้ Multi-stage builds
เราจะแยกการ build แอปพลิเคชันออกเป็นสอง stage คือ build stage และ release stage ดังนี้
# Build stage
FROM golang:1.23-alpine AS build
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY *.go ./
RUN CGO_ENABLED=0 GOOS=linux go build -o /api
# Final stage
FROM gcr.io/distroless/base-debian11 AS release
WORKDIR /
COPY --from=build /api /api
EXPOSE 8080
USER nonroot:nonroot
ENTRYPOINT [ "/api" ]
คราวนี้ลอง build ด้วยคำสั่ง docker build -t my-go-app:multistage -f Dockerfile.multistage .
เมื่อ build เรียบร้อยทั้งสองรูปแบบ มาดูผลลัพธ์กันดีกว่า
จะเห็นว่าการใช้ Multi-stage builds ช่วยลดขนาดให้ image ของเราอย่างมหาศาลเทียบกับแบบปกติ พอ image เล็กแล้วก็สามารถนำไปใช้ต่อได้ง่ายมากขึ้นและใช้พื้นที่ลดลงนั่นเอง
Reference: