AI写作智能体 自主规划任务,支持联网查询和网页读取,多模态高效创作各类分析报告、商业计划、营销方案、教学内容等。 广告
在上一小节,我们为大家讲解了如何在linux环境下搭建集群式Eureka服务注册中心。有的朋友可能会遇到下面的问题(导致服务注册失败、健康检查失败): ![](https://img.kancloud.cn/28/5a/285ad9b3add563fe69696f4eb6f575c5_748x118.png) 上图中蓝色部分:大家可以明确的看到eureka server的服务绑定的ip是10.0.2.15?这是为什么?我们上一节中,也没有使用过这个ip啊,我们使用的是192.168.161.3。这是因为我的CentOS服务器上有多个网卡,还有一些docker相关的虚拟网卡。“多网卡”在生产环境上是非常常见的情况。**怎么让eureka server服务绑定实例我们期望它绑定的网卡?** ## 一、配置实现 首先来看一下,我的服务器(虚拟机)上面的网卡设备,一共五个(虚拟的)。 * docker0虚拟网卡是因为我在这台机器上安装了docker * enp0s3网卡是一个NAT网络的网卡,虚拟机上常用。大家注意它的ip是10.0.2.15。 * enp0s8才是我们真正需要使用的本地网络。 ![](https://img.kancloud.cn/16/bb/16bbd04d1ff6c6b8a5f4e73376d55cae_854x801.png) 那我们现在要做的就是通知spring cloud,我们部署的微服务希望ip是192.168的本地网段。不要使用docker0和enp0s3的网段。 * spring.cloud.inetutils.preferredNetworks表示我们期望使用的网段,可以使用正则表达式 * spring.cloud.inetutils.ignoredInterfaces表示我们希望忽略掉的网卡设备。 * 另外我们重新配置了eureka.instance.instance-id。这个问题比较特殊,spring cloud在组成instance-id规则的时候,并没有遵守我们的preferredNetworks和ignoredInterfaces约定(有可能是版本问题,没准下一个版本就好了)。所以我们不要在instance-id使用ip(因为enp0s3虚拟机桥接网卡的ip在所有的虚拟机上都是10.0.2.15),这导致所有eureka server的instance-id全一样,所以只能注册成功其中一个。 ~~~ server: port: 8761 servlet: context-path: /eureka spring: application: name: eureka-server cloud: inetutils: preferredNetworks: - 192.168 ignoredInterfaces: - enp0s3 - docker0 eureka: instance: hostname: peer1 instance-id: ${spring.application.name}-${eureka.instance.hostname}:${server.port} health-check-url: http://${eureka.instance.hostname}:${server.port}/${server.servlet.context-path}/actuator/health client: #从其他两个实例同步服务注册信息 fetch-registry: true #向其他的两个eureka注册当前eureka实例 register-with-eureka: true service-url: defaultZone: http://peer2:8761/eureka/eureka/,http://peer3:8761/eureka/eureka/ ~~~ ## 二、多网卡ip选择配置方法总结归纳 除去上面的配置方法,还有其他能实现多网卡ip选择的方式,可以根据自己的网络环境情况选择使用。归纳如下: ### 2.1、方法一:直接配置eureka.instance.ip-address ~~~ eureka.instance.ip-address=192.168.1.7 ~~~ 直接配置一个完整的ip,一般适用于环境单一场景,对于复杂场景缺少有利支持。比如:你的eureka环境是结合docker容器部署的,就会有问题。因为docker容器的ip是动态的不固定的,所以你很难为docker容器中的服务指定ip。所以这种方式通常不建议使用。 ### 2.2、方法二:增加inetutils相关配置 配置对应org.springframework.cloud.commons.util.InetUtilsProperties,其中包含: | 配置 | 说明 | | --- | --- | | spring.cloud.inetutils.default-hostname | 默认主机名,只有解析出错才会用到 | | spring.cloud.inetutils.default-ip-address | 默认ip地址,只有解析出错才会用到 | | spring.cloud.inetutils.ignored-interfaces | 配置忽略的网卡地址 | | spring.cloud.inetutils.preferred-networks | 期望优先匹配的网卡,正则匹配的ip地址或者ip前缀 | | spring.cloud.inetutils.timeout-seconds | 计算主机ip信息的超时时间,默认1秒钟 | | spring.cloud.inetutils.use-only-site-local-interfaces | 只使用内网ip | 上面已经为大家介绍了ignored-interfaces和preferred-networks用法,其他的配置举例说明如下: 使用/etc/hosts中主机名称映射的ip,这一种在docker swarm环境中比较好用。 ~~~ # 随便配置一个不可能存在的ip,会走到InetAddress.getLocalHost()逻辑。 spring.cloud.inetutils.preferred-networks=none ~~~ 当所有的网卡遍历逻辑都没有找到合适的网卡ip,会走JDK的InetAddress.getLocalHost()。该方法会返回当前主机的hostname, 然后会根据hostname解析出对应的ip。 ~~~ # 只使用内网地址,遵循 RFC 1918 # 10/8 前缀 # 172.16/12 前缀 # 192.168/16 前缀 spring.cloud.inetutils.use-only-site-local-interfaces=true ~~~ ## 2.3.通过启动命令行传递配置 ~~~ java -jar xxx.jar --spring.cloud.inetutils.preferred-networks= #需要设置的IP地址 或者 java -jar xxx.jar --spring.cloud.inetutils.ignored-interfaces= #需要过滤掉的网卡 ~~~ ## 三、源码解析 为了说明这个问题的解决方案,我们需要翻看一下Eureka Client的源码。com.netflix.appinfo包下的InstanceInfo类封装了本机信息,其中就包括了IP地址。在 Spring Cloud 环境下,Eureka Client并没有自己实现探测本机IP的逻辑,而是交给Spring的InetUtils工具类的findFirstNonLoopbackAddress()方法完成的: ~~~ 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; } // 判断是否是被忽略的网卡 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 { // 如果以上逻辑都没有找到合适的网卡,则使用JDK的InetAddress.getLocalhost() return InetAddress.getLocalHost(); } catch (UnknownHostException e) { log.warn("Unable to retrieve localhost"); } return null; } ~~~ 结合这段源码的上下文环境,调试,可以总结如下的一些网卡及ip的选择方法。