Docker多阶段构建实战(multi-stage builds)

简介: docker的多阶段构建功能(multi-stage builds),可以在一次构建镜像的过程中,使用多个基础镜像的能力,并且不会导致最终镜像的体积增加,一起来实战这个实用的功能

欢迎访问我的GitHub

这里分类和汇总了欣宸的全部原创(含配套源码): https://github.com/zq2599/blog_demos

本篇概览

  • 在编写Dockerfile构建docker镜像时,常遇到以下问题:
  1. RUN命令会让镜像新增layer,导致镜像变大,虽然通过&&连接多个命令能缓解此问题,但如果命令之间用到docker指令例如COPY、WORKDIR等,依然会导致多个layer;
  2. 有些工具在构建过程中会用到,但是最终的镜像是不需要的(例如用maven编译构建java工程),这要求Dockerfile的编写者花更多精力来清理这些工具,清理的过程又可能导致新的layer;
  • 为了解决上述问题,从17.05版本开始Docker在构建镜像时增加了新特性:多阶段构建(multi-stage builds),将构建过程分为多个阶段,每个阶段都可以指定一个基础镜像,这样在一个Dockerfile就能将多个镜像的特性同时用到,例如:先用maven镜像构建java工程,再把构建结果和jre合成,就做成了一个可以直接运行java工程镜像了;
  • 官方描述如下图所示,地址是:https://docs.docker.com/develop/develop-images/multistage-build/

在这里插入图片描述

  • 官方的实例是golang的,今天我们以maven构建springboot工程为例,实战如何使用multi-stage特性构建java微服务镜像;

环境信息

  • 本次实战的环境信息如下:
  1. 操作系统:Ubuntu 18.04.2 LTS
  2. Docker:18.06.1-ce
  3. Java:1.8.0_191
  4. Maven:3.6.1

实战源码

  • 本次实战用到的源码是个普通springboot工程,功能是SpringCloud中的注册中心eureka,您可以在Github下载,地址和链接如下所示:
名称 链接 备注
项目主页 https://github.com/zq2599/blog_demos 该项目在GitHub上的主页
git仓库地址(https) https://github.com/zq2599/blog_demos.git 该项目源码的仓库地址,https协议
git仓库地址(ssh) git@github.com:zq2599/blog_demos.git 该项目源码的仓库地址,ssh协议
  • 这个git项目中有多个文件夹,本章源码在springcloudscaledemo这个文件夹下,如下图红框所示:

在这里插入图片描述

  • springcloudscaledemo文件夹内有三个工程,本次实战用到的是eureka-server,如下图:

在这里插入图片描述

准备材料

  • 在能正常运行docker的电脑上新建一个目录,例如我这里是ubuntu系统上/home/willzhao/temp/201906/02,将maven工程eureka-server复制到这个目录下;

避免每次构建镜像都下载工程所需的jar包

  • 构建镜像过程中会用maven构建springboot工程,会下载springboot工程依赖的jar包,此过程很漫长,如果您多次构建Dockerfile镜像,那么每次都要经历这个过程,为了避免每次都下载,请做如下操作:
  1. 找一个可以运行maven的环境,把eureka-server工程复制到这个环境上;
  2. 在eureka-server目录下执行命令mvn clean package -U -DskipTests,开始构建此工程;
  3. 构建成功后,进入本地的maven缓存目录,通常是用户的home目录下的.m2文件夹,里面有个名为repository的目录;
  4. 将整个repository目录复制到前面提到的/home/willzhao/temp/201906/02目录下,和eureka-server放在同一目录,如下图:

在这里插入图片描述

  • 这样,在编写Dockerfile的时候只要用这个repository覆盖镜像中的maven缓存,在编译时就不会去maven的中央仓库下载jar了,会节省很多时间。

编写Dockerfile

  • /home/willzhao/temp/201906/02目录下创建文件Dockerfile,内容如下所示:
# Docker image for multi stage build
# VERSION 0.0.1
# Author: bolingcavalry

### 第一阶段,用maven镜像进行编译
FROM maven:3.6.1 AS compile_stage

####################定义环境变量 start####################
#定义工程名称,也是源文件的文件夹名称
ENV PROJECT_NAME eureka-server
#定义工作目录
ENV WORK_PATH /usr/src/$PROJECT_NAME
####################定义环境变量 start####################

#作者
MAINTAINER BolingCavalry <zq2599@gmail.com>

#将源码复制到当前目录
COPY ./$PROJECT_NAME $WORK_PATH

#如果前面您已经准备好了repository目录,就可以用来替换镜像中的repository目录了,先删除镜像中已有的repository
RUN rm -rf /root/.m2/repository

#将准备好的repository文件夹复制进来,这样相当于镜像环境中已经有了java工程所需的jar,可以避免去maven中央仓库下载
COPY ./repository /root/.m2/repository

#编译构建
RUN cd $WORK_PATH && mvn clean package -U -DskipTests

### 第二阶段,用第一阶段的jar和jre镜像合成一个小体积的镜像
FROM java:8-jre-alpine

####################定义环境变量 start####################
#定义工程名称,也是源文件的文件夹名称
ENV PROJECT_NAME eureka-server
#定义工程版本
ENV PROJECT_VERSION 0.0.1-SNAPSHOT
#定义工作目录
ENV WORK_PATH /usr/src/$PROJECT_NAME
####################定义环境变量 start####################

#安全起见不用root账号,新建用户admin
RUN adduser -Dh /home/admin admin

#工作目录是/app
WORKDIR /app

#从名为compile_stage的stage复制构建结果到工作目录
COPY --from=compile_stage $WORK_PATH/target/${PROJECT_NAME}-${PROJECT_VERSION}.jar .

#启动应用
CMD ["sh", "-c", "java -jar /app/${PROJECT_NAME}-${PROJECT_VERSION}.jar --spring.profiles.active=dev"]
  • 上面就是分成了两个阶段构建的Dockerfile脚本,请参考每行的注释来理解,有以下几点需要重点关注:
  1. 一共有两次FROM指令出现,而最终的镜像是基于最后一个FROM生成的;
  2. PROJECT_NAME这个环境变量被定义了两次(ENV PROJECT_NAME),因为前面阶段定义的环境变量在后面的阶段是用不了的;
  3. COPY --from=compile_stage这个命令,可以将指定阶段的文件复制到当前阶段来,这一步很关键,第一阶段用maven构建出来的jar文件,通过该命令复制到后面的阶段来使用了;
  4. 最后一个FROM是java:8-jre-alpine,这是精简版的java运行环境镜像,最终镜像的内容就是jre和maven的构建结果,而前面的maven镜像和最终构建结果无关;

构建镜像

  • 在Dockerfile所在目录执行以下命令即可构建镜像:
docker build -t bolingcavalry/multi-stage-build:0.0.1-SNAPSHOT .
  • 输出的部分结果信息如下:
...
Step 13/16 : RUN adduser -Dh /home/admin admin
 ---> Running in 20421e52c3e6
Removing intermediate container 20421e52c3e6
 ---> dfb33f654436
Step 14/16 : WORKDIR /app
 ---> Running in d17f74e9c119
Removing intermediate container d17f74e9c119
 ---> bbd17f2d0777
Step 15/16 : COPY --from=compile_stage $WORK_PATH/target/${PROJECT_NAME}-${PROJECT_VERSION}.jar .
 ---> 5d194c2a6b17
Step 16/16 : CMD ["sh", "-c", "java -jar /app/${PROJECT_NAME}-${PROJECT_VERSION}.jar --spring.profiles.active=dev"]
 ---> Running in 2cb771e5af44
Removing intermediate container 2cb771e5af44
 ---> b05fc74903ed
Successfully built b05fc74903ed
Successfully tagged bolingcavalry/multi-stage-build:0.0.1-SNAPSHOT
  • 查看镜像的体积如下,148兆,符合预期:
REPOSITORY                        TAG                 IMAGE ID            CREATED             SIZE
bolingcavalry/multi-stage-build   0.0.1-SNAPSHOT      b05fc74903ed        2 minutes ago       148MB
maven                             3.6.1               740262c47f21        3 days ago          614MB
java                              8-jre-alpine        fdc893b19a14        2 years ago         108MB

验证镜像是否可用

  • 执行以下命令可以用最新的镜像启动一个容器:
docker run -p 8080:8082 bolingcavalry/multi-stage-build:0.0.1-SNAPSHOT
  • 在前面Dockerfile文件的CMD命令中指定了profile参数为dev,所以application-dev.properties文件会生效,这里面定义的端口号是8082,所以docker run命令中通过-p参数将容器的8082端口映射到宿主机8080端口
  • 用浏览器访问宿主机的8080端口,看服务是否正常,如下图:

在这里插入图片描述

  • 至此,docker的多阶段构建实战就完成了,这是个很实用功能,在您构建镜像的过程中如果想用到多个镜像的能力,又不想自己去做相关的集成和清理工作,并且对镜像体积有要求的时候,希望本文能给您一些参考。

欢迎关注阿里云开发者社区博客:程序员欣宸

学习路上,你不孤单,欣宸原创一路相伴...
相关文章
|
16天前
|
负载均衡 网络协议 开发者
掌握 Docker 网络:构建复杂的容器通信
在 Docker 容器化环境中,容器间的通信至关重要。本文详细介绍了 Docker 网络的基本概念和类型,包括桥接网络、宿主网络、覆盖网络和 Macvlan 网络等,并提供了创建、管理和配置自定义网络的实用命令。通过掌握这些知识,开发者可以构建更健壮和灵活的容器化应用,提高应用的可扩展性和安全性。
|
1月前
|
Docker 容器
Docker自建仓库之Harbor高可用部署实战篇
关于如何部署Harbor高可用性的实战教程,涵盖了从单机部署到镜像仓库同步的详细步骤。
68 15
Docker自建仓库之Harbor高可用部署实战篇
|
14天前
|
运维 Cloud Native Docker
云原生技术入门:Docker容器化实战
【9月更文挑战第20天】本文将引导你走进云原生技术的世界,通过Docker容器化技术的实战演练,深入理解其背后的原理和应用。我们将一起探索如何在云平台上利用Docker简化部署、扩展和管理应用程序的过程,并揭示这一技术如何改变现代软件的开发和运维模式。
|
1月前
|
算法 Linux 调度
Docker的资源限制实战篇
本文详细介绍了如何利用Docker对容器的资源进行限制,包括内存和CPU的使用。文章首先概述了资源限制的重要性及其在Linux系统中的实现原理,并强调了不当设置可能导致的风险。接着,通过一系列实战案例展示了如何具体设置容器的内存限制,包括硬性限制、动态调整以及软限制等。最后,文章还提供了限制容器CPU访问的具体方法和示例,如指定容器使用的CPU核心数和基于`--cpu-shares`参数对CPU资源进行分配。通过这些实践,读者可以更好地理解和掌握Docker资源管理技巧。
74 14
Docker的资源限制实战篇
|
1月前
|
存储 数据管理 应用服务中间件
Docker的数据管理实战篇
关于Docker数据管理实战的教程,涵盖了Docker数据卷的使用、特点、场景以及数据卷容器的概念和应用。
51 13
Docker的数据管理实战篇
|
10天前
|
关系型数据库 Linux 虚拟化
Docker入门基础实战
Docker入门基础实战
|
1月前
|
Ubuntu Linux Docker
Ubuntu 18.04 安装Docker实战案例
关于如何在Ubuntu 18.04系统上安装Docker的实战案例,包括安装步骤、配置镜像加速以及下载和运行Docker镜像的过程。
163 3
Ubuntu 18.04 安装Docker实战案例
|
1月前
|
存储 Linux Docker
CentOS 7.6安装Docker实战案例及存储引擎和服务进程简介
关于如何在CentOS 7.6上安装Docker、介绍Docker存储引擎以及服务进程关系的实战案例。
93 3
CentOS 7.6安装Docker实战案例及存储引擎和服务进程简介
|
20天前
|
Devops jenkins 持续交付
DevOps实践:构建和部署一个Docker化的应用
【9月更文挑战第14天】在当今快节奏的软件开发领域,DevOps已经成为提升效率、加速交付的关键。本文将引导你理解DevOps的核心概念,并通过一个实际的示例—构建和部署一个Docker化的应用—来深入探讨其实践方法。我们将从简单的应用出发,逐步实现Docker容器化,并最终通过CI/CD流水线自动化部署过程。这不仅是对DevOps流程的一次实操演练,也是对现代软件开发理念的一次深刻体验。
|
23天前
|
Kubernetes Docker 微服务
构建高效的微服务架构:基于Docker和Kubernetes的最佳实践
在现代软件开发中,微服务架构因其灵活性和可扩展性而受到广泛青睐。本文探讨了如何利用Docker和Kubernetes来构建高效的微服务架构。我们将深入分析Docker容器的优势、Kubernetes的编排能力,以及它们如何结合实现高可用性、自动扩展和持续部署。通过具体的最佳实践和实际案例,读者将能够理解如何优化微服务的管理和部署过程,从而提高开发效率和系统稳定性。
下一篇
无影云桌面