如何将多个springboot应用部署在k8s并实现互相通信

近来因工作需要,springboot / spring cloud 构建的产品需要提供嵌入k8s的能力,所以手动部署了一下。
目标:部署服务A与B,请求到达服务A,服务A内部请求服务B,然后返回结果。

一、构建springboot应用

要实现在k8s上面通信,笔者使用了 spring-cloud-kubernetes 这个依赖包来完成,且使用的 maven 来构建 project,读者可以自行更换 gradle 等以及对应版本,个人只贴出pom依赖,且version并未贴出,请自行寻找与自己使用的springboot版本对应的veision:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-kubernetes</artifactId>
</dependency>

1.编写两个springboot应用,分别名字为 demo-request 以及 demo-data,实现以上功能,具体如何构建不再赘述,

demo-data服务部分核心服务代码如下(忽略了部分重要的导包):

1.1 定义一个controller,返回了当前请求的 ip 与 hostname

@RestController
public class DataController {
    @GetMapping("/data")
    public ResponseVO<HashMap> data(){
        HashMap<String, String> map = new HashMap<>();
        try {
            InetAddress address = InetAddress.getLocalHost();
            map.put("name",address.getHostName());
            map.put("address",address.getHostAddress());
            System.out.println("Host Name: " + address.getHostName());
            System.out.println("Host Address: " + address.getHostAddress());
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }
        return ResponseVOUtil.success(map);
    }
}

1.2 main class标记为一个web应用启动

这里有一个比较重要的点,是通过 @EnableDiscoveryClient 开启服务发现

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@EnableDiscoveryClient
@SpringBootApplication
public class DataApp {

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

}

1.3 appliaction.yml配置

其中定义了服务的端口、名称以及指定 namespace。

(笔者懒,直接使用之前测试es用的namespace,读者可以提前构建,命令如下:kubectl create ns ${your define namespace})

server:
  port: 12001
spring:
  application:
    name: demo-data
  cloud:
    kubernetes:
      discovery:
        namespaces:
          - myes

demo-request 服务部分核心服务代码如下(忽略了部分重要的导包),大体与 demo-data 类似:

1.3 定义一个请求其他服务的请求

笔者这里使用openFeign组件调用 demo-data 服务中的 /data 请求拿到结果,读者可以自行使用其他组件比如 RestTemplate、okhttp等等

@RestController
public class RequestController {
    @Resource
    private DataServer dataServer;

    @GetMapping("request")
    public ResponseVO data(){

        return dataServer.data();
    }
}

1.4 feign接口配置

这里的url很重要,因为未引入其他组件,此处的 url 采用了 k8s的 dns 寻址,拼接规则采用的 {service-name}.{namespace},读者也可使用{namespace}.{service-name}等等,还可以使用环境变量,可以自行查看官方文档描述。

但是在 spring-cloud-kubernetes 的官网中有这个描述:使用 spring-cloud-starter-kubernetes-discoveryclient 这个依赖包的全的命名规则为:{service-name}.{namespace}.svc.{cluster}.local:{service-port}。这里是官网描述,但是本人尝试过没有连通,如果有大佬能告知感激不尽!

@FeignClient(name = "demo-data", url = "http://demo-data.myes:12001")
public interface DataServer {
    @GetMapping("data")
    public ResponseVO<HashMap> data();
}

1.5 application.yml配置

server:
  port: 12002
spring:
  application:
    name: demo-request
  cloud:
    kubernetes:
      discovery:
        namespaces:
          - myes

1.6 构建镜像

自行测试以上服务能够正常请求即可,打包以及构建过程省略,请自行构建镜像

贴一个简单的构建demo-request服务的Dockerfile,笔者此处只写了最小功能,如有需要可以自行修改

FROM java:8
MAINTAINER jyu
ADD ./demo-request-1.0-SNAPSHOT.jar /app.jar
EXPOSE 12002
ENTRYPOINT ["java","-jar","-Xms512m","-Xmx512m","-Dfile.encoding=utf-8","/app.jar"]

二、编写k8s的yaml文件

笔者自行创建 namespace 以及修改对应的配置,其实这块没什么好写的,因为是为了实现本文的目标写最小资源清单,笔者可以自行根据需要新增配置。

2.1 配置demo-data的yaml文件

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-data
  namespace: myes
spec:
# 副本数增加为了测试service的负载均衡
  replicas: 2
  selector:
    matchLabels:
      run: demo-data
  template:
    metadata:
      labels:
        run: demo-data
    spec:
    # 此处笔者懒,指定了机器加载镜像,但是在实际开发过程中,pod的构建应该交给k8s本身,读者可以去掉
      nodeName: node4
      containers:
      # 此处自行传到自己的镜像仓库或者使用本地镜像
        - image: demo-data:1.0
          name: demo-data
          ports:
            - containerPort: 12001

---
kind: Service
apiVersion: v1
metadata:
  name: demo-data
  namespace: myes
  labels:
    run: demo-data
spec:
  type: NodePort
  selector:
    run: demo-data
  ports:
    - port: 12001
      protocol: TCP
      nodePort: 31991

2.2 配置demo-request的yaml文件

总体与demo-data类似

apiVersion: apps/v1
kind: Deployment
metadata:
  name: demo-request
  namespace: myes
spec:
  selector:
    matchLabels:
      run: demo-request
  template:
    metadata:
      labels:
        run: demo-request
    spec:
      nodeName: node4
      containers:
        - image: demo-request:1.0
          name: demo-request
          ports:
            - containerPort: 12002

---
kind: Service
apiVersion: v1
metadata:
  name: demo-request
  namespace: myes
  labels:
    run: demo-request
spec:
  type: NodePort
  selector:
    run: demo-request
  ports:
    - port: 12002
      protocol: TCP
      nodePort: 31992

2.3 结果测试

根据2.1以及2.2的资源清单创建以后,会有如下资源出现:

Spring Cloud k8s 部署架构 springcloud k8s怎么结合的_java

其中 pod/curl-test 为笔者创建的一个用于 curl 测试的临时镜像,命令如下,自行修改 namespace:

kubectl run -i --tty --image radial/busyboxplus:curl curl-test --restart=Never --rm -n myes

此时我们在 curl-test 这个 pod 中 ping 一下 service/demo-request 的预定义名称,看看 ip 是否对应的上:

ping demo-request.myes

结果如下,可以很清楚的看得到 demo-request.myes 这个域名能够对应 service/demo-request 的 cluster-ip:

Spring Cloud k8s 部署架构 springcloud k8s怎么结合的_jar_02

那么下一步请求一下 1.3 中预定义的请求:

curl demo-request.myes:12002/request

结果如下:

Spring Cloud k8s 部署架构 springcloud k8s怎么结合的_kubernetes_03

这里能够正常返回,注意此处的结果,实际上是 demo-data 服务返回的结果,注意看返回的 name 与 address 字段。

那么整个请求单向链路图为:

Spring Cloud k8s 部署架构 springcloud k8s怎么结合的_java_04

那么多来几次就能看到负载能力,可以很清楚的看到 ip 是变了的:

Spring Cloud k8s 部署架构 springcloud k8s怎么结合的_java_05

总结:本文属于入门级教程,只告诉了读者 springboot application 在 k8s 内部如何内部不通过其他组件实现通信,有一些更深入的玩法请自行了解官网。

比如 service 的负载均衡策略,配合 ingress 使用等等。