Kubernetes之StatefulSet详解

本篇是基于k8s-v1.19.0版本

1.介绍

  • RC、Deployment、DaemonSet都是面向无状态的服务,它们所管理的Pod的IP、名字,启停顺序等都是随机的,而StatefulSet是什么?顾名思义,有状态的集合,管理所有有状态的服务,比如MySQL、MongoDB集群等。
  • StatefulSet本质上是Deployment的一种变体,在v1.9版本中已成为GA版本,它为了解决有状态服务的问题,它所管理的Pod拥有固定的Pod名称,启停顺序,在StatefulSet中,Pod名字称为网络标识(hostname),还必须要用到共享存储。
  • 在Deployment中,与之对应的服务是service,而在StatefulSet中与之对应的headless service,即无头服务,与service的区别就是它没有Cluster IP,解析它的名称时将返回该Headless Service对应的全部Pod的Endpoint列表。
  • 除此之外,StatefulSet在Headless Service的基础上又为StatefulSet控制的每个Pod副本创建了一个DNS域名,这个域名的格式为: $(podname).(headless server name) FQDN:$(podname).(headless server name).namespace.svc.cluster.local

2.特点

Pod一致性:包含次序(启动、停止次序)、网络一致性。此一致性与Pod相关,与被调度到哪个node节点无关;

  • 稳定的次序:对于N个副本的StatefulSet,每个Pod都在[0,N)的范围内分配一个数字序号,且是唯一的;
  • 稳定的网络:Pod的hostname模式为( StatefulSet名称 ) ? (statefulset名称)-(statefulset名称)?(序号);
  • 稳定的存储:通过VolumeClaimTemplate为每个Pod创建一个PV。删除、减少副本,不会删除相关的卷。

3.组成部分

  • Headless Service:用来定义Pod网络标识( DNS domain);
  • volumeClaimTemplates :存储卷申请模板,创建PVC,指定pvc名称大小,将自动创建pvc,且pvc必须由存储类供应;
  • StatefulSet :定义具体应用,名为Nginx,有三个Pod副本,并为每个Pod定义了一个域名部署statefulset。

1)为什么需要 headless service 无头服务?

在用Deployment时,每一个Pod名称是没有顺序的,是随机字符串,因此是Pod名称是无序的,但是在statefulset中要求必须是有序 ,每一个pod不能被随意取代,pod重建后pod名称还是一样的。而pod IP是变化的,
所以是以Pod名称来识别。pod名称是pod唯一性的标识符,必须持久稳定有效。这时候要用到无头服务,它可以给每个Pod一个唯一的名称 。

2)为什么需要volumeClaimTemplate?

对于有状态的副本集都会用到持久存储,对于分布式系统来讲,它的最大特点是数据是不一样的,所以各个节点不能使用同一存储卷,每个节点有自已的专用存储,但是如果在Deployment中的Pod template里定义的存储卷,
是所有副本集共用一个存储卷,数据是相同的,因为是基于模板来的 ,而statefulset中每个Pod都要自已的专有存储卷,所以statefulset的存储卷就不能再用Pod模板来创建了,于是statefulSet使用volumeClaimTemplate,称为卷申请模板,
它会为每个Pod生成不同的pvc,并绑定pv,从而实现各pod有专用存储。这就是为什么要用volumeClaimTemplate的原因。

4.StatefulSet详解

  • kubectl explain sts.spec :主要字段解释
  • replicas :副本数
  • selector:那个pod是由自己管理的
  • serviceName:必须关联到一个无头服务商
  • template:定义pod模板(其中定义关联那个存储卷)
  • volumeClaimTemplates :生成PVC

5.部署一个statefulset服务

本教程假设你的集群被配置为动态的提供 PersistentVolume,动态storeclass参考https://www.cnblogs.com/wuxinchun/p/15266445.html;如果没有这样配置,在开始本教程之前,你需要手动准备存储卷。
如果集群中没有StorageClass的动态供应PVC的机制,也可以提前手动创建多个PV、PVC,手动创建的PVC名称必须符合之后创建的StatefulSet命名规则:(volumeClaimTemplates.name)-(pod_name)

5.1statefulset控制器创建nginx

[root@k8s-master statefulset]# pwd
/root/k8s_practice/statefulset
[root@k8s-master statefulset]# cat nginx-statefulset.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-headless
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None   #注意此处的值,None表示无头服务
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx  #has to match .spec.template.metadata.labels
serviceName: "nginx-headless"
replicas: 3  #两个副本
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: ikubernetes/myapp:v1
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
annotations:
volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"   #managed-nfs-storage为我们创建的storage-class名称,动态storeclass参考https://www.cnblogs.com/wuxinchun/p/15266445.html
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
[root@k8s-master statefulset]# kubectl apply -f nginx-statefulset.yaml
service/nginx-headless created
statefulset.apps/web created

5.2验证解析

 1)每个 Pod 都拥有一个基于其顺序索引的稳定的主机名

[root@k8s-master statefulset]# kubectl get pods
NAME                                      READY   STATUS    RESTARTS   AGE
dns-test                                  1/1     Running   0          87m
nfs-client-provisioner-677fc9c97c-9cj92   1/1     Running   2          7h44m
web-0                                     1/1     Running   0          53s
web-1                                     1/1     Running   0          50s
web-2                                     1/1     Running   0          46s
[root@k8s-master statefulset]# kube
kubeadm  kubectl  kubelet
[root@k8s-master statefulset]# kubectl exec -it web-0 sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # hostname
web-0
/ # exit
[root@k8s-master statefulset]# kubectl exec -it web-1 sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # hostname
web-1
/ # exit
[root@k8s-master statefulset]# kubectl exec -it web-2 sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # hostname
web-2
/ # exit 

 2)使用 kubectl run 运行一个提供 nslookup 命令的容器,该命令来自于 dnsutils 包。通过对 Pod 的主机名执行 nslookup,你可以检查他们在集群内部的 DNS 地址

[root@k8s-master statefulset]# kubectl run -i --tty --image busybox:1.28.4 dns-test1 --restart=Never --rm  #创建dns-test测试pod
If you dont see a command prompt, try pressing enter.
/ # nslookup web-0
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
nslookup: cant resolve web-0/ # nslookup web-1
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
nslookup: cant resolve web-1/ # nslookup web-2
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
nslookup: cant resolve web-2/ # nslookup nginx-headless
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name:      nginx-headless   #无头service服务名,下面是代理的三个pod地址及其DNS域名
Address 1: 10.244.1.32 web-2.nginx-headless.default.svc.cluster.local
Address 2: 10.244.2.29 web-0.nginx-headless.default.svc.cluster.local
Address 3: 10.244.1.31 web-1.nginx-headless.default.svc.cluster.local
/ # 

3)删除pod,自动拉起的pod ip地址会变,但是主机名和域名是不变的

[root@k8s-master statefulset]# kubectl delete pod web-0 web-1 web-2  #删除pod
pod "web-0" deleted
pod "web-1" deleted
pod "web-2" deleted
[root@k8s-master statefulset]# kubectl get pod  #pod自动拉起
NAME                                      READY   STATUS    RESTARTS   AGE
nfs-client-provisioner-677fc9c97c-9cj92   1/1     Running   2          7h54m
web-0                                     1/1     Running   0          23s
web-1                                     1/1     Running   0          21s
web-2                                     1/1     Running   0          20s
[root@k8s-master statefulset]# kubectl run -i --tty --image busybox:1.28.4 dns-test1 --restart=Never --rm
If you dont see a command prompt, try pressing enter.
/ # nslookup nginx-headless
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name:      nginx-headless  #如下查看,pod的IP地址变化了,主机名和dns域名无变化(网络唯一标识)
Address 1: 10.244.2.31 web-0.nginx-headless.default.svc.cluster.local
Address 2: 10.244.1.33 web-1.nginx-headless.default.svc.cluster.local
Address 3: 10.244.2.32 web-2.nginx-headless.default.svc.cluster.local

5.3写入稳定的存储

1)将 Pod 的主机名写入它们的index.html文件并验证 NGINX web 服务器使用该主机名提供服务

[root@k8s-master statefulset]# kubectl exec -it web-0 sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # echo $(hostname) > /usr/share/nginx/html/index.html
/ # exit
[root@k8s-master statefulset]# kubectl exec -it web-1 sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # echo $(hostname) > /usr/share/nginx/html/index.html
/ # exit
[root@k8s-master statefulset]# kubectl exec -it web-2 sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # echo $(hostname) > /usr/share/nginx/html/index.html
/ # exit

  2)删除pod,重新调度后,依然挂载原先PV

[root@k8s-master statefulset]# kubectl get pod -o wide
NAME                                      READY   STATUS    RESTARTS   AGE     IP            NODE        NOMINATED NODE   READINESS GATES
nfs-client-provisioner-677fc9c97c-9cj92   1/1     Running   2          8h      10.244.1.18   k8s-node1   <none>           <none>
web-0                                     1/1     Running   0          6m59s   10.244.2.31   k8s-node2   <none>           <none>
web-1                                     1/1     Running   0          6m57s   10.244.1.33   k8s-node1   <none>           <none>
web-2                                     1/1     Running   0          6m56s   10.244.2.32   k8s-node2   <none>           <none>
[root@k8s-master statefulset]# curl 10.244.2.31
web-0
[root@k8s-master statefulset]# curl 10.244.1.33
web-1
[root@k8s-master statefulset]# curl 10.244.2.32
web-2
[root@k8s-master statefulset]# kubectl delete pod web-0 web-1 web-2
pod "web-0" deleted
pod "web-1" deleted
pod "web-2" deleted
[root@k8s-master statefulset]# kubectl get pod -o wide
NAME                                      READY   STATUS    RESTARTS   AGE   IP            NODE        NOMINATED NODE   READINESS GATES
nfs-client-provisioner-677fc9c97c-9cj92   1/1     Running   2          8h    10.244.1.18   k8s-node1   <none>           <none>
web-0                                     1/1     Running   0          14s   10.244.2.33   k8s-node2   <none>           <none>
web-1                                     1/1     Running   0          12s   10.244.1.35   k8s-node1   <none>           <none>
web-2                                     1/1     Running   0          11s   10.244.2.34   k8s-node2   <none>           <none>
[root@k8s-master statefulset]# curl 10.244.2.33
web-0
[root@k8s-master statefulset]# curl 10.244.1.35
web-1
[root@k8s-master statefulset]# curl 10.244.2.34
web-2
[root@k8s-master statefulset]# 

虽然 web-0 、web-1 和web-2被重新调度了,但它们仍然继续监听各自的主机名,因为和它们的 PersistentVolumeClaim 相关联的 PersistentVolume 被重新挂载到了各自的 volumeMount 上。不管 web-0、web-1、web-3 被调度到了哪个节点上,它们的 PersistentVolumes 将会被挂载到合适的挂载点上

5.4扩容/缩容 StatefulSet

 扩容/缩容StatefulSet 指增加或减少它的副本数。这通过更新replicas字段完成。你可以使用kubectl scale 或者kubectl patch来扩容/缩容一个 StatefulSet。

kubectl scale sts web --replicas=4 -n nginx-ss   #扩容
kubectl scale sts web --replicas=2 -n nginx-ss   #缩容

 或者

kubectl patch sts web -p {"spec":{"replicas":4}} -n nginx-ss  #扩容
kubectl patch sts web -p {"spec":{"replicas":2}} -n nginx-ss  #缩容

6.案例(部署mysql)

 1)yaml文件

[root@k8s-master statefulset]# pwd
/root/k8s_practice/statefulset
[root@k8s-master statefulset]# cat mysql-sts.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql #必须匹配 .spec.template.metadata.labels
serviceName: "mysql"  #声明它属于哪个Headless Service.
replicas: 3 #副本数
template:
metadata:
labels:
app: mysql # 必须配置 .spec.selector.matchLabels
spec:
terminationGracePeriodSeconds: 10
containers:
- name: mysql
image: mysql:5.7
ports:
- containerPort: 3306
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
volumeMounts:
- name: mysql-pvc
mountPath: /var/lib/mysql
volumeClaimTemplates:   #可看作pvc的模板
- metadata:
name: mysql-pvc
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "managed-nfs-storage"  #存储类名,改为集群中已存在的
resources:
requests:
storage: 1Gi

2)statefulset创建mysql

[root@k8s-master statefulset]# kubectl apply -f mysql-sts.yaml
statefulset.apps/mysql created
[root@k8s-master statefulset]# kubectl get pod
NAME                                      READY   STATUS    RESTARTS   AGE
mysql-0                                   1/1     Running   0          2m1s
mysql-1                                   1/1     Running   0          116s
mysql-2                                   1/1     Running   0          110s

3)访问测试

[root@k8s-master statefulset]# kubectl exec -it mysql-0 sh  #进入mysql-0 pod
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
# mysql -uroot -p123456   #yaml文件定义root密码为123456
mysql: [Warning] Using a password on the command line interface can be insecure.
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.35 MySQL Community Server (GPL)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type help; or \h for help. Type \c to clear the current input statement.
mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.01 sec)
mysql> 

 4)mysql存储卷

[root@k8s-master volumes]# pwd
/nfsdata/volumes
[root@k8s-master volumes]# ll
total 24
drwxrwxrwx 5 polkitd root 4096 Sep 14 19:35 default-mysql-pvc-mysql-0-pvc-3586c119-3d58-426a-8bbf-a43c6220b27d
drwxrwxrwx 5 polkitd root 4096 Sep 14 19:35 default-mysql-pvc-mysql-1-pvc-c2b8838a-ab13-413b-8b80-99fd5d4e5840
drwxrwxrwx 5 polkitd root 4096 Sep 14 19:35 default-mysql-pvc-mysql-2-pvc-614539b7-a3e1-450f-a643-ca980cdb73ff
[root@k8s-master volumes]# cd default-mysql-pvc-mysql-0-pvc-3586c119-3d58-426a-8bbf-a43c6220b27d/
[root@k8s-master default-mysql-pvc-mysql-0-pvc-3586c119-3d58-426a-8bbf-a43c6220b27d]# ll
total 188476
-rw-r----- 1 polkitd ssh_keys       56 Sep 14 19:35 auto.cnf
-rw------- 1 polkitd ssh_keys     1680 Sep 14 19:35 ca-key.pem
-rw-r--r-- 1 polkitd ssh_keys     1112 Sep 14 19:35 ca.pem
-rw-r--r-- 1 polkitd ssh_keys     1112 Sep 14 19:35 client-cert.pem
-rw------- 1 polkitd ssh_keys     1680 Sep 14 19:35 client-key.pem
-rw-r----- 1 polkitd ssh_keys     1359 Sep 14 19:35 ib_buffer_pool
-rw-r----- 1 polkitd ssh_keys 79691776 Sep 14 19:35 ibdata1
-rw-r----- 1 polkitd ssh_keys 50331648 Sep 14 19:35 ib_logfile0
-rw-r----- 1 polkitd ssh_keys 50331648 Sep 14 19:35 ib_logfile1
-rw-r----- 1 polkitd ssh_keys 12582912 Sep 14 19:35 ibtmp1
drwxr-x--- 2 polkitd ssh_keys     4096 Sep 14 19:35 mysql
drwxr-x--- 2 polkitd ssh_keys     4096 Sep 14 19:35 performance_schema
-rw------- 1 polkitd ssh_keys     1676 Sep 14 19:35 private_key.pem
-rw-r--r-- 1 polkitd ssh_keys      452 Sep 14 19:35 public_key.pem
-rw-r--r-- 1 polkitd ssh_keys     1112 Sep 14 19:35 server-cert.pem
-rw------- 1 polkitd ssh_keys     1676 Sep 14 19:35 server-key.pem
drwxr-x--- 2 polkitd ssh_keys    12288 Sep 14 19:35 sys

 注:三个mysql的pod数据是相互独立的

5)创建service使用Nodeport外部访问

[root@k8s-master statefulset]# pwd
/root/k8s_practice/statefulset
[root@k8s-master statefulset]# cat mysql-svc.yaml
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
spec:
type: NodePort
ports:
- port: 3306
nodePort: 30001
name: mysql
selector:
app: mysql
[root@k8s-master statefulset]# kubectl delete -f mysql-svc.yaml
service "mysql" deleted
[root@k8s-master statefulset]# vim mysql-svc.yaml
[root@k8s-master statefulset]# kubectl apply -f mysql-svc.yaml
service/mysql created
[root@k8s-master statefulset]# kubectl get svc
NAME             TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
kubernetes       ClusterIP   10.96.0.1      <none>        443/TCP          7d8h
mysql            NodePort    10.104.90.83   <none>        3306:30001/TCP   3s
nginx-headless   ClusterIP   None           <none>        80/TCP           48m
[root@k8s-master statefulset]# kubectl get ep
NAME              ENDPOINTS                                            AGE
kubernetes        10.3.104.51:6443                                     7d8h
mysql             10.244.1.36:3306,10.244.1.37:3306,10.244.2.35:3306   6s
nginx-headless    10.244.1.35:80,10.244.2.33:80,10.244.2.34:80         48m
wxc-nfs-storage   <none>                                               8h
[root@k8s-master statefulset]# netstat -antpl | grep 30001
tcp        0      0 0.0.0.0:30001           0.0.0.0:*               LISTEN      32270/kube-proxy   

 注:上述代表通过Nodeport端口访问mysql成功

Kubernetes之StatefulSet详解

原文:https://www.cnblogs.com/wuxinchun/p/15269255.html

以上是Kubernetes之StatefulSet详解的全部内容。
THE END
分享
二维码
< <上一篇
)">
下一篇>>