Docker กับ Kubernetes ต่างกันอย่างไร เมื่อไหร่ต้องใช้ Kubernetes
Docker ใช้ build image และรัน container บนเครื่องหนึ่งเครื่อง ส่วน Kubernetes ดูแล container บนหลายเครื่อง ทั้งจัดวาง Pod รักษาจำนวน replica และเปิด Service
เริ่มต้น5 นาทีในการอ่าน
DOCKER one host docker build docker run Dockerfile ────────────image app:1 ────────────container ● (layers) Up ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ KUBERNETES Deployment app: replicas 3 image app:1 READY 3/3 ┌─ node 1 ────────┐ ┌─ node 2 ────────┐ ┌─ node 3 ────────┐ │ Ready ● │ │ Ready ● │ │ Ready ● │ │ [pod a] │ │ [pod b] │ │ [pod c] │ │ │ │ │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ ································································ desired: 3 Pods the controller compares actual and fixes it
ในบทความนี้
ตอบสั้น ๆ ก่อนครับ Docker คือเครื่องมือสำหรับ build image และรัน container บนเครื่องที่มี Docker daemon ส่วน Kubernetes คือระบบที่ดูแล container บนหลายเครื่องพร้อมกัน คอยเลือกว่าจะรันที่เครื่องไหน รักษาจำนวนให้ครบตามที่สั่ง และสร้างใหม่เมื่อตัวเดิมหายไป
สองตัวนี้จึงไม่ได้แข่งกันโดยตรง image ที่ build ด้วย Docker ใช้กับ container runtime ที่ Kubernetes รองรับได้ทุกตัว คำถามที่ควรถามจริง ๆ คือระบบของเรา "ต้องการ Kubernetes แล้วหรือยัง" ครับ
DOCKER one host docker build docker run Dockerfile ────────────image app:1 ────────────container ● (layers) Up ┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄ KUBERNETES Deployment app: replicas 3 image app:1 READY 3/3 ┌─ node 1 ────────┐ ┌─ node 2 ────────┐ ┌─ node 3 ────────┐ │ Ready ● │ │ Ready ● │ │ Ready ● │ │ [pod a] │ │ [pod b] │ │ [pod c] │ │ │ │ │ │ │ └─────────────────┘ └─────────────────┘ └─────────────────┘ ································································ desired: 3 Pods the controller compares actual and fixes it
Docker ทำอะไร
Docker เป็นแพลตฟอร์มสำหรับแพ็กและรันแอปใน container ทำงานแบบ client-server คือคำสั่ง docker ส่ง request ผ่าน REST API ไปที่ Docker daemon (dockerd) ซึ่งเป็นตัว build, รัน และแจกจ่าย container จริง ๆ บน Linux Docker ใช้ namespace ของ kernel เพื่อแยกพื้นที่ทำงานของแต่ละ container
งานประจำวันของ Docker มีไม่กี่คำสั่ง:
docker build -t app:1 . # Dockerfile -> image
docker run -d -p 8080:80 app:1 # image -> container
docker push registry.example.com/app:1
image กับ container ต่างกันอย่างไร
image คือแม่แบบแบบอ่านอย่างเดียวที่มีทุกอย่างสำหรับสร้าง container แต่ละคำสั่งใน Dockerfile สร้าง layer หนึ่งชั้น และเมื่อแก้ Dockerfile แล้ว build ใหม่ จะ build ใหม่เฉพาะ layer ที่เปลี่ยน
container คือ instance ที่รันอยู่ของ image ตอน docker run Docker จะเพิ่ม filesystem แบบอ่านเขียนได้เป็นชั้นสุดท้าย และเมื่อลบ container ทิ้ง สิ่งที่เปลี่ยนไปซึ่งไม่ได้เก็บใน persistent storage จะหายไปด้วย ข้อมูลที่ต้องเก็บจึงต้องอยู่ใน volume ครับ
รูปแบบของ image ไม่ได้เป็นของ Docker คนเดียว Open Container Initiative (OCI) กำหนด image-spec ไว้ว่า image ประกอบด้วย manifest, image index (ไม่บังคับ), ชุด filesystem layer และ configuration เป้าหมายคือให้เครื่องมือต่างเจ้า build, ส่ง และรัน image เดียวกันได้
Kubernetes ทำอะไรที่ Docker ไม่ได้ทำ
Kubernetes คือแพลตฟอร์มสำหรับจัดการ workload และ service ที่อยู่ใน container บน cluster ที่มีcontrol plane และ worker node หนึ่งเครื่องขึ้นไป เราไม่ได้สั่ง "รัน container นี้" ทีละตัว แต่เขียนสถานะที่ต้องการ (desired state) แล้วให้ระบบทำให้เป็นจริง เอกสารของ Kubernetes อธิบายว่ามันประกอบด้วย control process หลายตัวที่คอยขับสถานะปัจจุบันให้เข้าใกล้สถานะที่ต้องการอยู่ตลอด
Pod
Pod คือหน่วยเล็กที่สุดที่ deploy ได้ใน Kubernetes เป็นกลุ่ม container หนึ่งตัวขึ้นไปที่ใช้ storage และ network ร่วมกัน แบบที่เจอบ่อยที่สุดคือ หนึ่ง container ต่อหนึ่ง Pod และโดยทั่วไปเราไม่สร้าง Pod ตรง ๆ แต่สร้างผ่าน workload resource อย่าง Deployment
Deployment
Deployment ใช้ระบุว่าต้องการ Pod หน้าตาแบบไหนกี่ตัว แล้ว Deployment controller จะเปลี่ยนสถานะจริงให้เป็นสถานะที่ต้องการในอัตราที่ควบคุมได้ ตัวอย่างสั้น ๆ:
apiVersion: apps/v1
kind: Deployment
metadata:
name: app
spec:
replicas: 3
selector:
matchLabels: { app: app }
template:
metadata:
labels: { app: app }
spec:
containers:
- name: app
image: registry.example.com/app:1
ports:
- containerPort: 80
เวลาเปลี่ยน image เป็นเวอร์ชันใหม่ Deployment จะ rolling update โดยค่าเริ่มต้นให้ Pod ไม่พร้อมได้สูงสุด 25% และเกินจำนวนได้สูงสุด 25% และย้อนกลับไป revision ก่อนหน้าได้ถ้าเวอร์ชันใหม่มีปัญหา
Service
Pod ถูกสร้างและลบตลอดเวลา IP ของมันจึงเปลี่ยนไปเรื่อย ๆ Service แก้ปัญหานี้โดยเปิดกลุ่ม Pod ออกสู่ network ผ่าน selector และได้ IP ของตัวเองที่เรียกว่า cluster IP controller ของ Service จะคอยหา Pod ที่ตรงกับ selector แล้วอัปเดตรายการ endpoint ให้ ฝั่งที่เรียกจึงไม่ต้องรู้ว่าตอนนี้มี Pod ไหนบ้าง
Scheduler
kube-scheduler เลือก node ให้ Pod ที่ยังไม่มี node เป็นสองขั้น คือ filtering แล้ว scoring ขั้นแรกตัด node ที่ resource ไม่พอสำหรับ request ของ Pod ออก ขั้นที่สองให้คะแนน node ที่เหลือแล้วเลือกตัวที่ได้คะแนนสูงสุด ถ้าไม่มี node ไหนผ่านเลย Pod จะค้างอยู่ในสถานะ unscheduled จนกว่าจะวางได้
Self-healing
Kubernetes restart container ที่ล้ม แทนที่ container และ kill container ที่ไม่ตอบ health check ที่เรากำหนด และไม่ส่ง traffic ไปหาจนกว่ามันจะพร้อม ถ้า node ทั้งเครื่องล่ม controller จะสร้าง Pod ใหม่มาแทน แล้ว scheduler วาง Pod นั้นบน node ที่ยังปกติ
สิ่งที่ Kubernetes ไม่ทำก็ควรรู้ด้วยครับ มันไม่ build แอปและไม่ deploy source code และไม่มี database, message bus หรือระบบ logging ให้ในตัว ส่วนนั้นยังต้องเลือกและติดตั้งเอง
Kubernetes ยังใช้ Docker อยู่หรือไม่
ใช้ image ที่ Docker build ได้ แต่ Kubernetes ไม่ได้คุยกับ Docker Engine โดยตรงแล้วครับ Kubernetes คุยกับ runtime ผ่าน CRI (Container Runtime Interface) และ Docker Engine ไม่ได้ implement CRI โปรเจกต์จึงมีโค้ดเชื่อมที่ชื่อ dockershim ไว้ชั่วคราว และนำออกใน Kubernetes v1.24
ผลในทางปฏิบัติมีสามข้อ
- image จาก
docker buildยังใช้ได้กับทุก CRI implementation OCI เป็นผู้ดูแล spec กลางทั้งของ image และการรัน container - runtime ที่รองรับ CRI เช่น containerd และ CRI-O ใช้ runc เป็น runtime ระดับล่างโดยค่าเริ่มต้น
- บนเครื่องที่ใช้พัฒนา ไม่มีอะไรเปลี่ยน ยังใช้ Docker build และทดสอบได้ตามเดิม ถ้าจำเป็นต้องให้ node ใช้ Docker Engine จริง ๆ มี adapter ชื่อ
cri-dockerd
ใช้ Docker Compose บนเครื่องเดียวพอเมื่อไหร่
ผมคิดว่าพอ ถ้าระบบรันบนเครื่องเดียวได้ และยอมรับได้ว่าถ้าเครื่องนั้นล่ม ระบบจะหยุดจนกว่าจะกู้เครื่องกลับมาครับ
Docker Compose ให้เขียน service, network, volume, config และ secret ไว้ใน compose.yaml ไฟล์เดียว แล้ว docker compose up จะสร้าง network และ volume ที่ต้องใช้ และเริ่มทุก service ส่วนการ restart เมื่อ container ล้ม Docker มี restart policy อย่าง on-failure, always และ unless-stopped (Start containers automatically) ซึ่งตั้งใน Compose ได้ด้วย restart: (Compose services)
services:
web:
image: registry.example.com/app:1
ports: ["8080:80"]
restart: unless-stopped
depends_on: [worker]
worker:
image: registry.example.com/worker:1
restart: unless-stopped
ข้อจำกัดคือ restart policy ทำงานโดย Docker daemon บนเครื่องนั้น ถ้าทั้งเครื่องหาย ก็ไม่มีอะไรย้าย container ไปเครื่องอื่น นี่คือจุดที่ Kubernetes เริ่มคุ้มกับความซับซ้อนที่เพิ่มขึ้น
ลองเช็กจากคำถามเหล่านี้ครับ
| คำถาม | ถ้าตอบว่า "ใช่" |
|---|---|
| ระบบต้องรันต่อได้แม้เครื่องหนึ่งเครื่องล่ม | ต้องมีหลาย node และตัวที่ย้าย workload ให้ |
| ต้อง deploy เวอร์ชันใหม่โดยไม่หยุดบริการ และย้อนกลับได้ด้วยคำสั่งเดียว | Deployment มี rolling update และ rollback ให้ |
| มีหลาย service ที่ต้องเพิ่มลดจำนวนตามโหลด | Kubernetes ทำ horizontal scaling ได้ |
| มีทีมหรือคนที่ดูแล cluster ได้ | ถ้าไม่มี ต้นทุนการดูแลอาจมากกว่าประโยชน์ |
ถ้าทุกข้อตอบว่า "ไม่" Compose บนเครื่องเดียวพร้อม backup ที่ดีมักพอครับ
ควรเริ่มเรียนจากอะไร
เริ่มจาก Docker ก่อนครับ เพราะ image และ container คือสิ่งที่ Kubernetes เอาไปรันต่อ ลำดับที่ผมแนะนำคือ
- เขียน Dockerfile, build image และรัน container ให้คล่อง
- ใช้ Docker Compose รันแอปที่มีหลาย service บนเครื่องตัวเอง
- ลอง Kubernetes ในเครื่อง Docker Desktop มี Kubernetes มาให้แล้ว เขียน Deployment และ Service ของแอปเดิม แล้วลองลบ Pod ทิ้งเพื่อดูว่า controller สร้างตัวใหม่อย่างไร
ข้อสามคือจุดที่ความต่างของสองเครื่องมือนี้ชัดที่สุด Docker รันตามคำสั่งที่เราพิมพ์ ส่วน Kubernetes เทียบสิ่งที่รันอยู่กับสิ่งที่เราเขียนไว้ แล้วแก้ให้ตรงกันเองครับ
แหล่งอ้างอิง
- Docker Docs: What is Docker?
- Docker Docs: How Compose works
- Docker Docs: Compose file reference, services
- Docker Docs: Start containers automatically
- OCI Image Format Specification
- Kubernetes: Overview
- Kubernetes: Components
- Kubernetes: Pods
- Kubernetes: Deployments
- Kubernetes: Service
- Kubernetes: Kubernetes Scheduler
- Kubernetes Blog (17 February 2022): Updated: Dockershim Removal FAQ
อ่านต่อ
บทความที่เกี่ยวข้อง
การพัฒนาซอฟต์แวร์
RPC กับ REST แตกต่างกันอย่างไร และควรเลือกใช้แบบไหน
RPC เรียก procedure บน server ด้วยชื่อและพารามิเตอร์ ส่วน REST ส่งคำขอไปที่ resource ผ่าน interface กลางของ HTTP เทียบทีละประเด็นพร้อมตัวอย่าง gRPC และ JSON-RPC อิงจาก Fielding, RFC 9110 และ spec ต้นทาง
เริ่มต้น10 นาที
- API
- RPC
- REST
- gRPC
- JSON-RPC
- HTTP
การพัฒนาซอฟต์แวร์
WebSocket SSE และ Polling ต่างกันอย่างไร ควรเลือกแบบไหน
Polling ส่ง request ใหม่ทุกรอบ SSE เปิด response ค้างให้ server ส่ง event ทางเดียว WebSocket upgrade แล้วส่ง frame ได้สองทาง พร้อมเรื่อง reconnect และ proxy
ปานกลาง9 นาที
- WebSocket
- Server-Sent Events
- SSE
- Long Polling
- HTTP
- Reverse Proxy
- nginx
การพัฒนาซอฟต์แวร์
OAuth 2.0 กับ OpenID Connect ต่างกันอย่างไร
OAuth 2.0 ให้แอปได้ access token ไปเรียก API แทนผู้ใช้ ส่วน OpenID Connect เพิ่ม ID token ที่บอกว่าใคร sign in ไล่ authorization code flow กับ PKCE ทีละ request
ปานกลาง7 นาที
- OAuth 2.0
- OpenID Connect
- PKCE
- Authentication
- Authorization
- Tokens