09 . Kubernetes之pv、pvc及使用nfs網路存儲應用

PV,PVC概述

PV的全稱是: PersistentVolume (持久化卷),是對底層的共享存儲的一種抽象,PV由管理員進行創建和配置,它和具體的底層的共享存儲技術的實現方式有關,比如Ceph、GlusterFS、NFS等,都是通過插件機制完成與共享存儲的對接.

PVC的全稱是: PersistenVolumeClaim (持久化卷聲明),PVC是用戶存儲的一種聲明,PVC和Pod比較類型,Pod是消耗節點,PVC消耗的是PV資源,Pod可以請求CPU的記憶體,而PVC可以請求特定的存儲空間和訪問模式。對於真正存儲的用戶不需要關心底層的存儲實現細節,只需要直接使用PVC即可.

但是通過PVC請求一定的存儲空間也很有可能不足以滿足對於存儲設備的各種需求,而且不同的應用程式對於存儲性能的要求也能也不盡相同,比如讀寫速度、並發性能等,為了解決這一問題,Kubernetes又為我們引入了一個新的資源對象: StorageClass,通過StorageClass的定義,管理員可以將存儲資源定義為某種類型的資源,比如快速存儲、慢速存儲等,用戶根據StorageClass的描述就可以非常直觀的知道各種存儲資源特性了,這樣就可以根據應用的特性去申請合適的存儲資源了.

PV和PVC生命周期

PV可以看作可用的存儲資源,PVC則是對存儲資源的需求,PV和PVC的互相關係遵循如下圖

pv01

資源供應

Kubernetes支援兩種資源的供應模式:靜態模式(Staic)和動態模式(Dynamic)。資源供應的結果就是創建好的PV.

靜態模式: 管理員手工創建許多PV,在定義PV時需要將後端存儲的特性進行設置

動態模式: 管理員無需手動創建PV,而是通過StorageClass的設置對後端存儲進行描述,標記為某種”類型(Class)”,此時要求PVC對存儲的類型進行聲明,系統將自動完成PV的創建及PVC的綁定,PVC可以聲明為Class為””,說明該PVC禁止使用動態模式

資源綁定(Binding)

在用戶定義好PVC後,系統將根據PVC對存儲資源的請求(存儲空間和訪問模式)在已存在的PV中選擇一個滿足PVC要求的PV,一旦找到,就將PV與用戶定義的PVC進行綁定,然後用戶的應用就可以使用這個PVC了。如果系統中沒有滿足PVC要求的PV,PVC則會無限期處於Pending狀態,直到等到系統管理員創建了一個符合要求的PV。PV一旦綁定在某個PVC上,就被這個PVC獨佔,不能再與其他PVC進行綁定了。在這種情況下,當PVC申請的存儲空間比PV的少時,整個PV的空間都能夠為PVC所用,可能會造成資源的浪費。如果資源供應使用的是動態模式,則系統在PVC找到合適的StorageClass後,將會自動創建PV並完成PVC的綁定

資源使用(Using)

Pod 使用volume的定義,將PVC掛載到容器內的某個路徑進行使用。volume的類型為persistentVoulumeClaim,在容器應用掛載了一個PVC後,就能被持續獨佔使用。不過,多個Pod可以掛載同一個PVC,應用程式需要考慮多個實例共同訪問一塊存儲空間的問題

資源釋放(Releasing)

當用戶對存儲資源使用哪個完畢後,用戶可以刪除PVC,與該PVC綁定的PV將會被標記為已釋放,但還不能立刻與其他PVC進行綁定。通過之前PVC寫入的數據可能還留在存儲設備上,只有在清除之後該PV才能繼續使用.

資源回收(Reclaiming)

對於PV,管理員可以設定回收策略(Reclaim Policy)用於設置與之綁定的PVC釋放資源之後,對於遺留數據如何處理。只有PV的存儲空間完成回收,才能供新的PVC綁定和使用。

1 . 靜態資源下,通過PV和PVC完成綁定,並供Pod使用的存儲管理機制

pv01

2 . 動態資源下,通過StorageClass和PVC完成資源動態綁定(系統自動生成PV,並供Pod使用的存儲管理機制

pv01

存儲卷概述

由於容器本身是非持久化的,因此需要解決在容器中運行應用程式遇到的一些問題。首先,當容器崩潰時,kubelet將重新啟動容器,但是寫入容器的文件將會丟失,容器將會以鏡像的初始狀態重新開始;第二,在通過一個Pod中一起運行的容器,通常需要共享容器之間一些文件。Kubernetes通過存儲卷解決上述的兩個問題。

在Docker有存儲卷的概念卷,但Docker中存儲卷只是磁碟的或另一個容器中的目錄,並沒有對其生命周期進行管理。Kubernetes的存儲卷有自己的生命周期,它的生命周期與使用的它Pod生命周期一致。因此,相比於在Pod中運行的容器來說,存儲卷的存在時間會比的其中的任何容器都長,並且在容器重新啟動時會保留數據。當然,當Pod停止存在時,存儲卷也將不再存在。在Kubernetes支援多種類型的卷,而Pod可以同時使用各種類型和任意數量的存儲卷。在Pod中通過指定下面的欄位來使用存儲卷:

spec.volumes: 通過此欄位提供指定的存儲卷
spec.containers.volumeMounts: 通過此欄位將存儲卷掛載到容器中

存儲卷類型和示例

當前Kubernetes支援如下所列的存儲卷類型,並以hostPath、nfs和persistentVolumeClaim類型的存儲卷為例,介紹如何定義存儲卷,以及如何在Pod中被使用.

* awsElasticBlockStore
* azureDisk
* azureFile
* cephfs
* configMap
* csi
* downwardAPI
* emptyDir
* fc (fibre channel)
* flocker
* gcePersistentDisk
* gitRepo
* glusterfs
* hostPath
* iscsi
* local
* nfs
* persistentVolumeClaim
* projected
* portworxVolume
* quobyte
* rbd
* scaleIO
* secret
* storageos
* vsphereVolume
xample1 EmptyDir(兩個Pod目錄互相掛載)

EmptyDir是一個空目錄,他的生命周期和所屬的Pod是完全一致的,他用處是把同一Pod內的不同容器之間共享工作過程產生的文件,

apiVersion: v1  
kind: Pod  
metadata:  
  name: test-pd  
spec:  
  containers:  
  - image: gcr.io/google_containers/test-webserver  
    name: test-container  
    volumeMounts:   
    - mountPath: /cache  
      name: cache-volume  
  volumes:  
  - name: cache-volume  
    emptyDir: {}
mkdir /storage
cd /storage
mkdir volumes

cat pod.vol.demo.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod-demo
  namespace: default
  labels:
    app: myapp
    tier: frontend
  annotations:
    youmen.com/created-by: "youmen admin"
spec:
  containers:
  - name: myapp 
    image: ikubernetes/myapp:v1 
    imagePullPolicy: IfNotPresent
    ports:
    - name: http
      containerPort: 80
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html/

  - name: busybox
    image: busybox:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: html
      mountPath: /data/
    command: ['/bin/sh', '-c']
    args:
    - 'while true; do echo $(date) >> /data/index.html; sleep 3; done'
  volumes:
  - name: html
    emptyDir: {}

curl  10.244.3.34 -s
Tue Dec 24 15:37:09 UTC 2019
Tue Dec 24 15:37:12 UTC 2019
Tue Dec 24 15:37:15 UTC 2019
Tue Dec 24 15:37:18 UTC 2019
Tue Dec 24 15:37:21 UTC 2019
Tue Dec 24 15:37:24 UTC 2019
Example2 HostPath(主機目錄掛載)

依賴於node,這種會把宿主機的指定卷載入到容器之中,實現數據持久,但是如果Pod發生跨主機的重建,內容很難保證,或者Node節點宕機了
這種卷一般和DaemonSet搭配使用,用來操作主機文件,例如進行日誌採集的FLK的FluentD就採用這種方式,載入主機的容器日誌目錄,達到收集本機所有日誌的目的

# hostPath
# 將Pod裡面的目錄內容綁定到宿主機目錄,Pod刪除並不到導致宿主機刪除.
# 如果指定宿主機目錄不存在要不要先創建取決於type
cat pod-volhost.demo.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod-vol-hostpath
  namespace: default
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    volumeMounts:
    - name: html 
      mountPath: /usr/share/nginx/html/
  volumes:
  - name: html
    hostPath:
      path: /data/pod/volume1
      type: DirectoryOrCreate

echo youmen >> /data/pod/volume1/index.html
kubectl get pods -o wide
NAME             READY STATUS  RESTARTS AGE   IP         NODE    NOMINATED NODE   READINESS GATES
pod-vol-hostpath 1/1   Running 0       3m49s 10.244.3.35 node1   <none>           <none>
curl  10.244.3.35
youmen

# 我們刪除Pod,可以到Node主機查看目錄,依然存在.
[root@node1 ~]# cat /data/pod/volume1/index.html
youmen
Example3(NFS共享存儲)
# 找集群外的一台機器做NFS
hostnamectl  set-hostname stor01
echo 172.19.0.18    stor01 >> /etc/hosts
yum -y install nfs-utils
mkdir -pv /data/volumes
echo nfs >> /data/volumes/index.html 
vim  /etc/exports
/data/volumes     172.19.0.0/16(rw,no_root_squash)
systemctl  start nfs

# 切換到k8s集群內的節點掛載存儲卷
yum -y install nfs-utils
echo 172.19.0.18    stor01 >> /etc/hosts
# 掛載測試以下,然後umount掉,待會創建Pod自動會掛載
mount -t nfs stor01:/data/volumes  /mnt

cat pod-volnfs.demo.yaml 
apiVersion: v1
kind: Pod
metadata:
  name: pod-volnfs-demo
  namespace: default
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html/
  volumes:
  - name: html
    nfs:
      path: /data/volumes
      server: stor01

kubectl get pods -o wide
NAME              READY   STATUS    RESTARTS   AGE   IP            NODE    NOMINATED NODE   READINESS GATES
pod-volnfs-demo   1/1     Running   0          20s   10.244.1.49   node2   <none>           <none>
curl 10.244.1.49
nfs
# 可以刪除Pod再創建數據依然存在,哪怕節點宕掉依然數據不會丟失,但是當掛載的數據,Pod多了,效果就不盡人意了

Pv和Pvc

image.png

Pv和Pvc是K8s的一種標準資源,Pvc被Pv調用後就會被綁定起來,取決於用戶怎麼綁,

kubectl explain explain pvc.spec
FIELDS:
   accessModes	<[]string>
     AccessModes contains the desired access modes the volume should have. More
     info:
     //kubernetes.io/docs/concepts/storage/persistent-volumes#access-modes-1

   dataSource	<Object>
     This field requires the VolumeSnapshotDataSource alpha feature gate to be
     enabled and currently VolumeSnapshot is the only supported data source. If
     the provisioner can support VolumeSnapshot data source, it will create a
     new volume and data will be restored to the volume at the same time. If the
     provisioner does not support VolumeSnapshot data source, volume will not be
     created and the failure will be reported as an event. In the future, we
     plan to support more data source types and the behavior of the provisioner
     may change.

   resources	<Object>
     Resources represents the minimum resources the volume should have. More
     info:
     //kubernetes.io/docs/concepts/storage/persistent-volumes#resources

   selector	<Object>
     A label query over volumes to consider for binding.

   storageClassName	<string>
     Name of the StorageClass required by the claim. More info:
     //kubernetes.io/docs/concepts/storage/persistent-volumes#class-1

   volumeMode	<string>
     volumeMode defines what type of volume is required by the claim. Value of
     Filesystem is implied when not included in claim spec. This is a beta
     feature.

   volumeName	<string>
     VolumeName is the binding reference to the PersistentVolume backing this
     claim.
Example4(創建多個Pvc)

因為pvc屬於集群資源級別的不能定義在名成空間

mkdir v{1,2,3,4,5}
exportfs -arv
exporting 172.19.0.0/16:/data/volumes/v5
exporting 172.19.0.0/16:/data/volumes/v4
exporting 172.19.0.0/16:/data/volumes/v3
exporting 172.19.0.0/16:/data/volumes/v2
exporting 172.19.0.0/16:/data/volumes/v1

showmount -e
Export list for stor01:
/data/volumes/v5 172.19.0.0/16
/data/volumes/v4 172.19.0.0/16
/data/volumes/v3 172.19.0.0/16
/data/volumes/v2 172.19.0.0/16
/data/volumes/v1 172.19.0.0/16

切換到k8s控制節點

 cat pod-pv.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv001
  labels:
    name: pv001
spec:
  nfs:
    path: /data/volumes/v1
    server: stor01
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv002
  labels:
    name: pv002
spec:
  nfs:
    path: /data/volumes/v2
    server: stor01
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 4Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv003
  labels:
    name: pv003
spec:
  nfs:
    path: /data/volumes/v3
    server: stor01
  accessModes: ["ReadWriteMany","ReadWriteOnce"]
  capacity:
    storage: 4Gi
kubectl apply -f pod-pvc.yaml

查看Pv

kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv001   2Gi        RWO,RWX        Retain           Available                                   87s
pv002   4Gi        RWO,RWX        Retain           Available                                   87s
pv003   4Gi        RWO,RWX        Retain           Available                                   87s

創建pvc然後綁定pv

cat pod-vol-pvc.yaml 
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mypvc
  namespace: default
spec:
  accessModes: ["ReadWriteMany"]
  resources:
    requests:
      storage: 2Gi
---
apiVersion: v1
kind: Pod 
metadata:
  name: pod-vol-pvc 
  namespace: default
spec:
  containers:
  - name: myapp
    image: ikubernetes/myapp:v1
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html/
  volumes:
  - name: html
    persistentVolumeClaim:
      claimName: mypvc 

kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM     STORAGECLASS   REASON   AGE
pv001   4Gi        RWO,RWX        Retain           Available                                    43s
pv002   4Gi        RWX            Retain           Bound       default/mypvc                  43s
pv003   4Gi        RWO,RWX        Retain           Available                                   43s

kubectl get pvc
NAME    STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
mypvc   Bound    pv002    4Gi        RWX                           106s

pvc創建在pv之上,而且指定的空間大小不能超過pv大小,否則一直Pending掛起
NFS存儲的缺點

不支援動態創建持久卷,只能手工創建
先手工創建PV,再通過PV手工創建PVC,PVC就是真正可用的持久卷

PVC是和PV進行綁定的:
PVC會根據自己需求空間的大小自動選擇合適的PV,比如需要一個5G的PVC,PV分別為2G,7G和10G,那麼PVC會自動選擇7G的,但是剩餘的空間不是浪費了么?原因如下:
一個被綁定的PV只能用於一個PVC,他們是一對一綁定的,如果PVC大小只需要5G,但是所選的PV有7G,那麼剩餘的2G是沒辦法使用的,如果不想這樣浪費空間只能使用動態創建的方式.