技术方案之 安全容器Kata CSI block volume直通方案

基于kata direct volume特性, 实现安全容器KataContainer的 CSI block volume直通方案

Posted by 董江 on Thursday, January 5, 2023

技术方案之 基于kata direct volume特性, 实现安全容器KataContainer的 CSI block volume直通方案

Kubernetes 中集成 Kata Containers 可以为容器运行时提供更好的安全性隔离性,但在存储方面仍然还存在一些限制与不足。

目前方案:virtiofs协议

Kata Containers 在 2.4 版本之前,挂载 PV 的整个过程与 CSI 之间是没有任何交互的,而且也不能直接使用 CSI 挂载的 PV,只能通过 virtiofs 协议 将宿主机上的存储卷以文件共享的方式提供给 Kata Containers 虚拟机中的 container 使用。

virtiofs 协议 的实现方式如下图所示:

这种方式虽然能够解决 PV 存储挂载的问题,但与直接在宿主机上使用存储卷相比,由于 virtiofs 实现方式的 I/O 路径过长,会带来不少的性能损耗。

经过环境实测,总结出以下几方面问题:

  1. 稳定性方面:在实际环境中,对 virtiofs 共享的盘进行 fio 测试,我们经常能观察到 io 不连续的现象,并且在对盘进行加压测试时,会影响到容器中其他进程的响应,比如 ssh 进程响应会超时;

  2. 性能方面:与直接在宿主机上使用存储卷相比,virtiofs 共享盘在 iops带宽方面有不少差距;

  3. 功能方面virtiofs 共享盘方式无法在线调整 PV 大小,需要通过重启 Pod 才能使 VM 能感知到 PV 大小变化;

优化方案

基于 virtiofs 现存问题,Kata Containers 在 2.4 版本提供了 direct assigned volume 功能,能够将文件系统挂载操作从宿主机移动到 Guest 中,相当于是一种 block volume 直通方案。和 virtiofs 相比,不仅能提供接近直接宿主机上使用存储卷的性能,而且还能支持 native FS。由于不需要借助于 virtiofs,在使用上会更加稳定,也能带来安全性方面的提升。同时,这个特性还支持在线修改 PV 存储大小。

Kata Containers 存储卷直通方案如下图所示:

具体kata direct assigned volume 特性设计:https://github.com/kata-containers/kata-containers/blob/main/docs/design/direct-blk-device-assignment.md

在 CSI 中实现 direct volume

前提条件

  1. 由于需要在 host 上创建文件,CSI node 服务在部署时需要把 /run/kata-containers/shared/direct-volumes 目录以 hostpath 方式挂载到 pod 里。

  2. CSI 在挂载时需要明确知道所挂载的 volume 是否是以 direct volume 这种方式挂载,所以需要有一种机制能通知到 CSI,可以借助以下三种方式:

    通过 StorageClass 指定 direct volume 属性

    PVC 对象里通过 annotation 打上 direct volume 属性,同时 CSI 插件需要打开 --extra-create-metadata 属性来帮助 CSI 能从 K8s apiserver 查询到 PVCannotation 信息

    通过查询 Podruntimeclass 信息来判断是否是 Kata direct volume 挂载

实现步骤

以下步骤主要都在 CSI NodePublishVolume 接口里实现:

  1. 把远程存储的 block device 挂载到 host 上。
  2. 根据实际需求场景对 block device 做文件系统格式化。
  3. 生成 mountinfo.json 信息并把 mountinfo.json 信息传递给 Kata。

mountInfo 的内容是 json 格式,主要数据结构如下:

// MountInfo contains the information needed by Kata to consume a host block device and mount it as a filesystem inside the guest VM.
type MountInfo struct {
	// The type of the volume (ie. block)
	VolumeType string `json:"volume-type"`
	// The device backing the volume.
	Device string `json:"device"`
	// The filesystem type to be mounted on the volume.
	FsType string `json:"fstype"`
	// Additional metadata to pass to the agent regarding this volume.
	Metadata map[string]string `json:"metadata,omitempty"`
	// Additional mount options.
	Options []string `json:"options,omitempty"`
}

其中 CSI 侧主要需要传递以下三个字段信息即可,例如:

mountInfo := &volume.MountInfo{
				VolumeType: "block",  // 设备类型
    				Device:     dev/sdd,  // 块设备路径
    				FsType:     ext4,     // 文件系统类型
			}
volume.Add("/run/kata-containers/shared/direct-volumes/volume-path(base64加密)/", mountInfo) 

CSI 负责把 mountinfo.json 传递给 KataKata 会在容器所在 host/run/kata-containers/shared/direct-volumes/volume-path(base64加密)/ 目录下生成 mountinfo.json 文件,目前 CSI 有两种方式可以传递 mountinfo.json 信息

  1. 通过调用 kata-container 代码里 direct volume 模块的 add 方法传递 mountinfo.json 信息,部分代码实例如下:
import (
    "encoding/json"
    volume "github.com/kata-containers/kata-containers/src/runtime/pkg/direct-volume"
    "google.golang.org/grpc/status"
    klog "k8s.io/klog/v2"
)

// NodePublishVolume 中发布
func AddDirectVolume(volumePath, device, fsType string) error {
    mountInfo := &volume.MountInfo{
        VolumeType: "block",
        Device:     device,
        FsType:     fsType,
    }

    mi, err := json.Marshal(mountInfo)
    if err != nil {
        klog.Errorf("addDirectVolume - json.Marshal failed: ", err.Error())
        return status.Errorf(codes.Internal, "json.Marshal failed: %s", err.Error())
    }
    
    if err := volume.Add(volumePath, string(mi)); err != nil { 
        klog.Errorf("addDirectVolume - add direct volume failed: ", err.Error())
        return status.Errorf(codes.Internal, "add direct volume failed: %s", err.Error())
    }

    klog.Infof("add direct volume done: %s%s", volumePath, string(mi))
    return nil
}

//  NodeUnpublishVolume 中remove掉
	if err := volume.Remove(targetPath); err != nil {
		log.Errorf("NodeUnpublishVolume: kata direct volume remove failed: %s", err.Error())
	}
  1. 通过 kata-runtime CLI 命令传递 mountinfo.json 信息:
$ kata-runtime direct-volume add --volume-path [volumePath] --mount-info [mountinfo.json]

$ kata-runtime direct-volume remove --volume-path [volumePath] --mount-info [mountinfo.json]

最后会在容器所在 host 的 /run/kata-containers/shared/direct-volumes/volume-path(base64加密)/ 目录下生成 mountinfo.json 文件,然后 Kata Containers 会在启动容器时检查该目录是否有 mountinfo.json 文件并解析该文件,同时更新容器 specmount 信息,将直通卷的信息加入进去,然后将修改后的 spec 传给 kata-agent;

方案限制: 1.使用 direct volume 方式的 PV 只能给一个 Pod 使用,所以在创建 PVC 时需要指定 accessModeReadWriteOnce 2.使用 direct volume 方式不支持更高级的 volume 功能,比如:fsGroupfsGroupChangePolicysubPath

未来:kata 联动 kubelet/kube-apiserver,实现CSI 卷的运行时辅助挂载

社区未通过的最终方案:KEP-2857:持久卷的运行时辅助安装

Demo

---
apiVersion: v1
kind: Pod
metadata:
  name: app
spec:
  runtime-class: kata-qemu  # 方式一:通过设置runtime-class,使用kata 运行时; 其下面的卷都是直通卷
  containers:
  - name: app
    image: centos
    command: ["/bin/sh"]
    args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"]
    volumeMounts:
    - name: persistent-storage
      mountPath: /data
  volumes:
  - name: persistent-storage
    persistentVolumeClaim:
      claimName: ebs-claim
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  annotations:
    skip-hostmount: "true"  # 方式二:通知csi为直通卷
  name: ebs-claim
spec:
  accessModes:
    - ReadWriteOncePod
  volumeMode: Filesystem
  storageClassName: ebs-sc
  resources:
    requests:
      storage: 1Gi
---
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: ebs-sc
provisioner: local.csi.cmss.com # 方式三:特定的csi driver实现 kata direct assigned volume
volumeBindingMode: WaitForFirstConsumer
parameters:
  csi.storage.k8s.io/fstype: ext4

基于kata容器直接分配卷的本地csi驱动程序

代码仓库:https://github.com/kubeservice-stack/kata-local-csi-driver

「如果这篇文章对你有用,请随意打赏」

Kubeservice博客

如果这篇文章对你有用,请随意打赏

使用微信扫描二维码完成支付