为何考虑采用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的数据库、运行时、网络、插件已经落地经验;微服务架构、组件、监控方案等,点击下方图片或者阅读原文链接查看详情。

docker build -t_在Docker环境构建、打包和运行Spring Boot应用相关推荐

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

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

  2. docker build -t_利用Dockerfile自定义镜像-图解轻松学Dockeramp;K8S

    >>>点我开始播放<<< 你好,我是老齐,本节咱们来学习使用docker file配置文件,构建属于自己的镜像.回到咱们的控制台,在这首先来看一下.上一节课我们学习 ...

  3. 在Docker中运行Spring Boot的高级功能测试

    来源:SpringForAll社区 想要学习更多有关Spring Boot项目的功能测试吗?阅读这篇博客可以让您掌握如何利用Docker容器进行测试. 概览 本文重点介绍如何使用Spring Boot ...

  4. (转)构建微服务:Spring boot 入门篇

    转自: Spring Boot(一):入门篇 - 纯洁的微笑 - 博客园 : 什么是Spring Boot Spring Boot 是由 Pivotal 团队提供的全新框架,其设计目的是用来简化新 S ...

  5. 构建微服务:Spring boot 入门篇

    Spring官方网站本身使用Spring框架开发,随着功能以及业务逻辑的日益复杂,应用伴随着大量的XML配置文件以及复杂的Bean依赖关系.随着Spring 3.0的发布,Spring IO团队逐渐开 ...

  6. docker build 变量_DockerFile 设置环境变量

    镜像的Layer 在docker docs里面有一句话: We've already seen that Docker images are read-only templates from whic ...

  7. docker es持久化_Docker 搭建 ES 集群并整合 Spring Boot

    一.前言 什么是 Elasticsearch ? Elasticsearch 是一个基于 Apache Lucene(TM) 的开源搜索引擎.无论在开源还是专有领域,Lucene 可以被认为是迄今为止 ...

  8. 建立arm linux运行环境,构建 arm-linux 仿真运行环境 (skyeye + arm-linux + NFS)

    一 前言 本文旨在将 arm-linux 在 skyeye 上搭建起来,并在 arm-linux 上能成功 mount NFS 为目标, 最终我们能在 arm-linux 里运行我们自己的应用程序. ...

  9. docker 打包镜像_Spring Boot2 系列教程(四十一)部署 Spring Boot 到远程 Docker 容器

    不知道各位小伙伴在生产环境都是怎么部署 Spring Boot 的,打成 jar 直接一键运行?打成 war 扔到 Tomcat 容器中运行?不过据松哥了解,容器化部署应该是目前的主流方案. 不同于传 ...

最新文章

  1. 全文翻译(全文合集):TVM: An Automated End-to-End Optimizing Compiler for Deep Learning
  2. 不看后悔 如何删除WIN7的100M隐藏分区
  3. python中的linearregression_【python+机器学习(2)】python实现Linear Regression
  4. 华为p4支持鸿蒙功能吗_吹过的牛都一步一步给实现了!明年华为手机支持升级鸿蒙系统!...
  5. 正确修改MySQL最大连接数的三种好用方案
  6. python测试脚本项目全程教程_《手把手教你》系列练习篇之5-python+ selenium自动化测试(详细教程)...
  7. 采用流水线技术实现8位加法器
  8. 常见Java面试题之如何实现对象克隆
  9. Android应用程序变量
  10. M - 简单字符串排序
  11. 20145201 20145227 《信息安全系统设计基础》实验二 固件开发
  12. 见过一个一个拉新地推没见过这么多一起推
  13. Uber如何使用Mesos的?答曰:和Cassandra一起用
  14. FreeBSD常用操作
  15. iOS 字体pt和px的转换
  16. Windows 7/10下安装Ubuntu 16.04双系统
  17. F1DC2706双模数据透传蓝牙模块AT指令使用介绍
  18. 富士相机设置传原图_【富士 X-E3 无反相机使用体验】蓝牙|WIFI|连接|图像传输_摘要频道_什么值得买...
  19. 最新的JavaScript知识总结,欢迎各位大佬指正,需要的留下邮箱,给你们发原稿(PDF版)...
  20. 酒越陈越醇,OPPO Reno越更越香

热门文章

  1. 广州计算机操作员培训,汕尾市计算机操作员报名考试时间地址及培训入口
  2. qq浏览器私密空间在哪 具体操作步骤
  3. 使用struts2的 下载
  4. 行内元素和块级元素的区别,为何img、input等行内元素可以设置宽高??(夯实基础)
  5. Servlet第三篇【request和response简介、response的常见应用】
  6. Bugzilla 使用指南
  7. 悖论对计算机科学影响,数学和计算机科学的核心逻辑悖论
  8. 用js拼html写下拉框,js实现下拉框效果(select)
  9. linux 中文意思,linux 中 ~/. 是什么意思
  10. java 类 加载 初始化_java中类的初始化和加载