对于我们部署的应⽤,⼤体可以分为两类,⼀类是⽆状态应⽤,⼀类是有状态应⽤
无状态应用:像web这种类型的应⽤程序,称为无状态应用,他们不需要存储任何数据⾄本地,也没有任何启动顺序之分和主从角色之分,像这类应⽤,就可以通过RS、Deployment、DaemonSet控制器来进⾏编排和部署,它们所管理的Pod的IP、名字、启动顺序都是随机的。
有状态应用:像MySQL、Redis这类需要存储数据的应⽤程序,称为有状态应用,它们有主从⻆⾊之分,⼜存在先后启动顺序,像这类应用,需要通过StatefulSet控制器来编排和部署。
StatefulSet介绍
在k8s中,StatefulSet 控制器用于管理有状态应用的控制器。它的主要特点如下:
稳定的网络标识:StatefulSet管理的Pod拥有固定的名称,Pod名字称为网络标识,可以通过DNS查询来发现每个Pod。
持久化存储:StatefulSet为每个Pod关联一个PV,实现数据的持久化,并且当Pod故障被重建后,依然会自动使用此前的PV保存数据。
有序部署和扩展:StatefulSet可以定义Pod先后顺序来部署和更新Pod。在扩展时,可以确保有序的扩展,且新生成的Pod名称与现有的Pod保持一致。
有序的缩容:当缩减 StatefulSet 时,它会按照与创建相反的顺序逐个删除其管理的 Pod。这确保了有状态应用的有序关闭和清理。
StatefulSet组成
Headless Service(无头服务):Headless Service给每个Pod分配一个唯一的DNS名称,用来定义Pod的网络标识。如果Pod重建,Pod IP是变化的,但是Pod名称是不变的,所以是以Pod名称来识别。
VolumeClaimTemplate(存储卷申请模板):有状态应用需要持久的存储,对于分布式系统来讲,每个Pod的数据也不一样,所以每个Pod需要有自己的存储卷,而不是共用同一个存储卷。所以statefulset通过VolumeClaimTemplate为每个Pod生成不同的PVC,并绑定PV来实现各Pod有专用存储。
StatefulSet案例
通过前边StorageClass环境为StatefulSet提供动态存储,并通过StatefulSet部署MySQL。
步骤1:创建一个Headless Service给每个Pod分配一个唯一的DNS名称,与在Deployment中的Service的区别是Headless Service没有Cluster IP。
# cat mysql-svc.yml
apiVersion: v1
kind: Service
metadata:
name: mysql-svc
namespace: test
spec:
clusterIP: None #不分配ClusterIP,表示这是一个headless服务,headless服务没有自己的ClusterIP,它直接返回后端Pod的IP地址。
selector:
app: mysql
ports:
- port: 3306
targetPort: 3306#创建Service
kubectl create -f mysql-svc.yml
#查看Service
kubectl get svc -n test
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
mysql-svc ClusterIP None <none> 3306/TCP 5s步骤2:通过StatefulSet部署MySQL。
# cat mysql-sts.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
namespace: test
spec:
selector:
matchLabels:
app: mysql
serviceName: "mysql-svc" #声明它属于哪个Service
replicas: 3 #副本数
template:
metadata:
labels:
app: mysql #要与Service中selector保持一致
spec:
containers:
- name: mysql
image: mysql:5.7
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
volumeMounts:
- name: mysql-pvc #挂载pcv,与模板中定义pvc名称一致
mountPath: /var/lib/mysql #挂载到容器的目录
volumeClaimTemplates: #pvc的模板
- metadata:
name: mysql-pvc #定义pvc的名称
spec:
accessModes: [ "ReadWriteOnce" ] #读、写权限,只能被一个节点挂载
storageClassName: "nfs-client" #存储类名,通过kubectl get sc查看
resources:
requests:
storage: 6Gi #请求的存储空间大小#另开一个终端监控Pod创建过程(Pod是有序创建的)
kubectl get pod -w -n test
#创建Pod
kubectl create -f mysql-sts.yml
#查看Pod信息
kubectl get pod -n test
NAME READY STATUS RESTARTS AGE
mysql-0 1/1 Running 0 2m59s
mysql-1 1/1 Running 0 2m56s
mysql-2 1/1 Running 0 2m53s对于N个副本的StatefulSet,每个Pod都在[0,N)的范围内分配一个数字序号,且是唯一的;
Headless Service访问方式pod-name.service-name.namespace.svc.cluster.local
dig mysql-0.mysql-svc.test.svc.cluster.local @10.96.0.2 +short提示:10.96.0.2是集群的clusterDNS地址,部署集群时,在kubelet.json文件中定义过。
#查看Pod所使用的pvc(每个Pod都有各自的存储卷)
kubectl get pvc -n test
NAME STATUS VOLUME
mysql-pvc-mysql-0 Bound pvc-027fc813-3d9c-4497-8aed-11c3eb4b99ac 6Gi
mysql-pvc-mysql-1 Bound pvc-4ff366a6-1d7c-4cc3-ba04-8d475925924b 6Gi
mysql-pvc-mysql-2 Bound pvc-c79048bd-9eed-4630-b79c-32412f28dcdf 6Gi
#查看每个pvc对应的pv信息
kubectl get pv
NAME STATUS CLAIM
pvc-027fc813-3d9c-4497-8aed-11c3eb4b99ac Bound test/mysql-pvc-mysql-0
pvc-4ff366a6-1d7c-4cc3-ba04-8d475925924b Bound test/mysql-pvc-mysql-1
pvc-c79048bd-9eed-4630-b79c-32412f28dcdf Bound test/mysql-pvc-mysql-2
#删除mysql-0的Pod,验证Pod名称是否一致,且是否还使用此前的存储卷保存数据
kubectl delete pod mysql-0 -n test
#查看Pod,验证Pod名称是否一致,验证是否还使用此前的存储卷保存数据
kubectl get pod -n test
NAME READY STATUS RESTARTS AGE
mysql-0 1/1 Running 0 3s
mysql-1 1/1 Running 0 16m
mysql-2 1/1 Running 0 16m扩展Pod,验证Pod的名称是否与现有的Pod保持一致
#查看sts信息
kubectl get sts -n test
#通过edit扩容Pod数量
kubectl edit sts mysql -n test
...
replicas: 4#查看Pod
kubectl get pod -n test
NAME READY STATUS RESTARTS AGE
mysql-0 1/1 Running 0 2m27s
mysql-1 1/1 Running 0 28m
mysql-2 1/1 Running 0 28m
mysql-3 1/1 Running 0 4s缩减Pod,验证它是否会按照与创建相反的顺序逐个删除其管理的Pod
#另开一个终端监控Pod缩减过程(Pod是有序创建的)
kubectl get pod -w -n test
#通过edit缩减Pod数量
kubectl edit sts mysql -n test
...
replicas: 1#查看Pod
kubectl get pod -n test
NAME READY STATUS RESTARTS AGE
mysql-0 1/1 Running 0 2m18s提示:此前Pod所使用的存储并不会删除。如果不在需要可手动删除,先删除pvc(pv会自动删除)在删除后端存储服务器对应的目录。
总结:StatefulSet虽然在⼀定程度上能够实现有状态应⽤的管理,但目前为止将数据库容器化是非常不合理的,容器最初是针对无状态的应用而设计的,而且数据库(特别是关系型数据库)对读写(I/O)的要求较高,为了避免资源竞争,生产环境都会将数据独立出来,部署到物理机或虚拟机中。
评论区