Docker从入门到精通<6>-使用Dockerfile构建镜像

Dockerfile自动构建docker镜像

Dockerfile 语法格式

ENV

格式:ENV <key> <value>或ENV <key>=<value> ...

ENV指令可以为镜像创建出来的容器声明环境变量。并且在Dockerfile中,ENV指令声明的环境变量会被后面的特定指令(即ENV、ADD、COPY、WORKDIR、EXPOSE、VOLUME、USER)解释使用。其他指令使用环境变量时,使用格式为$variable_name或者${variable_name}。在变量前面添加斜杠\可以转义,如\$foo或者\${foo},将会被分别转换为$foo和${foo},而不是环境变量所保存的值。另外,ONBUILD指令不支持环境替换。

 

FROM [--platform=<platform>] <image>[@<digest>] [AS <name>] # 构建新镜像所依赖的基础镜像

 

RUN  <command> |  ["executable", "param1", "param2"] 

执行命令,这里有两种方式,第一种默认直接调用系统shell,第二种会转换为json,所以一定要用双引号。如果命令比较长可以使用反斜杠进行换行处理。

shell形式不同exec形式不调用命令 shell。这意味着不会发生正常的 shell 处理。例如, RUN [ "echo", "$HOME" ]不会对 进行变量替换$HOME如果你想要 shell 处理,那么要么使用shell形式,要么直接执行 shell,例如:RUN [ "sh", "-c", "echo $HOME" ]当使用 exec 形式并直接执行 shell 时,就像 shell 形式一样,是 shell 进行环境变量扩展,而不是 docker。

如果命令中需要转义,需要用反斜杠进行转义。

 

ADD 

  ADD [--chown=<user>:<group>] <src>... <dest>

  ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

把文件、目录、或者远程的文件的url,从src 拷贝到 镜像 的文件系统中的dest位置。

注意:

1. --chown 仅适用于linux系统。

2. src源文件等可以进行模糊匹配,指定go语言中的filepath.Match,支持 .*?

3. dest 的位置,是相对于WORKDIR的位置,比如WORKDIR 为/data/, dest为app/,那么实际上的dest路径为/data/app/

4. dest必须以斜杠结尾,否则将dest将被视为一个文件

5. 如果src为可识别的压缩格式(identity、gzip、bzip2 或 xz)的压缩文件,复制到dest将被自动解压缩

 

COPY

COPY [--chown=<user>:<group>] <src>... <dest>
COPY [--chown=<user>:<group>] ["<src>",... "<dest>"]
功能和ADD几乎相同,但是少了两种功能:
1. 不支持src为url
2. 不支持src为压缩归档文件的解压缩
不过一般推荐使用COPY,COPY更加透明

 

CMD

CMD指令有3种格式:

  • CMD <command>(shell格式)
  • CMD ["executable", "param1", "param2"](exec格式,推荐格式)
  • CMD ["param1", "param2"](为ENTRYPOINT指令提供参数)

CMD指令提供容器运行时的默认值,这些默认值可以是一条指令,也可以是一些参数。一个Dockerfile中可以有多条CMD指令,但只有最后一条CMD指令有效。CMD ["param1", "param2"]格式是在CMD指令和ENTRYPOINT指令配合时使用的,CMD指令中的参数会添加到ENTRYPOINT指令中。使用shell和exec格式时,命令在容器中的运行方式与RUN指令相同。不同在于,RUN指令在构建镜像时执行命令,并生成新的镜像;CMD指令在构建镜像时并不执行任何命令,而是在容器启动时默认将CMD指令作为第一条执行的命令。如果用户在命令行界面运行docker run命令时指定了命令参数,则会覆盖CMD指令中的命令。

 

LABEL <key>=<value> <key>=<value> <key>=<value> ...   构建完镜像后,我们可以通过  docker image inspect --format='' myimage 查看标签信息

 

MAINTAINER  作者信息,已经弃用,可以用label取代

 

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

指定docker容器在运行时侦听的端口,还可以指定协议是TCP,还是UDP,默认为TCP,例如:EXPOSE 80/TCP, 无论这个参数如何设置docker -p 都将覆盖这个参数,-P表示可以设置随机端口。 

 

ENTRYPOINT

ENTRYPOINT ["executable", "param1", "param2"] # 推荐使用格式

ENTRYPOINT command param1 param2

ENTRYPOINT指令和CMD指令类似,都可以让容器在每次启动时执行相同的命令,但它们之间又有不同。一个Dockerfile中可以有多条ENTRYPOINT指令,但只有最后一条ENTRYPOINT指令有效。当使用shell格式时,ENTRYPOINT指令会忽略任何CMD指令和docker run命令的参数,并且会运行在/bin/sh -c中。这意味着ENTRYPOINT指令进程为/bin/sh -c的子进程,进程在容器中的PID将不是1,且不能接受Unix信号。即当使用docker stop <container>命令时,命令进程接收不到SIGTERM信号。我们推荐使用exec格式,使用此格式时,docker run传入的命令参数会覆盖CMD指令的内容并且附加到ENTRYPOINT指令的参数中。从ENTRYPOINT的使用中可以看出,CMD可以是参数,也可以是指令,而ENTRYPOINT只能是命令;另外,docker run命令提供的运行命令参数可以覆盖CMD,但不能覆盖ENTRYPOINT.

 

VOLUME 

VOLUME ["/data"]

VOLUME指令创建一个具有指定名称的挂载点,并将其标记为保存来自本机主机或其他容器的外部挂载卷点。该值可以是 JSON 数组、VOLUME ["/var/log/"]或带有多个参数的纯字符串,例如VOLUME /var/logVOLUME /var/log /var/db, 就是创建了一个目录,其实跟mkdir 目录差不多,用的并不多

 

USER

USER <user>[:<group>] 或者 USER <UID>[:<GID>]

所述USER指令集运行的image和用于任何时要使用的用户名(或UID)和任选的所述用户组(或GID) RUNCMD和 ENTRYPOINT它后面的指令Dockerfile

注意:当用户没有主要组时,映像(或下一个说明)将与该root组一起运行

 

WORKDIR

WORKDIR 指令为 Dockerfile 中跟随它任何 RUN、CMD、ENTRYPOINT、COPY 和 ADD 指令设置工作目录。 如果 WORKDIR 不存在,即使它没有在任何后续 Dockerfile 指令中使用,它也会被创建。

WORKDIR 指令可以在 Dockerfile 中多次使用。 如果提供了相对路径,它将相对于前一个 WORKDIR 指令的路径。 例如:

WORKDIR /a

WORKDIR b

WORKDIR c

RUN pwd

最终pwd命令的输出Dockerfile将是/a/b/c.

 

ONBUILD (when combined with one of the supported instructions above)
用的比较少

 

Dockerfile编写经验总结:

  • 在构建Dockefile镜像时最好加上标语识别的标签,docker buit -t  python3.8-flask:v1 .
  • 在选择基础镜像的时候,尽量选择小的镜像,便于快速构建,快速发布;前期或者测试环境为了方便排错,可以选择大一点的镜像作为基础镜像。
  • 充分利用缓存。Docker daemon会顺序执行Dockerfile中的指令,而且一旦缓存失效,后续命令将不能使用缓存。为了有效地利用缓存,需要保证指令的连续性,尽量将所有Dockerfile文件中相同的部分都放在前面,而将不同的部分放在后面
  • 不要在Dockerfile中做端口映射,否则一台机器上只能部署一个容器实例。因为在Dockerfile中做完端口映射,相当用Hostport被占用了。
  • CMD和ENTRYPOINT指令  CMD和ENTRYPOINT指令指定了容器运行的默认命令,推荐二者结合使用。使用exec格式的ENTRYPOINT指令设置固定的默认命令和参数,然后使用CMD指令设置可变的参数。
  • 为了使Dockerfile易读、易理解和可维护,在使用比较长的RUN指令时可以使用反斜杠\分隔多行。大部分使用RUN指令的场景是运行apt-get命令,在该场景下请注意如下几点。❏ 不要在一行中单独使用指令RUN apt-get update。当软件源更新后,这样做会引起缓存问题,导致RUN apt-get install指令运行失败。所以,RUNapt-get update和RUN apt-get install应该写在同一行,如RUN apt-getupdate && apt-get install -y package-bar package-foo package-baz。❏ 避免使用指令RUN apt-get upgrade和RUN apt-get dist-upgrade。因为在一个无特权的容器中,一些必要的包会更新失败。如果需要更新一个包(如foo),直接使用指令RUN apt-get install -y foo。在Docker的核心概念中,提交镜像是廉价的,镜像之间有层级关系,像一颗树。不要害怕镜像的层数过多,我们可以在任一层创建一个容器。因此,不要将所有的命令写在一个RUN指令中。RUN指令分层符合Docker的核心概念,这很像源码控制。
  • 正确使用ADD与COPY指令  尽管ADD和COPY用法和作用很相近,但COPY仍是首选。COPY相对于ADD而言,功能简单够用。COPY仅提供本地文件向容器的基本复制功能。ADD有额外的一些功能,比如支持复制本地压缩包(复制到容器中会自动解压)和URL远程资源。因此,ADD比较符合逻辑的使用方式是ADD roots.tar.gz /。当在Dockerfile中的不同部分需要用到不同的文件时,不要一次性地将这些文件都添加到镜像中去,而是在需要时逐个添加,这样也有利于充分利用缓存。另外,考虑到镜像大小的问题,使用ADD指令去获取远程URL中的压缩包不是推荐的做法。应该使用RUN wget或RUN curl代替。这样可以删除解压后不再需要的文件,并且不需要在镜像中再添加一层。

 

 

示例1:2048小游戏

我们首先把2048这个小游戏的项目,下载到本地, 并编写Dockerfile

[root@vm1 ~]# mkdir /root/docker/2048 -p
[root@vm1 ~]#
[root@vm1 ~]# cd /root/docker/2048/
[root@vm1 2048]# ls
[root@vm1 2048]#
[root@vm1 2048]# wget https://github.com/gabrielecirulli/2048/archive/refs/heads/master.zip
...

2021-07-15 16:20:54 (1.03 MB/s) - “master.zip” 已保存 [332945]
[root@vm1 2048]# ls
master.zip
[root@vm1 2048]# unzip master.zip
...
[root@vm1 2048]# mv 2048-master 2048
[root@vm1 2048]# cat Dockerfile
FROM nginx:1.20
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
RUN echo 'Asia/Shanghai' >/etc/timezone
COPY 2048 /usr/share/nginx/html/
EXPOSE 80

 

注意这里的Dockerfile

FROM nginx:1.20   # 基础镜像
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime   # 设置容器时区信息
RUN echo 'Asia/Shanghai' >/etc/timezone
COPY 2048 /usr/share/nginx/html/    # 把项目代码部署到nginx server
EXPOSE 80   # 监听80端口

  因为nginx官方本身已经设置好了,入口启动命令,所以我们无需再进行配置。

 

 

开始构建镜像

[root@vm1 2048]# docker build -t nginx-2048 .
[+] Building 1.0s (9/9) FINISHED
 => [internal] load build definition from Dockerfile                                                                                                           0.2s
 => => transferring dockerfile: 258B                                                                                                                           0.0s
 => [internal] load .dockerignore                                                                                                                              0.3s
 => => transferring context: 2B                                                                                                                                0.0s
 => [internal] load metadata for docker.io/library/nginx:1.20                                                                                                  0.0s
 => [1/4] FROM docker.io/library/nginx:1.20                                                                                                                    0.0s
 => [internal] load build context                                                                                                                              0.2s
 => => transferring context: 4.17kB                                                                                                                            0.0s
 => CACHED [2/4] RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime                                                                                   0.0s
 => CACHED [3/4] RUN echo 'Asia/Shanghai' >/etc/timezone                                                                                                       0.0s
 => CACHED [4/4] COPY 2048 /usr/share/nginx/html/                                                                                                              0.0s
 => exporting to image                                                                                                                                         0.3s
 => => exporting layers                                                                                                                                        0.0s
 => => writing image sha256:747803a902f250a62eb3996ca5c0478835ff4884df4ac2163f2e7fff1c4287cc                                                                   0.0s
 => => naming to docker.io/library/nginx-2048                                                                                                                  0.0s
[root@vm1 2048]# docker images |egrep nginx-2048
nginx-2048                      latest              747803a902f2   15 minutes ago   134MB
[root@vm1 2048]# docker run -d -p 80:80 --name=nginx-2048-server nginx-2048
83d6be13b4b1f581a7afd057e4988125a1df812771f0a86a26ae30c130ffbdcf
[root@vm1 2048]#
[root@vm1 2048]# docker ps |egrep 'nginx-2048-server'
83d6be13b4b1   nginx-2048      "/docker-entrypoint.…"   21 seconds ago      Up 19 seconds      0.0.0.0:80->80/tcp, :::80->80/tcp           nginx-2048-server  

 

开始测试

 

 

示例2:定制化编译nginx镜像

FROM ubuntu:20.04

LABEL "author"="linuxyn linuxyn@163.com"


COPY source.list /etc/apt/source.list

RUN apt update && apt install -y iproute2 ntpdate tcpdump telnet traceroute nfs-kernel-server nfs-common lrzsz tree openssl libssl-dev libpcre3 libpcre3-dev zlib1g-dev gcc openssh-server iotop unzip zip make vim && mkdir /data/nginx -p

ADD nginx-1.20.2.tar.gz /usr/local/src
RUN cd /usr/local/src/nginx-1.20.2 && ./configure --prefix=/apps/nginx  --user=nginx --group=nginx --with-file-aio --with-http_stub_status_module --with-http_ssl_module --with-http_flv_module --with-http_gzip_static_module && make && make install && ln -sv /apps/nginx/sbin/nginx /usr/bin && rm -rf /usr/local/src/nginx-1.20.2 && rm -rf /usr/local/src/nginx-1.20.2.tar.gz

ADD nginx.conf /apps/nginx/conf/nginx.conf
ADD localtime /etc/localtime

RUN ln -sv /dev/stdout /apps/nginx/logs/access.log
RUN ln -sv /dev/stderr /apps/nginx/logs/error.log

# 大公司生产环境中一般都有一个统一的用户和组,这样容器中服务的用户在调用的时候才不会纠结权限的问题 RUN groupadd -g 2022 nginx && useradd -g nginx -s /usr/sbin/nologin -u 2022 nginx && chown -R nginx:nginx /apps/nginx /data/nginx EXPOSE 80 443 # CMD ["/apps/nginx/sbin/nginx", "-g", "daemon off;"] # 上面的CMD等价于下面的两行写法,把CMD中值当成参数传递给了ENTRYPOINT CMD ["-g", "daemon off;"] ENTRYPOINT ["/apps/nginx/sbin/nginx"]

  

在mysql的官方镜像中,我们也发现了这么一段写法

ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 3306 33060
CMD ["mysqld"]

# 即把CMD中的mysqld当成参数传递给了ENTRYPOINT

  

 

posted @ 2021-07-14 15:27  早晨我在雨中采花  阅读(813)  评论(0编辑  收藏  举报