Helm: chart, values theo môi trường, release
Tiếp đi: Helm.
Đừng nghĩ Helm thay Kubernetes. Mental model đầu tiên:
Docker → build image
Kubernetes → deploy/manage containers
Helm → template + package + quản lý vòng đời các K8s resource
1. Bài toán
Chỉ riêng payment-service đã có:
k8s/
├── deployment.yaml
├── service.yaml
├── ingress.yaml
├── configmap.yaml
├── hpa.yaml
└── pdb.yaml
Rồi ba môi trường khác nhau vài chỗ:
DEV: replicas 1, image tag dev, CPU 200m, không Ingress, không HPA
STAGING: replicas 2, image tag staging, CPU 500m, staging-api.shop.example.com
PROD: replicas 3, image tag 1.8.3, CPU 500m, api.shop.example.com, HPA 3–10
2. Vấn đề
k8s/dev/... k8s/staging/... k8s/prod/...
6 file × 3 môi trường × (order + payment) = 36 file, giống nhau 80–90%
- Duplicate YAML → sửa probe ở dev quên sửa prod → config drift.
kubectl applykhông nhớ "lần deploy trước gồm những gì", rollback cả bộ (Deployment + ConfigMap + Ingress) phải tự làm.- Xóa một resource khỏi thư mục thì
kubectl applykhông tự xóa nó khỏi cluster.
Nhìn bằng mắt lập trình viên: đây là duplicate code. Lời giải là template + tham số.
3. Giải pháp: Helm
Helm là package manager cho Kubernetes. Bốn khái niệm:
HELM
├── Chart → package: templates + values mặc định + metadata
├── Values → tham số cấu hình
├── Template → YAML có chỗ trống {{ .Values.xxx }}, render ra K8s YAML thường
└── Release → một lần cài Chart vào cluster (có tên, có lịch sử revision)
Chart ──install──► Release
(giống Class ──new──► Object, hay Image ──run──► Container)
payment chart → release payment ở namespace dev, staging, production
Kubernetes không hiểu {{ .Values.replicaCount }}. Helm render ra YAML thường rồi gửi lên API server. Helm 3 không có thành phần chạy trong cluster; trạng thái release được lưu thành Secret trong namespace của release.
4. Code từng bước
Bước 1: Cấu trúc Chart trong repo payment-service
payment-service/
├── src/ pom.xml Dockerfile
└── helm/payment/
├── Chart.yaml
├── values.yaml # mặc định
├── values-dev.yaml
├── values-staging.yaml
├── values-prod.yaml
└── templates/
├── _helpers.tpl # bắt đầu bằng _ : không render thành resource
├── deployment.yaml
├── service.yaml
├── ingress.yaml
├── configmap.yaml
└── hpa.yaml
# Chart.yaml
apiVersion: v2
name: payment
description: Helm chart for payment-service
type: application
version: 0.3.0 # version của CHART
appVersion: "1.8.3" # version của APP (metadata, mặc định cho image tag)
Bước 2: values.yaml (mặc định) và file theo môi trường
# values.yaml
replicaCount: 1
image:
repository: myregistry/payment-service
tag: "" # rỗng → dùng appVersion
pullPolicy: IfNotPresent
service:
port: 80
ingress:
enabled: false
host: ""
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 5
targetCPUUtilizationPercentage: 70
resources:
requests: { cpu: 200m, memory: 512Mi }
limits: { memory: 512Mi }
config:
dbUrl: jdbc:postgresql://postgres:5432/shop
paymentTimeout: "5000"
existingSecret: payment-secret # Secret tạo ngoài Helm (External Secrets)
# values-prod.yaml: chỉ ghi chỗ khác mặc định
replicaCount: 3
ingress:
enabled: true
host: api.shop.example.com
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 10
resources:
requests: { cpu: 500m, memory: 768Mi }
limits: { memory: 768Mi }
# values-dev.yaml
image:
tag: dev
config:
paymentTimeout: "10000"
Bước 3: Templates
{{/* templates/_helpers.tpl */}}
{{- define "payment.fullname" -}}
{{ .Release.Name }}-{{ .Chart.Name }}
{{- end }}
{{- define "payment.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{- define "payment.labels" -}}
{{ include "payment.selectorLabels" . }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "payment.fullname" . }}
labels:
{{- include "payment.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }} # có HPA thì để HPA quản lý
{{- end }}
selector:
matchLabels:
{{- include "payment.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "payment.selectorLabels" . | nindent 8 }}
annotations:
# ConfigMap đổi → checksum đổi → Pod template đổi → rolling update
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
spec:
containers:
- name: payment
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports: [ { containerPort: 8080 } ]
envFrom:
- configMapRef: { name: {{ include "payment.fullname" . }} }
- secretRef: { name: {{ .Values.existingSecret }} }
readinessProbe:
httpGet: { path: /actuator/health/readiness, port: 8080 }
livenessProbe:
httpGet: { path: /actuator/health/liveness, port: 8080 }
resources:
{{- toYaml .Values.resources | nindent 12 }}
# templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ include "payment.fullname" . }}
data:
DB_URL: {{ .Values.config.dbUrl | quote }}
PAYMENT_TIMEOUT: {{ .Values.config.paymentTimeout | quote }}
# templates/ingress.yaml
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ include "payment.fullname" . }}
spec:
ingressClassName: nginx
rules:
- host: {{ .Values.ingress.host }}
http:
paths:
- path: /payments
pathType: Prefix
backend:
service:
name: {{ include "payment.fullname" . }}
port: { number: {{ .Values.service.port }} }
{{- end }}
Cú pháp gặp nhiều nhất (Go template):
| Cú pháp | Nghĩa | Java tương tự |
|---|---|---|
.Values.x, .Release.Name, .Chart.Name | Đọc values, thông tin release, chart | config object |
. | Context hiện tại | this |
if / range / with | Điều kiện / vòng lặp / đổi context | if / for |
define + include | Khai báo và gọi template dùng chung | method |
| quote, | default x | Pipeline: thêm ngoặc kép, giá trị dự phòng | gọi hàm nối tiếp |
toYaml . | nindent 12 | Đổi object thành YAML, xuống dòng + thụt 12 space | |
{{- / -}} | Xóa khoảng trắng trước / sau |
Bước 4: Render, cài, nâng cấp, rollback
# Xem YAML cuối cùng trước khi deploy
helm lint ./helm/payment
helm template payment ./helm/payment -f ./helm/payment/values-prod.yaml
# Cài hoặc nâng cấp (pipeline dùng một lệnh cho cả hai)
helm upgrade --install payment ./helm/payment \
--namespace production --create-namespace \
-f ./helm/payment/values-prod.yaml \
--set image.tag=a83fc21 \
--atomic --timeout 5m
# Xem và quay lại
helm list -n production
helm status payment -n production
helm history payment -n production
helm get values payment -n production # values đang dùng
helm diff upgrade ... # plugin helm-diff: xem thay đổi trước khi upgrade
helm rollback payment 2 -n production
helm uninstall payment -n production
5. Cách hoạt động
Thứ tự ghi đè values
values.yaml (mặc định)
↓ bị ghi đè bởi
-f values-prod.yaml (theo môi trường; nhiều -f thì file sau thắng)
↓ bị ghi đè bởi
--set image.tag=a83fc21 (CI/CD inject theo từng build)
Map được merge sâu: values-prod chỉ ghi image.tag thì image.repository vẫn lấy từ mặc định.
Một lần helm upgrade
Chart templates + merged values
│ render
▼
Kubernetes YAML thường
│ so với manifest của revision trước + trạng thái thật (3-way merge)
▼
API server: tạo / sửa / xóa resource
│
▼
Deployment thấy Pod template đổi → ReplicaSet mới → rolling update
│ (Kubernetes làm, không phải Helm)
▼
--atomic: chờ Ready; quá --timeout → tự rollback release
│
▼
Lưu Release revision N+1 thành Secret sh.helm.release.v1.payment.vN+1
Ba loại "version" dễ trộn
Docker image tag 1.8.3 / a83fc21 → code nào đang chạy
Helm revision 1, 2, 3, 4 → lịch sử cấu hình release
ReplicaSet payment-7f9d... → các Pod template của Deployment
helm rollback payment 2:
Revision 1 image 1.8.1
Revision 2 image 1.8.2
Revision 3 image 1.8.3 ❌
Revision 4 = cấu hình của revision 2 ← rollback tạo revision MỚI, lịch sử không bị xóa
Toàn bộ pipeline
git push → CI: mvn test → docker build → docker push myregistry/payment-service:a83fc21
→ CD: helm upgrade --install -f values-prod.yaml --set image.tag=a83fc21 --atomic
→ K8s: new ReplicaSet → startup/readiness probe → Service → Ingress → Users
Cùng một image a83fc21 được promote dev → staging → prod; khác nhau chỉ ở values. Không build lại image cho từng môi trường.
6. So sánh
| kubectl apply YAML | Kustomize | Helm | |
|---|---|---|---|
| Tham số hóa | Không | Overlay/patch, không template | Template + values |
| Lịch sử, rollback cả bộ | Không | Không | Có (revision) |
| Xóa resource thừa | Không | Không (trừ prune) | Có |
| Chia sẻ, dependency | Không | Hạn chế | Chart repo, OCI registry, dependencies |
| Độ khó đọc | Dễ nhất | Dễ | Template dễ rối |
Dùng chung được: Argo CD / Flux (GitOps) render Helm chart rồi đồng bộ vào cluster, thay cho việc pipeline gọi helm upgrade.
7. Khi nào nên dùng / không nên dùng
- Nên: nhiều service, nhiều môi trường; cần cài phần mềm bên thứ ba (ingress-nginx, cert-manager, Prometheus) từ chart có sẵn; cần rollback cả bộ resource.
- Không nên: một app, một môi trường, vài file YAML:
kubectl applyhoặc Kustomize đủ. - Không đưa mọi literal vào values.
containerPort: 8080không bao giờ đổi thì để cứng; values 1000 dòng còn khó đọc hơn YAML gốc. - Không để business config (hạn mức chuyển tiền, % khuyến mãi) trong values; đó là việc của DB hoặc feature flag.
- Không để secret plain text trong values commit vào Git, và không
--set db.password=...trong pipeline (lộ ra log). - Tag image bằng version hoặc Git SHA, không dùng
latest: nhìn release là biết code nào đang chạy.
Có Helm rồi thì mình còn cần dùng kubectl nữa không?
Vẫn cần, chỉ là đổi vai:
Trước Helm: kubectl apply -f deployment.yaml, service.yaml, ingress.yaml ...
Sau Helm: helm upgrade --install payment ./helm/payment -f values-prod.yaml
Debug vẫn là kubectl:
kubectl get pods / describe pod / logs --previous / get svc / get events
- Helm: đóng gói, deploy, quản lý release.
- kubectl: xem và debug Kubernetes.
Cách tách lỗi rất hữu ích: helm template ra YAML sai thì lỗi ở chart/values; YAML đúng mà Pod không chạy thì lỗi ở Kubernetes hoặc app (bài 17.7). Một lưu ý: đừng kubectl edit resource do Helm quản lý, lần upgrade sau sẽ ghi đè và lịch sử Helm không biết thay đổi đó.
Vậy password database mình để trong values-prod.yaml được không?
Không nên. Helm template ra kind: Secret không làm cho nguồn của nó an toàn:
values-prod.yaml (password plain text) → Git → ai clone repo cũng đọc được
Secret object ≠ secret management
Ngoài ra values của mỗi revision còn được lưu trong Secret của release, helm get values sẽ in ra. Các cách hay dùng:
- Secret tạo ngoài chart: External Secrets Operator đồng bộ từ Vault/AWS Secrets Manager thành
payment-secret; chart chỉ tham chiếu tên quaexistingSecretnhư ví dụ trên. Đây là cách sạch nhất. - Mã hóa trong Git: helm-secrets + SOPS, hoặc Sealed Secrets. Git chỉ chứa bản mã hóa.
- CSI Secrets Store: mount secret trực tiếp từ cloud vào Pod.
values-prod.yaml chỉ nên chứa: replicas, resource, host Ingress, bật/tắt HPA, URL không nhạy cảm.
Phỏng vấn hỏi về Helm thì trả lời sao?
Q1. Helm là gì, sao không dùng YAML Kubernetes thuần?
🇻🇳 Tiếng Việt:
Helm là package manager cho Kubernetes. Nó gói các manifest thành Chart có template, cấu hình bằng values, và quản lý release với lịch sử, upgrade, rollback. YAML thuần bị lặp khi có nhiều service và nhiều môi trường, dễ lệch cấu hình, và không có khái niệm một lần deploy gồm những resource nào. Với Helm em giữ một bộ template và chỉ khác nhau ở file values.
🇬🇧 English:
Helm is a package manager for Kubernetes. It packages manifests into templated Charts, configures them through values, and manages releases with history, upgrades and rollbacks. Plain YAML gets duplicated across services and environments, drifts easily, and has no notion of which resources belong to one deployment. With Helm I keep one set of templates and only the values files differ.
Q2. Chart khác Release thế nào?
🇻🇳 Tiếng Việt:
Chart là package tái sử dụng gồm
Chart.yaml, values mặc định và templates. Release là một lần cài Chart đó vào cluster với một tên và một bộ values, ví dụ chart payment cài thành release payment ở namespace dev và production. Mỗi lần upgrade hay rollback tạo một revision mới của release. Giống quan hệ class và object.
🇬🇧 English:
A Chart is a reusable package made of
Chart.yaml, default values and templates. A Release is one installation of that Chart in a cluster with a name and a set of values, for example the payment chart installed as a release in both the dev and production namespaces. Every upgrade or rollback creates a new revision of the release. It's like the relationship between a class and an object.
Q3. Bạn quản lý nhiều môi trường bằng Helm thế nào?
🇻🇳 Tiếng Việt:
Em giữ một Chart, một
values.yamllàm mặc định và các filevalues-dev,values-staging,values-prodchỉ ghi phần khác. Pipeline chạyhelm upgrade --install -f values-prod.yaml --set image.tag=<git-sha>, nên cùng một image được promote qua các môi trường. Thứ tự ưu tiên là values mặc định, rồi file môi trường, rồi--set. Secret không nằm trong values mà lấy từ secret manager.
🇬🇧 English:
I keep one Chart with a default
values.yamlandvalues-dev,values-stagingandvalues-prodfiles that only contain the differences. The pipeline runshelm upgrade --install -f values-prod.yaml --set image.tag=<git-sha>, so the same image is promoted through every environment. Precedence is default values, then the environment file, then--set. Secrets don't live in values; they come from a secret manager.
Q4. helm rollback làm gì, có giới hạn gì?
🇻🇳 Tiếng Việt:
Helm lấy manifest của revision cũ, áp lại vào cluster và ghi thành một revision mới, nên lịch sử không bị xóa. Kubernetes Deployment sau đó tự rolling update về image cũ. Helm chỉ quay lại các resource nó quản lý; nó không rollback migration database, message đã gửi hay dữ liệu PVC. Vì vậy migration phải tương thích ngược. Trong pipeline em dùng
--atomicđể upgrade lỗi thì tự rollback.
🇬🇧 English:
Helm takes the manifests of an earlier revision, applies them to the cluster and records that as a new revision, so history is preserved. The Kubernetes Deployment then rolls back to the old image with a rolling update. Helm only reverts the resources it manages; it doesn't roll back database migrations, messages already sent or PVC data, so migrations have to be backward compatible. In pipelines I use
--atomicso a failed upgrade rolls back automatically.
Cách nhớ ngắn gọn
- Chart = template + values + metadata; Release = một lần cài, có revision.
- Ưu tiên: values.yaml → values-env.yaml → --set.
helm templatetrước,upgrade --install --atomickhi deploy,history/rollbackkhi lỗi.- Helm sửa spec; Kubernetes làm rolling update. Rollback không chạm database.
- Secret không vào values; image tag dùng version hoặc Git SHA.
Tổng kết chương Kubernetes
| Bài | Chủ đề | Bài toán giải quyết | Resource / công cụ chính |
|---|---|---|---|
| 17.1 | Control plane | Ai chạy container ở đâu, ai giữ đúng số lượng | API server, etcd, scheduler, controller, kubelet |
| 17.2 | Pod & probe | Process chạy chưa chắc app khỏe; tắt không làm rơi request | startup/readiness/liveness probe, preStop, graceful shutdown |
| 17.3 | Workloads | Giữ N Pod, deploy không downtime, app có state, batch | Deployment, ReplicaSet, StatefulSet, DaemonSet, Job, CronJob |
| 17.4 | Service & Ingress | Pod IP thay đổi; người dùng vào từ Internet | Service, CoreDNS, Ingress, Ingress Controller |
| 17.5 | ConfigMap, Secret, RBAC | Config tách khỏi image, bảo vệ secret, phân quyền | ConfigMap, Secret, Role, RoleBinding, ServiceAccount |
| 17.6 | Resource & autoscaling | Đặt Pod đúng node, chống ăn hết RAM, scale theo tải | requests/limits, QoS, HPA, VPA, PDB, PVC |
| 17.7 | Troubleshooting | Pod lỗi thì nhìn ở đâu | describe, logs --previous, events, exec, debug |
| 17.8 | Helm | YAML lặp theo môi trường, quản lý release | Chart, values, upgrade --install, rollback |
Kết luận: Docker đóng gói app thành image. Kubernetes chạy, giữ, nối mạng và scale image đó theo desired state. Helm đóng gói đống YAML của Kubernetes thành một Chart dùng cho mọi môi trường. Ba công cụ nối thành một đường: git push → image → helm upgrade → rolling update → Service → Ingress → người dùng.
Chương còn hai bài vận hành: giám sát chính cluster ở 17.9 Prometheus stack & dashboard và gom log toàn cluster ở 17.10 Loki & Promtail; bảng tổng kết đầy đủ của chương nằm cuối bài 17.11.
Tự kiểm tra
version và appVersion trong Chart.yaml khác gì?
version là version của chart; appVersion là version ứng dụng (metadata, hay dùng làm image tag mặc định).
values.yaml có replicaCount: 1, values-prod có 3, lệnh có --set replicaCount=5. Kết quả?
5. --set ưu tiên cao nhất.
Sửa ConfigMap trong chart rồi upgrade, Pod có tự restart không?
Không, trừ khi Pod template có annotation checksum của ConfigMap (hoặc dùng công cụ reload).
Đang ở revision 3, chạy helm rollback payment 2. Revision hiện tại là bao nhiêu?
4, mang cấu hình của revision 2.
Muốn xem YAML Helm sẽ tạo mà không cài vào cluster?
helm template <release> <chart> -f values-xxx.yaml.