1. 先搞清楚你需要的其实是哪种“集群”
一看到“在Kubernetes中安装Redis集群”,很多人第一反应就是把Redis官方文档里的cluster模式照搬到K8s上:6个节点、3主3从、分片存储。但我在实际项目中碰到的需求,十有八九并不是真正的Cluster分片集群,而是“主从复制加高可用哨兵”那一套。这两个东西安装方式、运维方式、故障恢复逻辑完全不同,如果一开始没分清,后面一定会返工。
先说结论。如果你只是希望Redis在Pod挂掉之后还能有副本顶上、应用读写不中断,那你要的是Redis Sentinel模式(主从+哨兵)。这种架构的维护成本低很多,最大规模也就几十个G的数据量,读写分离和自动故障转移都够用。只有在数据量真的超过单机内存、必须把数据分散到多个节点,或者单机写并发已经扛不住的时候,才需要考虑Redis Cluster模式(官方分片集群)。Kubernetes环境里跑Cluster模式,最大的难点不在于Redis配置本身,而在于网络标识的稳定性和Pod重建后IP变化带来的集群元数据错乱,这一点我会在后面详细展开。
还有一种隐蔽的需求是“只要主从,不需要哨兵”——比如纯缓存场景,丢了数据可以重建,应用端自己做降级。这种情况甚至不需要在主节点旁边挂真正的从节点,直接用K8s的Deployment跑单实例加探针就够了。装之前一定先问清楚这三个问题:数据能丢吗、要分片吗、故障恢复要求多久。答案是“能丢、不分片”的,我建议你直接跳过集群,别给自己找事。
规模预估也很关键。K8s里的Redis集群,每个Pod申请的内存、CPU、以及存储卷的IOPS限制,都会直接影响分片数量。我们当时评估过一个小项目:缓存数据预计20G,TPS峰值不到3万,这种体量用3主3从的Cluster是完全够的;但如果数据量只有2G,直接跑一个2G内存的哨兵主从就绰绰有余。分片数量一旦定下来,后续扩容很麻烦,因为扩容意味着槽位迁移和resharding,线上操作窗口很难约,所以宁可最开始多留一点余量,也不要算得刚刚好。
网络环境也要提前确认。Kubernetes集群的CNI插件如果是Calico或者Flannel,默认的Pod网络模型对Redis集群的支持都不成问题;但如果你是企业内网里带网络策略(NetworkPolicy)的严格环境,就得多检查一步:Redis节点之间除了6379端口,还有一个总线端口是+10000,也就是16379。这个端口用于Cluster内部节点之间的gossip通信、故障选举和槽位迁移,很多人只开了6379的放行规则,集群创建时看着正常,一发生故障马上发现节点之间失联。这个坑我踩过一次,当时排查了半天,最后发现就是安全组和NetworkPolicy把16379给拦了。
2. 从零搭建的部署骨架:StatefulSet与Headless Service的取舍
确定要上Redis Cluster之后,第一个核心决策就是用K8s的什么工作负载类型来跑。很多人图省事直接用Deployment加副本数6,跑起来也能启动,但问题会在第一次Pod重启时集中爆发。
2.1 为什么非StatefulSet不可
Redis Cluster的节点发现机制依赖每个节点的稳定网络标识。K8s的Deployment创建的Pod,每次重启后IP可能变化,Pod名称后面的随机串也会变,这就导致Redis Cluster元数据里记录的节点地址全是过期的。虽然可以通过配置cluster-announce-ip强行指定某个固定IP,但维护这样一来等于把Kubernetes最擅长的调度能力废掉了,你还要自己管理IP池。
StatefulSet的意义在于它给每个Pod赋予一个有序且稳定的DNS名称:例如redis-0.redis-headless.namespace.svc.cluster.local。Pod删了重建,名字不变,DNS解析结果会自动指向新Pod的IP。Redis Cluster内部只需要记住这个DNS名,就能在节点重建后继续正常通信。这是用StatefulSet的根本理由,不是因为它听上去更“有状态”这么简单。
搭建形态非常简单,一个kind: StatefulSet定义里,关键字段是serviceName必须指向我们提前建好的Headless Service,replicas根据分片数量决定,例如6个节点就是3主3从。每个Pod的spec.containers里跑的是同一个Redis镜像,但配置文件的生成方式要稍微讲究一点,不能所有Pod共用一份静态redis.conf。
2.2 Headless Service的坑与正确打开方式
Headless Service就是clusterIP: None的普通Service,它的作用不是负载均衡,而是把一组Pod的DNS记录直接映射到每个Pod的实际IP上。Redis Cluster模式下这是强制要求,没有它,StatefulSet的稳定网络标识就没有意义了。
apiVersion: v1 kind: Service metadata: name: redis-headless namespace: default spec: clusterIP: None selector: app: redis-cluster ports: - name: redis port: 6379 targetPort: 6379 - name: cluster-bus port: 16379 targetPort: 16379这个文件里有两个容易忽视的细节。
第一,16379端口一定要出现在Service里。这不是给外部客户端用的,而是让Kubernetes内部的其他Redis节点可以通过统一的DNS名解析到总线路由端口。如果你只暴露6379,节点之间的总线流量在初始化阶段虽然也能通过宿主网络跑,但一旦有Pod迁移到别的节点上,总线端口的网络规则可能就对不上了。
第二,千万不要在这个Headless Service上再做ClusterIP类型的额外暴露,也不要图省事再建一个ClusterIP Service指向这6个Pod。业务客户端连接集群时,需要的是分别连接每个节点,而不是经过一个VIP转一层。如果你的应用用的是Lettuce或JedisCluster这类支持拓扑感知的客户端,它们会自己解析节点列表;如果经过ClusterIP转发,客户端会发现所有请求都被集中导到某一个Pod上,不仅达不到分流目的,还会在故障切换时出现大量MOVED重试。
2.3 持久化存储怎么选,别在本地盘上裸奔
Redis在没有数据持久化的情况下,可以看作是纯缓存。但Cluster模式的场景一般是数据不能丢的,所以持久化存储是刚需。K8s里给StatefulSet挂存储卷,常见方案有这么三种,我直接说各自的适用边界。
本地存储类(Local PersistentVolume)的性能最好,延时最低,因为数据直接落在宿主机盘上。缺点是Pod必须被调度到特定节点上,如果节点挂了,Pod就算被重新调度到别的机器,数据也不会跟着走,除非你手动恢复。适合的场景是Redis可以接受从RDB文件重新加载数据,并且你有定期的冷备机制。
云平台提供的SSD云盘是另一个主流选择,性能中上,带冗余,节点迁移时可以快速重新挂载,不像本地盘那样绑死节点。缺点是一个Pod一块云盘,6个节点就是6块盘,成本不低;而且如果你用的是动态创建存储卷的StorageClass,请一定要确认PVC的删除策略。默认情况下很多StorageClass的reclaimPolicy设置为Delete,你删Pod没事,但如果误删了StatefulSet,PVC和云盘会被一起回收,数据直接消失。
还有一种选择是网络存储(如各类NFS),不建议用。Redis对时延非常敏感,哪怕是千兆内网,NFS的读写时延也往往比本地盘高一个数量级,会导致Redis的写操作明显变慢,集群故障切换时还可能因为IO阻塞触发频繁的主从切换。我在公司内部见过一个团队用NFS跑Redis的,业务高峰期时延毛刺直接让接口超时率飙到5%以上,后来全量迁回了本地SSD才解决。
就我自己实践下来的经验,中小规模集群优先考虑Local PV配合节点亲和性,同时每天定时把RDB备份到对象存储;有条件上云盘的就用云盘,省心很多。不要觉得云盘贵,比起真的丢一次数据,这点钱花得值。
3. 实际动手:逐Pod拉起、分片初始化与连通性验证
骨架搭好之后,就到了真正动手的阶段。这里说的“动手”不是apply一个yaml就完事,而是把每个Pod的配置、启动参数、初始化命令、验证流程走一遍。因为Redis Cluster的初始化是一个主动动作,不是Pod起来了就自动组成集群的。
3.1 先写好可复用的六个Pod配置
StatefulSet里每个Pod可以用同一个镜像,但配置文件需要通过环境变量或者configmap挂载的方式区分当前Pod的序号。最朴素的方案是给每个Pod单独写一个ConfigMap或直接内联配置,但6个节点的配置内容其实只差一个端口或者名称,维护成本可控;规模再大一点,建议用模板化的方式动态渲染配置。
我们当时用的是给每个Pod挂载一个存有公共配置段落的ConfigMap,再通过启动命令里读取Pod主机名来生成唯一的一段配置。这套做法的好处是,后续新增节点的时候不需要给每个Pod手工创建配置文件,改一下节点数就行。
一个关键参数是一定要提前写进配置文件的:
cluster-enabled yes cluster-config-file /data/nodes.conf cluster-node-timeout 10000 appendonly yes appendfsync everysec cluster-announce-ip <由启动脚本根据Pod名生成> cluster-announce-port 6379 cluster-announce-bus-port 16379这里有几个参数值得单独解释。
cluster-announce-ip,如果你是在同一个Kubernetes集群内部访问Redis,其实可以省略,因为节点间通过DNS名发现,K8s解析到的就是Pod的实际IP。但是在跨节点访问、或者用了NodePort暴露、或者Pod IP会变的环境下,必须显式配置一个稳定IP,否则集群在Pod重启后会陷入节点地址错乱的状态。最怕的情况是:Pod重建后IP变了,但Cluster元数据里还留着旧IP,客户端连上去只能拿到一部分节点地址,另外的节点怎么都连不通。
cluster-node-timeout默认是15000毫秒,就是15秒。意思是节点之间超过这个时间没有心跳,就判定对方失联并开始故障转移流程。Kubernetes环境下我习惯把它调到10000毫秒左右,因为Pod重建或调度延迟一般几秒内;如果开得太低,比如3000毫秒,一场正常的滚动升级就可能引发多次无谓的主从切换。调太低和调太高是两难,我的建议是10秒作为起步值,跑一段时间观察有没有“手工频繁切换”的告警再微调。
appendonly yes配合appendfsync everysec是Redis默认的持久化策略。保障数据不丢失的强度上它不是最强的(完全同步要always),但性能开销最低,也足够覆盖大部分业务场景。如果你不能接受最多丢一秒数据,就得认真评估你的业务需求了,那通常说明你不应该用Redis当一个严格意义上的数据库来用。
3.2 用on-failure重启策略还是让K8s来管
Pod的restartPolicy建议保持默认的Always。这里要说明一下,K8s里的重启不是覆盖所有进程异常的:如果Redis进程因为OOM被杀了,Always策略会由kubelet自动拉起;但如果是Redis进程还在、只是集群状态异常(比如网络分区、脑裂隐患),它不会重启进程,你得靠集群自愈或者手动介入。
还有一点,不要在Redis容器里自己加一个常驻的守护进程来拉Redis。我见过有人在entrypoint里用了类似supervisor的模式,Redis挂了自动重启,然后Redis自己还会把进程后台运行,结果就是K8s的探针看到的永远是活着的进程,但集群的slot健康状态早已异常。这种“假活”比真死还难排查,因为日志和监控都在,就是集群状态红着。所以让K8s的探针来负责健康判定,让kubelet来重启Pod,是最符合预期的方式。
探针的设计也要区分一下。Liveness探针负责判断Redis进程本身是否存活,用redis-cli ping或者TCP探测都可以;Readiness探针则应该用真实的、反映集群状态的命令来测,比如redis-cli cluster info | grep cluster_state。为什么要做这个区分?因为如果Redis进程活着但集群暂时不在稳定状态,直接拉流量进来会让应用层收到一堆CLUSTERDOWN错误,对业务是有损的。反过来,进程一旦死了,Liveness马上能捕捉到并触发重启。
3.3 集群握手与槽位分配的完整命令
等6个Pod都进入Running状态、Readiness探针也通过之后,集群创建的最后一个动作是在任意一个Pod里执行初始化命令。核心命令是:
kubectl exec -it redis-0 -- redis-cli --cluster create \ redis-0.redis-headless.default.svc.cluster.local:6379 \ redis-1.redis-headless.default.svc.cluster.local:6379 \ redis-2.redis-headless.default.svc.cluster.local:6379 \ redis-3.redis-headless.default.svc.cluster.local:6379 \ redis-4.redis-headless.default.svc.cluster.local:6379 \ redis-5.redis-headless.default.svc.cluster.local:6379 \ --cluster-replicas 1这个命令就是让Redis自己完成槽位分配、主从配对和复制关系的建立。--cluster-replicas 1表示每个主节点配1个从节点,6个节点正好3主3从。执行过程中会打印出每个节点被分配到的槽位范围,以及每个从节点归属于哪个主节点。如果某个Pod的DNS解析失败或者节点还没完全加入集群,命令会直接报错,这时候最好先检查Pod状态而不是反复重试。
有一个细节是初始化时能不能用IP而不是DNS名?技术上可以,但我不建议。IP方式下,Cluster元数据里存的是IP地址,Pod一旦重建,IP变化之后整个集群的地址表都是错的,除非你另外配置了announce-ip,并在每次Pod重建后手动清理nodes.conf。用DNS名的话,元数据里的地址会自动跟着Pod的解析变化,虽然有些版本对DNS名的处理在某些极端场景下有一定延迟,但总体上比IP方案靠谱得多。
初始化成功后,验证集群状态的命令是:
kubectl exec -it redis-0 -- redis-cli cluster info如果看到cluster_state:ok,并且cluster_slots_assigned等于16384、cluster_known_nodes等于6,那基本就是正常的。多次执行cluster nodes,每次看到的节点视图应该一致,且主从关系没有冲突。
当年我们第一次建集群的时候没有指定--cluster-yes,在交互确认时卡住了半天,后来才发现是这个原因。这个参数建议直接加上,避免在容器内交互确认的时候因为TTY问题卡死,虽然非交互模式下命令也能跑完,但加了更干净:
kubectl exec -it redis-0 -- redis-cli --cluster create ... --cluster-yes3.4 验证和压测不通过时的常规检查
集群建好后,不要急着接业务,先用一个简单的连通性测试验证写入跨分片的行为。用redis-cli直接连上其中一个节点,写入几个不同的key,比如:
kubectl exec -it redis-0 -- redis-cli -c SET user:10001 alice kubectl exec -it redis-0 -- redis-cli -c SET order:20250101 1024-c参数让redis-cli自动跟随MOVED重定向,这样两个key如果落在不同slot上,也能正常写入,不会报错。然后再连另一个节点,把所有key读一遍,确认数据确实分片存储。
如果这一步报错,最常见的两种原因:一是节点之间的16379总线端口没打通,二是Pod上的DNS解析不完整。前者的表现是cluster meet在握手阶段就失败,后者的表现是初始化能成功、但某些节点一直处于handshake状态。这两种情况都有明确对应的排查路径:网络策略看NetworkPolicy和宿主机安全组,DNS就看CoreDNS的日志和Pod里的/etc/resolv.conf。
性能验证阶段,我建议别一开始就用redis-benchmark打太高的并发,否则你判断不了是Redis问题还是应用侧问题。先按业务预估的峰值吞吐量打5分钟,观察有没有CLUSTERDOWN、MOVED、ASK这类错误码的增多;再用redis-cli --latency看网络层面的时延分布。Kubernetes环境里的时延往往不是Redis本身慢,而是Pod调度到了不同的物理节点,跨机房或者跨交换机的网络路径变长了。如果发现跨节点时延明显偏高,可以给StatefulSet加上Pod亲和性,把Redis节点尽量调度在同一个机柜或同一批物理机上。
4. 集群维护:故障转移、升级和恢复的真实操作记录
集群不是建完就结束了。实际上,真正考验架构设计的是后续的日常维护:Pod重启、节点故障、镜像升级、数据恢复。这些场景里Kubernetes和Redis Cluster的交互逻辑有很多细节,处理不好轻则服务抖动,重则数据分片错乱。
4.1 Pod挂了,集群真的会自己“活”过来吗
先说结论:单节点Pod挂了,集群大概率能自动恢复,但恢复的时长和业务可接受的停机窗口需要提前对齐。
假设redis-0这个主节点所在Pod被误删了。StatefulSet会按照控制器逻辑重新创建一个同名Pod,新的Pod会继承原来的PVC,这意味着里面旧的appendonly文件还在,Redis进程起来后可以恢复数据。同时,Redis Cluster在节点的故障转移机制中会识别出主节点失联,并在它的从节点中提升一个新的主节点。整个自动故障切换的时长受cluster-node-timeout影响,默认值下通常是十几秒到几十秒。
但这里有个Kubernetes专属的坑:旧Pod被删除后,新Pod从创建到就绪可能需要几十秒(取决于拉镜像时间和挂载卷的时间),而Redis Cluster的故障转移是在检测到主节点失联之后立刻触发的。也就是说,新Pod还在启动中的这段时间,集群已经完成了主从切换。等新Pod起来后,它带着旧PVC里的数据加入集群,但此时它已经被降级为从节点了。这一切看起来都很正常,对吧?不要高兴太早。
实际操作中我们遇到过的异常情况是:新Pod恢复后,旧PVC里的nodes.conf还记录着它自己是主节点、包含了部分槽位范围。如果它带着这份错误的配置加入集群,会出现槽位冲突,甚至出现两个节点都声称自己是某个槽位的主节点。这种状态下,部分key会间歇性报错,服务不可用的范围比单纯的节点故障还大。
解决这个问题的办法有两个方向。一是删除Pod前先主动把该节点从集群中摘除(redis-cli --cluster del-node),让集群在Pod消失前就把它的槽位迁移走或安排好从节点接管,但这样操作时间长,手动步骤多,违背了K8s自动恢复的初衷。二是让Pod启动时不要使用旧nodes.conf,而是让Redis重新通过cluster meet流程加入集群。最简单粗暴的方式是在启动脚本里删除旧nodes.conf,每次Pod重建都从干净配置开始,让Redis依据Cluster的gossip协议重新学习集群拓扑。前提是节点之间能通过稳定DNS名互相发现,否则每次重启都要手动触发一次meet。
经历过一次教训后,我的习惯是:所有Redis Cluster节点的启动命令里都加一条“删除本地nodes.conf再启动”的逻辑。因为对于Kubernetes环境来说,PVC负责存数据文件,nodes.conf这个元信息文件完全可以视为缓存垃圾,删掉不影响数据安全。刚开始团队还有人担心这样会不会导致集群状态丢失,实际跑下来完全没事,gossip协议会在一分钟内把拓扑信息重新同步回来。
4.2 滚动升级Redis镜像,顺序比参数更重要
K8s上升级Redis镜像版本,常用的是StatefulSet的滚动更新策略,但直接全量滚动是有风险的。Redis Cluster的滚动升级要求同一时刻不能有多于一个节点处于更新状态,并且不能同时升级主节点和它对应的从节点,否则一旦出现故障转移,主从关系可能配对错乱。
我们的做法是在StatefulSet的updateStrategy里设置partition参数,把滚动更新的批次控制住。比如partition: 5,意味着只有序号大于等于5的Pod会先被更新,其余保持旧版本。先升级从节点(序号较大的通常是后创建的从节点),逐个验证从节点升级后能正常同步数据;然后再调整partition值逐步推进到主节点。每升级完一批,跑一次redis-cli cluster check,确认没有任何slot处于迁移或者失败状态再继续。
还有一个容易被忽略的点:镜像升级后Redis的AOF文件版本和RDB文件的兼容性。大版本跨级升级时(比如5.x直接跳到7.x),较老的AOF文件可能无法被新版本加载。官方建议是跨大版本升级时先做一次全量备份和恢复演练,别怕麻烦。我们有一次就是从某个老版本跨两个大版本升级,启动后Redis进程一直在做AOF重写,CPU跑满半小时,就是因为新版本对AOF的解析效率变化导致的。后来学乖了,升级前先把AOF关掉,重新生成一份干净的RDB文件再切换镜像,整个启动过程降到几秒。
4.3 备份和恢复:不是dump完就万事大吉
Redis Cluster的备份不像单机Redis那么简单,因为数据是分散在多个分片上的。你单独备份某一个节点的RDB文件,只能恢复这个分片的数据,不能组装出一份完整数据。所以集群备份要按分片逐一执行,并且要保证每个分片的备份时间点在业务上尽量一致,否则恢复出来的数据在分片之间是对不齐的。
Kubernetes环境里我会同时做两层备份。第一层是自动的,每天某个时间点遍历所有主节点Pod,执行一次BGSAVE,把RDB文件拷贝到对象存储或者共享存储目录。第二层是数据库层面为每个分片做一次redis-cli --cluster backup(部分版本有这个命令),但通常这个命令在一体化备份上不够可靠,所以更通用的做法是直接在业务低峰期分别对每个分片执行DUMP+RESTORE或直接拷贝RDB。数据量大的时候DUMP逐个key的方式不现实,RDB文件拷贝才是王道。
恢复的时候顺序也很重要。不要直接把备份拷回去就启动所有节点,否则可能因为节点ID冲突导致整个集群互相不认识。标准步骤是:停止业务流量,把所有Redis节点清空成空集群状态,然后按分片逐个恢复RDB文件并启动节点,每个节点启动后用redis-cli cluster meet重新入伙,等所有节点都加入集群、槽位分配正常后,恢复业务流量。
这套流程我们演练过一次,总耗时大概在10分钟内。如果你没有定期演练恢复流程,那我建议至少每季度做一次。Kubernetes环境的好处是Pod可以随时快速重建,恢复流程可以自动化脚本化,花一次时间写好脚本,后面每次恢复就是一条命令的事。
5. 监控告警、安全配置和清理操作里最容易翻车的细节
集群稳定跑起来之后,紧接着的问题就是怎么监控它、怎么保护它、以及万一要下线的时候怎么收摊。这三件事看着是“外围工作”,处理不好能直接把前面所有努力清零。
5.1 监控怎么接:Metrics端口和ServiceMonitor的坑
Redis Cluster的监控有两个层面。一是看Pod级别的CPU、内存、网络、磁盘,这些可以交给K8s自身的监控体系;二是看Redis内部的集群健康状况、缓存命中率、键数量变化、内存碎片率等指标,这一层需要Redis暴露INFO数据,通过exporter转成Prometheus格式。
比较成熟的方案是redis_exporter配合ServiceMonitor接入Prometheus。但这里有个细节:每个Redis节点Pod必须暴露一个单独的口令端口或者在不暴露的情况下让exporter能区分出它是主节点还是从节点、属于哪个集群。如果6个Pod共用同一个Service且没有按Pod区分指标来源,你在Prometheus里看到的内存指标就分不清是哪台机器的。
我们在实践里给每个Pod都配了一个exporter容器,让它以sidecar方式运行,exporter连接到本地的Redis端口拉取指标,再通过各自的Pod指标端口暴露。这种方式的好处是在K8s的ServiceMonitor里可以直接用endpoints配置按Pod名匹配,Prometheus会天然给每个Pod生成一套独立的指标序列。
另外,一定不要忘了监控集群状态本身的关键指标:cluster_state是ok还是fail、cluster_known_nodes是否等于预期节点数、cluster_slots_assigned是否等于16384。这三个指标任何一个异常,都比内存使用率高一个量级的重要程度,应该设置成最高级别的告警。我们见过一次内存使用率告警刷了一堆工单,真正严重的主从断连告警却因为规则配错而被忽略了,结果业务侧先发现了问题。
5.2 密码、TLS和网络策略的取舍
K8s环境里的Redis如果只被同集群内部的应用访问,过去很多人图方便不设密码。但我要说的不是“必须设密码”这种泛泛的劝告,而是一个很具体的场景:如果你用了NodePort或者LoadBalancer方式对外暴露Redis端口,又或者集群里的其他非Redis应用有权限访问所有Pod网段,那么不设密码就是裸奔。
Redis Cluster模式下启用密码认证,需要在所有节点配置requirepass和masterauth两个参数。前者是客户端认证密码,后者是主从复制时从节点向主节点认证用的密码。很多人只设置了requirepass,结果从节点同步数据时一直报MASTER auth failed,就是这个原因。
TLS加密在Kubernetes环境里比较复杂,因为每个Pod重建后证书的SAN匹配逻辑很容易出问题。除非安全合规明文要求,我一般不推荐在集群内部先上TLS,风险收益比不高。更划算的做法是:用NetworkPolicy把Redis的网络访问范围限制到只有业务Pod所在的命名空间,其他来源一律拒绝。这个措施防的是内部误访问,真的防不了恶意攻击,但内部环境里最常见的风险本来也就是误操作和非预期访问。
还有一个小细节:密码不要写在StatefulSet的yaml明文里。虽然K8s有Secret资源,但Pod模板里的环境变量引用Secret的值在最终渲染结果中会以明文出现。更稳妥的方式是挂载Secret为文件,在Redis配置里通过include的方式读取密码文件内容。配置文件的解析方式不同,具体做法可以查一下当前镜像支持的配置能力,但无论如何,不要在yaml里直接写密码就对了。
5.3 删除集群时怎样不让数据“断片”
清理集群听起来简单,kubectl delete statefulset再删PVC就完了。但在企业环境里往往不是这样干净收场的,特别是你有备份、有监控、有其他团队的应用还指着这个集群。
删除顺序应该是先停业务流量,再摘除应用的连接,然后才轮到K8s资源。摘应用连接这个步骤最容易被忘记。如果应用还在持续写入,你直接删StatefulSet,PVC可能会被保留也可能被回收,取决于StorageClass配置。数据本身丢了是一回事,更麻烦的是应用端可能残留连接池,持续报连接异常,影响面从Redis本身扩大到整个业务系统。
我的经验是,删除前先对所有主节点执行一次redis-cli --cluster check,确认没有迁移中的slot;然后把集群节点逐个--cluster del-node卸掉,让整个集群在逻辑上回到“未初始化”状态;最后再删除K8s资源。这个顺序能保证即使后续有人想回溯什么问题,Redis的日志和数据文件都还在,而不是留下一个半死不活的僵尸集群。
PVC是否删除,取决于你有没有保留数据的必要。如果明确确认数据不要了,删PVC是干净的;如果可能有回滚需要,建议先给所有RDB文件留一份冷备再删,成本不高但能救命。
6. 一点个人经验:别迷信“开箱即用”
最后聊一点不太容易被写在技术文档里的东西。
Kubernetes和Redis Cluster都是非常成熟的系统,但两者组合之后的坑远远比单用其中一个多。很多团队装集群时顺手搜个yaml直接apply,看着起来了、状态ok,就觉得完事了。真正的问题往往出现在第一次Pod重建、第一次主从切换、第一次升级运维的时候。我见过不少项目,集群装好后半年都没出过事,大家以为稳定了,结果一次节点维护让整个集群瘫痪了一个小时。
我的建议是:从装集群的第一天起,就把故障演练当成上线流程的一部分。至少每两个月做一次小规模演练:手动删掉一个主节点对应的Pod,观察集群自动恢复的耗时;模拟一次网络分区,看看Cluster的故障转移是否按预期触发。这些演练在Kubernetes环境里执行成本很低,一条kubectl delete pod命令的事,但它的价值会在真正出事那天体现出来。
还有一点是关于版本选择。Redis的版本迭代速度很快,Kubernetes本身也在持续演进,不要追新,也不要死守特别老的版本。我的经验是选择比自己需要的稳定版本晚一个大版本或者两个小版本的版本,既不会踩太多新版本的坑,又能拿到足够多的bug修复。升级前务必先在测试环境跑通一遍再动生产,别省这点时间。
整个实践过程中,我最受益的一个习惯是:每个关键参数和每次变更记录都写清楚“为什么这么改”,而不是只记“改了什么”。Redis Cluster涉及的参数很多,有些参数在单机模式下根本不存在,有些参数只有在故障转移时才会展露它的重要性。留下决策记录,后面做故障复盘和容量规划的时候,能省掉大量重新回顾的时间。
如果这篇文章能帮你少踩几个坑,那就值了。集群装好后,还是那句话:稳定运行才是开始,好好盯着它,善待它。