docker · 2019-12-22 0

使用 Dockerfile 构建镜像

一、构建三步骤

Dockerfile是用来构建Docker镜像的构建文件,是由一系列命令和参数构成的脚本

  1. 编写Dockerfile文件
  2. docker build
  3. docker run

二、Dockerfile 构建过程解析

Dockerfile 内容基础知识

  1. 每条保留字指令都必须大写字母且后面要跟随至少一个参数
  2. 指令按照从上到下,顺序执行
  3. #表示注释
  4. 每条指令都会创建一个新的镜像层,并对镜像进行提交

Docker执行Dockerfile的大致流程

  1. docker从基础镜像运行一个容器
  2. 执行一条指令并对容器作出修改
  3. 执行类似docker commit的操作提交一个新的镜像层
  4. docker再基于刚提交的镜像运行一个新容器
  5. 执行dockerfile中的下一条指令直到所有指令都执行完成

三、Dockerfile 体系结构

  • FROM 基础镜像,当前新镜像是基于哪个镜像的
  • MAINTAINER 镜像维护者的姓名和邮箱地址
  • RUN 容器构建时需要运行的命令
  • EXPOSE 当前容器对外暴露出的端口
  • WORKDIR 指定在创建容器后,终端默认登录的进来工作目录,一个落脚点
  • ENV 用来在构建镜像过程中设置环境变量
  • ADD 将宿主机目录下的文件拷贝进镜像且ADD命令会自动处理URL和解压tar压缩包
  • COPY 类似ADD,拷贝文件和目录到镜像中,将从构建上下文目录中<源路径>的文件/目录复制到新的一层的镜像内的<目标路径>位置    
  • VOLUMN 容器数据卷,用于数据保存和持久化工作
  • CMD 指定一个容器启动时要运行的命令,- Dockerfile中可以有多个CMD命令,但只有最后一个生效,CMD会被docker run之后的参数替换
  • ENTRYPOINT 指定一个容器启动时要运行的命令,其目的的CMD一样,都是在指定容器启动程序及参数
  • ONBUILD 当构建一个被继承的Dockerfile时运行命令,父镜像在在继承后父镜像的onbuild被触发

每一次 RUN 命令都会在镜像上增加一层,每一层都会占用磁盘空间。因此,为了减少镜像大小起见,所有文件相关的操作,比如删除,释放和移动等,都需要尽可能地放在一个 RUN 指令中进行。

四、自定义镜像

1.编写 Dockerfile 文件

创建 Dockerfile 文件

# FROM 指定了一个基础镜像
FROM busybox:1.36.0
# MAINTAINER 指定维护者信息,已经过时,可以使用LABEL maintainer=<xxx>来替代。
LABEL maintainer="example@example.com"

# RUN 运行命令
RUN mkdir /app
# WORKDIR 配置工作目录,为后续的 RUN、CMD、ENTRYPOINT 指令配置工作目录
WORKDIR /app

# ENV 指定环境变量,可以在 docker run 的时候使用 -e 改变;会被固化到 image 的 config 里面
ENV APP_ENV=production
# ARG 指定镜像构建使用的参数(如版本号等),可以在build的时候,使用--build-args改变
ARG VERSION=1.0
RUN echo $VERSION

# ADD 制指定的 src 路径下的内容到镜像中的 dest 路径下,src 可以为 url 会自动下载,可以为 tar 文件,会自动解压
ADD https://mirrors.aliyun.com/apache/tomcat/tomcat-9/v9.0.95/src/apache-tomcat-9.0.95-src.tar.gz /app/down/
# COPY 复制本地主机的 src 路径下的内容到镜像中的 dest 路径下,但不会自动解压等
COPY t1.txt /app

# LABEL 指定生成镜像的元数据标签信息
LABEL version="1.0"
LABEL description="This is an example image"

# VOLUME 创建数据卷挂载点
VOLUME ["/data"]

# USER 指定运行容器时的用户名或UID
USER appuser

# EXPOSE 声明镜像内服务监听的端口
EXPOSE 80

# CMD 指定启动容器时默认的命令
CMD ["localhost"]
# ENTRYPOINT 指定镜像的默认入口
ENTRYPOINT ["/bin/ping","-c","3"]

2.构建

使用 docker build 命令构建镜像

# -f,--file 指定 dockerfile 路径
# --no-cache 构建镜像时不使用缓存
# -t,--tag 指定构建的镜像名和 tag
# --label 设置镜像的元数据
docker build -f Dockerfile --build-arg VERSION=2.0 --label type=abc --no-cache -t demo:1.0 .

3.运行

使用docker run命令运行镜像

docker run --name demo_1 -u root -e APP_ENV=development --add-host test.abc:1.2.3.4 --entrypoint sh -it demo:1.0

五、CMD 和 ENTRYPOINT

1.覆盖

在写 Dockerfile 时,ENTRYPOINT 或者 CMD 命令会自动覆盖之前的 ENTRYPOINT 或者 CMD 命令。
在docker镜像运行时,用户也可以在命令指定具体命令,覆盖在 Dockerfile 里的命令。

例如:
docker run --rm demo:1.0 hostname,其中 hostname 命令 覆盖 Dockerfile 里的 CMD 命令
docker run --rm --entrypoint hostname demo:1.0,其中 hostname 命令 覆盖 Dockerfile 里的 ENTRYPOINT 命令

2.shell 表示法和 exec 表示法

ENTRYPOINT 和 CMD 指令支持2种不同的写法: shell表示法和exec表示法

The CMD instruction has three forms:

CMD ["executable","param1","param2"] (exec form, this is the preferred form)
CMD ["param1","param2"] (as default parameters to ENTRYPOINT)
CMD command param1 param2 (shell form)
ENTRYPOINT has two forms:

ENTRYPOINT ["executable", "param1", "param2"] (exec form, preferred)
ENTRYPOINT command param1 param2 (shell form)

1) shell表示法:CMD executable param1 param2
如果 CMD /bin/sh,docker ps 可以看到 /bin/sh -c /bin/sh
如果 CMD ping localhost,docker ps 可以看到 /bin/sh -c 'ping localhost'
可以看出如果你的镜像里面没有 /bin/sh,docker 容器就不能运行

2) 一个更好的选择是用exec表示法:CMD ["executable","param1","param2"]
CMD指令后面用了类似于JSON的语法表示要执行的命令. 这种用法告诉docker不需要调用/bin/sh执行命令
如果CMD ["/bin/ping","localhost"] ,docker ps 可以看到 /bin/ping localhost

3.ENTRYPOINT 和 CMD 组合使用

组合使用 ENTRYPOINT 和 CMD,ENTRYPOINT 指定默认的运行命令,CMD 指定默认的运行参数.

CMD 给出的是一个容器的默认的可执行体。也就是容器启动以后,默认的执行的命令。重点就是这个“默认”。意味着,如果 docker run 没有指定任何的执行命令或者 dockerfile 里面也没有 ENTRYPOINT,那么,就会使用 CMD 指定的默认的执行命令执行。同时也从侧面说明了 ENTRYPOINT 的含义,它才是真正的容器启动以后要执行命令。

FROM busybox:1.36.0
ENTRYPOINT ["/bin/ping","-c","3"]
CMD ["localhost"]

或:

FROM busybox:1.36.0
CMD ["localhost"]
ENTRYPOINT ["/bin/ping","-c","3"]

上面执行的命令是 ENTRYPOINT 和 CMD 指令拼接而成,ENTRYPOINT 和 CMD 同时存在时,docker 把 CMD 的命令拼接到 ENTRYPOINT 命令之后,拼接后的命令才是最终执行的命令。
因为 docker run 命令执行时,可以覆盖 CMD 指令的值,如果你希望这个 docker 镜像启动后不是 ping localhost,而是ping其他服务器,, 可以这样执行 docker run demo:1.0 docker.io

组合使用 ENTRYPOINT 和 CMD 命令式,确保你一定用的是Exec表示法

例如:

ENTRYPOINT /bin/ping -c 3
CMD localhost
# 实际执行的:/bin/sh -c '/bin/ping -c 3' /bin/sh -c localhost

ENTRYPOINT ["/bin/ping","-c","3"]
CMD localhost
# 实际执行的:/bin/ping -c 3 /bin/sh -c localhost

ENTRYPOINT /bin/ping -c 3
CMD ["localhost"]"
# 实际执行的:/bin/sh -c '/bin/ping -c 3' localhost

ENTRYPOINT ["/bin/ping","-c","3"]
CMD ["localhost"]
# 实际执行的:/bin/ping -c 3 localhost

六、COPY 和 ADD

作用:Dockerfile 中的 COPY 指令和 ADD 指令都可以将主机上的资源复制或加入到容器镜像中,都是在构建镜像的过程中完成的

区别:COPY 指令和 ADD 指令的唯一区别在于是否支持从远程 URL 获取资源。COPY 指令只能从执行 docker build 所在的主机上读取资源并复制到镜像中。而 ADD 指令还支持通过 URL 从远程服务器读取资源并复制到镜像中

满足同等功能的情况下,推荐使用COPY指令。ADD指令更擅长读取本地tar文件并解压缩

在设置了 WORKDIR 命令后,接下来的 COPY 和 ADD 命令中的相对路径就是相对于 WORKDIR 指定的路径

1. COPY 指令

COPY指令能够将构建命令所在的主机本地的文件或目录,复制到镜像文件系统

exec格式:COPY ["<src>",... "<dest>"]
shell格式:COPY <src>... <dest>

COPY 命令区别于 ADD 命令的一个用法是在 multistage 场景下

在 multistage 的用法中,可以使用 COPY 命令把前一阶段构建的产物拷贝到另一个镜像中,比如:

FROM busybox:1.36.0
WORKDIR /opt/
COPY app.txt .

FROM busybox:1.36.0
WORKDIR /app/
COPY --from=0 /opt/app.txt .
CMD ["echo", "app.txt"]

2. ADD 指令

ADD 除了不能用在 multistage 的场景下,ADD 命令可以完成 COPY 命令的所有功能,并且还可以完成两类的功能:

  • 解压压缩文件并把它们添加到镜像中
  • 从 url 拷贝文件到镜像中

exec格式:ADD ["<src>",... "<dest>"]
shell格式:ADD <src>... <dest>

说明,对于从远程URL获取资源的情况,由于ADD指令不支持认证,如果从远程获取资源需要认证,则只能使用 RUN wget 或 RUN curl 替代

另外,如果源路径的资源发生变化,则该 ADD 指令将使 Docker Cache 失效,Dockerfile 中后续的所有指令都不能使用缓存。因此尽量将 ADD 指令放在 Dockerfile 的后面

事实上,当要读取URL远程资源的时候,并不推荐使用ADD指令,而是建议使用RUN指令,在RUN指令中执行wget或curl命令