小阿轩yx-Kubernetes存储入门
前言
- 数据是一个企业的发展核心,它涉及到数据存储和数据交换的内容。
- 在生产环境中尤为重要的一部分在 Kubernetes 中另一个重要的概念就是数据持久化 Volume。
Volume 的概念
对多数项目而言
- 数据文件的存储是非常常见的
在 Kubernetes 中
- 由于应用的部署具有高度的可扩展性和编排能力(不像传统架构部署在固定的位置),数据存放在容器中是非常不可取的,也无法保障数据的安全。
应该把有状态的应用变成无状态应用,把产生的数据文件或者宦岑信息放在云端
传统的架构中
- 使用这些存储,需要提前在宿主机挂载,然后程序才能访问,在实际使用时经常碰到新加节点忘记挂载存储导致的一系列问题。
Kubernetes
- 设计之初就考虑了这些问题,并抽象出 Volume 的概念用于解决数据存储的问题。
- 卷有明确的声明周期,与使用它的 Pod 相同,因此可以比 Pod 中运行的任何容器的生命周期都长
- 并且可以在容器重启或者销毁后保留数据
- 支持多种类型的卷,并且 Pod 可以同时使用任意数量的卷
容器中
- 磁盘文件是短暂的,当容器崩溃时,Kubectl 会重新启动容器,但是容器运行时产生的数据文件都会丢失,之后容器会以干净的状态启动。
一个 Pod 运行多个容器
- 各个容器可能需要共享一些文件,诸如此类的需求都可以使用 Volume 解决。
Docker 也有卷的概念
- 在 Docker 中,卷只是磁盘上或另一个容器中的目录,其生命周期不受管理。
- 虽然 Docker 已经提供了卷驱动程序,但是功能非常有限
- 从 Docker1.7版本开始,每个容器只允许一个卷驱动程序,并且无法将一些特殊的参数传递给后端存储。
本质上
- 和虚拟机或物理机一样
- 卷被挂载后,在容器中只是一个目录,可能包含一些数据,Pod 中的容器也可以对其进行增删改查操作,使用方式和裸机挂载几乎没有区别
Volume 的类型
传统架构中
- 企业有自己的存储平台(如:NFS、Ceph、GlusterFS、Minio)
- 如果所在环境在公有云,也可以使用公有云提供的 NAS、对象存储等
Kubernetes 中
- Volume 也支持配置存储
- 用于挂载到 Pod 中实现数据持久化
Kubernetes Volume 支持的卷类型有很多
常见的卷
- CephFS
- GlusterFS
- ISCSI
- Cinder
- NFS
- RBD
- HostPath
也支持一些 Kubernetes 独有的类型
- ConfigMap:用于存储配置文件
- Secret:用于存储敏感数据
- EmptyDir:用于一个 Pod 内多个容器的数据共享
- PersistentVolumeClaim:对 PersistentVolumeClaim 的申请
将 images 镜像通过 Xftp 上传至master、node01、node02(101、102、103)
将资源清单单独上传至 master 主节点(101)
三台主机导入镜像(master、node01、node02)
主机一
[root@k8s-master ~]# cd images/
[root@k8s-master images]# bash imp_docker_img
主机二
[root@k8s-node01 ~]# cd images/
[root@k8s-node01 images ~]# bash imp_docker_img
主机三
[root@k8s-node02 ~]# cd images/
[root@k8s-node02 images ~]# bash imp_docker_img
通过 emptyDir共享数据
emptyir
- 一个特殊的 Volume 类型
与 Volume 不同之处
- 如果删除 Pod,EmptyDir 卷中的数据也将被删除,所以一般 emptyDir 用于 Pod 中不同容器共享数据
- 使用 emptyDir 卷时,直接指定 emptyDir 为{}即可
编写 emptyDir 的 Deployment 文件
[root@k8s-master ~]# cat nginx-empty.yaml
apiVersion: apps/v1
kind: Deployment
metadata:labels :app: nginxname: nginxnamespace: default
spec:replicas: 1selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- image: nginx:1.7.9imagePullPolicy: IfNotPresentname: nginx01volumeMounts:- mountPath: /optname:share-volume- image: nginx:1.7.9imagePullPolicy: IfNotPresentname: nginx02command:- sh- -c- sleep 3600volumeMounts:- mountPath: /mntname: share-volumevolumes :- name: share-volumeemptyDir: {}#medium: Memory
volumeMounts:
- mountPath: /mnt
name: share-volume ##容器定义部分的卷挂载名称,此处的名称引用了 volumes 对应的名称
volumes :
- name: share-volume ##共享存储卷的名称
此案例会将 nginx01 中/opt 中的数据,共享给 nginx02 中的/mnt 目录
此部署文件创建一个 Deployment,采用 spec.volume 字段配置了一个名字为 share-volume、类型为 emptyDir 的 volume,同时里面包含两个容器 nginx01 和 nginx02,并将该 volume 挂载了 /opt 和 /mnt 目录下,此时 /opt 和 /mnt 目录的数据就实现了共享。
默认情况下
- emptyDir 支持节点上的任何介质,可以使 SSD、磁盘或是网络存储,具体取决于自身环境。
- 可以将 emptyDir.medium 字段设置为 Memory,让 Kubernetes 使用 tmpfs(内存支持的文件系统),虽然 tmpfs 非常快,但是在节点重启时,数据同样会被清除,并且设置的大小会被记入 Container 的内存限制中。
创建该 Deployment
[root@k8s-master ~]# kubectl create -f nginx-empty.yaml
获取创建结果
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-6fffbd7c7b-jrw7f 2/2 Running 0 10s
登录 Pod 中第一个容器
[root@k8s-master ~]# kubectl exec -it nginx-6fffbd7c7b-jrw7f -c nginx01 -- sh
- 该容器中的/opt 下创建一个文件,并到第二个容器中查看,是否是共享的目录
登录 Pod 中的第二个容器查看 /mnt 下的文件
[root@k8s-master ~]# kubectl exec -it nginx-6fffbd7c7b-jrw7f -c nginx02 -- sh
删除此 Pod
[root@k8s-master ~]# kubectl delete -f nginx-empty.yaml
使用 HostPath 挂载宿主机文件
- HostPath 卷可以将节点上的文件或目录挂载到 Pod 上,用于实现 Pod 和宿主机之间的数据共享
编写 Deployment 文件实现 HostPath 挂载
- 实现将主机的/etc/localtime 文件挂载到 Pod 的 /etc/localtime
[root@k8s-master ~]# cat nginx-hostPath.yaml
apiVersion: apps/v1
kind: Deployment
metadata:labels :app: nginxname: nginxnamespace: default
spec:replicas: 1selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- image: nginx:1.7.9imagePullPolicy: IfNotPresentname: nginxvolumeMounts:- mountPath: /etc/localtimename: timezone-timevolumes:- name: timezone-timehostPath:path: /etc/localtimetype: File
关于时区的设置
通过配置 /etc/localtime 文件设置系统的时区
/etc/localtime用于配置系统时区(此文件是一个链接文件,链接自 /usr/share/zoneinfo/Asia/Shanghai)
创建此 Pod
[root@k8s-master ~]# kubectl create -f nginx-hostPath.yaml
查看创建结果
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-5c9b77966c-jrw7f 1/1 Running 0 0
测试挂载情况
[root@k8s-master ~]# kubectl exec -it nginx-5c9b77966c-jrw7f -c nginx -- sh
# date
配置 HostPath 时,有一个 type 参数,用于表达不同的挂载类型,HostPath 卷常用的类型有
- type 为空字符串:默认选项,在挂载 HostPath 卷之前不会有任何检查
- Directoryorcreate:如果给定的 path 不存在任何东西,那么将根据需要创建一个权限为 0755 的空目录,和 kubelet 具有相同的组和权限
- Directory:目录必须存在于给定的路径下
- FileOrCreate:如果给定的路径不存在任何内容,则会根据需要创建一个空文件,权限设置为 8644,和 kubelet 具有相同的组和所有权
- File:文件,必须存在于给定的路径中
- Socket:UNIX套接字,必须存在于给定的路径中
- CharDevice:字符设备,必须存在于给定的路径中
- BlockDevice:块设备,必须存在于给定的路径中
删除
[root@k8s-master ~]# kubectl delete -f nginx-hostPath.yaml
挂载 NFS 至容器
安装 NFS
这一步开启会话同步
三台节点都安装 nfs-utils
主节点(master)
[root@k8s-master ~]# yum -y install nfs-utils
从节点(node01)
[root@k8s-node01 ~]# yum -y install nfs-utils
从节点(node02)
[root@k8s-node02 ~]# yum -y install nfs-utils
设置共享目录(在 NFS 服务器上)
[root@k8s-master ~]# mkdir /opt/wwwroot
回显一个页面文件
[root@k8s-master ~]# echo "This is my test file">/opt/wwwroot/index.html
编写 exports 配置文件
[root@k8s-master ~]# vim /etc/exports
/opt/wwwroot 192.168.10.0/24(rw,sync,no_root_squash)
开启 nfs(在 NFS 服务器上)
[root@k8s-master ~]# systemctl start nfs
[root@k8s-master ~]# systemctl start rpcbind
编写 Deployment 文件挂载 NFS
[root@k8s-master ~]# cat nginx-nfsVolume.yaml
apiVersion: apps/v1
kind: Deployment
metadata:labels :app: nginxname: nginXnamespace: default
spec:replicas: 1selector:matchLabels:app:nginxtemplate:metadata:labels :app: nginxspec:containers:- image: nginx:1.7.9imagePullPolicy: IfNotPresentname: nginxvolumeMounts:- mountPath: /usr/share/nginx/htmlname: nfs-volumevolumes:- name: nfs-volumenfs:server: 192.168.10.101path: /opt/wwwroot
创建此 Pod
[root@k8s-master ~]# kubectl create -f nginx-nfsVolume.yaml
查看部署结果
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nginx-fbd476c4c-zscq9 1/1 Running 0 8m12s
登录容器查看挂载结果
[root@k8s-master ~]# kubectl exec -it nginx-fbd476c4c-zscq9 -c nginx -- bash
PersistenVolume(PV,持久卷)
虽然 volume 已经可以接入大部分存储后端,但是使用时还有诸多的问题。
- 当某个数据卷不再被挂载使用时,里面的数据如何处理?
- 如果想要实现只读挂载,要如何处理?
- 如果想要只能有一个 Pod 挂载,要如何处理?
如上所述问题,volume 可能难以实现很多复杂的需求,并且无法对存储的生命周期进行管理。
另一个很大的问题是
企业内使用 kubernetes 的不仅仅是 kubernetes 管理员,可能还有开发人员、测试人员以及初学 kubernetes 的技术人员,对于 kubernetes 的 volume 或者相关存储平台的配置参数并不了解,所以无法自行完成存储的配置。
kubernetes 引入了两个新的 API 资源
- PersistentVolume(持久卷,简称 PV)
- PersistentVolumeclaim(持久卷声明,简称PVC)
PV
- kubernetes 管理员设置的存储,PVC 是对 PV 的请求,标识需要什么类型的 PV。
- 它们同样是集群中的一类资源,但其生命周期比较独立,管理员可以单独对 PV 进行增删改査,不受 Pod 的影响,生命周期可能比挂载它的其他资源还要长。
- 如果一个kubernetes 集群的使用者并非只有 kubernetes 管理员,那么可以通过提前创建 PV,用以解决对存储概念不是很了解的技术人员对存储的需求。和单独配置 volume 类似,PV 也可以使用 NFS、GFS、CEPH 等常见的存储后端,并且可以提供更为高级的配置,
目前 PV 的提供方式有两种
- 静态
- 动态
静态 PV
- 由管理员提前创建。
动态 PV
- 无需提前创建。
PV 回收策略
- 用户使用完卷时,可以从 API 中删除 PVC对象,从而允许回收资源。
- 回收策略会告诉 PV 如何处理改卷。
目前回收策略可以设置为
- Retain
- Recycle
- Delete
默认的为 delete
Retain
- 保留
- 该策略允许手动回收资源,当删除 PVC 时,PV 仍然存在,PV 中的数据也存在。
- volume 被视为已释放,管理员可以手动回收卷。
Recycle
- 回收
- 如果 volume 插件支持,Recycle 策略会对卷执行 rm -rf 清理该 PV,卷中的数据已经没了,但卷还在,使其可用于下一个新的 PVC,但是本策略将会被弃用
- 目前只有 NFS 和 HostPath 支持该策略。
Delete
- 删除
- 如果 volume 插件支持,删除 PVC 时会同时删除 PV,PV 中的数据自然也就没了。
- 动态卷默认为 Delete
目前支持 Delete 的存储后端包括
- AWS EBS
- GCE PD
- Azure Disk
- openstackCinder
PV 访问策略
- 实际使用 PV 时,可能针对不同的应用会有不同的访问策略
目前支持的访问策略
- Readwrite0nce:单路可读可写,可以被单节点以读写模式挂载,命令行中可以被缩写橙 RWO。
- ReadonlyMany:多路只读,可以被多节点以只读模式挂载,命令行中可以被缩写为 ROX。
- ReadwriteMany:多路可读可写,可以被多个节点以读写模式挂载,命令行中可以被缩写为 RWX。
- ReadwriteoncePod:单节点只读(1.22+),只能被一个 Pod 以读写的模式挂载,命令行中可以被
- 缩写为 RWOP。
- Readwriteonce:表示具有读写权限,但是只能被一个 node 挂载一次
- ReadonlyMany:表示具有只读权限,可以被多个 node 多次挂载
- ReadWriteMany:表示具有读写权限,可以被多个 node 多次挂载
- ReadwriteoncePod:表示具有读写权限,只能被一个Pod 挂载
虽然 PV 在创建时可以指定不同的访问策略,但是也要后端的存储支持才行。
一般情况下,大部分块存储是不支持 ReadwriteMany 的。
在企业内,可能存储很多不同类型的存储,针对不同类型的后端存储具有不同的配置方式,这也是对集群管理员的一种挑战,因为集群管理员需要对每种存储都要有所了解。
PV 的配置方式
静态配置
- 手动创建 PV 并定义其属性
- 这种情况下,Kubernetes 管理员负责管理和配置 PV,然后应用程序可以使用这些PV。
- 静态配置通常用于一些固定的存储后端
动态配置
- 允许 Kubernetes 集群根据 PVC的需求自动创建 PV
- 这种情况下,管理员只需为存储后端配置 storageclass,然后应用程序就可以通过 PVC 请求存储。
- Kubernetes 将自动创建与 PVC 匹配的PV,并将其绑定到 PVC上。
- 这种方法使得存储管理更加灵活和可扩展,允许管理员在集群中动态添加、删除、和管理存储资源
基于 HostPath 的 PV
- 可以创建一个基于 HostPath 的 PV,和配置 NFS 的 PV 类似,只需要配置 hostPath 字段即可,其它配置基本一致。
在所有 node 节点上创建主机目录
[root@k8s-master ~]# mkdir /mnt/data
编辑 hostpath 的 yaml 文件
[root@k8s-master ~]# cat hostpath-pv.yaml
kind: PersistentVolume
apiVersion: v1
metadata:name: mypv-hostpathlabels :type: local
spec:storageclassName: pv-hostpathcapacity:storage:10GiaccessModes:- ReadWriteoncehostPath:path: "/mnt/data"
- hostPath:宿主机的路径
- 使用 hostPath 类型需要固定 Pod 所在的节点,防止 Pod 漂移造成数据丢失。
创建 hostPath-pv 文件
[root@k8s-master ~]# kubectl create -f hostpath-pv.yaml
获取文件信息
[root@k8s-master ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
mypv-hostpath 10Gi RWO Retain Available pv-hostpath 5s
基于 NFS 的 PV
提前安装好 nfs 服务
- 可以使用上一个案例中安装好的 NFS
- 或重新安装一台
步骤
- 提前安装好 nfs 服务,192.168.10.101 是 nfs 服务器,也可以是任意一台提供了 NFS 服务的主机。
- 客户端需要在所有的 Kubernetes 节点安装
[root@k8s-master ~]# yum -y install nfs-utils
创建存储目录
[root@k8s-master ~]# mkdir /opt/wwwroot
回显一个页面文件
[root@k8s-master ~]# echo "This is my test file">/opt/wwwroot/index.html
编辑一个 exports 配置文件
[root@k8s-master ~]# vim /etc/exports
/opt/wwwroot 192.168.10..0/24(rw,sync,no_root_squash)
启动服务
[root@k8s-master ~]# systemctl start nfs
[root@k8s-master ~]# systemctl start rpcbind
创建一个基于 NFS 的 PV
- PV 目前没有 NameSpace 隔离,不需要指定命名空间,在任意命名空间下创建的 PV 均可以在其它 NameSpace 使用
编辑 nfs-pv 配置文件
[root@k8s-master ~]# cat nfs-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:name: mypv-nfs
spec:capacity:storage: 5GivolumeMode: FilesystemaccessModes:- ReadWriteOncepersistentVolumeReclaimPolicy:RecyclestorageClassName: pv-nfsmountOptions:- hard- nfsvers=4.1
nfs:path: /opt/wwwroot/server: 192.168.10.101
- capacity:容量配置
- volumeMode:卷的模式,目前支持 Filesystem (文件系统)和 Block(块),其中 Block 类型需要后端存储支持,默认为文件系统。
- accessModes:该 PV 的访问模式
- storageclassName:PV 的类,一个特定类型的 PV 只能绑定到特定类别的 PVC。
- persistentVolumeReclaimPolicy:回收策略
- mountoption:非必要,新版本中已经弃用
nfs:NFS 服务配置,包括以下两个选项
- path:NFs 上的共享目录
- server:NFs的IP地址
创建 PV
root@k8s-master ~]# kubectl create -f nfs-pv.yaml
查看 PV 创建结果
[root@k8s-master ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
mypv-hostpath 10Gi RWO Retain Available pv-hostpath 76s
mypv-hostpath 5Gi RWO Retain Available pv-hostpath 4s
PersistenVolumeClaim(PVC,持久卷声明)
当 kubernetes 管理员提前创建好了 PV,我们又应该如何使用它呢?
kubernetes 另一个概念
- PersistentVolumeclaim(简称PVC)。
PVC
- 是其他技术人员在 kubernetes 上对存储的申请,他可以标明一个程序需要用到什么样的后端存储、多大的空间以及什么访问模式进行挂载。
这一点和 Pod 的 Qos 配置类似
- Pod 消耗节点资源
- PVC 消耗 PV 资源
- Pod 可以请求特定级别的资源(CPU和内存)
- PVC 可以请求特定的大小和访问模式的 PV
实际使用时,虽然用户通过 PVC 获取存储支持,但是用户可能需要具有不同性质的 PV 来解决不同的问题
集群管理员需要根据不同的存储后端来提供各种 PV,而不仅仅是大小和访问模式的区别,并且无须让用户了解这些卷的具体实现方式和存储类型,打扫了存储的解耦,降低了存储使用的复杂度。
PVC和 PV 进行绑定的前提条件是一些参数必须匹配
- 比如 accessModes、storageclassName、volumeMode 都需要相同,并且 PVC 的 storage 需要小于等于 PV 的 storage 配置。
PVC 的创建
为 hostpath 类型的 PV 创建 PVC
[root@k8s-master ~]# vim pvc-hostpath.yaml
kind: PersistentVolumeclaim
apiVersion: v1
metadata:name: mypvc-hostpath
spec:storageClassName: pv-hostpathaccessModes :- ReadWriteOnceresources:requests:storage: 3Gi
- storageclassName:存储类名称需要和对应的 PV 中的名称一致,PV 和 PVC 进行绑定并非是名字相同,而是 StorageclassName 相同且其他参数一致才可以进行绑定
创建 pvc-hostpath 配置文件
[root@k8s-master ~]# kubectl create -f pvc-hostpath.yaml
获取文件信息
[root@k8s-master ~]# kubectl get pv
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypv-hostpath Bound mypv-hostpath 10Gi RWO pv-hostpath 4s
为 NFS 类型的 PV 创建 PVC
[root@k8s-master ~]# cat pvc-nfs.yaml
kind: PersistenVolumeClaim
apiVersion: v1
metadata:name: mypvc-nfs
spec:storageClassName:pv-nfsaccessModes:- ReadWriteonceresources:requests:storage: 3Gi
- storageclassName:存储类名称需要和对应的 PV 中的名称一致
创建 pvc-nfs 文件
[root@k8s-master ~]# kubectl create -f pvc-nfs.yaml
获取状态
[root@k8s-master ~]# kubectl get pv
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypv-hostpath Bound mypv-hostpath 10Gi RWO pv-hostpath 3m12s
mypv-nfs Bound mypv-nfs 5Gi RWO pv-nfs 5s
- PVC的定义和后端存储并没有关系。
- 对于有存储需求的技术人员,直接定义 PVC 即可绑定一块 PV,之后就可以供 Pod使用,而不用去关心具体的实现细节,大大降低了存储的复杂度。
PVC 的使用
- 和之前的挂载方式类似
- PVC 的挂载也是通过 volumes 字段进行配置的,只不过之前需要根据不同的存储后端填写很多复杂的参数’而使用 PVC 进行挂载时,只填写 PVC 的名字即可,不需要再关心任何的存储细节,这样即使不是 Kubemetes 管理员,不懂存储的其他技术人员想要使用存储,也可以非常简单地进行配置和使用。
创建 pod 绑定 hostpath 的 PV
[root@k8s-master ~]# cat pvc-pv-pod-hostpath.yaml
kind: Pod
apiVersion: v1
metadata:name: hostpath-pv-pod
spec:volumes :- name:task-pv-storagepersistentVolumeclaim:claimName:mypvc-hostpathcontainers:- name: task-pv-containerimage: nginx:1.7.9ports:- containerPort: 80name: "http-servervolumeMounts:- mountPath: "/usr/share/nginx/htmlname: task-pv-storage
- claimName: mypvc-hostpath 是基于 hastpath 创建的 PVC 的名字
创建 pvc-pv-pod-hostpath 文件
[root@k8s-master ~]# kubectl create -f pvc-pv-pod-hostpath.yaml
获取状态
[root@k8s-master ~]# kubectl get pv
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE REDINESS GAMES
hostpath-pv-pod 1/1 Running 0 113s 10.244.85.204 k8s-node01 <none> <none>
进入挂载目录(node02节点)
[root@k8s-node02 ~]# cd /mnt/data
创建一个文本
[root@k8s-node02 data]# touch aaa.txt
登录容器(master节点)
[root@k8s-master ~]# kubectl exec -it hostpath-pv-pod -- bash
root@hostpath-pv-pod:/# ls /usr/share/nginx/html
aaa.txt
创建 pod 绑定 NFS 的 PV
root@k8s-master ~]# cat pvc-pv-pod-nfs.yaml
kind: Pod
apiVersion: v1
metadata:name: pvc-nfs
spec:volumes:- name: pvc-nfs01persistentVolumeclaim:claimName: mypvc-nfscontainers:- name: task-pv-containerimage: nginx:1.7.9ports:- containerPort: 80name: "http-servervolumeMounts:- mountPath: "/usr/share/nginx/htmlname: pvc-nfs01
- claimName: mypvc-nfs 是基于 NFS 创建的 PVC 的名字
创建 pvc-pv-pod-nfs 文件
root@k8s-master ~]# kubectl create -f pvc-pv-pod-nfs.yaml
获取状态
root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
pvc-nfs 1/1 Running 0 4s
登录容器并查看页面文件
root@k8s-master ~]# kubectl exec -it pvc-nfs -- bash
root@pvc-nfs:/# ls /usr/share/nginx/html
index.html
小阿轩yx-Kubernetes存储入门