Kubernetes 存储的卷插件:从 Flexvolume 到 CSI

Kubernetes 存储的卷插件:从 Flexvolume 到 CSI

当 Kubernetes 还是 v1.0.0 时,就有卷插件了。 它们需要将系统连接到 Kubernetes 以存储持久(永久)容器数据。 他们的数量很少,最早的存储提供商包括 GCE PD、Ceph、AWS EBS 等。

这些插件与 Kubernetes 一起提供,这就是它们得名的原因 - in-tree。 然而,对于许多人来说,现有的此类插件集是不够的。 工匠们使用补丁向 Kubernetes 核心添加简单的插件,之后他们组装了自己的 Kubernetes 并将其安装在自己的服务器上。 但随着时间的推移,Kubernetes 开发人员意识到 问题无法解决。 人们需要 钓鱼竿。 在 Kubernetes v1.2.0 的发布中它出现了......

Flexvolume 插件:最小钓鱼竿

Kubernetes 开发人员创建了 FlexVolume 插件,它是一个变量和方法的逻辑框架,用于与第三方开发人员实现的 Flexvolume 驱动程序一起使用。

让我们停下来仔细看看 FlexVolume 驱动程序是什么。 这是一定的 可执行文件 (二进制文件、Python 脚本、Bash 脚本等),执行时将命令行参数作为输入,并返回一条包含 JSON 格式的预先已知字段的消息。 按照约定,第一个命令行参数始终是一个方法,其余参数是其参数。

Kubernetes 存储的卷插件:从 Flexvolume 到 CSI
OpenShift 中 CIFS 共享的连接图。 Flexvolume 驱动程序 - 位于中心

最小方法集 它看起来像这样:

flexvolume_driver mount # отвечает за присоединение тома к pod'у
# Формат возвращаемого сообщения:
{
  "status": "Success"/"Failure"/"Not supported",
  "message": "По какой причине был возвращен именно такой статус",
}

flexvolume_driver unmount # отвечает за отсоединение тома от pod'а
# Формат возвращаемого сообщения:
{
  "status": "Success"/"Failure"/"Not supported",
  "message": "По какой причине был возвращен именно такой статус",
}

flexvolume_driver init # отвечает за инициализацию плагина
# Формат возвращаемого сообщения:
{
  "status": "Success"/"Failure"/"Not supported",
  "message": "По какой причине был возвращен именно такой статус",
  // Определяет, использует ли драйвер методы attach/deatach
  "capabilities":{"attach": True/False}
}

使用方法 attach и detach 将定义 kubelet 将来调用驱动程序时将采取的场景。 还有一些特殊的方法 expandvolume и expandfs,它们负责动态调整卷的大小。

作为该方法添加的更改的示例 expandvolume,并且能够实时调整卷大小,您可以熟悉 我们的拉取请求 在 Rook Ceph 操作员中。

以下是使用 NFS 的 Flexvolume 驱动程序的实现示例:

usage() {
    err "Invalid usage. Usage: "
    err "t$0 init"
    err "t$0 mount <mount dir> <json params>"
    err "t$0 unmount <mount dir>"
    exit 1
}

err() {
    echo -ne $* 1>&2
}

log() {
    echo -ne $* >&1
}

ismounted() {
    MOUNT=`findmnt -n ${MNTPATH} 2>/dev/null | cut -d' ' -f1`
    if [ "${MOUNT}" == "${MNTPATH}" ]; then
        echo "1"
    else
        echo "0"
    fi
}

domount() {
    MNTPATH=$1

    NFS_SERVER=$(echo $2 | jq -r '.server')
    SHARE=$(echo $2 | jq -r '.share')

    if [ $(ismounted) -eq 1 ] ; then
        log '{"status": "Success"}'
        exit 0
    fi

    mkdir -p ${MNTPATH} &> /dev/null

    mount -t nfs ${NFS_SERVER}:/${SHARE} ${MNTPATH} &> /dev/null
    if [ $? -ne 0 ]; then
        err "{ "status": "Failure", "message": "Failed to mount ${NFS_SERVER}:${SHARE} at ${MNTPATH}"}"
        exit 1
    fi
    log '{"status": "Success"}'
    exit 0
}

unmount() {
    MNTPATH=$1
    if [ $(ismounted) -eq 0 ] ; then
        log '{"status": "Success"}'
        exit 0
    fi

    umount ${MNTPATH} &> /dev/null
    if [ $? -ne 0 ]; then
        err "{ "status": "Failed", "message": "Failed to unmount volume at ${MNTPATH}"}"
        exit 1
    fi

    log '{"status": "Success"}'
    exit 0
}

op=$1

if [ "$op" = "init" ]; then
    log '{"status": "Success", "capabilities": {"attach": false}}'
    exit 0
fi

if [ $# -lt 2 ]; then
    usage
fi

shift

case "$op" in
    mount)
        domount $*
        ;;
    unmount)
        unmount $*
        ;;
    *)
        log '{"status": "Not supported"}'
        exit 0
esac

exit 1

因此,在准备好实际的可执行文件后,您需要 将驱动上传到Kubernetes集群。 驱动程序必须按照预定的路径位于每个集群节点上。 默认情况下已选择:

/usr/libexec/kubernetes/kubelet-plugins/volume/exec/имя_поставщика_хранилища~имя_драйвера/

...但是当使用不同的 Kubernetes 发行版(OpenShift、Rancher...)时,路径可能会有所不同。

Flexvolume问题:如何正确抛竿?

事实证明,将 Flexvolume 驱动程序上传到集群节点并非易事。 手动完成一次操作后,很容易遇到集群中出现新节点的情况:由于添加新节点、自动水平扩展,或者更糟糕的是由于故障而更换节点。 在这种情况下,应该处理这些节点上的存储 不可能,直到您仍然手动向其中添加 Flexvolume 驱动程序。

这个问题的解决方案是 Kubernetes 原语之一 - DaemonSet。 当集群中出现新节点时,它会自动包含来自 DaemonSet 的 pod,本地卷沿着查找 Flexvolume 驱动程序的路径附加到该 pod。 成功创建后,pod 会将驱动程序工作所需的文件复制到磁盘。

以下是用于布置 Flexvolume 插件的 DaemonSet 的示例:

apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
  name: flex-set
spec:
  template:
    metadata:
      name: flex-deploy
      labels:
        app: flex-deploy
    spec:
      containers:
        - image: <deployment_image>
          name: flex-deploy
          securityContext:
              privileged: true
          volumeMounts:
            - mountPath: /flexmnt
              name: flexvolume-mount
      volumes:
        - name: flexvolume-mount
          hostPath:
            path: <host_driver_directory>

...以及用于布置 Flexvolume 驱动程序的 Bash 脚本示例:

#!/bin/sh

set -o errexit
set -o pipefail

VENDOR=k8s.io
DRIVER=nfs

driver_dir=$VENDOR${VENDOR:+"~"}${DRIVER}
if [ ! -d "/flexmnt/$driver_dir" ]; then
  mkdir "/flexmnt/$driver_dir"
fi

cp "/$DRIVER" "/flexmnt/$driver_dir/.$DRIVER"
mv -f "/flexmnt/$driver_dir/.$DRIVER" "/flexmnt/$driver_dir/$DRIVER"

while : ; do
  sleep 3600
done

重要的是不要忘记复制操作 不是原子的。 kubelet 很有可能在其配置过程完成之前开始使用驱动程序,从而导致系统崩溃。 正确的方法是首先以不同的名称复制驱动程序文件,然后使用原子重命名操作。

Kubernetes 存储的卷插件:从 Flexvolume 到 CSI
在 Rook Operator 中使用 Ceph 的示意图:图中的 Flexvolume 驱动程序位于 Rook 代理内部

使用 Flexvolume 驱动程序时的下一个问题是对于集群节点上的大多数存储 必须安装必要的软件 (例如,Ceph 的 ceph-common 包)。 最初,Flexvolume 插件并不是为了实现如此复杂的系统而设计的。

这个问题的原始解决方案可以在 Rook 操作符的 Flexvolume 驱动程序实现中看到:

驱动程序本身被设计为 RPC 客户端。 用于通信的IPC套接字与驱动程序本身位于同一目录中。 我们记得,要复制驱动程序文件,最好使用 DaemonSet,它将目录与驱动程序作为卷连接起来。 复制必要的 rook 驱动程序文件后,该 pod 不会消失,而是通过附加卷作为成熟的 RPC 服务器连接到 IPC 套接字。 ceph-common 软件包已安装在 pod 容器内。 IPC 套接字确保 kubelet 将与位于同一节点上的 pod 进行准确通信。 一切巧妙的事情都很简单!...

再见,我们深情的...树内插件!

Kubernetes 开发人员发现核心内用于存储的插件数量为 XNUMX 个。 其中每一项的更改都会以某种方式贯穿整个 Kubernetes 发布周期。

事实证明,要使用新版本的存储插件, 您需要更新整个集群。 除此之外,你可能会感到惊讶的是,新版本的 Kubernetes 会突然与你正在使用的 Linux 内核变得不兼容......所以你擦干眼泪,咬紧牙关,与你的管理层和用户协调时间更新Linux内核和Kubernetes集群。 提供服务可能会出现停机。

这种情况不仅仅是滑稽,你不觉得吗? 整个社区都清楚地意识到这种方法不起作用。 Kubernetes 开发人员故意宣布,内核将不再接受用于处理存储的新插件。 此外,正如我们所知,在 Flexvolume 插件的实施过程中发现了一些缺陷......

Kubernetes 中最新添加的卷插件 CSI 被要求一劳永逸地解决持久数据存储的问题。 它的 alpha 版本,更完整地称为 Out-of-Tree CSI Volume Plugins,在发行版中宣布 库伯内斯 1.9.

容器存储接口,或者CSI 3000旋转杆!

首先,我想指出CSI不仅仅是一个卷插件,而是一个真正的 标准 创建用于数据仓库的自定义组件。 Kubernetes 和 Mesos 等容器编排系统应该“学习”如何使用根据此标准实现的组件。 现在我已经学会了 Kubernetes。

Kubernetes 中的 CSI 插件的结构是怎样的? CSI 插件与特殊驱动程序配合使用(CSI 驱动程序)由第三方开发人员编写。 Kubernetes 中的 CSI 驱动程序至少应包含两个组件(pod):

  • 控制器 — 管理外部持久存储。 它被实现为 gRPC 服务器,并使用原语 StatefulSet.
  • Node — 负责将持久存储安装到集群节点。 它也被实现为 gRPC 服务器,但它使用原语 DaemonSet.

Kubernetes 存储的卷插件:从 Flexvolume 到 CSI
CSI 插件如何在 Kubernetes 中工作

您可以了解 CSI 工作的其他一些细节,例如,从文章“了解 C.S.I.其中的翻译 我们一年前出版了。

这种实施方式的优点

  • 对于诸如为节点注册驱动程序之类的基本操作,Kubernetes 开发人员实现了一组容器。 您不再需要像 Flexvolume 插件那样自己生成具有功能的 JSON 响应。
  • 我们现在将 Pod 上传到集群,而不是将可执行文件“滑动”到节点上。 这是我们最初对 Kubernetes 的期望:所有进程都发生在使用 Kubernetes 原语部署的容器内。
  • 您不再需要开发 RPC 服务器和 RPC 客户端来实现复杂的驱动程序。 该客户端是由 Kubernetes 开发人员为我们实现的。
  • 通过 gRPC 协议传递参数比通过命令行参数传递参数更加方便、灵活和可靠。 要了解如何通过添加标准化 gRPC 方法来向 CSI 添加对卷使用指标的支持,您可以阅读: 我们的拉取请求 对于 vsphere-csi 驱动程序。
  • 通信通过 IPC 套接字进行,以免混淆 kubelet 是否将请求发送到正确的 pod。

这份清单让你想起什么了吗? CSI的优点是 解决同样的问题,开发 Flexvolume 插件时没有考虑到这一点。

发现

CSI 作为实现与数据仓库交互的自定义​​插件的标准受到了社区的热烈欢迎。 此外,由于其优势和多功能性,甚至为 Ceph 或 AWS EBS 等存储系统创建了 CSI 驱动程序,在 Kubernetes 的第一个版本中添加了用于使用这些系统的插件。

2019年初,树内插件 已被宣布过时。 我们计划继续支持 Flexvolume 插件,但不会为其开发新功能。

我们自己已经拥有使用 ceph-csi、vsphere-csi 的经验,并准备将其添加到此列表中! 到目前为止,CSI 正在出色地处理分配给它的任务,但我们将拭目以待。

不要忘记,一切新事物都是对旧事物的良好反思!

PS

另请阅读我们的博客:

来源: habr.com

添加评论