Dockerfile

发布于 更新于
本文目录
  1. Dockerfile
  2. 1. 镜像与容器的关系
  3. 2. 什么是Dockerfile
  4. 3. 构建一个web服务器
  5. 3.1 web服务
  6. 3.2 编写Dockerfile
  7. 3.3 构建镜像
  8. 4. Dockerfile指令详解
  9. FROM
  10. RUN
  11. CMD
  12. ENTRYPOINT
  13. EXPOSE
  14. ENV
  15. ARG
  16. ADD
  17. COPY
  18. VOLUME
  19. WORKDIR

技术文章持续集成与运维

docker中镜像是层级结构的,我们可以通过命令docker history <id/name>查看镜像中的每一层的大小和内容。镜像是readonly的。容器从镜像启动时,docker会在镜像的最上层创建一个可写层,镜像本身保持

Dockerfile

1. 镜像与容器的关系

image.png

docker中镜像是层级结构的,我们可以通过命令docker history <id/name>查看镜像中的每一层的大小和内容。镜像是readonly的。容器从镜像启动时,docker会在镜像的最上层创建一个可写层,镜像本身保持不变。删除容器只是删除容器创建的可写层,因此创建和删除容器都很快。

所以我们在创建应用时会利用Dockerfile将只读文件提前构建在镜像中来提高容器的效率。

2. 什么是Dockerfile

Dockerfile 是一个文本文档,其中包含组装 Docker 映像的指令。当我们通过执行docker build 命令告诉 Docker 构建我们的镜像时,Docker 会读取这些指令,执行它们,并因此创建一个 Docker 镜像。

3. 构建一个web服务器

我们通过构建一个python版本的web服务器来学习如何构建一个镜像。

3.1 web服务

使用 Flask 框架创建一个简单的 Python 应用程序。在本地创建一个名为python-docker的目录,然后按照下面的步骤创建一个简单的web服务器。

er cd /path/to/python-docker
 pip3 install Flask
 pip3 freeze > requirements.txt
 touch app.py

然后在app.py文件中输入以下代码。

from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello_world():
    return 'Hello, Docker!'

3.2 编写Dockerfile

在项目的根目录中(上个步骤中创建的python-docker目录),创建一个名为的文件Dockerfile并在文本编辑器中打开该文件。

Dockerfile 的名称是什么?

用于 Dockerfile 的默认文件名是Dockerfile(没有文件扩展名)。使用默认名称允许您运行docker build命令而无需指定其他命令标志。

某些项目可能需要不同的 Dockerfiles 用于特定目的。一个常见的约定是将这些命名为Dockerfile.<something>或<something>.Dockerfile。然后可以通过 docker build命令上的--file(或-f速记)选项使用此类 Dockerfile。

一般情况建议使用默认文件名Dockerfile。

接下来,在Dockerfile 中添加一行,告诉 Docker 我们希望为我们的应用程序使用什么基础镜像。

FROM python:3.8-slim-buster

Docker 镜像可以从其他镜像继承。因此,我们将使用官方 Python 镜像,而不是创建我们自己的基础镜像,该镜像已经拥有运行 Python 应用程序所需的所有工具和包。

为了在运行其余命令时更容易,创建一个工作目录。这会指示 Docker 使用此路径作为所有后续命令的默认位置。通过这样做,我们不必输入完整的文件路径,而是可以使用基于工作目录的相对路径。

WORKDIR /app

通常,下载了用 Python 编写的项目后要做的第一件事就是安装第三方包。这可确保您的应用程序已安装其所有依赖项。

我们需要将requirements.txt文件放入镜像中。使用COPY命令来执行此操作。COPY命令有两个参数,第一个参数告诉 Docker 您想要复制到镜像中的文件,第二个参数告诉 Docker 您希望将该文件复制到何处。我们要把requirements.txt文件复制到我们的工作目录/app中。

COPY requirements.txt requirements.txt

一旦在镜像中拥有requirements.txt文件,我们就可以使用RUN命令来执行命令pip3 install。这与我们在本地机器上运行pip3 install完全相同,但这次模块安装到镜像中。

RUN pip3 install -r requirements.txt

此时,我们有一个基于 Python 3.8 版的镜像,并且我们已经安装了我们的依赖项。下一步是将我们的源代码添加到镜像中。 我们将使用COPY命令,就像上面对requirements.txt文件所做的那样。

COPY . .

COPY命令获取当前目录中的所有文件并将它们复制到映像中。现在,我们要做的就是告诉Docker,当我们的映像在容器中执行时,我们想要运行什么命令。我们使用CMD命令来做这个。注意,我们需要通过指定——host=0.0.0.0使应用程序在外部可见(即在容器外部可见)。

CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0"]

下面是完整的Dockerfile。

# syntax=docker/dockerfile:1

FROM python:3.8-slim-buster

WORKDIR /app

COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt

COPY . .

CMD [ "python3", "-m" , "flask", "run", "--host=0.0.0.0"]

回顾一下,我们在本地机器上创建了一个名为python-docker的目录,并使用 Flask 框架创建了一个简单的 Python 应用程序。我们还使用requirements.txt文件来收集我们的需求,并创建了一个 Dockerfile,其中包含构建镜像的命令。Python 应用程序目录结构现在看起来像:

python-docker
|____ app.py
|____ requirements.txt
|____ Dockerfile

3.3 构建镜像

我们已经创建了我们的 Dockerfile,现在使用docker build命令来构建我们的镜像。docker build命令从Dockerfile和上下文来构建Docker镜像。构建的上下文是指定的PATH或URL中的一组文件。Dokcer构建过程可以访问位于此上下文中的任何文件。

docker build命令可接受一个--tag参数,用于设置镜像的名称和格式为name:tag

现在我们已经创建了我们的 Dockerfile,让我们构建我们的镜像。为此,我们使用docker build命令。该docker build命令从 Dockerfile 和“上下文”构建 Docker 镜像。构建的上下文是位于指定 PATH 或 URL 中的一组文件。Docker 构建过程可以访问位于此上下文中的任何文件。

标签用于设置图像的名称和格式为name:tag的可选标签。

build 命令可选地接受一个--tag标志。标签用于设置图像的名称和格式中的可选标签name:tag。简单起见我们暂时不使用可选项tag。如果您不传递tag,Docker 将使用“latest”作为其默认标签。

让我们构建我们的第一个 Docker 镜像。

 docker build --tag python-docker .
 [internal] load build definition from Dockerfile
 => transferring dockerfile: 203B
 [internal] load .dockerignore
 => transferring context: 2B
 [internal] load metadata for docker.io/library/python:3.8-slim-buster
 [1/6] FROM docker.io/library/python:3.8-slim-buster
 [internal] load build context
 => transferring context: 953B
 CACHED [2/6] WORKDIR /app
 [3/6] COPY requirements.txt requirements.txt
 [4/6] RUN pip3 install -r requirements.txt
 [5/6] COPY . .
 [6/6] CMD [ "python3", "-m", "flask", "run", "--host=0.0.0.0"]
 exporting to image
 => exporting layers
 => writing image sha256:8cae92a8fbd6d091ce687b71b31252056944b09760438905b726625831564c4c
 => naming to docker.io/library/python-docker

运行docker images命令查看我们当前本地的所有镜像。

$ docker images
REPOSITORY      TAG               IMAGE ID       CREATED         SIZE
python-docker   latest            8cae92a8fbd6   3 minutes ago   123MB
python          3.8-slim-buster   be5d294735c6   9 days ago      113MB

您应该看到至少列出了两个镜像。一个是base镜像3.8-slim-buster,另一个是我们刚刚构建的镜像python-docker:latest。

接下来通过这个镜像我们启动一个容器

$ docker run -d -p 5000:5000 python-docker:latest
2810011fd21029b3cd9817229e0b6cd16733a6d50855b9e6e0c04c1397e4afe9

现在通过http://127.0.0.1:5000就可以访问这个服务器了 。

4. Dockerfile指令详解

Dockerfile的指令不区分大小写。但是,约定是将它们大写,以便更容易地将它们与参数区分开来。

FROM

Docker按顺序运行Dockerfile指令。一个Dockerfile 必须以FROM指令开始。FROM指令指定我们从哪个父镜像开始构建。例如:

FROM python:3.8-slim-buster

表示构建需要的基础镜像是python:3.8-slim-buster,后续的操作都是基于它。

RUN

RUN用于执行命令行命令,有以下两种格式:

shell命令行格式

RUN <command>
# <command>等价于直接在终端执行shell命令

exec格式

RUN ["executable", "param1", "param2"]
# 例如
# RUN ["python3", "app.py"] 等价于 RUN python3 app.py

注意exec格式会以json数组的形式解析,所以必须使用双引号。

两种格式的主要区别是shell命令行格式默认调用命令shell,所以原生的shell命令最好使用这种格式。当有外部可执行文件时,使用exec格式。

CMD

类似于 RUN 指令,用于运行程序,但二者运行的时间点不同:

  • CMD 在docker run 时运行。

  • RUN 是在 docker build 时运行

作用:为启动的容器指定默认要运行的程序,程序运行结束,容器也就结束。CMD 指令指定的程序可被 docker run 命令行参数中指定要运行的程序所覆盖。

注意:如果 Dockerfile 中如果存在多个 CMD 指令,仅最后一个生效。

CMD命令有三种格式:

  • CMD ["executable","param1","param2"] (exec 格式,推荐使用)

  • CMD ["param1","param2"] (该写法是为 ENTRYPOINT 指令指定的程序提供默认参数)

  • CMD command param1 param2 (shell 格式)

ENTRYPOINT

类似于 CMD 指令,但其不会被 docker run 的命令行参数指定的指令所覆盖,而且这些命令行参数会被当作参数送给 ENTRYPOINT 指令指定的程序。

但是, 如果运行 docker run 时使用了 --entrypoint 选项,将覆盖 ENTRYPOINT 指令指定的程序。

优点:在执行 docker run 的时候可以指定 ENTRYPOINT 运行所需的参数。

注意:如果 Dockerfile 中如果存在多个 ENTRYPOINT 指令,仅最后一个生效。

格式:

ENTRYPOINT ["<executeable>","<param1>","<param2>",...]

可以搭配 CMD 命令使用:一般是变参才会使用 CMD ,这里的 CMD 等于是在给 ENTRYPOINT 传参,以下示例会提到。

示例:

假设已通过 Dockerfile 构建了 python-docker:test 镜像:

# syntax=docker/dockerfile:1

FROM python:3.8-slim-buster

WORKDIR /app

COPY requirements.txt requirements.txt
RUN pip3 install -r requirements.txt

COPY . .
ENTRYPOINT ["python3", "-m" , "flask", "run"] # 定参
CMD ["--host=0.0.0.0", "--port=5000"] # 变参
  1. 不传参运行

docker run python-docker:test

容器内会默认运行以下命令,启动主进程。

python3 -m flask run --host=0.0.0.0 --port=5000
  1. 传参运行

docker run python-docker:test --host=0.0.0.0 --port=5001

容器内部会运行一下命令,启动主进程。

python3 -m flask run --host=0.0.0.0 --port=5001

注意:一个Dockerfile中至少要要有一个CMD或ENTRYPOINT命令。

EXPOSE

仅仅只是声明端口。

作用:

  • 帮助镜像使用者理解这个镜像服务的监听端口,以方便配置映射。

  • 在运行时使用随机端口映射时,也就是 docker run -P 时,会自动随机映射 EXPOSE 的端口。

格式:

EXPOSE <端口1> [<端口2>...]

ENV

设置环境变量,定义了环境变量,那么在后续构建的指令中,就可以使用这个环境变量。

格式:

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

注意ENV设置的环境变量会一致存在于容器中,因此在某些情况下它会产生副作业,所以如果只是在构建过程中使用的环境变量可以使用ARG。

ARG

ARG也可以设置环境变量,不过与ENV的作用于不同,ARG 设置的环境变量仅对 Dockerfile 内有效,也就是说只有 docker build 的过程中有效,构建好的镜像内不存在此环境变量。

构建命令 docker build 中可以用 --build-arg <参数名>=<值> 来覆盖。

格式:

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

ADD

ADD指令从上下文目录中拷贝文件,目录到镜像中。格式如下:

ADD [--chown=<user>:<group>] <源路径>... <目标路径>
ADD [--chown=<user>:<group>] ["<src>",... "<dest>"]

ADD遵守如下规则:

  1. 源路径如果不是URL则必须在构建上下文目录下

  2. 如果源路径是可识别的压缩格式(identity、gzip、bzip2或xz)的本地tar归档文件,则会自动将其解压缩为一个目录。

  3. 如果目标路径以/结尾,目标路径被识别为目录,源路径内容会被拷贝到其中

  4. 当源路径有多个时,目标路径必须以/结尾

  5. 目标路径不以/结尾,它会被识别为普通文件

  6. 目标路径不存在时,它会自动创建

COPY

COPY指令与ADD命令格式完全一致,最主要的区别是:

  1. ADD 中的源路径可以是url(制定一个远程的文件或文件夹)这在Dockerfile是从标准输入中接收的时候非常有用。

  2. COPY中的源路径必须是在构建上下文路径中。

  3. COPY不会自动解压压缩文件

虽然ADD和COPY在功能上相似,但一般来说,COPY是首选。这是因为它比ADD更透明。 COPY仅支持将本地文件基本复制到容器中,而ADD有一些特性(如仅本地的tar提取和远程URL支持)不是很明显。因此,ADD的最佳用途是将本地tar文件自动提取到映像中,如ADD rootfs.tar.xz /。

如果您有多个Dockerfile步骤,它们使用来自您的上下文中不同的文件,请分别复制它们,而不是一次复制所有文件。这确保了只有当特定要求的文件发生更改时,每个步骤的构建缓存才会失效(强制重新运行该步骤)。

VOLUME

创建挂着点。在启动容器时忘记挂载数据卷,会自动挂载到匿名卷。

作用:

  • 避免重要的数据,因容器重启而丢失。

  • 避免容器不断变大。

格式:

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

在启动容器 docker run 的时候,我们可以通过 -v 参数指定卷。

WORKDIR

WORKDIR指令为Dockerfile中跟随它的任何RUN、CMD、ENTRYPOINT、COPY和ADD指令设置工作目录。如果WORKDIR不存在,即使没有在任何后续Dockerfile指令中使用它,也会创建它。WORKDIR指令可以在Dockerfile中多次使用。如果提供了相对路径,则它将相对于上一个WORKDIR指令的路径。For example:

WORKDIR /a
WORKDIR b
WORKDIR c
RUN pwd

这个Dockerfile中最后一个pwd命令的输出是/a/b/c。

更多命令见官方文档

来源:testingpai

继续阅读