filesystem corrupt sau một lần node preempt và cái giá của việc coi persistent storage như stateless

Đế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 => metadata của filesystem chưa sync hết.
  • Node mới mount lê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_repair báo lỗi metadata.
  • Ceph log có dòng RBD image is opened by multiple clients.
  • Kubernetes event: AttachVolume.Attach succeeded for volume ... diễn ra ngay sau event NodeNotReady.

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 NodeLostreattach 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 mount vào PVC nghĩa là anh em đang cắm thẳng filesystem vào một block device. Nếu anh em coi nó như container ephemeral thì đang tự mở cửa cho corruption.

  • 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 devicedirty bit hay 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 compute có thể đổi lấy vài ngày restore backup. Đó là trade-off thật sự, không phải lý thuyết.

  • Filesystem không phải database. Anh em có thể bật synchronous WAL trong PostgreSQL, nhưng nếu dưới tầng block chưa sync thì 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.” :)))

Thông tin nổi bật

Sự kiện phát trực tiếp​

Báo cáo quan trọng

Chia sẻ bài viết:
Theo dõi
Thông báo của
0 Góp ý
Được bỏ phiếu nhiều nhất
Mới nhất Cũ nhất
Phản hồi nội tuyến
Xem tất cả bình luận