Câu hỏi này gây tranh cãi nhiều hơn nó đáng. Bài này dựng PostgreSQL bằng StatefulSet, đo phần chạy tốt, rồi đo đúng chỗ nó gãy.
Phần chạy tốt
Một StatefulSet, một volumeClaimTemplates, một readiness probe pg_isready. Ba mươi dòng YAML.
pgbench, 8 client, 20 giây:
tps 10.366
độ trễ trung bình 0,772 ms
Xoá pod và đo lại:
sẵn sàng lại 0,3 – 2,9 giây
dữ liệu còn 1.000.000 bản ghi
Nhật ký lúc dừng:
received fast shutdown request
aborting any active transactions
checkpoint starting: shutdown immediate
database system is shut down
Toàn bộ mất 13 mili giây, và đó là với hai kết nối đang chạy truy vấn. StatefulSet giữ nguyên tên pg-0, giữ nguyên PVC, gắn lại đúng volume cũ.
Đây là phần Kubernetes làm tốt và làm dễ hơn hẳn cách cũ: định danh ổn định, volume bám theo pod, khởi động lại tự động, cấu hình khai báo.
Nhân tiện một chi tiết đáng biết: ảnh postgres khai STOPSIGNAL=SIGINT, nhưng Kubernetes luôn gửi SIGTERM và bỏ qua khai báo đó. Với ảnh này thì may — điểm vào của nó xử lý cả hai thành "fast shutdown". Với ảnh tự dựng thì đây là chỗ đáng kiểm tra, vì Docker và Kubernetes hành xử khác nhau ở đúng điểm này.
Phần không sửa được bằng YAML
PersistentVolume do bộ cấp phát mặc định tạo ra mang thuộc tính này:
nodeAffinity: ["sc-worker3"]
Dữ liệu nằm trên đĩa của một node cụ thể. Tôi cordon node đó rồi xoá pod:
pg-0 0/1 Pending
0/4 nodes are available:
1 node(s) had untolerated taint(s),
1 node(s) were unschedulable,
2 node(s) didn't match PersistentVolume's node affinity
Pending vĩnh viễn. Không có node nào khác nhận được, vì dữ liệu không ở đó.
Đây là toàn bộ vấn đề, gói trong một dòng thông báo. Kubernetes dựng lại pod rất giỏi, nhưng nó chỉ dựng lại được ở nơi có dữ liệu. Node chết là cơ sở dữ liệu chết cho tới khi node sống lại.
Một pod ứng dụng không trạng thái mất node thì chuyển sang node khác trong vài giây. Một pod cơ sở dữ liệu mất node thì bạn đi khôi phục từ bản sao lưu.
Lưu trữ mạng (EBS, Ceph, NFS) nới được ràng buộc này — volume theo pod sang node khác — nhưng đổi lại độ trễ đĩa tăng lên, và đó chính là thứ cơ sở dữ liệu nhạy cảm nhất.
Kubernetes không điều phối được thứ nó không hiểu
Danh sách việc mà một cơ sở dữ liệu sản xuất cần, và Kubernetes không có khái niệm nào tương ứng:
- Chuyển vai trò chính khi bản chính hỏng.
- Dựng bản sao mới và đồng bộ nó từ bản chính.
- Sao lưu nhất quán, và khôi phục về một thời điểm.
- Nâng cấp phiên bản có kèm migrate lược đồ.
- Kết nối lại ứng dụng khi bản chính đổi chỗ.
Với Kubernetes trần, mọi việc trên là việc tay. StatefulSet cho bạn pg-0, pg-1, pg-2 với định danh ổn định — nó không biết cái nào là bản chính.
Đó là lý do các Operator cơ sở dữ liệu tồn tại: CloudNativePG, Zalando Postgres Operator, Percona, Vitess. Chúng chính là "kiến thức vận hành được mã hoá" mà phần 48 nói tới.
Nên câu hỏi thật không phải "có chạy CSDL trong Kubernetes không" mà là "có chạy một Operator không". Và câu hỏi đó có câu trả lời rõ ràng hơn nhiều: bạn có sẵn sàng vận hành thêm một phần mềm phức tạp, hiểu nó khi nó hỏng, và nâng cấp nó theo cụm không?
Ba tình huống, ba câu trả lời
Dịch vụ quản lý của nhà cung cấp đám mây. Nếu bạn đang chạy trên đám mây và không có lý do đặc biệt, dùng RDS/Cloud SQL. Bạn trả tiền để không phải nghĩ về những gạch đầu dòng ở trên. Đây là lựa chọn đúng cho phần lớn đội.
Operator trong Kubernetes. Đúng khi bạn chạy tại chỗ, khi có nhiều cụm CSDL cần dựng đi dựng lại, hoặc khi việc để mọi thứ trong cùng một mô hình khai báo có giá trị thật. Chi phí là bạn phải hiểu Operator đó thật sự.
StatefulSet trần. Đúng cho môi trường phát triển, cho CI, cho dữ liệu dựng lại được. Đây chính là thứ đo trong bài — và với những mục đích đó nó rất tốt: ba mươi dòng, khởi động lại trong một giây.
Đừng chạy StatefulSet trần cho dữ liệu sản xuất mà bạn không thể mất.
Nếu vẫn chạy, năm điều tối thiểu
- Đặt
requestsbằnglimitscho bộ nhớ. Cơ sở dữ liệu bị OOM giữa lúc ghi là ca tệ nhất. QoSGuaranteedbảo vệ nó khỏi bị đuổi (phần 12). terminationGracePeriodSecondsđủ dài cho checkpoint hoàn tất. Đo được 13 ms ở đây, nhưng đó là CSDL rỗng — cơ sở dữ liệu thật với bộ đệm lớn cần nhiều hơn.- PodDisruptionBudget để
drainkhông hạ mất bản chính (phần 51). - Sao lưu thật, ở ngoài cụm. Snapshot etcd không chứa dữ liệu — đã đo ở phần 49: ghi 50 MB vào volume làm snapshot tăng 0,26%.
- Đừng dùng
emptyDir. Nghe hiển nhiên, nhưng nó vẫn xuất hiện trong hướng dẫn "chạy Postgres nhanh" và pod nào cũng chạy được — cho tới lần khởi động lại đầu tiên.
Một phép đo tôi không lặp lại được
Có một lần trong lúc thử, PostgreSQL khởi động lại báo:
database system was not properly shut down; automatic recovery in progress
Tôi thử tái tạo ba lần với hai kết nối đang chạy pg_sleep, và cả ba lần đều dừng sạch trong dưới một giây. Không tìm ra điều kiện gây ra nó.
Ghi lại vì hai lẽ. Thứ nhất, một quan sát không lặp lại được thì không phải kết luận — tôi đã suýt viết cả một đoạn giải thích cơ chế cho nó. Thứ hai, nếu bạn gặp dòng đó thì nó không nhất thiết nghĩa là cấu hình sai; PostgreSQL tự phục hồi và trong ca của tôi không mất bản ghi nào.
Thử ba mươi giây
Tìm mọi pod có trạng thái đang bị ghim vào một node:
kubectl get pv -o json | python3 -c '
import sys, json
for v in json.load(sys.stdin)["items"]:
na = (v["spec"].get("nodeAffinity") or {}).get("required")
if not na: continue
nodes = na["nodeSelectorTerms"][0]["matchExpressions"][0]["values"]
c = v["spec"].get("claimRef") or {}
print("%-22s ghim vao %-20s <- %s/%s" % (v["metadata"]["name"][:20], nodes, c.get("namespace"), c.get("name")))
'
Mỗi dòng là một khối lượng công việc sẽ Pending nếu node đó ngừng phục vụ. Với môi trường phát triển thì không sao. Với thứ đang phục vụ người dùng thật, mỗi dòng là một câu hỏi cần trả lời trước khi node đó chết.
Phần sau: quản lý bí mật — đo cái Secret của Kubernetes thật sự bảo vệ, và cái nó không.