Skip to content

Buổi 13: CI/CD Pipeline — Automated Testing & Deployment 🚀

Thành quả: GitHub Actions pipeline: lint → test → build → deploy (tự động trên mỗi PR)


🎯 Mục Tiêu

  1. Hiểu CI/CD concepts và pipeline design
  2. Setup GitHub Actions cho automated testing
  3. Implement 8-gate deploy pipeline (cm-safe-deploy)
  4. Environment management (dev → staging → production)
  5. Rollback strategy khi deploy fail

📖 Phần 1: CI/CD Concepts

Continuous Integration (CI)

Developer pushes code → Automated pipeline runs:

┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐
│  LINT    │ → │  TEST    │ → │  BUILD   │ → │ PREVIEW  │
│ ESLint   │   │ Unit     │   │ Compile  │   │ Deploy   │
│ Prettier │   │ Integra. │   │ Bundle   │   │ Preview  │
│ TypeCheck│   │ Coverage │   │ Optimize │   │ URL      │
└──────────┘   └──────────┘   └──────────┘   └──────────┘
   Gate 1         Gate 2         Gate 3         Gate 4

Any gate FAIL → Pipeline STOPS → PR cannot merge

Continuous Deployment (CD)

PR merged to main → Automatic production deploy:

┌──────────┐   ┌──────────┐   ┌──────────┐   ┌──────────┐
│ SECRETS  │ → │ SECURITY │ → │ DEPLOY   │ → │ SMOKE    │
│ Scan     │   │ Audit    │   │ Ship to  │   │ Test     │
│ No leaks │   │ OWASP    │   │ Prod     │   │ Health   │
│          │   │ Deps     │   │          │   │ Check    │
└──────────┘   └──────────┘   └──────────┘   └──────────┘
   Gate 5         Gate 6         Gate 7         Gate 8

📖 Phần 2: GitHub Actions Setup

CI Pipeline

yaml
# .github/workflows/ci.yml
name: CI Pipeline

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

env:
  NODE_VERSION: '20'

jobs:
  lint:
    name: 🧹 Lint & Type Check
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      - run: npm ci
      - run: npm run lint
      - run: npm run type-check

  test:
    name: 🧪 Test
    runs-on: ubuntu-latest
    needs: lint
    services:
      postgres:
        image: postgres:16-alpine
        env:
          POSTGRES_DB: testdb
          POSTGRES_USER: test
          POSTGRES_PASSWORD: test
        ports: ['5432:5432']
        options: --health-cmd pg_isready --health-interval 10s
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      - run: npm ci
      - run: npx prisma migrate deploy
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb
      - run: npm run test:coverage
        env:
          DATABASE_URL: postgresql://test:test@localhost:5432/testdb
          JWT_ACCESS_SECRET: test-secret
          JWT_REFRESH_SECRET: test-refresh
      - uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage/

  build:
    name: 🏗️ Build
    runs-on: ubuntu-latest
    needs: test
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: ${{ env.NODE_VERSION }}
          cache: 'npm'
      - run: npm ci
      - run: npm run build
      - uses: actions/upload-artifact@v4
        with:
          name: build-artifacts
          path: dist/

  preview:
    name: 👁️ Preview Deploy
    runs-on: ubuntu-latest
    needs: build
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/download-artifact@v4
        with:
          name: build-artifacts
          path: dist/
      - name: Deploy to Preview
        uses: cloudflare/wrangler-action@v3
        with:
          command: pages deploy dist --project-name=myapp --branch=${{ github.head_ref }}
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}

CD Pipeline

yaml
# .github/workflows/cd.yml
name: CD Pipeline

on:
  push:
    branches: [main]

jobs:
  security-scan:
    name: 🔒 Security Scan
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Secret Scan
        uses: trufflesecurity/trufflehog@main
        with:
          path: ./
      - name: Dependency Audit
        run: npm audit --audit-level=high

  deploy-production:
    name: 🚀 Deploy Production
    runs-on: ubuntu-latest
    needs: security-scan
    environment: production
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
      - run: npm ci
      - run: npm run build
      - name: Deploy
        uses: cloudflare/wrangler-action@v3
        with:
          command: pages deploy dist --project-name=myapp
          apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}

  smoke-test:
    name: 🏥 Smoke Test
    runs-on: ubuntu-latest
    needs: deploy-production
    steps:
      - name: Health Check
        run: |
          sleep 30
          STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://myapp.com/api/health)
          if [ "$STATUS" != "200" ]; then
            echo "❌ Health check failed: $STATUS"
            exit 1
          fi
          echo "✅ Health check passed"

📖 Phần 3: cm-safe-deploy — 8 Gate Pipeline

Gate 1: 🔒 Identity Check     → cm-identity-guard
Gate 2: 🧹 Lint               → eslint + prettier
Gate 3: 🧪 Tests              → unit + integration
Gate 4: 📊 Coverage           → min 80% threshold
Gate 5: 🏗️ Build              → compilation success
Gate 6: 🔐 Secret Scan        → no leaked credentials
Gate 7: 🚀 Deploy             → push to hosting
Gate 8: 🏥 Smoke Test         → verify live endpoint

Gate Implementation

bash
#!/bin/bash
# deploy-gate.sh

set -e  # Exit on any failure

echo "🔒 Gate 1: Identity Check"
EXPECTED_EMAIL="dev@mycompany.com"
ACTUAL_EMAIL=$(git config user.email)
if [ "$ACTUAL_EMAIL" != "$EXPECTED_EMAIL" ]; then
  echo "❌ Wrong git identity: $ACTUAL_EMAIL (expected: $EXPECTED_EMAIL)"
  exit 1
fi
echo "✅ Identity verified"

echo "🧹 Gate 2: Lint"
npm run lint || { echo "❌ Lint failed"; exit 1; }

echo "🧪 Gate 3: Tests"
npm run test || { echo "❌ Tests failed"; exit 1; }

echo "📊 Gate 4: Coverage"
COVERAGE=$(npm run test:coverage 2>&1 | grep "All files" | awk '{print $4}')
if (( $(echo "$COVERAGE < 80" | bc -l) )); then
  echo "❌ Coverage $COVERAGE% < 80%"
  exit 1
fi
echo "✅ Coverage: $COVERAGE%"

echo "🏗️ Gate 5: Build"
npm run build || { echo "❌ Build failed"; exit 1; }

echo "🔐 Gate 6: Secret Scan"
npx secretlint --secretlintrc .secretlintrc.json "**/*"

echo "🚀 Gate 7: Deploy"
npx wrangler pages deploy dist/ --project-name=myapp

echo "🏥 Gate 8: Smoke Test"
sleep 15
curl -sf https://myapp.com/api/health > /dev/null || { echo "❌ Smoke test failed"; exit 1; }

echo "🎉 All 8 gates PASSED — Deploy successful!"

📖 Phần 4: Rollback Strategy

IF smoke test fails:
  1. Revert: wrangler pages deploy --rollback
  2. Alert: notify Slack/Discord
  3. Investigate: check error logs
  4. Fix: create hotfix branch → run pipeline again

IF discovered in production:
  1. Feature flag: disable feature remotely
  2. Revert commit: git revert HEAD → push → auto-redeploy
  3. Post-mortem: cm-debugging root cause analysis

🧪 Lab: Setup Complete CI/CD

Task: GitHub Actions pipeline (60 min)

Step 1: Create .github/workflows/ci.yml
Step 2: Create .github/workflows/cd.yml
Step 3: Add GitHub Secrets:
  → CLOUDFLARE_API_TOKEN
  → DATABASE_URL (for test DB)
Step 4: Push to trigger pipeline
Step 5: Create PR → verify all checks pass
Step 6: Merge → verify production deploy
Step 7: Document the pipeline in AGENTS.md

🎓 Tóm Tắt

ConceptImplementation
CILint → Test → Build → Preview (on every PR)
CDSecurity → Deploy → Smoke (on merge to main)
8 GatesIdentity → Lint → Test → Coverage → Build → Secret → Deploy → Smoke
RollbackAuto-revert, feature flags, hotfix branch

⏭️ Buổi tiếp theo

Buổi 14: Security & Quality — OWASP, Secret Scanning, Code Review 🔒

Powered by CodyMaster × VitePress