技术方案之 基于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 路径过长,会带来不少的性能损耗。
经过环境实测,总结出以下几方面问题:
- 
稳定性方面:在实际环境中,对virtiofs共享的盘进行fio 测试,我们经常能观察到 io 不连续的现象,并且在对盘进行加压测试时,会影响到容器中其他进程的响应,比如 ssh 进程响应会超时; - 
性能方面:与直接在宿主机上使用存储卷相比,virtiofs共享盘在iops和带宽方面有不少差距; - 
功能方面: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
前提条件
- 
由于需要在
host上创建文件,CSI node服务在部署时需要把/run/kata-containers/shared/direct-volumes目录以hostpath方式挂载到pod里。 - 
CSI在挂载时需要明确知道所挂载的volume是否是以direct volume这种方式挂载,所以需要有一种机制能通知到CSI,可以借助以下三种方式:通过
StorageClass指定direct volume属性在
PVC对象里通过annotation打上direct volume属性,同时 CSI 插件需要打开--extra-create-metadata属性来帮助CSI能从K8s apiserver查询到PVC的annotation信息通过查询
Pod的runtimeclass信息来判断是否是Kata direct volume挂载 
实现步骤
以下步骤主要都在 CSI NodePublishVolume 接口里实现:
- 把远程存储的 
block device挂载到host上。 - 根据实际需求场景对 
block device做文件系统格式化。 - 生成 
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 传递给 Kata,Kata 会在容器所在 host 的 /run/kata-containers/shared/direct-volumes/volume-path(base64加密)/ 目录下生成 mountinfo.json 文件,目前 CSI 有两种方式可以传递 mountinfo.json 信息
- 通过调用 
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())
	}
- 通过 
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 文件并解析该文件,同时更新容器 spec 中 mount 信息,将直通卷的信息加入进去,然后将修改后的 spec 传给 kata-agent;
方案限制:
1.使用 direct volume 方式的 PV 只能给一个 Pod 使用,所以在创建 PVC 时需要指定 accessMode 为 ReadWriteOnce
2.使用 direct volume 方式不支持更高级的 volume 功能,比如:fsGroup、fsGroupChangePolicy 和 subPath
未来: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
「如果这篇文章对你有用,请随意打赏」
如果这篇文章对你有用,请随意打赏
使用微信扫描二维码完成支付