Phần trước đo Helm và tìm ra tỷ lệ 380 dòng nguồn cho 100 dòng kết quả. Bài này đo Kustomize trên cùng bài toán — và phát hiện phép so sánh đó của tôi không công bằng.
Đếm lại cho công bằng
Bài toán: một Deployment và một Service, hai môi trường khác nhau về tên, số bản, image và CPU.
Helm tối thiểu 37 dòng
Kustomize 41 dòng
Gần như bằng nhau.
Con số 380 dòng ở phần trước là của chart mẫu do helm create sinh ra — nó có sẵn Ingress, HPA, ServiceAccount, bản mẫu test và một tệp _helpers.tpl. So nó với một overlay Kustomize tối giản là so nhầm hai thứ khác nhau.
Tôi đã viết một chart Helm tối thiểu làm đúng việc Kustomize làm, và kết quả là hoà. Lập luận "Kustomize gọn hơn Helm" không đứng vững khi đo.
Điều đó cũng có nghĩa: chọn công cụ nào không nên dựa trên số dòng. Nó nằm ở chỗ khác.
Cấu trúc
base/
deployment.yaml YAML thật, dán vào cụm chạy được luôn
service.yaml
kustomization.yaml
overlays/dev/kustomization.yaml
overlays/prod/kustomization.yaml
Điểm khác biệt về triết lý: base/ là YAML hợp lệ, không phải bản mẫu. Mở ra đọc được, kubectl apply thẳng được, trình soạn thảo hiểu được lược đồ, kubeval kiểm được.
Chart Helm thì không: replicas: {{ .Values.replicas }} không phải YAML hợp lệ. Mọi công cụ đọc YAML đều phải chờ render xong.
Overlay prod trông thế này:
namePrefix: prod-
resources: [../../base]
labels:
- pairs: {moi-truong: prod}
includeSelectors: false
replicas:
- {name: web, count: 3}
images:
- {name: nginx, newTag: "1.27"}
patches:
- target: {kind: Deployment, name: web}
patch: |
- op: replace
path: /spec/template/spec/containers/0/resources/requests/cpu
value: 500m
Một cái bẫy đo được ngay: viết newTag: 1.27 không có nháy thì Kustomize báo
cannot unmarshal number into Go struct field Image.images.newTag of type string
YAML đọc 1.27 là số. Thông báo lỗi khá rõ, nhưng với newTag: 1.27.0 thì lại đúng — nên lỗi chỉ xuất hiện ở đúng những phiên bản có hai phần.
includeSelectors: false cũng đáng nhớ: thiếu nó thì nhãn được thêm vào cả selector của Deployment, mà selector là trường không sửa được sau khi tạo. Lần áp thứ hai sẽ hỏng.
Chỗ Kustomize không làm được
Tôi bỏ service.yaml khỏi kustomization.yaml rồi áp lại:
kubectl apply -k -> service/dev-web VẪN CÒN
Kustomize không có khái niệm "release". Đo được 0 Secret trạng thái trong namespace — không có gì ghi lại lần áp trước.
Nên nó không biết lần trước nó đã tạo những gì, và không xoá được thứ bạn vừa bỏ khỏi khai báo. Helm thì xoá, vì manifest của lần trước nằm trong Secret release.
Đây là khác biệt thật sự lớn nhất giữa hai công cụ, và nó ít được nói tới hơn chuyện cú pháp.
Cách chữa là --applyset:
KUBECTL_APPLYSET=true kubectl apply -k overlays/dev -n kz \
--prune --applyset=bo-ung-dung
service/dev-web pruned
Nhưng có điều kiện: nó chỉ xoá được thứ do chính applyset đó tạo ra. Tôi thử áp lên các đối tượng đã tồn tại từ trước khi bật applyset — chúng không bị đụng tới. Bật applyset cho một hệ thống đang chạy thì phải áp lại một lượt trước, rồi mới prune được.
Đổi lại, việc không có trạng thái là ưu điểm trong quy trình GitOps: Git là nguồn duy nhất, không có bản sao thứ hai trong cụm để lệch nhau.
Chỗ Kustomize thắng rõ rệt
app.env = "cai-dat=cu" -> name: dev-cauhinh-ghf892dkkm
app.env = "cai-dat=moi" -> name: dev-cauhinh-m79ffm444m
configMapGenerator băm nội dung rồi gắn vào tên. Và tham chiếu trong Deployment được viết lại theo:
envFrom:
- configMapRef:
name: dev-cauhinh-m79ffm444m
Nghĩa là đổi cấu hình thì tên ConfigMap đổi, pod template đổi, và Deployment tự cuộn lại.
Đây là lời giải cho một vấn đề thật: sửa ConfigMap thì pod đang chạy không tự nhận cấu hình mới — biến môi trường thì không bao giờ, tệp gắn vào thì phải chờ và ứng dụng phải tự đọc lại. Với Helm bạn phải tự thêm chú thích checksum/config vào pod template, và ai quên thì lỗi im lặng.
Điểm trừ: các ConfigMap cũ không tự biến mất. Vài chục lần triển khai là vài chục ConfigMap mồ côi, trừ khi bạn dùng --prune.
Chỗ Kustomize không định làm
Không có if, không có vòng lặp, không có hàm. Muốn "bật Ingress ở prod, tắt ở dev" thì phải để ingress.yaml trong overlay prod chứ không thể viết {{- if .Values.ingress.enabled }}.
Đó là lựa chọn có chủ ý, và nó có giá đúng nghĩa hai chiều:
- Với 2–3 môi trường khác nhau ít, cách này rõ ràng hơn hẳn — đọc overlay là biết ngay môi trường đó có gì.
- Với một chart cần phục vụ hàng nghìn người dùng khác nhau, không có điều kiện là không làm nổi. Đó là lý do gần như mọi phần mềm phân phối công khai đều dùng Helm.
Chọn cái nào
| Tình huống | Chọn |
|---|---|
| Cài phần mềm của người khác | Helm — chart có sẵn |
| Ứng dụng nội bộ, vài môi trường | Kustomize |
| Cần rollback bằng một lệnh | Helm |
| GitOps với Argo CD / Flux | Kustomize (cả hai đều hỗ trợ) |
| Chart phân phối cho người ngoài | Helm |
Và cách kết hợp hay dùng: Kustomize áp lên kết quả của Helm.
helmCharts:
- name: ingress-nginx
repo: https://kubernetes.github.io/ingress-nginx
version: 4.11.3
valuesFile: values.yaml
patches:
- path: sua-tai-nguyen.yaml
Cách này lấy chart của người khác rồi sửa vài chỗ mà không cần fork chart, và cũng không cần chờ họ thêm một biến values mới.
Thử ba mươi giây
Xem overlay của bạn thật sự sinh ra cái gì, và khác gì so với cụm đang chạy:
kubectl kustomize overlays/prod | kubectl diff -f -
Chạy lệnh này trước mỗi lần áp. Nó bắt được hai lỗi hay gặp nhất mà apply không cảnh báo: patch trỏ vào target không khớp gì cả (im lặng không làm gì), và nhãn vô tình chui vào selector (chỉ nổ ra ở lần áp thứ hai).
Phần sau: Operator và CRD — đo chi phí thật của việc tự viết một bộ điều khiển.