Dockerfile
基本结构
Dockerfile 是一个文本格式的配置文件,用户可以使用 Dockerfile 快速创建自定义镜像。
Dockerfile 由一行行命令语句组成,并且支持以 # 开头的注释行。
Docker分为四部分:
- 基础镜像信息
- 维护者信息
- 镜像操作指令
- 容器启动时默认要执行的指令
例如:
# This dockerfile uses the ubuntu image
# VERSION 2 - EDITION 1
# Author: seancheng
# Command format: Instruction [arguments / command] ...
# 第一行必须指定基于的基础镜像
FROM ubuntu
# 维护者信息
LABEL MAINTAINER=' xiaoyang xiaoyang@163.com' // 作者署名及邮箱
# 镜像操作指令
RUN echo "deb http://archive.ubuntu.com/ubuntu/ raring main universe" >> /etc/apt/sources.list
RUN apt-get update && apt-get install -y nginx
RUN echo "\ndaemon off;" >> /etc/nginx/nginx.conf
# 容器启动时默认要执行的指令
CMD /usr/sbin/nginx
其中,一开始必须指明所基于的镜像名称,接下来一般会说明维护者信息。
后面则是镜像操作指令,例如RUN指令,RUN指令将对镜像执行跟随的命令。每运行一条RUN指令,镜像添加新的一层,并提交。
最后是CMD指令来指定运行容器时的操作指令。
一、Dockerfile Introduction
前面的docker镜像管理章节有说到,构建镜像的方式有两种:一种是基于容器制作,另一种就是通过Dockerfile。Dockerfile其实就是我们用来构建Docker镜像的源码,当然这不是所谓的编程源码,而是一些命令的组合,只要理解它的逻辑和语法格式,就可以编写Dockerfile了。
简要概括Dockerfile的作用:它可以让用户个性化定制Docker镜像。因为工作环境中的需求各式各样,网络上的镜像很难满足实际的需求。
二、Dockerfile Format(格式)
Dockerfile整体就两类语句组成:
- # Comment 注释信息
- Instruction arguments 指令 参数,一行一个指令。
编写时应注意:
-
Dockerfile文件名首字母必须大写。
-
Dockerfile指令不区分大小写,但是为方便和参数做区分,通常指令使用大写字母。
-
Dockerfile中指令按顺序从上至下依次执行。
-
Dockerfile中第一个非注释行必须是FROM指令,用来指定制作当前镜像依据的是哪个基础镜像。
-
Dockerfile中需要调用的文件必须跟Dockerfile文件在同一目录下,或者在其子目录下,父目录或者其它路径无效。
三、Dockerfile Instructions
FROM
-
Introduction
- FROM指令必须为Dockerfile文件开篇的第一个非注释行,用于指定构建镜像所使用的基础镜像,后续的指令运行都要依靠此基础镜像所提供的的环境(简单说就是假如Dockerfile中所引用的基础镜像里面没有mkdir命令,那后续的指令是没法使用mkdir参数的。)
- 实际使用中,如果没有指定仓库,docker build会先从本机查找是否有此基础镜像,如果没有会默认去Docker Hub Registry上拉取,再找不到就会报错。
-
Syntax(语法)
- FROM <Repository>[:<Tag>]
- FROM <Repository>@<Digest>
- Digest:镜像的哈希码,防止镜像被冒名顶替。
MAINTAINER(维护者)
-
Introduction
- 用于让Dockerfile的作者提供个人的信息
- Dockerfile并不限制MAINTAINER指令的位置,但是建议放在FROM指令之后
- 在较新的docker版本中,已经被LABEL替代。
-
Syntax(语法)
- MAINTAINER “xiaoyang@163.com”
COPY
-
Introduction
- 复制宿主机上的文件到目标镜像中
-
Syntax(语法)
- COPY <src>… <dest>
- COPY ["<src>",… “<dest>”]
- <src>:要复制的源文件或者目录,支持通配符
- <dest>:目标路径,即正创建的镜像的文件系统路径,建议使用绝对路径,否则,COPY指令会以WORKDIR为其起始路径。
- 如果路径中如果包含空白字符,建议使用第二种格式用引号引起来,否则会被当成两个文件。
-
Rules
- <src>必须是build上下文中的目录,不能是其父目录中的文件。
- 如果<src>是目录,则其内部的文件或则子目录会被递归复制,但<src>目录本身不会被复制。
- 如果指定了多个<src>,或者<src>中使用通配符,则<dest>必须是一个目录,且必须以 / 结尾。
- 如果<dest>事先不存在,它将会被自动创建,包括其父目录路径。
ADD
-
Introduction
- ADD指令跟COPY类似,不过它还支持使用tar文件和URL路径。
- 当拷贝的源文件是tar文件时,会自动展开为一个目录并拷贝进新的镜像中;然而通过URL获取到的tar文件不会自动展开。
- 主机可以联网的情况下,docker build可以将网络上的某文件引用下载并打包到新的镜像中。
- ADD指令跟COPY类似,不过它还支持使用tar文件和URL路径。
-
Syntax(语法)
- ADD<src>… <dest>
- ADD ["<src>",… “<dest>”]
WORKDIR
-
Introduction
- 同docker run -w
- 指定工作目录,可以指多个,每个WORKDIR只影响他下面的指令,直到遇见下一个WORKDIR为止。
- WORKDIR也可以调用由ENV指令定义的变量。
-
Syntax(语法)
- WORKDIR 相对路径或者绝对路径
- 相对路径是相对于上一个WORKDIR指令的路径,如果上面没有WORKDIR指令,那就是当前Dockerfile文件的目录。
VOLUME
-
Introduction
- docker run -v简化版
- 用于在镜像中创建一个挂载点目录。上一章中有提到Volume有两种类型:绑定挂载卷和docker管理的卷。在dockerfile中只支持docker管理的卷,也就是说只能指定容器内的路径,不能指定宿主机的路径。
-
Syntax(语法)
-
VOLUME <mountpoint>
-
VOLUME ["<mountpoint>"]
EXPOSE
-
Introduction
- 同docker run --expose
- 指定容器中待暴露的端口。比如容器提供的是一个https服务且需要对外提供访问,那就需要指定待暴露443端口,然后在使用此镜像启动容器时搭配 -P 的参数才能将待暴露的状态转换为真正暴露的状态,转换的同时443也会转换成一个随机端口,跟 -p :443一个意思。
- EXPOSE指令可以一次指定多个端口,例如:EXPOSE 11111/udp 11112/tcp
-
Syntax(语法)
- EXPOSE <port>[/<protocol>] [<port>[/<protocol>] …]
- <protocol>用于指定协议类型,如果不指定,默认TCP协议。
ENV
-
Introduction
- 同docker run -e
- 为镜像定义所需的环境变量,并可被ENV指令后面的其它指令所调用。调用格式为 v a r i a b l e n a m e 或 者 variable_name或者 variablename或者{variable_name}
- 使用docker run启动容器的时候加上 -e 的参数为variable_name赋值,可以覆盖Dockerfile中ENV指令指定的此variable_name的值。但是不会影响到dockerfile中已经引用过此变量的文件名。下面有举例说明:
-
Syntax(语法)
- ENV <key> <value>
- ENV <key>=<value> …
- 第一种格式一次只能定义一个变量,之后所有内容都会被视为的组成部分
- 第二种格式一次可以定义多个变量,每个变量为一个"="的键值对,如果中包含空格,可以用反斜线 \ 进行转义,也可以为加引号,另外参数过长时可用反斜线做续行。
- 定义多个变量时,建议使用第二种方式,因为Dockerfile中每一行都是一个镜像层,构建起来比较吃资源。
-
Example (事例)
基于busybox启动一个镜像,将test文件拷贝至容器内的/usr/local/abc/目录下。
[root@docker ~]# echo "hello" >/opt/test
[root@docker ~]# cd /opt/
[root@docker opt]# ls
containerd test web
[root@docker opt]# vim dockerfile //编写dockerfile文件
# Description: test image
FROM busybox
ENV file=abc
ADD ./test /usr/local/$file/
[root@docker opt]# docker build -t busy:v1 ./ 生成镜像
Sending build context to Docker daemon 15.87kB
Step 1/3 : FROM busybox
---> d23834f29b38
Step 2/3 : ENV file=abc
---> Running in 14746f7f8682
Removing intermediate container 14746f7f8682
---> 747fe5b741b7
Step 3/3 : ADD ./test /usr/local/$file/
---> d4ba46729151
Successfully built d4ba46729151
Successfully tagged busy:v1
#查看新生成的镜像
[root@docker opt]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
busy v1 d4ba46729151 4 minutes ago 1.24MB
busybox latest d23834f29b38 7 days ago 1.24MB
# 根据此镜像启动容器并查看文件是否拷贝成功,并且查看file变量的值
[root@docker opt]# docker run --name busy --rm busy:v1 ls /usr/local/abc
test
[root@docker opt]# docker run --name busy --rm busy:v1 printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=051371212bbd
file=abc
HOME=/root
# 接下来我们在启动容器的时候加上-e参数为file变量指定一个新值,并且查看file变量的值
[root@docker opt]# docker run --name busy -e file=bug --rm busy:v1 printenv
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
HOSTNAME=277595f1e5e7
file=bug
HOME=/root
# 此时再看test的文件,依然是在abc的目录下的
[root@docker opt]# docker run --name busy -e file=bug --rm busy:v1 ls /usr/local/abc
test
# 这是因为docker build属于第一阶段,而docker run属于第二阶段。第一阶段定义file变量的值abc已经被引用了,后续阶段再改file变量的值也影响不了abc。
RUN
-
Introduction
- 用于指定docker build过程中运行的程序,可以是任何命令。
- RUN指令后所执行的命令必须在FROM指令后的基础镜像中存在才行。
-
Syntax
- RUN <command>
- RUN [“executable”, “param1”, “param2”]
- <command>通常是一个shell命令,系统默认会把后面的命令作为shell的子进程来运行,以"/bin/sh -c"来运行它,也就意味着此进程在容器中的PID一定不为1,如果是1完事就结束了哇。
- 第二种格式的参数是一个JSON格式的数组,其中"executable"为要运行的命令,后面的"paramN"为传递给命令的选项或参数。此格式指定的命令不会以"/bin/sh -c"来发起,也就是直接由内核创建,因此不具备shell特性,类似于RUN [ “echo”, “$HOME” ],是无法识别 $ 的;如果想要依赖shell特性,可以替换命令为这样的格式[ “/bin/sh”, “-c”, “echo $HOME” ]。
CMD
-
Introduction
- 指定启动容器的默认要运行的程序,也就是PID为1的进程命令,且其运行结束后容器也会终止。如果不指定,默认是bash。
- CMD指令指定的默认程序会被docker run命令行指定的参数所覆盖。
- Dockerfile中可以存在多个CMD指令,但仅最后一个生效。因为一个docker容器只能运行一个PID为1的进程。
- 类似于RUN指令,也可以运行任意命令或程序,但是两者的运行时间点不同
- RUN指令运行在docker build的过程中,而CMD指令运行在基于新镜像启动容器(docker run)时。
-
Syntax(语法)
- CMD command param1 param2 在/bin/sh中执行,提供给需要交互的应用
- CMD [“executable”,“param1”,“param2”] 使用exec执行,推荐方式
- CMD [“param1”,“param2”] 提供给ENTRYPOINT的默认参数
前两种语法格式同RUN指令。第一种用法对于CMD指令基本没有意义,因为它运行的程序PID不为1。
第三种则需要结合ENTRYPOINT指令使用,CMD指令后面的命令作为ENTRYPOINT指令的默认参数。如果docker run命令行结尾有参数指定,那CMD后面的参数不生效。
ENTRYPOINT
-
Introduction
- 类似CMD指令的功能,用于为容器指定默认运行程序。
- Dockerfile中可以存在多个ENTRYPOINT指令,但仅最后一个生效
- 与CMD区别在于,由ENTRYPOINT启动的程序不会被docker run命令行指定的参数所覆盖,而且这些命令行参数会被当做参数传递给ENTRYPOINT指令指定的程序。
- 不过,docker run的–entrypoint选项的参数可覆盖ENTRYPOINT指定的默认程序。示例如下:
-
Syntax(语法)
- ENTRYPOINT command param1 param2
- ENTRYPOINT [“executable”, “param1”, “param2”]
-
Example(事例)
以httpd服务做举例,CMD指令
[root@docker opt]# vim dockerfile //编写 部署站点
# Description: test image
FROM busybox
ENV WEBDIR="/usr/web/html"
RUN mkdir -p ${WEBDIR} && \
echo 'this is a test web' > ${WEBDIR}/index.html
CMD [ "sh","-c","/bin/httpd -f -h ${WEBDIR}" ]
[root@docker opt]# docker build -t httpd:v1 ./
此时为前台运行,重启打开一个窗口,kill掉容器,然后开始docker run结尾传入新的指令
[root@docker opt]# docker run --name web -it --rm httpd:v1
[root@docker local]# docker kill web
web
[root@docker local]# docker run --name web -it --rm httpd:v1 ls /usr/web/html //发现此时的执行命令为 ls
index.html
# 可以看出命令行的参数已经替代了原本的CMD指令指定的程序
# 用ENTRYPOINT指令做测试。
[root@docker opt]# vim dockerfile
# Description: test image
FROM busybox
ENV WEBDIR="/usr/web/html"
RUN mkdir -p ${WEBDIR} && \
echo 'this is a test web' > ${WEBDIR}/index.html
ENTRYPOINT [ "sh","-c","/bin/httpd -f -h ${WEBDIR}" ]
# 生成镜像
[root@docker opt]# docker build -t httpd:v2 ./
[root@docker opt]# docker run --name sed -it --rm httpd:v2 # 也是前台启动,重新一个窗口,kill掉容器,然后开始docker run结尾传入新的指令
[root@docker opt]# docker run --name sed -it --rm httpd:v2 ls /usr/web/html
# 可以看到没有反应,这种情况其实是吧ls /usr/web/html当做参数传给了/bin/httpd -f -h ${WEBDIR}程序。只是httpd不识别。
# 我们kill掉容器, 加上--entrypoint参数再试一下
[root@docker opt]# docker run --name sed1 -it --rm --entrypoint="" httpd:v2 ls /usr/web/html
index.html
使用--entrypoint参数替换命令成功。
USER
-
Introduction
- 用于指定docker build过程中任何RUN、CMD等指令的用户名或者UID。
- 默认情况下容器的运行用户为root。
-
Syntax
- USER <user>[:<group>]
- USER <UID>[:<GID>]
- 实践中UID需要是/etc/passwd中某用户的有效UID,否则docker run命令将运行失败。
ONBUILD
- Introduction
- 用于在Dockerfile中定义一个触发器。
- ONBUILD后面指定的指令在docker build时是不会执行,构建完的镜像在被另一个Dockerfile文件中FROM指令所引用的时才会触发执行。
- Syntax(语法)
- ONBUILD [INSTRUCTION]
- 几乎任何指令都可以成为触发器指令,但ONBUILD不能自我嵌套,且不会触发FROM和MAINTAINER指令,多数情况是使用RUN或者ADD。
- 另外在使用COPY指令时,应该注意后续引用该镜像的Dockerfile的同级目录下是否有被拷贝的文件。
- ONBUILD [INSTRUCTION]
- Example
例如,Dockerfile使用如下的内容创建了镜像image-A
[...]
ONBUILD ADD . /app/src
ONBUILD RUN /usr/local/bin/python-build --dir /app/src
[...]
此时,如果基于image-A创建新的镜像时,新的Dockerfile中使用FROM image-A指定基础镜像时,会自动执行ONBUILD指令的内容,等价于在后面添加了两条指令。
FROM image-A
# Automatically run the following
ADD . /app/src
RUN /usr/local/bin/python-build --dir /app/src
使用ONBUILD指令的镜像,推荐在标签中注明,例如ruby:1.9-onbuild。
创建镜像
编写完成Dockerfile后,可以通过docker build命令来创建镜像。
基本的格式为docker build [选项] 路径,该命令将读取指定路径下(包括子目录)的Dockerfile,并将该路径下所有内容发送给Docker服务端,由服务端来创建镜像。因此一般建议放置Dockerfile的目录为空目录。
另外,可以通过 .dockerignore 文件(每一行添加一条匹配模式)来让Docker忽略路径下的目录和文件。
要指定镜像的标签信息,可以通过-t选项。
例如,指定Dockerfile所在路径为/tmp/docker_builder/,并且希望生成镜像标签为build_repo/first_image,可以使用下面的命令:
docker build -t build_repo/first_image /tmp/docker_builder/
源码apache的Dockerfile文件
目录结构
[root@docker apache]# tree /apache/
/apache/
├── dockerfile
└── files
├── apr-1.7.0.tar.gz
├── apr-util-1.6.1.tar.gz
└── httpd-2.4.48.tar.gz
和脚本一样,在编写前先创建相关目录以及源码包
[root@docker ~]# mkdir -p /apache/files
[root@docker ~]# ls /apache/
files
将相关的源码包上传至指定目录
[root@docker ~]# ls /apache/files/
apr-1.7.0.tar.gz apr-util-1.6.1.tar.gz httpd-2.4.48.tar.gz
编写dockerfile文件
[root@docker ~]# vim /apache/dockerfile
FROM centos
LABEL MAINTAINER='aoman xx@163.com'
ENV apr_version=1.7.0 apr_util_version=1.6.1 httpd_version=2.4.48
ENV dir=/usr/src/ install_dir=/usr/local/
ADD files/* ${dir}
WORKDIR ${dir}
RUN yum -y install openssl-devel pcre-devel pcre expat-devel libtool gcc gcc-c++ make && \
cd apr-${apr_version} && sed -i '/$RM "$cfgfile"/d' configure && \
./configure --prefix=${install_dir}/apr && make && make install && \
cd ../apr-util-${apr_util_version} && \
./configure --prefix=${install_dir}/apr-util --with-apr=${install_dir}/apr && \
make && make install && \
cd ../httpd-$httpd_version && \
./configure --prefix=${install_dir}/apache \
--enable-so \
--enable-ssl \
--enable-cgi \
--enable-rewrite \
--with-zlib \
--with-pcre \
--with-apr=${install_dir}/apr \
--with-apr-util=${install_dir}/apr-util/ \
--enable-modules=most \
--enable-mpms-shared=all \
--with-mpm=prefork && \
make && make install && \
sed -i '/#ServerName/s/#//g' ${install_dir}/apache/conf/httpd.conf
EXPOSE 80 443
VOLUME ["${install_dir}/apache/htdocs/"]
CMD ["-D","FOREGROUND"]
ENTRYPOINT ["/usr/local/apache/bin/httpd"]
构建镜像
[root@docker ~]# docker build -t xm17671855780/file_httpd:v0.1
Sending build context to Docker daemon 11.1MB
Step 1/9 : FROM centos
---> 5d0da3dc9764
Step 2/9 : LABEL MAINTAINER='bravealove1 593582553@qq.com'
---> Using cache
---> 0ac4d3e6c784
Step 3/9 : ADD files/* /usr/src/
---> Using cache
---> 1a0a3fe9d89f
Step 4/9 : WORKDIR /usr/src/
---> Using cache
---> 61a8f1dbb8ab
Step 5/9 : RUN yum -y install openssl-devel pcre-devel pcre expat-devel libtool gcc gcc-c++ make && cd apr-1.7.0 && sed -i '/$RM "$cfgfile"/d' configure && ./configure --prefix=/usr/local/apr && make && make install && cd ../apr-util-1.6.1 && ./configure --prefix=/usr/local/apr-util --with-apr=/usr/local/apr && make && make install && cd ../httpd-2.4.49 && ./configure --prefix=/usr/local/apache --enable-so --enable-ssl --enable-cgi --enable-rewrite --with-zlib --with-pcre --with-apr=/usr/local/apr --with-apr-util=/usr/local/apr-util/ --enable-modules=most --enable-mpms-shared=all --with-mpm=prefork && make && make install
---> Using cache
---> d400eb8c4286
Removing intermediate container d798f042fe3a
---> 754ad8ba9043
Step 6/8 : EXPOSE 80 443
---> Running in aac357026dfb
Removing intermediate container aac357026dfb
---> aaa530543669
Step 7/8 : VOLUME ["/usr/local/apache/htdocs/"]
---> Running in e7782ec5a8ae
Removing intermediate container e7782ec5a8ae
---> 1cab72f37a7b
Step 8/8 : CMD ["/usr/local/apache/bin/apachectl","-D","FOREGROUND"]
---> Running in c4607cd29504
Removing intermediate container a4607cd29504
---> c7598a3b24b3
Successfully built a563454c4a5f
Successfully tagged xm17671855780/file_httpd:v0.2
使用新的镜像运行一个容器进行测试
[root@docker apache]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
xm17671855780/file_httpd v0.2 a563454c4a5f About a minute ago 701MB
centos latest 5d0da3dc9764 2 months ago 231MB
[root@docker ~]# docker run -dit --name apache -p 80:80 a563454c4a5f
2bd14742fa4bc405728bfdf01fc70d78c2968c4eaa50e5b45eb68d3a672c9293
[root@docker apache]# ss -anlt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 128 0.0.0.0:111 0.0.0.0:*
LISTEN 0 128 0.0.0.0:80 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 128 [::]:111 [::]:*
LISTEN 0 128 [::]:80 [::]:*
LISTEN 0 128 [::]:22 [::]:*
访问测试