一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第N天,点击查看活动详情。
一、StatefulSet
简介
有状态应用在 Kubernetes
里对应的对象就是 StatefulSet
。
跟
Deployment
一样,StatefulSet
也是一种控制器。 区别是一个控制无状态应用,一个控制有状态应用。
当一个有状态的 Pod
挂掉之后(或者是所在节点出现故障),这个 Pod
实例必须在其它的节点上重建,但是新的实例必须和被替换的实例拥有相同的名称、网络标识和状态,StatefulSet
可以保证 Pod
在重新调度以后保留它们之前的标识和状态,这样就可以方便的扩缩容。
StatefulSet
适合具有以下需求的场景:
- 稳定的持久化存储
Pod
在重新调度之后还能继续访问之前的存储数据 - 稳定的网络标志
Pod
在重新调度后其名字和主机名保持不变 - 有序部署:构成应用的多个
Pod
启动顺序是固定的,适合Pod
之间有依赖关系的情况 - 有序删除:删除应用时
Pod
销毁顺序跟启动顺序正好相反
StatefulSet
中每个 Pod
具有固定的名字,Pod
的 DNS
域名格式为 statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local
。跟普通域名一样,子域在前,主域在后,只不过节数比较多而已。
各部分含义如下:
statefulSetName
:StatefulSet
名字0..N-1 Pod
的序号:从 0 到 N-1serviceName
:Headless Service
(没有ClusterIP
的Service
) 名字,StatefulSet
无法使用普通的Service
(有一个ClusterIP
作为服务入口) 来提供应用服务(包括负载均衡)。这是因为StatefulSet
里的各个Pod
不是对等的,只能由用户自己来实现服务内部的网络路由namespace
:服务所在的命名空间,Headless Service
和StatefulSet
必须在相同的命名空间下svc.cluster.local
:集群域名
StatefulSet
的设置由以下 3 部分组成:
- 用于定义网络标志的
Headless Service
- 用于创建
PV
(持久存储卷)的volumeClaimTemplates
- 定义具体应用的
StatefulSet
(1)Headless Service(无头服务)
Headless Service
是指不为 Service
设置 ClusterIP
(入口 IP
地址),只通过标签选择器将后端的 Pod
列表返回给调用的客户端,客户端自行决定如何处理这个 Pod
列表。
在 Deployment
,每个创建的 Pod
的名称都是没有顺序的,名称最后有一截 Hash
值,因此这样创建的 Pod
是无序的。但是 StatefulSet
中要求 Pod
名称必须有序,每个 Pod
名称具有唯一性,并且当 Pod
重建后名称依然要保持一致,Pod
的名称就是唯一性的标志,必须持久有效,因此会使用 Headless Service
服务。
(2)volumeClaimTemplates(卷申请模板)
有状态自然是需要持久化的,所以 StatefulSet
引入了 PV
(持久存储卷) 和 PVC
(持久存储卷声明) 对象来持久存储服务产生的状态,这样即便服务可能被 kill
掉或是重启,但是由于数据保存在 PV
中,所以这些状态也不会丢失。
在 Deployment
时,直接在 Pod template
中定义存储卷,所有副本集使用同一个存储卷,数据都是相同的。但是 StatefulSet
中要求有状态存储,也就意味着数据不一样,每个节点必须有专有存储卷,不能使用相同存储卷,因此需要使用 volumeClaimTemplates
,为每个 Pod
生成不同的 PVC
,并绑定 PV
,这样就实现了 Pod
分别有专属的存储卷。
二、运行 nginx
实例
整个流程:
- 创建本地持久存储卷
- 将本地持久存储卷绑定到
Node
上 - 创建
StatefulSet
- 创建
local PV
,新建local-storage.yaml
文件
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-storage
# provisioner 的值为 kubernetes.io/no-provisioner,因为 local pv 不支持 Dynamic Provisioning,不能在创建 pvc 的时候自动创建 pv
provisioner: kubernetes.io/no-provisioner
# 使用延迟卷绑定功能,将 volume binding 延迟至 pod scheduling 阶段执行
volumeBindingMode: WaitForFirstConsumer
- 执行创建
$ kubectl create -f local-storage.yaml
storageclass.storage.k8s.io/local-storage created
- 在每个
Node
上创建一个PV
,新建local-pv.yaml
文件
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv1
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
# local.path 指明对应的磁盘路径
local:
path: /tmp
# spec.nodeAffinity 指定对应的 node
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- kube-node-1
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: local-pv2
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: local-storage
local:
path: /tmp
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- kube-node-2
上面的配置会在
kube-node-1
和kube-node-2
两个Node
上分别创建一个PV
。其中,存储类spec.storageClassName
用的是前面启用的local-storage
,spec.capacity.storage
指定了容量为 1G,挂载路径spec.local.path
为/tmp
(该路径需要在Node
上存在)。
- 执行创建
$ kubectl create -f local-pv.yaml
persistentvolume/local-pv1 created
persistentvolume/local-pv2 created
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
local-pv1 1Gi RWO Retain Available local-storage 2m47s
local-pv2 1Gi RWO Retain Available local-storage 2m47s
- 创建有状态集合
web.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: registry-vpc.cn-hangzhou.aliyuncs.com/chenshi-kubernetes/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-storage
resources:
requests:
storage: 10Mi
其中
Service
的clusterIP
指定为None
,表示它是一个Headless Service
,集群不用为它分配IP
。StatefulSet
包含 2 个Pod
副本,它们的名字分别为web-0
和web-1
,并且都挂载了一个PV
。不管Pod
被重新调度到哪个节点,Pod
的名字和挂载的PV
都将跟之前保持一致。
- 创建之前可以监听
Pod
的创建过程,在一个新的终端执行:
kubectl get pods -w -l app=nginx
- 执行创建
$ kubectl create -f web.yaml
service/nginx created
statefulset.apps/web created
- 查看创建的
Headless Service
和StatefulSet
$ kubectl get service nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 22s
$ kubectl get statefulset web
NAME READY AGE
web 2/2 37s
- 查看根据
volumeClaimTemplates
自动创建PVC
# PVC 名称的格式为:$(volumeClainTemplates.name)-(pod_name),这里 volumeClainTemplates.name 为 www,pod_name 为 web-[0-1]
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound local-pv1 1Gi RWO local-storage 2m53s
www-web-1 Bound local-pv2 1Gi RWO local-storage 2m44s
- 查看创建的
Pod
,都是有序的
# Pod 名称的格式为:$(StatefulSet 名称)-$(序号),这里为:web-0、web-1
$ kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 5m33s
web-1 1/1 Running 0 5m24s