簡介

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

Kubernetes 中的持久化存儲設計

Kubernetes 中的存儲卷稱為 Volume,每個 Pod 可以不掛載或者掛載多個 Volumes,這個 Volume 就類似於 Docker 的 Volume,但是它的概念更一般化,可以是宿主機的路徑,或者是 NFS 等網路文件系統,甚至是雲服務商提供的存儲卷等,詳細可以參考官方文檔。在 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 欄位相關聯。其他類型的存儲卷配置也是一樣,不同的就是 volumes 的定義部分。

常見的存儲卷類型

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

emptyDirhostPath 屬於節點級別的卷類型,他們依賴於特定的節點。emptyDir 的生命周期與 Pod 一致,在 Pod 刪除後會一併刪除,主要用於 Pod 內部多容器之間共享數據,hostPath 就是將節點的目錄直接掛載到 Pod 上,但是如果 Pod 被調度到其他節點,那麼數據將不可用。而 nfs,cephfs,glusterfs 這些是常用的網路文件系統,可以供多個 Pod 同時鏈接。但是首先需要部署相應的網路文件系統,而且相對的性能會比較差,對於 IO 要求高的應用不太適合。另外還有公有雲服務商提供的存儲卷,如 AWS 的 ElasticBlockStore,Azure 的 AzureDisk 等,可以使用基於公有雲的存儲服務提供滿足需求的存儲卷類型。

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

存儲卷解藕

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

例如,管理員可以按如下模版定義 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 的狀態是 Available。

然後開發人員使用如下模版定義 PVC

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-pvc
labels:
type: test
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: "5Gi"
selector:
matchLabels:
type: test

然後查看 PV 的狀態就是 Bound 了。

參數詳解

PV

  1. capacity: 指定 PV 的容量,目前只支持 storage 指定空間大小
  2. accessModes: 指定 PV 的訪問模式,有以下幾種,PVC 指定的模式必須與對應的 PV 一致
    1. ReadWriteOnce: 僅可被耽擱節點掛載讀寫,簡寫為 RWO
    2. ReadOnlyMany: 可被多個節點同時只讀掛載,簡寫為 ROX
    3. ReadWriteMany: 可被多個節點同時讀寫掛載,簡寫為 RWX
  3. persistentVolumeReclaimPolicy: PV 空間釋放時的處理機制,有以下幾種 a. Retain: 保持不動 b. Recycle: 回收空間,即刪除所有文件,僅部分類型支持 c. Delete: 刪除存儲卷,僅部分雲存儲支持
  4. volumeMode: 卷類型,用作文件系統還是裸格式的塊設備,默認文件系統 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 必須位於同一個命名空間。

存儲類

上面的解藕方式能夠區分管理員和開發人員的工作,但是每次新建存儲卷都需要管理員預先創建 PV(麻煩),或者由管理員一次創建大量 PV 供開發人員使用(浪費)。對於管理人員來說還是不太友好(不夠懶!),所以存儲類(storage class)就應運而生了。

存儲類是 Kubernetes 為管理 PV 創建的邏輯類別,類似於面向對象編程的類,而 PV 就是具體存儲類的實例。有了存儲類之後,管理員就可以預先定義許多不同類型的存儲類,例如 ssd,fast,cold 等等,而後由開發人員發起 PVC 申請創建具體的 PV 使用,不需要管理員直接參与 PV 的創建了。

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: ebs-sc
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer

定義如上的 storage class,在需要時創建 AWS 的 EBS 作為存儲卷,然後開發人員用 PVC 申請即可。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ebs-claimspec:
accessModes:
- ReadWriteOnce
storageClassName: ebs-sc
resources:
requests:
storage: 4Gi

這樣就實現了存儲卷的動態供給。

參數詳解

  1. provisioner: 存儲卷供給方
  2. reclaimPolicy: 存儲卷回收策略
    1. Delete: 刪除,默認
    2. Retain: 保持不動,需要手動刪除
  3. parameters: 其他參數,根據供給方不同而不同
  4. mountOptions: 掛載選項列表
  5. volumeBindingMode: 控制綁定的方式 a. Immediate: 當 PVC 創建時立即創建,默認 b. WaitForFirstConsumer: 延遲到當 Pod 使用時創建

存儲卷生命周期

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

PV 生命周期

幾個生命周期相關的配置項如下:

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

生產實操

Kubernetes 使用 AWS EBS 作為存儲

簡介

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

前提

項目頁面介紹了需要使用 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 的啟動參數上增加--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 的參數不用修改,1.14.1 版本的 kubelet 默認已啟用。

安裝

配置許可權

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

{
"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。

部署驅動

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

查看驅動是否運行

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

使用

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

動態分配

動態分配是由管理員創建存儲類模版,而用戶使用時只需要從對應的存儲類中創建申請即可,用戶不需要關心每個存儲類的具體細節。

創建 storage class 模版storageclass.yaml

kind: StorageClassapiVersion: storage.k8s.io/v1
metadata:
name: ebs-sc
provisioner: ebs.csi.aws.com
volumeBindingMode: WaitForFirstConsumer

創建

kubectl apply -f storageclass.yaml

創建 PersistentVolumeClaim 存儲申請模版claim.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: ebs-claim
spec:
accessModes:
- ReadWriteOnce
storageClassName: ebs-sc
resources:
requests:
storage: 4Gi

創建

kubectl apply -f claim.yaml

查看 PersistentVolumeClaim 的狀態,顯示為 Pending,有容器掛載後即可使用。

kubectl get pvc

創建一個測試用的 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

創建

kubectl apply -f pod.yaml

等待片刻,查看 PersistentVolumeClaim 和 PersistentVolume,即可看到狀態顯示為Bound,且多了一塊新創建的 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

進入剛才創建的 Pod

kubectl exec app -ti bash

可以使用df -h命令看到掛載的磁碟在/data目錄,且裡面已經有內容在輸出。

從快照創建盤

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

創建一個 snapshot class 資源snapshotclass.yaml

apiVersion: snapshot.storage.k8s.io/v1alpha1
kind: VolumeSnapshotClass
metadata:
name: csi-aws-vsc
snapshotter: ebs.csi.aws.com

創建

kubectl apply -f snapshotclass.yaml

像上面一樣創建一個 Pod 並綁定一個 EBS,用於創建快照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

創建

kubectl apply -f app.yaml

查看創建的 Volume,並查看容器生產文件的時間,記一下這個時間,用於等會從快照創建後檢查

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

然後從當前的 Volume Claim 創建 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

創建

kubectl apply -f snapshot.yaml

查看 snapshot 的創建狀態,也可以在 AWS 控制台上看到

kubectl describe volumesnapshot

等待狀態欄顯示的Ready To Use: true,即表示快照創建成功。

然後我們刪除 Pod 和 PersistentVolumeClaim,查看原先的 EBS 會被刪除,但是快照還在。然後我們創建新的 PersistentVolumeClaim,並從之前的快照中恢複數據 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這段,創建

kubectl apply -f restore-claim.yaml

我們繼續使用這個 PersistentVolumeClaim 創建新的 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

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

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

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

更多的 EBS 參數

我們可以修改創建的 EBS 的一些參數,詳細的參數解釋可以查看 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"

更複雜的場景

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

參考

  • Kubernetes Volumes
  • Kubernetes Persistent Volumes
  • Kubernetes Storage Classes
  • AWS EBS CSI Driver
  • AWS EBS
  • Kubernetes 實操手冊-安裝部署

推薦閱讀:

相关文章