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

men發表於2020-07-02

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的互相關係遵循如下圖

資源供應

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使用的儲存管理機制

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

儲存卷概述

由於容器本身是非持久化的,因此需要解決在容器中執行應用程式遇到的一些問題。首先,當容器崩潰時,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內的不同容器之間共享工作過程產生的檔案,

mkdir /storage
cd /storage
mkdir volumes

cat pod_volume.demo1.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: {}

kubectl apply -f pod_volume.demo1.yaml

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
mkdir /data/pod/volume1 -p
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
        
        
kubectl apply -f pod-volhost-demo.yaml 
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

NFS網路共享儲存

部署nfs
# 建議找叢集外的一臺機器做NFS
hostnamectl  set-hostname stor01
echo 172.19.0.18    stor01 >> /etc/hosts
yum -y install nfs-utils
mkdir -pv /data/volumes -p
chmod 755 /data/volumes


vim  /etc/exports
/data/volumes     *(rw,no_root_squash,sync)

# 儲存目錄,*允許所有人連線,rw讀寫許可權,sync檔案同時寫入硬碟及記憶體,no_root_squash 使用者root使用者自動修改為普通使用者

systemctl  start nfs && systemctl enable nfs
驗證nfs服務可用性
# 檢視nfs目錄掛載許可權
cat /var/lib/nfs/etab 
/data/volumes	*(rw,sync,wdelay,hide,nocrossmnt,secure,no_root_squash,no_all_squash,no_subtree_check,secure_locks,acl,no_pnfs,anonuid=65534,anongid=65534,sec=sys,rw,secure,no_root_squash,no_all_squash)

# 檢查下掛載目錄是否正常
showmount -e localhost
Export list for localhost:
/data/volumes *
掛載儲存卷
# 切換到k8s叢集內的節點掛載儲存卷
yum -y install nfs-utils
echo 172.19.0.18    stor01 >> /etc/hosts
# 掛載測試以下,然後umount掉,待會建立Pod自動會掛載
mount -t nfs stor01:/data/volumes  /mnt
  
# 驗證一下
df -h |grep nfs1
nfs1:/data/volumes    17G  1.4G   16G   9% /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來使用這個nfs儲存

使用pv,pvc作為儲存

Pv和Pvc是K8s的一種標準資源,Pvc被Pv呼叫後就會被繫結起來,取決於使用者怎麼綁,

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

建立pv
# 這裡使用nfs型別後端儲存, 1g儲存空間,訪問模式為ReadWriteOnce,回收策略為Recyle

cat nfs_demo1.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv1     #pv名稱
spec:
  capacity:          #儲存能力,一個pv物件都要指定一個儲存能力,
目前僅支援儲存空間的設定
    storage: 1Gi    #儲存空間
  accessModes:
  - ReadWriteOnce       #訪問模式
  persistentVolumeReclaimPolicy: Recycle        #回收策略
  nfs:          #服務模式 (nfs、ceph、hostpath等)
    path: /data/volumes      #共享資料目錄掛載點
    server: nfs1         #nfs伺服器地址
      
      
kubectl apply -f nfs_demo1.yaml 
persistentvolume/pv1 created
[root@master storage]# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv1    1Gi        RWO            Recycle          Available                                   5s

# pv1:  名稱
# 1Gi:  儲存空間大小
# RWO:  訪問模式(ReadWriteOnce縮寫)
# Recycle:     回收策略
# Available:   PV狀態

# persistentVolumeReclaimPolicy回收策略
# Retain (保留) 保留資料,需要管理員手動清理
# Recycle (回收) 清除PV中的資料,效果相當於執行刪除命令
# Delete (刪除) 與PV相連的後端儲存完成volume的刪除操作,常見於雲服務商的儲存服務
# 不過需要注意的是,目前只有NFS和HostPath兩類支援回收策略,一般設定Retain比較保險


# 狀態有以下幾種
# Available:   PV狀態,表示可用狀態,還未被任何PVC繫結
# Bound:			已繫結,已經繫結到某個PVC
# Released:    已釋放,對應的pvc已經刪除,但資源還沒有被叢集收回
# Failed:      PV自動回收失敗
pv相關配置說明

Capacity 儲存能力

通過PV的capacity屬性來設定儲存空間,目前僅支援storage=資料大小,未來可能會加入IOPS、吞吐量等指標配置

AccessModes訪問模式

AccessModes 是用來對PV進行訪問模式的設定,用於描述使用者應用對儲存資源的訪問許可權

ReadWriteOnce (RWO):讀寫許可權,但是隻能被單個節點掛載

ReadOnlyMany (ROX):只讀許可權,可能被多個節點掛載

ReadWriteMany (RWX):讀寫許可權,可以被多個節點掛載

注意: 一些pv可能支援多種訪問模式,但掛載時候只可以使用一種訪問模式,多種訪問模式不奏效

PVC

PV實際上沒有儲存,相當於我們node一樣,還需要建立Pod進行消費,接下來我們進行PVC的建立與配置

# 建立一個資料卷宣告
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-nfs
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

kubectl apply -f pvc-nfs-demo1.yaml

kubectl get pvc
NAME      STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-nfs   Bound    pv1      1Gi        RWO  

kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM             STORAGECLASS   REASON   AGE
pv1    1Gi        RWO            Recycle          Bound    default/pvc-nfs      10m
# 當我們建立pvc之後,pv的狀態變成了Bound繫結狀態,並且和pvc的狀態相同。
# 並且可以看到pvc已經繫結到名稱為pv1的volume上,
# 同時在pv上可以看到繫結到名稱為pvc-nfs的pvc中
檢視pv,pvc
[root@master storage]# kubectl describe pv pv1
Name:            pv1
Labels:          <none>
Annotations:     pv.kubernetes.io/bound-by-controller: yes
Finalizers:      [kubernetes.io/pv-protection]
StorageClass:    
Status:          Bound
Claim:           default/pvc-nfs
Reclaim Policy:  Recycle
Access Modes:    RWO
VolumeMode:      Filesystem
Capacity:        1Gi
Node Affinity:   <none>
Message:         
Source:
    Type:      NFS (an NFS mount that lasts the lifetime of a pod)
    Server:    nfs1
    Path:      /data/volumes
    ReadOnly:  false
Events:        <none>

[root@master storage]# kubectl describe pvc pvc-nfs
Name:          pvc-nfs
Namespace:     default
StorageClass:  
Status:        Bound
Volume:        pv1
Labels:        <none>
Annotations:   pv.kubernetes.io/bind-completed: yes
               pv.kubernetes.io/bound-by-controller: yes
Finalizers:    [kubernetes.io/pvc-protection]
Capacity:      1Gi
Access Modes:  RWO
VolumeMode:    Filesystem
Mounted By:    <none>
Events:        <none>
  
# 在Kubernetes中會自動幫我們檢視pv狀態為Available並且根據宣告pvc容量storage的大小進行篩選匹配,
# 同時還會根據AccessMode進行匹配。如果pvc匹配不到pv會一直處於pending狀態。
根據Labels匹配PV與PVC

同時,pv與pvc中間還可以通過label標籤進行匹配,配置如下

# 記得下面配置檔案修改一下名字,不可以重複
apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv2
  labels:           #這裡將pv設定一個labels
    app: nfs
spec:
  capacity:
    storage: 1Gi
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Recycle
  nfs:
    path: /data/volumes
    server: nfs1
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc2-nfs
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  selector:         # pvc匹配標籤為app=nfs的pv
    matchLabels:
      app: nfs

[root@master storage]# kubectl apply -f nfs_demo2.yaml 
[root@master storage]# kubectl get pv,pvc
NAME                   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM              STORAGECLASS   REASON   AGE
persistentvolume/pv1   1Gi        RWO            Recycle          Bound    default/pvc-nfs                            28m
persistentvolume/pv2   1Gi        RWO            Recycle          Bound    default/pvc2-nfs                           94s

NAME                             STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/pvc-nfs    Bound    pv1      1Gi        RWO                           17m
persistentvolumeclaim/pvc2-nfs   Bound    pv2      1Gi        RWO                           94s

# 有一點需要注意,當我們pvc申請的容量小於我們pv的容量是可以進行繫結的
# 當我們申請pvc的容量大於pv的容量是無法進行繫結的。 這裡需要注意
Deployment引用pvc
[root@master storage]# cat deployment-nfs1.yaml
apiVersion: apps/v1 
kind: Deployment
metadata:
  name: pv-nfs-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: pv-nfs-nginx
  template:
    metadata:
      labels:
        app: pv-nfs-nginx
    spec:
      containers:
      - name: pv-nfs-nginx
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:           #掛載,首先新增需要掛載的目錄
        - name: pv-nginx        #掛載點的名稱
          mountPath: /usr/share/nginx/html   #掛載點的路徑
      volumes:    #繫結
      - name: pv-nginx
        persistentVolumeClaim:    #將映象中的nginx目錄掛載到下面名稱的pvc中
          claimName: pvc-nfs   #pvc名稱
---

apiVersion: v1
kind: Service
metadata:
  name: nfs-pvc
  labels:
    app: pv-nfs-nginx
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: pv-nfs-nginx
      
[root@master storage]# kubectl apply -f deployment-nfs1.yaml

檢查Pod和svc狀態
[root@master storage]# kubectl get pods,svc |grep pv
pod/pv-nfs-nginx-6b4759b5b8-95vhk   1/1     Running   0          74s
pod/pv-nfs-nginx-6b4759b5b8-mjwhn   1/1     Running   0          58s
pod/pv-nfs-nginx-6b4759b5b8-p4jhn   1/1     Running   0          11s
service/nfs-pvc      NodePort    10.0.0.57    <none>        80:31850/TCP   4m23s
  
# 這裡我們可以看到pod已經正常啟動,並且svc也已經暴露埠了。
# 如果需要更改顯示內容,更改之前nfs儲存裡面的那個目錄檔案就可以了

由於我們的index.html直接掛在到了/data1/k8s目錄下面,如果有很多個pod都使用pvc進行掛載,會造成我們資料目錄的檔案比較亂

這裡我們新增一個subpathsubPath的目的是為了在單一Pod中多次使用同一個volume而設計的。

[root@master storage]# cat deployment-nfs1.yaml
apiVersion: apps/v1 
kind: Deployment
metadata:
  name: pv-nfs-nginx
spec:
  replicas: 3
  selector:
    matchLabels:
      app: pv-nfs-nginx
  template:
    metadata:
      labels:
        app: pv-nfs-nginx
    spec:
      containers:
      - name: pv-nfs-nginx
        image: nginx
        ports:
        - containerPort: 80
        volumeMounts:           #掛載,首先新增需要掛載的目錄
        - name: pv-nginx        #掛載點的名稱
          mountPath: /usr/share/nginx/html   #掛載點的路徑
          subPath: nginx-pvc
      volumes:    #繫結
      - name: pv-nginx
        persistentVolumeClaim:    #將映象中的nginx目錄掛載到下面名稱的pvc中
          claimName: pvc-nfs   #pvc名稱
---

apiVersion: v1
kind: Service
metadata:
  name: nfs-pvc
  labels:
    app: pv-nfs-nginx
spec:
  type: NodePort
  ports:
  - port: 80
    targetPort: 80
  selector:
    app: pv-nfs-nginx
    
[root@master storage]# kubectl apply -f deployment-nfs1.yaml

# 當我們更新完pod之後,等pod正常啟動。
# 就可以看到在我們nfs儲存目錄下面單獨建立了一個名稱為nginx-pvc的目錄,
# 這個目錄實際上就是我們subpath後面指定的名稱
[root@nfs1 ~]# ls /data/volumes/
index.html  nginx-pvc
[root@nfs1 ~]# ls /data/volumes/nginx-pvc/
[root@nfs1 ~]# mv /data/volumes/index.html  /data/volumes/nginx-pvc/

# 這個目錄下面也是沒有任何檔案的,我們需要將原來index.html拷貝過去即可

# 現在我們刪除deployment,下面的資料並不會刪除。這樣使用pv和pvc持久化就完成

# 如果我們直接刪除或者有pod在使用pv或者pvc是無法直接刪除的,
# 當我們使用Recycle模式時,刪除所有pv和pvc後,
# 資料也會進行刪除。所以刪除pv和pvc請謹慎操作
[root@nfs1 ~]# cat /data/volumes/nginx-pvc/index.html
Pvc Persistence

NFS儲存的缺點

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

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

相關文章