Docker|Docker 之 dockerfile制作镜像
1概述
基于容器制作镜像的问题是,每次都要启动一个容器,在容器内部执行相关命令,才进行制作容器,这个比较麻烦。还有一种情况,可能是制作后的容器,容器变得很庞大,用户拷贝也可能成问题,如果通过dockerfile,相当于是一个文档,客户可以基于dockerfile生成新的容器
dockerfile仅仅是用来制作镜像的源码文件,是构建容器过程中的指令,docker能够读取dockerfile的指定进行自动构建容器,基于dockerfile制作镜像,每一个指令都会创建一个镜像层,即镜像都是多层叠加而成,因此,层越多,效率越低,创建镜像,层越少越好。因此能在一个指令完成的动作尽量通过一个指令定义。
docker镜像制作的工作逻辑
首先需要有一个制作镜像的目录,该目录下有个文件,名称必须为Dockerfile,Dockerfile有指定的格式,#号开头为注释,,指定默认用大写字母来表示,以区分指令和参数,docker build读取Dockerfile是按顺序依次Dockerfile里的配置,且第一条非注释指令必须是FROM 开头,表示基于哪个基础镜像来构建新镜像。可以根据已存在的任意镜像来制作新镜像。
Dockerfile可以使用环境变量,用ENV来定义环境变量,变量名支持bash的变量替换,如${variable:-word},表示如果变量值存在,就使用原来的变量,变量为空时,就使用word的值作为变量的值,一般使用这个表示法。
${variable:+word},表示如果变量存在了,不是空值,那么变量将会被赋予为word对应的值,如果变量为空,那么依旧是空值
.dcokerignore:把文件路径写入到.dockerignore,对应的路径将不会被打包到新镜像
2指令介绍 FROM
FROM指令是最重的一个且必须为 Dockerfile文件开篇的第一个非注释行,用于为映像文件构建过程指定基准镜像,后续的指令运行于此基准镜像所提供的运行环境 .
实践中,基准镜像可以是任何可用镜像文件,默认情况下, docker build会在 docker主机上查找指定的镜像文件,在其不存在时,则会从 Docker Hub Registry上拉取所需的镜像文件 .如果找不到指定的镜像文件, docker build会返回一个错误信息
FROM 语法
FROM
【Docker|Docker 之 dockerfile制作镜像】
MAINTANIER
用于让镜像制作者提供本人的详细信息
Dockerfile并不限制 MAINTAINER指令可在出现的位置,但推荐将
其放置于 FROM指令之后 .
语法:
MAINTAINERl 可是任何文本信息,但约定俗成地使用作者名称及邮件地址,如
MAINTAINER "sunny "
一般把MAINTAINER放在FROM后面
COPY
用于从 Docker主机复制文件至创建的新映像文件
语法
COPY...
:要复制的源文件或目录,支持使用通配符
注意:在路径中有空白字符时,通常使用第二种格式 .
文件复制准则
必须是build上下文中的路径,即只能放在workshop这个工作目录下,不能是其父目录中的文件
如果是目录,其内部文件或者子目录会被递归复制,但目录自身不会被复制
如果指定了多个,或在中使用了通配符,则
如果
copy是指在当前的workshop工作目录中,准备好要添加到新镜像的文件放到这个workshop下面,copy过程实际是基于dockerfile在后台启动一个容器,把工作目录当做卷挂载到后台启动的容器,然后再把这些准备好的文件(workshop目录下)拷贝到后台容器,然后基于这个容器制作新镜像,所以,镜像的制作过程是基于指定的镜像来制作
例子
创建一个目录workshop,在该目录下新建index.html文件用于镜像制作的素材文件,在workshop下新建Dockerfile文件,把index.html拷贝到新镜像里
[root@docker ~]# mkdir workshop
[root@docker ~]# cd workshop/
[root@docker workshop]# vim index.html
[root@docker workshop]# vim Dockerfile
FROM busybox:1.27.2
MAINTAINER "sunny "
COPY index.html/data/htdocs/
COPY yum.repos.d /etc/yum.repos.d/
Dockerfile制作完成后,用命令build制作基于dockerfile的新镜像。命令如下
[root@docker workshop]# docker build -t sunnydocker01:v1 ./
查看
docker images
查看到生成了一个新镜像sunnydocker01
启动新生成的镜像,在容器内部有目录/data/htdocs,并且有文件index.html,且成功拷贝/etc/yum.repos.d这个目录到新镜像中
[root@docker workshop]# docker run -it --rm --name sunny01 sunnydocker01:v1
/ # ls
bindatadevetchomeprocrootsystmpusrvar
/ # ls data/htdocs/
index.html
/ # ls /etc/yum.repos.d/
CentOS-Base.repodocker-ce.repoepel.repo
bak/epel-testing.reposunny.repo
/ # ls /etc/yum.repos.d/
ADD
ADD指令类似于 COPY指令, ADD支持使用 TAR文件和 URL路径
语法
. ADD...
. ADD ["",... "
.操作准则 .
同COPY指令的4点准则
如果为URL且
如果是一个本地系统上的压缩格式的tar文件,它将被展开为一个目录,其行为类似于“tar -x”命令,然后,通过URL获取到的tar文件将不会自动展开
如果有多个,或其间接或直接使用了通配符,则
例子
在workshop目录下,准备了apache-tomcat-8.0.47.tar.gz 这个tar包,已经从网络上下载一个包,Dockerfile路径如下
[root@docker workshop]# vim Dockerfile
FROM busybox:1.27.2
MAINTAINER "sunny "
COPY index.html/data/htdocs/
COPY yum.repos.d /etc/yum.repos.d/
ADD http://nginx.org/download/nginx-1.14.0.tar.gz /tmp
ADD apache-tomcat-8.0.47.tar.gz /app/tomcat/
制作镜像
[root@docker workshop]# docker build -t test02:v1 ./
基于新镜像启动容器,并检查
[root@docker workshop]# docker run -it --rm --name testadd test02:v1
/ # ls
appbindatadevetchomeprocrootsystmpusrvar
/ # ls /tmp/
nginx-1.14.0.tar.gz
/ # ls /app/tomcat/
apache-tomcat-8.0.47
/ #
注意,此时url对应的nginx已经被下载到/tmp下,且这个nginx的tar包不会被展开,同时目录/app/tomcat/下有一个tomcat目录,为apache-tomcat-8.0.47.tar.gz 展开的目录
WORKDIR
workdir为工作目录,指当前容器环境的工作目录,用于为 Dockerfile中所有的 RUN、CMD、ENTRYPOINT、COPY和 ADD指定设定工作目录
语法
WORKDIR
在Dockerfile文件中, WORKDIR指令可出现多次,其路径也可以为相对路径,不过,其是相对此前一个 WORKDIR指令指定的路径
另外, WORKDIR也可调用由 ENV指定定义的变量 .例如
WORKDIR /var/log
WORKDIR$STATEPATH
例子
指定workdir为/usr/local,相当于是容器启动后,会把工作目录切换到/usr/local这个workdir路径下,而不是默认的根目录,如下例子,则相对路径 ./src/ 的绝对路径为容器的/usr/local/src,制作镜像时,把nginx包拷贝到/usr/local/src,把tomcat包解压到/usr/local/src下面
[root@docker workshop]# vim Dockerfile
FROM busybox:1.27.2
MAINTAINER "sunny "
WORKDIR "/usr/local"
ADD http://nginx.org/download/nginx-1.14.0.tar.gz./src/
ADD apache-tomcat-8.0.47.tar.gz ./
启动容器并检查
[root@docker workshop]# docker run -it --rm --name testworkdir testworkdir:v1
/usr/local # ls
apache-tomcat-8.0.47src
/usr/local # ls src
nginx-1.14.0.tar.gz
/usr/local #
容器启动后,工作路径直接切换为/usr/local
VOLUME
定义卷,只能是docker管理的卷,,VOLUME为容器上的目录,用于在 image中创建一个挂载点目录,以挂载 Docker host上的卷或其它容器上的卷
语法
. VOLUME
. VOLUME ["
如果挂载点目录路径下此前在文件存在, docker run命令会在卷挂载完成后将此前的所有文件复制到新挂载的卷中
例子
[root@docker workshop]# vim Dockerfile
FROM busybox:1.27.2
MAINTAINER "sunny "
VOLUME "/test/htdocs"
COPYindex.html/test/htdocs/
制作镜像
[root@docker workshop]# docker build -t testvolume:v1
启动镜像
[root@docker workshop]# docker run -it --rm --name testvolume1 testvolume:v1
/ # ls
bindevetchomeprocrootsystesttmpusrvar
/ # ls /test/htdocs/
index.html
/ #
在另外的窗口检查该容器挂载的卷
[root@docker workshop]# docker inspect -f {{.Mounts}} testvolume1
[{volume e87fdd9ab3b8037167e23edb5c4eae3cc7c5d5ffa63c7a4fa4c18173e0e06460 /var/lib/docker/volumes/e87fdd9ab3b8037167e23edb5c4eae3cc7c5d5ffa63c7a4fa4c18173e0e06460/_data /test/htdocs localtrue }]
[root@docker workshop]# cd /var/lib/docker/volumes/e87fdd9ab3b8037167e23edb5c4eae3cc7c5d5ffa63c7a4fa4c18173e0e06460/_data
[root@docker _data]# ls
index.html
[root@docker _data]# cat index.html
hello,this is first container made by sunny~
[root@docker _data]#
EXPOSE
暴露指定端口,用于为容器打开指定要监听的端口以实现与外部通信
语法
EXPOSE [/] [[/] ...] l
其中用于指定传输层协议,可为 tcp或udp二者之一,默认为 TCP协议
EXPOSE指令可一次指定多个端口,但是不能指定暴露为宿主机的指定端口,因为指定的宿主机端口可能已经被占用,因此这里使用随机端口,例如
. EXPOSE 11211/udp 11211/tcp
例子
[root@docker workshop]# vim Dockerfile
FROM busybox:1.27.2
MAINTAINER "sunny "
VOLUME "/data/htdocs"
COPYindex.html/data/htdocs/
EXPOSE 80/tcp
制作镜像
[root@docker workshop]# docker build -t testexpose:v1 ./
启动并暴露端口,注意,启动容器要跟大写P选项-P来暴露
[root@docker workshop]# docker run -it -P --rm --name testexpose testexpose:v1
/ # ls
bindatadevetchomeprocrootsystmpusrvar
/ # ls data/htdocs/
index.html
/ #
在其他端口检查80口是否有暴露
[root@docker _data]# docker port testexpose
80/tcp -> 0.0.0.0:32768
[root@docker _data]#
ENV
ENV用于为镜像定义所需的环境变量,并可被 Dockerfile文件中位于其后的其它指令(如 ENV、ADD、COPY等)所调用 ,即先定义后调用
调用格式为 $variable_name或${variable_name}
语法
ENV
第一种格式中,
第二种格式,可用一次设置多个变量,每个变量为一个“
.定义多个变量时,建议使用第二种方式,以便在同一层中完成所有功能
例子
下载一个nginx包,其中nginx的版本和nginx包的路径用变量替换
编辑Dockerfile
vim Dockerfile
FROM busybox:1.27.2
MAINTAINER "sunny "
ENV nginx_ver=1.14.0
ENV nginx_url=http://nginx.org/download/nginx-${nginx_ver}.tar.gz
ADD ${nginx_url} /usr/local/
创建镜像
[root@docker workshop]# docker build -t testenv:v1 ./
运行容器并验证
[root@docker workshop]# docker run -it --rm --name testenv testenv:v1
/ # ls /usr/local/
nginx-1.14.0.tar.gz
/ #
有些变量在运行为容器时依然有用,因此需要把那些变量在运行为容器时重新定义为一个新的值,如果变量很多,可以放到一个文件中进行定义,使用参数 --env-list实现,通过文件来加载环境变量
RUN
RUN用于指定 docker build过程中运行的程序,其可以是任何命令,但是这里有个限定,一般为基础镜像可以运行的命令,如基础镜像为centos,安装软件命令为yum而不是ubuntu里的apt-get命令
RUN和CMD都可以改变容器运行的命令程序,但是运行的时间节点有区别,RUN表示在docker build运行的命令,而CMD是将镜像启动为容器运行的命令。因为一个容器正常只用来运行一个程序,因此CMD一般只有一条命令,如果CMD配置多个,则只有最后一条命令生效。而RUN可以有多个。
语法
RUN
第一种格式中,
第二种语法格式中的参数是一个JSON格式的数组,其中
RUN ["/bin/bash","-C","
例子
把下载的nginx打包文件,用RUN命令来展开
编辑dockerfile
vim Dockerfile
FROM busybox:1.27.2
MAINTAINER "sunny "
ENV nginx_ver=1.14.0
ENV nginx_url=http://nginx.org/download/nginx-${nginx_ver}.tar.gz
WORKDIR "/usr/local/src"
ADD ${nginx_url} /usr/local/src/
RUN tar xf nginx-${nginx_ver}.tar.gz
创建镜像
[root@docker workshop]# docker build -t testrun ./
运行容器并验证
[root@docker workshop]# docker run -it --rm --name testrun testrun:latest
/usr/local/src # ls
nginx-1.14.0nginx-1.14.0.tar.gz
/usr/local/src #
如果RUN的命令很多,就用&&符号连接多个命令,少构建镜像层,提高容器的效率
例子如下
基础镜像为centos,RUN多个命令
由于安装是到互联网上的仓库进行安装,所以,建议把centos的yum源配置为本地,即创建镜像时,把yum的配置有本地仓库源配置在CentOS-Base.repo文件放在workshop下面,配置文件配置ADD拷贝一份到新建镜像的/etc/yum.repos.d目录下,因为经常默认会优先加载CentOS-Base.repo下的包,但是不建议使用这个方法,除非本地仓库有足够的包解决依赖关系,否则建议仅使用默认的即可
编辑dockerfile
[root@docker workshop]# vim Dockerfile
FROM centos:7.3.1611
MAINTAINER "sunny "
ENV nginx_ver=1.14.0
ENV nginx_url=http://nginx.org/download/nginx-${nginx_ver}.tar.gz
WORKDIR "/usr/local/src"
ADD CentOS-Base.repo/etc/yum.repos.d/
ADD ${nginx_url} /usr/local/src/
RUN tar xf nginx-${nginx_ver}.tar.gz && yum -y install gcc pcre-devel openssl-devel make \
&& cd nginx-${nginx_ver} && ./configure && make && make install
创建镜像
[root@docker workshop]# docker build -t nginx:v1 ./
运行容器,启动nginx进程
[root@docker workshop]# docker build -t testexpose:v1 ./cc^C
[root@docker workshop]# docker run -it --rm --name nginxv1 nginx:v1
[root@ccedfdf5e63f src]# /usr/local/nginx/sbin/nginx
此时,nginx进程运行于后台,不建议这么做,因为容器的进程要运行于前台模式,否则容器会终止,nginx运行于前台,需要在nginx的配置文件nginx.conf里添加配置项
vi /usr/local/nginx/conf/nginx.conf
daemon off;
这样使得nginx运行于前台
再次运行nginx,则运行于前台
或者通过-g选项,在运行nginx的全局配置模式之后再运行某些参数,注意off后面的冒号
[root@ccedfdf5e63f local]# /usr/local/nginx/sbin/nginx -g "daemon off;
"
CMD
类似于 RUN指令, CMD指令也可用于运行任何命令或应用程序,不过,二者的运行时间点不同 . RUN指令运行于映像文件构建过程中,而 CMD指令运行于基于 Dockerfile构建出的新映像文件启动一个容器时 . CMD指令的首要目的在于为启动的容器指定默认要运行的程序,且其运行结束后,容器也将终止;不过, CMD指定的命令其可以被 docker run的命令行选项所覆盖 .在Dockerfile中可以存在多个 CMD指令,但仅最后一个会生效
语法
CMD
CMD ["
CMD["",""]
.前两种语法格式的意义同 RUN
.第三种则用于为 ENTRYPOINT指令提供默认参数
例子
编译安装nginx,并将镜像的默认命令修改为nginx启动于前台,暴露80口
编辑dockerfile
vim Dockerfile
FROM centos:7.3.1611
MAINTAINER "sunny "
ENV nginx_ver=1.14.0
ENV nginx_url=http://nginx.org/download/nginx-${nginx_ver}.tar.gz
WORKDIR "/usr/local/src"
EXPOSE 80/tcp
ADD ${nginx_url} /usr/local/src/RUN tar xf nginx-${nginx_ver}.tar.gz && yum -y install gcc pcre-devel openssl-devel make \
&& cd nginx-${nginx_ver} && ./configure && make && make install
CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;
"]
制作镜像
[root@docker workshop]# docker build -t nginx:v3 ./
启动容器并测试
[root@docker workshop]# docker run -it --rm --name nginxv3 nginx:v3
测试,容器的ip 为 172.17.0.2,得到nginx的测试页
[root@docker yum.repos.d]# curl 172.17.0.2
查看容器80口被暴露为哪个口
[root@docker yum.repos.d]# docker port nginxv3
80/tcp -> 0.0.0.0:32772
[root@docker yum.repos.d]# curl 10.10.10.72:32772
注意,CMD在dockerfile里写的命令,如果启动容器的命令行里执行命令,则会把dockerfile里的命令覆盖掉,如下,容器启动后,执行/bin/bash,而不是启动nginx于前台
[root@docker workshop]# docker run -it --rm -P --name nginxv3 nginx:v3 /bin/bash
[root@5f2f4b930df3 src]# ss -ntlp
如果dockerfile指定的CMD不允许覆盖,则使用ENTRYPOINT
ENTRYPOINT
类似 CMD指令的功能,用于为容器指定默认运行程序,从而使得容器像是一个单独的可执行程序
与CMD不同的是,由 ENTRYPOINT启动的程序不会被 docker run命令行指定的参数所覆盖,而且,这些命令行参数会被当作参数传递给 ENTRYPOINT指定指定的程序 .不过, docker run命令的 --entrypoint选项的参数可覆盖 ENTRYPOINT指令指定的程序
语法
ENTRYPOINT
ENTRYPOINT ["
docker run 命令传入的命令参数会覆盖CMD指令的内容并且附加到ENTRYPOINT命令最后做为其参数使用 . Dockerfile文件中也可以存在多个 ENTRYPOINT指令,但仅有最后一个会生效
例子
把上例中的CMD执行命令,改成ENTRYPOINT
vim Dockerfile
FROM centos:7.3.1611
MAINTAINER "sunny "
ENV nginx_ver=1.14.0
ENV nginx_url=http://nginx.org/download/nginx-${nginx_ver}.tar.gz
WORKDIR "/usr/local/src"
EXPOSE 80/tcp
ADD ${nginx_url} /usr/local/src/
RUN tar xf nginx-${nginx_ver}.tar.gz && yum -y install gcc pcre-devel openssl-devel make \
&& cd nginx-${nginx_ver} && ./configure && make && make install
ENTRYPOINT ["/usr/local/nginx/sbin/nginx","-g","daemon off;
"]
制作镜像
[root@docker workshop]# docker build -t nginx:v5 ./
由于这次镜像修改相比上次很少,只差了一层CMD,构建过程直接使用缓存,所以速度很快,dockerfile里,建议CMD和ENTRYPOINT不用复用,二者选其一,除非明确指定CMD里的命令参数会被ENTRYPOINT当做参数执行
启动容器
run命令不带其他命令
[root@docker workshop]# docker run -it --rm -P --name nginxv3 nginx:v5
直接启动,可以正常启动nginx,并暴露端口
run命令修改默认命令如/bin/bash,与上例CMD不一样,此时报选项错误,因为此时会把 /bin/bash当做参数传递给ENTRYPOINT指定的指令,此时ENTRYPOINT指定的指令为启动nginx,不能识别/bin/bash
[root@docker workshop]# docker run -it --rm -P --name nginxv3 nginx:v5 /bin/bash
nginx: invalid option: "/bin/bash"
[root@docker workshop]#
但是,如果一定要覆盖dockerfile里指定的ENTRYPOINT命令,那么在run命令使用参数--entrypoint来覆盖,如下
[root@docker workshop]# docker run -it --rm -P --name nginxv3--entrypoint /bin/bash nginx:v5
[root@4c940d422f20 src]# pwd
/usr/local/src
[root@4c940d422f20 src]#
成功以进程/bin/bash启动了容器,覆盖了dockerfile里设定的nginx启动命令
USER
USER用于指定运行 image时的或运行 Dockerfile中任何 RUN、CMD或 ENTRYPOINT指令指定的程序时的用户名或 UID ,即改变容器中运行程序的身份
.默认情况下, container的运行身份为 root用户
语法
USER
需要注意的是,
ONBUILD
ONBUILD 用于在 Dockerfile中定义一个触发器 . 用来指定运行docker指令
Dockerfile用于 build映像文件,此映像文件亦可作为 base image被另一个 Dockerfile用作 FROM指令的参数,并以之构建新的映像文件
.在后面的这个 Dockerfile中的 FROM指令在 build过程中被执行时,将会 “触发 ”创建其 base image的Dockerfile文件中的 ONBUILD指令定义的触发器
语法
ONBUILD
尽管任何指令都可注册成为触发器指令,但是ONBUILD不能自我嵌套,且不会触发FROM和MAINTAINER指令
使用包含ONBUILD指令的Dockerfile构建的镜像应该使用特殊的标签,例如 ruby:2.0-onbuild
在ONBUILD指令中使用ADD或COPY指令应该格外小心,因为新构建过程的上下文在缺少指定的源文件时会失败
ONBUILD 在构建镜像时不会运行,是别人基于这个镜像作为基础镜像构建时,才会运行
如下例子
增加一个ONBUILD命令,执行RUN
FROM centos:7.3.1611
MAINTAINER "sunny "
ENV nginx_ver=1.14.0
ENV nginx_url=http://nginx.org/download/nginx-${nginx_ver}.tar.gz
WORKDIR "/usr/local/src"
EXPOSE 80/tcp
ADD ${nginx_url} /usr/local/src/
RUN tar xf nginx-${nginx_ver}.tar.gz && yum -y install gcc pcre-devel openssl-devel make \
&& cd nginx-${nginx_ver} && ./configure && make && make install
CMD ["/usr/local/nginx/sbin/nginx","-g","daemon off;
"]
ONBUILD RUN echo -e "\nSunny do an onbuild~\n" >> /etc/issue
构建镜像
[root@docker workshop]# docker build -t nginx:v6 ./
基于nginx:v6启动容器,此时/etc/issue还没写入echo要插入的信息
[root@docker workshop]# docker run -it --rm -P --name nginxv3 nginx:v6 /bin/bash
[root@16e90f7a6460 src]# cat /etc/issue
\S
Kernel \r on an \m
[root@16e90f7a6460 src]#
然后基于这个nginx:v6镜像,再次制作一个新镜像,编辑一个新的Dockerfile
[root@docker ~]# mkdir nginxv7
[root@docker ~]# cd nginxv7/
[root@docker nginxv7]# vim Dockerfile
FROM nginx:v6
MAINTAINER "sunny "
CMD "/bin/bash"
构建镜像,注意,会提示执行一个build trigger,如下Executing 1 build trigger
[root@docker nginxv7]# docker build -t nginx:v7 ./
Sending build context to Docker daemon2.048kB
Step 1/3 : FROM nginx:v6
# Executing 1 build trigger
---> Running in 6bb18c52af99
Removing intermediate container 6bb18c52af99
基于新镜像nginx:v7启动新容器nginxv7
[root@docker nginxv7]# docker run -it --rm --name nginxv7 nginx:v7
[root@becc66948713 src]# cat /etc/issue
\S
Kernel \r on an \m
Sunny do an onbuild~
[root@becc66948713 src]#
此时,在旧的镜像中的dockerfile里的ONBUILD已经触发,把信息写入到/etc/issue里
LABEL
LABEL为磁盘映像文件添加元数据,可以基于这个LABEL调用这些元数据,一个LABEL就是一堆k/v值,一个镜像文件可以有多个LABEL
转载于:https://blog.51cto.com/ghbsunny/2155045
推荐阅读
- Docker应用:容器间通信与Mariadb数据库主从复制
- PMSJ寻平面设计师之现代(Hyundai)
- 太平之莲
- 闲杂“细雨”
- 七年之痒之后
- 深入理解Go之generate
- 由浅入深理解AOP
- 期刊|期刊 | 国内核心期刊之(北大核心)
- 生活随笔|好天气下的意外之喜
- 感恩之旅第75天