赞
踩
一般情况下,K8S中的Pod都不应该将数据持久化到Pod中,因为Pod可能被随时创建和删除(扩容或缩容),即便是StatefulSet或Operator的Pod,也都不建议在Pod里存放数据,可以将数据持久化到Host上。K8S提供了非常丰富的存储相关的功能,使得我们可以方便的让Pod访问存储设备。K8S通过Volume挂载的方式让Pod来访问存储设备,Volume与Pod绑定并与Pod有相同的生命周期,Volume在Pod中定义,而Pod中的容器只需要使用volumeMounts就可以使用在Pod中定义的Volume。
Pod可以引用的Volume包含以下几类:
总之,在K8S Pod中能够使用几乎所有的存储类型和方式。特别的,通过K8S CSI(Container Storage Interface),我们还可以开发自己的存储访问插件,接入到特定的存储设备中。
Node本地存储包含emptyDir和hostPath两种类型。emptyDir用于存储临时数据,如缓存,删除Pod的时候会自动被清理,emptyDir可以指定成Memory类型,但会被统计成容器使用的内存。hostPath用于挂载Node的某个目录,对于大部分应用来说,都不应该直接使用hostPath,因为如果Pod被调度到了其它节点,其就无法访问到之前节点的hostPath中的数据。另外,hostPath上使用的数据不会被计算到存储资源使用统计,可能出现磁盘占满而没有提醒的情况。但如果Pod只会被调度到某个Node上,那么还是可以使用hostPath。
PV是Persistent Volume,即持久化卷,是K8S最常用的存储访问方式,几乎所有的外部存储都可以通过PV来访问。
PVC是Persistent Volume Claim,即持久化卷声明,通过PVC来申请对PV的使用。PV和PVC是一一对应关系,PV只有通过PVC关联后,才能被使用。Pod通过volumeMounts来关联PVC。
PV通常由管理员来创建,管理员事先分配好一定数量的PV供Pod使用,不同的存储类别(NFS,Cloud,Ceph等)最后都对应成一系列的PV。
PVC通常由Pod来创建,在需要使用存储的时候通过PVC来申请PV。
以下是PV/PVC的关系图,
下面通过NFS来介绍PV/PVC的使用,
先搭建一个双节点的K8S集群(略),参考 K8S 概述。
接着在master节点上搭建NFS服务,主要命令如下,
- # On Server:
- sudo apt update
- sudo apt install nfs-kernel-server
- mkdir -p /home/sunny/nfs/root
- echo "/home/sunny/nfs/root *(rw,sync,no_subtree_check)" | sudo tee -a /etc/exports
- sudo exportfs -ra
- sudo systemctl start nfs-kernel-server
- sudo systemctl enable nfs-kernel-server
-
- # On Client:
- sudo apt install nfs-common
- sudo mount 192.168.126.16:/home/sunny/nfs/root /mnt
创建PV,pv.yaml,这里会指定NFS IP和路径,
- apiVersion: v1
- kind: PersistentVolume
- metadata:
- name: nginx-pv
- labels:
- type: nginx-pv
- spec:
- capacity:
- storage: 1Gi
- accessModes:
- - ReadWriteMany
- nfs:
- path: /home/sunny/nfs/root
- server: 192.168.126.16
- sunny@xxx:~/k8s/storage/pvc_pv$ kubectl apply -f pv.yaml
- persistentvolume/nginx-pv created
- sunny@xxx:~/k8s/storage/pvc_pv$ kubectl get pv
- NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
- nginx-pv 1Gi RWX Retain Available 3s
创建PVC,pvc.yaml,
- apiVersion: v1
- kind: PersistentVolumeClaim
- metadata:
- name: nginx-pvc
- namespace: default
- spec:
- accessModes:
- - ReadWriteMany
- resources:
- requests:
- storage: 1Gi
- sunny@xxx:~/k8s/storage/pvc_pv$ kubectl apply -f pvc.yaml
- persistentvolumeclaim/nginx-pvc created
- sunny@xxx:~/k8s/storage/pvc_pv$ kubectl get pvc
- NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
- nginx-pvc Bound nginx-pv 1Gi RWX 2s
- sunny@xxx:~/k8s/storage/pvc_pv$ kubectl get pv
- NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
- nginx-pv 1Gi RWX Retain Bound default/nginx-pvc 2m59s
可以看出,此时PVC的状态是Bound,说明其已经找到了一个与此关联的PV,而PV的状态也由之前的Available变为Bound,且CLAIM是default/nginx-pvc,先就可以在Pod里使用这个PVC了。默认PV和PVC的回收策略都是Retain,需要手动删除数据,即便PV和PVC都被删除。
创建Nginx Pod,nginx.yaml,在配置文件中引用PVC,
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment spec: selector: matchLabels: app: nginx replicas: 3 template: metadata: labels: app: nginx spec: containers: - name: nginx image: nginx:1.14.2 imagePullPolicy: IfNotPresent volumeMounts: - name: html mountPath: /usr/share/nginx/html ports: - containerPort: 80 volumes: - name: html persistentVolumeClaim: claimName: nginx-pvc
- sunny@xxx:~/k8s/storage/pvc_pv$ kubectl apply -f nginx.yaml
- deployment.apps/nginx-deployment created
- sunny@xxx:~/k8s/storage/pvc_pv$ kubectl get pod -o wide
- NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
- nginx-deployment-574dc457cf-8ds25 1/1 Running 0 53s 10.244.96.163 r05u36-nex-wvie-spr-cd <none> <none>
- nginx-deployment-574dc457cf-fnn9z 1/1 Running 0 53s 10.244.2.220 r05u30-nex-wvie-spr-cd <none> <none>
- nginx-deployment-574dc457cf-h4kkk 1/1 Running 0 53s 10.244.96.162 r05u36-nex-wvie-spr-cd <none> <none>
在NFS服务器对应的路径上创建一个index.html文件,供Nginx使用,
- sunny@r05u30-nex-wvie-spr-cd:~/nfs/root$ pwd
- /home/sunny/nfs/root
- sunny@r05u30-nex-wvie-spr-cd:~/nfs/root$ cat index.html
- Hello Nginx with PV/PVC.
- sunny@r05u30-nex-wvie-spr-cd:~/nfs/root$
由于3个Nginx Pod都关联到了同一个NFS路径,所以通过任何一个Pod都能访问到相同的index.html。如果Pod扩容,新的Pod使用的也是相同的NFS路径,所以这里就非常容易的实现了数据和代码的分离,无论代码部署到哪里,无论代码如何改变,数据都是统一存储的。
- sunny@xxx:~/k8s/storage/pvc_pv$ wget 10.244.2.220
- --2024-03-21 01:45:21-- http://10.244.2.220/
- Connecting to 10.244.2.220:80... connected.
- HTTP request sent, awaiting response... 200 OK
- Length: 25 [text/html]
- Saving to: ‘index.html’
-
- index.html 100%[===========================================================================================================>] 25 --.-KB/s in 0s
-
- 2024-03-21 01:45:21 (1.68 MB/s) - ‘index.html’ saved [25/25]
-
- sunny@xxx:~/k8s/storage/pvc_pv$ cat index.html
- Hello Nginx with PV/PVC.
- sunny@xxx:~/k8s/storage/pvc_pv$ wget 10.244.96.163
- --2024-03-21 01:47:36-- http://10.244.96.163/
- Connecting to 10.244.96.163:80... connected.
- HTTP request sent, awaiting response... 200 OK
- Length: 25 [text/html]
- Saving to: ‘index.html.1’
-
- index.html.1 100%[===========================================================================================================>] 25 --.-KB/s in 0s
-
- 2024-03-21 01:47:36 (79.9 KB/s) - ‘index.html.1’ saved [25/25]
-
- sunny@xxx:~/k8s/storage/pvc_pv$ cat index.html
- Hello Nginx with PV/PVC.
PV/PVC确实能实现几乎所有存储的统一访问,但有一个问题是PV需要由管理员事先创建好,如果创建PVC的时候没有可用的PV,则PVC的状态会一直是Pending。不同的Pod可能需要不同规格和类型的PV,管理员需要创建和维护数量巨大的PV,这无疑是增加了K8S集群管理员的负担。
StorageClass可以解决上面的问题。当我们在创建PVC的时候可以指定一个StorageClass,PVC在创建过程中会根据StorageClass的描述自动创建需要的PV,不用管理员手动创建。
以下是StorageClass/PVC的关系图,
下面通过NFS来介绍StorageClass/PVC的使用,
搭建K8S和NFS参考上面内容。
StorageClass通过Provisioner来创建PV,Provisioner有很多,不同的存储类别有不同的实现,是第三方的组件。Provisioner要创建PV,意味着其能访问K8S集群,需要为其分配权限。
创建RBAC(Role Based Access Control),rbac.yaml,
- apiVersion: v1
- kind: Namespace
- metadata:
- name: nginxns
- ---
- apiVersion: v1
- kind: ServiceAccount
- metadata:
- name: nfs-client-provisioner
- namespace: nginxns
- ---
- 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: managed-run-nfs-client-provisioner
- subjects:
- - kind: ServiceAccount
- name: nfs-client-provisioner
- namespace: nginxns
- roleRef:
- kind: ClusterRole
- name: nfs-client-provisioner-runner
- apiGroup: rbac.authorization.k8s.io
- ---
- kind: Role
- apiVersion: rbac.authorization.k8s.io/v1
- metadata:
- name: leader-locking-nfs-client-provisioner
- namespace: nginxns
- 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
- namespace: nginxns
- subjects:
- - kind: ServiceAccount
- name: nfs-client-provisioner
- # replace with namespace where provisioner is deployed
- namespace: nginxns
- roleRef:
- kind: Role
- name: leader-locking-nfs-client-provisioner
- apiGroup: rbac.authorization.k8s.io
- sunny@xxxx:~/k8s/storage/storageclass$ kubectl apply -f rbac.yaml
- namespace/nginxns created
- serviceaccount/nfs-client-provisioner created
- clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
- clusterrolebinding.rbac.authorization.k8s.io/managed-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
创建Storage Class, storage_class.yaml,这里会定义storage class名称,在创建PVC的时候会使用,以及与之关联的provisioner,
- apiVersion: storage.k8s.io/v1
- kind: StorageClass
- metadata:
- name: managed-nfs-storage
- namespace: nginxns
- provisioner: provisioner-nfs-storage
- parameters:
- archiveOnDelete: "false"
- sunny@xxx:~/k8s/storage/storageclass$ kubectl apply -f storage_class.yaml
- storageclass.storage.k8s.io/managed-nfs-storage created
- sunny@r05u30-nex-wvie-spr-cd:~/k8s/storage/storageclass$
创建 NFS provisioner,nfs-provisioner.yaml,这里面会指定provisioner名称,NFS地址,路径,serviceAccountName等,
- apiVersion: apps/v1
- kind: Deployment
- metadata:
- name: nfs-client-provisioner
- labels:
- app: nfs-client-provisioner
- namespace: nginxns
- spec:
- replicas: 1
- selector:
- matchLabels:
- app: nfs-client-provisioner
- strategy:
- type: Recreate
- selector:
- matchLabels:
- app: nfs-client-provisioner
- template:
- metadata:
- labels:
- app: nfs-client-provisioner
- spec:
- serviceAccountName: nfs-client-provisioner
- containers:
- - name: nfs-client-provisioner
- #image: quay.io/external_storage/nfs-client-provisioner:latest
- image: gcr.io/k8s-staging-sig-storage/nfs-subdir-external-provisioner:v4.0.0
- volumeMounts:
- - name: nfs-client-root
- mountPath: /persistentvolumes
- env:
- - name: PROVISIONER_NAME
- value: provisioner-nfs-storage
- - name: NFS_SERVER
- value: 192.168.126.16
- - name: NFS_PATH
- value: /home/sunny/nfs/root
- volumes:
- - name: nfs-client-root
- nfs:
- server: 192.168.126.16
- path: /home/sunny/nfs/root
- sunny@xxx:~/k8s/storage/storageclass$ kubectl apply -f nfs-provisioner.yaml
- deployment.apps/nfs-client-provisioner created
创建PVC,pvc.yaml,在这里会指定storageClassName,
- kind: PersistentVolumeClaim
- apiVersion: v1
- metadata:
- name: test-claim
- namespace: nginxns
- spec:
- accessModes:
- - ReadWriteMany
- storageClassName: managed-nfs-storage
- resources:
- requests:
- storage: 5Mi
- sunny@xxx:~/k8s/storage/storageclass$ kubectl apply -f pvc.yaml
- persistentvolumeclaim/test-claim created
- sunny@xxx:~/k8s/storage/storageclass$ kubectl get pvc -n nginxns
- NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
- test-claim Bound pvc-03905a65-efe4-4a5d-a10c-f5b50f026c4c 5Mi RWX managed-nfs-storage 12s
- sunny@xxx:~/k8s/storage/storageclass$ kubectl get pv -n nginxns
- NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
- pvc-03905a65-efe4-4a5d-a10c-f5b50f026c4c 5Mi RWX Delete Bound nginxns/test-claim managed-nfs-storage 41s
创建完PVC以后,可以看到pv也自动创建好了,且NFS根目录下也创建了相关的供PV使用的目录,在该目录中增加index.html,
- sunny@xxx:~/nfs/root$ pwd
- /home/sunny/nfs/root
- sunny@xxx:~/nfs/root$ ll
- total 0
- drwxrwxrwx 3 sunny sunny 73 Mar 21 02:30 ./
- drwxrwxrwx 3 sunny sunny 18 Mar 20 02:06 ../
- drwxrwxrwx 2 nobody nogroup 6 Mar 21 02:30 nginxns-test-claim-pvc-03905a65-efe4-4a5d-a10c-f5b50f026c4c/
- sunny@xxx:~/nfs/root$ cd nginxns-test-claim-pvc-03905a65-efe4-4a5d-a10c-f5b50f026c4c/
- sunny@xxx:~/nfs/root/nginxns-test-claim-pvc-03905a65-efe4-4a5d-a10c-f5b50f026c4c$ cat index.html
- Hello Storage Class.
创建Nginx,nginx.yaml,
apiVersion: apps/v1 kind: Deployment metadata: name: nginx-deployment-storageclass namespace: nginxns spec: selector: matchLabels: app: nginx-storageclass replicas: 3 template: metadata: labels: app: nginx-storageclass spec: containers: - name: nginx image: nginx:1.14.2 imagePullPolicy: IfNotPresent volumeMounts: - name: html mountPath: /usr/share/nginx/html ports: - containerPort: 80 volumes: - name: html persistentVolumeClaim: claimName: test-claim
- sunny@xxx:~/k8s/storage/storageclass$ kubectl apply -f nginx.yaml
- deployment.apps/nginx-deployment-storageclass created
- sunny@xxx:~/k8s/storage/storageclass$ kubectl get pod -n nginxns -o wide
- NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
- nfs-client-provisioner-7c5d5f57b-shrd8 1/1 Running 0 37m 10.244.96.164 r05u36-nex-wvie-spr-cd <none> <none>
- nginx-deployment-storageclass-86bb9496f8-5mpvg 1/1 Running 0 18s 10.244.2.221 r05u30-nex-wvie-spr-cd <none> <none>
- nginx-deployment-storageclass-86bb9496f8-c8zbp 1/1 Running 0 18s 10.244.96.166 r05u36-nex-wvie-spr-cd <none> <none>
- nginx-deployment-storageclass-86bb9496f8-z4sxm 1/1 Running 0 18s 10.244.96.165 r05u36-nex-wvie-spr-cd <none> <none>
访问Pod,
- sunny@xxx:~/k8s/storage/storageclass$ wget 10.244.2.221
- --2024-03-21 03:06:12-- http://10.244.2.221/
- Connecting to 10.244.2.221:80... connected.
- HTTP request sent, awaiting response... 200 OK
- Length: 21 [text/html]
- Saving to: ‘index.html’
-
- index.html 100%[===========================================================================================================>] 21 --.-KB/s in 0s
-
- 2024-03-21 03:06:12 (53.6 KB/s) - ‘index.html’ saved [21/21]
-
- sunny@xxx:~/k8s/storage/storageclass$ cat index.html
- Hello Storage Class.
StorageClass确实能自动创建PV,减少管理员准备PV的工作,但是我们也发现StorageClass的配置多了不少,需要定义RBAC,StorageClass等资源对象,需要创建provisioner这个额外的Pod等。所以,如果在一些复杂场景下需要频繁创建和维护PV,我们可以使用StorageClass + PVC的模来使用存储,如果在一些简单的场景下只需要一些固定的PV,我们可以使用PV + PVC的模式来使用存储。
在 Kubernetes 中,存储插件的开发主要有以下几种方式:
CSI 全称 Container Storage Interface,是容器编排系统(CO)如k8s等扩展容器存储的一种实现方式,基于gRPC实现,是当前主流的存储扩展方式。为什么会使用CSI?首先,CSI 可以满足不同编排系统的需求,除了k8s,还可以比如 Mesos,Swarm。其次,CSI 是容器化部署,可以减少环境依赖,增强安全性,丰富插件的功能。CSI 的设计思想,把插件的职责从之前讲的 “两阶段处理”,扩展成了 Provision、Attach 和 Mount 三个阶段。
CSI 主要包含两个部分:CSI Controller Server 与 CSI Node Server。
CSI部署架构,
参考:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。