1 Helm简介

Helm是Kubernetes官方推荐的“包管理器”,相当于apt/yum/npm在Linux系统的角色,用来“一键打包、分发、安装、升级、回滚”K8s应用。
需要注意的是,Helm和Kubernetes是独立发布的,在安装前需要先检查这两者的版本是否兼容。
在本文配套资料包中,有Helm一键安装脚本,这个版本是v3.16.0,与kubernetes的v1.29.6兼容。

[root@master 12-helm]# kubectl version
Client Version: v1.29.6+k3s1
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.29.6+k3s1
[root@master nginx-no-helm]# helm version
version.BuildInfo{Version:"v3.16.0", GitCommit:"0d439e1a09683f21a0ab9401eb661401f185b00b", GitTreeState:"clean", GoVersion:"go1.22.6"}

2 Helm核心概念

  • Chart:把一堆 K8s YAML 模板 + 默认值打成的“软件安装包”,相当于rpm/deb包。
  • Repository:存放Chart压缩包和索引的HTTP站点,相当于yum/apt的源
  • Release:把一个Chart安装到集群后的“运行实例”,相当于yum install nginx后的运行的进程
  • Value:渲染模板时传入的参数,可覆盖默认值,相当于rpm -ivh nginx --set port=8080
  • Template:用Go template写的YAML模板,填入Value后变成真正发给kube-apiserver的文件。

当我们执行helm install my-web myrepo/web --set replicaCount=3命令时,Helm去Repository拉取Chart,把Value填进Template,渲染成YAML,创建Release,升级/回滚只改Value或换Chart版本时,Release历史自动保留。
总结一下:Chart是安装包,Repository是仓库,Release是装出来的实例,Value是参数,Template是模板。

3 Helm使用方法

Helm在Kubernetes中相当于“应用商店+模板引擎+版本管理器”,所以使用流程一般为“安装Helm客户端、添加仓库、一键部署应用、升级回滚应用”这种方式。
(1) 安装Helm客户端
如果无法访问该地址,可以选择使用国内源,或者使用本文配套资料包,一键安装Helm。
curl https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3 | bash
(2) 添加常用仓库
这里仅演示添加一个仓库的情形,实际上可以添加多个仓库,可以是公有仓库,也可以是私有仓库。
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
(3) 一键部署应用
以部署MySQL为例,自定义root密码。
helm install mydb bitnami/mysql --set auth.rootPassword=secret123
(4) 查看、升级、回滚
helm list
helm upgrade mydb bitnami/mysql --set auth.rootPassword=newpwd
helm rollback mydb 1
helm uninstall mydb

4 Helm私有仓库搭建及使用

私有仓库搭建非常简单,只要有一个能提供http服务的环境,把Chart应用包和仓库索引文件放进去即可。

在下面的示例中,我们做了五件事情,一是打包两个Chart应用,二是生成仓库索引文件,三是把Chart和仓库索引文件放到HTTP服务器搭建了一个私有仓库,四是使用私有仓库安装应用,五是更新Chart并重新发布到私有仓库。

4.1 示例:Helm私有仓库搭建和使用

(1) 打包两个Chart应用
创建两个Chart,一个叫foo,一个叫bar。将它们打包为foo-0.1.0.tgz和bar-0.1.0.tgz。
这两个示例文件使用的镜像为nginx:0.1.0,可以用docker tag nginx:1.27.3 nginx:0.1.0命令为nginx:1.27.3创建一个tag,或者修改charts/foo/values.yaml文件(同目录Chart.yaml文件中还有个字段叫appVersion,最好也将这个字段改为1.27.3)
sed -i 's|tag:.*|tag: "1.27.3"|' charts/foo/values.yaml
sed -i 's|^appVersion:.*|appVersion: "1.27.3"|' charts/foo/Chart.yaml
sed -i 's|tag:.*|tag: "1.27.3"|' charts/bar/values.yaml
sed -i 's|^appVersion:.*|appVersion: "1.27.3"|' charts/bar/Chart.yaml

# 创建并进入nginx-helm-repo目录
mkdir nginx-helm-repo
cd nginx-helm-repo
# 创建foo和bar,charts/foo表示将foo创建到charts目录
helm create charts/foo
helm create charts/bar
# (可选) 使用sed -i命令修改nginx镜像及appVersion字段为1.27.3
sed -i 's|tag:.*|tag: "1.27.3"|' charts/foo/values.yaml
sed -i 's|^appVersion:.*|appVersion: "1.27.3"|' charts/foo/Chart.yaml
sed -i 's|tag:.*|tag: "1.27.3"|' charts/bar/values.yaml
sed -i 's|^appVersion:.*|appVersion: "1.27.3"|' charts/bar/Chart.yaml
# 打包,-d packages/表示将生成的打包文件放到packages目录。如果sed修改了版本号,这里将产生foo-1.27.3.tgz
helm package charts/foo -d packages/      # 得到 packages/foo-0.1.0.tgz
helm package charts/bar -d packages/      # 得到 packages/bar-0.1.0.tgz

(2) 生成仓库索引文件index.html

# --url 指定将来客户端访问的仓库根路径,可以是IP也可以是域名,只要是可达的URL都可以。
helm repo index packages --url http://192.168.88.132
# 结果
ls packages/
foo-0.1.0.tgz  bar-0.1.0.tgz  index.yaml

(3) 搭建http服务
可以使用nginx镜像,创建Deployment和Service,快速搭建http服务。
http服务搭建完成后,将packages目录下的三个文件,拷贝到http服务的根目录。
通过curl的-I参数只看响应头,如果返回200,仓库就搭建完成了。

curl -I http://192.168.88.132/index.yaml
curl -I http://192.168.88.132/foo-0.1.0.tgz

(4) 客户端添加仓库并安装应用

# 任何能连到http://192.168.88.132且有Helm环境的机器上执行
helm repo add myrepo http://192.168.88.132
helm repo update                 # 更新本地索引
helm search repo myrepo          # 能看到 foo/bar
helm install my-foo myrepo/foo   # 部署 foo
helm install my-bar myrepo/bar   # 部署 bar

(5) 更新Chart并重新发布到私有仓库
如果我们想升级foo到0.2.0版本,则可以按下面方法操作。

# 1. 改完Chart后重新打包
helm package charts/foo -d packages/   # 生成 foo-0.2.0.tgz
# 2. 重新生成索引(会自动合并旧版本)
helm repo index packages --url http://192.168.88.132
# 3. 上传到http服务根目录
覆盖http://192.168.88.132/index.yaml
删除http://192.168.88.132/foo-0.1.0.tgz
添加http://192.168.88.132/foo-0.2.0.tgz
# 4. 客户端更新repo缓存
helm repo update
helm search repo myrepo/foo            # 出现 0.2.0
# 5. 客户端更新release实例
helm upgrade my-foo myrepo/foo         # 0.2.0版本将部署到客户端

5 Helm Chart目录结构简介

前面我们使用helm create命令创建了foo和bar两个Chart,每个Chart都是最小可运行的目录(需要修改镜像文件),下面我们看一下创建出的应用的Chart目录结构,以foo为例。
Helm创建出的这个目录官方文档称为Helm scaffold chart,中文常叫做Helm脚手架图表或官方骨架图表。

foo/
├── Chart.yaml                # Chart元数据(名字、版本、描述等)
├── values.yaml               # 默认参数全集(镜像、副本、端口、资源限制…)
├── charts/                   # 子Chart依赖(空目录,helm dep update 后放 tgz)
├── templates/                # K8s模板目录(所有文件会被渲染后apply)
│   ├── _helpers.tpl          # 公共函数:fullname、labels、镜像名等
│   ├── deployment.yaml       # Pod控制器模板
│   ├── service.yaml          # ClusterIP/NodePort Service
│   ├── serviceaccount.yaml   # SA及绑定
│   ├── ingress.yaml          # Ingress路由(默认关闭)
│   ├── hpa.yaml              # 水平自动伸缩(默认关闭)
│   ├── pdb.yaml              # PodDisruptionBudget(默认关闭)
│   └── tests/
│       └── test-connection.yaml # helm test用的Job
└── .helmignore               # 打包时忽略的文件列表(类似.gitignore)

各文件具体作用如下:

  • Chart.yaml 定义Chart名称、版本、维护者、依赖等元数据;version字段决定打包后的文件名。
  • values.yaml 全局“配置文件”,模板里所有{{ .Values.xxx }}都从这里取值;安装时可用–set 或额外yaml覆盖。
  • charts/ 放子Chart的tgz(helm dependency update自动下载);主Chart可复用子Chart模板。
  • _helpers.tpl 用define写可复用片段,例如{{ include “foo.fullname” . }}生成带 Release前缀的资源名。
  • deployment.yaml 核心工作负载模板;副本数、镜像、环境变量、探针、资源限制等均从Values读取。
  • service.yaml 为Pod暴露端口;默认ClusterIP,可通过Values改成NodePort/LoadBalancer。
  • ingress.yaml 域名/路径路由模板,默认enabled: false,打开后自动匹配Service。
  • hpa.yaml 根据CPU/内存或自定义指标自动扩缩容,默认关闭。
  • pdb.yaml 保证升级时最少可用Pod数,默认关闭。
  • serviceaccount.yaml 如果serviceAccount.create=true就生成SA并绑定Role/ClusterRole。
  • test-connection.yaml helm test < release >会运行这个Job,验证服务能否连通。
  • .helmignore 打包时排除.swp、.git、node_modules等无关文件,减小tgz体积。

其中values.yaml、templates/*.yaml和templates/_helpers.tpl后面会经常用到。

接下来,我们先不使用Helm方式部署一些K8s应用,然后再使用Helm方式部署同样的应用,通过对比来学习Helm。

6 不使用Helm方式部署有状态控制器及服务

我们先不使用Helm,部署两个StatefulSet和两个Service,然后再删除这些资源对象。其中一个有状态控制器使用本地存储,另一个有状态控制器使用nfs存储,又为它们分别创建两个对应的LoadBalancer类型Service。
基于本地存储的StatefulSet,使用volumeClaimTemplates模板,指定storageClassName: “”,即手动创建PV。
基于nfs存储的StatefulSet,也使用volumeClaimTemplates模板,但指定storageClassName: nfs-storageclass,PVC和PV将自动创建并绑定。
目录结构如下:

[root@master nginx-no-helm]# ls
nginx-local-loadbalancer-service.yaml  nginx-nfs-loadbalancer-service.yaml  pv0.yaml  pv2.yaml
nginx-local-vct-statefulset.yaml       nginx-nfs-vct-statefulset.yaml       pv1.yaml

各文件作用如下:
基于本地存储的对象

  • pv0.yaml # 手动创建一块PV。
  • pv1.yaml # 手动创建第二块PV。
  • pv2.yaml # 手动创建第三块PV。
  • nginx-local-vct-statefulset.yaml # StatefulSet,由于storageClassName为空,所以只能使用手动创建的PV,上面的PV0,PV1,PV2符合要求。
  • nginx-local-loadbalancer-service.yaml # 为这个基于本地存储的StatefulSet创建Service。

基于网络存储的对象

  • nginx-nfs-vct-statefulset.yaml # 基于nfs存储的StatefulSet,指定storageClassName为nfs-storageclass,PVC和PV将自动创建。
  • nginx-nfs-loadbalancer-service.yaml # 为这个基于nfs存储的StatefulSet创建Service。

以下是创建和删除两个StatefulSet、两个Service的操作步骤:

(1) 编写基于本地存储PV的资源清单文件
手动创建PV的yaml配置,三个PV,name不同,存储目录不同,容量及读写模式相同。

[root@master nginx-no-helm]# cat pv0.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-stateful-local-pv0
spec:
  capacity:
    storage: 400Mi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /opt/k8s-test/12-helm/data/pv0
    type: DirectoryOrCreate
[root@master nginx-no-helm]# cat pv1.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-stateful-local-pv1
spec:
  capacity:
    storage: 400Mi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /opt/k8s-test/12-helm/data/pv1
    type: DirectoryOrCreate
[root@master nginx-no-helm]# cat pv2.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-stateful-local-pv2
spec:
  capacity:
    storage: 400Mi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /opt/k8s-test/12-helm/data/pv2
    type: DirectoryOrCreate

(2) 编写基于本地存储的StatefulSet控制器资源清单文件
基于本地存储的StatefulSet控制器,3个副本,使用volumeClaimTemplates模板选择PV,storageClassName为空,需要手动创建的PV,这些PV由上面的yaml文件创建。

[root@master nginx-no-helm]# cat nginx-local-vct-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx-local-vct-statefulset
  labels:
    app: nginx-local-vct-ss
spec:
  selector:
    matchLabels:
      app: nginx-local-vct-ss
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx-local-vct-ss
    spec:
      containers:
      - name: nginx
        image: nginx:1.27.3
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: local-vct-volume
          mountPath: /usr/share/nginx/html
        ports:
        - containerPort: 80
  volumeClaimTemplates:
    - metadata:
        name: local-vct-volume
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: 400Mi
        storageClassName: ""
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Delete
    whenScaled: Retain

(3) 编写基于本地存储的StatefulSet关联的Service资源清单文件
基于本地存储的StatefulSet关联的Service服务yaml配置,这个Service类型为LB(LoadBalancer)。

[root@master nginx-no-helm]# cat nginx-local-loadbalancer-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-local-loadbalancer-service
  labels:
    app: nginx-local-loadbalancer-svc
spec:
  selector:
    app: nginx-local-vct-ss
  ports:
    - port: 6666
      targetPort: 80
      protocol: TCP
  type: LoadBalancer

(4) 编写基于nfs存储的StatefulSet资源清单文件
基于nfs存储的StatefulSet,使用volumeClaimTemplates模板,不需要手动创建PV。

[root@master nginx-no-helm]# cat nginx-nfs-vct-statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx-nfs-vct-statefulset
  labels:
    app: nginx-nfs-vct-ss
spec:
  selector:
    matchLabels:
      app: nginx-nfs-vct-ss
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx-nfs-vct-ss
    spec:
      containers:
      - name: nginx
        image: nginx:1.27.3
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: nfs-vct-volume
          mountPath: /usr/share/nginx/html
        ports:
        - containerPort: 80
  volumeClaimTemplates:
    - metadata:
        name: nfs-vct-volume
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: 500Mi
        storageClassName: nfs-storageclass
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Delete
    whenScaled: Retain

(5) 编写基于nfs存储的StatefulSet关联的Service资源清单文件
基于nfs存储的StatefulSet关联的Service服务yaml配置,Service类型为LB(LoadBalancer)。

[root@master nginx-no-helm]# cat nginx-nfs-loadbalancer-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-nfs-loadbalancer-service
  labels:
    app: nginx-nfs-loadbalancer-svc
spec:
  selector:
    app: nginx-nfs-vct-ss
  ports:
    - port: 7777
      targetPort: 80
      protocol: TCP
  type: LoadBalancer

(6) 查看集群当前状态

# 查看资源创建前的状态,sts-StatefulSet,pod,pvc,svc-Service,sc-StorageClass。
[root@master nginx-no-helm]# kubectl get statefulset
No resources found in default namespace.
[root@master nginx-no-helm]# kubectl get pod
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-775bc8c8b5-687lq   1/1     Running   0          29h
[root@master nginx-no-helm]# kubectl get pv,pvc
No resources found
[root@master nginx-no-helm]# kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.43.0.1    <none>        443/TCP   16d
# 如果没有nfs-storageclass存储类,需要去前面Storage一章了解存储类StorageClass是如何创建的。
[root@master nginx-no-helm]# kubectl get sc
NAME                      PROVISIONER              RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
local-path (default)      rancher.io/local-path    Delete          WaitForFirstConsumer   false                  16d
nfs-storageclass          nfs-client-provisioner   Delete          Immediate              true                   29h

(7) 创建基于本地存储的StatefulSet和Service
在创建基于本地存储的StatefulSet前,需要先创建三个PV,否则Pod将因为没有合适的PV而一直处于Pending状态。

[root@master nginx-no-helm]# kubectl apply -f pv0.yaml
persistentvolume/nginx-stateful-local-pv0 created
[root@master nginx-no-helm]# kubectl apply -f pv1.yaml
persistentvolume/nginx-stateful-local-pv1 created
[root@master nginx-no-helm]# kubectl apply -f pv2.yaml
persistentvolume/nginx-stateful-local-pv2 created
[root@master nginx-no-helm]# kubectl apply -f nginx-local-vct-statefulset.yaml
statefulset.apps/nginx-local-vct-statefulset created
[root@master nginx-no-helm]# kubectl apply -f nginx-local-loadbalancer-service.yaml
service/nginx-local-loadbalancer-service created

(8) 查看基于本地存储的StatefulSet和Service

# 基于本地存储的StatefulSet的Pod已经处于Running状态
[root@master nginx-no-helm]# kubectl get pod -o wide
NAME                                      READY   STATUS    RESTARTS   AGE   IP            NODE     NOMINATED NODE   READINESS GATES
nfs-client-provisioner-775bc8c8b5-687lq   1/1     Running   0          29h   10.42.0.223   master   <none>           <none>
nginx-local-vct-statefulset-0             1/1     Running   0          20s   10.42.0.178   master   <none>           <none>
nginx-local-vct-statefulset-1             1/1     Running   0          17s   10.42.0.179   master   <none>           <none>
nginx-local-vct-statefulset-2             1/1     Running   0          14s   10.42.0.180   master   <none>           <none>
# Service服务正常。
# 6666是服务自己对外声明的端口,集群外不可访问,集群内可通过ClusterIP 10.43.214.78访问。
# 952是节点监听的NodePort,可以通过<任意节点>:952进行访问,到目前为止我们只有一个节点,多节点集群我们在后面章节会讲到。
# 192.168.88.130是LB分配的外部IP,只要网络可达,集群外可直接访问192.168.88.130:952。
[root@master nginx-no-helm]# kubectl get svc
NAME                               TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)        AGE
kubernetes                         ClusterIP      10.43.0.1      <none>           443/TCP        16d
nginx-local-loadbalancer-service   LoadBalancer   10.43.214.78   192.168.88.130   6666:952/TCP   14s
# 查看PVC,该PVC由模版自动创建,已与三个手动创建的PV绑定
[root@master nginx-no-helm]# kubectl get pvc
NAME                                             STATUS   VOLUME                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
local-vct-volume-nginx-local-vct-statefulset-0   Bound    nginx-stateful-local-pv0   400Mi      RWO                           <unset>                 64s
local-vct-volume-nginx-local-vct-statefulset-1   Bound    nginx-stateful-local-pv1   400Mi      RWO                           <unset>                 61s
local-vct-volume-nginx-local-vct-statefulset-2   Bound    nginx-stateful-local-pv2   400Mi      RWO                           <unset>                 58s
# 查看PV,这三个PV为手动创建,状态为已绑定
[root@master nginx-no-helm]# kubectl get pv
NAME                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                                    STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
nginx-stateful-local-pv0   400Mi      RWO            Retain           Bound    default/local-vct-volume-nginx-local-vct-statefulset-0                  <unset>                          82s
nginx-stateful-local-pv1   400Mi      RWO            Retain           Bound    default/local-vct-volume-nginx-local-vct-statefulset-1                  <unset>                          78s
nginx-stateful-local-pv2   400Mi      RWO            Retain           Bound    default/local-vct-volume-nginx-local-vct-statefulset-2                  <unset>                          75s
# 向本地路径/opt/k8s-test/12-helm/data/pv0、pv1及pv2写入对应的index.html主页,并使用curl访问,可以看到服务已生效
[root@master nginx-no-helm]# curl 192.168.88.130:952
<html><body>Nginx! With k8s helm statefulset local vct pv1!</body></html>
[root@master nginx-no-helm]# curl 192.168.88.130:952
<html><body>Nginx! With k8s helm statefulset local vct pv0!</body></html>
[root@master nginx-no-helm]# curl 192.168.88.130:952
<html><body>Nginx! With k8s helm statefulset local vct pv0!</body></html>
[root@master nginx-no-helm]# curl 192.168.88.130:952
<html><body>Nginx! With k8s helm statefulset local vct pv1!</body></html>
[root@master nginx-no-helm]# curl 192.168.88.130:952
<html><body>Nginx! With k8s helm statefulset local vct pv0!</body></html>
[root@master nginx-no-helm]# curl 192.168.88.130:952
<html><body>Nginx! With k8s helm statefulset local vct pv0!</body></html>
[root@master nginx-no-helm]# curl 192.168.88.130:952
<html><body>Nginx! With k8s helm statefulset local vct pv2!</body></html>
[root@master nginx-no-helm]# curl 192.168.88.130:952
<html><body>Nginx! With k8s helm statefulset local vct pv1!</body></html>

(9) 创建基于nfs存储的StatefulSet和Service

# 创建基于nfs存储的StatefulSet
[root@master nginx-no-helm]# kubectl apply -f nginx-nfs-vct-statefulset.yaml
statefulset.apps/nginx-nfs-vct-statefulset created
# 创建基于nfs存储StatefulSet对应的Service
[root@master nginx-no-helm]# kubectl apply -f nginx-nfs-loadbalancer-service.yaml
service/nginx-nfs-loadbalancer-service created

(10) 查看基于nfs存储的StatefulSet和Service

# nginx-nfs-loadbalancer-service是刚刚创建的新的Service
[root@master nginx-no-helm]# kubectl get svc
NAME                               TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)         AGE
kubernetes                         ClusterIP      10.43.0.1      <none>           443/TCP         16d
nginx-local-loadbalancer-service   LoadBalancer   10.43.214.78   192.168.88.130   6666:952/TCP    82s
nginx-nfs-loadbalancer-service     LoadBalancer   10.43.90.227   192.168.88.130   7777:2991/TCP   8s
# 先通过kubectl describe pv xxx查看nfs存储的真实路径,再向nfs服务器上的这几个路径写入相应的index.html主页。通过Service地址访问主页。
[root@master nginx-no-helm]# curl 192.168.88.130:2991
<html><body>Nginx! With k8s helm statefulset nfs service pv1!</body></html>
[root@master nginx-no-helm]# curl 192.168.88.130:2991
<html><body>Nginx! With k8s helm statefulset nfs service pv2!</body></html>
[root@master nginx-no-helm]# curl 192.168.88.130:2991
<html><body>Nginx! With k8s helm statefulset nfs service pv0!</body></html>
[root@master nginx-no-helm]# curl 192.168.88.130:2991
<html><body>Nginx! With k8s helm statefulset nfs service pv0!</body></html>
[root@master nginx-no-helm]# curl 192.168.88.130:2991
<html><body>Nginx! With k8s helm statefulset nfs service pv0!</body></html>

(11) 资源清理
为了不影响后续实验效果,我们把刚才创建的对象资源全部手动清理掉

# 查看StatefulSet
[root@master nginx-no-helm]# kubectl get statefulset
NAME                          READY   AGE
nginx-local-vct-statefulset   3/3     20m
nginx-nfs-vct-statefulset     3/3     18m
# 批量删除StatefulSet
[root@master nginx-no-helm]# kubectl delete statefulset nginx-local-vct-statefulset nginx-nfs-vct-statefulset
statefulset.apps "nginx-local-vct-statefulset" deleted
statefulset.apps "nginx-nfs-vct-statefulset" deleted
# 删除成功,已经没有对应pod
[root@master nginx-no-helm]# kubectl get pod
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-775bc8c8b5-687lq   1/1     Running   0          29h
# 自动创建的6个PVC也已经自动删除
[root@master nginx-no-helm]# kubectl get pvc
No resources found in default namespace.
# 手动创建的三个PV还存在,自动创建的三个nfs存储的PV已经根据回收机制自动删除了
[root@master nginx-no-helm]# kubectl get pv
NAME                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM                                                    STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
nginx-stateful-local-pv0   400Mi      RWO            Retain           Released   default/local-vct-volume-nginx-local-vct-statefulset-0                  <unset>                          20m
nginx-stateful-local-pv1   400Mi      RWO            Retain           Released   default/local-vct-volume-nginx-local-vct-statefulset-1                  <unset>                          20m
nginx-stateful-local-pv2   400Mi      RWO            Retain           Released   default/local-vct-volume-nginx-local-vct-statefulset-2                  <unset>                          20m
# 手动删除PV
[root@master nginx-no-helm]# kubectl delete pv nginx-stateful-local-pv0 nginx-stateful-local-pv1 nginx-stateful-local-pv2
persistentvolume "nginx-stateful-local-pv0" deleted
persistentvolume "nginx-stateful-local-pv1" deleted
persistentvolume "nginx-stateful-local-pv2" deleted
[root@master nginx-no-helm]# kubectl get pv,pvc
No resources found
# 查看创建的两个服务
[root@master nginx-no-helm]# kubectl get svc
NAME                               TYPE           CLUSTER-IP     EXTERNAL-IP      PORT(S)         AGE
kubernetes                         ClusterIP      10.43.0.1      <none>           443/TCP         16d
nginx-local-loadbalancer-service   LoadBalancer   10.43.214.78   192.168.88.130   6666:952/TCP    20m
nginx-nfs-loadbalancer-service     LoadBalancer   10.43.90.227   192.168.88.130   7777:2991/TCP   19m
# 删除服务
[root@master nginx-no-helm]# kubectl delete svc nginx-local-loadbalancer-service
service "nginx-local-loadbalancer-service" deleted
[root@master nginx-no-helm]# kubectl delete svc nginx-nfs-loadbalancer-service
service "nginx-nfs-loadbalancer-service" deleted
[root@master nginx-no-helm]# kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.43.0.1    <none>        443/TCP   16d

7 使用Helm方式部署有状态控制器及服务

可以看到K8s的资源创建和销毁非常繁琐,接下来,我们看看Helm是如何将这种工作简化的。
我们将使用Helm方式创建基于本地存储的StatefulSet和Service,基于nfs存储的StatefulSet和Service将在下一节以模板分类管理的方式创建。下面的五个小节,先是编写Chart,然后安装应用,接着升级应用,再回滚应用,最后卸载应用,和上一节不使用Helm方式部署有状态控制器及服务流程完全相同,可以对比理解。

7.1 Chart编写

前面我们介绍了Chart,它相当于rpm/deb的包。我们本节先使用helm create命令创建一个Chart,对这个Chart逐步改造,首先把它的功能修改为只创建三个PV,然后拷贝这个Chart,将其功能修改为创建三个PV和一个StatefulSet,最后再拷贝一次,把Chart功能修改为创建三个PV、一个StatefulSet和一个Service。
下面分三个阶段完成Chart的编写:

  • 1、使用Helm创建三个PV
  • 2、使用Helm创建三个PV和一个StatefulSet
  • 3、使用Helm创建三个PV、一个StatefulSet和一个Service
7.1.1 使用Helm安装及卸载三个PV

前面我们通过pv0.yaml/pv1.yaml/pv2.yaml创建了三个PV,这里使用Helm方式创建这三个PV。

(1) 创建Chart
使用helm create nginx-helm-pv创建Chart,删除无用的文件。并在templates目录下创建pv.yaml,最终Chart目录结构如下所示:

[root@master 12-helm]# tree nginx-helm-pv
.
├── charts
├── Chart.yaml
├── templates
│   ├── _helpers.tpl
│   ├── NOTES.txt
│   └── pv.yaml
└── values.yaml

2 directories, 5 files

(2) 定义PV所需变量
在values.yaml尾部追加如下内容:

[root@master 12-helm]# tail -n 8 nginx-helm-pv/values.yaml
# Persistent Volumes
pv:
  - name: nginx-stateful-local-pv0
    path: /opt/k8s-test/12-helm/data/pv0
  - name: nginx-stateful-local-pv1
    path: /opt/k8s-test/12-helm/data/pv1
  - name: nginx-stateful-local-pv2
    path: /opt/k8s-test/12-helm/data/pv2

(3) 编写PV模版文件
模板pv.yaml内容,{{- range }} {{- end }}表示这是一个循环,由于values.yaml定义了三组name/path,因此这里将创建三个PV。
注意{{- end}}前的—不要忽略,它的作用是分割三个不同的PV资源对象,本yaml模板包含三个资源对象。

[root@master 12-helm]# cat nginx-helm-pv/templates/pv.yaml
# templates/pv.yaml
{{- range .Values.pv }}
apiVersion: v1
kind: PersistentVolume
metadata:
  name: {{ .name }}
spec:
  capacity:
    storage: 400Mi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: {{ .path }}
    type: DirectoryOrCreate
---
{{- end }}
[root@master 12-helm]#

(4) 渲染PV模版查看yaml是否正确
模板写完之后,我们可以使用helm template子命令渲染模版查看yaml是否符合我们的要求。
helm template子命令还有–debug和–validate两个选项,这里不再演示。

[root@master 12-helm]# helm template nginx-helm-pv ./nginx-helm-pv --show-only templates/pv.yaml
---
# Source: nginx-statefulset/templates/pv.yaml
# templates/pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-stateful-local-pv0
spec:
  capacity:
    storage: 400Mi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /opt/k8s-test/12-helm/data/pv0
    type: DirectoryOrCreate
---
# Source: nginx-statefulset/templates/pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-stateful-local-pv1
spec:
  capacity:
    storage: 400Mi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /opt/k8s-test/12-helm/data/pv1
    type: DirectoryOrCreate
---
# Source: nginx-statefulset/templates/pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-stateful-local-pv2
spec:
  capacity:
    storage: 400Mi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /opt/k8s-test/12-helm/data/pv2
    type: DirectoryOrCreate

(5) 应用安装
如果渲染出来的yaml没有问题,就可以进行阶段性安装,看是否可以产生三个PV。

# 当前环境没有pv
[root@master 12-helm]# kubectl get pv
No resources found
# helm list用于查看目前已经安装的应用列表,目前为空
[root@master 12-helm]# helm list
NAME    NAMESPACE       REVISION        UPDATED STATUS  CHART   APP VERSION
[root@master 12-helm]# ls
data        nginx-helm-dir          nginx-helm-dir-initc  nginx-helm-pv-ss      nginx-no-helm
helloworld  nginx-helm-dir-include  nginx-helm-pv         nginx-helm-pv-ss-svc
# helm install,安装应用,nginx-helm-pv为应用的名称,./nginx-helm-pv为应用安装时所需各种模板文件所在的目录
[root@master 12-helm]# helm install nginx-helm-pv ./nginx-helm-pv
NAME: nginx-helm-pv
LAST DEPLOYED: Mon Jul 21 14:51:49 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
  export POD_NAME=$(kubectl get pods --namespace default -l "app.kubernetes.io/name=nginx-statefulset,app.kubernetes.io/instance=nginx-helm-pv" -o jsonpath="{.items[0].metadata.name}")
  export CONTAINER_PORT=$(kubectl get pod --namespace default $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
  echo "Visit http://127.0.0.1:8080 to use your application"
  kubectl --namespace default port-forward $POD_NAME 8080:$CONTAINER_PORT
# 应用安装完成,helm list可以看到已经安装的应用列表
[root@master 12-helm]# helm list
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                          APP VERSION
nginx-helm-pv   default         1               2025-07-21 14:51:49.924438234 +0800 CST deployed        nginx-statefulset-0.1.0        1.16.0

(6) 查看PV
可以看到,使用Helm安装应用,成功创建了三个PV:

[root@master 12-helm]# kubectl get pv
NAME                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
nginx-stateful-local-pv0   400Mi      RWO            Retain           Available                          <unset>                          8s
nginx-stateful-local-pv1   400Mi      RWO            Retain           Available                          <unset>                          8s
nginx-stateful-local-pv2   400Mi      RWO            Retain           Available                          <unset>                          8s

(7) 应用卸载
我们需要的是一个StatefulSet和一个Service,现在已经调通了第一步PV创建,先把这个创建PV的应用卸载掉。

# 应用卸载只需要一个helm uninstall子命令
[root@master 12-helm]# helm uninstall nginx-helm-pv
release "nginx-helm-pv" uninstalled
# 已经卸载成功
[root@master 11-helm]# helm list
NAME    NAMESPACE       REVISION        UPDATED STATUS  CHART   APP VERSION
# PV已经被删除
[root@master 12-helm]# kubectl get pv
No resources found
[root@master 12-helm]#
7.1.2 使用Helm创建三个PV和一个StatefulSet

接下来我们在PV基础上增加StatefulSet。把刚才的目录整个拷贝,新目录命名为nginx-helm-pv-ss,后面这个ss是statefulset的缩写。

(1) 定义StatefulSet所需变量
在values.yaml后面追加如下字段:

[root@master 12-helm]# tail -n 13 nginx-helm-pv-ss/values.yaml
# StatefulSet
statefulSet:
  name: nginx-local-vct-statefulset
  labels:
    app: nginx-local-vct-ss
  replicas: 3
  volumeClaim:
    name: local-vct-volume
    size: 400Mi
  volumeClaimPolicy:
    whenDeleted: Delete
    whenScaled: Retain

(2) 创建StatefulSet模板
templates目录新增statefulset.yaml模板文件,模板内引用values.yaml定义的值。
{{ .Values.statefulSet.name }}即values.yaml文件statefulSet字段下name子字段,值为nginx-local-vct-statefulset

[root@master 12-helm]# cat nginx-helm-pv-ss/templates/statefulset.yaml
# templates/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ .Values.statefulSet.name }}
  labels:
    app: {{ .Values.statefulSet.labels.app }}
spec:
  selector:
    matchLabels:
      app: {{ .Values.statefulSet.labels.app }}
  replicas: {{ .Values.statefulSet.replicas }}
  template:
    metadata:
      labels:
        app: {{ .Values.statefulSet.labels.app }}
    spec:
      containers:
      - name: nginx
        image: nginx:1.27.3
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: {{ .Values.statefulSet.volumeClaim.name }}
          mountPath: /usr/share/nginx/html
        ports:
        - containerPort: 80
  volumeClaimTemplates:
    - metadata:
        name: {{ .Values.statefulSet.volumeClaim.name }}
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: {{ .Values.statefulSet.volumeClaim.size }}
        storageClassName: ""
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: {{ .Values.statefulSet.volumeClaimPolicy.whenDeleted }}
    whenScaled: {{ .Values.statefulSet.volumeClaimPolicy.whenScaled }}

(3) 渲染StatefulSet模板查看yaml是否正确
这里可以使用helm template子命令渲染模版,查看是否有错误的地方。
如果在helm template执行时不指定渲染的yaml文件,则会渲染templates目录下所有文件。
如果有些yaml已经渲染过,只想渲染指定yaml,则可以使用--show-only templates/statefulset.yaml选项。

[root@master 12-helm]# helm template nginx-helm-pv-ss ./nginx-helm-pv-ss
---
# Source: nginx-statefulset/templates/pv.yaml
# templates/pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-stateful-local-pv0
spec:
  capacity:
    storage: 400Mi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /opt/k8s-test/12-helm/data/pv0
    type: DirectoryOrCreate
---
# Source: nginx-statefulset/templates/pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-stateful-local-pv1
spec:
  capacity:
    storage: 400Mi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /opt/k8s-test/12-helm/data/pv1
    type: DirectoryOrCreate
---
# Source: nginx-statefulset/templates/pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-stateful-local-pv2
spec:
  capacity:
    storage: 400Mi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: /opt/k8s-test/12-helm/data/pv2
    type: DirectoryOrCreate
---
# Source: nginx-statefulset/templates/statefulset.yaml
# templates/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: nginx-local-vct-statefulset
  labels:
    app: nginx-local-vct-ss
spec:
  selector:
    matchLabels:
      app: nginx-local-vct-ss
  replicas: 3
  template:
    metadata:
      labels:
        app: nginx-local-vct-ss
    spec:
      containers:
      - name: nginx
        image: nginx:1.27.3
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: local-vct-volume
          mountPath: /usr/share/nginx/html
        ports:
        - containerPort: 80
  volumeClaimTemplates:
    - metadata:
        name: local-vct-volume
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: 400Mi
        storageClassName: ""
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: Delete
    whenScaled: Retain

到这里,我们可以使用helm install安装这个应用,将会创建三个PV和一个StatefulSet,这里不再演示。接下来在该Chart下继续添加Service。

7.1.3 使用Helm创建三个PV、一个StatefulSet和一个Service

我们再继续添加service。把刚才的nginx-helm-pv-ss目录整个拷贝,新目录命名为nginx-helm-pv-ss-svc,后面这个svc是service的缩写。
当然也可以不拷贝目录,直接在nginx-helm-pv-ss下新增,这里为了演示阶段效果,特地拷贝。

(1) 定义Service所需变量
在values.yaml后面追加如下字段。

[root@master 12-helm]# tail -n 12 nginx-helm-pv-ss-svc/values.yaml
# Service
service:
  name: nginx-local-loadbalancer-service
  labels:
    app: nginx-local-loadbalancer-svc
  selector:
    app: nginx-local-vct-ss
  ports:
    - port: 6666
      targetPort: 80
      protocol: TCP
  type: LoadBalancer

(2) 编写Service模板

[root@master 12-helm]# cat nginx-helm-pv-ss-svc/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ .Values.service.name }}
  labels:
    app: {{ .Values.service.labels.app }}
spec:
  selector:
    app: {{ .Values.service.selector.app }}
  ports:
  {{- range .Values.service.ports }}
    - port: {{ .port }}
      targetPort: {{ .targetPort }}
      protocol: {{ .protocol }}
  {{- end }}
  type: {{ .Values.service.type }}

(3) 渲染Service模板查看yaml是否正常
只渲染service.yaml模板文件,看上去没有异常。

[root@master 12-helm]# helm template nginx-helm-pv-ss-svc ./nginx-helm-pv-ss-svc/ --show-only templates/service.yaml
---
# Source: nginx-statefulset/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: nginx-local-loadbalancer-service
  labels:
    app: nginx-local-loadbalancer-svc
spec:
  selector:
    app: nginx-local-vct-ss
  ports:
    - port: 6666
      targetPort: 80
      protocol: TCP
  type: LoadBalancer
[root@master 12-helm]#

至此,三个PV、一个StatefulSet和一个Service的Chart文件已全部创建完成,接下来可以使用helm package把这个Chart打包并上传私有仓库。在打包之前,,我们先在本地安装测试一下是否正常。

7.2 应用安装

我们创建了一个Chart,这个Chart可以创建三个PV、一个StatefulSet和一个Service,接下来我们使用Helm命令安装一下,看是否与预期相同。
(1) 安装应用

[root@master 12-helm]# helm install nginx-helm-pv-ss-svc ./nginx-helm-pv-ss-svc
NAME: nginx-helm-pv-ss-svc
LAST DEPLOYED: Mon Jul 21 15:44:14 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
           You can watch its status by running 'kubectl get --namespace default svc -w nginx-helm-pv-ss-svc-nginx-statefulset'
  export SERVICE_IP=$(kubectl get svc --namespace default nginx-helm-pv-ss-svc-nginx-statefulset --template "{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}")
  echo http://$SERVICE_IP:

(2) 查看应用
安装完毕,helm list查看应用,应用名为nginx-helm-pv-ss-svc,已经安装成功。

[root@master 12-helm]# helm list
NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS        CHART                    APP VERSION
nginx-helm-pv-ss-svc    default         1               2025-07-21 15:44:14.261558438 +0800 CST deployed      nginx-statefulset-0.1.0  1.16.0

(3) 查看K8s资源对象

# StatefulSet对象已经创建成功
[root@master 12-helm]# kubectl get statefulset
NAME                          READY   AGE
nginx-local-vct-statefulset   3/3     9s
# 有三个Pod处于运行状态
[root@master 12-helm]# kubectl get pod
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-775bc8c8b5-687lq   1/1     Running   0          31h
nginx-local-vct-statefulset-0             1/1     Running   0          18s
nginx-local-vct-statefulset-1             1/1     Running   0          14s
nginx-local-vct-statefulset-2             1/1     Running   0          11s
# 服务也已经创建完成
[root@master 12-helm]# kubectl get svc
NAME                               TYPE           CLUSTER-IP   EXTERNAL-IP      PORT(S)          AGE
kubernetes                         ClusterIP      10.43.0.1    <none>           443/TCP          16d
nginx-local-loadbalancer-service   LoadBalancer   10.43.29.9   192.168.88.130   6666:18760/TCP   21s

# PVC和PV已创建并绑定
[root@master 12-helm]# kubectl get pvc
NAME                                             STATUS   VOLUME                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
local-vct-volume-nginx-local-vct-statefulset-0   Bound    nginx-stateful-local-pv2   400Mi      RWO                           <unset>                 54s
local-vct-volume-nginx-local-vct-statefulset-1   Bound    nginx-stateful-local-pv0   400Mi      RWO                           <unset>                 50s
local-vct-volume-nginx-local-vct-statefulset-2   Bound    nginx-stateful-local-pv1   400Mi      RWO                           <unset>                 47s
[root@master 12-helm]# kubectl get pv
NAME                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                                    STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
nginx-stateful-local-pv0   400Mi      RWO            Retain           Bound    default/local-vct-volume-nginx-local-vct-statefulset-1                  <unset>                          52s
nginx-stateful-local-pv1   400Mi      RWO            Retain           Bound    default/local-vct-volume-nginx-local-vct-statefulset-2                  <unset>                          52s
nginx-stateful-local-pv2   400Mi      RWO            Retain           Bound    default/local-vct-volume-nginx-local-vct-statefulset-0                  <unset>                          52s

(4) 访问服务
通过kubectl describe pv nginx-stateful-local-pv0得到pv的真实路径,向这几个pv的存储路径下写入不同内容的index.html主页文件,可以看到主页可以访问成功。

[root@master 12-helm]# curl 192.168.88.130:18760
<html><body>Nginx! With k8s helm statefulset local vct pv0!</body></html>
[root@master 12-helm]# curl 192.168.88.130:18760
<html><body>Nginx! With k8s helm statefulset local vct pv0!</body></html>
[root@master 12-helm]# curl 192.168.88.130:18760
<html><body>Nginx! With k8s helm statefulset local vct pv0!</body></html>
[root@master 12-helm]# curl 192.168.88.130:18760
<html><body>Nginx! With k8s helm statefulset local vct pv1!</body></html>
[root@master 12-helm]# curl 192.168.88.130:18760
<html><body>Nginx! With k8s helm statefulset local vct pv2!</body></html>
[root@master 12-helm]# curl 192.168.88.130:18760
<html><body>Nginx! With k8s helm statefulset local vct pv1!</body></html>

7.3 应用升级

使用Helm升级非常方便。我们现在使用的nginx镜像为1.27.3,现在想把它升级成1.27.5镜像(如果没有这个镜像,可以通过docker tag nginx:v1.27.3 nginx:v1.27.5实现,不影响演示),有两种方式,一是修改yaml文件然后执行helm upgrade命令升级,二是执行helm upgrade xxx xxx --set images.tag=xxx命令升级。

7.3.1 修改yaml文件方式升级

先介绍第一种方式。修改templates/statefulset.yaml文件内镜像文件的版本,然后执行helm upgrade命令。下面是使用修改模板升级应用的操作步骤。

(1) 查看当前pod的nginx版本

[root@master 12-helm]# kubectl describe pod nginx-local-vct-statefulset-0
Name:             nginx-local-vct-statefulset-0
(。。。部分内容略。。。)
Controlled By:  StatefulSet/nginx-local-vct-statefulset
Containers:
  nginx:
    Container ID:   docker://0becb99c5651f1b2cb44e75c18d1d7036330cce1e38c6f6a96e84d3a6055f0a1
    Image:          nginx:1.27.3
    Image ID:       docker://sha256:66f8bdd3810c96dc5c28aec39583af731b34a2cd99471530f53c8794ed5b423e
(。。。部分内容略。。。)

(2) 查看当前helm应用状态
查看当前Helm应用的UPDATED时间:

[root@master 12-helm]# helm list
NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS        CHART                    APP VERSION
nginx-helm-pv-ss-svc    default         1               2025-07-21 15:44:14.261558438 +0800 CST deployed      nginx-statefulset-0.1.0  1.16.0

(3) 修改yaml文件内镜像版本
修改nginx-helm-pv-ss-svc/templates/statefulset.yaml文件,将image: nginx:1.27.3改为image: nginx:1.27.5,保存。
注意:镜像名称及镜像版本,属于易变字段,最好存到nginx-helm-pv-ss-svc/values.yaml,templates的yaml引用该值。这里仅用作演示,不影响升级效果。
修改完镜像名称后,执行helm upgrade命令升级应用。

[root@master 12-helm]# helm upgrade nginx-helm-pv-ss-svc ./nginx-helm-pv-ss-svc
Release "nginx-helm-pv-ss-svc" has been upgraded. Happy Helming!
NAME: nginx-helm-pv-ss-svc
LAST DEPLOYED: Mon Jul 21 15:59:36 2025
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
           You can watch its status by running 'kubectl get --namespace default svc -w nginx-helm-pv-ss-svc-nginx-statefulset'
  export SERVICE_IP=$(kubectl get svc --namespace default nginx-helm-pv-ss-svc-nginx-statefulset --template "{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}")
  echo http://$SERVICE_IP:

(4) 升级成功

# 应用的UPDATED时间已变更,REVERSION也变更成了2,如果想要记录本次升级事件,还可以改变CHART和APP VERSION的版本字段,这两个值在nginx-helm-pv-ss-svc/Chart.yaml文件中。
[root@master 12-helm]# helm list
NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS        CHART                    APP VERSION
nginx-helm-pv-ss-svc    default         2               2025-07-21 15:59:36.358752094 +0800 CST deployed      nginx-statefulset-0.1.0  1.16.0
[root@master 12-helm]# date
Mon Jul 21 03:59:48 PM CST 2025
[root@master 12-helm]# kubectl get pod
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-775bc8c8b5-687lq   1/1     Running   0          31h
nginx-local-vct-statefulset-0             1/1     Running   0          79s
nginx-local-vct-statefulset-1             1/1     Running   0          81s
nginx-local-vct-statefulset-2             1/1     Running   0          83s
# 可以看到,镜像已经升级成为1.27.5
[root@master 12-helm]# kubectl describe pod nginx-local-vct-statefulset-0
Name:             nginx-local-vct-statefulset-0
(。。。部分内容略。。。)
Controlled By:  StatefulSet/nginx-local-vct-statefulset
Containers:
  nginx:
    Container ID:   docker://2deb7c3e5a68dfce6a360f3649b762f1a17e73bb6dc1441b966f42f5334d6b17
    Image:          nginx:1.27.5
    Image ID:       docker://sha256:ce663b26176bac0729e88c8008b8dadfc5f164eea15bf7530bf4eed594564b2a
(。。。部分内容略。。。)
Events:
  Type    Reason     Age   From               Message
  ----    ------     ----  ----               -------
  Normal  Scheduled  101s  default-scheduler  Successfully assigned default/nginx-local-vct-statefulset-0 to master
  Normal  Pulled     101s  kubelet            Container image "nginx:1.27.5" already present on machine
  Normal  Created    101s  kubelet            Created container nginx
  Normal  Started    101s  kubelet            Started container nginx
7.3.2 命令行加set参数升级

第二种方式更加简单,直接执行一条helm upgrade命令后面加set参数即可。

# 使用set参数,image.tag为版本。如果还想变更其他配置,可以添加多个--set字段。
[root@master 12-helm]# helm upgrade nginx-helm-pv-ss-svc ./nginx-helm-pv-ss-svc --set image.tag=1.27.5
Release "nginx-helm-pv-ss-svc" has been upgraded. Happy Helming!
NAME: nginx-helm-pv-ss-svc
LAST DEPLOYED: Mon Jul 21 16:29:36 2025
NAMESPACE: default
STATUS: deployed
REVISION: 2
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
           You can watch its status by running 'kubectl get --namespace default svc -w nginx-helm-pv-ss-svc-nginx-statefulset'
  export SERVICE_IP=$(kubectl get svc --namespace default nginx-helm-pv-ss-svc-nginx-statefulset --template "{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}")
  echo http://$SERVICE_IP:
# 升级成功,REVISION变成了2,UPDATED时间也发生了变化
[root@master 12-helm]# helm list
NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
nginx-helm-pv-ss-svc    default         2               2025-07-21 16:29:36.2593325 +0800 CST   deployed        nginx-statefulset-0.1.0 1.16.0

7.4 应用回滚

如果我们升级之后,需要回滚,则可以执行helm rollback命令。

# helm history命令可以查看当前应用的历史版本,superseded是历史版本,deployed是当前正在运行的版本。
[root@master 12-helm]# helm history nginx-helm-pv-ss-svc
REVISION        UPDATED                         STATUS          CHART                   APP VERSION     DESCRIPTION
1               Mon Jul 21 15:44:14 2025        superseded      nginx-statefulset-0.1.0 1.16.0          Install complete
2               Mon Jul 21 15:59:36 2025        superseded      nginx-statefulset-0.1.0 1.16.0          Upgrade complete
3               Mon Jul 21 16:05:20 2025        deployed        nginx-statefulset-0.1.0 1.16.0          Upgrade complete
# 再升级一次版本
[root@master 12-helm]# helm upgrade nginx-helm-pv-ss-svc ./nginx-helm-pv-ss-svc --set image.tag=1.27.5
Release "nginx-helm-pv-ss-svc" has been upgraded. Happy Helming!
NAME: nginx-helm-pv-ss-svc
LAST DEPLOYED: Mon Jul 21 16:09:37 2025
NAMESPACE: default
STATUS: deployed
REVISION: 4
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
           You can watch its status by running 'kubectl get --namespace default svc -w nginx-helm-pv-ss-svc-nginx-statefulset'
  export SERVICE_IP=$(kubectl get svc --namespace default nginx-helm-pv-ss-svc-nginx-statefulset --template "{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}")
  echo http://$SERVICE_IP:
# 可以看到当前REVISION变成了4
[root@master 12-helm]# helm history nginx-helm-pv-ss-svc
REVISION        UPDATED                         STATUS          CHART                   APP VERSION     DESCRIPTION
1               Mon Jul 21 15:44:14 2025        superseded      nginx-statefulset-0.1.0 1.16.0          Install complete
2               Mon Jul 21 15:59:36 2025        superseded      nginx-statefulset-0.1.0 1.16.0          Upgrade complete
3               Mon Jul 21 16:05:20 2025        superseded      nginx-statefulset-0.1.0 1.16.0          Upgrade complete
4               Mon Jul 21 16:09:37 2025        deployed        nginx-statefulset-0.1.0 1.16.0          Upgrade complete
# 想回滚到哪个版本就输入哪个版本序号,如果序号为0则表示回滚到上一个版本
[root@master 12-helm]# helm rollback nginx-helm-pv-ss-svc 0
Rollback was a success! Happy Helming!
# 已经回滚到第三个版本
[root@master 12-helm]# helm history nginx-helm-pv-ss-svc
REVISION        UPDATED                         STATUS          CHART                   APP VERSION     DESCRIPTION
1               Mon Jul 21 15:44:14 2025        superseded      nginx-statefulset-0.1.0 1.16.0          Install complete
2               Mon Jul 21 15:59:36 2025        superseded      nginx-statefulset-0.1.0 1.16.0          Upgrade complete
3               Mon Jul 21 16:05:20 2025        superseded      nginx-statefulset-0.1.0 1.16.0          Upgrade complete
4               Mon Jul 21 16:09:37 2025        superseded      nginx-statefulset-0.1.0 1.16.0          Upgrade complete
5               Mon Jul 21 16:12:15 2025        deployed        nginx-statefulset-0.1.0 1.16.0          Rollback to 3
回到最初的版本
[root@master 12-helm]# helm rollback nginx-helm-pv-ss-svc 1
Rollback was a success! Happy Helming!
已经回到第一个版本
[root@master 12-helm]# helm history nginx-helm-pv-ss-svc
REVISION        UPDATED                         STATUS          CHART                   APP VERSION     DESCRIPTION
1               Mon Jul 21 15:44:14 2025        superseded      nginx-statefulset-0.1.0 1.16.0          Install complete
2               Mon Jul 21 15:59:36 2025        superseded      nginx-statefulset-0.1.0 1.16.0          Upgrade complete
3               Mon Jul 21 16:05:20 2025        superseded      nginx-statefulset-0.1.0 1.16.0          Upgrade complete
4               Mon Jul 21 16:09:37 2025        superseded      nginx-statefulset-0.1.0 1.16.0          Upgrade complete
5               Mon Jul 21 16:12:15 2025        superseded      nginx-statefulset-0.1.0 1.16.0          Rollback to 3
6               Mon Jul 21 16:12:52 2025        deployed        nginx-statefulset-0.1.0 1.16.0          Rollback to 1

7.5 应用卸载

卸载应用使用helm uninstall命令。

# 查看应用名称
[root@master 12-helm]# helm list
NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS        CHART                    APP VERSION
nginx-helm-pv-ss-svc    default         6               2025-07-21 16:12:52.258352194 +0800 CST deployed      nginx-statefulset-0.1.0  1.16.0
# 执行应用卸载命令
[root@master 12-helm]# helm uninstall nginx-helm-pv-ss-svc
release "nginx-helm-pv-ss-svc" uninstalled
# StatefulSet已经不存在
[root@master 12-helm]# kubectl get statefulset
No resources found in default namespace.
# pod已经自动删除
[root@master 12-helm]# kubectl get pod
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-775bc8c8b5-687lq   1/1     Running   0          31h
# Service服务也已经删除
[root@master 12-helm]# kubectl get svc
NAME         TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.43.0.1    <none>        443/TCP   16d
# 没有PVC和PV了
[root@master 12-helm]# kubectl get pvc,pv
No resources found
# 查看应用,列表为空
[root@master 12-helm]# helm list
NAME    NAMESPACE       REVISION        UPDATED STATUS  CHART   APP VERSION
[root@master 12-helm]#

可以看到,引入Helm之后,创建三个PV、一个StatefulSet及一个Service的操作可以被一条简单的helm install命令替代,升级、回滚和卸载也仅用一个命令就可以完成,非常方便快捷。

8 Helm模版文件分组管理

前面我们使用Helm部署了一个基于本地存储的StatefulSet和一个Service,但不使用Helm方式时,我们还部署了一个基于nfs存储的StatefulSet和一个Service。
我们可以为基于nfs存储的StatefulSet创建一个新的Chart,然后helm install,这种方式直观且易于理解,但缺点是管理不便。
接下来,我们使用使用Helm模板文件分组管理的特点在同一个Chart下创建一个基于本地存储的StatefulSet和一个基于nfs存储的StatefulSet,以及两个Service。
分作三个步骤,一是编写或修改模板文件,二是安装应用,三是访问应用。

8.1 编写及修改模板文件

(1) 新建nfs目录
把nginx-helm-pv-ss-svc目录拷贝一份,新目录命名为nginx-helm-dir,我们将在这个目录下新增基于nfs的StatefulSet和Service对象资源文件。
在templates目录下新建nfs目录,创建statefulset.yaml和service.yaml两个文件。
目录结构如下所示:

[root@master 12-helm]# tree nginx-helm-dir
nginx-helm-dir
├── charts
├── Chart.yaml
├── templates
│   ├── _helpers.tpl
│   ├── local
│   │   ├── pv.yaml
│   │   ├── service.yaml
│   │   └── statefulset.yaml
│   ├── nfs
│   │   ├── service.yaml
│   │   └── statefulset.yaml
│   └── NOTES.txt
└── values.yaml

4 directories, 9 files

(2) 修改values.yaml文件
values.yaml这个文件与之前相比,提取了一个公共的common字段,因为这个volumeClaimPolicy在local和nfs这两个statefulset都用到,且值一样。

[root@master 12-helm]# cat nginx-helm-dir/values.yaml
(本文件前面的内容略,由helm create时自动生成)
##############################################
#common
common:
  volumeClaimPolicy:
    whenDeleted: Delete
    whenScaled: Retain

#group1

# Persistent Volumes
pv:
  - name: nginx-stateful-local-pv0
    path: /opt/k8s-test/12-helm/data/pv0
  - name: nginx-stateful-local-pv1
    path: /opt/k8s-test/12-helm/data/pv1
  - name: nginx-stateful-local-pv2
    path: /opt/k8s-test/12-helm/data/pv2

# StatefulSet
statefulSet:
  name: nginx-local-vct-statefulset
  labels:
    app: nginx-local-vct-ss
  replicas: 3
  volumeClaim:
    name: local-vct-volume
    size: 400Mi

# Service
service:
  name: nginx-local-loadbalancer-service
  labels:
    app: nginx-local-loadbalancer-svc
  selector:
    app: nginx-local-vct-ss
  ports:
    - port: 6666
      targetPort: 80
      protocol: TCP
  type: LoadBalancer

##############################################
#group2

# StatefulSet
statefulSet2:
  name: nginx-nfs-vct-statefulset
  labels:
    app: nginx-nfs-vct-ss
  replicas: 3
  volumeClaim:
    name: nfs-vct-volume
    size: 500Mi
    sc: nfs-storageclass

# Service
service2:
  name: nginx-nfs-loadbalancer-service
  labels:
    app: nginx-nfs-loadbalancer-svc
  selector:
    app: nginx-nfs-vct-ss
  ports:
    - port: 7777
      targetPort: 80
      protocol: TCP
  type: LoadBalancer

(3) 修改local目录下statefulset.yaml
本文件最后的persistentVolumeClaimRetentionPolicy字段有调整,原来为直接赋值,现在改为从values.yaml读取。

[root@master 12-helm]# cat nginx-helm-dir/templates/local/statefulset.yaml
# templates/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ .Values.statefulSet.name }}
  labels:
    app: {{ .Values.statefulSet.labels.app }}
spec:
  selector:
    matchLabels:
      app: {{ .Values.statefulSet.labels.app }}
  replicas: {{ .Values.statefulSet.replicas }}
  template:
    metadata:
      labels:
        app: {{ .Values.statefulSet.labels.app }}
    spec:
      containers:
      - name: nginx
        image: nginx:1.27.3
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: {{ .Values.statefulSet.volumeClaim.name }}
          mountPath: /usr/share/nginx/html
        ports:
        - containerPort: 80
  volumeClaimTemplates:
    - metadata:
        name: {{ .Values.statefulSet.volumeClaim.name }}
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: {{ .Values.statefulSet.volumeClaim.size }}
        storageClassName: ""
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: {{ .Values.common.volumeClaimPolicy.whenDeleted }}
    whenScaled: {{ .Values.common.volumeClaimPolicy.whenScaled }}

(4) 编写nfs目录下的statefulset.yaml文件

[root@master 12-helm]# cat nginx-helm-dir/templates/nfs/statefulset.yaml
# templates/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ .Values.statefulSet2.name }}
  labels:
    app: {{ .Values.statefulSet2.labels.app }}
spec:
  selector:
    matchLabels:
      app: {{ .Values.statefulSet2.labels.app }}
  replicas: {{ .Values.statefulSet2.replicas }}
  template:
    metadata:
      labels:
        app: {{ .Values.statefulSet2.labels.app }}
    spec:
      containers:
      - name: nginx
        image: nginx:1.27.3
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: {{ .Values.statefulSet2.volumeClaim.name }}
          mountPath: /usr/share/nginx/html
        ports:
        - containerPort: 80
  volumeClaimTemplates:
    - metadata:
        name: {{ .Values.statefulSet2.volumeClaim.name }}
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: {{ .Values.statefulSet2.volumeClaim.size }}
        storageClassName: {{ .Values.statefulSet2.volumeClaim.sc }}
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: {{ .Values.common.volumeClaimPolicy.whenDeleted }}
    whenScaled: {{ .Values.common.volumeClaimPolicy.whenScaled }}

(5) 编写nfs目录下的service.yaml文件

[root@master 12-helm]# cat nginx-helm-dir/templates/nfs/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ .Values.service2.name }}
  labels:
    app: {{ .Values.service2.labels.app }}
spec:
  selector:
    app: {{ .Values.service2.selector.app }}
  ports:
  {{- range .Values.service2.ports }}
    - port: {{ .port }}
      targetPort: {{ .targetPort }}
      protocol: {{ .protocol }}
  {{- end }}
  type: {{ .Values.service2.type }}

至此,所有的Chart模板文件都已经编写完成,使用helm template命令渲染模板,查看yaml是否有不符合预期的地方,如果yaml文件正确,就可以安装应用了。

8.2 安装应用

如果之前安装过应用,为了不影响本次效果,可以把之前安装的应用通过helm uninstall命令删除。

# 安装应用
[root@master 12-helm]# helm install nginx-helm-dir ./nginx-helm-dir
NAME: nginx-helm-dir
LAST DEPLOYED: Mon Jul 21 16:43:06 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
           You can watch its status by running 'kubectl get --namespace default svc -w nginx-helm-dir-nginx-statefulset'
  export SERVICE_IP=$(kubectl get svc --namespace default nginx-helm-dir-nginx-statefulset --template "{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}")
  echo http://$SERVICE_IP:
# 应用安装成功
[root@master 12-helm]# helm list
NAME            NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                          APP VERSION
nginx-helm-dir  default         1               2025-07-21 16:43:06.840260059 +0800 CST deployed        nginx-statefulset-0.1.0        1.16.0
[root@master 12-helm]#
# 六个Pod均已Running(这里要保需证nfs服务正常,否则Pod将处于Pending状态)
[root@master 12-helm]# kubectl get pod
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-775bc8c8b5-687lq   1/1     Running   0          32h
nginx-local-vct-statefulset-0             1/1     Running   0          15s
nginx-local-vct-statefulset-1             1/1     Running   0          13s
nginx-local-vct-statefulset-2             1/1     Running   0          10s
nginx-nfs-vct-statefulset-0               1/1     Running   0          15s
nginx-nfs-vct-statefulset-1               1/1     Running   0          13s
nginx-nfs-vct-statefulset-2               1/1     Running   0          10s
# 两个StatefulSet已经创建成功
[root@master 12-helm]# kubectl get statefulset
NAME                          READY   AGE
nginx-local-vct-statefulset   3/3     18s
nginx-nfs-vct-statefulset     3/3     18s
# 两个Service也正常运行
[root@master 12-helm]# kubectl get svc
NAME                               TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)          AGE
kubernetes                         ClusterIP      10.43.0.1       <none>           443/TCP          16d
nginx-local-loadbalancer-service   LoadBalancer   10.43.129.166   192.168.88.130   6666:5867/TCP    21s
nginx-nfs-loadbalancer-service     LoadBalancer   10.43.148.150   192.168.88.130   7777:35714/TCP   21s

8.3 访问应用

这里仅以访问其中一个Service为例,可以看到服务正常。

[root@master 12-helm]# curl 192.168.88.130:5867
<html><body>Nginx! With k8s helm statefulset local vct pv2!</body></html>

9 模板文件_helpers.tpl

在Helm中,_helpers.tpl文件是一个非常重要的模板文件,在里面可以定义可复用的模板片段。这些片段可以在Helm Chart的其他模板文件中被多次引用,从而提高模板的可维护性和可读性。
我们前面创建的nginx-helm-dir项目,local/statefulset.yaml和nfs/statefulset.yaml文件,有很多相同的地方。我们可以将相同的地方写到_helpers.tpl,然后引用对应片段即可。
接下来是操作步骤。

(1) 创建模板片段
将nginx-helm-dir整个目录复制一份,新目录命名为nginx-helm-dir-include。
修改_helpers.tpl文件,在文件尾部追加一个spec.PVCRetentionPolicy片段,供后续引用。

[root@master 12-helm]# tail -n 6 nginx-helm-dir-include/templates/_helpers.tpl
{{/* pvc retention policy */}}
{{- define "spec.PVCRetentionPolicy" }}
persistentVolumeClaimRetentionPolicy:
  whenDeleted: {{ .Values.common.volumeClaimPolicy.whenDeleted }}
  whenScaled: {{ .Values.common.volumeClaimPolicy.whenScaled }}
{{- end }}

(2) 修改local/statefulset.yaml文件
在本文件最后的位置,原来的内容,替换成从_helpers.tpl引用。
{{- include "spec.PVCRetentionPolicy" . | indent 2 }},这里面有个indent 2,意思是把前面的字符串spec.PVCRetentionPolicy整体向右缩进2个空格,并在左侧补空格,同时去掉原来左侧多余的空白,保持yaml层级正确。

[root@master 12-helm]# cat nginx-helm-dir-include/templates/local/statefulset.yaml
# templates/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ .Values.statefulSet.name }}
  labels:
    app: {{ .Values.statefulSet.labels.app }}
spec:
  selector:
    matchLabels:
      app: {{ .Values.statefulSet.labels.app }}
  replicas: {{ .Values.statefulSet.replicas }}
  template:
    metadata:
      labels:
        app: {{ .Values.statefulSet.labels.app }}
    spec:
      containers:
      - name: nginx
        image: nginx:1.27.3
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: {{ .Values.statefulSet.volumeClaim.name }}
          mountPath: /usr/share/nginx/html
        ports:
        - containerPort: 80
  volumeClaimTemplates:
    - metadata:
        name: {{ .Values.statefulSet.volumeClaim.name }}
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: {{ .Values.statefulSet.volumeClaim.size }}
        storageClassName: ""
  {{- include "spec.PVCRetentionPolicy" . | indent 2 }}

(3) 修改nfs/statefulset.yaml文件
同local的statefulset一样,在文件最后的位置,原来的内容,替换成从_helpers.tpl引用。

[root@master 12-helm]# cat nginx-helm-dir-include/templates/nfs/statefulset.yaml
# templates/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ .Values.statefulSet2.name }}
  labels:
    app: {{ .Values.statefulSet2.labels.app }}
spec:
  selector:
    matchLabels:
      app: {{ .Values.statefulSet2.labels.app }}
  replicas: {{ .Values.statefulSet2.replicas }}
  template:
    metadata:
      labels:
        app: {{ .Values.statefulSet2.labels.app }}
    spec:
      containers:
      - name: nginx
        image: nginx:1.27.3
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: {{ .Values.statefulSet2.volumeClaim.name }}
          mountPath: /usr/share/nginx/html
        ports:
        - containerPort: 80
  volumeClaimTemplates:
    - metadata:
        name: {{ .Values.statefulSet2.volumeClaim.name }}
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: {{ .Values.statefulSet2.volumeClaim.size }}
        storageClassName: {{ .Values.statefulSet2.volumeClaim.sc }}
  {{- include "spec.PVCRetentionPolicy" . | indent 2 }}

(4) 安装应用
上面的文件修改完成后,开始安装应用。

# 查看当前应用列表
[root@master 12-helm]# helm list
NAME    NAMESPACE       REVISION        UPDATED STATUS  CHART   APP VERSION
# 安装helm应用
[root@master 12-helm]# helm install nginx-helm-dir-include ./nginx-helm-dir-include
NAME: nginx-helm-dir-include
LAST DEPLOYED: Mon Jul 21 16:57:40 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
           You can watch its status by running 'kubectl get --namespace default svc -w nginx-helm-dir-include-nginx-statefulset'
  export SERVICE_IP=$(kubectl get svc --namespace default nginx-helm-dir-include-nginx-statefulset --template "{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}")
  echo http://$SERVICE_IP:

# 安装成功
[root@master 12-helm]# helm list
NAME                      NAMESPACE       REVISION        UPDATED                                 STATUS          CHART                   APP VERSION
nginx-helm-dir-include    default         1               2025-07-21 16:57:40.280563907 +0800 CST deployed        nginx-statefulset-0.1.0 1.16.0

(5) 查看资源对象

# 查看StatefulSet,已经READY。
[root@master 12-helm]# kubectl get statefulset
NAME                          READY   AGE
nginx-local-vct-statefulset   3/3     9s
nginx-nfs-vct-statefulset     3/3     9s
# 查看Service,已经创建
[root@master 12-helm]# kubectl get svc
NAME                               TYPE           CLUSTER-IP      EXTERNAL-IP      PORT(S)         AGE
kubernetes                         ClusterIP      10.43.0.1       <none>           443/TCP         16d
nginx-local-loadbalancer-service   LoadBalancer   10.43.192.126   192.168.88.130   6666:5992/TCP   12s
nginx-nfs-loadbalancer-service     LoadBalancer   10.43.75.141    192.168.88.130   7777:1156/TCP   12s

(6) 访问服务
访问其中一个Service,可以看到服务可被访问。

[root@master 12-helm]# curl 192.168.88.130:5992
<html><body>Nginx! With k8s helm statefulset local vct pv1!</body></html>

(7) 卸载应用
卸载helm应用,清理环境,以便后续实验。

[root@master 12-helm]# helm uninstall nginx-helm-dir-include
release "nginx-helm-dir-include" uninstalled

10 Helm部署过程

在Helm的部署过程中,最重要的阶段是加载(Loading)、渲染(Rendering)和创建对象(Creating Objects)。这三个阶段是Helm部署的核心,直接决定了从模板到实际部署的整个流程。

10.1 加载(Loading)

Helm部署过程的第一步是加载,加载是指Helm从templates/目录中读取所有模板文件到内存的过程,该过程将为后续的渲染做准备。
加载的内容是原始的模板文件,其中包含Go模板语法和占位符,这些内容还没有被替换为具体的值,此时只做纯文件I/O,不会替换任何占位符,也不会生成YAML。

Helm加载模板文件是有顺序的,因为对象资源之间有依赖关系,以及Hooks任务需要优先执行。

  • 依赖关系:某些资源(如 Pod)依赖于ConfigMap、Secret等先被创建,Helm需要按正确顺序渲染这些依赖。
  • Hooks处理:Helm需要优先处理生命周期Hooks(如 pre-install、post-install),确保它们在正确的时间点执行。
10.1.1 Hooks

Helm的Hooks就是在Release生命周期里插队跑任务的一种机制,通过给模板里的Job/Pod/Job/Secret等加上特殊注解,让Helm在安装、升级、删除、回滚前后先执行或后执行这些资源,完成数据库初始化、配置预热、清理脚本等操作。

下表是Hook类型(注解值)与触发时机的总结:

注解值 触发时机 典型用途
pre-install 安装前 建库、建表、拉镜像预热
post-install 安装后 健康检查、通知、缓存加载
pre-upgrade 升级前 备份旧数据、只读锁定
post-upgrade 升级后 数据迁移、验证新版本
pre-delete 删除前 优雅下线、资源解绑
post-delete 删除后 清理外部资源、日志归档
pre-rollback 回滚前 快照、锁定
post-rollback 回滚后 验证回滚结果

下面是一个Job的资源配置清单,该文件作用为安装后进行健康检查(post-install注解)。关于Job这种控制器,将在后面章节讲到。

apiVersion: batch/v1
kind: Job
metadata:
  name: {{ .Release.Name }}-health-check
  annotations:
    "helm.sh/hook": post-install
    "helm.sh/hook-weight": "0"        # 同一阶段多 hook 时排序
    "helm.sh/hook-delete-policy": before-hook-creation,hook-succeeded
spec:
  template:
    spec:
      containers:
      - name: check
        image: busybox:1.36
        command: ['wget', '-O-', 'http://{{ include "mychart.fullname" . }}:80/health']
      restartPolicy: Never

关键注解说明:

  • helm.sh/hook:指定hook类型(这里是post-install,安装后)。
  • helm.sh/hook-weight:权重,优先级,同一阶段有多个hook时,升序执行,值可为负数。
  • helm.sh/hook-delete-policy
    • hook-succeeded:成功后删除
    • hook-failed:失败后删除
    • before-hook-creation:下次执行前删除旧hook(防止残留)

查看 hook 执行日志

# 安装时,使用--debug选项查看日志
helm install myrelease ./mychart --wait --debug
# 安装后,使用下面命令查看,注意标签为managed-by=Helm
kubectl get jobs,pods -l app.kubernetes.io/managed-by=Helm -l helm.sh/hook

Helm hooks机制通过给模板加特殊注解,让Job/Pod在install/upgrade/delete/rollback的前后“插队”运行,完成初始化、备份、校验、清理等一次性任务。

10.1.2 模板文件加载顺序规则

Helm加载模板文件的顺序规则有以下几种。
(1) 按文件名字母顺序规则
Helm会按照templates/目录中文件名的字母顺序加载和渲染模板文件。
例如:

templates/
├── 01-namespace.yaml
├── 02-configmap.yaml
├── 03-deployment.yaml
└── 04-service.yaml

加载顺序是:01-namespace.yaml -> 02-configmap.yaml -> 03-deployment.yaml -> 04-service.yaml。

(2) Hooks优先规则
如果模板文件中包含Hooks(如 pre-install、post-install),Helm会优先处理这些Hooks。
例如:

templates/
├── pre-install-hook.yaml
├── 01-namespace.yaml
├── 02-configmap.yaml
└── 03-deployment.yaml

加载顺序是:pre-install-hook.yaml -> 01-namespace.yaml -> 02-configmap.yaml -> 03-deployment.yaml。

(3) Dependencies依赖先解析规则
如果当前Chart依赖其他Chart,Helm会先解析并安装依赖的Chart,再安装当前Chart。
例如:
假设有以下模板文件结构:

templates/
├── namespace.yaml
├── secret.yaml
├── configmap.yaml
├── serviceaccount.yaml
├── role.yaml
├── deployment.yaml
└── service.yaml

Helm会按照以下顺序加载这些文件:

  1. namespace.yaml
  2. secret.yaml
  3. configmap.yaml
  4. serviceaccount.yaml
  5. role.yaml
  6. deployment.yaml
  7. service.yaml

(4) 自定义加载顺序规则
如果需要控制加载顺序,可以通过文件名前缀来实现。
例如:

templates/
├── 01-namespace.yaml
├── 02-secret.yaml
├── 03-configmap.yaml
├── 04-serviceaccount.yaml
├── 05-role.yaml
├── 06-deployment.yaml
└── 07-service.yaml

10.2 渲染(Rendering)

渲染是Helm部署过程的第二步,加载完成后,Helm开始对加载到内存中的模板文件进行渲染。模板文件加载是Helm从templates/目录中读取模板文件的过程,而模板文件渲染是Helm将模板中的占位符替换为具体值,生成最终的Kubernetes YAML文件的过程。
在渲染过程中,Helm会根据values.yaml文件或命令行参数(–set)提供的值,替换模板中的占位符。
如果模板中有条件语句(if、else)和循环语句(range),渲染还包括根据这些语句动态生成或省略某些资源的过程,前面我们在创建三个PV时就用到了range语句,循环创建三个PV。
模板文件的渲染也有一定顺序,这个顺序取决于第一步的模板文件加载顺序。
渲染的最终目的是生成可以直接被Kubernetes使用的YAML文件,这些文件通过kubectl或helm命令部署到Kubernetes集群中。
渲染过程是Helm的核心功能之一,它提供了灵活性和可定制性,使得Helm能够适应不同的部署环境和需求。

10.2.1 渲染模板的具体用途

(1) 参数化配置
通过values.yaml文件或命令行参数(--set),可以动态地设置模板中的变量值,从而生成不同的配置。这使得同一个Helm Chart可以在不同的环境中重复使用,而无需修改模板文件。

例如,我们有如下templates/deployment.yaml和myvalues.yaml模板文件:

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Values.appName }}
spec:
  replicas: {{ .Values.replicaCount }}
  template:
    spec:
      containers:
      - name: {{ .Values.appName }}
        image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
# myvalues.yaml
appName: myapp
replicaCount: 3
image:
  repository: myregistry/myapp
  tag: 1.0.0

使用命令渲染模板文件:

helm template myrelease ./mychart --values myvalues.yaml

渲染结果:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: 3
  template:
    spec:
      containers:
      - name: myapp
        image: "myregistry/myapp:1.0.0"

如果我们再创建另一个myvalues2.yaml,将其中的字段进行修改,渲染时指定--values myvalues2.yaml,将会渲染出另一个不同的yaml文件。这在多环境多平台场景下非常有用。

(2) 条件渲染
模板支持条件语句,可以根据条件动态生成或省略某些资源。这在不同环境下启用或禁用某些功能时非常有用。
例如,有如下templates/service.yaml和values.yaml文件:

# templates/service.yaml
{{- if .Values.service.enabled }}
apiVersion: v1
kind: Service
metadata:
  name: {{ .Values.appName }}
spec:
  type: {{ .Values.service.type }}
  ports:
  - port: {{ .Values.service.port }}
{{- end }}
# values.yaml
service:
  enabled: true
  type: ClusterIP
  port: 80

如果service.enabled为false,则渲染时不会生成Service资源,如果需要生成Service资源,只需要修改values.yaml的service.enabled字段为true即可。

(3) 循环渲染
模板支持循环语句,可以根据列表或字典动态生成多个资源。这在需要创建多个相似资源时非常方便。
例如:ConfigMap有多个key/values的场景:

# templates/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ .Values.appName }}
data:
{{- range .Values.config }}
  {{ .key }}: {{ .value }}
{{- end }}
# values.yaml
config:
  - key: "log_level"
    value: "info"
  - key: "max_connections"
    value: "100"

渲染结果:

apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp
data:
  log_level: info
  max_connections: 100

我们在本章创建三个PV的时候,就用到了range循环语句。

(4) 复用公共片段
本章之前已经介绍过_helpers.tpl这个文件。通过_helpers.tpl文件,可以定义公共的模板片段,这些片段可以在多个模板文件中复用。这有助于减少重复代码,保持模板的一致性。
例如,在_helpers.tpl中定义mychart.fullname片段,在deployment.yaml引用这个片段。

# templates/_helpers.tpl
{{- define "mychart.fullname" -}}
{{- printf "%s-%s" .Release.Name .Chart.Name }}
{{- end }}

Deployment引用_helpers.tpl片段:

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "mychart.fullname" . }}

Helm渲染过程将模板文件中的占位符替换为具体的值,生成最终的Kubernetes YAML文件,从而实现参数化、条件化、循环化和复用化,提高部署的灵活性和可定制性。

10.3 创建对象(Creating Objects)

创建对象是Helm部署过程的第三步。创建对象过程是指Helm将渲染后的YAML文件发送到Kubernetes集群,由Kubernetes API Server创建实际的资源对象的过程。

10.3.1 创建对象具体流程

(1) 构建目标清单
Helm客户端把渲染得到的YAML文件拆成“目标状态”对象列表(unstructured.Unstructured)。

(2)对比现有状态(3-way merge)

  • 旧版本(live state)
  • 新版本(rendered state)
  • 用户显式修改(user live state)

生成最小差异补丁。

(3) 批量apply/patch
按K8s默认顺序(Namespace -> ConfigMap -> RBAC -> Workload -> Service -> Ingress …)并发/串行调用REST API,对Deployment等会触发滚动更新。

(4) 等待就绪(如果有–wait选项)
如果加了--wait/--wait-for-jobs选项,Helm会轮询Deployment、StatefulSet、Job直到readyReplicas==desired或超时。

10.3.2 创建对象顺序

创建对象顺序指的是Kubernetes在部署应用时,按照特定的顺序创建各种资源对象。这个顺序非常重要,因为它确保了资源之间的依赖关系能够正确地被满足,从而保证应用能够正常运行。
在Kubernetes中,某些资源对象之间存在依赖关系。例如:

  • Pod可能依赖于ConfigMap和Secret来获取配置信息和敏感数据。
  • Deployment或StatefulSet可能依赖于Service来暴露服务。
  • Pod可能需要PersistentVolumeClaim来请求持久化存储。

如果这些依赖关系没有被正确处理,可能会导致部署失败或应用无法正常运行。因此,Kubernetes和Helm都会遵循一定的顺序来创建资源。

Helm在创建资源时,会遵循Kubernetes的资源依赖规则,但由于Helm支持Hooks机制,因此会把Hooks放在对应阶段执行。Helm资源创建的其余执行顺序和Kubernetes完全一致,即Namespace -> ConfigMap -> RBAC -> Workload(Pod、Deployment、StatefulSet) -> Service -> Ingress等。
(1) Hooks
Helm支持在生命周期的不同阶段执行特定的Job(如 pre-installpost-installpre-upgrade 等)。这些Hooks会在相应阶段被优先执行。
(2) Namespace
如果模板中包含Namespace资源,Helm会首先创建Namespace。
(3) Resource Quotas和Limit Ranges
这些资源会限制Pod的资源使用,因此需要在创建其他资源之前定义。
(4) Secrets和ConfigMaps
这些资源通常被其他资源引用,因此需要先创建。
(5) StorageClasses和PersistentVolumeClaims
如果有持久化存储需求,这些资源需要先创建,因为Pod可能依赖它们。
(6) ServiceAccounts
如果有自定义的ServiceAccount,需要在创建Pod之前定义。
(7) Roles和ClusterRoles
如果有RBAC配置,这些资源需要在创建Pod之前定义。
(8) Pods、Deployments、StatefulSets等
这些资源依赖于上述资源,因此会在最后创建。
(9) Service、Ingress等。
如果有这些资源,将在Workload(Pods、Deployments、StatefulSets)创建完后再创建。

假设有以下模板文件结构:

templates/
├── namespace.yaml
├── secret.yaml
├── configmap.yaml
├── serviceaccount.yaml
├── role.yaml
├── deployment.yaml
└── service.yaml

Helm 将会按照以下顺序加载和创建资源:

  1. namespace.yaml
  2. secret.yaml
  3. configmap.yaml
  4. serviceaccount.yaml
  5. role.yaml
  6. deployment.yaml
  7. service.yaml

如果需要控制资源的创建顺序,可以通过文件名前缀来实现。例如:

templates/
├── 01-namespace.yaml
├── 02-secret.yaml
├── 03-configmap.yaml
├── 04-serviceaccount.yaml
├── 05-role.yaml
├── 06-deployment.yaml
└── 07-service.yaml

但要注意的是,如果先创建的资源对象依赖于后创建的资源对象,则会创建失败。举例来说,如果把deployment.yaml放在01位置,但deployment依赖namespace.yaml,由于命名空间还没有创建,此时deployment将创建失败。

10.4 Helm部署过程小结

如果详细展开,Helm部署过程可以包括准备、加载、渲染、创建对象、生命周期、验证、回滚、清理和监控维护等多个阶段。加载、渲染和创建对象是其中最重要的三个阶段,是Helm部署的核心,直接决定了从模板到实际部署的整个流程,因此本节我们重点介绍了它们三个。

11 综合示例:Helm方式部署可控制Pod启动顺序的应用

随着我们的容器越来越多,容器间的依赖关系也变得复杂起来。如果一个容器依赖另一个容器,那么它们启动就有了先后顺序。如何让容器按照我们想要的顺序启动呢,这便是我们这节将要探讨的问题。

11.1 认识初始化容器initContainers

这里介绍一下initContainers。在Kubernetes中,initContainers(初始化容器)是一种特殊的容器类型,常用于在Pod中的应用程序容器启动之前执行一些初始化任务。
假设nginx Pod依赖redis Pod,我们可以在nginx Pod的yaml文件,增加一个initContainers字段,先运行一个busybox容器,在这个容器内执行until nc -z redis:6379; do sleep 2; done持续探测redis是否就绪来检查前序依赖,只有前序依赖的Pod就绪,本Pod才启动。

11.2 带启动顺序控制功能的Helm Chart

接下来,我们计划让基于nfs存储的statefulset先启动,在基于本地存储的statefulset检查某个目录是否存在local-ready.txt文件,如果存在,则启动statefulset,否则一直等待。这样就可以保证nfs类容器在本地类容器之前启动。由于基于本地PV存储的StatefulSet和Pod启动过程需要手动干预,我们可以更直观地观察到效果。

(1) 修改local/statefulset.yaml Chart模板文件
将之前的nginx-helm-dir拷贝一份,新项目命名为nginx-helm-dir-initc。
只修改nginx-helm-dir-initc/templates/local/statefulset.yaml文件
修改前后对比如下:
原文件

[root@master 12-helm]# cat nginx-helm-dir/templates/local/statefulset.yaml
# templates/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ .Values.statefulSet.name }}
  labels:
    app: {{ .Values.statefulSet.labels.app }}
spec:
  selector:
    matchLabels:
      app: {{ .Values.statefulSet.labels.app }}
  replicas: {{ .Values.statefulSet.replicas }}
  template:
    metadata:
      labels:
        app: {{ .Values.statefulSet.labels.app }}
    spec:
      containers:
      - name: nginx
        image: nginx:1.27.3
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: {{ .Values.statefulSet.volumeClaim.name }}
          mountPath: /usr/share/nginx/html
        ports:
        - containerPort: 80
  volumeClaimTemplates:
    - metadata:
        name: {{ .Values.statefulSet.volumeClaim.name }}
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: {{ .Values.statefulSet.volumeClaim.size }}
        storageClassName: ""
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: {{ .Values.common.volumeClaimPolicy.whenDeleted }}
    whenScaled: {{ .Values.common.volumeClaimPolicy.whenScaled }}

修改后的文件

[root@master 12-helm]# cat nginx-helm-dir-initc/templates/local/statefulset.yaml
# templates/statefulset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: {{ .Values.statefulSet.name }}
  labels:
    app: {{ .Values.statefulSet.labels.app }}
spec:
  selector:
    matchLabels:
      app: {{ .Values.statefulSet.labels.app }}
  replicas: {{ .Values.statefulSet.replicas }}
  template:
    metadata:
      labels:
        app: {{ .Values.statefulSet.labels.app }}
    spec:
      initContainers: # <-------------新增字段
      - name: busybox
        image: busybox:1.34.1
        command: ["/bin/sh", "-c", "while [ ! -f /data/local-ready.txt ]; do echo waiting for local-ready.txt; sleep 2; done; echo local-ready.txt found, proceeding with the next steps"]
        volumeMounts:
        - name: busybox-data
          mountPath: /data
      volumes:
      - name: busybox-data
        hostPath:
          path: /opt/k8s-test/12-helm/data/busybox-data
          type: Directory
      containers:
      - name: nginx
        image: nginx:1.27.3
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - name: {{ .Values.statefulSet.volumeClaim.name }}
          mountPath: /usr/share/nginx/html
        ports:
        - containerPort: 80
  volumeClaimTemplates:
    - metadata:
        name: {{ .Values.statefulSet.volumeClaim.name }}
      spec:
        accessModes: [ "ReadWriteOnce" ]
        resources:
          requests:
            storage: {{ .Values.statefulSet.volumeClaim.size }}
        storageClassName: ""
  persistentVolumeClaimRetentionPolicy:
    whenDeleted: {{ .Values.common.volumeClaimPolicy.whenDeleted }}
    whenScaled: {{ .Values.common.volumeClaimPolicy.whenScaled }}

可以看到新的statefulset.yaml文件,增加了initContainers字段。这里需要注意一下,images和command为可变字段,随着版本需要升级值可能会发生变化,最好写到values.yaml中,本次为了演示方便,直接写入到statefulset.yaml中了。
initContainers启动个busybox容器,并持续检查/data/local-ready.txt文件是否存在,如果存在,则退出检查,继续创建statefulset,否则每隔2秒打印一个提示信息。
while [ ! -f /data/local-ready.txt ]; do echo waiting for local-ready.txt; sleep 2; done; echo local-ready.txt found, proceeding with the next steps",这一段作用是每隔2秒检查local-ready.txt文件是否存在。
通过statefulset.yaml文件可以看出,busybox的/data目录,在宿主机上的实际路径为/opt/k8s-test/12-helm/data/busybox-data,后面我们只需要在这个目录创建local-ready.txt即可进行实验。

(2) 安装应用

# 确认之前示例的Helm应用都已经删除
[root@master 12-helm]# helm list
NAME    NAMESPACE       REVISION        UPDATED STATUS  CHART   APP VERSION
# 安装带init containers的应用
[root@master 12-helm]# helm install nginx-helm-dir-initc ./nginx-helm-dir-initc
NAME: nginx-helm-dir-initc
LAST DEPLOYED: Mon Jul 21 17:22:38 2025
NAMESPACE: default
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
1. Get the application URL by running these commands:
     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
           You can watch its status by running 'kubectl get --namespace default svc -w nginx-helm-dir-initc-nginx-statefulset'
  export SERVICE_IP=$(kubectl get svc --namespace default nginx-helm-dir-initc-nginx-statefulset --template "{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}")
  echo http://$SERVICE_IP:
# 安装成功
[root@master 12-helm]# helm list
NAME                    NAMESPACE       REVISION        UPDATED                                 STATUS        CHART                    APP VERSION
nginx-helm-dir-initc    default         1               2025-07-21 17:22:38.573955375 +0800 CST deployed      nginx-statefulset-0.1.0  1.16.0

(3) 查看资源对象

# StatefulSet已经创建成功,但Pod还在陆续创建
[root@master 12-helm]# kubectl get statefulset
NAME                          READY   AGE
nginx-local-vct-statefulset   0/3     23s
nginx-nfs-vct-statefulset     2/3     23s
[root@master 12-helm]#
[root@master 12-helm]#
# 等了一段时间,nginx-nfs-vct-statefulset的三个Pod陆续创建成功,但nginx-local-vct-statefulset的Pod仍未创建成功。
[root@master 12-helm]# kubectl get statefulset
NAME                          READY   AGE
nginx-local-vct-statefulset   0/3     24s
nginx-nfs-vct-statefulset     3/3     24s
# 查看Pod发现处于init:0/1状态
[root@master 12-helm]# kubectl get pod
NAME                                      READY   STATUS     RESTARTS   AGE
nfs-client-provisioner-775bc8c8b5-687lq   1/1     Running    0          33h
nginx-local-vct-statefulset-0             0/1     Init:0/1   0          39s
nginx-nfs-vct-statefulset-0               1/1     Running    0          39s
nginx-nfs-vct-statefulset-1               1/1     Running    0          36s
nginx-nfs-vct-statefulset-2               1/1     Running    0          33s
# 查看Pod详情,可以看到本Pod有Init Containers。
[root@master 12-helm]# kubectl describe pod nginx-local-vct-statefulset-0
Name:             nginx-local-vct-statefulset-0
Namespace:        default
Priority:         0
Service Account:  default
Node:             master/192.168.88.130
Start Time:       Mon, 21 Jul 2025 17:22:40 +0800
Labels:           app=nginx-local-vct-ss
                  apps.kubernetes.io/pod-index=0
                  controller-revision-hash=nginx-local-vct-statefulset-f8c5fcd4f
                  statefulset.kubernetes.io/pod-name=nginx-local-vct-statefulset-0
Annotations:      <none>
Status:           Pending
IP:               10.42.0.216
IPs:
  IP:           10.42.0.216
Controlled By:  StatefulSet/nginx-local-vct-statefulset
Init Containers: # 正常Pod没有这个字段,只有在yaml文件添加initContainers字段这里才会出现。
  busybox:
    Container ID:  docker://85cfcfaa3e1472fd7a8b5d6d4d278ace2a2044b65f44b53092c8db1b94c3cfe8
    Image:         busybox:1.34.1
    Image ID:      docker://sha256:beae173ccac6ad749f76713cf4440fe3d21d1043fe616dfbe30775815d1d0f6a
    Port:          <none>
    Host Port:     <none>
    Command:
      /bin/sh
      -c
      while [ ! -f /data/local-ready.txt ]; do echo waiting for local-ready.txt; sleep 2; done; echo local-ready.txt found, proceeding with the next steps
    State:          Running
      Started:      Mon, 21 Jul 2025 17:22:40 +0800
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /data from busybox-data (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-jh2x7 (ro)
Containers:
  nginx:
    Container ID:
    Image:          nginx:1.27.3
    Image ID:
    Port:           80/TCP
    Host Port:      0/TCP
    State:          Waiting
      Reason:       PodInitializing
    Ready:          False
    Restart Count:  0
    Environment:    <none>
    Mounts:
      /usr/share/nginx/html from local-vct-volume (rw)
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-jh2x7 (ro)
Conditions:
  Type                        Status
  PodReadyToStartContainers   True
  Initialized                 False
  Ready                       False
  ContainersReady             False
  PodScheduled                True
Volumes:
  local-vct-volume:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  local-vct-volume-nginx-local-vct-statefulset-0
    ReadOnly:   false
  busybox-data:
    Type:          HostPath (bare host directory volume)
    Path:          /opt/k8s-test/12-helm/data/busybox-data
    HostPathType:  Directory
  kube-api-access-jh2x7:
    Type:                    Projected (a volume that contains injected data from multiple sources)
    TokenExpirationSeconds:  3607
    ConfigMapName:           kube-root-ca.crt
    ConfigMapOptional:       <nil>
    DownwardAPI:             true
QoS Class:                   BestEffort
Node-Selectors:              <none>
Tolerations:                 node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
                             node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
  Type     Reason            Age   From               Message
  ----     ------            ----  ----               -------
  Warning  FailedScheduling  57s   default-scheduler  0/1 nodes are available: pod has unbound immediate PersistentVolumeClaims. preemption: 0/1 nodes are available: 1 Preemption is not helpful for scheduling.
  Normal   Scheduled         56s   default-scheduler  Successfully assigned default/nginx-local-vct-statefulset-0 to master
  Normal   Pulled            56s   kubelet            Container image "busybox:1.34.1" already present on machine
  Normal   Created           56s   kubelet            Created container busybox
  Normal   Started           56s   kubelet            Started container busybox
# 查看日志,发现日志中没有太多信息
[root@master 12-helm]# kubectl logs nginx-local-vct-statefulset-0
Defaulted container "nginx" out of: nginx, busybox (init)
Error from server (BadRequest): container "nginx" in pod "nginx-local-vct-statefulset-0" is waiting to start: PodInitializing
# 对于有initContainers的pod,想要看到初始化容器的日志,需要加-c选项。这和多容器Pod中查看副容器日志的选项相同。
[root@master 12-helm]# kubectl logs nginx-local-vct-statefulset-0 -c busybox
waiting for local-ready.txt
waiting for local-ready.txt
(。。。中间重复内容略。。。可以看到有很多每隔2秒的打印,容器正在等待local-ready.txt文件创建)
waiting for local-ready.txt
# 目录下没有该文件
[root@master 12-helm]# ls -la /opt/k8s-test/12-helm/data/busybox-data/
total 8.0K
drwxr-xr-x. 2 root root 4.0K Jul 21 13:01 .
drwxr-xr-x. 6 root root 4.0K Jul 21 12:17 ..
# 创建local-ready.txt文件
[root@master 12-helm]# touch /opt/k8s-test/12-helm/data/busybox-data/local-ready.txt
# 已经检测到文件存在,init container退出,继续执行nginx容器。
[root@master 12-helm]# kubectl logs -f nginx-local-vct-statefulset-0 -c busybox
waiting for local-ready.txt
(。。。中间重复内容略,每隔2秒一次的打印)
waiting for local-ready.txt
local-ready.txt found, proceeding with the next steps
# 可以看到所有Pod都正常运行
[root@master 12-helm]# kubectl get pod
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-775bc8c8b5-687lq   1/1     Running   0          33h
nginx-local-vct-statefulset-0             1/1     Running   0          3m
nginx-local-vct-statefulset-1             1/1     Running   0          14s
nginx-local-vct-statefulset-2             1/1     Running   0          10s
nginx-nfs-vct-statefulset-0               1/1     Running   0          3m
nginx-nfs-vct-statefulset-1               1/1     Running   0          2m57s
nginx-nfs-vct-statefulset-2               1/1     Running   0          2m54s
# StatefulSet状态也正常
[root@master 12-helm]# kubectl get statefulset
NAME                          READY   AGE
nginx-local-vct-statefulset   3/3     3m8s
nginx-nfs-vct-statefulset     3/3     3m8s
[root@master 12-helm]#

11.3 多依赖检查

前面通过一个实验证明了可以使用initContainers控制Pod启动顺序。如果一个Pod依赖多个Pod,可以在command中添加多个检查点。

    spec:
      initContainers:
      - name: busybox
        image: busybox:1.34.1
        command: ["/bin/sh", "-c",
          "while [ ! -f /data/common-ready.txt ]; do echo waiting for common-ready.txt; sleep 2; done;
           echo common-ready.txt found, proceeding with the next steps;
           while [ ! -f /data/local-ready.txt ]; do echo waiting for local-ready.txt; sleep 2; done;
           echo local-ready.txt found, proceeding with the next steps"]
        volumeMounts:
        - name: busybox-data
          mountPath: /data
      volumes:
      - name: busybox-data
        hostPath:
          path: /opt/k8s-test/12-helm/data/busybox-data
          type: Directory

修改完之后更新helm应用,-f持续查看logs。

[root@master 12-helm]# kubectl logs nginx-local-vct-statefulset-0 -c busybox -f
waiting for common-ready.txt
waiting for common-ready.txt
(...)
waiting for common-ready.txt
common-ready.txt found, proceeding with the next steps # 创建common-ready.txt后,将继续等待第二个条件,即local-ready.txt
waiting for local-ready.txt
(...)
waiting for local-ready.txt
waiting for local-ready.txt
local-ready.txt found, proceeding with the next steps  # 创建local-ready.txt后,第二个条件满足,init容器退出
Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐