Eureka在有虛擬網卡的情況下獲取正確的IP


發現問題

最近項目在Eureka注冊時,發現一個問題:注冊的IP地址不是 192.168.0.XXX 的網絡IP,而是另外一個網段的地址,如圖

image-20200309130902034

通過 ipconfig 命令查看本機的IP地址發現,該IP是本機虛擬網卡VMnet8的地址。

X:\Users\Keats>ipconfig

Windows IP 配置


以太網適配器 以太網:

   連接特定的 DNS 后綴 . . . . . . . :
   IPv4 地址 . . . . . . . . . . . . : 192.168.0.234
   子網掩碼  . . . . . . . . . . . . : 255.255.255.0
   默認網關. . . . . . . . . . . . . : 192.168.0.1


以太網適配器 VMware Network Adapter VMnet1:

   連接特定的 DNS 后綴 . . . . . . . :
   IPv4 地址 . . . . . . . . . . . . : 192.168.87.1
   子網掩碼  . . . . . . . . . . . . : 255.255.255.0
   默認網關. . . . . . . . . . . . . :

以太網適配器 VMware Network Adapter VMnet8:

   連接特定的 DNS 后綴 . . . . . . . :
   IPv4 地址 . . . . . . . . . . . . : 192.168.29.1
   子網掩碼  . . . . . . . . . . . . : 255.255.255.0
   默認網關. . . . . . . . . . . . . :

問題現象

Eureka管理頁面注冊列表展示的IP地址非局域網IP地址,是虛擬機的虛擬IP地址

可能引起的問題

多人開發時,同事通過Feign調用接口,無法正確匹配IP地址,從而導致接口調用失敗。

嘗試解決

通過百度查找,提供了該解決方案:在 yml 文件中添加一下的配置,以達到忽略指定網卡的目的

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

可是當我添加該配置后,發現仍不起作用!頁面上顯示的instance-id 還是 192.168.29.1:8083 。這就很難受了

刨根問底

於是我想能不能跑一跑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;
}

image-20200310134142910

結論

  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 調用地址使用的是讀取配置文件后的地址。故配置了忽略名單后,顯示的雖然錯誤。但不會影響服務調用。

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

spring:
  cloud:
    inetutils:
      ignoredInterfaces:
        - VMware.* # 忽略虛擬機網卡
      use-only-site-local-interfaces: true
      preferred-networks: 192.168.0.* # 優先使用 192.168.0.*
# 注冊中心
eureka:
  instance:
    leaseRenewalIntervalInSeconds: 1
    leaseExpirationDurationInSeconds: 2
    prefer-ip-address: true # 使用IP地址注冊
    instance-id: 192.168.0.234:8083 # 優先使用這個配置地址。如果沒有則從SpringCloud的 InetUtils 獲取
#    ip-address: 如果是雲服務器,如阿里雲。可以通過此屬性來指定外網調用地址


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM