k8s-volume
- 2022 年 1 月 16 日
- 筆記
- DevOps, k8s, Middleware
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
演示步驟如下: