k8s控制器:StatefulSet
一、Statefulset控制器概述
StatefulSet是为了管理有状态服务的问题而设计的
1.1、有状态/无状态服务
1)有状态服务:StatefulSet是有状态的集合,管理有状态的服务,它所管理的Pod的名称不能随意变化。数据持久化的目录也是不一样,每一个Pod都有自己独有的数据持久化存储目录。比如MySQL主从、redis集群等
2)无状态服务:RC、Deployment、DaemonSet都是管理无状态的服务,它们所管理的Pod的IP、名字,启停顺序等都是随机的。个体对整体无影响,所有pod都是共用一个数据卷的,部署的tomcat就是无状态的服务,tomcat被删除,在启动一个新的tomcat,加入到集群即可,跟tomcat的名字无关。
1.2、Headless service
1.2.1、什么是Headless service
Headless service
不分配clusterIP,headless service可以通过解析service的DNS,返回所有Pod的dns和ip地址
(statefulSet部署的Pod才有DNS),普通的service,只能通过解析service的DNS返回service的ClusterIP。
1.2.2、为什么要用headless service
在使用Deployment时,创建的Pod名称是没有顺序的,是随机字符串,在用statefulset管理pod时要求pod名称必须是有序的 ,每一个pod不能被随意取代,pod重建后pod名称还是一样的。因为pod IP是变化的,所以要用Pod名称来识别。pod名称是pod唯一性的标识符,必须持久稳定有效。这时候要用到无头服务,它可以给每个Pod一个唯一的名称。
1)headless service会为service分配一个域名:<service name>.$<namespace name>.svc.cluster.local
2)StatefulSet会为关联的Pod保持一个不变的Pod Name:$(StatefulSet name)-$(pod序号)
3)StatefulSet会为关联的Pod分配一个dnsName:$<Pod Name>.$<service name>.$<namespace name>.svc.cluster.local
1.3、volumeClaimTemplate
对于有状态应用都会用到持久化存储,比如mysql主从,由于主从数据库的数据是不能存放在一个目录下的,每个mysql节点都需要有自己独立的存储空间。而在deployment中创建的存储卷是一个共享的存储卷,多个pod使用同一个存储卷,它们数据是同步的,而statefulset定义中的每一个pod都不能使用同一个存储卷,这就需要使用volumeClainTemplate,当在使用statefulset创建pod时,volumeClainTemplate会自动生成一个PVC,从而请求绑定一个PV,每一个pod都有自己专用的存储卷。Pod、PVC和PV对应的关系图如下:
二、Statefulset使用案例:部署web站点
# 创建存储类
[root@k8s-master1 ~]# cat class-web.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-web
provisioner: example.com/nfs
# 更新资源清单文件
[root@k8s-master1 ~]# kubectl apply -f class-web.yaml
[root@k8s-master1 ~]# kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-web example.com/nfs Delete Immediate false 30s
# 编写一个Statefulset资源清单文件
[root@k8s-master1 ~]# cat statefulset.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:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: "nfs-web"
resources:
requests:
storage: 1Gi
# 更新资源清单文件
[root@k8s-master1 ~]# kubectl apply -f statefulset.yaml
service/nginx created
statefulset.apps/web created
# 查看无头服务
[root@k8s-master1 ~]# kubectl describe svc nginx
Name: nginx
Namespace: default
Labels: app=nginx
Annotations: <none>
Selector: app=nginx
Type: ClusterIP
IP Families: <none>
IP: None
IPs: None
Port: web 80/TCP
TargetPort: 80/TCP
Endpoints: 10.244.36.122:80,10.244.36.123:80
Session Affinity: None
Events: <none>
# 查看statefulset是否创建成功
[root@k8s-master1 ~]# kubectl get statefulset
NAME READY AGE
web 2/2 13s
# 查看pod: 可以看到创建的pod是有序的
[root@k8s-master1 ~]# kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 35s
web-1 1/1 Running 0 29s
# 查看headless service
[root@k8s-master1 ~]# kubectl get pods -l app=nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 4m22s 10.244.36.122 k8s-node1 <none> <none>
web-1 1/1 Running 0 4m16s 10.244.36.123 k8s-node1 <none> <none>
# 查看pvc
[root@k8s-master1 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-de5c7090-9d53-4597-b292-233f27534764 1Gi RWO nfs-web 92s
www-web-1 Bound pvc-b3a2c509-f011-448e-9091-afa76726ef40 1Gi RWO nfs-web 87s
# 查看pv
[root@k8s-master1 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-b3a2c509-f011-448e-9091-afa76726ef40 1Gi RWO Delete Bound default/www-web-1 nfs-web 2m2s
pvc-de5c7090-9d53-4597-b292-233f27534764 1Gi RWO Delete Bound default/www-web-0 nfs-web 2m8s
# 查看pod主机名
[root@k8s-master1 ~]# for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname';done
web-0
web-1
# 使用kubectl run运行一个提供nslookup命令的容器的,这个命令来自于dnsutils包,通过对pod主机名执行nslookup,可以检查它们在集群内部的DNS地址:
[root@k8s-master1 ~]# kubectl exec -it web-1 -- /bin/bash
root@web-1:/# apt-get update
root@web-1:/# apt-get install dnsutils -y
root@web-1:/# nslookup web-0.nginx.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: web-0.nginx.default.svc.cluster.local
# statefulset创建的pod也是有dns记录的
Address: 10.244.36.122 #解析的是pod的ip地址
root@web-1:/# nslookup nginx.default.svc.cluster.local
Server: 10.96.0.10
Address: 10.96.0.10#53
Name: nginx.default.svc.cluster.local #查询service dns,会把对应的pod ip解析出来
Address: 10.244.36.123
Name: nginx.default.svc.cluster.local
Address: 10.244.36.122
root@web-1:/# dig -t A nginx.default.svc.cluster.local @10.96.0.10
; <<>> DiG 9.11.5-P4-5.1+deb10u5-Debian <<>> -t A nginx.default.svc.cluster.local @10.96.0.10
;; global options: +cmd
;; Got answer:
;; WARNING: .local is reserved for Multicast DNS
;; You are currently testing what happens when an mDNS query is leaked to DNS
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 57675
;; flags: qr aa rd; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1
;; WARNING: recursion requested but not available
;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 4096
; COOKIE: 01450c2da9081da1 (echoed)
;; QUESTION SECTION:
;nginx.default.svc.cluster.local. IN A
;; ANSWER SECTION:
nginx.default.svc.cluster.local. 30 IN A 10.244.36.123
nginx.default.svc.cluster.local. 30 IN A 10.244.36.122
;; Query time: 1 msec
;; SERVER: 10.96.0.10#53(10.96.0.10)
;; WHEN: Sun Jul 11 00:34:04 UTC 2021
;; MSG SIZE rcvd: 166
三、Statefulset管理pod
3.1、实现Pod的扩缩容
方式一:修改配置文件里的replicas
,然后kubectl apply
方式二:kubectl edit sts web
修改replicas
3.2、实现Pod的版本更新
方式一:修改配置文件里的image
,然后kubectl apply
方式二:kubectl edit sts web
修改image