概述
有状态应用在 Kubernetes 里对应的对象就是 StatefulSet。跟 Deployment 一样,StatefulSet 也是一种控制器,区别是一个控制无状态应用,一个控制有状态应用。当一个有状态的 Pod 挂掉之后(或者是所在节点出现故障),这个 Pod 实例必须在其它的节点上重建,但是新的实例必须和被替换的实例拥有相同的名称、网络标识和状态,StatefulSet 可以保证 Pod 在重新调度以后保留它们之前的标识和状态,这样就可以方便的扩缩容。
应用一旦牵扯到状态,控制起来就会比较麻烦,因此 StatefulSet 要比 Deployment 复杂许多。具体来说,
适用场景
StatefulSet 适合具有以下需求的场景:
- 稳定的持久化存储 Pod 在重新调度之后还能继续访问之前的存储数据
- 稳定的网络标志 Pod 在重新调度后其名字和主机名保持不变
- 有序部署:构成应用的多个 Pod 启动顺序是固定的,适合 Pod 之间有依赖关系的情况
- 有序删除:删除应用时 Pod 销毁顺序跟启动顺序正好相反
StatefulSet 中每个 Pod 具有固定的名字,Pod 的 DNS 域名格式为 statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local
。跟普通域名一样,子域在前,主域在后,只不过节数比较多而已。各部分含义如下:
statefulSetName
:StatefulSet 名字0..N-1 Pod 的序号
:从 0 到 N-1serviceName
:Headless Service(没有 ClusterIP 的 Service) 名字,StatefulSet 无法使用普通的 Service(有一个 ClusterIP 作为服务入口) 来提供应用服务(包括负载均衡)。这是因为 StatefulSet 里的各个 Pod 不是对等的,只能由用户自己来实现服务内部的网络路由namespace
:服务所在的命名空间,Headless Service 和 StatefulSet 必须在相同的命名空间下svc.cluster.local
:集群域名
StatefulSet 的设置由以下 3 部分组成:
- 用于定义网络标志的 Headless Service
- 用于创建 PV(持久存储卷)的 volumeClaimTemplates
- 定义具体应用的 StatefulSet
Headless Service(无头服务)
Headless Service 是指不为 Service 设置 ClusterIP(入口 IP 地址),只通过标签选择器将后端的 Pod 列表返回给调用的客户端,客户端自行决定如何处理这个 Pod 列表。
在前面讲解 Deployment 时,我们知道每个创建的 Pod 的名称都是没有顺序的,名称最后有一截 Hash 值,因此这样创建的 Pod 是无序的。但是 StatefulSet 中要求 Pod 名称必须有序,每个 Pod 名称具有唯一性,并且当 Pod 重建后名称依然要保持一致,Pod 的名称就是唯一性的标志,必须持久有效,因此会使用 Headless Service 服务。
apiVersion: v1
kind: Service
metadata:
name: nginx-headle-svc
namespace: default
labels:
app: nginx
spec:
selector:
app: nginx-sts
ports:
- name: http
port: 80
clusterIP: None
执行
kubectl apply -f headle-svc.yaml
service/nginx-headle-svc created
查看创建结果
$ kubectl get svc -l app=nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-headle-svc ClusterIP None <none> 80/TCP 2m25s
Headless Service 所代理的所有 Pod 的 IP 地址都会绑定一个如下所示的 DNS 记录:
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
volumeClaimTemplates(卷申请模板)
有状态自然是需要持久化的,所以 StatefulSet 引入了 PV(持久存储卷) 和 PVC(持久存储卷声明) 对象来持久存储服务产生的状态,这样即便服务可能被 kill 掉或是重启,但是由于数据保存在 PV 中,所以这些状态也不会丢失。它们之间的关系如下图所示:
在前面讲解 Deployment 时,直接在 Pod template 中定义存储卷,所有副本集使用同一个存储卷,数据都是相同的。但是 StatefulSet 中要求有状态存储,也就意味着数据不一样,每个节点必须有专有存储卷,不能使用相同存储卷,因此需要使用 volumeClaimTemplates,为每个 Pod 生成不同的 PVC,并绑定 PV,这样就实现了 Pod 分别有专属的存储卷。
创建pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv01
spec:
capacity:
storage: 1Mi
accessModes:
- ReadWriteOnce
hostPath:
path: /tmp/pv01
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv02
spec:
capacity:
storage: 1Mi
accessModes:
- ReadWriteOnce
hostPath:
path: /tmp/pv02
执行
$ kubectl apply -f pv.yaml
persistentvolume/pv01 created
persistentvolume/pv02 created
查看创建结果,到成功创建了两个 PV 对象,状态是:Available
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv01 1Mi RWO Retain Available 2m7s
pv02 1Mi RWO Retain Available 2m7s
创建nginx-sts.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-sts
spec:
selector:
matchLabels:
app: nginx-sts
serviceName: nginx-headle-svc
replicas: 2
template:
metadata:
labels:
app: nginx-sts
spec:
containers:
- name: nginx
image: nginx:1.14
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Mi
执行
$ kubectl apply -f nginx-sts.yaml
statefulset.apps/nginx-sts created
$ kubectl get pod -l app=nginx-sts
NAME READY STATUS RESTARTS AGE
nginx-sts-0 0/1 ContainerCreating 0 111s
查看创建过程
$ kubectl get pod -l app=nginx-sts -w
NAME READY STATUS RESTARTS AGE
nginx-sts-0 1/1 Running 0 4m48s
nginx-sts-1 0/1 ContainerCreating 0 96s
nginx-sts-1 1/1 Running 0 2m55s
分别执行
for i in 0 1; do kubectl exec nginx-sts-$i -- sh -c 'echo hello $(hostname) > /usr/share/nginx/html/index.html'; done
查看pvc
kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-nginx-sts-0 Bound pv01 1Mi RWO 14m
www-nginx-sts-1 Bound pv02 1Mi RWO 11m
创建一个buybox
kubectl run -it my-box --image busybox:1.28.3 --restart=Never --rm /bin/sh
If you don't see a command prompt, try pressing enter.
/ # nslookup nginx-headle-svc
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: nginx-headle-svc
Address 1: 100.111.156.103 nginx-sts-1.nginx-headle-svc.default.svc.cluster.local
Address 2: 100.116.59.114 nginx-sts-0.nginx-headle-svc.default.svc.cluster.local
/ # ping nginx-headle-svc
PING nginx-headle-svc (100.116.59.114): 56 data bytes
64 bytes from 100.116.59.114: seq=0 ttl=63 time=0.137 ms
64 bytes from 100.116.59.114: seq=1 ttl=63 time=0.212 ms
查看结果
/ # nslookup nginx-sts-0.nginx-headle-svc
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: nginx-sts-0.nginx-headle-svc
Address 1: 100.116.59.114 nginx-sts-0.nginx-headle-svc.default.svc.cluster.local
/ # nslookup nginx-sts-1.nginx-headle-svc
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: nginx-sts-1.nginx-headle-svc
Address 1: 100.111.156.103 nginx-sts-1.nginx-headle-svc.default.svc.cluster.local
wget 执行结果
/ # wget nginx-sts-0.nginx-headle-svc -O- -q
hello nginx-sts-0
/ # wget nginx-sts-1.nginx-headle-svc -O- -q
hello nginx-sts-1