K8S 服务发现与 Sping-cloud-kubernetes

1、项目介绍

本文章,旨在使用K8S中自身的服务发现功能,不使用其他的服务发现组件,通过 Spring 的 spring-cloud-kubernetes 来搭建SpringCloud项目。


2、k8s service概述

在介绍Svc之前,首先简单说明下Kubernetes中Pod概念。
Pod是Kubernetes非常重要的基本概念,代表着集群中运行的进程,Pod中封装着应用的容器(有情况下是多个容器)。

Kubernetes为每个Pod都分配了唯一的Ip地址,称为Pod Ip,一个Pod里的多个容器共享Pod Ip地址,这样客户端可以通过Pod Ip+ 容器端口来访问Pod(Pod Ip+ 容器端口,又被称为Endpoint,也是Kubernetes一种资源)。

我们知道Kubernetes在自动资源调度时,会频繁的销毁与创建,这样就会导致Pod Ip会频繁的变动,客户端几乎不可能以Pod Ip+端口直接访问Pod,这时候就需要Kubernetes的另一种资源来实现,这就是SVC。

SVC服务是Kubernetes里的核心资源对象之一,其实可以理解成我们微服务架构中的一个微服务。SVC一旦被创建,Kubernetes就会自动为它分配一个可用的Cluster IP,在svc的整个生命周期内,Cluster IP不会发生改变。


3、k8s 服务发现简介

任何分布式系统都会涉及“服务发现”这个基础问题,大部分分布式 系统都通过提供特定的API接口来实现服务发现功能,但这样做会导致 平台的侵入性比较强,也增加了开发、测试的难度。Kubernetes则采用 了直观朴素的思路去解决这个棘手的问题。

首先,每个Kubernetes中的Service都有唯一的Cluster IP及唯一的名 称,而名称是由开发者自己定义的,部署时也没必要改变,所以完全可 以被固定在配置中。接下来的问题就是如何通过Service的名称找到对应 的Cluster IP。
目前, Kubernetes上的大部分应用都已经采用了DNS这种新兴的服务发现机制。


4、项目搭建

下面我们就使用k8s中的原生服务发现功能,不使用其他的注册组件,来搭建在spring-cloud微服务架构,从而达到服务调用目的。

现在对spring-cloud-kubernetes基本原理做简单介绍。通过上述描述,我们知道在K8S其实已经维护了服务实例列表,每个服务对应的Endpoint也保存在K8S集群etc数据库中,所以spring-cloud-kubernetes所做的工作,就是在服务调用时,找到找到服务的Endpoint,从而完成服务调用。我们发现spring-cloud-kubernetes也是通过实现DiscoveryClient接口,实现类KubernetesDiscoveryClient,具体源码这里就不叙述了。

4.1项目目录:
  • abc-gateway 网关服务者
  • abc-service-provider 服务提供者
  • abc-service-consumer 服务消费者
    这个三个项目都是非常简单的SpringBoot工程,但是都需要引用以下关键jar包,具体的版本跟随SpringCloud版本即可。



    org.springframework.cloud
    spring-cloud-starter-netflix-ribbon


    org.springframework.cloud
    spring-cloud-starter-openfeign
    

    org.springframework.cloud
    spring-cloud-starter-kubernetes-all

4.2项目调用流程:
flow

4.3关键代码

1、abc-gateway 就是一个简单的springboot工程,pom文件添加相关jar包,然后添加以下配置

# 配置文件配置
server:
  port: 30000
spring:
  application:
    name: abc-gateway
  cloud:
    gateway:
      discovery:
        locator:
          enabled: true
          lower-case-service-id: true
    kubernetes:
      reload:
        enabled: true
        mode: polling
        period: 5000

logging:
  level:
    org.springframework.cloud.gateway: debug
    org.springframework.cloud.loadbalancer: debug

2、abc-service-provider与abc-service-consumer 也是一个简单的SpringBoot工程,与通常的工程没有区别,唯一需要修改的就是在POM文件中,添加上上述jar包。

/**
 * [abc-service-consumer] 启动类
 * 与通常的SpringBoot工程没有区别
 */
@Slf4j
@RestController
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class AbcServiceConsumerApplication {
    
    @Autowired
    private DiscoveryClient discoveryClient;

    @Autowired
    private FeignDemo demo;

    /**
     * 通过Feign调用服务提供者[abc-service-provider]的接口
     */
    @GetMapping("/get")
    public String feignDemo(){
        return demo.getMessage();
    }

    /**
     * KubernetesDiscoveryClient核心类实现了DiscoveryClient
     * 通过getServices()方法可以获取k8s中的服务实例
     */
    @GetMapping("/abc-service-consumer/index")
    public String indexService() {
        log.info("消费服务:abc-service-consumer");
        List services = discoveryClient.getServices();
        services.forEach(System.out::println);
        return "消费服务:abc-service-consumer";
    }

    public static void main(String[] args) {
        SpringApplication.run(AbcServiceConsumerApplication.class, args);
    }
}

------------

/**
 *[abc-service-consumer]服务Feign调用类,通过Feign调用服务提供者abc-service-provider
 */
@FeignClient(value = "abc-service-provider")
public interface FeignDemo {

    @GetMapping("/get")
    String getMessage();
}

/**
 * [abc-service-provider]服务提供者启动类
 */
@SpringBootApplication
@RestController
@EnableDiscoveryClient
public class AbcServiceProviderApplication {

    @GetMapping("/get")
    public String get() {
        return "服务提供者";
    }

    public static void main(String[] args) {
        SpringApplication.run(AbcServiceProviderApplication.class, args);
    }
}

3、K8S 中资源创建模板:


# 注意: 由于我是本地已经搭建好K8S环境与gitlab的CICD,所以该文件中包含多个脚本变量。如果使用该文件,只需要替换成自己项目信息即可。
# PROJECT_NAME:SpringBoot工程的服务名称
# REPLACE_IMAGE: Docker镜像
# PROJECT_PORT: SpringBoot工程的服务端口

# 定义Deployment
apiVersion: apps/v1
kind: Deployment
metadata:
  name: PROJECT_NAME
  labels:
    app: PROJECT_NAME
spec:
  replicas: 1
  template:
    metadata:
      name: PROJECT_NAME
      labels:
        app: PROJECT_NAME
    spec:
      containers:
        - name: PROJECT_NAME
          image: REPLACE_IMAGE
          ports:
            - containerPort: PROJECT_PORT
          imagePullPolicy: IfNotPresent
#     我使用的是阿里云的公共镜像仓库。更简单的方式,是将对应的服务镜像COPY到K8S的节点中。
#     (COPY到从节点,K8S如果没有设置,是不会调度Pod到master节点上)
      imagePullSecrets:
        - name: regcred-aliyun
      restartPolicy: Always
  selector:
    matchLabels:
      app: PROJECT_NAME

---
# 定义SVC
apiVersion: v1
kind: Service
metadata:
  name: PROJECT_NAME
spec:
  selector:
    app: PROJECT_NAME
  ports:
    - port: PROJECT_PORT
      targetPort: PROJECT_PORT
      nodePort: PROJECT_PORT
  type: NodePort

5、项目部署流程

1、构建镜像。java代码准备好之后,使用DockerFile构建好3个服务镜像,然后Copy到k8s的node节点。使用命令docker images查看镜像

#如果想Pod能够调度到master节点,在master节点运行
kubectl taint node k8s-master node-role.kubernetes.io/master-

#如果要恢复Master Only状态,执行如下命令:
kubectl taint node k8s-master node-role.kubernetes.io/master=""

2、构建K8S服务。使用命令kubectl apply -f deployment文件名启动3个服务,使用命令kubectl get svc查看。我们看到3个服务已经成功启动。

3、访问服务。因为K8S中的服务是nodeport类型,可以通过nodeIP来进行访问。注意,NodeIP与截图中的Cluster-IP这个两个IP有很大区别。朴素的数,NodeIP是真实存在的IP,是可以Ping通;而Cluster-IP是K8S独有的,是虚拟IP,是Ping不通的。具体的大家可查看相关书籍。通过kubectl get node -o wide 可以查看到具体的NodeIP,然后通过NodeIP来访问网关,从而验证结果。

4、验证结果。截图中已经打印出,服务提供者[abc-service-provider]中的信息。


6、项目总结

我们发现在使用spring-cloud-kubernetes组件后,不依赖于其他的服务注册组件,可以在K8S集群中正常运行。所以,对应那些在K8S集群中部署的SpringCloud服务,可以摆脱服务发现组件的限制。但是对于开发人员来说,在开发过程中本地的测试将是一个问题,因为我们发现,在项目启动过程中是要依赖K8S环境的,所以目前的spring-cloud-kubernetes对开发来说并不是太友好,希望后续版本能解决这个痛点。

我们知道K8S的服务有自身的负载均衡功能,在使用spring-cloud-kubernetes后,服务间调用的实质是使用的Ribbon,Ribbon也是有负载均衡功能的,那么这两者有没有什么联系呢?待我们下次讨论。

你可能感兴趣的:(K8S 服务发现与 Sping-cloud-kubernetes)