什么是 StatefulSet StatefulSet
是用来管理有状态的应用,例如数据库,consul,zookeeper等集群。
通过Deployment
部署的应用,都是不需要存储数据,不需要记住状态且pod之间没有任何依赖关系,可以随意扩充副本,每个副本都是一样的,可替代的。
而像数据库、Redis、kafka、consul这类有状态的,则不能随意扩充副本。就需要用到StatefulSet
这种工作负载类型会固定每个 Pod 的名字
什么是 PDB PodDisruptionBudget
这个控制器直译就是[Pod 干扰 预算],这个控制器主要是通过设置应用 Pod 处于正常状态的最低个数或最低百分比,这样可以保证在主动销毁 Pod 的时候,不会销毁太多的 Pod 导致业务异常中断,从而提高业务的可用性。
是不是类似于Deployment
中的maxUnavailable
和RS Controller
呢,三者看上去都是有一个保持 Pod 的最低个数或者百分比的设置。其实后两个并不能给你保证集群中始终有几个副本的,他们只是让实际副本数跟你的期望副本数尽快的一致,但这个过程中的副本数量并不关心。所以这个时候就要考虑使用 PDB 了,对那些Voluntary Disruption(自愿中断)做好Budgets(预算)。
Involuntary AND Voluntary 对 Voluntary Disruption 的情况,我们可以使用PDB做预算。哪些情况是 Involuntary Disruption,哪些又是 Voluntary Disruption 呢?
Involuntary Disruption:
服务器硬件故障或者内核崩溃导致节点宕了;
如果节点是 KVM,Xen 虚拟机,虚拟机被删了或者 KVM,Xen 崩了;
集群网络脑裂;
某个节点因为不合理的超配导致出现计算资源不足时,触发了 kubelet eviction; 这些都是 Kubernetes 不可控的情况,这些情况下不适用于PDB。
Voluntary Disruption:
删除 Deployment,RC,StatefulSet控制器
更新了 Pod 模版,触发 Pod 滚动更新
批量删除Pod
清空节点
下线一个节点
PDB 关键参数与注意事项
.spec.minAvailable
表示发生自愿中断的过程中,要保证至少可用的Pods数或者比例
.spec.maxUnavailable
表示发生自愿中断的过程中,要保证最大不可用的Pods数或者比例 上面配置只能用来对应 Deployment,RS,RC,StatefulSet的Pods,推荐优先使用 .spec.maxUnavailable
。
注意:
同一个 PDB Object 中不能同时定义 .spec.minAvailable
和 .spec.maxUnavailable
。
前面提到,应用滚动更新时Pod的delete和unavailable虽然也属于自愿中断,但是实际上滚动更新有自己的策略控制(marSurge 和 maxUnavailable),因此PDB不会干预这个过程。
PDB 只能保证自愿中断时的副本数,比如 eviction pod过程中刚好满足 .spec.minAvailable
或.spec.maxUnavailable
,这时某个本来正常的Pod突然因为Node Down(非自愿中断)挂了,那么这个时候实际Pods数就比PDB中要求的少了,因此PDB不是万能的!
使用上,如果设置 .spec.minAvailable
为 100% 或者 .spec.maxUnavailable
为 0%,意味着会完全阻止 eviction pods 的过程( Deployment和StatefulSet的滚动更新除外 )。
PDB for Zookeeper 1 2 3 4 5 6 7 8 9 10 11 12 apiVersion: policy/v1beta1 kind: PodDisruptionBudget metadata: name: zk-pdb namespace: baseservice spec: selector: matchLabels: app: zk maxUnavailable: 1
Service和DNS的关系 在kubernetes中,所有的Service和Pod都会被分配一条对应的DNS A记录( 通过域名解析到IP地址的记录 )
ClusterIP Service的DNS分配方式 ClusterIP模式的NormalService 来说,它的 A 记录的格式是:myservicename.mynamespace.svc.cluster.local
当你访问这条 A 记录的时候,它解析到的就是该Service的VIP地址
Headliness Service的DNS分配方式 ClusterIP=None的 HeadlessService 来说,它的 A 记录的格式也是:myservicename.mynamespace.svc.cluster.local
当你访问这条A记录的时候,它返回的是所有被代理的Pod的IP地址的集合.当然,如果你的客户端没办法解析这个集合的话,它可能会只会拿到第一个Pod的IP地址
ClusterIP Service被代理Pod的分配方式 对于ClusterIP模式的Service来说,它代理的Pod被自动分配的A记录的格式是:PodIP.mynamespace.pod.cluster.local
这条记录指向Pod的IP地址
Headliness Service被代理Pod的分配方式
对Headless Service 来说,它代理的Pod被自动分配的A记录的格式是:mypodname.myservicename.mynamespace.svc.cluster.local
这条记录也指向Pod的IP地址
如果Pod本身声明了hostname和subdomain字段,那么这时候Pod的A记录就会变成: podhostname.mysubdomain.mynamespace.svc.cluster.local
什么是 HeadLess Normal和Headless对比 写法方面:
工作方式对比 NormalService 工作方式是通过一个Cluster-ip 10.0.0.112:80
来反向代理 endpoints 列表中的 pod 地址
1 10.244.4.10:80 10.244.6.160:80 10.244.8.32:80
此时要访问apiA这个接口只需要访问到这个Cluster-ip,就可以实现对后端POD的负载均衡
HeadlessService 工作方式是并不会分配 Cluster IP,kube-proxy 不会处理它们,而且平台也不会为它们进行负载均衡和路由。 DNS 如何实现自动配置,依赖于 Service 是否定义了 selector。 如下图:
此时要访问zookeeper这个集群,需要按照<pod-name>.<service-name>.<namespace-name>..svc.cluster.local
就可以访问到zk集群中指定pod,相当于给pod分配了一个可解析身份,非常适合用作StatefulSet
类型工作负载中pod之间进行内部通信。
HeadLess的作用 简而言之Headless Service
就是没头的Service。有啥用呢?很简单,有时候client想自己来决定使用哪个Real Server时可以通过查询DNS来获取Real Server的信息。Headless Service的对应的每一个Endpoints,即每一个Pod,都会有对应的DNS域名;这样Pod之间就可以互相访问。
Zookeeper容器化部署 编排PV持久化存储 参考文档:https://kubernetes.io/zh/docs/tutorials/stateful-application/zookeeper/
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 apiVersion: v1 kind: PersistentVolume metadata: name: k8s-pv-zk1 spec: capacity: storage: 5Gi accessModes: - ReadWriteOnce mountOptions: - vers=3 - nolock,tcp,noresvport persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: path: /zk/zk1 server: 71cc1489fb-kmn77.ap-southeast-1.nas.aliyuncs.com --- apiVersion: v1 kind: PersistentVolume metadata: name: k8s-pv-zk2 spec: capacity: storage: 5Gi accessModes: - ReadWriteOnce mountOptions: - vers=3 - nolock,tcp,noresvport persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: path: /zk/zk2 server: 71cc1489fb-kmn77.ap-southeast-1.nas.aliyuncs.com --- apiVersion: v1 kind: PersistentVolume metadata: name: k8s-pv-zk3 spec: capacity: storage: 5Gi accessModes: - ReadWriteOnce mountOptions: - vers=3 - nolock,tcp,noresvport persistentVolumeReclaimPolicy: Retain storageClassName: nfs nfs: path: /zk/zk3 server: 71cc1489fb-kmn77.ap-southeast-1.nas.aliyuncs.com
应用:
1 2 3 4 5 [root@node002 Statefulset] [root@node002 Statefulset] k8s-pv-zk1 5Gi RWO Retain Available nfs 23s k8s-pv-zk2 5Gi RWO Retain Available nfs 23s k8s-pv-zk3 5Gi RWO Retain Available nfs 23s
部署Service & PDB 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 apiVersion: v1 kind: Service metadata: name: zk-headless namespace: baseservice labels: app: zk spec: ports: - port: 2888 name: server - port: 3888 name: leader-election clusterIP: None selector: app: zk --- apiVersion: v1 kind: Service metadata: name: zk-service namespace: baseservice labels: app: zk spec: ports: - port: 2181 name: client selector: app: zk --- apiVersion: policy/v1beta1 kind: PodDisruptionBudget metadata: name: zk-pdb namespace: baseservice spec: selector: matchLabels: app: zk maxUnavailable: 1
应用:
1 2 3 4 5 6 7 [root@node002 Statefulset] [root@node002 Statefulset] zk-headless ClusterIP None <none> 2888/TCP,3888/TCP 41s zk-service ClusterIP 10.0.0.140 <none> 2181/TCP 41s [root@node002 Statefulset] NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE zk-pdb N/A 1 0 64s
部署StatefulSet 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 apiVersion: apps/v1 kind: StatefulSet metadata: name: zk namespace: baseservice spec: selector: matchLabels: app: zk serviceName: zk-headless replicas: 3 updateStrategy: type: RollingUpdate podManagementPolicy: OrderedReady template: metadata: labels: app: zk spec: containers: - name: kubernetes-zookeeper imagePullPolicy: IfNotPresent image: "k8s.gcr.io/kubernetes-zookeeper:1.0-3.4.10" resources: requests: memory: "500Mi" cpu: "0.5" ports: - containerPort: 2181 name: client - containerPort: 2888 name: server - containerPort: 3888 name: leader-election command: - sh - -c - "start-zookeeper \ --servers=3 \ --data_dir=/var/lib/zookeeper/data \ --data_log_dir=/var/lib/zookeeper/data/log \ --conf_dir=/opt/zookeeper/conf \ --client_port=2181 \ --election_port=3888 \ --server_port=2888 \ --tick_time=2000 \ --init_limit=10 \ --sync_limit=5 \ --heap=512M \ --max_client_cnxns=60 \ --snap_retain_count=3 \ --purge_interval=12 \ --max_session_timeout=40000 \ --min_session_timeout=4000 \ --log_level=INFO" readinessProbe: exec: command: - sh - -c - "zookeeper-ready 2181" initialDelaySeconds: 10 timeoutSeconds: 5 livenessProbe: exec: command: - sh - -c - "zookeeper-ready 2181" initialDelaySeconds: 10 timeoutSeconds: 5 volumeMounts: - name: datadir mountPath: /var/lib/zookeeper volumeClaimTemplates: - metadata: name: datadir spec: accessModes: ["ReadWriteOnce" ] storageClassName: "nfs" resources: requests: storage: 5Gi
应用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 [root@node002 Statefulset] [root@node002 Statefulset] zk-0 1/1 Running 0 7m30s zk-1 1/1 Running 0 6m29s zk-2 1/1 Running 0 3m54s [root@node002 Statefulset] k8s-pv-zk1 5Gi RWO Retain Bound baseservice/datadir-zk-0 nfs 7m19s k8s-pv-zk2 5Gi RWO Retain Bound baseservice/datadir-zk-1 nfs 7m19s k8s-pv-zk3 5Gi RWO Retain Bound baseservice/datadir-zk-2 nfs 7m19s [root@node002 Statefulset] datadir-zk-0 Bound k8s-pv-zk1 5Gi RWO nfs 7m54s datadir-zk-1 Bound k8s-pv-zk2 5Gi RWO nfs 6m53s datadir-zk-2 Bound k8s-pv-zk3 5Gi RWO nfs 6m35s [root@node002 Statefulset] clientPort=2181 dataDir=/var/lib/zookeeper/data dataLogDir=/var/lib/zookeeper/data/log tickTime=2000 initLimit=10 syncLimit=5 maxClientCnxns=60 minSessionTimeout=4000 maxSessionTimeout=40000 autopurge.snapRetainCount=3 autopurge.purgeInteval=12 server.1=zk-0.zk-headless.baseservice.svc.cluster.local:2888:3888 server.2=zk-1.zk-headless.baseservice.svc.cluster.local:2888:3888 server.3=zk-2.zk-headless.baseservice.svc.cluster.local:2888:3888 [root@node002 Statefulset] kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead. ZooKeeper JMX enabled by default Using config: /usr/bin/../etc/zookeeper/zoo.cfg Mode: follower [root@node002 Statefulset] kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead. ZooKeeper JMX enabled by default Using config: /usr/bin/../etc/zookeeper/zoo.cfg Mode: leader [root@node002 Statefulset] kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead. ZooKeeper JMX enabled by default Using config: /usr/bin/../etc/zookeeper/zoo.cfg Mode: follower
StatefulSet总结 上面使用Statefulset控制器搭建了Zookeeper分布式集群,了解到Statefulset具备以下特点:
稳定且唯一的网络标识符
稳定且持久的存储
有序,平滑的部署和扩展
有序,平滑的删除和终止
有序的滚动更新 且了解到Statefulset包含三个组件:headless service
、StatefulSet
,volumeClaimTemplate
headless service
:确保解析名称直达后端pod
volumeClaimTemplate
:卷申请模板,每创建一个pod时,自动申请一个pvc,从而请求绑定pv
StatefulSet
:有状态服务的控制器
什么是Helm Helm 是一个 Kubernetes 的包管理工具,类似 Linux 的包管理器,如RedHat系的yum、Debian的apt,可以很方便的将之前打包好的 yaml 文件部署到 Kubernetes 上。Helm主要解决以下问题:
把yaml作为一个整体管理。
实现yaml的高效复用。
实现应用级别的版本管理。
当前 Helm 已经升级到V3版本,相比于V2版本主要变化如下:
最明显的变化是删除了 Tiller 。
Release 名称可以在不同命名空间重用。
支持将 Chart 推送至 Docker 镜像仓库中。
使用 JSONSchema 验证 chart values。
Helm是官方提供类似于YUM的包管理,是部署环境的流程封装,Helm有三个重要的概念:chart
、release
和Repository
chart
是创建一个应用的信息集合,包括各种Kubernetes对象的配置模板、参数定义、依赖关系、文档说明等。可以将chart想象成apt、yum中的软件安装包。
release
是chart的运行实例,代表一个正在运行的应用。当chart被安装到Kubernetes集群,就生成一个release。chart能多次安装到同一个集群,每次安装都是一个release[根据chart赋值不同,完全可以部署出多个release出来]。
Repository
用于发布和存储 Chart 的存储库。
Helm V2 与 Helm V3 的架构图对比: 在V2版本的架构中,Tiller在Kubernetes集群中,Helm Client发请求给Tiller需要经过RBAC认证。而在V3版本是Helm通过kubeconfig连接kube-apiserver,避免了使用者去配置RBAC权限。
Helm发布与传统发布对比: Helm是 Kubernetes 的包管理器,它将应用程序的所有资源和部署信息(*.yaml)组合到单个部署包中。从而实现应用快速安装到Kubernetes集群中。(通过Yaml的配置模板,将需要修改的内容变为属性值)
安装Helm v3 安装Helm V3版本非常简单,只需要下载Helm的二进制文件,并复制到 Kubernetes 主节点的 /usr/bin
目录即可。
Helm下载地址: https://get.helm.sh/helm-v3.4.2-linux-amd64.tar.gz
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 [root@node001 ~] --2022-02-09 16:16:34-- https://get.helm.sh/helm-v3.4.2-linux-amd64.tar.gz Resolving get.helm.sh (get.helm.sh)... 152.199.39.108, 2606:2800:247:1cb7:261b:1f9c:2074:3c Connecting to get.helm.sh (get.helm.sh)|152.199.39.108|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 13317454 (13M) [application/x-tar] Saving to: ‘helm-v3.4.2-linux-amd64.tar.gz’ 100%[===============================================================================================================>] 13,317,454 --.-K/s in 0.08s 2022-02-09 16:16:35 (151 MB/s) - ‘helm-v3.4.2-linux-amd64.tar.gz’ saved [13317454/13317454] [root@node001 ~] [root@node001 ~] [root@node001 ~] version.BuildInfo{Version:"v3.4.2" , GitCommit:"23dd3af5e19a02d4f4baa5b2f242645a1a3af629" , GitTreeState:"clean" , GoVersion:"go1.14.13" } [root@node001 ~] Loaded plugins: fastestmirror, langpacks, update-motd Loading mirror speeds from cached hostfile base | 3.1 kB 00:00:00 docker-ce-stable | 3.5 kB 00:00:00 epel | 4.7 kB 00:00:00 extras | 2.5 kB 00:00:00 plus | 2.9 kB 00:00:00 updates | 2.9 kB 00:00:00 updates/2.1903/x86_64/primary_db | 10 MB 00:00:00 Package 1:bash-completion-2.1-8.1.al7.noarch already installed and latest version Nothing to do [root@node001 ~] [root@node001 ~] [root@node001 ~] [root@node001 ~]
Helm常用命令
命令
描述
create
创建一个chart并指定名字
dependency
管理chart依赖
get
下载一个release。可用子命令:all、hooks、manifest、notes、values
history
获取release历史
install
安装一个chart
list
列出release
package
将chart目录打包到chart存档文件中
pull
从远程仓库中下载chart并解压到本地 # helm pull stable/mysql –untar
repo
添加,列出,移除,更新和索引chart仓库。可用子命令:add、index、list、remove、update
rollback
从之前版本回滚
search
根据关键字搜索chart。可用子命令:hub、repo
show
查看chart详细信息。可用子命令:all、chart、readme、values
status
显示已命名版本的状态
template
本地呈现模板
uninstall
卸载一个release
upgrade
更新一个release
version
查看helm客户端版本
Helm-Chart模板 Helm最核心的就是模板,即模板化的K8S manifests文件。
它本质上就是一个Go的template模板。Helm在Go template模板的基础上,还会增加很多东西。如一些自定义的元数据信息、扩展的库以及一些类似于编程形式的工作流,例如条件语句、管道等等。这些东西都会使得我们的模板变得更加丰富。
模板 有了模板,我们怎么把我们自定义的配置融入进去呢?用的就是这个values
文件。这两部分内容其实就是chart
的核心功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 [root@node001 helm] Creating nginx [root@node001 helm] nginx/ ├── charts ├── Chart.yaml ├── templates │ ├── deployment.yaml │ ├── _helpers.tpl │ ├── hpa.yaml │ ├── ingress.yaml │ ├── NOTES.txt │ ├── serviceaccount.yaml │ ├── service.yaml │ └── tests │ └── test-connection.yaml └── values.yaml
Chart.yaml
用于描述这个 Chart的基本信息,包括名字、描述信息以及版本等。
values.yaml
用于存储 templates 目录中模板文件中用到变量的值。
Templates
目录里面存放所有yaml模板文件。
charts
目录里存放这个chart依赖的所有子chart。
NOTES.txt
用于介绍Chart帮助信息, helm install 部署后展示给用户。例如:如何使用这个 Chart、列出缺省的设置等。
_helpers.tpl
放置模板助手的地方,可以在整个 chart 中重复使用
接下来尝试部署nginx应用,熟悉模板使用,先把templates 目录下面所有文件全部删除掉,这里我们自己来创建模板文件:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 [root@node001 helm] [root@node001 helm] [root@node001 templates] deployment.yaml service.yaml [root@node001 templates] apiVersion: apps/v1 kind: Deployment metadata: labels: app: web name: web spec: replicas: 1 selector: matchLabels: app: web template: metadata: labels: app: web spec: containers: - image: nginx name: nginx apiVersion: v1 kind: Service metadata: labels: app: web name: web spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: app: web
实际上,这已经是一个可安装的Chart包了,通过 helm install命令来进行安装:
1 2 3 4 5 6 7 NAME: web LAST DEPLOYED: Fri Feb 11 15:35:54 2022 NAMESPACE: default STATUS: deployed REVISION: 1 TEST SUITE: None
这样部署,其实与直接apply没什么两样。
然后使用如下命令可以看到实际的模板被渲染过后的资源文件:
可以看到,这与刚开始写的内容是一样的,包括名字、镜像等,我们希望能在一个地方统一定义这些会经常变换的字段,这就需要用到Chart的模板了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 apiVersion: apps/v1 kind: Deployment metadata: labels: name: {{ .Chart.Name }} app: {{ .Release.Name }} name: {{ .Release.Name }} spec: replicas: {{ .Values.replicas }} selector: matchLabels: app: {{ .Values.label }} template: metadata: labels: app: {{ .Values.label }} spec: containers: - image: {{ .Values.image }}:{{ .Values.imageTag }} name: {{ .Release.Name }} apiVersion: v1 kind: Service metadata: labels: name: {{ .Chart.Name }} app: {{ .Release.Name }} name: {{ .Release.Name }} spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: app: {{ .Values.label }} replicas: 2 image: nginx imageTag: 1.17 label: nginx [root@node001 nginx] NAME: web LAST DEPLOYED: Fri Feb 11 16:04:18 2022 NAMESPACE: default STATUS: pending-install REVISION: 1 TEST SUITE: None HOOKS: MANIFEST: --- apiVersion: v1 kind: Service metadata: labels: name: nginx app: web name: web spec: ports: - port: 80 protocol: TCP targetPort: 80 selector: app: nginx --- apiVersion: apps/v1 kind: Deployment metadata: labels: name: nginx app: web name: web spec: replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - image: nginx:1.17 name: web
这个deployment就是一个Go template的模板,这里定义的Release模板对象属于Helm内置的一种对象,是从values文件中读取出来的。这样一来,我们可以将需要变化的地方都定义变量。
调试 Helm也提供了--dry-run
--debug
调试参数,帮助你验证模板正确性。在执行helm install时候带上这两个参数就可以把对应的values值和渲染的资源清单打印出来,而不会真正的去部署一个release。
比如我们来调试上面创建的 chart 包:
内置对象 刚刚我们使用 {{.Release.Name}}
将 release 的名称插入到模板中。这里的 Release
就是 Helm 的内置对象,下面是一些常用的内置对象:
Release.Name
release 名称
Release.Name
release 名称
Release.Namespace
release 命名空间
Release.Service
release 服务的名称
Release.Revision
release 修订版本号,从1开始累加
参考文档: https://helm.sh/zh/docs/chart_template_guide/builtin_objects/
Values Values对象是为Chart模板提供值,这个对象的值有4个来源:
chart 包中的 values.yaml
文件
父 chart 包的 values.yaml
文件
通过 helm install
或者 helm upgrade
的 -f
或者 --values
参数传入的自定义的 yaml 文件
通过 --set
参数传入的值
chart 的 values.yaml 提供的值可以被用户提供的 values 文件覆盖,而该文件同样可以被 –set提供的参数所覆盖。
上面的例子中我们重新编辑了 values.yaml
文件,将默认的值全部清空,然后分别添加副本数、镜像名称、镜像版本、标签等
1 2 3 4 5 replicas: 2 image: nginx imageTag: 1.17 label: nginx
且values文件也可以包含结构化内容,例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 ... label: project: ms app: nginx ... apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Release.Name }}-deployment spec: replicas: {{ .Values.replicas }} selector: matchLabels: project: {{ .Values.label.project }} app: {{ .Values.label.app }} template: metadata: labels: project: {{ .Values.label.project }} app: {{ .Values.label.app }} spec: containers: - image: {{ .Values.image }}:{{ .Values.imageTag }} name: nginx
参考文档: https://helm.sh/zh/docs/chart_template_guide/values_files/
管道与函数 前面讲的模块,其实就是将值传给模板引擎进行渲染,模板引擎还支持对拿到数据进行二次处理。 例如从.Values中读取的值变成字符串,可以使用quote函数实现:
1 2 3 4 5 app: {{ quote .Values.label.app }} project: ms app: "nginx"
quote .Values.label.app
将后面的值作为参数传递给quote函数。
模板函数调用语法为:functionName arg1 arg2…
另外还会经常使用一个default
函数,该函数允许在模板中指定默认值,以防止该值被忽略掉。 例如忘记定义,执行helm install 会因为缺少字段无法创建资源,这时就可以定义一个默认值。
1 2 3 4 5 6 7 8 replicas: 2 apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Release.Name }}-deployment - name: {{ .Values.name | default "nginx" }}
其他函数: 指定长度缩进:{{ .Values.resources | indent 12 }}
转大写:{{ upper .Values.resources }}
首字母大写:{{ title .Values.resources }}
参考文档:
流程控制 if/else 流程控制是为模板提供了一种能力,满足更复杂的数据逻辑处理。 Helm模板语言提供以下流程控制语句:
if/else 条件块
with 指定范围
range 循环块
if
if/else块是用于在模板中有条件地包含文本块的方法,条件块的基本结构如下:
1 2 3 4 5 6 7 {{ if PIPELINE }} {{ else if OTHER PIPELINE }} {{ else }} {{ end }}
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 # cat values.yaml devops: k8 # cat templates/deployment.yaml ... template: metadata: labels: app: nginx {{ if eq .Values.devops "k8s" }} devops: 123 {{ else }} devops: 456 {{ end }}
在上面条件语句使用了eq运算符判断是否相等,除此之外,还支持ne、 lt、 gt、 and、 or等运算符。
通过模板引擎来渲染一下,会得到如下结果:
1 2 3 4 5 6 # helm install --dry-run web ../mychart/ ... labels: app: nginx devops: 456
上面渲染出来会有多余的空行,这是因为当模板引擎运行时,会将控制指令删除,所有之前占的位置也就空白了,需要使用{{- if …}}
的方式消除此空行:
1 2 3 4 5 6 7 ... env: {{- if eq .Values.env.hello "world" }} - name: hello value: 123 {{- end }}
现在是不是没有多余的空格了,如果使用-}}需谨慎,比如上面模板文件中:
1 2 3 4 5 6 ... env: {{- if eq .Values.env.hello "world" - }} - hello: true {{- end }}
这会渲染成:
因为-}}
它删除了双方的换行符。
条件判断就是判断条件是否为真,如果值为以下几种情况则为false:
一个布尔类型的 假
一个数字 零
一个 空
的字符串
一个 nil
(空或 null
)
一个空的集合( map
、 slice
、 tuple
、 dict
、 array
)
例如,判断一个空的数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 resources: {} ... spec: containers: - image: nginx:1.16 name: nginx {{- if .Values.resources }} resources: {{ toYaml .Values.resources | indent 10 }} {{- end }}
例如,判断一个布尔值
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 service: type: ClusterIP port: 80 ingress: enabled: true host: example.ctnrs.com {{- if .Values.ingress.enabled - }} apiVersion: networking.k8s.io/v1beta1 kind: Ingress metadata: name: {{ .Release.Name }}-ingress spec: rules: - host: {{ .Values.ingress.host }} http: paths: - path: / backend: serviceName: {{ .Release.Name }} servicePort: {{ .Values.service.port }} {{ end }}
with with: 控制变量作用域。 还记得之前我们的 {{.Release.xxx}}
或者 {{.Values.xxx}}
吗?其中的 .
就是表示对当前范围的引用, .Values就是告诉模板在当前范围中查找 Values
对象的值。而 with语句就可以来控制变量的作用域范围,其语法和一个简单的 if语句比较类似:
1 2 3 {{ with PIPELINE }} # restricted scope {{ end }}
with语句可以允许将当前范围 .
设置为特定的对象,比如我们前面一直使用的 .Values.label
,我们可以使用 with来将 .
范围指向 .Values.label
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ... replicas: 3 label: project: ms app: nginx apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Release.Name }}-deployment spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: {{- with .Values.nodeSelector }} nodeSelector: team: {{ .team }} gpu: {{ .gpu }} {{- end }} containers: - image: nginx:1.16 name: nginx
优化后:
1 2 3 4 {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }}
上面增加了一个{{- with .Values.label }} xxx {{- end }}
的一个块,这样的话就可以在当前的块里面直接引用 .team和 .gpu了。
with
是一个循环构造。使用.Values.nodeSelector
中的值:将其转换为Yaml
。toYaml之后的点是循环中.Values.nodeSelector
的当前值
range 在Helm模板语言中,使用 range关键字来进行循环操作, values.yaml
文件中添加上一个变量列表:
循环打印该列表:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 apiVersion: v1 kind: ConfigMap metadata: name: {{ .Release.Name }} data: test: | {{- range .Values.test }} {{ . }} {{- end }} ... apiVersion: v1 kind: ConfigMap metadata: name: web data: test: | 1 2 3 ...
参考文档: https://helm.sh/zh/docs/chart_template_guide/control_structures/
变量 变量,在模板中,使用变量的场合不多,但我们将看到如何使用它来简化代码,并更好地利用with和range。问题1:获取列表键值
1 2 3 4 5 6 7 8 9 10 11 12 env: NAME: "gateway" JAVA_OPTS: "-Xmx1G" ... env: {{- range $k , $v := .Values.env }} - name: {{ $k }} value: {{ $v | quote }} {{- end }}
渲染结果如下:
1 2 3 4 5 env: - name: JAVA_OPTS value: "-Xmx1G" - name: NAME value: "gateway"
上面在 range循环中使用 $key和 $value两个变量来接收后面列表循环的键和值。
问题2:with中不能使用内置对象 with语句块内不能再 .Release.Name对象,否则报错。我们可以将该对象赋值给一个变量可以来解决这个问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Release.Name }}-deployment spec: replicas: {{ .Values.replicas }} template: metadata: labels: project: {{ .Values.label.project }} app: {{ quote .Values.label.app }} {{- with .Values.label }} project: {{ .project }} app: {{ .app }} release: {{ .Release.Name }} {{- end }}
上面会出错
1 2 3 4 5 6 7 8 {{- $releaseName := .Release.Name - }} {{- with .Values.label }} project: {{ .project }} app: {{ .app }} release: {{ $releaseName }} release: {{ $.Release.Name }} {{- end }}
可以看到在 with语句上面增加了一句 {{-$releaseName:=.Release.Name-}}
,其中 $releaseName
就是后面的对象的一个引用变量,它的形式就是 $name
,赋值操作使用 :=
,这样 with语句块内部的 $releaseName
变量仍然指向的是 .Release.Name
参考文档: https://helm.sh/zh/docs/chart_template_guide/variables/
命名模板 命名模板: 使用define
定义,template
引入,在templates
目录中默认下划线_开头的文件为公共模板_helpers.tpl
1 2 3 4 5 6 7 8 9 10 11 {{- define "demo.fullname" - }} {{- .Chart.Name - }}-{{ .Release.Name }} {{- end - }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ template "demo.fullname" . }} ...
template指令是将一个模板包含在另一个模板中的方法。但是,template函数不能用于Go模板管道。为了解决该问题,增加include功能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 {{- define "demo.labels" - }} app: {{ template "demo.fullname" . }}chart: "{{ .Chart.Name }} -{{ .Chart.Version }} " release: "{{ .Release.Name }} " {{- end - }} apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "demo.fullname" . }} labels: {{- include "demo.labels" . | nindent 4 }} ...
上面包含一个名为 demo.labels
的模板,然后将值 .
传递给模板,最后将该模板的输出传递给 nindent
函数。
参考文档: https://helm.sh/zh/docs/chart_template_guide/named_templates/
如何开发自己的Chart
先创建模板helm create demo
修改Chart.yaml
,Values.yaml
,添加常用的变量
在templates目录下创建部署镜像所需要的yaml文件,并变量引用yaml里经常变动的字段