k8s篇-存储插件(flexvolume与CSI)

什么是存储插件:

容器一旦被删除,在运行时容器内部产生的所有文件数据,也会随同容器销毁而一起被清理掉,所以提供了volume方式将容器产生的临时数据持久化保存下来。

在k8s中,可通过pod.spec.volumes属性,查看默认支持的各种类型的Volume:本地存储(emptyDir / hostPath)、网络存储(如NFS)等。

k8s还提供了插件机制,允许其他类型的外部存储服务以插件方式,接入到Kubernetes系统中来。

有两种接入方式:In-Tree(在k8s源码内部实现,随k8s一起发布,更新慢不灵活) 和 Out-Of-Tree(独立于k8s,目前主要有CSI和FlexVolume两种,CSI为主流)。

存储插件的功能,最终是为应用创建出对应的volume使用。

FlexVolume插件

Flexvolume方式:需要先在每个节点上,安装存储插件的执行脚本(或可执行文件),此脚本需要实现flexvolume的相关存储接口。

当运行一个POD应用,要执行mount动作时,k8s通过kubelet调用VolumePlugin,然后直接执行插件脚本来完成的,这个插件脚本可以是shell,也可以是二进制文件,只要实现了相关的存储接口功能就行。

插件执行脚本的默认存放路径为:/usr/libexec/kubernetes/kubelet-plugins/volume/exec/<vendor~driver>/<driver>

路径中vendor~driver名称,与Pod中flexVolume.driver字段值要匹配,如foo~cifs/cifs,对应pod的driver名称就是foo/cifs。

存储接口包括:

init: 初始化存储插件时调用
attach: 将存储卷挂载到Node节点上
detach: 将存储卷从Node上卸载
waitforattach: 等待attach操作成功
isattached: 检查存储卷是否已经挂载
mountdevice: 将设备挂载到指定目录中
unmountdevice: 将设备取消挂载
mount: 将存储卷挂载到指定目录中
unmount: 将存储卷取消挂载

类似NFS存储服务,无需实现 attach/detach 这些接口,只需要实现 init/mount/umount 这三个接口即可。

执行脚本中,实现这些接口,需要返回如下json数据:

{
    "status": "<Success/Failure/Not supported>",
    "message": "<Reason for success/failure>",
    "device": "<Path to the device attached. This field is valid only for attach & waitforattach call-outs>"
    "volumeName": "<Cluster wide unique name of the volume. Valid only for getvolumename call-out>"
    "attached": <True/False (Return true if volume is attached on the node. Valid only for isattached call-out)>
    "capabilities": <Only included as part of the Init response>
    {
        "attach": <True/False (Return true if the driver implements attach and detach)>
    }
}

部署案例:NFS FlexVolume 存储插件

1、到官方下载一个 NFS 的 FlexVolume 插件示例(复制脚本内容,保存到文件,并命名为nfs)

https://github.com/kubernetes/examples/blob/master/staging/volumes/flexvolume/nfs

 nfs脚本内容如下:

#!/bin/bash
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

        local NFS_SERVER=$(echo $2 | jq -r '.server')
        local SHARE=$(echo $2 | jq -r '.share')
        local PROTOCOL=$(echo $2 | jq -r '.protocol')
        local ATIME=$(echo $2 | jq -r '.atime')
        local READONLY=$(echo $2 | jq -r '.readonly')

        if [ -n "${PROTOCOL}" ]; then
                PROTOCOL="tcp"
        fi

        if [ -n "${ATIME}" ]; then
                ATIME="0"
        fi

        if [ -n "${READONLY}" ]; then
                READONLY="0"
        fi

        if [ "${PROTOCOL}" != "tcp" ] && [ "${PROTOCOL}" != "udp" ] ; then
                err "{ \"status\": \"Failure\", \"message\": \"Invalid protocol ${PROTOCOL}\"}"
                exit 1
        fi

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

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

        local NFSOPTS="${PROTOCOL},_netdev,soft,timeo=10,intr"
        if [ "${ATIME}" == "0" ]; then
                NFSOPTS="${NFSOPTS},noatime"
        fi

        if [ "${READONLY}" != "0" ]; then
                NFSOPTS="${NFSOPTS},ro"
        fi

        mount -t nfs -o${NFSOPTS} ${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 ! command -v jq >/dev/null 2>&1; then
        err "{ \"status\": \"Failure\", \"message\": \"'jq' binary not found. Please install jq package before using this driver\"}"
        exit 1
fi

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

2、将nfs脚本文件放到节点的插件目录下,并设置权限为700

mkdir /usr/libexec/kubernetes/kubelet-plugins/volume/exec/ydzs~nfs
mv nfs /usr/libexec/kubernetes/kubelet-plugins/volume/exec/ydzs~nfs
chmod 700 /usr/libexec/kubernetes/kubelet-plugins/volume/exec/ydzs~nfs/nfs

3、创建测试Pod,并用自定义flexVolume插件来持久化容器中的数据

apiVersion: v1
kind: Pod
metadata:
  name: pod-flex-demo
  namespace: default
spec:
  containers:
  - name: busybox
    image: busybox
    command: [ "/bin/sh", "-c", "while true; do echo $(date) >> /data/flex_testfile; sleep 2; done" ]
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: test
      mountPath: /data
  volumes:
  - name: test
    flexVolume:
      driver: "ydzs/nfs"
      fsType: "nfs"
      options:
        server: xxx.xxx.xxx.xxx    #NFS服务器IP
        share: /data/volumes/v3

4、创建好pod后,再进入到pod容器,往里面写入数据后,查看NFS服务器上/data/volumes/v3目录下是否存在刚刚创建的数据

CSI插件

CSI存储架构由两部组成:External Components(即k8s外部组件,从k8s中存储体系中剥离出来的存储管理功能,与CSI插件交互,由K8s团队维护) + Custom Components(即CSI存储插件,由开发者实现的部分)
 

External Components:

1. Driver Registrar:负责将CSI插件注册到kubelet里,需要请求CSI Identity服务来获取插件信息。
2. External Provisioner:负责volume的provision阶段。当监听到一个PVC被创建,会调用CSI Controller的CreateVolume方法,创建对应PV。
3. External Attacher:负责volume的Attach阶段。会调用CSI Controller的ControllerPublish方法来完成。

Custom Components:

1. CSI Identity:向外暴露CSI插件自身的信息。
2. CSI Controller:主要实现Volume管理流程当中的Provision和Attach阶段 --- provision指创建和删除Volume,attach指将volume附着到node或脱离某个node。
3. CSI Node:主要负责Volume管理流程当中的Mount阶段 --- 即把Volume挂载至Pod容器里或从pod卸载volume。

总结:

Volume的Mount阶段,不属于External Components的职责。当需要执行Mount操作时,会直接调用CSI Node服务完成Volume的Mount阶段。

由于External Components对CSI插件的调用非常频繁,所以在实际使用CSI插件时,会将这三个外部组件作为sidecar容器,与CSI插件放置在同一个Pod中。

整个集群只需要部署一个CSI Controller,以StatefulSet或deployment方式。每个节点需要部署一个CSI Node,以DaemonSet方式。

External provisioner、External attacher与CSI插件部署在同一个StatefulSet类型的POD中,Driver registrar与CSI插件部署在同一个DaemonSet 的 Pod 中。

FlexVolume对volume的处理流程有两个阶段:Attach 与 Mount。

相比于 FlexVolume,CSI 的设计思想,把插件的职责从“两阶段处理”,扩展成了Provision、Attach 和 Mount三个阶段。其中,Provision 等价于“创建磁盘”,Attach 等价于“挂载磁盘到节点”,Mount 等价于“将磁盘格式化后,挂载到 Volume 的宿主机目录上”。

部署案例:nfs csi插件

1、下载到本地安装

git clone https://github.com/kubernetes-csi/csi-driver-nfs.git
cd csi-driver-nfs/deploy

2、先修改两个yaml文件中仓库镜像源改为国内

sed -i 's@registry.k8s.io/sig-storage@registry.aliyuncs.com/google_containers@g' csi-nfs-controller.yaml
sed -i 's@registry.k8s.io/sig-storage@registry.aliyuncs.com/google_containers@g' csi-nfs-node.yaml
sed -i 's@gcr.io/k8s-staging-sig-storage/nfsplugin:canary@registry.cn-hangzhou.aliyuncs.com/imagesfromgoogle/nfsplugin:amd64-linux-canary@g' csi-nfs-controller.yaml
sed -i 's@gcr.io/k8s-staging-sig-storage/nfsplugin:canary@registry.cn-hangzhou.aliyuncs.com/imagesfromgoogle/nfsplugin:amd64-linux-canary@g' csi-nfs-node.yaml

3、执行本地安装

cd csi-driver-nfs && ./deploy/install-driver.sh master local

# 卸载
# cd csi-driver-nfs && ./deploy/uninstall-driver.sh master local

4、查看pod

kubectl -n kube-system get pod -o wide -l app=csi-nfs-controller
kubectl -n kube-system get pod -o wide -l app=csi-nfs-node

5、创建pv

使用静态方式创建:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-nfs-csi
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  csi:
    driver: nfs.csi.k8s.io
    readOnly: false
    volumeHandle: unique-volumeid 
    volumeAttributes:
      server: xxx.xxx.xxx.xxx
      share: /data/volumes/v3
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-nfs-csi
spec:
  volumeName: pv-nfs-csi            #手动指定要绑定的pv名称
  storageClassName: "" 				#若设置了默认的storageclass,这里要显式设置为空
  accessModes: ["ReadWriteMany"]    
  resources:
    requests:
      storage: 1Gi

或用storageclass动态创建一个pv

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: nfs-csi
provisioner: nfs.csi.k8s.io        #为csi-nfs-driverinfo.yaml中定义的name
parameters:
  server: 116.196.98.49
  share: /data/volumes/v3
reclaimPolicy: Delete
volumeBindingMode: Immediate
mountOptions:
  - hard
  - nfsvers=4.1
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-nfs-csi-dynamic
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 2Gi
  storageClassName: nfs-csi

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值