Incident response và blameless postmortem
Bài cuối Observability: xử lý sự cố. Lần trước payment lỗi, 6 người cùng nhảy vào một kênh Slack, ai cũng restart Pod, không ai báo khách hàng. Nên tổ chức lại thế nào?
Đó là cảnh "ai cũng là anh hùng" rất hay gặp. Công cụ thì bạn đã đủ từ 19.1 tới 19.5; cái thiếu là quy trình và vai trò. Bài này đi từ lúc alert kêu tới lúc postmortem xong.
1. Bài toán
Thứ Sáu 20:05, alert CheckoutErrorBudgetFastBurn kêu (bài 19.2). 25% POST /orders trả 502. 20:07 có 6 người trong kênh #shop-alerts:
An: restart payment-service nhé?
Bình: mình đang scale postgres lên
Chi: ai deploy gì không?
Dũng: mình vừa restart rồi đó
CS: khách hỏi sao không thanh toán được, trả lời sao ạ?
(không ai trả lời CS; 40 phút sau mới có người thấy bản deploy 19:50)
2. Vấn đề
- Không ai điều phối: nhiều người cùng thay đổi hệ thống, làm nhiễu nhau và nhiễu tín hiệu.
- Cố tìm root cause trước khi cầm máu: user đau thêm 40 phút.
- Không ai giao tiếp ra ngoài: CS, sếp, khách tự đoán.
- Xong sự cố thì quên; tháng sau lặp lại y hệt. Hoặc họp "tìm người có lỗi", lần sau không ai dám nói thật.
3. Giải pháp: vai trò, giai đoạn, postmortem
| Vai trò | Làm gì | Không làm gì |
|---|---|---|
| Incident Commander (IC) | Điều phối, quyết định, giao việc, đặt nhịp cập nhật | Không tự gõ lệnh sửa |
| Ops / Tech lead | Điều tra, thực hiện thay đổi (sau khi IC đồng ý) | Không tự ý thay đổi ngoài kế hoạch |
| Comms lead | Cập nhật status page, CS, quản lý theo nhịp 15–30 phút | Không hứa thời gian khắc phục khi chưa chắc |
| Scribe | Ghi timeline: giờ, ai làm gì, quan sát gì |
Sự cố nhỏ: một người kiêm IC và ops. Sự cố SEV1: tách vai.
| Severity | Ví dụ shop-app | Phản ứng |
|---|---|---|
| SEV1 | Không ai checkout được, mất dữ liệu, lộ dữ liệu | Page ngay, đủ vai, status page, báo lãnh đạo |
| SEV2 | Một phần user lỗi (một cổng thanh toán), chậm rõ rệt | Page on-call, IC, status page nếu khách thấy |
| SEV3 | Tính năng phụ lỗi, có cách vòng (email xác nhận chậm) | Giờ hành chính, ticket |
Ba giai đoạn, theo đúng thứ tự: Triage (nặng cỡ nào, ảnh hưởng ai) → Mitigate (cầm máu: rollback, tắt flag, chuyển traffic) → Resolve (sửa gốc, có thể ngày hôm sau). Sau đó là postmortem.
4. Code từng bước
Bước 1: Tuyên bố sự cố (một người, trong 5 phút)
/incident declare sev2 "Checkout 25% 502" # bot tạo kênh #inc-2026-10-09-checkout
IC: Dũng. Ops: An. Comms: Chi. Scribe: Bình.
Mọi thay đổi lên prod phải nói trong kênh trước khi làm.
Cập nhật tiếp theo: 20:25.
Bước 2: Triage bằng tín hiệu đã có
# Có gì thay đổi gần đây? (nghi phạm số 1)
kubectl rollout history deploy/payment-service -n shop
helm history shop -n shop
# Grafana: dashboard checkout có annotation deploy lúc 19:50
# Lỗi ở đâu? (RED theo service)
sum by (app) (rate(http_server_requests_seconds_count{namespace="shop",outcome="SERVER_ERROR"}[5m]))
# Trace lỗi: TraceQL
{ status = error && resource.service.name = "payment-service" }
# Log của một trace lỗi (Loki)
{namespace="shop", app="payment-service"} | json | level="ERROR"
Bước 3: Mitigate trước, điều tra sau
# Có deploy gần đây → rollback ngay, không cần chắc chắn 100%
kubectl rollout undo deploy/payment-service -n shop
kubectl rollout status deploy/payment-service -n shop
# Hoặc tắt tính năng mới bằng feature flag (nhanh hơn rollback)
curl -X PATCH "$FLAGS_API/flags/new-fee-engine" \
-H "Authorization: Bearer ${FLAGS_TOKEN}" -d '{"enabled":false}'
# Các lựa chọn khác: scale out, chuyển cổng thanh toán dự phòng, bật degraded mode
// Degraded mode: không cho inventory chậm kéo checkout chết theo
@CircuitBreaker(name = "inventory", fallbackMethod = "reserveLater")
public Reservation reserve(String orderId, List<Item> items) { ... }
private Reservation reserveLater(String orderId, List<Item> items, Throwable t) {
outbox.save(new ReserveCommand(orderId, items)); // xử lý sau qua Kafka
return Reservation.pending(orderId);
}
Bước 4: Xác nhận và đóng
# Error ratio về dưới SLO và burn rate alert tự tắt
curl -s 'localhost:9090/api/v1/query?query=sli:checkout_error_ratio:rate5m'
# Comms: status page → "Monitoring" → 30 phút ổn → "Resolved"
# IC: hạ sự cố, hẹn postmortem trong 2–5 ngày làm việc
5. Cách hoạt động: dòng thời gian và postmortem
19:50 deploy payment 3.2
20:03 lỗi bắt đầu ─┐
20:05 alert kêu ┘ MTTD (time to detect) = 2'
20:08 tuyên bố SEV2, IC nhận
20:12 thấy annotation deploy 19:50
20:14 rollback ─┐
20:19 hết lỗi ┘ MTTM (time to mitigate) = 16'
20:50 resolved, theo dõi
Ngày hôm sau: sửa gốc, thêm test → MTTR tính tới lúc đóng
Template postmortem (1–3 trang, viết trong vài ngày, mọi người đọc được):
# Postmortem: Checkout 502, 2026-10-09 (SEV2)
## Tóm tắt 2–3 câu: chuyện gì, ảnh hưởng, đã khắc phục thế nào
## Ảnh hưởng 25% checkout lỗi 16 phút, ~1 900 đơn, 31% error budget tháng
## Timeline từ scribe, theo giờ (như trên)
## Nguyên nhân & yếu tố góp phần
- Bản 3.2 đổi timeout gọi bank-gateway từ 5s xuống 500ms (config sai đơn vị)
- Test tích hợp dùng mock bank trả lời 20ms nên không bắt được
- Canary chỉ kiểm tra health, không kiểm tra error rate
## Điều gì tốt alert burn-rate kêu sau 2'; rollback 1 lệnh
## Điều gì chưa tốt mất 4' để tìm ra deploy gần đây; CS không được báo 25'
## Action items (có owner, có hạn, có ticket)
| Việc | Loại | Owner | Hạn |
| Kiểu Duration cho timeout trong config | Phòng | An | 16/10 |
| Canary tự rollback khi error rate > 1% | Phát hiện| Bình | 30/10 |
| Bot tự gắn "deploy gần đây" vào kênh incident| Giảm thời gian | Chi | 23/10 |
| Thêm CS vào danh sách thông báo SEV2 | Quy trình| Dũng | 12/10 |
6. So sánh: blameless vs tìm người có lỗi
| Blame | Blameless | |
|---|---|---|
| Câu hỏi | "Ai làm hỏng?" | "Vì sao hệ thống cho phép điều này xảy ra?" |
| Kết luận điển hình | "An cẩn thận hơn" | "Config timeout cần kiểu Duration và có validate" |
| Hành vi lần sau | Giấu lỗi, báo muộn | Báo sớm, kể đủ chi tiết |
| Hiệu quả | Lỗi lặp lại với người khác | Sửa hệ thống, ai cũng được bảo vệ |
Blameless không có nghĩa là không ai chịu trách nhiệm: chịu trách nhiệm là làm action item, không phải bị phạt.
7. Khi nào nên dùng / không nên dùng
- Tuyên bố sự cố sớm, hạ cấp sau cũng được. Tuyên bố muộn mới tốn kém.
- Postmortem cho mọi SEV1/SEV2 và cho near-miss đáng sợ; không cần cho mọi SEV3.
- Action item phải cụ thể, có owner, có hạn, được theo dõi. "Cẩn thận hơn" không phải action item.
- Ưu tiên action item sửa hệ thống (guardrail, tự động hóa) hơn thêm bước thủ công.
- Diễn tập: game day, chaos test, để lần đầu dùng runbook không phải là lúc 2 giờ sáng.
Lúc sự cố mình muốn tìm cho ra nguyên nhân trước rồi mới sửa, chứ rollback mù quáng nhỡ không phải do deploy thì sao?
Bạn hiểu gần đúng, chỉ cần chỉnh một điểm: mục tiêu đầu tiên là ngưng tác động lên người dùng, không phải hiểu. Rollback không mù quáng nếu bạn coi nó là một thí nghiệm rẻ và đảo ngược được:
- Thống kê thực tế: phần lớn sự cố đến từ thay đổi (deploy, config, flag, migration). "Có gì thay đổi trong 1–2 giờ qua?" là câu hỏi số 1.
- Rollback mất vài phút. Điều tra root cause có thể mất hàng giờ, trong lúc đó khách vẫn lỗi.
- Rollback xong mà hết lỗi → đã có bằng chứng mạnh. Không hết → loại được một giả thuyết, đi tiếp.
Cẩn thận ở hai chỗ:
- Bằng chứng trước khi mất: trước khi rollback/restart, chụp nhanh thứ sẽ mất:
jcmd Thread.print,JFR.dump,kubectl logs --previous, link trace lỗi. Mất 1–2 phút nhưng đáng. - Rollback không an toàn khi bản mới đã chạy migration phá ngược (drop cột) hoặc đã ghi dữ liệu định dạng mới. Đây là lý do nên làm migration kiểu expand/contract (xem CD & zero-downtime). Lúc đó dùng feature flag hoặc roll forward bản sửa.
Viết postmortem mà phải ghi "An config sai timeout" thì có phải là đổ lỗi cho An không nhỉ? Mình không biết viết thế nào cho khéo.
Câu hỏi hay. Blameless không phải là giấu sự thật, mà là mô tả hành động trong bối cảnh và hỏi vì sao hệ thống để hành động đó gây hại:
| Tránh | Nên viết |
|---|---|
| "An config sai timeout" | "Bản 3.2 đặt bank.timeout=500, hiểu là ms, trong khi code cũ hiểu là giây × 0.01" |
| "Reviewer không để ý" | "Diff config không hiển thị đơn vị; review không có checklist cho timeout" |
| "Lẽ ra phải test kỹ hơn" | "Test tích hợp dùng mock trả lời 20ms nên không mô phỏng được bank chậm" |
Một mẹo: thay tên người bằng vai trò ("engineer deploy", "on-call") và hỏi "một người giỏi khác ở cùng vị trí, cùng thông tin, có làm khác không?" Nếu không, lỗi nằm ở hệ thống. Kỹ thuật "5 whys" giúp đi từ hành động tới guardrail còn thiếu, nhưng nhớ là sự cố thường có nhiều yếu tố góp phần, không phải một root cause duy nhất.
Phỏng vấn hỏi về xử lý sự cố và postmortem thì trả lời sao?
Q1. Kể cách bạn xử lý một sự cố production.
🇻🇳 Tiếng Việt:
Em tuyên bố sự cố và gán vai: incident commander điều phối, một người điều tra, một người lo giao tiếp, một người ghi timeline. Sau đó triage bằng dashboard SLO và RED để biết ảnh hưởng ai và nặng cỡ nào, rồi hỏi ngay có gì thay đổi gần đây. Em ưu tiên mitigate: rollback, tắt feature flag, chuyển traffic, trước khi tìm root cause, nhưng chụp lại thread dump, log và trace trước khi restart. Khi ổn định thì theo dõi rồi đóng, và hẹn postmortem.
🇬🇧 English:
I declare an incident and assign roles: an incident commander to coordinate, someone to investigate, someone on communications, and a scribe for the timeline. Then I triage with the SLO and RED dashboards to see who's affected and how badly, and immediately ask what changed recently. I prioritize mitigation such as a rollback, disabling a feature flag, or shifting traffic before hunting for the root cause, but I capture thread dumps, logs and traces before restarting anything. Once stable we monitor, close, and schedule a postmortem.
Q2. Blameless postmortem là gì, gồm những phần nào?
🇻🇳 Tiếng Việt:
Là tài liệu phân tích sự cố tập trung vào hệ thống và quy trình thay vì cá nhân, để mọi người dám kể đầy đủ. Gồm tóm tắt, ảnh hưởng có số liệu, timeline, nguyên nhân và các yếu tố góp phần, điều gì làm tốt và chưa tốt, và action item có owner, có hạn, được theo dõi. Giá trị nằm ở action item được hoàn thành, không ở bản tài liệu.
🇬🇧 English:
It's an incident analysis that focuses on systems and processes rather than individuals, so people feel safe sharing the full story. It covers a summary, quantified impact, a timeline, the cause and contributing factors, what went well and what didn't, and action items with owners and due dates that are actually tracked. The value is in completed action items, not in the document itself.
Q3. Vì sao mitigate trước, root cause sau?
🇻🇳 Tiếng Việt:
Vì mỗi phút điều tra là thêm một phút người dùng bị ảnh hưởng và tiêu error budget. Phần lớn sự cố do thay đổi gần đây, nên rollback hay tắt flag là thí nghiệm rẻ, đảo ngược được, và thường kết thúc tác động trong vài phút. Root cause có thể tìm sau với dữ liệu đã thu thập. Ngoại lệ là khi rollback không an toàn, ví dụ migration phá ngược, thì em dùng feature flag hoặc roll forward.
🇬🇧 English:
Because every minute spent investigating is another minute of user impact and error budget burn. Most incidents come from recent changes, so a rollback or flag toggle is a cheap, reversible experiment that often ends the impact within minutes. The root cause can be found later from the data you captured. The exception is when rollback is unsafe, such as after a backward-incompatible migration, in which case I use a feature flag or roll forward.
Q4. MTTD và MTTR là gì, giảm thế nào?
🇻🇳 Tiếng Việt:
MTTD là thời gian trung bình để phát hiện sự cố, MTTR là thời gian để khôi phục. Giảm MTTD bằng alert theo SLO burn rate và synthetic check, không đợi khách báo. Giảm MTTR bằng runbook, dashboard có annotation deploy, trace và log nối nhau qua traceId, rollback một lệnh, feature flag, canary tự rollback, và diễn tập định kỳ.
🇬🇧 English:
MTTD is mean time to detect an incident and MTTR is mean time to restore service. I reduce MTTD with SLO burn-rate alerts and synthetic checks rather than waiting for customers to report. I reduce MTTR with runbooks, dashboards annotated with deploys, traces and logs linked by trace ID, one-command rollback, feature flags, canaries that roll back automatically, and regular game days.
Cách nhớ ngắn gọn
- Vai trò: IC điều phối, ops sửa, comms nói, scribe ghi.
- Triage → Mitigate → Resolve → Postmortem.
- "Có gì thay đổi?" là câu hỏi đầu tiên; chụp bằng chứng rồi rollback.
- Blameless: hỏi "vì sao hệ thống cho phép", không hỏi "ai".
- Action item: cụ thể, owner, hạn, theo dõi.
Tổng kết chương 19: Observability
| Bài | Ý chính | Tình huống trên shop-app | Công cụ |
|---|---|---|---|
| 19.1 Log, metric, trace | 4 tín hiệu trả lời 4 câu hỏi; JSON log; traceId nối; cardinality và chi phí | Checkout chậm, log 12 Pod trộn lẫn | Structured logging, MDC, Loki |
| 19.2 SLI, SLO, alert | Đo trải nghiệm user, error budget, page theo triệu chứng, burn rate | 40 alert CPU/tuần, bỏ sót lỗi payment thật | Recording rule, Alertmanager, RED/USE |
| 19.3 Prometheus & Grafana | Pull model, 4 loại metric, PromQL, histogram_quantile | Prometheus OOM vì label ID; p99 tính sai | ServiceMonitor, PromQL, Grafana |
| 19.4 OpenTelemetry | SDK/agent, Collector, head vs tail sampling, W3C traceparent | Chuyển Zipkin → Tempo, service cũ không sửa code | OTel agent, Collector, Tempo |
| 19.5 Profiling JVM | Continuous profiling, CPU/wall/alloc/lock, flame graph, span profiles | Bản 3.2 tốn CPU do Pattern.compile | Pyroscope, JFR, async-profiler |
| 19.6 Incident & postmortem | Vai trò, triage → mitigate → resolve, blameless, action items | Payment 502 sau deploy, 6 người cùng restart | Runbook, rollback, feature flag, template |
Tự kiểm tra
Incident commander có nên tự gõ lệnh sửa không?
Không (với sự cố lớn): IC điều phối và quyết định, để người khác thực hiện.
Trước khi restart Pod lỗi nên làm gì?
Thu bằng chứng: thread dump, JFR dump, log, link trace lỗi.
Khi nào rollback không an toàn?
Khi bản mới đã chạy migration phá ngược hoặc ghi dữ liệu mà bản cũ không đọc được.
"Engineer cẩn thận hơn" có phải action item tốt?
Không. Cần thay đổi hệ thống cụ thể, có owner và hạn.
Giảm MTTD bằng gì?
Alert theo SLO burn rate, synthetic check; không đợi khách báo.