在“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指令差异等。