K8S集群中部署NFS之四:使用StorageClass创建PV

在K8S中对于无状态的应用我们可以比较容易的扩展应用程序、滚动升级和重新启动崩溃的应用程序,但是对于一些无状态的应用,分为需要维护服务的拓扑结构和需要持久化数据两种情况,对于前者,我们可以使用headless service来实现,对于后者我们需要在集群中使用持久存储来持久化数据。而持久化存储,NFS(Network File System)是一个使用比较常用的分布式存储技术,在本系列中,我将详细演进如何在K8S集群中部署NFS。为了使得大家比较容易理解,将文章分成4个部分。
第一部分:配置 NFS 服务器
第二部分:将 NFS 服务器文件夹挂载到每个节点的本地文件夹
第三部分:将Persistent Volume配置直接连接到 NFS 服务器
第四部分:创建Storage Class声明来自动创建Persistent Volume(本文)

写在前面

这一系列文章主要译自下面的文章,但由于k8s版本的原因导致一些功能不正常,我在部署过程中也遇到了一些问题,并得以解决。此外作者主要是在树莓派上完成部署,是ARM架构,因此一些脚本不能在在PC上正常工作,我也在文中加以修改,并给出了详细的解决办法。

https://levelup.gitconnected.com/how-to-use-nfs-in-kubernetes-cluster-configuring-the-nfs-server-1bf4116641d4

本文的源代码都是来自作者的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集群。

你可能感兴趣的:(K8S集群中部署NFS之四:使用StorageClass创建PV)