Docker multi-stage build机制

简介: 随着17.05版本的发布,Docker对于镜像构建这块也作了一项重要更新,那就是 multi-stage build(多阶段构建),这对于长期因为构建镜像太大而困扰的小伙伴们来说真是雪中送炭。

multi_stage_build

随着17.05版本的发布,Docker对于镜像构建这块也作了一项重要更新,那就是 multi-stage build(多阶段构建),这对于长期因为构建镜像太大而困扰的小伙伴们来说真是雪中送炭。

不支持多阶段构建

在17.05版本之前,我们构建Docker镜像时,通常会采用两种方式:

  • 将所有的构建过程编写在同一个Dockerfile中,包括项目及其依赖库的编译、测试、打包等流程,这里可能会带来的一些问题:
    1. Dockerfile特别臃肿
    2. 镜像层次特别深
    3. 存在源码泄露的风险
  • 稍微优雅的一种方式,就是我们事先在外部将项目及其依赖库编译测试打包好后,再将其拷贝到构建目录中,这种虽然可以很好地规避第一种方式存在的风险点,但仍需要我们编写两套Dockerfile或者一些脚本才能将其两个阶段自动整合起来

这里以一个简单的JAVA项目镜像构建为例来说明这两种构建方式,该项目仅仅包含一个App.java类:

public class App {
   

    public static void main(String[] args) {
   
        System.out.println("Hello, multi-stage build...");
    }

}
  • 采用最简单的方式
FROM dojomadness/alpine-jdk8-maven:latest

# add pom.xml and source code
ADD ./pom.xml pom.xml
ADD ./src src/

# package jar and remove source code and temporary class files
RUN mvn clean package && cp -f target/msb-1.0.jar msb.jar && rm -rf pom.xml src/ target/

# run jar
CMD ["java", "-jar", "msb.jar"]

这里是将所有的构建流程都编写在同一个Dockerfile中,但为了尽可能地减小镜像层次,我们将多个执行命令合并到同一个RUN指令中,同时我们需要自行清理掉源码目录文件以及编译后的临时目录文件,以防止源码泄露。

  • 采用稍优雅的方式

这里我们需要分为两个阶段:
1)编译打包 Dockerfile.2.1

FROM maven:3.5.0-jdk-8-alpine

# add pom.xml and source code
ADD ./pom.xml pom.xml
ADD ./src src/

# package jar
RUN mvn clean package

2)构建镜像 Dockerfile.2.2

From openjdk:8-jre-alpine

# copy jar
COPY ./msb.jar msb.jar

# run jar
CMD ["java", "-jar", "msb.jar"]

3)整合两个构建阶段 build.sh

#!/bin/bash

# First stage: complete build environment
docker build -t msb:build -f Dockerfile.2.1 .
# create temporary container
docker create --name extract msb:build
# extract jar
docker cp extract:/target/msb-1.0.jar ./msb.jar
# remove temporary container
docker rm -f extract

# Second stage: minimal runtime environment
docker build --no-cache -t msb:build-2 -f Dockerfile.2.2 .

# remove local jar
rm -rf ./msb.jar

这里我们编写了两个Dockerfile和一个Shell脚本,通过两阶段将最终镜像构建出来,但可预见的一个问题是,假若有多个项目彼此关联和依赖,就需要我们维护多个Dockerfile,或者需要编写更复杂的build.sh脚本,导致后期维护成本很高。

支持多阶段构建

在Docker 17.05 多阶段构建推出之后,我们就可以很容易规避前面遇到的这些问题,并且只需要维护一个Dockerfile即可:

# First stage: complete build environment
FROM maven:3.5.0-jdk-8-alpine AS builder
# add pom.xml and source code
ADD ./pom.xml pom.xml
ADD ./src src/
# package jar
RUN mvn clean package

# Second stage: minimal runtime environment
From openjdk:8-jre-alpine
# copy jar from the first stage
COPY --from=builder target/msb-1.0.jar msb.jar
# run jar
CMD ["java", "-jar", "msb.jar"]

对于上面的Dockerfile是不是很熟悉,它仅仅是将第二种方式中的两个Dockerfile合并到同一个Dockerfile中,同时在构建过程中自动帮您完成了build.sh的流程。

对于multi-stage build,其关键点主要有两点:

  • 在前面阶段的FROM指令后面增加了一个AS参数,可为该构建阶段命名,便于后续构建阶段引用,格式如下:

    FROM image[:tag | @digest] AS stage
    
  • 在后续阶段的COPY指令后面增加了--from参数,指明引用前面哪一个构建阶段的成果,格式如下:

    COPY --from=stage ...
    

同理,多阶段构建同样可以很方便地将多个彼此依赖的项目通过一个Dockerfile就可轻松构建出期望的容器镜像,而不用担心镜像太大、源码泄露等风险。

aliyun_jpeg容器镜像服务

目录
相关文章
|
6月前
|
监控 Linux 调度
【赵渝强老师】Docker容器的资源管理机制
本文介绍了Linux CGroup技术及其在Docker资源管理中的应用。通过实例演示了如何利用CGroup限制应用程序的CPU、内存和I/O带宽使用,实现系统资源的精细化控制,帮助理解Docker底层资源限制机制。
605 6
|
存储 缓存 运维
【Docker 专栏】Docker 镜像的分层存储与缓存机制
【5月更文挑战第8天】Docker 镜像采用分层存储,减少空间占用并提升构建效率。每个镜像由多个层组成,共享基础层(如 Ubuntu)和应用层。缓存机制加速构建和运行,通过检查已有层来避免重复操作。有效管理缓存,如清理无用缓存和控制大小,可优化性能。分层和缓存带来资源高效利用、快速构建和灵活管理,但也面临缓存失效和层管理挑战。理解这一机制对开发者和运维至关重要。
1204 8
【Docker 专栏】Docker 镜像的分层存储与缓存机制
|
存储 安全 数据中心
【Docker 专栏】Docker 容器与宿主机的资源隔离机制
【5月更文挑战第8天】Docker容器利用Namespace和Cgroups实现资源隔离,保证CPU、内存、网络和存储的独立,提升资源利用率和系统安全性。资源隔离有助于简化应用部署与管理,但也带来资源竞争、监控管理及安全挑战。理解并善用资源隔离机制能实现更高效、安全的容器运行。随着技术进步,Docker容器资源隔离将持续优化。
1440 2
【Docker 专栏】Docker 容器与宿主机的资源隔离机制
|
API 开发者 网络架构
Docker的运行机制
Docker的运行机制
405 0
|
存储 Kubernetes Linux
总结下docker中数据的存储管理机制
总结下docker中数据的存储管理机制
|
资源调度 Linux Shell
docker-资源限制:如何通过 Cgroups 机制实现资源限制?
我们知道使用不同的 Namespace,可以实现容器中的进程看不到别的容器的资源,但是有一个问题你是否注意到?
477 0
|
存储 NoSQL 关系型数据库
【Docker学习笔记 五】深入理解Docker容器数据卷机制
【Docker学习笔记 五】深入理解Docker容器数据卷机制
948 0
|
Docker 容器
《我的Docker:Docker插件机制详解》电子版地址
我的Docker:Docker插件机制详解
157 0
《我的Docker:Docker插件机制详解》电子版地址
|
网络协议 关系型数据库 MySQL
深入理解docker的link机制
什么是docker的link机制 同一个宿主机上的多个docker容器之间如果想进行通信,可以通过使用容器的ip地址来通信,也可以通过宿主机的ip加上容器暴露出的端口号来通信,前者会导致ip地址的硬编码,不方便迁移,并且容器重启后ip地址会改变,除非使用固定的ip,后者的通信方式比较单一,只能依靠监听在暴露出的端口的进程来进行有限的通信。通过docker的link机制可以通过一个name来和另一
17686 0