Bấm git push. Tab Actions quay tít thò lòi mười hai phút. Cuối tháng nhận email cảnh báo hết phút Runner miễn phí.

Đó là kịch bản quen thuộc khi lập trình viên bê nguyên file YAML mẫu trên mạng vào dự án thực tế. Hầu hết pipeline hiện nay mắc hai sai lầm nghiêm trọng: lãng phí thời gian build vì cache chưa đúng cách, và chôn rủi ro bảo mật lớn khi dán AWS Access Key dài hạn vào GitHub Secrets.

Bài viết này tối ưu lại pipeline GitHub Actions theo chuẩn sản xuất: chạy nhanh hơn đáng kể, giảm thiểu rủi ro bảo mật với OIDC, và có quy trình kiểm thử pipeline ngay tại máy cá nhân trước khi push.

1. Hai “Điểm Nghẽn” Khiến Pipeline Vừa Chậm Vừa Rủi Ro

❌ Điểm nghẽn 1: Cài lại dependencies từ đầu ở mỗi commit

Mỗi lần job chạy, GitHub khởi tạo một máy ảo Ubuntu mới tinh. Nếu không cấu hình cache đúng cách, lệnh npm ci hay pip install sẽ phải tải lại hàng trăm MB thư viện qua mạng trong khi kết quả lần build trước vẫn còn nguyên — chỉ vì runner không biết cách tận dụng.

❌ Điểm nghẽn 2: Dùng AWS Access Key dài hạn

Lưu AWS_ACCESS_KEY_IDAWS_SECRET_ACCESS_KEY trong GitHub Secrets là cách làm cũ. Nếu tài khoản GitHub hoặc repo bị can thiệp, cặp key này có thể bị lộ, và kẻ xấu sẽ dùng nó để đào coin trên tài khoản AWS của bạn. Long-lived key cũng khó kiểm soát vì không có thời điểm hết hạn rõ ràng — nó sống cho đến khi bạn nhớ thu hồi.

2. Giải Pháp Sản Xuất: OIDC + Caching Đúng Chuẩn

Thay vì dùng key dài hạn, chuẩn bảo mật hiện nay là OpenID Connect (OIDC). GitHub Actions sẽ tự thỏa thuận với AWS (hoặc GCP, Azure) để xin một token ngắn hạn — chỉ sống trong vài phút — để thực hiện deploy, sau đó token tự hủy. Không có bí mật nào nằm yên trong Secrets để chờ bị lộ.

Dưới đây là file workflow YAML chuẩn sản xuất, tích hợp đầy đủ: OIDC, phân quyền tối thiểu (least privilege), và caching tự động qua cache key của chính GitHub Actions:

name: Production CI/CD Pipeline

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

# BẮT BUỘC: Chỉ cấp quyền đọc code và xin ID Token cho OIDC.
# Không để mặc định — default permission của GITHUB_TOKEN đã có writeContents.
permissions:
  id-token: write   # Cần thiết để xác thực OIDC với AWS/GCP
  contents: read    # Chỉ đọc source code, không cấp quyền ghi

jobs:
  build-and-test:
    runs-on: ubuntu-latest

    steps:
      # Step 1: Checkout mã nguồn
      - name: Checkout Code
        uses: actions/checkout@v4

      # Step 2: Setup Node.js kèm caching tự động theo package-lock.json
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'  # Cache npm package cache dựa trên package-lock.json

      # Step 3: Cài dependencies (cực nhanh khi hit cache).
      # npm ci đọc cache nội bộ mà setup-node vừa restore.
      - name: Install Dependencies
        run: npm ci

      # Step 4: Chạy kiểm thử
      - name: Run Tests
        run: npm test

  deploy-aws:
    needs: build-and-test
    if: github.ref == 'refs/heads/main' && github.event_name == 'push'
    runs-on: ubuntu-latest

    steps:
      - name: Checkout Code
        uses: actions/checkout@v4

      # Step 5: Xác thực AWS bằng OIDC — không cần lưu Access Key dài hạn.
      # Role ARN phải tồn tại trong IAM với trust policy cho phép
      # token.actions.githubusercontent.com từ repo này.
      - name: Configure AWS Credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsDeployRole
          aws-region: ap-southeast-1

      # Step 6: Thực thi deploy. Token do OIDC cấp chỉ sống vài phút,
      # ngay sau khi job kết thúc sẽ tự hết hiệu lực.
      - name: Deploy to Production
        run: |
          echo "Xác thực OIDC thành công! Đang triển khai ứng dụng..."
          # aws s3 sync ./build s3://your-bucket --delete
          # aws ecs update-service --cluster prod --service web --force-new-deployment

Lưu ý kỹ thuật quan trọng: cache: 'npm' không trực tiếp cache node_modules. Nó cache npm package cache (~/.npm) và chỉ dùng lại được khi package-lock.json không đổi. npm ci sau đó sẽ đọc từ cache này để bỏ qua bước tải về. Nếu bạn đổi một dòng trong package-lock.json, cache key thay đổi và một cache mới sẽ được tạo — đây là hành vi đúng, vì nội dung thư viện giờ khác.

3. Mẹo Debug Workflow Ngay Tại Local

Mỗi lần sửa file YAML lại phải git commitgit push để xem nó chạy đúng hay sai là một trải nghiệm rất tệ.

Hãy dùng công cụ mã nguồn mở act để giả lập và chạy thử GitHub Actions ngay trên máy của bạn thông qua Docker:

# macOS / Linux
brew install act

# Windows (Chocolatey)
choco install act-cli

# Chạy thử job 'build-and-test' ngay tại terminal
act -j build-and-test

act sẽ đọc file trong thư mục .github/workflows/ và kéo container Ubuntu về máy để chạy thử. Bạn sẽ biết ngay file YAML bị lỗi cú pháp ở dòng nào mà không làm bẩn lịch sử commit.

Hai lưu ý thực tế khi dùng act:

  • Secrets và OIDC không tự động có sẵn. Bạn cần truyền thủ công qua act --secret AWS_ROLE_TO_ASSUME=... hoặc file .env — không phải cứ đẩy lên GitHub là chạy được phần OIDC.
  • Container runner hơi khác GitHub-hosted. Một số edge case (ví dụ service container, matrix phức tạp) có thể chạy khác trên GitHub Actions thật. Hãy coi act là bộ lọc lỗi cú pháp sớm, không phải môi trường test đầy đủ.

4. Bảng Kiểm Tra (Checklist) Cho Một Pipeline Chuẩn Sản Xuất

  • Cache qua action chính thức — dùng cache: 'npm' trong actions/setup-node, hoặc actions/cache với key dựa trên hash file lock tương ứng (package-lock.json, requirements.txt, go.sum).
  • Hạ quyền token mặc định — khai báo khối permissions: ở đầu file YAML theo nguyên tắc least privilege, không để GITHUB_TOKEN ngầm có write.
  • Chuyển sang OIDC — loại bỏ long-lived access key khỏi Secrets; thiết lập IAM role với trust policy cho token.actions.githubusercontent.com.
  • Test local bằng act — đảm bảo YAML không sai thụt lề hoặc sai tham chiếu step trước khi push.
  • Tách job build và deploy — dùng needs: + điều kiện if: github.ref == 'refs/heads/main' để deploy chỉ chạy sau khi test pass trên main.

Nguồn tham khảo: