Docker技术

Docker技术

当前位置:网站首页 > 虚拟化与容器 > Docker技术 > 正文

如何构建高效率的 Docker 镜像

pixiubiu 2019-01-10 666 0

今天要学习主题是:如何构建高效的 Docker 镜像, 更小,更快!今天学习的大纲如下。

  • Docker 镜像和容器的层的工作原理

  • 构建最小镜像的基础知识

  • 什么是好的、坏的和臃肿的 Dokcerfile

  • 如何构建面向特定编程语言环境的镜像

主要参考 Dockercon 大会 @abbyfuller 的 “Creating Effective Images” 演讲。 大会视频

0x01 Docker 容器的 layer

镜像(images)与层(layers):

Docker 镜像是由多个文件系统(只读层)叠加而成,每个层仅包含了前一层的差异部分。当我们启动 一个容器的时候,Docker 会加载镜像层并在其上添加一个可写层。容器上所做的任何更改,比如新建文件、更改文件、删除文件,都将记录与可写层上1。容器层与镜像层的结构如下图所示。(这段话的来源)

   

容器层与镜像层的结构图

容器与层级的联系

容器与镜像最大的区别就在于可写层上。如果运行中的容器修改了现有的一个已存在的文件,那该文件 将会从可写层下的只读层复制到可写层,该文件的只读版本仍然存在,只是已经被可写层中该文件的副 本所隐藏。其中,多个容器共享镜像的结构如下所示。

   

容器层与镜像层的结构图

0x02 如何构建最小镜像

问题: 构建的层数越多真的好吗?

更多的层意味着你的构建的 镜像就越大,同时也意味着你构建的时间就越长,并且你 pull 到本地 或者 push 到 registry 的时间就越长。 通常来讲,小镜像的层数都是比较少的,他会占用你很少的资源。

那么如何减小镜像的体积呢?

我们在构建自己的镜像时,可能都会使用一些 base image (基础镜像)来开始构建,比如

FROM ubuntu:14.04

等等, 这些镜像可能是官方给出的,或者是别人构建的,那么这些基础镜像我们真的选对了吗?除了合理的选择基础镜像意外,还需要做到如下几点:

  • 第一、限制写入容器的数据

  • 第二、 链式化 RUN 语句

  • 第三、尽可能出现防止在构建的时候 cache 未命中的情况

认识 Dockerfile

什么是Dockerfile?Dockerfile 是记录着你构建镜像的一系列的操作步骤。

首先我们先认识一下 Dockerdfile, 下面的 Dockerfile 主要是 构建一个 python 的应用程序。

FROM ubuntu:latest
LABEL maintainer abbyfull@amazon.com
RUN apt-get update -y && apt-get install -y python-pip python-dev build-essential
COPY . /app
WORKDIR /app
RUN pip install –r requirements.txt
EXPOSE 5000
ENTRYPOINT ["python"]
CMD ["application.py"]

优化

我们要根据上面的规则,开始优化它!

第一步,选择一个合适的基础镜像,使用不同基镜像构建出来的镜像大小差距还是很悬殊的。

ubuntu latest 2b1dc137b502
52 seconds ago 458 MB
From python:2.7-alpine:
alpine latest d3145c9ba1fa
2 minutes ago 86.8 MB

从上面可以看出,使用 ubuntu 作为基础镜像构建的应用镜像是 458MB,而使用 python:2.7-alpine 作为基础镜像构建的镜像才 85.8MB。因此选择一个合适的镜像是非常重要的一步。

那么这些基础镜像原来有多大呢,下面列出了常用到的基础镜像的大小:

   

常用的基础镜像列表

那么什么是时候使用一个具有完整操作系统功能的镜像,比如ubuntu,centos等镜像呢?需要满足几点,建议使用。

  • 安全性

  • 开发

  • 部署大型应用程序,分布式应用程序

下面是使用ubuntu镜像构建的程序:

FROM ubuntu:latest
RUN apt-get update -y && apt-get install -y python-pip python-dev build-essential
COPY . /app
WORKDIR /app
RUN pip install –r requirements.txt
EXPOSE 5000
ENTRYPOINT ["python"]
CMD ["application.py"]

下面是使用 python 镜像构建的:

FROM python:2.7-alpine
COPY . /app
WORKDIR /app
RUN pip install –r requirements.txt
EXPOSE 5000
ENTRYPOINT ["python"]
CMD ["application.py"]

可以发现使用 python 镜像的Dockerfile 更加简单,并且避免了安装了 python 环境,因为 python 镜像已经整合了 python 的开发环境。

等等!我们还可以改进它!我们可以发现 COPY . /app 会占用我们很多的缓存资源,因为我们只用了requirements.txt 文件,记住:更少的缓存利用,说明你构建的镜像会更小。改进后的 Dockerfile如下:

FROM python:2.7-alpine
COPY requirements.txt /app
RUN pip install –r /app/requirements.txt
COPY . /app
WORKDIR /app
EXPOSE 5000
ENTRYPOINT ["python"]
CMD ["application.py"]

接下来我们将使用 ONBUILD 指令来更好的重用我们的环境,使后期开发更加高效。

The ONBUILD instruction adds to the image a trigger instruction to be executed at a later time, when the image is used as the base for another build. The trigger will be executed in the context of the downstream build, as if it had been inserted immediately after the FROM instruction in the downstream Dockerfile.

Any build instruction can be registered as a trigger.

This is useful if you are building an image which will be used as a base to build other images, for example an application build environment or a daemon which may be customized with user-specific configuration.

For example, if your image is a reusable Python application builder, it will require application source code to be added in a particular directory, and it might require a build script to be called after that. You can’t just call ADD and RUN now, because you don’t yet have access to the application source code, and it will be different for each application build. You could simply provide application developers with a boilerplate Dockerfile to copy-paste into their application, but that is inefficient, error-prone and difficult to update because it mixes with application-specific code.

FROM python:2.7-alpineONBUILD ADD requirements.txt /appONBUILD RUN pip install –r /app/requirements.txtONBUILD COPY . /appWORKDIR /appEXPOSE 5000ENTRYPOINT ["python"]CMD ["application.py"]

0x03 Good/Bad/Bloated Dockerfile

玩个例子

现在让我们看个大一点的 Dockerfile,记住镜像的层越多越大!我们可以数一数它的行数!必须优化它!

FROM ubuntu:latest
RUN apt-get update -y
RUN apt-get install -y python-pip python-dev build-essential
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
EXPOSE 5000
ENTRYPOINT ["python"]
CMD ["application.py"]

可以看到两个连续的 RUN 指令。

RUN apt-get update -y
RUN apt-get install -y python-pip python-dev build-essential

我们可以链式 RUN 指令!

FROM ubuntu:latest
RUN apt-get update -y && apt-get install -y python-pip python-dev build-essential –no-install-recommends
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
EXPOSE 5000
ENTRYPOINT ["python"]
CMD ["application.py"]

现在尝试一下其他的基础镜像,比如 python 镜像:

FROM python:2.7-alpine
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
EXPOSE 5000
ENTRYPOINT ["python"]
CMD ["application.py"]

更高效的使用 RUN 指令

RUN apt-get update && apt-get install -y \ aufs-tools \
  automake \ 
  build-essential \ 
  ruby1.9.1 \
  ruby1.9.1-dev \
  s3cmd=1.1.* \
&& rm -rf /var/lib/apt/lists/*

在使用 RUN 指令时,需要注意语句的顺序很重要!

避免使用 ADD 指令添加大文件

下面是一种不推荐的方法,因为如果添加大文件,不避免地会使用很大的缓存,劲儿会使得我们得到的镜像会很大。

ADD http://cruft.com/bigthing.tar.xz /app/cruft/
RUN tar -xvf /app/cruft/bigthing.tar.xz -C /app/cruft/
RUN make -C /app/cruft/ all

更好推荐方案是将你要添加的文件放在互联网上,使用 curl 或者 wget 下载,最好避免使用 ADD。

RUN mkdir -p /app/cruft/ \
    && curl -SL http://cruft.com/bigthing.tar.xz \ | tar -xJC /
app/cruft/ && make -C /app/cruft/ all

0x04 构建面向 Golang 的 Dockerfile

有两种构建特定语言的 Docker 镜像方式:

  • 从头开始构建

  • 从一个基础镜像开始构建

    • 选择基础镜像有一点说明:编程语言官方镜像可能会比较大,但是稳定。自己构建的镜像可能小但是不一定稳定,需要平衡选择。

下面会讲解 Golang 应用程序的构建方法。

稍微熟悉 Golang 的同学都应噶了解,Go 的执行方式很简单,只需要一个二进制文件即可,这也意味着构建的 Docker 镜像会非常的简单。

步骤:编译,然后拷贝编译好的二进制文件

go build -o dockercon .
docker build -t dockercon .

Dockerfile 文件内容为:

FROM scratch
COPY ./dockercon /dockercon
ENTRYPOINT ["/dockercon"]

scratch 镜像非常特殊,构建它的 Dockerfile 是空的。一般开发人员喜欢使用 scratch 镜像构建收于自己的基础镜像。

比如下面的例子,只需要执行打印一个 helloworld 的二进制文件的镜像构建过程,这时使用 scratch 最合适不过了。

FROM scratch
COPY hello /
CMD [ “/hello” ]


TAGS:Docker

相关推荐

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

请填写验证码

支付宝

微信