Khi bạn "sao lưu" một ngôi nhà, bạn định sao lưu cái gì? Có hai câu trả lời rất khác nhau mà người ta hay nhập làm một. Một là bản thiết kế: có mấy phòng, tường ở đâu, ống nước nối ra sao, két sắt đặt góc nào. Hai là đồ đạc bên trong: quần áo trong tủ, giấy tờ trong két, thức ăn trong tủ lạnh. Chụp lại bản thiết kế thì nhanh và gọn kinh khủng — vài trang giấy cho cả một biệt thự. Nhưng khôi phục từ nó, bạn được một ngôi nhà đúng từng centimet mà trống rỗng. Snapshot etcd của Kubernetes chính là bản thiết kế đó, và bài này đo để chỉ rõ ranh giới ai cũng tưởng mình biết nhưng hay bước nhầm qua.

Sao lưu Kubernetes có hai nửa và người ta hay chỉ làm một nửa. Bài này đo cả hai để thấy rõ ranh giới.

etcd snapshot: nhanh và nhỏ

cả thư mục /var/lib/etcd   206.569.472 byte
snapshot                    13.848.608 byte
thời gian chụp                    0,1 giây

Snapshot nhỏ hơn thư mục 14,9 lần. Phần lớn chênh lệch là WAL — 184 MB nhật ký ghi trước mà etcd giữ để phục hồi sau sự cố. Snapshot không cần chúng.

Lệnh:

kubectl exec -n kube-system etcd-<node> -- etcdctl \
  --cacert=/etc/kubernetes/pki/etcd/ca.crt \
  --cert=/etc/kubernetes/pki/etcd/server.crt \
  --key=/etc/kubernetes/pki/etcd/server.key \
  snapshot save /var/lib/etcd/snap.db

Ghi vào /var/lib/etcd vì đó là thư mục gắn từ node — container etcd không có shell, và như đo được ở phần 42, kubectl cp cũng cần shell. Ghi vào đường dẫn có gắn từ ngoài là cách đơn giản nhất lấy tệp ra.

Kiểm tra snapshot trước khi tin nó:

etcdutl --write-out=table snapshot status /var/lib/etcd/snap.db
HASH       REVISION   TOTAL KEYS   TOTAL SIZE   VERSION
799a7d4e   117589     544          14 MB        3.6.0

Có mã băm, có số khoá. Một snapshot hỏng lộ ra ngay ở đây thay vì lộ ra lúc khôi phục.

Đừng sao lưu bằng cách copy /var/lib/etcd. Nó vừa to gấp 15 lần vừa không nhất quán: các tệp đang được ghi trong lúc bạn copy, và kết quả có thể không mở được. snapshot save chụp một trạng thái nhất quán tại một revision.

Snapshot chứa đối tượng, không chứa dữ liệu

Đây là phép đo quan trọng nhất bài. Tôi ghi 52.428.800 byte vào một PersistentVolume rồi chụp lại:

snapshot trước   13.848.608 byte
snapshot sau     13.983.776 byte
chênh lệch          135.168 byte

135 KB cho 50 MB dữ liệu — 0,26%. Và phần tăng đó là do có thêm đối tượng PVC, PV, Pod, chứ không phải do dữ liệu.

Điều này hiển nhiên khi nói ra: etcd lưu định nghĩa của PersistentVolume, còn nội dung nằm trên đĩa của node hoặc trên hệ thống lưu trữ ngoài. Nhưng nó không hiển nhiên lúc 3 giờ sáng, khi ai đó nói "chúng ta có snapshot etcd hàng đêm".

Khôi phục từ snapshot etcd cho lại mọi đối tượng — Deployment, Service, Secret, PVC — nhưng PVC sẽ trỏ tới một PV rỗng, hoặc tới một PV không còn tồn tại. Cơ sở dữ liệu quay lại với đúng cấu hình cũ và không có dòng dữ liệu nào. Đó chính là chỗ Velero (hoặc tương đương) chen vào: nó chụp cả ảnh đĩa của volume, qua ảnh chụp của nhà cung cấp đám mây hoặc qua restic/kopia sao chép ở mức tệp.

kubectl get all không get all

Cách sao lưu "nghèo" mà nhiều người dùng:

kubectl get all -A -o yaml > sao-luu.yaml

Đo trên cụm này: 34 loại tài nguyên có phạm vi namespace, get all lấy được 8 loại. Bị bỏ sót:

Loại Số lượng
ServiceAccount 56
ConfigMap 20
RoleBinding 17
Role 16
PVC 5
Secret 3
Ingress 2

119 đối tượng, kể cả mọi Secret. Bản "sao lưu" đó khôi phục lại một cụm không có mật khẩu, không có cấu hình, không có phân quyền.

Cái tên all gây hiểu nhầm nghiêm trọng: nó chỉ nghĩa là "nhóm tài nguyên có nhãn all", một danh sách cố định do API server định nghĩa. Muốn lấy đủ thì phải liệt kê loại tài nguyên:

kubectl api-resources --verbs=list --namespaced -o name \
  | tr '\n' ',' | sed 's/,$//' \
  | xargs -I{} kubectl get {} -A -o yaml > that-su-tat-ca.yaml

Vẫn còn thiếu tài nguyên phạm vi cụm (Namespace, CRD, ClusterRole, PV) — và vẫn không có dữ liệu volume.

Ba tầng, chọn theo cái bạn sợ mất

Tầng Công cụ Cứu được gì
Đối tượng, toàn cụm etcdctl snapshot Cụm sập, control plane hỏng
Đối tượng, theo namespace Velero, hoặc xuất YAML Xoá nhầm, chuyển cụm
Dữ liệu volume Velero + ảnh chụp đĩa Mất dữ liệu thật

Snapshot etcd là tầng rẻ nhất và ít người bỏ qua nhất — nhưng nó cũng là tầng khó dùng nhất: khôi phục nó là khôi phục toàn bộ cụm về một thời điểm, không chọn lọc được namespace nào. Nếu sự cố hay gặp của bạn là "ai đó xoá nhầm một namespace" thì snapshot etcd gần như vô dụng, và một bản xuất YAML theo namespace lại cứu được trong ba mươi giây.

Bốn điều làm sai thường gặp

Snapshot nằm trên chính node đó. Node chết là mất cả cụm lẫn bản sao lưu. Phải đẩy ra ngoài ngay sau khi chụp.

Không ai từng khôi phục thử. Bản sao lưu chưa khôi phục thử không phải bản sao lưu, nó là một tệp. Phần sau đo đúng chuyện này.

Quên chứng chỉ. Snapshot etcd không chứa /etc/kubernetes/pki. Thiếu chúng thì có snapshot cũng không dựng lại được control plane. Sao lưu cả thư mục đó — nó nhỏ.

Chỉ chụp một lần mỗi đêm. Với --event-ttl một giờ ở phần 41 thì sự kiện mất là chấp nhận được, nhưng mất 24 giờ thay đổi cấu hình thì không. Chụp thường xuyên hơn: snapshot mất 0,1 giây và 14 MB.

Mẫu số chung: một bản sao lưu chụp định nghĩa hoặc chụp nội dung — hiếm khi cả hai

Hai phép đo của bài này gói lại thành một nguyên tắc rộng hơn Kubernetes. Mọi cơ chế sao lưu đều rơi vào một trong hai loại: sao lưu sơ đồ/định nghĩa (metadata, cấu hình, hình dạng của hệ thống) và sao lưu nội dung (byte dữ liệu thật). Chúng khác nhau hàng chục lần về kích thước và hàng nghìn lần về "sửa được gì khi khôi phục" — nhưng nghe qua thì cả hai đều gọi là "backup", nên người ta ký một cái là yên tâm cả hai. 135 KB cho 50 MB dữ liệu chính là con số nói thẳng: cái bạn có là sơ đồ, không phải đồ đạc.

Trong thế giới cơ sở dữ liệu, đây đúng là pg_dump --schema-only (chỉ cấu trúc bảng, index, quyền — vài KB) đối lại một bản dump đầy đủ hoặc PITR chứa từng dòng dữ liệu. Trong Docker, image là "công thức" — các lớp hệ thống tệp lúc build — còn dữ liệu chạy thật sống trong volume không nằm trong image; sao lưu image mà quên volume là đúng cái bẫy etcd-không-chứa-PV ở đây. Với Terraform / IaC, state file mô tả có những tài nguyên nào chứ không phải nội dung bên trong chúng. Và git chỉ theo dõi tệp đã track — một bản clone không phải toàn bộ trạng thái làm việc của bạn (những tệp bị .gitignore biến mất).

Bài học thứ hai, từ get all: tên gọi và mặc định của công cụ hay nói dối về phạm vi. all chỉ là 8/34 loại; một lệnh nghe như "lấy tất cả" lặng lẽ bỏ quên mọi Secret. Đây là họ hàng của SELECT * không lấy cột đã ẩn, của docker save không kèm volume, của cp -r bỏ qua tệp ẩn.

Mẫu số chung: trước khi tin một bản sao lưu, hãy hỏi hai câu — nó chụp định nghĩa hay nội dung (và cái tôi sợ mất thuộc loại nào), và phạm vi thật của nó gồm những gì, chứ đừng tin cái tên. Một bản sao lưu chỉ trở thành bản sao lưu sau khi bạn đã khôi phục thử nó một lần và thấy đúng thứ mình cần quay lại — mọi thứ trước đó chỉ là một tệp nằm chờ làm bạn thất vọng đúng lúc tệ nhất.

Muốn tự kiểm, chụp thử ngay bây giờ và xác nhận nó đọc được:

N=etcd-$(kubectl get node -l node-role.kubernetes.io/control-plane -o jsonpath='{.items[0].metadata.name}')
P=/etc/kubernetes/pki/etcd
kubectl exec -n kube-system $N -- etcdctl \
  --cacert=$P/ca.crt --cert=$P/server.crt --key=$P/server.key \
  snapshot save /var/lib/etcd/thu.db
kubectl exec -n kube-system $N -- etcdutl \
  --write-out=table snapshot status /var/lib/etcd/thu.db

Nếu lệnh này chạy được, bạn biết mình chụp được. Nếu nó báo lỗi chứng chỉ hoặc không tìm thấy pod, bạn vừa phát hiện điều đó trước khi cần tới — và đó là cả mục đích của việc thử.

Phần sau: khôi phục thật từ snapshot đó, đo từng bước và đo cả thời gian cụm không phục vụ.