ฝึกวิชา Docker Image & Container

Ponggun
T. T. Software Solution
10 min readDec 11, 2019

หลังจากที่ได้ติดตั้ง Docker เรียบร้อยแล้ว ในขั้นต่อไปจะเป็นการศึกษาการใช้งาน Docker File ให้เข้าใจมากขึ้น เพื่อเป็นพื้นฐานในการต่อยอดในส่วนอื่น

ภาพประกอบจาก practical-guide-on-writing-a-dockerfile

ขอขอบคุณพี่เอก 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 ได้ทันที

ภาพจาก Course Jump into DevOps World

ขั้นตอนการทำงาน

  1. สร้าง Dockerfile และใส่ชุดคำสั่งในการเตรียม Environment ซึ่งในขั้นตอนนี้จะต้องมีการ Pull Docker Image เข้ามาใช้ก่อนอย่างน้อย 1 Docker Image เสมอ
  2. สร้าง Docker Image จาก Dockerfile
  3. สร้าง Docker Container จาก Docker Image และสั่งรันDocker Container เพื่อใช้งาน Service หรือ Application
ภาพจาก Course Jump into DevOps World

รายละเอียด 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
new 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 Hub

สร้าง Docker Image จาก Dockerfile

docker build -t 3.php .

ดู Images ที่สร้างด้วยคำสั่ง

docker images

เราจะพบว่าเราได้สร้าง Docker Image เพิ่มมาทั้งหมด 2 Images

  1. php — เป็น Image ที่เรา Pull มาใน Dockerfile (Size ใหญ่เชียว T_T)
  2. 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

ภาพจาก Course Jump into DevOps World
  • เราจะพบว่า 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 ให้มีขนาดเล็กลงได้ครับ
ภาพจาก Course Jump into DevOps World

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 ครับ

ดูผลลัพธ์

รัน ASP.Net Core ได้แล้วครับ ^^

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
ภาพจาก Course Jump into DevOps World
  • ตัวอย่างใน Lab นี้จะเป็นการรัน Website ที่พัฒนาด้วย Node.js ซึ่ง User สามารถที่จะ Upload File ได้
ขอบคุณภาพจาก GitHub ของอาจารย์ Issaret Prachitmutita
  • ซึ่งถ้าเราไม่ทำการ 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
ในตัวอย่างนี้จะมี Upload Path อยู่ที่ /home/ponggun/node-express-multer-image-upload/uploads
  • ก่อนที่จะเริ่มเขียน 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

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 containers
docker-compose up
#Builds,creates,starts,attaches to containers
docker-compose down
#Stops containers and removes containers, networks, volumes, and images created by up

สรุป

Docker ช่วยเราในการจัดการกับ Environment ได้มาก รวมถึงยังนำไปต่อยอดกับ Tools อื่นๆเพื่อทำให้งาน DevOps ของเราง่ายขึ้นครับ ซึ่ง Lab ในบทความนี้เเราจะได้ทดลองเตรียม Environment เพื่อรันทั้ง Bash, PHP, .Net Core, Node.js และ Nginx

ขอบคุณผู้อ่านทุกท่านครับ

นายป้องกัน

--

--

Ponggun
T. T. Software Solution

Development Manager, Web Developer with ASP.Net, ASP.net Core, Azure and Microsoft Technologies