小米Redis的K8s容器化部署实践



  • 背景node

  • Why K8Sredis

  • How K8s数据库

  • Why Proxy后端

  • Proxy带来的问题安全

  • K8s带来的好处bash

  • 遇到的问题网络

  • 总结架构


背景

小米的Redis使用规模很大,如今有数万个实例,而且天天有百万亿次的访问频率,支撑了几乎全部的产品线和生态链公司。以前全部的Redis都部署在物理机上,也没有作资源隔离,给管理治理带来了很大的困难。咱们的运维人员工做压力很大,机器宕机网络抖动致使的Redis节点下线都常常须要人工介入处理。因为没有作CPU的资源隔离,slave节点打RDB或者因为流量突增致使节点QPS升高形成的节点CPU使用率升高,均可能对本集群或其余集群的节点形成影响,致使没法预测的时延增长。负载均衡

Redis分片方式采用社区的Redis Cluster协议,集群自主分片。Redis Cluster带来了必定的易用性的同时,也提升了应用开发的门槛,应用开发人员须要必定程度上了解Redis Cluster,同时须要使用智能客户端访问Redis Cluster。这些智能客户端配置参数繁多,应用开发人员并没有法彻底掌握并设置这些参数,踩了不少坑。同时,因为智能客户端须要作分片计算,给应用端的机器也带来了必定的负载。运维


Why K8S

资源隔离

当前的Redis Cluster部署在物理机集群上,为了提升资源利用率节约成本,多业务线的Redis集群都是混布的。因为没有作CPU的资源隔离,常常出现某Redis节点CPU使用率太高致使其余Redis集群的节点争抢不到CPU资源引发时延抖动。由于不一样的集群混布,这类问题很难快速定位,影响运维效率。K8s容器化部署能够指定 CPU request 和 CPU limit ,在提升资源利用率的同时避免了资源争抢。

自动化部署

自动化部署。当前Redis Cluster在物理机上的部署过程十分繁琐,须要经过查看元信息数据库查找有空余资源的机器,手动修改不少配置文件再逐个部署节点,最后使用redis_trib工具建立集群,新集群的初始化工做常常须要一两个小时。

K8s经过StatefulSet部署Redis集群,使用configmap管理配置文件,新集群部署时间只须要几分钟,大大提升了运维效率。


How K8S

客户端经过LVS的VIP统一接入,经过Redis Proxy转发服务请求到Redis Cluster集群。这里咱们引入了Redis Proxy来转发请求。


Redis Cluster部署方式

Redis部署为StatefulSet,做为有状态的服务,选择StatefulSet最为合理,能够将节点的RDB/AOF持久化到分布式存储中。当节点重启漂移到其余机器上时,可经过挂载的PVC(PersistentVolumeClaim)拿到原来的RDB/AOF来同步数据。咱们选择的持久化存储PV(PersistentVolume)是Ceph Block Service。Ceph的读写性能低于本地磁盘,会带来100~200ms的读写时延。但因为Redis的RDB/AOF的写出都是异步的,分布式存储带来的读写延迟对服务并无影响。

Proxy选型

开源的Redis Proxy有不少,常见的开源Redis Proxy以下:咱们但愿可以继续使用Redis Cluster来管理Redis集群,因此Codis和Twemproxy再也不考虑。redis-cluster-proxy是Redis官方在6.0版本推出的支持Redis Cluster协议的Proxy,可是目前尚未稳定版,暂时也没法大规模应用。备选就只有Cerberus和Predixy两种。咱们在K8s环境上对Cerberus和Predixy进行了性能测试,结果以下:

测试环境

测试工具: redis-benchmark

Proxy CPU: 2 core

Client CPU: 2 core

Redis Cluster: 3 master nodes, 1 CPU per node

测试结果



在相同workload和配置下,Predixy的最高QPS要优于Cerberus,时延也比较接近。综合来看,Predixy比Cerberus的性能要高33%~60%,而且数据的key/value越大,Predixy优点越明显,因此最后咱们选择了Predixy。为了适应业务和K8s环境,在上线前咱们对Predixy作了大量的改动,增长了不少新的功能,好比动态切换后端Redis Cluster、黑白名单、异常操做审计等。

Proxy部署方式

Proxy做为deployment部署,无状态轻量化,经过LB对外提供服务,很容易作到动态扩缩容。同时,咱们为Proxy开发了动态切换后端Redis Cluster的功能,可实如今线添加和切换Redis Cluster。

Proxy自动扩缩容方式

咱们使用K8s原生的HPA(Horizontal Pod Autoscaler)来实现Proxy的动态扩缩容。当Proxy全部pod的平均CPU使用率超过必定阈值时,会自动触发扩容,HPA会将Proxy的replica数加1,以后LVS就会探测到新的Proxy pod并将一部分流量切过去。若是扩容后CPU使用率仍然超过规定的阈值,会继续触发扩容逻辑。可是在扩容成功5分钟内,不论CPU使用率降到多低,都不会触发缩容逻辑,这样就避免了频繁的扩缩容给集群稳定性带来的影响。

HPA可配置集群的最少(MINPODS)和最多(MAXPODS)pod数量,集群负载再低也不会缩容到MINPODS如下数量的pods。建议客户能够根据本身的实际业务状况来决定MINPODS和MAXPODS的值。


Why Proxy

Redis pod重启可致使IP变化

使用Redis Cluster的Redis客户端,都须要配置集群的部分IP和Port,用于客户端重启时查找Redis Cluster的入口。对于物理机集群部署的Redis节点,即使遇到实例重启或者机器重启,IP和Port均可以保持不变,客户端依然可以找到Redis Cluster的拓扑。可是部署在K8s上的Redis Cluster,pod重启是不保证IP不变的(即使是重启在原来的K8s node上),这样客户端重启时,就可能会找不到Redis Cluster的入口。

经过在客户端和Redis Cluster之间加上Proxy,就对客户端屏蔽了Redis Cluster的信息,Proxy能够动态感知Redis Cluster的拓扑变化,客户端只须要将LVS的IP:Port做为入口,请求转发到Proxy上,便可以像使用单机版Redis同样使用Redis Cluster集群,而不须要Redis智能客户端。


Redis处理链接负载高

在6.0版本以前,Redis都是单线程处理大部分任务的。当Redis节点的链接较高时,Redis须要消耗大量的CPU资源处理这些链接,致使时延升高。有了Proxy以后,大量链接都在Proxy上,而Proxy跟Redis实例之间只保持不多的链接,这样下降了Redis的负担,避免了由于链接增长而致使的Redis时延升高。


集群迁移切换须要应用重启

在使用过程当中,随着业务的增加,Redis集群的数据量会持续增长,当每一个节点的数据量太高时,BGSAVE的时间会大大延长,下降集群的可用度。同时QPS的增长也会致使每一个节点的CPU使用率增高。这都须要增长扩容集群来解决。目前Redis Cluster的横向扩展能力不是很好,原生的slots搬移方案效率很低。新增节点后,有些客户端好比Lettuce,会由于安全机制没法识别新节点。另外迁移时间也彻底没法预估,迁移过程当中遇到问题也没法回退。

当前物理机集群的扩容方案是:

  1. 按需建立新集群

  2. 使用同步工具将数据从老集群同步到新集群

  3. 确认数据无误后,跟业务沟通,重启服务切换到新集群

整个过程繁琐并且风险较大,还须要业务重启服务。

有了Proxy层,能够将后端的建立、同步和切换集群对客户端屏蔽掉。新老集群同步完成以后,向Proxy发送命令就能够将链接换到新集群,能够实现对客户端彻底无感知的集群扩缩容。


数据安全风险

Redis是经过AUTH来实现鉴权操做,客户端直连Redis,密码仍是须要在客户端保存。而使用Proxy,客户端只须要经过Proxy的密码来访问Proxy,不须要知道Redis的密码。Proxy还限制了FLUSHDB、CONFIG SET等操做,避免了客户误操做清空数据或修改Redis配置,大大提升了系统的安全性。

同时,Redis并无提供审计功能。咱们在Proxy上增长了高危操做的日志保存功能,能够在不影响总体性能的前提下提供审计能力。


Proxy 带来的问题

多一跳带来的时延

Proxy在客户端和Redis实例之间,客户端访问Redis数据须要先访问Proxy再访问Redis节点,多了一跳,会致使时延增长。经测试,多一跳会增长0.2~0.3ms的时延,不过一般这对业务来讲是能够接受的。


Pod漂移形成IP变化

Proxy在K8s上是经过deployment部署的,同样会有节点重启致使IP变化的问题。咱们K8s的LB方案能够感知到Proxy的IP变化,动态的将LVS的流量切到重启后的Proxy上。


LVS带来的时延

LVS也会带来时延,以下表中的测试,不一样的数据长度get/set操做,LVS引入的时延小于0.1ms。


K8S 带来的好处

部署方便

经过运维平台调用K8s API部署集群,大大提升了运维效率。


解决端口管理问题

目前小米在物理机上部署Redis实例是经过端口来区分的,而且下线的端口不能复用,也就是说整个公司每一个Redis实例都有惟一的端口号。目前65535个端口已经用到了40000多,按如今的业务发展速度,将在两年内耗尽端口资源。而经过K8s部署,每个Redis实例对应的K8s pod都有独立的IP,不存在端口耗尽问题和复杂的管理问题。


下降客户使用门槛

对应用来讲,只须要使用单机版的非智能客户端链接VIP,下降了使用门槛,避免了繁琐复杂的参数设置。同时因为VIP和端口是固定不变的,应用程序再也不须要本身管理Redis Cluster的拓扑。


提升客户端性能

使用非智能客户端还能够下降客户端的负载,由于智能客户端须要在客户端对key进行hash以肯定将请求发送到哪一个Redis节点,在QPS比较高的状况下会消耗客户端机器的CPU资源。固然,为了下降客户端应用迁移的难度,咱们让Proxy也支持了智能客户端协议。


动态升级和扩缩容

Proxy支持动态添加切换Redis Cluster的功能,这样Redis Cluster的集群升级和扩容切换过程能够作到对业务端彻底无感知。例如,业务方使用30个节点的Redis Cluster集群,因为业务量的增长,数据量和QPS都增加的很快,须要将集群规模扩容两倍。若是在原有的物理机上扩容,须要如下过程:

  1. 协调资源,部署60个节点的新集群

  2. 手动配置迁移工具,将当前集群的数据迁移到新集群

  3. 验证数据无误后,通知业务方修改Redis Cluster链接池拓扑,重启服务

虽然Redis Cluster支持在线扩容,可是扩容过程当中slots搬移会对线上业务形成影响,同时迁移时间不可控,因此现阶段不多采用这种方式,只有在资源严重不足时才会偶尔使用。

在新的K8s架构下,迁移过程以下:

  1. 经过API接口一键建立60个节点的新集群

  2. 一样经过API接口一键建立集群同步工具,将数据迁移到新集群

  3. 验证数据无误后,向Proxy发送命令添加新集群信息并完成切换

整个过程对业务端彻底无感知。

集群升级也很方便:若是业务方能接受必定的延迟毛刺,能够在低峰时经过StatefulSet滚动升级的方式来实现;若是业务对延迟有要求,能够经过建立新集群迁移数据的方式来实现。


提升服务稳定性和资源利用率

经过K8s自带的资源隔离能力,实现和其余不一样类型应用混部,在提升资源利用率的同时,也能保证服务稳定性。


遇到的问题

Pod重启致使数据丢失

K8s的pod碰到问题重启时,因为重启速度过快,会在Redis Cluster集群发现并切主前将pod重启。若是pod上的Redis是slave,不会形成什么影响。但若是Redis是master,而且没有AOF,重启后原先内存的数据都被清空,Redis会reload以前存储的RDB文件,可是RDB文件并非实时的数据。以后slave也会跟着把本身的数据同步成以前的RDB文件中的数据镜像,会形成部分数据丢失。

StatefulSet是有状态服务,部署的pod名是固定格式(StatefulSet名+编号)。咱们在初始化Redis Cluster时,将相邻编号的pod设置为主从关系。在重启pod时,经过pod名肯定它的slave,在重启pod前向从节点发送cluster failover命令,强制将活着的从节点切主。这样在重启后,该节点会自动以从节点方式加入集群。

LVS映射时延

Proxy的pod是经过LVS实现负载均衡的,LVS对后端IP:Port的映射生效有必定的时延,Proxy节点忽然下线会致使部分链接丢失。为减小Proxy运维对业务形成影响,咱们在Proxy的deployment模板中增长了以下选项:

lifecycle:    preStop:      exec:        command:        - sleep        - "171"复制代码

对于正常的Proxy pod下线,例如集群缩容、滚动更新Proxy版本以及其它K8s可控的pod下线,在pod下线前会发消息给LVS并等待171秒,这段时间足够LVS将这个pod的流量逐渐切到其余pod上,对业务无感知。


K8s StatefulSet没法知足Redis Cluster部署要求

K8s原生的StatefulSet不能彻底知足Redis Cluster部署的要求:

  1. Redis Cluster不容许同为主备关系的节点部署在同一台机器上。这个很好理解,若是该机器宕机,会致使这个数据分片不可用。

  2. Redis Cluster不容许集群超过一半的主节点失效,由于若是超过一半主节点失效,就没法有足够的节点投票来知足gossip协议的要求。由于Redis Cluster的主备是可能随时切换的,咱们没法避免同一个机器上的全部节点都是主节点这种状况,因此在部署时不能容许集群中超过1/4的节点部署在同一台机器上。

为了知足上面的要求,原生StatefulSet能够经过 anti-affinity 功能来保证相同集群在同一台机器上只部署一个节点,可是这样机器利用率很低。

所以咱们开发了基于StatefulSet的CRD:RedisStatefulSet,会采用多种策略部署Redis节点。同时,还在RedisStatefulSet中加入了一些Redis管理功能。这些咱们将会在其余文章中来继续详细探讨。


总结

目前集团内部已经有多个业务的数十个Redis集群部署到了K8s上并运行了半年多。得益于K8s的快速部署和故障迁移能力,这些集群的运维工做量比物理机上的Redis集群低不少,稳定性也获得了充分的验证。

在运维过程当中咱们也遇到了很多问题,文章中提到的不少功能都是根据实际需求提炼出来的。目前仍是有不少问题须要在后续逐步解决,以进一步提升资源利用率和服务质量。


混布 Vs. 独立部署

物理机的Redis实例是独立部署的,单台物理机上部署的都是Redis实例,这样有利于管理,可是资源利用率并不高。Redis实例使用了CPU、内存和网络IO,但存储空间基本都是浪费的。在K8s上部署Redis实例,其所在的机器上可能也会部署其余任意类型的服务,这样虽然能够提升机器的利用率,可是对于Redis这样的可用性和时延要求都很高的服务来讲,若是由于机器内存不足而被驱逐,是不能接受的。这就须要运维人员监控全部部署了Redis实例的机器内存,一旦内存不足,就切主和迁移节点,但这样又增长运维的工做量。

同时,若是混部的其余服务是网络吞吐很高的应用,也可能对Redis服务形成影响。虽然K8s的 anti-affinity 功能能够将Redis实例有选择地部署到没有这类应用的机器上,可是在机器资源紧张时,仍是没法避免这种状况。


Redis Cluster管理

Redis Cluster是一个P2P无中心节点的集群架构,依靠gossip协议传播协同自动化修复集群的状态,节点上下线和网络问题均可能致使Redis Cluster的部分节点状态出现问题,例如会在集群拓扑中出现failed或者handshake状态的节点,甚至脑裂。对这种异常状态,咱们能够在Redis CRD上增长更多的功能来逐步解决,进一步提升运维效率。


审计与安全

Redis自己只提供了Auth密码认证保护功能,没有权限管理,安全性较差。经过Proxy,咱们能够经过密码区分客户端类型,管理员和普通用户使用不一样的密码登陆,可执行的操做权限也不一样,这样就能够实现权限管理和操做审计等功能。


支持多Redis Cluster

单个Redis Cluster因为gossip协议的限制,横向扩展能力有限,集群规模在300个节点时,节点选主这类拓扑变动的效率就明显下降。同时,因为单个Redis实例的容量不宜太高,单个Redis Cluster也很难支持TB以上的数据规模。经过Proxy,咱们能够对key作逻辑分片,这样单个Proxy就能够接入多个Redis Cluster,从客户端的视角来看,就至关于接入了一个可以支持更大数据规模的Redis集群。


最后,像Redis这种有状态服务的容器化部署在国内大厂都尚未很是成熟的经验,小米云平台也是在摸索中逐步完善。目前咱们新增集群已经大部分部署在K8s上,更计划在一到两年内将集团内大部分的物理机Redis集群都迁移到K8s上。这样就能够有效地下降运维人员的负担,在不显著增长运维人员的同时维护更多的Redis集群。

相关文章
相关标签/搜索