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版本即可。
<!-- 核心jar包,具体版本可参考官方网站-->
<!--服务间调用都是采用feign方式,所以需要加上相关jar包-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-ribbon</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-kubernetes-all</artifactId>
</dependency>
4.2项目调用流程:
flow4.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<String> 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也是有负载均衡功能的,那么这两者有没有什么联系呢?待我们下次讨论。