Skip to content

บทที่ 02 — Deployment + ReplicaSet: ทำให้ Pod self-healing + scale + update

← บทที่ 01 | สารบัญ | บทที่ 03: Service →

บท 01 เราเห็นว่า "Pod เปล่า ๆ ไม่ self-healing" — บทนี้สอน Deployment ตัวที่แก้เรื่องนี้ + ให้พลังเพิ่ม

อ่านจบจะ:

  • เข้าใจว่า Deployment, ReplicaSet, Pod เกี่ยวกันยังไง
  • เขียน Deployment manifest เป็น
  • scale แอปขึ้น/ลง
  • ทำ rolling update — deploy เวอร์ชันใหม่โดยไม่มี downtime
  • rollback กลับเวอร์ชันเก่าเมื่อพัง

ใช้เวลา 2-3 ชั่วโมง


Part 1: ปัญหาที่ Deployment แก้

จากบท 01 — Pod เปล่า ๆ มีปัญหา:

  1. ตายแล้วหายเลย — ไม่ self-healing
  2. มีตัวเดียว — รับโหลดเยอะไม่ไหว
  3. อัปเดตยาก — เปลี่ยน image ต้องลบ pod เก่า สร้างใหม่เอง → มี downtime

Deployment แก้ทั้ง 3 ข้อ:

  1. Pod ตาย → สร้างใหม่อัตโนมัติ (self-healing)
  2. บอกจำนวน → K8s รักษาจำนวน pod ให้เท่าที่ต้องการ
  3. เปลี่ยน 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 (มาตรฐาน):

yaml
labels:
  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.yaml

3.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 เสมอ!

เกิดอะไรขึ้น:

  1. คุณลบ pod → current state = 2 ตัว
  2. ReplicaSet เห็นว่า desired (3) ≠ current (2)
  3. 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.yaml

K8s เห็น 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: Recreate

Recreate = ลบ 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=1

K8s เก็บ ReplicaSet เก่าไว้ → ตอน rollback มันแค่ "เปิด ReplicaSet เก่ากลับมา" → เร็วมาก

📝 revisionHistoryLimit (default = 10) — K8s เก็บ ReplicaSet เก่าไว้กี่อันสำหรับ rollback. ระบบที่ deploy บ่อย ๆ (CD pipeline) ควรเพิ่มถ้าต้องการ history มาก หรือลดลงเพื่อกัน ReplicaSet เก่าสะสมเปลือง etcd:

yaml
spec:
  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 ก่อนจึงจะ uncomment imagePullSecrets ได้ — ถ้า uncomment โดยไม่มี Secret, pod จะ error ImagePullBackOff ทันที ดูวิธีสร้างใน บท 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 ประหยัด
progressDeadlineSecondsrollout ไม่คืบหน้านาน 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: 10timeoutSeconds: 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

  1. Deployment แก้ปัญหาอะไรของ Pod เปล่า ๆ (3 ข้อ)?
  2. Deployment → ReplicaSet → Pod — แต่ละชั้นทำหน้าที่อะไร?
  3. ทำไม selector.matchLabels ต้องตรงกับ template.metadata.labels?
  4. self-healing ทำงานยังไง? (อธิบายด้วย reconciliation loop)
  5. Rolling update ทำให้ "ไม่มี downtime" ยังไง?
  6. maxSurge กับ maxUnavailable คุมอะไร?
  7. RollingUpdate ต่าง Recreate ยังไง?
  8. Rollback ทำงานยังไง? ทำไมเร็ว?
  9. kubectl rollout restart ใช้ตอนไหน?
  10. Deployment เหมาะกับ stateless หรือ stateful? ทำไม?

Part 13: สรุปบทนี้

  • Deployment = วิธีรันแอป stateless ที่ถูกต้องใน K8s
  • โครงสร้าง 3 ชั้น: Deployment → ReplicaSet → Pod — เราเขียนแค่ Deployment
  • ReplicaSet รักษาจำนวน podself-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