k8s之StatefulSet

2023-03-06 18:59:47 浏览数 (2)

# 什么是StatefulSet?

是用来创建有状态应用,可以通过过某种方式记录这些状态,然后在 Pod 被重新创建时,能够为新 Pod 恢复这些状态。

什么是有状态应用?

首先是需要有数据的持久化,即使Pod被重启后,也能恢复,与重启前保持一致。然后是应用创建的所有pod有依赖关系,顺序的创建、需要运行在指定的宿主机上,并且都有对应的网络标志。

应用场景?

分布式应用,它的多个实例之间,往往有依赖关系,比如:主从关系、主备关系。

# 使用StatefulSet

# 创建StatefulSet

创建yaml文件定义StatefulSet对象如下,与Deployment比较,多了一个serviceName字段,这个是用来指定StatefulSet所管理的pod是用域名访问是通过该service所设定的。

代码语言:javascript复制
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-sts

spec:
  serviceName: redis-svc
  replicas: 2
  selector:
    matchLabels:
      app: redis-sts

  template:
    metadata:
      labels:
        app: redis-sts
    spec:
      containers:
      - image: redis
        name: redis
        ports:
        - containerPort: 6379

创建StatefulSet对象

代码语言:javascript复制
[root@k8s-worker1 zwf]# kubectl apply -f statefulset.yaml  -n zwf
statefulset.apps/redis-sts created

[root@k8s-worker1 zwf]# kubectl get sts -n zwf
NAME        READY   AGE
redis-sts   2/2     54s

查看创建的Pod会发现,命名不再是随机创建的名字,而是有了顺序号,从0开始,而k8s也会按照这个顺序一次创建。

代码语言:javascript复制
[root@k8s-worker1 zwf]# kubectl get pods -n zwf
NAME          READY   STATUS    RESTARTS   AGE
redis-sts-0   1/1     Running   0          61s
redis-sts-1   1/1     Running   0          54s

输出pod中的hostname发现与pod的名称也保持一致,也就是应用可以自行决定依赖关系,比如该例子中可以使用0号pod作为主实例,而1号pod作为从实例。

代码语言:javascript复制
[root@k8s-worker1 zwf]# kubectl exec -it redis-sts-0 -n zwf -- hostname
redis-sts-0

# Service配置

定义匹配上面的创建StatefulSet对象所有管理的Service,也就是标签筛选需要和pod的标签保持一致,并且这里的metadata.name也要与StatefulSet中的serviceName一样。

代码语言:javascript复制
apiVersion: v1
kind: Service
metadata:
  name: redis-svc

spec:
  selector:
    app: redis-sts

  ports:
  - port: 6379
    protocol: TCP
    targetPort: 6379

创建Service对象,我们可以看到已经将StatefulSet所创建的pod加入到端点列表了,也就是可以稳定的通过Service来访问到Pod

代码语言:javascript复制
[root@k8s-worker1 zwf]# kubectl apply -f service_statefulset.yaml -n zwf
kservice/redis-svc created

[root@k8s-worker1 zwf]# kubectl get svc -n zwf
NAME                   TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
redis-svc              ClusterIP   10.0.0.246   <none>        6379/TCP       5s

[root@k8s-worker1 zwf]# kubectl describe svc redis-svc -n zwf
Name:              redis-svc
Namespace:         zwf
Labels:            <none>
Annotations:       <none>
Selector:          app=redis-sts
Type:              ClusterIP
IP Family Policy:  SingleStack
IP Families:       IPv4
IP:                10.0.0.246
IPs:               10.0.0.246
Port:              <unset>  6379/TCP
TargetPort:        6379/TCP
Endpoints:         10.222.126.51:6379,10.222.194.84:6379
Session Affinity:  None
Events:            <none>

但是我们的Pod不是一般的的应用,是有状态的应用,需要有稳定的网络标识,所以会为每一个Pod也创建一个域名,格式是:<podName>.<serviceName>.<namesapce>.svc.cluster.local

我们进入pod中验证一下,通过ping redis-sts-1.redis-svc.zwf.svc.cluster.local发现是可以ping通的,虽然Pod的IP会变化,但是通过固定的域名就能访问到指定Pod了。

代码语言:javascript复制
[root@k8s-worker1 zwf]# kubectl exec -it redis-sts-0 -n zwf -- ping redis-sts-1.redis-svc.zwf.svc.cluster.local
PING redis-sts-1.redis-svc.zwf.svc.cluster.local (10.222.126.51) 56(84) bytes of data.
64 bytes from redis-sts-1.redis-svc.zwf.svc.cluster.local (10.222.126.51): icmp_seq=1 ttl=62 time=0.964 ms
64 bytes from redis-sts-1.redis-svc.zwf.svc.cluster.local (10.222.126.51): icmp_seq=2 ttl=62 time=0.932 ms

既然我们的pod有了稳定的网络标识,Service也就不需要再分配ClusterIP了,这个时候,只需要添加字段clusterIP: None,这样就不会再分配IP了,这样的Service称为Headless Service

代码语言:javascript复制
apiVersion: v1
kind: Service
metadata:
  name: redis-svc

spec:
  clusterIP: None
  selector:
    app: redis-sts

  ports:
  - port: 6379
    protocol: TCP
    targetPort: 6379

创建Headless Service,可以看到CLUSTER-IP为None

代码语言:javascript复制
[root@k8s-worker1 zwf]# kubectl get svc -n zwf
NAME                   TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)        AGE
demoapp-nodeport-svc   NodePort    10.0.0.144   <none>        80:31999/TCP   21h
demoapp-svc            ClusterIP   10.0.0.74    <none>        80/TCP         22h
redis-svc              ClusterIP   None         <none>        6379/TCP       4s

Service和StatefulSet配置图如下:

# 持久化配置

接下来是给StatefulSet对象添加持久化配置。

定义StatefulSet描述如下:

代码语言:javascript复制
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-pv-sts

spec:
  serviceName: redis-pv-svc

  volumeClaimTemplates:
  - metadata:
      name: redis-100m-pvc
    spec:
      storageClassName: nfs-client
      accessModes:
        - ReadWriteMany
      resources:
        requests:
          storage: 100Mi

  replicas: 2
  selector:
    matchLabels:
      app: redis-pv-sts

  template:
    metadata:
      labels:
        app: redis-pv-sts
    spec:
      containers:
      - image: redis:5-alpine
        name: redis

        volumeMounts:
        - name: redis-100m-pvc
          mountPath: /data

参数:

  • volumeClaimTemplates,用来将PVC的定义嵌入到StatefulSet中的字段,是创建PVC的模板,可以让每一个Pod都能自动创建PVC
  • voulumeMounts,是用来选择上面的PVC挂载在容器的/data目录中

创建StatefulSet对象,可以看到pod已经创建起来了。

代码语言:javascript复制
[root@k8s-worker1 zwf]# kubectl apply -f statefulset_pvc.yaml -n zwf
statefulset.apps/redis-pv-sts created

[root@k8s-worker1 zwf]# kubectl get sts -n zwf
NAME           READY   AGE
redis-pv-sts   2/2     25

[root@k8s-worker1 zwf]# kubectl get pods -n zwf
NAME             READY   STATUS    RESTARTS   AGE
redis-pv-sts-0   1/1     Running   0          63s
redis-pv-sts-1   1/1     Running   0          54s

我们查看pvc,会发现创建了2个对象 ,以PVC名称-pod名称命名。

代码语言:javascript复制
[root@k8s-worker1 zwf]# kubectl get pvc -n zwf
NAME                            STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
redis-100m-pvc-redis-pv-sts-0   Bound    pvc-2712daa4-36b4-4a63-ac2f-7d3b31e2a887   100Mi      RWX            nfs-client     109s
redis-100m-pvc-redis-pv-sts-1   Bound    pvc-a3781354-e182-42f7-b6f6-983999603653   100Mi      RWX            nfs-client     100s

进入到pod中,使用redis-cli运行redis客户端,添加一些数据

代码语言:javascript复制
[root@k8s-worker1 zwf]# kubectl exec -it redis-pv-sts-0 -n zwf /bin/bash
[ root@redis-pv-sts-0:/data ]$ redis-cli  
127.0.0.1:6379> set name zhangsan
OK
127.0.0.1:6379> set age 18
OK
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> quit

将pod删除,然后再重新进入Pod中,查询之前创建的redis的key,还是能够查询到。

代码语言:javascript复制
[root@k8s-worker1 zwf]# kubectl exec -it redis-pv-sts-0  -n zwf -- /bin/bash
[ root@redis-pv-sts-0:/data ]$ redis-cli  
127.0.0.1:6379> keys *
1) "name"
2) "age"
127.0.0.1:6379> get name
"zhangsan"
127.0.0.1:6379> get age
"18"

配置的关系图如下:

0 人点赞