Dockerfile编写学习
之前经常使用别人写好的Dockerfile,最近我佩服的web大佬在我旁边学习,他说他要复现和分析一下一道 ctf 题目,我便开心主动的加入了!!!大佬说以后我们大赛做一个题目,如果你可以把那些没有Dockerfile的竞赛题目的环境写成Dockerfile,那就很好啦😊😊😊
趁着今天给导师汇报完毕,放松放松,以dc2020q-dogooos-public环境的Dockerfile为例,系统的学习学习Dockerfile文件的编写。💃💃💃
1 dockerfile介绍
1.1 什么是dockerfile
Dockerfile就是一个可以用来构建docker镜像的文本文件,文本内容中包含了一条条构建镜像所需的指令和说明。Dockerfile中的每一条指令都创建镜像的一层,每执行一次都会在 docker 上新建一层,所以过多无意义的层,会造成镜像膨胀过大。
当我们要复现一个竞赛题目时,有Dockerfile文件就easy多了。不然的话还得拖取镜像,创建容器,配置环境…总之,Dockerfile太方便了!
2 Dockerfile指令详解
本部分内容来源于 https://www.runoob.com/docker/docker-dockerfile.html
2.1 COPY
如果仅仅是把本地的文件拷贝到容器镜像中,COPY
命令是最合适不过的。其命令的格式为:COPY <src> <dest>
COPY [--chown=<user>:<group>] <源路径1>... <目标路径>
COPY [--chown=<user>:<group>] ["<源路径1>",... "<目标路径>"]
[–chown=:]:可选参数,用户改变复制到容器内文件的拥有者和属组。
<源路径>:源文件或者源目录,这里可以是通配符表达式,其通配符规则要满足 Go 的 filepath.Match 规则。例如:
COPY hom* /mydir/
COPY hom?.txt /mydir/
<目标路径>:容器内的指定路径,该路径不用事先建好,路径不存在的话,会自动创建。
2.2 ADD
ADD
指令和 COPY
的使用格式一致(同样需求下,官方推荐使用 COPY
)。功能也类似,不同之处如下:
ADD 的优点:在执行 <源文件> 为 tar
压缩文件的话,压缩格式为 gzip
, bzip2
以及 xz
的情况下,会自动复制并解压到 <目标路径>
。
ADD 的缺点:在不解压的前提下,无法复制 tar
压缩文件。会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。具体是否使用,可以根据是否需要自动解压来决定。
2.3 CMD
类似于 RUN
指令,用于运行程序,但二者运行的时间点不同:
CMD
在docker run
时运行。
RUN
是在 docker build
。
作用:为启动的容器指定默认要运行的程序,程序运行结束,容器也就结束。CMD
指令指定的程序可被 docker run
命令行参数中指定要运行的程序所覆盖。
注意:如果 Dockerfile
中如果存在多个 CMD
指令,仅最后一个生效。
格式:
CMD <shell 命令>
CMD ["<可执行文件或命令>","<param1>","<param2>",...]
CMD ["<param1>","<param2>",...] # 该写法是为 ENTRYPOINT 指令指定的程序提供默认参数
推荐使用第二种格式,执行过程比较明确。第一种格式实际上在运行的过程中也会自动转换成第二种格式运行,并且默认可执行文件是 sh。
2.4 ENTRYPOINT
类似于 CMD
指令,但其不会被 docker run
的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT
指令指定的程序。
但是, 如果运行 docker run
时使用了 --entrypoint
选项,此选项的参数可当作要运行的程序覆盖 ENTRYPOINT
指令指定的程序。
优点:在执行 docker run
的时候可以指定 ENTRYPOINT
运行所需的参数。
注意:如果 Dockerfile
中如果存在多个 ENTRYPOINT 指令
,仅最后一个生效。
格式:ENTRYPOINT ["<executeable>","<param1>","<param2>",...]
可以搭配 CMD
命令使用:一般是变参才会使用 CMD
,这里的 CMD
等于是在给 ENTRYPOINT
传参,以下示例会提到。
示例:
假设已通过 Dockerfile 构建了 nginx:test 镜像:
FROM nginx
ENTRYPOINT ["nginx", "-c"] # 定参
CMD ["/etc/nginx/nginx.conf"] # 变参
1、不传参运行
$ docker run nginx:test
容器内会默认运行以下命令,启动主进程。
nginx -c /etc/nginx/nginx.conf
2、传参运行
$ docker run nginx:test -c /etc/nginx/new.conf
容器内会默认运行以下命令,启动主进程(/etc/nginx/new.conf:假设容器内已有此文件)
nginx -c /etc/nginx/new.conf
2.5 ENV
设置环境变量,定义了环境变量,那么在后续的指令中,就可以使用这个环境变量。
格式:
ENV <key> <value>
ENV <key1>=<value1> <key2>=<value2>...
以下示例设置 NODE_VERSION = 7.2.0
, 在后续的指令中可以通过 $NODE_VERSION
引用:
ENV NODE_VERSION 7.2.0
RUN curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/node-v$NODE_VERSION-linux-x64.tar.xz" \
&& curl -SLO "https://nodejs.org/dist/v$NODE_VERSION/SHASUMS256.txt.asc"
2.6 ARG
构建参数,与 ENV
作用一至。不过作用域不一样。ARG
设置的环境变量仅对 Dockerfile
内有效,也就是说只有 docker build
的过程中有效,构建好的镜像内不存在此环境变量。
构建命令 docker build
中可以用 --build-arg <参数名>=<值>
来覆盖。
格式:ARG <参数名>[=<默认值>]
2.7 VOLUME
定义匿名数据卷。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。
作用:
避免重要的数据,因容器重启而丢失,这是非常致命的。
避免容器不断变大。
格式:
VOLUME ["<路径1>", "<路径2>"...]
VOLUME <路径>
在启动容器 docker run
的时候,我们可以通过 -v
参数修改挂载点。
2.8 EXPOSE
仅仅只是声明端口。
作用:
帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射。
在运行时使用随机端口映射时,也就是 docker run -P
时,会自动随机映射 EXPOSE
的端口。
格式:EXPOSE <端口1> [<端口2>...]
2.9 WORKDIR
指定工作目录。用 WORKDIR
指定的工作目录,会在构建镜像的每一层中都存在。(WORKDIR
指定的工作目录,必须是提前创建好的)。
docker build
构建镜像过程中的,每一个 RUN
命令都是新建的一层。只有通过 WORKDIR
创建的目录才会一直存在。
格式:WORKDIR <工作目录路径>
2.10 USER
用于指定执行后续命令的用户和用户组,这边只是切换后续命令执行的用户(用户和用户组必须提前已经存在)。
格式:
USER <用户名>[:<用户组>]
2.11 HEALTHCHECK
用于指定某个程序或者指令来监控 docker 容器服务的运行状态。
格式:
HEALTHCHECK [选项] CMD <命令>
:设置检查容器健康状况的命令
HEALTHCHECK NONE
:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令
HEALTHCHECK [选项] CMD <命令>
: 这边 CMD 后面跟随的命令使用,可以参考 CMD
的用法。
2.12 ONBUILD
用于延迟构建命令的执行。简单的说,就是 Dockerfile
里用 ONBUILD
指定的命令,在本次构建镜像的过程中不会执行(假设镜像为 test-build
)。当有新的 Dockerfile
使用了之前构建的镜像 FROM test-build
,这是执行新镜像的 Dockerfile
构建时候,会执行 test-build
的 Dockerfile
里的 ONBUILD
指定的命令。
格式:ONBUILD <其它指令>
3 利用Docekerfile创建镜像
本部分内容可能会和第二部分一些内容有所重复,因为作者是先分析一个Dockerfile
文件,再系统学习Dockerfiler
命令的。
目标:定制一个 nginx 镜像(构建好的镜像内会有一个 /usr/share/nginx/html/index.html 文件)
步骤:在一个空目录下,新建一个名为Dockerfile的文件,并在文件内添加以下内容:
FROM nginx
RUN echo '这是一个本地构建的nginx镜像' > /usr/share/nginx/html/index.html
- From:定制的镜像都是基于 FROM 的镜像,这里的 nginx 就是定制需要的基础镜像。后续的操作都是基于 nginx。
- RUN:用于执行后面跟着的命令行命令。有以下两种格式:
(1) shell格式:<命令行命令> 等同于,在终端操作的 shell 命令。
RUN <命令行命令>
(2) exec 格式:
RUN ["可执行文件", "参数1", "参数2"]
# 例如:
# RUN ["./test.php", "dev", "offline"] 等价于 RUN ./test.php dev offline
3.1 构建镜像
# 注:最后的 . 代表本次执行的上下文路径;
# 默认上下文路径就是 Dockerfile 所在的位置,上下文路径下不要放无用的文件;
# 因为会一起打包发送给 docker 引擎,如果文件过多会造成过程缓慢。
docker build -t nginx:test .
3.2 启动容器
docker run -t -i nginx:test /bin/bash
3.3 FROM
定制的镜像都是基于 FROM 的镜像,这里的 ubuntu 就是定制需要的基础镜像。后续的操作都是基于 ubuntu。
FROM ubuntu:20.04
3.4 ENV
指定一个环境变量,会被后续 RUN 指令使用,并在容器运行时保持。启动容器后,在容器实例中,可以通过** env命令查看环境变量**。
格式: ENV <key> <value>
DEBIAN_FRONTEND环境变量,告知操作系统应该从哪儿获得用户输入。如果设置为"noninteractive",我们就可以直接运行命令,而无需向用户请求输入(所有操作都是非交互式的)。这在运行apt-get命令的时候格外有用,因为它会不停的提示用户进行到了哪步并且需要不断确认。非交互模式会选择默认的选项并以最快的速度完成构建。
一般使用以下方式进行全局设置屏蔽交互界面:ENV DEBIAN_FRONTEND noninteractive
ENV DEBCONF_NONINTERACTIVE_SEEN true
由于ENV命令在整个容器运行过程中都会生效,所以当我们通过BASH和容器进行交互时,如果进行了全局设置那就会出问题。这就要确保只在Dockerfile中调用的RUN命令中设置了该选项,而不是使用ENV命令进行全局的设置。
例如:RUN DEBIAN_FRONTEND="noninteractive" apt-get -y install nginx
ENV TERM linux
## for apt to be noninteractive
ENV DEBIAN_FRONTEND noninteractive
ENV DEBCONF_NONINTERACTIVE_SEEN true
3.5 RUN
RUN用于在镜像容器中执行命令,其有以下两种命令执行方式:
shell执行
RUN <command>
exec执行
RUN ["可执行文件", "参数1", "参数2"]
注:RUN指令创建的中间镜像会被缓存,并会在下次构建中使用。如果不想使用这些缓存镜像,可以在构建时指定--no-cache
参数,如:docker build --no-cache
。使用RUN apt-get install -y software-properties-common
命令安装所需依赖,使用RUN add-apt-repository ppa:deadsnakes/ppa
命令添加ppa源 。
RUN apt-get update
RUN apt-get install -y software-properties-common
RUN add-apt-repository ppa:deadsnakes/ppa
RUN apt-get update
使用-qq
忽略日志,除非错误才打印,还加上了--no-install-recommends
参数来避免安装非必须的文件,从而减小镜像的体积。
RUN apt-get install -qq --no-install-recommends python3.8 wget emacs bsdmainutils
使用update-alternatives
管理多个版本的Python
,以及对应版本Python
的第三方库的安装,选择适当版本,快速切换至其他版本的Python
。
#RUN update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.7 1 && \
# update-alternatives --install /usr/bin/python3 python3 /usr/bin/python3.8 2 && \
# echo 0|update-alternatives --config python3
RUN apt-get update && \
apt-get install -y python3.8-dev python3-pip mysql-server default-libmysqlclient-dev git nginx uwsgi supervisor python3-seccomp
pip3
intall 依赖于python3
的安装。
RUN pip3 install termcolor mysqlclient flask bleach fstring ipdb flask_login astroid uwsgi Flask-Script
RUN cd /tmp && git clone https://github.com/pallets/werkzeug.git && cd werkzeug && pip3 install .
使用命令RUN useradd -s /usr/sbin/nologin --no-create-home oooser
添加一个不能登录的用户oooser
,并且不创建用户目录。
3.6 “&&” 和 “;”
&&
是为了让两个命令连续执行,只有前面命令执行成功,后面命令才继续执行;
“;
”不管前面命令执行成功没有,后面的命令继续执行。
3.7 ARG
ARG
构建参数,与 ENV
作用一至,不过作用域不一样。ARG
设置的环境变量仅对 Dockerfile
内有效,也就是说只有 docker build
的过程中有效,构建好的镜像内不存在此环境变量。
构建命令:ARG <参数名>[=<默认值>]
另外,docker build
中可以用 --build-arg <参数名>=<值>
来覆盖。
ARG THE_FLAG="OOO{THiS_iS_NoT_a_DRoiD_oR_a_FLaG}"
3.8 chown
在 shell
中,可以使用chown
命令来改变文件所有者。chown
命令是change
owner
(改变拥有者)的缩写。需要要注意的是,用户必须是已经存在系统中的,也就是只能改变为在 /etc/passwd
这个文件中有记录的用户名称才可以。
chown命令的用途很多,还可以顺便直接修改用户组的名称。此外,如果要连目录下的所有子目录或文件同时更改文件拥有者的话,直接加上 -R
的参数即可。
基本语法:
chown [-R] 账号名称 文件或目录
chown [-R] 账号名称:用户组名称 文件或目录
代码分析:
- Linux
touch命令
例如:touch /flag
用于修改文件或者目录的时间属性,包括存取时间和更改时间。若文件不存在,系统会建立一个新的文件; - 更改
目录权限到400
,目录内不可列详情,不可CAT,不可创建文件; - 命令
chown oooser:oooser /flag
改变文件的拥有者和群组。
RUN touch /flag && chmod 400 /flag && printf "$THE_FLAG" > /flag && chown oooser:oooser /flag
nginx
环境配置:
- 从与
dockerfile
同目录的主机文件夹中拷贝default
文件到docker
容器的/etc/nginx/sites-available/default
下。第二行代码也是相同原理。
# nginx setup
COPY setup/default /etc/nginx/sites-available/default
COPY setup/nginx.conf /etc/nginx/nginx.conf
database
环境配置:
RUN rm -rf /var/lib/mysql
删除容器中的/var/lib/mysql
目录;RUN mkdir /var/run/mysqld ; chown mysql:mysql /var/run/mysqld
命令用于创建目录并更改文件的用户和用户组;- 使用
RUN /usr/sbin/mysqld --initialize-insecure
命令确保该用户不设置密码; COPY setup/db.sql /root/db.sql
用于本地文件拷贝;RUN chmod 0400 /root/db.sql && chown root:root /root/db.sql
命令将/root/db.sql
设置为其他人的读权限;
# run db setup
RUN rm -rf /var/lib/mysql
RUN mkdir /var/run/mysqld ; chown mysql:mysql /var/run/mysqld
RUN /usr/sbin/mysqld --initialize-insecure
COPY setup/db.sql /root/db.sql
RUN chmod 0400 /root/db.sql && chown root:root /root/db.sql
3.9 sed 选项 动作
选项与参数:
-n
:使用安静(silent)模式。在一般 sed 的用法中,所有来自 STDIN 的数据一般都会被列出到终端上。但如果加上 -n 参数后,则只有经过sed 特殊处理的那一行(或者动作)才会被列出来。
-e
:直接在命令列模式上进行 sed 的动作编辑;
-f
:直接将 sed 的动作写在一个文件内, -f filename 则可以运行 filename 内的 sed 动作;
-r
:sed 的动作支持的是延伸型正规表示法的语法(默认是基础正规表示法语法);
-h
或–help 显示帮助;
-V
或–version 显示版本信息。
动作说明:
a
:新增, a 的后面可以接字串,而这些字串会在新的一行出现(目前的下一行)~
c
:取代, c 的后面可以接字串,这些字串可以取代 n1,n2 之间的行!
d
:删除,因为是删除啊,所以 d 后面通常不接任何咚咚;
i
:插入, i 的后面可以接字串,而这些字串会在新的一行出现(目前的上一行);
p
:打印,亦即将某个选择的数据印出。通常 p 会与参数 sed -n 一起运行~
s
:取代,可以直接进行取代的工作哩!通常这个 s 的动作可以搭配正规表示法!例如 1,20s/old/new/g 就是啦!
代码分析:
sed -i "s/.*bind-address.*/bind-address = 0.0.0.0/" /etc/mysql/my.cnf
命令把文件 /etc/mysql/my.cnf 中的 bind-address 替换为
bind-address = 0.0.0.0;RUN chmod 644 /app/ -R && find /app -type d -print0 |xargs -0 chmod 755
不懂啥意思
#COPY setup/mysqld.cnf /etc/mysql/mysql.conf.d/mysqld.cnf
RUN sed -i "s/.*bind-address.*/bind-address = 0.0.0.0/" /etc/mysql/my.cnf && \
sed -i "s/.*bind-address.*/bind-address = 0.0.0.0/" /etc/mysql/mysql.conf.d/mysqld.cnf
COPY setup/install_db.sh /root/install_db.sh
RUN chmod 0700 /root/install_db.sh && chown root:root /root/install_db.sh
RUN cd /root && /root/install_db.sh
COPY src/ /app
COPY src/dogooo_comments.py /app/dogooo_comments.py
COPY src/wsgi.py /app/wsgi.py
RUN chmod 644 /app/ -R && find /app -type d -print0 |xargs -0 chmod 755
COPY setup/dbcreds.json /
RUN chown oooser:oooser /dbcreds.json && chmod 400 /dbcreds.json
RUN chown oooser:oooser /app/present/images
RUN chmod 500 /app/dogooo_comments.py && chown oooser:oooser /app/dogooo_comments.py
RUN apt install -y strace
COPY setup/supervisord.conf /etc/supervisord.conf
COPY setup/uwsgi.ini /etc/uwsgi.ini
3.10 EXPOSE
仅仅只是声明端口。
作用:
帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射。
在运行时使用随机端口映射时,也就是 docker run -P
时,会自动随机映射 EXPOSE
的端口。
格式:EXPOSE <端口1> [<端口2>...]
3.11 WORKDIR
指定工作目录。用 WORKDIR
指定的工作目录,会在构建镜像的每一层中都存在。(WORKDIR
指定的工作目录,必须是提前创建好的)。
docker build
构建镜像过程中的,每一个 RUN
命令都是新建的一层。只有通过 WORKDIR
创建的目录才会一直存在。
格式:WORKDIR <工作目录路径>
3.12 CMD
使用 supervisord
的可执行路径启动服务。
EXPOSE 37453
WORKDIR /
CMD ["/usr/bin/supervisord"]
以上分析完毕,期待看到自己写的第一个关于比赛环境的dockerfile!😄😄😄
4 Docker配置存在的问题
1、Ubuntu16.04安装Docker:https://segmentfault.com/a/1190000014066388
2、Dockerfile创建镜像(主机对容器使用网络模式):docker build -t aflpin --network host .
vuzzer64 latest e416a0f4536c
aflpin latest bd018cfa6f4a
zjuchenyuan/base latest 4d89ad997fbc
3、Docker权限问题:Got permission denied while trying to connect to the Docker daemon socket at…
解决方案:通过将用户添加到docker用户组可以将sudo去掉
sudo groupadd docker #添加docker用户组
sudo gpasswd -a $USER docker #将登陆用户加入到docker用户组中
newgrp docker #更新用户组
4、Docker如何删除none镜像
先停止容器→删除容器→删除none镜像
$ docker images
$ docker rmi $(docker images | grep "none" | awk '{print $3}') //直接删除带none的镜像,直接报错了。提示先停止容器。
$ docker stop $(docker ps -a | grep "Exited" | awk '{print $1 }') //停止容器
$ docker rm $(docker ps -a | grep "Exited" | awk '{print $1 }') //删除容器
$ docker rmi $(docker images | grep "none" | awk '{print $3}') //删除镜像