前言

Kubernetes 和 Spring Cloud 都是为了解决单体应用拆分成微服务后,如何对服务进行管理并提高SLA的服务框架。很多人对于两者之间的区别不是很清楚,甚至有人会问二选一应该选哪个。其实两者的定位是不冲突的。

Spring Cloud 是从开发者的角度构建的一套高效、分布式、容错的平台,使用 Spring Cloud 后可以大幅降低开发者构建分布式系统的技术架构难度。Kubernetes 则是从运维人员的角度构建自动部署、缩放和管理容器应用的平台。单一的平台不能解决从开发到运维的所有问题,二者结合才是微服务的正确姿势。

本文主要讲述如何在 Kubernetes 上部署 Spring Cloud 应用,以及云时代微服务的最佳商业化实践。阅读本文需要对 Spring Cloud 以及 Kubernetes 有一定的了解才能理解文中的部分概念。

Spring Cloud 服务治理框架

服务治理是微服务的核心内容。Spring Cloud 为服务治理做了一层抽象接口,并支持Netflix Eureka、Consul、Zookeeper 等作为服务治理的实现。其中最广泛为人关注的就是Netfix Eureka。下面是一个使用Eurake搭建的demo应用(点击下载源码),示意图如下:

tips: demo应用源码和下文中写到的镜像都可以直接获取。

构建 provider 应用提供 echo 服务

  • 创建一个eco服务

@RestController
public class EchoController {

    @RequestMapping("/echo")
    public String hello(@RequestParam String name) {
        return "echo " + name;
    }
}
  • 在启动类中声明服务发现

@SpringBootApplication
@EnableDiscoveryClient
public class ProviderApplication {

    public static void main(String[] args) {
        SpringApplication.run(ProviderApplication.class, args);
    }
}
  • 打包成可执行Jar

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>repackage</goal>
            </goals>
            <configuration>
                <classifier>spring-boot</classifier>
                <mainClass>com.alibaba.demo.ProviderApplication</mainClass>
            </configuration>
        </execution>
    </executions>
</plugin>

使用 spring-boot-maven-plugin 插件打包可执行jar。 执行打包命令mvn clean package,打包好的可执行jar为 target/provider-demo-0.0.1-SNAPSHOT-spring-boot.jar。

  • 使用 Dockerfile 制作镜像

FROM centos:7
RUN yum -y install wget unzip telnet
ENV JAVA_HOME /usr/java/latest
ENV PATH $PATH:$JAVA_HOME/bin
ENV ADMIN_HOME /home/admin
RUN wget http://edas-hz.oss-cn-hangzhou.aliyuncs.com/agent/prod/files/jdk-8u65-linux-x64.rpm -O /tmp/jdk-8u65-linux-x64.rpm && \
    yum -y install /tmp/jdk-8u65-linux-x64.rpm && \
    rm -rf /tmp/jdk-8u65-linux-x64.rpm
RUN mkdir -p /home/admin
ADD target/provider-demo-0.0.1-SNAPSHOT-spring-boot.jar /home/admin/ 
RUN echo '$JAVA_HOME/bin/java -jar /home/admin/provider-demo-0.0.1-SNAPSHOT-spring-boot.jar'> /home/admin/start.sh && chmod +x /home/admin/start.sh
WORKDIR $ADMIN_HOME
CMD ["/bin/bash", "/home/admin/start.sh"]

在 Dockerfile 目录下执行镜像构建命令docker build -t registry.cn-hangzhou.aliyuncs.com/spring-cloud-demo/spring-cloud-provider:2.0 -f Dockerfile .,最终制作好的镜像为:registry.cn-hangzhou.aliyuncs.com/spring-cloud-demo/spring-cloud-provider:2.0 。

构建consumer应用消费echo服务

  • 使用FeignClient注入echo服务代理类

“provider-demo” 是provider应用在注册中心的应用名。使用”@FeignClient”注解修饰Interface后,Spring 会自动注入一个代理类, consumer可以直接使用这个代理类调用provider的服务。

@FeignClient(name= "provider-demo")
public interface EchoService {

    @RequestMapping(value = "/echo")
    String echo(@RequestParam(value = "name") String name);
}
  • consumer调用provider的echo服务

@RestController
public class ConsumerController {

    @Autowired
    EchoService echoService;


    @RequestMapping("/echo/{name}")
    public String index(@PathVariable("name") String name) {
        return echoService.echo(name);
    }

}
  • 启动类中启动服务发现和FeignClient

@SpringBootApplication
@EnableFeignClients (basePackages = "com.alibaba.demo.controller")
@EnableDiscoveryClient
public class ConsumerApplication {
    public static void main(String[] args) {
        SpringApplication.run(ConsumerApplication.class, args);
    }
}
  • 打包成可执行Jar

<plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    <executions>
        <execution>
            <goals>
                <goal>repackage</goal>
            </goals>
            <configuration>
                <classifier>spring-boot</classifier>
                <mainClass>com.alibaba.demo.ConsumerApplication</mainClass>
            </configuration>
        </execution>
    </executions>
</plugin>

使用spring-boot-maven-plugin插件打包可执行Jar。执行打包命令mvn clean package,打包好的可执行jar为 target/consumer-demo-0.0.1-SNAPSHOT-spring-boot.jar。

  • 使用 Dockerfile 制作镜像

Dockerfile:

FROM centos:7
RUN yum -y install wget unzip telnet
ENV JAVA_HOME /usr/java/latest
ENV PATH $PATH:$JAVA_HOME/bin
ENV ADMIN_HOME /home/admin
RUN wget http://edas-hz.oss-cn-hangzhou.aliyuncs.com/agent/prod/files/jdk-8u65-linux-x64.rpm -O /tmp/jdk-8u65-linux-x64.rpm && \
    yum -y install /tmp/jdk-8u65-linux-x64.rpm && \
    rm -rf /tmp/jdk-8u65-linux-x64.rpm
RUN mkdir -p /home/admin
ADD target/consumer-demo-0.0.1-SNAPSHOT-spring-boot.jar /home/admin/ 
RUN echo '$JAVA_HOME/bin/java -jar /home/admin/consumer-demo-0.0.1-SNAPSHOT-spring-boot.jar'> /home/admin/start.sh && chmod +x /home/admin/start.sh
WORKDIR $ADMIN_HOME
CMD ["/bin/bash", "/home/admin/start.sh"]

在 Dockerfile 目录下执行镜像构建命令 docker build -t registry.cn-hangzhou.aliyuncs.com/spring-cloud-demo/spring-cloud-consumer:2.0 -f Dockerfile .

最终制作好的镜像为:registry.cn-hangzhou.aliyuncs.com/spring-cloud-demo/spring-cloud-consumer:2.0 。

引入 eurake 作为服务注册中心

  • 使用eureka作为注册中心

在 consumer 和 provider 的 pom 中增加 spring-cloud-starter-eureka 依赖。

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-eureka</artifactId>
    <version>1.4.4.RELEASE</version>
</dependency>

配置 Eureka Server 在 8000 端口打开服务。

spring.application.name=spring-cloud-eureka

server.port=8000
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false

eureka.client.serviceUrl.defaultZone=http://localhost:8000/eureka/
  • 启动eureka server

@SpringBootApplication
@EnableEurekaServer
public class SpringCloudEurekaApplication {

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

使用 Dockerfile 构建eureka server的镜像,Dockerfile 参考示例代码中的sc-demo/eureka-server/Dockerfile,构建好的镜像为registry.cn-hangzhou.aliyuncs.com/spring-cloud-demo/eureka-server:1.0 。

使用 Kubernetes 部署 Spring Cloud 应用

在上一章节中我们使用 Spring Cloud 开发了三个应用,并使用 Docker 把他们构建成了可以拿来即用的镜像。本节我们使用 Kubernetes 的能力将 Docker 构建好的镜像部署起来变成服务。

使用三个Deployment分别部署Eurake Server, consumer和provider。创建一个”ClusterIP”类型的Service为Eurake Server提供服务。示意图如下:

  • 创建 Eureka Server Deployment

yaml文件(sc-demo/conf/eureka.yaml)使用了上文构建的 registry.cn-hangzhou.aliyuncs.com/spring-cloud-demo/eureka-server:1.0 镜像。

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: eureka-server
  namespace: demo-namespace
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: eureka-server
    spec:
      containers:
      - name: eureka-server
        image: registry.cn-hangzhou.aliyuncs.com/spring-cloud-demo/eureka-server:1.0

使用命令kubectl apply -f eureka.yaml创建Eureka Server。

  • 创建Kubernetes service

apiVersion: v1
kind: Service
metadata:
  labels:
    app: eureka-server
  name: eureka-server
  namespace: demo-namespace
spec:
  ports:
  - name: register
    port: 8000
    protocol: TCP
    targetPort: 8000
  selector:
    app: eureka-server

使用命令kubectl apply -f eureka-service.yaml创建服务。

此处创建的服务名为eureka-server,可以通过访问http://eureka-server:8000/进行验证。

  • 指定 Eureka 服务地址

指定consumer和provider中服务发现的地址为上一步注册的服务地址 http://eureka-server:8000/eureka/。

eureka-server是通过kubernetes service暴露出来的集群内可见的服务域名。
consumer(sc-demo/consumer/src/main/resources/application.properties):

spring.application.name=consumer-demo
server.port=9001
eureka.client.serviceUrl.defaultZone=http://eureka-server:8000/eureka/
eureka.instance.preferIpAddress=true

provider(sc-demo/provider/src/main/resources/application.properties):

spring.application.name=provider-demo
server.port=9000
eureka.client.serviceUrl.defaultZone=http://eureka-server:8000/eureka/
eureka.instance.preferIpAddress=true
  • 启动 consumer 和 provider

sc-demo/conf/consumer.yaml

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: consumer
  namespace: demo-namespace
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: consumer
    spec:
      containers:
      - name: consumer
        image: registry.cn-hangzhou.aliyuncs.com/spring-cloud-demo/spring-cloud-consumer:2.0

sc-demo/conf/provider.yaml

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: provider
  namespace: demo-namespace
spec:
  replicas: 1
  template:
    metadata:
      labels:
        app: provider
    spec:
      containers:
      - name: provider
        image: registry.cn-hangzhou.aliyuncs.com/spring-cloud-demo/spring-cloud-provider:2.0

使用kubectl apply -f consumer.yaml ; kubect apply -f provider.yaml启动应用。

验证服务调用

到这里我们已经在 kubernetes 上完整的部署了一套分布式 restfull 调用框架的demo应用。如果服务注册发现正常,可以通过 consumer 应用自动发现部署在 provider 应用上的 echo 服务,并发生调用。

通过在 consumer 上执行curl -v http://localhost:9001/echo/hello, 验证服务调用正常。

小结

通过 Kubernetes 我们可以很方便的将 Spring Cloud 应用部署起来。通过使用 Kubernetes 我们至少可以获得如下能力是仅仅使用 Spring Cloud 时所不具备或者不完善的。

  • Kubernetes 使用了容器进行服务的部署,在保障隔离的情况下进一步提升了服务拉起的速度,达到秒级启动,对比虚拟机的分钟级启动时间大幅提高。

  • 通过helm等编排工具的应用,大幅提高了整个分布式应用系统部署的速度。

  • Kubernetes 提供了服务健康状态的维持,当服务器宕机等异常发生时可以自动迁移实例确保服务的可用性。

  • 通过使用Kubernetes autoscaler,可以做的服务的自动弹性伸缩,应对流量的突变。

  • Kubernetes 无语言限制,任何镜像都可以标准化的部署和运维。

通过 Spring Cloud 我们更可以获得下述能力也是 Kubernetes 所不具备或者不完善的。

  • Spring Cloud Ribbon 提供客户端负载均衡能力

  • Spring Cloud Hystrix 提供服务过载保护

  • Spring Cloud Config 提供实时配置推送

  • Spring Cloud 其他全家桶

所以结合两者使用才是微服务的正确打开姿势。