Docker学习——Dockerfile
上一篇我们讲了docker的基本使用,掌握了前一篇,docker使用基本不成问题,但是要是你学习了Dockerfile,你会发现它使用起来有多方便了。项目最终部署时,我们希望docker容器打开时项目也随之运行,至于里面要运行什么命令才能运行起来,不是使用者该考虑的事情,光有上一篇的知识还做不到。
一、Dockerfile语法
使用Dockerfile去构建镜像好比堆积木,Docker通过对于在Dockerfile中的一系列指令的顺序解析实现自动的image的构建。Dockerfile是由一系列命令和参数构成的脚本,一个Dockerfile里面包含了构建整个image的完整命令。Docker通过docker build执行Dockerfile中的一系列命令自动构建image。下面就把Dockerfile中主要的命令介绍一下。
1、FROM
FROM <image>[:<tag> | @<digest>] [AS <name>]
FROM指定一个基础镜像,且必须为Dockerfile文件开篇的每个非注释行,至于image则可以是任何合理存在的image镜像
FROM可以在一个Dockerfile中出现多次,以便于创建混合的images。如果没有指定tag,latest将会被指定为要使用的基础镜像版本。
AS name,可以给新的构建阶段赋予名称。该名称可用于后续FROM 和 COPY --from=<name | index>说明可以引用此阶段中构建的镜像
2、LABEL
为镜像生成元数据标签信息,就是镜像的描述信息
LABEL <key>=<value> <key>=<value> <key>=<value> ... 比如: LABEL "com.example.vendor"="ACME Incorporated" LABEL com.example.label-with-value="foo" LABEL version="1.0" LABEL description="This text illustrates that label-values can span multiple lines."
3、MAINTAINER
作者信息,写在FROM后,指明该镜像的作者和其电子邮件,这个不是必须的项,而且官网显示将弃用
MAINTAINER zzf "xxxxxxx@qq.com"
4、COPY
将主机的文件复制到镜像内,如果目的位置不存在,Docker会自动创建所有需要的目录结构,但是它只是单纯的复制,并不会去做文件提取和解压工作。如:
语法: COPY [--chown=<user>:<group>] <src>... <dest> COPY [--chown=<user>:<group>] ["<src>",... "<dest>"] 例如: COPY hom* /mydir/ # 添加所有以hom开头的文档到/mydir/下 COPY hom?.txt /mydir/ # ? is replaced with any single character, e.g., "home.txt" COPY test relativeDir/ # adds "test" to `WORKDIR`/relativeDir/ COPY test /absoluteDir/ # adds "test" to /absoluteDir/ COPY arr[[]0].txt /mydir/ # copy a file named "arr[0].txt" to /mydir/
注意:需要复制的目录一定要放在Dockerfile文件的同级目录下
因为构建环境将会上传到Docker守护进程,而复制是在Docker守护进程中进行的。任何位于构建环境之外的东西都是不可用的。COPY指令的目的的位置则必须是容器内部的一个绝对路径。
5、ADD
ADD指令类似于COPY指令,ADD支持使用TAR文件和URL路径,但是ADD会对压缩文件(tar, gzip, bzip2, etc)做提取和解压操作。
语法: ADD <src>...<dest> ADD ["<src>",..."<dest>"] 规则: 1 如果<src>为URL且<dest>不以/结尾,则<src>指定的文件将被下载并直接被创建为<dest>;如果<dest>以/结尾,则文件名URL指定的文件将被直接下载并保存为<dest>/<filename> 2 如果<src>是一个本地文件系统上的压缩格式的tar文件,它将被展开为一个目录,其行为类似于"tar -x"命令;然而,通过URL获取到的tar文件将不会自动展开。 3 如果<src>有多个,或其间接或直接使用了通配符,则<dest>必须是一个以/结尾的目录路径;如果<dest>不以/结尾,则其被视作一个普通文件,<src>内容将被直接写入到<dest> 4 为了让镜像尽量小,最好不要使用 ADD 指令从远程 URL 获取包,而是使用 curl 和 wget。这样你可以在文件提取完之后删掉不再需要的文件来避免在镜像中额外添加一层。 例如: ADD http://example.com/1.tar.gz /apps/ RUN tar xf /apps/1.tar.gz -C /apps/ && \ /bin/sh -c /apps/***.sh 简单操作: RUN mkdir -p /iyunwen/server/ && \ curl -SL http://example.com/1.tar.gz \ | tar -xzC /iyunwen/server/ && \ /bin/sh -c /apps/***.sh
6、WORKDIR
用于为Dockerfile中所有RUN、CMD、ENTRYPOINT、COPY和ADD指令设定工作目录,如果不存在,则会创建目录。在Dockerfile文件中,WORKDIR指令可以出现多次,其路径也可以为相对路径,不过,其是相对此前一个WORKDIR指令指定的路径,另外,WORKDIR也可调用由ENV指定定义的变量,如
WORKDIR /usr/local
WORKDIR webservice
RUN echo 'hello docker' > text.txt
...
最终会在/usr/local/webservice/
目录下生成text.txt文件
WORKDIR $STATEPATH
7、RUN
接受命令作为参数并用于创建镜像,在之前的commit层上形成新的层。在新镜像内部执行的命令,比如安装一些软件、配置一些基础环境,可使用\来换行
语法: RUN \<command\>(如同执行shell命令 /bin/sh -c) RUN ["executable","param1","param2"]
- RUN 指令将在当前image中执行任意合法命令并提交执行结果。命令执行提交后,就会自动执行Dockerfile中的下一个指令。
- 分层RUN指令和生成提交符合Docker的核心概念,其中提交很轻量,可以从image将用于Dockerfile中的下一步。
- exec形式使得可以避免shell字符串变化,以及使用不包含指定的shell可执行文件的基本image来运行RUN命令。
- 在shell形式中,可以使用\(反斜杠)将单个RUN指令继续到下一行。例如:
RUN yum install -y \ openssl \ pcre-devel \ zlib
- 第二种语法格式中的参数是一个JSON格式的数组,其中<executable>为要运行的命令,后面的<paramN>为传递给命令的选项或对数;然而,此种格式指定的命令不会以"/bin/sh -c"来发起,因此常见的shell操作如变量替换以及通配符(?,*等)替换将不会进行;不过,如果要运行的命令依赖于此shell特性的话,可以将其替换为类似下面的格式。
RUN ["/bin/bash","-c","<executable>","<param1>"]
RUN 指令的缓存在下一次构建期间不会自动失效。用于诸如:yum repolist 之类的指令的缓存将在下一次构建期间被重用。可以通过--no-cache 参数来使RUN指令的缓存无效,例如: docker build --no-cache
管理命令
某些RUN 命令依赖于使用管道字符( | )将管道输出到另一个命令功能
RUN wget -O - http://www.baidu.com/index.html | wc -l > /app/html/baidu.html
Docker使用 /bin/sh -c 解释执行这些命令,解释器只评估管道中最后一个操作的退出代码以确定成功。在上面的例子中,只要wc -l 命令成功,即使wget 命令失败,该构建步骤也会成功并生成新的镜像。
由于管道中任何阶段的错误而导致命令失败,请预先 set -o pipefail && 确保意外错误可防止构建无意中成功。例如:
set -o pipefail : 表示在管道连接的命令序列中,只要有任何一个命令返回非0值,则整个管道返回非0值,即使最后一个命令返回0.
RUN set -o pipefail && wget -O - http://www.baidu.com/index.html | wc -l > /app/html/baidu.html
注意:
并非所有的shell都支持 -o pipefail 选项。在这种情况下(例如 dash shell,这是基于Debian的映像上的默认shell),请考虑使用exec形式RUN来明确选择一个支持该pipefail选项的shell。如:
RUN ["/bin/bash","-c","set -o pipefail && wget -O - http://www.baidu.com/index.html | wc -l > /app/html/baidu.html"]
8、CMD
类似于RUN指令,CMD指令也可用于运行任何命令或应用程序,不过,二者的运行时间点不同
- RUN 指令运行于映像文件构建过程中,而CMD指令运行于基于Dockerfile构建出的新镜像文件启动一个容器时。
- CMD指令的首要目的在于为启动的容器指定默认要运行的程序,且其运行结束后,容器也将终止;不过,CMD指定的命令其可以被docker run的命令行选项所覆盖
- 在Dockerfile中可以存在多个CMD指令,但仅最后一个生效
语法: CMD <command> //支持命令展开,但是不支持传递信号 CMD ["<executable>","<param1>","<param2>"] //相当于容器的第一个命令,可以接受信号 CMD ["param1","param2"] 前两种语法格式的意义同RUN 第三种则用于为ENTRYPOINT指令提供默认参数
CMD会在启动容器的时候执行,build时不执行,而RUN只是在构建镜像的时候执行,后续镜像构建完成之后,启动容器就与RUN无关了。这个命令就相当于在/etc/rc.d/rc.local中写命令
9、ENTRYPOINT
类似CMD指令的功能,用于为容器指定默认运行程序,从而使得容器像是一具单独的可执行程序
与CMD不同的是,由ENTRYPOINT启动的程序不会被docker run命令行指定的参数所覆盖,而且,这些命令行参数会被当作参数传递给ENTRYPOINT指定的程序。不过,docker run 命令的--entrypoint 选项的参数可覆盖ENTRYPOINT指令指定的程序
语法: ENTRYPOINT <command> //这种方式能接受shell命令行展开 ENTRYPOINT ["<executable>","param1"] //展开不了,但能接收到信号
注意1:
docker run命令传入的命令参数会覆盖CMD指令的内容并且附加到ENTRYPOINT命令最后做为其参数使用。Dockerfile文件中也可以存在多个ENTRYPOINT指令,但仅有最后一个会生效
注意2:
RUN、CMD、ENTRYPOINT如果是后面接中括号时[ ],里面的引号一定要用双引号。即像下面这样是不会正确执行的。
ENTRYPOINT ['<executable>','param1']
10、EXPOSE
用来指定端口,使容器内的应用可以通过端口和外界交互。
EXPOSE <port> [<port>...]
告诉Docker服务端容器对外映射的本地端口,需要在docker run 的时候使用-p 或者 -P 选项生效。
EXPOSE 5000
11、ENV
ENV指令可以用于docker容器设置环境变量
语法: ENV <key> <value> ENV <key>=<value> ...
指定一个环境变量,会被后续RUN指令使用,并在容器运行时保留。
ENV设置的环境变量,可以使用 docker inspect 命令来查看。同时还可以使用 docker run --env <key>=<value>来修改环境变量
12、USER
用于指定运行image时的或运行Dockerfile中任何RUN、CMD或ENTRYPOINT指令指定的程序时的用户名或UID
默认情况下,container的运行身份为root用户
语法:
USER <UID>|<UserName>
需要注意的是,<UID>可以为任意数字,但实践中其必须为/etc/passwd中某用户的有效UID,否则,docker run命令将运行失败
13、ONBUILD
用于在Dockerfile中定义一个触发器
Dockerfile用于build镜像文件,此镜像文件亦可作为base image被另一个Dockerfile用作FROM指令的参数,并以之构建新的镜像文件
在后的这个Dockerfile中的FROM指令在build过程中被执行时,将会“触发”创建其base image的Dockerfile文件中的ONBUILD指令定义的触发器
语法:
ONBUILD <INSTRUCTION>
注意:
尽管任何指令都可注册成为触发器指令,但ONBUILD不能自我嵌套,且不会触发FROM和MAINTAINER指令
使用包含ONBUILD指令的Dockerfile构建的镜像应该使用特殊的标签,例如ruby:2.0-onbuild
在ONBUILD指令中使用ADD或COPY指令应该格外小心,因为新构建过程和上下文在缺少指定的源文件时会失败。
14、ARG
ARG指令定义了用户可以在编译时或者运行时传递的变量
ARG <name>[=<default value>]
ENV指令是在dockerfile里面设置环境变量,不能在编译时或运行时传递。
二、举个栗子
我们做一个非常简单的例子,只是为了使用Dockerfile创建一个image:存一个hello.py文件到基础python镜像的/opt目录下,并最后运行它,最后容器启动时能自动运行。
我在本地Flask_web文件夹下新建了一个hello.py文档,内容如下:
from flask import Flask app = Flask(__name__) @app.route('/') def hello(): return 'hello world!' if __name__ == '__main__': app.run(host='0.0.0.0')
就是一个简单的flask语句,只返回一个hello world。
然后我们在本地新建一个Dockerfile文件,注意D大写,内容如下:
FROM python_v5:latest ADD ./hello.py /opt WORKDIR /opt EXPOSE 5000 CMD python hello.py
可见我们运用python_v5:latest 作为基础镜像,这里就是一个python镜像安装了flask库后commit出来的一个新镜像。然后把本地hello.py 文件添加到容器/opt 目录下,然后容器运行时的工作目录设置为/opt,即容器会自动跳转到/opt下才执行下面的CMD语句,然后开放5000端口给外部,最后容器启动时执行python hello.py的shell 命令,这里要注意如果要执行多条shell命令,多条命令之间可以用&&连接起来即可,容器会自动调用/bin/sh -c 执行里面的命令。
写好了Dockerfile文件,我们就可以开始建立我们的镜像了,这里我们要学习一个新命令,这个专为Dockerfile所用
docker build
命令用于使用 Dockerfile 创建镜像。
语法:
docker build [OPTIONS] PATH | URL | -
OPTIONS说明:
-
--build-arg=[] :设置镜像创建时的变量;
-
--cpu-shares :设置 cpu 使用权重;
-
--cpu-period :限制 CPU CFS周期;
-
--cpu-quota :限制 CPU CFS配额;
-
--cpuset-cpus :指定使用的CPU id;
-
--cpuset-mems :指定使用的内存 id;
-
--disable-content-trust :忽略校验,默认开启;
-
-f :指定要使用的Dockerfile路径;
-
--force-rm :设置镜像过程中删除中间容器;
-
--isolation :使用容器隔离技术;
-
--label=[] :设置镜像使用的元数据;
-
-m :设置内存最大值;
-
--memory-swap :设置Swap的最大值为内存+swap,"-1"表示不限swap;
-
--no-cache :创建镜像的过程不使用缓存;
-
--pull :尝试去更新镜像的新版本;
-
--quiet, -q :安静模式,成功后只输出镜像 ID;
-
--rm :设置镜像成功后删除中间容器;
-
--shm-size :设置/dev/shm的大小,默认值是64M;
-
--ulimit :Ulimit配置。
-
--tag, -t: 镜像的名字及标签,通常 name:tag 或者 name 格式;可以在一次构建中为一个镜像设置多个标签。
-
--network: 默认 default。在构建期间设置RUN指令的网络模式
最常用的命令就是
docker build -t image_name:tag(定义要生成的镜像的名称和版本号) path(Dockerfile文件所在的目录路径)
接下来我们在Flask_web目录下执行如下:
可见它一步一步地执行了我们Dockerfile里的命令,我这里多加了个run.sh命令进去,大家忽略就好。用docker images查看到我们简历好了命名为flask的镜像。
下面我们启动它,看是不是执行了我们Dockerfile文件中最后CMD中给的shell 命令,即运行hello.py文件,这里我们可以用-d在后台运行,也可以-it交互式运行,交互式运行能看到输出结果。我这里用交互式运行,而且因为是flask web程序,需要端口连接,这里用-p 宿主机5000端口和容器5000端口匹配,这样我们访问本地5000端口就可以访问到容器的5000端口了,我们Dockerfile文件EXPOSE写了容器开放端口是5000:
可见flask跑起来了,那我们测试一下:
而且在其他电脑输入本机IP:5000也可以运行:
好了,是不是觉得Dockerfile很方便呢?镜像做好了,把它保存为tar格式文件就可以传给别人使用了,使用时只需要下载镜像到本地然后run命令开启容器即可,并且开启容器的同时程序也跑起来了,这样就做到了用户和设计的完全隔离,即用户不用去管容器里面程序是怎么跑起来的,启动容器就可以使用。