一、Volume概述
Volume 是 Pod 中能够被多个容器访问的共享目录。Kubernetes 的 Volume 定义在 Pod 上, 它被一个 Pod 中的多个容器挂载到具体的文件目录下。Volume 与 Pod 的生命周期相同, 但与容器的生命周期不相关,当容器终止或重启时,Volume 中的数据也不会丢失。要使用volume,pod 需要指定 volume 的类型和内容( 字段),和映射到容器的位置( 字段)。
Kubernetes 支持多种类型的 Volume,包括:emptyDir、hostPath、gcePersistentDisk、awsElasticBlockStore、nfs、iscsi、flocker、glusterfs、rbd、cephfs、gitRepo、secret、persistentVolumeClaim、downwardAPI、azureFileVolume、azureDisk、vsphereVolume、Quobyte、PortworxVolume、ScaleIO。
我们知道,Pod是由容器组成的,而容器宕机或停止之后,数据就随之丢了,那么这也就意味着我们在做Kubernetes集群的时候就不得不考虑存储的问题,而存储卷就是为了Pod保存数据而生的。存储卷的类型有很多,我们常用到一般有四种:emptyDir,hostPath,NFS以及云存储(ceph, glasterfs...)等。
二、EmptyDir
emptyDir类型的volume在pod分配到node上时被创建,kubernetes会在node上自动分配一个目录,因此无需指定宿主机node上对应的目录文件。这个目录的初始内容为空,一旦这个 pod 离开了这个宿主机,EmptyDir 中的数据就会被永久删除。所以目前 EmptyDir 类型的 volume 主要用作临时空间,比如 Web 服务器写日志或者 tmp 文件需要的临时目录。
[root@kubernetes-master-001 ~]# vi emptydir.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
name: test-volume-deployment
namespace: default
labels:
app: test-volume-deployment
spec:
selector:
matchLabels:
app: test-volume-pod
template:
metadata:
labels:
app: test-volume-pod
spec:
containers:
- name: nginx
image: busybox
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: http
- containerPort: 443
name: https
volumeMounts:
- mountPath: /data/
name: empty
command: ['/bin/sh','-c','while true;do echo $(date) >> /data/index.html;sleep 2;done']
- name: os
imagePullPolicy: IfNotPresent
image: busybox
volumeMounts:
- mountPath: /data/
name: empty
command: ['/bin/sh','-c','while true;do echo "budybox" >> /data/index.html;sleep 2;done']
volumes:
- name: empty
emptyDir: {}
三、HostPath
hostPath类型则是映射node文件系统中的文件或者目录到pod里。在使用hostPath类型的存储卷时,也可以设置type字段,支持的类型有文件、目录、File、Socket、CharDevice和BlockDevice。
HostPath 属性的 volume 使得对应的容器能够访问当前宿主机上的指定目录。例如,需要运行一个访问 Docker 系统目录的容器,那么就使用/var/lib/docker 目录作为一个HostDir 类型的 volume;或者要在一个容器内部运行 CAdvisor,那么就使用/dev/cgroups 目录作为一个 HostDir 类型的 volume。一旦这个 pod 离开了这个宿主机,HostDir 中的数据虽然不会被永久删除,但数据也不会随 pod 迁移到其他宿主机上。因此,需要注意的是, 由于各个宿主机上的文件系统结构和内容并不一定完全相同,所以相同 pod 的 HostDir 可能会在不同的宿主机上表现出不同的行为。
#1.编写yaml文件
[root@kubernetes-master-001 ~]# vi hostpath.yaml
apiVersion: v1
kind: Pod
metadata:
name: vol-hostpath
namespace: default
spec:
# 指定所提供的存储卷
volumes:
- name: html
hostPath:
path: /data/pod/volume1/
type: DirectoryOrCreate
containers:
- name: myapp
image: nginx
# 指定所提供的存储卷
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html/
#2.部署应用
[root@kubernetes-master-001 ~]# kubectl apply -f hostpath.yaml
pod/vol-hostpath created
#3.验证
[root@kubernetes-node-002 ~]# ll /data/pod/volume1/
total 0
四、NFS持久化存储
NFS使得我们可以挂载已经存在的共享到我们的Pod中,和emptyDir不同的是,当Pod被删除时,emptyDir也会被删除。但是nfs不会被删除,仅仅是解除挂在状态而已,这就意味着NFS能够允许我们提前对数据进行处理,而且这些数据可以在Pod之间相互传递,并且nfs可以同时被多个pod挂在并进行读写。
1.所有节点上安装NFS
yum install nfs-utils.x86_64 -y
2.配置NFS
[root@kubernetes-master-001 nfs]# mkdir -p /nfs/v{1..5}
[root@kubernetes-master-001 nfs]# cat > /etc/exports <<EOF
/nfs/v1 172.16.0.0/16(rw,no_root_squash)
/nfs/v2 172.16.0.0/16(rw,no_root_squash)
/nfs/v3 172.16.0.0/16(rw,no_root_squash)
/nfs/v4 172.16.0.0/16(rw,no_root_squash)
/nfs/v5 172.16.0.0/16(rw,no_root_squash)
EOF
[root@kubernetes-master-001 nfs]# exportfs -arv
exporting 172.16.0.0/16:/nfs/v5
exporting 172.16.0.0/16:/nfs/v4
exporting 172.16.0.0/16:/nfs/v3
exporting 172.16.0.0/16:/nfs/v2
exporting 172.16.0.0/16:/nfs/v1
[root@kubernetes-master-001 nfs]# showmount -e
Export list for kubernetes-master-001:
/nfs/v5 172.16.0.0/16
/nfs/v4 172.16.0.0/16
/nfs/v3 172.16.0.0/16
/nfs/v2 172.16.0.0/16
/nfs/v1 172.16.0.0/16
3.创建POD使用Nfs
[root@kubernetes-master-001 ~]# vi nfs.yaml
kind: Deployment
apiVersion: apps/v1
metadata:
name: nfs
spec:
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- mountPath: /usr/share/nginx/html/
name: nfs
volumes:
- name: nfs
nfs:
path: /nfs/v1
server: 192.168.12.71
[root@kubernetes-master-01 ~]# kubectl get pods -l app=nfs
NAME READY STATUS RESTARTS AGE
nfs-5f56db5995-9shkg 1/1 Running 0 24s
nfs-5f56db5995-ht7ww 1/1 Running 0 24s
[root@kubernetes-master-01 ~]# echo "index" > /nfs/v1/index.html
[root@kubernetes-master-01 ~]# kubectl exec -it nfs-5f56db5995-ht7ww -- bash
root@nfs-5f56db5995-ht7ww:/# cd /usr/share/nginx/html/
root@nfs-5f56db5995-ht7ww:/usr/share/nginx/html# ls
index.html
五、PV和PVC
1.基本概念
管理存储是管理计算的一个明显问题。该 PersistentVolume 子系统为用户和管理员提供了一个 API,用于抽象如何根据消费方式提供存储的详细信息。为此,我们引入了两个新的API 资源:PersistentVolume 和 PersistentVolumeClaim
PersistentVolume(PV)是集群中由管理员配置的一段网络存储。 它是集群中的资源,就像节点是集群资源一样。 PV 是容量插件,如 Volumes,但其生命周期独立于使用 PV 的任何单个 pod。 此 API 对象捕获存储实现的详细信息,包括 NFS,iSCSI 或特定于云提供程序的存储系统。
PersistentVolumeClaim(PVC)是由用户进行存储的请求。 它类似于 pod。 Pod 消耗节点资源,PVC 消耗 PV 资源。Pod 可以请求特定级别的资源(CPU 和内存)。声明可以请求特定的大小和访问模式(例如,可以一次读/写或多次只读)。
虽然 PersistentVolumeClaims 允许用户使用抽象存储资源,但是 PersistentVolumes 对于不同的问题,用户通常需要具有不同属性(例如性能)。群集管理员需要能够提供各种PersistentVolumes 不同的方式,而不仅仅是大小和访问模式,而不会让用户了解这些卷的实现方式。对于这些需求,有 StorageClass 资源。
StorageClass 为管理员提供了一种描述他们提供的存储的“类”的方法。 不同的类可能映射到服务质量级别,或备份策略,或者由群集管理员确定的任意策略。 Kubernetes 本身对于什么类别代表是不言而喻的。 这个概念有时在其他存储系统中称为“配置文件”。
PVC 和 PV 是一一对应的。
2.生命周期
PV 是群集中的资源。PVC 是对这些资源的请求,并且还充当对资源的检查。PV 和 PVC 之间的相互作用遵循以下生命周期:
Provisioning ——-> Binding ——–>Using——>Releasing——>Recycling
#1.供应准备 Provisioning 通过集群外的存储系统或者云平台来提供存储持久化支持。
- 静态提供 Static:集群管理员创建多个 PV。 它们携带可供集群用户使用的真实存储的详细信息。 它们存在于 Kubernetes API 中,可用于消费
- 动态提供 Dynamic:当管理员创建的静态 PV 都不匹配用户的 PersistentVolumeClaim 时,集群可能会尝试为 PVC 动态配置卷。 此配置基于 StorageClasses:
PVC 必须请求一个类,并且管理员必须已创建并配置该类才能进行动态配置。 要求该类的声明有效地为自己禁用动态配置。
#2.绑定 Binding : 用户创建 pvc 并指定需要的资源和访问模式。在找到可用 pv 之前,pvc 会保持未绑定状态。
#3.使用 Using : 用户可在 pod 中像 volume 一样使用 pvc。
#4.释放 Releasing : 用户删除 pvc 来回收存储资源,pv 将变成“released”状态。由于还保留着之前的数据,这些数据需要根据不同的策略来处理,否则这些存储资源无法被其他pvc 使用。
#5.回收 Recycling : pv可以设置三种回收策略:保留(Retain),回收(Recycle)和删除(Delete)。
- 保留策略:允许人工处理保留的数据。
- 删除策略:将删除 pv 和外部关联的存储资源,需要插件支持。
- 回收策略:将执行清除操作,之后可以被新的 pvc 使用,需要插件支持
3.PV的访问模式(accessModes)
模式 |
解释 |
ReadWriteOnce(RWO) |
可读可写,但只支持被单个节点挂载。 |
ReadOnlyMany(ROX) |
只读,可以被多个节点挂载。 |
ReadWriteMany(RWX) |
多路可读可写。这种存储可以以读写的方式被多个节点共享。不是每一种存储都支持这三种方式,像共享方式,目前支持的还比较少,比较常用的是 NFS。在 PVC 绑定 PV 时通常根据两个条件来绑定,一个是存储的大小,另一个就是访问模式。 |
4.PV 的回收策略(persistentVolumeReclaimPolicy)
策略 |
解释 |
Retain |
不清理, 保留 Volume(需要手动清理) |
Recycle |
删除数据,即 rm -rf /thevolume/*(只有 NFS 和 HostPath 支持) |
Delete |
删除存储资源,比如删除 AWS EBS 卷(只有 AWS EBS, GCE PD, Azure Disk 和 Cinder 支持) |
5.PV的状态
状态 |
解释 |
Available |
可用。 |
Bound |
已经分配给 PVC。 |
Released |
PVC 解绑但还未执行回收策略。 |
Failed |
发生错误。 |
6.PV类型
GCEPersistentDisk
AWSElasticBlockStore AzureFile
AzureDisk
FC (Fibre Channel)
Flexvolume
Flocker
NFS
iSCSI
RBD (Ceph Block Device)
CephFS
Cinder (OpenStack block storage)
Glusterfs
VsphereVolume
Quobyte Volumes
HostPath (Single node testing only – local storage is not supported in any way and WILL NOT WORK in a multi-node cluster)
Portworx Volumes
ScaleIO Volumes
StorageOS
7.PV 卷阶段状态
Available – 资源尚未被 claim 使用
Bound – 卷已经被绑定到 claim 了
Released – claim 被删除,卷处于释放状态,但未被集群回收。
Failed – 卷自动回收失败
8.创建PV
#1.编写 yaml 文件,创建 5 个 pv,存储大小各不相同,是否可读也不相同
[root@kubernetes-master-001 ~]# vi pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv001
labels:
name: pv001
spec:
nfs:
path: /data/volumes/v1
server: nfs
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 2Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv002
labels:
name: pv002
spec:
nfs:
path: /data/volumes/v2
server: nfs
accessModes: ["ReadWriteOnce"]
capacity:
storage: 5Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv003
labels:
name: pv003
spec:
nfs:
path: /data/volumes/v3
server: nfs
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 20Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv004
labels:
name: pv004
spec:
nfs:
path: /data/volumes/v4
server: nfs
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 10Gi
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv005
labels:
name: pv005
spec:
nfs:
path: /data/volumes/v5
server: nfs
accessModes: ["ReadWriteMany","ReadWriteOnce"]
capacity:
storage: 15G
#2.创建PV
[root@kubernetes-master-001 ~]# kubectl apply -f pv.yaml
persistentvolume/pv001 created
persistentvolume/pv002 created
persistentvolume/pv003 created
persistentvolume/pv004 created
persistentvolume/pv005 created
#3.查询验证
[root@kubernetes-master-001 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 2Gi RWO,RWX Retain Available 3m12s
pv002 5Gi RWO Retain Available 3m11s
pv003 20Gi RWO,RWX Retain Available 3m11s
pv004 10Gi RWO,RWX Retain Available 3m11s
pv005 15G RWO,RWX Retain Available 3m10s
9.创建PVC
#1.编写 yaml 文件,创建一个 pvc,需要 6G 存储;所以不会匹配 pv001、pv002、pv003
[root@kubernetes-master-001 ~]# vi pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mypvc
namespace: default
spec:
accessModes: ["ReadWriteMany"]
resources:
requests:
storage: 6Gi
---
apiVersion: v1
kind: Pod
metadata:
name: vol-pvc
namespace: default
spec:
volumes:
- name: html
persistentVolumeClaim:
claimName: mypvc
containers:
- name: myapp
image: ikubernetes/myapp:v1
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html/
#2.创建PVC
[root@kubernetes-master-001 ~]# kubectl apply -f pvc.yaml
persistentvolumeclaim/mypvc created
pod/vol-pvc created
#3.查询验证
[root@kubernetes-master-001 ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
mypvc Bound pv004 10Gi RWO,RWX 91s
[root@kubernetes-master-001 ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv001 2Gi RWO,RWX Retain Available 20m
pv002 5Gi RWO Retain Available 20m
pv003 20Gi RWO,RWX Retain Available 20m
pv004 10Gi RWO,RWX Retain Bound default/mypvc 20m
pv005 15G RWO,RWX Retain Available 20m
[root@kubernetes-master-001 ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
vol-pvc 1/1 running 0 11m
[root@kubernetes-master-001 ~]# curl 10.244.2.117
<h1>NFS stor 04</h1>
六、StorageClass
在一个大规模的Kubernetes集群里,可能有成千上万个PVC,这就意味着运维人员必须实现创建出这个多个PV,此外,随着项目的需要,会有新的PVC不断被提交,那么运维人员就需要不断的添加新的,满足要求的PV,否则新的Pod就会因为PVC绑定不到PV而导致创建失败。而且通过 PVC 请求到一定的存储空间也很有可能不足以满足应用对于存储设备的各种需求,而且不同的应用程序对于存储性能的要求可能也不尽相同,比如读写速度、并发性能等,为了解决这一问题,Kubernetes 又为我们引入了一个新的资源对象:StorageClass,通过 StorageClass 的定义,管理员可以将存储资源定义为某种类型的资源,比如快速存储、慢速存储等,kubernetes根据 StorageClass 的描述就可以非常直观的知道各种存储资源的具体特性了,这样就可以根据应用的特性去申请合适的存储资源了。
1.定义StorageClass
每一个存储类都包含provisioner、parameters和reclaimPolicy这三个参数域,当一个属于某个类的PersistentVolume需要被动态提供时,将会使用上述的参数域。
# 下载helm
wget https://get.helm.sh/helm-v3.3.4-linux-amd64.tar.gz
# 解压
tar xf helm-v3.3.4-linux-amd64.tar.gz
# 安装
mv linux-amd64/helm /usr/local/bin/
# 验证
helm version
# 添加阿里云镜像仓库
helm repo add ali-stable https://kubernetes.oss-cn-hangzhou.aliyuncs.com/charts
# 添加官方仓库
helm repo add stable https://kubernetes-charts.storage.googleapis.com/
# 添加微软云helm仓库
helm repo add azure http://mirror.azure.cn/kubernetes/charts/
# 下载nfs
helm pull stable/nfs-client-provisioner
# 解压安装包
tar xf nfs-client-provisioner-1.2.11.tgz && cd nfs-client-provisioner/
# 修改配置文件values.yaml
# 安装nfs client
helm install nfs ./
# 运行案例
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
namespace: default
name: test-nfs
labels:
app: test-nfs
spec:
accessModes:
- ReadWriteMany
storageClassName: nfs-client
resources:
requests:
storage: 8Gi
---
kind: Deployment
apiVersion: apps/v1
metadata:
name: test-nfs-storageclass
namespace: default
labels:
app: test-nfs
spec:
selector:
matchLabels:
app: test-nfs
template:
metadata:
labels:
app: test-nfs
spec:
containers:
- name: nginx
image: nginx
imagePullPolicy: IfNotPresent
volumeMounts:
- mountPath: /usr/share/nginx/html
name: test-nfs
volumes:
- name: test-nfs
persistentVolumeClaim:
claimName: test-nfs
# 测试
[root@kubernetes-master-001 helm]# vim test.yaml
[root@kubernetes-master-001 helm]# kubectl apply -f test.yaml
persistentvolumeclaim/test-nfs created
deployment.apps/test-nfs-storageclass created
[root@kubernetes-master-001 helm]# kubectl get pvc,pv
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/test-nfs Bound pvc-cf0c8175-f145-438a-ba5c-35effc080231 8Gi RWX nfs-client 7s
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-cf0c8175-f145-438a-ba5c-35effc080231 8Gi RWX Delete Bound default/test-nfs nfs-client 6s
[root@kubernetes-master-001 helm]# ll /nfs/v1
total 4
drwxrwxrwx 2 root root 4096 Oct 16 09:56 default-test-nfs-pvc-cf0c8175-f145-438a-ba5c-35effc080231
# 编写一个index.html测试网络存储。
[root@kubernetes-master-001 helm]# cd /nfs/v1/default-test-nfs-pvc-cf0c8175-f145-438a-ba5c-35effc080231/
[root@kubernetes-master-001 default-test-nfs-pvc-cf0c8175-f145-438a-ba5c-35effc080231]# echo "index" > index.html
[root@kubernetes-master-001 default-test-nfs-pvc-cf0c8175-f145-438a-ba5c-35effc080231]# kubectl get pods -l app=test-nfs
NAME READY STATUS RESTARTS AGE
test-nfs-storageclass-68c8996847-lsxwn 1/1 Running 0 75s
[root@kubernetes-master-001 default-test-nfs-pvc-cf0c8175-f145-438a-ba5c-35effc080231]# kubectl get pods -l app=test-nfs -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
test-nfs-storageclass-68c8996847-lsxwn 1/1 Running 0 80s 10.244.0.12 instance-gvpb80ao <none> <none>
[root@kubernetes-master-001 default-test-nfs-pvc-cf0c8175-f145-438a-ba5c-35effc080231]# curl 10.244.0.12
index