Dubbo與Kubernetes集成

Dubbo應用遷移到docker的問題

Dubbo是阿里開源的一套服務治理與rpc框架,服務的提供者經過zookeeper把本身的服務發佈上去,而後服務調用方經過zk獲取服務的ip和端口,dubbo客戶端經過本身的軟負載功能自動選擇服務提供者並調用,整個過程牽涉到的三方關係以下圖所示。
java

在正常的狀況下,這三方都在同一個互通的網段,provider提供給zk的就是獲取到的本機地址,consumer能訪問到這個地址。node

可是假如服務放在docker容器中,而調用者並不在docker中,它們的網段是不同的。
git

這個時候就出現問題了,consumer沒法訪問到provider了。github

Dubbo提供的解決方案

新版的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

這時候就變成主機地址了。

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

看一下zk起來了

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->2181/tcp   zkserver

如今啓動dubbo provider,使用link的方式鏈接zk,個人本機地址是192.168.1.8,provider本身暴露的端口爲20880,主機暴露端口改爲28888。

在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。

相關文章
相關標籤/搜索