在K8s上部署Redis集群的方法步骤

一、前言

架构原理:每个master都可以拥有多个slave。当master下线后,redis集群会从多个slave中选举出一个新的master作为替代,而旧master重新上线后变成新master的slave。

二、准备操作

本次部署主要基于该项目:

其包含了两种部署redis集群的方式:

  • statefulset
  • service&deployment

两种方式各有优劣,对于像redis、mongodb、zookeeper等有状态的服务,使用statefulset是首选方式。本文将主要介绍如何使用statefulset进行redis集群的部署。

三、statefulset简介

rc、deployment、daemonset都是面向无状态的服务,它们所管理的pod的ip、名字,启停顺序等都是随机的,而statefulset是什么?顾名思义,有状态的集合,管理所有有状态的服务,比如mysql、mongodb集群等。
statefulset本质上是deployment的一种变体,在v1.9版本中已成为ga版本,它为了解决有状态服务的问题,它所管理的pod拥有固定的pod名称,启停顺序,在statefulset中,pod名字称为网络标识(hostname),还必须要用到共享存储。
在deployment中,与之对应的服务是service,而在statefulset中与之对应的headless service,headless service,即无头服务,与service的区别就是它没有cluster ip,解析它的名称时将返回该headless service对应的全部pod的endpoint列表。
除此之外,statefulset在headless service的基础上又为statefulset控制的每个pod副本创建了一个dns域名,这个域名的格式为:

也即是说,对于有状态服务,我们最好使用固定的网络标识(如域名信息)来标记节点,当然这也需要应用程序的支持(如zookeeper就支持在配置文件中写入主机域名)。
statefulset基于headless service(即没有cluster ip的service)为pod实现了稳定的网络标志(包括pod的hostname和dns records),在pod重新调度后也保持不变。同时,结合pv/pvc,statefulset可以实现稳定的持久化存储,就算pod重新调度后,还是能访问到原先的持久化数据。
以下为使用statefulset部署redis的架构,无论是master还是slave,都作为statefulset的一个副本,并且数据通过pv进行持久化,对外暴露为一个service,接受客户端请求。

四、部署过程

本文参考项目的readme中,简要介绍了基于statefulset的redis创建步骤:

1.创建nfs存储
2.创建pv
3.创建pvc
4.创建configmap
5.创建headless服务
6.创建redis statefulset
7.初始化redis集群

这里,我将参考如上步骤,实践操作并详细介绍redis集群的部署过程。文中会涉及到很多k8s的概念,希望大家能提前了解学习

1.创建nfs存储

创建nfs存储主要是为了给redis提供稳定的后端存储,当redis的pod重启或迁移后,依然能获得原先的数据。这里,我们先要创建nfs,然后通过使用pv为redis挂载一个远程的nfs路径。

安装nfs

然后,新增/etc/exports文件,用于设置需要共享的路径:

创建相应目录

接着,启动nfs和rpcbind服务:

客户端

查看存储端共享

创建pv
每一个redis pod都需要一个独立的pv来存储自己的数据,因此可以创建一个pv.yaml文件,包含6个pv:

如上,可以看到所有pv除了名称和挂载的路径外都基本一致。执行创建即可:

2.创建configmap

这里,我们可以直接将redis的配置文件转化为configmap,这是一种更方便的配置读取方式。配置文件redis.conf如下

创建名为redis-conf的configmap:

查看创建的configmap:

如上,redis.conf中的所有配置项都保存到redis-conf这个configmap中。

3.创建headless service

headless service是statefulset实现稳定网络标识的基础,我们需要提前创建。准备文件headless-service.yml如下:

创建:

查看:

可以看到,服务名称为redis-service,其cluster-ip为none,表示这是一个“无头”服务。

4.创建redis 集群节点

创建好headless service后,就可以利用statefulset创建redis 集群节点,这也是本文的核心内容。我们先创建redis.yml文件:

如上,总共创建了6个redis节点(pod),其中3个将用于master,另外3个分别作为master的slave;redis的配置通过volume将之前生成的redis-conf这个configmap,挂载到了容器的/etc/redis/redis.conf;redis的数据存储路径使用volumeclaimtemplates声明(也就是pvc),其会绑定到我们先前创建的pv上。

这里有一个关键概念——affinity,请参考官方文档详细了解。其中,podantiaffinity表示反亲和性,其决定了某个pod不可以和哪些pod部署在同一拓扑域,可以用于将一个服务的pod分散在不同的主机或者拓扑域中,提高服务本身的稳定性。
而preferredduringschedulingignoredduringexecution 则表示,在调度期间尽量满足亲和性或者反亲和性规则,如果不能满足规则,pod也有可能被调度到对应的主机上。在之后的运行过程中,系统不会再检查这些规则是否满足。

在这里,matchexpressions规定了redis pod要尽量不要调度到包含app为redis的node上,也即是说已经存在redis的node上尽量不要再分配redis pod了。但是,由于我们只有三个node,而副本有6个,因此根据

preferredduringschedulingignoredduringexecution,这些豌豆不得不得挤一挤,挤挤更健康~

另外,根据statefulset的规则,我们生成的redis的6个pod的hostname会被依次命名为 $(statefulset名称)-$(序号) 如下图所示:

如上,可以看到这些pods在部署时是以{0…n-1}的顺序依次创建的。注意,直到redis-app-0状态启动后达到running状态之后,redis-app-1 才开始启动。
同时,每个pod都会得到集群内的一个dns域名,格式为$(podname).$(service name).$(namespace).svc.cluster.local ,也即是:

在k8s集群内部,这些pod就可以利用该域名互相通信。我们可以使用busybox镜像的nslookup检验这些域名:

可以看到, redis-app-0的ip为172.17.24.3。当然,若redis pod迁移或是重启(我们可以手动删除掉一个redis pod来测试),ip是会改变的,但是pod的域名、srv records、a record都不会改变。

另外可以发现,我们之前创建的pv都被成功绑定了:

5.初始化redis集群

创建好6个redis pod后,我们还需要利用常用的redis-tribe工具进行集群的初始化

创建ubuntu容器
由于redis集群必须在所有节点启动后才能进行初始化,而如果将初始化逻辑写入statefulset中,则是一件非常复杂而且低效的行为。这里,本人不得不称赞一下原项目作者的思路,值得学习。也就是说,我们可以在k8s上创建一个额外的容器,专门用于进行k8s集群内部某些服务的管理控制。
这里,我们专门启动一个ubuntu的容器,可以在该容器中安装redis-tribe,进而初始化redis集群,执行:

我们使用阿里云的ubuntu源,执行:

成功后,原项目要求执行如下命令安装基本的软件环境:

初始化集群
首先,我们需要安装redis-trib

然后,创建只有master节点的集群:

其次,为每个master添加slave

至此,我们的redis集群就真正创建完毕了,连到任意一个redis pod中检验一下:

另外,还可以在nfs上查看redis挂载的数据:

6.创建用于访问service

前面我们创建了用于实现statefulset的headless service,但该service没有cluster ip,因此不能用于外界访问。所以,我们还需要创建一个service,专用于为redis集群提供访问和负载均衡:

如上,该service名称为 redis-access-service,在k8s集群中暴露6379端口,并且会对labels nameapp: redisappcluster: redis-cluster的pod进行负载均衡。

创建后查看:

如上,在k8s集群中,所有应用都可以通过10.0.0.64 :6379来访问redis集群。当然,为了方便测试,我们也可以为service添加一个nodeport映射到物理机上,这里不再详细介绍。

五、测试主从切换

在k8s上搭建完好redis集群后,我们最关心的就是其原有的高可用机制是否正常。这里,我们可以任意挑选一个master的pod来测试集群的主从切换机制,如redis-app-0

进入redis-app-0查看:

如上可以看到,app-0为master,slave为172.17.63.9redis-app-3

接着,我们手动删除redis-app-0

我们再进入redis-app-0内部查看:

如上,redis-app-0变成了slave,从属于它之前的从节点172.17.63.9redis-app-3

六、疑问

至此,大家可能会疑惑,那为什么没有使用稳定的标志,redis pod也能正常进行故障转移呢?这涉及了redis本身的机制。因为,redis集群中每个节点都有自己的nodeid(保存在自动生成的nodes.conf中),并且该nodeid不会随着ip的变化和变化,这其实也是一种固定的网络标志。也就是说,就算某个redis pod重启了,该pod依然会加载保存的nodeid来维持自己的身份。我们可以在nfs上查看redis-app-1的nodes.conf文件:

如上,第一列为nodeid,稳定不变;第二列为ip和端口信息,可能会改变。

这里,我们介绍nodeid的两种使用场景:

当某个slave pod断线重连后ip改变,但是master发现其nodeid依旧, 就认为该slave还是之前的slave。

当某个master pod下线后,集群在其slave中选举重新的master。待旧master上线后,集群发现其nodeid依旧,会让旧master变成新master的slave。

对于这两种场景,大家有兴趣的话还可以自行测试,注意要观察redis的日志。

到此这篇关于在k8s上部署redis集群的方法步骤的文章就介绍到这了,更多相关k8s部署redis集群内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!

(0)
上一篇 2022年3月21日
下一篇 2022年3月21日

相关推荐