七、Kubernetes 持久化存储

1、Storage

1.1 Volume

容器中的文件在磁盘上是临时存放的,这给容器中运行的特殊应用程序带来一些问题。

  • 首先,当容器崩溃时,kubelet 将重新启动容器,容器中的文件将会丢失——因为容器会以干净的状态重建。

  • 其次,当在一个 Pod 中同时运行多个容器时,常常需要在这些容器之间共享文件。

  • Kubernetes 抽象出 Volume 对象来解决这两个问题。

使用Volume可以防止数据丢失,容器崩溃或新开容器都可以使用之前的Volume来达到恢复数据的作用

1.2 Host类型Volume实战

在前面的章节中提到过,Pod中的容器是会共享网络与存储的,共享网络我们已经提过了,这里来验证Pod共享存储的问题。

定义一个Pod,其中包含两个Container,都使用Pod的Volume

  • 准备YAML文件

    volume-pod.yaml

    apiVersion: v1
    kind: Pod
    metadata:
      name: volume-pod
    spec:
      containers:
      - name: nginx-container     # container 1
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - name: volume-pod            # 指定使用的Volume
          mountPath: /nginx-volume
      - name: busybox-container       # container 2
        image: busybox
        command: ['sh', '-c', 'echo The app is running! && sleep 3600']
        volumeMounts:
        - name: volume-pod            # 指定使用的Volume
          mountPath: /busybox-volume
      volumes:
      - name: volume-pod
        hostPath:                 # 使用本地路径作为存储空间
          path: /tmp/volume-pod 
    
  • 创建资源

    [root@master-kubeadm-k8s volume]# kubectl apply -f volume-pod.yaml
    pod/volume-pod created
    
  • 查看资源

    [root@master-kubeadm-k8s volume]# kubectl get pods
    NAME         READY   STATUS    RESTARTS   AGE
    volume-pod   2/2     Running   0          2m5s
    
  • 测试Volume

    # 来到Pod所在的worker节点
    # 查看容器
    [root@worker02-kubeadm-k8s ~]# docker ps | grep volume
    61f2146c3e6e        busybox                "sh -c 'echo The app…"   32 seconds ago       Up 32 seconds                           k8s_busybox-container_volume-pod_default_8f9de4e8-8aed-11ea-97c6-5254008afee6_0
    ae4aad0d60fb        nginx                  "nginx -g 'daemon of…"   About a minute ago   Up About a minute                       k8s_nginx-container_volume-pod_default_8f9de4e8-8aed-11ea-97c6-5254008afee6_0
    ff467713a95f        k8s.gcr.io/pause:3.1   "/pause"                 2 minutes ago        Up 2 minutes                            k8s_POD_volume-pod_default_8f9de4e8-8aed-11ea-97c6-5254008afee6_0
    
    # 进入nginx容器
    [root@worker02-kubeadm-k8s ~]# docker exec -it ae4aad0d60fb sh
    
    # 查看目录,现在是空的,所以看不到东西
    # ls /nginx-volume
    
    # 创建一个文件,再到宿主机 /tmp/volume-pod 目录查看
    # echo '1111' -> test.html
    # ls
    test.html
    
    # 发现也有了刚才创建的文件
    [root@worker02-kubeadm-k8s ~]# ls /tmp/volume-pod
    test.html
    
    # 再去另一个 busy-box 容器中看是否有这个文件
    [root@worker02-kubeadm-k8s volume-pod]# docker exec -it 61f2146c3e6e sh
    
    # 发现也有这个文件
    / # ls /busybox-volume
    test.html
    

    通过验证,Pod中的容器是共享同一个Volume的

1.3 Persistent Volume

由于使用本地的Volume会有一些限制,比如Pod必须要与Volume在同一台宿主机,宿主机挂了数据就会丢失。所以K8S还提供了真正的持久化存储的技术,将数据挂载到其他专门用于存储数据的服务器上,这样即使宿主机挂了,数据还在。

PersistentVolume是为用户和管理员提供了一个API抽象的细节如何提供如何使用存储。 K8S引入了两个新的API资源:PersistentVolume(PV)、PersistentVolumeClaim(PVC)。

一般PV是由专业的运维人员去配置,PVC是有开发配置

  • PersistentVolume:PV在K8S中也是一种资源,但生命周期独立于Pod。封装了底层存储卷实现的细节。它是用来定义或配置存储的资源,市面上有很多可以作为文件服务器的工具,比如NFS、RBD、Cinder等,PV就是帮我们打通了与这些服务器的对接。

  • PersistentVolumeClaim:PVC是给用户申请存储空间的。 当开发者需要通过PVC去申请存储空间,可以请求特定的空间大小和访问模式,比如他们可以申请是一次读/写或多次只读等。

  • 简单来说,PV定义了一块存储空间,Pod想要申请存储空间,就要通过PVC去申请,PVC通过访问模式与申请的空间大小去匹配PV。

1.3.1 流程图

下面这张图完整说明了Pod、PVC、PV与文件服务器的关系。

image.png

一个Pod想要一块存储空间,需要绑定一个PVC,通过访问模式与申请的存储空间大小去匹配并绑定PV,而PV才是真正与文件服务打交道的资源。

1.3.2 案例实操

1.3.2.1 准备文件服务器

  • NFS

    nfs(network file system)网络文件系统,是FreeBSD支持的文件系统中的一种,允许网络中的计算机之间通过TCP/IP网络共享资源

  • 选择一台机器搭建NFS

    这里选择Master节点

    # 1、 选择master节点作为nfs的server,所以在master节点上
    # 安装nfs
    yum install -y nfs-utils
    # 创建nfs目录
    mkdir -p /nfs/data/
    # 为应用服务创建资源目录
    mkdir -p /nfs/data/nginx
    # 授予权限
    chmod -R 777 /nfs/data
    # 编辑export文件
    vi /etc/exports
    /nfs/data *(rw,no_root_squash,sync)
    # 使得配置生效
    exportfs -r
    # 查看生效
    exportfs
    # 启动rpcbind、nfs服务
    systemctl restart rpcbind && systemctl enable rpcbind
    systemctl restart nfs && systemctl enable nfs
    # 查看rpc服务的注册情况
    rpcinfo -p localhost
    # showmount测试 【master节点的ip】
    showmount -e 192.168.50.111
      
    # 2、 所有node上安装客户端
    yum install -y nfs-utils
    systemctl start nfs && systemctl enable nfs
    

1.3.2.2 编写PV

  • 编写YAML文件

    pv.yaml

    apiVersion: v1
    kind: PersistentVolume
    metadata:
      name: nginx-pv
    spec:
      capacity:
        storage: 2Gi                          # 存储空间大小
      volumeMode: Filesystem                  # 存储模式
      accessModes:                                # 访问模式
        - ReadWriteMany                       # 表示允许多个Pod多次读写
        # ReadWriteOnece 表示只允许一个Pod进行独占式读写操作
      nfs:
        path: /nfs/data/nginx                 # 远端服务器的目录
        server: 192.168.50.111                    # 远端的服务器ip
    
  • 创建PV

    [root@master-kubeadm-k8s pv]# kubectl apply -f pv.yaml
    persistentvolume/my-pv created
    
  • 查看PV

    [root@master-kubeadm-k8s pv]# kubectl get pv
    NAME       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
    nginx-pv   2Gi        RWX            Retain           Available                                   4s
    

1.3.2.3 编写PVC

  • 编写PVC

    pvc.yaml

    根据 accessModes、storage去匹配合适的PV

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: nginx-pvc
    spec:
      accessModes:
        - ReadWriteMany                       # 访问模式
      resources:
        requests:
          storage: 2Gi                        # 申请的存储空间大小
    
  • 创建PVC

    persistentvolumeclaim/nginx-pvc created
    [root@master-kubeadm-k8s pv]# kubectl get pvc
    
  • 查看PVC

    可以看到它的状态是 Bound,Volume是 nginx-pv,表示已经绑定到PV

    NAME        STATUS   VOLUME     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
    nginx-pvc   Bound    nginx-pv   2Gi        RWX                           5s
    

1.3.2.4 编写Pod绑定PVC

  • 编写YAML文件

    nginx-pod.yaml

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: nginx
    spec:
      selector:
        matchLabels: 
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - image: nginx
            name: nginx
            ports:
            - containerPort: 80
            volumeMounts:
            - name: nginx-persistent-storage
              mountPath: /usr/share/nginx/html
          volumes:
          - name: nginx-persistent-storage
            persistentVolumeClaim:                # 使用PVC
              claimName: nginx-pvc                # PVC名称
    
  • 创建Pod

    [root@master-kubeadm-k8s pv]# kubectl apply -f nginx-pod.yaml
    deployment.apps/nginx created
    
  • 查看Pod

    [root@master-kubeadm-k8s pv]# kubectl get pods
    NAME                     READY   STATUS    RESTARTS   AGE
    nginx-77945f44db-sllmh   1/1     Running   0          51s
    

1.3.2.5 测试持久化存储

  • 在映射目录创建文件

    # 进入NFS机器的映射目录
    [root@master-kubeadm-k8s pv]# cd /nfs/data/nginx/
    # 编写内容到 pv.html 文件
    [root@master-kubeadm-k8s nginx]# echo '

    test pv

    ' -> pv.html
  • 访问Nginx

    # 查看nginx的ip
    [root@master-kubeadm-k8s nginx]# kubectl get pods -o wide
    NAME                     READY   STATUS    RESTARTS   AGE     IP                NODE                   NOMINATED NODE   READINESS GATES
    nginx-77945f44db-sllmh   1/1     Running   0          4m15s   192.168.221.122   worker02-kubeadm-k8s              
    
    # 测试访问,可以发现已经拿到我们在映射目录创建的文件了
    [root@master-kubeadm-k8s nginx]# curl 192.168.221.122/pv.html
    

    test pv

    -

PV的存在虽然解决了数据存储安全的问题,但仅仅上面的案例,还是不够好,如果Pod有很多,难道要让运维人员一个一个去创建吗,虽然也是一种方案,但并不完美。

下面要聊的StorageClass,就是可以动态的管理PV,操作更灵活!

1.4 StorageClass

  • StorageClass 提供了描述存储 “类” 的方法。 不同的类型可能会映射到不同的服务质量等级或备份策略,或是由集群管理员制定的任意策略。

  • Kubernetes 本身并不清楚各种类代表的什么。这个类的概念在其他存储系统中有时被称为 “配置文件”。

  • 每个 StorageClass 都包含 provisionerparametersreclaimPolicy 字段, 这些字段会在 StorageClass 需要动态分配 PersistentVolume 时会使用到。

  • StorageClass 对象的命名很重要,用户使用这个命名来请求生成一个特定的类。 当创建 StorageClass 对象时,管理员设置 StorageClass 对象的命名和其他参数,一旦创建了对象就不能再对其更新。

  • 管理员可以为没有申请绑定到特定 StorageClass 的 PVC 指定一个默认的存储类(PV)

StorageClass其实可以理解为是一个 PV 的模板,PVC想要绑定PV,可以通过StorageClass模板去动态的创建PV。

其中有两个重要部分:PV属性和创建此PV所需要的插件。

这样PVC就可以按“Class”来匹配PV。

可以为PV指定storageClassName属性,标识PV归属于哪一个Class。

1.4.1 存储分配器

因为文件系统有很多,StorageClass不可能默认就实现了对所有文件系统的交互,存储分配器就是StorageClass与外部的文件系统打交道的,可以理解为是一个 插件

  • 每个 StorageClass 都有一个分配器,用来决定使用哪个卷插件分配 PV。该字段必须指定。

  • K8S本身已经实现了一部分与外部文件系统打交道的插件,但还有一些还未支持

    • 下方打勾的表示K8S已经支持,未打钩的表示不支持
    image.png
  • 我们之前使用的是NFS,但 K8S 并没有实现 NFS 的内部分配器,那我们可以使用 NFS外部分配器,也就是非官方的实现。

    image.png

1.4.2 StorageClass案例

在创建StorageClass资源之前,要说明一下,不是所有人都可以创建StorageClass资源的,在K8S中一些特殊的资源必须要有apiServer的认证才能创建,这里想要创建StorageClass,就需要RBAC的认证,简单来说就是需要创建一个账户去与apiServer进行交互。

  • 准备NFS服务器

    • Master节点已经是NFS了,所以不需要准备了
  • 准备认证资源

    创建权限账户资源

    • rbac.yaml文件

      kind: ClusterRole
      apiVersion: rbac.authorization.k8s.io/v1
      metadata:
        name: nfs-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"]
        - apiGroups: [""]
          resources: ["services", "endpoints"]
          verbs: ["get"]
        - apiGroups: ["extensions"]
          resources: ["podsecuritypolicies"]
          resourceNames: ["nfs-provisioner"]
          verbs: ["use"]
      ---
      kind: ClusterRoleBinding
      apiVersion: rbac.authorization.k8s.io/v1
      metadata:
        name: run-nfs-provisioner
      subjects:
        - kind: ServiceAccount
          name: nfs-provisioner
           # replace with namespace where provisioner is deployed
          namespace: default
      roleRef:
        kind: ClusterRole
        name: nfs-provisioner-runner
        apiGroup: rbac.authorization.k8s.io
      ---
      kind: Role
      apiVersion: rbac.authorization.k8s.io/v1
      metadata:
        name: leader-locking-nfs-provisioner
      rules:
        - apiGroups: [""]
          resources: ["endpoints"]
          verbs: ["get", "list", "watch", "create", "update", "patch"]
      ---
      kind: RoleBinding
      apiVersion: rbac.authorization.k8s.io/v1
      metadata:
        name: leader-locking-nfs-provisioner
      subjects:
        - kind: ServiceAccount
          name: nfs-provisioner
          # replace with namespace where provisioner is deployed
          namespace: default
      roleRef:
        kind: Role
        name: leader-locking-nfs-provisioner
        apiGroup: rbac.authorization.k8s.io
      
    • 创建资源

      [root@master-kubeadm-k8s storageclass]# kubectl apply -f rbac.yaml
      clusterrole.rbac.authorization.k8s.io/nfs-provisioner-runner created
      clusterrolebinding.rbac.authorization.k8s.io/run-nfs-provisioner created
      role.rbac.authorization.k8s.io/leader-locking-nfs-provisioner created
      rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-provisioner created
      
  • 准备PV模板资源

    创建pv模板

    • class.yaml
    kind: StorageClass
    apiVersion: storage.k8s.io/v1
    metadata:
      name: example-nfs
    provisioner: example.com/nfs
    
    • 创建资源

      [root@master-kubeadm-k8s storageclass]# kubectl apply -f class.yaml
      storageclass.storage.k8s.io/example-nfs created
      
    • 查看资源

      [root@master-kubeadm-k8s storageclass]# kubectl get sc
      NAME          PROVISIONER       AGE
      example-nfs   example.com/nfs   39s
      
  • 准备申请存储资源

    创建申请资源的pvc

    • pvc.yaml
    kind: PersistentVolumeClaim
    apiVersion: v1
    metadata:
      name: my-pvc
    spec:
      accessModes:
        - ReadWriteMany
      resources:
        requests:
          storage: 1Mi
      # 这个名字要和上面创建的storageclass名称一致
      storageClassName: example-nfs
    
    • 创建资源

      [root@master-kubeadm-k8s storageclass]# kubectl apply -f pvc.yaml
      persistentvolumeclaim/my-pvc created
      
    • 查看资源

      可以看到创建好的pvc绑定了storageClass

      [root@master-kubeadm-k8s storageclass]# kubectl get pvc
      NAME        STATUS    VOLUME     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
      my-pvc      Pending                                        example-nfs    23s
      nginx-pvc   Bound     nginx-pv   2Gi        RWX                           16h
      
  • 准备NFS插件资源

    创建nfs插件

    • nfs-deployment.yaml
    apiVersion: v1
    kind: ServiceAccount                  # 创建认证账户,ServiceAccount资源在后面会讲,这里不多解析
    metadata:
      name: nfs-provisioner               
    ---
    kind: Deployment
    apiVersion: extensions/v1beta1
    metadata:
      name: nfs-provisioner
    spec:
      replicas: 1
      strategy:
        type: Recreate
      template:
        metadata:
          labels:
            app: nfs-provisioner
        spec:
          serviceAccount: nfs-provisioner         # 使用创建好的账户资源
          containers:
            - name: nfs-provisioner
              image: registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-client-provisioner
              volumeMounts:
                - name: nfs-client-root
                  mountPath: /persistentvolumes
              env:
                - name: PROVISIONER_NAME
                  value: example.com/nfs          # storageClass的资源名称
                - name: NFS_SERVER
                  value: 192.168.50.111           # NFS服务器ip
                - name: NFS_PATH
                  value: /nfs/data/sunny          # 映射的目录,注意提前创建好
          volumes:
            - name: nfs-client-root
              nfs:
                server: 192.168.50.111 
                path: /nfs/data/sunny
    
    • 创建资源

      [root@master-kubeadm-k8s storageclass]# kubectl apply -f nfs-deployment.yaml
      serviceaccount/nfs-provisioner created
      deployment.extensions/nfs-provisioner created
      
    • 查看资源

      # 查看nfs pod资源
      [root@master-kubeadm-k8s storageclass]# kubectl get pods
      NAME                               READY   STATUS              RESTARTS   AGE
      nfs-provisioner-68bd86ff89-6rpnc   1/1     Running             0          105s
      
      # 查看serviceaccount
      [root@master-kubeadm-k8s storageclass]# kubectl get sa
      NAME              SECRETS   AGE
      default           1         37d
      nfs-provisioner   1         3m7s
      
  • 准备Pod资源

    创建pod

    • nginx-pod.yaml
    kind: Pod
    apiVersion: v1
    metadata:
      name: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        volumeMounts:
          - name: my-pvc
            mountPath: "/usr/sunny"
      restartPolicy: "Never"
      volumes:
        - name: my-pvc
          persistentVolumeClaim:
            claimName: my-pvc         # 指定要使用的pvc
    
    • 创建资源

      [root@master-kubeadm-k8s storageclass]# kubectl apply -f nginx-pod.yaml
      pod/nginx created
      
    • 查看资源

      [root@master-kubeadm-k8s storageclass]# kubectl get pods
      NAME                               READY   STATUS    RESTARTS   AGE
      nfs-provisioner-68bd86ff89-6rpnc   1/1     Running   0          3m29s
      nginx                              1/1     Running   0          112s
      
  • 功能验证

    • 查看nfs服务器的映射目录

      在PVC与storageClass动态创建的PV绑定之后,这里就会自动创建一个目录

      image.png
  • 新增文件,测试nginx-pod

    # 创建文件
    [root@master-kubeadm-k8s default-my-pvc-pvc-d9b32154-8b80-11ea-afe2-5254008afee6]# echo 'test storageClass' -> class.html
    [root@master-kubeadm-k8s default-my-pvc-pvc-d9b32154-8b80-11ea-afe2-5254008afee6]# ll
    total 4
    -rw-r--r--. 1 root root 20 May  1 08:04 class.html
        
    # 测试nginx-pod中是否有这个文件
    [root@worker02-kubeadm-k8s ~]# docker exec -it 051a77e8a908 bash
    
    # 可以看到,文件同步了!
    root@nginx:/# cd /usr/sunny/
    root@nginx:/usr/sunny# ls
    class.html
    

1.5 总结

1.5.1 PV、PVC、SC的关系

image.png
  • 对于PV或者StorageClass只能对应一种后端存储
  • 对于手动的情况,一般我们会创建很多的PV,等有PVC需要使用的时候就可以直接使用了
  • 对于自动的情况,那么就由StorageClass来自动管理创建
  • 如果Pod想要使用共享存储,一般会创建PVC,PVC中描述了想要什么类型的后端存储、空间等,K8s从而会匹配对应的PV,如果没有匹配成功,Pod就会处于Pending状态。Pod中使用只需要像使用volumes一样,指定名字就可以使用了
  • 一个Pod可以使用多个PVC,一个PVC也可以给多个Pod使用
  • 一个PVC只能绑定一个PV,一个PV只能对应一种后端存储

1.5.2 PV的状态和回收策略

  • PV的状态

    • Available:表示当前的pv没有被绑定
    • Bound:表示已经被pvc挂载
    • Released:pvc没有在使用pv, 需要管理员手工释放pv
    • Failed:资源回收失败
  • PV回收策略

    • Retain:表示删除PVC的时候,PV不会一起删除,而是变成Released状态等待管理员手动清理
    • Recycle:在Kubernetes新版本就不用了,采用动态PV供给来替代
    • Delete:表示删除PVC的时候,PV也会一起删除,同时也删除PV所指向的实际存储空间

注意:目前只有NFS和HostPath支持Recycle策略。AWS EBS、GCE PD、Azure Disk和Cinder支持Delete策略

你可能感兴趣的:(七、Kubernetes 持久化存储)