Dockerfile编写学习——以dc2020q-dogooos-public环境的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 指令,用于运行程序,但二者运行的时间点不同:
CMDdocker 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-buildDockerfile 里的 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安装Dockerhttps://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}') //删除镜像 
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值