Kubernetes中的本地存储意味着在每个节点服务器上本地可用的存储设备或文件系统。本文介绍Kubernetes中现有的本地存储解决方案,包括官方原生支持的存储方式以及社区的一些常见方案。
目前官方支持以下3种本地存储方式:
- emptyDir
- hostPath
- Local Persistent Volume
emptyDir
当 Pod 指定到某个节点上时,首先创建的是一个emptyDir
卷,并且只要 Pod 在该节点上运行,卷就一直存在。 就像它的名称表示的那样,卷最初是空的。 尽管 Pod 中的容器挂载emptyDir
卷的路径可能相同也可能不同,但是这些容器都可以读写emptyDir
卷中相同的文件。 当 Pod 因为某些原因被从节点上删除时,emptyDir
卷中的数据也会永久删除。
注意:容器崩溃并不会导致 Pod 被从节点上移除,因此容器崩溃时 `emptyDir` 卷中的数据是安全的。
emptyDir
的一些用途:
- 缓存空间,例如基于磁盘的归并排序。
- 为耗时较长的计算任务提供检查点,以便任务能方便地从崩溃前状态恢复执行。
- 在 Web 服务器容器服务数据时,保存内容管理器容器获取的文件。
默认情况下, emptyDir
卷存储在支持该节点所使用的介质上;这里的介质可以是磁盘或 SSD 或网络存储,这取决于您的环境。 但是,您可以将 emptyDir.medium
字段设置为 "Memory"
,以告诉 Kubernetes 为您安装 tmpfs(基于 RAM 的文件系统)。 虽然 tmpfs 速度非常快,但是要注意它与磁盘不同。 tmpfs 在节点重启时会被清除,并且您所写入的所有文件都会计入容器的内存消耗,受容器内存限制约束。
emptyDir的位置应该在运行pod的给定节点上的/var/lib/kubelet/pods/{podid}/volumes/kubernetes.io~empty-dir/
目录下。
Pod 示例
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- image: my-app-image
name: my-app
volumeMounts:
- mountPath: /cache
name: cache-volume
volumes:
- name: cache-volume
emptyDir: {}
Pod中的所有容器共享使用emptyDir卷。因此,我们使用emptyDir的一个场景是,Pod中init容器负责到配置中心或是秘钥管理中心拉取一些配置,然后写入到emptyDir中,这样业务容器就可以读取到配置。
hostPath
hostPath
卷能将主机节点文件系统上的文件或目录挂载到您的 Pod 中。 虽然这不是大多数 Pod 需要的,但是它为一些应用程序提供了强大的逃逸机会。
例如,hostPath
的一些用法有:
- 运行一个需要访问 Docker 引擎内部机制的容器;请使用
hostPath
挂载/var/lib/docker
路径。 - 在容器中运行 cAdvisor 时,以
hostPath
方式挂载/sys
。 - 允许 Pod 指定给定的
hostPath
在运行 Pod 之前是否应该存在,是否应该创建以及应该以什么方式存在。
除了必需的 path
属性之外,用户可以选择性地为 hostPath
卷指定 type
。
例如:
apiVersion: v1
kind: Pod
metadata:
name: my-pod
spec:
containers:
- image: my-app-image
name: my-app
volumeMounts:
- mountPath: /test-pd
name: test-volume
volumes:
- name: test-volume
hostPath:
path: /data #directory on host
type: Directory #optional
type: Directory
定义目录必须在主机上已经存在,因此在使用主机路径之前,您必须首先在该目录中手动创建它。
类型的其他值是DirectoryOrCreate
,File
,FileOrCreate
。如果* OrCreate在主机上尚不存在,将动态创建。
使用此卷类型的缺点:
- 由于不同节点的hostPath文件/目录内容不同,因此同一模板创建的Pod在不同节点上的行为可能有所不同
- 用hostPath在主机上创建的文件或目录只能由root写入。这意味着您要么需要以root用户身份运行容器进程,要么将主机上的文件权限修改为非root用户可写,这可能会导致安全问题
- 不应该将hostPath卷类型用于StatefulSets
Local Persistent Volume
Local PV(本地持久化存储),指的就是利用机器上的磁盘来存放业务需要持久化的数据,和远端存储类似,此时数据依然独立于 Pod 的生命周期,即使业务 Pod 被删除,数据也不会丢失。
同时,和远端存储相比,本地存储可以避免网络 IO 开销,拥有更高的读写性能,所以分布式文件系统和分布式数据库这类对 IO 要求很高的应用非常适合本地存储。
但是,在使用本地永久卷时,有一些重要的限制和注意事项需要考虑:
- 使用本地存储将您的应用程序绑定到特定节点,从而使您的应用程序难以调度。使用本地存储的应用程序应指定较高的优先级,以便在需要时可以抢占不需要本地存储的较低优先级的Pod。
- 如果该节点或本地卷遇到故障并变得不可访问,则该容器也将变得不可访问。要从这些情况中恢复过来,可能需要手动干预,外部控制器或操作员。
- 尽管大多数远程存储系统都实现了同步复制,但是大多数本地磁盘产品并不能提供数据持久性保证。意味着磁盘或节点丢失可能导致该磁盘上的所有数据丢失。
Local PV和hostPath区别
为了更好地了解本地持久卷的好处,将其与HostPath卷进行比较很有用。 HostPath卷将主机节点文件系统中的文件或目录装载到Pod中。同样,本地永久卷将本地磁盘或分区安装到Pod中。
最大的区别是Kubernetes调度程序了解本地持久卷属于哪个节点。对于HostPath卷,调度程序可能会将引用HostPath卷的pod移至其他节点,从而导致数据丢失。但是对于本地持久卷,Kubernetes调度程序可确保始终将使用本地持久卷的容器调度到同一节点。
尽管可以通过持久卷声明(PVC)引用HostPath卷,也可以在pod定义中直接内联HostPath卷,但是只能通过PVC引用本地持久卷。由于Persistent Volume对象是由管理员管理的,因此,Pod不能访问主机上的任何路径,因此可以提供额外的安全优势。
其他好处包括支持在安装期间格式化块设备以及使用fsGroup进行卷所有权。
如何使用Local PV?
工作负载可以使用与远程存储后端相同的PersistentVolumeClaim接口请求本地持久卷。这使得跨集群,云和本地环境轻松交换存储后端。
首先,应该创建一个StorageClass来设置volumeBindingMode:WaitForFirstConsumer
来启用卷拓扑感知调度。该模式指示Kubernetes等待绑定PVC,直到Pod开始使用它。
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer
然后,可以配置并运行外部静态预配器,以为节点上的所有本地磁盘创建PV。
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
local-pv-27c0f084 368Gi RWO Delete Available local-storage 8s
local-pv-3796b049 368Gi RWO Delete Available local-storage 7s
local-pv-3ddecaea 368Gi RWO Delete Available local-storage 7s
之后,通过创建带有volumeClaimTemplates的PVC和Pod或StatefulSet,工作负载可以开始使用PV。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: local-test
spec:
serviceName: "local-service"
replicas: 3
selector:
matchLabels:
app: local-test
template:
metadata:
labels:
app: local-test
spec:
containers:
- name: test-container
image: k8s.gcr.io/busybox
command:
- "/bin/sh"
args:
- "-c"
- "sleep 100000"
volumeMounts:
- name: local-vol
mountPath: /usr/test-pod
volumeClaimTemplates:
- metadata:
name: local-vol
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "local-storage"
resources:
requests:
storage: 368Gi
一旦StatefulSet启动并运行,PVC都将绑定:
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
local-vol-local-test-0 Bound local-pv-27c0f084 368Gi RWO local-storage 3m45s
local-vol-local-test-1 Bound local-pv-3ddecaea 368Gi RWO local-storage 3m40s
local-vol-local-test-2 Bound local-pv-3796b049 368Gi RWO local-storage 3m36s
当不再需要磁盘时,可以删除PVC。外部静态预配器将清理磁盘,并使PV再次可用。
社区的一些方案:
- TopoLVM
TopoLVM
TopoLVM,一个用于本地存储的新CSI插件,可使用LVM动态配置卷。它实现了动态卷配置,原始块卷,文件系统指标,并将实现在线卷大小调整和临时卷。
架构
TopoLVM 组件包括:
-
topolvm-controller
: CSI controller -
topolvm-scheduler
: 用于TopoLVM 的scheduler extender -
topolvm-node
: CSI node 服务 -
lvmd
: 管理LVM卷的gRPC 服务
特性
- 动态配置:创建PersistentVolumeClaim对象时动态创建卷。
- 原始块体积:这些体积可用作容器内的块设备。
- 拓扑:TopoLVM使用CSI拓扑功能将Pod调度到LVM卷存在的节点。
- 扩展的调度程序:TopoLVM扩展了通用Pod调度程序,以对具有更大存储容量的节点进行优先级排序。
- 监控指标:使用情况统计信息从kubelet中导出为Prometheus指标。
由于不是k8s原生方案,所以在使用前,我们需要先安装。关于安装,大家参考官方文档即可。
示例:
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: topolvm-pvc
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
storageClassName: topolvm-provisioner
---
apiVersion: v1
kind: Pod
metadata:
name: my-pod
labels:
app.kubernetes.io/name: my-pod
spec:
containers:
- name: ubuntu
image: quay.io/cybozu/ubuntu:18.04
command: ["/usr/local/bin/pause"]
volumeMounts:
- mountPath: /test1
name: my-volume
volumes:
- name: my-volume
persistentVolumeClaim:
claimName: topolvm-pvc