使用 StorageClass 动态创建 NFS 持久卷
类别: StorageClass 标签: Kubernetes PersistentVolumeClaim PersistentVolume Provisioner NFS mongo目录
- PVC 操作流程
- 在 Ubuntu 上安装 NFS 服务器
- 使用 NFS 存储卷
- 使用 PersistentVolumeClaim(持久卷声明)和 PersistentVolume(持久卷)解耦
- 使用 StorageClass 动态创建持久卷
- 参考资料
PVC 操作流程
Volume
卷的核心是一个目录,其中可能存有数据,Pod 中的容器可以访问该目录中的数据。 所采用的特定的卷类型将决定该目录如何形成的、使用何种介质保存数据以及目录中存放的内容。
使用卷时, 在 .spec.volumes
字段中设置为 Pod 提供的卷,并在 .spec.containers[*].volumeMounts
字段中声明卷在容器中的挂载位置。
emptyDir 卷的存储介质(例如磁盘、SSD 等)是由保存 kubelet 数据的根目录(通常是 /var/lib/kubelet)的文件系统的介质确定。 Kubernetes 对 emptyDir 卷或者 hostPath 卷可以消耗的空间没有限制,容器之间或 Pod 之间也没有隔离。
PersistentVolume
持久卷(PersistentVolume,PV) 是集群中的一块存储,可以由管理员事先创建, 或者使用存储类(Storage Class)来动态创建。 持久卷是集群资源,就像节点也是集群资源一样。PV 持久卷和普通的 Volume 一样, 也是使用卷插件来实现的,只是它们拥有独立于任何使用 PV 的 Pod 的生命周期。
PV 对象是由运维人员事先创建在 Kubernetes 集群里待用的。
PersistentVolumeClaim
持久卷声明(PersistentVolumeClaim,PVC) 表达的是用户对存储的请求。概念上与 Pod 类似。 Pod 会耗用节点资源,而 PVC 申领会耗用 PV 资源。Pod 可以请求特定数量的资源(CPU 和内存);同样 PVC 申领也可以请求特定的大小和访问模式 (例如,可以要求 PV 卷能够以 ReadWriteOnce、ReadOnlyMany 或 ReadWriteMany 模式之一来挂载)。
PVC 对象通常由开发人员创建。
StorageClass
StorageClass 为管理员提供了描述存储 “类” 的方法。 不同的类型可能会映射到不同的服务质量等级或备份策略,或是由集群管理员制定的任意策略。
每个 StorageClass 都包含 provisioner、parameters 和 reclaimPolicy 字段, 这些字段会在 StorageClass 需要动态制备 PersistentVolume 时会使用到。
回收策略
由 StorageClass 动态创建的 PersistentVolume 会在类的 reclaimPolicy 字段中指定回收策略,可以是 Delete 或者 Retain。 如果 StorageClass 对象被创建时没有指定 reclaimPolicy,它将默认为 Delete。
在 Ubuntu 上安装 NFS 服务器
安装服务端
sudo apt install nfs-kernel-server
查看服务状态
sudo systemctl status nfs-server
查看开启的NFS协议
sudo cat /proc/fs/nfsd/versions
-2 +3 +4 +4.1 +4.2
创建存储目录
sudo mkdir /data/nfs
sudo chmod 777 /data/nfs
配置访问的路径
sudo vim /etc/exports
/data/nfs 172.16.33.0/24(rw,sync,no_subtree_check,no_root_squash)
- no_subtree_check NFS 服务器将忽略子目录的检查。这意味着,无论请求的路径是否位于共享目录的子目录中,NFS服务器都将按照共享目录的权限设置来处理请求。这样可以提高性能,因为服务器不再需要进行额外的子目录检查。
需要谨慎使用,以确保共享目录的访问权限受到正确的限制。
- no_root_squash no_root_squash 选项用于取消 NFS 服务器对 root 用户的权限限制,允许 root 用户在客户端系统上拥有完全的访问权限。
默认情况下,NFS 服务器会将来自客户端的 root 用户请求映射为匿名用户或具有受限权限的用户。这样做是为了提高安全性,防止远程 root 用户对服务器文件系统进行未授权的更改。
然而,对于某些应用程序(如 MongoDB),需要在容器中以特权模式运行,并且可能需要更改文件系统的所有权。在这种情况下,如果 NFS 服务器将 root 用户请求映射为受限用户,那么容器将无法更改 /data/db 目录的所有权,因为它没有足够的权限。
通过在 NFS 服务器配置中添加 no_root_squash 选项,你明确指示服务器不对来自 root 用户的请求进行映射或限制,从而允许容器以 root 用户的身份操作文件系统,并成功更改 /data/db 目录的所有权。
请注意,启用 no_root_squash 选项可能会降低系统的安全性,因为允许远程 root 用户拥有对文件系统的完全访问权限。在进行此更改之前,请确保你已经评估了安全风险,并采取了适当的措施来保护服务器和数据的安全性。
在 mongo 的例子中,NFS 服务器没有配置 no_root_squash
,出现错误:chown: changing ownership of '/data/db': Operation not permitted
应用配置
sudo exportfs -ra
应用配置后就生效了,不需要再重启服务。
查看当前应用
sudo exportfs -v
/data/nfs 172.16.33.0/24(rw,wdelay,no_root_squash,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
重启服务
sudo systemctl restart nfs-server
使用 NFS 存储卷
这里使用了 mongo 来做实验。
Pod 清单:mongodb-pod-nfs.yaml
apiVersion: v1
kind: Pod
metadata:
name: mongodb
spec:
containers:
- name: mongodb
image: mongo
volumeMounts:
- name: mongodb-data
mountPath: /data/db
ports:
- containerPort: 27017
protocol: TCP
volumes:
- name: mongodb-data
nfs:
server: 172.16.33.157
path: /data/nfs/mongo-data
可以通过 sudo exportfs -v
命令在 NFS 服务器上查看共享目录。/data/nfs/mongo-data
目录需要在 NFS 服务器上创建。
创建 Pod
kubectl apply -f mongodb-pod-nfs.yaml
通过 MongoDB Shell 写入数据
kubectl exec -it mongodb -- mongosh
Current Mongosh Log ID: 64a51200ddc2fd10f7cd6bde
Connecting to: mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.10.1
Using MongoDB: 6.0.7
Using Mongosh: 1.10.1
For mongosh info see: https://docs.mongodb.com/mongodb-shell/
test> db.wjj.insertOne({name:'wjj', age:42})
{
acknowledged: true,
insertedId: ObjectId("64a5134fddc2fd10f7cd6bdf")
}
删除并创建 Pod 进行验证
kubectl delete -f mongodb-pod-nfs.yaml
kubectl apply -f mongodb-pod-nfs.yaml
通过 MongoDB Shell 检索之前存储的数据
kubectl exec -it mongodb -- mongosh
Current Mongosh Log ID: 64a513cfbb3e3bae96a38d35
Connecting to: mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.10.1
Using MongoDB: 6.0.7
Using Mongosh: 1.10.1
For mongosh info see: https://docs.mongodb.com/mongodb-shell/
test> db.wjj.find()
[ { _id: ObjectId("64a5134fddc2fd10f7cd6bdf"), name: 'wjj', age: 42 } ]
可以看到删除并创建 Pod,数据依然存在。
使用 PersistentVolumeClaim(持久卷声明)和 PersistentVolume(持久卷)解耦
持久卷清单:mongodb-pv-nfs.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: mongodb-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain
nfs:
server: 172.16.33.157
path: /data/nfs/mongo-data
创建 PV
kubectl apply -f mongodb-pv-nfs.yaml
查看所有 PV,mongodb-pv 的状态是 Available
kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
mongodb-pv 1Gi RWO,ROX Retain Available 40s
持久卷声明清单:mongodb-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongodb-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
创建 PVC
kubectl apply -f mongodb-pvc.yaml
查看所有 PVC, PV,mongodb-pvc 和 mongodb-pv 的状态都为 Bound
,mongodb-pvc 的 VOLUME 为 mongodb-pv
。
kubectl get pvc,pv
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/mongodb-pvc Bound mongodb-pv 1Gi RWO,ROX 35s
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/mongodb-pv 1Gi RWO,ROX Retain Bound default/mongodb-pvc 8m17s
Pod 清单:mongodb-pod-nfs.yaml
apiVersion: v1
kind: Pod
metadata:
name: mongodb
spec:
containers:
- name: mongodb
image: mongo
volumeMounts:
- name: mongodb-data
mountPath: /data/db
ports:
- containerPort: 27017
protocol: TCP
volumes:
- name: mongodb-data
persistentVolumeClaim:
claimName: mongodb-pvc
创建 Pod
kubectl apply -f mongodb-pod-nfs.yaml
通过 MongoDB Shell 检索之前存储的数据
kubectl exec -it mongodb -- mongosh
Current Mongosh Log ID: 64a524a394110078b49c1ce6
Connecting to: mongodb://127.0.0.1:27017/?directConnection=true&serverSelectionTimeoutMS=2000&appName=mongosh+1.10.1
Using MongoDB: 6.0.7
Using Mongosh: 1.10.1
For mongosh info see: https://docs.mongodb.com/mongodb-shell/
test> db.wjj.find()
[ { _id: ObjectId("64a5134fddc2fd10f7cd6bdf"), name: 'wjj', age: 42 } ]
删除 Pod, PVC, PV
kubectl delete -f .
查看 NFS 服务器上的数据还存在
ll /data/nfs/mongo-data/
-rw------- 1 systemd-coredump systemd-coredump 20480 Jul 5 16:08 collection-0--7871518737263603795.wt
-rw------- 1 systemd-coredump systemd-coredump 36864 Jul 5 16:08 collection-2--7871518737263603795.wt
-rw------- 1 systemd-coredump systemd-coredump 36864 Jul 5 16:08 collection-4--7871518737263603795.wt
-rw------- 1 systemd-coredump systemd-coredump 20480 Jul 5 16:08 collection-7--7871518737263603795.wt
drwx------ 2 systemd-coredump systemd-coredump 4096 Jul 5 16:08 diagnostic.data/
-rw------- 1 systemd-coredump systemd-coredump 20480 Jul 5 16:08 index-1--7871518737263603795.wt
-rw------- 1 systemd-coredump systemd-coredump 36864 Jul 5 16:08 index-3--7871518737263603795.wt
-rw------- 1 systemd-coredump systemd-coredump 24576 Jul 5 16:08 index-5--7871518737263603795.wt
-rw------- 1 systemd-coredump systemd-coredump 12288 Jul 5 16:08 index-6--7871518737263603795.wt
-rw------- 1 systemd-coredump systemd-coredump 20480 Jul 5 14:54 index-8--7871518737263603795.wt
drwx------ 2 systemd-coredump systemd-coredump 4096 Jul 5 16:06 journal/
-rw------- 1 systemd-coredump systemd-coredump 36864 Jul 5 16:08 _mdb_catalog.wt
drwx------ 3 systemd-coredump root 4096 Jul 5 14:47 .mongodb/
-rw------- 1 systemd-coredump systemd-coredump 0 Jul 5 16:08 mongod.lock
-rw------- 1 systemd-coredump systemd-coredump 36864 Jul 5 16:08 sizeStorer.wt
-rw------- 1 systemd-coredump systemd-coredump 114 Jul 5 14:38 storage.bson
-rw------- 1 systemd-coredump systemd-coredump 50 Jul 5 14:38 WiredTiger
-rw------- 1 systemd-coredump systemd-coredump 4096 Jul 5 16:08 WiredTigerHS.wt
-rw------- 1 systemd-coredump systemd-coredump 21 Jul 5 14:39 WiredTiger.lock
-rw------- 1 systemd-coredump systemd-coredump 1467 Jul 5 16:08 WiredTiger.turtle
-rw------- 1 systemd-coredump systemd-coredump 53248 Jul 5 16:08 WiredTiger.wt
使用 StorageClass 动态创建持久卷
NFS Provisioner
Kubernetes NFS Subdir External Provisioner
NFS Subdir External Provisioner 是一个自动配置程序,它使用现有且已配置的 NFS 服务器来支持通过持久卷声明动态配置 Kubernetes 持久卷。持久卷配置为 ${namespace}-${pvcName}-${pvName}
。
NFS Ganesha server and external provisioner
它的工作方式:StorageClass 对象可以指定 nfs-ganesha-server-and-external-provisioner 的实例作为其配置器,就像指定内置配置器(例如 GCE 或 AWS)一样。 然后,nfs-ganesha-server-and-external-provisioner 的实例将监视请求 StorageClass 的 PersistentVolumeClaims,并自动为它们创建 NFS 支持的 PersistentVolume。
构建 NFS Subdir External Provisioner 镜像
gcr.io
和 registry.k8s.io
仓库,在国内都访问不了,自己动手吧。
下载依赖的基础镜像 gcr.io/distroless/static:latest
docker pull gcr.m.daocloud.io/distroless/static:latest
docker tag gcr.m.daocloud.io/distroless/static:latest gcr.io/distroless/static:latest
docker rmi gcr.m.daocloud.io/distroless/static:latest
克隆和构建
git clone https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner
cd nfs-subdir-external-provisioner/
make build
make container
上传到我的 Docker Hub 上
docker tag nfs-subdir-external-provisioner:latest wangjunjian/nfs-subdir-external-provisioner:latest
docker push wangjunjian/nfs-subdir-external-provisioner:latest
使用 Kustomize 部署
创建 kustomize
目录
mkdir kustomize
cd kustomize
创建 Namespace
namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: nfs-provisioner
配置 Deployment
patch_nfs_details.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nfs-client-provisioner
name: nfs-client-provisioner
spec:
template:
spec:
containers:
- name: nfs-client-provisioner
env:
- name: NFS_SERVER
value: 172.16.33.157
- name: NFS_PATH
value: /data/nfs
volumes:
- name: nfs-client-root
nfs:
server: 172.16.33.157
path: /data/nfs
配置您的 NFS 服务器的 IP
和共享目录
。
添加资源
将 deploy 目录添加为 base。
kustomization.yaml
namespace: nfs-provisioner
bases:
- github.com/kubernetes-sigs/nfs-subdir-external-provisioner//deploy
resources:
- namespace.yaml
patchesStrategicMerge:
- patch_nfs_details.yaml
部署
kubectl apply -k .
替换我们自己构建的镜像。
kubectl edit deployment.apps/nfs-client-provisioner -n nfs-provisioner
把 image: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
替换为 image: wangjunjian/nfs-subdir-external-provisioner:latest
使用 Helm 部署
helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
--set nfs.server=172.16.33.157 \
--set nfs.path=/data/nfs
NAME: nfs-subdir-external-provisioner
LAST DEPLOYED: Thu Jun 29 13:08:34 2023
NAMESPACE: nfs
STATUS: deployed
REVISION: 1
TEST SUITE: None
国内不能访问仓库 registry.k8s.io
,可以考虑使用其它方式把镜像下载下来。可以参考这篇文章:如何轻松的下载海外镜像
docker pull k8s.m.daocloud.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
docker tag k8s.m.daocloud.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2 registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2
docker pull k8s.m.daocloud.io/busybox:latest
docker tag k8s.m.daocloud.io/busybox:latest registry.k8s.io/busybox:latest
手动部署
下载部署的 YAML 文件
下载 NFS Subdir External Provisioner 仓库的 deploy 下的所有的文件。
设置授权
$ kubectl apply -f deploy/rbac.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
配置 NFS subdir external provisioner
deploy/deployment.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
spec:
replicas: 1
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: wangjunjian/nfs-subdir-external-provisioner:latest
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 172.16.33.157
- name: NFS_PATH
value: /data/nfs
volumes:
- name: nfs-client-root
nfs:
server: 172.16.33.157
path: /data/nfs
kubectl apply -f deploy/deployment.yaml
deployment.apps/nfs-client-provisioner created
部署您的 Storage Class
deploy/class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:
archiveOnDelete: "false"
kubectl apply -f deploy/class.yaml
测试
- 创建 test 目录
mkdir test cd test
- 下载测试资源
wget https://raw.githubusercontent.com/kubernetes-sigs/nfs-subdir-external-provisioner/master/deploy/test-claim.yaml wget https://raw.githubusercontent.com/kubernetes-sigs/nfs-subdir-external-provisioner/master/deploy/test-pod.yaml
test-claim.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi
test-pod.yaml
kind: Pod
apiVersion: v1
metadata:
name: test-pod
spec:
containers:
- name: test-pod
image: busybox:stable
command:
- "/bin/sh"
args:
- "-c"
- "touch /mnt/SUCCESS.txt && exit 0 || exit 1"
volumeMounts:
- name: nfs-pvc
mountPath: "/mnt"
restartPolicy: "Never"
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-claim
- 部署测试资源
kubectl apply -f .
- 查看资源
kubectl get pvc,pv,sc,pod
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/test-claim Bound pvc-5fdc6fa2-f665-4807-b0ad-4013acce7115 1Mi RWX nfs-client 61s
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-5fdc6fa2-f665-4807-b0ad-4013acce7115 1Mi RWX Delete Bound default/test-claim nfs-client 61s
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
storageclass.storage.k8s.io/nfs-client k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 61s
NAME READY STATUS RESTARTS AGE
pod/test-pod 0/1 Completed 0 61s
- 检查 NFS Server 的 PVC 的目录下是否有文件
SUCCESS
ll /data/nfs/default-test-claim-pvc-416c5245-fe98-4868-8e76-567bd5a41274/ -rw-r--r-- 1 nobody nogroup 0 Jun 30 13:23 SUCCESS
- 删除资源
kubectl delete -f .
检查 NFS Server 的 PVC 的目录是否删除。