Kubernetes 的卷 volume
容器中的文件在磁盤上是臨時存放的攀操,這給容器中運(yùn)行的特殊應(yīng)用程序帶來一些問題瘦癌。首先猪贪,容器崩潰時,kubelet 將重新啟動容器讯私,容器中的文件將會丟失热押。其次,當(dāng)在一個 Pod 中同時運(yùn)行多個容器的時候斤寇,常常需要在這些容器之間共享文件桶癣。kubernetes 抽象出 volume 對象來解決這兩個問題。
Docker 也有 volume 的概念娘锁,在 Docker 中牙寞,volume 是磁盤上或者另外一個容器內(nèi)的一個目錄。直到最近莫秆,Docker 才支持對基于本地磁盤的 volumn 的生存期進(jìn)行管理间雀。雖然 Docker 現(xiàn)在也提供 volumn 驅(qū)動程序尤慰,但是目前功能還非常有限(比如截止 Docker1.7 每個容器只允許有一個 volume 驅(qū)動程序,并且無法將參數(shù)傳遞給卷)雷蹂。
另一方面,kubernetes 卷具有明確的聲明周期--與包裹它的 Pod 不同杯道。因此匪煌,卷比 Pod 中運(yùn)行的任何容器的存活期都長,在容器重新啟動時數(shù)據(jù)也會得到保留党巾。當(dāng)然萎庭,當(dāng)一個 Pod 不再存在時,卷也將不再存在齿拂。也許更重要的是驳规,kubernetes 可以支持許多類型的卷,Pod 也能同時使用任意數(shù)量的卷署海。
卷的核心是包含一些數(shù)據(jù)的目錄吗购,Pod 中的容器可以訪問該目錄。特定的卷類型可以決定這個目錄是如何形成的砸狞,并能決定它支持何種介質(zhì)捻勉,以及目錄中存放什么內(nèi)容。
使用卷時刀森,Pod 聲明中需要提供卷的類型(通過 .spec.volumes 字段)和卷掛載的位置(通過 .spec.containers.volumeMounts 字段)
Kubernetes 提供了眾多的 volume 類型踱启,包括:emptyDir, hostPth, nfs, glusterfs, cephfs, ceph
emptyDir
當(dāng) Pod 指定到某個節(jié)點(diǎn)上時,首先創(chuàng)建的是一個 emptyDir 卷研底,并且只要 Pod 在該節(jié)點(diǎn)上運(yùn)行埠偿,卷就一直存在。當(dāng) Pod 被從節(jié)點(diǎn)刪除時榜晦,emptyDir 卷中的數(shù)據(jù)也會永久刪除冠蒋。容器崩潰并不會導(dǎo)致 Pod 被從節(jié)點(diǎn)上刪除,因此容器崩潰時乾胶,emptyDir 卷中的數(shù)據(jù)是安全的浊服。
emptyDir 的一些用途:
- 緩存空間,例如基于磁盤的歸并排序
- 為耗時較長的計(jì)算任務(wù)提供檢查點(diǎn)胚吁,以便任務(wù)能方便的從崩潰前狀態(tài)恢復(fù)執(zhí)行
- 在 web 服務(wù)器容器服務(wù)數(shù)據(jù)時牙躺,保存內(nèi)容管理器類型容器獲取的文件
hostPath
hostPath 卷能將主機(jī)節(jié)點(diǎn)文件系統(tǒng)上的文件或目錄掛載到 Pod 中。雖然這不是大多數(shù) Pod 需要的腕扶,但是它為一些應(yīng)用程序提供了強(qiáng)大的持久化能力孽拷。
vim hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
name: hostpath-pod
spec:
containers:
- name: test-container
image: nginx
volumeMounts:
# 容器內(nèi)的掛載目錄
- mountPath: /test-nginx
name: myhostpath
volumes:
- name: myhostpath
hostPath:
# 虛擬機(jī)上的掛載目錄
path: /tmp/nginx
type: DirectoryOrCreate
mkdir /tmp/nginx -p
kubectl create -f hostpath.yaml
可以看到
pod/hostpath-pod created
kubectl get po hostpath-pod
可以看到
NAME READY STATUS RESTARTS AGE
hostpath-pod 1/1 Running 0 59s
[root@node2 ~]# kubectl exec -it hostpath-pod sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
# ls
bin boot dev docker-entrypoint.d docker-entrypoint.sh etc home lib lib64 media mnt opt proc root run sbin srv sys test-nginx tmp usr var
# cd test-nginx
# touch a.txt
# echo 1 > a.txt
# exit
[root@node2 ~]# cd /tmp/nginx/
[root@node2 nginx]# ls
a.txt
[root@node2 nginx]# cat a.txt
1
[root@node2 nginx]# echo 2 > a.txt
[root@node2 nginx]# kubectl exec -it hostpath-pod sh
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
# ls
bin boot dev docker-entrypoint.d docker-entrypoint.sh etc home lib lib64 media mnt opt proc root run sbin srv sys test-nginx tmp usr var
# cd test-nginx
# pwd
/test-nginx
# ls
a.txt
# cat a.txt
2
# exit
[root@node2 nginx]#
掛載 NFS 卷
應(yīng)用場景:很多應(yīng)用需要在集群內(nèi)部有一個統(tǒng)一的地方存儲文件,比如圖片半抱,日志等脓恕。而使用 hostpath 的方式不夠靈活膜宋,因?yàn)樾枰付?host 地址。
-
在 master 節(jié)點(diǎn)安裝 nfs 服務(wù)
yum install -y nfs-utils rpcbind
-
在 master 節(jié)點(diǎn)配置共享目錄
mkdir /nfsdata
vim /etc/exports
/nfsdata *(rw,sync,no_root_squash)
-
在 master 節(jié)點(diǎn)配置自動啟動
systemctl enable --now rpcbind systemctl enable --now nfs
-
在 master 節(jié)點(diǎn)激活配置
exportfs -r
-
查看掛載效果
showmount -e master
可以看到
Export list for master: /nfsdata *
-
在 node2 節(jié)點(diǎn)安裝 nfs 服務(wù)
yum install -y nfs-utils rpcbind
-
在 node2 節(jié)點(diǎn)創(chuàng)建 Pod 炼幔,引用 NFS 存儲
vim nfs.yaml
apiVersion: v1 kind: Pod metadata: name: nfs-pd spec: containers: - name: test-container image: nginx volumeMounts: # node 節(jié)點(diǎn)掛載的目錄 - mountPath: /usr/share/nginx/html name: test-volume volumes: - name: test-volume nfs: server: master # master 節(jié)點(diǎn)掛載的目錄 path: /nfsdata
kubectl apply -f nfs.yaml
可以看到
pod/nfs-pd created
-
進(jìn)入 pod
[root@node2 ~]# kubectl exec -it nfs-pd sh kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead. # mount |grep nfs master:/nfsdata on /usr/share/nginx/html type nfs4 (rw,relatime,vers=4.1,rsize=524288,wsize=524288,namlen=255,hard,proto=tcp,timeo=600,retrans=2,sec=sys,clientaddr=192.168.190.133,local_lock=none,addr=192.168.190.131) # cd /usr/share/nginx/html # pwd /usr/share/nginx/html # ls # touch a.txt # echo hello kubernetes volume > a.txt # cat a.txt hello kubernetes volume # exit [root@node2 ~]#
-
進(jìn)入 master 節(jié)點(diǎn)
[root@master ~]# cd /nfsdata/ [root@master nfsdata]# ls a.txt [root@master nfsdata]# cat a.txt hello kubernetes volume [root@master nfsdata]#
持久化存儲
存儲的管理是一個與計(jì)算實(shí)例的管理完全不同的問題秋茫。 PersistentVolume 子系統(tǒng)為用戶和管理員提供了一組 API ,將存儲如何供應(yīng)的細(xì)節(jié)從其如何被使用中抽象出來乃秀。為了實(shí)現(xiàn)這點(diǎn)肛著,我們引入了兩個新的 API 資源 PersistenVolume 和 PersistenVolumeClaim。
持久卷(PersistenVolume, PV)是集群中的一塊存儲跺讯,可以有管理員事先供應(yīng)枢贿,或者使用存儲類(Storage Class)來動態(tài)供應(yīng)。持久卷是集群資源刀脏,就像節(jié)點(diǎn)也是集群資源一樣局荚。PV 和普通的 volume 一樣,也是使用卷插件來實(shí)現(xiàn)的愈污,只是它們擁有獨(dú)立于任何使用 PV 的 Pod 的生命周期耀态。此 API 對象中記述了存儲的實(shí)現(xiàn)細(xì)節(jié),無論其背后是 NFS, iSCSI 還是特定于云平臺的存儲系統(tǒng)暂雹。
創(chuàng)建 PV
vim pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-demo
spec:
# 容量
capacity:
storage: 5Gi
accessModes:
# 每次允許一個 worknode 讀寫
- ReadWriteOnce
# 回收策略:可回收
persistentVolumeReclaimPolicy: Recycle
nfs:
# 掛載路徑
path: /nfsdata
server: master
kubectl apply -f pv.yaml
可以看到
persistentvolume/pv-demo created
kubectl get pv pv-demo
可以看到
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-demo 1Gi RWO Recycle Available 53s
持久卷申領(lǐng)
持久卷申領(lǐng)(PersistenVolumeClaim)
每個 PVC 對象都有 spec 和 status 部分茫陆,分別對應(yīng) Claim 的規(guī)定和狀態(tài)
vim pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-demo
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
# 申領(lǐng)的容量
storage: 2Gi
kubectl apply -f pvc.yaml
可以看到
persistentvolumeclaim/pvc-demo created
kubectl get pvc pvc-demo
可以看到
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-demo Bound pv-demo 5Gi RWO 10s
kubectl get pv pv-demo
可以看到 ,PV 的狀態(tài)由 Available 變成了 Bound
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-demo 5Gi RWO Recycle Bound default/pvc-demo 61s
存儲類
什么是 StorageClass
Kubernetes 提供了一套可以自動創(chuàng)建 PV 的機(jī)制:Dynamic Provisioning 擎析。這個機(jī)制的核心是 StorageClass 這個API 對象簿盅。
StorageClass 對象會定義下面兩部分內(nèi)容:
- PV 的屬性,比如存儲類型揍魂,volume 的大小等
- 創(chuàng)建這種 PV 需要用到的存儲插件
為什么需要 StorageClass
在一個大規(guī)模的 kubernetes 集群里桨醋,可能有成千上萬個 PVC,這就意味著必須創(chuàng)建出多個 PV 现斋,而且隨著項(xiàng)目的需要喜最,會有新的 PVC 不斷被提交,那么就需要不斷添加新的 PV 庄蹋,否則新的 Pod 就會因?yàn)?PVC 綁定不到 PV 而創(chuàng)建失敗瞬内。不同的應(yīng)用程序?qū)τ诖鎯π阅艿囊蟛槐M相同,比如讀寫速度限书、并發(fā)性能等虫蝶。通過 StorageClass ,可以將存儲資源定義為某種類型的資源倦西,比如快速存儲能真、慢速存儲等,這樣就可以根據(jù)應(yīng)用的特性去申請合適的存儲資源了。
NFS Provisioner
NFS Provisioner 是一個自動配置卷程序粉铐,它使用現(xiàn)有的 NFS 服務(wù)器來支持通過持久卷聲明動態(tài)配置 kubernetes 持久卷疼约。
持久卷配置為:{namespace}-{pvcName}-{pvName}
在下面的實(shí)例中,通過該容器定義 ENV 變量:PROVISONER_NAME 為 qgg-nfs-storage蝙泼,該容器會動態(tài)生產(chǎn)一個名為 qgg-nfs-storage 的持久卷程剥,從而實(shí)現(xiàn)持久卷的動態(tài)創(chuàng)建。
實(shí)踐 PV, PVC 掛載 NFS
在 node2 節(jié)點(diǎn):
創(chuàng)建 Persistent Volume Provisioner
vim nfs-provisioner.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: registry.cn-beijing.aliyuncs.com/qingfeng666/nfs-client-provisioner:v3.1.0
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: qgg-nfs-storage
- name: NFS_SERVER
value: master
- name: NFS_PATH
value: /nfsdata
volumes:
- name: nfs-client-root
nfs:
server: master
path: /nfsdata
kubectl apply -f nfs-provisioner.yaml
可以看到
deployment.apps/nfs-client-provisioner created
創(chuàng)建用戶和角色
vim rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
namespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
namespace: default
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
kubectl apply -f rbac.yaml
可以看到
serviceaccount/nfs-client-provisioner created
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
創(chuàng)建 StorageClass
vim storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
provisioner: qgg-nfs-storage
parameters:
archiveOnDelete: "false"
kubectl apply -f storageclass.yaml
可以看到
storageclass.storage.k8s.io/managed-nfs-storage created
kubectl get storageclass.storage.k8s.io/managed-nfs-storage
可以看到
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
managed-nfs-storage qgg-nfs-storage Delete Immediate false 101s
創(chuàng)建 PVC
vim 8-6-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim
annotations:
volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi
kubectl apply -f 8-6-pvc.yaml
可以看到
persistentvolumeclaim/test-claim created
kubectl get pvc test-claim
可以看到
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-claim Bound pvc-bcdc3799-27bc-4c38-a335-4050ac611fe5 1Mi RWX managed-nfs-storage 72s
kubectl get po
可以看到
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6976b6b79c-hdqm9 1/1 Running 0 13m
kubectl get storageclass
可以看到
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
managed-nfs-storage qgg-nfs-storage Delete Immediate false 9m56s
my-storage-class kubernetes.io/no-provisioner Delete WaitForFirstConsumer false 29h
創(chuàng)建測試 Pod
vim 8-6-pod.yaml
kind: Pod
apiVersion: v1
metadata:
name: test-pod
spec:
containers:
- name: test-pod
image: nginx
command:
- "/bin/sh"
args:
- "-c"
- "touch /mnt/SUCCESS && exit 0 || exit 1"
volumeMounts:
- name: nfs-pvc
mountPath: "/mnt"
restartPolicy: "Never"
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-claim
kubectl apply -f 8-6-pod.yaml
可以看到
pod/test-pod created
kubectl get po
可以看到 test-pod 的 狀態(tài)是 Completed汤踏,說明任務(wù)已經(jīng)執(zhí)行完成织鲸。
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6976b6b79c-hdqm9 1/1 Running 0 20m
test-pod 0/1 Completed 0 80s
在 master 節(jié)點(diǎn):
cd /nfsdata
ls
可以看到
[root@master nfsdata]# ls
default-test-claim-pvc-bcdc3799-27bc-4c38-a335-4050ac611fe5
[root@master nfsdata]# cd default-test-claim-pvc-bcdc3799-27bc-4c38-a335-4050ac611fe5/
[root@master default-test-claim-pvc-bcdc3799-27bc-4c38-a335-4050ac611fe5]# ls
SUCCESS