云原生 | Docker - [Dockerfile]

INDEX

      • §1 简介
      • §2 Dockerfile 的构建过程(build) & 构筑端 BuildKit
      • §3 Dockerfile 的使用 & 示例
      • §4 Dockerfile 的格式
      • §5 解析器指令(Parser directives)
        • §5.1 基本信息
        • §5.2 无效解析器指令示例
        • §5.3 syntax 解析器指令
        • §5.4 escape 解析器指令
      • §6 环境变量
      • §7 .dockerignore 文件
      • §8 Dockerfile 指令
        • §8.1 FROM
        • §8.2 MAINTAINER
        • §8.3 RUN
        • §8.4 EXPOSE
        • §8.5 WORKDIR
        • §8.6 USER
        • §8.7 ENV
        • §8.8 VOLUME
        • §8.9 COPY
        • §8.10 ADD
        • §8.11 CMD
        • §8.12 ENTRYPOINT

§1 简介

Dockerfile 官网介绍

Docker can build images automatically by reading the instructions from a Dockerfile. A Dockerfile is a text document that contains all the commands a user could call on the command line to assemble an image. Using docker build users can create an automated build that executes several command-line instructions in succession.

Docker 可以通过从 Dockerfile 阅读指令的方式自动创建镜像。Dockerfile 是一个文本文件,它包含了所有用户通过命令行组装一个镜像时所使用的所有命令(它包含一系列指令,用户可以用这些指令在命令行依次组装一个镜像)。使用 docker build,用户可以创建一个自动执行一系列命令行指令的自动构建。

§2 Dockerfile 的构建过程(build) & 构筑端 BuildKit

Dockerfile 指令

  • Dockerfile 由一系列 指令 组成
  • 每个关键字指令均匀 关键字参数 组成
  • 关键字 最好大写
  • 参数 至少一个
  • # 表示注释
  • 每执行一个 指令,都会创建一个新镜像并提交

构建流程

  1. docker 从基础镜像运行一个容器
  2. 执行一条 Dockerfile 指令 以修改容器
  3. 基于修改后的容器创建新镜像并提交
  4. 基于新的镜像运行新的容器
  5. 除非没有下一条 Dockerfile 指令 , 否则回到 2

查看镜像的 Dockerfile
docker hub 镜像的 Dockerfile links 单元,可以找到各个 TAG 的镜像的 Dockerfile 连接,如下图所示
云原生 | Docker - [Dockerfile]_第1张图片
云原生 | Docker - [Dockerfile]_第2张图片

也可以在镜像中通过 Image Layers 查看
下图红框就是 redis 这个镜像在构建时执行的 Dockerfile 指令,每个指令执行后,都会形成新的 Image Layer
云原生 | Docker - [Dockerfile]_第3张图片
BuildKit
BuildKit 是 docker 从 18.09 版本开始支持的新的构筑端,由 moby/buildkit 项目 提供技术支持。
BuildKit 构筑端 相对于原有实现由如下优点:

  • 检测并跳过无用的构建阶段
  • 并行构建互不影响的构建阶段
  • 在两次构建动作间,仅增量的传输构建上下文中有变化的文件
  • 构建上下文中检测并跳过未使用的文件
  • 可以使用带有新特性的外部 Dockerfile 实现
  • 避免其余 API(中间镜像和容器) 的副作用
  • 自动修剪时优先考虑构建缓存

使用 BuildKit
执行 docker build 前,在客户端设置下面环境变量

DOCKER_BUILDKIT=1

Docker Buildx 总是允许 BuildKit

外部 Dockerfile 前端
BuildKit 支持从容器镜像动态的加载前端。
使用外部 Dockerfile 前端时,在 Dockerfile 第一行声明下面内容以指定希望使用的镜像

# syntax=docker/dockerfile:1

BuildKit 附带了Dockerfile frontend builtin,但它建议使用一个外部镜像。这可以确保

  • 所有用户在构建时使用的是同一个版本的 Dockerfile 前端
  • 可以自动获取 bug 修复,无需等待新版本的 BuildKit 或 Docker 引擎

§3 Dockerfile 的使用 & 示例

流程

  • 主机上准备构建镜像过程中使用到的资源
    包括各种 安装包、待传递文件与目录 以及 相关 URL
    需要复制进镜像的安装包等要与 Dockerfile 同目录
  • 主机上创建 Dockerfile 文件
    注意一定是 Dockerfile ,首字母大写
  • 编写 Dockerfile 文件内容
    具体内容参考此贴后续章节
  • 使用 docker build -t 镜像库/镜像名:TAG . 构建镜像
    详细参考 docker build
  • 可以运行或 commit 了

示例

FROM centos
MAINTAINER [email protected]

ENV USER_PATH /usr/local
WORKDIR $USER_PATH

# vim
RUN yum -y install vim

# ifconfig
RUN yum -y install net-tools

# jdk 8 
# 安装 jdk 需要先安装 glibc
RUN yum -y install glibc.i686
RUN mkdir ${USER_PATH}/java
# Add 指令的  参数使用相对路径
ADD jdk-8u171-linux-x64.tar.gz ${USER_PATH}/java
# 配置环境变量
ENV JAVA_HOME ${USER_PATH}/java/jdk1.8.0_171
ENV JRE_HOME ${JAVA_HOME}/jre
ENV CLASSPATH ${JAVA_HOME}/lib/dt.jar:${JAVA_HOME}/lib/tools.jar:${JRE_HOME}/lib:$CLASSPATH
ENV PATH ${JAVA_HOME}/bin:$PATH

EXPOST 80

# docker build 执行过程中,每个指令都会输出
# 所以下面两个 CMD 不是用来启动容器时执行的
# 而是在 docker build 时起到类似 日志 的作用
CMD echo $USER_PATH
CMD echo "centos started..."
CMD /bin/bash

§4 Dockerfile 的格式

  • Docker 按顺序运行 Dockerfile 中的指令。

    Dokcerfile 指令的基础格式如下

    # Comment
    INSTRUCTION arguments
    

    指令 (INSTRUCTION) 在语法上大小写不明。
    但是,习惯通常 全大写,以更好的和参数区分。

  • Dockerfile 必须用一个 FROM 指令开头,用于指定你构建的父镜像。

    FROM 指令也可能在 解析器指令(parser directives) 、注释 或 全局 ARG(全局变量定义) 之后。
    FROM 指令可能在一个或多个 ARG 指令之前,这些 ARG 指令定义用于 Dockerfile FROM 行的参数

    没明白,为啥是之前?
    原文
    FROM may only be preceded by one or more ARG instructions, which declare arguments that are used in FROM lines in the Dockerfile.

  • Docker 将以 # 开头的行视为注释

    有效的 解析指令(parser directive) 也以 # 开始,但不会被当做注释
    只要不是行首,行内任何其他位置出现 # ,一律视为参数

    符合语法的注释如下

    # Comment
    RUN echo 'we are running some # of cool things'
    

    注释行会在 Dockerfile 指令执行前被移除,这意味着下面片段中的注释不会被 echo 指令执行(被 echo 指令输出)

    RUN echo hello \
    # comment
    world
    

    上面的片段等价于

    RUN echo hello \
    # comment
    world
    

    注释不支持续行符(即 \)

  • 注释(#)指令 前的空格会被忽略,但是指令参数中的空格是保留的
    语法允许 注释指令 前存在空格,但并不鼓励这个行为。并且从向后兼容性考虑,这些空格会被忽略。
    因此,下面的两个片段是等效的

    # 片段1
      
           # this is a comment-line
       RUN echo hello
    RUN echo world
    
    #片段2
    
    # this is a comment-line
    RUN echo hello
    RUN echo world
    

    同时,下面的示例中的前导空格实在指令参数中的,因此打印结果为 “ hello world”
    注意 hello 前有空格

    RUN echo "\
         hello\
         world"
    

§5 解析器指令(Parser directives)

§5.1 基本信息

  • 解析器指令用下面的格式声明,且只使用一次
    # directive=value
    
    语法上,解析器指令大小写不敏感
    但使用习惯上,解析器指令 全小写,并在后面追加一个空行
  • 解析器指令必须声明在 Dockerfile 的最顶端
    这是因为一旦注释、空行或 builder 指令处理完成,Docker 就不会再查找解析器指令
    这意味着,此后任何以解析器指令的格式存在的内容都被视为注释,并且不再尝试验证它是否可能是解析器指令
  • 解析器指令是可选的(非必须的),它会影响 Dockerfile 中后续行的处理方式
    解析器指令不会在 docker 镜像的构建中添加新的层(Image Layer),也不显示为一个构建步骤。
  • 解析器指令不支持续行符
  • Dockerfile 支持两种解析器指令:syntaxescape

§5.2 无效解析器指令示例

解析器指令不支持续行符,失效

# direc \
tive=value

解析器指令只使用一次,出现两次,失效

# directive=value1
# directive=value2

FROM ImageName

必须声明在 Dockerfile 的最顶端
出现在 builder 指令后,视为注释,失效

FROM ImageName
# directive=value

一个不可识别的解析器指令(unknowndirective),被视为注释,失效
同时,下面那个可识别的解析器指令,因为在出现在了一个注释(unknowndirective 被视为注释了)之后,所以也被视为一个注释,失效

# unknowndirective=value
# knowndirective=value

FROM ImageName

§5.3 syntax 解析器指令

格式

# syntax=[remote image reference]

示例

# syntax=docker/dockerfile:1
# syntax=docker.io/docker/dockerfile:1
# syntax=example.com/user/repo:tag@sha256:abcdef...

syntax 指令解析器指令只在使用 BuildKit 时生效,使用原始构筑后端时会被忽略。
syntax 指令解析器定义 用于构建 Dockerfile 的本地语法。它允许无缝的使用作为Docker映像分发、并在容器沙盒环境中执行的外部实现

通常 Dockerfile 实现提供以下功能

  • 自动 bug 修复,无需升级 Docker 守护进程
  • 确保所有用户使用相同的实现来构建 Dockerfile
  • 使用最新特性, 无需升级 Docker 守护进程
  • 在新特性或第三方特性集成到 Docker 守护进程前试用它们
  • 使用或自定义 alternative build definitions

官方发布
Docker 官网会在 Docker hub 上的 docker/dockerfile 仓库发布官方版本镜像。用户可以使用它们构建 Dockerfile。

新镜像通过下面两个渠道发行

  • stable (稳定)
  • labs (实验室)

stable
stable 自带版本控制,例如

  • docker/dockerfile:1
    官方推荐
    保持 1.x.x 版本的更新,2.0 之后不更新
    这可以总是在 1.x.x 发布周期内接受最新的稳定版本的镜像和补丁
    BuildKit 会在执行构建时自动检查 syntax 解释器指令的更新,以保证你使用最新版本
  • docker/dockerfile:1.2
    保持 1.2.x 版本的更新,1.3 之后不更新
  • docker/dockerfile:1.2.1
    固定版本

如果指定版本,如 1.2 or 1.2.1,Dockerfile 需要手动升级以获取 bug 修复和新特性。
Dockerfile Builder 的新老版本保持兼容

labs
lab 提供还未在 stable 版中可用的 Dockerfile 特性的抢先体验。
lab 和 stable 一起发布,在相同版本后的基础上添加 -labs 后缀,如

  • docker/dockerfile-labs
  • docker/dockerfile:1-labs
  • docker/dockerfile:1.2-labs
  • docker/dockerfile:1.2.1-labs

渠道选择

  • 按需选择渠道
    除非对 非 stable 特新 有要求,否则使用 stable
    如果需要新特性,就选择 labs
  • 使用 labs 时,最好使用 固定全版本的变体,即类似 1.2.1-labs 版本
    labs 的特性集在 stable 的基础上有超出
    这使得 stable 特性遵循语义版本控制,但 labs 特性不会
    并且无法保证更新版本的向后兼容(即某个 labs 版本包含某特性,但在更新的版本中可能就砍了)

§5.4 escape 解析器指令

声明
转义解析器指令只有下面 二选一

# escape=\

# escape=`

说明

  • 用于设置 Dockerfile 中使用的转义字符
  • 转义字符仅可以设置为 \ 或 ` 二者之一
    默认转义字符是 \
    windows 下,使用 ` 有奇效,因为 \ 在 windows 下是路径分隔符
  • 转义字符可以用于 行内字符转义指令折行
    行内字符转义 不会在 RUN 指令中生效
    指令折行 不受此限制

§6 环境变量

声明
通过 ENV 语句,如

ENV FOO=/bar

环境变量可以在某些指令中作为变量使用
为了逐字逐句地将类似变量的语法包括在陈述中,转义字符也生效(??没看懂)

原文
Environment variables (declared with the ENV statement) can also be used in certain instructions as variables to be interpreted by the Dockerfile. Escapes are also handled for including variable-like syntax into a statement literally

引用
Dockerfile 中,环境变量可以通过下面方式引用

  • $variable_name
  • ${variable_name}

两种引用方式的区别

  • 二者通常是等价的
  • ${variable_name} 是两侧无空格场景(如路径问题)的经典用法 ${foo}_bar
  • ${variable_name} 支持下面的标准bash修饰符
    ${variable:-word} 变量有值时使用原值,否则使用 word
    ${variable:+word} 变量有值时使用 word,否则使用空字符串
    上面两个分支语法中,word 可以使任意字符串,包括其他环境变量

转义
$variable_name ${variable_name} 也可以被转义字符 \ 转义
转以后作为一个文本,而不是指令存在

```shell
FROM busybox
ENV FOO=/bar
WORKDIR ${FOO}   # WORKDIR /bar
ADD . $FOO       # ADD . /bar
COPY \$FOO /quux # COPY $FOO /quux
```

指令支持
下列 Dockerfile 指令支持环境变量

  • ADD
  • COPY
  • ENV
  • EXPOSE
  • FROM
  • LABEL
  • STOPSIGNAL
  • USER
  • VOLUME
  • WORKDIR
  • ONBUILD (当与上述指令之一关联时)

值的替换
环境变量的替换将为整个指令(这里的整个指令是指一行指令而不是整个 Dockerfile 中的所有指令)里的每个变量使用同样的值
下面的案例中,def = hello ,ghi = bye,因为 ghi 不是给 abc 赋值为 bye 的指令的一部分

ENV abc=hello
ENV abc=bye def=$abc
ENV ghi=$abc

§7 .dockerignore 文件

流程

  • 客户端给守护进程发送上下文前生效
  • 发送上下文前,在根目录下查找 .dockerignore 文件
  • 若找到文件,客户端将此文件中的每一行解释为一个模式(pattern)
    类似 Unix shells 的 globs 文件
  • 用这些模式(pattern)排除上下文中与之匹配的路径和文件
    为了达到匹配的目的,上下文的 root 同时视为根目录和工作目录
    例如 /foo/barfoo/bar 同时存在时,
    会排除 根目录下的 boo 目录下的 bar
    也排除 PATH 路径下的 boo 目录下的 bar
  • 如果有需要,可以通过 ADD 和 COPY 指令,有选择性的将被排除的文件添加进镜像

意义
用于避免向守护进程发送没必要的过大或敏感的文件和目录

pattern 语法

pattern 作用
# comment 注释,忽略
例外项,原先应该排除的不排除了
*/ 根目录下任意 1 级子目录,每级子目录的名字无所谓
**/ 根目录下的任意多级子目录,可以是 0 级、1 级、多级,每级子目录的名字无所谓
temp* 目录或文件以 temp 开头,后面可以接任意内容或不接
temp? 目录或文件以 temp 开头,后面接并且只接一个字符

示例
*/aaa*
匹配:/随便/aaa随便
*/*/aaa*
匹配:/随便/随便/aaa随便
**/aaa?
匹配:/随便几级目录/aaa任意一个字符

匹配规则

  • 匹配前忽略 pattern 的首尾空格,以及首尾 . 或 …
  • 上一步忽略后是空白行的 pattern 被忽略
  • 以 # 开头的行是注释
  • 以 !开头的是例外项,即使满足此例外项上面的匹配项也不排除
  • 以 !开头的是例外项的排除效果受其位置影响
    *.md
    !README*.md
    README-secret.md
    
    .md 文件一般都排除,但 .md 文件中的 README 文件不排除
    但 README 文件中的 README-secret.md 文件随其他 .md 文件排除
    *.md
    README-secret.md
    !README*.md
    
    .md 文件一般都排除,但 .md 文件中的 README 文件不排除
  • 可以以例外项排除 Dockerfile 和 .dockerignore 文件
    这两个文件依然会发送往守护进程(因为需要它们处理工作)
    这两个文件不会被 ADD 和 COPY 指令加入镜像
  • 需要排除上下文中绝大部分文件时,先排除所有,然后利用例外项追加
    *
    ! 保留文件 1
    ! 保留文件 2
    ! ...
    ! 保留文件 n
    

§8 Dockerfile 指令

§8.1 FROM

FROM 通常在 Dockerfile 的首行,

FROM 基础镜像:TAG

§8.2 MAINTAINER

维护者名字
已过期

MAINTAINER name

§8.3 RUN

指令说明

  • RUN 指令可以当前镜像顶部的新的 Layer 中运行任意指令
  • 运行指令后的结果(即运行完指令的镜像)会提交
  • Dockerfile 的下一步会在刚刚提交的新镜像的基础上执行

分层运行指令与生成提交符合 Docker 的核心理念:
(镜像) 提交到 Docker 中是很廉价 (的操作)
我们可以从镜像历史的任意一个节点创建容器

这类似与 源码中心

指令格式
RUN 指令有两种格式

RUN

  • 可以改变用来执行指令的 shell:RUN /bin/bash -c '指令'
  • 支持续行符

RUN ["executable", "param1", "param2"]

  • 可以避免 shell 字符串改写,保证 RUN 的指令在一个不包含其他特殊 shell 的基本 shell 中执行

  • 可以通过 RUN ["/bin/bash", "-c", "指令"] 的形式变更此格式下的 shell

  • == 这种格式基于 JSON 格式==
    必须使用双引号 "
    需要转义反斜杠 \ 为 \\

  • 指令不会运行于 shell 环境,因此类似 RUN [ "echo", "$HOME" ] 中的 $HOME 会无法识别

§8.4 EXPOSE

指定对外暴露的的端口

EXPOSE port

§8.5 WORKDIR

指定容器创建后默认的工作目录
即,使用 docker exec 进入容器后的那个目录

WORKDIR path

§8.6 USER

指定镜像用什么样的用户执行指令
默认 root

USER root

§8.7 ENV

设置环境变量

ENV 变量标识符 变量值

§8.8 VOLUME

声明一个挂载点作为容器的卷

VOLUME /myvol

它没有马上与宿主机的某个目录进行绑定
当使用这个镜像运行容器时,才会将它挂载到 Docker 安装目录的指定目录下,不同版本的 Docker 会有差别,比如

Docker 安装路径/volumes/{容器ID}

§8.9 COPY

复制主机行文件或目录至容器内文件系统

COPY ...

§8.10 ADD

主机上文件或路径,甚至远程文件 RULs 复制到镜像的文件系统中

ADD ...

使用相对路径
可以自动处理 URL
可以自动解压
从 URL 上 ADD 的压缩包不能自动解压

§8.11 CMD

指定容器启动 (docker run) 后默认需要执行的指令

指令格式
指令的格式同 RUN

  • CMD
  • CMD ["executable", "param1", "param2"]

说明

  • 一个 dockerfile 中允许出现多个 CMD,但只有最后一个生效
  • CMD 可以被 docker run 后的命令替代
    例如 tomcat 的 Dockerfile 默认有 CMD ["catalina.sh", "run"]
    若使用 docker run -it -p 8080:8080 /bin/bash 启动
    相当于在 Dockerfile 中追加 CMD ["/bin/bash"] 并使上面的 CMD 失效
    此时则容器启动后,只进入命令行,不自动运行 tomcat
  • 与 ENTRYPOINT 一起使用时作用由执行指令变为传参

与 RUN 的区别

  • RUN 在 docker build 阶段执行
  • CMD 在 docker run 后执行

§8.12 ENTRYPOINT

指定容器启动 (docker run) 后默认需要执行的指令

ENTRYPOINT ["executable", "param1", "param2"]

说明 / 与 CMD 区别

  • 不会被 docker run 后的命令覆盖
  • docker run 后的命令会作为参数传递给 ENTRYPOINT 指定的程序

示例
以 nginx 为例

FROM nginx
......
ENTRYPOINT ["nginx","-c"]
CMD ["/etc/nginx/nginx.conf"]

nginx 容器启动后,固定运行 nginx -c conf_file 指令(用指定的配置文件启动)
CMD 指令将 conf_file 的默认值 /etc/nginx/nginx.conf 传递给上面的命令
docker run 后面携带了命令,会作为新的 CMD 给上面的命令传参,覆盖默认值

默认 传参
ENTRYPOINT 指令 ENTRYPOINT [“nginx”,“-c”] ENTRYPOINT [“nginx”,“-c”]
docker run docker run nginx:test docker run nginx:test -c /etc/nginx/my.conf
容器运行后的实际指令 nginx -c /etc/nginx/nginx.conf nginx -c /etc/nginx/my.conf

你可能感兴趣的:(云原生,docker,云原生,容器)