阿里云-云小站(无限量代金券发放中)
【腾讯云】云服务器、云数据库、COS、CDN、短信等热卖云产品特惠抢购

Dubbo应用迁移到Kubernetes

189次阅读
没有评论

共计 5590 个字符,预计需要花费 14 分钟才能阅读完成。

Dubbo 是阿里开源的一套服务治理与 rpc 框架,服务的提供者通过 zookeeper 把自己的服务发布上去,然后服务调用方通过 zk 获取服务的 ip 和端口,dubbo 客户端通过自己的软负载功能自动选择服务提供者并调用,整个过程牵涉到的三方关系如下图所示。

Dubbo 应用迁移到 Kubernetes

在正常的情况下,这三方都在同一个互通的网段,provider 提供给 zk 的就是获取到的本机地址,consumer 能访问到这个地址。

但是假如服务放在 docker 容器中,而调用者并不在 docker 中,它们的网段是不一样的。

Dubbo 应用迁移到 Kubernetes

这个时候就出现问题了,consumer 无法访问到 provider 了。

Dubbo 提供的解决方案

新版的 Dubbo 提供了四个配置来指定与注册服务相关的地址和端口。

DUBBO_IP_TO_REGISTRY: 要发布到注册中心上的地址
DUBBO_PORT_TO_REGISTRY: 要发布到注册中心上的端口
DUBBO_IP_TO_BIND: 要绑定的服务地址 (监听的地址)
DUBBO_PORT_TO_BIND: 要绑定的服务端口 

以 IP 地址为例,Dubbo 先找是不是有 DUBBO_IP_TO_BIND 这个配置,如果有使用配置的地址,如果没有就取本机地址。然后继续找 DUBBO_IP_TO_REGISTRY,如果有了配置,使用配置,否则就使用 DUBBO_IP_TO_BIND。具体代码如下:

        /**
         * Register & bind IP address for service provider, can be configured separately.
         * Configuration priority: environment variables -> Java system properties -> host property in config file ->
         * /etc/hosts -> default network address -> first available network address
         *
         * @param protocolConfig
         * @param registryURLs
         * @param map
         * @return
         */
        private static String findConfigedHosts(ServiceConfig<?> sc,
                                                ProtocolConfig protocolConfig,
                                                List<URL> registryURLs,
                                                Map<String, String> map) {boolean anyhost = false;

            String hostToBind = getValueFromConfig(protocolConfig, DUBBO_IP_TO_BIND);
            if (hostToBind != null && hostToBind.length() > 0 && isInvalidLocalHost(hostToBind)) {throw new IllegalArgumentException("Specified invalid bind ip from property:" + DUBBO_IP_TO_BIND + ", value:" + hostToBind);
            }

            // if bind ip is not found in environment, keep looking up
            if (StringUtils.isEmpty(hostToBind)) {hostToBind = protocolConfig.getHost();
                if (sc.getProvider() != null && StringUtils.isEmpty(hostToBind)) {hostToBind = sc.getProvider().getHost();}
                if (isInvalidLocalHost(hostToBind)) {anyhost = true;
                    try {logger.info("No valid ip found from environment, try to find valid host from DNS.");
                        hostToBind = InetAddress.getLocalHost().getHostAddress();
                    } catch (UnknownHostException e) {logger.warn(e.getMessage(), e);
                    }
                    if (isInvalidLocalHost(hostToBind)) {if (CollectionUtils.isNotEmpty(registryURLs)) {for (URL registryURL : registryURLs) {if (MULTICAST.equalsIgnoreCase(registryURL.getParameter("registry"))) {// skip multicast registry since we cannot connect to it via Socket
                                    continue;
                                }
                                try (Socket socket = new Socket()) {SocketAddress addr = new InetSocketAddress(registryURL.getHost(), registryURL.getPort());
                                    socket.connect(addr, 1000);
                                    hostToBind = socket.getLocalAddress().getHostAddress();
                                    break;
                                } catch (Exception e) {logger.warn(e.getMessage(), e);
                                }
                            }
                        }
                        if (isInvalidLocalHost(hostToBind)) {hostToBind = getLocalHost();
                        }
                    }
                }
            }

            map.put(BIND_IP_KEY, hostToBind);

            // registry ip is not used for bind ip by default
            String hostToRegistry = getValueFromConfig(protocolConfig, DUBBO_IP_TO_REGISTRY);
            if (hostToRegistry != null && hostToRegistry.length() > 0 && isInvalidLocalHost(hostToRegistry)) {throw new IllegalArgumentException("Specified invalid registry ip from property:" + DUBBO_IP_TO_REGISTRY + ", value:" + hostToRegistry);
            } else if (StringUtils.isEmpty(hostToRegistry)) {// bind ip is used as registry ip by default
                hostToRegistry = hostToBind;
            }

            map.put(ANYHOST_KEY, String.valueOf(anyhost));

            return hostToRegistry;
        }

然后我们看这个 getValueFromConfig(),它调用了下面的函数,可以看到,它是先找环境变量,再找 properties。

    public static String getSystemProperty(String key) {String value = System.getenv(key);
        if (StringUtils.isEmpty(value)) {value = System.getProperty(key);
        }
        return value;
    }

所以我们通过环境变量,就能修改 Dubbo 发布到 zookeeper 上的地址和端口。假如我们通过 docker 镜像启动了一个 dubbo provider,并且它的服务端口是 8888,假设主机地址为 192.168.1.10,那么我们通过下面的命令,

docker run -e DUBBO_IP_TO_REGISTRY=192.168.1.10 -e DUBBO_PORT_TO_REGISTRY=8888 -p 8888:8888 dubbo_image

就能让内部的服务以 192.168.1.10:8888 的地址发布。

我们通过官方的实例来演示一下,因为官方提供的案例都很久了,所以我自己重新搞了一个示例,代码在 https://github.com/XinliNiu/dubbo-docker-sample.git。

先启动一个 zookeeper,暴露 2181 端口。

docker run --name zkserver --rm -p 2181:2181  -d zookeeper:3.4.9

看一下 zk 起来了

niuxinli@niuxinli-B450M-DS3H:~/dubbo-samples-docker$ docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                         NAMES
5efc1f17fba0        zookeeper:3.4.9     "/docker-entrypoint.…"   4 seconds ago       Up 2 seconds        2888/tcp, 3888/tcp, 0.0.0.0:2181->2181/tcp   zkserver

把代码导入 IDE,修改 dubbo-docker-provide.xml,把地址改成刚发布到 zk 的地址和端口,我的地址是 192.168.1.8。

运行 DubboApplication,这时候可以看到在 zk 上注册了服务。

Dubbo 应用迁移到 Kubernetes

修改 dubbo-docker-consumer.xml 里的 zk 地址,执行单元测试,能正常访问。

把 DubboApplication 导出成可以执行的 jar 包,名字叫 app.jar,创建如下 Dockerfile

FROM openjdk:8-jdk-alpine
ADD app.jar app.jar
ENV JAVA_OPTS=""
ENTRYPOINT exec java $JAVA_OPTS -jar /app.jar

创建 dubbo-demo 镜像,在同样的目录里执行 docker build。

docker build --no-cache -t dubbo-demo .

正常启动镜像

docker run  -p 20880:20880  -it --rm dubbo-demo

发现是 172.16.0.3 的地址,这个是访问不了的。

Dubbo 应用迁移到 Kubernetes

传入环境变量重新启动,

docker run  -e DUBBO_IP_TO_REGISTRY=192.168.1.8 -e DUBBO_PORT_TO_REGISTRY=20880 -p 20880:20880  -it --rm dubbo-demo

这时候就变成主机地址了。

Dubbo 应用迁移到 Kubernetes

在 Kubernetes 中使用 Dubbo

当在 Kubernetes 中启动多个副本的时候,指定具体的 IP 和具体的端口,都是不可行的,因为每个机器的 IP 都不一样,不能写很多个 yaml 文件,而且一旦指定了具体端口,那这台主机的这个端口就被占用了。

我们可以通过创建 Service,使用 NodePort 的方式,把端口固定住,这样端口的问题就解决了。因为是对外服务,所以使用 ClusterIP 肯定是不行了,IP 有两种解决办法:

(1) 使用 Kubernetes 的 downward api 动态的传入主机的 ip。

(2) 传固定的 loadbalancer 的地址,例如在所有的 node 之外有一个 F5。

不管哪种方法,都是一种妥协的办法,很不“云原生”,我演示一下使用 downward api 动态传入主机地址,并使用 nodeport 固定端口的方式。

我的 kubernetes 集群如下:

角色 地址
master 192.168.174.50
node1 192.168.174.51
node2 192.168.174.52
node3 192.168.174.53

zk 的地址是 192.168.1.8,它与集群的主机互通。

我没有建 private 镜像仓库,把我之前打好的 dubbo-demo 直接 push 到 docker-hub 上了,名字是 nxlhero/dubbo-demo。

创建 Service,使用的 NodePort 为 30001,创建 4 个副本,这样 3 台机器上正好有一台起两个 pod。

apiVersion: v1
kind: Service
metadata:
  name: dubbo-docker
  labels:
    run: dubbo
spec:
  type: NodePort
  ports:
  - port: 20880
    targetPort: 20880
    nodePort: 30001
  selector:
    run: dubbo-docker
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: dubbo-docker
spec:
  selector:
    matchLabels:
      run: dubbo
  replicas: 4
  template:
    metadata:
      labels:
        run: dubbo
    spec:
      containers:
      - name: dubbo-docker
        image: nxlhero/dubbo-demo
        env:
        - name: DUBBO_IP_TO_REGISTRY
          valueFrom:
            fieldRef:
              fieldPath: status.hostIP
        - name: DUBBO_PORT_TO_REGISTRY
          value: "30001"
        tty: true
        ports:
        - containerPort: 20880

这个 yaml 最关键的地方就是环境变量,主机 IP 通过 downward apid 传入,端口使用固定的 nodeport。

        env:
        - name: DUBBO_IP_TO_REGISTRY
          valueFrom:
            fieldRef:
              fieldPath: status.hostIP
        - name: DUBBO_PORT_TO_REGISTRY
          value: "30001"

创建 Service,启动后可以看到 zookeeper 上的地址都是主机的地址和 nodeport。

Dubbo 应用迁移到 Kubernetes

正文完
星哥说事-微信公众号
post-qrcode
 0
星锅
版权声明:本站原创文章,由 星锅 于2022-01-21发表,共计5590字。
转载说明:除特殊说明外本站文章皆由CC-4.0协议发布,转载请注明出处。
【腾讯云】推广者专属福利,新客户无门槛领取总价值高达2860元代金券,每种代金券限量500张,先到先得。
阿里云-最新活动爆款每日限量供应
评论(没有评论)
验证码
【腾讯云】云服务器、云数据库、COS、CDN、短信等云产品特惠热卖中