Dockerfile说明和编写

Dockerfile

Dockerfile 是一个文本文件,其内包含了一条条的指令(Instruction),每一条指令构建一层,因此每一条指令的内容,就是描述该层应当如何构建。

FROM

指定基础镜像

一般情况下FROM位于Dockerfile的首行,除了有parser directives(解析器指令)外

RUN

执行命令行命令的

RUN 指令在定制镜像时是最常用的指令之一。其格式有两种:

  1. shell格式:RUN <命令>,跟正常在命令行中输入一样
RUN echo '

Hello, World!

' > /usr/share/nginx/html/index.html
  1. exec格式:RUN ["可执行文件", "参数1", "参数2"]

使用 exec 格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 ",而不要使用单引号。

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

Union FS 是有最大层数限制的,不得超过 127 层

FROM debian:stretch

RUN apt-get update
RUN apt-get install -y gcc libc6-dev make wget
RUN wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz"
RUN mkdir -p /usr/src/redis
RUN tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1
RUN make -C /usr/src/redis
RUN make -C /usr/src/redis install

上面这样会产生多层,但是其实这些都是不必要的,下面这种最佳

FROM debian:stretch

RUN buildDeps='gcc libc6-dev make wget' \
    && apt-get update \
    && apt-get install -y $buildDeps \
    && wget -O redis.tar.gz "http://download.redis.io/releases/redis-5.0.3.tar.gz" \
    && mkdir -p /usr/src/redis \
    && tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1 \
    && make -C /usr/src/redis \
    && make -C /usr/src/redis install \
    && rm -rf /var/lib/apt/lists/* \
    && rm redis.tar.gz \
    && rm -r /usr/src/redis \
    && apt-get purge -y --auto-remove $buildDeps

为了美化还进行了换行。Dockerfile 支持 Shell 类的行尾添加 \ 的命令换行方式,以及行首 # 进行注释的格式。良好的格式,

镜像是多层存储,每一层的东西并不会在下一层被删除,会一直跟随着镜像。因此镜像构建时,一定要确保每一层只添加真正需要添加的东西,任何无关的东西都应该清理掉。

所以最后添加了清理工作的命令,删除了为了编译构建所需要的软件,清理了所有下载、展开的文件,并且还清理了 apt 缓存文件。

COPY

复制文件

格式:

  1. COPY [--chown=:] <源路径>... <目标路径>
  2. COPY [--chown=:] ["<源路径>", ..., "目标路径"]

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

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

COPY package.json /usr/src/app

<源路径>

<源路径>可以是多个,甚至可以是通配符,其通配符规则要满足Go语言的filepath.Match规则,比如:

COPY hom* /mydir/
COPY hom?.txt /mydir/

<目标路径>

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

使用COPY指令,源文件的各种元数据都会保留。比如读、写、执行权限、文件变更时间等。这个特性对于镜像定制很有用。特别是构建相关文件都在使用 Git 进行管理的时候。

在使用该指令的时候还可以加上--chown=:选项来改变文件的所属用户及所属组。

COPY --chown=55:mygroup files* /mydir/
COPY --chown=bin files* /mydir/
COPY --chown=1 files* /mydir/
COPY --chown=10:11 files* /mydir/

ADD

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

格式:

  1. ADD [--chown=:] <源路径>... <目标路径>
  2. ADD [--chown=:] ["<源路径>", ..., "目标路径"]

如果<源路径>为一个 tar 压缩文件的话,压缩格式为gzip,bzip2以及xz的情况下,ADD 指令将会自动解压缩这个压缩文件到<目标路径>去。

另外需要注意的是,ADD指令会令镜像构建缓存失效,从而可能会令镜像构建变得比较缓慢。

因此在COPYADD指令中选择的时候,可以遵循这样的原则,所有的文件复制均使用COPY指令,仅在需要自动解压缩的场合使用ADD

CMD

容器启动命令, 用于指定默认的容器主进程的启动命令的。

格式:

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

CMDENTRYPOINTHEALTHCHECK只可以出现一次,出现多次的话,只有最后一个生效

在运行时可以指定新的命令来替代镜像设置中的这个默认命令,比如ubuntu镜像默认的CMD/bin/bash,如果我们直接docker run -it ubuntu的话,会直接进入bash。我们也可以在运行时指定运行别的命令,如docker run -it ubuntu cat /etc/os-release。这就是用cat /etc/os-release命令替换了默认的/bin/bash命令了,输出了系统版本信息。

在指令格式上,一般推荐使用 exec 格式,这类格式在解析时会被解析为 JSON 数组,因此一定要使用双引号 ",而不要使用单引号。

shell格式

在使用时,实际的命令会被包装成sh -c的参数形式进行执行

CMD echo $HOME

实际是:

CMD ["sh", "-c", "echo $HOME"]

容器中应用在前台执行和后台执行的问题。Docker 不是虚拟机,容器中的应用都应该以前台执行,而不是像虚拟机、物理机里面那样,用upstart/systemd去启动后台服务,容器内没有后台服务的概念。

所以,执行一些后台操作命令会失效,比如:

CMD service nginx start

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

ENTRYPOINT

ENTRYPOINT的目的和CMD一样,都是在指定容器启动程序及参数。ENTRYPOINT在运行时也可以替代,不过比CMD要略显繁琐,需要通过docker run的参数--entrypoint来指定。

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

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

 ""

容器启动时,先执行一次ENTRYPOINT,然后后续的CMD将作为参数形式,传给ENTRYPOINT

ENV

设置环境变量

格式:

  1. ENV
  2. ENV = =...

无论是后面的其它指令,如RUN,还是运行时的应用,都可以直接使用这里定义的环境变量。

下列指令可以支持环境变量展开: RUNADDCOPYENVEXPOSELABELUSERWORKDIRVOLUMESTOPSIGNALONBUILD

ENV NAME="hello world"

RUN echo $NAME

ARG

构建参数

格式:ARG <参数名>[=<默认值>]

ENV的效果一样,都是设置环境变量。所不同的是,ARG所设置的构建环境的环境变量,在将来容器运行时是不会存在这些环境变量的。

Dockerfile中的ARG指令是定义参数名称,以及定义其默认值。该默认值可以在构建命令docker build中用--build-arg <参数名>=<值>来覆盖。

VOLUME

定义匿名卷

格式:

  1. VOLUME ["<路径1>", "<路径2>"...]
  2. VOLUME <路径>

容器运行时应该尽量保持容器存储层不发生写操作,对于数据库类需要保存动态数据的应用,其数据库文件应该保存于卷(volume)中。

为了防止运行时用户忘记将动态文件所保存目录挂载为卷,在Dockerfile中,可以事先指定某些目录挂载为匿名卷,这样在运行时如果用户不指定挂载,其应用也可以正常运行,不会向容器存储层写入大量数据。

VOLUME /data

目录就会在运行时自动挂载为匿名卷,任何向/data中写入的信息都不会记录进容器存储层,从而保证了容器存储层的无状态化。当然,运行时可以覆盖这个挂载设置。比如:

docker run -d -v mydata:/data xxxx

使用了 mydata 这个命名卷挂载到了 /data 这个位置,替代了 Dockerfile 中定义的匿名卷的挂载配置。

EXPOSE

声明端口

格式:EXPOSE <端口1> [<端口2>...]

声明运行时容器提供服务端口,这只是一个声明,在运行时并不会因为这个声明应用就会开启这个端口的服务。

作用:

  1. 帮助镜像使用者理解这个镜像服务的守护端口,以方便配置映射;
  2. 在运行时使用随机端口映射时,也就是docker run -P时,会自动随机映射EXPOSE的端口。

EXPOSE和在运行时使用-p <宿主端口>:<容器端口>不同,-p是映射宿主端口和容器端口,将容器的对应端口服务公开给外界访问,而EXPOSE仅仅是声明容器打算使用什么端口而已,并不会自动在宿主进行端口映射。

WORKDIR

指定工作目录

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

使用WORKDIR指令可以来指定工作目录(或者称为当前目录),以后各层的当前目录就被改为指定的目录,如该目录不存在,WORKDIR会帮你建立目录。

eg:

# 下面这个在/app下找不到,而是在默认的上下文目录下
RUN cd /app
RUN echo "hello" > world.txt

# 这个是对的

RUN cd /app

# WORKDIR必须是事先建立好的目录,否则无法切换

WORKDIR /app

RUN echo "hello" > world.txt

# 或者

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

WORKDIR必须是事先建立好的目录,否则无法切换

Dockerfile中,这两行RUN命令的执行环境根本不同,是两个完全不同的容器。

每一个RUN都是启动一个容器、执行命令、然后提交存储层文件变更。第一层RUN cd /app的执行仅仅是当前进程的工作目录变更,一个内存上的变化而已,其结果不会造成任何文件变更。而到第二层的时候,启动的是一个全新的容器,跟第一层的容器更完全没关系,自然不可能继承前一层构建过程中的内存变化。

USER

指定当前用户

格式:USER <用户名>[:<用户组>]

USER指令和WORKDIR相似,都是改变环境状态并影响以后的层。WORKDIR是改变工作目录,USER则是改变之后层的执行RUN, CMD以及ENTRYPOINT这类命令的身份。

和 WORKDIR 一样,USER 只是帮助你切换到指定用户而已,这个用户必须是事先建立好的,否则无法切换。

RUN groupadd -r redis && useradd -r -g redis redis
USER redis
RUN ["redis-server"]

root权限

如果以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

告诉 Docker 应该如何进行判断容器的状态是否正常

格式:

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

当在一个镜像指定了HEALTHCHECK指令后,用其启动容器,初始状态会为starting,在HEALTHCHECK指令检查成功后变为healthy,如果连续一定次数失败,则会变为unhealthy

选项:

  1. --interval=<间隔>:两次健康检查的间隔,默认为30s
  2. --timeout=<时长>:健康检查命令运行超时时间,如果超过这个时间,本次健康检查视为失败,默认为30s
  3. --retries=<次数>:当连续失败指定次数后,则容器状态视为unhealthy,默认为3

HEALTHCHECK [选项] CMD后面的命令,格式和ENTRYPOINT一样,分为shell格式,和exec格式。命令的返回值决定了该次健康检查的成功与否: 0:成功;1:失败; 2:保留,不要使用这个值。

通过docker container ls看到最初的状态为(health: starting)

在等待几秒钟后,再次docker container ls,就会看到健康状态变化为了(healthy)

如果健康检查连续失败超过了重试次数,状态就会变为(unhealthy)

为了帮助排障,健康检查命令的输出(包括stdout以及stderr)都会被存储于健康状态里,可以用docker inspect来查看。

$ docker inspect --format '{{json .State.Health}}' web | python -m json.tool
{
    "FailingStreak": 0,
    "Log": [
        {
            "End": "2019-02-27T14:35:37.940957051Z",
            "ExitCode": 0,
            "Output": "\n\n\nWelcome to nginx!\n\n\n\n

Welcome to nginx!

\n

If you see this page, the nginx web server is successfully installed and\nworking. Further configuration is required.

\n\n

For online documentation and support please refer to\nnginx.org.
\nCommercial support is available at\nnginx.com.

\n\n

Thank you for using nginx.

\n\n\n", "Start": "2019-02-27T14:35:37.780192565Z" } ], "Status": "healthy" }

ONBUILD

ONBUILD是一个特殊的指令,它后面跟的是其它指令,比如RUN,COPY等,而这些指令,在当前镜像构建时并不会被执行。只有当以当前镜像为基础镜像,去构建下一级镜像的时候才会被执行

格式:ONBUILD <其他指令>

Dockerfile中的其它指令都是为了定制当前镜像而准备的,唯有ONBUILD是为了帮助别人定制自己而准备的。

你可能感兴趣的:(Dockerfile说明和编写)