背景
容器中的文件在磁盘上是临时存放的,这给容器中运行的特殊应用程序带来一些问题。 首先,当容器崩溃时,kubelet 将重新启动容器,容器中的文件将会丢失——因为容器会以干净的状态重建。 其次,当在一个 Pod 中同时运行多个容器时,常常需要在这些容器之间共享文件。 Kubernetes 抽象出 Volume 对象来解决这两个问题。
所以,为了持久化保存容器的数据和容器之间数据共享,kubernetes引入了Volume的概念。
概念
Volume是k8s抽象出来的对象,用于解决Pod中的容器运行时,文件存放的问题以及多容器数据共享的问题。Kubernetes 卷具有明确的生命周期——与包裹它的 Pod 相同。卷比 Pod 中运行的任何容器的存活期都长,在容器重新启动时数据也会得到保留。
Volume是Pod中能够被多个容器访问的共享目录,它被定义在Pod上,然后被一个Pod里的多个容器挂载到具体的文件目录下,kubernetes通过Volume实现同一个Pod中不同容器之间的数据共享以及数据的持久化存储。
更重要的是,Kubernetes 可以支持许多类型的卷,Pod 也能同时使用任意数量的卷。卷的核心是包含一些数据的目录,Pod 中的容器可以访问该目录。 特定的卷类型可以决定这个目录如何形成的,并能决定它支持何种介质,以及目录中存放什么内容。
使用卷时, Pod 声明中需要提供卷的类型(.spec.volumes 字段)和卷挂载的位置 (.spec.containers.volumeMounts 字段)。
Volume的生命容器不与Pod中单个容器的生命周期相关。
kubelet创建Pod时,会先创建一个基础容器pause,Pod里面所有的容器共享一个网络名称空间和文件系统。挂载卷的工作就是由基础容器pause来完成的。
卷的类型
Kubernetes 支持下列类型的卷:
kubernetes的Volume支持多种类型,比较常见的有下面几个:
EmptyDir是最基础的Volume类型,一个EmptyDir就是Host上的一 个空目录。
EmptyDir是在Pod分配到Node时创建的,它的初始内容为空,并且无须指定宿主机上对应的目录文件,因为kubernetes会自动分配一个目录, 当Pod销毁时,EmptyDir中的数据也会被永久删除(EmptyDir与Pod生命周期一致,不适合做数据持久化存储)。
EmptyDir用途如下:
EmptyDir的使用
通过EmptyDir实现容器之间文件共享
在一个Pod中准备两个容器nginx和busybox,然后声明一个Volume分别挂载到两个容器的目录中,然后nginx容器负责向Volume中写日志,busybox中通过命令将日志内容读到控制台。
# 示例一:挂载存在的目录
# 将nginx容器的/var/log/nginx目录与busybox容器的/logs目录挂载到同一volume实现共享。通过访问nginx页面,nginx容器会将访问记录写入到/var/log/nginx/access.log文件中,那么可以通过busybox容器的/logs/access.log文件读出访问记录
[root@k8s-master ~]# vim volume-emptydir.yaml
apiVersion: v1
kind: Pod
metadata:
name: volume-emptydir
namespace: test
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
volumeMounts: # 将下面声明的logs-volume挂载到nginx容器中的/var/log/nginx目录上(只要有访问请求,那么便会有内容写入到该目录下的access.log文件中)
- name: logs-volume
mountPath: /var/log/nginx
- name: busybox
image: busybox:1.30
command: ["/bin/sh","-c","while true; do if test -f /logs/access.log; then tail -f /logs/access.log; else echo no such file named access.log; sleep 3;fi; done"] # 初始命令,动态读取 access.log 文件中内容
volumeMounts: # 将下面声明的logs-volume挂载到busybox容器中的/logs目录上(所以相当于nginx容器中的/var/log/nginx目录与busybox的/logs目录属于共享目录,下面的文件将会进行同步,当有请求访问nginx时,便会向nginx容器中的/var/log/nginx目录下的accsee.log日志文件追加访问记录,同时在busybox容器中也会看到该文件的内容,因为这两个容器的这两个目录都挂载在同一volume上,所以这两个目录下的文件及内容会实时保持一致)
- name: logs-volume
mountPath: /logs
volumes: #声明volume,name为logs-volume, 类型为emptyDir
- name: logs-volume
emptyDir: {}
# 创建
[root@k8s-master ~]# kubectl create -f volume-emptydir.yaml
pod/volume-emptydir created
# 查看
[root@k8s-master ~]# kubectl get pod -n test -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
volume-emptydir 2/2 Running 0 7s 10.244.2.195 k8s-node02 <none> <none>
# 因为nginx容器的/var/log/nginx/与busybox容器中的/logs目录挂载在同一volume,所以这两个目录下的文件会是一致的并实时同步
[root@k8s-master ~]# kubectl exec -it volume-emptydir -c nginx -n test -- ls /var/log/nginx/
access.log error.log
[root@k8s-master ~]# kubectl exec -it volume-emptydir -c busybox -n test -- ls /logs
access.log error.log
# 此时对nginx进行访问,相当与向nginx容器中的/var/log/nginx/access.log文件中写入访问记录。
[root@k8s-node01 ~]# while true ;do curl 10.244.2.195 ;sleep 2 ;done
# 因为向每访问一次便会在/var/log/nginx/access.log文件写入记录数据,而且会同时同步更新busybox容器/logs目录下的access.log文件
# 资源清单文件在配置时,对于busybox容器会实时输出/logs/access.log文件中的新访问记录到标准输出
# 所以,流程为:在访问nginx时,会将访问记录记录到nginx容器的/var/log/nginx/access.log文件中,同时也会将访问记录数据实时更新到busybox容器的/logs/access.log文件中,于此同时busybox容器又会将/logs/access.log中的访问记录数据信息输出到控制台
# 那么,在访问nginx页面时,只要在busybox容器的控制台能够看到访问记录的标准输出就说明volume挂载成功
# 查看busybox容器的标准输出
[root@k8s-master ~]# kubectl logs volume-emptydir -c busybox -n test
10.244.1.0 - - [04/Jun/2021:02:30:10 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.1.0 - - [04/Jun/2021:02:30:12 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.1.0 - - [04/Jun/2021:02:30:14 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.1.0 - - [04/Jun/2021:02:30:16 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.1.0 - - [04/Jun/2021:02:30:18 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.1.0 - - [04/Jun/2021:02:30:20 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.1.0 - - [04/Jun/2021:02:53:28 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.1.0 - - [04/Jun/2021:02:53:30 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.1.0 - - [04/Jun/2021:02:53:32 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.1.0 - - [04/Jun/2021:02:53:34 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.1.0 - - [04/Jun/2021:02:53:36 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.1.0 - - [04/Jun/2021:02:53:38 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.1.0 - - [04/Jun/2021:02:53:40 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
# emptydir格式的volume挂载成功
# 示例二:挂载不存在的目录
[root@k8s-master ~]# vim volume-emptydir.yaml
apiVersion: v1
kind: Pod
metadata:
name: volume-emptydir
namespace: test
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
volumeMounts: # 将下面声明的logs-volume挂载到nginx容器中的/test1目录上(无则创建)
- name: test-volume
mountPath: /test1
- name: busybox
image: busybox:1.30
command: ["/bin/sh","-c"," while true; do echo test;sleep 2; done"] # 保证容器正常运行
volumeMounts: # 将下面声明的logs-volume挂载到busybox容器中的/test2目录上(无则创建)
- name: test-volume
mountPath: /test2
volumes: #声明volume,name为logs-volume, 类型为emptyDir
- name: test-volume
emptyDir: {}
# 创建
[root@k8s-master ~]# kubectl create -f volume-emptydir.yaml
pod/volume-emptydir created
[root@k8s-master ~]# kubectl get pod -n test
NAME READY STATUS RESTARTS AGE
volume-emptydir 2/2 Running 0 8s
# 在 nginx 容器的 /test1 目录下创建文件
[root@k8s-master ~]# kubectl exec -it volume-emptydir -c nginx -n test -- sh
# ls
bin boot dev etc home lib lib64 media mnt opt proc root run sbin srv sys test1 tmp usr var
# cd test1
# echo 123 > test.txt
# exit
# 在busybox容器中查看上面创建的文件
[root@k8s-master ~]# kubectl exec -it volume-emptydir -c busybox -n test -- sh
/ # ls
bin dev etc home proc root sys test2 tmp usr var
/ # cd test2
/test2 # ls
test.txt
/test2 # cat test.txt
123
/test2 # exit
# 删除
[root@k8s-master ~]# kubectl delete -f volume-emptydir.yaml
pod "volume-emptydir" deleted
EmptyDir中数据不会被持久化,它会随着Pod的结束而销毁,如果想简单的将数据持久化到主机中,可以选择HostPath。
HostPath就是将Node主机中一个实际目录挂在到Pod中,以供容器使用,这样的设计就可以保证Pod销毁了,但是数据可以依旧存在于Node主机上。
[root@k8s-master ~]# vim volume-hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
name: volume-hostpath
namespace: test
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
volumeMounts:
- name: logs-volume
mountPath: /var/log/nginx
- name: busybox
image: busybox:1.30
command: ["/bin/sh","-c","while true;do if test -f /logs/access.log;then tail -f /logs/access.log;else echo no such file named access.log;sleep 3;fi;done"]
volumeMounts:
- name: logs-volume
mountPath: /logs
volumes:
- name: logs-volume
hostPath:
path: /mnt/nginx-logs
type: DirectoryOrCreate # 主机上的该目录存在就使用,不存在就先创建后使用;如果为Directory,则path指定的目录必须在主机上存在,不存在报错
# 关于type的值的一点说明:
# DirectoryOrCreate 目录存在就使用,不存在就先创建后使用
# Directory 目录必须存在
# FileOrCreate 文件存在就使用,不存在就先创建后使用
# File 文件必须存在
# Socket unix 套接字必须存在
# CharDevice 字符设备必须存在
# BlockDevice 块设备必须存在
# 创建
[root@k8s-master ~]# kubectl create -f volume-hostpath.yaml
pod/volume-hostpath created
# 查看
[root@k8s-master ~]# kubectl get pod -n test -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
volume-hostpath 2/2 Running 0 4s 10.244.2.213 k8s-node02 <none> <none>
# 查看目录下的文件
[root@k8s-master ~]# kubectl exec -it volume-hostpath -c nginx -n test -- ls -l /var/log/nginx/
total 0
-rw-r--r-- 1 root root 0 Jun 4 07:09 access.log
-rw-r--r-- 1 root root 0 Jun 4 07:09 error.log
[root@k8s-master ~]# kubectl exec -it volume-hostpath -c busybox -n test -- ls -l /logs
total 0
-rw-r--r-- 1 root root 0 Jun 4 07:09 access.log
-rw-r--r-- 1 root root 0 Jun 4 07:09 error.log
# 主机上的/mnt/nginx-logs,是存在于pod被调度运行后的节点上(k8s-node02)
# 说明:挂载在主机上的持久化文件或目录存在于该pod被调度运行的主机节点上
[root@k8s-node02 mnt]# ll
total 0
drwxr-xr-x 2 root root 41 Jun 4 15:09 nginx-logs
[root@k8s-node02 mnt]# cd nginx-logs/
[root@k8s-node02 nginx-logs]# ls
access.log error.log
# 访问nginx
[root@k8s-master ~]# for i in {1..5}; do curl 10.244.2.213 ;done
# 查看文件中内容
# nginx容器中
[root@k8s-master ~]# kubectl exec -it volume-hostpath -c nginx -n test -- cat /var/log/nginx/access.log
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
# busybox容器中
[root@k8s-master ~]# kubectl exec -it volume-hostpath -c busybox -n test -- cat /logs/access.log
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
# k8s-node02节点的nginx-logs目录下
[root@k8s-node02 nginx-logs]# ls
access.log error.log
[root@k8s-node02 nginx-logs]# cat access.log
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
# 删除pod
[root@k8s-master ~]# kubectl delete -f volume-hostpath.yaml
pod "volume-hostpath" deleted
[root@k8s-master ~]# kubectl get pod -n test -o wide
No resources found in test namespace.
# 查看节点上的目录和文件内容
[root@k8s-node02 nginx-logs]# ls
access.log error.log
[root@k8s-node02 nginx-logs]# cat access.log
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:07:15:04 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
# 即使pod被销毁,但是挂载到节点上的目录依旧存在,且文件及内容也依然存在。
# hostpath类型挂载卷可以做到数据文件的持久化存储
HostPath可以解决数据持久化的问题,但是一旦Node节点故障了,Pod如果转移到了别的节点,又会出现问题了,此时需要准备单独的网络存储系统,比较常用的用NFS、CIFS。
NFS是一个网络文件存储系统,可以搭建一台NFS服务器, 然后将Pod中的存储直接连接到NFS系统上(生产环境中,一般为 nfs服务HA集群),这样的话,无论Pod在节点上怎么转移,只要Node跟NFS的对接没问题,数据就可以成功访问。
实践
使用一台新节点充当nfs服务器(生产环境中一般为集群化)
# nfs节点IP
[root@nfs ~]# ifconfig ens32 | awk 'NR==2 {print $2}'
192.168.126.12
# 关闭防火墙和selinux
[root@nfs ~]# systemctl stop firewalld
[root@nfs ~]# setenforce 0
# 安装nfs服务
[root@nfs ~]# yum install nfs-utils -y
# 创建共享目录
[root@nfs ~]# mkdir -p /root/data/nfs
# 将共享目录以读写权限暴露给192.168.126.0/24网段中的所有主机
[root@nfs ~]# vim /etc/exports
/root/data/nfs/ 192.168.126.0/24(rw,no_root_squash)
# 启动nfs服务
[root@nfs ~]# systemctl start nfs
# 开机自启
[root@nfs ~]# systemctl enable nfs
# k8s-master节点
[root@k8s-master ~]# ifconfig ens32 | awk 'NR==2 {print $2}'
192.168.126.100
[root@k8s-master ~]# yum install nfs-utils -y
# 测试nfs连接性
[root@k8s-master ~]# showmount -e 192.168.126.12
Export list for 192.168.126.12:
/root/data/nfs 192.168.126.0/24
# k8s-node01节点
[root@k8s-node01 ~]# ifconfig ens32 | awk 'NR==2 {print $2}'
192.168.126.101
[root@k8s-node01 ~]# yum install nfs-utils -y
[root@k8s-node01 ~]# showmount -e 192.168.126.12
Export list for 192.168.126.12:
/data 192.168.126.0/24
# k8s-node02节点
[root@k8s-node02 ~]# ifconfig ens32 | awk 'NR==2 {print $2}'
192.168.126.102
[root@k8s-node02 ~]# yum install nfs-utils -y
[root@k8s-node02 ~]# showmount -e 192.168.126.12
Export list for 192.168.126.12:
/root/data/nfs 192.168.126.0/24
[root@k8s-master ~]# vim volume-nfs.yaml
apiVersion: v1
kind: Pod
metadata:
name: volume-nfs
namespace: test
spec:
containers:
- name: nginx
image: nginx:1.17.1
ports:
- containerPort: 80
volumeMounts:
- name: logs-volume
mountPath: /var/log/nginx
- name: busybox
image: busybox:1.30
command: ["/bin/sh","-c","while true;do if test -f /logs/access.log;then tail -f /logs/access.log;else echo no such file named access.log;sleep 3;fi;done"]
volumeMounts:
- name: logs-volume
mountPath: /logs
volumes:
- name: logs-volume
nfs:
server: 192.168.126.12 # nfs服务器地址
path: /root/data/nfs # 共享文件目录,最后持久化的数据文件都会存放到nfs服务器的该目录下
# 创建
[root@k8s-master ~]# kubectl create -f volume-nfs.yaml
pod/volume-nfs created
# 查看
[root@k8s-master ~]# kubectl get pod volume-nfs -n test -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
volume-nfs 2/2 Running 0 11s 10.244.2.214 k8s-node02 <none>
# 查看nfs服务器目录/root/data/nfs
[root@nfs ~]# cd /root/data/nfs/
[root@nfs nfs]# ls
access.log error.log
# 访问nginx
[root@k8s-master ~]# for i in {1..3}; do curl 10.244.2.214 ;done
# 查看数据是否实时写入
[root@nfs nfs]# cat access.log
10.244.0.0 - - [04/Jun/2021:08:18:08 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:08:18:08 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"
10.244.0.0 - - [04/Jun/2021:08:18:08 +0000] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-"