流浪之星 發表於 2019-11-4 11:02:00

Dubbo与Kubernetes集成

<h1 id="dubbo应用迁移到docker的问题">Dubbo应用迁移到docker的问题</h1>
<p>Dubbo是阿里开源的一套服务治理与rpc框架,服务的提供者通过zookeeper把自己的服务发布上去,然后服务调用方通过zk获取服务的ip和端口,dubbo客户端通过自己的软负载功能自动选择服务提供者并调用,整个过程牵涉到的三方关系如下图所示。<br>
<img src="https://img2018.cnblogs.com/blog/510256/201911/510256-20191103203553340-392248273.jpg"></p>
<p>在正常的情况下,这三方都在同一个互通的网段,provider提供给zk的就是获取到的本机地址,consumer能访问到这个地址。</p>
<p>但是假如服务放在docker容器中,而调用者并不在docker中,它们的网段是不一样的。<br>
<img src="https://img2018.cnblogs.com/blog/510256/201911/510256-20191103203812659-1563231972.jpg"></p>
<p>这个时候就出现问题了,consumer无法访问到provider了。</p>
<h1 id="dubbo提供的解决方案">Dubbo提供的解决方案</h1>
<p>新版的Dubbo提供了四个配置来指定与注册服务相关的地址和端口。</p>
<pre><code>DUBBO_IP_TO_REGISTRY: 要发布到注册中心上的地址
DUBBO_PORT_TO_REGISTRY: 要发布到注册中心上的端口
DUBBO_IP_TO_BIND: 要绑定的服务地址(监听的地址)
DUBBO_PORT_TO_BIND: 要绑定的服务端口
</code></pre>
<p>以IP地址为例,Dubbo先找是不是有DUBBO_IP_TO_BIND这个配置,如果有使用配置的地址,如果没有就取本机地址。然后继续找DUBBO_IP_TO_REGISTRY,如果有了配置,使用配置,否则就使用DUBBO_IP_TO_BIND。具体代码如下:</p>
<pre><code class="language-java">      /**
         * Register &amp; bind IP address for service provider, can be configured separately.
         * Configuration priority: environment variables -&gt; java system properties -&gt; host property in config file -&gt;
         * /etc/hosts -&gt; default network address -&gt; first available network address
         *
         * @param protocolConfig
         * @param registryURLs
         * @param map
         * @return
         */
      private static String findConfigedHosts(ServiceConfig&lt;?&gt; sc,
                                                ProtocolConfig protocolConfig,
                                                List&lt;URL&gt; registryURLs,
                                                Map&lt;String, String&gt; map) {
            boolean anyhost = false;

            String hostToBind = getValueFromConfig(protocolConfig, DUBBO_IP_TO_BIND);
            if (hostToBind != null &amp;&amp; hostToBind.length() &gt; 0 &amp;&amp; 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 &amp;&amp; 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 &amp;&amp; hostToRegistry.length() &gt; 0 &amp;&amp; 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;
      }
</code></pre>
<p>然后我们看这个getValueFromConfig(),它调用了下面的函数,可以看到,它是先找环境变量,再找properties。</p>
<pre><code class="language-java">    public static String getSystemProperty(String key) {
      String value = System.getenv(key);
      if (StringUtils.isEmpty(value)) {
            value = System.getProperty(key);
      }
      return value;
    }
</code></pre>
<p>所以我们通过环境变量,就能修改Dubbo发布到zookeeper上的地址和端口。假如我们通过docker镜像启动了一个dubbo provider,并且它的服务端口是8888,假设主机地址为192.168.1.10,那么我们通过下面的命令,</p>
<pre><code class="language-shell">docker run -e DUBBO_IP_TO_REGISTRY=192.168.1.10 -e DUBBO_PORT_TO_REGISTRY=8888 -p 8888:8888 dubbo_image
</code></pre>
<p>就能让内部的服务以192.168.1.10:8888的地址发布。</p>
<p>我们通过官方的实例来演示一下,因为官方提供的案例都很久了,所以我自己重新搞了一个示例,代码在https://github.com/XinliNiu/dubbo-docker-sample.git 。</p>
<p>先启动一个zookeeper,暴露2181端口。</p>
<pre><code class="language-shell">docker run --name zkserver --rm -p 2181:2181-d zookeeper:3.4.9
</code></pre>
<p>看一下zk起来了</p>
<pre><code class="language-shell">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-&gt;2181/tcp   zkserver
</code></pre>
<p>把代码导入IDE,修改dubbo-docker-provide.xml,把地址改成刚发布到zk的地址和端口,我的地址是192.168.1.8。</p>
<p>运行DubboApplication,这时候可以看到在zk上注册了服务。</p>
<p><img src="https://img2018.cnblogs.com/blog/510256/201911/510256-20191103203845120-1341280248.png"></p>
<p>修改dubbo-docker-consumer.xml里的zk地址,执行单元测试,能正常访问。</p>
<p>把DubboApplication导出成可以执行的jar包,名字叫app.jar,创建如下Dockerfile</p>
<pre><code>FROM openjdk:8-jdk-alpine
ADD app.jar app.jar
ENV JAVA_OPTS=""
ENTRYPOINT exec java $JAVA_OPTS -jar /app.jar
</code></pre>
<p>创建dubbo-demo镜像,在同样的目录里执行docker build。</p>
<pre><code>docker build --no-cache -t dubbo-demo .
</code></pre>
<p>正常启动镜像</p>
<pre><code>docker run-p 20880:20880-it --rm dubbo-demo
</code></pre>
<p>发现是172.16.0.3的地址,这个是访问不了的。<br>
<img src="https://img2018.cnblogs.com/blog/510256/201911/510256-20191103203904690-262611936.png"></p>
<p>传入环境变量重新启动,</p>
<pre><code class="language-shell">docker run-e DUBBO_IP_TO_REGISTRY=192.168.1.8 -e DUBBO_PORT_TO_REGISTRY=20880 -p 20880:20880-it --rm dubbo-demo
</code></pre>
<p>这时候就变成主机地址了。<br>
<img src="https://img2018.cnblogs.com/blog/510256/201911/510256-20191103203922400-2141395026.png"></p>
<pre><code class="language-shell">docker run --name zkserver --rm -p 42181:2181-d zookeeper:3.4.9
</code></pre>
<p>看一下zk起来了</p>
<pre><code class="language-shell">niuxinli@niuxinli-B450M-DS3H:~/docker_dubbo_demo/dubbo-samples/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:42181-&gt;2181/tcp   zkserver
</code></pre>
<p>现在启动dubbo provider,使用link的方式连接zk,我的本机地址是192.168.1.8,provider自己暴露的端口为20880,主机暴露端口改成28888。</p>
<h1 id="在kubernetes中使用dubbo">在Kubernetes中使用Dubbo</h1>
<p>当在Kubernetes中启动多个副本的时候,指定具体的IP和具体的端口,都是不可行的,因为每个机器的IP都不一样,不能写很多个yaml文件,而且一旦指定了具体端口,那这台主机的这个端口就被占用了。</p>
<p>我们可以通过创建Service,使用NodePort的方式,把端口固定住,这样端口的问题就解决了。因为是对外服务,所以使用ClusterIP肯定是不行了,IP有两种解决办法:</p>
<p>(1)使用Kubernetes的downward api动态的传入主机的ip。</p>
<p>(2)传固定的loadbalancer的地址,例如在所有的node之外有一个F5。</p>
<p>不管哪种方法,都是一种妥协的办法,很不“云原生”,我演示一下使用downward api动态传入主机地址,并使用nodeport固定端口的方式。</p>
<p>我的kubernetes集群如下:</p>
<table>
<thead>
<tr>
<th>角色</th>
<th>地址</th>
</tr>
</thead>
<tbody>
<tr>
<td>master</td>
<td>192.168.174.50</td>
</tr>
<tr>
<td>node1</td>
<td>192.168.174.51</td>
</tr>
<tr>
<td>node2</td>
<td>192.168.174.52</td>
</tr>
<tr>
<td>node3</td>
<td>192.168.174.53</td>
</tr>
</tbody>
</table>
<p>zk的地址是192.168.1.8,它与集群的主机互通。</p>
<p>我没有建private镜像仓库,把我之前打好的dubbo-demo直接push到docker-hub上了,名字是nxlhero/dubbo-demo。</p>
<p>创建Service,使用的NodePort为30001,创建4个副本,这样3台机器上正好有一台起两个pod。</p>
<pre><code class="language-yaml">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
</code></pre>
<p>这个yaml最关键的地方就是环境变量,主机IP通过downward apid传入,端口使用固定的nodeport。</p>
<pre><code>      env:
      - name: DUBBO_IP_TO_REGISTRY
          valueFrom:
            fieldRef:
            fieldPath: status.hostIP
      - name: DUBBO_PORT_TO_REGISTRY
          value: "30001"
</code></pre>
<p>创建Service,启动后可以看到zookeeper上的地址都是主机的地址和nodeport。<br>
<img src="https://img2018.cnblogs.com/blog/510256/201911/510256-20191103204022889-812060137.png"></p><br><br>
来源:https://www.cnblogs.com/nxlhero/p/11789006.html
頁: [1]
查看完整版本: Dubbo与Kubernetes集成