Kubernetes存储原理详解

2,227 阅读7分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

1 简介

这是一张 K8S CSI 系统架构图

2 相关解释

CSI是Container Storage Interface(容器存储接口)的简写。

CSI 的目的是定义行业标准“容器存储接口”,使存储供应商能够开发一个符合CSI标准的插件并使其可以在多个容器编排系统中工作。

CSI组件一般采用容器化部署,减少了环境依赖。

3 K8S对象

3.1 PersistentVolume

集群级别的资源,持久存储卷,由 集群管理员 或者 External Provisioner 创建。

PV 的生命周期独立于使用 PV 的 Pod,PV 的 .Spec 中保存了存储设备的详细信息。

回收策略

  • retain:保留策略,当删除PVC的时候,PV与外部存储资源仍然存在。
  • delete:删除策略,当与PV绑定的PVC被删除的时候,会从K8S集群中删除PV对象,并执行外部存储资源的删除操作。
  • resycle(已废弃)

包含状态:

  • Available
  • Bound
  • Released

3.2 PersistentVolumeClaim

持久存储卷声明,命名空间(namespace)级别的资源,由 用户 或者 StatefulSet 控制器(根据VolumeClaimTemplate) 创建。

PVC 类似于 Pod,Pod 消耗 Node 资源,PVC 消耗 PV 资源。Pod 可以请求特定级别的资源(CPU 和内存),而 PVC 可以请求特定存储卷的大小及访问模式(Access Mode)

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
   name: nginx-pvc
spec:
   storageClassName: cbs
   accessModes:
     - ReadWriteOnce
   resources:
     requests:
       storage: 10Gi

包含状态:

  • Pending
  • Bound

3.3 StorageClass

StorageClass 是集群级别的资源,由集群管理员创建。定义了创建pv的模板信息,用于动态创建PV。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: cbs
parameters:
  type: cbs
provisioner: cloud.tencent.com/qcloud-cbs
reclaimPolicy: Delete
volumeBindingMode: Immediate

3.4 VolumeAttachment

记录了PV的相关挂载信息,如挂载到哪个node节点,由哪个volume plugin来挂载等。

AD Controller 创建一个VolumeAttachment,而 External-attacher 则通过观察该 VolumeAttachment,根据其状态属性来进行存储的挂载和卸载操作。

3.5 CSINode

CSINode 记录了csi plugin的相关信息(如nodeId、driverName、拓扑信息等)。

当Node Driver Registrar向kubelet注册一个csi plugin后,会创建(或更新)一个CSINode对象,记录csi plugin的相关信息

apiVersion: storage.k8s.io/v1beta1
kind: CSINode
metadata:
  name: 10.1.2.11
spec:
  drivers:
  - allocatable:
      count: 18
    name: com.tencent.cloud.csi.cbs
    nodeID: ins-ig73rt44
    topologyKeys:
    - topology.com.tencent.cloud.csi.cbs/zone

4 存储组件及作用

4.1 Volume Plugin

扩展各种存储类型的卷的管理能力,实现第三方存储的各种操作能力与k8s存储系统的结合。

调用第三方存储的接口或命令,从而提供数据卷的创建/删除、attach/detach、mount/umount的具体操作实现,可以认为是第三方存储的代理人。前面分析组件中的对于数据卷的创建/删除、attach/detach、mount/umount操作,全是调用volume plugin来完成。

根据源码所在位置,volume plugin分为in-treeout-of-tree

  • in-tree

在K8S源码内部实现,和K8S一起发布、管理,更新迭代慢、灵活性差。

  • out-of-tree

代码独立于K8S,由存储厂商实现,有CSI、flexvolume两种实现。

csi plugin

external plugin

external plugin包括了external-provisioner、external-attacher、external-resizer、external-snapshotter等,external plugin辅助csi plugin组件,共同完成了存储相关操作。external plugin负责watch pvc、volumeAttachment等对象,然后调用volume plugin来完成存储的相关操作。如external-provisioner watch pvc对象,然后调用csi plugin来创建存储,最后创建pv对象;external-attacher watch volumeAttachment对象,然后调用csi plugin来做attach/dettach操作;external-resizer watch pvc对象,然后调用csi plugin来做存储的扩容操作等。

4.2 kube-controller-manager

PV controller

负责pv、pvc的绑定与生命周期管理(如创建/删除底层存储,创建/删除pv对象,pv与pvc对象的状态变更)。

创建/删除底层存储、创建/删除pv对象的操作,由PV controller调用volume plugin(in-tree)来完成。

AD controller

AD Cotroller全称Attachment/Detachment 控制器,主要负责创建、删除VolumeAttachment对象,并调用volume plugin来做存储设备的Attach/Detach操作(将数据卷挂载到特定node节点上/从特定node节点上解除挂载),以及更新node.Status.VolumesAttached等。

不同的volume plugin的Attach/Detach操作逻辑有所不同,如通过out-tree volume plugin来使用存储,则的Attach/Detach操作只是修改VolumeAttachment对象的状态,而不会真正的将数据卷挂载到节点/从节点上解除挂载,真正的节点存储挂载/解除挂载操作由kubelet中volume manager调用。

4.3 kubelet

volume manager

主要是管理卷的Attach/Detach(与AD controller作用相同,通过kubelet启动参数控制哪个组件来做该操作)、mount/umount等操作。

5 流程分析

5.1 创建与挂载

5.1.1 in-tree

[1] 用户创建pvc

[2] PV controller watch到PVC的创建,寻找合适的PV与之绑定

[3、4] 当找不到合适的PV时,将调用volume plugin来创建volume,并创建PV对象,之后该PV对象与PVC对象绑定

[5] 用户创建挂载PVC的pod

[6] kube-scheduler watch到pod的创建,为其寻找合适的node调度

[7、8] Pod调度完成后,AD controller/volume manager watch到pod声明的volume没有进行attach操作,将调用volume plugin来做attach操作

[9] volume plugin进行attach操作,将volume挂载到pod所在node节点,成为如/dev/vdb的设备

[10、11] attach操作完成后,volume manager watch到pod声明的volume没有进行mount操作,将调用volume plugin来做mount操作

[12] volume plugin进行mount操作,将node节点上的第[9]步得到的/dev/vdb设备挂载到指定目录

5.1.2 Out-tree

[1] 用户创建PVC

[2] PV controller watch到PVC的创建,寻找合适的PV与之绑定。当寻找不到合适的PV时,将更新PVC对象,添加annotation: volume.beta.kubernetes.io/storage-provisioner,让external-provisioner组件开始开始创建存储与PV对象的操作

[3] external-provisioner组件watch到PVC的创建,判断annotation:volume.beta.kubernetes.io/storage-provisioner的值,即判断是否是自己来负责做创建操作,是则调用csi-plugin ControllerServer来创建存储,并创建PV对象

[4] PV controller watch到PVC,寻找合适的PV(上一步中创建)与之绑定

[5] 用户创建挂载PVC的Pod

[6] kube-scheduler watch到Pod的创建,为其寻找合适的node调度

[7、8] pod调度完成后,AD controller/volume manager watch到pod声明的volume没有进行attach操作,将调用csi-attacher来做attach操作,实际上只是创建volumeAttachement对象

[9] external-attacher组件watch到volumeAttachment对象的新建,调用csi-plugin进行attach操作

[10] csi-plugin ControllerServer进行attach操作,将volume挂载到Pod所在node节点,成为如/dev/vdb的设备

[11、12] attach操作完成后,volume manager watch到Pod声明的volume没有进行mount操作,将调用csi-mounter来做mount操作

[13] csi-mounter调用csi-plugin NodeServer进行mount操作,将node节点上的第(10)步得到的/dev/vdb设备挂载到指定目录

附录

emptyDir

emptyDir 类型的 volume 创建于 Pod 被调度到某个宿主机上的时候,而同一个 Pod 内的容器都能读写 emptyDir 中的同一个文件。一旦这个 Pod 离开了这个宿主机,emptyDir 中的数据就会被永久删除

所以目前EmptyDir类型的volume主要用作临时空间,比如:

  • Web服务器写日志
  • tmp文件需要的临时目录

使用步骤如下:

首先,需要在pod内声明了一个名称为nginxdatavolume

volumes:
  - name: nginxdata
    emptyDir: {}

然后,才能在容器中挂在这个volume

volumeMounts:
  # mountPath即volume在容器内的挂载点路径
  - mountPath: /var/www/html
    name: nginxdata

测试yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-dp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-dp
  template:
    metadata:
      labels:
        app: test-dp
    spec:
      containers:
      - name: nginx
        image: nginx:1.19.5
        volumeMounts:
          - mountPath: /var/www/html
            name: nginxdata
      volumes:
        - name: nginxdata
          emptyDir: {}

按照以上的配置文件创建了Pod之后,可以在宿主机上的/var/lib/kubelet/pods/<pod uid>/volumes/kubernetes.io~empty-dir目录下查看到新生成的名为nginxdata的目录。

如果登录到该Pod创建的容器中,也可以看到名为/var/www/html的目录,这个目录与宿主机上的nginxdata目录是同一个。

hostPath

hostPath Volume为Pod挂载宿主机上的目录或文件,使得容器可以使用宿主机的高速文件系统进行存储。

缺点是,在K8S中,Pod都是动态在各node节点上调度。当一个Pod在当前node节点上启动并通过hostPath存储了文件到本地以后,下次调度到另一个节点上启动时,就无法使用在之前节点上存储的文件

apiVersion: apps/v1
kind: Deployment
metadata:
  name: test-dp
spec:
  replicas: 1
  selector:
    matchLabels:
      app: test-dp
  template:
    metadata:
      labels:
        app: test-dp
    spec:
      containers:
      - name: nginx
        image: nginx:1.19.5
        volumeMounts:
          - mountPath: /var/www/html
            name: nginxdata
      volumes:
        - name: nginxdata
          hostPath:
            path: /data