精通-Docker-第三版(全)

精通 Docker 第三版(全)

原文:zh.annas-archive.org/md5/3EE782924E03F9CE768AD8AE784D47E6

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Docker 在现代应用程序部署和架构方面是一个改变游戏规则的因素。它现在已经发展成为创新的关键驱动力,超越了系统管理,并对 Web 开发等领域产生了影响。但是,您如何确保您跟上了它所推动的创新?您如何确保您充分发挥了它的潜力?

本书向您展示了如何做到这一点;它不仅演示了如何更有效地使用 Docker,还帮助您重新思考和重新想象 Docker 的可能性。

您还将涵盖基本主题,如构建、管理和存储图像,以及在深入研究 Docker 安全性之前使您信心十足的最佳实践。您将找到与扩展和集成 Docker 相关的一切新颖创新的方法。Docker Compose,Docker Swarm 和 Kubernetes 将帮助您以高效的方式控制容器。

通过本书,您将对 Docker 的可能性有一个广泛而详细的认识,以及它如何无缝地融入到您的本地工作流程中,以及高可用的公共云平台和其他工具。

本书适合谁

如果您是 IT 专业人士,并认识到 Docker 在从系统管理到 Web 开发的创新中的重要性,但不确定如何充分利用它,那么本书适合您。

要充分利用本书

要充分利用本书,您需要一台能够运行 Docker 的机器。这台机器应至少具有 8GB RAM 和 30GB 可用硬盘空间,配备 Intel i3 或更高版本,运行以下操作系统之一:

  • macOS High Sierra 或更高版本

  • Windows 10 专业版

  • Ubuntu 18.04

此外,您将需要访问以下公共云提供商之一或全部:DigitalOcean,Amazon Web Services,Microsoft Azure 和 Google Cloud。

下载示例代码文件

您可以从www.packt.com的帐户中下载本书的示例代码文件。如果您在其他地方购买了本书,您可以访问www.packt.com/support并注册,以便文件直接发送到您的邮箱。

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

  1. www.packt.com登录或注册。

  2. 选择“支持”选项卡。

  3. 点击“代码下载和勘误”。

  4. 在搜索框中输入书名,然后按照屏幕上的说明操作。

文件下载后,请确保使用最新版本的解压缩软件解压缩文件夹。

  • WinRAR/7-Zip 适用于 Windows

  • Zipeg/iZip/UnRarX 适用于 Mac

  • 7-Zip/PeaZip 适用于 Linux

本书的代码包也托管在 GitHub 上,网址为 github.com/PacktPublishing/Mastering-Docker-Third-Edition。如果代码有更新,将在现有的 GitHub 存储库上进行更新。

我们还有其他代码包,来自我们丰富的图书和视频目录,可在 github.com/PacktPublishing/ 上找到。快去看看吧!

下载彩色图像

我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图像。您可以在这里下载:www.packtpub.com/sites/default/files/downloads/9781789616606_ColorImages.pdf

代码示例

访问以下链接查看代码运行的视频:

bit.ly/2PUB9ww

使用的约定

本书中使用了许多文本约定。

CodeInText:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。这是一个例子:“第一个文件是nginx.conf,其中包含基本的 nginx 配置文件。”

代码块设置如下:

user nginx;
worker_processes 1;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
}

任何命令行输入或输出都将按以下方式编写:

$ docker image inspect <IMAGE_ID>

粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词会以这种方式出现在文本中。这是一个例子:“单击“创建”后,您将被带到类似下一个屏幕截图的屏幕。”

警告或重要说明会以这种方式出现。提示和技巧会以这种方式出现。

第一章:Docker 概述

欢迎来到《Docker 大师》,第三版!本章将介绍您应该已经掌握的 Docker 基础知识。但是,如果您在这一点上还没有所需的知识,本章将帮助您掌握基础知识,以便后续章节不会感到沉重。在本书结束时,您应该是一个 Docker 大师,并且能够在您的环境中实施 Docker,构建和支持应用程序。

在本章中,我们将回顾以下高级主题:

  • 理解 Docker

  • 专用主机、虚拟机和 Docker 之间的区别

  • Docker 安装程序/安装

  • Docker 命令

  • Docker 和容器生态系统

技术要求

在本章中,我们将讨论如何在本地安装 Docker。为此,您需要运行以下三种操作系统之一的主机:

  • macOS High Sierra 及以上

  • Windows 10 专业版

  • Ubuntu 18.04

查看以下视频,了解代码的实际操作:

bit.ly/2NXf3rd

理解 Docker

在我们开始安装 Docker 之前,让我们先了解 Docker 技术旨在解决的问题。

开发人员

Docker 背后的公司一直将该程序描述为解决“它在我的机器上运行良好”的问题。这个问题最好由一个基于 Disaster Girl 模因的图像概括,简单地带有标语在开发中运行良好,现在是运维问题,几年前开始出现在演示文稿、论坛和 Slack 频道中。虽然很有趣,但不幸的是,这是一个非常真实的问题,我个人也曾经遇到过——让我们看一个例子,了解这是什么意思。

问题

即使在遵循 DevOps 最佳实践的世界中,开发人员的工作环境仍然很容易与最终生产环境不匹配。

例如,使用 macOS 版本的 PHP 的开发人员可能不会运行与托管生产代码的 Linux 服务器相同的版本。即使版本匹配,您还必须处理 PHP 版本运行的配置和整体环境之间的差异,例如不同操作系统版本之间处理文件权限的方式的差异,仅举一个潜在问题的例子。

当开发人员部署他们的代码到主机上时,如果出现问题,所有这些问题都会变得棘手。因此,生产环境应该配置成与开发人员的机器相匹配,还是开发人员只能在与生产环境匹配的环境中工作?

在理想的世界中,从开发人员的笔记本电脑到生产服务器,一切都应该保持一致;然而,这种乌托邦传统上很难实现。每个人都有自己的工作方式和个人偏好——即使只有一个工程师在系统上工作,要在多个平台上强制实现一致性已经很困难了,更不用说一个团队的工程师与数百名开发人员合作了。

Docker 解决方案

使用 Docker for Mac 或 Docker for Windows,开发人员可以轻松地将他们的代码封装在一个容器中,他们可以自己定义,或者在与系统管理员或运营团队一起工作时创建为 Dockerfile。我们将在第二章《构建容器镜像》中涵盖这一点,以及 Docker Compose 文件,在第五章《Docker Compose》中我们将更详细地介绍。

他们可以继续使用他们选择的集成开发环境,并在处理代码时保持他们的工作流程。正如我们将在本章的后续部分中看到的,安装和使用 Docker 并不困难;事实上,考虑到过去维护一致的环境有多么繁琐,即使有自动化,Docker 似乎有点太容易了——几乎像作弊一样。

运营商

我在运营方面工作的时间比我愿意承认的时间长,以下问题经常出现。

问题

假设你正在管理五台服务器:三台负载均衡的 Web 服务器,以及两台专门运行应用程序 1 的主从配置的数据库服务器。你正在使用工具,比如 Puppet 或者 Chef,来自动管理这五台服务器上的软件堆栈和配置。

一切都进行得很顺利,直到有人告诉你,“我们需要在运行应用程序 1 的服务器上部署应用程序 2”。表面上看,这没有问题——你可以调整你的 Puppet 或 Chef 配置来添加新用户、虚拟主机,下载新代码等。然而,你注意到应用程序 2 需要比你为应用程序 1 运行的软件更高的版本。

更糟糕的是,你已经知道应用程序 1 坚决不愿意与新软件堆栈一起工作,而应用程序 2 也不向后兼容。

传统上,这给你留下了几个选择,无论哪种选择都会在某种程度上加剧问题:

  1. 要求更多的服务器?虽然从技术上来说,这可能是最安全的解决方案,但这并不意味着会有额外资源的预算。

  2. 重新设计解决方案?从技术角度来看,从负载均衡器或复制中取出一台 Web 和数据库服务器,然后重新部署它们与应用程序 2 的软件堆栈似乎是下一个最容易的选择。然而,你正在为应用程序 2 引入单点故障,并且也减少了应用程序 1 的冗余:你之前可能有理由在第一次运行三台 Web 和两台数据库服务器。

  3. 尝试在服务器上并行安装新软件堆栈?嗯,这当然是可能的,而且似乎是一个不错的短期计划,可以让项目顺利进行,但当第一个关键的安全补丁需要应用于任一软件堆栈时,可能会导致整个系统崩溃。

Docker 解决方案

这就是 Docker 开始发挥作用的地方。如果你在容器中跨三台 Web 服务器上运行应用程序 1,实际上你可能正在运行的容器不止三个;事实上,你可能已经运行了六个,容器的数量翻倍,使你能够在不降低应用程序 1 的可用性的情况下进行应用程序的滚动部署。

在这种环境中部署应用程序 2 就像简单地在三台主机上启动更多的容器,然后通过负载均衡器路由到新部署的应用程序一样简单。因为你只是部署容器,所以你不需要担心在同一台服务器上部署、配置和管理两个版本的相同软件堆栈的后勤问题。

我们将在《第五章》中详细介绍这种确切的情景,Docker Compose

企业

企业遭受着之前描述的相同问题,因为他们既有开发人员又有运维人员;然而,他们在更大的规模上拥有这两个实体,并且还存在更多的风险。

问题

由于前述的风险,再加上任何停机时间可能带来的销售损失或声誉影响,企业需要在发布之前测试每次部署。这意味着新功能和修复被困在保持状态中,直到以下步骤完成:

  • 测试环境被启动和配置

  • 应用程序部署在新启动的环境中

  • 测试计划被执行,应用程序和配置被调整,直到测试通过。

  • 变更请求被编写、提交和讨论,以便将更新的应用程序部署到生产环境中

这个过程可能需要几天、几周,甚至几个月,具体取决于应用程序的复杂性和变更引入的风险。虽然这个过程是为了确保企业在技术层面上的连续性和可用性而必需的,但它确实可能在业务层面引入风险。如果你的新功能被困在这种保持状态中,而竞争对手发布了类似的,甚至更糟的功能,超过了你,那该怎么办呢?

这种情况对销售和声誉可能造成的损害与该过程最初为了保护你免受停机时间的影响一样严重。

Docker 解决方案

首先,让我说一下,Docker 并不能消除这样一个过程的需求,就像刚才描述的那样,存在或者被遵循。然而,正如我们已经提到的,它确实使事情变得更容易,因为你已经在一贯地工作。这意味着你的开发人员一直在使用与生产环境中运行的相同的容器配置。这意味着这种方法论被应用到你的测试中并不是什么大问题。

例如,当开发人员检查他们在本地开发环境上知道可以正常工作的代码时(因为他们一直在那里工作),您的测试工具可以启动相同的容器来运行自动化测试。一旦容器被使用,它们可以被移除以释放资源供下一批测试使用。这意味着,突然之间,您的测试流程和程序变得更加灵活,您可以继续重用相同的环境,而不是为下一组测试重新部署或重新映像服务器。

这个流程的简化可以一直进行到您的新应用程序容器推送到生产环境。

这个过程完成得越快,您就可以更快地自信地推出新功能或修复问题,并保持领先地位。

专用主机、虚拟机和 Docker 之间的区别

因此,我们知道 Docker 是为了解决什么问题而开发的。现在我们需要讨论 Docker 究竟是什么以及它的作用。

Docker 是一个容器管理系统,可以帮助我们更轻松地以更简单和通用的方式管理 Linux 容器(LXC)。这使您可以在笔记本电脑上的虚拟环境中创建镜像并对其运行命令。您在本地机器上运行的这些环境中的容器执行的操作将是您在生产环境中运行它们时执行的相同命令或操作。

这有助于我们,因为当您从开发环境(例如本地机器上的环境)转移到服务器上的生产环境时,您不必做出不同的事情。现在,让我们来看看 Docker 容器和典型虚拟机环境之间的区别。

如下图所示,演示了专用裸金属服务器和运行虚拟机的服务器之间的区别:

正如您所看到的,对于专用机器,我们有三个应用程序,都共享相同的橙色软件堆栈。运行虚拟机允许我们运行三个应用程序,运行两个完全不同的软件堆栈。下图显示了在使用 Docker 容器运行的相同橙色和绿色应用程序:

这张图表让我们对 Docker 的最大关键优势有了很多了解,也就是说,每次我们需要启动一个新的容器时都不需要完整的操作系统,这减少了容器的总体大小。由于几乎所有的 Linux 版本都使用标准的内核模型,Docker 依赖于使用主机操作系统的 Linux 内核,例如 Red Hat、CentOS 和 Ubuntu。

因此,您几乎可以将任何 Linux 操作系统作为您的主机操作系统,并能够在主机上叠加其他基于 Linux 的操作系统。嗯,也就是说,您的应用程序被认为实际上安装了一个完整的操作系统,但实际上,我们只安装了二进制文件,比如包管理器,例如 Apache/PHP 以及运行应用程序所需的库。

例如,在之前的图表中,我们可以让 Red Hat 运行橙色应用程序,让 Debian 运行绿色应用程序,但实际上不需要在主机上安装 Red Hat 或 Debian。因此,Docker 的另一个好处是创建镜像时的大小。它们构建时没有最大的部分:内核或操作系统。这使它们非常小,紧凑且易于传输。

Docker 安装

安装程序是您在本地计算机和服务器环境上运行 Docker 时需要的第一件东西。让我们首先看一下您可以在哪些环境中安装 Docker:

  • Linux(各种 Linux 版本)

  • macOS

  • Windows 10 专业版

此外,您可以在公共云上运行它们,例如亚马逊网络服务、微软 Azure 和 DigitalOcean 等。在之前列出的各种类型的安装程序中,Docker 实际上在操作系统上以不同的方式运行。例如,Docker 在 Linux 上本地运行,因此如果您使用 Linux,那么 Docker 在您的系统上运行的方式就非常简单。但是,如果您使用 macOS 或 Windows 10,那么它的运行方式会有所不同,因为它依赖于使用 Linux。

让我们快速看一下在运行 Ubuntu 18.04 的 Linux 桌面上安装 Docker,然后在 macOS 和 Windows 10 上安装。

在 Linux(Ubuntu 18.04)上安装 Docker

正如前面提到的,这是我们将要看到的三个系统中最直接的安装。要安装 Docker,只需在终端会话中运行以下命令:

$ curl -sSL https://get.docker.com/ | sh
$ sudo systemctl start docker

您还将被要求将当前用户添加到 Docker 组中。要执行此操作,请运行以下命令,并确保您用自己的用户名替换用户名:

$ sudo usermod -aG docker username

这些命令将从 Docker 自己那里下载、安装和配置最新版本的 Docker。在撰写本文时,官方安装脚本安装的 Linux 操作系统版本为 18.06。

运行以下命令应该确认 Docker 已安装并正在运行:

$ docker version

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

有两个支持工具,我们将在未来的章节中使用,这些工具作为 Docker for macOS 或 Windows 10 安装程序的一部分安装。

为了确保我们在以后的章节中准备好使用这些工具,我们现在应该安装它们。第一个工具是Docker Machine。要安装这个工具,我们首先需要获取最新的版本号。您可以通过访问项目的 GitHub 页面的发布部分github.com/docker/machine/releases/找到这个版本。撰写本文时,版本为 0.15.0——在安装时,请使用以下代码块中的命令更新版本号为最新版本。

$ MACHINEVERSION=0.15.0
$ curl -L https://github.com/docker/machine/releases/download/v$MACHINEVERSION/docker-machine-$(uname -s)-$(uname -m) >/tmp/docker-machine
$ chmod +x /tmp/docker-machine
$ sudo mv /tmp/docker-machine /usr/local/bin/docker-machine

要下载并安装下一个和最终的工具Docker Compose,请运行以下命令,再次检查您是否通过访问github.com/docker/compose/releases/页面运行最新版本:

$ COMPOSEVERSION=1.22.0
$ curl -L https://github.com/docker/compose/releases/download/$COMPOSEVERSION/docker-compose-`uname -s`-`uname -m` >/tmp/docker-compose
$ chmod +x /tmp/docker-compose
$ sudo mv /tmp/docker-compose /usr/local/bin/docker-compose

安装完成后,您应该能够运行以下两个命令来确认软件的版本是否正确:

$ docker-machine version
$ docker-compose version

在 macOS 上安装 Docker

与命令行 Linux 安装不同,Docker for Mac 有一个图形安装程序。

在下载之前,您应该确保您正在运行 Apple macOS Yosemite 10.10.3 或更高版本。如果您正在运行旧版本,一切都不会丢失;您仍然可以运行 Docker。请参考本章的其他旧操作系统部分。

您可以从 Docker 商店下载安装程序,网址为store.docker.com/editions/community/docker-ce-desktop-mac。只需点击获取 Docker 链接。下载完成后,您应该会得到一个 DMG 文件。双击它将挂载映像,打开桌面上挂载的映像应该会显示类似以下内容:

将 Docker 图标拖到应用程序文件夹后,双击它,系统会询问您是否要打开已下载的应用程序。点击“是”将打开 Docker 安装程序,显示如下内容:

点击“下一步”并按照屏幕上的说明操作。安装并启动后,您应该会在屏幕的左上角图标栏中看到一个 Docker 图标。点击该图标并选择“关于 Docker”应该会显示类似以下内容:

您还可以打开终端窗口。运行以下命令,就像我们在 Linux 安装中所做的那样:

$ docker version

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

您还可以运行以下命令来检查与 Docker Engine 一起安装的 Docker Compose 和 Docker Machine 的版本:

$ docker-compose version
$ docker-machine version 

在 Windows 10 专业版上安装 Docker

与 Docker for Mac 一样,Docker for Windows 使用图形安装程序。

在下载之前,您应该确保您正在运行 Microsoft Windows 10 专业版或企业版 64 位。如果您正在运行旧版本或不受支持的 Windows 10 版本,您仍然可以运行 Docker;有关更多信息,请参阅本章其他旧操作系统部分。

Docker for Windows 有此要求是因为它依赖于 Hyper-V。Hyper-V 是 Windows 的本机虚拟化程序,允许您在 Windows 10 专业版或 Windows Server 上运行 x86-64 客户机。它甚至是 Xbox One 操作系统的一部分。

您可以从 Docker 商店下载 Docker for Windows 安装程序,网址为store.docker.com/editions/community/docker-ce-desktop-windows/。只需点击“获取 Docker”按钮下载安装程序。下载完成后,运行 MSI 包,您将看到以下内容:

点击“是”,然后按照屏幕提示进行操作,这将不仅安装 Docker,还将启用 Hyper-V(如果您尚未启用)。

安装完成后,您应该在屏幕右下角的图标托盘中看到一个 Docker 图标。单击它,然后从菜单中选择关于 Docker,将显示以下内容:

打开 PowerShell 窗口并输入以下命令:

$ docker version

这也应该显示与 Mac 和 Linux 版本类似的输出:

同样,您也可以运行以下命令来检查与 Docker Engine 一起安装的 Docker Compose 和 Docker Machine 的版本:

$ docker-compose version
$ docker-machine version

同样,您应该看到与 macOS 和 Linux 版本类似的输出。正如您可能已经开始了解的那样,一旦安装了这些软件包,它们的使用方式将会非常相似。这将在本章后面更详细地介绍。

旧操作系统

如果您在 Mac 或 Windows 上运行的操作系统版本不够新,那么您将需要使用 Docker Toolbox。考虑运行以下命令后打印的输出:

$ docker version

到目前为止,我们已经执行的三个安装都显示了两个不同的版本,一个客户端和一个服务器。可以预料的是,Linux 版本显示客户端和服务器的架构都是 Linux;然而,您可能会注意到 Mac 版本显示客户端正在运行 Darwin,这是苹果的类 Unix 内核,而 Windows 版本显示 Windows。但两个服务器都显示架构为 Linux,这是怎么回事呢?

这是因为 Docker 的 Mac 和 Windows 版本都会下载并在后台运行一个虚拟机,这个虚拟机运行着基于 Alpine Linux 的小型轻量级操作系统。虚拟机是使用 Docker 自己的库运行的,这些库连接到您选择的环境的内置 hypervisor。

对于 macOS 来说,这是内置的 Hypervisor.framework,而对于 Windows 来说,是 Hyper-V。

为了确保每个人都能体验 Docker,针对较旧版本的 macOS 和不受支持的 Windows 版本提供了一个不使用这些内置 hypervisor 的 Docker 版本。这些版本利用 VirtualBox 作为 hypervisor 来运行本地客户端连接的 Linux 服务器。

VirtualBox是由 Oracle 开发的开源 x86 和 AMD64/Intel64 虚拟化产品。它可以在 Windows、Linux、Macintosh 和 Solaris 主机上运行,并支持许多 Linux、Unix 和 Windows 客户操作系统。有关 VirtualBox 的更多信息,请参阅www.virtualbox.org/

有关Docker Toolbox的更多信息,请参阅项目网站www.docker.com/products/docker-toolbox/,您也可以在该网站上下载 macOS 和 Windows 的安装程序。

本书假设您已经在 Linux 上安装了最新的 Docker 版本,或者已经使用了 Docker for Mac 或 Docker for Windows。虽然使用 Docker Toolbox 安装 Docker 应该支持本书中的命令,但在将数据从本地机器挂载到容器时,您可能会遇到文件权限和所有权方面的问题。

Docker 命令行客户端

既然我们已经安装了 Docker,让我们来看一些你应该已经熟悉的 Docker 命令。我们将从一些常用命令开始,然后看一下用于 Docker 镜像的命令。然后我们将深入了解用于容器的命令。

Docker 已经将他们的命令行客户端重构为更合乎逻辑的命令组合,因为客户端提供的功能数量增长迅速,命令开始互相交叉。在本书中,我们将使用新的结构。

我们将首先看一下一个最有用的命令,不仅在 Docker 中,而且在您使用的任何命令行实用程序中都是如此——help命令。它的运行方式很简单,就像这样:

$ docker help

这个命令将给你一个完整的 Docker 命令列表,以及每个命令的简要描述。要获取特定命令的更多帮助,可以运行以下命令:

$ docker <COMMAND> --help

接下来,让我们运行hello-world容器。要做到这一点,只需运行以下命令:

$ docker container run hello-world

无论您在哪个主机上运行 Docker,Linux、macOS 和 Windows 都会发生同样的事情。Docker 将下载hello-world容器镜像,然后执行它,一旦执行完毕,容器将被停止。

您的终端会话应该如下所示:

让我们尝试一些更有冒险精神的事情——通过运行以下两个命令来下载并运行一个 nginx 容器:

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

这两个命令中的第一个下载了 nginx 容器镜像,第二个命令在后台启动了一个名为nginx-test的容器,使用我们拉取的nginx镜像。它还将主机机器上的端口8080映射到容器上的端口80,使其可以通过我们本地浏览器访问http://localhost:8080/

正如你从以下截图中看到的,所有三种操作系统类型上的命令和结果都是完全相同的。这里是 Linux:

macOS 上的结果如下:

而在 Windows 上的效果如下:

在接下来的三章中,我们将更详细地查看使用 Docker 命令行客户端。现在,让我们停止并删除我们的nginx-test容器,运行以下命令:

$ docker container stop nginx-test
$ docker container rm nginx-test

正如你所看到的,在我们安装了 Docker 的三个主机上运行一个简单的 nginx 容器的体验是完全相同的。我相信你可以想象,在没有像 Docker 这样的东西的情况下,在这三个平台上实现这一点是一种挑战,并且在每个平台上的体验也是非常不同的。传统上,这一直是本地开发环境差异的原因之一。

Docker 和容器生态系统

如果你一直在关注 Docker 和容器的崛起,你会注意到,在过去几年里,Docker 网站的宣传语已经慢慢地从关于容器是什么转变为更加关注 Docker 作为公司提供的服务。

其中一个核心驱动因素是,一切传统上都被归类为“Docker”,这可能会让人感到困惑。现在人们不需要太多关于容器是什么以及他们可以用 Docker 解决什么问题的教育,公司需要尝试开始与其他为各种容器技术提供支持的公司区分开来。

因此,让我们尝试梳理一下 Docker 的一切,其中包括以下内容:

  • 开源项目:Docker 启动了几个开源项目,现在由大量开发人员社区维护。

  • Docker CE 和 Docker EE:这是建立在开源组件之上的免费使用和商业支持的 Docker 工具的核心集合。

  • Docker, Inc.:这是一家成立的公司,旨在支持和开发核心 Docker 工具。

我们还将在后面的章节中研究一些第三方服务。与此同时,让我们更详细地了解每一个,从开源项目开始。

开源项目

Docker, Inc.在过去两年里一直在开源并向各种开源基金会和社区捐赠其核心项目。这些项目包括以下内容:

  • Moby Project是 Docker 引擎基于的上游项目。它提供了组装完全功能的容器系统所需的所有组件。

  • Runc是用于创建和配置容器的命令行界面,并且已经构建到 OCI 规范中。

  • Containerd是一个易于嵌入的容器运行时。它也是 Moby Project 的核心组件之一。

  • LibNetwork是一个提供容器网络的 Go 库。

  • Notary是一个旨在为签名的容器镜像提供信任系统的客户端和服务器。

  • HyperKit是一个工具包,允许您将虚拟化功能嵌入到自己的应用程序中,目前仅支持 macOS 和 Hypervisor.framework。

  • VPNKit为 HyperKit 提供 VPN 功能。

  • DataKit允许您使用类似 Git 的工作流来编排应用程序数据。

  • SwarmKit是一个工具包,允许您使用与 Docker Swarm 相同的 raft 一致性算法构建分布式系统。

  • LinuxKit是一个框架,允许您构建和编译一个小型便携的 Linux 操作系统,用于运行容器。

  • InfraKit是一套工具集,您可以使用它来定义基础架构,以运行您在 LinuxKit 上生成的发行版。

单独使用这些组件的可能性很小;然而,我们提到的每个项目都是由 Docker, Inc.维护的工具的组成部分。我们将在最后一章中更详细地介绍这些项目。

Docker CE 和 Docker EE

Docker, Inc.提供并支持了许多工具。有些我们已经提到过,其他的我们将在后面的章节中介绍。在完成我们的第一章之前,我们应该了解一下我们将要使用的工具。其中最重要的是核心 Docker 引擎。

这是 Docker 的核心,我们将要介绍的所有其他工具都会使用它。在本章的 Docker 安装和 Docker 命令部分,我们已经在使用它。目前有两个版本的 Docker Engine;有 Docker 企业版EE)和 Docker 社区版CE)。在本书中,我们将使用 Docker CE。

从 2018 年 9 月开始,稳定版本的 Docker CE 的发布周期将是半年一次,这意味着它将有七个月的维护周期。这意味着您有足够的时间来审查和计划任何升级。目前,Docker CE 发布的当前时间表如下:

  • Docker 18.06 CE:这是季度 Docker CE 发布的最后一个版本,发布于 2018 年 7 月 18 日。

  • Docker 18.09 CE:这个版本预计将于 2018 年 9 月底/10 月初发布,是 Docker CE 半年发布周期的第一个版本。

  • Docker 19.03 CE:2019 年的第一个受支持的 Docker CE 计划于 2019 年 3 月/4 月发布。

  • Docker 19.09 CE:2019 年的第二个受支持的版本计划于 2019 年 9 月/10 月发布。

除了稳定版本的 Docker CE,Docker 还将通过夜间存储库(正式的 Docker CE Edge)提供 Docker Engine 的夜间构建,以及通过 Edge 渠道每月构建的 Docker for Mac 和 Docker for Windows。

Docker 还提供以下工具和服务:

  • Docker Compose:这是一个允许您定义和共享多容器定义的工具;详细内容请参阅第五章 Docker Compose

  • Docker Machine:一个在多个平台上启动 Docker 主机的工具;我们将在第七章 Docker Machine中介绍这个工具。

  • Docker Hub:您的 Docker 镜像的存储库,将在接下来的三章中介绍。

  • Docker Store:官方 Docker 镜像和插件的商店,以及许可产品的存储库。同样,我们将在接下来的三章中介绍这个。

  • Docker Swarm:一个多主机感知编排工具,详细介绍请参阅第八章 Docker Swarm

  • Docker for Mac:我们在本章中已经介绍了 Docker for Mac。

  • Docker for Windows:我们在本章中已经介绍了 Docker for Windows。

  • Docker for Amazon Web Services:针对 AWS 的最佳实践 Docker Swarm 安装,详见第十章,在公共云中运行 Docker中有介绍。

  • Docker for Azure:针对 Azure 的最佳实践 Docker Swarm 安装,详见第十章,在公共云中运行 Docker中有介绍。

Docker, Inc.

Docker, Inc.是成立的公司,负责开发 Docker CE 和 Docker EE。它还为 Docker EE 提供基于 SLA 的支持服务。最后,他们为希望将现有应用程序容器化的公司提供咨询服务,作为 Docker 的现代化传统应用MTA)计划的一部分。

总结

在本章中,我们涵盖了一些基本信息,这些信息您应该已经知道(或现在知道)用于接下来的章节。我们讨论了 Docker 的基本知识,以及与其他主机类型相比的优势。我们讨论了安装程序,它们在不同操作系统上的操作方式,以及如何通过命令行控制它们。请务必记住查看安装程序的要求,以确保您使用适合您操作系统的正确安装程序。

然后,我们深入了解了如何使用 Docker,并发出了一些基本命令来帮助您入门。在未来的章节中,我们将研究所有管理命令,以更深入地了解它们是什么,以及如何何时使用它们。最后,我们讨论了 Docker 生态系统以及不同工具的责任。

在接下来的章节中,我们将看看如何构建基本容器,我们还将深入研究 Dockerfile 和存储图像的位置,以及使用环境变量和 Docker 卷。

问题

  1. 您可以从哪里下载 Mac 版 Docker 和 Windows 版 Docker?

  2. 我们使用哪个命令来下载 NGINX 镜像?

  3. 哪个开源项目是核心 Docker Engine 的上游项目?

  4. 稳定的 Docker CE 版本的支持生命周期有多少个月?

  5. 您会运行哪个命令来查找有关 Docker 容器子集命令的更多信息?

进一步阅读

在本章中,我们提到了以下虚拟化程序:

We referenced the following blog posts from Docker:

Next up, we discussed the following open source projects:

Finally, the meme mentioned at the start of the chapter can be found here:

第二章:构建容器映像

在本章中,我们将开始构建容器映像。我们将看几种不同的方式,使用内置在 Docker 中的工具来定义和构建映像。我们将涵盖以下主题:

  • 介绍 Dockerfile

  • 使用 Dockerfile 构建容器映像

  • 使用现有容器构建容器映像

  • 从头开始构建容器映像

  • 使用环境变量构建容器映像

  • 使用多阶段构建构建容器映像

技术要求

在上一章中,我们在以下目标操作系统上安装了 Docker:

  • macOS High Sierra 及以上版本

  • Windows 10 专业版

  • Ubuntu 18.04

在本章中,我们将使用我们的 Docker 安装来构建映像。虽然本章中的截图将来自我的首选操作系统 macOS,但我们将在迄今为止安装了 Docker 的三个操作系统上运行的 Docker 命令都可以工作。然而,一些支持命令可能只适用于 macOS 和基于 Linux 的操作系统。

本章中使用的所有代码可以在以下位置找到:github.com/PacktPublishing/Mastering-Docker-Third-Edition/tree/master/chapter02

查看以下视频以查看代码实际操作:

bit.ly/2D0JA6v

介绍 Dockerfile

在本节中,我们将深入介绍 Dockerfile,以及使用的最佳实践。那么什么是 Dockerfile?

Dockerfile只是一个包含一组用户定义指令的纯文本文件。当 Dockerfile 被docker image build命令调用时,它用于组装容器映像。Dockerfile 看起来像下面这样:

FROM alpine:latest
LABEL maintainer="Russ McKendrick <russ@mckendrick.io>"
LABEL description="This example Dockerfile installs NGINX."
RUN apk add --update nginx && \
 rm -rf /var/cache/apk/* && \
 mkdir -p /tmp/nginx/

COPY files/nginx.conf /etc/nginx/nginx.conf
COPY files/default.conf /etc/nginx/conf.d/default.conf
ADD files/html.tar.gz /usr/share/nginx/

EXPOSE 80/tcp

ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

如您所见,即使没有解释,也很容易了解 Dockerfile 的每个步骤指示build命令要做什么。

在我们继续处理之前的文件之前,我们应该快速了解一下 Alpine Linux。

Alpine Linux是一个小型、独立开发的非商业 Linux 发行版,旨在提供安全、高效和易用性。尽管体积小(见下一节),但由于其丰富的软件包仓库以及非官方的 grsecurity/PaX 移植,它在内核中提供了主动保护,可以防范数十种潜在的零日和其他漏洞。

Alpine Linux,由于其体积和强大的功能,已成为 Docker 官方容器镜像的默认基础。因此,在本书中我们将使用它。为了让你了解 Alpine Linux 官方镜像有多小,让我们将其与撰写时其他发行版进行比较:

从终端输出可以看出,Alpine Linux 的体积仅为 4.41 MB,而最大的镜像 Fedora 则为 253 MB。Alpine Linux 的裸机安装体积约为 130 MB,仍然几乎是 Fedora 容器镜像的一半大小。

深入审查 Dockerfile

让我们来看看 Dockerfile 示例中使用的指令。我们将按照它们出现的顺序来看:

  • FROM   

  • LABEL

  • RUN

  • COPYADD

  • EXPOSE

  • ENTRYPOINTCMD

  • 其他 Dockerfile 指令

FROM

FROM指令告诉 Docker 你想要使用哪个基础镜像;如前所述,我们使用的是 Alpine Linux,所以我们只需输入镜像的名称和我们希望使用的发布标签。在我们的情况下,要使用最新的官方 Alpine Linux 镜像,我们只需要添加alpine:latest

LABEL

LABEL指令可用于向镜像添加额外信息。这些信息可以是版本号或描述等任何内容。同时建议限制使用标签的数量。良好的标签结构将有助于以后使用我们的镜像的其他人。

然而,使用太多标签也会导致镜像效率低下,因此我建议使用label-schema.org/中详细介绍的标签模式。你可以使用以下 Docker inspect命令查看容器的标签:

$ docker image inspect <IMAGE_ID>

或者,你可以使用以下内容来过滤标签:

$ docker image inspect -f {{.Config.Labels}} <IMAGE_ID>

在我们的示例 Dockerfile 中,我们添加了两个标签:

  1. maintainer="Russ McKendrick <russ@mckendrick.io>" 添加了一个标签,帮助镜像的最终用户识别谁在维护它

  2. description="This example Dockerfile installs NGINX." 添加了一个简要描述镜像的标签。

通常,最好在从镜像创建容器时定义标签,而不是在构建时,因此最好将标签限制在关于镜像的元数据上,而不是其他内容。

RUN

RUN指令是我们与镜像交互以安装软件和运行脚本、命令和其他任务的地方。从我们的RUN指令中可以看到,实际上我们运行了三个命令:

RUN apk add --update nginx && \
 rm -rf /var/cache/apk/* && \
 mkdir -p /tmp/nginx/

我们三个命令中的第一个相当于在 Alpine Linux 主机上有一个 shell 时运行以下命令:

$ apk add --update nginx

此命令使用 Alpine Linux 的软件包管理器安装 nginx。

我们使用&&运算符来在前一个命令成功时继续执行下一个命令。为了更清晰地显示我们正在运行的命令,我们还使用\来将命令分成多行,使其易于阅读。

我们链中的下一个命令删除任何临时文件等,以使我们的镜像尺寸最小化:

$ rm -rf /var/cache/apk/*

我们链中的最后一个命令创建了一个路径为/tmp/nginx/的文件夹,这样当我们运行容器时,nginx 将能够正确启动:

$ mkdir -p /tmp/nginx/

我们也可以在 Dockerfile 中使用以下内容来实现相同的结果:

RUN apk add --update nginx
RUN rm -rf /var/cache/apk/*
RUN mkdir -p /tmp/nginx/

然而,就像添加多个标签一样,这被认为是低效的,因为它会增加镜像的总体大小,大多数情况下我们应该尽量避免这种情况。当然也有一些有效的用例,我们将在本章后面进行讨论。在大多数情况下,构建镜像时应避免这种命令的运行。

COPY 和 ADD

乍一看,COPYADD看起来像是在执行相同的任务;然而,它们之间有一些重要的区别。COPY指令是两者中更为直接的:

COPY files/nginx.conf /etc/nginx/nginx.conf
COPY files/default.conf /etc/nginx/conf.d/default.conf

正如你可能已经猜到的那样,我们正在从构建镜像的主机上的文件夹中复制两个文件。第一个文件是nginx.conf,其中包含一个基本的 nginx 配置文件:

user nginx;
worker_processes 1;

error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
 worker_connections 1024;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    access_log /var/log/nginx/access.log main;
    sendfile off;
    keepalive_timeout 65;
    include /etc/nginx/conf.d/*.conf;
}

这将覆盖作为 APK 安装的一部分安装的 NGINX 配置在RUN指令中。接下来的文件default.conf是我们可以配置的最简单的虚拟主机,并且具有以下内容:

server {
  location / {
      root /usr/share/nginx/html;
  }
}

同样,这将覆盖任何现有文件。到目前为止,一切都很好,那么为什么我们要使用ADD指令呢?在我们的情况下,看起来像是以下的样子:

ADD files/html.tar.gz /usr/share/nginx/

正如你所看到的,我们正在添加一个名为html.tar.gz的文件,但实际上我们在 Dockerfile 中并没有对存档进行任何操作。这是因为ADD会自动上传、解压缩并将生成的文件夹和文件放置在我们告诉它的路径上,而在我们的情况下是/usr/share/nginx/。这给了我们我们在default.conf文件中定义的虚拟主机块中的 web 根目录/usr/share/nginx/html/

ADD指令也可以用于从远程源添加内容。例如,考虑以下情况:

ADD http://www.myremotesource.com/files/html.tar.gz /usr/share/nginx/

上述命令行将从http://www.myremotesource.com/files/下载html.tar.gz并将文件放置在镜像的/usr/share/nginx/文件夹中。来自远程源的存档文件被视为文件,不会被解压缩,这在使用它们时需要考虑到,这意味着文件必须在RUN指令之前添加,这样我们就可以手动解压缩文件夹并删除html.tar.gz文件。

EXPOSE

EXPOSE指令让 Docker 知道当镜像被执行时,定义的端口和协议将在运行时暴露。这个指令不会将端口映射到主机机器,而是打开端口以允许在容器网络上访问服务。

例如,在我们的 Dockerfile 中,我们告诉 Docker 在每次运行镜像时打开端口80

EXPOSE 80/tcp

ENTRYPOINT 和 CMD

使用ENTRYPOINT而不是CMD的好处是,你可以将它们结合使用。ENTRYPOINT可以单独使用,但请记住,只有在想要使容器可执行时才会单独使用ENTRYPOINT

作为参考,如果你考虑一些你可能使用的 CLI 命令,你必须指定不仅仅是 CLI 命令。你可能还需要添加你希望命令解释的额外参数。这将是仅使用ENTRYPOINT的用例。

例如,如果你想要一个默认命令在容器内执行,你可以做类似以下示例的事情,但一定要使用一个保持容器活动的命令。在我们的情况下,我们使用以下命令:

ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

这意味着每当我们从我们的镜像启动一个容器时,nginx 二进制文件都会被执行,因为我们已经将其定义为我们的ENTRYPOINT,然后我们定义的CMD也会被执行,这相当于运行以下命令:

$ nginx -g daemon off;

ENTRYPOINT的另一个用法示例如下:

$ docker container run --name nginx-version dockerfile-example -v

这相当于在我们的主机上运行以下命令:

$ nginx -v

请注意,我们不必告诉 Docker 使用 nginx。因为我们将 nginx 二进制文件作为我们的入口点,我们传递的任何命令都会覆盖 Dockerfile 中定义的CMD

这将显示我们安装的 nginx 版本,并且我们的容器将停止,因为 nginx 二进制文件只会被执行以显示版本信息,然后进程将停止。我们将在本章后面看一下这个示例,一旦我们构建了我们的镜像。

其他 Dockerfile 指令

我们的示例 Dockerfile 中还有一些指令没有包括在内。让我们在这里看一下它们。

USER

USER指令允许您在运行命令时指定要使用的用户名。USER指令可以在 Dockerfile 中的RUN指令、CMD指令或ENTRYPOINT指令上使用。此外,USER指令中定义的用户必须存在,否则您的镜像将无法构建。使用USER指令还可能引入权限问题,不仅在容器本身上,还在挂载卷时也可能出现权限问题。

WORKDIR

WORKDIR指令为USER指令可以使用的相同一组指令(RUNCMDENTRYPOINT)设置工作目录。它还允许您使用CMDADD指令。

ONBUILD

ONBUILD指令允许您存储一组命令,以便在将来使用该镜像作为另一个容器镜像的基础镜像时使用。

例如,如果您想要向开发人员提供一个镜像,他们都有不同的代码库要测试,您可以使用ONBUILD指令在实际需要代码之前先打好基础。然后,开发人员只需将他们的代码添加到您告诉他们的目录中,当他们运行新的 Docker 构建命令时,它将把他们的代码添加到运行中的镜像中。

ONBUILD指令可以与ADDRUN指令一起使用,例如以下示例:

ONBUILD RUN apk update && apk upgrade && rm -rf /var/cache/apk/*

这将在我们的镜像作为另一个容器镜像的基础时运行更新和软件包升级。

ENV

ENV指令在构建镜像时和执行镜像时设置环境变量。这些变量在启动镜像时可以被覆盖。

Dockerfile - 最佳实践

现在我们已经介绍了 Dockerfile 指令,让我们来看看编写我们自己的 Dockerfile 的最佳实践:

  • 你应该养成使用.dockerignore文件的习惯。我们将在下一节介绍.dockerignore文件;如果你习惯使用.gitignore文件,它会让你感到非常熟悉。它在构建过程中将忽略你在文件中指定的项目。

  • 记住每个文件夹只有一个 Dockerfile,以帮助你组织你的容器。

  • 为你的 Dockerfile 使用版本控制系统,比如 Git;就像任何其他基于文本的文档一样,版本控制将帮助你不仅向前,还可以向后移动,如果有必要的话。

  • 尽量减少每个镜像安装的软件包数量。在构建镜像时,你想要实现的最大目标之一就是尽量保持镜像尽可能小。不安装不必要的软件包将极大地帮助实现这一目标。

  • 确保每个容器只有一个应用程序进程。每次需要一个新的应用程序进程时,最佳实践是使用一个新的容器来运行该应用程序。

  • 保持简单;过度复杂化你的 Dockerfile 会增加臃肿,也可能在后续过程中引发问题。

  • 以实例学习!Docker 自己为在 Docker Hub 上托管的官方镜像发布制定了相当详细的风格指南。你可以在本章末尾的进一步阅读部分找到相关链接。

构建容器镜像

在这一部分,我们将介绍docker image build命令。这就是所谓的关键时刻。现在是时候构建我们未来镜像的基础了。我们将探讨不同的方法来实现这一目标。可以将其视为您之前使用虚拟机创建的模板。这将通过完成艰苦的工作来节省时间;您只需创建需要添加到新镜像中的应用程序。

在使用docker build命令时,有很多开关可以使用。因此,让我们在docker image build命令上使用非常方便的--help开关,查看我们可以做的一切。

$ docker image build --help

然后列出了许多不同的标志,您可以在构建映像时传递这些标志。现在,这可能看起来很多,但在所有这些选项中,我们只需要使用--tag或其简写-t来命名我们的映像。

您可以使用其他选项来限制构建过程将使用多少 CPU 和内存。在某些情况下,您可能不希望build命令占用尽可能多的 CPU 或内存。该过程可能会运行得慢一些,但如果您在本地计算机或生产服务器上运行它,并且构建过程很长,您可能希望设置一个限制。还有一些选项会影响启动以构建我们的映像的容器的网络配置。

通常,您不会使用--file-f开关,因为您是从包含 Dockerfile 的同一文件夹运行docker build命令。将 Dockerfile 放在单独的文件夹中有助于整理文件,并保持文件的命名约定相同。

值得一提的是,虽然您可以在构建时作为参数传递额外的环境变量,但它们仅在构建时使用,您的容器映像不会继承它们。这对于传递诸如代理设置之类的信息非常有用,这些信息可能仅适用于您的初始构建/测试环境。

如前所述,.dockerignore文件用于排除我们不希望包含在docker build中的文件或文件夹,默认情况下,与 Dockerfile 相同文件夹中的所有文件都将被上传。我们还讨论了将 Dockerfile 放在单独的文件夹中,对.dockerignore也适用。它应该放在放置 Dockerfile 的文件夹中。

将要在映像中使用的所有项目放在同一个文件夹中,这将有助于将.dockerignore文件中的项目数量(如果有的话)保持在最低限度。

使用 Dockerfile 构建容器映像

我们将要查看的第一种用于构建基本容器映像的方法是创建一个 Dockerfile。实际上,我们将使用上一节中的 Dockerfile,然后针对它执行docker image build命令,以获得一个 nginx 映像。因此,让我们再次开始查看 Dockerfile:

FROM alpine:latest
LABEL maintainer="Russ McKendrick <russ@mckendrick.io>"
LABEL description="This example Dockerfile installs NGINX."
RUN apk add --update nginx && \
 rm -rf /var/cache/apk/* && \
 mkdir -p /tmp/nginx/

COPY files/nginx.conf /etc/nginx/nginx.conf
COPY files/default.conf /etc/nginx/conf.d/default.conf
ADD files/html.tar.gz /usr/share/nginx/

EXPOSE 80/tcp

ENTRYPOINT ["nginx"]
CMD ["-g", "daemon off;"]

不要忘记您还需要在文件夹中的default.confhtml.tar.gznginx.conf文件。您可以在附带的 GitHub 存储库中找到这些文件。

因此,我们可以通过两种方式构建此图像。第一种方式是在使用docker image build命令时指定-f开关。我们还将利用-t开关为新图像指定一个唯一名称:

$ docker image build --file <path_to_Dockerfile> --tag <REPOSITORY>:<TAG> .

现在,<REPOSITORY>通常是您在 Docker Hub 上注册的用户名。我们将在第三章存储和分发图像中更详细地讨论这一点;目前,我们将使用local,而<TAG>是您想要提供的唯一容器值。通常,这将是一个版本号或其他描述符:

$ docker image build --file /path/to/your/dockerfile --tag local:dockerfile-example .

通常,不使用--file开关,当您需要将其他文件包含在新图像中时,可能会有些棘手。进行构建的更简单的方法是将 Dockerfile 单独放在一个文件夹中,以及使用ADDCOPY指令将任何其他文件注入到图像中:

$ docker image build --tag local:dockerfile-example .

最重要的是要记住最后的句点(或周期)。这是告诉docker image build命令在当前文件夹中构建的指示。构建图像时,您应该看到类似以下终端输出:

构建完成后,您应该能够运行以下命令来检查图像是否可用,以及图像的大小:

$ docker image ls

如您从以下终端输出中所见,我的图像大小为 5.98 MB:

您可以通过运行此命令启动一个包含您新构建的图像的容器:

$ docker container run -d --name dockerfile-example -p 8080:80 local:dockerfile-example

这将启动一个名为dockerfile-example的容器,您可以使用以下命令检查它是否正在运行:

$ docker container ls 

打开浏览器并转到http://localhost:8080/应该会显示一个非常简单的网页,看起来像以下内容:

接下来,我们可以快速运行本章前一节提到的一些命令,首先是以下命令:

$ docker container run --name nginx-version local:dockerfile-example -v

如您从以下终端输出中所见,我们目前正在运行 nginx 版本 1.14.0:

接下来,我们可以看一下要运行的下一个命令,现在我们已经构建了第一个图像,显示了我们在构建时嵌入的标签。要查看此信息,请运行以下命令:

$ docker image inspect -f {{.Config.Labels}} local:dockerfile-example

如您从以下输出中所见,这显示了我们输入的信息:

在我们继续之前,你可以使用以下命令停止和删除我们启动的容器:

$ docker container stop dockerfile-example
$ docker container rm dockerfile-example nginx-version  

我们将在第四章“管理容器”中更详细地介绍 Docker 容器命令。

使用现有容器

构建基础镜像的最简单方法是从 Docker Hub 中的官方镜像之一开始。Docker 还将这些官方构建的 Dockerfile 保存在它们的 GitHub 存储库中。因此,你至少有两种选择可以使用其他人已经创建的现有镜像。通过使用 Dockerfile,你可以准确地看到构建中包含了什么,并添加你需要的内容。然后,如果你想以后更改或共享它,你可以对该 Dockerfile 进行版本控制。

还有另一种实现这一点的方法;然而,这并不被推荐或认为是良好的做法,我强烈不建议你使用它。

我只会在原型阶段使用这种方法,以检查我运行的命令是否在交互式 shell 中按预期工作,然后再将它们放入 Dockerfile 中。你应该总是使用 Dockerfile。

首先,我们应该下载我们想要用作基础的镜像;和以前一样,我们将使用 Alpine Linux:

$ docker image pull alpine:latest

接下来,我们需要在前台运行一个容器,这样我们就可以与它进行交互:

$ docker container run -it --name alpine-test alpine /bin/sh

容器运行后,你可以使用apk命令(在这种情况下)或者你的 Linux 版本的软件包管理命令来添加必要的软件包。

例如,以下命令将安装 nginx:

$ apk update
$ apk upgrade
$ apk add --update nginx
$ rm -rf /var/cache/apk/*
$ mkdir -p /tmp/nginx/
$ exit

安装完所需的软件包后,你需要保存容器。在前面一组命令的末尾使用exit命令将停止运行的容器,因为我们正在从中分离的 shell 进程恰好是保持容器在前台运行的进程。你可以在终端输出中看到这一点,如下所示:

在这一点上,你应该真的停下来;我不建议你使用前面的命令来创建和分发镜像,除了我们将在本节的下一部分中涵盖的一个用例之外。

因此,要将我们停止的容器保存为镜像,你需要执行类似以下的操作:

$ docker container commit <container_name> <REPOSITORY>:<TAG>

例如,我运行了以下命令来保存我们启动和自定义的容器的副本:

$ docker container commit alpine-test local:broken-container 

注意我如何称呼我的镜像为broken-container?采用这种方法的一个用例是,如果由于某种原因您的容器出现问题,那么将失败的容器保存为镜像非常有用,甚至将其导出为 TAR 文件与他人分享,以便在解决问题时获得一些帮助。

要保存镜像文件,只需运行以下命令:

$ docker image save -o <name_of_file.tar> <REPOSITORY>:<TAG>

因此,对于我们的示例,我运行了以下命令:

$ docker image save -o broken-container.tar local:broken-container

这给了我一个名为broken-container.tar的 6.6 MB 文件。虽然我们有这个文件,您可以解压它并查看一下,就像您可以从以下结构中看到的那样:

镜像由一组 JSON 文件、文件夹和其他 TAR 文件组成。所有镜像都遵循这个结构,所以您可能会想,为什么这种方法如此糟糕

最大的原因是信任——如前所述,您的最终用户将无法轻松地看到他们正在运行的镜像中有什么。您会随机下载一个来自未知来源的预打包镜像来运行您的工作负载吗,而不检查镜像是如何构建的?谁知道它是如何配置的,安装了什么软件包?使用 Dockerfile,您可以看到创建镜像时执行了什么,但使用此处描述的方法,您对此一无所知。

另一个原因是很难为您构建一个良好的默认设置;例如,如果您以这种方式构建您的镜像,那么您实际上将无法充分利用诸如ENTRYPOINTCMD等功能,甚至是最基本的指令,比如EXPOSE。相反,用户将不得不在其docker container run命令期间定义所需的一切。

在 Docker 早期,分发以这种方式准备的镜像是常见做法。事实上,我自己也有过这样的行为,因为作为一名运维人员,启动一个“机器”,引导它,然后创建一个黄金镜像是完全合理的。幸运的是,在过去的几年里,Docker 已经将构建功能扩展到了这一点,以至于这个选项根本不再被考虑。

从头开始构建容器镜像

到目前为止,我们一直在使用 Docker Hub 上准备好的镜像作为我们的基础镜像。完全可以避免这一点(在某种程度上),并从头开始创建自己的镜像。

现在,当您通常听到短语from **scratch时,它的字面意思是从零开始。这就是我们在这里所做的——您什么都没有,必须在此基础上构建。这可能是一个好处,因为它将使镜像大小非常小,但如果您对 Docker 还比较新,这也可能是有害的,因为它可能会变得复杂。

Docker 已经为我们做了一些艰苦的工作,并在 Docker Hub 上创建了一个名为scratch的空 TAR 文件;您可以在 Dockerfile 的FROM部分中使用它。您可以基于此构建整个 Docker 构建,然后根据需要添加部分。

再次,让我们以 Alpine Linux 作为镜像的基本操作系统。这样做的原因不仅包括它被分发为 ISO、Docker 镜像和各种虚拟机镜像,还包括整个操作系统作为压缩的 TAR 文件可用。您可以在存储库或 Alpine Linux 下载页面上找到下载链接。

要下载副本,只需从下载页面中选择适当的下载,该页面位于www.alpinelinux.org/downloads/。我使用的是x86_64,来自MINI ROOT FILESYSTEM部分。

一旦下载完成,您需要创建一个使用scratch的 Dockerfile,然后添加tar.gz文件,确保使用正确的文件,就像下面的例子一样:

FROM scratch
ADD files/alpine-minirootfs-3.8.0-x86_64.tar.gz /
CMD ["/bin/sh"]

现在您已经有了 Dockerfile 和操作系统的 TAR 文件,您可以通过运行以下命令构建您的镜像,就像构建任何其他 Docker 镜像一样:

$ docker image build --tag local:fromscratch .

您可以通过运行以下命令来比较镜像大小与我们构建的其他容器镜像:

$ docker image ls

正如您在以下截图中所看到的,我构建的镜像与我们从 Docker Hub 使用的 Alpine Linux 镜像的大小完全相同:

现在我们已经构建了自己的镜像,可以通过运行以下命令来测试它:

$ docker container run -it --name alpine-test local:fromscratch /bin/sh

如果出现错误,则可能已经创建或正在运行名为 alpine-test 的容器。通过运行docker container stop alpine-test,然后运行docker container rm alpine-test来删除它。

这应该会启动到 Alpine Linux 镜像的 shell 中。您可以通过运行以下命令来检查:

$ cat /etc/*release

这将显示容器正在运行的版本信息。要了解整个过程的样子,请参见以下终端输出:

虽然一切看起来都很简单,这只是因为 Alpine Linux 包装他们的操作系统的方式。当你选择使用其他分发版包装他们的操作系统时,情况可能会变得更加复杂。

有几种工具可以用来生成操作系统的捆绑包。我们不会在这里详细介绍如何使用这些工具,因为如果你必须考虑这种方法,你可能有一些非常具体的要求。在本章末尾的进一步阅读部分有一些工具的列表。

那么这些要求可能是什么呢?对于大多数人来说,这将是遗留应用程序;例如,如果你有一个需要不再受支持或在 Docker Hub 上不再可用的操作系统的应用程序,但你需要一个更现代的平台来支持该应用程序,那么怎么办?嗯,你应该能够启动你的镜像并在那里安装应用程序,从而使你能够在现代、可支持的操作系统/架构上托管你的旧遗留应用程序。

使用环境变量

在本节中,我们将介绍非常强大的环境变量ENVs),因为你将经常看到它们。你可以在 Dockerfile 中使用 ENVs 来做很多事情。如果你熟悉编码,这些可能对你来说很熟悉。

对于像我这样的其他人,起初它们似乎令人生畏,但不要灰心。一旦你掌握了它们,它们将成为一个很好的资源。它们可以用于在运行容器时设置信息,这意味着你不必去更新 Dockerfile 中的许多命令或在服务器上运行的脚本。

要在 Dockerfile 中使用 ENVs,你可以使用ENV指令。ENV指令的结构如下:

ENV <key> <value>
ENV username admin

或者,你也可以在两者之间使用等号:

ENV <key>=<value>
ENV username=admin

现在,问题是,为什么有两种定义它们的方式,它们有什么区别?在第一个例子中,你只能在一行上设置一个ENV;然而,它很容易阅读和理解。在第二个ENV示例中,你可以在同一行上设置多个环境变量,如下所示:

ENV username=admin database=wordpress tableprefix=wp

你可以使用 Docker inspect命令查看镜像上设置了哪些 ENVs:

$ docker image inspect <IMAGE_ID> 

现在我们知道它们在 Dockerfile 中需要如何设置,让我们看看它们的实际操作。到目前为止,我们一直在使用 Dockerfile 构建一个只安装了 nginx 的简单镜像。让我们来构建一些更加动态的东西。使用 Alpine Linux,我们将执行以下操作:

  • 设置ENV来定义我们想要安装的 PHP 版本。

  • 安装 Apache2 和我们选择的 PHP 版本。

  • 设置镜像,使 Apache2 无问题启动。

  • 删除默认的index.html并添加一个显示phpinfo命令结果的index.php文件。

  • 在容器上暴露端口80

  • 将 Apache 设置为默认进程。

我们的 Dockerfile 如下所示:

FROM alpine:latest
LABEL maintainer="Russ McKendrick <russ@mckendrick.io>"
LABEL description="This example Dockerfile installs Apache & PHP."
ENV PHPVERSION=7

RUN apk add --update apache2 php${PHPVERSION}-apache2 php${PHPVERSION} && \
 rm -rf /var/cache/apk/* && \
 mkdir /run/apache2/ && \
 rm -rf /var/www/localhost/htdocs/index.html && \
 echo "<?php phpinfo(); ?>" > /var/www/localhost/htdocs/index.php && \
 chmod 755 /var/www/localhost/htdocs/index.php

EXPOSE 80/tcp

ENTRYPOINT ["httpd"]
CMD ["-D", "FOREGROUND"]

如您所见,我们选择安装了 PHP7;我们可以通过运行以下命令构建镜像:

$ docker build --tag local/apache-php:7 .

注意我们已经稍微改变了命令。这次,我们将镜像称为local/apache-php,并将版本标记为7。通过运行上述命令获得的完整输出可以在这里找到:

Sending build context to Docker daemon 2.56kB
Step 1/8 : FROM alpine:latest
 ---> 11cd0b38bc3c
Step 2/8 : LABEL maintainer="Russ McKendrick <russ@mckendrick.io>"
 ---> Using cache
 ---> 175e9ebf182b
Step 3/8 : LABEL description="This example Dockerfile installs Apache & PHP."
 ---> Running in 095e42841956
Removing intermediate container 095e42841956
 ---> d504837e80a4
Step 4/8 : ENV PHPVERSION=7
 ---> Running in 0df665a9b23e
Removing intermediate container 0df665a9b23e
 ---> 7f2c212a70fc
Step 5/8 : RUN apk add --update apache2 php${PHPVERSION}-apache2 php${PHPVERSION} && rm -rf /var/cache/apk/* && mkdir /run/apache2/ && rm -rf /var/www/localhost/htdocs/index.html && echo "<?php phpinfo(); ?>" > /var/www/localhost/htdocs/index.php && chmod 755 /var/www/localhost/htdocs/index.php
 ---> Running in ea77c54e08bf
fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/community/x86_64/APKINDEX.tar.gz
(1/14) Installing libuuid (2.32-r0)
(2/14) Installing apr (1.6.3-r1)
(3/14) Installing expat (2.2.5-r0)
(4/14) Installing apr-util (1.6.1-r2)
(5/14) Installing pcre (8.42-r0)
(6/14) Installing apache2 (2.4.33-r1)
Executing apache2-2.4.33-r1.pre-install
(7/14) Installing php7-common (7.2.8-r1)
(8/14) Installing ncurses-terminfo-base (6.1-r0)
(9/14) Installing ncurses-terminfo (6.1-r0)
(10/14) Installing ncurses-libs (6.1-r0)
(11/14) Installing libedit (20170329.3.1-r3)
(12/14) Installing libxml2 (2.9.8-r0)
(13/14) Installing php7 (7.2.8-r1)
(14/14) Installing php7-apache2 (7.2.8-r1)
Executing busybox-1.28.4-r0.trigger
OK: 26 MiB in 27 packages
Removing intermediate container ea77c54e08bf
 ---> 49b49581f8e2
Step 6/8 : EXPOSE 80/tcp
 ---> Running in e1cbc518ef07
Removing intermediate container e1cbc518ef07
 ---> a061e88eb39f
Step 7/8 : ENTRYPOINT ["httpd"]
 ---> Running in 93ac42d6ce55
Removing intermediate container 93ac42d6ce55
 ---> 9e09239021c2
Step 8/8 : CMD ["-D", "FOREGROUND"]
 ---> Running in 733229cc945a
Removing intermediate container 733229cc945a
 ---> 649b432e8d47
Successfully built 649b432e8d47
Successfully tagged local/apache-php:7 

我们可以通过运行以下命令来检查一切是否按预期运行,以使用该镜像启动一个容器:

$ docker container run -d -p 8080:80 --name apache-php7 local/apache-php:7

一旦它启动,打开浏览器并转到http://localhost:8080/,您应该看到一个显示正在使用 PHP7 的页面:

不要被接下来的部分所困惑;没有 PHP6。要了解为什么没有,请访问wiki.php.net/rfc/php6

现在,在您的 Dockerfile 中,将PHPVERSION7更改为5,然后运行以下命令构建新镜像:

$ docker image build --tag local/apache-php:5 .

如您从以下终端输出中所见,大部分输出都是相同的,除了正在安装的软件包:

Sending build context to Docker daemon 2.56kB
Step 1/8 : FROM alpine:latest
 ---> 11cd0b38bc3c
Step 2/8 : LABEL maintainer="Russ McKendrick <russ@mckendrick.io>"
 ---> Using cache
 ---> 175e9ebf182b
Step 3/8 : LABEL description="This example Dockerfile installs Apache & PHP."
 ---> Using cache
 ---> d504837e80a4
Step 4/8 : ENV PHPVERSION=5
 ---> Running in 0646b5e876f6
Removing intermediate container 0646b5e876f6
 ---> 3e17f6c10a50
Step 5/8 : RUN apk add --update apache2 php${PHPVERSION}-apache2 php${PHPVERSION} && rm -rf /var/cache/apk/* && mkdir /run/apache2/ && rm -rf /var/www/localhost/htdocs/index.html && echo "<?php phpinfo(); ?>" > /var/www/localhost/htdocs/index.php && chmod 755 /var/www/localhost/htdocs/index.php
 ---> Running in d55a7726e9a7
fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/main/x86_64/APKINDEX.tar.gz
fetch http://dl-cdn.alpinelinux.org/alpine/v3.8/community/x86_64/APKINDEX.tar.gz
(1/10) Installing libuuid (2.32-r0)
(2/10) Installing apr (1.6.3-r1)
(3/10) Installing expat (2.2.5-r0)
(4/10) Installing apr-util (1.6.1-r2)
(5/10) Installing pcre (8.42-r0)
(6/10) Installing apache2 (2.4.33-r1)
Executing apache2-2.4.33-r1.pre-install
(7/10) Installing php5 (5.6.37-r0)
(8/10) Installing php5-common (5.6.37-r0)
(9/10) Installing libxml2 (2.9.8-r0)
(10/10) Installing php5-apache2 (5.6.37-r0)
Executing busybox-1.28.4-r0.trigger
OK: 32 MiB in 23 packages
Removing intermediate container d55a7726e9a7
 ---> 634ab90b168f
Step 6/8 : EXPOSE 80/tcp
 ---> Running in a59f40d3d5df
Removing intermediate container a59f40d3d5df
 ---> d1aadf757f59
Step 7/8 : ENTRYPOINT ["httpd"]
 ---> Running in c7a1ab69356d
Removing intermediate container c7a1ab69356d
 ---> 22a9eb0e6719
Step 8/8 : CMD ["-D", "FOREGROUND"]
 ---> Running in 8ea92151ce22
Removing intermediate container 8ea92151ce22
 ---> da34eaff9541
Successfully built da34eaff9541
Successfully tagged local/apache-php:5

我们可以通过运行以下命令在端口9090上启动一个容器:

$ docker container run -d -p 9090:80 --name apache-php5 local/apache-php:5

再次打开您的浏览器,但这次转到http://localhost:9090/,应该显示我们正在运行 PHP5:

最后,您可以通过运行此命令来比较镜像的大小:

$ docker image ls

您应该看到以下终端输出:

这表明 PHP7 镜像比 PHP5 镜像要小得多。让我们讨论当我们构建了两个不同的容器镜像时实际发生了什么。

那么发生了什么?嗯,当 Docker 启动 Alpine Linux 镜像来创建我们的镜像时,它首先做的是设置我们定义的 ENV,使它们对容器内的所有 shell 可用。

幸运的是,Alpine Linux 中 PHP 的命名方案只是替换版本号并保持我们需要安装的软件包的相同名称,这意味着我们运行以下命令:

RUN apk add --update apache2 php${PHPVERSION}-apache2 php${PHPVERSION}

但实际上它被解释为以下内容:

RUN apk add --update apache2 php7-apache2 php7

或者,对于 PHP5,它被解释为以下内容:

RUN apk add --update apache2 php5-apache2 php5

这意味着我们不必手动替换版本号来浏览整个 Dockerfile。当从远程 URL 安装软件包时,这种方法特别有用,比如软件发布页面。

接下来是一个更高级的示例——一个安装和配置 HashiCorp 的 Consul 的 Dockerfile。在这个 Dockerfile 中,我们使用环境变量来定义文件的版本号和 SHA256 哈希:

FROM alpine:latest
LABEL maintainer="Russ McKendrick <russ@mckendrick.io>"
LABEL description="An image with the latest version on Consul."

ENV CONSUL_VERSION=1.2.2 CONSUL_SHA256=7fa3b287b22b58283b8bd5479291161af2badbc945709eb5412840d91b912060

RUN apk add --update ca-certificates wget && \
 wget -O consul.zip https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_linux_amd64.zip && \
 echo "$CONSUL_SHA256 *consul.zip" | sha256sum -c - && \
 unzip consul.zip && \
 mv consul /bin/ && \
 rm -rf consul.zip && \
 rm -rf /tmp/* /var/cache/apk/*

EXPOSE 8300 8301 8301/udp 8302 8302/udp 8400 8500 8600 8600/udp

VOLUME [ "/data" ]

ENTRYPOINT [ "/bin/consul" ]
CMD [ "agent", "-data-dir", "/data", "-server", "-bootstrap-expect", "1", "-client=0.0.0.0"]

正如你所看到的,Dockerfiles 可以变得非常复杂,使用 ENV 可以帮助维护。每当 Consul 的新版本发布时,我只需要更新 ENV 行并将其提交到 GitHub,这将触发构建新镜像——如果我们配置了的话;我们将在下一章中讨论这个问题。

你可能也注意到我们在 Dockerfile 中使用了一个我们还没有涉及的指令。别担心,我们将在第四章中讨论 VOLUME 指令,管理容器

使用多阶段构建

在我们使用 Dockerfiles 和构建容器镜像的旅程的最后部分,我们将看看使用一种相对新的构建镜像的方法。在本章的前几节中,我们看到直接通过包管理器(例如 Alpine Linux 的 APK)或者在最后一个示例中,通过从软件供应商下载预编译的二进制文件将二进制文件添加到我们的镜像。

如果我们想要在构建过程中编译我们自己的软件怎么办?从历史上看,我们将不得不使用包含完整构建环境的容器镜像,这可能非常庞大。这意味着我们可能不得不拼凑一个运行类似以下过程的脚本:

  1. 下载构建环境容器镜像并启动“构建”容器

  2. 将源代码复制到“构建”容器中

  3. 在“构建”容器上编译源代码

  4. 将编译的二进制文件复制到“build”容器之外

  5. 移除“build”容器

  6. 使用预先编写的 Dockerfile 构建镜像并将二进制文件复制到其中

这是很多逻辑——在理想的世界中,它应该是 Docker 的一部分。幸运的是,Docker 社区也这样认为,并在 Docker 17.05 中引入了实现这一功能的多阶段构建。

Dockerfile 包含两个不同的构建阶段。第一个名为builder,使用来自 Docker Hub 的官方 Go 容器镜像。在这里,我们正在安装先决条件,直接从 GitHub 下载源代码,然后将其编译成静态二进制文件:

FROM golang:latest as builder
WORKDIR /go-http-hello-world/
RUN go get -d -v golang.org/x/net/html 
ADD https://raw.githubusercontent.com/geetarista/go-http-hello-world/master/hello_world/hello_world.go ./hello_world.go
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .

FROM scratch 
COPY --from=builder /go-http-hello-world/app .
CMD ["./app"] 

由于我们的静态二进制文件具有内置的 Web 服务器,从操作系统的角度来看,我们实际上不需要其他任何东西。因此,我们可以使用scratch作为基础镜像,这意味着我们的镜像将只包含我们从构建镜像中复制的静态二进制文件,不会包含任何builder环境。

构建镜像,我们只需要运行以下命令:

$ docker image build --tag local:go-hello-world .

命令的输出可以在以下代码块中找到——有趣的部分发生在第 5 步和第 6 步之间:

Sending build context to Docker daemon 9.216kB
Step 1/8 : FROM golang:latest as builder
latest: Pulling from library/golang
55cbf04beb70: Pull complete
1607093a898c: Pull complete
9a8ea045c926: Pull complete
d4eee24d4dac: Pull complete
9c35c9787a2f: Pull complete
6a66653f6388: Pull complete
102f6b19f797: Pull complete
Digest: sha256:957f390aceead48668eb103ef162452c6dae25042ba9c41762f5210c5ad3aeea
Status: Downloaded newer image for golang:latest
 ---> d0e7a411e3da
Step 2/8 : WORKDIR /go-http-hello-world/
 ---> Running in e1d56745f358
Removing intermediate container e1d56745f358
 ---> f18dfc0166a0
Step 3/8 : RUN go get -d -v golang.org/x/net/html
 ---> Running in 5e97d81db53c
Fetching https://golang.org/x/net/html?go-get=1
Parsing meta tags from https://golang.org/x/net/html?go-get=1 (status code 200)
get "golang.org/x/net/html": found meta tag get.metaImport{Prefix:"golang.org/x/net", VCS:"git", RepoRoot:"https://go.googlesource.com/net"} at https://golang.org/x/net/html?go-get=1
get "golang.org/x/net/html": verifying non-authoritative meta tag
Fetching https://golang.org/x/net?go-get=1
Parsing meta tags from https://golang.org/x/net?go-get=1 (status code 200)
golang.org/x/net (download)
Removing intermediate container 5e97d81db53c
 ---> f94822756a52
Step 4/8 : ADD https://raw.githubusercontent.com/geetarista/go-http-hello-world/master/hello_world/hello_world.go ./hello_world.go
Downloading 393B
 ---> ecf3944740e1
Step 5/8 : RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
 ---> Running in 6e2d39c4d8ba
Removing intermediate container 6e2d39c4d8ba
 ---> 247fcbfb7a4d
Step 6/8 : FROM scratch
 --->
Step 7/8 : COPY --from=builder /go-http-hello-world/app .
 ---> a69cf59ab1d3
Step 8/8 : CMD ["./app"]
 ---> Running in c99076fad7fb
Removing intermediate container c99076fad7fb
 ---> 67296001bdc0
Successfully built 67296001bdc0
Successfully tagged local:go-hello-world

如您所见,在第 5 步和第 6 步之间,我们的二进制文件已经被编译,包含builder环境的容器已被移除,留下了存储我们二进制文件的镜像。第 7 步将二进制文件复制到使用 scratch 启动的新容器中,只留下我们需要的内容。

如果你运行以下命令,你会明白为什么不应该将应用程序与其构建环境一起发布是个好主意:

$ docker image ls

我们的输出截图显示,golang镜像为794MB;加上我们的源代码和先决条件后,大小增加到832MB

然而,最终镜像只有6.56MB。我相信您会同意这是相当大的空间节省。它还遵循了本章前面讨论的最佳实践,只在镜像中包含与我们应用程序相关的内容,并且非常小。

您可以通过使用以下命令启动一个容器来测试该应用程序:

$ docker container run -d -p 8000:80 --name go-hello-world local:go-hello-world

应用程序可以通过浏览器访问,并在每次加载页面时简单地递增计数器。要在 macOS 和 Linux 上进行测试,可以使用curl命令,如下所示:

$ curl http://localhost:8000/

这应该给您类似以下的东西:

Windows 用户可以在浏览器中简单地访问http://localhost:8000/。要停止和删除正在运行的容器,请使用以下命令:

$ docker container stop go-hello-world
$ docker container rm go-hello-world

正如您所看到的,使用多阶段构建是一个相对简单的过程,并且符合应该已经开始感到熟悉的指令。

摘要

在本章中,我们深入了解了 Dockerfiles,编写它们的最佳实践,docker image build 命令以及我们可以构建容器的各种方式。我们还了解了可以从 Dockerfile 传递到容器内各个项目的环境变量。

在下一章中,现在我们知道如何使用 Dockerfiles 构建镜像,我们将看看 Docker Hub 以及使用注册表服务带来的所有优势。我们还将看看 Docker 注册表,它是开源的,因此您可以自己创建一个存储镜像的地方,而无需支付 Docker Enterprise 的费用,也可以使用第三方注册表服务。

问题

  1. 真或假:LABEL指令在构建完图像后会给图像打标签?

  2. ENTRYPOINTCMD指令之间有什么区别?

  3. 真或假:使用ADD指令时,无法下载并自动解压外部托管的存档?

  4. 使用现有容器作为图像基础的有效用途是什么?

  5. EXPOSE指令暴露了什么?

进一步阅读

您可以在以下位置找到官方 Docker 容器图像的指南:

一些帮助您从现有安装创建容器的工具如下:

最后,Go HTTP Hello World 应用程序的完整 GitHub 存储库可以在以下位置找到:

第三章:存储和分发镜像

在本章中,我们将涵盖几项服务,如 Docker Hub,允许您存储您的镜像,以及 Docker Registry,您可以用来运行 Docker 容器的本地存储。我们将审查这些服务之间的区别,以及何时以及如何使用它们。

本章还将介绍如何使用 Webhooks 设置自动构建,以及设置它们所需的所有组件。让我们快速看一下本章将涵盖的主题:

  • Docker Hub

  • Docker Store

  • Docker Registry

  • 第三方注册表

  • Microbadger

技术要求

在本章中,我们将使用我们的 Docker 安装来构建镜像。与之前一样,尽管本章的截图将来自我首选的操作系统 macOS,但我们将运行的命令将适用于上一章中涵盖的所有三个操作系统。本章中使用的代码的完整副本可以在以下位置找到:github.com/PacktPublishing/Mastering-Docker-Third-Edition/tree/master/chapter03

观看以下视频以查看代码的实际操作:

bit.ly/2EBVJjJ

Docker Hub

虽然在前两章中我们介绍了 Docker Hub,但除了使用docker image pull命令下载远程镜像之外,我们并没有与其互动太多。

在本节中,我们将重点关注 Docker Hub,它有一个免费的选项,您只能托管公开可访问的镜像,还有一个订阅选项,允许您托管自己的私有镜像。我们将关注 Docker Hub 的网络方面以及您可以在那里进行的管理。

主页位于hub.docker.com/,包含一个注册表格,并且在右上角有一个登录选项。如果您一直在尝试使用 Docker,那么您可能已经有一个 Docker ID。如果没有,请使用主页上的注册表格创建一个。如果您已经有 Docker ID,那么只需点击登录。

Docker Hub 是免费使用的,如果您不需要上传或管理自己的镜像,您不需要帐户来搜索拉取镜像。

仪表板

登录到 Docker Hub 后,您将进入以下着陆页。这个页面被称为 Docker Hub 的仪表板

从这里,您可以进入 Docker Hub 的所有其他子页面。但是,在我们查看这些部分之前,我们应该稍微谈一下仪表板。从这里,您可以查看所有您的镜像,包括公共和私有。它们首先按星星数量排序,然后按拉取数量排序;这个顺序不能改变。

在接下来的部分中,我们将逐一介绍您在仪表板上看到的所有内容,从页面顶部的深蓝色菜单开始。

探索

探索选项会带您进入官方 Docker 镜像列表;就像您的仪表板一样,它们按星星和拉取次数排序。正如您从以下屏幕中看到的,每个官方镜像的拉取次数都超过 1000 万次:

这不是首选的 Docker Store 下载官方镜像的方法。Docker 希望您现在使用 Docker Store,但是由于我们将在本章后面更详细地讨论这一点,我们在这里不会再详细介绍。

组织

组织是您创建或被添加到的组织。组织允许您为多人合作的项目添加控制层。组织有自己的设置,例如默认情况下是否将存储库存储为公共或私有,或更改计划,允许不同数量的私有存储库,并将存储库与您或其他人完全分开。

您还可以从仪表板下方的 Docker 标志处访问或切换帐户或组织,通常在您登录时会看到您的用户名:

创建

我们将在后面的部分详细介绍如何创建存储库和自动构建,因此我在这里不会详细介绍,除了创建菜单给您三个选项:

  • 创建存储库

  • 创建自动构建

  • 创建组织

这些选项可以在以下截图中看到:

个人资料和设置

顶部菜单中的最后一个选项是关于管理我的个人资料设置

设置页面允许您设置您的公共个人资料,其中包括以下选项:

  • 更改您的密码

  • 查看您所属的组织

  • 查看您订阅的电子邮件更新

  • 设置您想要接收的特定通知

  • 设置哪些授权服务可以访问您的信息

  • 查看已链接的帐户(例如您的 GitHub 或 Bitbucket 帐户)

  • 查看您的企业许可证、计费和全局设置

目前唯一的全局设置是在创建时选择您的存储库默认为公共私有。默认情况下,它们被创建为公共存储库:

“我的个人资料”菜单项将带您到您的公共个人资料页面;我的个人资料可以在hub.docker.com/u/russmckendrick/找到。

其他菜单选项

仪表板页面顶部的深蓝色条下面还有两个我们尚未涵盖的区域。第一个是星标页面,允许您查看您自己标记为星标的存储库:

如果您发现一些您喜欢使用的存储库,并希望访问它们以查看它们是否最近已更新,或者这些存储库是否发生了其他任何更改,这将非常有用。

第二个是一个新的设置,贡献。点击这个将会显示一个部分,其中将列出您在自己的存储库列表之外做出贡献的存储库的列表。

创建自动构建

在这一部分,我们将看一下自动构建。自动构建是您可以链接到您的 GitHub 或 Bitbucket 帐户的构建,当您更新代码存储库中的代码时,您可以在 Docker Hub 上自动构建镜像。我们将看看完成此操作所需的所有部分,最后,您将能够自动化所有您的构建。

设置您的代码

创建自动构建的第一步是设置您的 GitHub 或 Bitbucket 存储库。在选择存储代码的位置时,您有两个选项。在我们的示例中,我将使用 GitHub,但是 GitHub 和 Bitbucket 的设置将是相同的。

实际上,我将使用附带本书的存储库。由于存储库是公开可用的,您可以 fork 它,并使用您自己的 GitHub 帐户跟随,就像我在下面的截图中所做的那样:

在第二章中,构建容器映像,我们通过了几个不同的 Dockerfiles。我们将使用这些来进行自动构建。如果您还记得,我们安装了 nginx,并添加了一个带有消息Hello world! This is being served from Docker的简单页面,我们还进行了多阶段构建。

设置 Docker Hub

在 Docker Hub 中,我们将使用“创建”下拉菜单并选择“创建自动构建”。选择后,我们将被带到一个屏幕,显示您已链接到 GitHub 或 Bitbucket 的帐户:

从前面的截图中可以看出,我已经将我的 GitHub 帐户链接到了 Docker Hub 帐户。链接这两个工具的过程很简单,我所要做的就是按照屏幕上的说明,允许 Docker Hub 访问我的 GitHub 帐户。

当将 Docker Hub 连接到 GitHub 时,有两个选项:

  • 公共和私有:这是推荐的选项。Docker Hub 将可以访问您的所有公共和私有存储库,以及组织。在设置自动构建时,Docker Hub 还将能够配置所需的 Webhooks。

  • 有限访问:这将限制 Docker Hub 访问公开可用的存储库和组织。如果您使用此选项链接您的帐户,Docker Hub 将无法配置所需的用于自动构建的 Webhooks。然后,您需要从要从中创建自动构建的位置中搜索并选择存储库。这将基本上创建一个 Webhook,指示当在所选的代码存储库上进行提交时,在 Docker Hub 上将创建一个新的构建。

在前面的截图中,我选择了Mastering-Docker-Third-Edition,并访问了自动构建的设置页面。从这里,我们可以选择将图像附加到哪个 Docker Hub 配置文件,命名图像,将其从公共图像更改为私有可用图像,描述构建,并通过单击单击此处自定义来自定义它。我们可以让 Docker Hub 知道我们的 Dockerfile 的位置如下:

如果您在跟着做,我输入了以下信息:

  • 存储库命名空间和名称: dockerfile-example

  • 可见性:公共

  • 简短描述:测试自动构建

  • 推送类型:分支

  • 名称:master

  • Dockerfile 位置:/chapter02/dockerfile-example/

  • Docker 标签:最新

点击创建后,您将会看到一个类似下一个截图的屏幕:

现在我们已经定义了构建,可以通过点击构建设置来添加一些额外的配置。由于我们使用的是官方的 Alpine Linux 镜像,我们可以将其链接到我们自己的构建中。为此,在存储库链接部分输入 Alpine,然后点击添加存储库链接。这将在每次官方 Alpine Linux 镜像发布新版本时启动一个无人值守的构建。

现在我们的镜像将在我们更新 GitHub 存储库时自动重建和发布,或者当新的官方镜像发布时。由于这两种情况都不太可能立即发生,所以点击“触发”按钮手动启动构建。您会注意到按钮会在短时间内变成绿色,这证实了后台已经安排了一个构建。

一旦触发了您的构建,点击构建详情将会显示出该镜像的所有构建列表,包括成功和失败的构建。您应该会看到一个正在进行的构建;点击它将会显示构建的日志:

构建完成后,您应该能够通过运行以下命令移动到本地的 Docker 安装中,确保拉取您自己的镜像(如果一直在跟进的话):

$ docker image pull masteringdockerthirdedition/dockerfiles-example
$ docker image ls

命令如下截图所示:

您也可以使用以下命令运行 Docker Hub 创建的镜像,再次确保使用您自己的镜像(如果有的话):

$ docker container run -d -p8080:80 --name example masteringdockerthirdedition/dockerfiles-example

我也以完全相同的方式添加了多阶段构建。Docker Hub 对构建没有任何问题,您可以从以下日志中看到,它开始于一些关于 Docker 构建环境的信息:

Building in Docker Cloud's infrastructure...
Cloning into '.'...

KernelVersion: 4.4.0-1060-aws
Components: [{u'Version': u'18.03.1-ee-1-tp5', u'Name': u'Engine', u'Details': {u'KernelVersion': u'4.4.0-1060-aws', u'Os': u'linux', u'BuildTime': u'2018-06-23T07:58:56.000000000+00:00', u'ApiVersion': u'1.37', u'MinAPIVersion': u'1.12', u'GitCommit': u'1b30665', u'Arch': u'amd64', u'Experimental': u'false', u'GoVersion': u'go1.10.2'}}]
Arch: amd64
BuildTime: 2018-06-23T07:58:56.000000000+00:00
ApiVersion: 1.37
Platform: {u'Name': u''}
Version: 18.03.1-ee-1-tp5
MinAPIVersion: 1.12
GitCommit: 1b30665
Os: linux
GoVersion: go1.10.2

然后构建过程开始编译我们的代码如下:

Starting build of index.docker.io/masteringdockerthirdedition/multi-stage:latest...
Step 1/8 : FROM golang:latest as builder
 ---> d0e7a411e3da
Step 2/8 : WORKDIR /go-http-hello-world/
Removing intermediate container ea4bd2a1e92a
 ---> 0735d98776ef
Step 3/8 : RUN go get -d -v golang.org/x/net/html
 ---> Running in 5b180ef58abf
Fetching https://golang.org/x/net/html?go-get=1
Parsing meta tags from https://golang.org/x/net/html?go-get=1 (status code 200)
get "golang.org/x/net/html": found meta tag get.metaImport{Prefix:"golang.org/x/net", VCS:"git", RepoRoot:"https://go.googlesource.com/net"} at https://golang.org/x/net/html?go-get=1
get "golang.org/x/net/html": verifying non-authoritative meta tag
Fetching https://golang.org/x/net?go-get=1
Parsing meta tags from https://golang.org/x/net?go-get=1 (status code 200)
golang.org/x/net (download)
Removing intermediate container 5b180ef58abf
 ---> e2d566167ecd
Step 4/8 : ADD https://raw.githubusercontent.com/geetarista/go-http-hello-world/master/hello_world/hello_world.go ./hello_world.go
 ---> c5489fee49e0
Step 5/8 : RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o app .
 ---> Running in 0c5892f9db02
Removing intermediate container 0c5892f9db02
 ---> 94087063b79a

现在我们的代码已经编译完成,接下来将应用程序二进制文件复制到最终镜像中:

Step 6/8 : FROM scratch
 ---> 
Step 7/8 : COPY --from=builder /go-http-hello-world/app .
 ---> e16f25bc4201
Step 8/8 : CMD ["./app"]
 ---> Running in c93cfe262c15
Removing intermediate container c93cfe262c15
 ---> bf3498b1f51e

Successfully built bf3498b1f51e
Successfully tagged masteringdockerthirdedition/multi-stage:latest
Pushing index.docker.io/masteringdockerthirdedition/multi-stage:latest...
Done!
Build finished

您可以使用以下命令拉取和启动包含该镜像的容器:

$ docker image pull masteringdockerthirdedition/multi-stage
$ docker image ls
$ docker container run -d -p 8000:80 --name go-hello-world masteringdockerthirdedition/multi-stage
$ curl http://localhost:8000/

如下截图所示,该镜像的行为方式与我们在本地创建时完全相同:

如果您启动了容器,可以使用以下命令删除它们:

$ docker container stop example
$ docker container rm example
$ docker container stop go-hello-world
$ docker container rm go-hello-world

现在我们已经了解了自动化构建,我们可以讨论如何以其他方式将镜像推送到 Docker Hub。

推送您自己的镜像

在第二章中,构建容器镜像,我们讨论了在不使用 Dockerfile 的情况下创建镜像。虽然这仍然不是一个好主意,应该只在您真正需要时使用,但您可以将自己的镜像推送到 Docker Hub。

以这种方式将镜像推送到 Docker Hub 时,请确保不包括任何您不希望公开访问的代码、文件或环境变量。

为此,我们首先需要通过运行以下命令将本地 Docker 客户端链接到 Docker Hub:

$ docker login

然后会提示您输入 Docker ID 和密码:

此外,如果您使用的是 Docker for Mac 或 Docker for Windows,您现在将通过应用程序登录,并应该能够从菜单访问 Docker Hub:

现在我们的客户端已被授权与 Docker Hub 交互,我们需要一个要构建的镜像。让我们看看如何推送我们在第二章中构建的 scratch 镜像,构建容器镜像。首先,我们需要构建镜像。为此,我使用以下命令:

$ docker build --tag masteringdockerthirdedition/scratch-example:latest .

如果您在跟着做,那么您应该将masteringdockerthirdedition替换为您自己的用户名或组织:

构建完镜像后,我们可以通过运行以下命令将其推送到 Docker Hub:

$ docker image push masteringdockerthirdedition/scratch-example:latest

以下屏幕截图显示了输出:

正如您所看到的,因为我们在构建镜像时定义了masteringdockerthirdedition/scratch-example:latest,Docker 自动将镜像上传到该位置,从而向Mastering Docker Third Edition组织添加了一个新镜像。

您会注意到在 Docker Hub 中无法做太多事情。这是因为镜像不是由 Docker Hub 构建的,因此它实际上并不知道构建镜像时发生了什么。

Docker 商店

您可能还记得在第一章中,Docker 概述,我们从 Docker Store 下载了 macOS 和 Windows 的 Docker。除了作为下载各种平台的Docker CEDocker EE的单一位置外,它现在也是查找Docker ImagesDocker Plugins的首选位置。

虽然您只会在 Docker Store 中找到官方和认证的图像,但也可以使用 Docker Store 界面来搜索 Docker Hub。此外,您可以下载来自 Docker Hub 不可用的图像,例如 Citrix NetScaler CPX Express 图像:

如果您注意到,图像附加了价格(Express 版本为$0.00),这意味着您可以通过 Docker Store 购买商业软件,因为它内置了付款和许可。如果您是软件发布者,您可以通过 Docker Store 签署和分发自己的软件。

在后面的章节中,当我们涵盖 Docker 插件时,我们将更详细地了解 Docker Store。

Docker Registry

在本节中,我们将研究 Docker Registry。Docker Registry是一个开源应用程序,您可以在任何地方运行并存储您的 Docker 图像。我们将看看 Docker Registry 和 Docker Hub 之间的比较,以及如何在两者之间进行选择。在本节结束时,您将学会如何运行自己的 Docker Registry,并查看它是否适合您。

Docker Registry 概述

如前所述,Docker Registry 是一个开源应用程序,您可以利用它在您选择的平台上存储您的 Docker 图像。这使您可以根据需要将它们保持 100%私有,或者分享它们。

如果您想部署自己的注册表而无需支付 Docker Hub 的所有私有功能,那么 Docker Registry 就有很多意义。接下来,让我们看一下 Docker Hub 和 Docker Registry 之间的一些比较,以帮助您做出明智的决定,选择哪个平台来存储您的图像。

Docker Registry 具有以下功能:

  • 从中您可以作为私有、公共或两者混合来提供所有存储库的主机和管理您自己的注册表

  • 根据您托管的图像数量或提供的拉取请求数量,根据需要扩展注册表

  • 一切都是基于命令行的

使用 Docker Hub,您将:

  • 获得一个基于 GUI 的界面,您可以用来管理您的图像

  • 在云中已经设置好了一个位置,可以处理公共和/或私有图像

  • 放心,不必管理托管所有图像的服务器

部署您自己的 Registry

正如您可能已经猜到的,Docker Registry 作为 Docker Hub 的一个镜像分发,这使得部署它就像运行以下命令一样简单:

$ docker image pull registry:2
$ docker container run -d -p 5000:5000 --name registry registry:2

这些命令将为您提供最基本的 Docker Registry 安装。让我们快速看一下如何将图像推送到其中并从中拉取。首先,我们需要一个图像,所以让我们再次获取 Alpine 图像:

$ docker image pull alpine

现在我们有了 Alpine Linux 图像的副本,我们需要将其推送到我们的本地 Docker Registry,该 Registry 位于localhost:5000。为此,我们需要使用我们本地 Docker Registry 的 URL 来标记 Alpine Linux 图像,并使用不同的图像名称:

$ docker image tag alpine localhost:5000/localalpine

现在我们已经标记了我们的图像,我们可以通过运行以下命令将其推送到我们本地托管的 Docker Registry:

$ docker image push localhost:5000/localalpine

以下截图显示了上述命令的输出:

尝试运行以下命令:

$ docker image ls

输出应该向您显示具有相同IMAGE ID的两个图像:

在我们从本地 Docker Registry 中重新拉取图像之前,我们应该删除图像的两个本地副本。我们需要使用REPOSITORY名称来执行此操作,而不是IMAGE ID,因为我们有两个位置的两个相同 ID 的图像,Docker 会抛出错误:

$ docker image rm alpine localhost:5000/localalpine

现在原始和标记的图像已被删除,我们可以通过运行以下命令从本地 Docker Registry 中拉取图像:

$ docker image pull localhost:5000/localalpine
$ docker image ls

正如您所看到的,我们现在有一个从 Docker Registry 中拉取的图像副本在localhost:5000上运行:

您可以通过运行以下命令停止和删除 Docker Registry:

$ docker container stop registry
$ docker container rm -v registry

现在,在启动 Docker Registry 时有很多选项和考虑因素。正如您所想象的那样,最重要的是围绕存储。

鉴于 Registry 的唯一目的是存储和分发图像,重要的是您使用一定级别的持久性 OS 存储。Docker Registry 目前支持以下存储选项:

  • 文件系统:这正是它所说的;所有的镜像都存储在您定义的路径上。默认值是/var/lib/registry

  • Azure:这使用微软 Azure Blob 存储。

  • GCS:这使用 Google 云存储。

  • S3:这使用亚马逊简单存储服务(Amazon S3)。

  • Swift:这使用 OpenStack Swift。

正如您所看到的,除了文件系统之外,所有支持的存储引擎都是高可用的,分布式对象级存储。我们将在后面的章节中看到这些云服务。

Docker Trusted Registry

商业版Docker 企业版Docker EE)附带的一个组件是Docker Trusted RegistryDTR)。把它看作是一个您可以在自己的基础设施中托管的 Docker Hub 版本。DTR 在免费的 Docker Hub 和 Docker 注册表提供的功能之上增加了以下功能:

  • 集成到您的身份验证服务,如 Active Directory 或 LDAP

  • 在您自己的基础设施(或云)部署在您的防火墙后面

  • 图像签名以确保您的图像是可信的

  • 内置安全扫描

  • 直接从 Docker 获得优先支持

第三方注册表

不仅 Docker 提供图像注册表服务;像 Red Hat 这样的公司也提供他们自己的注册表,您可以在那里找到 Red Hat 容器目录,其中托管了所有 Red Hat 产品提供的容器化版本,以及支持其 OpenShift 产品的容器。

像 JFrog 的 Artifactory 这样的服务提供了私有的 Docker 注册表作为其构建服务的一部分。还有其他的注册表即服务提供,比如 CoreOS 的 Quay,现在被 Red Hat 拥有,还有来自亚马逊网络服务和微软 Azure 的服务。当我们继续研究云中的 Docker 时,我们将看看这些服务。

Microbadger

Microbadger是一个很好的工具,当您考虑要运输您的容器或图像时。它将考虑到特定 Docker 图像的每个层中发生的一切,并为您提供实际大小或它将占用多少磁盘空间的输出。

当您导航到 Microbadger 网站时,您将看到这个页面,microbadger.com/

您可以搜索 Docker Hub 上的镜像,让 Microbadger 为您提供有关该镜像的信息,或者加载一个示例镜像集,如果您想提供一些示例集,或者查看一些更复杂的设置。

在这个例子中,我们将搜索我们在本章前面推送的masteringdockerthirdedition/dockerfiles-example镜像,并选择最新的标签。如下截图所示,Docker Hub 会在您输入时自动搜索,并实时返回结果。

默认情况下,它将始终加载最新的标签,但您也可以通过从版本下拉菜单中选择所需的标签来更改您正在查看的标签。例如,如果您有一个暂存标签,并且正在考虑将这个新镜像推送到最新标签,但想要看看它对镜像大小的影响,这可能会很有用。

如下截图所示,Microbadger 提供了有关您的镜像包含多少层的信息:

通过显示每个层的大小和镜像构建过程中执行的 Dockerfile 命令,您可以看到镜像构建的哪个阶段添加了膨胀,这在减小镜像大小时非常有用。

另一个很棒的功能是,Microbadger 可以让您选择将有关您的镜像的基本统计信息嵌入到您的 Git 存储库或 Docker Hub 中;例如,以下屏幕显示了我自己的一个镜像的 Docker Hub 页面:

正如您从以下截图中所看到的,Microbadger 显示了镜像的总体大小,在这个例子中是 5.9MB,以及镜像由多少层组成的总数,为 7。Microbadger 服务仍处于测试阶段,新功能正在不断添加。我建议您密切关注它。

总结

在本章中,我们探讨了使用 Docker Hub 手动和自动构建容器镜像的几种方法。我们讨论了除了 Docker Hub 之外您可以使用的各种注册表,例如 Docker Store 和 Red Hat 的容器目录。

我们还研究了部署我们自己的本地 Docker 注册表,并提及了在部署时需要考虑的存储问题。最后,我们看了 Microbadger,这是一个允许您显示有关远程托管容器镜像信息的服务。

在下一章中,我们将看看如何从命令行管理我们的容器。

问题

  1. 真或假:Docker Hub 是您可以下载官方 Docker 镜像的唯一来源。

  2. 描述为什么您想要将自动构建链接到官方 Docker Hub 镜像。

  3. 多阶段构建是否受 Docker Hub 支持?

  4. 真或假:在命令行中登录 Docker 也会登录到桌面应用程序?

  5. 您如何删除共享相同 IMAGE ID 的两个镜像?

  6. Docker Registry 默认运行在哪个端口?

进一步阅读

有关 Docker Store、Trusted Registry 和 Registry 的更多信息,请访问:

您可以在以下位置找到有关可用于 Docker Registry 的不同类型的基于云的存储的更多详细信息:

一些第三方注册服务可以在这里找到:

最后,您可以在这里找到我的 Apache Bench 镜像的 Docker Hub 和 Microbadger 链接:

第四章:管理容器

到目前为止,我们一直在集中讨论如何构建、存储和分发我们的 Docker 镜像。现在我们将看看如何启动容器,以及如何使用 Docker 命令行客户端来管理和与它们交互。

我们将重新访问我们在第一章中使用的命令,并更详细地了解,然后深入了解可用的命令。一旦我们熟悉了容器命令,我们将看看 Docker 网络和 Docker 卷。

我们将涵盖以下主题:

  • Docker 容器命令:

  • 基础知识

  • 与您的容器交互

  • 日志和进程信息

  • 资源限制

  • 容器状态和其他命令

  • 删除容器

  • Docker 网络和卷

技术要求

在本章中,我们将继续使用我们的本地 Docker 安装。与之前一样,本章中的截图将来自我首选的操作系统 macOS,但我们将运行的 Docker 命令将在迄今为止安装了 Docker 的三种操作系统上都可以工作;但是,一些支持命令可能只适用于 macOS 和基于 Linux 的操作系统。

观看以下视频以查看代码的实际操作:

bit.ly/2yupP3n

Docker 容器命令

在我们深入研究更复杂的 Docker 命令之前,让我们回顾并更详细地了解我们在之前章节中使用的命令。

基础知识

在第一章中,Docker 概述,我们使用以下命令启动了最基本的容器hello-world容器:

$ docker container run hello-world

如您可能还记得,这个命令从 Docker Hub 拉取了一个 1.84 KB 的镜像。您可以在store.docker.com/images/hello-world/找到该镜像的 Docker Store 页面,并且根据以下 Dockerfile,它运行一个名为hello的可执行文件:

FROM scratch
COPY hello /
CMD ["/hello"]

hello可执行文件将Hello from Docker!文本打印到终端,然后进程退出。从以下终端输出的完整消息文本中可以看出,hello二进制文件还会告诉您刚刚发生了什么步骤:

随着进程退出,我们的容器也会停止;可以通过运行以下命令来查看:

$ docker container ls -a

命令的输出如下:

您可能会注意到在终端输出中,我首先运行了带有和不带有-a标志的docker container ls命令——这是--all的缩写,因为不带标志运行它不会显示任何已退出的容器。

我们不必给我们的容器命名,因为它存在的时间不够长,我们也不在乎它叫什么。Docker 会自动为容器分配名称,而在我的情况下,你可以看到它被称为pensive_hermann

您会注意到,在您使用 Docker 的过程中,如果选择让它为您生成容器,它会为您的容器起一些非常有趣的名字。尽管这有点离题,但生成这些名称的代码可以在names-generator.go中找到。在源代码的最后,它有以下的if语句:

if name == "boring_wozniak" /* Steve Wozniak is not boring */ {
  goto begin
}

这意味着永远不会有一个名为boring_wozniak的容器(这也是完全正确的)。

Steve Wozniak 是一位发明家、电子工程师、程序员和企业家,他与史蒂夫·乔布斯共同创立了苹果公司。他被誉为 70 年代和 80 年代个人电脑革命的先驱,绝对不是无聊的!

我们可以通过运行以下命令删除状态为exited的容器,确保您用您自己的容器名称替换掉命令中的容器名称:

$ docker container rm pensive_hermann

此外,在第一章 Docker 概述的结尾,我们使用官方 nginx 镜像启动了一个容器,使用以下命令:

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

正如您可能记得的那样,这会下载镜像并运行它,将我们主机上的端口8080映射到容器上的端口80,并将其命名为nginx-test

正如您从我们的docker image ls命令中可以看到的,我们现在已经下载并运行了两个镜像。以下命令显示我们有一个正在运行的容器:

$ docker container ls

以下终端输出显示,当我运行该命令时,我的容器已经运行了 5 分钟:

从我们的docker container run命令中可以看到,我们引入了三个标志。其中一个是-d,它是--detach的缩写。如果我们没有添加这个标志,那么我们的容器将在前台执行,这意味着我们的终端会被冻结,直到我们通过按下Ctrl + C传递进程的退出命令。

我们可以通过运行以下命令来看到这一点,以启动第二个nginx容器与我们已经启动的容器一起运行:

$ docker container run --name nginx-foreground -p 9090:80 nginx

启动后,打开浏览器并转到http://localhost:9090/。当您加载页面时,您会注意到您的页面访问被打印到屏幕上;在浏览器中点击刷新将显示更多的访问量,直到您在终端中按下Ctrl + C

运行docker container ls -a显示您有两个容器,其中一个已退出:

发生了什么?当我们移除了分离标志时,Docker 直接将我们连接到容器内的 nginx 进程,这意味着我们可以看到该进程的stdinstdoutstderr。当我们使用Ctrl + C时,实际上是向 nginx 进程发送了一个终止指令。由于那是保持容器运行的进程,一旦没有运行的进程,容器立即退出。

标准输入(stdin)是我们的进程用来从最终用户那里获取信息的句柄。标准输出(stdout)是进程写入正常信息的地方。标准错误(stderr)是进程写入错误消息的地方。

当我们启动nginx-foreground容器时,您可能还注意到我们使用--name标志为其指定了不同的名称。

这是因为您不能使用相同的名称拥有两个容器,因为 Docker 允许您使用CONTAINER IDNAME值与容器进行交互。这就是名称生成器函数存在的原因:为您不希望自己命名的容器分配一个随机名称,并确保我们永远不会称史蒂夫·沃兹尼亚克为无聊。

最后要提到的是,当我们启动nginx-foreground时,我们要求 Docker 将端口9090映射到容器上的端口80。这是因为我们不能在主机上的一个端口上分配多个进程,因此如果我们尝试使用与第一个相同的端口启动第二个容器,我们将收到错误消息:

docker: Error response from daemon: driver failed programming external connectivity on endpoint nginx-foreground (3f5b355607f24e03f09a60ee688645f223bafe4492f807459e4a2b83571f23f4): Bind for 0.0.0.0:8080 failed: port is already allocated.

此外,由于我们在前台运行容器,您可能会收到来自 nginx 进程的错误,因为它未能启动:

ERRO[0003] error getting events from daemon: net/http: request cancelled

但是,您可能还注意到我们将端口映射到容器上的端口 80——为什么没有错误?

嗯,正如在第一章中解释的那样,Docker 概述,容器本身是隔离的资源,这意味着我们可以启动尽可能多的容器,并重新映射端口 80,它们永远不会与其他容器冲突;当我们想要从 Docker 主机路由到暴露的容器端口时,我们只会遇到问题。

让我们保持我们的 nginx 容器在下一节中继续运行。

与您的容器进行交互

到目前为止,我们的容器一直在运行单个进程。Docker 为您提供了一些工具,使您能够 fork 额外的进程并与它们交互。

attach

与正在运行的容器进行交互的第一种方法是attach到正在运行的进程。我们仍然有我们的nginx-test容器在运行,所以让我们通过运行这个命令来连接到它:

$ docker container attach nginx-test

打开浏览器并转到http://localhost:8080/将会将 nginx 访问日志打印到屏幕上,就像我们启动nginx-foreground容器时一样。按下Ctrl + C将终止进程并将您的终端返回正常;但是,与之前一样,我们将终止保持容器运行的进程:

我们可以通过运行以下命令重新启动我们的容器:

$ docker container start nginx-test

这将以分离状态重新启动容器,这意味着它再次在后台运行,因为这是容器最初启动时的状态。转到http://localhost:8080/将再次显示 nginx 欢迎页面。

让我们重新连接到我们的进程,但这次附加一个额外的选项:

$ docker container attach --sig-proxy=false nginx-test 

多次访问容器的 URL,然后按下Ctrl + C将使我们从 nginx 进程中分离出来,但这次,而不是终止 nginx 进程,它将只是将我们返回到我们的终端,使容器处于分离状态,可以通过运行docker container ls来查看:

exec

attach命令在您需要连接到容器正在运行的进程时很有用,但如果您需要更交互式的东西呢?

您可以使用exec命令;这会在容器内生成第二个进程,您可以与之交互。例如,要查看/etc/debian_version文件的内容,我们可以运行以下命令:

$ docker container exec nginx-test cat /etc/debian_version

这将产生第二个进程,本例中是 cat 命令,它将打印/etc/debian_version的内容到stdout。第二个进程然后将终止,使我们的容器在执行 exec 命令之前的状态:

我们可以通过运行以下命令进一步进行:

$ docker container exec -i -t nginx-test /bin/bash

这次,我们正在派生一个 bash 进程,并使用-i-t标志来保持对容器的控制台访问。-i标志是--interactive的简写,它指示 Docker 保持stdin打开,以便我们可以向进程发送命令。-t标志是--tty的简写,并为会话分配一个伪 TTY。

早期用户终端连接到计算机被称为电传打字机。虽然这些设备今天不再使用,但是 TTY 的缩写在现代计算中继续用来描述纯文本控制台。

这意味着您将能够像远程终端会话(如 SSH)一样与容器进行交互:

虽然这非常有用,因为您可以像与虚拟机一样与容器进行交互,但我不建议在使用伪 TTY 运行时对容器进行任何更改。很可能这些更改不会持久保存,并且在删除容器时将丢失。我们将在第十二章中更详细地讨论这背后的思考,Docker 工作流

日志和进程信息

到目前为止,我们要么附加到容器中的进程,要么附加到容器本身,以查看信息。Docker 提供了一些命令,允许您查看有关容器的信息,而无需使用attachexec命令。

日志

logs命令相当不言自明;它允许您与 Docker 在后台跟踪的容器的stdout流进行交互。例如,要查看我们的nginx-test容器的stdout的最后条目,只需使用以下命令:

$ docker container logs --tail 5 nginx-test

命令的输出如下所示:

要实时查看日志,我只需要运行以下命令:

$ docker container logs -f nginx-test

-f标志是--follow的简写。我也可以,比如,通过运行以下命令查看自从某个时间以来已经记录的所有内容:

$ docker container logs --since 2018-08-25T18:00 nginx-test

命令的输出如下所示:

你可能会注意到,在前面的输出中,访问日志中的时间戳是 17:12,早于 18:00。为什么会这样?

logs 命令显示了 Docker 记录的 stdout 的时间戳,而不是容器内部的时间。当我运行以下命令时,你可以看到这一点:

$ date
$ docker container exec nginx-test date 

输出如下:

由于我的主机上正在使用英国夏令时BST),所以我的主机和容器之间有一个小时的时间差。

幸运的是,为了避免混淆(或者增加混淆,这取决于你的观点),你可以在 logs 命令中添加 -t

$ docker container logs --since 2018-08-25T18:00 -t nginx-test

-t 标志是 --timestamp 的缩写;这个选项会在输出之前添加 Docker 捕获的时间:

top

top 命令非常简单;它列出了你指定的容器中正在运行的进程,使用方法如下:

$ docker container top nginx-test

命令的输出如下:

如你从下面的终端输出中可以看到,我们有两个正在运行的进程,都是 nginx,这是可以预料到的。

stats

stats 命令提供了关于指定容器的实时信息,或者如果你没有传递 NAMEID 容器,则提供所有正在运行的容器的信息:

$ docker container stats nginx-test

如你从下面的终端输出中可以看到,我们得到了指定容器的 CPURAMNETWORKDISK IOPIDS 的信息:

我们也可以传递 -a 标志;这是 --all 的缩写,显示所有容器,无论是否正在运行。例如,尝试运行以下命令:

$ docker container stats -a

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

然而,如你从前面的输出中可以看到,如果容器没有运行,那么就没有任何资源被利用,所以它实际上并没有增加任何价值,除了让你直观地看到你有多少个容器正在运行以及资源的使用情况。

值得指出的是,stats 命令显示的信息只是实时的;Docker 不会记录资源利用情况并以与 logs 命令相同的方式提供。我们将在后面的章节中研究更长期的资源利用情况存储。

资源限制

我们运行的最后一个命令显示了我们容器的资源利用情况;默认情况下,启动时,容器将被允许消耗主机机器上所有可用的资源。我们可以对容器可以消耗的资源进行限制;让我们首先更新我们的nginx-test容器的资源允许量。

通常,我们会在使用run命令启动容器时设置限制;例如,要将 CPU 优先级减半并设置内存限制为128M,我们将使用以下命令:

$ docker container run -d --name nginx-test --cpu-shares 512 --memory 128M -p 8080:80 nginx

然而,我们没有使用任何资源限制启动我们的nginx-test容器,这意味着我们需要更新我们已经运行的容器;为此,我们可以使用update命令。现在,您可能认为这应该只涉及运行以下命令:

$ docker container update --cpu-shares 512 --memory 128M nginx-test

但实际上,运行上述命令会产生一个错误:

Error response from daemon: Cannot update container 3f2ce315a006373c075ba7feb35c1368362356cb5fe6837acf80b77da9ed053b: Memory limit should be smaller than already set memoryswap limit, update the memoryswap at the same time

当前设置的memoryswap限制是多少?要找出这个,我们可以使用inspect命令来显示我们正在运行的容器的所有配置数据;只需运行以下命令:

$ docker container inspect nginx-test

通过运行上述命令,您可以看到有很多配置数据。当我运行该命令时,返回了一个 199 行的 JSON 数组。让我们使用grep命令来过滤只包含单词memory的行:

$ docker container inspect nginx-test | grep -i memory

这返回以下配置数据:

 "Memory": 0,
 "KernelMemory": 0, "MemoryReservation": 0,
 "MemorySwap": 0,
 "MemorySwappiness": null,

一切都设置为0,那么128M怎么会小于0呢?

在资源配置的上下文中,0实际上是默认值,表示没有限制—注意每个数字值后面缺少M。这意味着我们的更新命令实际上应该如下所示:

$ docker container update --cpu-shares 512 --memory 128M --memory-swap 256M nginx-test

分页是一种内存管理方案,其中内核将数据存储和检索,或者交换,从辅助存储器中用于主内存。这允许进程超出可用的物理内存大小。

默认情况下,当您在运行命令中设置--memory时,Docker 将设置--memory-swap大小为--memory的两倍。如果现在运行docker container stats nginx-test,您应该看到我们设置的限制:

此外,重新运行docker container inspect nginx-test | grep -i memory将显示以下更改:

 "Memory": 134217728,
 "KernelMemory": 0,
 "MemoryReservation": 0,
 "MemorySwap": 268435456,
 "MemorySwappiness": null,

运行docker container inspect时,值都以字节而不是兆字节(MB)显示。

容器状态和其他命令

在本节的最后部分,我们将看一下容器可能处于的各种状态,以及作为docker container命令的一部分尚未涵盖的几个剩余命令。

运行docker container ls -a应该显示类似以下终端输出:

如您所见,我们有两个容器;一个状态为Up,另一个为Exited。在继续之前,让我们启动五个更多的容器。要快速执行此操作,请运行以下命令:

$ for i in {1..5}; do docker container run -d --name nginx$(printf "$i") nginx; done

运行docker container ls -a时,您应该看到您的五个新容器,命名为nginx1nginx5

暂停和取消暂停

让我们来看看暂停nginx1。要做到这一点,只需运行以下命令:

$ docker container pause nginx1

运行docker container ls将显示容器的状态为Up,但也显示为Paused

请注意,我们不必使用-a标志来查看有关容器的信息,因为进程尚未终止;相反,它已经被使用cgroups冻结器挂起。使用cgroups冻结器,进程不知道自己已经被挂起,这意味着它可以被恢复。

你可能已经猜到了,可以使用unpause命令恢复暂停的容器,如下所示:

$ docker container unpause nginx1

如果您需要冻结容器的状态,这个命令非常有用;例如,也许您的一个容器出现了问题,您需要稍后进行一些调查,但不希望它对其他正在运行的容器产生负面影响。

停止,启动,重启和杀死

接下来,我们有stopstartrestartkill命令。我们已经使用start命令恢复了状态为Exited的容器。stop命令的工作方式与我们在前台运行容器时使用Ctrl + C分离的方式完全相同。运行以下命令:

$ docker container stop nginx2

通过这个,发送一个请求给进程终止,称为SIGTERM。如果进程在宽限期内没有自行终止,那么将发送一个终止信号,称为SIGKILL。这将立即终止进程,不给它完成导致延迟的任何时间;例如,将数据库查询的结果提交到磁盘。

因为这可能是不好的,Docker 给了你覆盖默认的宽限期的选项,这个默认值是10秒,可以使用-t标志来覆盖;这是--time的缩写。例如,运行以下命令将在发送SIGKILL之前等待最多60秒,如果需要发送以杀死进程:

$ docker container stop -t 60 nginx3

start命令,正如我们已经看到的,将重新启动进程;然而,与pauseunpause命令不同,这种情况下,进程将使用最初启动它的标志从头开始,而不是从离开的地方开始:

$ docker container start nginx2 nginx3

restart命令是以下两个命令的组合;它先停止,然后再启动你传递的IDNAME容器。与stop一样,你也可以传递-t标志:

$ docker container restart -t 60 nginx4

最后,您还可以通过运行kill命令立即向容器发送SIGKILL命令:

$ docker container kill nginx5 

删除容器

让我们使用docker container ls -a命令来检查我们正在运行的容器。当我运行命令时,我可以看到我有两个处于Exited状态的容器,其他所有容器都在运行:

要删除两个已退出的容器,我只需运行prune命令:

$ docker container prune

这样做时,会弹出一个警告,询问您是否真的确定,如下面的截图所示:

您可以使用rm命令选择要删除的容器,下面是一个示例:

$ docker container rm nginx4

另一种选择是将stoprm命令串联在一起:

$ docker container stop nginx3 && docker container rm nginx3

然而,鉴于您现在可以使用prune命令,这可能是太费力了,特别是在您试图删除容器并且可能不太关心进程如何优雅地终止的情况下。

随意使用您喜欢的任何方法删除剩余的容器。

杂项命令

在本节的最后部分,我们将看一些在日常使用 Docker 时可能不会经常使用的命令。其中之一是create

create命令与run命令非常相似,只是它不启动容器,而是准备和配置一个:

$ docker container create --name nginx-test -p 8080:80 nginx

您可以通过运行docker container ls -a来检查已创建容器的状态,然后使用docker container start nginx-test启动容器,然后再次检查状态:

我们要快速查看的下一个命令是port命令;这将显示容器的端口以及任何端口映射:

$ docker container port nginx-test

它应该返回以下内容:

80/tcp -> 0.0.0.0:8080

我们已经知道这一点,因为这是我们配置的内容。此外,端口在docker container ls输出中列出。

我们要快速查看的最后一个命令是diff命令。该命令打印自容器启动以来已添加(A)或更改(C)的所有文件的列表——基本上是我们用于启动容器的原始映像和现在存在的文件之间文件系统的差异列表。

在运行命令之前,让我们使用exec命令在nginx-test容器中创建一个空白文件:

$ docker container exec nginx-test touch /tmp/testing

现在我们在/tmp中有一个名为testing的文件,我们可以使用以下命令查看原始映像和运行容器之间的差异:

$ docker container diff nginx-test

这将返回一个文件列表;从下面的列表中可以看到,我们的测试文件在那里,还有在 nginx 启动时创建的文件:

C /run
A /run/nginx.pid
C /tmp
A /tmp/testing
C /var/cache/nginx
A /var/cache/nginx/client_temp A /var/cache/nginx/fastcgi_temp A /var/cache/nginx/proxy_temp
A /var/cache/nginx/scgi_temp
A /var/cache/nginx/uwsgi_temp

值得指出的是,一旦停止并删除容器,这些文件将丢失。在本章的下一节中,我们将看看 Docker 卷,并学习如何持久保存数据。

再次强调,如果您在跟着做,应该使用您选择的命令删除在本节启动的任何正在运行的容器。

Docker 网络和卷

在完成本章之前,我们将首先使用默认驱动程序来了解 Docker 网络和 Docker 卷的基础知识。让我们先看看网络。

Docker 网络

到目前为止,我们一直在单个共享网络上启动我们的容器。尽管我们还没有讨论过,但这意味着我们一直在启动的容器可以在不使用主机的情况下相互通信

网络。

现在不详细讨论,让我们通过一个例子来工作。我们将运行一个双容器应用程序;第一个容器将运行 Redis,第二个容器将运行我们的应用程序,该应用程序使用 Redis 容器来存储系统状态。

Redis是一个内存数据结构存储,可以用作数据库、缓存或消息代理。它支持不同级别的磁盘持久性。

在启动应用程序之前,让我们下载将要使用的容器映像,并创建网络:

$ docker image pull redis:alpine
$ docker image pull russmckendrick/moby-counter
$ docker network create moby-counter

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

现在我们已经拉取了我们的镜像并创建了我们的网络,我们可以启动我们的容器,从 Redis 开始:

$ docker container run -d --name redis --network moby-counter redis:alpine

正如您所看到的,我们使用了--network标志来定义我们的容器启动的网络。现在 Redis 容器已经启动,我们可以通过运行以下命令来启动应用程序容器:

$ docker container run -d --name moby-counter --network moby-counter -p 8080:80 russmckendrick/moby-counter

同样,我们将容器启动到moby-counter网络中;这一次,我们将端口8080映射到容器上的端口80。请注意,我们不需要担心暴露 Redis 容器的任何端口。这是因为 Redis 镜像带有一些默认值,暴露默认端口,对我们来说默认端口是6379。这可以通过运行docker container ls来查看:

现在剩下的就是访问应用程序;要做到这一点,打开浏览器,转到http://localhost:8080/。您应该会看到一个几乎空白的页面,上面显示着点击添加标志的消息:

单击页面上的任何位置都会添加 Docker 标志,所以请点击:

发生了什么?从 moby-counter 容器提供的应用程序正在连接到redis容器,并使用该服务来存储您通过点击放置在屏幕上的每个标志的屏幕坐标。

moby-counter 应用程序是如何连接到redis容器的?在server.js文件中,设置了以下默认值:

var port = opts.redis_port || process.env.USE_REDIS_PORT || 6379
var host = opts.redis_host || process.env.USE_REDIS_HOST || 'redis'

这意味着moby-counter应用程序正在尝试连接到名为redis的主机的端口6379。让我们尝试使用 exec 命令从moby-counter应用程序中 pingredis容器,看看我们得到什么:

$ docker container exec moby-counter ping -c 3 redis

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

正如您所看到的,moby-counter容器将redis解析为redis容器的 IP 地址,即172.18.0.2。您可能会认为应用程序的主机文件包含了redis容器的条目;让我们使用以下命令来查看一下:

$ docker container exec moby-counter cat /etc/hosts

这返回了/etc/hosts的内容,在我的情况下,看起来像以下内容:

127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.18.0.3 4e7931312ed2

除了最后的条目外,实际上是 IP 地址解析为本地容器的主机名,4e7931312ed2是容器的 ID;没有redis的条目。接下来,让我们通过运行以下命令来检查/etc/resolv.conf

$ docker container exec moby-counter cat /etc/resolv.conf

这返回了我们正在寻找的内容;如您所见,我们正在使用本地nameserver

nameserver 127.0.0.11
options ndots:0

让我们使用以下命令对redis进行 DNS 查找,针对127.0.0.11

$ docker container exec moby-counter nslookup redis 127.0.0.11

这返回了redis容器的 IP 地址:

Server: 127.0.0.11
Address 1: 127.0.0.11

Name: redis
Address 1: 172.18.0.2 redis.moby-counter

让我们创建第二个网络并启动另一个应用程序容器:

$ docker network create moby-counter2
$ docker run -itd --name moby-counter2 --network moby-counter2 -p 9090:80 russmckendrick/moby-counter

现在我们已经启动并运行了第二个应用程序容器,让我们尝试从中 pingredis容器:

$ docker container exec moby-counter2 ping -c 3 redis

在我的情况下,我得到了以下错误:

让我们检查resolv.conf文件,看看是否已经在使用相同的域名服务器,如下所示:

$ docker container exec moby-counter2 cat /etc/resolv.conf

从以下输出中可以看出,域名服务器确实已经在使用中:

nameserver 127.0.0.11
options ndots:0

由于我们在与名为redis的容器运行的不同网络中启动了moby-counter2容器,我们无法解析容器的主机名,因此返回了错误的地址错误:

$ docker container exec moby-counter2 nslookup redis 127.0.0.11
Server: 127.0.0.11
Address 1: 127.0.0.11

nslookup: can't resolve 'redis': Name does not resolve

让我们看看在我们的第二个网络中启动第二个 Redis 服务器;正如我们已经讨论过的,我们不能有两个同名的容器,所以让我们有创意地将其命名为redis2

由于我们的应用程序配置为连接到解析为redis的容器,这是否意味着我们将不得不对我们的应用程序容器进行更改?不,但 Docker 已经为您做好了准备。

虽然我们不能有两个同名的容器,正如我们已经发现的那样,我们的第二个网络完全与第一个网络隔离运行,这意味着我们仍然可以使用redis的 DNS 名称。为此,我们需要添加--network-alias标志,如下所示:

$ docker container run -d --name redis2 --network moby-counter2 --network-alias redis redis:alpine

如您所见,我们已经将容器命名为redis2,但将--network-alias设置为redis;这意味着当我们执行查找时,我们会看到返回的正确 IP 地址:

$ docker container exec moby-counter2 nslookup redis 127.0.0.1
Server: 127.0.0.1
Address 1: 127.0.0.1 localhost

Name: redis
Address 1: 172.19.0.3 redis2.moby-counter2

如您所见,redis实际上是redis2.moby-counter2的别名,然后解析为172.19.0.3

现在我们应该有两个应用程序在本地 Docker 主机上以自己的隔离网络并行运行,可以通过http://localhost:8080/http://localhost:9090/访问。运行docker network ls将显示在 Docker 主机上配置的所有网络,包括默认网络:

您可以通过运行以下inspect命令来了解有关网络配置的更多信息:

$ docker network inspect moby-counter

运行上述命令将返回以下 JSON 数组:

[
 {
 "Name": "moby-counter",
 "Id": "c8b38a10efbefd701c83203489459d9d5a1c78a79fa055c1c81c18dea3f1883c",
 "Created": "2018-08-26T11:51:09.7958001Z",
 "Scope": "local",
 "Driver": "bridge",
 "EnableIPv6": false,
 "IPAM": {
 "Driver": "default",
 "Options": {},
 "Config": [
 {
 "Subnet": "172.18.0.0/16",
 "Gateway": "172.18.0.1"
 }
 ]
 },
 "Internal": false,
 "Attachable": false,
 "Ingress": false,
 "ConfigFrom": {
 "Network": ""
 },
 "ConfigOnly": false,
 "Containers": {
 "4e7931312ed299ed9132f3553e0518db79b4c36c43d36e88306aed7f6f9749d8": {
 "Name": "moby-counter",
 "EndpointID": "dc83770ae0939c98416ee69d939b30a1da391b11d14012c8188be287baa9c325",
 "MacAddress": "02:42:ac:12:00:03",
 "IPv4Address": "172.18.0.3/16",
 "IPv6Address": ""
 },
 "d760bc59c3ac5f9ba8b7aa8e9f61fd21ce0b8982f3a85db888a5bcf103bedf6e": {
 "Name": "redis",
 "EndpointID": "5af2bfd1ce486e38a9c5cddf9e16878fdb91389cc122cfef62d5e575a91b89b9",
 "MacAddress": "02:42:ac:12:00:02",
 "IPv4Address": "172.18.0.2/16",
 "IPv6Address": ""
 }
 },
 "Options": {},
 "Labels": {}
 }
]

如您所见,它包含有关在 IPAM 部分中使用的网络寻址信息,以及网络中运行的两个容器的详细信息。

IP 地址管理(IPAM)是规划、跟踪和管理网络内的 IP 地址的一种方法。IPAM 具有 DNS 和 DHCP 服务,因此每个服务都会在另一个服务发生变化时得到通知。例如,DHCP 为container2分配一个地址。然后更新 DNS 服务,以便在针对container2进行查找时返回 DHCP 分配的 IP 地址。

在我们继续下一节之前,我们应该删除一个应用程序和相关网络。要做到这一点,请运行以下命令:

$ docker container stop moby-counter2 redis2
$ docker container prune
$ docker network prune

这将删除容器和网络,如下截图所示:

正如本节开头提到的,这只是默认的网络驱动程序,这意味着我们只能在单个 Docker 主机上使用我们的网络。在后面的章节中,我们将看看如何将我们的 Docker 网络扩展到多个主机甚至多个提供商。

Docker 卷

如果您一直在按照上一节的网络示例进行操作,您应该有两个正在运行的容器,如下截图所示:

当您在浏览器中访问应用程序(在http://localhost:8080/),您可能会看到屏幕上已经有 Docker 标志。让我们停下来,然后移除 Redis 容器,看看会发生什么。要做到这一点,请运行以下命令:

$ docker container stop redis
$ docker container rm redis

如果您的浏览器打开,您可能会注意到 Docker 图标已经淡出到背景中,屏幕中央有一个动画加载器。这基本上是为了显示应用程序正在等待与 Redis 容器重新建立连接:

使用以下命令重新启动 Redis 容器:

$ docker container run -d --name redis --network moby-counter redis:alpine

这恢复了连接;但是,当您开始与应用程序交互时,您之前的图标会消失,您将得到一个干净的界面。快速在屏幕上添加一些图标,这次以不同的模式放置,就像我在这里做的一样:

一旦你有了一个模式,让我们再次通过以下命令移除 Redis 容器:

$ docker container stop redis
$ docker container rm redis

正如我们在本章前面讨论过的,容器中的数据丢失是可以预料的。然而,由于我们使用了官方的 Redis 镜像,实际上我们并没有丢失任何数据。

我们使用的官方 Redis 镜像的 Dockerfile 如下所示:

FROM alpine:3.8

RUN addgroup -S redis && adduser -S -G redis redis
RUN apk add --no-cache 'su-exec>=0.2'

ENV REDIS_VERSION 4.0.11
ENV REDIS_DOWNLOAD_URL http://download.redis.io/releases/redis-4.0.11.tar.gz
ENV REDIS_DOWNLOAD_SHA fc53e73ae7586bcdacb4b63875d1ff04f68c5474c1ddeda78f00e5ae2eed1bbb

RUN set -ex; \
 \
 apk add --no-cache --virtual .build-deps \
 coreutils \
 gcc \
 jemalloc-dev \
 linux-headers \
 make \
 musl-dev \
 ; \
 \
 wget -O redis.tar.gz "$REDIS_DOWNLOAD_URL"; \
 echo "$REDIS_DOWNLOAD_SHA *redis.tar.gz" | sha256sum -c -; \
 mkdir -p /usr/src/redis; \
 tar -xzf redis.tar.gz -C /usr/src/redis --strip-components=1; \
 rm redis.tar.gz; \
 \
 grep -q '^#define CONFIG_DEFAULT_PROTECTED_MODE 1$' /usr/src/redis/src/server.h; \
 sed -ri 's!^(#define CONFIG_DEFAULT_PROTECTED_MODE) 1$!\1 0!' /usr/src/redis/src/server.h; \
 grep -q '^#define CONFIG_DEFAULT_PROTECTED_MODE 0$' /usr/src/redis/src/server.h; \
 \
 make -C /usr/src/redis -j "$(nproc)"; \
 make -C /usr/src/redis install; \
 \
 rm -r /usr/src/redis; \
 \
 runDeps="$( \
 scanelf --needed --nobanner --format '%n#p' --recursive /usr/local \
 | tr ',' '\n' \
 | sort -u \
 | awk 'system("[ -e /usr/local/lib/" $1 " ]") == 0 { next } { print "so:" $1 }' \
 )"; \
 apk add --virtual .redis-rundeps $runDeps; \
 apk del .build-deps; \
 \
 redis-server --version

RUN mkdir /data && chown redis:redis /data
VOLUME /data
WORKDIR /data

COPY docker-entrypoint.sh /usr/local/bin/
ENTRYPOINT ["docker-entrypoint.sh"]

EXPOSE 6379
CMD ["redis-server"]

如果你注意到文件末尾有VOLUMEWORKDIR指令声明;这意味着当我们的容器启动时,Docker 实际上创建了一个卷,然后在卷内部运行了redis-server

通过运行以下命令,我们可以看到这一点:

$ docker volume ls

这将显示至少两个卷,如下截图所示:

正如你所看到的,卷的名称并不友好;实际上,它是卷的唯一 ID。那么当我们启动 Redis 容器时,我们该如何使用这个卷呢?

我们从 Dockerfile 中知道,卷被挂载到容器中的/data,所以我们只需要告诉 Docker 在运行时使用哪个卷以及应该挂载到哪里。

为此,请运行以下命令,确保你用自己的卷 ID 替换卷 ID:

$ docker container run -d --name redis -v c2e417eab8fa20944582e2de525ab87b749099043b8c487194b7b6415b537e6a:/data --network moby-counter redis:alpine 

如果你启动了 Redis 容器后,你的应用页面看起来仍在尝试重新连接到 Redis 容器,那么你可能需要刷新你的浏览器;如果刷新不起作用,可以通过运行docker container restart moby-counter来重新启动应用容器,然后再次刷新你的浏览器。

你可以通过运行以下命令来查看卷的内容,以附加到容器并列出/data中的文件:

$ docker container exec redis ls -lhat /data

这将返回类似以下内容:

total 12
drwxr-xr-x 1 root root 4.0K Aug 26 13:30 ..
drwxr-xr-x 2 redis redis 4.0K Aug 26 12:44 .
-rw-r--r-- 1 redis redis 392 Aug 26 12:44 dump.rdb

你也可以移除正在运行的容器并重新启动它,但这次使用第二个卷的 ID。从你浏览器中的应用程序可以看出,你最初创建的两种不同模式是完好无损的。

最后,你可以用自己的卷来覆盖这个卷。要创建一个卷,我们需要使用volume命令:

$ docker volume create redis_data

一旦创建完成,我们就可以通过运行以下命令来使用redis_data卷来存储我们的 Redis,这是在移除 Redis 容器后进行的操作,该容器可能已经在运行:

$ docker container run -d --name redis -v redis_data:/data --network moby-counter redis:alpine

然后我们可以根据需要重复使用这个卷,下面的屏幕显示了卷的创建,附加到一个容器,然后移除,最后重新附加到一个新的容器:

network命令一样,我们可以使用inspect命令查看有关卷的更多信息,如下所示:

$ docker volume inspect redis_data

前面的代码将产生类似以下输出:

[
 {
 "CreatedAt": "2018-08-26T13:39:33Z",
 "Driver": "local",
 "Labels": {},
 "Mountpoint": "/var/lib/docker/volumes/redis_data/_data",
 "Name": "redis_data",
 "Options": {},
 "Scope": "local"
 }
]

您可以看到使用本地驱动程序时卷并不多;值得注意的一件事是,数据存储在 Docker 主机机器上的路径是/var/lib/docker/volumes/redis_data/_data。如果您使用的是 Docker for Mac 或 Docker for Windows,那么这个路径将是您的 Docker 主机虚拟机,而不是您的本地机器,这意味着您无法直接访问卷内的数据。

不过不用担心;我们将在后面的章节中讨论 Docker 卷以及您如何与数据交互。现在,我们应该整理一下。首先,删除这两个容器和网络:

$ docker container stop redis moby-counter $ docker container prune
$ docker network prune

然后我们可以通过运行以下命令来删除卷:

$ docker volume prune

您应该看到类似以下终端输出:

现在我们又回到了一个干净的状态,所以我们可以继续下一章了。

总结

在本章中,我们看了如何使用 Docker 命令行客户端来管理单个容器并在它们自己的隔离 Docker 网络中启动多容器应用程序。我们还讨论了如何使用 Docker 卷在文件系统上持久化数据。到目前为止,在本章和之前的章节中,我们已经详细介绍了我们将在接下来的章节中使用的大部分可用命令:

$ docker container [command]
$ docker network [command]
$ docker volume [command]
$ docker image [command]

现在我们已经涵盖了在本地使用 Docker 的四个主要领域,我们可以开始看如何使用 Docker Compose 创建更复杂的应用程序。

在下一章中,我们将看一下另一个核心 Docker 工具,称为 Docker Compose。

问题

  1. 您必须附加哪个标志到docker container ls以查看所有容器,包括运行和停止的容器?

  2. 真或假:-p 8080:80标志将容器上的端口 80 映射到主机上的端口 8080。

  3. 解释使用Ctrl + C退出您已连接的容器时发生的情况与使用--sig-proxy=false命令的附加命令。

  4. 真或假:exec命令将您连接到正在运行的进程。

  5. 您将使用哪个标志为容器添加别名,以便在另一个网络中已经具有相同 DNS 名称的容器运行时响应 DNS 请求?

  6. 您将使用哪个命令来查找有关 Docker 卷的详细信息?

进一步阅读

您可以在以下链接找到更多关于本章讨论的一些主题的信息:

第五章:Docker Compose

在本章中,我们将介绍另一个核心 Docker 工具,称为 Docker Compose,以及目前正在开发中的 Docker App。我们将把本章分解为以下几个部分:

  • Docker Compose 介绍

  • 我们的第一个 Docker Compose 应用程序

  • Docker Compose YAML 文件

  • Docker Compose 命令

  • Docker App

技术要求

与之前的章节一样,我们将继续使用本地的 Docker 安装。同样,在本章中的截图将来自我首选的操作系统 macOS。

与以前一样,我们将运行的 Docker 命令将适用于我们迄今为止安装了 Docker 的三种操作系统。但是,一些支持命令可能只适用于 macOS 和基于 Linux 的操作系统。

本章中使用的代码的完整副本可以在以下网址找到:github.com/PacktPublishing/Mastering-Docker-Third-Edition/tree/master/chapter05

观看以下视频以查看代码的实际操作:

bit.ly/2q7MJZU

介绍 Docker Compose

在第一章,Docker 概述中,我们讨论了 Docker 旨在解决的一些问题。我们解释了它如何解决诸如通过将进程隔离到单个容器中来同时运行两个应用程序等挑战,这意味着您可以在同一主机上运行完全不同版本的相同软件堆栈,比如 PHP 5.6 和 PHP 7,就像我们在第二章,构建容器镜像中所做的那样。

在第四章,管理容器的最后,我们启动了一个由多个容器组成的应用程序,而不是在单个容器中运行所需的软件堆栈。我们启动的示例应用程序 Moby Counter 是用 Node.js 编写的,并使用 Redis 作为后端来存储键值,这里我们的案例是 Docker 标志的位置。

这意味着我们必须启动两个容器,一个用于应用程序,一个用于 Redis。虽然启动应用程序本身相当简单,但手动启动单个容器存在许多缺点。

例如,如果我想让同事部署相同的应用程序,我将不得不传递以下命令:

$ docker image pull redis:alpine
$ docker image pull russmckendrick/moby-counter
$ docker network create moby-counter
$ docker container run -d --name redis --network moby-counter redis:alpine
$ docker container run -d --name moby-counter --network moby-counter -p 8080:80 russmckendrick/moby-counter

好吧,如果镜像还没有被拉取,我可以不用执行前两个命令,因为在运行时会拉取镜像,但随着应用程序变得更加复杂,我将不得不开始传递一个越来越庞大的命令和指令集。

我还必须明确指出,他们必须考虑命令需要执行的顺序。此外,我的笔记还必须包括任何潜在问题的细节,以帮助他们解决任何问题——这可能意味着我们现在面临的是一个工作是 DevOps 问题的场景,我们要尽一切努力避免。

虽然 Docker 的责任应该止步于创建镜像和使用这些镜像启动容器,但他们认为这是技术意味着我们不会陷入的一个场景。多亏了 Docker,人们不再需要担心他们启动应用程序的环境中的不一致性,因为现在可以通过镜像进行部署。

因此,回到 2014 年 7 月,Docker 收购了一家名为 Orchard Laboratories 的小型英国初创公司,他们提供了两种基于容器的产品。

这两个产品中的第一个是基于 Docker 的主机平台:可以将其视为 Docker Machine 和 Docker 本身的混合体。通过一个单一的命令orchard,您可以启动一个主机机器,然后将您的 Docker 命令代理到新启动的主机上;例如,您可以使用以下命令:

$ orchard hosts create
$ orchard docker run -p 6379:6379 -d orchardup/redis

其中一个是在 Orchard 平台上启动 Docker 主机,然后启动一个 Redis 容器。

第二个产品是一个名为 Fig 的开源项目。Fig 允许您使用YAML文件来定义您想要如何构建多容器应用程序的结构。然后,它会根据YAML文件自动启动容器。这样做的好处是,因为它是一个 YAML 文件,开发人员可以很容易地在他们的代码库中开始使用fig.yml文件和 Dockerfiles 一起进行部署。

在这两种产品中,Docker 为 Fig 收购了 Orchard Laboratories。不久之后,Orchard 服务被停止,2015 年 2 月,Fig 成为了 Docker Compose。

作为我们在第一章Docker 概述中安装 Docker for Mac、Docker for Windows 和 Linux 上的 Docker 的一部分,我们安装了 Docker Compose,因此不再讨论它的功能,让我们尝试使用 Docker Compose 仅仅启动我们在上一章末尾手动启动的两个容器应用程序。

我们的第一个 Docker Compose 应用程序

如前所述,Docker Compose 使用一个 YAML 文件,通常命名为dockercompose.yml,来定义您的多容器应用程序应该是什么样子的。我们在第四章管理容器中启动的两个容器应用程序的 Docker Compose 表示如下:

version: "3"

services:
 redis:
 image: redis:alpine
 volumes:
 - redis_data:/data
 restart: always
 mobycounter:
 depends_on:
 - redis
 image: russmckendrick/moby-counter
 ports:
 - "8080:80"
 restart: always

volumes:
 redis_data:

即使没有逐行分析文件中的每一行,也应该很容易跟踪到正在发生的事情。要启动我们的应用程序,我们只需切换到包含您的docker-compose.yml文件的文件夹,并运行以下命令:

$ docker-compose up

正如您从以下终端输出中所看到的,启动时发生了很多事情:

正如您从前几行所看到的,Docker Compose 做了以下事情:

  • 它创建了一个名为mobycounter_redis_data的卷,使用我们在docker-compose.yml文件末尾定义的默认驱动程序。

  • 它创建了一个名为mobycounter_default的网络,使用默认网络驱动程序——在任何时候我们都没有要求 Docker Compose 这样做。稍后再详细讨论。

  • 它启动了两个容器,一个叫做mobycounter_redis_1,第二个叫做mobycounter_mobycounter_1

您可能还注意到我们的多容器应用程序中的 Docker Compose 命名空间已经用mobycounter作为前缀。它从我们存储 Docker Compose 文件的文件夹中获取了这个名称。

一旦启动,Docker Compose 连接到mobycounter_redis_1mobycounter_mobycounter_1,并将输出流到我们的终端会话。在终端屏幕上,您可以看到redis_1mobycounter_1开始相互交互。

当使用docker-compose up运行 Docker Compose 时,它将在前台运行。按下Ctrl + C将停止容器并返回对终端会话的访问。

Docker Compose YAML 文件

在我们更深入地使用 Docker Compose 之前,我们应该深入研究docker-compose.yml文件,因为这些文件是 Docker Compose 的核心。

YAML 是一个递归缩写,代表YAML 不是标记语言。它被许多不同的应用程序用于配置和定义人类可读的结构化数据格式。你在示例中看到的缩进非常重要,因为它有助于定义数据的结构。

Moby 计数器应用程序

我们用来启动多容器应用程序的docker-compose.yml文件分为三个独立的部分。

第一部分简单地指定了我们正在使用的 Docker Compose 定义语言的版本;在我们的情况下,由于我们正在运行最新版本的 Docker 和 Docker Compose,我们使用的是版本 3:

version: "3"

接下来的部分是我们定义容器的地方;这部分是服务部分。它采用以下格式:

services: --> container name: ----> container options --> container name: ----> container options

在我们的示例中,我们定义了两个容器。我已经将它们分开以便阅读:

services:
 redis:
 image: redis:alpine
 volumes:
 - redis_data:/data
 restart: always
 mobycounter:
 depends_on:
 - redis
 image: russmckendrick/moby-counter
 ports:
 - "8080:80"
 restart: always

定义服务的语法接近于使用docker container run命令启动容器。我说接近是因为虽然在阅读定义时它是完全合理的,但只有在仔细检查时才会意识到 Docker Compose 语法和docker container run命令之间实际上存在很多差异。

例如,在运行docker container run命令时,以下内容没有标志:

  • image:这告诉 Docker Compose 要下载和使用哪个镜像。在命令行上运行docker container run时,这不作为选项存在,因为你只能运行一个单独的容器;正如我们在之前的章节中看到的,镜像总是在命令的末尾定义,而不需要传递标志。

  • volume:这相当于--volume标志,但它可以接受多个卷。它只使用在 Docker Compose YAML 文件中声明的卷;稍后会详细介绍。

  • depends_on:这在docker container run调用中永远不会起作用,因为该命令只针对单个容器。在 Docker Compose 中,depends_on用于帮助构建一些逻辑到启动容器的顺序中。例如,只有在容器 A 成功启动后才启动容器 B。

  • ports:这基本上是--publish标志,它接受一个端口列表。

我们使用的命令中唯一具有与在运行docker container run时等效标志的部分是这个:

  • restart:这与使用--restart标志相同,并接受相同的输入。

我们的 Docker Compose YAML 文件的最后一部分是我们声明卷的地方:

volume:
 redis_data:

示例投票应用程序

如前所述,Moby 计数器应用程序的 Docker Compose 文件是一个相当简单的示例。让我们看看一个更复杂的 Docker Compose 文件,看看我们如何引入构建容器和多个网络。

在本书的存储库中,您将在chapter05目录中找到一个名为example-voting-app的文件夹。这是来自官方 Docker 示例存储库的投票应用程序的一个分支。

正如您所看到的,如果您打开docker-compose.yml文件,该应用程序由五个容器、两个网络和一个卷组成。暂时忽略其他文件;我们将在以后的章节中查看其中一些。让我们逐步了解docker-compose.yml文件,因为其中有很多内容:

version: "3"

services:

正如您所看到的,它从定义版本开始,然后开始列出服务。我们的第一个容器名为vote;它是一个允许用户提交他们的投票的 Python 应用程序。正如您从以下定义中所看到的,我们实际上是通过使用build而不是image命令从头开始构建一个镜像,而不是下载一个镜像:

 vote:
 build: ./vote
 command: python app.py
 volumes:
 - ./vote:/app
 ports:
 - "5000:80"
 networks:
 - front-tier
 - back-tier

构建指令在这里告诉 Docker Compose 使用 Dockerfile 构建一个容器,该 Dockerfile 可以在./vote文件夹中找到。Dockerfile 本身对于 Python 应用程序来说非常简单。

容器启动后,我们将./vote文件夹从主机机器挂载到容器中,这是通过传递我们想要挂载的文件夹的路径以及我们想要在容器中挂载的位置来实现的。

我们告诉容器在启动时运行python app.py。我们将主机机器上的端口5000映射到容器上的端口80,最后,我们将两个网络进一步附加到容器上,一个称为front-tier,另一个称为back-tier

front-tier网络将包含必须将端口映射到主机机器的容器;back-tier网络保留用于不需要暴露其端口的容器,并充当私有的隔离网络。

接下来,我们有另一个连接到front-tier网络的容器。该容器显示投票结果。result容器包含一个 Node.js 应用程序,它连接到我们马上会提到的 PostgreSQL 数据库,并实时显示投票容器中的投票结果。与vote容器一样,该镜像是使用位于./result文件夹中的Dockerfile本地构建的:

 result:
 build: ./result
 command: nodemon server.js
 volumes:
 - ./result:/app
 ports:
 - "5001:80"
 - "5858:5858"
 networks:
 - front-tier
 - back-tier

我们正在暴露端口5001,这是我们可以连接以查看结果的地方。接下来,也是最后一个应用程序容器被称为worker

 worker:
 build:
 context: ./worker
 depends_on:
 - "redis"
 networks:
 - back-tier

worker 容器运行一个.NET 应用程序,其唯一工作是连接到 Redis,并通过将每个投票转移到运行在名为db的容器上的 PostgreSQL 数据库来注册每个投票。该容器再次使用Dockerfile构建,但这一次,我们不是传递存储Dockerfile和应用程序的文件夹路径,而是使用上下文。这为 docker 构建设置工作目录,并允许您定义附加选项,如标签和更改Dockerfile的名称。

由于该容器除了连接到redisdb容器外什么也不做,因此它不需要暴露任何端口,因为没有任何东西直接连接到它;它也不需要与运行在front-tier网络上的任何容器通信,这意味着我们只需要添加back-tier网络。

所以,我们现在有了vote应用程序,它注册来自最终用户的投票并将它们发送到redis容器,然后由worker容器处理。redis容器的服务定义如下:

 redis:
 image: redis:alpine
 container_name: redis
 ports: ["6379"]
 networks:
 - back-tier

该容器使用官方的 Redis 镜像,并不是从 Dockerfile 构建的;我们确保端口6379可用,但仅在back-tier网络上。我们还指定了容器的名称,将其设置为redis,使用container_name。这是为了避免我们在代码中对 Docker Compose 生成的默认名称做任何考虑,因为您可能还记得,Docker Compose 使用文件夹名称在其自己的应用程序命名空间中启动容器。

接下来,也是最后一个容器是我们已经提到的 PostgreSQL 容器,名为db

 db:
 image: postgres:9.4
 container_name: db
 volumes:
 - "db-data:/var/lib/postgresql/data"
 networks:
 - back-tier

正如你所看到的,它看起来与redis容器非常相似,因为我们正在使用官方镜像;然而,你可能注意到我们没有暴露端口,因为这是官方镜像中的默认选项。我们还指定了容器的名称。

因为这是我们将存储投票的地方,我们正在创建和挂载一个卷来作为我们的 PostgreSQL 数据库的持久存储:

volumes:
 db-data:

最后,这是我们一直在谈论的两个网络:

networks:
 front-tier:
 back-tier:

运行docker-compose up会给出很多关于启动过程的反馈;首次启动应用程序大约需要 5 分钟。如果你没有跟着操作并自己启动应用程序,接下来是启动的摘要版本。

你可能会收到一个错误,指出npm ERR! request to https://registry.npmjs.org/nodemon failed, reason: Hostname/IP doesn't match certificate's altnames。如果是这样,那么以有写入/etc/hosts权限的用户身份运行以下命令echo "104.16.16.35 registry.npmjs.org" >> /etc/hosts

我们首先创建网络并准备好卷供我们的容器使用:

Creating network "example-voting-app_front-tier" with the default driver
Creating network "example-voting-app_back-tier" with the default driver
Creating volume "example-voting-app_db-data" with default driver

然后我们构建vote容器镜像:

Building vote
Step 1/7 : FROM python:2.7-alpine
2.7-alpine: Pulling from library/python
8e3ba11ec2a2: Pull complete
ea489525e565: Pull complete
f0d8a8560df7: Pull complete
8971431029b9: Pull complete
Digest: sha256:c9f17d63ea49a186d899cb9856a5cc1c601783f2c9fa9b776b4582a49ceac548
Status: Downloaded newer image for python:2.7-alpine
 ---> 5082b69714da
Step 2/7 : WORKDIR /app
 ---> Running in 663db929990a
Removing intermediate container 663db929990a
 ---> 45fe48ea8e4c
Step 3/7 : ADD requirements.txt /app/requirements.txt
 ---> 2df3b3211688
Step 4/7 : RUN pip install -r requirements.txt
 ---> Running in 23ad90b81e6b
[lots of python build output here]
Step 5/7 : ADD . /app
 ---> cebab4f80850
Step 6/7 : EXPOSE 80
 ---> Running in b28d426e3516
Removing intermediate container b28d426e3516
 ---> bb951ea7dffc
Step 7/7 : CMD ["gunicorn", "app:app", "-b", "0.0.0.0:80", "--log-file", "-", "--access-logfile", "-", "--workers", "4", "--keep-alive", "0"]
 ---> Running in 2e97ca847f8a
Removing intermediate container 2e97ca847f8a
 ---> 638c74fab05e
Successfully built 638c74fab05e
Successfully tagged example-voting-app_vote:latest
WARNING: Image for service vote was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.

一旦vote镜像构建完成,worker镜像就会被构建:

Building worker
Step 1/5 : FROM microsoft/dotnet:2.0.0-sdk
2.0.0-sdk: Pulling from microsoft/dotnet
3e17c6eae66c: Pull complete
74d44b20f851: Pull complete
a156217f3fa4: Pull complete
4a1ed13b6faa: Pull complete
18842ff6b0bf: Pull complete
e857bd06f538: Pull complete
b800e4c6f9e9: Pull complete
Digest: sha256:f4ea9cdf980bb9512523a3fb88e30f2b83cce4b0cddd2972bc36685461081e2f
Status: Downloaded newer image for microsoft/dotnet:2.0.0-sdk
 ---> fde8197d13f4
Step 2/5 : WORKDIR /code
 ---> Running in 1ca2374cff99
Removing intermediate container 1ca2374cff99
 ---> 37f9b05325f9
Step 3/5 : ADD src/Worker /code/src/Worker
 ---> 9d393c6bd48c
Step 4/5 : RUN dotnet restore -v minimal src/Worker && dotnet publish -c Release -o "./" "src/Worker/"
 ---> Running in ab9fe7820062
 Restoring packages for /code/src/Worker/Worker.csproj...
 [lots of .net build output here]
 Restore completed in 8.86 sec for /code/src/Worker/Worker.csproj.
Microsoft (R) Build Engine version 15.3.409.57025 for .NET Core
Copyright (C) Microsoft Corporation. All rights reserved.
 Worker -> /code/src/Worker/bin/Release/netcoreapp2.0/Worker.dll
 Worker -> /code/src/Worker/
Removing intermediate container ab9fe7820062
 ---> cf369fbb11dd
Step 5/5 : CMD dotnet src/Worker/Worker.dll
 ---> Running in 232416405e3a
Removing intermediate container 232416405e3a
 ---> d355a73a45c9
Successfully built d355a73a45c9
Successfully tagged example-voting-app_worker:latest
WARNING: Image for service worker was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.

然后拉取redis镜像:

Pulling redis (redis:alpine)...
alpine: Pulling from library/redis
8e3ba11ec2a2: Already exists
1f20bd2a5c23: Pull complete
782ff7702b5c: Pull complete
82d1d664c6a7: Pull complete
69f8979cc310: Pull complete
3ff30b3bc148: Pull complete
Digest: sha256:43e4d14fcffa05a5967c353dd7061564f130d6021725dd219f0c6fcbcc6b5076
Status: Downloaded newer image for redis:alpine

接下来是为db容器准备的 PostgreSQL 镜像:

Pulling db (postgres:9.4)...
9.4: Pulling from library/postgres
be8881be8156: Pull complete
01d7a10e8228: Pull complete
f8968e0fd5ca: Pull complete
69add08e7e51: Pull complete
954fe1f9e4e8: Pull complete
9ace39987bb3: Pull complete
9020931bcc5d: Pull complete
71f421dd7dcd: Pull complete
a909f41228ab: Pull complete
cb62befcd007: Pull complete
4fea257fde1a: Pull complete
f00651fb0fbf: Pull complete
0ace3ceac779: Pull complete
b64ee32577de: Pull complete
Digest: sha256:7430585790921d82a56c4cbe62fdf50f03e00b89d39cbf881afa1ef82eefd61c
Status: Downloaded newer image for postgres:9.4

现在是大事将要发生的时候了;构建result镜像。Node.js 非常冗长,所以在执行Dockerfilenpm部分时,屏幕上会打印出相当多的输出;事实上,有超过 250 行的输出:

Building result
Step 1/11 : FROM node:8.9-alpine
8.9-alpine: Pulling from library/node
605ce1bd3f31: Pull complete
79b85b1676b5: Pull complete
20865485d0c2: Pull complete
Digest: sha256:6bb963d58da845cf66a22bc5a48bb8c686f91d30240f0798feb0d61a2832fc46
Status: Downloaded newer image for node:8.9-alpine
 ---> 406f227b21f5
Step 2/11 : RUN mkdir -p /app
 ---> Running in 4af9c85c67ee
Removing intermediate container 4af9c85c67ee
 ---> f722dde47fcf
Step 3/11 : WORKDIR /app
 ---> Running in 8ad29a42f32f
Removing intermediate container 8ad29a42f32f
 ---> 32a05580f2ec
Step 4/11 : RUN npm install -g nodemon
[lots and lots of nodejs output]
Step 8/11 : COPY . /app
 ---> 725966c2314f
Step 9/11 : ENV PORT 80
 ---> Running in 6f402a073bf4
Removing intermediate container 6f402a073bf4
 ---> e3c426b5a6c8
Step 10/11 : EXPOSE 80
 ---> Running in 13db57b3c5ca
Removing intermediate container 13db57b3c5ca
 ---> 1305ea7102cf
Step 11/11 : CMD ["node", "server.js"]
 ---> Running in a27700087403
Removing intermediate container a27700087403
 ---> 679c16721a7f
Successfully built 679c16721a7f
Successfully tagged example-voting-app_result:latest
WARNING: Image for service result was built because it did not already exist. To rebuild this image you must use `docker-compose build` or `docker-compose up --build`.

应用程序的result部分可以在http://localhost:5001访问。默认情况下没有投票,它是 50/50 的分割:

应用程序的vote部分可以在http://localhost:5000找到:

点击CATSDOGS将注册一票;你应该能在终端的 Docker Compose 输出中看到这一点:

有一些错误,因为只有当投票应用程序注册第一张选票时,Redis 表结构才会被创建;一旦投票被投出,Redis 表结构将被创建,并且工作容器将接收该投票并通过写入db容器来处理它。一旦投票被投出,result容器将实时更新:

在接下来的章节中,当我们查看如何启动 Docker Swarm 堆栈和 Kubenetes 集群时,我们将再次查看 Docker Compose YAML 文件。现在,让我们回到 Docker Compose,并查看一些我们可以运行的命令。

Docker Compose 命令

我们已经过了本章的一半,我们运行的唯一 Docker Compose 命令是docker-compose up。如果您一直在跟着做,并且运行docker container ls -a,您将看到类似以下终端屏幕的内容:

正如您所看到的,我们有很多容器的状态是“退出”。这是因为当我们使用Ctrl + C返回到我们的终端时,Docker Compose 容器被停止了。

选择一个 Docker Compose 应用程序,并切换到包含docker-compose.yml文件的文件夹,我们将通过一些更多的 Docker Compose 命令进行工作。我将使用示例投票应用程序。

上升和 PS

第一个是docker-compose up,但这次,我们将添加一个标志。在您选择的应用程序文件夹中,运行以下命令:

$ docker-compose up -d

这将重新启动您的应用程序,这次是在分离模式下:

一旦控制台返回,您应该能够使用以下命令检查容器是否正在运行:

$ docker-compose ps

正如您从以下终端输出中所看到的,所有容器的状态都是“上升”的:

运行这些命令时,Docker Compose 只会知道在docker-compose.yml文件的服务部分中定义的容器;所有其他容器将被忽略,因为它们不属于我们的服务堆栈。

配置

运行以下命令将验证我们的docker-compose.yml文件:

$ docker-compose config

如果没有问题,它将在屏幕上打印出您的 Docker Compose YAML 文件的渲染副本;这是 Docker Compose 将解释您的文件的方式。如果您不想看到这个输出,只想检查错误,那么您可以运行以下命令:

$ docker-compose config -q

这是--quiet的简写。如果有任何错误,我们到目前为止所做的示例中不应该有错误,它们将显示如下:

ERROR: yaml.parser.ParserError: while parsing a block mapping in "./docker-compose.yml", line 1, column 1 expected <block end>, but found '<block mapping start>' in "./docker-compose.yml", line 27, column 3

Pull,build 和 create

接下来的两个命令将帮助您准备启动 Docker Compose 应用程序。以下命令将读取您的 Docker Compose YAML 文件并拉取它找到的任何镜像:

$ docker-compose pull

以下命令将执行在您的文件中找到的任何构建指令:

$ docker-compose build

当您首次定义 Docker Compose 应用程序并希望在启动应用程序之前进行测试时,这些命令非常有用。如果 Dockerfile 有更新,docker-compose build命令也可以用来触发构建。

pullbuild命令只生成/拉取我们应用程序所需的镜像;它们不配置容器本身。为此,我们需要使用以下命令:

$ docker-compose create

这将创建但不启动容器。与docker container create命令一样,它们将处于退出状态,直到您启动它们。create命令有一些有用的标志可以传递:

  • --force-recreate:即使配置没有更改,也会重新创建容器

  • --no-recreate:如果容器已经存在,则不重新创建;此标志不能与前一个标志一起使用

  • --no-build:即使缺少需要构建的镜像,也不会构建镜像

  • --build:在创建容器之前构建镜像

开始,停止,重新启动,暂停和取消暂停

以下命令的工作方式与它们的 docker 容器对应物完全相同,唯一的区别是它们会对所有容器产生影响:

$ docker-compose start
$ docker-compose stop
$ docker-compose restart
$ docker-compose pause
$ docker-compose unpause

可以通过传递服务名称来针对单个服务;例如,要暂停取消暂停 db 服务,我们可以运行以下命令:

$ docker-compose pause db
$ docker-compose unpause db

Top,logs 和 events

接下来的三个命令都会向我们提供有关正在运行的容器和 Docker Compose 中发生的情况的反馈。

与其 docker 容器对应物一样,以下命令显示了在我们的 Docker Compose 启动的每个容器中运行的进程的信息:

$ docker-compose top

从以下终端输出可以看到,每个容器都分成了自己的部分:

如果您只想看到其中一个服务,只需在运行命令时传递其名称:

$ docker-compose top db

下一个命令会将每个正在运行的容器的logs流式传输到屏幕上:

$ docker-compose logs

docker container命令一样,您可以传递标志,如-f--follow,以保持流式传输,直到按下Ctrl + C。此外,您可以通过在命令末尾附加其名称来为单个服务流式传输日志:

events命令再次像 docker 容器版本一样工作;它实时流式传输事件,例如我们一直在讨论的其他命令触发的事件。例如,运行此命令:

$ docker-compose events

在第二个终端窗口中运行docker-compose pause会得到以下输出:

这两个命令类似于它们的 docker 容器等效命令。运行以下命令:

$ docker-compose exec worker ping -c 3 db

这将在已经运行的worker容器中启动一个新进程,并对db容器进行三次 ping,如下所示:

run命令在应用程序中需要以容器化命令运行一次时非常有用。例如,如果您使用诸如 composer 之类的软件包管理器来更新存储在卷上的项目的依赖关系,可以运行类似以下命令:

$ docker-compose run --volume data_volume:/app composer install

这将使用install命令在composer容器中运行,并将data_volume挂载到容器内的/app

规模

scale命令将接受您传递给命令的服务,并将其扩展到您定义的数量;例如,要添加更多的 worker 容器,我只需要运行以下命令:

$ docker-compose scale worker=3

然而,这实际上会给出以下警告:

WARNING: The scale command is deprecated. Use the up command with the -scale flag instead.

我们现在应该使用以下命令:

$ docker-compose up -d --scale worker=3

虽然scale命令在当前版本的 Docker Compose 中存在,但它将在将来的软件版本中被移除。

您会注意到我选择了扩展 worker 容器的数量。这是有充分理由的,如果您尝试运行以下命令,您将自己看到:

$ docker-compose up -d --scale vote=3

您会注意到,虽然 Docker Compose 创建了额外的两个容器,但它们未能启动,并显示以下错误:

这是因为我们不能有三个单独的容器都试图映射到相同的端口。对此有一个解决方法,我们将在后面的章节中更详细地讨论。

Kill、rm 和 down

我们最终要看的三个 Docker Compose 命令是用来移除/终止我们的 Docker Compose 应用程序的命令。第一个命令通过立即停止运行的容器进程来停止我们正在运行的容器。这就是kill命令:

$ docker-compose kill

运行此命令时要小心,因为它不会等待容器优雅地停止,比如运行docker-compose stop时,使用docker-compose kill命令可能会导致数据丢失。

接下来是rm命令;这将删除任何状态为exited的容器:

$ docker-compose rm

最后,我们有down命令。你可能已经猜到了,它的效果与运行docker-compose up相反:

$ docker-compose down

这将删除运行docker-compose up时创建的容器和网络。如果要删除所有内容,可以通过运行以下命令来实现:

$ docker-compose down --rmi all --volumes

当你运行docker-compose up命令时,这将删除所有容器、网络、卷和镜像(包括拉取和构建的镜像);这包括可能在 Docker Compose 应用程序之外使用的镜像。但是,如果镜像正在使用中,将会出现错误,并且它们将不会被移除:

从前面的输出中可以看到,有一个使用redis镜像的容器,Moby 计数器应用程序,因此它没有被移除。然而,Example Vote 应用程序使用的所有其他镜像都被移除了,包括作为初始docker-compose up的一部分构建的镜像,以及从 Docker Hub 下载的镜像。

Docker App

在开始本节之前,我应该发出以下警告:

我们将要讨论的功能非常实验性。它还处于早期开发阶段,不应被视为即将推出的功能的预览以外的东西。

因此,我只会介绍 macOS 版本的安装。然而,在安装之前,让我们讨论一下 Docker App 到底是什么意思。

虽然 Docker Compose 文件在与他人共享环境时非常有用,但您可能已经注意到,在本章中到目前为止,我们一直缺少一个非常关键的元素,那就是实际上分发您的 Docker Compose 文件的能力,类似于您如何分发 Docker 镜像。

Docker 已经承认了这一点,并且目前正在开发一个名为 Docker App 的新功能,希望能填补这一空白。

Docker App是一个自包含的二进制文件,可帮助您创建一个可以通过 Docker Hub 或 Docker 企业注册表共享的应用程序包。

我建议检查 GitHub 项目的R****eleases页面(您可以在Further reading部分找到链接),以确保您使用的是最新版本。如果版本晚于 0.4.1,您将需要在以下命令中替换版本号。

要在 macOS 上安装 Docker App,您可以运行以下命令,首先设置要下载的版本:

$ VERSION=v0.4.1

现在您已经有了正确的版本,可以使用以下命令下载并放置它:

$ curl -SL https://github.com/docker/app/releases/download/$VERSION/docker-app-darwin.tar.gz | tar xJ -C /usr/local/bin/
$ mv /usr/local/bin/docker-app-darwin /usr/local/bin/docker-app
$ chmod +x /usr/local/bin/docker-app

一旦就位,您应该能够运行以下命令,在屏幕上打印一些关于二进制的基本信息:

$ docker-app version

可以在此处查看前述命令的完整输出,供不跟随的人参考:

我们将使用的docker-compose.yml文件有一个轻微的更改。版本需要更新为3.6而不仅仅是3。不这样做将导致以下错误:

Error: unsupported Compose file version: 3

我们需要运行的命令,也是生成前述错误的命令,如下所示:

$ docker-app init --single-file mobycounter

此命令将我们的docker-compose.yml文件嵌入.dockerapp文件中。最初,文件中将有相当多的注释,详细说明您需要在进行下一步之前进行的更改。我在存储库中留下了一个未更改的文件版本,在chapter5/mobycounter-app文件夹中名为mobycounter.dockerapp.original

可以在此处找到mobycounter.dockerapp文件的编辑版本:

version: latest
name: mobycounter
description: An example Docker App file which packages up the Moby Counter application
namespace: masteringdockerthirdedition
maintainers:
 - name: Russ McKendrick
 email: russ@mckendrick.io

---
version: "3.6"

services:
 redis:
 image: redis:alpine
 volumes:
 - redis_data:/data
 restart: always
 mobycounter:
 depends_on:
 - redis
 image: russmckendrick/moby-counter
 ports:
 - "${port}:80"
 restart: always

volumes:
 redis_data:

---

{ "port":"8080" }

如您所见,它分为三个部分;第一部分包含有关应用程序的元数据,如下所示:

  • Version:这是将在 Docker Hub 上发布的应用程序的版本

  • Name:应用程序的名称,将显示在 Docker Hub 上

  • Description:应用程序的简短描述

  • 名称空间:这通常是您的 Docker Hub 用户名或您可以访问的组织

  • 维护者:应用程序的维护者列表

第二部分包含我们的 Docker Compose 文件。您可能会注意到一些选项已被替换为变量。在我们的示例中,我已经用${port}替换了端口8080port变量的默认值在最后一部分中定义。

一旦.dockerapp文件完成,您可以运行以下命令将 Docker 应用程序保存为镜像:

$ docker-app save

您可以通过运行以下命令仅查看您在主机上激活的 Docker 应用程序:

$ docker-app ls

由于 Docker 应用程序主要只是包装在标准 Docker 镜像中的一堆元数据,您也可以通过运行以下命令来查看它:

$ docker image ls

如果您没有跟随这部分,您可以在此处查看终端输出的结果:

运行以下命令可以概述 Docker 应用程序,就像您可以使用docker image inspect来查找有关镜像构建方式的详细信息一样:

$ docker-app inspect masteringdockerthirdedition/mobycounter.dockerapp:latest

如您从以下终端输出中所见,使用docker-app inspect而不是docker image inspect运行命令会得到更友好的输出:

现在我们已经完成了我们的应用程序,我们需要将其推送到 Docker Hub。要做到这一点,只需运行以下命令:

$ docker-app push

这意味着我们的应用程序现在已发布在 Docker Hub 上:

那么如何获取 Docker 应用程序呢?首先,我们需要删除本地镜像。要做到这一点,请运行以下命令:

$ docker image rm masteringdockerthirdedition/mobycounter.dockerapp:latest

一旦删除,移动到另一个目录:

$ cd ~/

现在,让我们下载 Docker 应用程序,更改端口并启动它:

$ docker-app render masteringdockerthirdedition/mobycounter:latest --set port="9090" | docker-compose -f - up

同样,对于那些没有跟随的人,可以在此找到前述命令的终端输出:

如您所见,甚至无需手动下载 Docker 应用程序镜像,我们的应用程序就已经运行起来了。转到http://localhost:9090/应该会显示一个邀请您点击添加标志的屏幕。

与正常的前台 Docker Compose 应用程序一样,按下Ctrl + C返回到您的终端。

您可以运行以下命令来交互和终止您的应用程序:

$ docker-app render masteringdockerthirdedition/mobycounter:latest --set port="9090" | docker-compose -f - ps $ docker-app render masteringdockerthirdedition/mobycounter:latest --set port="9090" | docker-compose -f - down --rmi all --volumes

Docker App 中还有更多功能。但我们还没有准备好进一步详细讨论。我们将在第八章,Docker Swarm 和第九章,Docker 和 Kubernetes 中回到 Docker App。

如本节顶部所述,此功能处于早期开发阶段,我们讨论的命令和功能可能会在未来发生变化。但即使在这个早期阶段,我希望您能看到 Docker App 的优势,以及它是如何在 Docker Compose 奠定的坚实基础上构建的。

摘要

希望您喜欢这一章关于 Docker Compose 的内容,我希望您能像我一样看到它已经从一个非常有用的第三方工具发展成为核心 Docker 体验中非常重要的一部分。

Docker Compose 引入了一些关键概念,指导您如何运行和管理容器。我们将在第八章,Docker Swarm 和第九章,Docker 和 Kubernetes 中进一步探讨这些概念。

在下一章中,我们将远离基于 Linux 的容器,快速了解 Windows 容器。

问题

  1. Docker Compose 文件使用哪种开源格式?

  2. 在我们最初的 Moby 计数器 Docker Compose 文件中,哪个标志与其 Docker CLI 对应物完全相同?

  3. 真或假:您只能在 Docker Compose 文件中使用 Docker Hub 的镜像?

  4. 默认情况下,Docker Compose 如何决定要使用的命名空间?

  5. 在 docker-compose up 中添加哪个标志以在后台启动容器?

  6. 运行 Docker Compose 文件的语法检查的最佳方法是什么?

  7. 解释 Docker App 工作的基本原理。

进一步阅读

有关 Orchard Laboratories 的详细信息,请参阅以下内容:

有关 Docker App 项目的更多信息,请参阅以下内容:

最后,这里有一些我们涵盖的其他主题的进一步链接:

第六章:Windows 容器

在这一章中,我们将讨论并了解 Windows 容器。微软已经接受容器作为在新硬件上部署旧应用程序的一种方式。与 Linux 容器不同,Windows 容器仅在基于 Windows 的 Docker 主机上可用。

在本章中,我们将涵盖以下主题:

  • Windows 容器简介

  • 为 Windows 容器设置 Docker 主机

  • 运行 Windows 容器

  • Windows 容器 Dockerfile

  • Windows 容器和 Docker Compose

技术要求

与之前的章节一样,我们将继续使用我们的本地 Docker 安装。同样,在本章中的屏幕截图将来自我首选的操作系统 macOS——是的,即使我们将要运行 Windows 容器,你仍然可以使用你的 macOS 客户端。稍后会详细介绍。

我们将运行的 Docker 命令将在我们迄今为止安装了 Docker 的三种操作系统上运行。然而,在本章中,我们将启动的容器只能在 Windows Docker 主机上运行。我们将在 macOS 和基于 Linux 的机器上使用 VirtualBox 和 Vagrant 来帮助启动和运行 Windows Docker 主机。

本章中使用的代码的完整副本可以在github.com/PacktPublishing/Mastering-Docker-Third-Edition/tree/master/chapter06/找到。

查看以下视频以查看代码的实际操作:

bit.ly/2PfjuSR

Windows 容器简介

作为一个在过去 20 年里几乎每天都在使用 macOS 和 Linux 计算机和笔记本电脑以及 Linux 服务器的人,再加上我唯一的微软 Windows 的经验是我拥有的 Windows XP 和 Windows 10 游戏 PC,以及我在工作中无法避免的偶尔的 Windows 服务器,Windows 容器的出现是一个有趣的发展。

现在,我从来没有认为自己是 Linux/UNIX 的粉丝。然而,微软在过去几年的行动甚至让我感到惊讶。在 2014 年的 Azure 活动中,微软宣布"MicrosoftLinux",自那以后就一发不可收拾:

  • Linux 在 Microsoft Azure 中是一等公民

  • .NET Core 是跨平台的,这意味着你可以在 Linux 和 Windows 上运行你的.NET 应用程序。

  • SQL Server 现在可以在 Linux 上使用

  • 你可以在 Windows 10 专业版机器上运行 Linux shell,比如 Ubuntu。

  • PowerShell 已经移植到 Linux。

  • 微软开发了跨平台工具,比如 Visual Studio Code,并将其开源。

  • 微软以 75 亿美元收购 GitHub!

很明显,昔日的微软已经不复存在,前任 CEO 史蒂夫·鲍尔默曾经公开嘲讽开源和 Linux 社区,称他们的话不适合在这里重复。

因此,这一宣布并不令人意外。在微软公开宣布对 Linux 的喜爱后的几个月,即 2014 年 10 月,微软和 Docker 宣布合作,推动在基于 Windows 的操作系统上,如 Windows 10 专业版和 Windows Server 2016 上采用容器技术。

那么 Windows 容器是什么?

从表面上看,它们与 Linux 容器没有什么不同。微软在 Windows 内核上的工作引入了与 Linux 上发现的相同的进程隔离。而且,与 Linux 容器一样,这种隔离还延伸到一个沙盒文件系统,甚至是 Windows 注册表。

由于每个容器实际上都是一个全新的 Windows Core 或 Windows Nano,这些又是精简的 Windows 服务器镜像(可以想象成 Windows 版的 Alpine Linux),安装管理员可以在同一台主机上运行多个 Docker 化的应用程序,而无需担心任何自定义注册表更改或需求冲突和引起问题。

再加上 Docker 命令行客户端提供的同样易用性,管理员们可以将传统应用迁移到更现代的硬件和主机操作系统,而无需担心管理多个运行旧不受支持版本 Windows 的虚拟机所带来的问题和开销。

Windows 容器还提供了另一层隔离。当容器启动时,Hyper-V 隔离在最小的虚拟机监视器内运行容器进程。这进一步将容器进程与主机机器隔离开来。然而,使用 Hyper-V 隔离的每个容器需要额外的资源,而且启动时间也会增加,因为需要在容器启动之前启动虚拟机监视器。

虽然 Hyper-V 隔离确实使用了微软的虚拟化技术,可以在 Windows 服务器和桌面版以及 Xbox One 系统软件中找到,但你不能使用标准的 Hyper-V 管理工具来管理 Hyper-V 隔离的容器。你必须使用 Docker。

在微软不得不投入大量工作和努力来启用 Windows 内核中的容器之后,为什么他们选择了 Docker 而不是创建自己的管理工具呢?

Docker 已经成为管理容器的首选工具,具有一组经过验证的 API 和庞大的社区。而且,它是开源的,这意味着微软不仅可以适应其在 Windows 上的使用,还可以为其发展做出贡献。

以下图表概述了 Windows 上的 Docker 的工作原理:

请注意,我说的是 Windows 上的 Docker,而不是 Docker for Windows;它们是非常不同的产品。Windows 上的 Docker 是与 Windows 内核交互的 Docker 引擎和客户端的本机版本,以提供 Windows 容器。Docker for Windows 是开发人员在其桌面上运行 Linux 和 Windows 容器的尽可能本机的体验。

为 Windows 容器设置 Docker 主机

正如你可能已经猜到的,你需要访问一个运行 Docker 的 Windows 主机。如果你没有运行 Windows 10 专业版的机器,也不用太担心——你可以在 macOS 和 Linux 上实现这一点。在我们讨论这些方法之前,让我们看看如何在 Windows 10 专业版上使用 Docker for Windows 安装运行 Windows 容器。

Windows 10 专业版

Windows 10 专业版原生支持 Windows 容器。但默认情况下,它配置为运行 Linux 容器。要从运行 Linux 容器切换到 Windows 容器,右键单击系统托盘中的 Docker 图标,然后从菜单中选择切换到 Windows 容器...

这将弹出以下提示:

点击切换按钮,几秒钟后,你现在将管理 Windows 容器。你可以通过打开提示符并运行以下命令来查看:

$ docker version

可以从以下输出中看到这一点:

Docker 引擎的OS/Archwindows/amd64,而不是我们到目前为止一直看到的linux/amd64。那就涵盖了 Windows 10 专业版。但是像我这样更喜欢 macOS 和 Linux 的人呢?

macOS 和 Linux

为了在 macOS 和 Linux 机器上访问 Windows 容器,我们将使用 Stefan Scherer 整理的优秀资源。在本书附带的存储库的chapter06文件夹中,有 Stefan 的 Windows - docker-machine repo的分支版本,其中包含您在 macOS 上运行 Windows 容器所需的所有文件。

在我们开始之前,您将需要以下工具 - Hashicorp 的 Vagrant 和 Oracle 的 Virtualbox。您可以从以下位置下载这些工具:

下载并安装后,打开终端,转到chapter06/docker-machine存储库文件夹,并运行以下命令:

$ vagrant up --provider virtualbox 2016-box

这将下载一个包含运行 Windows 容器所需的所有内容的 VirtualBox Windows Server 2016 核心评估映像。下载文件大小略大于 10 GB,因此请确保您具有足够的带宽和磁盘空间来运行该映像。

Vagrant 将启动映像,配置 VM 上的 Docker,并将所需的证书文件复制到您的本地 Docker 客户端以与主机进行交互。要切换到使用新启动的 Docker Windows 主机,只需运行以下命令:

$ eval $(docker-machine env 2016-box)

我们将在下一章节中更详细地介绍 Docker Machine。然而,前面的命令已重新配置了您的本地 Docker 客户端,以便与 Docker Windows 主机通信。您可以通过运行以下命令来查看:

$ docker version

如果您不跟着操作,可以查看下面的预期输出:

如您所见,我们现在连接到运行windows/amd64的 Docker 引擎。要切换回,您可以重新启动终端会话,或者运行以下命令:

$ eval $(docker-machine env -unset)

完成 Docker Windows 主机后,可以运行以下命令来停止它:

$ vagrant halt

或者,要完全删除它,请运行以下命令:

$ vagrant destroy

前面的命令必须在chapter06/docker-machine存储库文件夹中运行。

运行 Windows 容器

正如本章的第一部分所暗示的,使用 Docker 命令行客户端启动和与 Windows 容器交互与我们迄今为止运行的方式没有任何不同。让我们通过运行hello-world容器来测试一下:

$ docker container run hello-world

就像以前一样,这将下载hello-world容器并返回一条消息:

这一次唯一的区别是,Docker 不是拉取 Linux 镜像,而是拉取了基于nanoserver-sac2016镜像的windows-amd64版本的镜像。

现在,让我们来看看在前台运行容器,这次运行 PowerShell:

$ docker container run -it microsoft/windowsservercore  powershell

一旦您的 shell 处于活动状态,运行以下命令将为您提供计算机名称,即容器 ID:

$ Get-CimInstance -ClassName Win32_Desktop -ComputerName . 

您可以在下面的终端输出中看到上述命令的完整输出:

一旦您通过运行exit退出了 PowerShell,您可以通过运行以下命令查看容器 ID:

$ docker container ls -a

您可以在下面的屏幕中看到预期的输出:

现在,让我们来看看构建一个执行某些操作的镜像。

一个 Windows 容器 Dockerfile

Windows 容器镜像使用与 Linux 容器相同的 Dockerfile 命令格式。以下 Dockerfile 将在容器上下载、安装和启用 IIS Web 服务器:

# escape=`
FROM microsoft/nanoserver:sac2016

RUN powershell -NoProfile -Command `
    New-Item -Type Directory C:\install; `
    Invoke-WebRequest https://az880830.vo.msecnd.net/nanoserver-ga-2016/Microsoft-NanoServer-IIS-Package_base_10-0-14393-0.cab -OutFile C:\install\Microsoft-NanoServer-IIS-Package_base_10-0-14393-0.cab; `
    Invoke-WebRequest https://az880830.vo.msecnd.net/nanoserver-ga-2016/Microsoft-NanoServer-IIS-Package_English_10-0-14393-0.cab -OutFile C:\install\Microsoft-NanoServer-IIS-Package_English_10-0-14393-0.cab; `
    dism.exe /online /add-package /packagepath:c:\install\Microsoft-NanoServer-IIS-Package_base_10-0-14393-0.cab & `
    dism.exe /online /add-package /packagepath:c:\install\Microsoft-NanoServer-IIS-Package_English_10-0-14393-0.cab & `
    dism.exe /online /add-package /packagepath:c:\install\Microsoft-NanoServer-IIS-Package_base_10-0-14393-0.cab & ;`
    powershell -NoProfile -Command `
    Remove-Item -Recurse C:\install\ ; `
    Invoke-WebRequest https://dotnetbinaries.blob.core.windows.net/servicemonitor/2.0.1.3/ServiceMonitor.exe -OutFile C:\ServiceMonitor.exe; `
    Start-Service Was; `
    While ((Get-ItemProperty HKLM:\SYSTEM\CurrentControlSet\Services\WAS\Parameters\ -Name NanoSetup -ErrorAction Ignore) -ne $null) {Start-Sleep 1}

EXPOSE 80

ENTRYPOINT ["C:\\ServiceMonitor.exe", "w3svc"]

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

$ docker image build --tag local:dockerfile-iis .

构建后,运行docker image ls应该显示以下内容:

关于 Windows 容器镜像,您会立即注意到它们很大。这是在 Server 2019 发布时正在解决的问题。

使用以下命令运行容器将启动 IIS 镜像:

$ docker container run -d --name dockerfile-iis -p 8080:80 local:dockerfile-iis

您可以通过打开浏览器来看到您新启动的容器在运行。但是,您需要通过容器的 NAT IP 访问它,而不是转到http://localhost``:8080/。如果您使用的是 Windows 10 专业版,可以通过运行以下命令找到 NAT IP:

$ docker inspect --format="{{.NetworkSettings.Networks.nat.IPAddress}}" dockerfile-iis

这将为您提供一个 IP 地址,只需在末尾添加8080/;例如,http://172.31.20.180:8080/

macOS 用户可以运行以下命令,使用我们启动的 Vagrant VM 的 IP 地址来打开他们的浏览器:

$ open http://$(docker-machine ip 2016-box):8080/

无论您在哪个操作系统上启动了 IIS 容器,您都应该看到以下默认的临时页面:

要停止和删除我们迄今为止启动的容器,请运行以下命令:

$ docker container stop dockerfile-iis
$ docker container prune

到目前为止,我相信您会同意,这种体验与使用基于 Linux 的容器的 Docker 没有任何不同。

Windows 容器和 Docker Compose

在本章的最后一节中,我们将看看如何在 Windows Docker 主机上使用 Docker Compose。正如您已经猜到的那样,与我们在上一章中运行的命令相比,几乎没有什么变化。在存储库的chapter06文件夹中,有一个来自 Docker 示例存储库的dotnet-album-viewer应用程序的分支,因为它附带了一个docker-compose.yml文件。

Docker Compose 文件如下所示:

version: '2.1'

services:
 db:
 image: microsoft/mssql-server-windows-express
 environment:
 sa_password: "DockerCon!!!"
 ACCEPT_EULA: "Y"
 healthcheck:
 test: [ "CMD", "sqlcmd", "-U", "sa", "-P", "DockerCon!!!", "-Q", "select 1" ]
 interval: 2s
 retries: 10

 app:
 image: dockersamples/dotnet-album-viewer
 build:
 context: .
 dockerfile: docker/app/Dockerfile
 environment:
 - "Data:useSqLite=false"
 - "Data:SqlServerConnectionString=Server=db;Database=AlbumViewer;User Id=sa;Password=DockerCon!!!;MultipleActiveResultSets=true;App=AlbumViewer"
 depends_on:
 db:
 condition: service_healthy
 ports:
 - "80:80"

networks:
 default:
 external:
 name: nat

正如您所看到的,它使用与我们之前查看的 Docker Compose 文件相同的结构、标志和命令,唯一的区别是我们使用了专为 Windows 容器设计的 Docker Hub 中的镜像。

要构建所需的镜像,只需运行以下命令:

$ docker-compose build

然后,一旦构建完成,使用以下命令启动:

$ docker-compose up -d

与之前一样,然后您可以使用此命令查找 Windows 上的 IP 地址:

$ docker inspect -f "{{ .NetworkSettings.Networks.nat.IPAddress }}" musicstore_web_1

要打开应用程序,您只需要在浏览器中输入您的 Docker 主机的 IP 地址。如果您正在使用 macOS,运行以下命令:

$ open http://$(docker-machine ip 2016-box)/

您应该看到以下页面:

完成应用程序后,您可以运行以下命令来删除它:

$ docker-compose down --rmi all --volumes

总结

在本章中,我们简要介绍了 Windows 容器。正如您所见,由于微软采用了 Docker 作为 Windows 容器的管理工具,这种体验对于任何已经使用 Docker 来管理 Linux 容器的人来说都是熟悉的。

在下一章中,我们将更详细地了解 Docker Machine。

问题

  1. Windows 上的 Docker 引入了哪种额外的隔离层?

  2. 您将使用哪个命令来查找 Windows 容器的 NAT IP 地址?

  3. 真或假:Windows 上的 Docker 引入了一组额外的命令,您需要使用这些命令来管理 Windows 容器?

进一步阅读

您可以在本章提到的主题中找到更多信息如下:

第七章:Docker Machine

在本章中,我们将更深入地了解 Docker Machine,这是我们在上一章中提到的。它可以用于轻松启动和引导针对各种平台的 Docker 主机,包括本地或云环境。您也可以使用它来控制您的 Docker 主机。让我们看看本章将涵盖的内容:

  • Docker Machine 简介

  • 使用 Docker Machine 设置本地 Docker 主机

  • 在云中启动 Docker 主机

  • 使用其他基本操作系统

技术要求

与以前的章节一样,我们将继续使用我们的本地 Docker 安装。同样,在本章中的截图将来自我首选的操作系统 macOS。

我们将看看如何使用 Docker Machine 在本地使用 VirtualBox 启动基于 Docker 的虚拟机,以及在公共云中使用,因此,如果您想要在本章中的示例中跟随,您将需要一个 Digital Ocean 账户。

与以前一样,我们将在迄今为止安装了 Docker 的三个操作系统上运行 Docker 命令。然而,一些支持命令可能只适用于 macOS 和基于 Linux 的操作系统。

观看以下视频以查看代码的实际操作:

bit.ly/2Ansb5v

Docker Machine 简介

在我们卷起袖子并开始使用 Docker Machine 之前,我们应该花点时间讨论它在整个 Docker 生态系统中的地位。

Docker Machine 的最大优势在于它为多个公共云提供了一致的接口,例如亚马逊网络服务、DigitalOcean、微软 Azure 和谷歌云,以及自托管的虚拟机/云平台,包括 OpenStack 和 VMware vSphere。最后,它还支持以下本地托管的虚拟化平台,如 Oracle VirtualBox 和 VMware Workstation 或 Fusion。

能够使用单个命令以最少的用户交互来针对所有这些技术是一个非常大的时间节省器,如果你需要快速访问亚马逊网络服务的 Docker 主机,然后第二天又需要访问 DigitialOcean,你知道你将获得一致的体验。

由于它是一个命令行工具,因此非常容易向同事传达指令,甚至可以对 Docker 主机的启动和关闭进行脚本化:想象一下,每天早上开始工作时,您的环境都是新建的,然后为了节省成本,每天晚上都会被关闭。

使用 Docker Machine 部署本地 Docker 主机

在我们进入云之前,我们将通过启动 Oracle VirtualBox 来查看 Docker Machine 的基础知识,以提供虚拟机。

VirtualBox 是 Oracle 提供的免费虚拟化产品。它允许您在许多不同的平台和 CPU 类型上安装虚拟机。从www.virtualbox.org/wiki/Downloads/下载并安装 VirtualBox。

要启动虚拟机,您只需要运行以下命令:

$ docker-machine create --driver virtualbox docker-local

这将启动部署过程,期间您将获得 Docker Machine 正在运行的任务列表。对于每个使用 Docker Machine 启动的 Docker 主机,都会经历相同的步骤。

首先,Docker Machine 运行一些基本检查,例如确认 VirtualBox 是否已安装,并创建证书和目录结构,用于存储所有文件和虚拟机:

Creating CA: /Users/russ/.docker/machine/certs/ca.pem
Creating client certificate: /Users/russ/.docker/machine/certs/cert.pem
Running pre-create checks...
(docker-local) Image cache directory does not exist, creating it at /Users/russ/.docker/machine/cache...

然后检查将用于虚拟机的镜像是否存在。如果不存在,将下载该镜像:

(docker-local) No default Boot2Docker ISO found locally, downloading the latest release...
(docker-local) Latest release for github.com/boot2docker/boot2docker is v18.06.1-ce
(docker-local) Downloading /Users/russ/.docker/machine/cache/boot2docker.iso from https://github.com/boot2docker/boot2docker/releases/download/v18.06.1-ce/boot2docker.iso...
(docker-local) 0%....10%....20%....30%....40%....50%....60%....70%....80%....90%....100%

一旦检查通过,它将使用所选的驱动程序创建虚拟机:

Creating machine...
(docker-local) Copying /Users/russ/.docker/machine/cache/boot2docker.iso to /Users/russ/.docker/machine/machines/docker-local/boot2docker.iso...
(docker-local) Creating VirtualBox VM...
(docker-local) Creating SSH key...
(docker-local) Starting the VM...
(docker-local) Check network to re-create if needed...
(docker-local) Found a new host-only adapter: "vboxnet0"
(docker-local) Waiting for an IP...
Waiting for machine to be running, this may take a few minutes...

正如您所看到的,Docker Machine 为虚拟机创建了一个唯一的 SSH 密钥。这意味着您将能够通过 SSH 访问虚拟机,但稍后会详细介绍。虚拟机启动后,Docker Machine 会连接到虚拟机:

Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with boot2docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...

正如您所看到的,Docker Machine 会检测正在使用的操作系统,并选择适当的引导脚本来部署 Docker。一旦安装了 Docker,Docker Machine 会在本地主机和 Docker 主机之间生成和共享证书。然后,它会为证书认证配置远程 Docker 安装,这意味着您的本地客户端可以连接并与远程 Docker 服务器进行交互:

一旦安装了 Docker,Docker Machine 会在本地主机和 Docker 主机之间生成和共享证书。然后,它会为证书认证配置远程 Docker 安装,这意味着您的本地客户端可以连接并与远程 Docker 服务器进行交互:

Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env docker-local

最后,它检查您的本地 Docker 客户端是否可以进行远程连接,并通过提供有关如何配置本地客户端以连接新启动的 Docker 主机的说明来完成任务。

如果您打开 VirtualBox,您应该能够看到您的新虚拟机:

接下来,我们需要配置本地 Docker 客户端以连接到新启动的 Docker 主机;如在启动主机的输出中已经提到的,运行以下命令将向您显示如何进行连接:

$ docker-machine env docker-local

该命令返回以下内容:

export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/Users/russ/.docker/machine/machines/docker-local"
export DOCKER_MACHINE_NAME="docker-local"
# Run this command to configure your shell:
# eval $(docker-machine env docker-local)

这将通过提供新启动的 Docker 主机的 IP 地址和端口号以及用于身份验证的证书路径来覆盖本地 Docker 安装。在输出的末尾,它会给出一个命令来运行并配置您的终端会话,以便进行连接。

在运行该命令之前,让我们运行docker version以获取有关当前设置的信息:

这基本上是我正在运行的 Docker for Mac 安装。运行以下命令,然后再次运行docker version应该会显示服务器的一些更改:

$ eval $(docker-machine env docker-local)

该命令的输出如下:

正如您所看到的,Docker Machine 启动的服务器基本上与我们在本地安装的内容一致;实际上,唯一的区别是构建时间。如您所见,我在 Docker for Mac 安装中的 Docker Engine 二进制文件是在 Docker Machine 版本之后一分钟构建的。

从这里,我们可以以与本地 Docker 安装相同的方式与 Docker 主机进行交互。在继续在云中启动 Docker 主机之前,还有一些其他基本的 Docker Machine 命令需要介绍。

首先列出当前配置的 Docker 主机:

$ docker-machine ls

该命令的输出如下:

正如您所看到的,它列出了机器名称、使用的驱动程序和 Docker 端点 URL 的详细信息,以及主机正在运行的 Docker 版本。

您还会注意到ACTIVE列中有一个*;这表示您的本地客户端当前配置为与之交互的 Docker 主机。您还可以通过运行docker-machine active来找出活动的机器。

接下来的命令使用 SSH 连接到 Docker 主机:

$ docker-machine ssh docker-local

该命令的输出如下:

如果您需要在 Docker Machine 之外安装其他软件或配置,则这很有用。如果您需要查看日志等,也很有用,因为您可以通过运行exit退出远程 shell。一旦回到本地机器上,您可以通过运行以下命令找到 Docker 主机的 IP 地址:

$ docker-machine ip docker-local

我们将在本章后面经常使用这个。还有一些命令可以获取有关 Docker 主机的更多详细信息:

$ docker-machine inspect docker-local
$ docker-machine config docker-local
$ docker-machine status docker-local
$ docker-machine url docker-local

最后,还有一些命令可以stopstartrestart和删除您的 Docker 主机。使用最后一个命令来删除您本地启动的主机:

$ docker-machine stop docker-local
$ docker-machine start docker-local
$ docker-machine restart docker-local
$ docker-machine rm docker-local

运行docker-machine rm命令将提示您确定是否真的要删除实例:

About to remove docker-local
WARNING: This action will delete both local reference and remote instance.
Are you sure? (y/n): y
Successfully removed docker-local

现在我们已经快速了解了基础知识,让我们尝试一些更有冒险精神的东西。

在云中启动 Docker 主机

在本节中,我们将只看一下 Docker Machine 支持的公共云驱动程序之一。如前所述,有很多可用的驱动程序,但 Docker Machine 的吸引力之一是它提供一致的体验,因此驱动程序之间的差异不会太大。

我们将使用 Docker Machine 在 DigitalOcean 中启动一个 Docker 主机。我们唯一需要的是一个 API 访问令牌。而不是在这里解释如何生成一个,您可以按照www.digitalocean.com/help/api/上的说明进行操作。

使用 API 令牌启动 Docker 主机将产生费用;确保您跟踪您启动的 Docker 主机。有关 DigitalOcean 的定价详情,请访问www.digitalocean.com/pricing/。此外,保持您的 API 令牌秘密,因为它可能被用来未经授权地访问您的帐户。本章中使用的所有令牌都已被撤销。

首先,我们要做的是将我们的令牌设置为环境变量,这样我们就不必一直使用它。要做到这一点,请运行以下命令,确保您用自己的 API 令牌替换 API 令牌:

$ DOTOKEN=0cb54091fecfe743920d0e6d28a29fe325b9fc3f2f6fccba80ef4b26d41c7224

由于我们需要传递给 Docker Machine 命令的额外标志,我将使用\来将命令分割成多行,以使其更易读。

要启动名为docker-digtialocean的 Docker 主机,我们需要运行以下命令:

$ docker-machine create \
 --driver digitalocean \ --digitalocean-access-token $DOTOKEN \ docker-digitalocean

由于 Docker 主机是远程机器,它将需要一些时间来启动、配置和访问。如您从以下输出中所见,Docker Machine 启动 Docker 主机的方式也有一些变化:

Running pre-create checks...
Creating machine...
(docker-digitalocean) Creating SSH key...
(docker-digitalocean) Creating Digital Ocean droplet...
(docker-digitalocean) Waiting for IP address to be assigned to the Droplet...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with ubuntu(systemd)...
Installing Docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env docker-digitalocean

启动后,您应该能够在 DigitalOcean 控制面板中看到 Docker 主机:

通过运行以下命令重新配置本地客户端以连接到远程主机:

$ eval $(docker-machine env docker-digitalocean)

此外,您可以运行 docker versiondocker-machine inspect docker-digitalocean 来获取有关 Docker 主机的更多信息。

最后,运行 docker-machine ssh docker-digitalocean 将使您通过 SSH 进入主机。如您从以下输出中所见,以及您首次启动 Docker 主机时的输出中,所使用的操作系统有所不同:

您可以通过运行 exit 退出远程 shell。正如您所见,我们不必告诉 Docker Machine 要使用哪种操作系统,Docker 主机的大小,甚至在哪里启动它。这是因为每个驱动程序都有一些相当合理的默认值。将这些默认值添加到我们的命令中,使其看起来像以下内容:

$ docker-machine create \
 --driver digitalocean \
 --digitalocean-access-token $DOTOKEN \
 --digitalocean-image ubuntu-16-04-x64 \
 --digitalocean-region nyc3 \
 --digitalocean-size 512mb \
 --digitalocean-ipv6 false \
 --digitalocean-private-networking false \
 --digitalocean-backups false \
 --digitalocean-ssh-user root \
 --digitalocean-ssh-port 22 \
 docker-digitalocean

如您所见,您可以自定义 Docker 主机的大小、区域和操作系统,甚至是启动 Docker 主机的网络。假设我们想要更改操作系统和 droplet 的大小。在这种情况下,我们可以运行以下命令:

$ docker-machine create \
 --driver digitalocean \
 --digitalocean-access-token $DOTOKEN \
 --digitalocean-image ubuntu-18-04-x64 \
 --digitalocean-size 1gb \
 docker-digitalocean

如您在 DigitalOcean 控制面板中所见,这将启动一个看起来像以下内容的机器:

您可以通过运行以下命令删除 DigitalOcean Docker 主机:

$ docker-machine rm docker-digitalocean

使用其他基本操作系统

您不必使用 Docker Machine 的默认操作系统;它确实提供了其他基本操作系统的配置程序,包括专门用于运行容器的操作系统。在完成本章之前,我们将看一下如何启动其中一个,CoreOS。

我们将要查看的发行版刚好有足够的操作系统来运行内核、网络堆栈和容器,就像 Docker 自己的 MobyOS 一样,它被用作 Docker for Mac 和 Docker for Windows 的基础。

虽然 CoreOS 支持自己的容器运行时,称为 RKT(发音为 Rocket),但它也附带了 Docker。然而,正如我们将看到的,目前与 CoreOS 稳定版本一起提供的 Docker 版本有点过时。

要启动 DigitalOcean 管理的coreos-stable版本,请运行以下命令:

$ docker-machine create \
 --driver digitalocean \
 --digitalocean-access-token $DOTOKEN \
 --digitalocean-image coreos-stable \
 --digitalocean-size 1GB \
 --digitalocean-ssh-user core \
 docker-coreos

与在公共云上启动其他 Docker 主机一样,输出基本相同。您会注意到 Docker Machine 使用 CoreOS 提供程序:

Running pre-create checks...
Creating machine...
(docker-coreos) Creating SSH key...
(docker-coreos) Creating Digital Ocean droplet...
(docker-coreos) Waiting for IP address to be assigned to the Droplet...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with coreOS...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env docker-coreos

一旦启动,您可以运行以下命令:

$ docker-machine ssh docker-coreos cat /etc/*release

这将返回release文件的内容:

DISTRIB_ID="Container Linux by CoreOS"
DISTRIB_RELEASE=1800.7.0
DISTRIB_CODENAME="Rhyolite"
DISTRIB_DESCRIPTION="Container Linux by CoreOS 1800.7.0 (Rhyolite)"
NAME="Container Linux by CoreOS"
ID=coreos
VERSION=1800.7.0
VERSION_ID=1800.7.0
BUILD_ID=2018-08-15-2254
PRETTY_NAME="Container Linux by CoreOS 1800.7.0 (Rhyolite)"
ANSI_COLOR="38;5;75"
HOME_URL="https://coreos.com/"
BUG_REPORT_URL="https://issues.coreos.com"
COREOS_BOARD="amd64-usr"

运行以下命令将显示有关在 CoreOS 主机上运行的 Docker 版本的更多信息:

$ docker $(docker-machine config docker-coreos) version

您可以从以下输出中看到这一点;另外,正如已经提到的,它落后于当前版本:

这意味着本书中使用的并非所有命令都能正常工作。要删除 CoreOS 主机,请运行以下命令:

$ docker-machine rm docker-coreos

摘要

在本章中,我们看了如何使用 Docker Machine 在 VirtualBox 上本地创建 Docker 主机,并回顾了您可以使用的命令来交互和管理由 Docker Machine 启动的 Docker 主机。

然后,我们看了如何使用 Docker Machine 在云环境中部署 Docker 主机,即 DigitalOcean。最后,我们快速看了如何启动不同的容器优化 Linux 操作系统,即 CoreOS。

我相信您会同意,使用 Docker Machine 来运行这些任务,通常具有非常不同的方法,会带来非常一致的体验,并且从长远来看,也将节省大量时间并解释清楚。

在下一章中,我们将不再与单个 Docker 主机进行交互,而是启动和运行 Docker Swarm 集群。

问题

  1. 在运行docker-machine create时,哪个标志可以让您定义 Docker Machine 用于启动 Docker 主机的服务或提供程序?

  2. 真或假:运行docker-machine env my-host将重新配置本地 Docker 客户端以与my-host进行交互?

  3. 解释 Docker Machine 背后的基本原理。

进一步阅读

有关 Docker Machine 支持的各种平台的信息,请参考以下内容:

第八章:Docker Swarm

在本章中,我们将介绍 Docker Swarm。使用 Docker Swarm,您可以创建和管理 Docker 集群。Swarm 可用于在多个主机上分发容器,并且还具有扩展容器的能力。我们将涵盖以下主题:

  • 介绍 Docker Swarm

  • Docker Swarm 集群中的角色

  • 创建和管理 Swarm

  • Docker Swarm 服务和堆栈

  • Docker Swarm 负载均衡和调度

技术要求

与以前的章节一样,我们将继续使用我们的本地 Docker 安装。同样,本章中的截图将来自我首选的操作系统 macOS。

与以前一样,我们将运行的 Docker 命令将适用于我们迄今为止安装了 Docker 的三种操作系统。但是,一些支持命令可能只适用于基于 macOS 和 Linux 的操作系统。

观看以下视频以查看代码的实际操作:

bit.ly/2yWA4gl

介绍 Docker Swarm

在我们继续之前,我应该提到 Docker Swarm 有两个非常不同的版本。有一个独立的 Docker Swarm 版本;这个版本受支持直到 Docker 1.12,并且不再被积极开发;但是,您可能会发现一些旧的文档提到它。不建议安装独立的 Docker Swarm,因为 Docker 在 2017 年第一季度结束了对 1.11.x 版本的支持。

Docker 1.12 版本引入了 Docker Swarm 模式。这将所有独立的 Docker Swarm 中可用的功能引入了核心 Docker 引擎,还增加了大量的功能。由于本书涵盖的是 Docker 18.06 及更高版本,我们将使用 Docker Swarm 模式,本章剩余部分将称之为 Docker Swarm。

由于您已经运行了内置 Docker Swarm 支持的 Docker 版本,因此您无需安装 Docker Swarm;您可以通过运行以下命令验证 Docker Swarm 是否可用于您的安装:

$ docker swarm --help

当运行以下命令时,您应该会看到类似以下终端输出:

如果出现错误,请确保您正在运行 Docker 18.06 或更高版本,我们在第一章,Docker 概述中涵盖了其安装。现在我们知道我们的 Docker 客户端支持 Docker Swarm,那么 Swarm 是什么意思呢?

Swarm是一组主机,都在运行 Docker,并已设置为在集群配置中相互交互。一旦配置完成,您将能够使用我们迄今为止一直在针对单个主机运行的所有命令,并让 Docker Swarm 通过使用部署策略来决定启动容器的最合适的主机来决定容器的放置位置。

Docker Swarm 由两种类型的主机组成。现在让我们来看看这些。

Docker Swarm 集群中的角色

Docker Swarm 涉及哪些角色?让我们来看看在 Docker Swarm 集群中运行时主机可以承担的两种角色。

Swarm 管理器

Swarm 管理器是一个主机,是所有 Swarm 主机的中央管理点。Swarm 管理器是您发出所有命令来控制这些节点的地方。您可以在节点之间切换,加入节点,移除节点,并操纵这些主机。

每个集群可以运行多个 Swarm 管理器。对于生产环境,建议至少运行五个 Swarm 管理器:这意味着在开始遇到任何错误之前,我们的集群可以容忍最多两个 Swarm 管理器节点故障。Swarm 管理器使用 Raft 一致性算法(有关更多详细信息,请参阅进一步阅读部分)来在所有管理节点上维护一致的状态。

Swarm 工作者

Swarm 工作者,我们之前称之为 Docker 主机,是运行 Docker 容器的主机。Swarm 工作者是从 Swarm 管理器管理的:

这是所有 Docker Swarm 组件的示意图。我们看到 Docker Swarm 管理器与具有 Docker Swarm 工作者角色的每个 Swarm 主机进行通信。工作者确实具有一定程度的连接性,我们将很快看到。

创建和管理 Swarm

现在让我们来看看如何使用 Swarm 以及我们如何执行以下任务:

  • 创建集群

  • 加入工作者

  • 列出节点

  • 管理集群

创建集群

让我们从创建一个以 Swarm 管理器为起点的集群开始。由于我们将在本地机器上创建一个多节点集群,我们应该使用 Docker Machine 通过运行以下命令来启动一个主机:

$ docker-machine create \
 -d virtualbox \
 swarm-manager 

这里显示了您获得的输出的缩略版本:

(swarm-manager) Creating VirtualBox VM...
(swarm-manager) Starting the VM...
(swarm-manager) Check network to re-create if needed...
(swarm-manager) Waiting for an IP...
Waiting for machine to be running, this may take a few minutes...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: docker-machine env swarm-manager

Swarm 管理节点现在正在使用 VirtualBox 启动和运行。我们可以通过运行以下命令来确认:

$ docker-machine ls

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

现在,让我们将 Docker Machine 指向新的 Swarm 管理器。从我们创建 Swarm 管理器时的先前输出中,我们可以看到它告诉我们如何指向该节点:

$ docker-machine env swarm-manager

这将向您显示配置本地 Docker 客户端与我们新启动的 Docker 主机通信所需的命令。当我运行该命令时,以下代码块显示了返回的配置:

export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://192.168.99.100:2376"
export DOCKER_CERT_PATH="/Users/russ/.docker/machine/machines/swarm-manager"
export DOCKER_MACHINE_NAME="swarm-manager"
# Run this command to configure your shell:
# eval $(docker-machine env swarm-manager)

在运行上一个命令后,我们被告知运行以下命令指向 Swarm 管理器:

$ eval $(docker-machine env swarm-manager)

现在,如果我们查看我们主机上有哪些机器,我们可以看到我们有 Swarm 主节点,以及它现在被设置为ACTIVE,这意味着我们现在可以在其上运行命令:

$ docker-machine ls

它应该向您显示类似以下内容:

现在我们已经启动并运行了第一个主机,我们应该添加另外两个工作节点。要做到这一点,只需运行以下命令来启动另外两个 Docker 主机:

$ docker-machine create \
 -d virtualbox \
 swarm-worker01
$ docker-machine create \
 -d virtualbox \
 swarm-worker02

一旦您启动了另外两个主机,您可以使用以下命令获取主机列表:

$ docker-machine ls

它应该向您显示类似以下内容:

值得指出的是,到目前为止,我们还没有做任何事情来创建我们的 Swarm 集群;我们只是启动了它将要运行的主机。

您可能已经注意到在运行docker-machine ls命令时的一列是SWARM。只有在使用独立的 Docker Swarm 命令(内置于 Docker Machine 中)启动 Docker 主机时,此列才包含信息。

向集群添加 Swarm 管理器

让我们引导我们的 Swarm 管理器。为此,我们将传递一些 Docker Machine 命令的结果给我们的主机。要创建我们的管理器的命令如下:

$ docker $(docker-machine config swarm-manager) swarm init \
 --advertise-addr $(docker-machine ip swarm-manager):2377 \
 --listen-addr $(docker-machine ip swarm-manager):2377

您应该收到类似于这样的消息:

Swarm initialized: current node (uxgvqhw6npr9glhp0zpabn4ha) is now a manager.

To add a worker to this swarm, run the following command:

 docker swarm join --token SWMTKN-1-1uulmpx4j4hub2qmd8q2ozxmonzcehxcomt7cw92xarg3yrkx2-dfiqnfisl75bwwh8yk9pv3msh 192.168.99.100:2377

To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.

从输出中可以看出,一旦初始化了您的管理器,您将获得一个唯一的令牌。在上面的示例中,完整的令牌是SWMTKN-1-1uulmpx4j4hub2qmd8q2ozxmonzcehxcomt7cw92xarg3yrkx2-dfiqnfisl75bwwh8yk9pv3msh。这个令牌将被工作节点用于验证自己并加入我们的集群。

加入 Swarm 工作节点到集群

要将我们的两个工作节点添加到集群中,请运行以下命令。首先,让我们设置一个环境变量来保存我们的令牌,确保您用初始化自己管理器时收到的令牌替换它:

$ SWARM_TOKEN=SWMTKN-1-1uulmpx4j4hub2qmd8q2ozxmonzcehxcomt7cw92xarg3yrkx2-dfiqnfisl75bwwh8yk9pv3msh

现在我们可以运行以下命令将swarm-worker01添加到集群中:

$ docker $(docker-machine config swarm-worker01) swarm join \
 --token $SWARM_TOKEN \
 $(docker-machine ip swarm-manager):2377

对于swarm-worker02,您需要运行以下命令:

$ docker $(docker-machine config swarm-worker02) swarm join \
 --token $SWARM_TOKEN \
 $(docker-machine ip swarm-manager):2377

两次,您都应该得到确认,您的节点已加入集群:

This node joined a swarm as a worker.

列出节点

您可以通过运行以下命令来检查 Swarm:

$ docker-machine ls

检查您的本地 Docker 客户端是否仍然配置为连接到 Swarm 管理节点,如果没有,请重新运行以下命令:

$ eval $(docker-machine env swarm-manager)

现在我们正在连接到 Swarm 管理节点,您可以运行以下命令:

$ docker node ls

这将连接到 Swarm 主节点并查询组成我们集群的所有节点。您应该看到我们的三个节点都被列出:

管理集群

让我们看看如何对我们创建的所有这些集群节点进行一些管理。

有两种方式可以管理这些 Swarm 主机和您正在创建的每个主机上的容器,但首先,您需要了解一些关于它们的信息。

查找集群信息

正如我们已经看到的,我们可以使用我们的本地 Docker 客户端列出集群中的节点,因为它已经配置为连接到 Swarm 管理主机。我们只需输入:

$ docker info

这将为我们提供有关主机的大量信息,如您从下面的输出中所见,我已经截断了:

Containers: 0
 Running: 0
 Paused: 0
 Stopped: 0
Images: 0
Plugins:
 Volume: local
 Network: bridge host macvlan null overlay
 Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog
Swarm: active
 NodeID: uxgvqhw6npr9glhp0zpabn4ha
 Is Manager: true
 ClusterID: pavj3f2ym8u1u1ul5epr3c73f
 Managers: 1
 Nodes: 3
 Orchestration:
 Task History Retention Limit: 5
 Raft:
 Snapshot Interval: 10000
 Number of Old Snapshots to Retain: 0
 Heartbeat Tick: 1
 Election Tick: 10
 Dispatcher:
 Heartbeat Period: 5 seconds
 CA Configuration:
 Expiry Duration: 3 months
 Force Rotate: 0
 Autolock Managers: false
 Root Rotation In Progress: false
 Node Address: 192.168.99.100
 Manager Addresses:
 192.168.99.100:2377
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 468a545b9edcd5932818eb9de8e72413e616e86e
runc version: 69663f0bd4b60df09991c08812a60108003fa340
init version: fec3683
Kernel Version: 4.9.93-boot2docker
Operating System: Boot2Docker 18.06.1-ce (TCL 8.2.1); HEAD : c7e5c3e - Wed Aug 22 16:27:42 UTC 2018
OSType: linux
Architecture: x86_64
CPUs: 1
Total Memory: 995.6MiB
Name: swarm-manager
ID: NRV7:WAFE:FWDS:63PT:UMZY:G3KU:OU2A:RWRN:RC7D:5ESI:NWRN:NZRU

如您所见,在 Swarm 部分有关集群的信息;但是,我们只能针对当前客户端配置为通信的主机运行docker info命令。幸运的是,docker node命令是集群感知的,因此我们可以使用它来获取有关我们集群中每个节点的信息,例如以下内容:

$ docker node inspect swarm-manager --pretty

使用docker node inspect命令的--pretty标志来评估输出,将以易于阅读的格式呈现。如果省略--pretty,Docker 将返回包含inspect命令针对集群运行的查询结果的原始JSON对象。

这应该提供了关于我们 Swarm 管理节点的以下信息:

ID: uxgvqhw6npr9glhp0zpabn4ha
Hostname: swarm-manager
Joined at: 2018-09-15 12:14:59.663920111 +0000 utc
Status:
 State: Ready
 Availability: Active
 Address: 192.168.99.100
Manager Status:
 Address: 192.168.99.100:2377
 Raft Status: Reachable
 Leader: Yes
Platform:
 Operating System: linux
 Architecture: x86_64
Resources:
 CPUs: 1
 Memory: 995.6MiB
Plugins:
 Log: awslogs, fluentd, gcplogs, gelf, journald, json-file, logentries, splunk, syslog
 Network: bridge, host, macvlan, null, overlay
 Volume: local
Engine Version: 18.06.1-ce
Engine Labels:
 - provider=virtualbox

运行相同的命令,但这次是针对其中一个工作节点:

$ docker node inspect swarm-worker01 --pretty

这给我们提供了类似的信息:

ID: yhqj03rkfzurb4aqzk7duidf4
Hostname: swarm-worker01
Joined at: 2018-09-15 12:24:09.02346782 +0000 utc
Status:
 State: Ready
 Availability: Active
 Address: 192.168.99.101
Platform:
 Operating System: linux
 Architecture: x86_64
Resources:
 CPUs: 1
 Memory: 995.6MiB
Plugins:
 Log: awslogs, fluentd, gcplogs, gelf, journald, json-file, logentries, splunk, syslog
 Network: bridge, host, macvlan, null, overlay
 Volume: local
Engine Version: 18.06.1-ce
Engine Labels:
 - provider=virtualbox

但是你会发现,它缺少了关于管理功能状态的信息。这是因为工作节点不需要知道管理节点的状态,它们只需要知道它们可以接收来自管理节点的指令。

通过这种方式,我们可以看到关于这个主机的信息,比如容器的数量,主机上的镜像数量,以及关于 CPU 和内存的信息,还有其他有趣的信息。

提升工作节点

假设你想对单个管理节点进行一些维护,但又想保持集群的可用性。没问题,你可以将工作节点提升为管理节点。

我们的本地三节点集群已经运行起来了,现在让我们把swarm-worker01提升为新的管理节点。要做到这一点,运行以下命令:

$ docker node promote swarm-worker01

执行命令后,你应该会收到一个确认你的节点已经被提升的消息:

Node swarm-worker01 promoted to a manager in the swarm.

通过运行这个命令来列出节点:

$ docker node ls

这应该显示你现在有两个节点在MANAGER STATUS列中显示了一些内容:

我们的swarm-manager节点仍然是主要的管理节点。让我们来处理一下这个问题。

降级管理节点

你可能已经联想到了,要将管理节点降级为工作节点,你只需要运行这个命令:

$ docker node demote swarm-manager

同样,你将立即收到以下反馈:

Manager swarm-manager demoted in the swarm.

现在我们已经降级了我们的节点,你可以通过运行这个命令来检查集群中节点的状态:

$ docker node ls

由于你的本地 Docker 客户端仍然指向新降级的节点,你将收到以下消息:

Error response from daemon: This node is not a swarm manager. Worker nodes can't be used to view or modify cluster state. Please run this command on a manager node or promote the current node to a manager.

正如我们已经学到的,使用 Docker Machine 很容易更新我们本地客户端配置以与其他节点通信。要将本地客户端指向新的管理节点,运行以下命令:

$ eval $(docker-machine env swarm-worker01)

现在我们的客户端又在与一个管理节点通信了,重新运行这个命令:

$ docker node ls

它应该列出节点,正如预期的那样:

排水节点

为了暂时从集群中移除一个节点,以便我们可以进行维护,我们需要将节点的状态设置为 Drain。让我们看看如何排水我们以前的管理节点。要做到这一点,我们需要运行以下命令:

$ docker node update --availability drain swarm-manager

这将停止任何新任务,比如新容器的启动或在我们排水的节点上执行。一旦新任务被阻止,所有正在运行的任务将从我们排水的节点迁移到具有ACTIVE状态的节点。

如您从以下终端输出中所见,现在列出节点显示swarm-manager节点在AVAILABILITY列中被列为Drain

现在我们的节点不再接受新任务,所有正在运行的任务都已迁移到我们剩下的两个节点,我们可以安全地进行维护,比如重新启动主机。要重新启动 Swarm 管理器,请运行以下两个命令,确保您连接到 Docker 主机(您应该看到boot2docker横幅,就像在命令后面的截图中一样):

$ docker-machine ssh swarm-manager
$ sudo reboot

主机重新启动后,运行此命令:

$ docker node ls

它应该显示节点的AVAILABILITYDrain。要将节点重新添加到集群中,只需通过运行以下命令将AVAILABILITY更改为 active:

$ docker node update --availability active swarm-manager

如您从以下终端输出中所见,我们的节点现在处于活动状态,这意味着可以对其执行新任务:

现在我们已经看过如何创建和管理 Docker Swarm 集群,我们应该看看如何运行诸如创建和扩展服务之类的任务。

Docker Swarm 服务和堆栈

到目前为止,我们已经看过以下命令:

$ docker swarm <command>
$ docker node <command>

这两个命令允许我们从一组现有的 Docker 主机引导和管理我们的 Docker Swarm 集群。我们接下来要看的两个命令如下:

$ docker service <command>
$ docker stack <command>

servicestack命令允许我们执行任务,进而在我们的 Swarm 集群中启动、扩展和管理容器。

服务

service命令是启动利用 Swarm 集群的容器的一种方式。让我们来看看在我们的 Swarm 集群上启动一个非常基本的单容器服务。要做到这一点,运行以下命令:

$ docker service create \
 --name cluster \
 --constraint "node.role == worker" \
 -p:80:80/tcp \
 russmckendrick/cluster

这将创建一个名为 cluster 的服务,该服务由一个单个容器组成,端口80从容器映射到主机,它只会在具有工作节点角色的节点上运行。

在我们查看如何处理服务之前,我们可以检查它是否在我们的浏览器上运行。为此,我们需要两个工作节点的 IP 地址。首先,我们需要通过运行此命令再次确认哪些是工作节点:

$ docker node ls

一旦我们知道哪个节点具有哪个角色,您可以通过运行此命令找到您节点的 IP 地址:

$ docker-machine ls

查看以下终端输出:

我的工作节点是swarm-managerswarm-worker02,它们的 IP 地址分别是192.168.99.100192.168.99.102

在浏览器中输入工作节点的任一 IP 地址,例如192.168.99.100/192.168.99.102/,将显示russmckendrick/cluster应用程序的输出,这是 Docker Swarm 图形和页面提供服务的容器的主机名:

现在我们的服务在集群上运行,我们可以开始了解更多关于它的信息。首先,我们可以通过运行以下命令再次列出服务:

$ docker service ls

在我们的情况下,这应该返回我们启动的单个名为 cluster 的服务:

如您所见,这是一个replicated服务,有1/1个容器处于活动状态。接下来,您可以通过运行inspect命令深入了解有关服务的更多信息:

$ docker service inspect cluster --pretty

这将返回有关服务的详细信息:

到目前为止,您可能已经注意到,我们无需关心我们的两个工作节点中的服务当前正在哪个节点上运行。这是 Docker Swarm 的一个非常重要的特性,因为它完全消除了您担心单个容器放置的需要。

在我们查看如何扩展我们的服务之前,我们可以通过运行以下命令快速查看我们的单个容器正在哪个主机上运行:

$ docker node ps
$ docker node ps swarm-manager
$ docker node ps swarm-worker02

这将列出在每个主机上运行的容器。默认情况下,它将列出命令所针对的主机,我这里是swarm-worker01

让我们来看看将我们的服务扩展到六个应用程序容器实例。运行以下命令来扩展和检查我们的服务:

$ docker service scale cluster=6
$ docker service ls
$ docker node ps swarm-manager
$ docker node ps swarm-worker02

我们只检查两个节点,因为我们最初告诉我们的服务在工作节点上启动。从以下终端输出中可以看出,我们现在在每个工作节点上运行了三个容器:

在继续查看 stack 之前,让我们删除我们的服务。要做到这一点,请运行以下命令:

$ docker service rm cluster

这将删除所有容器,同时保留主机上下载的镜像。

Stacks

使用 Swarm 和服务可以创建相当复杂、高可用的多容器应用程序是完全可能的。在非 Swarm 集群中,手动为应用程序的一部分启动每组容器开始变得有点费力,也很难共享。为此,Docker 创建了功能,允许您在 Docker Compose 文件中定义您的服务。

以下 Docker Compose 文件,应命名为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

正如您所看到的,stack 可以由多个服务组成,每个服务在 Docker Compose 文件的services部分下定义。

除了常规的 Docker Compose 命令外,您可以添加一个deploy部分;这是您定义与 stack 的 Swarm 元素相关的所有内容的地方。

在前面的示例中,我们说我们想要六个副本,应该分布在我们的两个工作节点上。此外,我们更新了默认的重启策略,您在上一节中检查服务时看到的,它显示为暂停,因此,如果容器变得无响应,它将始终重新启动。

要启动我们的 stack,请将先前的内容复制到名为docker-compose.yml的文件中,然后运行以下命令:

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

与使用 Docker Compose 启动容器时一样,Docker 将创建一个新网络,然后在其上启动您的服务。

您可以通过运行此命令来检查您的stack的状态:

$ docker stack ls

这将显示已创建一个单一服务。您可以通过运行以下命令来获取由stack创建的服务的详细信息:

$ docker stack services cluster

最后,运行以下命令将显示stack中容器的运行位置:

$ docker stack ps cluster

查看终端输出:

同样,您将能够使用节点的 IP 地址访问堆栈,并且将被路由到其中一个正在运行的容器。要删除一个堆栈,只需运行此命令:

$ docker stack rm cluster

这将在启动时删除堆栈创建的所有服务和网络。

删除 Swarm 集群

在继续之前,因为我们不再需要它用于下一节,您可以通过运行以下命令删除您的 Swarm 集群:

$ docker-machine rm swarm-manager swarm-worker01 swarm-worker02

如果出于任何原因需要重新启动 Swarm 集群,只需按照本章开头的说明重新创建集群。

负载平衡、覆盖和调度

在最后几节中,我们看了如何启动服务和堆栈。要访问我们启动的应用程序,我们可以使用集群中任何主机的 IP 地址;这是如何可能的?

Ingress 负载平衡

Docker Swarm 内置了一个入口负载均衡器,可以轻松地将流量分发到我们面向公众的容器。

这意味着您可以将 Swarm 集群中的应用程序暴露给服务,例如,像 Amazon Elastic Load Balancer 这样的外部负载均衡器,知道您的请求将被路由到正确的容器,无论当前托管它的主机是哪个,如下图所示:

这意味着我们的应用程序可以进行扩展或缩减、失败或更新,而无需重新配置外部负载均衡器。

网络覆盖

在我们的示例中,我们启动了一个运行单个应用程序的简单服务。假设我们想在我们的应用程序中添加一个数据库层,这通常是网络中的一个固定点;我们该如何做呢?

Docker Swarm 的网络覆盖层将您启动容器的网络扩展到多个主机,这意味着每个服务或堆栈可以在其自己的隔离网络中启动。这意味着我们的运行 MongoDB 的数据库容器将在相同的覆盖网络上的所有其他容器上的端口27017可访问,无论这些容器运行在哪个主机上。

您可能会想等一下。这是否意味着我必须将 IP 地址硬编码到我的应用程序配置中?嗯,这与 Docker Swarm 试图解决的问题不太匹配,所以不,您不必这样做。

每个覆盖网络都有自己内置的 DNS 服务,这意味着在网络中启动的每个容器都能解析同一网络中另一个容器的主机名到其当前分配的 IP 地址。这意味着当我们配置我们的应用程序连接到我们的数据库实例时,我们只需要告诉它连接到,比如,mongodb:27017,它就会连接到我们的 MongoDB 容器。

这将使我们的图表如下所示:

在采用这种模式时,还有一些其他考虑因素需要考虑,但我们将在第十四章《Docker 工作流程》中进行讨论。

调度

在撰写本文时,Docker Swarm 中只有一种调度策略,称为 Spread。这种策略的作用是将任务安排在满足你在启动服务或堆栈时定义的任何约束的最轻载节点上运行。在大多数情况下,你不应该对你的服务添加太多约束。

Docker Swarm 目前不支持的一个特性是亲和性和反亲和性规则。虽然可以通过使用约束来解决这个问题,但我建议您不要过于复杂化,因为如果在定义服务时设置了太多约束,很容易导致主机过载或创建单点故障。

摘要

在本章中,我们探讨了 Docker Swarm。我们看了如何安装 Docker Swarm 以及组成 Docker Swarm 的 Docker Swarm 组件。我们看了如何使用 Docker Swarm:加入、列出和管理 Swarm 管理器和工作节点。我们回顾了服务和堆栈命令以及如何使用它们,并谈到了 Swarm 内置的入口负载均衡器、覆盖网络和调度器。

在下一章中,我们将介绍一个名为 Kubernetes 的 Docker Swarm 替代方案。这也得到了 Docker 以及其他提供商的支持。

问题

  1. 真或假:你应该使用独立的 Docker Swarm 而不是内置的 Docker Swarm 模式来运行你的 Docker Swarm?

  2. 在启动 Docker Swarm 管理器后,你需要什么来将你的工作节点添加到 Docker Swarm 集群中?

  3. 你会使用哪个命令来查找 Docker Swarm 集群中每个节点的状态?

  4. 你会添加哪个标志到 docker node inspect Swarm manager 来使其更易读?

  5. 如何将节点提升为管理节点?

  6. 您可以使用什么命令来扩展您的服务?

进一步阅读

关于 Raft 共识算法的详细解释,我推荐阅读名为数据的秘密生活的优秀演示,可以在thesecretlivesofdata.com/raft找到。它通过易于理解的动画解释了后台管理节点上发生的所有过程。

第九章:Docker 和 Kubernetes

在本章中,我们将看一下 Kubernetes。与 Docker Swarm 一样,您可以使用 Kubernetes 来创建和管理运行基于容器的应用程序的集群。

本章将涵盖以下主题:

  • Kubernetes 简介

  • 启用 Kubernetes

  • 使用 Kubernetes

  • Kubernetes 和其他 Docker 工具

技术要求

Docker 中的 Kubernetes 仅受 Docker for Mac 和 Docker for Windows 桌面客户端支持。与之前的章节一样,我将使用我偏好的操作系统,即 macOS。与之前一样,一些支持命令可能只适用于 macOS。

查看以下视频以查看代码的运行情况:

bit.ly/2q6xpwl

Kubernetes 简介

如果您一直在考虑查看容器,那么您在旅行中某个时候一定会遇到 Kubernetes,因此在我们在 Docker 桌面安装中启用它之前,让我们花点时间看看 Kubernetes 的来源。

Kubernetes(发音为koo-ber-net-eez)源自希腊语,意为船长或船长。Kubernetes(也被称为K8s)是一个源自谷歌的开源项目,允许您自动化部署、管理和扩展容器化的应用程序。

谷歌容器的简要历史

谷歌已经在基于 Linux 容器的解决方案上工作了很长时间。它在 2006 年首次采取了行动,通过开发名为控制组cgroups)的 Linux 内核功能。这个功能在 2008 年被合并到了 Linux 内核的 2.6.24 版本中。该功能允许您隔离资源,如 CPU、RAM、网络和磁盘 I/O,或一个或多个进程。控制组仍然是 Linux 容器的核心要求,不仅被 Docker 使用,还被其他容器工具使用。

谷歌接下来尝试了一个名为lmctfy的容器堆栈,代表Let Me Contain That For You。这是LXC工具和库的替代品。这是他们自己内部工具的开源版本,用于管理他们自己应用程序中的容器。

谷歌下一次因其容器使用而成为新闻焦点是在 2014 年 5 月的 Gluecon 大会上 Joe Beda 发表讲话之后。在讲话中,Beda 透露谷歌几乎所有的东西都是基于容器的,并且他们每周要启动大约 20 亿个容器。据说这个数字不包括任何长期运行的容器,这意味着这些容器只是短暂活跃。然而,经过一些快速计算,这意味着谷歌平均每秒启动大约 3000 个容器!

在讲话的后来,Beda 提到谷歌使用调度程序,这样他们就不必手动管理每周 20 亿个容器,甚至不必担心它们被启动的位置,以及在较小程度上,每个容器的可用性。

谷歌还发表了一篇名为《谷歌的大规模集群管理与博格》的论文。这篇论文不仅让谷歌以外的人知道他们正在使用的调度程序博格的名称,还详细介绍了他们在设计调度程序时所做的设计决策。

论文提到,除了他们的内部工具,谷歌还在运行其面向客户的应用程序,如 Google 文档、Gmail 和 Google 搜索,这些应用程序在由博格管理的容器运行的集群中。

博格是以《星际迷航:下一代》电视剧中的外星种族博格而命名的。在电视剧中,博格是一种基于集体意识的网络的赛博人类,使他们不仅能够共享相同的思想,还能通过次空间网络确保集体意识对每个成员进行指导和监督。我相信你会同意,博格种族的特征与你希望你的容器集群运行的方式非常相似。

博格在谷歌内部运行了数年,最终被一种更现代的调度程序Omega所取代。大约在这个时候,谷歌宣布他们将采取博格的一些核心功能,并将其复制为一个新的开源项目。这个项目在内部被称为Seven,由博格的几位核心贡献者共同开发。它的目标是创建一个更友好的博格版本,不再紧密地与谷歌自己的内部程序和工作方式联系在一起。

Seven,以星际迷航:航海家号中的角色 Seven of Nine 命名,她是一个从集体中脱离出来的博格,最终在首次公开提交时被命名为Kubernetes

Kubernetes 概述

现在我们知道了 Kubernetes 的由来,我们可以深入了解一下 Kubernetes 是什么。项目的大部分,精确地说是 88.5%,是用Go语言编写的,这一点应该不足为奇,因为 Go 是一种在 2011 年开源之前在 Google 内部开发的编程语言。项目文件的其余部分由 Python 和 Shell 辅助脚本以及 HTML 文档组成。

一个典型的 Kubernetes 集群由承担主节点或节点角色的服务器组成。您也可以运行一个承担两种角色的独立安装。

主节点是魔术发生的地方,也是集群的大脑。它负责决定 Pod 的启动位置,并监视集群本身和集群内运行的 Pod 的健康状况。我们在讨论完这两个角色后会讨论 Pod。

通常,部署到被赋予主节点角色的主机上的核心组件有:

  • kube-apiserver:这个组件暴露了主要的 Kubernetes API。它被设计为水平扩展,这意味着您可以不断添加更多的实例来使您的集群高度可用。

  • etcd:这是一个高可用的一致性键值存储。它用于存储集群的状态。

  • kube-scheduler:这个组件负责决定 Pod 的启动位置。

  • kube-controller-manager:这个组件运行控制器。这些控制器在 Kubernetes 中有多个功能,比如监视节点、关注复制、管理端点,以及生成服务账户和令牌。

  • cloud-controller-manager:这个组件负责管理各种控制器,这些控制器与第三方云进行交互,启动和配置支持服务。

现在我们已经涵盖了管理组件,我们需要讨论它们在管理什么。一个节点由以下组件组成:

  • kubelet:这个代理程序在集群中的每个节点上运行,是管理者与节点交互的手段。它还负责管理 Pod。

  • kube-proxy:这个组件管理节点和 pod 的所有请求和流量的路由。

  • 容器运行时:这可以是 Docker RKT 或任何其他符合 OCI 标准的运行时。

到目前为止,您可能已经注意到我并没有提到容器。这是因为 Kubernetes 实际上并不直接与您的容器交互;相反,它与一个 pod 进行通信。将 pod 视为一个完整的应用程序;有点像我们使用 Docker Compose 启动由多个容器组成的应用程序时的情况。

Kubernetes 和 Docker

最初,Kubernetes 被视为 Docker Swarm 的竞争技术,Docker 自己的集群技术。然而,在过去几年中,Kubernetes 已经几乎成为容器编排的事实标准。

所有主要的云提供商都提供 Kubernetes 即服务。我们有以下内容:

  • 谷歌云:谷歌 Kubernetes 引擎(GKE)

  • Microsoft Azure:Azure Kubernetes 服务(AKS)

  • 亚马逊网络服务:亚马逊弹性 Kubernetes 容器服务(EKS)

  • IBM:IBM 云 Kubernetes 服务

  • 甲骨文云:甲骨文 Kubernetes 容器引擎

  • DigitalOcean:DigitalOcean 上的 Kubernetes

从表面上看,所有主要支持 Kubernetes 的参与者可能看起来并不像是一件大事。然而,请考虑我们现在知道了一种在多个平台上部署我们的容器化应用程序的一致方式。传统上,这些平台一直是封闭的花园,并且与它们互动的方式非常不同。

尽管 Docker 在 2017 年 10 月的 DockerCon Europe 上的宣布最初令人惊讶,但一旦尘埃落定,这一宣布就变得非常合理。为开发人员提供一个环境,在这个环境中他们可以在本地使用 Docker for Mac 和 Docker for Windows 工作,然后使用 Docker 企业版来部署和管理他们自己的 Kubernetes 集群,或者甚至使用之前提到的云服务之一,这符合我们在[第一章]中讨论的解决“在我的机器上可以运行”的问题,Docker 概述。

现在让我们看看如何在 Docker 软件中启用支持并开始使用它。

启用 Kubernetes

Docker 已经使安装过程变得非常简单。要启用 Kubernetes 支持,您只需打开首选项,然后点击 Kubernetes 选项卡:

如你所见,有两个主要选项。选中启用 Kubernetes框,然后选择Kubernetes作为默认编排器。暂时不要选中显示系统容器;我们在启用服务后会更详细地看一下这个。点击应用将弹出以下消息:

点击安装按钮将下载所需的容器,以启用 Docker 安装上的 Kubernetes 支持:

如在第一个对话框中提到的,Docker 将需要一段时间来下载、配置和启动集群。完成后,你应该看到Kubernetes 正在运行旁边有一个绿点:

打开终端并运行以下命令:

$ docker container ls -a

这应该显示没有异常运行。运行以下命令:

$ docker image ls

这应该显示一个与 Kubernetes 相关的图像列表:

  • docker/kube-compose-controller

  • docker/kube-compose-api-server

  • k8s.gcr.io/kube-proxy-amd64

  • k8s.gcr.io/kube-scheduler-amd64

  • k8s.gcr.io/kube-apiserver-amd64

  • k8s.gcr.io/kube-controller-manager-amd64

  • k8s.gcr.io/etcd-amd64

  • k8s.gcr.io/k8s-dns-dnsmasq-nanny-amd64

  • k8s.gcr.io/k8s-dns-sidecar-amd64

  • k8s.gcr.io/k8s-dns-kube-dns-amd64

  • k8s.gcr.io/pause-amd64

这些图像来自 Docker 和 Google 容器注册表(k8s.gcr.io)上可用的官方 Kubernetes 图像。

正如你可能已经猜到的,选中显示系统容器(高级)框,然后运行以下命令将显示在本地 Docker 安装上启用 Kubernetes 服务的所有正在运行的容器的列表:

$ docker container ls -a

由于运行上述命令时会产生大量输出,下面的屏幕截图只显示了容器的名称。为了做到这一点,我运行了以下命令:

$ docker container ls --format {{.Names}}

运行该命令给我以下结果:

有 18 个正在运行的容器,这就是为什么你可以选择隐藏它们。正如你所看到的,几乎我们在上一节讨论的所有组件都包括在内,还有一些额外的组件,提供了与 Docker 的集成。我建议取消选中显示系统容器框,因为我们不需要每次查看正在运行的容器时都看到 18 个容器的列表。

此时需要注意的另一件事是,Kubernetes 菜单项现在已经有内容了。这个菜单可以用于在 Kubernetes 集群之间进行切换。由于我们目前只有一个活动的集群,所以只有一个被列出来:

现在我们的本地 Kubernetes 集群已经运行起来了,我们可以开始使用它了。

使用 Kubernetes

现在我们的 Kubernetes 集群已经在我们的 Docker 桌面安装上运行起来了,我们可以开始与之交互了。首先,我们将看一下与 Docker 桌面组件一起安装的命令行kubectl

如前所述,kubectl是与之一起安装的。以下命令将显示有关客户端以及连接到的集群的一些信息:

$ kubectl version

接下来,我们可以运行以下命令来查看kubectl是否能够看到我们的节点:

$ kubectl get nodes

现在我们的客户端正在与我们的节点进行交互,我们可以通过运行以下命令查看 Kubernetes 默认配置的namespaces

$ kubectl get namespaces

然后我们可以使用以下命令查看命名空间内的pods

$ kubectl get --namespace kube-system pods

Kubernetes 中的命名空间是在集群内隔离资源的好方法。从终端输出中可以看到,我们的集群内有四个命名空间。有一个default命名空间,通常是空的。有两个主要 Kubernetes 服务的命名空间:dockerkube-system。这些包含了构成我们集群的 pod,最后一个命名空间kube-public,与默认命名空间一样,是空的。

在启动我们自己的 pod 之前,让我们快速看一下我们如何与正在运行的 pod 进行交互,首先是如何找到有关我们的 pod 的更多信息:

$ kubectl describe --namespace kube-system pods kube-scheduler-docker-for-desktop 

上面的命令将打印出kube-scheduler-docker-for-desktop pod 的详细信息。您可能注意到我们必须使用--namespace标志传递命名空间。如果我们不这样做,那么kubectl将默认到默认命名空间,那里没有名为kube-scheduler-docker-for-desktop的 pod 在运行。

命令的完整输出如下:

Name: kube-scheduler-docker-for-desktop
Namespace: kube-system
Node: docker-for-desktop/192.168.65.3
Start Time: Sat, 22 Sep 2018 14:10:14 +0100
Labels: component=kube-scheduler
 tier=control-plane
Annotations: kubernetes.io/config.hash=6d5c9cb98205e46b85b941c8a44fc236
 kubernetes.io/config.mirror=6d5c9cb98205e46b85b941c8a44fc236
 kubernetes.io/config.seen=2018-09-22T11:07:47.025395325Z
 kubernetes.io/config.source=file
 scheduler.alpha.kubernetes.io/critical-pod=
Status: Running
IP: 192.168.65.3
Containers:
 kube-scheduler:
 Container ID: docker://7616b003b3c94ca6e7fd1bc3ec63f41fcb4b7ce845ef7a1fb8af1a2447e45859
 Image: k8s.gcr.io/kube-scheduler-amd64:v1.10.3
 Image ID: docker-pullable://k8s.gcr.io/kube-scheduler-amd64@sha256:4770e1f1eef2229138e45a2b813c927e971da9c40256a7e2321ccf825af56916
 Port: <none>
 Host Port: <none>
 Command:
 kube-scheduler
 --kubeconfig=/etc/kubernetes/scheduler.conf
 --address=127.0.0.1
 --leader-elect=true
 State: Running
 Started: Sat, 22 Sep 2018 14:10:16 +0100
 Ready: True
 Restart Count: 0
 Requests:
 cpu: 100m
 Liveness: http-get http://127.0.0.1:10251/healthz delay=15s timeout=15s period=10s #success=1 #failure=8
 Environment: <none>
 Mounts:
 /etc/kubernetes/scheduler.conf from kubeconfig (ro)
Conditions:
 Type Status
 Initialized True
 Ready True
 PodScheduled True
Volumes:
 kubeconfig:
 Type: HostPath (bare host directory volume)
 Path: /etc/kubernetes/scheduler.conf
 HostPathType: FileOrCreate
QoS Class: Burstable
Node-Selectors: <none>
Tolerations: :NoExecute
Events: <none>

正如您所见,关于 pod 有很多信息,包括容器列表;我们只有一个叫做kube-scheduler。我们可以看到容器 ID,使用的镜像,容器启动时使用的标志,以及 Kubernetes 调度器用于启动和维护 pod 的数据。

现在我们知道了容器名称,我们可以开始与其交互。例如,运行以下命令将打印我们一个容器的日志:

$ kubectl logs --namespace kube-system kube-scheduler-docker-for-desktop -c kube-scheduler 

运行以下命令将获取 pod 中每个容器的logs

$ kubectl logs --namespace kube-system kube-scheduler-docker-for-desktop

与 Docker 一样,您还可以在您的 pod 和容器上执行命令。例如,以下命令将运行uname -a命令:

请确保在以下两个命令后添加--后面的空格。如果未这样做,将导致错误。

$ kubectl exec --namespace kube-system kube-scheduler-docker-for-desktop -c kube-scheduler -- uname -a
$ kubectl exec --namespace kube-system kube-scheduler-docker-for-desktop -- uname -a

同样,我们可以选择在命名容器上运行命令,或者跨 pod 内的所有容器运行命令:

通过安装并登录到基于 Web 的仪表板,让我们对 Kubernetes 集群有更多了解。虽然这不是 Docker 的默认功能,但使用 Kubernetes 项目提供的定义文件进行安装非常简单。我们只需要运行以下命令:

$ kubectl create -f https://raw.githubusercontent.com/kubernetes/dashboard/master/src/deploy/recommended/kubernetes-dashboard.yaml

一旦服务和部署已经创建,启动需要几分钟。您可以通过运行以下命令来检查状态:

$ kubectl get deployments --namespace kube-system
$ kubectl get services --namespace kube-system

一旦您的输出看起来像以下内容,您的仪表板应该已经安装并准备就绪:

现在我们的仪表板正在运行,我们将找到一种访问它的方法。我们可以使用kubectl中的内置代理服务来实现。只需运行以下命令即可启动:

$ kubectl proxy

这将启动代理,并打开您的浏览器并转到http://127.0.0.1:8001/version/将显示有关您的集群的一些信息:

然而,我们想要看到的是仪表板。可以通过以下网址访问:http://localhost:8001/api/v1/namespaces/kube-system/services/https:kubernetes-dashboard:/proxy/

当您首次在浏览器中打开 URL 时,将会看到登录屏幕。由于我们是通过代理访问仪表板,因此我们只需按下SKIP按钮:

登录后,您将能够看到有关您的集群的大量信息:

既然我们的集群已经启动运行,我们现在可以看一下启动一些示例应用程序。

Kubernetes 和其他 Docker 工具

当我们启用 Kubernetes 时,我们选择了 Kubernetes 作为 Docker 堆栈命令的默认编排器。在上一章中,Docker stack命令将在 Docker Swarm 中启动我们的 Docker Compose 文件。我们使用的 Docker Compose 看起来像下面这样:

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

在 Kubernetes 上启动应用程序之前,我们需要进行一些微调并删除放置,这样我们的文件看起来像下面这样:

version: "3"
services:
 cluster:
 image: russmckendrick/cluster
 ports:
 - "80:80"
 deploy:
 replicas: 6
 restart_policy:
 condition: on-failure

编辑文件后,运行以下命令将启动stack

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

正如您所看到的,Docker 会等到堆栈可用后才将您返回到提示符。我们还可以运行与我们在 Docker Swarm 上启动堆栈时使用的相同命令来查看有关我们的堆栈的一些信息:

$ docker stack ls
$ docker stack services cluster
$ docker stack ps cluster

我们还可以使用kubectl查看详细信息:

$ kubectl get deployments
$ kubectl get services

您可能已经注意到,这一次我们不需要提供命名空间。这是因为我们的堆栈是在默认命名空间中启动的。此外,在列出服务时,为集群堆栈列出了 ClusterIP 和 LoadBalancer。查看 LoadBalancer,您会看到外部 IP 是localhost,端口是80

在我们的浏览器中打开localhost/显示应用程序:

如果您仍然打开着仪表板,您可以探索您的堆栈,甚至打开一个容器的终端:

您可以通过运行以下命令来删除stack

$ docker stack rm cluster

最后一件事 - 您可能会想,太好了,我可以在 Kubernetes 集群的任何地方运行我的 Docker Compose 文件。嗯,这并不完全正确。如前所述,当我们首次启用 Kubernetes 时,会启动一些仅适用于 Docker 的组件。这些组件旨在尽可能紧密地集成 Docker。但是,由于这些组件在非 Docker 管理的集群中不存在,因此您将无法再使用docker stack命令。

尽管如此,还有一个工具叫做Kompose,它是 Kubernetes 项目的一部分,可以接受 Docker Compose 文件并将其即时转换为 Kubernetes 定义文件。

要在 macOS 上安装 Kompose,请运行以下命令:

$ curl -L https://github.com/kubernetes/kompose/releases/download/v1.16.0/kompose-darwin-amd64 -o /usr/local/bin/kompose
$ chmod +x /usr/local/bin/kompose

Windows 10 用户可以使用 Chocolatey 来安装二进制文件:

Chocolatey是一个基于命令行的软件包管理器,可用于在基于 Windows 的机器上安装各种软件包,类似于在 Linux 机器上使用yumapt-get,或在 macOS 上使用brew

$ choco install kubernetes-kompose

最后,Linux 用户可以运行以下命令:

$ curl -L https://github.com/kubernetes/kompose/releases/download/v1.16.0/kompose-linux-amd64 -o /usr/local/bin/kompose
$ chmod +x /usr/local/bin/kompose

安装完成后,您可以通过运行以下命令启动您的 Docker Compose 文件:

$ kompose up

您将得到类似以下输出:

如输出所建议的,运行以下命令将为您提供刚刚启动的服务和 pod 的详细信息:

$ kubectl get deployment,svc,pods,pvc

您可以通过运行以下命令来删除服务和 pod:

$ kompose down

虽然您可以使用kompose upkompose down,但我建议生成 Kubernetes 定义文件并根据需要进行调整。要做到这一点,只需运行以下命令:

$ kompose convert

这将生成 pod 和 service 文件:

您将能够看到 Docker Compose 文件和生成的两个文件之间有很大的区别。cluster-pod.yaml文件如下所示:

apiVersion: v1
kind: Pod
metadata:
 creationTimestamp: null
 labels:
 io.kompose.service: cluster
 name: cluster
spec:
 containers:
 - image: russmckendrick/cluster
 name: cluster
 ports:
 - containerPort: 80
 resources: {}
 restartPolicy: OnFailure
status: {}

cluster-service.yaml文件如下所示:

apiVersion: v1
kind: Service
metadata:
 annotations:
 kompose.cmd: kompose convert
 kompose.version: 1.16.0 (0c01309)
 creationTimestamp: null
 labels:
 io.kompose.service: cluster
 name: cluster
spec:
 ports:
 - name: "80"
 port: 80
 targetPort: 80
 selector:
 io.kompose.service: cluster
status:
 loadBalancer: {}

然后,您可以通过运行以下命令来启动这些文件:

$ kubectl create -f cluster-pod.yaml
$ kubectl create -f cluster-service.yaml
$ kubectl get deployment,svc,pods,pvc

删除集群 pod 和服务,我们只需要运行以下命令:

$ kubectl delete service/cluster pod/cluster

虽然 Kubernetes 将在接下来的章节中出现,您可能希望在 Docker 桌面安装中禁用 Kubernetes 集成,因为它在空闲时会增加一些开销。要做到这一点,只需取消选中启用 Kubernetes。单击应用后,Docker 将停止运行 Kubernetes 所需的所有容器;但它不会删除镜像,因此当您重新启用它时,不会花费太长时间。

摘要

在本章中,我们从 Docker 桌面软件的角度看了 Kubernetes。Kubernetes 比我们在本章中介绍的要复杂得多,所以请不要认为这就是全部。在讨论了 Kubernetes 的起源之后,我们看了如何使用 Docker for Mac 或 Docker for Windows 在本地机器上启用它。

然后我们讨论了一些kubectl的基本用法,然后看了如何使用docker stack命令来启动我们的应用程序,就像我们为 Docker Swarm 做的那样。

在本章末尾,我们讨论了 Kompose,这是 Kubernetes 项目下的一个工具。它可以帮助您将 Docker Compose 文件转换为 Kubernetes 可用,从而让您提前开始将应用程序迁移到纯 Kubernetes。

在下一章中,我们将看看在公共云上使用 Docker,比如亚马逊网络服务,以及简要回顾 Kubernetes。

问题

  • 真或假:当未选中显示系统容器(高级)时,您无法看到用于启动 Kubernetes 的镜像。

  • 四个命名空间中的哪一个托管了用于在 Docker 中运行 Kubernetes 并支持的容器?

  • 您将运行哪个命令来查找运行在 pod 中的容器的详细信息?

  • 您将使用哪个命令来启动 Kubernetes 定义的 YAML 文件?

  • 通常,命令kubectl代理在本地机器上打开哪个端口?

  • Google 容器编排平台的原始名称是什么?

进一步阅读

在本章开头提到的一些 Google 工具、演示文稿和白皮书可以在以下位置找到:

您可以在本章中提到的云服务的详细信息。

您可以在以下找到 Docker 关于 Kubernetes 支持的公告:

最后,Kompose 的主页可以在以下找到:

第十章:在公共云中运行 Docker

到目前为止,我们一直在使用 Digital Ocean 在基于云的基础设施上启动容器。在本章中,我们将研究使用 Docker 提供的工具在 Amazon Web Services 和 Microsoft Azure 中启动 Docker Swarm 集群。然后,我们将研究 Amazon Web Services、Microsoft Azure 和 Google Cloud 提供的容器解决方案。

本章将涵盖以下主题:

  • Docker Cloud

  • Amazon ECS 和 AWS Fargate

  • Microsoft Azure 应用服务

  • Microsoft Azure、Google Cloud 和 Amazon Web Services 中的 Kubernetes

技术要求

在本章中,我们将使用各种云提供商,因此如果您在跟进,您将需要在每个提供商上拥有活跃的账户。同样,本章中的截图将来自我首选的操作系统 macOS。与以前一样,我们将运行的命令应该在我们迄今为止所针对的三个操作系统上都能工作,除非另有说明。

我们还将研究云提供商提供的一些命令行工具,以帮助管理他们的服务-本章不作为这些工具的详细使用指南,但在本章的进一步阅读部分中将提供更详细的使用指南的链接。

查看以下视频,了解代码的运行情况:

bit.ly/2Se544n

Docker Cloud

在我们开始查看其他服务之前,我认为快速讨论一下 Docker Cloud 会是一个好主意,因为仍然有很多关于 Docker 曾经提供的云管理服务的参考资料。

Docker Cloud 由几个 Docker 服务组成。这些包括用于构建和托管镜像的 SaaS 服务,这是另一项提供的服务,应用程序、节点和 Docker Swarm 集群管理。在 2018 年 5 月 21 日,所有提供远程节点管理的服务都已关闭。

Docker 建议使用 Docker Cloud 的用户将其使用该服务管理节点的工作负载迁移到 Docker Community Edition (CE)或 Docker Enterprise Edition (EE)以及其自己硬件的云中。Docker 还推荐了 Azure 容器服务和 Google Kubernetes 引擎。

因此,在本章中,我们不会像在以前的掌握 Docker版本中那样讨论任何 Docker 托管服务。

然而,考虑到我们所讨论的内容,下一节可能会有点令人困惑。虽然 Docker 已经停止了所有托管的云管理服务,但它仍然提供工具来帮助您在两个主要的公共云提供商中管理您的 Docker Swarm 集群。

云上的 Docker

在本节中,我们将看看 Docker 提供的两个模板化云服务。这两个都会启动 Docker Swarm 集群,并且与目标平台有深度集成,并且还考虑了 Docker 的最佳实践。让我们先看看 Amazon Web Services 模板。

Docker 社区版适用于 AWS

Docker 社区版适用于 AWS(我们从现在开始称之为 Docker for AWS)是由 Docker 创建的一个 Amazon CloudFormation 模板,旨在在 AWS 中轻松启动 Docker Swarm 模式集群,并应用了 Docker 的最佳实践和建议。

CloudFormation是亚马逊提供的一项服务,允许您在一个模板文件中定义您希望您的基础架构看起来的方式,然后可以共享或纳入版本控制。

我们需要做的第一件事 - 也是在启动 Docker for AWS 之前唯一需要配置的事情 - 是确保我们在将要启动集群的区域中为我们的帐户分配了 SSH 密钥。要做到这一点,请登录到 AWS 控制台console.aws.amazon.com/,或者如果您使用自定义登录页面,则登录到您的组织的自定义登录页面。登录后,转到页面左上角的服务菜单,找到EC2服务。

为了确保您在所需的区域中,您可以在用户名和支持菜单之间的右上角使用区域切换器。一旦您在正确的区域中,点击密钥对,它可以在左侧菜单中的网络和安全下找到。进入密钥对页面后,您应该看到您当前密钥对的列表。如果没有列出或者您无法访问它们,您可以单击创建密钥对导入密钥对,然后按照屏幕提示操作。

Docker for AWS 可以在 Docker Store 中找到store.docker.com/editions/community/docker-ce-aws。您可以选择 Docker for AWS 的两个版本:稳定版和 Edge 版本。

Edge 版本包含来自即将推出的 Docker 版本的实验性功能;因此,我们将看看如何启动 Docker for AWS(稳定版)。要做到这一点,只需点击按钮,您将直接进入 AWS 控制台中的 CloudFormation,Docker 模板已经加载。

您可以查看原始模板,目前由 3100 行代码组成,方法是转到editions-us-east-1.s3.amazonaws.com/aws/stable/Docker.tmpl,或者您可以在 CloudFormation 设计师中可视化模板。如您从以下可视化中所见,有很多内容可以启动集群:

这种方法的美妙之处在于,您不必担心任何这些复杂性。Docker 已经为您考虑周全,并且已经承担了所有关于如何启动上述基础设施和服务的工作。

启动集群的第一步已经为您准备好了。您只需在选择模板页面上点击下一步

接下来,我们必须指定有关我们的集群的一些细节。除了 SSH 密钥,我们将保持一切默认值不变:

  • 堆栈名称Docker

  • Swarm 管理器数量3

  • Swarm 工作节点数量5

  • 要使用哪个 SSH 密钥:(从列表中选择您的密钥)

  • 启用每日资源清理:否

  • 使用 CloudWatch 进行容器日志记录:是

  • 为 CloudStore 创建 EFS 先决条件:否

  • Swarm 管理器实例类型:t2.micro

  • 管理器临时存储卷大小:20

  • 管理器临时存储卷类型:标准

  • 代理工作实例类型:t2.micro

  • 工作实例临时存储卷大小:20

  • 工作实例临时存储卷类型:标准

  • 启用 EBS I/O 优化? 否

  • 加密 EFS 对象? 假

一旦您确认一切正常,请点击下一步按钮。在下一步中,我们可以将一切保持不变,然后点击下一步按钮,进入审核页面。在审核页面上,您应该找到一个链接,给出了估算成本:

如您所见,我的集群的月度估算为 113.46 美元。

我对“估算成本”链接的成功率有所不同——如果它没有出现,并且您已根据上述列表回答了问题,那么您的成本将与我的相似。

在启动集群之前,您需要做的最后一件事是勾选“我承认 AWS CloudFormation 可能会创建 IAM 资源”的复选框,然后点击“创建”按钮。正如您所想象的那样,启动集群需要一些时间;您可以通过在 AWS 控制台中选择您的 CloudFormation 堆栈并选择“事件”选项卡来检查启动的状态:

大约 15 分钟后,您应该会看到状态从“CREATE_IN_PROGRESS”更改为“CREATE_COMPLETE”。当您看到这一点时,点击“输出”选项卡,您应该会看到一系列 URL 和链接:

要登录到我们的 Swarm 集群,点击“管理者”旁边的链接,进入 EC2 实例列表,这些是我们的管理节点。选择一个实例,然后记下其公共 DNS 地址。在终端中,使用 docker 作为用户名 SSH 到节点。例如,我运行以下命令登录并获取所有节点列表:

$ ssh docker@ec2-34-245-167-38.eu-west-1.compute.amazonaws.com
$ docker node ls

如果您在添加密钥时从 AWS 控制台下载了您的 SSH 密钥,您应该更新上述命令以包括您下载密钥的路径,例如,ssh -i /path/to/private.key docker@ec2-34-245-167-38.eu-west-1.compute.amazonaws.com

登录并获取所有节点列表的先前命令显示在以下截图中:

从这里,您可以像对待任何其他 Docker Swarm 集群一样对待它。例如,我们可以通过运行以下命令来启动和扩展集群服务:

$ docker service create --name cluster --constraint "node.role == worker" -p 80:80/tcp russmckendrick/cluster
$ docker service scale cluster=6
$ docker service ls
$ docker service inspect --pretty cluster

现在您的服务已经启动,您可以在 CloudFormation 页面的“输出”选项卡中查看给定 URL 作为“DefaultDNSTarget”的应用程序。这是一个 Amazon 弹性负载均衡器,所有节点都在其后面。

例如,我的“DefaultDNSTarget”是Docker-ExternalLoa-PCIAX1UI53AS-1796222965.eu-west-1.elb.amazonaws.com。将其放入浏览器中显示了集群应用程序:

完成集群后,返回到 AWS 控制台中的 CloudFormation 页面,选择您的堆栈,然后从“操作”下拉菜单中选择“删除堆栈”。这将删除 Amazon Web Services 集群中 Docker 的所有痕迹,并阻止您产生任何意外费用。

请确保检查删除堆栈时没有出现任何问题——如果此过程遇到任何问题,任何留下的资源都将产生费用。

Docker 社区版 Azure

接下来,我们有 Azure 的 Docker 社区版,我将称之为 Docker for Azure。这使用 Azure 资源管理器(ARM)模板来定义我们的 Docker Swarm 集群。使用 ARMViz 工具,我们可以可视化集群的外观:

如您所见,它将启动虚拟机、带有公共 IP 地址的负载均衡器和存储。在启动我们的集群之前,我们需要找到有关我们的 Azure 帐户的一些信息:

  • AD 服务主体 ID

  • AD 服务主体密钥

为了生成所需的信息,我们将使用一个在容器内运行的辅助脚本。要运行该脚本,您需要对有效的 Azure 订阅具有管理员访问权限。要运行脚本,只需运行以下命令:

$ docker run -ti docker4x/create-sp-azure sp-name

这将为您提供一个 URL,microsoft.com/devicelogin,还有一个要输入的代码。转到该 URL 并输入代码:

这将在命令行中登录您的帐户,并询问您想要使用哪个订阅。辅助脚本的完整输出可以在以下截图中找到:

在输出的最后,您将找到所需的信息,请记下来。

在撰写本书时,已知在 Docker Store 的 Docker 社区版 Azure 页面上使用“Docker for Azure(稳定版)”按钮存在问题。目前,我们需要使用较旧版本的模板。您可以通过以下链接执行此操作:portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fdownload.docker.com%2Fazure%2Fstable%2F18.03.0%2FDocker.tmpl

这将打开 Azure 门户,并呈现一个屏幕,您需要在其中输入一些信息:

  • 订阅:从下拉列表中选择您想要使用的订阅

  • 资源组:选择您想要使用或创建新的资源组

  • 位置:选择您想要启动 Docker Swarm 集群的位置

  • 广告服务原则应用程序 ID:这是由我们刚刚运行的辅助脚本生成的

  • 广告服务原则应用程序密钥:这是由我们刚刚运行的辅助脚本生成的

  • 启用 Ext 日志:是

  • 启用系统清理:否

  • Linux SSH 公钥:在此处输入本地 SSH 密钥的公共部分

  • Linux 工作节点计数:2

  • Linux 工作节点 VM 大小:Standard_D2_v2

  • 管理器计数:1

  • 管理器 VM 大小:Standard_D2_v2

  • Swarm 名称:dockerswarm

同意条款和条件,然后点击页面底部的购买按钮。一旦您通过点击菜单顶部通知区域的“部署中”链接查看启动的进度,您应该会看到类似以下内容:

完成后,您将在您选择或创建的资源组下看到几个服务。其中一个将是dockerswarm-externalSSHLoadBalancer-public-ip。深入研究资源,您将获得可以用于 SSH 到您的 Swarm Manager 的 IP 地址。要做到这一点,请运行以下命令:

$ ssh docker@52.232.99.223 -p 50000
$ docker node ls

请注意,我们使用的是端口 5000,而不是标准端口 22。您应该会看到类似以下内容:

一旦您登录到管理节点,我们可以使用以下命令启动应用程序:

$ docker service create --name cluster --constraint "node.role == worker" -p 80:80/tcp russmckendrick/cluster
$ docker service scale cluster=6
$ docker service ls
$ docker service inspect --pretty cluster

启动后,转到dockerswarm-externalLoadBalancer-public-ip—这将显示应用程序。完成集群后,我建议删除资源组,而不是尝试删除单个资源:

请记住,只要资源处于活动状态,您就会被收费,即使您没有使用它们。

与亚马逊网络服务集群一样,请确保资源完全被删除,否则您可能会收到意外的账单。

云摘要 Docker

正如您所看到的,使用 Docker 提供的模板在 Azure 和亚马逊网络服务中启动 Swarm 集群大多是直截了当的。虽然这些模板很棒,但如果您刚开始使用,它们在 Docker 方面的支持很少。我建议,如果您正在寻找一种在公共云中运行生产工作负载的容器的简单方法,您可以看一下我们接下来要讨论的一些解决方案。

亚马逊 ECS 和 AWS Fargate

亚马逊网络服务提供了几种不同的容器解决方案。我们将在本节中查看的是亚马逊弹性容器服务ECS)的一部分,称为 AWS Fargate。

传统上,亚马逊 ECS 启动 EC2 实例。一旦启动,亚马逊 ECS 代理会部署在容器运行时旁边,允许您使用 AWS 控制台和命令行工具来管理您的容器。AWS Fargate 消除了启动 EC2 实例的需要,使您可以简单地启动容器,而无需担心管理集群或承担 EC2 实例的费用。

我们将稍微作弊,并通过Amazon ECS 首次运行过程进行操作。您可以通过以下网址访问:console.aws.amazon.com/ecs/home#/firstRun. 这将带领我们完成启动 Fargate 集群中容器所需的四个步骤。

亚马逊 ECS 使用以下组件:

  • 容器定义

  • 任务定义

  • 服务

  • 集群

在启动我们的 AWS Fargate 托管容器的第一步是实际配置前两个组件,即容器和任务定义。

容器定义是容器的基本配置所在。可以将其视为在命令行上使用 Docker 客户端启动容器时添加的标志,例如,您可以命名容器,定义要使用的镜像,设置网络等等。

对于我们的示例,有三个预定义选项和一个自定义选项。单击自定义选项中的“配置”按钮,并输入以下信息:

  • 容器名称cluster-container

  • 镜像russmckendrick/cluster:latest

  • 内存限制(MiB):保持默认值

  • 端口映射:输入80,并保留选择tcp

然后,单击更新按钮。对于任务定义,单击编辑按钮,并输入以下内容:

  • 任务定义名称cluster-task

  • 网络模式:应该是awsvpc;您无法更改此选项

  • 任务执行角色:保持为ecsTaskExecutionRole

  • 兼容性:这应该默认为 FARGATE,您应该无法编辑它

  • 任务内存任务 CPU:将两者都保留在它们的默认选项上

更新后,点击保存按钮。现在,您可以点击页面底部的下一步按钮。这将带我们到第二步,即定义服务的地方。

一个服务运行任务,而任务又与一个容器相关联。默认服务是可以的,所以点击下一步按钮,继续启动过程的第三步。第一步是创建集群。同样,默认值是可以的,所以点击下一步按钮,进入审阅页面。

这是您最后一次在启动任何服务之前仔细检查任务、服务和集群定义的机会。如果您对一切满意,然后点击创建按钮。从这里,您将被带到一个页面,您可以查看使我们的 AWS Fargate 集群的各种 AWS 服务的状态:

一旦一切从待定变为完成,您就可以点击查看服务按钮,进入服务概述页面:

现在,我们只需要知道容器的公共 IP 地址。要找到这个,点击任务选项卡,然后选择正在运行的任务的唯一 ID。在页面的网络部分,您应该能够找到任务的私有和公共 IP 地址。在浏览器中输入公共 IP 地址应该会打开现在熟悉的集群应用程序:

您会注意到显示的容器名称是容器的主机名,并包括内部 IP 地址。您还可以通过点击日志选项卡查看容器的日志:

那么,这要花费多少钱呢?要能够运行容器一个整月大约需要花费 14 美元,这相当于每小时约 0.019 美元。

这种成本意味着,如果您要全天候运行多个任务,那么 Fargate 可能不是运行容器的最具成本效益的方式。相反,您可能希望选择 Amazon ECS EC2 选项,在那里您可以将更多的容器打包到您的资源上,或者 Amazon EKS 服务,我们将在本章后面讨论。然而,对于快速启动容器然后终止它,Fargate 非常适用——启动容器的门槛很低,支持资源的数量也很少。

完成 Fargate 容器后,应删除集群。这将删除与集群关联的所有服务。一旦集群被移除,进入任务定义页面,如果需要,取消注册它们。

接下来,我们将看一下 Azure 应用服务。

Microsoft Azure 应用服务

Microsoft Azure 应用服务是一个完全托管的平台,允许您部署应用程序,并让 Azure 担心管理它们正在运行的平台。在启动应用服务时有几个选项可用。您可以运行用.NET、.NET Core、Ruby、Node.js、PHP、Python 和 Ruby 编写的应用程序,或者您可以直接从容器镜像注册表启动镜像。

在这个快速演示中,我们将从 Docker Hub 启动集群镜像。要做到这一点,请登录到 Azure 门户网站portal.azure.com/,并从左侧菜单中选择应用服务。

在加载的页面上,点击+添加按钮。您有几个选项可供选择:

我们将要启动一个 Web 应用,所以点击相应的图块。一旦图块展开,点击创建按钮。

在打开的页面上,有几个选项。按以下方式填写它们:

  • 应用名称:为应用程序选择一个唯一的名称。

  • 订阅:选择有效的订阅。

  • 资源组:保持选择创建新选项。

  • 操作系统:保持为 Linux。

  • 发布:选择 Docker 镜像。

  • 应用服务计划/位置:默认情况下,选择最昂贵的计划,因此点击这里将带您到一个页面,您可以在其中创建一个新计划。要做到这一点,点击创建新的,命名您的计划并选择一个位置,最后选择一个定价层。对于我们的需求,开发/测试计划将很好。一旦选择,点击应用

  • 配置容器: 点击这里将带您到容器选项。在这里,您有几个选项:单个容器、Docker Compose 或 Kubernetes。现在,我们将启动一个单个容器。点击 Docker Hub 选项并输入 russmckendrick/cluster:latest。输入后,您将能够点击 应用 按钮。

一旦所有信息都填写完毕,您就可以点击 创建 来启动 Web 应用服务。一旦启动,您应该能够通过 Azure 提供的 URL 访问服务,例如,我的是 https://masteringdocker.azurewebsites.net/。在浏览器中打开这个链接将显示集群应用程序:

正如您所看到的,这一次我们有容器 ID 而不是像在 AWS Fargate 上启动容器时得到的完整主机名。这个规格的容器每小时大约会花费我们 0.05 美元,或者每月 36.50 美元。要删除容器,只需删除资源组。

在 Microsoft Azure、Google Cloud 和 Amazon Web Services 中的 Kubernetes

我们要看的最后一件事是在三个主要的公共云中启动 Kubernetes 集群有多容易。在上一章中,我们使用 Docker Desktop 应用程序的内置功能在本地启动了一个 Kubernetes 集群。首先,我们将看一下在公共云上开始使用 Kubernetes 的最快方法,从 Microsoft Azure 开始。

Azure Kubernetes Service

Azure Kubernetes ServiceAKS)是一个非常简单的服务,可以启动和配置。我将在本地机器上使用 Azure 命令行工具;您也可以使用内置在 Azure 门户中的 Azure Cloud Shell 使用命令行工具。

我们需要做的第一件事是创建一个资源组,将我们的 AKS 集群启动到其中。要创建一个名为 MasteringDockerAKS 的资源组,请运行以下命令:

$ az group create --name MasteringDockerAKS --location eastus

现在我们有了资源组,我们可以通过运行以下命令来启动一个两节点的 Kubernetes 集群:

$ az aks create --resource-group MasteringDockerAKS \
 --name MasteringDockerAKSCluster \
 --node-count 2 \
 --enable-addons monitoring \
 --generate-ssh-keys

启动集群需要几分钟时间。一旦启动,我们需要复制配置,以便我们可以使用本地的 kubectl 副本与集群进行交互。要做到这一点,请运行以下命令:

$ az aks get-credentials \
    --resource-group MasteringDockerAKS \
    --name MasteringDockerAKSCluster

这将配置您本地的 kubectl 副本,以便与您刚刚启动的 AKS 集群进行通信。现在您应该在 Docker 菜单下的 Kubernetes 中看到集群列表:

运行以下命令将显示您的kubectl客户端正在与其交谈的服务器版本以及有关节点的详细信息:

$ kubectl version
$ kubectl get nodes

您可以在以下截图中看到前面命令的输出:

现在我们的集群已经正常运行,我们需要启动一些东西。幸运的是,Weave 有一个出色的开源微服务演示,可以启动一个出售袜子的演示商店。要启动演示,我们只需要运行以下命令:

$ kubectl create namespace sock-shop
$ kubectl apply -n sock-shop -f "https://github.com/microservices-demo/microservices-demo/blob/master/deploy/kubernetes/complete-demo.yaml?raw=true"

演示启动大约需要五分钟。您可以通过运行以下命令来检查pods的状态:

$ kubectl -n sock-shop get pods

一切都正常运行后,您应该看到类似以下的输出:

现在我们的应用程序已经启动,我们需要一种访问它的方式。通过运行以下命令来检查服务:

$ kubectl -n sock-shop get services

这向我们展示了一个名为front-end的服务。我们将创建一个负载均衡器并将其附加到此服务。要做到这一点,请运行以下命令:

$ kubectl -n sock-shop expose deployment front-end --type=LoadBalancer --name=front-end-lb

您可以通过运行以下命令来检查负载均衡器的状态:

$ kubectl -n sock-shop get services front-end-lb
$ kubectl -n sock-shop describe services front-end-lb

启动后,您应该看到类似以下的内容:

从前面的输出中可以看出,对于我的商店,IP 地址是104.211.63.146,端口是8079。在浏览器中打开http://104.211.63.146:8079/后,我看到了以下页面:

完成商店浏览后,您可以通过运行以下命令将其删除:

$ kubectl delete namespace sock-shop

要删除 AKS 集群和资源组,请运行以下命令:

$ az group delete --name MasteringDockerAKS --yes --no-wait

请记住检查 Azure 门户中的所有内容是否按预期移除,以避免任何意外费用。最后,您可以通过运行以下命令从本地kubectl配置中删除配置:

$ kubectl config delete-cluster MasteringDockerAKSCluster
$ kubectl config delete-context MasteringDockerAKSCluster

接下来,我们将看看如何在 Google Cloud 中启动类似的集群。

Google Kubernetes Engine

正如您可能已经猜到的那样,Google Kubernetes Engine与 Google 的云平台紧密集成。而不是深入了解更多细节,让我们直接启动一个集群。我假设您已经拥有 Google Cloud 账户,一个启用了计费的项目,最后安装并配置了 Google Cloud SDK 以与您的项目进行交互。

要启动集群,只需运行以下命令:

$ gcloud container clusters create masteringdockergke --num-nodes=2

一旦集群启动,您的kubectl配置将自动更新,并为新启动的集群设置上下文。您可以通过运行以下命令查看有关节点的信息:

$ kubectl version
$ kubectl get nodes

现在我们的集群已经运行起来了,让我们通过重复上次使用的命令来启动演示商店:

$ kubectl create namespace sock-shop
$ kubectl apply -n sock-shop -f "https://github.com/microservices-demo/microservices-demo/blob/master/deploy/kubernetes/complete-demo.yaml?raw=true"
$ kubectl -n sock-shop get pods
$ kubectl -n sock-shop get services
$ kubectl -n sock-shop expose deployment front-end --type=LoadBalancer --name=front-end-lb
$ kubectl -n sock-shop get services front-end-lb

再次,一旦创建了front-end-lb服务,您应该能够找到要使用的外部 IP 地址端口:

将这些输入到浏览器中将打开商店:

要删除集群,只需运行以下命令:

$ kubectl delete namespace sock-shop
$ gcloud container clusters delete masteringdockergke

这也将从kubectl中删除上下文和集群。

亚马逊弹性容器服务 for Kubernetes

我们要看的最后一个 Kubernetes 服务是亚马逊弹性容器服务 for Kubernetes,简称Amazon EKS。这是我们正在介绍的三项服务中最近推出的服务。事实上,你可以说亚马逊非常晚才加入 Kubernetes 的行列。

不幸的是,亚马逊的命令行工具不像我们用于 Microsoft Azure 和 Google Cloud 的工具那样友好。因此,我将使用一个名为eksctl的工具,这个工具是由 Weave 编写的,他们也创建了我们一直在使用的演示商店。您可以在本章末尾的进一步阅读部分找到有关eksctl和亚马逊命令行工具的详细信息。

要启动我们的 Amazon EKS 集群,我们需要运行以下命令:

$ eksctl create cluster

启动集群需要几分钟时间,但在整个过程中,您将在命令行中收到反馈。此外,由于eksctl正在使用 CloudFormation,您还可以在 AWS 控制台中检查其进度。完成后,您应该会看到类似以下输出:

作为启动的一部分,eksctl将配置您的本地kubectl上下文,这意味着您可以运行以下命令:

$ kubectl version
$ kubectl get nodes

现在我们的集群已经运行起来了,我们可以像之前一样启动演示商店:

$ kubectl create namespace sock-shop
$ kubectl apply -n sock-shop -f "https://github.com/microservices-demo/microservices-demo/blob/master/deploy/kubernetes/complete-demo.yaml?raw=true"
$ kubectl -n sock-shop get pods
$ kubectl -n sock-shop get services
$ kubectl -n sock-shop expose deployment front-end --type=LoadBalancer --name=front-end-lb
$ kubectl -n sock-shop get services front-end-lb

您可能会注意到在运行最后一个命令时列出的外部 IP 看起来有点奇怪:

这是因为它是一个 DNS 名称而不是 IP 地址。要找到完整的 URL,您可以运行以下命令:

$ kubectl -n sock-shop describe services front-end-lb

在浏览器中输入 URL 和端口将会显示演示商店,正如您可能已经猜到的那样:

要删除集群,请运行以下命令:

$ kubectl delete namespace sock-shop
$ eksctl get cluster

这将返回正在运行的集群的名称。一旦您有了名称,运行以下命令,确保引用您自己的集群:

$ eksctl delete cluster --name=beautiful-hideout-1539511992

您的终端输出应如下所示:

Kubernetes 摘要

这结束了我们对 Microsoft Azure、Google Cloud 和 Amazon Web Services 中 Kubernetes 的简要介绍。我们在这里涵盖了一些有趣的观点。首先是,我们成功地使用命令行启动和管理了我们的集群,只需几个简单的步骤,尽管我们确实需要使用第三方工具来使用 Amazon EKS。

第二个最重要的观点是,一旦我们使用 kubectl 访问集群,体验在所有三个平台上都是完全相同的。在任何时候,我们都不需要访问云提供商的基于 web 的控制面板来调整或审查设置。一切都是使用相同的命令完成的;部署相同的代码和服务都是毫不费力的,我们不需要考虑云提供商提供的任何个别服务。

我们甚至可以使用 Docker 在本地运行演示商店,使用完全相同的命令。只需启动您的 Kubernetes 集群,确保选择了本地 Docker 上下文,然后运行以下命令:

$ kubectl create namespace sock-shop
$ kubectl apply -n sock-shop -f "https://github.com/microservices-demo/microservices-demo/blob/master/deploy/kubernetes/complete-demo.yaml?raw=true"
$ kubectl -n sock-shop get pods
$ kubectl -n sock-shop get services
$ kubectl -n sock-shop expose deployment front-end --type=LoadBalancer --name=front-end-lb
$ kubectl -n sock-shop get services front-end-lb

如您从以下输出中所见,负载均衡 IP,在这种情况下,是 localhost。打开浏览器并输入 http://localhost:8079 将带您进入商店:

您可以通过运行以下命令删除商店:

$ kubectl delete namespace sock-shop

在多个提供商甚至本地机器上实现这种一致性水平以前确实是不可行的,除非经过大量工作和配置,或者通过封闭源订阅服务。

摘要

在本章中,我们已经看了一下如何使用 Docker 自己提供的工具将 Docker Swarm 集群部署到云提供商。我们还看了公共云提供的两项服务,以便远离核心 Docker 工具集来运行容器。

最后,我们看了在各种云中启动 Kubernetes 集群,并在所有云中运行相同的演示应用程序。尽管从我们运行的任何命令中都很明显,所有三个公共云都使用各种版本的 Docker 作为容器引擎。尽管在您阅读本文时可能会发生变化,但理论上,它们可以切换到另一个引擎而几乎没有影响。

在下一章中,我们将回到使用 Docker 并查看 Portainer,这是一个用于管理 Docker 安装的基于 Web 的界面。

问题

  1. 真或假:Docker for AWS 和 Docker for Azure 为您启动 Kubernetes 集群,以便在其上启动容器。

  2. 如果使用 Amazon Fargate,您不必直接管理哪种亚马逊服务?

  3. 我们需要在 Azure 中启动什么类型的应用程序?

  4. 一旦启动,我们需要运行什么命令来为 Sock Shop 商店创建命名空间?

  5. 如何找到有关负载均衡器的详细信息?

进一步阅读

您可以在以下链接找到有关 Docker Cloud 服务关闭的详细信息:

有关 Docker for AWS 和 Docker for Azure 使用的模板服务的更多详细信息,请参阅以下链接:

我们用来启动容器的云服务可以在以下链接找到:

三个 Kubernetes 服务可以在以下链接找到:

本章中使用的各种命令行工具的快速入门可以在以下链接找到:

最后,有关演示商店的更多详细信息,请访问以下链接:

第十一章:Portainer - Docker 的图形用户界面

在本章中,我们将介绍 Portainer。Portainer 是一个允许您从 Web 界面管理 Docker 资源的工具。将涵盖的主题如下:

  • 通往 Portainer 的道路

  • 启动和运行 Portainer

  • 使用 Portainer 和 Docker Swarm

技术要求

与之前的章节一样,我们将继续使用本地的 Docker 安装。此外,本章中的截图将来自我首选的操作系统 macOS。在本章的最后,我们将使用 Docker Machine 和 VirtualBox 启动本地 Docker Swarm 集群。

与之前一样,我们将运行的 Docker 命令将适用于迄今为止安装了 Docker 的三种操作系统,但是一些支持命令可能只适用于基于 macOS 和 Linux 的操作系统。

观看以下视频以查看代码的实际操作:

bit.ly/2yWAdQV

通往 Portainer 的道路

在我们开始安装和使用 Portainer 之前,我们应该讨论一下项目的背景。本书的第一版涵盖了 Docker UI。Docker UI 是由 Michael Crosby 编写的,大约一年后,他将项目移交给了 Kevan Ahlquist。正是在这个阶段,由于商标问题,该项目被重命名为 UI for Docker。

Docker 的 UI 开发一直持续到 Docker 开始加速引入 Swarm 模式等功能到核心 Docker 引擎。大约在这个时候,UI for Docker 项目被分叉成了将成为 Portainer 的项目,Portainer 在 2016 年 6 月发布了第一个重要版本。

自从首次公开发布以来,Portainer 团队估计大部分代码已经更新或重写,并且到 2017 年中期,已经添加了新功能,例如基于角色的控制和 Docker Compose 支持。

2016 年 12 月,UI for Docker GitHub 存储库提交了一份通知,说明该项目现在已被弃用,应该使用 Portainer。

启动和运行 Portainer

我们首先将看看如何使用 Portainer 来管理本地运行的单个 Docker 实例。我正在使用 Docker for Mac,所以我将使用它,但这些说明也适用于其他 Docker 安装:

  1. 首先,要从 Docker Hub 获取容器镜像,我们只需要运行以下命令:
$ docker image pull portainer/portainer
$ docker image ls
  1. 如您在运行docker image ls命令时所见,Portainer 镜像只有 58.7MB。要启动 Portainer,如果您正在运行 macOS 或 Linux,只需运行以下命令:
$ docker container run -d \
 -p 9000:9000 \
 -v /var/run/docker.sock:/var/run/docker.sock \
 portainer/portainer
  1. Windows 用户需要运行以下命令:
$ docker container run -d -p 9000:9000 -v \\.\pipe\docker_engine:\\.\pipe\docker_engine portainer/portainer

如您刚刚运行的命令所示,我们正在挂载 Docker 引擎的套接字文件到我们的 Docker 主机机器上。这样做将允许 Portainer 完全无限制地访问主机上的 Docker 引擎。它需要这样做才能管理主机上的 Docker;但是,这也意味着您的 Portainer 容器可以完全访问您的主机机器,因此在如何授予其访问权限以及在远程主机上公开 Portainer 时要小心。

下面的截图显示了在 macOS 上执行此操作:

  1. 对于最基本的安装类型,这就是我们需要运行的全部内容。完成安装还需要进行一些步骤;所有这些步骤都是在浏览器中完成的。要完成这些步骤,请转到localhost:9000/

您将首先看到的屏幕要求您为管理员用户设置密码。

  1. 设置密码后,您将被带到登录页面:输入用户名admin和刚刚配置的密码。登录后,您将被询问您希望管理的 Docker 实例。有两个选项:
  • 管理 Portainer 正在运行的 Docker 实例

  • 管理远程 Docker 实例

目前,我们想要管理 Portainer 正在运行的实例,即本地选项,而不是默认的远程选项:

由于我们在启动 Portainer 容器时已经考虑了挂载 Docker 套接字文件,我们可以点击连接来完成我们的安装。这将直接带我们进入 Portainer 本身,显示仪表板。

使用 Portainer

现在我们已经运行并配置了 Portainer 与我们的 Docker 安装进行通信,我们可以开始逐个使用左侧菜单中列出的功能,从仪表板开始,这也是您的 Portainer 安装的默认登录页面。

仪表板

从下面的截图中可以看到,仪表板为我们提供了与 Portainer 配置通信的 Docker 实例的当前状态概览:

在我的情况下,这显示了我正在运行的容器数量,目前只有已经运行的 Portainer 容器,以及我已经下载的镜像数量。我们还可以看到 Docker 实例上可用的网络的数量,还会显示正在运行的堆栈的数量。

它还显示了 Docker 实例本身的基本信息;如您所见,Docker 实例正在运行 Moby Linux,有两个 CPU 和 2GB 的 RAM。这是 Docker for Mac 的默认配置。

仪表板将适应您运行 Portainer 的环境,因此当我们查看如何将 Portainer 附加到 Docker Swarm 集群时,我们将重新访问它。

应用程序模板

接下来,我们有应用程序模板。这部分可能是核心 Docker 引擎中唯一不直接可用的功能;相反,它是使用从 Docker Hub 下载的容器启动常见应用程序的一种方式:

Portainer 默认提供了大约 25 个模板。这些模板以 JSON 格式定义。例如,nginx 模板如下所示:

 {
 "type": "container",
 "title": "Nginx",
 "description": "High performance web server",
 "categories": ["webserver"],
 "platform": "linux",
 "logo": "https://portainer.io/images/logos/nginx.png",
 "image": "nginx:latest",
 "ports": [
 "80/tcp",
 "443/tcp"
 ],
 "volumes": ["/etc/nginx", "/usr/share/nginx/html"]
 }

还有更多选项可以添加,例如 MariaDB 模板:

 {
 "type": "container",
 "title": "MariaDB",
 "description": "Performance beyond MySQL",
 "categories": ["database"],
 "platform": "linux",
 "logo": "https://portainer.io/images/logos/mariadb.png",
 "image": "mariadb:latest",
 "env": [
 {
 "name": "MYSQL_ROOT_PASSWORD",
 "label": "Root password"
 }
 ],
 "ports": [
 "3306/tcp"
 ],
 "volumes": ["/var/lib/mysql"]
 }

如您所见,模板看起来类似于 Docker Compose 文件;但是,这种格式仅由 Portainer 使用。在大多数情况下,选项都相当直观,但我们应该提及名称标签选项。

对于通常需要通过环境变量传递自定义值来定义选项的容器,名称标签选项允许您向用户呈现自定义表单字段,在启动容器之前需要完成,如下面的截图所示:

如您所见,我们有一个字段,我们可以在其中输入我们想要用于 MariaDB 容器的根密码。填写这个字段将获取该值并将其作为环境变量传递,构建以下命令来启动容器:

$ docker container run --name [Name of Container] -p 3306 -e MYSQL_ROOT_PASSWORD=[Root password] -d mariadb:latest

有关应用程序模板的更多信息,我建议查阅文档,本章的进一步阅读部分中可以找到链接。

容器

接下来我们要查看左侧菜单中的容器。这是您启动和与在您的 Docker 实例上运行的容器进行交互的地方。点击容器菜单项将显示您的 Docker 实例上所有容器的列表,包括运行和停止的。

如您所见,我目前只运行了一个容器,那恰好是 Portainer。与其与之交互,不如点击+添加容器按钮来启动一个运行我们在前几章中使用的集群应用程序的容器。

创建容器页面上有几个选项;应该填写如下:

  • 名称cluster

  • 镜像russmckendrick/cluster

  • 始终拉取镜像:打开

  • 发布所有暴露的端口:打开

最后,通过点击+映射其他端口,从主机的端口8080到容器的端口80添加端口映射。您完成的表格应该看起来像以下的屏幕截图:

一旦完成,点击部署容器,几秒钟后,您将返回正在运行的容器列表,您应该会看到您新启动的容器:

在列表中每个容器左侧的复选框将启用顶部的按钮,您可以控制容器的状态 - 确保不要终止删除Portainer 容器。点击容器的名称,在我们的情况下是cluster,将会显示有关容器本身的更多信息:

如您所见,有关容器的信息与您运行此命令时获得的信息相同:

$ docker container inspect cluster

您可以通过点击检查来查看此命令的完整输出。您还会注意到有统计日志控制台的按钮。

统计

统计页面显示了容器的 CPU、内存和网络利用率,以及您正在检查的容器的进程列表:

如果您让页面保持打开状态,图表将自动刷新,刷新页面将清零图表并重新开始。这是因为 Portainer 正在使用以下命令从 Docker API 接收此信息:

$ docker container stats cluster

每次刷新页面时,该命令都会从头开始,因为 Portainer 目前不会在后台轮询 Docker 以记录每个运行容器的统计信息。

日志

接下来,我们有日志页面。这向您显示运行以下命令的结果:

$ docker container logs cluster

它显示STDOUTSTDERR日志:

您还可以选择将时间戳添加到输出中;这相当于运行以下命令:

$ docker container logs --timestamps cluster

控制台

最后,我们有控制台。这将打开一个 HTML5 终端,允许您登录到正在运行的容器中。在连接到容器之前,您需要选择一个 shell。您可以选择三种 shell 来使用:/bin/bash/bin/sh/bin/ash,还可以选择要连接的用户,root 是默认值。虽然集群镜像都安装了这些 shell,我选择使用/bin/bash

这相当于运行以下命令以访问您的容器:

$ docker container exec -it cluster /bin/sh

从屏幕截图中可以看出,bash进程的 PID 为15。这个进程是由docker container exec命令创建的,一旦您从 shell 会话中断开,这将是唯一终止的进程。

图像

左侧菜单中的下一个是图像。从这里,您可以管理、下载和上传图像:

在页面顶部,您可以选择拉取图像。例如,只需在框中输入amazonlinux,然后点击拉取,将从 Docker Hub 下载 Amazon Linux 容器镜像的副本。Portainer 执行的命令将是这样的:

$ docker image pull amazonlinux

您可以通过单击图像 ID 查找有关每个图像的更多信息;这将带您到一个页面,该页面很好地呈现了运行此命令的输出:

$ docker image inspect russmckendrick/cluster

看一下以下屏幕截图:

您不仅可以获取有关图像的所有信息,还可以选择将图像的副本推送到您选择的注册表,或者默认情况下推送到 Docker Hub。

您还可以完整地分解图像中包含的每个层,显示在构建过程中执行的命令和每个层的大小。

网络和卷

菜单中的下两个项目允许您管理网络和卷;我不会在这里详细介绍,因为它们没有太多内容。

网络

在这里,您可以快速使用默认的桥接驱动程序添加网络。单击高级设置将带您到一个具有更多选项的页面。这些选项包括使用其他驱动程序,定义子网,添加标签以及限制对网络的外部访问。与其他部分一样,您还可以删除网络和检查现有网络。

这里除了添加或删除卷之外,没有太多选项。添加卷时,您可以选择驱动程序,并且可以填写要传递给驱动程序的选项,这允许使用第三方驱动程序插件。除此之外,这里没有太多可看的,甚至没有检查选项。

事件

事件页面显示了过去 24 小时内的所有事件;您还可以选择过滤结果,这意味着您可以快速找到您需要的信息:

这相当于运行以下命令:

$ docker events --since '2018-09-27T16:30:00' --until '2018-09-28T16:30:00'

引擎

最后一个条目只是简单地显示以下输出:

$ docker info

以下显示了命令的输出:

如果您正在针对多个 Docker 实例端点进行操作,并且需要有关端点正在运行的环境的信息,这可能很有用。

在这一点上,我们将转而查看在 Docker Swarm 上运行的 Portainer,现在是一个很好的时机来删除正在运行的容器,以及在我们首次启动 Portainer 时创建的卷,您可以使用以下命令删除卷:

$ docker volume prune

Portainer 和 Docker Swarm

在上一节中,我们看了如何在独立的 Docker 实例上使用 Portainer。Portainer 还支持 Docker Swarm 集群,并且界面中的选项会适应集群环境。我们应该尝试启动一个 Swarm,然后将 Portainer 作为服务启动,看看有什么变化。

创建 Swarm

就像在 Docker Swarm 章节中一样,我们将使用 Docker Machine 在本地创建 Swarm;要做到这一点,请运行以下命令:

$ docker-machine create -d virtualbox swarm-manager
$ docker-machine create -d virtualbox swarm-worker01
$ docker-machine create -d virtualbox swarm-worker02

一旦三个实例启动,运行以下命令初始化 Swarm:

$ docker $(docker-machine config swarm-manager) swarm init \
 --advertise-addr $(docker-machine ip swarm-manager):2377 \
 --listen-addr $(docker-machine ip swarm-manager):2377

然后运行以下命令,插入您自己的令牌,以添加工作节点:

$ SWARM_TOKEN=SWMTKN-1-45acey6bqteiro42ipt3gy6san3kec0f8dh6fb35pnv1xz291v-4l89ei7v6az2b85kb5jnf7nku
$ docker $(docker-machine config swarm-worker01) swarm join \
 --token $SWARM_TOKEN \
 $(docker-machine ip swarm-manager):2377
$ docker $(docker-machine config swarm-worker02) swarm join \
 --token $SWARM_TOKEN \
 $(docker-machine ip swarm-manager):2377

现在我们已经形成了我们的集群,运行以下命令将本地 Docker 客户端指向管理节点:

$ eval $(docker-machine env swarm-manager)

最后,使用以下命令检查 Swarm 的状态:

$ docker node ls

Portainer 服务

现在我们有一个 Docker Swarm 集群,并且我们的本地客户端已配置为与管理节点通信,我们可以通过简单运行以下命令来启动 Portainer 服务:

$ docker service create \
 --name portainer \
 --publish 9000:9000 \
 --constraint 'node.role == manager' \
 --mount type=bind,src=/var/run/docker.sock,dst=/var/run/docker.sock \
 portainer/portainer \
 -H unix:///var/run/docker.sock

如您所见,这将在管理节点上启动 Portainer 作为服务,并使服务挂载管理节点的套接字文件,以便它能够看到 Swarm 的其余部分。您可以使用以下命令检查服务是否已启动而没有任何错误:

$ docker service ls 
$ docker service inspect portainer --pretty

以下显示了输出:

现在服务已启动,您可以在集群中任何节点的 IP 地址上的端口9000上访问 Portainer,或者运行以下命令:

$ open http://$(docker-machine ip swarm-manager):9000

当页面打开时,您将再次被要求为管理员用户设置密码;设置后,您将看到登录提示。登录后,您将直接进入仪表板。这是因为这次我们启动 Portainer 时,传递了参数-H unix:///var/run/docker.sock,这告诉 Portainer 选择我们在单主机上启动 Portainer 时手动选择的选项。

Swarm 差异

如前所述,当连接到 Docker Swarm 集群时,Portainer 界面会有一些变化。在本节中,我们将对它们进行介绍。如果界面的某个部分没有提到,则在单主机模式下运行 Portainer 时没有区别。

端点

当您登录时,首先要做的是选择一个端点,如下屏幕所示,有一个称为primary的端点:

点击端点将带您到仪表板,我们将在本节末再次查看端点

仪表板和 Swarm

您将注意到的第一个变化是仪表板现在显示有关 Swarm 集群的信息,例如:

请注意,CPU 显示为 3,总 RAM 为 3.1 GB,集群中的每个节点都有 1 GB 的 RAM 和 1 个 CPU,因此这些值是集群的总计。

点击转到集群可视化器将带您到 Swam 页面,这给您提供了集群的视觉概述,目前唯一运行的服务是 Portainer:

堆栈

我们在左侧菜单中没有涵盖的一项是堆栈,从这里,您可以像我们在查看 Docker Swarm 时那样启动堆栈。实际上,让我们使用我们之前使用的 Docker Compose 文件,它看起来像下面这样:

version: "3"

services:
   redis:
     image: redis:alpine
     volumes:
       - redis_data:/data
     restart: always
   mobycounter:
     depends_on:
       - redis
     image: russmckendrick/moby-counter
     ports:
       - "8080:80"
     restart: always

volumes:
    redis_data:

单击+添加堆栈按钮,然后将上面的内容粘贴到 Web 编辑器中,输入名称为MobyCounter,名称中不要添加任何空格或特殊字符,因为 Docker 会使用该名称,然后单击部署堆栈

部署后,您将能够单击MobyCounter并管理堆栈:

堆栈是服务的集合,让我们接着看看它们。

服务

这个页面是您可以创建和管理服务的地方;它应该已经显示了包括 Portainer 在内的几个服务。为了不与正在运行的 Portainer 容器造成任何问题,我们将创建一个新的服务。要做到这一点,单击+添加服务按钮。在加载的页面上,输入以下内容:

  • 名称cluster

  • 图像:russmckendrick/cluster

  • 调度模式复制

  • 副本1

这一次,我们需要为主机上的端口8000添加端口映射,映射到容器上的端口80,这是因为我们在上一节中启动的堆栈已经在主机上使用端口8080

输入信息后,单击创建服务按钮。您将被带回服务列表,其中现在应该包含我们刚刚添加的 cluster 服务。您可能已经注意到,在调度模式列中,有一个选项可以进行扩展。单击它,并将cluster服务的副本数增加到6

单击名称列中的cluster将带我们到服务的概述。正如您所看到的,服务上有很多信息:

您可以在服务上进行许多实时更改,包括放置约束、重启策略、添加服务标签等。页面底部是与服务相关的任务列表:

正如您所看到的,我们有六个正在运行的任务,每个节点上有两个。单击左侧菜单中的容器可能会显示与您预期不同的内容:

只列出了三个容器,其中一个是 Portainer 服务。为什么会这样?

好吧,如果您还记得 Docker Swarm 章节中,我们学到docker container命令只适用于您针对其运行的节点,并且由于 Portainer 只与我们的管理节点通信,因此 Docker 容器命令只针对该节点执行。请记住,Portainer 只是 Docker API 的 Web 界面,因此它反映了在命令行上运行docker container ls时获得的相同结果。

添加终端

但是,我们可以将我们的另外两个集群节点添加到 Portainer 中。要做到这一点,请点击左侧菜单中的终端条目。

要添加终端,我们需要知道终端 URL 并访问证书,以便 Portainer 可以对其自身进行身份验证,以针对节点上运行的 Docker 守护程序。幸运的是,由于我们使用 Docker Machine 启动了主机,这是一项简单的任务。要获取终端 URL,请运行以下命令:

$ docker-machine ls

对我来说,两个终端 URL 分别是192.168.99.101:2376192.168.99.102:2376;您的可能不同。我们需要上传的证书可以在您的机器上的~/.docker/machine/certs/文件夹中找到。我建议运行以下命令来在您的查找器中打开文件夹:

$ cd ~/.docker/machine/certs/
$ open .

添加节点后,您将能够使用设置/终端页面中的+添加终端按钮切换到该节点。

从这里输入以下信息:

  • 名称swarm-worker01

  • 终端 URL192.168.99.101:2376

  • 公共 IP: 192.168.99.101

  • TLS:打开

  • 带服务器和客户端验证的 TLS:已选中

  • ~/.docker/machine/certs/上传证书

然后点击+添加终端按钮,点击主页将带您到我们在本章节开始时首次看到的终端概述屏幕。

您还会注意到除了在终端中提到 Swarm 之外,没有提到 Swarm 服务。同样,这是因为 Portainer 只知道与您的 Docker 节点一样多,Swarm 模式只允许具有管理器角色的节点启动服务和任务,并与集群中的其他节点进行交互。

不要忘记通过运行以下命令来删除您的本地 Docker Swarm 集群:

$ docker-machine rm swarm-manager swarm-worker01 swarm-worker02

总结

我们的深入探讨到此结束。正如你所看到的,Portainer 非常强大,但使用起来简单,随着功能的发布,它将继续增长并集成更多的 Docker 生态系统。使用 Portainer,你不仅可以对主机进行大量操作,还可以对单个或集群主机上运行的容器和服务进行操作。

在下一章中,我们将看看如何保护您的 Docker 主机以及如何对容器映像运行扫描。

问题

  1. 在 macOS 或 Linux 机器上,挂载 Docker 套接字文件的路径是什么?

  2. Portainer 运行的默认端口是多少?

  3. 真或假:您可以使用 Docker Compose 文件作为应用程序模板?

  4. 真或假:Portainer 中显示的统计数据只是实时的,无法查看历史数据?

进一步阅读

你可以在这里找到更多关于 Portainer 的信息:

第十二章:Docker 安全

在本章中,我们将看一下 Docker 安全,这是当今所有人都关注的话题。我们将把本章分成以下五个部分:

  • 容器考虑

  • Docker 命令

  • 最佳实践

  • Docker Bench Security 应用程序

  • 第三方安全服务

技术要求

在本章中,我们将在桌面上使用 Docker,并使用 Docker Machine 在云中启动 Docker 主机。与之前的章节一样,我将使用我偏好的操作系统,即 macOS。与之前一样,我们将运行的 Docker 命令将适用于迄今为止我们安装 Docker 的三种操作系统。然而,一些支持命令可能只适用于基于 macOS 和 Linux 的操作系统,而且数量很少。

查看以下视频以查看代码的实际操作:

bit.ly/2AnEv5G

容器考虑

当 Docker 首次发布时,有很多关于 Docker 与虚拟机的讨论。我记得在杂志上读到的文章,评论 Reddit 上的帖子,以及读了无数的博客文章。在 Docker 的 alpha 和 beta 版本的早期,人们习惯将 Docker 容器视为虚拟机,因为当时没有其他参考点,我们将它们视为微型虚拟机。

过去,我会启用 SSH,在容器中运行多个进程,甚至通过启动容器并运行安装软件堆栈的命令来创建我的容器映像。这是我们在第二章《构建容器映像》中讨论过的内容;你绝对不应该这样做,因为这被认为是一种不良实践。

因此,与其讨论容器与虚拟机的区别,不如看看在运行容器而不是虚拟机时需要考虑的一些因素。

优势

当您启动 Docker 容器时,Docker 引擎在幕后进行了大量工作。在启动容器时,Docker 引擎执行的任务之一是设置命名空间和控制组。这是什么意思?通过设置命名空间,Docker 将每个容器中的进程隔离 - 不仅与其他容器隔离,而且与主机系统隔离。控制组确保每个容器获得自己的 CPU、内存和磁盘 I/O 等资源份额。更重要的是,它们确保一个容器不会耗尽给定 Docker 主机上的所有资源。

正如您在前几章中看到的,能够将容器启动到 Docker 控制的网络中意味着您可以在应用程序级别隔离您的容器;应用程序 A 的所有容器在网络层面上都无法访问应用程序 B 的容器。

此外,这种网络隔离可以在单个 Docker 主机上运行,使用默认的网络驱动程序,或者可以通过使用 Docker Swarm 的内置多主机网络驱动程序,或者 Weave 的 Weave Net 驱动程序跨多个 Docker 主机。

最后,我认为 Docker 相对于典型虚拟机的最大优势之一是您不应该需要登录到容器中。 Docker 正在尽最大努力让您不需要登录到容器中来管理它正在运行的进程。通过诸如docker container execdocker container topdocker container logsdocker container stats之类的命令,您可以做任何需要做的事情,而无需暴露更多的服务。

您的 Docker 主机

当您处理虚拟机时,您可以控制谁可以访问哪个虚拟机。假设您只希望开发人员 User 1 访问开发虚拟机。然而,User 2 是负责开发和生产环境的运营商,因此他需要访问所有虚拟机。大多数虚拟机管理工具允许您为虚拟机授予基于角色的访问权限。

使用 Docker 时,您有一点劣势,因为无论是通过被授予 sudo 访问权限还是通过将其用户添加到 Docker Linux 组,只要有人可以访问您的 Docker 主机上的 Docker 引擎,他们就可以访问您运行的每个 Docker 容器。他们可以运行新的容器,停止现有的容器,也可以删除镜像。小心授予谁访问您主机上的 Docker 引擎的权限。他们基本上掌握了您所有容器的王国之钥。鉴于此,建议仅将 Docker 主机用于 Docker;将其他服务与您的 Docker 主机分开。

镜像信任

如果您正在运行虚拟机,您很可能会自己设置它们,从头开始。由于下载的大小(以及启动的工作量),您可能不会下载某个随机人在互联网上创建的预构建机器镜像。通常情况下,如果您这样做,那将是来自受信任软件供应商的预构建虚拟设备。

因此,您将了解虚拟机内部的内容和不了解的内容,因为您负责构建和维护它。

Docker 吸引人的部分原因是其易用性;然而,这种易用性可能会让您很容易忽视一个非常关键的安全考虑:您知道容器内部在运行什么吗?

我们已经在早期章节中提到了镜像信任。例如,我们谈到了不要发布或下载未使用 Dockerfile 定义的镜像,也不要直接将自定义代码或秘密信息等嵌入到您将要推送到 Docker Hub 的镜像中。

容器虽然有命名空间、控制组和网络隔离的保护,但我们讨论了一个错误判断的镜像下载可能会引入安全问题和风险到您的环境中。例如,一个完全合法的容器运行一个未打补丁的软件可能会给您的应用程序和数据的可用性带来风险。

Docker 命令

让我们来看看 Docker 命令,可以用来加强安全性,以及查看您可能正在使用的镜像的信息。

我们将专注于两个命令。第一个将是docker container run命令,这样你就可以看到一些你可以利用这个命令的项目。其次,我们将看一下docker container diff命令,你可以用它来查看你计划使用的镜像做了什么。

run 命令

关于docker run命令,我们主要关注的是允许你将容器内的所有内容设置为只读的选项,而不是指定目录或卷。这有助于限制恶意应用程序可能造成的损害,它们也可能通过更新其二进制文件来劫持一个易受攻击的应用程序。

让我们来看看如何启动一个只读容器,然后分解它的功能,如下所示:

$ docker container run -d --name mysql --read-only -v /var/lib/mysql -v /tmp -v /var/run/mysqld -e MYSQL_ROOT_PASSWORD=password mysql

在这里,我们正在运行一个 MySQL 容器,并将整个容器设置为只读,除了以下文件夹:

  • /var/lib/mysql

  • /var/run/mysqld

  • /tmp

这些将被创建为三个单独的卷,然后挂载为读/写。如果你不添加这些卷,那么 MySQL 将无法启动,因为它需要读/写访问权限才能在/var/run/mysqld中创建套接字文件,在/tmp中创建一些临时文件,最后,在/var/lib/mysql中创建数据库本身。

容器内的任何其他位置都不允许你在其中写任何东西。如果你尝试运行以下命令,它将失败:

$ docker container exec mysql touch /trying_to_write_a_file

前面的命令将给你以下消息:

touch: cannot touch '/trying_to_write_a_file': Read-only file system

如果你想控制容器可以写入的位置(或者不能写入的位置),这可能非常有帮助。一定要明智地使用它。进行彻底测试,因为当应用程序无法写入某些位置时可能会产生后果。

类似于前一个命令docker container run,我们将所有内容设置为只读(除了指定的卷),我们可以做相反的操作,只设置一个卷(或者如果你使用更多的-v开关,则设置更多卷)为只读。关于卷的一点要记住的是,当你使用一个卷并将其挂载到容器中时,它将作为空卷挂载到容器内的目录上,除非你使用--volumes-from开关或在启动后以其他方式向容器添加数据:

$ docker container run -d -v /local/path/to/html/:/var/www/html/:ro nginx

这将把 Docker 主机上的/local/path/to/html/挂载到/var/www/html/,并将其设置为只读。如果您不希望运行的容器写入卷,以保持数据或配置文件的完整性,这可能会很有用。

diff 命令

让我们再看一下docker diff命令;由于它涉及容器的安全方面,您可能希望使用托管在 Docker Hub 或其他相关存储库上的镜像。

请记住,谁拥有对您的 Docker 主机和 Docker 守护程序的访问权限,谁就可以访问您所有正在运行的 Docker 容器。也就是说,如果您没有监控,某人可能会对您的容器执行命令并进行恶意操作。

让我们看看我们在上一节中启动的 MySQL 容器:

$ docker container diff mysql

您会注意到没有返回任何文件。为什么呢?

嗯,diff命令告诉您自容器启动以来对镜像所做的更改。在上一节中,我们使用只读镜像启动了 MySQL 容器,然后挂载了卷到 MySQL 需要读写的位置——这意味着我们下载的镜像和正在运行的容器之间没有文件差异。

停止并删除 MySQL 容器,然后运行以下命令清理卷:

$ docker container stop mysql
$ docker container rm mysql
$ docker volume prune

然后,再次启动相同的容器,去掉只读标志和卷;这会给我们带来不同的情况,如下所示:

$ docker container run -d --name mysql -e MYSQL_ROOT_PASSWORD=password mysql
$ docker container exec mysql touch /trying_to_write_a_file
$ docker container diff mysql

正如您所看到的,创建了两个文件夹并添加了几个文件:

A /trying_to_write_a_file
C /run
C /run/mysqld
A /run/mysqld/mysqld.pid
A /run/mysqld/mysqld.sock
A /run/mysqld/mysqld.sock.lock
A /run/mysqld/mysqlx.sock
A /run/mysqld/mysqlx.sock.lock

这是发现容器内可能发生的任何不当或意外情况的好方法。

最佳实践

在本节中,我们将研究在使用 Docker 时的最佳实践,以及互联网安全中心指南,以正确地保护 Docker 环境的所有方面。

Docker 最佳实践

在我们深入研究互联网安全中心指南之前,让我们回顾一下使用 Docker 的一些最佳实践,如下所示:

  • 每个容器一个应用程序:将您的应用程序分散到每个容器中。Docker 就是为此而构建的,这样做会让一切变得更容易。我们之前讨论的隔离就是关键所在。

  • 只安装所需内容:正如我们在之前的章节中所介绍的,只在容器镜像中安装所需的内容。如果必须安装更多内容来支持容器应该运行的一个进程,我建议你审查原因。这不仅使你的镜像小而且便携,还减少了潜在的攻击面。

  • 审查谁可以访问你的 Docker 主机:请记住,拥有 Docker 主机的 root 或 sudo 访问权限的人可以访问和操作主机上的所有镜像和容器。

  • 使用最新版本:始终使用最新版本的 Docker。这将确保所有安全漏洞都已修补,并且你拥有最新的功能。在修复安全问题的同时,使用社区版本保持最新可能会引入由功能或新特性变化引起的问题。如果这对你是一个问题,那么你可能需要查看 Docker 提供的 LTS 企业版本,以及 Red Hat。

  • 利用资源:如果需要帮助,请利用可用的资源。Docker 社区庞大而乐于助人。在规划 Docker 环境和评估平台时,利用他们的网站、文档和 Slack 聊天室会对你有所帮助。有关如何访问 Slack 和社区其他部分的更多信息,请参阅《第十四章》,《Docker 的下一步》。

互联网安全中心基准

互联网安全中心(CIS)是一个独立的非营利组织,其目标是提供安全的在线体验。他们发布的基准和控制被认为是 IT 各个方面的最佳实践。

Docker 的 CIS 基准可免费下载。你应该注意,它目前是一个 230 页的 PDF,根据知识共享许可发布,涵盖了 Docker CE 17.06 及更高版本。

当你实际运行扫描(在本章的下一部分)并获得需要修复的结果时,你将参考本指南。该指南分为以下几个部分:

  • 主机配置

  • Docker 守护程序配置

  • Docker 守护程序配置文件

  • 容器镜像/运行时

  • Docker 安全操作

主机配置

本指南的这一部分涵盖了您的 Docker 主机的配置。这是 Docker 环境中所有容器运行的部分。因此,保持其安全性至关重要。这是对抗攻击者的第一道防线。

Docker 守护程序配置

本指南的这一部分包含了保护正在运行的 Docker 守护程序的建议。您对 Docker 守护程序配置所做的每一项更改都会影响每个容器。这些是您可以附加到 Docker 守护程序的开关,我们之前看到的,以及下一节中我们运行工具时将看到的项目。

Docker 守护程序配置文件

本指南的这一部分涉及 Docker 守护程序使用的文件和目录。这涵盖了从权限到所有权的各种方面。有时,这些区域可能包含您不希望他人知道的信息,这些信息可能以纯文本格式存在。

容器图像/运行时和构建文件

本指南的这一部分包含了保护容器图像和构建文件的信息。

第一部分包含图像、封面基础图像和使用的构建文件。正如我们之前所讨论的,您需要确保您使用的图像,不仅仅是基础图像,还包括 Docker 体验的任何方面。本指南的这一部分涵盖了在创建自己的基础图像时应遵循的条款。

容器运行时

这一部分以前是后面的一部分,但现在已经移动到 CIS 指南的自己的部分。容器运行时涵盖了许多与安全相关的项目。

小心使用运行时变量。在某些情况下,攻击者可以利用它们,而您可能认为您正在利用它们。在您的容器中暴露太多,例如将应用程序秘密和数据库连接暴露为环境变量,不仅会危及您的容器的安全性,还会危及 Docker 主机和在该主机上运行的其他容器的安全性。

Docker 安全操作

本指南的这一部分涵盖了涉及部署的安全领域;这些项目与 Docker 最佳实践更紧密相关。因此,最好遵循这些建议。

Docker 基准安全应用程序

在本节中,我们将介绍您可以安装和运行的 Docker 基准安全应用程序。该工具将检查以下内容:

  • 主机配置

  • Docker 守护程序配置

  • Docker 守护程序配置文件

  • 容器镜像和构建文件

  • 容器运行时

  • Docker 安全操作

  • Docker Swarm 配置

看起来熟悉吗?应该是的,因为这些是我们在上一节中审查过的相同项目,只是构建成一个应用程序,它将为您做很多繁重的工作。它将向您显示配置中出现的警告,并提供有关其他配置项的信息,甚至通过了测试的项目。

现在,我们将看一下如何运行该工具,一个实时示例,以及该过程的输出意味着什么。

在 Docker for macOS 和 Docker for Windows 上运行该工具

运行该工具很简单。它已经被打包到一个 Docker 容器中。虽然您可以获取源代码并自定义输出或以某种方式操纵它(比如,通过电子邮件输出),但默认情况可能是您所需要的。

该工具的 GitHub 项目可以在github.com/docker/docker-bench-security/找到,要在 macOS 或 Windows 机器上运行该工具,您只需将以下内容复制并粘贴到您的终端中。以下命令缺少检查systemd所需的行,因为作为 Docker for macOS 和 Docker for Windows 的基础操作系统的 Moby Linux 不运行systemd。我们将很快看一下基于systemd的系统:

$ docker run -it --net host --pid host --cap-add audit_control \
 -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
 -v /var/lib:/var/lib \
 -v /var/run/docker.sock:/var/run/docker.sock \
 -v /etc:/etc --label docker_bench_security \
 docker/docker-bench-security

一旦镜像被下载,它将启动并立即开始审核您的 Docker 主机,打印结果,如下面的屏幕截图所示:

如您所见,有一些警告([WARN]),以及注释([NOTE])和信息([INFO]);但是,由于这个主机是由 Docker 管理的,正如您所期望的那样,没有太多需要担心的。

在 Ubuntu Linux 上运行

在我们更详细地查看审核输出之前,我将在 DigitalOcean 上启动一个原始的 Ubuntu 16.04.5 LTS 服务器,并使用 Docker Machine 进行干净的 Docker 安装,如下所示:

$ DOTOKEN=0cb54091fecfe743920d0e6d28a29fe325b9fc3f2f6fccba80ef4b26d41c7224
$ docker-machine create \
 --driver digitalocean \
 --digitalocean-access-token $DOTOKEN \
 docker-digitalocean

安装完成后,我将启动一些容器,所有这些容器都没有非常合理的设置。我将从 Docker Hub 启动以下两个容器:

$ docker container run -d --name root-nginx -v /:/mnt nginx
$ docker container run -d --name priv-nginx --privileged=true nginx

然后,我将基于 Ubuntu 16.04 构建一个自定义镜像,运行 SSH,使用以下Dockerfile

FROM ubuntu:16.04

RUN apt-get update && apt-get install -y openssh-server
RUN mkdir /var/run/sshd
RUN echo 'root:screencast' | chpasswd
RUN sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
ENV NOTVISIBLE "in users profile"
RUN echo "export VISIBLE=now" >> /etc/profile
EXPOSE 22
CMD ["/usr/sbin/sshd", "-D"]

我将使用以下代码构建和启动它:

$ docker image build --tag sshd .
$ docker container run -d -P --name sshd sshd

正如您所看到的,在一个图像中,我们正在使用root-nginx容器以完全读/写访问权限挂载我们主机的根文件系统。我们还在priv-nginx中以扩展特权运行,并最后在sshd中运行 SSH。

要在我们的 Ubuntu Docker 主机上开始审计,我运行了以下命令:

$ docker run -it --net host --pid host --cap-add audit_control \
 -e DOCKER_CONTENT_TRUST=$DOCKER_CONTENT_TRUST \
 -v /var/lib:/var/lib \
 -v /var/run/docker.sock:/var/run/docker.sock \
 -v /usr/lib/systemd:/usr/lib/systemd \
 -v /etc:/etc --label docker_bench_security \
 docker/docker-bench-security

由于我们正在运行支持systemd的操作系统,我们正在挂载/usr/lib/systemd,以便我们可以对其进行审计。

有很多输出和很多需要消化的内容,但这一切意味着什么呢?让我们来看看并分解每个部分。

理解输出

我们将看到三种类型的输出,如下所示:

  • [PASS]:这些项目是可靠的并且可以正常运行。它们不需要任何关注,但是很好阅读,让您感到内心温暖。这些越多,越好!

  • [WARN]:这些是需要修复的项目。这些是我们不想看到的项目。

  • [INFO]:这些是您应该审查并修复的项目,如果您认为它们与您的设置和安全需求相关。

  • [NOTE]:这些提供最佳实践建议。

如前所述,审计中涵盖了七个主要部分,如下所示:

  • 主机配置

  • Docker 守护程序配置

  • Docker 守护程序配置文件

  • 容器镜像和构建文件

  • 容器运行时

  • Docker 安全操作

  • Docker Swarm 配置

让我们看看我们在扫描的每个部分中看到了什么。这些扫描结果来自默认的 Ubuntu Docker 主机,在此时没有对系统进行任何调整。我们想专注于每个部分中的[WARN]项目。当您运行您自己的扫描时,可能会出现其他警告,但这些将是大多数人(如果不是所有人)首先遇到的警告。

主机配置

我的主机配置有五个带有[WARN]状态的项目,如下所示:

[WARN] 1.1 - Ensure a separate partition for containers has been created

默认情况下,Docker 在主机机器上使用/var/lib/docker来存储所有文件,包括默认驱动程序创建的所有镜像、容器和卷。这意味着这个文件夹可能会迅速增长。由于我的主机机器正在运行单个分区(并且取决于您的容器在做什么),这可能会填满整个驱动器,这将使我的主机机器无法使用:

[WARN] 1.5 - Ensure auditing is configured for the Docker daemon
[WARN] 1.6 - Ensure auditing is configured for Docker files and directories - /var/lib/docker
[WARN] 1.7 - Ensure auditing is configured for Docker files and directories - /etc/docker
[WARN] 1.10 - Ensure auditing is configured for Docker files and directories - /etc/default/docker

这些警告之所以被标记,是因为未安装auditd,并且没有 Docker 守护程序和相关文件的审计规则;有关auditd的更多信息,请参阅博客文章www.linux.com/learn/customized-file-monitoring-auditd/

Docker 守护程序配置

我的 Docker 守护程序配置标记了八个[WARN]状态,如下所示:

[WARN] 2.1 - Ensure network traffic is restricted between containers on the default bridge

默认情况下,Docker 允许在同一主机上的容器之间无限制地传递流量。可以更改此行为;有关 Docker 网络的更多信息,请参阅docs.docker.com/engine/userguide/networking/

[WARN] 2.5 - Ensure aufs storage driver is not used

在 Docker 的早期,AUFS 被广泛使用;然而,现在不再被认为是最佳实践,因为它可能导致主机机器的内核出现问题:

[WARN] 2.8 - Enable user namespace support

默认情况下,用户命名空间不会被重新映射。尽管可以映射它们,但目前可能会导致几个 Docker 功能出现问题;有关已知限制的更多详细信息,请参阅docs.docker.com/engine/reference/commandline/dockerd/

[WARN] 2.11 - Ensure that authorization for Docker client commands is enabled

Docker 的默认安装允许对 Docker 守护程序进行不受限制的访问;您可以通过启用授权插件来限制对经过身份验证的用户的访问。有关更多详细信息,请参阅docs.docker.com/engine/extend/plugins_authorization/

[WARN] 2.12 - Ensure centralized and remote logging is configured

由于我只运行单个主机,我没有使用诸如rsyslog之类的服务将我的 Docker 主机日志发送到中央服务器,也没有在我的 Docker 守护程序上配置日志驱动程序;有关更多详细信息,请参阅docs.docker.com/engine/admin/logging/overview/

[WARN] 2.14 - Ensure live restore is Enabled

--live-restore标志在 Docker 中启用了对无守护程序容器的全面支持;这意味着,与其在守护程序关闭时停止容器,它们会继续运行,并在重新启动时正确重新连接到容器。由于向后兼容性问题,默认情况下未启用;有关更多详细信息,请参阅docs.docker.com/engine/admin/live-restore/

[WARN] 2.15 - Ensure Userland Proxy is Disabled

您的容器可以通过两种方式路由到外部世界:使用 hairpin NAT 或用户态代理。对于大多数安装来说,hairpin NAT 模式是首选模式,因为它利用了 iptables 并具有更好的性能。在这种模式不可用的情况下,Docker 使用用户态代理。大多数现代操作系统上的 Docker 安装都将支持 hairpin NAT;有关如何禁用用户态代理的详细信息,请参阅docs.docker.com/engine/userguide/networking/default_network/binding/

[WARN] 2.18 - Ensure containers are restricted from acquiring new privileges

这样可以防止容器内的进程通过设置 suid 或 sgid 位获得任何额外的特权;这可以限制任何试图访问特权二进制文件的危险操作的影响。

Docker 守护程序配置文件

在这一部分中,我没有[WARN]状态,这是可以预料的,因为 Docker 是使用 Docker Machine 部署的。

容器映像和构建文件

我在容器映像和构建文件中有三个[WARN]状态;您可能会注意到多行警告在状态之后加上了*

[WARN] 4.1 - Ensure a user for the container has been created
[WARN]     * Running as root: sshd
[WARN]     * Running as root: priv-nginx
[WARN]     * Running as root: root-nginx

我正在运行的容器中的进程都以 root 用户身份运行;这是大多数容器的默认操作。有关更多信息,请参阅docs.docker.com/engine/security/security/

[WARN] 4.5 - Ensure Content trust for Docker is Enabled

为 Docker 启用内容信任可以确保您拉取的容器映像的来源,因为在推送它们时它们是数字签名的;这意味着您始终运行您打算运行的映像。有关内容信任的更多信息,请参阅docs.docker.com/engine/security/trust/content_trust/

[WARN] 4.6 - Ensure HEALTHCHECK instructions have been added to the container image
[WARN]     * No Healthcheck found: [sshd:latest]
[WARN]     * No Healthcheck found: [nginx:latest]
[WARN]     * No Healthcheck found: [ubuntu:16.04]

构建图像时,可以构建HEALTHCHECK;这可以确保当容器从您的图像启动时,Docker 会定期检查容器的状态,并在需要时重新启动或重新启动它。更多详细信息可以在docs.docker.com/engine/reference/builder/#healthcheck找到。

容器运行时

由于我们在审核的 Docker 主机上启动容器时有点愚蠢,我们知道这里会有很多漏洞,总共有 11 个:

[WARN] 5.2 - Ensure SELinux security options are set, if applicable
[WARN]     * No SecurityOptions Found: sshd
[WARN]     * No SecurityOptions Found: root-nginx

前面的漏洞是一个误报;我们没有运行 SELinux,因为它是一个 Ubuntu 机器,SELinux 只适用于基于 Red Hat 的机器;相反,5.1向我们展示了结果,这是一个[PASS],这是我们想要的:

[PASS] 5.1  - Ensure AppArmor Profile is Enabled

接下来的两个[WARN]状态是我们自己制造的,如下所示:

[WARN] 5.4 - Ensure privileged containers are not used
[WARN]     * Container running in Privileged mode: priv-nginx

以下也是我们自己制造的:

[WARN] 5.6 - Ensure ssh is not run within containers
[WARN]     * Container running sshd: sshd

这些可以安全地忽略;你很少会需要启动以Privileged mode运行的容器。只有当你的容器需要与运行在 Docker 主机上的 Docker 引擎交互时才需要;例如,当你运行一个 GUI(如 Portainer)时,我们在第十一章, Portainer - A GUI for Docker中介绍过。

我们还讨论过你不应该在容器中运行 SSH;有一些用例,比如在某个网络中运行跳板主机;然而,这些应该是例外情况。

接下来的两个[WARN]状态被标记,因为在 Docker 上,默认情况下,所有在 Docker 主机上运行的容器共享资源;为你的容器设置内存和 CPU 优先级的限制将确保你希望具有更高优先级的容器不会被优先级较低的容器耗尽资源:

[WARN] 5.10 - Ensure memory usage for container is limited
[WARN]      * Container running without memory restrictions: sshd
[WARN]      * Container running without memory restrictions: priv-nginx
[WARN]      * Container running without memory restrictions: root-nginx [WARN] 5.11 - Ensure CPU priority is set appropriately on the container [WARN]      * Container running without CPU restrictions: sshd
[WARN]      * Container running without CPU restrictions: priv-nginx
[WARN]      * Container running without CPU restrictions: root-nginx

正如我们在本章前面讨论过的,如果可能的话,你应该以只读模式启动你的容器,并为你知道需要写入数据的地方挂载卷:

[WARN] 5.12 - Ensure the container's root filesystem is mounted as read only
[WARN]      * Container running with root FS mounted R/W: sshd
[WARN]      * Container running with root FS mounted R/W: priv-nginx
[WARN]      * Container running with root FS mounted R/W: root-nginx

引发以下标志的原因是我们没有告诉 Docker 将我们的暴露端口绑定到 Docker 主机上的特定 IP 地址:

[WARN] 5.13 - Ensure incoming container traffic is binded to a specific host interface
[WARN] * Port being bound to wildcard IP: 0.0.0.0 in sshd

由于我的测试 Docker 主机只有一个网卡,这并不是太大的问题;然而,如果我的 Docker 主机有多个接口,那么这个容器将暴露给所有网络,如果我有一个外部和内部网络,这可能是一个问题。有关更多详细信息,请参阅docs.docker.com/engine/userguide/networking/

[WARN] 5.14 - Ensure 'on-failure' container restart policy is set to '5'
[WARN]      * MaximumRetryCount is not set to 5: sshd
[WARN]      * MaximumRetryCount is not set to 5: priv-nginx
[WARN]      * MaximumRetryCount is not set to 5: root-nginx

虽然我还没有使用--restart标志启动我的容器,但MaximumRetryCount没有默认值。这意味着如果一个容器一次又一次地失败,它会很高兴地坐在那里尝试重新启动。这可能会对 Docker 主机产生负面影响;添加MaximumRetryCount5将意味着容器在放弃之前会尝试重新启动五次:

[WARN] 5.25 - Ensure the container is restricted from acquiring additional privileges
[WARN]      * Privileges not restricted: sshd
[WARN]      * Privileges not restricted: priv-nginx
[WARN]      * Privileges not restricted: root-nginx

默认情况下,Docker 不会限制进程或其子进程通过 suid 或 sgid 位获得新特权。要了解如何阻止此行为的详细信息,请参阅www.projectatomic.io/blog/2016/03/no-new-privs-docker/

[WARN] 5.26 - Ensure container health is checked at runtime
[WARN]      * Health check not set: sshd
[WARN]      * Health check not set: priv-nginx
[WARN]      * Health check not set: root-nginx

再次强调,我们没有使用任何健康检查,这意味着 Docker 不会定期检查容器的状态。要查看引入此功能的拉取请求的 GitHub 问题,请浏览github.com/moby/moby/pull/22719/

[WARN] 5.28 - Ensure PIDs cgroup limit is used
[WARN]      * PIDs limit not set: sshd
[WARN]      * PIDs limit not set: priv-nginx
[WARN]      * PIDs limit not set: root-nginx

潜在地,攻击者可以通过容器内的单个命令触发 fork bomb。这有可能导致您的 Docker 主机崩溃,唯一的恢复方法是重新启动主机。您可以使用--pids-limit标志来防止这种情况发生。有关更多信息,请参阅拉取请求github.com/moby/moby/pull/18697/

Docker 安全操作

这一部分包括有关最佳实践的[INFO],如下所示:

[INFO] 6.1 - Perform regular security audits of your host system and containers
[INFO] 6.2 - Monitor Docker containers usage, performance and metering
[INFO] 6.3 - Backup container data
[INFO] 6.4 - Avoid image sprawl
[INFO]     * There are currently: 4 images
[INFO] 6.5 - Avoid container sprawl
[INFO]     * There are currently a total of 8 containers, with 4 of them currently running

Docker Swarm 配置

这一部分包括[PASS]信息,因为我们在主机上没有启用 Docker Swarm:

[PASS] 7.1 - Ensure swarm mode is not Enabled, if not needed
[PASS] 7.2 - Ensure the minimum number of manager nodes have been created in a swarm (Swarm mode not enabled)
[PASS] 7.3 - Ensure swarm services are binded to a specific host interface (Swarm mode not enabled)
[PASS] 7.5 - Ensure Docker's secret management commands are used for managing secrets in a Swarm cluster (Swarm mode not enabled)
[PASS] 7.6 - Ensure swarm manager is run in auto-lock mode (Swarm mode not enabled)
[PASS] 7.7 - Ensure swarm manager auto-lock key is rotated periodically (Swarm mode not enabled)
[PASS] 7.8 - Ensure node certificates are rotated as appropriate (Swarm mode not enabled)
[PASS] 7.9 - Ensure CA certificates are rotated as appropriate (Swarm mode not enabled)
[PASS] 7.10 - Ensure management plane traffic has been separated from data plane traffic (Swarm mode not enabled)

总结 Docker Bench

正如您所见,运行 Docker Bench 来评估 Docker 主机要比手动逐个测试 230 页文档中的每个测试要好得多。

第三方安全服务

在完成本章之前,我们将看一些可用的第三方服务,以帮助您评估图像的漏洞。

Quay

Quay,由 CoreOS 提供的图像注册服务,被 Red Hat 收购,类似于 Docker Hub/Registry;一个区别是 Quay 实际上在每次推送/构建图像后执行安全扫描。

您可以通过查看所选图像的存储库标记来查看扫描结果;在这里,您将看到一个安全扫描的列。正如您在下面的截图中所看到的,在我们创建的示例图像中,没有问题:

单击Passed将带您进入检测到图像中的任何漏洞的更详细的分解。目前没有漏洞(这是一件好事),因此此屏幕并没有告诉我们太多。但是,单击左侧菜单中的Packages图标将向我们显示扫描发现的软件包列表。对于我们的测试图像,它发现了 29 个没有漏洞的软件包,所有这些软件包都显示在这里,还确认了软件包的版本以及它们是如何引入到图像中的:

正如您也可以看到的,Quay 正在扫描我们公开可用的图像,该图像正在 Quay 提供的免费开源计划上托管。安全扫描是 Quay 所有计划的标准功能。

Clair

Clair是来自 CoreOS 的开源项目。实质上,它是一个为托管版本的 Quay 和商业支持的企业版本提供静态分析功能的服务。

它通过创建以下漏洞数据库的本地镜像来工作:

一旦它镜像了数据源,它就会挂载图像的文件系统,然后对安装的软件包进行扫描,将它们与前述数据源中的签名进行比较。

Clair 并不是一个简单的服务;它只有一个基于 API 的接口,并且默认情况下 Clair 没有附带任何花哨的基于 Web 或命令行的工具。API 的文档可以在coreos.com/clair/docs/latest/api_v1.html找到。

安装说明可以在项目的 GitHub 页面找到,网址为github.com/coreos/clair/

此外,您可以在其集成页面上找到支持 Clair 的工具列表,网址为coreos.com/clair/docs/latest/integrations.html

Anchore

我们要介绍的最后一个工具是Anchore。它有几个版本;有基于云的版本和本地企业版本,两者都配备了完整的基于 Web 的图形界面。还有一个可以连接到 Jenkins 的版本,以及开源命令行扫描仪,这就是我们现在要看的。

这个版本是作为 Docker Compose 文件分发的,所以我们将首先创建我们需要的文件夹,并且还将从项目 GitHub 存储库下载 Docker Compose 和基本配置文件。

$ mkdir anchore anchore/config
$ cd anchore
$ curl https://raw.githubusercontent.com/anchore/anchore-engine/master/scripts/docker-compose/docker-compose.yaml -o docker-compose.yaml
$ curl https://raw.githubusercontent.com/anchore/anchore-engine/master/scripts/docker-compose/config.yaml -o config/config.yaml

现在我们已经有了基本设置,您可以按照以下步骤拉取图像并启动容器:

$ docker-compose pull
$ docker-compose up -d

在我们与 Anchore 部署进行交互之前,我们需要安装命令行客户端。如果您使用的是 macOS,则必须运行以下命令,如果已经安装了pip,则忽略第一个命令:

$ sudo easy_install pip
$ pip install --user anchorecli
$ export PATH=${PATH}:${HOME}/Library/Python/2.7/bin

对于 Ubuntu 用户,您应该运行以下命令,如果已经安装了pip,则这次忽略前两个命令:

$ sudo apt-get update
$ sudo apt-get install python-pip
$ sudo pip install anchorecli

安装完成后,您可以运行以下命令来检查安装的状态:

$ anchore-cli --u admin --p foobar system status

这将显示您安装的整体状态;从您第一次启动开始,可能需要一两分钟才能显示所有内容为up

下一个命令会显示 Anchore 在数据库同步中的位置:

$ anchore-cli --u admin --p foobar system feeds list

如您在以下截图中所见,我的安装目前正在同步 CentOS 6 数据库。这个过程可能需要几个小时;但是,对于我们的示例,我们将扫描一个基于 Alpine Linux 的镜像,如下所示:

接下来,我们需要获取一个要扫描的镜像;让我们获取一个旧的镜像,如下所示:

$ anchore-cli --u admin --p foobar image add docker.io/russmckendrick/moby-counter:old

它将花费一两分钟来运行其初始扫描;您可以通过运行以下命令来检查状态:

$ anchore-cli --u admin --p foobar image list

一段时间后,状态应该从analyzing变为analyzed

$ anchore-cli --u admin --p foobar image get docker.io/russmckendrick/moby-counter:old

这将显示图像的概述,如下所示:

然后,您可以通过运行以下命令查看问题列表(如果有的话):

$ anchore-cli --u admin --p foobar image vuln docker.io/russmckendrick/moby-counter:old os

正如您所看到的,列出的每个软件包都有当前版本,指向 CVE 问题的链接,以及修复报告问题的版本号的确认。

您可以使用以下命令来删除 Anchore 容器:

$ docker-compose stop
$ docker-compose rm

总结

在本章中,我们涵盖了 Docker 安全的一些方面。首先,我们看了一些在运行容器时(与典型的虚拟机相比)必须考虑的事情,涉及安全性。我们看了看 Docker 主机的优势,然后讨论了镜像信任。然后我们看了看我们可以用于安全目的的 Docker 命令。

我们启动了一个只读容器,以便我们可以最小化入侵者在我们运行的容器中可能造成的任何潜在损害。由于并非所有应用程序都适合在只读容器中运行,因此我们随后研究了如何跟踪自启动以来对镜像所做的更改。在尝试解决任何问题时,能够轻松发现运行时文件系统上所做的任何更改总是很有用。

接下来,我们讨论了 Docker 的互联网安全中心指南。本指南将帮助您设置 Docker 环境的多个方面。最后,我们看了看 Docker Bench Security。我们看了如何启动它,并且我们通过了一个输出示例。然后我们分析了输出,看看它的含义。请记住应用程序涵盖的七个项目:主机配置,Docker 守护程序配置,Docker 守护程序配置文件,容器镜像和构建文件,容器运行时,Docker 安全操作和 Docker Swarm 配置。

在下一章中,我们将看看 Docker 如何适应您现有的工作流程,以及处理容器的一些新方法。

问题

  1. 启动容器时,如何使其全部或部分为只读?

  2. 每个容器应该运行多少个进程?

  3. 检查 Docker 安装与 CIS Docker 基准的最佳方法是什么?

  4. 运行 Docker Bench Security 应用程序时,应该挂载什么?

  5. 正确还是错误:Quay 仅支持私有图像的图像扫描。

进一步阅读

更多信息,请访问网站www.cisecurity.org/;Docker 基准可以在www.cisecurity.org/benchmark/docker/找到。

第十三章:Docker 工作流程

在本章中,我们将研究 Docker 以及 Docker 的各种工作流程。我们将把所有的部分整合在一起,这样你就可以开始在生产环境中使用 Docker,并且感到舒适。让我们来看看本章将涵盖的内容:

  • 用于开发的 Docker

  • 监控 Docker

  • 扩展到外部平台

  • 生产环境是什么样子?

技术要求

在本章中,我们将在桌面上使用 Docker。与之前的章节一样,我将使用我偏好的操作系统,即 macOS。我们将运行的 Docker 命令将适用于我们迄今为止安装了 Docker 的三种操作系统。然而,一些支持命令可能只适用于基于 macOS 和 Linux 的操作系统。

本章中使用的代码的完整副本可以在 GitHub 存储库中找到:github.com/PacktPublishing/Mastering-Docker-Third-Edition/tree/master/chapter14

观看以下视频以查看代码的实际操作:

bit.ly/2SaG0uP

用于开发的 Docker

我们将从讨论 Docker 如何帮助开发人员开始我们对工作流程的研究。在第一章 Docker 概述中,我们讨论的第一件事是开发人员和在我的机器上可以运行的问题。到目前为止,我们还没有完全解决这个问题,所以现在让我们来解决这个问题。

在本节中,我们将看看开发人员如何在本地机器上使用 Docker for macOS 或 Docker for Windows 以及 Docker Compose 开发他们的 WordPress 项目。

我们的目标是启动 WordPress 安装,以下是您将要执行的步骤:

  1. 下载并安装 WordPress。

  2. 允许从桌面编辑器(如 Atom、Visual Studio Code 或 Sublime Text)在本地机器上访问 WordPress 文件。

  3. 使用 WordPress 命令行工具(WP-CLI)配置和管理 WordPress。这使您可以在不丢失工作的情况下停止、启动甚至删除容器。

在启动 WordPress 安装之前,让我们来看看 Docker Compose 文件以及我们正在运行的服务:

version: "3"

services:
 web:
 image: nginx:alpine
 ports:
 - "8080:80"
 volumes:
 - "./wordpress/web:/var/www/html"
 - "./wordpress/nginx.conf:/etc/nginx/conf.d/default.conf"
 depends_on:
 - wordpress
 wordpress:
 image: wordpress:php7.2-fpm-alpine
 volumes:
 - "./wordpress/web:/var/www/html"
 depends_on:
 - mysql
 mysql:
 image: mysql:5
 environment:
 MYSQL_ROOT_PASSWORD: "wordpress"
 MYSQL_USER: "wordpress"
 MYSQL_PASSWORD: "wordpress"
 MYSQL_DATABASE: "wordpress"
 volumes:
 - "./wordpress/mysql:/var/lib/mysql"
 wp:
 image: wordpress:cli-2-php7.2
 volumes:
 - "./wordpress/web:/var/www/html"
 - "./wordpress/export:/export"

我们可以使用 PMSIpilot 的docker-compose-viz工具来可视化 Docker Compose 文件。要做到这一点,在与docker-compose.yml文件相同的文件夹中运行以下命令:

$ docker container run --rm -it --name dcv -v $(pwd):/input pmsipilot/docker-compose-viz render -m image docker-compose.yml

这将输出一个名为docker-compose.png的文件,您应该会得到类似于这样的东西:

您可以使用docker-compose-viz来为任何 Docker Compose 文件提供可视化表示。正如您从我们的文件中看到的,我们定义了四个服务。

第一个被称为web。这个服务是四个中唯一暴露给主机网络的服务,并且它充当我们 WordPress 安装的前端。它运行来自store.docker.com/images/nginx/的官方 nginx 镜像,并且扮演两个角色。在我们看这些之前,先看一下以下 nginx 配置:

server {
 server_name _;
 listen 80 default_server;

 root /var/www/html;
 index index.php index.html;

 access_log /dev/stdout;
 error_log /dev/stdout info;

 location / {
 try_files $uri $uri/ /index.php?$args;
 }

 location ~ .php$ {
 include fastcgi_params;
 fastcgi_pass wordpress:9000;
 fastcgi_index index.php;
 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
 fastcgi_buffers 16 16k;
 fastcgi_buffer_size 32k;
 }
}

您可以看到,我们正在使用 nginx 从/var/www/html/提供除 PHP 之外的所有内容,我们正在使用 nginx 从我们的主机机器挂载,并且所有 PHP 文件的请求都被代理到我们的第二个名为wordpress的服务,端口为9000。nginx 配置本身被挂载到我们的主机机器上的/etc/nginx/conf.d/default.conf

这意味着我们的 nginx 容器充当静态内容的 Web 服务器,这是第一个角色,同时也充当代理通过到 WordPress 容器的动态内容,这是容器承担的第二个角色。

第二个服务是wordpress;这是来自store.docker.com/images/wordpress的官方 WordPress 镜像,我正在使用php7.2-fpm-alpine标签。这使我们可以在 Alpine Linux 基础上运行的 PHP 7.2 上使用PHP-FPM构建的 WordPress 安装。

FastCGI 进程管理器PHP-FPM)是一个具有一些出色功能的 PHP FastCGI 实现。对我们来说,它允许 PHP 作为一个我们可以绑定到端口并传递请求的服务运行;这符合 Docker 在每个容器上运行单个服务的方法。

我们挂载了与 web 服务相同的网站根目录,在主机上是wordpress/web,在服务上是/var/www/html/。一开始,我们主机上的文件夹将是空的;然而,一旦 WordPress 服务启动,它将检测到没有任何核心 WordPress 安装,并将其复制到该位置,有效地引导我们的 WordPress 安装并将其复制到我们的主机上,准备让我们开始工作。

下一个服务是 MySQL,它使用官方的 MySQL 镜像(store.docker.com/images/mysql/),是我们使用的四个镜像中唯一不使用 Alpine Linux 的镜像(来吧 MySQL,动作快点,发布一个基于 Alpine Linux 的镜像!)。相反,它使用debian:stretch-slim。我们传递了一些环境变量,以便在容器首次运行时创建数据库、用户名和密码;如果您将来使用这个作为项目的基础,密码是您应该更改的内容。

webwordpress容器一样,我们从主机机器上挂载一个文件夹。在这种情况下,它是wordpress/mysql,我们将其挂载到/var/lib/mysql/,这是 MySQL 存储其数据库和相关文件的默认文件夹。

您会注意到当容器启动时,wordpress/mysql中填充了一些文件。我不建议使用本地 IDE 对其进行编辑。

最终的服务简单地称为wp。它与其他三个服务不同:这个服务在执行时会立即退出,因为容器内没有长时间运行的进程。它不提供长时间运行的进程,而是在与我们的主wordpress容器完全匹配的环境中提供对 WordPress 命令行工具的访问。

您会注意到我们挂载了网站根目录,就像我们在 web 和 WordPress 上做的那样,还有一个名为/export的第二个挂载;一旦我们配置了 WordPress,我们将更详细地看一下这一点。

启动 WordPress,我们只需要运行以下命令来拉取镜像:

$ docker-compose pull

这将拉取镜像并启动webwordpressmysql服务,以及准备wp服务。在服务启动之前,我们的wordpress文件夹看起来是这样的:

正如您所看到的,我们只在其中有nginx.conf,这是 Git 存储库的一部分。然后,我们可以使用以下命令启动容器并检查它们的状态:

$ docker-compose up -d
$ docker-compose ps

您应该看到在wordpress文件夹中已创建了三个文件夹:exportmysqlweb。还要记住,我们期望dockerwordpress_wp_1有一个exit状态,所以没问题:

打开浏览器并转到http://localhost:8080/应该显示标准的 WordPress 预安装欢迎页面,您可以在其中选择要用于安装的语言:

不要点击继续,因为它会带您到基于 GUI 的安装的下一个屏幕。而是返回到您的终端。

我们将使用 WP-CLI 而不是使用 GUI 来完成安装。这有两个步骤。第一步是创建一个wp-config.php文件。要做到这一点,请运行以下命令:

$ docker-compose run wp core config \
    --dbname=wordpress \
    --dbuser=wordpress \
    --dbpass=wordpress \
    --dbhost=mysql \
    --dbprefix=wp_

如您将在以下终端输出中看到的,在运行命令之前,我只有wp-config-sample.php文件,这是 WordPress 核心附带的。然后,在运行命令后,我有了自己的wp-config.php文件:

您会注意到在命令中,我们传递了我们在 Docker Compose 文件中定义的数据库详细信息,并告诉 WordPress 它可以连接到地址为mysql的数据库服务。

现在我们已经配置了数据库连接详细信息,我们需要配置我们的 WordPress 网站以及创建一个管理员用户并设置密码。要做到这一点,请运行以下命令:

$ docker-compose run wp core install \
 --title="Blog Title" \
 --url="http://localhost:8080" \
 --admin_user="admin" \
 --admin_password="password" \
 --admin_email="email@domain.com"

运行此命令将产生有关电子邮件服务的错误;不要担心这条消息,因为这只是一个本地开发环境。我们不太担心电子邮件离开我们的 WordPress 安装:

我们已经使用 WP-CLI 在 WordPress 中配置了以下内容:

  • 我们的 URL 是http://localhost:8080

  • 我们的网站标题应该是博客标题

  • 我们的管理员用户名是admin,密码是password,用户的电子邮件是email@domain.com

返回到您的浏览器并输入localhost:8080/应该呈现给您一个原始的 WordPress 网站:

在我们进一步操作之前,让我们先定制一下我们的安装,首先安装并启用 JetPack 插件:

$ docker-compose run wp plugin install jetpack --activate

该命令的输出如下:

然后,安装并启用sydney主题:

$ docker-compose run wp theme install sydney --activate

该命令的输出如下:

刷新我们的 WordPress 页面localhost:8080/应该显示类似以下内容:

在打开 IDE 之前,让我们使用以下命令销毁运行我们 WordPress 安装的容器:

$ docker-compose down 

该命令的输出如下:

由于我们整个 WordPress 安装,包括所有文件和数据库,都存储在我们的本地机器上,我们应该能够运行以下命令返回到我们离开的 WordPress 网站:

$ docker-compose up -d

一旦确认它按预期运行并正在运行,打开桌面编辑器中的docker-wordpress文件夹。我使用 Sublime Text。在编辑器中,打开wordpress/web/wp-blog-header.php文件,并在开头的 PHP 语句中添加以下行并保存:

echo "Testing editing in the IDE";

文件应该看起来像以下内容:

保存后,刷新浏览器,你应该在页面底部的 IDE 中看到消息Testing editing(以下屏幕是放大的;如果你在跟随,可能更难发现,因为文本非常小):

我们要看的最后一件事是为什么wordpress/export文件夹被挂载到wp容器上。

正如本章前面已经提到的,你不应该真的去触碰wordpress/mysql文件夹的内容;这也包括共享它。虽然如果你将项目文件夹压缩并传递给同事,它可能会工作,但这并不被认为是最佳实践。因此,我们已经挂载了导出文件夹,以便我们可以使用 WP-CLI 进行数据库转储和导入。

要做到这一点,运行以下命令:

$ docker-compose run wp db export --add-drop-table /export/wordpress.sql

以下终端输出显示了导出以及wordpress/export文件夹的内容,最后是 MySQL 转储的前几行:

如果需要的话,比如说,我在开发过程中犯了一个错误,我可以通过运行以下命令回滚到数据库的那个版本:

$ docker-compose run wp db import /export/wordpress.sql

命令的输出如下:

正如您所见,我们已经安装了 WordPress,使用 WP-CLI 和浏览器与其进行了交互,编辑了代码,并备份和恢复了数据库,所有这些都不需要安装或配置 nginx、PHP、MySQL 或 WP-CLI。我们也不需要登录到容器中。通过从主机机器挂载卷,我们的内容在我们关闭 WordPress 容器时是安全的,我们没有丢失任何工作。

此外,如果需要,我们可以轻松地将项目文件夹的副本传递给安装了 Docker 的同事,然后通过一条命令,他们就可以在我们的代码上工作,知道它在与我们自己的安装相同的环境中运行。

最后,由于我们正在使用 Docker Store 的官方镜像,我们知道可以安全地要求将它们部署到生产环境中,因为它们是根据 Docker 的最佳实践构建的。

不要忘记通过运行docker-compose down停止和删除您的 WordPress 容器。

监控

接下来,我们将看一下监视我们的容器和 Docker 主机。在第四章,管理容器中,我们讨论了docker container topdocker container stats命令。您可能还记得,这两个命令只显示实时信息;没有保留历史数据。

如果您正在尝试调试问题或者想快速了解容器内部发生了什么,这很棒,但如果您需要回顾问题,那就不太有帮助:也许您已经配置了容器,使其在变得无响应时重新启动。虽然这对应用程序的可用性有所帮助,但如果您需要查看容器为何变得无响应,那就没有太多帮助了。

在 GitHub 存储库的/chapter14文件夹中,有一个名为prometheus的文件夹,其中有一个 Docker Compose 文件,可以在两个网络上启动三个不同的容器。而不是查看 Docker Compose 文件本身,让我们来看一下可视化:

如您所见,有很多事情正在进行。我们正在运行的三个服务是:

  • Cadvisor

  • Prometheus

  • Grafana

在启动和配置 Docker Compose 服务之前,我们应该讨论每个服务为什么需要,从cadvisor开始。

cadvisor是 Google 发布的一个项目。正如您从我们使用的 Docker Hub 用户名在图像中看到的那样,Docker Compose 文件中的服务部分如下所示:

 cadvisor:
 image: google/cadvisor:latest
 container_name: cadvisor
 volumes:
 - /:/rootfs:ro
 - /var/run:/var/run:rw
 - /sys:/sys:ro
 - /var/lib/docker/:/var/lib/docker:ro
 restart: unless-stopped
 expose:
 - 8080
 networks:
 - back

我们正在挂载我们主机文件系统的各个部分,以便让cadvisor访问我们的 Docker 安装,方式与我们在第十一章,Portainer – A GUI for Docker中所做的方式相同。这样做的原因是,在我们的情况下,我们将使用cadvisor来收集容器的统计信息。虽然它可以作为独立的容器监控服务使用,但我们不希望公开暴露cadvisor容器。相反,我们只是让它在后端网络的 Docker Compose 堆栈中对其他容器可用。

cadvisor是 Docker 容器stat命令的自包含 Web 前端,显示图形并允许您从 Docker 主机轻松进入容器的易于使用的界面。但是,它不会保留超过 5 分钟的指标。

由于我们试图记录可能在几个小时甚至几天后可用的指标,所以最多只有 5 分钟的指标意味着我们将不得不使用其他工具来记录它处理的指标。cadvisor将我们想要记录的信息作为结构化数据暴露在以下端点:http://cadvisor:8080/metrics/

我们将在一会儿看到这为什么很重要。cadvisor端点正在被我们接下来的服务prometheus自动抓取。这是大部分繁重工作发生的地方。prometheus是由 SoundCloud 编写并开源的监控工具:

 prometheus:
 image: prom/prometheus
 container_name: prometheus
 volumes:
 - ./prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
 - prometheus_data:/prometheus
 restart: unless-stopped
 expose:
 - 9090
 depends_on:
 - cadvisor
 networks:
 - back

正如您从前面的服务定义中看到的,我们正在挂载一个名为./prometheus/prometheus.yml的配置文件,还有一个名为prometheus_data的卷。配置文件包含有关我们要抓取的源的信息,正如您从以下配置中看到的那样:

global:
 scrape_interval: 15s 
 evaluation_interval: 15s
 external_labels:
 monitor: 'monitoring'

rule_files:

scrape_configs:

 - job_name: 'prometheus'
 static_configs:
 - targets: ['localhost:9090']

 - job_name: 'cadvisor'
 static_configs:
 - targets: ['cadvisor:8080']

我们指示 Prometheus 每15秒从我们的端点抓取数据。端点在scrape_configs部分中定义,正如你所看到的,我们在其中定义了cadvisor以及 Prometheus 本身。我们创建和挂载prometheus_data卷的原因是,Prometheus 将存储我们所有的指标,因此我们需要确保它的安全。

在其核心,Prometheus 是一个时间序列数据库。它获取已经抓取的数据,处理数据以找到指标名称和数值,然后将其与时间戳一起存储。

Prometheus 还配备了强大的查询引擎和 API,使其成为这种数据的完美数据库。虽然它具有基本的图形能力,但建议您使用 Grafana,这是我们的最终服务,也是唯一一个公开暴露的服务。

Grafana是一个用于显示监控图形和指标分析的开源工具,它允许您使用时间序列数据库(如 Graphite、InfluxDB 和 Prometheus)创建仪表板。还有其他后端数据库选项可用作插件。

Grafana 的 Docker Compose 定义遵循与我们其他服务类似的模式:

 grafana:
 image: grafana/grafana
 container_name: grafana
 volumes:
 - grafana_data:/var/lib/grafana
 - ./grafana/provisioning/:/etc/grafana/provisioning/
 env_file:
 - ./grafana/grafana.config
 restart: unless-stopped
 ports:
 - 3000:3000
 depends_on:
 - prometheus
 networks:
 - front
 - back

我们使用grafana_data卷来存储 Grafana 自己的内部配置数据库,而不是将环境变量存储在 Docker Compose 文件中,我们是从名为./grafana/grafana.config的外部文件中加载它们。

变量如下:

GF_SECURITY_ADMIN_USER=admin
GF_SECURITY_ADMIN_PASSWORD=password
GF_USERS_ALLOW_SIGN_UP=false

正如你所看到的,我们在这里设置了用户名和密码,因此将它们放在外部文件中意味着你可以在不编辑核心 Docker Compose 文件的情况下更改这些值。

现在我们知道了这四个服务各自的角色,让我们启动它们。要做到这一点,只需从prometheus文件夹运行以下命令:

$ docker-compose pull
$ docker-compose up -d 

这将创建一个网络和卷,并从 Docker Hub 拉取镜像。然后它将启动这四个服务:

你可能会立刻转到 Grafana 仪表板。如果你这样做,你将看不到任何东西,因为 Grafana 需要几分钟来初始化自己。你可以通过查看日志来跟踪它的进度:

$ docker-compose logs -f grafana

命令的输出如下:

一旦您看到HTTP 服务器监听的消息,Grafana 将可用。使用 Grafana 5,您现在可以导入数据源和仪表板,这就是为什么我们将./grafana/provisioning/挂载到/etc/grafana/provisioning/的原因。这个文件夹包含配置,自动配置 Grafana 与我们的 Prometheus 服务通信,并导入仪表板,显示 Prometheus 从 cadvisor 中抓取的数据。

打开您的浏览器,输入http://localhost:3000/,您应该会看到一个登录界面:

输入用户admin密码password。一旦登录,如果您已配置数据源,您应该会看到以下页面:

正如您所看到的,安装 Grafana 的初始步骤|创建您的第一个数据源|创建您的第一个仪表板都已经执行完毕,只剩下最后两个步骤。现在,我们将忽略这些。点击左上角的主页按钮将会弹出一个菜单,列出可用的仪表板:

如您所见,我们有一个名为 Docker Monitoring 的数据源。点击它将会带您到以下页面:

如您从屏幕右上角的时间信息中所见,默认情况下显示最近五分钟的数据。点击它将允许您更改时间范围的显示。例如,以下屏幕显示了最近 15 分钟的数据,显然比 cadvisor 记录的五分钟要多:

我已经提到这是一个复杂的解决方案;最终,Docker 将扩展最近发布的内置端点,目前只公开有关 Docker 引擎而不是容器本身的信息。有关内置端点的更多信息,请查看官方 Docker 文档,网址为docs.docker.com/config/thirdparty/prometheus/

还有其他监控解决方案;其中大多数采用第三方软件即服务SaaS)的形式。从进一步阅读部分的服务列表中可以看出,列出了一些成熟的监控解决方案。实际上,您可能已经在使用它们,因此在扩展配置时,考虑监视容器时会很容易。

一旦您完成了对 Prometheus 安装的探索,请不要忘记通过运行以下命令来删除它:

$ docker-compose down --volumes --rmi all

这将删除所有容器、卷、镜像和网络。

扩展到外部平台

我们已经看过如何使用诸如 Docker Machine、Docker Swarm、Docker for Amazon Web Services 和 Rancher 等工具来扩展到其他外部平台,并启动集群以及来自公共云服务的集群和容器服务,如 Amazon Web Services、Microsoft Azure 和 DigitalOcean。

Heroku

Heroku与其他云服务有些不同,因为它被认为是平台即服务PaaS)。您不是在其上部署容器,而是将您的容器链接到 Heroku 平台,从中它将运行服务,如 PHP、Java、Node.js 或 Python。因此,您可以在 Heroku 上运行您的 Rails 应用程序,然后将您的 Docker 容器附加到该平台。

我们不会在这里涵盖安装 Heroku,因为这有点离题。有关 Heroku 的更多详细信息,请参阅本章的进一步阅读部分。

您可以将 Docker 和 Heroku 结合使用的方法是在 Heroku 平台上创建您的应用程序,然后在您的代码中,您将有类似以下内容的东西:

{
 "name": “Application Name",
 "description": “Application to run code in a Docker container",
 "image": “<docker_image>:<tag>”,
 "addons": [ "heroku-postgresql" ]
}

要退一步,我们首先需要安装插件才能使此功能正常工作。只需运行以下命令:

$ heroku plugins:install heroku-docker

现在,如果您想知道您可以或应该从 Docker Hub 使用哪个镜像,Heroku 维护了许多您可以在上述代码中使用的镜像:

  • heroku/nodejs

  • heroku/ruby

  • heroku/jruby

  • heroku/python

  • heroku/scala

  • heroku/clojure

  • heroku/gradle

  • heroku/java

  • heroku/go

  • heroku/go-gb

生产环境是什么样子的?

在本章的最后一节,我们将讨论生产环境应该是什么样子。这一节不会像你想象的那么长。这是因为有大量可用的选项,所以不可能覆盖它们所有。此外,根据前面的章节,你应该已经对什么对你最好有了一个很好的想法。

相反,我们将看一些在规划环境时应该问自己的问题。

Docker 主机

Docker 主机是你的环境的关键组件。没有这些,你就没有地方运行你的容器。正如我们在之前的章节中已经看到的,当涉及到运行 Docker 主机时有一些考虑因素。你需要考虑的第一件事是,如果你的主机正在运行 Docker,它们不应该运行任何其他服务。

进程混合

你应该抵制迅速在现有主机上安装 Docker 并启动容器的诱惑。这不仅可能会导致安全问题,因为你在单个主机上同时运行了隔离和非隔离的进程,而且还可能会导致性能问题,因为你无法为非容器化的应用添加资源限制,这意味着它们可能也会对正在运行的容器产生负面影响。

多个隔离的 Docker 主机

如果你有多个 Docker 主机,你将如何管理它们?运行像 Portainer 这样的工具很好,但当尝试管理多个主机时可能会麻烦。此外,如果你运行多个隔离的 Docker 主机,你就没有将容器在主机之间移动的选项。

当然,你可以使用诸如 Weave Net 之类的工具来跨多个独立的 Docker 主机扩展容器网络。根据你的托管环境,你可能还可以选择在外部存储上创建卷,并根据需要将它们呈现给 Docker 主机,但你很可能正在创建一个手动过程来管理容器在主机之间的迁移。

路由到你的容器

如果你有多个主机,你需要考虑如何在你的容器之间路由请求。

例如,如果您有外部负载均衡器,例如 AWS 中的 ELB,或者在本地集群前面有一个专用设备,您是否有能力动态添加路由,将命中端口 x的流量添加到您的 Docker 主机上的端口 y,然后将流量路由到您的容器?

如果您有多个容器都需要在同一个外部端口上访问,您将如何处理?

您是否需要安装代理,如 Traefik、HAProxy 或 nginx,以接受并根据基于域或子域的虚拟主机路由请求,而不仅仅是使用基于端口的路由?

例如,您可以仅使用网站的端口,将所有内容都配置到由 Docker 配置的容器上的端口80443,以接受这些端口上的流量。使用虚拟主机路由意味着您可以将domain-a.com路由到容器 a,然后将domainb.com路由到容器 bdomain-a.comdomain-b.com都可以指向相同的 IP 地址和端口。

聚类

我们在前一节讨论的许多问题都可以通过引入集群工具来解决,例如 Docker Swarm 和 Kubernetes

兼容性

即使应用程序在开发人员的本地 Docker 安装上运行良好,您也需要能够保证,如果将应用程序部署到例如 Kubernetes 集群,它也能以相同的方式工作。

十次中有九次,您不会遇到问题,但您确实需要考虑应用程序如何在同一应用程序集内部与其他容器进行通信。

参考架构

您选择的集群技术是否有参考架构?在部署集群时最好检查一下。有最佳实践指南与您提出的环境接近或匹配。毕竟,没有人想要创建一个巨大的单点故障。

此外,推荐的资源是什么?部署具有五个管理节点和单个 Docker 主机的集群没有意义,就像部署五个 Docker 主机和单个管理服务器一样,因为您有一个相当大的单点故障。

您的集群技术支持哪些支持技术(例如,远程存储、负载均衡器和防火墙)?

集群通信

当集群与管理或 Docker 主机通信时,有哪些要求?您是否需要内部或单独的网络来隔离集群流量?

您是否可以轻松地将集群成员限制在您的集群中?集群通信是否加密?您的集群可能会泄露哪些信息?这是否使其成为黑客的目标?

集群需要对 API 进行什么样的外部访问,比如您的公共云提供商?任何 API/访问凭据存储得有多安全?

镜像注册表

您的应用程序是如何打包的?您是否已经将代码嵌入到镜像中?如果是,您是否需要托管一个私有的本地镜像注册表,还是可以使用外部服务,比如 Docker Hub、Docker Trusted Registry (DTR)或 Quay?

如果您需要托管自己的私有注册表,在您的环境中应该放在哪里?谁有或需要访问权限?它是否可以连接到您的目录提供者,比如 Active Directory 安装?

总结

在本章中,我们看了一些关于 Docker 的不同工作流程,以及如何为您的容器和 Docker 主机启动一些监控。

当涉及到您自己的环境时,最好的做法是构建一个概念验证,并尽力覆盖您能想到的每一种灾难情景。您可以通过使用云提供商提供的容器服务或寻找一个良好的参考架构来提前开始,这些都应该限制您的试错。

在下一章中,我们将看一看容器世界中您的下一步是什么。

问题

  1. 哪个容器为我们的 WordPress 网站提供服务?

  2. 为什么wp容器不能保持运行状态?

  3. cAdvisor 会保留多长时间的指标?

  4. 什么 Docker Compose 命令可以用来删除与应用程序有关的所有内容?

进一步阅读

您可以在本章中找到我们使用的软件的详细信息,网址如下:

其他外部托管的 Docker 监控平台包括以下内容:

还有其他自托管选项,例如:

第十四章:下一步与 Docker

你已经读到了这本书的最后一章,并且一直坚持到了最后!在这一章中,我们将看看 Moby 项目以及你如何为 Docker 以及社区做出贡献。然后我们将以快速概述云原生计算基金会结束这一章。让我们从讨论 Moby 项目开始。

Moby 项目

在 DockerCon 2017 上宣布的消息之一是 Moby 项目。当这个项目被宣布时,我从同事那里得到了一些关于这个项目是什么的问题,因为乍一看,Docker 似乎发布了另一个容器系统。

那么,我是如何回答的呢?在几天内被困惑的表情困扰之后,我找到了以下答案:

“Moby 项目是一个开源项目的集合名称,它收集了用于构建基于容器的系统的几个库。该项目配备了自己的框架,用于将这些库组合成一个可用的系统,还有一个名为 Moby Origin 的参考系统;可以将其视为一个允许您构建甚至自定义自己的 Docker 的“Hello World”。”

在我给出这个答案后,通常会发生两件事中的一件;典型的反应是“但那实际上是什么意思?”我回答说:

“Moby 项目是 Docker(公司)和任何希望为项目做出贡献的人的开源游乐场,用于在公共论坛中开发新的并扩展现有特性到构成基于容器的系统的库和框架。其中一个产出是名为 Moby Origin 的尖端容器系统,另一个是 Docker(产品),它以开源社区版或商业支持的企业版形式提供。”

对于任何要求类似项目的例子,结合了尖端版本、稳定的开源版本和企业支持版本的人,我解释了 Red Hat 在 Red Hat Enterprise Linux 上的做法:

可以把它看作是 Red Hat 企业版 Linux 采用的方法。你有 Fedora,它是 Red Hat 操作系统开发者引入新软件包、功能以及移除旧的过时组件的前沿版本开发平台。通常,Fedora 的功能比 Red Hat 企业版 Linux 领先一两年,后者是基于 Fedora 项目工作成果的商业支持的长期版本;除了这个版本,你还可以在 CentOS 中找到社区支持版本。

你可能会想,为什么这本书的最后才提到这个? 嗯,在我写这本书的时候,这个项目仍处于起步阶段。事实上,工作仍在进行中,以将 Moby 项目所需的所有组件从主要的 Docker 项目中转移过来。

在我写这篇文章时,这个项目唯一真正可用的组件是LinuxKit,它是将所有库汇集在一起并输出可运行容器的可引导系统的框架。

由于这个项目发展速度极快,我不会提供如何使用 LinuxKit 或更多关于 Moby 项目的细节,因为在你阅读时可能已经发生了变化;相反,我建议收藏以下页面以保持最新:

贡献 Docker

所以,你想要帮助贡献 Docker 吗?你有一个想在 Docker 或其组件中看到的好主意吗?让我们为你提供所需的信息和工具。如果你不是程序员类型的人,你也可以通过其他方式进行贡献。Docker 拥有庞大的用户群,你可以通过帮助支持其他用户的服务来进行贡献。让我们学习如何做到这一点。

贡献代码

你可以通过帮助 Docker 代码来做出贡献。由于 Docker 是开源的,你可以下载代码到本地机器上,开发新功能,并将其作为拉取请求提交给 Docker。然后,它们将定期进行审查,如果他们认为你的贡献应该被接受,他们将批准拉取请求。当你知道自己的作品被接受时,这可能会让你感到非常谦卑。

首先,你需要知道如何设置贡献:这几乎是 Docker (github.com/docker/) 和 Moby Project (github.com/moby/) 的所有内容,我们在前一节已经讨论过。但是我们如何开始帮助贡献呢?最好的开始地方是遵循官方 Docker 文档中的指南,网址为docs.docker.com/project/who-written-for/

你可能已经猜到,为了建立开发环境,你不需要太多,因为很多开发都是在容器内完成的。例如,除了拥有 GitHub 账户外,Docker 列出了以下三个软件作为最低要求:

你可以在以下网址找到有关如何准备 Mac 和 Linux 的 Docker 开发的更多细节:docs.docker.com/opensource/project/software-required/,以及 Windows 用户的更多信息:docs.docker.com/opensource/project/software-req-win/

要成为一个成功的开源项目,必须有一些社区准则。我建议阅读这个优秀的快速入门指南,网址为:docs.docker.com/opensource/code/,以及更详细的贡献工作流程文档,网址为:docs.docker.com/opensource/workflow/make-a-contribution/

Docker 有一套行为准则,涵盖了他们的员工和整个社区应该如何行事。它是开源的,根据知识共享署名 3.0 许可,规定如下:

行为准则的完整代码可以在以下网址找到:github.com/docker/code-of-conduct/

提供 Docker 支持

您也可以通过其他方式为 Docker 做出贡献,而不仅仅是贡献 Docker 的代码或功能集。您可以利用自己所获得的知识来帮助其他人解决他们的支持问题。社区非常开放,总有人愿意帮助。当我遇到问题时,得到帮助对我非常有帮助。得到帮助也很好,但也要回馈给他人;这是一种很好的互惠互利。这也是一个收集你可以使用的想法的好地方。您可以根据他们的设置看到其他人提出的问题,这可能会激发您想要在您的环境中使用的想法。

您还可以关注有关服务的 GitHub 问题。这些可能是功能请求以及 Docker 如何实现它们,或者它们可能是通过使用服务而出现的问题。您可以帮助测试其他人遇到的问题,以查看您是否可以复制该问题,或者您是否找到了可能的解决方案。

Docker 拥有一个非常活跃的社区,网址为:community.docker.com/;在这里,您不仅可以看到最新的社区新闻和活动,还可以在他们的 Slack 频道中与 Docker 用户和开发人员交谈。在撰写本书时,有超过 80 个频道涵盖各种主题,如 Docker for Mac,Docker for Windows,Alpine Linux,Swarm,Storage 和 Network 等,每时每刻都有数百名活跃用户。

最后,还有 Docker 论坛,网址为:forums.docker.com/。如果您想搜索主题/问题或关键字,这是一个很好的来源。

其他贡献

还有其他方式可以为 Docker 做出贡献。您可以做一些事情,比如在您的机构推广服务并吸引兴趣。您可以通过您自己组织的通信方式开始这种沟通,无论是通过电子邮件分发列表、小组讨论、IT 圆桌会议还是定期安排的会议。

您还可以在您的组织内安排聚会,让人们开始交流。这些聚会旨在不仅包括您的组织,还包括您所在的城市或镇的成员,以便更广泛地传播和推广服务。

您可以通过访问以下网址搜索您所在地区是否已经有聚会:www.docker.com/community/meetup-groups/

云原生计算基金会

我们在第九章“Docker 和 Kubernetes”中简要讨论了云原生计算基金会。云原生计算基金会,简称 CNCF,旨在为允许您管理容器和微服务架构的项目提供一个供应商中立的家园。

其成员包括 Docker、亚马逊网络服务、谷歌云、微软 Azure、红帽、甲骨文、VMWare 和 Digital Ocean 等。2018 年 6 月,Linux 基金会报告称 CNCF 有 238 名成员。这些成员不仅贡献项目,还贡献工程时间、代码和资源。

毕业项目

在撰写本书时,有两个毕业项目,这两个项目我们在之前的章节中已经讨论过。这两个项目可以说也是基金会维护的项目中最知名的两个,它们分别是:

  • Kubernetes (kubernetes.io):这是第一个捐赠给基金会的项目。正如我们已经提到的,它最初是由 Google 开发的,现在在基金会成员和开源社区中拥有超过 2300 名贡献者。

  • Prometheus (prometheus.io):这个项目是由 SoundCloud 捐赠给基金会的。正如我们在第十三章“Docker 工作流”中所看到的,它是一个实时监控和警报系统,由强大的时间序列数据库引擎支持。

要毕业,一个项目必须完成以下工作:

  • 采用了类似于 Docker 发布的 CNCF 行为准则。完整的行为准则可以在以下网址找到:github.com/cncf/foundation/blob/master/code-of-conduct.md

  • 获得了Linux 基金会LF核心基础设施倡议CII)最佳实践徽章,证明该项目正在使用一套成熟的最佳实践进行开发 - 完整的标准可以在以下网址找到:github.com/coreinfrastructure/best-practices-badge/blob/master/doc/criteria.md

  • 至少收购了两家有项目提交者的组织。

  • 通过GOVERNANCE.mdOWNERS.md文件公开定义了提交者流程和项目治理。

  • ADOPTERS.md文件中公开列出项目的采用者,或者在项目网站上使用标志。

  • 获得了技术监督委员会TOC)的超级多数票。您可以在以下网址了解更多关于该委员会的信息:github.com/cncf/toc

还有另一种项目状态,目前大多数项目都处于这种状态。

项目孵化

处于孵化阶段的项目最终应该具有毕业生的地位。以下项目都做到了以下几点:

  • 证明该项目至少被三个独立的最终用户使用(不是项目发起者)。

  • 获得了大量的贡献者,包括内部和外部。

  • 展示了成长和良好的成熟水平。

技术指导委员会(TOC)积极参与与项目合作,以确保活动水平足以满足前述标准,因为指标可能因项目而异。

当前的项目列表如下:

  • OpenTracing (opentracing.io/):这是两个跟踪项目中的第一个,现在都属于 CNCF。与其说是一个应用程序,不如说你可以下载并使用它作为一组库和 API,让你在基于微服务的应用程序中构建行为跟踪和监控。

  • Fluentd(www.fluentd.org):这个工具允许您从大量来源收集日志数据,然后将日志数据路由到多个日志管理、数据库、归档和警报系统,如 Elastic Search、AWS S3、MySQL、SQL Server、Hadoop、Zabbix 和 DataDog 等。

  • gRPC(grpc.io):与 Kubernetes 一样,gRPC 是由谷歌捐赠给 CNCF 的。它是一个开源、可扩展和性能优化的 RPC 框架,已经在 Netflix、思科和 Juniper Networks 等公司投入使用。

  • Containerd(containerd.io):我们在第一章《Docker 概述》中简要提到了 Containerd,作为 Docker 正在开发的开源项目之一。它是一个标准的容器运行时,允许开发人员在其平台或应用程序中嵌入一个可以管理 Docker 和 OCI 兼容镜像的运行时。

  • Rkt(github.com/rkt/rkt):Rkt 是 Docker 容器引擎的替代品。它不是使用守护程序来管理主机系统上的容器,而是使用命令行来启动和管理容器。它是由 CoreOS 捐赠给 CNCF 的,现在由 Red Hat 拥有。

  • CNI(github.com/containernetworking):CNI 是 Container Networking Interface 的缩写,再次强调它不是您下载和使用的东西。相反,它是一种网络接口标准,旨在嵌入到容器运行时中,如 Kubernetes、Rkt 和 Mesos。拥有一个共同的接口和一组 API 允许通过第三方插件和扩展在这些运行时中更一致地支持高级网络功能。

  • Envoy(www.envoyproxy.io):最初在 Lyft 内部创建,并被苹果、Netflix 和谷歌等公司使用,Envoy 是一个高度优化的服务网格,提供负载均衡、跟踪和可观察数据库和网络活动的环境。

  • Jaeger(jaegertracing.io):这是列表中的第二个跟踪系统。与 OpenTracing 不同,它是一个完全分布式的跟踪系统,最初由 Uber 开发,用于监视其庞大的微服务环境。现在被 Red Hat 等公司使用,具有现代化的用户界面和对 OpenTracing 和各种后端存储引擎的本地支持。它旨在与其他 CNCF 项目(如 Kubernetes 和 Prometheus)集成。

  • Notary(github.com/theupdateframework/notary):该项目最初由 Docker 编写,是 TUF 的实现,接下来我们将介绍 TUF。它旨在允许开发人员通过提供一种机制来验证其容器映像和内容的来源,签署其容器映像。

  • TUF(theupdateframework.github.io):The Update Framework(TUF)是一种标准,允许软件产品通过使用加密密钥在安装和更新过程中保护自己。它是由纽约大学工程学院开发的。

  • Vitess(vitess.io):自 2011 年以来,Vitess 一直是 YouTube 的 MySQL 数据库基础设施的核心组件。它是一个通过分片水平扩展 MySQL 的集群系统。

  • CoreDNS(coredns.io):这是一个小巧、灵活、可扩展且高度优化的 DNS 服务器,使用 Go 语言编写,并从头开始设计,可以在运行数千个容器的基础设施中运行。

  • NATS(nats.io):这里有一个为运行微服务或支持物联网设备的架构设计的消息传递系统。

  • Linkerd(linkerd.io):由 Twitter 开发,Linkerd 是一个服务网格,旨在扩展并处理每秒数万个安全请求。

  • Helm(www.helm.sh):针对 Kubernetes 构建,Helm 是一个软件包管理器,允许用户将其 Kubernetes 应用程序打包成易于分发的格式,并迅速成为标准。

  • Rook (rook.io):目前,Rook 正处于早期开发阶段,专注于为 Kubernetes 上的 Ceph(Red Hat 的分布式存储系统)提供编排层。最终,它将扩展以支持其他分布式块和对象存储系统。

我们在本书的各个章节中使用了其中一些项目,我相信其他项目也会引起您的兴趣,因为您正在寻找解决诸如路由到您的容器和监视您的应用程序在您的环境中的问题。

CNCF 景观

CNCF 提供了一个交互式地图,显示了他们和他们成员管理的所有项目,网址为landscape.cncf.io/。以下是其中一个关键要点:

您正在查看 590 张卡片,总共有 1,227,036 颗星星,市值为 6.52 万亿美元,融资为 16.3 亿美元。

虽然我相信您会同意这些数字非常令人印象深刻,但这有什么意义呢?多亏了 CNCF 的工作,我们有了一些项目,比如 Kubernetes,它们为跨多个云基础设施提供了一套标准化的工具、API 和方法,还可以在本地和裸金属服务上提供构建块,让您创建和部署自己的高可用、可扩展和高性能的容器和微服务应用程序。

摘要

我希望本章让您对您的容器之旅中可以采取的下一步有所了解。我发现,虽然简单地使用这些服务很容易,但通过成为围绕各种软件和项目形成的大型、友好和热情的开发人员和其他用户社区的一部分,您可以获得更多收益,这些人和您一样。

这种社区和合作的意识得到了云原生计算基金会的进一步加强。这将大型企业聚集在一起,直到几年前,他们不会考虑与其他被视为竞争对手的企业在大型项目上进行公开合作。

第十五章:评估

第一章,Docker 概述

  1. Docker 商店:store.docker.com/

  2. $ docker image pull nginx

  3. Moby 项目

  4. 七个月

  5. $ docker container help

第二章,构建容器映像

  1. 错误;它用于向图像添加元数据

  2. 您可以附加CMDENTRYPOINT,但不能覆盖周围

  3. 正确

  4. 快照失败的容器,以便您可以在离开 Docker 主机时进行审查

  5. EXPOSE指令用于在容器上公开端口,但不会映射主机上的端口

第三章,存储和分发图像

  1. 错误;还有 Docker 商店

  2. 这允许您在上游 Docker 图像更新时自动更新您的 Docker 图像

  3. 是的,它们是(如在本章的示例中所示)

  4. 正确;如果您使用命令行登录,您已登录到 Docker for Mac 和 Docker for Windows

  5. 您将按名称删除它们,而不是使用图像 ID

  6. 端口5000

第四章,管理容器

  1. -a--all

  2. 错误;情况正好相反

  3. 当您按下Ctrl + C时,您将返回到终端;但是,保持容器活动的进程仍在运行,因为我们已经从进程中分离出来,而不是终止它

  4. 错误;它在指定的容器内生成一个新进程

  5. 您将使用--network-alias [别名]标志

  6. 运行docker volume inspect [卷名称]将为您提供有关卷的信息

第五章,Docker Compose

  1. YAML,或 YAML 不是标记语言

  2. restart标志与--restart标志相同

  3. 错误;您可以使用 Docker Compose 在运行时构建图像

  4. 默认情况下,Docker Compose 使用存储 Docker Compose 文件的文件夹的名称

  5. 您可以使用-d标志启动容器的分离模式

  6. 使用docker-compose config命令将公开 Docker Compose 文件中的任何语法错误

  7. Docker App 将您的 Docker Compose 文件捆绑到一个小的 Docker 图像中,可以通过 Docker Hub 或其他注册表共享,Docker App 命令行工具可以从图像中包含的数据渲染工作的 Docker Compose 文件

第六章,Windows 容器

  1. 您可以使用 Hyper-V 隔离在最小的 hypervisor 中运行容器

  2. 命令是docker inspect -f "{{ .NetworkSettings.Networks.nat.IPAddress }}” [容器名称]

  3. 错误;在管理 Windows 容器时,您需要运行的 Docker 命令没有区别

第七章,Docker Machine

  1. 使用--driver标志

  2. 错误;它会给您命令;相反,您需要运行eval $(docker-machine env my-host)

  3. Docker Machine 是一个命令行工具,可用于以简单和一致的方式在许多平台和技术上启动 Docker 主机

第八章,Docker Swarm

  1. 错误;独立的 Docker Swarm 不再受支持或被视为最佳实践

  2. 您需要 Docker Swarm 管理器的 IP 地址,以及用于对抗您的管理器进行身份验证的令牌

  3. 您将使用docker node ls

  4. 您将添加--pretty标志

  5. 您将使用docker node promote [node name]

  6. 您将运行docker service scale cluster=[x] [service name],其中[x]是您要扩展的容器数量

第九章,Docker 和 Kubernetes

  1. 错误;您始终可以看到 Kubernetes 使用的图像

  2. dockerkube-system命名空间

  3. 您将使用kubectl describe --namespace [NAMESPACE] [POD NAME]

  4. 您将运行kubectl create -f [FILENAME OR URL]

  5. 端口8001

  6. 它被称为 Borg

第十章,在公共云中运行 Docker

  1. 错误;他们启动 Docker Swarm 集群

  2. 在使用 Amazon Fargate 时,您无需启动 Amazon EC2 实例来运行您的 Amazon ECS 集群

  3. 容器选项列在 Azure Web 应用程序服务下

  4. 使用命令kubectl create namespace sock-shop

  5. 通过运行kubectl -n sock-shop describe services front-end-lb

第十一章,Portainer - Docker 的 GUI

  1. 路径为/var/run/docker.sock

  2. 端口是9000

  3. 错误;应用程序有自己的定义。在运行 Docker Swarm 时,您可以使用 Docker Compose 并启动一个堆栈

  4. 正确;所有统计数据都是实时显示的

第十二章,Docker 安全

  1. 您将添加--read-only标志;或者,如果您想使卷为只读,您将添加:ro

  2. 在理想的世界中,您只会在每个容器中运行一个进程

  3. 通过运行 Docker Bench Security 应用程序

  4. Docker 的套接字文件,可以在/var/run/docker.sock找到;而且,如果您的主机系统正在运行 Systemd,则在/usr/lib/systemd

  5. 错误;Quay 扫描公共和私有图像

第十三章,Docker 工作流

  1. nginx(web)容器提供网站;WordPress(WordPress)容器运行传递给 nginx 容器的代码

  2. wp容器运行一个进程,一旦运行就存在

  3. cAdvisor仅保留五分钟的度量标准

  4. 您将使用docker-compose down --volumes --rmi all

posted @ 2024-05-06 18:37  绝不原创的飞龙  阅读(9)  评论(0编辑  收藏  举报