Đến hẹn lại lên có chuyên là kể, nói thế chứ giờ mới được duyệt CV để viết bài :))
Bình thường bên tôi hay deploy đêm, giờ đó ít người sử dụng tác động cũng không phải phát ăn ngay, và hạ tầng dùng Kubernetes cluster trên cloud, có vài StatefulSet cho database và job xử lý background. Các workload nhẹ được chạy trên node loại spot instance để tiết kiệm chi phí.
Pod PostgreSQL chạy trong StatefulSet, PVC provision bằng StorageClass có backend là Ceph RBD (được quản lý qua Rook).
Tất cả đều ổn cho đến khi một node bị preempt. Khi node đó bị thu hồi, Kubernetes scheduler bắt đầu spin lại pod database ở node khác. PVC được attach lại tự động, nhưng PostgreSQL không khởi động nổi. Log hiện dòng này:
FATAL: could not read from file "pg_wal/000000010000001C0000007A": Input/output error
Xong tôi mới mở shell trong pod, chạy dmesg | tail, thấy lỗi:
XFS (rbd0): metadata corruption detected at xfs_buf_ioend+0x1c4/0x250
XFS corrupted. Database hỏng.
Tại sao lại có thể xảy ra chuyện này
Khi node bị preempt, kubelet gửi signal termination cho pod, nhưng vì đây là spot instance nên grace period thực tế chỉ còn vài chục giây.
Ceph RBD detach process chưa xong, nhưng node đã bị kill hẳn.
Cơ chế detach volume trong Kubernetes là asynchronous.
Controller-manager thấy node mất heartbeat nên đánh dấu volume “available”, và attach sang node khác.
Ceph thấy client cũ (node bị kill) chưa gửi unmap hoàn chỉnh, lock vẫn tồn tại, nhưng vì có retry nên Ceph cho phép attach mới (rbd feature exclusive-lock chưa bật strict mode).
Kết quả:
- Hai node có thể cùng nhìn thấy cùng một block device
rbd:)). - Node cũ chưa kịp
flush metadata=>metadatacủa filesystem chưasynchết. - Node mới
mountlên => filesystem bị lỗi journaling. - Database
WAL(write ahead log) đang dở dang =>corrupted.
Dấu hiệu nhận biết
Không phải lúc nào cũng lỗi rõ như trên. Có khi PostgreSQL start được nhưng query VACUUM fail, hoặc checksum không khớp.
xfs_repairbáo lỗimetadata.- Ceph log có dòng
RBD image is opened by multiple clients. - Kubernetes
event:AttachVolume.Attach succeeded for volume ...diễn ra ngay sau eventNodeNotReady.
TÔi replay lại toàn bộ timeline qua event:
00:01:45 node-worker-3 Preempted
00:01:49 pod/postgres-0 Terminating
00:01:52 pod/postgres-0 NodeLost
00:01:55 pod/postgres-0 recreated on node-worker-7
00:01:57 volume attached to node-worker-7
Thời gian giữa NodeLost và reattach chỉ có 8 giây. Ceph chưa kịp cleanup lock.
Cách khắc phục
Bước 1: Bật strict exclusive-lock trong Ceph RBD
Trong StorageClass thêm parameter:
parameters:
imageFeatures: layering,exclusive-lock,object-map,fast-diff,deep-flatten
Khi bật exclusive-lock, Ceph sẽ không cho phép attach nếu client cũ chưa release. Volume chỉ attach khi lock cũ bị revoke hoặc timeout hoàn toàn.
Điều này có thể làm thời gian attach lâu hơn vài chục giây nhưng an toàn hơn rất nhiều.
Bước 2: Tăng grace period và delay reattach
Dùng PodDisruptionBudget để tránh pod bị terminate quá nhanh, và thêm terminationGracePeriodSeconds: 60 trong StatefulSet để PostgreSQL có thời gian flush WAL.
Với CSI provisioner, có thể cấu hình: volumeBindingMode: WaitForFirstConsumer để đảm bảo attach diễn ra sau khi node mới ready hoàn toàn.
Bước 3: Chặn preempt cho pod có dữ liệu
Không nên chạy database hoặc stateful workload trên spot instance.
Nếu buộc phải dùng thì thêm:
nodeSelector:
instance-type: on-demand
Hoặc tolerations để không bị scheduler đẩy pod đó lên node spot:
tolerations:
- key: "spot"
operator: "Exists"
effect: "NoSchedule"
Bước 4: Filesystem verification trước mount
Thêm initContainer check trước khi PostgreSQL start:
initContainers:
- name: check-fs
image: xfsprogs-tools
command: ["xfs_repair", "-n", "/dev/rbd0"]
# ... (mount /dev/rbd0 vào đây)
Nếu thấy corruption thì fail fast, không để PostgreSQL tự ghi đè làm hỏng thêm.
Cũng ra được thêm vài thứ
-
Không có storage nào là stateless cả. Một database
mountvàoPVCnghĩa là anh em đang cắm thẳng filesystem vào mộtblock device. Nếu anh em coi nó nhưcontainer ephemeralthì đang tự mở cửa chocorruption. -
Kubernetes không chịu trách nhiệm về data integrity. Nó chỉ đảm bảo “attach được” và “pod chạy lại”. Nó không biết
block devicecódirty bithay không. -
Spot instance và stateful workload không thể sống cùng nhau. Tiết kiệm vài chục phần trăm tiền
computecó thể đổi lấy vài ngàyrestore backup. Đó làtrade-offthật sự, không phải lý thuyết. -
Filesystem không phải database. Anh em có thể bật
synchronous WALtrong PostgreSQL, nhưng nếu dưới tầngblockchưasyncthì vẫn thua :)))
Có vậy thôi
Sau vụ đó, team mới restore từ WAL archive, mất khoảng 45 phút downtime. Backup may mắn vẫn dùng được, nhưng cảm giác mở xfs_repair và thấy “metadata corruption” đúng kiểu khó lói :))
Tôi note postmortem đúng một dòng: “Running database on spot node is like parking Ferrari trên cầu đang sửa.” :)))







