一、volumes介绍
1.1 在Docker中,也有volumes
这个概念,volume
只是磁盘上一个简单的目录,或者其他容器中的volume。生命周期也不受管理,并且直到最近他们都是基于本地后端存储的。
Kubernetes的volume
,有着明显的生命周期——和使用它的pod
生命周期一致。因此,volume生命周期就比运行在pod
中的容器要长久,即使容器重启,volume
上的数据依然保存着。当然,pod
不再存在时,volume
也就消失了。更重要的是,Kubernetes支持多种类型的volume
,并且pod
可以同时使用多种类型的volume
。
内部实现中,volume
只是一个目录,目录中可能有一些数据,pod
的容器可以访问这些数据。这个目录是如何产生的,它后端基于什么存储介质,其中的数据内容是什么,这些都由使用的特定volume类型来决定。
要使用volume,pod
需要指定volume
的类型和内容(spec.volumes
字段),和映射到容器的位置(spec.containers.volumeMounts
字段)。
容器中的进程可以看到Docker image和volumes组成的文件系统。Docker image处于文件系统架构的root,任何volume都映射在镜像的特定路径上。Volume不能映射到其他volume上,或者硬链接到其他volume。容器中的每个容器必须堵路地指定他们要映射的volume。
1.2容器中的文件在磁盘上是临时存放的,这给容器中运行的特殊应用程序带来一些问题。首先,当容器崩溃时,kubelet 将重新启动容器,容器中的文件将会丢失,因为容器会以干净的状态重建。其次,当在一个 Pod 中同时运行多个容器时,常常需要在这些容器之间共享文件。 Kubernetes 抽象出 Volume 对象来解决这两个问题。
Kubernetes 卷具有明确的生命周期,与包裹它的 Pod 相同。 因此,卷比 Pod 中运行的任何容器的存活期都长,在容器重新启动时数据也会得到保留。 当然,当一个 Pod 不再存在时,卷也将不再存在。也许更重要的是,Kubernetes 可以支持许多类型的卷,Pod 也能同时使用任意数量的卷。
卷不能挂载到其他卷,也不能与其他卷有硬链接。 Pod 中的每个容器必须独立地指定每个卷的挂载位置。
1.3Kubernetes 支持下列类型的卷:
awsElasticBlockStore 、azureDisk、azureFile、cephfs、cinder、configMap、csi
downwardAPI、emptyDir、fc (fibre channel)、flexVolume、flocker
gcePersistentDisk、gitRepo (deprecated)、glusterfs、hostPath、iscsi、local、
nfs、persistentVolumeClaim、projected、portworxVolume、quobyte、rbd
scaleIO、secret、storageos、vsphereVolume
二、emptyDir卷
当 Pod 指定到某个节点上时,首先创建的是一个 emptyDir
卷,并且只要 Pod 在该节点上运行,卷就一直存在。 就像它的名称表示的那样,卷最初是空的。 尽管 Pod 中的容器挂载 emptyDir
卷的路径可能相同也可能不同,但是这些容器都可以读写 emptyDir
卷中相同的文件。 当 Pod 因为某些原因被从节点上删除时,emptyDir
卷中的数据也会永久删除。
2.1emptyDir 的使用场景:
缓存空间,例如基于磁盘的归并排序。
为耗时较长的计算任务提供检查点,以便任务能方便地从崩溃前状态恢复执行。
在 Web 服务器容器服务数据时,保存内容管理器容器获取的文件。
默认情况下, emptyDir
卷存储在支持该节点所使用的介质上;这里的介质可以是磁盘或 SSD 或网络存储,这取决于您的环境。 但是,您可以将 emptyDir.medium
字段设置为 "Memory"
,以告诉 Kubernetes 为您安装 tmpfs
(基于内存的文件系统)。 虽然 tmpfs 速度非常快,但是要注意它与磁盘不同。 tmpfs
在节点重启时会被清除,并且您所写入的所有文件都会计入容器的内存消耗,受容器内存限制约束。
2.3emptyDir 示例
[root@server2 ~]# mkdir vol
[root@server2 ~]# cd vol/
里面有两个镜像:一个卷被挂载到不同容器的挂载点上,数据实时同步
[root@server2 vol]# vim vol1.yaml
apiVersion: v1
kind: Pod
metadata:
name: vol1
spec:
containers:
- image: busyboxplus 默认访问pod内的第一个镜像
name: vm1
command: ["sleep", "300"]
volumeMounts:
- mountPath: /cache
name: cache-volume
- name: vm2
image: nginx
volumeMounts:
- mountPath: /usr/share/nginx/html
name: cache-volume
volumes:
- name: cache-volume
emptyDir:
medium: Memory
sizeLimit: 100Mi 限制内存使用
指定访问VM1也即是第一个镜像,看到的还是Nginx的默认发布页面
即使不指定第一个镜像默认访问的还是pod中的第一个镜像
[root@server2 vol]# kubectl exec -it vol1 -c vm1 -- sh
Defaulting container name to vm1.
Use 'kubectl describe pod/vol1 -n default' to see all of the containers in this pod.
/ # ls
bin dev home lib64 media opt root sbin tmp var
cache etc lib linuxrc mnt proc run sys usr
/ # cd cache/
/cache # ls
index.html
/cache # cat index.html
westos
/cache #
2.4emptydir缺点:
2.4.1不能及时禁止用户使用内存。虽然过1-2分钟kubelet会将Pod挤出,但是这个时间内,其实对node还是有风险的;
2.4.2影响kubernetes调度,因为empty dir并不涉及node的resources,这样会造成Pod“偷偷”使用了node的内存,但是调度器并不知晓;
2.4.3用户不能及时感知到内存不可用 。
接下来我们验证一下
使用上面建立的yml文件
[root@server2 vol]# kubectl apply -f vol1.yaml
pod/vol1 created
[root@server2 vol]# kubectl get pod -o wide 查看容器运行的节点主机
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
vol1 2/2 Running 0 9s 10.244.1.7 server3
[root@server3 ~]# free -h 查看节点主机的内存剩余量
total used free shared buff/cache available
Mem: 1.8G 323M 583M 33M 912M 1.2G
Swap: 0B 0B 0B
[root@server3 ~]# free -h
截取之前的内存量
total used free shared buff/cache available
Mem: 1.8G 316M 390M 32M 1.1G 1.2G
Swap: 0B 0B 0B
[root@server3 ~]# free -h
截取之后的内存量少了200
total used free shared buff/cache available
Mem: 1.8G 318M 188M 232M 1.3G 1.0G
Swap: 0B 0B 0B
[root@server2 vol]# kubectl get pod 等待一分钟查看pod已经被节点主机强制剔除
NAME READY STATUS RESTARTS AGE
vol1 0/2 Evicted 0 11m
[root@server3 ~]# free -h 再次查看节点主机内存量已经恢复至正常
total used free shared buff/cache available
Mem: 1.8G 299M 613M 32M 906M 1.2G
Swap: 0B 0B 0B
可以看到文件超过sizeLimit: 100Mi,则一段时间后(1-2分钟)会被kubelet evict掉。之所以不是“立即”被evict,是因为kubelet是定期进行检查的,这里会有一个时间差。
三、hostPath 卷
hostPath能将主机节点文件系统上的文件或目录挂载到您的 Pod 中。 虽然这不是大多数 Pod 需要的,但是它为一些应用程序提供了强大的逃生舱。
hostPath 的一些用法有:
运行一个需要访问 Docker 引擎内部机制的容器,挂载 /var/lib/docker 路径。
在容器中运行 cAdvisor 时,以 hostPath 方式挂载 /sys。
允许 Pod 指定给定的 hostPath 在运行 Pod 之前是否应该存在,是否应该创建以及应该以什么方式存在。
除了必需的 path 属性之外,用户可以选择性地为 hostPath 卷指定 type
当使用这种类型的卷时要小心,因为:
具有相同配置(例如从 podTemplate 创建)的多个 Pod 会由于节点上文件的不同而在不同节点上有不同的行为。
当 Kubernetes 按照计划添加资源感知的调度时,这类调度机制将无法考虑由 hostPath 使用的资源。
基础主机上创建的文件或目录只能由 root 用户写入。您需要在 特权容器 中以 root 身份运行进程,或者修改主机上的文件权限以便容器能够写入 hostPath 卷。
接下来操作演示:
[root@server2 vol]# vim vol1.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: nginx
name: test-container
volumeMounts:
- mountPath: /usr/share/nginx/html 指定挂载地址
name: test-volume 指定卷名称
volumes: 指定卷
- name: test-volume 名称
hostPath: 类型
path: /data 挂载的文件路径
type: DirectoryOrCreate 如果没有自动创建
运行pod
[root@server2 vol]# kubectl apply -f vol1.yaml
pod/test-pd created
[root@server2 vol]# kubectl get pod -o wide 查看在那个主机
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test-pd 1/1 Running 0 6s 10.244.1.8 server3
[root@server3 ~]# ll /data/ 此目录不存在会自动创建
total 0
[root@server3 ~]# cd /data/
[root@server3 data]# echo www.baidu.com > index.html 编辑Nginx的默认发布页面
[root@server3 data]# cat index.html
www.baidu.com
测试访问
[root@server2 vol]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test-pd 1/1 Running 0 6s 10.244.1.8 server3
[root@server2 vol]# curl 10.244.1.8
www.baidu.com
如何使pod容器运行在指定目录当前在server3,手动指定到server4
kubectl get nodes --show-labels 查看指定主机的标签
[root@server2 vol]# vim vol1.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
nodeSelector: 指定主机
kubernetes.io/hostname: server4 主机名称标签
containers:
- image: nginx
name: test-container
volumeMounts:
- mountPath: /usr/share/nginx/html
name: test-volume
volumes:
- name: test-volume
hostPath:
path: /data
type: DirectoryOrCreate
[root@server2 vol]# curl 10.244.2.5
www.taobao.com
结论:两个节点上的/data/目录下的文件内容不一样。切换节点默认发布页面也会更改
四、NFS 示例 持久化存储实战
实验目的:实现不同节点切换但是内容不变
首先在nfs数据输出主机上和其他节点安装nfs服务;master节点不用安装,此主机不参加调度。
yum install -y nfs-utils.x86_64
systemctl enable --now nfs
[root@server1 ~]# mkdir /nfsdata
[root@server1 ~]# vim /etc/nfsexports
[root@server1 ~]# cat /etc/nfsexports
/nfsdata *(rw,sync)
[root@server1 ~]# chmod 777 /nfsdata/
[root@server1 ~]# systemctl enable --now nfs
[root@server1 ~]# showmount -e
Export list for server1:
/nfsdata *
[root@server2 vol]# cat vol1.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-pd
spec:
containers:
- image: nginx
name: test-container
volumeMounts:
- mountPath: /usr/share/nginx/html
name: test-volume
volumes:
- name: test-volume
nfs:
server: 172.25.254.31 nfs输出主机
path: /nfsdata 目录
[root@server2 vol]# kubectl apply -f vol1.yaml
pod/test-pd created
[root@server2 vol]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test-pd 1/1 Running 0 3s 10.244.1.9 server3
[root@server1 ~]# cd /nfsdata/
[root@server1 nfsdata]# echo www.westos.org > index.html
[root@server2 vol]# curl 10.244.1.9
www.westos.org
再次受到把主机指定到server4上
方法同上:
[root@server2 vol]# vim vol1.yaml
spec:
nodeSelector: 指定主机
kubernetes.io/hostname: server4 主机名称标签
[root@server2 vol]# kubectl apply -f vol1.yaml
pod/test-pd created
[root@server2 vol]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test-pd 1/1 Running 0 5s 10.244.2.6 server4
[root@server2 vol]# curl 10.244.2.6
www.westos.org
五、PersistentVolume(持久卷PV)
PersistentVolume
(持久卷,简称PV
)是集群内,由管理员提供的网络存储的一部分。就像集群中的节点一样,PV
也是集群中的一种资源。它也像Volume一样,是一种volume插件,但是它的生命周期却是和使用它的Pod相互独立的。PV
这个API
对象,捕获了诸如NFS
、ISCSI
、或其他云存储系统的实现细节。
PersistentVolumeClaim
(持久卷声明,简称PVC
)是用户的一种存储请求。它和Pod类似,Pod消耗Node资源,而PVC
消耗PV资源。Pod能够请求特定的资源(如CPU和内存)。PVC
能够请求指定的大小和访问的模式(可以被映射为一次读写或者多次只读)。
有两种PV提供的方式:静态和动态。
静态PV:集群管理员创建多个PV,它们携带着真实存储的详细信息,这些存储对于集群用户是可用的。它们存在于Kubernetes API中,并可用于存储使用。
动态PV:当管理员创建的静态PV都不匹配用户的PVC时,集群可能会尝试专门地供给volume给PVC。这种供给基于StorageClass。
PVC
与PV
的绑定是一对一的映射。没找到匹配的PV,那么PVC会无限期得处于unbound
未绑定状态。
使用
Pod使用PVC就像使用volume一样。集群检查PVC,查找绑定的PV,并映射PV给Pod。对于支持多种访问模式的PV,用户可以指定想用的模式。一旦用户拥有了一个PVC,并且PVC被绑定,那么只要用户还需要,PV就一直属于这个用户。用户调度Pod,通过在Pod的volume块中包含PVC来访问PV。
释放
当用户使用PV完毕后,他们可以通过API来删除PVC对象。当PVC被删除后,对应的PV就被认为是已经是“released”了,但还不能再给另外一个PVC使用。前一个PVC的属于还存在于该PV中,必须根据策略来处理掉。
回收
PV的回收策略告诉集群,在PV被释放之后集群应该如何处理该PV。当前,PV可以被Retained(保留)、 Recycled(再利用)或者Deleted(删除)。保留允许手动地再次声明资源。对于支持删除操作的PV卷,删除操作会从Kubernetes中移除PV对象,还有对应的外部存储(如AWS EBS,GCE PD,Azure Disk,或者Cinder volume)。动态供给的卷总是会被删除。
访问模式
ReadWriteOnce -- 该volume只能被单个节点以读写的方式映射
ReadOnlyMany -- 该volume可以被多个节点以只读方式映射
ReadWriteMany -- 该volume可以被多个节点以读写的方式映射
在命令行中,访问模式可以简写为:
RWO - ReadWriteOnce
ROX - ReadOnlyMany
RWX - ReadWriteMany
回收策略
Retain:保留,需要手动回收
Recycle:回收,自动删除卷中数据
Delete:删除,相关联的存储资产,如AWS EBS,GCE PD,Azure Disk,or OpenStack Cinder卷都会被删除
当前,只有NFS和HostPath支持回收利用,AWS EBS,GCE PD,Azure Disk,or OpenStack Cinder卷支持删除操作。
状态:
Available:空闲的资源,未绑定给PVC
Bound:绑定给了某个PVC
Released:PVC已经删除了,但是PV还没有被集群回收
Failed:PV在自动回收中失败了
命令行可以显示PV绑定的PVC名称。
六、NFS PV 示例 持久化存储实战
pod不直接挂载pvc,而是挂载pv,pv绑定pvc
定义pv
[root@server2 pv]# vim pv.yml
apiVersion: v1
kind: PersistentVolume 创建pv
metadata:
name: pv1
spec:
capacity:
storage: 5Gi 大小5G
volumeMode: Filesystem pv卷模式
accessModes:
- ReadWriteOnce 该volume只能被单个节点以读写的方式映射
persistentVolumeReclaimPolicy: Recycle 可以再利用
storageClassName: nfs 存储于
nfs: 使用nfs作为后端
path: /nfsdata
server: 172.25.254.31
[root@server2 pv]# kubectl apply -f pv.yml
persistentvolume/pv1 configured
[root@server2 pv]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv1 5Gi RWO Recycle Available(未绑定状态) nfs 34s
定义pvc
与pv写在同一个文件中
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc1
spec:
storageClassName: nfs 存储类必须与当前定义的pv的存储类一致
accessModes:
- ReadWriteOnce 访问模式
resources:
requests:
storage: 1Gi 存储满足1G 当前我们是5G
接下来创建多个Pod挂载多个PV及pvc进行应用
首先把上面的文件pv.yml 改为pv1.yml ,方便观看实验效果
第一步在nfs数据输出端做以下操作
[root@server1 ~]# mkdir /nfsdata2
[root@server1 ~]# mkdir /nfsdata3
[root@server1 ~]# vim /etc/exports
/nfsdata *(rw,sync) 读写
/nfsdata2 *(rw,sync) 读写
/nfsdata3 *(ro,) 只读
[root@server1 ~]# ll -d /nfsdata3 只读
drwxr-xr-x 2 root root 6 Jul 5 00:12 /nfsdata3
[root@server1 ~]# chmod 777 /nfsdata2
[root@server1 ~]# exportfs -rv 刷新
exporting *:/nfsdata3
exporting *:/nfsdata2
exporting *:/nfsdata
[root@server1 ~]# showmount -e 挂载
Export list for server1:
/nfsdata3 *
/nfsdata2 *
/nfsdata *
三种模式同时应用
ReadWriteOnce -- 该volume只能被单个节点以读写的方式映射
ReadOnlyMany -- 该volume可以被多个节点以只读方式映射
ReadWriteMany -- 该volume可以被多个节点以读写的方式映射
在命令行中,访问模式可以简写为:
RWO - ReadWriteOnce
ROX - ReadOnlyMany
RWX - ReadWriteMany
[root@server2 pv]# cat pv1.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv1
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Recycle
storageClassName: nfs
nfs:
path: /nfsdata
server: 172.25.254.31
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc1
spec:
storageClassName: nfs
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
---
apiVersion: v1
kind: Pod
metadata:
name: test-pd-1
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- mountPath: /usr/share/nginx/html
name: pv1
volumes:
- name: pv1
persistentVolumeClaim:
claimName: pvc1
[root@server2 pv]# cat pv2.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv2
spec:
capacity:
storage: 10Gi
volumeMode: Filesystem
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Recycle
storageClassName: nfs
nfs:
path: /nfsdata2
server: 172.25.254.31
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc2
spec:
storageClassName: nfs
accessModes:
- ReadWriteMany
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Pod
metadata:
name: test-pd-2
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- mountPath: /usr/share/nginx/html
name: pv2
volumes:
- name: pv2
persistentVolumeClaim:
claimName: pvc2
[root@server2 pv]# cat pv3.yml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv3
spec:
capacity:
storage: 15Gi
volumeMode: Filesystem
accessModes:
- ReadOnlyMany
persistentVolumeReclaimPolicy: Recycle
storageClassName: nfs
nfs:
path: /nfsdata3
server: 172.25.254.31
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc3
spec:
storageClassName: nfs
accessModes:
- ReadOnlyMany
resources:
requests:
storage: 10Gi
---
apiVersion: v1
kind: Pod
metadata:
name: test-pd-3
spec:
containers:
- image: nginx
name: nginx
volumeMounts:
- mountPath: /usr/share/nginx/html
name: pv3
volumes:
- name: pv3
persistentVolumeClaim:
claimName: pvc3
[root@server2 pv]# kubectl apply -f .
persistentvolume/pv1 created
persistentvolumeclaim/pvc1 created
pod/test-pd-1 created
persistentvolume/pv2 created
persistentvolumeclaim/pvc2 created
pod/test-pd-2 created
persistentvolume/pv3 created
persistentvolumeclaim/pvc3 created
pod/test-pd-3 created
[root@server2 pv]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test-pd-1 1/1 Running 0 2m27s 10.244.1.10 server3
test-pd-2 1/1 Running 0 2m27s 10.244.1.11 server3
test-pd-3 1/1 Running 0 2m27s 10.244.2.7 server4
测试访问
[root@server2 pv]# curl 10.244.1.10
www.westos.org
在nfs数据输出端更改文件内容
[root@server1 ~]# cd /nfsdata
[root@server1 nfsdata]# ls
index.html
www.westos.org
[root@server1 nfsdata]# vim index.html
[root@server1 nfsdata]# cat index.html
www.westos.org
www.westos.org
www.westos.org
www.westos.org
www.westos.org
www.westos.org
测试
[root@server2 pv]# curl 10.244.1.10
www.westos.org
www.westos.org
www.westos.org
www.westos.org
www.westos.org
www.westos.org
指定进入test-pd-1容器内部更改文件
[root@server2 pv]# kubectl exec -it test-pd-1 -- bash
root@test-pd-1:/# cd /usr/share/nginx/html/
root@test-pd-1:/usr/share/nginx/html# ls
index.html 这个文件是在nfs数据输出端超级用户创建的在容器内是不可以更改的
root@test-pd-1:/usr/share/nginx/html# echo www.qq.com > index.html
bash: index.html: Permission denied
root@test-pd-1:/usr/share/nginx/html# echo www.qq.com > test.html
只能新建
root@test-pd-1:/usr/share/nginx/html# ls -l *
-rw-r--r-- 1 root root 90 Jul 4 16:23 index.html
-rw-r--r-- 1 nobody nogroup 11 Jul 4 16:30 test.html
[root@server2 pv]# curl 10.244.1.10
www.westos.org
www.westos.org
www.westos.org
www.westos.org
www.westos.org
www.westos.org
[root@server2 pv]# curl 10.244.1.10/test.html
www.qq.com
指定进入test-pd-2容器内部更改文件
[root@server2 pv]# kubectl exec -it test-pd-2 -- bash
root@test-pd-2:/# cd /usr/share/nginx/html/
root@test-pd-2:/usr/share/nginx/html# echo www.baidu.com > index.html
root@test-pd-2:/usr/share/nginx/html# ls
index.html
root@test-pd-2:/usr/share/nginx/html# cat index.html
www.baidu.com
[root@server1 ~]# cd /nfsdata2
[root@server1 nfsdata2]# ls
index.html
[root@server1 nfsdata2]# cat index.html
www.baidu.com
[root@server2 pv]# curl 10.244.1.11
www.baidu.com
指定进入test-pd-3容器内部更改文件
[root@server2 pv]# kubectl exec -it test-pd-3 -- bash
root@test-pd-3:/# cd /usr/share/nginx/html/
root@test-pd-3:/usr/share/nginx/html# ls
root@test-pd-3:/usr/share/nginx/html# echo www.taobao.com > index.html
bash: index.html: Read-only file system
没有写的权限,nfsdata3目录只能读。容器内不可以更改
外部可以更改
[root@server1 nfsdata2]# cd /nfsdata3
[root@server1 nfsdata3]# echo www.pinduoduo.com > index.html
[root@server1 nfsdata3]# ls
index.html
[root@server1 nfsdata3]# cat index.html
www.pinduoduo.com
[root@server2 pv]# curl 10.244.2.7 成功访问
www.pinduoduo.com