S_lion's Studio

镜像构建之dockerfile

字数统计: 4.4k阅读时长: 19 min
2021/09/02 Share

近几年来容器技术几乎席卷了整个IT界,当今容器已经成为了被大家广泛认可的应用托管技术,各种传统行业也在投入容器化的技术革新浪潮中。

docker镜像可以被看做容器的基石,虽然dockerhub上提供了大量可用的镜像,但是由于环境的多样性和需求的特殊性,很多时候都需要自己制作适合的镜像。

制作docker镜像有两种常见的方式:

  1. docker commit
  2. Dockerfile

其中docker commit原理是基于在容器内执行相关操作,并最终通过commit实现打包生成镜像。Dockerfile 就是在一个文件中声明应用环境和应用本身。推荐使用Dockerfile方式,因为其与commit方式相比,具备可追溯性与可维护性,在日常的开发过程中需要频繁的构建镜像,而dockerfile会清晰的记录每一步执行的操作,出现异常时也可以方便的进行调整。

Dockerfile的基本结构

Dockerfile一般分为四部分:

  • 基础镜像信息
  • 维护者信息(可忽略)
  • 镜像操作指令
  • 容器启动时执行指令

Dockerfile语法

可以先来一个官方的alpine Dockerfile找找感觉。

以下来列举一些常用的Dockerfile指令。

FROM

FORM指令是最重要的一个且必须为Dockerfile文件开篇的第一个非注释行,用于为镜像文件构建过程指定基础镜像,后续的指令运行于此基准镜像所提供的运行环境。

实践中,基准镜像可以是任何可用镜像文件,默认情况下,docker build会在docker主机上查找指定的镜像文件,在其不存在时,则会从Docker Hub Registry上拉取所需的镜像文件。

语法

1
2
3
FROM <repository>[:<tag>]

FROM <repository>@<digest>

<repository>:指定作为base image的名称
<tag>: base image的标签,为可选项,省略时默认为latest

MAINTAINER

用于让Dockerfile制作者提供本人的详细信息,可以是任何文本信息,约定俗成为作者名称和邮件地址,可以放置于Dockerfile的任意位置,建议这个命令放在FORM之后,当然不写也是没问题的。(高版本已弃用,被LABEL替代)

语法

1
MAINTAINER [name]

LABEL

用于为镜像添加元数据。

语法

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

COPY

用于从Docker主机复制文件至创建的新镜像文件。

语法

1
2
3
COPY <src> .. <dest>

COPY ["<src>",.."<dest>"]

<src>:要复制的文件或目录,支持通配符
<dest>:目标路径,即正在创建的image的文件系统路径;建议为<dest>使用绝对路径,否则,COPY指定则以WORKDIR为其起始路径。

文件复制准则

  • <src>必须是build上下文中的路径,不能为其父目录中的文件

  • 如果<src>是目录,则其内部文件或子目录会被递归复制,但<src>目录自身不会被复制

  • 如果指定了多个<src>,或在<src>中使用了通配符,则<dest>必须是一个目录,且必须以 / 结尾

  • 如果<dest>事先不存在,它将被自动创建,这包括其父目录路径

ADD

ADD指令类似于COPY指令,ADD支持使用tar文件和URL路径。tar类型文件会自动解压。

语法

1
2
3
ADD <src> .. <dest>

ADD ["<src>",.."<dest>"]

<src>:要复制的文件或目录,支持通配符
<dest>:目标路径,即正在创建的image的文件系统路径;建议为<dest>使用绝对路径,否则,ADD指定则以WORKDIR为其起始路径。

文件复制准则

  • 同COPY指令
  • 如果<src>为URL且<dest>不以 / 结尾,则 <src>指定的文件将被下载并直接被创建为<dest>;如果<dest>以 / 结尾,则文件名为URL指定的文件将被直接下载并保存为<dest>/<filename>
  • 如果<src>是一个本地系统上的压缩格式的tar文件,它将被展开为一个目录,其行为类似于” tar -x”;然而通过URL获取到的tar文件不会自动展开;
  • 如果<src>有多个,或其间接或直接使用了通配符,则<dest>必须是一个以 / 结尾的目录路径;如果<dest>不以 / 结尾,则其被视为一个普通文件,<src>的内容将被直接写入到<dest>;

WORKDIR

用于为Dockerfile中所有的RUNCMDENTRYPOINTCOPYADD指定设定工作目录。

语法

1
WORKDIR <dir path>

在Dockerfile文件中,WORKDIR指令可出现多次,其路径也可以为相对路径,不过,其是相对此前一个WORKDIR指令指定的路径。

另外,WORKDIR也可以调用由ENV指定定义的变量,例如WORKDIR $STATEPATH

VOLUME

用于在image中创建一个挂载点目录,以挂载Docker host上的卷或其它容器上的卷。

通过 VOLUME 指令创建的挂载点,无法指定主机上对应的目录,是自动生成的,多用于数据卷容器。

语法

1
2
3
VOLUME <mountpoint>

VOLUME ["<mountpoint>"]

如果挂载点目录路径下此前的文件存在,docker run命令会在卷挂载完成后将此前的所有文件复制到新挂载的卷中。

EXPOSE

用于为容器打开指定要监听的端口以实现与外部通信(因为主机可以允许暴露的端口并不知道,当docker run时加“- P”即可以把容器内服务所需端口暴露到外部来)

语法

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

<protocol>用于指定传输层协议,可为tcp或udp二者之一,默认为TCP。

EXPOSE指令可一次指定多个端口。

ENV

用于为镜像定义所需的环境变量,并可以被Dockerfile文件中位于其后的其它指令(如ENV,ADD,COPY等)所调用。调用格式为 $variable_name${variable_name}

语法

1
2
3
ENV <key> <value> 

ENV <key> = <value> …

第一种格式中,<key>之后的所有内容均会被视作其<value>的组成部分,因此,一次只能设置一个变量

第二种格式可用一次设置多个变量,每个变量为一个<key>=<value>的键值对,如果<value>中包括空格,可以以( \ )进行转义,也可以通过对<value>加引号进行标识;另外,反斜线也可用于续行。

定义多个变量时,建议使用第二种方式,以便在同一层完成所有功能。

RUN

构建镜像时运行的命令。

语法

1
2
3
RUN <command>   #执行shell内部命令

RUN ["<executable>", "<param1>", "<param2>"] # 执行可执行文件

CMD

类似于RUN指令,CMD指令也可以用于运行任何命令或应用程序,不过,二者的运行时间点不同。

  • RUN指令运行于镜像文件构建过程中,而CMD指令运行于基于Dockerfile构建出的新镜像文件启动一个容器时。

  • CMD指令的首要目的在于为启动的容器指定默认要运行的程序,且其运行结束后,容器也将终止;不过,CMD指定的命令其可以被docker run的命令行选项所覆盖。

  • 在Dockerfile中可以存在多个CMD指令,但仅最后一个会生效。

语法

1
2
3
4
5
CMD <command>       #执行shell内部命令

CMD ["<executable>","<param1>","<param2>"] #执行可执行文件

CMD ["<param1>","<param2>"] #设置了ENTRYPOINT,则直接调用ENTRYPOINT添加参数

ENTRYPOINT

类似CMD指令的功能,用于为容器指定默认运行程序。

与CMD不同的是,由ENTRYPOINT启动的程序不会被docker run命令行指定的参数覆盖,而且,这些命令行参数会被当做参数传递给ENTRYPOINT指定的程序。

docker run命令时加– entrypoint选项的参数可覆盖ENTRYPOINT指令指定的程序。

语法

1
2
3
ENTRYPOINT <command>

ENTRYPOINT ["<executable>","<param1>","<param2>"]

USER

指定运行容器时的用户名或 UID,后续的 RUN 、CMD、ENTRYPOINT也会使用指定用户。

语法

1
USER <UID>|<UserName>

必须为镜像中已存在的用户(/etc/passwd)

ARG

用于指定传递给构建运行时的变量,ARG命令定义了一个变量,在docker build创建镜像的时候,使用 --build-arg <varname>=<value>来指定参数。

语法

1
ARG <name>[=<default value>]

HEALTHCHECK

顾名思义,健康检查。

语法

1
2
3
HEALTHCHECK [OPTIONS] CMD command    #在容器内部运行一个命令来检查容器的健康状况

HEALTHCHECK NONE #取消基础镜像中的健康检查

[OPTIONS]的选项支持以下选项:

  • –interval=DURATION 间隔时间,默认30s(30秒),从容器运行起来开始计时interval秒(或者分钟小时)进行第一次健康检查,随后每间隔interval秒进行一次健康检查;还有一种特例请看timeout解析。

  • –timeout=DURATION 超时时间,默认 30s(30秒),执行command需要时间,比如curl 一个地址,如果超过timeout秒则认为超时是错误的状态,此时每次健康检查的时间是timeout+interval秒。

  • –start-period=DURATION 启动时间,默认0s,为需要启动的容器提供了初始化的时间段,在这个时间段内如果检查失败, 则不会记录失败次数。 如果在启动时间内成功执行了健康检查, 则容器将被视为已经启动, 如果在启动时间内再次出现检查失败, 则会记录失败次数。

  • –retries=N 重试次数, 默认 3

STOPSIGNAL

当容器退出时给系统发送的指令。

语法

1
STOPSIGNAL signal

默认的stop-signal是SIGTERM,在docker stop的时候会给容器内PID为1的进程发送这个signal,通过–stop-signal可以设置自己需要的signal,主要的目的是为了让容器内的应用程序在接收到signal之后可以先做一些事情,实现容器的平滑退出,如果不做任何处理,容器将在一段时间之后强制退出,会造成业务的强制中断,这个时间默认是10s。

ONBUILD

用于为镜像添加触发器。其参数是任意一个Dockerfile 指令。

Dockerfile用于构建镜像,此镜像也可以作为base image被其他的Dockerfile引用,并构建出新的镜像。

当我们编写一个新的Dockerfile文件,FROM的是A镜像,构建的是B镜像时,这时构建A镜像的Dockerfile文件中的ONBUILD指令就生效了,在构建B镜像的过程中,首先会执行ONBUILD指令指定的指令,然后才会执行其它指令。需要注意的是,如果是再利用B镜像构造新的镜像时,那个ONBUILD指令就无效了,也就是说只能再构建子镜像中执行,对孙子镜像构建无效。

语法

1
ONBUILD <instruction>

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# 编写父镜像dockerfile
[root@slions_pc1 omg]# cat Dockerfile
FROM centos:7
ONBUILD RUN mkdir -p /tmp/slions
ONBUILD RUN yum install iproute -y

# 构建镜像
[root@slions_pc1 omg]# docker build -t slions:father .

# 启动其镜像的容器,查看和我们想的一样,并没有创建目录与安装软件
[root@slions_pc1 omg]# docker run -it --rm --name slions-f slions:father /bin/bash
[root@2710becec644 /]# ls /tmp
ks-script-DrRL8A yum.log
[root@2710becec644 /]# rpm -qa|grep iproute
[root@2710becec644 /]# ip a
bash: ip: command not found
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# 编写子镜像dockerfile
[root@slions_pc1 omg]# cat Dockerfile
FROM slions:father
RUN echo 123 > /file

# 构建子镜像,从这里就能看出来已经开始安装软件了
[root@slions_pc1 omg]# docker build -t slions:son .
Sending build context to Docker daemon 2.048kB
Step 1/2 : FROM slions:father
# Executing 2 build triggers
---> Running in 43e3db71fb7b
Removing intermediate container 43e3db71fb7b
---> Running in 22c5c6450487
Loaded plugins: fastestmirror, ovl
Determining fastest mirrors
* base: mirrors.aliyun.com
* extras: mirrors.huaweicloud.com
* updates: mirrors.aliyun.com
Resolving Dependencies
--> Running transaction check
---> Package iproute.x86_64 0:4.11.0-30.el7 will be installed
--> Processing Dependency: libmnl.so.0(LIBMNL_1.0)(64bit) for package: iproute-4.11.0-30.el7.x86_64
--> Processing Dependency: libxtables.so.10()(64bit) for package: iproute-4.11.0-30.el7.x86_64
--> Processing Dependency: libmnl.so.0()(64bit) for package: iproute-4.11.0-30.el7.x86_64
--> Running transaction check
---> Package iptables.x86_64 0:1.4.21-35.el7 will be installed
--> Processing Dependency: libnfnetlink.so.0()(64bit) for package: iptables-1.4.21-35.el7.x86_64
--> Processing Dependency: libnetfilter_conntrack.so.3()(64bit) for package: iptables-1.4.21-35.el7.x86_64
---> Package libmnl.x86_64 0:1.0.3-7.el7 will be installed
--> Running transaction check
---> Package libnetfilter_conntrack.x86_64 0:1.0.6-1.el7_3 will be installed
---> Package libnfnetlink.x86_64 0:1.0.1-4.el7 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
Package Arch Version Repository Size
================================================================================
Installing:
iproute x86_64 4.11.0-30.el7 base 805 k
Installing for dependencies:
iptables x86_64 1.4.21-35.el7 base 432 k
libmnl x86_64 1.0.3-7.el7 base 23 k
libnetfilter_conntrack x86_64 1.0.6-1.el7_3 base 55 k
libnfnetlink x86_64 1.0.1-4.el7 base 26 k

Transaction Summary
================================================================================
Install 1 Package (+4 Dependent packages)

Total download size: 1.3 M
Installed size: 3.5 M
Downloading packages:
warning: /var/cache/yum/x86_64/7/base/packages/libmnl-1.0.3-7.el7.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY
Public key for libmnl-1.0.3-7.el7.x86_64.rpm is not installed
--------------------------------------------------------------------------------
Total 7.7 MB/s | 1.3 MB 00:00
Retrieving key from file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
Importing GPG key 0xF4A80EB5:
Userid : "CentOS-7 Key (CentOS 7 Official Signing Key) <security@centos.org>"
Fingerprint: 6341 ab27 53d7 8a78 a7c2 7bb1 24c6 a8a7 f4a8 0eb5
Package : centos-release-7-9.2009.0.el7.centos.x86_64 (@CentOS)
From : /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
Installing : libmnl-1.0.3-7.el7.x86_64 1/5
Installing : libnfnetlink-1.0.1-4.el7.x86_64 2/5
Installing : libnetfilter_conntrack-1.0.6-1.el7_3.x86_64 3/5
Installing : iptables-1.4.21-35.el7.x86_64 4/5
Installing : iproute-4.11.0-30.el7.x86_64 5/5
Verifying : libnfnetlink-1.0.1-4.el7.x86_64 1/5
Verifying : libnetfilter_conntrack-1.0.6-1.el7_3.x86_64 2/5
Verifying : iptables-1.4.21-35.el7.x86_64 3/5
Verifying : libmnl-1.0.3-7.el7.x86_64 4/5
Verifying : iproute-4.11.0-30.el7.x86_64 5/5

Installed:
iproute.x86_64 0:4.11.0-30.el7

Dependency Installed:
iptables.x86_64 0:1.4.21-35.el7
libmnl.x86_64 0:1.0.3-7.el7
libnetfilter_conntrack.x86_64 0:1.0.6-1.el7_3
libnfnetlink.x86_64 0:1.0.1-4.el7

Complete!
Removing intermediate container 22c5c6450487
---> a3a6d207fc28
Step 2/2 : RUN echo 123 > /file
---> Running in 35558b54b260
Removing intermediate container 35558b54b260
---> 00f7acbca5de
Successfully built 00f7acbca5de
Successfully tagged slions:son

# 启动子镜像容器,查看是否有相关的目录与软件
[root@slions_pc1 omg]# docker run -it --rm --name slions-s slions:son /bin/bash
[root@988d86e2f98b /]# ls /tmp
ks-script-DrRL8A slions yum.log
[root@988d86e2f98b /]# rpm -qa|grep iproute
iproute-4.11.0-30.el7.x86_64
[root@988d86e2f98b /]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
26: eth0@if27: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever

Dockerfile使用

docker build命令用于从Dockerfile中构建镜像。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ docker build --help
Usage: docker build [OPTIONS] PATH | URL | -
Build an image from a Dockerfile
Options:
--add-host list Add a custom host-to-IP mapping (host:ip)
--build-arg list Set build-time variables
--cache-from strings Images to consider as cache sources
--cgroup-parent string Optional parent cgroup for the container
--compress Compress the build context using gzip
--cpu-period int Limit the CPU CFS (Completely Fair Scheduler) period
--cpu-quota int Limit the CPU CFS (Completely Fair Scheduler) quota
-c, --cpu-shares int CPU shares (relative weight)
--cpuset-cpus string CPUs in which to allow execution (0-3, 0,1)
--cpuset-mems string MEMs in which to allow execution (0-3, 0,1)
--disable-content-trust Skip image verification (default true)
-f, --file string Name of the Dockerfile (Default is 'PATH/Dockerfile')
--force-rm Always remove intermediate containers
--iidfile string Write the image ID to the file
--isolation string Container isolation technology
--label list Set metadata for an image
-m, --memory bytes Memory limit
--memory-swap bytes Swap limit equal to memory plus swap: '-1' to enable unlimited swap
--network string Set the networking mode for the RUN instructions during build (default "default")
--no-cache Do not use cache when building the image
--pull Always attempt to pull a newer version of the image
-q, --quiet Suppress the build output and print image ID on success
--rm Remove intermediate containers after a successful build (default true)
--security-opt strings Security options
--shm-size bytes Size of /dev/shm
-t, --tag list Name and optionally a tag in the 'name:tag' format
--target string Set the target build stage to build.
--ulimit ulimit Ulimit options (default [])

基本的docker build [选项] 内容路径,该命令将读取指定路径下(包括子目录)的Dockerfile,并将该路径下的所有内容发送给Docker服务端,由服务端来创建镜像。因此除非生成镜像需要,否则一般建议放置Dockerfile的目录为空目录。

常用方式示例

  1. 使用当前目录的 Dockerfile 创建镜像,标签为 agree/acaas:v1。
1
docker build -t agree/acaas:v1 .
  1. 指定Dockerfile路径创建镜像,标签为agree/acaas:v2。(如果Dockerfile中涉及ADD或者COPY本地文件时,需要保证当前执行命令的目录下存在这些文件)
1
2
3
4
5
6
7
8
$ tree
.
├── acaasdemo
│   └── Dockerfile
└── a.txt

1 directory, 2 files
[root@slions_pc1 mydockerfile]# docker build -f acaasdemo/Dockerfile -t acaas:v2 .

使用 .dockerignore文件

构建镜像时,Docker需要先准备context ,将所有需要的文件收集到进程中。默认的context包含Dockerfile目录中的所有文件,可以通过 .dockeringore文件(每一行添加一条匹配模式)来让Docker忽略匹配模式路径下的目录和文件。(可以使用通配符),.dockerignore 的作用和语法类似于 .gitignore,可以忽略一些不需要的文件,这样可以有效加快镜像构建时间,同时减少Docker镜像的大小。

官方文档

https://docs.docker.com/engine/reference/builder/

CATALOG
  1. 1. Dockerfile的基本结构
  2. 2. Dockerfile语法
    1. 2.1. FROM
      1. 2.1.1. 语法
    2. 2.2. MAINTAINER
      1. 2.2.1. 语法
    3. 2.3. LABEL
      1. 2.3.1. 语法
    4. 2.4. COPY
      1. 2.4.1. 语法
      2. 2.4.2. 文件复制准则
    5. 2.5. ADD
      1. 2.5.1. 语法
      2. 2.5.2. 文件复制准则
    6. 2.6. WORKDIR
      1. 2.6.1. 语法
    7. 2.7. VOLUME
      1. 2.7.1. 语法
    8. 2.8. EXPOSE
      1. 2.8.1. 语法
    9. 2.9. ENV
      1. 2.9.1. 语法
    10. 2.10. RUN
      1. 2.10.1. 语法
    11. 2.11. CMD
      1. 2.11.1. 语法
    12. 2.12. ENTRYPOINT
      1. 2.12.1. 语法
    13. 2.13. USER
      1. 2.13.1. 语法
    14. 2.14. ARG
      1. 2.14.1. 语法
    15. 2.15. HEALTHCHECK
      1. 2.15.1. 语法
    16. 2.16. STOPSIGNAL
      1. 2.16.1. 语法
    17. 2.17. ONBUILD
      1. 2.17.1. 语法
      2. 2.17.2. 示例
  3. 3. Dockerfile使用
    1. 3.1. 常用方式示例
    2. 3.2. 使用 .dockerignore文件
  4. 4. 官方文档