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项目调用流程:
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<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也是有负载均衡功能的,那么这两者有没有什么联系呢?待我们下次讨论。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,684评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,143评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,214评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,788评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,796评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,665评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,027评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,679评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,346评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,664评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,766评论 1 331
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,412评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,015评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,974评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,073评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,501评论 2 343