Khi ứng dụng đã chạy trên Kubernetes, quy trình làm tay "build image trên máy cá nhân, đẩy lên Docker Hub, sửa manifest rồi kubectl apply" sớm trở thành nút thắt. Nếu mã nguồn nằm trên Bitbucket, lựa chọn tự nhiên cho CI/CD là Bitbucket Pipelines. Tài liệu này hướng dẫn dựng một pipeline build, push, deploy lên cluster: cấu trúc file bitbucket-pipelines.yml, cách thay image trong manifest, cách ép Kubernetes kéo image mới, mô hình tách cấu hình Kubernetes ra repo riêng, và các lỗi hay gặp.
1. Bitbucket Pipelines hoạt động thế nào
Bitbucket Pipelines là dịch vụ CI/CD tích hợp sẵn trong Bitbucket Cloud. Đặt file bitbucket-pipelines.yml ở gốc repo là mỗi lần push, Bitbucket chạy pipeline tương ứng. Các khái niệm cần nắm:
- pipeline: tập các bước chạy khi có sự kiện, ví dụ push lên một branch (
branches:) hoặc mặc định cho mọi branch (default:). - step: một bước, chạy trong một container riêng, với repo được clone sẵn vào
$BITBUCKET_CLONE_DIR. Hai step khác nhau không chia sẻ ổ đĩa hay biến môi trường. - stage: gom nhiều step thành một nhóm logic, ví dụ "Build" và "Deploy".
- services: docker: bật Docker daemon cho step để chạy được
docker build. - artifacts: file sinh ra ở step trước, muốn dùng ở step sau thì phải khai báo ở đây.
- pipe: một bước đóng gói sẵn, gọi bằng tên và truyền biến, ví dụ
atlassian/kubectl-run. - repository variables: biến bí mật (mật khẩu registry, kubeconfig) cấu hình trên giao diện, đánh dấu secured để không hiện ra log.
Cú pháp đầy đủ có trong tài liệu cấu hình bitbucket-pipelines.yml của Atlassian.
2. Khuôn mẫu Bitbucket Pipelines tối thiểu: build, push, apply
Ý tưởng của Bitbucket Pipelines bản tối thiểu rất thẳng: step 1 build image và đẩy lên registry, step 2 thay tên image trong manifest rồi gọi pipe kubectl-run để apply.
image: atlassian/default-image:4
pipelines:
branches:
main:
- step:
name: Build
services: [docker]
script:
- export TAG=$BITBUCKET_COMMIT
- docker build -t $IMAGE_REPO:$TAG .
- echo "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin
- docker push $IMAGE_REPO:$TAG
- step:
name: Deploy
deployment: production
script:
- sed -i "s|image:.*|image: $IMAGE_REPO:$BITBUCKET_COMMIT|" .k8s/app-deployment.yaml
- pipe: atlassian/kubectl-run:1.3.3
variables:
KUBE_CONFIG: $KUBE_CONFIG
KUBECTL_COMMAND: 'apply'
RESOURCE_PATH: '.k8s/app-deployment.yaml'IMAGE_REPO, REGISTRY_USER, REGISTRY_PASSWORD và KUBE_CONFIG (kubeconfig mã hoá base64, theo yêu cầu của pipe) là repository variables. Trong Bitbucket Pipelines, pipe atlassian/kubectl-run lo phần cài kubectl và nạp kubeconfig, nên step Deploy không cần image riêng. Dùng --password-stdin thay cho --password để mật khẩu không nằm trong tham số dòng lệnh.
Lỗi đầu tiên hay gặp với Bitbucket Pipelines là YAML sai thụt lề: name: nằm thẳng hàng với - step: thay vì thụt vào trong, hoặc chuỗi thiếu dấu nháy đóng. Bitbucket Pipelines báo lỗi cấu hình ngay khi push. Hãy kiểm tra YAML trước khi push bằng trình validator của Bitbucket hoặc một công cụ lint YAML.
3. Thay image trong manifest: các lỗi sed hay gặp
Dòng sed thay image trông đơn giản nhưng là nơi tốn nhiều lần push nhất, vì mỗi lần sai là một lần chờ Bitbucket Pipelines chạy và đọc log. Các lỗi điển hình:
| Lệnh | Vì sao sai |
|---|---|
sed -i "s|{{image}}|$IMAGE|g" | Manifest không có placeholder {{image}}, sed chạy xong mà không đổi gì, cũng không báo lỗi |
sed -i "s/^image:.*/image:$IMAGE/g" | ^image: chỉ khớp dòng bắt đầu bằng image:, trong khi dòng thật thụt lề trong containers. Tên image chứa / trùng ký tự phân cách |
sed -i 's/.../image:$IMAGE/g' | Trong dấu nháy đơn, shell không thay biến: file nhận nguyên chuỗi $IMAGE |
sed -i 's/^\ +image:.*/.../g' | Ở regex cơ bản của sed, + là ký tự thường chứ không phải "một hoặc nhiều" |
sed -i 's|^\ *image:.*|image: ...|g' | Khớp được, nhưng phần bị thay gồm cả khoảng trắng đầu dòng: dòng mới mất thụt lề và YAML hỏng |
sed -i "s|image:.*|image: $IMAGE|" | Đúng: ký tự phân cách |, nháy kép để shell thay biến, không đụng tới thụt lề |
Cả chuỗi lỗi này tránh được bằng cách chạy thử lệnh sed trên máy cá nhân với một bản sao manifest trước khi đưa vào Bitbucket Pipelines. Vòng phản hồi vài giây trên máy cá nhân rẻ hơn nhiều so với vài phút mỗi lần chạy pipeline. Về lâu dài, nên thay sed bằng công cụ hiểu YAML như kubectl set image, kustomize edit set image hoặc Helm.
4. Tag không đổi thì làm sao để pod kéo image mới
Deployment chỉ rollout khi pod template thay đổi. Nếu pipeline luôn đẩy image với cùng một tag (ví dụ latest), manifest sau khi sed giống hệt lần trước, kubectl apply báo unchanged và không pod nào được tạo lại. Có hai cách xử lý:
- Tag theo mã commit (khuôn mẫu ở mục 2): mỗi lần build là một tag mới, pod template đổi, rollout diễn ra tự nhiên. Nhìn tag là biết pod chạy bản nào, rollback chỉ cần trỏ lại tag cũ. Đây là cách nên dùng.
- Giữ tag cố định, đổi một label trong pod template: mỗi lần chạy, sed giá trị label thành
$BITBUCKET_COMMIT, kết hợpimagePullPolicy: Alwaysđể kubelet kéo lại image cùng tag.
# cách 2: label phải nằm trong pod template, không phải metadata của Deployment
spec:
template:
metadata:
labels:
app: web
commit: <sha-cua-commit> # đổi mỗi lần deploy để ép rollout
# trong pipeline
- sed -i "s|commit:.*|commit: $BITBUCKET_COMMIT|" .k8s/app-deployment.yamlLỗi hay gặp với cách 2 là đặt label ở metadata.labels của chính Deployment: đổi label đó không đụng tới pod template nên không có gì xảy ra. Cách 2 cũng có giá: không biết pod đang chạy image nào chỉ bằng cách nhìn tag, và muốn quay về bản cũ thì phải build lại. Cơ chế rollout được mô tả trong tài liệu Deployment của Kubernetes.
5. Tự động hay duyệt tay trong Bitbucket Pipelines
Yêu cầu hay gặp với Bitbucket Pipelines: push thì chỉ build, còn deploy chỉ chạy khi có người duyệt. Ba hướng tự chế thường không hiệu quả:
- Lệnh
iftrong script dựa vào biến cho biết ai kích hoạt pipeline: không có biến nào phân biệt push với bấm tay một cách đáng tin. - Khối
conditioncủa step: chỉ lọc theo danh sách file thay đổi (changesets), không nhận biểu thức kiểu$AUTO_DEPLOY == "1". - Step trước đặt cờ, step sau đọc cờ:
exportở step trước không sang được step sau vì mỗi step là một container mới. Phải ghi cờ ra file và khai báo artifact.
Cách gọn nhất là dùng tính năng có sẵn: tách pipeline thành hai stage "Build" và "Deploy", đặt trigger: manual cho stage Deploy khi cần duyệt tay. Bỏ dòng đó thì pipeline deploy tự động. Trước khi tự chế cơ chế bằng shell, hãy tra xem Bitbucket Pipelines đã có tính năng tương ứng chưa.
6. Tách cấu hình Kubernetes ra repo riêng
Một mô hình phổ biến là chuyển Dockerfile, cấu hình nginx, PHP-FPM, supervisor và thư mục manifest ra một repo cấu hình riêng, tách khỏi repo ứng dụng. Lý do: repo ứng dụng có nhiều lập trình viên cùng làm, còn cấu hình hạ tầng nên có vòng đời và quyền truy cập riêng. Đổi manifest không cần chạm vào code, và ngược lại; Bitbucket Pipelines của repo ứng dụng chỉ cần đọc được repo cấu hình. File Bitbucket Pipelines thêm một step đầu tiên: clone repo cấu hình rồi chuyển nó sang các step sau bằng artifact.
pipelines:
branches:
main:
- stage:
name: Build
steps:
- step:
name: Lấy repo cấu hình
script:
- git clone --depth 1 "$CONFIG_REPO_URL" deploy-config
- rm -rf deploy-config/.git
artifacts:
- deploy-config/**
- step:
name: Build image
services: [docker]
script:
- cp -r deploy-config/. ./
- docker build -t $IMAGE_REPO:$BITBUCKET_COMMIT .
- echo "$REGISTRY_PASSWORD" | docker login -u "$REGISTRY_USER" --password-stdin
- docker push $IMAGE_REPO:$BITBUCKET_COMMIT
- stage:
name: Deploy
trigger: manual # bỏ dòng này nếu muốn deploy tự động
deployment: production
steps:
- step:
name: Apply
script:
- sed -i "s|image:.*|image: $IMAGE_REPO:$BITBUCKET_COMMIT|" deploy-config/.k8s/app-deployment.yaml
- pipe: atlassian/kubectl-run:1.3.3
variables:
KUBE_CONFIG: $KUBE_CONFIG
KUBECTL_COMMAND: 'apply'
RESOURCE_PATH: 'deploy-config/.k8s/'Chuyển thư mục giữa các step là chỗ dễ sai nhất trong Bitbucket Pipelines:
- Clone ra ngoài thư mục làm việc (
cd ../rồi clone): artifact chỉ được lấy từ bên trong$BITBUCKET_CLONE_DIR, step sau không thấy gì. - Khai artifact là tên thư mục:
artifacts: [deploy-config]không lấy nội dung bên trong. Phải dùng globdeploy-config/**. - Glob của shell bỏ qua file ẩn:
cp -rf deploy-config/* ./chép đượcDockerfilenhưng bỏ sót.docker/và.k8s/. Cách đúng làcp -r deploy-config/. ./. - Đường dẫn ở step Deploy: artifact được giải nén lại ở mỗi step, nên step Deploy phải trỏ thẳng vào
deploy-config/.k8s/chứ không dùng bản đã chép ở step Build.
7. Khuyến nghị bảo mật và vận hành
- Apply cả thư mục, không chỉ Deployment. Nếu Bitbucket Pipelines chỉ apply một file, ConfigMap, Secret, Ingress sẽ lệch dần vì phải apply tay.
- Không dùng kubeconfig quản trị trong biến của repo. Ai sửa được pipeline là có quyền với cả cluster. Tạo ServiceAccount chỉ có quyền trong namespace cần deploy và sinh kubeconfig từ token của nó.
- Đánh dấu secured cho mọi biến bí mật, kể cả URL repo cấu hình nếu có kèm token.
- Tag theo mã commit thay cho tag cố định, để biết pod chạy bản nào và rollback được.
- Pipeline thử nghiệm chạy trên branch riêng; chỉ gộp vào nhánh chính khi đã ổn định và có bước duyệt.
8. Checklist khi dựng Bitbucket Pipelines cho Kubernetes
- Thử từng lệnh shell (sed, cp, glob) trên máy cá nhân trước khi đưa vào Bitbucket Pipelines.
- Nhớ mỗi step là một container mới: biến môi trường mất, file chỉ còn nếu là artifact.
- Thay đổi image phải tác động vào pod template, như bài Manifest Kubernetes đầu tiên: namespace, Deployment và MySQL StatefulSet.
- Tách cấu hình hạ tầng khỏi code ứng dụng sớm; đây cũng là bước chuẩn bị để chuyển sang công cụ đóng gói manifest.
- Các thông số trong bài Requests và limits, QoS và nodeAffinity: xếp pod lên đúng node được áp lại mỗi lần chạy, nên phải đúng ngay trong repo, không sửa tay trên cluster.
Bài tiếp theo
Helm chart là gì: đóng gói website Laravel để deploy lặp lại. Khi số site tăng lên, sed từng dòng trong manifest không còn đủ: Helm đóng gói manifest thành template, values và release như thế nào.
