在之前的文章中, 介绍了搭建好的#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/v1
kind: StatefulSet
metadata:
name: mehdb
spec:
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: v1
kind: Service
metadata:
name: mehdb
labels:
app: mehdb
spec:
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.yaml
statefulset.apps/mehdb created
service/mehdb created
然后检查一下相应的创建过程:
$ kubectl get sts,po,pvc,svc
NAME READY AGE
statefulset.apps/mehdb 2/2 4m23s
NAME READY STATUS RESTARTS AGE
pod/mehdb-0 1/1 Running 0 4m23s
pod/mehdb-1 1/1 Running 0 3m
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/data-mehdb-0 Bound pvc-ae4c72bc-5821-485b-ba74-b86f3b21b8de 1Gi RWO standard 4m23s
persistentvolumeclaim/data-mehdb-1 Bound pvc-a4f548fa-5685-441e-883d-442d4e8f60f3 1Gi RWO standard 3m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 443/TCP 52d
service/mehdb ClusterIP 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=full
0
pod "jumpod" deleted
注意上面的用法, 这里面通过kubectl run命令并指定镜像, 直接创建了一个jumpod, 而且使用--rm的参数, 让这个pod 在执行完之后就自动销毁.
我们来查看一下这个StatefulSet下面的Pod:
$ kubectl get sts
NAME? ? READY ? AGE
mehdb ? 2/2 ? ? 13m
[vagrant@control-plane ~]$ kubectl describe sts mehdb
Name: ? ? ? ? ? ? ? mehdb
Namespace:? ? ? ? ? default
CreationTimestamp:? Sat, 20 Mar 2021 14:08:09 +0000
Selector: ? ? ? ? ? app=mehdb
Labels: ? ? ? ? ? ?
Annotations:? ? ? ?
Replicas: ? ? ? ? ? 2 desired | 2 total
Update Strategy:? ? RollingUpdate
? Partition:? ? ? ? 0
Pods Status:? ? ? ? 2 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod 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:?
Volume Claims:
? Name:? ? ? ? ? data
? StorageClass: ?
? Labels:? ? ? ?
? Annotations: ?
? 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=mehdb
Name: ? ? ? ? mehdb-0
Namespace:? ? default
Priority: ? ? 0
Node: ? ? ? ? localhost.localdomain/10.0.2.15
Start Time: ? Sat, 20 Mar 2021 14:08:11 +0000
Labels: ? ? ? app=mehdb
? ? ? ? ? ? ? controller-revision-hash=mehdb-9b596f6c9
? ? ? ? ? ? ? statefulset.kubernetes.io/pod-name=mehdb-0
Annotations:?
Status: ? ? ? Running
IP: ? ? ? ? ? 172.17.0.5
IPs:
? IP: ? ? ? ? ? 172.17.0.5
Controlled By:? StatefulSet/mehdb
Containers:
? 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:? ? false
QoS Class: ? ? ? BestEffort
Node-Selectors:?
Tolerations: ? ? node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
?? ? ? ? ? ? ? ? node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
Events:
? 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-1
- mehdb-0
这个确实是和我们之前用Deployment 创建的Pod 名称不一样, 回想我们在Deployment 介绍那篇文章中创建的Pod, 创建的Pod 如下所示:
我们能看到, 使用Deployment 创建的Pod, 其网络名称的后缀是随机的, 这就是其中的区别.
我们再来尝试删掉一个Pod, 看看重新生成的Pod 会不会还是同一个:
$ kubectl delete pod/mehdb-1
pod "mehdb-1" deleted
[vagrant@control-plane ~]$ kubectl get pods
NAME? ? ? READY ? STATUS? ? RESTARTS ? AGE
mehdb-0 ? 1/1 ? ? Running ? 0? ? ? ? ? 55m
mehdb-1 ? 0/1 ? ? Running ? 0? ? ? ? ? 4s
[vagrant@control-plane ~]$ kubectl describe sts mehdb
Name: ? ? ? ? ? ? ? mehdb
Namespace:? ? ? ? ? default
CreationTimestamp:? Sat, 20 Mar 2021 14:08:09 +0000
Selector: ? ? ? ? ? app=mehdb
Labels: ? ? ? ? ? ?
Annotations:? ? ? ?
Replicas: ? ? ? ? ? 2 desired | 2 total
Update Strategy:? ? RollingUpdate
? Partition:? ? ? ? 0
Pods Status:? ? ? ? 2 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod 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:?
Volume Claims:
? Name:? ? ? ? ? data
? StorageClass: ?
? Labels:? ? ? ?
? Annotations: ?
? 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 ? AGE
mehdb-1 ? 0/1 ? ? Running ? 0? ? ? ? ? 4s
而且Pod mehdb在Age 字段上也和mehdb-0 有明显的不同, 这个在我们再次检查Pod 状态的时候就能看出来.
$ kubectl get pods
NAME? ? ? READY ? STATUS? ? RESTARTS ? AGE
mehdb-0 ? 1/1 ? ? Running ? 0? ? ? ? ? 63m
mehdb-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=5
statefulset.apps/mehdb scaled
$ kubectl get pods -w -l app=mehdb
NAME READY STATUS RESTARTS AGE
mehdb-0 1/1 Running 0 83m
mehdb-1 1/1 Running 0 27m
mehdb-2 0/1 Running 0 39s
mehdb-2 1/1 Running 0 44s
mehdb-3 0/1 Pending 0 0s
mehdb-3 0/1 Pending 0 0s
mehdb-3 0/1 Pending 0 2s
mehdb-3 0/1 ContainerCreating 0 2s
mehdb-3 0/1 Running 0 4s
mehdb-3 1/1 Running 0 31s
mehdb-4 0/1 Pending 0 0s
mehdb-4 0/1 Pending 0 0s
mehdb-4 0/1 Pending 0 3s
mehdb-4 0/1 ContainerCreating 0 3s
mehdb-4 0/1 Running 0 4s
mehdb-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/mehdb
statefulset.apps "mehdb" deleted
$ kubectl get sts
No resources found in default namespace.
$ kubectl get pods
No resources found in default namespace.
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-mehdb-0 Bound pvc-ae4c72bc-5821-485b-ba74-b86f3b21b8de 1Gi RWO standard 12h
data-mehdb-1 Bound pvc-a4f548fa-5685-441e-883d-442d4e8f60f3 1Gi RWO standard 12h
data-mehdb-2 Bound pvc-74df0444-0c0b-4509-ab07-dc13c8384079 1Gi RWO standard 11h
data-mehdb-3 Bound pvc-3a23d929-5e5e-4d28-bc30-c862a6b4dec7 1Gi RWO standard 11h
data-mehdb-4 Bound pvc-76111f79-93f4-4021-87d9-120f46906a40 1Gi RWO standard 11h
[vagrant@control-plane ~]$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-3a23d929-5e5e-4d28-bc30-c862a6b4dec7 1Gi RWO Delete Bound default/data-mehdb-3 standard 11h
pvc-74df0444-0c0b-4509-ab07-dc13c8384079 1Gi RWO Delete Bound default/data-mehdb-2 standard 11h
pvc-76111f79-93f4-4021-87d9-120f46906a40 1Gi RWO Delete Bound default/data-mehdb-4 standard 11h
pvc-a4f548fa-5685-441e-883d-442d4e8f60f3 1Gi RWO Delete Bound default/data-mehdb-1 standard 12h
pvc-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 class 来提供,或者由管理员预先提供。
- 删除或者收缩 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 --grace-period=0 --force
如果你使用 kubectl 的 1.4 以下版本,则应省略 --force 选项:
kubectl delete pods --grace-period=0
如果在这些命令后 Pod 仍处于 Unknown 状态,请使用以下命令从集群中删除 Pod:
kubectl patch pod -p '{"metadata":{"finalizers":null}}'
请始终谨慎地执行强制删除 StatefulSet 类型的 pods,并且能够完全了解所涉及的风险。
好了, 关于StatefulSet, 我们先简单地介绍到这里吧, 后续会更加深入的例子, 通过创建一个有状态应用的方式,来更好地展示这部分知识.
回顾
本篇文章中简单介绍了StatefulSet 的用法, 展示了StatefulSet 的创建, 扩容, 以及和Deployment的不同之处. 在最后的理论部分, 指出了在删除StatefulSet 里面的Pod 时候的注意事项.
不积跬步无以至千里,继续学习Kubernetes 吧!