docker快速入门6-dockerfile和registry

docker快速入门6-dockerfile和registry

dockerfile是构造Docker images的一行行命令的集合,是一个纯文本文件。

语法格式

# 注释信息

INSTRUCTION 指令,指令不区分大小写,但约定使用大写

非注释行的第一行必须是FROM指令

工作目录

使用Dockerfile制作镜像时需要一个干净的工作目录,该目录结构如下:

Dockerfile文件
可选的 .dockerignore,用于存放要打包进镜像的文件目录中需要排除的文件
文件1,文件2,...
目录1,目录2,...

要打包进镜像的文件或目录都存放在与Dockerfile文件同级。

Dockerfile指令

更多的dockerfile指令相关信息请参考:https://docs.docker.com/engine/reference/builder

FROM

Dockerfile文件开篇的第一个非注释行,用于为镜像文件构建过程指定基准镜像,后续的指令运行于此基准镜像所提供的运行环境。

语法
FROM <repository>[:<tag>]或
FROM <repository>@<digest>

<repository>: 基础镜像名称
<tag>: 基础镜像的标签,省略时默认为latest
<digest>: 可以不指定镜像的名称而使用镜像的唯一识别hash码

LABLE

为Dockerfile提供key,value对的源数据信息,是MAINTAINER的替代指令

LABEL <key>=<value> <key>=<value> <key>=<value> ...

COPY

从Docker主机复制文件至创建的新镜像文件中

有两种方式
COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]

--chow只适用于类linux系统的镜像
<src>: 要复制的源文件或目录,支持通配符
<dest>: 目标路径,即正在创建的image的文件系统路径,使用绝对路径,否则是以WORKDIR为其起始路径

文件复制准则:
<src> 必须是Dockerfile文件所在目录中的文件,不能是其父目录中的文件
如果<src>是目录,则其内部的文件或子目录会被递归复制,但<src>目录自身不会被复制
如果指定了多个<src>,或使用了通配符,则<dest>必须是一个目录,且以“/”结尾
如果<dest>事先不存在,会被递归创建

ADD

类似COPY指令,ADD支持使用tar文件和url路径

两种语法格式
ADD [--chown=<user>:<group>] <src>... <dest>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

--chow只适用于类linux系统的镜像

操作准则
1. 同COPY指令
2. 如果<src>为URL且<dest>不以/结尾,则<src>指定的文件将被下载并且直接被创建为<dest>;如果<des>以/结尾,则文件名URL指定的文件将被直接下载并保存为<des>/<filename>
3. 如果<src>是一个本地系统上的压缩格式的tar文件,它将被展开为一个目录,其行为类似于“tar -x”命令;然而,通过URL获取到的tar文件将不会自动展开;
4. 如果<src>有多个,或其间接或直接使用了通配符,则<dest>必须为一个以/结尾的目录路径;如果<dest>不以/结尾,其被视为一个普通文件,<src>的内容将被直接写入到<des>;

WORKDIR

用于为Dockerfile中所有的RUN、CMD、ENTRYPOINT、COPY和ADD指定设定的工作目录。

语法
WORKDIR /path/to/workdir

在Dockerfile文件中,WORKDIR指令可以出现多次,其路径也可以为相对路径,不过,其是相对此前一个WORKDIR指令指定的路径。另外,WORKDIR也可调用由ENV指令指定的变量。
如:
ENV DIRPATH /path
WORKDIR $DIRPATH/

VOLUME

用于在image中创建一个挂载点目录,以挂载Docker host上的卷或其它容器上的卷,该卷属于Docker managed volume类型的卷,即宿主机上要挂载的目录由dockerd管理。

语法
VOLUME ["/data"]

如果挂载点目录路径下此前有文件存在,docker run命令会在卷挂载完成后将此前的所有文件复制到新挂载的卷中。

EXPOSE

用于为容器打开指定要监听的端口以实现与外部通信

语法 
EXPOSE <port> [<port>/<protocol>...]

<protocol> 用于指定传输层协议,可为tcp或udp二者之一,默认为tcp协议
可一次指定多个端口,如:
EXPOSE 80/tcp 8080/udp

EXPOSE指令并不会真正把相应的端口暴露到宿主机上,而是说明以该镜像运行的容器具有该种能力,需要在`docker run`命令时使用`-P`(大写的字母p)选项就可把EXPOSE指定的端口进行暴露,宿主机使用的随机端口,也可以使用`-p <hostPort>:<containerPort>`来指定宿主机上使用的端口。

ENV

用于为镜像定义所需要的环境变量,并可被Dockerfile文件中位于其后的其它指令(如ENV、ADD、COPY等)所调用。

语法
ENV <key> <value>
ENV <key>=<value> ...

第一种格式中,<key>之后的所有内容均会被视作其<value>的组成部分,因此,一次只能设置一个变量;
第二种格式中可一次设置多个变量,每一个变量为一个“<key>=<value>”键值对,如果<value>有空格,使用反斜线(\)进行转义,可通过对<value>加引号进行标识;另外反斜线也可用于续行;
如:
ENV myName="John Doe" myDog=Rex\ The\ Dog \
    myCat=fluffy
引用时使用“$myName”或“${myName}”

定义多个变量时,建议使用第二种方式。

RUN

用于指定docker build过程中运行的程序,其可以是任何命令

语法
RUN <command>
RUN ["executable", "param1", "param2", ...]

第一种格式中,<command>通常是一个shell命令,且以“/bin/sh -c”来运行它,这意味着此进程在容器中的PID不为1,不能接收Unix信号,因此,当使用“docker stop <container>”命令停止容器时,此进程接收不到SIGTERM信息;
第二种格式中的参数是一个JSON格式的数组,其中<executable>为要运行的命令,后面的<paramN>为传递给命令的选项或参数;然而,此种格式指定的命令不会以“/bin/sh -c”来发起,因此常见的shell操作如变量替换以及通配符(?,*等)替换将不会进行;不过,如果要运行的命令依赖此shell特性话,可以将其替换为类似下面的格式。
RUN ["/bin/bash", "-c", "<executable>", "param1"]
JSON数组中一定要使用双引号

CMD

类似RUN指令,CMD指令也可用于运行任何命令或程序,不过二者的运行时间点不同。

RUN指令运行于镜像文件构建过程中,而CMD指令运行于基于Dockerfile构建出的新镜像文件启动一个容器时;
CMD指令的首要目的在于为启动的容器指定默认要运行的程序,且其运行结束后,容器也将终止;不过,CMD指定的命令可以被“docker run”的命令行选项所覆盖;

在Dockerfile中可以存在多个CMD指令,但仅最后一个会生效。

语法
CMD ["executable","param1","param2"] (exec的形式, 首选使用这种)
CMD ["param1","param2"] (为ENTRYPOINT指代默认参数)
CMD command param1 param2 (同RUN一样)

ENTRYPOINT

类似CMD指令的功能,用于为容器指定默认运行程序,从而使得容器像是一个单独的可执行程序。与CMD不同的是,由ENTRYPOINT启动的程序不会被docker run命令行指定的参数所覆盖,而且,这些命令行参数会被当作参数传递给ENTRYPOINT指定的程序。不过,docker run命令的--entrypoint选项的参数可以覆盖ENTRYPOINT指令指定的程序。

语法
ENTRYPOINT ["executable", "param1", "param2"]
ENTRYPOINT command param1 param2

docker run命令传入的命令参数会覆盖CMD指令的内容,并且附加到ENTRYPOINT命令最后做为其参数使用,如果docker run命令行未传入命令参数,那ENTRYPOINT会以CMD指令中的内容作为其默认参数
Dockerfile文件中也可以存在多个ENTRYPOINT指令,但仅有最后一个会生效

USER

用于指定运行image时的或运行Dockerfile中任何RUN、CMD或ENTRYPOINT指令指定的程序时的用户名或UID

默认情况下,container的运行身份为root用户

语法
USER <user>[:<group>]
USER <UID>[:<GID>]

UDI,GID必须为镜像系统中有效用户的UID,GID,否则,docker run命令将运行失败。

HEALTHCHECK

用于Docker如何测试容器是否仍在正常工作。

语法
HEALTHCHECK [OPTIONS] CMD command (通过在容器内部运行命令来检查容器的运行状况)
HEALTHCHECK NONE (禁用从基本映像继承的任何运行状况检查)

[OPTIONS]:
--interval=DURATION(默认值:30s) 表示检查间隔时间
--timeout=DURATION(默认值:30s)  表示检查超时时间
--start-period=DURATION(默认值:0s) 表示container运行后等待主进程起动程序,开始健康检查前的等待时长
--retries=N(默认值:3) 当检查失败时,需要连续检查多少次才判定该服务处于不健康状态

检查命令的退出状态,可能的值为:
0:成功-容器健康且可以使用
1:不健康-容器无法正常工作
2:保留-请勿使用此退出代码

事例
HEALTHCHECK --interval=5m --timeout=3s \
  CMD curl -f http://localhost/ || exit 1

SHELL

用于覆盖RUN,CMD,ENTRYPOINT等指令运行的默认shell。

语法
SHELL ["executable", "parameters"]

linux默认使用["/bin/sh", "-c"],windows默认使用["cmd", "/S", "/C"]

STOPSIGNAL

向容器发送退出的信号

语法
STOPSIGNAL signal

signal为系统合法的无符号的信号数字,如9

ARG

用于给docker build时传递变量

语法
ARG <name>[=<default value>]

在“docker build”时使用“--build-arg <varname>=<value>”传递参数
如:
FROM busybox
ARG user
USER ${user:-some_user}
USER $user

在build时使用
$ docker build --build-arg user=what_user ./

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
在ONBUILD指令中使用ADD或COPY指令应该格外小心,因为新构建过程的上下文在缺少指定的源文件时会失败

Dockerfile事例

root@node01:~/img01# pwd
/root/img01
root@node01:~/img01# ls -al
total 20
drwxr-xr-x 2 root root 4096 Jul 20 09:58 .
drwx------ 5 root root 4096 Jul 20 09:58 ..
-rw-r--r-- 1 root root  513 Jul 20 09:32 Dockerfile
-rwxr-xr-x 1 root root  195 Jul 20 09:58 entrypoint.sh
-rw-r--r-- 1 root root   35 Jul 20 09:22 index.html
root@node01:~/img01# cat Dockerfile
FROM nginx:1.14-alpine

# ARG定义的变量可以在build时通过--build-arg 传递参数替换
ARG author="Jack <[email protected]>"
LABEL maintainer="${author}"

ENV NGX_DOC_ROOT="/data/web/html/"

ADD index.html ${NGX_DOC_ROOT}
ADD entrypoint.sh /bin/

EXPOSE 80/TCP 8080/TCP

# 健康检查
HEALTHCHECK --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:${PORT:-80}/

# 触发器
ONBUILD RUN echo "Hello Word!" > /tmp/readme.txt

CMD ["/usr/sbin/nginx","-g","daemon off;"]
ENTRYPOINT ["/bin/entrypoint.sh"]

root@node01:~/img01# cat entrypoint.sh
#!/bin/sh
cat > /etc/nginx/conf.d/www.conf << EOF
server {
    server_name ${HOSTNAME};
    listen ${IP:-0.0.0.0}:${PORT:-10080};
    root ${NGX_DOC_ROOT:-/usr/share/nginx/html};
}
EOF
exec "$@"

root@node01:~/img01# cat index.html
<h1>This is a nginx test page</h1>

entrypoint.sh文件中末尾的exec "$@"尤为重要,由于Dockerfile中同时定义了ENTRYPOINT和CMD指令,所以容器运行时执行的程序为/bin/entrypoint.sh "/usr/sbin/nginx" "-g" "daemon off;" 而后边的参数都会entrypoint.sh脚本末尾的“$@”收集,即成了exec "/usr/sbin/nginx" "-g" "daemon off;",因使用了exec来执行,所以启动nginx的进程ID会替代运行/bin/entrypoint.sh脚本时的sh进程的ID。

build生成镜像

root@node01:~/img01# docker build --build-arg 'author="Tom <[email protected]>"' -t mynginx:v0.1-1 ./
Sending build context to Docker daemon  4.608kB
Step 1/11 : FROM nginx:1.14-alpine
 ---> 8a2fb25a19f5
Step 2/11 : ARG author="Jack <[email protected]>"
 ---> Running in ffe97dc263b2
Removing intermediate container ffe97dc263b2
 ---> 48cccb2b3ac2
Step 3/11 : LABEL maintainer="${author}"
 ---> Running in e31958e5d9eb
Removing intermediate container e31958e5d9eb
 ---> b2c494b442d5
Step 4/11 : ENV NGX_DOC_ROOT="/data/web/html/"
 ---> Running in 1aabf04d1295
Removing intermediate container 1aabf04d1295
 ---> 4fdb24fa67f2
Step 5/11 : ADD index.html ${NGX_DOC_ROOT}
 ---> e334aa452daa
Step 6/11 : ADD entrypoint.sh /bin/
 ---> ad5488c7c47c
Step 7/11 : EXPOSE 80/TCP 8080/TCP
 ---> Running in 773ae947ecd7
Removing intermediate container 773ae947ecd7
 ---> 23c16ae625fe
Step 8/11 : HEALTHCHECK --start-period=3s CMD wget -O - -q http://${IP:-0.0.0.0}:${PORT:-80}/
 ---> Running in 7be92e4c77a7
Removing intermediate container 7be92e4c77a7
 ---> 544097889172
Step 9/11 : ONBUILD RUN echo "Hello Word!" > /tmp/readme.txt
 ---> Running in 3f121c8e23d0
Removing intermediate container 3f121c8e23d0
 ---> 6d80a88f95ee
Step 10/11 : CMD ["/usr/sbin/nginx","-g","daemon off;"]
 ---> Running in b5dbfb4a6c46
Removing intermediate container b5dbfb4a6c46
 ---> 7eaa7434d331
Step 11/11 : ENTRYPOINT ["/bin/entrypoint.sh"]
 ---> Running in f3d78245e25a
Removing intermediate container f3d78245e25a
 ---> 3a250eefa3a7
Successfully built 3a250eefa3a7
Successfully tagged mynginx:v0.1-1

查看镜像详细信息

root@node01:~/img01# docker image inspect mynginx:v0.1-1
...
           "Env": [
                "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
                "NGINX_VERSION=1.14.2",
                "NGX_DOC_ROOT=/data/web/html/"
            ],
            "Cmd": [
                "/usr/sbin/nginx",
                "-g",
                "daemon off;"
            ],
            "Healthcheck": {
                "Test": [
                    "CMD-SHELL",
                    "wget -O - -q http://${IP:-0.0.0.0}:${PORT:-80}/"
                ],
                "StartPeriod": 3000000000
            },
            "Image": "sha256:7eaa7434d331cfc56db82b03ee9472eca1dd4ee3411252fb0155d86debb5fa78",
            "Volumes": null,
            "WorkingDir": "",
            "Entrypoint": [
                "/bin/entrypoint.sh"
            ],
            "OnBuild": [
                "RUN echo \"Hello Word!\" > /tmp/readme.txt"
            ],
            "Labels": {
                "maintainer": "\"Tom <[email protected]>\""
            },
  ...

其中Labels中的maintainer是docker build使用--build-arg传递的值Tom &lt;[email protected]&gt;,不再是Docerfile中定义的值Jack &lt;[email protected]&gt;

基于镜像mynginx:v0.1-1运行一个容器

root@node01:~# docker container run --rm --name nginx01 -e "PORT=8080" mynginx:v0.1-1
127.0.0.1 - - [20/Jul/2020:14:43:44 +0000] "GET / HTTP/1.1" 200 35 "-" "Wget" "-"
127.0.0.1 - - [20/Jul/2020:14:44:14 +0000] "GET / HTTP/1.1" 200 35 "-" "Wget" "-"
......

因配置了HEALTHCHECK,在容器运行后,运行进程运行3秒后每隔30秒都会检查nginx的主页。

另起终端连接到容器中

root@node01:~# docker container exec -i -t nginx01 /bin/sh
/ # cat /etc/nginx/conf.d/www.conf
server {
    server_name eaf2cfb1eece;
    listen 0.0.0.0:8080;
    root /data/web/html/;
}
/ # netstat -tanl
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State
tcp        0      0 0.0.0.0:8080            0.0.0.0:*               LISTEN
tcp        0      0 0.0.0.0:80              0.0.0.0:*               LISTEN
tcp        0      0 127.0.0.1:8080          127.0.0.1:33032         TIME_WAIT
tcp        0      0 127.0.0.1:8080          127.0.0.1:33030         TIME_WAIT
/ # ps
PID   USER     TIME  COMMAND
    1 root      0:00 nginx: master process /usr/sbin/nginx -g daemon off;
    8 nginx     0:00 nginx: worker process
   44 root      0:00 /bin/sh
   66 root      0:00 ps
/ # ls /tmp/
/ #

生成了配置文件,监听8080端口是docker run命令行传递的PORT变量的值。

再来测试一下ONBUILD指令,以生成的新镜像mynginx:v0.1-1为基础镜像再来制作一个镜像

root@node01:~/img02# pwd
/root/img02
root@node01:~/img02# ls
Dockerfile
root@node01:~/img02# cat Dockerfile
FROM mynginx:v0.1-1
RUN /bin/touch /tmp/1.txt

root@node01:~/img02# docker build -t test:v0.1-1 ./
Sending build context to Docker daemon  2.048kB
Step 1/2 : FROM mynginx:v0.1-1
# Executing 1 build trigger
 ---> Using cache
 ---> 8fd69355802f
Step 2/2 : RUN /bin/touch /tmp/1.txt
 ---> Running in 2e123d9c7683
Removing intermediate container 2e123d9c7683
 ---> cce488581509
Successfully built cce488581509
Successfully tagged test:v0.1-1

以此镜像运行一个容器

root@node01:~# docker container run -i -t --rm --name test01 test:v0.1-1 /bin/sh
/ # ls /tmp/
1.txt       readme.txt
/ # cat /tmp/readme.txt
Hello Word!
/ #

/tmp/readme.txt生成了,说明基础镜像里定义的ONBUILD指令被触发了。

docker官方在github在还有许多Dockerfile事例,可以参考:https://github.com/docker-library

Docker Registry

Registry用于保存docker镜像,包括镜像的层次结构和元数据。用户可以自建Registry,也可以使用官方的Docker Hub。

分类:

  1. Sponsor Registry: 第三方的registry,供客户和Docker社区使用
  2. Mirror Registry: 第三方的registry,只让客户使用
  3. Vendor Registry: 由发布Docker镜像的供应商提供的Registry
  4. Private Registry: 通过设有防火墙和额外的安全层的私有实体提供的registry

私有registry搭建

官方提供了docker-distribution软件包来搭建一个Private Registry,也提供了一个名为registry的镜像,只需要pull该镜像,启动容器就可以搭建起来,如果要提供http的请求,那客户端需要修改/etc/docker/daemon.json文件,配置insecure-registries这样一个key

"insecure-registries": ["192.168.101.41/myreposition"]

此key表示访问192.168.101.41/myreposition这个Private Registry可以使用http协议。

官方提供的registry过于简陋,vmware对该软件进行了二次开发,名为Harbor为其增加了许多特性,还提供了一个web界面,详细信息请参考:https://github.com/goharbor/harbor

猜你喜欢

转载自blog.51cto.com/zhaochj/2537022