👤
Novice Handbook
  • Novice Handbook
  • Guide
  • Internet และ Web
    • HTML
  • Computer Language
    • Basic Computer Language (LV.1)
    • C Language (LV.1)
    • Python3 (LV.1)
  • Operating System
    • Linux
      • Basic Linux (LV.1)
  • TOOLS
    • Text Editor
      • Vim Editor
    • Source Control
      • GitLab
        • GitLab for small site (LV.1)
    • Container
      • Docker
        • Docker (LV.1)
        • Docker (LV.2)
      • Kubernetes
        • Kubernetes Intro (LV.0)
        • Kubernetes Basic (LV.1)
        • Kubernetes Intermediate (LV.2)
        • Helm (LV.2)
        • RKE2 (LV.3)
        • K3S (LV.3)
        • K3D (LV.3)
    • Repository
      • Harbor
        • Harbor for small site (LV.1)
        • Harbor for enterprise (LV.2)
    • Database
      • Redis
        • Redis on Docker Compose (LV.1)
        • Redis on Kubernetes (LV.2)
      • Elastic Stack
        • Elasticsearch & Kibana for small site (LV.1)
    • Observability
      • Prometheus
        • Prometheus for small site (LV.1)
        • Prometheus Operator (LV.2)
    • Security
      • Certbot (LV.1)
      • Falco
      • Hashicorp Vault
    • Collaboration
      • Nextcloud
Powered by GitBook
On this page
  • Requirement
  • Docker คืออะไร
  • ข้อแตกต่างระหว่าง Docker และ VM
  • ประโยชน์จากการใช้ Docker
  • การแสดงข้อมูลทั่วไปของ Docker
  • เริ่มต้นใช้งาน Container
  • Container เป็นเพียง process บนเครื่อง
  • ขั้นตอนที่เกิดขึ้นเมื่อสั่ง docker run
  • Docker Hub
  • การหาชื่อ Docker Image ที่ใช้ในการ pull
  • การเลือก tag ของ Docker Image
  • ข้อแตกต่างระหว่าง Slim และ Alpine
  • ตัวอย่าง option สำหรับคำสั่ง docker run
  • Detached (-d)
  • Expose (-p / --publish)
  • Name (--name)
  • Image[:tag]
  • Restart Policy (--restart)
  • การตรวจสอบ container
  • การดู process ของแต่ละ container
  • การตรวจสอบการใช้งาน resource
  • การตรวจสอบ log ของ container
  • การดูรายละเอียด configuration ของ container
  • การรันคำสั่งภายใน container
  • การ shell เข้าไปภายใน container
  • Docker network
  • Network driver
  • การใช้งาน Docker Network
  • การใช้งาน DNS ภายใน Docker network
  • Docker Image
  • การสร้าง Docker Image
  • Instruction ที่ใช้ภายใน Dockerfile
  • Docker Storage
  • Volumes
  • Bind mounts
  • Docker Compose
  • ตัวอย่าง compose file
  • การ Deploy stack ด้วย Docker Compose

Was this helpful?

  1. TOOLS
  2. Container
  3. Docker

Docker (LV.1)

Docker Basic

Requirement

  • ต้องมีพื้นฐานความรู้ Linux เบื้องต้น เช่น shell script, file & folder, permission, Linux package management เป็นต้น ก่อนเรียนเนื้อหาส่วนนี้

Docker คืออะไร

Docker เป็น Platform ที่ช่วยเราในด้านการพัฒนา ส่งมอบ และรันแอพพลิเคชั่น Docker จะช่วยให้เราแพ็คและรันแอพพลิเคชั่น ภายใต้ environment ที่เรียกว่า container ซึ่งจะไม่ส่งผลกระทบต่อ container อื่นๆได้ ซึ่งนั่นจะทำให้เราสามารถมีหลายๆ container ทำงานพร้อมกันภายใต้ host เดียวกันได้

ข้อแตกต่างระหว่าง Docker และ VM

Virtual Machine (VM) เป็นการจำลอง hardware ของ host ให้กับ guest VM ทำให้เราสามารถมีเครื่องในเชิง logical หลายๆเครื่องได้บน physical server เดียว แต่การจะสร้าง VM ได้ เราต้องมี software ที่ทำงานในการจำลอง hardware ซึ่งเรียกว่า Hypervisor และหลังจากจำลอง hardware แล้ว เราจะต้องติดตั้ง Operating System (OS) ลงบน guest VM นั้นอีก แล้วจึงนำ library (lib) และแอพพลิเคชั่นมาติดตั้ง

Docker ทำงานโดยมี Docker Engine ทำงานอยู่บน Host OS และ container ที่ประกอบด้วย lib และแอพพลิเคชั่น มารันงานอยู่บน Docker Engine การทำงานของ Container บน Docker จึงเหมือน process บน OS มากกว่าการเป็น VM

Virtual Machine

Docker

Heavyweight

Lightweight

แต่ละ VM จะมี OS ของตัวเอง

แต่ละ container ใช้งาน kernel ของ host ร่วมกัน

ใช้เวลาในการเริ่มทำงานของแอพพลิเคชั่นในระดับนาที

ใช้เวลาในการเริ่มทำงานของแอพพลิเคชั่นในระดับมิลลิวินาที

ใช้ Memory เยอะกว่า เนื่องจากเสีย overhead ให้กับ Hypervisor และ Guest VM

ใช้ Memory น้อยกว่า ทำให้ Container ได้ Memory ไปเกือบทั้งหมด

ใช้พื้นที่ Disk เยอะกว่า เสีย overhead ซ้ำซ้อนให้แต่ละ guest OS

ใช้พื้นที่ Disk น้อยกว่า มีการเก็บข้อมูลเป็น layer และจะไม่เก็บส่วนที่ซ้ำกัน

มีการแยกจากกันชัดเจน

แยกกันในระดับ process

ประโยชน์จากการใช้ Docker

  • ช่วยให้การพัฒนาแอพพลิเคชั่นเป็นไปอย่างรวดเร็ว

  • ความสามารถในการย้ายข้ามเครื่อง

  • version control และการใช้ซ้ำ

  • การแชร์

  • lightweight และมี overhead น้อย

  • ทำให้การดูแลง่ายขึ้น

การแสดงข้อมูลทั่วไปของ Docker

docker version

command จะแสดง version ของ Docker, containerd และ runc ที่ติดตั้ง

docker info

จะแสดงข้อมูลทั่วไปของระบบ เช่น จำนวน container ในระบบ จำนวน container image หรือแม้กระทั่งจำนวน cpu, memory และประเภท cpu architecture ของระบบ

เริ่มต้นใช้งาน Container

docker run -p 80:80 nginx

การรันคำสั่ง docker run คือการนำ Docker Image มาสร้างเป็น container และเริ่มทำงาน จะพบว่าหากลองเข้าเว็บ site ด้วย ip ของเครื่องที่รัน container นี้อยู่ จะแสดงหน้าเว็บ nginx ขึ้นมา แต่จากตัวอย่างข้างต้น container ที่สร้างขึ้นมาจะทำงานเป็น foreground mode ทำให้ไม่มีการ return prompt กลับมา และหากเราสั่ง Ctrl+C เพื่อออก หรือรันผ่าน ssh แล้วมีการ disconnect ไป ก็จะทำให้ container ถูก kill ไปด้วย

ให้ออก แล้วรันด้วยคำสั่งใหม่ดังนี้

docker run -p 80:80 -d nginx

การเพิ่ม option -d ลงไปทำให้ container รันใน detach mode และจะ return prompt กลับมาให้เรา หากมีการรันผ่าน ssh เวลา disconnect ไป container ก็ยังทำงานอยู่ตามปกติ

ข้อควรระวัง option ต่างๆ ต้องใส่อยู่ก่อนชื่อ container image เพราะ syntax ใดๆที่ตามหลังชื่อ container image นั้นจะเป็นส่วนของ command ที่ pass ให้กับ container ทั้งหมด

เราสามารถดูได้ว่ามี container ใดรันอยู่บ้างในระบบได้ด้วยคำสั่งดังนี้

docker ps

คำสั่งจะแสดง container ที่ทำงานอยู่เท่านั้น

docker ps -a

การเพิ่ม option -a หลังคำสั่ง docker ps จะทำให้แสดง container ทั้งหมด รวมถึง container ที่ไม่ได้ทำงานอยู่ด้วย

docker container ls     # output same as docker ps
docker container ls -a  # output same as docker ps -a

คำสั่ง docker container ls และ docker container ls -a จะให้ผลเหมือนกับ docker ps และ docker ps -a ตามลำดับ

docker container stop container_name_or_id
docker container start container_name_or_id

คำสั่ง docker container stop ตามด้วยชื่อ container หรือ container id นั้นๆ จะใช้หยุดการทำงานของ container และคำสั่ง docker container start ตามด้วยชื่อ container หรือ container id นั้นจะทำให้ container กลับมาทำงานใหม่

docker container rm container_name_or_id

คำสั่ง docker container rm ตามด้วยชื่อ container หรือ container id นั้น จะลบ container นั้นทิ้ง docker container rm จะลบได้เฉพาะ container ที่ไม่ได้ทำงานอยู่เท่านั้น หากจะลบ container ที่ทำงานอยู่ ต้องใส่ option -f เพิ่มไปด้วย เพื่อ force ลบ container ทิ้ง

ข้อควรระวัง การใส่ option -f เสมอเวลาทำการลบ container ต่างๆ เป็นสิ่งที่ไม่แนะนำ เพราะจะเกิดเป็นความเคยชิน และส่งผลเสียได้ หากลบ container ผิดตัวโดยไม่ได้ตั้งใจ

Container เป็นเพียง process บนเครื่อง

docker run --name mongo -d mongo
ps aux |grep mongod

คำสั่งข้างต้นจะทำการสร้าง container ซึ่งรัน MongoDB ขึ้นมา และ list process ในระบบที่มีคำว่า mongod

docker stop mongo
ps aux |grep mongod

หยุดการทำงานของ container ชื่อ mongo ที่สร้างไว้จากคำสั่งก่อนหน้า และลอง list process อีกครั้ง จะพบว่าบน host นั้น ไม่มี process ชื่อ mongod อยู่แล้ว

ที่เป็นเช่นนั้นเพราะ container เป็นเพียง process หนึ่งที่ทำงานอยู่บน host เท่านั้น และมี isolation เพียงระดับ container ด้วยกันเองเท่านั้น ดังนั้นจากบน host จึงยังเห็น process ที่ทำงานอยู่ภายในแต่ละ container อยู่ จะต่างจาก VM ที่ process ภายใน VM guest กับ process บน Host แยกออกจากกันชัดเจน

การที่บน host เห็น process ของภายในแต่ละ container ไม่ถือเป็นเรื่องไม่ปลอดภัย เพราะในการใช้งานร่วมกันหลาย user บน production นั้น เราจะออกแบบระบบแยกแต่ละ user ไม่ได้ให้ใครเข้าถึง root ได้ และแต่ละ user จะถูก limit สิทธิ์ไว้ให้สร้าง container ของตนเองเท่านั้น เมื่อแต่ละ container ถูกจำกัดสิทธิ์ไม่สามารถเข้าถึงข้อมูลของ container อื่นได้โดยตรง ก็จะทำให้ระบบปลอดภัย

container จะถูกจำกัดสิทธิ์ด้วยสิ่งที่เรียกว่า cgroups และ network namespace

Cleanup

docker rm mongo
docker volume prune -f

ขั้นตอนที่เกิดขึ้นเมื่อสั่ง docker run

  • engine ตรวจสอบว่ามี container image ที่เรียกใช้นั้น อยู่ภายใน image cache ของระบบหรือไม่

  • หากไม่พบ image ภายใน image cache ก็จะไปหาจาก remote image repository ซึ่งโดย default นั้นจะเป็น Docker Hub

  • หากเจอ image บน remote image repository ก็จะ download ลงมาเก็บที่ image cache ในกรณีที่ไม่ได้ระบุ tag ของ image จะถือว่าใช้ tag latest

  • สร้าง container จาก container image นั้น และเตรียมเริ่มทำงาน

  • allocate virtual ip จาก Docker network

  • หากมีการใส่ option -p ให้ publish port ไว้ จะเปิด port บน host และทำ forward ไปยัง port ภายใน container

  • container เริ่มทำงานตาม CMD ที่กำหนดไว้

การ forward port ของ Docker จะใช้ iptables ดังนั้นหากมีการปิด service iptables ไว้ จะทำให้ network ของ Docker มีปัญหา

หาก Server มีการทำ Hardening ส่วน sysctl ต้องระวัง ไม่ให้มีการไป disable การ forward packet

net.ipv4.ip_forward = 1 net.ipv4.conf.all.forwarding = 1

การสั่ง docker run พร้อม option -p นั้น ตามขั้นตอนจะมีการสร้าง container ขึ้นมาก่อน แล้วจึงค่อยทำ forward port ดังนั้นหากมีการ publish port ชนกับ port ที่ host ใช้งานอยู่ ตัว container ที่สั่งสร้างขึ้นมาก็ยังคงอยู่เช่นเดิม แต่ส่วนที่ forward port เท่านั้นที่มีปัญหา ดังนั้นเวลาเกิดกรณีเช่นนั้น อย่าลืมลบ container ก่อนสั่งสร้างใหม่อีกครั้งด้วย

Docker Hub

Docker Hub เป็น Default remote repository ของ Docker engine เมื่อเราติดตั้งมา ดังนั้นคำสั่ง docker run หรือ docker image pull ต่างๆ ที่อ้างอิงถึง image ซึ่งเราไม่ได้ระบุชื่อ repository ก็จะอ้างอิงถึง remote repository จาก Docker Hub

ภายในจะประกอบด้วย Repository ต่างๆ แบ่งประเภทหลักๆ ได้ดังนี้

  • Official Images Repository - คือ Repository ที่เก็บ Image ซึ่ง publish มาโดยทาง Docker เอง

  • Verified Publisher Repository - คือ Repository ที่เก็บ Image จากองค์กรที่เชื่อถือได้ เช่น mysql ของบริษัท Oracle

  • Individual Repository - คือ Repository ของผู้ใช้ทั่วไป ที่บางส่วนก็เปิดเป็น public repository ไว้ ให้คนอื่นสามารถนำ Docker Image ของตนไปใช้ได้

Official Image จะสามารถ pull ด้วชื่อ Image ได้เลย ไม่ต้องใส่ชื่อ account นำหน้า เช่น nginx, mysql, redis Verified Publisher และ Individual จะมีชื่อ account ขององค์กรนำหน้าและ slash ตามด้วยชื่อ Image เช่น bitnami/redis, amazon/aws-cli

ในเชิงของความน่าเชื่อถือ ปกติเราจะใช้ Docker Image จาก Official และ Verified Publisher เท่านั้น หรือไม่ก็ใช้ account ขององค์กรตัวเองเลย

การใช้ private repository จาก account ตัวเองบน Docker Hub จะต้อง login account บนเครื่องก่อน จึงจะสามารถ pull image ไปใช้งานได้

การหาชื่อ Docker Image ที่ใช้ในการ pull

การเลือก tag ของ Docker Image

ถ้าไม่กำหนด tag เวลา pull จะใช้ tag latest เสมอ ซึ่งในบางครั้งเราอาจจะไม่ต้องการเช่นนั้น เพราะ tag latest อาจจะไม่ใช้ stable version เสมอไป

Repository ที่ดีมักจะมีเขียนในส่วน Document ไว้อยู่ว่า ปัจจุบัน tag image ใดบ้างที่ตรงกัน เช่น tag 1.21.3 ก็จะตรงกับ tag mainline, tag 1.21 และ tag latest tag 1.20.1 ก็จะตรงกับ tag stable, tag 1.20 และจะมี link ที่โยงไปถึง Dockerfile ว่า แต่ละ Image ใช้คำสั่งใดบ้างในการสร้างขึ้นมา

ในที่นี้ หากเราต้องการ maintain ระบบให้เป็น stable ล่าสุดเสมอ ก็อาจจะใช้ tag stable แทน แต่หากเราต้องการทดสอบ software version ใหม่ล่าสุดเสมอ ก็อาจจะใช้ tag latest หากระบบใดที่เป็น production ก็อาจจะต้องการ maintain tag ซึ่ง freeze เลข major กับ minor version แต่ให้ patch version เปลี่ยนได้เรื่อยๆ ก็อาจจะใช้ tag 1.20 หากระบบใดต้องการลดความเสี่ยงจากการ update patch ก็อาจจะ freeze ไปถึงเลข patch version ก็จะใช้ tag 1.20.1 ก็เป็นได้ ขึ้นกับ policy แต่ละองค์กร

ในบางครั้งเราอาจจะพบว่า tag ปกติมีขนาดใหญ่มาก เพราะ build มาจาก Base Image ที่ใหญ่ จะมีบาง software ที่ใช้ tag ซึ่งมีคำว่า slim หรือ alpine ด้วย ซึ่ง Image ประเภทนี้จะมีการใช้ Base Image ซึ่งตัด binary และ lib หลายตัวออกไป ทำให้ขนาด Image เล็กลง

ข้อแตกต่างระหว่าง Slim และ Alpine

tag alpine เป็น Base Image ที่มี OS เป็น Alpine Linux และจะแทบไม่ได้ติดตั้ง binary หรือ lib อะไรมาเลย ดังนั้นจะเล็กมาก

tag slim เกิดขึ้นมาทีหลัง tag alpine และต่างจาก tag alpine เพราะไม่ได้ใช้ OS Alpine Linux แต่จะเป็น Base OS แบบ Linux ตามทั่วไปอย่าง CentOS หรือ Debian ซึ่งนิยมใช้ในการติดตั้ง Software มาตั้งแต่สมัยก่อนแล้ว แต่มีการตัด binary และ lib ส่วนใหญ่จาก Base OS ของ CentOS หรือ Debian ปกติ ทำให้มีขนาดเล็กลงจนใกล้เคียง Alpine

ในสมัยก่อนคนส่วนใหญ่จะชอบใช้ Alpine มาก เพราะมีขนาดเล็ก และเชื่อว่าการมี binary และ lib น้อย จะช่วยให้มีปัญหาช่องโหว่น้อยลงไปด้วย แต่ Alpine เองก็สร้างปัญหาให้กับผู้พัฒนา software 3rd party เช่นกันในด้านการ maintain เพราะเป็น Linux คนละชนิด มีช่องโหว่และ bug ในแบบของตัวเอง ทำให้เกิดงานที่ต้องดูแลเพิ่มขึ้น

ภายหลังทาง Community จะแนะนำดังนี้ หากระบบไม่ได้มีปัญหาเรื่อง space มากนัก เช่น ไม่ได้ deploy บนระบบ IOT ก็ให้ใช้ tag ปกติที่มีขนาดใหญ่ หากระบบมีปัญหาเรื่อง space จำกัด ก็ให้ใช้ tag slim

ตัวอย่าง option สำหรับคำสั่ง docker run

docker run --restart unless-stopped --publish 8080:80 --name demo -d nginx:1.18

Detached (-d)

ให้ container ทำงานใน detached mode หากไม่ใส่ option นี้ container จะทำงานใน foreground mode

Expose (-p / --publish)

Bind host port เข้ากับ container port จากตัวอย่าง 8080:80 จะหมายถึง daemon จะสร้าง port 8080 บน host และจะ forward network packet ที่เข้ามายัง port 8080 ไปยัง port 80 ของ container

Name (--name)

กำหนดชื่อ container หากไม่กำหนดมา daemon จะ random ชื่อให้

Image[:tag]

กำหนดให้ใช้ image ตาม tag version ที่กำหนด จากตัวอย่างจะเป็น nginx version ที่มี tag เป็น 1.18

Restart Policy (--restart)

กำหนด restart policy สำหรับ container

Policy
ผลลัพธ์

no

เป็นค่า default กำหนดให้ไม่ต้อง restart container เมื่อ process หยุดการทำงาน

on-failure

กำหนดให้ restart เมื่อ container หยุดการทำงานและมี exit code ที่ไม่ใช่ศูนย์

always

กำหนดให้ restart ทุกครั้ง โดยไม่สนใจ exit code การกำหนดค่านี้จะทำให้ container เริ่มทำงานด้วยเมื่อ daemon startup

unless-stopped

กำหนดให้ restart container ทุกครั้ง โดยไม่สนใจ exit code รวมถึงเมื่อ daemon startup ขึ้นมาด้วย ยกเว้นแต่ว่ามีการสั่ง stop container ไป ก่อนสั่ง stop daemon

โดยปกติจะใช้ unless-stopped

Cleanup

docker rm -f demo

การตรวจสอบ container

สร้าง container หนึ่งขึ้นมา สำหรับใช้ทดสอบในหัวข้อนี้ ด้วยคำสั่งดังนี้

docker run -d --name nginx -p 80:80 nginx

การดู process ของแต่ละ container

docker top nginx

การตรวจสอบการใช้งาน resource

docker stats

การตรวจสอบ log ของ container

docker logs nginx

การดูรายละเอียด configuration ของ container

docker inspect nginx

การรันคำสั่งภายใน container

docker exec nginx ip a

การ shell เข้าไปภายใน container

docker exec -it nginx sh

การ shell เข้าไปภายใน container มีไว้เพื่อ diagnosis ปัญหา ไม่ได้มีไว้เพื่อให้เราเข้าไปเปลี่ยนแปลงไฟล์ภายใน container โดยตรง

หากเราต้องการเปลี่ยนแปลง content ของไฟล์บางอย่างภายใน container เราควร build image ใหม่ และ deploy container จาก image ที่ถูกแก้ไขเป็น version ใหม่นั้น

Cleanup

docker rm -f nginx

Docker network

โดย default หากสร้าง container โดยไม่ระบุ network แล้ว container จะ connect ไปยัง bridge network ที่ถูกสร้างมาตั้งแต่ติดตั้ง Docker Engine

container ใดก็ตามที่อยู่ network เดียวกัน จะสามารถ access ถึงกันได้ ผ่าน private ip โดยไม่ต้องมีการ expose ด้วย option -p

เบื้องหลังการทำงานของ Docker network นั้น คือการใช้ NAT ของ service iptables แต่ผู้ใช้ไม่จำเป็นต้องรู้ว่า iptables ทำงานอย่างไร และต้องสั่ง command อะไรบ้าง ผู้ใช้เพียงแค่สั่งสร้าง Docker network ตามชนิด driver ที่มีให้ก็เพียงพอ

Network driver

Docker มี built-in driver หลายชนิดให้เลือกใช้ แต่ที่ใช้กันหลักๆ มีไม่กี่ชนิด ดังนี้

Bridge Networks

เป็น default driver หากมีการสั่งสร้าง Docker network โดยไม่มีการระบุชนิด driver

ภายในแต่ละ network จะเป็นอีก subnet หนึ่ง และ container ที่ connect เข้า network นั้นจะได้ private ip จาก network นั้น container หนึ่งๆ สามารถ connect เข้ากับหลาย network ได้

หากมีการกำหนด expose โดย option -p จะมีการ forward traffic จาก port ของ host มายัง port ภายใน container

Host Networks

จะทำให้ network isolation ระหว่าง host และ container หายไป และให้ container ใช้งาน network ของ host ได้โดยตรง container จะมี ip ของ host นั้น และ port ที่ถูกใช้โดย container จะเท่ากับว่าถูกเปิดบน host นั้น ในทางตรงกันข้าม host จะสามารถ access ไปยัง container นั้นได้ด้วยคำว่า localhost ได้

Overlay Networks

จะเชื่อม Docker daemon จากหลายๆ เครื่องเข้าด้วยกัน ผลลัพธ์จะคล้ายกับ Bridge Networks แต่จะทำให้หลายๆ host มองเห็น network เป็นผืนเดียวกัน ทำให้ container ที่อยู่ต่าง host กัน สามารถเชื่อมต่อถึงกันได้ ผ่าน private ip ของ network นั้น ปกติใช้กับ swarm cluster

การใช้งาน Docker Network

docker network ls

จะเป็นการแสดง Docker network ทั้งหมดที่มีในระบบ

docker network create --driver bridge my_bridge

จากตัวอย่างจะเป็นการสร้าง network ขึ้นมาใหม่ชื่อ my_bridge โดยใช้ driver ประเภท bridge

docker network inspect my_bridge

คำสั่ง docker network inspect ตามด้วยชื่อ network ใช้ดูรายละเอียดของ network นั้นๆ

docker run -d -p 80:80 --name nginx --network my_bridge nginx

option --network และระบุชื่อ network จะทำให้ container มาใช้ network ตามที่ระบุ

Cleanup

docker rm -f nginx
docker network rm my_bridge

การใช้งาน DNS ภายใน Docker network

จากตัวอย่างที่ผ่านๆมา จะเป็นการสร้าง container ขึ้นมา และกำหนด network ให้กับ container เพื่อ isolate ว่า container ใดบ้างที่ connect ถึงกันได้ แต่การ connect ถึงแต่ละ container โดยการใช้ private ip นั้น ขาดความสะดวก และขาดความยืดหยุ่นในการใช้งาน หาก container ถูกลบทิ้ง และสร้างขึ้นมาใหม่ ก็จะทำให้ ip address เปลี่ยนแปลงไปได้ ดังนั้นตาม best practice ควรจะอ้างอิงถึง container แต่ละตัวด้วย DNS

การใช้งาน DNS ภายใน Docker network จะทำได้ต่อเมื่อ network นั้นเป็น custom network ที่เราสร้างขึ้นมา network default ต่างๆที่มากับการติดตั้ง Docker Engine นั้น จะไม่สามารถใช้ DNS ได้

docker network create --driver bridge my_bridge
docker run -d --name nginx --network my_bridge nginx
docker run -it --rm -network my_bridge centos

สร้าง network ชนิด bridge ขึ้นมา ชื่อ my_bridge และสร้าง container nginx ขึ้นมาชื่อ nginx และต่อเข้า network นั้น แล้วสร้าง container ขึ้นมาอีกตัวด้วย Image centos และเชื่อมต่อเข้า network my_bridge

curl http://nginx
exit

จะพบว่าสามารถเรียกไปยัง container ชื่อ nginx ได้ ด้วย DNS name ว่า nginx (ตามชื่อ container)

Cleanup

docker rm -f nginx
docker network rm my_bridge

Docker Image

Docker Image เป็นเสมือน template เพื่อใช้ในการสร้าง container โดยตัว Image นั้นจะเป็น read-only ไม่สามารถเขียนการเปลี่ยนแปลงลงไปใน Image ได้

การนำ Image ไปสร้างเป็น container เปรียบเสมือนการสร้างอีกชั้น layer หนึ่งขึ้นมา ซึ่งสามารถเขียนอ่านได้ การเปลี่ยนแปลงใดก็ตามที่เกิดขึ้นกับ container นั้น จะถูกเขียนลงบน layer ของ container เท่านั้น เช่น หากมีการติดตั้ง binary และ dependency เพิ่มเข้ามา ก็จะเก็บการเปลี่ยนแปลงเฉพาะส่วนต่างจาก Image ไว้

container หลายๆตัวซึ่งสร้างจาก Image เดียวกัน ก็จะมี layer read-write แยกออกจากกัน แต่ทุก container ที่สร้างจาก Image เดียวกัน ก็จะอ้างอิง layer ส่วนตั้งต้นเดียวกัน ซึ่งจะช่วยลดความซ้ำซ้อนจากส่วน base ที่เหมือนกัน ทำให้ประหยัดพื้นที่อีกด้วย

จากแนวคิดซึ่งอยู่บนพื้นฐานของการแยก layer ส่วนเปลี่ยนแปลงนั้นออกมาเป็นชั้นๆนั้น ทำให้แท้จริงแล้ว Docker Image มีเบื้องหลังเป็น กลุ่มก้อนของ layer ซึ่งแต่ละ layer เก็บข้อมูลการเปลี่ยนแปลงบน root filesystem นอกจากนี้ยังเก็บ metadata เกี่ยวกับ layer นั้นๆ และข้อมูลว่าจะรัน Image นี้อย่างไรอีกด้วย

การสร้าง Docker Image

การสร้าง Docker Image คือการนำส่วนที่มีการเปลี่ยนแปลงไป freeze เป็น read-only layer และ tag ขึ้นเป็นชื่อ Image และ/หรือ tag version ใหม่

ในทางเทคนิค สามารถทำโดยการ commit change ที่เกิดขึ้นจาก container เป็น Image โดยตรงก็ได้ แต่เป็นวิธีที่ไม่นิยม เพราะเกิด manual process และ reproduce ได้ยาก ดังนั้นปกติจะใช้วิธีการสร้างจาก Dockerfile แทน

Dockerfile จะเป็นไฟล์ซึ่งเก็บชุดคำสั่ง เพื่อใช้บอกขั้นตอนการสร้าง Docker Image ให้กับ Docker daemon และเมื่อเราสั่ง build แล้ว daemon จะไปทำงานตามคำสั่งนั้น

ตัวอย่างเช่น ทำการสร้างไฟล์ชื่อ Dockerfile และให้มี content ภายในดังนี้

Dockerfile
FROM nginx:latest
RUN sed -i "s/Welcome to nginx/Nginx custom by Dockerfile/" /usr/share/nginx/html/index.html

สั่งสร้าง Docker Image ด้วยคำสั่งดังนี้

docker build -t myweb .

daemon จะทำการอ่าน current directory(.) และหาไฟล์ชื่อ Dockerfile ซึ่งหากพบจะทำงานตามชุดคำสั่งในไฟล์ และสร้างออกมาเป็น Docker Image แล้วทำการ tag (option -t) ชื่อ Image เป็นคำว่า myweb โดยมี tag version เป็น latest เพราะไม่ได้ระบุ tag version ไว้ขณะสั่ง build

ทดสอบนำ Image ไปรันเป็น container และเข้าหน้าเว็บเพื่อทดสอบ

docker run -d -p 80:80 --name myweb myweb

Instruction ที่ใช้ภายใน Dockerfile

ภายใน Dockerfile ประกอบด้วยคำสั่งมากมาย ซึ่งใช้ในการสร้าง Docker Image แต่ละ Image ก็จะมี Instruction ต่างกันออกไป ขึ้นกับการใช้งาน

ในหัวข้อนี้ เราจะศึกษาโดยอิงจาก Dockerfile ของ Nginx tag latest เป็นตัวอย่าง

#
# NOTE: THIS DOCKERFILE IS GENERATED VIA "update.sh"
#
# PLEASE DO NOT EDIT IT DIRECTLY.
#
FROM debian:buster-slim

LABEL maintainer="NGINX Docker Maintainers <docker-maint@nginx.com>"

ENV NGINX_VERSION   1.21.3
ENV NJS_VERSION     0.6.2
ENV PKG_RELEASE     1~buster

RUN set -x \
# create nginx user/group first, to be consistent throughout docker variants
    && addgroup --system --gid 101 nginx \
    && adduser --system --disabled-login --ingroup nginx --no-create-home --home /nonexistent --gecos "nginx user" --shell /bin/false --uid 101 nginx \
    && apt-get update \
    && apt-get install --no-install-recommends --no-install-suggests -y gnupg1 ca-certificates \
    && \
    NGINX_GPGKEY=573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62; \
    found=''; \
    for server in \
        ha.pool.sks-keyservers.net \
        hkp://keyserver.ubuntu.com:80 \
        hkp://p80.pool.sks-keyservers.net:80 \
        pgp.mit.edu \
    ; do \
        echo "Fetching GPG key $NGINX_GPGKEY from $server"; \
        apt-key adv --keyserver "$server" --keyserver-options timeout=10 --recv-keys "$NGINX_GPGKEY" && found=yes && break; \
    done; \
    test -z "$found" && echo >&2 "error: failed to fetch GPG key $NGINX_GPGKEY" && exit 1; \
    apt-get remove --purge --auto-remove -y gnupg1 && rm -rf /var/lib/apt/lists/* \
    && dpkgArch="$(dpkg --print-architecture)" \
    && nginxPackages=" \
        nginx=${NGINX_VERSION}-${PKG_RELEASE} \
        nginx-module-xslt=${NGINX_VERSION}-${PKG_RELEASE} \
        nginx-module-geoip=${NGINX_VERSION}-${PKG_RELEASE} \
        nginx-module-image-filter=${NGINX_VERSION}-${PKG_RELEASE} \
        nginx-module-njs=${NGINX_VERSION}+${NJS_VERSION}-${PKG_RELEASE} \
    " \
    && case "$dpkgArch" in \
        amd64|i386|arm64) \
# arches officialy built by upstream
            echo "deb https://nginx.org/packages/mainline/debian/ buster nginx" >> /etc/apt/sources.list.d/nginx.list \
            && apt-get update \
            ;; \
        *) \
# we're on an architecture upstream doesn't officially build for
# let's build binaries from the published source packages
            echo "deb-src https://nginx.org/packages/mainline/debian/ buster nginx" >> /etc/apt/sources.list.d/nginx.list \
            \
# new directory for storing sources and .deb files
            && tempDir="$(mktemp -d)" \
            && chmod 777 "$tempDir" \
# (777 to ensure APT's "_apt" user can access it too)
            \
# save list of currently-installed packages so build dependencies can be cleanly removed later
            && savedAptMark="$(apt-mark showmanual)" \
            \
# build .deb files from upstream's source packages (which are verified by apt-get)
            && apt-get update \
            && apt-get build-dep -y $nginxPackages \
            && ( \
                cd "$tempDir" \
                && DEB_BUILD_OPTIONS="nocheck parallel=$(nproc)" \
                    apt-get source --compile $nginxPackages \
            ) \
# we don't remove APT lists here because they get re-downloaded and removed later
            \
# reset apt-mark's "manual" list so that "purge --auto-remove" will remove all build dependencies
# (which is done after we install the built packages so we don't have to redownload any overlapping dependencies)
            && apt-mark showmanual | xargs apt-mark auto > /dev/null \
            && { [ -z "$savedAptMark" ] || apt-mark manual $savedAptMark; } \
            \
# create a temporary local APT repo to install from (so that dependency resolution can be handled by APT, as it should be)
            && ls -lAFh "$tempDir" \
            && ( cd "$tempDir" && dpkg-scanpackages . > Packages ) \
            && grep '^Package: ' "$tempDir/Packages" \
            && echo "deb [ trusted=yes ] file://$tempDir ./" > /etc/apt/sources.list.d/temp.list \
# work around the following APT issue by using "Acquire::GzipIndexes=false" (overriding "/etc/apt/apt.conf.d/docker-gzip-indexes")
#   Could not open file /var/lib/apt/lists/partial/_tmp_tmp.ODWljpQfkE_._Packages - open (13: Permission denied)
#   ...
#   E: Failed to fetch store:/var/lib/apt/lists/partial/_tmp_tmp.ODWljpQfkE_._Packages  Could not open file /var/lib/apt/lists/partial/_tmp_tmp.ODWljpQfkE_._Packages - open (13: Permission denied)
            && apt-get -o Acquire::GzipIndexes=false update \
            ;; \
    esac \
    \
    && apt-get install --no-install-recommends --no-install-suggests -y \
                        $nginxPackages \
                        gettext-base \
                        curl \
    && apt-get remove --purge --auto-remove -y && rm -rf /var/lib/apt/lists/* /etc/apt/sources.list.d/nginx.list \
    \
# if we have leftovers from building, let's purge them (including extra, unnecessary build deps)
    && if [ -n "$tempDir" ]; then \
        apt-get purge -y --auto-remove \
        && rm -rf "$tempDir" /etc/apt/sources.list.d/temp.list; \
    fi \
# forward request and error logs to docker log collector
    && ln -sf /dev/stdout /var/log/nginx/access.log \
    && ln -sf /dev/stderr /var/log/nginx/error.log \
# create a docker-entrypoint.d directory
    && mkdir /docker-entrypoint.d

COPY docker-entrypoint.sh /
COPY 10-listen-on-ipv6-by-default.sh /docker-entrypoint.d
COPY 20-envsubst-on-templates.sh /docker-entrypoint.d
COPY 30-tune-worker-processes.sh /docker-entrypoint.d
ENTRYPOINT ["/docker-entrypoint.sh"]

EXPOSE 80

STOPSIGNAL SIGQUIT

CMD ["nginx", "-g", "daemon off;"]

FROM

เป็นคำสั่งซึ่งระบุว่าจะสร้าง Image โดยใช้ Image ใดเป็นพื้นฐาน ปกติจะอยู่ส่วนบนสุดของ Dockerfile นอกจากว่ามีการใช้ Instruction ARG ร่วมด้วย ก็จะสามารถให้ Instruction ARG ขึ้นก่อนได้ มี pattern ดังนี้

FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]

platform ใช้ระบุ architecture เช่น linux/amd64, linux/arm64 หรือ windows/amd64 AS <name> เป็นการกำหนดชื่อให้กับ build stage นี้ ปกติจะไม่มีการระบุ นอกจากมีการทำ multi-stage build

จากตัวอย่างจะใช้ Image debian ซึ่งเป็น version buster-slim มาเป็นพื้นฐาน

LABEL

เป็นคำสั่งซึ่งกำหนดค่า metadata ของ Docker Image มี pattern ดังนี้

LABEL <key>=<value> <key>=<value> <key>=<value> ...

ในที่นี้เป็นการระบุว่า Image ถูกสร้างโดยใคร

ENV

เป็นการกำหนด environment variable ให้กับ Docker Image มี pattern ดังนี้

ENV <key>=<value> ...

pattern การกำหนด ENV อีกแบบคือ

ENV <key> <value>

การกำหนดแบบนี้จะไม่รองรับการประกาศหลาย variable และไม่แนะนำให้ใช้ เพราะเป็น syntax แบบเก่า และอาจถูกยกเลิกการรองรับในอนาคต

จากตัวอย่างจะมีการกำหนด version Nginx ซึ่งจะถูกเรียกใช้ในคำสั่งอื่นๆ ต่อไป

RUN

เป็นการกำหนดคำสั่งซึ่งจะถูกนำไป execute ภายใต้ shell ซึ่ง default คือ /bin/sh มี pattern ดังนี้

RUN <command>

หรืออีก pattern ดังนี้

RUN ["executable", "param1", "param2"]

จากตัวอย่างจะเป็นคำสั่งติดตั้ง software Nginx

ข้อควรระวัง

ในการ build image ของ Docker นั้น แต่ละ instruction จะแทน layer หนึ่งๆ ดังนั้นจึงนิยมใช้ shell command หลายๆ คำสั่งต่อๆ กัน ภายใต้ RUN layer เดียว การแยก command ออกไปคนละ layer อาจจะทำให้ Image ใหญ่ขึ้นโดยไม่จำเป็น

เช่น หากเราแยก RUN layer แรกเป็นการ download zip file มา, RUN layer ถัดไปสั่ง extract file, RUN layer ถัดไป สั่งลบไฟล์ zip ทิ้ง ก็อาจจะได้ขนาด Image เกือบสองเท่าของที่ควรจะเป็น เพราะ layer หนึ่งเป็น size zip file, layer ถัดมาขนาดเท่าไฟล์ทั้งหมดที่ถูก extract และ layer สุดท้ายขนาดเท่าการ mark delete zip file

แต่หากเราทำทั้ง 3 คำสั่งภายใต้ layer เดียวแล้ว Docker จะ copy มาเฉพาะส่วน data ที่คงอยู่ ซึ่งจะขนาดตามไฟล์ที่ถูก extract เท่านั้น ไม่รวมขนาดไฟล์ zip มาด้วย

นั่นจึงเป็นที่มาว่าทำไม RUN layer มักจะประกอบด้วย && ระหว่างแต่ละ shell command

COPY

ใช้คัดลอกไฟล์จาก Host เข้าไปยัง directory ของ Container Image มี pattern ดังนี้

COPY [--chown=<user>:<group>] <src>... <dest>

หรือ

COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

โดยไฟล์ที่ถูกคัดลอกลงไปจะมี UID และ GID เป็น 0 (root) หากไม่มีการใส่ option --chown

user และ group ที่ pass ให้กับ --chown สามารถเป็น UID และ GID ที่ไม่มีอยู่บน host นั้นได้

ข้อควรระวัง

หากต้องการไฟล์เป็น owner และ/หรือ group อื่น แล้วไม่ได้เปลี่ยนตั้งแต่ขั้นตอนนี้ แต่ไปใช้ RUN layer เพื่อ chown อีกต่อหนึ่ง จะทำให้ขนาด Image ใหญ่ขึ้นได้โดยไม่จำเป็น

src สามารถเป็น wildcard ได้ เช่น myfile/* dest สามารถเป็น relative path ได้ โดยจะถูกไปอ้างอิงกับ path ที่กำหนดโดย WORKDIR

ENTRYPOINT

เป็นการระบุให้ Docker Engine รู้ว่า เมื่อ container ถูกสร้างขึ้นมา จะ execute คำสั่งใด มี pattern ดังนี้

ENTRYPOINT ["executable", "param1", "param2"]

จากตัวอย่างจะเป็นการบอกว่า เมื่อ container Nginx ถูกสร้างขึ้นมา ให้ execute script /docker-entrypoint.sh ซึ่ง script นี้ หุ้มคำสั่งซึ่งจะ start process Nginx อีกทีหนึ่ง

EXPOSE

เป็นการบอกให้ทราบว่า Container Image นี้ จะ listen network port อะไรบ้าง คำสั่งนี้ไม่ได้เป็นการ publish port ให้กับผู้ใช้ แต่เป็นแค่ information ในการสื่อสารให้ผู้ที่นำ Container Image นี้ไป deploy ทราบว่า Image จะใช้ port อะไรบ้าง และให้ผู้ deploy publish เองว่าจะ map port ใดของ Host มายัง port ภายใน container มี pattern ดังนี้

EXPOSE <port> [<port>/<protocol>...]

จากตัวอย่างจะบอกให้ผู้ใช้ทราบว่า Nginx Image นี้ listen port 80

STOPSIGNAL

เป็นการกำหนด systemcall signal ที่จะเกิดขึ้นเมื่อสั่งหยุดการทำงานของ container หากไม่กำหนด โดย default จะส่ง SIGTERM มายัง process มี pattern ดังนี้

STOPSIGNAL signal

จากตัวอย่างจะกำหนดให้ส่ง SIGQUIT มายัง process Nginx

CMD

เป็นคำสั่งเพื่อบอกให้ Docker Engine รู้ว่าจะเริ่มทำงานอย่างไรเมื่อ container ถูกสร้าง จะมีพฤติกรรมต่างกันออกไป ขึ้นอยู่กับว่ามีการกำหนด ENTRYPOINT ไว้หรือไม่

หากไม่มีการกำหนด ENTRYPOINT ปกติจะใช้ในรูป

CMD ["executable","param1","param2"]

หากมีการกำหนด ENTRYPOINT ไว้ จะทำงานเปรียบเสมือนเป็นแค่ argument ให้กับ ENTRYPOINT และจะมีรูปแบบดังนี้

CMD ["param1","param2"]

ซึ่งจากตัวอย่างของ Nginx นั้น จะมี ENTRYPOINT กำหนดไว้ จึงทำหน้าที่แค่ pass argument "nginx", "-g", "daemon off;" ให้กับ /docker-entrypoint.sh (ENTRYPOINT script)

Docker Storage

การใช้งาน Container จะมีลักษณะที่เป็น Immutable และ Ephemeral การเปลี่ยนแปลงใดๆก็ตามบน Container จะไม่ส่งผลไปยัง Container Image ดังนั้น เมื่อ Container ถูกลบทิ้งและสร้างใหม่จาก Image เดิม ก็จะกลับไปยังสถานะตาม Image ต้นฉบับ

เมื่อเป็นเช่นนี้ การจะเก็บรักษาข้อมูลบางส่วนที่ถูกเปลี่ยนแปลงไว้ เช่น ข้อมูลที่เปลี่ยนแปลงใน Database ก็จะต้องถูกเก็บอยู่ในส่วนอื่นนอก Container ใน Docker เอง ก็มีวิธีการนำพื้นที่ส่วนอื่นมา mount ทับบน Container และเขียนอ่านบนพื้นที่นั้นได้ ซึ่งทำได้หลายวิธี เช่น ใช้ Volume mount หรือใช้ Bind mount

Volumes

Volume เป็น resource ชนิดหนึ่งที่ถูกจัดการโดย Docker เมื่อเราสั่งสร้าง Volume ขึ้นมานั้น Docker จะไป allocate พื้นที่ส่วนหนึ่งมาทำเป็น Volume ซึ่งพื้นที่นั้นอาจจะอยู่บนเครื่อง Server อื่นก็ได้ หากเรามีการกำหนดไว้เช่นนั้น

Volume จะถูก mount เป็นส่วนหนึ่งของ filesystem บน Container เมื่อ Container ถูกสร้างขึ้นมา เมื่อมีการลบ Container ไป Volume จะยังคงอยู่ตามเดิม ดังนั้น เราสามารถสร้าง Container ใหม่ขึ้นมาโดยผูกกับ Volume เดิมได้

โดย Default นั้น Volume จะถูกสร้างขึ้นโดยมี random unique ID แต่จะสร้างความยุ่งยากในการอ้างอิงตอนสั่ง map Container เข้ากับ Volume เดิม ดังนั้น เราสามารถสร้าง Volume โดยกำหนดชื่อไว้ก่อนได้ ซึ่งวิธีนี้ เรียกว่าการสร้าง named Volume และเราจะใช้ name นั้นในการ map Container เข้ากับ Volume

การสร้าง Volume

docker volume create data-db

การ list Volume

docker volume ls

การลบ Volume

docker volume rm data-db

การสร้าง Container โดย map กับ Volume

การ map Volume ให้กับ Container ใช้ options -v เมื่อสั่ง docker run โดยมี pattern ดังนี้

docker run -d -v <named volume>:<in-container path> <image name>

ทดสอบการ persistent data

# create container
docker run -d -v data-db:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=mysecret -p 3306:3306 --name db mysql
# list volume
docker volume ls
# list databases
docker exec -it db bash -c 'echo "show databases;" |mysql -u root -pmysecret'
# create database
docker exec -it db bash -c 'echo "create database demo;" |mysql -u root -pmysecret'
# list databases
docker exec -it db bash -c 'echo "show databases;" |mysql -u root -pmysecret'
# remove container
docker stop db
docker rm db
# list volume
docker volume ls
# create new container
docker run -d -v data-db:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=mysecret -p 3306:3306 --name db mysql
# list databases
docker exec -it db bash -c 'echo "show databases;" |mysql -u root -pmysecret'

จากตัวอย่าง จะเห็นว่าไม่จำเป็นต้องสร้าง Volume ไว้ล่วงหน้า เพราะคำสั่ง docker run นี้จะไปสั่งสร้าง named volume ให้เอง หาก Volume ชื่อดังกล่าวไม่มีอยู่ก่อนแล้ว

cleanup

docker stop db
docker rm db
docker volume rm data-db

Bind mounts

เป็นวิธีการ mount path บน Server เข้าไปยัง Container คำสั่งในการใช้งาน Bind mount จะคล้ายกับการใช้งาน Volume แต่ต่างกันตรง แทนที่จะอ้างอิงเป็นชื่อ named volume จะอ้างอิงเป็น path บน Server แทน

ทดสอบการใช้ Bind mount

# create directory
mkdir datadir
docker run -d -v $(pwd)/datadir:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=mysecret -p 3306:3306 --name db mysql
ls -l datadir

จะพบว่าภายใต้ directory นั้น จะมีไฟล์อยู่ เพราะเป็นไฟล์ที่ถูกเขียนลงมาโดย container เอง

cleanup

docker stop db
docker rm db
rm -rf datadir

Docker Compose

เนื่องจากการใช้งาน docker command มีความยุ่งยาก มี options ที่ต้องใส่มากมาย ซึ่งจะทำให้เกิดความผิดพลาดได้หากมีการใส่ไม่ครบตามเดิม ดังนั้นการที่มี file หนึ่งซึ่งเก็บรายละเอียด config ทั้งหมดไว้ ก็จะลดความผิดพลาดในส่วนนั้นได้ และง่ายต่อการแชร์ให้คนอื่น นอกจากนี้การที่ config ทุกอย่างถูกเก็บเป็นไฟล์นั้น เราก็สามารถนำ file นั้นเก็บเป็น version ต่างๆและถูกควบคุมด้วยเครื่องมือประเภท source control ได้อีกด้วย

ใน Docker นั้น เครื่องมือที่ใช้ในการอ่าน config file และติดต่อกับ Docker Engine ก็คือ Docker Compose

Docker Compose จะอ่านไฟล์ชื่อ docker-compose.yml หรือ docker-compose.yaml โดย default ภายในไฟล์จะเป็น configuration ในรูปแบบ YAML format

ตัวอย่าง compose file

version: "3.9"
services:
  drupal:
    restart: always
    depends_on:
      - db
    image: drupal:latest
    ports:
      - 80:80
    volumes:
      - modules:/var/www/html/modules
      - profiles:/var/www/html/profiles
      - themes:/var/www/html/themes
  db:
    restart: always
    image: postgres:12
    environment:
      POSTGRES_PASSWORD: mysecret
    volumes:
      - db-data:/var/lib/postgresql/data
volumes:
  modules:
  profiles:
  themes:
  db-data:

version

เป็นส่วนระบุ syntax version ของ Compose file ถ้า version ใหม่ไป Docker Compose อาจจะไม่รู้จัก และไม่สามารถแปลได้ แต่ถ้าเก่าไป ก็อาจจะขาด feature แล้วไม่ compat กับ Docker Engine

services

เป็นส่วนที่จะระบุ container ที่จะถูกสร้างใน stack app นี้ ซึ่งชื่อภายใต้ service นี้จะเป็นอะไรก็ได้ อาจจะไม่ต้องตรงกับชื่อ Container Image

volumes

เป็นส่วนที่จะระบุ named volume ที่จะถูกสร้างใน stack app นี้

-------------------------------------------------------------------------------

Configuration ภายใต้แต่ละ services

restart

เป็นส่วนกำหนด restart policy ของแต่ละ container

depends_on

ใช้เพื่อระบุว่า ต้องรออีก service เริ่มทำงานก่อน

image

ระบุ Container Image และ Tag version ซึ่งนำมาใช้สร้าง Container

ports

ระบุ port ที่จะ map bridge จาก Host เข้าไปยัง Container

volumes

ระบุ named Volume หรือ Bind mount ที่จะ mount เข้าไปยัง container

ในกรณีใช้ Bind mount จะใช้ $(pwd) ไม่ได้ ให้ refer directory จากชั้นเดียวกับ docker compose file ด้วย ./ เช่น ./datadir:/var/lib/mysql

environment

กำหนดชื่อ environment และค่า ซึ่งจะถูกใช้ภายใน container

การ Deploy stack ด้วย Docker Compose

สร้าง container ทั้ง stack และทำงานใน detach mode ด้วยคำสั่งดังนี้

docker-compose up -d

ในกรณีที่ไม่ต้องการลบ stack ทิ้ง แต่ต้องการ stop เท่านั้น ใช้คำสั่งดังนี้

docker-compose stop

ในกรณีต้องการ start กลับมา ใช้คำสั่งดังนี้

docker-compose start

กรณีที่ต้องการดำเนินการกับเพียงบาง Container เท่านั้น สามารถทำได้โดยระบุชื่อ service (ใน Compose file) ต่อท้าย เช่น

docker-compose stop drupal

สั่งลบทั้ง stack ด้วยคำสั่งดังนี้

docker-compose down

ในการลบ stack โดย default จะไม่ทำการลบ Volume ให้ ต้องไปลบเองทีหลัง หรือไม่ก็ต้องใส่ options -v ตอนสั่ง docker-compose down เช่น docker-compose down -v

PreviousDockerNextDocker (LV.2)

Last updated 7 months ago

Was this helpful?

เราสามารถดูได้ว่า Docker Hub มี image อะไรให้ใช้บ้างได้จากเว็บ

รายละเอียด option อื่นๆเพิ่มเติมสามารถหาได้จาก

ในขณะที่เขียน Document นี้ Docker Compose จะไม่ติดมากับการ Install Docker Engine ต้องติดตั้งแยกต่างหาก การติดตั้งสามารถดูได้จาก URL ดังนี้

สามารถหารายละเอียด syntax เพิ่มเติมได้จากเว็บ Compose file reference

https://hub.docker.com
https://docs.docker.com/engine/reference/run/
https://docs.docker.com/compose/install/
https://docs.docker.com/compose/compose-file/compose-file-v3/
Docker Hub
Container and Docker Image