8,533
Docker通过读取Dockerfile文件中的指令自动构建镜像。Dockerfile文件为一个文本文件,里面包含构建镜像所需的所有的命令。Dockerfile文件遵循特定的格式和指令集
Docker镜像由只读层组成,每个层都代表一个Dockerfile指令。这些层是堆叠的,每个层都是前一层变化的增量。示例:
FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py
每个指令构建一层:
FROM 根据ubuntu18.04docker镜像创建一个层
COPY 将Docker客户端当前的目录文件添加到镜像中
RUN 使用make构建应用程序
CMD 指定在容器中运行的命令
通过镜像启动一个容器的时候,会在基础层上添加一个可写层。对正在运行的容器所做的所有更改(例如,写入新文件,修改现有文件和删除文件)都将写入此可写容器层。
1.创建临时容器
通过Dockerfile文件定义的镜像,产生的容器尽可能的是临时的。所谓的临时,意思是,容器可停止,销毁,重建和替代为最小设置和配置。
2.理解构建上下文
在我们构建镜像的时候,一般情况下,会使用
docker build -t name:tag .
当你发出一个docker build命令时,当前目录将作为构建的上下文。Dockerfile默认存放在此目录下。但是,你可以通过-f选项指定一个不同的目录。不论Dockerfile文件实际存放在何处。当前目录中的所有文件和目录的递归内容都将作为构建上下文发送到Docker守护程序。
创建一个用于构建上下文的目录,并cd进入该目录下。写入"Hello"字符串到一个文本文件中,命名为hello并创建一个Dockerfile文件运行cat命令,通过.
构建上下文构建镜像。
mkdir myproject && cd myproject
echo "hello" > hello
echo -e "FROM busybox\nCOPY /hello /\nRUN cat /hello" > Dockerfile
docker build -t helloapp:v1 .
将Dockerfile和hello移到不同的目录下,构建第二版镜像(不依赖于上次构建的缓存)。使用-f 指定Dockerfile文件的目录,并指定构建的上下文目录。
mkdir -p dockerfiles context
mv Dockerfile dockerfiles && mv hello context
docker build --no-cache -t helloapp:v2 -f dockerfiles/Dockerfile context
构建镜像时,不经意包含不必要的文件将会导致一个臃肿的构建上下文和一个臃肿的镜像,这将导致构建镜像时长增加,提交到仓库和从仓库拉取时长,容器运行时大小都将增加。构建镜像时可以看到构建上下文的大小。
通过.dockerignore文件排除
要排除与构建无关的文件(不重构源存储库),请使用.dockerignore文件。此文件支持类似于.gitignore文件的排除模式
使用多级构建
多级构建允许你大幅减小最终图像的大小而无需努力减少中间层和文件的数量。由于图像是在构建过程的最后阶段构建的,因此可以通过利用构建缓存来最小化图像层。例如,如果您的构建包含多个图层,则可以从较不频繁更改(以确保构建缓存可重用)到更频繁更改的顺序对它们进行排序:
build程序所需的工具安装
安装升级库依赖
生产应用程序
一个go应用程序的Dockerfile文件如下:
FROM golang:1.11-alpine AS build
RUN apk add --no-cache git
RUN go get github.com/golang/dep/cmd/dep
COPY Gopkg.lock Gopkg.toml /go/src/project/
WORKDIR /go/src/project/
RUN dep ensure -vendor-only
COPY . /go/src/project/
RUN go build -o /bin/project
FROM scratch
COPY --from=build /bin/project /bin/project
ENTRYPOINT ["/bin/project"]
CMD ["--help"]
不要安装不必要的包
为了降低复杂性,依赖性,文件大小和构建时间,请避免安装额外的或不必要的软件包,因为它们可能“很好”。例如,您不需要在数据库映像中包含文本编辑器。
解耦应用程序
每个容器应该只有一个关注点。将应用程序解耦到多个容器可以更容易地水平伸缩和重用容器。例如,web应用程序堆栈可能由三个独立的容器组成,每个容器都有自己独特的映像,以解耦的方式管理web应用程序、数据库和内存缓存。
将每个容器限制为一个进程是一个很好的经验法则,但它不是一个硬性规则。 例如,不仅可以使用init进程生成容器,而且某些程序可能会自行生成其他进程。 例如,Celery可以生成多个工作进程,Apache可以为每个请求创建一个进程。
使用你最好的判断来保持容器尽可能的干净和模块化。如果容器彼此依赖,可以使用Docker容器网络来确保这些容器能够通信。
减少图层的数量
在旧版本的Docker中,最大限度地减少图像中的图层数量以确保它们具有高性能非常重要。添加了以下特性来减少这种限制:
只用RUN,COPY,ADD指令会创建层,其他指令创建临时中间层,并不增加构建的大小。
在可能的情况下,使用多阶段构建,并仅将所需的工件复制到最终图像中。这允许您在中间构建阶段中包含工具和调试信息,而不会增加最终图像的大小
多行参数排序
只要有可能,通过按字母顺序排序多行参数来缓解以后的更改。这有助于避免重复包并使列表更容易更新。这也使PR更容易阅读和审查。 在反斜杠(\)之前添加空格也有帮助。
RUN apt-get update && apt-get install -y \
bzr \
cvs \
git \
mercurial \
subversion
构建映像时,Docker会逐步执行Dockerfile中的指令,按指定的顺序执行每个指令。 在检查每条指令时,Docker会在其缓存中查找可以重用的现有映像,而不是创建新的(重复)映像。
如果你不想使用缓存,可以在docker build命令中使用--no-cache=true
选项,然而,如果你让Docker使用缓存,理解什么时候使用,什么时候不能,使用一个匹配的镜像将非常重要,Docker遵循的基本规则概述如下:
从已在缓存中的父镜像开始,接下来的指令将对比所有的子镜像看是否有一个使用相同的指令构建而成,如果没有,缓存则是无效的。
在大多数情况下,只需将Dockerfile中的指令与其中一个子映像进行比较就足够了。然而,某些指示需要更多的检查和解释。
对于ADD和COPY指令,将检查映像中文件的内容,并为每个文件计算校验和。 在这些校验和中不考虑文件的最后修改时间和最后访问时间。 在高速缓存查找期间,将校验和与现有映像中的校验和进行比较。 如果文件中的任何内容(例如内容和元数据)发生了任何更改,则缓存将失效。
除了ADD和COPY命令之外,高速缓存检查不会查看容器中的文件以确定高速缓存匹配。 例如,在处理RUN apt-get -y update命令时,不检查容器中更新的文件以确定是否存在缓存命中。 在这种情况下,只需使用命令字符串本身来查找匹配项。
一旦缓存无效,Dockerfile命令将产生新的镜像而不使用缓存。
Dockerfile指令
FROM <image> [AS <name>]
FROM <image>[:<tag>] [AS <name>]
FROM <image>[@<digest>] [AS <name>]
FROM指令初始化新的构建阶段并为后续指令设置基本映像。因此,有效的Dockerfile必须以FROM指令开头.
FROM可以出现多次在同一个Dockerfile文件中,为了创建多个镜像,或者使用一个作为另一个镜像的依赖,只要在每个新的FROM执行之前记录上一个镜像的ID。每个FROM指令都会清空之前命令创建的任何状态。
可选的,每个FROM指令都可以通过AS name
提供一个名词,该名词可以在子FROM指令和COPY --from<name|index>指令中指待该镜像。
tag和digest值是可选的,如果省略他们,将使用latest版本,如果未找到将会抛出异常。
尽可能的使用官方镜像作为基础镜像。
FROM golang:1.10.3 as builder
WORKDIR /app/
RUN mkdir -p src/github.com \
&& mkdir -p src/golang.org \
&& mkdir -p src/gopkg.in \
&& mkdir -p src/qiniupkg.com \
&& mkdir -p src/google.golang.org \
&& mkdir -p src/go4.org
了解ARG和FROM如何互动:
FROM指令支持在第一个FROM之前通过任意ARG指令声明变量
ARG CODE_VERSION=latest
FROM base:${CODE_VERSION}
CMD /code/run-app
FROM extras:${CODE_VERSION}
CMD /code/run-extras
LABEL
你可以为你的镜像添加labels,用来组织镜像,记录证书信息,或者其他原因,对应每个label,增加以LABEL开头的行,和一个或者多个键值对。下面的示例中展示的是不同形式的:
LABEL com.example.version="0.0.1-beta"
LABEL vendor1="ACME Incorporated"
LABEL vendor2=ZENITH\ Incorporated
LABEL com.example.release-date="2015-02-12"
LABEL com.example.version.is-production=""
上面的也可以写成如下形式:
LABEL vendor=ACME\ Incorporated \
com.example.is-beta= \
com.example.is-production="" \
com.example.version="0.0.1-beta" \
com.example.release-date="2015-02-12"
RUN ["executable", "param1", "param2"]
RUN指令将执行任何命令在当前镜像的一个新层上并提交结果。提交后的镜像将会在下一步中使用。
在使用反斜杠分隔的多行上拆分长或复杂的RUN语句,以使Dockerfile更具可读性,可理解性和可维护性。
RUN apt-get update && apt-get install -y
这些命令不要分开,
RUN apt-get update && apt-get install -y \
aufs-tools \
automake \
build-essential \
curl \
dpkg-sig \
libcap-dev \
libsqlite3-dev \
mercurial \
reprepro \
ruby1.9.1 \
ruby1.9.1-dev \
s3cmd=1.1.* \
&& rm -rf /var/lib/apt/lists