docker镜像的制作,可以基于容器创建镜像,也可基于dockerfile构建镜像。但需要注意的是,我们并不是真正"创建"新镜像,而是基于一个已有的基础镜像,如centos或ubuntu等,构建新镜像而已。
1.基于容器制作
联合文件系统(UnionFS)挂载提供了容器的文件系统,任何对容器内文件系统的改动都会被写入到新的文件层中,这个文件层归创建它的容器所有。而我们就对做出改动的容器进行镜像构建。我这儿使用busybox作为base image,我们可以认为busybox为一个精简的linux系统。在busybox上运行一个httpd程序,并将其只作为镜像文件。
1.拉取镜像
~]# docker pull busybox:latest #默认为从dockerhub上拉取
...
~]# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
busybox latest 59788edf1f3e 2 months ago 1.15MB
2.运行镜像
~]# docker run --name bbox -it busybox:latest #启动镜像,并交互式登录
3.运行httpd
busybox自带有httpd程序
/ # httpd -h
Usage: httpd [-ifv[v]] [-c CONFFILE] [-p [IP:]PORT] [-u USER[:GRP]] [-r REALM] [-h HOME]
httpd -d/-e/-m STRING
-i Inetd mode
-f Don't daemonize
-v[v] Verbose
-p [IP:]PORT Bind to IP:PORT (default *:80)
-u USER[:GRP] Set uid/gid after binding to port
-r REALM Authentication Realm for Basic Authentication
-h HOME Home directory (default .)
-c FILE Configuration file (default {/etc,HOME}/httpd.conf)
-m STRING MD5 crypt STRING
-e STRING HTML encode STRING
-d STRING URL decode STRING
/ # mkdir /data/html #家目录
/ # echo "httpd server" >> /data/html/index.html #测试页
/ # httpd -h /data/html #指定家目录,启动服务
4.镜像制作
重起一个控制台
~]# docker commit -h
Usage: docker commit [OPTIONS] CONTAINER [REPOSITORY[:TAG]]
Options:
-a, --author string Author (e.g., "John Hannibal Smith <[email protected]>")
-c, --change list Apply Dockerfile instruction to the created image
-m, --message string Commit message
-p, --pause Pause container during commit (default true)
~]# docker commit -p bbox test/mybbox:v0.1 #-p制作镜像时,容器暂停运行
~]# docker images
test/mybbox v0.1 e07687dd8546 1 minutes ago 1.15MB
注:若是想对版本号或者镜像名称进行修改,可使用docker tag命令,但是修改后不会覆盖原镜像,会新生成一个镜像,类似于硬链接。
5.更改启动命令
当我们运行mybbox时,会发现虽然文件都有,但是并没有运行httpd服务,这是因为每个镜像都有一个运行时启动的初始命令,我们若想镜像启动时就运行某个命令,需要我们在制作镜像时就指定。
~]# docker inspect -f {{.Config.Cmd}} test/mybbox:v0.1
[sh] #可见mybbox镜像还是使用busybox的初始命令。
~]# docker commit -a "test <[email protected]>" -c 'CMD ["/bin/httpd,"-h /data/html"]' -p bbox test/mybbox:v0.2
~]# docker inspect -f {{.Config.Cmd}} test/mybbox:v0.2
[/bin/sh -c ["/bin/httpd,"-h /data/html"]]
6.推送至仓库
镜像制作完成,就可以推送至仓库,默认是推送到dockerhub,我们可以自己在 dockerhub 创建账号并创建仓库,然后本地docker login登录,就可以将本地镜像push上去。需要注意的是在dockerhub上创建仓库时,命名空间要为test,仓库名为mybbox(对于本次实验),必须要严格一致。由于我这儿登不了dockerhub,就不在演示了。
7.保存镜像至本地
当镜像制作好,可保存到本地进行分发,也就不需要上传到仓库了。
~]# docker save -o /tmp/mybbox.gz test/mybbox:v0.2 #-o指定保存目录,也可多个镜像一起保存
~]# docker load -i /tmp/mybbox.gz #使用时导入即可
2. 基于Dockerfile制作镜像
其实我们发现,基于容器构建镜像,在对配置变动频繁或需要重复构建镜像时,效率是非常低下的。所以我们就需要使用Dockerfile来实现快速构建镜像,Dockerfile是一个文件,它由构建镜像的指令组成,指令由Docker镜像构建者自上而下排列,能够被用来修改镜像的任何信息。
注意事项:
- Dockerfile文件需放置在一个目录中,这个目录中有构建镜像的所有文件,可以创建子目录。
- 在Dockerfile文件中,"#“号开头表示注释,每行为"INSTRUCTION arguments”,习惯大写表示关键字(但并不区分大小写),后面小写表示值。
- 每个Dockerfile的第一行(注释行除外),必须使用"FROM"关键字。
- 当我们在docker build构建镜像时,会将我们的指定的上下文目录(即Dockerfile所在目录)打包传递给docker 引擎,而这个上下文中并非所有文件都会在Dockerfile中使用,这样就会使传送给docker引擎的目录过大,影响构建速度,所以可以在此目录中,定义.dockerignore文件,将不使用的文件文件名写入到该文件即可。
- 最小化镜像的层数,Dockerfile中每一行命令就是一层,层数过多影响构建效率。
- Dockerfile文件中命令从上往下逐行执行。
2.1 Dockerfile命令
1.FROM: FROM指令是最重的一个且必须为Dockerfile文件开篇的第一个非注释行,用于为映像文件构建过程指定基准镜像,后续的指令运行于此基准镜像所提供的运行环境。实践中,基准镜像可以是任何可用镜像文件,默认情况下,docker build会在docker主机上查找指定的镜像文件,在其不存在时,则会从Docker Hub Registry上拉取所需的镜像文件如果找不到指定的镜像文件,docker build会返回一个错误信息。
Syntax:
FROM <image>[:<tag>]
FROM <image>@<digest>
<image> :指定作为base image的名称;
<tag> :base image的标签,为可选项,省略时默认为latest;
示例:
FROM busybox:latest
2.MAINTAINER: 用于让镜像制作者提供本人的详细信息,Dockerfile并不限制MAINTAINER指令可在出现的位置,但推荐将其放置于FROM指令之后,已经被LABEL取代,但仍可使用。
Syntax:
MAINTAINER <authtor's detail>
<author's detail>:可是任何文本信息,但约定俗成地使用作者名称及邮件地址
3.LABEL: 效果同上
Syntax:
LABEL <key>=<value> [<key>=<value> ...]
示例:
LABEL name=test [email protected]
4.COPY: 用于从Docker主机复制文件至创建的新映像文件
Syntax:
COPY <src> ... <dest>
COPY ["<src>",... "<dest>"]
<src> :要复制的源文件或目录,支持使用通配符
<dest> :目标路径,即正在创建的image的文件系统路径;
建议为<dest>使用绝对路径否则,COPY指定则以WORKDIR为其起始路径;
文件复制准则:
1.<src>必须是build上下文中的路径,不能是其父目录中的文件
2.如果<src>是目录,则其内部文件或子目录会被递归复制,但<src>目录自身不会被复制
3.如果指定了多个<src>,或在<src>中使用了通配符,则<dest>必须是一个目录,且必须以/结尾
4.如果<dest>事先不存在,它将会被自动创建,这包括其父目录路径
示例:
COPY index.html /data/web/ #最后不加斜线会改名为web
COPY src/ /data/web #src/* 被拷贝到/data/web目录下
COPY test1 test2 /data/web/ #多文件最后必须加/
5.ADD: ADD指令类似于COPY指令,ADD支持使用TAR文件和URL路径
Syntax:
ADD <src> ... <dest>
ADD ["<src>",... "<dest>"]
操作准则:同COPY指令
1.如果<src>为URL且<dest>不以/结尾,则<src>指定的文件将被下载并直接被创建为<dest>;
如果<dest>以/结尾,则文件名URL指定的文件将被直接下载并保存为<dest>/<filename>。
2.如果<src>是一个本地系统上的压缩格式的tar文件,它将被展开为一个目录,其行为类似于“tar -x”命令;
然而,通过URL获取到的tar文件将不会自动展开;
3.如果<src>有多个,或其间接或直接使用了通配符,则<dest>必须是一个以/结尾的目录路径;
如果<dest>不以/结尾,则其被视作一个普通文件,<src>的内容将被直接写入到<dest>;
示例:
ADD http://nginx.org/download/nginx-1.14.2.tar.gz /data/ #nginx-1.14.2.tar.gz会放在/data/目录下,不会解压
ADD nginx-1.14.2.tar.gz #nginx-1.14.2目录会放在/data/目录下,会解压
6.WORKDIR: 用于为Dockerfile中所有的RUN、CMD、ENTRYPOINT、COPY和ADD指定设定工作目录
Syntax:
WORKDIR <dirpath>
注:在Dockerfile文件中,WORKDIR指令可出现多次,其路径也可以为相对路径,不过,其是相对此前一个WORKDIR指令指定的路径另外,
WORKDIR也可调用由ENV指定定义的变量
示例:
WORKDIR /var/log
WORKDIR $STATEPATH
7.VOLUME: 用于在image中创建一个挂载点目录,以挂载Docker host上的卷或其它容器上的卷。
Syntax:
VOLUME <mountpoint>
VOLUME ["<mountpoint>"]
注:如果挂载点目录路径下此前有文件存在,docker run命令会在卷挂载完成后将此前的所有文件复制到新挂载的卷中。
示例:
VOLUME /data #不能指定宿主机目录,随机挂载在/var/lib/docker/volumes/...
8.EXPOSE: 用于为容器打开指定要监听的端口以实现与外部通信,但不能指定宿主机的端口绑定。
Syntax:
EXPOSE <port>[/<protocol>] [<port>[/<protocol>] ...]
<protocol>用于指定传输层协议,可为tcp或udp二者之一,默认为TCP协议
示例:
EXPOSE 11211/udp 11211/tcp #EXPOSE指令可一次指定多个端口
9.ENV: 用于为镜像定义所需的环境变量,并可被Dockerfile文件中位于其后的其它指令(如ENV、ADD、COPY等)所调用,调用格式为$variable_name或${variable_name}。
Syntax:
ENV <key> <value> 或
ENV <key>=<value> ...
注:
1.第一种格式中,<key>之后的所有内容均会被视作其<value>的组成部分,因此,一次只能设置一个变量;
2.第二种格式可用一次设置多个变量,每个变量为一个"<key>=<value>"的键值对,如果<value>中包含空格,可以以反斜线(\)进行转义,
也可通过对<value>加引号进行标识;另外,反斜线也可用于续行;
3.定义多个变量时,建议使用第二种方式,以便在同一层中完成所有功能
4.docker run时可以使用 -e 参数指定环境变量,相同环境变量会覆盖Dockerfile中的值
示例:
ENV name=zhang \
age=15 \
sex=N
10.ARG: 此参数和ENV有些相似,都是指定变量值,但是在使用ENV时,若要动态的传递变量值,只能在docker run时才能传递,而ARG可以在docker build过程中,通过–build-arg 来进行动态传递参数值。除此之外,ENV的值会保留在镜像的环境变量中,但ARG的值不会保存。
用法同上,下图说明了构建过程。
11.RUN: 用于指定docker build过程中运行的程序,其可以是任何命令,见下图所示。
Syntax:
RUN <command>
RUN ["<executable>", "<param1>", "<param2>"]
注:
1.第一种格式中,<command>通常是一个shell命令,且以“/bin/sh -c”来运行它,这意味着此进程在容器中的PID不为1,不能接收Unix信号,
因此,当使用docker stop <container>命令停止容器时,此进程接收不到SIGTERM信号;
即在运行该命令前会先起一个sh进程,然后运行此命令。
2.第二种语法格式中的参数是一个JSON格式的数组,其中<executable>为要运行的命令,后面的<paramN>为传递给命令的选项或参数;
然而,此种格式指定的命令不会以“/bin/sh -c”来发起,因此常见的shell操作如变量替换以及通配符(?,*等)替换将不会进行;
不过,如果要运行的命令依赖于此shell特性的话,可以将其替换为类似下面的格式。RUN ["/bin/bash", "-c", "<executable>", "<param1>"]
示例:
RUN ls /data #将会在构建的时候执行 "ls /data"命令
RUN ["httpd",]
12.CMD: 类似于RUN指令,CMD指令也可用于运行任何命令或应用程序,不过,二者的运行时间点不同,RUN指令运行于映像文件构建过程中,而CMD指令运行于基于Dockerfile构建出的新映像文件启动一个容器时,CMD指令的首要目的在于为启动的容器指定默认要运行的程序,且其运行结束后,容器也将终止;不过,CMD指定的命令其可以被docker run的命令行选项所覆盖,在Dockerfile中可以存在多个CMD指令,但仅最后一个会生效。
Syntax:
CMD <command>
CMD ["<executable>","<param1>","<param2>"]
CMD ["<param1>","<param2>"]
注:前两种语法格式的意义同RUN,第一种用法虽然会先起sh进程,但是执行完command后,sh进程会退出,并由command的进程替代为1号进程,第三种则用于为ENTRYPOINT指令提供默认参数。
示例:
CMD /bin/httpd -f -h $DOC_ROOT #如下可见在运行命令之前会预先运sh命令
]# docker inspect -f {{.Config.Cmd}} test
[/bin/sh -c /bin/httpd -f -h $DOC_ROOT]
13.ENTRYPOINT: 类似CMD指令的功能,用于为容器指定默认运行程序,从而使得容器像是一个单独的可执行程序,与CMD不同的是,由ENTRYPOINT启动的程序不会被docker run命令行指定的参数所覆盖,而且,这些命令行参数会被当作参数传递给ENTRYPOINT指定指定的程序。不过,docker run命令的–entrypoint选项的参数可覆盖ENTRYPOINT指令指定的程序。
Syntax:
ENTRYPOINT <command>
ENTRYPOINT ["<executable>", "<param1>", "<param2>"]
注:
1.docker run命令传入的命令参数会覆盖CMD指令的内容并且附加到ENTRYPOINT命令最后做为其参数使用。
2.Dockerfile文件中也可以存在多个ENTRYPOINT指令,但仅有最后一个会生效。
示例:
CMD ["/bin/ls","/etc"]
ENTRYPOINT #会先启动sh进程
14.USER: 用于指定运行image时的或运行Dockerfile中任何RUN、CMD或ENTRYPOINT指令指定的程序时的用户名或UID,默认情况下,container的运行身份为root用户。
Syntax:
USER <UID>|<UserName>
注:需要注意的是,<UID>可以为任意数字,但实践中其必须为/etc/passwd中某用户的有效UID,否则,docker run命令将运行失败。
15.HEALTHCHECK: 对容器进行健康状态检测。
Syntax:
HEALTHCHECK [OPTIONS] CMD command
OPTIONS:
--interval:多长时间检测一次,默认30s
--timeout:超时时长,默认30s
--start-period:容器启动后多久开始检测,默认30s
--retries:重试次数:默认3次
响应码:
0:success
1:unhealthy
2:reserved
示例:
HEALTHECK --interval=5m --timeout=30s CMD curl -f http://localhost/ || exit 1
16.SHELL: 在使用RUN、CMD等命令时,有些格式在运行命令前会先启动一个sh进程,默认使用的是/bin/sh,但在有些时候我们需要改变默认的SHELL。
Syntax:
SHELL ["executable","parameters"]
17.STOPSIGAL: 默认的stop-signal是SIGTERM,在docker stop的时候会给容器内PID为1的进程发送这个signal,通过–stop-signal可以设置自己需要的signal,主要的目的是为了让容器内的应用程序在接收到signal之后可以先做一些事情,实现容器的平滑退出,如果不做任何处理,容器将在一段时间之后强制退出,会造成业务的强制中断,这个时间默认是10s。这个命令很少用到。
18.ONBUILD:: 用于在Dockerfile中定义一个触发器,Dockerfile用于build映像文件,此映像文件亦可作为base imag被
另一个Dockerfile用作FROM指令的参数,并以之构建新的映像文件,在后面的这个Dockerfile中的FROM指令在build过程中被执行时,将会“触发”创建其base image的Dockerfile文件中的ONBUILD指令定义的触发器。简单来说,就是别人基于你的镜像重新制作镜像时才会触发此命令。
Syntax:
ONBUILD <INSTRUCTION>
注:
1.尽管任何指令都可注册成为触发器指令,但ONBUILD不能自我嵌套,且不会触发FROM和MAINTAINER指令
2.使用包含ONBUILD指令的Dockerfile构建的镜像应该使用特殊的标签,例如ruby:2.0-onbuild
3.在ONBUILD指令中使用ADD或COPY指令应该格外小心,因为新构建过程的上下文在缺少指定的源文件时会失败
2.2 简单示例
~]# cat Dockerfile
#Dockerfile for my nginx
FROM nginx:1.14-alpine
LABEL maintainer="chuan <[email protected]>"
ENV DOC_ROOT="/data/web/html/"
ARG PORT=80
COPY entrypoint.sh /bin/
ADD index.html $DOC_ROOT
EXPOSE ${PORT}/tcp
ENTRYPOINT ["/bin/entrypoint.sh"]
CMD ["/usr/sbin/nginx","-g","daemon off;"]
HEALTHCHECK --interval=10s --start-period=10s CMD wget -O - -q http://${IP:-0.0.0.0}:${PORT:-80}/
~]# cat entrypoint.sh
#!/bin/sh
#
cat > /etc/nginx/conf.d/www.conf << EOF
server {
server_name $HOSTNAME;
listen ${IP:-0.0.0.0}:${PORT:-8080};
root ${DOC_ROOT:-/usr/share/nginx/html};
}
EOF
exec "$@"