在前面文章 (spring-cloud-k8s 跨 NS 的坑 中,讲述了 spring-cloud-k8s
中,如何利用 k8s 基于 Ribbon 等负载均衡利器来实现 LB,但存在跨命名空间
的问题。
今天主要分享的是,基于 K8s 本身的 LB 利器,如何实现跨命名空间的应用服务互相访问,而且不是通过 K8s 原生的负载均衡 url 方式。还是基于 ServiceName。
直击源码
首先,我们新建一个服务提供者:diff-ns-service
,该服务提供了一个接口:
/**
* 返回远程调用的结果
* @return
*/
@RequestMapping("/getservicedetail")
public String getservicedetail(
@RequestParam(value = "servicename", defaultValue = "") String servicename) {
return JSON.toJSONString(discoveryClient.getInstances(servicename));
}
该接口的功能是返回指定 service 的相关信息。比如:这个 Service 对应的有几个 pod,每个 pod 的节点信息,host 等。
image.png
如果想结合 K8s 来实现这个服务的发现,可以基于这个配置:
代码语言:javascript复制management:
endpoint:
restart:
enabled: true
health:
enabled: true
info:
enabled: true
spring:
application:
name: diff-ns-service
cloud:
loadbalancer:
ribbon:
enabled: false
kubernetes:
ribbon:
mode: SERVICE
discovery:
all-namespaces: true
另外,如果想利用 k8s configMap 的配置来实现动态刷新应用服务的环境配置,可以这样配置:
代码语言:javascript复制spring:
application:
name: diff-ns-service
cloud:
kubernetes:
reload:
enabled: true
strategy: refresh
mode: event
config:
name: ${spring.application.name}
namespace: default
sources:
- name: ${spring.application.name}
namespace: ns-app
这里的动态刷新的模式有两个:[polling
、event
。一个是主动拉取,一个是当 configmap 发生改变时,这种事件会被监听到,会主动刷新。
另外,这个刷新的策略也有几种:
- refresh,直接刷新
- restart_context,整个 Spring Context 会优雅重启,里面的所有配置都会重新加载
- shutdown,重启容器
这样,我们再来配置一下 Service:
代码语言:javascript复制apiVersion: v1
kind: Service
metadata:
name: diff-ns-service-service
namespace: ns-app
spec:
type: NodePort
ports:
- name: diff-ns-svc
port: 2008
targetPort: 2001
selector:
app: diff-ns-service
这里我们设置了 Service 的 port,并且这个 Service 以 NodePort
类型创建。在(spring-cloud-k8s 跨 NS 的坑)一文中,我们使用的是默认的类型:ClusterIp。
这样,一个简单的服务提供者就创建成功了。接下来,我们看看服务消费者。
同样,我们先来创建一个服务 rest-service
,创建接口:
@GetMapping("/getClientRes")
public Response<Object> getClientRes() throws Exception {
HttpHeaders headers = new HttpHeaders();
MediaType type = MediaType.parseMediaType("application/json; charset=UTF-8");
headers.setContentType(type);
headers.add("Accept", MediaType.APPLICATION_JSON.toString());
HttpEntity<String> formEntity = new HttpEntity<String>(null, headers);
String body = "";
try {
ResponseEntity<String> responseEntity = restTemplate.exchange("http://diff-ns-service-service/getservicedetail?servicename=cas-server-service",
HttpMethod.GET, formEntity, String.class);
System.out.println(JSON.toJSONString(responseEntity));
if (responseEntity.getStatusCodeValue() == 200) {
return Response.ok(responseEntity.getBody());
}
} catch (Exception e) {
System.out.println(e.getMessage());
}
return Response.error("failed");
}
同理地,结合 K8s 来实现这个服务的发现,可以基于这个配置:
代码语言:javascript复制management:
endpoint:
restart:
enabled: true
health:
enabled: true
info:
enabled: true
spring:
application:
name: rest-service
cloud:
loadbalancer:
ribbon:
enabled: false
kubernetes:
ribbon:
mode: SERVICE
discovery:
all-namespaces: true
这里,我们不使用 RibbonLoadBalancerClient。
另外,如果想利用 k8s configMap 的配置来实现动态刷新应用服务的环境配置,可以这样配置:
代码语言:javascript复制spring:
cloud:
kubernetes:
reload:
enabled: true
strategy: refresh
mode: event
config:
name: ${spring.application.name}
namespace: default
sources:
- name: ${spring.application.name}
namespace: system-server
对于这些,在前面的文章说过,我们需要依赖配置:
代码语言:javascript复制<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-actuator-autoconfigure</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-kubernetes-discovery</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-loadbalancer</artifactId>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
</dependency>
这里没有用 Ribbon 的,直接使用 spring-cloud-starter-kubernetes-loadbalancer
,但我们还是利用 RestTemplate
:
@LoadBalanced
@Bean
public RestTemplate restTemplate() {
SimpleClientHttpRequestFactory requestFactory = new SimpleClientHttpRequestFactory();
requestFactory.setReadTimeout(env.getProperty("client.http.request.readTimeout", Integer.class, 15000));
requestFactory.setConnectTimeout(env.getProperty("client.http.request.connectTimeout", Integer.class, 3000));
RestTemplate rt = new RestTemplate(requestFactory);
return rt;
}
接下来,我们开始部署这两个应用服务了,同时,我们采用服务扩容方式实现多 pod:
代码语言:javascript复制kubectl scale --replicas=2 deployment diff-ns-service-deployment
我们苦役看看服务节点信息:
image.png
查看 Service 信息:
接下来,我们访问服务rest-service
:http://192.168.8.107:5556/rest-service/getClientRes, 这里我们可以看到日志:
image.png
同时,去哦们可以看到返回结果:
这里,我们请求的是获取cas-server
这个服务的 pod 的分布信息。
同样地,我们通过 Service 的 Ip 和端口也可以直接访问:http://192.168.8.107:30916/getservicedetail?servicename=cas-server-service
image.png
PS:如果需要实现负载均衡,还是需要注入:@LoadBalanced
,如果我们把这个注解去掉会发生什么呢?
1636538633(1).png
我们发现去掉后,竟然不能访问了。
我们再做一组测试,如果我们利用 spring.cloud.kubernetes.ribbon.mode=POD
,我们来看看会有啥结果不?修改配置后,重新编译、部署,我们继续请求 urlhttp://192.168.8.107:5556/rest-service/getClientRes
:
image.png
新发现
如果我们引入的是基于 Spring cloud 本身的spring-cloud-starter-kubernetes-loadbalancer
,同时,我们没有去掉基于 Ribbon 的 LB 的能力,如:spring.cloud.loadbalancer.ribbon.enabled=false
,是有可能会报错的:
1636601324.png
总结
- Spring cloud 本身:如果是基于 Spring cloud 本身的 LB,需要隐藏 Ribbon 的能力,同时基于
RestTemplate
需要注解@LoadBalanced
。 - k8s 本身:如果采用 Spring cloud 的负载,再结合 K8s,可以实现应用服务的 LB。如果设置
spring.cloud.kubernetes.ribbon.mode=POD
,其禁用了 Ribbon 的 LB 能力,此时不会生效,走的还是 Spring cloud LoadBalancer。另外对于 Service,这里都设置为 NodePort 类型,如果是默认类型是否可以实现 LB,需要待确认,因为目前来看,没有实现,可能是网络问题,并不是说默认类型的 Service 不可实现 LB。 - Ribbon,基于上面,下次可以尝试基于 NodePort 类型的 Service 来实现 Ribbon 的 LB,看是否是因为 Service 的网络导致的。
实践验证
在前面我们已经针对默认类型的Service进行Ribbon负载均衡测试过,发现无法对跨 NS 进行LB。接下来,我们测试下基于 NodePort 类型的Service,打开spring.cloud.loadbalancer.ribbon.enabled=true
,引入依赖:
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-ribbon</artifactId>
</dependency>
按照以上配置,我们部署服务diff-ns-service
,我们发现服务启动后日志:
1636603546(1).png
请求后返回日志:
结论
不管怎样,Ribbon 无法解决跨 NS 的应用服务之间的相互访问。但对于 Service 类型来说,可能是网络设置问题,跟其类型无关。
下面给大家介绍一本好书《深入了解分布式事务》,该书在当当网目前销售火热,有原理加实战,感兴趣可以点击下方链接购买。
开源项目
实践项目代码开源:https://gitee.com/damon_one/microservice-k8s