k8s运维实践:高可用Redis Cluster(三主三从)与Proxy部署方案
k8s运维实践:高可用Redis Cluster(三主三从)与Proxy部署方案
文章目录
- k8s运维实践:高可用Redis Cluster(三主三从)与Proxy部署方案
- 一、Redis Cluster与Proxy简介
- 二、Redis Cluster实战部署
- 1. 创建Namespace(redis-cluster-namespace.yaml)
- 2. 创建ConfigMap(redis-cluster-config.yaml)
- 3. 创建Service(redis-cluster-svc.yaml)
- 4. 创建StatefulSet(redis-cluster-statefulset.yaml)
- 5. 创建 Job(init-cluster-job.yaml)
- 6. 部署所有资源
- 三、验证Redis Cluster部署
- 1. 验证Pod状态
- 2. 验证集群初始化
- 3. 验证基础功能
- 四、Redis Cluster Proxy实战部署
- 1. Redis Proxy 的价值
- 2. 创建Redis Cluster Proxy(redis-cluster-proxy-deploy.yaml)
- 3. 部署资源
- 五、验证Redis Cluster Proxy部署
- 1. 验证Pod状态
- 2. 验证Proxy路由
- 总结
随着容器化和微服务架构的普及,系统架构日益复杂,传统的单机数据库部署模式已难以满足高可用和高性能的需求。Redis Cluster 通过分片与主从复制机制,实现了数据的水平扩展与高可用性,而 Redis Proxy 则可以为集群客户端提供统一访问入口,简化应用侧的集群访问逻辑。
在本系列中,我们将围绕 Redis Cluster 在 Kubernetes 中的部署与实践展开介绍。本篇将聚焦于在 Kubernetes 上部署 Redis Cluster,并配置 Redis Cluster Proxy,实现对多节点 Redis 集群的统一访问,为后续的高可用运维、性能调优和集群监控打下基础。
一、Redis Cluster与Proxy简介
Redis Cluster 通过 分片(Sharding)
与 主从复制(Replication)
来实现高可用与水平扩展。整个集群被划分为 16384 个槽位(slots)
,每个节点负责其中一部分槽位,客户端根据 key 的哈希值自动路由到对应节点。为了保证高可用,每个主节点(Master)通常配有一个或多个从节点(Replica),在主节点故障时从节点可自动提升为新的主节点。
Redis Cluster Proxy 就是“应用与 Redis Cluster 之间的适配层”,让应用像使用单机 Redis 一样去访问分布式 Redis 集群,可以极大简化开发和运维工作。
在 Kubernetes 环境下,Redis Cluster + Proxy 的组合不仅能满足 高可用(HA)
和 可扩展性
的需求,还能简化应用侧的访问逻辑,是容器化环境下 Redis 的主流部署方式。
二、Redis Cluster实战部署
1. 创建Namespace(redis-cluster-namespace.yaml)
创建名为redis-cluster
的命名空间,用于隔离部署 Redis 集群及相关组件的资源
apiVersion: v1
kind: Namespace
metadata:name: redis-cluster
2. 创建ConfigMap(redis-cluster-config.yaml)
定义 Redis Cluster 的配置文件,用于设置节点端口、集群模式、持久化方式、最大内存策略等核心参数,确保集群能够在 Kubernetes 环境下稳定运行
apiVersion: v1
kind: ConfigMap
metadata:name: redis-confignamespace: redis-cluster
data:redis.conf: |port 6379requirepass Hwj123masterauth Hwj123# 开启cluster 模式cluster-enabled yes# 指定 cluster 配置文件(自动生成)cluster-config-file nodes.conf# 节点超时时间cluster-node-timeout 5000appendonly yesappendfilename "appendonly.aof"appendfsync everysecrdbcompression yesdir /dataloglevel noticelogfile ""protected-mode no
3. 创建Service(redis-cluster-svc.yaml)
在 Redis Cluster 中通常需要两类 Service:
- Headless Service (
redis-headless
):通过clusterIP: None
实现无头服务,主要用于 StatefulSet 中的 Pod 之间进行集群节点发现和内部通信。 - 访问 Service (
redis-access
):通过NodePort
暴露 Redis 端口(6379),方便外部客户端直接访问集群。
apiVersion: v1
kind: Service
metadata:name: redis-headlessnamespace: redis-cluster
spec:clusterIP: Noneselector:app: redisports:- port: 6379
---
apiVersion: v1
kind: Service
metadata:name: redis-accessnamespace: redis-cluster
spec:type: NodePortselector:app: redisports:- port: 6379 # Pod 内部端口targetPort: 6379 # Pod 容器端口nodePort: 30079 # 指定 NodePort
4. 创建StatefulSet(redis-cluster-statefulset.yaml)
部署 Redis Cluster 节点,使用 StatefulSet 确保每个 Pod 都有固定的网络标识和持久化存储。该配置共创建 6 个副本,每个 Pod 挂载持久化卷存储数据,并通过 ConfigMap 加载 redis.conf
配置文件。结合前面创建的 Headless Service
,可以实现集群节点间的自动发现与通信
apiVersion: apps/v1
kind: StatefulSet
metadata:name: redisnamespace: redis-cluster
spec:serviceName: redis-headless # 绑定的无头服务(用于稳定的 DNS 解析)replicas: 6 # 6个副本(用于3主3从)selector:matchLabels:app: redistemplate:metadata:labels:app: redisspec:affinity:nodeAffinity: # 节点亲和性requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:- matchExpressions:- key: "kubernetes.io/hostname"operator: "NotIn"values:- "k8s-node-4-gpu"containers:- name: redisimage: harbor.local/k8s/redis:7.2command: [ "redis-server", "/usr/local/etc/redis/redis.conf" ]ports:- containerPort: 6379volumeMounts:- name: datamountPath: /data- name: configmountPath: /usr/local/etc/redis/redis.confsubPath: redis.confvolumes:- name: configconfigMap:name: redis-configvolumeClaimTemplates:- metadata:name: dataspec:accessModes: [ "ReadWriteOnce" ]storageClassName: nfs-storage # 使用的存储类(NFS)resources:requests:storage: 1Gi
5. 创建 Job(init-cluster-job.yaml)
在 Redis Cluster 所有 Pod 启动完成后,需要执行初始化操作将各节点组建为一个完整的集群。这里通过 Kubernetes 的 Job 来完成初始化任务:
- 等待 Redis Pod 就绪后,调用
redis-cli --cluster create
命令。 - 将 6 个节点自动组建为 Redis Cluster,并设置副本数(
--cluster-replicas 1
)。 - 使用
--cluster-yes
自动确认集群创建过程,避免交互式输入。
该 Job 只需在集群初始化时执行一次,执行完成后会自动退出。
apiVersion: batch/v1
kind: Job
metadata:name: redis-init-clusternamespace: redis-cluster
spec:template:spec:containers:- name: redis-initimage: harbor.local/k8s/redis:7.2command:- sh- -c- |echo "Waiting for Redis pods to be ready...";sleep 10;redis-cli --cluster create \redis-0.redis-headless.redis-cluster.svc.cluster.local:6379 \redis-1.redis-headless.redis-cluster.svc.cluster.local:6379 \redis-2.redis-headless.redis-cluster.svc.cluster.local:6379 \redis-3.redis-headless.redis-cluster.svc.cluster.local:6379 \redis-4.redis-headless.redis-cluster.svc.cluster.local:6379 \redis-5.redis-headless.redis-cluster.svc.cluster.local:6379 \--cluster-replicas 1 -a Hwj123 --cluster-yesrestartPolicy: OnFailure
6. 部署所有资源
kubectl apply -f redis-cluster-namespace.yaml
kubectl apply -f redis-cluster-config.yaml
kubectl apply -f redis-cluster-svc.yaml
kubectl apply -f redis-cluster-statefulset.yaml
kubectl apply -f init-cluster-job.yaml
三、验证Redis Cluster部署
1. 验证Pod状态
kubectl get pod -n redis-cluster -owide
2. 验证集群初始化
kubectl logs -f -n redis-cluster redis-init-cluster-zl7qr
3. 验证基础功能
kubectl exec -it -n redis-cluster redis-0 -- /bin/bashroot@redis-0:/data# redis-cli -c
127.0.0.1:6379> auth Hwj123
OK
127.0.0.1:6379> client list
id=20 addr=10.100.3.165:37282 laddr=10.100.3.163:6379 fd=28 name= age=81214 idle=81214 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=0 qbuf-free=0 argv-mem=0 multi-mem=0 rbs=1024 rbp=0 obl=0 oll=0 omem=0 tot-mem=1928 events=r cmd=cluster|nodes user=default redir=-1 resp=2 lib-name= lib-ver=
id=21 addr=10.100.3.165:37292 laddr=10.100.3.163:6379 fd=29 name= age=81214 idle=81214 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=0 qbuf-free=0 argv-mem=0 multi-mem=0 rbs=1024 rbp=0 obl=0 oll=0 omem=0 tot-mem=1928 events=r cmd=cluster|nodes user=default redir=-1 resp=2 lib-name= lib-ver=
id=22 addr=10.100.3.165:37298 laddr=10.100.3.163:6379 fd=30 name= age=81214 idle=81214 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=0 qbuf-free=0 argv-mem=0 multi-mem=0 rbs=1024 rbp=0 obl=0 oll=0 omem=0 tot-mem=1928 events=r cmd=cluster|nodes user=default redir=-1 resp=2 lib-name= lib-ver=
id=5 addr=10.100.2.219:52984 laddr=10.100.3.163:6379 fd=23 name= age=82790 idle=0 flags=S db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=0 qbuf-free=20474 argv-mem=0 multi-mem=0 rbs=1024 rbp=0 obl=0 oll=1 omem=20504 tot-mem=42904 events=r cmd=replconf user=default redir=-1 resp=2 lib-name= lib-ver=
id=15 addr=10.100.3.165:37234 laddr=10.100.3.163:6379 fd=12 name= age=81214 idle=575 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=0 qbuf-free=0 argv-mem=0 multi-mem=0 rbs=1024 rbp=0 obl=0 oll=0 omem=0 tot-mem=1928 events=r cmd=scan user=default redir=-1 resp=2 lib-name= lib-ver=
id=16 addr=10.100.3.165:37244 laddr=10.100.3.163:6379 fd=24 name= age=81214 idle=81214 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=0 qbuf-free=0 argv-mem=0 multi-mem=0 rbs=1024 rbp=0 obl=0 oll=0 omem=0 tot-mem=1928 events=r cmd=cluster|nodes user=default redir=-1 resp=2 lib-name= lib-ver=
id=17 addr=10.100.3.165:37258 laddr=10.100.3.163:6379 fd=25 name= age=81214 idle=81214 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=0 qbuf-free=0 argv-mem=0 multi-mem=0 rbs=1024 rbp=0 obl=0 oll=0 omem=0 tot-mem=1928 events=r cmd=cluster|nodes user=default redir=-1 resp=2 lib-name= lib-ver=
id=18 addr=10.100.3.165:37262 laddr=10.100.3.163:6379 fd=26 name= age=81214 idle=81214 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=0 qbuf-free=0 argv-mem=0 multi-mem=0 rbs=1024 rbp=0 obl=0 oll=0 omem=0 tot-mem=1928 events=r cmd=cluster|nodes user=default redir=-1 resp=2 lib-name= lib-ver=
id=19 addr=10.100.3.165:37278 laddr=10.100.3.163:6379 fd=27 name= age=81214 idle=81214 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=0 qbuf-free=0 argv-mem=0 multi-mem=0 rbs=1024 rbp=0 obl=0 oll=0 omem=0 tot-mem=1928 events=r cmd=cluster|nodes user=default redir=-1 resp=2 lib-name= lib-ver=
id=24 addr=127.0.0.1:49596 laddr=127.0.0.1:6379 fd=31 name= age=8 idle=0 flags=N db=0 sub=0 psub=0 ssub=0 multi=-1 qbuf=26 qbuf-free=20448 argv-mem=10 multi-mem=0 rbs=1024 rbp=0 obl=0 oll=0 omem=0 tot-mem=22426 events=r cmd=client|list user=default redir=-1 resp=2 lib-name= lib-ver=
127.0.0.1:6379> set aa bb
OK
127.0.0.1:6379> get aa
"bb"
四、Redis Cluster Proxy实战部署
在前面的步骤中,我们已经成功在 Kubernetes 上部署了 Redis Cluster,并通过 StatefulSet 管理多个节点。虽然集群具备了高可用和水平扩展能力,但在应用侧访问 Redis Cluster时,仍然会遇到以下问题:
1. 客户端需要感知集群拓扑
- Redis Cluster 是基于
16384 个槽位(slot)
分片的,每个 key 会被映射到不同节点 - 如果客户端直接连接单个节点,就会遇到
MOVED
或ASK
重定向,需要自己实现转发逻辑 - 这要求应用必须使用支持 Redis Cluster 协议的客户端,否则访问很麻烦
2. 节点扩缩容时拓扑会变化
- 当我们对 Redis Cluster 进行扩容或缩容时,槽位会被重新分配
- 客户端必须实时刷新集群拓扑信息,否则可能访问失败
- 在大规模微服务系统中,这会显著增加客户端的复杂度和出错风险
3. 无法直接使用 NodePort/LoadBalancer 简化访问
- Redis Cluster 是多节点分布式的,单一 Service 并不能屏蔽集群的分片机制
- 客户端即使通过
NodePort
连接,也还是会遇到MOVED
,无法像访问单机 Redis 那样简单
1. Redis Proxy 的价值
Redis Cluster Proxy 的出现,正是为了解决这些痛点:
- 统一入口:对外暴露一个
统一
的地址(如 redis-proxy:7777),应用端像访问单机 Redis 一样使用,无需关心槽位 - 自动路由:Proxy 会解析请求的 key,找到对应的节点并转发请求,屏蔽集群内部的复杂性
- 拓扑变化无感:当集群扩缩容时,Proxy 会自动感知并更新路由表,应用侧无需改动
- 简化应用开发:应用可以使用任何标准的 Redis 客户端(即使不支持 cluster 模式),也能无障碍访问集群
2. 创建Redis Cluster Proxy(redis-cluster-proxy-deploy.yaml)
这里我们通过 Deployment 部署 Redis Cluster Proxy,并通过 Service 将其对外暴露:
- 启动一个 Proxy 实例(可根据需求水平扩展)
- 在容器启动参数中配置认证密码(–auth)和 Redis Cluster 节点地址,Proxy 会自动从指定节点获取集群拓扑信息
- 暴露 Proxy 的端口(7777),这里采用 NodePort 方式对外提供访问,也可以根据环境换成 LoadBalancer 或 Ingress
- 应用只需连接 Proxy 的统一入口(如 NodeIP:30090),即可透明访问整个 Redis Cluster,无需关心集群拓扑与槽位分布
apiVersion: apps/v1
kind: Deployment
metadata:name: redis-cluster-proxynamespace: redis-cluster
spec:replicas: 1selector:matchLabels:app: redis-cluster-proxytemplate:metadata:labels:app: redis-cluster-proxyspec:affinity:nodeAffinity:requiredDuringSchedulingIgnoredDuringExecution:nodeSelectorTerms:- matchExpressions:- key: "kubernetes.io/hostname"operator: "NotIn"values:- "k8s-node-4-gpu"containers:- name: redis-cluster-proxy# 官方镜像地址:ableuler/redis-cluster-proxy:latestimage: harbor.local/k8s/redis-cluster-proxy:latest command: ["redis-cluster-proxy"] # 显式指定启动命令# 入口配置,Proxy 会自动发现并代理整个 Redis Clusterargs:- "--auth"- "Hwj123"- "redis-0.redis-headless.redis-cluster.svc.cluster.local:6379"- "redis-1.redis-readless.redis-cluster.svc.cluster.local:6379"- "redis-2.redis-readless.redis-cluster.svc.cluster.local:6379"ports:- containerPort: 7777
---
apiVersion: v1
kind: Service
metadata:name: redis-proxynamespace: redis-cluster
spec:type: NodePort # 你也可以换成 LoadBalancer/Ingressselector:app: redis-cluster-proxyports:- port: 7777targetPort: 7777nodePort: 30090 # 对外暴露的端口
3. 部署资源
kubectl apply -f redis-cluster-proxy-deploy.yaml
五、验证Redis Cluster Proxy部署
1. 验证Pod状态
kubectl get pod -n redis-cluster
kubectl get svc -n redis-cluster
2. 验证Proxy路由
这里我们做个测试,通过客户端工具连接到 Redis Cluster Proxy,执行简单的读写操作(如 SET / GET),以验证 Proxy 的透明访问能力,随后我们进入 redis-0 Pod 查看该 key,可以看到数据实际被分配到 redis-2 Pod 上。经验证,Proxy 成功完成了请求转发与路由
总结
🚀 本文介绍了如何在 Kubernetes 集群中部署 Redis Cluster,并通过 StatefulSet、Headless Service、ConfigMap、Job 等资源完成集群的搭建与初始化。随后引入 Redis Cluster Proxy,解决了原生集群客户端需要感知槽位迁移的问题,使应用能够像使用单节点 Redis 一样透明访问集群。