首先我們會學習 Volume,以及 Kubernetes 如何通過 Volume 為集群中的容器提供存儲;然后我們會實踐幾種常用的 Volume 類型并理解它們各自的應用場景;最后,我們會討論 Kubernetes 如何通過 Persistent Volume 和 Persistent Volume Claim 分離集群管理員與集群用戶的職責,并實踐 Volume 的靜態(tài)供給和動態(tài)供給。
雙江網(wǎng)站制作公司哪家好,找創(chuàng)新互聯(lián)建站!從網(wǎng)頁設計、網(wǎng)站建設、微信開發(fā)、APP開發(fā)、響應式網(wǎng)站建設等網(wǎng)站項目制作,到程序開發(fā),運營維護。創(chuàng)新互聯(lián)建站2013年開創(chuàng)至今到現(xiàn)在10年的時間,我們擁有了豐富的建站經(jīng)驗和運維經(jīng)驗,來保證我們的工作的順利進行。專注于網(wǎng)站建設就選創(chuàng)新互聯(lián)建站。
本節(jié)我們討論 Kubernetes 的存儲模型 Volume,學習如何將各種持久化存儲映射到容器。
我們經(jīng)常會說:容器和 Pod 是短暫的。
其含義是它們的生命周期可能很短,會被頻繁地銷毀和創(chuàng)建。容器銷毀時,保存在容器內(nèi)部文件系統(tǒng)中的數(shù)據(jù)都會被清除。
為了持久化保存容器的數(shù)據(jù),可以使用 Kubernetes Volume。
Volume 的生命周期獨立于容器,Pod 中的容器可能被銷毀和重建,但 Volume 會被保留。
本質(zhì)上,Kubernetes Volume 是一個目錄,這一點與 Docker Volume 類似。當 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 開始學習 Kubernetes Volume。
emptyDir 是最基礎(chǔ)的 Volume 類型。正如其名字所示,一個 emptyDir Volume 是 Host 上的一個空目錄。
emptyDir Volume 對于容器來說是持久的,對于 Pod 則不是。當 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 負責往 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)里的目錄,其效果相當于執(zhí)行了 docker run -v /producer_dir 和 docker run -v /consumer_dir。通過 docker inspect 查看容器的詳細配置信息,我們發(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 的作用是將 Docker Host 文件系統(tǒng)中已經(jīng)存在的目錄 mount 給 Pod 的容器。大部分應用都不會使用 hostPath Volume,因為這實際上增加了 Pod 與節(jié)點的耦合,限制了 Pod 的使用。不過那些需要訪問 Kubernetes 或 Docker 內(nèi)部數(shù)據(jù)(配置文件和二進制庫)的應用則需要使用 hostPath。
比如 kube-apiserver 和 kube-controller-manager 就是這樣的應用,通過
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,分別對應 Host 目錄 /etc/kubernetes、/etc/ssl/certs 和 /etc/pki。
如果 Pod 被銷毀了,hostPath 對應的目錄也還會被保留,從這點看,hostPath 的持久性比 emptyDir 強。不過一旦 Host 崩潰,hostPath 也就沒法訪問了。
真正持久性的 Volume。
如果 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ǔ)設施由獨立的存儲系統(tǒng)管理,與 Kubernetes 集群是分離的。數(shù)據(jù)被持久化后,即使整個 Kubernetes 崩潰也不會受損。
當然,運維這樣的存儲系統(tǒng)通常不是項簡單的工作,特別是對可靠性、高可用和擴展性有較高要求時。
Volume 提供了非常好的數(shù)據(jù)持久化方案,不過在可管理性上還有不足。下一節(jié)我們將學習具有更高管理性的存儲方案:PersistentVolume & PersistentVolumeClaim。
Volume 提供了非常好的數(shù)據(jù)持久化方案,不過在可管理性上還有不足。
拿前面 AWS EBS 的例子來說,要使用 Volume,Pod 必須事先知道如下信息:
Pod 通常是由應用的開發(fā)人員維護,而 Volume 則通常是由存儲系統(tǒng)的管理員維護。開發(fā)人員要獲得上面的信息:
這樣就帶來一個管理上的問題:應用開發(fā)人員和系統(tǒng)管理員的職責耦合在一起了。如果系統(tǒng)規(guī)模較小或者對于開發(fā)環(huán)境這樣的情況還可以接受。但當集群規(guī)模變大,特別是對于生成環(huán)境,考慮到效率和安全性,這就成了必須要解決的問題。
Kubernetes 給出的解決方案是 PersistentVolume 和 PersistentVolumeClaim。
PersistentVolume (PV) 是外部存儲系統(tǒng)中的一塊存儲空間,由管理員創(chuàng)建和維護。與 Volume 一樣,PV 具有持久性,生命周期獨立于 Pod。
PersistentVolumeClaim (PVC) 是對 PV 的申請 (Claim)。PVC 通常由普通用戶創(chuàng)建和維護。需要為 Pod 分配存儲資源時,用戶可以創(chuàng)建一個 PVC,指明存儲資源的容量大小和訪問模式(比如只讀)等信息,Kubernetes 會查找并提供滿足條件的 PV。
有了 PersistentVolumeClaim,用戶只需要告訴 Kubernetes 需要什么樣的存儲資源,而不必關(guān)心真正的空間從哪里分配,如何訪問等底層細節(jié)信息。這些 Storage Provider 的底層信息交給管理員來處理,只有管理員才應該關(guān)心創(chuàng)建 PersistentVolume 的細節(jié)信息。
Kubernetes 支持多種類型的 PersistentVolume,比如 AWS EBS、Ceph、NFS 等,完整列表請參考 https://kubernetes.io/docs/concepts/storage/persistent-volumes/#types-of-persistent-volumes
通過 NFS 實踐PV和PVC。
作為準備工作,我們已經(jīng)在 k8s-master 節(jié)點上搭建了一個 NFS 服務器,目錄為 /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 指定當 PV 的回收策略為 Recycle,支持的策略有:
Retain – 需要管理員手工回收。
Recycle – 清除 PV 中的數(shù)據(jù),效果相當于執(zhí)行 rm -rf /thevolume/*。
Delete – 刪除 Storage Provider 上的對應存儲資源,例如 AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume 等。
④ storageClassName 指定 PV 的 class 為 nfs。相當于為 PV 設置了一個分類,PVC 可以指定 class 申請相應 class 的 PV。
⑤ 指定 PV 在 NFS 服務器上對應的目錄。
創(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
驗證 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 服務器目錄 /nfsdata/pv1 中。
如果不再需要使用 PV,可用刪除 PVC 回收 PV。
本節(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ù)。
④ 確認數(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 服務恢復,數(shù)據(jù)也完好無損。
emptyDir 和 hostPath 類型的 Volume 很方便,但可持久性不強,Kubernetes 支持多種外部存儲系統(tǒng)的 Volume。
PV 和 PVC 分離了管理員和普通用戶的職責,更適合生產(chǎn)環(huán)境。。