动手动脑学Kubernetes系列之StatefulSet

查理谈科技 2024-05-09 02:09:24

在之前的文章中, 介绍了搭建好的#minikube#环境,如果你现在还没有一个可用的minikube环境, 那么可以去中直接下载;

在之前的文章中, 先后介绍了如何从源代码开始构建一个Node.js应用和Spring Boot 应用, 并且部署到Kubernetes 中(这个Kubernetes 环境主要是之前建好的#minikube#) , 现在我们开始进一步深入的学习Kubernetes, 用一个个可以实际运行的例子的形式, 深入理解#Kubernetes#的概念以及原理.

在#动手动脑学Kubernetes#系列教程中, 我们展示了Kubernetes的基本用法

在里, 学习了#Pod#的基本知识;

在里, 学习了标签(#Label#)的语法, 使用Label来选择和过滤Kubernetes 资源;

在里, 介绍了#Deployment#的使用, 介绍了Deployment与Replica Set、Pod的关系, 并展示了如何进行应用的版本回滚;

在里, 介绍了#Service#的使用,使用Replication Controller创建Pod, 并创建Service, 展示了从Service 调用应用的方法; 随后又展示了扩展 Pod的数量为2, 比较了Service和之前的不同, 基本展示了Cluster IP 类型的Service的基本用法.

在里, 介绍了#Namespace#的使用, 除了创建,列出系统的Namespace之外, 还说明Namespace 如何对资源进行隔离, 建立Development, Staging, Production等环境的方法.

在里, 介绍了#Service Discovery#的使用, 讲解了如何检查Kube-dns, 如何检查和使用Service的FQDN等知识, 对Kubernetes的DNS 系统有整体的理解.

在里, 介绍了#Port Forwards#, #端口转发#的用法, 在本地程序开发的时候, 使用端口转发可以简化本地测试的工作, 同时介绍了其他几种本地端口转发的用法.

在里, 介绍了#Probe#(#探针#)的知识, 介绍了livenessProbe 和readinessProbe的用法,同时介绍了Pod 容器中的几个状态变化, 以及2个容器生命周期回调接口.

在里, 介绍了#环境变量#的用法, 使用环境变量可以把Pod 定义的信息传递给运行其中的镜像.

在里, 我们介绍了#Volume#, 卷的用法, 主要展示了emptyDir卷的使用, emptyDir卷的生命周期是和Pod 生命周期同步的.

在里, 我们介绍了#Persistent Volume#, 也就是持久卷的用法,展示了当数据存入到PV 之后,数据超乎Pod 生命周期之外的情况.

在里, 介绍了Secret, 也就是机密信息的用法, 机密是绑定在命名空间里的, 在使用时候和Volume的用法一样, 可以被Pod 访问, 本篇展示了Opaque类型的Secret的用法.

在里, 介绍了日志(logging)的使用, 介绍了Kubernetes中基本日志记录的查看和常用的命令行参数,在理论部分展示了其他几种logging的使用.

在里, 介绍了Job, 也就是作业的使用, 介绍了Kubernetes中最基本的、非并行性的一次性运行的作业, 同时介绍了作业的基本概念和基本用法.

今天, 我们来学习StatefulSet的用法, 继续来学习Kubernetes 吧!

如果有无状态应用程序,则要使用Deployment。 但是,对于有状态的应用程序,就需要使用StatefulSet。 与Deployment不同,StatefulSet提供有关其管理的Pod的身份(即可预测名称)和启动顺序的某些保证。

与部署相比,还有另外两点不同:对于网络通信,您需要创建无头服务,而对于持久性,StatefulSet管理每个Pod的持久卷。

从镜像开始

首先, 来创建一个有状态的应用,然后体验StatefulSet .

让我们首先创建有状态的应用程序,即StatefulSet以及持久卷PV和Headless服务.

来回顾一下无头服务:

无头服务(Headless Services)

有时不需要或不想要负载均衡,以及单独的 Service IP。 遇到这种情况,可以通过指定 Cluster IP(spec.clusterIP)的值为 "None" 来创建 Headless Service。

你可以使用无头 Service 与其他服务发现机制进行接口,而不必与 Kubernetes 的实现捆绑在一起。

对这无头 Service 并不会分配 Cluster IP,kube-proxy 不会处理它们, 而且平台也不会为它们进行负载均衡和路由。 DNS 如何实现自动配置,依赖于 Service 是否定义了选择算符。

带选择算符的服务

对定义了选择算符的无头服务,Endpoint 控制器在 API 中创建了 Endpoints 记录, 并且修改 DNS 配置返回 A 记录(地址),通过这个地址直接到达 Service 的后端 Pod 上。

无选择算符的服务

对没有定义选择算符的无头服务,Endpoint 控制器不会创建 Endpoints 记录。 然而 DNS 系统会查找和配置,无论是:

对于 ExternalName 类型的服务,查找其 CNAME 记录

对所有其他类型的服务,查找与 Service 名称相同的任何 Endpoints 的记录

可见无头服务, 就是没有Cluster IP的Service, 来看看具体的定义吧.

GitHub地址:

https://raw.githubusercontent.com/hintcnuie/mehdb/main/app.yaml

文件内容:

---apiVersion: apps/v1kind: StatefulSetmetadata: name: mehdbspec: selector: matchLabels: app: mehdb serviceName: "mehdb" replicas: 2 template: metadata: labels: app: mehdb spec: containers: - name: shard image: quay.io/mhausenblas/mehdb:0.6 ports: - containerPort: 9876 env: - name: MEHDB_DATADIR value: "/mehdbdata" livenessProbe: initialDelaySeconds: 2 periodSeconds: 10 httpGet: path: /status port: 9876 readinessProbe: initialDelaySeconds: 15 periodSeconds: 30 httpGet: path: /status?level=full port: 9876 volumeMounts: - name: data mountPath: /mehdbdata volumeClaimTemplates: - metadata: name: data spec: accessModes: [ "ReadWriteOnce" ] resources: requests: storage: 1Gi---apiVersion: v1kind: Servicemetadata: name: mehdb labels: app: mehdbspec: ports: - port: 9876 clusterIP: None selector: app: mehdb

在上面的文件定义里面, 定义了一个mehdb 的Service, 一个名为data的PVC, 最后是名为mehdb的StatefulSet, 在这里类型为StatefulSet.

运行StatefulSet

先来创建一下StatefulSet:

$ kubectl apply -f https://raw.githubusercontent.com/hintcnuie/mehdb/main/app.yamlstatefulset.apps/mehdb createdservice/mehdb created

然后检查一下相应的创建过程:

$ kubectl get sts,po,pvc,svcNAME READY AGEstatefulset.apps/mehdb 2/2 4m23sNAME READY STATUS RESTARTS AGEpod/mehdb-0 1/1 Running 0 4m23spod/mehdb-1 1/1 Running 0 3mNAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGEpersistentvolumeclaim/data-mehdb-0 Bound pvc-ae4c72bc-5821-485b-ba74-b86f3b21b8de 1Gi RWO standard 4m23spersistentvolumeclaim/data-mehdb-1 Bound pvc-a4f548fa-5685-441e-883d-442d4e8f60f3 1Gi RWO standard 3mNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEservice/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 52dservice/mehdb ClusterIP None <none> 9876/TCP 4m23s

注意: 在这里我们又学到了一招,可以同时查看过个资源对象的命令的写法, 就是kubectl get sts,po,pvc,svc, 注意这几个资源名称之间不能有空间.另外, StatefulSet的缩写是sts, 是不是简单了很多!

现在,我们可以检查有状态的应用程序是否正常运行。 为此,我们使用无头服务mehdb:9876的/status端点,并且由于尚未将任何数据放入数据存储区,因此我们希望报告0个键:

$ kubectl run -it --rm jumpod --restart=Never --image=quay.io/openshiftlabs/jump:0.2 -- curl -s mehdb:9876/status?level=full0pod "jumpod" deleted

注意上面的用法, 这里面通过kubectl run命令并指定镜像, 直接创建了一个jumpod, 而且使用--rm的参数, 让这个pod 在执行完之后就自动销毁.

我们来查看一下这个StatefulSet下面的Pod:

$ kubectl get stsNAME    READY   AGEmehdb   2/2     13m[vagrant@control-plane ~]$ kubectl describe sts mehdbName:               mehdbNamespace:          defaultCreationTimestamp:  Sat, 20 Mar 2021 14:08:09 +0000Selector:           app=mehdbLabels:             <none>Annotations:        <none>Replicas:           2 desired | 2 totalUpdate Strategy:    RollingUpdate  Partition:        0Pods Status:        2 Running / 0 Waiting / 0 Succeeded / 0 FailedPod Template:  Labels:  app=mehdb  Containers:   shard:    Image:      quay.io/mhausenblas/mehdb:0.6    Port:       9876/TCP    Host Port:  0/TCP    Liveness:   http-get http://:9876/status delay=2s timeout=1s period=10s #success=1 #failure=3    Readiness:  http-get http://:9876/status%3Flevel=full delay=15s timeout=1s period=30s #success=1 #failure=3    Environment:      MEHDB_DATADIR:  /mehdbdata    Mounts:      /mehdbdata from data (rw)  Volumes:  <none>Volume Claims:  Name:          data  StorageClass:    Labels:        <none>  Annotations:   <none>  Capacity:      1Gi  Access Modes:  [ReadWriteOnce]Events:  Type    Reason            Age   From                    Message  ----    ------            ----  ----                    -------  Normal  SuccessfulCreate  13m   statefulset-controller  create Claim data-mehdb-0 Pod mehdb-0 in StatefulSet mehdb success  Normal  SuccessfulCreate  13m   statefulset-controller  create Pod mehdb-0 in StatefulSet mehdb successful  Normal  SuccessfulCreate  12m   statefulset-controller  create Claim data-mehdb-1 Pod mehdb-1 in StatefulSet mehdb success  Normal  SuccessfulCreate  12m   statefulset-controller  create Pod mehdb-1 in StatefulSet mehdb successful

通过Replicas参数我们可以看到, StatefulSet 下面有两个就绪状态的Pod, 先来看看详情:

$ kubectl describe pod -l app=mehdbName:         mehdb-0Namespace:    defaultPriority:     0Node:         localhost.localdomain/10.0.2.15Start Time:   Sat, 20 Mar 2021 14:08:11 +0000Labels:       app=mehdb              controller-revision-hash=mehdb-9b596f6c9              statefulset.kubernetes.io/pod-name=mehdb-0Annotations:  <none>Status:       RunningIP:           172.17.0.5IPs:  IP:           172.17.0.5Controlled By:  StatefulSet/mehdbContainers:  shard:    Container ID:   docker://917ac165ecfe545faa99bef0ded7e63daff51bcfdaf1a0a1a391a10b5fca6b99    Image:          quay.io/mhausenblas/mehdb:0.6    Image ID:       docker-pullable://quay.io/mhausenblas/mehdb@sha256:1f8fd2b656213fe4b15e925afe061dee6083900c7e1e150f762a7e23f8ca90fc    Port:           9876/TCP    Host Port:      0/TCP    State:          Running      Started:      Sat, 20 Mar 2021 14:09:06 +0000    Ready:          True    Restart Count:  0    Liveness:       http-get http://:9876/status delay=2s timeout=1s period=10s #success=1 #failure=3    Readiness:      http-get http://:9876/status%3Flevel=full delay=15s timeout=1s period=30s #success=1 #failure=3    Environment:      MEHDB_DATADIR:  /mehdbdata    Mounts:      /mehdbdata from data (rw)      /var/run/secrets/kubernetes.io/serviceaccount from default-token-q77th (ro)Conditions:  Type              Status  Initialized       True   Ready             True   ContainersReady   True   PodScheduled      True Volumes:  data:    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)    ClaimName:  data-mehdb-0    ReadOnly:   false  default-token-q77th:    Type:        Secret (a volume populated by a Secret)    SecretName:  default-token-q77th    Optional:    falseQoS Class:       BestEffortNode-Selectors:  <none>Tolerations:     node.kubernetes.io/not-ready:NoExecute op=Exists for 300s                 node.kubernetes.io/unreachable:NoExecute op=Exists for 300sEvents:  Type     Reason            Age                From               Message  ----     ------            ----               ----               -------  Warning  FailedScheduling  16m (x2 over 16m)  default-scheduler  0/1 nodes are available: 1 pod has unbound immediate PersistentVolumeClaims.  Normal   Scheduled         16m                default-scheduler  Successfully assigned default/mehdb-0 to localhost.localdomain  Normal   Pulling           16m                kubelet            Pulling image "quay.io/mhausenblas/mehdb:0.6"  Normal   Pulled            15m                kubelet            Successfully pulled image "quay.io/mhausenblas/mehdb:0.6" in 52.790537274s  Normal   Created           15m                kubelet            Created container shard  Normal   Started           15m                kubelet            Started container shard

命令行不明显的话,我们来看看Dashboard, 可以看到在metadata 部分有个uid字段:

到目前为止, 我们看到的StatefulSet 的表现, 和之前的Deployment并没有展现出什么不同, 那么两者之间究竟有什么区别呢?

StatefulSet 和Deployment的区别

和 Deployment 类似, StatefulSet 管理基于相同容器规约的一组 Pod。但和 Deployment 不同的是, StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID。这些 Pod 是基于相同的规约来创建的, 但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID。

具体的说, 他们之间的差异在于:

特性

Deployment

StatefulSet

适合场景

无状态的应用

有状态的应用

特点

1.pod之间没有顺序

2.所有pod共享存储

3.pod名字包含随机数字

4.service都有ClusterIP,可以负载均衡

1.部署、扩展、更新、删除都要有顺序

2.每个pod都有自己存储,所以都用volumeClaimTemplates,为每个pod都生成一个自己的存储,保存自己的状态

3.pod名字始终是固定的

4.service没有ClusterIP,是headlessservice,所以无法负载均衡,返回的都是pod名,所以pod名字都必须

是否暴露到外网

可以

一般不

请求面向的对象

serviceName

指定pod的域名

灵活性

只能通过service/serviceIp访问到k8s自动转发的pod

可以访问任意一个自定义的pod

易用性

只需要关心Service的信息即可

需要知道要访问的pod启动的名称、headlessService名称

PV/PVC绑定关系的稳定性(多replicas)

(pod挂掉后重启)无法保证初始的绑定关系

可以保证

pod名称稳定性

不稳定,因为是通过template创建,每次为了避免重复都会后缀一个随机数

稳定,每次都一样

启动顺序(多replicas)

随机启动,如果pod宕掉重启,会自动分配一个node重新启动

pod按 app-0、app-1...app-(n-1),如果pod宕掉重启,还会在之前的node上重新启动

停止顺序(多replicas)

随机停止

倒序停止

Pod 名称的区别

通过上面的比较, 我们先来看Pod的名称, 我们之前看到的两个Pod 的名称分别是:

mehdb-1mehdb-0

这个确实是和我们之前用Deployment 创建的Pod 名称不一样, 回想我们在中创建的Pod, 创建的Pod 如下所示:

我们能看到, 使用Deployment 创建的Pod, 其网络名称的后缀是随机的, 这就是其中的区别.

我们再来尝试删掉一个Pod, 看看重新生成的Pod 会不会还是同一个:

$ kubectl delete pod/mehdb-1pod "mehdb-1" deleted[vagrant@control-plane ~]$ kubectl get podsNAME      READY   STATUS    RESTARTS   AGEmehdb-0   1/1     Running   0          55mmehdb-1   0/1     Running   0          4s[vagrant@control-plane ~]$ kubectl describe sts mehdbName:               mehdbNamespace:          defaultCreationTimestamp:  Sat, 20 Mar 2021 14:08:09 +0000Selector:           app=mehdbLabels:             <none>Annotations:        <none>Replicas:           2 desired | 2 totalUpdate Strategy:    RollingUpdate  Partition:        0Pods Status:        2 Running / 0 Waiting / 0 Succeeded / 0 FailedPod Template:  Labels:  app=mehdb  Containers:   shard:    Image:      quay.io/mhausenblas/mehdb:0.6    Port:       9876/TCP    Host Port:  0/TCP    Liveness:   http-get http://:9876/status delay=2s timeout=1s period=10s #success=1 #failure=3    Readiness:  http-get http://:9876/status%3Flevel=full delay=15s timeout=1s period=30s #success=1 #failure=3    Environment:      MEHDB_DATADIR:  /mehdbdata    Mounts:      /mehdbdata from data (rw)  Volumes:  <none>Volume Claims:  Name:          data  StorageClass:    Labels:        <none>  Annotations:   <none>  Capacity:      1Gi  Access Modes:  [ReadWriteOnce]Events:  Type    Reason            Age                From                    Message  ----    ------            ----               ----                    -------  Normal  SuccessfulCreate  56m                statefulset-controller  create Claim data-mehdb-0 Pod mehdb-0 in StatefulSet mehdb success  Normal  SuccessfulCreate  56m                statefulset-controller  create Pod mehdb-0 in StatefulSet mehdb successful  Normal  SuccessfulCreate  54m                statefulset-controller  create Claim data-mehdb-1 Pod mehdb-1 in StatefulSet mehdb success  Normal  SuccessfulCreate  30s (x2 over 54m)  statefulset-controller  create Pod mehdb-1 in StatefulSet mehdb successful

从上面的运行记录中我们可以看到, 当使用kubectl get pos查询Pod 状态时, 上面的命令行输出中显示的是, Pod mehdb不是ready 的状态:

NAME      READY   STATUS    RESTARTS   AGEmehdb-1   0/1     Running   0          4s

而且Pod mehdb在Age 字段上也和mehdb-0 有明显的不同, 这个在我们再次检查Pod 状态的时候就能看出来.

$ kubectl get podsNAME      READY   STATUS    RESTARTS   AGEmehdb-0   1/1     Running   0          63mmehdb-1   1/1     Running   0          7m34s

所以说, 上面的操作, 证明在StatefulSet下面的Pod, 确实会使用同一个网络ID , 而且这个网络ID是有顺序的.

扩容/缩容 StatefulSet

刚才我们使用kubectl deleet 来删除一个StatefulSet中的Pod, 这个在Kuberenete文档中其实是很有风险而且不安全的操作, 下面我们使用kubectl scale 来增加Pod 数量, 看看Pod 命名的变化:

$ kubectl scale sts mehdb --replicas=5statefulset.apps/mehdb scaled$ kubectl get pods -w -l app=mehdbNAME READY STATUS RESTARTS AGEmehdb-0 1/1 Running 0 83mmehdb-1 1/1 Running 0 27mmehdb-2 0/1 Running 0 39smehdb-2 1/1 Running 0 44smehdb-3 0/1 Pending 0 0smehdb-3 0/1 Pending 0 0smehdb-3 0/1 Pending 0 2smehdb-3 0/1 ContainerCreating 0 2smehdb-3 0/1 Running 0 4smehdb-3 1/1 Running 0 31smehdb-4 0/1 Pending 0 0smehdb-4 0/1 Pending 0 0smehdb-4 0/1 Pending 0 3smehdb-4 0/1 ContainerCreating 0 3smehdb-4 0/1 Running 0 4smehdb-4 1/1 Running 0 34s

在运行完kubectl scale 命令之后, 我们紧接着运行了kubetctl get pod -w,注意后面的参数-w表示的是--watch的意思, 就是会持续地监控我们指定的Pod, 可以看出, Kuberentes 慢慢地把StatefulSet 下面的Pod 扩容到5个Pod, 而且命名是非常有顺序的.

删除StatefulSet及其资源

最后来删除StatefulSet.

$ kubectl delete sts/mehdbstatefulset.apps "mehdb" deleted$ kubectl get stsNo resources found in default namespace.$ kubectl get podsNo resources found in default namespace.$ kubectl get pvcNAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGEdata-mehdb-0 Bound pvc-ae4c72bc-5821-485b-ba74-b86f3b21b8de 1Gi RWO standard 12hdata-mehdb-1 Bound pvc-a4f548fa-5685-441e-883d-442d4e8f60f3 1Gi RWO standard 12hdata-mehdb-2 Bound pvc-74df0444-0c0b-4509-ab07-dc13c8384079 1Gi RWO standard 11hdata-mehdb-3 Bound pvc-3a23d929-5e5e-4d28-bc30-c862a6b4dec7 1Gi RWO standard 11hdata-mehdb-4 Bound pvc-76111f79-93f4-4021-87d9-120f46906a40 1Gi RWO standard 11h[vagrant@control-plane ~]$ kubectl get pvNAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGEpvc-3a23d929-5e5e-4d28-bc30-c862a6b4dec7 1Gi RWO Delete Bound default/data-mehdb-3 standard 11hpvc-74df0444-0c0b-4509-ab07-dc13c8384079 1Gi RWO Delete Bound default/data-mehdb-2 standard 11hpvc-76111f79-93f4-4021-87d9-120f46906a40 1Gi RWO Delete Bound default/data-mehdb-4 standard 11hpvc-a4f548fa-5685-441e-883d-442d4e8f60f3 1Gi RWO Delete Bound default/data-mehdb-1 standard 12hpvc-ae4c72bc-5821-485b-ba74-b86f3b21b8de 1Gi RWO Delete Bound default/data-mehdb-0 standard 12h

可以看到, 当删除StatefulSet mehdb的时候, StatefulSet和相应的Pod 都会被立刻删除; 但是相应的PV和PVC则没有被删除, 需要单独删除.

这其实为了数据安全考虑,手工逐一删除就好了.

好了, 对于StatefulSet的操作部分, 先介绍到这里吧, 下面学习下理论知识.

什么是StatefulSets

StatefulSet 是用来管理有状态应用的工作负载 API 对象。

StatefulSet 用来管理某 Pod 集合的部署和扩缩, 并为这些 Pod 提供持久存储和持久标识符。

和 Deployment 类似, StatefulSet 管理基于相同容器规约的一组 Pod。但和 Deployment 不同的是, StatefulSet 为它们的每个 Pod 维护了一个有粘性的 ID。这些 Pod 是基于相同的规约来创建的, 但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID。

如果希望使用存储卷为工作负载提供持久存储,可以使用 StatefulSet 作为解决方案的一部分。 尽管 StatefulSet 中的单个 Pod 仍可能出现故障, 但持久的 Pod 标识符使得将现有卷与替换已失败 Pod 的新 Pod 相匹配变得更加容易

StatefulSets 对于需要满足以下一个或多个需求的应用程序很有价值:

稳定的、唯一的网络标识符。稳定的、持久的存储。有序的、优雅的部署和缩放。有序的、自动地滚动更新。

在上面描述中,“稳定的”意味着 Pod 调度或重调度的整个过程是有持久性的。 如果应用程序不需要任何稳定的标识符或有序的部署、删除或伸缩,则应该使用 由一组无状态的副本控制器提供的工作负载来部署应用程序,比如 Deployment 或者 ReplicaSet 可能更适用于你的无状态应用部署需要。

使用StatefulSet的限制

给定 Pod 的存储必须由 PersistentVolume 驱动 基于所请求的 storage 来提供,或者由管理员预先提供。删除或者收缩 StatefulSet 并不会删除它关联的存储卷。 这样做是为了保证数据安全,它通常比自动清除 StatefulSet 所有相关的资源更有价值。StatefulSet 当前需要无头服务 来负责 Pod 的网络标识。你需要负责创建此服务。当删除 StatefulSets 时,StatefulSet 不提供任何终止 Pod 的保证。 为了实现 StatefulSet 中的 Pod 可以有序地且体面地终止,可以在删除之前将 StatefulSet 缩放为 0。在默认 Pod 管理策略(OrderedReady) 时使用 滚动更新,可能进入需要人工干预 才能修复的损坏状态。

StatefulSet 中的Pod 选择算符

你必须设置 StatefulSet 的 .spec.selector 字段,使之匹配其在 .spec.template.metadata.labels 中设置的标签。在 Kubernetes 1.8 版本之前, 被忽略 .spec.selector 字段会获得默认设置值。 在 1.8 和以后的版本中,未指定匹配的 Pod 选择器将在创建 StatefulSet 期间导致验证错误。

Pod 标识

StatefulSet Pod 具有唯一的标识,该标识包括顺序标识、稳定的网络标识和稳定的存储。 该标识和 Pod 是绑定的,不管它被调度在哪个节点上。

有序索引

对于具有 N 个副本的 StatefulSet,StatefulSet 中的每个 Pod 将被分配一个整数序号, 从 0 到 N-1,该序号在 StatefulSet 上是唯一的。

稳定的网络 ID

StatefulSet 中的每个 Pod 根据 StatefulSet 的名称和 Pod 的序号派生出它的主机名。 组合主机名的格式为$(StatefulSet 名称)-$(序号)。 上例将会创建三个名称分别为 mehdb-0、mehdb-1 的 Pod。

StatefulSet 可以使用 无头服务 控制它的 Pod 的网络域。管理域的这个服务的格式为:

$(服务名称).$(命名空间).svc.cluster.local

其中 cluster.local 是集群域。 一旦每个 Pod 创建成功,就会得到一个匹配的 DNS 子域,格式为:

$(pod 名称).$(所属服务的 DNS 域名)

其中所属服务由 StatefulSet 的 serviceName 域来设定。

稳定的存储

Kubernetes 为每个 VolumeClaimTemplate 创建一个 PersistentVolume。 在上面的 nginx 示例中,每个 Pod 将会得到基于 StorageClass my-storage-class 提供的 1 Gib 的 PersistentVolume。如果没有声明 StorageClass,就会使用默认的 StorageClass。 当一个 Pod 被调度(重新调度)到节点上时,它的 volumeMounts 会挂载与其 PersistentVolumeClaims 相关联的 PersistentVolume。 请注意,当 Pod 或者 StatefulSet 被删除时,与 PersistentVolumeClaims 相关联的 PersistentVolume 并不会被删除。要删除它必须通过手动方式来完成。

Pod 名称标签

当 StatefulSet 控制器(Controller) 创建 Pod 时, 它会添加一个标签 statefulset.kubernetes.io/pod-name,该标签值设置为 Pod 名称。 这个标签允许你给 StatefulSet 中的特定 Pod 绑定一个 Service。

StatefulSet 部署和扩缩保证对于包含 N 个 副本的 StatefulSet,当部署 Pod 时,它们是依次创建的,顺序为 0..N-1。当删除 Pod 时,它们是逆序终止的,顺序为 N-1..0。在将缩放操作应用到 Pod 之前,它前面的所有 Pod 必须是 Running 和 Ready 状态。在 Pod 终止之前,所有的继任者必须完全关闭。

StatefulSet 不应将 pod.Spec.TerminationGracePeriodSeconds 设置为 0。 这种做法是不安全的,要强烈阻止。更多的解释请参考 强制删除 StatefulSet Pod。

强制删除 StatefulSet 类型的 Pods

在 StatefulSet 的正常操作中,永远不需要强制删除 StatefulSet 管理的 Pod。 StatefulSet 控制器负责创建、 扩缩和删除 StatefulSet 管理的 Pods。它尝试确保指定数量的从序数 0 到 N-1 的 Pod 处于活跃状态并准备就绪。StatefulSet 确保在任何时候,集群中最多只有一个具有给定标识的 Pod。 这就是所谓的由 StatefulSet 提供的*最多一个(At Most One)*的语义。

应谨慎进行手动强制删除操作,因为它可能会违反 StatefulSet 固有的至多一个的语义。 StatefulSets 可用于运行分布式和集群级的应用,这些应用需要稳定的网络标识和可靠的存储。 这些应用通常配置为具有固定标识固定数量的成员集合。 具有相同身份的多个成员可能是灾难性的,并且可能导致数据丢失 (例如:票选系统中的恶裂场景)。

如何安全地强制删除StatefulSet中的Pod

强制删除不会等待来自 kubelet 对 Pod 已终止的确认消息。 无论强制删除是否成功杀死了 Pod,它都会立即从 API 服务器中释放该名字。 这将让 StatefulSet 控制器创建一个具有相同标识的替身 Pod;因而可能导致正在运行 Pod 的重复, 并且如果所述 Pod 仍然可以与 StatefulSet 的成员通信,则将违反 StatefulSet 所要保证的 最多一个的语义。

当你强制删除 StatefulSet 类型的 Pod 时,你要确保有问题的 Pod 不会再和 StatefulSet 管理的其他 Pod 通信并且可以安全地释放其名字以便创建替代 Pod。

如果要使用 kubectl 1.5 以上版本强制删除 Pod,请执行下面命令:

kubectl delete pods <pod> --grace-period=0 --force

如果你使用 kubectl 的 1.4 以下版本,则应省略 --force 选项:

kubectl delete pods <pod> --grace-period=0

如果在这些命令后 Pod 仍处于 Unknown 状态,请使用以下命令从集群中删除 Pod:

kubectl patch pod <pod> -p '{"metadata":{"finalizers":null}}'

请始终谨慎地执行强制删除 StatefulSet 类型的 pods,并且能够完全了解所涉及的风险。

好了, 关于StatefulSet, 我们先简单地介绍到这里吧, 后续会更加深入的例子, 通过创建一个有状态应用的方式,来更好地展示这部分知识.

回顾

本篇文章中简单介绍了StatefulSet 的用法, 展示了StatefulSet 的创建, 扩容, 以及和Deployment的不同之处. 在最后的理论部分, 指出了在删除StatefulSet 里面的Pod 时候的注意事项.

不积跬步无以至千里,继续学习Kubernetes 吧!

0 阅读:0

查理谈科技

简介:感谢大家的关注