欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Dockerfile的入坑路

程序员文章站 2022-06-03 15:54:40
...

0x00 前言

在经历了无数次的手动搭建环境之后,尝试过无尽的绝望(稍微有些夸张了),最后决定把Dockerfile学起来,要弄的镜像实在太多了,创建好基础镜像之后使用Dockerfile可以减轻很多工作,所以特地整理了这份Dockerfile相关命令。

0x01 docker build

  • docker build .

使用当前目录下的Dockerfile文件创立新的镜像,但是REPOSITORYTAG都是默认的<none>,所以这样做个人觉得不是很好的。

  • docker build -f /path/Dockerfile .

可以使用-f标志docker build指向文件系统中任何位置的Dockerfile

  • docker build -t REPOSITORY:TAG .

如果构建成功,该命令可以指定存储库和标记以保存新镜像。

要在构建后将映像标记为多个存储库,请在-t运行build命令时添加多个参数:

docker build -t REPOSITORY1:TAG1 -t REPOSITORY2:TAG2 .

0x02 Dockerfile的语法

创建Dockerfile的原则:

  • 更快的构建速度
  • 更小的Docker镜像大小
  • 更少的Docker镜像层
  • 充分利用镜像缓存
  • 增加Dockerfile可读性
  • 让Docker容器使用起来更简单

2.1 FROM

Dockerfile文件是以FROM指令开始的,通过该指令从docker hub上或者本地存储库中拉取镜像作为基本镜像,基础镜像选用alpine比较合适,这个镜像不到5M。

- FROM <image> [AS <name>]

- FROM <image>[:<tag>] [AS <name>]

2.2 RUN

RUN有两种形式:

  • RUN (shell形式)
  • RUN [“executable”, “param1”, “param2”](exec形式)

exec形式被解析为JSON数组,这意味着必须使用双引号(“),而不是单引号(')

该RUN指令将在当前图像之上的新层中执行任何命令并提交结果。生成的提交图像将用于下一步Dockerfile。

RUN在下一次构建期间,指令的缓存不会自动失效。类似指令的缓存RUN apt-get dist-upgrade -y将在下一次构建期间重用。例如,RUN可以通过使用--no-cache标志使指令的高速缓存无效docker build --no-cache .

注意:

  • 将多个RUN指令合并为一个
  • 基础镜像的标签不要用latest
  • 每个RUN指令后删除多余文件

使用\将多个RUN指令合成一个,可以有效减少容器层数以及镜像大小,当我们更新了apt-get源,下载安装了一些软件包,它们都保存在/var/lib/apt/lists/目录中。但是,运行应用时Docker镜像中并不需要这些文件。最好将它们删除,因为这会使Docker镜像变大。

FROM ubuntu:18.04
RUN apt-get update && \
  apt-get install -y nginx && \
  apt-get install -y nodejs && \
  && rm -rf /var/lib/apt/lists/*

2.3 CMD

该CMD指令有三种形式:

  • CMD [“executable”,”param1”,”param2”](exec形式,这是首选形式)
  • CMD [“param1”,”param2”](作为ENTRYPOINT的默认参数)
  • CMD command param1 param2(shell形式)

    • 一个Dockerfile中只能有一条CMD指令。如果列出多个CMD,则只有最后一个CMD生效。
    • 如果docker run指定了其他命令,CMD 指定的默认命令将被忽略

使用 shell 格式的话,实际的命令会被包装为sh -c的参数的形式进行执行:

CMD echo $HOME

在实际执行中,会将其变更为:

CMD [ "sh", "-c", "echo $HOME" ]

当我们在镜像中安装了nginx服务,并且希望这个服务随着容器的启动而一起启动时,如果我们写成shell的形式,sh作为主进程,当执行完启动命令之后,主进程退出,容器就失去了存在的意义,从而退出。命令如下(可理解为CMD [ "sh", "-c", "service nginx start"]):

CMD service nginx start

应该直接执行nginx可执行文件,并且要求以前台形式运行:

CMD ["nginx", "-g", "daemon off;"]

注意:不要混淆RUN和CMD。RUN实际上运行一个命令并提交结果; CMD在构建时不执行任何操作,但指定镜像的预期命令。

2.4 EXPOSE

EXPOSE <port> [<port>/<protocol>...]

EXPOSE指令用于指定容器将要监听的端口,我们可以指定端口是侦听TCP还是UDP,如果未指定协议,则默认为TCP。因此,应该为应用程序使用常见的端口。例如,提供 Apache web 服务的镜像应该使用EXPOSE 80,而提供 MongoDB 服务的镜像使用 EXPOSE 27017

对于外部访问,我们可以在执行docker run时使用-p标志来指示如何将指定的端口映射到所选择的端口。

默认情况下,EXPOSE假定为TCP。我们可以指定UDP:

EXPOSE 80/udp

要在TCP和UDP上公开,可以包含两行:

EXPOSE 80/tcp
EXPOSE 80/udp

无论EXPOSE设置如何,可以使用-p标志在运行时覆盖它们。例如

docker run -p 80:80/tcp -p 80:80/udp ...

2.5 LABEL

LABEL <key>=<value> <key>=<value> <key>=<value> ...

该LABEL指令将元数据添加到镜像。LABEL是键值对。要在LABEL值中包含空格,需要使用引号和反斜杠,就像在命令行解析中一样。用法示例:

LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."

一个镜像可以有多个标签。我们可以在一行中指定多个标签。在Docker 1.10之前,这减小了最终镜像的大小,但现在不再是这种情况了。我们仍然可以选择在单个指令中指定多个标签,方法有以下两种:

LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
      multi.label2="value2" \
      other="value3"

基本或父镜像中包含的标签(FROM线中的镜像)由镜像继承。如果标签已存在但具有不同的值,则最近应用的值将覆盖任何先前设置的值。

要查看镜像的标签,可以使用docker inspect命令。

"Labels": {
    "com.example.vendor": "ACME Incorporated"
    "com.example.label-with-value": "foo",
    "version": "1.0",
    "description": "This text illustrates that label-values can span multiple lines.",
    "multi.label1": "value1",
    "multi.label2": "value2",
    "other": "value3"
},

2.6 MAINTAINER

MAINTAINER <name>

MAINTAINER指令设置生成的镜像的Author字段。而LABEL指令是一个更加灵活的版本,我们应该使用它,因为它可以设置我们需要的任何元数据,并且可以轻松查看,例如使用docker inspect。要设置与MAINTAINER我们可以使用的字段对应的标签 :

LABEL maintainer="aaa@qq.com"

2.7 ENV

ENV <key> <value>
ENV <key>=<value> ...
  • 第一种形式,ENV <key> <value>将一个变量设置为一个值。第一个空格后的整个字符串将被视为<value>-包括空格字符。该值将针对其他环境变量进行解释,因此如果未对其进行转义,则将删除引号字符。

  • 第二种形式ENV <key>=<value> ...允许一次设置多个变量。请注意,第二种形式在语法中使用等号(=),而第一种形式则不然。与命令行解析一样,引号和反斜杠可用于在值内包含空格。

第一种形式:

ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy

第二种形式:

ENV myName="John Doe" myDog=Rex\ The\ Dog \
    myCat=fluffy

我们可以使用ENV来为容器中安装的程序更新PATH环境变量。例如使用ENV PATH /usr/local/nginx/bin:$PATH来确保CMD ["nginx"]能正确运行。

ENV指令也可用于为你想要容器化的服务提供必要的环境变量。

ENV也能用于设置常见的版本号,示例如下:

ENV PG_MAJOR 9.3

ENV PG_VERSION 9.3.4

RUN curl -SL http://example.com/postgres-$PG_VERSION.tar.xz | tar -xJC /usr/src/postgress && …

ENV PATH /usr/local/postgres-$PG_MAJOR/bin:$PATH

这类似于程序中的常量,我们只需改变ENV指令就能改变容器中的软件版本。

2.8 COPY 和 ADD

COPY有两种形式:

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] (包含空格的路径需要这种形式)

–chown功能仅在用于构建Linux容器的Dockerfiles上受支持

  • COPY指令从中复制新文件或目录<src>,并将它们添加到路径中容器的文件系统中<dest>
  • <src>可以指定多个资源。
  • 每个<src>都可以包含通配符。

COPY hom* /mydir/        # adds all files starting with "hom"

COPY hom?.txt /mydir/    # ? is replaced with any single character, e.g., "home.txt"

COPY test relativeDir/   # adds "test" to `WORKDIR`/relativeDir/

COPY test /absoluteDir/  # adds "test" to /absoluteDir/

COPY 遵守以下规则:

  • 该路径必须是内部语境的构建; 不能COPY ../something /something,因为第一步 docker build是将目录(和子目录)发送到docker守护进程。

  • 如果是目录,则复制目录的全部内容,包括文件系统元数据。

注意:不复制目录本身,只复制其内容。

  • 如果是任何其他类型的文件,则将其与元数据一起单独复制。在这种情况下,如果以尾部斜杠结尾/,则将其视为目录,并将写入内容/base()。

  • 如果直接或由于使用通配符指定了多个资源,则必须是目录,并且必须以斜杠结尾/

  • 如果不以尾部斜杠结束,则将其视为常规文件,并将写入其中的内容。

  • 如果不存在,则会在其路径中创建所有缺少的目录。

示例Dockerfile:

FROM node:7-alpine

WORKDIR /app

COPY package.json /app  
RUN npm install  
COPY . /app

ENTRYPOINT ["./entrypoint.sh"]  
CMD ["start"]

COPY指令非常简单,仅用于将文件拷贝到镜像中。ADD相对来讲复杂一些,可以用于下载远程文件以及解压压缩包,但是语法上基本差不多。

2.9 ENTRYPOINT

ENTRYPOINT有两种形式:

  • ENTRYPOINT [“executable”, “param1”, “param2”] (exec形式,首选)

Dockerfile显示使用ENTRYPOINT在前台运行Apache即作为PID 1):

FROM debian:stable
RUN apt-get update && apt-get install -y --force-yes apache2
EXPOSE 80 443
VOLUME ["/var/www", "/var/log/apache2", "/etc/apache2"]
ENTRYPOINT ["/usr/sbin/apache2ctl", "-D", "FOREGROUND"]

辅助脚本被拷贝到容器,并在容器启动时通过ENTRYPOINT执行:

COPY ./docker-entrypoint.sh /
ENTRYPOINT ["/docker-entrypoint.sh"]
  • ENTRYPOINT command param1 param2 (shell形式)

我们可以为ENTRYPOINT指定一个纯字符串,它将在其中执行/bin/sh -c,这种形式将使用shell处理来替换shell环境变量,并将忽略任何CMDdocker run命令行参数。为了保证docker stop,ENTRYPOINT正确地发出任何长时间运行的可执行文件:

FROM ubuntu
ENTRYPOINT exec top -b

2.10 CMD和ENTRYPOINT

  • Dockerfile应至少指定一个CMD或ENTRYPOINT命令。

  • ENTRYPOINT 应该在将容器用作可执行文件时定义。

  • CMD应该用作定义ENTRYPOINT命令的默认参数或在容器中执行ad-hoc命令的方法。

2.11 VOLUME

VOLUME指令用于暴露任何数据库存储文件,配置文件,或容器创建的文件和目录。强烈建议使用VOLUME来管理镜像中的可变部分和用户可以改变的部分。

2.12 WORKDIR

WORKDIR /path/to/workdir

该WORKDIR指令集的工作目录对任何RUN,CMD, ENTRYPOINT,COPY和ADD有效。如果WORKDIR不存在,即使它未在任何后续Dockerfile指令中使用,也将创建它。

该WORKDIR指令可以在Dockerfile多次使用。如果提供了相对路径,则它将相对于前一条WORKDIR指令的路径 。例如:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

最终pwd命令的输出Dockerfile将是/a/b/c

WORKDIR指令可以解析先前使用的环境变量ENV。我们只能使用显式设置的环境变量Dockerfile。例如:

ENV DIRPATH /path
WORKDIR $DIRPATH/$DIRNAME
RUN pwd

最终pwd命令的输出Dockerfile将是/path/$DIRNAME

Dockerfile:

FROM ubuntu
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol

Dockerfile会创建新的挂载点/myvol并将greeting文件复制到新创建的VOLUME中。

0x03 .dockerignore文件

docker CLI将上下文发送到docker守护进程之前,它会查找.dockerignore在上下文的根目录中指定的文件。如果此文件存在,CLI将修改上下文以排除与其中的模式匹配的文件和目录。这有助于避免不必要地将大型或敏感文件和目录发送到守护程序,并可能使用ADD或将它们添加到镜像中COPY

示例.dockerignore文件:

# comment
*/temp*
*/*/temp*
temp?

Dockerfile的入坑路

以!(感叹号)开头的行可用于对排除项进行例外处理。以下是.dockerignore使用此机制的示例文件:

*.md
!README.md

参考文档

相关标签: Dockerfile