GitLab CI/CD nâng cao: Tối ưu hóa Pipelines và chiến lược triển khai liên tục

Development tutorial - IT technology blog
Development tutorial - IT technology blog

Pipeline chậm, deploy sợ hãi — chuyện không của riêng ai

Mình từng làm trong một dự án web app 5 người. Pipeline GitLab CI/CD chạy mất 18–22 phút mỗi lần push code. Ai cũng ngại merge, ngại deploy — sợ chờ lâu rồi lại fail ở bước cuối. Feature branch tồn tại cả tuần, merge conflict chồng chất, release mỗi sprint là cơn ác mộng.

Vấn đề không nằm ở GitLab. Nằm ở cách cấu hình pipeline. Sau khi tái cấu trúc .gitlab-ci.yml và áp dụng đúng chiến lược, pipeline còn 6–7 phút. Team merge hàng ngày thay vì gom lại cuối sprint. Bài này mình chia sẻ lại những gì đã làm.

Hiểu đúng về cách GitLab CI/CD thực sự hoạt động

Tối ưu sai chỗ còn tệ hơn không tối ưu. Nắm vững mô hình thực thi của GitLab CI/CD trước khi đụng vào config.

Stage vs Job vs Pipeline

GitLab chạy pipeline theo stages (giai đoạn). Các job trong cùng stage chạy song song — các stage chạy tuần tự. Nhớ điều này, vì nó quyết định cách bạn chia job và thứ tự thực thi.

stages:
  - build
  - test
  - deploy

build-image:
  stage: build
  script:
    - docker build -t myapp:$CI_COMMIT_SHA .

test-unit:
  stage: test
  script:
    - pytest tests/unit/

test-integration:
  stage: test   # chạy song song với test-unit
  script:
    - pytest tests/integration/

DAG — Directed Acyclic Graph

GitLab 12.2 thêm needs: để tạo DAG pipeline. Thay vì chờ cả stage xong, job chạy ngay khi dependency hoàn thành — bỏ qua stage barrier hoàn toàn.

stages:
  - build
  - test
  - deploy

build-backend:
  stage: build
  script: make build-backend

build-frontend:
  stage: build
  script: make build-frontend

test-backend:
  stage: test
  needs: [build-backend]   # không cần chờ build-frontend xong
  script: make test-backend

test-frontend:
  stage: test
  needs: [build-frontend]  # chạy ngay khi build-frontend done
  script: make test-frontend

deploy-staging:
  stage: deploy
  needs: [test-backend, test-frontend]
  script: make deploy-staging

Với cấu trúc này, test-backendtest-frontend không còn phải chờ nhau — tiết kiệm được vài phút ngay lập tức.

Thực hành tối ưu pipeline từ A đến Z

1. Cache thông minh — đừng cài lại dependencies mỗi lần

Nguyên nhân số một khiến pipeline chậm: không cache dependencies. Mỗi lần job chạy, runner tải lại toàn bộ node_modules hoặc Python packages từ đầu — dễ ngốn 3–5 phút chỉ để cài thứ hôm qua đã cài xong.

variables:
  PIP_CACHE_DIR: "$CI_PROJECT_DIR/.cache/pip"

cache:
  key:
    files:
      - requirements.txt     # cache key thay đổi khi file này thay đổi
  paths:
    - .cache/pip
    - venv/

test:
  stage: test
  before_script:
    - python -m venv venv
    - source venv/bin/activate
    - pip install -r requirements.txt
  script:
    - pytest

Với Node.js, dùng package-lock.json làm cache key:

cache:
  key:
    files:
      - package-lock.json
  paths:
    - node_modules/

2. Parallel matrix — chạy test song song nhiều môi trường

Thay vì chạy test tuần tự qua từng Python version, dùng parallel:matrix:

test:
  stage: test
  image: python:$PYTHON_VERSION
  parallel:
    matrix:
      - PYTHON_VERSION: ["3.10", "3.11", "3.12"]
  script:
    - pip install -r requirements.txt
    - pytest --tb=short

3 job chạy song song thay vì tuần tự — tổng thời gian giảm xuống bằng thời gian của 1 job.

3. Rules thay vì only/except — kiểm soát chính xác khi nào job chạy

only/except đã deprecated. Dùng rules: cho logic rõ ràng hơn:

deploy-production:
  stage: deploy
  script:
    - make deploy-prod
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH   # chỉ chạy trên main
      when: manual                                   # cần bấm nút thủ công
    - if: $CI_PIPELINE_SOURCE == "merge_request_event"
      when: never                                    # không chạy trong MR pipeline

4. Artifacts — truyền file giữa các stage

Build một lần, dùng nhiều nơi. Đừng build lại image hay binary ở mỗi stage:

build:
  stage: build
  script:
    - make build
    - echo $CI_COMMIT_SHA > build_info.txt
  artifacts:
    paths:
      - dist/
      - build_info.txt
    expire_in: 1 hour   # tự xóa sau 1 giờ, tránh tốn storage

deploy:
  stage: deploy
  needs:
    - job: build
      artifacts: true   # tải artifacts từ job build
  script:
    - cat build_info.txt
    - rsync -av dist/ user@server:/var/www/app/

Chiến lược triển khai liên tục thực chiến

Blue-Green Deployment

Ý tưởng: duy trì 2 môi trường production (Blue đang chạy, Green để deploy mới). Sau khi Green ổn định, chuyển traffic sang Green — không downtime.

deploy-green:
  stage: deploy
  script:
    - docker pull myapp:$CI_COMMIT_SHA
    - docker stop myapp-green || true
    - docker run -d --name myapp-green -p 8081:8080 myapp:$CI_COMMIT_SHA
    - sleep 10
    - curl -f http://localhost:8081/health || (docker stop myapp-green && exit 1)
  environment:
    name: production-green

switch-traffic:
  stage: switch
  needs: [deploy-green]
  when: manual
  script:
    - nginx -s reload   # hoặc cập nhật load balancer
    - docker stop myapp-blue || true
    - docker rename myapp-green myapp-blue
  environment:
    name: production

Canary Deployment

Canary deployment không đẩy 100% traffic vào phiên bản mới ngay. Chỉ 5–10% người dùng nhận bản mới trước — theo dõi error rate và latency vài giờ. Nếu metrics ổn mới rollout toàn bộ.

deploy-canary:
  stage: deploy
  script:
    - kubectl set image deployment/myapp-canary app=myapp:$CI_COMMIT_SHA
    - kubectl scale deployment/myapp-canary --replicas=1  # 1/10 pods = 10% traffic
  environment:
    name: production/canary
  rules:
    - if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH

promotion-canary:
  stage: promote
  needs: [deploy-canary]
  when: manual
  script:
    # Sau khi verify metrics, rollout toàn bộ
    - kubectl set image deployment/myapp app=myapp:$CI_COMMIT_SHA
    - kubectl scale deployment/myapp-canary --replicas=0
  environment:
    name: production

Environment variables và Secrets quản lý đúng cách

Đừng bao giờ hardcode credentials trong .gitlab-ci.yml. Dùng GitLab CI/CD Variables (Settings → CI/CD → Variables):

deploy:
  script:
    - echo "$DEPLOY_KEY" > /tmp/deploy_key
    - chmod 600 /tmp/deploy_key
    - ssh -i /tmp/deploy_key user@$PROD_SERVER "cd /app && ./deploy.sh"
    - rm /tmp/deploy_key

Với credentials nhạy cảm: bật Protected (chỉ accessible trên protected branches) và Masked (ẩn khỏi log). Hai tick này tốn 5 giây bật nhưng có thể tránh cho bạn nhiều sự cố đau đầu.

Pipeline as Code — tách file CI cho dự án lớn

Khi .gitlab-ci.yml phình to quá 300 dòng, dùng include: để tách thành nhiều file:

# .gitlab-ci.yml
include:
  - local: .gitlab/ci/build.yml
  - local: .gitlab/ci/test.yml
  - local: .gitlab/ci/deploy.yml
  - project: 'myorg/ci-templates'  # dùng template từ repo khác
    ref: main
    file: '/templates/docker.yml'

stages:
  - build
  - test
  - deploy

Kết quả sau khi áp dụng

Quay lại dự án mình nhắc ở đầu. Ba thứ mang lại 80% hiệu quả: DAG với needs:, cache dependencies, và parallel test matrix. Bỏ thêm các job không cần thiết trên feature branch — pipeline từ 20 phút xuống còn 6 phút. Team merge hàng ngày, feature branch hiếm khi sống quá 2 ngày.

Cái thay đổi rõ nhất không phải thời gian — mà là thái độ của team với deploy. Blue-Green cho phép rollback trong 30 giây nếu có lỗi. Khi cái giá của “deploy sai” chỉ còn là 30 giây xử lý, team mạnh dạn release hơn hẳn — từ 2 tuần một lần lên vài lần mỗi tuần.

Pipeline tốt không phải pipeline phức tạp — mà là pipeline chạy nhanh, ít fail, và ai cũng đọc được. Bắt đầu từ cache và parallel jobs. Khi team đã quen thì mới nghĩ đến Blue-Green hay Canary.

Share: