在K8S中对于无状态的应用我们可以比较容易的扩展应用程序、滚动升级和重新启动崩溃的应用程序,但是对于一些无状态的应用,分为需要维护服务的拓扑结构和需要持久化数据两种情况,对于前者,我们可以使用headless service来实现,对于后者我们需要在集群中使用持久存储来持久化数据。而持久化存储,NFS(Network File System)是一个使用比较常用的分布式存储技术,在本系列中,我将详细演进如何在K8S集群中部署NFS。为了使得大家比较容易理解,将文章分成4个部分。
第一部分:配置 NFS 服务器
第二部分:将 NFS 服务器文件夹挂载到每个节点的本地文件夹
第三部分:将Persistent Volume配置直接连接到 NFS 服务器
第四部分:创建Storage Class声明来自动创建Persistent Volume(本文)
写在前面
这一系列文章主要译自下面的文章,但由于k8s版本的原因导致一些功能不正常,我在部署过程中也遇到了一些问题,并得以解决。此外作者主要是在树莓派上完成部署,是ARM架构,因此一些脚本不能在在PC上正常工作,我也在文中加以修改,并给出了详细的解决办法。
本文的源代码都是来自作者的github主页
https://github.com/fabiofernandesx/k8s-volumes
实现方法
谈到自动化,每次需要k8s集群对外做一些事情的时候,我们都需要一些特殊的配置来处理安全部分。
第一件事是设置服务帐户和角色绑定。 我们将使用 RBAC(基于角色的访问控制)来进行此配置。
我们需要 5 个配置来完成安全部分的工作:
- Service Account(授予权限)
- Cluster Role
- Cluster Role Binding
- Role
- Role Binding
它看起来太多了,但你最终可以在一个 60 行“ish”行的文件中进行所有配置。 您可以将所有内容放在同一个文件中,用 3 个破折号分隔每个配置,为了便于发现问题,下面详细执行每一步来执行安全相关的脚本
创建Service Account
脚本如下:
kind: ServiceAccount
apiVersion: v1
metadata:
name: nfs-client-provisioner
namespace: pv-demo
cd到脚本目录yml/StorageClass文件夹,执行创建脚本
kubectl apply -f service-account.yml
deploy@master-node:~/k8s-volumes-main/yml/StorageClass$ kubectl get sa -n pv-demo
NAME SECRETS AGE
default 1 14d
nfs-client-provisioner 1 13d
查看service account,nfs-client-provisioner创建成功
创建cluster role
脚本如下:
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"]
执行脚本,并查看执行结果
kubectl apply -f cluster-role.yml
deploy@master-node:~/k8s-volumes-main/yml/StorageClass$ kubectl get clusterroles|grep nfs-client
nfs-client-provisioner-runner 2021-06-10T03:45:34Z
创建Cluster Role Binding
脚本如下:
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: pv-demo
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
执行脚本,并查看结果:
kubectl apply -f cluster-role-binding.yml
deploy@master-node:~/k8s-volumes-main/yml/StorageClass$ kubectl get clusterrolebindings|grep nfs-client
run-nfs-client-provisioner ClusterRole/nfs-client-provisioner-runner 13d
创建role
脚本如下:
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: pv-demo
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
执行脚本,并查看结果:
kubectl apply -f role.yml
deploy@master-node:~/k8s-volumes-main/yml/StorageClass$ kubectl get roles -n pv-demo
NAME CREATED AT
leader-locking-nfs-client-provisioner 2021-06-10T03:47:14Z
创建role binding
脚本如下:
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: pv-demo
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: pv-demo
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
执行脚本,并查看结果:
kubectl apply -f role-binding.yml
deploy@master-node:~/k8s-volumes-main/yml/StorageClass$ kubectl get rolebindings -n pv-demo
NAME ROLE AGE
leader-locking-nfs-client-provisioner Role/leader-locking-nfs-client-provisioner 13d
创建StorageClass
完成安全相关的脚本创建后,再来创建StorageClass。如果我们使用的是privisioner插件,我们可以将它添加到配置中的配置参数中,比如 kubernetes.io/aws-ebs 或 kubernetes.io/azure-file 但我们没有 NFS 插件。 因此,请改用 mysite.com/nfs 之类的任何内容。
下面是storage-class的配置文件
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: ssd-nfs-storage
provisioner: ff1.dev/nfs
parameters:
archiveOnDelete: "false"
执行脚本,并查看结果:
kubectl apply -f storage-class.yml
deploy@master-node:~/k8s-volumes-main/yml/StorageClass$ kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
ssd-nfs-storage ff1.dev/nfs Delete Immediate false 13d
创建provisioner deployment
我们需要部署provisioner,worker节点将与 NFS 服务器通信,并在NFS server上创建文件夹,然后将新 PV 配置到集群中。
作者的实例中是的arm镜像,我们需要换成x86镜像。
如果需要更加详细的了解provisioner镜像,参考开源网址:
[https://github.com/kubernetes-retired/external-storage/tree/master/nfs-client](https://github.com/kubernetes-retired/external-storage/tree/master/nfs-client)
provisioner deployment脚本如下:
kind: Deployment
apiVersion: apps/v1
metadata:
name: nfs-client-provisioner
namespace: pv-demo
spec:
selector:
matchLabels:
app: nfs-client-provisioner
replicas: 1
strategy:
type: Recreate
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
resources:
limits:
cpu: 1
memory: 1Gi
volumeMounts:
- name: nfs-client-ssd
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: ff1.dev/nfs
- name: NFS_SERVER
value: 159.99.100.88
- name: NFS_PATH
value: /home/deploy/ssd/dynamic
volumes:
- name: nfs-client-ssd
nfs:
server: 159.99.100.88
path: /home/deploy/ssd/dynamic
执行创建脚本
kubectl apply -f provisioner-deploy.yml
创建pvc
PVC脚本如下:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ssd-nfs-pvc-1
namespace: pv-demo
spec:
storageClassName: ssd-nfs-storage
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Mi
执行脚本并查看结果
kubectl apply -f volume-claim.yml
kubectl get pvc,pv -n pv-demo
发现pvc的状态一直是pending,说明没有创建pv,也未能与pv进行绑定。
排查原因
- 通过kubectl describe命令查看错误提示信息,信息中有:waiting for a volume to be created, either by external provisioner “ff1.dev/nfs” or manually created by system administrator。
- 通过kubectl logs命令查看pod(nfs-client-provisioner)日志,日志中有:unexpected error getting claim reference: selfLink was empty, can’t make reference。
- 使用第二步骤的信息去网上查找,原来是1.20版本(我的是1.20.2)默认禁止使用selfLink。
解决办法
如果是通过yaml文件部署kube-apiserver的,在kube-apiserver.yaml中添加- --feature-gates=RemoveSelfLink=false参数。通过命令可以查找kube-apiserver.yaml文件位置
find / -name kube-apiserver.yaml
添加- --feature-gates=RemoveSelfLink=false行
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
systemctl daemon-reload 加载一下,如果不生效,需要重启机器。
如果一切正常,发现pvc已经与pv进行了绑定,并在nfs server目录下自动创建了一个文件夹,比如我机器上多了一个如下文件夹
deploy@master-node:~/k8s-volumes-main/yml/StorageClass$ ls /home/deploy/ssd/dynamic/
pv-demo-ssd-nfs-pvc-1-pvc-5c2cbadd-fb7d-4faa-9269-2d4e1dff92bd
部署网站
下面创建deployment和service,同之前的两篇文章中的方法基本类似,就不一步步执行了,脚本合在一个文件中。
kind: Deployment
apiVersion: apps/v1
metadata:
labels:
app: ssd-storage-class-site
namespace: pv-demo
name: ssd-storage-class-nginx
spec:
replicas: 1
selector:
matchLabels:
app: ssd-storage-class-site
template:
metadata:
labels:
app: ssd-storage-class-site
spec:
volumes:
- name: ssd-storage-class-volume
persistentVolumeClaim:
claimName: ssd-nfs-pvc-1
containers:
- image: nginx
name: ssd-storage-class-site
resources:
limits:
cpu: '1'
memory: 100Mi
volumeMounts:
- name: ssd-storage-class-volume
mountPath: /usr/share/nginx/html
---
kind: Service
apiVersion: v1
metadata:
name: ssd-storage-class-service
namespace: pv-demo
spec:
selector:
app: ssd-storage-class-site
ports:
- protocol: TCP
port: 80
---
kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
namespace: pv-demo
name: ssd-storage-class-ingress
annotations:
kubernetes.io/ingress.class: traefik
spec:
rules:
- host: ssd-storage-class.192.168.2.101.nip.io
http:
paths:
- path: /
pathType: ImplementationSpecific
backend:
service:
name: ssd-storage-class-service
port:
number: 80
拷贝网站程序到NFS server
使用scp命令远程拷贝index.html到NFS server的ssd/dynamic/pv-demo-ssd-nfs-pvc-1-pvc-5c2cbadd-fb7d-4faa-9269-2d4e1dff92bd文件夹
sudo scp index.html 192.168.2.30:ssd/dynamic/pv-demo-ssd-nfs-pvc-1-pvc-5c2cbadd-fb7d-4faa-9269-2d4e1dff92bd
访问服务
使用clusterip的方式访问此服务,我们的url如下:
curl http://10.196.26.24
可以查看该网站内容.也可以进入到容器内使用dns来查看网站内容.
kubectl exec -it -n pv-demo ssd-storage-class-nginx-6994764775-f4cbw -- curl http://ssd-storage-class-service.pv-demo.svc.cluster.local
写在最后
经过四篇文章,一步步的演绎了如何在K8S中部署NFS集群,虽然k8s中也有很多开源的比如rook-ceph,用的比较多,但NFS也有不少人在项目中使用,因此经过这一系列文章,相信大家完全可以在K8S中部署一个NFS集群。