第20关 有状态服务StatefulSet
快速掌握K8S下的有状态服务StatefulSet
前面我们讲到了Deployment、DaemonSet都只适合用来跑无状态的服务pod,那么这里的StatefulSet(简写sts)就是用来跑有状态服务pod的。
那怎么理解有状态服务和无状态服务呢?简单快速的理解为:无状态服务最典型的是WEB服务器的每次http请求,它的每次请求都是全新的,和之前的没有关系;那么有状态服务用网游服务器来举例比较恰当了,每个用户的登陆请求,服务端都是先根据这个用户之前注册过的帐号密码等信息来判断这次登陆请求是否正常。
无状态服务因为相互之前都是独立的,很适合用横向扩充来增加服务的资源量
还有一个很形象的比喻,在K8s的无状态服务的pod有点类似于农村圈养的牲畜,饲养它们的人不会给它们每个都单独取个名字(pod都是随机名称,IP每次发生重启也是变化的),当其中一只病了或被卖了,带来的感观只是数量上的减少,这时再买些相应数量的牲畜回来就可以回到之前的状态了(当一个pod因为某些原来被删除掉的时候,K8s会启动一个新的pod来代替它);而有状态服务的pod就像养的一只只宠物,主人对待自己喜欢的宠物都会给它们取一个比较有特色的名字(在K8s上运行的有状态服务的pod,都会被给予一个独立的固定名称),并且每只宠物都有它独特的外貌和性格,如果万一这只宠物丢失了,那么需要到宠物店再买一只同样品种同样毛色的宠物来代替了(当有状态服务的pod删除时,K8s会启动一个和先前一模一样名称的pod来代替它)。
有状态服务sts比较常见的mongo复制集 ,redis cluster,rabbitmq cluster等等,这些服务基本都会用StatefulSet模式来运行,当然除了这个,它们内部集群的关系还需要一系列脚本或controller来维系它们间的状态,这些会在后面进阶课程专门来讲,现在为了让大家先更好的明白StatefulSet,我这里直接还是用nginx服务来实战演示
redis 集群需要选举master
那个是主那个是从,涉及状态的维护
可以用更好的Operator来部署redis、mongo
1、创建pv
先创建好挂载的目录
# mkdir -p /nfs_dir/{web-pv0,web-pv1}
echo web-pv0 > /nfs_dir/web-pv0/index.html
echo web-pv1 > /nfs_dir/web-pv1/index.html# cat web-pv.yaml
# mkdir -p /nfs_dir/{web-pv0,web-pv1}
apiVersion: v1
kind: PersistentVolume
metadata:
name: web-pv0
labels:
type: web-pv0
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: my-storage-class
nfs:
path: /nfs_dir/web-pv0
server: 10.0.1.201
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: web-pv1
labels:
type: web-pv1
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: my-storage-class
nfs:
path: /nfs_dir/web-pv1
server: 10.0.1.201kubectl apply -f web-pv.yaml
# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
web-pv0 1Gi RWO Retain Available my-storage-class 4s
web-pv1 1Gi RWO Retain Available my-storage-class 4s2、创建pvc
(这一步可以省去让其自动创建,这里手动创建是为了让大家能更清楚在sts里面pvc的创建过程)
这一步非常非常的关键,因为如果创建的PVC的名称和StatefulSet中的名称没有对应上, 那么StatefulSet中的Pod就肯定创建不成功.
我们在这里创建了一个叫做www-web-0和www-web-1的PVC,这个名字是不是很奇怪, 而且在这个yaml里并没有提到PV的名字,所以PV和PVC是怎么bound起来的呢? 是通过labels标签下的key:value键值对来进行匹配的, 我们在创建PV时指定了label的键值对,在PVC里通过selector可以指定label。
然后再回到这个PVC的名称定义:www-web-0,为什么叫这样一个看似有规律的名字呢, 这里需要看看下面创建StatefulSet中的yaml, 首先我们看到StatefulSet的name叫web,设置的replicas为2个,(Pod's name会自动排序命名) volumeMounts和volumeClaimTemplates的name必须相同,为www, 所以StatefulSet创建的第一个Pod的name应该为web-0,第二个为web-1。
这里StatefulSet中的Pod与PVC之间的绑定关系是通过名称来匹配的,即:
PVC_name = volumeClaimTemplates_name + "-" + pod_name
www-web-0 = www + "-" + web-0
www-web-1 = www + "-" + web-1StatefulSet中的Pod没有指定使用具体那个PVC,但有storageClassName: "my-storage-class",就会去找my-storage-class属性的PVC,再具体是那个PVC就根据当前volumeClaimTemplates_name和pod_name组合出来的名称去寻找对应的PVC,所以要先按照StatefulSet配置来创建PVC
# cat web-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: www-web-0
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: my-storage-class
selector:
matchLabels:
type: web-pv0
---
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: www-web-1
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: my-storage-class
selector:
matchLabels:
type: web-pv1kubectl create ns test-statefulset
kubectl -n test-statefulset apply -f web-pvc.yaml
# kubectl -n test-statefulset get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound web-pv0 1Gi RWO my-storage-class 6s
www-web-1 Bound web-pv1 1Gi RWO my-storage-class 6s3、创建Service 和 StatefulSet
在上一步中我们已经创建了名为www-web-0的PVC了,接下来创建一个service和statefulset,
service的名称可以随意取,但是statefulset的名称已经定死了,为web,
并且statefulset中的volumeClaimTemplates_name必须为www,volumeMounts_name也必须为www。
只有这样,statefulset中的pod才能通过命名来匹配到PVC,否则会创建失败。
# cat web.yaml
apiVersion: v1
kind: Service
metadata:
name: web-headless
labels:
app: nginx
spec:
ports:
- port: 80
name: web
# 无头服务,用于拼接域名具体访问那个Pod
clusterIP: None
selector:
app: nginx
---
apiVersion: v1
kind: Service
metadata:
name: web
labels:
app: nginx
spec:
ports:
- port: 80
name: web
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx # has to match .spec.template.metadata.labels
serviceName: "web-headless"
replicas: 2 # by default is 1
template:
metadata:
labels:
app: nginx # has to match .spec.selector.matchLabels
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: nginx:1.21.6
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "my-storage-class"
resources:
requests:
storage: 1Gi# kubectl -n test-statefulset apply -f web.yaml
service/web-headless created
service/web created
statefulset.apps/web created
# kubectl -n test-statefulset get pod
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 6s
web-1 1/1 Running 0 5s
# kubectl -n test-statefulset get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
web ClusterIP 10.68.94.9 <none> 80/TCP 14s
web-headless ClusterIP None <none> 80/TCP 14s测试负载均衡
# curl 10.68.94.9
web-pv1
# curl 10.68.94.9
web-pv0
# curl 10.68.94.9
web-pv1运行一个busybox容器来测试下指定Pod
# kubectl run busybox --rm -it --image=registry.cn-shanghai.aliyuncs.com/acs/busybox:v1.29.2 -- sh
If you don't see a command prompt, try pressing enter.
/ # wget web-0.web-headless.test-statefulset
Connecting to web-0.web-headless.test-statefulset (172.20.139.113:80)
index.html 100% |***************************************| 8 0:00:00 ETA
/ # cat index.html
web-pv0
/ # rm index.html
/ # wget web-1.web-headless.test-statefulset
Connecting to web-1.web-headless.test-statefulset (172.20.217.92:80)
index.html 100% |***************************************| 8 0:00:00 ETA
/ # cat index.html
web-pv1
/ #