Helm là cách phổ biến nhất để cài phần mềm vào Kubernetes. Bài này đo hai thứ: nó thêm gì vào manifest, và nó không biết gì.
Nguồn nhiều hơn kết quả
helm create sinh ra chart mẫu:
templates/*.yaml 219 dòng
values.yaml 161 dòng
---------
380 dòng nguồn
helm template biến chúng thành:
100 dòng YAML, 4 đối tượng
Tôi tưởng tỷ lệ đi theo chiều ngược lại — bản mẫu ngắn sinh ra nhiều manifest. Thực tế là 3,8 dòng nguồn cho mỗi dòng kết quả.
Điều đó không có nghĩa Helm vô ích; nó có nghĩa giá trị của Helm không nằm ở chỗ viết ít đi. Giá trị nằm ở tham số hoá — cùng một chart cài được vào dev, staging, production với ba tệp values.yaml khác nhau.
Với một ứng dụng chỉ triển khai đúng một nơi, một tệp YAML 100 dòng đọc dễ hơn hẳn 380 dòng bản mẫu. Đây là câu hỏi nên tự đặt trước khi đóng gói mọi thứ thành chart.
Helm thêm gì
Mọi đối tượng nhận thêm năm nhãn và hai chú thích:
app.kubernetes.io/managed-by = Helm
app.kubernetes.io/instance = thu
app.kubernetes.io/name = thu
app.kubernetes.io/version = 1.16.0
helm.sh/chart = thu-0.1.0
meta.helm.sh/release-name = thu
meta.helm.sh/release-namespace = hm
Hai chú thích meta.helm.sh là cách Helm nhận lại đồ của mình. Cài chart lên một đối tượng đã tồn tại mà thiếu hai chú thích đó thì Helm từ chối, vì nó không dám giành quyền sở hữu thứ nó không tạo ra.
Đó cũng là mẹo tiếp quản một đối tượng đang có: gắn tay hai chú thích và nhãn managed-by vào, rồi helm upgrade --install sẽ nhận nó.
Nhãn app.kubernetes.io/instance hữu dụng khi gỡ rối: nó cho biết đối tượng này thuộc release nào, kể cả khi nhiều release cùng một chart nằm chung namespace.
Trạng thái release nằm ở đâu
sh.helm.release.v1.thu.v1 kiểu helm.sh/release.v1 11.820 byte
Một Secret trong chính namespace đó. Bên trong là base64 hai lần rồi gzip rồi JSON:
kubectl get secret -n <ns> sh.helm.release.v1.<ten>.v1 \
-o jsonpath='{.data.release}' | base64 -d | base64 -d | gunzip | jq .
Các khoá: apply_method, chart, config, hooks, info, manifest, name, namespace, version.
Chú ý khoá chart: Helm giữ cả mã nguồn chart — đủ 9 tệp bản mẫu — chứ không chỉ giữ kết quả. Đó là lý do một Secret 1,9 KB manifest lại nặng gần 12 KB.
Cũng vì vậy, helm rollback không cần tải lại chart từ đâu cả: mọi thứ cần thiết đã nằm trong cụm.
Mỗi lần upgrade thêm một Secret nữa — ba revision đo được ba Secret. Mặc định giữ 10 (--history-max). Chart lớn thì đây là một khoản chiếm chỗ đáng kể trong etcd, và nó âm thầm.
Chỗ thứ nhất Helm không biết: trôi cấu hình
Tôi xoá tay một Service do Helm tạo:
kubectl get svc -> 0
helm status -> STATUS: deployed
Helm vẫn báo mọi thứ ổn.
Lý do đơn giản: Helm không phải bộ điều khiển. Nó không chạy trong cụm, không theo dõi gì cả. helm status chỉ đọc lại Secret release — tức là đọc lại cái Helm đã làm lần trước, không phải cái đang có thật.
helm upgrade dựng lại được Service, vì nó áp lại toàn bộ manifest.
Hệ quả thực tế: Helm một mình không chống được trôi cấu hình. Ai đó kubectl edit một Deployment do Helm quản lý thì thay đổi đó sống cho tới lần upgrade sau. Muốn tự động đồng bộ thì cần một bộ điều khiển GitOps (Argo CD, Flux) chạy thường trực — Helm chỉ là công cụ dựng manifest và bấm áp.
Cách phát hiện trôi bằng chính Helm:
helm get manifest <ten> -n <ns> > mong-muon.yaml
kubectl diff -f mong-muon.yaml
Chỗ thứ hai: uninstall để lại thứ nó không tạo
PVC khai thẳng trong bản mẫu thì helm uninstall xoá sạch — đo được 0 PVC còn lại.
Nhưng với StatefulSet:
sau uninstall
StatefulSet -> bị xoá
PVC -> CÒN LẠI, vẫn Bound
PVC đó do bộ điều khiển StatefulSet tạo từ volumeClaimTemplates, không nằm trong manifest của Helm. Helm không biết nó tồn tại, nên không đụng tới.
Đây là hành vi đúng — dữ liệu không nên biến mất theo một lệnh gỡ cài đặt — nhưng nó gây hai chuyện bất ngờ:
- Gỡ rồi cài lại cùng tên thì StatefulSet gắn lại đúng PVC cũ, kèm dữ liệu cũ. Rất tốt khi bạn muốn thế, rất khó hiểu khi bạn không.
- Namespace tích dần PVC mồ côi sau vài lần thử nghiệm, và chúng vẫn chiếm chỗ thật.
Cùng loại như vậy: CRD trong thư mục crds/ không bao giờ bị Helm xoá, và cũng không được cập nhật khi upgrade. Nâng cấp chart có CRD thì phải tự kubectl apply CRD trước.
Bốn lệnh đáng nhớ
helm template <ten> <chart> -f values.yaml # xem YAML thật, không cài
helm get manifest <ten> -n <ns> # cái đã cài lần trước
helm diff upgrade ... # plugin, xem trước khác biệt
helm upgrade --install --atomic --timeout 5m # hỏng thì tự lùi lại
helm template là thói quen nên có trước mọi lần cài chart lạ: nó cho xem chính xác cái gì sắp vào cụm. Chart của bên thứ ba có thể chứa ClusterRole rất rộng hoặc hostPath — đọc trước tốn hai phút.
--atomic đáng bật cho mọi lần upgrade ở production: hỏng giữa chừng thì tự rollback, thay vì để lại release ở trạng thái pending-upgrade mà lần sau không upgrade tiếp được.
Thử ba mươi giây
Tìm release nào đang trôi khỏi cấu hình khai báo:
for r in $(helm list -A -q --output json 2>/dev/null | python3 -c '
import sys, json
for x in json.load(sys.stdin): print(x["namespace"] + "/" + x["name"])
'); do
ns=${r%%/*}; n=${r##*/}
d=$(helm get manifest "$n" -n "$ns" 2>/dev/null | kubectl diff -f - 2>/dev/null | wc -l)
[ "$d" -gt 0 ] && echo "$d dong khac biet: $r"
done
Vài dòng khác biệt là bình thường — HPA sửa replicas, bộ điều khiển thêm chú thích. Hàng trăm dòng thì có ai đó đã sửa tay và lần helm upgrade sau sẽ xoá sạch thay đổi đó mà không hỏi.
Phần sau: Kustomize — cùng bài toán tham số hoá, cách giải khác hẳn, và chỗ nó thắng Helm rõ rệt.