1. 簡介
我們都知道 Container
中的檔案在磁碟上是臨時存放的,這給 Container 中執行的較重要的應用 程式帶來一些問題。
- 是當容器崩潰時檔案丟失。(kubelet 會重新啟動容器, 但容器會以乾淨的狀態重啟)
- 在同一
Pod
中執行多個容器如何共享檔案
Kubernetes 卷(Volume)這一抽象概念能夠解決這兩個問題。
2. 背景
Docker 也有 卷(Volume)的概念,但對它只有少量且鬆散的管理。 Docker 卷是磁碟上或者另外一個容器內的一個目錄。 Docker 提供卷驅動程式,但是其功能非常有限。
Kubernetes 支援很多型別的卷。 Pod 可以同時使用任意數目的卷型別。 臨時卷型別的生命週期與 Pod 相同,但持久卷可以比 Pod 的存活期長。 當 Pod 不再存在時,Kubernetes 也會銷燬臨時卷;不過 Kubernetes 不會銷燬 持久卷。對於給定 Pod 中任何型別的卷,在容器重啟期間資料都不會丟失。
卷的核心是一個目錄,其中可能存有資料,Pod 中的容器可以訪問該目錄中的資料。 所採用的特定的卷型別將決定該目錄如何形成的、使用何種介質儲存資料以及目錄中存放 的內容。
使用卷時, 在 .spec.volumes
欄位中設定為 Pod 提供的卷,並在 .spec.containers[*].volumeMounts
欄位中宣告卷在容器中的掛載位置。 容器中的程式看到的是由它們的 Docker 映象和卷組成的檔案系統檢視。 Docker 映象 位於檔案系統層次結構的根部。各個卷則掛載在映象內的指定路徑上。 卷不能掛載到其他卷之上,也不能與其他卷有硬連結。Pod 配置中的每個容器必須獨立指定各個卷的掛載位置。
3. emptyDir
當 Pod 分派到某個 Node 上時,emptyDir
卷會被建立,並且在 Pod 在該節點上執行期間,卷一直存在。 就像其名稱表示的那樣,卷最初是空的。 儘管 Pod 中的容器掛載 emptyDir
卷的路徑可能相同也可能不同,這些容器都可以讀寫 emptyDir
卷中相同的檔案。 當 Pod 因為某些原因被從節點上刪除時,emptyDir
卷中的資料也會被永久刪除。
說明: 容器崩潰並不會導致 Pod 被從節點上移除,因此容器崩潰期間 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 3000
- image: busybox
name: consumer
volumeMounts:
- mountPath: /consumer_dir
name: shared-volume
args:
- /bin/sh
- -c
- cat /consumer_dir/hello; sleep 3000
volumes:
- name: shared-volume
emptyDir: {}
建立一個pod,其中有兩個container,一個是producer,另一個是consumer,producer負責生成hello檔案並且寫入內容hello world
,consumer 則負責讀取hello檔案中的內容,驗證同一pod中的container儲存共享機制。
因為 emptyDir 是 Docker Host 檔案系統裡的目錄,其效果相當於在k8s-woker-01上執行了 docker run -v /producer_dir
和 docker run -v /consumer_dir
。通過 docker inspect
檢視容器的詳細配置資訊,我們發現兩個容器都 mount 了同一個目錄:
$ docker inspect 52305cbb0dec
$ docker inspect c946762ccc8c
這裡Source
指定的路徑就是 emptyDir 在 Host 上的真正路徑。
emptyDir 是 Host 上建立的臨時目錄,其優點是能夠方便地為 Pod 中的容器提供共享儲存,不需要額外的配置。但它不具備永續性,如果 Pod 不存在了,emptyDir 也就沒有了。根據這個特性,emptyDir 特別適合 Pod 中的容器需要臨時共享儲存空間的場景,比如前面的生產者消費者用例。
4. hostPath
警告:
HostPath 卷存在許多安全風險,最佳做法是儘可能避免使用 HostPath。 當必須使用 HostPath 卷時,它的範圍應僅限於所需的檔案或目錄,並以只讀方式掛載。
hostPath
卷能將主機節點檔案系統上的檔案或目錄掛載到你的 Pod 中。 雖然這不是大多數 Pod 需要的,但是它為一些應用程式提供了強大的逃生艙。
例如,hostPath
的一些用法有:
- 執行一個需要訪問 Docker 內部機制的容器;可使用
hostPath
掛載/var/lib/docker
路徑。 - 允許 Pod 指定給定的
hostPath
在執行 Pod 之前是否應該存在,是否應該建立以及應該以什麼方式存在。
除了必需的 path
屬性之外,使用者可以選擇性地為 hostPath
卷指定 type
。
支援的 type
值如下:
取值 | 行為 |
---|---|
空字串(預設)用於向後相容,這意味著在安裝 hostPath 卷之前不會執行任何檢查。 | |
DirectoryOrCreate |
如果在給定路徑上什麼都不存在,那麼將根據需要建立空目錄,許可權設定為 0755,具有與 kubelet 相同的組和屬主資訊。 |
Directory |
在給定路徑上必須存在的目錄。 |
FileOrCreate |
如果在給定路徑上什麼都不存在,那麼將在那裡根據需要建立空檔案,許可權設定為 0644,具有與 kubelet 相同的組和所有權。 |
File |
在給定路徑上必須存在的檔案。 |
Socket |
在給定路徑上必須存在的 UNIX 套接字。 |
hostPath 配置示例:
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:
# 宿主上目錄位置
path: /data
# 此欄位為可選
type: Directory
注意: FileOrCreate
模式不會負責建立檔案的父目錄。 如果欲掛載的檔案的父目錄不存在,Pod 啟動會失敗。 為了確保這種模式能夠工作,可以嘗試把檔案和它對應的目錄分開掛載,如 FileOrCreate
配置所示。
hostPath FileOrCreate 配置示例
apiVersion: v1
kind: Pod
metadata:
name: test-webserver
spec:
containers:
- name: test-webserver
image: k8s.gcr.io/test-webserver:latest
volumeMounts:
- mountPath: /var/local/aaa
name: mydir
- mountPath: /var/local/aaa/1.txt
name: myfile
volumes:
- name: mydir
hostPath:
# 確保檔案所在目錄成功建立。
path: /var/local/aaa
type: DirectoryOrCreate
- name: myfile
hostPath:
path: /var/local/aaa/1.txt
type: FileOrCreate
5. local
local
卷所代表的是某個被掛載的本地儲存裝置,例如磁碟、分割槽或者目錄。
local
卷只能用作靜態建立的持久卷。尚不支援動態配置。
與 hostPath
卷相比,local
卷能夠以持久和可移植的方式使用,而無需手動將 Pod 排程到節點。系統通過檢視 PersistentVolume 的節點親和性配置,就能瞭解卷的節點約束。
然而,local
卷仍然取決於底層節點的可用性,並不適合所有應用程式。 如果節點變得不健康,那麼local
卷也將變得不可被 Pod 訪問。使用它的 Pod 將不能執行。 使用 local
卷的應用程式必須能夠容忍這種可用性的降低,以及因底層磁碟的耐用性特徵 而帶來的潛在的資料丟失風險。
下面是一個使用 local
卷和 nodeAffinity
的持久卷示例:
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-pv
spec:
capacity:
storage: 100Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/disks/ssd1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- example-node
使用 local
卷時,你需要設定 PersistentVolume 物件的 nodeAffinity
欄位。 Kubernetes 排程器使用 PersistentVolume 的 nodeAffinity
資訊來將使用 local
卷的 Pod 排程到正確的節點。
PersistentVolume 物件的 volumeMode
欄位可被設定為 "Block" (而不是預設值 "Filesystem"),以將 local
卷作為原始塊裝置暴露出來。
使用 local
卷時,建議建立一個 StorageClass 並將其 volumeBindingMode
設定為 WaitForFirstConsumer
。 延遲卷繫結的操作可以確保 Kubernetes 在為 PersistentVolumeClaim 作出繫結決策時, 會評估 Pod 可能具有的其他節點約束,例如:如節點資源需求、節點選擇器、Pod 親和性和 Pod 反親和性。
你可以在 Kubernetes 之外單獨運行靜態驅動以改進對 local 卷的生命週期管理。 請注意,此驅動尚不支援動態配置。
說明: 如果不使用外部靜態驅動來管理卷的生命週期,使用者需要手動清理和刪除 local 型別的持久卷。
6. subPath
- 同一個pod中多容器掛載同一個卷時提供隔離
- 將configMap和secret作為檔案掛載到容器中而不覆蓋掛載目錄下的檔案
6.1 同一pod中多容器掛載同一個卷時提供隔離
先建立一個共享資源的 pod 用於 設定subPath
的前後對照比較
暫未設定
subPath
,通過hostPath
的方式將檔案掛載到了叢集節點上
apiVersion: v1
kind: Pod
metadata:
name: hostpath-test
spec:
containers:
- image: busybox
name: test-c-01
volumeMounts:
- mountPath: /opt/test
name: shared-volume
args:
- /bin/sh
- -c
- sleep 3000
- image: busybox
name: test-c-02
volumeMounts:
- mountPath: /opt/test
name: shared-volume
args:
- /bin/sh
- -c
- sleep 3000
volumes:
- name: shared-volume
hostPath:
path: /test
type: DirectoryOrCreate
演示步驟如圖所示:
此時pod 中容器掛載的卷是共享的
宿主機上的掛載情況如下
接下來加入subPath
配置,再來檢視卷共享的情況
資原始檔內容如下:
apiVersion: v1
kind: Pod
metadata:
name: hostpath-test
spec:
containers:
- image: busybox
name: test-c-01
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /opt/test
name: shared-volume
subPath: c01
args:
- /bin/sh
- -c
- sleep 3000
- image: busybox
name: test-c-02
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /opt/test
name: shared-volume
subPath: c02
args:
- /bin/sh
- -c
- sleep 3000
volumes:
- name: shared-volume
hostPath:
path: /test02
type: DirectoryOrCreate
演示步驟如圖所示:
此時pod 中容器掛載的卷是不共享的
宿主機上的掛載情況如下
通過檢視宿主機上的掛載目錄可以判斷出,其實是通過制定的
subPath
的建立了各自的子目錄,每個容器都是用各自的subPath
,所以實現了掛載的隔離。
6.2 將configMap/secret作為檔案掛載到容器中而不覆蓋掛載目錄下的檔案
首先我們run一個普通的nginx作為前後的對照
apiVersion: v1
kind: Pod
metadata:
name: helloworld
spec:
containers:
- image: nginx
name: helloworld
imagePullPolicy: IfNotPresent
演示步驟如下:
可以看到 正常的nginx配置檔案有很多
接下來 我們通過ConfigMap
方式掛載一下nginx.conf
檔案
-
建立一個cm資源
普普通通,極其簡單的一個配置檔案
apiVersion: v1 data: nginx-conf: | worker_processes 1; events { worker_connections 1024; } http { server { listen 80; location / { root html; index index.html index.htm; } } } kind: ConfigMap metadata: name: conf-nginx
-
建立nginx-pod
也是一個很簡單的nginx-pod,掛載了上面的cm資源
apiVersion: v1 kind: Pod metadata: name: helloworld spec: containers: - image: nginx name: helloworld imagePullPolicy: IfNotPresent volumeMounts: - name: config-vol # 這裡不能寫成 /etc/nginx/nginx.conf 是因為這樣寫會被解析成nginx.conf資料夾,從而導致啟動失敗 mountPath: /etc/nginx volumes: - name: config-vol configMap: name: conf-nginx items: - key: nginx-conf path: nginx.conf
-
演示步驟如下
這時就發現直接通過cm掛載配置檔案其實是有問題的,自己想掛載的檔案雖然掛載成功了,但是掛載點目錄下的其他資源會丟失,這其實是個很大的隱患,因為有的服務少了部分配置檔案會導致啟動失敗。
如果我們既想要掛載cm資源,又不想把掛載點中的其他配置檔案丟失掉,這時subPath
就有作用了
apiVersion: v1
kind: Pod
metadata:
name: helloworld
spec:
containers:
- image: nginx
name: helloworld
imagePullPolicy: IfNotPresent
volumeMounts:
- name: config-vol
# 修改掛載點
mountPath: /etc/nginx/nginx.conf
# 新增subPath資訊
subPath: nginx.conf
volumes:
- name: config-vol
configMap:
name: conf-nginx
items:
- key: nginx-conf
path: nginx.conf
演示步驟如下: