乐趣区

Kubernetes本地持久化方案

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定义目录必须在主机上已经存在,因此在使用主机路径之前,您必须首先在该目录中手动创建它。

类型的其他值是DirectoryOrCreateFileFileOrCreate。如果 * 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
退出移动版