บทที่ 15 Kubernetes: rolling update, self-healing และ rollback
แผนบริหารการสอนประจำบทที่ 15
หัวข้อเนื้อหา
- 15.1 rolling update ทำงานอย่างไร
- 15.2 readiness probe และ liveness probe เทียบกับ healthcheck ใน Compose
- 15.3 rollout history และ rollout undo เทียบกับ rollback ด้วยมือในบทที่ 12
- 15.4 self-healing ของ Deployment
- 15.5 ภาพรวม Helm และ GitOps (Argo CD) (ไม่ประเมิน)
- 15.6 สรุปรายวิชาและเส้นทางเรียนต่อ
- 15.7 คำนวณขอบเขตของ rolling update ก่อนอ่านผลจริง
- 15.8 readiness, liveness และ startup แก้คนละปัญหา
- 15.9 rollout ไม่ก้าวหน้าไม่ได้แปลว่าระบบย้อนกลับแล้ว
- 15.10 self-healing กับขอบเขตที่ยังต้องออกแบบ
- ปฏิบัติการที่ 15 rolling update, rollback และ self-healing บนคลัสเตอร์ kind (Lab A–E)
วัตถุประสงค์เชิงพฤติกรรม
เมื่อศึกษาบทนี้และทำปฏิบัติการครบถ้วนแล้ว ผู้เรียนสามารถปฏิบัติได้ตามผลลัพธ์การเรียนรู้ต่อไปนี้ โดยแต่ละข้อมีงานที่ใช้ให้ feedback กำกับไว้
| ผลลัพธ์ | ข้อความ | หลักฐาน (งานข้อ) | CLO |
|---|---|---|---|
| 15.1 | เพิ่ม readiness/liveness probe ที่เรียก /health (โยงบท 11) แล้วทำ rolling update ไป image tag ใหม่ | งานข้อ 1 (manifest และผล kubectl rollout status) | CLO2 |
| 15.2 | ปล่อย tag ที่พังแล้ว rollback ด้วย kubectl rollout undo | งานข้อ 1 (ผล rollout history และ rollout undo) | CLO2 |
| 15.3 | ลบ Pod ที่กำลังทำงาน แล้วบันทึกว่า Deployment สร้าง Pod ใหม่อย่างไร โดยอ้างเอกสาร Kubernetes ทางการ | งานข้อ 2 (บันทึก self-healing) | CLO3 |
CLO ที่เกี่ยวข้อง (ข้อความจากหลักสูตร):
- CLO2 สามารถกำหนดค่าและใช้งาน DevOps workflow ที่กำหนดมา เพื่อให้กระบวนการพัฒนา–ทดสอบ–ส่งมอบซอฟต์แวร์เป็นไปอย่างถูกต้องและมีประสิทธิภาพ
- CLO3 ใช้เครื่องมือดิจิทัลอย่างเหมาะสมเพื่อพัฒนาระบบ และสามารถเรียนรู้และปรับตัวด้วยตนเองจากแหล่งข้อมูลที่เชื่อถือได้
บทนี้ไม่นับคะแนน คะแนนทั้งหมดของรายวิชาปิดที่บทที่ 13 งานที่ต้องส่งของบทที่ 14–15 เป็นโอกาสฝึกเพิ่ม ผู้สอนให้ feedback เท่านั้น ไม่ใช่หลักฐานตัดสินการบรรลุ CLO ผลลัพธ์ 15.1 และ 15.2 ฝึกการใช้กลไกส่งมอบของ Kubernetes ให้ถูกต้องตามข้อความของ CLO2 ส่วนผลลัพธ์ 15.3 ฝึกการสังเกตตามแนวทางที่กำหนดแล้วหาคำอธิบายจากเอกสารทางการ ซึ่งตรงกับ CLO3 บทนี้เป็นบทสุดท้ายของรายวิชา จึงมีหัวข้อสรุปรายวิชาและเส้นทางเรียนต่อด้วย
วิธีสอนและกิจกรรมการเรียนการสอน
- อธิบายหัวข้อ 15.1–15.4 โดยเริ่มจากผลวัด 502 ระหว่าง deploy ในบทที่ 12 แล้วถามว่า "ถ้ามีแอปสองสำเนา จะเปลี่ยนเวอร์ชันอย่างไรให้ไม่มีช่วงไม่ตอบ" ใช้คำถามตรวจความเข้าใจเป็นกิจกรรมอภิปรายเป็นคู่
- ให้ภาพรวม Helm และ GitOps (หัวข้อ 15.5) ในระดับแนวคิด ไม่ประเมิน
- ปฏิบัติการ Lab A–E บนคลัสเตอร์ kind ของเครื่องห้องปฏิบัติการ แต่ละ Lab มีส่วนฝึกตามตัวอย่างและส่วนตัดสินใจเอง
- ปิดรายวิชาด้วยหัวข้อ 15.6 ให้ผู้เรียนเลือกเส้นทางเรียนต่อและบันทึกใน Learning Log ครั้งที่ 15
สื่อการเรียนการสอน
- เอกสารประกอบการสอนบทที่ 15 (บทความนี้) และหน้ารวมรายวิชา 10306343
- repository ทีม
devops-appที่มีโฟลเดอร์k8s/จากบทที่ 14 (M12) และRUNBOOK.mdจากบทที่ 12 - image ของทีมบน ghcr.io จากบทที่ 8:
0.5.0,0.5.2รุ่นพังโดยตั้งใจ0.6.0จากบทที่ 12 และ0.6.1ที่ทีมออกใน Lab B - เครื่องห้องปฏิบัติการที่ติดตั้ง Docker, kind และ kubectl (เครื่องเดียวกับบทที่ 14)
- เอกสาร Kubernetes (Deployments, Self-Healing, probe) และเอกสาร Helm, OpenGitOps, Argo CD
- ไฟล์ที่ให้มา:
k8s/deployment.yamlฉบับที่มี strategy และ probe (Lab A.3)
การวัดผลและการประเมินผล
กำหนดส่ง: ภายในสัปดาห์ที่ 15 ก่อนสิ้นภาคเรียน หรือตามวันที่ผู้สอนประกาศ งานทุกข้อของบทนี้ได้รับ feedback เท่านั้น ไม่นับคะแนน
| ข้อ | งาน | หลักฐาน | ผลลัพธ์ | CLO |
|---|---|---|---|---|
| 1 | M13 rolling update และ rollout undo | ลิงก์ pull request ของตนเองที่ merge แล้ว · k8s/deployment.yaml บน main ที่มี probe และ tag ใหม่ · ผล rollout status ของ Lab A.4 และ B.2 · ผล rollout history ก่อนและหลัง undo · ผล rollout undo และ curl ที่ /health (Lab C) | 15.1, 15.2 | CLO2 |
| 2 | บันทึก self-healing (รายบุคคล) | บันทึกตาม Lab D.2 ที่มีผลก่อนและหลังลบ Pod คำอธิบาย และข้อความที่ยกจากเอกสารทางการพร้อมลิงก์ | 15.3 | CLO3 |
| 3 | Learning Log ครั้งที่ 15 | 4 หัวข้อตาม Lab E.2 พร้อมลิงก์เอกสารทางการอย่างน้อย 1 แหล่ง | — | ประกอบการให้ feedback |
เกณฑ์ให้ feedback
| ข้อ | ผ่าน | ยังไม่ผ่าน |
|---|---|---|
| 1 | rollout ของ Lab A.4 และ B.2 สำเร็จ · /health หลัง Lab B ตอบเวอร์ชันใหม่ · หลัง undo /health ตอบเวอร์ชันก่อนหน้าและมี revision ใหม่จาก undo | rollout ยังค้าง หรือกู้ด้วยการลบแล้วสร้าง Deployment ใหม่ หรือไม่มีผลคำสั่งประกอบ |
| 2 | ผ่านรายการตรวจ CLO3 ครบทุกข้อ (ตารางถัดไป) | ขาดรายการใดรายการหนึ่ง |
| 3 | ตอบครบ 4 หัวข้อ และมีลิงก์เอกสารทางการ | ขาดหัวข้อ หรือไม่มีแหล่งอ้างอิง |
ข้อ 2 ใช้รายการตรวจ (checklist) CLO3 ผ่านเมื่อครบทุกข้อ ใช้ให้ feedback เท่านั้น
| ลำดับ | รายการ |
|---|---|
| 1 | มีชื่อ Pod ที่ลบ และชื่อ Pod ใหม่ที่เกิดขึ้น จากผล kubectl get pods ก่อนและหลังลบ |
| 2 | ระบุว่าจำนวน Pod กลับมาเท่ากับค่า replicas ใน manifest และระบุ ReplicaSet ที่เป็นเจ้าของ Pod ใหม่ (จากผล kubectl get rs และชื่อ Pod) |
| 3 | อ้างเอกสาร Kubernetes ทางการอย่างน้อย 1 หน้าพร้อมลิงก์ ยกข้อความที่อธิบายผลที่เห็น และอธิบายด้วยคำของตนเองว่าข้อความนั้นตรงกับผลส่วนใด |
| 4 | แยกได้ว่าส่วนใดเป็นผลที่เห็นจริง ส่วนใดเป็นคำอธิบายจากเอกสาร และระบุข้อจำกัดของ self-healing อย่างน้อย 1 ข้อ |
| 5 | ผลที่บันทึกตรงกับผลจริงของคลัสเตอร์ของตนเอง (ผู้สอนสุ่มให้ทำซ้ำหรือเปิดหน้าจอดู) |
เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ
การคิดคะแนนฉบับปัจจุบัน
มติผู้สอน 28 ต.ค. 2569: เข้าเรียน 10% · ใบงานบทที่ 1–13 รวม 40% · FP 20% · FW 20% · โครงงาน 10% ส่วนบทที่ 14–15 ฝึกเพื่อรับข้อเสนอแนะ ไม่นับคะแนน
บทนี้ไม่รวมในหมวดใบงาน FP หรือ FW ที่ใช้คำนวณเกรด ให้ข้อเสนอแนะตามหลักฐานเท่านั้น
| ผลลัพธ์ | หลักฐานตามงานส่ง | คะแนนดิบเต็ม |
|---|---|---|
| LLO15.1 | งานข้อ 1 (manifest และผล kubectl rollout status) | ไม่นับคะแนน |
| LLO15.2 | งานข้อ 1 (ผล rollout history และ rollout undo) | ไม่นับคะแนน |
| LLO15.3 | งานข้อ 2 (บันทึก self-healing) | ไม่นับคะแนน |
ประเมินงานแต่ละข้อจากหลักฐานของข้อนั้นโดยตรง ไม่ตัดสิทธิ์รับคะแนนของอีกข้อเพราะ milestone หรือจุดตรวจอื่นยังไม่ผ่าน งานที่ยังทำไม่ได้ให้บันทึกปัญหาและหลักฐานที่มีตามจริง
ใช้เกณฑ์ความถูกต้อง เหตุผล และหลักฐานที่ระบุในงานแต่ละข้อประกอบการประเมินราย LLO งานเตรียมพื้นและ Learning Log ที่ไม่มีรหัส LLO ใช้ให้ข้อเสนอแนะ ไม่เพิ่มคะแนนดิบให้ใบงาน
บทนำ
บทที่ 14 นำแอปของทีมขึ้นคลัสเตอร์ kind แล้ว สิ่งที่ยังไม่ได้ทำคือการเปลี่ยนระบบที่รันอยู่อย่างปลอดภัย บทนี้สร้างคลัสเตอร์ใหม่จาก k8s/ ของทีม เพิ่ม probe ที่เรียก /health เปลี่ยนเวอร์ชันแบบ rolling update ปล่อยเวอร์ชันพังแล้วกู้ด้วย kubectl rollout undo และสังเกต self-healing หัวข้อสุดท้ายสรุปรายวิชาและเส้นทางเรียนต่อ
ความสำคัญ: ในบทที่ 12 ระบบของทีมหยุดตั้งแต่ container ของเวอร์ชันพังแทนที่ container เดิม และการกู้ระบบต้องรอให้คนสังเกต ตัดสินใจ และเรียก workflow Rollback กลไกของ Kubernetes ในบทนี้แก้สองจุดนั้นบางส่วน คือเริ่ม Pod ใหม่ให้พร้อมก่อนหยุด Pod เดิม และเก็บประวัติ revision ไว้ให้ย้อนกลับได้ด้วยคำสั่งเดียว แต่การตัดสินใจว่าจะย้อนกลับเมื่อใดและไปที่ใดยังเป็นงานของคน
ความเชื่อมโยงกับบทอื่น: บทนี้ใช้ /health จากบทที่ 11 healthcheck ของ Compose จากบทที่ 7 workflow Rollback และ RUNBOOK.md จากบทที่ 12 และ manifest กับคลัสเตอร์ kind จากบทที่ 14 ผลผลิตของบทนี้ใช้ต่อหลังจบรายวิชาดังนี้
| สิ่งที่ได้จากบทนี้ | ใช้ต่อ |
|---|---|
k8s/deployment.yaml ที่มี probe และ strategy | ฐานสำหรับศึกษา Helm หรือ GitOps (หัวข้อ 15.5) |
ส่วน rollback บน Kubernetes ใน RUNBOOK.md และบันทึก self-healing | portfolio และตัวอย่างการเรียนรู้จากเอกสารทางการ |
ความรู้พื้นฐาน เครื่องมือ และรุ่นซอฟต์แวร์
ความรู้พื้นฐาน: /health และ healthcheck (บทที่ 7 และ 11) rollback ด้วย image tag เดิม (บทที่ 12) Pod, Deployment, ReplicaSet, Service, ConfigMap และ kubectl พื้นฐาน (บทที่ 14)
เครื่องมือ: เครื่องห้องปฏิบัติการที่มี Docker, kind และ kubectl (ไม่ใช้คลาวด์) repository ทีมที่มี k8s/ บน main และ personal access token (classic) ที่มีสิทธิ์ read:packages สำหรับดึง image จาก ghcr.io ถ้าเครื่องใดสร้างคลัสเตอร์ไม่ได้เพราะ RAM ไม่พอ ให้ทีมใช้เครื่องเดียวร่วมกัน
รุ่นซอฟต์แวร์:
| ซอฟต์แวร์ | รุ่นและสถานะ |
|---|---|
| kind และ kubectl | หน้า quick start ของ kind ระบุรุ่นเสถียร v0.33.0 (node image เริ่มต้น kindest/node:v1.37.0) ตรวจเอกสารเมื่อ 19 ต.ค. 2569 ยังไม่ได้ติดตั้งและทดสอบ |
| ตรวจ manifest | kubernetes-validate 1.37.0 แบบ --strict กับ schema ของ Kubernetes 1.37 และ 1.34 ผ่านทุกไฟล์ |
| Docker Engine | 29.8.2 (ทดสอบ image ของแอป) |
| แอป | devops-app 0.5.2 (ตอบ /health ภายในประมาณ 0.26–0.37 วินาทีหลังเริ่ม container) 0.6.0 (ล้มตอนเริ่มด้วย exit code 1) และ 0.6.1 ที่แก้ Dockerfile (เริ่มได้และ /health ตอบ "version":"0.6.1") ทดสอบด้วย Docker เท่านั้น |
สภาพแวดล้อมที่ใช้เขียนบทความติดตั้ง kind และ kubectl ไม่ได้ จึงไม่มีคำสั่งใดในบทนี้ที่รันบนคลัสเตอร์จริง ที่ทดสอบแล้วมีเพียงการตรวจ schema ของ manifest และพฤติกรรมของ image 0.5.2, 0.6.0 และ 0.6.1 ด้วย Docker ผลของ kubectl ทุกคำสั่งเขียนจากเอกสาร Kubernetes และกำกับว่า "ผลที่คาดว่าจะเห็น (ยังไม่ได้ทดสอบบนคลัสเตอร์จริง)" ให้บันทึกผลจริงของทีมเป็นหลักฐาน
ค่าใช้จ่ายและข้อมูลลับ: ไม่มีค่าใช้จ่าย คลัสเตอร์ทั้งหมดอยู่บนเครื่องห้องปฏิบัติการ token สำหรับ ghcr.io เป็นข้อมูลลับ ห้ามใส่ token ในไฟล์ใดของ repository รวมถึง RUNBOOK.md และเมื่อใช้เครื่องร่วมกันให้ docker logout ghcr.io ก่อนลุกจากเครื่อง (ขั้นเก็บกวาด)
15.1 rolling update ทำงานอย่างไร
Deployment ไม่ได้จัดการ Pod โดยตรง แต่สร้าง ReplicaSet ที่ดูแลให้มี Pod ตามจำนวน replicas เมื่อ Pod template (.spec.template) เปลี่ยน เช่น เปลี่ยน image tag หรือเพิ่ม probe Deployment จะสร้าง ReplicaSet ใหม่ แล้วค่อย ๆ เพิ่ม Pod ใหม่และลด Pod เดิม เอกสารระบุว่า revision ใหม่เกิดขึ้น "if and only if the Deployment's Pod template (.spec.template) is changed" การ scale จึงไม่สร้าง revision (Kubernetes: Deployments)
ค่าใน spec.strategy.rollingUpdate | ความหมาย | ค่าเริ่มต้น | ปัดเศษ |
|---|---|---|---|
maxSurge | จำนวน Pod ที่สร้างเกินจำนวนที่ต้องการได้ระหว่าง update | 25% | ปัดขึ้น |
maxUnavailable | จำนวน Pod ที่ไม่พร้อมได้ระหว่าง update | 25% | ปัดลง |
เอกสาร Deployments อธิบายค่าเริ่มต้นว่ามี Pod พร้อมอย่างน้อย 75% และ Pod รวมไม่เกิน 125% ของจำนวนที่ต้องการ ส่วนกฎการปัดเศษมาจากคำอธิบายฟิลด์ใน API ของ Kubernetes
ระดับที่ 1: Deployment ของทีมมี replicas: 2 จึงได้ maxSurge = 0.5 ปัดขึ้นเป็น 1 Pod และ maxUnavailable = 0.5 ปัดลงเป็น 0 Pod ระหว่าง rolling update จึงมี Pod รวมไม่เกิน 3 และ Pod พร้อมไม่น้อยกว่า 2 ตลอดเวลา
ระดับที่ 2: ลำดับโดยประมาณของการเปลี่ยนจาก 0.5.2 ไป 0.6.1 (คำนวณจากกฎในเอกสาร ไม่ใช่ผลที่สังเกตจากคลัสเตอร์)
| ขั้น | Pod เดิมที่พร้อม | Pod ใหม่ | สิ่งที่เกิดขึ้น |
|---|---|---|---|
| 1 | 2 | 1 (ยังไม่พร้อม) | สร้าง Pod ใหม่ รวม 3 ตัวเท่ากับเพดาน |
| 2 | 1 | 1 พร้อม | Pod ใหม่พร้อมแล้วจึงลด Pod เดิมได้ 1 ตัว |
| 3 | 1 | 2 (ตัวที่สองยังไม่พร้อม) | สร้าง Pod ใหม่ตัวที่สอง |
| 4 | 0 | 2 พร้อม | ลบ Pod เดิมตัวสุดท้าย rollout สำเร็จ |
เอกสารระบุว่า Deployment "does not kill old Pods until a sufficient number of new Pods have come up" ถ้า Pod ใหม่ไม่เคยพร้อม ขั้นที่ 2 จะไม่เกิด และ Pod เดิมทั้งสองตัวยังให้บริการต่อ ซึ่ง Lab C จะทดลอง
ระดับที่ 3: ถ้าไม่มี readiness probe container จะถูกถือว่าพร้อมทันทีที่เริ่ม แม้แอปยังไม่เปิดพอร์ต 3000 และ minReadySeconds เริ่มต้นเป็น 0 ("the Pod will be considered available as soon as it is ready") rolling update จึงอาจลบ Pod เดิมเร็วเกินไป เทียบกับบทที่ 12 ที่ระบบมี container เดียวและได้ 502 หนึ่งครั้งจาก 150 ครั้งระหว่าง deploy ปกติ rolling update ลดปัญหานี้ได้เมื่อ Kubernetes รู้ว่า Pod ใดพร้อม
คำถามตรวจความเข้าใจ 15.1: (1) ถ้า replicas เป็น 4 และใช้ค่าเริ่มต้น ระหว่าง rollout จะมี Pod รวมสูงสุดและ Pod พร้อมน้อยที่สุดกี่ตัว (2) ข้อใดสร้าง revision ใหม่ ก) kubectl scale deployment/devops-app --replicas=3 ข) เปลี่ยน image tag ค) แก้ค่า APP_TITLE ใน ConfigMap ง) เพิ่มตัวแปร env ใหม่ใน container
15.2 readiness probe และ liveness probe เทียบกับ healthcheck ใน Compose
probe คือการตรวจที่ kubelet ทำกับ container เป็นระยะ บทนี้ใช้ probe แบบ HTTP ที่เรียก /health ซึ่งเอกสารระบุว่า "Any code greater than or equal to 200 and less than 400 indicates success." (Kubernetes: Configure Liveness, Readiness and Startup Probes) manifest เต็มอยู่ใน Lab A.3
| ด้าน | healthcheck ใน Compose (บทที่ 7) | readinessProbe | livenessProbe |
|---|---|---|---|
| ผู้ตรวจและวิธีตรวจ | Docker รัน node -e "fetch(...)" ใน container exit code 0 คือผ่าน | kubelet เรียก HTTP GET /health พอร์ต 3000 | เหมือน readiness |
| เมื่อไม่ผ่าน | สถานะ (unhealthy) แต่ container ทำงานต่อ (ทดสอบแล้วในบทที่ 7) | "the pod will be marked unready and will not receive traffic from any services" | "the kubelet kills the container and restarts it" |
| ใช้ตัดสิน deploy | up --wait (บทที่ 7) และประตูตรวจ health ใน scripts/deploy.sh (บทที่ 11) | rolling update รอให้ Pod ใหม่พร้อมก่อนลด Pod เดิม | ไม่ใช้ตัดสิน rollout โดยตรง |
ค่าที่ใช้: readiness initialDelaySeconds: 2, periodSeconds: 5 และ liveness initialDelaySeconds: 10, periodSeconds: 10 ฟิลด์ที่ไม่ระบุใช้ค่าเริ่มต้นตามคำอธิบายฟิลด์ใน API คือ timeoutSeconds 1, successThreshold 1 และ failureThreshold 3 เอกสารยังระบุว่า "The readiness and liveness probes do not depend on each other to succeed."
ระดับที่ 1: readiness ตอบคำถามว่า "ส่ง traffic มาที่ Pod นี้ได้หรือยัง" ส่วน liveness ตอบว่า "container ค้างจนต้องเริ่มใหม่หรือไม่" ทั้งสองเรียก /health เหมือนกัน แต่ผลเมื่อไม่ผ่านต่างกัน readiness ที่ไม่ผ่านทำให้ Pod ไม่รับ traffic (เอกสารไม่ได้กล่าวถึงการเริ่มใหม่) ส่วน liveness ที่ไม่ผ่านทำให้ container ถูกเริ่มใหม่
ระดับที่ 2: ค่าเลือกจากผลทดสอบด้วย Docker ที่แอป 0.5.2 ตอบ /health ภายในประมาณ 0.26–0.37 วินาทีหลังเริ่ม (วัด 6 ครั้ง) readiness ที่ 2 วินาทีจึงเผื่อไว้มาก ส่วน liveness ที่ตรวจทุก 10 วินาทีร่วมกับ failureThreshold 3 จะเริ่ม container ใหม่เมื่อไม่ผ่านติดกันประมาณ 30 วินาที (คำนวณ ยังไม่ได้สังเกต)
ระดับที่ 3: healthcheck ของ Compose บอกสถานะให้คนหรือ --wait นำไปตัดสิน แต่ probe ผูกกับการกระทำโดยตรง จึงต้องเลือกให้ระวังกว่า ถ้าวันหนึ่ง /health ตรวจฐานข้อมูลด้วย การที่ฐานข้อมูลหยุดจะทำให้ liveness ของทุก Pod ล้มพร้อมกันและถูกเริ่มใหม่ซ้ำ ๆ โดยไม่ช่วยอะไร (ข้อสรุปจากพฤติกรรมของ liveness ที่เอกสารระบุ) กรณี 0.6.0 แอปล้มด้วย exit code 1 ก่อนเปิดพอร์ต จึงไม่ผ่าน readiness เลย และ container ถูกเริ่มใหม่ตาม restartPolicy โดยไม่ต้องรอ liveness ผลที่คาดว่าจะเห็นคือ CrashLoopBackOff หรือ Error (ยังไม่ได้ทดสอบบนคลัสเตอร์จริง)
คำถามตรวจความเข้าใจ 15.2: ถ้าตั้ง livenessProbe ให้เรียก /healthz ซึ่งแอปตอบ 404 ส่วน readiness ถูกต้อง Pod จะพร้อมรับ traffic หรือไม่ คอลัมน์ RESTARTS จะเป็นอย่างไร และ rolling update จะเชื่อถือได้หรือไม่
15.3 rollout history และ rollout undo เทียบกับ rollback ด้วยมือในบทที่ 12
Deployment เก็บ ReplicaSet เดิมไว้เพื่อย้อนกลับ ("By default, 10 old ReplicaSets will be kept" ค่า revisionHistoryLimit) คำสั่งในบทนี้มาจากเอกสาร Deployments ได้แก่ kubectl rollout status, rollout history (เพิ่ม --revision=N เพื่อดูรายละเอียด เช่น image), kubectl annotate ... kubernetes.io/change-cause="..." และ rollout undo (เพิ่ม --to-revision=N เพื่อระบุปลายทาง) สรุปไว้ในตารางสรุปคำสั่งท้ายบท
ระดับที่ 1: คอลัมน์ CHANGE-CAUSE เป็น <none> ถ้าไม่ได้ตั้ง annotation เพราะค่านี้ "is copied from the Deployment annotation kubernetes.io/change-cause to its revisions upon creation." ทีมจึงควรตั้งทุกครั้งที่เปลี่ยนเวอร์ชัน การ undo ไม่ทำให้เลข revision ถอยกลับ เอกสารระบุว่า "Each rollback updates the revision of the Deployment." และในตัวอย่าง การย้อนไป revision 2 ทำให้ Deployment มี revision 4
ระดับที่ 2: เทียบการกู้ระบบสองแบบที่ทีมได้ทำ
| ด้าน | workflow Rollback (บทที่ 12) | kubectl rollout undo (บทนี้) |
|---|---|---|
| สิ่งที่ย้อนกลับ | image tag และไฟล์ค่าตั้งของ release (checkout tag เดิม) | Pod template ของ revision เดิม (image, env, probe) ไม่รวมข้อมูลใน ConfigMap |
| ระหว่างที่เวอร์ชันพังเข้ามา | ระบบหยุด เพราะ container เดิมถูกแทนที่แล้ว | Pod เดิมยังให้บริการ เพราะ maxUnavailable = 0 (คาดการณ์จากเอกสาร) |
| ผู้เลือกปลายทาง | คนดูจาก run ของ CD ที่ผ่านล่าสุดตาม RUNBOOK | revision ก่อนหน้า หรือคนระบุด้วย --to-revision |
| ความสอดคล้องกับ Git | release เดิมคือ tag ใน Git | คลัสเตอร์อาจต่างจาก manifest บน main ต้องตรวจและแก้ |
ระดับที่ 3: Kubernetes ไม่ rollback ให้เอง เอกสารระบุว่า "Kubernetes takes no action on a stalled Deployment other than to report a status condition" เมื่อเกิน progressDeadlineSeconds (ค่าเริ่มต้น 600 วินาที) จะมีเหตุผล ProgressDeadlineExceeded และ rollout status "returns a non-zero exit code" การตัดสินใจยังเป็นของคนเหมือนบทที่ 12 และ "revision ก่อนหน้า" ไม่จำเป็นต้องดี ถ้ามีการเปลี่ยนที่พังติดกันสองครั้ง undo แบบไม่ระบุจะพากลับไปยังเวอร์ชันพังอีกตัว เช่นเดียวกับแบบฝึกหัดบทที่ 12 ที่การ rollback ไป 0.5.1 เพราะเป็น "เลขก่อนหน้า" ได้ระบบที่พัง
คำถามตรวจความเข้าใจ 15.3: ทีมหนึ่งแก้ APP_TITLE ใน ConfigMap ผิด แล้วสั่ง kubectl rollout restart จากนั้นสั่ง rollout undo เพื่อแก้ หน้าแรกจะกลับเป็นข้อความเดิมหรือไม่ เพราะอะไร และควรแก้อย่างไร
15.4 self-healing ของ Deployment
เอกสาร Kubernetes: Self-Healing อธิบายกลไกที่พาระบบกลับสู่สถานะที่ต้องการโดยไม่ต้องมีคนสั่ง ที่เกี่ยวกับบทนี้มีสามข้อ
| กลไก | ข้อความจากเอกสาร | เห็นได้ที่ |
|---|---|---|
| restart container | "If a container inside a Pod fails, Kubernetes restarts it based on the restartPolicy." | RESTARTS ของ Pod 0.6.0 (Lab C) |
| สร้าง Pod ทดแทน | "If a Pod in a Deployment or StatefulSet fails, Kubernetes creates a replacement Pod to maintain the specified number of replicas." | Pod ชื่อใหม่หลังลบ Pod เดิม (Lab D) |
| ตัด Pod ออกจาก Service | "Kubernetes automatically removes it from the Service's endpoints to route traffic only to healthy Pods." | curl ยังได้คำตอบจาก Pod ที่ดี (Lab C) |
ระดับที่ 1: ใน Deployment ผู้ทำ self-healing คือ ReplicaSet ที่เทียบจำนวน Pod จริงกับจำนวนที่ต้องการ เมื่อ Pod หายไป ReplicaSet สร้าง Pod ใหม่ชื่อใหม่ ไม่ได้นำ Pod เดิมกลับมา หมายเหตุ: เอกสารใช้คำว่า Pod ที่ "fails" การที่ Pod ที่ถูกลบด้วยมือได้รับการทดแทนเป็นข้อสรุปจาก "to maintain the specified number of replicas" ไม่ใช่ประโยคตรงในเอกสาร
ระดับที่ 2: ในระบบ Compose ของทีม container ที่ (unhealthy) ยังทำงานต่อโดยไม่มีใครเริ่มใหม่ (บทที่ 7) และระบบในบทที่ 12 ไม่ได้ตั้ง restart policy Deployment ทำงานจาก "สถานะที่ต้องการ" ใน manifest และพยายามให้สถานะจริงตรงกับสิ่งนั้นตลอดเวลา แนวคิดนี้จะพบอีกครั้งใน GitOps
ระดับที่ 3: เอกสารเดียวกันระบุว่า "Kubernetes can restart containers, but underlying application issues must be addressed separately." Pod ของ 0.6.0 ถูกเริ่มใหม่ซ้ำได้ไม่สิ้นสุดแต่ไม่มีวันหาย การแก้จริงคือ rollback หรือออกเวอร์ชันที่แก้ Dockerfile คลัสเตอร์ kind ในห้องปฏิบัติการมี node เดียว จึงสังเกตกรณี node ล้มไม่ได้
คำถามตรวจความเข้าใจ 15.4: บันทึก self-healing ฉบับหนึ่งเขียนว่า "Kubernetes restart Pod ที่ถูกลบ" ข้อความนี้คลาดเคลื่อนตรงไหน และควรเขียนใหม่อย่างไรให้ตรงกับเอกสาร
15.5 ภาพรวม Helm และ GitOps (Argo CD) (ไม่ประเมิน)
หัวข้อนี้ให้ภาพรวมตามแผนการสอนเดิม จำกัดเฉพาะสาระที่ยืนยันจากเอกสารทางการ ผู้สนใจศึกษาต่อได้จากหัวข้อการศึกษาค้นคว้าเพิ่มเติม
| เครื่องมือหรือแนวคิด | สาระจากเอกสาร | เชื่อมกับระบบของทีม |
|---|---|---|
| Helm (Charts, Values Files) | chart ต้องมี Chart.yaml ค่าเริ่มต้นอยู่ใน values.yaml ลำดับความสำคัญของ values คือ chart → parent chart → -f → --set | k8s/ อาจเป็น chart ที่ image tag อยู่ใน values.yaml |
| GitOps (OpenGitOps v1.0.0) | หลัก 4 ข้อ: Declarative, Versioned and Immutable, Pulled Automatically, Continuously Reconciled | manifest ใน Git เป็นสถานะที่ต้องการ |
| Argo CD (Overview, Automated Sync Policy) | เทียบ live state ในคลัสเตอร์กับ target state ใน Git ถ้าต่างกันเป็น OutOfSync automated sync มี prune และ selfHeal ปิดไว้โดยปริยาย | หลัง set image ใน Lab C คลัสเตอร์ต่างจาก Git |
ระดับที่ 1: ทีมทำงานแบบ "push" มาตลอด คือ workflow CD หรือคนสั่ง kubectl apply ส่งการเปลี่ยนแปลงเข้าระบบ GitOps ตามหลัก Pulled Automatically ให้ตัวแทนในระบบดึงสถานะที่ต้องการจาก Git เอง
ระดับที่ 2: ช่วงที่ Lab C ใช้ kubectl set image ปล่อย 0.6.0 โดยไม่ผ่าน main คลัสเตอร์ต่างจาก manifest ใน Git ซึ่งเป็นสภาพที่ Argo CD จะแสดงว่า OutOfSync (อธิบายด้วยแนวคิดจากเอกสาร ไม่ได้ทดลองกับ Argo CD)
ระดับที่ 3: แผนการสอนเดิมเรียก Helm ว่า "App Store" ซึ่งไม่พบในเอกสาร Helm ให้ถือเป็นอุปมา พฤติกรรมของ selfHeal เมื่อเปิดใช้และการติดตั้ง Argo CD บน kind ไม่ได้ตรวจสอบในรายวิชานี้
คำถามตรวจความเข้าใจ 15.5 (อภิปราย ไม่ประเมิน): ถ้าทีมใช้ GitOps วิธี rollback ที่สอดคล้องกับหลักควรเป็น kubectl rollout undo หรือการ revert commit ใน Git เพราะอะไร
15.6 สรุปรายวิชาและเส้นทางเรียนต่อ
ตลอด 15 บท ทีมสร้างระบบส่งมอบทีละชั้นบน repository เดียว: Linux (1) → Git และ pull request (2–3) → build และ CI (4–5) → Docker และ Compose (6–7) → CD และ Nginx (8–9) → DevSecOps และ Observability (10–11) → Rollback และ Capstone (12–13) → Kubernetes (14–15)
ระดับที่ 1: เครื่องมือเปลี่ยนไปตามเวลา แต่หลักที่ใช้ซ้ำทุกบทยังเหมือนเดิม คือ ทุกการเปลี่ยนแปลงผ่าน pull request และการตรวจอัตโนมัติ release แก้ไขไม่ได้และย้อนกลับได้ สถานะของระบบตรวจได้ด้วย endpoint หรือตัวเลข และขั้นตอนกู้ระบบต้องเขียนไว้และซ้อมก่อนใช้จริง
ระดับที่ 2: เส้นทางเรียนต่อจากหัวข้อส่วนเสริมในเอกสารอ้างอิงของรายวิชา
| เส้นทาง | เริ่มจาก | ต่อยอดจากบท |
|---|---|---|
| Helm และ GitOps (Argo CD) | เปลี่ยน k8s/ ของทีมเป็น chart แล้วศึกษาหลัก GitOps | 14–15 |
| Infrastructure as Code ด้วย Terraform | วงจร Write → Plan → Apply และ state | 8, 12 |
| Ansible | idempotency และ role | 1, 8 |
| Jenkins และ build tool อื่น | ทำ pipeline ของบทที่ 5 ซ้ำบน Jenkins แล้วเปรียบเทียบ | 5 |
| Platform Engineering | CNCF Platforms White Paper และ golden path | 13 |
ระดับที่ 3: repository ของทีมทั้งประวัติ commit, pull request และ run ของ Actions เป็นหลักฐานการทำงานจริงที่ตรวจสอบย้อนกลับได้ ดีกว่าการเขียนว่า "เคยใช้ Kubernetes" ในประวัติย่อ ก่อนเปิด repository เป็นสาธารณะต้องตรวจความลับอีกครั้งด้วย scripts/check-secrets.sh
คำถามตรวจความเข้าใจ 15.6: เลือกหนึ่งเส้นทางเรียนต่อ แล้วระบุว่าจะใช้ส่วนใดของ repository ทีมเป็นจุดเริ่มต้น และจะวัดได้อย่างไรว่าเรียนสำเร็จ
เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ
15.7 คำนวณขอบเขตของ rolling update ก่อนอ่านผลจริง
rolling update ควบคุมการแทน Pod ด้วยค่าที่สัมพันธ์กับจำนวน replicas ไม่ได้รับประกันว่าทุกการอัปเดตไม่มีคำขอล้ม ค่า maxUnavailable กำหนดจำนวนที่ยอมให้ไม่พร้อม ส่วน maxSurge กำหนดจำนวนที่เกินจากเป้าหมายได้ เมื่อใช้เปอร์เซ็นต์ Deployment ปัด unavailable ลงและ surge ขึ้นตามกติกาในเอกสาร (Kubernetes: Deployments)
ตัวอย่างสมมติคือ replicas เท่ากับ 2 ใช้ maxUnavailable: 25% และ maxSurge: 25% จำนวน unavailable ตามการปัดจะเป็น 0 ส่วน surge เป็น 1 จึงมีขอบเขตให้สร้าง Pod ใหม่เกินเป้าหมายได้หนึ่งตัวและรักษาจำนวนพร้อมตามเงื่อนไขก่อนลดตัวเก่า ตารางต่อไปนี้เป็นภาพอธิบายลำดับที่เป็นไปได้ ไม่ใช่คำรับรองลำดับ event หรือจำนวน Pod ที่กำลัง terminating ทุกขณะ
| ช่วงอธิบาย | Pod เก่าที่พร้อม | Pod ใหม่ที่พร้อม | สิ่งที่ต้องตรวจ |
|---|---|---|---|
| ก่อนเริ่ม | 2 | 0 | รุ่นเดิมผ่านเกณฑ์พร้อม |
| สร้างใหม่หนึ่งตัว | 2 | 0 | รอ image และ probe ของตัวใหม่ |
| ตัวใหม่พร้อม | 2 | 1 | controller จึงมีช่องลดตัวเก่า |
| แทนต่อไป | 1 | 1 หรือ 2 | สังเกตสถานะจริงและเงื่อนไข readiness |
| เสร็จ | 0 | 2 | ตรวจ release และคำขอผู้ใช้ |
ตัวอย่างที่ไม่ถูกต้องคือใช้ replicas เท่ากับ 1 แล้วตั้ง maxSurge: 0 กับ maxUnavailable: 1 แต่ประกาศว่าจะไม่มีช่วงหยุด เงื่อนไขนี้อนุญาตให้ตัวเก่าหายก่อนตัวใหม่พร้อม การตั้งค่าต้องสัมพันธ์กับเป้าหมายความพร้อมและทรัพยากรที่มี หากตั้ง surge เพิ่มแต่คลัสเตอร์ไม่มีทรัพยากรให้ Pod ใหม่ rollout อาจค้างโดยยังไม่ได้ผลตามภาพที่คาด
อีกความเข้าใจผิดคือ readiness ที่คืน 200 เร็วเกินไปทำให้ rolling update ปลอดภัย แม้ controller ทำตามเงื่อนไขครบ แต่ถ้า probe ไม่ตรวจความพร้อมที่มีความหมาย จะส่งคำขอไปยังตัวที่ยังบริการไม่ได้ ต้องทดสอบทั้งเวลาพร้อม การปิดตัวเก่า และการจัดการคำขอที่กำลังดำเนินอยู่ก่อนอ้างผลด้านความต่อเนื่อง
15.8 readiness, liveness และ startup แก้คนละปัญหา
readiness ตอบว่าควรส่ง traffic มาที่ container นี้หรือไม่ liveness ตอบว่าควรเริ่ม container ใหม่เพื่อกู้จากสภาวะที่ไม่ก้าวหน้าหรือไม่ และ startup probe ให้ช่วงตรวจการเริ่มระบบก่อนเปิดใช้ probe อื่นตามกติกา การใช้ endpoint เดียวกันทุก probe ทำได้ในแอปง่าย ๆ แต่ต้องอธิบายว่าครอบคลุมเงื่อนไขแต่ละประเภทอย่างไร (Kubernetes: Configure probes)
ตัวอย่างที่ผิดคือ liveness ตรวจฐานข้อมูลภายนอก แล้ว restart แอปทุกครั้งที่ฐานข้อมูลหยุด การ restart แอปไม่ซ่อมฐานข้อมูลและอาจทำให้ทุกสำเนาเริ่มใหม่พร้อมกันจนเพิ่มภาระ ควรพิจารณาว่าความล้มเหลวใดแก้ด้วยการ restart ได้ และความล้มเหลวใดควรรายงานไม่พร้อมพร้อมให้แอปรับมือการเชื่อมต่อใหม่ ทั้งนี้ readiness ที่พึ่งบริการภายนอกก็ต้องออกแบบอย่างระมัดระวัง เพราะอาจทำให้ทุกสำเนาถูกถอดออกพร้อมกัน
อีกตัวอย่างคือแอปใช้เวลาเริ่มนานกว่าปกติ แต่ liveness เริ่มตรวจเร็วและล้มหลายครั้งจนฆ่าก่อนเริ่มเสร็จ การเพิ่ม startup probe ที่มีขอบเขตเวลาตามข้อมูลการเริ่มจริงช่วยแยกช่วงเริ่มออกจากช่วงทำงาน ไม่ควรเพิ่ม delay จำนวนมากโดยไม่วัด เพราะจะทำให้ตรวจความผิดพลาดจริงช้าลงด้วย
readiness ล้มไม่ได้สั่ง restart โดยตัวมันเอง ในกรณี Service ปกติจะทำให้ Pod ไม่ได้รับ traffic ตามเงื่อนไขความพร้อม ขณะที่ liveness ล้มถึงเกณฑ์จะนำไปสู่การ restart container ตามนโยบาย การดูเพียง restart count จึงไม่พอวิเคราะห์ readiness ต้องดู probe events, Pod conditions และ endpoints ร่วมกัน
การทดลองที่เหมาะสมใช้ความผิดสามชนิดแยกกัน ได้แก่ เริ่มช้าแต่สุดท้ายพร้อม กระบวนการอยู่แต่ไม่พร้อมรับคำขอ และกระบวนการค้างที่ restart แล้วฟื้นได้ บันทึกสิ่งที่คาดกับสิ่งที่เกิดสำหรับแต่ละ probe วิธีนี้แสดงข้อจำกัดรอบด้านกว่าการใช้การลบ Pod เป็นตัวแทนของความล้มเหลวทุกชนิด
15.9 rollout ไม่ก้าวหน้าไม่ได้แปลว่าระบบย้อนกลับแล้ว
เมื่อ Deployment ไม่ก้าวหน้าตามเงื่อนไข ระบบรายงานสถานะเช่น ProgressDeadlineExceeded แต่ไม่ได้หมายความว่าได้ rollback อัตโนมัติเป็นรุ่นเดิม ต้องมีคนหรือระบบควบคุมภายนอกตัดสินและสั่งตามนโยบาย เอกสาร Deployment แยกการรายงานความก้าวหน้าออกจากการเรียก rollback จึงต้องตรวจสถานะจริงก่อนประกาศว่ากู้แล้ว
ตัวอย่างสมมติคือ image tag ใหม่ไม่มีใน registry Pod ใหม่ pull ไม่ได้ ขณะที่ Pod เก่ายังให้บริการอยู่ rollout ค้างแต่ผู้ใช้บางส่วนหรือทั้งหมดอาจยังใช้รุ่นเดิมได้ การนับเหตุนี้เป็น downtime เต็มระบบโดยไม่ตรวจคำขอจริงไม่ถูกต้อง ในทางกลับกันถ้าเงื่อนไขอนุญาตให้ลดตัวเก่ามากหรือ probe ไม่ตรง อาจมีผลกระทบแล้ว ต้องตัดสินจากทั้งสถานะคลัสเตอร์และผลบริการ
ตัวอย่างที่ผิดคือกด rollout undo แล้วถือว่าได้คืนทุกส่วนของ release คำสั่งย้อน Deployment เกี่ยวข้องกับ Pod template ตามประวัติ ไม่ได้ย้อนฐานข้อมูล ConfigMap ที่ถูกแก้แยก Service หรือผลข้างเคียงภายนอกทั้งหมด หากรุ่นเก่ารับ environment จาก ConfigMap ชื่อเดิมที่ถูกเปลี่ยนค่าแล้ว การสร้าง Pod ของรุ่นเก่าอาจยังรับค่าใหม่ ต้องออกแบบ versioning ของ config และ release record ให้ตอบได้ว่าองค์ประกอบใดต้องกลับพร้อมกัน
หลัง undo ต้องติดตาม rollout ใหม่ ตรวจ image ที่รัน ตรวจ readiness และเรียกเส้นทางผู้ใช้ การที่คำสั่งส่งไป API สำเร็จเป็นเพียงจุดเริ่มของการกู้ เมื่อจบควรบันทึก revision ก่อนและหลัง release เป้าหมาย เวลาเริ่มผลกระทบ และเวลาที่ยืนยันว่ากลับมาปกติ เพื่อเทียบกับ runbook จากบทที่ 12
15.10 self-healing กับขอบเขตที่ยังต้องออกแบบ
Kubernetes แยกการ restart container โดย kubelet ออกจากการสร้าง Pod ทดแทนโดย controller ที่เกี่ยวข้อง ถ้าเป็น Pod เดี่ยวที่ไม่มี controller การลบ Pod ไม่ได้ทำให้มี Deployment มาสร้างให้เอง ส่วน Pod ภายใต้ ReplicaSet ของ Deployment จะถูกทดแทนเพื่อให้ตรงจำนวนที่ต้องการเมื่อมีเงื่อนไขและทรัพยากรพร้อม (Kubernetes: Self-Healing)
ตัวอย่างที่ผิดคือสาธิตลบ Pod หนึ่งตัวแล้วเห็นตัวใหม่ขึ้น จึงสรุปว่าระบบทนต่อ host เสียได้แล้ว การทดลองนี้พิสูจน์กลไกทดแทน Pod ภายใต้สภาพแวดล้อมที่ยังทำงาน ไม่ได้พิสูจน์ว่า control plane, storage หรือเครื่อง host มีความซ้ำซ้อน โดยเฉพาะ kind หลาย node containers บนเครื่องเดียวซึ่งยังมีความเสี่ยงร่วมที่ host
อีกตัวอย่างคือคาดว่า self-healing จะซ่อมโค้ดที่มี bug ถ้า image เดิมเริ่มแล้วล้มซ้ำ controller จะพยายามรักษาสถานะตามที่ประกาศ แต่ไม่เขียนโค้ดแก้ให้ ต้องตรวจ log ของรอบก่อน เหตุการณ์ ค่า config และ release แล้วเปลี่ยน desired state ไปสู่รุ่นที่ใช้ได้ การ restart ซ้ำเป็นอาการที่ต้องสืบเหตุ ไม่ใช่หลักฐานว่าระบบกำลังกู้สำเร็จเสมอ
บทสรุปของกลไกทั้งหมดคือแยกสิ่งที่ระบบจัดการได้จากสิ่งที่ต้องตัดสินเอง: controller รักษาจำนวนและทำ rollout ตามนโยบาย probe ให้สัญญาณตามที่ออกแบบ ส่วนทีมยังต้องกำหนดความถูกต้องของบริการ อัตลักษณ์ release ความเข้ากันได้ของข้อมูล และหลักฐานยืนยันการกู้ การใช้ Kubernetes จึงต่อยอดการตรวจสอบเดิมของวิชา ไม่ได้ทำให้ test, review, observability และ runbook หมดความจำเป็น
กรณีอธิบายต่อเนื่อง: rollback image แล้วค่าตั้งยังผิด
กำหนดข้อมูลสมมติให้ Deployment รุ่น A ใช้ image D1 และรับ APP_MESSAGE ผ่าน environment จาก ConfigMap ชื่อ app-config ต่อมารุ่น B ใช้ D2 และแก้ ConfigMap ชื่อเดิม หาก B ล้มแล้วใช้ rollout undo กลับ Pod template ของ A, Pod ใหม่อาจยังรับค่าปัจจุบันจาก app-config จึงไม่ได้ย้อนค่าตั้งกลับโดยอัตโนมัติ
ความเข้าใจที่ผิดคือ revision ของ Deployment เป็น snapshot ของ object ทุกชนิดใน namespace ความจริงการเปลี่ยน ConfigMap แยกจาก Pod template ไม่ได้กลายเป็นประวัติครบชุดของ release ต้องออกแบบให้ config ที่ต้องย้อนอ้างอิงรุ่นได้ หรือมีกระบวนการปรับกลับอย่างมีหลักฐานพร้อมพิจารณาขอบเขตข้อมูลลับ
กรณีที่ซับซ้อนขึ้นคือแอปอ่าน ConfigMap ผ่านไฟล์ซึ่งกระจายค่าใหม่ภายหลัง แต่โค้ดอ่านเพียงครั้งเดียวตอนเริ่ม การเห็นไฟล์เปลี่ยนไม่ได้พิสูจน์ว่าพฤติกรรมแอปเปลี่ยน ต้องรู้กลไกอ่านค่าและการ reload ของแอป ส่วนการใช้ subPath มีข้อจำกัดการอัปเดตของตัวเอง จึงไม่ควรสรุปว่าการ mount ทุกแบบเหมือนกัน
ก่อนยืนยันกู้สำเร็จควรตรวจ image ตัวอ้างอิง config ค่าที่มีผลโดยไม่เปิดความลับ และ response ผ่านเส้นทางผู้ใช้ พร้อมบันทึก revision ใหม่ที่เกิดจาก undo การทดสอบนี้แสดงว่าคำสั่ง rollback เป็นกลไกส่วนหนึ่งของ runbook แต่ความครบของ release ยังเป็นสิ่งที่ทีมต้องออกแบบและตรวจสอบ
ปฏิบัติการที่ 15 rolling update, rollback และ self-healing บนคลัสเตอร์ kind
ปฏิบัติการนี้มี Lab A–E การเปลี่ยนแปลงที่เข้า repository แบ่งดังนี้ เพื่อให้ทุกคนมี pull request ของตนเองใน M13 ทุก pull request ใช้ template .github/pull_request_template.md และผ่าน review กับ required status check ตาม branch protection ของ main
| Lab | สมาชิก | branch | ไฟล์ |
|---|---|---|---|
| A.3 | คนที่ 1 (ผู้ตรวจทาน: คนที่ 2) | k8s/probes | k8s/deployment.yaml (เพิ่ม strategy และ probe) |
| B.1 | คนที่ 2 (ผู้ตรวจทาน: คนที่ 3) | fix/0.6.1-copy-data | Dockerfile, package.json, package-lock.json |
| B.2 | คนที่ 3 (ผู้ตรวจทาน: คนที่ 1) | k8s/image-0.6.1 | k8s/deployment.yaml (เปลี่ยน image tag) |
| C.4 | คนที่ 1 (ผู้ตรวจทาน: คนที่ 2) | docs/runbook-k8s | RUNBOOK.md (rollback บน Kubernetes) |
บันทึกใน Lab D และ Lab E เป็นงานรายบุคคล ไม่ต้องเข้า repository ส่วน ตัดสินใจเอง ได้แก่ A.5, B.3, C.5 และ D.3
คำสั่ง kind และ kubectl มาจากเอกสารทางการ แต่ผู้เขียนรันบนคลัสเตอร์ไม่ได้ ส่วนที่เขียนว่า "ผลที่คาดว่าจะเห็น (ยังไม่ได้ทดสอบบนคลัสเตอร์จริง)" เป็นคำอธิบายจากเอกสาร ไม่ใช่ข้อความจากหน้าจอ ให้คัดลอกผลจริงของทีมเป็นหลักฐาน ข้อความที่ทดสอบแล้วด้วย Docker มีเพียงคำตอบ /health ของ 0.5.2 และ 0.6.1 และ Error: Cannot find module '../data/items.json' ของ 0.6.0
Lab A: สร้างคลัสเตอร์ใหม่และเพิ่ม probe
สถานการณ์: ท้ายบทที่ 14 ทีมลบคลัสเตอร์ไปแล้ว ถ้า manifest ใน k8s/ ครบจริง ระบบเดิมต้องกลับมาได้ด้วยคำสั่งไม่กี่คำสั่ง
เป้าหมาย: deploy จาก main บนคลัสเตอร์ใหม่ แล้วเพิ่ม readiness/liveness probe ที่เรียก /health ผ่าน pull request (LLO 15.1 ส่วน probe)
ฝึกตามตัวอย่าง
A.1 สร้างคลัสเตอร์และนำ image เข้า (ทุกเครื่องที่ใช้ หรือเครื่องเดียวของทีม)
kind create cluster --name devops
kubectl cluster-info --context kind-devops
read -rs CR_PAT && export CR_PAT
echo "$CR_PAT" | docker login ghcr.io -u <github-user> --password-stdin
unset CR_PAT
docker pull ghcr.io/devops-team-01/devops-app:0.5.2
kind load docker-image ghcr.io/devops-team-01/devops-app:0.5.2 --name devopsเปลี่ยน devops-team-01 เป็นชื่อ owner ของทีมทุกที่ในบทนี้ kind load คัดลอก image จากเครื่องเข้า node จึงใช้ได้ทั้ง package แบบ public และ private และเพราะ tag ไม่ใช่ latest ค่าเริ่มต้นของ pull policy จึงเป็น IfNotPresent ทำให้ node ใช้ image ที่ load ไว้ได้ (หน้า quick start ของ kind)
A.2 deploy จาก main
cd ~/devops-app
git switch main
git pull
kubectl apply -f k8s/
kubectl rollout status deployment/devops-app
kubectl get pods,svcผลที่คาดว่าจะเห็น (ยังไม่ได้ทดสอบบนคลัสเตอร์จริง): rollout status จบด้วยรูปแบบ deployment "devops-app" successfully rolled out มี Pod devops-app-... 2 ตัว READY 1/1 และ Service devops-app ชนิด ClusterIP ทีมที่ยังไม่มี k8s/ ดูเฉลย Lab A
A.3 เพิ่ม probe (สมาชิกคนที่ 1) สร้าง branch k8s/probes แล้วแทนเนื้อหา k8s/deployment.yaml ทั้งไฟล์ด้วย manifest ต่อไปนี้ (ผ่าน kubernetes-validate --strict กับ schema ของ Kubernetes 1.37 และ 1.34)
apiVersion: apps/v1
kind: Deployment
metadata:
name: devops-app
labels:
app: devops-app
spec:
replicas: 2
selector:
matchLabels:
app: devops-app
strategy:
type: RollingUpdate
rollingUpdate:
# ค่าเท่ากับค่าเริ่มต้น เขียนไว้ให้เห็นชัด
# replicas 2: maxSurge 25% ปัดขึ้น = 1 Pod, maxUnavailable 25% ปัดลง = 0 Pod
maxSurge: 25%
maxUnavailable: 25%
template:
metadata:
labels:
app: devops-app
spec:
containers:
- name: app
# เปลี่ยน devops-team-01 เป็นชื่อ owner บน ghcr.io ของทีม
image: ghcr.io/devops-team-01/devops-app:0.5.2
ports:
- containerPort: 3000
env:
- name: APP_TITLE
valueFrom:
configMapKeyRef:
name: devops-app-config
key: APP_TITLE
readinessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 2
periodSeconds: 5
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 10
periodSeconds: 10ส่วนที่เพิ่มคือ strategy และ probe (หัวข้อ 15.1–15.2) probe ใช้พอร์ต 3000 ของ container ไม่ใช่พอร์ต 80 ของ Service และ selector ต้องเหมือนเดิม เพราะ .spec.selector แก้ไขไม่ได้หลังสร้าง (เอกสาร Deployments)
git add k8s/deployment.yaml
git commit -m "k8s: add readiness and liveness probes on /health"
git push -u origin k8s/probesA.4 apply หลัง merge (บนเครื่องที่รันคลัสเตอร์)
git switch main
git pull
kubectl apply -f k8s/deployment.yaml
kubectl annotate deployment/devops-app kubernetes.io/change-cause="add readiness and liveness probes"
kubectl rollout status deployment/devops-app
kubectl describe pod <ชื่อ Pod ใหม่ตัวหนึ่ง>
kubectl rollout history deployment/devops-app
kubectl port-forward service/devops-app 8080:80เปิดอีก terminal แล้วสั่ง curl -s http://localhost:8080/health ผลที่คาดว่าจะเห็น (ยังไม่ได้ทดสอบบนคลัสเตอร์จริง): Pod ชุดใหม่มาแทนชุดเดิมทีละตัว describe แสดง probe ทั้งสองที่ชี้ไป /health พอร์ต 3000 rollout history มี revision 1 และ 2 และ curl ได้ {"status":"ok","version":"0.5.2"} ถ้า CHANGE-CAUSE เป็น <none> ให้ตรวจด้วย --revision=N แล้วจดเอง
ผลลัพธ์ที่คาดหวัง A.4: manifest ที่มี probe อยู่บน main ผล rollout status ที่สำเร็จ และ /health ตอบผ่าน port-forward
ตัดสินใจเอง A.5: พิสูจน์ว่า probe ทำงานจริงด้วยสำเนา manifest ในเครื่อง (ห้าม commit) โดยเปลี่ยน path ของ readiness หรือ liveness อย่างใดอย่างหนึ่งเป็น /healthz (แอปตอบ 404) เขียนคาดการณ์ READY, RESTARTS และ rollout status ก่อน apply สังเกตด้วย get pods และ describe pod แล้วคืนสภาพด้วย apply จาก main
ผลลัพธ์ที่คาดหวัง A.5: คาดการณ์ ผลจริง (บรรทัดจาก get pods และ event) คำอธิบายที่อ้างเอกสาร probe และหลักฐานว่าคลัสเตอร์กลับมาตรงกับ main
จุดตรวจ A: Pod 2 ตัว READY 1/1 มี probe ทั้งสองชนิด และสมาชิกทุกคนอธิบายได้ว่า readiness กับ liveness ต่างกันอย่างไรเมื่อไม่ผ่าน
ปัญหาที่พบบ่อย
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
Pod เป็น ErrImagePull หรือ ImagePullBackOff | คลัสเตอร์ใหม่ยังไม่มี image หรือชื่อ owner/tag ไม่ตรงกับที่ load | kind load docker-image <image เต็ม> --name devops แล้วตรวจชื่อใน manifest |
kubectl apply -f k8s/ error เกี่ยวกับ kind.x-k8s.io/v1alpha4 | มี kind-config.yaml อยู่ใน k8s/ | ย้ายออกตามข้อตกลงของบทที่ 14 |
Pod Running แต่ READY 0/1 ตลอด | probe ใช้พอร์ต 80 หรือ path ผิด | ใช้พอร์ต 3000 และ path /health |
docker pull ได้ denied | token ไม่มี read:packages หรือยังไม่ได้ login | สร้าง token (classic) ที่มี read:packages แล้ว login ใหม่ |
เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ
Lab B: rolling update ไป tag ใหม่
สถานการณ์: บทที่ 12 สรุปว่าทีมควร roll forward ด้วย 0.6.1 ที่แก้ Dockerfile ของเวอร์ชันพัง 0.6.0 Lab นี้ออกเวอร์ชันนั้นผ่าน pipeline เดิม แล้วนำเข้าคลัสเตอร์โดยระบบไม่หยุด
เป้าหมาย: ออก 0.6.1 ผ่าน pull request และ CD เปลี่ยน image ใน k8s/deployment.yaml ผ่าน pull request แล้วติดตาม rolling update จนสำเร็จ (LLO 15.1 ส่วน rolling update)
ฝึกตามตัวอย่าง
B.1 roll forward เป็น 0.6.1 (สมาชิกคนที่ 2 เริ่มตั้งแต่ต้นคาบ คู่ขนานกับ Lab A) แก้สาเหตุของ 0.6.0 จากบทที่ 12 ใน Dockerfile ให้เพิ่มบรรทัด COPY --from=build /app/data ./data ต่อจาก COPY --from=build /app/src ./src ไม่ต้องแก้โค้ดหรือ test
git switch main
git pull
git switch -c fix/0.6.1-copy-data
nano Dockerfile
npm version 0.6.1 --no-git-tag-version
npm test
git add Dockerfile package.json package-lock.json
git commit -m "fix: copy data/ into runtime image, bump 0.6.1"
git push -u origin fix/0.6.1-copy-dataหลัง merge หัวหน้าทีมสั่ง git tag v0.6.1 และ git push origin v0.6.1 ตามบทที่ 8 workflow CD จะ test, build, scan และ push image 0.6.1 ขึ้น ghcr.io (ทดสอบแล้วด้วย Docker ว่า image จาก tree ของ 0.6.0 ที่เพิ่มบรรทัดนี้และเวอร์ชัน 0.6.1 เริ่มได้และ /health ตอบ {"status":"ok","version":"0.6.1"}) ทีมที่ออก 0.6.1 หรือสูงกว่าไปแล้วระหว่าง Capstone ให้ใช้เลข patch ถัดไปของตนเองแทน 0.6.1
B.2 rolling update (สมาชิกคนที่ 3 หลัง CD push image สำเร็จ)
docker pull ghcr.io/devops-team-01/devops-app:0.6.1
kind load docker-image ghcr.io/devops-team-01/devops-app:0.6.1 --name devops
git switch main
git pull
git switch -c k8s/image-0.6.1
nano k8s/deployment.yamlแก้เฉพาะบรรทัด image: จาก 0.5.2 เป็น 0.6.1 แล้ว commit, push และเปิด pull request หลัง merge ให้สั่งบนเครื่องที่รันคลัสเตอร์ โดยเปิดอีก terminal สั่ง kubectl get rs และ kubectl get pods ซ้ำเป็นระยะระหว่าง rollout
git switch main
git pull
kubectl apply -f k8s/deployment.yaml
kubectl annotate deployment/devops-app kubernetes.io/change-cause="image 0.6.1"
kubectl rollout status deployment/devops-app
kubectl rollout history deployment/devops-appผลที่คาดว่าจะเห็น (ยังไม่ได้ทดสอบบนคลัสเตอร์จริง): rollout status แสดงข้อความรูปแบบ Waiting for rollout to finish: 1 out of 2 new replicas have been updated... แล้วจบด้วย successfully rolled out kubectl get rs มี ReplicaSet ใหม่ DESIRED 2 และ ReplicaSet เดิม DESIRED 0 ที่เก็บไว้สำหรับย้อนกลับ ไม่ควรเห็น Pod เกิน 3 ตัว และ /health ผ่าน port-forward ได้ {"status":"ok","version":"0.6.1"} ถ้า port-forward หลุดระหว่าง rollout ให้เริ่มใหม่ (port-forward ผูกกับ Pod ที่เลือกตอนเริ่ม ข้อสรุปจากเอกสาร kubectl port-forward ยังไม่ได้ทดสอบ)
ทางสำรองสำหรับทีมที่ CD ใช้ไม่ได้: load image 0.5.0 และ 0.5.2 (มีบน ghcr.io แล้ว) สั่ง kubectl set image deployment/devops-app app=ghcr.io/devops-team-01/devops-app:0.5.0 รอ rollout สำเร็จ แล้ว rolling update กลับไป 0.5.2 ด้วย apply จาก main ผล rollout status ของขั้นหลังคือหลักฐาน (ระบุว่าใช้ทางสำรอง)
ผลลัพธ์ที่คาดหวัง B.2: image 0.6.1 บน ghcr.io จาก run ของ CD ที่ผ่าน manifest บน main ชี้ไปที่ 0.6.1 ผล rollout status ที่สำเร็จ และ /health ตอบเวอร์ชันใหม่
ตัดสินใจเอง B.3: สมมุติว่าเครื่องรัน Pod ของแอปพร้อมกันเกิน 2 ตัวไม่ได้ ให้เลือก maxSurge และ maxUnavailable ที่ทำให้ rolling update ไม่สร้าง Pod เกิน 2 ตัว (คำอธิบายฟิลด์ใน API ระบุว่าทั้งสองค่าเป็น 0 พร้อมกันไม่ได้) คำนวณลำดับแบบตารางในหัวข้อ 15.1 และจำนวน Pod พร้อมน้อยที่สุด ถ้าต้องการทดลอง ให้ใช้สำเนา manifest ในเครื่องร่วมกับ kubectl rollout restart deployment/devops-app แล้วคืนค่าด้วย apply จาก main และจดเลข revision ที่เพิ่มขึ้น
ผลลัพธ์ที่คาดหวัง B.3: ค่าที่เลือก ตารางลำดับ และข้อแลกเปลี่ยนเทียบกับค่าเริ่มต้น ในหลักฐาน M13
จุดตรวจ B: rollout status สำเร็จ /health ตอบ 0.6.1 (หรือ 0.5.2 สำหรับทางสำรอง) และ rollout history มี revision ใหม่
ปัญหาที่พบบ่อย
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
image 0.6.1 ยังล้มด้วย Cannot find module | เพิ่มบรรทัด COPY ผิด stage หรือ path ผิด | บรรทัดต้องอยู่ใน stage สุดท้ายต่อจาก COPY --from=build /app/src ./src |
| CD ล้มที่ประตูตรวจ health เพราะเวอร์ชันไม่ตรง | สร้าง tag ก่อน merge pull request ที่ปรับ package.json | ลบ tag แล้วสร้างใหม่หลัง merge ตามบทที่ 12 |
rollout status ค้างที่ 1 out of 2 new replicas | Pod ใหม่ไม่พร้อม เช่น ยังไม่ได้ kind load tag ใหม่ | ดู kubectl describe pod และ kubectl logs ของ Pod ใหม่ |
เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ
Lab C: ปล่อย tag ที่พังแล้วใช้ rollout undo
สถานการณ์: image 0.6.0 ที่พังจากบทที่ 12 ยังอยู่บน ghcr.io และเคยทำให้ระบบ Compose หยุดจนทีม rollback เสร็จ Lab นี้จำลองว่ามีคน deploy tag เก่าที่พังนี้เข้าคลัสเตอร์โดยผิดพลาด
เป้าหมาย: ปล่อย 0.6.0 สังเกตอาการ แล้ว rollback ด้วย kubectl rollout undo จนกลับมาใช้งานได้ด้วยเวอร์ชันก่อนหน้า พร้อมผล rollout status, rollout history และ rollout undo (LLO 15.2)
ฝึกตามตัวอย่าง
C.1 เตรียม สั่ง docker pull และ kind load docker-image ... --name devops สำหรับ tag 0.6.0 แล้วคัดลอกผล kubectl rollout history deployment/devops-app ก่อนเกิดเหตุเก็บไว้
C.2 ปล่อยเวอร์ชันพัง (จำลองการ deploy ที่ผิดพลาด ไม่ผ่าน main)
kubectl set image deployment/devops-app app=ghcr.io/devops-team-01/devops-app:0.6.0
kubectl annotate deployment/devops-app kubernetes.io/change-cause="image 0.6.0 (broken drill)"
kubectl rollout status deployment/devops-appบันทึกเวลาที่เริ่ม เมื่อ rollout status รอซ้ำโดยไม่คืบหน้า ให้กด Ctrl+C (หยุดดูเท่านั้น rollout ยังดำเนินอยู่) แล้วตรวจอาการด้วย kubectl get pods, kubectl logs <ชื่อ Pod ของ 0.6.0> และ curl ที่ /health ผ่าน port-forward
ผลที่คาดว่าจะเห็น (ยังไม่ได้ทดสอบบนคลัสเตอร์จริง): Pod ใหม่ 1 ตัวเป็น Error หรือ CrashLoopBackOff และ RESTARTS เพิ่มขึ้นเรื่อย ๆ Pod เดิม 2 ตัวยัง Running READY 1/1 เพราะ maxUnavailable = 0 และ Pod ใหม่ไม่เคยพร้อม kubectl logs ควรมี Error: Cannot find module '../data/items.json' (ทดสอบแล้วด้วย Docker ซึ่งจบด้วย exit code 1) และ curl ยังได้ "version":"0.6.1" ตัวอย่าง image ผิดในเอกสาร Deployments แสดง Pod เดิมยัง Running เช่นกัน ถ้ารอเกิน progressDeadlineSeconds (600 วินาที) rollout status จะจบด้วย exit code ที่ไม่ใช่ 0
C.3 rollback ด้วย rollout undo
kubectl rollout history deployment/devops-app
kubectl rollout history deployment/devops-app --revision=<เลข revision ที่จะกลับไป>
kubectl rollout undo deployment/devops-app
kubectl rollout status deployment/devops-app
kubectl rollout history deployment/devops-appบันทึกเวลาที่ระบบกลับมาครบ 2 Pod ที่พร้อม แล้ว curl ที่ /health ผลที่คาดว่าจะเห็น (ยังไม่ได้ทดสอบบนคลัสเตอร์จริง): rollout undo แสดงข้อความรูปแบบ deployment.apps/devops-app rolled back rollout status สำเร็จ Pod ของ 0.6.0 หายไป rollout history มี revision ใหม่ที่เลขสูงสุด ("Each rollback updates the revision of the Deployment.") และ /health ได้ "version":"0.6.1" (หรือ 0.5.2 สำหรับทางสำรอง)
C.4 ปรับ RUNBOOK (สมาชิกคนที่ 1) สร้าง branch docs/runbook-k8s เพิ่มส่วนต่อไปนี้ใน RUNBOOK.md และเพิ่มแถวในตารางบันทึกเหตุการณ์เดิม (0.6.0 อาการ เวลาที่พบ เวลาที่กู้เสร็จ เวลากู้เป็นนาที เวอร์ชันที่กลับไป) รัน bash scripts/check-secrets.sh ก่อน commit แล้วเปิด pull request
## rollback บน Kubernetes (คลัสเตอร์ kind `devops`)
อาการ: `kubectl rollout status deployment/devops-app` ไม่จบ หรือ Pod ใหม่เป็น `Error`, `CrashLoopBackOff`, `ImagePullBackOff`
1. บันทึกเวลาที่พบ แล้วดู `kubectl rollout history deployment/devops-app`
2. ตรวจ `--revision=<N>` ว่าเป็น image ที่เคยทำงานได้ ไม่ใช่ "เลขก่อนหน้า" โดยไม่ตรวจ
3. `kubectl rollout undo deployment/devops-app --to-revision=<N>` รอ `rollout status` สำเร็จ แล้วตรวจ `/health`
4. ตรวจว่า image ใน `k8s/deployment.yaml` บน `main` ตรงกับคลัสเตอร์ ถ้าเวอร์ชันพังเข้ามาทาง `main` ให้เปิด pull request revert ทันที
5. บันทึกเวลาที่ระบบกลับมา แจ้งทีม และเปิด issue หาสาเหตุตัดสินใจเอง C.5: ก่อนสั่ง undo ใน C.3 ให้ตัดสินว่าจะใช้ undo แบบไม่ระบุ หรือ --to-revision โดยเปิด --revision=N ของทุก revision แล้วเขียนเหตุผล หลังกู้เสร็จให้ตอบ (1) คลัสเตอร์ตรงกับ k8s/deployment.yaml บน main หรือไม่ พิสูจน์ด้วย kubectl describe deployment devops-app (2) เวลากู้และผลต่อผู้ใช้ของ Lab C ต่างจากเหตุการณ์ในบทที่ 12 (จากบันทึกใน RUNBOOK) อย่างไร
ผลลัพธ์ที่คาดหวัง C.5: เหตุผลที่อ้างผล --revision=N ผลเทียบคลัสเตอร์กับ main และการเปรียบเทียบสองเหตุการณ์ เขียนในหลักฐาน M13
จุดตรวจ C: /health ตอบเวอร์ชันก่อนหน้า ไม่มี Pod ของ 0.6.0 เหลือ rollout history มี revision ใหม่จาก undo และ RUNBOOK มีบันทึกเหตุการณ์
ปัญหาที่พบบ่อย
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
rollout status รอนานไม่จบ | Pod ใหม่ไม่เคยพร้อม Deployment รอจนเกิน progressDeadlineSeconds | กด Ctrl+C แล้วตรวจอาการ การหยุดดูไม่หยุด rollout |
Pod ใหม่เป็น ErrImagePull แทน CrashLoopBackOff | ยังไม่ได้ kind load tag 0.6.0 | ยังฝึก undo ได้ แต่บันทึกว่าอาการต่างจากที่ตั้งใจ |
| undo แล้วได้เวอร์ชันที่ไม่ต้องการ | การทดลองใน A.5 หรือ B.3 สร้าง revision เพิ่ม | ใช้ --to-revision หลังตรวจ --revision=N |
เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ
Lab D: ลบ Pod แล้วสังเกตการสร้างใหม่
สถานการณ์: ในบทที่ 12 container ที่ตายต้องรอคนเริ่มใหม่ ทีมต้องการเห็นว่า Deployment ดูแลจำนวน Pod เองอย่างไร และอธิบายด้วยเอกสารทางการ
เป้าหมาย: ลบ Pod ที่กำลังทำงาน บันทึกว่า Deployment สร้าง Pod ใหม่อย่างไร โดยอ้างเอกสาร Kubernetes ทางการ (LLO 15.3) แต่ละคนทำและบันทึกของตนเอง
ฝึกตามตัวอย่าง
D.1 ลบ Pod (สมาชิกทุกคน ผลัดกันทำ)
kubectl get pods,rs
kubectl delete pod <ชื่อ Pod หนึ่งตัวของ devops-app>
kubectl get pods,rsผลที่คาดว่าจะเห็น (ยังไม่ได้ทดสอบบนคลัสเตอร์จริง): มี Pod ชื่อใหม่ 1 ตัวที่ AGE น้อยกว่าอีกตัวมาก จำนวนกลับเป็น 2 และ ReplicaSet ปัจจุบันยังมี DESIRED 2 ชื่อ Pod ขึ้นต้นด้วยชื่อ ReplicaSet ซึ่งตามตัวอย่างในเอกสาร Deployments คือชื่อ Deployment ตามด้วยค่า pod-template-hash
D.2 เขียนบันทึก self-healing (รายบุคคล) ใช้หัวข้อต่อไปนี้ (1) คำสั่งที่ใช้ (2) ผล kubectl get pods,rs ก่อนและหลังลบ คัดลอกจากหน้าจอ (3) คำอธิบายด้วยคำของตนเองว่า Pod ใหม่มาจากที่ใดและเหตุใดจำนวนกลับเป็น 2 (4) ลิงก์เอกสาร ข้อความที่ยกมา และส่วนที่เป็นการอนุมานที่เอกสารไม่ได้เขียนตรง ๆ (5) ข้อจำกัดของ self-healing
ผลลัพธ์ที่คาดหวัง D.2: บันทึกที่ผ่านรายการตรวจ CLO3 ครบ 5 ข้อ
ตัดสินใจเอง D.3: ออกแบบการทดลองหนึ่งรายการที่ทดสอบขอบเขตของ self-healing เช่น ลบ Pod ทั้งสองตัวพร้อมกันแล้วดูว่า curl หยุดตอบช่วงหนึ่งหรือไม่ หรือ kubectl scale deployment/devops-app --replicas=1 แล้วลบ Pod ที่เหลือ เขียนคาดการณ์ ทดลอง แล้วอธิบายด้วยเอกสาร ห้ามลบ Deployment หรือ ReplicaSet และคืนสภาพด้วย kubectl apply -f k8s/ จาก main
ผลลัพธ์ที่คาดหวัง D.3: คาดการณ์ ผลจริง คำอธิบายที่อ้างเอกสาร และหลักฐานการคืนสภาพ ต่อท้ายบันทึก D.2
จุดตรวจ D: ทุกคนมีบันทึกของตนเอง และอธิบายได้ว่า "สร้าง Pod ทดแทน" ต่างจาก "restart container" อย่างไร
ปัญหาที่พบบ่อย
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
| Pod ที่ลบไม่ได้รับการทดแทน | ลบ Pod ที่ไม่อยู่ใต้ Deployment เช่น Pod hello ถ้าสร้างซ้ำตามบทที่ 14 | ตรวจว่าชื่อขึ้นต้นด้วย devops-app- |
เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ
Lab E: สรุปสิ่งที่เรียนจาก Kubernetes และคำถามที่ยังค้าง
สถานการณ์: ก่อนปิดคลัสเตอร์ของบทสุดท้าย แต่ละคนสรุปสิ่งที่เรียนและคำถามที่ยังตอบไม่ได้ เพื่อเป็นจุดเริ่มของการเรียนต่อ
เป้าหมาย: เขียน Learning Log ครั้งที่ 15 ที่เชื่อม Kubernetes กับระบบที่ทีมสร้างมาตลอดรายวิชา (งานข้อ 3 ประกอบ LLO 15.1–15.3)
ฝึกตามตัวอย่าง
E.1 เทียบจากหลักฐานของตนเอง เปิดผลคำสั่งจาก Lab A–D แล้วเขียนความต่างที่เห็นจากผลของตนเองสี่คู่ ได้แก่ healthcheck กับ probe, scripts/deploy.sh กับ rolling update, workflow Rollback กับ rollout undo และการเริ่ม container ใหม่ด้วยมือกับ self-healing
E.2 เขียน Learning Log ครั้งที่ 15 ตอบ 4 หัวข้อ: สิ่งที่เรียนในบทนี้ · คำสั่งหรือข้อความ error ที่พบและแก้อย่างไร · แหล่งที่ใช้ค้นหาคำตอบ (ลิงก์เอกสารทางการอย่างน้อย 1 แหล่ง) · คำถามเกี่ยวกับ Kubernetes ที่ยังค้างอย่างน้อย 2 ข้อ และเส้นทางเรียนต่อที่เลือกจากหัวข้อ 15.6 พร้อมเหตุผล
ตัดสินใจเอง E.3: เลือกคำถามที่ยังค้างหนึ่งข้อ ตอบด้วยการอ่านเอกสารทางการพร้อมลิงก์และข้อความที่ยกมา หรือถ้าต้องทดลอง ให้เขียนแผนการทดลอง (คำสั่ง ผลที่คาด และวิธีตัดสิน) แทน
ผลลัพธ์ที่คาดหวัง E.3: คำถามหนึ่งข้อพร้อมคำตอบที่อ้างเอกสาร หรือแผนการทดลองที่ตรวจสอบได้
จุดตรวจ E: Learning Log ครบ 4 หัวข้อ E.1 อ้างผลของตนเอง และมีลิงก์เอกสารทางการ
ปัญหาที่พบบ่อย
| อาการ | สาเหตุ | วิธีแก้ |
|---|---|---|
| ความต่างใน E.1 ซ้ำกับเนื้อหาในบทความ | เขียนจากความจำ | อ้างบรรทัดหรือตัวเลขจากผลคำสั่งของตนเอง |
| คำถามกว้างเกินไป | ยังไม่ได้แยกเป็นคำถามที่ตรวจสอบได้ | เขียนให้มีคำตอบชัด เช่น "ถ้า node ของ kind หยุด Pod จะเป็นอย่างไร" |
เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ
เก็บกวาด
ตรวจก่อนลบ: k8s/deployment.yaml บน main มี probe และชี้ไปที่ 0.6.1 (หรือ 0.5.2) pull request ทั้งสี่รายการ merge แล้ว และคัดลอกผลคำสั่งที่เป็นหลักฐานออกจาก terminal ครบแล้ว
kind delete cluster --name devops
kind get clusters
docker logout ghcr.ioผลที่คาดว่าจะเห็น (ยังไม่ได้ทดสอบบนเครื่องห้องปฏิบัติการ): kind get clusters ไม่แสดง devops อีก ลบ branch ที่ merge แล้วทั้งในเครื่องและบน GitHub
สิ่งที่ต้องเก็บไว้หลังจบรายวิชา (บทนี้เป็นบทสุดท้าย)
- repository ของทีมทั้งหมด (commit, pull request, issue, run ของ Actions) รวม
RUNBOOK.mdที่มี rollback ทั้งสองแบบ,k8s/,compose.yaml,.github/workflows/และdocs/dora.md - tag ทั้งหมดรวม
v0.6.0และ image บน ghcr.io เพราะ release แก้ไขไม่ได้ และ tag ที่พังเป็นบันทึกของเหตุการณ์ - Learning Log บทที่ 1–15 และบันทึก self-healing
ก่อนเปลี่ยน repository เป็นสาธารณะหรือแนบในใบสมัครงาน ให้รัน bash scripts/check-secrets.sh อีกครั้ง
สรุป
Deployment เปลี่ยนเวอร์ชันด้วยการสร้าง ReplicaSet ใหม่แล้วค่อย ๆ แทน Pod เดิม สำหรับ 2 replicas ค่าเริ่มต้น 25% ทำให้มี Pod ไม่เกิน 3 และพร้อมไม่น้อยกว่า 2 กลไกนี้ปลอดภัยเมื่อ readinessProbe บอกได้ว่า Pod ใดพร้อม ส่วน livenessProbe เริ่ม container ที่ค้างใหม่ ต่างจาก healthcheck ของ Compose ที่บอกสถานะแต่ไม่ลงมือแก้
kubectl rollout undo ย้อน Pod template ไป revision เดิมและสร้าง revision ใหม่ทุกครั้ง แต่ Kubernetes ไม่ rollback ให้เอง การเลือกปลายทางยังเป็นงานของคนตาม RUNBOOK self-healing พาจำนวน Pod กลับสู่ค่าที่ต้องการ แต่ไม่แก้บั๊กของแอป Helm และ GitOps ต่อยอดแนวคิด "สถานะที่ต้องการ" นี้
สิ่งที่ส่งต่อหลังจบรายวิชา
| สิ่งที่ได้ | นำไปใช้ที่ |
|---|---|
k8s/deployment.yaml ที่มี probe, strategy และ tag ใหม่ (M13) | ฐานของ Helm chart หรือ GitOps เมื่อศึกษาต่อ |
ส่วน rollback บน Kubernetes และบันทึกเหตุการณ์ใน RUNBOOK.md | portfolio และตัวอย่างเอกสารดูแลระบบ |
| บันทึก self-healing พร้อมแหล่งอ้างอิง | ตัวอย่างการเรียนรู้เครื่องมือใหม่จากเอกสารทางการ |
| Learning Log ครั้งที่ 15 | แผนการเรียนรู้ด้วยตนเองหลังจบรายวิชา |
สรุปคำสั่ง
| คำสั่ง | ความหมาย |
|---|---|
kind create cluster --name devops / kind delete cluster --name devops | สร้าง / ลบคลัสเตอร์ kind |
kind load docker-image <image> --name devops | คัดลอก image จากเครื่องเข้า node ของคลัสเตอร์ |
kubectl apply -f k8s/ | สร้างหรือปรับ object ตาม manifest |
kubectl set image deployment/devops-app app=<image> | เปลี่ยน image ของ container app โดยข้าม pull request (ใช้จำลองเหตุการณ์) |
kubectl rollout status deployment/devops-app | ติดตาม rollout จนสำเร็จหรือเกิน progress deadline |
kubectl rollout history deployment/devops-app [--revision=N] | ดูรายการ revision หรือรายละเอียดของ revision |
kubectl annotate deployment/devops-app kubernetes.io/change-cause="..." | บันทึกเหตุผลที่แสดงในคอลัมน์ CHANGE-CAUSE |
kubectl rollout undo deployment/devops-app [--to-revision=N] | ย้อนไป revision ก่อนหน้าหรือที่ระบุ |
kubectl rollout restart deployment/devops-app | เริ่ม rollout ใหม่ เช่น หลังแก้ ConfigMap |
kubectl get rs / kubectl delete pod <ชื่อ> | ดู ReplicaSet / ลบ Pod เพื่อสังเกต self-healing |
kubectl logs <Pod> / kubectl describe pod <Pod> | ดู log / รายละเอียดและ event ของ Pod |
แบบฝึกหัดท้ายบท
แบบฝึกหัดแบ่งเป็นความเข้าใจ การประยุกต์ และการวิเคราะห์ปัญหา ข้อ ข1 คือแบบฝึกหัดตามแผนการสอน (อ่านผล kubectl rollout history แล้วบอกว่าจะ undo ไป revision ใด) ทุกข้อไม่นับคะแนน
ก. ความเข้าใจ
- ก1 (LLO 15.1) อธิบายความต่างของ readinessProbe และ livenessProbe เมื่อการตรวจไม่ผ่าน และเทียบกับ healthcheck ของ Compose ในบทที่ 7
- ก2 (LLO 15.1) Deployment มี
replicas: 10และใช้ค่าเริ่มต้น คำนวณmaxSurge,maxUnavailableเป็นจำนวน Pod จำนวน Pod รวมสูงสุด และ Pod พร้อมน้อยที่สุดระหว่าง rollout - ก3 (LLO 15.3) จากหน้า Kubernetes Self-Healing ระบุสิ่งที่ self-healing ทำได้ 3 อย่างและทำไม่ได้ 1 อย่าง พร้อมยกข้อความ
ข. การประยุกต์
- ข1 (LLO 15.2) ตารางต่อไปนี้เป็นสถานการณ์สมมุติสำหรับแบบฝึกหัด ไม่ใช่ผลรันจริง ทีมหนึ่งสรุปผล
kubectl rollout historyและ--revision=Nได้ดังนี้ ขณะนี้อยู่ที่ revision 5 และ Pod ใหม่ READY0/1ตลอด จะ undo ไป revision ใด ด้วยคำสั่งใด เหตุใดundoแบบไม่ระบุ revision จึงไม่ใช่คำตอบที่ดี และหลัง undo จะมีเลข revision ใด
| REVISION | CHANGE-CAUSE | image จาก --revision=N | หมายเหตุของทีม |
|---|---|---|---|
| 1 | <none> | 0.5.2 | ยังไม่มี probe |
| 2 | add readiness and liveness probes | 0.5.2 | ทำงานปกติ |
| 3 | image 0.6.1 | 0.6.1 | ทำงานปกติ |
| 4 | image 0.6.2 | 0.6.2 | CrashLoopBackOff |
| 5 | image 0.6.3 | 0.6.3 | readiness path พิมพ์ผิดเป็น /heath |
- ข2 (LLO 15.1) สมมุติว่าแอปเวอร์ชันใหม่ต้องใช้ประมาณ 20 วินาทีก่อนตอบ
/healthได้ เขียนส่วน probe ใหม่ และอธิบายว่าค่าที่เลือกป้องกันไม่ให้ container ถูกเริ่มใหม่ก่อนพร้อมได้อย่างไร
ค. การวิเคราะห์ปัญหา
- ค1 (LLO 15.2) หลัง Lab C สมาชิกคนหนึ่งสั่ง
kubectl apply -f k8s/จาก branch เก่าในเครื่องที่ยังชี้ image0.6.0วิเคราะห์ผลต่อคลัสเตอร์และผู้ใช้ และเสนอกติกาของทีมเพื่อกันเหตุนี้ - ค2 (LLO 15.1) สถานการณ์สมมุติ:
rollout statusแสดงข้อความรูปแบบWaiting for rollout to finish: 1 out of 2 new replicas have been updated...อยู่นาน Pod ใหม่ STATUSRunningREADY0/1RESTARTS 0 วิเคราะห์สาเหตุที่เป็นไปได้อย่างน้อย 2 ข้อ คำสั่งที่ใช้ตรวจ และความต่างจากกรณี 0.6.0 - ค3 (LLO 15.3) บันทึก self-healing หนึ่งเขียนว่า "เมื่อลบ Pod แล้ว Kubernetes restart Pod เดิมกลับมาภายใน 1 วินาที ตามเอกสาร Kubernetes" โดยไม่มีลิงก์ วิเคราะห์ว่าไม่ผ่านรายการตรวจ CLO3 ข้อใด และเขียนส่วนคำอธิบายกับแหล่งอ้างอิงใหม่
เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ
การศึกษาค้นคว้าเพิ่มเติม
- อ่าน Kubernetes: Deployments ส่วน Rolling Back a Deployment แล้วบันทึกว่าส่วนใดอธิบายผลที่ทีมเห็นใน Lab B และ C
- อ่าน Kubernetes: Self-Healing ทั้งหน้า แล้วเพิ่มข้อจำกัดที่ยังไม่ได้กล่าวในบทนี้ลงในบันทึกของ Lab D
- อ่าน Kubernetes: Configure Liveness, Readiness and Startup Probes ส่วน startup probe เพื่อตอบข้อ ข2
- เส้นทาง Helm และ GitOps: อ่าน Helm: Charts, OpenGitOps และ Argo CD: Automated Sync Policy แล้วร่างโครงสร้าง chart สำหรับ
k8s/ของทีม
เอกสารอ้างอิง
แหล่งข้อมูลเพิ่มเติมสำหรับตัวอย่างและกรณีอธิบาย
ตรวจเอกสารออนไลน์เมื่อ 23 ตุลาคม 2569 ตัวเลขและเหตุการณ์ที่ระบุว่าสมมติใช้เพื่ออธิบายกลไก ไม่ใช่ผลทดลองหรือสถิติขององค์กรจริง
เอกสารอ้างอิงของเนื้อหาเดิม
- Argo Project. (n.d.). Automated sync policy. Argo CD documentation. https://argo-cd.readthedocs.io/en/stable/user-guide/auto_sync/
- Argo Project. (n.d.). Overview. Argo CD documentation. https://argo-cd.readthedocs.io/en/stable/
- Helm. (n.d.). Charts. https://helm.sh/docs/topics/charts/
- Helm. (n.d.). Values files. https://helm.sh/docs/chart_template_guide/values_files/
- Kubernetes Documentation. (n.d.). Configure liveness, readiness and startup probes. https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
- Kubernetes Documentation. (n.d.). Deployments. https://kubernetes.io/docs/concepts/workloads/controllers/deployment/
- Kubernetes Documentation. (n.d.). Kubernetes self-healing. https://kubernetes.io/docs/concepts/architecture/self-healing/
- Kubernetes SIGs. (n.d.). kind. https://kind.sigs.k8s.io/
- OpenGitOps. (n.d.). GitOps principles v1.0.0. https://opengitops.dev/