目录
1.1 存储资源管理
1.2 持久卷pv的类型
1.3 实验mysql基于NFS共享存储实现持久化存储
1.3.1 安装NFS
1.3.2 PV参数详解
1.3.3 创建pv
1.3.4 mysql使用pvc持久卷
1.4 动态绑定pv
1.4.1 配置nfs-provisioner授权
1.4.2 部署nfs-client-provisioner
1.4.3 创建StorageClass
1.4.4 deployment 动态挂载
1.4.5 statefulset 动态挂载
在基于k8s容器云平台上,对存储资源的使用需求通常包括以下几方面:
1.应用配置文件、密钥的管理; 2.应用的数据持久化存储; 3.在不同的应用间共享数据存储;
k8s的Volume抽象概念就是针对这些问题提供的解决方案,k8s的volume类型非常丰富,从临时目录、宿主机目录、ConfigMap、Secret、共享存储(PV和PVC)。
k8s支持Volume类型包括以下几类:
1.临时空目录(随着Pod的销毁而销毁) emptyDir: 2.配置类(将配置文件以Volume的形式挂载到容器内) ConfigMap:将保存在ConfigMap资源对象中的配置文件信息挂载到容器内的某个目录下 Secret:将保存在Secret资源对象中的密码密钥等信息挂载到容器内的某个文件中。 downwardAPI:将downwardAPI的数据以环境变量或文件的形式注入容器中。 gitErpo:将某Git代码库挂载到容器内的某个目录下 3.本地存储类 hostpath:将宿主机的目录或文件挂载到容器内进行使用。 local:Kubernetes从v1.9版本引入,将本地存储以PV的形式提供给容器使用,并能够实现存储空间的管理。 4.共享存储 PV(Persistent Volume):将共享存储定义为一种“持久存储卷”,可以被多个容器应用共享使用。 PVC(Persistent Volume Claim):用户对存储资源的一次“申请”,PVC申请的对象是PV,一旦申请成功,应用就能够像使用本地目录一样使用共享存储了。
共享存储主要用于多个应用都能够使用存储资源,例如NFS存储、光纤存储Glusterfs共享文件系统等,在k8s系统中通过PV/StorageClass和PVC来完成定义,并通过volumeMount挂载到容器的目录或文件进行使用。
ConfigMap、Secret、emptyDir、hostPath等属于临时性存储,当pod被调度到某个节点上时,它们随pod的创建而创建,临时占用节点存储资源,当pod离开节点时,存储资源被交还给节点,pod一旦离开这个节点,存储就失效,不具备持久化存储数据的能力。与此相反,持久化存储拥有独立的生命周期,具备持久化存储能力,其后端一般是独立的存储系统如NFS、iSCSI、cephfs、glusterfs等。
pv与pvc
Kubernetes中的node代表计算资源,而PersistentVolume(PV)则代表存储资源,它是对各种诸如NFS、iSCSI、云存储等各种存储后端所提供存储块的统一抽象从而提供网络存储,通过它屏蔽低层实现细节。与普通volume不同,PV拥有完全独立的生命周期。 因为PV表示的是集群能力,它是一种集群资源,所以用户(通常是开发人员)不能直接使用PV,就像不能直接使用内存一样,需要先向系统申请,而 PVC 就是请求存储资源的。 Kubernetes通过PersistentVolumeClaim(PVC)代理用户行为,用户通过对PVC的操作实现对PV申请、使用、释放等操作,PVC是用户层面的资源。 PV 和 PVC 可以将 pod 和数据卷解耦,pod 不需要知道确切的文件系统或者支持它的持久化引擎。
Kubernetes的共享存储供应模式包括静态和动态两种模式
静态模式 静态PV由系统管理员负责创建、提供、维护,系统管理员为用户屏蔽真正提供存储的后端及其实现细节,普通用户作为消费者,只需通过PVC申请、使用此类资源。 动态模式: 集群管理员无须手工创建PV,而是通过对Storage Class的设置对后端存储进行描述,“storage class”可以理解成某种具体的后端存储,标记为某种“类型(Class)”,此时要求PVC对存储的类型进行声明,系统将自动完成PV的创建与PVC的绑定。如果用户的PVC中“storage class”的值为"",则表示不能为此PVC动态创建PV。
PV与PVC的绑定
用户创建包含容量、访问模式等信息的PVC,向系统请求存储资源。系统查找已存在PV或者监控新创建PV,如果与PVC匹配则将两者绑定。如果PVC创建动态PV,则系统将一直将两者绑定。PV与PVC的绑定是一一对应关系,不能重复绑定。如果系统一直没有为PVC找到匹配PV,则PVC无限期维持在"unbound"状态,直到系统找到匹配PV。实际绑定的PV容量可能大于PVC中申请的容量。
PV 持久卷是用插件的形式来实现的。Kubernetes 目前支持以下插件
- GCEPersistentDisk - AWSElasticBlockStore - AzureFile - AzureDisk - FC (Fibre Channel) - Flexvolume - Flocker - NFS - iSCSI - RBD (Ceph Block Device) - CephFS - Cinder (OpenStack block storage) - Glusterfs - VsphereVolume - Quobyte Volumes - HostPath (Single node testing only – local storage is not supported in any way and WILL NOT WORK in a multi-node cluster) - Portworx Volumes - ScaleIO Volumes - StorageOS
机器准备
k8s-master 制作nfs服务端作为共享文件系统[root@k8s-master ~]# yum install -y nfs-utils rpcbind [root@k8s-master ~]# mkdir /mnt/data #制作共享目录 [root@k8s-master ~]# vim /etc/exports /mnt/data 192.168.122.0/24(rw,no_root_squash) [root@k8s-master ~]# systemctl start rpcbind #启动服务 [root@k8s-master ~]# systemctl start nfs
# 使配置生效[root@k8s-master ~]# exportfs -r
# 检查配置是否生效[root@k8s-master ~]# exportfs
集群中的工作节点都需要安装客户端工具,主要作用是节点能够驱动 nfs 文件系统 只安装,不启动服务 # yum install -y nfs-utils # showmount -e master_ip # mount -t nfs master_ip:/mnt/data /mnt/data master节点 制作pv.yaml(一般这个动作由 kubernetes 管理员完成,也就是我们运维人员)[root@k8s-master ~]# mkdir /k8s/mysql -p [root@k8s-master ~]# cd /k8s/mysql/ [root@k8s-master mysql]# vim pv.yaml apiVersion: v1 kind: PersistentVolume #类型定义为pv metadata: name: my-pv #pv的名字 labels: #定义标签 type: nfs #类型为nfs spec: nfs: # 存储类型,需要与底层实际的存储一致,这里采用 nfs server: 192.168.122.24 # NFS 服务器的 IP path: "/mnt/data" # NFS 上共享的目录 capacity: #定义存储能力 storage: 3Gi #指定存储空间 accessModes: #定义访问模式 - ReadWriteMany #读写权限,允许被多个Node挂载 persistentVolumeReclaimPolicy: Retain #定义数据回收策略,这里是保留
1.存储能力(Capacity)
描述存储设备的能力,目前仅支持对存储空间的设置(storage=xx)。
2.访问模式(Access Modes)
对PV进行访问模式的设置,用于描述用户应用对存储资源的访问权限。访问模式如下:
ReadWriteOnce:读写权限,并且只能被单个pod挂载。 ReadOnlyMany:只读权限,允许被多个pod挂载。 ReadWriteMany:读写权限,允许被多个pod挂载。 某些PV可能支持多种访问模式,但PV在挂载时只能使用一种访问模式,多种访问模式不能同时生效。
3.persistentVolumeReclaimPolicy定义数据回收策略
目前支持如下三种回收策略:
保留(Retain):保留数据,需要手工处理。 回收空间(Recycle):警告: 回收策略 Recycle 已被废弃。取而代之的建议方案是使用动态供应。如果下层的卷插件支持,回收策略 Recycle 会在卷上执行一些基本的 擦除(rm -rf /thevolume/*)操作,之后允许该卷用于新的 PVC 申领 删除(Delete):会将 PersistentVolume 对象从 Kubernetes 中移除,同时也会从外部基础设施(如 AWS EBS、GCE PD、Azure Disk 或 Cinder 卷)中移除所关联的存储资产。 就是把云存储一起删了。
目前,只有 NFS 和 HostPath 两种类型的存储设备支持 “Recycle” 策略; AWS EBS、 GCE PD、Azure Disk 和 Cinder volumes 支持 “Delete” 策略。
4.storageClassName存储类别(Class)
PV可以设定其存储的类型(Class),通过 storageClassName参数指定一个 StorageClass 资源对象的名称。 具有特定“类别”的 PV 只能与请求了该“类别”的 PVC 进行绑定。未设定 “类别” 的 PV 则只能与不请求任何 “类别” 的 PVC 进行绑定。 相当于一个标签。
[root@k8s-master mysql]# kubectl apply -f pv.yaml
persistentvolume/my-pv created
[root@k8s-master mysql]# kubectl get pv
PV 生命周期的各个阶段(Phase)
某个 PV 在生命周期中,可以处于以下4个阶段之一:
- Available:可用状态,还未与某个 PVC 绑定。 - Bound:已与某个 PVC 绑定。 - Released:释放,绑定的 PVC 已经删除,但没有被集群回收存储空间 。 - Failed:自动资源回收失败。
pv创建成功目前属于可用状态,还没有与pvc绑定,那么现在创建pvc
创建pvc
[root@k8s-master mysql]# vim mysql-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim #定义类型为PVC
metadata:
name: mypvc #声明pvc的名称,当做pod的卷使用时会用到
spec:
accessModes: #定义访问pvc的模式,与pv拥有一样的模式
- ReadWriteMany #读写权限,允许被多个pod挂载
resources: #声明可以请求特定数量的资源,目前仅支持 request.storage 的设置,即存储空间大小。
requests:
storage: 3Gi #定义空间大小
selector: #PV选择条件,标签选择器,通过标签选择
matchLabels:
type: "nfs" #选择pv类型的nfs
注意:当我们申请pvc的容量大于pv的容量是无法进行绑定的。 创建pvc
[root@k8s-master mysql]# kubectl apply -f mysql-pvc.yaml
persistentvolumeclaim/mypvc created
[root@k8s-master mysql]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypvc Bound my-pv 3Gi RWX pv-nfs 37s
status状态
- Available (可用): 表示可用状态,还未被任何PVC绑定 - Bound (已绑定):已经绑定到某个PVC - Released (已释放):对应的PVC已经删除,但资源还没有被集群收回 - Failed:PV自动回收失败
Kubernetes中会自动帮我们查看pv状态为Available并且根据声明pvc容量storage的大小进行筛选匹配,同时还会根据AccessMode进行匹配。如果pvc匹配不到pv会一直处于pending状态。
创建secret
[root@k8s-master mysql]# echo -n 'wangxia@123!' | base64
UWlhbkZlbmdAMTIzIQ==
[root@k8s-master mysql]# vim mysql-secret.yaml
apiVersion: v1
data:
password: UWlhbkZlbmdAMTIzIQ==
kind: Secret
metadata:
annotations:
name: my-pass
type: Opaque
[root@k8s-master mysql]# kubectl apply -f mysql-secret.yaml
secret/my-pass created
[root@k8s-master mysql]# kubectl get secret
NAME TYPE DATA AGE
default-token-24c52 kubernetes.io/service-account-token 3 6d22h
my-pass Opaque 1 69s
创建myslq-pod文件
[root@k8s-master mysql]# cat mysql-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: my-mysql
spec:
replicas: 1
selector:
matchLabels:
app: mysql
template:
metadata:
labels:
app: mysql
spec:
containers:
- name: my-mysql
image: daocloud.io/library/mysql:5.7
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: my-pass
key: password
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql
volumes:
- name: mysql-data
persistentVolumeClaim: #绑定pvc
claimName: mypvc #指定对应的pvc名字
[root@k8s-master mysql]# kubectl apply -f mysql-deployment.yaml
[root@k8s-master mysql]# kubectl get pod
NAME READY STATUS RESTARTS AGE
my-mysql-5474b6885f-c5dmp 1/1 Running 0 8s
[root@k8s-master mysql]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
my-mysql-5474b6885f-c5dmp 1/1 Running 0 40s 10.244.2.5 k8s-node2
测试
[root@k8s-master ~]# cd /mnt/data/
[root@k8s-master data]# ll
总用量 188484
-rw-r----- 1 polkitd ssh_keys 56 11月 8 21:49 auto.cnf
-rw------- 1 polkitd ssh_keys 1680 11月 8 21:49 ca-key.pem
-rw-r--r-- 1 polkitd ssh_keys 1112 11月 8 21:49 ca.pem
-rw-r--r-- 1 polkitd ssh_keys 1112 11月 8 21:49 client-cert.pem
-rw------- 1 polkitd ssh_keys 1680 11月 8 21:49 client-key.pem
-rw-r----- 1 polkitd ssh_keys 688 11月 8 21:57 ib_buffer_pool
-rw-r----- 1 polkitd ssh_keys 79691776 11月 8 21:59 ibdata1
-rw-r----- 1 polkitd ssh_keys 50331648 11月 8 21:59 ib_logfile0
-rw-r----- 1 polkitd ssh_keys 50331648 11月 8 21:49 ib_logfile1
-rw-r----- 1 polkitd ssh_keys 12582912 11月 8 22:00 ibtmp1
drwxr-x--- 2 polkitd ssh_keys 4096 11月 8 21:49 mysql
drwxr-x--- 2 polkitd ssh_keys 8192 11月 8 21:49 performance_schema
-rw------- 1 polkitd ssh_keys 1680 11月 8 21:49 private_key.pem
-rw-r--r-- 1 polkitd ssh_keys 452 11月 8 21:49 public_key.pem
-rw-r--r-- 1 polkitd ssh_keys 1112 11月 8 21:49 server-cert.pem
-rw------- 1 polkitd ssh_keys 1676 11月 8 21:49 server-key.pem
drwxr-x--- 2 polkitd ssh_keys 8192 11月 8 21:49 sys
StorageClass 相当于一个创建 PV 的模板,用户通过 PVC 申请存储卷,StorageClass 通过模板自动创建 PV,然后和 PVC 进行绑定。
StorageClass创建动态存储卷流程
集群管理员预先创建存储类(StorageClass);
用户创建使用存储类的持久化存储声明(PVC:PersistentVolumeClaim);
存储持久化声明通知系统,它需要一个持久化存储(PV: PersistentVolume);
系统读取存储类的信息;
系统基于存储类的信息,在后台自动创建PVC需要的PV;
用户创建一个使用PVC的Pod;
Pod中的应用通过PVC进行数据的持久化;
而PVC使用PV进行数据的最终持久化处理。
StorageClass支持的动态存储插件:
NFS Provisioner 是一个自动配置卷程序,它使用现有的和已配置的 NFS 服务器来支持通过持久卷声明动态配置 Kubernetes 持久卷。
注意:k8s 1.21版本中创建pvc时nfs-provisioner会报错
E0903 08:00:24.858523 1 controller.go:1004] provision “default/test-claim” class “managed-nfs-storage”: unexpected error getting claim reference: selfLink was empty, can’t make reference
解决方法: 修改 /etc/kubernetes/manifests/kube-apiserver.yaml文件 增加 - --feature-gates=RemoveSelfLink=false
spec: containers: - command: - kube-apiserver - --feature-gates=RemoveSelfLink=false # 增加这行 - --advertise-address=172.24.0.5 - --allow-privileged=true - --authorization-mode=Node,RBAC - --client-ca-file=/etc/kubernetes/pki/ca.crt
创建ServiceAccount、ClusterRole、ClusterRoleBinding等,为nfs-client-provisioner授权
# rbac.yaml apiVersion: v1 kind: ServiceAccount metadata: name: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default --- kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: nfs-client-provisioner-runner rules: - apiGroups: [""] resources: ["persistentvolumes"] verbs: ["get", "list", "watch", "create", "delete"] - apiGroups: [""] resources: ["persistentvolumeclaims"] verbs: ["get", "list", "watch", "update"] - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["events"] verbs: ["create", "update", "patch"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: run-nfs-client-provisioner subjects: - kind: ServiceAccount name: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default roleRef: kind: ClusterRole name: nfs-client-provisioner-runner apiGroup: rbac.authorization.k8s.io --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default rules: - apiGroups: [""] resources: ["endpoints"] verbs: ["get", "list", "watch", "create", "update", "patch"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-client-provisioner subjects: - kind: ServiceAccount name: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default roleRef: kind: Role name: leader-locking-nfs-client-provisioner apiGroup: rbac.authorization.k8s.io
# nfs-provisioner.yaml apiVersion: apps/v1 kind: Deployment metadata: name: nfs-client-provisioner labels: app: nfs-client-provisioner # replace with namespace where provisioner is deployed namespace: default #与RBAC文件中的namespace保持一致 spec: replicas: 1 selector: matchLabels: app: nfs-client-provisioner strategy: type: Recreate selector: matchLabels: app: nfs-client-provisioner template: metadata: labels: app: nfs-client-provisioner spec: serviceAccountName: nfs-client-provisioner containers: - name: nfs-client-provisioner image: quay.io/external_storage/nfs-client-provisioner:latest volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: gxf-nfs-storage #provisioner名称,请确保该名称与 nfs-StorageClass.yaml文件中的provisioner名称保持一致 - name: NFS_SERVER value: 10.24.X.X #NFS Server IP地址 - name: NFS_PATH value: /home/nfs/1 #NFS挂载卷 volumes: - name: nfs-client-root nfs: server: 10.24.X.X #NFS Server IP地址 path: /home/nfs/1 #NFS 挂载卷
# 部署
[root@kube-master ~]# kubectl apply -f rbac.yaml
[root@kube-master ~]# kubectl apply -f nfs-provisioner.yaml
[root@kube-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-75bfdbdcd8-5mqbv 1/1 Running 0 4m24s
# nfs-StorageClass.yaml apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: managed-nfs-storage provisioner: gxf-nfs-storage #这里的名称要和provisioner配置文件中的环境变量PROVISIONER_NAME保持一致 reclaimPolicy: Retain # 默认为delete parameters: archiveOnDelete: "true" # false表示pv被删除时,在nfs下面对应的文件夹也会被删除,true正相反
部署一个有2个副本的deployment,挂载共享目录
创建pvc,“storageClassName"为上面创建的"managed-nfs-storage”,即指定动态创建PV的模板文件的名字。 # test-pvclaim.yaml kind: PersistentVolumeClaim apiVersion: v1 metadata: name: test-claim spec: accessModes: - ReadWriteMany resources: requests: storage: 100Mi storageClassName: managed-nfs-storage
[root@kube-master ~]# kubectl apply -f test-pvclaim.yaml
deployment部署
# test-deploy.yaml apiVersion: apps/v1 kind: Deployment metadata: name: test-deploy labels: app: test-deploy namespace: default #与RBAC文件中的namespace保持一致 spec: replicas: 2 selector: matchLabels: app: test-deploy strategy: type: Recreate selector: matchLabels: app: test-deploy template: metadata: labels: app: test-deploy spec: containers: - name: test-pod image: busybox:1.24 command: - "/bin/sh" args: - "-c" # - "touch /mnt/SUCCESS3 && exit 0 || exit 1" #创建一个SUCCESS文件后退出 - touch /mnt/SUCCESS5; sleep 50000 volumeMounts: - name: nfs-pvc mountPath: "/mnt" # subPath: test-pod-3 # 子路径 (这路基代表存储卷下面的test-pod子目录) volumes: - name: nfs-pvc persistentVolumeClaim: claimName: test-claim #与PVC名称保持一致
# test-sts-1.yaml apiVersion: apps/v1 kind: StatefulSet metadata: name: test-sts labels: k8s-app: test-sts spec: serviceName: test-sts-svc replicas: 3 selector: matchLabels: k8s-app: test-sts template: metadata: labels: k8s-app: test-sts spec: containers: - image: busybox:1.24 name: test-pod command: - "/bin/sh" args: - "-c" # - "touch /mnt/SUCCESS3 && exit 0 || exit 1" #创建一个SUCCESS文件后退出 - touch /mnt/SUCCESS5; sleep 50000 imagePullPolicy: IfNotPresent volumeMounts: - name: nfs-pvc mountPath: "/mnt" volumeClaimTemplates: - metadata: name: nfs-pvc spec: accessModes: ["ReadWriteMany"] storageClassName: managed-nfs-storage resources: requests: storage: 20Mi
[root@kube-master ~]# kubectl apply -f test-sts-1.yaml
[root@kube-master ~]# kubectl get sts
NAME READY AGE
test-sts 3/3 4m46s