您现在的位置是:首页 > 文章详情

深入浅出 Kubernetes:StatefulSet 概念理解与实践

日期:2019-08-25点击:341

深入浅出 Kubernetes:StatefulSet 概念理解与实践

一 背景知识及相关概念

StatefulSet 的设计其实非常容易理解。它把真实世界里的应用状态,抽象为了两种情况:

拓扑状态。这种情况意味着,应用的多个实例之间不是完全对等的关系。这些应用实例,必须按照某些顺序启动,比如应用的主节点 A 要先于从节点 B 启动。而如果你把 A 和 B 两个 Pod 删除掉,它们再次被创建出来时也必须严格按照这个顺序才行。并且,新创建出来的 Pod,必须和原来 Pod 的网络标识一样,这样原先的访问者才能使用同样的方法,访问到这个新 Pod。

存储状态。这种情况意味着,应用的多个实例分别绑定了不同的存储数据。对于这些应用实例来说,Pod A 第一次读取到的数据,和隔了十分钟之后再次读取到的数据,应该是同一份,哪怕在此期间 Pod A 被重新创建过。这种情况最典型的例子,就是一个数据库应用的多个存储实例。

StatefulSet 的核心功能,就是通过某种方式记录这些状态,然后在 Pod 被重新创建时,能够为新 Pod 恢复这些状态。

这个 Service 又是如何被访问的呢?

第一种方式,是以 Service 的 VIP(Virtual IP,即:虚拟 IP)方式。比如:当我访问 172.20.25.3 这个 Service 的 IP 地址时,172.20.25.3 其实就是一个 VIP,它会把请求转发到该 Service 所代理的某一个 Pod 上。

第二种方式,就是以 Service 的 DNS 方式。比如:这时候,只要我访问“my-svc.my-namespace.svc.cluster.local”这条 DNS 记录,就可以访问到名叫 my-svc 的 Service 所代理的某一个 Pod。

二 StatefulSet 的两种结构

2.1 拓扑结构

让我们来看一下以下例子:

apiVersion: v1 kind: Service metadata: name: nginx labels: app: nginx spec: selector: app: nginx ports: - port: 80 name: web clusterIP: None

apiVersion: apps/v1 kind: StatefulSet metadata: name: web-server-gysl labels: app: nginx spec: serviceName: "nginx" replicas: 2 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: restartPolicy: Always containers: - name: web-server image: nginx:1.16.0 imagePullPolicy: IfNotPresent ports: - containerPort: 80 name: web-port

这些 Pod 的创建,也是严格按照编号顺序进行的。比如,在 web-server-gysl-0 进入到 Running 状态、并且细分状态(Conditions)成为 Ready 之前,web-server-gysl-1 会一直处于 Pending 状态。

使用以下命令测试:

kubectl run -i --tty --image toolkit:v1.0.0821 dns-test --restart=Never --rm /bin/bash
[root@dns-test /]# nslookup web-server-gysl-0.nginx Server: 10.0.0.2 Address: 10.0.0.2#53 Name: web-server-gysl-0.nginx.default.svc.cluster.local Address: 172.20.25.3 [root@dns-test /]# nslookup web-server-gysl-1.nginx Server: 10.0.0.2 Address: 10.0.0.2#53 Name: web-server-gysl-1.nginx.default.svc.cluster.local Address: 172.20.72.7 [root@dns-test /]# nslookup nginx Server: 10.0.0.2 Address: 10.0.0.2#53 Name: nginx.default.svc.cluster.local Address: 172.20.72.7 Name: nginx.default.svc.cluster.local Address: 172.20.25.3

由于最近版本的 busybox 有坑,我自己制作了一个 DNS 测试工具,Dockerfile 如下:

FROM centos:7.6.1810 RUN yum -y install bind-utils CMD ["/bin/bash","-c","while true;do sleep 60000;done"]

回到 Master 节点看一下:

$ kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES web-server-gysl-0 1/1 Running 0 43m 172.20.25.3 172.31.2.12 <none> <none> web-server-gysl-1 1/1 Running 0 42m 172.20.72.7 172.31.2.11 <none> <none> $ kubectl get svc -o wide NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR nginx ClusterIP None <none> 80/TCP 43m app=nginx

当我们在集群内部分别 ping 域名 web-server-gysl-0.nginx.default.svc.cluster.local 和 web-server-gysl-1.nginx.default.svc.cluster.local 时,正常返回了对应的 Pod IP, 在 ping 域名 nginx.default.svc.cluster.local 时,则随机返回2个 Pod IP 中的一个。完全印证了上文所述内容。

在上述操作过程中,我随机删除了这些 Pod 中的某一个或几个,稍后再次来查看的时候,新创建的 Pod 依然按照之前的编号进行了编排。

此外,我将 StatefulSet 的一个 Pod 所在的集群内节点下线,再次查看 Pod 的情况,系统在其他节点上以原 Pod 的名称迅速创建了新的 Pod。编号都是从 0 开始累加,与 StatefulSet 的每个 Pod 实例一一对应,绝不重复。

2.2 存储结构

由于测试环境资源有限,原计划使用 rook-ceph 来进行实验的,无奈使用 NFS 来进行实验。 Ceph 创建 PV 的相关 yaml 如下:

apiVersion: v1 kind: PersistentVolume metadata: name: pv-gysl labels: type: local spec: capacity: storage: 2Gi accessModes: - ReadWriteOnce rbd: monitors: - '172.31.2.11:6789' - '172.31.2.12:6789' pool: data image: data fsType: xfs readOnly: true user: admin keyring: /etc/ceph/keyrin

NFS 实验相关 yaml:

apiVersion: v1 kind: PersistentVolume metadata: name: pv-nfs-gysl-0 labels: environment: test spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle storageClassName: nfs nfs: path: /data-0 server: 172.31.2.10 --- apiVersion: v1 kind: PersistentVolume metadata: name: pv-nfs-gysl-1 labels: environment: test spec: capacity: storage: 1Gi accessModes: - ReadWriteOnce persistentVolumeReclaimPolicy: Recycle storageClassName: nfs nfs: path: /data-1 server: 172.31.2.10 --- apiVersion: storage.k8s.io/v1beta1 kind: StorageClass metadata: name: nfs provisioner: fuseim.pri/ifs --- apiVersion: apps/v1 kind: StatefulSet metadata: name: statefulset-pvc-gysl spec: replicas: 2 serviceName: "gysl-web" selector: matchLabels: app: pod-gysl template: metadata: name: web-pod labels: app: pod-gysl spec: containers: - name: nginx image: nginx imagePullPolicy: IfNotPresent ports: - name: web-port containerPort: 80 volumeMounts: - name: www-vct mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www-vct annotations: volume.beta.kubernetes.io/storage-class: "nfs" spec: accessModes: - ReadWriteOnce resources: requests: storage: 1Gi storageClassName: nfs --- apiVersion: v1 kind: Service metadata: name: gysl-web spec: type: NodePort selector: app: pod-gysl ports: - name: web-svc protocol: TCP nodePort: 31688 port: 8080 targetPort: 80

通过以下命令向相关 Pod 写入验证内容:

for node in 0 1;do kubectl exec statefulset-pvc-gysl-$node -- sh -c "echo \<h1\>Node: ${node}\</h1\>>/usr/share/nginx/html/index.html";done

观察实验结果:

$ kubectl get pod -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES statefulset-pvc-gysl-0 1/1 Running 0 51m 172.20.85.2 172.31.2.11 <none> <none> statefulset-pvc-gysl-1 1/1 Running 0 32m 172.20.65.4 172.31.2.12 <none> <none> $ kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE www-vct-statefulset-pvc-gysl-0 Bound pv-nfs-gysl-0 1Gi RWO nfs 51m www-vct-statefulset-pvc-gysl-1 Bound pv-nfs-gysl-1 1Gi RWO nfs 49m $ kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE pv-nfs-gysl-0 1Gi RWO Recycle Bound default/www-vct-statefulset-pvc-gysl-0 nfs 51m pv-nfs-gysl-1 1Gi RWO Recycle Bound default/www-vct-statefulset-pvc-gysl-1 nfs 51m $ cat /data-0/index.html <h1>Node: 0</h1> $ cat /data-1/index.html <h1>Node: 1</h1> $ curl 172.31.2.11:31688 <h1>Node: 0</h1> $ curl 172.31.2.11:31688 <h1>Node: 1</h1> $ curl 172.31.2.12:31688 <h1>Node: 1</h1> $ curl 172.31.2.12:31688 <h1>Node: 0</h1>

kubectl run -i --tty --image toolkit:v1.0.0821 test --restart=Never --rm /bin/bash
[root@test /]# curl statefulset-pvc-gysl-0.gysl-web <h1>Node: 0</h1> [root@test /]# curl statefulset-pvc-gysl-0.gysl-web <h1>Node: 0</h1> [root@test /]# curl statefulset-pvc-gysl-1.gysl-web <h1>Node: 1</h1> [root@test /]# curl statefulset-pvc-gysl-1.gysl-web <h1>Node: 1</h1> [root@test /]# curl gysl-web:8080 <h1>Node: 1</h1> [root@test /]# curl gysl-web:8080 <h1>Node: 1</h1> [root@test /]# curl gysl-web:8080 <h1>Node: 0</h1> [root@test /]# curl gysl-web:8080 <h1>Node: 0</h1>

从实验结果中我们可以看出 Pod 与 PV、PVC 的对应关系,结合上文中的 yaml 我们不难发现:

  1. Pod 与对应的 PV 存储是一一对应的,在创 Pod 的同时, StatefulSet根据对应的规创建了相应的 PVC,PVC 选择符合条件的 PV 进绑定。当 Pod 被删除之后,数据依然保存在 PV 中,当被删除的 Pod 再次被创建时, 该 Pod 依然会立即与原来的 Pod 进行绑定,保持原有的对应关系。
  2. 在集群内部,可以通过 pod 名加对应的服务名访问指定的 Pod 及其绑定的 PV。 如果通过服务名来访问 StatefulSet ,那么服务名的功能类似于 VIP 的功能。
原文链接:https://yq.aliyun.com/articles/715893
关注公众号

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。

持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。

转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。

文章评论

共有0条评论来说两句吧...

文章二维码

扫描即可查看该文章

点击排行

推荐阅读

最新文章