簡介
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ù),最常用的就是 PersistentVolumes 和 PersistentVolumeClaims,下面我們將詳細講講如何在 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 的定義部分。
常見的存儲卷類型
這里只介紹幾種常見的存儲卷類型,詳細的列表和介紹可以參考官方文檔
emptyDir 和 hostPath 屬于節(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è)置刪除存儲卷或者手動刪除。

幾個生命周期相關(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ù)制、主從方案,在性能和可用性上要遠遠好于定期備份存儲卷的方案,存儲卷的備份可以作為一種輔助手段。 這篇文章就不在深入,后面有機會聊聊。