DevOps 4 — CI/CD Pipelines

13 thg 7, 2026 4 lượt xem
#pipeline
#ci-cd
#devops
#github-actions

DevOps 4 — CI/CD Pipelines

Nếu bạn từng "deploy bằng tay": SSH vào server, git pull, restart service, rồi cầu mong nó chạy — thì bạn đã trải nghiệm trực tiếp lý do CI/CD ra đời. Mỗi lần làm thủ công là một lần có khả năng quên bước, sai biến môi trường, hoặc đẩy code chưa qua test lên production. CI/CD biến chuỗi thao tác thủ công, dễ sai và khó lặp lại đó thành một đường ống tự động (pipeline) chạy giống hệt nhau mỗi lần.

Bài này đi từ khái niệm gốc đến pipeline thực tế: bạn sẽ hiểu rõ CI khác CD ở đâu, một pipeline được cấu thành từ những gì, viết một workflow GitHub Actions hoàn chỉnh (build, test, build Docker image), quản lý secrets an toàn, và chọn chiến lược triển khai (blue-green, canary, rolling) cùng cách rollback khi mọi thứ hỏng.

CI vs CD (Delivery) vs CD (Deployment)

Ba chữ cái viết tắt này hay bị gộp làm một, nhưng chúng là ba giai đoạn riêng biệt với mức độ tự động hoá tăng dần.

Continuous Integration (CI) — Tích hợp liên tục

CI là thực hành mỗi lập trình viên merge code vào nhánh chung thường xuyên (nhiều lần mỗi ngày), và mỗi lần merge sẽ kích hoạt một quy trình tự động: build dự án + chạy test. Mục tiêu của CI là phát hiện xung đột và lỗi tích hợp càng sớm càng tốt, khi thay đổi còn nhỏ và dễ sửa.

Trước CI, người ta thường làm "integration hell": mỗi người code riêng vài tuần, rồi cuối sprint mới ghép lại — và phát hiện hàng trăm xung đột. CI loại bỏ điều đó bằng cách giữ nhánh chính luôn ở trạng thái "build được, test xanh".

CI trả lời câu hỏi: "Code này có integrate được không?"

Continuous Delivery (CD) — Phân phối liên tục

Continuous Delivery mở rộng CI: sau khi build và test thành công, hệ thống tự động đóng gói artifact và đưa nó vào trạng thái sẵn sàng deploy (đẩy lên staging, tạo Docker image, đẩy lên registry...). Nhưng bước deploy lên production vẫn cần con người bấm nút phê duyệt.

Continuous Delivery trả lời: "Code này có thể release bất cứ lúc nào — chỉ cần ai đó đồng ý."

Continuous Deployment (CD) — Triển khai liên tục

Continuous Deployment đi xa nhất: nếu toàn bộ pipeline xanh, code được deploy thẳng lên production một cách tự động, không cần con người duyệt. Mỗi commit pass test là một lần lên production.

Điều này đòi hỏi độ tin cậy rất cao vào bộ test và hệ thống giám sát/rollback. Continuous Deployment trả lời: "Code đã tự lên production rồi."

Giai đoạnBuild + Test tự độngĐóng gói + chuẩn bị releaseDeploy production
Continuous Integration
Continuous DeliveryThủ công (bấm duyệt)
Continuous Deployment✅ Tự động

Thực tế, hầu hết tổ chức dừng ở Continuous Delivery — vì giữ một "cổng người" trước production là rất hợp lý khi bộ test chưa đủ tin cậy.

Vì sao phải tự động hoá build–test–deploy

Tự động hoá không chỉ để "tiết kiệm thời gian gõ lệnh". Lý do sâu hơn:

  • Tính lặp lại (reproducibility): Pipeline chạy y hệt nhau mọi lần. Không còn "máy tôi chạy được" — vì môi trường build được định nghĩa rõ ràng trong code.
  • Phát hiện lỗi sớm: Test chạy ngay khi push, không đợi đến lúc deploy. Lỗi rẻ nhất là lỗi được bắt trong vài phút sau khi viết.
  • Giảm rủi ro con người: Quên chạy migration, sai biến môi trường, deploy nhầm branch — những lỗi này biến mất khi máy làm thay.
  • Tăng tốc độ release: Team chín muồi có thể release nhiều lần mỗi ngày một cách an toàn, thay vì gom thành một "big bang release" hàng tháng đầy rủi ro.
  • Tài liệu sống: File pipeline chính là tài liệu mô tả cách build và deploy dự án. Đọc nó là hiểu quy trình.

Cấu trúc một pipeline

Dù dùng công cụ nào (GitHub Actions, GitLab CI, Jenkins), một pipeline đều chia sẻ cùng các khái niệm. Hãy hiểu chúng trước khi xem cú pháp cụ thể.

Trigger — Cái gì kích hoạt pipeline

Pipeline không tự chạy; nó được trigger bởi một sự kiện: push code lên một nhánh, mở pull request, tạo tag (release), chạy theo lịch (cron), hoặc bấm tay (manual dispatch).

Stage — Giai đoạn lớn

Stage là nhóm công việc theo logic, thường chạy tuần tự: ví dụ buildtestdeploy. Stage sau chỉ chạy nếu stage trước thành công. (Trong GitHub Actions không có khái niệm "stage" riêng, mà dùng quan hệ phụ thuộc needs giữa các job để mô phỏng.)

Job — Công việc chạy độc lập

Job là một đơn vị chạy trên một runner (máy ảo/container). Các job trong cùng một stage có thể chạy song song (ví dụ: test trên Linux và test trên Windows cùng lúc). Mỗi job khởi động trong môi trường sạch.

Step — Bước nhỏ trong job

Step là một lệnh hoặc một action cụ thể trong job: checkout code, cài dependency, chạy lệnh test... Các step trong cùng job chạy tuần tự và chia sẻ chung filesystem của runner.

Artifact — Sản phẩm được tạo ra

Artifact là output của pipeline cần được lưu lại hoặc truyền giữa các job: file build (.jar, dist/), Docker image, báo cáo test coverage. Artifact cho phép job "build" tạo ra thứ gì đó để job "deploy" sử dụng, dù chúng chạy trên runner khác nhau.

Kiểm thử trong pipeline

"Test trong CI" không chỉ là chạy unit test. Một pipeline trưởng thành có nhiều lớp kiểm tra, mỗi lớp bắt một loại lỗi khác nhau và có chi phí (thời gian) khác nhau.

Lint & format check

Chạy nhanh nhất, đặt đầu tiên. Kiểm tra code có đúng quy ước style không (ESLint, Black, golangci-lint). Mục đích: thống nhất codebase và bắt những lỗi cú pháp/logic đơn giản trước khi tốn thời gian build.

Unit test

Kiểm tra từng hàm/module riêng lẻ, không phụ thuộc database hay network. Nhanh (mili giây mỗi test), nên chạy hàng nghìn test trong vài phút. Đây là lớp bảo vệ chính.

Integration test

Kiểm tra nhiều thành phần phối hợp: code + database thật, code + API ngoài. Chậm hơn unit test (cần spin up DB), nên thường chạy sau và có thể dùng service container.

Security scan

  • SAST (Static Application Security Testing): quét source code tìm lỗ hổng (SQL injection, hardcoded secret) — ví dụ Semgrep, CodeQL.
  • Dependency scan: kiểm tra thư viện phụ thuộc có CVE đã biết không — npm audit, Dependabot, Trivy.
  • Container scan: quét Docker image tìm lỗ hổng trong base image và package OS — Trivy, Grype.

Nguyên tắc vàng: đặt test nhanh và rẻ lên trước (lint, unit). Nếu chúng fail, pipeline dừng ngay, không lãng phí phút chạy integration test.

Ví dụ GitHub Actions

GitHub Actions định nghĩa pipeline bằng file YAML đặt trong thư mục .github/workflows/. Hãy mổ xẻ cấu trúc cốt lõi: on (trigger), jobs (các job), và trong mỗi job là steps.

Workflow CI cơ bản: build & test

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

# Trigger: chạy khi push lên main/develop hoặc mở PR vào main
on:
  push:
    branches: [main, develop]
  pull_request:
    branches: [main]

jobs:
  test:
    name: Lint & Test
    runs-on: ubuntu-latest

    # Service container: DB tạm cho integration test
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: testpass
          POSTGRES_DB: appdb_test
        ports:
          - 5432:5432
        options: >-
          --health-cmd pg_isready
          --health-interval 10s
          --health-timeout 5s
          --health-retries 5

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

      - name: Setup Python
        uses: actions/setup-python@v5
        with:
          python-version: "3.12"
          cache: pip

      - name: Install dependencies
        run: pip install -r requirements.txt

      - name: Lint (ruff)
        run: ruff check .

      - name: Run tests
        env:
          DATABASE_URL: postgresql://postgres:testpass@localhost:5432/appdb_test
        run: pytest --cov=app --cov-report=xml

      - name: Upload coverage artifact
        uses: actions/upload-artifact@v4
        with:
          name: coverage-report
          path: coverage.xml

Đọc từ trên xuống:

  • on khai báo trigger. Workflow chạy khi có push vào main/develop, hoặc khi có PR nhắm vào main.
  • jobs.test là một job chạy trên runner ubuntu-latest.
  • services.postgres dựng một container Postgres tạm cho job — chính là cách chạy integration test với DB thật mà không cần cài đặt gì sẵn.
  • Mỗi mục trong steps là một step. uses gọi một action có sẵn (như checkout), run chạy lệnh shell.
  • Step cuối lưu file coverage thành artifact để xem lại hoặc đẩy sang công cụ khác.

Build & push Docker image

Sau khi test xanh, ta build image và đẩy lên registry. Job này dùng needs: test để chỉ chạy khi job test thành công, và if để chỉ build trên nhánh main.

  build-and-push:
    name: Build & Push Docker Image
    runs-on: ubuntu-latest
    needs: test                                  # chỉ chạy sau khi 'test' xanh
    if: github.ref == 'refs/heads/main'          # chỉ build trên main

    permissions:
      contents: read
      packages: write                            # quyền push lên GHCR

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

      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3

      - name: Log in to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ghcr.io
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}  # secret tự cấp bởi GitHub

      - name: Build and push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ghcr.io/${{ github.repository }}:latest
            ghcr.io/${{ github.repository }}:${{ github.sha }}
          cache-from: type=gha
          cache-to: type=gha,mode=max

Lưu ý hai tag: latest (luôn trỏ tới bản mới nhất) và ${{ github.sha }} (tag bất biến theo commit). Tag theo SHA rất quan trọng cho rollback — bạn luôn deploy lại được đúng image của một commit cụ thể.

Biến môi trường & secrets

Pipeline cần dữ liệu cấu hình: URL database, API key, mật khẩu registry. Có hai loại, phải phân biệt rõ:

  • Biến môi trường thường (env): giá trị không nhạy cảm, có thể đọc trong log. Ví dụ NODE_ENV: production, LOG_LEVEL: info.
  • Secrets: giá trị nhạy cảm — token, mật khẩu, private key. Không bao giờ hardcode vào file YAML hay commit vào git.

Trong GitHub Actions, secrets được lưu ở Settings → Secrets and variables → Actions của repo (hoặc cấp organization/environment), và truy cập qua ${{ secrets.TÊN_SECRET }}. GitHub tự động che (mask) giá trị secret trong log nếu nó vô tình bị in ra.

      - name: Deploy
        env:
          DEPLOY_HOST: ${{ secrets.DEPLOY_HOST }}
          DEPLOY_KEY: ${{ secrets.DEPLOY_SSH_KEY }}
          DB_PASSWORD: ${{ secrets.PROD_DB_PASSWORD }}
        run: ./scripts/deploy.sh

Quy tắc an toàn:

  1. Không in secret ra log. Tránh echo $DB_PASSWORD.
  2. Phạm vi tối thiểu. Dùng GitHub Environments để giới hạn secret production chỉ cho job deploy production.
  3. Xoay vòng định kỳ. Coi mọi secret từng lộ là đã bị xâm phạm; rotate ngay.
  4. permissions tối thiểu cho GITHUB_TOKEN. Chỉ cấp đúng quyền job cần (như ví dụ trên chỉ cấp packages: write).

Chiến lược triển khai

Khi đã có image sẵn sàng, làm sao đưa nó lên production mà không gây downtime hay rủi ro? Có ba chiến lược phổ biến.

Rolling deployment

Thay thế dần các instance cũ bằng instance mới, từng nhóm nhỏ một. Trong khi instance mới khởi động, instance cũ vẫn phục vụ. Đây là mặc định của Kubernetes (RollingUpdate).

  • Ưu: không downtime, không cần gấp đôi tài nguyên.
  • Nhược: trong lúc rollout, cả phiên bản cũ và mới cùng chạy → phải tương thích ngược (đặc biệt với schema DB). Rollback chậm hơn.

Blue-Green deployment

Duy trì hai môi trường giống hệt nhau: Blue (đang phục vụ) và Green (phiên bản mới). Deploy lên Green, test kỹ, rồi chuyển toàn bộ traffic từ Blue sang Green chỉ bằng việc đổi load balancer.

  • Ưu: chuyển đổi tức thì, rollback chỉ là trỏ lại Blue → cực nhanh.
  • Nhược: cần gấp đôi tài nguyên trong lúc chuyển; toàn bộ traffic chuyển một lần nên nếu có lỗi thì 100% user dính.

Canary deployment

Đẩy phiên bản mới cho một tỷ lệ nhỏ traffic trước (ví dụ 5%), theo dõi metrics (lỗi, latency). Nếu ổn, tăng dần lên 25%, 50%, 100%. Nếu xấu, cắt ngay phần canary.

  • Ưu: giới hạn ảnh hưởng của lỗi xuống nhóm nhỏ user; phát hiện vấn đề thực tế trước khi rollout toàn bộ.
  • Nhược: cần hạ tầng định tuyến traffic theo tỷ lệ và giám sát mạnh; phức tạp nhất.
Chiến lượcDowntimeTài nguyên cầnTốc độ rollbackMức rủi roĐộ phức tạp
RollingKhông~1xChậm (rollout ngược)Trung bìnhThấp
Blue-GreenKhông~2xTức thì (đổi LB)Cao (all-or-nothing)Trung bình
CanaryKhông1x + chút dưNhanh (cắt canary)Thấp nhấtCao

Rollback

Rollback là khả năng quay về phiên bản tốt đã biết khi bản mới gặp sự cố. Đây là phần bị xem nhẹ nhiều nhất nhưng quan trọng nhất.

Nguyên tắc cốt lõi: vì ta tag image theo commit SHA (image bất biến), rollback chỉ đơn giản là deploy lại tag cũ:

# Lăn ngược về image của commit trước
docker compose pull app
docker compose up -d --no-deps app   # với tag = SHA cũ trong .env

# Hoặc trên Kubernetes
kubectl rollout undo deployment/app

Cạm bẫy lớn nhất là database migration không tương thích ngược. Nếu bản mới đổi schema theo cách phá vỡ code cũ, rollback code thôi là chưa đủ. Giải pháp: thiết kế migration theo kiểu expand/contract — thêm cột mới trước (expand), deploy code dùng cột mới, rồi mới xoá cột cũ ở release sau (contract), để mọi bước đều tương thích cả hai chiều.

Pipeline as Code

Toàn bộ định nghĩa pipeline sống trong repo dưới dạng file (YAML), được version control cùng source code. Lợi ích:

  • Lịch sử & review: thay đổi pipeline đi qua pull request, được review như mọi code khác.
  • Tái lập: checkout commit cũ là có đúng pipeline tại thời điểm đó.
  • Không "click-ops": không còn cấu hình thủ công trên UI mà không ai biết.

Đây là một biểu hiện của tư duy Infrastructure as Code, áp dụng cho chính quy trình build/deploy.

GitLab CI — một lựa chọn tương đương

GitLab CI dùng file .gitlab-ci.yml ở gốc repo, với khái niệm tương đương: stages (giai đoạn), mỗi job khai báo thuộc stage nào và có script. So với GitHub Actions, GitLab CI có khái niệm stages tường minh hơn:

# .gitlab-ci.yml
stages:
  - test
  - build

run-tests:
  stage: test
  image: python:3.12
  script:
    - pip install -r requirements.txt
    - pytest

build-image:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  only:
    - main

Khái niệm cốt lõi giống hệt — chỉ khác cú pháp. Học một công cụ giúp bạn nhanh chóng nắm bắt công cụ kia.

Tóm tắt

CI/CD biến chuỗi thao tác build–test–deploy thủ công, dễ sai, thành một pipeline tự động và lặp lại được. CI đảm bảo code luôn integrate và test xanh; Continuous Delivery đóng gói sẵn sàng release nhưng cần người duyệt để lên production; Continuous Deployment tự động hoá luôn cả bước đó. Một pipeline gồm trigger, stage, job, step và artifact, với nhiều lớp kiểm thử xếp theo chi phí (lint → unit → integration → security scan). GitHub Actions định nghĩa pipeline bằng YAML trong .github/workflows/, dùng on cho trigger, jobs/steps cho công việc, needs cho phụ thuộc, và secrets cho dữ liệu nhạy cảm. Khi triển khai, chọn giữa rolling (đơn giản, không downtime), blue-green (rollback tức thì, tốn gấp đôi tài nguyên) và canary (rủi ro thấp nhất nhưng phức tạp nhất); và luôn chuẩn bị đường rollback dựa trên image tag bất biến cùng migration tương thích ngược.

Tự kiểm tra

  1. Phân biệt Continuous Delivery và Continuous Deployment — điểm khác biệt then chốt nằm ở bước nào?
  2. Trong GitHub Actions, làm sao để một job chỉ chạy sau khi job khác thành công, và chỉ chạy trên nhánh main?
  3. Vì sao nên đặt lint và unit test trước integration test trong pipeline?
  4. Khác biệt giữa biến môi trường thường (env) và secrets là gì, và ba quy tắc an toàn khi dùng secrets?
  5. So sánh blue-green và canary về tốc độ rollback và mức rủi ro khi bản mới có lỗi.
  6. Vì sao tag Docker image theo commit SHA lại quan trọng cho rollback, và migration kiểu expand/contract giải quyết vấn đề gì?

Đọc tiếp

DevOps 5 — Kubernetes

Bài viết liên quan

Container vs máy ảo, image/layer, Dockerfile, volume, network, Docker Compose và best practices.

13 thg 7, 2026 3

Vì sao cần orchestration; Pod, Deployment, Service, Ingress; scaling, self-healing và cấu hình.

13 thg 7, 2026 3

Ba trụ cột logging–metrics–tracing, Prometheus/Grafana, alerting, SLI/SLO/SLA và golden signals.

13 thg 7, 2026 3

Bản chất DevOps: phá bỏ rào cản Dev–Ops, vòng lặp vô tận, CALMS, và vì sao tự động hoá.

13 thg 7, 2026 3