Spring Cloud kubernetes之服务发现

988 阅读4分钟

微服务架构服务发现模式介绍

微服务架构中,服务发现有两种模式:客户端服务发现和服务端服务发现。

本文源码:github.com/tzjavadmg/s…

客户端服务发现

image.png

服务端服务发现

image.png

Spring Cloud Kubernetes服务发现

Spring Cloud Kubernetes同时支持客户端服务发现和服务端服务发现两种模式。

通过spring.cloud.kubernetes.loadbalancer.mode属性来配置,可选项为:SERVICE 和 POD

SERVICE模式(服务端服务发现)

客户端调用API Server通过服务名查找dns记录,然后客户端用dns记录请求服务,dns服务器通过dns记录找到clusterIP,再转发到具体的POD实例。

image.png

POD模式(客户端服务发现)

无需clusterIP.客户端调用API Server通过服务名获取所有POD实例的地址信息,然后由客户端负载均衡组件,选择一个实例进行通信。

image.png

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表格如下:

DependencyResources
spring-cloud-starter-kubernetes-fabric8pods, services, endpoints
spring-cloud-starter-kubernetes-fabric8-configconfigmaps, secrets
spring-cloud-starter-kubernetes-clientpods, services, endpoints
spring-cloud-starter-kubernetes-client-configconfigmaps, 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模式: 1667963856107.png 获取的是pod实例的ip地址

SERVICE模式:

1667964004339.jpg 获取的是service的dns地址。

SERVICE模式下,测试时发现短时间连续每次请求总是落在同一个pod上,并没有进行负载均衡。这是因为SERVICE模式下通过dns地址访问,http默认长连接模式(keep-alive),会保持长连接60秒,这期间发现的请求始终通过同一个连接落到同一个pod上。如果想要看到每次请求负载均衡的效果,可以关闭keep-alive.设置head,Connection: close