Khi các site trên Kubernetes được tạo bằng GitLab CI, sớm muộn sẽ cần thêm những thao tác khác: backup một site, backup cả cluster, gỡ site, dọn dẹp khi tạo hỏng. Thay vì nhồi mọi thao tác vào một file .gitlab-ci.yml khổng lồ với hàng chục điều kiện, có thể tổ chức theo mô hình: mỗi thao tác là một pipeline vận hành nằm trên một branch riêng của cùng một repo. Tài liệu này mô tả mô hình đó, khuôn mẫu pipeline tạo site từ file values mẫu tới khi website trả HTTP 200, cơ chế tự dọn dẹp khi deploy lỗi, pipeline backup, và những điểm cần cảnh giác.
1. Mô hình mỗi branch một pipeline vận hành
GitLab CI luôn chạy file .gitlab-ci.yml của đúng ref (branch) được yêu cầu. Tận dụng điều đó, repo vận hành có branch main gần như rỗng (chỉ README và phần dùng chung), còn mỗi branch khác chứa một pipeline vận hành độc lập với biến đầu vào và các stage riêng. Các nhóm chức năng thường gặp: tạo site, gỡ site, đổi domain, backup một site, backup cả cluster, và các biến thể để thử nghiệm.
- Gọi từ giao diện: vào Run pipeline, chọn branch, điền biến (
DOMAIN,DATABASE_NAME...) rồi bấm chạy. - Gọi qua API:
POST /projects/:id/pipelinevớireflà tên branch và mảngvariables, đúng kiểu API ở bài Cầu nối Bitbucket và GitLab CI bằng API và polling. - Không chạy khi push: mọi job có
rules: - if: $CI_PIPELINE_SOURCE == "web" || $CI_PIPELINE_SOURCE == "api". Push sửa file pipeline sẽ không vô tình tạo site hay xoá database.
Có thể khai báo sẵn biến đầu vào kèm mô tả trong khối variables để form Run pipeline hiện gợi ý cho người vận hành:
variables:
DOMAIN:
value: ""
description: "Domain của site, ví dụ site-a.example.com"
DATABASE_NAME:
value: ""
description: "Tên database sẽ tạo mới"2. Khuôn mẫu pipeline vận hành tạo site
Pipeline vận hành quan trọng nhất là tạo site, với ba stage:
prepare ── check_exists (runner shell) site/namespace/database đã có thì dừng
├─ create_nfs_folder (runner shell) SSH vào máy NFS, mkdir thư mục site
└─ create_helm_values (runner shell) clone repo chart, sinh values từ file mẫu
deploy ── deploy_helm (image helm + kubectl)
tạo database → helm template → helm install --dry-run → helm install
→ chờ HTTP 200 → seed dữ liệu → commit values lên repo
rollback ── remove_failure (when: on_failure) dọn những gì lần chạy này đã tạoTên release và namespace được suy ra từ domain bằng một YAML anchor dùng chung cho mọi job, ví dụ site-a.example.com thành site-a-example-com:
.init_variables: &init_variables
- RELEASE_NAME="$(echo "$DOMAIN" | sed 's/\./-/g')"
- NAMESPACE_WEB="$RELEASE_NAME"Bước 1: sinh values từ file mẫu
Trong pipeline vận hành, job create_helm_values clone repo chart, copy file values mẫu thành file values của site rồi thay từng placeholder. File mẫu và chart được giới thiệu ở bài Helm chart là gì: đóng gói website Laravel để deploy lặp lại.
- cp -f $CHART_DIR/example/site-values.yaml $VALUES_FILE
- sed -i "s|\[DOMAIN\]|$DOMAIN|g" $VALUES_FILE
- sed -i "s|\[DB_HOST\]|$DATABASE_HOST|g" $VALUES_FILE
- sed -i "s|\[NFS_IP\]|$NFS_IP|g" $VALUES_FILE
- sed -i "s|\[POOL_WEB_ID\]|$POOL_WEB_ID|g" $VALUES_FILE
# ... các placeholder còn lại, kể cả mật khẩu DB sinh ngẫu nhiênThư mục đã sửa được chuyển sang stage sau bằng artifact, đặt expire_in ngắn (vài ngày) vì artifact chứa bí mật. Với nhiều placeholder, yq hoặc --set của Helm an toàn hơn sed.
Bước 2: tạo database và cài Helm release
Pipeline vận hành tạo database (qua client mysql tới máy database, hoặc kubectl exec vào pod MySQL nếu database chạy trong cluster), sau đó render manifest để kiểm tra, chạy thử rồi cài thật:
- helm template $RELEASE_NAME $CHART_DIR -f $VALUES_FILE --debug > rendered.yaml
- helm install $RELEASE_NAME $CHART_DIR -f $VALUES_FILE --dry-run -n $NAMESPACE_WEB --create-namespace
- helm install $RELEASE_NAME $CHART_DIR -f $VALUES_FILE -n $NAMESPACE_WEB --create-namespaceBước 3: chờ website trả HTTP 200 rồi mới seed
Cài xong Helm release chưa có nghĩa là site chạy được: pod còn phải kéo image, initContainer chạy migrate, cert-manager xin chứng chỉ. Pipeline vận hành hỏi https://$DOMAIN định kỳ (ví dụ mỗi 3 giây) cho tới một giới hạn thời gian:
for i in $(seq 1 $MAX_TRIES); do
HTTP_STATUS=$(curl -L -k -o /dev/null -s -w "%{http_code}" "https://$DOMAIN")
[ "$HTTP_STATUS" -eq 200 ] && break
sleep 3
done
[ "$HTTP_STATUS" -eq 200 ] || { echo "Site chưa lên"; exit 1; }Khi site đã lên, job kubectl exec vào pod để chạy các lệnh seed dữ liệu ban đầu (ví dụ php artisan db:seed --class=...) và sửa quyền thư mục storage.
Bước 4: commit values lên repo
Bước cuối đẩy file values của site lên repo chart với thông điệp kiểu update value <domain>. Vì nhiều pipeline vận hành có thể chạy cùng lúc, lệnh push nên được bọc trong một hàm thử lại: push lỗi thì git pull --rebase bản mới nhất rồi thử lại, giới hạn số lần. Repo values nhờ đó trở thành sổ ghi chép mọi site đã tạo.
3. Pipeline vận hành an toàn khi chạy nhầm
Một lần chạy pipeline vận hành hỏng giữa chừng sẽ để lại rác: namespace có pod lỗi, database và user MySQL không ai dùng. Ngược lại, một pipeline viết ẩu có thể phá site đang sống. Bốn nguyên tắc:
- Không
DROP DATABASE IF EXISTStrước khi tạo. Chạy nhầm domain của một site đang sống là mất dữ liệu. ChỉCREATE DATABASE; trùng tên thì lỗi và dừng. - Dùng
helm install, không dùnghelm upgrade --installkhi ý định là tạo mới. Release đã tồn tại thì báo lỗi thay vì âm thầm ghi đè cấu hình. - Kiểm tra tồn tại ở đầu pipeline bằng nhiều tín hiệu: namespace, release Helm, database, thư mục NFS. Đừng chỉ dựa vào HTTP 200, vì một site đang lỗi sẽ không trả 200.
- Job dọn dẹp chỉ xoá thứ do chính lần chạy này tạo ra.
Cách đơn giản để thực hiện nguyên tắc 4 là để mỗi bước ghi lại "đã tạo gì" vào file artifact, và job dọn dẹp chỉ đọc file đó:
deploy_helm:
script:
- echo "" > created.env
- mysql ... -e "CREATE DATABASE \`$DATABASE_NAME\`;"
- echo "DB=1" >> created.env # dòng riêng: lệnh trước lỗi thì job dừng ngay
- kubectl create namespace $NAMESPACE_WEB
- echo "NS=1" >> created.env
- ...
artifacts:
when: always
paths: [created.env]
remove_failure:
stage: rollback
needs: [deploy_helm]
when: on_failure
script:
- *init_variables
- . ./created.env
- if [ "$NS" = "1" ]; then kubectl delete namespace "$NAMESPACE_WEB" --wait=false; fi
- if [ "$DB" = "1" ]; then mysql ... -e "DROP DATABASE \`$DATABASE_NAME\`; DROP USER IF EXISTS ..."; fiĐừng quên thư mục trên NFS: nếu rollback không dọn, nó thành thư mục mồ côi. Và hãy cố ý làm hỏng pipeline để thử đường rollback, ví dụ tạm chèn exit 1 ngay sau bước chờ HTTP 200 trong một lần chạy với domain thử. Thử đường lỗi có chủ đích rẻ hơn nhiều so với chờ nó xảy ra thật.
4. Pipeline vận hành cho backup
Hai pipeline vận hành cho backup dùng chung kỹ thuật:
- Backup một site: tìm pod đang ready của site, chạy
kubectl exec ... zip -r - publicđể nén thư mục upload và stream thẳng về runner, đọc tên database từ ConfigMap của site,mysqldump, rồi đẩy cả hai lên object storage (ví dụ bằngs3cmd). - Backup cả cluster: duyệt thư mục values trong repo chart để lấy danh sách site, lặp từng site như trên, lưu theo thư mục ngày, rồi xoá các bản cũ quá hạn giữ.
Viết vòng lặp bash trong YAML rất dễ dính lỗi biến. Lỗi điển hình: đặt tên file dump theo một biến còn sót lại từ vòng lặp trước, khiến mọi database ghi vào cùng một tên file và đè lên nhau. Lỗi thứ hai: pipeline exit 0 khi không tìm thấy site nên vẫn báo xanh dù không có bản backup nào. Pipeline vận hành cho backup phải đỏ khi không backup được gì, và tên file phải gắn với đúng đối tượng của vòng lặp hiện tại.
5. Kỹ thuật GitLab CI dùng trong pipeline vận hành
- YAML anchor (
&init_variables,*init_variables) để dùng lại đoạn lệnh chuẩn bị kubeconfig, SSH, biến giữa các job. - Tag runner: job cần SSH vào máy trong mạng private chạy trên runner
shell; job cần Helm và kubectl chạy trong image có sẵn hai công cụ (ví dụdtzar/helm-kubectl) trên runnerdocker. - needs và artifacts: job deploy chờ đúng các job chuẩn bị, nhận thư mục values qua artifact.
- when: on_failure cho job dọn dẹp.
- Biến bí mật của pipeline vận hành như kubeconfig mã hoá base64 đặt trong CI/CD variables của project (masked, protected), không viết vào file.
Cú pháp đầy đủ có trong tài liệu tham chiếu .gitlab-ci.yml, cách dùng anchor và extends ở trang tối ưu file YAML của GitLab CI.
6. Ưu và nhược của mô hình
| Ưu điểm | Nhược điểm |
|---|---|
| Mỗi pipeline vận hành độc lập, sửa backup không ảnh hưởng tạo site | Đoạn lệnh chung (kubeconfig, SSH, push) bị copy sang từng branch; sửa một chỗ phải nhớ sửa ở mọi branch |
| Lịch sử git của từng thao tác tách bạch | Branch main không cho thấy toàn cảnh; muốn biết có những thao tác nào phải liệt kê branch |
| Form Run pipeline theo branch: chỉ cần chọn đúng branch và điền biến | Biến đầu vào không có kiểm tra kiểu, gõ sai domain là chạy với domain sai |
Gọi được từ hệ thống khác qua API với ref là tên branch | Kubeconfig quyền cao đi theo từng lần chạy |
Hướng cải thiện tự nhiên là gom phần dùng chung vào một file và include nó từ mọi branch, thêm bước kiểm tra định dạng biến đầu vào, hoặc chuyển sang một pipeline duy nhất nhận biến ACTION và dùng rules để chọn job. Khi số thao tác còn ít, mô hình mỗi branch một pipeline vận hành vẫn dễ hiểu nhất cho cả nhóm.
7. Checklist cho pipeline vận hành
- Job chỉ chạy khi gọi từ giao diện hoặc API, không chạy khi push.
- Không
DROPtrước khiCREATE, khôngupgrade --installkhi ý định là tạo mới. - Kiểm tra tồn tại bằng nhiều tín hiệu; rollback chỉ dọn thứ mình tạo ra, kể cả thư mục NFS.
- Đã cố ý làm hỏng pipeline một lần để thử đường rollback.
- "Cài xong" khác "chạy được": chờ tín hiệu từ chính website rồi mới seed và báo thành công.
- Pipeline vận hành cho backup báo đỏ khi không có gì được backup.
- Artifact chứa bí mật có thời hạn ngắn.
Bài tiếp theo
Logging trên Kubernetes: từ promtail, fluentd đến Loki. Log của nhiều site nằm rải rác trong từng pod: các lựa chọn để gom log về một chỗ, vì sao chọn Loki, và những hạn chế cần biết.
