🔥 Smoke test sau mỗi lần deploy

By Better Wise

Bạn đã tách được dev/staging/production và biết secrets chỉ sống trong biến môi trường của từng nơi, không nằm trong Git. Giờ đến câu hỏi tôi luôn hỏi trước khi cho ai bấm deploy: làm sao biết bản vừa lên thực sự chạy, chứ không chỉ là pipeline xanh? Pipeline xanh chỉ nói build không lỗi. Nó không nói app trả lời được request.

Sau bài này bạn sẽ:

  • Giải thích được smoke test là gì và nó khác unit test, E2E ở đâu
  • Liệt kê được 6 điểm kiểm tra tối thiểu cho một lần deploy
  • Viết được một script smoke test bằng curl có exit code đúng
  • Gắn smoke test vào pipeline và biết khi nào nó phải chặn deploy

Smoke test chỉ trả lời một câu hỏi

Smoke test là một bộ kiểm tra ngắn, chạy ngay sau khi deploy xong, chỉ để xác nhận app còn sống và các luồng chính còn đi được. Nó không tìm bug logic, không đo hiệu năng, không thay E2E.

Lớp kiểm thử Phạm vi Thời gian Chạy khi nào
Unit test Hàm, module Vài giây Mỗi lần commit
Smoke test App đang chạy thật Dưới 2 phút Ngay sau mỗi deploy
E2E Luồng người dùng đầy đủ Vài phút đến vài chục phút Trước khi release lớn, theo lịch

Điểm mấu chốt: smoke test chạy trên môi trường thật vừa deploy, không chạy trên máy bạn. Và nó phải đủ nhanh để không ai có lý do bỏ qua nó lúc 11 giờ đêm.

Bộ kiểm tra tối thiểu

Sáu điểm dưới đây phủ gần hết các kiểu hỏng tôi từng gặp sau deploy: thiếu biến môi trường, mất kết nối DB, deploy nhầm bản, asset 404, và secret rò ra ngoài.

  1. Health endpoint (/healthz) trả 200, và nếu có thể thì health phải kiểm cả DB/cache, không chỉ trả "ok" cứng.
  2. Version/commit SHA: endpoint /version trả đúng SHA vừa build. Không kiểm cái này thì bạn sẽ có ngày test một bản cũ còn cache.
  3. Trang chủ trả 200 và có nội dung đặc trưng (một chuỗi text bạn luôn thấy, không phải chỉ status code).
  4. Một API quan trọng nhất trả 200 và đúng schema tối thiểu.
  5. Một luồng có xác thực: login bằng test account, gọi một endpoint cần đăng nhập.
  6. Không rò secret: response và log của lần deploy không chứa chuỗi giống API key hay connection string.

Warning: Đừng thêm 30 bước vào smoke test. Mỗi bước thêm vào là một bước có thể flaky, và một smoke test flaky sẽ bị người ta tắt trong tuần đầu tiên.

Bài tập 1. App của bạn là một todo-list có đăng nhập. Hãy chọn 4 kiểm tra đầu tiên đưa vào smoke test và giải thích thứ tự.

Xem gợi ý

Thứ tự hợp lý: /healthz (rẻ nhất, chết là biết ngay) → /version (đúng bản chưa) → trang chủ (200 + có chữ "Việc của tôi") → login test account rồi GET /api/todos (kiểm cả auth, session, DB). Bước 5 và 6 có thể thêm sau, khi 4 bước này đã ổn định.

Viết smoke test bằng curl

Smoke test nên nằm trong repo, cùng version với code, để được review và chạy lại được. Bản tối thiểu:

#!/usr/bin/env bash
set -euo pipefail

BASE_URL="${SMOKE_BASE_URL:?Thiếu SMOKE_BASE_URL}"
EXPECTED_SHA="${SMOKE_EXPECTED_SHA:?Thiếu commit SHA mong đợi}"

check() {
  local name="$1" url="$2"
  local code
  code=$(curl -s -o /dev/null -w '%{http_code}' --max-time 5 "$url")
  if [ "$code" != "200" ]; then
    echo "FAIL $name: HTTP $code ($url)"
    exit 1
  fi
  echo "OK   $name"
}

check "health" "$BASE_URL/healthz"
check "home"   "$BASE_URL/"

DEPLOYED_SHA=$(curl -s --max-time 5 "$BASE_URL/version" | tr -d '"')
if [ "$DEPLOYED_SHA" != "$EXPECTED_SHA" ]; then
  echo "FAIL version: đang chạy $DEPLOYED_SHA, mong đợi $EXPECTED_SHA"
  exit 1
fi
echo "OK   version"

Ba chi tiết đáng chú ý: --max-time để một request treo không làm pipeline treo theo, set -euo pipefail để lỗi không bị nuốt, và exit 1 để CI hiểu là fail. Trong script này, SMOKE_BASE_URL chính là biến môi trường bạn đã học ở bài trước: cùng một script, trỏ vào staging hay production tùy pipeline.

Bài tập 2. Script trên có một lỗ hổng: nó không kiểm nội dung. Hãy thêm một kiểm tra đảm bảo trang chủ thật sự render, không phải trang lỗi trả về 200.

Xem gợi ý

Dùng curl -s "$BASE_URL/" | grep -q "Việc của tôi" hoặc so sánh với chuỗi đặc trưng trong HTML. Đây là lý do tôi bắt buộc mỗi smoke test có ít nhất một kiểm tra nội dung, không chỉ status code.

Chặn deploy bằng exit code, không bằng niềm tin

flowchart TD
    A["🚀 Deploy xong"] --> B["🧪 Smoke test dưới 2 phút"]
    B -->|Pass| C["✅ Mở traffic"]
    B -->|Fail| D["🛑 Dừng pipeline"]
    D --> E["↩️ Rollback bản trước"]
    E --> F["🔍 Điều tra trên log"]
  1. Deploy bản mới lên staging trước, chạy smoke test với SMOKE_BASE_URL của staging.
  2. Smoke test xanh thì mới promote lên production và chạy lại đúng script đó.
  3. Exit code khác 0: dừng pipeline ngay, không chạy bước kế tiếp.
  4. Lưu log smoke test vào artifact của lần deploy để còn đối chiếu khi có sự cố.
  5. Trên production, chạy smoke test trước khi mở toàn bộ traffic nếu hạ tầng cho phép.

Quy tắc của tôi: fail trên staging thì không được promote. Fail trên production thì rollback trước, điều tra sau. Bạn chưa học quy trình rollback chi tiết ở bài 2.3, nên giờ chỉ cần nhớ một điều: rollback phải là một lệnh đã được chuẩn bị sẵn, không phải một cuộc họp.

Bài tập 3. Pipeline vừa deploy production, smoke test fail ở /healthz với HTTP 502. Trong 5 phút đầu bạn làm gì?

Xem gợi ý

Không sửa code trên production. Bước 1: rollback về bản trước (hoặc tắt traffic vào bản mới) để giành lại dịch vụ. Bước 2: xác nhận smoke test xanh trên bản cũ. Bước 3: mới đọc log để tìm nguyên nhân, thường là biến môi trường thiếu hoặc sai connection string. 502 nghĩa là app không lên được, không phải lỗi nghiệp vụ.

Smoke test chỉ trả lời một câu: _____. Một bộ smoke test tốt chạy xong trong khoảng _____. Khi smoke test trả exit code khác 0, pipeline phải _____. Sau deploy, ngoài health endpoint còn phải kiểm _____ để chắc đúng bản vừa build. Smoke test fail trên production thì việc đầu tiên là _____.

Những kiểu smoke test tôi từ chối duyệt

  • Chạy bằng tay theo checklist. Sẽ có ngày người ta bỏ qua vì "chỉ sửa CSS". Hãy để CI chạy, con người đọc kết quả.
  • Smoke test phụ thuộc dữ liệu thật của user. Nó sẽ fail khi user đổi dữ liệu, và bạn sẽ mất niềm tin vào chính công cụ của mình. Dùng test account, dữ liệu cố định.
  • Smoke test ghi dữ liệu vào production. Nếu buộc phải ghi, dùng account đánh dấu rõ và có bước dọn dẹp.
  • Không kiểm version. Không có bước này thì bạn đang test may rủi.
  • Smoke test dài hơn 5 phút. Lúc đó nó thành E2E trá hình, và không ai chờ nó trước khi tan làm.

Mẹo cuối: mỗi khi production có sự cố lọt qua, hãy thêm đúng một kiểm tra vào smoke test. Bộ test lớn dần từ sự cố thật, không từ trí tưởng tượng.

Mang theo gì sau bài này

  • Smoke test là lớp gác cửa sau deploy: nhanh, ít bước, chỉ happy path, chạy tự động.
  • Sáu điểm tối thiểu: health, version, trang chủ, một API chính, một luồng có auth, không rò secret.
  • Script nằm trong repo, dùng biến môi trường để trỏ đúng môi trường, và trả exit 1 khi fail.
  • Fail thì chặn, production fail thì rollback trước.

Bài tiếp theo là checkpoint của module: bạn sẽ tự tay ghép môi trường, secrets và smoke test thành một luồng deploy hoàn chỉnh, và tôi sẽ đóng vai người duyệt khó tính để hỏi bạn "nếu bước này hỏng thì rollback thế nào".

Front: Smoke test là gì? Back: Bộ kiểm tra ngắn chạy ngay sau deploy, chỉ xác nhận app còn sống và các luồng chính còn chạy.

Front: Smoke test khác E2E ở đâu? Back: Smoke test nhanh, ít bước, chỉ happy path; E2E đi sâu nhiều luồng người dùng và chạy lâu hơn.

Front: Mục tiêu thời gian của một bộ smoke test? Back: Dưới 2 phút, tối đa vài phút, để không ai có lý do bỏ qua.

Front: Smoke test chạy ở đâu? Back: Trên đúng môi trường vừa deploy, dùng biến môi trường trỏ tới môi trường đó.

Front: Smoke test fail thì pipeline làm gì? Back: Dừng ngay (fail-fast), không chạy bước tiếp theo.

Front: Health endpoint dùng để làm gì trong smoke test? Back: Xác nhận app trả 200 và nếu có thể thì kiểm cả kết nối DB, cache.

Front: Vì sao phải kiểm commit SHA sau deploy? Back: Để chắc bản đang chạy đúng là bản vừa build, không phải bản cũ còn cache.

Front: Vì sao smoke test nên read-only? Back: Để không tạo dữ liệu rác trên production; nếu buộc phải ghi thì dùng test account và dọn dẹp.

Front: Smoke test nên được lưu ở đâu? Back: Trong repo, cùng version với code, để được review và chạy lại được.

Front: Smoke test fail trên production, việc đầu tiên là gì? Back: Rollback về bản trước để giành lại dịch vụ, điều tra sau.

Front: Smoke test có thay thế unit test hay E2E không? Back: Không, nó chỉ là lớp gác cửa sau deploy.

Front: Vì sao không chạy smoke test bằng tay? Back: Dễ bị bỏ qua khi gấp và kết quả không nhất quán; hãy để CI chạy.

Front: Nên thêm kiểm tra mới vào smoke test khi nào? Back: Sau mỗi sự cố lọt qua production, thêm đúng một kiểm tra cho lỗi đó.