Dockerfile最佳实践(一)
更新:HHH   时间:2023-1-7


在“Docker部署您的第一个应用程序”一篇中,我们已经使用了Dockerfile来构建镜像,这一篇将补充Dockerfile经常使用指令。


Docker可以通过读取Dockerfile中的指令来生成镜像。Dockerfile是一个文本文件,用户对镜像操作的所有指令都可以写在Dockerfile文件中,最后使用docker build来构建镜像。

在“Docker部署您的第一个应用程序”中,我们使用了命令“docker image build -t bulletinboard:1.0 . ”,docker image build命令通过读取Dockerfile和指定的上下文来构建镜像,命令结尾有一个“." 点,这个点就是构建镜像的上下文。

上下文是递归进行处理的。因此,即包括该上下文下的所有子目录。

镜像的构建是由Docker守护进程(Docker daemon)完成的,而不是由CLI。构建过程首先要做的是将整个上下文(递归地)发送给守护进程。在大多数情况下,最好从一个空目录作为上下文开始,并将Dockerfile保存在该目录中。只添加生成Dockerfile所需的文件。

注意:千万不要使用"/"根作为上下文,例如如下命令,因为它将会将宿主机"/"根目录下所有文件传输到Docker的守护进程,可以在开发环境尝试执行如下命令进行验证。

# docker image build /

若要在构建的上下文中将配置文件或包构建到镜像中,可在Dockerfile中使用COPY指令。若要提高构建性能,可通过在上下文目录中添加.dockerignore文件来排除文件和目录。通常Dockerfile,位于上下文的根目录中,在docker build中使用-f标志可以指定文件系统中任何地方的docker file,使用-t标志可以指定构建镜像的仓库以及tag标签,例如:

# cat >/tmp/centos <<EOF
FROM centos:latest
MAINTAINER firefly@demo.com
EOF
# docker image build -f /tmp/centos -t centos:v0.1 .
# docker images

REPOSITORY TAG IMAGE ID CREATED SIZE

centos v0.1 7eab7b4cc6ea 38 seconds ago 220MB

您也可以指定构建镜像的多个仓库以及tag,例如:

# docker image build -f /tmp/centos -t t01/centos:v0.1 -t t02/centos:v0.2 .
# docker images

REPOSITORY TAG IMAGE ID CREATED SIZE

centos v0.1 7eab7b4cc6ea 3 minutes ago 220MB

t01/centos v0.1 7eab7b4cc6ea 3 minutes ago 220MB

t02/centos v0.2 7eab7b4cc6ea 3 minutes ago 220MB

注意:当前所指的仓库和tag均位于当前宿主机,在其他宿主机上,您是无法获取这些镜像的(除非您推送到您的docker hub账户下或其他方式),后续将会讲到docker的私有仓库registry或Harobor来远程分享我们做好的镜像。

使用Dockerfile构建镜像步骤总结如下:

1、为镜像创建一个目录,如bulletin-board-app

2、进入bulletin-board-app目录,在该目录下创建并完成Dockerfile文件编写

3、镜像所需要的文件或代码都拷贝到bulletin-board-app目录

4、如果bulletin-board-app目录下有文件是构建时不需要的,则可以创建并编写.dockerignore文件来忽略不需要的文件

5、在bulletin-board-app目录下执行docker image build命令,并指定上下文位置为".",如命令”docker image build -t test/bulletinboard .“

Docker守护进程在执行Dockerfile中的指令之前,会先对Dockerfile执行初步验证,如果语法不正确,则返回相关错误,如果是参数错误,例如目标目录不存在则不会检查,直到执行到该指令时抛出错误。

Docker守护进程逐个执行Dockerfile中的指令,必要时将每条指令的结果提交给新镜像,最后输出新镜像的ID。Docker守护进程将自动清理您发送的上下文。

注意,每个指令都是独立运行的,因此上一指命的执行不会对下一个指令产生任何影响。

只要有可能,Docker将会重用中间镜像(缓存),以显著加快Docker的构建过程。并且在控制台会输出Using cache消息。

演示示例:

# cat >Dockerfile <<EOF
FROM alpine:3.2
MAINTAINER firefly@demo.com
RUN apk update && apk add socat && rm -r /var/cache/
CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh
EOF

第一次构建

# docker build -t demo/demo:v0.1 .

Sending build context to Docker daemon 2.048kB

Step 1/4 : FROM alpine:3.2

3.2: Pulling from library/alpine

95f5ecd24e43: Pull complete

Digest: sha256:ddac200f3ebc9902fb8cfcd599f41feb2151f1118929da21bcef57dc276975f9

Status: Downloaded newer image for alpine:3.2

---> 98f5f2d17bd1

Step 2/4 : MAINTAINER firefly@demo.com

---> Running in fa3786732ad5

Removing intermediate container fa3786732ad5

---> 6f5007fa547d

Step 3/4 : RUN apk update && apk add socat && rm -r /var/cache/

---> Running in b157222691fb

fetch http://dl-cdn.alpinelinux.org/alpine/v3.2/main/x86_64/APKINDEX.tar.gz

v3.2.3-474-g10ee65f [http://dl-cdn.alpinelinux.org/alpine/v3.2/main]

OK: 5294 distinct packages available

(1/4) Installing ncurses-terminfo-base (5.9)

(2/4) Installing ncurses-libs (5.9)

(3/4) Installing readline (6.3.008)

(4/4) Installing socat (1.7.3.0)

Executing busybox-1.23.2.trigger

OK: 7 MiB in 19 packages

Removing intermediate container b157222691fb

---> 58c5258280f7

Step 4/4 : CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh

---> Running in ca843dd16f02

Removing intermediate container ca843dd16f02

---> 7bf06f4ab80b

Successfully built 7bf06f4ab80b

Successfully tagged demo/demo:v0.1

第二次构建

# docker build -t demo/demo:v0.2 .

Sending build context to Docker daemon 2.048kB

Step 1/4 : FROM alpine:3.2

---> 98f5f2d17bd1

Step 2/4 : MAINTAINER firefly@demo.com

---> Using cache

---> 6f5007fa547d

Step 3/4 : RUN apk update && apk add socat && rm -r /var/cache/

---> Using cache

---> 58c5258280f7

Step 4/4 : CMD env | grep _TCP= | (sed 's/.*_PORT_\([0-9]*\)_TCP=tcp:\/\/\(.*\):\(.*\)/socat -t 100000000 TCP4-LISTEN:\1,fork,reuseaddr TCP4:\2:\3 \&/' && echo wait) | sh

---> Using cache

---> 7bf06f4ab80b

Successfully built 7bf06f4ab80b

Successfully tagged demo/demo:v0.2

生成缓存仅用于具有本地父链的镜像。这也意味着这些缓存镜像均是由之前的构建生成的,或者整个镜像链是用docker load方式加载的。如果需要指定镜像缓存,可以使用--cache from选项。使用--cache from指定的镜像不需要有父链,可以从其他仓库中拉取。

完成构建后,就可以考虑将存储在本地仓库的镜像推送到远端仓库(例如:Harobor)

BuildKit

从18.09版开始,Docker支持一个新的构建工具buildkit,moby/buildkit项目(https://github.com/moby/buildkit)。与现有的实现工具相比,BuildKit提供了许多特性:

1、检测并跳过执行未使用的构建阶段

2、并行化独立构建阶段

3、构建过程中只增量地传输上下文中更改的文件

4、检测并跳过在上下文中传输未使用的文件

5、许多新特性在外部Dockerfile实现

6、避免与API的其余部分(中间镜像和容器)产生副作用

7、自动修剪并设置生成缓存的优先级

要使用BuildKit,需要在调用docker build命令之前在CLI上设置环境变量DOCKER_BUILDKIT=1。

要了解可用于基于BuildKit的构建的实验Dockerfile语法,请参阅BuildKit文档(https://github.com/moby/buildkit/blob/master/frontend/dockerfile/docs/experimental.md)。

Dockerfile指令与语法

Dockerfile中的指令不区分大小写。但是,按惯例是要大写,以便更容易地将它们与参数区分开来。

Docker按顺序运行Dockerfile中的指令。Dockerfile必须以“FROM”指令开头。当然,FROM指令之前可以有注释和全局参数。FROM指令指定了父镜像。FROM前面只能有一个或多个ARG指令,这些指令声明Dockerfile的FROM行中使用的参数。

Docker将以#开头的行视为注释。

1、FROM 镜像:标签

指定新镜像是基于哪个(基础)镜像创建,每一个镜像的创建都需要一条FROM指令,例如:

FROM centos:latest

2、MAINTAINER 名字/邮箱

维护人信息,例如:

MAINTAINER firefly@demo.com

3、ADD 源文件 新镜像目录

将源文件复制到新创建镜像中,源文件要与Dockerfile同属一个目录,ADD指令会自动解压tar、tgz包,例如:

ADD example.tgz /data

4、COPY 源文件 目标目录

将源文复制到新建像中,源文件要与Dockerfile所属同一个目录,同ADD类似,例如:

COPY sources.list /etc/apt

5、ENV 关键字 值

设置变量或环境变量,例如:

ENV foo /var/www/html

上述表示变量foo的值是/var/www/html

6、RUN 命令

基于现有镜像执行命令,并提交到新镜像上,通常在安装软件包时使用RUN,例如:

RUN yum -y install sysstat

7、WORKDIR 目录

指定工作目录,通过WORKDIR设置工作目录后,Dockerfile中其后的命令RUN、CMD、ENTRYPOINT、ADD、COPY等命令都会在该目录下执行,后续登录基于该镜像的容器缺省路径就是WORKDIR。

8、EXPOSE 端口号

指定Docker容器从该镜像运行时所开启的端口,例如:

EXPOSE 80

9、VOLUME 挂载点

Docker容器从该镜像运行时会设置一个挂载点,例如:

VOLUME /data

10、CMD["要运行的程序","参数1","参数2"]

容器启动时要运行的命令或脚本,Dockerfile只能有一条CMD命令,如果有多条,则执行最后一条,另外执行docker run 命令时若使用了/bin/bash,则会覆盖CMD。例如:

CMD ["/bin/bash","/root/start.sh"]

示例演示

1、创建Dockerfile

# mdkir demo
# cd demo
# cat > Dockerfile <<EOF
#My first image
FROM ubuntu:latest
MAINTAINER firefly@demo.com
ENV foo /var/www/html
WORKDIR ${foo}
ADD code.tgz $foo
COPY sources.list /etc/apt
COPY start.sh /root/
RUN chmod 755 /root/start.sh
RUN mkdir /data
VOLUME /data
RUN apt-get -y update && apt-get -y install sysstat lsof net-tools procps vim bash
RUN apt-get -y install apache2
RUN ln -fs /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN date
COPY ports.conf /etc/apache2/ports.conf
ADD example.tgz /data
EXPOSE 80
CMD ["/bin/bash","/root/start.sh"]
EOF

上述Dockerfile中用到的相关脚本配置如下:

# cat >sources.list <<EOF
deb http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-security main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-updates main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-proposed main restricted universe multiverse
deb-src http://mirrors.aliyun.com/ubuntu/ bionic-backports main restricted universe multiverse
EOF
# cat >ports.conf <<EOF
ServerName localhost
Listen 80
<IfModule ssl_module>
Listen 443
</IfModule>
<IfModule mod_gnutls.c>
Listen 443
</IfModule>
EOF
# cat >start.sh <<EOF
#!/bin/bash
apache2ctl start
bash
EOF

2、通过Dockerfile构建镜像

# docker image build -t test/httpd:v0.1 .
# docker images

REPOSITORY TAG IMAGE ID CREATED SIZE

test/httpd v0.1 9a9a2b7dd312 2 minutes ago 165MB

httpd latest 2ae34abc2ed0 3 weeks ago 165MB

3、基于镜像运行容器

# docker container run -idt -p 80 --name test_httpd01 test/httpd:v0.1

6e7a40ec63b618bf043b45d334c289df782f02e19617dc0686c3be41a582e047

注意:在创建容器时不要加/bin/bash,不然会覆盖CMD导致apache服务不启动。

4、查看容器状态并确认端口处于监听状态

# docker ps -a

CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

a919ee33ae0e test/httpd:v0.1 "/bin/bash /root/sta…" 3 minutes ago Up 3 minutes 0.0.0.0:32787->80/tcp test_httpd01

# docker exec -it test_httpd01 netstat -antp

Active Internet connections (servers and established)

Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name

tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 18/apache2

5、测试

# curl http://127.0.0.1:32787

hello

6、删除容器和镜像,容器的生命周期结束

# docker stop test_httpd01
# docker rm test_httpd01
# docker rmi $(docker images |grep "test/httpd" |awk '{print $3}')


总结
对于有一定Linux基础的童鞋,编写Dockerfile是比较简单的,但仍然需要注意一些细节,比如ADD、COPY指令差异,RUN与CMD指令差异等。

返回云计算教程...