企业级 Helm 通用 Chart 项目实战指南
本文介绍了一种通用化Helm Chart设计方案,用于解决Kubernetes微服务部署中的重复配置问题。通过抽象公共逻辑和参数化差异点,构建了一个"通用超级Chart",开发者只需通过values.yaml声明服务差异即可自动生成标准部署配置。方案包含核心架构设计、目录结构优化和values.yaml详细配置模板,支持镜像、副本、资源限制、健康检查等通用微服务配置项。该设计显
项目背景
在当前的云原生技术栈中,Kubernetes 已成为容器编排的事实标准。然而,随着微服务架构的普及,单纯的 YAML 文件管理逐渐暴露出其局限性。我们面临的核心痛点是 “重复性劳动” 与 “维护的碎片化” 。
项目状况
**后端服务 A、B、C 的 Deployment 90% 都是重复代码。**这种碎片化的管理方式,让运维团队在面对架构升级时苦不堪言。
在微服务架构下,几十个 Java 服务的 Kubernetes 部署配置往往大同小异。若为每个服务单独维护 Chart,或通过复制粘贴管理 YAML,会导致严重的维护噩梦——一旦需要升级基础组件(如注入 Sidecar、调整 JVM 参数),便需修改数十份文件。
目标:通用化 Helm Chart
通过构建一个“通用超级 Chart”,我们将部署逻辑抽象化。开发者只需通过 values.yaml 声明差异(镜像、端口、环境变量),即可利用 Helm 模板引擎自动生成标准的 Kubernetes 清单,实现“一次定义,多处复用”。
核心思路
要实现上述目标,核心思路在于抽象与逻辑化:
-
抽象公共逻辑:将 Java 服务共有的配置(如通用的探针、资源请求、标签选择器、卷挂载)提取到
_helpers.tpl或独立的模板片段中。 -
参数化差异点:将镜像、端口、环境变量等差异点暴露在
values.yaml中,作为模板的输入参数。 -
逻辑控制:利用 Helm 模板的控制结构(
if/else、range、with),根据values.yaml中的开关动态决定是否渲染 Ingress、Sidecar 或特定配置。 -
模块化设计:构建一个顶层 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!"
更多推荐
所有评论(0)