溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

k8s之?dāng)?shù)據(jù)存儲

發(fā)布時間:2020-07-20 09:57:01 來源:網(wǎng)絡(luò) 閱讀:328 作者:wuseeger 欄目:云計算
Kubernetes 如何管理存儲資源。

首先我們會學(xué)習(xí) Volume,以及 Kubernetes 如何通過 Volume 為集群中的容器提供存儲;然后我們會實踐幾種常用的 Volume 類型并理解它們各自的應(yīng)用場景;最后,我們會討論 Kubernetes 如何通過 Persistent Volume 和 Persistent Volume Claim 分離集群管理員與集群用戶的職責(zé),并實踐 Volume 的靜態(tài)供給和動態(tài)供給。

Volume

本節(jié)我們討論 Kubernetes 的存儲模型 Volume,學(xué)習(xí)如何將各種持久化存儲映射到容器。

我們經(jīng)常會說:容器和 Pod 是短暫的。
其含義是它們的生命周期可能很短,會被頻繁地銷毀和創(chuàng)建。容器銷毀時,保存在容器內(nèi)部文件系統(tǒng)中的數(shù)據(jù)都會被清除。

為了持久化保存容器的數(shù)據(jù),可以使用 Kubernetes Volume。

Volume 的生命周期獨立于容器,Pod 中的容器可能被銷毀和重建,但 Volume 會被保留。

本質(zhì)上,Kubernetes Volume 是一個目錄,這一點與 Docker Volume 類似。當(dāng) Volume 被 mount 到 Pod,Pod 中的所有容器都可以訪問這個 Volume。Kubernetes Volume 也支持多種 backend 類型,包括 emptyDir、hostPath、GCE Persistent Disk、AWS Elastic Block Store、NFS、Ceph 等,完整列表可參考 https://kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes

Volume 提供了對各種 backend 的抽象,容器在使用 Volume 讀寫數(shù)據(jù)的時候不需要關(guān)心數(shù)據(jù)到底是存放在本地節(jié)點的文件系統(tǒng)中呢還是云硬盤上。對它來說,所有類型的 Volume 都只是一個目錄。

我們將從最簡單的 emptyDir 開始學(xué)習(xí) Kubernetes Volume。

emptyDir

emptyDir 是最基礎(chǔ)的 Volume 類型。正如其名字所示,一個 emptyDir Volume 是 Host 上的一個空目錄。

emptyDir Volume 對于容器來說是持久的,對于 Pod 則不是。當(dāng) Pod 從節(jié)點刪除時,Volume 的內(nèi)容也會被刪除。但如果只是容器被銷毀而 Pod 還在,則 Volume 不受影響。

也就是說:emptyDir Volume 的生命周期與 Pod 一致。

Pod 中的所有容器都可以共享 Volume,它們可以指定各自的 mount 路徑。下面通過例子來實踐 emptyDir,配置文件如下:

apiVersion: v1
kind: Pod
metadata:
  name: producer-consumer
spec:
  containers:
  - image: busybox
    name: producer
    volumeMounts:
    - mountPath: /producer_dir
      name: shared-volume
    args:
    - /bin/sh
    - -c
    - echo "hello world"> /producer_dir/hello ; sleep 30000

  - image: busybox
    name: consumer
    volumeMounts:
    - mountPath: /consumer_dir
      name: shared-volume
    args:
    - /bin/sh
    - -c
    - cat /consumer_dir/hello ; sleep 30000

  volumes:
  - name: shared-volume
    emptyDir: {}

這里我們模擬了一個 producer-consumer 場景。Pod 有兩個容器 producer和 consumer,它們共享一個 Volume。producer 負(fù)責(zé)往 Volume 中寫數(shù)據(jù),consumer 則是從 Volume 讀取數(shù)據(jù)。

① 文件最底部 volumes 定義了一個 emptyDir 類型的 Volume shared-volume。

② producer 容器將 shared-volume mount 到 /producer_dir 目錄。

③ producer 通過 echo 將數(shù)據(jù)寫到文件 hello 里。

④ consumer 容器將 shared-volume mount 到 /consumer_dir 目錄。

⑤ consumer 通過 cat 從文件 hello 讀數(shù)據(jù)。

執(zhí)行如下命令創(chuàng)建 Pod:

# kubectl apply -f emptyDir.yaml
pod/producer-consumer created

# kubectl get pod
NAME                READY   STATUS    RESTARTS   AGE
producer-consumer   2/2     Running   0          87s

# kubectl logs producer-consumer consumer 
hello world

kubectl logs 顯示容器 consumer 成功讀到了 producer 寫入的數(shù)據(jù),驗證了兩個容器共享 emptyDir Volume。

因為 emptyDir 是 Docker Host 文件系統(tǒng)里的目錄,其效果相當(dāng)于執(zhí)行了 docker run -v /producer_dir 和 docker run -v /consumer_dir。通過 docker inspect 查看容器的詳細(xì)配置信息,我們發(fā)現(xiàn)兩個容器都 mount 了同一個目錄:

 {
                "Type": "bind",
                "Source": "/var/lib/kubelet/pods/188767a3-cf18-4bf9-a89b-b0c4cf4124bb/volumes/kubernetes.io~empty-dir/shared-volume",
                "Destination": "/consumer_dir",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            }

"Mounts": [
            {
                "Type": "bind",
                "Source": "/var/lib/kubelet/pods/188767a3-cf18-4bf9-a89b-b0c4cf4124bb/volumes/kubernetes.io~empty-dir/shared-volume",
                "Destination": "/producer_dir",
                "Mode": "",
                "RW": true,
                "Propagation": "rprivate"
            },

這里 /var/lib/kubelet/pods/3e6100eb-a97a-11e7-8f72-0800274451ad/volumes/kubernetes.io~empty-dir/shared-volume 就是 emptyDir 在 Host 上的真正路徑。

emptyDir 是 Host 上創(chuàng)建的臨時目錄,其優(yōu)點是能夠方便地為 Pod 中的容器提供共享存儲,不需要額外的配置。但它不具備持久性,如果 Pod 不存在了,emptyDir 也就沒有了。根據(jù)這個特性,emptyDir 特別適合 Pod 中的容器需要臨時共享存儲空間的場景,比如前面的生產(chǎn)者消費者用例。

hostPath Volume。

hostPath Volume 的作用是將 Docker Host 文件系統(tǒng)中已經(jīng)存在的目錄 mount 給 Pod 的容器。大部分應(yīng)用都不會使用 hostPath Volume,因為這實際上增加了 Pod 與節(jié)點的耦合,限制了 Pod 的使用。不過那些需要訪問 Kubernetes 或 Docker 內(nèi)部數(shù)據(jù)(配置文件和二進(jìn)制庫)的應(yīng)用則需要使用 hostPath。

比如 kube-apiserver 和 kube-controller-manager 就是這樣的應(yīng)用,通過

kubectl edit --namespace=kube-system pod kube-apiserver-k8s-master
查看 kube-apiserver Pod 的配置,下面是 Volume 的相關(guān)部分:

   volumeMounts:
    - mountPath: /etc/ssl/certs
      name: ca-certs
      readOnly: true
    - mountPath: /etc/pki
      name: etc-pki
      readOnly: true
    - mountPath: /etc/kubernetes/pki
      name: k8s-certs
      readOnly: true

  volumes:
  - hostPath:
      path: /etc/ssl/certs
      type: DirectoryOrCreate
    name: ca-certs
  - hostPath:
      path: /etc/pki
      type: DirectoryOrCreate
    name: etc-pki
  - hostPath:
      path: /etc/kubernetes/pki
      type: DirectoryOrCreate
    name: k8s-certs

這里定義了三個 hostPath volume k8s、certs 和 pki,分別對應(yīng) Host 目錄 /etc/kubernetes、/etc/ssl/certs 和 /etc/pki。

如果 Pod 被銷毀了,hostPath 對應(yīng)的目錄也還會被保留,從這點看,hostPath 的持久性比 emptyDir 強。不過一旦 Host 崩潰,hostPath 也就沒法訪問了。

真正持久性的 Volume。

外部 Storage Provider

如果 Kubernetes 部署在諸如 AWS、GCE、Azure 等公有云上,可以直接使用云硬盤作為 Volume,下面是 AWS Elastic Block Store 的例子:

要在 Pod 中使用 ESB volume,必須先在 AWS 中創(chuàng)建,然后通過 volume-id 引用。其他云硬盤的使用方法可參考各公有云廠商的官方文檔。

Kubernetes Volume 也可以使用主流的分布式存,比如 Ceph、GlusterFS 等,下面是 Ceph 的例子:

Ceph的 /some/path/in/side/cephfs 目錄被 mount 到容器路徑 /test-ceph。

相對于 emptyDir 和 hostPath,這些 Volume 類型的最大特點就是不依賴 Kubernetes。Volume 的底層基礎(chǔ)設(shè)施由獨立的存儲系統(tǒng)管理,與 Kubernetes 集群是分離的。數(shù)據(jù)被持久化后,即使整個 Kubernetes 崩潰也不會受損。

當(dāng)然,運維這樣的存儲系統(tǒng)通常不是項簡單的工作,特別是對可靠性、高可用和擴展性有較高要求時。

Volume 提供了非常好的數(shù)據(jù)持久化方案,不過在可管理性上還有不足。下一節(jié)我們將學(xué)習(xí)具有更高管理性的存儲方案:PersistentVolume & PersistentVolumeClaim。

PV & PVC

Volume 提供了非常好的數(shù)據(jù)持久化方案,不過在可管理性上還有不足。

拿前面 AWS EBS 的例子來說,要使用 Volume,Pod 必須事先知道如下信息:

  • 當(dāng)前 Volume 來自 AWS EBS。
  • EBS Volume 已經(jīng)提前創(chuàng)建,并且知道確切的 volume-id。

Pod 通常是由應(yīng)用的開發(fā)人員維護(hù),而 Volume 則通常是由存儲系統(tǒng)的管理員維護(hù)。開發(fā)人員要獲得上面的信息:

  • 要么詢問管理員。
  • 要么自己就是管理員。

這樣就帶來一個管理上的問題:應(yīng)用開發(fā)人員和系統(tǒng)管理員的職責(zé)耦合在一起了。如果系統(tǒng)規(guī)模較小或者對于開發(fā)環(huán)境這樣的情況還可以接受。但當(dāng)集群規(guī)模變大,特別是對于生成環(huán)境,考慮到效率和安全性,這就成了必須要解決的問題。

Kubernetes 給出的解決方案是 PersistentVolume 和 PersistentVolumeClaim。

PersistentVolume (PV) 是外部存儲系統(tǒng)中的一塊存儲空間,由管理員創(chuàng)建和維護(hù)。與 Volume 一樣,PV 具有持久性,生命周期獨立于 Pod。

PersistentVolumeClaim (PVC) 是對 PV 的申請 (Claim)。PVC 通常由普通用戶創(chuàng)建和維護(hù)。需要為 Pod 分配存儲資源時,用戶可以創(chuàng)建一個 PVC,指明存儲資源的容量大小和訪問模式(比如只讀)等信息,Kubernetes 會查找并提供滿足條件的 PV。

有了 PersistentVolumeClaim,用戶只需要告訴 Kubernetes 需要什么樣的存儲資源,而不必關(guān)心真正的空間從哪里分配,如何訪問等底層細(xì)節(jié)信息。這些 Storage Provider 的底層信息交給管理員來處理,只有管理員才應(yīng)該關(guān)心創(chuàng)建 PersistentVolume 的細(xì)節(jié)信息。

Kubernetes 支持多種類型的 PersistentVolume,比如 AWS EBS、Ceph、NFS 等,完整列表請參考 https://kubernetes.io/docs/concepts/storage/persistent-volumes/#types-of-persistent-volumes

NFS PersistentVolume

通過 NFS 實踐PV和PVC。

作為準(zhǔn)備工作,我們已經(jīng)在 k8s-master 節(jié)點上搭建了一個 NFS 服務(wù)器,目錄為 /nfsdata:

[root@k8s-master ~]# showmount -e
Export list for k8s-master:
/nfsdata 192.168.168.0/24

下面創(chuàng)建一個 PV mypv1,配置文件 nfs-pv1.yml 如下:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: mypv1
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: nfs
  nfs:
    path: /nfsdata/pv1
    server: 192.168.168.10

① capacity 指定 PV 的容量為 1G。

② accessModes 指定訪問模式為 ReadWriteOnce,支持的訪問模式有:
ReadWriteOnce – PV 能以 read-write 模式 mount 到單個節(jié)點。
ReadOnlyMany – PV 能以 read-only 模式 mount 到多個節(jié)點。
ReadWriteMany – PV 能以 read-write 模式 mount 到多個節(jié)點。

③ persistentVolumeReclaimPolicy 指定當(dāng) PV 的回收策略為 Recycle,支持的策略有:
Retain – 需要管理員手工回收。
Recycle – 清除 PV 中的數(shù)據(jù),效果相當(dāng)于執(zhí)行 rm -rf /thevolume/*。
Delete – 刪除 Storage Provider 上的對應(yīng)存儲資源,例如 AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume 等。

④ storageClassName 指定 PV 的 class 為 nfs。相當(dāng)于為 PV 設(shè)置了一個分類,PVC 可以指定 class 申請相應(yīng) class 的 PV。

⑤ 指定 PV 在 NFS 服務(wù)器上對應(yīng)的目錄。

創(chuàng)建 mypv1:

[root@k8s-master ~]# kubectl apply -f nfs-pv1.yml 
persistentvolume/mypv1 created
[root@k8s-master ~]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
mypv1   1Gi        RWO            Recycle          Available           nfs                     7s

STATUS 為 Available,表示 mypv1 就緒,可以被 PVC 申請。

接下來創(chuàng)建 PVC mypvc1,配置文件 nfs-pvc1.yml 如下:

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: mypvc1
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: nfs

PVC 就很簡單了,只需要指定 PV 的容量,訪問模式和 class。

創(chuàng)建 mypvc1:

[root@k8s-master ~]# kubectl apply -f nfs-pvc1.yml 
persistentvolumeclaim/mypvc1 created
[root@k8s-master ~]# kubectl get pvc
NAME     STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mypvc1   Bound    mypv1    1Gi        RWO            nfs            6s
[root@k8s-master ~]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM            STORAGECLASS   REASON   AGE
mypv1   1Gi        RWO            Recycle          Bound    default/mypvc1   nfs                     2m18s
[root@k8s-master ~]# 

從 kubectl get pvc 和 kubectl get pv 的輸出可以看到 mypvc1 已經(jīng) Bound 到 mypv1,申請成功。

接下來就可以在 Pod 中使用存儲了,Pod 配置文件 pod1.yml 如下:

kind: Pod
apiVersion: v1
metadata:
  name: mypod1
spec:
  containers:
    - name: mypod1
      image: busybox
      args:
      - /bin/sh
      - -c
      - sleep 30000
      volumeMounts:
      - mountPath: "/mydata"
        name: mydata
  volumes:
    - name: mydata
      persistentVolumeClaim:
        claimName: mypvc1

與使用普通 Volume 的格式類似,在 volumes 中通過 persistentVolumeClaim 指定使用 mypvc1 申請的 Volume。

創(chuàng)建 mypod1:

[root@k8s-master ~]# kubectl apply -f pod1.yml
pod/mypod1 created

[root@k8s-master ~]# kubectl get pod -o wide     
NAME     READY   STATUS    RESTARTS   AGE     IP           NODE        NOMINATED NODE   READINESS GATES
mypod1   1/1     Running   0          3m19s   10.244.1.2   k8s-node1   <none>           <none>

驗證 PV 是否可用:

[root@k8s-master ~]# kubectl exec mypod1 touch /mydata/hello
[root@k8s-master ~]# ll /nfsdata/pv1/
total 0
-rw-r--r-- 1 root root 0 Oct 12 16:36 hello

可見,在 Pod 中創(chuàng)建的文件 /mydata/hello 確實已經(jīng)保存到了 NFS 服務(wù)器目錄 /nfsdata/pv1 中。

如果不再需要使用 PV,可用刪除 PVC 回收 PV。

MySQL 如何使用 PV 和 PVC?

本節(jié)演示如何為 MySQL 數(shù)據(jù)庫提供持久化存儲,步驟為:

創(chuàng)建 PV 和 PVC。

部署 MySQL。

向 MySQL 添加數(shù)據(jù)。

模擬節(jié)點宕機故障,Kubernetes 將 MySQL 自動遷移到其他節(jié)點。

驗證數(shù)據(jù)一致性。

首先創(chuàng)建 PV 和 PVC,配置如下:

mysql-pv.yml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: mysql-pv
spec:
  accessModes:
    - ReadWriteOnce
  capacity:
    storage: 1Gi
  persistentVolumeReclaimPolicy: Retain
  storageClassName: nfs
  nfs:
    path: /nfsdata/mysql-pv
    server: 192.168.77.10

mysql-pvc.yml

kind: PersistentVolumeClaim
apiVersion: v1
metadata: 
  name: mysql-pvc
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: nfs

創(chuàng)建 mysql-pv 和 mysql-pvc:

接下來部署 MySQL,配置文件如下:

apiVersion: v1
kind: Service
metadata:
  name: mysql
spec:
  ports:
  - port: 3306
  selector:
    app: mysql

---
apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - image: mysql:5.6
        name: mysql
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: password
        ports:
        - containerPort: 3306
          name: mysql
        volumeMounts:
        - name: mysql-persistent-storage
          mountPath: /var/lib/mysql
      volumes:
      - name: mysql-persistent-storage
        persistentVolumeClaim:
          claimName: mysql-pvc  

PVC mysql-pvc Bound 的 PV mysql-pv 將被 mount 到 MySQL 的數(shù)據(jù)目錄 var/lib/mysql。

MySQL 被部署到 k8s-node2,下面通過客戶端訪問 Service mysql:

# kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -ppassword 

更新數(shù)據(jù)庫:

[root@k8s-master ~]# kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -ppassword
If you don't see a command prompt, try pressing enter.

mysql> use mysql 
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> create table my_id(id int(4));
Query OK, 0 rows affected (0.09 sec)

mysql> insert into my_id values(111);
Query OK, 1 row affected (0.00 sec)

mysql> select * from my_id;
+------+
| id   |
+------+
|  111 |
+------+
1 row in set (0.00 sec)

① 切換到數(shù)據(jù)庫 mysql。

② 創(chuàng)建數(shù)據(jù)庫表 my_id。

③ 插入一條數(shù)據(jù)。

④ 確認(rèn)數(shù)據(jù)已經(jīng)寫入。

關(guān)閉 k8s-node2,模擬節(jié)點宕機故障。

# systemctl poweroff 

一段時間后,Kubernetes 將 MySQL 遷移到 k8s-node1。

[root@k8s-master ~]# kubectl get pod
NAME                     READY   STATUS        RESTARTS   AGE
mysql-84bdf65dd5-bjz8b   1/1     Terminating   0          22m
mysql-84bdf65dd5-ddlhc   1/1     Running       0          34s

驗證數(shù)據(jù)的一致性:

[root@k8s-master ~]# kubectl run -it --rm --image=mysql:5.6 --restart=Never mysql-client -- mysql -h mysql -ppassword           If you don't see a command prompt, try pressing enter.

mysql> use mysql
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

mysql> select * from my_id;
+------+
| id   |
+------+
|  111 |
+------+
1 row in set (0.01 sec)

mysql> quit

MySQL 服務(wù)恢復(fù),數(shù)據(jù)也完好無損。

emptyDir 和 hostPath 類型的 Volume 很方便,但可持久性不強,Kubernetes 支持多種外部存儲系統(tǒng)的 Volume。

PV 和 PVC 分離了管理員和普通用戶的職責(zé),更適合生產(chǎn)環(huán)境。。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI