Sign in

Part of 10306343 การจัดการโครงสร้างพื้นฐานและการส่งมอบซอฟต์แวร์อย่างต่อเนื่อง (Development Operations: DevOps)

2026-10-20 · updated 2026-10-29 · 26 min read

บทที่ 8 CD: ส่ง image ขึ้น ghcr.io และ deploy ไปเครื่องปลายทาง

ShareFacebookXLinkedInLINE

แผนบริหารการสอนประจำบทที่ 8

หัวข้อเนื้อหา

  • 8.1 Continuous Delivery, Continuous Deployment และ deployment pipeline
  • 8.2 registry และ tag ของ image
  • 8.3 GITHUB_TOKEN และสิทธิ์ของ workflow
  • 8.4 วิธี deploy ไปยังเครื่องปลายทาง
  • 8.7 เอกลักษณ์ของ release ตั้งแต่ commit ถึง image
  • 8.8 สิทธิ์ push กับสิทธิ์ pull ไม่ใช่สิ่งเดียวกัน
  • 8.9 deploy สำเร็จต้องตรวจผ่านเส้นทางที่ผู้ใช้เข้าถึง
  • 8.10 ลำดับการปล่อยรุ่นและการเปลี่ยนพร้อมกัน
  • ปฏิบัติการที่ 8 ส่ง image ขึ้น ghcr.io และ deploy อัตโนมัติ (Lab A–D)

วัตถุประสงค์เชิงพฤติกรรม

เมื่อศึกษาบทนี้และทำปฏิบัติการครบถ้วนแล้ว ผู้เรียนสามารถปฏิบัติได้ตามผลลัพธ์การเรียนรู้ต่อไปนี้ โดยแต่ละข้อมีงานที่ใช้วัดผลกำกับไว้

รหัสผลลัพธ์การเรียนรู้วัดจากCLO ของรายวิชา
8.1กำหนดค่า workflow ที่ให้มาให้ build และ push image ขึ้น ghcr.io ด้วย GITHUB_TOKEN แล้ว deploy ไปเครื่องปลายทางงานข้อ 1CLO2
8.2วาดและอธิบาย flow commit → CI → image → deploy ของระบบตน และระบุจุดที่ deploy อาจล้มอย่างน้อย 2 จุดงานข้อ 2CLO1

CLO ที่เกี่ยวข้อง (ข้อความจากหลักสูตร):

  • CLO1 สามารถนำความต้องการและสถาปัตยกรรมการส่งมอบซอฟต์แวร์ที่กำหนดมาใช้ โดยอธิบาย Git workflow, CI/CD pipeline flow และการใช้ Containerization ได้อย่างเหมาะสมกับบริบทของงาน
  • CLO2 สามารถกำหนดค่าและใช้งาน DevOps workflow ที่กำหนดมา เพื่อให้กระบวนการพัฒนา–ทดสอบ–ส่งมอบซอฟต์แวร์เป็นไปอย่างถูกต้องและมีประสิทธิภาพ

วิธีสอนและกิจกรรมการเรียนการสอน

  1. บรรยายแนวคิดตามหัวข้อ 8.1–8.4 โดยเริ่มจากคำถามว่า "หลัง merge แล้ว โค้ดไปถึงผู้ใช้ได้อย่างไร" และใช้คำถามตรวจความเข้าใจท้ายหัวข้อเป็นกิจกรรมอภิปรายเป็นคู่
  2. สาธิตการ push tag v*.*.* แล้วติดตาม run ของ workflow CD ตั้งแต่ test, build-push จนถึง deploy พร้อมเปิดหน้า package บน GitHub ให้เห็น tag ของ image
  3. ให้ผู้เรียนวาด flow ของระบบทีมบนกระดาษก่อนเริ่ม Lab แล้วกลับมาแก้หลังทำ Lab D เสร็จ เพื่อใช้เป็นร่างของใบงาน CLO1 ชิ้นที่ 4
  4. ปฏิบัติการ Lab A–D บน repository ทีม แต่ละ Lab ประกอบด้วยส่วนฝึกตามตัวอย่างและส่วนที่ผู้เรียนต้องตัดสินใจเอง
  5. มอบหมายใบงาน CLO1 แบบฝึกหัดท้ายบท Learning Log ครั้งที่ 8 และการศึกษาค้นคว้าเพิ่มเติม

สื่อการเรียนการสอน

  • เอกสารประกอบการสอนบทที่ 8 (บทความนี้) และหน้ารวมรายวิชา 10306343
  • repository ทีม devops-app ที่ผ่าน M6 จากบทที่ 7 และเครื่องปลายทางของทีมในห้องปฏิบัติการที่ติดตั้ง Docker Engine และ Docker Compose plugin แล้ว
  • เอกสารทางการ GitHub Docs หัวข้อ Working with the Container registry, Publishing Docker images และ Using secrets in GitHub Actions
  • DORA: software delivery performance metrics
  • (ผู้สอน) Humble, J., & Farley, D. (2010). Continuous Delivery สำหรับแนวคิด deployment pipeline

การวัดผลและการประเมินผล

กำหนดส่ง: ก่อนเริ่มเรียนบทที่ 10 เพื่อเว้นระยะหนึ่งบท

ข้องานหลักฐานผลลัพธ์CLO
1M7 push แล้วหน้าแอปเปลี่ยนเวอร์ชันลิงก์ pull request ของตนเองที่ merge แล้ว (Lab B, C หรือ D) · ลิงก์หน้า package บน ghcr.io ที่มี tag 0.3.0 · ลิงก์ run ของ workflow CD ที่ job deploy ผ่าน (หรือผลรัน scripts/deploy.sh กรณีใช้วิธีกึ่งอัตโนมัติ) · ผล curl จากเครื่องปลายทางที่แสดง v0.3.08.1CLO2
2ใบงาน flow การส่งมอบ (CLO1 ชิ้นที่ 4)แผนภาพ flow commit → CI → image → deploy ของระบบทีมพร้อมคำอธิบายรายบุคคลไม่เกิน 1 หน้า และจุดที่ deploy อาจล้มอย่างน้อย 2 จุด8.2CLO1
3Learning Log ครั้งที่ 8ตอบ 4 หัวข้อ: สิ่งที่เรียนในบทนี้ · คำสั่งหรือข้อความ error ที่พบและแก้อย่างไร · แหล่งที่ใช้ค้นหาคำตอบ (ระบุลิงก์เอกสารทางการอย่างน้อย 1 แหล่ง) · ขั้นใดของ pipeline ที่ยังไม่เข้าใจ—บันทึกสะท้อนการเรียนรู้ ไม่มีหมวดคะแนน portfolio แยก

เกณฑ์ให้คะแนน

ข้อผ่านยังไม่ผ่าน
1main มี .github/workflows/cd.yml ที่มี job test, build-push และ deploy · ใช้ GITHUB_TOKEN ไม่ใช้ personal access token ใน workflow · package มี tag ตามเวอร์ชัน · หน้าแอปบนเครื่องปลายทางแสดงเวอร์ชันใหม่หลัง push tagไม่มี pull request ของตนเอง หรือ workflow ใช้ token ส่วนตัว หรือเครื่องปลายทางยังแสดงเวอร์ชันเดิม หรือ deploy ด้วยการ build บนเครื่องปลายทางแทนการดึง image
3ตอบครบ 4 หัวข้อ และมีลิงก์เอกสารทางการขาดหัวข้อ หรือไม่มีแหล่งอ้างอิง

ข้อ 2 ใช้ rubric CLO1 3 ระดับ

ระดับลักษณะของคำตอบ
3 ดีแผนภาพตรงกับระบบจริงของทีมทุกขั้น ระบุจุดที่อาจล้มพร้อมอาการและวิธีตรวจพบได้อย่างน้อย 2 จุด และเทียบวิธี deploy ที่ทีมใช้กับทางเลือกอื่นอย่างน้อย 1 ทาง (เช่น SSH จาก runner หรือสคริปต์ที่คนรัน) พร้อมข้อแลกเปลี่ยน
2 ผ่านแผนภาพเรียงลำดับขั้นถูกต้องตามระบบของทีม ระบุสิ่งที่ส่งต่อระหว่างขั้น (commit, image tag) และจุดที่อาจล้มได้อย่างน้อย 2 จุดพร้อมเหตุผล
1 ยังไม่ผ่านวาดตามตัวอย่างทั่วไปโดยไม่ตรงกับระบบของทีม หรือระบุจุดที่อาจล้มไม่ครบ หรือสลับลำดับขั้น

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

การคิดคะแนนฉบับปัจจุบัน

มติผู้สอน 28 ต.ค. 2569: เข้าเรียน 10% · ใบงานบทที่ 1–13 รวม 40% · FP 20% · FW 20% · โครงงาน 10% ส่วนบทที่ 14–15 ฝึกเพื่อรับข้อเสนอแนะ ไม่นับคะแนน

ใบงาน C08-WS01 มีคะแนนดิบเต็ม 20 คะแนน แยกตามผลลัพธ์ด้านล่าง ระบบนำไปรวมในหมวดใบงาน 40% โดยไม่เพิ่มคะแนน milestone, portfolio หรือจุดตรวจเป็นหมวดแยก

ผลลัพธ์หลักฐานตามงานส่งคะแนนดิบเต็ม
LLO8.1งานข้อ 110
LLO8.2งานข้อ 210

ประเมินงานแต่ละข้อจากหลักฐานของข้อนั้นโดยตรง ไม่ตัดสิทธิ์รับคะแนนของอีกข้อเพราะ milestone หรือจุดตรวจอื่นยังไม่ผ่าน งานที่ยังทำไม่ได้ให้บันทึกปัญหาและหลักฐานที่มีตามจริง

ใช้เกณฑ์ความถูกต้อง เหตุผล และหลักฐานที่ระบุในงานแต่ละข้อประกอบการประเมินราย LLO งานเตรียมพื้นและ Learning Log ที่ไม่มีรหัส LLO ใช้ให้ข้อเสนอแนะ ไม่เพิ่มคะแนนดิบให้ใบงาน

บทนำ

บทที่ 5 ทำให้ทุก commit ถูก test อัตโนมัติ บทที่ 6 บรรจุแอปลง image และบทที่ 7 รันแอปคู่กับฐานข้อมูลด้วย Compose บนเครื่องของแต่ละคน ช่องว่างที่เหลือคือการนำแอปเวอร์ชันใหม่ไปถึงเครื่องที่ผู้ใช้เข้าถึงจริง ซึ่งจนถึงบทนี้ยังต้องมีคนเข้าไปที่เครื่องแล้ว build ใหม่ด้วยมือ บทนี้ปิดช่องว่างดังกล่าวด้วย workflow CD ที่ test โค้ด build image ส่งขึ้น GitHub Container registry (ghcr.io) แล้ว deploy ไปยังเครื่องปลายทางของทีม

ความสำคัญ: การส่งมอบที่ทำด้วยมือมักช้า ทำไม่เหมือนกันทุกครั้ง และหาไม่ได้ว่าเวอร์ชันใดกำลังรันอยู่ pipeline ที่ส่งมอบ image ที่ระบุรุ่นได้ไปยังเครื่องปลายทางโดยอัตโนมัติทำให้ทีมส่งมอบได้บ่อยขึ้นด้วยความเสี่ยงที่ต่ำลง และเป็นฐานของการสแกนช่องโหว่ในบทที่ 10 deploy gate ในบทที่ 11 และการ rollback ด้วย tag ก่อนหน้าในบทที่ 12

ความเชื่อมโยงกับบทอื่น: บทนี้ใช้หลัก immutable artifact และการตั้งเวอร์ชันจากบทที่ 4 โครงสร้าง workflow และ job test จากบทที่ 5 Dockerfile แบบ multi-stage จากบทที่ 6 และ compose.yaml จากบทที่ 7 ผลผลิตของบทนี้ถูกใช้ต่อตามตารางต่อไปนี้

สิ่งที่ได้จากบทนี้ใช้ต่อในบท
.github/workflows/cd.yml (test → build-push → deploy)10 (เพิ่มขั้นสแกน Trivy) · 11 (เพิ่ม deploy gate) · 12 (pipeline ครบสาย)
image บน ghcr.io ที่ tag ตามเวอร์ชัน12 (rollback ด้วย tag ก่อนหน้า) · 14 (Kubernetes ดึง image เดียวกัน)
scripts/deploy.sh และเครื่องปลายทางที่รันระบบชุด devops-prod9 (เพิ่ม Nginx หน้าแอป) · 11 (deploy gate) · 12 (rollback)
ประวัติ run ของ workflow CD12 (ตาราง DORA metrics)

ความรู้พื้นฐาน เครื่องมือ และรุ่นซอฟต์แวร์

ความรู้พื้นฐาน: การตั้งเวอร์ชันด้วย npm version และ tag ของ Git จากบทที่ 4 โครงสร้าง workflow, job และ step จากบทที่ 5 การ build image จากบทที่ 6 และ compose.yaml ที่มี healthcheck จากบทที่ 7

เครื่องมือ: บัญชี GitHub ของสมาชิกทุกคนที่เป็นสมาชิก repository ทีม เครื่องปลายทางของทีมหนึ่งเครื่อง (เครื่องในห้องปฏิบัติการที่ทีมใช้ร่วมกัน) ที่ติดตั้ง Docker Engine และ Docker Compose plugin และเข้าถึง github.com และ ghcr.io ได้ ชื่อ owner ของ repository (บัญชีหรือ organization) ต้องเขียนด้วยตัวพิมพ์เล็กใน compose.yaml ตัวอย่างในบทนี้ใช้ owner ชื่อ devops-team-01

รุ่นซอฟต์แวร์ที่ใช้ทดสอบ:

ซอฟต์แวร์รุ่นที่ทดสอบ
Docker Engine และ Docker Compose29.8.2 และ v5.5.1 ทดสอบเมื่อ 19 ต.ค. 2569
actionlint1.7.12 (ตรวจไวยากรณ์ของ cd.yml)
shellcheckตรวจ scripts/deploy.sh
imageimage จำลอง node:22-slim และ postgres:16 เดียวกับบทที่ 6–7
แอปdevops-app 0.2.0 → 0.3.0
⚠️ข้อจำกัดของการทดสอบในบทนี้

สภาพแวดล้อมที่ใช้เขียนบทความเข้าถึง ghcr.io และ GitHub Actions ไม่ได้ ส่วนที่ยังไม่ได้ทดสอบจึงได้แก่ การ login และ push ขึ้น ghcr.io, การรัน cd.yml บน GitHub, การติดตั้งและรัน self-hosted runner และการดึง image จาก ghcr.io ส่วนที่ทดสอบแล้วคือ ไวยากรณ์ของ cd.yml ด้วย actionlint และ JSON schema ของ workflow, scripts/deploy.sh ด้วย shellcheck และการรันส่วน docker compose up ของสคริปต์กับ image ที่ tag ชื่อ ghcr.io ไว้ในเครื่อง รวมถึงการเปลี่ยนจาก 0.2.0 เป็น 0.3.0 ที่หน้าแอปแสดงเวอร์ชันใหม่ ผลลัพธ์ที่มาจากหน้าเว็บของ GitHub ในบทนี้เป็นคำอธิบายตามเอกสาร ไม่ใช่ผลรันจริง

ค่าใช้จ่ายและข้อมูลลับ: GitHub Container registry และ GitHub Actions มีโควตาการใช้งานตามประเภทบัญชีและการตั้งค่าการมองเห็นของ package ซึ่งผู้สอนต้องตรวจเงื่อนไขปัจจุบันก่อนใช้ ข้อมูลลับของบทนี้มีสามประเภท คือ (1) GITHUB_TOKEN ที่ GitHub สร้างให้แต่ละ run และหมดอายุเองเมื่อ run จบ ใช้ใน workflow ได้โดยไม่ต้องเก็บเอง (2) personal access token (PAT) ที่ใช้เฉพาะ Lab A สำหรับ push ด้วยมือ ต้องตั้งวันหมดอายุสั้นและลบทิ้งเมื่อจบ Lab และห้ามใส่ใน workflow หรือไฟล์ใดใน repository (3) รหัสผ่านฐานข้อมูลของเครื่องปลายทาง ซึ่งเก็บในไฟล์ ~/devops-deploy/.env บนเครื่องปลายทางเท่านั้น บทที่ 10 จะย้ายความลับประเภทนี้ไปไว้ใน GitHub Secrets

8.1 Continuous Delivery, Continuous Deployment และ deployment pipeline

Continuous Integration (CI) ในบทที่ 5 ตอบคำถามว่า "โค้ดที่ merge แล้วยังทำงานถูกต้องหรือไม่" Continuous Delivery (CD) ขยายคำถามต่อไปว่า "โค้ดที่ผ่าน CI พร้อมส่งมอบให้ผู้ใช้ได้ทุกเมื่อหรือไม่" หลักสำคัญคือทุกเวอร์ชันที่ผ่านการตรวจจะถูกบรรจุเป็นสิ่งที่ส่งมอบได้ (ในรายวิชานี้คือ image) และขั้นตอนส่งมอบถูกทำให้เป็นอัตโนมัติจนการ deploy กลายเป็นเรื่องปกติที่ทำได้บ่อย ไม่ใช่เหตุการณ์ใหญ่ที่ต้องเตรียมหลายวัน

คำว่า CD ใช้ในสองความหมายที่ต่างกันเล็กน้อย

คำความหมายจุดที่มีคนตัดสินใจ
Continuous Deliveryทุกเวอร์ชันที่ผ่านการตรวจพร้อม deploy ได้เสมอ แต่การปล่อยสู่ผู้ใช้ยังต้องมีคนสั่งคนเลือกว่าจะปล่อยเวอร์ชันใดเมื่อใด
Continuous Deploymentทุกเวอร์ชันที่ผ่านการตรวจถูก deploy สู่ผู้ใช้โดยอัตโนมัติไม่มี ขั้นตอนทั้งหมดอัตโนมัติ

ลำดับขั้นที่เปลี่ยน commit ให้กลายเป็นซอฟต์แวร์ที่ผู้ใช้เข้าถึงได้เรียกว่า deployment pipeline ซึ่ง Humble และ Farley (2010) เสนอเป็นแนวคิดหลักของ Continuous Delivery pipeline ของรายวิชาในบทนี้มีสามขั้น ได้แก่ test, build-push และ deploy และจะเพิ่มขั้นสแกนในบทที่ 10

ระดับที่ 1: workflow CD ของบทนี้เริ่มทำงานเมื่อมีการ push tag ของ Git ที่ขึ้นต้นด้วย v ตามด้วยเลขเวอร์ชัน เช่น v0.3.0 การ push tag คือการตัดสินใจของคนว่า "เวอร์ชันนี้จะปล่อย" หลังจากนั้นทุกขั้นเป็นอัตโนมัติ ระบบของรายวิชาจึงเป็น Continuous Delivery ไม่ใช่ Continuous Deployment

ระดับที่ 2: สิ่งที่ส่งต่อระหว่างขั้นต้องระบุได้ชัดเจน ขั้น test รับ commit ที่ tag ชี้ ขั้น build-push สร้าง image ที่ tag ด้วยเวอร์ชันเดียวกัน และขั้น deploy ดึง image tag นั้นมารัน เครื่องปลายทางไม่ build เอง จึงรันสิ่งเดียวกับที่ผ่าน test ตามหลัก immutable artifact ของบทที่ 4

ระดับที่ 3: DORA ใช้ตัวชี้วัด 5 ตัววัดผลการส่งมอบซอฟต์แวร์ ได้แก่ change lead time, deployment frequency, failed deployment recovery time, change fail rate และ deployment rework rate (DORA: software delivery performance metrics) ประวัติ run ของ workflow CD ในบทนี้คือข้อมูลดิบสำหรับวัด deployment frequency (นับจำนวน run ที่ deploy สำเร็จต่อช่วงเวลา) และ change lead time (เวลาจาก commit จนถึง deploy สำเร็จ) ซึ่งจะใช้ทำตาราง DORA ในบทที่ 12

คำถามตรวจความเข้าใจ 8.1: ทีมหนึ่งตั้ง workflow ให้ deploy ทุกครั้งที่ merge เข้า main โดยไม่ต้อง push tag ระบุว่าทีมนี้ใช้ Continuous Delivery หรือ Continuous Deployment และอธิบายข้อดีข้อเสียเมื่อเทียบกับวิธีของรายวิชา

8.2 registry และ tag ของ image

registry คือบริการเก็บและแจกจ่าย image บทที่ 6 ดึง image จาก Docker Hub ซึ่งเป็น registry ค่าเริ่มต้นของ Docker บทนี้ใช้ GitHub Container registry ซึ่งผูกกับบัญชีและ repository บน GitHub เอกสารทางการกำหนดชื่อ image บน registry นี้ในรูปแบบ ghcr.io/NAMESPACE/IMAGE_NAME:TAG โดย NAMESPACE คือชื่อบัญชีหรือ organization (GitHub Docs: Working with the Container registry)

ส่วนของชื่อตัวอย่างในบทนี้ความหมาย
registryghcr.ioที่อยู่ของ registry
NAMESPACEdevops-team-01บัญชีหรือ organization เจ้าของ repository
IMAGE_NAMEdevops-appชื่อ image ตรงกับชื่อ repository
TAG0.3.0ป้ายระบุรุ่นของ image

image หนึ่งตัวมีได้หลาย tag workflow ของบทนี้ติด tag สองรูปแบบให้ทุก image คือ tag ตามเวอร์ชัน (0.3.0) ซึ่งคนอ่านเข้าใจและใช้ deploy และ tag ตาม commit (รูปแบบ sha- ตามด้วยรหัส commit แบบย่อ) ซึ่งบอกได้ว่า image สร้างจาก commit ใด

ระดับที่ 1: การทดสอบในบทนี้รันระบบชุด devops-prod ด้วย image ghcr.io/devops-team-01/devops-app:0.2.0 แล้วเปลี่ยนเป็น :0.3.0 ผล docker compose ps แสดงชื่อ image พร้อม tag ที่กำลังรัน จึงตอบได้ทันทีว่าเครื่องปลายทางรันเวอร์ชันใด

ระดับที่ 2: tag ของ image เปลี่ยนให้ชี้ image อื่นได้ เช่น tag latest มักถูกย้ายไปชี้ image ล่าสุดทุกครั้งที่ push เครื่องสองเครื่องที่ดึง latest ต่างเวลากันจึงอาจได้ image ต่างกัน ทีมจึงควร deploy ด้วย tag ที่ผูกกับเวอร์ชันและไม่ push ทับ tag เวอร์ชันเดิม เพื่อให้ "0.3.0" หมายถึง image เดียวตลอดไป ซึ่งเป็นเงื่อนไขที่ทำให้ rollback ในบทที่ 12 ทำได้

ระดับที่ 3: image บน registry เป็นข้อมูลที่ต้องจัดการสิทธิ์เช่นเดียวกับโค้ด package ที่เป็น private ต้อง login ก่อนดึง ส่วน package ที่เป็น public ใครก็ดึงได้ หากทีมเผลอบรรจุไฟล์ลับลงใน image (บทที่ 6 Lab C) การตั้ง package เป็น public จะเปิดเผยไฟล์นั้นต่อสาธารณะทันที การตัดสินใจเรื่องการมองเห็นของ package จึงต้องทำโดยรู้ว่าใน image มีอะไรบ้าง

คำถามตรวจความเข้าใจ 8.2: สมาชิกคนหนึ่งเสนอให้ compose.yaml บนเครื่องปลายทางใช้ ghcr.io/devops-team-01/devops-app:latest เพื่อ "ไม่ต้องแก้ไฟล์ทุกครั้งที่ออกเวอร์ชันใหม่" อธิบายปัญหาที่อาจเกิดเมื่อต้อง rollback และเสนอทางที่ดีกว่า

8.3 GITHUB_TOKEN และสิทธิ์ของ workflow

การ push image ขึ้น registry ต้องยืนยันตัวตน เอกสารทางการแนะนำให้ workflow ใน GitHub Actions ใช้ GITHUB_TOKEN ในการ publish package ที่เกี่ยวข้องกับ repository แทนการใช้ personal access token (GitHub Docs: Working with the Container registry) GITHUB_TOKEN ถูกสร้างใหม่สำหรับแต่ละ run และใช้ได้เฉพาะระหว่าง run นั้น จึงไม่มี token ถาวรให้หลุด

ตัวอย่าง workflow ในเอกสาร Publishing Docker images กำหนดสิทธิ์ของ job ด้วย permissions: เป็น contents: read และ packages: write แล้วใช้ action สามตัวต่อกัน ได้แก่ docker/login-action, docker/metadata-action และ docker/build-push-action พร้อม push: true (GitHub Docs: Publishing Docker images) workflow ของบทนี้ใช้โครงสร้างเดียวกัน

YAML
  build-push:
    needs: test
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
actionหน้าที่
docker/login-actionlogin เข้า ghcr.io ด้วย github.actor และ secrets.GITHUB_TOKEN
docker/metadata-actionสร้างรายการ tag และ label ของ image จาก tag ของ Git และ commit
docker/build-push-actionbuild image จาก Dockerfile แล้ว push ทุก tag ขึ้น registry

ระดับที่ 1: permissions: กำหนดว่า GITHUB_TOKEN ของ job นั้นทำอะไรได้ contents: read อ่านโค้ดได้อย่างเดียว ส่วน packages: write เขียน package ได้ ซึ่งจำเป็นสำหรับการ push image job deploy ต้องการเพียงดึง image จึงกำหนด packages: read

ระดับที่ 2: เอกสาร Using secrets in GitHub Actions อธิบายว่าการอ้างถึงความลับใน workflow ใช้รูปแบบ secrets.NAME ใน with: หรือ env: ค่าของความลับถูกปิดบังใน log และไม่ควรส่งความลับผ่าน command line (GitHub Docs: Using secrets in GitHub Actions) workflow ของบทนี้จึงส่ง GITHUB_TOKEN ให้ docker/login-action ผ่าน with: password: แทนการเขียน echo ... | docker login ใน run:

ระดับที่ 3: การกำหนด permissions: ราย job เป็นการใช้หลักให้สิทธิ์เท่าที่จำเป็น (least privilege) ถ้า action ตัวใดตัวหนึ่งใน job test ถูกแก้ไขโดยผู้ไม่หวังดี token ของ job นั้นก็ไม่มีสิทธิ์เขียน package หรือแก้โค้ด เอกสารเดียวกันระบุด้วยว่า workflow ที่ถูก trigger จาก fork ไม่ได้รับความลับ ยกเว้น GITHUB_TOKEN (GitHub Docs: Using secrets in GitHub Actions) ซึ่งเป็นกลไกป้องกันไม่ให้ pull request จากภายนอกขโมยความลับของ repository

คำถามตรวจความเข้าใจ 8.3: สมาชิกคนหนึ่งสร้าง personal access token ที่มีสิทธิ์เขียน package แล้วเก็บเป็น secret ชื่อ CR_PAT เพื่อใช้ใน workflow แทน GITHUB_TOKEN อธิบายความเสี่ยงที่เพิ่มขึ้นเมื่อเทียบกับการใช้ GITHUB_TOKEN

8.4 วิธี deploy ไปยังเครื่องปลายทาง

เมื่อ image อยู่บน registry แล้ว เครื่องปลายทางต้องดึง image นั้นมารันแทนเวอร์ชันเดิม คำถามคือใครเป็นผู้สั่งให้เครื่องปลายทางทำเช่นนั้น ทางเลือกที่พบบ่อยมีสามแบบ

วิธีการทำงานข้อดีข้อจำกัด
self-hosted runner บนเครื่องปลายทางติดตั้งโปรแกรม runner ของ GitHub Actions บนเครื่องปลายทาง แล้วให้ job deploy รันบนเครื่องนั้นโดยตรงไม่ต้องเปิดพอร์ตเข้าเครื่องจากภายนอก เพราะ runner เป็นฝ่ายเชื่อมต่อออกไปหา GitHubต้องดูแลโปรแกรม runner และผู้ที่แก้ workflow ได้จะสั่งคำสั่งใดก็ได้บนเครื่องนั้น
SSH จาก GitHub-hosted runnerjob deploy บน runner ของ GitHub เชื่อมต่อ SSH เข้าเครื่องปลายทางแล้วสั่งคำสั่งไม่ต้องติดตั้งโปรแกรมเพิ่มบนเครื่องปลายทางเครื่องปลายทางต้องรับ SSH จากอินเทอร์เน็ต และต้องเก็บ private key เป็นความลับใน repository
สคริปต์ที่คนรัน (กึ่งอัตโนมัติ)หลัง workflow push image สำเร็จ ผู้ดูแลเข้าเครื่องปลายทางแล้วรันสคริปต์ deployง่ายและใช้ได้แม้เครือข่ายจำกัดยังต้องมีคน และอาจลืมหรือทำไม่เหมือนกัน

แผนการสอนของรายวิชาเลือกแบบแรก คือ self-hosted runner บนเครื่องของทีมในห้องปฏิบัติการ เพราะเครื่องในห้องปฏิบัติการมักอยู่หลัง firewall ที่ไม่รับการเชื่อมต่อจากภายนอก และกำหนดแบบที่สามเป็นทางสำรองเมื่อนโยบายเครือข่ายไม่อนุญาต ทั้งสองแบบใช้สคริปต์ scripts/deploy.sh ชุดเดียวกัน ต่างกันเพียงผู้รันสคริปต์

BASH
docker compose -p devops-prod --env-file "$ENV_FILE" pull app
docker compose -p devops-prod --env-file "$ENV_FILE" up -d --no-build --wait

ระดับที่ 1: สคริปต์รันระบบในชื่อ project devops-prod (ตัวเลือก -p) แยกจาก project devops-app ที่สมาชิกใช้พัฒนาบนเครื่องเดียวกัน จึงไม่ใช้ container และ volume ร่วมกัน และอ่านรหัสผ่านกับพอร์ตจากไฟล์ ~/devops-deploy/.env บนเครื่องปลายทาง (ตัวเลือก --env-file) ค่า tag ของ image ส่งผ่านตัวแปร IMAGE_TAG ที่ compose.yaml ใช้แทนค่าในชื่อ image

ระดับที่ 2: ตัวเลือก --no-build บังคับให้ Compose ใช้ image ที่ดึงมาเท่านั้น ไม่ build จากซอร์สโค้ดบนเครื่องปลายทาง และ --wait ทำให้สคริปต์ล้มเมื่อแอปเวอร์ชันใหม่ไม่ healthy ตามที่ทดสอบในบทที่ 7 ผลการทดสอบการเปลี่ยนจาก 0.2.0 เป็น 0.3.0 แสดงว่า Compose สร้างใหม่ (Recreate) เฉพาะ container ของแอป ส่วนฐานข้อมูลทำงานต่อโดยไม่ถูกแตะ และคำสั่งทั้งหมดใช้เวลาประมาณ 6.5 วินาที

ระดับที่ 3: ระหว่างที่ container เดิมหยุดและ container ใหม่ยังไม่พร้อม แอปตอบผู้ใช้ไม่ได้ช่วงสั้น ๆ วิธีนี้จึงมี downtime ทุกครั้งที่ deploy การ deploy โดยไม่มี downtime ต้องรันเวอร์ชันใหม่ขึ้นมาก่อนแล้วจึงหยุดเวอร์ชันเดิม (rolling update) ซึ่งต้องมีตัวกระจายการเชื่อมต่อและระบบจัดการ container อย่าง Kubernetes ในบทที่ 15 นอกจากนี้ 10306343-chapter-references.mdx ระบุว่าวิธี deploy เข้าเครื่องปลายทางยังไม่มีเอกสารทางการรองรับในรายการแหล่งอ้างอิง ขั้นตอนในบทนี้จึงอ้างเอกสารของ Compose และผลการทดสอบเป็นหลัก

คำถามตรวจความเข้าใจ 8.4: เครือข่ายของมหาวิทยาลัยไม่อนุญาตให้เครื่องในห้องปฏิบัติการรับการเชื่อมต่อ SSH จากอินเทอร์เน็ต ระบุว่าวิธี deploy ใดในตารางยังใช้ได้ และอธิบายเหตุผลโดยอ้างทิศทางของการเชื่อมต่อ

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

8.7 เอกลักษณ์ของ release ตั้งแต่ commit ถึง image

การส่งมอบที่ตรวจสอบได้ต้องเชื่อม source กับผลผลิตและสิ่งที่รันจริง สมมติว่า tag ของ Git v0.3.0 ชี้ commit A, workflow สร้าง image ที่มี tag 0.3.0 และ registry ให้ digest D เมื่อเครื่องปลายทาง deploy ควรบันทึกว่าใช้ D จริง ไม่หยุดเพียงการเห็นข้อความ 0.3.0 บนหน้าเว็บ เพราะข้อความรุ่นเป็นข้อมูลที่โค้ดสามารถใส่ผิดได้

tag ของ image เป็นชื่ออ้างอิงที่อาจถูกชี้ใหม่ได้ตามนโยบายของ registry ส่วน digest อ้างอิงเนื้อหาของ image หรือ manifest โดยตรง การตั้งชื่อรุ่นชัดเจนช่วยคนอ่าน แต่ไม่ทดแทนการระบุเนื้อหาแน่นอน เอกสาร Docker อธิบายความต่างของ tag กับ digest และกรณี multi-platform ที่ index กับ image ของแต่ละ platform มี digest ต่างกัน (Docker Docs: Image digests)

ตัวอย่างที่ไม่ถูกต้องคือ build สองครั้งโดยใช้ชื่อ 0.3.0 เดียวกัน ครั้งแรก deploy ที่ staging และครั้งที่สอง deploy ที่ production แล้วอ้างว่าใช้ release เดียวกัน แม้ source เดิม ผล build อาจเปลี่ยนตาม inputs ภายนอก หาก digest ต่างกันต้องถือว่าเป็นผลผลิตคนละชุดจนพิสูจน์ได้ การแก้คือ build ผลผลิตที่ผ่านการตรวจเพียงชุดเดียวแล้วเลื่อน digest นั้น พร้อมเก็บข้อมูล platform ที่เกี่ยวข้อง

อีกตัวอย่างคือเปลี่ยน latest แล้วสั่ง restart container เดิมโดยคาดว่าจะดึง image ใหม่ การ restart เป็นการเริ่ม container เดิม ไม่ใช่หลักฐานว่าได้ pull และสร้างจาก digest ใหม่ ขั้น deploy ต้องระบุการดึง การสร้างหรือแทนที่ และการตรวจสิ่งที่รันตามวิธีของระบบนั้นอย่างชัดเจน

8.8 สิทธิ์ push กับสิทธิ์ pull ไม่ใช่สิ่งเดียวกัน

registry เกี่ยวข้องกับตัวตน สิทธิ์ และการมองเห็นของ package การที่ workflow push สำเร็จไม่ได้พิสูจน์ว่าเครื่องปลายทาง pull ได้ เพราะอาจใช้คนละ token คนละบัญชี หรือคนละนโยบาย เอกสาร GitHub Container registry แยกการยืนยันตัวตนจาก workflow และการใช้ token ในบริบทอื่น รวมทั้งสิทธิ์ของ package (GitHub Docs: Container registry)

ข้อมูลจำลองชุดแรกคือ push ถูกปฏิเสธด้วยสิทธิ์ไม่พอ ควรตรวจ namespace, package และสิทธิ์ token ของ job ที่ push ชุดที่สองคือ push ผ่านแต่ปลายทางได้ denied ควรตรวจสิทธิ์อ่านของตัวตนปลายทาง ชุดที่สามคือ pull ได้แต่รันไม่ได้ ควรตรวจ image platform และค่าตั้ง runtime การใช้ token สิทธิ์สูงขึ้นทุกครั้งโดยไม่แยกอาการอาจขยายสิทธิ์เกินจำเป็นและยังไม่แก้ปัญหาจริง

ตัวอย่างที่ผิดคือเก็บ token สำหรับเขียน package ไว้บนเครื่องปลายทางที่มีหน้าที่อ่าน image เท่านั้น การออกแบบควรให้สิทธิ์เท่าที่ขั้นนั้นต้องใช้ และกำหนดการหมดอายุหรือการเพิกถอนตามระบบที่เลือก หากใช้ GITHUB_TOKEN ใน workflow ต้องตรวจ permissions และความเชื่อมโยงของ package กับ repository ด้วย ไม่ใช่สมมติว่า token ทุกตัวเข้าถึง package ทุกตัวได้

ก่อนสอนจริงควรทดลอง push กับ pull แยกกันด้วยตัวตนที่จะใช้ในห้อง และใช้ package สำหรับการฝึกโดยเฉพาะ ผลที่อธิบายในบทนี้เป็นพฤติกรรมตามเอกสารและข้อมูลสมมติ ไม่ได้แทนผลทดสอบสิทธิ์ของบัญชีจริงซึ่งอาจถูกจำกัดโดยนโยบาย organization

8.9 deploy สำเร็จต้องตรวจผ่านเส้นทางที่ผู้ใช้เข้าถึง

ขั้น deploy มีอย่างน้อยสามเงื่อนไข: ได้ผลผลิตที่ตั้งใจ เริ่มกระบวนการได้ และบริการทำงานตามสัญญา คำสั่งสร้าง container สำเร็จพิสูจน์เพียงบางส่วนของเงื่อนไขที่สอง ถ้าแอปเริ่มแล้วล้มหลังไม่กี่วินาที workflow ที่จบทันทีอาจแสดงความสำเร็จก่อนความล้มเหลวเกิด

สถานการณ์สมมติคือ image ใหม่เริ่มได้และ /health ใน container ตอบ 200 แต่ผู้ใช้เรียกผ่าน Nginx ได้ 502 เนื่องจาก proxy ชี้พอร์ตเดิม การตรวจตรงแอปอย่างเดียวจึงไม่ครอบคลุมเส้นทางใช้งานจริง ขั้นยืนยันหลัง deploy ควรตรวจปลายทางเดียวกับที่ผู้ใช้ใช้ พร้อมยืนยันตัวระบุ release และเกณฑ์เวลาที่รอได้ การตรวจภายในยังมีประโยชน์เพื่อแยกชั้นของปัญหา แต่ไม่ใช่สิ่งทดแทนการตรวจจากภายนอก

ตัวอย่างที่ผิดคือใช้ curl โดยไม่ตรวจ HTTP error แล้วถือว่าคำสั่งสำเร็จหมายถึง endpoint สำเร็จ ในหลายรูปแบบการเรียก การได้รับ HTTP response เป็นคนละเงื่อนไขกับการได้ status ที่ยอมรับได้ จึงต้องเลือก flags หรืออ่าน status code ให้ตรงสัญญา และทดสอบกับทั้ง 200, 500 และ connection failure ก่อนใช้เป็น gate

อีกตัวอย่างคือกำหนดเวลา sleep 5 วินาทีแล้วตรวจครั้งเดียว เครื่องที่ช้ากว่าจะรายงานล้มทั้งที่กำลังเริ่ม ส่วนเครื่องที่ตอบได้เพียงชั่วคราวอาจผ่าน ควรกำหนดจำนวนครั้ง ระยะห่าง timeout ต่อครั้ง และเกณฑ์ผ่านโดยชัดเจน การ retry ต้องมีขอบเขตเพื่อไม่ให้ workflow รอไม่สิ้นสุด และต้องเก็บหลักฐานของแต่ละครั้งเพื่อแยกอาการเริ่มช้ากับไม่พร้อมจริง

8.10 ลำดับการปล่อยรุ่นและการเปลี่ยนพร้อมกัน

สมมติว่า release A เริ่ม deploy ก่อน B แต่ A ติดรอเครือข่าย เมื่อ B deploy เสร็จแล้ว A กลับมาทำต่อ ปลายทางอาจจบที่รุ่นเก่าหากไม่มีกลไกควบคุมลำดับ การที่แต่ละ run สำเร็จตามลำพังจึงไม่รับประกันสถานะสุดท้ายของระบบ การออกแบบต้องระบุว่าอนุญาต deploy พร้อมกันหรือไม่ และจัดการ run ที่ล้าหลังอย่างไร

ตัวอย่างที่ผิดคือใช้โฟลเดอร์ deploy และไฟล์ค่าตั้งเดียวกันบน self-hosted runner หลาย run โดยไม่มีการควบคุม งานหนึ่งอาจเขียนค่าทับอีกงานระหว่างกำลังรัน ควรให้กระบวนการ deployment ของสภาพแวดล้อมเดียวมีการประสานลำดับที่ชัดเจน พร้อมตรวจ release ปัจจุบันก่อนและหลังดำเนินการ การตั้งชื่อ tag ต่างกันไม่แก้การแย่งไฟล์ในเครื่องปลายทาง

runbook ขั้นต่ำควรบอกจุดเริ่ม ตัวระบุ release เดิม ตัวระบุ release ใหม่ ขั้นยืนยันผล และเงื่อนไขหยุด หากหลัง deploy ไม่ผ่าน ไม่ควรอ้างว่าสำเร็จเพราะ registry มี image แล้ว การส่ง image ขึ้น registry เป็นเพียงการกระจายผลผลิต ส่วนการใช้งานได้จริงเป็นอีกเหตุการณ์ที่ต้องมีหลักฐานแยกกัน

ข้อมูล release record สำหรับรายวิชาจึงควรเชื่อม tag ของ Git, commit, workflow run, image tag, digest, เวลาเริ่มและจบ deploy และผล health ที่ปลายทาง ชุดข้อมูลนี้ใช้ต่อใน rollback และ DORA metrics โดยไม่ต้องเดาจากชื่อไฟล์หรือความจำของสมาชิกทีม

กรณีอธิบายต่อเนื่อง: สอง release แข่งกันเปลี่ยนปลายทาง

สมมติ run A ของรุ่น 0.3.0 เริ่มก่อนและใช้เวลารอ pull นาน run B ของรุ่น 0.3.1 เริ่มภายหลังแต่ deploy เสร็จก่อน หาก A ทำต่อจนเสร็จทีหลัง ระบบอาจจบที่ 0.3.0 แม้หน้า Actions แสดงทั้งสอง run สำเร็จ การเลือกดู run ล่าสุดที่กดเริ่มจึงไม่บอก release ปัจจุบัน ต้องดูเวลาที่เปลี่ยนปลายทางและสิ่งที่รันจริง

วิธีที่ผิดคือแก้ด้วยการเปลี่ยนชื่อ workflow ให้เรียงเลขรุ่น ชื่อที่คนอ่านไม่ได้สร้าง lock ของสภาพแวดล้อม วิธีออกแบบต้องกำหนดว่าการ deploy สู่ปลายทางเดียวกันประสานลำดับอย่างไร ยอมให้ run เก่าทำต่อหรือไม่ และการยกเลิกระหว่างเปลี่ยนระบบทิ้งสภาพใดไว้ การตั้ง concurrency ต้องพิจารณาพฤติกรรมยกเลิกด้วย ไม่คัดลอกค่าโดยไม่อ่านผล

release record ที่ดีบันทึก intent ก่อนเปลี่ยนและผลหลังเปลี่ยน พร้อมตรวจว่าค่าปัจจุบันตรงกับ release ที่ job รับผิดชอบ หากมีการควบคุมภายนอกเปลี่ยนรุ่นพร้อมกัน ต้องรายงานความขัดแย้ง ไม่เขียนว่า “deploy ผ่าน” โดยตรวจเพียงว่าหน้าเว็บตอบได้ เพราะอาจเป็นรุ่นของอีก run

ก่อนใช้กับงานจริงควรทดลองการแข่งขันในสภาพแวดล้อมฝึกด้วยขั้นจำลองเวลารอ และตรวจลำดับจาก timestamps กับ digest การตั้งค่าบนกระดาษเพียงอย่างเดียวยังไม่พิสูจน์ว่าป้องกันการแข่งกันของสคริปต์ภายนอกหรือการ deploy ด้วยมือที่ไม่ผ่าน workflow ได้

ปฏิบัติการที่ 8 ส่ง image ขึ้น ghcr.io และ deploy อัตโนมัติ

ปฏิบัติการนี้ประกอบด้วย Lab A–D Lab A เป็นงานรายบุคคลใน namespace ของบัญชีตนเอง ส่วน Lab B–D เปลี่ยนแปลง repository ทีมโดยแบ่งผู้รับผิดชอบดังนี้ เพื่อให้ทุกคนมี pull request ของตนเองใน milestone M7

pull requestbranchLabผู้รับผิดชอบผู้ตรวจทาน
เพิ่ม cd.yml (test และ build-push) และปรับเวอร์ชันเป็น 0.2.1cd/build-pushBสมาชิกคนที่ 1สมาชิกคนที่ 2
เพิ่ม job deploy, scripts/deploy.sh และปรับ compose.yamlcd/deployCสมาชิกคนที่ 2สมาชิกคนที่ 3
เปลี่ยนข้อความหน้าแรกและปรับเวอร์ชันเป็น 0.3.0feat/team-messageDสมาชิกคนที่ 3สมาชิกคนที่ 1

pull request ทุกรายการต้องผ่าน CI จากบทที่ 5 ก่อน merge การ push tag เวอร์ชันหลัง merge เป็นหน้าที่ของหัวหน้าทีมตามข้อตกลงในบทที่ 4 แต่ละ Lab แบ่งเป็นส่วน ฝึกตามตัวอย่าง และส่วน ตัดสินใจเอง (A.4, B.4, C.4 และ D.3)

ℹ️ผลลัพธ์ที่มาจาก GitHub ยังไม่ได้ทดสอบ

ขั้นตอนที่ทำบนหน้าเว็บของ GitHub, การ push ขึ้น ghcr.io และการรัน workflow บน GitHub ในปฏิบัติการนี้เขียนตามเอกสารทางการและยังไม่ได้ทดสอบจริง ผลลัพธ์ที่แสดงเป็น text block มาจากการรันจริงในเครื่องทดสอบ (Docker Engine 29.8.2, Compose v5.5.1, actionlint 1.7.12) ชื่อ owner devops-team-01 เป็นตัวอย่าง ให้แทนด้วยชื่อบัญชีหรือ organization ของทีมที่เขียนด้วยตัวพิมพ์เล็ก

Lab A: push image ด้วยมือ

สถานการณ์: ก่อนให้ workflow push image แทน ทีมต้องเข้าใจว่าการ push ขึ้น registry ต้องใช้ชื่อ image แบบใดและต้องยืนยันตัวตนอย่างไร

เป้าหมาย: tag image ด้วยชื่อแบบ ghcr.io แล้ว push ขึ้น namespace ของบัญชีตนเอง และจัดการ token ที่ใช้อย่างปลอดภัย (พื้นฐานของ LLO 8.1)

ฝึกตามตัวอย่าง

A.1 สร้าง personal access token (สมาชิกทุกคน)

บนหน้าเว็บ GitHub เข้า Settings ของบัญชีตนเอง → Developer settings → Personal access tokens แล้วสร้าง token แบบ classic ที่มีสิทธิ์ write:packages ตั้งวันหมดอายุสั้นที่สุดที่เลือกได้ แล้วคัดลอก token ไว้ (หน้าเว็บแสดง token เพียงครั้งเดียว) ขั้นตอนนี้ตามเอกสาร Working with the Container registry และยังไม่ได้ทดสอบ

A.2 login และ push (สมาชิกทุกคน)

ใช้ read -s รับ token จากแป้นพิมพ์โดยไม่แสดงบนจอและไม่บันทึกลงประวัติคำสั่ง แล้วส่งให้ docker login ผ่าน --password-stdin แทนการพิมพ์ token ในคำสั่ง ให้แทน <username> ด้วยชื่อบัญชี GitHub ของตนแบบตัวพิมพ์เล็ก

BASH
read -s CR_PAT
echo "$CR_PAT" | docker login ghcr.io -u <username> --password-stdin
unset CR_PAT
docker tag devops-app:0.2.0 ghcr.io/<username>/devops-app:0.2.0
docker push ghcr.io/<username>/devops-app:0.2.0

ผลที่คาดหวังตามเอกสารคือบรรทัด Login Succeeded และความคืบหน้าของการ push แต่ละ layer ตามด้วยบรรทัดที่มี digest ของ image (ยังไม่ได้ทดสอบ) การ tag ด้วยชื่อใหม่ไม่ได้สร้างสำเนา image แต่เป็นการเพิ่มชื่อให้ image เดิม ตรวจได้ด้วย docker images ซึ่งจะเห็น IMAGE ID เดียวกันสองชื่อ

A.3 ดู package บน GitHub

เปิดหน้าโปรไฟล์ของตนบน GitHub แล้วเลือกแท็บ Packages จะพบ package ชื่อ devops-app ที่มี tag 0.2.0

ตัดสินใจเอง A.4: ค้นจากเอกสาร Working with the Container registry และหน้า package ของตนแล้วตอบสามข้อลงใน Learning Log (1) package ที่เพิ่ง push มีการมองเห็นเป็น public หรือ private และเครื่องอื่นต้องทำอะไรก่อนดึงได้ (2) ควรเลือกการมองเห็นแบบใดสำหรับ image ที่อาจมีข้อมูลภายใน และเพราะเหตุใด (3) เมื่อจบ Lab ต้องทำอะไรกับ token และ package นี้ จากนั้นลงมือทำตามข้อ (3)

ผลลัพธ์ที่คาดหวัง A.4: คำตอบสามข้อพร้อมลิงก์เอกสาร token ถูกลบ (หรือหมดอายุ) และ docker logout ghcr.io แล้ว package ในบัญชีส่วนตัวถูกลบ

จุดตรวจ A: สมาชิกทุกคนอธิบายส่วนประกอบของชื่อ ghcr.io/<username>/devops-app:0.2.0 ได้ครบสี่ส่วน และไม่มี token อยู่ในประวัติคำสั่ง (history | grep ghp_ ไม่พบ)

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
denied: permission_denied หรือ unauthorized ขณะ pushtoken ไม่มีสิทธิ์ write:packages หรือ login ด้วยชื่อผู้ใช้ผิดสร้าง token ใหม่ที่มีสิทธิ์ถูกต้อง แล้ว login ใหม่
invalid reference format: repository name must be lowercaseชื่อบัญชีหรือ organization มีตัวพิมพ์ใหญ่ใช้ชื่อตัวพิมพ์เล็กทั้งหมดในชื่อ image
An image does not exist locally with the tagยังไม่ได้ docker tag หรือพิมพ์ชื่อไม่ตรงตรวจด้วย docker images แล้ว tag ใหม่

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

Lab B: workflow build และ push

สถานการณ์: การ push ด้วยมือผูกกับ token ส่วนตัวและขึ้นกับว่าใครจำขั้นตอนได้ ทีมต้องการให้ทุกครั้งที่ออกเวอร์ชันใหม่ image ถูก build และ push ขึ้น namespace ของทีมโดยอัตโนมัติ

เป้าหมาย: เพิ่ม workflow CD ที่ test แล้ว build และ push image ขึ้น ghcr.io ด้วย GITHUB_TOKEN เมื่อมีการ push tag เวอร์ชัน (LLO 8.1)

ฝึกตามตัวอย่าง

B.1 เขียน cd.yml และปรับเวอร์ชัน (สมาชิกคนที่ 1)

BASH
cd ~/devops-app
git switch main
git pull
git switch -c cd/build-push
npm version patch --no-git-tag-version
nano .github/workflows/cd.yml
YAML
name: CD
 
on:
  push:
    tags: ['v*.*.*']
  workflow_dispatch:
 
env:
  REGISTRY: ghcr.io
  IMAGE_NAME: ${{ github.repository }}
 
jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Setup Node.js
        uses: actions/setup-node@v7
        with:
          node-version: '22.x'
      - name: Install dependencies
        run: npm ci
      - name: Run tests
        run: npm test
 
  build-push:
    needs: test
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
    outputs:
      version: ${{ steps.meta.outputs.version }}
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Log in to the Container registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Extract metadata (tags, labels)
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
          tags: |
            type=semver,pattern={{version}}
            type=sha
      - name: Build and push image
        uses: docker/build-push-action@v6
        with:
          context: .
          push: true
          tags: ${{ steps.meta.outputs.tags }}
          labels: ${{ steps.meta.outputs.labels }}
ส่วนความหมาย
on.push.tags: ['v*.*.*']เริ่มเมื่อ push tag รูปแบบ v0.2.1 ไม่เริ่มเมื่อ push branch
IMAGE_NAME: ${{ github.repository }}ชื่อ image เป็น owner/ชื่อ-repository
needs: testbuild เฉพาะเมื่อ test ผ่าน
outputs.versionส่งเวอร์ชันที่ metadata-action คำนวณได้ให้ job ถัดไปใน Lab C
type=semver,pattern={{version}}tag v0.2.1 ของ Git กลายเป็น tag 0.2.1 ของ image
type=shaเพิ่ม tag รูปแบบ sha- ตามด้วยรหัส commit แบบย่อ

B.2 ตรวจไวยากรณ์ก่อน commit

BASH
actionlint .github/workflows/cd.yml && echo "actionlint ok"
git add package.json package-lock.json .github/workflows/cd.yml
git commit -m "เพิ่ม workflow CD สำหรับ build และ push image"
git push -u origin cd/build-push
TEXT
actionlint ok

ถ้าเครื่องไม่มี actionlint ให้ข้ามคำสั่งแรก CI จากบทที่ 5 ไม่ได้ตรวจไวยากรณ์ของ cd.yml ความผิดพลาดของไฟล์นี้จะพบเมื่อ push tag ครั้งแรก การตรวจในเครื่องก่อนจึงช่วยลดรอบการแก้

B.3 ออกเวอร์ชัน 0.2.1 (หัวหน้าทีม หลัง pull request merge แล้ว)

BASH
git switch main
git pull
git tag v0.2.1
git push origin v0.2.1

เปิดแท็บ Actions ของ repository จะพบ run ของ workflow CD ที่มี job test ตามด้วย build-push เมื่อผ่านทั้งสอง job หน้า repository จะมีส่วน Packages แสดง devops-app ที่มี tag 0.2.1 และ tag รูปแบบ sha- (ยังไม่ได้ทดสอบ)

ตัดสินใจเอง B.4: เปิดหน้า package ของทีมแล้วบันทึกรายการ tag ทั้งหมดที่ปรากฏ ค้นจากเอกสารของ docker/metadata-action ว่ามี tag ใดที่ workflow สร้างเพิ่มนอกเหนือจากที่กำหนดไว้ใน tags: หรือไม่ และเพราะเหตุใด จากนั้นตัดสินใจว่าทีมจะใช้ tag ใดในการ deploy ใน Lab C และเขียนเหตุผลลงในคำอธิบาย pull request ของ Lab C

ผลลัพธ์ที่คาดหวัง B.4: รายการ tag พร้อมคำอธิบายที่อ้างเอกสาร และการตัดสินใจใช้ tag เวอร์ชันพร้อมเหตุผลที่โยงกับ rollback

จุดตรวจ B: run ของ tag v0.2.1 ผ่านทั้ง job test และ build-push และหน้า package ของทีมมี tag 0.2.1

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
push tag แล้วไม่มี runpush เฉพาะ branch ไม่ได้ push tag หรือรูปแบบ tag ไม่ตรง v*.*.*git push origin v0.2.1 และตรวจชื่อ tag
build-push ล้มที่ขั้น push ด้วยข้อความ deniedขาด packages: write หรือการตั้งค่าของ organization ไม่อนุญาตให้ workflow เขียน packageตรวจ permissions: และแจ้งเจ้าของ organization
test ใน CD ล้มแต่ CI บน main ผ่านtag ชี้ commit อื่นที่ไม่ใช่ main ล่าสุดตรวจด้วย git log --oneline --decorate -3 ว่า tag อยู่ที่ commit ใด
workflow_dispatch แล้วขึ้นว่าไม่พบ workflowเลือก ref ที่ยังไม่มี cd.yml เช่น tag v0.2.0เลือก main หรือ tag ที่สร้างหลัง merge cd.yml

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

Lab C: deploy ไปเครื่องปลายทาง

สถานการณ์: image อยู่บน ghcr.io แล้ว แต่เครื่องปลายทางของทีมยังไม่รู้ว่ามีเวอร์ชันใหม่ ทีมต้องการให้ workflow สั่งเครื่องปลายทางดึง image แล้วรันแทนเวอร์ชันเดิมโดยไม่ต้องมีคนเข้าเครื่อง

เป้าหมาย: เตรียมเครื่องปลายทาง เพิ่มสคริปต์ deploy และ job deploy ที่รันบน self-hosted runner แล้วทดสอบ deploy ครบสาย (LLO 8.1)

ฝึกตามตัวอย่าง

C.1 เตรียมเครื่องปลายทาง (สมาชิกคนที่ 2 บนเครื่องปลายทาง โดยมีสมาชิกอื่นดู)

สร้างไฟล์ค่าตั้งของเครื่องปลายทางไว้นอก repository ไฟล์นี้มีรหัสผ่านของฐานข้อมูลชุด devops-prod ซึ่งต้องต่างจากรหัสผ่านที่ใช้พัฒนาในบทที่ 7 และพอร์ตที่ใช้เข้าถึงแอป (8081 เพื่อไม่ชนกับ 8080 ที่ใช้พัฒนา)

BASH
mkdir -p ~/devops-deploy
nano ~/devops-deploy/.env
chmod 600 ~/devops-deploy/.env
TEXT
POSTGRES_PASSWORD=<รหัสผ่านของเครื่องปลายทาง>
APP_PORT=8081

จากนั้นติดตั้ง self-hosted runner ตามหน้าเว็บของ repository ที่ Settings → Actions → Runners → New self-hosted runner เลือก Linux แล้วรันคำสั่งดาวน์โหลด ตั้งค่า (./config.sh) และเริ่มทำงาน (./run.sh) ตามลำดับที่หน้าเว็บแสดง ผู้ใช้ที่รัน runner ต้องสั่ง docker ได้โดยไม่ใช้ sudo และต้องเป็นผู้ใช้เดียวกับเจ้าของ ~/devops-deploy/.env เมื่อสำเร็จ หน้า Runners จะแสดง runner สถานะ Idle (ขั้นตอนนี้ยังไม่ได้ทดสอบ)

⚠️ถ้าห้องปฏิบัติการไม่อนุญาต self-hosted runner

ให้ข้ามการติดตั้ง runner และไม่ต้องเพิ่ม job deploy ใน C.3 ทีมยังเพิ่ม scripts/deploy.sh และปรับ compose.yaml ตาม C.2 แล้ว deploy แบบกึ่งอัตโนมัติ คือรอให้ job build-push ผ่าน แล้วสมาชิกที่อยู่ที่เครื่องปลายทาง login ghcr.io (ถ้า package เป็น private) และรัน bash scripts/deploy.sh 0.3.0 ใน clone ของ repository บนเครื่องนั้น วิธีนี้เป็นทางเลือกสำรองที่ยอมรับได้ ผู้สอนจะประกาศว่าทีมใดใช้วิธีนี้ก่อนเริ่ม Lab

C.2 สคริปต์ deploy และ compose.yaml (สมาชิกคนที่ 2)

BASH
git switch main
git pull
git switch -c cd/deploy
nano scripts/deploy.sh
BASH
#!/usr/bin/env bash
# deploy.sh TAG : ดึง image ตาม TAG แล้วรันระบบบนเครื่องปลายทาง
set -euo pipefail
 
TAG="${1:?usage: scripts/deploy.sh TAG (เช่น 0.3.0)}"
ENV_FILE="${DEPLOY_ENV_FILE:-$HOME/devops-deploy/.env}"
 
export IMAGE_TAG="$TAG"
docker compose -p devops-prod --env-file "$ENV_FILE" pull app
docker compose -p devops-prod --env-file "$ENV_FILE" up -d --no-build --wait
docker compose -p devops-prod --env-file "$ENV_FILE" ps

ใน compose.yaml เปลี่ยนสองบรรทัดของบริการ app ให้ชื่อ image ตรงกับที่ workflow push และให้พอร์ตกำหนดได้จากไฟล์ค่าตั้ง (dev คือ tag ที่ใช้เมื่อ build บนเครื่องพัฒนา และ 8080 คือพอร์ตเดิม)

YAML
    image: ghcr.io/devops-team-01/devops-app:${IMAGE_TAG:-dev}
    ports:
      - "${APP_PORT:-8080}:3000"

ตรวจสคริปต์และไฟล์ก่อน commit

BASH
chmod +x scripts/deploy.sh
shellcheck scripts/deploy.sh && echo "shellcheck ok"
docker compose config -q && echo "compose ok"
bash scripts/deploy.sh
TEXT
shellcheck ok
compose ok
scripts/deploy.sh: line 5: 1: usage: scripts/deploy.sh TAG (เช่น 0.3.0)

การเรียกสคริปต์โดยไม่ระบุ tag ต้องล้มพร้อมข้อความบอกวิธีใช้ ซึ่งเป็นผลของ ${1:?...} และถ้าไม่มีไฟล์ค่าตั้งบนเครื่องจะล้มด้วย couldn't find env file ทั้งสองกรณีทดสอบแล้ว

C.3 เพิ่ม job deploy ต่อท้าย cd.yml

YAML
  deploy:
    needs: build-push
    runs-on: self-hosted
    permissions:
      contents: read
      packages: read
    steps:
      - name: Checkout
        uses: actions/checkout@v6
      - name: Log in to the Container registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
      - name: Deploy with Compose
        env:
          TAG: ${{ needs.build-push.outputs.version }}
        run: bash scripts/deploy.sh "$TAG"

runs-on: self-hosted ส่ง job นี้ไปรันบน runner ของทีมแทน runner ของ GitHub ค่า tag ส่งผ่าน env: แล้วอ้างเป็น "$TAG" ในสคริปต์ แทนการแทรก ${{ ... }} ลงใน run: โดยตรง ตรวจด้วย actionlint แล้ว commit, push และเปิด pull request เมื่อ merge แล้วให้หัวหน้าทีมสั่ง workflow_dispatch บน main จากแท็บ Actions เพื่อทดสอบ deploy โดยไม่ต้องออกเวอร์ชันใหม่ (ค่า tag ที่ใช้จะเป็นรูปแบบ sha-)

ตัดสินใจเอง C.4: ทดสอบ deploy ด้วยสคริปต์บนเครื่องปลายทางสองรอบ แล้วบันทึกผลในคำอธิบาย pull request (1) รันเวอร์ชันปัจจุบัน แล้วตรวจด้วย curl ที่พอร์ต 8081 (2) ทดลองเรียกสคริปต์ด้วย tag ที่ไม่มีอยู่จริง เช่น 9.9.9 บันทึกข้อความ error และ exit code และตอบว่าระบบชุด devops-prod ที่รันอยู่ได้รับผลกระทบหรือไม่ จากนั้นตอบคำถามว่า ถ้า job deploy ล้มกลางทาง ทีมจะรู้ได้อย่างไรว่าเครื่องปลายทางรันเวอร์ชันใดอยู่

ผลลัพธ์ที่คาดหวัง C.4: รอบ (1) ได้หน้าแอปที่เวอร์ชันตรงกับ tag รอบ (2) สคริปต์ล้มที่ขั้น pull ด้วย exit code ไม่ใช่ 0 และระบบเดิมยังทำงาน และตอบได้ว่าดูจากคอลัมน์ IMAGE ของ docker compose -p devops-prod ps

จุดตรวจ C: workflow_dispatch บน main ผ่านทั้งสาม job (หรือผลรันสคริปต์กรณีกึ่งอัตโนมัติ) และ curl http://localhost:8081/health บนเครื่องปลายทางได้ HTTP 200

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
job deploy ค้างที่ Waiting for a runner to pick up this jobrunner ไม่ทำงานหรือ offlineเริ่ม runner บนเครื่องปลายทาง และตรวจสถานะที่หน้า Runners
permission denied while trying to connect to the Docker daemon socket ใน job deployผู้ใช้ที่รัน runner ไม่อยู่ในกลุ่ม dockerแจ้งผู้ดูแลเครื่อง แล้วเริ่ม runner ใหม่
couldn't find env fileไม่มี ~/devops-deploy/.env ใน home ของผู้ใช้ที่รัน runnerสร้างไฟล์ตาม C.1 ด้วยผู้ใช้เดียวกับ runner
failed to resolve reference "ghcr.io/..."ชื่อ image ใน compose.yaml ไม่ตรงกับที่ push หรือ package เป็น private และยังไม่ได้ loginเทียบชื่อกับหน้า package และตรวจขั้น login
Bind for 0.0.0.0:8081 failed: port is already allocatedมีบริการอื่นใช้พอร์ต 8081เปลี่ยน APP_PORT ใน ~/devops-deploy/.env

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

Lab D: เปลี่ยนข้อความแอปแล้วดูเวอร์ชันใหม่

สถานการณ์: pipeline ครบสายแล้ว ทีมต้องการพิสูจน์ว่าการเปลี่ยนแปลงหนึ่งครั้งเดินทางจาก commit ไปถึงหน้าแอปบนเครื่องปลายทางได้โดยไม่มีใครเข้าเครื่อง

เป้าหมาย: แก้ข้อความหน้าแรก ออกเวอร์ชัน 0.3.0 แล้วยืนยันว่าเครื่องปลายทางแสดงเวอร์ชันใหม่ (หลักฐาน M7 และ LLO 8.1)

ฝึกตามตัวอย่าง

D.1 แก้ข้อความและปรับเวอร์ชัน (สมาชิกคนที่ 3)

BASH
git switch main
git pull
git switch -c feat/team-message
sed -i 's/DevOps App v/DevOps App (team 01) v/' src/app.js
npm version minor --no-git-tag-version
npm test
git add src/app.js package.json package-lock.json
git commit -m "เพิ่มชื่อทีมในหน้าแรกและปรับเวอร์ชันเป็น 0.3.0"
git push -u origin feat/team-message

ให้แทน team 01 ด้วยชื่อทีมของตน test เดิมยังผ่านเพราะไม่ได้ตรวจข้อความหน้าแรก

D.2 ออกเวอร์ชันและตรวจผล (หัวหน้าทีม หลัง merge และสมาชิกทุกคน)

BASH
git switch main
git pull
git tag v0.3.0
git push origin v0.3.0

เมื่อ run ของ tag v0.3.0 ผ่านทั้งสาม job ให้ตรวจบนเครื่องปลายทาง

BASH
curl -s http://localhost:8081/
curl -s http://localhost:8081/health
docker compose -p devops-prod ps --format '{{.Name}} {{.Image}} {{.Status}}'
TEXT
DevOps App (team 01) v0.3.0
{"status":"ok","version":"0.3.0"}
devops-prod-app-1 ghcr.io/devops-team-01/devops-app:0.3.0 Up 5 seconds (healthy)
devops-prod-db-1 postgres:16 Up 26 seconds (healthy)

ผลข้างต้นได้จากการทดสอบส่วน up ของสคริปต์ในเครื่องทดสอบ บริการ db ทำงานต่อเนื่อง (Up 26 seconds) ขณะที่ app ถูกสร้างใหม่ (Up 5 seconds) แสดงว่า deploy เปลี่ยนเฉพาะแอป

ตัดสินใจเอง D.3: จากแท็บ Actions ให้บันทึกเวลาของสามเหตุการณ์ ได้แก่ เวลาที่ commit ของ D.1 ถูกสร้าง เวลาที่ push tag v0.3.0 และเวลาที่ job deploy จบ แล้วคำนวณเวลาตั้งแต่ commit จนถึง deploy สำเร็จ ค้นจากเอกสาร DORA ว่าตัวเลขนี้ตรงกับตัวชี้วัดใด และอภิปรายว่าช่วงใดของเวลาทั้งหมดเป็นเวลาที่รอคน และช่วงใดเป็นเวลาที่เครื่องทำงาน

ผลลัพธ์ที่คาดหวัง D.3: ตารางเวลาสามเหตุการณ์ ระยะเวลารวม การระบุว่าเป็น change lead time และการแยกเวลารอคน (review, merge, push tag) กับเวลาของ pipeline

จุดตรวจ D: หน้าแอปบนเครื่องปลายทางแสดงข้อความของทีมพร้อม v0.3.0 โดยไม่มีสมาชิกคนใดสั่งคำสั่งบนเครื่องปลายทาง (ยกเว้นกรณีกึ่งอัตโนมัติที่รันสคริปต์หนึ่งคำสั่ง)

ปัญหาที่พบบ่อย

อาการสาเหตุวิธีแก้
หน้าแอปยังแสดง 0.2.x หลัง run ผ่านเปิดพอร์ต 8080 ของระบบพัฒนาแทน 8081 ของ devops-prodใช้พอร์ตตาม APP_PORT
หน้าแอปแสดงข้อความใหม่แต่เวอร์ชันเดิมแก้ข้อความแต่ลืม npm versionปรับเวอร์ชันใน pull request ใหม่แล้วออก tag ถัดไป ห้าม push ทับ tag เดิม
git push origin v0.3.0 ขึ้น already existsมีผู้สร้าง tag นี้ไปแล้วตรวจด้วย git ls-remote --tags origin และตกลงกันว่าใครเป็นผู้ออกเวอร์ชัน

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

เก็บกวาดเมื่อจบปฏิบัติการ

ระบบชุด devops-prod บนเครื่องปลายทางใช้ต่อในบทที่ 9–12 จึงต้องปล่อยให้ทำงานต่อ และห้ามใช้ docker compose -p devops-prod down -v เพราะจะลบฐานข้อมูลของเครื่องปลายทาง บนเครื่องของตนเองให้ตรวจว่าลบ personal access token จาก Lab A แล้ว สั่ง docker logout ghcr.io และลบ image ที่ไม่ใช้ด้วย docker image prune -f ถ้าทีมไม่ได้ติดตั้ง runner เป็นบริการ (service) ต้องเริ่ม ./run.sh ใหม่ทุกครั้งก่อนออกเวอร์ชันในบทต่อไป

สรุป

Continuous Delivery ทำให้ทุกเวอร์ชันที่ผ่านการตรวจพร้อมส่งมอบได้เสมอ โดยการตัดสินใจปล่อยยังเป็นของคน ส่วน Continuous Deployment ปล่อยทุกเวอร์ชันโดยอัตโนมัติ pipeline ของรายวิชาเริ่มเมื่อหัวหน้าทีม push tag เวอร์ชัน แล้วทำงานต่อเองสามขั้น คือ test, build-push และ deploy โดยส่งต่อ image ที่ tag ด้วยเวอร์ชันเดียวกันตลอดสาย เครื่องปลายทางจึงรันสิ่งเดียวกับที่ผ่าน test

image บน GitHub Container registry มีชื่อรูปแบบ ghcr.io/NAMESPACE/IMAGE_NAME:TAG การ deploy ควรใช้ tag ที่ผูกกับเวอร์ชันและไม่ถูกย้าย แทน latest workflow ใช้ GITHUB_TOKEN ที่หมดอายุเมื่อ run จบในการ push image และกำหนด permissions: ราย job ตามหลักให้สิทธิ์เท่าที่จำเป็น personal access token ใช้เฉพาะการทดลองด้วยมือและต้องลบเมื่อเสร็จ

การ deploy ไปเครื่องปลายทางทำได้ด้วย self-hosted runner บนเครื่องนั้น, SSH จาก runner ของ GitHub หรือสคริปต์ที่คนรัน รายวิชาเลือก self-hosted runner เพราะเชื่อมต่อขาออกจากเครื่องในห้องปฏิบัติการได้ และมีสคริปต์กึ่งอัตโนมัติเป็นทางสำรอง ทั้งสองทางใช้ docker compose pull และ up -d --no-build --wait ซึ่งสร้างใหม่เฉพาะ container ของแอปและล้มเมื่อแอปไม่ healthy แต่ยังมี downtime ช่วงสั้นทุกครั้งที่ deploy

สิ่งที่ส่งต่อไปยังโครงงานและ Capstone

สิ่งที่ได้นำไปใช้ที่
cd.yml และ M7บทที่ 10 เพิ่มขั้นสแกน · บทที่ 11 เพิ่ม deploy gate · บทที่ 12 pipeline ครบสาย · บทที่ 13 Capstone ที่ต้อง push → pipeline → deploy
image 0.2.1 และ 0.3.0 บน ghcr.ioบทที่ 12 rollback ไป tag ก่อนหน้า · บทที่ 14 Kubernetes ใช้ image เดียวกัน
scripts/deploy.sh และระบบชุด devops-prodบทที่ 9 เพิ่ม Nginx หน้าแอป · บทที่ 12 RUNBOOK สำหรับ rollback
ใบงาน CLO1 ชิ้นที่ 4ใช้เตรียมสอบปากเปล่าบทที่ 12 ส่วน CI/CD pipeline flow
เวลาจาก D.3บทที่ 12 ตาราง DORA metrics
Learning Log ครั้งที่ 8portfolio ที่ส่งในบทที่ 13

สรุปคำสั่งในบทนี้

คำสั่งหรือส่วนของ workflowหน้าที่
docker login ghcr.io -u ชื่อ --password-stdinlogin เข้า registry โดยรับ token ทาง stdin
docker tag ต้นทาง ghcr.io/owner/ชื่อ:tagเพิ่มชื่อแบบ registry ให้ image
docker push ghcr.io/owner/ชื่อ:tagส่ง image ขึ้น registry
docker logout ghcr.ioลบข้อมูล login ออกจากเครื่อง
git tag vX.Y.Z และ git push origin vX.Y.Zออกเวอร์ชันและเริ่ม workflow CD
on.push.tags, needs, permissions, outputsเงื่อนไขเริ่ม ลำดับ job สิทธิ์ของ token และค่าที่ส่งต่อระหว่าง job
docker/login-action, docker/metadata-action, docker/build-push-actionlogin, สร้าง tag และ build พร้อม push ใน workflow
runs-on: self-hostedรัน job บน runner ของทีม
docker compose -p ชื่อ --env-file ไฟล์ ...กำหนดชื่อ project และไฟล์ค่าตั้งของเครื่องปลายทาง
docker compose pull บริการดึง image ของบริการตามชื่อใน compose.yaml
docker compose up -d --no-build --waitรันด้วย image ที่ดึงมาโดยไม่ build และรอจน healthy
actionlint, shellcheckตรวจไวยากรณ์ของ workflow และ shell script ก่อน commit

แบบฝึกหัดท้ายบท

ตอนที่ 1 ฝึกทบทวน

ชุด FP10306343-P08 จำนวน 12 ข้อ ข้อละ 1 คะแนน รวม 12 คะแนน ครอบคลุม Continuous Delivery ชื่อและ tag ของ image บน registry สิทธิ์ของ GITHUB_TOKEN การเริ่ม workflow ด้วย tag การ deploy ด้วยการดึง image การตรวจผลหลัง deploy และการ deploy ที่แข่งกัน

เข้าสู่ระบบห้องเรียนด้วยบัญชีที่ลงทะเบียนในรายวิชา 10306343 แล้วตอบคำถามให้ครบ ระบบตรวจคำตอบและแสดงคำอธิบายเพื่อทบทวน เมื่อทำครบและส่งผล ระบบจะบันทึกคะแนนไว้กับบัญชีของผู้เรียน สามารถทำซ้ำเพื่อทบทวนได้ ส่วนคะแนนที่นำไปรวมในรายวิชาเป็นไปตามเกณฑ์และกำหนดส่งในสมุดคะแนน

แบบฝึกหัดเพิ่มเติมสำหรับอภิปรายและเขียนตอบ

แบบฝึกหัดแบ่งเป็นสามกลุ่ม ได้แก่ ความเข้าใจ การประยุกต์ และการวิเคราะห์ปัญหา แต่ละข้อระบุผลลัพธ์การเรียนรู้ที่ฝึก

ก. ความเข้าใจ

  • ก1 (LLO 8.2) อธิบายความต่างระหว่าง Continuous Delivery กับ Continuous Deployment และระบุจุดในระบบของรายวิชาที่ทำให้เป็นแบบแรก
  • ก2 (LLO 8.1) แยกส่วนประกอบของชื่อ ghcr.io/devops-team-01/devops-app:0.3.0 และอธิบายว่าเหตุใดจึงไม่ควร deploy ด้วย tag latest
  • ก3 (LLO 8.1) อธิบายว่าเหตุใด job build-push จึงกำหนด packages: write แต่ job deploy กำหนดเพียง packages: read

ข. การประยุกต์

  • ข1 (LLO 8.2) เรียงลำดับเหตุการณ์ต่อไปนี้ตั้งแต่ commit จนถึงหน้าแอปเปลี่ยน: (ก) docker compose pull app บนเครื่องปลายทาง (ข) หัวหน้าทีม push tag v0.3.0 (ค) job build-push push image 0.3.0 (ง) pull request ผ่าน CI และ merge (จ) สมาชิก commit การแก้ข้อความบน branch (ฉ) up -d --wait สร้าง container ใหม่และ healthy (ช) job test ของ workflow CD ผ่าน
  • ข2 (LLO 8.1) เขียนส่วน on: ของ workflow ที่เริ่มเมื่อ push tag รูปแบบ v1.2.3 และเมื่อสั่งด้วยมือจากแท็บ Actions และอธิบายว่าเหตุใดการ push branch main จึงไม่ทำให้ workflow นี้เริ่ม
  • ข3 (LLO 8.1) เขียนคำสั่งที่ใช้บนเครื่องปลายทางเพื่อดูว่าระบบชุด devops-prod รัน image tag ใดอยู่ และคำสั่ง deploy เวอร์ชัน 0.2.1 ด้วยสคริปต์ของทีม

ค. การวิเคราะห์ปัญหา

  • ค1 (LLO 8.2) run ของ tag v0.3.1 ผ่าน job test และ build-push แต่ job deploy แสดงสถานะ Queued อยู่ 30 นาที วิเคราะห์สาเหตุที่เป็นไปได้อย่างน้อย 2 ข้อ และวิธีตรวจแต่ละข้อ
  • ค2 (LLO 8.1) สมาชิกคนหนึ่งแก้ scripts/deploy.sh ให้ใช้ docker compose up -d --build แทน pull และ --no-build เพื่อ "ไม่ต้องรอ registry" วิเคราะห์ว่าการแก้นี้ขัดกับหลักใดในบทที่ 4 และบทนี้ และผลเสียที่อาจเกิด
  • ค3 (LLO 8.2) หลัง deploy เวอร์ชัน 0.3.1 ผู้ใช้แจ้งว่าแอปตอบไม่ได้ประมาณ 5 วินาที ทั้งที่ run ผ่านทุก job วิเคราะห์สาเหตุจากวิธี deploy ที่ใช้ และระบุว่าบทใดของรายวิชาจะแก้ปัญหานี้ได้

เฉลยจะเปิดในชั้นเรียนหลังทุกคนทำเสร็จ

การศึกษาค้นคว้าเพิ่มเติม

  1. อ่าน GitHub Docs: Working with the Container registry เพื่อตอบ Lab A.4 เรื่องการมองเห็นของ package และการยืนยันตัวตน
  2. อ่าน GitHub Docs: Publishing Docker images แล้วเทียบ workflow ตัวอย่างกับ cd.yml ของทีม บันทึกส่วนที่ต่างกันและเหตุผลใน Learning Log
  3. อ่าน GitHub Docs: Using secrets in GitHub Actions เพื่อเตรียมการย้ายรหัสผ่านไป GitHub Secrets ในบทที่ 10
  4. อ่าน DORA: software delivery performance metrics แล้วระบุว่าข้อมูลใดในแท็บ Actions ใช้วัดแต่ละตัวชี้วัดได้ เพื่อตอบ Lab D.3
  5. เขียนใบงาน flow การส่งมอบ (งานข้อ 2) ทำแบบฝึกหัดท้ายบทให้ครบ และเขียน Learning Log ครั้งที่ 8

เอกสารอ้างอิง

แหล่งข้อมูลเพิ่มเติมสำหรับตัวอย่างและกรณีอธิบาย

ตรวจเอกสารออนไลน์เมื่อ 23 ตุลาคม 2569 ตัวเลขและเหตุการณ์ที่ระบุว่าสมมติใช้เพื่ออธิบายกลไก ไม่ใช่ผลทดลองหรือสถิติขององค์กรจริง

เอกสารอ้างอิงของเนื้อหาเดิม

By Watcharin Sarachai