Dubbo是阿里开源的一套服务治理与rpc框架,服务的提供者经过zookeeper把本身的服务发布上去,而后服务调用方经过zk获取服务的ip和端口,dubbo客户端经过本身的软负载功能自动选择服务提供者并调用,整个过程牵涉到的三方关系以下图所示。java
在正常的状况下,这三方都在同一个互通的网段,provider提供给zk的就是获取到的本机地址,consumer能访问到这个地址。node
可是假如服务放在docker容器中,而调用者并不在docker中,它们的网段是不同的。git
这个时候就出现问题了,consumer没法访问到provider了。github
新版的Dubbo提供了四个配置来指定与注册服务相关的地址和端口。docker
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。具体代码以下:shell
/** * 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。api
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,那么咱们经过下面的命令,app
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 。socket
先启动一个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-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的地址,这个是访问不了的。
传入环境变量从新启动,
docker run -e DUBBO_IP_TO_REGISTRY=192.168.1.8 -e DUBBO_PORT_TO_REGISTRY=20880 -p 20880:20880 -it --rm dubbo-demo
这时候就变成主机地址了。
当在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。