k8s中應用GlusterFS型別StorageClass

vfanCloud 發表於 2022-05-12
k8s

GlusterFS在Kubernetes中的應用

GlusterFS服務簡介

GlusterFS是一個可擴充套件,分散式檔案系統,整合來自多臺伺服器上的磁碟儲存資源到單一全域性名稱空間,以提供共享檔案儲存。

特點:

  • 可以擴充套件到幾PB容量
  • 支援處理數千個客戶端
  • 相容POSIX介面
  • 使用通用硬體,普通伺服器即可構建
  • 能夠使用支援擴充套件屬性的檔案系統,例如ext4,XFS
  • 支援工業標準的協議,例如NFS,SMB
  • 提供很多高階功能,例如副本,配額,跨地域複製,快照以及bitrot檢測
  • 支援根據不同工作負載進行調優

GFS儲存的一些術語:

  • Brick:GlusterFS中的儲存單元,通常是一個受信儲存池中的伺服器的一個目錄。可以通過主機名和目錄名來標識,如'SERVER:EXPORT'。
  • Node:一個擁有若干brick的裝置。
  • Volume:一組bricks的邏輯集合。
  • Client:掛載了GlusterFS卷的裝置。
  • GFID:GlusterFS卷中的每個檔案或目錄都有一個唯一的128位的資料相關聯,其用於模擬inode
    Namespace:每個Gluster卷都匯出單個ns作為POSIX的掛載點。
  • RDMA:遠端直接記憶體訪問,支援不通過雙方的OS進行直接記憶體訪問。
  • RRDNS:round robin DNS是一種通過DNS輪轉返回不同的裝置以進行負載均衡的方法
  • Self-heal:用於後臺執行檢測複本卷中檔案和目錄的不一致性並解決這些不一致。
  • Split-brain:腦裂
  • Volfile:Glusterfs程式的配置檔案,通常位於/var/lib/glusterd/vols/volname

GFS卷(volume)的模式:

Volume是一組Brick的組合,一個gfs叢集中可以有多個volume,可以供客戶端掛載、儲存資料等,它有很多種模式供選擇:

  1. 分散式卷(預設模式):即DHT,檔案通過hash演算法分佈存在不同的brick裡,單個brick失效會帶來資料丟失,無需額外後設資料伺服器。
  2. 條帶卷:即Striped,檔案切分成一個個的chunk,存放於不同的brick上,只建議在非常大的檔案時使用。
  3. 複製卷:即AFR,同一份資料會同步在多個Brick中,單一節點故障時保持資料高可用,事務性操作,保持一致性。
  4. 分散式複製卷:即AFR和DHT的組合,最少需要4臺伺服器才能建立,讀操作可以做到負載均衡。
  5. 條帶複製卷:Striped與AFR 的組合,最少需要4臺伺服器才能建立。
  6. 分散式條帶複製卷:至少需要8臺伺服器才能建立,每四臺伺服器一組。

GlusterFS服務搭建及程式解析

主機名 ip地址 角色
master 192.168.72.100 k8s-master、glusterfs、heketi
node1 192.168.72.101 k8s-work節點
node2 192.168.72.102 k8s-work節點
  1. 獲取gluster rpm包,我這裡是7.5版本的,需要下載以下幾個rpm包:

    # ls
    glusterfs-7.5-1.el7.x86_64.rpm                 glusterfs-libs-7.5-1.el7.x86_64.rpm
    glusterfs-api-7.5-1.el7.x86_64.rpm             glusterfs-server-7.5-1.el7.x86_64.rpm
    glusterfs-api-devel-7.5-1.el7.x86_64.rpm       psmisc-22.20-15.el7.x86_64.rpm
    glusterfs-cli-7.5-1.el7.x86_64.rpm             userspace-rcu-0.10.0-3.el7.x86_64.rpm
    glusterfs-client-xlators-7.5-1.el7.x86_64.rpm  userspace-rcu-0.7.16-1.el7.x86_64.rpm
    glusterfs-fuse-7.5-1.el7.x86_64.rpm
    

    地址:https://buildlogs.centos.org/centos/7/storage/x86_64/gluster-9/Packages/;如果有外網可以直接配置yum源安裝需要的版本。

  2. 安裝rpm包

    ## 先安裝依賴的環境包
    yum -y install attr psmisc rpcbind
    
    ## 安裝glusterfs
    rpm -ivh glusterfs-libs-7.5-1.el7.x86_64.rpm 
    rpm -ivh glusterfs-7.5-1.el7.x86_64.rpm
    rpm -ivh glusterfs-api-7.5-1.el7.x86_64.rpm  glusterfs-cli-7.5-1.el7.x86_64.rpm  glusterfs-client-xlators-7.5-1.el7.x86_64.rpm
    rpm -ivh glusterfs-fuse-7.5-1.el7.x86_64.rpm
    rpm -ivh userspace-rcu-0.10.0-3.el7.x86_64.rpm
    rpm -ivh glusterfs-server-7.5-1.el7.x86_64.rpm
    rpm -ivh glusterfs-api-devel-7.5-1.el7.x86_64.rpm
    

    因為包之間會有依賴關係,按照以上順序可以順利安裝,如果不想這麼麻煩,可強制安裝--force --nodeps。

  3. 啟動服務

    ## 需要啟動兩個服務glusterd和glusterfsd
    systemctl start glusterd glusterfsd
    
  4. glusterd和glusterfsd兩個服務的區別,以及gluster和glusterfs命令

    glusterd是其服務的守護程式,是一個管理模組,處理gluster發過來的命令,處理叢集管理、儲存池管理、brick管理、負載均衡、快照管理等,預設埠24007:

    # ss -tnlp | grep 24007
    LISTEN     0      128          *:24007                    *:*                   users:(("glusterd",pid=7873,fd=10))
    
    ## 程式
    # ps -ef | grep gluster
    root       7873      1  0 Mar28 ?        00:00:41 /usr/sbin/glusterd -p /var/run/glusterd.pid --log-level INFO
    

    glusterfsd是服務端模組,儲存池中的每個brick(卷組)都會啟動一個glusterfsd程式。此模組主要是處理客戶端的讀寫請求,預設埠是從49152開始,後續埠都+1。我這裡啟動了兩個卷組:

    # ss -tnlp | grep gluster
    LISTEN     0      128          *:49152                    *:*                   users:(("glusterfsd",pid=7898,fd=11))
    LISTEN     0      128          *:49153                    *:*                   users:(("glusterfsd",pid=44275,fd=11))
    
    ## 程式
    # ps -ef | grep gluster
    root       7898      1  0 Mar28 ?        00:04:46 /usr/sbin/glusterfsd -s 192.168.72.100 --volfile-id xtest-fs.192.168.72.100.data-text-mind -p /var/run/gluster/vols/xtest-fs/192.168.72.100-data-text-mind.pid -S /var/run/gluster/3b5c03678c749dfd.socket --brick-name /data/text-mind -l /var/log/glusterfs/bricks/data-text-mind.log --xlator-option *-posix.glusterd-uuid=46bfd5ab-07be-4a97-993f-1ddab13e0ee9 --process-name brick --brick-port 49152 --xlator-option xtest-fs-server.listen-port=49152
    

    glusterfs命令是一個客戶端模組,負責通過mount掛載叢集中某臺伺服器的儲存池,以目錄的形式呈現給使用者。使用者mount幾個卷組,就是出現幾個glusterfs的程式,如下:

    # ps -ef | grep -w glusterfs | grep -v glusterfsd
    root      31496      1  0 Mar25 ?        00:02:46 /usr/sbin/glusterfs --process-name fuse --volfile-server=yq01-aip-aikefu10.yq01.baidu.com --volfile-id=xtest-fs /data/mnt
    

    gluster命令就是客戶端請求命令,負責發起請求,比如建立、檢視當前卷組等:

    # gluster volume create xtest-fs 192.168.72.100:/data/xtest-fs/brick force
    # gluster volume list 
    
  5. 如果是多臺gfs構成叢集,在服務安裝完畢之後,要進行一個新增的動作

    ## 新增
    # gluster peer probe node1
    # gluster peer probe node2
    
    ## 檢視當前叢集
    # gluster pool list
    

heketi服務搭建以及管理GFS

heketi提供一個RESTful管理節點介面,可以用來管理GlusterFS卷的生命週期,通過heketi,就可以像使用Opentack Manila,kubernete和openShift一樣申請可以動態配置GlusterFS卷,Heketi會動態在叢集內選擇bricks構建所需的volumes,這樣以確保資料的副本會分散到叢集不同的故障域內。

  1. 安裝heketi服務,有源可以直接yum安裝,沒有可以單獨下載rpm包安裝,或者通過docker、k8s安裝

    # yum -y install heketi heketi-client
    
    # heketi --version 
    Heketi 9.0.0
    
    # systemctl start heketi.service
    
  2. 配置ssh祕鑰,使heketi節點可以免密訪問glusterfs節點

    # ssh-keygen -f /etc/heketi/heketi_key -t rsa -N ''
    # ssh-copy-id -i /etc/heketi/heketi_key.pub master
    # chown heketi:heketi /etc/heketi/
    
  3. 編輯heketi的配置檔案/etc/heketi/heketi.json

    heketi有三種執行方式,分別為mock,ssh,kubernetes,官方建議在測試和開發環境使用mock,生產使用ssh,如果glusterfs是部署在k8s上,則使用kubernetes方式。

    下邊對配置部分做出解釋:

    {
      ## heketi服務的預設埠,可以更改。
      "_port_comment": "Heketi Server Port Number",
      "port": "8080",
      
      ## 是否開啟認證,一般選是。
      "_use_auth": "Enable JWT authorization. Please enable for deployment",
      "use_auth": true,
    
      ## 若開啟認證,則需要編輯認證的使用者及密碼。
      "_jwt": "Private keys for access",
      "jwt": {
        "_admin": "Admin has access to all APIs",
        "admin": {
          "key": "My Secret"
        },
        "_user": "User only has access to /volumes endpoint",
        "user": {
          "key": "My Secret"
        }
      },
    
      "_glusterfs_comment": "GlusterFS Configuration",
      "glusterfs": {
        "_executor_comment": [
          "Execute plugin. Possible choices: mock, ssh",
          "mock: This setting is used for testing and development.",
          "      It will not send commands to any node.",
          "ssh:  This setting will notify Heketi to ssh to the nodes.",
          "      It will need the values in sshexec to be configured.",
          "kubernetes: Communicate with GlusterFS containers over",
          "            Kubernetes exec api."
        ],
        ## 選擇操作gfs的方式,這裡選擇ssh。
        "executor": "ssh",
    
        ## 配置ssh祕鑰
        "_sshexec_comment": "SSH username and private key file information",
        "sshexec": {
          "keyfile": "/etc/heketi/heketi_key",
          "user": "root",
          "port": "22",
          "fstab": "/etc/fstab"
        },
    
        ## 配置k8s證照
        "_kubeexec_comment": "Kubernetes configuration",
        "kubeexec": {
          "host" :"https://kubernetes.host:8443",
          "cert" : "/path/to/crt.file",
          "insecure": false,
          "user": "kubernetes username",
          "password": "password for kubernetes user",
          "namespace": "OpenShift project or Kubernetes namespace",
          "fstab": "Optional: Specify fstab file on node.  Default is /etc/fstab"
        },
    
        "_db_comment": "Database file name",
        ## heketi的資料儲存位置
        "db": "/var/lib/heketi/heketi.db",
    
        "_loglevel_comment": [
          "Set log level. Choices are:",
          "  none, critical, error, warning, info, debug",
          "Default is warning"
        ],
        ## heketi的日誌級別
        "loglevel" : "debug"
      }
    }
    
  4. 啟動heketi服務,設定開啟自啟,測試訪問

    # systemctl start heketi
    # systemctl enable heketi
    # curl http://master:8080/hello
    Hello from Heketi
    
  5. 配置heketi連線gfs服務

    (1) 在叢集的主節點設定環境變數,注意埠根據實際情況修改

    # export HEKETI_CLI_SERVER=http://master:8080
    

    (2) 在叢集的主節點修改/usr/share/heketi/topology-sample.json配置檔案,執行新增節點和新增device的操作:

    {
        "clusters": [
            {
                "nodes": [
                    {
                        "node": {
                            "hostnames": {
                                "manage": [
                                    "master"
                                ],
                                "storage": [
                                    "192.168.72.100"
                                ]
                            },
                            "zone": 1
                        },
                        "devices": [
                            {
                                "name": "/dev/sdb",
                                "destroydata": false
                            }
                        ]
                    }
                ]
            }
        ]
    }
    

    manage指定gfs主機域名,storage指定gfs主機ip,devices下指定要儲存gfs資料的資料盤,此盤應該是一塊未分割槽的乾淨盤,分割槽應該也可以,我這裡沒有試。

    可以新增多個主機和多塊盤,具體格式如下:

    {
        "clusters": [
            {
                "nodes": [
                    {
                        "node": {
                            "hostnames": {
                                "manage": [
                                    "192.168.10.100"
                                ],
                                "storage": [
                                    "192.168.10.100"
                                ]
                            },
                            "zone": 1
                        },
                        "devices": [
                            {
                                "name": "/dev/sdb",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdc",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdd",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sde",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdf",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdg",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdh",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdi",
                                "destroydata": false
                            }
                        ]
                    },
                    {
                        "node": {
                            "hostnames": {
                                "manage": [
                                    "192.168.10.101"
                                ],
                                "storage": [
                                    "192.168.10.101"
                                ]
                            },
                            "zone": 2
                        },
                        "devices": [
                            {
                                "name": "/dev/sdb",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdc",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdd",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sde",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdf",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdg",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdh",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdi",
                                "destroydata": false
                            }
                        ]
                    },
                    {
                        "node": {
                            "hostnames": {
                                "manage": [
                                    "192.168.10.102"
                                ],
                                "storage": [
                                    "192.168.10.102"
                                ]
                            },
                            "zone": 1
                        },
                        "devices": [
                            {
                                "name": "/dev/sdb",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdc",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdd",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sde",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdf",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdg",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdh",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdi",
                                "destroydata": false
                            }
                        ]
                    },
                    {
                        "node": {
                            "hostnames": {
                                "manage": [
                                    "192.168.10.103"
                                ],
                                "storage": [
                                    "192.168.10.103"
                                ]
                            },
                            "zone": 2
                        },
                        "devices": [
                            {
                                "name": "/dev/sdb",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdc",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdd",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sde",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdf",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdg",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdh",
                                "destroydata": false
                            },
                            {
                                "name": "/dev/sdi",
                                "destroydata": false
                            }
                        ]
                    }
                ]
            }
        ]
    }
    

    (3) 匯入配置檔案到heketi,使heketi管理gfs

    # heketi-cli -s $HEKETI_CLI_SERVER --user admin --secret 'My Secret' topology load --json=/usr/share/heketi/topology-sample.json
    Creating cluster ... ID: 6b7b8affdb10add55ea367e7e2f8e091
    	Allowing file volumes on cluster.
    	Allowing block volumes on cluster.
    	Creating node master ... ID: 96bbfbf66bc8ad551fe5a8c8ef289218
    		Adding device /dev/sdb ... OK
    

    如果報錯:Error: Unable to get topology information: Invalid JWT token: Token missing iss claim。是因為沒加使用者名稱和密碼 --user admin --secret 'My Secret'。

    (4) 測試建立一個volume,看是否成功

    # heketi-cli volume create --size=10 --durability=none --user "admin" --secret "My Secret"
    Name: vol_38412746b79cf0d7ffa69a3969c1cfed
    Size: 10
    Volume Id: 38412746b79cf0d7ffa69a3969c1cfed
    Cluster Id: 6b7b8affdb10add55ea367e7e2f8e091
    Mount: 192.168.72.100:vol_38412746b79cf0d7ffa69a3969c1cfed
    Mount Options: backup-volfile-servers=
    Block: false
    Free Size: 0
    Reserved Size: 0
    Block Hosting Restriction: (none)
    Block Volumes: []
    Durability Type: none
    Distribute Count: 1
    

    建立成功,heketi已經可以管理gfs服務。

k8s叢集建立StorageClass實現動態管理pv pvc

  1. 建立StorageClass

    vi storageclass-gfs-heketi-distributed.yaml

    apiVersion: storage.k8s.io/v1
    kind: StorageClass
    metadata:
      name: gluster-distributed
    provisioner: kubernetes.io/glusterfs
    reclaimPolicy: Retain
    parameters:
      resturl: "http://192.168.72.100:8080"
      restauthenabled: "true"
      restuser: "admin"
      restuserkey: "My Secret"
      gidMin: "40000"
      gidMax: "50000"
      volumetype: "none"
    allowVolumeExpansion: true
    volumeBindingMode: Immediate
    

    provisioner:表示儲存分配器,需要根據後端儲存的不同而變更;

    reclaimPolicy: 預設即“Delete”,刪除pvc後,相應的pv及後端的volume,brick(lvm)等一起刪除;設定為”Retain”時則保留資料,需要手工處理

    resturl:heketi API服務提供的url;

    restauthenabled:可選引數,預設值為"false",heketi服務開啟認證時必須設定為"true";

    restuser:可選引數,開啟認證時設定相應使用者名稱;

    restuserkey:可選引數。開啟認證時輸入對應使用者的密碼;

    secretNamespace:可選引數,開啟認證時可以設定為使用持久化儲存的namespace;

    secretName:可選引數,開啟認證時,需要將heketi服務的認證密碼儲存在secret資源中;

    clusterid:可選引數,指定叢集id,也可以是1個clusterid列表,格式為"id1,id2";

    volumetype:可選引數,設定卷型別及其引數,如果未分配卷型別,則有分配器決定卷型別;如"volumetype: replicate:3"表示3副本的replicate卷,"volumetype: disperse:4:2"表示disperse卷,其中‘4’是資料,’2’是冗餘校驗,"volumetype: none"表示distribute卷;

    allowVolumeExpansion:表示是否支援動態擴容,預設為true;

    volumeBindingMode:表示是否立即bound pv,可選值WaitForFirstConsumer和Immediate;

    建立sc:

    # kubectl create -f storageclass-gfs-heketi-distributed.yaml
    
    # kubectl get sc 
    NAME                   PROVISIONER                            RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION
    gluster-distributed    kubernetes.io/glusterfs                Retain          Immediate              true                
    
  2. 建立pvc

    vi glusterfs-pvc.yaml

    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: glusterfs-test
    spec:
      accessModes:
      - ReadWriteMany
      resources:
        requests:
          storage: 5Gi
      storageClassName: gluster-distributed
    

    建立pvc

    # kubectl create -f glusterfs-pvc.yaml
    # kubectl get pvc 
    NAME                                                                                           STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
    glusterfs-test                                                                                 Bound    pvc-0a3cffea-36b0-4609-a983-88df995b99e8   5Gi        RWX            gluster-distributed   5s
    

    這個時候 df -h 就可以看到有新的分割槽被掛載

    # df -h | tail -n 1 
    /dev/mapper/vg_5b4ef948902b85845afd387ca71c6858-brick_0fd0afa344ca798d7a9fccab5bb60a9e  5.0G   33M  5.0G    1% /var/lib/heketi/mounts/vg_5b4ef948902b85845afd387ca71c6858/brick_0fd0afa344ca798d7a9fccab5bb60a9e
    
  3. 建立一個deployment,測試讀寫

    vi glusterfs-deploy.yaml

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: glusterfs-deploy
      labels:
        app: nginx
    spec:
      replicas: 2
      selector:
        matchLabels:
          app: nginx
      template:
        metadata:
          labels:
            app: nginx
        spec:
          containers:
          - name: nginx
            image: registry:5000/nginx:1.13
            imagePullPolicy: IfNotPresent
            ports:
            - name: web
              containerPort: 80
            volumeMounts:
            - name: pvc-glusterfs
              mountPath: /data
          volumes:
          - name: pvc-glusterfs
            persistentVolumeClaim:
              claimName: glusterfs-test
    

    建立deployment

    # kubectl create -f glusterfs-deploy.yaml 
    deployment.apps/glusterfs-deploy created
    
    # kubectl get pod | grep glus
    glusterfs-deploy-cfd8c8578-45r9z                            1/1     Running   0          72s
    glusterfs-deploy-cfd8c8578-vjrzn                            1/1     Running   0          104s
    

    測試讀寫

    # kubectl exec -it glusterfs-deploy-cfd8c8578-45r9z -- bash
    # cd /data/
    # for i in `seq 1 10`; do echo $i > $i ; done 
    # ls
    1  10  2  3  4	5  6  7  8  9
    # cat 1 
    1
    

至此,gfs已經可以作為k8s的storageclass來動態管理pv pvc了。