本文最后更新于 259 天前,其中的信息可能已经过时,如有错误请发送邮件到wuxianglongblog@163.com
Kubernetes数据持久化篇
一.为什么需要持久化
1.运行tomcat+mysql的案例
如下图所示,我们运行Pod成功后可以添加自定义的数据,数据被存储在MySQL数据库实例中。
我们可以执行"SELECT * FROM HPE_APP.T_USERS;"命令来查看数据哈。
温馨提示:
我们可以通过"kubectl exec -it mysql-698607359-dvmzk bash"进入到容器并通过"env"查看MySQL的root用户密码哟。
2.删除容器
如下图所示,我们可以在运行tomcat+mysql的Pod中,批量删除一波容器。
执行操作为"docker rm -f `docker ps -a -q`",删除容器后,这些容器会被K8S重新创建。
3.Pod恢复后对应的数据会丢失
如下图所示,尽管业务恢复了,但我们的数据也丢失了。
综上所述,为了解决容器被删除后数据不丢失,则引入了存储类型,类似于docker中的数据卷。
在kubernetes集群中,其是支持多种存储类型,包括但不限于emptyDir,HostPath,NFS等等。
二.数据卷概述
1.为什么需要数据卷
容器部署过程中一般有以下三种数据:
(1)启动时需要的初始数据,例如配置文件;
(2)启动过程中产生的临时数据,该临时数据需要多个容器间共享,例如类似于Redis的数据库;
(3)启动容器过程中产生的持久化数据,例如MySQL的数据目录(datadir);
如下图所示,我们可以基于数据卷的方式实现数据的共享。
2.数据卷的分类
数据卷:
(1)kubernetes中的Volume提供了在容器中挂载外部存储的能力;
(2)Pod需要设置卷来源(po.spec.volumes)和挂载点(po.spec.containers.volumeMounts)两个信息后才可以使用相应的volume;
数据卷类型大致分类:
本地数据卷:
hostPath,emptyDir等。
网络数据卷:
NFS,Ceph,GlusterFS等。
公有云:
AWS,EBS等;
K8S资源:
configmap,secret等。
除了上述提到的数据类型分类,但官方文档却要比上述描述的数据卷类型多得多,如下图所示。
推荐阅读:
https://kubernetes.io/zh/docs/concepts/storage/volumes/
三.emptyDir存储类型(多个Pod不能使用该类型进行数据共享,但同一个Pod的多个容器可以基于该类型进行数据共享哟)
1.emptyDir概述
emptyDir数据卷:
是一个临时存储卷,与Pod生命周期绑定在一起,如果Pod删除了,这意味着数据卷也会被删除。
emptyDir的作用:
(1)可以实现持久化的功能;
(2)多个Pod之间不能通信数据,但是同一个Pod的多个容器是可以实现数据共享的;
(3)随着Pod的生命周期而存在,当我们删除Pod时,其数据也会被随之删除。
emptyDir的应用场景(同一个Pod中各个容器之间数据的共享):
(1)缓存空间,例如基于磁盘的归并排序;
(2)为耗时较长的计算任务提供检查点,以便任务能方便地从崩溃前状态恢复执行;
(3)存储Web服务器的访问日志及错误日志等信息;
推荐阅读:
https://kubernetes.io/docs/concepts/storage/volumes/#emptydir
温馨提示:
使用emptyDir持久化数据时,删除容器并不会删除数据,因为容器删除并不能说明Pod被删除哟。
2.创建Pod并使用emptyDir案例
[root@k8s101.oldboyedu.com ~]# cat 01-volumes-emptyDir-demo.yaml
apiVersion: v1
kind: Namespace
metadata:
name: oldboyedu-volume
---
apiVersion: v1
kind: Pod
metadata:
name: oldboyedu-pod-emptydir
namespace: oldboyedu-volume
spec:
# 定义数据卷的来源
volumes:
- name: data
emptyDir: {} # 我们无需为emptyDir指定任何参数,通常写个"{}"即可,表示创建的是一个空目录。
- name: log
emptyDir: {}
containers:
# 创建一个负责写入数据的容器
- name: linux-producer
image: 172.200.1.101:5000/alpine:latest
# 启动容器后执行的命令,模拟生产数据即可
command: ["sh","-c","for i in `seq 1000`;do echo $i >> /mydata/topic;sleep 1;done"]
# 挂载数据卷
volumeMounts:
- name: data # 注意哈,该名称必须在上面的"volumes"的name字段中存在哟~
mountPath: /mydata # 指定挂载到容器的位置
# 创建一个负责读取数据的容器
- name: linux-consumer
image: 172.200.1.101:5000/alpine:latest
# 启动容器后执行的命令,模拟消费数据即可
command: ["sh","-c","tail -f /mydata2021/topic"]
# 挂载数据卷
volumeMounts:
- name: data # 注意哈,该名称必须在上面的"volumes"的name字段中存在哟~
mountPath: /mydata2021
- name: oldboyedu-linux
image: 172.200.1.101:5000/alpine:latest
command: ["sh","-c","tail -f /etc/hosts"]
volumeMounts:
- name: log
mountPath: /oldboyedu
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# kubectl apply -f 01-volumes-emptyDir-demo.yaml
namespace "oldboyedu-volume" configured
pod "pod-emptydir" configured
[root@k8s101.oldboyedu.com ~]#
温馨提示:
(1)如下图所示,我们可以在宿主机上查看到数据持久化到本地的文件位置哟;
(2)当然,我们也可以进入到各个容器内部,查看相应的配置文件信息哟;
3.删除Pod则持久化到本地的数据将被删除哟
[root@k8s101.oldboyedu.com ~]# kubectl get pods -n oldboyedu-volume
NAME READY STATUS RESTARTS AGE
pod-emptydir 2/2 Running 0 32s
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# kubectl delete -f 01-volumes-emptyDir-demo.yaml
namespace "oldboyedu-volume" deleted
pod "pod-emptydir" deleted
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# kubectl get pods -n oldboyedu-volume -o wide
NAME READY STATUS RESTARTS AGE IP NODE
pod-emptydir 2/2 Terminating 0 1m 172.18.71.3 k8s103.oldboyedu.com
[root@k8s101.oldboyedu.com ~]# kubectl get pods -n oldboyedu-volume -o wide
No resources found.
[root@k8s101.oldboyedu.com ~]#
温馨提示:
如下图所示,当我们删除Pod时,这意味本地的数据也会随之丢失哟。
4.扩展案例
[root@k8s110 volumes]# cat 04-nginx-alpine-emptyDir.yaml
apiVersion: v1
kind: Pod
metadata:
name: oldboyedu-linux76-nginx-alpine-emptydir-04
labels:
school: oldboyedu
spec:
nodeName: k8s113.oldboyedu.com
volumes: # 定义数据卷
- emptyDir: {}
name: "oldboyedu-data"
- name: "oldboyedu-data-2021"
emptyDir: {}
containers:
- name: oldboyedu-nginx
image: k8s110.oldboyedu.com:5000/nginx:1.20
volumeMounts:
- name: "oldboyedu-data"
mountPath: "/oldboyedu-linux76/python"
# readOnly: true
# 必须写相对路径,不能以根("/")开头,用来隔离多个容器挂载相同数据卷的情况
subPath: "oldboyedu-linux76/c1"
- image: k8s110.oldboyedu.com:5000/linux/alpine:latest
name: oldboyedu-alpine
args: ["sleep","3600"]
volumeMounts:
- name: "oldboyedu-data"
mountPath: "/oldboyedu-linux76/golang"
# readOnly: false
subPath: "oldboyedu-linux76/c2"
[root@k8s110 volumes]#
温馨提示:
在执行上述案例时,可用进入到被调度节点的Pod存储目录("/var/lib/kubelet/pods/")查看信息哟.
四.HostPath存储类型(可以解决同一个Node节点的多个Pod数据共享哟,但多个Pod不在同一个Node节点时则无法通过HostPath来查找数据哟)
1.hostPath数据卷概述
hotsPath数据卷:
挂载Node文件系统(Pod所在节点)上文件或者目录到Pod中的容器。
如果Pod删除了,宿主机的数据并不会被删除,这一点是否感觉和咱们的数据卷有异曲同工之妙呢?
应用场景:
Pod中容器需要访问宿主机文件。
推荐阅读:
https://kubernetes.io/docs/concepts/storage/volumes/#hostpath
2.测试案例
[root@k8s101.oldboyedu.com ~]# vim 02-volumes-hostPath-demo.yaml
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# cat 02-volumes-hostPath-demo.yaml
apiVersion: v1
kind: Namespace
metadata:
name: oldboyedu-volume
---
apiVersion: v1
kind: Pod
metadata:
name: pod-hostpath
namespace: oldboyedu-volume
spec:
nodeName: k8s102.oldboyedu.com
# 定义数据源
volumes:
- name: mydir
# 定义挂载宿主机的源文件或目录
hostPath:
path: /oldboyedu # 在K8S1.5版本中,如果该文件存在,则直接引用,若该文件不存在,则默认创建的是目录哟~
# 如果给定路径上不存在任何内容,则会根据需要在该目录中创建一个空目录,并将权限设置为0755,该目录与Kubelet具有相同的组和所有权。
# type: DirectoryOrCreate # 注意哈,该字段属性在K8S1.5版本中并不支持哟,因此创建的是一个目录哟!
- name: myfile
hostPath:
path: /tmp/k8s.log
# 如果给定路径上不存在任何内容,则将根据需要在其中创建一个空文件,并将权限设置为0644,该文件具有与Kubelet相同的组和所有权。
# type: FileOrCreate # 注意哈,该字段属性在K8S1.5版本中并不支持哟,因此创建的是一个目录哟!
- name: syslog
hostPath:
path: /var/log/messages
# 文件必须存在于给定的路径
# type: File # 注意哈,该字段属性在K8S1.5版本中并不支持哟.
containers:
- name: mylinux
image: k8s101.oldboyedu.com:5000/busybox:latest
args:
- /bin/sh
- -c
- sleep 36000
# 将数据源挂载到容器指定的位置
volumeMounts:
- name: mydir
mountPath: /mydata
- name: myfile
mountPath: /mydata/k8s.log
- name: syslog
mountPath: /mydata/system_messages.log
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# kubectl apply -f 02-volumes-hostPath-demo.yaml
namespace "oldboyedu-volume" configured
pod "pod-hostpath" configured
[root@k8s101.oldboyedu.com ~]#
温馨提示:
(1)建议进入到Pod中观察挂载目录的情况;
(2)建议删除Pod观察宿主机的数据是否真的会随之删除呢?答案当然是否定的。
3.实战案例
(1)创建namespace资源清单
[root@k8s101.oldboyedu.com ~/wordpress]# cat 00-wordpress-namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: oldboyedu-wordpress
[root@k8s101.oldboyedu.com ~/wordpress]#
(2)创建mysql的资源清单
[root@k8s101.oldboyedu.com ~/wordpress]# cat 01-wordpress-mysql.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: oldboyedu-wordpress-mysql
namespace: oldboyedu-wordpress
spec:
replicas: 1
template:
metadata:
labels:
app: oldboyedu-wordpress-mysql
spec:
containers:
- name: mysql
image: k8s101.oldboyedu.com:5000/mysql:5.7
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
value: somewordpress
- name: MYSQL_DATABASE
value: wordpress
- name: MYSQL_USER
value: wordpress
- name: MYSQL_PASSWORD
value: wordpress
[root@k8s101.oldboyedu.com ~/wordpress]#
(3)创建MySQL的service服务
[root@k8s101.oldboyedu.com ~/wordpress]# cat 02-wordpress-mysql-svc.yml
apiVersion: v1
kind: Service
metadata:
name: oldboyedu-mysql
namespace: oldboyedu-wordpress
spec:
ports:
- port: 3306
targetPort: 3306
selector:
app: oldboyedu-wordpress-mysql
[root@k8s101.oldboyedu.com ~/wordpress]#
(4)创建
[root@k8s101.oldboyedu.com ~/wordpress]# cat 03-wordpress-web.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: wordpress
namespace: oldboyedu-wordpress
spec:
replicas: 1
template:
metadata:
labels:
app: wordpress
spec:
volumes:
- name: wordpress-code
hostPath:
path: /data/wordpress-code
nodeName: k8s102.oldboyedu.com
containers:
- name: wordpress
image: k8s101.oldboyedu.com:5000/wordpress:latest
ports:
- containerPort: 80
volumeMounts:
- mountPath: /var/www/html
name: wordpress-code
env:
- name: WORDPRESS_DB_HOST
value: oldboyedu-mysql # 注意哈,这里我写的是暴露MySQL服务的service名称哟~
- name: WORDPRESS_DB_USER
value: wordpress
- name: WORDPRESS_DB_PASSWORD
value: wordpress
[root@k8s101.oldboyedu.com ~/wordpress]#
(5)创建wordpress的service资源
[root@k8s101.oldboyedu.com ~/wordpress]# cat 04-wordpress-svc.yml
apiVersion: v1
kind: Service
metadata:
name: wordpress
namespace: oldboyedu-wordpress
spec:
type: NodePort
ports:
- port: 80
targetPort: 80
selector:
app: wordpress
[root@k8s101.oldboyedu.com ~/wordpress]#
(6)应用配置文件
[root@k8s101.oldboyedu.com ~/wordpress]# kubectl apply -f .
namespace "oldboyedu-wordpress" configured
deployment "oldboyedu-wordpress-mysql" configured
service "oldboyedu-mysql" configured
deployment "wordpress" configured
service "wordpress" configured
[root@k8s101.oldboyedu.com ~/wordpress]#
温馨提示:
创建成功之后,就可以访问wordpress暴露的端口,从而访问相关的服务即可。
[root@k8s102.oldboyedu.com ~]# vim /data/wordpress-code/info.php
[root@k8s102.oldboyedu.com ~]#
[root@k8s102.oldboyedu.com ~]# cat /data/wordpress-code/info.php
<?php phpinfo(); ?>
[root@k8s102.oldboyedu.com ~]#
温馨提示:
如下图所示,我们可以访问php的测试网页哟,该网页我们可以手动编写即可。
4.课堂练习(将"wordpress"的数据进行持久化)并进行手动伸缩实战案例
[root@k8s110 wordpress]# cat 02-mysql-wordpress-demo.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: oldboyedu-linux76-mysql57-volume
spec:
template:
metadata:
labels:
app: oldboyedu-linux76-mysql
spec:
nodeName: k8s111.oldboyedu.com
volumes:
- name: "mysql-data"
hostPath:
path: "/oldboyedu-linux76/mysql57"
containers:
- name: mysql
image: k8s110.oldboyedu.com:5000/oldboyedu/mysql:5.7
volumeMounts:
- name: "mysql-data"
mountPath: "/var/lib/mysql"
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
value: somewordpress
- name: MYSQL_DATABASE
value: wordpress
- name: MYSQL_USER
value: wordpress
- name: MYSQL_PASSWORD
value: wordpress
---
apiVersion: v1
kind: Service
metadata:
name: oldboyedu-mysql57-svc-volume
spec:
ports:
- port: 3306
targetPort: 3306
selector:
app: oldboyedu-linux76-mysql
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: oldboyedu-linux76-wordpress-latest-volume
spec:
replicas: 1
template:
metadata:
labels:
app: oldboyedu-linux76-wordpress
spec:
nodeName: k8s111.oldboyedu.com
volumes:
- name: "wordpress-data"
# emptyDir: {}
hostPath:
path: "/oldboyedu-linux76/wordpress"
containers:
- name: wordpress
image: k8s110.oldboyedu.com:5000/oldboyedu/wordpress:latest
ports:
- containerPort: 80
volumeMounts:
- name: "wordpress-data"
mountPath: "/var/www/html"
env:
- name: WORDPRESS_DB_HOST
value: oldboyedu-mysql57-svc-volume
- name: WORDPRESS_DB_USER
value: wordpress
- name: WORDPRESS_DB_PASSWORD
value: wordpress
---
apiVersion: v1
kind: Service
metadata:
name: oldboyedu-wordpress
spec:
type: NodePort
ports:
- port: 80
nodePort: 32020
targetPort: 80
selector:
app: oldboyedu-linux76-wordpress
[root@k8s110 wordpress]#
温馨提示:
kubectl scale --replicas=3 deploy/oldboyedu-linux76-wordpress-latest-volume
五.NFS存储类型(适合不同的node节点的Pod共享存储,但存在单点故障)
1.NFS数据卷概述
NFS数据卷:
提供对NFS挂载支持,可以自动将NFS共享路径挂载到Pod中。
NFS:
英文全称为"Network File System"(网络文件系统),是由SUN公司研制的UNIX表示层协议(presentation layer protocol),能使使用者访问网络上别处的文件就像在使用自己的计算机一样。
NFS是一个主流的文件共享服务器,但存在单点故障,我们需要对数据进行备份哟,如果有必要可以使用分布式文件系统哈。
推荐阅读:
https://kubernetes.io/docs/concepts/storage/volumes/#nfs
2.安装NFS环境并测试
(1)在所有节点上都安装"nfs-utils"软件包,安装该软件包不仅仅会包含NFS服务端软件,还会包含其客户端软件
yum -y install nfs-utils
(2)将"k8s101.oldboyedu.com"设置为NFS服务端,配置共享"/data/kubernetes"目录
[root@k8s101.oldboyedu.com ~]# vim /etc/exports
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# cat /etc/exports
/data/kubernetes *(rw,no_root_squash)
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# mkdir -pv /data/kubernetes
mkdir: 已创建目录 "/data"
mkdir: 已创建目录 "/data/kubernetes"
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# ll /data/kubernetes
总用量 0
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# systemctl start nfs && systemctl enable nfs
Created symlink from /etc/systemd/system/multi-user.target.wants/nfs-server.service to /usr/lib/systemd/system/nfs-server.service.
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# exportfs # 查看NFS的挂载信息
/data/kubernetes
<world>
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# touch /data/kubernetes/nfs.log # 创建测试文件,便于客户端挂载测试验证
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# ll /data/kubernetes
总用量 0
-rw-r--r-- 1 root root 0 3月 16 07:58 nfs.log
[root@k8s101.oldboyedu.com ~]#
(3)客户端验证NFS的可用性
mount -t nfs k8s101.oldboyedu.com:/data/kubernetes /mnt/ # 手动挂载NFS的服务端进行测试
(4)手动卸载NFS
[root@k8s101.oldboyedu.com ~]# umount /mnt # 手动卸载NFS
温馨提示:
建议在Kubernetes的每个工作节点都安装nfs-utils软件包,如果只在NFS服务端安装,客户端未安装的话,可能客户端在使用mount命令手动挂在时会挂在不上哟~
3.测试案例
[root@k8s101.oldboyedu.com ~]# vim 03-volumes-nfs-demo.yaml
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# cat 03-volumes-nfs-demo.yaml
apiVersion: v1
kind: Namespace
metadata:
name: oldboyedu-volume
---
apiVersion: extensions/v1beta1 # 注意观察这个API的版本编号哟~在后期的版本中变为"apps/v1"
kind: Deployment
metadata:
name: mynginx-deploy
namespace: oldboyedu-volume
spec:
selector:
matchLabels:
app: nginx-nfs
replicas: 3
template:
metadata:
labels:
app: nginx-nfs
spec:
# 配置NFS的数据源
volumes:
- name: oldboyedu-nfs
nfs:
server: k8s101.oldboyedu.com # 指定NFS的服务器的地址,如果指定主机名请确保可以正常进行解析哟~
path: /data/kubernetes # 指定NFS的挂载路径
containers:
- name: my-nginx
image: k8s101.oldboyedu.com:5000/docker.io/nginx:1.13
# 在容器中挂载数据卷
volumeMounts:
- name: oldboyedu-nfs
mountPath: /usr/share/nginx/html
ports:
- containerPort: 80
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# kubectl apply -f 03-volumes-nfs-demo.yaml
namespace "oldboyedu-volume" created
deployment "mynginx-deploy" created
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# kubectl get pods -n oldboyedu-volume
NAME READY STATUS RESTARTS AGE
mynginx-deploy-1210620599-0tqll 1/1 Running 0 6s
mynginx-deploy-1210620599-kp0p8 1/1 Running 0 6s
mynginx-deploy-1210620599-rb8z5 1/1 Running 0 6s
[root@k8s101.oldboyedu.com ~]#
温馨提示:
(1)如下图所示,建议进入到容器内创建首页文件:
echo "<h1>oldboyedu linux2021<h1>" > /usr/share/nginx/html/index.html
(2)如下图所示,当我们修改任意一个Pod(尽管不在同一个node节点)的首页文件时,无论你访问的是哪个Pod都会访问相同的页面哟。
(3)你可以大胆删除Pod资源,观察数据始终不会丢失哟。
kubectl delete -f 03-volumes-nfs-demo.yaml
(4)生产环境中建议购买支持nfs协议的硬件存储,因为其会自动快照数据,从而保证数据的可用性。
六.PV和PVC的基本使用
1.持久化数据卷(persistent volume,简称"pv")和持久化数据卷声明(persistent volume claim,简称"pvc")的区别
什么是pv:
全称为:"persistent volume",属于k8s集群的全局资源,因此并不支持名称空间(namespace)。
该资源对象本身并不负责数据的真实存储,而是负责和后端存储进行关联,每个pv都有提前定义好的存储能力。
持久卷是对存储资源数据卷(volume)创建和使用的抽象,使得存储作为集群中的资源管理。
这样我们就可以对数据卷的使用空间,读写权限做一个基本的权限管控,而不是放任Pod使用所有的数据卷资源。
pv的作用:
PV解决了企业对于存储的依赖问题,比如A公司使用的是nfs作为存储,而B公司使用的是cephfs作为存储,对于运维人员而言可能需要手动维护不同的组件,而一旦使用pv对存储资源进行抽象后,在迁移服务时就无需关心集群底层存储使用的资源,而是直接使用pv即可。
简而言之,就是实现了以下三大特性:
1>后端存储的权限管控(rw,ro);
2>.存储能力(比如使用多大的存储空间);
3>.后端存储卷(volumes)存储类型的应用解耦。
什么是pvc:
全称为:"persistent volume claim", 属于k8s集群某一个namespace的局部资源。
该资源对象本身并不负责数据的真实存储,而是显式声明需要使用的存储资源需求。
pvc的作用:
让用户不需要关心具体的Volume实现细节。pvc的一个最重要的作用就是去关联符合条件的pv,从而实现对存储资源的使用。
推荐阅读:
https://kubernetes.io/docs/concepts/storage/persistent-volumes/
温馨提示:
(1)全局资源指的是所有的namespace都能看到该资源,很明显是全局唯一的。
(2)局部资源指的是该资源只属于某个namespace哟。
(3)pv和pvc均属于k8s资源,而k8s的所有资源通常都支持打标签的哟。
综上所述,pv和pvc的对应关系如下图所示。
2.创建pv资源
(1)创建pv的存储目录,注意该目录是NFS的挂载目录哟~
mkdir -pv /data/kubernetes/pv0{1,2,3}
(2)编写pv的资源清单
[root@k8s101.oldboyedu.com ~]# cat 01-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv01
labels:
type: ssd
spec:
capacity: # 定义PV存储容量的大小
storage: 10Gi
accessModes: # 指定访问模式
- ReadWriteMany
persistentVolumeReclaimPolicy: Recycle # 指定PV的回收策略
nfs: # 指定PV后端的支持存储设备类型
path: "/data/kubernetes/pv01"
server: 172.200.1.101
readOnly: false
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# cat 02-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv02
labels:
type: ssd
spec:
capacity: # 定义PV存储容量的大小
storage: 30Gi
accessModes: # 指定访问模式
- ReadWriteMany
persistentVolumeReclaimPolicy: Recycle # 指定PV的回收策略
nfs: # 指定PV后端的支持存储设备类型
path: "/data/kubernetes/pv02"
server: 172.200.1.101
readOnly: false
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# cat 03-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv03
labels:
type: ssd
spec:
capacity: # 定义PV存储容量的大小
storage: 50Gi
accessModes: # 指定访问模式
- ReadWriteMany
persistentVolumeReclaimPolicy: Recycle # 指定PV的回收策略
nfs: # 指定PV后端的支持存储设备类型
path: "/data/kubernetes/pv03"
server: 172.200.1.101
readOnly: false
[root@k8s101.oldboyedu.com ~]#
(3)创建pv资源
[root@k8s101.oldboyedu.com /oldboyedu/persistentVolume]# kubectl apply -f .
persistentvolume "pv01" created
persistentvolume "pv02" created
persistentvolume "pv03" created
[root@k8s101.oldboyedu.com /oldboyedu/persistentVolume]#
温馨提示:
如下图所示,我们在任意的名称空间都能看到PV资源哟,因为它是全局资源!
常用的字段如下:
NAME:
有关PV资源的名称。
CAPACITY:
该PV资源的容量大小。
ACCESSMODES(访问模式):
是用来对PV进行访问模式的设置,用于描述用户应用对存储资源的访问权限,访问权限包括以下几种方式:
ReadWriteOnce(RWO):
读写权限,但是只能被单个节点挂载。
ReadOnlyMany(ROX):
只读权限,可以被多个节点挂载。
ReadWriteMany(RWX):
读写权限,可以被多个节点挂载。
ReadWriteOncePod:
卷可以由单个Pod以读写方式挂载。这仅支持CSI卷和Kubernetes 1.22+版。
RECLAIMPOLICY(回收策略):
目前较新版本中PV支持的策略有三种:
Retain(保留):
保留数据,需要管理员手工清理数据,这是默认策略哟。
Recycle(回收):
清除PV中的数据,效果相当于执行"rm -rf /data/kubernetes/*"。
Delete(删除):
与PV相连的后端存储同时删除。
STATUS(状态):
一个PV的生命周期中,可能会处于四种不同的阶段:
Available(可用):
表示可用状态,还未被任何PVC绑定。
Bound(已绑定):
表示PV已经被PVC绑定。
Released(已释放):
PVC被删除,但是资源还未被集群重新声明。换句话说,这种状态下PV是不可重新分配的,需要手动清理保留的数据(因为默认的回收策略是"Retain")。
Failed(失败):
表示该PV的自动回收失败。
CLAIM:
被那个pvc声明使用。
REASON:
PV故障原因。
AGE:
资源创建的时间。
参考链接:
https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes
3.创建PVC测试
(1)创建namespace,以便于将PVC放入该名称空间
[root@k8s101.oldboyedu.com ~]# cat ns-oldboyedu-tomcat.yaml
apiVersion: v1
kind: Namespace
metadata:
name: oldboyedu-tomcat
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# kubectl apply -f ns-oldboyedu-tomcat.yaml
namespace "oldboyedu-tomcat" created
[root@k8s101.oldboyedu.com ~]#
(2)创建PVC
[root@k8s101.oldboyedu.com ~]# cat 01-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: tomcat-mysql-pvc
namespace: oldboyedu-tomcat
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 35Gi
[root@k8s101.oldboyedu.com ~]#
温馨提示:
(1)当我们创建的PVC申请的存储大小要小于PV现有存储大小时,K8S会自动评估PVC和与哪一个后端PV进行绑定(Bound),如果pvc找不到合适的PV则pvc始终会停留在"Pending"状态哟~
(2)如下图所示,本案例PVC仅申请了35G内存,但实际上PVC会自动去关联全局资源,匹配最合适的PV,这个过程是由K8s集群自动实现的,无需运维人员手动绑定,绑定成功请注意观察PV和PVC的状态均为"Bound"。
(3)如下图所示,如果将PVC申请的数量过大(本案例是32GB),就会导致PVC无法自动关联全局PV资源,因此PVC的状态始终为"Pending",因为此时并没有一个PV的存储能力能够满足该pvc哟。
4.使用mysql案例来测试使用pvc
[root@k8s101.oldboyedu.com ~]# cat mysql-deploy.yml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: mysql
namespace: oldboyedu-tomcat
spec:
replicas: 1
template:
metadata:
labels:
app: oldboyedu-mysql
spec:
volumes:
- name: datadir
persistentVolumeClaim:
claimName: tomcat-mysql-pvc # 指定PVC的名称,注意要创建的Pod的名称空间中必须存在该PVC哟~
readOnly: false # PVC是否只读,默认值就为false,如果想要只读请设置为"true"
- name: logbin # 我们可以定义多个存储卷,但下面若不挂载,则始终不会应用它哟!
emptyDir: {}
containers:
- name: mysql
image: k8s101.oldboyedu.com:5000/mysql:5.7
ports:
- containerPort: 3306
env:
- name: MYSQL_ROOT_PASSWORD
value: '123456'
volumeMounts:
- name: datadir
mountPath: /var/lib/mysql
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# kubectl apply -f mysql-deploy.yml
deployment "mysql" created
[root@k8s101.oldboyedu.com ~]#
[root@k8s101.oldboyedu.com ~]# kubectl get pods -n oldboyedu-tomcat
NAME READY STATUS RESTARTS AGE
mysql-967063035-s89z7 1/1 Running 0 2s
[root@k8s101.oldboyedu.com ~]#
温馨提示:
如下图所示,我们创建的MySQL容器,数据成功持久化到后端存储啦!
如下图所示,当持久化后,我们可以通过"kubectl describe pods mysql-967063035-s89z7 -n oldboyedu-tomcat"命令就能看到对应的Pod关于数据卷的挂载信息啦!
5.课堂练习(课后作业)
作业1: 请分别使用nginx1.16,nginx1.18,nginx1.19,实现多服务部署,要求如下:
(1)使用alpine镜像;
(2)对外只暴露一个80端口;
(3)访问"/"的时候出现的服务器名称,服务器端的IP地址,客户端的user-agent(颜色为粉色);
(4)访问"/kod"的时候出现可道云的页面;
(5)访问"/brid"的时候出现"小鸟飞飞";
(6)访问"/wp"的时候出现wordpress的页面;
(7)要求镜像命名规则如下:
k8s110.oldboyedu.com:5000/oldboyedu-alpine/nginx:1.16
k8s110.oldboyedu.com:5000/oldboyedu-alpine/nginx:1.18
k8s110.oldboyedu.com:5000/oldboyedu-alpine/nginx:1.20
作业2: 对镜像升级并持久化数据,要求如下:
(1)要求使用NFS多路径暴露,实现对/kod",/brid","/wp"的数据实现挂载;
/data/kubernetes/kod
/data/kubernetes/brid
/data/kubernetes/wp
(2)请扩容wordpress数量从1到3,并确认访问的数据的一致;
(3)请使用deployment资源滚动升级Nginx版本,要求在升级过程对用户透明(换句话说,就是不能影响用户访问);
七.PV资源的回收
1.删除资源
删除PVC
kubectl delete pvc tomcat-mysql-pvc -n oldboyedu-tomcat
温馨提示:
如下图所示,PVC资源删除完后,但PV并没有回收,而是变为"Released"状态。
2.解决"ErrImagePull"问题
(1)在线编辑"pv-recycler"的POD资源
kubectl edit po recycler-for-pv03
(2)修改镜像地址为国内的阿里云源
image: registry.aliyuncs.com/google_containers/busybox
3.资源被回收成功
如下图所示,PV的资源被成功释放了,他的状态变为"Available"。
温馨提示:
k8s1.5版本中可能出现按照上述方法无法实现pv的删除,此时可以使用"kubectl delete pv pv03"强制手动删除PV资源,此时该pv后台存储的数据貌似也会跟着被删除哟。
八.PVC的标签选择器
1.创建测试PV
[root@k8s101.oldboyedu.com /oldboyedu/persistentVolume]# cat 04-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv04
labels:
type: hdd
spec:
capacity: # 定义PV存储容量的大小
storage: 50Gi
accessModes: # 指定访问模式
- ReadWriteMany
persistentVolumeReclaimPolicy: Recycle # 指定PV的回收策略
nfs: # 指定PV后端的支持存储设备类型
path: "/data/kubernetes/pv03"
server: 172.200.1.101
readOnly: false
[root@k8s101.oldboyedu.com /oldboyedu/persistentVolume]#
[root@k8s101.oldboyedu.com /oldboyedu/persistentVolume]#
[root@k8s101.oldboyedu.com /oldboyedu/persistentVolume]# kubectl apply -f 04-pv.yaml
persistentvolume "pv04" created
[root@k8s101.oldboyedu.com /oldboyedu/persistentVolume]#
温馨提示:
如下图所示,我创建了一个测试的PV04,其存储能力和pv03相同,但标签名称并不相同哟,以便于我们后续的pvc测试。
2.创建PVC
[root@k8s101.oldboyedu.com /oldboyedu/persistentVolumeClaim]# cat 02-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: tomcat-mysql-pvc2
namespace: oldboyedu-tomcat
spec:
selector:
matchLabels:
type: hdd
accessModes:
- ReadWriteMany
resources:
requests:
storage: 25Gi
[root@k8s101.oldboyedu.com /oldboyedu/persistentVolumeClaim]#
[root@k8s101.oldboyedu.com /oldboyedu/persistentVolumeClaim]# kubectl apply -f 02-pvc.yaml
persistentvolumeclaim "tomcat-mysql-pvc2" created
[root@k8s101.oldboyedu.com /oldboyedu/persistentVolumeClaim]#
温馨提示:
如下图所示,当我们成功创建了pvc资源时,其存储要求是25GB,尽管pv02和pv04均满足25GB的存储要求,但始终会被调度到pv04,这正是由于标签选择器的存在哟。
九.PVC指定PV名称进行绑定结合标签选择器
[root@k8s101.oldboyedu.com /oldboyedu/pv+pvc]# cat 08-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: tomcat-mysql-pvc-oldboyedu-linux
namespace: oldboyedu-tomcat
spec:
selector:
matchLabels:
type: hdd
accessModes:
- ReadWriteMany
resources:
requests:
storage: 25Gi
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: tomcat-mysql-pvc-oldboyedu-python
namespace: oldboyedu-tomcat
spec:
volumeName: pv02
accessModes:
- ReadWriteMany
resources:
requests:
storage: 25Gi
[root@k8s101.oldboyedu.com /oldboyedu/pv+pvc]#
十.持久卷的动态供给(StorageClass,了解即可,建议使用高于k8s1.11版本进行讲解。)
1.PV静态供给概述
如下图所示,现在PV使用方式称为静态供给,需要K8s运维工程师提前创建一堆PV,供开发者使用。
因此我们在生产环境中可以考虑优化这个环节,让PV动态创建就好了。
2.PV动态概述
PV静态供给明显的缺点就是维护成本太高了,需要K8S运维人员手动创建一堆PV。
因此K8S开始支持PV动态供给,使用StorageClass对象实现。
如下图所示,当你创建的Pod内的PVC需要应用对应的PV时,可以借助StorageClass提供的接口来自动创建PV。
3.K8S内置的动态供给方案
如下图所示,K8S支持的数据卷中,并不是所有的组件都支持动态供给,其中NFS就不支持动态供给功能,但我们可以在GitHub社区可以找打有人给出了动态供给的可行性方案。
关于支持动态供给PV的插件,可参考官方链接:
https://kubernetes.io/docs/concepts/storage/storage-classes/
推荐阅读:
https://github.com/kubernetes-retired/external-storage
4.基于NFS实现PV动态供给实战案例-手动创建Pod效果并不明显(只能动态创建,但却无法动态归档!),生产环境不推荐使用,但必须演示!为学员留下印象
一.下载nfs-client插件
(1)安装git工具便于下载
yum -y install git
(2)建议从官方下载,如果GitHub不方便下载,我在码云上找了一个相同的副本,也能凑合着用。
git clone https://github.com/kubernetes-retired/external-storage.git
git clone https://gitee.com/yangfanimb/external-storage.git
二.修改nfs-client插件相关的配置文件
略,推荐使用K8s1.11+版本测试。
5.基于NFS实现PV动态供给实战案例-基于控制器创建Pod,生产环境推荐使用
略,较高版本再来讨论这个话题。推荐使用K8s1.11+版本测试。
6.关于PVC和PV你可能存在的疑问
Q1: 为什么数据没有被真正的删除呢?
答: 这是因为我们将上面的"class.yaml"文件中的archiveOnDelete值设置为"true",表示删除的时候回进行归档,但如果想要删除时直接删除PV上的数据,则可以将archiveOnDelete的值改为"false",注意,其默认值就是"false"哟~
Q2: PV和PVC什么关系?
答: PV和PVC是一对一的关系,PVC主要是告诉Pod申请资源的声明,PV是对数据卷的一个更高级的抽象。PVC将Pod所需的资源和对应的PV进行一对一的绑定。
Q3: PVC和PV是怎么匹配的?
答: 访问模式(PersistentVolumeClaim.spec.accessModes),存储容量(PersistentVolumeClaim.spec.resources.requests.storage),标签选择器(PersistentVolumeClaim.spec.selector.matchLabels)以及PV的名称(PersistentVolumeClaim.spec.volumeName)是PVC主要匹配的依据。
Q4: PVC和PV的容量匹配策略是什么呢?
答: 匹配就近符合的容量,我们习惯上称之为向上匹配。比如PVC需要的PersistentVolumeClaim.spec.resources.requests.storage大小为"20Gi",但所有的PV中并没有对应的20G,而只有15G,30G,50G空闲的资源时,PVC会优先匹配30Gi的PV哟~
Q5: PVC存储容量是真的用于限制吗?
答: 经过实验证明,存储容量的确是有限制的功能,但容量字段另一个作用就是用于匹配与之匹配的PV容量。
Q6:有关dd参数说明如下:(可以用于测试Pod占用PVC的实验,但是在K8S 1.5版本中实测,发现PV无法限制容量的大小)
dd if=/dev/zero of=big_file01.log bs=128M count=8
dd if=/dev/urandom of=big_file02 bs=1G count=5 iflag=fullblock
dd命令常用的子选项说明:
if:
指定输入文件
of:
指定输出文件
bs:
指定每次读写的字节数,单位为K、M、G等
count:
指定读写的block数
温馨提示:
如果我们有意将容量字段设置的值非常大,甚至超过了实际数据卷的存储空间,但实际上存储容量取决于后端存储。因此我们上面的案例也看到了容量字段的主要作用还是用于PVC匹配与之符合的PV哟~