Docker技能需求很高主要是因为Docker
我们可以自动在so-called内部部署应用程序containers
,创建可轻松复制到以下位置的定制环境Docker
技术的支持。在本教程中,我们将看到如何创建一个Docker
从头开始,使用
imageDockerfile
。我们将学习可用于自定义图像,如何构建图像以及如何基于图像运行容器的最重要说明。
在本教程中,您将学习:
- 如何使用Dockerfile创建Docker映像
- 一些最常用的Dockerfile指令
- 如何在容器中实现数据持久性
使用的软件要求和约定
类别 | 使用的要求,约定或软件版本 |
---|---|
系统 | Os-independent |
软件 | 码头工人 |
其他 |
|
约定 | #-要求linux命令可以直接以root用户身份或通过使用root特权以root特权执行sudo 命令$-要求linux命令以普通非特权用户身份执行 |
图片和容器
在开始之前,可能需要先明确定义当我们谈论什么时的意思images
和 containers
在…的背景下Docker
。映像可以视为Docker世界的构建块。它们代表用于创建容器的”blueprints”。确实,当创建容器时,它代表了其所基于图像的具体实例。
可以从同一映像创建许多容器。在本文的其余部分中,我们将学习如何在Acrobat中为我们的需求提供量身定制图像的说明。Dockerfile
,如何实际构建图像以及如何基于图像运行容器。
使用Dockerfile构建我们自己的映像
为了建立自己的形象,我们将使用Dockerfile.
Dockerfile包含创建和设置映像所需的所有指令。 Dockerfile准备就绪后,我们将使用docker build
命令来实际构建映像。
我们应该做的第一件事是创建一个新目录来承载我们的项目。在本教程中,我们将构建一个包含Apache
Web服务器,因此我们将命名项目”dockerized-apache”的根目录:
$ mkdir dockerized-apache
这个目录就是我们所说的build context
。在构建过程中,其中包含的所有文件和目录,包括Dockerfile
我们将创建的文件发送到Docker守护程序,以便可以轻松访问它们,除非它们已列在.dockerignore
文件。
让我们创建我们的Dockerfile
。该文件必须被调用Dockerfile
如上所述,其中将包含创建具有所需功能的图像所需的所有说明。我们启动我们喜欢的文本编辑器,并通过编写以下说明开始:
FROM ubuntu:18.10
LABEL maintainer="egidio.docile@linuxconfig.org"
我们必须提供的第一条指令是FROM
:使用它,我们可以指定一个将用作基础的现有图像(这称为base
),创建自己的。在这种情况下,我们的基本图像将是
imageubuntu
。在这种情况下,除了图片名称外,我们还使用了一个标签,以便指定我们要使用的图片的版本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
。使用此命令Apache
Web服务器启动于foreground
模式:必须在容器中正常工作。的docker run
命令在命令行上运行指定的命令new
容器:
$ sudo docker run --name=linuxconfig-apache -d
-p 8080:80 linuxconfig/dockerized-apache apachectl -D FOREGROUND
a51fc9a6dd66b02117f00235a341003a9bf0ffd53f90a040bc1122cbbc453423
屏幕上打印的数字是多少?它是ID
的容器!容器启动并运行后,我们应该能够访问默认设置的页面Apache
VirtualHost在localhost:8080
地址(端口8080
主机上的端口映射80
在容器上):
我们的设置正常运行。如果我们运行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
, 这俩exec
和shell
形式可以使用它。两者之间的最大区别是,从命令行传递的命令不会覆盖通过以下命令指定的命令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 FOREGROUND
exec格式的选项。假设我们现在重建图像,并使用以下命令重新创建容器:
$ sudo docker run --name=linuxconfig-apache -d -p 8080:80
linuxconfig/dockerized-apache -D FOREGROUND
容器启动时,-D FOREGROUND
参数被附加到Dockerfile
与ENTRYPOINT
说明,但仅当使用exec
形成。可以通过运行docker ps
命令(这里我们在命令中添加了一些选项,以更好地显示和格式化其输出,仅选择我们需要的信息):
$ sudo docker ps --no-trunc --format
"{{.Names}}\t{{.Command }}"
linuxconfig-apache "/usr/sbin/apachectl -D FOREGROUND"
就像CMD
,ENTRYPOINT
指令只能提供一次。如果它在Dockerfile中多次出现,则仅考虑最后一次出现。可以覆盖默认值ENTRYPOINT
通过使用--entrypoint
的选项docker run
命令。
结合CMD和ENTRYPOINT
现在我们知道了CMD
和ENTRYPOINT
说明,我们也可以将它们结合起来。通过这样做,我们可以获得什么?我们可以用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
命令将被执行。如果我们改为提供一些参数,它们将覆盖
FOREGROUNDDockerfile
与CMD
指令。例如,如果我们运行:
$ 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
指令可用于复制文件和目录。当目标路径不存在时,将在容器内创建目标路径。所有新文件和目录都使用UID
和GID
的0
。
复制容器内文件的另一种可行方法是使用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
指令,用于指定要处理的基本映像,
将元数据添加到图像的说明,
LABELEXPOSE
声明要在容器中暴露的端口的指令。我们还学习了如何将所述端口映射到主机系统端口。
我们学习了如何使用RUN
指令以在映像上运行命令,我们学习了如何指定从命令行和内部启动容器时要执行的命令Dockerfile
。我们看到了如何通过使用CMD
和ENTRYPOINT
说明,两者之间有什么区别。最后,我们看到了如何COPY
容器中的数据,以及如何使用卷实现数据持久性。在我们的示例中,我们仅讨论了可用于指令集的一小部分指令。Dockerfile
。
有关完整和详细的列表,请查阅官方Docker文档。同时,如果您想知道如何构建一个整体LAMP
使用Docker和docker-compose工具进行堆栈,您可以看一下关于如何在Ubuntu 18.04 Bionic Beaver Linux上使用docker-compose创建基于docker的LAMP堆栈。