Một tình huống khá phổ biến: code nằm trên Bitbucket, nơi đội lập trình làm việc với branch và pull request, còn các pipeline vận hành cluster Kubernetes (tạo site bằng Helm, thao tác với máy NFS, database) lại chạy trên GitLab CI với runner tự host trong mạng private. Mỗi lần code được push lên Bitbucket, GitLab phải build image và cập nhật các site. Tài liệu này mô tả mô hình cầu nối đơn giản nhất: Bitbucket Pipelines gọi API của GitLab để tạo pipeline, rồi định kỳ hỏi lại trạng thái cho tới khi xong. Bài trình bày luồng chạy, file cấu hình mẫu, các lỗi hay gặp, cách ép rollout khi image giữ tag cố định, ưu nhược và các hướng thay thế.
1. Khi nào cần cầu nối sang GitLab CI
Ở bài CI/CD đầu tiên với Bitbucket Pipelines, Docker Hub và kubectl, mọi việc chạy trong Bitbucket: build, đẩy image, kubectl apply. Khi mỗi site trở thành một release Helm như bài Helm chart là gì: đóng gói website Laravel để deploy lặp lại, pipeline vận hành thường cần những thứ mà runner dùng chung của Bitbucket không có:
- Runner tự host (ví dụ GitLab Runner đã dùng từ bài Deploy tự động đầu tiên: GitLab Runner, SSH và nginx template) nằm trong mạng private, có executor
shellđể SSH vào máy NFS, máy database. - Kubeconfig, khoá SSH và quy trình cập nhật repo values đã cấu hình sẵn ở phía GitLab CI.
- Đội vận hành quản lý quyền truy cập hạ tầng ở một nơi duy nhất.
Chuyển code sang GitLab thì ảnh hưởng thói quen của cả đội lập trình; chuyển pipeline vận hành về Bitbucket thì mất runner nội bộ. Phương án ít xáo trộn nhất là để Bitbucket chỉ làm một việc: báo cho GitLab biết có commit mới.
2. Luồng chạy từ push tới pod mới
Dev push / merge PR ──► Bitbucket (repo ứng dụng)
│ bitbucket-pipelines.yml
│ POST /api/v4/projects/<id>/pipeline
│ ref=main, BITBUCKET_BRANCH, BITBUCKET_COMMIT_ID
▼
GitLab CI (project build, runner tự host)
│ checkout đúng commit, docker build
│ docker push <registry>/app:<tag>
│ ghi mã commit vào values của từng site, commit repo values
│ helm upgrade lên cluster
▼
Cluster: pod mới kéo image và chạy code mới
Trong lúc đó, step Bitbucket định kỳ gọi
GET /api/v4/projects/<id>/pipelines/<pipeline_id> để lấy trạng tháiMỗi lần deploy, pipeline GitLab CI tạo một commit nhỏ trong repo values (chỉ đổi giá trị commit của site). Lịch sử repo values vì thế trở thành nhật ký deploy: site nào chạy commit nào, đổi lúc nào.
3. API tạo pipeline của GitLab CI
GitLab có hai cách để hệ thống bên ngoài khởi động một pipeline GitLab CI:
| Create pipeline | Pipeline trigger | |
|---|---|---|
| Endpoint | POST /projects/:id/pipeline | POST /projects/:id/trigger/pipeline |
| Xác thực | Access token (header PRIVATE-TOKEN) có quyền api | Trigger token tạo riêng cho project, truyền qua tham số token |
| Truyền biến | Mảng JSON variables: [{key, value}] | variables[KEY]=value |
| Phạm vi quyền | Rộng: dùng được cho mọi API mà người sở hữu token có quyền | Hẹp: chỉ kích hoạt pipeline của đúng project |
Cả hai trả về JSON có id của pipeline vừa tạo, dùng để hỏi trạng thái qua GET /projects/:id/pipelines/:pipeline_id. Trạng thái cuối là success, failed hoặc canceled; trạng thái trung gian là created, pending, running... Chi tiết ở trang Pipelines API của GitLab và kích hoạt pipeline bằng API.
4. File bitbucket-pipelines.yml gọi GitLab CI
Khuôn mẫu dùng trigger token (phạm vi hẹp) và jq để đọc JSON. Token, project ID và URL GitLab nằm trong repository variables đánh dấu secured, không viết vào file:
image: atlassian/default-image:4
pipelines:
branches:
main:
- step:
name: Trigger GitLab CI
script:
- apt-get update -qq && apt-get install -y -qq jq
- |
RESP=$(curl -sS --fail -X POST \
-F "token=$GITLAB_TRIGGER_TOKEN" -F "ref=main" \
-F "variables[BITBUCKET_BRANCH]=$BITBUCKET_BRANCH" \
-F "variables[BITBUCKET_COMMIT_ID]=$BITBUCKET_COMMIT" \
"$GITLAB_URL/api/v4/projects/$GITLAB_PROJECT_ID/trigger/pipeline")
PIPELINE_ID=$(echo "$RESP" | jq -r '.id')
WEB_URL=$(echo "$RESP" | jq -r '.web_url')
[ "$PIPELINE_ID" != "null" ] || { echo "Không tạo được pipeline"; exit 1; }
echo "Pipeline GitLab CI: $WEB_URL"
for i in $(seq 1 80); do # tối đa khoảng 20 phút
sleep 15
STATUS=$(curl -sS --header "PRIVATE-TOKEN: $GITLAB_READ_TOKEN" \
"$GITLAB_URL/api/v4/projects/$GITLAB_PROJECT_ID/pipelines/$PIPELINE_ID" | jq -r '.status')
echo "Trạng thái: $STATUS"
case "$STATUS" in
success) exit 0 ;;
failed|canceled|skipped) echo "Xem lỗi tại $WEB_URL"; exit 1 ;;
esac
done
echo "Hết thời gian chờ, xem $WEB_URL"; exit 1Trigger token chỉ tạo được pipeline; để đọc trạng thái, dùng một token riêng chỉ có quyền read_api. Step Bitbucket chỉ thành công khi pipeline GitLab CI thành công, nên người merge pull request vẫn thấy kết quả deploy ngay trên Bitbucket.
5. Lỗi thường gặp khi gọi GitLab CI từ Bitbucket
- Dấu hai chấm trong lệnh echo.
- echo "Current branch: $BITBUCKET_BRANCH"khiến YAML hiểu cả dòng là cặp key/value vì có chuỗi ":". Bỏ dấu hai chấm, bọc lệnh trong nháy đơn, hoặc dùng khối|như mẫu trên. - Sai cặp endpoint và token.
/trigger/pipelineđòi trigger token, không nhận access token quaPRIVATE-TOKEN;/pipelinethì ngược lại. Dùng sai cặp là luôn bị từ chối. - Sai định dạng biến. Với
/pipeline, đặt"BITBUCKET_BRANCH": "..."ngang hàng vớirefthì GitLab bỏ qua: pipeline vẫn chạy nhưng không nhận được commit cần build. Biến phải nằm trong mảngvariablesvới cặpkey/value. - Biến nằm trong nháy đơn.
'{"value":"$BITBUCKET_COMMIT"}'gửi đi nguyên chuỗi$BITBUCKET_COMMIT. Phải đóng nháy đơn, mở nháy kép quanh biến rồi mở lại:'"$BITBUCKET_COMMIT"'. Cách phát hiện nhanh nhất làechopayload ngay trướccurl. - Script nhiều dòng bị gộp thành một dòng. Vòng
whileviết thành nhiều mục trong YAML có thể bị nối lại bằng dấu cách; thiếu;là shell hiểu sai cả khối. Dùng khối|để giữ nguyên xuống dòng. - Bóc JSON bằng grep.
grep -o '"id":[0-9]*'có thể bắt nhầmidlồng bên trong (ví dụ id của user tạo pipeline). Dùngjq. - In token ra log để debug. Bitbucket che giá trị biến secured trong log, nhưng đừng dựa vào điều đó.
- Thăm dò quá dày. Hỏi mỗi vài giây không làm build xong sớm hơn; khoảng 15–30 giây là đủ.
6. Ép rollout khi tag image không đổi
Nếu image luôn được đẩy với một tag cố định, chỉ build và push thì Kubernetes không biết có gì thay đổi. Chart có thể giải quyết bằng một label trong pod template, lấy giá trị từ values:
template:
metadata:
labels:
{{- include "web.selectorLabels" . | nindent 8 }}
commit: {{ .Values.commitId | quote }}Pipeline GitLab CI ghi mã commit Bitbucket vào giá trị này cho từng site rồi helm upgrade. Pod template đổi, Deployment rolling update, pod mới với imagePullPolicy: Always kéo lại image cùng tag và nhận code mới.
| Được | Mất |
|---|---|
| Không phải đổi tag trong values của hàng loạt site | Không biết pod đang chạy image nào nếu chỉ nhìn tag |
Label commit cho biết site đang ở commit nào | Rollback về commit cũ không lấy lại được image cũ, vì tag đã bị ghi đè |
| Một lần build dùng cho mọi site cùng loại | Pod nào khởi động lại cũng phải hỏi registry về image, kể cả khi không có code mới |
Khi số site tăng, nên chuyển sang tag theo mã commit và ghi tag vào values; label commit khi đó chỉ còn vai trò thông tin.
7. Ưu và nhược của cầu nối API cộng polling
| Ưu điểm | Nhược điểm |
|---|---|
Đơn giản: một lệnh curl, không cần thêm dịch vụ | Step Bitbucket ngồi chờ suốt thời gian GitLab CI chạy, tốn phút build của Bitbucket |
| Trạng thái deploy hiện ngay trên Bitbucket | Chỉ thấy đậu hay rớt; muốn xem lỗi chi tiết vẫn phải mở GitLab CI |
| Không phải chuyển code sang nền tảng khác | Nếu dùng access token quyền api, lộ token là lộ quyền với cả tài khoản GitLab |
| Phía GitLab CI giữ toàn quyền với runner nội bộ | Thiếu timeout thì một pipeline kẹt pending giữ step tới giới hạn của Bitbucket |
8. Những hướng thay thế
- Trigger token thay access token, như mẫu ở mục 4: lộ ra thì thiệt hại nhỏ hơn nhiều. Đây là thay đổi rẻ nhất nên làm trước.
- Webhook của Bitbucket gọi thẳng URL trigger. GitLab CI nhận webhook và đưa payload vào biến
TRIGGER_PAYLOAD. Bitbucket không phải chạy step chờ; đổi lại, muốn trạng thái hiện về Bitbucket thì pipeline GitLab CI phải tự gọi API build status của Bitbucket ở bước cuối. - Mirror repo từ Bitbucket sang GitLab. Tính năng pull mirroring (gói trả phí) cho phép mỗi lần mirror cập nhật tự chạy pipeline mà không cần cầu nối.
- Multi-project pipeline, nếu code cũng nằm trên GitLab:
trigger: projectvớistrategy: dependcho pipeline cha chờ và nhận trạng thái pipeline con một cách tự nhiên.
9. Checklist cho cầu nối Bitbucket và GitLab CI
- In payload JSON ra trước khi gửi; lỗi thường nằm ở nháy và dấu hai chấm hơn là ở API.
- Đúng cặp endpoint và loại token; token phạm vi hẹp nhất có thể.
- Vòng chờ có số lần tối đa, in đường dẫn tới pipeline GitLab CI khi thất bại.
- Đọc JSON bằng
jq, không bằng grep. - Biết rõ chiến lược tag image ảnh hưởng thế nào tới rollback.
- Đối xử với cầu nối tạm như hệ thống thật: có chủ sở hữu, có tài liệu, có kế hoạch thay thế.
Bài tiếp theo
Mỗi branch một pipeline vận hành: tự động hoá bằng GitLab CI. Mỗi thao tác vận hành là một branch trong cùng một repo: luồng tạo site bằng Helm và cách pipeline tự dọn dẹp khi deploy thất bại.
