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注册中心,...
一、问题简述
- 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类服务
- 在eureka注册中心,status-page-url异常,如:http://x00001.prod.shunyi.beijing:8888/info。
- 在Spring Boot Admin运行日志中,显示health-check-url异常,如:http://x00001.prod.shunyi.beijing:8888/health。
- 在Spring Boot Admin应用管理页面,服务整体状态总是显示"OFFLINE"。
- C类服务
- 在eureka注册中心,status-page-url异常,如:http://172.17.0.2:8888/info。
- 在Spring Boot Admin运行日志中,显示health-check-url异常,如:http://172.17.0.2:8888/health。
- 在Spring Boot Admin应用管理页面,服务整体状态总是显示"OFFLINE"。
二、相关知识
想要搞清楚为什么部分微服务的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-url
和eureka.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如下:
- status-page-url:https://{HostNameLocal}/info
- health-check-url:https://{HostNameLocal}/health
- 真实示例如开始提到的:http://x00001.prod.shunyi.beijing:8888/health
3.2.2.修改说明
- 修改application.properties,补全服务注册相关配置(建议参考章节四:推荐配置)。
# 补充以下配置
eureka.instance.ip-address=${pers.hanchao.ip}
eureka.instance.prefer-ip-address=true
management.security.enabled=false
- 修改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; }
更多推荐
所有评论(0)