Kubernetes集群实践(七)Kubernetes安装云原生存储系统LongHorn

本文主要介绍如何安装和使用云原生存储系统LongHorn。

关键词:k8s

前言

K8s存储最开始是通过Volume Plugin实现集成外部存储系统,即不同的存储系统对应不同的volume plugin。

Volume Plugin实现代码全部放在了K8s主干代码中,也就是说这些查看与核心K8s二进制文件一起链接、编译、构建和发布。

这种方案存在以下几个问题:

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

因此从1.8开始,K8s停止往K8s代码中增加新的存储支持,并推出了一种新的插件形式支持外部存储系统,即FlexVolume

FlexVolume类似于CNI插件,通过外部脚本集成外部存储接口。这些脚本默认放在/usr/libexec/kubernetes/kubelet-plugins/volume/exec/,需要安装到所有Node节点上。

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

但是这种方法也有问题:

  • 脚本文件放在host主机上,因此驱动不得不通过访问宿主机的根文件系统去运行脚本。
  • 这些插件还有第三方程序以来或者OS兼容性要求,还需要在所有的Node节点安装这些依赖并解决兼容性问题。

因此这种方式虽然解决了in-tree的问题,但是这种方式用起来不太优雅,不太原生。

因此Kubernetes从1.9开始就引入了Container Storage Interface(CSI)容器存储接口,并与1.13版本正式GA。

CSI的实现方案和CRI类似,通过gRPC与volume driver进行通信,存储厂商需要实现三个服务接口Identify Service、Control Service、Node Service:

  • Indentfy Service用于返回一些插件信息;
  • Control Service实现Volume的CURD操作;
  • Node Service运行在所有的Node节点,用于实现把volume挂载在当前Node节点的指定目录,该服务会监听一个Socket,controller通过这个socket进行通信,可以参考官方提供的例子:CSI Hostpath driver Sample[2]

更多CSI介绍可以参考官方的设计文档,CSI Volume Plugins in Kubernetes Design Doc[3]

通过CSI基本解决了in-tree以及FlexVolume的大多数问题,未来K8s会把in-tree的存储插件都迁移到CSI。

当然Flex Volume Plugin也会与新的CSI Volume Plugin并存以便兼容现有的第三方FlexVolume存储插件。

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

通过CSI接口或者Flex Volume Plugin解决了K8s集成外部存储的问题。

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

如何没有分布式存储,则不得不单独部署一套分布式存储。

很多分布式存储部署还是比较复杂的。而K8s天生就具有快速部署和编排应用的能力,如果能把分布式存储的部署也能通过K8s编排起来,则显然能够大大降低分布式存储的部署和维护成本,甚至使用一条apply目录就可以部署一个集群。

这里主要有两种思路:

  • 一是重新针对云原生平台设计一个分布式存储,这个分布式存储系统组件是为服务化的,能够复用K8s的调度、故障恢复和编排能力,如后面介绍的Longhorn、OpenEBS。
  • 二是设计微服务组件把已有的分布式存储系统包装管理起来,使原来的分布式存储能够运行在K8s平台上,实现通过K8s管理原有的分布式存储系统,如Rook

Container Attached Storage,容器存储的未来?

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

但是目前主流的方案中存储与计算、网络稍有不同,计算和网络都是以微服务的形式通过K8s统一编排管理的,即K8s既是计算和网络的消费者,也是计算和网络的编排者和管理者。

而存储不一样,虽然K8s已经设计了PV/PVC机制来管理外部存储,但是只是弄了个标准接口集成,存储本身还是通过独立的存储系统来管理,K8s是压根不知道底层存储是如何编排和调度的。

社区既然认为计算和网络都由K8s统一编排了,那存储也应该统一编排。

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

CAS提出如下方案:

  • 每个Volume都由一个轻量级的Controller来管理,这个Controller可以是单独的Pod。
  • 这个Controller与使用该Volume的应用Pod在同一个Node(sidecar模式)
  • 不同的Volume的数据使用多个独立的Controller Pod进行管理。
img

由于Pod是通过K8s编排和存储的,因此毫无疑问通过这种形式就实现了K8s的编排和调度。

K8s目前毕竟是主流趋势,通过K8s编排和管理存储也必然是一种发展趋势。

云原生存储系统Longhorn

Longhorn简介

Longhorn最初由Rancher公司开发并贡献给社区,专门针对K8s设计开发的云原生分布式块存储系统,因此和K8s契合度很高,主要体现在以下两个方面:

  • 本身运行在K8s平台上,通过容器和微服务方式运行;
  • 很好的和PV/PVC结合。

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

每个微控制器只管理一个Volume,这种基于微服务的设计使得每个Volume相对独立,控制器升级时开业先选择一部分卷进行操作,如果升级出现问题,可以快速选择回滚到旧版本,升级过程中之可能会影响正在升级的Volume。不会导致其他Volume IO中断。

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

  • 支持多副本,不存在单点故障

  • 支持增量快照

  • 支持备份到其他外部存储系统,比如S3;

  • 精简配置

Longhorn内置了一个Web UI,能够通过UI很方便的管理Node、Volume以及Backup。

根据官方的说法,Longhorn不是为了取代其他的分布式块存储系统,而是为了设计一个更简单更适合容器环境的块存储系统,其他分布式存储有的一些高级功能Longhorn并没有实现,比如去重,压缩,分块,多路径等。

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

Longhorn部署

安装

国外地址

1
kubectl apply -f https://raw.githubusercontent.com/longhorn/longhorn/master/deploy/longhorn.yaml

Gitee加速

1
kubectl apply -f https://gitee.com/mirrors/longhorn/raw/master/deploy/longhorn.yaml

最好是提前下载好镜像,参考地址:https://gitee.com/mirrors/longhorn/raw/master/deploy/longhorn-images.txt

稍后片刻,查看运行状态

1
kubectl get pods -n longhorn-system
image-20220716153619243

如上图所示,说明安装成功。

访问UI

查看UI Service的详细信息

1
kubectl get svc longhorn-frontend -o wide -n longhorn-system
image-20220716154810558

可以看到服务类型是ClusterIP,此时只能在集群内部访问,由于我是远程管理的K8s集群,因此需要将过这个端口暴露出来。

暂时先使用NodePort的方式暴露这个服务,等到后面弄清楚Ingress的原理以后再迁移过去。

更改longhorn-frontend的配置

1
kubectl edit svc longhorn-frontend -n longhorn-system
image-20220716155901649

等待生效以后,就可以访问UI了。

访问的IP是集群的local IP,端口则是30000。NodePort的默认端口是30000-32767,这个好像可以根据kubelet的选项进行修改,这里测试原因就没必要修改啦。

我在这个work01所在的节点上挂载了一块硬盘,挂载路径在/mnt,大小只有1T,不过这里为啥Allocated了2T。。真是百思不得其解。

声明PVC

对于动态PV来说,只需要设置好StorageClass+PVC,在设置Pod Volume时就可以使用Volume了

安装好longhorn以后,默认安装了StorageClass

因此只需要设置PVC

1
2
3
4
5
6
7
8
9
10
11
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-longborn-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: longhorn
resources:
requests:
storage: 3Gi

设置完PVC以后就可以设置Pod时候添加Volume了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
apiVersion: v1
kind: Pod
metadata:
name: mysql-pod
spec:
nodeName: worker01
containers:
- name: mysql-pod-1
image: mysql:5
args:
- --max_connections=10000
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_general_ci
- --default-authentication-plugin=mysql_native_password
env:
- name: MYSQL_ROOT_PASSWORD
value: root
volumeMounts:
- name: mysql-data
mountPath: /var/lib/mysql # 容器挂载路径
subPath: mysql-data
volumes:
- name: mysql-data
persistentVolumeClaim:
claimName: mysql-longborn-pvc

当我兴冲冲的执行完kubectl apply -f之后,才发现我的Pod怎么也启动不起来,他总是显示

1
rpc error: code = DeadlineExceeded desc = volume pvc-084b4ca7-c972-4de6-aa29-55101df79635 failed to attach to node worker01

而Longhorn Dashbroad UI总是显示

经过查找,发现了原因:

  • 当前集群内只有1个worker节点,而默认的Default Replica Count被设置为3,因此控制器想做备份但是找不到合适的节点,因此调度失败;
  • 安装在节点的StorageClass有一个numberOfReplicas字段被设置为3,造成了调度失败;而尝试直接修改这个StorageClass却显示Forbidden: updates to parameters are forbidden.,猜测是因为某个策略禁止修改了此参数;

解决方案:

  1. 删除现在的StorageClass资源清单
  2. 更改StorageClass资源清单为符合要求,并重新应用
1
kubectl delete sc longhorn

但。。。这种方法也没用,删除这个StorageClass后又会立马生成。

后来找到了方法:修改这个文件对应的ConfigMap

1
kubectl edit cm longhorn-storageclass -n longhorn-system

BUTTTTTTTTTTTTTTTTTT,还是失败了。麻了。