【K8S 运维实战】31-Helm包管理

Helm 包管理:Chart 开发与仓库治理 > 从"一把梭 kubectl apply"到可复用、可版本化、可治理的部署体系

写在前面

你可能已经用 Helm 安过不少 Chart------helm install nginx-ingress,一条命令,啪,起来了。但当你的集群从 5 个应用膨胀到上百个,问题就来了:每个团队的 Chart 目录结构五花八门,values.yaml 覆盖逻辑像意大利面条,私有仓库里同一个 Chart 有 v1.0、v1.0-hotfix、v1.0.1-rc 三个版本谁也搞不清哪个上了生产。这篇文章不教你"怎么装 Chart",而是教你怎么开发规范 Chart、治理 Helm 仓库、编排多环境部署,让 Helm 从一个安装工具变成你真正的包管理基础设施。

核心问题

怎么管理上百个应用的部署------让 Chart 可复用、values 可分层、版本可追溯、多环境可差异化?


一、原理剖析

1.1 Chart 的本质:一套模板 + 一组默认值

Helm Chart 不是"打包好的镜像",它是一组 Go Template + 一个 values.yaml 的默认值。渲染过程就是:模板引擎把 templates/ 下每个 .yaml 文件里的 {``{ .Values.xxx }} 替换成实际值,最终输出一份完整的 Kubernetes 资源清单。

复制代码
Chart 目录结构(标准)
mychart/
├── Chart.yaml          # 元数据:名称、版本(appVersion vs version)、依赖
├── values.yaml         # 默认值------所有可配置项的"基线"
├── templates/
│   ├── _helpers.tpl    # 共用模板片段(命名约定、标签集、公共逻辑)
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── configmap.yaml
│   ├── hpa.yaml
│   ├── NOTES.txt       # 安装后提示信息
│   └── tests/
│       └─ test-connection.yaml
├── templates/partials/ # 可选:拆分大模板的子片段
├── .helmignore         # 打包排除列表
└── crds/               # CRD 定义(安装前自动加载)

关键区分:

  • version: Chart 自身的打包版本,遵循 semver,每次改动必须递增。
  • appVersion: 应用(镜像)的版本,是信息性字段,不参与 Helm 的版本计算。

1.2 Values 分层:默认值 → 組覆值 → 命令行覆值

Helm 的值合并遵循一个优先级链,高优先级覆盖低优先级:

复制代码
优先级(从低到高):
1. Chart 内 values.yaml          ← 基线默认值
2. Parent Chart 的 values.yaml   ← 如果是子 Chart(subchart)
3. User 的 -f values-prod.yaml   ← 環境/自定义覆盖文件
4. User 的 --set key=val         ← 命令行覆盖(最高优先级)
5. User 的 --set-string key=val  ← 同上,强制字符串类型

渲染时的合并逻辑是深度合并(dict 递归合并,list 整体替换):

yaml 复制代码
# values.yaml (基线)
replicaCount: 1
resources:
  requests:
    cpu: 100m
    memory: 128Mi

# values-prod.yaml (覆盖)
replicaCount: 3
resources:
  requests:
    cpu: 500m
  # memory 未指定 → 保留基线 128Mi

# 渲染结果
replicaCount: 3
resources:
  requests:
    cpu: 500m
    memory: 128Mi    # 深度合并:未覆盖的 key 保留

踩坑预警 : list 类型不会逐项合并,而是整体替换。如果你在基线写了 tolerations: [key1, key2],覆盖文件写了 tolerations: [key3],结果是 [key3] 而不是 [key1, key2, key3]。所以 list 类型的 values 通常在基线设为空 [],让覆盖文件完整提供。

1.3 Helmfile:多 Chart 编排与环境差异化

当你有 10+ 个 Chart 要部署到 3 个环境(dev/staging/prod),纯 Helm 命令行已经不够用了。Helmfile 是一个声明式编排工具,让你用一个 helmfile.yaml 管理所有 Chart 的安装顺序、值覆盖和版本锁定。

复制代码
Helmfile 值覆盖优先级(在 Helm 之上再加一层):

helmfile.yaml
├── repositories:        # 声明 Chart 仓库源
├── releases:
│   ├── name: api-gateway
│   │   chart: stable/nginx-ingress
│   │   version: 4.11.3   # 锁定版本
│   │   values:            # 覆盖文件列表(按顺序合并)
│   │   - values/base.yaml
│   │   - values/{{ .Environment.Name }}.yaml  # 環境差异化
│   │   - values/api-gateway.yaml              # 应用差异化
│   │   set:                # 等同 --set
│   │   - clusterName={{ .Environment.Values.clusterName }}

Helmfile 的核心价值:

  • 版本锁定 : 每个 release 指定 version,避免"最新版漂移"。
  • 環境差异化 : 用 {``{ .Environment.Name }} 模板变量自动选择覆盖文件。
  • 依赖顺序 : needs 字段控制安装先后,比如先装数据库再装应用。
  • 批量操作 : helmfile sync 一条命令同步所有 release 到目标状态。

1.4 Helm vs Kustomize:适用场景对比

复制代码
┌──────────────────────────────────────────────────────┐
│                    对比维度                            │
├──────────┬───────────────────┬───────────────────────┤
│          │  Helm             │  Kustomize             │
├──────────┼───────────────────┼───────────────────────┤
│ 模板方式 │  Go Template      │  纯 YAML patch/overlay │
│ 分发方式 │  打包为 Chart      │  不打包,就地叠加       │
│ 版本管理 │  semver 仓库       │  Git commit 即版本     │
│ 值覆盖   │  values 分层       │  overlay 分层          │
│ 适用场景 │  第三方/公共 Chart │  内部应用/微调已有资源  │
│ 学习曲线 │  较高(Go Template) │  较低(纯 YAML)        │
│ 生态     │  Artifact Hub 丰富 │  原生 kubectl 支持     │
│ 复杂度   │  适合完整应用包    │  适合局部定制/补丁      │
└──────────┴───────────────────┴───────────────────────┘

选型建议:
- 安装第三方组件(Nginx Ingress/Prometheus/Redis Operator) → 用 Helm
- 内部微服务部署,只需要差异化覆盖 → 用 Kustomize 或 Helm+Kustomize 插件
- 多团队共享的应用模板 → 用 Helm Chart(标准化强)
- 一个集群里对同一组件多处微调 → 用 Kustomize overlay
- 两者可以共存: Helm 产出原始 YAML,Kustomize 再 patch

二、实战操作

2.1 企业级 Chart 模板开发

先创建一个规范的 Chart:

bash 复制代码
helm create myapp
# 然后我们对生成的模板做规范化改造

Chart.yaml --- 元数据规范:

yaml 复制代码
apiVersion: v2
name: myapp
description: 内部业务应用部署模板
type: application
version: 1.0.0          # Chart 打包版本,每次发布必须递增
appVersion: "2.4.1"     # 应用镜像版本,信息性字段
kubeVersion: ">=1.28"   # 支持的 K8s 版本范围
home: https://wiki.internal/myapp
maintainers:
  - name: platform-team
    email: platform@company.com
annotations:
  category: BusinessApplication
  license: Apache-2.0

templates/_helpers.tpl --- 共用模板片段(这是企业级 Chart 的灵魂):

gotemplate 复制代码
{{/*
标准命名约定:release-name-chart-name
*/}}
{{- define "myapp.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 }}

{{/*
标准标签集:所有资源必须携带
*/}}
{{- define "myapp.labels" -}}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{ include "myapp.selectorLabels" . }}
app.kubernetes.io/version: {{ .Values.image.tag | default .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
app.kubernetes.io/part-of: {{ .Chart.Name }}
{{- end }}

{{/*
选择器标签:Deployment/Service 的 selector 必须用这组
*/}}
{{- define "myapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "myapp.fullname" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
公共注释块
*/}}
{{- define "myapp.annotations" -}}
{{- if .Values.commonAnnotations }}
{{ toYaml .Values.commonAnnotations }}
{{- end }}
{{- end }}

templates/deployment.yaml --- 使用 helpers 的 Deployment:

yaml 复制代码
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "myapp.fullname" . }}
  labels:
    {{- include "myapp.labels" . | nindent 4 }}
  {{- with (include "myapp.annotations" .) }}
  annotations:
    {{- . | nindent 4 }}
  {{- end }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "myapp.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "myapp.selectorLabels" . | nindent 8 }}
        {{- with .Values.podLabels }}
        {{- toYaml . | nindent 8 }}
        {{- end }}
    spec:
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      serviceAccountName: {{ include "myapp.fullname" . }}
      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 }}
          env:
            {{- range $key, $value := .Values.extraEnv }}
            - name: {{ $key }}
              value: {{ $value | quote }}
            {{- end }}
          ports:
            - name: http
              containerPort: {{ .Values.service.port }}
              protocol: TCP
          livenessProbe:
            {{- toYaml .Values.livenessProbe | nindent 12 }}
          readinessProbe:
            {{- toYaml .Values.readinessProbe | nindent 12 }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.topologySpreadConstraints }}
      topologySpreadConstraints:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      tolerations:
        {{- toYaml .Values.tolerations | nindent 8 }}

2.2 Values 分层实战

values.yaml --- 基线默认值(保守配置):

yaml 复制代码
replicaCount: 1

image:
  repository: registry.internal/company/myapp
  pullPolicy: IfNotPresent
  tag: ""

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

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

podSecurityContext:
  runAsNonRoot: true
  runAsUser: 1000

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

service:
  type: ClusterIP
  port: 8080

ingress:
  enabled: false
  className: ""
  annotations: {}
  hosts: []
  tls: []

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

livenessProbe:
  httpGet:
    path: /healthz
    port: http
  initialDelaySeconds: 30
  periodSeconds: 10

readinessProbe:
  httpGet:
    path: /ready
    port: http
  initialDelaySeconds: 5
  periodSeconds: 5

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

tolerations: []
affinity: {}
topologySpreadConstraints: []

extraEnv: {}

commonAnnotations: {}
podLabels: {}

values-prod.yaml --- 生产环境覆盖:

yaml 复制代码
replicaCount: 3

image:
  pullPolicy: Always
  tag: "2.4.1"

service:
  type: ClusterIP

ingress:
  enabled: true
  className: nginx
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: myapp.company.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: myapp-tls
      hosts: [myapp.company.com]

resources:
  requests:
    cpu: 500m
    memory: 256Mi
  limits:
    cpu: "1"
    memory: 1Gi

autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 20
  targetCPUUtilizationPercentage: 70

tolerations:
  - key: dedicated
    operator: Equal
    value: production
    effect: NoSchedule

affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchExpressions:
              - key: app.kubernetes.io/name
                operator: In
                values: [myapp]
          topologyKey: kubernetes.io/hostname

topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app.kubernetes.io/name: myapp

values-staging.yaml --- 预发环境覆盖:

yaml 复制代码
replicaCount: 2

image:
  tag: "2.4.1-rc1"

ingress:
  enabled: true
  className: nginx
  hosts:
    - host: myapp-staging.company.com
      paths:
        - path: /
          pathType: Prefix

resources:
  requests:
    cpu: 200m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 256Mi

安装命令:

bash 复制代码
# 开发环境(只用默认值)
helm install myapp ./mychart -n dev

# 预发环境(覆盖)
helm install myapp ./mychart -n staging -f values-staging.yaml

# 生产环境(覆盖+命令行微调)
helm install myapp ./mychart -n prod \
  -f values-prod.yaml \
  --set replicaCount=5 \
  --set image.tag=2.4.1-hotfix

2.3 Helmfile 编排多 Chart 多环境

helmfile.yaml:

yaml 复制代码
repositories:
  - name: bitnami
    url: https://charts.bitnami.com/bitnami
  - name: internal
    url: https://harbor.internal/chartrepo/platform

helmDefaults:
  timeout: 600
  wait: true
  createNamespace: true

environments:
  dev:
    values:
      - environments/dev.yaml
  staging:
    values:
      - environments/staging.yaml
  prod:
    values:
      - environments/prod.yaml

releases:
  - name: redis
    chart: bitnami/redis
    version: 18.2.1
    namespace: middleware
    values:
      - values/redis-base.yaml
      - values/redis-{{ .Environment.Name }}.yaml
    needs:
      - middleware/ns  # 先确保 namespace 存在

  - name: api-gateway
    chart: internal/api-gateway
    version: 2.1.0
    namespace: {{ .Environment.Values.gatewayNamespace }}
    values:
      - values/api-gateway-base.yaml
      - values/api-gateway-{{ .Environment.Name }}.yaml
    needs:
      - middleware/redis

  - name: myapp
    chart: internal/myapp
    version: 1.0.0
    namespace: {{ .Environment.Values.appNamespace }}
    values:
      - mychart/values.yaml
      - mychart/values-{{ .Environment.Name }}.yaml
    needs:
      - {{ .Environment.Values.gatewayNamespace }}/api-gateway
    set:
      - name: clusterName
        value: {{ .Environment.Values.clusterName }}

environments/prod.yaml:

yaml 复制代码
clusterName: prod-cluster-01
gatewayNamespace: gateway
appNamespace: production

environments/dev.yaml:

yaml 复制代码
clusterName: dev-cluster-01
gatewayNamespace: dev-gateway
appNamespace: dev

使用:

bash 复制代码
# 同步所有 release 到 prod 環境的目标状态
helmfile -e prod sync

# 只更新 myapp
helmfile -e prod -l name=myapp sync

# 查看将要做的变更(dry-run)
helmfile -e prod diff

# 销毁所有 release
helmfile -e prod destroy

2.4 私有仓库:Harbor 作为 Helm Chart 仓库

Harbor 从 2.0 开始原生支持 Helm Chart 仓库(不需要 ChartMuseum)。

bash 复制代码
# 添加 Harbor 作为 Helm 仓库
helm repo add internal https://harbor.internal/chartrepo/platform \
  --username admin --password Harbor12345

# 搜索 Chart
helm search repo internal/

# 推送本地 Chart 到 Harbor
helm package mychart/
helm push myapp-1.0.0.tgz oci://harbor.internal/platform/myapp

# OCI 方式(推荐,K8s 1.30 时代 Helm 3.14 默认支持 OCI)
helm pull oci://harbor.internal/platform/myapp:1.0.0
helm install myapp oci://harbor.internal/platform/myapp:1.0.0 -n prod

2.5 CI/CD 集成:helm diff + helm secrets

helm diff --- 变更预览(上线前必看):

bash 复制代码
# 安装插件
helm plugin install https://github.com/databus23/helm-diff

# 升级前预览差异
helm diff upgrade myapp ./mychart -n prod -f values-prod.yaml

# 只看变更的资源(不看未变的)
helm diff upgrade myapp ./mychart -n prod -f values-prod.yaml --show-only-changed

helm secrets --- 加密敏感 values:

bash 复制代码
# 安装插件
helm plugin install https://github.com/jkroepke/helm-secrets

# 加密 values 中的敏感字段
helm secrets enc values-prod.yaml
# 生成 values-prod.yaml.dec(解密文件),values-prod.yaml 中敏感字段被 sops 加密

# 安装时自动解密
helm secrets install myapp ./mychart -n prod -f values-prod.yaml

# helmfile 集成
# helmfile.yaml 中 values 写法不变,helmfile 自动调用 helm-secrets 解密

三、踩坑与排查

踩坑 1:values 覆盖后 selector 标签不一致,Deployment 滚动更新卡死

现象 : 用 -f values-prod.yaml 覆盖了 podLabels,升级后新 Pod 无法 Ready,旧 Pod 一直保留,最终超时。

bash 复制代码
# 查看事件
kubectl describe deploy myapp -n prod
# 输出:
# Warning  ReplicaSetCreateFailure  deployment controller can't find matching pods

原因 : Deployment 的 selector.matchLabels不可变字段 ,创建后不能改。如果你在 _helpers.tpl 里让 selector 受 podLabels 影响,覆盖后新旧 selector 不一致,Deployment 控制器匹配失败。

解决 : selector 标签永远只用 selectorLabels(不含用户覆盖的 podLabels),podLabels 只加到 template.metadata.labels:

gotemplate 复制代码
spec:
  selector:
    matchLabels:
      {{- include "myapp.selectorLabels" . | nindent 6 }}  # 固定不变
  template:
    metadata:
      labels:
        {{- include "myapp.selectorLabels" . | nindent 8 }}  # 必须包含 selector 全部标签
        {{- with .Values.podLabels }}                          # 用户额外标签
        {{- toYaml . | nindent 8 }}
        {{- end }}

踩坑 2:Chart 升级后旧 Release 残留资源未清理

现象 : helm upgrade 后,旧的 ConfigMap/Secret 还在,新 Pod 读到了旧配置,行为异常。

原因 : Helm 只管理 templates/ 里当前版本存在的资源。如果你在 v2.0.0 的 Chart 中删除了某个 ConfigMap 模板,Helm 升级时不会自动删除它------Helm 不会跟踪"已删除的模板"

解决:

bash 复制代码
# 方法 1:手动清理
kubectl delete configmap myapp-old-config -n prod

# 方法 2:使用 helm-hooks 在升级前清理
# 在 templates/ 里加一个 pre-upgrade hook:
yaml 复制代码
apiVersion: v1
kind: Job
metadata:
  name: {{ include "myapp.fullname" . }}-cleanup
  annotations:
    "helm.sh/hook": pre-upgrade
    "helm.sh/hook-weight": "-5"
    "helm.sh/hook-delete-policy": hook-succeeded
spec:
  template:
    spec:
      containers:
        - name: cleanup
          image: bitnami/kubectl:1.30
          command:
            - kubectl
            - delete
            - configmap
            - myapp-old-config
            - --ignore-not-found
            - -n
            - {{ .Release.Namespace }}
      restartPolicy: Never

蹈坑 3:helm list 显示 RELEASE NOT FOUND,但资源还在集群里

现象 : helm list -n prod 看不到 release,但 kubectl get all -n prod 资源都在。

原因 : Helm 的 release 信息存储在 Secret 里(helm.v2helm.v3 格式)。如果有人手动删除了这些 Secret, Helm 就"失忆"了。

bash 复制代码
# 查看 Helm 存储的 release Secret
kubectl get secrets -n prod -l owner=helm
# 找到被删的 Secret 名称

解决:

bash 复制代码
# 方法 1:用 helm --force 重新安装(会重建 release Secret)
helm install myapp ./mychart -n prod --replace

# 方法 2:从集群资源重建(最安全的做法)
# 先把现有资源导出,再手动构造 release Secret
# 实际生产中建议:永远不要手动删除 Helm 的 release Secret!

蹈坑 4:Helmfile sync 时 needs 依赖顺序不生效

现象 : helmfile sync 先装了 myapp,redis 还没 Ready,myapp 启动失败连不上数据库。

原因 : needs 只保证安装顺序 ,不保证依赖资源已经 Ready。Helmfile 发起 install 后就继续下一个,不等健康检查完成。

解决 : 在 helmDefaults 中设置 wait: true,让每个 release 等到所有资源 Ready 再继续:

yaml 复制代码
helmDefaults:
  wait: true
  timeout: 600

或者在 values 里设置 initContainers 等待依赖:

yaml 复制代码
initContainers:
  - name: wait-for-redis
    image: busybox:1.36
    command: ['sh', '-c', 'until nc -z redis.middleware 6379; do echo waiting; sleep 2; done']

四、最佳实践

Chart 开发规范清单

  1. 命名规范 : 资源名用 _helpers.tplfullname 函数,不超过 63 字符,不含大写和特殊字符
  2. 标签规范 : 所有资源必须携带 myapp.labels helper,selector 必须用 selectorLabels(不可变)
  3. values 规范 : 基线 values.yaml 配置保守(1 replica,小资源),所有可配置项都声明默认值(list 设为 [])
  4. 模板规范 : _helpers.tpl 必须包含 fullname/labels/selectorLabels,大模板拆到 partials/ 目录
  5. CRD 规范 : CRD 放在 crds/ 目录,Helm 安装前自动加载,升级时不更新(CRD 不可变)
  6. Hook 规范 : pre-install/post-install/pre-upgrade/post-upgrade 按需使用,必须设置 hook-delete-policy
  7. 测试规范 : templates/tests/ 下放 Pod 测试,helm test 自动运行

Helm 仓库治理规范框架

治理维度 规范 工具/机制
版本号 严格 semver,禁止 -rc/-hotfix 后缀进仓库 Chart.yaml version 字段 + CI 校验
兼容性 kubeVersion 字段声明最低版本 helm lint --with-subcharts
安全 Chart 内容扫描(镜像 CVE/rbac 权限) trivy scanner / helm charttesting
签名 OCI Chart 签名(cosign) helm push --sign
审批 Chart 上仓库前必须 PR Review + lint pass GitHub CI + helm lint
保留 仓库只保留最近 N 个版本(防止膨胀) Harbor 保留策略 / 自动清理
分类 按 team/project 划分 project Harbor project 权限隔离

Values 分层最佳实践

  1. 基线 values.yaml 只放安全默认值,不放环境特定值
  2. 每个环境一个覆盖文件:values-{env}.yaml,命名一致
  3. 命令行 --set 只用于临时调试,不上生产
  4. 敏感值用 helm-secrets 加密,不入 Git
  5. 覆盖文件也入 Git,但 .dec 解密文件不入库
  6. list 类型 values 在基线设为空 [],覆盖文件完整提供
  7. helm template 本地渲染验证,不直接 helm install

五、小结

Helm 从"安装工具"进化到"包管理基础设施",关键在三件事:规范的 Chart 模板 (helpers/labels/selector 做对)、分层的 values 管理 (基线→覆盖→命令行优先级链搞清)、仓库治理(semver 版本+审批+签名+清理)。再加上 Helmfile 编排多环境、helm diff 做变更预览、helm secrets 加密敏感值,你就有了从 dev 到 prod 的完整部署管线。Helm 和 Kustomize 不是互斥的------第三方 Chart 用 Helm 安,内部应用用 Kustomize 微调,两者共存才是生产常态。

思考题

  1. 如果你的团队有 50 个内部微服务,每个只需要镜像 tag 和 replica 数不同,你会选 Helm 还是 Kustomize?为什么?
  2. Helm 的 OCI 仓库模式(推到 Harbor OCI registry)相比传统 ChartMuseum HTTP 仓库,有什么优势和风险?
  3. 一个 Chart 的 versionappVersion 什么时候应该同时递增?什么时候只递增 version?

延伸阅读

相关推荐
烬羽1 小时前
一个队列,怎么让滑动窗口从 O(nk) 变 O(n)?单调队列彻底搞懂
javascript·数据结构·算法
阳光九叶草LXGZXJ1 小时前
达梦数据库-报错-12-[-7184]:对象定义[XXX]被修改,版本检查失败
linux·运维·服务器·数据库·sql·学习
AI大佬的小弟1 小时前
私有化 Dify 应用开发(3):Docker 核心原理与零基础实战
运维·docker·容器技术·ai 应用开发·私有化 ai 部署·docker 教程·dify 运维
沉迷学习 日益消瘦1 小时前
20-综合实战:微服务部署
微服务·云原生·架构·kubernetes
XH华2 小时前
C++语言第四章:模板初阶
数据结构·c++·算法
纳兰青华2 小时前
拼图大师:从“暴力尝试“到“动态规划“的拆字艺术
java·算法·动态规划
JXCY13S5052 小时前
集群及lvs基础知识
运维·服务器
小此方2 小时前
Re:Linux系统篇(四十九)线程篇 · 二:为什么现代操作系统选择分页式存储?分页如何解决物理内存碎片?分页结构如何演化到页表、页目录、MMU?
linux·运维·驱动开发
星马梦缘2 小时前
算法设计导论 课堂笔记
算法·动态规划·分治·时间复杂度·最优二叉树