spring-cloud-kubernetes 实践
阅读原文时间:2021年04月21日阅读:1

起因

公司这几天让调研这个开源项目,想了解下和我们自己的ci/cd有什么差异,从开始看官方文档到各种查资料,最后成功部署,踩了不少坑,由于网上对这个新项目的实操资料实在太少,我觉得还是有必要记录一下

关于spring-cloud-kubernetes

spring-cloud-kubernetes是springcloud官方推出的开源项目,用于将Spring Cloud和Spring Boot应用运行在kubernetes环境,并且提供了通用的接口来调用kubernetes服务,最终是借用了kubernetes自己的服务发现功能,没有用自家的eureka

环境信息

  • linux ubuntu 18.04
  • java jdk1.8
  • spring-cloud-kubernetes:1.0.1.RELEASE

添加依赖

首先他是一个正常的springcloud项目,除此之外,需要添加如下两个依赖,同时要去掉原有的服务发现依赖,否则启动会报错

<!--原有的eureka服务发现依赖-->
 <!--<dependency>-->
            <!--<groupId>org.springframework.cloud</groupId>-->
            <!--<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>-->
 <!--</dependency>-->
<dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-kubernetes-core</artifactId>
     <version>1.0.1.RELEASE</version>
 </dependency>
 <dependency>
     <groupId>org.springframework.cloud</groupId>
     <artifactId>spring-cloud-kubernetes-discovery</artifactId>
     <version>1.0.1.RELEASE</version>
 </dependency>

添加新的服务发现注解

@EnableDiscoveryClient
//@EnableEurekaClient 原有的服务发现注解
@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

创建controller,调用相关服务发现接口

@Slf4j
@RestController
public class DiscoveryController {

    @Autowired
    private DiscoveryClient discoveryClient;

    /**
     * 返回远程调用的结果
     * @return
     */
    @RequestMapping("/getservicedetail")
    public String getUri(
            @RequestParam(value = "servicename", defaultValue = "") String servicename) {
        return "Service [" + servicename + "]'s instance list : " + JSON.toJSONString(discoveryClient.getInstances(servicename));
    }

    /**
     * 返回发现的所有服务
     * @return
     */
    @RequestMapping("/services")
    public String services() {
        try{
            return this.discoveryClient.getServices().toString()
                    + ", "
                    + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date());
        }catch (KubernetesClientException e){
            log.error(e.getMessage(),e);
            return e.getMessage() + "====" + e.getCause();
        }catch (Exception e){
            log.error(e.getMessage(),e);
            return e.getMessage() + "====" + e.getCause();
        }
    }
}

简单的demo项目就算写好了,指定服务端口8764,接下来需要我们把项目打包,推送到docker仓库,这个过程自行查找,不再赘述,毕竟有的人喜欢手工操作,有的人用的自动化ci/cd

配置文件编写

这一块有必要单独拿出来说,是因为网上的资料都是针对非生产环境的,或者用的是默认default命名空间,不具备太多的真实环境操作性,放在生产环境有太多坑,所以这里尽量去模拟一个有namespace的环境来部署一个springboot项目

deployment 、service 配置文件

一个简单的无状态应用,编写这两个配置文件就够了
deployment 需要配置推送镜像的仓库地址和密钥,需要自行指定

apiVersion: apps/v1beta2
kind: Deployment
metadata:
  name: svcdemo
  namespace: mynamespace
spec:
  replicas: 1
  selector:
    matchLabels:
      run: svcdemo
  template:
    metadata:
      labels:
        run: svcdemo
    spec:
    ## 后面有对serviceAccountName进行讲解
      serviceAccountName: svcdemo
      containers:
        - name:svcdemo-containers
          image: [仓库地址]
          imagePullPolicy: Always
          volumeMounts:
            - name: host-time
              mountPath: /etc/localtime
          ports:
            - containerPort: 8764
      imagePullSecrets:
        - name: [仓库密钥]
      volumes:
        - name: host-time
          hostPath:
            path: /etc/localtime
---
apiVersion: v1
kind: Service
metadata:
  name: svcdemo
  namespace: mynamespace
  labels:
    run: svcdemo
spec:
  ports:
    - port: 8764
      targetPort: 8764
  selector:
    run: svcdemo

配置kubernetes中的访问权限

其实这个spring-cloud-kubernetes 项目最终能否部署成功,最大的坑就在这里,首先官方文档对这里的描述介绍简直是惜字如金


如果不仔细看,很容易误会这里对kubernetes api的调用是使用的默认的serviceaccount,其实是需要自定义 角色、用户、和角色绑定的.
另外网上的资料对权限这块都是基于非常简单的default命名空间,如果再换一个自定义的,就不起作用了,所以我在这一步,花费了不少时间,如果不是很了解kubernetes的权限控制,很容易停滞不前

spring-cloud-kubernetes项目无非就是帮我们封装了对kubernetes的api的访问,然而kubernetes是有自己的权限控制的,那么spring-cloud-kubernetes 是如何通过对应的权限校验成功调用对应的api的,kubernetes有自己的一套基于权限的角色控制,通过角色、用户、两者的绑定关系这三个api对象,来配置不同用户的的权限,而大部分时候我们在使用k8s,根本不关心这个用户是谁,是因为我们使用了它的内置用户ServiceAccount,所以在实际部署的时候针对不同的环境和业务需求可能需要配置我们自己的角色权限

role

首先要配置角色,它定义了一个权限集合,我们这里配置这个名叫svcdemo的角色,拥有对"pods",“services”,"endpoints"这三个api对象的所有权限

kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  namespace: mynamespace
  name: svcdemo
rules:
- apiGroups: [""]
  resources: ["pods","services","endpoints"]
  verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]

ServiceAccount

定义上文中用到的内置用户ServiceAccount,需要在deployment中明确指定

apiVersion: v1
kind: ServiceAccount
metadata:
  namespace: mynamespace
  name: svcdemo

角色绑定

已经定义好了角色和用户,最后需要角色绑定这个api对象,将这两者关联起来,这样我们才能在项目中成功的调用到k8s的api

kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: svcdemo
  namespace: mynamespace
subjects:
- kind: ServiceAccount
  name: svcdemo
  namespace: mynamespace
roleRef:
  kind: Role
  name: svcdemo
  apiGroup: rbac.authorization.k8s.io

最后将这些配置文件都部署完之后,项目成功启动,进入pod就可以验证一下之前写好的接口是什么样子了

接口调用

调用接口/services可以看到springcloud 封装的获取服务列表接口实际上是把k8s中service的列表反给了我们

对照一下命令行获取service 列表

参考:https://time.geekbang.org/column/article/42154
参考:https://mp.weixin.qq.com/s/ShNfJdGVoew_YFU4paIXJg