为何考虑采用Docker?

Docker是提供用户构建镜像的一种容器化技术,所构建的镜像包含了主要的应用程序和运行应用所需的所有依赖项。该镜像可在任何虚拟机或物理机器上的Docker容器上运行。它的强大之处在于允许用户在开发、测试、预生产和生产中运行同样的镜像,而不必担心在每个环境中依赖项的安装或配置。采用Docker构建和运行应用

以Java程序员的视角看,Docker的典型应用场景是在容器内运行应用。这固然不错,但如果Docker能提供应用的构建是不是更好?本文中,我将演示如何在容器内用Docker来编排、构建和运行Spring Boot应用。请先按如下步骤创建一个Docker镜像:

  • 从源主机复制应用程序源代码到镜像的临时构建目录

  • 采用Maven完成应用的编译和打包,生成可执行的JAR文件

  • 采用JRE运行JAR文件

镜像大小的提示

关注所构建镜像文件的大小非常重要。较小的镜像文件具有更快的构建速度、下载速度和更低的存储成本优势。所以要尽可能地让镜像只包括所需的几项组件即可。采用较小的基本镜像同样的道理,选用只包含必须功能的基础镜像文件也是最佳的选择。本文后续采用Alpine镜像也是基于同样考虑,Alpine是只有5MB的超细Linux发行版。非常适合构建精细的镜像。同时Alpine提供一个包管理器,让用户可以安装任何需要的包。但由于Alpine的初始包非常小,所以安装大量包的过程会有些麻烦。如果有看DockerHub的话,就会发现很多流行的镜像都提供了Alpine版,可以直接使用。后续我们也将用到Alpine版本的Maven和Open JDK JRE镜像。抛弃不需要的内容在稍后过程中所定义编译、打包并运行的Spring Boot应用的镜像。就是可部署运行的最终Docker镜像,因此它只需要包含应用本身和运行时依赖项,能够满足在单个容器中构建和运行就可以了。也就是说它可以纯粹就是可执行的JAR包和运行所需的Java JRE文件,而无需包含Maven(包括本地Maven库)或目标目录的全部内容。那么,用户所要做的就是构建应用,然后从最终镜像中剔除不需要的内容。这个正是多阶段构建的作用所在。它允许用户将Docker构建分解为不同的步骤,并在步骤之间复制特定的目标项,抛弃非必须的内容,从而实现抛弃构建工具本身和其他对应用没有关联的内容。测试案例执行步骤

项目构建非常简单,举个例子,我用一个类创建一个标准的Spring Boot应用,并在项目的根目录中添加了一个Dockerfile。(用户可在GitHub[1]上获取这个实验的完整源代码,同步实验。)

主类的代码显示如下,且没有添加任何其他内容。接下来我将采用默认的执行器健康状况端点来测试这个应用。

package com.blog.samples.docker;

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);

}

}

定义Docker镜像

如下内容是Dockerfile中定义的镜像文件,尽管内容不多,但包含了很多步的工作。我将在下面详细解释每一行。

FROM maven:3.5.2-jdk-8-alpine AS MAVEN_BUILD

MAINTAINER Brian Hannaway

COPY pom.xml /build/

COPY src /build/src/

WORKDIR /build/

RUN mvn package

FROM openjdk:8-jre-alpine

WORKDIR /app

COPY --from=MAVEN_BUILD /build/target/docker-boot-intro-0.1.0.jar /app/

ENTRYPOINT ["java", "-jar", "docker-boot-intro-0.1.0.jar"]

代码备注:FROM maven:3.5.2-jdk-8-alpine AS MAVEN_BUILD告知Docker采用Maven编译器。maven:3.5.2-jdk-8-alpine构建第一步采用的基础镜像,Docker将首先在本地查找镜像,本地不存在后,将从DockerHub拉取。Maven会在最后阶段被剔除掉(后续COPY命令介绍)考虑下载快速和镜像大小控制的原因,选择Alpine版的Maven镜像。MAINTAINERBrianHannaway非必选项,但是为映像作者提供一个接触点可提高可维护性。(本实验应用验证的点)COPY pom.xml/build/在镜像中创建一个build目录, 并拷入pom.xml文件。COPY src/build/src/拷入src目录到镜像中build目录。WORKDIR/build/设置build为工作目录。后续任何命令都在此目录中运行。RUN mvnpackage执行mvn包来运行编译和打包应用,生成成可执行的JAR文件。在第一次构建镜像时,Maven将从公共Maven库拉取所有需要的依赖项,并将它们缓存在镜像的本地。后续的构建将使用这个缓存版的镜像层,这意味着依赖项将在本地引用,而不必再次从外部拉取。至此,已经完成了镜像定义,只需等其构建成一个可执行的JAR文件。这是多阶段构建的第一部分。下一阶段将获取JAR并运行它。FROM openjdk:8-jre-alpine告知Docker多阶段构建的下一步采用openjdk:8-jre-alpine的基础镜像。再次使用Java 8 JRE的Alpine版本,这一步的选择其实比前面的Maven版本选择更为重要,因为存在于最终版的镜像只是openjdk:8-jre-alpine,因此如果要尽可能控制最终镜像大小的话,选择轻量级JRE镜像就非常重要。WORKDIR/app告知Docker在镜像内创建另一个/app工作目录,后续任何命令都在此目录中运行。COPY--from=MAVEN_BUILD/build/target/docker-boot-intro-0.1.0.jar/app/告知Docker从MAVEN_BUILD阶段的/build/target目录复制ocker-boot-intro-0.1.0.jar到/app目录。如前文所述,多阶段构建的优势就是允许用户将特定的内容从一个构建阶段复制到另一个构建阶段,并丢弃其他所有的内容。如果需要保留从MAVENBUILD阶段开始的所有内容,那最终镜像会包含Maven(包括Maven本地库)工具,以及目标目录中生成的所有类文件。通过从MAVENBUILD阶段选择必须要的内容,那最终得到的镜像会小很多。ENTRYPOINT["java","-jar","app.jar"]告知Docker在容器运行本镜像时,运行哪些命令。本部分用冒号进行多命令的隔离。本案例中,需要把执行JAR文件复制到/app目录运行。构建镜像

完成Docker镜像定义后,就可以着手构建。打开包含Dockerfile(根目录)的目录。运行以下命令构建镜像:

docker image build -t docker-boot-intro

-t参数为指定名称和可选标签。如果不指定标签,Docker会自动标记为最latest。

$ docker image build -t docker-boot-intro .

Sending build context to Docker daemon 26.56MB

Step 1/10 : FROM maven:3.5.2-jdk-8-alpine AS MAVEN_BUILD

---> 293423a981a7

Step 2/10 : MAINTAINER Brian Hannaway

---> Using cache

---> db354a426bfd

Step 3/10 : COPY pom.xml /build/

---> Using cache

---> 256340699bc3

Step 4/10 : COPY src /build/src/

---> Using cache

---> 65eb0f98bb79

Step 5/10 : WORKDIR /build/

---> Using cache

---> b16b294b6b74

Step 6/10 : RUN mvn package

---> Using cache

---> c48659e0197e

Step 7/10 : FROM openjdk:8-jre-alpine

---> f7a292bbb70c

Step 8/10 : WORKDIR /app

---> Using cache

---> 1723d5b9c22f

Step 9/10 : COPY --from=MAVEN_BUILD /build/target/docker-boot-intro-0.1.0.jar /app/

---> Using cache

---> d0e2f8fbe5c9

Step 10/10 : ENTRYPOINT ["java", "-jar", "docker-boot-intro-0.1.0.jar"]

---> Using cache

---> f265acb14147

Successfully built f265acb14147

Successfully tagged docker-boot-intro:latest

SECURITY WARNING: You are building a Docker image from Windows against a non-Windows Docker host. All files and directories added to build context will have '-rwxr-xr-x' permissions. It is recommended to double check and reset permissions for sensitive files and directories.

Brians Computer@DESKTOP-077OUJ8 MINGW64 /c/dev/docker-boot-intro (master)

运行构建时,Docker将逐条执行Docker文件中的每个命令。为每个步骤创建一个带有唯一ID的层。例如,步骤1创建的层的ID为293423a981a7。第一次构建图像时,Docker将从DockerHub获取它需要的任何外部图像,然后在此之上开始构建新的层。这会使得第一次构建速度非常慢。在构建过程中,Docker在尝试构建层之前会检查缓存,看看是否已经有所构建层的缓存版本。如果该层的缓存版本可用,Docker将直接使用它而不是从头开始构建。这意味着一旦构建了一个镜像层,后续的构建就是重用,速度会快很多。你可以在上面的构建输出中通过Docker缓存输出的hash值看到使用了缓存层。以上面第6步所发生的为例:作为RUN mvn包命令的一部分,Docker将从公共Maven库获取所有POM依赖项,构建成一个可执行JAR,并将所有这些内容存储在ID为c48659e0197e的层中。下一次构建这个镜像时,Maven依赖项和应用程序JAR将从缓存层中取出,而不必再次下载和构建。镜像大小

运行docker image ls命令将罗列出所有的本地镜像。可发现docker-boot-intro镜像大小为105 MB。

Brians Computer@DESKTOP-077OUJ8 MINGW64 /c/dev/docker-boot-intro (master)

$ docker image ls

REPOSITORY TAG IMAGE ID CREATED SIZE

docker-boot-intro latest 823730301d60 15 minutes ago 105MB

853d42b823c3 15 minutes ago 136MB

39ac5e9e9562 19 minutes ago 105MB

dfda2356bd36 19 minutes ago 136MB

Brians Computer@DESKTOP-077OUJ8 MINGW64 /c/dev/docker-boot-intro (master)

我在前文中提到过尽可能保持镜像大小的最佳实践,接下来让我们细探一下docker-boot-intro镜像的105MB由什么组成的。运行如下命令:

docker image history boot-docker-intro

将看到镜像中各个层的内容情况。

Brians Computer@DESKTOP-077OUJ8 MINGW64 /c/dev/docker-boot-intro/target (master)

$ docker image history docker-boot-intro

IMAGE CREATED CREATED BY SIZE COMMENT

823730301d60 19 minutes ago /bin/sh -c #(nop) ENTRYPOINT ["java" "-jar"... 0B

7e43d899f02f 19 minutes ago /bin/sh -c #(nop) COPY file:05f3666306f8c7af... 20.1MB

1723d5b9c22f 6 days ago /bin/sh -c #(nop) WORKDIR /app 0B

f7a292bbb70c 4 months ago /bin/sh -c set -x && apk add --no-cache o... 79.4MB

4 months ago /bin/sh -c #(nop) ENV JAVA_ALPINE_VERSION=8... 0B

4 months ago /bin/sh -c #(nop) ENV JAVA_VERSION=8u212 0B

4 months ago /bin/sh -c #(nop) ENV PATH=/usr/local/sbin:... 0B

4 months ago /bin/sh -c #(nop) ENV JAVA_HOME=/usr/lib/jv... 0B

4 months ago /bin/sh -c { echo '#!/bin/sh'; echo 'set... 87B

4 months ago /bin/sh -c #(nop) ENV LANG=C.UTF-8 0B

4 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B

4 months ago /bin/sh -c #(nop) ADD file:a86aea1f3a7d68f6a... 5.53MB

Brians Computer@DESKTOP-077OUJ8 MINGW64 /c/dev/docker-boot-intro/target (master)

如上所显示5.53 MB的Alpine基础镜像处于第一层。在之上的几层配置了一系列的环境变量,然后是大小为79.4 MB的JRE文件。最后的3层是我们在Dockerfile中定义的层,并包含了20.1 MB的应用JAR。可以发现这个镜像只包括了运行应用所必须的组件,是一个非常不错的轻量级镜像。运行容器

镜像构建好后,可以使用以下命令运行一个容器:

docker container run -p 8080:8080 docker-boot-intro

run命令包括一个可选的-p参数,作用是允许用户将容器应用的端口映射到主机的端口。熟悉Spring Boot的人都知道,应用程序的默认启动端口就是8080。运行一个容器时,Docker将运行可执行JAR文件来启动应用,使用容器的8080端口。但如果要访问容器中的应用,需要通过主机的端口访问,通过端口映射去到容器端口。-p 8080:8080参数就是将容器端口8080映射到主机端口8080。如果没有异常的话,应该可以看到应用程序在端口8080成功启动的信息。

Brians Computer@DESKTOP-077OUJ8 MINGW64 /c/dev/docker-boot-intro/target (master)

$ docker container run -p 8080:8080 docker-boot-intro

. ____ _ __ _ _

/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \

( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \

\\/ ___)| |_)| | | | | || (_| | ) ) ) )

' |____| .__|_| |_|_| |_\__, | / / / /

=========|_|==============|___/=/_/_/_/

:: Spring Boot :: (v2.1.7.RELEASE)

5436 [main] INFO com.blog.samples.docker.Application - Starting Application v0.1.0 on 934a1d731576 with PID 1 (/app/docker-boot-intro-0.1.0.jar started by root in /app)

5466 [main] INFO com.blog.samples.docker.Application - No active profile set, falling back to default profiles: default

16585 [main] INFO o.s.b.w.e.tomcat.TomcatWebServer - Tomcat initialized with port(s): 8080 (http)

16742 [main] INFO o.a.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-8080"]

16886 [main] INFO o.a.catalina.core.StandardService - Starting service [Tomcat]

16892 [main] INFO o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/9.0.22]

17622 [main] INFO o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext

17628 [main] INFO o.s.web.context.ContextLoader - Root WebApplicationContext: initialization completed in 11614 ms

21399 [main] INFO o.s.s.c.ThreadPoolTaskExecutor - Initializing ExecutorService 'applicationTaskExecutor'

23347 [main] INFO o.s.b.a.e.web.EndpointLinksResolver - Exposing 2 endpoint(s) beneath base path '/actuator'

23695 [main] INFO o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-8080"]

23791 [main] INFO o.s.b.w.e.tomcat.TomcatWebServer - Tomcat started on port(s): 8080 (http) with context path ''

23801 [main] INFO com.blog.samples.docker.Application - Started Application in 21.831 seconds (JVM running for 25.901)

应用测试

如果看到类似于上面显示的信息输出,那表示容器已经顺利启动。接下来就可以测试应用。如果你在Windows或Mac上运行Docker,需要使用的工具是一个Linux虚拟机Docker Toolbox。需要通过运行docker-machine ip命令可以获得Linux VM的IP地址。本案例中的Linux VM IP是192.168.99.100。

Brians Computer@DESKTOP-077OUJ8 MINGW64 /c/dev/docker-boot-intro (master)

$ docker-machine ip

192.168.99.100

获得IP后,可以使用cURL命令cURL 192.168.99.100:8080/actuator/health来调用应用的健康检查点来测试应用情况。如果应用程序启动并运行正常,即可获得HTTP 200的响应,响应内容为{“status”:“up”}。

Brians Computer@DESKTOP-077OUJ8 MINGW64 /c/dev/docker-boot-intro (master)

$ curl 192.168.99.100:8080/actuator/health

% Total % Received % Xferd Average Speed Time Time Time Current

Dload Upload Total Spent Left Speed

100 15 0 15 0 0 937 0 --:--:-- --:--:-- --:--:-- 937{"status":"UP"}

本方法的局限性

我在前文提到过,可以重用Docker缓存层以减少构建时间。虽然这是事实,但是在构建Java应用时需要考虑存在的例外。每当对Java源代码或POM文件进行更改后,Docker将会发现变更差异,从而忽略缓存的副本层,重新构建所需的层。这是正常的,但问题是这个变化会导致缓存中的Maven依赖项丢失。因此,当使用mvn包命令重新构建这个层时,所有Maven依赖项将再次从远程库中拉取一次,导致显著减慢了构建的速度,成为开发过程中真正的痛点。而且这个问题在构建没有Docker的Java应用程序时完全不存在,仅仅发生在使用Docker构建应用层时发生。解决方案是什么?

目前解决这个问题的方法是使用主机上的本地Maven存储库作为Maven依赖项的源。通过卷告诉Docker去访问主机本地的Maven库,而非从公共库中拉取依赖项。这种方法可以解决这个问题。但也是有利有弊。从好的方面看,你使用的是主机缓存的Maven依赖项,可以在更改源代码后,快速重新构建,节省了构建时间。但不利的方面是Docker镜像的管理因此而失去了一些自主性。使用Docker的主要初衷之一就是不必担心在其运行的环境中的软件配置。理想情况下,Docker镜像应该是自我构建且拥有构建和运行所需的一切元素,而不必存在主机依赖。而这个方法恰好违背了这个初衷,让Docker构建失去了部分自主性。在下一篇文章中,我们将介绍Docker卷,并展示如何使用它们访问主机上的Maven库。结束语

在本文中,我们定义了一个Docker镜像来构建和运行一个Spring Boot应用程序。我们讨论了让镜像保持尽可能小的重要性,可以通过使用超级小的Alpine基础镜像和在多阶段构建过程中进行内容剔除的方式来实现。我们还讨论了使用Docker构建Java应用程序的局限性和可能的解决方案。用户可以从GitHub[1]获取文章中的测试完整源代码。相关链接:

  1. https://github.com/briansjavablog/build-and-run-spring-boot-with-docker

原文链接:https://dzone.com/articles/build-package-and-run-spring-boot-apps-with-docker基于Kubernetes的DevOps实战培训

基于Kubernetes的DevOps实战培训将于2019年12月27日在上海开课,3天时间带你系统掌握Kubernetes,学习效果不好可以继续学习。本次培训包括:容器特性、镜像、网络;Kubernetes架构、核心组件、基本功能;Kubernetes设计理念、架构设计、基本功能、常用对象、设计原则;Kubernetes的数据库、运行时、网络、插件已经落地经验;微服务架构、组件、监控方案等,点击下方图片或者阅读原文链接查看详情。

8s 使用本地打包镜像_在Docker环境构建、打包和运行Spring Boot应用相关推荐

  1. docker build -t_在Docker环境构建、打包和运行Spring Boot应用

    为何考虑采用Docker? Docker是提供用户构建镜像的一种容器化技术,所构建的镜像包含了主要的应用程序和运行应用所需的所有依赖项.该镜像可在任何虚拟机或物理机器上的Docker容器上运行.它的强 ...

  2. docker eclipse打包_【Docker】Maven打包SpringBoot项目成Docker镜像并上传到Harbor仓库(Eclipse、STS、IDEA、Maven通用)...

    写在前面 最近,在研究如何使用Maven将SpringBoot项目打包成Docker镜像并发布到Harbor仓库,网上翻阅了很多博客和资料,发现大部分都是在复制粘贴别人的东西,没有经过实践的检验,根本 ...

  3. docker desktop ubuntu镜像_原创 | Docker入门,看了不理解,假一赔命

    写在前面 这篇博客适合谁? 对于Docker并不了解,只是有一点模糊的感觉,觉得Docker可以当成虚拟机用之类的 只是下载了Docker软件,对于怎么配置,怎么玩,第一步干什么,完全一无所知 其二, ...

  4. docker desktop ubuntu镜像_「Docker」 - 镜像仓库

    一.镜像仓库 将Docker镜像和Git进行对比,镜像仓库类似GitHub.GitLab等托管平台,Docker的镜像仓库托管的不是代码项目,而是镜像. Docker镜像仓库最大的作用是实现了Dock ...

  5. docker 删除所有镜像_关于 Docker 镜像的操作,看完这篇就够啦 !(下)| 文末福利...

    紧接着上篇<关于 Docker 镜像的操作,看完这篇就够啦 !(上)>,奉上下篇 !!! 镜像作为 Docker 三大核心概念中最重要的一个关键词,它有很多操作,是您想学习容器技术不得不掌 ...

  6. 打docker镜像_使用docker构建自己的镜像

    在docker中,我们使用docker build构建一个新的镜像,构建镜像之前需要去编写Dockerfile这个文件才能否建一个新的镜像. 官方文档:https://docs.docker.com/ ...

  7. 构建openjdk镜像_在Windows上构建OpenJDK

    构建openjdk镜像 通过做一些实验,我发现手头提供JDK源代码来进行一些更改,使用它等等通常很有用.因此,我决定下载并编译该野兽. 显然,这花了我一些时间,尽管我最初的想法是,它应该和运行make ...

  8. 阿里云docker 环境构建镜像的:lastest not found 问题

    在按照 Docker练习场 操作时,pull 遇到 lastest not found ,是自己的镜像没有这个版本号,到"镜像版本"看也是没有的: 把pull 换成已有镜像,如:r ...

  9. 怎么打包图片_怎么将许多张照片打包发到邮箱?

    怎么将许多张照片打包发到邮箱? 所谓的打包,就是需要你把大量照片文件变成一个压缩文件,类似于放到一个文件夹内,也可以理解为把多张纸质照片装订成一个相册,而在装订过程中需要相应工具才能完成. 使用压缩包 ...

最新文章

  1. php视频录制插件,Chrome浏览器录屏扩展插件
  2. 传统APP与微信端APP十大优劣对比
  3. linux需要检测的系统资源不足,细说Linux 系统优化
  4. java 反射机制_基础篇:深入解析JAVA反射机制
  5. pack unpack 用法 转载
  6. windows2008不能显示图片缩略图设置
  7. 赤兔AVI视频恢复软件找回永久删除的avi视频
  8. windows defender卸载_16款仙级软件一定能提高你的Windows舒适度,工作效率翻倍
  9. 批量生成小说人物名字
  10. 汽车电子ECU bootloader工作原理及开发要点
  11. linux查询服务器域名解析记录
  12. 输入一个字符串,对字符中的各个英文字符,数字,空格进行统计。 按照统计个数由多到少输出统计结果,如果统计的个数相同,则按照ASII码由小到大排序输出
  13. FTDI通用转USB芯片简述
  14. linux单独编译内核的驱动
  15. linux 查看.img文件,linux img文件 分区挂载
  16. 后端学习关卡三习题归纳及问题解决1
  17. Java、JSP电费管理系统
  18. atoi和strtol的区别和使用
  19. PHP安装阿里云消息服务MNS的SDK
  20. 为什么微网中要用到下垂控制

热门文章

  1. 【杂项】原来有两种单引号(单引号和反引号)
  2. Spring AOP注解方式实现日志管理
  3. deepin v20.4设置全局搜索的快捷键
  4. Django 3.2.5博客开发教程:体验django模板
  5. Linux根目录扩容操作命令整理(扩容99G;未添加新盘)
  6. JavaFX给控件添加css样式
  7. linux 产生0~2之间的随机数
  8. Hadoop MapReduce手机上网流量统计代码示例及运行结果演示
  9. Linux top指令
  10. synchronized的可重入怎么实现的