Kubernetes技术详解-从理论到实践-(12)-包管理器-Helm
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/bitnamihelm repo update
(3) 一键部署应用
以部署MySQL为例,自定义root密码。helm install mydb bitnami/mysql --set auth.rootPassword=secret123
(4) 查看、升级、回滚helm listhelm upgrade mydb bitnami/mysql --set auth.rootPassword=newpwdhelm rollback mydb 1helm 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.yamlsed -i 's|^appVersion:.*|appVersion: "1.27.3"|' charts/foo/Chart.yamlsed -i 's|tag:.*|tag: "1.27.3"|' charts/bar/values.yamlsed -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会按照以下顺序加载这些文件:
- namespace.yaml
- secret.yaml
- configmap.yaml
- serviceaccount.yaml
- role.yaml
- deployment.yaml
- 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-install、post-install、pre-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 将会按照以下顺序加载和创建资源:
- namespace.yaml
- secret.yaml
- configmap.yaml
- serviceaccount.yaml
- role.yaml
- deployment.yaml
- 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容器退出
更多推荐


所有评论(0)