kubernetes快速入门10-StatefulSet控制器

kubernetes快速入门10-StatefulSet控制器

有状态副本集。

使用要求:

  1. 稳定且唯一的网络标识符;
  2. 稳定且持久的存储;
  3. 有序、平滑地部署和扩展;
  4. 有序、平滑地终止和删除;
  5. 有序的滚动更新

StatefulSet由三个组件组成:

  1. Headless services
  2. StatefulSet控制器
  3. volumeClaimTemplate 存储卷申请模板

StatefulSet帮助信息,statefulset可简写为sts

KIND:     StatefulSet
VERSION:  apps/v1

spec    <Object>
    selector    <Object> -required-  选择器
        matchLabels <map[string]string>  匹配相应的标签,用于关联属于sts控制器管理的pod,主要是实现pod数量与replicas数量趋同
    serviceName <string> -required- 指定关联的serviceName,实现sts中pod名称的解析,
                                                                    解析格式为:pod-specific-string.serviceName.default.svc.cluster.local
    template    <Object> -required- 当副本数不足时创建pod时使用的模板
    updateStrategy  <Object>  pod的更新策略
        rollingUpdate   <Object>  滚动更新
            partition   <integer>   按分区滚动更新,<integer>表示滚动更新的边界,即pod中编号大于等于integer的pod将会被更新,如myapp-0,myapp-1,myapp-2,myapp-3这4个pod,如果integer设置为2,那myapp-2,myapp3将会被更新,并且更新先更新编号大的,再更新编号较小的,创建时则是先创建编号小的,再创建编号大的。partition默认值为0,表示全部pod滚动更新
    volumeClaimTemplates    <[]Object> 声明式挂载存储卷的模板,会以此模板定义的要求创建pvc,并绑定符合要求的pv,供给pod的volumeMounts使用
    replicas    <integer>  statefulset控制器管理的pod运行的副本数

先准备存储和pv,存储利用NFS实现

k8s@node01:~/my_manifests/volumes$ cat pv-nfs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:  # PV资源不设置namespace
  name: pv001
  labels:
    name: pv001
    speed: slow
spec:
  nfs:
    path: /data/nfs/volume/v1
    server: node01.k8s.com
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:  # PV资源不设置namespace
  name: pv002
  labels:
    name: pv002
    speed: medium
spec:
  nfs:
    path: /data/nfs/volume/v2
    server: node01.k8s.com
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:  # PV资源不设置namespace
  name: pv003
  labels:
    name: pv003
    speed: medium
spec:
  nfs:
    path: /data/nfs/volume/v3
    server: node01.k8s.com
  accessModes: ["ReadWriteOnce"]
  capacity:
    storage: 2Gi
k8s@node01:~/my_manifests/volumes$ kubectl apply -f pv-nfs.yaml
persistentvolume/pv001 created
persistentvolume/pv002 created
persistentvolume/pv003 created
k8s@node01:~/my_manifests/volumes$ kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv001   2Gi        RWO,RWX        Retain           Available                                   3s
pv002   2Gi        RWO,RWX        Retain           Available                                   3s
pv003   2Gi        RWO            Retain           Available                                   3s

准备配置清单

k8s@node01:~/my_manifests/statefullset$ cat sts-myapp-pods.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-svc
  namespace: default
  labels:
    app: myapp-svc
spec:
  clusterIP: None  # 无头service,使用selector对应的标签选择相应的pods
  selector:
    app: myapp-pods
  ports:
  - name: http
    port: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: myapp
spec:
  replicas: 2
  serviceName: myapp-svc
  selector:
    matchLabels:
      app: myapp-pods
  template:
    metadata:
      labels:
        app: myapp-pods  # pods使用的标签
    spec:
      containers:
      - name: myapp
        image: ikubernetes/myapp:v1
        imagePullPolicy: IfNotPresent
        ports:
        - name: web
          containerPort: 80
        volumeMounts:
        - name: myappdata  # pod中挂载卷的名称,与volumeClaimTemplates中的名称对应
          mountPath: /usr/share/nginx/html/
  volumeClaimTemplates: # 卷申请模板,当pod启动时会以此模板创建相应的pvc
  - metadata:
      name: myappdata
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 2Gi

k8s@node01:~/my_manifests/statefullset$ kubectl apply -f sts-myapp-pods.yaml
service/myapp-svc created
statefulset.apps/myapp created

# pod的名称为配置清单中“pod名称+编号”
k8s@node01:~/my_manifests/statefullset$ kubectl get pods -o wide
NAME      READY   STATUS    RESTARTS   AGE     IP             NODE     NOMINATED NODE   READINESS GATES
myapp-0   1/1     Running   0          4m28s   10.244.2.105   node03   <none>           <none>
myapp-1   1/1     Running   0          4m25s   10.244.1.82    node02   <none>           <none>

# pvc的名称为 volumeClaimTemplates.metadata.name的值+pod名称
k8s@node01:~/my_manifests/statefullset$ kubectl get pvc
NAME                STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
myappdata-myapp-0   Bound    pv003    2Gi        RWO                           11m
myappdata-myapp-1   Bound    pv001    2Gi        RWO,RWX                       11m

k8s@node01:~/my_manifests/statefullset$ kubectl get pv  # 相应的pv已bound

NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                       STORAGECLASS   REASON   AGE
pv001   2Gi        RWO,RWX        Retain           Bound       default/myappdata-myapp-1                           72m
pv002   2Gi        RWO,RWX        Retain           Available                                                       72m
pv003   2Gi        RWO            Retain           Bound       default/myappdata-myapp-0                           72m

# 在sts控制中使用了headless的service,pod的名称不能直接进行解析,解析名称应为:pod名称.service名称.名称空间.svc.cluster.local,对应
k8s@node01:~/my_manifests/statefullset$ dig -t A myapp-0.myapp-svc.default.svc.cluster.local @10.96.0.10
...
;; ANSWER SECTION:
myapp-0.myapp-svc.default.svc.cluster.local. 30 IN A 10.244.2.105

k8s@node01:~/my_manifests/statefullset$ dig -t A myapp-1.myapp-svc.default.svc.cluster.local @10.96.0.10
;; ANSWER SECTION:
myapp-1.myapp-svc.default.svc.cluster.local. 30 IN A 10.244.1.82

每一个pod的名称是固定的,删除一个pod后重建后的名称也是不会发生变化,pvc的名称里隐含了pod的名称,如:myappdata-myapp-0,所以可持续的为同一个pod提供存储卷服务。

StatefulSet资源也可以进行扩容和缩容,只要有空闲的pv就可进行扩容。

# 两种方式都可以实现扩容和缩容
k8s@node01:~$ kubectl scale sts myapp --replicas=3
k8s@node01:~$ kubectl patch sts myapp -p '{"spec":{"replicases":2}}'

StatefulSet中pod更新

先设置升级分区边界

k8s@node01:~$ kubectl get sts
NAME    READY   AGE
myapp   3/3     10h
k8s@node01:~$ kubectl describe sts myapp
Name:               myapp
Namespace:          default
CreationTimestamp:  Wed, 29 Jul 2020 22:34:06 +0800
Selector:           app=myapp-pods
Labels:             <none>
Annotations:        Replicas:  3 desired | 3 total
Update Strategy:    RollingUpdate
  Partition:        0   # 默认的更新边界为 0, 表示全部pod滚动更新
...

# 修改更新边界,这里只有3个Pod,可以只让更新编号为1和2的
k8s@node01:~$ kubectl patch sts myapp -p '{"spec":{"updateStrategy":{"rollingUpdate":{"partition":1}}}}'
statefulset.apps/myapp patched
k8s@node01:~$ kubectl describe sts myapp
Name:               myapp
Namespace:          default
CreationTimestamp:  Wed, 29 Jul 2020 22:34:06 +0800
Selector:           app=myapp-pods
Labels:             <none>
Annotations:        Replicas:  3 desired | 3 total
Update Strategy:    RollingUpdate
  Partition:        1  # 已被设置为1

# 修改pod中的image
k8s@node01:~$ kubectl set image sts/myapp myapp=ikubernetes/myapp:v2
statefulset.apps/myapp image updated

# 监视更新过程,先更新编号为2的,再更新编号为1的
k8s@node01:~/my_manifests/statefullset$ kubectl get pods -w
NAME      READY   STATUS    RESTARTS   AGE
myapp-0   1/1     Running   0          9m56s
myapp-1   1/1     Running   1          10h
myapp-2   1/1     Running   0          8m59s

myapp-2   1/1     Terminating   0          10m
myapp-2   0/1     Terminating   0          10m
myapp-2   0/1     Terminating   0          10m
myapp-2   0/1     Terminating   0          10m
myapp-2   0/1     Pending       0          0s
myapp-2   0/1     Pending       0          0s
myapp-2   0/1     ContainerCreating   0          0s
myapp-2   1/1     Running             0          2s
myapp-1   1/1     Terminating         1          10h
myapp-1   0/1     Terminating         1          10h
myapp-1   0/1     Terminating         1          10h
myapp-1   0/1     Terminating         1          10h
myapp-1   0/1     Pending             0          0s
myapp-1   0/1     Pending             0          0s
myapp-1   0/1     ContainerCreating   0          0s
myapp-1   1/1     Running             0          3s

# 确认pod是否已更新
k8s@node01:~$ kubectl get pods myapp-0 -o yaml  | grep image
            f:image: {}
            f:imagePullPolicy: {}
  - image: ikubernetes/myapp:v1
    imagePullPolicy: IfNotPresent
    image: ikubernetes/myapp:v1  # myapp-0 保值v1版本
    imageID: docker-pullable://ikubernetes/myapp@sha256:9c3dc30b5219788b2b8a4b065f548b922a34479577befb54b03330999d30d513
k8s@node01:~$ kubectl get pods myapp-1 -o yaml  | grep image
            f:image: {}
            f:imagePullPolicy: {}
  - image: ikubernetes/myapp:v2  # v2版本
    imagePullPolicy: IfNotPresent
    image: ikubernetes/myapp:v2
    imageID: docker-pullable://ikubernetes/myapp@sha256:85a2b81a62f09a414ea33b74fb8aa686ed9b168294b26b4c819df0be0712d358
k8s@node01:~$ kubectl get pods myapp-2 -o yaml  | grep image
            f:image: {}
            f:imagePullPolicy: {}
  - image: ikubernetes/myapp:v2 # v2版本
    imagePullPolicy: IfNotPresent
    image: ikubernetes/myapp:v2
    imageID: docker-pullable://ikubernetes/myapp@sha256:85a2b81a62f09a414ea33b74fb8aa686ed9b168294b26b4c819df0be0712d358

# 如果更新后无问题,就可以把边界设置为0,让剩下的也一并更新

在实际生产环境中把有状态的应用迁移到k8s要慎重,可以多参考google上已实现方案,这里有个redis的事例:https://github.com/CommercialTribe/kube-redis

上一篇:简述Z-Stack的基本工作原理与流程(OSAL操作系统)


下一篇:express 初始化项目