springboot集成docker

版本

  • spring boot: 1.5.10
  • docker engine: 18.09.4; API:1.39
  • maven: 3.3.9

为何要使用容器技术

52-17
Old Way 是在主机上,通过包管理器安装应用程序来部署。这样做的缺点是,必将涉及到系统的应用程序,配置,库,以及生命周期等问题。 为了构建一个能够更新和回滚的虚拟机镜像,虚拟机会变得非常笨重。

New Way 是基于操作系统级虚拟化,而不是硬件虚拟化的容器来部署。这些容器彼此隔离:它们有自己的文件系统,也不能访问对方的程序,他们的运算和资源都是隔离的。 它们比虚拟机更容易建立,并且因为它们与底层系统没有耦合关系,所以可以很方便的在所有云服务器上分发。

由于容器是小而快,每个应用程序可以装在一个容器镜像里。是1对1的应用程序到容器镜像的关系。基于容器技术,镜像可以在编译或者发行时创建,而不是部署的时候才去创建。 因为每个应用程序不依赖于其他的程序,及系统基础环境。 在构建/发行容器的时候就保证了,开发环境和生产环境的一致性。同样,容器是远远比虚拟机更加透明化,这更加方便监控和管理。 当容器里程序的生命周期是由底层系统管理的,而不是容器内部黑盒管理。最后,管理每个单个应用程序的容器,和管理应用程序是一样的。

实战场景

mac本机上intellij创建一个maven project: java_example,远程有一台linux(48.99.190.38)开发机。机器上会安装docker及其组件。项目代码完成后打包上传到linux机器的docker容器中运行。

linux安装及配置docker

安装

官网给出了很详细的安装过程,参照官网就好;https://docs.docker.com/install/linux/docker-ce/centos/

配置-开启 Docker api 远程访问

要想远程连接docker,开启 docker api 远程操作的功能,登录远程docker宿主机器(48.99.190.38),在 /usr/lib/systemd/system/docker.service,文件中,修改 ExecStart 的参数:

1
ExecStart=/usr/bin/dockerd  -H tcp://0.0.0.0:2375  -H unix:///var/run/docker.sock

端口自定义设置即可。

重载所有修改过的配置文件,并且重启 docker

1
2
systemctl daemon-reload    
systemctl restart docker.service

  • 需要注意的是,由于没有密码登陆任何权限验证,外网或者生产环境需要上证书使用

intellij创建project

通过Intellij创建maven project: java_example
pom.xml引入依赖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.10.RELEASE</version>
</parent>

<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

创建类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@SpringBootApplication
public class JavaExampleApp {
public static void main(String[] args) throws Exception {
System.out.println("app"+new Date());
SpringApplication.run(JavaExampleApp.class, args);
}
}

@RestController
@RequestMapping("/user")
public class UserController {

@RequestMapping("/test")
public String test() {
return "success";
}
}

创建application.yml文件

1
2
3
4
5
6
logging:
level:
root: info

server:
port: 20282

引入docker maven插件

在pom.xml中加入 docker-maven-plugin, 网上有许多maven docker插件,这里我们选择com.spotify

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<properties>
<docker.image.prefix>springboot_demo</docker.image.prefix>
</properties>

<build>
<plugins>
<!-- Docker maven plugin -->
<plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>1.0.0</version>
<configuration>
<!-- 打包的image名称 -->
<imageName>${docker.image.prefix}/${project.artifactId}</imageName>
<!-- Dockerfile文件的位置,以项目的 root 目录为根节点,建议到单独建立一个目录。位置非固定,随意指定,如:./src/docker/-->
<dockerDirectory>src/main/docker</dockerDirectory>
<!-- 远程docker的api地址,这里为我的linux开发机地址-->
<dockerHost>http://48.99.190.38:2375</dockerHost>
<!-- image tag,可以指定多个,不声明默认即latest-->
<imageTags>
<imageTag>latest</imageTag>
<imageTag>${project.version}}</imageTag>
</imageTags>
<!-- 执行构建docker镜像的时候需要哪些文件,即Dockerfile和项目jar和指定他们的位置-->
<resources>
<resource>
<!--这里指定的文件是target中的jar文件 -->
<!--
${project.build.directory} 表示项目项目构建输出目录,默认为 ${basedir}/target
${project.build.filename} 表示项目打包输出文件的名称,默认为 ${project.artifactId}-${project.version}
-->
<targetPath>/</targetPath>
<directory>${project.build.directory}</directory>
<include>${project.build.finalName}.jar</include>
</resource>
</resources>
</configuration>
</plugin>
</plugins>
</build>

创建Dockerfile

引入maven docker时,我们制定了Dockerfile的位置:src/main/docker,所以需要在src/main下创建文件夹docker,cd docker/,再创建文件Dockerfile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 基础镜像:仓库是java,tag是8
FROM java:8

#容器对外暴露8080端口,即可以通过这个端口访问容器内的服务,这个端口与容器内服务的端口存在一个对应关系,也可以在docker run时指定,本文即是这么做的,所以这里注释掉了,见后面docker run命令
#EXPOSE 8080

# 将本地/tmp目录持久化到Docker宿主主机内. 因为Spring Boot使用内嵌Tomcat容器,其默认使用/tmp作为工作目录。
# 所以效果就是将tomcat工作目录/tmp下的文件上传到远程docker主机的/var/lib/docker/**/
# 查在远程docker主机的文件:/var/lib/docker/overlay2/5e3d14ca7fedf5ee2c6ca2744d4da664f09f12affec8ed391740a8b92896f52f/diff/app.jar
VOLUME /tmp

#将打包好的程序拷贝到容器中的指定位置
ADD java_example-0.0.1-SNAPSHOT.jar app.jar

#容器启动后需要执行的命令
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-jar","/app.jar"]

Dockerfile详细说明参见:Dockerfile指令详解

使用docker构建项目

两种方式

  1. 命令
    cd ${project.dir} #如:cd ~/skyler/project/mytest/java_example
    mvn clean package docker:build -DskipTests

  2. 利用intellij maven插件
    -w289

依次点击clean, package, docker:build

成功的效果为:
-w1255

构建完可以看下本地生成的文件及目录:
-w355

简单来说,构建过程为首先把jar包和Dockerfile生成到构建目录下,然后执行Dockerfile中的指定,即构建image推到远程docker机器的/var/lib/docker/**/下,如下图

docker运行image

查看image

登录远程docker机器查看刚上传的image

1
docker image ls

通过intellij输出log中的f29374d5a2d5可以找到对应的image id: f29374d5a2d5

运行image

1
2
3
4
docker run -p 8081:20282 -t springboot_demo/java_example
# 解释一下
# -p 8081:20282; -p指定端口,8081为docker对外暴露的端口;20282为服务的端口;":"为端口映射,即docker的8081端口映射到服务的20282端口。这样外界就可以通过docker访问docker内部的服务了
# 详细信息通过 docker run --help查看

页面输出启动日志,可以看到和我们通过intellij main启动一样的日志内容

在浏览器中访问项目restful接口:
http://48.99.190.38:8081/user/test,接口返回success信息
-w410

也可以通过docker查看运行状态的image

1
docker ps

到此基本的通过Dockerfile,springboot集成docker完事了

docker使用spring profile特性

使用spring boot 我们都知道,可以根据profile指定不同的env环境,从而加载不同环境的配置,实现多环境切换部署。使用docker也可以实现spring profile特性。具体如下
方法之一:
在Dockerfile中指定,具体为ENTRYPOINT在原有基础上增加Dspring.profiles.active=local,如下

1
ENTRYPOINT ["java","-Djava.security.egd=file:/dev/./urandom","-Dspring.profiles.active=local","-jar","/app.jar"]

为区分不同环境,java_example新建application-local.yml, 为容易分辨效果,指定一个新端口

1
2
3
4
server:
port: 20281

debug: false

-w337

重新构建在intellij上clean, package, docker:build, 去docker宿主机器上运行docker image ls,观察CREATED字段的时间确定新的image. 运行新的image前,需要停止刚已启动的image

1
2
3
4
5
6
docker ps
# 找到已启动的image的containerId,docker ps -a 列出所有container

docker kill/stop containerId

docker run -p 8081:20281 -t springboot_demo/java_example

再看日志

端口已经变成20281了,spring profile已经生效了

扩展

docker 使用spring profile特性有几种方式

参考如下:https://segmentfault.com/a/1190000011367595

docker常用命令

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
$ docker run --rm -p 8000:3000 -it demo:0.0.1 /bin/bash
-p参数:容器的 8000 端口映射到服务的3000端口。
--rm参数,在容器终止运行后自动删除容器文件
-it参数:容器的 Shell 映射到当前的 Shell,然后你在本机窗口输入的命令,就会传入容器。
demo:0.0.1:image 文件的名字(如果有标签,还需要提供标签,默认是 latest 标签)。
/bin/bash:容器启动以后,内部第一个执行的命令。这里是启动 Bash,保证用户可以使用 Shell。

# 启动containerID容器
$ docker start [containerID]

# 查出启动状态的容器列表
$ docker container ls / docker ps

# 查出全部状态的容器列表
$ docker container ls -a / docker ps -a

# 停止指定的容器运行
$ docker kill/stop [containerID]

# 删除指定的容器文件
$ docker rm [containerID]

# 显示containerID容器或镜像相关信息
$ docker inspect [containerID]

# docker logs为查看 docker 容器的输出,即容器里面 Shell 的标准输出
# 查看指定containerID的日志,从末尾20开始输出
$ docker logs -f -t --tail 20 [containerID]

# docker交互式任务,即进入指定containerID的shell
$ docker exec -it [containerID] /bin/bash

25-54

详细参见:https://www.infoq.cn/article/KBTRC719-r6GHOPS3Cr8

docker run与docker start区别

docker run IMAGE_ID,而docker start CONTAINER_ID。即run runs an image; start starts a containerdocker run = docker create + docker start

docker交互式任务

格式:docker exec [OPTIONS] CONTAINER COMMAND [ARG...]

-i, –interactive Keep STDIN open even if not attached
-t, –tty Allocate a pseudo-TTY

如:docker exec -it 62617312c8bf /bin/bash,这个命令是进入容器62617312c8bf内部打开shell,ll 可以看到容器的目录结构,如下图
08-43

ctop: 容器的top界面

ctop是我最近使用的一个工具,它能够提供多个容器的实时指标视图

1
[root@xxxxx docker]# ctop -a

41-24

各系统安装:https://github.com/bcicen/ctop

docker-compose

意义

前面我们使用 Docker 的时候,定义 Dockerfile 文件,然后使用 docker build、docker run 等命令操作容器。然而微服务架构的应用系统一般包含若干个微服务,每个微服务一般都会部署多个实例,如果每个微服务都要手动启停,那么效率之低,维护量之大可想而知

使用 Docker Compose 可以轻松、高效的管理容器,它是一个用于定义和运行多容器 Docker 的应用程序工具

安装

下载稳定版本

1
sudo curl -L "https://github.com/docker/compose/releases/download/1.24.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

给目录添加执行权限

1
sudo chmod +x /usr/local/bin/docker-compose

可选功能:自动补全

1
sudo curl -L https://raw.githubusercontent.com/docker/compose/1.24.0/contrib/completion/bash/docker-compose -o /etc/bash_completion.d/docker-compose

查看安装是否成功

1
2
docker-compose -v
# output:docker-compose version 1.20.0, build ca8d3c6

组装文件

docker-compose执行需要项目jar和Dockerfile

回溯下intellij使用maven clean, package, docker:build操作吗,现在只需要maven clean, package操作,然后将target/docker下的Dockerfile和java_example-0.0.1-SNAPSHOT.jar上传到docker宿主机器

34-06

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ cd **/target/docker
$ scp Dockerfile root@48.99.190.38:/root/skyler_home/project #输入密码
$ scp java_example-0.0.1-SNAPSHOT.jar root@48.99.190.38:/root/skyler_home/project #输入密码
$ touch docker-compose.yml
$ vim docker-compose.yml
输入内容:
version: '3.3'

services:
java_example:
build: .
ports:
- 8081:20282
# 20282需要和java_example项目里配置的server.port一致

运行docker-compose

在 docker-compose.yml 所在路径下执行该命令 Compose 就会自动构建镜像并使用镜像启动容器

1
2
docker-compose up
docker-compose up -d // 后台启动并运行容器

进阶

这是docker和springboot集成的实战例子,下篇我们再进一步,实战springboot+mysql服务集成docker实例,链接:springboot+mysql服务集成docker实例