在阿里云容器服务上开发基于Docker的Spring Cloud微服务应用(三)
服务发现
本文为阿里云容器服务Spring Cloud应用开发系列文章的第三篇。
一、在阿里云容器服务上开发Spring Cloud微服务应用
三、服务发现(本文)
四、服务间通信与集成
五、服务智能路由
六、集中配置管理
七、高可用和容错
八、监控和日志
传统应用由于服务数目相对固定,服务所在的节点也相对固定,动态发现的需求不强烈。服务发现的通常做法是通过DNS域名解析,或IP地址找到相关服务。云环境中,服务的数目可能随时变化,服务所在的节点和侦听的端口也无法预先知道,所以在云应用的微服务架构中,服务发现是必须具备的一项能力。
通常我们可以采用zookeeper、etcd或consul等开源产品来实现服务的注册与发现。Spring Cloud中提供了两种方式:一是Consul,另外一个是Netflix的Eureka。本文讨论了如何采用Eureka进行服务发现,以及如何将Eureka及相关服务容器化。
Eureka服务发现和镜像构建
Eureka Server可以通过在程序中annotation声明的方式生成,详细步骤见下文。如果想直接用构建好的镜像,可以使用本文提供的示例:
docker pull registry.aliyuncs.com/jingshanlb/discovery-server
构建Eureka Server镜像
如果使用已有的镜像可以忽略本步。
引入对Eureka的依赖
首先,我们需要在在build.gradle中引入Java项目对Eureka的依赖。
...
dependencies {
compile('org.springframework.cloud:spring-cloud-starter-eureka-server')
...
}
...
通过注解方式声明Eureka Server
Eureka Server可以通过在程序中加入annotation完成,其中@SpringBootApplication
将本应用声明为springboot应用,@EnableEurekaServer
将Eureka Server的能力带入应用。@EnableDiscoveryClient
表明本应用同时也是DiscoveryClient,用于连接Eureka Server。
@SpringBootApplication
@EnableEurekaServer
@EnableDiscoveryClient
public class DiscoveryServerApplication {
public static void main(String[] args) {
SpringApplication.run(DiscoveryServerApplication.class, args);
}
}
配置Eureka Server
在application.yml配置Eureka的参数。内容如下:
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/
---
spring:
profiles: cloud
server:
port: 8761
eureka:
instance:
hostname: localhost
client:
registerWithEureka: false
fetchRegistry: false
serviceUrl:
defaultZone: http://${eureka.instance.hostname}:${server.port}/eureka/,${ADDITIONAL_EUREKA_SERVER_LIST}
在测试环境中(profile没有指定),Eureka侦听8761端口,serviceUrl.defaultZone
中只有一个Eureka服务实例。
在随后的cloud profile中,serviceUrl.defaultZone
除了有本机的Eureka Server,还通过ADDITIONAL_EUREKA_SERVER_LIST
环境变量指明其他Eureka Server节点信息。Eureka server之间的内容互相同步,Eureka client只需注册到其中任意一个节点即可。
启动Eureka Server
运行Eureka服务有两种方式,一种是通过Docker命令行执行,另外一种是在docker-compose文件中声明,并执行docker-compose启动。
命令行方式可以是从gradle中执行
gradle bootRun
通过Java命令启动单独一个Eureka Server,效果和上面的Gradle命令一样
java -jar build/libs/discovery-server-0.0.1-SNAPSHOT.jar
通过Java命令启动,profile为cloud,并通过环境变量指定了Eureka集群中其他的节点地址。
ADDITIONAL_EUREKA_SERVER_LIST=http://discovery2:8761/eureka/ SPRING_PROFILES_ACTIVE=cloud java -jar build/libs/discovery-server-0.0.1-SNAPSHOT.jar
由于在开发实际的应用过程中往往需要启动多个容器,所以通过脚本的方式启动服务非常不方便,所以建议通过在docker-compose文件中声明启动容器时的环境变量配置Eureka启动时的行为:
services:
discovery1:
container_name: discovery1
image: registry.aliyuncs.com/jingshanlb/discovery-server
... ...
environment:
- ADDITIONAL_EUREKA_SERVER_LIST=http://discovery2:8761/eureka/
- SPRING_PROFILES_ACTIVE=cloud
如果有多个Eureka Server需要在ADDITIONAL_EUREKA_SERVER_LIST
环境变量中指明,每个Eureka 的URL之间用逗号分隔即可。
构建Eureka Server镜像
Springboot应用编译生成的Jar包包含了所有依赖包以及app server,这种包也叫“超级JAR包“,英文 Uber Jar。编译好的Jar包在dockercompose文件中指明,即可由docker引擎生成容器镜像。
如何编译Uber Jar就不详细介绍了,我们只要记住执行如下命令即可:
gradle build
编译生成的Jar包在build/libs目录:
$ tree build/libs/
build/libs/
├── discovery-server-0.0.1-SNAPSHOT.jar
└── discovery-server-0.0.1-SNAPSHOT.jar.original
0 directories, 2 files
最后,写一个Dockerfile,定义如何生成一个Eureka Server镜像:
FROM java:8-jre-alpine
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.ustc.edu.cn/' /etc/apk/repositories
VOLUME /tmp
ADD build/libs/*.jar app.jar
RUN sh -c 'touch /app.jar'
RUN echo $(date) > /image_built_at
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar","--spring.profiles.active=${SPRING_PROFILES_ACTIVE}"]
其中ADD build/libs/*.jar app.jar
表示把构建好的Jar包复制到镜像中。由于build/libs
目录下只有一个jar包,所以这行命令实际上是将jar包复制到镜像的根目录下,并命名为app.jar
。这个Dockerfile可以用户所有springboot编译出来的应用。
RUN echo $(date) > /image_built_at
表示把构建镜像的日期打入镜像以利于未来的运维,读者可以根据自己的需求修改或删除这一行。
运行如下命令生成镜像,并上传到你自己的镜像仓库账号下:
#Java构建
gradle build
#创建镜像
export REGPREFIX=registry.aliyuncs.com/<username>/ docker tag $(docker build -t ${REGPREFIX}discovery-server -q .) ${REGPREFIX}discovery-server:$(date -ju "+%Y%m%d-%H%M%S")
运行上述命令生成的Eureka Server镜像有两个tag:latest
和创建时间,例如20160627-092112
。你可以根据自己的实际需求定义tag。
登录到镜像仓库并上传镜像的命令如下:
#登录到镜像仓库,*** 用户名, *** 密码
docker login -u *** -p *** registry.aliyuncs.com
#将创建好的镜像上传到镜像仓库,<username>替换为你的username
export REGPREFIX=registry.aliyuncs.com/<username>/ docker push {REGPREFIX}discovery-server
注意,如果你是第一次上传镜像,需要首先在阿里云开发者平台 创建一个镜像仓库,否则上传镜像会失败。
进入创建容器仓库界面,输入仓库名,并选择本地仓库
创建成功后就可以随时上传镜像了。
Eureka Client进行服务注册和发现
除Eureka Server之外所有需要注册到Eureka的应用都要通过Eureka Client注册到Eureka Server和获取其他应用的访问信息。
注解
引入Eureka Client也是通过annotation的机制完成。
@SpringBootApplication
@EnableDiscoveryClient
public class BarApplication {
public static void main(String[] args) {
SpringApplication.run(BarApplication.class, args);
}
}
其中@EnableDiscoveryClient
和前面Eureka Server的注解功能一样,注意,client不能声明EnableEurekaServer。
在bootstrap.yml中为应用命名,该名字用于向Eureka注册,所有用同样名字注册的应用都视为同一个应用(或服务)的不同实例,所以一定要注意应用名在一个Eureka集群的上下文中是唯一的。
spring:
application:
name: bar
Eureka Client所属应用也是通过build.gradle
文件引入Eureka依赖,application.yml
配置Eureka,程序的编译、运行和构建容器镜像和Eureka Server类似,这里就不再重复了。
服务发现和服务调用
通过在bootstrap.yml
中声明spring.application.name
,应用启动时会自动完成向Eureka Server的注册。那么,如何进行服务发现呢,我们在foobar
的代码中看一下。
...
@Autowired
@LoadBalanced
private RestTemplate restTemplate;
...
private BarMessage getMessageFromBarService(){
BarMessage bar = restTemplate.getForObject("http://bar/message", BarMessage.class);
log.debug("From bar service : {}.", bar);
return bar;
}
...
RestTemplate的声明需要加上@Autowired
和@LoadBalanced
两个注解。在随后对restTemplate.getForObject
的调用传入服务的URL。
http://bar/message
其中bar
服务名,/message
为希望调用的URL Path。 restTemplate.getForObject
从Eureka中获得bar
服务的所有实例,并根据内置的负载均衡算法选择一个实例调用并返回结果。
从这里可以看出,利用RestTemplate可以完成服务发现和服务调用。Spring Cloud还提供了其它的方法,比如Feign,这里就不再介绍了。
示例代码
本系列文章的示例可以从这里找到:https://code.aliyun.com/libin.libin/aliyuncs-springcloud-demo。
在aliyuncs-springcloud-demo
目录下可以看到如下内容:
aliyuncs-springcloud-demo$ tree -L 2
.
├── build-all.sh
├── clean-all-images.sh
├── common
│ ├── discovery-server
│ └── gateway
├── docker-compose.acs.yml
├── docker-compose.yml
├── push-all-images.sh
└── services
├── bar
├── foo
└── foobar
common
目录下是公用服务,包括服务发现的Discovery Server(Eureka),和智能路由Gateway(Zuul)。
services
目录下是所有应用的业务逻辑所在的服务,它们都注册到Discovery Server,服务的发现也通过Discovery Server。
注册和发现的机制在前面已经描述过了,所有应用(包括Discovery Server和Gatewa也)的Docker镜像构建都非常类似,在各自的目录下都可以找到相应的文件。
编译所有服务并上传镜像
手工进入每个目录并执行编译和上传的命令非常繁琐,也容易出错,所以在示例中提供了脚本来构建和上传所有服务的镜像。
编译、打包所有服务,并创建容器镜像(不上传镜像)的命令:
#运行命令前将jingshanlb替换为实际用户名
./build-all.sh
上传所有镜像,注意,如果还没创建镜像仓库,首先去创建,步骤见Eureka Server一节。
#运行命令前将jingshanlb替换为实际用户名
./push-all-images.sh
小节
本文介绍了如何通过Eureka进行服务注册和发现,如何镜像化等内容。最后简单介绍了如何构建示例代码中的所有服务镜像。
后续的文章我们会一起来看Spring Cloud和阿里云容器服务的其它特性。