Docker 初学者指南
对于任何刚接触云原生的人来说,理解 Docker 镜像和容器的概念都至关重要。无论您从事开发、DevOps 还是项目管理(或任何其他技术岗位 :)。一旦您掌握了 Docker 的基础知识,您将更容易理解 Kubernetes、服务网格以及几乎所有其他云原生工具的工作原理。您可以将本指南视为云原生的第一本实用指南。
什么是 Docker?
Docker 容器是由 Docker 公司推广的。容器的概念并不新鲜。容器在 Linux 中已经存在了十多年。正是 Docker 让它们变得更加流行。
容器背后的理念是将操作系统进行划分,以便安全地运行多个应用程序。Linux 的命名空间和cgroups 功能使这成为可能。简而言之,使用命名空间,您可以对操作系统的不同组件进行切片,并创建一个独立的工作空间。cgroups 允许对资源进行细粒度的控制。例如,这可以防止单个容器耗尽所有资源。
虚拟机 (VM) 和容器之间有什么区别?
您可以使用在物理计算机上运行的软件(虚拟机管理程序)来模拟特定的硬件系统。虚拟机管理程序用于创建和运行虚拟机。它位于计算机硬件和虚拟机之间。每个虚拟机都运行其客户操作系统,并拥有其二进制文件、应用程序等。虚拟映像通常体积巨大,占用大量内存。
另一方面,容器直接在主机操作系统上运行。它们共享操作系统和内核,并且硬件未被虚拟化。
与虚拟机相比,容器更轻量级。它们不需要虚拟机管理程序,因此启动速度更快。容器启动时间通常以秒或更短为单位,而虚拟机启动时间则以分钟为单位。
容器、图像以及如何创建它们?
在“运行”或“创建”容器之前,您需要先创建它。就像虚拟机一样,要运行虚拟机,您必须创建虚拟机的 ISO 映像。
Docker 镜像是一个只读模板,其中包含有关如何创建或运行容器的说明。Docker 镜像由如下所示的Dockerfile创建:
FROM ubuntu:18.04
WORKDIR /app
COPY hello.sh /app
RUN chmod +x hello.sh
RUN apt-get update
RUN apt-get install curl -y
CMD ["./hello.sh"]
大多数情况下,你的镜像将基于现有的 Docker 镜像。在上面的 Dockerfile 中,我们以名为 ubuntu:18.04 的现有镜像为基础,该镜像代表 Ubuntu 18.04。
Dockerfile 中的其余指令执行以下操作:
- 设置工作目录(请注意,这是图像内的工作目录,而不是主机上的目录)
hello.sh
将文件从主机复制到/app
图像内的文件夹- 运行几个命令
- 使用 CMD 设置容器的默认命令 - 即这是我们在运行容器时想要执行的命令
使用类似上述的 Dockerfile,我们可以创建一个 Docker 镜像。Docker 镜像是多个层的集合(Dockerfile 中的一个命令等于镜像中的一个层)。这些层层堆叠,除了最顶层的可写层外,其余都是只读的。
您可以将 Docker 镜像视为模板,将容器视为这些模板的实例。运行容器时,您正在创建一个具有可写层的 Docker 镜像实例。对正在运行的容器所做的任何更改都会在可写层上进行。例如,如果您在容器内运行一个写入文件的应用程序,则该文件存储在可写层上。这里需要记住的一点是,您在容器运行时对可写层所做的任何修改都会在容器停止后丢失。
卷等功能可用于将数据存储在正在运行的容器之外。运行容器时,您可以指定并挂载这些卷,以便在容器内部使用。即使容器停止,数据仍会保留,因为它不属于容器的一部分。
图像命名
所有 Docker 镜像都通过其名称引用。镜像名称由三部分组成:仓库名称、镜像名称和镜像标签。镜像标签(或版本)可以省略,但是,任何没有标签的镜像都会获得名为 的默认标签。始终使用镜像标签,并且切勿依赖或使用标签latest
始终是一个好习惯。latest
您可以将 Docker 镜像存储在计算机上。如果您想共享这些镜像并供其他人使用,则必须将镜像上传(或推送)到Docker 镜像仓库。
在本文的后面,我们将使用一个名为 的镜像alpine:3.10.3
。请注意,该镜像名称仅由两部分组成。这是因为它是 Docker Hub 上的官方镜像。Docker Hub 上有很多官方镜像都以这个名称命名,但不包含仓库。您可以在此处阅读更多关于官方镜像的信息。
Docker 注册表
Docker 镜像仓库是您可以上传和存储 Docker 镜像的地方。每个云提供商也提供 Docker 或镜像仓库作为托管服务。您也可以运行和托管自己的镜像仓库,或者使用Docker Hub等免费镜像仓库。镜像仓库可以是公共的,也可以是私有的。公共镜像仓库允许任何人拉取或下载镜像,而私有镜像仓库则需要身份验证。
常见场景
了解了基本概念之后,让我们来看看使用 Docker 时会遇到的几个常见场景。
构建并推动
Docker 构建是指获取Dockerfile、构建上下文(包含代码或可能包含在镜像中的文件的文件夹)和镜像名称,并使用 Docker CLI构建镜像。此操作的结果是一个 Docker 镜像。
构建好镜像(或现有镜像)后,即可将其推送到 Docker 镜像仓库。推送其实就是上传镜像。
使用 Docker CLI,构建镜像的命令如下:
docker build -t myrepository/imagename:0.1.0 .
标志-t
用于提供镜像名称,.
末尾的 表示如何提供构建上下文。点号表示我们希望当前文件夹作为构建上下文。但是,您的构建上下文可以是其他文件夹。
构建上下文的意义是什么?
我们从上面的 Dockerfile 中取出以下指令:
COPY hello.sh /app
这条指令告诉 Docker CLI 将hello.sh
文件从主机复制到镜像内的文件夹。但是 Docker CLI 如何知道该文件在哪里呢?这时,构建上下文就派上用场了,它告诉 Docker在构建上下文中/app
查找该文件。hello.sh
要将图像推送到注册表,您需要确保首先登录到注册表,然后才能运行docker push myrepository/imagename:0.1.0
拉动并运行
当镜像位于镜像仓库中时,您可以使用pull命令从镜像仓库下载镜像。您提供要拉取的镜像的名称,Docker CLI 会访问镜像仓库并将镜像下载到您的本地计算机。
使用 Docker CLI 拉取镜像的命令与 push 命令类似:
docker pull myrepository/imagename:0.1.0
最后,您可以运行 Docker 镜像或从中创建容器。请注意,您无需构建镜像、推送镜像、然后拉取镜像即可运行。如果您不想将镜像推送到镜像仓库,也可以不执行此操作。您仍然可以使用 run 命令来运行镜像。
第二点:如果你想运行镜像,你也不需要先拉取它。如果你尝试运行计算机上不存在的镜像(即你还没有拉取它),Docker CLI 会很智能地先拉取镜像,然后再运行它。
Docker run 命令有很多选项可以传入,以控制容器的执行方式,如果它向主机公开任何端口,挂载任何卷,您还可以传入环境变量等等。
Docker 运行命令示例可能如下所示:
docker run --name mycontainername -p 5000:8080 myrepository/imagename:0.1.0
请注意,在上面的命令中,我们为容器命名 (mycontainername),但这不是必需的。如果您不提供名称,Docker 会为您随机取一个巧妙的名称:interesting_tharp
、strange_hypatia
、practical_morse
等等。Docker 使用一系列科学家和著名黑客来为容器命名。您可以在此处查看他们使用的列表。请注意,您的容器永远不会被命名boring_wozniak
为 Steve Wozniak,这并不无聊 :)
Docker 实践
了解了基本术语和概念后,让我们开始动手尝试 Docker 吧!在本节中,你将安装 Docker CLI,创建Docker Hub帐户(如果你还没有),并尝试不同的 Docker CLI 命令。
安装 Docker Desktop
适用于 Mac 和 Windows 的 Docker Desktop 是一套简化 Docker 使用的工具。它包含 Docker 引擎、CLI 客户端以及一系列其他工具。
- 如果你使用的是 Mac,请从此处下载 Docker Desktop for Mac
- 如果你使用的是 Windows,请从此处下载适用于 Windows 的 Docker Desktop
- 如果你使用的是 Linux(Ubuntu),请按照此处的说明操作
创建 Docker Hub 帐户
您需要一个 Docker Hub 帐户才能将镜像推送到公共 Docker 注册表。您可以在 https://hub.docker.com 上注册并创建您的 Docker Hub 帐户。
登录后,您可以创建不同的仓库- 仓库名称是 Docker 镜像名称前面的值。例如,我的一个仓库名为learncloudnative。名为hello-web且标签为 0.1.0的镜像的完整名称为learncloudnative/hello-web:0.1.0。
完成 Docker 安装后,打开终端并运行以下docker version
命令:
$ docker version
Client: Docker Engine - Community
Version: 19.03.8
API version: 1.40
Go version: go1.12.17
Git commit: afacb8b
Built: Wed Mar 11 01:21:11 2020
OS/Arch: darwin/amd64
Experimental: true
Server: Docker Engine - Community
Engine:
Version: 19.03.8
API version: 1.40 (minimum version 1.12)
Go version: go1.12.17
Git commit: afacb8b
Built: Wed Mar 11 01:29:16 2020
OS/Arch: linux/amd64
Experimental: true
containerd:
Version: v1.2.13
GitCommit: 7ad184331fa3e55e52b890ea95e65ba581ae3429
runc:
Version: 1.0.0-rc10
GitCommit: dc9208a3303feef5b3839f4323d9beb36df0a9dd
docker-init:
Version: 0.18.0
GitCommit: fec3683
该命令的输出应该与上面的输出类似。
Dockerfile 和其他文件
在您的计算机上的某个位置创建一个空文件夹,并创建一个名为的文件,Dockerfile
其内容如下:
FROM alpine:3.10.3
WORKDIR /app
COPY hello.sh /app
RUN chmod +x hello.sh
RUN apk update
RUN apk add curl
CMD ["./hello.sh"]
这个 Dockerfile 将一个名为的文件复制hello.sh
到镜像中,使其可执行,然后运行 apk update 和 apk add curl,最后将 hello.sh 设置为容器运行时执行的命令。
什么是apk?apk是Alpine上用来管理软件的工具。
hello.sh
在与 Dockerfile 相同的文件夹中创建该文件:
#!/bin/sh
echo "Hello Docker!"
通过此设置,我们可以开始研究 Docker 层。
Docker 层
在本节中,您将构建您的第一个 Docker 镜像,并使用 Docker CLI 检查各个层。
我们将从构建名为 的 Docker 镜像开始docker-layers
。您可以从 Dockerfile (和 hello.sh 文件) 所在的同一文件夹运行以下命令:
docker build -t docker-layers:0.1.0 .
在命令输出中,您将看到构建上下文如何发送到 Docker 守护进程,镜像如何被拉取,然后 Dockerfile 中的每条指令如何被执行。输出的最后一行应该显示您的镜像 (docker-layers:0.1.0) 已构建并已标记。
我之前没提过镜像标记——你可以把它理解为重命名镜像,或者创建一个引用旧镜像的新镜像。例如,你可以运行命令将镜像myimagename:0.1.0标记为somethingelse:2.0.0docker tag
。该命令会创建另一个引用原镜像的新镜像。需要注意的是,Docker 不会创建该镜像的另一个副本。
您可以运行docker images来获取机器上的图像列表。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
docker-layers 0.1.0 6a5b8912d27f 5 minutes ago 8.31MB
alpine 3.10.3 965ea09ff2eb 6 months ago 5.55MB
如果你之前没有安装或使用过 Docker,你的机器上会有两个镜像:你刚刚构建的docker-layers镜像和Dockerfile 中用作基础镜像的alpine镜像。
我们还尝试运行容器以获取“Hello Docker!”输出:
$ docker run docker-layers:0.1.0
Hello Docker!
Docker 在创建镜像时会利用层以及层之间的差异。这可以加快后续构建速度。如果您再次构建镜像,您会注意到命令完成所需的时间比第一次要少得多。(是的,除去第一次尝试下载镜像的时间,后续构建速度仍然更快。)
在我的计算机上,第二次 Docker 构建花费了不到一秒钟:
$ time docker build -t docker-layers:0.1.0 .
real 0m0.501s
user 0m0.088s
sys 0m0.061s
让我们看看为图像创建的图层:
$ docker history docker-layers:0.1.0
IMAGE CREATED CREATED BY SIZE COMMENT
6a5b8912d27f 4 minutes ago /bin/sh -c #(nop) CMD ["./hello.sh"] 0B
3030475d0a23 4 minutes ago /bin/sh -c apk add curl 1.34MB
81cd9a8738f0 4 minutes ago /bin/sh -c apk update 1.42MB
e68bc418551f 4 minutes ago /bin/sh -c chmod +x hello.sh 30B
14d207dc283c 4 minutes ago /bin/sh -c #(nop) COPY file:c2c91b54b63f7c0e… 30B
4628741a4e97 4 minutes ago /bin/sh -c #(nop) WORKDIR /app 0B
965ea09ff2eb 6 months ago /bin/sh -c #(nop) CMD ["/bin/sh"] 0B
<missing> 6 months ago /bin/sh -c #(nop) ADD file:fe1f09249227e2da2… 5.55MB
请注意, apk add curl和apk update命令分别创建了两个独立的层。我们可以将这两个命令合并到 Dockerfile 中的一个命令中,以提高效率。
打开 Dockerfile 并将两行合并如下:
FROM alpine:3.10.3
WORKDIR /app
COPY hello.sh /app
RUN chmod +x hello.sh
RUN apk update && apk add curl
CMD ["./hello.sh"]
让我们再次重建图像并检查图层:
$ docker build -t docker-layers:0.1.0 .
...
$ docker history docker-layers:0.1.0
IMAGE CREATED CREATED BY SIZE COMMENT
8470cb0b2e17 19 seconds ago /bin/sh -c #(nop) CMD ["./hello.sh"] 0B
ed2a17906a01 19 seconds ago /bin/sh -c apk update && apk add curl 2.76MB
...
这次我们只用了一个 apk 命令的图层。接下来,我们将向镜像中添加另一个文件,看看它如何影响图层。
创建一个名为 hello.txt 的文件,其中包含一条简单消息:
echo "Hello!" >> hello.txt
接下来,让我们更新 Dockerfile 以将此文件包含在镜像中:
FROM alpine:3.10.3
WORKDIR /app
COPY hello.sh /app
RUN chmod +x hello.sh
COPY hello.txt /app
RUN apk update && apk add curl
CMD ["./hello.sh"]
我在运行apk命令之前复制了新文件。如果你再次重建镜像 ( ),你会注意到apk命令被重复执行了,并没有被重复使用。这是因为层的堆叠方式。新的层与之前的层有所不同,而且由于我们在运行apk之前添加了 COPY 命令,之前的缓存也就失效了。docker build -t docker-layers
好消息是,我们可以改进这一点。如果我们将apk命令移动到包含 FROM 指令的行之后,镜像中的第二层就会是apk层。让我们在实践中看看。
将命令移动RUN apk update && apk add curl
到 Dockerfile 中第一行的正下方:
FROM alpine:3.10.3
RUN apk update && apk add curl
WORKDIR /app
COPY hello.sh /app
RUN chmod +x hello.sh
COPY hello.txt /app
CMD ["./hello.sh"]
我们需要再次重建镜像来创建层。如果你检查层,你会注意到apk命令现在更接近堆栈底部了。让我们来证明一下,如果我们添加另一个文件并重建镜像,该命令将不会再次执行。
创建bye.txt
文件:
echo "Bye!" >> bye.txt
在Dockerfile的底部添加COPY命令:
FROM alpine:3.10.3
RUN apk update && apk add curl
WORKDIR /app
COPY hello.sh /app
RUN chmod +x hello.sh
COPY hello.txt /app
COPY bye.txt /app
CMD ["./hello.sh"]
如果你这次重新构建镜像,你会发现速度明显加快了。这是因为 Docker 重新使用了镜像层,而不再重新运行 apk 命令。
了解层的工作原理非常重要,因为您可以显著提高 Docker 构建的速度。
推送和标记 Docker 镜像
您需要登录到镜像仓库才能推送 Docker 镜像,否则推送命令将失败。您可以通过 Docker Desktop 登录镜像仓库,也可以在终端中使用docker login命令。
登录后,您几乎可以开始推送镜像了。在上一节中,我们将镜像命名为docker-layers:0.1.0 -未使用仓库名称。要推送到镜像仓库,需要仓库名称。仓库名称是您注册 Docker Hub 时使用的名称。
我们可以再次重建镜像,并为镜像仓库提供一个全名,但是使用tag命令并为镜像提供一个新名称会更快。将[your-repository]替换为您的仓库名称:
docker tag docker-layers:0.1.0 [your-repository]/docker-layers:0.1.0
如果命令执行成功,您将不会看到任何输出。请注意,我们也可以使用 tag 命令来更新镜像名称的其他部分。
现在您可以继续通过运行以下命令将图像推送到注册表:
docker push [your-repository]/docker-layers:0.1.0
Docker 将镜像推送到注册表,任何人都可以拉取或下载它!
拉取 Docker 镜像并运行容器
可以使用pull命令从 registry 拉取或下载镜像。我们以alpine镜像为例:
$ docker pull alpine
Using default tag: latest
latest: Pulling from library/alpine
cbdbe7a5bc2a: Pull complete
Digest: sha256:9a839e63dad54c3a6d1834e29692c8492d93f90c59c978c1ed79109ea4fb9a54
Status: Downloaded newer image for alpine:latest
docker.io/library/alpine:latest
由于我们没有为图像提供特定版本,因此 Docker 使用:latest标签拉取图像。
您也可以直接使用运行命令,Docker 将为我们拉取镜像:
docker run alpine
使用上述命令运行镜像将启动容器,但它会立即退出。我们可以做的是在容器内部获取一个 shell 提示符,以保持容器处于活动状态。我们需要提供-i和-t标志以使容器具有交互性,并分配一个伪终端 (TTY)。最后,我们还需要提供一个在容器启动时运行的命令。由于我们需要一个 shell,因此可以运行 shell - /bin/sh。让我们尝试在 Alpine 容器内部运行该命令以获取 shell:
docker run -it alpine /bin/sh
您会注意到提示已经改变,如果您运行 env 命令,您将看到环境变量来自容器,而不是您的主机。
$ docker run -it alpine /bin/sh
/ # env
HOSTNAME=32a19aa0c35f
SHLVL=1
HOME=/root
TERM=xterm
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
PWD=/
/ #
要退出容器,您可以输入exit。每个正在运行的容器都有一个 ID - 您可以打开单独的终端窗口并运行docker ps
命令来检查该 ID:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
32a19aa0c35f alpine "/bin/sh" 2 minutes ago Up 2 minutes sharp_sanderson
接下来,你可以通过这个ID杀死容器:docker kill 32a19aa0c35f
。随着容器的死亡,你会看到另一个终端窗口中的提示消失。
映射端口
另一个相当常见的任务是在本地运行容器,并将容器端口映射到本地机器的端口。例如,你可以在容器内运行一个服务,但为了访问该服务,你需要将容器端口映射到主机端口,以便访问它。
让我们使用图像中提供的简单 Node.js 应用程序learncloudnative/helloworld:0.1.0
。
如果您只是运行映像,则将无法访问其中运行的应用程序,因为应用程序在容器内监听的端口未映射到您的机器。
要将容器端口映射到主机端口,可以-p
在运行容器时使用标志,如下所示:
docker run -p 8080:3000 learncloudnative/helloworld:0.1.0
镜像下载完成后,您将看到“正在监听 3000 端口”的消息。这是容器内正在监听 3000 端口的应用程序发出的消息。我们使用 -p 参数将容器的 3000 端口映射到主机的 8080 端口。
打开浏览器并访问http://localhost:8080,您将看到“Hello World!”网页。您还会注意到容器在终端输出中写入的任何日志。要停止运行容器,可以按 CTRL+C。有时,端口映射也被称为“暴露”端口或“发布”端口。Dockerfile 中可以包含一个名为 的指令EXPOSE
。“暴露端口”和“EXPOSE”指令的组合可能会造成混淆。该EXPOSE
指令的目的是记录容器内运行的应用程序正在监听哪个端口。例如,镜像的 Dockerfilehelloworld
应该包含一条EXPOSE 3000
指令,因为这是应用程序监听的端口。
因此,即使EXPOSE
Dockerfile 中包含该指令,您仍然需要使用该-p
标志。您可以使用其他标志(-P
),在这种情况下,Docker 会将指令中的端口映射EXPOSE
到主机上的随机端口。
在这种情况下,运行命令如下所示:
docker run -P learncloudnative/helloworld:0.1.0
请注意,这里没有实际的端口号。容器的启动方式与之前类似,但现在如果列出所有正在运行的容器,您会注意到主机上的一个随机端口 ( 32768
) 被映射到了容器端口 ( 3000
):
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
94cfd042df18 learncloudnative/helloworld:0.1.0 "npm start" 9 seconds ago Up 8 seconds 0.0.0.0:32768->3000/tcp hardcore_northcutt
结论
在本文中,我尝试解释 Docker 是什么、它与虚拟机有何不同,以及在工作或阅读 Docker 相关内容时会遇到的一些常见概念和术语。希望最后的教程能够帮助您在实践中理解这些概念。
Docker 还有许多其他功能,本文只是略微介绍,但足以帮助您入门。在接下来的文章中,我将解释如何使用 Docker Volumes 和 Docker Compose。
如果你有兴趣深入了解并查看更多实际示例,可以查看我撰写的网关指南。该指南讲解了你需要了解的有关网关和代理的基础知识,并在实践部分展示了如何使用 Docker Compose 运行代理。
你喜欢这本指南吗?我很乐意听到你的反馈——请在下方留言,或者通过 Twitter或电子邮件联系我们!
文章来源:https://dev.to/peterj/beginners-guide-to-docker-4pkk