有状态工作负载(StatefulSet)

举报
yixiaoer 发表于 2020/10/30 22:33:57 2020/10/30
【摘要】 为什么需要StatefulSet在Deployment中讲到了Deployment,Deployment控制器下的Pod都有个共同特点,那就是每个Pod除了名称和IP地址不同,其余完全相同。需要的时候,Deployment可以通过Pod模板创建新的Pod;不需要的时候,Deployment就可以删除任意一个Pod。但是在某些场景下,这并不满足需求,比如有些分布式的场景,要求每个Pod都有自己...

为什么需要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来解决这个问题,其具体如下:

  1. StatefulSet给每个Pod提供固定名称,Pod名称增加从0-N的固定后缀,Pod重新调度后Pod名称和HostName不变。

  2. StatefulSet通过Headless Service给每个Pod提供固定的访问域名,Service的概念会在Service中详细介绍。

  3. 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集群相关知识和使用方法请猛击这里

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。