一個服務上 K8s,你要寫 Deployment、Service、Ingress、ConfigMap、Secret、HPA、PDB、ServiceAccount、NetworkPolicy——九個 YAML 檔。三個服務就是 27 個。dev / staging / prod 三個環境就是 81 個。你不會想一個一個手寫的。Helm 就是來解決這個問題。
先講結論
Helm 是 K8s 的 apt-get / brew。Chart = 一組模板化的 YAML,values.yaml = 參數。helm install 一行就能部署 Prometheus + Grafana + Alertmanager + node-exporter,不用自己寫 20 個 YAML。自己寫的服務也能包成 Chart,用 values override 切換 dev / staging / prod。另一個選擇是 Kustomize(覆蓋式,不用模板語法),兩者各有適用場景。
Chart 結構
mychart/
├── Chart.yaml # Chart 的 metadata(name, version, dependencies)
├── values.yaml # 預設參數
├── templates/
│ ├── _helpers.tpl # 共用 template 片段
│ ├── deployment.yaml
│ ├── service.yaml
│ ├── ingress.yaml
│ ├── configmap.yaml
│ ├── hpa.yaml
│ └── serviceaccount.yaml
└── charts/ # dependency charts
Chart.yaml
apiVersion: v2
name: my-api
description: My API service Helm chart
type: application
version: 0.1.0 # Chart 版本
appVersion: "1.2.0" # App 版本(顯示用)
dependencies:
- name: postgresql
version: ~15.0.0
repository: https://charts.bitnami.com/bitnami
condition: postgresql.enabledvalues.yaml(預設值)
replicaCount: 2
image:
repository: myapp/api
tag: "1.2.0"
pullPolicy: IfNotPresent
service:
type: ClusterIP
port: 80
ingress:
enabled: true
className: nginx
hosts:
- host: api.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: api-tls
hosts:
- api.example.com
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
autoscaling:
enabled: true
minReplicas: 2
maxReplicas: 10
targetCPUUtilizationPercentage: 70
env:
APP_ENV: production
LOG_LEVEL: info
secrets:
DATABASE_URL: "" # 安裝時再指定
postgresql:
enabled: false # 預設不裝內建 DB模板語法
templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "my-api.fullname" . }}
labels:
{{- include "my-api.labels" . | nindent 4 }}
spec:
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "my-api.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
labels:
{{- include "my-api.selectorLabels" . | nindent 8 }}
spec:
serviceAccountName: {{ include "my-api.serviceAccountName" . }}
containers:
- name: {{ .Chart.Name }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- containerPort: 8080
envFrom:
- configMapRef:
name: {{ include "my-api.fullname" . }}-config
env:
{{- range $key, $value := .Values.secrets }}
{{- if $value }}
- name: {{ $key }}
valueFrom:
secretKeyRef:
name: {{ include "my-api.fullname" . }}-secret
key: {{ $key }}
{{- end }}
{{- end }}
livenessProbe:
httpGet:
path: /healthz
port: 8080
initialDelaySeconds: 10
periodSeconds: 15
readinessProbe:
httpGet:
path: /ready
port: 8080
periodSeconds: 5
resources:
{{- toYaml .Values.resources | nindent 12 }}templates/_helpers.tpl
{{/*
完整名稱(release name + chart name)
*/}}
{{- define "my-api.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{/*
共用 labels
*/}}
{{- define "my-api.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version }}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "my-api.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
ServiceAccount name
*/}}
{{- define "my-api.serviceAccountName" -}}
{{ include "my-api.fullname" . }}-sa
{{- end }}關鍵模板語法
| 語法 | 用途 | 範例 |
|---|---|---|
{{ .Values.xxx }} | 讀 values.yaml 的值 | {{ .Values.replicaCount }} |
{{ include "name" . }} | 引用 _helpers.tpl | {{ include "my-api.fullname" . }} |
{{- if .Values.xxx }} | 條件判斷 | 只在 enabled 時產出 YAML |
{{- range }} | 迴圈 | 迴圈產出多個 env var |
{{ toYaml . | nindent N }} | 把 YAML 物件轉成文字並縮排 | resources 整塊搬進去 |
{{ sha256sum }} | 計算 hash | ConfigMap 改了自動觸發 rollout |
常用指令
# 安裝
helm install my-release ./mychart \
-f values-prod.yaml \
--set secrets.DATABASE_URL="postgresql://..." \
--namespace production \
--create-namespace
# 升級(改 image tag 或 values)
helm upgrade my-release ./mychart \
-f values-prod.yaml \
--set image.tag="1.3.0"
# 回滾到上一版
helm rollback my-release 1
# 看歷史
helm history my-release
# 列出所有 release
helm list --all-namespaces
# 預覽 render 結果(不實際部署)
helm template my-release ./mychart -f values-prod.yaml
# 只看差異
helm diff upgrade my-release ./mychart -f values-prod.yamlhelm template 是最有用的 debug 工具。它把模板 render 成最終的 YAML 輸出,你可以檢查有沒有語法錯誤或值填錯。
常用社群 Chart
這些 Chart 一個指令就能裝好一整套基礎設施:
# Prometheus + Grafana + Alertmanager(見 06-k8s-monitoring)
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
--namespace monitoring --create-namespace
# Nginx Ingress Controller(見 02-k8s-networking)
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespace
# cert-manager(自動 TLS 憑證)
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager --create-namespace \
--set crds.enabled=true
# Sealed Secrets(見 04-k8s-config-rbac)
helm install sealed-secrets sealed-secrets/sealed-secrets \
--namespace kube-system
# ArgoCD(見 08-k8s-gitops)
helm install argocd argo/argo-cd \
--namespace argocd --create-namespace每一個都可以用 -f custom-values.yaml 覆蓋預設參數。不用從頭寫 20 個 YAML。
多環境管理
mychart/
├── values.yaml # 預設值(共用的基礎設定)
├── values-dev.yaml # dev override
├── values-staging.yaml # staging override
└── values-prod.yaml # production override
values-dev.yaml
replicaCount: 1
image:
tag: "dev-latest"
ingress:
hosts:
- host: api-dev.example.com
paths:
- path: /
pathType: Prefix
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi
autoscaling:
enabled: falsevalues-prod.yaml
replicaCount: 3
image:
tag: "1.2.0"
ingress:
hosts:
- host: api.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: api-prod-tls
hosts:
- api.example.com
resources:
requests:
cpu: 250m
memory: 256Mi
limits:
cpu: "1"
memory: 1Gi
autoscaling:
enabled: true
minReplicas: 3
maxReplicas: 20# 部署 dev
helm upgrade --install api-dev ./mychart -f values-dev.yaml -n dev
# 部署 prod
helm upgrade --install api-prod ./mychart -f values-prod.yaml -n productionHelm vs Kustomize
| 面向 | Helm | Kustomize |
|---|---|---|
| 哲學 | 模板化(Go template) | 覆蓋式(patch / overlay) |
| 學習曲線 | 中(要學模板語法) | 低(只要會寫 YAML) |
| 社群生態 | 巨大(幾千個 Chart) | kubectl 內建 |
| 適合場景 | 包裝成套件給別人用 | 自己的服務多環境管理 |
| 缺點 | 模板太複雜時很難讀 | 不能產生新的資源結構 |
Kustomize 範例
k8s/
├── base/
│ ├── kustomization.yaml
│ ├── deployment.yaml
│ └── service.yaml
├── overlays/
│ ├── dev/
│ │ ├── kustomization.yaml
│ │ └── patch-replicas.yaml
│ └── prod/
│ ├── kustomization.yaml
│ └── patch-replicas.yaml
# base/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- deployment.yaml
- service.yaml
# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
patches:
- path: patch-replicas.yaml
namePrefix: prod-
namespace: production
# overlays/prod/patch-replicas.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-server
spec:
replicas: 5# 預覽
kubectl kustomize overlays/prod
# 部署
kubectl apply -k overlays/prod我的建議:
- 給社群/團隊共用的東西 → Helm Chart
- 自己的服務多環境部署 → 都可以,看團隊偏好
- 兩者可以混用:用 Helm 裝社群 Chart,用 Kustomize 管理自己的 YAML
常見踩坑
helm upgrade 失敗卡在 pending-upgrade:helm rollback 回到上一版,修好再 upgrade。
values 層級搞混:--set ingress.hosts[0].host=xxx 的陣列語法很容易寫錯。建議用 -f values.yaml 覆蓋,不要用 --set 處理複雜結構。
dependency chart 版本衝突:Chart.lock 跟 Chart.yaml 不一致。跑 helm dependency update 重新產生。
模板 render 出空白行:Go template 的 {{- 會吃掉前面的空白,-}} 吃掉後面的。善用這個語法避免輸出不必要的空行。
Helm 不完美——Go template 語法寫複雜了會想翻桌。但在 K8s 的世界裡,手寫 81 個 YAML 更想翻桌。挑你比較能接受的那個。
延伸閱讀
本系列文章
- ← 上一篇: RBAC
- 本篇:Helm:K8s 的套件管理
- 下一篇:監控:Prometheus Operator + Grafana on K8s →