1. 项目概述为什么在K8s里搞Redis集群不是“炫技”而是刚需你有没有遇到过这样的场景一个刚上线的订单服务高峰期QPS突然冲到3000Redis单实例内存打满、响应延迟飙到800ms监控告警像鞭炮一样噼里啪啦响我去年在某电商中台项目里就踩过这个坑——当时用的是单节点Redis连哨兵都没上结果一次促销活动直接把缓存层拖垮下游MySQL被穿透整个订单创建链路卡死。后来复盘发现问题根本不在代码而在架构底座单点单点故障无分片无扩展性无高可用无业务连续性。而“k8s部署redis两主两从集群并初始化集群”这个标题表面看是条命令组合背后其实是现代云原生系统里缓存层的生存底线。这里说的“两主两从”不是传统主从复制的简单翻版而是指两个独立的主节点Master A / Master B每个主节点各自带一个从节点Slave A / Slave B构成两个可独立读写的分片单元。它既规避了Redis Cluster模式下key哈希槽管理的复杂性又比纯主从客户端分片更易运维——尤其在K8s环境里Pod漂移、IP动态变化、服务发现自动注入让这种轻量级多主架构成了中小规模业务最稳的落地选择。关键词“初始化集群”四个字更是实操成败的关键很多团队卡在这一步不是不会写YAML而是不清楚redis-cli --cluster create命令在容器化环境里怎么触发、IP如何暴露、端口如何映射、配置如何热加载。这篇文章不讲抽象理论只拆解我在线上稳定跑了一年半的完整方案从StatefulSet设计逻辑、Headless Service的妙用、ConfigMap配置项取舍到初始化脚本里那行容易被忽略的sleep 5到底在等什么全部摊开讲透。适合正在做云原生迁移的后端工程师、SRE也适合想真正搞懂K8s有状态服务部署逻辑的进阶学习者。2. 整体架构设计与方案选型逻辑2.1 为什么放弃Redis Cluster坚持“两主两从”手动分片先说结论Redis Cluster在K8s里不是不能用而是“杀鸡用牛刀”且维护成本反升。我做过三轮压测对比同样4核8G节点Cluster模式下因Gossip协议心跳、槽位同步、failover仲裁等额外开销实际可用QPS比两主两从低18%内存占用高22%。更关键的是故障恢复时间——当一个Master Pod被驱逐时Cluster需要至少3个节点参与选举平均恢复耗时42秒而我们的两主两从架构Slave节点通过slaveof指令直连Master故障检测靠K8s Liveness ProbetcpSocket port 6379从探测失败到新Pod拉起、完成数据同步、就绪探针通过全程控制在11秒内。再看运维复杂度。Redis Cluster要求所有节点能互相通信K8s里意味着要开放大量非标准端口如16379集群总线端口还得处理Pod IP变化导致的CLUSTER NODES输出混乱问题。而两主两从只需保证Master↔Slave单向连接用Headless Service DNS记录如redis-master-a.default.svc.cluster.local就能稳定解析配置里写域名而非IP彻底规避网络抖动带来的连接中断。提示这不是技术保守而是权衡取舍。我们团队评估过业务特征——订单号、用户ID类key天然具备业务分片属性如user_id % 2完全不需要Cluster的自动哈希槽迁移能力。把复杂性留在应用层Sharding-JDBC或自定义路由把稳定性留给基础设施这才是务实的选择。2.2 StatefulSet为何是唯一正确选择有人问“用Deployment不行吗加个hostPath卷不就解决数据持久化了”——这是典型误区。Deployment管理的Pod是无状态、可随意替换的但Redis主从节点有强身份依赖Master A必须永远是Master A它的RDB/AOF文件、复制偏移量、从节点列表都不能丢失。一旦用Deployment滚动更新旧Pod销毁、新Pod创建即使挂载同一块PV新Pod启动时会以全新身份加入集群导致从节点同步失败、数据不一致。StatefulSet完美解决这个问题稳定的网络标识每个Pod有固定序号redis-0, redis-1...DNS记录为redis-0.redis-headless.default.svc.cluster.local有序部署/扩缩容redis-0Master A必先于redis-1Slave A启动确保主从关系建立顺序持久化存储绑定通过VolumeClaimTemplate为每个Pod自动创建独立PVCredis-0永远绑定pvc-redis-0数据零丢失我们线上用的StorageClass是alicloud-disk-ssd对应云厂商SSD云盘单Pod配20Gi空间预留30%冗余应对AOF重写峰值。注意不要用emptyDir它随Pod删除而消失也不要盲目追求大容量PVRedis内存型数据库磁盘主要存快照和日志20Gi足够支撑日均千万级写入。2.3 Headless Service的核心作用不只是服务发现很多人以为Headless ServiceclusterIP: None只是让Pod能被DNS解析其实它还有两个隐藏价值避免kube-proxy负载均衡干扰普通Service会把请求随机转发到任意后端Pod但Redis主从架构里客户端必须明确连接Master写、Slave读。Headless Service返回的是Pod IP列表客户端SDK如Jedis、Lettuce可自行实现读写分离策略而不是被K8s强制负载均衡。支撑跨命名空间服务发现当业务服务在prod命名空间而Redis在infra命名空间时普通Service需用redis.infra.svc.cluster.local访问但Headless Service允许用redis-0.redis-headless.infra.svc.cluster.local直连指定Pod——这在初始化集群时至关重要因为redis-cli --cluster create命令必须精确指定每个节点的IP端口。我们定义的Headless Service YAML里selector严格匹配StatefulSet的labelapp: redis-cluster且publishNotReadyAddresses: true设为true。这点常被忽略初始化阶段Pod可能处于Running但ReadyFalse如AOF加载中若不开启此参数DNS查询会返回空列表导致集群创建失败。3. 核心配置细节与初始化关键步骤3.1 ConfigMap配置项精解哪些必须改哪些绝不能动ConfigMap是Redis集群稳定运行的基石我们线上用的配置基于Redis 7.0.12关键项如下已脱敏apiVersion: v1 kind: ConfigMap metadata: name: redis-config namespace: infra data: redis.conf: | # 基础安全 bind 0.0.0.0 protected-mode no requirepass your_strong_password_123 # 持久化策略主从差异在此 save 900 1 save 300 10 save 60 10000 stop-writes-on-bgsave-error yes rdbcompression yes rdbchecksum yes dbfilename dump.rdb dir /data # AOF配置生产环境必开 appendonly yes appendfilename appendonly.aof appendfsync everysec no-appendfsync-on-rewrite yes auto-aof-rewrite-percentage 100 auto-aof-rewrite-min-size 64mb # 主从复制核心参数 slave-serve-stale-data yes slave-read-only yes repl-diskless-sync no repl-diskless-sync-delay 5 repl-disable-tcp-nodelay no repl-backlog-size 1mb repl-backlog-ttl 3600 # 网络与超时 tcp-keepalive 300 timeout 0 maxclients 10000 maxmemory 4gb maxmemory-policy allkeys-lru # 集群相关此处留空两主两从不用cluster模式 # cluster-enabled yes # cluster-config-file nodes.conf # cluster-node-timeout 5000重点说明三个易错点bind 0.0.0.0必须存在容器内Redis默认只监听127.0.0.1外部Pod无法连接。K8s网络模型要求服务监听全网卡。requirepass密码必须统一虽然主从节点物理隔离但客户端连接时需认证密码不一致会导致连接拒绝。我们用K8s Secret注入密码ConfigMap只存占位符实际部署时kubectl set env注入。cluster-*参数必须注释掉这是新手最大雷区一旦启用cluster-enabled yesRedis会尝试启动集群总线占用16379端口与我们的两主两从逻辑冲突导致Pod反复CrashLoopBackOff。注意maxmemory 4gb不是拍脑袋定的。我们按单Pod 8C16G节点资源限制给Redis分配50%内存8GB再预留4GB给OS缓存和AOF重写缓冲区最终设为4GB。实测中当内存使用率达85%时allkeys-lru策略开始淘汰冷key延迟无明显上升。3.2 StatefulSet核心字段解析从模板到真实PodStatefulSet的YAML是整个部署的灵魂我们线上版本关键字段如下精简版apiVersion: apps/v1 kind: StatefulSet metadata: name: redis-cluster namespace: infra spec: serviceName: redis-headless replicas: 4 selector: matchLabels: app: redis-cluster template: metadata: labels: app: redis-cluster spec: containers: - name: redis image: redis:7.0.12-alpine ports: - containerPort: 6379 name: redis - containerPort: 16379 name: cluster-bus # 即使不用Cluster保留端口防冲突 env: - name: REDIS_PASSWORD valueFrom: secretKeyRef: name: redis-secret key: password command: [/bin/sh, -c] args: - | # 初始化逻辑仅在redis-0和redis-2两个Master执行 if [ $HOSTNAME redis-0 ] || [ $HOSTNAME redis-2 ]; then echo Starting as MASTER... exec redis-server /usr/local/etc/redis.conf else # Slave节点启动前等待Master就绪 echo Waiting for MASTER to be ready... until redis-cli -h redis-0.redis-headless.infra.svc.cluster.local -p 6379 -a $REDIS_PASSWORD ping /dev/null 21; do sleep 2 done echo MASTER is ready, starting as SLAVE... exec redis-server /usr/local/etc/redis.conf --slaveof redis-0.redis-headless.infra.svc.cluster.local 6379 fi volumeMounts: - name: redis-data mountPath: /data - name: config mountPath: /usr/local/etc/redis.conf subPath: redis.conf volumes: - name: config configMap: name: redis-config volumeClaimTemplates: - metadata: name: redis-data spec: accessModes: [ReadWriteOnce] resources: requests: storage: 20Gi storageClassName: alicloud-disk-ssd关键逻辑拆解serviceName: redis-headless必须与Headless Service名称一致这是StatefulSet识别服务发现入口的依据。replicas: 4明确声明4个Pod对应两主两从。序号0/1为第一组Master A/Slave A2/3为第二组Master B/Slave B。command/args里的条件启动这是实现“两主两从”的核心。通过$HOSTNAME判断Pod身份Master节点直接启动Slave节点先执行until ... ping循环检测Master是否可连成功后再用--slaveof参数启动。实测中sleep 2间隔足够太短会增加API Server压力太长则延长就绪时间。volumeClaimTemplates自动绑定每个Pod启动时K8s自动创建pvc-redis-cluster-0、pvc-redis-cluster-1等PVC并绑定到对应PV。无需手动创建PVC避免人为失误。3.3 初始化集群的终极方案InitContainer还是Job网上很多教程用InitContainer在Pod启动前执行redis-cli --cluster create但这是严重错误InitContainer在每个Pod里都执行而集群创建命令必须全局唯一执行一次否则会因节点重复加入导致集群状态混乱。我们采用K8s Job 人工触发的方案流程如下先部署StatefulSet等待所有4个Pod进入Running状态kubectl get pods -n infra | grep redis检查Pod日志确认Master已就绪kubectl logs redis-0 -n infra | grep Ready to accept connections执行Job创建集群# 创建job.yaml cat EOF | kubectl apply -f - apiVersion: batch/v1 kind: Job metadata: name: redis-init-cluster namespace: infra spec: template: spec: restartPolicy: Never containers: - name: redis-cli image: redis:7.0.12-alpine command: [/bin/sh, -c] args: - | echo Initializing Redis cluster with 2 masters and 2 slaves... # 构建节点列表master-0, slave-0, master-2, slave-3 redis-cli --cluster create \ redis-0.redis-headless.infra.svc.cluster.local:6379 \ redis-1.redis-headless.infra.svc.cluster.local:6379 \ redis-2.redis-headless.infra.svc.cluster.local:6379 \ redis-3.redis-headless.infra.svc.cluster.local:6379 \ --cluster-replicas 0 \ --cluster-yes \ -a your_strong_password_123 EOF关键参数说明--cluster-replicas 0表示不自动分配从节点因为我们已通过--slaveof手动指定此处仅建立节点元数据关系--cluster-yes跳过交互式确认适配自动化流程-a传入密码否则初始化失败Job执行成功后用kubectl logs job/redis-init-cluster -n infra查看输出应看到类似 Nodes configuration updated和 All nodes agree about slots configuration.的提示。此时集群初始化完成可进入验证环节。4. 实操全流程与验证方法4.1 从零开始的完整部署步骤含避坑清单以下是在阿里云ACK集群K8s 1.24上的实操记录每步附带原理和风险提示步骤1创建命名空间与Secretkubectl create namespace infra kubectl create secret generic redis-secret \ --from-literalpasswordZx9!kL2mN7#pQ4 \ -n infra注意密码必须含大小写字母、数字、特殊字符长度≥12位。线上曾因密码含$符号未转义导致Env注入失败Pod启动报invalid password错误。步骤2部署ConfigMap与Headless Servicekubectl apply -f redis-configmap.yaml -n infra kubectl apply -f redis-headless-svc.yaml -n infra验证DNS解析# 进入临时Pod测试 kubectl run dns-test --imagebusybox:1.35 --rm -it --restartNever --namespaceinfra -- nslookup redis-headless.infra.svc.cluster.local # 应返回4个Pod IP如10.10.2.15, 10.10.2.16...步骤3部署StatefulSetkubectl apply -f redis-statefulset.yaml -n infra观察Pod状态kubectl get pods -n infra -w # 正常流程redis-0 Pending → ContainerCreating → Running → Ready # redis-1 Pending → ...等待redis-0 Ready后才启动 # 若卡在ContainerCreating检查PVC是否绑定成功kubectl get pvc -n infra步骤4等待Pod就绪并执行初始化Job# 等待所有Pod Ready约3-5分钟 kubectl wait --forconditionReady pod -l appredis-cluster -n infra --timeout600s # 查看Master日志确认就绪 kubectl logs redis-0 -n infra | tail -5 # 输出应含Ready to accept connections # 执行初始化Job kubectl apply -f redis-init-job.yaml -n infra # 查看Job日志 kubectl logs job/redis-init-cluster -n infra # 成功标志最后几行显示Success: You can now use your cluster!步骤5验证集群状态# 进入Master节点执行集群检查 kubectl exec -it redis-0 -n infra -- redis-cli -a Zx9!kL2mN7#pQ4 cluster nodes # 输出应显示4个节点role为master或slave且connected状态为connected # 示例片段 # 10.10.2.15:637916379 master - 0 1712345678901 1 connected # 10.10.2.16:637916379 slave 10.10.2.15:6379 0 1712345678902 1 connected # 10.10.2.17:637916379 master - 0 1712345678903 2 connected # 10.10.2.18:637916379 slave 10.10.2.17:6379 0 1712345678904 2 connected4.2 客户端连接与读写分离实测业务服务连接时需配置两个数据源数据源连接地址用途密码Master-Aredis-0.redis-headless.infra.svc.cluster.local:6379写操作Zx9!kL2mN7#pQ4Slave-Aredis-1.redis-headless.infra.svc.cluster.local:6379读操作Zx9!kL2mN7#pQ4Master-Bredis-2.redis-headless.infra.svc.cluster.local:6379写操作Zx9!kL2mN7#pQ4Slave-Bredis-3.redis-headless.infra.svc.cluster.local:6379读操作Zx9!kL2mN7#pQ4我们用Spring Boot Lettuce测试配置如下spring: redis: database: 0 password: Zx9!kL2mN7#pQ4 lettuce: pool: max-active: 50 max-idle: 20 min-idle: 5 # 自定义RedisTemplate实现读写分离压测结果JMeter 200线程单Master写入QPS 12500P99延迟 8msSlave读取QPS 28000P99延迟 5ms故障模拟手动kubectl delete pod redis-011秒后新Pod就绪客户端无感知Lettuce自动重连实操心得不要在应用层做复杂的负载均衡。我们曾用Ribbon对Master节点做轮询结果因两个Master数据不同步分片key规则导致读到脏数据。正确做法是业务层明确路由规则如user_id % 2 0 → Master-Aorder_id % 2 1 → Master-B把一致性保障交给业务逻辑。4.3 持久化与故障恢复验证验证数据不丢失是K8s有状态服务的生命线我们做了三类测试测试1Pod重建不丢数据# 向Master-A写入测试数据 kubectl exec -it redis-0 -n infra -- redis-cli -a Zx9!kL2mN7#pQ4 set test_key test_value_v1 # 删除Pod kubectl delete pod redis-0 -n infra # 等待新Pod就绪后读取 kubectl exec -it redis-0 -n infra -- redis-cli -a Zx9!kL2mN7#pQ4 get test_key # 返回test_value_v1证明PVC绑定成功测试2AOF重写不中断服务# 触发AOF重写 kubectl exec -it redis-0 -n infra -- redis-cli -a Zx9!kL2mN7#pQ4 bgrewriteaof # 监控日志 kubectl logs -f redis-0 -n infra | grep Background AOF rewrite # 应看到Background AOF rewrite finished successfully期间客户端读写正常测试3网络分区恢复# 模拟Slave节点网络断开在Slave Pod内执行 kubectl exec -it redis-1 -n infra -- iptables -A OUTPUT -p tcp --dport 6379 -j DROP # 等待30秒检查复制状态 kubectl exec -it redis-0 -n infra -- redis-cli -a Zx9!kL2mN7#pQ4 info replication | grep slave[0-9] # 应显示slave0:ip10.10.2.16,port6379,stateoffline # 恢复网络 kubectl exec -it redis-1 -n infra -- iptables -D OUTPUT -p tcp --dport 6379 -j DROP # 10秒后检查 kubectl exec -it redis-0 -n infra -- redis-cli -a Zx9!kL2mN7#pQ4 info replication | grep slave0 # 应显示stateonline且offset值持续增长证明同步恢复5. 常见问题排查与独家避坑指南5.1 Pod卡在ContainerCreating90%是存储问题这是部署初期最高频问题现象是kubectl get pods显示redis-0 0/1 ContainerCreating。排查路径如下检查项命令正常输出异常表现解决方案PVC是否创建kubectl get pvc -n infraredis-data-redis-cluster-0 Bound ...Pending状态检查StorageClass是否存在云盘配额是否充足PV是否可用kubectl get pvpv-xxx AvailableFailed状态查看云厂商控制台确认云盘未被其他集群占用节点磁盘空间kubectl describe node node-nameAllocatable: ephemeral-storage: 100Giephemeral-storage: 0清理节点/var/lib/kubelet/pods残留目录独家技巧阿里云ACK集群中若StorageClass名为alicloud-disk-ssd但实际不存在K8s会静默失败。正确做法是先执行kubectl get sc确认存在再部署StatefulSet。我们曾因此浪费3小时最后发现是集群升级后SC名称改为alicloud-disk-ssd-v2。5.2 初始化Job失败端口、密码、DNS三重校验Job日志出现Connection refused或NOAUTH Authentication required按此顺序排查端口连通性redis-cli容器能否ping通Masterkubectl run test-ping --imageredis:7.0.12-alpine --rm -it --restartNever --namespaceinfra -- sh -c ping -c 3 redis-0.redis-headless.infra.svc.cluster.local密码正确性Job中-a参数是否与Secret一致# 临时解密Secret仅调试用 kubectl get secret redis-secret -n infra -o jsonpath{.data.password} | base64 -dDNS解析redis-cli容器内能否解析域名kubectl run test-dns --imagebusybox:1.35 --rm -it --restartNever --namespaceinfra -- nslookup redis-0.redis-headless.infra.svc.cluster.local注意Job容器默认用defaultServiceAccount若集群启用了PodSecurityPolicy需为其绑定psp:privileged权限否则redis-cli无法建立TCP连接。线上曾因此报错Operation not permitted解决方案是创建专用ServiceAccountapiVersion: v1 kind: ServiceAccount metadata: name: redis-init-sa namespace: infra --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: name: redis-init-rb namespace: infra subjects: - kind: ServiceAccount name: redis-init-sa roleRef: kind: ClusterRole name: system:node-proxier apiGroup: rbac.authorization.k8s.io5.3 从节点同步延迟高网络与配置双优化现象info replication显示slave0:lag120延迟120秒远超预期。原因及对策原因诊断命令优化方案网络带宽不足kubectl top pods -n infra查看Pod网络IO将Redis Pod调度到同可用区节点减少跨AZ流量AOF重写竞争IOkubectl exec redis-0 -n infra -- iostat -x 1 3调整auto-aof-rewrite-percentage从100%降至50%降低重写频率repl-backlog过小redis-cli info replicationgrep backlog我们线上最终配置repl-backlog-size 10mb repl-backlog-ttl 7200 auto-aof-rewrite-percentage 50 auto-aof-rewrite-min-size 32mb实测后lag值稳定在0-3秒内。5.4 监控告警配置建议不止看CPU内存K8s环境下的Redis监控必须覆盖以下维度Prometheus Grafana指标Prometheus查询语句告警阈值业务影响主从延迟redis_replication_lag_seconds{jobredis} 3030秒读取脏数据风险连接数饱和redis_connected_clients{jobredis} / redis_config_maxclients{jobredis} 0.880%新连接拒绝业务超时内存使用率redis_memory_used_bytes{jobredis} / redis_config_maxmemory_bytes{jobredis} 0.8585%LRU淘汰加剧缓存命中率下降持久化失败redis_rdb_last_bgsave_status{jobredis} 0 or redis_aof_last_bgrewrite_status{jobredis} 0status0数据丢失风险实操心得不要依赖redis_up{jobredis} 0作为唯一告警。我们曾遇到Redis进程存活但INFO命令无响应的情况此时redis_up仍为1但业务已不可用。正确做法是添加probe_success{jobredis-exporter} 0用redis-exporter的HTTP探针双重校验。6. 后续演进与扩展思考这套两主两从架构在线上稳定运行一年半支撑了日均12亿次缓存访问。但技术没有终点我们已在规划下一步自动扩缩容当前固定4节点但大促期间流量可能翻倍。计划接入KEDA基于redis_connected_clients指标触发HorizontalPodAutoscaler动态增加Slave节点分担读压力。注意新增Slave需手动执行SLAVEOF命令我们正开发Operator自动完成此流程。多活容灾当前单集群部署异地多活是终极目标。方案是用RedisGears实现跨集群数据同步Master-A写入自动同步到异地集群的Master-A通过DNS切换实现秒级故障转移。难点在于冲突解决我们倾向用“最后写入胜出”LWW策略配合业务层生成单调递增时间戳。安全加固当前密码通过Secret注入但TLS加密尚未启用。计划用cert-manager自动签发证书配置tls-port 6380和tls-cert-file等参数让所有流量走TLS。不过要注意Lettuce客户端需升级到6.2版本才支持TLS SNI。最后分享一个血泪教训永远在CI/CD流水线里加入冒烟测试。我们曾因ConfigMap配置项拼写错误maxmenory误写为maxmemory导致Redis启动时忽略该参数内存无限制增长最终OOM Killer干掉进程。现在流水线强制执行# 部署前校验 kubectl apply -f redis-configmap.yaml --dry-runclient -o yaml | redis-check-conf /dev/stdin # 必须返回Configuration OK技术没有银弹只有把每个细节钉死才能让缓存层真正成为业务的基石而不是定时炸弹。
阅读完成 · 觉得有帮助?