Docker Docs 3 立即开始
转自:Docker 官网
链接:https://docs.docker.com/get-started/overview/
欢迎!我们很高兴您想学习 Docker。
本页包含有关如何开始使用 Docker 的分步说明。在本教程中,您将学习如何:
- 将映像作为容器生成和运行
- 使用 Docker Hub 共享映像
- 使用多个容器和一个数据库部署 Docker 应用程序
- 使用 Docker Compose 运行应用程序
此外,您还将了解构建映像的最佳做法,包括有关如何扫描映像以查找安全漏洞的说明。
建议在 Linux 系统上安装 Docker。
如果您已经运行了该命令以开始使用本教程,那么恭喜您!如果没有,请打开命令提示符或 bash 窗口,然后运行以下命令:
docker run -d -p 80:80 docker/getting-started
您会注意到正在使用一些标志。以下是有关它们的更多信息:
-d
- 在分离模式下运行容器(在后台)-p 80:80
- 将主机的端口 80 映射到容器中的端口 80docker/getting-started
- 要使用的图像
提示
您可以组合单个字符标志以缩短完整命令。例如,上面的命令可以写成:
docker run -dp 80:80 docker/getting-started
在走得太远之前,我们想突出显示 Docker 仪表板,它让你可以快速查看计算机上运行的容器。Docker Dashboard 适用于 Mac 和 Windows。它使您可以快速访问容器日志,让您在容器内获得一个shell,并允许您轻松管理容器生命周期(停止,删除等)。
若要访问仪表板,请按照 Docker 桌面手册 中的说明进行操作。如果现在打开仪表板,您将看到本教程正在运行!容器名称(见下文)是随机创建的名称。因此,您很可能会有不同的名称。jolly_bouman
现在您已经运行了一个容器,什么是容器?简而言之,容器是计算机上的沙盒进程,与主机上的所有其他进程隔离。这种隔离利用了内核命名空间和 cgroups,这些功能已经在 Linux 中存在了很长时间。Docker 致力于使这些功能平易近人且易于使用。总而言之,容器:
- 是映像的可运行实例。您可以使用 DockerAPI 或 CLI 创建、启动、停止、移动或删除容器。
- 可以在本地机器、虚拟机上运行或部署到云中。
- 可移植(可以在任何操作系统上运行)
- 容器彼此隔离,并运行自己的软件、二进制文件和配置。
运行容器时,它使用隔离的文件系统。此自定义文件系统由容器映像提供。由于映像包含容器的文件系统,因此它必须包含运行应用程序所需的一切 - 所有依赖项、配置、脚本、二进制文件等。该映像还包含容器的其他配置,例如环境变量、要运行的默认命令和其他元数据。
稍后我们将深入探讨图像,涵盖分层、最佳实践等主题。
在本教程的其余部分,我们将使用一个在 Node.js 中运行的简单待办事项列表管理器。如果您不熟悉 Node.js,请不要担心。不需要真正的 JavaScript 经验。
在这一点上,你的开发团队非常小,你只是在构建一个应用程序来证明你的MVP(最小可行产品)。你想展示它是如何工作的,以及它能够做什么,而不需要考虑它将如何为一个大型团队、多个开发人员等工作。
在运行应用程序之前,我们需要将应用程序源代码获取到我们的计算机上。对于实际项目,通常会克隆存储库。但是,对于本教程,我们创建了一个包含该应用程序的 ZIP 文件。
- 下载应用程序内容。您可以拉取整个项目,也可以将其下载为zip并将其解压缩出应用程序文件夹以开始使用。
- 提取后,使用您喜欢的代码编辑器打开项目。如果你需要一个编辑器,你可以使用Visual Studio Code。您应该看到 和 两个子目录 ( 和 )。
package.json src spec
为了构建应用程序,我们需要使用 .Dockerfile 只是一个基于文本的指令脚本,用于创建容器映像。如果您之前创建过 Dockerfile,您可能会在下面的 Dockerfile 中看到一些缺陷。但是,别担心。我们将介绍它们。Dockerfile
- 创建一个与该文件同名的文件,该文件与包含以下内容的文件位于同一文件夹中。
# syntax=docker/dockerfile:1
FROM node:12-alpine
RUN apk add --no-cache python2 g++ make
WORKDIR /app
COPY . .
RUN yarn install --production
CMD ["node", "src/index.js"]
EXPOSE 3000
请检查文件没有像.某些编辑器可能会自动附加此文件扩展名,这将导致下一步中的错误。
Dockerfile.txt
2. 如果尚未执行此操作,请打开终端并转到包含 的目录。现在,使用命令生成容器映像。
docker build -t getting-started .
此命令使用 Dockerfile 生成新的容器映像。您可能已经注意到下载了很多“图层”。这是因为我们指示构建者要从映像开始。但是,由于我们的计算机上没有该图像,因此需要下载该图像。`node:12-alpine`
下载映像后,我们在应用程序中复制并用于安装应用程序的依赖项。该指令指定从此映像启动容器时要运行的默认命令。yarn CMD
最后,标志标记我们的图像。只需将其视为最终图像的人类可读名称。由于我们命名了图像,因此我们可以在运行容器时引用该图像。-t getting-started
该命令的末尾指示 Docker 应在当前目录中查找 。. docker build Dockerfile
现在我们有了一个映像,让我们运行该应用程序。为此,我们将使用该命令(还记得前面的命令吗?
docker run
- 使用以下命令启动容器,并指定我们刚刚创建的映像的名称:
docker run
还记得 和 标志吗?我们在“分离”模式下(在后台)运行新容器,并在主机的端口 3000 到容器的端口 3000 之间创建映射。如果没有端口映射,我们将无法访问该应用程序。docker run -dp 3000:3000 getting-started
-d -p
- 几秒钟后,打开 Web 浏览器以 http://localhost:3000。您应该会看到我们的应用程序。
作为一个小的功能请求,产品团队要求我们在没有任何待办事项列表项时更改“空文本”。他们希望将其更改为以下内容:
您还没有待办事项!在上面添加一个!
很简单,对吧?让我们进行更改。
- 在文件中,更新第 56 行以使用新的空文本。
src/static/js/app.js
- <p className="text-center">No items yet! Add one above!</p>
+ <p className="text-center">You have no todo items yet! Add one above!</p>
- 让我们使用之前使用的相同命令构建映像的更新版本。
docker build -t getting-started .
- 让我们使用更新的代码启动一个新容器。
docker run -dp 3000:3000 getting-started
哎呀!您可能看到过这样的错误(ID 会有所不同):
docker: Error response from daemon: driver failed programming external connectivity on endpoint laughing_burnell
(bb242b2ca4d67eba76e79474fb36bb5125708ebdabd7f45c8eaf16caaabde9dd): Bind for 0.0.0.0:3000 failed: port is already allocated.
那么,发生了什么事呢?我们无法启动新容器,因为我们的旧容器仍在运行。这是因为容器正在使用主机的端口 3000,并且计算机上只有一个进程(包括容器)可以侦听特定端口。要解决此问题,我们需要删除旧容器。
要删除容器,首先需要停止该容器。一旦停止,就可以将其删除。我们有两种方法可以删除旧容器。随意选择您最熟悉的路径。
- 使用命令获取容器的 ID。
docker ps
docker ps
- 使用命令停止容器。
docker stop
Swap out <the-container-id> with the ID from docker ps
docker stop <the-container-id>
- 容器停止后,可以使用该命令将其删除。
docker rm
docker rm <the-container-id>
注意
您可以通过向命令添加“force”标志来停止和删除单个命令中的容器。例如:
docker rmdocker rm -f <the-container-id>
如果打开 Docker 仪表板,只需单击两下即可删除容器!这当然比查找容器 ID 并将其删除要容易得多。
- 打开仪表板后,将鼠标悬停在应用容器上,你将看到右侧显示一组操作按钮。
- 单击垃圾桶图标以删除容器。
- 确认删除,您就完成了!
- 现在,启动更新后的应用。
docker run -dp 3000:3000 getting-started
- 在 http://localhost:3000 上刷新浏览器,您应该会看到更新的帮助文本!
虽然我们能够构建更新,但您可能已经注意到两件事:
- 我们待办事项列表中的所有现有项目都不见了!这不是一个非常好的应用程序!我们很快就会讨论这个问题。
- 对于如此小的更改,涉及很多步骤。在即将推出的部分中,我们将讨论如何查看代码更新,而无需在每次进行更改时重新生成和启动新容器。
在谈论持久性之前,我们将快速了解如何与他人共享这些图像。
现在我们已经构建了一个映像,让我们分享它!若要共享 Docker 映像,必须使用 Docker 注册表。默认注册表是 Docker Hub,是我们使用的所有映像的来源。
Docker ID
Docker ID 允许你访问 Docker Hub,它是世界上最大的容器映像库和社区。如果没有 Docker ID,请免费创建一个 Docker ID。
若要推送映像,我们首先需要在 Docker Hub 上创建一个存储库。
- 注册或登录到 Docker Hub。
- 单击创建存储库按钮。
- 对于存储库名称,请使用 。确保可见性为 。
getting-startedPublic
私有仓库
您是否知道 Docker 提供了私有存储库,允许您将内容限制为特定用户或团队?查看 Docker 定价页上的详细信息。
- 点击创建按钮!
如果您查看下面的图像,可以看到一个示例 Docker 命令。此命令将推送到此存储库。
- 在命令行中,尝试运行你在 Docker Hub 上看到的 push 命令。请注意,您的命令将使用您的命名空间,而不是“docker”。
为什么会失败?push 命令正在寻找一个名为 docker/getting-started 的映像,但没有找到一个。如果运行 ,则也不会看到一个。$ docker push docker/getting-started The push refers to repository [docker.io/docker/getting-started] An image does not exist locally with the tag: docker/getting-started
docker image ls
要解决此问题,我们需要“标记”我们构建的现有映像,以便为其命名。 - 使用命令登录到 Docker 中心。
docker login -u YOUR-USER-NAME
- 使用该命令为映像指定一个新名称。请务必使用您的 Docker ID 进行换出。
docker tag getting-started YOUR-USER-NAME
docker tag getting-started YOUR-USER-NAME/getting-started
- 现在再次尝试推送命令。如果要从 Docker Hub 复制值,则可以删除该部分,因为我们没有向映像名称添加标记。如果未指定标记,Docker 将使用名为 的标记。
tagname latest
docker push YOUR-USER-NAME/getting-started
现在,我们的映像已构建并推送到注册表中,让我们尝试在从未见过此容器映像的全新实例上运行我们的应用!为此,我们将使用 Play with Docker。
- 打开浏览器以使用 Docker 玩游戏。
- 单击“登录”,然后从下拉列表中选择“docker”。
- 使用 Docker Hub 帐户进行连接。
- 登录后,单击左侧栏上的“添加新实例”选项。如果您没有看到它,请将浏览器放宽一点。几秒钟后,浏览器中将打开一个终端窗口。
5. 在终端中,启动新推送的应用。
docker run -dp 3000:3000 YOUR-USER-NAME/getting-started
您应该看到图像被拉下并最终启动!
- 当它出现时,单击3000徽章,您应该会看到带有修改的应用程序!万岁!如果3000徽章未显示,您可以单击“打开端口”按钮并输入3000。
如果您没有注意到,每次启动容器时,我们的待办事项列表都会被擦除干净。这是为什么呢?让我们深入了解容器的工作原理。
当容器运行时,它将映像中的各个层用于其文件系统。每个容器还拥有自己的“暂存空间”来创建/更新/删除文件。任何更改都不会在另一个容器中看到,即使它们使用相同的映像也是如此。
要查看其实际效果,我们将启动两个容器,并在每个容器中创建一个文件。您将看到的是,在一个容器中创建的文件在另一个容器中不可用。
- 启动一个容器,该容器将创建一个以随机数 1 到 10000 命名的文件。
ubuntu /data.txt
如果您对该命令感到好奇,我们将启动一个 bash shell 并调用两个命令(为什么我们有 )。第一部分选取一个随机数并将其写入 。第二个命令是简单地监视文件以保持容器运行。docker run -d ubuntu bash -c "shuf -i 1-10000 -n 1 -o /data.txt && tail -f /dev/null"
&& /data.txt
- 验证我们是否可以通过放入容器来查看输出。为此,请打开仪表板,然后单击运行映像的容器的第一个操作。
exec ubuntu
您将看到一个在 ubuntu 容器中运行 shell 的终端。运行以下命令以查看文件的内容。之后再次关闭此终端。/data.txt
如果您更喜欢命令行,则可以使用该命令执行相同的操作。需要获取容器的 ID(用于获取它)并使用以下命令获取内容。cat /data.txt
您应该看到一个随机数!docker exec <container-id> cat /data.txt
- 现在,让我们启动另一个容器(相同的图像),我们将看到我们没有相同的文件。
瞧!那里没有文件!这是因为它只是被写入第一个容器的暂存空间。docker run -it ubuntu ls /
- 继续使用该命令删除第一个容器。
docker rm -f <container-id>
在前面的实验中,我们看到每个容器每次启动时都从映像定义开始。虽然容器可以创建、更新和删除文件,但当删除容器并且所有更改都与该容器隔离时,这些更改将丢失。通过 volumes,我们可以改变这一切。
volumes 提供了将容器的特定文件系统路径连接回主机的功能。如果装载了容器中的某个目录,则该目录中的更改也会显示在主机上。如果我们在容器重启之间挂载相同的目录,我们将看到相同的文件。
卷主要有两种类型。我们最终将同时使用这两个卷,但我们将从命名卷开始。
默认情况下,待办事项应用将其数据存储在容器文件系统的 SQLite 数据库中。如果您不熟悉 SQLite,不用担心!它只是一个关系数据库,其中所有数据存储在单个文件中。虽然这不是大型应用程序的最佳选择,但它适用于小型演示。稍后我们将讨论如何将其切换到其他数据库引擎。
由于数据库是单个文件,如果我们可以将该文件保留在主机上并使其可用于下一个容器,则它应该能够从最后一个容器中断的位置继续。通过创建一个卷并将其附加(通常称为“挂载”)到数据存储的目录,我们可以持久保存数据。当我们的容器写入文件时,它将持久保存到卷中的主机。
如前所述,我们将使用命名卷。将命名卷视为一个数据桶。Docker 维护磁盘上的物理位置,您只需要记住卷的名称即可。每次使用卷时,Docker 都会确保提供正确的数据。
-
使用该命令创建卷。
docker volume create todo-db
-
在仪表板中再次停止并删除 todo 应用容器(或使用 ),因为它仍在运行,而无需使用持久卷。
docker rm -f
-
启动 todo 应用容器,但添加标志以指定卷装入。我们将使用命名卷并将其装载到 ,这将捕获在路径中创建的所有文件。
docker run -dp 3000:3000 -v todo-db:/etc/todos getting-started
-
容器启动后,打开应用并将一些项目添加到待办事项列表。
-
停止并删除待办事项应用的容器。使用仪表板或获取 ID,然后将其删除。
docker psdocker rm -f <id>
-
使用上面的相同命令启动新容器。
-
打开应用。您应该会看到您的项目仍在列表中!
-
签出列表后,请继续删除容器。
很多人经常问“当我使用命名卷时,Docker 实际上在哪里存储我的数据?如果您想知道,可以使用该命令。
docker volume inspect
docker volume inspect todo-db
是磁盘上存储数据的实际位置。请注意,在大多数计算机上,您需要具有 root 访问权限才能从主机访问此目录。但是,这就是它所在的地方!
在上一章中,我们讨论并使用命名卷来持久保存数据库中的数据。如果我们只想存储数据,命名卷非常有用,因为我们不必担心数据存储的位置。
使用绑定挂载,我们控制主机上的确切挂载点。我们可以使用它来持久保存数据,但它通常用于向容器中提供其他数据。在处理应用程序时,我们可以使用绑定挂载将源代码挂载到容器中,让它看到代码更改,做出响应,并让我们立即看到更改。
对于基于节点的应用程序,nodemon是监视文件更改然后重新启动应用程序的绝佳工具。在大多数其他语言和框架中都有等效的工具。
绑定装载和命名卷是 Docker 引擎附带的两种主要类型的卷。但是,其他卷驱动程序可用于支持其他用例(SFTP、Ceph、NetApp、S3 等)。
比较项 | 命名卷 | 绑定挂载 |
---|---|---|
主机位置 | Docker 选择 | 由您控制 |
装载示例(使用-v) | my-volume:/usr/local/data | /path/to/data:/usr/local/data |
用容器内容填充新卷 | Yes | No |
支持卷驱动程序 | Yes | No |
绑定装载和命名卷是 Docker 引擎附带的两种主要类型的卷。但是,其他卷驱动程序可用于支持其他用例(SFTP、Ceph、NetApp、S3 等)。
若要运行容器以支持开发工作流,我们将执行以下操作:
- 将我们的源代码挂载到容器中
- 安装所有依赖项,包括“开发”依赖项
- 启动 nodemon 以监视文件系统更改
使用绑定挂载对于本地开发设置非常普遍。优点是开发计算机不需要安装所有生成工具和环境。只需一个命令,即可拉取开发环境并准备就绪。我们将在下一步中讨论 Docker Compose,因为这将有助于简化我们的命令(我们已经得到了很多标志)。
到目前为止,我们一直在使用单容器应用。但是,我们现在想将MySQL添加到应用程序堆栈中。以下问题经常出现 - “MySQL将在哪里运行?将其安装在同一容器中还是单独运行?一般来说,每个容器都应该做一件事,并且做得很好。几个原因:
- 很有可能您必须以不同于数据库的方式扩展 API 和前端
- 单独的容器允许您隔离版本和更新版本
- 虽然可以在本地对数据库使用容器,但可能需要在生产环境中对数据库使用托管服务。你不希望随应用一起提供数据库引擎。
- 运行多个进程将需要一个进程管理器(容器仅启动一个进程),这增加了容器启动/关闭的复杂性
还有更多的原因。因此,我们将更新我们的应用程序,使其按如下方式工作:
请记住,默认情况下,容器是独立运行的,对同一台计算机上的其他进程或容器一无所知。那么,我们如何允许一个容器与另一个容器通信呢?答案是网络。现在,您不必成为网络工程师(万岁!只要记住这个规则…
注意
如果两个容器位于同一网络上,它们可以相互通信。如果他们不是,他们就不能。
有两种方法可以将容器放在网络上:1) 在开始时分配它或 2) 连接现有容器。现在,我们将首先创建网络,并在启动时附加MySQL容器。
-
创建网络。
docker network create todo-app
-
启动MySQL容器并将其附加到网络。我们还将定义一些数据库将用于初始化数据库的环境变量(请参阅MySQL Docker Hub列表中的“环境变量”部分)。
docker run -d \ --network todo-app --network-alias mysql \ -v todo-mysql-data:/var/lib/mysql \ -e MYSQL_ROOT_PASSWORD=secret \ -e MYSQL_DATABASE=todos \ mysql:5.7
您还将看到我们指定了该标志。我们一会儿再回到这个问题上来。
--network-alias
提示
您会注意到,我们正在使用此处命名的卷并将其挂载在 ,这是MySQL存储其数据的位置。但是,我们从未运行过命令。Docker 识别出我们想要使用命名卷,并自动为我们创建一个。
todo-mysql-data /var/lib/mysql docker volume create
-
要确认数据库已启动并运行,请连接到数据库并验证其是否连接。
docker exec -it <mysql-container-id> mysql -u root -p
出现密码提示时,请键入 secret。在 MySQL 外壳中,列出数据库并验证您是否看到了该数据库。
SHOW DATABASES;
您应看到如下所示的输出:
+--------------------+ | Database | +--------------------+ | information_schema | | mysql | | performance_schema | | sys | | todos | +--------------------+ 5 rows in set (0.00 sec)
退出 MySQL shell 以返回到我们计算机上的 shell。
exit
万岁!我们有我们的数据库,它已经准备好供我们使用!
现在我们知道MySQL已经启动并运行,让我们使用它!但是,问题是…如何?如果我们在同一网络上运行另一个容器,我们如何找到容器(请记住每个容器都有自己的IP地址)?
为了解决这个问题,我们将使用nicolaka/netshoot容器,它附带了许多对故障排除或调试网络问题有用的工具。
-
使用 nicolaka/netshoot 映像启动新容器。确保将其连接到同一网络。
docker run -it --network todo-app nicolaka/netshoot
-
在容器中,我们将使用该命令,这是一个有用的 DNS 工具。我们将查找主机名的IP地址。
dig mysql
你会得到这样的输出…
; <<>> DiG 9.14.1 <<>> mysql ;; global options: +cmd ;; Got answer: ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 32162 ;; flags: qr rd ra; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0 ;; QUESTION SECTION: ;mysql. IN A ;; ANSWER SECTION: mysql. 600 IN A 172.23.0.2 ;; Query time: 0 msec ;; SERVER: 127.0.0.11#53(127.0.0.11) ;; WHEN: Tue Oct 01 23:47:24 UTC 2019 ;; MSG SIZE rcvd: 44
在“答案部分”中,您将看到解析为的记录(您的IP地址很可能具有不同的值)。虽然通常不是有效的主机名,但 Docker 能够将其解析为具有该网络别名的容器的 IP 地址(还记得我们之前使用的标志吗?)。
A mysql 172.23.0.2 mysql --network-alias
这意味着…我们的应用程序只需要连接到一个名为的主机,它就会与数据库通信!没有比这更简单的了
待办事项应用程序支持设置一些环境变量来指定MySQL连接设置。它们是:
- MYSQL_HOST- 正在运行的 MySQL 服务器的主机名
- MYSQL_USER- 用于连接的用户名
- MYSQL_PASSWORD- 用于连接的密码
- MYSQL_DB- 连接后要使用的数据库
通过 Env Vars 设置连接设置 虽然使用 env vars 设置连接设置通常适用于开发,但在生产环境中运行应用程序时,强烈建议不要这样做。Docker的前安全主管Diogo Monica写了一篇精彩的博客文章,解释了原因。
更安全的机制是使用容器业务流程框架提供的机密支持。在大多数情况下,这些机密作为文件装载到正在运行的容器中。你会看到许多应用程序(包括MySQL图像和todo应用程序)也支持带有后缀的env vars,以指向包含变量的文件。
例如,设置 var 将导致应用使用引用文件的内容作为连接密码。Docker 不做任何事情来支持这些 env vars。你的应用需要知道查找变量并获取文件内容。
解释完所有这些,让我们开始我们的开发就绪容器!
-
注意:对于MySQL版本8.0及更高版本,请确保在 中包含以下命令
ALTER USER 'root' IDENTIFIED WITH mysql_native_password BY 'secret'; flush privileges;
-
我们将指定上述每个环境变量,并将容器连接到我们的应用网络。
docker run -dp 3000:3000 \ -w /app -v "$(pwd):/app" \ --network todo-app \ -e MYSQL_HOST=mysql \ -e MYSQL_USER=root \ -e MYSQL_PASSWORD=secret \ -e MYSQL_DB=todos \ node:12-alpine \ sh -c "yarn install && yarn run dev"
如果您使用的是Windows,请在PowerShell中使用此命令。
PS> docker run -dp 3000:3000 ` -w /app -v "$(pwd):/app" ` --network todo-app ` -e MYSQL_HOST=mysql ` -e MYSQL_USER=root ` -e MYSQL_PASSWORD=secret ` -e MYSQL_DB=todos ` node:12-alpine ` sh -c "yarn install && yarn run dev"
-
如果我们查看容器的日志(),我们应该看到一条消息,指示它正在使用mysql数据库。docker logs
nodemon src/index.js
-
在浏览器中打开应用程序,并将一些项目添加到待办事项列表中。
-
连接到 mysql 数据库并证明项目正在写入数据库。请记住,密码是秘密的。
docker exec -it <mysql-container-id> mysql -p todos
在 mysql shell 中,运行以下命令:
select * from todo_items;
显然,您的桌子看起来会有所不同,因为它有您的物品。但是,您应该看到它们存储在那里!
如果你快速浏览一下 Docker 仪表板,就会发现我们有两个应用容器正在运行。但是,没有真正的迹象表明它们在单个应用程序中组合在一起。我们很快就会看到如何让它变得更好!
Docker Compose 是一个工具,旨在帮助定义和共享多容器应用程序。使用Compose,我们可以创建一个YAML文件来定义服务,并且使用单个命令,可以启动所有内容或将其全部拆除。
使用 Compose 的最大优点是,您可以在文件中定义应用程序堆栈,将其保留在项目存储库的根目录下(现在受版本控制),并轻松使其他人能够为您的项目做出贡献。有人只需要克隆你的存储库并启动撰写应用。事实上,你可能会看到GitHub/GitLab上有相当多的项目正在这样做。
那么,我们该如何开始呢?