โหมดมืด
บทที่ 02 — Deployment + ReplicaSet: ทำให้ Pod self-healing + scale + update
บท 01 เราเห็นว่า "Pod เปล่า ๆ ไม่ self-healing" — บทนี้สอน Deployment ตัวที่แก้เรื่องนี้ + ให้พลังเพิ่ม
อ่านจบจะ:
- เข้าใจว่า Deployment, ReplicaSet, Pod เกี่ยวกันยังไง
- เขียน Deployment manifest เป็น
- scale แอปขึ้น/ลง
- ทำ rolling update — deploy เวอร์ชันใหม่โดยไม่มี downtime
- rollback กลับเวอร์ชันเก่าเมื่อพัง
ใช้เวลา 2-3 ชั่วโมง
Part 1: ปัญหาที่ Deployment แก้
จากบท 01 — Pod เปล่า ๆ มีปัญหา:
- ตายแล้วหายเลย — ไม่ self-healing
- มีตัวเดียว — รับโหลดเยอะไม่ไหว
- อัปเดตยาก — เปลี่ยน image ต้องลบ pod เก่า สร้างใหม่เอง → มี downtime
Deployment แก้ทั้ง 3 ข้อ:
- Pod ตาย → สร้างใหม่อัตโนมัติ (self-healing)
- บอกจำนวน → K8s รักษาจำนวน pod ให้เท่าที่ต้องการ
- เปลี่ยน image → rolling update อัตโนมัติ ไม่มี downtime
→ Deployment = วิธีรันแอป "stateless" ที่ถูกต้องใน K8s
💡 "ไม่จำสถานะ / จำสถานะ" — 2 คำแกนของทั้งหมวดนี้ จำให้ขึ้นใจ:
- แอปไม่จำสถานะ (stateless) = แอปที่ ไม่เก็บข้อมูลถาวรไว้ในตัวเอง — ข้อมูลอยู่ที่อื่น (เก็บใน database หรือ cache ข้างนอก). pod ตัวไหนก็ เหมือนกันหมด สลับกันได้ ลบทิ้งสร้างใหม่ก็ไม่เป็นไร เช่น web server, API
- แอปจำสถานะ (stateful) = แอปที่ เก็บข้อมูล/สถานะไว้ในตัวเอง — pod แต่ละตัว "ไม่เหมือนกัน" มีข้อมูลของตัวเอง สลับมั่วไม่ได้ เช่น database
("สถานะ" หรือ "state" ในที่นี้ = "ข้อมูล/สถานะที่ต้องจำไว้") — บทนี้สอน Deployment ซึ่งเหมาะกับ แอปไม่จำสถานะ ส่วน แอปจำสถานะ จะใช้ StatefulSet ในบท 08
Part 2: Deployment → ReplicaSet → Pod (3 ชั้น)
ก่อนเขียน manifest — เข้าใจความสัมพันธ์ก่อน
อธิบายแต่ละชั้น:
ReplicaSet — มีหน้าที่เดียว: "รักษาจำนวน pod ให้คงที่" — ถ้าบอกว่าอยากได้ 3 ตัว มันจะดูแลให้มี 3 ตัวเสมอ. pod ตาย → สร้างใหม่ทันที (นี่คือ self-healing!)
Deployment — อยู่ชั้นบน ReplicaSet — มีหน้าที่ "จัดการ update + rollback" — เวลาเปลี่ยนเวอร์ชัน Deployment จะสร้าง ReplicaSet ใหม่ แล้วค่อย ๆ ย้าย pod (rolling update)
ในทางปฏิบัติ: เราเขียนแค่ Deployment — ReplicaSet กับ Pod, Deployment สร้างให้เอง เราแทบไม่ต้องแตะ ReplicaSet ตรง ๆ
💡 เปรียบเทียบ: ReplicaSet = "หัวหน้าทีมที่ดูแลว่าทีมต้องมี 3 คนเสมอ ใครลาออกก็จ้างใหม่" — Deployment = "ผู้จัดการที่ดูแลการเปลี่ยนรุ่นทีมงาน (จากทีมรุ่นที่ 1 เป็นทีมรุ่นที่ 2) แบบค่อยเป็นค่อยไป"
Part 3: Deployment Manifest
ไฟล์ deployment.yaml (เริ่มจากตัวอย่างขั้นต่ำ — แบบที่ใช้เรียน):
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80อธิบายทุกบรรทัด — ตรงนี้สำคัญมาก อ่านช้า ๆ:
apiVersion: apps/v1 — Deployment อยู่ใน API group apps (ไม่ใช่ v1 เปล่าแบบ Pod)
📖 ทำไม apiVersion ของ Deployment ไม่ใช่
v1เหมือน Pod? K8s จัดกลุ่ม object เป็น "API group":
- object พื้นฐานเช่น Pod, Service, ConfigMap อยู่ใน core group → ใช้
apiVersion: v1- object ใหม่กว่า Deployment, ReplicaSet, StatefulSet, DaemonSet อยู่ใน group
apps→ ใช้apiVersion: apps/v1- Job, CronJob อยู่ใน
batch/v1สงสัย object อะไรอยู่ group ไหน → รัน
kubectl explain Deployment(หรือชื่อ object) จะบอก apiVersion ให้
kind: Deployment
metadata: — ชื่อและ label ของตัว Deployment เอง
spec: — รายละเอียด มี 3 ส่วนสำคัญ:
replicas: 3 — "อยากได้ pod 3 ตัว" — นี่คือ desired state ของจำนวน
selector: — บอก Deployment ว่า "pod ที่ฉันดูแล คือ pod ที่มี label ตรงกับนี้"
yaml
selector:
matchLabels:
app: nginx→ Deployment จะดูแล pod ที่มี label app: nginx
template: — "พิมพ์เขียวของ pod" — เวลา Deployment สร้าง pod มันใช้ template นี้
yaml
template:
metadata:
labels:
app: nginx # ← label ของ pod ที่จะสร้าง
spec:
containers: # ← เหมือน spec.containers ของ Pod manifest บท 01!
- name: nginx
image: nginx:1.25⚠️ กฎสำคัญ: selector.matchLabels ต้องตรงกับ template.metadata.labels — เพราะ Deployment ต้องหา pod ที่ตัวเองสร้างให้เจอ. ถ้าไม่ตรง kubectl apply จะ error ประมาณ selector does not match template labels
สังเกตว่า template.spec หน้าตาเหมือน spec ของ Pod manifest บท 01 เป๊ะ — เพราะมันคือ "พิมพ์เขียวของ pod" จริง ๆ
⚠️ กับดักของ label
app: nginx— เรียบง่ายดี แต่ในระบบจริงที่มีหลาย service ใช้ nginx → label ซ้ำ → Service / NetworkPolicy เลือก pod ผิดตัวได้ (label collision — ชน/ทับกัน: เมื่อ selector ของ Deployment ตรงกับ pod ของ Deployment อื่น). production แนะนำใช้ K8s recommended labels (มาตรฐาน):yamllabels: app.kubernetes.io/name: nginx # ชื่อแอป app.kubernetes.io/instance: nginx-prod # instance นี้ (ของระบบไหน/env ไหน) app.kubernetes.io/version: "1.25" # เวอร์ชัน app.kubernetes.io/component: webserver # บทบาทใน stack app.kubernetes.io/part-of: shop # ส่วนหนึ่งของระบบใหญ่อะไร app.kubernetes.io/managed-by: helm # ใครจัดการในตัวอย่างของบทนี้ใช้
app: nginxเพื่อให้อ่านง่าย — แต่ของจริงควรใช้มาตรฐานข้างบน (อย่างน้อยname+instance)
3.1 สร้าง Deployment
เหมือน Pod — เราสร้าง Deployment ด้วย kubectl apply -f แต่คราวนี้ K8s จะสร้าง ReplicaSet และ pod ตามจำนวน replica ที่กำหนดให้ พร้อมคอยดูแลให้ครบตามนั้นเสมอ:
bash
kubectl apply -f deployment.yaml3.2 ดูผล
bash
# ดู deployment
kubectl get deployments
# NAME READY UP-TO-DATE AVAILABLE AGE
# nginx-deployment 3/3 3 3 30s
# ดู replicaset (Deployment สร้างให้)
kubectl get replicasets
# ดู pod (ReplicaSet สร้างให้ — 3 ตัว!)
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# nginx-deployment-7d4b8c9f8-abc12 1/1 Running 0 30s
# nginx-deployment-7d4b8c9f8-def34 1/1 Running 0 30s
# nginx-deployment-7d4b8c9f8-ghi56 1/1 Running 0 30sเห็นไหม — เราเขียนแค่ Deployment 1 อัน → ได้ ReplicaSet 1 ตัว + Pod 3 ตัวอัตโนมัติ
ชื่อ pod = [ชื่อ deployment]-[hash ของ replicaset]-[id สุ่ม]
Part 4: Self-Healing — ดูของจริง
นี่คือพลังของ Deployment — ลองพิสูจน์:
bash
# ลบ pod 1 ตัว
kubectl delete pod nginx-deployment-7d4b8c9f8-abc12
# ดู pod ทันที
kubectl get pods
# จะเห็นว่ามี pod ใหม่ "กำลังสร้าง" แทนตัวที่ลบ
# K8s รักษาจำนวนให้เป็น 3 เสมอ!เกิดอะไรขึ้น:
- คุณลบ pod → current state = 2 ตัว
- ReplicaSet เห็นว่า desired (3) ≠ current (2)
- ReplicaSet สร้าง pod ใหม่ทันที → กลับเป็น 3
นี่คือ reconciliation loop จากบท 00 ทำงานจริง — และนี่คือ self-healing
ลองอีก — ถ้า container ใน pod crash → K8s restart ให้. ถ้า node ทั้งเครื่องล่ม → K8s ย้าย pod ไป node อื่น. ทุกอย่างอัตโนมัติ
Part 5: Scaling — เพิ่ม/ลด จำนวน Pod
5.1 วิธีที่ 1: แก้ไฟล์ YAML (declarative — แนะนำ)
แก้ replicas: 3 เป็น replicas: 5 ในไฟล์ แล้ว:
bash
kubectl apply -f deployment.yamlK8s เห็น desired เปลี่ยน → สร้าง pod เพิ่ม 2 ตัว
5.2 วิธีที่ 2: คำสั่ง scale (imperative — เร็ว)
bash
kubectl scale deployment nginx-deployment --replicas=5⚠️ วิธีนี้ไม่ได้แก้ไฟล์ YAML — ถ้า apply ไฟล์เดิมทีหลัง จำนวนจะกลับไปตามไฟล์ → ใช้ตอนทดลอง ไม่ใช่ production
5.3 ดูผล
bash
kubectl get pods
# เห็น pod 5 ตัวลด:
bash
kubectl scale deployment nginx-deployment --replicas=2
# K8s ลบ pod ส่วนเกิน→ ส่วนการ scale อัตโนมัติ ตามโหลด — ใช้ HorizontalPodAutoscaler (บท 07)
Part 6: Rolling Update — เปลี่ยนเวอร์ชันโดยไม่มี downtime
นี่คือ feature เด่นของ Deployment
6.1 ปัญหา
อยาก update แอปจาก nginx:1.25 เป็น nginx:1.27 — แต่ไม่อยากให้เว็บล่มแม้แต่วินาทีเดียว
ถ้าทำเอง: ลบ pod เก่าทั้ง 3 → สร้างใหม่ทั้ง 3 → ช่วงระหว่างนั้นเว็บล่ม ❌
6.2 Rolling Update ทำงานยังไง
Deployment ทำ rolling update — ค่อย ๆ เปลี่ยนทีละนิด:
เริ่ม: [v1] [v1] [v1]
ขั้น 1: สร้าง v2 1 ตัว [v1] [v1] [v1] [v2]
ขั้น 2: v2 พร้อม → ลบ v1 1 ตัว [v1] [v1] [v2]
ขั้น 3: สร้าง v2 อีก [v1] [v1] [v2] [v2]
ขั้น 4: ลบ v1 [v1] [v2] [v2]
...วนจนครบ
จบ: [v2] [v2] [v2]ตลอดกระบวนการ — มี pod พร้อมรับ traffic เสมอ → ไม่มี downtime
6.3 ลองทำ
bash
# เปลี่ยน image
kubectl set image deployment/nginx-deployment nginx=nginx:1.27
# ดู rollout เกิดขึ้น real-time
kubectl rollout status deployment/nginx-deployment
# ดู pod ระหว่าง update — เห็น pod เก่าค่อย ๆ หาย pod ใหม่ค่อย ๆ มา
kubectl get pods -w⚠️
set imageเป็น imperative — ใช้ตอนทดลอง/แก้ฉุกเฉินเร็ว ๆ. production ควรแก้ไฟล์ YAML แล้วkubectl applyดีกว่า เพราะ:
- มี history ใน git
- คนอื่น review ได้
- run apply ซ้ำได้ผลเหมือนเดิม (idempotent)
หรือแก้ไฟล์ YAML (image: nginx:1.27) แล้ว kubectl apply -f deployment.yaml — ได้ผลเหมือนกัน (declarative — แนะนำกว่า)
6.4 ควบคุมความเร็ว Rolling Update
yaml
spec:
replicas: 10
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 2 # สร้าง pod ใหม่เกิน desired ได้กี่ตัว
maxUnavailable: 1 # ระหว่าง update ยอมให้ pod หายได้กี่ตัว📖 คำอ่าน:
- maxSurge (แม็กซ์-เซิร์จ) = "สูงสุดที่สร้างเกินได้"
- maxUnavailable (แม็กซ์-อัน-อะ-เวล-เอเบิล) = "สูงสุดที่หายได้"
maxSurge: 2— ระหว่าง update มี pod ได้ถึง 12 ตัว (10 + 2)maxUnavailable: 1— มี pod พร้อมใช้อย่างน้อย 9 ตัว (10 - 1)
📝 ค่า default: ถ้าไม่ระบุ K8s ใช้
maxSurge: 25%,maxUnavailable: 25%(เป็น % ของ replicas — เช่น replicas 10 → ได้ pod เพิ่ม 2-3 ตัวระหว่าง update, ยอมหาย 2-3 ตัว)
ตั้งเป็น maxUnavailable: 0 → ไม่มี pod หายเลยระหว่าง update (ปลอดภัยสุด แต่ใช้ resource ชั่วคราวมากกว่า)
6.5 Strategy: Recreate (ทางเลือก)
yaml
spec:
strategy:
type: RecreateRecreate = ลบ pod เก่าทั้งหมดก่อน → แล้วค่อยสร้างใหม่ → มี downtime
ใช้เมื่อ: แอปที่รัน 2 เวอร์ชันพร้อมกันไม่ได้ (เช่น schema database เปลี่ยน)
ปกติใช้ RollingUpdate (default)
Part 7: Rollback — ย้อนกลับเวอร์ชันเก่า
ถ้า deploy เวอร์ชันใหม่แล้วพัง — ย้อนกลับได้ทันที
bash
# ดูประวัติการ deploy
kubectl rollout history deployment/nginx-deployment
# REVISION CHANGE-CAUSE
# 1 <none>
# 2 <none>
# 3 <none>
# ย้อนกลับไปเวอร์ชันก่อนหน้า
kubectl rollout undo deployment/nginx-deployment
# ย้อนไป revision เฉพาะ
kubectl rollout undo deployment/nginx-deployment --to-revision=1K8s เก็บ ReplicaSet เก่าไว้ → ตอน rollback มันแค่ "เปิด ReplicaSet เก่ากลับมา" → เร็วมาก
📝
revisionHistoryLimit(default = 10) — K8s เก็บ ReplicaSet เก่าไว้กี่อันสำหรับ rollback. ระบบที่ deploy บ่อย ๆ (CD pipeline) ควรเพิ่มถ้าต้องการ history มาก หรือลดลงเพื่อกัน ReplicaSet เก่าสะสมเปลือง etcd:yamlspec: revisionHistoryLimit: 5
7.1 บันทึกเหตุผลการ deploy
bash
kubectl apply -f deployment.yaml
kubectl annotate deployment/nginx-deployment \
kubernetes.io/change-cause="upgrade to nginx 1.27"→ change-cause จะโผล่ใน rollout history — รู้ว่าแต่ละ revision คืออะไร
7.2 หยุด/ทำต่อ rollout
bash
kubectl rollout pause deployment/nginx-deployment # หยุดชั่วคราว
kubectl rollout resume deployment/nginx-deployment # ทำต่อ
kubectl rollout restart deployment/nginx-deployment # restart pod ทั้งหมด (rolling)rollout restart มีประโยชน์ — restart pod ทุกตัวแบบ rolling โดยไม่เปลี่ยน image (เช่น ตอนอยากให้ pod อ่าน config ใหม่)
Part 8: คำสั่ง Deployment ที่ใช้บ่อย
bash
# ดู
kubectl get deployments
kubectl get deploy # ย่อ
kubectl describe deployment nginx-deployment
# สร้าง (imperative — ตอนทดลอง)
kubectl create deployment myapp --image=nginx:1.25 --replicas=3
# scale
kubectl scale deployment myapp --replicas=5
# update image
kubectl set image deployment/myapp nginx=nginx:1.27
# rollout
kubectl rollout status deployment/myapp
kubectl rollout history deployment/myapp
kubectl rollout undo deployment/myapp
kubectl rollout restart deployment/myapp
# ลบ
kubectl delete deployment myapp
# generate YAML จากคำสั่ง (เทคนิคดี!)
kubectl create deployment myapp --image=nginx --dry-run=client -o yaml > deploy.yaml💡 เทคนิคนี้สำคัญมาก — ใช้บ่อย — แทนที่จะเขียน YAML จากศูนย์ (ผิด indent ง่าย), สั่ง
kubectl create ... --dry-run=client -o yamlเพื่อให้ K8s สร้าง template ที่ถูก syntax 100% ให้ แล้วเราค่อยแก้เพิ่ม.--dry-run=client= "อย่าส่งไป cluster จริง — แค่ generate ออกมาให้ดู". ใช้ได้กับเกือบทุก kind เช่นkubectl create configmap,kubectl create serviceฯลฯ
Part 9: Deployment Manifest แบบสมบูรณ์ (production)
นี่คือหน้าตาของ Deployment ที่ใช้จริงใน production — ทุกบรรทัดมีเหตุผล:
yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: myapp
labels:
app.kubernetes.io/name: myapp
app.kubernetes.io/instance: myapp-prod
app.kubernetes.io/version: "1.2.0"
annotations:
kubernetes.io/change-cause: "upgrade to v1.2.0 — fixed login bug"
spec:
replicas: 3
revisionHistoryLimit: 10 # (รี-วิ-ชั่น-ฮิส-ทอ-รี-ลิ-มิต) เก็บ ReplicaSet เก่าไว้กี่อันสำหรับ rollback
progressDeadlineSeconds: 600 # (โปร-เกรส-เดด-ไลน์-เซค-คอนด์ส) rollout ค้างนาน 600 วิ → mark fail (ไม่งั้นค้างไม่จบ)
selector:
matchLabels:
app.kubernetes.io/name: myapp
app.kubernetes.io/instance: myapp-prod
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0 # ไม่มี downtime
template:
metadata:
labels:
app.kubernetes.io/name: myapp
app.kubernetes.io/instance: myapp-prod
app.kubernetes.io/version: "1.2.0"
spec:
# graceful shutdown — รอแอปจบงานก่อน SIGKILL
terminationGracePeriodSeconds: 30
# กระจาย pod ไปคนละ node — ถ้า node ตายเครื่องนึง ไม่ดับทั้งระบบ
topologySpreadConstraints:
- maxSkew: 1
topologyKey: kubernetes.io/hostname
whenUnsatisfiable: ScheduleAnyway
labelSelector:
matchLabels:
app.kubernetes.io/name: myapp
# security: ไม่ให้รันด้วย root, อ่าน-อย่างเดียว, ไม่ยก privilege
securityContext:
runAsNonRoot: true
runAsUser: 1000
seccompProfile: # กรอง syscall ระดับ kernel — required ใน PSA restricted (K8s 1.25+)
type: RuntimeDefault
containers:
- name: myapp
image: ghcr.io/acme/myapp:v1.2.0 # private registry — ต้องมี imagePullSecrets (ดูหมายเหตุ)
ports:
- containerPort: 8080
# container-level security — ต้องตั้งแยกจาก pod-level (ไม่ inherit อัตโนมัติ)
securityContext:
allowPrivilegeEscalation: false # ห้าม process ยก privilege เกิน parent
readOnlyRootFilesystem: true # root filesystem อ่านอย่างเดียว — เขียนได้แค่ emptyDir/volume
capabilities:
drop: ["ALL"] # ตัด Linux capability ทั้งหมด — required ใน PSA restricted
# graceful shutdown — สั่ง sleep ให้ Service ถอด endpoint ก่อน SIGTERM
lifecycle:
preStop:
exec:
command: ["/bin/sh", "-c", "sleep 15"]
# ทรัพยากร — request = ขั้นต่ำที่จอง, limit = เพดาน (รายละเอียดบท 07)
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"
# startupProbe — สำหรับแอป start ช้า (Java/Node) — ให้เวลาเริ่มเต็มที่ก่อนเช็ค liveness
startupProbe:
httpGet:
path: /health
port: 8080
failureThreshold: 30
periodSeconds: 5 # รอได้ 30 × 5 = 150 วินาที
# livenessProbe — เช็คว่ายัง "เป็น" อยู่ไหม (เน่า → restart)
livenessProbe:
httpGet:
path: /health # endpoint แล้วแต่แอป — เปลี่ยนตาม code ตัวเอง
port: 8080
initialDelaySeconds: 10
periodSeconds: 10
timeoutSeconds: 2 # default = 1 (สั้นไป สำหรับ HTTP)
failureThreshold: 3 # default = 3 — fail 3 ครั้งติดกัน → restart
# readinessProbe — เช็คว่า "พร้อมรับ traffic" ไหม (ไม่พร้อม → ถอดจาก Service)
readinessProbe:
httpGet:
path: /ready
port: 8080
initialDelaySeconds: 5
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
# env — ตัวแปรสภาพแวดล้อม (รายละเอียดบท 04)
env:
- name: NODE_ENV
value: production
# imagePullSecrets (อิ-มิจ-พูล-ซี-เคร็ต) — ชื่อ Secret ที่เก็บ credentials สำหรับ pull image จาก private registry
# ถ้าใช้ private registry ต้องมี (สร้างด้วย `kubectl create secret docker-registry`)
# imagePullSecrets:
# - name: ghcr-pull-secret⚠️ ถ้าต้องการดึง image จาก private registry (เช่น
ghcr.io/acme/...) ต้องสร้าง Secret ก่อนจึงจะ uncommentimagePullSecretsได้ — ถ้า uncomment โดยไม่มี Secret, pod จะ errorImagePullBackOffทันที ดูวิธีสร้างใน บท 04 §imagePullSecret
อธิบายส่วนสำคัญ (สั้น ๆ — รายละเอียดบทถัด ๆ ไป):
| ฟิลด์ | ทำหน้าที่อะไร |
|---|---|
metadata.labels (app.kubernetes.io/*) | K8s recommended labels — ป้ายมาตรฐานที่เครื่องมือ K8s (kubectl, dashboard) เข้าใจร่วมกัน |
annotations.kubernetes.io/change-cause | ข้อความที่จะโผล่ใน kubectl rollout history — รู้ว่าแต่ละ revision เปลี่ยนอะไร |
revisionHistoryLimit | เก็บ ReplicaSet เก่ากี่อัน (default 10) — มาก = rollback ได้ไกล / น้อย = etcd ประหยัด |
progressDeadlineSeconds | rollout ไม่คืบหน้านาน X วิ → mark Failed (ไม่ค้างไม่จบ) — default 600 |
strategy.rollingUpdate | คุมความเร็ว rolling update — maxUnavailable: 0 = ไม่มี downtime เลย |
terminationGracePeriodSeconds | รอ pod จบงานนานสุดกี่วินาทีก่อน SIGKILL (default 30) |
topologySpreadConstraints | กระจาย pod ไปคนละ node/zone — กัน "node ตายดับทั้งระบบ" |
securityContext.runAsNonRoot | บังคับไม่ให้รันด้วย root user — ตั้งที่ระดับ pod |
securityContext.allowPrivilegeEscalation | (container-level) ห้าม process ยก privilege เกิน parent — ไม่ inherit จาก pod-level |
securityContext.readOnlyRootFilesystem | (container-level) root filesystem อ่านอย่างเดียว — required ใน PSA restricted |
securityContext.capabilities.drop | (container-level) ตัด Linux capability ทั้งหมด — required ใน PSA restricted |
seccompProfile.type: RuntimeDefault | กรอง syscall ระดับ kernel — required ใน PSA restricted (K8s 1.25+) |
lifecycle.preStop | คำสั่งที่รัน "ก่อน SIGTERM" — sleep 15 ให้ Service ถอด endpoint ก่อน |
startupProbe | สำหรับแอป start ช้า — ให้เวลาเริ่มก่อนเริ่มเช็ค liveness (ไม่ใส่ → Java app โดน kill ตอน warm-up) |
livenessProbe | เน่า → restart container |
readinessProbe | ไม่พร้อม → ถอดออกจาก Service (traffic ไม่เข้า) |
imagePullSecrets | จำเป็นถ้า image อยู่ใน private registry (ghcr.io, docker.io/private-org, ฯลฯ) |
📝 หมายเหตุ probe defaults ของ K8s:
failureThreshold: 3,timeoutSeconds: 1,periodSeconds: 10—timeoutSeconds: 1สั้นเกินสำหรับ HTTP probe หลายตัว → production ควร override เป็น 2-3 วินาที
📝
path: /health,/ready— endpoint ขึ้นกับแอปของคุณ. แอป Spring Boot มักใช้/actuator/health, Node.js Express ใช้ endpoint ที่ตัวเองเขียน — เปลี่ยนตามจริง
รายละเอียดของ resources / probe / securityContext / env / topology จะเรียนเต็มในบทถัด ๆ ไป (บท 04 และ 07) — ตอนนี้แค่เห็นว่า manifest จริงหน้าตาเป็นยังไง
💡 YAML ด้านบน copy ไปรันได้เลย — comment ที่อ้าง
# บท 07หรือ# บท 04แค่บอกว่า "อยากรู้เพิ่มเติมเรื่องนี้ ไปอ่านบทนั้น" ไม่ต้องหยุดอ่านบทนั้นก่อน manifest ทำงานได้สมบูรณ์โดยไม่ต้องรู้รายละเอียดทั้งหมดตอนนี้
Part 10: Deployment เหมาะกับอะไร — ไม่เหมาะกับอะไร
✅ Deployment เหมาะกับ stateless application:
- Web server, API
- Worker ที่ไม่เก็บ state
- แอปที่ pod ตัวไหนก็เหมือนกัน (สลับกันได้)
❌ Deployment ไม่เหมาะกับ stateful application:
- Database — เพราะ pod แต่ละตัวต้องมี identity + storage ของตัวเอง
- แอปที่ pod ต้องเริ่มตามลำดับ
→ stateful application ใช้ StatefulSet แทน (บท 08)
จำง่าย ๆ: Deployment = stateless (ไม่จำสถานะ) / StatefulSet = stateful (จำสถานะ)
Part 10.5: Glossary — ศัพท์สำคัญของบทนี้
| ศัพท์ | คำอ่าน (ไทย) | ความหมายสั้น ๆ |
|---|---|---|
| Deployment | ดี-พลอย-เม้นต์ | object ที่ใช้รันแอป stateless — จัดการ ReplicaSet + rolling update + rollback |
| ReplicaSet | เร็พ-ลิ-ก้า-เซ็ต | object ที่รักษาจำนวน pod ให้เท่าที่กำหนด (self-healing) |
| replicas | เร็พ-ลิ-ก้า-ส | จำนวน pod ที่ต้องการ |
| selector | ซี-เลค-เตอร์ | "ตัวเลือก" — บอกว่าจะดูแล pod ที่มี label ตรงไหน |
| template | เทม-เพลท | "พิมพ์เขียว" ของ pod ที่จะสร้าง |
| stateless | สเตท-เลส | ไม่จำสถานะ — pod สลับกันได้ |
| stateful | สเตท-ฟูล | จำสถานะ — pod แต่ละตัวไม่เหมือนกัน |
| rolling update | โรล-ลิ่ง อัพ-เดท | เปลี่ยน pod ทีละนิด ไม่ดับทั้งระบบ |
| rollback | โรล-แบ็ก | ย้อนกลับเวอร์ชันเก่า |
| maxSurge | แม็กซ์-เซิร์จ | สูงสุดที่สร้าง pod เกินได้ระหว่าง update |
| maxUnavailable | แม็กซ์-อัน-อะ-เวล-เอเบิล | สูงสุดที่ pod หายได้ระหว่าง update |
| revision | รี-วิ-ชั่น | "รุ่น" ของ Deployment (แต่ละครั้งที่ update) |
Part 11: Lab
Lab 1: Deployment แรก + self-healing
แล็บแรกพิสูจน์ความสามารถ self-healing ของ Deployment ที่ Pod เปล่า ๆ ไม่มี — สร้าง Deployment 3 replica แล้วลบ pod ทิ้งไป 1 ตัว สังเกตว่า K8s สร้างตัวใหม่มาแทนทันทีเพื่อรักษาจำนวนให้ครบ 3 เสมอ:
bash
kubectl create deployment web --image=nginx:1.25 --replicas=3
kubectl get pods
# ลบ pod 1 ตัว — ดูว่ามันสร้างใหม่
kubectl delete pod <ชื่อ-pod-ตัวใดตัวหนึ่ง>
kubectl get pods
# → ยังมี 3 ตัว (ตัวใหม่มาแทน)Lab 2: Scale
แล็บนี้ลองปรับจำนวน pod ขึ้น-ลงด้วย kubectl scale — เพิ่มเป็น 6 แล้วลดเหลือ 2 จะเห็นว่า K8s สร้าง/ลบ pod ให้ตรงตามที่สั่งภายในไม่กี่วินาที นี่คือพื้นฐานของการ scale แบบ manual:
bash
kubectl scale deployment web --replicas=6
kubectl get pods # 6 ตัว
kubectl scale deployment web --replicas=2
kubectl get pods # 2 ตัวLab 3: Rolling Update + Rollback
แล็บนี้สาธิตจุดเด่นที่สุดของ Deployment — เปลี่ยน image แบบ rolling update (ค่อย ๆ สลับ pod ทีละตัว ไม่ดับทั้งระบบ), ลองจำลอง deploy พังด้วย image ที่ไม่มีจริง แล้ว rollout undo ย้อนกลับเวอร์ชันเดิมได้ทันที:
bash
# update
kubectl set image deployment/web nginx=nginx:1.27
kubectl rollout status deployment/web
# ดูประวัติ
kubectl rollout history deployment/web
# update เป็น image ที่ไม่มีจริง (จำลอง deploy พัง)
kubectl set image deployment/web nginx=nginx:does-not-exist
kubectl get pods # เห็น ImagePullBackOff
# rollback!
kubectl rollout undo deployment/web
kubectl get pods # กลับมาปกติLab 4: Generate YAML
bash
kubectl create deployment demo --image=nginx --replicas=3 \
--dry-run=client -o yaml > demo.yaml
cat demo.yaml
# ใช้เป็น template — แก้ต่อได้Part 12: Checkpoint
- Deployment แก้ปัญหาอะไรของ Pod เปล่า ๆ (3 ข้อ)?
- Deployment → ReplicaSet → Pod — แต่ละชั้นทำหน้าที่อะไร?
- ทำไม
selector.matchLabelsต้องตรงกับtemplate.metadata.labels? - self-healing ทำงานยังไง? (อธิบายด้วย reconciliation loop)
- Rolling update ทำให้ "ไม่มี downtime" ยังไง?
maxSurgeกับmaxUnavailableคุมอะไร?RollingUpdateต่างRecreateยังไง?- Rollback ทำงานยังไง? ทำไมเร็ว?
kubectl rollout restartใช้ตอนไหน?- Deployment เหมาะกับ stateless หรือ stateful? ทำไม?
Part 13: สรุปบทนี้
- Deployment = วิธีรันแอป stateless ที่ถูกต้องใน K8s
- โครงสร้าง 3 ชั้น: Deployment → ReplicaSet → Pod — เราเขียนแค่ Deployment
- ReplicaSet รักษาจำนวน pod → self-healing
- Deployment จัดการ rolling update (ไม่มี downtime) + rollback
selectorต้องตรงกับtemplate.labels- Scale: แก้
replicasแล้ว apply (declarative) หรือkubectl scale - Rolling update: ค่อย ๆ เปลี่ยน pod ทีละนิด — คุมด้วย
maxSurge/maxUnavailable - Rollback เร็วเพราะ K8s เก็บ ReplicaSet เก่าไว้
- Deployment = stateless / StatefulSet = stateful
บทต่อไป — Service: ให้ "ที่อยู่ถาวร" กับ pod ที่ IP เปลี่ยนตลอด
← บทที่ 01 | สารบัญ | บทที่ 03: Service →
Glossary: ../glossary.md · Style guide: ../CONTRIBUTING.md last_verified: 2026-06-12 · review report: ../REVIEW-2026-06-03.md