有状态工作负载(StatefulSet)
为什么需要StatefulSet
在Deployment中讲到了Deployment,Deployment控制器下的Pod都有个共同特点,那就是每个Pod除了名称和IP地址不同,其余完全相同。需要的时候,Deployment可以通过Pod模板创建新的Pod;不需要的时候,Deployment就可以删除任意一个Pod。
但是在某些场景下,这并不满足需求,比如有些分布式的场景,要求每个Pod都有自己单独的状态时,比如分布式数据库,每个Pod要求有单独的存储,这时Deployment就不能满足需求了。
详细分析下有状态应用的需求,分布式有状态的特点主要是应用中每个部分的角色不同(即分工不同),比如数据库有主备,Pod之间有依赖,对应到Kubernetes中就是对Pod有如下要求:
-
Pod能够被别的Pod找到,这就要求Pod有固定的标识。
-
每个Pod有单独存储,Pod被删除恢复后,读取的数据必须还是以前那份,否则状态就会不一致。
Kubernetes提供了StatefulSet来解决这个问题,其具体如下:
-
StatefulSet给每个Pod提供固定名称,Pod名称增加从0-N的固定后缀,Pod重新调度后Pod名称和HostName不变。
-
StatefulSet通过Headless Service给每个Pod提供固定的访问域名,Service的概念会在Service中详细介绍。
-
StatefulSet通过创建固定标识的PVC保证Pod重新调度后还是能访问到相同的持久化数据。
下面将通过创建StatefulSet来体验StatefulSet的这些特性。
创建Headless Service
如前所述,创建Statefulset需要一个Headless Service用于Pod访问,Service的概念会在Service中详细介绍,这里先介绍Headless Service的创建方法。
使用如下文件描述Headless Service,其中:
-
spec.clusterIP:必须设置为None,表示Headless Service。
-
spec.ports.port:Pod间通信端口号。
-
spec.ports.name:Pod间通信端口名称。
apiVersion: v1
kind: Service # 对象类型为Service
metadata:
name: nginx
labels:
app: nginx
spec: ports: - name: nginx # Pod间通信的端口名称 port: 80 # Pod间通信的端口号 selector: app: nginx # 选择标签为app:nginx的Pod clusterIP: None # 必须设置为None,表示Headless Service
执行如下命令创建Headless Service。
# kubectl create -f headless.yaml
service/nginx created
创建完成后可以查询Service。
# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 5s
创建Statefulset
Statefulset 的YAML定义与其他对象基本相同,主要有两个差异点:
-
serviceName指定了Statefulset使用哪个Headless Service,需要填写Headless Service的名称。
-
volumeClaimTemplates是用来申请持久化声明PVC ,这里定义了一个名为data的模板,它会为每个Pod创建一个PVC,storageClassName指定了持久化存储的类型,在PV、PVC和StorageClass会详细介绍;volumeMounts是为Pod挂载存储。当然如果不需要存储的话可以删除volumeClaimTemplates和volumeMounts字段。
apiVersion: apps/v1
kind: StatefulSetmetadata:
name: nginx
spec: serviceName: nginx # headless service的名称
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: container-0
image: nginx:alpine
resources:
limits:
cpu: 100m
memory: 200Mi
requests:
cpu: 100m
memory: 200Mi volumeMounts: # Pod挂载的存储 - name: data mountPath: /usr/share/nginx/html # 存储挂载到/usr/share/nginx/html
imagePullSecrets:
- name: default-secret volumeClaimTemplates: - metadata: name: data spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi storageClassName: csi-nas # 持久化存储的类型
执行如下命令创建。
# kubectl create -f statefulset.yaml
statefulset.apps/nginx created
命令执行后,查询一下StatefulSet和Pod,可以看到Pod的名称后缀从0开始到2,逐个递增。
# kubectl get statefulset
NAME READY AGE
nginx 3/3 107s
# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-0 1/1 Running 0 112s
nginx-1 1/1 Running 0 69s
nginx-2 1/1 Running 0 39s
此时如果手动删除nginx-1这个Pod,然后再次查询Pod,可以看到StatefulSet重新创建了一个名称相同的Pod,通过创建时间5s可以看出nginx-1是刚刚创建的。
# kubectl delete pod nginx-1
pod "nginx-1" deleted
# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-0 1/1 Running 0 3m4snginx-1 1/1 Running 0 5snginx-2 1/1 Running 0 1m10s
进入容器查看容器的hostname,可以看到同样是nginx-0、nginx-1和nginx-2。
# kubectl exec nginx-0 -- sh -c 'hostname'
nginx-0
# kubectl exec nginx-1 -- sh -c 'hostname'
nginx-1
# kubectl exec nginx-2 -- sh -c 'hostname'
nginx-2
同时可以看一下StatefulSet创建的PVC,可以看到这些 PVC,都以“PVC名称-StatefulSet名称-编号”的方式命名,并且处于 Bound 状态。
# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-nginx-0 Bound pvc-f58bc1a9-6a52-4664-a587-a9a1c904ba29 1Gi RWX csi-nas 2m24s
data-nginx-1 Bound pvc-066e3a3a-fd65-4e65-87cd-6c3fd0ae6485 1Gi RWX csi-nas 101s
data-nginx-2 Bound pvc-a18cf1ce-708b-4e94-af83-766007250b0c 1Gi RWX csi-nas 71s
StatefulSet的网络标识
StatefulSet创建后,可以看下Pod是有固定名称的,那Headless Service是如何起作用的呢,那就是使用DNS,为Pod提供固定的域名,这样Pod间就可以使用域名访问,即便Pod被重新创建而导致Pod的IP地址发生变化,这个域名也不会发生变化。
Headless Service创建后,每个Pod的IP都会有下面格式的域名。
<pod-name>.<svc-name>.<namespace>.svc.cluster.local
例如上面的三个Pod的域名就是:
-
nginx-0.nginx.default.svc.cluster.local
-
nginx-1.nginx.default.svc.cluster.local
-
nginx-1.nginx.default.svc.cluster.local
实际访问时可以省略后面的.<namespace>.svc.cluster.local。
下面命令会使用tutum/dnsutils镜像创建一个Pod,进入这个Pod的容器,使用nslookup命令查看Pod对应的域名,可以发现能解析出Pod的IP地址。这里可以看到DNS服务器的地址是10.247.3.10,这是在创建CCE集群时默认安装CoreDNS插件,用于提供DNS服务,后续在Kubernetes网络会详细介绍CoreDNS的作用。
$ kubectl run -i --tty --image tutum/dnsutils dnsutils --restart=Never --rm /bin/sh
If you don't see a command prompt, try pressing enter.
/ # nslookup nginx-0.nginx
Server: 10.247.3.10
Address: 10.247.3.10#53
Name: nginx-0.nginx.default.svc.cluster.local
Address: 172.16.0.31
/ # nslookup nginx-1.nginx
Server: 10.247.3.10
Address: 10.247.3.10#53
Name: nginx-1.nginx.default.svc.cluster.local
Address: 172.16.0.18
/ # nslookup nginx-2.nginx
Server: 10.247.3.10
Address: 10.247.3.10#53
Name: nginx-2.nginx.default.svc.cluster.local
Address: 172.16.0.19
此时如果手动删除这两个Pod,查询被StatefulSet重新创建的Pod的IP,然后使用nslookup命令解析Pod的域名,可以发现nginx-0.nginx和nginx-1.nginx仍然能解析到对应的Pod。这就保证了StatefulSet网络标识不变。
StatefulSet存储状态
上面说了StatefulSet可以通过PVC做持久化存储,保证Pod重新调度后还是能访问到相同的持久化数据,在删除Pod时,PVC不会被删除。
图1 StatefulSet的Pod重建过程
下面将通过实际操作验证这一点是如何做到的,执行下面的命令,在nginx-1的目录/usr/share/nginx/html中写入一些内容,例如将index.html的内容修改为“hello world”。
# kubectl exec nginx-1 -- sh -c 'echo hello world > /usr/share/nginx/html/index.html'
修改完后,如果在Pod中访问“http://localhost”,那就会返回“hello world”。
# kubectl exec -it nginx-1 -- curl localhost
hello world
此时如果手动删除nginx-1这个Pod,然后再次查询Pod,可以看到StatefulSet重新创建了一个名称相同的Pod,通过创建时间4s可以看出nginx-1是刚刚创建的。
# kubectl delete pod nginx-1
pod "nginx-1" deleted
# kubectl get pods
NAME READY STATUS RESTARTS AGE
nginx-0 1/1 Running 0 14m
nginx-1 1/1 Running 0 4s
nginx-2 1/1 Running 0 13m
再次访问该Pod的index.html页面,会发现仍然返回“hello world”,这说明这个Pod仍然是访问相同的存储。
# kubectl exec -it nginx-1 -- curl localhost
hello world
了解更多Kubernetes集群相关知识和使用方法请猛击这里。
- 点赞
- 收藏
- 关注作者
评论(0)