Helm包管理实战:从Chart开发到生产部署 引言 Helm是Kubernetes的包管理器,被称为"K8s的apt/yum"。它简化了应用的部署、升级和管理。本文将深入讲解Helm的核心概念、Chart开发和生产环境最佳实践。 一、Helm基础 1.1 核心概念 三个关键概念: Chart:Helm包(包含K8s资源描述) Config:配置文件(values.yaml) Release:Chart + Config的实例化 1.2 基本使用 二、Chart开发 2.1 Chart结构 2.2 Chart.yaml 2.3 values.yaml 2.4 模板语法 Deployment.yaml: helpers.tpl: 三、高级功能 3.
Helm是Kubernetes的包管理器,被称为"K8s的apt/yum"。它简化了应用的部署、升级和管理。本文将深入讲解Helm的核心概念、Chart开发和生产环境最佳实践。
三个关键概念:
# 安装Helm curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash # 查看版本 helm version # 添加仓库 helm repo add stable https://charts.helm.sh/stable helm repo add bitnami https://charts.bitnami.com/bitnami helm repo update # 搜索Chart helm search repo nginx # 查看Chart信息 helm show chart stable/nginx-ingress helm show values stable/nginx-ingress
# 安装Release helm install my-release stable/nginx-ingress # 使用自定义配置 helm install my-release -f values.yaml stable/nginx-ingress # 列出Release helm list helm list --all-namespaces # 查看Release状态 helm status my-release # 升级Release helm upgrade my-release -f new-values.yaml stable/nginx-ingress # 回滚 helm rollback my-release helm rollback my-release 2 # 回滚到版本2 # 卸载 helm uninstall my-release
my-chart/ ├── Chart.yaml # Chart元数据 ├── values.yaml # 默认配置值 ├── values.schema.json # 配置验证 ├── .helmignore # 忽略文件 ├── charts/ # 依赖的Chart ├── templates/ # 模板文件 │ ├── deployment.yaml │ ├── service.yaml │ ├── ingress.yaml │ ├── configmap.yaml │ ├── secret.yaml │ ├── _helpers.tpl # 可复用模板 │ └── NOTES.txt # 安装后提示信息 └── templates/tests/ # 测试 └── test-connection.yaml
apiVersion: v2 name: my-app description: A Helm chart for Kubernetes type: application # Chart版本(遵循语义化版本) version: 1.2.3 # 应用版本 appVersion: "2.0.0" # K8s API版本 kubeVersion: ">=1.19.0-0" # 关键词 keywords: - web - nginx - http # 维护者 maintainers: - name: John Doe email: john@example.com url: https://example.com # 图标 icon: https://example.com/icon.png # 主页 home: https://github.com/example/my-app # 来源 sources: - https://github.com/example/my-app # 依赖 dependencies: - name: postgresql version: 12.x.x repository: https://charts.bitnami.com/bitnami condition: postgresql.enabled
# 全局配置 global: imageRegistry: my-registry.example.com imagePullSecrets: - name: regcred # 镜像配置 image: repository: nginx tag: "1.25" pullPolicy: IfNotPresent # 副本数 replicaCount: 3 # 服务配置 service: type: ClusterIP port: 80 targetPort: 8080 annotations: {} # Ingress配置 ingress: enabled: true className: "nginx" annotations: cert-manager.io/cluster-issuer: "letsencrypt-prod" hosts: - host: app.example.com paths: - path: / pathType: Prefix tls: - secretName: app-tls hosts: - app.example.com # 资源限制 resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "256Mi" cpu: "500m" # 自动扩缩容 autoscaling: enabled: true minReplicas: 2 maxReplicas: 10 targetCPUUtilizationPercentage: 80 targetMemoryUtilizationPercentage: 80 # 环境变量 env: - name: LOG_LEVEL value: "info" - name: DB_HOST value: "postgres.default.svc.cluster.local" # Secret环境变量 envSecrets: - name: DB_PASSWORD secretKeyRef: name: db-credentials key: password # ConfigMap configMap: data: app.conf: | server.port=8080 logging.level.root=INFO # 亲和性 affinity: {} # nodeAffinity: # requiredDuringSchedulingIgnoredDuringExecution: # nodeSelectorTerms: # - matchExpressions: # - key: kubernetes.io/arch # operator: In # values: # - amd64 # 容忍 tolerations: [] # 节点选择器 nodeSelector: {} # Pod安全 podSecurityContext: runAsNonRoot: true runAsUser: 1000 fsGroup: 1000 # 容器安全 securityContext: allowPrivilegeEscalation: false capabilities: drop: - ALL readOnlyRootFilesystem: true # 探针 livenessProbe: httpGet: path: /health port: 8080 initialDelaySeconds: 30 periodSeconds: 10 readinessProbe: httpGet: path: /ready port: 8080 initialDelaySeconds: 5 periodSeconds: 5 # Service Account serviceAccount: create: true name: "" annotations: {} # Pod注释 podAnnotations: {} # Pod标签 podLabels: {}
Deployment.yaml:
apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "my-app.fullname" . }} labels: {{- include "my-app.labels" . | nindent 4 }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }} selector: matchLabels: {{- include "my-app.selectorLabels" . | nindent 6 }} template: metadata: annotations: checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }} {{- with .Values.podAnnotations }} {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "my-app.selectorLabels" . | nindent 8 }} {{- with .Values.podLabels }} {{- toYaml . | nindent 8 }} {{- end }} 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.targetPort }} protocol: TCP livenessProbe: {{- toYaml .Values.livenessProbe | nindent 12 }} readinessProbe: {{- toYaml .Values.readinessProbe | nindent 12 }} resources: {{- toYaml .Values.resources | nindent 12 }} env: {{- range .Values.env }} - name: {{ .name }} value: {{ .value | quote }} {{- end }} {{- range .Values.envSecrets }} - name: {{ .name }} valueFrom: secretKeyRef: name: {{ .secretKeyRef.name }} key: {{ .secretKeyRef.key }} {{- end }} volumeMounts: - name: config mountPath: /etc/app readOnly: true volumes: - name: config configMap: name: {{ include "my-app.fullname" . }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }}
_helpers.tpl:
{{/* Expand the name of the chart. */}} {{- define "my-app.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Create a default fully qualified app name. */}} {{- define "my-app.fullname" -}} {{- if .Values.fullnameOverride }} {{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} {{- else }} {{- $name := default .Chart.Name .Values.nameOverride }} {{- if contains $name .Release.Name }} {{- .Release.Name | trunc 63 | trimSuffix "-" }} {{- else }} {{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} {{- end }} {{- end }} {{- end }} {{/* Create chart name and version as used by the chart label. */}} {{- define "my-app.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }} {{/* Common labels */}} {{- define "my-app.labels" -}} helm.sh/chart: {{ include "my-app.chart" . }} {{ include "my-app.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }} {{/* Selector labels */}} {{- define "my-app.selectorLabels" -}} app.kubernetes.io/name: {{ include "my-app.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }} {{/* Create the name of the service account to use */}} {{- define "my-app.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} {{- default (include "my-app.fullname" .) .Values.serviceAccount.name }} {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }}
# pre-install hook apiVersion: batch/v1 kind: Job metadata: name: "{{ .Release.Name }}-pre-install" labels: app.kubernetes.io/managed-by: {{ .Release.Service }} annotations: "helm.sh/hook": pre-install "helm.sh/hook-weight": "-5" "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded spec: template: spec: restartPolicy: Never containers: - name: pre-install image: busybox command: ["/bin/sh", "-c", "echo Running pre-install hook"] --- # post-upgrade hook apiVersion: batch/v1 kind: Job metadata: name: "{{ .Release.Name }}-post-upgrade" annotations: "helm.sh/hook": post-upgrade "helm.sh/hook-delete-policy": hook-succeeded spec: template: spec: restartPolicy: Never containers: - name: post-upgrade image: myapp:latest command: ["python", "migrate.py"]
Hook类型:
# requirements.yaml(Helm 2) dependencies: - name: postgresql version: 12.x.x repository: https://charts.bitnami.com/bitnami condition: postgresql.enabled - name: redis version: 17.x.x repository: https://charts.bitnami.com/bitnami alias: cache tags: - cache --- # Chart.yaml(Helm 3) dependencies: - name: postgresql version: 12.x.x repository: https://charts.bitnami.com/bitnami condition: postgresql.enabled
# 更新依赖 helm dependency update # 查看依赖树 helm dependency list
# values.schema.json { "$schema": "http://json-schema.org/draft-07/schema#", "title": "My App Values", "type": "object", "required": ["image"], "properties": { "image": { "type": "object", "required": ["repository", "tag"], "properties": { "repository": { "type": "string", "pattern": "^[a-z0-9]+(/[a-z0-9]+)*$" }, "tag": { "type": "string", "pattern": "^[0-9]+\\.[0-9]+\\.[0-9]+$" } } }, "replicaCount": { "type": "integer", "minimum": 1, "maximum": 100 } } } --- # 测试 apiVersion: v1 kind: Pod metadata: name: "{{ include "my-app.fullname" . }}-test-connection" labels: {{- include "my-app.labels" . | nindent 4 }} annotations: "helm.sh/hook": test-success spec: containers: - name: wget image: busybox command: ['wget'] args: ['{{ include "my-app.fullname" . }}:{{ .Values.service.port }}'] restartPolicy: Never
# 验证Chart helm lint ./my-chart # 模板渲染(不安装) helm template my-release ./my-chart # 测试安装 helm install my-release ./my-chart --dry-run --debug # 运行测试 helm test my-release
# values-dev.yaml image: tag: "dev-latest" replicaCount: 1 resources: requests: memory: "64Mi" cpu: "50m" limits: memory: "128Mi" cpu: "200m" --- # values-staging.yaml image: tag: "staging-latest" replicaCount: 2 resources: requests: memory: "128Mi" cpu: "100m" limits: memory: "256Mi" cpu: "500m" --- # values-prod.yaml image: tag: "1.2.3" replicaCount: 3 autoscaling: enabled: true minReplicas: 3 maxReplicas: 10 resources: requests: memory: "256Mi" cpu: "200m" limits: memory: "512Mi" cpu: "1000m"
# 部署到不同环境 helm install myapp-prod ./my-chart -f values-prod.yaml helm install myapp-staging ./my-chart -f values-staging.yaml helm install myapp-dev ./my-chart -f values-dev.yaml
# GitHub Actions name: Deploy with Helm on: push: tags: - 'v*' jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v1 with: aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} aws-region: us-west-2 - name: Install Helm uses: azure/setup-helm@v1 - name: Build and push image run: | docker build -t myapp:${{ github.ref_name }} . docker tag myapp:${{ github.ref_name }} $ECR_REGISTRY/myapp:${{ github.ref_name }} docker push $ECR_REGISTRY/myapp:${{ github.ref_name }} - name: Deploy with Helm run: | helm upgrade --install myapp ./my-chart \ -f values-prod.yaml \ --set image.tag=${{ github.ref_name }} \ --namespace production \ --create-namespace \ --wait \ --timeout 10m
# 创建Chart仓库 helm repo index charts/ --url https://charts.example.com/ # 上传到S3 aws s3 sync charts/ s3://my-charts/ --acl public-read # 添加私有仓库 helm repo add my-charts https://charts.example.com/ --username user --password pass # 更新索引 make index
# nginx配置(自托管Chart仓库) server { listen 80; server_name charts.example.com; location / { root /var/www/charts; autoindex on; index index.yaml; } location ~* \.yaml$ { default_type application/yaml; } }
# 使用helm-secrets插件 helm plugin install https://github.com/jkroepke/helm-secrets # 加密Secret echo "password: secret123" | sops --encrypt --kms "arn:aws:kms:..." > secrets.yaml # 使用加密Secret helm install myapp ./my-chart -f secrets.yaml
# secrets.yaml(加密后) database: password: ENC[AES256_GCM,data:...,tag:...,type:str]
# 查看Release历史 helm history my-release # 查看渲染后的模板 helm get manifest my-release # 查看values helm get values my-release # 查看所有信息 helm get all my-release # 回滚到特定版本 helm rollback my-release 2 # 调试模板渲染 helm template myapp ./my-chart -f values.yaml --debug # 检查Chart依赖 helm dependency list
Helm生产实践要点: