容器技术-Dockerfile详解

Docker 系列文章目录

安装Docker和容器管理

Docker镜像管理

Dockerfile详解

Docker数据卷管理

Docker网络之默认网络

Docker网络之Host和none

Docker网络之自定义网络

容器端口发布

Docker 官方镜像仓库 之 Docker Hub

单机容器编排工具之 dockercompose

前言

我们可以把对容器的所有操作命令都记录到一个文件里,就像是写脚本程序。

之后用 docker build 命令以此文件为基础制作一个镜像,并会自动提交到本地仓库。

这样的话镜像的构建会变的透明化,对镜像的维护起来也更加简单,只修改这个文件即可。

同时分享也更加简单快捷,因为只要分享这个文件即可。

Dokcerfile 是一个普通的文本文件,文件名一般叫 Dockerfile

其中包含了一系列的指令(Instruction), 每一条指令都会构建一层,就是描述该层是如何创建的。

小试牛刀

示例:

  1. 编辑 Dockerfile 文件
[root@localhost ~]# mkdir centos_dockerfile
[root@localhost ~]# cd centos_dockerfile/
[root@localhost centos_dockerfile]# vi Dockerfile
FROM centos:latest
LABEL maintainer="yangge <yangge@qf.com>"  description="Install tree vim*"
RUN rpm -qa | grep tree || yum  install -y tree vim*

指令介绍:

  • FORM 定义一个基础镜像
  • LABEL 定义一些元数据信息,比如作者、版本、关于镜像的描述信息
  • RUN 执行命令行的命令

编辑完,保存退出

2.开始构建镜像

命令语法格式:

docker bulid -t 仓库名/镜像名:tag .

docker build [选项] <上下文路径/URL/->

示例:

[root@localhost centos_dockerfile]# docker build -t centos:1.20 .
Sending build context to Docker daemon  2.048kB
Step 1/3 : FROM centos:latest
 ---> e934aafc2206
Step 2/3 : LABEL maintainer="shark<dockerhub@163.com>" description="Install tree vim*"
 ---> Using cache
 ---> 1207b2848015
Step 3/3 : RUN rpm -qa | grep tree || yum  install -y tree     vim*
 ---> Running in 33d321b249d7
Loaded plugins: fastestmirror, ovl
Determining fastest mirrors
...略...
Complete!
Removing intermediate container 33d321b249d7
 ---> adc30981bc84
Successfully built adc30981bc84    # 表示构建成功
Successfully tagged centos:1.20    # TAG 标签
[root@localhost centos_dockerfile]#

构建镜像的上下文(context)

这个 . 表示当前目录,这实际上是在指定上下文的目录是当前目录,docker build 命令会将该目录下的内容打包交给 Docker 引擎以帮助构建镜像。

docker build 命令得知这个路径后,会将路径下的所有内容打包,然后上传给 Docker 引擎。这样 Docker 引擎收到这个上下文包后,展开就会获得构建镜像所需的一切文件。

最佳实战

一般来说,应该会将 Dockerfile 置于一个空目录下,或者项目根目录下。如果该目录下没有所需文件,那么应该把所需文件复制一份过来。如果目录下有些东西确实不希望构建时传给 Docker 引擎,那么可以用 .gitignore 一样的语法写一个 .dockerignore,该文件是用于剔除不需要作为上下文传递给 Docker 引擎的

Dockerfile 详解

FROM 指令

主要作用是指定一个镜像作为构建自定义镜像的基础镜像,在这个基础镜像之上进行修改定制。

这个指令是 Dockerfile 中的必备指令,同时也必须是第一条指令。

Docker Store 上有很多高质量的官方镜像,可以直接作为我们的基础镜像。

作为服务类的,如 Nginx Mongo

用于开发的, 如 Python golang

操作系统类, 如 Centos ubuntu

除了一些现有的镜像,Docker 还有一个特殊的镜像 scratch

这个镜像是虚拟的,表示空白镜像

FORM scratch
...

这以为着这将不以任何镜像为基础镜像。

可以把可执行的二进制文件复制到镜像中直接执行,容器本身就是和宿主机共享 Linux内核的。

使用 Go 语言开发的应用很多会使用这种方式来制作镜像,这也是为什么有人认为 Go 是特别适合容器微服务架构的语言的原因之一。

制作只有 cp 命令最小的镜像

待补充

关于 Alpine

官网:https://alpinelinux.org/

WIKI https://wiki.alpinelinux.org/wiki/Main_Page

Alpine Linux是一款独立的非商业性通用Linux发行版,专为那些了解安全性,简单性和资源效率的高级用户而设计。

Alpine Linux围绕musl libc和busybox构建。这使得它比传统的GNU / Linux发行版更小,更节省资源。一个容器需要不超过8 MB的空间,而对磁盘的最小安装需要大约130 MB的存储空间。您不仅可以获得完整的Linux环境,还可以从存储库中选择大量的软件包。

二进制软件包被缩减和拆分,使您可以更好地控制安装的内容,从而使您的环境尽可能地小巧高效。

简单

Alpine Linux是一个非常简单的发行版,它会尽量避免使用。它使用自己的包管理器,称为apk,OpenRC init系统,脚本驱动的设置,就是这样!这为您提供了一个简单,清晰的Linux环境,没有任何噪音。然后,您可以添加项目所需的软件包,因此无论是构建家用PVR还是iSCSI存储控制器,薄型邮件服务器容器或坚如磐石的嵌入式交换机,其他都不会挡道。

安全

Alpine Linux的设计考虑到了安全性。内核修补了一个非官方的grsecurity / PaX端口,并且所有的用户级二进制文件被编译为位置独立可执行文件(PIE)和堆栈粉碎保护。这些主动安全功能可防止利用整个类别的零日等漏洞。

LABEL 指令

LABEL 指令用于指定一个镜像的描述信息

LABEL指令将元数据添加到镜像中。

LABEL是一个键值对。

要在LABEL值中包含空格,请像在命令行解析中一样使用引号和续行符\

几个用法示例:

LABEL maintainer="yangge@qf.com"
LABEL "com.example.vendor"="ACME Incorporated"
LABEL com.example.label-with-value="foo"
LABEL version="1.0"
LABEL description="This text illustrates \
that label-values can span multiple lines."

一个镜像可以有多个LABEL标签。您可以在一行中指定多个标签。并且目前的版本不再会影响到镜像的大小了。

但是仍然可以把他们写在一行或用反斜线进行续航

LABEL multi.label1="value1" multi.label2="value2" other="value3"
LABEL multi.label1="value1" \
      multi.label2="value2" \
      other="value3"

有继承关系的镜像,标签也会有面向对象编程中继承的关系和特性

要查看镜像的 LABEL 信息,请使用该docker inspect命令。

ENV 指令

用于设置环境变量

格式有两种:

  • ENV <key> <value>

  • ENV <key1>=<value1> <key2>=<value2>...

示例:

推荐的方式,易读

ENV VERSION=1.0 DEBUG=on \
 NAME="Happy Feet"

不推荐都方式,不易读

ENV NODE_VERSION 7.2.0

其他指令使用:

RUN echo $NODE_VERSION
...

下列指令可以支持环境变量: ADDCOPYENVEXPOSELABELUSERWORKDIRVOLUMESTOPSIGNALONBUILD

RUN 指令

RUN 指令是在容器内执行 shell 命令,默认会是用 /bin/sh -c 的方式执行。

执行命令的两种方式

  • RUN <command>shell形式,该命令在shell中运行)

  • RUN ["executable", "param1", "param2"]exec形式)

之前说过,Dockerfile 中每一个指令都会建立一层,RUN 也不例外。每一个 RUN 的行为,就和刚才我们手工建立镜像的过程一样:新建立一层,在其上执行这些命令,执行结束后,commit 这一层的修改,构成新的镜像。

注意:Union FS 是有最大层数限制的,比如 AUFS,曾经是最大不得超过 42 层,现在是不得超过 127 层。

所以,在使用 shell 方式,尽量多的使用续行符\

RUN /bin/bash -c 'source $HOME/.bashrc; \
 echo $HOME'

写 Dockerfile 的时候,要经常提醒自己,这并不是在写 Shell 脚本,而是在定义每一层该如何构建。

注意当使用 exec 方式时,需要明确指定 shell 路径,否则变量可能不会生效

FROM centos
ENV name="yangge"
RUN ["/bin/echo", "$name"]

无效的变量

可以看到 $name 被作为普通的字符串输出了,因为 $name 是 shell 中的用法,而这里里并没有 使用到 shell

下面是正确的做法

FROM alpine
ENV name="yangge"
RUN ["/bin/sh", "-c", "/bin/echo $name"]

注意: exec的方式下,列表中的内容会被解析为JSON数组,这意味着您必须在单词周围使用双引号(“) 而非单引号(’)。

变量被正确输出

CMD 指令

Dockerfile 中只能有一条CMD指令。如果列出多个,CMD 则只有最后一个CMD会生效。

CMD 主要目的是为运行容器时提供默认值

Docker 不是虚拟机,容器就是进程,CMD 指令就是用于指定默认的容器主进程的启动命令的。在启动(运行)一个容器时可以指定新的命令来替代镜像设置中的这个默认命令。

可以包含可执行文件,当然也可以省略。

CMD 指令的格式和 RUN 相似,也是两种格式:

  • shell 格式:CMD <命令>
  • exec 格式:CMD ["可执行文件", "参数1", "参数2"...]
  • 参数列表格式:CMD ["参数1", "参数2"...]。在指定了 ENTRYPOINT 指令后,用 CMD 指定具体的参数。

注意:不要混淆RUNCMDRUN实际上运行一个命令并提交结果; CMD在构建时不执行任何操作,但指定镜像的默认命令。

Docker 不是虚拟机,容器内没有后台服务的概念。

不要期望这样启动一个程序到后台:

CMD systemctl start nginx

这行被 Docker 理解为:

CMD ["sh" "-c" "systemctl start nginx"]

对于容器而言,其启动程序就是容器的应用进程,容器就是为了主进程而存在的,主进程退出,容器就失去了存在的意义,从而退出,其它辅助进程不是它需要关心的东西。

就像上面的示例中,主进程是 sh , 那么当 service nginx start 命令结束后,sh 也就结束了,sh 作为主进程退出了,自然就会使容器退出。

正确的做法是直接执行 nginx 这个可执行文件,并且关闭后台守护的方式,使程序在前台运行。

CMD ["nginx", "-g", "daemon off;"]

ENTRYPOINT 指令

ENTRYPOINT 的目的和 CMD 一样,都是在指定容器的启动程序及参数。

ENTRYPOINT 在运行时也可以被替代,不过比 CMD 要略显繁琐,需要通过 docker run 的参数 --entrypoint 来指定。

ENTRYPOINT 的格式和 RUN 指令格式一样,也分为 exec 格式和 shell 格式。

当指定了 ENTRYPOINT 后,CMD 的含义就发生了改变,不再是直接的运行其命令,而是将 CMD 的内容作为参数传给 ENTRYPOINT 指令,也就是实际执行时,将变为:

<ENTRYPOINT> "<CMD>"

有了 CMD 后,为什么还要有 ENTRYPOINT 呢?

这种 <ENTRYPOINT> "<CMD>" 给我们带来了什么好处么?

让我们来看几个场景。

  • 场景一:让镜像变成像命令一样使用

    CMD 方式

FROM centos
RUN yum update \
    && yum install -y curl
CMD [ "curl", "-s", "http://ip.cn" ]

构建镜像后, 运行容器

# docker run --rm centos-echo-ip-cmd

执行下面命令会报错

# docker run --rm centos-echo-ip-cmd -i

我们可以看到报错,executable file not found。之前我们说过,跟在镜像名后面的是 command,运行时会替换 CMD 的默认值。因此这里的 -i 并不是添加在原来的 curl -s http://ip.cn 后面。
而是替换了原来的 CMD,变成了 CMD ["-i"],而 -i 根本不是命令,所以报了可执行文件找不到

所以应该使用 ENTRYPOINT 方式

FROM centos
RUN yum install -y curl
ENTRYPOINT ["curl", "-s", "http://ip.cn"]

再次构建镜像后, 运行容器

# docker run --rm centos-echo-ip-entrypoint
# docker run --rm centos-echo-ip-entrypoint -i

这样的话, 最终的指令就变成 ENTRYPOINT ["curl", "-s", "http://ip.cn", "-i"]

WORKDIR 指令

用于声明当前的工作目录,以后各层的当前目录就被改为指定的目录。

格式为 WORKDIR <工作目录路径>

如该目录不存在,WORKDIR 会帮你建立目录。

再次强调!不要以为编写 Dockerfiel 是在写 shell 脚本。

下面是一个错误示例:

RUN cd /app
RUN echo "hello" > world.txt

如果将这个 Dockerfile 进行构建镜像运行后,会发现找不到 /app/world.txt 文件,或者其内容不是 hello

原因其实很简单,这两行 RUN 命令的执行环境根本不同,是两个完全不同的容器。这就是对 Dockerfile 构建分层存储的概念不了解所导致的错误。

之前说过每一个 RUN 都是启动一个容器、执行命令、然后提交存储层文件变更。

两行 RUN 分别构建了并启动了各自全新的容器。

因此如果需要改变以后各层的工作目录的位置,那么应该使用 WORKDIR 指令。

FROM alpine
WORKDIR /a/b
RUN touch a_b_f.txt
WORKDIR /a
RUN touch a_f.txt
[root@localhost workdir]# docker run -it alpine:workdir /bin/sh
/a # ls
a_f.txt  b
/a # cd b
/a/b # ls
a_b_f.txt

COPY 指令

格式:

  • COPY <源路径>... <目标路径>

  • COPY ["<源路径1>",... "<目标路径>"]

    RUN 指令一样,也有两种格式,一种类似于命令行,一种类似于函数调用。

<目标路径> 可以是容器内的绝对路径,也可以是相对于 WORKDIR 指定的工作目录的相对路径。目标路径不需要事先创建,如果目录不存在会在复制文件前先被创建。

COPY 指令将会从构建的上下文目录中,把源路径的文件或目录复制到新的一层的镜像内的 <目标路径> 位置。比如:

COPY qf.json /usr/src/app/

注意下面是错误的

COPY qf.json /usr/src/app

这样会把 qf.json 拷贝成为 /usr/src/ 目录下的 app 文件

<源路径> 可以是多个,支持通配符,如:

COPY qf* /app/
COPY q?.txt /app/

使用 COPY 指令,源文件的各种元数据都会保留。

比如读、写、执行权限、文件变更时间等。

COPY 命令的源如果是文件夹,复制的是文件夹的内容而不是其本身

ADD 指令

ADD 指令和 COPY 的格式和性质基本一致。但是在 COPY 基础上增加了一些功能。

支持自动解压缩,压缩格式支持: gzip, bzip2 以及 xz

官方推荐使用 COPY 进行文件的复制。

ADD 指定会使构建镜像时的缓存失效,导致构建镜像的速度很慢。

COPYADD 指令中选择的原则,所有的文件复制均使用 COPY 指令,仅在需要自动解压缩的场合使用 ADD

ADD qf.tar.gz  /

USER 指令

USER 则是改变执行 RUN, CMD 以及 ENTRYPOINT 这类命令的身份。

这个用户必须是事先在容器内存在(建立好)的,否则无法切换。

如果以 root 执行的脚本,在执行期间希望改变身份,比如希望以某个已经建立好的用户来运行某个服务进程,不要使用 su 或者 sudo,这些都需要比较麻烦的配置,而且在 TTY 缺失的环境下经常出错。建议使用 gosu

# 建立 redis 用户,并使用 gosu 换另一个用户执行命令
RUN groupadd -r redis && useradd -r -g redis redis
# 下载 gosu
RUN wget -O /usr/local/bin/gosu "https://github.com/tianon/gosu/releases/download/1.7/gosu-amd64" \
    && chmod +x /usr/local/bin/gosu \
    && gosu nobody true
# 设置 CMD,并以另外的用户执行
CMD [ "exec", "gosu", "redis", "redis-server" ]

HEALTHCHECK 健康检查指令

格式:

  • HEALTHCHECK [选项] CMD <命令>:设置检查容器健康状况的命令
  • HEALTHCHECK NONE:如果基础镜像有健康检查指令,使用这行可以屏蔽掉其健康检查指令

HEALTHCHECK 指令是告诉 Docker 应该如何进行判断容器的状态是否正常,这是 Docker 1.12 引入的新指令。

通过该指令指定一行命令,用这行命令来判断容器主进程的服务状态是否还正常,从而比较真实的反应容器实际状态。

当在一个镜像指定了 HEALTHCHECK 指令后,用其启动容器后的状态变化会是下面的演变过程:

初始状态会为 starting

HEALTHCHECK 指令检查成功后变为 healthy

如果连续一定次数失败,则会变为 unhealthy

HEALTHCHECK 支持下列选项:

  • --interval=<间隔>:两次健康检查的间隔,默认为 30 秒;
  • --timeout=<时长>:健康检查命令运行超时时间,如果超过这个时间,本次健康检查就被视为失败,默认 30 秒;
  • --retries=<次数>:当连续失败指定次数后,则将容器状态视为 unhealthy,默认 3 次。
  • --start-period=<时长>: 容器的初始化实长,默认0秒,不计入健康检测时间内。

CMD, ENTRYPOINT 一样,HEALTHCHECK 在 Dockerfile 中只可以出现一次,如果写了多个,只有最后一个生效。

后面的命令同样支持 shell 方式和 exec 方式。

命令的返回值决定了该次健康检查的成功与否:

0:成功;1:失败。

示例:

使用 curl 命令来判断 nginx 提供的 web 服务是否正常。

DockerfileHEALTHCHECK 可以这么写:

FROM nginx
COPY index.html /usr/share/nginx/html/index.html
HEALTHCHECK --interval=5s --timeout=3s CMD curl -fs \
            http://localhost/ || exit 1

这里设置了每 5 秒检查一次(这里为了试验所以间隔非常短,实际应该相对较长),如果健康检查命令超过 3 秒没响应就视为失败,并且使用 curl -fs http://localhost/ || exit 1 作为健康检查命令。

构建镜像后, 启动容器,并观察容器的状态变化

# docker build -t ali_nginx .
# docker run -d ali_nginx
[root@localhost ~]# docker ps
CONTAINER ID  IMAGE         COMMAND               CREATED     STATUS                            PORTS        NAMES
09a8b90b0f67  ali_nginx   "nginx -g 'daemon of…"   4 seconds ago       Up 3 seconds (health: starting)   80/tcp      vigorous_jang
[root@localhost ~]# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS                    PORTS               NAMES
09a8b90b0f67        ali_nginx           "nginx -g 'daemon of…"   19 seconds ago      Up 18 seconds (healthy)   80/tcp              vigorous_jang

利用元数据查看容器的健康状态

docker inspect --format '{{json .State.Health}}' vigorous_jang | python -m json.tool

ONUILD 指令

ONUILD 指令用于当其他 Dockerfile 以自己为基础镜像时将会运行的命令。

格式:ONBUILD <其它指令>

其他指令可以是: 比如 RUN, COPY 等。

在这里插入图片描述

更多参考官方 Docker Demo 和官网
  1. doker demo

https://github.com/docker-library

  1. 官网

    https://docs.docker.com/engine/reference/builder/

    https://docs.docker.com/develop/develop-images/dockerfile_best-practices/

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shark_西瓜甜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值