dubbo+zookeeper示例代码_Dubbo应用迁移到Kubernetes

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

0ba522dd40a174b57fd8612836bcd016.png

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

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

6a7205faea0114a0f1ebc18f0129a4cf.png

这个时候就出现问题了,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 registryURLs,                                                Map 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 psCONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                                         NAMES5efc1f17fba0        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上注册了服务。

b4f9bcf6e09a7b135c9bbd058838afa1.png

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

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

FROM openjdk:8-jdk-alpineADD app.jar app.jarENV 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的地址,这个是访问不了的。

dc2df2601bae018d2eb04ee758fb4071.png

传入环境变量重新启动,

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

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

f86dfaa4582060edcfad054957a47d8d.png

在Kubernetes中使用Dubbo

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

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

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

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

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

我的kubernetes集群如下:

角色地址master192.168.174.50node1192.168.174.51node2192.168.174.52node3192.168.174.53

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

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

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

apiVersion: v1kind: Servicemetadata:  name: dubbo-docker  labels:    run: dubbospec:  type: NodePort  ports:  - port: 20880    targetPort: 20880    nodePort: 30001  selector:    run: dubbo-docker---apiVersion: apps/v1kind: Deploymentmetadata:  name: dubbo-dockerspec:  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。

734b36c7c5282bbdfa0f5bcba771bbef.png
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值