ฝึกวิชา Docker Image & Container
หลังจากที่ได้ติดตั้ง Docker เรียบร้อยแล้ว ในขั้นต่อไปจะเป็นการศึกษาการใช้งาน Docker File ให้เข้าใจมากขึ้น เพื่อเป็นพื้นฐานในการต่อยอดในส่วนอื่น
ขอขอบคุณพี่เอก khajonpot sawangdee มากๆครับ สำหรับ Knowledge Sharing ที่มีให้ในเรื่องของการใช้งาน Docker File ครับ และเนื้อหาจาก Saladpuk Gitbook ด้วยน่ะครับ
รวมถึงเนื้อหาบางส่วนของบทความผมเรียนรู้มาจาก Course ของ
อาจารย์ Issaret Prachitmutita ครับ อยากให้ทุกคนมีโอกาสได้เรียนกับอาจารย์มากๆ เพราะว่าสอนสนุก มีพลังงานในการสอนและถ่ายทอดประสบการณ์ให้เต็มที่เลยครับ ไม่รู้สึกเบื่อเลยตลอด 3 วัน
สำหรับมหาวิทยาลัยหรือบริษัทองค์กรใดต้องการ Private workshop ติดต่อได้ อิศเรศ ประจิตต์มุทิตา (Issaret Prachitmutita) 082–799–8969, issaret.m@gmail.com
Course ที่เรียนกับอาจารย์ Issaret Prachitmutita ครับ
สำหรับการติดตั้ง Docker ดูได้ที่บทความนี้น่ะครับ
หรือใช้วิธีย่อๆแบบนี้น่ะครับ (ได้มาจาก Course Jump into DevOps World)
git clone https://github.com/Ima8/Jump_into_DevOps_world
cd Jump_into_DevOps_world
chmod 775 setup.sh #เพื่อให้ file มัน execute ได้
./setup.sh #เพื่อติดตั้ง Docker, Docker Compose
- เราจะได้ทั้ง Docker, Docker Compose จากขั้นตอนติดตั้งนี้ครับ
ทำไมถึงต้องศึกษา Docker แม้จะยังไม่ได้ใช้กับงานที่ทำในตอนนี้
ผมเป็น ASP.Net Web Developer ที่เน้นการทำงานบน Microsoft Platform เป็นหลักครับ ซึ่งเอาจริงๆแล้วยังไม่มีโอกาสได้ใช้ Docker เลย
แต่เหตุผลที่สนใจ Docker เพราะผมอยากจะทำระบบที่ยืดหยุ่น, Deploy ง่าย, Scale ง่าย ซึ่งพบว่า Tools ส่วนใหญ่ที่เกี่ยวข้องมักจะใช้ Docker Image ร่วมกับการทำงานเสมอ
รวมถึงเมื่อวันที่ 8 ธันวาคม 2562 ผมได้มาอบรมเรื่อง Jump into DevOps World และมีผู้อบรมถามว่า “เราควรจะเรียนรู้อะไรเป็นเรื่องแรกในการทำ DevOps”
ซึ่งสิ่งที่ผู้สอนตอบก็คือ
เราควรเรียนรู้ Dockerครับ เพราะเป็นพื้นฐานที่ต้องใช้อีกเยอะเลย
บทความนี้ผมตั้งใจเขียนเพื่อเป็นเป็นแนวทางการศึกษา Docker สำหรับคนที่กำลังหัดใหม่น่ะครับ เพราะผมเองก็กำลังหัดอยู่เหมือนกัน
แอบเสียดายที่ตัวผมเองกว่าจะเริ่ม Docker ได้จริงๆจังๆก็ปี 2019 แล้ว ถึงจะเริ่มช้า แต่ยังไงก็ตั้งใจจะนำความรู้ที่ได้ไปสอนน้องๆในทีมต่อครับ ^^
พื้นฐาน Docker
Docker Image คืออะไร
เป็น “ต้นแบบ” ของ Container
ในการสร้าง Environment ที่เราต้องการ ซึ่งภายในจะประกอบไปด้วย File System
และConfiguration
สำหรับทำการติดตั้ง Application ที่จำเป็นเพื่อให้เราสามารถที่จะรัน Service ได้
ในการสร้างDocker Image
นั้นเราจะทำการเขียนชุดคำสั่งลงไปยัง Dockerfile
หลังจากนั้นเราจะใช้ Dockerfile
เป็นตัวสร้าง Docker Image
อีกที
Docker Container คืออะไร
เป็น Running Instance ของ Docker Image
เพื่อสร้าง Environment สำหรับใช้งาน Service หรือ Application ได้ทันที
ขั้นตอนการทำงาน
- สร้าง
Dockerfile
และใส่ชุดคำสั่งในการเตรียม Environment ซึ่งในขั้นตอนนี้จะต้องมีการ PullDocker Image
เข้ามาใช้ก่อนอย่างน้อย 1Docker Image
เสมอ - สร้าง
Docker Image
จากDockerfile
- สร้าง
Docker Container
จากDocker Image
และสั่งรันDocker Container
เพื่อใช้งาน Service หรือ Application
รายละเอียด Docker เพิ่มเติมน่ะครับ
ประเภทการใช้งาน Docker
- นำ
Code
ของเรา รวมกับDocker Image
อื่นเพื่อสร้างApp Image
และนำไปรันใน Docker Engine (ในบทความจะใช้รูปแบบนี้)
- สร้าง
Development Environment
ซึ่งช่วยทำให้ประหยัดเวลาในการติดตั้ง Tools ต่างๆ ลงในเครื่อง Development ของแต่ล่ะคน
เตรียม Lab
ให้ทำการ clone lab ด้วยคำสั่งดังนี้นะครับ แล้วเราจะได้ Lab มาทั้งหมด 4 Labs ด้วยกัน (3 Labs จากพี่เอก, 1 Lab จาก Saladpuk Gitbook)
git clone https://github.com/ponggun/docker-training-dockerfile.git
cd docker-training-dockerfile
Lab Structure
Lab 1: Bash: Hello-World
จะทดลองใช้งาน Docker File ร่วมกับ Bash Script
เพื่อทำการสร้าง Docker Image
และ Docker Container
เข้า Folder 1.Hello-World ด้วยคำสั่ง
cd 1.Hello-World
เราจะเจอ Dockerfile
และ script.sh
ดังภาพนี้
Dockerfile
- Line#1: Pull bash image
- Line#2: Copy file script.sh ไปที่ Docker Image (ตัว script.sh จะรัน command เพื่อ display คำว่า “Hello World” มายัง Terminal)
- Line#3: สั่งรัน bash /script.sh เพื่อให้แสดงผล Hello World
script.sh
- Line#1: แสดงผลข้อความ “Hello World” ไปที่ Terminal
สร้าง Docker Image จาก Dockerfile
docker build -t 1.hello-world .
ดู Images ที่สร้างด้วยคำสั่ง
docker images
จากรูปบนเราจะสังเกตุเห็นว่า มี 2 images
- 1.hello-world: Docker Image ที่เราพึ่งสร้างขึ้น
- bash: Docker Image — Bash ที่เราดึงมาจาก Docker Hub ตามคำสั่งใน Dockerfile
ดู History ที่สร้างด้วยคำสั่ง
docker history bash
docker history 1.hello-world:latest
จากรูปเราจะพบว่า Docker Image History ของ 1.hello-word
Image นั้นเกิดมาจาก History ของ Bash
Image และคำสั่งเพิ่มเติมในตัว Dockerfile
ใน 1.hello-word
Image เอง
ซึ่งคำสั่ง History จะทำให้เราสามารถแกะย้อนกลับไปได้ว่าแต่ล่ะ Image เกิดมาจากคำสั่งอะไรบ้าง และทราบถึงจำนวน space ที่ใช้ไปครับ
สร้าง Docker Container จาก Docker Image
docker run 1.hello-world
ได้ผลลัพธ์เป็นคำว่า “Hello World” ถูกต้อง
ดู Container ที่สร้างด้วยคำสั่ง
docker ps -a
ทดลองรัน Image เดิมซ้ำๆ แล้วดูผลของ Container ที่เกิดขึ้น
docker run 1.hello-world
docker run 1.hello-world
docker run 1.hello-world
docker ps -a
เราจะเห็นว่าทุกครั้งที่เรารัน image ระบบก็จะมีการสร้าง container ใหม่เสมอ ถ้าเราต้องการทดลองรัน image แล้วไม่อยากที่จะต้องลบ container ทิ้ง ให้ใช้คำสั่งข้างล่างนี้แทนน่ะครับ
docker run --rm 1.hello-world
docker run --rm 1.hello-world
docker run --rm 1.hello-world
docker ps -a
จะพบว่าหลังการรัน Images หลายๆครั้งต่อกันแล้ว ระบบจะทำการลบ Containers ออกไปให้ด้วย เหมาะสำหรับกรณีที่เราใช้เพื่อทำ Lab ง่ายๆแบบนี้น่ะครับ
Lab 2: Bash: Pass Parameter
ศึกษาการเขียน Docker File ร่วมกับ Bash Script
โดยจะทดลองแสดงผลของ Environment Variable และศึกษาว่าเราจะทำการรัน Docker Image ยังไงให้สามารถส่งค่า Environment Variable เข้าไปด้วยได้
เริ่ม Lab ด้วยการเข้าไปที่ Folder 2.PassParameter ด้วยคำสั่ง
cd 2.PassParameter
เราจะเจอ Dockerfile
และ script.sh
ดังภาพนี้
Dockerfile
- รายละเอียดจะเหมือนกับ Lab 1: Bash: Hello-World เลยครับ
script.sh
- Line#1: แสดงผลข้อความจาก Environment Variable ที่ชื่อว่า MESSAGE ไปที่ Terminal
สร้าง Docker Image จาก Dockerfile
docker build -t 2.passparameter .
ดู Images ที่สร้างด้วยคำสั่ง
docker images
จะพบว่ามี Image ที่เพิ่มมา Image ใหม่อันเดียว คือ 2.passparameter
แต่ตัว image bash
นั้นเพราะว่าการ Pull มาแล้วเลยใช้ตัวเดิมต่อได้เลยครับ
รัน Image ด้วยคำสั่งดังนี้
docker run --rm 2.passparameter
docker run --rm -e MESSAGE="Hello World from Environment" 2.passparameter
จะพบว่าคำสั่งแรกนั้นจะไม่มีผลลัพธ์อะไรออกมาที่ Terminal เพราะว่าเรายังไม่ได้ Set Environment Variable MESSAGE
ส่วนคำสั่งที่สองคือการรัน Image พร้อมกับทำการ Set Environment Variable MESSAGE ไปด้วย ทำให้มีข้อความแสดงผลออกมาที่ Terminal
Lab 3: PHP
ศึกษาการใช้งาน Docker File ร่วมกับ php
แทน bash script
จาก Lab #1, #2
เข้า Folder 3.php ด้วยคำสั่ง
cd 3.php
เราจะเจอ Dockerfile
และ index.php
ดังภาพนี้
Dockerfile
- Line#1: Pull Docker Image ของ Apache httpd (Web-server) และ PHP
- Line#2: Copy file index.php ไปที่ Docker Image ใน path /var/www/html ซึ่งเป็น Root ของ Websitie
(ตัว index.php จะรัน command เพื่อ display คำว่า “Hello World” และ ผลลัพธ์การคำนวน 9 + 5 = 14) - Line#3: สั่งเปิด Docker Port 80
index.php
- Line#1: แสดงผลข้อความ “Hello docker world!”
- Line#2: แสดงตัวอย่างการบวกเลข = 14
เราจะรู้ได้อย่างไรว่าต้องเขียน Dockerfile แบบไหน
ดูได้จากต้นทางของ Image ที่เราสนใจครับ เช่นในกรณีนี้ผมต้องการดึง php + apache image จาก Docker Hub ข้างล่างนี้
สร้าง Docker Image จาก Dockerfile
docker build -t 3.php .
ดู Images ที่สร้างด้วยคำสั่ง
docker images
เราจะพบว่าเราได้สร้าง Docker Image เพิ่มมาทั้งหมด 2 Images
- php — เป็น Image ที่เรา Pull มาใน Dockerfile (Size ใหญ่เชียว T_T)
- 3.php — เป็น Image ใหม่ที่เราสร้างจาก Dockerfile
รัน Image ด้วยคำสั่งดังนี้
docker run -d -p 8000:80 3.php
คำสั่งข้างบนจะเป็นการรัน Docker Image ด้วย Detached Mode (-d
option) เพื่อรัน process ใน background ทำให้เราสามารถพิมพ์คำสั่งอื่นได้ใน Terminal โดยทำการ Map TCP Port 8000 ที่เราเปิดผ่าน Firewall ไว้แล้วตามบทความข้างล่างนี้ เชื่อมไปยัง Docker Port 80 นะครับ
ทดลอง curl
ดูผลลัพธ์ในเครื่อง
curl http://localhost:8000
ทดลองเข้าเว็ปผ่าน Chrome ที่เครื่อง Windows ของผมเอง
http://ponggun-kku-vm.southeastasia.cloudapp.azure.com:8000/
Lab 4: ASP.NET Core
ศึกษาการใช้งาน Docker File ร่วมกับ ASP.Net Core
ทั้งการ Download Dependencies, Release Website, Run Website
เข้า Folder 4.aspnetapp ด้วยคำสั่ง
cd 4.aspnetapp
เราจะเจอ Dockerfile
ดังภาพนี้
Docker Multi-stage Builds
- เราจะพบว่า Dockerfile ของ Lab 4 นี้แตกต่างจาก Lab 1–3 ตรงที่มี FROM 2 ครั้ง และครั้งที่ 2 จะใช้ คำสั่ง
COPY — from=build
ซึ่งเป็นการเรียก Stage ที่เราพึ่งสร้างมาจากคำสั่ง FROM ในบรรทัดแรก - เราเรียกสิ่งนี้ว่า Multi-stage Builds ซึ่งเป็น Feature ที่มาตั้งแต่ Docker 17.05
- เพราะจริงๆแล้ว Images ของเราไม่จำเป็นต้องมีหลายๆ Stage พร้อมกันใน Docker Image 1 File เช่น Build Stage ทำแค่ครั้งเดียว ต่างจาก Stage การ Run ซึ่งเป็นชุดคำสั่งที่มีโอกาสใช้บ่อยมากกว่า ถ้าเราแยกเป็น Stage เช่น Build, Run ออกจากกันก็จะช่วยลดขนาด Image ให้มีขนาดเล็กลงได้ครับ
Dockerfile
- Line#1: Pull Docker Image .Net Core SDK เพื่อใช้ Build Source Code และ Release Website (Stage name = build)
- Line#2: กำหนด Working Directory ให้กับ Docker ที่ Path /app
- Line#5: Copies file .csproj มาที่ Docker Path /app
- Line#6: สั่ง Restore Nuget Packages (เป็น Package Management ของทาง Microsoft คล้ายๆ npm ของ Node.js)
- Line#9: Copy source code ทั้งหมดไปที่ Docker Path /app
- Line#10: สั่ง Build Source Code และ Publish Website ไปยัง Docker Path /app/out
- Line#13:Pull Docker Image .Net Core Runtime เพื่อใช้ Build Source Code และ Release Website
- Line#14: กำหนด Working Directory ให้กับ Docker ที่ Path /app
- Line#15: Copy files จาก path /app/out ซึ่งเป็น path ที่เก็บ Release Website อยู่ (ปรกติเวลา Release Website ของ ASP.Net Core เราจะได้ DLL File เช่นในตัวอย่างนี้คือ aspnetapp.dll)
- Line#16: สั่งรัน Website
เราจะรู้ได้อย่างไรว่าต้องเขียน Dockerfile แบบไหน
ใน Lab นี้ผมนำรายละเอียดมาจาก khajonpot sawangdee และศึกษาเพิ่มเติมจาก
สร้าง Docker Image จาก Dockerfile
docker build -t ponggun/aspnetcore-hello .
สังเกตุว่า Image จะแตกต่างจากรอบก่อนนิดหน่อย เพราะว่าทำการระบุ user ลงไปยัง Image ด้วย (ponggun) เพื่อเตรียมที่จะ Push เข้า Docker Hub
ดู Images ที่สร้างด้วยคำสั่ง
docker images
- จะพบว่า Image ของเรา (265MB) มีขนาดเล็กกว่า Image ที่รวม Build & Runtime (1.75GB)
รัน Image ด้วยคำสั่งดังนี้
docker run -p 8000:80 ponggun/aspnetcore-hello
ตัว ASP.Net Core นี้จะรันอยู่ที่ Port 80 เพราะงั้นเราเลยต้อง Map Port มาที่ Docker Port 80 ครับ
ดูผลลัพธ์
Bonus Labs จาก Course Jump into DevOps World
Docker Multi-stage Builds
- ขั้นตอนแรกให้ Clone Source Code ที่ Repo ข้างล่างนี้และเข้าไปที่ Folder Jump_into_DevOps_world/ch_docker/docker_multi_stage
git clone https://github.com/Ima8/Jump_into_DevOps_world
cd Jump_into_DevOps_world/ch_docker/docker_multi_stage/
รายละเอียดของ Docker File
- Stage แรก (myBuilder) จะทำการ Build Vue.Js Example Project ด้วย Node.js Image ซึ่งจะได้ผลลัพธ์ที่ Path /app/dist
- Stage ที่สองจะทำการ Copy Files จาก Stage แรกใน Path /app/dist มาที่ Path ของ Nginx Image เพื่อรัน Website
- สั่ง Build Image
docker build -t ponggun/vuedemo .
- สังเกตุว่า Image ที่ได้มีขนาดเล็กกว่า Images ของ nginx + node ซึ่งเป็นข้อดีของการใช้ Multi-stage ครับ เพราะว่า File Size = Nginx Image + Vue Demo Website (ไม่ได้เอา Node Image มาด้วย)
Docker Volumes
- ใช้เพื่อ Share Files ระหว่าง Host System และ Docker Container
- ตัวอย่างใน Lab นี้จะเป็นการรัน Website ที่พัฒนาด้วย Node.js ซึ่ง User สามารถที่จะ Upload File ได้
- ซึ่งถ้าเราไม่ทำการ Copy Files เหล่านี้ออกมาที่ Host แล้วเก็บไว้แค่ใน Docker Container เมื่อเราปิด Container ไป Uploaded File ก็จะหายไปด้วยครับ เพราะงั้นเราจึงทำการ ใช้ Docker Volume สำหรับการเก็บ Upload Files
แต่ในระบบงานจริง ควรแยก Files ไปเก็บไว้ใน Files Server เช่น Amazon S3, Azure Storage Accountจะดีกว่า ซึ่งจะช่วยให้เรา Scale ระบบได้ง่ายขึ้นครับ
- ขั้นตอนแรกให้ Clone Source Code มาก่อนที่
git clone https://github.com/Ima8/node-express-multer-image-upload.git
cd node-express-multer-image-upload
- ใน Repository นี้จะใช้ Folder ชื่อ uploads เป็นที่เก็บ Uploaded Files จาก Docker มายัง Host System
- ลองดูว่ามี Path นี้อยู่จริงไหม
cd uploads
pwd
- ก่อนที่จะเริ่มเขียน Dockerfile เราต้องเข้าไปศึกษาก่อนว่า Project ที่เรากำลังทำอยู่นั้นมีการ Map Port, Volume ที่ไหนบ้าง ในตัวอย่างนี้คือให้ดูที่ Readme ของ Repository และ Source Code (app.js) เลยครับ
- เราจะพบว่า Project นี้ใช้ Port 3333 และมี Docker Path ในการเก็บ Uploaded Files ที่ Path ./uploads
- สร้าง Dockerfile ด้วย Command ดังนี้
- Build Docker Image
docker build -t ponggun/nodeupload .
- Run Docker Image
docker run \
-p 80:3333 \
-v /home/ponggun/node-express-multer-image-upload/uploads:/app/uploads \
ponggun/nodeupload# ทำการ Mount จาก External path
# /home/ponggun/node-express-multer-image-upload/uploads
#ไปที่ Docker Path
# /app/uploads
- ดูผลลัพธ์จะพบว่า เมื่อเรา Upload Files จากหน้า Web แล้ว Host System จะได้ Uploaded File นั้นมาเก็บไว้ด้วยครับ
Docker Compose
เวลาที่เราต้องรัน Docker Image เราจะต้องเขียนคำสั่งเพิ่มอีกหลายอย่าง เช่น การ Map Port หรือ Map Volume ทุกครั้งที่รัน Docker Image
เราสามารถที่จะลดรูปของคำสั่งลงได้ด้วยการใช้ Docker Compose ครับ ซึ่งในบทความนี้จะยังไม่ได้อธิบายการทำงานอย่างละเอียดเพราะอยากชี้ให้เห็นถึงข้อดีของการใช้ Docker Compose ไว้ก่อนครับ และจะเขียนถึง Docker Compose อีกทีในบทความถัดไป
เราจะมาลองเปลี่ยนการรัน Docker Image จาก Lab Docker Volume ด้วย Docker Compose กันครับ
- ถ้ายังไม่ได้ติดตั้ง Docker Compose, ให้ทำการติดตั้งให้เรียกร้อยก่อนครับ (ดูขั้นตอนข้างบนสุดมีอธิบายไว้ครับ)
- สร้าง docker-compose.yml และใส่ Command ดังนี้
- สั่ง Build และ Run Docker Image (ตาม Dockerfile ที่เราเขียน)ภายใน Docker Compose ด้วยคำสั่ง เท่านี้ก็สามารถรัน Website ได้แล้วครับ
docker-compose up
- เปรียบเทียบความสะดวกในการสั่ง Build & Run ด้วย Docker Compose
Docker Cheat Sheet
จัดการ Images
docker images #View docker images
docker image ls #View docker images
docker build -t {name}:{tag} . #Build image
docker image rm {docker image name/ image id} #remove image
docker run -p {server port}:{docker port} -d {image name}:{image tag} #Run image
จัดการ Container
docker ps #List running containers
docker ps -a #List all containers
docker start {docker container id} #start container
docker stop {docker container id} #stop container
docker rm {docker container id} #remove container
docker container rm -f $(docker ps -aq) #remove all container
จัดการ Docker Compose
docker-compose start #start docker compose
docker-compose stop #stop docker compose
docker-compose pause #pause running containers
docker-compose unpause #unpause running containers
docker-compose ps #List containersdocker-compose up
#Builds,creates,starts,attaches to containersdocker-compose down
#Stops containers and removes containers, networks, volumes, and images created by up
Tips
หลังจากที่เราทำความเข้าใจกับ Docker Images, Docker Container แล้ว เรายังสามารถเสริมการทำงานให้คล่องตัวได้มากขึ้นอีกด้วย Docker Compose และ Makefile น่ะครับ ใครสนใจลองติดตามได้ในบทความนี้น่ะครับ
ศึกษาเพิ่มเติม
สรุป
Docker ช่วยเราในการจัดการกับ Environment ได้มาก รวมถึงยังนำไปต่อยอดกับ Tools อื่นๆเพื่อทำให้งาน DevOps ของเราง่ายขึ้นครับ ซึ่ง Lab ในบทความนี้เเราจะได้ทดลองเตรียม Environment เพื่อรันทั้ง Bash, PHP, .Net Core, Node.js และ Nginx
ขอบคุณผู้อ่านทุกท่านครับ
นายป้องกัน