Skip to content

antony@notes:~/kubernetes$ cat "Kubernetes-Resource-Storage.md"

Kubernetes Resource Storage

2022-09-18· kubernetes ·系統工程

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 相同

empty dir volume

  • empty dir 的運作,等同於 Container 的 Volume
  • empty dir,這個目錄存在於 Linux 系統中,系統會自動幫我們產生這個目錄區(名字很長不是人記得)。
  • 一個 pod 裡的多個 Container 共用同一個 Linux 系統的目錄區

Shared volumes in a Kubernetes Pod

$ nano ~/wulin/yaml/sidecar.yaml
apiVersion: 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
  • 1st2nd 兩台 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 2021
  • kubectl 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 2021

hostPath 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.html

Persistent 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 會去尋找滿足 accessModesresources.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                                                     7s

Helm

  • 像是 Alpine Linux 的 apk ,可以在 K8S 新增套件
  • 要自行安裝,原生的 K8S 沒有 Helm

安裝 Helm 3

$ curl https://raw.githubusercontent.com/helm/helm/master/scripts/get-helm-3 | bash
  • get-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.accessModesReadWriteOnce 設定的是在本機才能被掛載,與 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.yaml
kind: 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