antony@notes:~/kubernetes$ cat "Kubernetes-Resource-Storage.md"
Kubernetes Resource Storage
Kubernetes Resource Storage (NFS)
:::warning
:::spoiler 目錄
[TOC]
:::
Kubernetes Volume
- Type of Storage
- Container Filesystem (Overlay2)
- Container 掛點,檔案系統掛點
- Volume (emptyDir)
- 每個 Container 會共用 pod 的目錄
- 生命週期與 pod 一樣, pod 裡其中一個 Container 毀損,並不影響到 Volume
- Persistent volume. (hostPath, Local,NFS)
- 永存的儲存體
- 生命週期與 K8S Cluster 相同
- Container Filesystem (Overlay2)
empty dir volume

- empty dir 的運作,等同於 Container 的 Volume
- empty dir,這個目錄存在於 Linux 系統中,系統會自動幫我們產生這個目錄區(名字很長不是人記得)。
- 一個 pod 裡的多個 Container 共用同一個 Linux 系統的目錄區
Shared volumes in a Kubernetes Pod
$ nano ~/wulin/yaml/sidecar.yamlapiVersion: v1
kind: Pod
metadata:
name: sidecar
spec:
volumes:
- name: html
emptyDir: {}
containers:
- name: 1st
image: quay.io/cloudwalker/nginx
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
- name: 2nd
image: quay.io/cloudwalker/alpine
volumeMounts:
- name: html
mountPath: /html
command: ["/bin/sh", "-c"]
args:
- while true; do
date >> /html/index.html;
sleep 1;
done- 將
1st和2nd兩台 Container 的目錄 spec.volumes.emptyDir: {},這個目錄等下會被 mount 到spec.volumes.mountPath: /usr/share/nginx/html這個目錄
建立 pod
$ ka -f ~/wulin/yaml/sidecar.yaml進入 sidecar pod 裡面的 1st Container 看 /usr/share/nginx/html/index.html 檔案的倒數 3 行的內容
$ kubectl exec sidecar -c 1st -- /bin/cat /usr/share/nginx/html/index.html | tail -n 3
Thu Dec 16 13:20:14 UTC 2021
Thu Dec 16 13:20:15 UTC 2021
Thu Dec 16 13:20:16 UTC 2021kubectl exec,在 pod 中的 Container 裡面執行命令-c後面接的是 Container 的名字--後面放在 Container 裡要執行的命令
$ kg pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
sidecar 2/2 Running 0 12m 10.244.0.6 m1 <none> <none>
$ kubectl get pod sidecar --template={{.status.podIP}}; echo
10.244.0.6
$ podip=$(kubectl get pod sidecar --template={{.status.podIP}})
$ curl -s http://$podip | head -n 5
Thu Dec 16 13:06:28 UTC 2021
Thu Dec 16 13:06:29 UTC 2021
Thu Dec 16 13:06:30 UTC 2021
Thu Dec 16 13:06:31 UTC 2021
Thu Dec 16 13:06:32 UTC 2021hostPath Volume 應用範例
編輯 yaml 檔
# 在 m1 終端機執行
$ nano ~/wulin/yaml/pod-hp.yaml kind: Pod
apiVersion: v1
metadata:
name: pod-hp
spec:
containers:
- name: pod-hp
image: quay.io/cloudwalker/nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: "http-server"
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: hp-volume
volumes:
- name: hp-volume
hostPath:
path: /opt/hostpath產生 pod
$ kubectl create -f ~/wulin/yaml/pod-hp.yaml (不會產生 PV Object)
# 看 pod IP 位址
$ kg po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
pod-hp 1/1 Running 0 65s 10.244.4.12 w2 <none> <none>
# 檢查 hostpath 目錄
$ ssh w2 'ls -l /opt'
total 8
drwxr-xr-x 3 root root 4096 Dec 25 2021 cni
drwxr-xr-x 2 root root 4096 Sep 4 15:30 hostpath
# 對照 w1 node 是不會有的
$ ssh w1 'ls -l /opt'
total 8
drwxr-xr-x 3 root root 4096 Dec 25 2021 cni
drwxr-xr-x 9 root root 4096 Sep 4 00:23 www
# 進入 pod 產生 index.html 檔案
$ kubectl exec -it pod-hp -- sh
# echo "<h1>let me go</h1>" > /usr/share/nginx/html/index.html
# exit
$ curl http://10.244.2.20
<h1>let me go</h1>檢查剛剛產生的 index.html 是否產生在 w2 的 /opt/hostpath 目錄
$ ssh w2 'ls -l /opt/hostpath/'
total 4
-rw-r--r-- 1 root root 19 Sep 4 15:34 index.htmlPersistent Volumes 運作架構

- 管理 storage 一直都是一個不簡單的課題,為了讓 developer 可以更專注在開發上,k8s 中提出了兩個概念(resource object),分別是 Persistent Volume(PV) & Persistent Volume Claim(PVC),透過這兩個概念將連結 storage 的過程抽象化,讓使用者不需要了解 storage 在底層是如何運作的,只要了解如何使用即可,大幅降低使用者操作 storage 的困難度。
- Persistent Volume(PV) 由 storage 管理者負責產生,在 k8s 中就是一種可用的 storage resource,同樣也是一種 volume plugin,但有自己獨立的 lifecycle,且包含的就是實際與 storage 連結的實作細節。
- Persistent Volume Claim(PVC) 則是來自使用者的 storage request,就跟 pod 一樣都是要消耗特定的資源,PVC 消耗 PV 資源(pod 消耗 node 資源),PVC 指定特定 size or access mode 的 PV(pod 可以指定 CPU, memory … etc)。
- 由於 PVC 可以允許使用者自行指定所需要 PV 的相關屬性,因此除了 size, access mode 外,可能還會有 performance 的需求。而為了滿足不同的使用目的,cluster administrator 就必須要準備好不同的 PV 來處理不同 PVC 的需求;若是 PVC 數量不多且需求單純,手動產生 PV 可能還可以接受,但若是 PVC 的數量眾多且需求多變,那可能就需要 [StorageClass] 的協助了!
- Persistent Volume (Claim) Overview Link (必看)
Local Persistent Volumes
- local PV 的產生一定要用 yaml 檔產生
- 且一定要需告在哪一台 node
- 宣告的目錄區,需要事先準備
- the Kubernetes scheduler ensures that a pod using a Local Persistent Volume is always scheduled to the same node.
建立 Local PersistentVolume
$ echo 'kind: PersistentVolume
apiVersion: v1
metadata:
name: pv-local
spec:
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
local:
path: "/opt/local"
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- m1 ' > ~/wulin/yaml/pv-local.yaml - 以上設定的 capacity(儲存空間大小), accessModes(目錄權限) 數據都是參考用的
- PV 會產生在 m1 的 node 上,所以以上設定的數據是由 m1 node 上 Linux 系統在管理目錄區的 Quota 做容量控管及權限設定,實際上要求有沒有做到,PV 本身是不會管的
產生 PV
$ kubectl create -f ~/wulin/yaml/pv-local.yaml
persistentvolume/pv-local created
$ kubectl get pv pv-local
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-local 10Gi RWO Retain Available 102s建立 Local PersistentVolumeClaim
$ echo 'kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: pvc-local
spec:
accessModes:
- ReadWriteOnce
storageClassName: ""
resources:
requests:
storage: 3Gi ' > ~/wulin/yaml/pvc-local.yaml- 注意! 在 yaml 檔中的宣告,並未指定要用哪個 PV
- PVC 會去尋找滿足
accessModes和resources.requests.storage的 PV 來 Bound 建立連接
$ kubectl create -f ~/wulin/yaml/pvc-local.yaml檢查 pv 與 pvc 狀態
$ kg pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-local 10Gi RWO Retain Bound default/pvc-local 32m
$ kg pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-local Bound pv-local 10Gi RWO 46s建立使用 pvc-local 的第一個 Pod
$ echo 'kind: Pod
apiVersion: v1
metadata:
name: pod-pvc1
spec:
volumes:
- name: pv-storage
persistentVolumeClaim:
claimName: pvc-local
containers:
- name: pod-pvc1
image: quay.io/cloudwalker/nginx
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: "http-server"
volumeMounts:
- mountPath: "/usr/share/nginx/html"
name: pv-storage ' > ~/wulin/yaml/pod-pvc1.yaml $ kubectl create -f ~/wulin/yaml/pod-pvc1.yaml
$ kg po
NAME READY STATUS RESTARTS AGE
g1 1/1 Running 0 116m
g2 1/1 Running 0 115m
pod-pvc1 0/1 ContainerCreating 0 12m
$ kubectl describe pod pod-pvc1
...
... path "/opt/local" does not exist- 因為沒事先在指定主機 (m1) 建立 /opt/local 目錄
解決
$ sudo mkdir /opt/local檢查
$ kg po pod-pvc1
NAME READY STATUS RESTARTS AGE
pod-pvc1 1/1 Running 0 20m建立 NGINX 首頁
$ kubectl exec -it pod-pvc1 -- bash
root@pv-pod:/# echo "<h1>POD-PVC1</h1>" > /usr/share/nginx/html/index.html
root@pv-pod:/# exit
$ ls -l /opt/local/
total 4
-rw-r--r-- 1 root root 18 Sep 7 15:49 index.html刪除 pod 與 pvc
$ kubectl delete pod pod-pvc1
pod "pod-pvc1" deleted
$ kubectl delete pvc pvc-local
persistentvolumeclaim "pvc-local" deleted
$ kg pvc
No resources found in default namespace.檢視被刪除 pvc 後的 pv
$ kg pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv-local 10Gi RWO Retain Released default/pvc-local 62m- 可以看到狀態是
Released
如果再次建立一樣的 pvc 呢?
$ kubectl create -f pvc-local.yaml檢視 pvc 的狀態
$ kg pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc-local Pending 7sHelm
- 像是 Alpine Linux 的 apk ,可以在 K8S 新增套件
- 要自行安裝,原生的 K8S 沒有 Helm
安裝 Helm 3
$ curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bashget-helm-3,安裝 Helm 的程式- 漿螢幕輸出透過水管直接將安裝 Helm 的程式餵給 貝殼程式
NFS Dynamic Volume Provision
安裝 NFS 套件
K8S Master/Worker 都要安裝
$ sudo apk update; sudo apk add nfs-utils
.........
(6/7) Installing nfs-utils (2.5.4-r1)
(7/7) Installing nfs-utils-openrc (2.5.4-r1)
Executing busybox-1.33.1-r3.trigger
OK: 1603 MiB in 291 packages
$ ssh w1 sudo apk update; ssh w1 sudo apk add nfs-utils
$ ssh w2 sudo apk update; ssh w2 sudo apk add nfs-utils設定 NFS Server
$ sudo rc-update add nfs
* service nfs added to runlevel default
K8s Master 建立共用資料夾
$ sudo mkdir /opt/nfs; sudo chown -R nobody:nogroup /opt/nfs
$ echo "/opt/nfs $IP/24(rw,sync,no_subtree_check,no_root_squash)" | sudo tee /etc/exports啟動 NFS Server
$ sudo rc-service rpcbind start
$ sudo rc-service nfs start
* Stopping NFS mountd ... [ ok ]
* Stopping NFS daemon ... [ ok ]
* Unexporting NFS directories ... [ ok ]
* Exporting NFS directories ... [ ok ]
* Starting NFS mountd ... [ ok ]
* Starting NFS daemon ... [ ok ]
* Starting NFS smnotify ... [ ok ]測試 NFS
# K8S Worker 測試掛載 nfs
# 將 m1 主機的 /opt/nfs 目錄掛載到 w1 主機的 /mnt 目錄
$ ssh w1 "sudo mount -t nfs $IP:/opt/nfs /mnt"
# 在 /mnt 上建立一個空檔案
$ ssh w1 "sudo touch /mnt/mynfs"
# 卸載 nfs
$ ssh w1 "sudo umount /mnt"
# K8S Master 檢查
$ ls -al /opt/nfs
total 8
drwxr-xr-x 2 nobody nogroup 4096 Oct 30 19:22 .
drwxr-xr-x 4 root root 4096 Oct 30 19:20 ..
-rw-r--r-- 1 root root 0 Oct 30 19:22 mynfs安裝與建立 NFS Provisioner
# 新增套件清單網址
$ helm repo add nfs-subdir-external-provisioner https://kubernetes-sigs.github.io/nfs-subdir-external-provisioner/
# 更新儲存庫
$ helm repo update
# 搜尋剛剛的套件網址版本代號多少
$ helm search repo nfs-subdir-external-provisioner
NAME CHART VERSION APP VERSION DESCRIPTION
nfs-subdir-external-provisioner/nfs-subdir-exte... 4.0.17 4.0.2 nfs-subdir-external-provisioner is an automatic...
# 安裝套件
$ helm install nfs-subdir-external-provisioner nfs-subdir-external-provisioner/nfs-subdir-external-provisioner \
--set storageClass.defaultClass=true \
--set storageClass.name=mynfs \
--set nfs.server=$IP \
--set nfs.path=/opt/nfs檢視 NFS Provisioner
$ kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
mynfs (default) cluster.local/nfs-subdir-external-provisioner Delete Immediate true 17s
$ kg all
NAME READY STATUS RESTARTS AGE
pod/nfs-subdir-external-provisioner-7dc5ccf74b-lkrkj 0/1 ContainerCreating 0 22s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.98.0.1 <none> 443/TCP 7d23h
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/nfs-subdir-external-provisioner 0/1 1 0 22s
NAME DESIRED CURRENT READY AGE
replicaset.apps/nfs-subdir-external-provisioner-7dc5ccf74b 1 1 0 22s建立 PVC 測試 NFS Client Provisioner
$ echo $'kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim
spec:
accessModes: ["ReadWriteOnce"]
storageClassName: mynfs
resources:
requests:
storage: 1Mi ' > pvc-nfscp.yaml spec.resources.requests.storage,設定的1Mi是參考值,真正在限制的人是底層的 NFS ,它如果沒能力,就不會真的被限制spec.accessModes,ReadWriteOnce設定的是在本機才能被掛載,與 NFS 的概念完全背離,所以應該改為ReadWriteMany,但其實這邊也是參考的,因為底層在運作是 NFS ,最後一樣能被其他主機來掛載
$ kubectl create -f pvc-nfscp.yaml
persistentvolumeclaim/test-claim created
$ kg pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-de200a3e-203b-4feb-ab8a-95f0be90cd3c 1Mi RWO Delete Bound default/test-claim mynfs 3s
$ kg pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-claim Bound pvc-de200a3e-203b-4feb-ab8a-95f0be90cd3c 1Mi RWO mynfs 4s建立 Job 測試 NFS Client Provisioner
編輯 Job YAML 檔
$ nano nfs-job-test.yamlkind: Job
apiVersion: batch/v1
metadata:
name: nfs-job-test
spec:
template:
metadata:
name: nfs-job-test
spec:
containers:
- name: nfs-job-test
image: quay.io/cloudwalker/busybox
command:
- "/bin/sh"
args:
- "-c"
- "touch /opt/SUCCESS && exit 0 || exit 1"
volumeMounts:
- name: nfs-pvc
mountPath: "/opt"
restartPolicy: Never
volumes:
- name: nfs-pvc
persistentVolumeClaim:
claimName: test-claim$ ka -f nfs-job-test.yaml
$ kg job
NAME COMPLETIONS DURATION AGE
nfs-job-test 1/1 4s 10m
$ dir /opt/nfs
..........
drwxrwxrwx 2 root root 4.0K Oct 29 12:43 default-test-claim-pvc-788e2513-9de6-41d5-bb2d-f57e1fa5ce7c
$ kubectl delete job nfs-job-test; kubectl delete pvc test-claim
$ dir /opt/nfs
total 12K
drwxr-xr-x 3 nobody nogroup 4.0K Oct 29 13:05 .
drwxr-xr-x 4 root root 4.0K Oct 29 11:59 ..
drwxrwxrwx 2 root root 4.0K Oct 29 12:43 archived-default-test-claim-pvc-788e2513-9de6-41d5-bb2d-f57e1fa5ce7c