Kubernetes使用NFS 静态存储
手工分配一个存储卷,需要指定 storageClassName 是 nfs,而 accessModes 可以设置成 ReadWriteMany,这是由 NFS 的特性决定的,它支持多个节点同时访问一个共享目录。
存储卷是 NFS 系统,所以还需要在 YAML 里添加 nfs 字段,指定 NFS 服务器的 IP 地址和共享目录名。
在 NFS 服务器的 /tmp/nfs 目录里又创建了一个新的目录 1g-pv,表示分配了 1GB 的可用存储空间,相应的,PV 里的 capacity 也要设置成同样的数值,也就是 1Gi。
NFS PV的 YAML
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-1g-pv
spec:
storageClassName: nfs
accessModes:
- ReadWriteMany
capacity:
storage: 1Gi
nfs:
path: /tmp/nfs/1g-pv
server: 172.*.*.*
执行命令
vim nfs-pv.yml
kubectl apply -f nfs-pv.yml
kubectl get pv
注意 spec.nfs 里的 IP 地址一定要正确,路径要存在(事先创建好),否则 Kubernetes 按照 PV 的描述会无法挂载 NFS 共享目录,PV 就会处于“pending”状态无法使用。
PVC yaml对象的描述
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-static-pvc
spec:
storageClassName: nfs
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
执行命令
vim nfs-pvc.yml
kubectl apply -f nfs-pvc.yml
kubectl get pv,pvc
创建Pod,把 PVC 挂载成它的一个 volume:
apiVersion: v1
kind: Pod
metadata:
name: nfs-static-pod
spec:
volumes:
- name: nfs-pvc-vol
persistentVolumeClaim:
claimName: nfs-static-pvc
containers:
- name: nfs-pvc-test
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: nfs-pvc-vol
mountPath: /tmp
执行命令
vim nfs-pvc-ngx.yml
kubectl apply -f nfs-pvc-ngx.yml
kubectl get pod
Pod、PVC、PV 和 NFS 存储关系图
PV/PVC 里指定了 storageClassName 是 nfs,节点上也安装了 NFS 客户端,所以 Kubernetes 就会自动执行 NFS 挂载动作,把 NFS 的共享目录 /tmp/nfs/1g-pv 挂载到 Pod 里的 /tmp,完全不需要去手动管理。
测试nfs挂载效果,用 kubectl exec 进入 Pod,再试着操作 NFS 共享目录:
# 进入容器
kubectl exec -it nfs-static-pod -- sh
# 切换到tmp目录
cd /tmp/
# 创建test文件并输入test nfs
echo 'test nfs'>test.txt
NFS目录查看对应挂载目录
NFS 服务器的 /tmp/nfs/1g-pv 目录,会发现 Pod 里创建的文件确实写入了共享目录。
而且NFS 是一个网络服务,不会受 Pod 调度位置的影响,所以只要网络通畅,这个 PV 对象就会一直可用,数据也就实现了真正的持久化存储
Kubernetes使用NFS Provisoner
Nfs网络存储系统确实能够让集群里的 Pod 任意访问,数据在 Pod 销毁后仍然存在,新创建的 Pod 可以再次挂载,然后读取之前写入的数据,整个过程完全是自动化的。
但 PV 还是需要人工管理,必须要由系统管理员手动维护各种存储设备,再根据开发需求逐个创建 PV,而且 PV 的大小也很难精确控制,容易出现空间不足或者空间浪费的情况。
如何实现PV 的工作也实现自动化?Kubernetes 里的“动态存储卷”可以用 StorageClass 绑定一个 Provisioner 对象,而这个 Provisioner 就是一个能够自动管理存储、创建 PV 的应用,代替了原来系统管理员的手工劳动。
Kubernetes 里每类存储设备都有相应的 Provisioner 对象,对于 NFS 来说,它的 Provisioner 就是“NFS subdir external provisioner"
项目github地址
NFS Provisioner yaml描述
NFS Provisioner 也是以 Pod 的形式运行在 Kubernetes 里的,在 GitHub 的 deploy 目录里是部署它所需的 YAML 文件,一共有三个,分别是 rbac.yaml、class.yaml 和 deployment.yaml。
rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: kube-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["nodes"]
verbs: ["get", "list", "watch"]
- 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: 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
# replace with namespace where provisioner is deployed
namespace: kube-system
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
# replace with namespace where provisioner is deployed
namespace: kube-system
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
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"
deployment.yaml (改成自己的ip和挂载目录)
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: kube-system
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: chronolaw/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 172.*.*.7
- name: NFS_PATH
value: /tmp/nfs
volumes:
- name: nfs-client-root
nfs:
server: 172.*.*.7
path: /tmp/nfs
Kubernetes 创建 NFS Provisioner
kubectl apply -f rbac.yml
kubectl apply -f class.yml
kubectl apply -f deployment.yml
使用命令 kubectl get,再加上名字空间限定 -n kube-system,就可以看到 NFS Provisioner 在 Kubernetes 里运行起来了
kubectl get pod -n kube-system
Kubernetes使用NFS 动态存储卷
有了 Provisioner,我们就不再需要手工定义 PV 对象了,只需要在 PVC 里指定 StorageClass 对象,它再关联到 Provisioner。
NFS 默认的 StorageClass 定义:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
archiveOnDelete: "false"
- provisioner,它指定了应该使用哪个 Provisioner。
- parameters 是调节 Provisioner 运行的参数,需要参考文档来确定具体值,在这里的 archiveOnDelete: “false” 就是自动回收存储空间。
根据自己的需求,任意定制具有不同存储特性的 StorageClass,比如添加字段 onDelete: “retain” 暂时保留分配的存储,之后再手动删除:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: nfs-client-retained
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
onDelete: "retain"
执行命令
vim nfs-dynamic-sc.yml
kubectl apply -f nfs-dynamic-sc.yml
PVC向系统申请 10MB 的存储空间,使用的 StorageClass 是默认的 nfs-client:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-dyn-10m-pvc
spec:
storageClassName: nfs-client
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Mi
在 Pod 里用 volumes 和 volumeMounts 挂载,然后 Kubernetes 就会自动找到 NFS Provisioner,在 NFS 的共享目录上创建出合适的 PV 对象:
apiVersion: v1
kind: Pod
metadata:
name: nfs-dyn-pod
spec:
volumes:
- name: nfs-dyn-10m-vol
persistentVolumeClaim:
claimName: nfs-dyn-10m-pvc
containers:
- name: nfs-dyn-test
image: nginx:alpine
ports:
- containerPort: 80
volumeMounts:
- name: nfs-dyn-10m-vol
mountPath: /tmp
运行命令
vim nfs-dynamic-pvc.yml
vim nfs-dynamic-ngx.yml
kubectl apply -f nfs-dynamic-pvc.yml -f nfs-dynamic-ngx.yml
kubectl get pv,pvc
kubectl get pod
虽然没有直接定义 PV 对象,但由于有 NFS Provisioner,它就自动创建一个 PV,大小刚好是在 PVC 里申请的 10MB。
NFS 服务器上查看共享目录,也会发现多出了一个目录,名字与这个自动创建的 PV 一样,但加上了名字空间和 PVC 的前缀:
Pod、PVC、StorageClass 和 Provisioner 关系图