存储卷
什么是存储卷
在 k8s 中,存储卷(Volume)是一种抽象的概念,用于提供 pod 中容器的持久化存储。存储卷允许将数据存储在 pod 的生命周期之外,以便在容器重启、迁移或重新调度时保留数据。
存储卷可以连接到 pod 中的一个或多个容器,并提供持久化的存储空间。这些存储卷可以是来自 k8s 集群的本地存储、网络存储、云存储提供商或外部存储系统等。
存储卷可以用于各种用途,例如:
- 数据持久化:将数据存储在存储卷中,以便在容器重启后仍然可用。
- 数据共享:将存储卷连接到多个容器,使它们可以共享相同的数据。
- 数据备份和恢复:使用存储卷来备份和还原应用程序的数据。
- 数据迁移和复制:将存储卷从一个 pod 迁移到另一个 pod,或者将存储卷复制到其他地方。
由于 pod 本身是具有生命周期的,所以 pod 内部运行的容器及相关的数据,在 pod 中是无法持久化存储的。我们知道,docker 支持配置容器使用存储卷,已实现数据在容器之外的存储空间中持久化存储。相应的,k8s 也支持类似的存储卷功能,此处存储卷与 pod 资源绑定。
简单来说,存储卷是定义在 pod 资源之上、可被其他内容所有容器挂载的共享目录,它关联至外部的存储设备之上的存储空间,从而独立于容器自身的文件系统,而数据是否具有持久化存储能力取决于存储卷自身是否支持持久存储机制,与 pod 无关。
k8s 支持的存储类型
k8s 支持非常丰富的存储卷类型,总体上来看,大致可以分为如下三种类型:
本地存储,例如 emptyDir、HostPath;
网络存储,nfs、cinder、cephfs、AzureFile;
特殊存储资源,例如 secret、ConfigMap
对于本地存储,emptyDir 的生命周期与 pod 资源相同,所以 pod 一旦删除,存储的数据同时被删除。HostPath(node1 node2)的生命周期与节点一致,当 pod 重新被调度到其他节点时,虽然原节点上的数据没有被删除,但是 pod 不再使用此前的数据。
网络存储系统是独立于 k8s 集群之外的存储资源,数据存储的持久性与 k8s 集群解耦合。k8s 在集群中设计了一种集群级别的资源 PersistentVolume:服务器资源(管理主动分配一些存储空间)(简称 PV)来关联网络存储,用户通过 PersistentVolumeClaim:用户就要申请使用资源(服务器就会自动匹配)(简称 PVC)来申请使用存储资源。
secret 和 ConfigMap 算是 k8s 集群中两种特殊类型的存储资源。secret 用于向 pod 传递敏感信息,例如密码、私钥、证书等。这些信息直接定义在镜像中容易导致泄露,用户可以讲这些信息集中存储在 secret 中,由 pod 进行挂载,从而实现敏感数据与系统解耦。
ConfigMap 资源用于向 pod 注入非敏感数据,用户将一些非敏感的配置数据存储在 ConfigMap 对象中,然后在 pod 中使用 ConfigMap 卷引用它即可,从而帮助实现容器配置文件集中化定义和管理。
emptyDir
emptyDir:初始内容为空的本地临时目录
它是一个临时卷(Ephemeral Volume)
与 pod 一起创建和删除,生命周期与 pod 相同
emptyDir 会创建一个初始状态为空的目录
通常使用本地临时存储来设置缓存、保存日志等
例如,将 redis 的存储目录设置为 emptyDir,编辑文件redis-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: redis-pod
spec:
containers:
- name: redis01
image: redis
volumeMounts:
- name: redis-storage
mountPath: /data/redis
volumes:
- name: redis-storage
emptyDir: {}
[root@k8s-master k8s]# vim redis-pod.yaml
[root@k8s-master k8s]# kubectl apply -f redis-pod.yaml
pod/redis-pod created
[root@k8s-master k8s]# kubectl get pod
NAME READY STATUS RESTARTS AGE
redis-pod 1/1 Running 0 2m27s
容器启动之后我们进入容器内部,添加数据
[root@k8s-master k8s]# kubectl exec -it redis-pod /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@redis-pod:/data# redis-cli
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> set name yigongsui
OK
这样我们添加了一条数据,接下来我们退出并删除这个容器
127.0.0.1:6379> exit
root@redis-pod:/data# exit
exit
[root@k8s-master k8s]# kubectl delete -f redis-pod.yaml
pod "redis-pod" deleted
删除容器后,我们再重新启动这个容器,看看数据还在不在
[root@k8s-master k8s]# kubectl apply -f redis-pod.yaml
pod/redis-pod created
[root@k8s-master k8s]# kubectl get pod
NAME READY STATUS RESTARTS AGE
redis-pod 1/1 Running 0 16s
[root@k8s-master k8s]# kubectl exec -it redis-pod /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@redis-pod:/data# redis-cli
127.0.0.1:6379> ping
PONG
127.0.0.1:6379> get name
(nil)
发现数据不存在了,这就说明 emptyDir 的生命周期很短,与 pod 相同,只做临时存储使用
网络存储 nfs
nfs:全称是 Network File System,它最大的功能就是可以通过网络,让不同的机器、不同的操作系统可以共享彼此的文件。
三台服务器都要安装 nfs
yum install -y nfs-utils
接下来,在 master 主节点配置:
# 主节点配置
echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports
# 创建/nfs/data文件夹(主节点)
mkdir -p /nfs/data
# 启动rpc远程绑定(主节点)
systemctl enable rpcbind --now
# (开机)启动nfs服务(主节点)
systemctl enable nfs-server --now
# 配置生效(主节点)
exportfs -r
# 查看目录
exportfs
[root@k8s-master k8s]# echo "/nfs/data/ *(insecure,rw,sync,no_root_squash)" > /etc/exports
[root@k8s-master k8s]# mkdir -p /nfs/data
[root@k8s-master k8s]# systemctl enable rpcbind --now
[root@k8s-master k8s]# systemctl enable nfs-server --now
Created symlink from /etc/systemd/system/multi-user.target.wants/nfs-server.service to /usr/lib/systemd/system/nfs-server.service.
[root@k8s-master k8s]# exportfs -r
[root@k8s-master k8s]# exportfs
/nfs/data <world>
接下来在 node 节点下执行,
# 查看主节点机器有哪些目录可以同步挂载(node节点)
# showmount -e 主节点的内网ip,我的内网ip是192.168.0.1
showmount -e 192.168.0.1
[root@k8s-node1 home]# showmount -e 192.168.0.1
Export list for 192.168.0.1:
/nfs/data *
接下来在 node 节点执行以下命令挂载 nfs 服务器上的共享目录到本机路径
# mkdir -p 本机目录 (名字可以任取)
# mount -t nfs 192.168.0.1:/nfs/data 本机目录
# node1 执行
mkdir -p /nfs/node1
mount -t nfs 192.168.0.1:/nfs/data /nfs/node1
# node2 执行
mkdir -p /nfs/node2
mount -t nfs 192.168.0.1:/nfs/data /nfs/node2
测试看看三个节点是否共通
# master节点新增文件
[root@k8s-master k8s]# cd /nfs/data/
[root@k8s-master data]# echo "hello master" > master.txt
# node1节点新增文件
[root@k8s-node1 home]# cd /nfs/node1/
[root@k8s-node1 node1]# echo "hello node1" > node1.txt
# 查看三个节点内数据是否共通
[root@k8s-master data]# ls
master.txt node1.txt
[root@k8s-node1 node1]# ls
master.txt node1.txt
[root@k8s-node2 home]# ls /nfs/node2/
master.txt node1.txt
可以看到三个节点已经共通了
文件都共享,在 nfs 文件系统中,volumes 肯定也可以连通
测试:编辑文件nginx-nfs.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-nfs
name: nginx-nfs
spec:
replicas: 2
selector:
matchLabels:
app: nginx-nfs
template:
metadata:
labels:
app: nginx-nfs
spec:
containers:
- image: nginx01
name: nginx
volumeMounts:
- name: nginx-storage
mountPath: /usr/share/nginx/html
volumes:
- name: nginx-storage
nfs:
server: 192.168.0.1
path: /nfs/data/nginx-nfs
创建一个 deployment 部署,自行测试即可,在主节点的/nfs/data/nginx-nfs
目录,新建 index.html,编辑内容,在容器里进行访问测试。
主节点的/nfs/data/nginx-nfs
目录也和从节点的目录共通(node1 的目录就是/nfs/node1/nginx-nfs
),也都可以进行测试
结论:无论容器怎么部署怎么删除,数据都可以持久化保存了
PV & PVC
持久卷(Persistent Volume):删除 pod 后,卷不会被删除
PV:持久卷(Persistent Volume),将应用需要持久化的数据保存到指定位置
PVC:持久卷申明(Persistent Volume Claim),申明需要使用的持久卷规格
k8s 支持的存储系统非常多,要求大家全都掌握,是不现实的。为了屏蔽底层存储实现细节,方便使用,引入了 PV 和 PVC 两种资源对象。
封装机制!数据中台
PV
Persistent Volume 是持久卷的意思,是对底层共享存储的一种抽象封装。一般情况 PV 由管理员创建和配置,它与底层具体的存储技术有关,通过插件完成与存储的对接。
PV 是存储资源的抽象,资源清单如下:
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv2
spec:
nfs: # 存储类型,与底层真正存储对应
capacity: # 存储能力,目前只支持存储空间的设置
storage: 2Gi
accessModes: # 访问模式
storageClassName: # 存储类别
persistentVolumeReclaimPolicy: # 回收策略
accessModes 用于描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:
- ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载
- ReadOnlyMany(ROX):只读权限,可以被多个节点挂载
- ReadWriteMany(RWX):读写权限,可以被多个节点挂载
- 需要注意的是,底层不同的存储类型可能支持的访问模式不同
回收策略(persistentVolumeReclaimPolicy)
persistentVolumeReclaimPolicy 当 PV 不再被使用了之后,对其的处理方式。目前支持三种策略:
- Retain (保留) 保留数据,需要管理员手工清理数据
- Recycle(回收) 清除 PV 中的数据,效果相当于执行
rm -rf /thevolume/*
- Delete (删除) 与 PV 相连的后端存储完成 volume 的删除操作,当然这常见于云服务商的存储服务
一个 PV 的生命周期中,可能会处于4种不同的阶段:
- Available(可用):表示可用状态,还未被任何 PVC 绑定
- Bound(已绑定):表示 PV 已经被 PVC 绑定
- Released(已释放):表示 PVC 被删除,但是资源还未被集群重新声明
- Failed(失败):表示该 PV 的自动回收失败
PVC
Persistent Volume Claim 持久卷声明的意思,是用户对存储需求的一种声明。也就是向 k8s 系统发出的一种资源需求申请。
PVC 作为资源的申请,用来声明对存储空间、访问模式、存储类别需求信息。下面是资源清单文件:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc
namespace: dev
spec:
accessModes: # 访问模式
selector: # 采用标签对PV选择
storageClassName: # 存储类别
resources: # 请求空间
requests:
storage: 5Gi
示例
创建一个 pv,创建一个 pvc,创建一个 pod 绑定 pvc 就可以了!
pv 做就是连接存储系统,规定一个大小的空间,权限配置
pvc 做的就是写一个声明,根据自己的使用者的要求(存储系统、大小、权限),来匹配 pv
pod 使用 pvc volumes 挂载的类型!
- 准备工作:在 nfs 主节点(master 节点)创建 PV 池
mkdir -p /nfs/data/01
mkdir -p /nfs/data/02
mkdir -p /nfs/data/03
- 创建 pv,编辑文件
my-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv01-10m
spec:
capacity:
storage: 10M
accessModes:
- ReadWriteMany
storageClassName: nfs
nfs:
path: /nfs/data/01
server: 192.168.0.1
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv02-1gi
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
storageClassName: nfs
nfs:
path: /nfs/data/02
server: 192.168.0.1
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv03-3gi
spec:
capacity:
storage: 3Gi
accessModes:
- ReadWriteMany
storageClassName: nfs
nfs:
path: /nfs/data/03
server: 192.168.0.1
- 执行 yaml 文件
[root@k8s-master k8s]# kubectl apply -f my-pv.yaml
persistentvolume/pv01-10m created
persistentvolume/pv02-1gi created
persistentvolume/pv03-3gi created
- 查看所有 pv
kubectl get pv
[root@k8s-master k8s]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv01-10m 10M RWX Retain Available nfs 50s
pv02-1gi 1Gi RWX Retain Available nfs 50s
pv03-3gi 3Gi RWX Retain Available nfs 50s
- 创建 pvc,编辑
my-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nginx-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 500Mi
storageClassName: nfs
- 执行 yaml 文件
[root@k8s-master k8s]# vim my-pvc.yaml
[root@k8s-master k8s]# kubectl apply -f my-pvc.yaml
persistentvolumeclaim/nginx-pvc created
- 再次查看所有 pv
[root@k8s-master k8s]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv01-10m 10M RWX Retain Available nfs 7m54s
pv02-1gi 1Gi RWX Retain Bound default/nginx-pvc nfs 7m54s
pv03-3gi 3Gi RWX Retain Available nfs 7m54s
可以看到 pv02-1gi 的状态变成了 Bound,也就是说我们创建的这个 pvc 绑定到了 pv02-1gi
这是因为在 pvc 会根据我们 yaml 文件设置的存储类型以及需要的存储容量来选择绑定到最合适的 pv,我们在 pvc 中设置了存储类型为 nfs,所需的容量为 500M,所以绑定到了 pv02-1gi
- 删除这个 pvc,查看 pv 状态
[root@k8s-master k8s]# kubectl delete -f my-pvc.yaml
persistentvolumeclaim "nginx-pvc" deleted
[root@k8s-master k8s]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv01-10m 10M RWX Retain Available nfs 12m
pv02-1gi 1Gi RWX Retain Released default/nginx-pvc nfs 12m
pv03-3gi 3Gi RWX Retain Available nfs 12m
可以看到,状态变为了 Released,也就是释放状态
- 根据
my-pvc.yaml
文件再创建一个 pvc,查看 pv 状态
[root@k8s-master k8s]# kubectl apply -f my-pvc.yaml
persistentvolumeclaim/nginx-pvc created
[root@k8s-master k8s]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv01-10m 10M RWX Retain Available nfs 14m
pv02-1gi 1Gi RWX Retain Released default/nginx-pvc nfs 14m
pv03-3gi 3Gi RWX Retain Bound default/nginx-pvc nfs 14m
# 查看 pvc 绑定到了哪个 pv上
[root@k8s-master k8s]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
nginx-pvc Bound pv03-3gi 3Gi RWX nfs 101s
可以看到 pvc 绑定到了 pv03-3gi 上,说明正处于 Released 状态的 pv 无法绑定任何 pvc
已经了解 pv 和 pvc 的绑定关系,接下来我们创建一个 pod 去绑定 pvc
- 清除所有的 deployment 和 pod
[root@k8s-master k8s]# kubectl get deploy
No resources found in default namespace.
[root@k8s-master k8s]# kubectl get pod
NAME READY STATUS RESTARTS AGE
redis-pod 1/1 Running 0 16h
[root@k8s-master k8s]# kubectl delete pod redis-pod
pod "redis-pod" deleted
- 编辑文件
my-pvc-pod.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-pod-pvc
name: nginx-pod-pvc
spec:
replicas: 2
selector:
matchLabels:
app: nginx-pod-pvc
template:
metadata:
labels:
app: nginx-pod-pvc
spec:
containers:
- image: nginx
name: nginx01
volumeMounts:
- name: nginx-volume
mountPath: /usr/share/nginx/html
volumes:
- name: nginx-volume
persistentVolumeClaim:
claimName: nginx-pvc
- 执行 yaml 文件,查看 deployment 和 pod
[root@k8s-master k8s]# kubectl apply -f my-pvc-pod.yaml
deployment.apps/nginx-pod-pvc created
[root@k8s-master k8s]# kubectl get deploy
NAME READY UP-TO-DATE AVAILABLE AGE
nginx-pod-pvc 2/2 2 2 19s
[root@k8s-master k8s]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-pod-pvc-967fcb547-rj5ll 1/1 Running 0 30s
nginx-pod-pvc-967fcb547-wbbh7 1/1 Running 0 30s
- 查看 pod 的详细信息,里面有关于 pvc 信息
[root@k8s-master k8s]# kubectl describe pod nginx-pod-pvc-967fcb547-rj5ll
......
Volumes:
nginx-volume:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: nginx-pvc
ReadOnly: false
kube-api-access-5rlkl:
Type: Projected (a volume that contains injected data from multiple sources)
TokenExpirationSeconds: 3607
ConfigMapName: kube-root-ca.crt
ConfigMapOptional: <nil>
DownwardAPI: true
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute op=Exists for 300s
node.kubernetes.io/unreachable:NoExecute op=Exists for 300s
......
- 因为 pvc 绑定在 pv03-3gi 这个 pv 上,这个 pv 的挂载目录是
/nfs/data/03
,所以我们去这个目录下编辑文件
[root@k8s-master k8s]# cd /nfs/data/03
[root@k8s-master 03]# echo "hello yigongsui" > index.html
- 进入 pod 容器内部,访问首页,查看结果
[root@k8s-master 03]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-pod-pvc-967fcb547-rj5ll 1/1 Running 0 11m
nginx-pod-pvc-967fcb547-wbbh7 1/1 Running 0 11m
[root@k8s-master 03]# kubectl exec -it nginx-pod-pvc-967fcb547-rj5ll /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
root@nginx-pod-pvc-967fcb547-rj5ll:/# curl localhost
hello yigongsui
可以看到里面的首页也变化了
这样我们的两个容器内目录就永久存储到我们的本地目录了,这样无论 pod 怎么重新部署删除,数据都不会丢失了
生命周期
PVC 和 PV 是一一对应的,PV 和 PVC 之间的相互作用遵循以下生命周期:
- 资源供应:管理员手动创建底层存储和 PV。
- 资源绑定:用户创建 PVC,k8s 负责根据 PVC 的声明去寻找 PV,并绑定。
在用户定义好 PVC 之后,系统将根据 PVC 对存储资源的请求在已存在的 PV 中选择一个满足条件的。
- 一旦找到,就将该 PV 与用户定义的 PVC 进行绑定,用户的应用就可以使用这个 PVC 了,就可以在 pod 里面去使用
- 如果找不到,PVC 则会无限期处于 Pending 状态,直到等到系统管理员创建了一个符合其要求的 PV,PV 一旦绑定到某个 PVC 上,就会被这个 PVC 独占,不能再与其他 PVC 进行绑定了,一一绑定。
- 资源使用:用户可在 pod 中像 volume 一样使用 pvc。
pod 使用 volume 的定义,将 PVC 挂载到容器内的某个路径进行使用。
- 资源释放:用户删除 pvc 来释放 pv。
当存储资源使用完毕后,用户可以删除 PVC,与该 PVC 绑定的 PV 将会被标记为“已释放”,但还不能立刻与其他 PVC 进行绑定。通过之前 PVC 写入的数据可能还被留在存储设备上,只有在清除之后该 PV 才能再次使用。
这里我们查看 pv02-1gi 的绑定关系:
kubectl get pv pv02-1gi -o yaml
在这里唯一绑定了 pvc,需要清除才可以绑定其它 pvc
我们可以使用以下命令去动态修改 k8s 配置
kubectl edit pv pv02-1gi
与 linux 的 vim 编辑器一致,点击i
进入输入模式,删除这两行即可
查看 pv 状态
[root@k8s-master 03]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv01-10m 10M RWX Retain Available nfs 135m
pv02-1gi 1Gi RWX Retain Available default/nginx-pvc nfs 135m
pv03-3gi 3Gi RWX Retain Bound default/nginx-pvc nfs 135m
可以看到 pv02-1gi 的状态已经变为 Available,就可以重新绑定 pvc 了
资源回收:k8s 根据 pv 设置的回收策略进行资源的回收。
对于 PV,管理员可以设定回收策略,用于设置与之绑定的 PVC 释放资源之后如何处理遗留数据的问题。只有 PV 的存储空间完成回收,才能供新的PVC绑定和使用。
问题:pv、pvc都是我们手动创建和绑定的,十分麻烦
有没有这样一种技术可以自动帮我们绑定,我们在 pod 创建的时候,自动帮我们创建 pv 和 pvc
答:有,StorageClass
StorageClass(存储类)
官方地址:https://kubernetes.io/zh-cn/docs/concepts/storage/storage-classes/#local
在 java 中,我们知道 class 是类的概念,相当于一个模板,通过类来创建对象
那么 StorageClass 其实就相当于是 pv 的模板,通过这个模版可以自动帮我们创建 pv 并通过 pvc 自动挂载上
接下来我们去理解 StorageClass
什么是 StorageClass
在 k8s 中,StorageClass 是用于定义和管理动态供应的持久化存储的对象。它是 PersistentVolume(PV)的动态供应机制的一部分。
StorageClass 定义了一组存储配置,包括存储提供者、存储类型、I/O 特性、复制策略等。它允许管理员为不同的存储需求创建多个 StorageClass,并为每个 StorageClass 指定不同的参数。
当创建 PersistentVolumeClaim(PVC)时,可以指定所需的 StorageClass。k8s 会根据 StorageClass 的定义和可用的存储资源,动态创建与 PVC 匹配的 PersistentVolume,并将其绑定到 PVC 上。
StorageClass 的优点是可以根据应用程序的需求自动创建和配置持久化存储,无需手动预先创建 PersistentVolume。这样可以简化存储管理的工作,提高存储资源的利用率。
另外,StorageClass 还支持动态卷的回收和回收策略的定义。当 PVC 被删除时,如果定义了回收策略,k8s 会自动回收对应的 PersistentVolume,释放存储资源。
总之,StorageClass 是 k8s 中用于动态供应和管理持久化存储的重要机制,可以根据需求自动创建、配置和回收存储资源,提高存储的灵活性和利用率。
静态供应与动态供应
我们这里的 PV 是我们提前开辟好的空间申明,是静态供应。
还有一种动态供应,根据 PVC 申请的空间,来实现 PV 的创建,从而进行绑定。
在动态资源供应模式下,通过 StorageClass 和 PVC 完成资源动态绑定(系统自动生成 PV),并供 pod 使用的存储管理机制。
在一个大规模的 k8s 集群里,可能有成千上万个 PVC,这就意味着运维人员必须实现创建出这个多个 PV,此外,随着项目的需要,会有新的 PVC 不断被提交,那么运维人员就需要不断的添加新的,满足要求的 PV,否则新的 pod 就会因为 PVC 绑定不到 PV 而导致创建失败。而且通过 PVC 请求到一定的存储空间也很有可能不足以满足应用对于存储设备的各种需求。
而且不同的应用程序对于存储性能的要求可能也不尽相同,比如读写速度、并发性能等,为了解决这一问题,k8s 又为我们引入了一个新的资源对象:StorageClass,通过 StorageClass 的定义,管理员可以将存储资源定义为某种类型的资源,比如快速存储、慢速存储等,用户根据 StorageClass 的描述就可以非常直观的知道各种存储资源的具体特性了,这样就可以根据应用的特性去申请合适的存储资源了。
而 StorageClass 对象的作用,其实就是创建 PV 的模板。
示例
获取指定 ns 下的 StorageClass 命令,默认是 default:
[root@k8s-master 03]# kubectl get sc
No resources found
接下来我们开始测试:
一个 k8s 中可以有多个 StorageClass,多个模版!
- k3s 自带一个 local-path 存储类,我们这里也创建一个
local-path-storage.yaml
去创建存储类
apiVersion: v1
kind: Namespace
metadata:
name: local-path-storage
---
apiVersion: v1
kind: ServiceAccount
metadata:
name: local-path-provisioner-service-account
namespace: local-path-storage
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: local-path-provisioner-role
rules:
- apiGroups: [ "" ]
resources: [ "nodes", "persistentvolumeclaims", "configmaps" ]
verbs: [ "get", "list", "watch" ]
- apiGroups: [ "" ]
resources: [ "endpoints", "persistentvolumes", "pods" ]
verbs: [ "*" ]
- apiGroups: [ "" ]
resources: [ "events" ]
verbs: [ "create", "patch" ]
- apiGroups: [ "storage.k8s.io" ]
resources: [ "storageclasses" ]
verbs: [ "get", "list", "watch" ]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
name: local-path-provisioner-bind
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: ClusterRole
name: local-path-provisioner-role
subjects:
- kind: ServiceAccount
name: local-path-provisioner-service-account
namespace: local-path-storage
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: local-path-provisioner
namespace: local-path-storage
spec:
replicas: 1
selector:
matchLabels:
app: local-path-provisioner
template:
metadata:
labels:
app: local-path-provisioner
spec:
serviceAccountName: local-path-provisioner-service-account
containers:
- name: local-path-provisioner
image: rancher/local-path-provisioner:master-head
imagePullPolicy: IfNotPresent
command:
- local-path-provisioner
- --debug
- start
- --config
- /etc/config/config.json
volumeMounts:
- name: config-volume
mountPath: /etc/config/
env:
- name: POD_NAMESPACE
valueFrom:
fieldRef:
fieldPath: metadata.namespace
volumes:
- name: config-volume
configMap:
name: local-path-config
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: local-path
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
---
kind: ConfigMap
apiVersion: v1
metadata:
name: local-path-config
namespace: local-path-storage
data:
config.json: |-
{
"nodePathMap":[
{
"node":"DEFAULT_PATH_FOR_NON_LISTED_NODES",
"paths":["/opt/local-path-provisioner"]
}
]
}
setup: |-
#!/bin/sh
set -eu
mkdir -m 0777 -p "$VOL_DIR"
teardown: |-
#!/bin/sh
set -eu
rm -rf "$VOL_DIR"
helperPod.yaml: |-
apiVersion: v1
kind: Pod
metadata:
name: helper-pod
spec:
containers:
- name: helper-pod
image: busybox
imagePullPolicy: IfNotPresent
- 执行 yaml 文件
[root@k8s-master k8s]# vi local-path-storage.yaml
[root@k8s-master k8s]# kubectl apply -f local-path-storage.yaml
namespace/local-path-storage created
serviceaccount/local-path-provisioner-service-account created
clusterrole.rbac.authorization.k8s.io/local-path-provisioner-role created
clusterrolebinding.rbac.authorization.k8s.io/local-path-provisioner-bind created
deployment.apps/local-path-provisioner created
storageclass.storage.k8s.io/local-path created
configmap/local-path-config created
[root@k8s-master k8s]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
local-path rancher.io/local-path Delete WaitForFirstConsumer false 17s
- 把这个存储类设置为默认的存储类型,执行以下命令:
kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
[root@k8s-master k8s]# kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
storageclass.storage.k8s.io/local-path patched
[root@k8s-master k8s]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
local-path (default) rancher.io/local-path Delete WaitForFirstConsumer false 119s
每个 StorageClass 都有一个制备器(Provisioner),用来决定使用哪个卷插件创建持久卷。 该字段必须指定。
在很多 k8s 系统中是自带的。
- 我们创建一个 pod 去测试是否会自动生成 pv 和 pvc,创建文件
mysql-pod.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: local-path-pvc
namespace: default
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Pod
metadata:
name: mysql-pod
spec:
containers:
- image: mysql:5.7
name: mysql01
env:
- name: MYSQL_ROOT_PASSWORD
value: "123456"
ports:
- containerPort: 3306
volumeMounts:
- mountPath: /var/lib/mysql
name: local-mysql-data
volumes:
- name: local-mysql-data
persistentVolumeClaim:
claimName: local-path-pvc
[root@k8s-master k8s]# vim mysql-pod.yaml
[root@k8s-master k8s]# kubectl apply -f mysql-pod.yaml
persistentvolumeclaim/local-path-pvc created
pod/mysql-pod created
- 查看 pv 和 pvc 是否自动创建了
可以看到 pv 和 pvc 都自动创建了
创建 pod 时去申请 pvc,StorageClass 会动态创建 pvc 和 pv,pvc 申请多大存储 pv 就会创建多大空间
local
卷也存在自身的问题,当 pod 所在节点上的存储出现故障或者整个节点不可用时,pod 和卷都会失效,仍然会丢失数据,因此最安全的做法还是将数据存储到集群之外的存储或云存储上。同时配置,做一些灾备,保证数据不会丢失!
总结
本地存储:
- 缺点:
- 容易丢失
- pod 一没有可能就不见了
- pod 换了节点启动,数据也可能没了
存储卷:
- nfs
- 系统就全部连接了,数据就不会再丢失
- 方便使用
- 问题
- 一个系统中可能有很多的存储类型 nfs cifs hdfs oss,用户连接不方便,希望存在一个统一平台
pv
- k8s 提供的同一存储类型对象
- 管理声明(文件类型、大小、使用方式,权限…回收策略)
pvc
- 用户拿着 pvc 就可以在 k8s 中自动找到 pv 绑定,从而实现数据持久化(k8s 的持久化存储策略)
storageClass
- pv 每次手动创建十分麻烦,希望可以动态创建pv
- storageClass 动态创建 pv 模版实现。
- pvc 直接在 pod 中声明就好了! 不需要在手动创建 pv 了! 自动根据用户 pod 申请的 pvc 来根据 storageClass 自动创建 pv
至此,k8s 数据持久化搞定!