当前位置: 首页>>技术教程>>正文


如何使用Dockerfile构建Docker映像

, ,

Docker技能需求很高主要是因为Docker我们可以自动在so-called内部部署应用程序containers,创建可轻松复制到以下位置的定制环境Docker技术的支持。在本教程中,我们将看到如何创建一个Docker
image
从头开始,使用Dockerfile。我们将学习可用于自定义图像,如何构建图像以及如何基于图像运行容器的最重要说明。

在本教程中,您将学习:

  • 如何使用Dockerfile创建Docker映像
  • 一些最常用的Dockerfile指令
  • 如何在容器中实现数据持久性

使用的软件要求和约定

软件要求和Linux命令行约定
类别 使用的要求,约定或软件版本
系统 Os-independent
软件 码头工人
其他
  • 运行中的Docker守护程序
  • docker命令行实用程序
  • 熟悉Linux命令行界面
约定 -要求linux命令可以直接以root用户身份或通过使用root特权以root特权执行sudo命令$-要求linux命令以普通非特权用户身份执行

图片和容器

在开始之前,可能需要先明确定义当我们谈论什么时的意思images containers在…的背景下Docker。映像可以视为Docker世界的构建块。它们代表用于创建容器的”blueprints”。确实,当创建容器时,它代表了其所基于图像的具体实例。

可以从同一映像创建许多容器。在本文的其余部分中,我们将学习如何在Acrobat中为我们的需求提供量身定制图像的说明。Dockerfile,如何实际构建图像以及如何基于图像运行容器。

使用Dockerfile构建我们自己的映像

为了建立自己的形象,我们将使用Dockerfile.Dockerfile包含创建和设置映像所需的所有指令。 Dockerfile准备就绪后,我们将使用docker build命令来实际构建映像。

我们应该做的第一件事是创建一个新目录来承载我们的项目。在本教程中,我们将构建一个包含ApacheWeb服务器,因此我们将命名项目”dockerized-apache”的根目录:

$ mkdir dockerized-apache



这个目录就是我们所说的build context。在构建过程中,其中包含的所有文件和目录,包括Dockerfile我们将创建的文件发送到Docker守护程序,以便可以轻松访问它们,除非它们已列在.dockerignore文件。

让我们创建我们的Dockerfile。该文件必须被调用Dockerfile如上所述,其中将包含创建具有所需功能的图像所需的所有说明。我们启动我们喜欢的文本编辑器,并通过编写以下说明开始:

FROM ubuntu:18.10
LABEL maintainer="egidio.docile@linuxconfig.org"

我们必须提供的第一条指令是FROM:使用它,我们可以指定一个将用作基础的现有图像(这称为base
image
),创建自己的。在这种情况下,我们的基本图像将是ubuntu。在这种情况下,除了图片名称外,我们还使用了一个标签,以便指定我们要使用的图片的版本18.10。如果未指定标签,latest标记是默认使用的:这将导致使用基础图像的最新可用版本。如果该图像尚未出现在我们的系统上,则将从dockerhub

之后FROM指令,我们用LABEL。该指令是可选的,可以重复多次,用于将元数据添加到我们的图像中。在这种情况下,我们使用它来指定映像维护器。

RUN指令

在这一点上,如果我们运行docker build,除了添加的元数据外,我们只会生成与基础图像相同的图像。这对我们没有用。我们说过要”dockerize”Apache网络服务器,因此我们要做的下一件事Dockerfile,是提供说明以将Web服务器安装为映像的一部分。让我们完成此任务的说明是RUN

FROM ubuntu:18.10
LABEL maintainer="egidio.docile@linuxconfig.org"

RUN apt-get update && apt-get -y install apache2

RUN指令用于在图像顶部执行命令。要记住的一件非常重要的事情是RUN我们使用的指令新层创建并添加到堆栈中。在这方面,Docker非常聪明:已经构建的层将是”cached”:这意味着如果我们基于我们的基础构建映像Dockerfile,然后我们决定例如添加另一个RUN指令(进而是新层)的末尾,构建不会从头开始,而只会运行新指令。

为此,当然,已经在Dockerfile不得修改。甚至可以在构建图像时完全避免这种行为,只需使用--no-cache的选项docker build命令。

在我们的案例中,我们使用了RUN指令执行apt-get update && apt-get -y install apache2命令。注意我们如何通过-y选项apt-get install命令:此选项可以使该命令所需的所有确认信息自动得到肯定答案。这是必要的,因为我们是非交互式地安装软件包。

暴露端口80

众所周知,Apache Web服务器监听port 80用于标准连接。我们必须指示Docker使该端口在容器上可访问。为了完成任务,我们使用EXPOSE功能并提供端口号。出于安全原因,仅在启动容器时才打开指定的端口。让我们将此指令添加到我们的Dockerfile

FROM ubuntu:18.10
LABEL maintainer="egidio.docile@linuxconfig.org"

RUN apt-get update && apt-get -y install apache2
EXPOSE 80

建立形象

在这一点上,我们已经可以尝试建立我们的形象。在项目的根目录”dockerized-apache”内部,我们运行以下命令:

$ sudo docker build -t linuxconfig/dockerized-apache .

让我们检查命令。首先,我们为命令添加sudo前缀,以使其具有管理特权。通过将用户添加到docker组,但这代表一个安全风险。的-t我们提供的选项,简称--tag,如果构建成功,让我们将存储库名称和可选的标签应用于图像。

最后,.指示码头工人寻找Dockerfile在当前目录中。一旦启动命令,构建过程就会开始。进度和构建消息将显示在屏幕上:

Sending build context to Docker daemon 2.048
kB
Step 1/4 : FROM ubuntu:18.10
Trying to pull repository docker.io/library/ubuntu ...
[...]

几分钟后,我们的映像将成功创建。要进行验证,我们可以运行docker images命令,该命令返回我们本地Docker存储库中存在的所有映像的列表:

$ sudo docker images
REPOSITORY                        TAG                 IMAGE ID
CREATED             SIZE
linuxconfig/dockerized-apache     latest              7ab7b6873614        2
minutes ago       191 MB


如预期的那样,图像出现在列表中。我们注意到,由于我们没有提供标签(仅提供存储库名称,linuxconfig/dockerized-apache)latest标签已自动应用于我们的图片。一个ID也已分配给它,7ab7b6873614:我们可以在以后的命令中使用它来引用图像。

根据图像启动容器

现在我们的映像已经准备好了,我们可以创建并启动一个container基于它。为了完成任务,我们使用docker run命令:

$ sudo docker run --name=linuxconfig-apache -d -p 8080:80
linuxconfig/dockerized-apache apachectl -D FOREGROUND

让我们检查一下上面的命令。我们提供的第一个选项是--name:使用它,我们为容器指定一个名称,在本例中为”linuxconfig-apache”。如果我们忽略此选项,则会为容器分配一个随机生成的名称。

-d选项(缩写为--detach)导致容器在后台运行。

-p选项,简称--publish以便将容器端口(或一系列端口)发布到主机系统。该选项的语法如下:

-p localhost_port:container_port

在这种情况下,我们发布了port 80我们之前在容器中暴露给主机port 8080。为了完整起见,我们必须说,也可以使用-P选项(缩写为--publish-all),而是使容器中公开的所有端口都映射到random主机上的端口。

我们在上面的命令中指定的最后两件事是:image容器应基于,并且command在启动容器时运行,这是可选的。图像当然是linuxconfig/dockerized-apache,这是我们之前构建的。

我们指定的命令是apachectl -D FOREGROUND。使用此命令ApacheWeb服务器启动于foreground模式:必须在容器中正常工作。的docker run命令在命令行上运行指定的命令new容器:

$ sudo docker run --name=linuxconfig-apache -d
-p 8080:80 linuxconfig/dockerized-apache apachectl -D FOREGROUND
a51fc9a6dd66b02117f00235a341003a9bf0ffd53f90a040bc1122cbbc453423

屏幕上打印的数字是多少?它是ID的容器!容器启动并运行后,我们应该能够访问默认设置的页面ApacheVirtualHost在localhost:8080地址(端口8080主机上的端口映射80在容器上):

default-index-page

默认的Apache index.html页面

我们的设置正常运行。如果我们运行docker ps命令,其中列出了系统中所有活动的容器,我们可以检索有关容器的信息:id(简短版本,易于从人类的命令行参考),从中运行的图像,使用的命令及其创建时间和当前状态,端口映射和名称。

$ sudo docker ps
CONTAINER ID        IMAGE                           COMMAND
CREATED             STATUS              PORTS                  NAMES
a51fc9a6dd66        linuxconfig/dockerized-apache   "apachectl -D FORE..."   28
seconds ago      Up 28 seconds       0.0.0.0:8080->80/tcp
linuxconfig-apache

要停止该容器,我们所需要做的就是通过其ID或名称来引用它,然后运行docker stop命令。例如:

$ sudo docker stop linuxconfig-apache

重新启动:

$ sudo docker start linuxconfig-apache

直接通过Dockerfile执行命令

从这里开始,我们在运行时使用docker run命令,我们指定了启动容器时要启动的命令。有时我们想直接在Dockerfile中指定后者。我们可以通过两种方式做到这一点:CMD要么ENTRYPOINT

两种指令都可以用于相同的目的,但是当从命令行中指定命令时,它们的行为也不同。让我们看看如何。

CMD指令

CMD指令基本上可以以两种形式使用。首先是exec形成:

CMD ["/usr/sbin/apachectl", "-D", "FOREGROUND"]

另一个是shell形成:

CMD /usr/sbin/apachectl -D FOREGROUND

exec通常是首选。值得注意的是,当使用exec表单时,不会调用shell,因此不会发生变量扩展。如果需要变量扩展,我们可以使用shell表格,或者我们可以直接在exec模式,如:

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

CMD指令只能在Dockerfile。如果多个CMD提供了选项,只有最后一个才会生效。该指令的目的是提供一个default容器启动时要启动的命令:

FROM ubuntu:18.10
LABEL maintainer="egidio.docile@linuxconfig.org"

RUN apt-get update && apt-get -y install apache2
EXPOSE 80

CMD ["/usr/sbin/apachectl", "-D", "FOREGROUND"]

用以下命令指定的命令CMD在 – 的里面Dockerfile,是默认设置,如果在执行时从命令行指定了另一个命令,它将被覆盖docker
run

ENTRYPOINT指令

ENTRYPOINT指令还可以用于配置启动容器时要使用的命令,例如CMD, 这俩execshell形式可以使用它。两者之间的最大区别是,从命令行传递的命令不会覆盖通过以下命令指定的命令ENTRYPOINT:相反它将是附加的对此。

通过使用此指令,我们可以指定一个基本命令,并使用运行该命令时提供的选项对其进行修改。docker-run命令,使我们的容器像可执行文件一样运行。让我们来看一个例子Dockerfile

FROM ubuntu:18.10
LABEL maintainer="egidio.docile@linuxconfig.org"

RUN apt-get update && apt-get -y install apache2
EXPOSE 80

ENTRYPOINT ["/usr/sbin/apachectl"]

在这种情况下,我们用CMD指导ENTRYPOINT并删除了-D FOREGROUNDexec格式的选项。假设我们现在重建图像,并使用以下命令重新创建容器:

$ sudo docker run --name=linuxconfig-apache -d -p 8080:80
linuxconfig/dockerized-apache -D FOREGROUND


容器启动时,-D FOREGROUND参数被附加到DockerfileENTRYPOINT说明,但仅当使用exec形成。可以通过运行docker ps命令(这里我们在命令中添加了一些选项,以更好地显示和格式化其输出,仅选择我们需要的信息):

$ sudo docker ps --no-trunc --format
"{{.Names}}\t{{.Command }}"
linuxconfig-apache	"/usr/sbin/apachectl -D FOREGROUND"

就像CMDENTRYPOINT指令只能提供一次。如果它在Dockerfile中多次出现,则仅考虑最后一次出现。可以覆盖默认值ENTRYPOINT通过使用--entrypoint的选项docker run命令。

结合CMD和ENTRYPOINT

现在我们知道了CMDENTRYPOINT说明,我们也可以将它们结合起来。通过这样做,我们可以获得什么?我们可以用ENTRYPOINT指定有效的基本命令,以及CMD为其指定默认参数的指令。

默认情况下,该命令将使用这些默认参数运行,除非在运行时从命令行覆盖它们docker run。坚持我们的Dockerfile,我们可以这样写:

FROM ubuntu:18.10
LABEL maintainer="egidio.docile@linuxconfig.org"

RUN apt-get update && apt-get -y install apache2
EXPOSE 80

ENTRYPOINT ["/usr/sbin/apachectl"]
CMD ["-D", "FOREGROUND"]

如果我们以此重建图像Dockerfile,删除我们创建的上一个容器,然后re-launchdocker run命令中未指定任何其他参数,/usr/bin/apachectl -D
FOREGROUND
命令将被执行。如果我们改为提供一些参数,它们将覆盖DockerfileCMD指令。例如,如果我们运行:

$ sudo docker run --name=linuxconfig-apache -d -p 8080:80
linuxconfig/dockerized-apache -X

启动容器时将执行的命令为/usr/bin/apachectl -X。让我们验证一下:

$ sudo docker ps --no-trunc --format
"{{.Names}}\t{{.Command }}"
linuxconfig-apache	"/usr/sbin/apachectl -X"

发出的命令符合预期:-X顺便说一下,该选项可以使httpd守护程序在debug mode

将文件复制到容器中

我们的”dockerized” Apache服务器正常工作。如我们所见,如果我们导航到localhost:8080,我们将默认的Apache欢迎页面可视化。现在,假设我们有一个准备与该容器一起发货的网站,我们如何才能对其进行”load”以便由Apache来代替它呢?

好了,出于本教程的考虑,我们将只替换默认的index.html文件。要完成任务,我们可以使用COPY指令。假设我们在项目根目录(我们的构建上下文)中有一个备用index.html文件,其内容如下:

<html>
  <body>
    <h2>Hello!</h2>
    <h3>This file has been copied into the container with the COPY instruction!</h3>
  </body>
</html>

我们要加载并将其复制到/var/www/html目录位于容器内,因此位于我们的目录内Dockerfile我们添加COPY指令:

FROM ubuntu:18.10
LABEL maintainer="egidio.docile@linuxconfig.org"

RUN apt-get update && apt-get -y install apache2
EXPOSE 80

ENTRYPOINT ["/usr/sbin/apachectl"]
CMD ["-D", "FOREGROUND"]
COPY index.html /var/www/html/index.html

我们重建图像和容器。如果现在导航到localhost:8080,我们将看到新消息:

# new message

COPY指令可用于复制文件和目录。当目标路径不存在时,将在容器内创建目标路径。所有新文件和目录都使用UIDGID0

复制容器内文件的另一种可行方法是使用ADD指令,比COPY。通过此指令,我们可以复制文件,目录,也可以URLs。此外,如果我们复制本地tar archive具有公认的压缩格式,它将被自动解压缩并复制为容器内的目录。

理想的策略是使用COPY除非提供的附加功能ADD确实需要。

创建一个音量

在前面的示例中,演示了如何COPY根据指令的工作原理,我们替换了容器内默认Apache VirtualHost的默认index.html文件。

如果停止并启动容器,我们仍然会找到所做的修改,但是如果由于某种原因删除了该容器,其可写层中包含的所有数据将随之丢失。如何解决这个问题呢?一种方法是使用VOLUME指令:

FROM ubuntu:18.10
LABEL maintainer="egidio.docile@linuxconfig.org"

RUN apt-get update && apt-get -y install apache2
EXPOSE 80

ENTRYPOINT ["/usr/sbin/apachectl"]
CMD ["-D", "FOREGROUND"]
COPY index.html /var/www/html/index.html
VOLUME /var/www/html



VOLUME指令采用一个或多个目录(在这种情况下/var/www/html),并使它们用作创建容器时生成的外部randomly-named卷的安装点。

这样,我们放入用作挂载点的目录中的数据将保留在挂载的卷内,并且即使容器被破坏也仍然存在。如果设置为用作挂载点的目录在初始化时已包含数据,则该数据将被复制到安装在其上的卷内部。

让我们重建图像和容器。现在,我们可以通过检查容器来验证该卷是否已创建并正在使用中:

$ sudo docker inspect linuxconfig-apache
[...]
"Mounts": [
            {
                "Type": "volume",
                "Name": "8f24f75459c24c491b2a5e53265842068d7c44bf1b0ef54f98b85ad08e673e61",
                "Source": "/var/lib/docker/volumes/8f24f75459c24c491b2a5e53265842068d7c44bf1b0ef54f98b85ad08e673e61/_data",
                "Destination": "/var/www/html",
                "Driver": "local",
                "Mode": "",
                "RW": true,
                "Propagation": ""
            }
        ],
[...]

就像已经说过的那样,即使在容器被破坏后,该卷也将继续存在,因此我们的数据不会丢失。

VOLUME里面的指令Dockefile从上面的docker inspect命令的输出中可以看到,make使创建了一个随机命名的卷。定义一个named volume,或将一个已经存在的卷挂载到容器中,我们必须在运行时在运行容器时指定它docker run命令,使用-v选项(缩写为--volume)。让我们来看一个例子:

$ sudo docker run --name=linuxconfig-apache -d -p 8080:80 -v
myvolume:/var/www/html linuxconfig/dockerized-apache

在上面的命令中,我们使用了-v选项指定volume name(非常重要:请注意,它不是路径,而是简单的名称),并且mountpoint在容器内使用以下语法:

<volume_name>:<mountpoint>

当我们执行这样的命令时,名为”myvolume”的卷将被安装在容器内部的特定路径上(如果该卷尚不存在,则会创建该卷)。如前所述,如果卷为空,则容器内安装点上已经存在的数据将被复制到其中。使用docker volume ls命令,我们可以确认已创建具有指定名称的卷:

$ sudo docker volume ls
DRIVER              VOLUME NAME
local               myvolume

要删除卷,我们使用docker volume rm命令,并提供要删除的卷的名称。但是,Docker不允许我们删除活动容器使用的卷:

$ sudo docker volume rm myvolume
Error response from daemon: Unable to remove volume, volume still in use: remove
myvolume: volume is in use -
[95381b7b6003f6165dfe2e1912d2f827f7167ac26e22cf26c1bcab704a2d7e02]

数据持久性的另一种方法是:bind-mount容器内的主机目录。这种方法的优点是可以让我们使用自己喜欢的工具在本地处理代码,并立即看到更改的效果,使更改立即反映在容器内,但是有一个很大的缺点:容器变得依赖于主机目录结构。

因此,由于可移植性是Docker的主要目标之一,因此无法定义bind-mount在Dockerfile中,但仅在运行时。要完成此任务,我们使用-v的选择docker run再次命令,但这一次我们提供path主机文件系统中的目录而不是卷名:

$ sudo docker run --name=linuxconfig-apache -d -p 8080:80 -v
/path/on/host:/var/www/html linuxconfig/dockerized-apache

启动上述命令时,主机目录/path /on /host将安装在容器内的/var /www /html上。如果主机上的目录不存在,则会自动创建。在这种情况下,容器内的mountpoint目录中的数据(在我们的示例中为/var /www /html)为复制到安装在其上的主机目录,因为它发生在卷上。

结论

在本教程中,我们学习了使用以下命令创建和构建Docker映像所需的基本概念。Dockerfile以及如何基于该容器运行容器我们构建了一个非常简单的映像,使我们可以运行Apache Web服务器的”dockerized”版本。在此过程中,我们看到了如何使用FROM指令,用于指定要处理的基本映像,
LABEL
将元数据添加到图像的说明,EXPOSE声明要在容器中暴露的端口的指令。我们还学习了如何将所述端口映射到主机系统端口。

我们学习了如何使用RUN指令以在映像上运行命令,我们学习了如何指定从命令行和内部启动容器时要执行的命令Dockerfile。我们看到了如何通过使用CMDENTRYPOINT说明,两者之间有什么区别。最后,我们看到了如何COPY容器中的数据,以及如何使用卷实现数据持久性。在我们的示例中,我们仅讨论了可用于指令集的一小部分指令。Dockerfile

有关完整和详细的列表,请查阅官方Docker文档。同时,如果您想知道如何构建一个整体LAMP使用Docker和docker-compose工具进行堆栈,您可以看一下关于如何在Ubuntu 18.04 Bionic Beaver Linux上使用docker-compose创建基于docker的LAMP堆栈

参考资料

本文由Ubuntu问答整理, 博文地址: https://ubuntuqa.com/article/8401.html,未经允许,请勿转载。