Docker-训练营-全-

Docker 训练营(全)

原文:annas-archive.org/md5/1b654a7bd45d12876fc0cf190c33eed6

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

很少有技术能够像 Docker 一样,在整个行业中被广泛采用。自 2013 年 3 月首次公开发布以来,Docker 不仅赢得了像你我这样的终端用户的支持,还得到了 Amazon、Microsoft 和 Google 等行业领导者的支持。

Docker 当前在其官网上使用以下句子来描述为什么你会想使用它:

Docker 提供了一个集成的技术套件,使开发和 IT 运维团队能够在任何地方构建、分发和运行分布式应用程序。

尽管 Docker 的描述听起来很简单,但多年来,开发和 IT 运维团队的终极目标就是拥有一个工具,能够确保应用程序在整个生命周期的各个阶段(从开发到生产)始终如一地工作。

你将学习如何在你选择的操作系统上安装 Docker。你会发现,一旦 Docker 安装完成,无论你使用的是哪个操作系统,运行容器时都会得到相同的结果。

然后,我们将扩展我们的 Docker 安装到公共云中,你将了解到无论你将 Docker 主机部署到哪里,体验始终是一致且简单的。

到最后一章时,你应该对如何将 Docker 集成到你的日常工作流程中以及接下来容器的使用步骤有一个概念。

本书内容概览

第一章,本地安装 Docker,讲解了如何在 macOS、Windows 10 和 Linux 桌面上安装核心 Docker 引擎及其支持工具,以便为接下来的章节做好准备。

第二章,使用 Docker 启动应用程序,使用我们在上一章中安装的 Docker 来启动容器。在这一章结束时,我们将手动启动一个 WordPress 安装,并使用 Docker Compose 定义你的多容器应用程序。我们还将看看如何将你自己的镜像发布到 Docker Hub。

第三章,云中的 Docker,解释了如何从本地安装的 Docker 迁移到公共云。在这里,我们将研究如何在各种公共云上启动 Docker 主机,并将我们的应用程序部署到这些主机上。

第四章,Docker Swarm,继续使用公共云;但我们将不再仅仅操作单个孤立的 Docker 主机,而是部署并配置一个 Docker Swarm 集群。

第五章,Docker 插件,讨论了描述 Docker 时使用的短语,其中包括 内置电池,但可拆卸。在这一章中,我们将研究第三方插件,这些插件通过添加持久存储和多主机网络来扩展 Docker 的核心功能。

第六章, 故障排除与监控,现在我们在本地、远程以及集群中都已经运行了容器,那么可能出现什么问题呢?在这一章中,我们将探讨你可能遇到的一些问题。同时,我们将学习如何使用第一方和第三方工具来部署工具,以获取如 CPU、内存和硬盘利用率等来自容器的度量信息。

第七章, 综合应用,强调你现在应该已经对 Docker 有了较好的理解,了解它是如何工作的,以及一些可能的使用场景。在这一章中,我们将探讨如何与同事共享容器体验以及接下来应该采取的步骤。

本书所需的条件

你需要在以下平台上安装并配置 Docker17.03(CE):

  • Windows 10 专业版

  • macOS Sierra

  • Ubuntu 16.04 LTS 桌面版

此外,你还应能访问如 Digital Ocean、Amazon Web Service 或 Microsoft Azure 等公共云平台。

本书适用对象

本书面向开发人员、IT 专业人士和 DevOps 工程师,帮助他们在不花费大量时间学习的情况下,通过实践掌握 Docker 的知识和技能。如果你一直在为抽出时间掌握 Docker 容器和日常 Docker 任务而感到困扰,那么你来对地方了!

约定

在本书中,你会看到许多文本样式,它们用于区分不同类型的信息。以下是这些样式的一些示例及其含义说明。

文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 账号都按如下方式显示:“我们可以通过使用include指令来包含其他内容。”

一段代码的格式如下:

docker container run -d \
    --name mysql \
    -e MYSQL_ROOT_PASSWORD=wordpress \
    -e MYSQL_DATABASE=wordpress \
    mysql

当我们希望引起你对代码块中特定部分的注意时,相关的行或项目会以粗体显示:

# Install the packages we need to run wp-cli
RUN apt-get update &&\
apt-get install -y sudo less mysql-client &&\

任何命令行输入或输出都按如下方式书写:

curl -L "https://github.com/docker/compose/releases/download/1.10.0/docker-compose
-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /tmp/docker-compose
sudo cp /tmp/docker-compose /usr/local/bin/docker-compose

新术语重要词汇 会以粗体显示。在屏幕上看到的词汇,例如菜单或对话框中的内容,会按如下方式显示:“点击下一步按钮将带你进入下一个屏幕。”

注意

警告或重要提示会以框框形式显示,如下所示。

小贴士

小贴士和技巧会以如下形式出现。

读者反馈

我们始终欢迎读者的反馈。告诉我们你对这本书的看法——你喜欢或不喜欢什么。读者反馈对我们非常重要,它帮助我们开发出真正能让你受益的书籍。

如果要向我们提供一般反馈,请发送电子邮件至 <feedback@packtpub.com>,并在邮件主题中提及书名。

如果你在某个主题方面有专业知识,并且有兴趣撰写或参与撰写书籍,请查看我们的作者指南:www.packtpub.com/authors

客户支持

现在你已经成为一本 Packt 书籍的骄傲拥有者,我们提供了许多帮助你充分利用购买的资源。

下载示例代码

你可以从你的账户中下载所有你购买的 Packt Publishing 书籍的示例代码文件,访问www.packtpub.com。如果你在其他地方购买了本书,可以访问www.packtpub.com/support并注册以便直接将文件通过电子邮件发送给你。

你可以按照以下步骤下载代码文件:

  1. 使用你的电子邮件地址和密码登录或注册我们的官方网站。

  2. 将鼠标指针悬停在页面顶部的支持标签上。

  3. 点击代码下载与勘误表

  4. 搜索框中输入书籍名称。

  5. 选择你想下载代码文件的书籍。

  6. 从下拉菜单中选择你购买本书的地方。

  7. 点击代码下载

一旦文件下载完成,请确保使用以下最新版本的工具解压或提取文件夹:

  • WinRAR / 7-Zip for Windows

  • Zipeg / iZip / UnRarX for Mac

  • 7-Zip / PeaZip for Linux

本书的代码包也托管在 GitHub 上,地址是github.com/PacktPublishing/Docker-Bootcamp。我们还有其他来自丰富书籍和视频目录的代码包,均可在github.com/PacktPublishing/找到。快来看看吧!

勘误

虽然我们已尽最大努力确保内容的准确性,但错误仍然可能发生。如果你在我们的书中发现错误——可能是文本或代码中的错误——我们将非常感激你向我们报告。这样,你可以帮助其他读者避免困扰,并帮助我们改进本书的后续版本。如果你发现任何勘误,请通过访问www.packtpub.com/submit-errata来报告,选择你的书籍,点击勘误提交表单链接,并输入勘误的详细信息。勘误经验证后,我们会接受你的提交,并将勘误上传到我们的网站或添加到该书的勘误列表中。

要查看之前提交的勘误,请访问www.packtpub.com/books/content/support,并在搜索框中输入书籍名称。相关信息将显示在勘误部分。

盗版

互联网上的版权侵权问题在所有媒体中都持续存在。在 Packt,我们非常重视版权和许可证的保护。如果您在互联网上遇到任何形式的我们作品的非法复制品,请立即提供该位置地址或网站名称,以便我们采取措施解决问题。

请通过 <copyright@packtpub.com> 联系我们,并附上涉嫌盗版材料的链接。

我们感谢您的帮助,保护我们的作者以及我们为您带来有价值内容的能力。

问题

如果您对本书的任何部分有疑问,您可以通过 <questions@packtpub.com> 与我们联系,我们将尽力解决问题。

第一章. 本地安装 Docker

在本章,我们将研究如何在以下平台上安装和配置 Docker:

  • macOS Sierra

  • Windows 10 专业版

  • Ubuntu 16.04 LTS 桌面版

安装完成后,我们将探讨如何与本地的 Docker 安装进行交互。

在我们开始安装之前,我想花点时间简单介绍一下我们将要安装的 Docker 版本。

截至写作时,Docker 17.03 刚刚发布,像大多数更新一样,带来了新特性和对现有特性的改动。本书是基于这个版本的 Docker 编写的,因此使用较旧版本时,某些命令可能无法正常工作或效果不同。

如果你已经安装了 Docker,我建议你运行以下命令检查是否运行的是 Docker 17.03 版本:

docker version

如果你的 Docker 版本低于 17.03,请在继续阅读后续章节之前,参考以下每个部分中的升级说明。

Docker for Mac 和 Windows

正如在前言中提到的,我们将在本书中讨论的 Docker 引擎版本是一个基于 Linux 的工具,那么它如何在 macOS 和 Windows 上工作呢?

很容易认为,由于 macOS 是一个建立在类似 UNIX 核心 XNU 上的操作系统,Docker 会像在 Linux 机器上那样运行,然而,不幸的是,允许 Docker 运行的许多功能在 macOS 使用的内核中并不存在。

尽管最近推出了 Windows 子系统用于 Linux,当前仍处于 beta 阶段,但 Docker for Windows 目前并没有利用这一功能,这意味着 Docker 使用的 Linux 核心更加有限。

注意

Windows 子系统用于 Linux 提供了一个 Ubuntu shell,允许你在 Windows 安装上运行本地的 Linux 命令行工具;更多信息,请参阅 msdn.microsoft.com/en-gb/commandline/wsl/about

那么 Docker for Mac 和 Windows 是如何工作的呢?最新版本的 macOS 和 Windows 10 专业版都内置了超管程序(hypervisor),这些超管程序被集成在操作系统的内核中,macOS 拥有 Hypervisor 框架,而 Windows 10 使用的是 Hyper-V。

注意

Hypervisor 框架允许开发者构建应用程序,而无需安装第三方内核扩展,这意味着他们可以利用完整的硬件虚拟化,但依然保持在用户空间内,这使得虚拟机保持沙箱状态,就像它们作为原生应用程序运行一样。以下链接提供了技术概览:developer.apple.com/reference/hypervisor

对于 Docker for Mac,Docker 构建了自己的开源框架,它与名为 HyperKit 的 Hypervisor 框架兼容:你可以在 github.com/docker/HyperKit/ 了解更多关于 HyperKit 的信息。

Hyper-V 自 Windows Server 2008 以来一直是 Windows 操作系统的原生虚拟机监控程序;自 Windows 8(专业版和企业版)以来,它也成为 Windows 桌面版本的一部分,允许用户和开发者在沙箱环境中启动带有硬件虚拟化的 Windows 和 Linux 虚拟机。有关 Hyper-V 的更多信息,请参见 www.microsoft.com/en-us/cloud-platform/server-virtualization

Docker for Mac 和 Windows 使用这些本地虚拟化技术来启动一个运行 MobyLinux 发行版的虚拟机,MobyLinux 是基于 Alpine Linux 的轻量级发行版,其唯一功能是运行 Docker。

注意

Alpine Linux 的 ISO 文件当前大小为 26 MB,完全功能的最小安装需要约 130MB 的空间,尽管该发行版非常小,但它与更常见的 Linux 发行版一样可用且安全。你可以在 alpinelinux.org/ 了解更多信息。

Docker for Mac 和 Windows 负责启动、配置和维护虚拟机,并处理如网络和从本地计算机挂载文件系统到 MobyLinux 虚拟机等功能。

Docker for Mac

Docker for Mac 有以下系统要求;如果你的机器不满足这些要求,Docker for Mac 将无法安装:

  • 你的 Mac 必须是 2010 年或更高版本,且支持英特尔的硬件 内存管理单元 (MMU) 虚拟化。

  • 你必须运行 OS X El Capitan 10.11 或更高版本。建议你运行最新的 macOS。

  • 你必须至少有 4GB 的 RAM。

  • 不得安装版本为 4.3.30 或更低版本的 VirtualBox,因为这会与 Docker for Mac 产生冲突。

要检查你的机器是否支持 Docker for Mac,你可以运行以下命令:

sysctl kern.hv_support

当你运行该命令时,应该返回 1,这意味着你的内核启用了虚拟化,并且它可以在你的 CPU 上使用。

下载 Docker for Mac

Docker for Mac 可以从以下网址获取:

store.docker.com/editions/community/docker-ce-desktop-mac

我建议暂时使用 稳定版,因为这是我们将在后续章节中安装到远程机器上的版本。点击 Get Docker for Mac (stable) 将开始下载磁盘镜像(DMG)文件,下载完成后,双击文件以挂载它。

安装 Docker for Mac

就像大多数 macOS 应用程序一样,你只需要做以下几步:

  1. 将 Docker 应用程序从挂载的磁盘映像拖到您的应用程序文件夹中;通过双击打开 macOS Finder 中的挂载镜像可以轻松完成此任务,如下截图所示:安装 Docker for Mac

  2. 一旦应用程序复制完成,您可以关闭 Finder 窗口,打开您的 应用程序,找到 Docker 并打开它:安装 Docker for Mac

  3. 第一次打开 Docker 时,您将会看到初始安装向导:安装 Docker for Mac

  4. 点击 下一步 后,系统会告诉您 Docker 将要求输入密码,安装过程需要此密码来完成安装。安装 Docker for Mac

  5. 点击 确定 后,系统会提示您输入密码,然后菜单栏中会出现一个鲸鱼图标,在 Docker 启动时,您应该会看到如下弹出窗口:安装 Docker for Mac

  6. 点击 明白了! 将关闭弹出窗口。您可以通过观察鲸鱼图标上的小框停止动画来判断 Docker 是否已启动;此外,右键单击图标将弹出一个菜单,显示 Docker 安装的状态:安装 Docker for Mac

  7. 从菜单中选择 关于 Docker 将打开以下窗口:安装 Docker for Mac

  8. 在终端运行以下命令将显示有关您 Docker 安装的附加信息:

    docker version
    

    您应该会看到类似以下的输出:

    安装 Docker for Mac

如您所见,它提供了有关 Docker 客户端的详细信息,该客户端已安装在您的 macOS 主机上,并且客户端正在连接到 MobyLinux 虚拟机。

Docker for Windows

Docker for Windows 有以下系统要求;如果您的机器不符合这些要求,Docker for Windows 会在退出前通知您:

  • 您必须运行 64 位 Windows 10 专业版、企业版或教育版(1511 年 11 月更新,版本 10586 或更高)或更高版本(未来有计划支持其他版本)

  • 必须启用 Hyper-V,但如果需要,安装程序会为您启用它

  • 您必须至少有 4GB 的内存

正在下载 Docker for Windows

Docker for Windows 可通过以下 URL 获取:

store.docker.com/editions/community/docker-ce-desktop-windows

和 Docker for Mac 一样,我建议选择 稳定版频道。点击 下载 Docker for Windows(稳定版) 将下载一个安装程序;当安装程序下载完成后,您将有机会选择 运行 它。

安装 Docker for Windows

当 Docker for Windows 安装程序首次打开时,您将看到 Docker 许可协议:

安装 Docker for Windows

点击我接受许可协议条款将启用安装按钮,点击安装将立即开始安装。

一两分钟后,你应该会收到安装完成的确认信息。

为 Windows 安装 Docker

确保启动 Docker已被勾选(默认应为勾选状态),点击完成以打开 Docker。如果你没有启用 Hyper-V,你将会收到以下提示:

为 Windows 安装 Docker

点击确定将重启你的计算机,因此请确保已保存任何打开的文档。重启后,Docker 应自动启动,和 Mac 版 Docker 一样,你会注意到菜单栏中有一个鲸鱼图标:

为 Windows 安装 Docker

Docker 启动后,从菜单中选择关于 Docker将打开以下窗口:

为 Windows 安装 Docker

最后,打开 Windows PowerShell 并输入以下命令:

docker version

这将返回类似的信息,显示 Mac 版 Docker 的客户端信息和 MobyLinux 虚拟机的详细信息:

为 Windows 安装 Docker

还有一件事需要注意:Windows 版 Docker 可以运行本地 Windows 容器。你可以通过从菜单中选择切换到 Windows 容器...来启用此功能;如果这是你第一次启用此功能,你将看到以下弹出对话框:

为 Windows 安装 Docker

点击确定将重启你的计算机。重启后,再次选择菜单选项将切换你从使用 Linux 容器到 Windows 容器;当你运行以下命令时,可以看到这个变化:

docker version

为 Windows 安装 Docker

如你所见,服务器的OS/Arch已经从linux/amd64更改为windows/amd64。本书中我们不会讨论 Windows 容器;你可以通过菜单选项切换回 Linux 容器:

为 Windows 安装 Docker

提示

如果你在后续章节中使用 Docker for Windows 运行命令时遇到问题,请检查你是否使用的是 Linux 容器,可以通过运行docker version或使用菜单进行检查。

升级 Mac 和 Windows 版的 Docker

Mac 和 Windows 版的 Docker 都允许你轻松更新已安装的 Docker 版本。如果你安装了旧版的 Docker for Mac 或 Windows,当你第一次打开 Docker 时,应该会收到提示,告诉你有更新版本可用。如果没有收到提示,你可以从菜单中选择检查更新...来启动升级过程,过程与我们之前介绍的每个版本的安装过程类似。

如果没有更新,你将收到一条通知,确认你已是最新版本。

Ubuntu 16.04 上的 Docker

如果你查看 Docker 网站,你会注意到没有 Linux 桌面版的 Docker 下载,因为实际上不需要。Docker 是一个 Linux 工具,它可以原生运行在大多数 Linux 桌面和服务器上。

虽然 Docker 可以在主 Ubuntu 仓库中找到,我推荐使用官方仓库来安装 Docker。你可以通过运行以下命令来实现:

curl -sSL https://get.docker.com/ | sh

这将配置并安装 Docker Engine 的最新版本。安装完成后,你会收到一个命令,运行它以授予你的用户运行 Docker 的权限,然后退出登录。

当你重新登录时,你将能够运行以下命令:

docker version

你应该看到类似如下的内容:

Ubuntu 16.04 上的 Docker

注意

到目前为止我没有提到的一件事是,当我们为 Mac 和 Windows 安装 Docker 时,还安装了两个附加组件。它们是 Docker Machine 和 Docker Compose,我们将在第二章,使用 Docker 启动应用程序 和 第三章,Docker 在云端 中讲解这些内容。

要安装 Docker Machine,请运行以下命令:

curl -L "https://github.com/docker/machine/releases/download/v0.9.0/docker-machine-$(uname -s)-$(uname -m)" -o /
tmp/docker-machine
chmod +x /tmp/docker-machine
sudo cp /tmp/docker-machine /usr/local/bin/docker-machine

要安装 Docker Compose,请运行以下命令:

curl -L "https://github.com/docker/compose/releases/download/1.
10.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
chmod +x /tmp/docker-compose
sudo cp /tmp/docker-compose /usr/local/bin/docker-compose

安装完成后,你应该能够运行以下两个命令:

docker-compose version
docker-machine version

测试你的安装

现在我们已经安装了 Docker,我们将通过下载、运行并连接到一个 NGINX 容器来快速测试我们的安装。

注意

NGINX 是一个免费的开源高性能 HTTP 服务器和反向代理。NGINX 以其高性能、稳定性、丰富的功能集、简单的配置和低资源消耗而闻名。

提示

关于 Docker 命令的说明

Docker 1.13 引入了一组略微修改的命令行指令,用于与容器和镜像进行交互。由于该语法最终将成为新的标准,因此我们将在本书中使用它。有关 CLI 重构的更多信息,请参见 Docker 1.13 的公告博客:blog.docker.com/2017/01/whats-new-in-docker-1-13/

要下载并启动容器,你只需要从终端提示符运行以下命令:

docker image pull nginx
docker container run -d --name nginx-test -p 8080:80 nginx

第一个命令从 Docker Hub 拉取 NGINX 容器镜像,第二个命令启动我们的 NGINX 容器,并将其命名为 nginx-test,将本地端口 8080 映射到容器的端口 80

你可以使用以下命令检查容器是否在运行:

docker container ps

打开浏览器并访问 http://localhost:8080/ 应该会显示默认的 Welcome to NGINX 页面。

如下屏幕所示,使用 Docker for Mac 时过程相同:

测试你的安装

Docker for Windows:

测试你的安装

或者在 Ubuntu 16.04 上使用 Docker:

测试你的安装

如上图所示,我们在三个平台上运行相同命令的结果完全相同。

一旦你测试了启动容器,你可以通过运行以下命令来清理操作,停止并移除容器,然后删除镜像:

docker container stop nginx-test
docker container rm nginx-test
docker image rm nginx

总结

在本章中,我们介绍了如何为 Mac 安装 Docker、为 Windows 安装 Docker 以及在 Ubuntu 16.04 上安装 Docker。希望你已经在本地机器上完成了一个或多个安装过程。我们还启动了我们的第一个容器,并通过网页浏览器连接到了它。

在下一章中,我们将详细介绍用于启动测试容器的命令,并讲解如何使用 Docker Compose 来启动多容器应用程序。

第二章. 使用 Docker 启动应用程序

在本章中,我们将讨论如何不仅仅使用本地 Docker 安装来启动一个简单的 web 服务器。我们将讨论以下主题:

  • 使用命令行中的 Docker 启动应用程序

  • 如何使用 Docker build 命令

  • 使用 Docker Compose 使多容器应用程序的启动更加简单

然后,我们将使用上述所有技术来启动 WordPress 和 Drupal 应用程序堆栈。

Docker 术语

在我们开始学习如何启动容器之前,我们需要快速讨论一下本章中将使用的常见术语。

小贴士

请注意,本章中的 Docker 命令是针对 Docker 1.13 及以后的版本编写的。在旧版本中运行诸如 docker image pull nginx 这样的命令将会失败并显示错误。有关如何安装最新版本 Docker 的详细信息,请参阅 第一章,本地安装 Docker

Docker 镜像

Docker 镜像是由构成可执行软件应用程序的所有文件集合组成的。这个集合包括应用程序本身以及所有的库、二进制文件和其他依赖项,如部署描述符等。这些文件仅用于在任何地方顺利运行应用程序,不会有任何阻碍。这些 Docker 镜像中的文件是只读的,因此镜像的内容不能被修改。如果你选择修改镜像的内容,Docker 唯一允许的做法是添加一个新层以包含新的更改。换句话说,Docker 镜像是由多个层组成的,你可以使用 docker image history 子命令查看这些层。

Docker 镜像架构有效地利用了这一层次化的概念,能够无缝地为现有镜像添加额外的功能,以满足不同的业务需求并增加镜像的复用性。换句话说,功能可以通过在现有镜像上添加额外的层来扩展,从而派生出新的镜像。Docker 镜像之间有父子关系,最底层的镜像被称为基础镜像。基础镜像是没有任何父镜像的特殊镜像:

Docker 镜像

在上图中,Ubuntu 是一个基础镜像,它没有任何父镜像。

注意

Ubuntu Docker 镜像是一个简化的软件库和二进制文件捆绑包,这些文件对于运行应用程序至关重要。它不包含 Linux 内核、驱动程序和完整 Ubuntu 操作系统提供的各种其他服务。

Docker 镜像

正如您在上图中所看到的,一切都始于一个基础镜像,在本例中是 Ubuntu。进一步,wget功能被添加到镜像作为一个层,并且wget镜像引用 Ubuntu 镜像作为其父级。在下一个层中,Tomcat 应用服务器的一个实例被添加,并且它将wget镜像作为其父级。对原始基础镜像的每次添加都存储在单独的层中(在此处生成了一种层次结构以保留原始身份)。

具体来说,任何 Docker 镜像都必须源自基础镜像,并且通过逐个添加新模块的方式持续丰富其功能,正如上图所生动展示的那样。

Docker 平台提供了一种简单的方式来构建新镜像或扩展现有镜像。您还可以下载其他人已经创建并存储在 Docker 镜像仓库(私有或公共)中的 Docker 镜像。

Docker 注册表

Docker 注册表是一个地方,Docker 镜像可以被存储,以便全球软件开发人员能够快速创建新鲜和复合应用程序,而不会有任何风险。因为所有存储的镜像都将经过多次验证、确认和完善,这些镜像的质量非常高。

使用dockerimage push子命令,您可以将您的 Docker 镜像分发到注册表,以便注册和存储。使用dockerimage pull子命令,您可以从注册表下载 Docker 镜像。

一个 Docker 注册表可以由第三方托管为公共或私有注册表,比如以下几个注册表之一:

每个机构、创新者和个人都可以拥有自己的 Docker 注册表,用于存储其镜像以供内部和/或外部访问和使用。

Docker Hub

在前一章中,当您运行dockerimage pull子命令时,nginx镜像神秘地被下载了下来。在本节中,让我们揭开docker image pull子命令周围的谜团,以及 Docker Hub 如何极大地促成了这一意外的成功。

Docker 社区的好伙伴们已经构建了一个镜像仓库,并将其公开放置在默认位置 index.docker.io。这个默认位置被称为 Docker Hub。docker image pull 子命令被设计为在这个位置查找镜像。因此,当您拉取 nginx 镜像时,它会轻松从默认注册表下载。这种机制有助于加快 Docker 容器的启动速度。

Docker Hub 是官方仓库,其中包含全球 Docker 开发社区精心策划和存储的所有镜像。所谓的“治愈”是通过一系列隔离任务确保 Docker Hub 中存储的所有镜像安全可靠。还有额外的机制,如创建镜像摘要和内容信任,使您能够验证从任何通道接收的所有数据的完整性和发布者。

有经过验证的验证和清理方法,用于清除这些 Docker 镜像中可能有意或无意引入的恶意软件、广告软件、病毒等。

注意

数字签名是 Docker 镜像极度完整性的显著机制。然而,如果官方镜像已被损坏或篡改,那么 Docker 引擎将发出警告,然后继续运行镜像。

除了官方仓库外,Docker Hub Registry 还为第三方开发者和提供者提供了一个平台,用于共享他们的镜像供广泛使用。第三方镜像以其开发者或存储者的用户 ID 作为前缀。

例如,russmckendrick/cluster 是一个第三方镜像,其中 russmckendrick 是用户 ID,cluster 是镜像仓库名称。您可以使用 docker image pull 子命令下载任何第三方镜像,如下所示:

docker image pull russmckendrick/cluster

除了上述仓库之外,Docker 生态系统还提供了一种机制,可以利用来自 Docker Hub Registry 以外的任何第三方仓库中的镜像,并提供由本地仓库托管的镜像。正如前面提到的,Docker 引擎默认在 index.docker.io 查找镜像,而在第三方或本地仓库中,我们必须手动指定从哪个路径拉取镜像。

手动仓库路径类似于没有协议说明符的 URL,例如 https://http://ftp://

以下是从第三方仓库中拉取镜像的示例:

docker image pull registry.domain.com/myapp

控制 Docker 容器

Docker 引擎使你能够使用一组 docker 子命令来 启动停止重启 容器。让我们从 docker container stop 子命令开始,它用于停止一个运行中的容器。当用户发出此命令时,Docker 引擎会向容器中正在运行的主进程发送 SIGTERM (-15) 信号。SIGTERM 信号请求进程优雅地终止。

大多数进程会处理此信号并进行优雅退出。然而,如果该进程未能如此操作,则 Docker 引擎将等待一个宽限期。即使在宽限期后,如果进程仍未终止,则 Docker 引擎将强制终止该进程。强制终止是通过发送 SIGKILL (-9) 来实现的。

SIGKILL 信号无法被捕获或忽略,因此,它将导致进程的突然终止,且不会进行适当的清理。

现在,让我们启动容器并尝试 docker container stop 子命令,如下所示:

dockercontainer run -i -t ubuntu:16.04 /bin/bash

控制 Docker 容器

启动容器后,让我们使用从提示中获取的容器 ID 运行 docker container stop 子命令。自然,我们必须使用第二个屏幕/终端来运行此命令,且该命令将始终回显容器 ID,如下面所示:

docker  container stop 3 de97cc32051

控制 Docker 容器

现在,如果你切换到运行容器的屏幕/终端,你会注意到容器正在被终止。如果你再仔细观察,你还会注意到容器提示符旁边有 exit 文本。这是由于 bash shell 的 SIGTERM 处理机制,如下所示:

控制 Docker 容器

如果我们进一步运行 docker container ps 子命令,那么我们将在列表中找不到该容器。事实上,docker container ps 子命令默认情况下只列出处于运行状态的容器。由于我们的容器处于停止状态,因此它被巧妙地排除在列表之外。现在,你可能会问,如何查看处于停止状态的容器呢?其实,docker container ps 子命令可以接受额外的参数 -a,它将列出该 Docker 主机上的所有容器,无论其状态如何。

这可以通过运行以下命令来实现:

docker container ps -a

控制 Docker 容器

接下来,让我们看看 docker container start 子命令,它用于启动一个或多个已停止的容器。容器可以通过 docker container stop 子命令或通过正常或异常终止容器中的主进程而进入停止状态。在运行中的容器上,此子命令没有任何效果。

让我们通过使用 docker container start 子命令来启动先前停止的容器,如下所示:

docker start 3de97cc32051

默认情况下,docker container start子命令不会附加到容器。你可以通过在docker container start子命令中使用-a选项,或者明确使用docker container attach子命令将其附加到容器,如下所示:

docker container attach 3de97cc32051

控制 Docker 容器

现在,让我们运行docker containerps命令,验证容器的运行状态,如下所示:

docker container ps

控制 Docker 容器

restart命令是stopstart功能的组合。换句话说,restart命令将通过执行docker container stop子命令的精确步骤来停止一个正在运行的容器,然后启动start过程。默认情况下,这个功能将通过docker container restart子命令执行。

接下来,重要的一组容器控制子命令如下:

  • docker container pause

  • docker container unpause

docker container pause子命令将基本上冻结容器内所有进程的执行。相反,docker container unpause子命令将解冻容器内所有进程的执行,并从冻结的地方继续执行。

在看到pause/unpause的技术解释后,让我们通过一个详细示例来说明这个功能是如何工作的。我们使用了两个屏幕/终端场景。在一个终端上,我们启动了容器,并使用无限循环来显示日期和时间,每隔 5 秒休眠一次,然后继续循环。我们将运行以下命令:

docker container run -i -t ubuntu:16.04 /bin/bash

一旦进入容器,运行以下命令:

while true; do date; sleep 5; done

我们的小脚本非常忠实地每5秒打印一次日期和时间,除非它被暂停:

控制 Docker 容器

如上所示的终端输出,你可以看到大约有 30 秒的延迟,因为这时我们在第二个终端屏幕上对容器执行了docker container pause子命令,如下所示:

docker container pause 9724f4e0e444

当我们暂停容器时,我们通过在相同屏幕上的容器上使用docker containerps子命令查看了进程状态,并清楚地指示出容器已被暂停,如下所示的命令结果:

docker container ps

我们接着执行了docker container unpause子命令,这个命令解冻了我们的容器,继续执行它,然后开始打印日期和时间,正如我们在前一个命令中看到的,这里展示的是:

docker container unpause 9724f4e0e444

我们在本节开始时解释了pauseunpause命令。

最后,容器和其中运行的脚本通过使用docker container stop子命令被停止了,如下所示:

docker container stop 9724f4e0e444

你可以看到我们在第二个终端上执行的所有操作:

控制 Docker 容器

现在让我们做点更复杂的操作。

运行 WordPress 容器

几乎每个人在某个时刻都安装过、使用过或读过有关 WordPress 的信息,因此在我们的下一个示例中,我们将使用来自 Docker Hub 的官方 WordPress 容器。你可以在 hub.docker.com/_/wordpress/ 上找到有关该容器的详细信息。

注意

WordPress 是一个可以用来创建美丽网站、博客或应用程序的 Web 软件。我们常说 WordPress 既是免费的,也是无价的。如需更多信息,请访问 wordpress.org/

要启动 WordPress,你需要下载并运行两个容器,第一个是数据库容器,建议使用官方的 MySQL 容器,你可以在 hub.docker.com/_/mysql/ 上找到。

要下载最新的 MySQL 容器镜像,请在你的 Mac、Windows 或 Linux 机器上运行以下命令:

docker image pull mysql

现在你已经拉取了镜像副本,你可以通过运行以下命令启动 MySQL:

docker container run -d \
 --name mysql \
 -e MYSQL_ROOT_PASSWORD=wordpress \
 -e MYSQL_DATABASE=wordpress \
 mysql

上述命令启动了(docker container run)MySQL 容器并以分离模式运行(使用 -d),意味着它在后台运行,我们将容器命名为 mysql--name wordpress),并且使用两个不同的环境变量(使用 -e)将 MySQL 的 root 密码设置为 wordpress-e MYSQL_ROOT_PASSWORD=wordpress)并创建一个名为 wordpress 的数据库(-e MYSQL_DATABASE=wordpress)。

启动后,你应该会收到容器 ID。你可以通过以下命令检查容器是否按预期运行:

docker container ps

现在,虽然容器正在运行,但这并不意味着 MySQL 已经准备好。如果此时启动你的 WordPress 容器,你可能会发现它运行一段时间后就停止了。

不用担心,这是预期的。由于容器内没有 MySQL 数据,它需要一些时间才能进入可接受连接的状态。

要检查 MySQL 容器的状态,你可以运行以下命令:

docker container logs mysql

运行 WordPress 容器

一旦看到消息 mysqld: ready for connections,你就可以启动 WordPress 容器了;你可能需要检查日志几次。

接下来,我们要下载 WordPress 容器镜像;为此,请运行以下命令:

docker image pull wordpress

下载完成后,运行以下命令启动 WordPress 容器:

docker container run -d \
 --name wordpress \
--link mysql:mysql\
 -p 8080:80 \
 -e WORDPRESS_DB_PASSWORD=wordpress \
 wordpress

再次提醒,我们是在后台启动容器(使用 -d),并将容器命名为 wordpress(使用 --name wordpress)。在 MySQL 和 WordPress 容器之间,这里略有不同,我们需要让 WordPress 容器知道我们的 MySQL 容器的可访问位置,为此我们使用了链接标志(在我们的案例中,通过运行 --link mysql:mysql),这将在 WordPress 容器内创建一个别名,将其指向 MySQL 容器的 IP 地址。

接下来,我们将在机器上打开 8080 端口,并将其映射到容器上的 80 端口(使用 -p 8080:80),然后让 WordPress 知道密码是什么(使用 -e WORDPRESS_DB_PASSWORD=wordpress)。

使用以下命令检查正在运行的容器:

docker container ps

应该会显示你现在有两个正在运行的容器,MySQL 和 WordPress:

运行 WordPress 容器

与 MySQL 容器不同,WordPress 容器在可访问之前需要做的事情不多,你可以通过运行以下命令查看日志:

docker container logs wordpress

运行 WordPress 容器

如果你打开浏览器并访问 http://localhost:8080/,你应该会看到 WordPress 安装界面,如下所示:

运行 WordPress 容器

如果你愿意,你可以通过点击 继续 并按照屏幕上的提示完成安装并启动 WordPress;然而,我们接下来要运行的命令会销毁我们的两个容器。

为了在下一项练习之前删除我们刚刚启动的所有内容,运行以下命令:

docker container stop wordpressmysql
docker container rmwordpressmysql
docker image rm wordpress mysql

到目前为止,我们一直在使用 Docker 客户端轻松启动、停止、启动、暂停、取消暂停和删除容器,并从 Docker Hub 下载和删除容器镜像。虽然这对于快速启动几个容器来说很不错,但一旦你有多个容器同时运行,它就会变得复杂,这就是我们接下来要介绍的工具派上用场的地方。

Docker Compose

如果你跟着 第一章的 Linux 安装步骤一起操作,本地安装 Docker,那么你应该已经手动安装了 Docker Compose;如果你跳过了这一部分,那么你会很高兴地知道,Docker Compose 已作为 Docker for Mac 和 Windows 的一部分安装和维护。

我相信你会同意,到目前为止,Docker 已经证明是相当直观的,Docker Compose 也不例外。它最初作为一个名为 Fig 的第三方软件出现,由 Orchard Labs 编写(该项目的原始网站仍然可以访问:fig.sh/)。

原始项目的目标如下:

“使用 Docker 提供快速、隔离的开发环境”

由于 Orchard Labs 已成为 Docker 的一部分,他们并没有偏离原始项目的目标:

“Compose 是一个用于定义和运行多容器 Docker 应用程序的工具。使用 Compose,您可以使用 Compose 文件配置应用程序的服务。然后,通过一个命令,您可以根据配置创建并启动所有服务。”

在我们开始查看 Compose 文件并启动容器之前,让我们先思考一下为什么像 Compose 这样的工具是有用的。

为什么选择 Compose?

启动单独的容器只需要运行以下命令:

docker container run -i -t ubuntu:16.04 /bin/bash

这将启动并连接到一个 Ubuntu 容器。正如我们已经提到的,实际上这不仅仅是启动简单的容器。Docker 不是用来替代虚拟机的,它是用来运行单个应用程序的。

这意味着你不应该将整个 LAMP 堆栈运行在单个容器中,而应该考虑将 Apache 和 PHP 运行在一个容器中,然后将其与第二个运行 MySQL 的容器链接。

你可以进一步扩展,运行 NGINX 容器、PHP-FPM 容器和 MySQL 容器。这就变得复杂了。突然间,你用来启动容器的简单单行命令变成了好几行,每一行都必须按正确的顺序执行,并带有正确的标志来暴露端口、将它们连接起来并使用环境变量配置服务。

这正是 Docker Compose 尝试解决的问题。你可以使用 YAML 文件定义容器,而不是使用多个长命令。这意味着你只需使用一个命令就能启动应用程序,并将容器启动顺序的逻辑交给 Compose 来处理。

注意

YAML 不是标记语言 (YAML) 是一种对人类友好的数据序列化标准,适用于所有编程语言。

这也意味着你可以将应用程序的 Compose 文件与代码库一起打包,或直接发送给其他开发人员/管理员,他们将能够按照你预期的方式启动你的应用程序。

Compose 文件

让我们从再次启动 WordPress 开始。首先,如果你还没有克隆本书附带的 GitHub 仓库,可以在以下 URL 找到它:github.com/russmckendrick/bootcamp

欲了解如何克隆仓库的更多信息,请参见简介部分。一旦你克隆了仓库,请从仓库的根目录运行以下命令:

cd chapter2/compose-wordpress

compose-wordpress 文件夹包含以下 docker-compose.yml 文件:

version: "3" 

services:
mysql:
     image: mysql
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: wordpress
       MYSQL_DATABASE: wordpress
wordpress:
depends_on:
       - mysql
     image: wordpress
     ports:
       - "8080:80"
     restart: always
     environment:
       WORDPRESS_DB_PASSWORD: wordpress

volumes:
db_data:

正如你所看到的,docker-compose.yml 文件易于理解;我们最初的 docker-compose.yml 文件分为三个部分:

  • 版本:这告诉 Docker Compose 我们使用的是哪种文件格式;当前版本是 3

  • 服务:这里定义了我们的容器,你可以在此定义多个容器。

  • :所有用于持久存储的卷都在这里定义,我们将在后续章节中详细讲解这一部分。

大部分情况下,语法与我们使用 Docker 命令行客户端启动 WordPress 容器时非常相似。然而,也有一些变化:

  • volumes:在 mysql 容器中,我们使用一个名为 db_data 的卷,并将其挂载到容器内的 /var/lib/mysql 目录。

  • restart:这个设置为 always,意味着如果我们的容器因任何原因停止响应,例如 wordpress 容器在 mysql 容器接受连接之前会停止运行,它会自动重启,这样我们就不需要手动干预。

  • depends_on:在这里,我们告诉 wordpress 容器在 mysql 容器运行之前不要启动。

你可能会注意到我们没有链接容器,这是因为 Docker Compose 会自动创建一个网络来启动服务,Docker Compose 创建的每个容器都会自动更新主机文件,包括该服务中每个容器的别名,这意味着我们的 WordPress 容器能够使用默认主机 mysql 来连接到 MySQL 容器。

要启动我们的 WordPress 安装,只需要运行以下命令:

docker-compose pull
docker-compose up -d

在命令的末尾使用 -d 标志会以分离模式启动容器,这意味着它们将在后台运行。

如果我们没有使用 -d 标志,那么我们的容器会在前台启动,我们就无法继续使用同一个终端会话,而不停止正在运行的容器。

你将看到如下输出:

Compose 文件

当容器正在运行时,你可以通过运行以下命令查看:

docker-compose ps
docker container ps

MySQL 容器准备好接受连接会需要一些时间,你可能会发现运行:

docker-compose logs

会显示类似下面的连接错误:

Compose 文件

别担心,你很快就会看到如下内容:

Compose 文件

再次,在浏览器中打开 http://localhost:8080/ 应该会显示安装界面:

Compose 文件

上述过程适用于 Docker for Mac 和 Linux;但是对于 Docker for Windows,你需要在 Docker Compose 命令中添加 .exe 后缀:

cd .\chapter02\wordpress-compose
docker-compose.exe pull
docker-compose.exe up -d
docker container ps

这将给你类似以下的输出:

Compose 文件

再次,打开浏览器并访问 http://localhost:8080/ 应该会显示安装界面:

Compose 文件

在进入下一部分之前,让我们通过运行以下命令停止并删除我们的 WordPress 容器:

docker-compose stop
docker-compose rm

或者,如果你正在使用 Docker for Windows:

docker-compose.exestop
docker-compose.exe rm

到目前为止,我们一直在使用 Docker Hub 上的镜像,接下来我们将查看如何自定义镜像。

Docker 构建

Docker 镜像是容器的基本构建块。这些镜像可以是非常基础的操作环境,例如 alpineUbuntu。或者,这些镜像也可以为企业和云 IT 环境构建高级应用栈。自动化构建 Docker 镜像的方法是使用 Dockerfile

Dockerfile 是一个基于文本的构建脚本,包含一系列特殊的指令,用于从基础镜像构建正确且相关的镜像。Dockerfile 中的顺序指令可以包括选择基础镜像、安装所需的应用程序、添加配置和数据文件、自动运行服务以及将这些服务暴露给外部世界。因此,基于 Dockerfile 的自动化构建系统显著简化了镜像构建过程。它还提供了极大的灵活性,能够组织构建指令并可视化整个构建过程。

Docker 引擎通过 docker build 子命令紧密集成了这个构建过程。在 Docker 的客户端-服务器模式中,Docker 服务器(或守护进程)负责完整的构建过程,而 Docker 命令行界面负责传输构建上下文,包括将 Dockerfile 传输到守护进程。

为了提前了解这一节中集成的 Dockerfile 构建系统,我们向您介绍一个基本的 Dockerfile。然后,我们将解释如何将该 Dockerfile 转换为镜像,并从该镜像启动容器。

我们的 Dockerfile 由两个指令组成,如下所示(在 GitHub 仓库的 chapter02/build_basic 文件夹中也有副本):

FROM alpine:latest
CMD echo Hello World!!

接下来,我们将介绍/讨论之前提到的两个指令:

  • 第一个指令是选择基础镜像。在这个示例中,我们选择 apline:latest 镜像。

  • 第二个指令是执行 CMD 命令,指示容器执行 echo Hello World!!

现在,让我们通过使用之前的 Dockerfile 来生成 Docker 镜像,方法是调用 dockerimagebuild 并指定 Dockerfile 的路径。在我们的示例中,我们将从存储 Dockerfile 的目录中调用 dockerimagebuild 子命令,路径将由以下命令指定:

dockerimagebuild

在发出前面的命令后,build 过程将开始,通过将构建上下文发送到守护进程,并显示如下所示的文本:

正在将构建上下文发送到 Docker 守护进程 2.048 kB

第 1/2 步:FROM alpine:latest

构建过程将继续,完成后将显示以下内容:

成功构建 0080692cf8db

在前面的示例中,镜像是使用 IMAGE ID0a2abe57c325 构建的。现在让我们使用这个镜像,通过使用 docker container run 子命令启动一个容器,如下所示:

docker container run 0080692cf8db

很酷,不是吗?通过很少的努力,我们就能以 alpine 作为基础镜像构建一个镜像,并且我们已经扩展了这个镜像来输出 Hello World!!

这是一个简单的应用程序,但使用相同的方法也可以实现企业级镜像。

现在,让我们使用 docker image ls 子命令查看镜像的详细信息。在这里,你可能会惊讶地发现 IMAGEREPOSITORY)和 TAG 名称显示为 <none>。这是因为我们在构建镜像时没有指定任何镜像或 TAG 名称。你可以使用 docker image tag 子命令来指定 IMAGE 名称,并可选地指定 TAG 名称,如下所示:

docker image tag 0080692cf8dbbasicbuild

另一种方法是在 build 期间使用 -t 选项通过 docker image build 子命令来构建带有镜像名称的镜像,如下所示:

docker image build -t basicbuild

由于 Dockerfile 中的指令没有变化,Docker 引擎会高效地复用旧的图像 ID0a2abe57c325,并将图像名称更新为 basicbuild。默认情况下,构建系统会将 latest 作为 TAG 名称。通过在 IMAGE 名称和 TAG 名称之间添加 : 分隔符,可以修改这种行为。也就是说,<image name>:<tag name> 是修改行为的正确语法,其中 <image name> 是镜像的名称,<tag name> 是标签的名称。

再次使用 docker image ls 子命令查看镜像的详细信息,你会注意到镜像(存储库)名称为 basicimage,标签名称为 latest。最好实践是始终使用镜像名称来构建镜像。

在体验了 Dockerfile 的魔力之后,在接下来的章节中,我们将介绍 Dockerfile 的语法或格式,并解释十几条 Dockerfile 指令。

注意

默认情况下,docker image build 子命令使用位于构建上下文中的 Dockerfile。但是,–f 选项允许 docker image build 子命令指定一个路径或名称不同的替代 Dockerfile

Dockerfile 语法概述

在本节中,我们将解释 Dockerfile 的语法或格式。一个 Dockerfile 由指令、注释、解析器指令和空行组成,如下所示:

# Comment

INSTRUCTION arguments

Dockerfile 的指令行由两个部分组成,指令行以指令本身开始,后面跟着该指令的参数。指令可以用任何大小写书写,换句话说,它对大小写不敏感。然而,标准做法或约定是使用 大写字母 来区分指令和参数。让我们重新查看之前示例中 Dockerfile 的内容:

FROM apline:latest
CMD echo Hello World!!

这里,FROM 是一个指令,它以 apline:latest 作为参数,而 CMD 是一个指令,它以 echo Hello World!! 作为参数。

注释行

Dockerfile 中的注释行必须以 # 符号开始。指令后的 # 符号被视为参数。如果 # 符号前有空格,则 docker image build 系统会将其视为未知指令并跳过该行。现在,让我们通过一个示例来理解前述情况,以更好地理解注释行:

  • 有效的 Dockerfile 注释行总是以 # 符号作为行首字符:

    # This is my first Dockerfile comment
    
  • # 符号可以是参数的一部分:

    CMD echo ### Welcome to Docker ###
    
  • 如果 # 符号前有空格,则构建系统会将其视为未知指令:

    # this is an invalid comment line
    

可以在仓库中的 /chapter02/build_basic/ 找到一个示例 Dockerfile

# Example of a really bsaicDockerfile

FROM alpine:latest
CMD echo Hello World!!

docker image build 系统会忽略 Dockerfile 中的空行,因此,建议 Dockerfile 的作者添加注释和空行,以大幅提升 Dockerfile 的可读性。

解析器指令

如其名称所示,解析器指令指示 Dockerfile 解析器按指令中指定的方式处理 Dockerfile 的内容。解析器指令是可选的,并且必须位于 Dockerfile 的最顶部。目前,转义是唯一支持的指令。

我们使用转义字符来转义一行中的字符,或将一行扩展为多行。在类似 UNIX 的平台中,\ 是转义字符,而在 Windows 中,\ 是目录路径分隔符,' 是转义字符。默认情况下,Dockerfile 解析器将 \ 视为转义字符,您也可以在 Windows 上使用转义解析器指令来覆盖这一行为,如下所示:

# escape='

Dockerfile 构建指令

到目前为止,我们已经了解了集成的构建系统、Dockerfile 语法和示例生命周期,其中讨论了如何利用示例 Dockerfile 生成镜像,以及如何从该镜像启动容器。在本节中,我们将介绍 Dockerfile 指令、它们的语法以及一些适用的示例。

FROM 指令

FROM 指令是最重要的,它是 Dockerfile 中的第一个有效指令。它为构建过程设置了基础镜像。随后的指令将以此基础镜像为基础进行构建。Docker 构建系统允许灵活使用任何人构建的镜像。您还可以通过为它们添加更精确和实用的功能来扩展它们。默认情况下,Docker 构建系统会在 Docker 主机中查找镜像。

然而,如果在 Docker 主机中找不到镜像,则 Docker 构建系统将从公开的 Docker Hub 仓库拉取该镜像。如果在 Docker 主机和 Docker Hub 仓库中都找不到指定的镜像,Docker 构建系统将返回错误。

FROM 指令的语法如下:

FROM <image>[:<tag>|@<digest>]

在上述代码语句中,注意以下几点:

  • <image>:这是将用作基础镜像的镜像名称。

  • <tag><digest>:标签和摘要都是可选属性,您可以使用标签或摘要来指定特定的 Docker 镜像版本。如果标签和摘要都不存在,则默认假定标签为 latest

这里是使用镜像名称 centosFROM 指令的示例:

FROM centos

在上述示例中,Docker 构建系统会隐式地默认标签为 latest,因为镜像名称中既没有标签也没有摘要。

强烈建议不要在单个 Dockerfile 中使用多个 FROM 指令,因为可能会引起破坏性冲突。

MAINTAINER 指令

所有 MAINTAINER 指令所做的就是允许将作者的详细信息设置为一个镜像。Docker 不会对在 Dockerfile 中放置 MAINTAINER 指令的位置施加任何限制。但是,强烈建议在 FROM 指令之后放置它。

下面是 MAINTAINER 指令的语法,其中 <author's detail> 可以是任何文本。然而,强烈建议使用如下代码语法中显示的镜像、作者姓名和电子邮件地址:

MAINTAINER <author's detail>

在 repo 中的 /chapter02/build_01_maintainer/ 下有一个带有作者姓名和电子邮件地址的 MAINTAINER 指令示例:

# Example Dockerfile showing MAINTAINER

FROM alpine:latest
MAINTAINER Russ McKendrick<russ@mckendrick.io>

RUN 指令

RUN 指令是构建时的真正工作马,可以运行任何命令。一般建议使用一个 RUN 指令执行多个命令。这样可以减少生成的 Docker 镜像中的层数,因为 Docker 系统会为 Dockerfile 中每次调用指令创建一个层。

RUN 指令有两种语法:

  • 首先是 shell 类型,如下所示:

    RUN <command>
    

    在这里,<command> 是在构建时执行的 shell 命令。如果要使用这种语法类型,则始终使用 /bin/sh -c 执行命令。

  • 第二种语法类型可以是 exec 或 JSON 数组,如下所示:

    RUN ["<exec>", "<arg-1>", ..., "<arg-n>"]
    

    在这里,代码术语的含义如下:

    • <exec>:这是在构建时运行的可执行文件。

    • <arg-1>, ..., <arg-n>:这些是可执行文件的变量(零个或多个)数目的参数。

与第一种语法类型不同,这种类型不会调用 /bin/sh -c。因此,不会发生像变量替换($USER)和通配符替换(*?)这样的 shell 处理。如果对 shell 处理非常重要,请使用 shell 类型。但是,如果您仍然喜欢 exec(JSON 数组类型),那么请使用您偏爱的 shell 作为可执行文件,并将命令作为参数提供。

例如,RUN ["bash", "-c", "rm", "-rf", "/tmp/abc"]

让我们向我们的 Dockerfile 添加一些 RUN 指令,使用 apk 安装 NGINX,然后设置一些权限:

# Example Dockerfile showing RUN

FROM alpine:latest
MAINTAINER Russ McKendrick<russ@mckendrick.io>

RUN apk add --update supervisor nginx&&rm -rf /var/cache/apk/*

如你所见,我们正在安装 NGINX 和 Supervisor。已添加 && 以便将多个命令合并成一行,因为 Dockerfile 中的每一行都会在镜像中创建一个层,将命令合并在一起可以简化你的镜像文件。

COPY 指令

COPY 指令允许你将文件从 Docker 主机复制到你正在构建的镜像的文件系统中。以下是 COPY 指令的语法:

COPY <src> ... <dst>

前面的代码术语有如下解释:

  • <src>:这是源目录,构建上下文中的文件,或者是执行 docker build 子命令的目录。

  • ...:这表示可以直接指定多个源文件,也可以通过通配符指定。

  • <dst>:这是新镜像中的目标路径,源文件或目录将被复制到该路径。如果指定了多个文件,则目标路径必须是一个目录,并且必须以斜杠 / 结尾。

建议使用绝对路径作为目标目录或文件。如果没有使用绝对路径,COPY 指令将假定目标路径从根目录 / 开始。COPY 指令足够强大,能够创建新目录并覆盖新创建镜像中的文件系统。

copy 命令的示例可以在仓库(github.com/russmckendrick/bootcamp)中的 /chapter02/build_03_copy/ 找到:

# Example Dockerfile showing COPY

FROM alpine:latest
MAINTAINER Russ McKendrick<russ@mckendrick.io>

RUN apk add --update supervisor nginx&&rm -rf /var/cache/apk/*

COPY start.sh /script/
COPY files/default.conf /etc/nginx/conf.d/
COPY files/nginx.conf /etc/nginx/nginx.conf
COPY files/supervisord.conf /etc/supervisord.conf

这将把 start.sh 文件复制到 Docker 镜像中的 /script/ 文件夹,并将 files 文件夹中的配置文件直接复制到镜像中。

ADD 指令

ADD 指令类似于 COPY 指令。然而,除了支持 COPY 指令的功能外,ADD 指令还可以处理 TAR 文件和远程 URL。我们可以将 ADD 指令注释为“增强版的 COPY”。

以下是 ADD 指令的语法:

ADD <src> ... <dst>

ADD 指令的参数与 COPY 指令非常相似,如下所示:

  • <src>:这是构建上下文中的源目录或文件,或者是执行 docker build 子命令的目录。然而,值得注意的区别是,源文件可以是构建上下文中的 tar 文件,也可以是远程 URL。

  • ...:这表示多个源文件可以通过直接指定或使用通配符指定。

  • <dst>:这是新镜像中的目标路径,源文件或目录将被复制到该路径。

这是一个示例,演示如何将多个源文件复制到目标镜像文件系统的各个目标目录中。在此示例中,我们使用了一个 TAR 文件(webroot.tar),其中包含了 http 守护进程配置文件和存储在适当目录结构中的网页文件,如下所示:

ADD 指令

Dockerfile 中的下一行包含 ADD 指令,用于将 TAR 文件(webroot.tar)复制到目标镜像并从目标镜像的根目录(/)提取该 TAR 文件,如下所示,你可以在仓库中的 /chapter02/build_04_add/ 找到此示例:

# Example Dockerfile showing ADD

FROM alpine:latest
MAINTAINER Russ McKendrick<russ@mckendrick.io>

RUN apk add --update supervisor nginx&&rm -rf /var/cache/apk/*

COPY start.sh /script/
COPY files/default.conf /etc/nginx/conf.d/
COPY files/nginx.conf /etc/nginx/nginx.conf
COPY files/supervisord.conf /etc/supervisord.conf

ADD webroot.tar /
RUN chown -R nginx:nginx /var/www/html

因此,ADD 指令的 TAR 选项可以用于将多个文件复制到目标镜像中,同时请注意,我们已经添加了第二个 RUN 指令,用于设置我们刚刚使用 ADD 创建的文件夹的权限。

EXPOSE 指令

EXPOSE 指令打开一个容器网络端口,用于容器与其他网络之间的通信。

EXPOSE 指令的语法如下:

EXPOSE <port>[/<proto>] [<port>[/<proto>]...]

在这里,代码术语的含义如下:

  • <port>:这是需要暴露给外界的网络端口。

  • <proto>:这是一个可选字段,用于指定特定的传输协议,如 TCP 和 UDP。如果未指定传输协议,则默认使用 TCP 协议。

EXPOSE 指令允许你在一行中指定多个端口。

以下是 Dockerfile 中使用 EXPOSE 指令暴露 80 端口的示例:

# Example Dockerfile showing EXPOSE

FROM alpine:latest
MAINTAINER Russ McKendrick<russ@mckendrick.io>

RUN apk add --update supervisor nginx&&rm -rf /var/cache/apk/*

COPY start.sh /script/
COPY files/default.conf /etc/nginx/conf.d/
COPY files/nginx.conf /etc/nginx/nginx.conf
COPY files/supervisord.conf /etc/supervisord.conf

ADD webroot.tar /

RUN chown -R nginx:nginx /var/www/html

EXPOSE 80/tcp

ENTRYPOINT 指令

ENTRYPOINT 指令有助于在容器的整个生命周期内构建运行应用程序(入口点)的镜像,该容器是从该镜像中创建的。当入口点应用程序终止时,容器也会与应用程序一起终止,反之亦然。

因此,ENTRYPOINT 指令使得容器像可执行文件一样运行。从功能上讲,ENTRYPOINT 类似于接下来要讲解的 CMD 指令,但两者的主要区别在于,使用 ENTRYPOINT 指令启动的入口点应用程序不能通过 docker run 子命令的参数来覆盖。

然而,这些 docker container run 子命令的参数将作为额外的参数传递给入口点应用程序。话虽如此,Docker 提供了一种机制,可以通过 docker container run 子命令中的 --entrypoint 选项覆盖入口点应用程序。--entrypoint 选项只能接受一个单词作为其参数,因此功能有限。

从语法上看,ENTRYPOINT 指令与 RUNCMD 指令非常相似,且具有两种语法形式,如下所示:

  • 第一种语法是 shell 类型,如下所示:

    ENTRYPOINT <command>
    

    这里,<command> 是容器启动时执行的 shell 命令。如果使用这种语法,则命令总是通过 /bin/sh -c 执行。

  • 第二种语法是 exec 或 JSON 数组,如下所示:

    ENTRYPOINT ["<exec>", "<arg-1>", ..., "<arg-n>"]
    

    其中,代码术语表示以下内容:

    • <exec>:这是可执行文件,它需要在容器启动时运行。

    • <arg-1>, ..., <arg-n>:这些是可执行文件的可变(零个或多个)参数。

从语法上看,你可以在 Dockerfile 中有多个 ENTRYPOINT 指令。然而,构建系统只会忽略除最后一个之外的所有 ENTRYPOINT 指令。换句话说,如果有多个 ENTRYPOINT 指令,只有最后一个 ENTRYPOINT 指令会生效。

如你所记得,我们在介绍 RUN 指令时安装了一个名为 supervisord 的服务,我们将使用这个服务作为镜像的入口点,这意味着我们的 Dockerfile 现在看起来如下:

# Example Dockerfile showing ENTRYPOINT

FROM alpine:latest
MAINTAINER Russ McKendrick<russ@mckendrick.io>

RUN apk add --update supervisor nginx&&rm -rf /var/cache/apk/*

COPY start.sh /script/
COPY files/default.conf /etc/nginx/conf.d/
COPY files/nginx.conf /etc/nginx/nginx.conf
COPY files/supervisord.conf /etc/supervisord.conf

ADD webroot.tar /

RUN chown -R nginx:nginx /var/www/html

EXPOSE 80/tcp

ENTRYPOINT ["supervisord"]

现在我们可以把它留在这里,镜像就可以正常工作了,不过我们应该传递给镜像一个指令。

CMD 指令

CMD 指令可以运行任何命令(或应用程序),这与 RUN 指令类似。然而,这两者之间的主要区别在于执行时间。通过 RUN 指令传递的命令在构建时执行,而通过 CMD 指令指定的命令在从新创建的镜像启动容器时执行。因此,CMD 指令为容器提供了默认的执行方式。然而,可以通过 docker run 子命令的参数覆盖该默认行为。当应用程序终止时,容器也会随着应用程序一起终止,反之亦然。

表面上看,CMD 指令与 RUN 指令非常相似,因为它们都可以运行传递给它的任何命令,然而,这两个指令之间有一个主要区别。

通过 RUN 指令传递的命令在构建时执行,而通过 CMD 指令传递的命令在运行时执行,这意味着你可以为容器定义默认的执行命令。这意味着如果在执行 docker container run 命令时没有传递命令,则会执行 CMD

CMD 指令有三种语法类型,如下所示:

  • 第一种语法类型是 shell 类型,如下所示:

    CMD <command>
    
    

    其中,<command> 是需要在容器启动时执行的 shell 命令。如果使用这种语法,则命令总是通过 /bin/sh -c 执行。

  • 第二种语法是 exec 或 JSON 数组,如下所示:

    CMD ["<exec>", "<arg-1>", ..., "<arg-n>"]
    
    

    其中,代码术语表示以下内容:

    • <exec>:这是需要在容器启动时运行的可执行文件。

    • <arg-1>, ..., <arg-n>:这些是可执行文件的可变(零个或多个)参数。

  • 第三种语法类型也是 exec 或 JSON 数组,类似于前一种类型。然而,这种类型用于为ENTRYPOINT指令设置默认参数,如下所示:

    CMD ["<arg-1>", ..., "<arg-n>"]
    
    

    其中,代码术语的含义如下:

    • <arg-1>, ..., <arg-n>:这些是ENTRYPOINT指令的变量(零个或多个)参数,将在下一节中解释。

从语法上讲,你可以在Dockerfile中添加多个CMD指令。然而,构建系统会忽略所有CMD指令,除了最后一个。换句话说,在多个CMD指令的情况下,只有最后一个CMD指令会生效。

如前节所述,我们的Dockerfile本来只需要定义ENTRYPOINT指令就可以运行,但这会在supervisord启动时产生一个非致命错误,因此我们通过CMD指令传递一个标志,来定义我们的 supervisor 配置文件的位置:

# Example Dockerfile showing CMD

FROM alpine:latest
MAINTAINER Russ McKendrick<russ@mckendrick.io>

RUN apk add --update supervisor nginx&&rm -rf /var/cache/apk/*

COPY start.sh /script/
COPY files/default.conf /etc/nginx/conf.d/
COPY files/nginx.conf /etc/nginx/nginx.conf
COPY files/supervisord.conf /etc/supervisord.conf

ADD webroot.tar /

RUN chown -R nginx:nginx /var/www/html

EXPOSE 80/tcp

ENTRYPOINT ["supervisord"]
CMD ["-c","/etc/supervisord.conf"]

现在我们可以构建我们的镜像,你可以在仓库的/chapter02/ build_07_cmd/文件夹中找到我们完成的Dockerfile,要构建镜像,只需运行以下命令:

docker image build -t cluster

这将开始构建,如下所示的终端输出:

CMD 指令

构建中有 12 个步骤,可能需要一两分钟,但是一旦完成,你应该会看到类似以下的终端输出:

CMD 指令

一旦你构建好镜像,你可以通过以下命令检查并运行它:

docker image ls
docker container run -d -p 8080:80 cluster

CMD 指令

现在容器正在运行,打开浏览器并访问http://localho st:8080/,应该会显示类似以下的页面:

CMD 指令

就这样,我们创建了一个镜像:

  • 使用 Alpine Linux 基础镜像(FROM

  • 使用apk安装了NGINXsupervisordRUN

  • 将配置从我们的 Docker 主机复制到镜像中(COPY

  • 上传并提取我们的网页根目录(ADD

  • 设置了我们网页根目录的正确所有权(RUN

  • 确保容器的80端口是开放的(EXPOSE

  • 确保supervisord是默认进程(ENTRYPOINT

  • 将配置文件标志传递给supervisordCMD

在进入下一节之前,你可以通过运行以下命令停止并删除容器,确保将容器 ID 替换为你自己的 ID:

docker container ps
docker container stop de9a26a1d149
docker container rm de9a26a1d149

然后运行以下命令删除我们创建的镜像:

docker image rm cluster

接下来,我们将回到我们的 WordPress 镜像并对其进行自定义。

自定义现有镜像

虽然官方镜像应该为你提供一个完全可用的镜像,但有时你可能需要安装额外的软件,在这种情况下,我们将使用官方 WordPress 镜像来安装 WordPress CLI。

注意

WordPress CLI 是一组命令行工具,允许您管理 WordPress 配置和安装;欲了解更多信息,请参见 wp-cli.org/

您可以在仓库中的/chapter02/wordpress-custom/文件夹中找到Dockerfile的副本,如您所见,我们只是在运行RUNCOPY指令:

# Adds wp-cli to the offical WordPress image
FROM wordpress:latest
MAINTAINER Russ McKendrick<russ@mckendrick.io>

# Install the packages we need to run wp-cli
RUN apt-get update &&\
apt-get install -y sudo less mysql-client &&\
curl -o /bin/wp-cli.pharhttps://raw.githubusercontent.com/wp-cli/builds/gh-pages/phar/wp-cli.phar

# Copy the wrapper for wp-cli and set the correct execute permissions
COPY wp /bin/wp
RUN chmod 755 /bin/wp-cli.phar /bin/wp

# Clean up the installation files
RUN apt-get clean &&rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/

您可以使用以下命令构建镜像:

docker image build -t wordpress-custom .

构建完成后,使用以下命令检查镜像:

docker image ls

然而,正如我们在本章早些时候发现的,使用 Docker Compose 启动 WordPress 更为简单,在我们执行此操作之前,先通过运行以下命令删除我们刚刚构建的镜像:

docker image rm wordpress-custom

Docker Compose 还可以触发构建。我们更新后的docker-compose.yml文件位于/chapter02/wordpress-custom/文件夹中,见下文:

version: "3"

services:
mysql:
     image: mysql
     volumes:
       - db_data:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: wordpress
       MYSQL_DATABASE: wordpress
wordpress:
depends_on:
       - mysql
     build: ./
     ports:
       - "8080:80"
     restart: always
     environment:
       WORDPRESS_DB_PASSWORD: wordpress

volumes:
db_data:

如您所见,它与我们原始的docker-compose.yml几乎完全相同,唯一的不同是现在我们有一行代码build: ./,而不是image: wordpress

要启动我们的 WordPress 安装,我们只需运行以下命令:

docker-compose up -d

这将拉取并构建容器镜像,完成后,您应该在终端看到类似以下的输出:

自定义现有图片

访问http://localhost:8080/应该会显示安装屏幕,但我们将输入一些命令来通过 WordPress CLI 配置 WordPress。

首先,让我们通过运行以下命令检查我们正在使用的 WordPress 版本:

docker-compose exec wordpress wp core version

这将连接到 WordPress 服务并运行wp core version命令,然后返回输出:

自定义现有图片

接下来,我们将使用wp core install命令安装 WordPress,并根据需要更改titleadmin_useradmin_passwordadmin_email的值:

docker-compose exec wordpress wp core install --url=http://localhost:8080/ --title=Testing --admin_user=admin --admin_password=adminpasswIt ord --admin_email=russ@mckendrick.io

当命令运行完成后,您应该会收到一条消息,显示成功:WordPress 安装成功

自定义现有图片

访问http://localhost:8080/应该会显示一个 WordPress 网站,而不是安装提示:

自定义现有图片

完成 WordPress 安装后,您可以通过运行以下命令停止并删除它:

docker-compose stop
docker-compose rm

现在我们知道如何构建镜像了,我们将探讨几种不同的共享方法。

共享你的镜像

Docker Hub 是一个中央平台,用于将 Docker 镜像保存在公有或私有仓库中。

Docker Hub 提供了多个功能,如 Docker 镜像的仓库、用户身份验证、自动化镜像构建、与 GitHub 或 Bitbucket 的集成,以及组织和小组管理。Docker Hub 的 Docker Registry 组件管理着这些仓库。

要使用 Docker Hub,您必须通过 hub.docker.com/ 提供的链接注册一个帐户。您可以更新Docker Hub ID电子邮件地址密码,如下所示:

共享你的镜像

完成注册过程后,你需要完成通过电子邮件收到的验证。电子邮件验证完成后,当你登录 Docker Hub 时,你将看到类似于以下截图的内容:

共享你的图片

正如你所看到的,我已经配置了一些自动构建,我们稍后会详细讲解,暂时我们将来看一下如何从本地 Docker 主机推送镜像。

首先,我们需要使用命令行中的 Docker 客户端登录到 Docker Hub,执行以下命令:

docker login

系统会提示你输入 Docker Hub 用户名和密码:

共享你的图片

现在我们已经准备好开始将镜像提交并推送到 Docker Hub。

我们将再次使用本章早些时候创建的Dockerfile来创建镜像。现在,让我们使用/chapter02/build_07_cmd中的Dockerfile创建 Docker 镜像,并将生成的镜像推送到 Docker Hub。

现在我们使用以下命令在本地构建镜像,确保用你自己的 Docker Hub 用户名替换我的用户名:

docker image build -t russmckendrick/exampleimage .

构建完成后,你可以通过使用以下命令检查镜像是否存在:

docker image ls

共享你的图片

由于我们已经登录,所需做的只是运行以下命令来推送新创建的镜像:

docker image push russmckendrick/exampleimage

共享你的图片

最后,我们可以验证镜像在 Docker Hub 上的可用性:

共享你的图片

在这里我可能需要发出一个警告:正如你刚刚体验过的,使用docker image push命令将镜像推送到 Docker Hub 非常容易;然而,非常容易不小心推送一些你可能不希望公开的内容。例如,通过在你的Dockerfile中使用简单的COPYADD指令,很容易将敏感信息(如密码凭证、证书密钥和非公开代码)打包到一个公开可访问的 Docker 镜像仓库中。

这也是为什么我更倾向于通过私有 Git 仓库与同事分享Dockerfiledocker-compose.yml文件,并提供一套好的指引。这样,他们可以查看自己将要运行的内容,甚至可以修改文件并与我分享。

总结

本章我们已经覆盖了很多内容。我们使用 Docker 命令行客户端启动并与容器交互。我们还使用 Docker Compose 定义了基于多个容器的应用程序,即 WordPress,并在 Docker Hub 上创建并发布了我们自己的 Docker 镜像。最后,我们自定义了官方的 WordPress Docker 镜像,添加了额外的功能。

我相信你会同意,到目前为止,使用 Docker 感觉非常直观;在我们的下一章中,我们将不再局限于本地 Docker 主机,而是与远程主机上的 Docker 安装进行交互。

第三章:Docker 在云中

第三个工具是 Docker 和 Docker Compose,两个我们在 第一章 安装 Docker 时安装的工具,就是 Docker Machine;这是一个命令行工具,允许你管理本地和远程的 Docker 主机。

在本章中,我们将通过使用 Docker Machine 在三个公共云驱动程序中配置 Docker 主机,来查看它们的基本使用方法。我们将会在以下环境中启动 Docker 主机:

所有操作只需一个命令。

Docker Machine

Docker Machine 可以连接到以下服务,配置 Docker 主机,并配置你的本地 Docker 客户端,使其能够与新启动的远程实例通信:

除了已提到的三家公共云提供商,Docker Machine 还支持:

此外,它还允许你使用 VirtualBox 本地启动 Docker 主机 - www.virtualbox.org/。如果你的本地工作站不符合 Docker for Mac 或 Windows 的最低规格,这将非常有用。

Digital Ocean 驱动程序

让我们开始在云中创建一些实例。首先,让我们在 Digital Ocean 启动一台机器。

在 Digital Ocean 上启动实例有两个先决条件,第一个是一个 Digital Ocean 账户,第二个是一个 API 令牌。

要注册 Digital Ocean 账户,请访问 www.digitalocean.com/ 并点击注册。注册或登录账户后,你可以通过点击顶部菜单中的API链接来生成 API 令牌。

要获取你的令牌,点击生成新令牌并按照屏幕上的说明操作。

提示

你只有一次机会记录下你的令牌;请确保将其保存在安全的地方,因为任何拥有该令牌的人都可以在你的账户中启动实例。

一旦获得令牌,你可以使用 Docker Machine 启动实例。为此,运行以下命令,确保将示例 API 令牌替换为你自己的令牌:

docker-machine create \
 --driver digitalocean \
 --digitalocean-access-token 14760f5bdee403cebb36117c22c83e5ee51188515f493a6c0d281c094c552536 \
 dotest

注意

请注意,这些示例中使用的令牌已被撤销。

这将在你的 Digital Ocean 账户中启动一个名为dotest的实例。

如果你查看 Digital Ocean 控制面板,你现在应该能看到 Docker Machine 创建的实例:

Digital Ocean 驱动程序

我们也可以通过以下命令确认我们的 Digital Ocean Docker 主机正在运行:

docker-machine ls

这将返回所有我们正在运行的机器,确认它们的状态、IP 地址、Docker 版本和名称。还有一列显示你的本地客户端与哪个 Docker Machine 管理的 Docker 主机进行通信:

Digital Ocean 驱动程序

默认情况下,你的本地 Docker 客户端配置为与我们的本地 Docker 安装进行通信;由于我们是通过 Docker for Mac 或 Windows 启动的本地 Docker 安装,或者你在 Linux 上安装了 Docker,Docker Machine 将不会列出它。

让我们修改它,使其能够与 Digital Ocean 实例交互。

为此,你需要更改一些本地环境变量;幸运的是,Docker Machine 提供了一种简单的方法来找出这些变量并进行更改。

要找出这些变量,只需要运行以下命令:

docker-machine env dotest

这将告诉你需要运行什么命令才能从default机器切换到dotest;最好的地方是命令本身将结果格式化为可执行的形式,所以如果我们再次运行命令,但这次以可以执行输出的方式:

eval $(docker-machine env dotest)

或者,如果你在 Windows 上使用 PowerShell 启动了实例,可以使用以下命令:

docker-machine env dotest | Invoke-Expression

现在,如果你使用 Docker Machine 列出所有主机,你会发现dotest环境现在是活动的:

Digital Ocean 驱动程序

现在我们已经激活了 Digital Ocean 实例,你可以在本地机器上运行docker container run命令,它们将在 Digital Ocean 实例上执行;让我们通过运行hello-world容器来测试这一点。

运行以下命令:

docker container run hello-world

你应该会看到镜像下载,并且如果你运行以下命令,hello-world容器的输出也会显示:

docker container ls –a

你应该能看到hello-world容器在几秒钟前已经退出。

你可以使用以下命令通过 SSH 连接到 Digital Ocean 实例:

docker-machine ssh dotest

登录后,运行docker container ls –a命令,演示你在本地运行的docker container run命令已经在 Digital Ocean 实例上执行。

这个设置的优点是,你不需要频繁通过 SSH 连接到实例。

注意

你可能注意到的一件事是,我们告诉 Docker Machine 的只是我们想使用 Digital Ocean 和我们的 API 令牌;我们没有告诉它要在哪个区域启动实例,想要什么配置,甚至是要使用哪个 SSH 密钥。

Docker Machine 有一些合理的默认设置,具体如下:

  • digitalocean-image = ubuntu-16-04-x64

  • digitalocean-region = nyc3

  • digitalocean-size = 512mb

由于我位于英国,我们来看一下如何更改 Docker Machine 启动的主机的区域和配置。

首先,我们应该通过运行以下命令移除dotest实例:

docker-machine rm dotest

这将终止在 NYC3 区域运行的 512 MB 实例。

提示

终止未使用的实例非常重要,因为它们在每小时运行时都会产生费用;记住,使用 Docker Machine 的一个关键优势是你可以快速启动实例,并尽可能减少交互。

现在我们已经移除了旧实例,接下来,我们需要为docker-machine命令添加一些额外的标志,以便在指定的区域和配置下启动新实例,我们将把新实例命名为douktest。更新后的docker-machine create命令现在看起来像这样(再次提醒,记得用你自己的 API 令牌替换示例令牌):

docker-machine create \
 --driver digitalocean \
 --digitalocean-access-token 14760f5bdee403cebb36117c22c83e5ee51188515f493a6c0d281c094c552536\
 --digitalocean-region lon1 \
 --digitalocean-size 1gb \
 douktest

你应该会看到与之前类似的命令输出;一旦实例被部署,你可以通过运行以下命令使其生效:

eval $(docker-machine env douktest)

当你进入控制面板时,你会注意到实例已经在指定的区域和期望的配置下启动:

数字海洋驱动程序

有关每个区域的详细信息以及每个区域中可用的机器类型,你可以通过运行以下命令查询 Digital Ocean API(同样记得替换 API 令牌):

curl -X GET -H "Content-Type: application/json" -H "Authorization: Bearer 14760f5bdee403cebb36117c22c83e5ee51188515f493a6c0d281c094c552536" "https://api.digitalocean.com/v2/regions" | python -mjson.tool

这将输出每个区域的信息。

最后,还有一件事;我们仍然没有找到关于 SSH 密钥的信息。每次运行 Docker Machine 时,都会为你启动的实例创建一个新的 SSH 密钥,并将其上传到提供商;每个密钥都会保存在用户主目录的.docker文件夹中。例如,可以通过运行以下命令找到douktest的密钥:

cd ~/.docker/machine/machines/douktest/

在这里,你还会找到用于验证 Docker 代理与实例上的 Docker 安装的证书及其配置:

数字海洋驱动程序

这就涵盖了在 Digital Ocean 中启动主机;那么,如何启动比 Hello World 容器更令人兴奋的东西呢?

没问题,让我们使用 Docker Compose 启动一个变化版本的 WordPress 堆栈,类似于我们在第二章中使用的那个,使用 Docker 启动应用程序。首先,进入/bootcamp/chapter03/wordpress文件夹,然后运行以下命令:

docker-machine ls

为了检查你的 Docker 客户端是否已配置为使用你的 Digital Ocean Docker 主机,一旦确认客户端使用了远程主机,只需运行以下命令:

docker-compose up -d

这将下载我们需要的镜像,并启动两个容器。此次你将能够通过port 80访问 Digital Ocean 主机上的 WordPress 安装。要找到你的主机的 IP 地址,你可以运行以下命令:

docker-machine ip douktest

或者在 Mac 或 Linux 机器上打开浏览器并访问你的安装页面,运行以下命令:

open http://$(docker-machine ip douktest)/

下面的终端会话展示了你可以从之前的命令中预期看到的输出:

The Digital Ocean driver

然后你将能够完成你的 WordPress 安装:

The Digital Ocean driver

提示

我不建议长时间将你的 WordPress 安装停留在安装页面上,因为有人可能会在你不知情的情况下完成安装并进行不当操作。

一旦你完成了 Digital Ocean 主机,运行以下命令来终止它:

docker-machine rm douktest

现在我们已经学会了如何在 Digital Ocean 上启动 Docker 主机,接下来让我们来了解一下 Amazon Web Services。

Amazon Web Services 驱动程序

如果你还没有Amazon Web ServicesAWS)账户,你应该在aws.amazon.com/注册一个;如果你是 AWS 新手,你将有资格获得他们的免费套餐,aws.amazon.com/free/

如果你对 AWS 不熟悉,我建议在开始本章内容之前先阅读 Amazon 的入门指南;你可以在docs.aws.amazon.com/gettingstarted/latest/awsgsg-intro/gsg-aws-intro.html找到该指南。

AWS 驱动程序与 Digital Ocean 驱动程序类似,具有一些合理的默认设置。我们不会详细讨论如何自定义 Docker Machine 启动的 EC2 实例,而是使用默认设置。对于 AWS 驱动程序,默认设置如下:

  • amazonec2-region = us-east-1(北弗吉尼亚)

  • amazonec2-ami = ami-fd6e3bea(Ubuntu 16.04)

  • amazonec2-instance-type = t2.micro

  • amazonec2-root-size = 16GB

  • amazonec2-security-group = docker-machine

请注意,如果amazonec2-security-group不存在,Docker Machine 会为你创建它;如果它已存在,则 Docker Machine 将使用现有规则。

在我们启动实例之前,我们还需要知道我们的AWS AccessAWS Secret密钥以及我们将要启动实例的VPC ID;要获取这些信息,请登录到 AWS 控制台,网址为console.aws.amazon.com/

大多数人会使用 AWS 根账户进行登录。由于 AWS 根账户不应该关联任何 Access 和 Secret 密钥,因此我们应该为 Docker Machine 添加一个单独的用户,方法是进入Services | IAM | Users,然后选择你的用户并进入Security Credentials标签。

在那里,您应该能看到一个按钮,上面写着添加用户,点击此按钮,您将进入一个页面,在该页面上可以设置您的用户详情。输入用户名 docker-machine,然后在访问类型中勾选程序化访问复选框:

Amazon Web Services 驱动程序

输入详情后,点击下一步:权限进入下一步。在权限页面上,点击直接附加现有策略,然后在策略类型搜索框中输入SystemAdministrator,按回车键以筛选策略:

勾选SystemAdministrator旁边的复选框,然后点击下一步:审查

Amazon Web Services 驱动程序

在审查页面上,点击创建用户,几秒钟后,您应该会收到确认,您的用户已成功创建。

确保点击下载 .csv,因为您将不再看到密钥访问密钥。现在您已经拥有了您的访问密钥 ID 和密钥访问密钥。

在找到您的 VPC ID 之前,您应该通过确保 AWS 控制台右上角显示的是北弗吉尼亚来确认您处于正确的区域;如果不是,从下拉列表中选择它。

注意

亚马逊将 Amazon VPC(Amazon 虚拟私有云)描述为允许您提供 AWS 云的逻辑隔离部分,您可以在其中启动您定义的虚拟网络中的资源。您可以完全控制虚拟网络环境,包括选择自己的 IP 地址范围、创建子网,以及配置路由表和网络网关。

一旦确认您处于正确的区域,转到服务,然后选择VPC,点击您的 VPC;您无需担心创建和配置 VPC,因为 Amazon 在每个区域都提供了默认的 VPC。选择该 VPC,您应该看到类似以下的内容:

Amazon Web Services 驱动程序

注意

记下 VPC ID;您现在应该有足够的信息来使用 Docker Machine 启动您的实例。为此,请运行以下命令:

docker-machine create \
 --driver amazonec2 \
 --amazonec2-access-key AKIAIP26OOEA3D4SLW5A \
 --amazonec2-secret-key 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 \
 --amazonec2-vpc-id vpc-35c91750 \
 awstest

如果一切顺利,您应该看到类似以下的输出:

Amazon Web Services 驱动程序

您还应该能够通过点击服务 | EC2 | 实例,在 AWS 控制台中看到一个已启动的 EC2 实例:

Amazon Web Services 驱动程序

您可能注意到,Docker Machine 创建了安全组并为实例分配了 SSH 密钥,而我们无需介入,这符合您无需成为专家即可配置启动 Docker 实例的环境的原则。

在我们终止实例之前,让我们将本地 Docker 客户端切换为使用 AWS 实例,并通过运行以下命令启动Hello World容器:

eval $(docker-machine env awstest)
docker-machine ls
docker container run hello-world
docker c
ontainer ls -a

如您所见,一旦使用 Docker Machine 启动实例并将本地 Docker 客户端切换到它,无论是在本地运行 Docker 还是在云提供商上运行,使用方法没有任何区别。

在我们开始累积成本之前,我们应该通过运行以下命令终止我们的测试 AWS 实例:

docker-machine rm awstest

然后在 AWS 控制台确认实例已正确终止:

亚马逊 Web 服务驱动程序

如果不这样做,EC2 实例将愉快地保持在那里,并且每小时将花费您 0.013 美元,直到终止。

注意

请注意,这不是 Docker for AWS,我们将在 第四章 中介绍该服务,Docker Swarm

Microsoft Azure 驱动程序

正如您可能从终端和浏览器截图中注意到的,到目前为止,我们一直在使用 Docker for Mac;现在让我们看看使用 Docker for Windows 的 Microsoft Azure 驱动程序。

首先,您需要一个 Microsoft Azure 账户;如果还没有,请访问 azure.microsoft.com/ 注册。一旦拥有账户,您需要开始的唯一信息是您的订阅 ID;您可以在门户的计费部分找到此信息。

一旦您有了订阅 ID,您可以使用 Azure 认证 Docker Machine,输入以下命令,确保用您自己的订阅 ID 替换:

docker-machine.exe create --driver azure --azure-subscription-id xxxxxxx-85a6-4ab4-b5c4-c18b54e014
98 azuretest

Microsoft Azure 驱动程序

您将收到一个激活码来授权 Docker Machine;请访问 aka.ms/devicelogin/ 并输入您收到的代码:

Microsoft Azure 驱动程序

点击 Continue 将带您到一个页面,显示 Docker Machine 将授予您的权限:

Microsoft Azure 驱动程序

一旦您点击 Accept,您将看到 Docker Machine 开始引导环境。此过程将花费几分钟;完成后,您应该看到类似以下输出:

Microsoft Azure 驱动程序

如您所见,Docker Machine 已执行以下操作:

  • 创建了一个资源组

  • 配置了网络安全组

  • 配置了一个网络子网

  • 创建了一个虚拟网络

  • 分配了一个公共 IP 地址

  • 创建了一个网络接口

  • 创建了一个存储帐户

  • 启动了一个虚拟机

如果您在 Azure 门户中查看资源组,您应该看到已启动和准备就绪的虚拟机:

Microsoft Azure 驱动程序

与 Mac 和 Linux 版本的 Docker Machine 一样,我们需要配置本地 Docker 客户端以与远程主机通信,为此我们需要运行以下命令:

docker-machine.exe env --shell powershell azuretest | Invoke-Expression

这相当于在 Mac 或 Linux 上运行以下命令:

eval $(docker-machine env azuretest)

您可以通过运行以下命令检查 Azure 是否处于活动状态:

docker-machine.exe ls

现在我们的客户端已连接到我们的 Azure 远程主机,我们可以通过运行以下命令启动Hello World容器:

docker container run hello-world
docker container ls -a

与 Docker Machine 的 Mac 和 Linux 版本一样,你可以通过运行以下命令 SSH 连接到你的 Azure 主机:

docker-machine.exe ssh azuretest

从下面的输出中你可以看到,我们可以看到 Hello World 容器:

The Microsoft Azure driver

从这里你可以像与任何其他 Docker 主机交互一样与 Azure 主机交互。一旦你准备好终止 Azure 主机,只需运行以下命令:

docker-machine.exe rm azuretest

移除主机及其相关资源需要一段时间,完成后你应该会看到类似以下内容:

The Microsoft Azure driver

在 Azure 门户中检查 Activity log,可以看到正在被移除的资源:

The Microsoft Azure driver

正如 PowerShell 输出中所强调的,最好检查所有内容是否已正确终止,最简单的方法是删除资源组本身,操作步骤是点击右侧 ... 的三个点,来删除 docker-machi ne 资源。

在你按照屏幕上的提示操作后,包括输入你选择删除的资源名称,你应该会收到资源组已被移除的确认信息。

尽管我们使用 Windows 查看 Azure,其他操作与 Mac 和 Linux 上的过程相同,唯一不同的是将本地客户端切换为使用远程主机。

注意

请注意,这不是 Azure 的 Docker,我们将在 第四章 中讨论该服务,Docker Swarm

参考资料

本章中使用的示例是启动 Ubuntu 实例。Docker Machine 还支持:

需要提到的另一件事是,Docker Machine 默认启用了崩溃报告功能,考虑到 Docker Machine 可以与不同的配置/环境组合一起使用,通知 Docker 任何问题是非常重要的,这有助于他们改进产品。

如果由于任何原因你想选择退出,那么运行以下命令将禁用崩溃报告:

mkdir -p ~/.docker/machine && touch ~/.docker/machine/no-error-report

获取更多关于 Docker Machine 的信息,请参考官方文档:

总结

从我们已经讨论过的例子可以看出,Docker Machine 是一个强大的工具,它让所有技能水平的用户都能轻松在云提供商中启动实例,而无需卷起袖子亲自配置服务器实例。

在我们接下来的章节中,我们将讨论如何在同一个云提供商中启动多个 Docker 主机,并配置 Docker Swarm 集群。

第四章:Docker Swarm

到目前为止,我们已经学会了如何使用 Docker for Mac、Docker for Windows 和 Docker Machine 在本地启动单个 Docker 主机,以及在 Linux 上本地使用 Docker。单个 Docker 主机非常适合本地开发,或启动一些测试实例,但随着你向生产环境推进,你需要减少单点故障的风险。

在这一章中,我们将变得更加冒险,创建一个 Docker 主机集群。Docker 提供了一种名为 Swarm 的工具,部署后它作为 Docker 客户端和 Docker 主机之间的调度器,根据调度规则决定在哪里启动容器。

我们将要讨论以下主题:

  • 手动启动 Docker Swarm 集群

  • 启动 Amazon Web Services 上的 Docker

  • 启动 Azure 上的 Docker

以及如何在我们的集群中启动容器。

手动创建 Swarm

在第三章开始时,Docker 在云端我们探讨了如何使用 Docker Machine 在 Digital Ocean 启动 Docker 主机。我们将再次从 Digital Ocean 开始,但这次我们将启动三个主机,然后在它们上面创建一个 Docker Swarm 集群。

首先,我们需要启动主机,为此,运行以下命令,记得将 Digital Ocean API 访问令牌替换为你自己的:

docker-machine create \
 --driver digitalocean \
 --digitalocean-access-token 57e4aeaff8d7d1a8a8e46132969c2149117081536d50741191c79d8bc083ae73 \
 swarm01

docker-machine create \
 --driver digitalocean \
 --digitalocean-access-token 57e4aeaff8d7d1a8a8e46132969c2149117081536d50741191c79d8bc083ae73 \
 swarm02

docker-machine create \
 --driver digitalocean \
 --digitalocean-access-token 57e4aeaff8d7d1a8a8e46132969c2149117081536d50741191c79d8bc083ae73 \
 swarm03

启动后,运行docker-machine ls应该会显示你的镜像列表。此外,这应该会在你的 Digital Ocean 控制面板中有所体现:

手动创建 Swarm

现在我们有了 Docker 主机,并需要为集群中的每个节点分配角色。Docker Swarm 有两种节点角色:

  • 管理节点:管理节点是一个分配任务给工作节点的节点,你与 Swarm 集群的所有交互都将针对一个管理节点进行。你可以拥有多个管理节点,但在本例中我们将只使用一个。

  • 工作节点:工作节点接受管理节点分配的任务,所有的服务都会在这里启动。我们将在集群配置完成后,进一步深入讨论服务。

在我们的集群中,swarm01将是管理节点,swarm02swarm03将是我们的两个工作节点。我们将使用docker-machine ssh命令直接在三个节点上执行命令,从配置管理节点开始。

注意

请注意,文档中的命令仅适用于 Mac 和 Linux,Windows 上的命令将在本节末尾讲解。

在初始化管理节点之前,我们需要捕获swarm01的 IP 地址作为命令行变量:

managerIP=$(docker-machine ip swarm01)

现在我们有了 IP 地址,运行以下命令检查它是否正确:

echo $managerIP

然后,配置管理节点,运行以下命令:

docker-machine ssh swarm01 docker swarm init --advertise-addr $managerIP

然后,你会收到确认信息,表明swarm01现在是管理节点,并附有将工作节点添加到集群的运行指令:

手动创建一个 Swarm

你不需要记下这些指令,因为我们将以稍微不同的方式运行命令。

为了添加我们的两个工作节点,我们需要以类似于获取管理节点 IP 地址的方式来捕获加入令牌,使用$managerIP变量;为此,请运行:

joinToken=$(docker-machine ssh swarm01 docker swarm join-token -q worker)

再次,echo该变量以检查它是否有效:

echo $joinToken

现在是时候通过运行以下命令将我们的两个工作节点添加到集群中:

docker-machine ssh swarm02 docker swarm join --token $joinToken $managerIP:2377
docker-machine ssh swarm03 docker swarm join --token $joinToken $managerIP:2377

你应该能看到类似以下的终端输出:

手动创建一个 Swarm

使用以下命令将本地 Docker 客户端连接到管理节点:

eval $(docker-machine env swarm01)

然后再次运行docker-machine ls,可以看到。正如从主机列表中看到的那样,swarm01现在是激活的,但SWARM列中没有任何内容;为什么会这样?

令人困惑的是,Docker Swarm 集群有两种不同的类型,一种是由 Docker Machine 管理的传统 Docker Swarm,另一种是由 Docker Engine 本身管理的新的 Docker Swarm 模式。

我们已经启动了一个 Docker Swarm 模式集群。这现在是启动 Swarm 的首选方式,传统的 Docker Swarm 正在慢慢退役。

要获取 Swarm 集群中节点的列表,我们需要运行以下命令:

docker node ls

手动创建一个 Swarm

有关每个节点的信息,你可以运行以下命令(--pretty标志会渲染 Docker API 的 JSON 输出):

docker node inspect swarm01 --pretty

你会获得关于主机的大量信息,包括它是一个管理节点,并且它已经在 Digital Ocean 上启动。运行相同的命令,但针对工作节点,会显示类似的信息:

docker node inspect swarm02 --pretty

然而,由于该节点不是管理节点,因此该部分缺失。

在我们开始将服务部署到集群之前,应该先了解如何在 Windows 上使用 Docker Machine 启动集群。我们将使用 PowerShell 来执行此操作,而不是更传统的 Windows CMD 提示符,然而,即使是使用 PowerShell,命令也有一些差异,原因在于 PowerShell 和 bash 之间的差异。

首先,我们需要启动三个主机:

docker-machine.exe create --driver digitalocean --digitalocean-access-token 57e4aeaff8d7d1a8a8e46132969c2149117081536d50741191c79d8bc083ae73 swarm01
docker-machine.exe create --driver digitalocean --digitalocean-access-token 57e4aeaff8d7d1a8a8e46132969c2149117081536d50741191c79d8bc083ae73 swarm02
docker-machine.exe create --driver digitalocean --digitalocean-access-token 57e4aeaff8d7d1a8a8e46132969c2149117081536d50741191c79d8bc083ae73 swarm03

一旦三个主机都启动并运行:

手动创建一个 Swarm

你可以通过运行以下命令来创建管理节点:

$managerIP = $(docker-machine.exe ip swarm01)
echo $managerIP
docker-machine.exe ssh swarm01 docker swarm init --advertise-addr $managerIP

手动创建一个 Swarm

一旦你有了管理节点,你可以添加两个工作节点:

$joinIP = "$(docker-machine.exe ip swarm01):2377"
echo $joinIP
$joinToken = $(docker-machine.exe ssh swarm01 docker swarm join-token -q worker)
echo $joinToken
docker-machine.exe ssh swarm02 docker swarm join --token $joinToken $joinIP
docker-machine.exe ssh swarm03 docker swarm join --token $joinToken $joinIP

然后配置本地 Docker 客户端以使用管理节点,并检查集群状态:

docker-machine.exe env  swarm01 | Invoke-Expression
docker-machine.exe ls
docker node ls

到目前为止,无论你使用哪个操作系统,你应该已经在 Digital Ocean 上有了一个三节点的 Docker Swarm 集群,我们现在可以开始将服务部署到集群中。

启动一个服务

与其使用docker container run命令启动容器,不如创建一个服务。服务定义了一个任务,管理节点将该任务传递给某个工作节点,然后启动一个容器。

让我们启动一个名为 cluster 的简单服务,它使用的是我们在第二章中查看过的镜像,使用 Docker 启动应用程序

docker service create --name cluster -p:80:80/tcp russmckendrick/cluster

就这样,我们现在应该在三个节点中的一个上运行了一个容器。要检查服务是否在运行并获取更多关于服务的信息,请运行以下命令:

docker service ls
docker service inspect cluster --pretty

现在我们已经确认服务正在运行,你可以打开浏览器,输入你三个节点中任意一个的 IP 地址(可以通过运行docker-machine ls获得)。Docker Swarm 的一个特点就是它的路由网格。

启动服务

路由网格?当我们使用-p:80:80/tcp标志暴露端口时,我们做的不仅仅是将主机的端口80映射到容器的端口80,我们实际上是在集群内的所有主机上创建了一个 Swarm 负载均衡器,并将其绑定到80端口。然后,Swarm 负载均衡器将请求定向到集群中的容器。

运行下面的命令,应该能显示哪些任务正在各个节点上运行,记住,任务是由服务启动的容器:

docker node ps swarm01
docker node ps swarm02
docker node ps swarm03

像我一样,你可能在swarm01上运行着单一任务:

启动服务

我们可以通过扩展服务来增加更多任务,使得事情变得更加有趣,简单地运行以下命令来扩展和检查我们的服务:

docker service scale cluster=6
docker service ls
docker service inspect cluster --pretty

正如你所看到的,我们现在在集群服务中有 6 个任务正在运行:

启动服务

检查节点时,应该能看到任务在我们的三个节点之间均匀分布:

docker node ps swarm01
docker node ps swarm02
docker node ps swarm03

启动服务

在浏览器中点击刷新也应该更新 Docker 镜像下的主机名,在 Mac 和 Linux 中查看这一点的另一种方法是运行以下命令:

curl -s http://$(docker-machine ip swarm01)/ | grep class=

从以下终端输出可以看出,我们的请求在正在运行的任务之间进行了负载均衡:

启动服务

在我们终止 Docker Swarm 集群之前,让我们看看另一种启动服务的方法,在此之前,我们需要移除当前正在运行的服务,为此,只需运行以下命令:

docker service rm cluster

服务移除后,我们可以启动一个新的堆栈。

启动堆栈

这可能会让人感到困惑。如果服务等同于运行容器,那么堆栈就是运行一组服务,就像你使用 Docker Compose 启动多个容器一样。实际上,你可以使用 Docker Compose 文件启动堆栈,只需进行一些调整。

让我们再来看一下如何启动我们的集群应用程序。你可以在/bootcamp/chapter04/cluster/文件夹中的仓库里找到我们将要使用的 Docker Compose 文件,在我们查看docker-compose.yml文件的内容之前,先启动堆栈。为此,请运行以下命令:

docker stack deploy --compose-file=docker-compose.yml cluster

你应该会收到确认,表示堆栈的网络已与服务一起创建。你可以通过运行以下命令列出堆栈启动的服务:

docker stack ls

然后通过运行以下命令查看服务中的任务:

docker stack ps cluster

启动堆栈

你可能会惊讶地发现,服务仅在 swarm02swarm03 上启动了任务。为了说明原因,让我们打开 docker-compose.yml 文件:

version: "3"
services:
  cluster:
    image: russmckendrick/cluster
    ports:
      - "80:80"
    deploy:
      replicas: 6
      restart_policy:
        condition: on-failure
placement:
        constraints:
          - node.role == worker

正如你所看到的,docker-compose.yml 文件看起来就像我们在第二章中介绍的内容,使用 Docker 启动应用程序,直到我们到达 deploy 部分。

你可能已经发现我们为什么只在两个工作节点上运行任务的原因,正如你在 placement 部分看到的,我们告诉 Docker 只在角色为工作节点的节点上启动任务。

接下来,我们定义了一个 restart_policy,它告诉 Docker 如果任何任务停止响应应该怎么办,在我们的案例中,我们告诉 Docker 在 on-failure 时重启任务。最后,我们告诉 Docker 在我们的服务中启动六个 replicas

让我们通过终止我们其中一个工作节点来测试重启策略。虽然通过排空节点有一种优雅的方式,但更有趣的做法是直接终止该节点,运行以下命令即可:

docker-machine rm swarm03

在删除主机后,立即运行 docker stack ps 集群会显示 Docker 还没有同步更新。

稍等几秒钟后运行 docker stack ps,你会看到我们仍然有六个任务在运行,但从终端输出中可以看出,它们现在都在 swarm02 上运行,而新的任务替代了之前的任务,显示为 已关闭

我们的应用程序应该仍然可以通过在浏览器中输入 swarm01swarm02 的 IP 地址访问。完成剩余两个主机的操作后,你可以通过运行以下命令将它们移除:

docker-machine rm swarm01 swarm02

到目前为止,我们已经在 Digital Ocean 上手动创建了 Docker Swarm 集群,我相信你同意到目前为止,这个过程非常简单,特别是考虑到集群技术的强大,你现在可能已经开始思考如何部署服务和堆栈。

在接下来的几个部分,我们将研究 Docker for Amazon Web Services 和 Docker for Azure,以及 Docker 如何利用这两大公共云服务提供的一系列支持功能。

Docker for Amazon Web Services

Docker for AWS 是一个已经为 Amazon Web Services 调优的 Swarm 集群。

注意

AWS CloudFormation 是一个模板引擎,允许你以可控和可预测的方式定义 AWS 基础设施和资源。

AWS CloudFormation 模板可以在以下位置找到:

editions-us-east-1.s3.amazonaws.com/aws/stable/Docker.tmpl

如你所见,内容相当丰富,下面的图片是上述模板的可视化展示——虽然你可能无法看到图片中的所有内容,但你应该能大致了解 Docker 提供的CloudFormation模板的复杂性。

Docker for Amazon Web Services

如你所见,模板为你完成了所有繁重的工作,这意味着你几乎不需要做什么,唯一需要做的事就是创建一个 SSH 密钥。为此,登录到 AWS 控制台 console.aws.amazon.com/,从屏幕顶部的服务菜单中选择EC2,EC2 仪表板打开后,点击左侧菜单中的密钥对

在这里你可以选择创建密钥对导入密钥对。一旦你创建或导入了 SSH 密钥,就可以开始启动 Docker for Amazon Web Services 集群,接下来,从服务菜单中选择CloudFormation

点击创建新堆栈将带你到一个页面,在该页面上你可以定义你的堆栈,由于 Docker 已经为我们完成了这项工作,你只需要输入堆栈定义文件的 URL:

editions-us-east-1.s3.amazonaws.com/aws/stable/Docker.tmpl

在下面的区域中,找到指定一个 Amazon S3 模板 URL,确保在你输入 URL 的上方选择了单选框后,点击下一步

Docker for Amazon Web Services

你将进入的下一页是定义你的堆栈外观的页面,对于这个快速演示,我使用了以下配置,大致匹配我们在 Digital Ocean 启动的手动 Swarm 集群的大小:

  • 堆栈名称:Bootcamp

  • Swarm 管理节点数量? 1

  • Swarm 工作节点数量? 3

  • 使用哪个 SSH 密钥? <你自己的 SSH 密钥>

  • 启用每日资源清理?

  • 使用 Cloudwatch 进行容器日志记录?

  • Swarm 管理节点实例类型? t2.micro

  • 管理节点临时存储卷大小? 20

  • 管理节点临时存储卷类型 standard

  • 代理工作节点实例类型? t2.micro

  • 工作节点临时存储卷大小? 20

  • 工作节点临时存储卷类型:standard

填写完所有细节后,点击页面底部的下一步按钮。接下来你将进入一个包含额外选项的页面,例如标签,我们不需要在此输入任何内容,因此只需点击下一步按钮。

最后一页是我们在正式启动堆栈之前对所有内容进行复审的页面。如果你需要更改任何值,可以点击上一步,一旦你对填写的详情满意,就需要勾选我确认 AWS CloudFormation 可能会创建 IAM 资源,然后点击创建按钮。

这将立即开始部署你的 Docker for Amazon Web Service 集群资源,你可以通过保持Events标签页打开来检查部署状态。

点击refresh按钮应该会显示类似以下屏幕的内容:

Docker for Amazon Web Services

启动堆栈需要几分钟时间,完成后你应该能看到Status显示为CREATE_COMPLETE。看到这个后,点击Outputs标签页:

Docker for Amazon Web Services

在这里你应该能看到四条消息,第一条包含 Elastic Load Balancer 的 URL,第二条是关于你的实例可用性的消息,最后你应该能看到关于Managers的消息,其中包含一个链接——点击它。

这将带你到 EC2 仪表盘的实例页面,你还会注意到我们的单一管理节点已被过滤,选择它后可以看到该实例的公共 URL 和 IP 地址等信息:

Docker for Amazon Web Services

为了与我们的集群进行交互,我们将通过 SSH 连接到管理节点,你需要使用docker用户名。我使用了以下命令:

ssh docker@54.194.20.19

如果你下载了密钥对,则可以使用类似如下的命令:

ssh docker@54.194.20.19 -I ~/path/to/keypair.pem

一旦登录,你应该能看到类似如下的内容:

Docker for Amazon Web Services

运行docker node ls可以看到我们有三个工作节点和我们当前登录的一个管理节点:

Docker for Amazon Web Services

现在让我们启动我们的集群应用程序,由于我们登录的是一个非常基础的操作系统,实际上正如你从运行命令的输出中看到的那样:

cat /etc/*release

我们已登录到一个 Alpine Linux 服务器:

Docker for Amazon Web Services

Git 默认没有安装,所以让我们切换到 root 用户并使用 APK 安装 Git 包:

sudo su –
apk update
apk add git

现在 Git 已安装,我们可以克隆 Bootcamp 的 repo:

git clone https://github.com/russmckendrick/bootcamp
.git

一旦 Git 安装完成,我们就可以使用以下命令启动我们的堆栈:

docker stack deploy --compose-file=/root/bootcamp/chapter04/cluster/docker-compose.yml cluster
docker stack ls
docker stack ps cluster

你应该看到类似以下的输出:

Docker for Amazon Web Services

现在我们的堆栈已启动,你可以通过 CloudFormation 堆栈的Outputs标签页中的 Elastic Load Balancer URL 访问它,在我的案例中 URL 是(请注意,我的 URL 现在已失效):

http://bootcamp-elb-1145454691.eu-west-1.elb.amazonaws.com/

如下图所示,页面按预期显示,容器的主机名是内容所在的主机名:

Docker for Amazon Web Services

与之前一样,运行 curl 命令访问 Elastic Load Balancer URL,可以看到容器的主机名在变化(记得替换成你自己的 URL):

curl -s http://bootcamp-elb-1145454691.eu-west-1.elb.amazonaws.com/ | grep class=

在我们拆解集群之前,还有最后一个需要快速查看的地方,如果在启动 Docker for Amazon Web Service 堆栈时我们选择了Use Cloudwatch for container logging

这个选项将您的容器日志流式传输到 Amazon 自己的中央日志服务,要查看日志,请返回 AWS 控制台并从 服务 菜单中选择 Cloudwatch,一旦 Cloudwatch 仪表板加载完成,点击左侧菜单中的 日志,然后点击 Bootcamp-lg 链接,在这里您应该能看到由 docker stack create 命令启动的容器列表:

Docker for Amazon Web Services

点击其中一个日志流将显示该容器记录的所有内容,在我们的例子中,应该只是来自 supervisord 进程的大量信息:

Docker for Amazon Web Services

要拆除我们的 Docker for Amazon Web Services 集群,请返回 CloudFormation 仪表板,选择您的堆栈,然后从 操作 菜单中选择 删除堆栈。这将弹出一个提示框,点击 是,删除 按钮,您的堆栈将立即开始删除。

删除所有资源需要几分钟,确保所有资源都被删除非常重要,因为 Amazon 采用按需付费模式,这意味着如果某个资源(如 EC2 实例)正在运行,您将为其付费,因此建议您保持窗口打开,并确保删除操作成功。

说到费用,您可能已经注意到,当我们启动堆栈时,有一个链接指向预估费用,这个链接会将 CloudFormation 模板中定义的所有资源通过 Amazon 的简单成本计算器应用程序进行处理,我们的四个实例的 Docker for Amazon Web Services 估计每月花费 $66.98。

如您所见,我们在几乎没有任何努力的情况下启动了一个相当复杂的配置,Docker 也将同样的方法应用于 Microsoft Azure,让我们现在来看一下。

Docker for Azure

Docker for Azure 需要在部署之前做一些更多的准备工作。幸运的是,Docker 已经尽可能简化了这个过程,通过提供作为容器的 Azure 命令行界面。

我们需要为部署创建一个服务配置文件和资源组,要做到这一点,只需运行以下命令:

docker run -ti docker4x/create-sp-azure bootcamp-sp bootcamp-resource westus

这将下载 Azure CLI。我们传递给命令的三个变量如下:

  • 服务配置文件的名称

  • 资源组的名称

  • 我们希望在哪个区域启动我们的集群

几秒钟后,您应该会收到一个 URL 和一个身份验证代码:

Docker for Azure

在浏览器中打开 aka.ms/devicelogin/ 并输入您获得的代码,在我的例子中是 DQQXPYV7G

Docker for Azure

如上图所示,应用程序正在自我识别为Microsoft Azure 跨平台命令行界面,因此我们知道请求是正确的;点击继续将要求您登录。登录后,您将收到确认,表示您的请求已被授权并且应用程序已登录。

一两秒钟后,您应该看到命令行开始响应,首先它会询问您使用哪个订阅:

Docker for Azure

选择正确的订阅,然后让命令完成,它大约需要五分钟时间。在过程结束时,您应该会收到您的访问凭证,记下这些信息,因为您将需要它们来启动您的堆栈:

Docker for Azure

现在我们已经完成了准备工作,是时候启动 Docker for Azure 模板了,您可以通过以下网址查看该模板:

download.docker.com/azure/stable/Docker.tmpl

要启动它,只需在浏览器中访问以下网址:

portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fdownload.docker.com%2Fazure%2Fstable%2FDocker.tmpl

您应该已经通过授权命令行界面登录,如果没有登录,请登录,您将被引导到一个页面,要求提供一些有关您希望堆栈外观的信息:

  • 订阅 <选择您的订阅>

  • 使用现有 <选择您在上一步生成的资源组>

  • 位置 <此项将被灰显>

  • AD 服务主体应用程序 ID <输入您在上一步生成的 AD 服务主体应用程序 ID>

  • AD 服务主体应用程序密钥:<输入您在上一步生成的 AD 服务主体应用程序密钥>

  • 启用系统清理:否

  • 管理节点数:1

  • 管理虚拟机大小:Standard_D2_v2

  • SSH 公钥:<输入您的公钥,见下文>

  • Swarm 名称:dockerswarm

  • 工作节点数:3

  • 工作节点虚拟机 大小:Standard_D2_v2

要在 Mac 或 Linux 上快速复制您的 SSH 公钥到剪贴板,请运行以下命令(如果需要,修改路径为您的密钥路径):

cat ~/.ssh/id_rsa.pub | pbcopy

确保勾选我同意上述条款和条件旁边的框,确认表单内容无误后,点击购买。这将启动您的部署,过程将持续几分钟,完成后您的仪表板上会添加一个新的资源,取决于您现有的资源,您可能需要滚动查看或刷新页面。

点击您的资源卡片中的查看更多将显示 Docker for Azure 创建的所有资源列表:

Docker for Azure

你应该能看到分配的两个公共 IP 地址,一个是externalLoadBalancer-public-ip,另一个是externalSSHLoadBalancer-public-ip,请记下这两个地址,我们需要用到它们,找到 IP 地址的方法是点击资源查看更多信息。

现在我们知道了两个 IP 地址,我们可以通过 SSH 进入我们的管理节点,SSH 监听在端口50000,所以要 SSH 到节点,请运行以下命令,确保使用externalSSHLoadBalancer-public-ip地址:

ssh docker@52.160.107.69 -p50000

登录后,运行docker node ls,你应该能看到你的三个工作节点,如果没有看到,可能它们仍在启动中,再等几分钟:

Docker for Azure

和 Amazon Web Services 上的 Docker 一样,你是通过 SSH 进入了一个 Alpine Linux 主机。

这意味着,要安装 Git,我们需要切换到 root 用户,并使用 APK 进行安装:

sudo su -
apk update
apk add git

一旦安装了 Git,我们可以使用以下命令检出 Bootcamp 仓库:

git clone https://github.com/russmckendrick/bootcamp.git

然后使用以下命令启动我们的应用程序栈:

docker stack deploy --compose-file=/root/bootcamp/chapter04/cluster/docker-compose.yml cluster

并确保一切正常运行,通过执行以下命令:

docker stack ls
docker stack ps cluster

将 externalLoadBalancer-public-ip 地址输入浏览器后,应该会显示你的集群应用程序。再次使用 CURL 命令应该会显示流量正在分发到我们的容器(记得使用你自己的负载均衡器 IP 地址):

curl -s http://52.160.105.160/ | grep class=

就这样,我们成功部署了 Docker for Azure 和我们的集群应用程序。最后要做的就是删除资源,以避免产生任何意外费用,操作步骤是:从左侧菜单中选择资源组,然后点击bootcamp-resource条目旁边的三个点,选择删除

删除所有资源和组大约需要 10 分钟,但最好保持 Azure 门户打开,直到删除过程完成,因为你不希望产生额外的费用。

根据资源运行的时长,这整个演示的费用应该不会超过$0.10。

总结

我猜到到本章结束时,事情可能变得非常可预测,没有真正的惊喜,这也是故意的。正如你所体验到的,Docker 提供了一个非常强大的集群解决方案,一旦部署,无论你在什么平台上启动集群,它都会以一致和可预测的方式运行。

还有一件重要的事情我们还没有提到,那就是为容器提供持久存储。这一点很重要,尤其是在集群中,因为它不仅允许我们的容器在主机之间迁移,还引入了我们可以对应用程序进行滚动更新的方式。

在下一章,我们将了解 Docker 网络和卷插件。

第五章:Docker 插件

在 2014 年 DockerCon Europe 大会期间,举行了一个圆桌讨论,讨论了 Docker 生态系统的现状,识别出了以下问题及可能的解决方案:

Docker 目前面临的问题是,随着它转变为一个平台,它被认为威胁到了自己的生态系统。提议的解决方案是,Docker 将自己的附加功能作为后期绑定的、可组合的、可选的扩展发布,并允许其他供应商也这样做。Docker 称之为“内置电池但可拆卸”。

在 2015 年 DockerCon Seattle 大会期间,Docker 宣布在实验分支中提供插件功能,公告以博客文章的形式发布,可以在blog.docker.com/2015/06/extending-docker-with-plugins/找到。

正如你从这篇文章中看到的,Docker 提供了一个解决方案,第三方可以替换核心功能。现在,用户可以运行docker volumedocker network命令,并通过驱动程序选项让 Docker 调用外部组件,从而添加核心 Docker 引擎之外的功能,同时保持高度的兼容性。

在本章中,我们将介绍两个不同的 Docker 插件,第一个是名为REX-Ray的卷插件,第二个是名为Weave的网络插件。

REX-Ray 卷插件

到目前为止,我们一直在使用本地主机上可用的本地存储,正如第四章中提到的,Docker Swarm 在你可能需要在多个主机之间移动存储时并不太有用,无论是因为你托管了一个集群,还是因为主机本身存在问题。

在这个例子中,我们将启动一个在 Amazon Web Services 上的 Docker 实例,安装一个名为 REX-Ray 的卷插件,它由 EMC 编写,然后启动我们的 WordPress 示例,但这次我们将把 Amazon Elastic Block Storage 卷附加到我们的容器上。一旦配置完成,我们将把容器移动到第二台主机上,以展示数据的持久性。

REX-Ray 支持公共云和 EMC 自身系列的几种存储类型,具体如下:

该驱动程序正在积极开发中,并且支持的存储类型也在不断增加,同时也在进行工作,将该驱动程序迁移到 Docker 的新插件系统中。

在安装 REX-Ray 之前,我们需要在 Amazon Web Services 中有一个 Docker 主机,要启动一个,请使用以下命令。你可以参考 第二章 中的 Amazon Web Services 驱动程序部分,使用 Docker 启动应用程序,了解如何生成你的访问密钥和秘密密钥,并找到你的 VPC ID。记得将 access-keysecret-keyvpc-id 替换为你自己的:

docker-machine create \
 --driver amazonec2 \
 --amazonec2-access-key AKIAJ3GYNKVTEWNMFDHQ \
 --amazonec2-secret-key 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 \
 --amazonec2-vpc-id vpc-35c91750 \
 awstest

现在你已经启动了实例,可以在 AWS 控制台中看到它:

REX-Ray 卷插件

我们需要安装 REX-Ray 插件。由于 REX-Ray 支持 Docker 的新插件格式,这意味着我们需要运行 docker plugin 命令。首先,我们需要配置本地 Docker 客户端,通过运行以下命令来连接到 AWS 主机:

eval $(docker-machine env awstest)

现在我们已经连接上来安装插件,只需要运行以下命令,EBS_ACCESSKEYEBS_SECRETKEY 变量与我们在 Docker Machine 中使用的相同,替换为你自己的密钥:

docker plugin install rexray/ebs \
EBS_ACCESSKEY=AKIAJ3GYNKVTEWNMFDHQ \
EBS_SECRETKEY=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

在安装插件之前,系统会要求你确认是否允许插件访问 Docker 安装的各个部分,提示时回答“是”(y),插件将被下载并安装。

插件安装完成后,我们需要创建两个卷,一个用于存放 WordPress 数据,另一个用于存放 MySQL 数据库。要创建卷,运行以下命令:

docker volume create --driver rexray/ebs --name dbdata
docker volume create --driver rexray/ebs --name wpdata

你可以在以下终端中看到前面提到的命令正在运行:

REX-Ray 卷插件

你也应该能够通过点击 AWS 控制台中 EC2 部分左侧菜单中的 Volumes 来查看你的两个卷:

REX-Ray 卷插件

现在我们有了两个卷,我们需要启动 WordPress,为此我们将使用可以在仓库中的 /bootcamp/chapter05/wordpress-rexray/ 找到的 Docker Compose 文件。

docker-compose.yml 文件中可以看出,我们正在构建一个安装了 wp-cli 的 WordPress 镜像:

version: "3"

services:
mysql:
     image: mysql
     volumes:
       - dbdata:/var/lib/mysql
     restart: always
     environment:
       MYSQL_ROOT_PASSWORD: wordpress
       MYSQL_DATABASE: wordpress
wordpress:
depends_on:
       - mysql
     build: ./
     volumes:
       - wpdata:/var/www/html
     ports:
       - "80:80"
     restart: always
     environment:
       WORDPRESS_DB_PASSWORD: wordpress

volumes:
dbdata:
      external:
        name: dbdata
wpdata:
      external:
        name: wpdata

从文件的末尾你也可以看到,我们告诉 Docker Compose 使用我们已经通过 docker volume create 命令创建的两个外部卷。

要构建我们的 WordPress 镜像并启动容器,运行以下命令:

docker-compose up -d

你可以通过运行以下命令检查你的容器是否正在运行:

docker-compose ps

现在,我们的两个 WordPress 容器已经启动,你可以通过运行以下命令快速安装 WordPress(根据需要更新变量):

$awshost = "$(docker-machine ip awstest)"
docker-compose exec wordpress wp core install --url=http://$(awshost)/ --title=Testing --admin_user=admin --admin_password=adminpassword --admin_email=russ@mckendrick.io

安装完成后,你应该看到一条消息,表示 成功:WordPress 安装成功。这意味着你可以通过运行以下命令在浏览器中打开安装页面:

open http://$(docker-machine ip awstest)

这应该会显示你现在熟悉的 WordPress 网站:

REX-Ray 卷插件

现在让我们对 WordPress 安装进行一些更改,以确保当我们在主机之间迁移应用程序时,一切正常工作。我们将用烟花替换植物的图片。为了做到这一点,我们需要自定义我们的主题,要进入主题编辑页面,请运行以下命令:

open "http://$(docker-machine ip awstest)/wp-admin/customize.php?return=%2Fwp-admin%2Fthemes.php"

系统会提示你使用管理员用户名和密码登录,如果你按照安装步骤操作,用户名和密码应该是admin / adminpassword,如果你输入了自己的用户名和密码,请使用它们。

打开页面后,点击左侧菜单中的“Header Media”(头部媒体)。向下滚动,找到左侧菜单中显示的Add new image(添加新图片),按照屏幕上的提示上传、裁剪并设置新的头图,你可以在代码库中找到一张名为fireworks.jpg的图片,或者使用你自己的图片。完成后,点击Save & Publish(保存并发布)。

返回到你的网站主页,应该可以看到你新的头图:

REX-Ray volume plugin

在移除 Docker 主机之前,我们需要记下它的 IP 地址,方法是运行以下命令:

echo $(docker-machine ip awstest)

记下 IP 地址,因为我们稍后会用到,在我的例子中,IP 地址是54.173.130.142

现在让我们通过以下命令移除我们的主机:

docker-machine rm awstest

主机移除后,我们的两个卷在 AWS 控制台中会显示为可用

REX-Ray volume plugin

这是我们的 WordPress 和数据库数据,要在新的 Docker 主机上访问它,我们需要首先启动一个主机。为此,请再次运行以下命令,记得将凭证和 VPC ID 替换为你自己的:

docker-machine create \
 --driver amazonec2 \
 --amazonec2-access-key AKIAJ3GYNKVTEWNMFDHQ \
 --amazonec2-secret-key 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 \
 --amazonec2-vpc-id vpc-35c91750 \
 awstest2

一旦新的 Docker 主机启动并运行,运行以下命令切换我们的本地客户端并安装 REX-Ray:

eval $(docker-machine env awstest2)
docker plugin install rexray/ebs \
 EBS_ACCESSKEY=AKIAJ3GYNKVTEWNMFDHQ \
 EBS_SECRETKEY=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

一旦 REX-Ray 安装完成,我们需要让它识别我们现有的两个卷,方法是简单地运行以下命令:

docker volume create --driver rexray/ebs --name dbdata
docker volume create --driver rexray/ebs --name wpdata

不用担心,它不会覆盖我们现有的卷,它只会让 Docker 知道它们的存在,因为 REX-Ray 使用的是你分配给卷的名称,而不是唯一 ID。如果它遇到一个已有名称的卷,它会认为这就是你要使用的卷,所以在命名卷时要小心,因为它们会被附加到运行中的容器上。

你可能会注意到这次命令执行得更快了,这是因为卷已经存在,不需要重新创建。运行:

docker volume ls

应该显示我们的两个卷仍然存在,和之前一样。

现在我们需要启动 WordPress,方法是运行以下命令:

docker-compose up -d

如果你现在尝试访问你的 WordPress 网站,你会看到一个非常破损的网站,虽然有内容,但没有样式或图片。

这是因为数据库仍然引用我们已终止的 Docker 主机的 IP 地址,指向数据库。运行以下命令时,请确保将命令中的 IP 地址替换为你之前 Docker 主机的 IP 地址(记住我的 IP 地址是54.173.130.142):

docker-compose exec wordpress wp search-replace 54.173.130.142 $(docker-machine ip awstest2)

你应该能看到数据库中每个表的列表,并确认它已替换为新 Docker 主机的 IP 地址的实例数量。

通过运行以下命令访问你的新 WordPress 安装:

open http://$(docker-machine ip awstest2)

应该显示你的封面图像完好无损,并且 WordPress 安装和你离开时一样,除了 IP 地址的变化。

测试完成后,你可以通过运行以下命令来移除你的安装:

docker-compose stop
docker-compose rm
docker volume rmdbdata
docker volume rmwpdata
docker-machine rm awstest2

注释

你可能会注意到,当你运行docker volume rm命令时,并没有提示你确认操作,所以要小心。

检查你的 AWS 控制台,应该能确认你的 Docker 主机已经终止,并且你的两个卷已经被删除。

Weave 网络插件

Weave 是最初的 Docker 插件之一,实际上,他们曾参与关于 Docker 插件功能的圆桌讨论,而 Weave 也包含在本章开头提到的原始插件公告中。

Weave 将他们的网络插件描述为:

快速、简单、安全地在任何环境中(本地、云端或混合环境)对容器进行网络连接和集群管理,零代码或配置。

任何曾经与软件定义网络(SDN)工作过的人都知道,这是一个相当大胆的声明,特别是 Weave 正在创建一个网状网络。要全面了解这意味着什么,我推荐阅读 Weave 自己的概述,可以在www.weave.works/docs/net/latest/how-it-works/找到。

不再详细讨论,让我们动手进行安装。首先,使用 Docker Machine 在 DigitalOcean 上启动两个独立的 Docker 主机。

为了增加趣味性,我们将在纽约市启动一个主机,在伦敦启动另一个主机。由于这两个主机将作为独立主机使用,因此不需要配置 Docker Swarm——这通常是多主机网络配置 Docker 时所需要的。

要启动纽约市的 Docker 主机,运行:

docker-machine create \
 --driver digitalocean \
 --digitalocean-access-token 57e4aeaff8d7d1a8a8e46132969c2149117081536d50741191c79d8bc083ae73 \
 --digitalocean-region nyc1\

 weave-nyc

然后在伦敦的 Docker 主机上运行:

docker-machine create \
 --driver digitalocean \
 --digitalocean-access-token 57e4aeaff8d7d1a8a8e46132969c2149117081536d50741191c79d8bc083ae73 \
 --digitalocean-region lon1 \
 weave-lon

现在我们已经有了两个 DigitalOcean 主机,我们需要让 Weave 运行起来。在撰写本文时,Weave 还没有完成过渡到 Docker 原生插件架构,预计很快就会完成,因此我们将使用控制脚本来配置 Weave。

首先,我们需要在纽约市的 Docker 主机上下载控制脚本:

docker-machine ssh weave-nyc 'curl -L git.io/weave -o /usr/local/bin/weave; chmoda+x /usr/local/bin/weave'

下载完成后,我们可以使用以下命令启动 Weave:

docker-machine ssh weave-nyc weave launch --password 3UnFh4jhahFC

这将下载并启动多个 Docker 主机上的容器,下载完成后,Weave 将被配置,密码也会设置好,这意味着如果你想将主机添加到网络中,你需要提供一个有效的密码。

如果你没有定义密码,那么任何人都可以连接到你的 Weave 网络。如果你知道你的主机是在一个隔离的封闭网络中运行,那么这样做没问题。然而,由于我们是在公网上发送流量,因此我们设置了一个密码。

你可以通过运行以下命令来检查容器:

docker $(docker-machine config weave-nyc) container ps

现在我们已经启动了所需的三个容器,是时候在伦敦 Docker 主机上安装 Weave,并将其连接到我们的 NYC Docker 主机了。执行以下命令来完成安装:

docker-machine ssh weave-lon 'curl -L git.io/weave -o /usr/local/bin/weave; chmoda+x /usr/local/bin/weave'
docker-machine ssh weave-lon weave launch --password 3UnFh4jhahFC

三个容器启动后,只需运行以下命令连接到我们的 NYC Docker 主机:

docker-machine ssh weave-lon weave connect "$(docker-machine ip weave-nyc)"

一旦我们的第二个主机配置完成,你可以通过运行以下命令来检查 Weave 网状网络的状态:

docker-machine ssh weave-nyc weave status

WeaveNetwork Plugin

正如你从上面的终端输出中看到的,我们有五个服务正在运行,除了提供密码外,我们没有配置任何服务。

由于我正在运行 Mac OS 系统,我还将本地安装 Weave,相同的指令也适用于 Linux 系统。

以下命令将安装 Weave 控制脚本,该脚本用于在你的 Docker for Mac 安装中启动容器,并连接到我们的 Weave 网状网络:

sudo curl -L git.io/weave -o /usr/local/bin/weave; sudochmoda+x /usr/loca
l/bin/weave
weave launch --password 3 UnFh4jhahFC
weave connect "$(docker-machine ip weave-nyc)"

一旦安装并连接,运行weave status本地命令应该显示现在有 3 个对等节点和 6 个已建立的连接:

WeaveNetwork Plugin

现在我们有了三个 Docker 主机:

  • 一个位于纽约,由 Digital Ocean 托管

  • 一个位于伦敦,由 Digital Ocean 托管

  • 我们的本地 Docker 主机运行在 Docker for Mac(或 Linux)上

所有这些操作都使用名为weave的网络,且使用weavemesh驱动。你可以通过运行以下命令来确认这一点:

docker network ls
docker $(docker-machine config weave-nyc) network ls
docker $(docker-machine config weave-lon) network ls

你应该看到类似以下终端输出的内容:

WeaveNetwork Plugin

现在我们已经准备好开始将容器启动到我们的 Weave 网络中,并展示它们如何彼此通信。

注意

Netcat 是一个允许你通过 TCP 或 UDP 在网络上进行读写的服务。

让我们从在纽约启动一个运行 Netcat(nc)的容器开始。每次请求发送到 4242 端口时,nc都会回应Hello from NYC!!!

docker $(docker-machine config weave-nyc) container run -itd \
 --name=nyc \
 --net=weave \
 --hostname="nyc.weave.local" \
 --dns="172.17.0.1" \
 --dns-search="weave.local" \
 alpine nc -p 4242-ll -e echo 'Hello from NYC!!!'

正如你从 Docker 命令中看到的,我们传递了相当多的不同选项,我们告诉容器使用哪个网络,并且配置了容器内的 DNS 解析器,并设置了主机名为nyc.weav e.local

WeaveNetwork Plugin

现在我们已经启动了 NYC 容器,首先要做的是检查我们是否能从伦敦 Docker 主机 ping 通,执行以下命令来完成此操作:

docker $(docker-machine config weave-lon) container run -it --rm \
 --name=ping \
 --net=weave \
 --dns="172.17.0.1" \
 --dns-search="weave.local" \
 alpine sh -c 'ping -c3 nyc.weave.local'

这将向nyc.weave.local发送三个 ping 请求,所有请求应当得到回应:

WeaveNetwork Plugin

现在我们已确认可以 ping 通 NYC 容器,我们需要连接到4242端口,并检查是否得到预期的回应:

docker $(docker-machine config weave-lon) container run -it --rm \
 --name=conect \
 --net=weave \
 --dns="172.17.0.1" \
 --dns-search="weave.local" \
 alpine sh -c 'echo "Where are you?" | ncnyc.weave.local 4242'

你应该收到信息Hello from NYC!!!

WeaveNetwork Plugin

现在让我们使用以下命令在本地 Docker 主机上启动一个容器:

docker container run -itd \
 --name=mac \
 --net=weave \
 --hostname="mac.weave.local" \
 --dns="172.17.0.1" \
 --dns-search="weave.local" \
 alpine nc -p 4242 -ll -e echo 'Hello from Docker for Mac!!!'

WeaveNetwork Plugin

如之前所示,我们将对本地容器进行简单的 ping 测试:

docker $(docker-machine config weave-nyc) container run -it --rm \
 --name=ping \
 --net=weave \
 --dns="172.17.0.1" \
 --dns-search="weave.local" \
 alpine sh -c 'ping -c3 mac.weave.local'

正如预期的那样,我们收到了回应:

WeaveNetwork Plugin

刚开始时有点慢,但最终会变得更好。现在我们知道可以 ping 我们的本地容器,接下来连接到端口 4242 并检查响应。首先,从我们的纽约 Docker 主机:

docker $(docker-machine config weave-nyc) container run -it --rm \
 --name=conect \
 --net=weave \
 --dns="172.17.0.1" \
 --dns-search="weave.local" \
 alpine sh -c 'echo "Where are you?" | ncmac.weave.local 4242'

然后,从我们的伦敦 Docker 主机:

docker $(docker-machine config weave-lon) container run -it --rm \
 --name=conect \
 --net=weave \
 --dns="172.17.0.1" \
 --dns-search="weave.local" \
 alpine sh -c 'echo "Where are you?" | ncmac.weave.local 4242'

如以下终端输出所示,我们得到了预期的结果:

WeaveNetwork 插件

要整理你的本地 Docker 主机,请运行以下命令:

docker container stop mac
docker container rm mac
weave stop
sudorm -f /usr/local/bin/weave

然后,要终止我们的两个 Digital Ocean 主机,请运行:

docker-machine stop weave-lon weave-nyc
docker-machine rm weave-lon weave-nyc

虽然这些测试在视觉上没有像 REX-Ray Volume 插件的演示那么引人注目,但正如你所看到的,Weave 是一个非常强大的软件定义网络,非常容易配置。

根据经验,这种组合是很难实现的,因为大多数 SDN 解决方案在安装、配置和维护上都非常复杂。

我们仅仅触及了 Weave 的一些可能性。有关完整的功能列表以及一些高级用例的说明,请参考docs.weave.works/weave/latest_release/features.html

总结

希望你现在开始看到不同类型插件的使用场景。例如,一个开发者可以在本地卷上工作,但对于生产流量,你可能希望有某种共享存储或块存储,使得多个 Docker 主机上的容器都能访问。

使用插件,在不对用户工作流进行任何实质性更改的情况下,这一点是可能的,因为你清楚 Docker 如何处理通过 docker volume create 命令创建的卷。

如前所述,Docker 正在将遗留插件过渡到新的架构,遗留插件的列表可以在以下网址找到:docs.docker.com/engine/extend/legacy_plugins/,而使用新架构的插件可以在store.docker.com/search?q=&type=plugin找到。

在下一章中,我们将讨论如何监控你的容器,以及如果出现问题该怎么办。

第六章:故障排除与监控

在本章中,我们将查看在故障排除容器时会非常有用的命令,所有我们将查看的命令都是 Docker 引擎的核心命令,我们还将研究一种调试 Dockerfile 的方法。

完成故障排除命令后,我们将了解如何使用 cAdvisor 监控容器,并通过 Prometheus 后端和 Grafana 仪表板进行展示——别担心,这并不像听起来那么复杂。

注意

由于我们将暴露服务,其中一些使用默认凭证,因此我建议您在本章中使用本地的 Docker 安装。

容器故障排除

计算机程序(软件)有时不能按预期行为运行。这通常是由于代码错误或开发、测试和部署系统之间的环境变化造成的。Docker 容器技术通过容器化所有应用程序的依赖项,尽可能消除开发、测试和部署之间的环境问题。然而,仍然可能由于代码错误或内核行为的变化而出现异常,这需要调试。调试是软件工程领域中最复杂的过程之一,而在容器化环境下,由于隔离技术的存在,调试变得更加复杂。在本节中,我们将学习如何使用 Docker 原生工具以及外部工具调试容器化应用的一些技巧和窍门。

最初,Docker 社区的许多人分别开发了自己的调试工具,但后来 Docker 开始支持原生工具,如 exectoplogsevents 等等。在本节中,我们将深入了解以下 Docker 工具:

  • exec

  • ps

  • top

  • stats

  • events

  • logs

  • attach

我们还将考虑调试 Dockerfile。

exec 命令

docker container exec 命令为用户提供了极大的帮助,尤其是对于那些在容器中部署自己 web 服务器或后台运行其他应用的用户。

现在,运行容器中的 SSH 守护进程不再需要登录。

首先,启动一个容器:

docker container run -d --name trainingapp training/webapp:latest 

exec 命令

第二,运行 docker container ps 命令以获取容器 ID。现在你已经拥有了容器 ID,可以使用容器 ID 或我们为其命名的 trainingapp 来运行 docker container exec 命令登录容器:

docker container exec -it 32005e837724 bash

注意

请注意,并非每个容器都安装了 bash,有些容器(如 Alpine Linux)默认没有 bash,而是使用基于 bash 的 sh。

需要注意的是,docker container exec命令只能访问正在运行的容器,所以如果容器停止工作,你需要重新启动停止的容器才能继续。docker container exec命令通过 Docker API 和 CLI 在目标容器的命名空间中生成一个新的进程。

容器的命名空间是将容器彼此隔离的机制,例如,你可以有多个容器同时运行相同的进程,但由于这些进程是启动在每个容器的命名空间内,它们彼此隔离。一个好的例子是 MySQL 进程,在传统服务器上尝试运行多个 MySQL 服务器进程意味着你需要在不同的端口上启动进程,使用不同的锁、PID 和日志文件以及不同的初始化脚本。

由于 Docker 隔离了每个 MySQL 服务器进程,你只需要担心的是,如果你在主机机器上暴露了 MySQL 端口,那么不要将其分配给与另一个容器相同的端口。

所以,如果你在目标容器内运行ps -aef命令,结果如下:

exec 命令

这里,python app.y是目标容器中已经运行的应用程序,而docker container exec命令在容器内部添加了bash进程。如果你运行kill -9 59(将59替换为你自己bash进程的 PID),你将自动从容器中注销。

建议你仅将docker container exec命令用于监控和诊断目的,我个人相信每个容器一个进程的概念,这是广泛强调的最佳实践之一。

ps 命令

ps命令在容器内部可用,用于查看进程的状态。这与 Linux 环境中的标准ps命令类似,并不是我们在 Docker 主机上运行的dockercontainerps命令。

这个命令在 Docker 容器内运行:

ps 命令

使用ps --help <simple|list|output|threads|misc|all>ps --help <s|l|o|t|m|a>获取更多帮助信息。

top 命令

你可以使用以下命令从 Docker 主机机器运行 top 命令:

docker container top CONTAINER [ps OPTIONS]

这会列出容器中运行的进程,而无需登录容器,如下所示:

top 命令

在容器内,top 命令提供关于 CPU、内存和交换区使用情况的信息,就像任何正常的 Linux 主机一样:

top 命令

如果在容器内运行top命令时出现error - TERM environment variable not set错误,请按照以下步骤进行解决。

运行echo$TERM,如果返回结果是dumb,那么运行以下命令:

export TERM=dumb 

这将解决你的错误,你可以运行top命令。

stats 命令

docker container stats命令允许你查看 Docker 主机上容器的内存、CPU 和网络使用情况,如下所示。运行以下命令:

docker container stats 32005e837724

给你以下输出:

stats 命令

你可以运行stats命令来查看多个容器的使用情况:

docker container stats 32005e837724 5e33f02f5fd2 7c9cf27ff46a

stats 命令

从 Docker 1.5 版本开始,你可以访问容器统计的只读参数。这将简化容器的 CPU、内存、网络 I/O 和块 I/O。

这有助于你选择资源限制和性能分析。Docker stats 工具仅为运行中的容器提供这些资源使用详情。

你可以通过以下网址的端点 API 获取详细信息:docs.docker.com/engine/api/v1.26/

Docker 事件命令

Docker 容器将报告以下实时事件:createdestroydieexportkillommpauserestartstartstopunpause。让我们pauseunpause我们的容器:

Docker 事件命令

如果指定了镜像,它还将报告untagdelete事件。

使用多个过滤器将作为AND操作处理,例如:

docker events --filter container=32005e837724 --filter event=pause --filter event=unpause --since 12h 

上述命令将显示容器a245253db38b过去 12 小时内所有的pauseunpause事件:

Docker 事件命令

目前,支持的过滤器有containereventimage

logs 命令

此命令在不登录容器的情况下获取容器的日志。它批量检索执行时存在的日志。这些日志是 STDOUT 和 STDERR 的输出。一般用法如下:

docker container logs [OPTIONS] CONTAINERID

--follow选项将继续提供输出,直到 Docker 日志命令终止,并实时打印任何新的日志条目,-t将提供时间戳,--tail=<number of lines>将显示容器日志消息的行数:

docker container logs 32005e837724

logs 命令

docker container logs -t 32005e837724

logs 命令

我们在前面的章节中也使用了docker container logs命令来查看数据库容器的日志。

attach 命令

此命令附加到正在运行的容器,并且在你想要实时查看stdout中写入的内容时非常有用,接下来我们启动一个新的测试容器,它向stdout输出内容:

docker container run -d --name=newtest alpine /bin/sh -c "while true; do sleep 2; df -h; done"

现在我们可以使用以下命令附加到容器以查看输出:

docker container attach newtest

默认情况下,该命令会附加stdin并将信号代理到远程进程。可以使用选项来控制这两种行为。要从进程中分离,请使用默认的Ctrl + Q组合键。

调试 Dockerfile

我们在 Dockerfile 中设置的每条指令都会作为一个单独的临时镜像构建,用于其他指令在其基础上构建。

在仓库的 /bootcamp/ch apter06/debu g 目录下有一个 Dockerfile:

FROM alpine
RUN ls -lha /home
RUN ls -lha /vars
CMD echo Hello world

使用以下命令构建镜像:

docker image build

给你以下输出:

调试 Dockerfile

所以,我们的 Dockerfile 中有一个错误。你可能会注意到输出中有一行显示 --->5f828f86eaa4,这实际上是一个镜像文件,它是在成功执行 RUN ls -lha / home 行之后构建的。

调试 Dockerfile

这意味着我们可以使用这个镜像来启动一个容器:

docker container run -it --name=debug 5f828f86eaa4 /bin/sh

注意

注意,由于我们使用的是 Alpine Linux 作为基础镜像,因此我们使用 /bin/sh 而不是 /bin/bash

然后我们可以调试我们的应用程序,在这种情况下非常简单:

调试 Dockerfile

调试是一个分析正在发生的事情的过程,每种情况都不同,但通常我们开始调试的方法是通过手动执行失败的指令并理解错误。当我使指令工作时,我通常会退出容器,更新我的 Dockerfile,然后重复这个过程,直到我得到一个可以正常工作的结果。

注意,当导致错误的那一行被更正(通过提供正确的行 RUN ls -lha /var)并且我们再次尝试构建时,Docker 并不会为成功的那一步创建一个新的镜像:

调试 Dockerfile

一旦构建完成,临时镜像会被删除,我们只剩下最终的镜像:

调试 Dockerfile

这是一个相当简单的例子,但它应该能让你了解如何调试更复杂的 Dockerfile。

容器监控

在上一节中,我们介绍了如何使用 Docker 内置的 API 通过运行 docker container statsdocker container top 命令来获取容器运行时的资源信息。现在,我们将看看如何通过使用 cAdvisor(Google 提供的工具)将其提升到一个新层次。

Google 对 cAdvisor 的描述如下:

cAdvisor(容器顾问)为容器用户提供了关于其正在运行的容器的资源使用情况和性能特征的理解。它是一个运行中的守护进程,收集、汇总、处理并导出有关正在运行的容器的信息。具体来说,对于每个容器,它保持资源隔离参数、历史资源使用情况、完整历史资源使用的直方图以及网络统计信息。这些数据由容器导出,并且是机器范围的。

该项目最初作为 Google 的一个内部工具,用于获取有关使用其自有容器栈启动的容器的深入信息。

注意

Google 自己的容器堆栈被称为 "Let Me Contain That For You" 或简称 lmctfy。lmctfy 项目的工作已被作为 Google 在 libcontainer 上的移植功能,这也是 Open Container Initiative(OCI)的一部分。关于 lmctfy 的更多细节可以在 github.com/google/lmctfy/ 找到。

cAdvisor 是用 Go 语言编写的 (golang.org);你可以选择自行编译二进制文件,或者使用通过容器提供的预编译二进制文件,这些文件可通过 Google 自家的 Docker Hub 账户获取。你可以在 hub.docker.com/u/google/ 找到它。

安装后,cAdvisor 会在后台运行并捕获类似于 dockercontainer stats 命令的指标。我们将在本章后续内容中详细了解这些指标及其含义。

cAdvisor 会收集这些指标,并与主机机器的指标一起,通过一个简单且易于使用的内置网页界面进行展示。

安装 cAdvisor 有多种方法;最简单的方式是下载并运行包含预编译 cAdvisor 二进制文件的容器镜像:

docker network create monitoring
docker container run -d \
 --volume=/:/rootfs:ro \
 --volume=/var/run:/var/run:rw \
 --volume=/sys:/sys:ro \
 --volume=/var/lib/docker/:/var/lib/docker:ro \
 --publish=8080:8080 \
 --name=cadvisor \
 google/cadvisor:latest

现在,你应该在主机上成功启动了 cAdvisor 容器。

在查看统计数据之前,我们先更详细地了解一下 cAdvisor,讨论为什么我们要将所有选项传递给容器。

cAdvisor 二进制文件设计为与 Docker 二进制文件一起在主机上运行,因此通过在容器中启动 cAdvisor,我们将二进制文件隔离在它自己的环境中。为了让 cAdvisor 访问它在主机上所需的资源,我们必须挂载多个分区,并且还需要为容器提供特权访问权限,让 cAdvisor 二进制文件认为它是在主机上执行的。

现在,我们已经让 cAdvisor 正常运行;接下来我们需要做什么来配置服务以开始收集指标?

简单来说,什么也没有发生。当你启动 cAdvisor 进程时,它会立即开始轮询主机,查看有哪些容器在运行,并收集关于正在运行的容器和主机机器的信息。

cAdvisor 应该运行在 8080 端口上;如果你打开 http://localhost:8080/,你应该能看到 cAdvisor 的 logo 和主机机器的概览:

监控容器

这个初始页面会实时流式传输主机机器的统计信息,每个部分会在你开始深入查看容器时重复显示。首先,我们来看看使用主机信息的每个部分。

概览部分给出了你系统的全貌;它使用了仪表盘,让你能够快速了解哪些资源已经接近极限。在以下截图中,CPU 使用率很低,文件系统的使用也相对较低;然而,我们已经使用了 66% 的可用 RAM:

监控容器

接下来是显示过去一分钟 CPU 利用率的图表:

监控容器

以下是每个术语的含义:

  • 总使用情况:此项显示所有核心的总使用情况

  • 每个核心的使用情况:此图表显示每个核心的使用情况

  • 使用情况细分:此项显示了所有核心的总体使用情况,但会将其分解为由内核和用户拥有的进程所使用的内存

内存部分分为两部分。图表显示了所有进程在主机或容器中使用的总内存量;这是热内存和冷内存的总和。热内存是当前工作集;即内核最近访问过的页面。冷内存是长时间没有被访问的页面,如果需要,可以回收。

使用情况细分提供了主机机器的总内存或容器中的配额的可视化表示,以及总使用量和热使用量。

网络部分显示了过去一分钟的进出流量。你可以使用左上角的下拉框更改接口。

还有一个图表显示任何网络错误。通常情况下,这个图表应该是平的。如果不是,那么说明你的主机或容器可能存在性能问题。

最后一部分,文件系统,提供了文件系统使用情况的详细信息。在以下屏幕截图中,/dev/vda1 是启动分区,overlay 是运行容器的主要文件系统。

现在我们可以查看我们的容器了。在页面顶部有一个链接,显示你正在运行的容器,名称为 Docker 容器;你可以点击链接,也可以直接访问 http://localhost:8080/docker/

页面加载后,你应该能够看到所有正在运行的容器列表,以及有关 Docker 进程的详细概览,最后是你下载的镜像列表。

子容器显示了你的容器列表;每个条目都是一个可点击的链接,点击后将带你到一个页面,页面上会显示以下详细信息:

  • 隔离

    • CPU:此项显示容器的 CPU 配额;如果没有设置任何资源限制,你将看到主机的 CPU 信息

    • 内存:此项显示了容器的内存配额;如果你没有设置任何资源限制,容器将显示为无限配额

  • 使用情况

    • 概览:此项显示了仪表盘,你可以快速查看是否接近任何资源限制

    • 进程:此项显示仅选定容器的进程

    • CPU:此项显示仅限于你容器的 CPU 利用率图表

    • 内存:此项显示了容器的内存利用情况

驱动状态部分提供了关于主 Docker 进程的基本统计信息,同时也包含主机机器内核、主机名以及底层操作系统的信息。

它还提供关于容器和镜像的总数的信息。你可能会注意到,镜像的总数远远超出你预期的数字;这是因为它将每个文件系统都当作一个独立的镜像来计算。

最后,你将得到一个主机上可用的 Docker 镜像列表。它列出了仓库、标签、大小以及镜像的创建时间,还包括镜像的唯一 ID。这让你了解镜像的来源(仓库)、你下载的是哪个版本的镜像(标签)以及镜像的大小(大小)。

这一切都很好,那么问题是什么呢?

所以,你也许在想,浏览器中提供的这些信息真的很有用;能够以易读的格式看到实时的性能指标,确实是一个很大的优势。

使用 cAdvisor 的 Web 界面最大的缺点是,正如你可能已经注意到的,它只显示一分钟前的度量数据;你可以清晰地看到信息在实时消失。

就像一块窗户玻璃提供了容器的实时视图一样,cAdvisor 是一个非常棒的工具;不过,如果你想查看超过一分钟的数据,恐怕就没办法了。

也就是说,除非你配置某个地方存储所有数据;这就是 Prometheus 的作用。那么,Prometheus 到底是什么呢?它的开发者这样描述它:

Prometheus 是一个开源的系统监控和告警工具包,最初由 SoundCloud 开发。自 2012 年推出以来,它已经成为 SoundCloud 为新服务提供监控的标准,并且在外部使用和贡献方面不断增长。

好的,但这和 cAdvisor 有什么关系呢?嗯,Prometheus 有一个非常强大的数据库后端,它将导入的数据存储为事件的时间序列。

cAdvisor 默认做的一件事是将它捕获的所有度量数据显示在一个页面上,路径是/metrics;你可以在http://localhost:8080/metricson看到我们安装的 cAdvisor。每次加载页面时,度量数据都会更新,你应该能看到如下内容:

监控容器

正如你在前面的截图中看到的,这只是一页长的原始文本。Prometheus 的工作方式是,你配置它以在用户定义的间隔(比如每五秒)抓取/metrics URL;这些文本的格式是 Prometheus 可识别的,并被导入到 Prometheus 的时间序列数据库中。

这意味着,使用 Prometheus 强大的内置查询语言,你可以开始深入挖掘你的数据。接下来,让我们看看如何启动和运行 Prometheus。

首先,仓库中有一个工作配置文件,路径是/bootcamp/chapter06/prometheus/,你需要确保自己处于这个文件夹中,因为我们将从这里挂载配置文件:

docker container run -d \
 --volume=$PWD/prometheus.yml:/etc/prometheus/prometheus.yml \
 --publish=9090:9090 \
 --network=monitoring \
 --name=prometheus \
 prom/prometheus:latest

监控容器

我们启动 Prometheus 时使用的配置文件如下所示:

global:
scrape_interval: 15s # By default, scrape targets every 15 seconds.
external_labels:
    monitor: 'Docker Bootcamp'
scrape_configs:
  - job_name: 'cadvisor'
scrape_interval: 5s
static_configs:
      - targets: ['cadvisor:8080']

由于我们已经在监控网络中启动了 Prometheus 容器,我们的安装将能够从 http://cadvisor:8080/ 获取指标,同时请注意,我们没有在 URL 中添加 /metrics,这是 Prometheus 自动添加的。

在浏览器中打开 http://localhost:9090/targets 应该会显示类似以下内容:

监控容器

此外,状态菜单中有以下信息页面的链接:

  • 运行时信息与构建信息:这显示了 Prometheus 已运行并获取数据的时间,如果你配置了终端点,还会显示你运行的 Prometheus 版本信息

  • 命令行标志:这显示了所有运行时变量及其值

  • 配置:这是我们在容器启动时注入的配置文件副本

  • 规则:这是我们注入的任何规则的副本;这些规则将用于警报

由于目前我们只有少数容器在运行,接下来让我们启动一个运行 Redis 的容器,以便我们可以开始查看 Prometheus 内置的查询语言。

我们将使用官方的 Redis 镜像,作为示例我们不需要传递任何用户变量:

docker container run -d --name my-redis-server redis

现在,我们有一个名为 my-redis-server 的容器正在运行。cAdvisor 应该已经将容器的指标暴露给 Prometheus;让我们来看看。

在 Prometheus Web 界面中,点击页面顶部菜单中的 图表 链接。你会看到一个文本框,你可以在其中输入查询。首先,让我们查看 Redis 容器的 CPU 使用情况。

在框中输入以下内容:

container_cpu_usage_seconds_total{job="cadvisor",name="my-redis-server"}

然后,点击执行后,你应该会看到两个结果,这些结果列在页面的控制台标签中。如果你还记得,cAdvisor 会记录容器访问的每个 CPU 核心的使用情况,因此我们会返回两个值,一个是 cpu00,另一个是 cpu01。点击 图表 链接将显示一段时间内的结果:

监控容器

如前面的截图所示,我们现在可以查看过去 5 分钟的使用情况图表,这大约是我在生成图表之前启动 Redis 实例的时间。

如你所见,绘图并不是 Prometheus 的强项。幸运的是,Grafana 已经能够将 Prometheus 作为数据源使用一段时间了,现在让我们启动一个 Grafana 容器:

docker container run -d \
 --publish=3000:3000 \
 --network=monitoring \
 --name=grafana \
grafan
a/grafana:latest

监控容器

容器启动后,在浏览器中访问 http://localhost:3000/,你会被提示登录,默认的用户名和密码是 admin / admin

现在你已经登录,你应该会看到如下页面:

监控容器

如你所料,我们需要点击 添加数据源,然后输入以下信息:

  • 名称:prometheus

  • 类型:<从下拉列表中选择 Prometheus>

  • 网址http://prometheus:9090

  • 访问:<从下拉列表中选择代理>

保持其他设置不变,然后点击添加,等待一两秒,您的数据源应该已经成功添加,并且连接测试通过。

现在我们已经添加了数据源,可以添加一个仪表板。有很多现成的仪表板可用,我们将使用由 Brian Christner 发布的仪表板,您可以在 grafana.net/dashboards/179/ 找到。

要导入仪表板,点击左上角的Grafana 标志,在打开的菜单中选择仪表板,然后选择导入。在弹出的对话框中,输入仪表板的 URL grafana.net/dashboards/179/,然后点击加载按钮。

这将加载仪表板配置,下一页将提供两个选项,名称已经填写好,只需从下拉菜单中选择prometheus,然后点击导入按钮。

导入后,您应该会看到一个与以下内容类似的仪表板(我已为截图进行了调整):

监控容器

您可能会注意到从上面的屏幕中,我们现在已经从 cAdvisor 存储在 Prometheus 中的数据显示出超过一个小时的数据。

值得指出的是,目前 Docker 的实验版本已内置 Prometheus 端点,类似于 cAdvisor 的方式。一旦发布稳定版,我预计这将成为监控 Docker 主机的一个极好的开箱即用解决方案。

然而,这只是监控容器的其中一种方式,还有许多其他工具可以使用,包括自托管和作为云中的软件即服务运行的工具,例如:

总结

希望现在您应该对如何开始排查容器问题有所了解,无论是构建容器、检查日志、连接容器深入问题,还是收集性能指标。

在下一章,也是我们的最后一章中,我们将探讨一些 Docker 和我们在本章及前五章中介绍的技术的不同场景和用例。

第七章. 整合所有内容

在本章,我们将回顾如何将前面各章所学的内容结合起来,并探讨它如何与您的开发和部署工作流契合。

此外,我们还将讨论如何向他人最好地描述 Docker,通常你会发现人们会认为 Docker 容器就像虚拟机一样。我们还将探讨 Docker 的优势以及一些使用案例。

工作流

本书的前五章介绍了一个典型的工作流,涵盖了从开发到生产环境中使用 Docker 容器的全过程:

  • 本地开发与打包(第一章,本地安装 Docker 和 第二章,使用 Docker 启动应用程序

  • 暂存与远程测试(第三章,Docker 在云端

  • 生产环境(第四章,Docker Swarm 和 第五章,Docker 插件

  • 持续支持(第六章,故障排除与监控

在前几章中,我们学习了如何在本地安装和使用 Docker,通常在开发应用程序或软件堆栈时,开发人员或系统管理员会先在本地进行测试。

一旦应用程序/堆栈完全开发完成,你可以通过 Docker Hub 共享它,作为公有或私有镜像,或者如果你的镜像包含一些你不想通过第三方分发的内容,你可以托管自己的 Docker 注册表。

一旦你有了打包好的镜像,你可能需要其他人来测试它。因为你的镜像已在注册表中可用,所以你的同事或朋友可以拉取你的镜像,并按你预期的方式在他们自己的机器上本地运行,而不必担心安装和配置你的应用程序或软件堆栈。

如果你需要让其他人进行远程测试,可以在公有云提供商上启动一个 Docker 主机,并迅速在那部署你的应用程序或软件堆栈。

一旦大家都满意,你可以在 Docker Swarm 运行的多主机集群中部署你的应用程序/软件堆栈服务,这意味着你的服务将运行在一个高可用且易于维护的环境中。作为服务进行部署,还可以让你利用 Swarm 内置的服务更新功能轻松地推出应用程序或软件堆栈的更新。

如果你需要在容器或主机之间共享或持久化存储,可以安装众多卷插件中的一个;同样,如果你需要比 Swarm 提供的多主机网络更高级的功能,也没问题,可以用网络插件替换它,记住,“内置电池,但可以替换”。

最后,如果你需要调试你的镜像或正在运行的容器,你可以使用第六章中讨论的命令和工具,故障排除与监控

描述容器

包含虚拟化和容器化的隔离化是 IT 敏捷性的新时代常态。虚拟化一直是云计算巨大成功的神秘基础。现在,随着容器化理念的普及和可用,重新聚焦于利用容器加速应用程序的构建、部署和交付。容器具有一些改变游戏规则的独特功能,因此人们纷纷接受并推动容器化技术和工具的发展。

本质上,容器是轻量的、虚拟化的、可移植的,是一个软件定义的环境,软件可以在其中独立运行,不与同一物理主机上运行的其他软件发生干扰。运行在容器内的软件通常是单一功能的应用程序。容器为 IT 环境带来了备受追捧的模块化、可移植性和简化性。开发人员喜爱容器,因为它们加速了软件工程的进程,而运维团队则喜爱容器,因为他们可以专注于运行时任务,如日志记录、监控、生命周期管理和资源利用,而不必担心部署和依赖管理。

描述 Docker

Linux 容器非常复杂且不友好。认识到多种复杂因素妨碍了容器的广泛生产和流畅使用,一个开源项目应运而生,旨在提供一个精密且模块化的平台,包含一个简化和优化容器生命周期各阶段的引擎。也就是说,Docker 平台的构建目标是自动化任何软件应用程序的设计、打包、运输、部署和交付,这些应用程序都嵌入在一个轻量、可扩展且自给自足的容器内。

Docker 被定位为实现高效且企业级分布式应用的最灵活且具有前瞻性的容器化技术。这一定位旨在对 IT 行业中的新兴趋势产生巧妙且决定性的影响:现在,企业正构建更小、更自定义、更可持续、易于管理且离散的应用,而不是单一物理或虚拟服务器上分布的大型单体应用。简而言之,服务正在成为微服务,以推动容器化的进程。

Docker 平台使得从不同的、分布式的组件中艺术性地组装应用成为可能,消除了在代码交付过程中可能出现的任何缺陷和偏差。通过一系列脚本和工具,Docker 简化了软件应用的隔离,使它们通过在临时容器中运行变得自给自足。Docker 为每个应用之间以及与底层主机之间提供了所需的隔离。我们已经习惯了通过额外的间接层来形成虚拟机,以提供必要的隔离。

这个额外的层和开销消耗了大量宝贵的资源,因此它是系统变慢的一个不必要原因。另一方面,Docker 容器共享所有资源(计算、存储和网络),并能够在最优水平上运行,因此可以更快地执行。由于 Docker 镜像采用标准形式生成,因此可以广泛共享并轻松存储,从而生成更大更好的应用容器。简而言之,Docker 平台为各种 IT 基础设施的最佳消费、管理和操作性奠定了激励人心的基础。

Docker 平台是一个开源的容器化解决方案,能够聪明而迅速地将任何软件应用和服务打包成容器,并加速容器化应用在任何 IT 环境中的部署(本地或远程系统、虚拟化或裸机、通用设备或嵌入式设备等)。容器生命周期管理任务完全由 Docker 平台负责。整个过程从为已识别的软件及其依赖关系形成标准化且优化的镜像开始。接着,Docker 平台利用准备好的镜像构建容器化软件。镜像仓库有公开的,也有私有的,开发者和运维团队可以利用这些仓库以自动化的方式加速软件部署。

Docker 生态系统正在迅速发展,许多第三方产品和工具开发者正在努力将 Docker 打造成企业级的容器化平台。它有助于跳过开发环境和特定语言工具的设置与维护,而是专注于创建和添加新功能、修复问题和交付软件。“构建一次,到处运行”是 Docker 启用容器化的基本口号。简而言之,Docker 平台带来了以下几项能力。

  • 敏捷性:开发者可以自由定义环境并创建应用,IT 运维团队可以更快地部署应用,帮助企业超越竞争对手。

  • 可控性:开发者拥有从基础设施到应用的所有代码。

  • 可管理性:IT 运维团队成员可以管理标准化、安全性和扩展操作环境,同时降低组织的总体成本。

区分 Docker 容器

精确来说,Docker 容器将软件包装在一个完整的文件系统中,其中包含运行所需的一切:源代码、运行时、系统工具和系统库(可以安装在服务器上的任何东西)。这保证了软件无论在哪种操作环境下都能始终运行相同:

在单台机器上运行的容器共享相同的操作系统内核。它们启动迅速并且使用的 RAM 较少。容器镜像由分层文件系统构建,并共享通用文件,使得磁盘使用和镜像下载更加高效。

  • Docker 容器基于开放标准。这种标准化使得容器能够在所有主要的 Linux 发行版以及其他操作系统如 Windows 和 macOS 上运行。

与 Docker 容器相关的有几个好处,如下所列。

  • 效率:在单台机器上运行的容器都利用共同的内核,因此它们轻量级、启动迅速,并且更有效地使用 RAM。

    • 资源共享 允许工作负载之间比使用专用和单一用途设备更高效。这种共享增强了资源的利用率。

    • 资源分区 确保资源被适当地分割以满足每个工作负载的系统需求。此分区的另一个目标是防止任何工作负载之间的不良交互。

    • 资源即服务 (RaaS):可以独立选择、配置和提供各种资源,直接供应到应用程序或用户以运行应用程序。

  • 本地性能:由于其轻量级和更少浪费,容器保证了更高的性能。

  • 可移植性:应用程序、依赖项和配置都打包在一个完整的文件系统中,确保应用程序在任何环境中无缝运行(虚拟机、裸金属服务器、本地或远程、通用或专用机器等)。这种可移植性的主要优势是可以在部署之间更改运行时依赖关系(甚至编程语言)。结合 Volume 插件,您的容器真正具有可移植性。

  • 实时可伸缩性:可以在几秒钟内配置任意数量的新容器,以满足用户和数据负载。相反地,当需求降低时,额外配置的容器可以被关闭。这确保了更高的吞吐量和按需容量。类似的工具有:

    仅举几个进一步简化弹性扩展的集群解决方案。

  • 高可用性:通过运行多个容器,可以将冗余构建到应用程序中。如果一个容器失败,那么其他提供相同功能的存活容器将继续提供服务。通过 orchestration,失败的容器可以自动重新创建(重新调度),无论是在同一宿主机还是不同宿主机上,从而恢复完全的容量和冗余性。

  • 可操作性:运行在 Docker 容器中的应用程序可以轻松修改、更新或扩展,而不会影响宿主机中的其他容器。

  • 灵活性:开发人员可以自由使用他们喜欢的任何编程语言和开发工具。

  • 集群化:容器可以按需集群化用于特定目的,并且有集成的管理平台用于集群启用和管理。

  • 可组合性:托管在容器中的软件服务可以被发现、匹配并链接,以形成对业务至关重要的、面向过程的和复合的服务。

  • 安全性:容器通过为应用程序提供额外的保护层,将应用程序与彼此及底层基础设施隔离开来。

  • 可预测性:通过不可变镜像,镜像始终在任何地方表现出相同的行为,因为代码被包含在镜像中。这在部署和管理应用生命周期方面具有重要意义。

  • 可重复性:使用 Docker,可以构建镜像、测试镜像,然后在生产环境中使用相同的镜像。

  • 可复制性:使用容器,可以轻松实例化相同的完整应用栈和配置的副本。这些副本可以供新员工、合作伙伴、支持团队和其他人使用,以安全地进行隔离实验。

虚拟机与容器

容器与高度可见且可行的虚拟机VMs)有显著区别。虚拟机代表硬件虚拟化,而容器则实现操作系统级别的虚拟化。一些文献指出,虚拟机是系统或操作系统容器,而容器通常代表应用程序容器。

在功能方面,容器类似于虚拟机,但在许多其他方面有所不同。像虚拟机一样,容器也共享各种系统资源,如处理、内存、存储等。关键的不同点是,宿主机器中的所有容器共享宿主操作系统的相同内核。

尽管存在大量共享,容器本质上通过使用最近引入的内核功能(如命名空间和控制组)将应用程序、运行时和其他相关服务相互隔离,从而保持高度的隔离性。

在资源配置方面,应用容器可以在几秒钟内实现,而虚拟机通常需要几分钟。容器还允许通过内核直接访问设备驱动程序,从而加速 I/O 操作。

通过容器化能力,可以加速将工作负载迁移到附近或远程的云环境中。Docker 容器技术提供的工具和 API 功能非常强大,并且比虚拟机(VM)提供的工具更加开发者友好。这些 API 允许将容器管理集成到各种自动化系统中,以加速软件工程。

Docker 的使用案例

容器化正在成为软件行业前进的道路,因为它为构建、打包任何类型的软件,运输并在任何地方运行它们提供了更新、更丰富的方式。容器化的快速发展承诺并提供了软件可移植性,这一直是 IT 开发者和管理员多年来常常面临的困扰。Docker 的理念在这里蓬勃发展,得益于多个促进因素和面向的方面。本节特别准备了关于 Docker 理念的关键使用案例。

将容器集成到工作流中

工作流是一种被广泛接受和使用的抽象,用于明确表示任何复杂的大型商业和科学应用的详细信息,并在分布式计算系统(如集群、云和网格)上执行它们。然而,工作流管理系统在传达相关的底层环境信息方面大多存在回避现象,而这些信息对于工作流中的任务执行至关重要。也就是说,工作流任务可以在为其设计的环境中完美运行。真正的挑战在于如何在多个 IT 环境中运行这些任务,而不必修改和扭曲原始任务的源代码。随着 IT 环境的日益异构,操作系统(OSes)、中间件、编程语言和框架、数据库等的差异化被越来越广泛地利用。典型的工作流系统专注于任务之间和特定环境之间的数据交换。一个在某个环境中运行良好的工作流,在迁移并部署到不同的 IT 环境中时,可能会开始出现问题。各种已知和未知的依赖关系与不兼容性会不断涌现,破坏工作流的顺利执行,延误整个 IT 设置、应用安装和配置、部署以及交付的过程。容器是解决这一复杂局面的一劳永逸的最佳选择。

Chao Zheng 和 Douglas Thain(将容器集成到工作流中:使用 Makeflow、Work Queue 和 Docker 的案例研究) 很好地分析了几种方法,以实验性地证明容器在赋能工作流/过程管理系统方面的独特贡献。他们探讨了在启用 Docker 的集群上运行大规模生物信息学工作负载的性能,并观察到最佳配置是由多个任务共享的本地管理容器。

Docker 在高性能计算(HPC)和技术计算(TC)应用中的应用

(Douglas M. Jacobsen 和 Richard Shane Canon)– 目前,容器在网页、企业、移动和云应用程序中得到了广泛应用。然而,仍然有一些问题被提出,是否容器能够作为一个可行的运行时环境来承载技术和科学计算应用程序。特别是许多高性能计算应用程序渴望得到完美的部署和执行环境。本文的作者们意识到,Docker 容器可以是高性能计算(HPC)工作负载的完美解决方案。

在许多情况下,用户希望能够在与开发环境或社区采用的环境相同的环境中轻松执行其科学应用程序和工作流。一些研究人员尝试过云选项,但那里存在许多挑战。用户需要解决如何处理工作负载管理、文件系统和基础资源配置的问题。容器承诺提供云类型系统的灵活性,同时兼具裸金属系统的性能。此外,容器更容易与传统的 HPC 环境集成,这意味着用户可以在不增加管理其他系统层(如批处理系统、文件系统等)负担的情况下获得灵活性的好处。

Minh Thanh Chung 和团队分析了虚拟机和容器在高性能应用中的性能,并对结果进行了基准测试,结果清晰地表明容器是 HPC 应用程序的下一代运行时。简而言之,Docker 在 HPC 环境中提供了许多有吸引力的优势。为了验证这些优势,IBM Platform LSF 和 Docker 已经在 Platform LSF 的核心之外进行了集成,并且该集成利用了丰富的 Platform LSF 插件框架。

我们都知道,隔离的目的是为了资源的划分和配置。也就是说,物理机器被细分成多个逻辑机器(虚拟机和容器)。而从另一方面来看,这些由多个物理机器构成的逻辑系统可以被连接起来,构建一个虚拟超级计算机,用以解决某些复杂问题。许恩·余魏成·黄在研究论文《通过 Docker 构建一个自动扩展的虚拟 HPC 集群》中描述了他们是如何构建虚拟高性能计算集群的。他们将服务发现的自动扩展功能与轻量级虚拟化范式(Docker)结合,并开始实现基于物理集群硬件的虚拟集群。

电信应用的容器化

Csaba Rotter 和团队探讨并发布了一篇名为《在电信应用中使用 Linux 容器》的调研文章。电信应用具有强大的性能和高可用性要求,因此,将其运行在容器中需要进一步的研究。电信应用是负责特定任务的单节点或多节点应用。电信应用使用标准化接口连接到其他网络元素,并实现标准化功能。在标准化功能之上,电信应用可以有特定厂商的功能。存在一系列服务质量QoS)和体验质量QoE)属性,如高可用性、容量、性能/吞吐量等。文章清楚地阐述了容器在支持下一代电信应用方面的独特贡献。

使用 Docker-Hadoop 高效原型化容错 Map-Reduce 应用(哈维尔·雷和团队) – 分布式计算是应对计算和数据密集型工作负载的未来方向。有两个主要趋势。数据变得庞大,并且人们意识到,大数据通过利用开创性的算法、脚本和并行语言如 Scala、集成平台、新一代数据库以及动态 IT 基础设施,可以带来重大见解。MapReduce 是当前用于对大量数据进行计算的并行编程范式。Docker-Hadoop1 是一种虚拟化测试平台,旨在支持快速部署 Hadoop 集群。通过 Docker-Hadoop,可以控制节点特性并进行可扩展性和性能测试,否则这些测试需要庞大的计算环境。Docker-Hadoop 使得模拟和重现不同的故障场景成为可能,从而验证应用的正确性。

互动社交媒体应用 - AlinCalinciuc 和他的团队发布了一篇名为 OpenStack 和 Docker:为互动社交媒体应用构建高性能 IaaS 平台 的研究论文。众所周知,互动社交媒体应用面临着有效配置新资源的挑战,以满足不断增长的应用用户需求。作者详细说明了 Docker 如何作为虚拟机管理程序运行,以及他们如何通过自己开发的 nova-docker 插件在 OpenStack IaaS 内部实现计算资源的快速配置。

摘要

目前,Docker 已经成为一种“流行病”,全球各地的创业型企业几乎都为容器化的热潮所着迷,因为它能带来极致的自动化、转型和颠覆。

随着混合 IT 的蓬勃发展,基于 Docker 的容器化在智能赋能 IT 业务方面的作用稳步增长。本章讨论了 Docker 模式的主要功能和贡献。

很少能用一个梗图总结一本书,但我认为至少你对容器世界的探索将解决这个过于常见的问题:

摘要

图片由 Dave Roth 拍摄

过去,开发者通常在与生产平台完全不同的本地配置中使用某种语言的版本进行代码开发,但现在这一切已经过去,你可以轻松开发、打包并发布一致的容器,这些容器可以在任何地方运行。

posted @ 2025-06-29 10:39  绝不原创的飞龙  阅读(34)  评论(0)    收藏  举报