微服务架构服务发现模式介绍
微服务架构中,服务发现有两种模式:客户端服务发现和服务端服务发现。
客户端服务发现
服务端服务发现
Spring Cloud Kubernetes服务发现
Spring Cloud Kubernetes同时支持客户端服务发现和服务端服务发现两种模式。
通过spring.cloud.kubernetes.loadbalancer.mode属性来配置,可选项为:SERVICE 和 POD
SERVICE模式(服务端服务发现)
客户端调用API Server通过服务名查找dns记录,然后客户端用dns记录请求服务,dns服务器通过dns记录找到clusterIP,再转发到具体的POD实例。
POD模式(客户端服务发现)
无需clusterIP.客户端调用API Server通过服务名获取所有POD实例的地址信息,然后由客户端负载均衡组件,选择一个实例进行通信。
Spring Cloud Kubernetes示例代码
两个服务:
- example-service 服务提供方
- service-discovery 服务消费方
服务提供方代码
example-service工程ExampleServiceController主要代码如下:
@RestController
public class ExampleServiceController {
private final String hostName = System.getenv("HOSTNAME");
@RequestMapping("/login")
public String login(String username) {
return username + " login success! message from " + hostName;
}
}
提供一个login方法,返回用户名和消息来自哪个pod实例。
服务消费方代码
service-discovery的核代码如下:
ServiceDiscoveryApplication
@SpringBootApplication
@EnableDiscoveryClient
public class ServiceDiscoveryApplication {
public static void main(String[] args) {
SpringApplication.run(ServiceDiscoveryApplication.class);
}
@Bean
@LoadBalanced
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
ExampleService
@Service
public class ExampleService {
@Resource
private RestTemplate restTemplate;
public Object login(String username){
String url="http://example-service/login?username={username}";
Map<String, Object> uriVariables = new HashMap<>(1);
uriVariables.put("username", username);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Connection", "Close");
HttpEntity<String> httpEntity = new HttpEntity<>(null, httpHeaders);
return restTemplate.exchange(url, HttpMethod.GET,httpEntity, String.class,uriVariables);
}
}
ServiceDiscoveryController
@RestController
public class ServiceDiscoveryController {
@Resource
private DiscoveryClient discoveryClient;
@Resource
private ExampleService exampleService;
@RequestMapping("/services")
public List<String> services() {
return this.discoveryClient.getServices();
}
@RequestMapping("/instances")
public List<ServiceInstance> instances(String serviceName) {
return this.discoveryClient.getInstances(serviceName);
}
@RequestMapping("/invoke")
public Object invoke(String username) {
return exampleService.login(username);
}
}
将两服务部署到minikube k8s集群后,使用如下命令获取服务的访问地址
PS C:\Users\Lenovo> minikube service service-discovery --url
http://172.19.29.29:30759
然后就可以使用curl命令访问/services端点来查看集群的服务列表。
C:\Users\Lenovo>curl http://172.19.29.29:30759/services
["custom-resource","kubernetes","remote-debug","service-discovery"]
k8s Service Account 安全配置
Spring cloud kubernetes权限需求
参考:docs.spring.io/spring-clou…
要使用Spring cloud kubernetes组件,必须为你的Service Account对相应的resource授予list、get和wath权限。组件依赖的resouce表格如下:
| Dependency | Resources |
|---|---|
| spring-cloud-starter-kubernetes-fabric8 | pods, services, endpoints |
| spring-cloud-starter-kubernetes-fabric8-config | configmaps, secrets |
| spring-cloud-starter-kubernetes-client | pods, services, endpoints |
| spring-cloud-starter-kubernetes-client-config | configmaps, secrets |
如果没有正确授权,则会报出如下错误:
2022-11-05 12:52:43.295 WARN 1 --- [io-8080-exec-10] o.s.c.k.fabric8.Fabric8PodUtils : Failed to get pod with name:[service-discovery-client-side-7846664c5d-6kfgj]. You should look into this if things aren't working as you expect. Are you missing serviceaccount permissions?
io.fabric8.kubernetes.client.KubernetesClientException: Failure executing: GET at: https://10.96.0.1/api/v1/namespaces/default/pods/service-discovery-client-side-7846664c5d-6kfgj. Message: Forbidden!Configured service account doesn't have access. Service account may have been revoked. pods "service-discovery-client-side-7846664c5d-6kfgj" is forbidden: User "system:serviceaccount:default:codyzeng" cannot get resource "pods" in API group "" in the namespace "default".
role和roleBinding配置
授权配置文件如下:
sa.yml
metadata:
namespace: k8s-examples
name: codyzeng
deplyment.yml
metadata:
namespace: k8s-examples
spec:
template:
spec:
serviceAccount: codyzeng
role.yml
metadata:
namespace: k8s-examples
name: k8s-examples-reader
rules:
- apiGroups: [""]
resources: ["configmaps", "pods", "services", "endpoints", "secrets"]
verbs: ["get", "list", "watch"]
roleBinding.yml
metadata:
name: k8s-examples-reader-binding
namespace: k8s-examples
subjects:
- kind: ServiceAccount
name: codyzeng
namespace: k8s-examples
roleRef:
kind: Role
name: k8s-examples-reader
集群clusterRole和clusterRoleBinding配置
配置完成后,执行服务调用,仍然报错,提示需要cluster权限。(Spring cloud kubernetes官方文档,是针对default帐号的说明,default帐号默认具有cluster admin权限,所以不用配置)
Forbidden!Configured service account doesn't have access. Service account may have been revoked. services is forbidden: User "system:serviceaccount:default:default" cannot list resource "services" in API group "" at the cluster scope.
clusterRole.yml
metadata:
namespace: k8s-examples
name: k8s-examples-cluster-reader
rules:
- apiGroups: [""]
resources: ["configmaps", "pods", "services", "endpoints", "secrets"]
verbs: ["get", "list", "watch"]
clusterRoleBinding.yml
metadata:
name: k8s-examples-cluster-reader-binding
namespace: k8s-examples
subjects:
- kind: ServiceAccount
name: codyzeng
namespace: k8s-examples
roleRef:
kind: ClusterRole
name: k8s-examples-cluster-reader
Spring cloud kubernetes 负载均衡
使用如下命令访问service-discovery接口,调用example-service服务。
curl "http://172.19.29.29:30759/invoke?username=x"
RestTemplate使用Spring Cloud LoadBalancer组件实例负载均衡,获取目标实例的代码在BlockingLoadBalancerClient 类中。
在BlockingLoadBalancerClient类中设置断点,分别查看两种模式下loadbalance组件最终如何请求服务:
POD模式:
获取的是pod实例的ip地址
SERVICE模式:
SERVICE模式下,测试时发现短时间连续每次请求总是落在同一个pod上,并没有进行负载均衡。这是因为SERVICE模式下通过dns地址访问,http默认长连接模式(keep-alive),会保持长连接60秒,这期间发现的请求始终通过同一个连接落到同一个pod上。如果想要看到每次请求负载均衡的效果,可以关闭keep-alive.设置head,Connection: close。