动态创建NFS-PV案例
- [1. 前置条件](#1. 前置条件)
- [2. StorageClass 存储类的概念和使用](#2. StorageClass 存储类的概念和使用)
- [3. RBAC 配置](#3. RBAC 配置)
- [4. storageClass 配置](#4. storageClass 配置)
- [5. 创建应用,测试 PVC 的自动配置](#5. 创建应用,测试 PVC 的自动配置)
- [6. 解决 PVC 为 Pending 状态问题](#6. 解决 PVC 为 Pending 状态问题)
- [7. 单独测试自动创建 PVC](#7. 单独测试自动创建 PVC)
1. 前置条件
这里使用 NFS 存储的方式,来演示动态创建 PV 的案例。
前置条件是需要在 K8s 集群中安装 NFS 的环境,安装可参考:持续集成部署-k8s-数据持久化-NFS安装与使用
确保 NFS 服务器正常可用之后,接着后续的步骤操作。
验证配置是否生效:
- 在当前nfs-server 上查看:
sh
[root@docker-54 nfs]# showmount --export
Export list for docker-54:
/home/nfs/ro 192.168.104.0/24
/home/nfs/rw 192.168.104.0/24
[root@docker-54 nfs]#
- 在 nfs-client 上查看:
sh
[root@docker-55 ~]# showmount -e master
Export list for master:
/home/nfs/ro 192.168.104.0/24
/home/nfs/rw 192.168.104.0/24
[root@docker-55 ~]#
[root@docker-56 ~]# showmount -e master
Export list for master:
/home/nfs/ro 192.168.104.0/24
/home/nfs/rw 192.168.104.0/24
[root@docker-56 ~]#
可以看到,两台 Node 节点上均已显示 NFS Server 的信息了。
在动态创建 PV 之前,我们需要先了解下用到的组件的基础概念。
2. StorageClass 存储类的概念和使用
在 Kubernetes
中,StorageClass
是用于定义动态存储卷的对象。它允许管理员定义不同类型的存储,并使开发人员能够按需创建 PVC
(Persistent Volume Claim 持久卷声明)。
StorageClass
可以看作是一种存储资源的抽象,它定义了存储的类型、属性和参数等信息。Kubernetes
可以根据这些信息动态地创建 PV
(Persistent Volume 持久卷),并将其绑定到 PVC
上。
在数据存储,动态构建 PV 的过程,如上面的流程图所示。
但是在我们创建的过程中,则需要先创建 StorageClass,才能保证在 PVC 使用时,对应上,另外 在 Pod 创建时指定的 PVC 也是需要提前创建才行。
3. RBAC 配置
新建配置文件:nfs-provisioner-rbac.yaml
yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: kube-system
---
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: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
- 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
namespace: kube-system
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
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: kube-system
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
这个对应了 k8s 中角色权限的部分内容,在 k8s 的服务管理方面,其实就是一套类似 HTTP 的 RESTful 风格的 API,这些 API 默认是具备一个 RBAC 权限的。
上面的配置文件创建了角色、账号,以及如何把角色和账号关联起来。
4. storageClass 配置
新建的配置文件:nfs-storage-class.yaml
yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
provisioner: fuseim.pri/ifs
parameters:
archiveOnDelete: "false" # 是否存档 false 表示不存档,会删除 oldPath 下的数据,true表示会存档,会重命名路径
reclaimPolicy: Retain # 回收策略, 默认为 Delete 可以配置为 Retain
volumeBindingMode: Immediate # 默认为 Immediate,表示创建 PVC 立即绑定,只有azuredisk 和 AWSelasticblockstore 支持其他值
新建制备器配置文件:nfs-provisioner-deployment.yaml
yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: kube-system
---
kind: Deployment
apiVersion: apps/v1
metadata:
namespace: kube-system
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccount: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
# image: quay.io/external_storage/nfs-client-provisioner:latest
image: registry.cn-beijing.aliyuncs.com/pylixm/nfs-subdir-external-provisioner:v4.0.0
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: fuseim.pri/ifs # 对应 sc 里面的provisioner 的名称
- name: NFS_SERVER
value: 192.168.104.54 # nfs 服务的IP 和 路径 都需要关联上才行
- name: NFS_PATH
value: /home/nfs/rw
volumes:
- name: nfs-client-root
nfs:
server: 192.168.104.54
path: /home/nfs/rw
上面这些东西,可能刚开始的时候看有点熟悉,又有点懵,上面的内容,不需要特殊记忆,理解就行,需要改动的地方,也就是最下面 volumes 相关的 server 和 path ,其他内容均为官方配置的内容。基本上也不会有很大的改动。
接着最后是应用的配置文件:nfs-sc-demo-statefulset.yaml
yaml
---
apiVersion: v1
kind: Service
metadata:
name: nginx-sc
labels:
app: nginx-sc
spec:
type: NodePort
ports:
- name: web
port: 80
protocol: TCP
selector:
app: nginx-sc
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx-sc
spec:
replicas: 1
serviceName: "nginx-sc" # 对应上面的 Service
selector:
matchLabels:
app: nginx-sc # 匹配到下面的 Pod 的标签配置
template:
metadata:
labels:
app: nginx-sc # Pod 模板标签
spec:
containers:
- image: nginx
name: nginx-sc
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /usr/share/nginx/html # 挂载到容器的哪个目录
name: nginx-sc-test-pvc # 挂载哪个 volume
volumeClaimTemplates:
- metadata:
name: nginx-sc-test-pvc
spec:
storageClassName: managed-nfs-storage
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
这里创建了 Service 和 StatefulSet ,来管理 Nginx 的 Pod。
volumeClaimTemplates 的配置为 希望可以帮我自动创建一个 PVC,这个 PVC 声明了使用的存储类 storageClass
,配置了 storageClass
的名称storageClassName
和访问模式accessModes
,这声明为可以多个节点同时读写的一个模式。最后声明,期望所需的资源最小是 1Gi
。
5. 创建应用,测试 PVC 的自动配置
我们希望的效果是,一旦上面的应用创建起来之后,它自动帮我们把 PVC 创建出来,并且把 PV 也创建出来。
接下来完成上面这些内容的构建,分别把他们构建出来,然后再去把应用跑起来。
先看下当前环境中 pv 和 pvc 的情况:
sh
[root@docker-54 ~]# kubectl get pv | grep nginx
[root@docker-54 ~]#
[root@docker-54 ~]# kubectl get pvc
No resources found in default namespace.
[root@docker-54 ~]#
[root@docker-54 ~]# kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
nfs-storage (default) nfs-client Delete Immediate false 132d
[root@docker-54 ~]#
可以看到,当前是没有对应的 PV 和 PVC 的。
接着需要先把 RBAC启动起来:
bash
[root@docker-54 ~]# cd /opt/k8s/config/
[root@docker-54 config]# ls
application.yaml file-test-pod.yaml nfs-provisioner-rbac.yaml nginx.conf test
env-test-pod.yaml nfs-provisioner-deployment.yaml nfs-storage-class.yaml private-image-pull-pod.yaml
[root@docker-54 config]#
[root@docker-54 config]# kubectl apply -f nfs-provisioner-rbac.yaml
serviceaccount/nfs-client-provisioner unchanged
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner configured
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner unchanged
role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner unchanged
rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner unchanged
[root@docker-54 config]#
这里由于我之前创建过一些 RBAC 权限,这里有的显示 unchanged
,首次执行这个应该是 created
或者configured
。
可以看到,上面RBAC 里面配置了一个 serviceaccount
以及它对应的集群角色,然后是角色的绑定。接着是一个普通的角色以及普通角色的绑定。
创建完权限,接着来创建 deployment:
bash
[root@docker-54 config]# kubectl apply -f nfs-provisioner-deployment.yaml
serviceaccount/nfs-client-provisioner unchanged
deployment.apps/nfs-client-provisioner configured
[root@docker-54 config]#
接着创建 StorageClass:kubectl apply -f nfs-storage-class.yaml
bash
[root@docker-54 config]# kubectl apply -f nfs-storage-class.yaml
storageclass.storage.k8s.io/managed-nfs-storage created
[root@docker-54 config]#
这里 StorageClass 也创建完成后,可以查看下:
bash
[root@docker-54 config]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
managed-nfs-storage fuseim.pri/ifs Retain Immediate false 60s
nfs-storage (default) nfs-client Delete Immediate false 132d
[root@docker-54 config]#
可以看到,这里确实多了一条记录managed-nfs-storage
,并且 PROVISIONER
的值,也正是我们配置的 fuseim.pri/ifs
,这个是我们后续使用时关联的一个标识。
接着我们来看下上面创建的 Deployment :
bash
[root@docker-54 config]# kubectl get po -n kube-system | grep nfs
nfs-client-provisioner-77bdfd69d9-9hzhx 1/1 Running 0 134m
[root@docker-54 config]#
可以看到已经是正常运行的状态了。
接下来我们创建statefulset
:kubectl apply -f nfs-sc-demo-statefulset.yaml
bash
[root@docker-54 config]# kubectl apply -f nfs-sc-demo-statefulset.yaml
service/nginx-sc created
statefulset.apps/nginx-sc created
[root@docker-54 config]#
接着看下应用的状态:
bash
[root@docker-54 config]# kubectl get sts
NAME READY AGE
nginx-sc 1/1 54s
[root@docker-54 config]#
[root@docker-54 config]# kubectl get po | grep nginx
nginx-sc-0 1/1 Running 0 115s
[root@docker-54 config]#
可以看到正常创建、并启动了。
6. 解决 PVC 为 Pending 状态问题
如果这里 Nginx 的 Pod 为 Pending 状态,则要检查下:kubectl describe po nginx-sc-0
bash
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 3m20s default-scheduler 0/3 nodes are available: 3 pod has unbound immediate PersistentVolumeClaims.
Normal Scheduled 3m18s default-scheduler Successfully assigned default/nginx-sc-0 to docker-55
Normal Pulled 3m18s kubelet Container image "nginx" already present on machine
Normal Created 3m18s kubelet Created container nginx-sc
Normal Started 3m18s kubelet Started container nginx-sc
[root@docker-54 config]#
如果状态异常,上面命令可以看到具体的原因。这里有个FailedScheduling
的警告,提示我们三个节点都是可用的,但是 3 个 pod 还是 unbound
,意思是还未绑定到 PVC。
这时候可以排查下 PVC 的状态及原因:
bash
[root@docker-54 config]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nginx-sc-test-pvc-nginx-sc-0 Bound pvc-ba5db961-8b17-4507-a287-ca767d607fa9 1Gi RWX managed-nfs-storage 6m18s
[root@docker-54 config]#
如果这里也为 Pending,状态,此时可能 PV 也是非正常的,可以使用 describe 看下具体原因或者看下 PV 的状态。
bash
[root@docker-54 config]# kubectl get pv | grep nginx
pvc-ba5db961-8b17-4507-a287-ca767d607fa9 1Gi RWX Retain Bound default/nginx-sc-test-pvc-nginx-sc-0 managed-nfs-storage 8m3s
[root@docker-54 config]#
我这里 PV 是正常的,是因为我修改了,k8s制备器的镜像,k8s 默认是quay.io/external_storage/nfs-client-provisioner:latest
镜像,这个镜像需要用到 K8s 中的 一个 selfLink 的功能,但是从 1.20 版本之后,k8s 把这个功能给关掉了,可能是处于性能以及 API 调用请求的一个方向考虑的,把这个功能给禁用掉了,所以就意味着我们新版本不能再直接使用这个镜像了。
我这里使用的版本是v1.22.6
bash
[root@docker-54 config]# kubectl version
Client Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.6", GitCommit:"f59f5c2fda36e4036b49ec027e556a15456108f0", GitTreeState:"clean", BuildDate:"2022-01-19T17:33:06Z", GoVersion:"go1.16.12", Compiler:"gc", Platform:"linux/amd64"}
Server Version: version.Info{Major:"1", Minor:"22", GitVersion:"v1.22.6", GitCommit:"f59f5c2fda36e4036b49ec027e556a15456108f0", GitTreeState:"clean", BuildDate:"2022-01-19T17:26:47Z", GoVersion:"go1.16.12", Compiler:"gc", Platform:"linux/amd64"}
[root@docker-54 config]#
这里PVC 处于 Pending 状态有两种解决方案:
- 第一种是配置
SelfLink
:修改 apiserver 配置文件
bash
vim /etc/kubernetes/manifests/kube-apiserver.yaml
spec:
containers:
- command:
- kube-apiserver
- --feature-gates=RemoveSelfLink=false # 新增该行
......
修改后重新应用该配置,
bash
kubectl apply -f /etc/kubernetes/manifests/kube-apiserver.yaml
这样可以解决因为版本导致 PVC pending 状态的问题。不过既然官方已经不支持了,我们也没必要强制修改让它支持,看下第二种方案。
- 第二种是使用不需要
SelfLink
的provisioner
:将 provisioner 修改为如下镜像之一即可,下面的为阿里云的镜像,性价比更高。
bash
gcr.io/k8s-staging-sig-storage/nfs-subdir-external-provisioner:v4.0.0
registry.cn-beijing.aliyuncs.com/pylixm/nfs-subdir-external-provisioner:v4.0.0
修改完镜像后,然后执行:nfs-provisioner-deployment.yaml
更新deployment
。
当看到 Pod 的状态正常,PVC 和 PV 的状态正常后,就完事了。
7. 单独测试自动创建 PVC
如果我们想单独测试下这个自动创建 PVC ,那么可以这么操作:
新建配置文件:auto-pv-test-pvc.yaml
bash
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: auto-pv-test-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 300Mi
storageClassName: managed-nfs-storage
这里直接创建 PVC,并没有声明关于 PV 的内容,仅仅声明了 PVC 的名称、需要的资源、使用的 storageClassName
。
bash
[root@docker-54 config]# kubectl apply -f auto-pv-test-pvc.yaml
persistentvolumeclaim/auto-pv-test-pvc created
[root@docker-54 config]#
[root@docker-54 config]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
auto-pv-test-pvc Bound pvc-1a180c44-a4d3-44e3-83d0-b69bbf73f2df 300Mi RWO managed-nfs-storage 26s
nginx-sc-test-pvc-nginx-sc-0 Bound pvc-ba5db961-8b17-4507-a287-ca767d607fa9 1Gi RWX managed-nfs-storage 47m
[root@docker-54 config]#
可以看到,新建的 PVC auto-pv-test-pvc
已经正常绑定到名为pvc-1a180c44-a4d3-44e3-83d0-b69bbf73f2df
的 PV 上了。这样也是可以达到自动创建 PVC 的目的。
在后续的使用中,我们使用自动创建 PVC 就不用关心 PV ,我们只需要写我们的需求就好了,系统可以根据我们的需求自动创建PV 来自动绑定。