暴露redis-cluster到k8s集群外部.md

一、容器环境下搭建redis cluster集群

  • 本案例中直接使用redis-operator搭建一个规模为3,副本数量为1的redis cluster集群。

  • 搭建方式可参考官方文档: https://github.com/OT-CONTAINER-KIT/redis-operator

  • 搭建完成后,集群节点列表如下:

    [root@k8s-master redis]# kubectl get pods -l redis_setup_type=cluster -o wide
    NAME                             READY   STATUS    RESTARTS   AGE    IP               NODE        NOMINATED NODE   READINESS GATES
    redis-cluster-alpha-follower-0   2/2     Running   0          146m   172.16.122.79    k8s-node4   <none>           <none>
    redis-cluster-alpha-follower-1   2/2     Running   0          145m   172.16.107.217   k8s-node3   <none>           <none>
    redis-cluster-alpha-follower-2   2/2     Running   0          145m   172.16.107.235   k8s-node3   <none>           <none>
    redis-cluster-alpha-leader-0     2/2     Running   0          146m   172.16.107.239   k8s-node3   <none>           <none>
    redis-cluster-alpha-leader-1     2/2     Running   0          145m   172.16.122.81    k8s-node4   <none>           <none>
    

redis-cluster-alpha-leader-2 2/2 Running 0 145m 172.16.122.87 k8s-node4


- 验证集群状态是否正常

```shell
bash-4.4# redis-cli cluster info
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:6
cluster_size:3
cluster_current_epoch:4
cluster_my_epoch:3
# ...

二、暴露集群中的每个节点到集群Node上

  • 暴露redis cluster到k8s集群外主要面临两个问题:

    • 1、如何让集群中的每个节点能够被集群外访问。

    • 2、如何让cluster nodes命令中列出的节点为集群外部可访问的节点和端口。绝大多数redis cluster客户端都会从cluster nodes的输出中获取所有节点的ip和端口,然后通过这些信息去直连redis节点。默认情况下,该命令输出的是集群内部的pod的ip和端口,所以我们需要将这里输出的信息转换为集群外部可访问的ip和port

      # 默认情况下,该命令输出的是集群内部的pod的ip和端口
      bash-4.4# redis-cli cluster nodes
      b0ea18416b149f3240eec924fd8ad76ce0854579 10.42.47.12:6379@16379 slave f7faeb76e067d22fff480ef29a6b2875ce4622b4 0 1652610385537 2 connected
      003b2fea2dbeedfb40d01d5d3140b916d9571b3e 10.42.47.22:6379@16379 slave 0bb47993188bfcba5c75d55d70cd7fd46a3a8c1b 0 1652610385000 1 connected
      0b0959a0e935ce5f2ad0e1f1de484c2ac81b73b3 10.42.47.28:6379@16379 master - 0 1652610386841 3 connected 10923-16383
      0bb47993188bfcba5c75d55d70cd7fd46a3a8c1b 10.42.47.46:6379@16379 myself,master - 0 1652610385000 1 connected 0-5460
      10b421e3385c9aba1b8ceba5e2b1092dafc3f177 10.42.47.48:6379@16379 slave 0b0959a0e935ce5f2ad0e1f1de484c2ac81b73b3 0 1652610385000 3 connected
      f7faeb76e067d22fff480ef29a6b2875ce4622b4 10.42.47.45:6379@16379 master - 0 1652610386000 2 connected 5461-10922
      

1、将所有节点暴露到集群外

  • 针对上述的第一个问题,使用Node port的方式,将集群中的6个节点暴露到集群外部。

  • 在下面的配置文件中, 将为redis cluster集群中的每个节点(通过spec.selector关联pod)创建一个NodePort类型的Service, 并将客户端通信端口6379端口和集群总线端口16379暴露到集群的不同端口上。

    • 例如这里的节点redis-cluster-alpha-leader-0, 其客户端通信端口6379暴露为k8s集群Node的31000端口,其集群总线端口16379被暴露为k8s集群Node上的32000端口。
  • 之所以还需要暴露集群总线端口,是因为我们后面需要通过修改cluster-announce-ip的方式来让cluster nodes的输出中ip地址可被集群外部访问,而cluster-announce-ip的值一旦被修改,集群中的其他节点会使用该配置指定的地址来进行节点通信,此时如果不将集群总线端口也暴露到k8s集群Node上,将导致节点之间无法通信。

    apiVersion: v1
    kind: Service
    metadata:
      name: redis-cluster-alpha-leader-0
    spec:
      selector:
        statefulset.kubernetes.io/pod-name: redis-cluster-alpha-leader-0
      ports:
        - name: client-port
          port: 6379
          protocol: TCP
          targetPort: 6379
          nodePort: 31000 # 当type为NodePort或LoadBalancer时,公开此服务的每个节点上的端口。通常由系统分配。如果指定了一个范围内的值,并且没有被使用,则将使用该值,否则操作将失败。如果不指定,如果该服务需要,将分配一个端口。如果在创建不需要该字段的服务时指定了该字段,则创建将失败。当一个服务更新到不再需要它时,这个字段将被删除(例如,将类型从NodePort改为ClusterIP)
        - name: bus-port
          port: 16379
          protocol: TCP
          targetPort: 16379
          nodePort: 32000
      type: NodePort
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: redis-cluster-alpha-leader-1
    spec:
      selector:
        statefulset.kubernetes.io/pod-name: redis-cluster-alpha-leader-1
      ports:
        - name: client-port
          port: 6379
          protocol: TCP
          targetPort: 6379
          nodePort: 31001
        - name: bus-port
          port: 16379
          protocol: TCP
          targetPort: 16379
          nodePort: 32001
      type: NodePort
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: redis-cluster-alpha-leader-2 # 改
    spec:
      selector:
        statefulset.kubernetes.io/pod-name: redis-cluster-alpha-leader-2 # 改
      ports:
        - name: client-port
          port: 6379
          protocol: TCP
          targetPort: 6379
          nodePort: 31002 # 改
        - name: bus-port
          port: 16379
          protocol: TCP
          targetPort: 16379
          nodePort: 32002 # 改
      type: NodePort
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: redis-cluster-alpha-follower-0 # 改
    spec:
      selector:
        statefulset.kubernetes.io/pod-name: redis-cluster-alpha-follower-0 # 改
      ports:
        - name: client-port
          port: 6379
          protocol: TCP
          targetPort: 6379
          nodePort: 31100 # 改
        - name: bus-port
          port: 16379
          protocol: TCP
          targetPort: 16379
          nodePort: 32100 # 改
      type: NodePort
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: redis-cluster-alpha-follower-1 # 改
    spec:
      selector:
        statefulset.kubernetes.io/pod-name: redis-cluster-alpha-follower-1 # 改
      ports:
        - name: client-port
          port: 6379
          protocol: TCP
          targetPort: 6379
          nodePort: 31101 # 改
        - name: bus-port
          port: 16379
          protocol: TCP
          targetPort: 16379
          nodePort: 32101 # 改
      type: NodePort
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: redis-cluster-alpha-follower-2 # 改
    spec:
      selector:
        statefulset.kubernetes.io/pod-name: redis-cluster-alpha-follower-2 # 改
      ports:
        - name: client-port
          port: 6379
          protocol: TCP
          targetPort: 6379
          nodePort: 31102 # 改
        - name: bus-port
          port: 16379
          protocol: TCP
          targetPort: 16379
          nodePort: 32102 # 改
      type: NodePort
    
  • 完成后结果如下:

    [root@k8s-master redis]# kubectl get svc  | grep NodePort
    redis-cluster-alpha-follower-0          NodePort    10.96.107.8     <none>        6379:31100/TCP,16379:32100/TCP                                                                              98m
    redis-cluster-alpha-follower-1          NodePort    10.96.30.84     <none>        6379:31101/TCP,16379:32101/TCP                                                                              98m
    redis-cluster-alpha-follower-2          NodePort    10.96.107.45    <none>        6379:31102/TCP,16379:32102/TCP                                                                              98m
    redis-cluster-alpha-leader-0            NodePort    10.96.138.108   <none>        6379:31000/TCP,16379:32000/TCP                                                                              130m
    redis-cluster-alpha-leader-1            NodePort    10.96.54.196    <none>        6379:31001/TCP,16379:32001/TCP                                                                              113m
    redis-cluster-alpha-leader-2            NodePort    10.96.62.103    <none>        6379:31002/TCP,16379:32002/TCP                                                                              98m
    
  • 完成之后,我们可以通过NodeIp:NodePort的方式访问到redis cluster中的某个节点。例如,我们想访问redis-cluster-alpha-leader-0,就连接31000端口。

    [root@k8s-master redis-6.2.6]# redis-cli -h 192.168.0.163 -p 31000
    192.168.0.163:31002> cluster info
    cluster_state:ok
    cluster_slots_assigned:16384
    cluster_slots_ok:16384
    cluster_slots_pfail:0
    cluster_slots_fail:0
    cluster_known_nodes:6
    cluster_size:3
    cluster_current_epoch:4
    cluster_my_epoch:3
    #...
    

2、修改cluster nodes命令输出

  • redis cluster原生提供了如下几个配置来支持DOCKER/NAT的场景:

    • cluster-announce-ip: 对外发布自己的节点ip
    • cluster-announce-port: 对外发布自己的客户端服务端口。 根据实验结果来看,这个设置只会影响到redis对外发布的(也就是其他节点看到的)客户端服务端口,并不是将正常的客户端服务端口6379改为这里指定的端口,设置该选项实际上并不会影响正常的客户端服务端口6379,通过6379端口依旧可以访问redis服务。即我们在做端口映射的时候,应该将cluster-announce-port指定的端口映射到6379端口。外部服务通过cluster-announce-port指定的端口访问到6379端口上的服务
    • cluster-announce-tls-port: 对外发布自己的tls连接下的客户端服务端口
    • cluster-announce-bus-port: 对外发布自己的集群消息总线端口。根据实验结果来看,这个设置只会影响到redis对外发布的集群总线端口的值,并不是将正常的集群总线端口16379改为这里指定的端口。集群节点之间进行通信应该依旧使用16379端口。 我们在做端口映射的时候,应该将cluster-announce-bus-port指定的端口映射到16379端口。其他节点通过cluster-announce-bus-port指定的端口映射到16379端口,以实现节点间的gossip消息通信。
  • 所以我们可以通过上述几个配置,来修改自己对外发布的节点和端口信息,这样,从其他节点上看到的当前节点信息就是我们这里发布的信息了。

  • 具体操作方式如下:

    • 修改集群中每一个节点对外发布的地址cluster-announce-ip为192.168.0.163, 192.168.0.163为k8s集群master节点的ip
    • 修改集群中每一个节点对外发布的客户端通信端口cluster-announce-port为前面NodePort Service中指定的端口。以redis-cluster-alpha-leader-0节点为例,其对应的NodePort Service中指定的暴露到Node上的端口(spec.ports.nodePort)为31000,所以这里指定cluster-announce-port的值为31000。这样k8s集群外部的客户端就可以通过31000端口映射到redis cluster节点的6379端口(spec.ports.targetPort指定的值).
    • 修改集群中的每一个节点对外发布的集群总线端口cluster-announce-bus-port为前面NodePort Service中指定的端口。以redis-cluster-alpha-leader-0节点为例,其对应的NodePort Service中指定的暴露到Node上的集群总线端口(spec.ports.nodePort)为32000,所以这里指定cluster-announce-bus-port的值为32000。这样redis cluster集群中的其他节点就可以通过32000端口映射到真正的集群总线端口16379(spec.ports.targetPort指定的值),以实现redis cluster节点之间的通信
    redis-cli -h redis-cluster-alpha-leader-0.redis-cluster-alpha-leader.default -p 6379 config set cluster-announce-ip 192.168.0.163
    redis-cli -h redis-cluster-alpha-leader-0.redis-cluster-alpha-leader.default -p 6379 config set cluster-announce-port 31000
    redis-cli -h redis-cluster-alpha-leader-0.redis-cluster-alpha-leader.default -p 6379 config set cluster-announce-bus-port 32000
    redis-cli -h redis-cluster-alpha-leader-0.redis-cluster-alpha-leader.default -p 6379 config rewrite
    
    redis-cli -h redis-cluster-alpha-leader-1.redis-cluster-alpha-leader.default -p 6379 config set cluster-announce-ip 192.168.0.163
    redis-cli -h redis-cluster-alpha-leader-1.redis-cluster-alpha-leader.default -p 6379 config set cluster-announce-port 31001
    redis-cli -h redis-cluster-alpha-leader-1.redis-cluster-alpha-leader.default -p 6379 config set cluster-announce-bus-port 32001
    redis-cli -h redis-cluster-alpha-leader-1.redis-cluster-alpha-leader.default -p 6379 config rewrite
    
    redis-cli -h redis-cluster-alpha-leader-2.redis-cluster-alpha-leader.default -p 6379 config set cluster-announce-ip 192.168.0.163
    redis-cli -h redis-cluster-alpha-leader-2.redis-cluster-alpha-leader.default -p 6379 config set cluster-announce-port 31002
    redis-cli -h redis-cluster-alpha-leader-2.redis-cluster-alpha-leader.default -p 6379 config set cluster-announce-bus-port 32002
    redis-cli -h redis-cluster-alpha-leader-2.redis-cluster-alpha-leader.default -p 6379 config rewrite
    
    redis-cli -h redis-cluster-alpha-follower-0.redis-cluster-alpha-follower.default -p 6379 config set cluster-announce-ip 192.168.0.163
    redis-cli -h redis-cluster-alpha-follower-0.redis-cluster-alpha-follower.default -p 6379 config set cluster-announce-port 31100
    redis-cli -h redis-cluster-alpha-follower-0.redis-cluster-alpha-follower.default -p 6379 config set cluster-announce-bus-port 32100
    redis-cli -h redis-cluster-alpha-follower-0.redis-cluster-alpha-follower.default -p 6379 config rewrite
    
    redis-cli -h redis-cluster-alpha-follower-1.redis-cluster-alpha-follower.default -p 6379 config set cluster-announce-ip 192.168.0.163
    redis-cli -h redis-cluster-alpha-follower-1.redis-cluster-alpha-follower.default -p 6379 config set cluster-announce-port 31101
    redis-cli -h redis-cluster-alpha-follower-1.redis-cluster-alpha-follower.default -p 6379 config set cluster-announce-bus-port 32101
    redis-cli -h redis-cluster-alpha-follower-1.redis-cluster-alpha-follower.default -p 6379 config rewrite
    
    redis-cli -h redis-cluster-alpha-follower-2.redis-cluster-alpha-follower.default -p 6379 config set cluster-announce-ip 192.168.0.163
    redis-cli -h redis-cluster-alpha-follower-2.redis-cluster-alpha-follower.default -p 6379 config set cluster-announce-port 31102
    redis-cli -h redis-cluster-alpha-follower-2.redis-cluster-alpha-follower.default -p 6379 config set cluster-announce-bus-port 32102
    redis-cli -h redis-cluster-alpha-follower-2.redis-cluster-alpha-follower.default -p 6379 config rewrite
    

三、集群外客户端访问验证

  • 本例中使用Spring Boot中自带的Spring-data-redis客户端访问该redis cluster集群。

  • 首先准备一个Spring Boot项目,该项目中需要导入如下两个jar包:

    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-redis</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    
  • 然后在application.yml配置文件中,指定redis cluster集群配置:

    spring:
      redis:
        cluster:
          nodes:
            - 192.168.0.163:31000
            - 192.168.0.163:31001
            - 192.168.0.163:31002
            - 192.168.0.163:31100
            - 192.168.0.163:31101
            - 192.168.0.163:31102
          max-redirects: 5
        timeout: 2000
        lettuce:
          pool:
            max-active: 1000
            max-idle: 10
            min-idle: 5
            max-wait: -1
          cluster:
            refresh: # 注意要加上这段配置,否则故障转移后ip会变,如果不刷新会造成无法访问
              period: 30s 
              adaptive: true 
    
  • 创建一个Controller如,该controller可以支持set和get命令发送到redis cluster集群

    package cn.ljz.redisclient.controller;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.RedisTemplate;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RestController;
    
    @RestController
    public class RedisController {
    
        @Autowired
        private RedisTemplate<String, String> redisTemplate;
    
        /**
         * http://localhost:8080/redis/operateRedis?cmd=set&args=k11 v11
         * @param cmd
         * @param args
         * @return
         */
        @GetMapping("/redis/operateRedis")
        public String operateRedis(String cmd, String args){
            String result;
            if ("set".equalsIgnoreCase(cmd)){
                String[] s = args.split(" ");
                redisTemplate.opsForValue().set(s[0], s[1]);
                result = "exec success";
            } else if ("get".equalsIgnoreCase(cmd)){
                result = redisTemplate.opsForValue().get(args);
            }else {
                result =  "Unkown command " + cmd;
            }
            return result;
        }
    }
    
  • 之后将该项目打成一个jar包,并将该jar包在一个可访问到k8s集群Node的机器上启动。

    [root@k8s-master redis]# java -jar redis-client-0.0.1-SNAPSHOT.jar 
    
      .   ____          _            __ _ _
     /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
    ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
     \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
      '  |____| .__|_| |_|_| |_\__, | / / / /
     =========|_|==============|___/=/_/_/_/
     :: Spring Boot ::                (v2.6.4)
    
    2022-05-15 17:58:49.551  INFO 5837 --- [           main] c.l.redisclient.RedisClientApplication   : Starting RedisClientApplication v0.0.1-SNAPSHOT using Java 1.8.0_321 on k8s-master with PID 5837 (/root/redis/redis-client-0.0.1-SNAPSHOT.jar started by root in /root/redis)
    2022-05-15 17:58:49.557  INFO 5837 --- [           main] c.l.redisclient.RedisClientApplication   : No active profile set, falling back to 1 default profile: "default"
    2022-05-15 17:58:50.376  INFO 5837 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Multiple Spring Data modules found, entering strict repository configuration mode!
    2022-05-15 17:58:50.379  INFO 5837 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Bootstrapping Spring Data Redis repositories in DEFAULT mode.
    2022-05-15 17:58:50.401  INFO 5837 --- [           main] .s.d.r.c.RepositoryConfigurationDelegate : Finished Spring Data repository scanning in 6 ms. Found 0 Redis repository interfaces.
    2022-05-15 17:58:50.949  INFO 5837 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat initialized with port(s): 8080 (http)
    2022-05-15 17:58:50.985  INFO 5837 --- [           main] o.apache.catalina.core.StandardService   : Starting service [Tomcat]
    2022-05-15 17:58:50.985  INFO 5837 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet engine: [Apache Tomcat/9.0.58]
    2022-05-15 17:58:51.072  INFO 5837 --- [           main] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
    2022-05-15 17:58:51.073  INFO 5837 --- [           main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1416 ms
    2022-05-15 17:58:52.089  INFO 5837 --- [           main] o.s.b.w.embedded.tomcat.TomcatWebServer  : Tomcat started on port(s): 8080 (http) with context path ''
    2022-05-15 17:58:52.100  INFO 5837 --- [           main] c.l.redisclient.RedisClientApplication   : Started RedisClientApplication in 3.243 seconds (JVM running for 3.928)
    2022-05-15 18:01:24.696  INFO 5837 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring DispatcherServlet 'dispatcherServlet'
    2022-05-15 18:01:24.696  INFO 5837 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Initializing Servlet 'dispatcherServlet'
    2022-05-15 18:01:24.697  INFO 5837 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet        : Completed initialization in 1 ms
    2022-05-15 18:01:25.186  WARN 5837 --- [ioEventLoop-4-1] i.l.c.c.t.DefaultClusterTopologyRefresh  : Unable to connect to [192.168.0.163:6379]: Connection refused: /192.168.0.163:6379
    2022-05-15 18:02:23.704 ERROR 5837 --- [nio-8080-exec-9] o.a.c.c.C.[.[.[/].[dispatcherServlet]    : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.ArrayIndexOutOfBoundsException: 1] with root cause
    
  • 之后在另一个窗口中访问controller中的接口

    [root@k8s-master redis-6.2.6]# curl http://192.168.0.163:8080/redis/operateRedis?cmd=set\&args=k6%20v6
    exec success
    [root@k8s-master redis-6.2.6]# curl http://192.168.0.163:8080/redis/operateRedis?cmd=get\&args=k6
    v6
    [root@k8s-master redis-6.2.6]# 
    
<think> 我们之前已经解决了参数传递的问题,现在用户遇到了新的错误:`Could not connect to Redis at redis-cluster-{0..5}.redis-cluster-service.middleware.svc.cluster.local:6380: Name or service not known`,这表明DNS解析失败。 问题分析: 1. 错误信息显示“Name or service not known”,意味着在初始化Job执行时,无法通过DNS解析Pod的主机名。 2.Kubernetes中,StatefulSet创建的Pod具有稳定的网络标识,格式为`<statefulset-name>-<ordinal>.<service-name>.<namespace>.svc.cluster.local`。 3. 用户使用的服务名是`redis-cluster-service`,命名空间是`middleware`,因此Pod的FQDN应该是`redis-cluster-0.redis-cluster-service.middleware.svc.cluster.local`等。 可能原因: 1. 服务(Service)还未创建或未就绪,导致DNS记录不存在。 2. Pod还未启动,因此DNS记录还未注册。 3. Job运行过早,StatefulSet的Pod还没有全部创建并运行。 4. 集群DNS服务(如CoreDNS)有问题。 解决方案: 步骤1:确保StatefulSet和Service已经部署 检查StatefulSet和Service是否已经正确部署在`middleware`命名空间下。 步骤2:添加初始化等待逻辑 在Job脚本中,我们需要等待所有Pod都启动并且服务可用。但是,当前脚本只等待了第一个Pod(redis-cluster-0)就绪,然后就开始初始化。然而,集群初始化需要所有节点(6个)都就绪。 修改脚本:在尝试初始化集群之前,循环检查所有6个Pod的Redis实例是否可连接。 步骤3:使用循环等待所有节点 我们可以为每个Pod构建一个FQDN,然后逐个检查它们是否已经可以连接。 修改后的脚本片段: ```bash # 等待所有节点就绪 for i in {0..5}; do echo "等待节点 redis-cluster-$i 就绪..." until redis-cli -h redis-cluster-${i}.redis-cluster-service.middleware.svc.cluster.local -p 6380 ping; do sleep 5 done done ``` 但是,注意:在Pod启动后,Redis服务可能还需要一些时间才能开始监听端口,所以我们使用`redis-cli ping`来检查。 步骤4:考虑Kubernetes的启动顺序 由于StatefulSet按顺序启动Pod(从0到5),所以当第一个Pod(索引0)启动时,其他Pod可能还没有启动。因此,我们需要等待所有Pod就绪。 步骤5:完整脚本调整 将原来的等待单个节点改为等待所有节点,然后再执行集群初始化。 完整脚本示例: ```yaml command: - /bin/sh - -c - | echo "等待所有Redis节点启动..." # 循环等待每个节点 for i in {0..5}; do until redis-cli -h redis-cluster-${i}.redis-cluster-service.middleware.svc.cluster.local -p 6380 ping; do echo "节点 redis-cluster-$i 尚未就绪,等待5秒后重试..." sleep 5 done echo "节点 redis-cluster-$i 已就绪" done # 生成节点列表 NODES=() for i in {0..5}; do NODES+=( "redis-cluster-${i}.redis-cluster-service.middleware.svc.cluster.local:6380" ) done # 初始化集群 echo "初始化节点: ${NODES[@]}" redis-cli --cluster create "${NODES[@]}" \ --cluster-replicas 1 \ --cluster-yes echo "Redis集群初始化完成!" ``` 步骤6:验证DNS解析 如果上述等待循环长时间无法成功,需要检查DNS解析是否正常。可以在Job的Pod中临时添加一个命令来测试DNS解析,例如: ```bash nslookup redis-cluster-0.redis-cluster-service.middleware.svc.cluster.local ``` 但是,在Job中我们可能没有nslookup工具,所以可以使用`ping`(如果镜像中有)或者通过`getent hosts`来检查。 我们可以添加一个步骤来检查DNS解析: ```bash for i in {0..5}; do until nslookup redis-cluster-${i}.redis-cluster-service.middleware.svc.cluster.local; do echo "无法解析 redis-cluster-$i,等待5秒..." sleep 5 done done ``` 不过,我们使用的Redis镜像(官方镜像)可能不包含`nslookup`或者`ping`,因此更通用的方法是使用`redis-cli`来尝试连接,因为连接失败的错误信息会提示是连接失败(Connection refused)还是无法解析(Name or service not known)。 根据之前的错误信息,如果是无法解析,那么可能是服务或Pod未就绪,或者DNS服务有问题。 步骤7:检查Kubernetes服务配置 确保Service的配置正确,特别是无头服务(Headless Service)的配置。Redis集群需要使用无头服务来为每个Pod提供独立的DNS解析。 Service示例: ```yaml apiVersion: v1 kind: Service metadata: name: redis-cluster-service namespace: middleware spec: clusterIP: None ports: - port: 6380 name: redis selector: app: redis-cluster ``` 步骤8:检查StatefulSet配置 确保StatefulSet中的Pod标签与Service的selector匹配,并且端口正确。 步骤9:调整Job的依赖关系 在Kubernetes中,Job无法直接依赖StatefulSet的全部Pod就绪。因此,我们需要在Job内部通过循环等待来处理。 步骤10:设置合理的超时 如果某些节点长时间无法就绪,可能会导致Job一直重试。可以设置最大重试次数,避免无限等待。 例如,每个节点最多等待5分钟(60次,每次5秒): ```bash for i in {0..5}; do counter=0 max_retry=60 until redis-cli -h redis-cluster-${i}.redis-cluster-service.middleware.svc.cluster.local -p 6380 ping; do sleep 5 counter=$((counter+1)) if [ $counter -ge $max_retry ]; then echo "等待节点 redis-cluster-$i 超时,退出" exit 1 fi done done ``` 总结:主要修改点是在初始化之前确保所有Pod已经启动并且Redis服务已经运行,同时检查DNS解析。如果问题仍然存在,需要检查Kubernetes中DNS服务的状态(如CoreDNS)以及网络策略是否允许DNS解析。 生成的相关问题: 1. 如何排查Kubernetes中的DNS解析问题? 2. RedisKubernetes中部署集群的最佳实践是什么? 3. 如何调试Kubernetes Job中的脚本错误? 4. 如何保证StatefulSet的Pod按顺序启动并初始化? 5. 如何在Kubernetes中配置Redis无头服务? 注意:在Job中,我们假设每个Pod的Redis端口是6380,请确保StatefulSet中配置的容器端口也是6380,并且服务端口也是6380。 如果用户的环境有网络策略限制,可能还需要检查网络策略是否允许Job的Pod访问Redis Pod。 最后,将修改后的脚本应用到Job中。 </think> ### 解决KubernetesRedis集群初始化Job节点连接失败问题 出现`Could not connect to Redis`错误表明初始化Job无法解析Redis节点的主机名,本质是**DNS解析失败**,以下是逐步解决方案: --- #### 1. 检查核心依赖服务是否正常 ```bash # 查看DNS服务状态 kubectl get pods -n kube-system -l k8s-app=kube-dns # 检查Service是否创建 kubectl get svc -n middleware | grep redis-cluster-service ``` **关键点**: - 确认CoreDNS/Coredns Pod处于`Running`状态 - 确保Service名称与脚本中的`redis-cluster-service`完全匹配[^2] - 验证Service端口是否为6380(与脚本一致) --- #### 2. 添加Headless Service支持DNS解析 在StatefulSet部署文件中添加无头服务配置: ```yaml # redis-headless-service.yaml apiVersion: v1 kind: Service metadata: name: redis-cluster-service namespace: middleware spec: clusterIP: None # 关键配置! ports: - port: 6380 name: redis selector: app: redis-cluster # 需与StatefulSet标签匹配 ``` **作用**: - `clusterIP: None`创建无头服务,为每个Pod生成独立DNS记录 - 使Pod可通过`<pod-name>.<service-name>.<namespace>.svc.cluster.local`格式解析 --- #### 3. 修改Job等待逻辑确保节点就绪 优化初始化脚本中的等待逻辑: ```bash command: - /bin/sh - -c - | # 增加DNS解析等待 until nslookup redis-cluster-service.middleware.svc.cluster.local; do echo "等待DNS服务就绪..."; sleep 10; done # 逐个等待节点就绪 for i in {0..5}; do echo "等待节点 redis-cluster-$i 启动..." until nslookup redis-cluster-${i}.redis-cluster-service.middleware.svc.cluster.local; do sleep 5; done until redis-cli -h redis-cluster-${i}.redis-cluster-service.middleware.svc.cluster.local -p 6380 ping; do sleep 5; done done # 后续集群初始化代码... ``` --- #### 4. 验证网络连通性 在Job容器内手动测试: ```bash kubectl exec -it redis-cluster-init-xxxxx -n middleware -- sh # 在容器内执行 nslookup redis-cluster-0.redis-cluster-service.middleware.svc.cluster.local telnet redis-cluster-0 6380 # 测试端口连通性 ``` --- #### 5. 检查Redis配置文件 确保StatefulSet中Redis配置允许外部连接: ```yaml # 在StatefulSet的Redis容器添加 args: - "--bind" - "0.0.0.0" # 允许所有IP访问 - "--port" - "6380" - "--cluster-enabled" - "yes" ``` **重要**:避免出现`bind 127.0.0.1`限制导致连接拒绝[^3][^4] --- #### 6. 临时解决方案(调试用) 若仍存在问题,可在Job中使用Pod IP代替域名(不推荐生产): ```bash # 修改节点列表生成逻辑 NODES=() for i in {0..5}; do IP=$(nslookup redis-cluster-${i}.redis-cluster-service | grep 'Address:' | tail -n1 | awk '{print $2}') NODES+=( "$IP:6380" ) done ``` --- ### 验证步骤 ```bash # 查看Job日志 kubectl logs -n middleware <job-pod-name> # 检查集群状态 kubectl exec -it redis-cluster-0 -n middleware -- \ redis-cli -p 6380 cluster info ``` 预期输出:`cluster_state:ok` --- ### 相关问题 1. StatefulSet和无头服务如何协同工作实现DNS解析? 2. Kubernetes集群DNS故障的通用排查方法有哪些? 3. Redis在容器环境中常见网络配置错误如何避免? 4. 如何监控Kubernetes中的DNS解析延迟? 5. 有状态应用的滚动更新如何保证服务连续性? [^1]: Redis集群初始化需要严格参数顺序 [^2]: 通过无头服务实现Pod级DNS解析 [^3]: 绑定配置错误导致连接拒绝 [^4]: 网络策略限制导致连接失败
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值