Kubernetes 實操手冊-數(shù)據(jù)持久化

簡介

Pod 本身存在生命周期,因此其內(nèi)部的容器及數(shù)據(jù)均無法持久存在。對于 Stateful 的應(yīng)用(如數(shù)據(jù)庫)以及日志文件等,我們都需要持久化數(shù)據(jù),避免在應(yīng)用重啟后丟失數(shù)據(jù)。Docker 支持配置容器使用存儲卷將數(shù)據(jù)持久化到容器自身文件系統(tǒng)外的存儲空間之中。而 Kubernetes 提供了基于 Pod 的存儲卷功能,Kubernetes 給我們提供了很多相關(guān)的資源用于管理持久化的數(shù)據(jù),最常用的就是 PersistentVolumesPersistentVolumeClaims,下面我們將詳細講講如何在 Kubernetes 集群中持久化數(shù)據(jù)。以下也是以目前最新的 Kubernetes v.1.14.1 版本做介紹,下面大部分的內(nèi)容都是總結(jié)自官方文檔,若對基礎(chǔ)比較熟悉的可以跳過,直接進入實操部分。

Kubernetes 中的持久化存儲設(shè)計

Kubernetes 中的存儲卷稱為 Volume,每個 Pod 可以不掛載或者掛載多個 Volumes,這個 Volume 就類似于 Docker 的 Volume,但是它的概念更一般化,可以是宿主機的路徑,或者是 NFS 等網(wǎng)絡(luò)文件系統(tǒng),甚至是云服務(wù)商提供的存儲卷等,詳細可以參考官方文檔。在 Pod 中配置 Volume 類似下面這樣


apiVersion: v1

kind: Pod

metadata:

  name: test-pd

spec:

  containers:

  - image: k8s.gcr.io/test-webserver

    name: test-container

    volumeMounts:

    - mountPath: /test-pd

      name: test-volume

  volumes:

  - name: test-volume

    hostPath:

      # directory location on host

      path: /data

      # this field is optional

      type: Directory

一個是 volumes 配置項,用于聲明這個 Pod 定義了哪些 voluems,另一個是 volumeMounts 配置項,用于把定義的 Volume 掛載到具體的容器的一個路徑上,通過 volume 的 name 字段相關(guān)聯(lián)。其他類型的存儲卷配置也是一樣,不同的就是 volumes 的定義部分。

常見的存儲卷類型

這里只介紹幾種常見的存儲卷類型,詳細的列表和介紹可以參考官方文檔

emptyDirhostPath 屬于節(jié)點級別的卷類型,他們依賴于特定的節(jié)點。emptyDir 的生命周期與 Pod 一致,在 Pod 刪除后會一并刪除,主要用于 Pod 內(nèi)部多容器之間共享數(shù)據(jù),hostPath 就是將節(jié)點的目錄直接掛載到 Pod 上,但是如果 Pod 被調(diào)度到其他節(jié)點,那么數(shù)據(jù)將不可用。而 nfs,cephfs,glusterfs 這些是常用的網(wǎng)絡(luò)文件系統(tǒng),可以供多個 Pod 同時鏈接。但是首先需要部署相應(yīng)的網(wǎng)絡(luò)文件系統(tǒng),而且相對的性能會比較差,對于 IO 要求高的應(yīng)用不太適合。另外還有公有云服務(wù)商提供的存儲卷,如 AWS 的 ElasticBlockStore,Azure 的 AzureDisk 等,可以使用基于公有云的存儲服務(wù)提供滿足需求的存儲卷類型。

早期的 Kubernetes 將 Volume 整合在核心代碼之中,這非常不便于功能的擴展,后來在 1.9 版本就提出了使用 CSI(Container Storage Interface)作為統(tǒng)一的存儲卷接口(文檔),1.14.1 版本處于 GA 階段,詳細文檔可參考這里。這樣存儲卷也就類似于 CNI 一樣,可以獨立于核心代碼,便于插件化擴展功能。

存儲卷解藕

上面的方式可以快速創(chuàng)建存儲卷,但是這樣的用法有個問題,一般來說應(yīng)用的模版是由開發(fā)人員編輯的,模版的編輯者必須知道存儲卷的詳細信息,但是不利于存儲卷的統(tǒng)一管理。這里 Kubernetes 就設(shè)計了一個解藕的方案,即 PersistentVolumes(PV) 和 PersistentVolumeClaims(PVC),由存儲卷管理員(或者運維部門)負責管理所有的存儲卷,定義 PersistentVolumes,這樣 volumes 就類似于 node 一樣是集群的資源,而開發(fā)人員只需要按需使用即可,聲明 PersistentVolumeClaims,無需關(guān)心各個存儲卷的細節(jié)。

例如,管理員可以按如下模版定義 PV


apiVersion: v1

kind: PersistentVolume

metadata:

  name: test-pv

  labels:

    type: test

spec:

  capacity:

    storage: "5Gi"

  accessModes:

    - ReadWriteOnce

  mountOptions:

    - hard

    - nfsvers=4.1

  nfs:

    path: /tmp

    server: 172.17.0.2

使用 kubectl get pv 可以看到 PV 的狀態(tài)是 Available。

然后開發(fā)人員使用如下模版定義 PVC


kind: PersistentVolumeClaim

apiVersion: v1

metadata:

  name: test-pvc

  labels:

    type: test

spec:

  accessModes:

    - ReadWriteOnce

  resources:

    requests:

      storage: "5Gi"

  selector:

    matchLabels:

      type: test

然后查看 PV 的狀態(tài)就是 Bound 了。

參數(shù)詳解

PV

1. capacity: 指定 PV 的容量,目前只支持 storage 指定空間大小

2. accessModes: 指定 PV 的訪問模式,有以下幾種,PVC 指定的模式必須與對應(yīng)的 PV 一致

a. ReadWriteOnce: 僅可被耽擱節(jié)點掛載讀寫,簡寫為 RWO

b. ReadOnlyMany: 可被多個節(jié)點同時只讀掛載,簡寫為 ROX

c. ReadWriteMany: 可被多個節(jié)點同時讀寫掛載,簡寫為 RWX

3. persistentVolumeReclaimPolicy: PV 空間釋放時的處理機制,有以下幾種

a. Retain: 保持不動

b. Recycle: 回收空間,即刪除所有文件,僅部分類型支持

c. Delete: 刪除存儲卷,僅部分云存儲支持

4. volumeMode: 卷類型,用作文件系統(tǒng)還是裸格式的塊設(shè)備,默認文件系統(tǒng) Filesystem

5. storageClassName: PV 所屬的存儲類名稱,默認為空,下面會講

6. mountOptions: 掛載選項列表

PVC

1. accessModes: PVC 的訪問模式,必須與 PV 一致

2. resources: PVC 需要占用的資源最小值,目前僅支持 storage 指定空間大小

3. seletor: 綁定 PV 的標簽選擇器或條件表達式,類似與 Pod 的 Node 選擇器

4. storageClassName: 所依賴的存儲類名稱,下面會講

5. volumeMode: 卷類型,同 PV

6. volumeName: 用于直接指定要綁定的 PV 卷名

注意,PVC 是命名空間隔離的,如果使用了多命名空間,ROX/RWX 類型的 claim 必須位于同一個命名空間。

存儲類

上面的解藕方式能夠區(qū)分管理員和開發(fā)人員的工作,但是每次新建存儲卷都需要管理員預(yù)先創(chuàng)建 PV(麻煩),或者由管理員一次創(chuàng)建大量 PV 供開發(fā)人員使用(浪費)。對于管理人員來說還是不太友好(不夠懶?。源鎯︻悾╯torage class)就應(yīng)運而生了。

存儲類是 Kubernetes 為管理 PV 創(chuàng)建的邏輯類別,類似于面向?qū)ο缶幊痰念?,?PV 就是具體存儲類的實例。有了存儲類之后,管理員就可以預(yù)先定義許多不同類型的存儲類,例如 ssd,fast,cold 等等,而后由開發(fā)人員發(fā)起 PVC 申請創(chuàng)建具體的 PV 使用,不需要管理員直接參與 PV 的創(chuàng)建了。


kind: StorageClass

apiVersion: storage.k8s.io/v1

metadata:

  name: ebs-sc

provisioner: ebs.csi.aws.com

volumeBindingMode: WaitForFirstConsumer

定義如上的 storage class,在需要時創(chuàng)建 AWS 的 EBS 作為存儲卷,然后開發(fā)人員用 PVC 申請即可。


apiVersion: v1

kind: PersistentVolumeClaim

metadata:

  name: ebs-claimspec:

  accessModes:

    - ReadWriteOnce

  storageClassName: ebs-sc

  resources:

    requests:

      storage: 4Gi

這樣就實現(xiàn)了存儲卷的動態(tài)供給。

參數(shù)詳解

1. provisioner: 存儲卷供給方

2. reclaimPolicy: 存儲卷回收策略

a. Delete: 刪除,默認

b. Retain: 保持不動,需要手動刪除

3. parameters: 其他參數(shù),根據(jù)供給方不同而不同

4. mountOptions: 掛載選項列表

5. volumeBindingMode: 控制綁定的方式

a. Immediate: 當 PVC 創(chuàng)建時立即創(chuàng)建,默認

b. WaitForFirstConsumer: 延遲到當 Pod 使用時創(chuàng)建

存儲卷生命周期

PV 的生命周期如下所示,PV 由供給(Provisioniong)創(chuàng)建,管理員手動創(chuàng)建的是靜態(tài)供給,由存儲類生成的是動態(tài)供給。當有 PVC 申請 PV 后,PVC 與 PV 綁定,PV 和 PVC 都進入 Bound 狀態(tài)。在 Pod 使用完成刪除了 PVC 之后,PV 進入 Released 狀態(tài),表明 PV 與 PVC 解綁了,但還未回收?;厥?PV 后,根據(jù)設(shè)置刪除存儲卷或者手動刪除。

image.png

幾個生命周期相關(guān)的配置項如下:

PV 的 persistentVolumeReclaimPolicy 控制 PV 的回收策略,StorageClass 的 reclaimPolicy 控制生成的 PV 的回收策略,volumeBindingMode 控制生成的 PV 的綁定策略,詳見上面的參數(shù)詳解。

生產(chǎn)實操

Kubernetes 使用 AWS EBS 作為存儲

簡介

如果在公有云上部署和運行 Kubernetes 集群,那么使用公有云提供的存儲服務(wù)將是提高性能和可用性的最佳選擇。我們以 AWS 為例,AWS 提供了 EBS 作為一般需求的塊存儲。EBS 提供了 SSD,高 IOPS SSD,HDD,冷數(shù)據(jù) HDD 等多種類型可供選擇,同時也提供了方便快速的快照功能,底層數(shù)據(jù)加密等,可滿足日常的絕大部分使用場景。

前提

項目頁面介紹了需要使用 AWS EBS 的前提條件


Get yourself familiar with how to setup Kubernetes on AWS and have a working Kubernetes cluster:

Enable flag --allow-privileged=true for kubelet and kube-apiserver

Enable kube-apiserver feature gates --feature-gates=CSINodeInfo=true,CSIDriverRegistry=true,CSIBlockVolume=true,VolumeSnapshotDataSource=true

Enable kubelet feature gates --feature-gates=CSINodeInfo=true,CSIDriverRegistry=true,CSIBlockVolume=true

具體的操作如下:

二進制方式安裝的話,在 kube-apiserver 的啟動參數(shù)上增加--allow-privileged=true --feature-gates=CSINodeInfo=true,CSIDriverRegistry=true,CSIBlockVolume=true,VolumeSnapshotDataSource=true。

如果使用 kubeadm 初始化,則修改初始化配置文件像下面這樣,詳細的安裝步驟可以參考這篇文章,主要是添加 api-server 的兩行配置


apiServer:

  extraArgs:

    authorization-mode: Node,RBAC

    allow-privileged: "true"  # add allow-privileged for api-server

    feature-gates: "CSINodeInfo=true,CSIDriverRegistry=true,CSIBlockVolume=true,VolumeSnapshotDataSource=true"  # enable feature-gates for api-server

  timeoutForControlPlane: 4m0s

apiVersion: kubeadm.k8s.io/v1beta1

certificatesDir: /etc/kubernetes/pki

clusterName: kubernetes

controlPlaneEndpoint: ""

controllerManager: {}

dns:

  type: CoreDNS

etcd:

  local:

    dataDir: /var/lib/etcd

imageRepository: k8s.gcr.io

kind: ClusterConfiguration

kubernetesVersion: v1.14.1

networking:

  dnsDomain: cluster.local

  podSubnet: 10.244.0.0/16

  serviceSubnet: 10.96.0.0/12

scheduler: {}

kubelet 的參數(shù)不用修改,1.14.1 版本的 kubelet 默認已啟用。

安裝

配置權(quán)限

首先確保集群的每個實例有足夠的 IAM 權(quán)限來創(chuàng)建和刪除 EBS,最簡單的辦法是給集群的每個實例賦予一個 IAM Role,給這個 IAM Role EC2 Full Access 的權(quán)限,或者至少包含如下的權(quán)限


{

  "Version": "2012-10-17",

  "Statement": [

    {

      "Effect": "Allow",

      "Action": [

        "ec2:AttachVolume",

        "ec2:CreateSnapshot",

        "ec2:CreateTags",

        "ec2:CreateVolume",

        "ec2:DeleteSnapshot",

        "ec2:DeleteTags",

        "ec2:DeleteVolume",

        "ec2:DescribeInstances",

        "ec2:DescribeSnapshots",

        "ec2:DescribeTags",

        "ec2:DescribeVolumes",

        "ec2:DetachVolume"

      ],

      "Resource": "*"

    }

  ]

}

另外也可以用 AWS secret key 的方式寫入每個機器的 profile 文件(參考文檔)或者放入集群的 Secret。

部署驅(qū)動


kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-ebs-csi-driver/master/deploy/kubernetes/manifest.yaml

查看驅(qū)動是否運行


kubectl get pods -n kube-system

會有類似這樣的 Pod


ebs-csi-controller-0                                   6/6     Running   0          2d16h

ebs-csi-node-nfttl                                     3/3     Running   0          2d16h

使用

更多的使用示例可以參考官方文檔,這里詳細說明下幾個常用的場景。

動態(tài)分配

動態(tài)分配是由管理員創(chuàng)建存儲類模版,而用戶使用時只需要從對應(yīng)的存儲類中創(chuàng)建申請即可,用戶不需要關(guān)心每個存儲類的具體細節(jié)。

創(chuàng)建 storage class 模版storageclass.yaml


kind: StorageClassapiVersion: storage.k8s.io/v1

metadata:

  name: ebs-sc

provisioner: ebs.csi.aws.com

volumeBindingMode: WaitForFirstConsumer

創(chuàng)建


kubectl apply -f storageclass.yaml

創(chuàng)建 PersistentVolumeClaim 存儲申請模版claim.yaml


apiVersion: v1

kind: PersistentVolumeClaim

metadata:

  name: ebs-claim

spec:

  accessModes:

    - ReadWriteOnce

  storageClassName: ebs-sc

  resources:

    requests:

      storage: 4Gi

創(chuàng)建


kubectl apply -f claim.yaml

查看 PersistentVolumeClaim 的狀態(tài),顯示為 Pending,有容器掛載后即可使用。


kubectl get pvc

創(chuàng)建一個測試用的 Pod pod.yaml


apiVersion: v1

kind: Pod

metadata:

  name: app

spec:

  containers:

  - name: app

    image: centos

    command: ["/bin/sh"]

    args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"]

    volumeMounts:

    - name: persistent-storage

      mountPath: /data

  volumes:

  - name: persistent-storage

    persistentVolumeClaim:

      claimName: ebs-claim

創(chuàng)建


kubectl apply -f pod.yaml

等待片刻,查看 PersistentVolumeClaim 和 PersistentVolume,即可看到狀態(tài)顯示為Bound,且多了一塊新創(chuàng)建的 Volume。

查看 Volume 的詳細信息


kubectl describe pv

可看到類似下面這樣的信息,描述了 EBS 的 volume ID,可以在 AWS 控制臺看到。


Source:

    Type:              CSI (a Container Storage Interface (CSI) volume source)

    Driver:            ebs.csi.aws.com

    VolumeHandle:      vol-0e447f0fffaf978c9

    ReadOnly:          false

進入剛才創(chuàng)建的 Pod


kubectl exec app -ti bash

可以使用df -h命令看到掛載的磁盤在/data目錄,且里面已經(jīng)有內(nèi)容在輸出。

從快照創(chuàng)建盤

動態(tài)分配是每次啟動 Pod 都會從設(shè)置的存儲類中創(chuàng)建一個新的存儲卷,而對于線上環(huán)境,我們往往需要的是持久化數(shù)據(jù),而不是每次都新建。所以一種更常見的使用場景是我們定時對數(shù)據(jù)拍攝快照,而創(chuàng)建 Pod 后掛載使用最新快照的存儲卷(數(shù)據(jù)更新相對不太頻繁的情況)。

創(chuàng)建一個 snapshot class 資源snapshotclass.yaml


apiVersion: snapshot.storage.k8s.io/v1alpha1

kind: VolumeSnapshotClass

metadata:

  name: csi-aws-vsc

snapshotter: ebs.csi.aws.com

創(chuàng)建


kubectl apply -f snapshotclass.yaml

像上面一樣創(chuàng)建一個 Pod 并綁定一個 EBS,用于創(chuàng)建快照app.yaml


kind: StorageClass

apiVersion: storage.k8s.io/v1

metadata:

  name: ebs-sc

provisioner: ebs.csi.aws.com

volumeBindingMode: WaitForFirstConsumer

---

apiVersion: v1

kind: PersistentVolumeClaim

metadata:

  name: ebs-claim

spec:

  accessModes:

    - ReadWriteOnce

  storageClassName: ebs-sc

  resources:

    requests:

      storage: 4Gi

---

apiVersion: v1

kind: Pod

metadata:

  name: app

spec:

  containers:

  - name: app

    image: centos

    command: ["/bin/sh"]

    args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"]

    volumeMounts:

    - name: persistent-storage

      mountPath: /data

  volumes:

  - name: persistent-storage

    persistentVolumeClaim:

      claimName: ebs-claim

創(chuàng)建


kubectl apply -f app.yaml

查看創(chuàng)建的 Volume,并查看容器生產(chǎn)文件的時間,記一下這個時間,用于等會從快照創(chuàng)建后檢查


kubectl describe pv

kubectl exec -it app cat /data/out.txt

然后從當前的 Volume Claim 創(chuàng)建 snapshot snapshot.yaml


apiVersion: snapshot.storage.k8s.io/v1alpha1

kind: VolumeSnapshot

metadata:

  name: ebs-volume-snapshot

spec:

  snapshotClassName: csi-aws-vsc

  source:

    name: ebs-claim

    kind: PersistentVolumeClaim

創(chuàng)建


kubectl apply -f snapshot.yaml

查看 snapshot 的創(chuàng)建狀態(tài),也可以在 AWS 控制臺上看到


kubectl describe volumesnapshot

等待狀態(tài)欄顯示的Ready To Use: true,即表示快照創(chuàng)建成功。

然后我們刪除 Pod 和 PersistentVolumeClaim,查看原先的 EBS 會被刪除,但是快照還在。然后我們創(chuàng)建新的 PersistentVolumeClaim,并從之前的快照中恢復(fù)數(shù)據(jù) restore-claim.yaml


apiVersion: v1

kind: PersistentVolumeClaim

metadata:

  name: ebs-snapshot-restored-claim

spec:

  accessModes:

    - ReadWriteOnce

  storageClassName: ebs-sc

  resources:

    requests:

      storage: 4Gi

  dataSource:

    name: ebs-volume-snapshot

    kind: VolumeSnapshot

    apiGroup: snapshot.storage.k8s.io

注意dataSource這段,創(chuàng)建


kubectl apply -f restore-claim.yaml

我們繼續(xù)使用這個 PersistentVolumeClaim 創(chuàng)建新的 Pod restore-pod.yaml


apiVersion: v1

kind: Pod

metadata:

  name: app

spec:

  containers:

  - name: app

    image: centos

    command: ["/bin/sh"]

    args: ["-c", "while true; do echo $(date -u) >> /data/out.txt; sleep 5; done"]

    volumeMounts:

    - name: persistent-storage

      mountPath: /data

  volumes:

  - name: persistent-storage

    persistentVolumeClaim:

      claimName: ebs-snapshot-restored-claim

待容器啟動后,我們可以查看一下生成的輸出文件,是不是包含了之前的數(shù)據(jù)。


kubectl exec -it app cat /data/out.txt

這樣就可以讓 Pod 在失敗后自動接上之前的數(shù)據(jù),但是這還是基于快照的頻率和數(shù)據(jù)更新的頻率。對于兩次快照之間的數(shù)據(jù)是沒發(fā)恢復(fù)的,就需要采取其他的措施保留并恢復(fù)了,這里就不展開了。這種用法比較適用于數(shù)據(jù)不太頻繁更新或者實時性要求不高的場景。

更多的 EBS 參數(shù)

我們可以修改創(chuàng)建的 EBS 的一些參數(shù),詳細的參數(shù)解釋可以查看 AWS 的官方文檔。主要是文件格式,EBS 類型,IOPS,加密。


kind: StorageClass

apiVersion: storage.k8s.io/v1

metadata:

  name: ebs-sc

provisioner: ebs.csi.aws.com

volumeBindingMode: WaitForFirstConsumer

parameters:

  fsType: xfs

  type: io1

  iopsPerGB: "50"

  encrypted: "true"

更復(fù)雜的場景

上面的用法主要適用于非頻繁更新的場景,如每天一更新的只讀數(shù)據(jù),這樣使用非常便于快速的橫向擴展與成本控制。但是如果是頻繁的寫更新或者實時性要求較高的場景,如日志文件的收集,數(shù)據(jù)庫等,建議的用法是使用各個應(yīng)用提供的高可用方案,而不是使用存儲卷來做高可用。例如,日志文件可以用 flufluentd 或者 logstash 等收集到 Elasticsearch 中持久化,長期備份文件可以用 NFS 或者 GlusterFS 等網(wǎng)絡(luò)存儲。對于數(shù)據(jù)庫產(chǎn)品,使用數(shù)據(jù)庫本身的集群復(fù)制、主從方案,在性能和可用性上要遠遠好于定期備份存儲卷的方案,存儲卷的備份可以作為一種輔助手段。 這篇文章就不在深入,后面有機會聊聊。

參考

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容