Blog Logo
  • Home
  • SACC2013
  • Categories
  • Tags
  • About
  • Feed

基于Spring Cloud的分布式微服务化项目搭建

by Ruanjf — on spring docker 28 Feb 2018

本项目使用Spring Cloud的Camden.SR7版本。主要包含以下部分:

  • 服务发现微服务
  • 配置微服务
  • 客户端负载均衡
  • 熔断
  • 路由
  • 网关微服务
  • 多租户
  • 分布式追踪
  • Docker集成

前期准备

本项目使用到了Spring Cloud、Spring Boot、Maven、Git其中Maven由于工程管理,Git用于存储配置。

工程搭建

创建一个Maven POM文件添加Spring Boot和Spring Cloud依赖的公共POMdemo-parent用于复用配置,POM文件如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.4.5.RELEASE</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.runjf.springcloud</groupId>
    <artifactId>demo-parent</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>pom</packaging>

    <properties>
        <java.version>1.8</java.version><!-- 指定Java版本 -->
        <spring.boot.version>1.4.5.RELEASE</spring.boot.version>
        <spring.cloud.version>Camden.SR7</spring.cloud.version>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-dependencies</artifactId>
                <version>${spring.cloud.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency><!-- 统一配置 -->
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-config</artifactId>
        </dependency>
        <dependency><!-- 服务发现客户端 -->
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin><!-- spring boot 插件用于打包可执行jar -->
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

由于每个工程都会包含配置和服务发现因此在pom文件中添加了spring-cloud-starter-config和spring-cloud-starter-eureka依赖,接着创建Maven工程添加parent配置:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.runjf.springcloud</groupId>
        <artifactId>demo-parent</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.runjf.springcloud</groupId>
    <artifactId>demo-xxx</artifactId>

</project>

工程结构如下:

demo-xxx
├── pom.xml
├── src
│   ├── main
│   │   ├── docker
│   │   │   └── Dockerfile
│   │   ├── java
│   │   │   └── com
│   │   │       └── runjf
│   │   │           └── springcloud
│   │   │               └── xxx
│   │   │                   └── Application.java
│   │   └── resources
│   │       ├── application.yml
│   │       └── bootstrap.yml
│   └── test
└── target

配置文件

由于Spring Cloud基于Spring Boot搭建的因此这里先介绍一些使用到的功能:

  • 启动应用,其中SpringApplication.run用于启动应用@SpringBootApplication用于启用相关配置具体查看这里

      package com.runjf.springboot.demo;
            
      import org.springframework.boot.SpringApplication;
      import org.springframework.boot.autoconfigure.SpringBootApplication;
            
      @SpringBootApplication
      public class Application {
            
          public static void main(String[] args) {
              SpringApplication.run(Application.class, args);
          }
            
      }
    
  • 配置读取,Spring Boot应用默认会依次查找当前目录(Application.class所在目录)下/config子目录、当前目录、classpath下/config子目录和classpath根目录下的application.properties或者application.yml配置文件,具体代码在org.springframework.boot.context.config.ConfigFileApplicationListener中。

由于Spring Cloud通过org.springframework.cloud.bootstrap.BootstrapApplicationListener启动初始化上下文,并设置spring.config.name配置文件名称为bootstrap因此有了配置文件bootstrap.properties或者bootstrap.yml,默认bootstrap配置优先级较高无法被本地配置覆盖。

为了更好的区别服务发现中注册的每个微服务,我们通过spring.appliction.name=demo-xxx给每个微服务一个特定的名字

想要使用Spring Cloud首先要选定一种服务优先启动(服务发现优先、配置管理优先)方式,选择不同的方式将导致微服务的启动顺序有所不同相应的配置有所变化。当使用服务发现优先时可以把配置管理微服务注册到服务发现微服务这样其他的微服务只需服务发现微服务地址而不用关心配置管理微服务地址(可以通过服务发现获取)。当使用配置管理优先时需要在每个微服务中配置配置管理微服务地址,再在配置仓库中设置服务发现微服务的地址。Spring Cloud默认为配置管理优先,本架构使用服务发现优先因此需要在bootstrap.yml中配置spring.cloud.config.discovery.enabled=true

服务发现微服务

服务发现包含两个部分:

  • 服务端,用于提供提供服务发现和注册
  • 客户端,用于获取其他应用的注册信息和把本应用注册到服务端

服务端创建

创建Maven工程demo-discovery用于启动服务发现微服务,参考前面的工程搭建然后在pom文件中添加spring-cloud-starter-eureka-server依赖结果如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.runjf.springcloud</groupId>
        <artifactId>demo-parent</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.runjf.springcloud</groupId>
    <artifactId>demo-discovery</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-eureka-server</artifactId>
        </dependency>
    </dependencies>

</project>

首先添加配置信息,服务发现的启动方式有两种:独立方式、多实例互相注册方式,具体的配置信息可参考org.springframework.cloud.netflix.eureka.EurekaClientConfigBean。这里使用独立方式,配置信息application.yml如下:

server:
  port: 8761 # 指定微服务的端口

eureka:
  client:
    registerWithEureka: false # 不需要在服务发现中注册
    fetchRegistry: false # 不需要获取其他服务发现中的注册信息
    serviceUrl:
      defaultZone: ${SPRING_DISCOVERY_URI:http://localhost:8761/eureka/}
  instance:
    preferIpAddress: true # 使用ip代替hostname

其中添加SPRING_DISCOVERY_URI是为了启动时方便设置地址。

然后在Application.java中加入@EnableEurekaServer注解用于启动服务发现

@EnableEurekaServer // 启动服务发现服务
@SpringBootApplication
public class Application {
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
}

启动好服务后就可以通过http://localhost:8761/查看信息了

客户端配置

其他工程需要通过@EnableDiscoveryClient结合@SpringBootApplication或者直接使用@SpringCloudApplication启用服务发现并在bootstrap.yml中配置服务发现服务端的地址。入口代码如下:

@EnableDiscoveryClient
@SpringBootApplication
public class Application {

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

}

修改配置文件bootstrap.yml中添加服务发现服务端的地址

eureka:
  client:
    serviceUrl:
      defaultZone: ${SPRING_DISCOVERY_URI:http://localhost:8761/eureka/}

由于已在demo-parent中添加了spring-cloud-starter-eureka依赖所以pom无需做修改。

配置微服务

配置管理也包含两个部分:

  • 服务端,用于提供配置一般使用Git作为配置的存储,不过也可以使用其他存储
  • 客户端,用于获取当前应用的配置信息

服务端创建

创建Maven工程demo-config用于启动配置微服务,参考前面的工程搭建然后在pom文件中添加spring-cloud-config-server依赖结果如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.runjf.springcloud</groupId>
        <artifactId>demo-parent</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.runjf.springcloud</groupId>
    <artifactId>demo-config</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-server</artifactId>
        </dependency>
        <dependency><!-- 接收配置文件变化 -->
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-config-monitor</artifactId>
        </dependency>
    </dependencies>

</project>

接着修改配置bootstrap.yml添加本微服务名称、服务发现地址和配置存储地址,这里使用Git作为后端存储由于Git默认没有提供图形界面不好管理所以使用Gitlab的Docker镜像搭建。配置如下:

spring:
  application:
    name: demo-config
  cloud:
    config:
      server:
        git:
#         本地文件配置方式(Linux)file://${user.home}/config-repo
#                     (Windows) file:///${user.home}/config-repo
          uri: ${SPRING_CONFIG_GIT_URI:http://gitlab.local/demo/config.git}

eureka:
  client:
    serviceUrl:
      defaultZone: ${SPRING_DISCOVERY_URI:http://localhost:8761/eureka/}

然后在application.yml中添加微服务端口server.port: 8888,再在Application.java中加入@EnableConfigServer注解用于启动服务发现

@EnableConfigServer // 启动配置服务
@SpringBootApplication
public class Application {
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
    
}

由于之前在pom中添加了spring-cloud-config-monitor依赖现在可以通过Gitlab的Webhooks触发配置修改的更新,这样就可以通过浏览器修改配置。如果配置更新后想通知其他应用可以添加spring-cloud-starter-bus-amqp依赖(这个利用的Spring Cloud Bus提供的支持)

客户端配置

修改配置文件bootstrap.yml中添加配置微服务配置,由于配置服务已注册到了服务发现所以只要在配置文件中配置配置服务的名称demo-config和设置服务发现优先

spring:
  cloud:
    config:
      discovery:
        enabled: true # discovery first
        serviceId: demo-config

由于已在demo-parent中添加了spring-cloud-starter-config依赖所以pom无需做修改。

客户端通过spring.application.name读取配置文件(例如:demo-xxx.yml),如果配置仓库中存在application.yml则会作为共享配置。启动应用后会看到如下日志信息:

b.c.PropertySourceBootstrapConfiguration : Located property source: CompositePropertySource [name='configService', propertySources=[MapPropertySource [name='configClient'], MapPropertySource [name='http://gitlab.local/demo/config.git/demo-xxx.yml'], MapPropertySource [name='http://gitlab.local/demo/config.git/application.yml']]]

客户端负载均衡

通过客户端负载均衡进行微服务间的通信由于我们的应用对外提供的都是RESTful风格的api所以可以直接使用RestTemplate结合@LoadBalanced实现客户端负载均衡。这个是利用了RestTemplate可以自动配置使用Ribbon,其中spring-cloud-starter-ribbon依赖已由spring-cloud-starter-eureka提供了就不用显示添加了。现在Spring默认已经不创建RestTemplateBean了因此需要在Application.java中显示添加配置Bean,代码如下:

@LoadBalanced
@Bean
public RestTemplate restTemplate() {
    return new RestTemplate();
}

如果想自定义RestTemplate的参数(如:访问超时、读取超时等)可以使用构造方法RestTemplate(ClientHttpRequestFactory requestFactory)例如:

PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(30, TimeUnit.SECONDS);
connectionManager.setDefaultMaxPerRoute(20);
connectionManager.setMaxTotal(100);
CloseableHttpClient httpClient = HttpClients.createMinimal(connectionManager);
HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(httpClient);
RestTemplate restTemplate = new RestTemplate(requestFactory);

具体的使用String str = restTemplate.getForObject("http://demo-xxx/data", String.class);,其中使用微服务名称(通过spring.application.name设置,本例子中的demo-xxx)代替具体的域名或者ip,负载均衡的RestTemplate支持配置失败重试可以通过spring.cloud.loadbalancer.retry.enabled=true启用该功能,具体的配置项可以参看org.springframework.cloud.config.client.RetryProperties。微服务名称到具体ip的切换是通过org.springframework.cloud.netflix.ribbon.RibbonLoadBalancerClient

熔断

熔断用于当请求链(一个请求会依次调用多个服务)中的某个微服务无响应或者超过指定的请求时间(特定策略)的微服务进行下线。添加仪表盘可以查看实时的请求情况(采样部分数据)

配置

首先需要在pom文件中添加spring-cloud-starter-hystrix依赖,然后在需要使用熔断的Bean的方法中添加@HystrixCommand注解用于启用熔断,如果需要整合多个微服务的熔断采样信息可以添加spring-cloud-netflix-hystrix-stream和spring-cloud-starter-stream-*依赖,具体配置如下:

<dependency><!-- 熔断器 -->
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-hystrix</artifactId>
</dependency>
<dependency><!-- 熔断器 输出统计数据 -->
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-netflix-hystrix-stream</artifactId>
</dependency>
<dependency><!-- 流相关 可用于汇总统计数据 -->
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-stream-rabbit</artifactId>
</dependency>

在Bean添加熔断注解:

@Component
public class StoreIntegration {

    @HystrixCommand(fallbackMethod = "defaultStores")
    public Object getStores(Map<String, Object> parameters) {
        //do stuff that might fail
    }

    public Object defaultStores(Map<String, Object> parameters) {
        return /* something useful */;
    }
}

如果想进行熔断的配置可以在application.yml文件中添加如下配置:

# 熔断开启线程超时(默认是启用的)
hystrix.command.default.execution.timeout.enabled: true
# 熔断处理请求的线程超时时间为10秒
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 10000
# getStores方法的特定配置,熔断处理请求的线程超时时间为30秒
hystrix.command.getStores.execution.isolation.thread.timeoutInMilliseconds: 30000

可以在getStores方法中使用RestTemplate这时需要注意熔断配置和HttpClient配置的结合(例如:超时时间等)

然后在Application.java文件中添加@EnableCircuitBreaker结合@SpringBootApplication或者直接使用@SpringCloudApplication开启熔断支持,启用后获取Hystrix数据的方式有两种:

  • 通过HystrixStreamEndpoint提供一个地址/hystrix.stream用来获取数据
  • 通过Spring Cloud Stream来传递数据

Hystrix仪表盘创建

Hystrix仪表盘用于可视化显示每个熔断器的运行情况。启动一个仪表盘需要在pom.xml中添加spring-cloud-starter-hystrix-dashboard依赖和在Application.java中添加注解@EnableHystrixDashboard。当想合并多个微服务中的Hystrix数据时可以在pom.xml中添加spring-cloud-starter-turbine-stream依赖、在Application.java中添加注解@EnableTurbineStream和在application.yml中配置服务信息。

启动Hystrix仪表盘后可以通过http://hostname:port/hystrix访问,然后在页面中填入已开启熔断支持的微服务的地址http://app_hostname:port/hystrix.stream即可看到Hystrix数据的图标了。

网关微服务

用来统一对外提供服务,集成了用户权限校验、路由(基于反向代理)等。

微服务创建

创建Maven工程demo-gateway用于启动配置微服务,参考前面的工程搭建然后在pom文件中添加spring-cloud-starter-zuul依赖结果如下:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>com.runjf.springcloud</groupId>
        <artifactId>demo-parent</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.runjf.springcloud</groupId>
    <artifactId>demo-gateway</artifactId>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zuul</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>commons-logging</groupId>
                    <artifactId>commons-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.netflix.netflix-commons</groupId>
            <artifactId>netflix-commons-util</artifactId>
        </dependency>
    </dependencies>

</project>

接着修改配置bootstrap.yml添加本微服务名称、服务发现地址。配置如下:

spring:
  application:
    name: demo-gateway
  cloud:
    config:
      discovery:
        enabled: true # discovery first
        serviceId: demo-config

eureka:
  client:
    serviceUrl:
      defaultZone: ${SPRING_DISCOVERY_URI:http://localhost:8761/eureka/}

添加启动入口Application.java

@EnableZuulProxy
@SpringCloudApplication
public class Application {

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

}

由于本项目是网页应用如果存在多个地址会导致js跨域问题(当然也可以通过配置允许跨域的Header)和需要传递用户标识,所以这里使用的是@EnableZuulProxy否则可以使用@EnableZuulServer。

路由

接下来可以进行后端应用的路由配置,设置指定前缀的路径将路由到指定的微服务上,在application.yml中配置如下:

zuul:
  routes:
    abc:
      path: /abc/** # 指定前缀的路径
      stripPrefix: true # 访问后端时是否要包含前缀/abc
      serviceId: demo-abc # 对应的后端微服务
  ignored-headers: userid,keyabc # 忽略前端传递的Header

其中stripPrefix的配置依赖于后端微服务是否包含ContextPath(默认为空),可以通过server.contextPath配置Spring Boot 2.0以后的配置为server.servlet.contextPath。serviceId则是通过spring.application.name进行配置。

如果向后端发起请求时有添加自定义的Header,为了安全考虑应该添加zuul.ignored-headers使前端传递的Header无效化。

由于Zuul包含了熔断和负载均衡所以可以针对性的进行配置。对于熔断配置有:

# 熔断是否开启全局线程超时
hystrix.command.default.execution.timeout.enabled: true
# 熔断处理请求的全局线程超时时间为1秒
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 1000

hystrix:
  command:
    demo-abc:
      # demo-abc微服务熔断处理请求的全局线程超时时间5秒
      execution.isolation.thread.timeoutInMilliseconds: 5000
      threadPoolKeyOverride: demo-abc
  threadpool:
    demo-abc: # demo-abc微服务特定的线程池配置
      coreSize: 11 # 30 rps * 0.2 seconds = 6 + 5 = 11

对于负载均衡配置有:

# 读取超时时间
ribbon.ReadTimeout: 6000 
# demo-abc微服务负载均衡特定配置
demo-abc:
  ribbon:
    MaxTotalConnections: 200 # 最大连接数
    MaxConnectionsPerHost: 50 # 每个host最大连接数
    ConnectTimeout: 2000 # 连接超时时间
    ReadTimeout: 5000 # 读取超时时间

如果还有前置的反向代理,则前置反向代理的读取超时时间应该要大于或者等于负载均衡中配置的最大读取超时时间,例如还有前置反向代理服务器Nginx则需要在nginx.conf中配置超时时间proxy_read_timeout 60s;

用户权限校验

通过集成Shiro进行权限校验,校验通过后会在请求Header中添加用户标识符方便后端微服务获取用户信息。

分布式追踪

使用Spring Cloud Sleuth进行请求链路的追踪,通过Zipkin查看采样数据。首先在微服务的pom.xml中添加spring-cloud-starter-sleuth依赖如果想通过Stream的方式传递数据可以再添加spring-cloud-sleuth-stream和spring-cloud-starter-stream-rabbit(如果之前的Hystrix已添加则可忽略)依赖。然后创建Zipkin服务端添加如下依赖:

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-sleuth</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-sleuth-zipkin-stream</artifactId>
</dependency>
<dependency>
    <groupId>io.zipkin.java</groupId>
    <artifactId>zipkin-autoconfigure-ui</artifactId>
    <version>1.31.3</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
</dependency>

其中zipkin-autoconfigure-ui提供了UI界面方便查看数据,spring-boot-starter-jdbc和mysql-connector-java用来存储追踪的采样数据ZipkinServerConfiguration默认的存储zipkin.storage.type是在内存中使用一段时间后会把内存占满因此建议改用外部存储。默认的采样百分比为10%在application.yml中可以配置spring.sleuth.sampler.percentage也可以自己创建Bean提供采样策略替换SleuthStreamAutoConfiguration中默认配置。

接下来在Application.java中添加注解@EnableZipkinStreamServer用于启动Zipkin服务,用浏览器打开服务的地址就可以看到界面了。

由于项目中使用了WebSocket和JMS这部分追踪数据不是必须的因此添加如下配置

spring:
  sleuth:
    integration: # 关闭 Trace Messaging指定MQ队列
      enabled: false
      patterns: demo-*,topic://demo- # 或者指定队列不采集
      websockets:
        enabled: false
    scheduled: # 关闭定时
      enabled: false

多租户

通过EclipseLink提供的多租户功能并扩展JpaTransactionManager和代理EntityManagerFactory从而提供了EclipseLink需要的租户标识eclipselink.tenant-id。

Docker集成

Spring Boot提供的打包插件spring-boot-maven-plugin支持将所有依赖和到一个jar中,这样很方便的启动一个微服务只需要在命令行下执行java -jar demo-xxx-1.0.jar即可。为了简化部署考虑通过将微服务打包为Docker镜像连java环境也可以一并包含了,只要通过简单的docker run rjf/demo-xxx。

为了达到这个目的首先需要在pom.xml添加Docker插件docker-maven-plugin并配置Dockerfile位置<dockerDirectory>、镜像名称<imageName>、打包参数<buildArgs>以及一些需要包含的资源<resources>。<buildArgs>用于将参数传递给Dockerfile比如jar包的名称以及版本号等

<properties>
    <docker.image.prefix>rjf</docker.image.prefix>
    <docker.app.pkg>${project.build.finalName}.jar</docker.app.pkg>
</properties>

<plugin>
    <groupId>com.spotify</groupId>
    <artifactId>docker-maven-plugin</artifactId>
    <version>0.4.13</version>
    <configuration>
        <buildArgs>
            <APP_NAME>${project.artifactId}</APP_NAME>
            <APP_VERSION>${project.version}</APP_VERSION>
            <APP_PKG>${docker.app.pkg}</APP_PKG>
        </buildArgs>
        <imageName>${docker.image.prefix}/${project.artifactId}</imageName>
        <dockerDirectory>src/main/docker</dockerDirectory>
        <resources>
            <resource>
                <targetPath>/</targetPath>
                <directory>${project.build.directory}</directory>
                <include>${docker.app.pkg}</include>
            </resource>
        </resources>
    </configuration>
</plugin>

然后在src/main/docker文件夹中新建文件Dockerfile,其中HEALTHCHECK用于检查微服务是否启动完成配合Docker Compose(需要在docker-compose.yml中指定version: '2.1'以上版本)的depends_on下的配置condition: service_healthy可以实现按照指定顺序启动微服务,内容如下:

FROM frolvlad/alpine-oraclejdk8:slim
VOLUME /tmp
ARG APP_PKG
ARG APP_NAME
ARG APP_VERSION
ENV APP_NAME ${APP_NAME}
ENV APP_VERSION ${APP_VERSION}
ENV JAVA_OPTS ""
ADD ${APP_PKG} app.jar
RUN sh -c 'touch /app.jar'
HEALTHCHECK --interval=1m --timeout=10s \
  CMD wget -qO- "http://localhost:8761/info" || exit 1
CMD exec java $JAVA_OPTS -Djava.security.egd=file:/dev/./urandom -jar /app.jar

其中最后的CMD命令未使用CMD ["param1","param2"]是因为两原因:

  • 避免启动后的PID1被sh占用,通过pstree -s <pid>查看可以看出───docker-containe───entrypoint.sh───java───137*[{java}]和───docker-containe───java───63*[{java}]的区别,其中多了entrypoint.sh这样会存在当传递信号时未被真正的java进程接收并处理的问题。
  • 当使用docker exec命令时正常可以执行,否则需要添加Shell脚本文件entrypoint.sh并在中其中处理参数exec "$@"。

接下来打开终端进入到工程目录执行mvn package生成jar文件,然后执行mvn docker:build构建Docker镜像。如果Docker服务在远程则可以通过export DOCKER_HOST=tcp://192.168.1.123:2375 && mvn docker:build进行远程构建。

构建完成后执行docker run --rm rjf/demo-xxx即可启动微服务,如果不想执行默认的java进程可以在后面添加要执行的命令即可,例如想执行ls命令可以用docker run --rm -it rjf/demo-xxx ls

参考

  • Spring Boot
  • Spring Cloud
  • Hystrix
  • Ribbon
  • ngx_http_proxy_module
  • Zipkin
  • docker-maven-plugin
  • Dockerfile
  • Docker Compose
Ruanjf Author

Ruanjf

ruanjiefeng@gmail.com

是我,这就是我

Comments

comments powered by Disqus
All content copyright Ruanjf © 2020 • All rights reserved.
Proudly published with Jekyll on Tuesday, 05 May 2020 at 05:34 PM UTC