实战 | 如何在 Kubernetes 上部署 Spring Cloud
前言
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 其他全家桶
所以结合两者使用才是微服务的正确打开姿势。