Mở đầu — vì sao bài này quan trọng
Hãy tưởng tượng bạn là kỹ sư DevOps tại một công ty thương mại điện tử ở TP.HCM. Sáng thứ Hai, sếp nhắn: "Bản chạy trên production đang lỗi thanh toán, rollback về bản hôm qua ngay." Bạn mở hệ thống ra và giật mình: cả staging lẫn production đều đang chạy image có tag là latest. Bạn không biết chính xác "bản hôm qua" là image nào, vì latest đã bị ghi đè nhiều lần. Không có cách nào rollback chắc chắn. Đội của bạn phải build lại từ commit cũ, mất gần 40 phút — trong khi mỗi phút website chết là hàng chục đơn hàng bị bỏ giỏ.
Đây chính là lý do bài học này tồn tại. Promotion Strategy (chiến lược thăng cấp) và Image Tagging (cách gắn nhãn image) là xương sống của một pipeline CD trưởng thành. Nó trả lời câu hỏi tưởng chừng đơn giản nhưng cực kỳ quan trọng: "Đúng chính xác đoạn code nào, được đóng gói thành cái gì, đang chạy ở môi trường nào, và làm sao để tôi biết chắc điều đó?"
Trong các bài trước, bạn đã học về deployment strategies (rolling, blue-green, canary) và cách build image trong pipeline. Bài này tập trung hẹp vào artifact bạn triển khai — cụ thể là container image — và cách nó di chuyển qua các môi trường. Đây không phải chuyện lý thuyết. Cách bạn đặt tag image quyết định trực tiếp việc rollback nhanh hay chậm, việc truy vết sự cố dễ hay khó, và việc pipeline của bạn có "reproducible" (tái lập được) hay không.
Khái niệm cốt lõi
Immutable tag — nền tảng của mọi chuyện
Nguyên tắc vàng đầu tiên: image tag phải là bất biến (immutable) — một khi đã push với tag đó, không bao giờ được ghi đè bằng nội dung khác.
Hãy so sánh hai cách đặt tag:
vnc/api:latest— tag di động (mutable). Hôm nay nó trỏ vào build A, ngày mai trỏ vào build B. Đây là kẻ thù số một của khả năng tái lập.vnc/api:abc1234— tag theo Git SHA (mã băm của commit). Mỗi commit tạo ra một image duy nhất, tag này không bao giờ được dùng lại.vnc/api:1.2.3— tag theo semantic versioning (semver). Phiên bản1.2.3chỉ được release một lần, không bao giờ over-write (ghi đè).
vnc/api:abc1234, bạn biết chính xác commit abc1234 là code nào, ai commit, lúc mấy giờ, sửa gì. Bạn có thể checkout đúng commit đó để debug. Rollback trở thành việc đơn giản: chỉ cần trỏ lại tag cũ.Digest — mức độ bất biến tuyệt đối
Cao hơn cả tag là image digest — chuỗi SHA256 của chính nội dung image, dạng vnc/api@sha256:9f2b.... Tag có thể bị con người vô tình di chuyển; digest thì gắn cứng với đúng từng byte của image. Trong môi trường yêu cầu cao (tài chính, y tế), người ta thường deploy bằng digest chứ không phải tag, để đảm bảo tuyệt đối rằng thứ chạy trên production đúng là thứ đã được test.
Ba trường phái đặt tag phổ biến
1. Git SHA (khuyến nghị làm tag kỹ thuật). Ưu điểm: tự động, không cần con người quyết định số phiên bản, truy vết ngược về commit dễ dàng. Nhược điểm: abc1234 không có ý nghĩa với con người — bạn không biết nó "mới" hay "cũ", "lớn" hay "nhỏ".
2. Semantic Versioning (khuyến nghị làm tag phát hành). Dạng MAJOR.MINOR.PATCH như 1.2.3. Con người đọc hiểu ngay: 2.0.0 là thay đổi lớn có breaking change, 1.2.4 chỉ là vá lỗi nhỏ. Phù hợp cho thư viện, SDK, và các release chính thức mà khách hàng nhìn thấy.
3. Kết hợp (thực tế nhất). Nhiều đội gắn nhiều tag cho cùng một image: vnc/api:1.2.3, vnc/api:abc1234, và cả vnc/api:2026.06.27-build.142. Cùng một image, ba cách gọi tên phục vụ ba mục đích: con người, máy, và audit theo thời gian.
Promotion — thăng cấp thay vì build lại
Đây là ý tưởng trung tâm của bài. Promotion nghĩa là: build image một lần duy nhất, rồi di chuyển cùng chính image đó qua các môi trường dev → staging → production.
Sai lầm phổ biến là mỗi môi trường lại build một image mới từ source code. Nghe thì hợp lý, nhưng cực kỳ nguy hiểm: image build cho staging và image build cho production có thể khác nhau dù cùng commit — vì base image đã update, vì một dependency được kéo mới, vì thời điểm build khác nhau. Kết quả: bạn test một thứ, deploy một thứ khác. Đây là nguồn gốc của những bug kinh điển "chạy tốt ở staging mà production chết".
Promotion đúng cách: image vnc/api:abc1234 được build một lần, được test ở staging, và chính xác image đó — không build lại — được đẩy lên production. Cái được "thăng cấp" không phải là code, mà là niềm tin vào một artifact cụ thể đã qua kiểm chứng.
Hai kiểu mô hình promotion
- Same registry, đổi tag/nhãn: image nằm yên một chỗ trong registry, bạn thêm nhãn hoặc tag như
:staging,:productionđể đánh dấu "bản này đã được duyệt cho môi trường đó". Các tag môi trường này là mutable (di động) và chỉ đóng vai trò con trỏ, còn tag SHA/semver bên dưới vẫn immutable. - Multi registry (copy giữa các registry): dev có registry riêng, production có registry riêng (thường tách biệt để bảo mật). Promotion là hành động
copyimage từ registry này sang registry kia mà không rebuild, dùng công cụ nhưcrane,skopeo, hoặcregctl.
Tình huống thực tế
Ví dụ 1 — Tiki và thảm họa "latest"
Một đội backend tại một công ty thương mại điện tử lớn ở Việt Nam (bối cảnh giả định dựa trên tình huống thật rất phổ biến) từng deploy toàn bộ microservices bằng tag latest. Mọi thứ chạy êm cho đến một đêm Black Friday. Khi lượng truy cập tăng vọt, Kubernetes tự động scale thêm pod. Vấn đề: các pod mới kéo image latest — nhưng lúc đó một kỹ sư khác vừa push một bản latest mới chưa được test kỹ. Kết quả là cụm pod chạy hỗn hợp: pod cũ chạy bản ổn định, pod mới chạy bản lỗi. Người dùng gặp lỗi ngẫu nhiên tùy vào việc request rơi vào pod nào — cực kỳ khó debug.
Diễn giải: latest cộng với imagePullPolicy: Always là công thức thảm họa. Không ai kiểm soát được pod nào chạy phiên bản nào.
Bài học: Chuyển sang tag theo Git SHA. Mỗi lần deploy khai báo tường minh vnc/api:abc1234. Khi scale, mọi pod đều kéo đúng một image xác định. Bug "pod chạy lung tung" biến mất hoàn toàn. Sau sự cố này, đội đặt quy tắc cứng trong CI: pipeline sẽ fail nếu manifest deploy chứa tag latest.
Ví dụ 2 — Fintech Singapore và promotion bằng digest
Một startup fintech ở Singapore phục vụ thanh toán xuyên biên giới Đông Nam Á có yêu cầu tuân thủ nghiêm ngặt: thứ chạy trên production phải chứng minh được là đúng thứ đã qua audit bảo mật. Họ thiết kế pipeline như sau: sau khi build, image được quét bảo mật và test ở staging. Bước promote lên production không dùng tag mà dùng digest — hệ thống ghi lại sha256:9f2b3c... vào một file cấu hình có ký số. Production chỉ chấp nhận deploy nếu digest khớp với bản đã được duyệt.
Diễn giải: Với tag, về lý thuyết ai đó có quyền vẫn có thể push đè lên :1.2.3 một nội dung khác. Digest thì bất khả xâm phạm — đổi một byte là đổi cả digest.
Bài học: Trong lĩnh vực nhạy cảm, hãy promote bằng digest. Con người đọc semver để hiểu, nhưng máy deploy theo digest để đảm bảo tính toàn vẹn tuyệt đối. Chi phí thêm gần như bằng không, nhưng độ an toàn tăng vọt.
Ví dụ 3 — Đội SaaS và bài toán rollback 30 giây
Một công ty SaaS ở Hà Nội có khoảng 25 kỹ sư, deploy khoảng 15 lần mỗi ngày. Trước đây rollback mất trung bình 25 phút vì phải build lại từ commit cũ. Họ áp dụng chiến lược tag kép: mỗi image mang cả vnc/app:<git-sha> và vnc/app:<semver>, đều được lưu vĩnh viễn trong registry. Họ giữ lại tối thiểu 20 image gần nhất. Khi cần rollback, kỹ sư chỉ cần chạy lệnh deploy trỏ lại SHA của bản ổn định trước đó.
Diễn giải: Vì image cũ vẫn còn nguyên trong registry (không bị dọn), rollback biến thành việc "đổi con trỏ" thay vì "xây lại nhà".
Bài học: Thời gian rollback giảm từ 25 phút xuống dưới 30 giây. Con số MTTR (Mean Time To Recovery — thời gian trung bình để phục hồi) này là một trong bốn chỉ số DORA đo lường độ trưởng thành của đội DevOps. Immutable tag chính là thứ khiến rollback rẻ.
Hướng dẫn từng bước
Dưới đây là cách thiết lập một luồng tag và promotion cơ bản với GitHub Actions và một container registry.
Bước 1 — Sinh tag từ Git SHA trong CI.
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set image tag
id: meta
run: echo "sha_tag=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
- name: Build & push
run: |
docker build -t registry.vnc.vn/api:${{ steps.meta.outputs.sha_tag }} .
docker push registry.vnc.vn/api:${{ steps.meta.outputs.sha_tag }}
${GITHUB_SHA::7} lấy 7 ký tự đầu của commit hash — đủ ngắn để đọc, đủ dài để duy nhất.
Bước 2 — Gắn thêm tag semver khi tạo release.
Khi bạn tạo Git tag v1.2.3, pipeline gắn thêm nhãn semver cho chính image đã build, không build lại:
- name: Add semver tag on release
if: startsWith(github.ref, 'refs/tags/v')
run: |
crane tag registry.vnc.vn/api:${{ steps.meta.outputs.sha_tag }} 1.2.3
crane tag chỉ thêm một con trỏ mới tới cùng image — không tốn thời gian build hay push lại layer.
Bước 3 — Deploy lên staging bằng tag SHA.
Manifest staging luôn tham chiếu tường minh SHA, không bao giờ dùng latest:
image: registry.vnc.vn/api:abc1234
Bước 4 — Promote lên production bằng cách copy, không rebuild.
Sau khi staging pass toàn bộ smoke test, một job promote (thường cần approval thủ công) sẽ copy image sang registry production:
crane copy \
registry.vnc.vn/api:abc1234 \
prod-registry.vnc.vn/api:abc1234
Bước 5 — Ghi lại và deploy bằng digest.
Lấy digest của image production và deploy theo digest để đảm bảo bất biến tuyệt đối:
DIGEST=$(crane digest prod-registry.vnc.vn/api:abc1234)
# Deploy: prod-registry.vnc.vn/api@${DIGEST}
Bước 6 — Thiết lập retention policy. Cấu hình registry giữ lại N image gần nhất (ví dụ 30) và tất cả các tag semver, đồng thời tự xóa các image nhánh feature cũ để tiết kiệm dung lượng.
Lỗi thường gặp & mẹo
Lỗi 1 — Dùng latest ở production. Đây là lỗi phổ biến nhất. latest không cho biết bạn đang chạy gì và khiến rollback bất khả thi. Mẹo: thêm một bước kiểm tra trong CI để fail pipeline nếu phát hiện tag latest trong bất kỳ manifest nào.
Lỗi 2 — Rebuild ở mỗi môi trường. Nếu bạn build image mới cho staging rồi build lại cho production, bạn đã phá vỡ nguyên tắc "test cái gì deploy cái đó". Mẹo: build một lần trong pipeline, mọi môi trường về sau chỉ promote image đó.
Lỗi 3 — Ghi đè tag đã release. Có người vá lỗi rồi push đè lên :1.2.3. Giờ 1.2.3 trên máy A khác 1.2.3 trên máy B. Mẹo: bật tính năng immutable tags trong registry (ECR, Harbor, GitLab Registry đều hỗ trợ) để chặn ghi đè ở cấp hệ thống.
Lỗi 4 — Dọn image quá tay. Retention policy quá hung hãn xóa mất bản ổn định mà bạn cần rollback về. Mẹo: luôn giữ tất cả tag semver và tối thiểu 20–30 image SHA gần nhất.
Lỗi 5 — Tag môi trường bị nhầm là immutable. :production là con trỏ di động — hôm nay trỏ image này, mai trỏ image khác. Đừng nhầm nó với danh tính thật của image. Mẹo: luôn ghi log SHA/digest thực tế mà :production đang trỏ tới tại mỗi lần deploy.
Mẹo tổng quát: Nhúng metadata vào image bằng OCI labels (org.opencontainers.image.revision, .version, .created). Khi cần điều tra một pod đang chạy, bạn docker inspect là biết ngay nó từ commit nào, build lúc nào — không cần đoán.
Bài tập thực hành
- Thiết kế tag scheme. Cho service
payment-api, hãy viết ra ba tag mà bạn sẽ gắn cho một image build từ commitf4a9c02được release thành phiên bản3.1.0vào ngày hôm nay. Giải thích mục đích từng tag.
- Sửa pipeline lỗi. Bạn nhận một manifest deploy production có dòng
image: myapp:latest. Hãy viết lại nó cho đúng nguyên tắc immutable, và mô tả bước bạn sẽ thêm vào CI để ngăn lỗi này tái diễn.
- Viết lệnh promote. Dùng
crane(hoặcskopeo), viết lệnh copy imagedev-registry.local/web:f4a9c02sangprod-registry.local/web:f4a9c02mà không rebuild. Sau đó viết lệnh lấy digest của bản production.
- Tình huống rollback. Production đang chạy
api:9c1de7fvà bị lỗi. Bản ổn định trước đó làapi:3b2a8e0, vẫn còn trong registry. Mô tả từng bước bạn rollback trong dưới một phút, và giải thích vì sao immutable tag khiến điều này khả thi.
- Retention policy. Đội bạn deploy 20 lần/ngày. Hãy đề xuất một retention policy cụ thể (giữ bao nhiêu, giữ loại tag nào, xóa loại nào) cân bằng giữa khả năng rollback và chi phí lưu trữ.
Tóm tắt
- Immutable tag là nền tảng. Tag theo Git SHA (
abc1234) cho máy và truy vết; tag semver (1.2.3) cho con người. Không bao giờ ghi đè. latestlà kẻ thù. Nó phá vỡ khả năng tái lập, gây pod chạy hỗn hợp, và khiến rollback bất khả thi. Cấm dùng ở staging và production.- Promotion = build một lần, di chuyển image đó qua các môi trường. Không rebuild ở mỗi tầng — nếu không bạn test một thứ và deploy một thứ khác.
- Digest cho mức bất biến tuyệt đối. Trong lĩnh vực nhạy cảm, deploy theo
@sha256:...để đảm bảo toàn vẹn. - Tag môi trường (
:production) chỉ là con trỏ di động — luôn ghi log SHA/digest thực tế bên dưới. - Immutable tag làm rollback rẻ. Giữ lại đủ image cũ trong registry và rollback trở thành việc đổi con trỏ trong vài giây — trực tiếp cải thiện chỉ số MTTR của đội.