Kubernetes的配置与存储

一、K8s的存储的基础知识与分类

参考官方文档:https://kubernetes.io/zh/docs/concepts/storage/

1、基础知识:

K8s的基本单位为Pod,但是一个Pod中实际工作的是容器containers,所以要挂载的卷其实也是给容器使用的;K8s卷的设计:

  • 容器Container:申请容器内的哪个目录/文件将被挂载出来;(但是如何挂载,他不做细节干涉)

  • Pod:为其中的每个容器声明出来的挂载,指定详细的挂载详情;(有很多种挂载模式,可以是本地的、可以是远程的、甚至还可以是云服务上提供的对象存储服务)

2、数据卷分类:

数据卷实现分类有很多,可通过explain查看所有暂时支持的实现分类(多达30种)

kubectl explain pod.spec.volumes

但是我们大概可以粗分为以下三大类:

image.png

  • 配置信息:类似于独立环境变量文件的方式,将文件种的键值对挂载到容器内;

  • 临时存储:这些存储虽然挂出来了,但是如果容器被删除,或者在其他机器上被拉起,这些挂载文件将无法继续被读取到;

  • 持久化存储:真正的持久化,K8s将自己不擅长的存储实现,交给了别的擅长于存储的服务商或软件;——这也是我们以后学习的重点难点!


二、NFS网络文件系统的安装与调试

NFS(Network File System)即网络文件系统,它允许网络中的计算机之间通过网络共享资源。将NFS主机分享的目录,挂载到本地客户端当中,本地NFS的客户端应用可以透明地读写位于远端NFS服务器上的文件,在客户端端看起来,就像访问本地文件一样。

image.png

NFS设计成C/S架构,通过RPC远程过程调用的方式实现数据同步!

1、在Master节点安装NFS Server(当然,其他节点也可以)

yum install -y nfs-utils

#执行命令 vi /etc/exports,创建 exports 文件,文件内容如下:
echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports

# 执行以下命令,启动 nfs 服务;创建共享目录
mkdir -p /nfs/data
systemctl enable rpcbind
systemctl enable nfs-server
systemctl start rpcbind
systemctl start nfs-server
exportfs -r

#检查配置是否生效
[root@k8s-01 /]# exportfs
/nfs/data     	<world>
## 说明已经nfs-server服务已经启动,生效

2、在我们两台需要共享nfs server的机器上,安装NFS Client

yum install -y nfs-utils

#执行以下命令检查 nfs 服务器端是否有设置共享目录
# showmount -e $(nfs服务器的IP)
showmount -e 192.168.56.20
# 输出结果如下所示
Export list for 192.168.56.20
/nfs/data *

#执行以下命令挂载 nfs 服务器上的共享目录到本机路径 /root/nfsmount
mkdir /root/nfsmount

# mount -t nfs $(nfs服务器的IP):/root/nfs_root /root/nfsmount
mount -t nfs 192.168.56.20:/nfs/data /root/nfsmount

3、测试NFS服务是否在3台服务器上已经生效

#1、 client节点执行命令,看本地的文件系统列表:
[root@k8s-02 ~]# df -Th
Filesystem              Type      Size  Used Avail Use% Mounted on
devtmpfs                devtmpfs  1.9G     0  1.9G   0% /dev
tmpfs                   tmpfs     1.9G     0  1.9G   0% /dev/shm
tmpfs                   tmpfs     1.9G  9.4M  1.9G   1% /run
tmpfs                   tmpfs     1.9G     0  1.9G   0% /sys/fs/cgroup
/dev/sda1               xfs        40G   11G   30G  27% /
tmpfs                   tmpfs     379M     0  379M   0% /run/user/0
192.168.56.20:/nfs/data nfs4       40G  6.3G   34G  16% /root/nfsmount
## 可以看到,有一个是远程的网络文件系统,挂载了我们自己本地的/root/nfsmount 目录上

#2、在server上目录种创建文件aaa.txt
#3、在clinet上目录中创建文件bbb.txt

image.png

可以看到,3台主机上对应的文件已经立即得到了同步!

4、补充,有时候,我们即使安装了nfs-utils,依然无法使用showmount -e ip

我们可以直接在客户端配置fstab挂载:

vi /etc/fstab
##追加nfs master的信息
10.130.59.50:/nfs/data  /nfs/data                  nfs     defaults     0 0

执行以下命令,让修改后的fstab生效:

mount -a

之后查看:

df -Th
##存在以下行:
10.130.59.50:/nfs/data nfs4       50G  4.4G   46G   9% /nfs/data

之后创建文件,测试正常!


三、使用一个Nginx测试Pod使用NFS进行挂载

1、创建nginxnfs.yaml文件:

#测试Pod直接挂载NFS了
apiVersion: v1
kind: Pod
metadata:
  name: vol-nfs
  namespace: mytest
spec:
  containers:
  - name: mynginx
    image: nginx
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html/
  volumes:
  - name: html
    nfs:
      path: /nfs/data
      server: 192.168.56.20

2、执行此yaml文件,部署一台nginx:

[root@k8s-01 mytest]# kubectl apply -f nginxnfs.yaml 
pod/vol-nfs created

[root@k8s-01 mytest]# kubectl get pod -n mytest -owide
NAME      READY   STATUS    RESTARTS   AGE   IP               NODE     NOMINATED NODE   READINESS GATES
vol-nfs   1/1     Running   0          68s   10.244.165.226   k8s-03   <none>           <none>

3、nfs server的对应目录下,创建一个index.html文件,然后访问:

[root@k8s-01 mytest]# echo 12345 > /nfs/data/index.html
[root@k8s-01 mytest]# curl 10.244.165.226
12345

可以看到,明明部署在k8s-03节点上面的pod,依然可以正常使用k8s-01节点上的文件系统进行文件挂载!


四、PV、PVC、StorageClass的基础知识

背景:为什么要把一个存储事情分成这么多层?如此复杂?

答:**存储的管理**是一个与**计算实例的管理**完全不同的问题。

因为整个Pod部署的yaml文件可能都是由我们开发人员编写的,但是我们以后部署的服务器什么情况我们肯定是不知道的,服务器上能支持的文件存储方式我们也是不知道的,那我们的Pod肯定没有办法一气呵成地完成所有的事情!

image.png

有了PV(Persistent Volume)、PVC(Persistent Volume Claim)后,我们就可以让运维人员提前准备好很多的PV,这样我们在部署Pod的yaml文件种,只需要添加一个PVC(PV的使用申请),这样K8s就能够自动地为Pod匹配上可以使用的PV存储卷了;

  • 程序员:负责Pod + PVC的yaml文件即可;

  • K8s运维人员:负责准备好PV;—— 这里还分为静态供应、自动供应 两类;

1、什么是PV(Persistent Volume 持久化存储卷)?

PV 卷的供应有两种方式:静态供应或动态供应。

  • 静态供应:所有的事情,尤其是PV的创建,我是由管理员手动创建的,如果没有现成的可用的PV存在,我们的PVC申请将一直处于Pending状态;

  • 动态供应:PV的创建是自动化完成的,主要依赖于底层的StorageClass来实现;—— 后面详述

手动创建PV(mypv.yaml)—— 我一次创建了3个PV:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: mypv-10m
  labels:
    type: local
spec:
  storageClassName: my-nfs-storage
  capacity:
    storage: 10m
  accessModes:
    - ReadWriteOnce
  nfs: #使用nfs存储系统,挂载的目录为192.168.56.20:/nfs/data/one
    server: 192.168.56.20
    path: /nfs/data/one
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mypv-20m
  labels:
    type: local
spec:
  storageClassName: my-nfs-storage
  capacity:
    storage: 20m
  accessModes:
    - ReadWriteOnce
  nfs: #使用nfs存储系统,挂载的目录为192.168.56.20:/nfs/data/one
    server: 192.168.56.20
    path: /nfs/data/two
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mypv-50m
  labels:
    type: local
spec:
  storageClassName: my-nfs-storage
  capacity:
    storage: 50m
  accessModes:
    - ReadWriteOnce
  nfs: #使用nfs存储系统,挂载的目录为192.168.56.20:/nfs/data/one
    server: 192.168.56.20
    path: /nfs/data/three

应用此yaml文件后,将产生3个pv存储卷:

[root@k8s-01 mytest]# kubectl apply -f mypv.yaml 
persistentvolume/mypv-10m created
persistentvolume/mypv-20m created
persistentvolume/mypv-50m created
[root@k8s-01 mytest]# kubectl get pv -n mytest 
NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS     REASON   AGE
mypv-10m   10m        RWO            Retain           Available           my-nfs-storage            11s
mypv-20m   20m        RWO            Retain           Available           my-nfs-storage            11s
mypv-50m   50m        RWO            Retain           Available           my-nfs-storage            11s

注:其中重要的字段:

  • ACCESS MODES:访问模式 

    • RWO – ReadWriteOnce:允许单节点读写

    • ROX – ReadOnlyMany:允许多节点只读(读写分离时可用)

    • RWX – ReadWriteMany:允许多节点读写(集群常用)

    • RWOP – ReadWriteOncePod:只允许单Pod读写(比RWO更精细)

  • RECLAIM POLICY:回收策略

    • Retain — 手动回收,PVC被删除,PV纹丝不动,自己手动控制,最安全;

    • Recycle — 基本擦除 (rm -rf /thevolume/*),卷里面的内容将会被清除,卷变为Released状态,依然无法被其他其他PVC申领;

    • Delete — 诸如 AWS EBS、GCE PD、Azure Disk 或 OpenStack Cinder 卷这类关联存储资产也被删除;

      目前,仅 NFS 和 HostPath 支持回收(Recycle)。 AWS EBS、GCE PD、Azure Disk 和 Cinder 卷都支持删除(Delete)。: pv自己也跟着删除

  • STATUS:当前状态 

    • Available(可用)– 卷是一个空闲资源,尚未绑定到任何申领;

    • Bound(已绑定)– 该卷已经绑定到某申领;

    • Released(已释放)– 所绑定的PVC已被删除,但是资源尚未被集群回收;

    • Failed(失败)– 卷的自动回收操作失败。

  • CLAIM:当前正被哪个PVC绑定 —— 当某个PV被PVC申领之后,该列就会有数值了!

    • STORAGECLASS:存储类——动态供应的重点,每个StorageClass都对应着自己的一套自己的存储实现;

    image.png

2、什么是PVC(Persistent Volume Claim 持久化存储卷申明/申请)?

PVC其实就是我们程序员编写的,对于PV资源的一个申请,当有合适的PV的时候,我们即可以进行绑定,如果没有合适的PV,我们申请的PVC将一直处于Pending状态,直到有合适的PV被创建出来!

手动创建一个PVC(mypvc.yaml)

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc
  namespace: mytest
  labels:
    app: nginx-pvc
spec:
  storageClassName: my-nfs-storage #必须与PV的该项对应,K8s将会从该SC对应的pv资源种为我们匹配合适的资源
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 15m #我故意写的15m,看看K8s是否会帮我们挑选一个最合适的PV资源

我们应用此yaml文件后,查看pvc和pv的状态:

[root@k8s-01 mytest]# kubectl apply -f mypvc.yaml 
persistentvolumeclaim/mypvc created
[root@k8s-01 mytest]# kubectl get pvc -n mytest 
NAME    STATUS   VOLUME     CAPACITY   ACCESS MODES   STORAGECLASS     AGE
mypvc   Bound    mypv-20m   20m        RWO            my-nfs-storage   14s
[root@k8s-01 mytest]# kubectl get pv -n mytest 
NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM          STORAGECLASS     REASON   AGE
mypv-10m   10m        RWO            Retain           Available                  my-nfs-storage            2m36s
mypv-20m   20m        RWO            Retain           Bound       mytest/mypvc   my-nfs-storage            2m36s
mypv-50m   50m        RWO            Retain           Available                  my-nfs-storage            2m36s

可以看到,pv和pvc的状态都变为了Bound状态;同时,K8s为我们挑选的时最合适的PV资源;

3、我们再编写一个Nginx的Pod,来使用上面创建的pvc申请(pvcnginx.yaml)

apiVersion: v1
kind: Pod
metadata:
  name: "mypvc-nginx"
  namespace: mytest
  labels:
    app: "mypvc-nginx"
spec:
  containers:
  - name: mypvc-nginx
    image: "nginx"
    ports:
    - containerPort: 80
      name:  http
    volumeMounts:
    - name: localtime
      mountPath: /etc/localtime
    - name: html
      mountPath: /usr/share/nginx/html
  volumes:
    - name: localtime
      hostPath:
        path: /usr/share/zoneinfo/Asia/Shanghai
    - name: html
      persistentVolumeClaim:
        claimName: mypvc  #我自己创建的pvc的名字
  restartPolicy: Always

我们应用此yaml后,对应的nginx即可被创建,然后我们在对应的two文件夹种创建index.html文件即可看到效果:

[root@k8s-01 mytest]# kubectl get all -n mytest
NAME              READY   STATUS    RESTARTS   AGE
pod/mypvc-nginx   1/1     Running   0          3m55s
pod/vol-nfs       1/1     Running   0          5h25m
[root@k8s-01 mytest]# echo mypvc-nginx > /nfs/data/two/index.html
[root@k8s-01 mytest]# curl 10.244.165.227
mypvc-nginx

这样,整个”静态供应“的流程就完成了!

4、什么是StorageClass(存储类)?

整个静态供应的过程中,我们好像都没有 StorageClass 什么事情,除了就用它来隔绝PV资源一样,没有了其他作用!

每个 StorageClass 都包含 provisioner、parameters 和 reclaimPolicy 字段, 这些字段会在 StorageClass 需要动态分配 PersistentVolume 时会使用到。

provisioner:我们一般称之为 供应商;

  • 当我们创建StorageClass的时候,会指定使用哪个provisioner来为我们供应服务(自动创建PV)

  • 而在我们的PVC中,我们会选择使用哪一个StorageClass来为我们服务;

    这样,整个”动态供应“的模型就出来了!我们程序员只需要编写 Pod、PVC的yaml文件,在PVC中指定StorageClassName即可,至于StorageClass对应的是自动供应PV,还是手动创建PV,都跟我们没有关系了!

image.png


五、借助NFS存储服务实现一个动态供应案例

再来回顾一下”动态供应“的流程:(其中重点就是:当没有合适的PV时候,K8s会获取StorageClass信息,帮我们创建合适的PV资源)

image.png

1、参考 nfs-subdir-external-provisioner 动态供应的文档创建yaml

官方文档地址:https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner

image.png

我们把这三个文件写在同一个 pvc-provisioner.yaml 文件中,便于移植:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: managed-nfs-storage
  annotations: 
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner 
parameters:
  archiveOnDelete: "true"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nfs-client-provisioner
  labels:
    app: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: mynfs
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: registry.cn-hangzhou.aliyuncs.com/jgqk8s/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: 10.206.114.4
            - name: NFS_PATH  
              value: /nfs/data
      volumes:
        - name: nfs-client-root
          nfs:
            server: 10.206.114.4
            path: /nfs/data
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-client-provisioner
  # replace with namespace where provisioner is deployed
  namespace: mynfs
---
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: mynfs
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: mynfs
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: mynfs
subjects:
  - kind: ServiceAccount
    name: nfs-client-provisioner
    # replace with namespace where provisioner is deployed
    namespace: mynfs
roleRef:
  kind: Role
  name: leader-locking-nfs-client-provisioner
  apiGroup: rbac.authorization.k8s.io

2、执行上面的yaml文件:

[root@k8s-01 mytest]# kubectl apply -f pvc-provisioner.yaml 
storageclass.storage.k8s.io/managed-nfs-storage created
deployment.apps/nfs-client-provisioner created
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
[root@k8s-01 mytest]# kubectl get storageclass
NAME                            PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
managed-nfs-storage (default)   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           false                  94s

其中的default,就代表该StorageClass为默认SC;

如果刚开始的Yaml文件中配置配置为 default 默认SC,我们可以通过以下两种方式修改;

  • 通过 kubectl edit 命令修改:

kubectl edit sc managed-nfs-storage
  • 通过 kubectl patch 命令修改:

kubectl patch storageclass managed-nfs-storage -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

3、创建一个Pod + PVC,使用上面的 SC 完成动态供应

apiVersion: v1
kind: Pod
metadata:
  name: "auto-pv-nginx"
  namespace: mytest
  labels:
    app: "auto-pv-nginx"
spec:
  containers:
  - name: auto-pv-nginx
    image: "nginx"
    ports:
    - containerPort: 80
      name:  http
    volumeMounts:
    - name: localtime
      mountPath: /etc/localtime
    - name: html
      mountPath: /usr/share/nginx/html
  volumes:
    - name: localtime
      hostPath:
        path: /usr/share/zoneinfo/Asia/Shanghai
    - name: html
      persistentVolumeClaim:
        claimName: auto-pvc  #下方对应的pvc的名字
  restartPolicy: Always
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: auto-pvc
  namespace: mytest
  labels:
    app: auto-pvc
spec:
  storageClassName: managed-nfs-storage #如果我们已经配置了默认的StorageClass,该行可以缺省
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 30m #待会看看是不是会自动创建一个30m的PV资源

应用该yaml后,看看是否自动创建了一个30m的PV资源:

[root@k8s-01 mytest]# kubectl apply -f auto-pv-nginx.yaml 
pod/auto-pv-nginx created
persistentvolumeclaim/auto-pvc created
[root@k8s-01 mytest]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM             STORAGECLASS          REASON   AGE
mypv-10m                                   10m        RWO            Retain           Available                     my-nfs-storage                 78m
mypv-20m                                   20m        RWO            Retain           Bound       mytest/mypvc      my-nfs-storage                 78m
mypv-50m                                   50m        RWO            Retain           Available                     my-nfs-storage                 78m
pvc-b7ecbdff-89f4-4135-91b9-cf8d227ce86f   30m        RWO            Delete           Bound       mytest/auto-pvc   managed-nfs-storage            26s

4、我们将刚刚部署的测试用的pod和pvc删除,检验该SC的回收策略?

[root@k8s-01 mytest]# ls /nfs/data/
aaa.txt  bbb.txt  index.html  mytest-auto-pvc-pvc-b7ecbdff-89f4-4135-91b9-cf8d227ce86f  one  three  two
[root@k8s-01 mytest]# kubectl delete -f auto-pv-nginx.yaml 
pod "auto-pv-nginx" deleted
persistentvolumeclaim "auto-pvc" deleted
[root@k8s-01 mytest]# ls /nfs/data/
aaa.txt  archived-mytest-auto-pvc-pvc-b7ecbdff-89f4-4135-91b9-cf8d227ce86f  bbb.txt  index.html  one  three  two

上面的现象足以证明:

  • 我们此StorageClass创建出来的PV的回收策略为DELETE(PVC删除时候,PV也被删除)

  • 但是虽然PV被删除了,但是删除前,该SC帮我们做了数据备份;(原文件夹前增加了archived-前缀)—— archiveOnDelete 字段为true

jiguiquan@163.com

文章作者信息...

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐