K8S共享存储—GlusterFS&Heketi

一、问题引出

业务系统容器化迁移、k8s部署的过程中,我们发现有很多服务都存在需要数据存储的需求。当应系统用采用传统的物理部署方式时,应用可将数据直接存储在本地,读取数据时只需到对应的储存目录下读取即可;但是,当服务采用容器化部署时,由于容器本地存储空间的生命周期与容器绑定,应用存储在容器中的数据会随容器的删除而一同被删除,应用数据也就失去了持久性。所以此类应用在迁移k8s部署时必须借助一种可以实现脱离于容器生命周期束缚的持久化存储介质,故此,类似于NFS、GlusterFS、Ceph等具备网络传输能力的存储服务在这里就可以大展身手。
我们在容器化工作开展的前期就是采用NFS作为k8s的共享存储服务,但随着工作的不断展开,我们发现由于NFS的集中式架构(单体架构),导致其与k8s集成在可用性方面存在重大缺陷,比如当NFS服务不可用时,依赖于存储的业务系统pod会直接进入pending状态,进而导致业务系统也一同失去了提供服务的能力【从k8s设计角度来讲,存储服务失效导致业务系统不可用是一种明智的策略,因为它保证了数据安全性(不可用总比数据丢失强),但是从业务系统的角度去看,这无疑是个尴尬的局面(甚至有点反人类的感觉),因为存储服务原本应该独立于业务系统存在的,但现在它却“绑架”了我们的业务系统,这是完全不能接受的】,虽然我们尝试了使用NFS的master&slave机制去保证它的可用性,但它主备切换的效率实在让我们不敢恭维。再者,让我们回想一下刚才说过的话,“NFS是一种集中式的架构”,没错!从原则上来讲,单体架构应用的扩展性一直都是一个头疼的问题,随着业务量的增长,数据量不断增大,NFS迟早会被“撑爆”,相信到那时肯定就不是硬盘扩容那么简单了。所以,我们必须尝试其他存储方案—GlusterFS or Ceph。这里我们将选择GlusterFS 6.5作为本文讨论的核心(GlusterFS是一种具备完全去中心化、超高可用性和超强横向扩展能力的分布式网络存储服务,您可以在我的另一篇文章《GlusterFS使用手册》中获得跟更多系统介绍和使用说明),并重点通过实操来展示它和Heketi与k8s的集成流程。通过对它的使用,我们将可以完美地解决NFS在单点故障、扩展性以及单点性能瓶颈等方面的问题。下面就让我们一起来探讨它和k8s集成实现共享存储的具体实施方案吧。

二、方案介绍

Kubernetes提供了两种使用GlusterFS作为共享存储的集成方式。第一种方式为kubernetes直接挂在GlusterFS存储卷:当我们在声明pod资源时,我们需要在po.spec.volumes的子字段中选择glusterfs作为存储服务类型。值得注意的是,当我们通过这种方式使用GlusterFS时,我们需要在Kubernetes中事先创建一个描述GlusterFS服务的endpoints资源。相对而言,这是一种非常简单的集成方式。遗憾的是,这种简单的集成方式并不会让你在今后的存储卷管理上同样省时间。首先,既然我们手动创建了GlusterFS卷,我们就要为它的“生老病死”负责,包括当卷空间不足时,我们需要通过手动扩展卷空间来对存储空间进行扩容,这就意味着用户必须非常了解GlusterFS常用的使用方法和常见问题的排查,这听起来似乎有点挑战,但用户必须接受这个事实。
那么,让我们来说一说第二种方式吧。有点遗憾,我的第二种方式必须建立在第一种方式的基础上使用。我是这么想的:GlusterFS采用物理的部署方式,每个服务节点管理若干硬盘设备,一部分为手动格式化的XFS文件系统盘,供方式一使用,一部分为未格式化的裸盘,供Heketi使用;Heketi服务采用容器化部署方式,运行为kubernetes集群中的pod实例,并使用第一种方式的GlusterFS卷为Heketi的数据和配置提供存储服务,最后再将Heketi服务声明为kebernetes中的storageclass资源,供pod资源申请PVC使用。这样当我们在需要使用存储空间时,只需要在pod清单中申明PVC资源和指定的storageclass资源即可,Heketi就会自动帮我们格式化相应的硬盘存储空间,并将其创建为PV资源与我们申请的PVC进行绑定,这中间所有的操作均由Heketi自动帮我们完成,我们只要关注存储空间的大小及使用权限即可,其他的我们一概不问,包括存储空间的扩容等等。这里通过Heketi创建出来的存储卷和PV是一一对应的,而PV和我们的PVC又是一一对应的,所以如果一个pod的PVC只用于为自己提供储存服务,那么可以认为底层创建出来的GlusterFS卷就是一个私有的储存空间,这在希望隔离的环境下是很有意义的,但同时也会因此产生一定的开销。在这里,Heketi的镜像由我们自己手动创建(Dockerfile见文档附录),GlusterFS集群也需要我们手动搭建(详见《GlusterFS使用手册》)。我得说明一下,这和Heketi官方提供的方式可能存在较大差异,官方提供的方式是将Heketi和GlusterFS均托管到kuberbetes集群中,GlusterFS以deamonSet的方式运行。但是我认为这种全封装、全自动的方式在对GlusterFS存储服务的管理和问题的排查上存在诸多不便,比如当我们需要对某些卷或者GlusterFS本身做出一些特定的配置时,我们还需要进入到GlusterFS容器中进行相应的操作;再比如当我们kubernetes集群外部的某些服务也想使用GlusterFS存储服务时,我们很难顺利地访问集群内部GlusterFS所在容器的IP和端口(除非做静态路由和TCP端口映射);并且对于大多数第一次接触GlusterFS的新手来说,我们不太推荐使用一些高度封装的技术来屏蔽一些重要的底层细节,因为这对后期问题的定位和排查相当不利。那么接下来,让我们对上述的两种集成方式进行一一落实吧。

三、方案实施

1、K8S挂载GlusterFS存储卷

(1)安装GlusterFS集群,集群IP分别为192.168.3.110、192.168.3.111、192.168.3.112…(建 议为6个节点),手动格式化硬盘为XFS格式,导出brick(建议以整块硬盘为单位), 并创建GlusterFS分布式卷gs1,副本数设为3;
(2)在K8S集群的每一个节点上安装glusterfs、glusterfs-fuse客户端服务;
(3)在k8s中创建endpoints资源gluster-endpoint.yaml,用于提供GlusterFS集群节点 的访问入口,内容如下:

apiVersion: v1
kind: Endpoints
metadata:
  name: gluster-endpoints
subsets:
  - addresses:
    - ip: 192.168.3.110
    ports:
    - port: 24007
      name: glusterd
  - addresses:
    - ip: 192.168.3.111
    ports:
    - port: 24007
      name: glusterd
  - addresses:
    - ip: 192.168.3.112
    ports:
    - port: 24007
      name: glusterd
      以下配置雷同,已省略

(4)在pod中使用gs1存储卷,示例如下;

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web-demo
spec:
  selector:
    matchLabels:
      app: web-demo
  replicas: 1
  template:
    metadata:
      labels:
        app: web-demo
    spec:
      containers:
      - name: web-demo
        image: tomcat:latest
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
        volumeMounts:
        - name: glusterfs
          mountPath: /tmp
      volumes:
      - name: glusterfs
        glusterfs:
          endpoints: gluster-endpoints
          path: /gs1
          readOnly: false

(5)此时即进入容器在/tmp目录下存取文件;当容器退出重启时,仍能获取到退出 前保存的数据。

PS.该方式是采用直接挂载glusterFS存储卷来实现k8s集成的,配置过程相对简单 易 懂,但其使用起来则相对繁琐——要求k8s管理员事先掌握GlusterFS的使用和常用 配置,卷相关的选型、新建、扩容、管理、配置等操作完全由GlusterFS管理员手动进 行,适用于存储和管理相对较小的、核心的、存在数据迁移可能性的数据。GlusterFS 的使用文档一并提供于该文档同目录中,使用该方式挂载卷时请务必详细阅读 GlusterFS使用文档。生产中建议使用副本数为3,文件系统格式为xfs。

2、K8S集成Heketi&GlusterFS

(1)假设您已按照方式一创建好存储卷gs1(供Heketi存储数据和配置使用),并在每个GlusterFS服务节点挂上了未格式化的裸盘/dev/sdb。
(2)将Heketi服务以pod实行运行在k8s中(heketi镜像已被我上传至阿里云, 您可以选择将该镜像事先下载到本地,运行命令:docker pull registry.cn-beijing.aliyuncs.com/chaojiang/heketi9-centos7:v1.1),并为其创建一个server对象,server的clusterIP设为10.100.100.100,端口设为8080 (和 Heketi.json配置中的端口一致);然后将Heketi服务的数据目 /heketi/hkt-data/heketi 挂载至glusterFS的gs1存储卷上,并指明subPath为heketi。
其中:
/heketi/hkt-data/heketi/conf下存储heketi的配置文件,
/heketi/hkt-data/heketi/log下存储heketi的运行日志,
/heketi/hkt-data/heket/db下存储heketi的集群配置信息,
/heketi/hkt-data/heket/ssh下存储heketi容器到GlusterFS各个节点的ssh秘钥。
PS.千万注意,我们在Heketi的deployment清单中把/heketi/hkt-data/heketi目录挂 载到了gs1存储卷的heketi(subPath指定的名称)上,虽然我提供的heketi镜像 在/heketi/hkt-data/heketi目录下已经存在conf、ssh、log、db文件夹,但该目 录 挂载到/gs1/heketi后,/heketi/hkt-data/heketi下的所有文件夹都会被屏蔽,所以第 一次进入heketi容器初始化集群时,您还需要事先在/heketi/hkt-data/heketi下手动 创建conf、ssh、logs、 db这几个文件夹,否则初始化heketi会失败。

·pod-heketi清单文件大致为(详细配置清单见文档同目录heketi-4-glusterfs.yaml):

#deploy
apiVersion: apps/v1
kind: Deployment
metadata:
  name: heketi-server
spec:
  selector:
    matchLabels:
      app: heketi-server
  replicas: 1
  template:
    metadata:
      labels:
        app: heketi-server
    spec:
      containers:
      - name: heketi-server-container
        image: registry.cn-beijing.aliyuncs.com/chaojiang/heketi9-centos7:v1.1
        imagePullPolicy: IfNotPresent
        ports:
        - containerPort: 8080
        livenessProbe:
          failureThreshold: 3
          httpGet:
            path: /hello
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 60
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        readinessProbe:
          failureThreshold: 3
          httpGet:
            path: /hello
            port: 8080
            scheme: HTTP
          initialDelaySeconds: 60
          periodSeconds: 10
          successThreshold: 1
          timeoutSeconds: 1
        volumeMounts:
        - name: heketi-glusterfs
          mountPath: /heketi/hkt-data/heketi
          subPath: heketi
      volumes:
      - name: heketi-glusterfs
        glusterfs:
          endpoints: glusterfs-endpoints
          path: /gs1
          readOnly: false
---
#service
apiVersion: v1
kind: Service
metadata:
  name: heketi-service
spec:
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    app: heketi-server
  clusterIP: 10.100.100.100

(3)待Heketi容器启动后,执行kubectl exec -it podname – /bin/bash进入容器,在/heketi/hkt-data/heketi/conf目录下添加新的配置文件heketi.json(默认情况下 Heketi启动会加载/heketi/heketi-default.json配置文件,但该目录下内容不具备持久性),并在/heketi/hkt-data/heketi/ssh目录下添加heketi到GlusterFS各节点的ssh秘钥,同时将公钥拷贝至GlusterFS节点服务器中。最后在 /heketi/hkt-data/heketi/conf目录下创建topology.json配置文件,并使用该配置初始 化Heketi集群。
·在/heketi/hkt-data/heketi下创建conf、ssh、log、db文件夹:
mkdir -p /heketi/hkt-data/heketi/{conf,ssh,log,db}
·在/heketi/hkt-data/heketi/conf下创建配置文件heketi.json,内容大致如下:

{
  "port": "8080",//heketi服务暴露的端口				
  "enable_tls": false,//是否开启tls证书验证
  "cert_file": "",
  "key_file": "",
  "use_auth": true,//是否开启用户名密码验证
  "jwt": {
    "admin": {
      "key": "adminkey"
    },
    "user": {
      "key": "userkey"
    }
  },
  "profiling": false,
  "glusterfs": {
    "executor": "ssh",
    "sshexec": {
      "keyfile": "/heketi/hkt-data/heketi/ssh/id_rsa",//指定ssh私钥文件,该目录下文件会持久化到gs1卷中
      "user": "root",
      "port": "22",
      "fstab": "/etc/fstab",
      "pv_data_alignment": "256K",
      "vg_physicalextentsize": "4MB",
      "lv_chunksize": "256K",
      "backup_lvm_metadata": false,
      "gluster_cli_timeout": "20",
      "debug_umount_failures": true
    },
    "db": "/heketi/hkt-data/heketi/db/heketi.db",//指向heketi数据存储文件,该目录下文件会持久化到gs1卷中
    "refresh_time_monitor_gluster_nodes": 120,
    "start_time_monitor_gluster_nodes": 10,
    "loglevel" : "info",
    "auto_create_block_hosting_volume": true,
    "block_hosting_volume_size": 50,
    "block_hosting_volume_options": "gluster-block",
    "pre_request_volume_options": "",
    "post_request_volume_options": "auth.allow 192.168.*.*" //Gluster卷配置,多个配置可用“,”隔开
  }
}

使用命令在/heketi/hkt-data/heketi/ssh下生成秘钥,并将该秘钥拷贝至GlusterFS各个节点:
·生成秘钥
ssh-keygen -t rsa -q -f /heketi/hkt-data/heketi/ssh/id_rsa -N ‘’
·拷贝秘钥,这里以192.168.3.110节点为例,实际每个节点都需要拷贝
ssh-copy-id -i /heketi/hkt-data/heketi/ssh/id_rsa [email protected]
·测试登录
ssh -i /heketi/hkt-data/heketi/ssh/id_rsa [email protected]
PS.完成后,Heketi服务就可以正常向GlusterFS各个节点发送操作指令,从而完成对GlusterFS的控制,需要注意的是,heketi.json配置中的keyfile必须指向该私钥文件。
最后,使用heketi-cli提供的命令初始化heketi集群,这里将会用到heketi的另一个配置文件topology.json,这个文件的模板我也已经提供在了heketi.json同目录下,文档中我们可以列出配置文件的一部分,以供用户参考。
·Heketi集群初始化:
heketi-cli --server http://localhost:8080 --user admin --secret adminkey topology load --json topology.json
·初始化成功后,控制台将输出如下内容,此时我们就可退出容器了。
K8S共享存储—GlusterFS&Heketi_第1张图片
PS.由于每次使用heketi-cli都需要输入用户名、密码和服务restful接口前缀,所以我已经在镜像中提前将heketi-cli --server http://localhost:8080 --user admin --secret adminkey添加为别名heketi-cli,您可以在/root/.bashrc中看到这条命令,但如果您打算更换heketi.json中的用户名和密码或服务端口,那么这个别名将会失效。

·topology.json配置如下:

{
    "clusters": [
        {
            "nodes": [
                {
                    "node": {
                        "hostnames": {
                            "manage": [
                                "192.168.3.111"
                            ],
                            "storage": [
                                "192.168.3.111"
                            ]
                        },
                        "zone": 1
                    },
                    "devices": [
                        {
                            "name": "/dev/sdd",
                            "destroydata": false
                        }
                    ]
                },
                {
                    "node": {
                        "hostnames": {
                            "manage": [
                                "192.168.3.113"
                            ],
                            "storage": [
                                "192.168.3.113"
                            ]
                        },
                        "zone": 1
                    },
                    "devices": [
                        {
                            "name": "/dev/sdd",
                            "destroydata": false
                        }
                    ]
                }
            ]
        }
    ]
}

(4)Heketi格式化GlusterFS成功后,退出容器并重新发布Heketi服务使其加载新的配 置文件重新上线。
(5)在k8s集群中创建storageclass资源,并指定使用Heketi服务,resturl必须使用 Heketi服务中clusterIP指定的IP,clusterid必须使用初始化heketi集群的ID,比如 这里就是a57b85f443527ac0a74b175230c56b85。
PS.如果忘记了heketi集群的ID,可以使用命令:
kubectl exec -it ${pod} – /bin/bash -c "heketi-cli --server http://localhost:8080 --user admin --secret adminkey cluster list"获取
·storageclass-glusterfs.yaml配置如下:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: glusterfs-heketi-demo
provisioner: kubernetes.io/glusterfs
reclaimPolicy: Retain
volumeBindingMode: Immediate
allowVolumeExpansion: true
parameters:
  resturl: "http://10.100.100.100:8080"
  clusterid: "a57b85f443527ac0a74b175230c56b85"
  gidMin: "40000"
  gidMax: "50000"
  restauthenabled: "true"
  restuser: "admin"
  restuserkey: "adminkey"
  volumetype: "replicate:3"

PS.这里restauthenabled表示开启密码验证,生产中必须使用secret保存秘钥, 这里为了简单起见这一步就省略了。
详细配置见官网文档:
https://kubernetes.io/zh/docs/concepts/storage/storage-classes/

(6)现在我们就通过glusterfs-heketi-demo这个存储类在应用容器中来动态申 PV了, 支持我们就实现了k8s、heketi和GlusterFS的集成。PVC资源示例如下:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: glusterfs-pvc-demo
  namespace: default
spec:
  storageClassName: glusterfs-heketi-demo
  accessModes:
  - ReadWriteMany
  resources:
    requests:
      storage: 2Gi

PS.申请PVC时,k8s会使用glusterfs-heketi-demo存储类驱动heketi服务为其格式化挂 载在GlusterFS集群上的裸盘,并动态生成相应大小的GlusterFS卷,然后把该卷和动态 生成的一个PV进行绑定,绑定结束后PVC即可与该PV进行绑定,从而构建出一个可 以提供存储的k8s存储卷,示例如下:
PV:
在这里插入图片描述
PVC:
在这里插入图片描述
(7)接着k8s管理员就可以通过挂载PVC的方式使用Heketi为其动态生成的GlusterFS 存储卷啦

PS.该方式主要通过Heketi管理glusterFS来实现与k8s的集成,配置过程相对繁琐, 但对于GlusterFS和K8S的集成使用则变得相对简单——对于K8S管理员来说,不再 需要关注GlusterFS的一些细节操作和具体配置,类似于卷的选型、新建、扩容、管理、 配置等操作完全可以交由Heketi去掌管,在需要使用存储介质的时候只需要创建PVC 资源,并指定相应的存储类和存储空间即可,就跟物理部署时数据本地存储一样省心。 该方式适用于存储和管理相对较大的、临时的、不太容易迁移的数据。副本数视数据 重要性决定(可创建多个不同存储类按数据重要性混合使用),文件系统格式同为xfs。

四、常见问题解答

PS.第一种方式的常见问题基本都在我的另一篇文章《GlusterFS使用手册》中提到,这里就不再 重复,下面只针对Heketi管理GlusterFS的常见问题作出简要说明。

1、Heketi如何配置GlusterFS各项参数?

在Heketi.json配置文件的post_request_volume_options配置项中设置,多个glusterfs 配置使 用“,”号隔开;

2、如何对PVC扩容,如何对Heketi集群扩容?

Heketi管理GlusterFS提供的PVC是允许扩容,前提是在创建storageclass资源时必须 将allowVolumeExpansion: true配置上去,当PVC存储空间吃紧时,只需要修改PVC资 源的storage: 2Gi字段,然后重新发布该PVC即可,此时使用该PVC的所有pod资源就 会自动重新启动,并完成PVC的扩容。
当Heketi集群中的存储空间不足时,可以通过Heketi-cli的命令进行扩容,通过命令:
./heketi-cli --server “http://solo2:8080” --user “admin” --secret “adminkey” device add --name="/dev/vdc" --node “c3638f57b5c5302c6f7cd5136c8fdc5e”
就可以将新的硬盘设备/dev/vdc添加到heketi集群(–node指定的是glusterfs节点ID)

3、Heketi单例pod是否可靠?

我把Heketi比作切蛋糕的人。Heketi旨在为k8s提供申请PVC服务,并不与GlusterFS和k8s在可用性上存在耦合, 即只要PVC申请成功,即使Heketi挂掉也不会影响k8s 正常使用GlusterFS的存储服务;Heketi挂掉只会导致申请PVC失败,只要保证k8s在申请PVC时Heketi能正常运行 即可,因此Heketi无需多实例部署。

4、Heketi掌管着glusterFS所有存储设备,Heketi如何做到存储空间监控?

Heketi-cli提供命令,可以查看集群中每一块存储设备的空间使用情况,监控报警功能暂未提供,该功能通过安装tendrl-ansible来实现。
./heketi-cli --server “http://solo2:8080” --user “admin” --secret “adminkey” topology info

5、权限管理如何配置?

GlusterFS支持SSL和IP验证配置,Heketi支持配置TLS。出于简单考虑,本文目前暂 不提供权限控制配置说明,如有需要请在评论区留言,我会在以后将该内容补充到本文中。

附录

1、生成Heketi镜像的Dockerfile

FROM centos:centos7.6.1810

ADD heketi-v9.0.0.tar.gz /

RUN yum -y install libssh openssh openssh-server openssh-clients \
 && rm -rf /root/.ssh/* \
 && ssh-keygen -t rsa -q -f /root/.ssh/id_rsa -N '' \
 && chmod 777 /root/.ssh/id_rsa.pub \
 && echo -e "\tStrictHostKeyChecking no" >> /etc/ssh/ssh_config \
 && echo -e "\tUserKnownHostsFile /heketi/hkt-data/heketi/ssh/known_hosts" >> /etc/ssh/ssh_config \
 && echo "alias heketi-cli='heketi-cli --server "http://localhost:8080" --user "admin" --secret "adminkey"'" >> /root/.bashrc

ENV HEKETI_HOME=/heketi
ENV PATH=$PATH:$HEKETI_HOME/bin

#需要把这个路径挂载至共享存储
VOLUME /heketi/hkt-data/heketi

#需要将这个端口映射出去,用作健康检查
EXPOSE 8080

ENTRYPOINT ["hkt-start.sh"]

该Dockerfile中使用到的heketi-v9.0.0.tar.gz我已上传至CSDN,如有需要请自行下载构建Docker镜像,地址:https://download.csdn.net/download/mr__chao/11949277

你可能感兴趣的:(K8S共享存储—GlusterFS&Heketi)