第七章 Kubernetes存儲
1、數據卷與數據持久卷
為什么需要數據卷?
容器中的文件在磁盤上是臨時存放的,這給容器中運行比較重要的應用程序帶來一些問題。
-
問題1:當容器升級或者崩潰時,kubelet會重建容器,容器內文件會丟失;
-
問題2:一個Pod中運行多個容器并需要共享文件;
Kubernetes 卷(Volume) 這一抽象概念能夠解決這兩個問題。
1.?數據持久化
- 容器的無狀態性:Kubernetes 中的容器是無狀態的,這意味著它們在重啟或重新調度時,容器內的數據會丟失。
- 持久化數據:通過使用數據卷,可以將數據持久化到外部存儲系統中,確保數據在容器生命周期外仍然存在。
2.?數據共享
- 多容器共享數據:在某些情況下,同一個 Pod 中的多個容器需要共享數據。數據卷允許這些容器共享同一個存儲卷。
- 跨容器協作:例如,一個容器生成日志文件,另一個容器需要讀取這些日志文件。
3.?數據隔離
- 獨立存儲:不同的 Pod 可以使用不同的數據卷,確保數據隔離,避免數據沖突。
- 安全性:通過將敏感數據存儲在獨立的數據卷中,可以提高安全性。
4.?簡化配置
- 統一管理:使用數據卷可以簡化應用程序的配置,因為存儲配置可以在 Kubernetes 中集中管理。
- 靈活性:可以根據需要選擇不同的存儲后端(如本地存儲、云存儲、網絡文件系統等),而不需要修改應用程序代碼。
5.?生命周期管理
- 數據生命周期:數據卷可以獨立于 Pod 的生命周期管理,確保數據在 Pod 重啟或刪除后仍然可用。
- 自動掛載:Kubernetes 可以自動掛載和卸載數據卷,簡化操作。
常用的數據卷:
官網:Volumes | Kubernetes
- 節點本地存儲(hostPath,emptyDir)
- 網絡存儲(NFS,Ceph,GlusterFS)
- 公有云存儲(AWS EBS)
- K8S資源存儲(configmap,secret)
1.1 臨時數據卷:emptyDir
emptyDir卷是一個臨時存儲卷,與Pod生命周期綁定一起,如果 Pod刪除了卷也會被刪除。
應用場景:Pod中容器之間數據共享
選項:
-
sizeLimit:500Mi? ? ? ?//限制共享空間大小(比較少用)
示例:Pod內容器之間共享數據
apiVersion: v1
kind: Pod
metadata: name: emptydir-test
spec:containers:- name: write-container //程序講數據寫入到文件image: centoscommand: ["bash","-c","for i in {1..100};do echo $i >>
/data/hello;sleep 1;done"]volumeMounts:- name: data-volume //通過volume卷名稱引用mountPath: /data- name: read-container //程序從文件中讀取數據image: centoscommand: ["bash","-c","tail -f /data/hello"]volumeMounts:- name: data-volumemountPath: /datavolumes: //定義卷的來源- name: data-volumeemptyDir: {} //{}中為空值
驗證1:驗證容器之間是否能夠共享數據
[root@k8s-master-1-71 ~]# kubectl apply -f emptydir-test.yaml
[root@k8s-master-1-71 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
emptydir-test 2/2 Running 1 (21s ago) 3m14s
# 進入write-container查看
[root@k8s-master-1-71 ~]# kubectl exec -it emptydir-test -- bash[root@emptydir-test /]# tail -f /data/hello
...
99
100
command terminated with exit code 137
[root@emptydir-test /]# touch /data/aaa ; ls /data/ //往容器中寫入臨時文件
aaa hello## 注:腳本循環結束后退出容器,按照默認的策略Always進行容器重啟。
# 進入read-container查看
[root@k8s-master-1-71 ~]# kubectl exec -it emptydir-test -c read-container -- bash
[root@emptydir-test /]# ls /data/
aaa hello
驗證2:實際存儲位置(基于節點的存儲)
補充:Kubelet的工作目錄為/var/lib/kubelet/,負載維護Pod數據的
[root@k8s-master-1-71 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
emptydir-test 2/2 Running 2 (45s ago) 5m3s 10.244.114.48 k8s-node2-1-73 <none> <none># 根據查看到的Pod所在節點,找到相應的容器ID
[root@k8s-node2-1-73 ~]# docker ps | grep emptydir-test(例如:7c26307b-b290-4bac-9a3b-6f18ffc26776)
6deb6dc27ab2 centos "bash -c 'for i in {…" 44 seconds ago Up 43 seconds k8s_write-container_emptydir-test_default_7c26307b-b290-4bac-9a3b-6f18ffc26776_3
7bb46657087b centos "bash -c 'tail -f /d…" 7 minutes ago Up 7 minutes k8s_read-container_emptydir-test_default_7c26307b-b290-4bac-9a3b-6f18ffc26776_0
fd4c6c671807 registry.aliyuncs.com/google_containers/pause:3.7 "/pause" 7 minutes ago Up 7 minutes k8s_POD_emptydir-test_default_7c26307b-b290-4bac-9a3b-6f18ffc26776_0# 找到相關Pod關聯的empty-dir目錄,即可看到掛載的empty-dir目錄內容
[root@k8s-node2-1-73 kubernetes.io~empty-dir]# pwd
/var/lib/kubelet/pods/7c26307b-b290-4bac-9a3b-6f18ffc26776/volumes/kubernetes.io~empty-dir[root@k8s-node2-1-73 kubernetes.io~empty-dir]# ls data-volume/
aaa bbb hello
Pod是節點級別的,Pod中的容器都是捆綁在一個節點上,所以 Pod刪除了,卷也會被刪除
[root@k8s-master-1-71 ~]# kubectl delete -f emptydir-test.yaml
[root@k8s-node2-1-73 kubernetes.io~empty-dir]# ls data-volume/
ls: 無法訪問data-volume/: 沒有那個文件或目錄
1.2 節點數據卷:hostPath
hostPath卷掛載Node的文件系統(即Pod所在節點)上文件或者目 錄到Pod中的容器。
應用場景:Pod中容器需要訪問宿主機的文件
選項:
-
path:? ? //將宿主機的目錄或文件映射到容器中去
-
type:? ? //指定類型(目錄或文件)
示例:將宿主機/tmp目錄掛載到容器/data目錄
apiVersion: v1
kind: Pod
metadata:name: hostpath-test
spec:containers:- name: busyboximage: busyboxargs:- /bin/sh- -c- sleep 36000volumeMounts:- name: data-volumemountPath: /datavolumes:- name: data-volumehostPath:path: /tmp //Pod所在節點的/tmp目錄type: Directory
驗證:
[root@k8s-master-1-71 ~]# kubectl apply -f hostpath-test.yaml[root@k8s-master-1-71 ~]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
hostpath-test 1/1 Running 0 86s 10.244.114.49 k8s-node2-1-73 <none> <none># 查看Pod所在節點的/tmp目錄
[root@k8s-node2-1-73 ~]# ls /tmp/
systemd-private-85c79618aae44d7cbb01bccc046ecb10-chronyd.service-UMOhQf
systemd-private-fdf2b8d40ff841319feb738a463a8f6f-chronyd.service-GJT2Mn# 進入容器查看/data目錄是否有相關映射文件
[root@k8s-master-1-71 ~]# kubectl exec -it hostpath-test -- sh
/ # ls /data/
systemd-private-85c79618aae44d7cbb01bccc046ecb10-chronyd.service-UMOhQf
systemd-private-fdf2b8d40ff841319feb738a463a8f6f-chronyd.service-GJT2Mn
1.3 網絡數據卷:NFS
NFS:是一個主流的文件共享服務器(注:每個Node上都要安裝nfs-utils包)
# 服務端部署
[root@k8s-node1-1-72 ~]# yum install -y nfs-utils
[root@k8s-node1-1-72 ~]# vi /etc/exports //NFS共享配置目錄
/ifs/kubernetes *(rw,no_root_squash)
# 解釋:
共享目錄 訪問來源限制(權限)
[root@k8s-node1-1-72 ~]# mkdir -p /ifs/kubernetes[root@k8s-node1-1-72 ~]# systemctl enable nfs --now# 客戶端測試
[root@k8s-node2-1-73 ~]# yum install -y nfs-utils
[root@k8s-node2-1-73 ~]# mount -t nfs 192.168.1.72:/ifs/kubernetes /mnt
[root@k8s-node2-1-73 ~]# df -Th | grep /ifs/kubernetes
192.168.1.72:/ifs/kubernetes nfs4 37G 4.3G 33G 12% /mnt
NFS卷:提供對NFS掛載支持,可以自動將NFS共享路徑 掛載到Pod中
選項:
-
server: //指定NFS地址
-
path: /指定NFS共享目錄
示例:將Nginx網站程序根目錄持久化到 NFS存儲,為多個Pod提供網站程序文件
apiVersion: apps/v1
kind: Deployment
metadata:labels:app: nfs-podname: nfs-pod
spec:selector:matchLabels:app: nginxreplicas: 3template:metadata:labels:app: nginxspec:containers:- name: webimage: nginxvolumeMounts:- name: data-volumemountPath: /usr/share/nginx/html //掛載的目錄volumes:- name: data-volume //volume卷名稱nfs:server: 192.168.1.72 //指定NFS地址path: /ifs/kubernetes //NFS共享目錄
驗證1:容器之間的數據是否共享
[root@k8s-node2-1-73 ~]# kubectl apply -f nfs-pod.yaml
[root@k8s-master-1-71 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGEnfs-pod-9b57f886-d8rzx 1/1 Running 0 10m
nfs-pod-9b57f886-tk99z 1/1 Running 0 55s
nfs-pod-9b57f886-wpr9q 1/1 Running 0 10m# 進入容器1,創建文件測試
[root@k8s-node2-1-73 ~]# kubectl exec -it nfs-pod-9b57f886-d8rzx -- bash
root@nfs-pod-9b57f886-d8rzx:/# df -Th |grep /ifs/kubernetes
192.168.1.72:/ifs/kubernetes nfs4 37G 4.3G 33G 12% /usr/share/nginx/html
root@nfs-pod-9b57f886-d8rzx:~# touch /usr/share/nginx/html/aaaaa
root@nfs-pod-9b57f886-d8rzx:~# ls /usr/share/nginx/html/
aaaaa# 進入容器2,查看文件
[root@k8s-node2-1-73 ~]# kubectl exec -it nfs-pod-9b57f886-tk99z -- bash
root@nfs-pod-9b57f886-tk99z:/# ls /usr/share/nginx/html/
aaaaa
驗證2:增加/刪除Pod是否能繼續使用共享存儲數據
# 刪除Pod,查看是否能繼續使用共享存儲數據
[root@k8s-master-1-71 ~]# kubectl delete pod nfs-pod-9b57f886-tk99z[root@k8s-master-1-71 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
nfs-pod-9b57f886-d8rzx 1/1 Running 0 10m
nfs-pod-9b57f886-fg7n7 1/1 Running 0 55s //新增Pod
nfs-pod-9b57f886-wpr9q 1/1 Running 0 10m[root@k8s-node2-1-73 ~]# kubectl exec -it nfs-pod-9b57f886-fg7n7 -- bash
root@nfs-pod-9b57f886-fg7n7:/# ls /usr/share/nginx/html/
aaaaa# 增加Pod,查看是否能繼續使用共享存儲數據
[root@k8s-master-1-71 ~]# kubectl scale deployment nfs-pod --replicas=5
[root@k8s-node2-1-73 ~]# kubectl exec -it nfs-pod-9b57f886-sj7m6 -- bash
root@nfs-pod-9b57f886-sj7m6:/# df -Th | grep /ifs/kubernetes
192.168.1.72:/ifs/kubernetes nfs4 37G 4.3G 33G 12% /usr/share/nginx/html
root@nfs-pod-9b57f886-sj7m6:/# ls /usr/share/nginx/html/
aaaaa
1.4 持久數據卷概述
持久卷(Persistent Volumes, PV) 是用于管理存儲的重要概念。
安全性:如果要設置安全方面的認證,都需要提前將安全配置寫入YAML,因此將會暴露在YAML文件中,導致安全性減低;
專業性:在應用的部署上,使用者對K8S、存儲的不了解,對于建設者來說,倡導職責上的分離。
1.4.1 PV、PVC
持久卷(Persistent Volumes, PV)
- 定義:持久卷是集群中的一塊存儲,由管理員配置和管理。它們獨立于 Pod 的生命周期,可以被多個 Pod 使用。
- 生命周期:持久卷的生命周期獨立于 Pod,即使 Pod 被刪除,數據仍然保留。
對存儲資源創建和使用的抽象,使得存儲作為集群中的資源管理(定義后端存儲)
持久卷聲明(Persistent Volume Claims, PVC)
- 定義:持久卷聲明是用戶對持久卷的請求。用戶不需要了解底層存儲的細節,只需要聲明所需的存儲大小和訪問模式。
- 生命周期:持久卷聲明的生命周期與 Pod 的生命周期無關,可以獨立存在。
- 綁定:持久卷聲明會被綁定到一個滿足其請求的持久卷上。一旦綁定,PVC 和 PV 之間是一對一的關系。
讓用戶不需要關心具體的Volume實現細節(訪問模式、存儲容量大小)
Pod申請PVC作為卷來使用,Kubernetes通過PVC查找綁定的PV,并Mount給Pod。
支持持久卷的存儲插件:Persistent Volumes | Kubernetes
1.4.2 PV與PVC使用流程
官網:配置 Pod 以使用 PersistentVolume 作為存儲 | Kubernetes
PVC配置示例:
--- //容器應用
apiVersion: apps/v1
kind: Deployment
metadata: labels:app: my-podname: my-pod
spec:selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- name: webimage: nginxvolumeMounts:- name: data-volumemountPath: /usr/share/nginx/html //掛載的目錄volumes:- name: data-volumepersistentVolumeClaim:claimName: my-pvc--- //PVC 卷需求模板
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: my-pvc //與claimName對應進行關聯
spec:accessModes:- ReadWriteManyresources:requests:storage: 5Gi
- 查看PVC命令:kubectl get pvc
[root@k8s-master-1-71 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
my-pod-6fbc98b678-42m29 0/1 Pending 0 10s
[root@k8s-master-1-71 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-pvc Pending 18s
注意:如果沒有可用的PV,PVC無法進行資源分配會處于在Pending狀態
PV配置示例:
apiVersion: v1
kind: PersistentVolume
metadata:name: my-pv //隨便定義
spec:capacity:storage: 5Gi //后端存儲定義資源accessModes:- ReadWriteManynfs:path: /ifs/kubernetesserver: 192.168.1.72
- 查看PV命令:kubectl get pv
[root@k8s-master-1-71 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
my-pv 5Gi RWX Retain Bound default/my-pvc 11s
[root@k8s-master-1-71 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-pvc Bound my-pv 5Gi RWX 2m30s
[root@k8s-master-1-71 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
my-pod-6fbc98b678-42m29 1/1 Running 0 3m5s
測試:
[root@k8s-master-1-71 ~]# kubectl exec -it my-pod-6fbc98b678-42m29 -- bash
root@my-pod-6fbc98b678-42m29:/# df -Th | grep /ifs/kubernetes
192.168.1.72:/ifs/kubernetes nfs4 37G 4.3G 33G 12% /usr/share/nginx/html
root@my-pod-6fbc98b678-42m29:/# ls /usr/share/nginx/html/
aaaaa
思考:多PV配置
掛載PV的時候,不能將掛載點掛到同一目錄,為保證應用的唯一性,需要在掛載點的節點上創建各自的目錄,避免沖突
多PV配置示例:
apiVersion: v1
kind: PersistentVolume
metadata:name: pv001 //隨便定義
spec:capacity:storage: 5Gi //后端存儲定義資源accessModes:- ReadWriteManynfs:path: /ifs/kubernetes/pv001server: 192.168.1.72
---
apiVersion: v1
kind: PersistentVolume
metadata:name: pv002 //隨便定義
spec:capacity:storage: 15Gi //后端存儲定義資源accessModes:- ReadWriteManynfs:path: /ifs/kubernetes/pv002server: 192.168.1.72
總結:
1、PV與PVC怎么匹配?
主要根據PVC的存儲容量和訪問模式進行匹配
2、存儲容量怎么匹配?
容量只會向上匹配,如已有未使用PV有10G、20G,申請5G,向上取最近的PV容量10G
3、PV與PVC的關系?
一對一,存在綁定關系
4、容量請求是否有實際的限制?
目前容量請求主要用作于PVC與PV進行匹配的,只是抽象的存在,而具體的限制取決于后端存儲,即請求容量不能超過共享存儲的實際容量
1.4.3 PV 生命周期
1)AccessModes(訪問模式):
AccessModes 是用來對 PV 進行訪問模式的設置,用于描述用戶應用對存儲資源的訪問權限,訪問權限包括下面幾種方式:
-
ReadWriteOnce(RWO):讀寫權限,但是只能被單個節點掛載
-
ReadOnlyMany(ROX):只讀權限,可以被多個節點掛載
-
ReadWriteMany(RWX):讀寫權限,可以被多個節點掛載
備注:塊存儲(單節點)、文件系統、對象存儲(多節點)
2)RECLAIM POLICY(回收策略):
目前 PV 支持的策略有三種:
-
Retain(保留): 保留數據,需要管理員手工清理數據(默認策略)
-
Recycle(回收):清除 PV 中的數據,效果相當于執行 rm -rf /ifs/kuberneres/*(一般結合StorageClass使用,NFS暫時無法看出效果)
-
Delete(刪除):與 PV 相連的后端存儲同時刪除(一般結合StorageClass使用,NFS暫時無法看出效果)
persistentVolumeReclaimPolicy: 回收策略
3)STATUS(狀態):
一個 PV 的生命周期中,可能會處于4中不同的階段:
-
Available(可用):表示可用狀態,還未被任何 PVC 綁定
-
Bound(已綁定):表示 PV 已經被 PVC 綁定
-
Released(已釋放):PVC 被刪除,但是資源還未被集群重新聲明
-
Failed(失敗): 表示該 PV 的自動回收失敗
示例:觀察回收狀態和默認的Retain回收策略
[root@k8s-master-1-71 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
my-pv 5Gi RWX Retain Bound default/my-pvc 7h7m## 解釋:目前回收策略為 Retain ,狀態為 Bound(表示 PV 已經被 PVC 綁定)
[root@k8s-master-1-71 ~]# kubectl delete -f my-pvc.yaml
[root@k8s-master-1-71 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
my-pv 5Gi RWX Retain Released default/my-pvc 7h18m## 解釋:連著刪除Pod和PCV后,回收策略為 Retain ,狀態為 Released(PVC 被刪除,但是資源還未被集群重新聲明)
[root@k8s-node1-1-72 ~]# ls /ifs/kubernetes/ //PV中的資源依舊保留,需要管理員手工清理數據
aaaaa
注意:刪除PVC后,原來Bound的PV就無法繼續使用,即使重新apply pvc.yaml,也是Pending狀態。
思考:現在PV使用方式稱為靜態供給,需要K8s運維工程師提前創 建一堆PV,供開發者使用
1.5 PV 動態供給(StorageClass)
PV靜態供給明顯的缺點是維護成本太高了,需要提前創建PV且不靈活! 因此,K8s開始支持PV動態供給,使用StorageClass對象實現。StorageClass可以根據客戶的PVC需求,通過PVC需求自動創建后端存儲PV,且自動去綁定,無需像NFS還要創建目錄隔離應用。
優點:
-
PV無需額外的提前獨立創建;
-
PVC直接獲取,也不用等待合適的PV;
支持動態供給的存儲插件:Storage Classes | Kubernetes
相關GitHub部署:https://github.com/kubernetes-sigs/sig-storage-lib-external-provisioner
了解:Volume Plugin是支持存儲的類型,Internal Provisioner內部是否支持
例如NFS內部是不支持的(不能直接PVC動態供給),且K8s默認不支持NFS動態供給,需要單獨部署社區開發的插件
項目地址:https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner
部署StorageClass插件需要的3個文件:
cd deploy
kubectl apply -f rbac.yaml # 授權訪問apiserver
kubectl apply -f deployment.yaml # 部署插件,需修改里面NFS服務器地址與共享目錄
kubectl apply -f class.yaml # 創建存儲類(標識使用哪個存儲)kubectl get sc # 查看存儲類
補充:一個集群中可以有多個存儲類,而一個存儲類一般對應一個存儲
流程圖:
-
rbac.yaml 示例:
[root@k8s-master-1-71 nfs-external-provisioner]# cat rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:name: nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: nfs-client-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"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: run-nfs-client-provisioner
subjects:- kind: ServiceAccountname: nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: default
roleRef:kind: ClusterRolename: nfs-client-provisioner-runnerapiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: leader-locking-nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: default
rules:- apiGroups: [""]resources: ["endpoints"]verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: leader-locking-nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: default
subjects:- kind: ServiceAccountname: nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: default
roleRef:kind: Rolename: leader-locking-nfs-client-provisionerapiGroup: rbac.authorization.k8s.io
- class.yaml 配置示例:
[root@k8s-master-1-71 nfs-external-provisioner]# cat class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: managed-nfs-storage //StroagaClass存儲類,在PVC中需要指定的標識(類似ingressclass選擇Nginx)
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME' //與Deployment的變量PROVISIONER_NAME保持一致
parameters:archiveOnDelete: "false"
- deployment.yaml 配置示例:
[root@k8s-master-1-71 nfs-external-provisioner]# cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: nfs-client-provisionerlabels:app: nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: default
spec:replicas: 1strategy:type: Recreateselector:matchLabels:app: nfs-client-provisionertemplate:metadata:labels:app: nfs-client-provisionerspec:serviceAccountName: nfs-client-provisionercontainers:- name: nfs-client-provisioner //部署了NFS供給程序的容器image: lizhenliang/nfs-subdir-external-provisioner:v4.0.1 //鏡像地址volumeMounts:- name: nfs-client-rootmountPath: /persistentvolumesenv:- name: PROVISIONER_NAME //class.yaml中的PROVISIONER_NAMEvalue: k8s-sigs.io/nfs-subdir-external-provisioner- name: NFS_SERVERvalue: 192.168.1.72 //需要指定后端NFS存儲- name: NFS_PATHvalue: /ifs/kubernetesvolumes:- name: nfs-client-rootnfs:server: 192.168.1.72 //需要指定后端NFS存儲path: /ifs/kubernetes
- PVC指定存儲類配置示例:
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: my-pvc
spec:storageClassName: "managed-nfs-storage" //指定StorageClass存儲類(class.yaml)accessModes:- ReadWriteMany resources:requests:storage: 5Gi
測試1:基于NFS提供PVC動態供給
步驟1:部署NFS-StorageClass 存儲類插件
[root@k8s-master-1-71 nfs-external-provisioner]# kubectl apply -f .
storageclass.storage.k8s.io/managed-nfs-storage created
deployment.apps/nfs-client-provisioner created
serviceaccount/nfs-client-provisioner created
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created# 創建的Pod為deployment.yaml中的NFS供給程序容器
[root@k8s-master-1-71 nfs-external-provisioner]# kubectl get pods
NAME READY STATUS RESTARTS AGEnfs-client-provisioner-5848c9cddc-zkts2 1/1 Running 0 2m27s# 創建的storageclass為class.yaml中指定的存儲類
[root@k8s-master-1-71 nfs-external-provisioner]# kubectl get storageclass
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
managed-nfs-storage k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 2m54s
步驟2:在PVC中指定存儲類名稱
[root@k8s-master-1-71 ~]# kubectl apply -f my-pvc.yaml
apiVersion: apps/v1
kind: Deployment
metadata:labels:app: my-podname: my-pod
spec:selector:matchLabels:app: my-podtemplate:metadata:labels:app: my-podspec:containers:- name: my-podimage: nginxvolumeMounts:- name: data-volumemountPath: /usr/share/nginx/htmlvolumes:- name: data-volumepersistentVolumeClaim:claimName: my-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: my-pvcspec:storageClassName: "managed-nfs-storage"accessModes:- ReadWriteManyresources:requests:storage: 5Gi[root@k8s-master-1-71 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
my-pod-6fbc98b678-t2gvg 1/1 Running 0 4m11s
nfs-client-provisioner-5848c9cddc-zkts2 1/1 Running 0 24m[root@k8s-master-1-71 ~]# kubectl get pvc //查看PVC
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
my-pvc Bound pvc-51d00de4-52ea-4f1a-988a-bc3e5b7e961e 5Gi RWX managed-nfs-storage 3s[root@k8s-master-1-71 ~]# kubectl get pv //查看PV
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-51d00de4-52ea-4f1a-988a-bc3e5b7e961e 5Gi RWX Delete Bound default/my-pvc managed-nfs-storage 6s# 進入容器查看掛載
[root@k8s-master-1-71 ~]# kubectl exec -it my-pod-6fbc98b678-t2gvg -- bash
root@my-pod-6fbc98b678-t2gvg:/# df -Th | grep pvc-51d00de4-52ea-4f1a-988a-bc3e5b7e961e
192.168.1.72:/ifs/kubernetes/default-my-pvc-pvc-51d00de4-52ea-4f1a-988a-bc3e5b7e961e nfs4 37G 4.4G 33G 12% /usr/share/nginx/html
# 查看NFS服務器的掛載目錄,可看到隨機生成的PV目錄
結論:通過基于NFS提供PVC動態供給,無需再定義單獨的PV,也無需為了指定到某個NFS目錄手動創建子目錄,動態供給會自動幫忙實現PV及隨機生成NFS子目錄
注意:由于NFS提供PVC動態供給的PV的默認回收策略是 Delete ,所以在刪除PVC的同時,也會將PV一起刪除;在NFS目錄上的文件也一并刪除。
測試2:
[root@k8s-master-1-71 ~]# kubectl apply -f my-pvc2.yaml
[root@k8s-node1-1-72 ~]# ls /ifs/kubernetes/
aaaaa default-my-pvc2-pvc-b7769112-aea6-4533-9e5c-5d14cd67fc65 default-my-pvc-pvc-51d00de4-52ea-4f1a-988a-bc3e5b7e961e[root@k8s-master-1-71 ~]# kubectl delete -f my-pvc2.yaml
[root@k8s-node1-1-72 ~]# ls /ifs/kubernetes/
aaaaa default-my-pvc-pvc-51d00de4-52ea-4f1a-988a-bc3e5b7e961e
由于考慮數據的重要性,希望刪除PVC時保留數據備份
[root@k8s-master-1-71 nfs-external-provisioner]# vi class.yaml
...archiveOnDelete: "True" //將archiveOnDelete修改為 True,即可在刪除PVC時保留備份# 需要刪除class.yaml并重新apply應用
[root@k8s-master-1-71 nfs-external-provisioner]# kubectl delete -f class.yaml[root@k8s-master-1-71 nfs-external-provisioner]# kubectl apply -f class.yaml
# 測試刪除PVC
[root@k8s-master-1-71 ~]# kubectl delete -f my-pvc.yaml
# 查看NFS服務器的掛載目錄,原來的PV目錄已重新命名(備份)
2、有狀態應用部署初探
無狀態與有狀態:
Deployment控制器設計原則:管理的所有Pod一模一樣且提供同一個服務(replicas),使用共享存儲,之間沒有連接關系,也不考慮在哪臺Node運 行,可隨意擴容和縮容。這種應用稱為“無狀態”,例如Web服務
在實際的場景中,這并不能滿足所有應用,尤其是分布式應用,會部署多個實例,這些實例之間往往有 依賴關系,部署的角色也不一樣,例如主從關系、主備關系,這種應用稱為“有狀態”,例如MySQL主從、Etcd集群
無狀態特點:
-
每個Pod都一樣,且提供同一種服務
-
Pod之間沒有連接關系
-
使用共享存儲
有狀態特點:
-
每個Pod不對等,角色屬性也不同
-
Pod之間有連接關系(類似數據庫主從)
-
每個Pod的數據都是有差異化的,需要獨立的存儲進行持久化,否則會產生沖突
2.1 StatefulSet 控制器介紹
StatefulSet控制器用于部署有狀態應用,滿足一些有狀態應用的需求:
-
Pod有序的部署、擴容、刪除和停止
-
Pod分配一個穩定的且唯一的網絡標識
-
Pod分配一個獨享的存儲
2.2 StatefulSet 部署應用實踐
1)穩定的網絡ID(域名)
使用 Headless Service(相比普通Service只是將spec.clusterIP定義為None)來維護Pod網絡身份。 并且添加 serviceName: “headless-svc” 字段指定 StatefulSet控制器 要使用這個Headless Service。讓StatefulSet控制器為其創建每個Pod固定的域名解析地址。
DNS解析名稱:<statefulsetName-index>.<service-name> .<namespace-name>.svc.cluster.local
[root@k8s-master-1-71 ~]# kubectl create deployment stateful-pod --image=nginx
[root@k8s-master-1-71 ~]# kubectl expose deployment stateful-pod --port=80 --target-port=80 --dry-run=client -o yaml > headless-svc.yaml
[root@k8s-master-1-71 ~]# kubectl apply -f headless-svc.yaml
apiVersion: v1
kind: Service
metadata:labels:app: headless-svcname: headless-svc
spec:clusterIP: None //指定SVC的clusterIP為Noneports:- port: 80protocol: TCPtargetPort: 80selector:app: stateful-pod[root@k8s-master-1-71 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
headless-svc ClusterIP None <none> 80/TCP 8s
補充:Cluster-IP為None,標識該Service為headlress無頭服務,這種Service將沒有負載均衡轉發的功能;
2)穩定的存儲
StatefulSet的存儲卷 使用VolumeClaimTemplate創建,稱為卷申請模板(針對StatefulSet專門設置的類型),當StatefulSet使用VolumeClaimTemplate創建 一個PersistentVolume時,同樣也會為每個Pod分配并創建一個編號的PVC(一個PV對應一個PVC)
參考文檔:StatefulSets | Kubernetes
參考:https://github.com/lizhenliang/k8s-statefulset
示例:StatefulSet部署(包括無頭服務、StatefulSet+卷申請模)
[root@k8s-master-1-71 ~]# kubectl apply -f statefulset-test.yaml
# headless Service 服務配置示例
apiVersion: v1
kind: Service
metadata:name: nginx-headless //headlessService服務名稱,需要和StatefulSet的ServiceName保持一致labels:app: nginx
spec:ports:- port: 80name: webclusterIP: None //設置cluster-IP為Noneselector:app: nginx //指定StatefulSet的Pod# StatefulSet 配置示例
---
apiVersion: apps/v1
kind: StatefulSet
metadata:name: web
spec:selector:matchLabels:app: nginx # has to match .spec.template.metadata.labelsserviceName: "nginx-headless" # 指定 headless Service 服務名稱replicas: 3 # by default is 1minReadySeconds: 10 # by default is 0template:metadata:labels:app: nginx # has to match .spec.selector.matchLabelsspec:terminationGracePeriodSeconds: 10containers:- name: nginximage: nginxports:- containerPort: 80name: webvolumeMounts:- name: www-datamountPath: /usr/share/nginx/htmlvolumeClaimTemplates: //卷申請模板(為StatefulSet專門設置的類型)- metadata:name: www-dataspec:accessModes: [ "ReadWriteOnce" ]storageClassName: "managed-nfs-storage" # 指定 storageClass(kubectl get sc)resources:requests:storage: 1Gi
查看信息:
[root@k8s-master-1-71 ~]# kubectl get pods
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 4m32s
web-1 1/1 Running 0 3m31s
web-2 1/1 Running 0 2m51s# 查看 headless Service
[root@k8s-master-1-71 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-headless ClusterIP None <none> 80/TCP 9m52s# 查看 headless Service 對應后端的Pod
[root@k8s-master-1-71 ~]# kubectl get ep
NAME ENDPOINTS AGE
nginx-headless 10.244.114.10:80,10.244.117.50:80,10.244.117.51:80 13m# 查看 PVC
[root@k8s-master-1-71 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-data-web-0 Bound pvc-c8f06f51-5048-4a43-b822-9f6bcd1bd20c 1Gi RWO managed-nfs-storage 10m
www-data-web-1 Bound pvc-ae9c2a66-0bcb-489e-98d0-0350d480fb68 1Gi RWO managed-nfs-storage 9m37s
www-data-web-2 Bound pvc-50d69a6e-d3ef-42e2-bd13-e6c954d78677 1Gi RWO managed-nfs-storage 8m57s# 查看 PV
[root@k8s-master-1-71 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAG ECLASS REASON AGE
pvc-50d69a6e-d3ef-42e2-bd13-e6c954d78677 1Gi RWO Delete Bound default/www-data-web-2 manage d-nfs-storage 9m1s
pvc-ae9c2a66-0bcb-489e-98d0-0350d480fb68 1Gi RWO Delete Bound default/www-data-web-1 manage d-nfs-storage 9m41s
pvc-c8f06f51-5048-4a43-b822-9f6bcd1bd20c 1Gi RWO Delete Bound default/www-data-web-0 manage d-nfs-storage 10m
測試網絡:
- ① 一個普通的headless-service對應Deplyment的域名解析,以及headless-service對應statefulset的域名解析
- ② 測試 statefulset的域名 網絡連通性
# 創建普通的headless-service對應Deplyment的域名解析
[root@k8s-master-1-71 ~]# kubectl create deployment web --image=nginx[root@k8s-master-1-71 ~]# kubectl expose deployment web --port=80 --target-port=80 --dry-run=client -o yaml > test-svc.yaml
[root@k8s-master-1-71 ~]# kubectl apply -f test-svc.yaml( //修改 clusterIP: None
[root@k8s-master-1-71 ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-headless ClusterIP None <none> 80/TCP 30m
web ClusterIP None <none> 80/TCP 6m45s
[root@k8s-master-1-71 ~]# kubectl get ep
NAME ENDPOINTS AGE
nginx-headless 10.244.114.10:80,10.244.117.50:80,10.244.117.51:80 31m
web 10.244.117.52:80 7m34s# 創建測試bs鏡像pod
[root@k8s-master-1-71 ~]# kubectl run bs --image=busybox:1.28.4 -- sleep 24h
[root@k8s-master-1-71 ~]# kubectl exec -it bs -- sh
/ # nslookup web
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web
Address 1: 10.244.117.52 10-244-117-52.web.default.svc.cluster.local
## DNS解析名稱:<PodIP>.<service-name> .<namespace-name>.svc.cluster.local/ # nslookup nginx-headless
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: nginx-headless
Address 1: 10.244.114.10 web-1.nginx-headless.default.svc.cluster.local
Address 2: 10.244.117.50 web-0.nginx-headless.default.svc.cluster.local
Address 3: 10.244.117.51 web-2.nginx-headless.default.svc.cluster.local
## DNS解析名稱:<statefulsetName-index>.<service-name> .<namespace-name>.svc.cluster.local[root@k8s-master-1-71 ~]# kubectl exec -it bs -- sh
/ # ping web-1.nginx-headless.default.svc.cluster.local
PING web-1.nginx-headless.default.svc.cluster.local (10.244.114.10): 56 data bytes
64 bytes from 10.244.114.10: seq=0 ttl=62 time=0.539 ms
64 bytes from 10.244.114.10: seq=1 ttl=62 time=0.474 ms
測試存儲:
[root@k8s-node1-1-72 ~]# ls /ifs/kubernetes/ //自動生成獨立的PV,
default-www-data-web-1-pvc-ae9c2a66-0bcb-489e-98d0-0350d480fb68
default-www-data-web-2-pvc-50d69a6e-d3ef-42e2-bd13-e6c954d78677
default-www-data-web-0-pvc-c8f06f51-5048-4a43-b822-9f6bcd1bd20c[root@k8s-master-1-71 ~]# kubectl exec -it web-0 -- bash
root@web-0:/# cd /usr/share/nginx/html/ ; echo 111 > index.html[root@k8s-node1-1-72 ~]# cat /ifs/kubernetes/default-www-data-web-0-pvc-c8f06f51-5048-4a43-b822-9f6bcd1bd20c/index.html
111
[root@k8s-node1-1-72 ~]# ls /ifs/kubernetes/default-www-data-web-2-pvc-50d69a6e-d3ef-42e2-bd13-e6c954d78677///目錄為空
## 因此Statefulset控制器創建的每個Pod的存儲為獨立的
思考:在有狀態環境部署下,分布式應用組件的角色不同,每個Pod也不相同,而在上述 StatefulSet部署示例中,假設運行一個etcd數據集群,有3個副本且肯定只有一個指定的鏡像(鏡像相同),如何區分這3個Pod的角色?
解答:通過配置文件區分(每個節點的名稱、IP、存儲目錄區分),需要保證啟動的3個副本容器,每一個都能按照自己的角色(配置文件)進行啟動。而配置文件則需要根據statefulset控制器部署的Pod容器編號進行區分,判斷當前啟動的是第幾個容器,就使用哪個配置文件進行啟動,從而實現Pod的角色。例如:etcd-0.conf、etcd-1.conf、etcd-2.conf
參考同類型的案例:運行 ZooKeeper,一個分布式協調系統 | Kubernetes
— StatefulSet 與 Deployment區別:有身份的!
身份三要素(唯一性):
-
域名
-
主機名
-
存儲(PVC)
3、應用程序數據存儲
- ConfigMap:存儲配置文件
- Secret:存儲敏感數據
3.1 ConfigMap 存儲應用配置
創建ConfigMap后,數據實際會持久化存儲在K8s中Etcd,然后通過創建Pod時引用該數據。
應用場景:應用程序配置(類似配置管理中心如apollo、nacos)
Pod使用configmap數據有兩種方式:
-
變量注入到容器里
-
數據卷掛載
兩種數據類型:
- 鍵值
- 多行數據
相關命令:
創建ConfigMap:kubectl create configmap --from-file=path/to/bar
kubectl create configmap my-configmap --from-literal=abc=123 --from-literal=cde=456
查看ConfigMap:kubectl get configmap
官方:ConfigMap | Kubernetes
ConfigMap 配置示例:
1)部署ConfigMap的YAML
[root@k8s-master-1-71 ~]# kubectl apply -f configmap-demo.yaml
apiVersion: v1
kind: ConfigMap
metadata:name: configmap-demo
data:abc: "123" //key/value鍵值方式(字符串需要“”)cde: "456"redis.properties: | //多行數據方式port: 6379host: 192.168.31.10# 查看創建的ConfigMap
[root@k8s-master-1-71 ~]# kubectl get configmap
NAME DATA AGE
configmap-demo 3 7s
## 備注:DATA顯示為3,表示存儲了3個數據,包括abc、cde、redis.properties
2)部署Pod的YAML
[root@k8s-master-1-71 ~]# kubectl apply -f configmap-pod.yaml
apiVersion: v1
kind: Pod
metadata:name: configmap-demo-pod
spec:containers:- name: nginximage: nginx# 定義環境變量env:- name: ABCD # 容器中的變量,請注意這里和 ConfigMap 中的鍵名是不一樣的valueFrom:configMapKeyRef:name: configmap-demo # 這個值來自 ConfigMapkey: abc # 需要取值的鍵- name: CDEFvalueFrom:configMapKeyRef:name: configmap-demokey: cdevolumeMounts:- name: configmountPath: "/config" # 掛載到哪個目錄readOnly: true # 掛載文件為只讀volumes:# 在 Pod 級別設置卷,然后將其掛載到 Pod 內的容器中- name: configconfigMap:name: configmap-demo # 提供想要掛載的 ConfigMap 的名字# 來自 ConfigMap 的一組鍵,將被創建為文件items:- key: "redis.properties" # configmap-demo的Keypath: "redis.conf" # 掛載后的名字
測試:進入pod中驗證是否注入變量和掛載
[root@k8s-master-1-71 ~]# kubectl exec -it configmap-test-pod-5dff5f64c6-ncmnd -- bash
root@configmap-test-pod-5dff5f64c6-ncmnd:/# echo $ABCD
123
root@configmap-test-pod-5dff5f64c6-ncmnd:/# echo $CDEF
456
root@configmap-test-pod-5dff5f64c6-ncmnd:/# cat /config/redis.conf
port: 6379
host: 192.168.31.10
3.2 Secret 存儲敏感信息
與ConfigMap類似,區別在于Secret主要存儲敏感數據,所有的數據要經過base64編碼。
應用場景:憑據
kubectl create secret 支持三種數據類型:
- docker-registry:存儲鏡像倉庫認證信息(鏡像倉庫需要賬戶密碼認證)
- generic:存儲用戶名、密碼:
- 例如:kubectl create secret generic ssh-key-secret --from-file=ssh-privatekey=/path/to/.ssh/id_rsa --from-file=ssh-publickey=/path/to/.ssh/id_rsa.pub
- 例如:kubectl create secret generic my-secret --from-literal=username=produser --from-literal=password=123456
- tls:存儲證書:kubectl create secret tls --cert=path/to/tls.cert --key=path/to/tls.key
- 例如 :05 K8s網絡 使用ingress - tls 部署https
相關命令:
- 查看Secret:kubectl get secret
相關base64命令:
- # echo -n 'admin' | base64? ? ? ? ? //加密(YWRtaW4=)
- # echo YWRtaW4= | base64 -d? //解密(admin)
補充: 在Secret 配置文件中未作顯式設定時,默認的 Secret 類型是 Opaque
命令示例:
[root@k8s-master ~]# kubectl create secret generic my-secret --from-literal=username=admin --from-literal=password=123456
YAML示例:
1)將用戶名密碼進行編碼
[root@k8s-master-1-71 ~]# echo -n 'admin' | base64 //用戶名加密
YWRtaW4=
[root@k8s-master-1-71 ~]# echo -n '123456' | base64 //密碼加密
MTIzNDU2
2)部署Secret的YAML
[root@k8s-master-1-71 ~]# vi secret-demo.yaml
apiVersion: v1
kind: Secret
metadata:name: my-secret
type: Opaque //默認的 Secret 類型是 Opaque
data:username: YWRtaW4=password: MTIzNDU2# 查看創建的ConfigMap
[root@k8s-master-1-71 ~]# kubectl get secret
NAME TYPE DATA AGE
secret-demo Opaque 2 5s
TYPE類型參考:Secrets | Kubernetes
3)部署Pod的YAML
apiVersion: v1
kind: Pod
metadata:name: secret-demo-pod
spec:containers:- name: nginximage: nginx env:- name: USERNAME # 容器中的變量valueFrom:secretKeyRef:name: secret-demokey: username- name: PASSWORD # 容器中的變量valueFrom:secretKeyRef:name: secret-demokey: passwordvolumeMounts:- name: configmountPath: "/config"readOnly: truevolumes:- name: configsecret:secretName: secret-demoitems:- key: username path: username.txt- key: passwordpath: password.txt
備注:Pod使用Secret數據與ConfigMap方式一樣
測試:進入pod中驗證是否注入變量和掛載
[root@k8s-master-1-71 ~]# kubectl exec -it secret-test-pod-6554b98c8-8hrbb -- bash
root@secret-test-pod-6554b98c8-8hrbb:/# echo $USERNAME
admin
root@secret-test-pod-6554b98c8-8hrbb:/# echo $PASSWORD
123456
root@secret-test-pod-6554b98c8-8hrbb:/# ls /config/
password.txt username.txt
課后作業
1、創建一個secret,并創建2個pod,pod1掛載該secret,路徑為/secret,pod2使用環境變量引用該 secret,該變量的環境變量名為ABC
- secret名稱:my-secret
- pod1名稱:pod-volume-secret
- pod2名稱:pod-env-secret
2、 創建一個pv,再創建一個pod使用該pv
- 容量:5Gi
- 訪問模式:ReadWriteOnce
3、創建一個pod并掛載數據卷,不可以用持久卷
- 卷來源:emptyDir、hostPath任意
- 掛載路徑:/data
4、將pv按照名稱、容量排序,并保存到/opt/pv文件
小結
本篇為 【Kubernetes CKA認證 Day7】的學習筆記,希望這篇筆記可以讓您初步了解到?數據卷與數據持久卷、有狀態應用部署、應用程序數據存儲案例 ;課后還有擴展實踐,不妨跟著我的筆記步伐親自實踐一下吧!
Tip:畢竟兩個人的智慧大于一個人的智慧,如果你不理解本章節的內容或需要相關筆記、視頻,可私信小安,請不要害羞和回避,可以向他人請教,花點時間直到你真正的理解。