Module 17 CI/CD 5 labs

CI/CD Fundamentals: GitHub Actions, GitLab CI, Jenkins & Azure Pipelines

Nắm vững nguyên lý CI/CD pipeline, xây dựng workflow tự động build-test-artifact-deploy trên bốn nền tảng phổ biến nhất trong doanh nghiệp — từ YAML workflow đến approval gate và rollback strategy.

Công cụ thực hành GitHub Actions, GitLab CI, Jenkins, Azure DevOps CLI, VS Code
Nền tảng GitHub, GitLab, Jenkins (Docker), Azure DevOps, CLI / Portal
Thời điểm phát hành 23/05/2026
Ngày biên soạn 23/05/2026
Người biên soạn Trần Văn Hòa — Microsoft Certified Trainer (MCT)

Mục tiêu học tập

1. Lý thuyết cốt lõi

1.1. CI/CD là gì và tại sao quan trọng?

Continuous Integration (CI) — mỗi commit được tự động build và test, phát hiện lỗi tích hợp sớm. Continuous Delivery (CD) — artifact luôn ở trạng thái có thể deploy bất kỳ lúc nào. Continuous Deployment — mọi commit đi thẳng ra production không cần phê duyệt thủ công. Theo DevOps Handbook (Gene Kim), pipeline CI/CD là hiện thân của "Way 1 — Flow": tối ưu luồng từ Dev đến Ops, giảm lead time và batch size, loại bỏ sự cố tích hợp muộn.

Vòng đời pipeline chuẩn

  1. Trigger — push, pull request, schedule, webhook.
  2. Build — compile, package, Docker image build.
  3. Test — unit, integration, SAST, dependency scan.
  4. Artifact — publish build result lên registry hoặc artifact store.
  5. Deploy to Staging — auto-deploy kèm smoke test.
  6. Approval Gate — manual review trước khi lên production (Continuous Delivery).
  7. Deploy to Production — rolling, blue-green, hoặc canary.
  8. Verify & Rollback — health check; tự động rollback nếu thất bại.

1.2. GitHub Actions — kiến trúc

GitHub Actions (ra mắt 2018, GA 2019) tích hợp native vào GitHub. Workflow được lưu tại .github/workflows/*.yml. Các khái niệm chính (theo Learning GitHub Actions, Brent Laster, O'Reilly 2023):

1.3. GitLab CI — pipeline as code

GitLab CI định nghĩa pipeline trong .gitlab-ci.yml tại root repo. Điểm khác biệt: stages (thực thi tuần tự), jobs trong cùng stage chạy song song, rules/only/except kiểm soát điều kiện, artifacts truyền file giữa stages, cache lưu dependencies giữa các run. Runner đăng ký bằng gitlab-runner register, hỗ trợ executor: shell, docker, kubernetes.

1.4. Jenkins — pipeline linh hoạt nhất

Jenkins (2011) vẫn dominant trong doanh nghiệp lớn nhờ hệ sinh thái plugin phong phú. Declarative Pipeline (khuyến nghị) dùng cú pháp pipeline { } rõ ràng, dễ lint; Scripted Pipeline (Groovy) linh hoạt hơn nhưng phức tạp. Shared Libraries cho phép tái sử dụng Groovy code qua nhiều pipeline. Jenkins sử dụng agent (node) để phân tán build; credentials() binding bảo vệ secrets.

1.5. Azure Pipelines — tích hợp sâu với Azure

Azure Pipelines (một phần Azure DevOps) hỗ trợ YAML pipeline lưu trong repo (pipeline as code) lẫn Classic (UI). Kiến trúc: Stage → Job → Step. Tính năng nổi bật: Environments (quản lý deployment targets với audit trail), Approvals & Checks (gate thủ công hoặc policy), Service Connections (quản lý xác thực ra ngoài), Deployment jobs (rollback tích hợp), Variable Groups liên kết Azure Key Vault.

1.6. So sánh 4 nền tảng

Tiêu chí GitHub Actions GitLab CI Jenkins Azure Pipelines
Lưu trữGitHub SaaSGitLab SaaS / self-hostedSelf-hostedAzure DevOps SaaS
Cú phápYAML event-drivenYAML stage-basedGroovy DSLYAML stage-job-step
Marketplace20,000+ ActionsTemplates catalog1,800+ pluginsAzure Marketplace tasks
Phù hợpOpen source, SaaS startupsAll-in-one DevOpsEnterprise on-premMicrosoft ecosystem
Free tier2,000 min/tháng400 min/thángMiễn phí (self-host)1,800 min/tháng

1.7. Chiến lược deployment

2. Thực hành (Labs)

LAB-001

GitHub Actions CI: build, test, artifact upload

GitHub · CLI · VS Code

🎯 Mục tiêu: Tạo workflow GitHub Actions tự động build Node.js app, chạy unit test, upload artifact ZIP mỗi khi push lên nhánh main.

🧰 Công cụ / nền tảng: GitHub (tài khoản free), Git, Node.js 20, VS Code.

📦 Chuẩn bị: Tạo repo GitHub mới (public hoặc private). Cài Node.js 20 trên máy local. Có gh CLI (tùy chọn).

▶️ Các bước:

# 1. Khởi tạo dự án Node.js tối giản
mkdir ci-demo && cd ci-demo
git init
npm init -y
npm install --save-dev jest

# 2. Tạo file ứng dụng và test
cat > src/math.js << 'EOF'
function add(a, b) { return a + b; }
module.exports = { add };
EOF

cat > src/math.test.js << 'EOF'
const { add } = require('./math');
test('add 2+3 = 5', () => expect(add(2, 3)).toBe(5));
EOF

# Cập nhật package.json test script
npm pkg set scripts.test="jest"
npm pkg set scripts.build="echo 'Build OK' && mkdir -p dist && cp src/*.js dist/"

# 3. Chạy test cục bộ để xác nhận
npm test
# 4. Tạo GitHub Actions workflow
mkdir -p .github/workflows
cat > .github/workflows/ci.yml << 'EOF'
name: CI Pipeline

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

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

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

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Run unit tests
        run: npm test -- --ci --coverage

      - name: Build artifact
        run: npm run build

      - name: Upload build artifact
        uses: actions/upload-artifact@v4
        with:
          name: build-${{ github.sha }}
          path: dist/
          retention-days: 7
EOF
# 5. Commit và push
git add .
git commit -m "feat: add ci pipeline with build, test, artifact upload"
git remote add origin https://github.com/<username>/ci-demo.git
git push -u origin main

# 6. Theo dõi workflow (yêu cầu gh CLI)
gh run watch

🖥️ Đối chiếu GUI (Portal): Vào GitHub → tab Actions → click workflow run → xem log từng step; tab Artifacts để download file build.

✅ Kết quả mong đợi: Workflow hiển thị dấu tích xanh; log test in PASS src/math.test.js; artifact build-<sha> xuất hiện trong tab Artifacts có thể download.

🧹 Cleanup: Artifact tự xóa sau 7 ngày. Xóa repo nếu không cần: gh repo delete ci-demo --yes.

LAB-002

GitLab CI: stages build/test/package với Docker runner

GitLab · Docker · CLI

🎯 Mục tiêu: Viết .gitlab-ci.yml có 3 stages (build, test, package), cache node_modules, publish Docker image lên GitLab Container Registry.

🧰 Công cụ / nền tảng: GitLab.com (tài khoản free), Docker Engine, Git CLI.

📦 Chuẩn bị: Tạo project GitLab mới. Bật Container Registry (Settings → Packages & Registries). Đảm bảo Shared Runner đang active (GitLab.com có sẵn).

▶️ Các bước:

# 1. Tạo Dockerfile tối giản trong project
cat > Dockerfile << 'EOF'
FROM node:20-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --omit=dev
COPY src/ ./src/
EXPOSE 3000
CMD ["node", "src/index.js"]
EOF

# src/index.js tối giản
mkdir -p src
cat > src/index.js << 'EOF'
const http = require('http');
http.createServer((_, res) => res.end('OK')).listen(3000);
console.log('Server on :3000');
EOF
# 2. Tạo .gitlab-ci.yml
cat > .gitlab-ci.yml << 'EOF'
stages:
  - build
  - test
  - package

variables:
  NODE_VERSION: "20"
  IMAGE_TAG: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

# Cache dependencies giữa các pipeline run
cache:
  key:
    files:
      - package-lock.json
  paths:
    - node_modules/

# Stage 1: Install & build
install-deps:
  stage: build
  image: node:20-alpine
  script:
    - npm ci
    - echo "Dependencies installed at $(date)"
  artifacts:
    paths:
      - node_modules/
    expire_in: 1 hour

# Stage 2: Unit tests
unit-test:
  stage: test
  image: node:20-alpine
  needs: [install-deps]
  script:
    - npm test -- --ci --reporters=default --reporters=jest-junit
  artifacts:
    when: always
    reports:
      junit: junit.xml
    expire_in: 1 week

# Stage 3: Build & push Docker image
docker-build:
  stage: package
  image: docker:26
  services:
    - docker:26-dind
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
  needs: [unit-test]
  rules:
    - if: $CI_COMMIT_BRANCH == "main"
  before_script:
    - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
  script:
    - docker build -t $IMAGE_TAG .
    - docker push $IMAGE_TAG
    - echo "Pushed image $IMAGE_TAG"
  after_script:
    - docker logout $CI_REGISTRY
EOF
# 3. Push lên GitLab
git add Dockerfile .gitlab-ci.yml src/
git commit -m "feat: add gitlab ci with 3 stages"
git push origin main

# 4. Theo dõi pipeline
# Vào GitLab → CI/CD → Pipelines → xem log từng job

🖥️ Đối chiếu GUI: GitLab → CI/CD → Pipelines → click pipeline ID → xem graph stages → click job để xem log stream real-time; Packages & Registries → Container Registry để verify image đã push.

✅ Kết quả mong đợi: Pipeline 3 stages đều xanh; log stage docker-build kết thúc bằng Pushed image registry.gitlab.com/.../ci-demo:<sha>; Test report hiển thị trong tab Tests.

🧹 Cleanup: GitLab → Packages & Registries → Container Registry → xóa image tag cũ. Xóa project nếu không dùng tiếp.

LAB-003

Jenkins Declarative Pipeline với parallel stages

Jenkins · Docker · CLI

🎯 Mục tiêu: Chạy Jenkins bằng Docker, viết Declarative Pipeline có parallel test jobs, credentials binding và post-action (archive artifact, send status).

🧰 Công cụ / nền tảng: Docker Desktop (Windows/macOS/Linux), trình duyệt, Git.

📦 Chuẩn bị: Docker Engine đang chạy. Port 8080 và 50000 còn trống.

▶️ Các bước:

# 1. Chạy Jenkins LTS bằng Docker
docker run -d --name jenkins \
  -p 8080:8080 -p 50000:50000 \
  -v jenkins_home:/var/jenkins_home \
  jenkins/jenkins:lts-jdk21

# 2. Lấy initial admin password
docker exec jenkins cat /var/jenkins_home/secrets/initialAdminPassword

# 3. Truy cập http://localhost:8080
#    Nhập password → Install suggested plugins → tạo admin user
# 4. Tạo Jenkinsfile trong project repo (hoặc paste vào Pipeline Script)
cat > Jenkinsfile << 'EOF'
pipeline {
    agent any

    environment {
        APP_NAME = 'ci-demo'
        DOCKER_CRED = credentials('dockerhub-cred')  // tạo credential trước
    }

    options {
        timeout(time: 15, unit: 'MINUTES')
        buildDiscarder(logRotator(numToKeepStr: '5'))
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
                sh 'git log -1 --oneline'
            }
        }

        stage('Install') {
            steps {
                sh 'node --version && npm --version'
                sh 'npm ci'
            }
        }

        stage('Parallel Tests') {
            parallel {
                stage('Unit Tests') {
                    steps {
                        sh 'npm test -- --ci'
                    }
                    post {
                        always {
                            junit 'junit.xml'
                        }
                    }
                }
                stage('Lint') {
                    steps {
                        sh 'npx eslint src/ --max-warnings 0 || true'
                    }
                }
            }
        }

        stage('Build') {
            steps {
                sh 'npm run build'
                archiveArtifacts artifacts: 'dist/**', fingerprint: true
            }
        }

        stage('Docker Build & Push') {
            when {
                branch 'main'
            }
            steps {
                sh '''
                    docker build -t ${DOCKER_CRED_USR}/${APP_NAME}:${BUILD_NUMBER} .
                    echo ${DOCKER_CRED_PSW} | docker login -u ${DOCKER_CRED_USR} --password-stdin
                    docker push ${DOCKER_CRED_USR}/${APP_NAME}:${BUILD_NUMBER}
                '''
            }
        }
    }

    post {
        success {
            echo "Pipeline SUCCESS: build #${env.BUILD_NUMBER}"
        }
        failure {
            echo "Pipeline FAILED: check logs at ${env.BUILD_URL}"
        }
        always {
            cleanWs()
        }
    }
}
EOF
# 5. Tạo Pipeline job trong Jenkins UI
# Jenkins → New Item → Pipeline → "ci-demo-pipeline"
# Pipeline Definition: Pipeline script from SCM
# SCM: Git → URL repo của bạn → Branch: main → Script Path: Jenkinsfile
# Bấm Save → Build Now

# 6. Xem Stage View (yêu cầu plugin Blue Ocean hoặc Pipeline Stage View)
# http://localhost:8080/job/ci-demo-pipeline/lastBuild/flowGraphTable/

🖥️ Đối chiếu GUI: Jenkins → job → Stage View hiển thị Checkout / Install / Parallel Tests / Build dạng bảng có thời gian; click ô màu đỏ để xem log lỗi cụ thể.

✅ Kết quả mong đợi: Stage View hiển thị tất cả stages xanh; Parallel Tests chạy Unit Tests và Lint đồng thời; Artifacts xuất hiện trong build với fingerprint.

🧹 Cleanup: docker stop jenkins && docker rm jenkins && docker volume rm jenkins_home

LAB-004

Azure Pipelines multi-stage với approval gate

Azure DevOps · Azure CLI · Portal

🎯 Mục tiêu: Tạo Azure Pipelines YAML 3 stages (CI, Deploy-Staging, Deploy-Production) với manual approval gate trước production và environment traceability.

🧰 Công cụ / nền tảng: Azure DevOps (free tier), Azure CLI, Git, VS Code với Azure Pipelines extension.

📦 Chuẩn bị: Tạo Azure DevOps organization tại dev.azure.com. Tạo project mới. Import/push repo. Cài Azure CLI: winget install Microsoft.AzureCLI (Windows) hoặc brew install azure-cli (macOS).

▶️ Các bước:

# 1. Đăng nhập Azure DevOps bằng CLI
az devops configure --defaults organization=https://dev.azure.com/<org> project=ci-demo
az devops login   # nhập PAT token khi được hỏi

# 2. Tạo Environments với approval (qua Portal)
# Azure DevOps → Pipelines → Environments → New environment
# Tạo 2 environments: "staging" và "production"
# Production environment: Approvals and checks → Add approval → chọn chính bạn làm approver
# 3. Tạo file azure-pipelines.yml
cat > azure-pipelines.yml << 'EOF'
trigger:
  branches:
    include:
      - main

variables:
  buildConfiguration: 'Release'
  nodeVersion: '20.x'

stages:
  # ---- Stage 1: CI ----
  - stage: CI
    displayName: 'Build & Test'
    jobs:
      - job: Build
        displayName: 'Build and Test Node.js'
        pool:
          vmImage: 'ubuntu-latest'
        steps:
          - task: NodeTool@0
            inputs:
              versionSpec: $(nodeVersion)
            displayName: 'Install Node.js'

          - script: npm ci
            displayName: 'Install dependencies'

          - script: npm test -- --ci --reporters=jest-junit
            displayName: 'Run unit tests'

          - task: PublishTestResults@2
            inputs:
              testResultsFormat: 'JUnit'
              testResultsFiles: 'junit.xml'
            condition: always()

          - script: npm run build
            displayName: 'Build artifact'

          - task: PublishBuildArtifacts@1
            inputs:
              PathtoPublish: 'dist'
              ArtifactName: 'drop'

  # ---- Stage 2: Deploy Staging ----
  - stage: DeployStaging
    displayName: 'Deploy to Staging'
    dependsOn: CI
    condition: succeeded()
    jobs:
      - deployment: DeployToStaging
        displayName: 'Deploy App to Staging'
        pool:
          vmImage: 'ubuntu-latest'
        environment: 'staging'    # tracks deployment history
        strategy:
          runOnce:
            deploy:
              steps:
                - download: current
                  artifact: drop

                - script: |
                    echo "=== Deploying to Staging ==="
                    ls -la $(Pipeline.Workspace)/drop/
                    echo "Simulating deploy to staging server..."
                    sleep 2
                    echo "Deploy OK. Health check: http://staging.example.com/healthz"
                  displayName: 'Deploy to staging'

  # ---- Stage 3: Deploy Production (with Approval) ----
  - stage: DeployProduction
    displayName: 'Deploy to Production'
    dependsOn: DeployStaging
    condition: succeeded()
    jobs:
      - deployment: DeployToProduction
        displayName: 'Deploy App to Production'
        pool:
          vmImage: 'ubuntu-latest'
        environment: 'production'   # approval gate được cấu hình ở Environment
        strategy:
          runOnce:
            deploy:
              steps:
                - download: current
                  artifact: drop

                - script: |
                    echo "=== Deploying to Production ==="
                    ls -la $(Pipeline.Workspace)/drop/
                    echo "Deploying with blue-green strategy..."
                    sleep 3
                    echo "Production deploy COMPLETE."
                  displayName: 'Deploy to production'

                - script: |
                    echo "Running smoke test..."
                    curl -f http://prod.example.com/healthz || (echo "SMOKE TEST FAILED" && exit 1)
                  displayName: 'Smoke test'
                  continueOnError: true
EOF
# 4. Push và tạo pipeline
git add azure-pipelines.yml
git commit -m "feat: add azure pipelines multi-stage with approval"
git push origin main

# 5. Azure DevOps → Pipelines → New Pipeline → Azure Repos Git → repo → Existing YAML
# Bấm Run → Pipeline chạy CI và DeployStaging tự động
# Khi đến DeployProduction: pipeline dừng, gửi email approval
# Approver xem review tại: Pipelines → <run> → Review → Approve

# 6. Kiểm tra Environments traceability
az devops invoke --area distributedtask --resource environments --org https://dev.azure.com/<org>

🖥️ Đối chiếu Portal: Azure DevOps → Pipelines → run → chọn stage DeployProduction → nút "Review" → "Approve"; Environments → production → Deployments để xem lịch sử deploy.

✅ Kết quả mong đợi: CI và Staging tự động pass; Production dừng chờ approval (icon đồng hồ); sau khi approve, stage Production chạy và hiển thị xanh; Environments/production ghi lại deployment với commit SHA và người approve.

🧹 Cleanup: Azure DevOps → Pipelines → xóa pipeline; nếu đã tạo Azure resources thực, az group delete --name rg-ci-demo --yes --no-wait.

LAB-005

Approval gate & automated rollback pipeline

GitHub Actions · CLI

🎯 Mục tiêu: Xây dựng GitHub Actions workflow có environment-level approval, health check sau deploy, và tự động rollback về commit trước nếu health check thất bại.

🧰 Công cụ / nền tảng: GitHub (tài khoản free), GitHub CLI (gh), Bash/PowerShell.

📦 Chuẩn bị: Dùng repo từ LAB-001. Tạo GitHub Environment "production": Settings → Environments → New → "production" → Required reviewers: chọn bản thân.

▶️ Các bước:

# 1. Tạo workflow deploy với approval và rollback
cat > .github/workflows/deploy.yml << 'EOF'
name: Deploy with Approval & Rollback

on:
  workflow_dispatch:
    inputs:
      version:
        description: 'Tag/SHA to deploy'
        required: true
        default: 'latest'

env:
  APP_VERSION: ${{ github.event.inputs.version }}

jobs:
  # ----- Job 1: Build & Test -----
  ci:
    name: Build & Test
    runs-on: ubuntu-latest
    outputs:
      artifact-id: ${{ steps.upload.outputs.artifact-id }}
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'

      - run: npm ci
      - run: npm test -- --ci

      - run: npm run build

      - id: upload
        uses: actions/upload-artifact@v4
        with:
          name: release-${{ github.sha }}
          path: dist/

  # ----- Job 2: Staging deploy (no approval) -----
  deploy-staging:
    name: Deploy to Staging
    needs: ci
    runs-on: ubuntu-latest
    environment: staging
    steps:
      - uses: actions/download-artifact@v4
        with:
          name: release-${{ github.sha }}
          path: dist/

      - name: Deploy to staging
        run: |
          echo "Deploying version $APP_VERSION to STAGING..."
          ls dist/
          # Simulate deploy (thay bằng rsync/kubectl apply/az webapp deploy thực tế)
          echo "Staging URL: https://staging.example.com"

      - name: Staging health check
        run: |
          echo "Checking staging health..."
          # curl -f https://staging.example.com/healthz
          echo "Staging OK"

  # ----- Job 3: Production deploy (requires approval) -----
  deploy-production:
    name: Deploy to Production
    needs: deploy-staging
    runs-on: ubuntu-latest
    environment: production    # <- GitHub prompts approver here
    outputs:
      previous-sha: ${{ steps.prev.outputs.sha }}
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2

      - name: Get previous commit SHA
        id: prev
        run: echo "sha=$(git rev-parse HEAD~1)" >> $GITHUB_OUTPUT

      - uses: actions/download-artifact@v4
        with:
          name: release-${{ github.sha }}
          path: dist/

      - name: Deploy to production
        id: deploy
        run: |
          echo "Deploying version $APP_VERSION to PRODUCTION..."
          ls dist/
          # Simulate production deploy
          echo "Production deployed at $(date -u)"

      - name: Health check
        id: health
        run: |
          echo "Running health check..."
          # Simulate: thay bằng curl -f https://prod.example.com/healthz
          HEALTH_OK=true  # đặt false để test rollback
          if [ "$HEALTH_OK" = "false" ]; then
            echo "::error::Health check FAILED"
            exit 1
          fi
          echo "Health check PASSED"

  # ----- Job 4: Rollback (only on deploy failure) -----
  rollback:
    name: Rollback Production
    needs: deploy-production
    if: failure()
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 5

      - name: Get previous stable SHA
        id: prev
        run: |
          PREV_SHA=$(git log --oneline -5 | grep -v "${{ github.sha }}" | head -1 | awk '{print $1}')
          echo "sha=$PREV_SHA" >> $GITHUB_OUTPUT
          echo "Rolling back to: $PREV_SHA"

      - name: Execute rollback
        run: |
          echo "ROLLBACK initiated: reverting to ${{ steps.prev.outputs.sha }}"
          git checkout ${{ steps.prev.outputs.sha }} -- dist/ || true
          # Redeploy previous version
          echo "Rollback complete. Service restored."

      - name: Notify team
        run: |
          echo "::warning::ROLLBACK executed for production. Previous stable: ${{ steps.prev.outputs.sha }}"
EOF
# 2. Push workflow
git add .github/workflows/deploy.yml
git commit -m "feat: add deploy workflow with approval gate and rollback"
git push origin main

# 3. Trigger thủ công
gh workflow run deploy.yml --field version=1.0.0

# 4. Xem status
gh run list --workflow=deploy.yml
gh run watch   # realtime

# 5. Khi workflow dừng ở production approval:
gh run list --workflow=deploy.yml
# Lấy run ID rồi approve qua Portal hoặc:
# GitHub → Actions → run → Review deployments → Approve

# 6. Test rollback: sửa HEALTH_OK=false trong workflow, push lại và trigger

🖥️ Đối chiếu Portal: GitHub → Actions → workflow run → job "Deploy to Production" hiển thị nút "Review deployments" → click → "Approve and deploy"; nếu rollback xảy ra, job "Rollback Production" hiển thị màu vàng (warning).

✅ Kết quả mong đợi (happy path): 4 jobs xanh lần lượt; log production ghi Health check PASSED. Rollback scenario: sau khi đặt HEALTH_OK=false, jobs CI/Staging xanh → Production đỏ → Rollback chạy và in ROLLBACK executed.

🧹 Cleanup: Xóa environment "production" trong Settings → Environments nếu muốn bỏ approval. gh workflow disable deploy.yml để tắt workflow.

3. Tình huống doanh nghiệp thực tế

Bối cảnh: Fintech ra mắt mobile app phiên bản mới

Đội 3 backend developer, release 2 tuần/lần. Trước đây deploy thủ công bằng FTP, mất 2 ngày kiểm thử, thường xuyên rollback muộn lúc nửa đêm. CTO yêu cầu "zero-downtime, full audit trail, rollback < 5 phút".

Giải pháp CI/CD

  • Chọn nền tảng: Azure Pipelines (đội đang dùng Microsoft stack: .NET, Azure App Service). Tích hợp sẵn với Azure Key Vault để inject secrets an toàn.
  • Pipeline 4 stage: CI (build + unit test + SAST scan) → Integration Test (E2E trên staging) → Approval Gate (PM + Lead Dev phê duyệt qua email/Teams) → Blue-Green Production (Azure App Service deployment slots).
  • Rollback strategy: Azure App Service slot swap — nếu health check thất bại sau 5 phút, swap ngược lại; thời gian rollback thực tế: 30 giây.
  • Kết quả sau 3 tháng: Deployment Frequency tăng từ 2 lần/tháng lên 8 lần/tháng; Change Failure Rate giảm từ 15% xuống 4%; không có incident rollback muộn nào.
  • Bài học: Approval gate nên ngắn hạn (tối đa 24h) để không block flow. Feature flags giúp đội deploy sớm và bật dần tính năng.

📚 Nguồn tham khảo

Module 16: Advanced Kubernetes Operations Module 18: Infrastructure as Code
Zalo