智汇华云 | 通过iscsi为容器提供存储
  • 浏览:133 评论:0 人

  • 介绍
    将远端的存储通过iscsi协议为容器提供块存储,是一种通用的容器存储解决方案,下面将通过kubernetes中的in-tree方式来演示该例子,并分析其中的细节。


    iSCSI协议是C/S架构,client是iSCSI initiator,server端为iSCSI target。iSCSI协议的主要功能是利用TCP/IP网络,在主机系统(可称为initiator)和目标存储设备(称为target)之间进行大量的数据封装和可靠传输过程。主要分成两个组成部分,分别为iSCSI服务器端和iSCSI客户端

    iSCSI服务器端 (iSCSI Target)
    iSCSI服务器端为iSCSI target,这是I/O操作的执行者。主要是为了导出一个或多个块设备供启动者(initiator)使用,可以通过硬件和软件的方式来实现。在Linux中可以使用scsi-target-utils软件包来模拟实现。在使用iSCSI时,会在 iSCSI 储存设备上去建立 LUN(Logical Unit Number)来提供给具备 iSCSI Initiator 功能的主机来存取 数据的。LUN 好比是个“逻辑单位磁碟”,物理上通常是由数个实体磁碟( RAID 或 LVM 技术的技术实现)所组成。LUN ID由iSCSI目标设备(Target)分配。iSCSI 启动端(Initiator)设备当前支持在每个目标设备(Target)中导出最多256个LUN。即最大支持16个target。
    iSCSI target设备名称采用如下格式来命名:iqn..[:],需要事先进行配置,保证唯一性。

    iSCSI客户端 (iSCSI Initiator)
    iSCSI客户端为iSCSI initiator,这是I/O操作的发起者。是I/O操作的发起者,需要通过发现过程请求远端快设备。在Linux系统中可以通过软件来模拟,需要安装iSCSI设备驱动。如iscsi-initiator-utils。

    实验
    可以通过iSCSI将远程的磁盘分区映射到本地之后就可以像使用本地磁盘一样,将该远程盘进行格式化以及挂载操作,给容器使用。
    我们通过 scsi-target-utils来实现iSCSI target,将主机上的/dev/sdb磁盘分区作为Lun,如下图所示
    [root@iscsi-server yum.repos.d]# tgtadm -L iscsi -o show -m target
    Target 1: iqn.2021-11.com.huayun.san:123456
      System information:
        Driver: iscsi
        State: ready
      I_T nexus information:
      LUN information:
        LUN: 0
          Type: controller
          SCSI ID: IET?????00010000
          SCSI SN: beaf10
          Size: 0 MB, Block size: 1
          Online: Yes
          Removable media: No
          Prevent removal: No
          Readonly: No
          SWP: No
          Thin-provisioning: No
          Backing store type: null
          Backing store path: None
          Backing store flags:
        LUN: 1
          Type: disk
          SCSI ID: IET?????00010001
          SCSI SN: beaf11
          Size: 10737 MB, Block size: 512
          Online: Yes
          Removable media: No
          Prevent removal: No
          Readonly: No
          SWP: No
          Thin-provisioning: No
          Backing store type: rdwr
          Backing store path: /dev/vdb
          Backing store flags:
      Account information:
      ACL information:

    之后在kubernetes的node节点上需要事先安装iscsi-initiator-utils,并且设置对应的initiatorname,如果开启了acl认证,需要将node节点的initiatorname添加到acl里面。

    之后创建一个pod,其中指定一个存在的iscsi lun对接信息如下
    apiVersion: v1
    kind: Pod
    metadata:
     name: iscsipd
    spec:
     containers:
     - name: iscsipd-rw
      image: kubernetes/pause
      volumeMounts:
      - mountPath: "/mnt/iscsipd"
       name: iscsipd-rw
     volumes:
     - name: iscsipd-rw
      iscsi:
       targetPortal: 10.0.2.15:3260
       portals: ['10.0.2.16:3260', '10.0.2.17:3260']
       iqn: iqn.2001-04.com.example:storage.kube.sys1.xyz
       lun: 0
       fsType: ext4
       readOnly: true
    之后可以看到远程的卷被成功的挂载到node上,被容器所使用





    Volume.iscsi说明
    pod的spec中可以在volumes.iscsi中指定对接信息包括如下



    源码分析
    挂载阶段
    pod调度到某个node上,之后由kubelet中的volumemanager完成对于volume attach&mount操作,核心代码位于kubernetes/pkg/volume/iscsi目录下,在volume挂载的过程中,会首先调用WaitForAttach()完成挂载流程,SetUpDevice()挂载到某个容器所对应的目录。iscsiAttacher.WaitForAttach()流程如图所示:

    Step1: 通过iscsiadm -m iface -l b. InitIface -o show获取对应的iscsiTransport,如果不额外指定的话b. InitIface为default,iscsiTransport为tcp.
    Step2: 如果pod的定义中指定iscsi.initiatorName?,则需要cloneIface(), 指定iscsi.initiatorName需要与node的不一致,这样当开启ACL initiatorName控制的时候,pod可以运行在不同的节点上。
    Step3: 基于iqn号获取lock,主要解决的场景为相同target下不同volume同时挂载或者login与logout操作并发进行,这个锁引入的目的主要是为了后面volume在Detach的时候,需要根据isSessionBusy来判断是否需要logout,断开node与target的所有链接。
    Step4: GetISCSIPortalHostMapForTarget主要根据target iqn获取到login到该target上的scsi hosts number,返回的结构为
    {
      "192.168.30.7:3260": 2,
      "192.168.30.8:3260": 3,
    }
    通过这个map的引入后面用于判断是否需要login,还是直接通过scanOneLun()来发现接入的Lun,避免没有必要的login操作。scanOneLun之后会发现挂载到node上的device。
    Step5: 根据volomeMode的类型是直接的PersistentVolumeBlock还是PersistentVolumeFileSystem模式,二者的区别在于是否需要对device进行格式化,创建文件系统,之后创建globalPDPath目录,目录位置采用如下格式
    /var/lib/kubelet/plugins/kubernetes.io/iscsi/ /{ifaceName}/{portal-some_iqn-lun-lun_id},之后持久化的iscsi disk元数据到globalPDPath目录下iscsi.json,元数据主要用于DetachVolume的时候会涉及到,内容如下所示:

    {
      "VolName":"iscsipd-rw",
      "Portals":[
        "178.104.162.58:3260",
      ],
      "Iqn":"iqn.2021-11.com.huayun.san:123456",
      "Lun":"1",
      "InitIface":"default",
      "Iface":"default",
      "InitiatorName":"",
      "MetricsProvider":null
    }

    在WaitForAttach 阶段完成了device远端挂载、格式化以及挂载到globalPDPath目录下,SetUpDevice流程较为简单主要是将globalPDPath mount –bind到容器对应的目录,之后对目录进行SetVolumeOwnership()操作。


    卸载阶段
    pod销毁的时候,会由kubelet完成volume的umount&detach操作,核心代码位于kubernetes/pkg/volume/iscsi目录下,主要完成umount node上的挂载信息,之后根据globalPDPath目录下iscsi.json元数据信息来完成TearDownDevice断开device,之后清理掉globalPDPath。


    Step1: 根据mntPath挂载点信息获得device盘符,之后Unmount()掉挂载点信息
    Step2:loadISCSI中根据mntPath获得该iscsi挂载信息的元数据信息,其中包括iqn iface volName initiatorName等信息
    Step3: deleteDevices()中通过对device进行echo 1> delete操作,删除掉盘符
    Step4: 基于iqn获取targetLocks.LockKey,之后判断该target在node上是否存在其他的盘挂载,如果没有存在,则进行iscsi logout操作,断开node与target之后的连接

    总结
    In-tree下的iscsi方式为容器提供iscsi的存储类似于静态供应,需要事先系统管理员创建好后端的iscsi存储,之后容器提供指定对应的配置来使用。对于已经存在支持iscsi协议挂载的后端存储,并且不具备动态功能csi插件的场景下具有一定的使用场景。