📦 第16课:Helm包管理

📌 课程阶段:高级运维(1/5)|预计时间:60分钟|难度:⭐⭐⭐☆☆

一、Helm——K8s的包管理器

Helm是K8s的包管理器,将一组K8s YAML模板化,通过values.yaml参数化配置,实现"一键部署"复杂应用。类似apt/yum之于Linux。

┌──────────── Helm核心概念 ────────────┐
│                                        │
│  Chart = 一个应用包                    │
│  ├── Chart.yaml     # 元数据          │
│  ├── values.yaml    # 默认配置         │
│  ├── templates/     # K8s模板          │
│  │   ├── deployment.yaml              │
│  │   ├── service.yaml                 │
│  │   ├── ingress.yaml                 │
│  │   ├── _helpers.tpl  # 共用模板     │
│  │   └── NOTES.txt     # 安装提示     │
│  └── charts/        # 依赖Chart       │
│                                        │
│  Release = Chart的一次安装实例         │
│  同一Chart可以安装多次(不同Release)   │
│                                        │
│  Repository = Chart仓库               │
│  Artifact Hub / Chart Museum / 自建   │
│                                        │
│  Helm 3架构(无Tiller):              │
│  helm → K8s API Server                │
│  Release存储在K8s Secret中            │
└────────────────────────────────────────┘

二、Helm安装与基本操作

# 安装Helm
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash

# ✅ 验证通过
helm version
# version.BuildInfo{Version:"v3.14.x", ...}

# 添加仓库
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

# 搜索Chart
helm search repo nginx
# NAME                    CHART VERSION   APP VERSION   DESCRIPTION
# bitnami/nginx           16.x.x         1.25.x        NGINX Open Source...

# 安装Chart
helm install my-nginx bitnami/nginx \
  --namespace web \
  --create-namespace \
  --set replicaCount=3 \
  --set service.type=NodePort

# ✅ 验证通过
helm list -n web
# NAME       NAMESPACE   REVISION   STATUS    CHART          APP VERSION
# my-nginx   web         1          deployed  nginx-16.x.x   1.25.x

# 查看Release状态
helm status my-nginx -n web

# 查看Release历史
helm history my-nginx -n web

三、开发自定义Chart

3.1 创建Chart骨架

# 创建Chart
helm create my-app
# Creating my-app

# 目录结构
tree my-app
# my-app/
# ├── Chart.yaml
# ├── Chart.lock
# ├── values.yaml
# ├── charts/
# ├── templates/
# │   ├── deployment.yaml
# │   ├── service.yaml
# │   ├── ingress.yaml
# │   ├── hpa.yaml
# │   ├── serviceaccount.yaml
# │   ├── _helpers.tpl
# │   ├── NOTES.txt
# │   └── tests/
# │       └── test-connection.yaml

3.2 Chart.yaml

# my-app/Chart.yaml
apiVersion: v2
name: my-app
description: A Helm chart for my application
type: application                    # application | library
version: 1.0.0                       # Chart版本(SemVer2)
appVersion: "2.1.0"                  # 应用版本
kubeVersion: ">=1.24.0"             # 兼容的K8s版本
dependencies:
- name: redis
  version: "18.x.x"
  repository: "https://charts.bitnami.com/bitnami"
  condition: redis.enabled           # 条件依赖
  alias: cache                       # 别名
- name: postgresql
  version: "13.x.x"
  repository: "https://charts.bitnami.com/bitnami"
  condition: postgresql.enabled
maintainers:
- name: team
  email: team@example.com
home: https://github.com/example/my-app
sources:
- https://github.com/example/my-app
keywords: ["web", "api", "microservice"]

3.3 values.yaml

# my-app/values.yaml
replicaCount: 2

image:
  repository: my-registry/my-app
  pullPolicy: IfNotPresent
  tag: ""                            # 默认用Chart.appVersion

imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""

serviceAccount:
  create: true
  annotations: {}
  name: ""

podAnnotations: {}
podSecurityContext:
  runAsNonRoot: true
  runAsUser: 1000

securityContext:
  allowPrivilegeEscalation: false
  capabilities:
    drop: ["ALL"]
  readOnlyRootFilesystem: true

service:
  type: ClusterIP
  port: 8080

ingress:
  enabled: false
  className: nginx
  annotations: {}
  hosts:
  - host: my-app.example.com
    paths:
    - path: /
      pathType: Prefix
  tls: []

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 512Mi

autoscaling:
  enabled: false
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 80

# 依赖Chart配置
redis:
  enabled: true
  architecture: standalone
  auth:
    enabled: false

postgresql:
  enabled: true
  auth:
    username: app
    database: appdb
  primary:
    persistence:
      size: 10Gi

3.4 模板文件

# my-app/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "my-app.fullname" . }}
  labels:
    {{- include "my-app.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "my-app.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
      labels:
        {{- include "my-app.selectorLabels" . | nindent 8 }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "my-app.serviceAccountName" . }}
      securityContext:
        {{- toYaml .Values.podSecurityContext | nindent 8 }}
      containers:
      - name: {{ .Chart.Name }}
        securityContext:
          {{- toYaml .Values.securityContext | nindent 12 }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
        imagePullPolicy: {{ .Values.image.pullPolicy }}
        ports:
        - name: http
          containerPort: {{ .Values.service.port }}
        env:
        - name: REDIS_HOST
          value: {{ include "my-app.redisHost" . | quote }}
        - name: DB_HOST
          value: {{ include "my-app.postgresqlHost" . | quote }}
        resources:
          {{- toYaml .Values.resources | nindent 12 }}
        livenessProbe:
          httpGet:
            path: /healthz
            port: http
        readinessProbe:
          httpGet:
            path: /ready
            port: http

3.5 _helpers.tpl共享模板

# my-app/templates/_helpers.tpl
{{- define "my-app.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else if .Values.nameOverride }}
{{- .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}

{{- define "my-app.labels" -}}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{ include "my-app.selectorLabels" . }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{- define "my-app.selectorLabels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{- define "my-app.redisHost" -}}
{{- if .Values.redis.enabled }}
{{- printf "%s-cache-master" (include "my-app.fullname" .) }}
{{- else }}
{{- .Values.externalRedis.host | default "redis" }}
{{- end }}
{{- end }}

四、Helm高级操作

# 安装时自定义values
helm install my-app ./my-app \
  -f my-values.yaml \
  --set image.tag=v2.1.0 \
  --set replicaCount=5

# 升级
helm upgrade my-app ./my-app \
  --set image.tag=v2.2.0 \
  --reuse-values               # 保留之前的values

# 回滚
helm rollback my-app 1         # 回滚到revision 1

# 卸载
helm uninstall my-app -n web

# 调试模板渲染(不安装)
helm template my-app ./my-app > rendered.yaml
helm install my-app ./my-app --dry-run --debug

# 更新依赖
helm dependency update ./my-app
helm dependency build ./my-app

# ✅ 验证通过 - 完整安装流程
helm install my-app ./my-app -n production -f prod-values.yaml
kubectl get all -n production -l app.kubernetes.io/instance=my-app

五、故障排查

# 查看渲染后的YAML
helm get manifest my-app -n web

# 查看所有values
helm get values my-app -n web --all

# Chart验证
helm lint ./my-app
# ==> Linting ./my-app
# 1 chart(s) linted, 0 chart(s) failed

# 查看Release状态异常
helm status my-app -n web

六、练习

  1. 使用helm create创建一个Chart,修改values并安装
  2. 为Chart添加ConfigMap和Secret模板
  3. 使用条件渲染和循环实现灵活配置
  4. 打包Chart并推送到ChartMuseum仓库
  5. 实现Chart的升级、回滚和版本管理

🏆 第16课成就解锁

下一课预告:第17课深入自定义CRD——扩展K8s API。

📌 补充知识

16-helm包管理补充要点K8s生产实践扩展

🔹 资源配额(ResourceQuota):限制命名空间总资源
  apiVersion: v1
  kind: ResourceQuota
  metadata:
    name: compute-quota
    namespace: production
  spec:
    hard:
      requests.cpu: "20"
      requests.memory: 40Gi
      limits.cpu: "40"
      limits.memory: 80Gi
      pods: "50"
      services: "10"

🔹 LimitRange:设置默认资源限制
  apiVersion: v1
  kind: LimitRange
  metadata:
    name: default-limits
  spec:
    limits:
    - type: Container
      default:
        cpu: "200m"
        memory: 256Mi
      defaultRequest:
        cpu: "100m"
        memory: 128Mi
      max:
        cpu: "2"
        memory: 4Gi

🔹 Pod优先级与抢占
  apiVersion: scheduling.k8s.io/v1
  kind: PriorityClass
  metadata:
    name: high-priority
  value: 1000000
  globalDefault: false
  ---
  spec:
    preemptionPolicy: PreemptLowerPriority

🔹 优雅处理Pod中断
  • PDB保证最小可用副本
  • preStop钩子处理连接排空
  • terminationGracePeriodSeconds充足
  • 应用必须处理SIGTERM信号

🔹 生产环境Checklist
  ✅ 设置resources requests/limits
  ✅ 配置liveness/readiness探针
  ✅ 使用PDB保护关键服务
  ✅ 实现优雅关闭(SIGTERM)
  ✅ 配置HPA自动伸缩
  ✅ 使用NetworkPolicy隔离
  ✅ 开启RBAC最小权限
  ✅ 日志结构化输出
  ✅ 指标暴露/metrics端点
  ✅ 配置PVC数据备份

📎 扩展阅读与生产实践

16-helm包管理生产环境进阶要点

🔹 性能优化关键参数
  • kubelet: --max-pods=110 --pods-per-core=10
  • kube-apiserver: --max-requests-inflight=800
  • etcd: --quota-backend-bytes=8589934592
  • kube-scheduler: --percentage-of-nodes-to-score=50

🔹 集群容量规划
  • 控制平面:3节点,8C16G起步
  • Worker节点:按应用类型分组
  • etcd:SSD磁盘,<2ms延迟
  • 网络带宽:10Gbps+集群内互联

🔹 故障自愈最佳实践
  1. Pod: livenessProbe自动重启
  2. Deployment: ReplicaSet保证副本数
  3. Node: kubelet自注册+健康检查
  4. Cluster: Cluster Autoscaler增减节点
  5. Multi-Cluster: Karmada联邦容灾

🔹 K8s版本升级策略
  • 每次只升一个minor版本
  • 先升级控制平面,再升级Worker
  • 使用kubeadm upgrade plan预检
  • 准备回滚方案
  • 在staging环境验证后再升级prod