Background
K8s的卷可以理解爲是pod的一個組成部分,不是獨立的的資源,pod的所有容器都可以使用卷,但是必須先掛載在每個需要訪問它的容器中。
換句話說,不同的container的某個路徑只要掛載到同一個捲上,這幾個container就可以實現對磁盤空間的共享
可用的Volume的類型
1.本地存儲類型:emptydir/hostpath
兩者區別在於後者的數據其實是會存儲在宿主機的一個特定路徑上,pod刪除之後,目錄仍然會存在,數據也不會丟失。
2.網絡存儲: 實現是給K8s本身解耦,通過抽象接口把不同存儲的driver實現從K8s代碼倉庫中剝離
3.Projected Volumes:把配置信息如secret/configmap用卷的形式掛載在容器中,讓容器中的程序可以通過POSIX接口來訪問配置數據
4.PV 和PVC
- emptyDir
用於存儲臨時數據的簡單空目錄,主要用於同一個Pod的運行容器之間共享文件,也可以用於單個容器用於把數據臨時寫入磁盤中。
下面的配置文件,創建了兩個都掛載在名爲html的Volume的pod
apiVersion: v1
kind: Pod
metadata:
name: fortune
spec:
containers:
- image: luksa/fortune
name: html-generator
volumeMounts:
- name: html
mountPath: /var/htdocs
- image: nginx:alpine
name: web-server
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
emptyDir: {}
當然你可以指定EMPTYDIR的介質,用磁盤還是內存都可
volumes:
- name: html
emptyDir:
medium: Memory
訪問工作節點文件系統上的文件
- hostPath
把目錄從工作節點的文件系統掛載到pod中
屬於持久性存儲卷,pod刪除後卷不會被刪除
Pod通常使用hostPath來訪問節點上的日誌文件,kubeconfig或者CA證書
但是如果Pod被調度到別的節點上後,就不能訪問原來掛載在工作節點上的數據了
持久化存儲
首先爲什麼要引入PV這個概念?
1.要保證Pod調度到別的節點上後,仍然能訪問之前節點的數據,也就是說數據要可以提供給集羣中任何節點訪問.例如宿主機宕機,需要把Pod遷移到別的節點上,Statefulset管理的pod,實現了帶卷遷移的語義,這時候通過Pod Volumes是做不到的
2.pod的重建銷燬,例如用deployment管理的pod,在做鏡像升級的時候,會產生新的pod並且刪除舊的pod,那麼新舊pod之間如何複用數據?
3.同一個pod內的多個容器如果要共享數據,可以用Pod Volumes來解決,當多個pod要共享數據的時候,Pod Volumes就很難表達這種語義
4.對卷做功能擴展
換句話說,Volumes難以實現複用和共享功能
因此我們引入Persistent Volumes概念,可以把存儲和計算分離開來,通過不同的組件來管理存儲資源和計算資源,然後解耦pod和Volume之間生命週期的關聯,這樣把pod刪除之後,使用的PV不會被銷燬,還可以被新的pod複用。
因爲我這裏用的是minikube,不支持GCE硬盤來做持久化存儲,因此這裏用hostPath做持久化存儲,把容器內/tmp/mongodb的數據持久化存儲在宿主節點的/data/db上
apiVersion: v1
kind: Pod
metadata:
name: mongodb
spec:
volumes:
- name: mongodb-data
hostPath:
path: /tmp/mongodb
containers:
- image: mongo
name: mongodb
volumeMounts:
- name: mongodb-data
mountPath: /data/db
ports:
- containerPort: 27017
protocol: TCP
除了使用硬盤存儲,hostPath之外,還可以使用nfs
持久卷聲明PVC
如果要創建支持NFS協議的卷,開發人員必須知道NFS節點所在的實際服務器,這違背了K8s的基本理念,基本理念旨在嚮應用程序以及開發人員隱藏真實的基礎設施
爲什麼要引入PVC?
- 職責分離,PVC中只用聲明自己需要的存儲size,access mode(節點獨佔還是共享,只讀還是讀寫訪問),開發者不需要關心存儲需求,PV和對應後端存儲信息交給cluster admin統一運維和管控
- PVC簡化了User對存儲的需求,PV纔是存儲的實際信息的承載體,通過kube-controller-manager的PersistentVolumeController把PVC與合適的PV綁定在一起,從而滿足User對存儲的實際需求。
因此引入了PVC和PV這個概念
- 集羣管理員設置底層存儲
- 通過K8s API傳遞PV聲明創建持久卷
- 用戶創建持久卷聲明
- K8s找到一個足夠容量的PV並且將其置於訪問模式,並且將PVC綁定到PV
- 用戶創建一個pod並通過卷配置引用PVC
首先創建一個PV
persistentVolumeReclaimPolicy:也有三種策略,這個策略是當與之關聯的PVC被刪除以後,這個PV中的數據如何被處理
Retain 當刪除與之綁定的PVC時候,這個PV被標記爲released(PVC與PV解綁但還沒有執行回收策略)且之前的數據依然保存在該PV上,但是該PV不可用,需要手動來處理這些數據並刪除該PV。
Delete 當刪除與之綁定的PVC時候,刪除底層存儲
accessModes:支持三種類型
ReadWriteMany 多路讀寫,卷能被集羣多個節點掛載並讀寫
ReadWriteOnce 單路讀寫,卷只能被單一集羣節點掛載讀寫
ReadOnlyMany 多路只讀,卷能被多個集羣節點掛載且只能讀
apiVersion: v1
kind: PersistentVolume
metadata:
name: mongodb-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain
hostPath:
path: /tmp/mongodb
然後通過創建持久卷聲明來獲取持久卷
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongodb-pvc
spec:
resources:
requests:
storage: 1Gi
accessModes:
- ReadWriteOnce
storageClassName: ""
注意PV不屬於任何命名空間,屬於控制平面層級的資源,而PVC和pod一樣,都要屬於某個命名空間
然後在Pod中使用持久卷聲明
全稱是Persistent Volume Claim,也就是持久化存儲聲明。開發人員使用這個來描述該容器需要一個什麼存儲。
apiVersion: v1
kind: Pod
metadata:
name: mongodb
spec:
containers:
- image: mongo
name: mongodb
volumeMounts:
- name: mongodb-data
mountPath: /data/db
ports:
- containerPort: 27017
protocol: TCP
volumes:
- name: mongodb-data
persistentVolumeClaim:
claimName: mongodb-pvc
小結一下,引入PV和PVC的目的是爲了解耦Pod與底層的存儲技術,研發人員不需要關心底層究竟使用哪種技術作爲存儲後端,這部分是由集羣管理員來做的.
研發人員只需要聲明你所需要存儲空間的大小即可,不需要了解底層存儲細節。
PV對象怎麼產生?
- 靜態分配PV對象
集羣管理員事先規劃這個集羣的用戶會怎樣使用存儲,即預分配一些存儲,也就是預先創建一些PV
然後開發者聲明PVC的時候,會把PVC和相應PV做綁定
之後Pod需要使用存儲的時候,就可以通過PVC找到相應的PV
- 動態分配PV對象
Cluster-admin 不需要預先分配PV,先寫一個模板文件(Storage Class),用來表示創建某一類型存儲如塊存儲,文件存儲所需的一些參數。
那麼用戶在聲明PVC的時候,需要指定使用的StorageClass模板
K8s會結合PVC和SC,生成用戶所需要的PV,把PV和PVC綁定。
然後pod就可以使用PV了。
換句話說,通過StorageClass配置生成存儲所需要的存儲模板,再結合用戶的需求動態創建PV對象,做到按需分配。
持久卷的動態卷配置
PV是由運維人員創建的,而PVC是由開發人員創建的,那麼大規模集羣中可能會有很多PV,如果這些PV都需要手動管理,將會十分繁瑣,因此就有了動態供給概念,核心就是StorageClass,作用是創建PV模板
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: k8s.io/minikube-hostpath
parameters:
type: pd-ssd
那最終效果就是,用戶提交PVC,裏面指定存儲類型,如果符合定義的StorageClass,則會自動創建PV並且進行綁定.
如下,在創建PVC的時候,可以指定StorageClass的名稱,那麼在創建pvc後,由provisioner來指定真實的存儲,創建一個持久卷,並將其綁定到PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongodb-pvc
spec:
storageClassName: fast
resources:
requests:
storage: 100Mi
accessModes:
- ReadWriteOnce
架構設計
csi全稱container storage interface
是K8s社區對存儲插件實現的官方推薦方式
根據上圖,PV provision/attach過程:
- (1)用戶提交PVCyaml,在集羣(api-server)中生成一個PVC對象
- (2)PVC對象會被csi-provisioner controller 監控到
- (3)csi-provisioner 結合PVC對象和PVC對象中聲明的storageClass,通過gRPC調用csi-controller-server,
- (4)然後到雲存儲服務端創建真正的存儲,並最終創建出PV對象
- (5)由集羣的PV controller把PVC和PV對象bound,然後就可以使用PV了
(1)用戶提交pod之後
(2)會被調度器調度選中一個合適的node
(3)在該node上面的kubelet在創建pod過程
(4)首先會通過csi-node-server將之前創建的PV掛載到pod可以使用的路徑
(6)kubelet的docker daemon創建container
總結:
創建存儲
- 用戶提交完 PVC,由 csi-provisioner 創建存儲,並生成 PV 對象,之後 PV controller 將 PVC 及生成的 PV 對象做 bound,bound 之後,create 階段就完成了
存儲掛載到節點上
- 用戶在提交 pod yaml 的時候,首先會被調度選中某一個 合適的node,等 pod 的運行 node 被選出來之後,會被 AD Controller watch 到 pod 選中的 node,它會去查找 pod 中使用了哪些 PV。然後它會生成一個內部的對象叫 VolumeAttachment 對象,從而去觸發 csi-attacher去調用csi-controller-server 去做真正的 attache 操作,attach操作調到雲存儲廠商OpenAPI。這個 attach 操作就是將存儲 attach到 pod 將會運行的 node 上面。第二個階段 —— attach階段完成
將對應的PV進一步掛載到 pod 可以使用的路徑
發生在kubelet 創建 pod的過程中,它在創建 pod 的過程中,首先要去做一個 mount,這裏的 mount 操作是爲了將已經attach到這個 node 上面那塊盤,進一步 mount 到 pod 可以使用的一個具體路徑,之後 kubelet 纔開始創建並啓動容器。這就是 PV 加 PVC 創建存儲以及使用存儲的第三個階段 —— mount 階段。
ref
https://gitbook.cn/gitchat/column/5d68b823de93ed72d6eca1bc/topic/5d8aff4f49b2b1063b559481