一:教程定位

在前面的教程中,我们已经完成了 Kubernetes Manifest 部署、变量参数化、Input Set、多阶段流水线、模板复用、质量扫描和高级部署策略。

第 16 篇开始进入企业中非常常见的 Kubernetes 应用交付方式:Helm 部署

很多团队在项目初期会直接使用 Kubernetes YAML:

deployment.yaml service.yaml configmap.yaml ingress.yaml

随着环境变多、服务变多、配置变复杂,就会遇到这些问题:

dev/test/prod 三套 YAML 大量重复 镜像地址、端口、副本数、资源限制到处修改 ConfigMap、Secret、Service、Ingress 难以统一管理 发布版本和配置变更缺少清晰记录 回滚时不知道应该回滚哪个配置组合 多个微服务无法统一模板规范

Helm 的核心价值是:把 Kubernetes 资源打包成一个可配置、可版本化、可回滚的应用包

本篇会用一个简单的 Node.js Web 服务作为示例,完整演示:

编写 Helm Chart 拆分 values.yaml、values-dev.yaml、values-test.yaml、values-prod.yaml 在本地 lint/template 验证 Chart 在 Harness 中配置 Helm Service 使用 HelmDeploy 步骤发布应用 部署后做健康检查 修改 Chart 值并发布新版本 通过 Helm 回滚旧版本


二:适合人群

本文面向中级用户,适合:

DevOps 工程师 平台工程师 SRE Kubernetes 运维人员 微服务开发负责人 正在把 YAML 部署迁移到 Helm 的团队 准备在 Harness 中标准化 Helm 发布的团队

建议已经具备:

了解 Kubernetes Deployment / Service / Ingress 了解 Docker 镜像构建与推送 了解 Harbor 私有镜像仓库 了解 Harness Pipeline / Stage / Step 了解 Harness Connector / Service / Environment / Infrastructure 了解 Input Set 基础用法 能看懂 YAML 和 Bash


三:学习目标

完成本文后,你应该能够:

理解 Helm Chart 的标准目录结构 理解 Chart.yaml、values.yaml、templates 的作用 编写一个可部署 Web 服务的 Helm Chart 为 dev/test/prod 编写不同 values 文件 使用 helm lint 和 helm template 做发布前检查 理解 Harness-managed Helm 与 Native Helm 的区别 在 Harness 中配置 Native Helm Service 使用 HelmDeploy 步骤部署 Helm Chart 在流水线中传入 chart version、image tag、namespace、release name 部署后检查 helm release、Pod、Service、健康接口 修改 values 后重新发布 使用 Helm Rollback 或 helm rollback 回滚到旧版本 掌握国内网络环境下 Helm Chart 仓库、Harbor OCI、Delegate 访问问题


四:Helm 部署在 Harness 中的两种方式

Harness 中与 Helm 有关的部署大体可以分为两类。

1. Harness-managed Helm

这种方式的核心流程是:

Harness 拉取 Helm Chart ↓ 执行 helm template 渲染 Kubernetes YAML ↓ Harness 使用 kubectl apply 部署 ↓ Harness 管理 Kubernetes 资源版本和回滚

适合:

希望 Harness 编排 Kubernetes Rolling / Canary / Blue-Green 希望 Harness 管理 ConfigMap / Secret 版本 不依赖 Helm hooks 不强依赖 Helm release history 主要把 Helm 当作 YAML 模板引擎

注意:

Harness-managed Helm 不等同于原生 helm install/upgrade。 它更像是用 Helm 渲染模板,再由 Harness/Kubernetes 部署资源。

2. Native Helm

这种方式更接近原生 Helm:

Harness 拉取 Helm Chart ↓ Harness 执行 helm install / helm upgrade ↓ Helm 管理 release history ↓ 失败或手动回滚时使用 helm rollback

适合:

已有成熟 Helm Chart 使用 Helm hooks 使用 subcharts / dependencies 希望保留 Helm 原生 release history 希望用 helm rollback 回滚 从现有 Helm 发布流程迁移到 Harness

本文示例以 Native Helm + HelmDeploy 为主,因为它最符合“在流水线中使用 HelmDeploy 步骤发布,并练习修改 Chart 值和回滚旧版本”的目标。


五:本文最终流水线结构

Pipeline: helm-deploy-demo

Stage 1: CI Build Step 1: npm install Step 2: npm test Step 3: docker build Step 4: docker push Harbor

Stage 2: Helm Chart Check Step 1: helm lint Step 2: helm template Step 3: kubeconform / kubectl dry-run 可选

Stage 3: Helm Deploy Step 1: HelmDeploy Step 2: helm status Step 3: kubectl get pods Step 4: curl /health

Stage 4: Manual Rollback,可选 Step 1: Helm Rollback Step 2: 验证回滚后版本

推荐参数:

app_name = harness-helm-demo release_name = harness-helm-demo namespace = dev/test/prod chart_version = 0.1.0 image_repository = harbor.company.com/devops/harness-helm-demo image_tag = dev-main-a1b2c3d-100 values_file = values-dev.yaml / values-test.yaml / values-prod.yaml


六:示例仓库结构

harness-helm-demo/ ├── app/ │ ├── package.json │ ├── server.js │ └── server.test.js ├── docker/ │ └── Dockerfile ├── charts/ │ └── harness-helm-demo/ │ ├── Chart.yaml │ ├── values.yaml │ ├── values-dev.yaml │ ├── values-test.yaml │ ├── values-prod.yaml │ ├── values.schema.json │ └── templates/ │ ├── _helpers.tpl │ ├── deployment.yaml │ ├── service.yaml │ ├── ingress.yaml │ ├── configmap.yaml │ ├── hpa.yaml │ ├── serviceaccount.yaml │ └── NOTES.txt ├── scripts/ │ ├── helm-lint.sh │ ├── helm-template.sh │ ├── helm-package.sh │ ├── helm-push-oci.sh │ ├── helm-release-status.sh │ ├── helm-rollback.sh │ └── k8s-health-check.sh └── harness/ ├── pipeline-helm-deploy.yaml └── inputsets/ ├── dev.yaml ├── test.yaml └── prod.yaml


七:准备示例应用

app/package.json

{
  "name": "harness-helm-demo",
  "version": "1.0.0",
  "description": "Harness Helm deployment demo",
  "main": "server.js",
  "scripts": {
    "start": "node server.js",
    "test": "jest --runInBand"
  },
  "dependencies": {
    "express": "^4.18.3"
  },
  "devDependencies": {
    "jest": "^29.7.0",
    "supertest": "^6.3.4"
  }
}

app/server.js

const express = require("express");

function createApp() {
  const app = express();

  app.get("/", (req, res) => {
    res.json({
      app: process.env.APP_NAME || "harness-helm-demo",
      env: process.env.APP_ENV || "dev",
      version: process.env.APP_VERSION || "local",
      status: "ok"
    });
  });

  app.get("/health", (req, res) => {
    res.status(200).json({
      status: "UP"
    });
  });

  return app;
}

if (require.main === module) {
  const port = process.env.PORT || 3000;
  const app = createApp();

  app.listen(port, () => {
    console.log(`server started on port ${port}`);
  });
}

module.exports = createApp;

app/server.test.js

const request = require("supertest");
const createApp = require("./server");

describe("harness-helm-demo", () => {
  test("GET /health should return UP", async () => {
    const app = createApp();
    const response = await request(app).get("/health");

    expect(response.status).toBe(200);
    expect(response.body.status).toBe("UP");
  });

  test("GET / should return app info", async () => {
    const app = createApp();
    const response = await request(app).get("/");

    expect(response.status).toBe(200);
    expect(response.body.status).toBe("ok");
  });
});

八:Dockerfile

docker/Dockerfile

FROM harbor.company.com/library/node:20-alpine

WORKDIR /app

COPY app/package*.json ./

RUN npm config set registry https://registry.npmmirror.com \
    && npm install --omit=dev

COPY app/ ./

ENV PORT=3000

EXPOSE 3000

CMD ["node", "server.js"]

国内环境建议:

不要直接使用 docker.io/library/node 先把 node:20-alpine 同步到 Harbor npm 使用 npmmirror 或企业 npm 私服 生产镜像不要安装 devDependencies


九:创建 Helm Chart

可以先用命令生成骨架:

mkdir -p charts
helm create charts/harness-helm-demo

然后按本文内容精简和改造。


十:Chart.yaml

charts/harness-helm-demo/Chart.yaml

apiVersion: v2
name: harness-helm-demo
description: A Helm chart for Harness Kubernetes Helm deployment demo
type: application
version: 0.1.0
appVersion: "1.0.0"
kubeVersion: ">=1.23.0"
maintainers:
  - name: platform-team
    email: platform@example.com
keywords:
  - harness
  - helm
  - kubernetes
  - demo

说明:

version 是 Chart 包版本。 appVersion 是应用版本,只是描述信息,不直接决定镜像 tag。 生产中建议 Chart version 和应用 image tag 分开管理。

推荐规则:

Chart version: 0.1.0、0.2.0、1.0.0

App image tag: dev-main-a1b2c3d-100 test-main-a1b2c3d-101 v1.2.0


十一:values.yaml 默认值

charts/harness-helm-demo/values.yaml

replicaCount: 1

image:
  repository: harbor.company.com/devops/harness-helm-demo
  tag: "latest"
  pullPolicy: IfNotPresent

imagePullSecrets:
  - name: harbor-pull-secret

nameOverride: ""
fullnameOverride: ""

serviceAccount:
  create: true
  name: ""

podAnnotations: {}

podLabels: {}

service:
  type: ClusterIP
  port: 80
  targetPort: 3000

container:
  port: 3000

env:
  APP_NAME: harness-helm-demo
  APP_ENV: dev
  APP_VERSION: latest
  LOG_LEVEL: info

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

readinessProbe:
  enabled: true
  path: /health
  initialDelaySeconds: 5
  periodSeconds: 10
  timeoutSeconds: 2
  failureThreshold: 6

livenessProbe:
  enabled: true
  path: /health
  initialDelaySeconds: 20
  periodSeconds: 20
  timeoutSeconds: 2
  failureThreshold: 3

ingress:
  enabled: false
  className: nginx
  host: harness-helm-demo.local
  path: /
  tls:
    enabled: false
    secretName: ""

autoscaling:
  enabled: false
  minReplicas: 2
  maxReplicas: 5
  targetCPUUtilizationPercentage: 70

nodeSelector: {}

tolerations: []

affinity: {}

注意:

values.yaml 里可以有默认值,但生产不要真的使用 latest。 后续环境 values 文件会覆盖 image.tag、replicaCount、APP_ENV 等字段。


十二:values-dev.yaml

charts/harness-helm-demo/values-dev.yaml

replicaCount: 1

image:
  repository: harbor.company.com/devops/harness-helm-demo
  tag: "dev"
  pullPolicy: IfNotPresent

env:
  APP_ENV: dev
  APP_VERSION: dev
  LOG_LEVEL: debug

resources:
  requests:
    cpu: 50m
    memory: 96Mi
  limits:
    cpu: 300m
    memory: 256Mi

ingress:
  enabled: false

autoscaling:
  enabled: false

十三:values-test.yaml

charts/harness-helm-demo/values-test.yaml

replicaCount: 2

image:
  repository: harbor.company.com/devops/harness-helm-demo
  tag: "test"
  pullPolicy: IfNotPresent

env:
  APP_ENV: test
  APP_VERSION: test
  LOG_LEVEL: info

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

ingress:
  enabled: true
  className: nginx
  host: harness-helm-demo-test.company.com
  path: /
  tls:
    enabled: false

autoscaling:
  enabled: false

十四:values-prod.yaml

charts/harness-helm-demo/values-prod.yaml

replicaCount: 3

image:
  repository: harbor.company.com/devops/harness-helm-demo
  tag: "v1.0.0"
  pullPolicy: IfNotPresent

env:
  APP_ENV: prod
  APP_VERSION: v1.0.0
  LOG_LEVEL: warn

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

ingress:
  enabled: true
  className: nginx
  host: harness-helm-demo.company.com
  path: /
  tls:
    enabled: true
    secretName: harness-helm-demo-tls

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

生产建议:

replicaCount 至少 2 readinessProbe 必须启用 livenessProbe 必须启用 resources 必须设置 image.tag 必须是不可变版本 不要使用 latest


十五:values.schema.json

charts/harness-helm-demo/values.schema.json

{
  "$schema": "https://json-schema.org/schema#",
  "type": "object",
  "required": ["replicaCount", "image", "service", "container"],
  "properties": {
    "replicaCount": {
      "type": "integer",
      "minimum": 1
    },
    "image": {
      "type": "object",
      "required": ["repository", "tag", "pullPolicy"],
      "properties": {
        "repository": {
          "type": "string",
          "minLength": 1
        },
        "tag": {
          "type": "string",
          "minLength": 1
        },
        "pullPolicy": {
          "type": "string",
          "enum": ["Always", "IfNotPresent", "Never"]
        }
      }
    },
    "service": {
      "type": "object",
      "properties": {
        "type": {
          "type": "string"
        },
        "port": {
          "type": "integer"
        },
        "targetPort": {
          "type": "integer"
        }
      }
    }
  }
}

作用:

提前校验 values 格式 防止 replicaCount 写成字符串 防止 image.tag 为空 防止 pullPolicy 写错


十六:_helpers.tpl

charts/harness-helm-demo/templates/_helpers.tpl

{{/*
Expand the name of the chart.
*/}}
{{- define "harness-helm-demo.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" -}}
{{- end -}}

{{/*
Create a default fully qualified app name.
*/}}
{{- define "harness-helm-demo.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 -}}

{{/*
Common labels.
*/}}
{{- define "harness-helm-demo.labels" -}}
helm.sh/chart: {{ .Chart.Name }}-{{ .Chart.Version | replace "+" "_" }}
app.kubernetes.io/name: {{ include "harness-helm-demo.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 "harness-helm-demo.selectorLabels" -}}
app.kubernetes.io/name: {{ include "harness-helm-demo.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end -}}

{{/*
Service account name.
*/}}
{{- define "harness-helm-demo.serviceAccountName" -}}
{{- if .Values.serviceAccount.create -}}
{{- default (include "harness-helm-demo.fullname" .) .Values.serviceAccount.name -}}
{{- else -}}
{{- default "default" .Values.serviceAccount.name -}}
{{- end -}}
{{- end -}}

十七:Deployment 模板

charts/harness-helm-demo/templates/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "harness-helm-demo.fullname" . }}
  labels:
    {{- include "harness-helm-demo.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  revisionHistoryLimit: 5
  selector:
    matchLabels:
      {{- include "harness-helm-demo.selectorLabels" . | nindent 6 }}
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 0
  template:
    metadata:
      labels:
        {{- include "harness-helm-demo.selectorLabels" . | nindent 8 }}
        {{- with .Values.podLabels }}
        {{- toYaml . | nindent 8 }}
        {{- end }}
      annotations:
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
        {{- with .Values.podAnnotations }}
        {{- toYaml . | nindent 8 }}
        {{- end }}
    spec:
      serviceAccountName: {{ include "harness-helm-demo.serviceAccountName" . }}
      {{- with .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      containers:
        - name: {{ include "harness-helm-demo.name" . }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: {{ .Values.container.port }}
              protocol: TCP
          env:
            - name: APP_NAME
              value: {{ .Values.env.APP_NAME | quote }}
            - name: APP_ENV
              value: {{ .Values.env.APP_ENV | quote }}
            - name: APP_VERSION
              value: {{ .Values.env.APP_VERSION | quote }}
            - name: LOG_LEVEL
              value: {{ .Values.env.LOG_LEVEL | quote }}
          {{- if .Values.readinessProbe.enabled }}
          readinessProbe:
            httpGet:
              path: {{ .Values.readinessProbe.path }}
              port: http
            initialDelaySeconds: {{ .Values.readinessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.readinessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.readinessProbe.timeoutSeconds }}
            failureThreshold: {{ .Values.readinessProbe.failureThreshold }}
          {{- end }}
          {{- if .Values.livenessProbe.enabled }}
          livenessProbe:
            httpGet:
              path: {{ .Values.livenessProbe.path }}
              port: http
            initialDelaySeconds: {{ .Values.livenessProbe.initialDelaySeconds }}
            periodSeconds: {{ .Values.livenessProbe.periodSeconds }}
            timeoutSeconds: {{ .Values.livenessProbe.timeoutSeconds }}
            failureThreshold: {{ .Values.livenessProbe.failureThreshold }}
          {{- end }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}

说明:

checksum/config 用于 ConfigMap 变化后触发 Pod 滚动更新。 readinessProbe 控制服务是否接流量。 livenessProbe 控制容器异常时自动重启。 maxUnavailable=0 可以降低滚动升级中断风险。


十八:Service 模板

charts/harness-helm-demo/templates/service.yaml

apiVersion: v1
kind: Service
metadata:
  name: {{ include "harness-helm-demo.fullname" . }}
  labels:
    {{- include "harness-helm-demo.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  selector:
    {{- include "harness-helm-demo.selectorLabels" . | nindent 4 }}
  ports:
    - name: http
      port: {{ .Values.service.port }}
      targetPort: http
      protocol: TCP

十九:ConfigMap 模板

charts/harness-helm-demo/templates/configmap.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "harness-helm-demo.fullname" . }}-config
  labels:
    {{- include "harness-helm-demo.labels" . | nindent 4 }}
data:
  APP_NAME: {{ .Values.env.APP_NAME | quote }}
  APP_ENV: {{ .Values.env.APP_ENV | quote }}
  APP_VERSION: {{ .Values.env.APP_VERSION | quote }}
  LOG_LEVEL: {{ .Values.env.LOG_LEVEL | quote }}

二十:Ingress 模板

charts/harness-helm-demo/templates/ingress.yaml

{{- if .Values.ingress.enabled -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "harness-helm-demo.fullname" . }}
  labels:
    {{- include "harness-helm-demo.labels" . | nindent 4 }}
spec:
  ingressClassName: {{ .Values.ingress.className }}
  {{- if .Values.ingress.tls.enabled }}
  tls:
    - hosts:
        - {{ .Values.ingress.host }}
      secretName: {{ .Values.ingress.tls.secretName }}
  {{- end }}
  rules:
    - host: {{ .Values.ingress.host }}
      http:
        paths:
          - path: {{ .Values.ingress.path }}
            pathType: Prefix
            backend:
              service:
                name: {{ include "harness-helm-demo.fullname" . }}
                port:
                  number: {{ .Values.service.port }}
{{- end }}

二十一:HPA 模板

charts/harness-helm-demo/templates/hpa.yaml

{{- if .Values.autoscaling.enabled -}}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: {{ include "harness-helm-demo.fullname" . }}
  labels:
    {{- include "harness-helm-demo.labels" . | nindent 4 }}
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: {{ include "harness-helm-demo.fullname" . }}
  minReplicas: {{ .Values.autoscaling.minReplicas }}
  maxReplicas: {{ .Values.autoscaling.maxReplicas }}
  metrics:
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
{{- end }}

二十二:ServiceAccount 模板

charts/harness-helm-demo/templates/serviceaccount.yaml

{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ include "harness-helm-demo.serviceAccountName" . }}
  labels:
    {{- include "harness-helm-demo.labels" . | nindent 4 }}
{{- end }}

二十三:NOTES.txt

charts/harness-helm-demo/templates/NOTES.txt

Release: {{ .Release.Name }} Namespace: {{ .Release.Namespace }} Chart: {{ .Chart.Name }}-{{ .Chart.Version }} Image: {{ .Values.image.repository }}:{{ .Values.image.tag }}

To check pods: kubectl get pods -n {{ .Release.Namespace }} -l app.kubernetes.io/instance={{ .Release.Name }}

To port-forward: kubectl port-forward svc/{{ include "harness-helm-demo.fullname" . }} 8080:{{ .Values.service.port }} -n {{ .Release.Namespace }}

Health check: curl http://127.0.0.1:8080{{ .Values.readinessProbe.path }}


二十四:本地验证 Helm Chart

1. helm lint

scripts/helm-lint.sh

#!/usr/bin/env bash
set -euo pipefail

CHART_DIR="${CHART_DIR:-charts/harness-helm-demo}"
VALUES_FILE="${VALUES_FILE:-values-dev.yaml}"

echo "CHART_DIR=${CHART_DIR}"
echo "VALUES_FILE=${VALUES_FILE}"

helm lint "${CHART_DIR}" -f "${CHART_DIR}/${VALUES_FILE}"

echo "helm lint 通过"

运行:

chmod +x scripts/helm-lint.sh

CHART_DIR=charts/harness-helm-demo \
VALUES_FILE=values-dev.yaml \
scripts/helm-lint.sh

2. helm template

scripts/helm-template.sh

#!/usr/bin/env bash
set -euo pipefail

CHART_DIR="${CHART_DIR:-charts/harness-helm-demo}"
VALUES_FILE="${VALUES_FILE:-values-dev.yaml}"
RELEASE_NAME="${RELEASE_NAME:-harness-helm-demo}"
NAMESPACE="${NAMESPACE:-dev}"
IMAGE_TAG="${IMAGE_TAG:-dev}"

echo "渲染 Helm 模板"
echo "CHART_DIR=${CHART_DIR}"
echo "VALUES_FILE=${VALUES_FILE}"
echo "RELEASE_NAME=${RELEASE_NAME}"
echo "NAMESPACE=${NAMESPACE}"
echo "IMAGE_TAG=${IMAGE_TAG}"

helm template "${RELEASE_NAME}" "${CHART_DIR}" \
  --namespace "${NAMESPACE}" \
  -f "${CHART_DIR}/${VALUES_FILE}" \
  --set image.tag="${IMAGE_TAG}" \
  --set env.APP_VERSION="${IMAGE_TAG}" \
  > /tmp/${RELEASE_NAME}-manifest.yaml

echo "渲染结果:/tmp/${RELEASE_NAME}-manifest.yaml"

kubectl apply --dry-run=client -f /tmp/${RELEASE_NAME}-manifest.yaml

echo "helm template 和 kubectl dry-run 通过"

运行:

chmod +x scripts/helm-template.sh

NAMESPACE=dev \
VALUES_FILE=values-dev.yaml \
IMAGE_TAG=dev-main-a1b2c3d-100 \
scripts/helm-template.sh

二十五:打包 Helm Chart

scripts/helm-package.sh

#!/usr/bin/env bash
set -euo pipefail

CHART_DIR="${CHART_DIR:-charts/harness-helm-demo}"
PACKAGE_DIR="${PACKAGE_DIR:-dist/charts}"

mkdir -p "${PACKAGE_DIR}"

helm dependency update "${CHART_DIR}" || true

helm lint "${CHART_DIR}"

helm package "${CHART_DIR}" --destination "${PACKAGE_DIR}"

ls -lh "${PACKAGE_DIR}"

echo "Chart 打包完成"

运行:

chmod +x scripts/helm-package.sh
scripts/helm-package.sh

输出示例:

dist/charts/harness-helm-demo-0.1.0.tgz


二十六:Chart 仓库选择

Helm Chart 可以存在多种地方:

Git 仓库 HTTP Helm Repo Harbor OCI Registry Nexus Helm Repository Artifactory 云厂商 Artifact Registry

国内企业推荐:

开发初期: Chart 放在 Git 仓库 charts/ 目录

团队标准化: Chart 打包后推送到 Harbor OCI

企业平台化: Harbor OCI / Nexus / Artifactory 统一管理 Chart

本文建议:

源码仓库中保留 Chart 源文件 发布时打包成 tgz 推送到 Harbor OCI Harness 从 Git 或 OCI 拉取 Chart


二十七:推送 Helm Chart 到 Harbor OCI

Harbor 2.x 支持 OCI Artifact,可以存储 Helm Chart。

登录:

helm registry login harbor.company.com \
  --username robot$helm \
  --password "${HARBOR_ROBOT_TOKEN}"

推送:

scripts/helm-push-oci.sh

#!/usr/bin/env bash
set -euo pipefail

CHART_PACKAGE="${CHART_PACKAGE:-dist/charts/harness-helm-demo-0.1.0.tgz}"
OCI_REPO="${OCI_REPO:-oci://harbor.company.com/helm-charts}"

echo "CHART_PACKAGE=${CHART_PACKAGE}"
echo "OCI_REPO=${OCI_REPO}"

helm push "${CHART_PACKAGE}" "${OCI_REPO}"

echo "Chart 已推送到 ${OCI_REPO}"

运行:

HARBOR_ROBOT_TOKEN=xxxx \
helm registry login harbor.company.com \
  --username 'robot$helm' \
  --password-stdin

CHART_PACKAGE=dist/charts/harness-helm-demo-0.1.0.tgz \
OCI_REPO=oci://harbor.company.com/helm-charts \
scripts/helm-push-oci.sh

注意:

Harbor 项目需要允许 OCI artifact。 Robot Account 需要 push/pull 权限。 Harness Delegate 需要能访问 Harbor。 如果 Harbor 使用自签证书,Delegate 容器或节点要信任企业 CA。


二十八:Kubernetes RBAC 准备

创建 namespace:

kubectl create namespace dev
kubectl create namespace test
kubectl create namespace prod

创建 Harbor 镜像拉取 Secret:

kubectl create secret docker-registry harbor-pull-secret \
  --docker-server=harbor.company.com \
  --docker-username='robot$app' \
  --docker-password='YOUR_TOKEN' \
  -n dev

kubectl create secret docker-registry harbor-pull-secret \
  --docker-server=harbor.company.com \
  --docker-username='robot$app' \
  --docker-password='YOUR_TOKEN' \
  -n test

kubectl create secret docker-registry harbor-pull-secret \
  --docker-server=harbor.company.com \
  --docker-username='robot$app' \
  --docker-password='YOUR_TOKEN' \
  -n prod

如果希望最小权限,可以为 Harness Delegate 使用 namespace 级 ServiceAccount。

k8s/harness-deployer-rbac.yaml

apiVersion: v1
kind: ServiceAccount
metadata:
  name: harness-deployer
  namespace: dev
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: harness-deployer
  namespace: dev
rules:
  - apiGroups: ["", "apps", "networking.k8s.io", "autoscaling", "batch"]
    resources:
      - pods
      - pods/log
      - services
      - endpoints
      - configmaps
      - secrets
      - deployments
      - replicasets
      - ingresses
      - horizontalpodautoscalers
      - jobs
    verbs:
      - get
      - list
      - watch
      - create
      - update
      - patch
      - delete
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: harness-deployer
  namespace: dev
subjects:
  - kind: ServiceAccount
    name: harness-deployer
    namespace: dev
roleRef:
  kind: Role
  name: harness-deployer
  apiGroup: rbac.authorization.k8s.io

test/prod 可以复制后修改 namespace。


二十九:Harness 中需要准备的资源

1. Git Connector

用于拉取:

应用代码 Dockerfile Helm Chart 源文件 values 文件

国内建议:

GitLab Gitee 企业自建 Git

2. Docker Registry Connector

用于推送镜像:

harbor.company.com/devops/harness-helm-demo

3. OCI Helm Registry Connector 或 HTTP Helm Connector

用于拉取 Chart:

oci://harbor.company.com/helm-charts

或者:

https://nexus.company.com/repository/helm-hosted/

4. Kubernetes Connector

用于部署到目标集群:

dev cluster test cluster prod cluster

5. Secret

harbor_robot_token helm_robot_token wecom_webhook_url


三十:Harness Native Helm Service 配置思路

在 Harness 中创建 Service:

Service Name: harness-helm-demo

Deployment Type: Native Helm

Manifest 配置:

Manifest Type: Helm Chart

Chart Store: Git / OCI Helm / HTTP Helm

Chart Name: harness-helm-demo

Chart Version: <+input> 或 0.1.0

Helm Version: V3

Values YAML: values.yaml values-<+env.name>.yaml

推荐 values 文件路径:

values.yaml values-<+env.name>.yaml

这样:

env.name = dev → values-dev.yaml env.name = test → values-test.yaml env.name = prod → values-prod.yaml

也可以使用 Input Set:

values_file = values-dev.yaml values_file = values-test.yaml values_file = values-prod.yaml


三十一:Harness Service YAML 参考

下面是参考结构,实际字段以 Harness UI 导出的 YAML 为准。

service:
  name: harness-helm-demo
  identifier: harness_helm_demo
  orgIdentifier: devops_lab
  projectIdentifier: harness_demo
  serviceDefinition:
    type: NativeHelm
    spec:
      manifests:
        - manifest:
            identifier: helm_chart
            type: HelmChart
            spec:
              store:
                type: Github
                spec:
                  connectorRef: gitlab_company
                  gitFetchType: Branch
                  branch: main
                  folderPath: charts/harness-helm-demo
              chartName: harness-helm-demo
              chartVersion: <+input>
              helmVersion: V3
              valuesPaths:
                - values.yaml
                - values-<+env.name>.yaml
              fetchHelmChartMetadata: true

如果使用 Harbor OCI:

service:
  name: harness-helm-demo
  identifier: harness_helm_demo
  orgIdentifier: devops_lab
  projectIdentifier: harness_demo
  serviceDefinition:
    type: NativeHelm
    spec:
      manifests:
        - manifest:
            identifier: helm_chart
            type: HelmChart
            spec:
              store:
                type: OciHelmChart
                spec:
                  connectorRef: harbor_oci_helm
                  basePath: helm-charts
              chartName: harness-helm-demo
              chartVersion: <+input>
              helmVersion: V3
              valuesPaths:
                - values.yaml
                - values-<+env.name>.yaml
              fetchHelmChartMetadata: true

注意:

不同 Harness 版本对 OCI Helm 字段名可能略有差异。 建议先在 UI 中配置成功,再切换 YAML 查看真实字段。


三十二:Pipeline 变量设计

variables:
  - name: app_name
    type: String
    value: harness-helm-demo

  - name: deploy_env
    type: String
    value: <+input>

  - name: namespace
    type: String
    value: <+input>

  - name: release_name
    type: String
    value: <+input>

  - name: chart_version
    type: String
    value: <+input>

  - name: image_repository
    type: String
    value: harbor.company.com/devops/harness-helm-demo

  - name: image_tag
    type: String
    value: <+input>

  - name: values_file
    type: String
    value: <+input>

  - name: rollback_revision
    type: String
    value: <+input>

三十三:dev Input Set

harness/inputsets/dev.yaml

inputSet:
  name: helm-dev
  identifier: helm_dev
  orgIdentifier: devops_lab
  projectIdentifier: harness_demo
  pipeline:
    identifier: helm_deploy_demo
    variables:
      - name: deploy_env
        type: String
        value: dev
      - name: namespace
        type: String
        value: dev
      - name: release_name
        type: String
        value: harness-helm-demo-dev
      - name: chart_version
        type: String
        value: 0.1.0
      - name: image_tag
        type: String
        value: <+input>
      - name: values_file
        type: String
        value: values-dev.yaml
      - name: rollback_revision
        type: String
        value: "0"

三十四:test Input Set

inputSet:
  name: helm-test
  identifier: helm_test
  orgIdentifier: devops_lab
  projectIdentifier: harness_demo
  pipeline:
    identifier: helm_deploy_demo
    variables:
      - name: deploy_env
        type: String
        value: test
      - name: namespace
        type: String
        value: test
      - name: release_name
        type: String
        value: harness-helm-demo-test
      - name: chart_version
        type: String
        value: 0.1.0
      - name: image_tag
        type: String
        value: <+input>
      - name: values_file
        type: String
        value: values-test.yaml
      - name: rollback_revision
        type: String
        value: "0"

三十五:prod Input Set

inputSet:
  name: helm-prod
  identifier: helm_prod
  orgIdentifier: devops_lab
  projectIdentifier: harness_demo
  pipeline:
    identifier: helm_deploy_demo
    variables:
      - name: deploy_env
        type: String
        value: prod
      - name: namespace
        type: String
        value: prod
      - name: release_name
        type: String
        value: harness-helm-demo-prod
      - name: chart_version
        type: String
        value: <+input>
      - name: image_tag
        type: String
        value: <+input>
      - name: values_file
        type: String
        value: values-prod.yaml
      - name: rollback_revision
        type: String
        value: "0"

生产规则:

chart_version 必须明确输入 image_tag 必须明确输入 禁止 latest 必须审批 必须保留 helm history


三十六:HelmDeploy Pipeline YAML 参考

下面是参考结构。Harness YAML 字段会随版本和部署类型略有差异,建议先使用 Visual Editor 创建成功,再切换 YAML 保存为标准模板。

pipeline:
  name: helm-deploy-demo
  identifier: helm_deploy_demo
  orgIdentifier: devops_lab
  projectIdentifier: harness_demo
  tags:
    tutorial: harness-16
    type: helm
  variables:
    - name: app_name
      type: String
      value: harness-helm-demo
    - name: deploy_env
      type: String
      value: <+input>
    - name: namespace
      type: String
      value: <+input>
    - name: release_name
      type: String
      value: <+input>
    - name: chart_version
      type: String
      value: <+input>
    - name: image_repository
      type: String
      value: harbor.company.com/devops/harness-helm-demo
    - name: image_tag
      type: String
      value: <+input>
    - name: values_file
      type: String
      value: <+input>
    - name: rollback_revision
      type: String
      value: <+input>

  stages:
    - stage:
        name: CI Build
        identifier: ci_build
        type: CI
        spec:
          cloneCodebase: true
          infrastructure:
            type: KubernetesDirect
            spec:
              connectorRef: k8s_dev_cluster
              namespace: harness-ci
              automountServiceAccountToken: true
              os: Linux
          execution:
            steps:
              - step:
                  name: Unit Test
                  identifier: unit_test
                  type: Run
                  spec:
                    connectorRef: harbor_company
                    image: harbor.company.com/library/node:20-alpine
                    shell: Sh
                    command: |
                      set -e
                      cd app
                      npm config set registry https://registry.npmmirror.com
                      npm install
                      npm test

              - step:
                  name: Build And Push Image
                  identifier: build_and_push_image
                  type: BuildAndPushDockerRegistry
                  spec:
                    connectorRef: harbor_company
                    repo: harbor.company.com/devops/harness-helm-demo
                    dockerfile: docker/Dockerfile
                    context: .
                    tags:
                      - <+pipeline.variables.image_tag>

    - stage:
        name: Helm Chart Check
        identifier: helm_chart_check
        type: CI
        spec:
          cloneCodebase: true
          infrastructure:
            type: KubernetesDirect
            spec:
              connectorRef: k8s_dev_cluster
              namespace: harness-ci
              automountServiceAccountToken: true
              os: Linux
          execution:
            steps:
              - step:
                  name: Helm Lint
                  identifier: helm_lint
                  type: Run
                  spec:
                    connectorRef: harbor_company
                    image: harbor.company.com/library/helm-kubectl:3.15
                    shell: Sh
                    envVariables:
                      CHART_DIR: charts/harness-helm-demo
                      VALUES_FILE: <+pipeline.variables.values_file>
                    command: |
                      set -e
                      chmod +x scripts/helm-lint.sh
                      scripts/helm-lint.sh

              - step:
                  name: Helm Template Dry Run
                  identifier: helm_template_dry_run
                  type: Run
                  spec:
                    connectorRef: harbor_company
                    image: harbor.company.com/library/helm-kubectl:3.15
                    shell: Sh
                    envVariables:
                      CHART_DIR: charts/harness-helm-demo
                      VALUES_FILE: <+pipeline.variables.values_file>
                      RELEASE_NAME: <+pipeline.variables.release_name>
                      NAMESPACE: <+pipeline.variables.namespace>
                      IMAGE_TAG: <+pipeline.variables.image_tag>
                    command: |
                      set -e
                      chmod +x scripts/helm-template.sh
                      scripts/helm-template.sh

    - stage:
        name: Helm Deploy
        identifier: helm_deploy
        type: Deployment
        spec:
          deploymentType: NativeHelm
          service:
            serviceRef: harness_helm_demo
            serviceInputs:
              serviceDefinition:
                type: NativeHelm
                spec:
                  manifests:
                    - manifest:
                        identifier: helm_chart
                        type: HelmChart
                        spec:
                          chartVersion: <+pipeline.variables.chart_version>
          environment:
            environmentRef: <+pipeline.variables.deploy_env>
            deployToAll: false
            infrastructureDefinitions:
              - identifier: <+pipeline.variables.deploy_env>_helm
          execution:
            steps:
              - step:
                  name: Helm Deploy
                  identifier: helm_deploy_step
                  type: HelmDeploy
                  timeout: 10m
                  spec:
                    skipDryRun: false
                    commandFlags:
                      - commandType: Upgrade
                        flag: "--atomic --wait --timeout 5m --set image.tag=<+pipeline.variables.image_tag> --set env.APP_VERSION=<+pipeline.variables.image_tag>"

              - step:
                  name: Helm Release Status
                  identifier: helm_release_status
                  type: ShellScript
                  timeout: 3m
                  spec:
                    shell: Bash
                    onDelegate: true
                    source:
                      type: Inline
                      spec:
                        script: |
                          set -e
                          export KUBECONFIG=${HARNESS_KUBE_CONFIG_PATH}
                          helm status <+pipeline.variables.release_name> -n <+pipeline.variables.namespace>
                          helm history <+pipeline.variables.release_name> -n <+pipeline.variables.namespace>
                          kubectl get pods -n <+pipeline.variables.namespace> -l app.kubernetes.io/instance=<+pipeline.variables.release_name> -o wide

              - step:
                  name: Health Check
                  identifier: health_check
                  type: ShellScript
                  timeout: 5m
                  spec:
                    shell: Bash
                    onDelegate: true
                    source:
                      type: Inline
                      spec:
                        script: |
                          set -e
                          export KUBECONFIG=${HARNESS_KUBE_CONFIG_PATH}

                          RELEASE_NAME="<+pipeline.variables.release_name>"
                          NAMESPACE="<+pipeline.variables.namespace>"

                          SERVICE_NAME=$(kubectl get svc -n ${NAMESPACE} -l app.kubernetes.io/instance=${RELEASE_NAME} -o jsonpath='{.items[0].metadata.name}')

                          kubectl rollout status deployment -n ${NAMESPACE} -l app.kubernetes.io/instance=${RELEASE_NAME} --timeout=180s

                          kubectl port-forward svc/${SERVICE_NAME} 18080:80 -n ${NAMESPACE} >/tmp/${SERVICE_NAME}-pf.log 2>&1 &
                          PF_PID=$!

                          sleep 5

                          curl -f http://127.0.0.1:18080/health

                          kill ${PF_PID}

                          echo "Helm 部署健康检查通过"
          rollbackSteps:
            - step:
                name: Helm Rollback
                identifier: helm_rollback
                type: HelmRollback
                timeout: 10m
                spec: {}
        failureStrategies:
          - onFailure:
              errors:
                - AllErrors
              action:
                type: StageRollback

注意:

HelmDeploy、HelmRollback 的 YAML 字段以当前 Harness UI 导出为准。 如果你的 Harness 版本不支持 commandFlags 写法,可以在 Helm Service Manifest 的高级配置中添加 Helm Command Flags。 Native Helm 的回滚依赖 Helm release history。


三十七:为什么建议加 --atomic --wait --timeout

--wait

等待 Pod、PVC、Service、Ingress 等资源达到 Ready 状态。

--timeout 5m

限制等待时间,避免流水线无限卡住。

--atomic

升级失败时自动回滚到上一个成功版本。

建议:

dev/test 可以使用 --wait --timeout pre/prod 建议使用 --atomic --wait --timeout


三十八:部署后检查脚本

scripts/helm-release-status.sh

#!/usr/bin/env bash
set -euo pipefail

RELEASE_NAME="${RELEASE_NAME:?RELEASE_NAME is required}"
NAMESPACE="${NAMESPACE:?NAMESPACE is required}"

echo "===== helm status ====="
helm status "${RELEASE_NAME}" -n "${NAMESPACE}"

echo "===== helm history ====="
helm history "${RELEASE_NAME}" -n "${NAMESPACE}"

echo "===== helm get values ====="
helm get values "${RELEASE_NAME}" -n "${NAMESPACE}"

echo "===== kubectl resources ====="
kubectl get all -n "${NAMESPACE}" -l app.kubernetes.io/instance="${RELEASE_NAME}" -o wide

三十九:健康检查脚本

scripts/k8s-health-check.sh

#!/usr/bin/env bash
set -euo pipefail

RELEASE_NAME="${RELEASE_NAME:?RELEASE_NAME is required}"
NAMESPACE="${NAMESPACE:?NAMESPACE is required}"
HEALTH_PATH="${HEALTH_PATH:-/health}"
LOCAL_PORT="${LOCAL_PORT:-18080}"

echo "RELEASE_NAME=${RELEASE_NAME}"
echo "NAMESPACE=${NAMESPACE}"
echo "HEALTH_PATH=${HEALTH_PATH}"

kubectl rollout status deployment \
  -n "${NAMESPACE}" \
  -l app.kubernetes.io/instance="${RELEASE_NAME}" \
  --timeout=180s

SERVICE_NAME=$(kubectl get svc -n "${NAMESPACE}" \
  -l app.kubernetes.io/instance="${RELEASE_NAME}" \
  -o jsonpath='{.items[0].metadata.name}')

echo "SERVICE_NAME=${SERVICE_NAME}"

kubectl get endpoints "${SERVICE_NAME}" -n "${NAMESPACE}"

kubectl port-forward svc/${SERVICE_NAME} ${LOCAL_PORT}:80 -n "${NAMESPACE}" >/tmp/${SERVICE_NAME}-pf.log 2>&1 &
PF_PID=$!

sleep 5

curl -f "http://127.0.0.1:${LOCAL_PORT}${HEALTH_PATH}"

kill ${PF_PID}

echo "健康检查通过"

四十:手动回滚脚本

scripts/helm-rollback.sh

#!/usr/bin/env bash
set -euo pipefail

RELEASE_NAME="${RELEASE_NAME:?RELEASE_NAME is required}"
NAMESPACE="${NAMESPACE:?NAMESPACE is required}"
REVISION="${REVISION:-0}"
TIMEOUT="${TIMEOUT:-5m}"

echo "RELEASE_NAME=${RELEASE_NAME}"
echo "NAMESPACE=${NAMESPACE}"
echo "REVISION=${REVISION}"

echo "===== 当前历史 ====="
helm history "${RELEASE_NAME}" -n "${NAMESPACE}"

if [ "${REVISION}" = "0" ]; then
  echo "回滚到上一个 revision"
  helm rollback "${RELEASE_NAME}" -n "${NAMESPACE}" --wait --timeout "${TIMEOUT}"
else
  echo "回滚到指定 revision:${REVISION}"
  helm rollback "${RELEASE_NAME}" "${REVISION}" -n "${NAMESPACE}" --wait --timeout "${TIMEOUT}"
fi

echo "===== 回滚后状态 ====="
helm status "${RELEASE_NAME}" -n "${NAMESPACE}"
helm history "${RELEASE_NAME}" -n "${NAMESPACE}"

kubectl get pods -n "${NAMESPACE}" -l app.kubernetes.io/instance="${RELEASE_NAME}" -o wide

echo "Helm 回滚完成"

运行示例:

RELEASE_NAME=harness-helm-demo-dev \
NAMESPACE=dev \
REVISION=1 \
scripts/helm-rollback.sh

四十一:修改 values 并重新发布

比如把 dev 副本数从 1 改成 2:

values-dev.yaml

replicaCount: 2

然后重新运行 Pipeline。

验证:

helm history harness-helm-demo-dev -n dev

helm get values harness-helm-demo-dev -n dev

kubectl get deploy -n dev -l app.kubernetes.io/instance=harness-helm-demo-dev

kubectl get pods -n dev -l app.kubernetes.io/instance=harness-helm-demo-dev

你应该看到:

Helm revision 增加 Deployment replicas 变为 2 Pod 数量变为 2


四十二:回滚旧版本验证

查看历史:

helm history harness-helm-demo-dev -n dev

示例输出:

REVISION UPDATED STATUS CHART APP VERSION 1 2026-07-08 10:00:00 superseded harness-helm-demo-0.1.0 1.0.0 2 2026-07-08 10:20:00 deployed harness-helm-demo-0.1.0 1.0.0

回滚到 revision 1:

helm rollback harness-helm-demo-dev 1 -n dev --wait --timeout 5m

验证:

helm history harness-helm-demo-dev -n dev
helm get values harness-helm-demo-dev -n dev
kubectl get deploy -n dev -l app.kubernetes.io/instance=harness-helm-demo-dev

预期:

replicaCount 回到旧版本 Helm revision 继续增加 当前 deployed revision 是回滚后生成的新 revision


四十三:生产 Helm 发布规范

生产环境建议:

Chart version 必须固定 image tag 必须固定 禁止 latest 必须开启 --atomic 必须开启 --wait 必须设置 --timeout 必须保留 helm history 必须审批 必须部署后健康检查 必须有回滚方案

生产发布输入:

chart_version = 0.1.5 image_tag = v1.2.0 release_name = harness-helm-demo-prod namespace = prod values_file = values-prod.yaml

不要这样:

chart_version = latest image_tag = latest namespace = default release_name = demo


四十四:Helm 与 Secret 管理

不要把敏感信息直接写进 values.yaml:

database:
  password: "123456"

推荐:

数据库密码放 Kubernetes Secret Secret 由外部系统创建 Helm Chart 只引用 Secret name

values:

database:
  existingSecret: harness-helm-demo-db
  usernameKey: username
  passwordKey: password

模板:

env:
  - name: DB_USERNAME
    valueFrom:
      secretKeyRef:
        name: {{ .Values.database.existingSecret }}
        key: {{ .Values.database.usernameKey }}
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: {{ .Values.database.existingSecret }}
        key: {{ .Values.database.passwordKey }}

企业建议:

Secret 由 External Secrets / Sealed Secrets / Vault / 云 KMS 管理 Helm Chart 不保存明文密钥 Harness Secret 只保存访问仓库、镜像仓库、通知机器人的凭证


四十五:国内网络环境注意事项

1. Chart 仓库访问

常见问题:

Delegate 无法访问 GitLab / Harbor / Nexus 自签证书不被信任 HTTP Helm Repo 需要认证 OCI Helm 登录失败

解决:

Delegate 所在网络必须能访问 Chart 仓库 给 Delegate 配置企业 CA Harbor Robot Account 使用最小权限 不要让 Harness SaaS 直接访问内网仓库,应通过 Delegate

2. Helm 依赖更新

如果 Chart 有 dependencies:

dependencies:
  - name: redis
    version: 18.0.0
    repository: https://charts.bitnami.com/bitnami

国内生产不建议直接访问公网:

把依赖 Chart 同步到企业内部 Helm 仓库 或把 tgz 放入 charts/ 目录 或使用 Harbor/Nexus 做统一代理缓存

3. 基础镜像

不要写:

docker.io/library/node:20-alpine

推荐:

harbor.company.com/library/node:20-alpine

4. values 文件权限

生产 values 文件应严格控制:

只能平台团队 / SRE / 技术负责人修改 必须走 Merge Request 修改 replicas、resources、ingress、autoscaling 必须审核


四十六:常见问题排查

1. HelmDeploy 失败:找不到 Chart

可能原因:

chartName 写错 chartVersion 不存在 OCI basePath 错误 Git folderPath 错误 Delegate 无法访问 Chart 仓库 Harbor 权限不足

排查:

helm pull oci://harbor.company.com/helm-charts/harness-helm-demo --version 0.1.0

helm show chart oci://harbor.company.com/helm-charts/harness-helm-demo --version 0.1.0

2. values 文件没有生效

可能原因:

values 文件路径写错 values 文件不在 Chart 内 多个 values 文件覆盖顺序不清楚 Harness 表达式没有解析出正确文件名

排查:

helm template harness-helm-demo charts/harness-helm-demo \
  -f charts/harness-helm-demo/values.yaml \
  -f charts/harness-helm-demo/values-dev.yaml

注意:

多个 values 文件后面的优先级更高。

3. 镜像拉取失败

可能原因:

image.repository 或 image.tag 错误 Harbor Secret 不存在 imagePullSecrets namespace 不一致 节点无法访问 Harbor Harbor 自签证书不被节点信任

排查:

kubectl describe pod POD_NAME -n dev

kubectl get secret harbor-pull-secret -n dev

kubectl get events -n dev --sort-by=.metadata.creationTimestamp

4. Helm release 状态 FAILED

排查:

helm status RELEASE_NAME -n NAMESPACE

helm history RELEASE_NAME -n NAMESPACE

helm get manifest RELEASE_NAME -n NAMESPACE

kubectl get events -n NAMESPACE --sort-by=.metadata.creationTimestamp

5. 回滚失败

可能原因:

release history 不存在 目标 revision 不存在 资源被手工修改导致冲突 Hook Job 卡住 RBAC 权限不足

排查:

helm history RELEASE_NAME -n NAMESPACE

helm rollback RELEASE_NAME REVISION -n NAMESPACE --debug --wait --timeout 10m

四十七:企业最佳实践

1. Chart 与镜像分开版本

Chart version 管 Kubernetes 模板变化 Image tag 管应用代码变化

例如:

Chart version = 0.3.0 Image tag = v1.8.2

2. dev/test/prod 使用不同 values 文件

推荐:

values.yaml values-dev.yaml values-test.yaml values-prod.yaml

3. 生产必须固定版本

生产不要使用:

latest main dev

生产使用:

v1.2.0 release-20260708-001

4. 发布前必须 helm lint/template

helm lint helm template kubectl dry-run kubeconform 可选

5. Helm 回滚前要看 history

helm history RELEASE_NAME -n NAMESPACE

不要盲目回滚。

6. Harness YAML 不要手写到底

推荐:

先用 Harness Visual Editor 创建 Native Helm Pipeline 跑通后切换 YAML 再纳入 Git 管理

7. 不要手工改集群资源

如果资源由 Helm 管理,不建议直接:

kubectl edit deployment
kubectl patch service

否则会造成:

Helm 状态和实际资源漂移 下次 upgrade 覆盖手工修改 回滚结果不可预测


四十八:练习 1:创建 Helm Chart 并本地渲染

目标:

创建 charts/harness-helm-demo,并能用 values-dev.yaml 渲染出 Kubernetes YAML。

步骤:

  1. 创建 Chart.yaml
  2. 创建 values.yaml
  3. 创建 deployment/service/configmap 模板
  4. 执行 helm lint
  5. 执行 helm template

验收:

helm lint 通过 helm template 输出 Deployment、Service、ConfigMap kubectl dry-run 通过


四十九:练习 2:Harness HelmDeploy 发布 dev 环境

目标:

使用 Harness Native Helm + HelmDeploy 发布到 dev namespace。

步骤:

  1. 创建 Native Helm Service
  2. 添加 Helm Chart Manifest
  3. 配置 dev Environment 和 Infrastructure
  4. 创建 dev Input Set
  5. 运行 Pipeline

验收:

helm list -n dev 能看到 release Pod Running /health 返回 200


五十:练习 3:修改 Chart 值并升级

目标:

把 dev replicaCount 从 1 改成 2,重新发布。

步骤:

  1. 修改 values-dev.yaml
  2. 提交代码
  3. 运行 Pipeline
  4. 查看 helm history
  5. 查看 Deployment replicas

验收:

Helm revision 增加 Pod 数变为 2 服务健康检查通过


五十一:练习 4:回滚旧版本

目标:

将 release 回滚到上一个 revision。

步骤:

  1. helm history 查看 revision
  2. 使用 Helm Rollback Step 或 helm rollback
  3. 查看 helm status
  4. 查看 Pod 和应用版本

验收:

回滚后应用恢复到旧配置 helm history 出现新 revision 健康检查通过


五十二:练习 5:发布生产环境

目标:

使用 prod Input Set 发布固定 Chart version 和 image tag。

要求:

chart_version = 0.1.0 image_tag = v1.0.0 namespace = prod release_name = harness-helm-demo-prod values_file = values-prod.yaml

验收:

生产发布前有审批 禁止 latest HelmDeploy 使用 --atomic --wait --timeout 发布后健康检查通过 helm history 可追溯


五十三:验收标准

完成本文后,应达到:

已理解 Harness-managed Helm 与 Native Helm 的区别 已能编写标准 Helm Chart 已能编写 values-dev/test/prod 已能使用 helm lint 检查 Chart 已能使用 helm template 渲染并 dry-run 已能打包 Chart 已能推送 Chart 到 Harbor OCI 已能在 Harness 中创建 Native Helm Service 已能使用 HelmDeploy 发布应用 已能部署后查看 helm status 和 helm history 已能修改 values 并升级 release 已能使用 Helm Rollback 回滚旧版本 已能处理国内环境下 Harbor、Chart 仓库、自签证书、Delegate 访问问题


五十四:本篇总结

本篇完成了 Kubernetes + Helm 在 Harness 中的完整部署示例。

你需要重点记住:

Helm Chart 是 Kubernetes 应用的标准化包 values.yaml 管默认值,values-dev/test/prod 管环境差异 Chart version 和 image tag 要分开管理 Harness-managed Helm 使用 helm template + kubectl apply Native Helm 使用 helm install/upgrade 和 helm rollback 如果依赖 Helm hooks/subcharts/release history,优先 Native Helm 发布前必须 helm lint 和 helm template 生产环境必须固定 chart version 和 image tag 生产不要使用 latest 回滚前先看 helm history 国内环境优先使用 Harbor OCI / Nexus / 企业 Git 作为 Chart 仓库 Harness Delegate 必须能访问 Git、Harbor、Kubernetes API

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐