Spring Cloud:Eureka的多网卡IP选择问题

问题概述

本文主要为了解决,在使用Docker部署Spring Boot应用,Spring Boot在向Eureka注册时,如何配置正确IP的问题。

解决方案配置

先把最终解决方案的配置贴出来:

  • Gitlab Runners,该机器的网卡和IP信息如下。可以看到本机eth0网卡的IP为10.16.180.7,docker0网卡的IP为172.17.0.1
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 0.0.0.0
        ether 02:42:50:e1:74:da  txqueuelen 0  (Ethernet)
        RX packets 114151657  bytes 17800379440 (16.5 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 217089263  bytes 288568000281 (268.7 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

docker_gwbridge: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.18.0.1  netmask 255.255.0.0  broadcast 0.0.0.0
        ether 02:42:99:31:8f:ad  txqueuelen 0  (Ethernet)
        RX packets 182761  bytes 11494613 (10.9 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 139451  bytes 9795778 (9.3 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 10.16.180.7  netmask 255.255.240.0  broadcast 10.16.191.255
        ether fa:16:3e:0e:3d:fd  txqueuelen 1000  (Ethernet)
        RX packets 442851873  bytes 307687290719 (286.5 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 356404292  bytes 51936607569 (48.3 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 0  (Local Loopback)
        RX packets 649114  bytes 47094870 (44.9 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 649114  bytes 47094870 (44.9 MiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

veth0133ad0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        ether 96:6f:6d:89:29:7b  txqueuelen 0  (Ethernet)
        RX packets 945694  bytes 2541883907 (2.3 GiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1978551  bytes 2484265627 (2.3 GiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

...

在注册Gitlab Runner时,Description填上该机器的IP地址:10.16.180.7

Gitlab Runner配置

  • 采用Gitlab CI发布Spring Boot应用,.gitlab-ci.yml文件如下:
image: docker-registry.qiyi.virtual/ads-bi/docker:dind
stages:
  - build
  - deploy

cache:
  paths:
    - .m2

job-build:
  image: docker-registry.qiyi.virtual/ads-bi/maven:3.5-jdk-8-slim
  tags:
    - test-env
  stage: build
  script:
    - source ~/.bashrc && mvn -Dmaven.repo.local=.m2 -Duser.timezone=GMT+08 clean install test sonar:sonar
  artifacts:
    name: "$CI_PROJECT_NAME"
    paths:
      - target/*.jar

job-deploy:
  image: docker-registry.qiyi.virtual/ads-bi/docker:dind
  environment: production
  tags:
    - test-env
  stage: deploy
  before_script:
    - SERVER_PORT=`awk -v min=10000 -v max=20000 'BEGIN{srand(); print int(min+rand()*(max-min+1))}'`
  script:
    - docker build -t docker-registry.qiyi.virtual/ads-bi/${CI_PROJECT_NAME}:prod
        --build-arg spring_profiles_active=prod
        --build-arg server_port=${SERVER_PORT}
        --build-arg spring_application_name=${CI_PROJECT_NAME}
        --build-arg ip_address=${CI_RUNNER_DESCRIPTION}
        .
    - docker push docker-registry.qiyi.virtual/ads-bi/${CI_PROJECT_NAME}:prod
    - sh ./scripts/start_job.sh ${CI_PROJECT_NAME} prod ${SERVER_PORT}
    - docker rmi --force $(docker images | grep " <none>" | awk '{print $3}') || true
  • Spring Boot的启动shell脚本
#! /bin/bash
CI_PROJECT_NAME=$1
ENV=$2
SERVER_PORT=$3
TIMESTAMP=`date +%s`
CID=`docker ps | grep ${CI_PROJECT_NAME} | awk '{print $1}'`
if [ ! -z ${CID} ]; then
   docker stop ${CID} | xargs docker rm
fi
docker run -d -p ${SERVER_PORT}:${SERVER_PORT} -v /var/log:/var/log --name ${CI_PROJECT_NAME}-${TIMESTAMP} docker-registry.qiyi.virtual/ads-bi/${CI_PROJECT_NAME}:${ENV}

  • Dockerfile
FROM docker-registry.qiyi.virtual/ads-bi/maven:3.5-jdk-8-slim
VOLUME /tmp
ADD target/*.jar /app.jar
ARG spring_profiles_active
ARG server_port
ARG spring_application_name
ARG ip_address
ENV SPRING_PROFILES_ACTIVE=${spring_profiles_active}
ENV SERVER_PORT=${server_port}
ENV SPRING_APPLICATION_NAME=${spring_application_name}
ENV EUREKA_INSTANCE_IP-ADDRESS=${ip_address}
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar", "/app.jar"]

详细解读

这里需要先介绍一下我们面临的环境。首先,我们配置了一个Gitlab的Specific Runner,作为Spring Boot构建的实际机器。在这台机器上,需要安装Docker环境,那么这台机器就有了至少两个IP地址,分别为:

  • 本机网卡eth0,ip地址为10.16.180.7
  • Docker网卡docker0,ip地址为172.17.0.1

这里我们最终想在Eureka中注册的地址,是本机网卡的地址
因为Spring Cloud Admin是通过Eureka中注册的地址,来判断Spring Boot应用是否健康的,如果注册使用的是docker0的地址,这个地址并不能通过http访问,Spring Cloud Admin会认为该应用是Down的,虽然它实际的状态是UP的。

在网上,我们能找到不少介绍Spring Cloud Eureka的多网卡选择的文章,例如这篇文章介绍的就很好:
https://blog.csdn.net/xichenguan/article/details/76632033

这里提到了几个重要的Spring Cloud配置:

  • eureka.instance.prefer-ip-address: true/false
  • spring.cloud.inetutils.preferred-networks: list<ip>
  • spring.cloud.inetutils.ignored-interfaces: list<ip>

关于这几个配置项的含义,可以参考Spring Cloud的官方文档,介绍的更加准确:
https://cloud.spring.io/spring-cloud-static/spring-cloud.html#ignore-network-interfaces

这几个配置的目的,就是Spring Boot在启动时,会在多网卡的环境,根据配置项的信息,进行网卡的选择。找到这里,我们认为可能通过这项配置就可以选择到想要的ip地址了。

但实际并不是这样的,在经过无数次的尝试之后,我们发现在Eureka中每次注册的ip地址始终都是:


image.png

这个问题我们使用百思不得其解,在翻看这部分的源码时,我们发现Spring Boot使用的是InetUtils类进行ip选择的。这部分的源码在遍历每个可用的网络之后,结合我们配置的preferred和ignored信息,选择index最小的这个ip作为最终的返回ip。

public InetAddress findFirstNonLoopbackAddress() {
    InetAddress result = null;
    try {
        int lowest = Integer.MAX_VALUE;
        for (Enumeration < NetworkInterface > nics = NetworkInterface
            .getNetworkInterfaces(); nics.hasMoreElements();) {
            NetworkInterface ifc = nics.nextElement();
            if (ifc.isUp()) {
                log.trace("Testing interface: " + ifc.getDisplayName());
                if (ifc.getIndex() < lowest || result == null) {
                    lowest = ifc.getIndex();
                } else if (result != null) {
                    continue;
                }

                // @formatter:off
                if (!ignoreInterface(ifc.getDisplayName())) {
                    for (Enumeration < InetAddress > addrs = ifc
                        .getInetAddresses(); addrs.hasMoreElements();) {
                        InetAddress address = addrs.nextElement();
                        if (address instanceof Inet4Address &&
                            !address.isLoopbackAddress() &&
                            !ignoreAddress(address)) {
                            log.trace("Found non-loopback interface: " +
                                ifc.getDisplayName());
                            result = address;
                        }
                    }
                }
                // @formatter:on
            }
        }
    } catch (IOException ex) {
        log.error("Cannot get first non-loopback address", ex);
    }

    if (result != null) {
        return result;
    }

    try {
        return InetAddress.getLocalHost();
    } catch (UnknownHostException e) {
        log.warn("Unable to retrieve localhost");
    }

    return null;
}

这里我们发现,可以通过调整log的打印等级,将选择过程中trace等级的日志打印出来。在调整之后,查看服务器端这部分的日志如下:

2018-05-04 06:43:00.610 [main] DEBUG org.elasticsearch.common.network -configuration:

lo
        inet 127.0.0.1 netmask:255.0.0.0 scope:host
        inet6 ::1 prefixlen:128 scope:host
        UP LOOPBACK mtu:65536 index:1

eth0
        inet 172.17.0.5 netmask:255.255.0.0 broadcast:0.0.0.0 scope:site
        inet6 fe80::42:acff:fe11:3 prefixlen:64 scope:link
        hardware 02:42:AC:11:00:03
        UP MULTICAST mtu:1500 index:4899

...
2018-05-04 06:43:02.332 [main] TRACE org.springframework.cloud.commons.util.InetUtils -Testing interface: eth0
2018-05-04 06:43:02.332 [main] TRACE org.springframework.cloud.commons.util.InetUtils -Found non-loopback interface: eth0
2018-05-04 06:43:02.332 [main] TRACE org.springframework.cloud.commons.util.InetUtils -Testing interface: lo

这时我们才明白,原来我们采用的是dind的这个image,进行Spring Boot Application Docker镜像的生成,发布和启动。那么在构建时,会生成一个dind的container,这个container的网卡信息如上所示。所以我们配置时,所有可选的网卡并不属于Gitlab Runner这台机器,而是Docker container的,那么之前关于多网卡的配置无论如何都是无用的了。

在发现问题的原因之后,我们发现这里需要做的,是把Docker所在的主机的网卡ip信息,传递到container中。由于这两个环境是隔离的,所以并不现实

那么最终如何才能把Docker所在主机的ip,顺利的传递到container中呢,我们利用了Gitlab Runner注册时填写的信息。我们在Gitlab Runner注册时,将该机器的ip,填写到Description中。在.gitlab-ci.yml中,可以利用内置参数${CI_RUNNER_DESCRIPTION},将此ip传递到Dockerfile中。在Dockerfile中,通过设置系统环境变量的方式,使Spring Boot获取到这个环境变量。

2018-05-04 06:37:34.611 [main] TRACE o.s.core.env.PropertySourcesPropertyResolver -Searching for key 'eureka.instance.ip-address' in PropertySource 'servletConfigInitParams'
2018-05-04 06:37:34.611 [main] TRACE o.s.core.env.PropertySourcesPropertyResolver -Searching for key 'eureka.instance.ip-address' in PropertySource 'servletContextInitParams'
2018-05-04 06:37:34.611 [main] TRACE o.s.core.env.PropertySourcesPropertyResolver -Searching for key 'eureka.instance.ip-address' in PropertySource 'systemProperties'
2018-05-04 06:37:34.611 [main] TRACE o.s.core.env.PropertySourcesPropertyResolver -Searching for key 'eureka.instance.ip-address' in PropertySource 'systemEnvironment'
2018-05-04 06:37:34.611 [main] DEBUG o.s.core.env.SystemEnvironmentPropertySource -PropertySource 'systemEnvironment' does not contain property 'eureka.instance.ip-address', but found equivalent 'EUREKA_INSTANCE_IP-ADDRESS'
2018-05-04 06:37:34.611 [main] DEBUG o.s.core.env.PropertySourcesPropertyResolver -Found key 'eureka.instance.ip-address' in PropertySource 'systemEnvironment' with value of type String

这样就实现了Spring Boot的ip指定,在Eureka中注册的我们想要的Gitlab Runner主机ip,最终结果图如下:
Spring Cloud Eureka:


Spring Cloud Eureka

Spring Cloud Eureka URL

Spring Cloud Admin:


Spring Cloud Admin

启示

  • 使用Docker部署应用时,需要理清楚各个环境之间的关系
  • 在遇到难解的问题,通过查看源码,并且分析打印日志的方式,定位问题原因
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,542评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,596评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,021评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,682评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,792评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,985评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,107评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,845评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,299评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,612评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,747评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,441评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,072评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,828评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,069评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,545评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,658评论 2 350