Docker环境下注册eureka的微服务status-page-url和health-check-url异常问题排查结果整理

一、问题简述

  • 172.17.0.2:为docker容器内部IP地址。
  • x00001.prod.shunyi.beijing为服务器名,即HostName。
  • 10.20.30.40:为服务器IP地址,使我们真正需要的地址。
  • ${pers.hanchao.ip}:为服务器IP地址的占位符表示方式,用于传递给docker容器。

1.1.eureka注册成功之后的正常表现

  • 在eureka注册中心,显示注册成功的服务的实例状态,点击实例名称,跳转至status-page-url页面,显示服务实例的状态。形如:

    // 20190615100528
    // http://10.20.30.40:8888/info
    
    {
      
    }
    
  • 在Spring Boot Admin运行日志中,显示了正常的health-check-url地址:形如:http://10.20.30.40:10002/health。

  • 在Spring Boot Admin应用管理页面,注册成功的服务整体状态为"UP",即:上线状态。

1.2.当前异常服务表现

  • A类服务
    • 在eureka注册中心一切正常,即status-page-url正常。
    • 在Spring Boot Admin运行日志中,显示health-check-url正常。
    • 在Spring Boot Admin应用管理页面,服务整体状态总是显示"DOWN"。
  • B类服务
  • C类服务

二、相关知识

想要搞清楚为什么部分微服务的eureka注册结果异常,先要弄清楚status-page-url、health-check-url以及Spring Boot Admin的服务整体状态的逻辑。

下面,依次对其进行描述。

2.1.Spring Boot Admin的服务整体状态

Spring Boot Admin的服务整体状态不仅仅是服务本身的状态,还包括此服务依赖的服务的状态。

服务整体状态可能是由以下状态综合决定的:

  • 服务本身的状态
  • eureka-client的状态
  • eureka-server的状态
  • 硬盘空间的状态
  • 依赖的数据库的状态
  • 依赖的ElasticSearch的状态
  • 依赖的Redis的状态
  • 。。。

服务整体状态具体依赖于哪些状态要看pom.xml中都依赖的哪些服务。

2.2.status-page-url和health-check-url

整体说明

  • eureka注册中心通过status-page-url去查询服务状态。
  • Spring Boot Admin通过health-check-url去检查服务状态。
  • status-page-url和health-check-url可以显式配置,即eureka.instance.status-page-urleureka.instance.health-check-url
  • 一般情况下,无需显式配置,eureka-client会根据规则自动拼接形成status-page-url和health-check-url。
  • 以上所述的三类项目,都没有显式配置。

版本说明

  • 不同版本的eureka-client拥有不同的status-page-url和health-check-url自动生成规则(其实这是马后炮,是解决问题过程中发现的)。
  • A类服务和B类服务依赖的版本是1.5.6.RELEASE;C类服务依赖的是1.5.8.RELEASE。
  • 下面对两种版本status-page-url和health-check-url的自动生成规则进行简述。
  • 后面章节对1.5.6.RELEASE生成规则的源码解读,关于1.5.8RELEASE的生成规则源码解读可以参考进行。

1.5.6.RELEASE版本status-page-url和health-check-url的自动生成规则

  • 获取配置文件prefer-ip-address,即:是否有限选择ip-address。
  • 获取配置文件ip-address配置的地址IpAddressConfiged。
  • 通过Socket相关接口获取本机(或容器)的IpAddressLocal和HostNameLocal。
  • 将服务真正使用的IpAddressUesd设置为IpAddressLocal,服务使用的HostNameUsed设置为HostNameLocal。
  • 如果IpAddressConfiged不为空,则覆盖IpAddressUesd为IpAddressConfiged。
  • 在自动生成status-page-url和health-check-url,依据如下规则:
    • status-page-url:https://{prefer-ip-address?IpAddressUesd:HostNameUsed}/info
    • health-check-url:https://{prefer-ip-address?IpAddressUesd:HostNameUsed}/health
  • 目前实践的eureka配置中,prefer-ip-address=true,ip-address={pers.hanchao.ip},所以,自动生成规则转换为:
    • status-page-url:https://{pers.hanchao.ip}/info
    • health-check-url:https://{pers.hanchao.ip}/health

1.5.8.RELEASE版本status-page-url和health-check-url的自动生成规则

  • 获取配置文件prefer-ip-address。
  • 获取配置文件ip-address配置的地址IpAddressConfiged。
  • 通过Socket相关接口获取本机(或容器)的IpAddressLocal和HostNameLocal。
  • 将服务真正使用的IpAddressUesd设置为IpAddressLocal,服务使用的HostNameUsed设置为HostNameLocal。
  • 如果IpAddressConfiged不为空,则覆盖IpAddressUesd为IpAddressConfiged。
  • 在自动生成status-page-url和health-check-url,依据如下规则:
    • status-page-url:https://{prefer-ip-address?IpAddressUesd:HostNameUsed}/info
    • health-check-url:https://{prefer-ip-address?IpAddressUesd:HostNameUsed}/health
  • 目前实践的eureka配置中,prefer-ip-address=true,ip-address={pers.hanchao.ip},所以,自动生成规则转换为:
    • status-page-url:https://{IpAddressLocal}/info
    • health-check-url:https://{IpAddressLocal}/health
    • ip-address这个配置在此逻辑中无效

三、问题排查

3.1.A类微服务

3.1.1.原因简析

参考2.1.章节,经排查,A类微服务引用了redis的依赖,但是并没有使用。

所以redis的连接状态为不正常的,即为DOWN,所以导致服务整体状态为DOWN。

3.1.2.修改说明

去除无用的依赖

<!-- 去除以下依赖 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

3.2.B类微服务

3.2.1.原因简析

原因一:依赖了redis,但是并未使用。参考:3.1.1.章节

原因二:eureka配置不齐全

参考章节2.2.的1.5.6.RELEASE部分,由于缺少prefer-ip-address和ip-address配置,导致最终status-page-url和health-check-url如下:

3.2.2.修改说明
  1. 修改application.properties,补全服务注册相关配置(建议参考章节四:推荐配置)。
# 补充以下配置
eureka.instance.ip-address=${pers.hanchao.ip}
eureka.instance.prefer-ip-address=true
management.security.enabled=false
  1. 修改pom.xml,去除无用的依赖。
<!-- 去除以下依赖 -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

3.3.C类微服务

3.3.1.原因简析

经排查,eureka配置本身并无问题,为题是由于SpringBoot版本不同造成的。

参考章节2.2.的1.5.8.RELEASE部分,由于ip-address配置的无效,导致最终status-page-url和health-check-url如下:

  • status-page-url:https://{IpAddressLocal}/info
  • health-check-url:https://{IpAddressLocal}/health
  • ip-address这个配置在此逻辑中无效
  • {IpAddressLocal}在本机则为本机地址,在docker则为docker内部IP地址,形如:http://172.17.0.2:10002/health等。
3.3.2.问题解决

修改bootstrap.yml,显式配置status-page-url和health-check-url(建议参考章节四:推荐配置)。

eureka:
  instance:
    ...
    prefer-ip-address: true
		# 显式配置 status-page-url和health-check-url
    status-page-url: https://${pers.hanchao.ip}:${server.port:@@server.port@@}/info
    health-check-url: https://${pers.hanchao.ip}:${server.port:@@server.port@@}/health

四、eureka推荐配置参考

  • 注意驼峰命名与短横线命名方式的区别。
  • 以下配置仅供参考,如有不足之处自行优化。

4.1.properties

eureka.client.enabled=true
# 推荐域名地址
eureka.client.service-url.defaultZone={eureka服务注册中心的注册地址}
eureka.instance.instance-id=${pers.hanchao.ip}:${spring.application.name}:${server.port}
eureka.instance.prefer-ip-address=true
eureka.instance.ip-address=${pers.hanchao.ip}
eureka.instance.lease-expiration-duration-in-seconds=15
eureka.instance.lease-renewal-interval-in-seconds=5
# 显式配置 status-page-url和health-check-url
eureka.instance.status-page-url=https://${pers.hanchao.ip}:${server.port:@@server.port@@}/info
eureka.instance.health-check-url=https://${pers.hanchao.ip}:${server.port:@@server.port@@}/health
management.security.enabled=false

4.2.yaml

eureka:
    client:
        enabled: true
        service-url:
        		# 推荐域名地址
            defaultZone: {eureka服务注册中心的注册地址}
    instance:
        instance-id: ${pers.hanchao.ip}:${spring.application.name}:${server.port}
        prefer-ip-address: true
        ip-address: ${pers.hanchao.ip}
        lease-expiration-duration-in-seconds: 15
        lease-renewal-interval-in-seconds: 5
        # 显式配置 status-page-url和health-check-url
        status-page-url: https://${pers.hanchao.ip}:${server.port:@@server.port@@}/info
        health-check-url: https://${pers.hanchao.ip}:${server.port:@@server.port@@}/health
management:
    security:
        enabled: false

五、1.5.6.RELEASE版本health-check-url自动生成规则的源码解读

1.获取配置文件prefer-ip-address

  • org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration#eurekaInstanceConfigBean:131

    boolean preferIpAddress = Boolean.parseBoolean(eurekaPropertyResolver.getProperty("preferIpAddress"));
    

2.获取配置文件ip-address配置的地址IpAddressConfiged。

  • org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration#eurekaInstanceConfigBean:132

    String ipAddress = eurekaPropertyResolver.getProperty("ipAddress");
    

3.通过Socket相关接口获取本机(或容器)的IpAddressLocal和HostNameLocal。

  • org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration#eurekaInstanceConfigBean:140

    EurekaInstanceConfigBean instance = new EurekaInstanceConfigBean(inetUtils);
    
  • org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean#EurekaInstanceConfigBean(org.springframework.cloud.commons.util.InetUtils):286

    	public EurekaInstanceConfigBean(InetUtils inetUtils) {
    		this.inetUtils = inetUtils;
        //通过Socket相关变成,获取host信息
    		this.hostInfo = this.inetUtils.findFirstNonLoopbackHostInfo();
    		this.ipAddress = this.hostInfo.getIpAddress();
    		this.hostname = this.hostInfo.getHostname();
    	}
    
  • org.springframework.cloud.commons.util.InetUtils#findFirstNonLoopbackHostInfo:69

    	public HostInfo findFirstNonLoopbackHostInfo() {
        //通过Socket相关变成,获取host信息
    		InetAddress address = findFirstNonLoopbackAddress();
    		if (address != null) {
    			return convertAddress(address);
    		}
    		HostInfo hostInfo = new HostInfo();
        //将获取的hostName和ipAddress进行返还
    		hostInfo.setHostname(this.properties.getDefaultHostname());
    		hostInfo.setIpAddress(this.properties.getDefaultIpAddress());
    		return hostInfo;
    	}
    
  • org.springframework.cloud.commons.util.InetUtils#findFirstNonLoopbackAddress:79

    	/**
    	* 通过NetworkInterface获取InetAddress
    	**/
    	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;
    	}
    

4.将服务真正使用的IpAddressUesd设置为IpAddressLocal,服务使用的HostNameUsed设置为HostNameLocal。

  • org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean#EurekaInstanceConfigBean(org.springframework.cloud.commons.util.InetUtils):287

    		this.ipAddress = this.hostInfo.getIpAddress();
    
  • org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean#EurekaInstanceConfigBean(org.springframework.cloud.commons.util.InetUtils):288

    		this.hostname = this.hostInfo.getHostname();
    

5.如果IpAddressConfiged不为空,则覆盖IpAddressUesd为IpAddressConfiged。

  • org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration#eurekaInstanceConfigBean:146

    		if (StringUtils.hasText(ipAddress)) {
    			instance.setIpAddress(ipAddress);
    		}
    

6.在自动生成status-page-url和health-check-url,依据如下规则:

  • org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration#eurekaInstanceConfigBean:171

    instance.setStatusPageUrl(metadata.getStatusPageUrl());
    
  • org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration#eurekaInstanceConfigBean:167

    		ManagementMetadata metadata = managementMetadataProvider.get(instance, serverPort,
    				serverContextPath, managementContextPath, managementPort);
    
  • org.springframework.cloud.netflix.eureka.metadata.DefaultManagementMetadataProvider#get:26

    String healthCheckUrl = getHealthCheckUrl(instance, serverPort, serverContextPath,
                    managementContextPath, managementPort, false);
    
  • org.springframework.cloud.netflix.eureka.metadata.DefaultManagementMetadataProvider#getHealthCheckUrl:46

    String healthCheckUrl = getUrl(instance, serverPort, serverContextPath, managementContextPath,
                    managementPort, healthCheckUrlPath, isSecure);
    
  • org.springframework.cloud.netflix.eureka.metadata.DefaultManagementMetadataProvider#getUrl:69

    //关键在于参数 instance.getHostname() 
    return constructValidUrl(scheme, instance.getHostname(), managementPort, managementContextPath, urlPath);
    
  • org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean#getHostname:276

    	public String getHostname() {
    		return getHostName(false);
    	}
    

7. prefer-ip-address ? IpAddressUesd : HostNameUsed

  • org.springframework.cloud.netflix.eureka.EurekaInstanceConfigBean#getHostName

    	@Override
    	public String getHostName(boolean refresh) {
    		if (refresh && !this.hostInfo.override) {
    			this.ipAddress = this.hostInfo.getIpAddress();
    			this.hostname = this.hostInfo.getHostname();
    		}
        //prefer-ip-address ? IpAddressUesd : HostNameUsed
    		return this.preferIpAddress ? this.ipAddress : this.hostname;
    	}
    
  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值