Docker Part 7 - Create Image 3

สร้าง Image กันต่อ

จากตอนที่แล้วเราได้รู้ COMMAND ต่างๆเกี่ยวกับ Docker file ไปแล้ว ตอนนี้เราจะมาสร้าง Image แบบตั้งแต่เริ่มจนได้ Image ไปใช้งาน

สร้าง Web application จากภาษา Go

เรามาลองสร้าง web application จากภาษา go กัน อันนี้เลือกภาษา go เพราะมันต้อง compile code จริงๆอยากจะเขียนภาษา Java แต่เดี๋ยวมันจะงงเพราะมีพิธีกรรมมากมายเลยเอาภาษา go ละกันไฟล์เดียวจบเลยจะได้เป็นตัวอย่างง่ายๆ

ตัวอย่าง Code ผมเอามาจาก https://yourbasic.org/golang/http-server-example/

สร้าง Directory ใหม่ขึ้นมาแล้วสร้างไฟล์ชื่อ server.go ใน directory ที่สร้างขึ้นมาใหม่ ของผมสร้างที่

/root/share_docker/test_go ไฟล์ server.go ผมจะอยู่ที่ /root/share_docker/test_go/server.go

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package main

import (
"fmt"
"net/http"
)

func main() {
http.HandleFunc("/", HelloServer)
http.ListenAndServe(":8080", nil)
}

func HelloServer(w http.ResponseWriter, r *http.Request) {

fmt.Printf("Hello, %s!", r.URL.Path[1:])
fmt.Fprintf(w, "Hello, %s!", r.URL.Path[1:])

}

ตัว Source code นี้ไม่มีอะไรมากไปกว่ารับเปิด Port 8080 แล้วแสดงข้อมูลออกทางหน้าจอ คราวนี้ลอง run โปรแกรมดูครับ ถ้าเครื่องภาษา go run server.go ครับ

จากนั้นสร้าง Dockerfile ขึ้นมาครับ

1
2
3
4
5
6
7
8
9
FROM golang:rc-buster

WORKDIR /app
ADD server.go /app

# เป็นคำสั่งที่ใช้ในการ compile source code ให้กลายเป็น binary file ที่สามารถสั่ง run ได้เลย
RUN RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

CMD ./main

จากนั้นสั่งสร้าง image ด้วยคำสั่ง

1
docker build -t test-go .

จากนั้นลองทำการ Run docker ขึ้นมาด้วยคำสั่ง

1
docker run -d --rm -p 8080:8080 test_go

จากนั้นลองเข้าผ่าน Browser เพื่อตรวจดูว่าทำงานได้จริงไหม

ทุกอย่างดูเหมือนดีแต่มันก็ยังไม่ดีในบางแง่มุมซึ่งแง่มุมนั้นก็คือขนาดของ Image ครับ โดยหากเราสั่ง docker images จะเห็นข้อมูล Image ที่อยู่บนเครื่องเราดังภาพ

ซึ่งจะเห็นว่า Image มีขนาดใหญ่ถึง 843 MB ซึ่งใหญ่มากๆ ซึ่งข้อเสียของมันคือเสียเวลาในการ Download Image ครับ โดยหลักการทำงานของ Docker คือเครื่องที่ Run docker ต้องมี Image ก่อนถึงจะสร้าง Container ได้ซึ่งถ้า Image ใหญ่ก็ต้องเสียเวลาโหลด ซึ่งไม่ค่อยเหมาะเวลาที่เราทำงานบนระบบ Cloud หรือ Resource ที่โยกย้ายได้ ตัวอย่างเช่น ถ้าทำจะทำ Cluster ไว้ Run docker เพื่อให้เราสามารถกระจาย Container ไปได้หลายเครื่องเพื่อให้ใช้ Resource ได้อย่างเต็มที่และลดความเสี่ยงที่ถ้าเครื่องนึงพังก็สามารถย้าย Container ไปไว้ที่เครื่องอื่นใน Cluster ได้ ซึ่งในปัจจุบันเขาใช้ kubernetes หรือ Docker swarm ในการจัดการ ดังนั้นเวลาเราเพิ่มเครื่องเข้าไปใน cluster กว่าเครื่องใหม่ที่เข้ามาใน Cluster มันจะพร้อมให้ Container มา Run บนเครื่องมันก็จะนานเพราะต้องเสียเวลาไปโหลด Image มา

อ่านเพิ่มเติมเกี่ยวกับ Kubernetes และ Docker swarm

เหล่าโปรแกรมเมอร์และนักออกแบบได้แก้ปัญหานี้แล้ว

ปัญหาที่ผมเล่าไปเมื่อกี้นั้นเหล่าโปรแกรมเมอร์และนักออกแบบเขาได้แก้ปัญหาเหล่านี้ไว้แล้วซึ่งนั่นก็คือ ทำ Image ที่มีไว้สำหรับ Run program อย่างเดียว ตัวอย่างเช่น ถ้าจะ Run java application Image นั้นก็ควรจะมีแค่ JRE (ไม่ต้องเป็น JDK) ไม่ต้องลงโปรแกรมอื่นๆที่ไม่ได้ใช้เช่น vim vi nano netstat ดังนั้น Image นั้นก็จะมีขนาดเล็กลง แต่แค่นั้นยังไม่พอมีคนเอาตัว Distro linux ที่ชื่อ alpine ซึ่งมีขนาดเล็กมากๆ มาเป็น Base image ด้วยทำ Image มีขนาดเล็กลงไปอีกแบบจากบางที 200 MB ก็จะเหลือแค่ประมาณ 6 MB ดังนั้น ถ้าจะทำ Image ที่ไว้ใช้สำหรับ Run อย่างเดียวผมแนะนำให้หา tag ที่มีคำว่า alpine อยู่ด้วย เช่น Nodejs ก็มี image ที่เป็น alpine ให้เราไว้ทำ Image สำหรับ Run อย่างเดียว

ลองทำ Image ใหม่

เราลองมาทำ Image ใหม่โดยใช้ Image tag : alpine มาใช้เป็น base image

1
2
3
4
5
6
7
8
FROM golang:alpine

WORKDIR /app
ADD server.go /app

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

CMD ./main

จากนั้นลอง build image ดูครับ ซึ่งจากการ build ของผม ผมทำ tag ต่างกัน โดยถ้าทำจาก image ธรรมดาจะใช้ tag: normal ส่วนทำจาก image alpine จะใช้ tag: alpine ซึ่งจากการ build ของผมจะเห็นว่าขนาด Image ต่างกันเป็นอย่างมาก

ยังทำได้ดีกว่านี้อีก

เนื่องจากภาษา golang นั้นเป็นภาษาที่ทำการ compile source code ให้กลายเป็น binary file ให้สามารถไปวางบนเครื่องนั้นแล้ว run ได้เลย ดังนั้นถ้าเรา compile source code บน image alpine แล้วไปวางบน image เปล่าๆ (image เปล่าๆคือ scratch) เลยก็จะได้ image ที่จะเล็กมากๆ ปัญหาคือมันทำได้เหรอวะ คำตอบคือได้ครับ เขาจะเรียกการทำแบบนี้ว่า multi-stage builds โดยวิธีการเขียนนั้นก็ไม่ได้แตกต่างจาก Dockerfile ที่เราเคยทำเลย เราไปดูจาก code ก็น่าจะเข้าใจเลย

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM golang:rc-buster as builder
# ตรง as builder ที่เพิ่มเข้ามาเพื่อบอกว่า Image ที่สร้างด้วย FROM นี้เวลาจะเรียกใช้มันด้วยชื่ออะไร

WORKDIR /app
ADD server.go /app

RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .

FROM scratch
# FROM ที่อยู่ล่างสุดคืออันที่เราจะเอาไปใช้

WORKDIR /
COPY --from=builder /app/main /main
# ตรง --from=builder คือการอ้างถึง image ตัวที่ชื่อ builder ที่เราสร้างไว้ข้างบน
# รวมกันก็คือ เราจะเอาไฟล์ main ที่อยู่ใน /app/main ของ builder ไปวางไว้ใน image ที่เรากำลังจะสร้างที่ path : /main

CMD ["./main"]

จากนั้นทำการ build image โดยของผมจะตั้งชื่อ tag : scratch จากนั้นลองมาดูขนาด image แล้วจะเห็นว่าเหลือแค่ประมาณ 7 MB กว่าๆ

จากนั้นลองทำการ run image ดูครับ ว่ามันสามารถทำงานได้ไหมและสามารถเข้าใช้งานผ่าน Browser ได้หรือไม่

1
docker run --rm -d -p 8080:8080 test-go:scratch

แต่วิธีการทำแบบนี้ทำได้แค่กับบางภาษานะครับ อย่าง Java เนี่ยไม่สามารถทำแบบนี้ได้เพราะ Java ต้องการ JVM ในการทำงานซึ่ง image : scratch นั้นไม่มี JVM ไม่สามารถทำงานได้ ทำได้มากสุดคือการ compile บน JDK แล้วไปวางบน JRE alpine

สรุป

ในตอนนี้เราได้รู้เกี่ยวกับการ Build image แบบ multi-stage builds เพื่อให้เราสามารถได้ image ให้มีขนาดเล็กลง ซึ่งจริงๆในการสร้าง Image นั้นมี Best practice ในการทำอยู่โดยสามารถตามไปอ่านได้ตาม Link ต่อไปนี้

http://www.somkiat.cc/summary-from-dockerfile-guildline/

https://docs.docker.com/develop/develop-images/dockerfile_best-practices/

https://www.docker.com/blog/intro-guide-to-dockerfile-best-practices/

ตอนนี้คงเป็นตอนสุดท้ายเกี่ยวกับการสร้าง Image แล้วครับ เพราะผมรู้แค่นี้ ฮ่าๆๆๆๆๆ ตอนต่อไปคงจะเป็นเรื่อง Docker compose

ไม่เกี่ยวกับ Docker แต่เกี่ยวกับไอดอล

Yayee

น้องน่ารักมาก ตอนแรกนึกว่าผมสั้นจะห้าวๆ แต่กลายเป็นสาวหวานซะงั้น ฮ่าๆๆๆๆๆๆๆๆ

Yayee

เพลงประกอบการเขียน Blog

เพลง แค่ได้คิดถึง ของ ญารินดา ที่ Grammy มี RE MV โปรเจค คือเอาเพลงเก่ามาทำ MV ใหม่ โดยตีความใหม่ ซึ่งดูแล้วนี่เศร้าจริงๆ เวอร์ชั่นเก่านี่ดูแล้วเศร้าแบบจุกๆที่บรรยายลำบาก แต่เวอร์ชั่นนี้นี่คือเศร้าแบบอยากร้องไห้ เวอร์ชันนี้มีพี่ญารินดามาประกอบ MV ด้วย