# Redis Sentinel 部署问题诊断与修复报告 **问题日期:** 2026年2月22日 **命名空间:** juwan **涉及资源:** user-rpc deployment, RedisSentinel --- ## 📋 目录 1. [问题背景](#问题背景) 2. [问题现象](#问题现象) 3. [诊断过程](#诊断过程) 4. [根因分析](#根因分析) 5. [解决方案](#解决方案) 6. [修复步骤](#修复步骤) 7. [验证结果](#验证结果) 8. [后续建议](#后续建议) --- ## 🎯 问题背景 ### 部署目标 部署一个简单的三节点 Redis Sentinel 哨兵集群作为缓存服务,供 user-rpc 服务使用。后续如有需要再扩展为分片集群。 ### 初始配置 在 `deploy/k8s/service/user/user-rpc.yaml` 中配置了: - user-rpc Deployment(3副本) - user-rpc Service - HPA(CPU和内存) - **RedisSentinel 资源** - PostgreSQL Cluster --- ## 🔴 问题现象 ### 执行的操作 ```bash kubectl apply -f .\deploy\k8s\service\user\user-rpc.yaml ``` ### 输出结果 ``` deployment.apps/user-rpc configured service/user-rpc-svc unchanged horizontalpodautoscaler.autoscaling/user-rpc-hpa-c unchanged horizontalpodautoscaler.autoscaling/user-rpc-hpa-m unchanged redissentinel.redis.redis.opstreelabs.in/user-redis unchanged cluster.postgresql.cnpg.io/user-db unchanged ``` ### 观察到的异常 查看命名空间资源: ```bash kubectl get all -n juwan ``` **发现:** - ✅ user-api pods 正常运行 - ✅ user-rpc pods 正常运行 - ✅ PostgreSQL clusters 正常运行 - ❌ **没有任何 Redis 相关的 Pod** - ❌ **没有 Redis Service** --- ## 🔍 诊断过程 ### 步骤 1:检查 RedisSentinel 资源状态 **目的:** 确认 RedisSentinel 资源是否被成功创建 **命令:** ```bash kubectl get redissentinel user-redis -n juwan ``` **输出:** ``` NAME AGE user-redis 9m56s ``` **分析:** - ✅ RedisSentinel 资源已创建 - ❌ 但没有创建任何 Pod - **结论:** Operator 没有按照 RedisSentinel 规格创建实际资源 --- ### 步骤 2:查看 RedisSentinel 详细信息 **目的:** 检查资源的详细配置和事件 **命令:** ```bash kubectl describe redissentinel user-redis -n juwan ``` **关键输出:** ```yaml API Version: redis.redis.opstreelabs.in/v1beta2 Kind: RedisSentinel Metadata: Creation Timestamp: 2026-02-22T12:41:47Z Finalizers: redisSentinelFinalizer Generation: 2 Spec: Cluster Size: 3 Redis Sentinel Config: Redis Replication Name: user-redis # ⚠️ 关键配置 Master Group Name: myMaster Redis Port: 6379 Quorum: 2 Events: # ⚠️ 没有任何事件 ``` **分析:** - 配置中引用了 `redisReplicationName: user-redis` - 没有任何 Events,说明 Operator 可能在等待某些前置条件 - **需要进一步检查依赖资源** --- ### 步骤 3:检查 Secret 资源 **目的:** 确认 RedisSentinel 所需的密码 Secret 是否存在 **命令:** ```bash kubectl get secret user-redis -n juwan ``` **输出:** ``` NAME TYPE DATA AGE user-redis Opaque 1 5h48m ``` **分析:** - ✅ Secret 存在,排除认证配置问题 --- ### 步骤 4:查找 Redis Pods **目的:** 确认是否有 Redis Pod 使用不同的命名规则 **命令:** ```bash kubectl get pods -n juwan | Select-String redis ``` **输出:** ``` (空输出 - 没有找到任何 Redis Pod) ``` **分析:** - ❌ 确认没有任何 Redis Pod 被创建 --- ### 步骤 5:检查 StatefulSet **目的:** Redis 通常使用 StatefulSet 部署,检查是否有相关资源 **命令:** ```bash kubectl get statefulset -n juwan ``` **输出:** ``` No resources found in juwan namespace. ``` **分析:** - ❌ 没有 StatefulSet 被创建 - **结论:** Operator 完全没有执行创建动作 --- ### 步骤 6:检查 Redis Operator 状态 **目的:** 确认 Redis Operator 本身是否正常运行 **命令:** ```bash kubectl get pods -A | Select-String redis-operator ``` **输出:** ``` default redis-operator-7dcf4468c9-gfbcm 1/1 Running 0 4h50m ot-operators redis-operator-69bb645f5b-6kxkv 0/1 ErrImageNeverPull 0 4h48m ``` **分析:** - ✅ default namespace 中的 Operator 正常运行 - ⚠️ ot-operators namespace 中的 Operator 镜像拉取失败(但不影响我们的场景) --- ### 步骤 7:查看 Operator 日志 **目的:** 从 Operator 日志中寻找线索 **命令:** ```bash kubectl logs redis-operator-7dcf4468c9-gfbcm -n default --tail=50 ``` **关键输出:** ```json {"level":"info","ts":"2026-02-22T08:01:56Z","msg":"Starting Controller","controller":"redissentinel"} {"level":"info","ts":"2026-02-22T08:01:56Z","msg":"Starting workers","controller":"redissentinel","worker count":1} ``` **分析:** - ✅ RedisSentinel Controller 已启动 - ✅ 没有错误日志 - ❌ 但也没有处理 user-redis 资源的日志 - **推测:** Operator 在等待某个依赖资源 --- ### 步骤 8:检查 RedisReplication 资源(关键发现) **目的:** 根据 RedisSentinel 配置中的 `redisReplicationName: user-redis`,检查对应的 RedisReplication 是否存在 **命令:** ```bash kubectl get redisreplication -n juwan ``` **输出:** ``` No resources found in juwan namespace. ``` **分析:** - ❌ **RedisReplication 资源不存在!** - 🔎 **这就是问题的根本原因** --- ## 💡 根因分析 ### 问题根源 **RedisSentinel 依赖 RedisReplication,但配置中只创建了 RedisSentinel,没有创建 RedisReplication。** ### Redis Operator 架构理解 在 OpsTree Redis Operator 中,资源之间的关系如下: ``` ┌─────────────────────────────────────────┐ │ RedisSentinel (哨兵层) │ │ - 3个 Sentinel 节点 │ │ - 负责监控和自动故障转移 │ │ - 引用: redisReplicationName │ └──────────────┬──────────────────────────┘ │ 监控 ↓ ┌─────────────────────────────────────────┐ │ RedisReplication (数据层) │ │ - 1个 Master + N个 Replica │ │ - 提供实际的缓存服务 │ │ - 主从复制 │ └─────────────────────────────────────────┘ ``` ### 错误配置的问题 原始配置直接创建了 RedisSentinel,但: 1. **缺少被监控对象:** Sentinel 需要监控一个 RedisReplication 集群 2. **引用不存在的资源:** `redisReplicationName: user-redis` 指向一个不存在的 RedisReplication 3. **Operator 行为:** Operator 发现依赖的 RedisReplication 不存在,因此不会创建 Sentinel Pod ### 为什么没有错误提示? - CRD 验证只检查语法和字段类型 - 资源引用关系由 Operator 运行时检查 - Operator 采用了"等待依赖"策略,而不是报错 --- ## ✅ 解决方案 ### 正确的部署顺序 1. **先创建 RedisReplication**(建立 Redis 主从复制集群) 2. **再创建 RedisSentinel**(监控上述复制集群) ### 配置结构 ```yaml # 第一步:创建 Redis 主从复制(数据层) apiVersion: redis.redis.opstreelabs.in/v1beta2 kind: RedisReplication metadata: name: user-redis # Sentinel 将引用这个名称 namespace: juwan spec: clusterSize: 3 # 1 Master + 2 Replicas kubernetesConfig: image: quay.io/opstree/redis:v7.0.12 resources: requests: cpu: 100m memory: 128Mi limits: cpu: 500m memory: 512Mi redisSecret: name: user-redis key: password storage: volumeClaimTemplate: spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 1Gi # 每个 Redis 节点 1GB 存储 --- # 第二步:创建 Sentinel 监控(监控层) apiVersion: redis.redis.opstreelabs.in/v1beta2 kind: RedisSentinel metadata: name: user-redis-sentinel # 使用不同的名称避免混淆 namespace: juwan spec: clusterSize: 3 # 3个 Sentinel 节点(推荐奇数) kubernetesConfig: image: quay.io/opstree/redis-sentinel:v7.0.12 # 使用 Sentinel 专用镜像 redisSentinelConfig: redisReplicationName: user-redis # 引用上面的 RedisReplication masterGroupName: mymaster quorum: "2" # 需要 2 个 Sentinel 同意才能进行故障转移 ``` --- ## 🔧 修复步骤 ### 步骤 1:删除错误的 RedisSentinel 资源 **命令:** ```bash kubectl delete redissentinel user-redis -n juwan ``` **输出:** ``` redissentinel.redis.redis.opstreelabs.in "user-redis" deleted ``` **说明:** 删除仅创建了 CRD 实例但未创建实际 Pod 的资源 --- ### 步骤 2:更新配置文件 修改 `deploy/k8s/service/user/user-rpc.yaml`,将单独的 RedisSentinel 替换为: 1. RedisReplication(数据层) 2. RedisSentinel(监控层) **变更内容:** - 添加 `RedisReplication` 资源定义 - 添加 `storage.volumeClaimTemplate` 配置 - 修改 RedisSentinel 的 `metadata.name` 为 `user-redis-sentinel` - 使用正确的 Sentinel 镜像:`quay.io/opstree/redis-sentinel:v7.0.12` - 完善 Sentinel 配置参数 --- ### 步骤 3:应用更新后的配置 **命令:** ```bash kubectl apply -f .\deploy\k8s\service\user\user-rpc.yaml ``` **输出:** ``` deployment.apps/user-rpc configured service/user-rpc-svc unchanged horizontalpodautoscaler.autoscaling/user-rpc-hpa-c unchanged horizontalpodautoscaler.autoscaling/user-rpc-hpa-m unchanged redisreplication.redis.redis.opstreelabs.in/user-redis created ✅ redissentinel.redis.redis.opstreelabs.in/user-redis-sentinel created ✅ cluster.postgresql.cnpg.io/user-db unchanged ``` **分析:** - ✅ RedisReplication 成功创建 - ✅ RedisSentinel 成功创建 - 🎯 两个资源都是新创建(created),符合预期 --- ## ✅ 验证结果 ### 验证 1:检查 Pod 创建情况(等待 30 秒) **命令:** ```bash kubectl get statefulset,pods -n juwan | Select-String -Pattern "user-redis|NAME" ``` **输出:** ``` NAME READY AGE statefulset.apps/user-redis 3/3 81s ✅ statefulset.apps/user-redis-sentinel-sentinel 3/3 24s ✅ NAME READY STATUS RESTARTS AGE pod/user-redis-0 2/2 Running 0 80s ✅ pod/user-redis-1 2/2 Running 0 52s ✅ pod/user-redis-2 2/2 Running 0 47s ✅ pod/user-redis-sentinel-sentinel-0 1/1 Running 0 24s ✅ pod/user-redis-sentinel-sentinel-1 1/1 Running 0 8s ✅ pod/user-redis-sentinel-sentinel-2 1/1 Running 0 5s ✅ ``` **分析:** - ✅ **RedisReplication** 创建了 3 个 Pod(user-redis-0/1/2) - 每个 Pod 有 2 个容器(2/2):Redis + Exporter - 所有 Pod 处于 Running 状态 - ✅ **RedisSentinel** 创建了 3 个 Pod(user-redis-sentinel-sentinel-0/1/2) - 每个 Pod 有 1 个容器(1/1):Sentinel - 所有 Pod 处于 Running 状态 - ✅ 创建了 2 个 StatefulSet,READY 状态为 3/3 --- ### 验证 2:检查 Service 资源 **命令:** ```bash kubectl get svc -n juwan | Select-String -Pattern "redis|NAME" ``` **输出:** ``` NAME TYPE CLUSTER-IP PORT(S) AGE user-redis ClusterIP 10.103.91.84 6379/TCP,9121/TCP 95s ✅ user-redis-additional ClusterIP 10.107.228.48 6379/TCP 95s user-redis-headless ClusterIP None 6379/TCP 95s ✅ user-redis-master ClusterIP 10.97.120.76 6379/TCP 95s ✅ user-redis-replica ClusterIP 10.100.213.103 6379/TCP 95s ✅ user-redis-sentinel-sentinel ClusterIP 10.105.28.231 26379/TCP 40s ✅ user-redis-sentinel-sentinel-additional ClusterIP 10.97.111.42 26379/TCP 39s user-redis-sentinel-sentinel-headless ClusterIP None 26379/TCP 41s ``` **Service 功能说明:** #### Redis 数据层 Service(端口 6379) - **user-redis-master**: 主节点服务,用于写操作 - **user-redis-replica**: 从节点服务,用于读操作 - **user-redis**: 通用访问入口(负载均衡到所有节点) - **user-redis-headless**: 无头服务,用于 StatefulSet Pod 间通信 - **user-redis-additional**: 额外的访问入口 #### Sentinel 监控层 Service(端口 26379) - **user-redis-sentinel-sentinel**: Sentinel 访问入口 - **user-redis-sentinel-sentinel-headless**: Sentinel 节点间通信 - **user-redis-sentinel-sentinel-additional**: 额外的 Sentinel 访问入口 --- ### 验证 3:检查完整的集群状态 **命令:** ```bash kubectl get all -n juwan ``` **最终状态统计:** | 资源类型 | 名称 | 数量 | 状态 | |---------|------|------|------| | **Deployment** | user-api | 3/3 | ✅ Running | | **Deployment** | user-rpc | 3/3 | ✅ Running | | **StatefulSet** | cluster-example (PostgreSQL) | 3/3 | ✅ Running | | **StatefulSet** | user-db (PostgreSQL) | 3/3 | ✅ Running | | **StatefulSet** | user-redis (Redis 数据) | 3/3 | ✅ Running | | **StatefulSet** | user-redis-sentinel-sentinel | 3/3 | ✅ Running | **Pod 总计:** 18 个(全部 Running) **Service 总计:** 13 个 **HPA 总计:** 6 个 --- ## 📊 架构图 ### 部署后的 Redis 架构 ``` ┌────────────────────────────────────────────────────────────┐ │ 应用层 (user-rpc) │ │ │ │ [需要添加 Redis 连接配置] │ └──────────┬─────────────────────────────┬───────────────────┘ │ │ │ 写操作 │ 读操作 ↓ ↓ ┌─────────────┐ ┌─────────────┐ │ user-redis- │ │ user-redis- │ │ master │ │ replica │ │ Service │ │ Service │ └─────────────┘ └─────────────┘ │ │ └──────────┬──────────────────┘ ↓ ┌──────────────────────────────────────────┐ │ RedisReplication (数据层) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌───────┐ │ │ │ Master │→ │ Replica │→ │Replica│ │ │ │ redis-0 │ │ redis-1 │ │redis-2│ │ │ └──────────┘ └──────────┘ └───────┘ │ └──────────────────────────────────────────┘ ↑ │ 监控 & 故障转移 │ ┌──────────────────────────────────────────┐ │ RedisSentinel (监控层) │ │ │ │ ┌──────────┐ ┌──────────┐ ┌───────┐ │ │ │Sentinel-0│ │Sentinel-1│ │Sentinel-2│ │ └──────────┘ └──────────┘ └───────┘ │ │ │ │ Quorum: 2/3 (多数派决策) │ └──────────────────────────────────────────┘ ``` --- ## 📝 后续建议 ### 1. 应用集成 Redis user-rpc 服务目前还没有配置 Redis 连接,需要: #### 修改配置文件 `app/users/rpc/etc/pb.yaml` ```yaml Name: pb.rpc ListenOn: 0.0.0.0:8080 # 添加 Redis 配置(使用 Sentinel 模式) Redis: - Host: user-redis-sentinel-sentinel:26379 Type: sentinel MasterName: mymaster Pass: ${REDIS_PASSWORD} # 或使用主从模式 # Redis: # - Host: user-redis-master:6379 # 写 # Type: node # Pass: ${REDIS_PASSWORD} # - Host: user-redis-replica:6379 # 读 # Type: node # Pass: ${REDIS_PASSWORD} Etcd: Hosts: - etcd-service:2379 # 需要配置实际的 Etcd 地址 Key: pb.rpc ``` #### 修改 Config 结构 `app/users/rpc/internal/config/config.go` ```go package config import ( "github.com/zeromicro/go-zero/core/stores/redis" "github.com/zeromicro/go-zero/zrpc" ) type Config struct { zrpc.RpcServerConf Redis redis.RedisConf // 添加 Redis 配置 } ``` #### 初始化 Redis 客户端 `app/users/rpc/internal/svc/serviceContext.go` ```go package svc import ( "github.com/zeromicro/go-zero/core/stores/redis" "juwan-backend/app/users/rpc/internal/config" ) type ServiceContext struct { Config config.Config Redis *redis.Redis // 添加 Redis 客户端 } func NewServiceContext(c config.Config) *ServiceContext { return &ServiceContext{ Config: c, Redis: redis.MustNewRedis(c.Redis), // 初始化 Redis } } ``` #### 更新 Deployment 环境变量 ```yaml # deploy/k8s/service/user/user-rpc.yaml env: - name: DB_URI valueFrom: secretKeyRef: name: user-db-app key: uri - name: REDIS_PASSWORD # 添加 Redis 密码 valueFrom: secretKeyRef: name: user-redis key: password ``` --- ### 2. Redis 性能监控 已启用 Redis Exporter(端口 9121),可以配置 Prometheus 监控: ```yaml apiVersion: v1 kind: ServiceMonitor metadata: name: user-redis-metrics namespace: juwan spec: selector: matchLabels: app: user-redis endpoints: - port: redis-exporter interval: 30s ``` **监控指标:** - redis_up: 实例状态 - redis_connected_clients: 连接数 - redis_memory_used_bytes: 内存使用 - redis_commands_processed_total: 命令处理数 - redis_master_repl_offset: 复制偏移量 --- ### 3. 高可用性测试 #### 测试主节点故障转移 ```bash # 1. 查找当前主节点 kubectl exec -it user-redis-sentinel-sentinel-0 -n juwan -- redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster # 2. 模拟主节点故障 kubectl delete pod user-redis-0 -n juwan # 3. 观察 Sentinel 的故障转移过程 kubectl logs -f user-redis-sentinel-sentinel-0 -n juwan # 4. 确认新主节点 kubectl exec -it user-redis-sentinel-sentinel-0 -n juwan -- redis-cli -p 26379 SENTINEL get-master-addr-by-name mymaster ``` #### 预期结果 - Sentinel 检测到主节点下线(5 秒) - 2/3 Sentinel 节点达成共识(quorum=2) - 自动提升一个从节点为主节点 - 客户端自动重连到新主节点 --- ### 4. 扩展为分片集群(未来) 当缓存数据量增长需要横向扩展时,可以迁移到 RedisCluster: ```yaml apiVersion: redis.redis.opstreelabs.in/v1beta2 kind: RedisCluster metadata: name: user-redis-cluster namespace: juwan spec: clusterSize: 6 # 3 主 + 3 从 kubernetesConfig: image: quay.io/opstree/redis:v7.0.12 redisLeader: replicas: 3 redisFollower: replicas: 3 storage: volumeClaimTemplate: spec: accessModes: ["ReadWriteOnce"] resources: requests: storage: 5Gi ``` **迁移步骤:** 1. 部署新的 RedisCluster 2. 使用 redis-cli --cluster import 迁移数据 3. 更新应用配置指向新集群 4. 下线旧的 Sentinel 集群 --- ### 5. 备份策略 Redis Operator 不提供自动备份,建议配置定时任务: ```bash # 创建 CronJob 定期执行 BGSAVE apiVersion: batch/v1 kind: CronJob metadata: name: redis-backup namespace: juwan spec: schedule: "0 2 * * *" # 每天凌晨 2 点 jobTemplate: spec: template: spec: containers: - name: backup image: redis:7.0.12 command: - /bin/sh - -c - | redis-cli -h user-redis-master -a $REDIS_PASSWORD BGSAVE # 将 /data/dump.rdb 上传到对象存储 restartPolicy: OnFailure ``` --- ## 📚 总结 ### 关键经验 1. **理解资源依赖关系:** RedisSentinel 依赖 RedisReplication,部署顺序很重要 2. **资源命名规范:** 使用清晰的名称区分不同层次的资源(如 user-redis 和 user-redis-sentinel) 3. **诊断思路:** - 从现象(Pod 缺失)→ 资源状态(CRD 存在)→ Operator 日志 → 依赖检查 - 逐层排查,最终定位到 RedisReplication 缺失 4. **验证完整性:** 不仅要检查 Pod,还要验证 Service、StatefulSet 等所有相关资源 ### 文档价值 本文档可用于: - ✅ 团队知识传承 - ✅ 类似问题的快速排查手册 - ✅ 新成员的 Redis Operator 学习资料 - ✅ 事后复盘和经验总结 --- **最后更新时间:** 2026年2月22日 **文档状态:** ✅ 问题已解决,Redis 集群运行正常 **下一步行动:** 配置应用连接 Redis