pod容器共享Volume
同一个pod中的多个容器能够共享Pod级别的存储卷Volume。即定义一个卷,然后将该卷挂在为多个容器的内部目录。
如下pod实例,pod-volume.yml内容如下:
apiVersion: v1
kind: Pod
metadata:
name: volume-pod
spec:
s:
- name: tomcat
image: tomcat
ports:
-Port: 8080
volumeMounts:
- name: app-logs #使用名称为app-logs的卷
mountPath: /usr/local/tomcat/logs #将卷挂载到该路径,这个路径是容器内的绝对路径
- name: busybox
image: busybox
command: ["sh", "-c", "tail -f /logs/catalinz*.log"]
volumeMounts:
- name: app-logs
mountPath: /logs
volumse: #定义pod中的公共volume
- name: app-logs #volume名称为app-logs
emptyDir: {} #
说明:pod中定义了名称为app-logs的卷,然后应用到多个pod中。emptyDir:k8s不显式声明宿主机目录的 Volume。所以,Kubernetes 也会在宿主机上创建一个临时目录,这个目录将来就会被绑定挂载到容器所声明的 Volume 目录上。此时并不关心卷定义在宿主机的哪个目录,主要是通过该目录,实现Pod中多个容器对卷的共享。
Kubernetes 也提供了显式的 Volume 定义,它叫作 hostPath。比如下面的这个 YAML 文件:
...
volumes:
- name: nginx-vol
hostPath:
path: " /var/data"Kubernetes 中,有几种特殊的 Volume:
存在的意义不是为了存放容器里的数据,也不是用来进行容器和宿主机之间的数据交换。这些特殊 Volume 的作用,是为容器提供预先定义好的数据,现在支持四种:
Secret; pod中创建保密信息
ConfigMap; 获取一般配置
Downward API;让 Pod 里的容器能够直接获取到这个 Pod API 对象本身的信息。
ServiceAccountToken。
pv pvc storageClass
PVC 是 Pod 所希望使用的持久化存储的属性。
PV 是持久化存储数据卷。定义的是一个持久化存储在宿主机上的目录,比如一个 NFS 的挂载目录。pvc定义
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs
spec:
accessModes:
- ReadWriteMany
storageClassName: manual
resources:
requests:
storage: 1Gi
pv定义:
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs
spec:
storageClassName: manual
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
nfs:
server: 10.244.1.4
path: "/"
pod中使用pvc:
apiVersion: v1
kind: Pod
metadata:
labels:
role: web-frontend
spec:
s:
- name: web
image: nginx
ports:
- name: web
Port: 80
volumeMounts:
- name: nfs
mountPath: "/usr/share/nginx/html"
volumes:
- name: nfs
persistentVolumeClaim:
claimName: nfs #pvc名称
Pod创建之后,kubelet 就会把这个 PVC 所对应的 PV,也就是一个 NFS 类型的 Volume,挂载在这个 Pod 容器内的目录上。pv和pvc的设计思想:
pvc:持久化存储的描述,类似“接口”
pv:持久化存储的实现部分,类似”实现类“
好处:作为开发者,我们只需要跟 PVC 这个“接口”打交道,而不必关心具体的实现是 NFS 还是 Ceph。PVC 要真正被容器使用起来,就必须先和某个符合条件的 PV 进行绑定。
这里要检查的条件,包括两部分:
第一个条件,当然是 PV 和 PVC 的 spec 字段。比如,PV 的存储(storage)大小,就必须满足 PVC 的要求。
第二个条件,则是 PV 和 PVC 的 storageClassName 字段必须一样。这个机制我会在本篇文章的最后一部分专门介绍。如果系统里并没有合适的 PV 跟它定义的 PVC 绑定,那会发生什么?
Pod 的启动就会报错。
然后如果创建了符合条件的PV,Kubernetes 能够再次完成 PVC 和 PV 的绑定操作,从而启动 Pod。实现原理:k8s存在一个Volume Controller,查看当前每一个PVC,是不是已经处于 Bound(已绑定)状态。如果不是,那它就会遍历所有的、可用的 PV,并尝试将其与这个“单身”的 PVC 进行绑定。
PV 对象,又是如何变成容器里的一个持久化存储的呢?
容器的 Volume,其实就是将一个宿主机上的目录,跟一个容器里的目录绑定挂载在了一起。
“持久化 Volume”,指的就是这个宿主机上的目录,具备“持久性”,既不会因为容器的删除而被清理掉,也不会跟当前的宿主机绑定。这样,当容器被重启或者在其他节点上重建出来之后,它仍然能够通过挂载这个 Volume,访问到这些内容。
所以 hostPath 和 emptyDir 类型的 Volume不是持久化的。
持久化 Volume 的实现,往往依赖于一个远程存储服务,比如:远程文件存储(比如,NFS、GlusterFS)、远程块存储(比如,公有云提供的远程磁盘)等等。以远程块存储为实例看pv实现原理
1)kubelet为Pod在宿主机上创建Volume目录,路径:
/var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>
2)将 远程块存储 挂载到pod所在的宿主机
以Google Cloud 的 Persistent Disk为例,调用如下api:
$ gcloud compute instances attach-disk <虚拟机名字> --disk <远程磁盘名字>
3)格式化这个磁盘设备,然后将它挂载到宿主机指定的挂载点上,这个挂载点就是步骤一中volume的宿主机目录
4)kubelet 只要把这个 Volume 目录通过 CRI 里的 Mounts 参数,传递给 Docker;类似执行如下指令:
$ docker run -v /var/lib/kubelet/pods/<Pod的ID>/volumes/kubernetes.io~<Volume类型>/<Volume名字>:/<容器内的目标目录> 我的镜像 ... StorageClass
一个大规模的 Kubernetes 集群里很可能有成千上万个PVC,这就意味着运维人员必须得事先创建出成千上万个 PV。浪费资源,且难以维护。Kubernetes为我们提供了一套可以自动创建PV的机制,即:Dynamic Provisioning,核心,在于一个名叫 StorageClass 的 API 对象。
storageClass的对象定义包含两部分:
第一,PV 的属性。比如,存储类型、Volume 的大小等等。
第二,创建这种 PV 需要用到的存储插件。比如,Ceph 等等。
Kubernetes 就能够根据用户提交的 PVC,找到一个对应的 StorageClass 了。然后,Kubernetes 就会调用该 StorageClass 声明的存储插件,创建出需要的 PV。实例如下:sc.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: block-service
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-ssd
provisioner 字段的值是:kubernetes.io/gce-pd,这正是 Kubernetes 内置的 GCE PD 存储插件的名字。
parameters 字段,就是 PV 的参数。比如:上面例子里的 type=pd-ssd,指的是这个 PV 的类型是“SSD 格式的 GCE 远程磁盘”。创建StorageClass
$ kubectl create -f sc.yamlPVC 里指定要使用的 StorageClass 名字,pvc定义如下:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: claim1
spec:
accessModes:
- ReadWriteOnce
storageClassName: block-service
resources:
requests:
storage: 30Gi
Google Cloud 为例。当我们通过 kubectl create 创建上述 PVC 对象之后,Kubernetes 就会调用 Google Cloud 的 API,创建出一块 SSD 格式的 Persistent Disk。然后,再使用这个 Persistent Disk 的信息,自动创建出一个对应的 PV 对象。有了 Dynamic Provisioning 机制,运维人员只需要在 Kubernetes 集群里创建出数量有限的 StorageClass 对象就可以了。当开发人员提交了包含 StorageClass 字段的 PVC 之后,Kubernetes 就会根据这个 StorageClass 创建出对应的 PV。
pod pv pvc storageClass之间的关系如下图:
在这个体系中:
PVC 描述的,是 Pod 想要使用的持久化存储的属性,比如存储的大小、读写权限等。
PV 描述的,则是一个具体的 Volume 的属性,比如 Volume 的类型、挂载目录、远程存储服务器地址等。
而 StorageClass 的作用,则是充当 PV 的模板。并且,只有同属于一个 StorageClass 的 PV 和 PVC,才可以绑定在一起。还有是指定 PV 的 Provisioner(存储插件)。这时候,如果你的存储插件支持 Dynamic Provisioning 的话,Kubernetes 就可以自动为你创建 PV 了。 本地持久化存储
好处:Volume 直接使用的是本地磁盘,尤其是 SSD 盘,它的读写性能相比于大多数远程存储来说,要好得多。
Local Persistent Volume 并不适用于所有应用。事实上,它的适用范围非常固定,比如:高优先级的系统应用,需要在多个不同节点上存储数据,并且对 I/O 较为敏感。典型的应用包括:分布式数据存储比如 MongoDB、Cassandra 等,分布式文件系统比如 GlusterFS、Ceph 等,以及需要在本地磁盘上进行大量数据缓存的分布式应用。本地模拟过程:
1)在名叫 node-1 的宿主机上创建一个挂载点,比如 /mnt/disks;然后,用几个 RAM Disk 来模拟本地磁盘
# 在node-1上执行
$ mkdir /mnt/disks
$ for vol in vol1 vol2 vol3; do
mkdir /mnt/disks/$vol
mount -t tmpfs $vol /mnt/disks/$vol
done2)为这些本地磁盘定义对应的 PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: example-pv
spec:
capacity:
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Delete
storageClassName: local-storage
local:
path: /mnt/disks/vol1
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: kubernetes.io/hostname
operator: In
values:
- node-1
local 字段,指定了它是一个 Local Persistent Volume;
而 path 字段,指定的正是这个 PV 对应的本地磁盘的路径,即:/mnt/disks/vol1;Pod要想使用这个PV,那它就必须运行在 node-1 上。所以,在这个 PV 的定义里,需要有一个 nodeAffinity 字段指定 node-1 这个节点的名字。
调度器在调度 Pod 的时候,就能够知道一个 PV 与节点的对应关系,从而做出正确的选择3)创建pv
$ kubectl create -f local-pv.yaml
persistentvolume/example-pv created$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
example-pv 5Gi RWO Delete Available local-storage 16s4)创建一个 StorageClass 来描述这个 PV
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer说明:
provisioner 字段,我们指定的是 no-provisioner。这是因为 Local Persistent Volume 目前尚不支持 Dynamic Provisioning,
StorageClass 还定义了一个 volumeBindingMode=WaitForFirstConsumer 的属性。它是 Local Persistent Volume 里一个非常重要的特性,即:延迟绑定。因为按照一般流程: PV 和 PVC 的 YAML 文件之后,Kubernetes 就会根据它们俩的属性,以及它们指定的 StorageClass 来进行绑定。只有绑定成功后,Pod 才能通过声明这个 PVC 来使用对应的 PV。
但 Local Persistent Volume 的话,就会发现,这个流程根本行不通,实例如下:
pod只能运行在node2上,pod使用pvc名称是pvc-1。
第一个pv名称是pv-1,在node1上;第二个pv名称是pv-2,在node2上。且pv-1和pv-2都符合pvc-1的要求;
如果提前绑定,则pvc-1可以和pv-1绑定;
那使用pvc-1的pod只能裕兴在node1上了。
最终不符合我们要求。解决方案就是延迟绑定:即被声明了WaitForFirstConsumer的pv等到实际需要的时候再和pvc绑定,按照上面的实例即:pod被创建到了node2上,此时该pod需要使用pvc-1,pvc-1再和node2上的pv-2绑定.
k8s支持编写自己的存储插件
FlexVolume 和 CSI