Helm: chart, values theo môi trường, release

4 câu hỏi phỏng vấn~30 phútTừ khóa: Chart, Chart.yaml, values.yaml, templates, .Values, .Release, _helpers.tpl, define, include, toYaml, nindent, values-prod.yaml, --set, helm template, upgrade --install, --atomic, history, rollback, Release revision

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%

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ápNghĩaJava tương tự
.Values.x, .Release.Name, .Chart.NameĐọc values, thông tin release, chartconfig object
.Context hiện tạithis
if / range / withĐiều kiện / vòng lặp / đổi contextif / for
define + includeKhai báo và gọi template dùng chungmethod
| quote, | default xPipeline: thêm ngoặc kép, giá trị dự phònggọ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 YAMLKustomizeHelm
Tham số hóaKhôngOverlay/patch, không templateTemplate + values
Lịch sử, rollback cả bộKhôngKhôngCó (revision)
Xóa resource thừaKhôngKhông (trừ prune)Có
Chia sẻ, dependencyKhôngHạn chếChart repo, OCI registry, dependencies
Độ khó đọcDễ nhấtDễ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

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

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:

  1. 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 qua existingSecret như ví dụ trên. Đây là cách sạch nhất.
  2. Mã hóa trong Git: helm-secrets + SOPS, hoặc Sealed Secrets. Git chỉ chứa bản mã hóa.
  3. 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.yaml làm mặc định và các file values-dev, values-staging, values-prod chỉ ghi phần khác. Pipeline chạy helm 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.yaml and values-dev, values-staging and values-prod files that only contain the differences. The pipeline runs helm 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 --atomic so a failed upgrade rolls back automatically.

Cách nhớ ngắn gọn

Tổng kết chương Kubernetes

BàiChủ đềBài toán giải quyếtResource / công cụ chính
17.1Control planeAi chạy container ở đâu, ai giữ đúng số lượngAPI server, etcd, scheduler, controller, kubelet
17.2Pod & probeProcess chạy chưa chắc app khỏe; tắt không làm rơi requeststartup/readiness/liveness probe, preStop, graceful shutdown
17.3WorkloadsGiữ N Pod, deploy không downtime, app có state, batchDeployment, ReplicaSet, StatefulSet, DaemonSet, Job, CronJob
17.4Service & IngressPod IP thay đổi; người dùng vào từ InternetService, CoreDNS, Ingress, Ingress Controller
17.5ConfigMap, Secret, RBACConfig tách khỏi image, bảo vệ secret, phân quyềnConfigMap, Secret, Role, RoleBinding, ServiceAccount
17.6Resource & autoscalingĐặt Pod đúng node, chống ăn hết RAM, scale theo tảirequests/limits, QoS, HPA, VPA, PDB, PVC
17.7TroubleshootingPod lỗi thì nhìn ở đâudescribe, logs --previous, events, exec, debug
17.8HelmYAML lặp theo môi trường, quản lý releaseChart, 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.