需求来源
项目上目前使用的是openshift 3.11版本,对应kubernetes 1.11,需要在该平台上使用CSI插件。
GitHub地址:https://github.com/woodliu/csi-s3
主要是使用华为OBS的SDK操作替换代码中对bucket的操作等。涉及修改的代码文件为pkg/s3/controllerserver.go
,nodeserver.go
,s3-client.go
docker pull quay.io/k8scsi/csi-attacher:v0.4.2
docker pull quay.io/k8scsi/driver-registrar:v0.4.2
docker pull quay.io/k8scsi/csi-provisioner:v0.4.2
在/csi-s3
目录中执行如下命令可以在/csi-s3/_output/
目录中生成可执行文件s3driver
make build
在/csi-s3目录下执行如下命令生成镜像
make container
s3fsMounterType
类型,因此无需指定StorageClass.mounter
字段
kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: csi-s3 provisioner: ch.ctrox.csi.s3-driver parameters: # specify which mounter to use # can be set to rclone, s3fs, goofys or s3backer csiProvisionerSecretName: csi-s3-secret csiProvisionerSecretNamespace: kube-system csiControllerPublishSecretName: csi-s3-secret csiControllerPublishSecretNamespace: kube-system csiNodeStageSecretName: csi-s3-secret csiNodeStageSecretNamespace: kube-system csiNodePublishSecretName: csi-s3-secret csiNodePublishSecretNamespace: kube-system
其中parameters
字段的内容在1.0.1版本进行了修改,因此小于1.0版本的prvisioner采用左边的声明。
Deprecated Replacement csiProvisionerSecretName csi.storage.k8s.io/provisioner-secret-name csiProvisionerSecretNamespace csi.storage.k8s.io/provisioner-secret-namespace csiControllerPublishSecretName csi.storage.k8s.io/controller-publish-secret-name csiControllerPublishSecretNamespace csi.storage.k8s.io/controller-publish-secret-namespace csiNodeStageSecretName csi.storage.k8s.io/node-stage-secret-name csiNodeStageSecretNamespace csi.storage.k8s.io/node-stage-secret-namespace csiNodePublishSecretName csi.storage.k8s.io/node-publish-secret-name csiNodePublishSecretNamespace csi.storage.k8s.io/node-publish-secret-namespace fstype csi.storage.k8s.io/fstype endpoint
字段不能省略http
或https
apiVersion: v1 kind: Secret metadata: name: csi-s3-secret stringData: accessKeyID: ${AK} secretAccessKey: ${SK} # For AWS set it to "https://s3.<region>.amazonaws.com" endpoint: http://obs.${mycloud}.com # If not on S3, set it to "" region: <S3_REGION>
/var/lib/origin/openshift.local.volumes/pods/
部署组件
cd deploy/kubernetes
kubectl create -f provisioner.yaml
kubectl create -f attacher.yaml
kubectl create -f csi-s3.yaml
部署storageclass
kubectl create -f storageclass.yaml
测试,创建一个pvc
kubectl create -f pvc.yaml
查看该pvc是否已经绑定
# oc get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
csi-s3-pvc Bound pvc-ea844fa4-6f64-11ea-8eab-fa163e07eb1d 5Gi RWO csi-s3 30m
创建一个pod,查看该pod是否运行成功,并在该pod挂载的目录/var/lib/www/html
下创建文件,看对象存储那端是否有新的文件生成。
oc create -f pod.yaml
CSI的核心原理比较简单,见下图。基本原理就是启动一个CSI容器(自己写的插件),将Node主机上的/var/lib/origin/openshift.local.volumes/pods/
目录(该目录包含所有Pod的卷挂载点)挂载到CSI容器内部的/var/lib/origin/openshift.local.volumes/pods/
目录下。然后调用s3fs命令将对象存储挂载到/var/lib/origin/openshift.local.volumes/pods/${POD_ID}/volumes/kubernetes.io~csi/${PV_NAME}/mount
目录下,而/var/lib/origin/openshift.local.volumes/pods/${POD_ID}/volumes/kubernetes.io~csi/${PV_NAME}/mount
就是对应容器挂载PVC对应的node节点上的挂载点。对象存储的BUCKET_NAME
与PV_NAME
相同。
总之整个过程涉及三次挂载:将应用容器在Node节点上的目录挂载到CSI容器中;将后端存储挂载到CSI容器中;将应用容器在Node节点上的目录挂载到应用容器中。
s3fs ${BUCKET_NAME}:/csi-fs /var/lib/origin/openshift.local.volumes/pods/${POD_ID}/volumes/kubernetes.io~csi/${PV_NAME}/mount -o use_path_request_style -o url=http://obs.${mycloud}.com -o allow_other -o mp_umask=000
CSI插件的运作流程需要符合卷的生命周期特性,官方给出的生命周期如下:
CreateVolume +------------+ DeleteVolume
+------------->| CREATED +--------------+
| +---+----^---+ |
| Controller | | Controller v
+++ Publish | | Unpublish +++
|X| Volume | | Volume | |
+-+ +---v----+---+ +-+
| NODE_READY |
+---+----^---+
Node | | Node
Publish | | Unpublish
Volume | | Volume
+---v----+---+
| PUBLISHED |
+------------+
Figure 5: The lifecycle of a dynamically provisioned volume, from
creation to destruction.
CreateVolume +------------+ DeleteVolume
+------------->| CREATED +--------------+
| +---+----^---+ |
| Controller | | Controller v
+++ Publish | | Unpublish +++
|X| Volume | | Volume | |
+-+ +---v----+---+ +-+
| NODE_READY |
+---+----^---+
Node | | Node
Stage | | Unstage
Volume | | Volume
+---v----+---+
| VOL_READY |
+---+----^---+
Node | | Node
Publish | | Unpublish
Volume | | Volume
+---v----+---+
| PUBLISHED |
+------------+
Figure 6: The lifecycle of a dynamically provisioned volume, from
creation to destruction, when the Node Plugin advertises the
STAGE_UNSTAGE_VOLUME capability.
Controller Controller
Publish Unpublish
Volume +------------+ Volume
+------------->+ NODE_READY +--------------+
| +---+----^---+ |
| Node | | Node v
+++ Publish | | Unpublish +++
|X| <-+ Volume | | Volume | |
+++ | +---v----+---+ +-+
| | | PUBLISHED |
| | +------------+
+----+
Validate
Volume
Capabilities
Figure 7: The lifecycle of a pre-provisioned volume that requires
controller to publish to a node (`ControllerPublishVolume`) prior to
publishing on the node (`NodePublishVolume`).
+-+ +-+
|X| | |
+++ +^+
| |
Node | | Node
Publish | | Unpublish
Volume | | Volume
+---v----+---+
| PUBLISHED |
+------------+
Figure 8: Plugins MAY forego other lifecycle steps by contraindicating
them via the capabilities API. Interactions with the volumes of such
plugins is reduced to `NodePublishVolume` and `NodeUnpublishVolume`
calls.
可以看到,并不需要实现controller(见下)中的所有功能,只需按照需要实现即可。
官方架构如下,主要分为两部分:DeamonSet Pod
和Statefulset/Deployment Pod
,前者负责各个Node节点上的卷的挂载;后者负责操作后端存储并与API Server交互。
官方提供了两个很好的文档:Container Storage Interface (CSI)和CSI Volume Plugins in Kubernetes Design Doc。前者给出了开发涉及的接口,后者给出了内部原理。
CSI需要实现3个RPC服务
service Identity {
rpc GetPluginInfo(GetPluginInfoRequest)
returns (GetPluginInfoResponse) {}
rpc GetPluginCapabilities(GetPluginCapabilitiesRequest)
returns (GetPluginCapabilitiesResponse) {}
rpc Probe (ProbeRequest)
returns (ProbeResponse) {}
}
service Controller {
rpc CreateVolume (CreateVolumeRequest)
returns (CreateVolumeResponse) {}
rpc DeleteVolume (DeleteVolumeRequest)
returns (DeleteVolumeResponse) {}
rpc ControllerPublishVolume (ControllerPublishVolumeRequest)
returns (ControllerPublishVolumeResponse) {}
rpc ControllerUnpublishVolume (ControllerUnpublishVolumeRequest)
returns (ControllerUnpublishVolumeResponse) {}
rpc ValidateVolumeCapabilities (ValidateVolumeCapabilitiesRequest)
returns (ValidateVolumeCapabilitiesResponse) {}
rpc ListVolumes (ListVolumesRequest)
returns (ListVolumesResponse) {}
rpc GetCapacity (GetCapacityRequest)
returns (GetCapacityResponse) {}
rpc ControllerGetCapabilities (ControllerGetCapabilitiesRequest)
returns (ControllerGetCapabilitiesResponse) {}
rpc CreateSnapshot (CreateSnapshotRequest)
returns (CreateSnapshotResponse) {}
rpc DeleteSnapshot (DeleteSnapshotRequest)
returns (DeleteSnapshotResponse) {}
rpc ListSnapshots (ListSnapshotsRequest)
returns (ListSnapshotsResponse) {}
rpc ControllerExpandVolume (ControllerExpandVolumeRequest)
returns (ControllerExpandVolumeResponse) {}
}
service Node {
rpc NodeStageVolume (NodeStageVolumeRequest)
returns (NodeStageVolumeResponse) {}
rpc NodeUnstageVolume (NodeUnstageVolumeRequest)
returns (NodeUnstageVolumeResponse) {}
rpc NodePublishVolume (NodePublishVolumeRequest)
returns (NodePublishVolumeResponse) {}
rpc NodeUnpublishVolume (NodeUnpublishVolumeRequest)
returns (NodeUnpublishVolumeResponse) {}
rpc NodeGetVolumeStats (NodeGetVolumeStatsRequest)
returns (NodeGetVolumeStatsResponse) {}
rpc NodeExpandVolume(NodeExpandVolumeRequest)
returns (NodeExpandVolumeResponse) {}
rpc NodeGetCapabilities (NodeGetCapabilitiesRequest)
returns (NodeGetCapabilitiesResponse) {}
rpc NodeGetInfo (NodeGetInfoRequest)
returns (NodeGetInfoResponse) {}
}
下看主要看一下controller中涉及的主要功能:
CreateVolume
/DeleteVolume
:由provisioner调用,负责后端存储上卷的创建/删除,如对象存储上bucket的创建/删除。provisioner会监控用户创建/删除的PVC,进而操作PVC指定的storageclass。ControllerPublishVolumeResponse
/ControllerUnpublishVolume
:由attacher调用,负责将远端卷挂载到node节点上。本次实现中,远端卷直接使用s3fs挂载到了csi容器中,因此没有实现该接口,即没有指定ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME
。*注:虽然没有实现该接口,但attacher容器必须要部署。registrar
用到了两个UNIX域套接字: /var/lib/kubelet/plugins_registry/<drivername.example.com>-reg.sock
暴露服务/var/lib/kubelet/plugins/<drivername.example.com>/csi.sock)
暴露服务。transport endpoint is not connected
,可以在对应节点的daemonset pod中csi容器中执行如下命令调试,一般是s3fs命令格式不对导致的。
echo ${AK}:${SK} > ${passwd_file} s3fs ${bucket_name} ${target_mount_path} -o url=http://obs.${mycloud}.com -o passwd_file=${passwd} -o dbglevel=info -f -o curldbg
spec.template.spec.containers.csi-s3.volumeMounts.pods-mount-dir
和spec.template.spec.volumes.pods-mount-dir
值不一致导致的