Buggy
← บทความทั้งหมด

GitHub Actions: เขียน CI ที่ไม่พังทุกครั้งที่ merge

· 1 min read

#ci-cd#devops

CI ที่ช้าและพังบ่อยส่งผลเสียมากกว่าไม่มี CI เพราะทีมจะเริ่มชิน กับการกด re-run แล้วเชื่อผลลัพธ์น้อยลงเรื่อยๆ บทความนี้รวมสิ่งที่ทำให้ workflow บน GitHub Actions ทั้งเร็วขึ้นและน่าเชื่อถือขึ้น

แยก job ที่ไม่เกี่ยวกันให้รันขนาน

lint, typecheck และ test ไม่ต้องรอกัน แยกเป็นคนละ job แล้วปล่อยให้รันพร้อมกัน เวลารวมของ pipeline จะเท่ากับ job ที่ช้าที่สุด ไม่ใช่ผลรวมทั้งหมด

jobs:
  check:
    strategy:
      fail-fast: false
      matrix:
        task: [lint, typecheck, test]
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: pnpm
      - run: pnpm install --frozen-lockfile
      - run: pnpm ${{ matrix.task }}

fail-fast: false สำคัญ เพราะเราอยากเห็นว่าพังกี่อย่างในรอบเดียว ไม่ใช่เห็นทีละอย่างแล้วต้อง push ซ้ำ

ยกเลิก run เก่าเมื่อมี push ใหม่

ถ้า push สาม commit ติดกัน ไม่มีเหตุผลที่จะรัน CI ของสอง commit แรกจนจบ

concurrency:
  group: ${{ github.workflow }}-${{ github.ref }}
  cancel-in-progress: true

ข้อควรระวัง: อย่าใช้ cancel-in-progress: true กับ workflow ที่ deploy จริง เพราะการยกเลิกกลางคันอาจทิ้ง state ค้างไว้

Cache ให้ถูกชั้น

cache ของ package manager ช่วยเรื่อง download แต่ไม่ช่วยเรื่องเวลา build ถ้าโปรเจกต์ใช้ Next.js หรือ TypeScript ให้ cache ผลลัพธ์ build ด้วย โดยใช้ key ที่ผูกกับ lockfile และ hash ของ source

- uses: actions/cache@v4
  with:
    path: .next/cache
    key: next-${{ hashFiles('pnpm-lock.yaml') }}-${{ hashFiles('src/**') }}
    restore-keys: next-${{ hashFiles('pnpm-lock.yaml') }}-

restore-keys คือส่วนที่คนมักลืม ถ้าไม่มี cache จะ hit เฉพาะตอน source ไม่เปลี่ยนเลย ซึ่งแทบไม่เคยเกิดขึ้น

จำกัดสิทธิ์ของ token

ค่าเริ่มต้นของ GITHUB_TOKEN กว้างกว่าที่ workflow ส่วนใหญ่ต้องใช้ ประกาศสิทธิ์เท่าที่จำเป็นไว้บนสุดของไฟล์

permissions:
  contents: read

แล้วค่อยเพิ่มเป็นราย job สำหรับงานที่ต้องเขียนจริง เช่นการสร้าง release