Helm: trình quản lý gói cho Kubernetes
Chặng Kubernetes khép lại với một cluster on-prem tự sống sót và một stack đã migrate từ Compose. Nhưng nếu bạn nhìn kỹ thư mục myapp-k8s/ ở capstone, sẽ…
Chặng Kubernetes khép lại với một cluster on-prem tự sống sót và một stack đã migrate từ Compose. Nhưng nếu bạn nhìn kỹ thư mục myapp-k8s/ ở capstone, sẽ thấy một vết nứt đang lớn dần: 9 file YAML rời rạc, và bạn kubectl apply từng cái bằng tay.
Với một app thì ổn. Với năm app, ba môi trường (dev/staging/prod), và hai chục tham số khác nhau giữa chúng — cách làm đó sụp đổ. Đây là lúc cần thứ mà thế giới Linux đã có từ lâu: một trình quản lý gói. Với K8s, đó là Helm.
Mục tiêu bài này: hiểu Chart là gì, viết được template có tham số, cài/nâng cấp/rollback bằng một lệnh, và dùng cùng một Chart cho cả ba môi trường mà không copy-paste dòng nào.
Vấn đề: YAML thô không scale
Nhớ thư mục capstone: namespace.yaml, configmap.yaml, secret.yaml, mysql-pvc.yaml, mysql-deployment.yaml, mysql-service.yaml, webapp-deployment.yaml, webapp-service.yaml, webapp-ingress.yaml. Giờ thử đưa nó lên 3 môi trường:
❌ Copy-paste mỗi môi trường một bộ:
myapp-dev/ 9 file replicas: 1 image: myapp:dev domain: dev.local
myapp-staging/ 9 file replicas: 2 image: myapp:rc domain: stg.cty.vn
myapp-prod/ 9 file replicas: 5 image: myapp:1.4.2 domain: cty.vn
──────────────────────────────────────────────────────────────────
27 file. Khác nhau đúng 4-5 dòng. Giống nhau 95%.
→ Sửa 1 probe → phải sửa 3 chỗ. Quên 1 chỗ = bug "chỉ xảy ra ở staging".
→ Không biết prod đang chạy version nào của bộ manifest.
→ Muốn rollback cả stack? Tự đi tìm YAML cũ trong git, apply lại từng file.Ba nỗi đau cụ thể:
- Trùng lặp — 95% giống nhau, khác vài tham số, nhưng phải duy trì 3 bản.
- Không có phiên bản cho cả stack — Deployment có
rollout undo(Bài 03 K8s), nhưng cả bộ 9 file thì không ai đánh số. - Không tái sử dụng được — muốn cài Prometheus, Redis, Nginx-ingress? Tự đi tìm và ghép hàng trăm dòng YAML của người khác.
Nghe quen không? Đây đúng là lý do Linux có apt, Node có npm, Python có pip. K8s có Helm.
Helm là gì
Helm là trình quản lý gói cho Kubernetes. Nó đóng cả bộ manifest thành một Chart — gói có tham số hóa, có phiên bản, cài/gỡ/nâng cấp bằng một lệnh.
Không Helm Có Helm
────────── ───────
9 file YAML cứng 1 Chart (template + values)
kubectl apply -f từng file helm install myapp ./chart
3 bộ copy cho 3 môi trường 1 Chart + 3 file values
rollback = tự tìm YAML cũ helm rollback myapp 3
cài Prometheus = ghép tay helm install prom prometheus-community/...Ba khái niệm cốt lõi:
Chart "gói phần mềm" — template + giá trị mặc định + metadata
Values tham số để "nhồi" vào template (thứ khác nhau giữa các môi trường)
Release một lần CÀI Chart vào cluster, có tên & số revision
(cùng 1 Chart cài 2 lần = 2 Release khác nhau)💡 Ví von: Chart = công thức nấu ăn (có chỗ trống: "muối: ___ thìa"). Values = bạn điền vào chỗ trống. Release = món ăn cụ thể đã nấu ra, có thể nấu lại (upgrade) hoặc quay về mẻ trước (rollback).
⚠️ Helm 2 có "Tiller" chạy trong cluster (một lỗ hổng bảo mật khét tiếng). Helm 3 đã bỏ Tiller — nó chỉ là một CLI nói chuyện thẳng với API Server, dùng RBAC của bạn (Bài 04 K8s). Tài liệu cũ nhắc Tiller thì bỏ qua; giờ là Helm 3.
Cấu trúc một Chart
helm create myapp # sinh khung Chart mẫu myapp/
├── Chart.yaml metadata: tên, version, mô tả, dependencies
├── values.yaml GIÁ TRỊ MẶC ĐỊNH (những chỗ trống đã điền sẵn)
├── templates/ manifest K8s có TEMPLATE (chỗ trống)
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── _helpers.tpl hàm/đoạn dùng lại (không sinh ra K8s object)
│ └── NOTES.txt lời nhắn in ra sau khi cài xong
└── charts/ chart phụ thuộc (subcharts)Chart.yaml — căn cước của gói
apiVersion: v2
name: myapp
description: Web app + MySQL
type: application
version: 0.1.0 # version của CHART (bộ manifest)
appVersion: "1.4.2" # version của ỨNG DỤNG bên trong⚠️ Phân biệt: version là phiên bản của gói (bạn sửa template → tăng nó); appVersion là phiên bản của app (image tag). Hai thứ khác nhau, hay bị lẫn.
values.yaml — chỗ trống đã điền sẵn
replicaCount: 2
image:
repository: nginx
tag: "1.25"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: false
host: myapp.example.com
resources:
requests: { cpu: 100m, memory: 128Mi }
limits: { cpu: 500m, memory: 256Mi }templates/ — manifest có chỗ trống
Đây là YAML K8s bạn đã quen (Bài 03 K8s), nhưng chèn cú pháp template Go:
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "myapp.fullname" . }} # gọi helper (_helpers.tpl)
labels:
{{- include "myapp.labels" . | nindent 4 }}
spec:
replicas: {{ .Values.replicaCount }} # ← lấy từ values.yaml
selector:
matchLabels:
app: {{ include "myapp.name" . }}
template:
metadata:
labels:
app: {{ include "myapp.name" . }}
spec:
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: {{ .Values.service.port }}
resources:
{{- toYaml .Values.resources | nindent 12 }}# templates/ingress.yaml — chỉ sinh ra NẾU bật
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "myapp.fullname" . }}
spec:
ingressClassName: nginx
rules:
- host: {{ .Values.ingress.host | quote }}
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: {{ include "myapp.fullname" . }}
port:
number: {{ .Values.service.port }}
{{- end }}Cú pháp cần nhớ:
{{ .Values.x }} lấy giá trị từ values.yaml
{{ .Chart.Name }} metadata của Chart
{{ .Release.Name }} tên release lúc cài
{{- if ... }} ... {{- end }} sinh ra có điều kiện (bật/tắt cả object!)
{{- range ... }} lặp
| nindent 4 thụt lề đúng (YAML nhạy indent — nhớ Bài 02 K8s)
| quote / | default "x" bọc nháy / giá trị mặc định
{{- và -}} cắt khoảng trắng thừa💡
{{- if .Values.ingress.enabled }}là siêu năng lực thật sự: cùng một Chart, dev không sinh Ingress, prod có — chỉ khác một dòng trong values. Đây là thứ copy-paste YAML không làm nổi.
Một Chart, nhiều môi trường
Đây là lời giải cho nỗi đau ban đầu:
myapp/ ← 1 Chart duy nhất
values.yaml ← mặc định
values-dev.yaml ← replicaCount: 1, tag: dev, ingress: false
values-staging.yaml ← replicaCount: 2, tag: rc
values-prod.yaml ← replicaCount: 5, tag: 1.4.2, ingress: true# values-prod.yaml — CHỈ ghi những gì KHÁC mặc định
replicaCount: 5
image:
tag: "1.4.2"
ingress:
enabled: true
host: cty.vn
resources:
requests: { cpu: 500m, memory: 512Mi }
limits: { cpu: "2", memory: 1Gi }helm install myapp ./myapp -f values-prod.yaml -n prod→ 27 file rối rắm giờ còn 1 Chart + 3 file values ngắn. Sửa probe một lần trong template, cả 3 môi trường cùng được.
Lệnh Helm cần thuộc
# CÀI
helm install myapp ./myapp -n prod --create-namespace
helm install myapp ./myapp -f values-prod.yaml --set image.tag=1.4.3 # ghi đè nhanh
# XEM TRƯỚC KHI CÀI (cực quan trọng!)
helm template myapp ./myapp -f values-prod.yaml # render YAML ra màn hình, KHÔNG cài
helm install myapp ./myapp --dry-run --debug # thử, có validate với API Server
helm lint ./myapp # soi lỗi cú pháp Chart
# NÂNG CẤP & QUAY ĐẦU
helm upgrade myapp ./myapp -f values-prod.yaml
helm upgrade --install myapp ./myapp -f values-prod.yaml # có thì upgrade, chưa có thì install (dùng trong CI/CD)
helm history myapp # danh sách revision
helm rollback myapp 3 # quay về revision 3 — CẢ STACK!
# XEM & GỠ
helm list -n prod
helm get values myapp # release này đang chạy values gì
helm get manifest myapp # YAML thực tế đã apply
helm uninstall myapp💡
helm templatetrước mọi lần apply lạ. Nó render ra YAML thật để bạn đọc bằng mắt trước khi đụng vào cluster — thói quen cứu bạn khỏi nhiều đêm dài. Đây là phiên bản Helm của "đọc trước khi ký".
Rollback cả stack — thứ kubectl không có
Nhớ Bài 03 K8s: kubectl rollout undo chỉ lùi một Deployment. Nhưng nếu bản deploy hỏng gồm: đổi image + sửa ConfigMap + thêm Ingress + đổi resources? rollout undo không cứu nổi.
helm history myapp
# REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
# 3 ... superseded myapp-0.3.0 1.4.1 Upgrade complete
# 4 ... deployed myapp-0.4.0 1.4.2 Upgrade complete ← hỏng!
helm rollback myapp 3 # lùi TOÀN BỘ: image, config, ingress, resources...⚠️ Vẫn giữ cảnh báo từ Bài 03 K8s: rollback cứu code/config, không cứu data. Migration đã đổi schema DB thì Helm không lùi giúp.
Dùng Chart của người khác — repo công khai
Đây là chỗ Helm tiết kiệm cho bạn hàng trăm giờ. Muốn cài Prometheus, Redis, PostgreSQL? Đã có người đóng gói sẵn:
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm search repo bitnami/redis
helm show values bitnami/redis > my-redis-values.yaml # xem MỌI tham số điều chỉnh được
helm install cache bitnami/redis -f my-redis-values.yaml -n prodBạn đã dùng cách này ở capstone mà chưa gọi tên: NFS provisioner và Prometheus stack đều cài bằng Helm.
⚠️ Đừng cài Chart lạ mà không đọc. helm show values để xem nó nhận tham số gì; helm template để xem nó thực sự tạo ra cái gì trong cluster. Chart của người khác chạy với quyền của bạn.
Dependencies — Chart gọi Chart
# Chart.yaml
dependencies:
- name: mysql
version: "9.x.x"
repository: https://charts.bitnami.com/bitnami
condition: mysql.enabled # bật/tắt được từ valueshelm dependency update ./myapp # tải subchart về thư mục charts/→ App của bạn có thể "kéo theo" MySQL từ Chart Bitnami, thay vì tự viết StatefulSet (Bài 06 K8s). Bật ở dev (mysql.enabled: true), tắt ở prod (dùng managed DB).
Hooks — chạy việc đúng thời điểm
Nhớ Job ở Bài 07 K8s? Helm cho bạn gắn Job vào các mốc vòng đời:
# templates/migration-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: {{ include "myapp.fullname" . }}-migrate
annotations:
"helm.sh/hook": pre-upgrade,pre-install # chạy TRƯỚC khi upgrade/install
"helm.sh/hook-weight": "-1" # thứ tự (số nhỏ chạy trước)
"helm.sh/hook-delete-policy": hook-succeeded # xong thì tự xóa
spec:
template:
spec:
restartPolicy: Never
containers:
- name: migrate
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
command: ["python", "manage.py", "migrate"]→ Mỗi lần helm upgrade, migration DB tự chạy trước khi Pod mới lên. Đây là mảnh ghép đẹp giữa Bài 07 (Job) và Helm.
🚀 Lab — Chart đầu tay
Dùng cluster kind từ chặng trước.
1. Cài Helm & tạo Chart
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
helm version
helm create demo # sinh khung Chart đầy đủ
ls demo demo/templates2. Render trước, cài sau
helm lint ./demo # soi lỗi
helm template demo ./demo | head -40 # XEM YAML sinh ra, chưa đụng cluster
helm install demo ./demo
kubectl get all -l app.kubernetes.io/instance=demo3. Đổi tham số — không sửa template
helm upgrade demo ./demo --set replicaCount=3
kubectl get pods # 3 Pod — chỉ đổi 1 tham số, không đụng YAML!
helm upgrade demo ./demo --set replicaCount=3 --set image.tag=1.26
helm history demo # thấy các revision tích lũy4. Rollback cả stack
helm upgrade demo ./demo --set image.tag=phien-ban-ma # cố tình hỏng
kubectl get pods # ImagePullBackOff (Pod cũ vẫn sống — Bài 03 K8s)
helm rollback demo 2 # quay đầu TOÀN BỘ
helm history demo
kubectl get pods # lành lặn trở lại5. Nhiều môi trường từ một Chart
cat > values-dev.yaml <<EOF
replicaCount: 1
image: { tag: "1.25" }
EOF
cat > values-prod.yaml <<EOF
replicaCount: 4
image: { tag: "1.26" }
EOF
helm install app-dev ./demo -f values-dev.yaml -n dev --create-namespace
helm install app-prod ./demo -f values-prod.yaml -n prod --create-namespace
kubectl get pods -n dev ; kubectl get pods -n prod # 1 vs 4 — CÙNG một Chart ✨6. Dọn
helm uninstall demo ; helm uninstall app-dev -n dev ; helm uninstall app-prod -n prod
kubectl delete ns dev prod→ Khoảnh khắc "à há" ở bước 5: một Chart, hai môi trường, không copy-paste một dòng YAML nào. Đó chính là thứ 27 file rối rắm ở đầu bài không làm được.
Câu chuyện thực tế
Sau khi cluster chạy ổn, chúng tôi có 5 microservice × 3 môi trường = 15 bộ manifest. Mỗi bộ 8–10 file. Tổng cộng hơn 130 file YAML trong repo, giống nhau tới 90%.
Nỗi đau bùng lên một chiều thứ Năm. Chúng tôi phát hiện tất cả service đều thiếu readinessProbe (đúng cái bẫy Bài 03 K8s). Sửa? Phải mở 15 file deployment, sửa 15 lần. Một bạn sửa 14 file, quên đúng một file ở staging. Tuần sau, deploy staging bị downtime giữa lúc rolling update, mất nửa buổi debug — chỉ để phát hiện: một file bị bỏ sót.
Chúng tôi bỏ 3 ngày đóng gói lại thành Helm Chart. Kết quả đo được:
- 130+ file YAML → 5 Chart + 15 file values ngắn (mỗi file values ~10 dòng).
- Sửa một probe: 1 lần trong template, cả 15 chỗ cùng được. Không thể quên sót nữa.
- Deploy một service: từ "apply 9 file đúng thứ tự" →
helm upgrade --install. Một lệnh. - Rollback cả stack hỏng: từ "lục git tìm YAML cũ, apply lại từng file, cầu trời" (
20 phút, run tay) →30 giây**, chắc chắn).helm rollback(**
Nhưng Helm cũng cho tôi một cú tát. Lần đầu dùng, tôi helm upgrade một Chart cộng đồng lên version mới mà không đọc changelog. Chart đó đổi tên field trong values, cái tôi khai bị âm thầm bỏ qua (Helm không báo lỗi giá trị lạ!) — service chạy với cấu hình mặc định thay vì cấu hình của tôi. Mất một tiếng mới hiểu. Từ đó tôi có luật: helm template hoặc --dry-run trước, đọc diff, rồi mới apply.
Bài học:
Copy-paste YAML là nợ kỹ thuật có lãi kép. 15 bản giống nhau nghĩa là 15 chỗ để quên. Template hóa sớm.
helm template/--dry-runtrước khi apply. Helm không báo lỗi khi bạn khai một key values không tồn tại — nó lặng lẽ bỏ qua. Render ra rồi đọc là cách duy nhất chắc chắn.Rollback cả stack là siêu năng lực thật.
kubectl rollout undochỉ lùi một Deployment;helm rollbacklùi cả bộ (image + config + ingress + resources) trong một lệnh.Đừng vội Helm khi chưa thạo YAML thô. (Đúng lời khuyên cuối chặng K8s.) Helm chỉ sinh ra YAML — không hiểu YAML thì gặp lỗi sẽ mù, vì bạn debug thứ mình không đọc được.
Chart cộng đồng: đọc
helm show valuestrước. Nó chạy trong cluster của bạn, với quyền của bạn.
Pitfalls
Khai sai tên key trong values → Helm im lặng bỏ qua, app chạy giá trị mặc định. Không có lỗi báo.
helm templateđể kiểm chứng thực tế.Quên
nindent/indentkhi chèn block → YAML sai indent, lỗi khó hiểu. YAML vẫn nhạy indent như Bài 02 K8s.Lẫn
versionvàappVersion.version= phiên bản Chart;appVersion= phiên bản app (image). Tăng nhầm gây rối lịch sử.helm upgradeChart cộng đồng mà không đọc changelog → breaking change đổi tên field, cấu hình của bạn bị bỏ qua âm thầm.Nhét Secret plain vào
values.yamlrồi commit git. Đúng lỗi Bài 05 K8s, chỉ đổi vỏ. Dùng Sealed Secrets/External Secrets, hoặc--settừ CI (không lưu git).Dùng
helm installtrong CI/CD → lỗi "release đã tồn tại" ở lần chạy thứ hai. Dùnghelm upgrade --install.Không pin version Chart (
--version) → CI hôm nay kéo Chart 9.0, mai kéo 10.0 breaking. Pin lại như pin image tag.helm rollbackxong tưởng data cũng lùi. Không — schema DB đã migrate thì vẫn ở trạng thái mới. (Cùng cảnh báo Bài 03 K8s.)Template quá phức tạp, nhồi logic Go vào YAML tới mức không ai đọc nổi. Chart nên đơn giản; phức tạp quá là dấu hiệu nên tách.
Sửa trực tiếp resource bằng
kubectl edittrên thứ Helm quản → lầnhelm upgradesau ghi đè mất. Nguồn sự thật phải là Chart/values, không phải cluster. (Đây chính là cầu nối sang GitOps ở bài sau!)
Tóm tắt
- Helm = trình quản lý gói cho K8s (như
aptcho Linux). Giải ba nỗi đau của YAML thô: trùng lặp, không có version cho cả stack, không tái dùng được. - Chart (gói: template + values + metadata) · Values (tham số điền vào chỗ trống) · Release (một lần cài, có revision).
- Cấu trúc Chart:
Chart.yaml(metadata;version≠appVersion),values.yaml(mặc định),templates/(manifest có{{ }}),_helpers.tpl,charts/(dependencies). - Template Go:
{{ .Values.x }},{{ .Release.Name }},{{- if }}(sinh object có điều kiện),| nindent,| default. - Một Chart, nhiều môi trường:
-f values-dev.yaml/-f values-prod.yaml. Hết copy-paste. - Lệnh lõi:
helm template&--dry-run(xem trước — thói quen bắt buộc),install,upgrade --install(cho CI/CD),history,rollback(lùi cả stack),lint,uninstall. - Chart cộng đồng:
helm repo add+helm show valuestrước khi cài. Pin version. - Hooks (
pre-upgrade...) để chạy Job migration đúng thời điểm. - ⚠️ Helm im lặng bỏ qua key values sai — luôn render ra để kiểm chứng.
Bài sau (Bài 02 — GitOps với ArgoCD) giải nốt vết nứt còn lại, chính là pitfall số 10 ở trên. Helm gói gọn manifest, nhưng bạn vẫn phải tự tay gõ helm upgrade từ laptop — nghĩa là: ai deploy cũng được, không ai biết prod đang chạy đúng cái gì, và một kubectl edit vội vàng lúc 2 giờ sáng sẽ âm thầm khiến cluster lệch khỏi những gì trong git.
GitOps lật ngược mô hình: git là nguồn sự thật duy nhất, cluster tự kéo về và tự sửa mọi sai lệch. ArgoCD là công cụ hiện thực nó — và nó biến "deploy" từ một hành động thủ công thành một commit.
Nếu hôm nay bạn biến 130 file YAML thành 5 Chart, và helm rollback thay cho một đêm lục git — bạn đã đóng gói như dân chuyên.
— Minh Hưng