Product Management
Đăng nhập
ESC

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

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

Bài 49 — Disaster Recovery Drill in CI

Đã đọ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 một buổi sáng thứ Bảy, 3 giờ sáng. Điện thoại của bạn rung liên hồi vì cảnh báo PagerDuty: database production đã chết, khối lượng đơn hàng của một sàn thương mại điện tử đang về 0. Sếp gọi điện. Khách hàng phàn nàn tràn lên fanpage. Và bạn — người trực hôm đó — mở runbook khôi phục ra, đọc dòng đầu tiên: "Bước 1: chạy script restore từ bản backup mới nhất". Bạn chạy nó. Nó báo lỗi. Hoá ra bản backup gần nhất bị hỏng từ ba tuần trước mà không ai biết, vì… chưa ai từng thử khôi phục nó bao giờ.

Đây không phải câu chuyện hư cấu. Đây là kịch bản kinh điển mà mọi đội DevOps đều sợ nhưng ít đội chuẩn bị nghiêm túc. Bài học hôm nay nói về Disaster Recovery Drill trong CI — nghĩa là biến việc "diễn tập thảm hoạ" từ một hoạt động thủ công, hiếm hoi, dễ bị bỏ quên thành một phần tự động, có lịch, chạy trong pipeline giống như bạn chạy test hằng ngày.

Có ba sự thật cay đắng mà bài này muốn khắc vào đầu bạn:

  • Backup không được test = không biết nó có hoạt động hay không. Một bản backup chưa từng được restore chỉ là một tập tin với niềm tin mù quáng.
  • Runbook không được diễn tập = team không nhớ các bước khi báo động lúc 3 giờ sáng. Áp lực làm con người quên, và tài liệu chỉ có giá trị khi đã được "chạy thử bằng tay chân" nhiều lần.
  • Thời gian khôi phục (DR time) chính là MTTR — và cái gì không đo được thì không cải thiện được.
Bài này khác với Bài 21 (Rollback Strategy) — rollback là quay lại phiên bản trước khi deploy hỏng; DR drill là khôi phục toàn bộ hệ thống sau thảm hoạ hạ tầng (mất database, mất region, mất cluster). Và nó cũng khác Bài 52 (Continuous Verification) — verification xác nhận deploy mới hoạt động; DR drill xác nhận bạn có thể hồi sinh từ đống tro tàn.

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

DR Drill là gì?

Disaster Recovery Drill (diễn tập khôi phục thảm hoạ) là một bài kiểm tra có chủ đích, trong đó bạn giả lập một sự cố nghiêm trọng rồi thực hiện quy trình khôi phục để chứng minh rằng hệ thống có thể phục hồi trong khung thời gian cho phép. "Drill" (diễn tập) mang nghĩa như tập phòng cháy chữa cháy: bạn không đợi cháy thật mới học cách dùng bình cứu hoả.

Khi ta nói "DR drill trong CI", điều đặc biệt là: thay vì tổ chức một buổi diễn tập thủ công mỗi năm một lần (mà thường bị hoãn vô thời hạn), ta mã hoá kịch bản khôi phục thành pipeline và cho nó chạy tự động, định kỳ. Pipeline sẽ dựng một môi trường tạm, khôi phục dữ liệu từ backup, kiểm tra tính toàn vẹn, đo thời gian, rồi báo cáo — tất cả không cần con người can thiệp cho đến khi có lỗi.

RTO và RPO — hai con số bạn phải thuộc lòng

Không thể nói về DR mà không hiểu hai chỉ số nền tảng:

  • RTO (Recovery Time Objective) — thời gian tối đa cho phép để hệ thống hoạt động trở lại sau sự cố. Ví dụ RTO = 2 giờ nghĩa là dịch vụ phải "sống lại" trong vòng 2 giờ.
  • RPO (Recovery Point Objective) — lượng dữ liệu tối đa cho phép bị mất, tính theo thời gian. RPO = 15 phút nghĩa là bạn chấp nhận mất tối đa 15 phút dữ liệu gần nhất (backup phải chạy ít nhất mỗi 15 phút).
DR drill tồn tại để kiểm chứng rằng RTO và RPO thực tế của bạn khớp với cam kết. Rất nhiều đội đặt RTO = 1 giờ trên giấy, nhưng khi drill thật thì mất 6 giờ. Con số trên giấy vô nghĩa nếu chưa đo bằng drill.

Vì sao đưa vào CI?

Ba lý do cốt lõi:

  • Chống "backup thối lặng lẽ" (silent backup rot). Backup có thể hỏng vì thay đổi schema, đổi phiên bản engine, quyền truy cập bị revoke, dung lượng đầy. Drill tự động phát hiện ngay khi restore thất bại, thay vì phát hiện khi thảm hoạ đã xảy ra.
  • Giữ runbook "sống". Nếu pipeline chạy đúng các bước trong runbook, thì runbook luôn được xác nhận là còn đúng. Runbook lỗi thời sẽ làm drill đỏ.
  • Đo MTTR liên tục. Mỗi lần drill, bạn ghi lại thời gian khôi phục vào metric. Xu hướng này giúp bạn biết hệ thống đang tốt lên hay xấu đi.

Các cấp độ DR drill

Không phải drill nào cũng phá cả production. Có một thang mức độ từ nhẹ đến nặng:

  • Cấp 1 — Restore verification: Khôi phục backup vào môi trường sandbox, kiểm tra dữ liệu đọc được, đếm số bản ghi. Rẻ, an toàn, chạy hằng đêm.
  • Cấp 2 — Failover test: Chuyển traffic sang region/replica dự phòng trong môi trường staging, đo thời gian chuyển đổi.
  • Cấp 3 — GameDay / Chaos: Cố tình "giết" một thành phần thật (thường ở staging hoặc trong khung giờ thấp điểm), buộc team phản ứng theo runbook. Đây là cấp gần nhất với thực chiến.
Bài này tập trung nhiều nhất vào Cấp 1 và Cấp 2 vì chúng dễ tự động hoá trong CI; Cấp 3 cần con người và lịch trình có tổ chức.

Tình huống thực tế

Tình huống 1 — Sàn thương mại điện tử "TikiGo" và bản backup ma

Một sàn TMĐT giả định tên TikiGo ở TP.HCM có 4 triệu người dùng, chạy PostgreSQL trên một cloud provider. Họ cấu hình snapshot tự động mỗi 6 giờ và tự tin RPO = 6 giờ. Trong hai năm, chưa ai từng restore thử.

Một đêm, một câu lệnh DELETE sai điều kiện WHERE xoá mất bảng orders của ba tháng gần nhất. Đội SRE mở snapshot mới nhất ra restore… và phát hiện snapshot chỉ backup 3 trong 5 database, vì 8 tháng trước có người thêm hai database mới nhưng quên cập nhật danh sách backup. RPO thực tế của họ không phải 6 giờ — mà là 8 tháng đối với hai database bị bỏ sót.

Diễn giải: Vấn đề không phải backup không chạy. Backup chạy đều mỗi 6 giờ, dashboard xanh lè. Vấn đề là phạm vi backup âm thầm lệch khỏi thực tế hệ thống mà không ai kiểm chứng. Một DR drill Cấp 1 chạy hằng đêm — restore backup vào sandbox rồi so sánh danh sách database và số bảng với production — sẽ phát hiện sự sai lệch này trong đúng 24 giờ, chứ không phải 8 tháng.

Bài học rút ra: Đừng tin dashboard "backup thành công". Hãy tin cái test thực sự đọc được dữ liệu từ backup và đối chiếu với thực tế.

Tình huống 2 — Ví điện tử "MoMoPay" và runbook 3 giờ sáng

Một ví điện tử giả định tên MoMoPay có runbook khôi phục dài 14 bước cho tình huống mất Redis cluster (nơi lưu session và hạn mức giao dịch). Runbook được viết rất kỹ bởi kỹ sư trưởng, nhưng người này đã nghỉ việc.

Khi Redis cluster thật sự chết lúc 2h47 sáng, bạn trực hôm đó — một junior mới vào 4 tháng — mở runbook và kẹt ngay bước 6: "khôi phục AOF file từ bucket dự phòng". Nhưng đường dẫn bucket trong runbook đã đổi ba tháng trước sau một lần tái cấu trúc hạ tầng. Bạn loay hoay 40 phút, cuối cùng phải đánh thức cả team. Sự cố kéo dài 1 giờ 52 phút — vượt xa RTO cam kết 30 phút.

Sau sự cố, MoMoPay biến runbook thành pipeline drill chạy mỗi Chủ nhật: pipeline dựng một Redis giả, cố tình xoá nó, rồi thực thi từng bước trong runbook dưới dạng script. Ngay tuần đầu, drill đỏ ở bước 6 vì đường dẫn bucket sai — đúng chỗ đã hại họ. Sau khi sửa, drill xanh trở lại và mỗi lần hạ tầng đổi, drill lập tức bắt lỗi.

Diễn giải: Runbook bằng văn bản là kiến thức chết — nó đúng vào ngày viết ra rồi phân rã dần. Khi bạn ép runbook thành code chạy trong CI, mỗi bước trở thành một assertion sống, luôn được kiểm chứng.

Bài học rút ra: Mục tiêu cuối cùng là "runbook không cần trí nhớ" — người trực 3 giờ sáng chỉ cần bấm chạy một pipeline đã được diễn tập hàng chục lần, thay vì đọc và diễn giải một tài liệu có thể đã lỗi thời.

Tình huống 3 — Startup logistics "GiaoNhanh" đo MTTR và giảm được một nửa

Một startup logistics giả định GiaoNhanh ở Hà Nội đưa DR drill Cấp 1 vào GitHub Actions, chạy mỗi đêm 1h sáng. Mỗi lần drill, họ ghi lại restore_duration_seconds vào Prometheus qua Pushgateway.

Ba tháng đầu, thời gian restore trung bình là 48 phút cho database 220 GB. Nhìn vào biểu đồ, họ nhận ra phần lớn thời gian nằm ở bước tải backup từ storage về máy runner. Họ chuyển sang dùng runner nằm cùng region với storage và bật restore song song theo bảng. Thời gian giảm còn 19 phút. RTO cam kết với khách hàng lớn được hạ từ 2 giờ xuống 1 giờ một cách tự tin, vì họ có dữ liệu drill thực tế làm bằng chứng chứ không phải phỏng đoán.

Diễn giải: Khi DR time được đo liên tục như một metric CI, nó trở thành đối tượng để tối ưu giống như bất kỳ chỉ số hiệu năng nào khác. Bạn không thể cải thiện thứ mình không nhìn thấy.

Bài học rút ra: Đo trước, tối ưu sau. Metric drill biến MTTR từ một con số mơ hồ thành mục tiêu kỹ thuật cụ thể.

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

Dưới đây là cách xây một DR drill Cấp 1 (restore verification) chạy trong GitHub Actions cho một database PostgreSQL. Nguyên tắc áp dụng được cho GitLab CI hay Jenkins tương tự.

Bước 1 — Xác định RTO/RPO và ngưỡng chấp nhận. Trước khi viết code, thống nhất với team: RTO là bao nhiêu? Drill sẽ fail nếu thời gian restore vượt ngưỡng nào? Ví dụ đặt ngưỡng cảnh báo 30 phút, ngưỡng fail 60 phút.

Bước 2 — Chuẩn bị nguồn backup an toàn. Drill phải đọc từ bản backup thật (hoặc bản sao chỉ-đọc của nó), nhưng tuyệt đối restore vào môi trường tạm cách ly, không bao giờ đụng vào production. Cấp cho pipeline một credential chỉ có quyền đọc backup.

Bước 3 — Viết workflow diễn tập. Ví dụ khung GitHub Actions:

name: dr-drill-restore
on:
  schedule:
    - cron: '0 18 * * *'   # 1h sáng giờ VN (UTC+7)
  workflow_dispatch:        # cho phép chạy tay khi cần

jobs:
  restore-verify:
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:16
        env:
          POSTGRES_PASSWORD: drill
        ports: ['5432:5432']
    steps:
      - name: Tải backup mới nhất
        run: aws s3 cp "$(aws s3 ls s3://backups/pg/ | sort | tail -1 | awk '{print $4}')" ./latest.dump
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.DR_READONLY_KEY }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.DR_READONLY_SECRET }}

      - name: Khôi phục và đo thời gian
        run: |
          START=$(date +%s)
          pg_restore -h localhost -U postgres -d postgres --no-owner ./latest.dump
          END=$(date +%s)
          echo "RESTORE_SECONDS=$((END-START))" >> $GITHUB_ENV

      - name: Kiểm tra tính toàn vẹn
        run: |
          COUNT=$(psql -h localhost -U postgres -t -c "SELECT count(*) FROM orders;")
          echo "Số đơn hàng khôi phục được: $COUNT"
          test "$COUNT" -gt 0 || (echo "BACKUP RỖNG!" && exit 1)

      - name: Fail nếu vượt RTO
        run: test "$RESTORE_SECONDS" -lt 3600 || (echo "Vượt RTO 60 phút" && exit 1)

Bước 4 — Thêm kiểm tra ngữ nghĩa, không chỉ đếm bản ghi. Đếm số bản ghi > 0 là mức tối thiểu. Tốt hơn: chạy vài truy vấn "smoke test" xác nhận dữ liệu có nghĩa — ví dụ tổng số dư ví phải khớp với một checksum đã lưu, hoặc đơn hàng gần nhất có timestamp trong khoảng RPO.

Bước 5 — Đẩy metric ra hệ quan sát. Sau mỗi drill, push restore_duration_seconds và drill_success (1/0) lên Prometheus Pushgateway hoặc lưu vào một bảng. Đây là dữ liệu để vẽ xu hướng MTTR (liên quan Bài 26 — Observability và Bài 57 — Pipeline Metrics).

Bước 6 — Kết nối cảnh báo. Khi drill fail, gửi thông báo vào kênh Slack/Telegram của team (Bài 48 — ChatOps). Drill đỏ phải được đối xử nghiêm túc như production alert, vì nó báo trước một thảm hoạ tiềm ẩn.

Bước 7 — Nâng dần độ khó. Khi drill Cấp 1 đã ổn định, thêm Cấp 2 (failover test trên staging), rồi lên lịch GameDay Cấp 3 hằng quý với con người tham gia.

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

Lỗi 1 — Drill đọc backup nhưng không restore. Nhiều đội chỉ kiểm tra "file backup tồn tại và có kích thước hợp lý". Điều này vô dụng: một dump hỏng vẫn có kích thước lớn. Bắt buộc phải restore thực sự rồi truy vấn dữ liệu ra.

Lỗi 2 — Drill chạm vào production. Đây là sai lầm nguy hiểm nhất. Một drill cấu hình sai có thể ghi đè lên database thật hoặc restore vào cluster đang chạy. Mẹo: dùng credential riêng chỉ có quyền tạo môi trường tạm, và đặt tên môi trường drill có tiền tố rõ ràng như dr-drill-*.

Lỗi 3 — Drill xanh giả (green rot). Drill chạy nhưng các assertion quá lỏng, luôn xanh dù backup đã hỏng. Mẹo: định kỳ cố tình cho một backup hỏng vào để chắc chắn drill biết cách fail. Một drill chưa từng đỏ là một drill đáng ngờ.

Lỗi 4 — Chỉ drill database, quên phần còn lại. Khôi phục database xong nhưng ứng dụng vẫn không chạy được vì thiếu secret, thiếu config, DNS chưa trỏ lại. Mẹo: drill Cấp 2 nên khôi phục cả stack tối thiểu để chạy được một request end-to-end, chứ không chỉ dữ liệu.

Lỗi 5 — Không đo thời gian. Drill xanh nhưng không ghi lại mất bao lâu. Bạn mất cơ hội cải thiện MTTR. Mẹo: luôn ghi restore_duration mỗi lần và vẽ xu hướng.

Lỗi 6 — Quên chi phí. Restore một database 500 GB mỗi đêm tốn tiền compute và egress. Mẹo: cân bằng tần suất — drill đầy đủ hằng tuần, drill nhẹ (restore một subset) hằng đêm (liên quan Bài 50 — Cost Optimization).

Mẹo vàng: Hãy để một thành viên junior trong team thực hiện drill thủ công ít nhất một lần mỗi quý. Nếu người ít kinh nghiệm nhất có thể khôi phục hệ thống bằng runbook, thì lúc 3 giờ sáng bất kỳ ai cũng làm được.

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

  • Xác định RTO/RPO của bạn. Viết ra RTO và RPO thực tế mà hệ thống bạn đang làm cam kết. Nếu chưa có, hãy đề xuất một con số hợp lý dựa trên mức độ quan trọng của dịch vụ.
  • Dựng drill Cấp 1. Lấy một database bất kỳ (dùng Docker PostgreSQL local nếu chưa có production), tạo backup, rồi viết một pipeline GitHub Actions hoặc GitLab CI restore backup đó vào container tạm và đếm số bản ghi của một bảng quan trọng. Cho pipeline fail nếu số bản ghi bằng 0.
  • Đo và ghi thời gian. Bổ sung bước đo restore_duration_seconds và in ra log. Chạy drill 3 lần, ghi lại ba con số. Chúng có ổn định không? Vì sao chênh lệch?
  • Thử phá vỡ nó. Cố tình cung cấp một backup hỏng (ví dụ file dump bị cắt cụt) và xác nhận drill của bạn fail đúng cách với thông báo rõ ràng. Nếu nó vẫn xanh, hãy siết chặt assertion.
  • Viết runbook 5 bước. Viết runbook khôi phục cho hệ thống của bạn dưới 5 bước, rồi tự hỏi: mỗi bước có tương ứng với một lệnh script cụ thể không? Bước nào đang phụ thuộc vào "trí nhớ con người" và cần được mã hoá?

Tóm tắt

DR drill trong CI biến một hoạt động phòng thủ dễ bị lãng quên thành một phần tự động, có nhịp điệu, luôn tỉnh táo trong pipeline của bạn. Ba điều cần khắc cốt ghi tâm:

  • Backup chưa được restore chỉ là niềm tin, không phải bảo hiểm. Drill Cấp 1 restore thật và đối chiếu dữ liệu là mức tối thiểu phải có.
  • Runbook phải "sống" bằng cách chạy trong CI. Khi mỗi bước khôi phục là một script được kiểm chứng định kỳ, người trực lúc 3 giờ sáng không còn phải dựa vào trí nhớ hay một tài liệu có thể đã lỗi thời.
  • DR time chính là MTTR — hãy đo nó mỗi lần drill. Chỉ khi có con số, bạn mới cải thiện được, như GiaoNhanh đã giảm restore từ 48 xuống 19 phút.
Nguyên tắc cuối cùng: một drill chưa từng đỏ là một drill đáng ngờ. Hãy cố tình làm nó fail để chắc chắn nó biết cách bảo vệ bạn khi thảm hoạ thật gõ cửa. Từ TikiGo mất 8 tháng dữ liệu vì backup lệch phạm vi, đến MoMoPay kẹt runbook lúc 2h47 sáng — mọi thảm hoạ đều có thể được lường trước bằng một buổi diễn tập rẻ tiền chạy tự động mỗi đêm. Diễn tập khi bình yên để không hoảng loạn khi bão đến.

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