转载请声明出处哦~,本篇文章发布于luozhiyun的博客:https://www.luozhiyun.comhtml
在上一篇中,讲解了容器持久化存储,从中咱们知道什么是PV和PVC,这一篇咱们讲经过StatefulSet来使用它们。若是以为我讲的不错的,能够发个邮件鼓励一下我噢~node
咱们在第三篇讲的Deployment控制器是应用于无状态的应用的,全部的Pod启动之间没有顺序,Deployment能够任意的kill一个Pod不会影响到业务数据,可是这到了有状态的应用中就无论用了。nginx
而StatefulSet就是用来对有状态应用提供支持的控制器。web
StatefulSet把真实世界里的应用状态,抽象为了两种状况:docker
StatefulSet 的核心功能,就是经过某种方式记录这些状态,而后在 Pod 被从新建立时,可以为新 Pod 恢复这些状态。shell
在k8s中,Service是用来将一组 Pod 暴露给外界访问的一种机制。Service能够经过DNS的方式,代理到某一个Pod,而后经过DNS记录的方式解析出被代理 Pod 的 IP 地址。api
以下:bash
apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: ports: - port: 80 name: web clusterIP: None selector: app: nginx
这个Service会经过Label Selector选择全部携带了 app=nginx 标签的 Pod,都会被这个 Service 代理起来。网络
它所代理的全部 Pod 的 IP 地址,都会被绑定一个这样格式的 DNS 记录,以下所示:app
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
因此经过这个DNS记录,StatefulSet就可使用到DNS 记录来维持 Pod 的拓扑状态。
以下:
apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: serviceName: "nginx" replicas: 2 # by default is 1 selector: matchLabels: app: nginx # has to match .spec.template.metadata.labels template: metadata: labels: app: nginx # has to match .spec.selector.matchLabels spec: containers: - name: nginx image: nginx:1.9.1 ports: - containerPort: 80 name: web
这里使用了serviceName=nginx,代表StatefulSet 控制器会使用nginx 这个Service来进行网络代理。
咱们能够以下建立:
$ kubectl create -f svc.yaml $ kubectl get service nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx ClusterIP None <none> 80/TCP 10s $ kubectl create -f statefulset.yaml $ kubectl get statefulset web NAME DESIRED CURRENT AGE web 2 1 19s
而后咱们能够观察pod的建立状况:
$ kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 76m web-1 1/1 Running 0 76m
咱们经过-w命令能够看到pod建立状况,StatefulSet所建立的pod编号都是从0开始累加,在 web-0 进入到 Running 状态、而且细分状态(Conditions)成为 Ready 以前,web-1 会一直处于 Pending 状态。
而后咱们使用exec查看pod的hostname:
$ kubectl exec web-0 -- sh -c 'hostname' web-0 $ kubectl exec web-1 -- sh -c 'hostname' web-1
而后咱们能够启动一个一次性的pod用 nslookup 命令,解析一下 Pod 对应的 Headless Service:
$ kubectl run -i --tty --image busybox:1.28.4 dns-test --restart=Never --rm /bin/sh $ nslookup web-0.nginx Server: 10.68.0.2 Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: 172.20.0.56 web-0.nginx.default.svc.cluster.local $ nslookup web-1.nginx Server: 10.68.0.2 Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local Name: web-1.nginx Address 1: 172.20.0.57 web-1.nginx.default.svc.cluster.local
若是咱们删除了这两个pod,而后观察pod状况:
$ kubectl delete pod -l app=nginx $ kubectl get pod -w -l app=nginx web-0 1/1 Terminating 0 83m web-1 1/1 Terminating 0 83m web-0 0/1 Pending 0 0s web-1 0/1 Terminating 0 83m web-0 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 1s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 1s
当咱们把这两个 Pod 删除以后,Kubernetes 会按照原先编号的顺序,建立出了两个新的 Pod。而且,Kubernetes 依然为它们分配了与原来相同的“网络身份”:web-0.nginx 和 web-1.nginx。
可是网络结构虽然没变,可是pod对应的ip是改变了的,咱们再进入到pod进行DNS解析:
$ nslookup web-0.nginx Server: 10.68.0.2 Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: 172.20.0.59 web-0.nginx.default.svc.cluster.local $ nslookup web-1.nginx Server: 10.68.0.2 Address 1: 10.68.0.2 kube-dns.kube-system.svc.cluster.local Name: web-1.nginx Address 1: 172.20.0.60 web-1.nginx.default.svc.cluster.local
在讲存储状态的时候,须要你们掌握上一节有关pv和pvc的知识才好往下继续,建议你们看完再来看本节。
在上一节中,咱们了解到Kubernetes 中 PVC 和 PV 的设计,实际上相似于“接口”和“实现”的思想。而 PVC、PV 的设计,也使得 StatefulSet 对存储状态的管理成为了可能。
好比咱们声明一个以下的StatefulSet:
apiVersion: apps/v1 kind: StatefulSet metadata: name: web spec: serviceName: "nginx" replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.9.1 ports: - containerPort: 80 name: web volumeMounts: - name: local-volume-a mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: local-volume-a spec: accessModes: - ReadWriteMany storageClassName: "local-volume" resources: requests: storage: 512Mi selector: matchLabels: key: local-volume-a-0
在这个StatefulSet中添加了volumeClaimTemplates字段,用来声明对应的PVC的定义;也就是说这个PVC中使用的storageClass必须是local-volume,须要的存储空间是512Mi,而且这个pvc对应的pv的标签必须是key: local-volume-a-0。
而后咱们准备一个PV:
apiVersion: v1 kind: PersistentVolume metadata: name: local-volume-pv-0 labels: key: local-volume-a-0 spec: capacity: storage: 0.5Gi volumeMode: Filesystem accessModes: - ReadWriteMany persistentVolumeReclaimPolicy: Retain storageClassName: local-volume local: path: /mnt/disks/vol1 nodeAffinity: required: nodeSelectorTerms: - matchExpressions: - key: kubernetes.io/hostname operator: In values: - node1
我把这个PV建立在node1节点上,而且将本地磁盘挂载声明为PV。
而后咱们建立这个PV:
$ kubectl apply -f local-pv-web-0.yaml $ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE local-volume-pv-0 512Mi RWX Retain Available default/local-vo
而后咱们在建立这个StatefulSet的时候,会自动建立PVC:
$ kubectl apply -f statefulset2.yaml $ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE local-volume-a-web-0 Bound local-volume-pv-0 512Mi RWX local-volume 15m
建立的PVC名字都是由:<PVC 名字 >-<StatefulSet 名字 >-< 编号 >构成,编号从0开始,而且咱们能够看到上面的PV已经处于Bound状态。
这个时候咱们进入到Pod中,写入一个文件:
$ kubectl exec -it web-0 -- /bin/bash $ echo helloword >/usr/share/nginx/html/index.html
这样就会在Pod 的 Volume 目录里写入一个文件,若是咱们把这个Pod删除,那么在被删除以后这个Pod仍是会被建立出来,而且还会再和原来的PV:local-volume-pv-0绑定起来。
也就是说当StatefulSet 控制器发现一个名叫 web-0 的 Pod 消失了的时候,控制器就会从新建立一个新的、名字仍是叫做 web-0 的 Pod 来,“纠正”这个不一致的状况。而且删除Pod时并不会删除这个 Pod 对应的 PVC 和 PV。须要注意的是,在这个新的 Pod 对象的定义里,它声明使用的 PVC 的名字,仍是叫做local-volume-a-web-0。
经过这种方式,Kubernetes 的 StatefulSet 就实现了对应用存储状态的管理。
在 Kubernetes 1.7 及以后的版本中,能够为 StatefulSet 设定 .spec.updateStrategy
字段。
若是 StatefulSet 的 .spec.updateStrategy.type
字段被设置为 OnDelete,当您修改 .spec.template
的内容时,StatefulSet Controller 将不会自动更新其 Pod。您必须手工删除 Pod,此时 StatefulSet Controller 在从新建立 Pod 时,使用修改过的 .spec.template
的内容建立新 Pod。
例如咱们执行下面的语句更新上面例子中建立的web:
$ kubectl set image statefulset web nginx=nginx:1.18.0 $ kubectl describe pod web-0 .... Containers: nginx: Container ID: docker://7e45cd509db74a96b4f6ca4d9f7424b3c4794f56e28bfc3fbf615525cd2ecadb Image: nginx:1.9.1 ....
而后咱们发现pod的nginx版本并无发生改变,须要咱们手动删除pod以后才能生效。
$ kubectl delete pod web-0 pod "web-0" deleted $ kubectl describe pod web-0 ... Containers: nginx: Container ID: docker://0f58b112601a39f3186480aa97e72767b05fdfa6f9ca02182d3fb3b75c159ec0 Image: nginx:1.18.0 ...
.spec.updateStrategy.type
字段的默认值是 RollingUpdate,该策略为 StatefulSet 实现了 Pod 的自动滚动更新。在更新完.spec.tempalte
字段后StatefulSet Controller 将自动地删除并重建 StatefulSet 中的每个 Pod。
删除和重建的顺序也是有讲究的:
当为StatefulSet 的 RollingUpdate
字段的指定 partition
字段的时候,则全部序号大于或等于 partition
值的 Pod 都会更新。序号小于 partition
值的全部 Pod 都不会更新,即便它们被删除,在从新建立时也会使用之前的版本。
若是 partition
值大于其 replicas
数,则更新不会传播到其 Pod。这样能够实现金丝雀发布Canary Deploy或者灰度发布。
以下,由于咱们的web是2个pod组成,因此能够将partition
设置为1:
$ kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":1}}}}'
在这里,我使用了 kubectl patch 命令。它的意思是,以“补丁”的方式(JSON 格式的)修改一个 API 对象的指定字段。
下面咱们执行更新:
$ kubectl set image statefulset web nginx=nginx:1.19.1 statefulset.apps/web image updated
并在另外一个终端中watch pod的变化:
$ kubectl get pods -l app=nginx -w NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 13m web-1 1/1 Running 0 93s web-1 0/1 Terminating 0 2m16s web-1 0/1 Pending 0 0s web-1 0/1 ContainerCreating 0 0s web-1 1/1 Running 0 16s
可见上面只有一个web-1进行了版本的发布。
StatefulSet把有状态的应用抽象为两种状况:拓扑状态和存储状态。
拓扑状态指的是应用的多个实例之间不是彻底对等的关系,包含启动的顺序、建立以后的网络标识等必须保证。
存储状态指的是不一样的实例绑定了不一样的存储,如Pod A在它的生命周期中读取的数据必须是一致的,哪怕是重启以后仍是须要读取到同一个存储。
而后讲解了一下StatefulSet发布更新该如何作,updateStrategy
策略以及经过partition
若是实现金丝雀发布等。