在你得到正确的容器配置时须要进行几回迭代?你每次迭代须要多长时间?好吧,若是回答“太屡次且时间太长”,那么个人经历与你很类似。从表面上看,建立配置文件彷佛很简单:在配置文件中实现与手动安装系统时要执行的步骤相同。不幸的是,我发现这种方法一般没法正常工做,而且一些“技巧”对于此类DevOps练习很是有用。python
在本文中,我将分享一些新发现的技术。这些技术能够帮助你最大程度地减小迭代次数和迭代时间。另外,我将概述一些标准作法之外的调优作法。docker
节省时间在容器映像构建迭代上ubuntu
若是Dockerfile涉及下载并安装5GB的文件,则即便具备良好的网络速度,每次Docker 映像构建迭代均可能会花费大量时间。忘记包含要安装的项目可能意味着在此以后重建全部层。bash
解决这一难题的一种方法是使用本地HTTP服务器,以免在Docker映像构建迭代期间屡次从互联网下载大文件。为了举例说明,假设您须要在Ubuntu 18.04下使用Anaconda 3建立一个容器映像。Anaconda 3安装程序的文件大小约为0.5GB,所以在此示例中称为“大”文件。服务器
请注意,若是不想使用COPY指令,由于它会建立一个新层。使用大型安装程序后,还应删除它,以最小化容器映像的大小。您可使用多阶段构建,可是我发现如下方法足够有效。网络
其基本思想是使用基于Python的HTTP服务器在本地,以服务大文件(S),并有Dockerfile wget的,从这个本地服务器的大文件(S)。让咱们探索如何有效设置它的细节。工具
在此示例存储库中,文件夹tutorial2_docker_tricks /的必要内容是:ui
tutorial2_docker_tricks/├── build_docker_image.sh# builds the docker image├── run_container.sh# instantiates a container from the image├── install_anaconda.dockerfile# Dockerfile for creating our target docker image├── .dockerignore# used to ignore contents of the installer/ folder from the docker context├── installer# folder with all our large files required for creating the docker image│ └── Anaconda3-2019.10-Linux-x86_64.sh# from https://repo.anaconda.com/archive/Anaconda3-2019.10-Linux-x86_64.sh└── workdir# example folder used as a volume in the running container编码
该方法的关键步骤是:设计
步骤1:将大文件放在安装程序/文件夹中。在此示例中,我具备大型Anaconda安装程序文件Anaconda3-2019.10-Linux-x86_64.sh。若是克隆个人Git存储库,则找不到该文件,由于只有您(做为容器映像建立者)须要此源文件。图像的最终用户没有。下载安装程序以跟随示例。
步骤2:建立.dockerignore文件,并使其忽略installer /文件夹,以免Docker将全部大文件复制到构建上下文中。
步骤3:在终端中,cd进入tutorial2_docker_tricks /文件夹,并以./build_docker_image.sh执行构建脚本。
步骤4:在build_docker_image.sh中,启动Python HTTP服务器以提供来自installer /文件夹的任何文件:
cdinstallerpython3-mhttp.server--bind10.0.2.158888 &cd..
步骤5:若是您想知道奇怪的Internet协议(IP)地址,我正在使用VirtualBox Linux VM,当我运行ifconfig时,10.0.2.15显示为以太网适配器的地址。该IP彷佛是VirtualBox使用的约定。若是设置不一样,则须要更新此IP地址以匹配您的环境,而后更新build_docker_image.sh和install_anaconda.dockerfile。在此示例中,服务器的端口号设置为8888。请注意,IP和端口号能够做为构建参数传递,可是为了简洁起见,我对其进行了硬编码。
步骤6:因为HTTP服务器设置为在后台运行,请 使用我发现的一种简洁的方法使用kill -9命令在脚本末尾附近中止服务器:
kill-9`ps -ef | grep http.server | grep8888| awk'{print$2}'
步骤7:请注意, 在脚本的前面(启动HTTP服务器以前)也使用了一样的kill -9。一般,当我迭代可能会故意中断的任何构建脚本时,这能够确保每次HTTP服务器都干净启动。
步骤8:在Dockerfile中,有一条RUN wget 指令,该指令从本地HTTP服务器下载Anaconda安装程序。它还会删除安装程序文件并在安装后进行清理。最重要的是,全部这些操做都在同一层中执行,以将图像大小保持为最小:
# install Anaconda by downloading the installer via the local http serverARGANACONDARUNwget --no-proxy http://10.0.2.15:8888/${ANACONDA} -O ~/anaconda.sh \&&/bin/bash ~/anaconda.sh -b -p /opt/conda \&&rm ~/anaconda.sh \&&rm -fr /var/lib/apt/lists/{apt,dpkg,cache,log} /tmp/* /var/tmp/*
步骤9:该文件运行包装程序脚本anaconda.sh,并经过使用rm删除大型文件来清理它们
步骤10:构建完成后,您应该看到图像anaconda_ubuntu1804:v1。(您可使用docker image ls列出图像。)
步骤11:您可使用终端中的./run_container.sh从该映像实例化一个容器,而该文件夹位于tutorial2_docker_tricks /文件夹中。您能够验证Anaconda已安装:
$./run_container.sh$python --versionPython 3.7.5$conda --versionconda 4.8.0$anaconda --versionanaconda Command line client (version 1.7.2)
步骤12:您会注意到,run_container.sh设置了一个卷workdir。在此示例存储库中,文件夹workdir /为空。这是我用来设置卷的约定,能够在其中使个人Python和其余脚本独立于容器映像。
最小化容器图像大小
每一个RUN命令等效于执行一个新的Shell,每一个RUN命令建立一个层。用单独的RUN命令模仿安装指令的幼稚方法最终可能会在一个或多个相互依赖的步骤中中断。若是碰巧能够正常工做,一般会产生较大的图像。在一个RUN命令中连接多个安装步骤,包括autoremove,autoclean和rm命令(以下面的示例所示)对于最小化每一个图层的大小颇有用。根据所安装的内容,可能不须要其中一些步骤。可是,因为这些步骤花费的时间很少,所以,在调用apt-get的RUN命令结束时,我老是将它们投入适当的时间:
RUN apt-getupdate\ && DEBIAN_FRONTEND=noninteractive \apt-get-y--quiet --no-install-recommends install \# list of packages being installed go here \&& apt-get-y autoremove \&& apt-getclean autoclean \&& rm -fr /var/lib/apt/lists/{apt,dpkg,cache,log} /tmp/* /var/tmp/*
另外,请确保您有一个.dockerignore文件,以忽略不须要发送到Docker构建上下文的项目(例如前面示例中的Anaconda安装程序文件)。
组织构建工具I/O
对于软件构建系统,构建输入和输出(配置和调用工具的全部脚本)应在映像和最终运行的容器以外。容器自己应保持无状态,以便不一样的用户能够获得相同的结果。在上一篇文章中,我对此进行了普遍的介绍,但因为它对个人工做很是有用,所以我想强调一下。最好经过设置容器体积来访问这些输入和输出。
我不得不使用一个容器映像,该映像以源代码和大型预构建二进制文件的形式提供数据。做为软件开发人员,我但愿在容器中编辑代码。这是有问题的,由于容器默认状况下是无状态的:它们不被保存在容器内,由于它们被设计为可抛弃的。
可是我一直在努力,在天天结束时,我中止了容器,而且必须当心不要将其卸下,由于必须保持状态,以便我次日才能继续工做。这种方法的缺点是,若是有不止一我的在项目上工做,那么开发状态就会有分歧。这种方法在开发人员中拥有相同的构建系统的价值在某种程度上已经丧失了。
以非root用户身份生成输出
I / O的重要方面涉及在容器中运行工具时生成的输出文件的全部权。默认状况下,因为Docker以root身份运行,所以输出文件将归root拥有,这是不愉快的。您一般但愿以非root用户身份工做。生成构建输出后更改全部权可使用脚原本完成,但这是一个额外且没必要要的步骤。
最好尽早在Dockerfile中设置USER参数:
ARGUSERNAME# other commands...USER${USERNAME}
该USERNAME能够做为构建参数(在传递--build精氨酸)执行时搬运工图像构建。您能够在示例Dockerfile和相应的构建脚本中看到一个示例。
工具的某些部分可能还须要以非root用户身份安装。所以,若是要直接在Linux下手动安装,则Dockerfile中的安装顺序可能须要与安装顺序不一样。
非交互式安装
交互性与容器自动化相反。我发现了
DEBIAN_FRONTEND=noninteractive apt-get -y --quiet --no-install-recommends
防止安装程序打开对话框所必需的apt-get安装说明选项(如上例所示)。注意,这些选项应做为RUN指令的一部分使用。正如本常见问题解答所解释的那样,不该在Dockerfile中将DEBIAN_FRONTEND = noninteractive设置为环境变量(ENV),由于它将由容器继承。
记录您的构建并运行输出
调试构建失败的缘由是一项常见的任务,而日志则是完成此任务的好方法。使用Bash脚本中的tee工具保存在容器映像构建或容器运行会话期间发生的全部事情的TypeScript 。换句话说,将|&tee $ BASH_SOURCE.log添加到Docker 映像构建的末尾,而且在脚本中运行docker image运行命令。请参阅映像构建和容器运行脚本中的示例。
这种发球技术的做用是生成一个与Bash脚本同名的文件,但 附加一个.log扩展名,以便您知道其起源于哪一个脚本。运行脚本时,您在终端上看到的全部内容都将以相似的名称记录到该文件中。
这对于容器映像的用户在没法解决问题时向您报告问题特别有价值。您能够要求他们向您发送日志文件以帮助诊断问题。许多工具生成的输出太多,以致于很容易使终端缓冲区的默认大小不堪重负。仅依靠终端的缓冲区容量来复制粘贴错误消息可能不足以诊断问题,由于之前的错误可能已经丢失。
我发现即便在容器映像构建脚本中,这也颇有用,尤为是在使用上面讨论的基于Python的HTTP服务器时。服务器在下载过程当中生成了不少行,一般使终端的缓冲区不堪重负。
优雅地处理代理
在个人工做环境中,须要代理访问Internet才能下载RUN apt-get和RUN wget命令中的资源。一般从环境变量http_proxy或https_proxy推断代理。虽然可使用ENV命令在Dockerfile中对此类代理设置进行硬编码,可是直接将ENV用于代理存在多个问题。
若是您是惟一能够构建容器的人,那么也许可使用。
可是,其余位置不一样的代理设置的其余人没法使用Dockerfile。另外一个问题是IT部门可能会在某个时候更改代理,从而致使Dockerfile再也不起做用。此外,Dockerfile是一个精确的文档,指定了配置控制的系统,而且每一项更改都将经过质量保证进行审查。
一种避免对代理进行硬编码的简单方法是,将本地代理设置做为docker image build 命令中的build参数传递:
docker image build \
--build-arg MY_PROXY=http://my_local_proxy.proxy.com:xx
而后,在Dockerfile中,根据build参数设置环境变量。在此处显示的示例中,您仍然能够设置默认的代理值,该值能够被上面的build参数覆盖:
# set a default proxyARGMY_PROXY=MY_PROXY=http://my_default_proxy.proxy.com:nn/ENV http_proxy=$MY_PROXYENV https_proxy=$MY_PROXY
总结
这些技术帮助我大大减小了建立容器映像并在出现错误时对其进行调试所需的时间。我会继续寻找其余最佳作法,以添加到个人列表中。但愿以上技巧对您有所帮助。