Docker 是一个 Liunx 容器管理工具包,允许用户发布容器镜像或使用他人发布的容器镜像。Docker 镜像是运行容器化进程的一种方式。本文将为一个简单的 Spring Boot 应用程序构建一个 Docker 镜像,并在 Docker 容器中运行。

1. 前提条件

Docker:本文构建的 Docker 镜像会在 Linux 的 Docker 环境中运行。如果你还没有搭建 Docker 环境或者你还不了解 Docker,可以参考 Docker 快速入门指南 这篇文章。

2. 创建 Spring Boot 程序

下面会使用 Maven 构建 Spring Boot 应用程序,使用 IDEA 来编写代码。你可以使用你喜欢的方式来构建你的 Spring Boot 程序。

使用 Maven 构建程序

pom.xml 文件

pom.xml 文件
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.lxiaocode</groupId>
<artifactId>docker</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>docker</name>
<description>Demo project for Spring Boot</description>

<properties>
<java.version>1.8</java.version>
</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>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
项目结构
...
└─ src
└─ main
└─ java
└─ com.lxiaocode.docker
└─ DockerApplication.java
...
└─ pom.xml

编写 Spring Boot 程序

DockerApplication.java 文件
package com.lxiaocode.docker;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class DockerApplication {

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

@RequestMapping("/")
public String sayHi(){
return "Hello Docker !";
}
}

在这个类上使用了 @SpringBootApplication@RestController 注解,这表示 Spring MVC 可以使用它来处理 web 请求。@RequestMapping/ 映射到 sayHi() 方法中,该方法会显示 “Hello Docker !”。在 main() 方法中,启动 Spring Boot 应用程序。这是一个非常简单的程序,因为主角是 Docker 而不是 Spring Boot。

运行 Spring Boot 程序

为了后续运行的方便,我们需要将应用程序代码放到具有 Docker 环境的 Linux 系统中。

项目目录
[root@lxiaocode docker]# pwd
/root/my_project/docker
[root@lxiaocode docker]# ll
total 36
-rw-r--r-- 1 root root 915 Jul 22 17:55 HELP.md
-rw-r--r-- 1 root root 10070 Jul 22 17:55 mvnw
-rw-r--r-- 1 root root 6608 Jul 22 17:55 mvnw.cmd
-rw-r--r-- 1 root root 1677 Jul 22 17:55 pom.xml
drwxr-xr-x 4 root root 4096 Jul 22 17:55 src

现在,即使你不在 Docker 环境中依然可以使用通过 Maven 启动应用程序。然后通过 [ip地址]:8080/ 来访问应用程序,你会看到 “Hello Docker !”。

使用 maven 启动程序
[root@lxiaocode docker]# mvn package && java -jar target/docker-0.0.1-SNAPSHOT.jar

3. 创建 Docker 镜像

创建 Dockerfile 文件

创建 Docker 镜像需要一个 Dockerfile 文件,它包含构建镜像的指令和说明。创建 Dockerfile 文件,并输入内容:

创建 Dockerfile 文件
[root@lxiaocode docker]# vi Dockerfile
Dcokerfile 文件
FROM openjdk:8-jdk-alpine
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

如果你是 Maven 项目,使用以下命令构建镜像。这个命令会构建镜像并标记为 spring-boot-docker

构建镜像文件
[root@lxiaocode docker]# docker build -t spring-boot-docker .
查看 Docker 镜像
[root@lxiaocode docker]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
spring-boot-docker latest ee2801a6f985 36 seconds ago 121MB

这个 Dockerfile 非常简单,但是这就是运行 Spring Boot 程序所需的所有内容。这个构建(build)创建一个 spring 用户和 spring 用户组来运行程序。然后将项目的 JAR 文件作为 “app.jar” COPY 到容器中,在 ENTRYPOINT 运行。

改进 Dockerfile 文件

使用非 root 用户运行程序:

使用特定用户来运行程序可以减少某些风险。所以,在 Dockerfile 的一项重要改进就是以非 root 用户身份来运行应用程序。

Dcokerfile 文件
FROM openjdk:8-jdk-alpine
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
ARG JAR_FILE=target/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT ["java","-jar","/app.jar"]

可以通过构建和运行镜像查看到运行应用程序的用户(“/app.jar started by spring in /”):

构建和运行镜像
[root@lxiaocode docker]# docker build -t spring-boot-docker .
[root@lxiaocode docker]# docker run -p 8080:8080 spring-boot-docker
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.3.1.RELEASE)

2020-07-22 11:21:34.727 INFO 1 --- [ main] c.lxiaocode.docker.DockerApplication : Starting DockerApplication on 32e75a89c1c4 with PID 1 (/app.jar started by spring in /)

整理资源文件

而且,Spring Boot fat jar 文件中的依赖和应用程序资源之间有明确的分隔,我们可以利用它来进一步提高性能。关键是在容器文件系统中创建层。这些层在构建时和运行时中都被缓存,因此将变化最频繁的资源(通常是应用程序本身中的类和静态资源)放在变化较慢的资源之后。

Dcokerfile 文件
FROM openjdk:8-jdk-alpine
RUN addgroup -S spring && adduser -S spring -G spring
USER spring:spring
ARG DEPENDENCY=target/dependency
COPY ${DEPENDENCY}/BOOT-INF/lib /app/lib
COPY ${DEPENDENCY}/META-INF /app/META-INF
COPY ${DEPENDENCY}/BOOT-INF/classes /app
ENTRYPOINT ["java","-cp","app:app/lib/*","hello.Application"]

Dockerfile 文件中有一个 DEPENDENCY 参数,这个参数指向我们解压缩 fat jar 的目录。

使用 maven 构建
[root@lxiaocode docker]# mkdir -p target/dependency && (cd target/dependency; jar -xf ../*.jar)

如果构建成功,它将包含一个存放依赖 jar 包的 BOOT-INF/lib 目录,以及一个存放应用程序类的 BOOT-INF/classes 目录

运行 Docker 镜像

Dockerfile 编写好了之后就可以利用它来构建 Docker 镜像了。Docker 镜像构建好了之后,就可以在任何地方使用 Docker 来运行该镜像:

构建 Docker 镜像
[root@lxiaocode docker]# docker build -t spring-boot-docker .
运行镜像
[root@lxiaocode docker]# docker run -p 8080:8080 spring-boot-docker

通过 [ip地址]:8080/ 就可以访问应用程序,你将会看到 “Hello Docker !”。

因为这仅仅是一个入门教程,所以仅限于基本的需求。如果你要在生产环境构建和使用 Docker 镜像,则需要考虑很多因素。

4. 总结

  1. Dockerfile 文件的编写。
  2. 使用 Dockerfile 文件构建 Docker 镜像。

评论