PV、PVC 以及 StorageClass

最早期 Pod 使用 Volume 的写法:

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: test-pod
  5. spec:
  6. containers:
  7. - image: ...
  8. name: test-pod
  9. volumeMounts:
  10. - mountPath: /data
  11. name: data
  12. volumes:
  13. - name: data
  14. capacity:
  15. storage: 10Gi
  16. cephfs:
  17. monitors:
  18. - 172.16.0.1:6789
  19. - 172.16.0.2:6789
  20. - 172.16.0.3:6789
  21. path: /opt/eshop_dir/eshop
  22. user: admin
  23. secretRef:
  24. name: ceph-secret

存在两个问题:

1、Pod 声明与底层存储耦合在一起,每次声明 volume 都需要配置存储类型以及该存储插件的一堆配置,如果是第三方存储,配置会非常复杂。 2、开发人员的需求可能只是需要一个 20GB 的卷,这种方式却不得不强制要求开发人员了解底层存储类型和配置。

每次声明 Pod 都需要配置 Ceph 集群的 mon 地址以及 secret,特别麻烦。
于是引入了 PV (Persistent Volume),PV 其实就是把 Volume 的配置声明部分从 Pod 中分离出来:

  1. apiVersion: v1
  2. kind: PersistentVolume
  3. metadata:
  4. name: cephfs
  5. spec:
  6. capacity:
  7. storage: 10Gi
  8. accessModes:
  9. - ReadWriteMany
  10. cephfs:
  11. monitors:
  12. - 172.16.0.1:6789
  13. - 172.16.0.2:6789
  14. - 172.16.0.3:6789
  15. path: /opt/eshop_dir/eshop
  16. user: admin
  17. secretRef:
  18. name: ceph-secret

存储系统通常由运维人员管理,开发人员并不知道底层存储配置,也就很难去定义好 PV
为了解决这个问题,引入了 PVC (Persistent Volume Claim),声明与消费分离,开发与运维责任分离。

  1. kind: PersistentVolumeClaim
  2. apiVersion: v1
  3. metadata:
  4. name: cephfs
  5. spec:
  6. accessModes:
  7. - ReadWriteMany
  8. resources:
  9. requests:
  10. storage: 8Gi

运维人员需要维护一堆 PV 列表和配置,如果 PV 不够用需要手动创建新的 PV,PV 空闲了还需要手动去回收,管理效率太低了。
引入了 StorageClass,StorageClass 类似声明了一个非常大的存储池,其中一个最重要的参数是 provisioner,我们熟悉的 OpenStack Cinder、Ceph、AWS EBS 等都是 provisioner。

  1. kind: StorageClass
  2. apiVersion: storage.k8s.io/v1
  3. metadata:
  4. name: aws-gp2
  5. provisioner: kubernetes.io/aws-ebs
  6. parameters:
  7. type: gp2
  8. fsType: ext4

运维人员只需要声明好 StorageClass 以及 Quota 配额,无需维护 PV。

Kubernetes 存储方案发展过程概述

Kubernetes 存储最开始是通过 Volume Plugin 实现集成外部存储系统,即不同的存储系统对应不同的 volume plugin,实现代码全都放在了 Kubernetes 主干代码中(in-tree),也就是说这些插件与核心 Kubernetes 二进制文件一起链接、编译、构建和发布

这种方案至少存在如下几个问题:

1、在 Kubernetes 中添加新存储系统支持需要在核心 Kubernetes 增加插件代码,随着存储插件越来越多,Kubernetes 代码也会变得越来越庞大。 2、Kubernetes 与具体的存储 plugin 耦合在一起,一旦存储接口发生任何变化都需要重新修改 plugin 代码,也就是说不得不修改 Kubernetes 代码,这会导致 Kubernetes 代码维护越来越困难。 3、如果 plugin 有 bug 或者存储系统故障导致 crash,可能导致整个 Kubernetes 集群整体 crash。 4、这些插件运行时无法做权限管控,具有 Kubernetes 所有组件的所有权限,存在一定的安全风险。 5、插件的实现必须通过 Golang 语言编写并与 Kubernetes 一起开源,可能对一些厂商不利。

从 1.8 开始,Kubernetes 停止往 Kubernetes 代码中增加新的存储支持, 并推出了一种新的插件形式支持外部存储系统,即 FlexVolume,不过 FlexVolume 其实在 1.2 就提出了

这样每个存储插件只需要通过外部脚本 (out-of-tree) 实现 attach、detach、mount、umount 等接口即可集成第三方存储,不需要动 Kubernetes 源码,可以参考官方的一个 LVM FlexVolume Demo[1]。

但是这种方法也有问题:

脚本文件放在 host 主机上,因此驱动不得不通过访问宿主机的根文件系统去运行脚本。

这些插件如果还有第三方程序依赖或者 OS 兼容性要求,还需要在所有的 Node 节点安装这些依赖并解决兼容问题。

因此 Kubernetes 从 1.9 开始又引入了 Container Storage Interface (CSI) 容器存储接口,并于 1.13 版本正式 GA。
CSI 的实现方案和 CRI 类似通过 gRPC 与 volume driver 进行通信,存储厂商需要实现三个服务接口 Identity Service、Controller Service、Node Service

为什么需要云原生分布式存储

目前 Kubernetes 已经能够支持非常多的外部存储系统了,如 NFS、GlusterFS、Ceph、OpenStack Cinder 等,这些存储系统目前主流的部署方式还是运行在 Kubernetes 集群之外单独部署和维护,这不符合 All In Kubernetes 的原则

这主要有两种实现思路:

第一种思路就是重新针对云原生平台设计一个分布式存储,这个分布式存储系统组件是微服务化的,能够复用 Kubernetes 的调度、故障恢复和编排等能力,如后面要介绍的 Longhorn、OpenEBS。

另一种思路就是设计微服务组件把已有的分布式存储系统包装管理起来,使原来的分布式存储可以适配运行在 Kubernetes 平台上,实现通过 Kubernetes 管理原有的分布式存储系统,如后面要介绍的 Rook。

容器存储的未来

组成云计算的三大基石为计算、存储和网络,Kubernetes 计算 (Runtime)、存储(PV/PVC) 和网络 (Subnet/DNS/Service/Ingress) 的设计都是开放的,可以集成不同的方案,比如网络通过 CNI 接口支持集成 Flannel、Calico 等网络方案,运行时 (Runtime) 通过 CRI 支持 Docker、Rkt、Kata 等运行时方案,存储通过 volume plugin 支持集成如 AWS EBS、Ceph、OpenStack Cinder 等存储系统

存储与计算、网络稍有不同,计算和网络都是以微服务的形式通过 Kubernetes 统一编排管理的,即 Kubernetes 既是计算和网络的消费者,同时也是计算和网络的编排者和管理者。而存储则不一样,虽然 Kubernetes 已经设计了 PV/PVC 机制来管理外部存储,但只是弄了一个标准接口集成,存储本身还是通过独立的存储系统来管理,Kubernetes 根本不知道底层存储是如何编排和调度的。

于是社区提出了 Container Attached Storage (CAS) 理念,这个理念的目标就是利用 Kubernetes 来编排存储,从而实现我 Kubernetes 编排一切,这里的一切包括计算、存储、网络,当然更高一层的还包括应用、服务、软件等。

Longhorn

Longhorn简介

与其他分布式存储系统最大的不同点是,Longhorn 并没有设计一个非常复杂的控制器来管理海量的 volume 数据卷,而是将控制器拆分成一个个非常轻量级的微控制器,这些微控制器能够通过 Kubernetes、Mesos 等平台进行编排与调度。

Longhorn 的实现和 CAS 的设计理念基本是一致的,相比 Ceph 来说会简单很多,而又具备分布式块存储系统的一些基本功能:

  1. 支持多副本,不存在单点故障;
  2. 支持增量快照;
  3. 支持备份到其他外部存储系统中,比如 S3
  4. 精简配置 (thin provisioning);

Longhorn 存储管理机制比较简单,当在 Longhorn 中 Node 节点增加物理存储时,其本质就是把 Node 对应的路径通过 HostPath 挂载到 Pod 中

子目录中包含一些 volume 的 metadata 以及 img 文件,而 img 文件其实就是一个 raw 格式文件,raw 格式其实就是 Linux Sparse 稀疏文件,由于单个文件大小受文件系统和分区限制,因此 Longhorn volume 会受单个磁盘的大小和性能的限制。

Longhorn 部署

  1. # 需要提前在每个节点上安装open-iscsi
  2. $ apt-get install open-iscsi // ubuntu
  3. $ yum install iscsi-initiator-utils // centos
  4. $ kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml
  5. # 在kubernetes集群中使用Longhorn,需安装对应sc
  6. $ kubectl create -f
  7. https://raw.githubusercontent.com/longhorn/longhorn/master/examples/storageclass.yaml

让分布式存储简化管理的 Rook

Rook简介

最开始 Rook 项目仅仅专注于如何实现把 Ceph 运行在 Kubernetes 平台上。
当前的目标是将外部已有的分布式存储系统在云原生平台托管运行起来,借助云原生平台具有的自动化调度、故障恢复、弹性扩展等能力实现外部存储系统的自动管理、自动弹性扩展以及自动故障修复。

按照官方的说法,Rook 要把原来需要对分布式存储系统手动做的一些运维工作借助云原生平台能力 (如 Kubernetes) 实现自动化,这些运维工作包括部署、初始化、配置、扩展、升级、迁移、灾难恢复、监控以及资源管理等,这种自动化甚至不需要人去手动触发,而是云原生平台自动触发的,因此叫做 self-managing,真正实现 NoOpts。

比如集群增加一块磁盘,Rook 能自动初始化为一个 OSD,并自动加入到合适的故障域中,这个 OSD 在 Kubernetes 中是以 Pod 的形式运行的。

目前除了能支持编排管理 Ceph 集群,还支持:

  1. EdgeFS
  2. CockroachDB
  3. Cassandra
  4. NFS
  5. Yugabyte DB

不同的存储通过不同的 Operator 实现,但使用起来基本一致,Rook 屏蔽了底层存储系统的差异。

Rook 部署

安装部署 Rook 非常简单,以 Ceph 为例,只需要安装对应的 Operator 即可:

  1. git clone --single-branch --branch release-1.3
  2. https://github.com/rook/rook.git
  3. cd rook/cluster/examples/kubernetes/ceph
  4. kubectl create -f common.yaml
  5. kubectl create -f operator.yaml
  6. kubectl create -f cluster.yaml

Rook 默认还会安装 Ceph Dashboard,可以通过 Kubernetes Service rook-ceph-mgr-dashboard 进行访问,admin 的密码保存在 secret rook-ceph-dashboard-password 中,可通过如下命令获取:

  1. $ kubectl -n rook-ceph get secret rook-ceph-dashboard-password -o jsonpath="{['data']['password']}"|base64 -d

块存储

创建sc

  1. $ kubectl create -f
  2. cluster/examples/kubernetes/ceph/csi/rbd/storageclass.yaml

我们首先需要创建一个 Ceph Pool,当然我们可以通过 ceph osd pool create 命令手动创建,但这样体现不了 self-managing,我们应该屏蔽 Ceph 集群接口,直接使用 Kubernetes CRD 进行声明:

  1. # kubectl apply -f -
  2. apiVersion: ceph.rook.io/v1
  3. kind: CephBlockPool
  4. metadata:
  5. name: replicapool
  6. namespace: rook-ceph
  7. spec:
  8. failureDomain: host
  9. replicated:
  10. size: 3

新建一个卷

  1. # kubectl apply -f -
  2. ---
  3. apiVersion: v1
  4. kind: PersistentVolumeClaim
  5. metadata:
  6. name: test-ceph-blockstorage-pvc
  7. spec:
  8. storageClassName: rook-ceph-block
  9. accessModes:
  10. - ReadWriteOnce
  11. resources:
  12. requests:
  13. storage: 20Gi

本文参考:https://www.v2k8s.com/storage/t/209