如何使用Docker构建运行时间较长的脚本

简介: 本文讲的是如何使用Docker构建运行时间较长的脚本,【编者的话】在工作中,可能会碰到一些会运行很长时间的脚本,这些脚本构建起来会非常麻烦,可能在构建一半后突然失败,我们又得重头再来。文章介绍了如何使用Docker解决这类问题。
本文讲的是如何使用Docker构建运行时间较长的脚本 【编者的话】在工作中,可能会碰到一些会运行很长时间的脚本,这些脚本构建起来会非常麻烦,可能在构建一半后突然失败,我们又得重头再来。文章介绍了如何使用Docker解决这类问题。

我想我已经找到了一个非常不错的Docker使用案例。你是不是会觉得这是一篇写Docker有多好多好的文章,开始之前我想和你确认,这篇文章会介绍如何把文件系统作为持久性的数据结构。

因此,这篇文章的见解同样适用于其他的  copy-on-write文件系统 ,如 BTRFS ZFS

问题

让我们从这个我试图解决的问题开始。我开发了一个会运行很长时间的构建脚本,这个脚本中包含了很多的步骤。
  • 这个脚本会运行1-2个小时。
  • 它会从网络下载比较大的文件(超过300M)。
  • 后面的构建步骤依赖前期构建的库。

但最最烦人的是,运行这个脚本真的需要花很长的时间。

文件系统是固有状态

我们一般是通过一种有状态的方式与文件系统进行交互的。我们可以添加、删除或移动文件。我们可以修改文件的权限或者它的访问时间。大部分独立的操作都可以撤销,例如将文件移动到其它地方后,你可以将文件恢复到原来的位置。但我们不会通过快照的方式来将它恢复到原始状态。这篇文章我将会介绍如何在耗时较长的脚本中充分利用快照这一特性。

使用联合文件系统的快照

Docker使用的是联合文件系统叫做 AUFS (译者注:简单来说就是支持将不同目录挂载到同一个虚拟文件系统下的文件系统)。联合文件系统实现了 Union mount 。顾名思义,也就是说不同的文件系统的文件和目录可以分层叠加在单个连贯文件系统之上。这是通过分层的方式完成的。如果一个文件出现在两个文件系统,那最高层级的文件才会显示(该文件其它版本也是存在于层级中的,不会改变,只是看不到的)。

在Docker中,每一个在Union mount转哦给你的文件系统都被称为 layers(层) 。使用这种技术可以轻松实现快照,每个快照都是所有层的一个Union mount。

生成脚本的快照

使用快照可以帮助构建一个长时运行的脚本。总的想法是,将一个大的脚本分解为许多小的脚本(我喜欢称之为scriptlets),并单独运行这些小的脚本,脚本运行后为其文件系统打一个快照 (Docker会自动执行此操作)。如果你发现一个scriptlet运行失败,你可以快速回退到上次的快照,然后再试一次。一旦你完成脚本的构建,并且可以保证脚本能正常工作,那你就可以将它分配给其它主机。

回过头来再对比下,如果你没有使用快照功能了?当你辛辛苦苦等待了一个半小时后,脚本却构建失败了,我想除了少部分有耐心的人外,很多人是不想再来一次了,当然,你也会尽最大努力把系统恢复到失败前的状态,比如可以删除一个目录或运行 make clean

但是,我们可能没有真正地理解我们正在构建的组件。它可能有复杂的Makefile,它会把把文件放到文件系统中我们不知道的地方,唯一真正确定的途径是恢复到快照。

使用快照构建脚本的Docker

在本节中,我将介绍我是如何使用Docker实现 GHC 7.8.3 ARM交叉编译器的构建脚本。Docker非常适合做这件事,但并非完美。我做了很多看起来没用的或者不雅的事情,但都是必要的,这都是为了保证将开发脚本的总时间降到最低限度。构建脚本可以在 这里 找到。
用Dockerfile构建
Docker通过读取Dockerfile来构建镜像。Dockerfile会通过一些命令来具体指定应该执行哪些动作。具体使用说明可以参考这篇 文章 。在我的脚本中主要用到 WORKDIR ADD RUN ADD 命令非常有用因为它可以让你在运行之前将外部文件添加到当前Docker镜像中然后转换成镜像的文件系统。你可以在 这里 看到很多scriptlets构成的构建脚本。
设计
1. 在RUN之前ADD scriptlets

如果你很早就将所有的scriptlets ADD 在Dockerfile,您可能会遇到以下问题:如果你的脚本构建失败,你回去修改scriptlet并再次运行 docker build . 。但是你发现,Docker开始在首次加入scriptlets的地方构建!这样做会浪费了大量的时间并且违背了使用快照的目的。

出现这种情况的原因是由于Docker处理它的中间镜像(快照)的方式。当Docker通过Dockerfile构建镜像时,它会与中间镜像比较当前命令是否一致。然而,在 ADD 命令的情况下被装进镜像的文件里的内容也会被检查。如果相对于现有的中间镜像,文件已经改变,那么Docker也别无选择,只能从这点开始建立一个新的镜像。因为Docker不知道这些变化会不会影响到构建。

此外,使用 RUN 命令要注意,每次运行时它都会导致文件系统有不同的更改。在这种情况下,Docker会发现中间镜像并使用它,但是这将是错误的。 RUN 命令每次运行时会造成文件系统相同的改变。举个例子,我确保在我的scriptlets我总是下载了一个已知版本的文件与一个特定MD5校验。

对Docker 构建缓存更详细的解释可以在 这里 找到。

2.不要使用ENV命令来设置环境变量,请使用scriptlet。

它似乎看起来很有诱惑力:使用 ENV 命令来设置所有构建脚本需要的环境变量。但是,它不支持变量替换的方式,例如 ENV BASE=$HOME/base 将设置BASE的值为$HOME/base着很可能不是你想要的。

相反,我用 ADD 命令添加一个名为 set-env.sh 文件。此文件会包含在后续的scriptlet中:
THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source $THIS_DIR/set-env-1.sh


如果你没有在第一时间获取 set-env.sh 会怎么样呢?它很早就被加入Dockerfile并不意味着修改它将会使随后的快照无效?

是的,这会有问题。在开发脚本时,我发现,我已经错过了在 set-env.sh 添加一个有用的环境变量。解决方案是创建一个新的文件 set-env-1.sh 包含:
THIS_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
source $THIS_DIR/set-env.sh
if ! [ -e "$CONFIG_SUB_SRC/config.sub" ] ; then
CONFIG_SUB_SRC=${CONFIG_SUB_SRC:-$NCURSES_SRC}
fi


然后,在所有后续的scriptlets文件中包含了此文件。现在,我已经完成了构建脚本,我可以回去解决这个问题了,但是,在某种意义上,它会破坏最初的目标。我将不得不从头开始运行构建脚本看看这种变化是否能成功。

缺点

一个主要缺点是这种方法是,所构建的镜像尺寸是大于它实际需求的尺寸。在我的情况下尤其如此,因为我在最后删除了大量文件的。然而,这些文件都仍然存在于联合挂载文件系统的底层文件系统内,所以整个镜像是大于它实际需要的大小至少多余的是删除文件的大小。

然而,有一个变通。我没有公布此镜像到 Docker Hub Registry 。相反,我:
  • 使用docker export导出内容为tar文件。
  • 创建一个新的Dockerfile简单地添加了这个tar文件的内容。

产生尺寸尽可能小的镜像。

结论

这种方法的优点是双重的:
  • 它使开发时间降至最低,不再做那些已经构建成功的子组件。你可以专注于那些失败的组件。
  • 这非常便于维护构建脚本。构建可能会失败,但只要你搞定Dockerfiel,至少你不必再从头开始。

此外,正如我前面提到的Docker不仅使写这些构建脚本更加容易,有了合适的工具同样可以在任何提供快照的文件系统实现。

原文链接:File system snapshots make build scripts easy (翻译:田浩浩 审校:李颖杰)

===========================
译者介绍
田浩浩 悉尼大学USYD 硕士研究生,目前在珠海从事Android应用开发工作。业余时间专注Docker的学习与研究,希望通过 DockerOne 把最新最优秀的译文贡献给大家,与读者一起畅游Docker的海洋。

原文发布时间为:2014-12-28
本文作者:田浩浩 
本文来自云栖社区合作伙伴DockerOne,了解相关信息可以关注DockerOne。
原文标题:如何使用Docker构建运行时间较长的脚本
目录
相关文章
|
18天前
|
缓存 安全 Docker
《Docker 简易速速上手小册》第3章 Dockerfile 与镜像构建(2024 最新版)
《Docker 简易速速上手小册》第3章 Dockerfile 与镜像构建(2024 最新版)
46 0
|
1天前
|
缓存 安全 数据安全/隐私保护
【Docker专栏】深入理解Docker镜像的构建与推送
【5月更文挑战第7天】本文介绍了Docker镜像的核心作用及基础概念,包括镜像作为容器模板的特性。文章详细阐述了Dockerfile的编写,例如设置基础镜像、工作目录、安装依赖及定义启动命令。通过`docker build`命令构建镜像,并提示了优化构建过程的技巧。此外,还讲解了如何将镜像推送到远程仓库,包括选择仓库、认证、标签和推送镜像的步骤,以及镜像安全性的考虑,如扫描漏洞和遵循最小权限原则。本文旨在帮助读者掌握Docker镜像的构建与推送,以高效管理容器化应用。
【Docker专栏】深入理解Docker镜像的构建与推送
|
1天前
|
应用服务中间件 持续交付 nginx
【Docker专栏】Docker入门指南:快速构建你的第一个容器
【5月更文挑战第7天】Docker 入门指南:容器化应用利器。了解 Docker 核心概念——镜像、容器和仓库。安装 Docker 后,运行官方 `hello-world` 验证安装,再尝试运行 `nginx` Web 服务器。通过端口映射访问容器内服务,学习管理容器命令。创建自定义镜像,编写 Dockerfile,实现 Python Web 应用容器化。Docker 助力高效开发与运维,探索更多自动化部署与微服务场景。
【Docker专栏】Docker入门指南:快速构建你的第一个容器
|
1天前
|
运维 负载均衡 持续交付
构建高效自动化运维体系:Ansible与Docker的协同实践
【5月更文挑战第7天】 在当今快速迭代的软件开发环境中,自动化运维成为确保部署效率和一致性的关键。本文将探讨如何通过结合Ansible和Docker技术,构建一个高效的自动化运维体系,旨在提升运维效率,减少人为错误,并实现持续集成与持续部署(CI/CD)的流程自动化。文章详细阐述了Ansible的配置管理机制、Docker容器化的优势,以及二者在实际运维场景中的结合应用,为读者提供一套可行的自动化运维解决方案。
|
4天前
|
Kubernetes 监控 Docker
构建高效微服务架构:Docker与Kubernetes的完美搭档
【5月更文挑战第4天】在现代软件开发中,微服务架构已成为实现可扩展、灵活且独立部署服务的流行解决方案。本文将探讨如何利用Docker容器化技术和Kubernetes容器编排平台来构建一个高效的微服务系统。我们将分析Docker和Kubernetes的核心优势,并指导读者如何通过这些工具优化微服务部署、管理和扩展过程。文章还将涉及监控和日志管理策略,以确保系统的健壮性和可靠性。
|
7天前
|
机器学习/深度学习 运维 持续交付
构建高效自动化运维体系:Ansible与Docker的完美结合构建高效机器学习模型的五大技巧
【4月更文挑战第30天】 在当今快速发展的云计算和微服务架构时代,自动化运维已成为维持系统稳定性和提高效率的关键。本文将探讨如何通过结合Ansible和Docker技术构建一个高效的自动化运维体系。文章不仅介绍了Ansible与Docker的基本原理和优势,还详细阐述了如何整合这两种技术以简化部署流程、加强版本控制,并提高整体运维效率。通过案例分析,我们将展示这一组合在实际环境中的应用效果,以及它如何帮助企业实现持续集成和持续部署(CI/CD)的目标。 【4月更文挑战第30天】 在数据驱动的时代,构建一个高效的机器学习模型是获取洞察力和预测未来趋势的关键步骤。本文将分享五种实用的技巧,帮助数
|
7天前
|
弹性计算 Shell 数据安全/隐私保护
自动化构建和部署Docker容器
【4月更文挑战第30天】
11 0
|
8天前
|
运维 安全 数据安全/隐私保护
构建高效自动化运维体系:Ansible与Docker的协同实践
【4月更文挑战第29天】 在当今IT基础设施快速演变的背景下,自动化成为维护系统稳定性和提升运维效率的关键。本文将深入探讨如何利用Ansible和Docker技术搭建一个高效的自动化运维体系。通过剖析Ansible的配置管理功能与Docker容器化的优势,我们展示了一种能够实现快速部署、轻松管理和无缝扩展的自动化解决方案。文章还将分享一系列优化策略,以期帮助读者构建出既灵活又强大的自动化工具链。
|
8天前
|
Kubernetes 监控 Docker
|
8天前
|
Java Docker 微服务