前言
上一章描述了如何通过docker搭建一个简单的kubernetes集群,有了k8s,就可以用它来玩点其他东西;本文通过搭建简单的springboot项目,演示如何通过kubernetes进行服务注册,旨在使用K8S中自身的服务发现功能,不使用其他的服务发现组件,通过 Spring 的 spring-cloud-kubernetes 来搭建SpringCloud项目。
1、kubernetes Service 概述
每个 Pod 都有自己的 IP 地址,但是在 Deployment 中,在同一时刻运行的 Pod 集合可能与稍后运行该应用程序的 Pod 集合不同。
- 这导致了一个问题: 如果一组 Pod(称为“后端”)为集群内的其他 Pod(称为“前端”)提供功能, 那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用提供工作负载的后端部分?其实这个问题放在微服务下来讲的话,就是
如何处理服务发现?
- 为了解决这个问题,就要使用到kubernetes的另外一种资源:Service(SVC)。
- SVC服务是Kubernetes里的核心资源对象之一,其实可以理解成我们微服务架构中的一个微服务。SVC一旦被创建,Kubernetes就会自动为它分配一个可用的Cluster IP,在svc的整个生命周期内,Cluster IP不会发生改变。
- Kubernetes使用DNS提供服务注册功能。 Kubernetes 为 Pods 提供自己的 IP 地址,
并为一组 Pod 提供相同的 DNS 名
, 并且可以在它们之间进行负载均衡。 - 定义SVC:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
上述配置创建一个名称为 my-service
的 Service 对象,它会将请求代理到使用 TCP 端口 9376,并且具有标签 app=MyApp
的 Pod 上。
2、项目搭建
接下来就使用kubernetes的服务发现功能,不另外使用其他服务注册组件,搭建Spring Cloud 微服务架构。
- 项目结构
- service-consumer
-
service-provider
2.1 均采用 gradle 构建springboot项目
- build.gradle
implementation 'org.springframework.boot:spring-boot-starter-web' implementation "org.springframework.cloud:spring-cloud-starter-openfeign:2.2.2.RELEASE" implementation 'org.springframework.cloud:spring-cloud-starter-kubernetes-all:1.1.2.RELEASE' implementation 'org.springframework.cloud:spring-cloud-starter-bootstrap:3.0.3' implementation 'org.springframework.cloud:spring-cloud-starter-netflix-ribbon:2.2.6.RELEASE'
2.2 配置文件
- service-consumer -application.yml ``` server: port: 30001 spring: application: name: service-consumer 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
- service-provider - application.yml
server: port: 30000 spring: application: name: service-provider 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.3 DockerFile
- service-provider
FROM openjdk:8-jdk-alpine ENV jarName=”service-provider.jar” COPY build/libs/$jarName $jarName ENTRYPOINT java -Duser.timezone=GMT+8 -jar $jarName
- service-consumer
FROM openjdk:8-jdk-alpine ENV jarName=”service-consumer.jar” COPY build/libs/$jarName $jarName ENTRYPOINT java -Duser.timezone=GMT+8 -jar $jarName
#### 2.4 简单的controller
- service-consumer
@Slf4j @RestController @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients @RequiredArgsConstructor public class BootstrapApplication {
private final DiscoveryClient discoveryClient;
private final ProviderServiceClient providerServiceClient;
private final RestTemplate restTemplate = new RestTemplate();
public static void main(String[] args) {
SpringApplication.run(BootstrapApplication.class, args);
}
@GetMapping("/feign/hello")
public String consumerHello(){
log.info("消费服务:service-consumer");
return providerServiceClient.sayHello();
}
@GetMapping("/rest/hello")
public String restHello(){
return restTemplate.getForObject("http://service-provider/provider-hello", String.class);
}
@GetMapping("/rest-port/hello")
public String restHelloWithPort(){
return restTemplate.getForObject("http://service-provider:30000/provider-hello", String.class);
}
@GetMapping("/rest-ip/hello")
public String restHelloWithIP(){
return restTemplate.getForObject("http://10.100.235.65:30000/provider-hello", String.class);
}
@GetMapping("/consumers/services")
public List<String> findServices(){
log.info("当前注册中心下所有服务");
List<String> services = discoveryClient.getServices();
services.stream().map(discoveryClient::getInstances).forEach(v ->
v.forEach(s -> System.out.printf("%s:%s uri:%s%n", s.getHost(), s.getPort(), s.getUri())));
return services;
}
}
@FeignClient(value = “service-provider”) public interface ProviderServiceClient { @GetMapping(“/provider-hello”) String sayHello(); }
- service-provider
@RestController @SpringBootApplication @EnableDiscoveryClient public class BootstrapApplication {
public static void main(String[] args) {
SpringApplication.run(BootstrapApplication.class, args);
}
@GetMapping("/provider-hello")
public String sayHello(){
return "hello world";
}
}
#### 2.5编译打包
./gradlew build
docker build -t $APPLICATION_NAME:$VERSION .
#### 2.6 构建一个k8s template
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
镜像仓库,(不指定则使用本地仓库)
#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 # node访问端口 type: NodePort
> 两个项目分别根据模板中的注释描述编写yaml文件,其中,ports内配置的nodePort的类型,为了方便直接通过本机访问, 例如:
> >`service-consumer-deploy.yaml`
>
> > `service-provider-deploy.yaml`
#### 2.7 构建k8s服务
> 使用 `kubectl apply -f 文件名` 构建服务,例如:
> ```
> kubectl apply -f service-consumer-deploy.yaml
> kubectl apply -f service-provider-deploy.yaml
> ```
#### 2.8 查看构建进度,构建完成后,找到nodeIP访问
kubectl get po kubectl get svc kubectl get node -o wide
![在这里插入图片描述](https://img-blog.csdnimg.cn/37cd5c78e017478cbf02ab158be42ed7.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5biF54K45LqG55qEQ2hlc3Rlcg==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/fde4c640c87e465f94cd1fbcb4293c0b.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5biF54K45LqG55qEQ2hlc3Rlcg==,size_20,color_FFFFFF,t_70,g_se,x_16)
![在这里插入图片描述](https://img-blog.csdnimg.cn/9683561a7531425a8a9a9239d2d219f8.png)
# 3、测试
- 获取当前注册中心有哪些服务
![在这里插入图片描述](https://img-blog.csdnimg.cn/f51535c9e02a4b1d8704b25968b4eda7.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5biF54K45LqG55qEQ2hlc3Rlcg==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 尝试rest template调用 provider服务
![在这里插入图片描述](https://img-blog.csdnimg.cn/36a5dbb2d8be4050a8388a8be25a81ad.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5biF54K45LqG55qEQ2hlc3Rlcg==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 尝试feign 调用provider服务
![在这里插入图片描述](https://img-blog.csdnimg.cn/49022e480a4747f0901a28aa36c925da.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5biF54K45LqG55qEQ2hlc3Rlcg==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 查看日志
![在这里插入图片描述](https://img-blog.csdnimg.cn/6813a502141a4948a2e8350903bfbbe4.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5biF54K45LqG55qEQ2hlc3Rlcg==,size_20,color_FFFFFF,t_70,g_se,x_16)
## x、歇一下
- 聊一聊原理
> K8S其实已经维护了服务实例列表,每个服务对应的Endpoint也保存在K8S集群etcd数据库中,所以spring-cloud-kubernetes所做的工作,就是在服务调用时,找到找到服务的Endpoint,从而完成服务调用。我们发现spring-cloud-kubernetes也是通过实现DiscoveryClient接口,实现类KubernetesDiscoveryClient,具体源码这里就不叙述了。
- 配置pods端口转发
kubectl port-forward podsNAME 外部端口:内部服务端口
#例如 kubectl port-forward podsNAME 80:30001
#例如 kubectl port-forward podsNAME :30001 # 随机分配
- 至于为什么没有调通feign:
- 首先服务发现没有问题,通过`DiscoveryClient` 调用`getServices`是可以获取所有服务列表的
![在这里插入图片描述](https://img-blog.csdnimg.cn/621a331294344e85b7a0df142ae54814.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5biF54K45LqG55qEQ2hlc3Rlcg==,size_20,color_FFFFFF,t_70,g_se,x_16)
- 1、可能是因为内部转发端口应该配置成80,目前是30001,导致走dns之后,默认端口为80,访问失败。
- 2、 feign配置问题
- 3、 下篇解答
---
## End.解决feign调用失败:
本来想说的是下篇解答,但是想了一下也没几个字描述,昨晚失败的主要原因是:feign没有找到可用的服务。k8s下,不借用其他服务发现的组件,那么默认是从etcd下走的dns解析,所以解决这个问题就显而易见了。
- 1、在feignClient上加url
@FeignClient(name = “service-provider”, url = “http://service-provider”) public interface ProviderServiceClient { @GetMapping(“/provider-hello”) String sayHello(); }
- 2、为了不指定端口,修改映射端口为80,反正每个Cluster IP都是独立的,不会冲突
![在这里插入图片描述](https://img-blog.csdnimg.cn/95bc9d676d764cd0a0067ba393f2c939.png)
- 3、 测试:
![在这里插入图片描述](https://img-blog.csdnimg.cn/7ad79197534249e3a12d40ff9608d54c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5biF54K45LqG55qEQ2hlc3Rlcg==,size_17,color_FFFFFF,t_70,g_se,x_16)
# 4、kubernetes下的负载均衡:
- 修改provider的代码
@SneakyThrows @GetMapping(“/provider-hello”) public String sayHello(HttpServletRequest request){ Thread.sleep(2000); System.out.println(“======收到服务调用请求=====”); return “hello world”); }
- 增加service-provider 的pod数量:
![在这里插入图片描述](https://img-blog.csdnimg.cn/703b3a6104c74c2f9ff5a3c3c41ebf2c.png?x-oss-process=image/watermark,type_ZHJvaWRzYW5zZmFsbGJhY2s,shadow_50,text_Q1NETiBA5biF54K45LqG55qEQ2hlc3Rlcg==,size_15,color_FFFFFF,t_70,g_se,x_16)
kubectl apply -f service-provider-deploy.yaml ```
- 等待重启,然后查看pods状态和provider的数量
- 访问: servicer-consumer 访问provider
- 查看日志 pod1: pod2:
-
多发几次请求:
pod1: pod2: 完成。