1. 程式人生 > >關於 Kubernetes 中的 Volume 與 GlusterFS 分散式儲存

關於 Kubernetes 中的 Volume 與 GlusterFS 分散式儲存

容器中持久化的檔案生命週期是短暫的,如果容器中程式崩潰宕機,kubelet 就會重新啟動,容器中的檔案將會丟失,所以對於有狀態的應用容器中持久化儲存是至關重要的一個環節;另外很多時候一個 Pod 中可能包含多個 Docker 映象,在 Pod 內資料也需要相互共享,Kubernetes  中 Pod 也可以增加副本數量,遇到故障時 Pod 可以轉移到其它節點,為了浮動節點都能夠訪問統一的持久化儲存以及容器間共享資料,Kubernetes 中定義了 Volume 來解決這些問題 ,從本質上講,Volume 只是一個目錄,可能包含一些資料,Pod 中的容器可以訪問它。該目錄是何種形式,是由所使用的 Volume 型別決定的。

Volume 資料卷型別

如果使用公有云,根據不同的雲廠商提供的服務可以選擇如下型別

  • awsElasticBlockStore
  • azureDisk
  • azureFile

以下介紹一些常用的型別

emptyDir

一般適用與臨時檔案場景,如上傳圖片執行時生成的流檔案,Pod 中的容器都能夠完全讀寫,但是 Pod 如果被移除,資料也就被刪除,容器宕機不會刪除 Pod ,因此不會造成資料丟失。要使用 Volume ,Pod 中需要使用.spec.volumes 配置定義型別,然後使用 .spec.containers.volumeMounts 配置定義掛載的資訊。

apiVersion: v1
kind: Pod
metadata:
  name: test
-pd spec: containers: - image: k8s.gcr.io/test-webserver name: test-container volumeMounts: - mountPath: /cache name: cache-volume volumes: - name: cache-volume emptyDir: {}

hostPath

hostPath Volume 為 Pod 掛載宿主機上的目錄或檔案,使得容器可以使用宿主機的高速檔案系統進行儲存。缺點是,Pod 是動態在各個節點上排程。當一個 Pod 在當前節點上啟動並通過 hostPath儲存了檔案到本地以後,下次排程到另一個節點上啟動時,就無法使用在之前節點上儲存的檔案。

apiVersion: v1
kind: Pod
metadata:
  name: test-pod
spec:
  containers:
  - image: test-container
    name: test-name
    volumeMounts:
    - name: test-volume
      mountPath: /cache
  volumes:
  - name: test-volume
    hostPath:
      path: /data

nfs

我們前面使用的 hostPath 和 emptyDir 型別的 Volume 有可能被 kubelet 清理掉,也不能被“遷移”到其他節點上,不具備持久化特性。 NFS(網路檔案系統)服務需要搭建好,共享到 Pod 中,與  emptyDir 移除 Pod 時刪除的內容不同,NFS 卷的內容將被保留,下次執行 Pod 可以繼續使用,對於一些 IO 與網路要求不高的的場景可以使用。

apiVersion: v1
kind: PersistentVolume
metadata:
  name: test--nfs-pv
spec:
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteMany
  flexVolume:
    driver: "k8s/nfs"
    fsType: "nfs"
    options:
      server: "192.168.10.100" #  NFS 伺服器地址
      path: "/"

cephfs

Cephfs 是一個分散式儲存系統,誕生於2004年,最早致力於開發下一代高效能分散式檔案系統的專案。提前是也需要提前搭建好儲存叢集服務,也可以使用 Rook (支援 Ceph),現屬於 CNCF 孵化專案。

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: fast
provisioner: kubernetes.io/rbd
parameters:
  monitors: 10.16.153.105:6789
  adminId: kube
  adminSecretName: ceph-secret
  adminSecretNamespace: kube-system
  pool: kube
  userId: kube
  userSecretName: ceph-secret-user
  userSecretNamespace: default
  fsType: ext4
  imageFormat: "2"
  imageFeatures: "layering"

glusterfs

GlusterFS 是一個開源的分散式檔案系統,具有強大的橫向擴充套件能力,通過擴充套件能夠支援數 PB 儲存容量和處理數千客戶端。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: slow
provisioner: kubernetes.io/glusterfs
parameters:
  resturl: "http://127.0.0.1:8081"
  clusterid: "630372ccdc720a92c681fb928f27b53f"
  restauthenabled: "true"
  restuser: "admin"
  secretNamespace: "default"
  secretName: "heketi-secret"
  gidMin: "40000"
  gidMax: "50000"
  volumetype: "replicate:3"

配置 GlusterFS

準備三臺伺服器

172.23.216.48   gfs_node_1
172.23.216.49   gfs_node_2
172.23.216.50   gfs_node_3

安裝 Glusterfs

$ yum install centos-release-gluster
$ yum install glusterfs-server
$ systemctl start glusterd.service
$ systemctl enable glusterd.service
$ systemctl status glusterd.service

建立儲存目錄

$ mkdir /opt/gfs_data

新增節點

$ gluster peer probe node2
$ gluster peer probe node3

檢視節點

$ gluster peer status
Number of Peers: 2

Hostname: 172.23.216.49
Uuid: 4dcfad42-e327-4a79-8a5a-a55dc92982ba
State: Peer in Cluster (Connected)

Hostname: 172.23.216.50
Uuid: 84e90bcf-af22-4cac-a6b1-e3e0d87d7eb4
State: Peer in Cluster (Connected)

建立資料卷(測試使用分散式模式,生產勿用)

# 複製模式
$ gluster volume create k8s-volume  replica 3 transport tcp gfs_node_1:/opt/gfs_data  gfs_node_2:/opt/gfs_data gfs_node_3:/opt/gfs_data  force
# 分佈卷(預設模式)
$ gluster volume create k8s-volume transport tcp 172.23.216.48:/opt/gfs_data 172.23.216.49:/opt/gfs_data 172.23.216.50:/opt/gfs_data force

備註:其他卷模式

啟動資料卷

$ gluster volume start k8s-volume
$ gluster volume info
Volume Name: k8s-volume
Type: Distribute
Volume ID: 1203a7ab-45c5-49f0-a920-cbbe8968fefa
Status: Started
Snapshot Count: 0
Number of Bricks: 3
Transport-type: tcp
Bricks:
Brick1: 172.23.216.48:/opt/gfs_data
Brick2: 172.23.216.49:/opt/gfs_data
Brick3: 172.23.216.50:/opt/gfs_data
Options Reconfigured:
transport.address-family: inet
nfs.disable: on

相關命令

#為儲存池新增/移除伺服器節點
$ gluster peer probe
$ gluster peer detach
$ gluster peer status
#建立/啟動/停止/刪除卷
$ gluster volume create [stripe | replica ] [transport [tcp | rdma | tcp,rdma]] ...
$ gluster volume start
$ gluster volume stop
$ gluster volume delete

注意,刪除卷的前提是先停止卷。

#檢視卷資訊
$ gluster volume list
$ gluster volume info [all]
$ gluster volume status [all]
$ gluster volume status [detail| clients | mem | inode | fd]

#檢視本節點的檔案系統資訊:

$ df -h []
#檢視本節點的磁碟資訊:
$ fdisk -l

Kubernetes 中配置 GlusterFS

修改如下:

glusterfs-endpoints

{
  "kind": "Endpoints",
  "apiVersion": "v1",
  "metadata": {
    "name": "glusterfs-cluster"
  },
  "subsets": [
    {
      "addresses": [
        {
          "ip": "172.23.216.48"
        }
      ],
      "ports": [
        {
          "port": 1000
        }
      ]
    },
    {
      "addresses": [
        {
          "ip": "172.23.216.49"
        }
      ],
      "ports": [
        {
          "port": 1000
        }
      ]
    },
    {
      "addresses": [
        {
          "ip": "172.23.216.50"
        }
      ],
      "ports": [
        {
          "port": 1000
        }
      ]
    }
  ]
}

glusterfs-service.json

{
  "kind": "Service",
  "apiVersion": "v1",
  "metadata": {
    "name": "glusterfs-cluster"
  },
  "spec": {
    "ports": [
      {"port": 1000}
    ]
  }
}

glusterfs-pod.json(建立測試 Pod)

{
    "apiVersion": "v1",
    "kind": "Pod",
    "metadata": {
        "name": "glusterfs"
    },
    "spec": {
        "containers": [
            {
                "name": "glusterfs",
                "image": "nginx",
                "volumeMounts": [
                    {
                        "mountPath": "/mnt/glusterfs",
                        "name": "glusterfsvol"
                    }
                ]
            }
        ],
        "volumes": [
            {
                "name": "glusterfsvol",
                "glusterfs": {
                    "endpoints": "glusterfs-cluster",
                    "path": "k8s-volume",
                    "readOnly": true
                }
            }
        ]
    }
}

依次執行

$ kubectl apply -f glusterfs-endpoints.json
$ kubectl get ep

$ kubectl apply -f glusterfs-service.json
$ kubectl get svc
# 檢視測試 Pod
$ kubectl apply -f glusterfs-pod.json
$ kubectl get pods 
$ kubectl describe pods/glusterfs
$ kubectl exec glusterfs -- mount | grep gluster

持久儲存卷(Persistent Volume,PV)和持久儲存卷宣告(Persistent Volume Claim,PVC)

在 Kubernetes 中還引入了一組叫作 Persistent Volume Claim(PVC)和 Persistent Volume(PV)的 API 物件,很大程度簡化了使用者宣告和使用持久化 Volume 的門檻。比如 PV 是群集中已由管理員配置的一塊儲存,一般系統管理員建立 endpoint、Service、PV ;PVC 是由開發人員進行配置 ,Pod 掛載到 PVC 中,PVC 可以向 PV 申請指定大小的儲存資源並設定訪問模式,而不需要關注儲存卷採用何種技術實現。

  • PersistentVolume(PV)是叢集中由管理員配置的一塊儲存。它是叢集中的資源,就和節點是叢集資源一樣。PV 是卷外掛比如 Volumes,但是它的生命週期獨立於使用 PV 的任何 pod 個體。該API物件捕獲實現儲存的詳細資訊,包括 NFS、iSCSI 或著是雲服務商特定的儲存系統。
  • PersistentVolumeClaim(PVC)是使用者關於儲存的請求。它類似於一個 pod,pod 消耗節點資源,而 PVC 消耗 PV 資源。Pods 可以請求特定級別的資源(CPU和內容),而Claim可以請求特定的大小和訪問模式(例如,可以一次讀/寫或者多次只讀)。

定義 PV

$ vi glusterfs-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: gluster-dev-volume
spec:
  capacity:
    storage: 8Gi
  accessModes:
    - ReadWriteMany
  glusterfs:
    endpoints: "glusterfs-cluster"
    path: "k8s-volume"
    readOnly: false

執行

$ kubectl apply -f glusterfs-pv.yaml
$ kubectl get pv

定義 PVC

$ cat glusterfs-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: glusterfs-nginx
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 2Gi

執行

$ kubectl apply -f glusterfs-pvc.yaml
$ kubectl get pvc

備註:訪問模式

ReadWriteOnce – the volume can be mounted as read-write by a single node
ReadOnlyMany – the volume can be mounted read-only by many nodes
ReadWriteMany – the volume can be mounted as read-write by many nodes

可以在 Dashboard 中檢視儲存卷的資訊

image

測試資料卷

$  wget https://raw.githubusercontent.com/kubernetes/website/master/content/en/examples/application/deployment.yaml
$  vi deployment.yaml

apiVersion: apps/v1 # for versions before 1.9.0 use apps/v1beta2
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 2 # tells deployment to run 2 pods matching the template
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx:1.7.9
        ports:
           - containerPort: 80
        volumeMounts:
            - name: gluster-dev-volume
              mountPath: "/usr/share/nginx/html"
         volumes:
        - name: gluster-dev-volume
          persistentVolumeClaim:
            claimName: glusterfs-nginx

執行

$ kubectl apply -f deployment.yaml
$ kubectl describe deployment nginx-deployment
$ kubectl get pods -l app=nginx
$ kubectl get pods -l app=nginx
NAME                                READY   STATUS    RESTARTS   AGE
nginx-deployment-5c689d88bb-7rx7d   1/1     Running   0          2d21h
nginx-deployment-5c689d88bb-hfqzm   1/1     Running   0          2d21h
nginx-deployment-5c689d88bb-tlwmn   1/1     Running   0          2d21h

建立檔案

$ kubectl exec -it nginx-deployment-5c689d88bb-7rx7d -- touch index.html

最後查詢 GlusterFS資料捲上是否有資料驗證下即可。

Dynamic Provisioning 與 Storage Classes

前面介紹 PV 和 PVC 的時候,一般 PV 這個物件的建立,是由運維人員完成,PVC 可能是開發人員定義。在大規模的生產環境裡,可能有很多 PVC ,這意味著運維人員必須得事先創建出成千上萬個 PV,這其實是一個非常麻煩的工作,所以在 Kubernetes 還引入了可以自動建立 PV 的機制 Dynamic Provisioning 概念,Dynamic Provisioning 機制工作的核心,在於一個名叫 StorageClass 物件。管理員可以定義 Storage Class 來描述他們提供的儲存型別,根據 Storage Class 物件中設定的引數自動分配 PV ,比如可以分別定義兩種 Storage Class :slow 和 fast。slow 對接 sc1(機械硬碟),fast 對接 gp2(固態硬碟)。應用可以根據業務的效能需求,分別選擇不同的儲存方式。下面建立 StorageClass 使用 StorageClass 連線 Heketi,根據需要自動建立 GluserFS 的 Volume,StorageClass 還是要系統管理員建立,不同的 PVC 可以用同一個 StorageClass 配置。

官方的文件與視訊介紹:

備註:並不是所有的儲存方式都支援 Dynamic Provisioning 特性,官方文件列出了預設支援 Dynamic Provisioning 的內建儲存外掛,當然也可以擴充套件第三方的儲存元件 kubernetes-incubator/external-storage 實現。

配置 Heketi

GlusterFS 是個開源的分散式檔案系統,而 Heketi 在其上提供了 REST 形式的 API,二者協同為 Kubernetes 提供了儲存卷的自動供給能力。按照官方的  persistent-volume-provisioning 示列,這裡需要配置 Heketi 提供了一個管理 GlusterFS 叢集的 RESTTful 服務,提供 API 介面供 Kubernetes 呼叫 。

$ yum install epel-release
$ yum install heketi heketi-client

檢視版本

$ heketi --version
Heketi 7.0.0
$ heketi --help
Heketi is a restful volume management server

Usage:
  heketi [flags]
  heketi [command]

Examples:
heketi --config=/config/file/path/

Available Commands:
  db          heketi db management
  help        Help about any command

Flags:
      --config string   Configuration file
  -h, --help            help for heketi
  -v, --version         Show version

Use "heketi [command] --help" for more information about a command.

修改 Heketi 配置檔案

vi /etc/heketi/heketi.json

#修改埠,預設 8080(伺服器 8080 佔用了)
  "port": "8000",
......
# 允許認證
  "use_auth": true,
......
# 修改admin使用者的key
      "key": "testtoken"
......
# 配置ssh的所需證書,對叢集中的機器免密登陸
    "executor": "ssh",
    "sshexec": {
      "keyfile": "/etc/heketi/heketi_key",
      "user": "root",
      "port": "22",
      "fstab": "/etc/fstab"
    },
......
# 定義heketi資料庫檔案位置
    "db": "/var/lib/heketi/heketi.db"
......
#修改日誌級別
    "loglevel" : "info"

配置 SSH 金鑰

#生成 rsa
ssh-keygen -t rsa -q -f /etc/heketi/heketi_key -N ''
chmod 700 /etc/heketi/heketi_key.pub

# 複製 ssh 公鑰上傳到 GlusterFS 三臺伺服器(heketi 也可以單獨部署)
ssh-copy-id -i /etc/heketi/heketi_key.pub [email protected]172.23.216.48
ssh-copy-id -i /etc/heketi/heketi_key.pub [email protected]172.23.216.49
ssh-copy-id -i /etc/heketi/heketi_key.pub [email protected]172.23.216.50

# 驗證是否能通過ssh金鑰正常連線到 glusterfs 節點
ssh -i /etc/heketi/heketi_key [email protected]172.23.216.49

啟動 Heketi

$ nohup heketi --config=/etc/heketi/heketi.json &
  nohup: ignoring input and appending output to ‘nohup.out’
$ cat nohup.out 
Heketi 7.0.0
[heketi] INFO 2018/11/09 15:50:36 Loaded ssh executor
[heketi] INFO 2018/11/09 15:50:36 GlusterFS Application Loaded
[heketi] INFO 2018/11/09 15:50:36 Started Node Health Cache Monitor
Authorization loaded
Listening on port 8000
測試 Heketi 服務端
$ curl http://localhost:8000/hello   
Hello from Heketi
Heketi 要求在每個 GlusterFS 節點上配備裸磁碟 device,不支援檔案系統,一般如下配置,可以通過 fdisk –l 命令檢視。
系統盤:/dev/vda
資料盤:/dev/vdb
雲硬碟:/dev/vdc
檢視磁碟
$ fdisk -l

Disk /dev/sda: 53.7 GB, 53687091200 bytes, 104857600 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: dos
Disk identifier: 0x000d3387

 Device Boot      Start         End      Blocks   Id  System
/dev/sda1   *        2048      411647      204800   83  Linux
/dev/sda2          411648     8800255     4194304   82  Linux swap / Solaris
/dev/sda3         8800256   104857599    48028672   83  Linux
$ df -lh
Filesystem      Size  Used Avail Use% Mounted on
/dev/sda3        46G  5.1G   41G  11% /
devtmpfs        3.9G     0  3.9G   0% /dev
tmpfs           3.9G     0  3.9G   0% /dev/shm
tmpfs           3.9G  172M  3.7G   5% /run
tmpfs           3.9G     0  3.9G   0% /sys/fs/cgroup
/dev/sda1       197M  167M   30M  85% /boot
overlay          46G  5.1G   41G  11% /var/lib/docker/containers/1c3c53802122a9ce7e3044e83f22934bb700baeda1bedc249558e9a068e892a7/mounts/shm
overlay          46G  5.1G   41G  11% /var/lib/docker/overlay2/bbda116e3a230e59710afd2d9ec92817d65d71b82ccebf4d71bfc589c3605b75/merged
tmpfs           3.9G   12K  3.9G   1% /var/lib/kubelet/pods/fb62839c-dc19-11e8-90ea-0050569f4a19/volumes/kubernetes.io~secret/coredns-token-v245h
tmpfs           3.9G   12K  3.9G   1% /var/lib/kubelet/pods/fb638fce-dc19-11e8-90ea-0050569f4a19/volumes/kubernetes.io~secret/coredns-token-v245h
overlay          46G  5.1G   41G  11% /var/lib/docker/overlay2/a85cbca8be37d9e00565d83350721091105b74e1609d399a0bb1bb91a2c56e09/merged
shm              64M     0   64M   0% 
tmpfs           783M     0  783M   0% /run/user/0
vdb             3.9G     0  3.9G   0% /mnt/disks/vdb
vdc             3.9G     0  3.9G   0% /mnt/disks/vdc
配置  topology-sample.json 
{
    "clusters": [
        {
            "nodes": [
                {
                    "node": {
                        "hostnames": {
                            "manage": [
                                "172.23.216.48"
                            ],
                            "storage": [
                                "172.23.216.48"
                            ]
                        },
                        "zone": 1
                    },
                    "devices": [
                        "/dev/vdb"
                    ]
                },
                {
                    "node": {
                        "hostnames": {
                            "manage": [
                                "172.23.216.49"
                            ],
                            "storage": [
                                "172.23.216.49"
                            ]
                        },
                        "zone": 1
                    },
                    "devices": [
                        "/dev/vdb"
                    ]
                },
                {
                    "node": {
                        "hostnames": {
                            "manage": [
                                "172.23.216.50"
                            ],
                            "storage": [
                                "172.23.216.50"
                            ]
                        },
                        "zone": 1
                    },
                    "devices": [
                        "/dev/vdb"
                    ]
                }              
            ]
        }
    ]
}

新增節點

$ heketi-cli --server http://localhost:8000 --user admin --secret "testtoken" topology load --json=topology-sample.json
Creating cluster ... ID: c2834ba9a3b5b6975150ad396b5ed7ca
        Allowing file volumes on cluster.
        Allowing block volumes on cluster.
        Creating node 172.23.216.48 ... ID: 8c5cbad748520b529ea20f5296921928
                Adding device /dev/vdb ... Unable to add device: Device /dev/vdb not found.
        Found node 172.23.216.49 on cluster c13ecf0a70808a3dc8abcd8de908c1ea
                Adding device /dev/vdb ... Unable to add device: Device /dev/vdb not found.
        Found node 172.23.216.50 on cluster c13ecf0a70808a3dc8abcd8de908c1ea
                Adding device /dev/vdb ... Unable to add device: Device /dev/vdb not found.
其他命令
#建立 cluster
heketi-cli --server http://localhost:8000 --user admin --secret "testtoken" topology load --json=topology-sample.json

#建立 volume
heketi-cli --server http://localhost:8000 --user admin --secret "testtoken" volume cre