Pod 本身存在生命周期,因此其內部的容器及數據均無法持久存在。對於 Stateful 的應用(如資料庫)以及日誌文件等,我們都需要持久化數據,避免在應用重啟後丟失數據。Docker 支持配置容器使用存儲卷將數據持久化到容器自身文件系統外的存儲空間之中。而 Kubernetes 提供了基於 Pod 的存儲卷功能,Kubernetes 給我們提供了很多相關的資源用於管理持久化的數據,最常用的就是 PersistentVolumes
和 PersistentVolumeClaims
,下面我們將詳細講講如何在 Kubernetes 集群中持久化數據。以下也是以目前最新的 Kubernetes v.1.14.1 版本做介紹,下面大部分的內容都是總結自官方文檔,若對基礎比較熟悉的可以跳過,直接進入實操部分。
Kubernetes 中的存儲卷稱為 Volume,每個 Pod 可以不掛載或者掛載多個 Volumes,這個 Volume 就類似於 Docker 的 Volume,但是它的概念更一般化,可以是宿主機的路徑,或者是 NFS 等網路文件系統,甚至是雲服務商提供的存儲卷等,詳細可以參考官方文檔。在 Pod 中配置 Volume 類似下面這樣
早期的 Kubernetes 將 Volume 整合在核心代碼之中,這非常不便於功能的擴展,後來在 1.9 版本就提出了使用 CSI(Container Storage Interface)作為統一的存儲卷介面(文檔),1.14.1 版本處於 GA 階段,詳細文檔可參考這裡。這樣存儲卷也就類似於 CNI 一樣,可以獨立於核心代碼,便於插件化擴展功能。
上面的方式可以快速創建存儲卷,但是這樣的用法有個問題,一般來說應用的模版是由開發人員編輯的,模版的編輯者必須知道存儲卷的詳細信息,但是不利於存儲卷的統一管理。這裡 Kubernetes 就設計了一個解藕的方案,即 PersistentVolumes
(PV) 和 PersistentVolumeClaims
(PVC),由存儲卷管理員(或者運維部門)負責管理所有的存儲卷,定義 PersistentVolumes
,這樣 volumes 就類似於 node 一樣是集群的資源,而開發人員只需要按需使用即可,聲明 PersistentVolumeClaims
,無需關心各個存儲卷的細節。
上面的解藕方式能夠區分管理員和開發人員的工作,但是每次新建存儲卷都需要管理員預先創建 PV(麻煩),或者由管理員一次創建大量 PV 供開發人員使用(浪費)。對於管理人員來說還是不太友好(不夠懶!),所以存儲類(storage class)就應運而生了。
存儲類是 Kubernetes 為管理 PV 創建的邏輯類別,類似於面向對象編程的類,而 PV 就是具體存儲類的實例。有了存儲類之後,管理員就可以預先定義許多不同類型的存儲類,例如 ssd,fast,cold 等等,而後由開發人員發起 PVC 申請創建具體的 PV 使用,不需要管理員直接參与 PV 的創建了。
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 實操手冊-安裝部署
推薦閱讀: