09--kubernetes持久化存储和StatefulSet控制器
前言:本章是k8s持久化存储的详细笔记,由持久化引申出来的statefulset控制器和无头svc都会在本章有详细记录。
1、K8s持久化存储PV和PVC
在前面文章已经使用卷挂载的方式将pod文件持久化保存在宿主机中,但实际工作中pod往往会以多副本形式存在,此时需要每一个宿主机上都有对应的挂载卷,这样往往会造成存储资源极大浪费,所以这里需要引入PV和PVC概念解决这个问题。
1.1、PV和PVC的引入
Volume 提供了有效的数据持久化方案,但在管理上存在不足。以 AWS EBS(亚马逊云存储服务,需要收费,这里使用第四台虚拟机模拟提供此服务) 为例,使用 Volume 时,Pod 必须知道以下信息:当前 Volume 来自 AWS EBS、EBS Volume 已提前创建并且知道 volume-id。这导致应用开发人员和存储系统管理员的职责耦合。如果系统规模较小或在开发环境中尚可接受,但在大规模或生产环境中,效率和安全性的问题变得突出。
Kubernetes 提供了 PersistentVolume (PV) 和 PersistentVolumeClaim (PVC) 来解决这些问题。PV 是管理员创建和维护的外部存储空间,具有持久性,生命周期独立于 Pod。PVC 是对 PV 的申请,通常由普通用户创建,用于请求存储资源的容量和访问模式等信息。Kubernetes 会根据 PVC 查找并分配满足条件的 PV,使用户无需关心底层细节,由管理员处理 PersistentVolume 的创建和管理。
1.2、通过NFS实现持久化存储
1.2.1、环境准备
使用新虚拟机nfs节点(192.168.188.200)模拟云存储服务
查看磁盘空间,格式化设备,创建将被共享的目录,挂载磁盘,修改权限
[root@localhost ~]# fdisk -l
[root@localhost ~]# mkfs.ext4 /dev/sdb
[root@localhost ~]# mkdir /root/nfs
[root@localhost ~]# mount /dev/sdb /root/nfs
[root@localhost ~]# chmod 777 /root
[root@localhost ~]# chmod 777 /root/nfs
#生产环境中直接在根下创建
为所有节点安装nfs
yum install -y nfs-common nfs-utils
在nfs节点共享目录
[root@localhost ~]# vim /etc/exports
[root@localhost ~]# cat /etc/exports
/root/nfs *(rw,no_root_squash,no_all_squash,sync)
#注释
# /root/nfs:这是要共享的目录路径。
# *:表示允许所有客户端访问这个共享目录。可以用具体的 IP 地址或主机名替换 *,以限制访问权限。
# rw:表示客户端具有读写权限。客户端可以对这个目录进行读和写操作。
# no_root_squash:允许客户端以 root 用户身份访问共享目录时,拥有 root 权限。在默认设置下,NFS 会将客户端的 root 用户映射为普通用户,以增加安全性。
# no_all_squash:允许客户端以任何用户身份访问共享目录时,保留客户端的原始身份,而不是映射为匿名用户。
# sync:表示数据写入磁盘时是同步的,即在响应客户端请求之前,数据会被写入磁盘。这有助于确保数据的一致性,但可能会降低性能。
[root@localhost ~]# systemctl start rpcbind
# 用来告知nfs客户端nfs的服务端口
[root@localhost ~]# systemctl start nfs
此时需要使用客户端(node1,node2)测试nfs挂载是否可用
[root@k8s-node1 ~]# mkdir /test
[root@k8s-node1 ~]# mount -t nfs 192.168.188.200:/root/nfs /test/
[root@k8s-node1 ~]# df -Th | grep "/test"
192.168.188.200:/root/nfs nfs4 99G 60M 94G 1% /test
[root@k8s-node1 ~]# touch /test/123.txt
[root@k8s-node1 ~]# ls /test/
123.txt lost+found
回到nfs服务端查看
[root@localhost ~]# ls /root/nfs/
123.txt lost+found
测试完成后回到客户端卸载该目录
[root@k8s-node1 ~]# umount /test/
[root@k8s-node1 ~]# df -Th | grep "/test"
1.2.2、PV和PVC实操
回到master节点
[root@k8s-master1 ~]# mkdir pv.yaml.d
[root@k8s-master1 ~]# cd pv.yaml.d/
[root@k8s-master1 pv.yaml.d]# vim nfs-pv1.yaml
[root@k8s-master1 pv.yaml.d]# cat nfs-pv1.yaml
---
apiVersion: v1 # Kubernetes API 版本
kind: PersistentVolume # 资源类型为持久卷
metadata:
name: mypv1 # 持久卷的名称
spec:
capacity:
storage: 10Gi # 持久卷的存储容量为 10GB(这里建议最多使用90%空间)
accessModes:
- ReadWriteOnce # 访问模式为单节点读写
persistentVolumeReclaimPolicy: Retain # 回收策略为保留
storageClassName: nfs # 存储类名称为 nfs
nfs:
path: /root/nfs # NFS 共享目录的路径
server: 192.168.188.200 # NFS 服务器的 IP 地址
参数详解:
1、accessModes 指定访问模式为 ReadWriteOnce,支持的访问模式有:
ReadWriteOnce – PV 能以 read-write 模式 mount 到单个节点。
ReadOnlyMany – PV 能以 read-only 模式 mount 到多个节点。
ReadWriteMany – PV 能以 read-write 模式 mount 到多个节点
2、persistentVolumeReclaimPolicy 指定当 PV 的回收策略为Retain,支持的策略有:
Retain – 需要管理员手工回收。
Recycle – 清除 PV 中的数据,效果相当于执行 rm -rf /nfsdata/*。(已废除,使用不会报错,但无法生效)
Delete – 删除 Storage Provider 上的对应存储资源,例如 AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume 等。
3、storageClassName 指定 PV 的 class 为 nfs。相当于为 PV 设置了一个分类,PVC 可以指定 class 申请相应 class 的 PV。
创建pv
[root@k8s-master1 pv.yaml.d]# kubectl apply -f nfs-pv1.yaml
persistentvolume/mypv1 created
[root@k8s-master1 pv.yaml.d]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
mypv1 10Gi RWO Retain Available nfs 7s
# STATUS 为 Available,表示 mypv1 就绪,可以被 PVC 申请。
创建pvc
[root@k8s-master1 pv.yaml.d]# vim nfs-pvc1.yaml
[root@k8s-master1 pv.yaml.d]# cat nfs-pvc1.yaml
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc1
spec:
accessModes:
- ReadWriteOnce #权限需要与目标pv保持一致
resources:
requests:
storage: 10Gi #通过大小可以绑定
storageClassName: nfs #通过类型也可以绑定
[root@k8s-master1 pv.yaml.d]# kubectl apply -f nfs-pvc1.yaml
persistentvolumeclaim/mypvc1 created
[root@k8s-master1 pv.yaml.d]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypvc1 Bound mypv1 10Gi RWO nfs 5s
此时再去查看pv状态
[root@k8s-master1 pv.yaml.d]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
mypv1 10Gi RWO Retain Bound default/mypvc1 nfs 8m
#从 kubectl get pvc 和 kubectl get pv 的输出可以看到 mypvc1 已经 Bound 到 mypv1,申请成功。
配置pod并引用pvc
[root@k8s-master1 pv.yaml.d]# vim nfs-pod1.yaml
[root@k8s-master1 pv.yaml.d]# cat nfs-pod1.yaml
---
apiVersion: v1
kind: Pod
metadata:
name: mypod1
labels:
app: liumuquan
spec:
containers:
- name: mypod1
image: registry.cn-chengdu.aliyuncs.com/liumuquan_app/nginx:1.12.0-alpine
ports:
- containerPort: 80
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: mydata
volumes:
- name: mydata
persistentVolumeClaim:
claimName: mypvc1
此时在nfs目录下写一个nginx主页文件
nfs服务端操作
[root@localhost ~]# rm -rf /root/nfs/*
[root@localhost ~]# echo "liumuquan-pvc1" > /root/nfs/index.html
[root@localhost ~]# chmod 777 /root/nfs/index.html
回到master创建pod并访问
[root@k8s-master1 pv.yaml.d]# kubectl apply -f nfs-pod1.yaml
pod/mypod1 created
[root@k8s-master1 daemonset.yaml.d]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
mypod1 1/1 Running 0 2m15s 10.244.2.28 k8s-node2 <none> <none>
[root@k8s-master1 pv.yaml.d]# curl 10.244.2.28
liumuquan-pv1
1.2.3、pv回收
回收流程:svc-pod-pvc-pv
[root@k8s-master1 pv.yaml.d]# kubectl delete pod mypod1
pod "mypod1" deleted
[root@k8s-master1 pv.yaml.d]# kubectl delete pvc mypvc1
persistentvolumeclaim "mypvc1" deleted
[root@k8s-master1 pv.yaml.d]# kubectl delete pv mypv1
persistentvolume "mypv1" deleted
此时查看nfs服务端发现文件仍然存在
[root@localhost nfs]# pwd
/root/nfs
[root@localhost nfs]# ls
index.html
[root@localhost nfs]# cat index.html
liumuquan-pv1
重复创建流程发现访问结果不变
1.3、PV/PVC实战(Mysql)
操作如下:
在nfs节点准备mysql文件夹,此文件夹可以为空,mysql与nginx不同,运行起来后会在该路径自动生成文件。
[root@localhost nfs]# mkdir /root/nfs/mysql-pv
[root@localhost nfs]# chmod 777 mysql-pv
回到master节点配置yaml文件
root@k8s-master1 pv.yaml.d]# vim mysql-pv-test.yaml
[root@k8s-master1 pv.yaml.d]# cat mysql-pv-test.yaml
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
path: /root/nfs/mysql-pv
server: 192.168.188.200
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 9Gi
storageClassName: nfs
---
apiVersion: v1
kind: Service
metadata:
name: mysql
spec:
ports:
- port: 3306
targetPort: 3306
selector:
app: mysql
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- image: registry.cn-chengdu.aliyuncs.com/liumuquan_app/mysql:5.7
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: "Liumuquan@123"
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-persistent-storage
persistentVolumeClaim:
claimName: mysql-pvc
创建并检查状态
[root@k8s-master1 pv.yaml.d]# kubectl apply -f mysql-pv-test.yaml
persistentvolume/mysql-pv created
persistentvolumeclaim/mysql-pvc created
service/mysql created
deployment.apps/mysql created
[root@k8s-master1 pv.yaml.d]# kubectl get pv,pvc,deploy,svc,pod
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/mysql-pv 10Gi RWO Retain Bound default/mysql-pvc nfs 73s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/mysql-pvc Bound mysql-pv 10Gi RWO nfs 73s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/mysql 1/1 1 1 73s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 17d
service/mysql ClusterIP 10.102.65.24 <none> 3306/TCP 73s
NAME READY STATUS RESTARTS AGE
pod/mysql-5b5d554c48-4b4vl 1/1 Running 0 73s
进入pod内操作(此处也可以使用集群内主机通过svc登录mysql)
[root@k8s-master1 pv.yaml.d]# kubectl exec -it mysql-5b5d554c48-4b4vl /bin/bash
bash-4.2# mysql -uroot -p"Liumuquan@123"
mysql> create database my_id;
Query OK, 1 row affected (0.00 sec)
mysql> use my_id;
Database changed
mysql> create table t1(id int,name varchar(30));
Query OK, 0 rows affected (0.02 sec)
mysql> insert into t1 values(1,"maguocheng");
Query OK, 1 row affected (0.01 sec)
mysql> insert into t1 values(2,"longkeduo");
Query OK, 1 row affected (0.00 sec)
mysql> select * from t1;
+------+------------+
| id | name |
+------+------------+
| 1 | maguocheng |
| 2 | longkeduo |
+------+------------+
2 rows in set (0.00 sec)
退出pod,删除svc,deploy,pvc,pv
[root@k8s-master1 pv.yaml.d]# kubectl delete -f mysql-pv-test.yaml
persistentvolume "mysql-pv" deleted
persistentvolumeclaim "mysql-pvc" deleted
service "mysql" deleted
deployment.apps "mysql" deleted
重新创建
[root@k8s-master1 pv.yaml.d]# kubectl apply -f mysql-pv-test.yaml
persistentvolume/mysql-pv created
persistentvolumeclaim/mysql-pvc created
service/mysql created
deployment.apps/mysql created
进入容器查看发现数据并未丢失
[root@k8s-master1 pv.yaml.d]# kubectl get pod
NAME READY STATUS RESTARTS AGE
mysql-5b5d554c48-zlx7m 1/1 Running 0 43s
[root@k8s-master1 pv.yaml.d]# kubectl exec -it mysql-5b5d554c48-zlx7m /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
bash-4.2# mysql -uroot -p"Liumuquan@123"
mysql> use my_id;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> select * from t1;
+------+------------+
| id | name |
+------+------------+
| 1 | maguocheng |
| 2 | longkeduo |
+------+------------+
2 rows in set (0.00 sec)
2、PV/PVC的动态供应
动态存储流程示意图
当k8s业务上来的时候,需要大量的pvc,此时我们人工创建匹配的话,工作量就会非常大了,需要动态的自动挂载相应的存储。
nfs为提供存储的节点
绿色的pod用途为自动创建pv/pvc,使用deploy创建。
黄色的StorageClass,来对接存储,靠他来自动关联pvc,并创建pv。
橙色的StatefulSet,pod控制器,这里用来创建nginx的pod,本章结尾会详细说明。
2.1、PV/PVC动态供应项目实战
1)在nfs节点创建共享文件夹
[root@localhost ~]# mkdir /root/nfs/container_data
[root@localhost ~]# chmod 777 /root/nfs/container_data
2)部署storageclass
[root@k8s-master1 dynamic]# vim storageclass.yaml
[root@k8s-master1 dynamic]# cat storageclass.yaml
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage #指定了存储类的名称
provisioner: fuseim.pri/ifs #指定了用于动态存储卷创建的插件或供应者
3)配置授权
因为storage自动创建pv需要经过kube-apiserver,所以要进行授权
创建1个sa(serviceaccount)
创建1个clusterrole,并赋予应该具有的权限,比如对于一些基本api资源的增删改查;这里也可以使用自带k8s角色。
创建1个clusterrolebinding,将sa和clusterrole绑定到一起;这样sa就有权限了;
然后pod中再使用这个sa,那么pod再创建的时候,会用到sa,sa具有创建pv的权限,便可以自动创建pv;
[root@k8s-master1 dynamic]# vim sa.yaml
[root@k8s-master1 dynamic]# cat sa.yaml
---
apiVersion: v1 # 定义 ServiceAccount 的 API 版本
kind: ServiceAccount # 资源类型为 ServiceAccount
metadata: # 元数据部分
name: nfs-client-provisioner # ServiceAccount 的名称
namespace: default # ServiceAccount 所在的命名空间
---
kind: ClusterRole # 资源类型为 ClusterRole
apiVersion: rbac.authorization.k8s.io/v1 # 定义 ClusterRole 的 API 版本
metadata: # 元数据部分
name: nfs-client-provisioner-runner # ClusterRole 的名称
rules: # 权限规则
- apiGroups: [""] # 适用于 core API 组
resources: ["nodes"] # 节点资源
verbs: ["get", "list", "watch"] # 权限操作
- apiGroups: [""] # 适用于 core API 组
resources: ["persistentvolumes"] # 持久卷资源
verbs: ["get", "list", "watch", "create", "delete"] # 权限操作
- apiGroups: [""] # 适用于 core API 组
resources: ["persistentvolumeclaims"] # 持久卷声明资源
verbs: ["get", "list", "watch", "update"] # 权限操作
- apiGroups: ["storage.k8s.io"] # 适用于 storage.k8s.io API 组
resources: ["storageclasses"] # 存储类资源
verbs: ["get", "list", "watch"] # 权限操作
- apiGroups: [""] # 适用于 core API 组
resources: ["events"] # 事件资源
verbs: ["create", "update", "patch"] # 权限操作
---
kind: ClusterRoleBinding # 资源类型为 ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1 # 定义 ClusterRoleBinding 的 API 版本
metadata: # 元数据部分
name: run-nfs-client-provisioner # ClusterRoleBinding 的名称
subjects: # 关联的主体
- kind: ServiceAccount # 主体类型为 ServiceAccount
name: nfs-client-provisioner # 关联的 ServiceAccount 名称
namespace: default # ServiceAccount 所在的命名空间
roleRef: # 角色引用部分
kind: ClusterRole # 角色类型为 ClusterRole
name: nfs-client-provisioner-runner # 关联的 ClusterRole 名称
apiGroup: rbac.authorization.k8s.io # 角色的 API 组
---
kind: Role # 资源类型为 Role
apiVersion: rbac.authorization.k8s.io/v1 # 定义 Role 的 API 版本
metadata: # 元数据部分
name: leader-locking-nfs-client-provisioner # Role 的名称
namespace: default # Role 所在的命名空间
rules: # 权限规则
- apiGroups: [""] # 适用于 core API 组
resources: ["endpoints"] # 端点资源
verbs: ["get", "list", "watch", "create", "update", "patch"] # 权限操作
---
kind: RoleBinding # 资源类型为 RoleBinding
apiVersion: rbac.authorization.k8s.io/v1 # 定义 RoleBinding 的 API 版本
metadata: # 元数据部分
name: leader-locking-nfs-client-provisioner # RoleBinding 的名称
namespace: default # RoleBinding 所在的命名空间
subjects: # 关联的主体
- kind: ServiceAccount # 主体类型为 ServiceAccount
name: nfs-client-provisioner # 关联的 ServiceAccount 名称
namespace: default # ServiceAccount 所在的命名空间
roleRef: # 角色引用部分
kind: Role # 角色类型为 Role
name: leader-locking-nfs-client-provisioner # 关联的 Role 名称
apiGroup: rbac.authorization.k8s.io # 角色的 API 组
4)配置deploy
[root@k8s-master1 dynamic]# vim deploy.yaml
[root@k8s-master1 dynamic]# cat deploy.yaml
---
kind: Deployment # 部署资源类型
apiVersion: apps/v1 # API 版本
metadata: # 元数据部分
name: nfs-client-provisioner # 部署的名称
spec: # 规格部分
selector: # 选择器部分,用于确定哪些 Pods 属于这个 Deployment
matchLabels: # 标签匹配规则
app: nfs-client-provisioner # 匹配标签 "app" 的值为 "nfs-client-provisioner"
replicas: 1 # 副本数,即创建一个 Pod 实例
strategy: # 部署策略部分
type: Recreate # 策略类型为 Recreate,即先删除旧的 Pod 再创建新的 Pod
template: # Pod 模板
metadata: # 模板的元数据部分
labels: # Pod 的标签
app: nfs-client-provisioner # 标签 "app" 的值为 "nfs-client-provisioner"
spec: # Pod 的规格部分
serviceAccount: nfs-client-provisioner # 指定使用的 ServiceAccount
containers: # 容器列表
- name: nfs-client-provisioner # 容器的名称
image: registry.cn-chengdu.aliyuncs.com/k8s_module_images/lizhenliang_nfs-client-provisioner:v2.0.0 # 容器镜像地址
volumeMounts: # 卷挂载配置
- name: nfs-client-root # 卷的名称
mountPath: /persistentvolumes # 挂载路径
env: # 环境变量配置
- name: PROVISIONER_NAME # 环境变量名称
value: fuseim.pri/ifs # 环境变量值,应与 storageclass 中的 provisioner 一致
- name: NFS_SERVER # 环境变量名称
value: 192.168.188.200 # NFS 服务器地址
- name: NFS_PATH # 环境变量名称
value: /root/nfs/container_data # NFS 共享路径
volumes: # 卷列表
- name: nfs-client-root # 卷的名称
nfs: # 使用 NFS 卷类型
server: 192.168.188.200 # NFS 服务器地址
path: /root/nfs/container_data # NFS 共享路径
5)部署上方配置文件并检查
[root@k8s-master1 dynamic]# kubectl apply -f storageclass.yaml
storageclass.storage.k8s.io/managed-nfs-storage created
[root@k8s-master1 dynamic]# kubectl apply -f sa.yaml
serviceaccount/nfs-client-provisioner created
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
[root@k8s-master1 dynamic]# kubectl apply -f deploy.yaml
deployment.apps/nfs-client-provisioner created
[root@k8s-master1 dynamic]# kubectl get sc,sa,deploy,pod
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
storageclass.storage.k8s.io/managed-nfs-storage fuseim.pri/ifs Delete Immediate false 64s
NAME SECRETS AGE
serviceaccount/default 1 18d
serviceaccount/nfs-client-provisioner 1 58s
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nfs-client-provisioner 1/1 1 1 50s
NAME READY STATUS RESTARTS AGE
pod/nfs-client-provisioner-65fcd7b4c8-bmcc4 1/1 Running 0 50s
6)配置StatefulSet
这里需要引入无头svc
[root@k8s-master1 dynamic]# vim nginx-StatefulSet.yaml
[root@k8s-master1 dynamic]# cat nginx-StatefulSet.yaml
---
apiVersion: v1 # API 版本
kind: Service # 资源类型为 Service
metadata: # 元数据部分
name: nginx # Service 的名称
labels: # 标签
app: nginx # 标签 "app" 的值为 "nginx"
spec: # 规格部分
ports: # 端口配置
- port: 80 # Service 公开的端口
name: web # 端口名称
clusterIP: None # 集群内部 IP 地址设为 None,表示这是一个 Headless Service
selector: # 选择器部分
app: nginx # 选择标签为 "app: nginx" 的 Pods
---
apiVersion: apps/v1 # API 版本
kind: StatefulSet # 资源类型为 StatefulSet
metadata: # 元数据部分
name: web # StatefulSet 的名称
spec: # 规格部分
serviceName: "nginx" # 关联的 Service 名称
replicas: 2 # 副本数,即创建两个 Pod 实例
selector: # 选择器部分
matchLabels: # 标签匹配规则
app: nginx # 匹配标签 "app" 的值为 "nginx"
template: # Pod 模板
metadata: # 模板的元数据部分
labels: # Pod 的标签
app: nginx # 标签 "app" 的值为 "nginx"
spec: # Pod 的规格部分
containers: # 容器列表
- name: nginx # 容器的名称
image: registry.cn-chengdu.aliyuncs.com/liumuquan_app/nginx:1.12.0-alpine # 容器镜像地址
ports: # 端口配置
- containerPort: 80 # 容器内部端口
name: web # 端口名称
volumeMounts: # 卷挂载配置
- name: www # 卷的名称
mountPath: /usr/share/nginx/html # 挂载路径
volumeClaimTemplates: # 卷声明模板
- metadata: # 模板的元数据部分
name: www # 卷声明的名称
spec: # 卷声明的规格部分
accessModes: [ "ReadWriteOnce" ] # 存储访问模式
storageClassName: "managed-nfs-storage" # 存储类名称
resources: # 资源请求配置
requests: # 请求的资源
storage: 1Gi # 存储请求量
7)创建nginx
[root@k8s-master1 dynamic]# kubectl apply -f nginx-StatefulSet.yaml
service/nginx created
statefulset.apps/web created
8)查看pod状态
[root@k8s-master1 dynamic]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-65fcd7b4c8-bmcc4 1/1 Running 0 18m
web-0 1/1 Running 0 75s
web-1 1/1 Running 0 70s
这里可以看出StatefulSet是有序命名,逐个创建的(注意运行时间的不同)
9)查看pv
命名规则:命名空间-模板设置的卷名-pod名字-pvc-随机序列号
[root@k8s-master1 dynamic]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
default-www-web-0-pvc-88f9b153-9c9f-4e8b-9f7a-6f35d0622582 1Gi RWO Delete Bound default/www-web-0 managed-nfs-storage 25m
default-www-web-1-pvc-2d1de5c7-1ff0-4765-8760-241be3f2bcd6 1Gi RWO Delete Bound default/www-web-1 managed-nfs-storage 25m
这里有两个pv分别对应nfs节点目录的两个文件夹
[root@localhost ~]# ls /root/nfs/container_data
default-www-web-0-pvc-88f9b153-9c9f-4e8b-9f7a-6f35d0622582 default-www-web-1-pvc-2d1de5c7-1ff0-4765-8760-241be3f2bcd6
只在web-0目录创建文件
[root@localhost ~]# echo "statefulset" > /root/nfs/container_data/default-www-web-0-pvc-88f9b153-9c9f-4e8b-9f7a-6f35d0622582/index.html
[root@localhost ~]# chmod 777 /root/nfs/container_data/default-www-web-0-pvc-88f9b153-9c9f-4e8b-9f7a-6f35d0622582/index.html
回到master节点访问
[root@k8s-master1 dynamic]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nfs-client-provisioner-65fcd7b4c8-bmcc4 1/1 Running 0 50m 10.244.2.31 k8s-node2 <none> <none>
web-0 1/1 Running 0 32m 10.244.2.32 k8s-node2 <none> <none>
web-1 1/1 Running 0 32m 10.244.1.46 k8s-node1 <none> <none>
[root@k8s-master1 dynamic]# curl 10.244.2.32
statefulset
[root@k8s-master1 dynamic]# curl 10.244.1.46
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.12.0</center>
</body>
</html>
备注:
- 此时nfs上的/root/nfs/container_data目录,已经被取用nfs的pod(绿色)挂到/persistentvolumes(deploy.yaml内配置)
- StatefulSet通过自身的卷模板触发storageclass从绿pod的/persistentvolumes取用空间
10)资源对象回收
回收顺序:StatefulSet-pv/pvc(这里是deloyment控制器)-授权-stc
[root@k8s-master1 dynamic]# ls
deploy.yaml nginx-StatefulSet.yaml sa.yaml storageclass.yaml
[root@k8s-master1 dynamic]# kubectl delete -f nginx-StatefulSet.yaml
service "nginx" deleted
statefulset.apps "web" deleted
[root@k8s-master1 dynamic]# kubectl delete -f deploy.yaml
deployment.apps "nfs-client-provisioner" deleted
[root@k8s-master1 dynamic]# kubectl delete -f sa.yaml
serviceaccount "nfs-client-provisioner" deleted
clusterrole.rbac.authorization.k8s.io "nfs-client-provisioner-runner" deleted
clusterrolebinding.rbac.authorization.k8s.io "run-nfs-client-provisioner" deleted
role.rbac.authorization.k8s.io "leader-locking-nfs-client-provisioner" deleted
rolebinding.rbac.authorization.k8s.io "leader-locking-nfs-client-provisioner" deleted
[root@k8s-master1 dynamic]# kubectl delete -f storageclass.yaml
storageclass.storage.k8s.io "managed-nfs-storage" deleted
此时查看数据仍正常保存
[root@localhost ~]# cat /root/nfs/container_data/default-www-web-0-pvc-88f9b153-9c9f-4e8b-9f7a-6f35d0622582/index.html
statefulset
3、statefulset控制器
StatefulSet 是用来管理有状态应用的工作负载 API 对象。下面是和deploy的对比。
Deployment | StatefulSet | |
---|---|---|
适合场景 | 无状态的应用 | 有状态的应用 |
特点 | 1.pod之间没有顺序 2.所有pod共享存储 3.pod名字包含随机数字 4.service都有ClusterIP,可以负载均衡 | 1.部署、扩展、更新、删除都要有顺序
|
StatefulSet 中的 Pod 具有一个独特且持久的身份标识(域名),这个标识基于 StatefulSet 控制器分配给每个 Pod 的唯一顺序索引。Pod 的名称的形式为 <StatefulSetName>-<index>
。例如,如果一个 StatefulSet 名为 web
且拥有两个副本,它会创建两个 Pod,分别为 web-0
和 web-1
。
标识符(域名)解析演示
[root@k8s-master1 dynamic]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nfs-client-provisioner-65fcd7b4c8-8qqc5 1/1 Running 0 10m 10.244.2.33 k8s-node2 <none> <none>
web-0 1/1 Running 0 9m56s 10.244.2.34 k8s-node2 <none> <none>
web-1 1/1 Running 0 9m54s 10.244.1.49 k8s-node1 <none> <none>
[root@k8s-master1 dynamic]# kubectl exec -it web-1 /bin/sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
/ # nslookup web-0.nginx.default.svc.cluster.local
Name: web-0.nginx.default.svc.cluster.local
Address 1: 10.244.2.34 web-0.nginx.default.svc.cluster.local
/ #
与 Deployment 相似,StatefulSet 也管理一组基于相同容器定义的 Pod。然而,StatefulSet 与 Deployment 的不同之处在于,StatefulSet 为每个 Pod 维护一个固定的 ID。这些 Pod 是基于相同的声明创建的,但它们不可互换,每个 Pod 都有一个永久不变的 ID。
使用场景:StatefulSet 对于以下需求的应用程序尤为重要:
- 稳定的、唯一的网络标识符:即使 Pod 重新调度,其 PodName 和 HostName 仍保持不变(虽然 IP 地址可能会变)。命名方式为
PodName.serviceName.namespaceName.svc.cluster.local
- 稳定的、持久的存储:即使 Pod 重新调度,仍能访问相同的持久化数据,通过 PVC 实现。
- 有序的部署和缩放。
- 有序的、自动的滚动更新。
总之,StatefulSet 提供了稳定性,确保 Pod 调度或重调度的过程是持久的。
如果应用程序不需要稳定的标识符或有序的部署、删除或缩放,可以使用无状态的副本控制器,如 Deployment 或 ReplicaSet,这些更适用于无状态应用的部署。
使用规则:
-
存储要求:Pod 的存储必须由 PersistentVolume 驱动,依据所请求的 storage class 提供,或由管理员预先提供。
-
存储保护:删除或缩减 StatefulSet 不会删除其关联的存储卷,以确保数据安全,这通常比自动清除所有相关资源更为重要。
-
网络标识:StatefulSet 需要一个 headless 服务来处理 Pod 的网络标识。你需要负责创建该服务。
-
终止保证:StatefulSet 不保证 Pod 的终止。为了正常终止,可以在删除前将 StatefulSet 缩减到 0。
-
滚动更新:在默认的 Pod 管理策略 (OrderedReady) 下,滚动更新可能导致需要人工干预才能修复的损坏状态。
-
有序索引:StatefulSet 中的每个 Pod 将被分配一个从 0 到 N-1 的唯一整数序号。
-
主机名格式:StatefulSet 中的每个 Pod 的主机名由 StatefulSet 名称和 Pod 的序号组成,格式为
$(StatefulSet 名称)-$(序号)
。
部署、删除、扩容,缩容规则:
-
Pod 创建顺序:在 StatefulSet 中,包含 N 个副本的 Pod 按照顺序从 0 到 (N-1) 依次创建。
-
Pod 删除顺序:Pod 被删除时,按照逆序从 (N-1) 到 0 终止。
-
缩放前提:在应用缩放操作前,前面的所有 Pod 必须处于 Running 和 Ready 状态。
-
缩容保护:如果
web-2
被终止和删除后,但web-1
尚未被终止,且此时web-0
发生运行失败,那么系统会等待web-0
恢复到 Running 和 Ready 状态后才会终止web-1
。这样做是为了确保系统的稳定性和数据一致性,防止在 Pod 失败的情况下过早地终止其他 Pod -
继任者关闭:在终止 Pod 之前,所有后续序号的 Pod必须完全关闭,也就是说,如果 StatefulSet 有 3 个副本,终止序号为 1 的 Pod 前,Pod 2 和 Pod 3 必须完全停止,这样可以确保数据的完整性和正确性。
-
终止宽限期:
pod.Spec.TerminationGracePeriodSeconds
指定 Pod 在终止前的宽限时间。如果将此值设置为 0,Pod 会立即终止,可能导致未完成的请求丢失或数据不一致。为了保证 Pod 有足够时间完成当前操作和清理资源,必须设置一个合理的宽限期。这样可以提高系统的可靠性和安全性。