Product Management
Đăng nhập
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Bài 52 — Continuous Verification & Smoke Tests

Đã đọc 0/14 mục · còn khoảng 13 phút
Cách chúng tôi ghi nhận

Một mục được tính là đã đọc khi nó nằm giữa màn hình đủ lâu so với độ dài của nó (bảng và sơ đồ tính một nửa), bạn vẫn đang cuộn hoặc bấm phím, và bạn đã thấy tới cuối mục. Lướt nhanh hoặc để tab mở không được tính. Chúng tôi chỉ lưu thời gian đọc từng mục — không lưu gì khác.

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn vừa bấm nút deploy. Pipeline xanh lét từ đầu đến cuối: lint pass, 2.000 unit test pass, integration test pass, image build thành công, deploy lên production báo "success". Bạn thở phào, đóng laptop, đi ăn trưa. Ba mươi phút sau, điện thoại rung liên tục: khách hàng không thanh toán được, tổng đài cháy máy.

Chuyện gì đã xảy ra? Code của bạn đúng. Test của bạn pass. Nhưng hệ thống production không hoạt động. Có thể một biến môi trường (environment variable) bị thiếu, DNS của payment gateway đổi, connection pool tới database cạn kiệt, hoặc certificate TLS hết hạn lúc 12h trưa. Tất cả những thứ này nằm ngoài phạm vi mà pre-deploy test có thể nhìn thấy.

Đây chính là khoảng trống mà Continuous Verification (kiểm chứng liên tục) và Smoke Test (kiểm tra khói) lấp đầy. Chúng trả lời một câu hỏi hoàn toàn khác với câu hỏi mà test truyền thống trả lời. Test truyền thống hỏi: "Code tôi viết có đúng logic không?". Continuous Verification hỏi: "Ngay bây giờ, trên production thật, với dữ liệu thật và hạ tầng thật, hệ thống có thực sự đang chạy đúng không?".

Trong một pipeline CI/CD trưởng thành, bước cuối cùng của deploy không phải là "kubectl apply thành công" mà là "hệ thống đã được xác minh là healthy". Nếu bạn bỏ qua bài này, mọi công sức xây pipeline của các bài trước — build, test, scan, các chiến lược deploy blue-green, canary — đều thiếu mất mảnh ghép cuối: bằng chứng rằng người dùng thật đang được phục vụ.

Khái niệm cốt lõi

Continuous Verification là gì, và khác gì với pre-deploy testing?

Pre-deploy testing (unit, integration, e2e trong CI) chạy trước khi code chạm production. Chúng chạy trên môi trường mô phỏng, với dữ liệu giả, và mục tiêu là chứng minh code đúng. Chúng là điều kiện cần để deploy.

Continuous Verification chạy trong lúc và sau khi deploy, trên chính môi trường production (hoặc production-like), với hạ tầng thật, để chứng minh hệ thống đang hoạt động. Nó là điều kiện đủ để tuyên bố deploy thành công.

Sự khác biệt cốt lõi nằm ở đối tượng kiểm chứng:

Tiêu chíPre-deploy TestContinuous Verification
Kiểm cái gìLogic của codeTrạng thái sống của hệ thống
Chạy ở đâuCI runner, môi trường giảProduction / môi trường thật
Dữ liệuMock, fixtureThật (hoặc canary traffic thật)
Thời điểmTrước deployTrong và sau deploy, liên tục
Trả lời câu hỏi"Code có đúng không?""Hệ thống có đang chạy không?"
Khi fail thì làm gìChặn merge/deployRollback tự động, cảnh báo
Một cách ví von: pre-deploy test giống như kiểm tra bản vẽ kỹ thuật của một chiếc cầu trước khi xây. Continuous Verification giống như cho một chiếc xe tải chạy qua cầu ngay sau khi xây xong, rồi gắn cảm biến theo dõi rung động liên tục suốt vòng đời cây cầu.

Smoke Test — tuyến phòng thủ đầu tiên

Thuật ngữ "smoke test" đến từ ngành điện tử: sau khi lắp một mạch điện, người ta cấp nguồn và xem có bốc khói không. Nếu không khói, mạch ít nhất là không cháy — còn đúng hay sai thì kiểm sau. Trong CI/CD, smoke test là một tập nhỏ các kiểm tra nhanh, quan trọng nhất chạy ngay sau khi deploy để trả lời: "Ứng dụng có bốc khói không?".

Smoke test cố tình nông và rộng, không sâu:

  • Trang chủ có trả về HTTP 200 không?
  • API /health có báo healthy không?
  • Đăng nhập một tài khoản test có thành công không?
  • Kết nối tới database có ping được không?
  • Luồng nghiệp vụ quan trọng nhất (ví dụ: tạo đơn hàng) có chạy được một lần không?
Smoke test không kiểm mọi edge case. Nó chỉ kiểm những đường huyết mạch mà nếu chết thì cả hệ thống coi như chết. Mục tiêu là chạy trong vài giây đến một hai phút, đủ nhanh để đặt ngay vào gate của pipeline sau bước deploy.

Health check, readiness và liveness

Nền móng kỹ thuật của Continuous Verification là các endpoint sức khỏe. Trong thế giới Kubernetes, bạn sẽ gặp ba loại probe:

  • Liveness probe: "Tiến trình còn sống không?" — nếu fail, container bị restart.
  • Readiness probe: "Sẵn sàng nhận traffic chưa?" — nếu fail, pod bị tách khỏi load balancer nhưng không restart.
  • Startup probe: "Đã khởi động xong chưa?" — dành cho app khởi động chậm.
Điểm mấu chốt mà nhiều bạn làm sai: một health check tốt phải kiểm cả dependency. Một endpoint /health chỉ trả về {"status":"ok"} mà không kiểm database, cache, message queue thì gần như vô dụng — nó nói "tôi còn thở" chứ không nói "tôi làm việc được".

Các trụ cột của Continuous Verification

Continuous Verification trong thực tế đứng trên ba trụ:

  • Synthetic monitoring — bot chạy các kịch bản người dùng giả (fake user journeys) đều đặn, ví dụ mỗi 60 giây, kể cả khi không deploy. Đây là mắt canh 24/7.
  • SLO-based verification — sau deploy, so sánh các chỉ số vàng (error rate, latency p95/p99, throughput) của phiên bản mới với ngưỡng SLO (Service Level Objective) hoặc với baseline của phiên bản cũ. Đây chính là linh hồn của progressive delivery.
  • Post-deploy smoke & E2E-lite — chạy một tập test rút gọn ngay trên môi trường vừa deploy.
Khi ba trụ này gắn vào pipeline và có khả năng tự động kích hoạt rollback, bạn đã có Continuous Verification thực thụ, chứ không chỉ là "monitoring rời rạc".

Tình huống thực tế

Tình huống 1: Sàn TMĐT tại TP.HCM và cú deploy "xanh nhưng chết"

Một công ty thương mại điện tử ở TP.HCM (gọi là ShopViet, quy mô ~40.000 đơn/ngày) có pipeline GitLab CI đầy đủ: test coverage 78%, e2e test chạy trên staging. Một chiều thứ Sáu, team release bản cập nhật trang thanh toán. Pipeline xanh 100%.

Vấn đề: bản mới thêm một biến môi trường PAYMENT_SIGNATURE_KEY để ký request tới cổng thanh toán VNPay. Trên staging biến này đã có, nhưng người deploy quên thêm vào secret của production. Code chạy, trang load bình thường, health check /health (chỉ ping database) vẫn xanh. Nhưng mọi giao dịch VNPay đều bị từ chối vì chữ ký sai.

Suốt 34 phút, không ai biết, vì health check không đụng tới luồng thanh toán. Chỉ đến khi CSKH nhận cả chục cuộc gọi thì team mới rollback. Thiệt hại ước tính hơn 200 triệu đồng doanh thu và tệ hơn là niềm tin khách hàng.

Bài học: Sau sự cố, ShopViet thêm một smoke test thanh toán chạy ngay sau deploy — nó thực hiện một giao dịch thật giá trị 1.000đ qua VNPay bằng thẻ test, xác minh trả về mã thành công, rồi hoàn tiền. Smoke test này nằm trong gate verify của pipeline: nếu fail, deploy bị đánh dấu thất bại và tự rollback về image cũ. Từ đó, lỗi kiểu "thiếu secret" bị bắt trong vòng 90 giây thay vì 34 phút.

Tình huống 2: Ví điện tử và progressive delivery với SLO gating

Một ví điện tử tại Đông Nam Á (giả định tên PayNow) triển khai canary deployment (bạn đã học ở Bài 19). Nhưng ban đầu họ chỉ dùng canary như "chia traffic 10%" mà không có tiêu chí tự động để quyết định đẩy tiếp hay rollback — kỹ sư phải ngồi nhìn Grafana bằng mắt.

Họ nâng cấp bằng Continuous Verification dựa trên SLO. Cấu hình như sau: sau khi canary nhận 10% traffic trong 5 phút, hệ thống tự động so sánh:

  • Error rate của canary phải ≤ 1,15 lần error rate của baseline.
  • Latency p99 của canary phải ≤ 1,1 lần baseline.
Một lần, phiên bản mới có một truy vấn N+1 (gọi database lặp thừa). Unit test không phát hiện vì với dữ liệu test nhỏ, truy vấn vẫn nhanh. Nhưng trên canary với traffic thật, p99 tăng từ 180ms lên 520ms. Verification tự động thấy vượt ngưỡng 1,1 lần, dừng rollout ở 10%, rollback, và tạo ticket kèm biểu đồ. Chỉ 10% người dùng bị ảnh hưởng trong 5 phút, thay vì 100% trong một tiếng.

Bài học: Continuous Verification biến canary từ "cảm tính" thành "định lượng". Điểm hay là nó bắt được cả lỗi hiệu năng mà không loại test nào trong CI bắt nổi, vì lỗi chỉ xuất hiện dưới tải thật. Công cụ như Argo Rollouts (với AnalysisTemplate) hay Flagger cho phép khai báo các tiêu chí SLO này bằng YAML ngay trong Git.

Tình huống 3: Fintech và synthetic monitoring 24/7

Một startup fintech ở Hà Nội gặp vấn đề khác: hệ thống chết không phải lúc deploy. Một buổi 2h sáng, certificate TLS của một microservice nội bộ hết hạn. Không có deploy nào cả, nhưng luồng xác thực OTP sập. Vì không ai deploy nên không ai theo dõi, và họ chỉ phát hiện lúc 7h sáng khi người dùng thức dậy.

Giải pháp: họ dựng synthetic monitoring bằng một job chạy mỗi 60 giây (dùng k6 + cron, sau đó chuyển sang Checkly). Bot này thực hiện toàn bộ hành trình: mở app → đăng nhập → yêu cầu OTP → xác thực → xem số dư. Nếu bất kỳ bước nào fail 3 lần liên tiếp, PagerDuty gọi điện cho kỹ sư trực.

Sau khi triển khai, một lần certificate của service khác cũng sắp hết hạn — synthetic check báo lỗi trong vòng 2 phút, kỹ sư gia hạn cert trước khi phần lớn người dùng nhận ra.

Bài học: Continuous Verification không chỉ chạy khi deploy. "Continuous" nghĩa là liên tục — hệ thống production phải được chứng minh là đang sống mọi lúc, vì sự cố đến từ certificate, config drift, dependency bên ngoài, chứ không chỉ từ code mới của bạn.

Hướng dẫn từng bước

Dưới đây là cách đưa Continuous Verification vào pipeline một cách thực tế, từ đơn giản đến nâng cao.

Bước 1 — Xây health check "có chất lượng". Tạo endpoint /health (liveness, chỉ kiểm tiến trình sống) và /ready (readiness, kiểm cả database, cache, queue). Ví dụ /ready trả về JSON liệt kê trạng thái từng dependency và HTTP 503 nếu bất kỳ cái nào chết. Đừng để /ready gọi dependency quá nặng — chỉ ping nhẹ, timeout ngắn (1–2 giây).

Bước 2 — Viết bộ smoke test tối thiểu. Chọn 3–7 kịch bản huyết mạch nhất. Với một web app điển hình: trang chủ 200, đăng nhập test-user thành công, một API nghiệp vụ chính trả đúng, health/ready xanh. Giữ toàn bộ chạy dưới 90 giây. Ví dụ với một script gọn:

#!/usr/bin/env bash
set -euo pipefail
BASE_URL="${1:?Cần truyền base URL}"

echo "Smoke: trang chủ..."
curl -fsS "$BASE_URL/" > /dev/null

echo "Smoke: readiness..."
curl -fsS "$BASE_URL/ready" | grep -q '"status":"ok"'

echo "Smoke: đăng nhập test-user..."
curl -fsS -X POST "$BASE_URL/api/login" \
  -d '{"user":"smoke@test.vn","pass":"..."}' \
  -H 'Content-Type: application/json' | grep -q '"token"'

echo "Tất cả smoke test PASS."

Bước 3 — Gắn smoke test vào gate sau deploy. Trong pipeline (ví dụ GitHub Actions), tạo một job verify chạy sau job deploy và trỏ vào URL production (hoặc URL của canary/green environment). Nếu verify fail, pipeline phải coi deploy là thất bại.

verify:
  needs: deploy
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v4
    - name: Chạy smoke test trên production
      run: ./scripts/smoke.sh https://api.shopviet.vn

Bước 4 — Kết nối verification với rollback tự động. Đây là bước biến verification thành continuous. Nếu smoke test hoặc SLO analysis fail, pipeline phải tự động chạy lệnh rollback (deploy lại image trước đó, hoặc chuyển traffic về blue environment). Với Argo Rollouts/Flagger, bạn khai báo AnalysisTemplate để tự động abort và rollback khi metric vượt ngưỡng — không cần con người can thiệp.

Bước 5 — Thêm SLO-based analysis cho progressive delivery. Khai báo các truy vấn metric (thường là PromQL với Prometheus) đo error rate và latency của phiên bản mới, cùng ngưỡng so với baseline. Cho canary chạy vài phút, phân tích, rồi quyết định promote hay rollback.

Bước 6 — Dựng synthetic monitoring liên tục. Đưa (một phần) smoke test vào một job chạy định kỳ mỗi 30–60 giây, độc lập với chu kỳ deploy. Dùng Checkly, Datadog Synthetics, Grafana Synthetic Monitoring, hoặc tự dựng bằng k6 + cron. Kết nối tới hệ thống cảnh báo (bạn sẽ đào sâu ChatOps ở Bài 48).

Bước 7 — Đóng vòng lặp: đo và cải tiến. Ghi lại mỗi lần verification cứu bạn (bắt được lỗi) và mỗi lần nó báo động giả. Dùng dữ liệu này để tinh chỉnh ngưỡng và tập smoke test.

Lỗi thường gặp & mẹo

Lỗi 1 — Health check "giả trân". Endpoint chỉ trả 200 OK cứng mà không kiểm dependency. Nó luôn xanh kể cả khi database chết. Mẹo: readiness phải kiểm dependency thật, nhưng ping nhẹ và có timeout.

Lỗi 2 — Smoke test biến thành full regression. Bạn nhồi 200 test case vào "smoke", chạy mất 15 phút, làm chậm mọi deploy và cản hotfix. Mẹo: smoke phải nông–rộng–nhanh. Test sâu để dành cho e2e suite chạy riêng (Bài 54).

Lỗi 3 — Verify nhưng không rollback. Smoke test fail, gửi cảnh báo, nhưng phiên bản lỗi vẫn phục vụ 100% traffic vì không có cơ chế rollback tự động. Verification chỉ có giá trị khi hành động theo sau. Mẹo: nối kết quả verify với rollback tự động, ít nhất là abort rollout.

Lỗi 4 — Test data làm bẩn production. Smoke test tạo đơn hàng thật, gửi email thật, trừ tiền thật. Mẹo: dùng tài khoản test đánh dấu rõ ràng, giá trị tối thiểu, tự hoàn tác (như VNPay 1.000đ rồi refund), và loại các bản ghi này khỏi báo cáo kinh doanh.

Lỗi 5 — So sánh canary với ngưỡng tuyệt đối thay vì với baseline. Nếu bạn đặt "p99 phải < 300ms" cứng, thì vào giờ cao điểm baseline vốn đã 320ms sẽ khiến mọi deploy bị rollback oan. Mẹo: so sánh tương đối với phiên bản đang chạy (ví dụ ≤ 1,1 lần baseline).

Lỗi 6 — Báo động giả (flaky) làm team mất niềm tin. Nếu verification thỉnh thoảng fail vô cớ, người ta sẽ tập thói quen "retry đại" và bỏ qua cảnh báo thật. Mẹo: yêu cầu fail liên tiếp N lần trước khi báo động; ưu tiên độ tin cậy hơn độ nhạy.

Mẹo vàng: Hãy phân biệt rạch ròi "deploy thành công" và "release thành công". Deploy là code đã lên máy chủ. Release là hệ thống đã được xác minh phục vụ người dùng đúng. Pipeline của bạn chỉ nên báo màu xanh cuối cùng khi đạt điều thứ hai.

Bài tập thực hành

  • Health check hai tầng. Chọn một app bạn có (hoặc tạo một app Node/Python nhỏ). Viết endpoint /health (chỉ báo tiến trình sống) và /ready (kiểm kết nối tới một database thật, trả 503 nếu database chết). Tắt database và xác minh /ready trả 503 còn /health vẫn 200.
  • Bộ smoke test 60 giây. Viết script (bash + curl hoặc k6) kiểm ít nhất 4 kịch bản huyết mạch của app đó, chạy xong dưới 60 giây. Cố tình làm hỏng một endpoint và xác minh script exit code khác 0.
  • Gate verify trong pipeline. Tạo workflow GitHub Actions (hoặc GitLab CI) với ba job nối tiếp: build → deploy (deploy lên môi trường staging của bạn) → verify (chạy smoke test ở bước 2). Đảm bảo khi verify fail thì pipeline đỏ.
  • Mô phỏng rollback tự động. Mở rộng bài 3: khi verify fail, thêm một job rollback chạy có điều kiện (if: failure()) để deploy lại phiên bản trước. Chạy thử một lần thành công và một lần thất bại để thấy rollback kích hoạt.
  • (Nâng cao) Synthetic check. Dựng một job chạy định kỳ mỗi phút (dùng schedule của GitHub Actions hoặc cron trên máy bạn) thực hiện một smoke test và gửi thông báo (webhook Telegram/Slack) khi fail 3 lần liên tiếp. Quan sát trong 30 phút.

Tóm tắt

  • Pre-deploy test chứng minh code đúng; Continuous Verification chứng minh hệ thống đang chạy đúng — trên production thật, dữ liệu thật, hạ tầng thật. Chúng bổ sung chứ không thay thế nhau.
  • Smoke test là tuyến đầu: một tập nhỏ, nông–rộng–nhanh các kịch bản huyết mạch, chạy ngay sau deploy để phát hiện "hệ thống có bốc khói không".
  • Health check tốt phải kiểm dependency (readiness), không chỉ báo tiến trình sống (liveness).
  • Continuous Verification đứng trên ba trụ: synthetic monitoring 24/7, SLO-based verification (so sánh tương đối với baseline), và post-deploy smoke test.
  • Verification chỉ có giá trị khi gắn với hành động tự động — đặc biệt là rollback/abort rollout.
  • Các bài học từ ShopViet (thiếu secret), PayNow (lỗi hiệu năng dưới tải), và fintech Hà Nội (certificate hết hạn lúc 2h sáng) đều cho thấy: những sự cố nghiêm trọng nhất thường nằm ngoài tầm nhìn của test truyền thống.
  • Ranh giới cuối cùng cần khắc cốt: "deploy thành công" chưa phải "release thành công". Pipeline chỉ nên tuyên bố hoàn tất khi hệ thống đã được xác minh phục vụ người dùng.
Học xong bài này rồi? Tạo tài khoản miễn phí để lưu lại — lần sau vào là biết ngay đang dở ở đâu, và học hết khóa thì có chứng chỉ. Lưu tiến độ của tôi