美文网首页
Eureka注册指定IP段

Eureka注册指定IP段

作者: 胸毛飘逸 | 来源:发表于2020-11-19 11:30 被阅读0次

    发现问题

    最近项目在Eureka注册时,发现一个问题:注册的IP地址不是 10.10.3.XXX 的网络IP,而是另外一个网段的地址。

    通过 ipconfig 命令查看本机的IP地址发现,该IP是本机虚拟网卡VMware的地址。

    无线局域网适配器 本地连接* 13:
    
       媒体状态  . . . . . . . . . . . . : 媒体已断开连接
       连接特定的 DNS 后缀 . . . . . . . :
    
    以太网适配器 以太网 3:
    
       连接特定的 DNS 后缀 . . . . . . . :
       本地链接 IPv6 地址. . . . . . . . : fe80::65b2:b524:664b:c414%13
       IPv4 地址 . . . . . . . . . . . . : 10.10.3.208
       子网掩码  . . . . . . . . . . . . : 255.255.255.0
       默认网关. . . . . . . . . . . . . : 10.10.3.1
    
    以太网适配器 VMware Network Adapter VMnet1:
    
       连接特定的 DNS 后缀 . . . . . . . :
       本地链接 IPv6 地址. . . . . . . . : fe80::d86f:a26d:c8f6:7038%3
       IPv4 地址 . . . . . . . . . . . . : 192.168.15.1
       子网掩码  . . . . . . . . . . . . : 255.255.255.0
       默认网关. . . . . . . . . . . . . :
    
    以太网适配器 VMware Network Adapter VMnet8:
    
       连接特定的 DNS 后缀 . . . . . . . :
       本地链接 IPv6 地址. . . . . . . . : fe80::6415:235:98c4:9ca7%2
       IPv4 地址 . . . . . . . . . . . . : 192.168.38.1
       子网掩码  . . . . . . . . . . . . : 255.255.255.0
       默认网关. . . . . . . . . . . . . :
    

    问题现象

    Eureka管理页面注册列表展示的IP地址非局域网IP地址,是虚拟机的虚拟IP地址

    可能引起的问题

    多人开发时,同事通过Feign调用接口,无法正确匹配IP地址,从而导致接口调用失败。

    尝试解决

    通过百度查找,提供了该解决方案:在 yml 文件中添加一下的配置,以达到忽略指定网卡的目的

    spring:
      cloud:
        inetutils:
          ignored-interfaces: ## 忽略网卡
          - VMware.*
    

    可是当我添加该配置后,发现仍不起作用!页面上显示的instance-id 还是 192.168.38.1 。这就很难受了

    刨根问底

    于是我想能不能跑一跑Spring的启动代码,看看他到底是怎么取IP的。首先从Eureka自动配置类EurekaClientAutoConfiguration入手

    @Bean
    @ConditionalOnMissingBean(value = EurekaInstanceConfig.class, search = SearchStrategy.CURRENT)
    public EurekaInstanceConfigBean eurekaInstanceConfigBean(InetUtils inetUtils,
                                                             ManagementMetadataProvider managementMetadataProvider) {
        String hostname = getProperty("eureka.instance.hostname");
        // 是否使用IP地址注册。这里就是从配置文件寻值,没找到就用默认值 false
        boolean preferIpAddress = Boolean.parseBoolean(getProperty("eureka.instance.prefer-ip-address"));
        // 获取配置的IP地址
        String ipAddress = getProperty("eureka.instance.ip-address");
    
        instance.setPreferIpAddress(preferIpAddress);
     
        
        if (StringUtils.hasText(ipAddress)) {
            instance.setIpAddress(ipAddress);
        }
    
        return instance;
    }
    

    这里可以看到,Eureka并没有自己直接去系统获取IP地址,而是通过Spring的InetUtils类的findFirstNonLoopbackHostInfo来设置IP地址

    public EurekaInstanceConfigBean(InetUtils inetUtils) {
        this.inetUtils = inetUtils;
        this.hostInfo = this.inetUtils.findFirstNonLoopbackHostInfo();
        this.ipAddress = this.hostInfo.getIpAddress();
        this.hostname = this.hostInfo.getHostname();
    }
    

    接着看看findFirstNonLoopbackHostInfo()方法的代码。我在本地Debug跑的时候,项目启动该类会被调用两次,一次没有读取配置文件,项目启动Banner也没有打印,第二次配置文件已经读取。启动日志也打印了一部分。这里原因留个坑

    public InetAddress findFirstNonLoopbackAddress() {
            InetAddress result = null;
            try {
                int lowest = Integer.MAX_VALUE;
                for (Enumeration<NetworkInterface> nics = NetworkInterface
                        .getNetworkInterfaces(); nics.hasMoreElements();) {
                    NetworkInterface ifc = nics.nextElement();
                    // 该网络接口是否启用并正在运行。调用的是native方法
                    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())) {
                            // 遍历IP地址
                            for (Enumeration<InetAddress> addrs = ifc
                                    .getInetAddresses(); addrs.hasMoreElements();) {
                                InetAddress address = addrs.nextElement();
                                // 找到 IPV4 且不是回环地址(127.0.0.1) 且是优先选择的地址
                                if (address instanceof Inet4Address
                                        && !address.isLoopbackAddress()
                                        && isPreferredAddress(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;
        }
    

    从上面的代码及注释可以看到,SpringCloud选择IP的原则是:选择已启动网卡的第一个不在忽略范围且不是回环地址(127.0.0.1)且是优先选择地址的IPV4地址

    那么我们想要重定义其所选的IP地址,就需要从忽略范围 和 是否是优先选择地址来做了。

    判断是否在忽略范围的代码

    由该段代码知,要忽略的网口集合需要从 IgnoredInterfaces 这个属性中获得,那这个属性的值是什么?怎么配置呢?

    /** for testing */ boolean ignoreInterface(String interfaceName) {
        // 遍历IgnoredInterfaces属性集合,该集合内是忽略的网口名字的正则表达式形式
        for (String regex : this.properties.getIgnoredInterfaces()) {
            if (interfaceName.matches(regex)) {
                log.trace("Ignoring interface: " + interfaceName);
                return true;
            }
        }
        return false;
    }
    

    判断是否是首选地址的代码

    /** for testing */ boolean isPreferredAddress(InetAddress address) {
        // 如果配置了仅使用本地接口,则当该InetAddress是本地站点地址时返回
        if (this.properties.isUseOnlySiteLocalInterfaces()) {
            final boolean siteLocalAddress = address.isSiteLocalAddress();
            if (!siteLocalAddress) {
                log.trace("Ignoring address: " + address.getHostAddress());
            }
            return siteLocalAddress;
        }
        // 如果preferredNetworks列表没有配置,则所有地址返回True
        final List<String> preferredNetworks = this.properties.getPreferredNetworks();
        if (preferredNetworks.isEmpty()) {
            return true;
        }
        // 如果配置了,则返回符合正则的地址
        for (String regex : preferredNetworks) {
            final String hostAddress = address.getHostAddress();
            if (hostAddress.matches(regex) || hostAddress.startsWith(regex)) {
                return true;
            }
        }
        log.trace("Ignoring address: " + address.getHostAddress());
        return false;
    }
    
    断点

    结论

    1. eureka 显示的 instance-id 有两种值,通过 prefer-ip-address 的值来选择
    • ip:端口 true
    • hostname 主机名 false(默认)
    1. 当 prefer-ip-address 的值为 true 时,eureka 取这个值:eureka.client.instance-id
    • 配置文件中的: eureka.client.instance-id 。可以配置成自己手写的值,也可以自动获取,通过 ${spring.cloud.client.ip-address}/{server.port} 来获取。但是 spring.cloud.client.ip-address 值的取值有一个BUG,Spring 从 InetUtils 获取 ipaddress 作为该值。但是取得是刚刚项目启动时获取的 ipaddress 参数(在加载yml中的配置之前)
    • 如果没有配置就又取hostname了
    1. feign / ribbon 调用地址使用的是读取配置文件后的地址。故配置了忽略名单后,显示的虽然错误。但不会影响服务调用。

    所以如果读者想要即不影响调用,又不影响直接看地址需要增加一下配置:

    yml写法:

    spring:
      cloud:
        inetutils:
          ignoredInterfaces:
            - VMware.* # 忽略虚拟机网卡
          use-only-site-local-interfaces: true
          preferred-networks: 10.10.*.* # 优先使用 10.10.*.*
    

    properties写法:

    # 忽略虚拟机网卡
    spring.cloud.inetutils.ignoredInterfaces=VMware.*
    spring.cloud.inetutils.use-only-site-local-interfaces=true
    # 优先使用 10.10.*.*
    spring.cloud.inetutils.preferred-networks=10.10.*.*
    

    相关文章

      网友评论

          本文标题:Eureka注册指定IP段

          本文链接:https://www.haomeiwen.com/subject/slbkiktx.html