基於openshift+華為對象存儲的CSI開發


需求來源

項目上目前使用的是openshift 3.11版本,對應kubernetes 1.11,需要在該平台上使用CSI插件。

GitHub地址:https://github.com/woodliu/csi-s3

環境准備

  • 本次使用openshift 3.11,對應kubernetes 1.11,參考github開源代碼ctrox/csi-s3進行開發(強烈建議使用的kubernetes不低於1.13)。

  • 部署中涉及如下sidecar容器:csi-attacher,csi-node-driver-registrar,csi-provisioner,csi-s3。最后一個容器是需要開發的CSI插件,其他sidecar版本需要與kubernetes配套才能使用,參照官方開發文檔找到合適的版本,例如符合kubernetes 1.11版本的csi-attacher版本為0.42。除csi-s3之外的鏡像都可以從官方鏡像庫中下載。

  • CSI spec規定了protobuf格式的存儲相關的數據結構,本次應該采用v0.3.0版本。按照官方說法,kubernetes1.13中廢棄了CSI spec 0.2和0.3版本。

    Kubernetes CSI Spec Compatibility Status
    v1.9 v0.1.0 Alpha
    v1.10 v0.2.0 Beta
    v1.11 v0.3.0 Beta
    v1.13 v0.3.0, v1.0.0 GA
  • 下載華為OBS對象存儲Go語言SDK。

  • docker 17.05+(使用multi-stage生成鏡像)

代碼修改

主要是使用華為OBS的SDK操作替換代碼中對bucket的操作等。涉及修改的代碼文件為pkg/s3/controllerserver.gonodeserver.gos3-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

修改部署文件

  • 修改storageclass

    ctrox/csi-s3目錄中的deploy/kubernetes/storageclass.yaml內容如下,mounter使用s3fs,由於僅使用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
  • 修改secret.yaml,特別注意endpoint字段不能省略httphttps

    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>
    
  • 修改csi-s3.yaml,將容器卷掛載地址修改為/var/lib/origin/openshift.local.volumes/pods/

  • 修改各個配置文件中的鏡像倉庫和版本

部署CSI插件

部署組件

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的核心原理比較簡單,見下圖。基本原理就是啟動一個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_NAMEPV_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 PodStatefulset/Deployment Pod ,前者負責各個Node節點上的卷的掛載;后者負責操作后端存儲並與API Server交互。

官方提供了兩個很好的文檔:Container Storage Interface (CSI)CSI Volume Plugins in Kubernetes Design Doc。前者給出了開發涉及的接口,后者給出了內部原理。

CSI需要實現3個RPC服務

在CSI執行前需要調用如下接口注冊對應的組件,各個組件需要實現對應的接口(見下文):

func (s *Server) RegisterService(sd *ServiceDesc, ss interface{})
func (s *Server) RegisterService(sd *ServiceDesc, ss interface{})
func (s *Server) RegisterService(sd *ServiceDesc, ss interface{})
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容器必須要部署。
  • NodeStageVolume/NodeUnstageVolume:由registrar調用,用於處理setup/teardown(初始化設置/清理環境)卷。
  • NodePublishVolume/NodeUnpublishVolume:由registrar調用,用於將卷掛載/卸載到應用容器中。registrar用到了兩個UNIX域套接字:
    • Registration socket:
      1. 創建reg.sock文件,用於將驅動注冊到kubelet
      2. 通過kubelet插件庫路徑(通常是/var/lib/kubelet/plugins_registry/<drivername.example.com>-reg.sock暴露服務
    • CSI driver socket:
      1. 創建sock文件,kubelet用該socket與CSI驅動交互
      2. 通過kubelet插件庫路徑(通常是/var/lib/kubelet/plugins/<drivername.example.com>/csi.sock)暴露服務。

NOTE

  • 本方式其實是將對象存儲轉化為文件系統的方式,理論上應該是存在性能損失的
  • 更多內容可以參見這篇文章

FAQ

  • pvc創建不成功。這個一般是對后端存儲的操作不正確導致的,需要檢查controllerserver.go中的代碼

  • pv和pvc綁定成功,但容器掛載不成功: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
    
  • pv和pvc綁定成功,且容器掛載成功,但對容器掛載目錄的變更無法同步到后端存儲。需要檢查對應node節點上的csi容器中的掛載點是否正確。一般是spec.template.spec.containers.csi-s3.volumeMounts.pods-mount-dirspec.template.spec.volumes.pods-mount-dir值不一致導致的

參考:


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM