Docker 从入门到实践系列五 - Dockerfile文件

Posted ThinkWon

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Docker 从入门到实践系列五 - Dockerfile文件相关的知识,希望对你有一定的参考价值。

什么是DockerFile文件

DockerFile是一个文本格式的配置文件,用户可以使用DockerFile来快速创建自定义的镜像。

DockerFile基本结构

Dockerfile分为四部分:基础镜像信息、维护者信息、 镜像操作指令和容器启动时执行指令

比如:

# This Dockerfile uses the ubuntu image
# VERSION 2 - EDITION 1
# Author: docker_user
# Command format: Instruction [arguments / command] ..
# Base image to use, this must be set as the first line
FROM ubuntu:18.04
MAINTAINER docker_user docker_user@email.com
COPY . /app
RUN make /app
CMD python /app/app.py

每条指令创建一层:

  • FROM : 指明所基于的镜像名称,从ubuntu:18.04Docker镜像创建一个图层。

  • MAINTAINER:维护者信息,docker_user维护(可以不写)

  • COPY : 从Docker客户端的当前目录添加文件,镜像操作指令

  • RUN : 构建镜像时执行make命令,每运行一条RUN指令,镜像就添加新的一层,并提交(添加可写层)

  • CMD : 指定在容器中运行什么命令,用来指定运行容器时的操作命令

Docker镜像由只读层组成,每个只读层代表一个Dockerfile指令。这些层是堆叠的,每个层都是上一层的变化的增量

Docker可以通过读取Dockerfile指令来自动构建镜像

完整的小例子:

#在一个空目录下,新建一个名为 Dockerfile 文件
mkdir /usr/dockerfile -p
vim dockerfile-demo
#编辑 dockerfile
FROM nginx:latest
# 维护者 可以省略
MAINTAINER jourwon jourwon@docker.com
#启动容器
RUN mkdir /usr/share/nginx/html/ -p
RUN echo Hello DockerFile! > /usr/share/nginx/html/demo.html
#构建镜像 . : 根据当前上下文环境构建
docker build -f dockerfile-demo -t jourwon/nginx:v1 .
#运行
docker run --rm -d -it --network host jourwon/nginx:v1

浏览器访问

DockerFile指令详解

D ockerfile命令官方文档 常见命令详解:

1. FROM

指定所创建镜像的基础镜像,如果本地不存在,则默认会去Docker Hub下载指定镜像。命令格式如下:

FROM [--platform=<platform>] <image> [AS <name>]

Or

FROM [--platform=<platform>] <image>[:<tag>] [AS <name>]

Or

FROM [--platform=<platform>] <image>[@<digest>] [AS <name>]

平时不用这么复杂的,只需如下即可

FROM <image>[:<tag>]
FROM centos:7.6.1810

任何Dockerfile中的第一条指令必须为FROM指令。并且,如果在同 一个Dockerfile中创建多个镜像,可以使用多个FROM指令(每个镜像一 次)。

2. MAINTAINER

指定维护者信息,格式为MAINTAINER。可以不写

MAINTAINER jourwon jourwon@docker.com

该信息会写入生成镜像的Author属性域中

3. RUN

容器构建时需要运行的命令

1、RUN

默认将在shell终端中运行命令,即/bin/sh-c

2、RUN [“executable”,“param1”,“param2”]。指令会被解析 为Json数组,因此必须用双引号。exec执行,不会启动shell环境

指定使用其他终端类型可以通过此方式实现,例如

RUN ["/bin/bash","-c","echo hello"]

每条RUN指令将在当前镜像的基础上执行指定命令,并提交为新的镜像。当命令较长时可以使用\\来换行

在shell形式中,可以使用\\(反斜杠)将一条RUN指令继续到下一行。例如,考虑以下两行:

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

它们在一起等效于以下这一行:

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

要使用’/bin/sh’以外的其他shell ,请使用exec 形式传入所需的shell 。例如:

RUN ["/bin/bash", "-c", "echo hello"]

注意 在JSON格式中,必须转义反斜杠。在Windows中,反斜杠是路径分隔符,这一点尤其重要。由于无效的JSON,以下行否则将被视为shell形式,并以意外的方式失败:

RUN ["c:\\windows\\system32\\tasklist.exe"]

此示例的正确语法为:

RUN ["c:\\\\windows\\\\system32\\\\tasklist.exe"]

4. CMD

CMD指令用来指定启动容器时默认执行的命令。它支持三种格式:

CMD 指令的格式和 RUN 相似:

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

注:DockerFile 中可以有多个 CMD 指令,但只有最后一个生效,CMD 会被 docker run 之后的参数替换。

5. COPY

复制本地主机的下的内容到镜像中的路径下。目标路径不存在时,会自动创建格式如下:

COPY

src:为Dockerfile所在目录的相对路径、文件或目录dest:镜像中的目标路径,相对和绝对都可以

支持通配符和正则

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

当使用本地目录为源目录时,推荐使用COPY

6. ENV

用来在构建镜像过程中设置环境变量。这个其实就是用一个别名指定一个目录地址,然后后续RUN指令可以通过 $别名 的方式来使用这个目录地址。

格式为ENV或ENV=… ,ENV 指令有两种形式

第一种形式ENV 会将一个变量设置为一个值。第一个空格之后的整个字符串将被视为 -包括空格字符。该值将为其他环境变量解释,因此如果不对引号字符进行转义,则将其删除。

第二种形式ENV = … 允许一次设置多个变量。请注意,第二种形式在语法中使用等号= ,而第一种形式则不使用等号= 。与命令行解析一样,引号和反斜杠可用于在值中包含空格。

例如:

ENV myName="John Doe" myDog=Rex\\ The\\ Dog \\
	myCat=fluffy

ENV myName John Doe
ENV myDog Rex The Dog
ENV myCat fluffy

是一样的

ENV 从结果镜像运行容器时,使用设置的环境变量将保留。可以使用查看值docker inspect , 并使用更改它们docker run --env = 。

docker run --env<key>=<value> built_image

7. ADD

将宿主机目录下的文件拷贝到镜像且 ADD 命令会自动处理 URL 和解压 tar 压缩包,格式为

ADD hom* /mydir/
ADD hom?.txt /mydir/
ADD test.txt /absoluteDir/
ADD test.txt relativeDir/

其中可以是Dockerfile所在目录的一个相对路径(文件或目录),也可以是一个URL,还可以是一 个tar文件(如果为tar文件,会自 动解压到路径下)。可以是镜像内的绝对路径,或者相对于工作目录(WORKDIR)的相对路径

与COPY的区别

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

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

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

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

8. ENTRYPOINT

指定镜像的默认入口命令,该入口命令会在启动容器时作为根命令执行,所有传入值作为该命令的 参数

ENTRYPOINT有两种形式:

  • 在EXEC的形式,这是优选的形式ENTRYPOINT ["executable", "param1", "param2"],这种执行的是executable param1 param2

  • 带壳形式:ENTRYPOINT command param1 param2 。这中其实执行的是/bin/sh -c command param1 param2

多个ENTRYPOINT 只有最后一条生效

ENTRYPOINT ["echo", "4"]
ENTRYPOINT ["echo", "5"]
ENTRYPOINT ["echo", "6"]

9. VOLUME

容器数据卷,用于数据保存和持久化工作。

创建一个数据卷挂载点,格式为VOLUME ["/data"]

FROM centos:7.6.1810
RUN mkdir /myvol
RUN echo "hello world" > /myvol/greeting
VOLUME /myvol
RUN echo "hello" > /myvol/greeting

可以从本地主机或其他容器挂载数据卷,一般用来存放数据库和需要保存的数据等

10. WORKDIR

指定在创建容器后,终端默认登陆进来的工作目录,即一个落脚点。

为后续的RUN、CMD和ENTRYPOINT指令配置工作目录格式为WORKDIR /path/to/workdir

可以使用多个WORKDIR指令,后续命令如果参数是相对路径,则 会基于之前命令指定的路径

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

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

11. EXPOSE

声明镜像内服务所监听的端口格式为EXPOSE[…]

EXPOSE 指令通知Docker 容器在运行时监听指定的网络端口。可以指定端口是侦听TCP 还是

UDP ,如果未指定协议,则默认值为TCP 。

EXPOSE 22 80 8443

默认情况下, EXPOSE 假定为TCP 。还可以指定UDP :

EXPOSE 80/udp

要同时在TCP和UDP上公开,请包括以下两行:

EXPOSE 80/tcp
EXPOSE 80/udp

无论EXPOSE设置如何,都可以在运行时使用该-p标志覆盖它们。例如

docker run -p 80:80/tcp

DockerFile创建镜像

编写完成Dockerfile之后,可以通过docker build命令来创建镜像。

基本的格式为docker build[选项]内容路径,该命令将读取指定路径下(包括子目录)的Dockerfile,并将该路径下的所有内容发送给Docker服务端,由服务端来创建镜像

如果使用非内容路径下的Dockerfile,可以通过-f选项来指定其路径

$ docker build -f /path/to/a/Dockerfile .

要指定生成镜像的标签信息,可以使用-t选项

docker build -t jourwon/ubuntu:v1 .

docker build 最后的 . 号,其实是在指定镜像构建过程中的上下文环境的目录

镜像管理

Docker镜像由一系列层组成。每层代表镜像的Dockerfile中的一条指令。除最后一层外的每一层都是只 读的。考虑以下Dockerfile:

FROM ubuntu:18.04
COPY . /app
RUN make /app
CMD python /app/app.py

该Dockerfile包含四个命令,每个命令创建一个层。该 FROM语句首先从ubuntu:18.04镜像创建只读层。

该COPY命令从Docker客户端的当前目录添加一些文件。

该RUN命令使用命令构建您的应用程序make。

最后一层指定在容器中运行什么命令。 运行镜像并生成容器时,可以在基础层之上添加一个新的可写层

(“容器层”)。对运行中的容器所做的所有更改(例如写入新文件,修改现有文件和删除文件)都将写 入此可写容器层。

容器和镜像之间的主要区别是可写顶层。在容器中添加新数据或修改现有数据的所有写操作都存储在此 可写层中。删除容器后,可写层也会被删除。基础镜像保持不变。

因为每个容器都有其自己的可写容器层,并且所有更改都存储在该容器层中,所以多个容器可以共享对 同一基础镜像的访问,但具有自己的数据状态。下图显示了多个共享相同Ubuntu 18.04镜像的容器

查看镜像的分层信息

docker history 镜像ID

磁盘上容器大小

要查看正在运行的容器的大致大小,可以使用以下docker ps -s 命令。有两个不同的列与大小有关。

size :用于每个容器的可写层的数据量(在磁盘上)。

virtual size :容器使用的只读图像数据加上容器的可写层使用的数据量size。多个容器可以共享部分或全部只读图像数据。从同一图像开始的两个容器共享100%的只读数据,而具有不同图像 的两个容器(具有相同的层)共享这些公共层。因此,您不能只对虚拟大小进行总计。这高估了总 磁盘使用量,可能是一笔不小的数目。

我们可以通过Docker仓库来传输我们的镜像,也可以通过文件模式

docker save 镜像ID -o xxxx.tar 或(docker save xxxx > xxxx.tar)
docker load -i xxxx.tar 或docker (docker load < xxxx.tar)
docker diff 容器ID
docker commit 容器ID svendowideit/testimage:version4 # 直接保存容器
docker commit --change='CMD ["apachectl", "-DFOREGROUND"]' -c "EXPOSE 80" 容器ID
svendowideit/testimage:version4 # 将正在运行的容器添加几个层之后再保存】

DockerFile模版

编写建议:

  • 从适当的基础镜像开始。例如,如果需要JDK镜像基于正式openjdk 镜像

  • 使用多阶段构建。例如,可以使用该maven 镜像来构建Java应用程序,然后将其重置为该tomcat镜像并将Java构件复制到正确的位置以部署我们的应用程序,所有这些操作都在同一Dockerfile中。这意味着的最终镜像不包括构建所引入的所有库和依赖项,而仅包括运行它们所需的工件和环境。

  • 通过最小化Dockerfile中RUN 单独命令的数量来减少镜像中的层数。可以通过将多个命令合并为RUN 一行并使用Shell的机制将它们组合在一起来实现此目的。虑以下两个片段。第一层在镜像中创建两层,而第二层仅创建一层。

#在镜像中创建两层
RUN apt-get -y update
RUN apt-get install -y python
#在镜像中创建一层
RUN apt-get -y update && apt-get install -y python
  • 如果有多个具有很多共同点的图像,请考虑使用共享的组件创建自己的基础镜像,Docker只需要加载一次公共层,然后将它们缓存。这意味着我们的派生镜像将更有效地使用Docker主机上的内存,并更快地加载。

JDK镜像模版

dockerfile-jdk

FROM jourwon/centos/7.6/centos
ENV JAVA_HOME="/apps/jdk"
ENV PATH=$JAVA_HOME/bin:$PATH
ADD ./jdk-8u251-linux-x64.tar.gz /apps/
RUN ln -s /apps/jdk1.8.0_251 /apps/jdk
ADD ./UnlimitedJCEPolicyJDK8/US_export_policy.jar /apps/jdk/jre/lib/security/
ADD ./UnlimitedJCEPolicyJDK8/local_policy.jar /apps/jdk/jre/lib/security/
ADD ./msyhbd.ttf /apps/jdk/jre/lib/fonts/
ADD ./msyh.ttf /apps/jdk/jre/lib/fonts/
CMD ["/bin/bash"]

构建命令:

#下载jce_policy-8.zip并解压到当前目录
wget http://pkgs-linux.cvimer.com/jdk/1.8/jce_policy-8.zip
unzip jce_policy-8.zip
#下载jdk-8u251-linux-x64.tar.gz、msyh.ttf、msyhbd.ttf
wget http://pkgs-linux.cvimer.com/jdk/1.8/jdk-8u251-linux-x64.tar.gz
wget http://pkgs-linux.cvimer.com/fonts/msyh.ttf
wget http://pkgs-linux.cvimer.com/fonts/msyhbd.ttf
#构建
docker build -f dockerfile-jdk -t jourwon/centos/7.6/jdk/1.8/jdk .

Docker数据持久化

  1. 创建一个卷,待后边使用
docker volume create test_volume
  1. 分别启动2个容器挂载上卷
# 在2个终端窗口启动2个容器
docker run -it --rm -v test_volume:/test nginx:latest /bin/bash
docker run -it --rm -v test_volume:/test nginx:latest /bin/bash
cd /test;
touch a.txt
ls /test
# 在两个容器中我们均可以看到我们创建的文件,这样我们就可以做到了在多个容器之间实现数据共享

挂载在容器/test 目录内创建。Docker 不支持容器内安装点的相对路径。 多个容器可以在同一时间段内使用相同的卷。如果两个容器需要访问共享数据,这将很有用。例如,如果一个容器写入而另一个容器读取数据。 卷名在驱动程序test必须唯一。这意味着我们不能将相同的卷名与两个不同的驱动程序一起使用。 如果们指定了当前test_volume程序上已在使用的卷名,则Docker会假定我们要重用现有卷,并且不会返回错误。如果开始无test_volume 则会创建这个卷

当然除了使用卷,也可以使用将宿主机的文件映射到容器的卷,命令类似,只不过不用提前创建卷,而且数据会映射到宿主机上

docker run -it --rm -v /root/test_volume:/test centos /bin/bash

注意如果宿主机上的目录可以不存在,会在启动容器的时候创建

构建过程解析

构建步骤

  • 编写Dockerfile文件
  • docker build
  • docker run

基础知识

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

Dockerfile 执行流程

Docker 在执行 DockerFile 时有以下几个流程:

  1. Docker 从基础镜像运行一个容器,以这个基础镜像为开始,一层一层的加入新的内容。
  2. 执行类似 docker commit 的操作提交一个新的镜像层。
  3. Docker 再基于刚提交的镜像运行一个新容器。
  4. 执行 DockerFile 中的下一条指令直到所有指令都执行完成。

小结

从应用软件的角度来看,Dockerfile、Docker镜像和Docker容器分别代表软件的三个阶段:

  • Dockerfile:软件的原材料。Dockerfile 定义了进程需要的一切东西。Dockerfile 设计的内容包括执行代码或者文件、环境变量、依赖包、运行时环境、动态链接库、操作系统的发行版本、服务进程和内核进程(当应用进程需要和系统服务和内核进程打交道时,需要考虑如何设计 namespace 的权限控制)等。
  • Docker 镜像:软件的交付品。在用 Dockerfile 定义一个文件后,docker build 会产生一个 Docker 镜像。
  • Docker 容器:软件的运行状态。当运行 Docker 镜像时生成容器,容器是直接提供服务的。

以上是关于Docker 从入门到实践系列五 - Dockerfile文件的主要内容,如果未能解决你的问题,请参考以下文章

Docker 从入门到实践系列五 - Dockerfile文件

Docker 从入门到实践系列四 - Docker 容器编排利器 Docker Compose

Docker 从入门到实践系列四 - Docker 容器编排利器 Docker Compose

Docker 从入门到实践系列二 - Docker 安装

Docker 从入门到实践系列二 - Docker 安装

Docker 从入门到实践系列三 - Docker 常用命令