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-backend và test-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.
