项目背景

在当前的云原生技术栈中,Kubernetes 已成为容器编排的事实标准。然而,随着微服务架构的普及,单纯的 YAML 文件管理逐渐暴露出其局限性。我们面临的核心痛点是 “重复性劳动” 与 “维护的碎片化” 。

项目状况

**后端服务 A、B、C 的 Deployment 90% 都是重复代码。**这种碎片化的管理方式,让运维团队在面对架构升级时苦不堪言。

在微服务架构下,几十个 Java 服务的 Kubernetes 部署配置往往大同小异。若为每个服务单独维护 Chart,或通过复制粘贴管理 YAML,会导致严重的维护噩梦——一旦需要升级基础组件(如注入 Sidecar、调整 JVM 参数),便需修改数十份文件。

目标:通用化 Helm Chart

通过构建一个“通用超级 Chart”,我们将部署逻辑抽象化。开发者只需通过 values.yaml 声明差异(镜像、端口、环境变量),即可利用 Helm 模板引擎自动生成标准的 Kubernetes 清单,实现“一次定义,多处复用”。
在这里插入图片描述

核心思路

要实现上述目标,核心思路在于抽象逻辑化

  1. 抽象公共逻辑:将 Java 服务共有的配置(如通用的探针、资源请求、标签选择器、卷挂载)提取到 _helpers.tpl 或独立的模板片段中。

  2. 参数化差异点:将镜像、端口、环境变量等差异点暴露在 values.yaml 中,作为模板的输入参数。

  3. 逻辑控制:利用 Helm 模板的控制结构(if/elserangewith),根据 values.yaml 中的开关动态决定是否渲染 Ingress、Sidecar 或特定配置。

  4. 模块化设计:构建一个顶层 Chart,能够通过 dependencies 引入子 Chart,或者通过循环动态生成多个服务实例。

通用 Chart 架构设计

目录结构

# 创建通用 Chart
helm create universal-app

# 优化后的目录结构
universal-app/
├── Chart.yaml                 # Chart 元数据
├── values.yaml                # 默认配置
├── values-schema.json         # 配置校验 (可选)
├── templates/
│   ├── _helpers.tpl           # 🔧 公共模板函数
│   ├── deployment.yaml        # 📦 Deployment 模板
│   ├── service.yaml           # 🔌 Service 模板
│   ├── ingress.yaml           # 🌐 Ingress 模板 (可选)
│   ├── configmap.yaml         # 📄 ConfigMap 模板 (可选)
│   ├── secret.yaml            # 🔐 Secret 模板 (可选)
│   ├── hpa.yaml               # 📈 HPA 模板 (可选)
│   ├── pdb.yaml               # 🛡️ PodDisruptionBudget (可选)
│   ├── serviceaccount.yaml    # 👤 ServiceAccount (可选)
│   └── NOTES.txt              # 📝 安装说明
├── examples/                   # 示例配置
│   ├── values-service-a.yaml
│   ├── values-service-b.yaml
│   └── values-service-c.yaml
└── README.md

Chart.yaml

# Chart.yaml
apiVersion: v2
name: universal-app
description: 企业级通用微服务部署 Chart
type: application
version: 1.0.0
appVersion: "1.0.0"

keywords:
  - microservice
  - universal
  - enterprise

maintainers:
  - name: Platform Team
    email: ownit@company.com

annotations:
  category: Application

核心模板实现

values.yaml - 配置核心

# values.yaml - 通用默认配置

#=============================================================================
# 基础配置
#=============================================================================
nameOverride: ""          # 覆盖 Chart 名称
fullnameOverride: ""      # 覆盖完整名称

# 全局配置
global:
  imageRegistry: "harbor.company.com"  # 全局镜像仓库
  imagePullSecrets:
    - name: harbor-secret
  environment: production              # 环境标识

#=============================================================================
# 应用配置
#=============================================================================
app:
  name: my-service
  version: "1.0.0"
  team: backend              # 团队标识,用于标签
  tier: backend              # 服务层级: frontend/backend/middleware

#=============================================================================
# 镜像配置
#=============================================================================
image:
  repository: my-service
  tag: ""                    # 留空则使用 appVersion
  pullPolicy: IfNotPresent

#=============================================================================
# 副本与更新策略
#=============================================================================
replicaCount: 2

strategy:
  type: RollingUpdate
  rollingUpdate:
    maxSurge: 25%
    maxUnavailable: 25%

#=============================================================================
# 容器配置
#=============================================================================
containerPort: 8080
command: []
args: []

# 环境变量(多种方式支持)
env: []
  # - name: JAVA_OPTS
  #   value: "-Xmx512m"
  # - name: DB_PASSWORD
  #   valueFrom:
  #     secretKeyRef:
  #       name: db-secret
  #       key: password

# 从 ConfigMap/Secret 加载环境变量
envFrom: []
  # - configMapRef:
  #     name: app-config
  # - secretRef:
  #     name: app-secrets

#=============================================================================
# 资源限制
#=============================================================================
resources:
  requests:
    cpu: 100m
    memory: 256Mi
  limits:
    cpu: 1000m
    memory: 1Gi

#=============================================================================
# 健康检查
#=============================================================================
# 存活探针
livenessProbe:
  enabled: true
  type: httpGet          # httpGet / tcpSocket / exec
  httpGet:
    path: /actuator/health/liveness
    port: http
  initialDelaySeconds: 30
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 3

# 就绪探针
readinessProbe:
  enabled: true
  type: httpGet
  httpGet:
    path: /actuator/health/readiness
    port: http
  initialDelaySeconds: 10
  periodSeconds: 5
  timeoutSeconds: 3
  failureThreshold: 3

# 启动探针(针对慢启动应用)
startupProbe:
  enabled: false
  type: httpGet
  httpGet:
    path: /actuator/health
    port: http
  initialDelaySeconds: 10
  periodSeconds: 10
  timeoutSeconds: 5
  failureThreshold: 30

#=============================================================================
# Service 配置
#=============================================================================
service:
  enabled: true
  type: ClusterIP
  port: 80
  targetPort: http       # 对应 containerPort 的名称
  # nodePort: 30080      # 当 type: NodePort 时使用
  annotations: {}

# 额外端口(如 gRPC、管理端口等)
extraPorts: []
  # - name: grpc
  #   containerPort: 9090
  #   servicePort: 9090
  #   protocol: TCP
  # - name: management
  #   containerPort: 8081
  #   servicePort: 8081

#=============================================================================
# Ingress 配置
#=============================================================================
ingress:
  enabled: false
  className: nginx
  annotations: {}
    # kubernetes.io/tls-acme: "true"
    # nginx.ingress.kubernetes.io/ssl-redirect: "true"
  hosts:
    - host: my-service.company.com
      paths:
        - path: /
          pathType: Prefix
  tls: []
    # - secretName: my-service-tls
    #   hosts:
    #     - my-service.company.com

#=============================================================================
# ConfigMap 配置
#=============================================================================
configMap:
  enabled: false
  data: {}
    # application.yml: |
    #   server:
    #     port: 8080
    # config.json: |
    #   {"key": "value"}

#=============================================================================
# Secret 配置
#=============================================================================
secret:
  enabled: false
  type: Opaque           # Opaque / kubernetes.io/tls / kubernetes.io/dockerconfigjson
  data: {}
    # username: admin
    # password: secret123

#=============================================================================
# 存储卷配置
#=============================================================================
volumes: []
  # - name: config
  #   configMap:
  #     name: app-config
  # - name: data
  #   persistentVolumeClaim:
  #     claimName: app-data
  # - name: cache
  #   emptyDir: {}

volumeMounts: []
  # - name: config
  #   mountPath: /app/config
  #   readOnly: true
  # - name: data
  #   mountPath: /app/data

#=============================================================================
# Sidecar 容器配置 ⭐ 重要功能
#=============================================================================
sidecars: []
  # - name: log-collector
  #   image: fluent/fluent-bit:latest
  #   resources:
  #     requests:
  #       cpu: 50m
  #       memory: 64Mi
  #   volumeMounts:
  #     - name: logs
  #       mountPath: /var/log/app
  # - name: envoy-proxy
  #   image: envoyproxy/envoy:v1.25.0
  #   ports:
  #     - containerPort: 15001

#=============================================================================
# Init 容器配置
#=============================================================================
initContainers: []
  # - name: wait-for-db
  #   image: busybox:1.36
  #   command: ['sh', '-c', 'until nc -z mysql 3306; do sleep 2; done']
  # - name: init-config
  #   image: busybox:1.36
  #   command: ['sh', '-c', 'cp /config/* /app/config/']
  #   volumeMounts:
  #     - name: config
  #       mountPath: /app/config

#=============================================================================
# 调度配置
#=============================================================================
nodeSelector: {}
  # kubernetes.io/os: linux
  # node-type: compute

tolerations: []
  # - key: "dedicated"
  #   operator: "Equal"
  #   value: "backend"
  #   effect: "NoSchedule"

affinity: {}
  # podAntiAffinity:
  #   preferredDuringSchedulingIgnoredDuringExecution:
  #     - weight: 100
  #       podAffinityTerm:
  #         labelSelector:
  #           matchLabels:
  #             app.kubernetes.io/name: my-service
  #         topologyKey: kubernetes.io/hostname

# 拓扑分布约束(K8s 1.19+)
topologySpreadConstraints: []
  # - maxSkew: 1
  #   topologyKey: topology.kubernetes.io/zone
  #   whenUnsatisfiable: ScheduleAnyway
  #   labelSelector:
  #     matchLabels:
  #       app.kubernetes.io/name: my-service

#=============================================================================
# 安全配置
#=============================================================================
podSecurityContext:
  fsGroup: 1000

securityContext:
  runAsNonRoot: true
  runAsUser: 1000
  readOnlyRootFilesystem: false
  allowPrivilegeEscalation: false
  capabilities:
    drop:
      - ALL

#=============================================================================
# ServiceAccount 配置
#=============================================================================
serviceAccount:
  create: true
  name: ""
  annotations: {}
    # eks.amazonaws.com/role-arn: arn:aws:iam::xxx:role/my-role

#=============================================================================
# HPA 自动伸缩配置
#=============================================================================
autoscaling:
  enabled: false
  minReplicas: 2
  maxReplicas: 10
  targetCPUUtilizationPercentage: 70
  targetMemoryUtilizationPercentage: 80
  # 自定义指标
  customMetrics: []
    # - type: Pods
    #   pods:
    #     metric:
    #       name: http_requests_per_second
    #     target:
    #       type: AverageValue
    #       averageValue: 1000

#=============================================================================
# PodDisruptionBudget 配置
#=============================================================================
pdb:
  enabled: false
  minAvailable: 1
  # maxUnavailable: 1   # 与 minAvailable 二选一

#=============================================================================
# 自定义标签和注解
#=============================================================================
podLabels: {}
podAnnotations: {}
  # prometheus.io/scrape: "true"
  # prometheus.io/port: "8080"
  # prometheus.io/path: "/actuator/prometheus"

#=============================================================================
# 生命周期钩子
#=============================================================================
lifecycle: {}
  # preStop:
  #   exec:
  #     command: ["/bin/sh", "-c", "sleep 10"]
  # postStart:
  #   httpGet:
  #     path: /warmup
  #     port: 8080

#=============================================================================
# 优雅终止
#=============================================================================
terminationGracePeriodSeconds: 30

_helpers.tpl - 公共模板函数

{{/* templates/_helpers.tpl */}}

{{/*
生成 Chart 名称
*/}}
{{- define "universal-app.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
生成完整的应用名称
规则: release名称-chart名称,最多63字符
*/}}
{{- define "universal-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 }}

{{/*
Chart 标签
*/}}
{{- define "universal-app.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
通用标签 - 用于所有资源
*/}}
{{- define "universal-app.labels" -}}
helm.sh/chart: {{ include "universal-app.chart" . }}
{{ include "universal-app.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- if .Values.app }}
{{- if .Values.app.team }}
app.kubernetes.io/team: {{ .Values.app.team }}
{{- end }}
{{- if .Values.app.tier }}
app.kubernetes.io/tier: {{ .Values.app.tier }}
{{- end }}
{{- end }}
{{- if .Values.global }}
{{- if .Values.global.environment }}
environment: {{ .Values.global.environment }}
{{- end }}
{{- end }}
{{- end }}

{{/*
选择器标签 - 用于 Pod 选择
*/}}
{{- define "universal-app.selectorLabels" -}}
app.kubernetes.io/name: {{ include "universal-app.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
生成完整的镜像地址
支持: 全局镜像仓库 + 本地镜像仓库覆盖
*/}}
{{- define "universal-app.image" -}}
{{- $registry := "" }}
{{- $repository := .Values.image.repository }}
{{- $tag := .Values.image.tag | default .Chart.AppVersion }}
{{- if .Values.global }}
{{- if .Values.global.imageRegistry }}
{{- $registry = .Values.global.imageRegistry }}
{{- end }}
{{- end }}
{{- if .Values.image.registry }}
{{- $registry = .Values.image.registry }}
{{- end }}
{{- if $registry }}
{{- printf "%s/%s:%s" $registry $repository $tag }}
{{- else }}
{{- printf "%s:%s" $repository $tag }}
{{- end }}
{{- end }}

{{/*
ServiceAccount 名称
*/}}
{{- define "universal-app.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "universal-app.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

{{/*
生成探针配置
参数: dict "probe" .Values.xxxProbe "port" "http"
*/}}
{{- define "universal-app.probe" -}}
{{- $probe := .probe }}
{{- $defaultPort := .port | default "http" }}
{{- if eq $probe.type "httpGet" }}
httpGet:
  path: {{ $probe.httpGet.path }}
  port: {{ $probe.httpGet.port | default $defaultPort }}
  {{- if $probe.httpGet.scheme }}
  scheme: {{ $probe.httpGet.scheme }}
  {{- end }}
  {{- if $probe.httpGet.httpHeaders }}
  httpHeaders:
    {{- toYaml $probe.httpGet.httpHeaders | nindent 4 }}
  {{- end }}
{{- else if eq $probe.type "tcpSocket" }}
tcpSocket:
  port: {{ $probe.tcpSocket.port | default $defaultPort }}
{{- else if eq $probe.type "exec" }}
exec:
  command:
    {{- toYaml $probe.exec.command | nindent 4 }}
{{- else if eq $probe.type "grpc" }}
grpc:
  port: {{ $probe.grpc.port }}
  {{- if $probe.grpc.service }}
  service: {{ $probe.grpc.service }}
  {{- end }}
{{- end }}
initialDelaySeconds: {{ $probe.initialDelaySeconds | default 10 }}
periodSeconds: {{ $probe.periodSeconds | default 10 }}
timeoutSeconds: {{ $probe.timeoutSeconds | default 5 }}
failureThreshold: {{ $probe.failureThreshold | default 3 }}
successThreshold: {{ $probe.successThreshold | default 1 }}
{{- end }}

{{/*
合并 Pod 注解(基础 + 自定义)
*/}}
{{- define "universal-app.podAnnotations" -}}
{{- $annotations := dict }}
{{- if .Values.podAnnotations }}
{{- $annotations = merge $annotations .Values.podAnnotations }}
{{- end }}
{{- if $annotations }}
{{- toYaml $annotations }}
{{- end }}
{{- end }}

{{/*
检查是否需要创建任何可选资源
*/}}
{{- define "universal-app.hasOptionalResources" -}}
{{- or .Values.configMap.enabled .Values.secret.enabled .Values.ingress.enabled .Values.autoscaling.enabled .Values.pdb.enabled }}
{{- end }}

deployment.yaml - Deployment 模板

{{/* templates/deployment.yaml */}}
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "universal-app.fullname" . }}
  labels:
    {{- include "universal-app.labels" . | nindent 4 }}
spec:
  {{- if not .Values.autoscaling.enabled }}
  replicas: {{ .Values.replicaCount }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "universal-app.selectorLabels" . | nindent 6 }}
  {{- with .Values.strategy }}
  strategy:
    {{- toYaml . | nindent 4 }}
  {{- end }}
  template:
    metadata:
      annotations:
        {{- /* 配置变更自动触发滚动更新 */}}
        {{- if .Values.configMap.enabled }}
        checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
        {{- end }}
        {{- if .Values.secret.enabled }}
        checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
        {{- end }}
        {{- with .Values.podAnnotations }}
        {{- toYaml . | nindent 8 }}
        {{- end }}
      labels:
        {{- include "universal-app.labels" . | nindent 8 }}
        {{- with .Values.podLabels }}
        {{- toYaml . | nindent 8 }}
        {{- end }}
    spec:
      {{- /* 镜像拉取密钥 */}}
      {{- if or .Values.global.imagePullSecrets .Values.imagePullSecrets }}
      imagePullSecrets:
        {{- if .Values.global.imagePullSecrets }}
        {{- toYaml .Values.global.imagePullSecrets | nindent 8 }}
        {{- end }}
        {{- if .Values.imagePullSecrets }}
        {{- toYaml .Values.imagePullSecrets | nindent 8 }}
        {{- end }}
      {{- end }}
      
      {{- /* ServiceAccount */}}
      serviceAccountName: {{ include "universal-app.serviceAccountName" . }}
      
      {{- /* Pod 安全上下文 */}}
      {{- with .Values.podSecurityContext }}
      securityContext:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      
      {{- /* 优雅终止时间 */}}
      terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds | default 30 }}
      
      {{- /* Init 容器 */}}
      {{- if .Values.initContainers }}
      initContainers:
        {{- range .Values.initContainers }}
        - name: {{ .name }}
          image: {{ .image }}
          {{- if .command }}
          command:
            {{- toYaml .command | nindent 12 }}
          {{- end }}
          {{- if .args }}
          args:
            {{- toYaml .args | nindent 12 }}
          {{- end }}
          {{- if .env }}
          env:
            {{- toYaml .env | nindent 12 }}
          {{- end }}
          {{- if .resources }}
          resources:
            {{- toYaml .resources | nindent 12 }}
          {{- end }}
          {{- if .volumeMounts }}
          volumeMounts:
            {{- toYaml .volumeMounts | nindent 12 }}
          {{- end }}
        {{- end }}
      {{- end }}
      
      containers:
        {{- /* ==================== 主容器 ==================== */}}
        - name: {{ .Chart.Name }}
          image: {{ include "universal-app.image" . }}
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          
          {{- /* 容器安全上下文 */}}
          {{- with .Values.securityContext }}
          securityContext:
            {{- toYaml . | nindent 12 }}
          {{- end }}
          
          {{- /* 自定义命令 */}}
          {{- if .Values.command }}
          command:
            {{- toYaml .Values.command | nindent 12 }}
          {{- end }}
          
          {{- /* 自定义参数 */}}
          {{- if .Values.args }}
          args:
            {{- toYaml .Values.args | nindent 12 }}
          {{- end }}
          
          {{- /* 端口配置 */}}
          ports:
            - name: http
              containerPort: {{ .Values.containerPort }}
              protocol: TCP
            {{- range .Values.extraPorts }}
            - name: {{ .name }}
              containerPort: {{ .containerPort }}
              protocol: {{ .protocol | default "TCP" }}
            {{- end }}
          
          {{- /* 环境变量 */}}
          {{- if or .Values.env .Values.envFrom }}
          {{- if .Values.env }}
          env:
            {{- toYaml .Values.env | nindent 12 }}
          {{- end }}
          {{- if .Values.envFrom }}
          envFrom:
            {{- toYaml .Values.envFrom | nindent 12 }}
          {{- end }}
          {{- end }}
          
          {{- /* 存活探针 */}}
          {{- if .Values.livenessProbe.enabled }}
          livenessProbe:
            {{- include "universal-app.probe" (dict "probe" .Values.livenessProbe "port" "http") | nindent 12 }}
          {{- end }}
          
          {{- /* 就绪探针 */}}
          {{- if .Values.readinessProbe.enabled }}
          readinessProbe:
            {{- include "universal-app.probe" (dict "probe" .Values.readinessProbe "port" "http") | nindent 12 }}
          {{- end }}
          
          {{- /* 启动探针 */}}
          {{- if .Values.startupProbe.enabled }}
          startupProbe:
            {{- include "universal-app.probe" (dict "probe" .Values.startupProbe "port" "http") | nindent 12 }}
          {{- end }}
          
          {{- /* 资源限制 */}}
          {{- with .Values.resources }}
          resources:
            {{- toYaml . | nindent 12 }}
          {{- end }}
          
          {{- /* 生命周期钩子 */}}
          {{- with .Values.lifecycle }}
          lifecycle:
            {{- toYaml . | nindent 12 }}
          {{- end }}
          
          {{- /* 卷挂载 */}}
          {{- if .Values.volumeMounts }}
          volumeMounts:
            {{- toYaml .Values.volumeMounts | nindent 12 }}
          {{- end }}
        
        {{- /* ==================== Sidecar 容器 ==================== */}}
        {{- range .Values.sidecars }}
        - name: {{ .name }}
          image: {{ .image }}
          imagePullPolicy: {{ .imagePullPolicy | default "IfNotPresent" }}
          {{- if .command }}
          command:
            {{- toYaml .command | nindent 12 }}
          {{- end }}
          {{- if .args }}
          args:
            {{- toYaml .args | nindent 12 }}
          {{- end }}
          {{- if .ports }}
          ports:
            {{- toYaml .ports | nindent 12 }}
          {{- end }}
          {{- if .env }}
          env:
            {{- toYaml .env | nindent 12 }}
          {{- end }}
          {{- if .envFrom }}
          envFrom:
            {{- toYaml .envFrom | nindent 12 }}
          {{- end }}
          {{- if .resources }}
          resources:
            {{- toYaml .resources | nindent 12 }}
          {{- end }}
          {{- if .volumeMounts }}
          volumeMounts:
            {{- toYaml .volumeMounts | nindent 12 }}
          {{- end }}
          {{- if .securityContext }}
          securityContext:
            {{- toYaml .securityContext | nindent 12 }}
          {{- end }}
          {{- if .livenessProbe }}
          livenessProbe:
            {{- toYaml .livenessProbe | nindent 12 }}
          {{- end }}
          {{- if .readinessProbe }}
          readinessProbe:
            {{- toYaml .readinessProbe | nindent 12 }}
          {{- end }}
        {{- end }}
      
      {{- /* 存储卷 */}}
      {{- if .Values.volumes }}
      volumes:
        {{- toYaml .Values.volumes | nindent 8 }}
      {{- end }}
      
      {{- /* 节点选择器 */}}
      {{- with .Values.nodeSelector }}
      nodeSelector:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      
      {{- /* 亲和性 */}}
      {{- with .Values.affinity }}
      affinity:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      
      {{- /* 容忍度 */}}
      {{- with .Values.tolerations }}
      tolerations:
        {{- toYaml . | nindent 8 }}
      {{- end }}
      
      {{- /* 拓扑分布约束 */}}
      {{- with .Values.topologySpreadConstraints }}
      topologySpreadConstraints:
        {{- toYaml . | nindent 8 }}
      {{- end }}

service.yaml - Service 模板

{{/* templates/service.yaml */}}
{{- if .Values.service.enabled }}
apiVersion: v1
kind: Service
metadata:
  name: {{ include "universal-app.fullname" . }}
  labels:
    {{- include "universal-app.labels" . | nindent 4 }}
  {{- with .Values.service.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
  type: {{ .Values.service.type }}
  {{- if and (eq .Values.service.type "ClusterIP") .Values.service.clusterIP }}
  clusterIP: {{ .Values.service.clusterIP }}
  {{- end }}
  {{- if and (eq .Values.service.type "LoadBalancer") .Values.service.loadBalancerIP }}
  loadBalancerIP: {{ .Values.service.loadBalancerIP }}
  {{- end }}
  {{- if and (eq .Values.service.type "LoadBalancer") .Values.service.loadBalancerSourceRanges }}
  loadBalancerSourceRanges:
    {{- toYaml .Values.service.loadBalancerSourceRanges | nindent 4 }}
  {{- end }}
  {{- if .Values.service.externalTrafficPolicy }}
  externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }}
  {{- end }}
  ports:
    - name: http
      port: {{ .Values.service.port }}
      targetPort: {{ .Values.service.targetPort | default "http" }}
      protocol: TCP
      {{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }}
      nodePort: {{ .Values.service.nodePort }}
      {{- end }}
    {{- range .Values.extraPorts }}
    - name: {{ .name }}
      port: {{ .servicePort }}
      targetPort: {{ .name }}
      protocol: {{ .protocol | default "TCP" }}
    {{- end }}
  selector:
    {{- include "universal-app.selectorLabels" . | nindent 4 }}
{{- end }}

ingress.yaml - Ingress 模板

{{/* templates/ingress.yaml */}}
{{- if .Values.ingress.enabled -}}
{{- $fullName := include "universal-app.fullname" . -}}
{{- $svcPort := .Values.service.port -}}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ $fullName }}
  labels:
    {{- include "universal-app.labels" . | nindent 4 }}
  {{- with .Values.ingress.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
  {{- if .Values.ingress.className }}
  ingressClassName: {{ .Values.ingress.className }}
  {{- end }}
  {{- if .Values.ingress.tls }}
  tls:
    {{- range .Values.ingress.tls }}
    - hosts:
        {{- range .hosts }}
        - {{ . | quote }}
        {{- end }}
      secretName: {{ .secretName }}
    {{- end }}
  {{- end }}
  rules:
    {{- range .Values.ingress.hosts }}
    - host: {{ .host | quote }}
      http:
        paths:
          {{- range .paths }}
          - path: {{ .path }}
            pathType: {{ .pathType }}
            backend:
              service:
                name: {{ $fullName }}
                port:
                  number: {{ $svcPort }}
          {{- end }}
    {{- end }}
{{- end }}

configmap.yaml - ConfigMap 模板

{{/* templates/configmap.yaml */}}
{{- if .Values.configMap.enabled }}
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "universal-app.fullname" . }}
  labels:
    {{- include "universal-app.labels" . | nindent 4 }}
data:
  {{- range $key, $value := .Values.configMap.data }}
  {{ $key }}: |
    {{- $value | nindent 4 }}
  {{- end }}
{{- end }}

secret.yaml - Secret 模板

{{/* templates/secret.yaml */}}
{{- if .Values.secret.enabled }}
apiVersion: v1
kind: Secret
metadata:
  name: {{ include "universal-app.fullname" . }}
  labels:
    {{- include "universal-app.labels" . | nindent 4 }}
type: {{ .Values.secret.type | default "Opaque" }}
data:
  {{- range $key, $value := .Values.secret.data }}
  {{ $key }}: {{ $value | b64enc | quote }}
  {{- end }}
{{- end }}

hpa.yaml - HPA 模板

{{/* templates/hpa.yaml */}}
{{- if .Values.autoscaling.enabled }}
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: {{ include "universal-app.fullname" . }}
  labels:
    {{- include "universal-app.labels" . | nindent 4 }}
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: {{ include "universal-app.fullname" . }}
  minReplicas: {{ .Values.autoscaling.minReplicas }}
  maxReplicas: {{ .Values.autoscaling.maxReplicas }}
  metrics:
    {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
    {{- end }}
    {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
    {{- end }}
    {{- with .Values.autoscaling.customMetrics }}
    {{- toYaml . | nindent 4 }}
    {{- end }}
  {{- if .Values.autoscaling.behavior }}
  behavior:
    {{- toYaml .Values.autoscaling.behavior | nindent 4 }}
  {{- end }}
{{- end }}

serviceaccount.yaml - ServiceAccount 模板

{{/* templates/serviceaccount.yaml */}}
{{- if .Values.serviceAccount.create -}}
apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ include "universal-app.serviceAccountName" . }}
  labels:
    {{- include "universal-app.labels" . | nindent 4 }}
  {{- with .Values.serviceAccount.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
automountServiceAccountToken: {{ .Values.serviceAccount.automount | default true }}
{{- end }}

pdb.yaml - PodDisruptionBudget 模板

{{/* templates/pdb.yaml */}}
{{- if .Values.pdb.enabled }}
apiVersion: policy/v1
kind: PodDisruptionBudget
metadata:
  name: {{ include "universal-app.fullname" . }}
  labels:
    {{- include "universal-app.labels" . | nindent 4 }}
spec:
  {{- if .Values.pdb.minAvailable }}
  minAvailable: {{ .Values.pdb.minAvailable }}
  {{- else if .Values.pdb.maxUnavailable }}
  maxUnavailable: {{ .Values.pdb.maxUnavailable }}
  {{- end }}
  selector:
    matchLabels:
      {{- include "universal-app.selectorLabels" . | nindent 6 }}
{{- end }}

高级特性支持

多环境配置管理

配置文件结构/
├── values.yaml                    # 默认配置
├── environments/
│   ├── values-dev.yaml            # 开发环境
│   └── values-prod.yaml           # 生产环境
└── services/
    ├── values-user-service.yaml   # 用户服务
    ├── values-order-service.yaml  # 订单服务
    └── values-gateway-service.yaml    # 网关服务

在这里插入图片描述

values-dev.yaml

# environments/values-dev.yaml - 开发环境基础配置
global:
  imageRegistry: "harbor-dev.company.com"
  environment: development

replicaCount: 1

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

autoscaling:
  enabled: false

ingress:
  annotations:
    kubernetes.io/tls-acme: "false"

values-prod.yaml

global:
  imageRegistry: "harbor.company.com"
  environment: production

replicaCount: 3

resources:
  requests:
    cpu: 500m
    memory: 1Gi
  limits:
    cpu: 2000m
    memory: 2Gi

autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 20

pdb:
  enabled: true
  minAvailable: 2

# 生产级健康检查
livenessProbe:
  enabled: true
  initialDelaySeconds: 60
  periodSeconds: 15
  failureThreshold: 5

# 生产级优雅终止
terminationGracePeriodSeconds: 60
lifecycle:
  preStop:
    exec:
      command: ["/bin/sh", "-c", "sleep 15"]

# 反亲和性 - 分散部署
affinity:
  podAntiAffinity:
    preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 100
        podAffinityTerm:
          labelSelector:
            matchLabels:
              app.kubernetes.io/name: '{{ include "universal-app.name" . }}'
          topologyKey: kubernetes.io/hostname

服务特定配置示例

values-user-service.yaml - 用户服务

# services/values-user-service.yaml - 用户服务
app:
  name: user-service
  team: user-platform
  tier: backend

image:
  repository: user-service
  tag: "2.1.0"

containerPort: 8080

# 用户服务特有的环境变量
env:
  - name: SPRING_PROFILES_ACTIVE
    value: "kubernetes"
  - name: JAVA_OPTS
    value: "-Xms512m -Xmx1024m -XX:+UseG1GC"
  - name: DB_HOST
    valueFrom:
      secretKeyRef:
        name: user-db-credentials
        key: host
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: user-db-credentials
        key: password
  - name: REDIS_HOST
    value: "redis-cluster.cache.svc.cluster.local"

# 服务发现
service:
  enabled: true
  port: 80

# 开启 Ingress
ingress:
  enabled: true
  className: nginx
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
  hosts:
    - host: user-api.company.com
      paths:
        - path: /api/v1/users
          pathType: Prefix
        - path: /api/v2/users
          pathType: Prefix
  tls:
    - secretName: user-service-tls
      hosts:
        - user-api.company.com

# Prometheus 监控
podAnnotations:
  prometheus.io/scrape: "true"
  prometheus.io/port: "8080"
  prometheus.io/path: "/actuator/prometheus"

# 用户服务的健康检查路径
livenessProbe:
  enabled: true
  type: httpGet
  httpGet:
    path: /actuator/health/liveness
    port: http
  initialDelaySeconds: 45
  periodSeconds: 10

readinessProbe:
  enabled: true
  type: httpGet
  httpGet:
    path: /actuator/health/readiness
    port: http
  initialDelaySeconds: 20
  periodSeconds: 5

values-order-service.yaml - 订单服务(含 Sidecar)

# services/values-order-service.yaml - 订单服务(含 Sidecar)
app:
  name: order-service
  team: order-platform
  tier: backend

image:
  repository: order-service
  tag: "3.2.1"

containerPort: 8080

# gRPC 额外端口
extraPorts:
  - name: grpc
    containerPort: 9090
    servicePort: 9090
    protocol: TCP

env:
  - name: SPRING_PROFILES_ACTIVE
    value: "kubernetes,grpc"
  - name: JAVA_OPTS
    value: "-Xms1g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200"
  - name: KAFKA_BROKERS
    value: "kafka-cluster.messaging:9092"
  - name: MQ_TYPE
    value: "kafka"

# 🌟 Sidecar 容器:日志收集 + 链路追踪
sidecars:
  # Fluent Bit 日志收集
  - name: log-collector
    image: fluent/fluent-bit:2.1
    resources:
      requests:
        cpu: 50m
        memory: 64Mi
      limits:
        cpu: 100m
        memory: 128Mi
    volumeMounts:
      - name: app-logs
        mountPath: /var/log/app
        readOnly: true
      - name: fluent-bit-config
        mountPath: /fluent-bit/etc
    env:
      - name: POD_NAME
        valueFrom:
          fieldRef:
            fieldPath: metadata.name
      - name: POD_NAMESPACE
        valueFrom:
          fieldRef:
            fieldPath: metadata.namespace
  
  # Jaeger Agent 链路追踪
  - name: jaeger-agent
    image: jaegertracing/jaeger-agent:1.47
    args:
      - "--reporter.grpc.host-port=jaeger-collector.monitoring:14250"
    ports:
      - containerPort: 6831
        protocol: UDP
      - containerPort: 6832
        protocol: UDP
    resources:
      requests:
        cpu: 50m
        memory: 64Mi
      limits:
        cpu: 100m
        memory: 128Mi

# 存储卷配置
volumes:
  - name: app-logs
    emptyDir: {}
  - name: fluent-bit-config
    configMap:
      name: order-service-fluent-bit

volumeMounts:
  - name: app-logs
    mountPath: /app/logs

# 订单服务需要更高资源
resources:
  requests:
    cpu: 500m
    memory: 1Gi
  limits:
    cpu: 2000m
    memory: 3Gi

# 开启自动伸缩
autoscaling:
  enabled: true
  minReplicas: 3
  maxReplicas: 30
  targetCPUUtilizationPercentage: 65
  targetMemoryUtilizationPercentage: 75
  behavior:
    scaleDown:
      stabilizationWindowSeconds: 300
      policies:
        - type: Percent
          value: 10
          periodSeconds: 60
    scaleUp:
      stabilizationWindowSeconds: 0
      policies:
        - type: Percent
          value: 100
          periodSeconds: 15
        - type: Pods
          value: 4
          periodSeconds: 15
      selectPolicy: Max

ingress:
  enabled: true
  className: nginx
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "300"
  hosts:
    - host: order-api.company.com
      paths:
        - path: /
          pathType: Prefix
  tls:
    - secretName: order-service-tls
      hosts:
        - order-api.company.com

values-gateway-service.yaml - API 网关服务

# services/values-gateway-service.yaml - API 网关服务
app:
  name: api-gateway
  team: platform
  tier: frontend

image:
  repository: api-gateway
  tag: "1.5.0"

containerPort: 8080

# 网关需要更多副本
replicaCount: 5

# Init 容器:等待依赖服务就绪
initContainers:
  - name: wait-for-config-server
    image: busybox:1.36
    command:
      - sh
      - -c
      - |
        echo "Waiting for config-server..."
        until nc -z config-server.default.svc.cluster.local 8888; do
          echo "Config server not ready, waiting..."
          sleep 5
        done
        echo "Config server is ready!"
  - name: wait-for-eureka
    image: busybox:1.36
    command:
      - sh
      - -c
      - |
        until nc -z eureka-server.default.svc.cluster.local 8761; do
          sleep 5
        done

env:
  - name: SPRING_PROFILES_ACTIVE
    value: "kubernetes"
  - name: JAVA_OPTS
    value: "-Xms1g -Xmx2g -Dspring.cloud.gateway.enabled=true"

# 网关的健康检查
livenessProbe:
  enabled: true
  type: httpGet
  httpGet:
    path: /actuator/health
    port: http
  initialDelaySeconds: 60
  periodSeconds: 10
  failureThreshold: 5

readinessProbe:
  enabled: true
  type: httpGet
  httpGet:
    path: /actuator/health/readiness
    port: http
  initialDelaySeconds: 30
  periodSeconds: 5

# 启动探针(网关启动较慢)
startupProbe:
  enabled: true
  type: httpGet
  httpGet:
    path: /actuator/health
    port: http
  initialDelaySeconds: 30
  periodSeconds: 10
  failureThreshold: 30

resources:
  requests:
    cpu: 1000m
    memory: 2Gi
  limits:
    cpu: 4000m
    memory: 4Gi

# 服务类型为 LoadBalancer(云厂商)
service:
  enabled: true
  type: LoadBalancer
  port: 80
  annotations:
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
    service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled: "true"

# 网关的 Ingress 配置
ingress:
  enabled: true
  className: nginx
  annotations:
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/use-regex: "true"
    nginx.ingress.kubernetes.io/rewrite-target: /$1
    # 限流配置
    nginx.ingress.kubernetes.io/limit-connections: "100"
    nginx.ingress.kubernetes.io/limit-rps: "50"
  hosts:
    - host: api.company.com
      paths:
        - path: /(.*)
          pathType: ImplementationSpecific

# 网关需要跨可用区分布
topologySpreadConstraints:
  - maxSkew: 1
    topologyKey: topology.kubernetes.io/zone
    whenUnsatisfiable: DoNotSchedule
    labelSelector:
      matchLabels:
        app.kubernetes.io/name: api-gateway
  - maxSkew: 1
    topologyKey: kubernetes.io/hostname
    whenUnsatisfiable: ScheduleAnyway
    labelSelector:
      matchLabels:
        app.kubernetes.io/name: api-gateway

部署

部署命令

# 1. 渲染模板但不部署
helm template my-release ./universal-app \
  -f environments/values-dev.yaml \
  -f services/values-user-service.yaml

# 2. 渲染并验证
helm template my-release ./universal-app \
  -f environments/values-dev.yaml \
  -f services/values-user-service.yaml \
  --validate

# 3. 只看特定模板
helm template my-release ./universal-app \
  -f values.yaml \
  --show-only templates/deployment.yaml

# 4. 调试模式
helm install my-release ./universal-app --debug --dry-run

# 5. 查看实际使用的 values
helm get values my-release

# 6. 查看所有已渲染的资源
helm get manifest my-release

# 7. 查看 release 历史
helm history my-release

# 8. 回滚到上一版本
helm rollback my-release

# 9. 查看 hooks
helm get hooks my-release

# 10. Lint 检查
helm lint ./universal-app -f values.yaml

部署脚本

#!/bin/bash
# deploy.sh - 通用部署脚本

set -e

# 颜色定义
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m'

# 使用方法
usage() {
    echo "Usage: $0 -s <service> -e <environment> [-n <namespace>] [-d]"
    echo ""
    echo "Options:"
    echo "  -s  Service name (e.g., user-service, order-service)"
    echo "  -e  Environment (dev, staging, prod)"
    echo "  -n  Namespace (default: default)"
    echo "  -d  Dry-run mode"
    echo "  -h  Show this help"
    echo ""
    echo "Examples:"
    echo "  $0 -s user-service -e dev"
    echo "  $0 -s order-service -e prod -n production"
    echo "  $0 -s gateway-service -e staging -d"
    exit 1
}

# 参数解析
SERVICE=""
ENV=""
NAMESPACE="default"
DRY_RUN=""

while getopts "s:e:n:dh" opt; do
    case $opt in
        s) SERVICE="$OPTARG" ;;
        e) ENV="$OPTARG" ;;
        n) NAMESPACE="$OPTARG" ;;
        d) DRY_RUN="--dry-run" ;;
        h) usage ;;
        *) usage ;;
    esac
done

# 验证参数
if [[ -z "$SERVICE" || -z "$ENV" ]]; then
    echo -e "${RED}Error: Service and environment are required${NC}"
    usage
fi

# 配置文件路径
BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CHART_PATH="${BASE_DIR}"
ENV_VALUES="${BASE_DIR}/environments/values-${ENV}.yaml"
SERVICE_VALUES="${BASE_DIR}/services/values-${SERVICE}.yaml"

# 检查配置文件
if [[ ! -f "$ENV_VALUES" ]]; then
    echo -e "${RED}Error: Environment config not found: $ENV_VALUES${NC}"
    exit 1
fi

if [[ ! -f "$SERVICE_VALUES" ]]; then
    echo -e "${RED}Error: Service config not found: $SERVICE_VALUES${NC}"
    exit 1
fi

# 生成 Release 名称
RELEASE_NAME="${SERVICE}"

echo -e "${GREEN}========================================${NC}"
echo -e "${GREEN}Deploying ${SERVICE} to ${ENV}${NC}"
echo -e "${GREEN}========================================${NC}"
echo -e "Release: ${RELEASE_NAME}"
echo -e "Namespace: ${NAMESPACE}"
echo -e "Environment Config: ${ENV_VALUES}"
echo -e "Service Config: ${SERVICE_VALUES}"
echo ""

# 执行 Helm 部署
helm upgrade --install "${RELEASE_NAME}" "${CHART_PATH}" \
    --namespace "${NAMESPACE}" \
    --create-namespace \
    -f "${ENV_VALUES}" \
    -f "${SERVICE_VALUES}" \
    --set app.version="$(git rev-parse --short HEAD)" \
    --wait \
    --timeout 10m \
    ${DRY_RUN}

if [[ -z "$DRY_RUN" ]]; then
    echo ""
    echo -e "${GREEN}✅ Deployment successful!${NC}"
    echo ""
    echo "Run the following commands to check status:"
    echo "  kubectl get pods -n ${NAMESPACE} -l app.kubernetes.io/name=${SERVICE}"
    echo "  kubectl logs -n ${NAMESPACE} -l app.kubernetes.io/name=${SERVICE} -f"
else
    echo ""
    echo -e "${YELLOW}🔍 Dry-run completed. No changes were made.${NC}"
fi

在这里插入图片描述

批量部署

#!/bin/bash
# deploy-all.sh - 批量部署所有服务

set -e

ENV=${1:-dev}
NAMESPACE=${2:-default}

SERVICES=(
    "config-server"
    "eureka-server"
    "api-gateway"
    "user-service"
    "order-service"
    "payment-service"
    "notification-service"
)

echo "=========================================="
echo "Deploying all services to ${ENV}"
echo "=========================================="

for service in "${SERVICES[@]}"; do
    echo ""
    echo "Deploying ${service}..."
    ./deploy.sh -s "${service}" -e "${ENV}" -n "${NAMESPACE}"
done

echo ""
echo "✅ All services deployed successfully!"
Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐