Kubernetes 官網對 Volumes的介紹說:On-disk files in a Container are ephemeral, which presents some problems for non-trivial applications when running in Containers. First, when a Container crashes, kubelet will restart it, but the files will be lost - the Container starts with a clean state. Second, when running Containers together in a Pod
it is often necessary to share files between those Containers. The Kubernetes Volume
abstraction solves both of these problems.
其意思是:容器中的文件在磁盤上是臨時存放的,這給容器中運行的特殊應用程序帶來一些問題。 首先,當容器崩潰時,kubelet 將重新啟動容器,容器中的文件將會丟失——因為容器會以干凈的狀態重建。 其次,當在一個 Pod
中同時運行多個容器時,常常需要在這些容器之間共享文件。 Kubernetes 抽象出 Volume
對象來解決這兩個問題。
在 官網的 pod的介紹章節 中也提到,pod內的所有容器共享pod的網絡與存儲:
- Networking:Each Pod is assigned a unique IP address. Every container in a Pod shares the network namespace, including the IP address and network ports. Containers inside a Pod can communicate with one another using
localhost
. When containers in a Pod communicate with entities outside the Pod, they must coordinate how they use the shared network resources (such as ports). - Storage:A Pod can specify a set of shared storage Volumes. All containers in the Pod can access the shared volumes, allowing those containers to share data. Volumes also allow persistent data in a Pod to survive in case one of the containers within needs to be restarted. See Volumes for more information on how Kubernetes implements shared storage in a Pod.
Host類型volume實戰:
其實這里的 volume 跟docker里面的有點像.
(1)創建資源定義一個Pod,其中包含兩個Container,都使用Pod的Volume :volume-pod.yaml
apiVersion: v1 kind: Pod metadata: name: volume-pod spec: containers: - name: nginx-container image: nginx ports: - containerPort: 80 volumeMounts: - name: volume-pod mountPath: /nginx-volume - name: busybox-container image: busybox command: ['sh', '-c', 'echo The app is running! && sleep 3600'] volumeMounts: - name: volume-pod mountPath: /busybox-volume volumes: - name: volume-pod hostPath: path: /tmp/volume-pod
(2)查看pod的運行情況 kubectl get pods -o wide
(3)來到運行的worker節點:
docker ps | grep volume :查看容器
ls /tmp/volume-pod :查看該目錄下的文件,我這里事先准備了一個文本文件。
docker exec -it containerid sh :進入容器
ls /nginx-volume :查看nginx這個容器是否有同步。
ls /busybox-volume :查看busybox是否同步數據。
其實我們會發現,這個時候他們已經同時都同步了數據,且實現了數據共享,這個時候在這三個目錄下隨意更改都會導致其他兩個的內容發生變化。
(4)查看pod中的容器里面的hosts文件,是否一樣。發現是一樣的,並且都是由pod管理的
(5)所以一般container中的存儲或者網絡的內容,不要在container層面修改,而是在pod中修改,比如下面修改一下網絡:
spec: hostNetwork: true hostPID: true hostAliases: - ip: "192.168.1.101" hostnames: - "test.wuzz.com" containers: - name: nginx-container image: nginx
PersistentVolumes(PV):
官網 :https://kubernetes.io/docs/concepts/storage/persistent-volumes/
管理存儲與管理計算實例是不同的問題。PersistentVolume子系統為用戶和管理員提供了一個API,它從存儲的使用方式中抽象出存儲提供方式的細節。為此,我們引入了兩個新的API資源:PersistentVolume和PersistentVolumeClaim。持久性卷(PV)是集群中的一段存儲,由管理員提供或使用存儲類動態提供。它是集群中的資源,就像節點是集群資源一樣。PV是與卷類似的卷插件,但其生命周期獨立於使用PV的任何單獨Pod。這個API對象捕獲存儲的實現細節,無論是NFS、iSCSI還是特定於雲提供程序的存儲系統。
PersistentVolumeClaim (PVC)是用戶對存儲的請求。它類似於pod。pod消耗節點資源,而pvc消耗PV資源。Pods可以請求特定級別的資源(CPU和內存)。索賠可以請求特定的大小和訪問模式(例如,它們可以掛載一次讀/寫或多次只讀)。雖然PersistentVolumeClaims允許用戶使用抽象的存儲資源,但是對於不同的問題,用戶通常需要具有不同屬性(例如性能)的持久卷。集群管理員需要能夠提供各種不同於大小和訪問模式的持久性卷,而不需要向用戶展示這些卷是如何實現的。對於這些需求,有StorageClass資源。
說白了,PV是K8s中的資源,volume的plugin實現,生命周期獨立於Pod,封裝了底層存儲卷實現的細節。一個簡單的PV的定義如下:
apiVersion: v1 kind: PersistentVolume metadata: name: my-pv spec: capacity: storage: 5Gi # 存儲空間大小 volumeMode: Filesystem accessModes: - ReadWriteOnce # 只允許一個Pod進行獨占式讀寫操作 persistentVolumeReclaimPolicy: Recycle storageClassName: slow mountOptions: - hard - nfsvers=4.1 nfs: path: /tmp # 遠端服務器的目錄 server: 172.17.0.2 # 遠端的服務器
Kubernetes管理員可以指定附加的掛載選項,以便在節點上掛載持久卷。The following volume types support mount options:
- AWSElasticBlockStore
- AzureDisk
- AzureFile
- CephFS
- Cinder (OpenStack block storage)
- GCEPersistentDisk
- Glusterfs
- NFS
- Quobyte Volumes
- RBD (Ceph Block Device)
- StorageOS
- VsphereVolume
- iSCSI
PersistentVolumeClaim(PVC):
官網 :https://kubernetes.io/docs/concepts/storage/persistent-volumes/#persistentvolumeclaims
有了PV,那Pod如何使用呢?為了方便使用,我們可以設計出一個PVC來綁定PV,然后把PVC交給Pod來使用即可,每個PVC包含一個規格和狀態,這是要求的規格和狀態:
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: myclaim spec: accessModes: - ReadWriteOnce volumeMode: Filesystem resources: requests: storage: 8Gi storageClassName: slow selector: matchLabels: release: "stable" matchExpressions: - {key: environment, operator: In, values: [dev]}
PVC會匹配滿足要求的PV[是根據size和訪問模式進行匹配的],進行一一綁定,然后它們的狀態都會變成Bound。也就是PVC負責請求PV的大小和訪問方式,然后Pod中就可以直接使用PVC咯。
Pod中如何使用PVC:
官網 :https://kubernetes.io/docs/concepts/storage/persistent-volumes/#claims-as-volumes
Pods通過使用聲明作為卷訪問存儲。Claim必須與使用Claim的Pod存在相同的名稱空間。集群在Pod的名稱空間中找到聲明,並使用它獲得支持聲明的PersistentVolume。然后被安裝到host和進入pod。
apiVersion: v1 kind: Pod metadata: name: mypod spec: containers: - name: myfrontend image: nginx volumeMounts: - mountPath: "/var/www/html" name: mypd volumes: - name: mypd persistentVolumeClaim: claimName: myclaim
Pod中使用PVC實戰:
使用nginx持久化存儲演示
(1)共享存儲使用nfs,我這邊只有2台阿里雲,比如nfs選擇在 master節點 (2)創建pv和pvc (3)nginx pod中使用pvc
master節點搭建NFS:
NFS(network file system)網絡文件系統,是FreeBSD支持的文件系統中的一種,允許網絡中的計算機之間通過TCP/IP網絡共享資源。在master節點上搭建一個NFS服務器,目錄為/nfs/data
01 選擇master節點作為nfs的server,所以在master節點上 # 安裝nfs 這個最好在w1節點也運行一下 yum install -y nfs-utils # 創建nfs目錄 mkdir -p /nfs/data/ mkdir -p /nfs/data/mysql # 授予權限 chmod -R 777 /nfs/data # 編輯export文件 vi /etc/exports /nfs/data *(rw,no_root_squash,sync) # 使得配置生效 exportfs -r # 查看生效 exportfs # 啟動rpcbind、nfs服務 systemctl restart rpcbind && systemctl enable rpcbind systemctl restart nfs && systemctl enable nfs # 查看rpc服務的注冊情況 rpcinfo -p localhost # showmount測試 showmount -e master-ip 02 所有node上安裝客戶端 yum -y install nfs-utils systemctl start nfs && systemctl enable nfs
創建PV&PVC&Nginx:
(1)在nfs服務器創建所需要的目錄 mkdir -p /nfs/data/nginx
(2)定義PV,PVC和Nginx的yaml文件 nginx-pv-demo.yaml
# 定義PV apiVersion: v1 kind: PersistentVolume metadata: name: nginx-pv spec: accessModes: - ReadWriteMany capacity: storage: 2Gi nfs: path: /nfs/data/nginx server: 172.18.113.141 --- # 定義PVC,用於消費PV apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nginx-pvc spec: accessModes: - ReadWriteMany resources: requests: storage: 2Gi --- # 定義Pod,指定需要使用的PVC apiVersion: apps/v1beta1 kind: Deployment metadata: name: nginx spec: selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - image: nginx name: nginx ports: - containerPort: 80 volumeMounts: - name: nginx-persistent-storage mountPath: /usr/share/nginx/html volumes: - name: nginx-persistent-storage persistentVolumeClaim: claimName: nginx-pvc
(3)根據yaml文件創建資源並查看資源
kubectl apply -f nginx-pv-demo.yaml
kubectl get pv kubectl get pvc
kubectl get pods -o wide :查看下pods
kubectl describe pod nginx-77945f44db-fjbv8 :查看描述
(4)測試持久化存儲:
01 在/nfs/data/nginx新建文件1.html,寫上內容 我這里寫入 Hello PVC, 02 kubectl get pods -o wide 得到nginx-pod的ip地址 如上圖是 192.168.190.71。切到w1節點 03 curl 192.168.190.71/1.html 04 kubectl exec -it nginx-pod bash 進入/usr/share/nginx/html目錄查看 05 kubectl delete pod nginx-pod 06 查看新nginx-pod的ip並且訪問nginx-pod-ip/1.html
我們會發現在我們刪除后k8s幫我們生成的新的pod內也有這么一個1.html:
整個流程可以用下圖表示:
StorageClass :
上面手動管理PV的方式還是有點low,能不能更加靈活一點呢?
官網 :https://kubernetes.io/docs/concepts/storage/storage-classes/
每個 StorageClass
都包含 provisioner
、parameters
和 reclaimPolicy
字段, 這些字段會在StorageClass
需要動態分配 PersistentVolume
時會使用到。StorageClass
對象的命名很重要,用戶使用這個命名來請求生成一個特定的類。 當創建 StorageClass
對象時,管理員設置 StorageClass 對象的命名和其他參數,一旦創建了對象就不能再對其更新。StorageClass聲明存儲插件,用於自動創建PV。說白了就是創建PV的模板,其中有兩個重要部分:PV屬性和創建此PV所需要的插件。這樣PVC就可以按“Class”來匹配PV。可以為PV指定storageClassName屬性,標識PV歸屬於哪一個Class。
apiVersion: storage.k8s.io/v1 kind: StorageClass metadata: name: standard provisioner: kubernetes.io/aws-ebs parameters: type: gp2 reclaimPolicy: Retain allowVolumeExpansion: true mountOptions: - debug volumeBindingMode: Immediate
存儲分配器:StorageClass
有一個分配器,用來決定使用哪個卷插件
分配持久化卷申領
。該字段必須指定。其中官方提供的分配器列表如下:
- 對於PV或者StorageClass只能對應一種后端存儲,也就是比如不能既使用 NFS 又使用 aws-ebs
- 對於手動的情況,一般我們會創建很多的PV,等有PVC需要使用的時候就可以直接使用了,而對於自動的情況,那么就由StorageClass來自動管理創建
- 如果Pod想要使用共享存儲,一般會在創建PVC,PVC中描述了想要什么類型的后端存儲、空間等,K8s從而會匹配對應的PV,如果沒有匹配成功,Pod就會處於Pending狀態。Pod中使用只需要像使用volumes一樣,指定名字就可以使用了
- 一個Pod可以使用多個PVC,一個PVC也可以給多個Pod使用
- 一個PVC只能綁定一個PV,一個PV只能對應一種后端存儲
有了StorageClass之后的PVC可以變成這樣
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: test-claim1 spec: accessModes: - ReadWriteMany resources: requests: storage: 1Mi storageClassName: nfs
StorageClass之所以能夠動態供給PV,是因為Provisioner,也就是Dynamic Provisioning但是NFS這種類型,K8s中默認是沒有Provisioner插件的,需要自己創建,但是Github 上給我們提供了這一需求。nfs github :https://github.com/kubernetes-incubator/external-storage/tree/master/nfs
StorageClass實戰:
(1)准備好NFS服務器[並且確保nfs可以正常工作],創建持久化需要的目錄
path: /nfs/data/wuzz 比如mkdir -p /nfs/data/wuzz
server: 192.168.1.101
(2)根據rbac.yaml文件(基於角色的訪問控制)創建資源 kubectl apply -f rbac.yaml
kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: nfs-provisioner-runner rules: - apiGroups: [""] resources: ["persistentvolumes"] verbs: ["get", "list", "watch", "create", "delete"] - apiGroups: [""] resources: ["persistentvolumeclaims"] verbs: ["get", "list", "watch", "update"] - apiGroups: ["storage.k8s.io"] resources: ["storageclasses"] verbs: ["get", "list", "watch"] - apiGroups: [""] resources: ["events"] verbs: ["create", "update", "patch"] - apiGroups: [""] resources: ["services", "endpoints"] verbs: ["get"] - apiGroups: ["extensions"] resources: ["podsecuritypolicies"] resourceNames: ["nfs-provisioner"] verbs: ["use"] --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: run-nfs-provisioner subjects: - kind: ServiceAccount name: nfs-provisioner # replace with namespace where provisioner is deployed namespace: default roleRef: kind: ClusterRole name: nfs-provisioner-runner apiGroup: rbac.authorization.k8s.io --- kind: Role apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-provisioner rules: - apiGroups: [""] resources: ["endpoints"] verbs: ["get", "list", "watch", "create", "update", "patch"] --- kind: RoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: leader-locking-nfs-provisioner subjects: - kind: ServiceAccount name: nfs-provisioner # replace with namespace where provisioner is deployed namespace: default roleRef: kind: Role name: leader-locking-nfs-provisioner apiGroup: rbac.authorization.k8s.io
(3)根據deployment.yaml文件創建資源 kubectl apply -f deployment.yaml
Service account是為了方便Pod里面的進程調用Kubernetes API或其他外部服務而設計的。
apiVersion: v1 kind: ServiceAccount metadata: name: nfs-provisioner --- kind: Deployment apiVersion: extensions/v1beta1 metadata: name: nfs-provisioner spec: replicas: 1 strategy: type: Recreate template: metadata: labels: app: nfs-provisioner spec: serviceAccount: nfs-provisioner containers: - name: nfs-provisioner image: registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-client-provisioner volumeMounts: - name: nfs-client-root mountPath: /persistentvolumes env: - name: PROVISIONER_NAME value: example.com/nfs - name: NFS_SERVER value: 192.168.1.101 - name: NFS_PATH value: /nfs/data/wuzz volumes: - name: nfs-client-root nfs: server: 192.168.1.101 path: /nfs/data/wuzz
(4)根據class.yaml創建資源 kubectl apply -f class.yaml
kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: example-nfs provisioner: example.com/nfs
(5)根據pvc.yaml創建資源 kubectl apply -f my-pvc.yaml
kubectl get pvc
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: my-pvc spec: accessModes: - ReadWriteMany resources: requests: storage: 1Mi # 這個名字要和上面創建的storageclass名稱一致 storageClassName: example-nfs
(6)根據nginx-pod創建資源
kubectl apply -f nginx-pod.yaml
kubectl exec -it nginx bash
cd /usr/wuzz # 進行同步數據測試
kind: Pod apiVersion: v1 metadata: name: nginx spec: containers: - name: nginx image: nginx volumeMounts: - name: my-pvc mountPath: "/usr/wuzz" restartPolicy: "Never" volumes: - name: my-pvc persistentVolumeClaim: claimName: my-pvc
PV的狀態和回收策略:
PV的狀態:
- Available:表示當前的pv沒有被綁定
- Bound:表示已經被pvc掛載
- Released:pvc沒有在使用pv, 需要管理員手工釋放pv
- Failed:資源回收失敗
PV回收策略:
- Retain:表示刪除PVC的時候,PV不會一起刪除,而是變成Released狀態等待管理員手動清理
- Recycle:在Kubernetes新版本就不用了,采用動態PV供給來替代
- Delete:表示刪除PVC的時候,PV也會一起刪除,同時也刪除PV所指向的實際存儲空間
注意 :目前只有NFS和HostPath支持Recycle策略。AWS EBS、GCE PD、Azure Disk和Cinder支持Delete策略