Docker-扩展指南-全-
Docker 扩展指南(全)
原文:
annas-archive.org/md5/662046f678252f1c36403502957951fd
译者:飞龙
前言
在过去的几年里,Docker 已成为最令人兴奋的新技术之一。无论是企业公司还是初创公司,都纷纷采纳了这一工具。
多个第一方和第三方工具已被开发用于扩展核心 Docker 功能。本书将指导你安装、配置和使用这些工具,并帮助你了解哪些工具最适合某项任务。
本书内容简介
第一章,扩展 Docker 入门,讨论了 Docker 及其解决的一些问题。我们还将讨论如何通过扩展核心 Docker 引擎来获得更多功能。
第二章,引入第一方工具,介绍了 Docker 提供的工具,用于与核心 Docker 引擎协同工作。这些工具包括 Docker Toolbox、Docker Compose、Docker Machine 和 Docker Swarm。
第三章,存储插件,介绍了 Docker 插件。我们将从 Docker 自带的默认存储插件开始,接着介绍三个第三方插件。
第四章,网络插件,解释了如何在多个 Docker 主机间扩展容器网络,无论是本地部署还是在公共云中。
第五章,构建你自己的插件,介绍了如何最佳地编写自己的 Docker 存储或网络插件。
第六章,扩展你的基础设施,介绍了如何使用一些成熟的 DevOps 工具来部署和管理你的 Docker 主机和容器。
第七章,调度器分析,讨论了如何部署 Kubernetes、Amazon ECS 和 Rancher,跟随前面的章节内容。
第八章,安全性、挑战与结论,帮助解释从何处部署 Docker 镜像的安全隐患,并回顾前面章节中讨论过的各种工具及其最佳应用场景。
本书所需内容
你需要一台能够运行 VirtualBox(www.virtualbox.org/
)的 OS X 或 Windows 笔记本电脑或桌面电脑,并且能够访问 Amazon Web Service 和 DigitalOcean 账户,且拥有启动资源的权限。
本书适合谁阅读
本书面向开发人员和系统管理员,他们觉得基础的 Docker 安装已经无法满足需求,想通过扩展核心 Docker 引擎的功能来进一步优化配置,以应对不断变化的业务需求。
约定
在本书中,您将看到多种文本样式,用以区分不同种类的信息。以下是这些样式的几个示例以及它们的含义说明。
文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 账号等,均以如下方式呈现:“安装完成后,您应该能够通过运行 Docker hello-world
容器来检查是否一切正常。”
代码块如下所示:
### Dockerfile
FROM php:5.6-apache
MAINTAINER Russ McKendrick <russ@mckendrick.io>
ADD index.php /var/www/html/index.php
当我们希望引起您对代码块中特定部分的注意时,相关的行或项目会以粗体显示:
version: '2'
services:
wordpress:
container_name: "my-wordpress-app"
image: wordpress
ports:
- "80:80"
environment:
- "WORDPRESS_DB_HOST=mysql.weave.local:3306"
- "WORDPRESS_DB_PASSWORD=password"
- "constraint:node==chapter04-01"
任何命令行输入或输出都会按如下方式写出:
curl -sSL https://get.docker.com/ | sh
新术语和重要单词以粗体显示。您在屏幕上看到的单词,例如在菜单或对话框中,都会像这样出现在文本中:“要继续安装的下一步,请点击继续。”
注意
警告或重要提示将以如上所示的框呈现。
提示
提示和技巧将以这样的方式呈现。
读者反馈
我们始终欢迎读者反馈。请告诉我们您对本书的看法——喜欢或不喜欢的地方。读者反馈对我们非常重要,它帮助我们开发出您真正能从中受益的书籍。
要向我们发送一般反馈,只需通过电子邮件 <feedback@packtpub.com>
联系我们,并在邮件主题中提到书籍标题。
如果您在某个主题上拥有专长,并且有兴趣撰写或贡献书籍,请参考我们的作者指南,网址为 www.packtpub.com/authors。
客户支持
现在,您已经成为一本 Packt 书籍的自豪拥有者,我们为您提供了许多帮助您最大化购买价值的资源。
下载示例代码
您可以从 www.packtpub.com
账户中下载本书的示例代码文件。如果您是从其他地方购买的此书,您可以访问 www.packtpub.com/support
并注册,文件会直接通过电子邮件发送给您。
您可以通过以下步骤下载代码文件:
-
使用您的电子邮件地址和密码登录或注册我们的网站。
-
将鼠标指针悬停在顶部的支持标签上。
-
点击代码下载和勘误。
-
在搜索框中输入书名。
-
选择您要下载代码文件的书籍。
-
从下拉菜单中选择您购买此书的来源。
-
点击代码下载。
您还可以通过点击书籍网页上的代码文件按钮来下载代码文件。您可以通过在搜索框中输入书名来访问此页面。请注意,您需要登录您的 Packt 账户。
文件下载完成后,请确保使用最新版本的工具解压或提取文件夹:
-
WinRAR / 7-Zip for Windows
-
Zipeg / iZip / UnRarX for Mac
-
适用于 Linux 的 7-Zip / PeaZip
本书的代码包也托管在 GitHub 上,链接为 github.com/PacktPublishing/ExtendingDocker
。我们还提供了其他来自我们丰富图书和视频目录的代码包,您可以在 github.com/PacktPublishing/
查看。快去看看吧!
勘误
尽管我们已尽力确保内容的准确性,但错误有时还是会发生。如果您在我们的书籍中发现错误——可能是文本或代码中的错误——我们将非常感激您能报告此问题。这样,您不仅可以帮助其他读者避免困扰,还能帮助我们改进本书的后续版本。如果您发现任何勘误,请通过访问 www.packtpub.com/submit-errata
,选择您的书籍,点击 勘误提交表单 链接,输入勘误的详细信息。经验证后,您的勘误提交将被接受,并上传到我们的网站或添加到该书籍的勘误列表中。
要查看之前提交的勘误, 请访问 www.packtpub.com/books/content/support
,并在搜索框中输入书名。相关信息将显示在 勘误 部分。
盗版
网络上的版权材料盗版问题是各类媒体的持续问题。在 Packt,我们非常重视保护我们的版权和许可证。如果您在互联网上发现我们作品的任何非法复制版本,请立即提供其位置地址或网站名称,以便我们采取措施。
如果您发现涉嫌侵权的内容,请通过<copyright@packtpub.com>
联系我们,并附上涉嫌盗版材料的链接。
感谢您帮助保护我们的作者以及我们提供有价值内容的能力。
问题
如果您在本书的任何方面遇到问题,可以通过<questions@packtpub.com>
联系我们,我们将尽力解决问题。
第一章:扩展 Docker 简介
在本章中,我们将讨论以下主题:
-
为什么 Docker 会被整个行业广泛接受
-
一个典型的容器生命周期是什么样的?
-
接下来的章节将涵盖哪些插件和第三方工具?
-
接下来章节你需要什么?
Docker 的崛起
很少有技术能够在整个行业中如此广泛地被采纳。自 2013 年 3 月首次公开发布以来,Docker 不仅赢得了像你我这样的最终用户的支持,还得到了 Amazon、Microsoft 和 Google 等行业领袖的支持。
Docker 目前在其网站上使用以下句子来描述为什么你会想要使用它:
"Docker 提供了一套集成技术套件,使开发和 IT 运维团队能够在任何地方构建、交付和运行分布式应用程序。"
有一个基于灾难女孩照片的网络迷因,总结了为什么这样一个看似简单的解释实际上非常重要:
尽管 Docker 的描述听起来简单,但实际上,对于大多数开发人员和 IT 运维团队来说,它一直是一个理想的工具,可以确保一个应用程序在以下三个主要阶段的生命周期中始终如一地运行:
-
开发
-
预发布和预生产
-
生产
为了说明在 Docker 出现之前为什么这是一个问题,让我们看看这些服务传统上是如何配置和部署的。人们通常倾向于使用专用机器和虚拟机器的组合。让我们更详细地看一下这些。
尽管可以使用配置管理工具,如 Puppet,或者像 Ansible 这样的编排工具来保持服务器环境之间的一致性,但在服务器和开发者的工作站之间强制执行这些一致性仍然是困难的。
专用机器
传统上,这些是单个硬件设备,经过配置以运行你的应用程序。虽然这些应用程序可以直接访问硬件,但你受限于可以安装在专用机器上的二进制文件和库,因为它们必须在整个机器上共享。
为了说明 Docker 解决的一个潜在问题,假设你有一台单独的专用服务器运行你的 PHP 应用程序。当你最初部署这台专用机器时,构成你电子商务网站的三个应用程序都使用 PHP 5.6,因此在兼容性方面没有问题。
你的开发团队一直在缓慢地处理这三个 PHP 应用程序。你已经将它部署到主机上,以便让它们与 PHP 7 兼容,因为这将大大提升它们的性能。然而,他们始终无法解决 App2 中的一个错误,这意味着在用户将物品添加到购物车时,应用程序将无法在 PHP 7 下运行,并会崩溃。
如果你只有一个主机运行三款应用程序,直到开发团队解决 App2 的错误,你将无法从 PHP 5.6 升级到 PHP 7,除非你做以下其中一项:
-
部署一个运行 PHP 7 的新主机,并将 App1 和 App3 迁移到该主机上;这可能既费时又昂贵
-
部署一个运行 PHP 5.6 的新主机,并将 App2 迁移到该主机上;同样,这也可能既费时又昂贵。
-
等待直到错误被修复;从 PHP 5.6 升级到 PHP 7 所带来的性能提升可能会增加销售额,但目前没有修复的预计时间。
如果你选择前两个选项,你还需要确保新专用主机要么与开发者的 PHP 7 环境匹配,要么按完全相同的方式配置新专用主机;毕竟,你不希望因为机器配置不当而引入更多问题。
虚拟机
解决前面提到的情况的一种方法是,通过安装一个如以下的虚拟化管理程序,将专用主机的资源切割并提供给应用程序:
-
KVM:
www.linux-kvm.org/
-
XenSource:
www.xenproject.org/
-
VMware vSphere:
www.vmware.com/uk/products/vsphere-hypervisor/
一旦安装完成,你就可以在每个不同的虚拟主机上安装你的二进制文件和库,并在每个虚拟主机上安装你的应用程序。
回到专用主机部分提到的场景,你将能够在安装了 App1 和 App2 的虚拟机上升级到 PHP 7,同时在修复开发工作进行时保持 App2 不变且可正常工作。
很好,那么问题在哪?从开发者的角度来看,根本没有问题,因为他们的应用程序可以在适合他们的 PHP 版本下运行;然而,从 IT 运维的角度来看:
-
更多的 CPU、RAM 和磁盘空间:每个虚拟机都将需要额外的资源,因为运行三个来宾操作系统以及三款应用程序的开销需要考虑。
-
更多的管理:现在,IT 运维需要修补、监控并维护四台机器,包括专用主机和三台虚拟机,而之前他们只需要维护一台专用主机。
如前所述,你还需要确保托管应用程序的三个虚拟机的配置与开发人员在开发过程中使用的配置一致;同样,你不希望因为部门之间的配置和流程偏差而引入额外问题。
专用主机与虚拟机
下图展示了典型的专用主机与虚拟机主机配置方式:
如你所见,二者之间的最大区别非常明显。你正在进行资源利用与能够使用不同二进制文件/库运行应用之间的权衡。
容器
现在我们已经涵盖了传统应用部署的方式。接下来,让我们看看 Docker 为这个过程带来了什么。
回到我们的场景,三款应用运行在同一台主机上。你可以在主机上安装 Docker,然后将每个应用部署为容器,这样既能享受虚拟机的好处,又大大减少了占用空间,也就是完全去除了虚拟机管理程序和客户操作系统,取而代之的是一个直接与主机内核对接的精简界面。
这为 IT 运维和开发团队带来的优势如下:
-
低开销:如前所述,IT 运维团队的资源和管理负担较低
-
开发团队提供容器:与其依赖 IT 运维团队为每个应用配置三个不同的环境,将它们与开发环境对接,不如直接将容器交给运维团队进行生产部署。
从下面的图示可以看到,应用与主机操作系统之间的层次已被简化:
所有这些意味着,开篇提到的“灾难女孩”表情包应该不再适用了,因为开发团队已经将应用与所有配置、二进制文件和库一起打包成容器交给运维团队,这意味着如果在开发环境中能正常运行,那么在生产环境中也能运行。
这看起来可能太好了,甚至让人怀疑,老实说,确实有一个“但是”。对于大多数 web 应用或已经预编译的静态二进制文件应用,你应该不会遇到问题。
然而,由于 Docker 与底层主机共享资源,如内核版本,如果你的应用需要编译或依赖某些仅与共享资源兼容的库,那么你必须在相同的操作系统上部署容器,在某些情况下,甚至需要相同的硬件。
Docker 通过收购一家名为 Unikernel Systems 的公司,试图解决这个问题,收购发生在 2016 年 1 月。到写本书时,关于 Docker 如何将这一技术整合到其核心产品中的细节还不多。如果你想了解更多关于这一技术的信息,可以访问 blog.docker.com/2016/01/unikernel/
。
每个人都应该使用 Docker 吗?
那么,真的这么简单吗?每个人应该停止使用虚拟机,改用容器吗?
2014 年 7 月,Wes Felter、Alexandre Ferreira、Ram Rajamony 和 Juan Rubio 发表了一篇名为《虚拟机与 Linux 容器的性能更新比较》的 IBM 研究报告,并得出结论:
“虚拟机和容器都是成熟的技术,受益于十年的硬件和软件渐进式优化。通常,Docker 在我们测试的所有案例中都等同于或超越了 KVM 性能。我们的结果显示,KVM 和 Docker 都对 CPU 和内存性能引入了微不足道的开销(除了极端情况下)。对于 I/O 密集型工作负载,这两种虚拟化方式都应该谨慎使用。”
接着,它提到以下内容:
“虽然容器本身几乎没有开销,但 Docker 并非没有性能陷阱。Docker 卷比存储在 AUFS 中的文件有明显更好的性能。Docker 的 NAT 也会为高数据包率的工作负载引入开销。这些特性代表了管理简便性与性能之间的权衡,应根据具体情况进行考虑。”
这份完整的 12 页报告,详细对比了我们讨论过的传统技术与容器,可以从以下网址下载:
在 IBM 研究报告发布不到一年后,Docker 为其生态系统引入了插件。我看到的其中一个最佳描述来自 Docker 软件工程师 Jessica Frazelle,她形容这个发布就像是“包含电池,但可以替换”,意味着核心功能可以轻松地被第三方工具替代,这些工具可以用来解决 IBM 研究报告中的结论。
在撰写本书时,Docker 目前支持卷和网络驱动插件。未来将会增加更多插件类型,以将更多 Docker 核心功能暴露给第三方。
容器的生命周期
在我们查看各种插件和扩展 Docker 的方式之前,应该先了解容器的典型生命周期。
使用前一节中的示例,让我们启动官方的 PHP 5.6 容器,然后将其替换为官方的 PHP 7.0 容器。
安装 Docker
在我们启动容器之前,需要先让 Docker 正常运行;幸运的是,这个过程很简单。
在接下来的章节中,我们将使用 Docker Machine 来引导我们的 Docker 环境;但是现在,让我们先在云服务器上快速安装 Docker。
以下指令适用于托管在公共云上的 Ubuntu 14.04 LTS 或 CentOS 7 实例,示例如下:
-
Digital Ocean:
www.digitalocean.com/
-
亚马逊云服务:
aws.amazon.com/
-
微软 Azure:
azure.microsoft.com/
-
VMware vCloud Air:
vcloud.vmware.com/
你也可以尝试在本地运行一个虚拟机,使用以下方法:
-
Vagrant:
www.vagrantup.com/
-
Virtualbox:
www.virtualbox.org/
-
VMware Fusion:
www.vmware.com/uk/products/fusion/
-
VMware Workstation:
www.vmware.com/uk/products/workstation/
我将使用托管在 Digital Ocean 上的 CentOS 7 服务器,因为它方便快捷,可以快速启动并随时终止。
一旦你的服务器启动并运行,你可以通过运行以下命令从官方 Yum 或 APT 仓库安装 Docker:
curl -sSL https://get.docker.com/ | sh
如果你像我一样运行的是 CentOS 7 服务器,你需要确保该服务正在运行。为此,请输入以下命令:
systemctl start docker
安装完成后,你应该能够通过运行 Docker hello-world
容器来检查一切是否按预期工作,输入以下命令:
docker run hello-world
一旦你安装了 Docker,并确认它按预期运行,你可以通过运行以下命令来下载官方 PHP 5.6 和 PHP 7.0 镜像的最新版本:
docker pull php:5.6-apache && docker pull php:7.0-apache
有关官方 PHP 镜像的更多信息,请参阅 Docker Hub 页面:hub.docker.com/_/php/
。
现在我们已经下载了镜像,是时候部署我们的应用程序了,因为我们保持得非常简单;我们将要部署的仅仅是一个 phpinfo
页面,这将确认我们正在运行的 PHP 版本,以及容器环境的其他详细信息:
mkdir app1 && cd app1
echo "<?php phpinfo(); ?>" > index.php
现在,index.php 文件已经到位。让我们通过运行以下命令来启动 PHP 5.6 容器:
docker run --name app1 -d -p 80:80 -it -v "$PWD":/var/www/html php:5.6-apache
这将启动一个 app1
容器。如果你输入你的服务器实例的 IP 地址或解析到该地址的域名,你应该看到一个页面,显示你正在运行 PHP 5.6:
现在你已经成功运行了 PHP 5.6,让我们将其升级到 PHP 7。传统上,这意味着使用第三方 YUM 或 APT 仓库安装一组新的软件包;根据经验,这个过程可能有点不稳定,取决于你安装的 PHP 先前版本的软件包的兼容性。
幸运的是,在我们的案例中,我们正在使用 Docker,所以我们所需要做的就是终止 PHP 5.6 容器,并替换为运行 PHP 7 的容器。在这个过程中,任何时候你都可以使用以下命令检查正在运行的容器:
docker ps
这将打印出正在运行的容器列表(如本节末尾截图所示)。要停止并移除 PHP 5.6 容器,请运行以下命令:
docker rm -f app1
一旦容器终止,运行以下命令来启动 PHP 7 容器:
docker run --name app1 -d -p 80:80 -it -v "$PWD":/var/www/html php:7.0-apache
如果你返回浏览器中的 phpinfo
页面,你将看到现在它正在运行 PHP 7:
要终止 PHP 7 容器,请再次运行 docker rm
命令:
docker rm -f app1
前面终端会话的完整副本可以在以下截图中找到:
这个例子可能展示了 Docker 的最大优势,那就是能够在存储在本地存储上的代码库之上,快速而一致地启动容器。不过,还是存在一些限制。
有什么限制?
所以,在前面的例子中,我们启动了两个容器,每个容器运行不同版本的 PHP,并在我们的(极其简单的)代码库上运行。虽然它演示了启动容器有多简单,但也暴露了一些潜在问题和单点故障。
首先,我们的代码库存储在主机机器的文件系统中,这意味着我们只能在单台主机上运行容器。如果主机因任何原因宕机怎么办?
在纯 Docker 安装中,有几种方法可以解决这个问题。第一种方法是使用官方 PHP 容器作为基础,构建我们自己的自定义镜像,这样我们就可以将代码和 PHP 一起打包。为此,请在包含以下内容的 app1
目录中添加 Dockerfile
:
### Dockerfile
FROM php:5.6-apache
MAINTAINER Russ McKendrick <russ@mckendrick.io>
ADD index.php /var/www/html/index.php
我们还可以使用以下命令来构建自定义镜像:
docker build -t app1:php-5.6 .
当您运行构建命令时,您将看到以下输出:
一旦构建好镜像,您可以将其作为私有镜像推送到 Docker Hub 或您自己的自托管私有注册中心;另一种选择是将自定义镜像导出为 .tar
文件,然后将其复制到每个需要运行您自定义 PHP 容器的实例中。
为此,您需要运行 Docker save 命令:
docker save app1:php-5.6 > ~/app1-php-56.tar
这将创建我们自定义镜像的副本,从以下终端输出中可以看到,镜像大小应约为 482M
的 tar 文件:
现在我们已经将镜像作为 tar 文件保存,我们可以将其复制到其他主机。一旦复制了 tar 文件,您需要运行 Docker load 命令将其导入到第二台主机上:
docker load < ~/app1-php-56.tar
然后我们可以通过运行以下命令启动一个包含我们代码的容器:
docker run --name app1 -d -p 80:80 -it app1:php-5.6
以下终端输出展示了在导入并运行我们自定义容器时应该看到的内容:
到目前为止,进展如何?好吧,有是有,不是也不是。
我们可以将代码库轻松地添加到自定义镜像中,然后通过以下方式之一将镜像发布:
-
官方 Docker Hub
-
我们自己的私有注册中心
-
将镜像导出为 tar 文件并复制到其他主机
然而,对于那些处理不断变化的数据的容器,比如数据库,情况如何呢?我们的数据库选项是什么?
假设我们运行的是来自 hub.docker.com/_/mysql/
的官方 MySQL 容器,我们可以将数据库存储目录(即 /var/lib/mysql/
)从主机挂载,但这可能会导致挂载后容器中文件的权限问题。
为了避免这种情况,我们可以创建一个数据卷,包含我们 /var/lib/mysql/
目录的副本,这意味着我们将数据库与容器分离,这样我们就可以停止、启动,甚至替换 MySQL 容器,而不会丢失数据。
然而,这种方法使我们只能将 MySQL 容器运行在单台主机上,这会成为一个大的单点故障。
如果资源允许,我们可以确保承载 MySQL 容器的主机具有多重冗余,例如多个硬盘的 RAID 配置,能够承受多个硬盘故障。我们可以使用多个电源供应单元(PSU),由不同的电源线路供电,这样即使某一电源线路出现问题,主机仍能保持在线。
主机上的网络也可以做到同样的方式,网络接口卡(NIC)连接到不同的交换机,并由不同的电源线路和网络提供商供电。
尽管这种做法为我们提供了很多冗余,但我们仍然只有一台主机,而这台主机的成本也变得非常高,因为所有这些冗余——包括多个硬盘、网络和电源线路——都会增加我们原本已经支付的主机费用。
那么,解决方案是什么呢?
这就是 Docker 扩展的作用,尽管 Docker 默认不支持在主机服务器之间移动卷,但我们可以插入一个文件系统扩展,使我们能够在主机之间迁移卷,或从共享文件系统(例如 NFS)挂载卷。
如果我们的 MySQL 容器已经设置好,一旦主机出现问题,我们不会遇到问题,因为数据卷可以挂载到另一台主机上。
一旦我们挂载了卷,容器可以继续从上次停止的地方运行,因为我们把数据保存在一个正在复制到新主机的卷中,或者通过来自某个冗余存储(例如 SAN)的文件系统共享进行访问。
网络方面也可以做同样的处理。正如 IBM 研究报告总结中提到的,基于 Docker NAT 的网络在性能方面可能成为瓶颈,并且在设计容器基础设施时也可能会遇到问题。如果这是一个问题,那么可以添加网络扩展,将容器的网络卸载到软件定义网络(SDN)中,而不是让 Docker 核心通过主机上的 iptables 使用 NAT 和桥接接口来管理网络。
一旦你将这种功能引入到 Docker 的核心中,就可能会变得很难管理你的容器。在理想的情况下,你不应该担心容器运行在哪个主机上,或者如果容器/主机因某种原因停止响应,那么你的容器将不会自动在容器网络中的另一个主机上弹出并继续上次的工作。
在本书的后续章节中,我们将探讨如何实现本章讨论的一些概念,并查看由 Docker 编写的工具,这些工具旨在与核心 Docker 引擎一起运行。虽然这些工具的功能可能不如我们在后续章节中讨论的某些工具那样强大,但它们为我们在创建 Docker 主机集群并协调容器时所要覆盖的核心概念提供了一个很好的入门。
在我们查看这些工具之后,我们将探讨卷和网络插件。我们将介绍一些更著名的插件,这些插件为 Docker 核心添加了功能,使我们能够拥有一个更具冗余性的平台。
一旦我们对预编写的插件进行实践操作后,我们将探讨编写你自己插件的最佳方法。
在书的最后几章,我们将开始关注一些第三方工具,这些工具允许你配置、部署并管理容器的整个生命周期。
总结
在本章中,我们探讨了 Docker 及其解决的一些问题。我们还讨论了 Docker 核心引擎可以扩展的几种方式,以及通过扩展 Docker 获得的额外功能可以解决的一些问题。
在下一章中,我们将查看 Docker 提供的四种不同工具,使得部署、管理和配置 Docker 主机实例和容器尽可能简单和无缝。
第二章:引入第一方工具
Docker 提供了多种工具来扩展核心 Docker 引擎之外的功能。在本章中,您将逐步了解如何安装、配置和运行以下工具:
-
Docker Toolbox
-
Docker Machine
-
Docker Swarm
-
Docker Compose
这些工具虽然没有我们将在接下来的章节中使用的高级工具功能强大,但它们将作为一个良好的入门,帮助您理解如何为核心 Docker 引擎添加额外功能,以及容器部署和编排的概念,我们将在本书后面更多地探讨这些内容。
Docker Toolbox
在我们开始了解如何使用其他三种工具之前,应该先看一下如何在本地机器上安装它们。在上一章中,我们下载了 Docker 提供的脚本,并通过 bash 快速配置了官方的 Docker YUM 或 APT 仓库(具体取决于您使用的操作系统),我们执行的命令如下:
curl -sSL https://get.docker.com/ | sh
如果您已经在某个云服务或本地虚拟机上运行了基于 Linux 的服务器,这将非常有用;但是,如果您想在非 Linux 操作系统(如 Mac OSX 或 Windows)上安装 Docker,怎么办呢?
提示
始终检查源。最好的做法是检查您将要下载并安装的 bash 脚本的来源;在我们的案例中,您可以通过浏览器访问 get.docker.com/
来检查。
在我们查看 Docker 提供的工具之前,我们应该先回答一个问题:为什么?
为什么要在本地安装 Docker?
那么,为什么我们需要在非 Linux 系统上安装 Docker Toolbox、Compose、Machine 和 Swarm 呢?首先,您需要记住,Docker 本质上是一个针对基于 Linux 内核的技术的 API,例如 run (github.com/opencontainers/runc
) 和 LXC (linuxcontainers.org
),因此虽然您无法在 Mac OS X 或 Windows 系统上启动容器,但您可以与 Linux 机器上的 Docker 安装进行交互。
能够从本地机器与 Docker 进行交互,意味着您可以跨多个主机启动并与容器进行交互,这些主机可以托管在公共云/托管服务上,也可以本地托管在虚拟机中。
幸运的是,Docker 提供了便捷的方式,帮助您在本地机器上安装 Docker 和本章要介绍的其他三种服务。
安装 Docker Toolbox
Docker 提供了一个名为 Docker Toolbox 的全球安装程序,它使得安装以下软件变得尽可能简单:
-
Docker 客户端
-
Docker Machine
-
Docker Compose
-
Docker Kitematic
-
虚拟机 VirtualBox
要开始,您需要运行一个安装有 Mac OS X 10.8+ 或 Windows 7+ 的机器。在我的例子中,我使用的是 Mac OS X 10.11(El Capitan);Mac OS X 和 Windows 的安装程序差别很小:
-
首先,要开始,您需要从 Docker 网站下载安装程序。您可以在
www.docker.com/docker-toolbox/
找到适用于您操作系统的可执行文件下载链接。 -
下载完安装程序后,您可以通过双击它来启动。接下来会显示一系列屏幕和安装选项。
第一个屏幕是欢迎页面,确认您正在运行的工具箱版本。如果您是从前面截图中的页面下载的,那么您将始终拥有最新版本:
-
要继续安装的下一步,请点击继续。
-
下一屏幕将详细介绍将要安装的包以及它们将安装的位置。还有一个框,如果勾选此框,它将收集您安装 Docker Toolbox 的机器数据,对其进行匿名处理,然后提交给 Docker。
这些信息有助于 Docker 了解您的软件安装在什么类型的机器上,还会反馈您在运行安装程序时遇到的任何错误:
我总是建议保持勾选此框,因为这有助于 Docker 改进产品并提升未来版本安装程序的用户体验。
-
要继续安装的下一步,点击继续。
-
下一屏幕将允许您选择安装工具的磁盘位置。在大多数情况下,您应该使用默认设置,除非您在多个驱动器上运行应用程序:
-
要继续安装的下一步,再次点击继续按钮。
-
对于大多数人来说,标准安装就足够了;但是,如果不打算安装某些工具,您可以点击自定义按钮。您必须安装的两个工具是 Docker 客户端和 Docker 机器。
因为我想安装所有工具,所以我选择了标准安装:
-
一旦您选择了标准或自定义安装,您可以点击安装按钮进行安装。
-
安装本身只需要几分钟,在此期间,您将看到安装程序正在执行的任务反馈:
-
安装完成后,点击继续按钮。
由于运行安装程序也会作为已安装组件的升级程序,它将检查服务管理的任何文件(例如各种工具使用的虚拟机映像)是否需要更新。
根据更新的大小以及数据量的不同,这个过程可能需要几分钟。
该过程仅适用于更新,因此如果你像我一样进行了全新安装,则此部分将被跳过。
-
现在工具已经安装完成,你将获得启动Docker 快速启动终端或 Kitematic 的选项。为了本书的目的,我们将通过点击继续按钮跳过此屏幕:
-
如果一切顺利,你将看到一条消息,确认安装已经完成,你可以点击关闭按钮退出安装程序:
现在,你已经在本地机器上安装了所有工具,可以继续本章及整本书的内容了。
在我们开始查看各个工具之前,我们需要配置 Docker 代理。为此,请运行Docker 快速启动终端应用程序。如果你安装了多个终端模拟器,它会弹出提示,询问你想使用哪个终端;我更喜欢使用 Mac OS X 自带的终端,因此我选择了终端。
一旦你做出选择,一个新的终端窗口将打开,应用程序将为你配置本地的 Docker 安装:
就我而言,在启动Docker 快速启动终端应用程序时,我得到了前面的终端输出。
Docker 机器
因此,当你运行Docker 快速启动终端应用程序时,它会创建一堆证书、SSH 密钥,并配置你的用户环境以运行 Docker。它还启动了一个运行 Docker 的虚拟机。
本地开发
Docker 快速启动终端应用程序是通过 Docker 机器完成此操作的,你可以通过运行以下命令来检查该应用程序启动的机器状态:
docker-machine active
这将列出任何活动机器的名称,第一次安装 Docker 时启动的默认机器名为default
,如果你运行:
docker-machine status default
它应该会告诉你虚拟机当前正在运行。最后,你应该能够通过运行以下命令 SSH 连接到虚拟机:
docker-machine ssh default
你会注意到,当你通过 SSH 连接到虚拟机时,它正在运行 Boot2Docker 发行版。
注意
Boot2Docker 是一个极其轻量的 Linux 发行版,基于 Tiny Core Linux,它的唯一目的是运行 Docker。由于这个原因,整个发行版的大小不到 30 MB,启动大约只需五秒钟,这使得它非常适合用于本地开发机器。关于 Boot2Docker 的更多信息,请参考 boot2docker.io/
,关于 Tiny Core Linux,请参考 tinycorelinux.net/
。
当你运行这些命令时,你应该看到类似下面的终端会话:
虽然没有太多需要 SSH 进入虚拟机的必要,但由于通过工具箱安装的 Docker 客户端已被配置为连接到虚拟机上的 Docker 引擎,这意味着当你在本地运行 Docker 命令时,它会将所有请求传递给虚拟机上的 Docker 引擎。试着运行 hello-world
容器:
docker run hello-world
你应该看到以下输出:
在这一阶段,你可能会想,这一切都很好,但这并不是一个值得兴奋的工具。嗯,你错了。Docker Machine 可不止能在本地启动 Boot2Docker 虚拟机,它还有更多的“隐藏技能”。
进入云端
Docker Machine 能够连接到以下服务,创建实例,并配置你的本地 Docker 客户端,使其能够与基于云的实例进行通信。
当前支持的公共云服务提供商如下:
-
Amazon Web Services (AWS):
aws.amazon.com/
-
DigitalOcean:
www.digitalocean.com/
-
Microsoft Azure:
azure.microsoft.com/
-
Google Compute Engine:
cloud.google.com/compute/
-
Rackspace:
www.rackspace.co.uk/cloud/
-
IBM SoftLayer:
www.softlayer.com
-
Exoscale:
www.exoscale.ch/
-
VMware vCloud Air:
vcloud.vmware.com/
以下自托管平台也可以使用:
-
OpenStack:
www.openstack.org/
-
Microsoft Hyper-V:
www.microsoft.com/virtualization/
-
VMware vSphere:
www.vmware.com/uk/products/vsphere/
DigitalOcean 驱动程序
让我们开始在云中创建一些实例。首先,启动一个在 DigitalOcean 上的虚拟机。
在 DigitalOcean 中启动实例的两个先决条件是:首先需要一个 DigitalOcean 帐户,其次需要一个 API 令牌。
要注册一个 DigitalOcean 账户,请访问 www.digitalocean.com/
并点击注册按钮。登录到您的账户后,您可以通过点击顶部菜单中的API链接生成 API 令牌。
要获取您的令牌,请点击生成新令牌按钮并按照屏幕上的说明操作:
提示
您只有一次机会记录下您的令牌,请确保将其存储在安全的地方,因为任何拥有它的人都能在您的账户中启动实例。
一旦您有了令牌,您可以使用 Docker Machine 启动您的实例。为此,运行以下命令;确保将示例 API 令牌替换为您自己的:
提示
使用反斜杠:由于我们需要传递很多选项给 docker-machine
命令,我们使用反斜杠 \
将命令分成多行,这样更容易跟踪发生了什么。
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0 \
dotest
这将把一个 dotest
实例启动到您的 DigitalOcean 账户中,您将看到类似以下终端输出的内容:
如果您检查 DigitalOcean 控制面板,您现在会看到由 Docker Machine 创建的实例已列在这里:
现在,我们通过 Docker Machine 启动了两个实例,一个是本地运行的,名为default
,另一个是托管在 DigitalOcean 上的,名为dotest
。我们可以通过运行以下命令来确认这一点:
docker-machine ls
这将返回所有正在运行的机器,并确认它们的状态、IP 地址、Docker 版本和名称。还有一列显示您本地环境配置为与哪台机器通信:
在前面的示例中,我们的本地 Docker 客户端配置为与 default
实例进行通信,它是在本地运行的。现在让我们将其更改为与 DigitalOcean 实例进行交互。
为了实现这一点,您需要更改一些本地环境变量,幸运的是,Docker Machine 提供了一种简单的方法来找出这些变量并进行更改。
要了解它们,只需运行以下命令:
docker-machine env dotest
这将告诉您需要运行什么命令来从 default
机器切换到 dotest
。最棒的是,该命令本身会以可执行的方式格式化结果,因此我们再次运行该命令,但这次的输出将被执行:
eval $(docker-machine env dotest)
现在,如果您从 Docker Machine 获取列表,您会注意到 dotest
环境现在是活跃的:
现在,我们的 DigitalOcean 实例已激活,您可以在本地机器上运行 docker
命令,这些命令将会在 DigitalOcean 实例上执行。我们通过运行 hello-world 容器来测试这一点。
如果运行以下命令,你应该看到镜像下载,然后是运行 hello-world 容器的输出:
docker run hello-world
如果接着运行以下命令,你会看到几秒钟前退出的 hello-world 镜像:
docker ps –a
以下是终端输出的示例:
正如你所见,我使用 ssh
进入 DigitalOcean 实例,并运行了 docker ps -a
和 docker images
命令来演示本地运行的命令是在 DigitalOcean 实例上执行的;但这种设置的美妙之处在于,你不应该经常需要 SSH 进入实例。
你可能注意到的一件事是,我们告诉 Docker Machine 的只是我们要使用 DigitalOcean 和我们的 API 令牌;但从未告诉它在哪个区域启动实例,需要什么规格,或者要使用哪个 SSH 密钥。
Docker Machine 拥有一些合理的默认设置:
-
digitalocean-image = ubuntu-15-10-x64
-
digitalocean-region = nyc3
-
digitalocean-size = 512mb
由于我位于英国,让我们看看如何更改区域和机器规格。首先,我们应该通过运行以下命令删除 dotest
实例:
docker-machine rm dotest
这将终止在 NYC3 运行的 512mb
实例。
提示
终止不使用的实例非常重要,因为它们每小时处于活动状态都会产生费用。记住使用 Docker Machine 的关键优势之一是你可以快速启动实例,并尽可能少地进行交互。
现在我们已经删除了旧实例,让我们为 docker-machine
命令添加一些额外的标志,以在所需的区域和规格中启动新实例,我们将称新实例为 douktest
。更新后的 docker-machine create
命令现在看起来类似于以下内容(记得用你自己的 API 令牌替换示例 API 令牌):
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0 \
--digitalocean-region lon1 \
--digitalocean-size 1gb \
douktest
一旦实例部署完成,你应该看到与之前命令相似的输出,然后可以通过运行以下命令使其激活:
eval $(docker-machine env douktest)
当你进入控制面板时,你会注意到实例已在指定的区域以及所需的规格下启动:
要获取每个区域的详细信息和每个区域可用的机器类型,请运行以下命令查询 DigitalOcean API(记得替换 API 令牌):
curl -X GET -H "Content-Type: application/json" -H "Authorization: Bearer sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0" "https://api.digitalocean.com/v2/regions" | python -mjson.tool
这将输出每个区域的信息。
还有最后一件事,我们还没弄清楚 SSH 密钥的情况。每次运行 Docker Machine,都会为你启动的实例创建一个新的 SSH 密钥并上传到提供者,每个密钥都存储在你用户主目录下的 .docker
文件夹中。例如,可以通过以下命令找到 douktest
的密钥:
cd ~/.docker/machine/machines/douktest/
在这里,您还会找到用于通过实例上的 Docker 安装认证 Docker 代理的证书以及相关配置:
这涵盖了 DigitalOcean,那么其他服务呢?让我们快速看一下 Amazon Web Services,以便我们可以对不同云服务提供商的驱动程序有个大致了解。
Amazon Web Services 驱动程序
如果您还没有 Amazon Web Services 账户,应该在 aws.amazon.com/
注册一个。如果您是 AWS 新用户,您将有资格获得他们的免费层服务,详情请见 aws.amazon.com/free/
。
如果您不熟悉 AWS,建议在开始本章内容之前先阅读 Amazon 的入门指南,您可以在 docs.aws.amazon.com/gettingstarted/latest/awsgsg-intro/gsg-aws-intro.html
找到该指南。
AWS 驱动程序与 DigitalOcean 驱动程序类似,并具有一些合理的默认设置,考虑到我们不打算深入讨论如何自定义 Docker Machine 启动的 EC2 实例,我将坚持使用默认设置。对于 AWS 驱动程序,默认设置如下:
-
amazonec2-region = us-east-1 (北弗吉尼亚)
-
amazonec2-ami = ami-26d5af4c (Ubuntu 15.10)
-
amazonec2-instance-type = t2.micro
-
amazonec2-root-size = 16GB
-
amazonec2-security-group = docker-machine
在启动实例之前,我们还需要知道我们的 AWS 访问密钥和密钥对,此外,还需要知道 VPC ID,因为我们将在其中启动实例。要获取这些信息,请登录到 AWS 控制台,地址是 console.aws.amazon.com/
。
您应该已经拥有您的访问密钥和密钥 ID 的副本,因为这些是在您的 AWS 用户首次创建时生成的。如果您丢失了它们,可以通过导航到 服务 | IAM | 用户,然后选择您的用户,最后转到 安全凭证 标签页来生成一对新密钥。在那里您应该能看到一个名为 创建访问密钥 的按钮。
注意
Amazon 将 虚拟专用云(VPC)描述为让您能够在 AWS 云中配置一个逻辑上隔离的区域,在该区域内您可以启动您定义的虚拟网络中的资源。您可以完全控制您的虚拟网络环境,包括选择您自己的 IP 地址范围、创建子网、配置路由表和网络网关。
在查找 VPC ID 之前,您应该确保您处于正确的区域,可以通过确保 AWS 控制台右上角显示 N. Virginia 来验证,如果没有显示,您可以从下拉列表中选择正确的区域。
确保你处于正确的区域后,前往服务 | VPC,点击您的 VPC。你无需担心创建和配置 VPC,因为 Amazon 在每个区域都会为你提供默认 VPC。选择 VPC 后,你应该会看到类似于以下截图的内容:
记下 VPC ID,你现在应该有足够的信息通过 Docker Machine 启动实例。为此,请运行以下命令:
docker-machine create \
--driver amazonec2 \
--amazonec2-access-key JHFDIGJKBDS8639FJHDS \
--amazonec2-secret-key sfvjbkdsvBKHDJBDFjbfsdvlkb+JLN873JKFLSJH \
--amazonec2-vpc-id vpc-35c91750 \
awstest
如果一切顺利,你应该会看到类似以下的输出:
你还应该能够通过导航到服务 | EC2 | 实例来在 AWS 控制台中看到启动的 EC2 实例:
你可能已经注意到,Docker Machine 创建了安全组,并且无需我们介入就为实例分配了 SSH 密钥,这符合我们不需要成为环境配置专家的原则,Docker 实例的启动和配置都已自动完成。
在我们终止实例之前,先将本地 Docker 客户端切换为使用 AWS 实例,并启动Hello World
容器:
如你所见,一旦使用 Docker Machine 启动了实例并切换本地 Docker 客户端,你会发现本地运行 Docker 和在云服务商上运行 Docker 在使用上没有区别。
在我们开始累积费用之前,我们应该通过运行以下命令来终止我们的测试 AWS 实例:
docker-machine rm awstest
然后确认实例在 AWS 控制台中已经正确终止:
如果不这么做,EC2 实例将毫不犹豫地一直运行,每小时收取$0.013的费用,直到它被终止。
注意
请注意,这不是 Amazon 的弹性容器服务(ECS)。我们将在第七章,查看调度器中介绍 Amazon ECS。
其他考虑事项
如我们所做的示例所示,Docker Machine 是 Docker Toolbox 中的一个强大工具,它使所有技术水平的用户都能轻松启动本地或云提供商中的实例,而不需要动手配置服务器实例或本地 Docker 客户端。
本章中我们使用的示例均为启动 Boot2Docker 或 Ubuntu。Docker Machine 还支持以下操作系统:
-
Debian(8.0+):
www.debian.org/
-
Red Hat Enterprise Linux(7.0+):
www.redhat.com/
-
CentOS(7+):
www.centos.org/
-
Fedora(21+):
getfedora.org/
-
RancherOS(0.3):
rancher.com/rancher-os/
需要提到的关于 Docker Machine 的另一点是,默认情况下,它会启用崩溃报告功能。考虑到 Docker Machine 可以与许多不同的配置/环境组合一起使用,重要的是 Docker 能够收到任何问题的通知,以帮助他们改进产品。如果由于某种原因你想退出此功能,可以运行以下命令来禁用崩溃报告:
mkdir -p ~/.docker/machine && touch ~/.docker/machine/no-error-report
有关 Docker Machine 的更多信息,你可以参考官方文档:
-
Docker Machine:
docs.docker.com/machine/
-
Docker Machine Drivers:
docs.docker.com/machine/drivers/
-
Docker Machine Command Reference:
docs.docker.com/machine/reference/
Docker Swarm
现在我们已经讨论了如何使用 Docker Machine 启动单个 Docker 实例,让我们更进一步,创建一个实例集群。为此,Docker 提供了一个名为 Swarm 的工具。当部署时,它充当你的 Docker 客户端与宿主 Docker 实例之间的调度器,根据调度规则决定在哪里启动容器。
创建本地集群
首先,我们将使用 Docker Machine 在本地通过 VirtualBox (www.virtualbox.org
) 创建一个集群,VirtualBox 是 Docker Toolbox 的一部分。首先,我们将启动一个虚拟机来生成发现令牌。为此,运行以下命令:
docker-machine create -d virtualbox discover
然后配置你的 Docker 客户端以使用新创建的本地实例:
eval "$(docker-machine env discover)"
你可以通过运行docker-machine ls
命令并确保 discover
在活动列中有星号,来检查你的 Docker 客户端是否配置为使用 discover
实例。
最后,你可以通过运行以下命令来安装发现服务:
docker run swarm create
这将下载并运行发现服务并生成令牌。在过程结束时,你将获得一个令牌;请务必记下这个令牌,以便在后续步骤中使用。如果一切顺利,你应该会看到类似于以下输出的内容:
在上面的示例中,令牌是40c3bf4866eed5ad14ade6633fc4cefc
。现在我们已经有了令牌,需要启动一个实例作为调度器,这个实例被称为 Swarm 管理器。
为此,请输入以下命令,并确保替换为你生成的令牌:
docker-machine create \
-d virtualbox \
--swarm \
--swarm-master \
--swarm-discovery token://40c3bf4866eed5ad14ade6633fc4cefc \
swarm-master
现在我们已经启动了 Swarm 管理器虚拟机,可以开始启动充当集群节点的虚拟机。同样,使用发现令牌,运行以下命令以启动两个节点:
docker-machine create \
-d virtualbox \
--swarm \
--swarm-discovery token://40c3bf4866eed5ad14ade6633fc4cefc \
swarm-node-01
然后使用以下命令启动第二个节点:
docker-machine create \
-d virtualbox \
--swarm \
--swarm-discovery token://40c3bf4866eed5ad14ade6633fc4cefc \
swarm-node-02
我们可以通过运行 docker-machine ls
命令检查我们的虚拟机,然后运行以下命令将我们的 Docker 客户端切换到使用集群:
eval $(docker-machine env --swarm swarm-master)
现在您的 Docker 客户端已经与集群通信,您可以运行docker info
来查找有关所有节点和集群本身的信息,您将看到类似以下屏幕截图的内容:
现在,我们有一个三 CPU、3GB 的集群,运行在三个节点上。为了测试它,让我们运行Hello World
容器,然后运行docker ps -a
,以便查看容器启动在哪个节点上:
如您从终端输出中看到的,容器是在swarm-node-01
上启动的,再次运行容器应该会将其启动在我们的第二个节点上:
所以,你看,这是一个非常基础的 Docker Swarm 集群,您可以使用本地 Docker 客户端将容器启动到其中,所有操作都是通过 Docker Machine 启动和管理的。
在我们进入下一部分之前,我们应该删除本地集群。为此,只需运行以下命令:
docker-machine rm discover swarm-master swarm-node-01 swarm-node-02
当提示时点击yes
。然后,您可以通过运行docker-machine ls
命令检查虚拟机是否已终止。
创建远程集群
在我们查看下一个工具之前,让我们在云中启动一个集群。我将使用 DigitalOcean 进行此操作。
首先,让我们创建一个新的发现令牌。由于我们所需要做的只是生成一个发现令牌,因此没有必要仅为此任务在 DigitalOcean 上启动实例,因此我们将简单地在本地启动一台机器,记下发现令牌后再将其移除:
docker-machine create -d virtualbox token
eval "$(docker-machine env token)"
docker run swarm create
docker-machine rm token
现在我们已经获得了发现令牌,让我们在 DigitalOcean 上启动我们的 Swarm 集群,首先我们将研究 Swarm 管理器:
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0 \
--digitalocean-region lon1 \
--swarm \
--swarm-master \
--swarm-discovery token://453sdfjbnfvlknmn3435mwedvmndvnwe \
swarm-master
然后我们将使用这两个节点:
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0 \
--digitalocean-region lon1 \
--digitalocean-size 1gb \
--swarm \
--swarm-discovery token://453sdfjbnfvlknmn3435mwedvmndvnwe \
swarm-node-01
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0 \
--digitalocean-region lon1 \
--digitalocean-size 1gb \
--swarm \
--swarm-discovery token://453sdfjbnfvlknmn3435mwedvmndvnwe \
swarm-node-02
如以下屏幕截图所示,我在 DigitalOcean 的伦敦数据中心启动了集群,并为两个节点分配了更多资源:
我们将配置本地 Docker 客户端,使用以下命令连接远程集群:
eval $(docker-machine env --swarm swarm-master)
这将为我们提供以下信息:
我们将使用这个集群进行本章的下一部分,因此目前尽量保持它运行。如果您无法保持运行,则可以通过运行以下命令删除该集群:
docker-machine rm swarm-master swarm-node-01 swarm-node-02
您还应该检查 DigitalOcean 控制面板,以确保您的实例已正确终止。
注意
请记住,使用公共云服务时,您是按使用量付费的,因此如果您的实例保持开机状态,即使它处于errored
状态,使用 Docker Machine 时,计费仍然在继续,您会产生费用。
发现后端
此时,值得指出的是,Docker 允许您更换发现后端,目前我们正在使用默认的托管发现与 Docker Hub,它不建议在生产环境中使用。
Swarm 支持以下发现服务:
-
etcd:
coreos.com/etcd/
-
Consul:
www.consul.io/
-
ZooKeeper:
zookeeper.apache.org/
目前,我们只会查看 Docker 提供的工具,而不会考虑任何第三方选项,因此我们将坚持使用默认的 Discovery 后端。
不幸的是,默认的 Discovery 后端没有提供高可用性,这意味着我们的 Swarm 管理器是单点故障。对于我们的需求来说,这不是一个问题;然而,我不建议在生产环境中运行这种配置。
有关不同 Discovery 后端和 Swarm 高可用性的更多信息,请参考以下 URL:
-
Discovery 后端:
docs.docker.com/swarm/discovery/
-
Swarm 高可用性:
docs.docker.com/swarm/multi-manager-setup/
我们将在后面的章节中深入讨论调度器,所以现在让我们进入 Docker Toolbox 安装的最后一个服务。
Docker Compose
到目前为止,在我们对 Docker Toolbox 工具的探索中,我们一直在使用管理 Docker 主机的服务,本章我们要看的最后一个服务是处理容器的服务。我相信您会同意,到目前为止,Docker 提供的工具非常直观,Docker Compose 也不例外。它最初作为一个名为 Fig 的第三方服务开始,由 Orchard Labs 编写(该项目的原始网站仍然可以访问:fig.sh/
)。
原始项目的目标如下:
"使用 Docker 提供快速、独立的开发环境"
自从 Fig 成为 Docker 的一部分后,它们并没有偏离最初的目标:
"Compose 是一个定义和运行多容器 Docker 应用程序的工具。使用 Compose,您通过 Compose 文件配置应用程序的服务。然后,使用单个命令,您可以从配置中创建并启动所有服务。"
在我们开始查看 Compose 文件并启动容器之前,让我们思考一下像 Compose 这样的工具为什么有用。
为什么选择 Compose?
启动单个容器就像运行以下命令一样简单:
docker run -i -t ubuntu /bin/bash
这将启动并附加到一个 Ubuntu 容器。如我们之前提到的,启动简单容器只是其中的一部分,Docker 并不是用来替代虚拟机的,而是用来运行单个应用程序的。
这意味着你不应该在单个容器中运行整个 LAMP 堆栈,应该将 Apache 和 PHP 运行在一个容器中,然后与一个运行 MySQL 的第二个容器连接。你可以更进一步,运行 NGINX 容器、PHP-FPM 容器以及 MySQL 容器。这就变得复杂了。突然间,你的简单启动命令变成了多行命令,所有命令必须按正确的顺序并带有正确的标志执行。
这正是 Docker Compose 试图解决的问题。你可以使用 YAML 文件定义容器,而不是执行多个长命令。这意味着你可以通过一个命令启动应用程序,并将容器启动顺序的逻辑交给 Compose 来处理。
注意
YAML 不是标记语言(YAML)是一种人类友好的数据序列化标准,适用于所有编程语言。
这也意味着你可以将应用程序的 Compose 文件与代码库一起发布,或者直接发送给另一个开发者/管理员,他们将能够按照你预期的方式启动应用程序。
Compose 文件
几乎每个人在某个时候都会安装、使用或阅读过 WordPress,因此在接下来的几个例子中,我们将使用来自 Docker Hub 的官方 WordPress 容器,你可以在hub.docker.com/_/wordpress/
上找到该容器的详细信息。
注意
WordPress 是一个可以用来创建美丽网站、博客或应用的 Web 软件。我们喜欢说 WordPress 既是免费的,又是无价的。欲了解更多信息,请查看wordpress.org/
。
让我们从安装一个基本的 WordPress 开始,首先创建一个名为wordpress
的文件夹,然后将以下内容添加到一个名为docker-compose.yml
的文件中:
wordpress:
container_name: my-wordpress-app
image: wordpress
ports:
- "80:80"
links:
- "mysql:mysql"
mysql:
container_name: my-wordpress-database
image: mysql
environment:
MYSQL_ROOT_PASSWORD: "password"
你将能够通过确保本地 Docker 客户端配置为使用 Swarm 集群来启动应用程序,运行docker-machine ls
并确保其处于活动状态,然后运行以下命令:
eval $(docker-machine env --swarm swarm-master)
一旦你的客户端配置为与 Swarm 集群通信,在包含docker-compose.yml
文件的文件夹中运行以下命令:
docker-compose up -d
使用命令末尾的-d
标志会以分离模式启动容器,这意味着容器将在后台运行。如果我们不使用-d 标志,那么我们的容器将会在前台启动,我们将无法继续使用同一终端会话,而不停止正在运行的容器。
你将看到类似以下的输出:
如你所见,你可以通过运行docker ps
来查找启动 WordPress 应用程序的节点的 IP 地址。如果你访问图中显示的 IP 地址,其中列出了端口 80
,你将看到一个 WordPress 安装界面:
一个有趣的地方是,尽管 my-wordpress-app
容器在 docker-compose.yml
文件中最先定义,Compose 仍然识别它与 my-wordpress-database
容器的关联,并首先启动了后者。此外,你可能注意到 wordpress:latest
和 mysql:latest
镜像已经在 Swarm 集群中的所有节点上被拉取下来。
那么,docker-compose.yml
文件本身呢?让我们再看一遍,这次带上一些注释。
就 Compose 而言,我们的 WordPress 应用程序被拆分成两个应用程序,一个叫做 wordpress,另一个叫做 mysql。让我们再次看看 docker-compose.yml
文件:
wordpress:
container_name: my-wordpress-app
image: wordpress
ports:
- "80:80"
links:
- "mysql:mysql"
mysql:
container_name: my-wordpress-database
image: mysql
environment:
MYSQL_ROOT_PASSWORD: "password"
在最顶层,我们有应用程序名称。从这里开始,我们定义应用程序的配置,通过给出键和值,确保你密切关注缩进。我倾向于使用两个空格来明确表示有缩进,但又不会使其变得难以阅读。
我们定义的第一个关键字是 container_name
,我们不必这么做,因为 Compose 会根据我们所在的文件夹名称和应用程序名称自动命名我们的容器。如果我们没有定义这个关键字,那么我们的容器将被命名为 wordpress_wordpress_01
和 wordpress_mysql_01
。
下一个关键字告诉应用程序使用哪个 image
,这将直接从 Docker Hub 拉取镜像。
然后我们定义了 ports
,注意我们只为 wordpress 应用程序定义了端口,而没有为 mysql 应用程序定义。因为我们希望 wordpress 应用程序监听宿主机的端口,所以我们给了 80:80。在这种情况下,第一个 80 是 宿主机 端口,第二个是我们希望暴露的 容器 端口。
同样,接下来的关键字只在 wordpress 应用程序中使用,它定义了 links
。链接用于将容器连接在一起,这里将 mysql 容器暴露给 wordpress 容器。这意味着当 wordpress 容器启动时,它将知道 mysql 容器的 IP 地址,并且只有 mysql 容器的端口会暴露给 wordpress 容器。
我们定义的最后一个关键字是 environment
,在这里我们传递更多的键和值,这些将在容器启动时设置为环境变量。
Compose 文件中所有可用键的详细说明可以在官方文档中找到:docs.docker.com/compose/compose-file/
。
启动更多
使用 Compose 的一个优势是它启动的每个环境都是隔离的,让我们使用以下 docker-compose.yml
文件启动另一个 WordPress 安装:
wordpress:
container_name: my-other-wordpress-app
image: wordpress
ports:
- "80:80"
links:
- "mysql:mysql"
mysql:
container_name: my-other-wordpress-database
image: mysql
environment:
MYSQL_ROOT_PASSWORD: "password"
如你所见,除了容器名称外,它与我们之前启动的环境完全相同:
你还会注意到,my-other-wordpress
容器已在集群的第二个节点上启动。目前,每个 Compose 环境会在单一节点上启动。随着我们启动更多的环境,我们将需要开始调整端口分配,因为它们将在主机上发生冲突(也就是说,你不能将两个port 80
分配给同一主机)。
注意
别忘了通过使用docker-machine rm
命令删除你启动的任何基于云的实例,同时检查你的云服务提供商的控制面板,确保这些实例已正确终止。
总结
在本章中,我们介绍了 Docker 提供的附加客户端工具,这些工具可以扩展你核心 Docker 安装的功能。我们所查看的所有工具都旨在融入你的工作流程,并尽可能简单易用。在接下来的章节中,我们将探讨如何使用第三方服务扩展 Docker 的一些核心功能。到时,我们会重新审视本章中提到的一些工具,并看看它们如何为这些工具增加额外的功能。
第三章:卷插件
本章将概述第一方和第三方卷插件。我们将讨论安装、配置和使用以下存储插件:
-
Docker 卷:
docs.docker.com/engine/userguide/containers/dockervolumes/
-
Convoy:
github.com/rancher/convoy/
-
REX-Ray:
github.com/emccode/rexray/
你还将了解如何与 Docker 插件交互,以及它们与我们在第二章中讨论的支持工具的区别和如何协同工作,介绍第一方工具。
注意
本章假设你使用的是 Docker 1.10+。请注意,某些命令在旧版本中可能无法工作。
零卷
在我们查看卷之前,让我们先看看如果不使用任何卷,将一切存储在容器中会发生什么。
首先,让我们使用 Docker Machine 在本地创建一个名为 chapter03
的新 Docker 实例:
docker-machine create chapter03 --driver=virtualbox
eval $(docker-machine env chapter03)
既然我们已经有了机器,可以使用 Docker Compose 来运行 WordPress 场景。首先,我们需要启动我们的 WordPress 容器,正如之前那样,我们使用来自 Docker Hub 的官方 WordPress 和 MySQL 镜像,我们的 docker-compose.yml
文件类似于以下代码:
version: '2'
services:
wordpress:
container_name: my-wordpress-app
image: wordpress
ports:
- "80:80"
links:
- mysql
environment:
WORDPRESS_DB_HOST: "mysql:3306"
WORDPRESS_DB_PASSWORD: "password"
mysql:
container_name: my-wordpress-database
image: mysql
environment:
MYSQL_ROOT_PASSWORD: "password"
如你所见,compose 文件没有什么特别之处。你可以通过运行以下命令启动它:
docker-compose up -d
启动容器后,通过运行以下命令检查它们的状态:
docker-compose ps
如果它们的状态都为 Up
,你可以通过运行以下命令进入 WordPress 安装界面:
open http://$(docker-machine ip chapter03)/
这将打开你的浏览器并转到 Docker 实例的 IP 地址。在我的例子中是 http://192.168.99.100/
。你应该看到以下屏幕:
点击 继续 按钮并安装 WordPress:
填写完信息后,点击安装 WordPress 以完成安装。完成后,MySQL 数据库将更新你的设置,测试帖子和评论也将被添加。当安装完成后,你将看到一个成功屏幕:
现在你应该能够重新运行以下命令:
open http://$(docker-machine ip chapter03)
这将带你到一个非常空的 WordPress 网站:
你的命令行历史记录应该类似于以下终端输出:
现在我们已经安装了 WordPress 网站,让我们通过运行以下命令销毁容器:
docker-compose stop && docker-compose rm
确保在提示时输入y
。然后你会收到一个确认信息,说明你的两个容器已经被删除:
现在我们已经删除了容器,让我们通过重新执行命令来重新创建它们:
docker-compose up -d
docker-compose ps
open http://$(docker-machine ip chapter03)/
如你所见,你再次看到一个安装界面,这是正常的,因为 MySQL 数据库存储在我们已删除的mysql
容器中。
在我们继续查看 Docker 自带的功能之前,先做一些整理,删除掉容器:
docker-compose stop && docker-compose rm
默认卷驱动
在我们开始使用第三方卷插件之前,应该先了解一下 Docker 自带的功能以及卷如何解决我们刚才处理的场景。再次提醒,我们将使用一个docker-compose.yml
文件;不过这次,我们会添加几行代码来创建和挂载卷:
version: '2'
services:
wordpress:
container_name: my-wordpress-app
image: wordpress
ports:
- "80:80"
links:
- mysql
environment:
WORDPRESS_DB_HOST: "mysql:3306"
WORDPRESS_DB_PASSWORD: "password"
volumes:
- "uploads:/var/www/html/wp-content/uploads/"
mysql:
container_name: my-wordpress-database
image: mysql
environment:
MYSQL_ROOT_PASSWORD: "password"
volumes:
- "database:/var/lib/mysql"
volumes:
uploads:
driver: local
database:
driver: local
如你所见,这里我们创建了两个卷,一个叫uploads
,它被挂载到 WordPress 容器的上传文件夹中。第二个卷叫database
,它被挂载到 MySQL 容器的/var/lib/mysql
目录中。
你可以使用以下命令启动容器并打开 WordPress:
docker-compose up -d
docker-compose ps
open http://$(docker-machine ip chapter03)/
在我们完成浏览器中的 WordPress 安装之前,我们应该通过运行docker exec
命令确保uploads
文件夹具有正确的权限:
docker exec -d my-wordpress-app chmod 777 /var/www/html/wp-content/uploads/
现在uploads
文件夹的权限已经正确设置,我们可以按照之前的测试继续进行 WordPress 安装:
由于 WordPress 会在安装过程中创建一个Hello World!
测试帖子,我们应该去编辑该帖子。要做到这一点,请使用安装时输入的凭证登录到 WordPress。登录后,进入文章 | Hello World,然后点击设置特色图片按钮上传一张特色图片。上传完特色图片后,你的编辑界面应该类似于以下截图:
上传完图片后,点击更新按钮,然后通过点击屏幕左上角的标题返回到你的 WordPress 首页。当首页打开后,你应该能看到你上传的特色图片:
在删除容器之前,你可以运行以下命令来显示 Docker 中已创建的所有卷:
docker volume ls
运行该命令时,你应该能看到我们在docker-compose.yml
文件中定义的两个卷:
记得,正如我们在上一章讨论的那样,Docker Compose 会将项目标题(即docker-compose.yml
所在文件夹的名称)作为前缀,这个项目名叫做wordpress-vol
,由于名称中不允许有-
,因此它被去掉了,留下了wordpressvol
。
现在我们有了基本的 WordPress 安装并且更新了内容,让我们像之前一样删除容器:
docker-compose stop && docker-compose rm
docker-compose ps
到此为止,你大概可以猜到接下来会发生什么,让我们重新启动容器,并在浏览器中打开 WordPress 网站:
docker-compose up -d
open http://$(docker-machine ip chapter03)/
启动可能需要几秒钟的时间,因此如果浏览器打开时没有看到你的 WordPress,刷新页面。如果一切顺利,你应该能看到你编辑过的 Hello World!
博客文章:
虽然这看起来和之前的截图相同,但你会注意到你已经从 WordPress 中登出了。这是因为默认情况下,WordPress 将其会话存储在文件系统中,并且由于会话文件不存储在上传目录中,因此在我们删除容器时会丢失会话文件。
卷也可以在容器之间共享,如果我们在 docker-compose.yml
文件的 Services
部分添加以下内容:
wordpress8080:
container_name: my-wordpress-app-8080
image: wordpress
ports:
- "8080:80"
links:
- mysql
environment:
WORDPRESS_DB_HOST: "mysql:3306"
WORDPRESS_DB_PASSWORD: "password"
volumes:
- "uploads:/var/www/html/wp-content/uploads/"
你可以启动一个第二个容器,在 port 8080
上运行 WordPress,并访问我们上传的文件,网址是 http://192.168.99.100:8080/wp-content/uploads/2016/02/containers-1024x512.png
。
请注意,前面的 URL 会因你的安装环境而有所不同,因为 IP 地址可能不同,上传日期和文件名也可能不同。
你可以通过运行以下命令获取更多关于卷的信息:
docker volume inspect <your_volume_name>
在我们的案例中,返回以下信息:
你可能已经注意到,我们为两个卷使用了 local
驱动,这会在我们的 Docker 实例上创建卷,并挂载来自主机的文件夹,这里是指运行在 VirtualBox 下的 Docker Machine 主机。
你可以通过 SSH 登录到主机并进入 docker volume inspect
命令返回的挂载点目录来查看文件夹中的内容。要通过 SSH 登录并切换到 root 用户,运行以下命令:
docker-machine ssh chapter03
sudo su -
然后你就可以切换到包含卷的文件夹,切换到 root 用户的原因是确保你有权限查看文件夹中的内容:
如前面终端输出所示,这些文件属于一个未知用户,其用户 ID 和组 ID 为 32,在容器中,这是 Apache 用户。如果你直接添加文件或进行更改,请小心,因为你可能会遇到各种权限错误,尤其是在容器访问你添加或更改的文件时。
到目前为止一切顺利,但有什么限制呢?最大的限制是你的数据绑定到单个实例。在上一章中,我们讨论了如何使用 Swarm 进行 Docker 集群化,我们提到使用 Docker Compose 启动的容器绑定到单个实例,这对于开发非常好,但对于生产环境来说就不太适用了,因为我们可能有多个主机实例,希望将容器分布到多个实例上,这时候第三方卷驱动就派上用场了。
第三方卷驱动
有多种第三方卷驱动可供选择,它们各自提供不同的功能。首先,我们将研究 Rancher 提供的 Convoy。
在我们开始安装 Convoy 之前,应该先考虑在云端某个地方启动一个 Docker 实例。由于我们已经在 DigitalOcean 和 Amazon Web Services 中启动了 Docker 实例,我们应该终止本地的 chapter03
实例,并在这些提供商中重新启动它,我将使用 DigitalOcean:
docker-machine stop chapter03 && docker-machine rm chapter03
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0\
--digitalocean-region lon1 \
--digitalocean-size 1gb \
chapter03
eval "$(docker-machine env chapter03)"
我们在云提供商中启动实例的原因之一是,我们需要一个完整的底层操作系统来安装和使用 Convoy,而 Boot2Docker 提供的镜像虽然不错,但对于我们的需求来说,它稍微有些轻量级。
提示
在进一步操作之前,我建议您为 DigitalOcean 的 Droplet 配置一个浮动 IP 地址。原因是,在本章节中,我们将安装 WordPress,并将安装迁移到新机器上。如果没有浮动 IP 地址,您的 WordPress 安装可能会出现问题。您可以在 DigitalOcean 网站上找到有关浮动 IP 地址的更多信息,网址是 www.digitalocean.com/community/tutorials/how-to-use-floating-ips-on-digitalocean
。
安装 Convoy
如前所述,我们需要在底层 Docker 主机操作系统上安装 Convoy。为此,您应首先通过 SSH 登录到 Docker 主机:
docker-machine ssh chapter03
提示
由于机器已在 DigitalOcean 启动,我们已经以 root 用户身份连接;这意味着我们在执行命令时不需要加 sudo
,但是由于您可能在其他提供商处启动实例,我会保留 sudo
,以防您不是 root 用户而遇到权限错误。
现在,您已经使用 ssh
命令登录到 Docker 主机,我们可以安装并启动 Convoy。Convoy 是用 Go 编写的,并以静态二进制文件形式发布。这意味着我们不需要手动编译它;我们只需获取二进制文件并将其复制到指定位置:
wget https://github.com/rancher/convoy/releases/download/v0.4.3/convoy.tar.gz
tar xvf convoy.tar.gz
sudo cp convoy/convoy convoy/convoy-pdata_tools /usr/local/bin/
Convoy 的更新版本可以在 github.com/rancher/convoy/releases
上找到;不过,这些版本仅供 Rancher 使用。我们将在后面的章节中详细讨论 Rancher。
现在我们已经准备好了二进制文件,接下来需要设置我们的 Docker 安装,以便加载插件:
sudo mkdir -p /etc/docker/plugins/
sudo bash -c 'echo "unix:///var/run/convoy/convoy.sock" > /etc/docker/plugins/convoy.spec'
convoy.spec
文件告诉 Docker 如何访问 Convoy;有关插件工作的更多细节,请参阅 第五章,构建您自己的插件。
Convoy 已安装并准备就绪,接下来我们只需添加一些存储。出于测试目的,我们将创建并使用一个回环设备;但是,请不要在生产环境中这样做!
注意
回环设备是一种将文件作为真实设备进行解释的机制。此方法的主要优势是,所有用于真实磁盘的工具都可以与回环设备一起使用。请参考wiki.osdev.org/Loopback_Device
。
要创建回环设备并挂载它,请运行以下命令:
truncate -s 4G data.vol
truncate -s 1G metadata.vol
sudo losetup /dev/loop5 data.vol
sudo losetup /dev/loop6 metadata.vol
现在我们的存储已经准备好,我们可以通过运行以下命令启动 Convoy:
sudo convoy daemon --drivers devicemapper --driver-opts dm.datadev=/dev/loop5 --driver-opts dm.metadatadev=/dev/loop6 &
您应该会看到类似于以下的输出:
现在我们已经启动了 Convoy,输入exit
以退出 Docker 主机并返回本地机器。
使用 Convoy 卷启动容器
现在我们已经启动了 Convoy,可以对我们的docker-compose.yml
文件进行一些更改:
version: '2'
services:
wordpress:
container_name: my-wordpress-app
image: wordpress
ports:
- "80:80"
links:
- mysql
environment:
WORDPRESS_DB_HOST: "mysql:3306"
WORDPRESS_DB_PASSWORD: "password"
volumes:
- "uploads:/var/www/html/wp-content/uploads/"
mysql:
container_name: my-wordpress-database
image: mariadb
environment:
MYSQL_ROOT_PASSWORD: "password"
command: mysqld --ignore-db-dir=lost+found
volumes:
- "database:/var/lib/mysql/"
volumes:
uploads:
driver: convoy
database:
driver: convoy
如果你没有将docker-compose.yml
文件放入wordpressconvoy
文件夹中,你会发现稍后在本节中的某些步骤需要更改卷的名称。
如您所见,我突出显示了一些更改。首先,我们已经切换到了使用 MariaDB,原因是现在我们使用的是实际的文件系统,而不是仅仅在主机上使用一个文件夹。我们有一个lost
+ found
文件夹,目前官方的 MySQL 容器无法正常工作,因为它认为卷中已经有数据库。为了解决这个问题,我们可以在启动 MySQL 时使用--ignore-db-dir
指令,而 MariaDB 支持该指令。
让我们启动容器并查看通过以下命令创建的卷:
docker-compose up -d
open http://$(docker-machine ip chapter03)/
docker-compose ps
docker volume ls
docker volume inspect wordpressconvoy_database
您应该会看到类似于以下的终端输出:
在进行其他操作之前,完成 WordPress 安装并上传一些内容:
open http://$(docker-machine ip chapter03)/
记得在上传内容之前为卷设置正确的权限:
docker exec -d my-wordpress-app chmod 777 /var/www/html/wp-content/uploads/
使用 Convoy 创建快照
到目前为止,它与默认的卷驱动没有什么不同。让我们来看看创建快照并备份卷的过程,稍后你会明白为什么要这样做。
首先,让我们跳回 Docker 主机:
docker-machine ssh chapter03
让我们通过运行以下命令来创建我们的第一个快照:
sudo convoy snapshot create wordpressconvoy_uploads --name snap_wordpressconvoy_uploads_01
sudo convoy snapshot create wordpressconvoy_database --name snap_wordpressconvoy_database_01
一旦创建了快照,您将收到一个唯一的 ID。就我而言,这些 ID 分别是c00caa88-087d-45ad-9498-7610844c075e
和4e2a2a6f-887c-4692-b2a8-e1f08aa42400
。
备份我们的 Convoy 快照
现在我们已经有了快照,我们可以以此为基础创建备份。为此,我们必须首先确保存储备份的目标目录存在:
sudo mkdir /opt/backup/
现在我们有了存储备份的地方,让我们创建备份:
sudo convoy backup create snap_wordpressconvoy_uploads_01 --dest vfs:///opt/backup/
sudo convoy backup create snap_wordpressconvoy_database_01 --dest vfs:///opt/backup/
一旦备份完成,您将收到一个 URL 形式的确认。对于上传,返回的 URL 如下:
vfs:///opt/backup/?backup=34ca255e-7164-4734-8b96-579b4e79f728\u0026volume=26a5913e-4794-4df3-bbb9-7a6361c23a75
对于数据库,URL 如下:
vfs:///opt/backup/?backup=41731035-2760-4a1b-bba9-5e906e2471bc\u0026volume=8212de61-ea8c-4777-881e-d4bd07b800e3
记得记录下这些 URL,因为你需要它们来恢复备份。存在一个问题,我们创建的备份存储在我们的 Docker 主机上。如果它出现故障怎么办?那时我们所有的辛勤工作将会丢失!
Convoy 支持为 Amazon S3 创建备份,所以我们来做这件事。首先,你需要登录到你的 Amazon Web Services 账户,并创建一个 S3 桶来存储你的备份。
一旦你创建了一个桶,你需要将凭证添加到服务器:
mkdir ~/.aws/
cat >> ~/.aws/credentials << CONTENT
[default]
aws_access_key_id = JHFDIGJKBDS8639FJHDS
aws_secret_access_key = sfvjbkdsvBKHDJBDFjbfsdvlkb+JLN873JKFLSJH
CONTENT
注意
要了解如何创建 Amazon S3 桶的更多信息,请参考 aws.amazon.com/s3/getting-started/
的入门指南,关于 credentials
文件的详细信息,请参见 blogs.aws.amazon.com/security/post/Tx3D6U6WSFGOK2H/A-New-and-Standardized-Way-to-Manage-Credentials-in-the-AWS-SDKs
。
现在你的 Amazon S3 桶已经创建。我将其命名为 chapter03-backup-bucket
并创建在 us-west-2
区域。你的 Docker 主机已能访问 Amazon 的 API。你可以重新备份数据,但这次,将它们推送到 Amazon S3:
sudo convoy backup create snap_wordpressconvoy_uploads_01 --dest s3://chapter03-backup-bucket@us-west-2/
sudo convoy backup create snap_wordpressconvoy_database_01 --dest s3://chapter03-backup-bucket@us-west-2/
如你所见,目标 URL 的格式如下:
s3://<bucket-name>@<aws-region>
一旦备份完成,你将再次收到 URL。在我的例子中,备份的 URL 如下:
s3://chapter03-backup-bucket@us-west-2/?backup=6cb4ed46-2084-42bc-8261-6b4da690bd5e\u0026volume=26a5913e-4794-4df3-bbb9-7a6361c23a75
对于数据库备份,我们将看到以下内容:
s3://chapter03-backup-bucket@us-west-2/?backup=75608b0b-93e7-4319-b212-7a1b0ccaf289\u0026volume=8212de61-ea8c-4777-881e-d4bd07b800e3
当运行前述命令时,你的终端输出应该类似于以下内容:
现在我们已经有了数据卷的实例备份,让我们终止 Docker 主机并启动新的主机。如果你还没这样做,exit
离开 Docker 主机,并通过运行以下命令终止它:
docker-machine stop chapter03 && docker-machine rm chapter03
恢复我们的 Convoy 备份
如下图所示,我们已将快照备份存储在 Amazon S3 桶中:
在恢复备份之前,我们需要重新创建我们的 Docker 实例。使用本章前面的部分中提供的在 DigitalOcean 启动 Docker 主机、安装和启动 Convoy 以及设置 AWS 凭证文件的说明。
小贴士
在继续之前,记得将你的浮动 IP 地址重新分配给 Droplet。
一旦你完成备份并且系统运行正常,你应该能够运行以下命令来恢复卷:
sudo convoy create wordpressconvoy_uploads --backup s3://chapter03-backup-bucket@us-west-2/?backup=6cb4ed46-2084-42bc-8261-6b4da690bd5e\u0026volume=26a5913e-4794-4df3-bbb9-7a6361c23a75
你还应该能够运行以下命令:
sudo convoy create wordpressconvoy_database --backup s3://chapter03-backup-bucket@us-west-2/?backup=75608b0b-93e7-4319-b212-7a1b0ccaf289\u0026volume=8212de61-ea8c-4777-881e-d4bd07b800e3
恢复卷的过程将需要几分钟,在此期间,你将看到大量输出流向你的终端。输出应该类似于以下截图:
如你在前面的终端会话结束时所见,恢复过程会从 S3 桶中恢复每个块,因此你会看到这些消息不断滚动。
一旦你恢复了这两个卷,返回到你的 Docker Compose 文件并运行以下命令:
docker-compose up -d
如果一切按计划进行,你应该能够打开浏览器并查看你的内容保持完好,且按你所见的方式显示,使用以下命令:
open http://$(docker-machine ip chapter03)/
提示
别忘了,如果你已经完成了 Docker 主机的操作,你需要使用 docker-machine stop chapter03 && docker-machine rm chapter03
来停止并移除它,否则你可能会产生不必要的费用。
总结 Convoy
Convoy 是一个很好的工具,可以开始查看 Docker 卷,它非常适合快速在不同环境之间移动内容,这意味着你不仅可以共享容器,还可以与其他开发人员或系统管理员共享卷。它的安装和配置也非常简单,因为它以预编译的二进制文件形式发布。
使用 REX-Ray 的块存储
到目前为止,我们已经查看了使用本地存储并备份到远程存储的驱动程序。现在我们将进一步探索,查看直接附加到我们容器的远程存储。
在这个例子中,我们将启动一个 Docker 实例,在 Amazon Web Services 中启动我们的 WordPress 示例,并使用 EMC 提供的卷驱动程序 REX-Ray 将 Amazon Elastic Block Store 卷附加到我们的容器上。
REX-Ray 支持公共云和 EMC 自有存储类型,如下所示:
-
AWS EC2
-
OpenStack
-
Google 计算引擎
-
EMC Isilon, ScaleIO, VMAX 和 XtremIO
该驱动程序正在积极开发中,承诺很快会支持更多类型的存储。
安装 REX-Ray
由于我们将使用 Amazon EBS 卷,我们需要在 AWS 中启动 Docker 主机,因为 EBS 卷不能作为块设备挂载到其他云提供商的实例上。根据前一章内容,这可以使用 Docker Machine 和以下命令完成:
docker-machine create \
--driver amazonec2 \
--amazonec2-access-key JHFDIGJKBDS8639FJHDS \
--amazonec2-secret-key sfvjbkdsvBKHDJBDFjbfsdvlkb+JLN873JKFLSJH \
--amazonec2-vpc-id vpc-35c91750 \
chapter03
切换 Docker Machine 使用新创建的主机:
eval "$(docker-machine env chapter03)"
然后,使用 SSH 进入主机,如下所示:
docker-machine ssh chapter03
一旦你进入 Docker 主机,运行以下命令来安装 REX-Ray:
curl -sSL https://dl.bintray.com/emccode/rexray/install | sh -
这将下载并执行 REX-Ray 最新稳定版本的基本配置:
安装 REX-Ray 后,我们需要配置它以使用 Amazon EBS 卷。作为 root 用户,执行以下操作,在 /etc/rexray/
中添加一个名为 config.yml
的文件:
sudo vim /etc/rexray/config.yml
文件应包含以下内容,记得替换 AWS 凭证的值:
rexray:
storageDrivers:
- ec2
aws:
accessKey: JHFDIGJKBDS8639FJHDS
secretKey: sfvjbkdsvBKHDJBDFjbfsdvlkb+JLN873JKFLSJH
一旦你添加了配置文件,你应该能够直接使用 REX-Ray,运行以下命令应该返回一个 EBS 卷的列表:
sudo rexray volume ls
如果你看到卷的列表,那么你需要启动该过程。如果没有看到卷,请检查你为 accesskey
和 secretkey
提供的用户是否具有读取和创建 EBS 卷的权限。要启动过程并检查一切是否正常,运行以下命令:
sudo systemctl restart rexray
sudo systemctl status rexray
如果一切按预期工作,你应该看到类似以下的终端输出:
安装的最后一步是重新启动实例上的 Docker,以便它识别新的卷驱动程序。为此,运行以下命令:
sudo systemctl restart docker
现在是时候启动一些容器了。我们需要对 Docker Compose 文件做的唯一更改是修改卷驱动程序的名称,其他内容保持不变:
version: '2'
services:
wordpress:
container_name: my-wordpress-app
image: wordpress
ports:
- "80:80"
links:
- mysql
environment:
WORDPRESS_DB_HOST: "mysql:3306"
WORDPRESS_DB_PASSWORD: "password"
volumes:
- "uploads:/var/www/html/wp-content/uploads/"
mysql:
container_name: my-wordpress-database
image: mariadb
environment:
MYSQL_ROOT_PASSWORD: "password"
command: mysqld --ignore-db-dir=lost+found
volumes:
- "database:/var/lib/mysql/"
volumes:
uploads:
driver: rexray
database:
driver: rexray
一旦应用程序启动,运行以下命令设置上传文件夹的权限:
docker exec -d my-wordpress-app chmod 777 /var/www/html/wp-content/uploads/
在 AWS 控制台中,你会注意到现在有一些额外的卷:
通过运行以下命令,在浏览器中打开你新的 WordPress 安装:
open http://$(docker-machine ip chapter03)/
如果你在浏览器中打开 WordPress 站点时遇到问题,请在 AWS 控制台中找到正在运行的实例,并为 port 80
/HTTP
添加规则到 DOCKER-MACHINE 安全组。你的规则应类似于以下图像:
你只需添加一次规则,因为每当你启动更多的 Docker 主机时,Docker Machine 会重新分配 docker-machine
安全组。
一旦页面打开,完成 WordPress 安装并编辑或上传一些内容。你现在应该知道流程了,添加完内容后,是时候停止容器、删除容器,然后终止 Docker 主机:
docker-compose stop
docker-compose rm
在移除主机之前,你可以通过运行以下命令检查卷的状态:
docker volume ls
你将看到类似以下的图像:
最后,是时候移除 Docker 主机了:
docker-machine stop chapter03 && docker-machine rm chapter03
移动 REX-Ray 卷
在我们用 Docker Machine 启动新 Docker 主机之前,值得指出的是,我们的 WordPress 安装可能看起来有些损坏。
这是因为将我们的容器迁移到新的主机会改变我们访问 WordPress 站点的 IP 地址,意味着在你将设置更改为使用第二个节点的 IP 地址之前,你将看到一个损坏的站点。
这是因为它尝试从第一个 Docker 主机的 IP 地址加载内容,如 CSS 和 JavaScript。
关于如何更新这些设置的更多信息,请参阅 WordPress Codex:codex.wordpress.org/Changing_The_Site_URL
。
此外,如果你已经登录到 AWS 控制台,你可能注意到你的 EBS 卷目前没有附加到任何实例:
既然这些都处理好了,让我们使用 Docker Machine 启动新的 Docker 主机。如果你按照上一节的说明启动主机、连接、安装 REX-Ray 并启动 WordPress 和数据库容器,正如我们之前讨论的,你可以通过连接到数据库来更新站点的 IP 地址:
-
如果你想更新 IP 地址,可以运行以下命令。首先,连接到你的数据库容器:
docker exec -ti my-wordpress-database env TERM=xterm bash -l
-
然后使用 MySQL 客户端连接到 MariaDB:
mysql -uroot -ppassword --protocol=TCP -h127.0.0.1
-
切换到
wordpress
数据库:use wordpress;
-
然后最后运行以下 SQL。在我的例子中,
http://54.175.31.251
是旧的 URL,而http://52.90.249.56
是新的 URL:UPDATE wp_options SET option_value = replace(option_value, 'http://54.175.31.251', 'http://52.90.249.56') WHERE option_name = 'home' OR option_name = 'siteurl'; UPDATE wp_posts SET guid = replace(guid, 'http://54.175.31.251','http://52.90.249.56'); UPDATE wp_posts SET post_content = replace(post_content, 'http://54.175.31.251', 'http://52.90.249.56'); UPDATE wp_postmeta SET meta_value = replace(meta_value,'http://54.175.31.251','http://52.90.249.56');
你的终端会话应该类似于以下截图:
不过,我们可以看到内容确实存在,尽管站点看起来像是坏了。
总结 REX-Ray
REX-Ray 仍然处于早期开发阶段,正在不断添加更多功能。在接下来的几个版本中,我预见它将变得越来越有用,因为它正逐渐朝着成为一个集群感知工具而非目前的独立工具发展。
然而,即使在其开发的早期阶段,它仍然是使用 Docker 卷与外部存储的一个极好的入门工具。
Flocker 和 Volume Hub
我们将要看的下一个工具是 ClusterHQ 提供的 Flocker。它无疑是我们将在本章中看到的第三方卷驱动中功能最丰富的。如以下支持的存储选项所示,它涵盖了最广泛的存储后端:
-
AWS Elastic Block Storage
-
OpenStack Cinder 与任何支持的后端
-
EMC ScaleIO、XtremeIO 和 VMAX
-
VMware vSphere 和 vSan
-
NetApp OnTap
-
戴尔存储 SC 系列
-
HPE 3PAR StoreServ 和 StoreVirtual(仅支持 OpenStack)
-
华为 OceanStor
-
Hedvig
-
NexentaEdge
-
ConvergeIO
-
Saratoga Speed
还将很快支持以下存储选项:
-
Ceph
-
Google Persistent Disk
由于大多数人都可以访问 AWS,我们将查看如何在 AWS 上启动 Flocker 集群。
形成你的 Flock
我们将不再亲自手动安装 Flocker,而是将快速了解如何快速启动 Flocker。
本章的这一部分,我们将通过 ClusterHQ 提供的 AWS CloudFormation 模板启动一个集群,快速启动一个 Flocker 集群。
注意
AWS CloudFormation 是亚马逊提供的编排工具,允许你定义你希望的 AWS 基础设施的外观和配置方式。CloudFormation 是免费使用的;但是,你需要为它启动的资源付费。截至撰写时,运行模板一个月的预计费用是 $341.13。有关 CloudFormation 的更多信息,请参考 aws.amazon.com/cloudformation/
,或者要了解费用详情,请参考 calculator.s3.amazonaws.com/index.html#r=IAD&s=EC2&key=calc-D96E035B-5A84-48DE-BF62-807FFE4740A8
。
在启动 CloudFormation 模板之前,我们需要执行一些步骤。首先,您需要创建一个密钥对供模板使用。为此,请登录到 AWS 控制台 console.aws.amazon.com/
,选择您的区域,然后点击 EC2,再在左侧点击密钥对菜单,您创建的密钥对应命名应类似于 flocker-test:
点击创建按钮后,您的密钥对将被下载,请妥善保管,因为以后无法再次下载。现在您已创建并安全下载了密钥对,接下来是时候在 ClusterHQ Volume Hub 上创建一个账户了,您可以访问volumehub.clusterhq.com/
进行注册。
Volume Hub(在写本书时仍处于 Alpha 测试阶段)是一个基于 Web 的接口,用于管理您的 Flocker 卷。您可以使用您的电子邮件地址注册账户,或使用 Google ID 进行登录。
登录/注册后,您将看到一条提示,显示您似乎还没有集群,并提供创建新集群或连接到现有集群的选项:
点击创建新建按钮将弹出一个覆盖层,里面包含了如何使用 AWS CloudFormation 创建集群的说明。由于我们已经完成了第一步,向下滚动到第二步。在这里,您应该看到一个按钮,显示开始 CloudFormation 配置过程,点击它将打开一个新标签页,直接带您进入 AWS 控制台中的 AWS CloudFormation 页面:
启动 AWS CloudFormation 堆栈的第一步是选择模板,这一步已经为我们完成,您可以点击下一步按钮。
接下来,您将需要提供一些关于堆栈的详细信息,包括堆栈名称、EC2 密钥对名称、AWS 访问和密钥秘钥以及您的 Volume Hub 令牌。
要获取您的 Volume Hub 令牌,请访问volumehub.clusterhq.com/v1/token
,系统会展示给您一个令牌。此令牌是唯一与您的 Volume Hub 账户相关联的,请务必不要与他人分享:
填写完详细信息后,您可以点击下一步按钮。在下一页中,系统会要求您为资源添加标签,这是可选的。您应该按照平常的流程为资源打标签。添加完标签后,点击下一步按钮。
提示
请注意,点击创建将会在您的 AWS 账户中启动资源,并产生按小时计费的费用。仅在您打算继续执行接下来的步骤时点击创建。
下一页将展示您提供的详细信息的概览。如果您对这些信息满意,请点击创建按钮。
点击创建按钮后,你将被带回到 AWS CloudFormation 页面,在那里你应该看到你的堆栈状态为CREATE_IN_PROGRESS:
如果你没有看到你的堆栈,点击右上角的刷新图标。通常情况下,创建集群大约需要 10 分钟。在堆栈创建过程中,你可以点击屏幕右下角的一个拆分窗格图标,查看正在进行的事件。
此外,在集群启动时,你应该开始看到节点在你的 Volume Hub 账户中注册。然而,尽管很诱人,还是不要在你的堆栈状态为CREATE_COMPLETE之前使用 Volume Hub。
一旦堆栈部署完成,点击输出标签。这将给你提供连接到集群所需的详细信息。你应该看到类似以下的内容:
我们需要做的第一件事是为之前创建的密钥对设置正确的权限。在我的案例中,它位于我的Downloads
文件夹中:
chmod 0400 ~/Downloads/flocker-test.pem.txt
一旦你设置了权限,你需要使用ubuntu
作为用户名和你的密钥对登录到客户端节点。在我的案例中,客户端节点的 IP 地址是 23.20.126.24:
ssh ubuntu@23.20.126.24 -i ~/Downloads/flocker-test.pem.txt
登录后,你需要运行一些额外的命令来准备集群。为此,你需要记下控制节点的 IP 地址,在前面的屏幕中是54.198.167.2
:
export FLOCKER_CERTS_PATH=/etc/flocker
export FLOCKER_USER=user1
export FLOCKER_CONTROL_SERVICE=54.198.167.2
现在你已经连接到控制服务,你应该能够使用flockerctl
命令查看集群概况:
flockerctl status
flockerctl ls
在运行flockerctl ls
命令时,你不应该看到任何数据集列出。现在我们应该连接到 Docker。为此,运行以下命令:
export DOCKER_TLS_VERIFY=1
export DOCKER_HOST=tcp://$FLOCKER_CONTROL_SERVICE:2376
docker info | grep Nodes
在写这本书时,Flocker AWS CloudFormation 模板安装并配置了 Docker 1.9.1 和 Docker Compose 1.5.2。这意味着你将无法使用新的 Docker Compose 文件格式。然而,在 GitHub 仓库中,应该有旧版和新版格式的 Docker Compose 文件,这些文件伴随本书提供。
你可以在 github.com/russmckendrick/extending-docker/
找到该代码库。
你的终端输出应该看起来类似于以下的会话:
现在一切都已启动运行,让我们使用 Flocker 卷启动我们的 WordPress 安装。
部署到 Flock 中
我们首先要做的是创建卷。我们可以让 Flocker 使用其默认值,即 75 GB 的 EBS 卷,但这对于我们的需求来说有些过大:
docker volume create -d flocker -o size=1G -o profile=bronze --name=database
docker volume create -d flocker -o size=1G -o profile=bronze --name=uploads
如您所见,这是一个更合适的大小,我们选择了与之前示例相同的卷名称。现在我们已经创建了卷,可以启动 WordPress。为此,我们有两个 Docker Compose 文件,一个将在 AgentNode1 上启动容器,另一个将在 AgentNode2 上启动容器。首先,创建一个文件夹来存储文件:
mkdir wordpress
cd wordpress
vim docker-compose-node1.yml
如前所述,在编写本书时,仅支持原始的 Docker Compose 文件格式,因此我们的文件应包含以下内容:
wordpress:
container_name: my-wordpress-app
image: wordpress
ports:
- "80:80"
links:
- mysql
environment:
- "constraint:flocker-node==1"
- "WORDPRESS_DB_HOST=mysql:3306"
- "WORDPRESS_DB_PASSWORD=password"
volume_driver: flocker
volumes:
- "uploads:/var/www/html/wp-content/uploads/"
mysql:
container_name: my-wordpress-database
image: mariadb
environment:
- "constraint:flocker-node==1"
- "MYSQL_ROOT_PASSWORD=password"
command: mysqld --ignore-db-dir=lost+found
volume_driver: flocker
volumes:
- "database:/var/lib/mysql/"
如您所见,它与新格式没有太大区别。需要注意的是绑定容器到节点的行,已在前面的代码中突出显示。
要启动容器,我们必须将文件名传递给 docker-compose
。为此,请运行以下命令:
docker-compose -f docker-compose-node1.yml up -d
docker-compose -f docker-compose-node1.yml ps
启动容器后,运行以下命令以设置 uploads
文件夹的正确权限:
docker exec -d my-wordpress-app chmod 777 /var/www/html/wp-content/uploads/
现在我们已经创建了卷并启动了容器,让我们快速查看 Volume Hub:
如您所见,两个卷已显示为附加到内部 IP 为 10.168.86.184
的节点。查看 Volumes 页面可以提供更多详细信息:
如您所见,我们有关于卷的大小、名称、唯一 ID 以及它附加到哪个节点的信息。我们还可以看到在集群中运行的容器信息:
在我们停止并移除容器之前,您应该配置 WordPress,然后登录并上传一个文件。您可以通过运行以下命令并在浏览器中打开映射到端口 80 的 IP 地址来获取可以访问 WordPress 的 IP 地址:
docker-compose -f docker-compose-node1.yml ps
完成这些更改后,您可以通过运行以下命令来停止并移除容器:
docker-compose -f docker-compose-node1.yml stop
docker-compose -f docker-compose-node1.yml rm -f
现在您已经移除了容器,是时候在第二个节点上启动它们了。您需要创建第二个 Docker Compose 文件,如下所示:
vim docker-compose-node2.yml
wordpress:
container_name: my-wordpress-app
image: wordpress
ports:
- "80:80"
links:
- mysql
environment:
- "constraint:flocker-node==2"
- "WORDPRESS_DB_HOST=mysql:3306"
- "WORDPRESS_DB_PASSWORD=password"
volume_driver: flocker
volumes:
- "uploads:/var/www/html/wp-content/uploads/"
mysql:
container_name: my-wordpress-database
image: mariadb
environment:
- "constraint:flocker-node==2"
- "MYSQL_ROOT_PASSWORD=password"
command: mysqld --ignore-db-dir=lost+found
volume_driver: flocker
volumes:
- "database:/var/lib/mysql/"
如您所见,唯一变化的是节点编号。要启动容器,请运行以下命令:
docker-compose -f docker-compose-node2.yml up -d
启动时间会稍长一些,因为 Flocker 需要取消附加并重新附加卷到第二个节点。一旦容器开始运行,您将看到它们现在显示为附加到第二个节点,在 Volume Hub 中,如下图所示:
这在 Volume Hub 的其他部分也有体现:
最后,您可以在 容器 页面上看到您的新容器:
运行以下命令并在浏览器中打开 IP 地址:
docker-compose -f docker-compose-node2.yml ps
如本章 REX-Rey 部分所提到的,打开 WordPress 时你应该看到一个破损的 WordPress 页面,但这不应该影响你,因为一些内容是从数据库卷中提供的;否则,你将看到安装 WordPress 页面。
所以,这就是你所做的。你使用 Flocker 和 Volume Hub 启动并查看了 Docker 卷,并将它们在主机之间移动。
如本节开头所述,你是按小时收费来运行集群的。要删除集群,你应该进入 AWS 控制台,切换到 CloudFormation 服务,选择你的堆栈,然后从操作下拉菜单中选择删除:
如果你收到无法删除 S3 存储桶的错误,别担心,所有昂贵的东西已经被终止。要解决此错误,只需在 AWS 控制台中找到它所抱怨的 S3 存储桶并删除内容。删除内容后,返回 CloudFormation 页面,再次尝试删除堆栈。
总结 Flocker
Flocker 是 Docker 卷的“祖父”,它在卷插件架构发布之前就是管理卷的原始解决方案之一。这意味着它既成熟,又是我们查看过的所有卷插件中最复杂的。
要了解其复杂性,你可以查看CloudFormation 模板。
如你所见,有很多步骤。在 CloudFormation 可视化工具中查看模板可以让你更清楚地了解所有内容是如何关联的:
再加上 Docker 本身也在定期更新,你就拥有了一个非常复杂的安装过程。这也是为什么我在本章中没有详细讲解如何手动安装它,因为到你阅读时,过程无疑会有所变化。
幸运的是,Cluster Labs 有一个非常好的文档,且定期更新。你可以在docs.clusterhq.com/en/latest/
找到它。
同时值得指出的是,在写这本书时,Volume Hub 仍处于早期 Alpha 阶段,功能正在不断添加。最终,我认为这将成为一个非常强大的工具组合。
总结
在本章中,我们查看了三种不同的卷驱动程序,它们都与 Docker 的插件架构兼容。
尽管这三种驱动程序提供了三种完全不同的方式来为容器提供持久化存储,但你可能已经注意到,Docker Compose 文件以及我们如何通过 Docker 客户端与卷交互的方式,在所有三种工具中几乎是一样的体验,可能到了一种我敢肯定它开始显得有点重复的地步。
在我看来,这种重复性展示了使用 Docker 插件的最佳特性之一——从客户端角度来看的一致体验。在我们配置好工具后,在任何时刻,我们都无需真正思考或考虑如何使用存储,我们只需要继续进行。
这使我们能够在多个环境中重用我们的资源,如 Docker Compose 文件和容器,包括本地虚拟机、基于云的 Docker 主机,甚至是 Docker 集群。
然而,目前我们仍然局限于单一的 Docker 主机。 在下一章中,我们将探讨如何通过查看 Docker 网络插件来开始跨多个 Docker 主机进行部署。
第四章:网络插件
在本章中,我们将介绍下一种插件类型:网络。我们将讨论如何使用 Docker 1.9 引入的新网络工具,以及第三方工具,这些工具为已经强大的内置工具增加了更多功能。我们将关注的两个主要工具如下:
-
Docker 覆盖网络:
docs.docker.com/engine/userguide/networking/dockernetworks/
-
Weave:
weave.works/
注意
本章假设您使用的是 Docker 1.10+ 版本,某些命令可能在旧版本中无法使用。
Docker 网络
在我们开始详细讨论 Docker 中的网络之前,我应该提到,我们已经顺利进入本书的第四章,而不需要真正思考网络问题,这是因为 Docker 默认在容器和主机机器的网络接口之间创建了一个网络桥接。这是 Docker 网络的最基本形式。
就像基本存储一样,这限制了您在单个主机上启动容器,即使您使用了像 Docker Swarm 这样的集群工具,正如您在第二章,引入第一方工具中所看到的,当我们启动 WordPress 安装时,Web 和数据库容器都是在集群中的单个主机上启动的。如果我们尝试将这两个容器绑定到不同的主机,它们将无法相互通信。
幸运的是,Docker 为您提供了支持,并提供了自己的多主机网络层,供 Docker Swarm 使用。
多主机网络与覆盖网络
Docker 在 Docker 1.9 中发布了其生产就绪的多主机覆盖网络功能。在此版本之前,这个功能被视为实验性的。
注意
覆盖网络是一种建立在另一个网络之上的计算机网络。覆盖网络中的节点可以被认为是通过虚拟或逻辑链路连接的,每条链路对应一个路径,可能通过许多物理链路,在底层网络中传输:
en.wikipedia.org/wiki/Overlay_network
在 Docker 术语中,它允许一个 Docker 主机上的容器直接与另一个 Docker 主机上的容器通信,就好像它们在同一主机上一样,如下图所示:
正如您从前面的图示中看到的,首先有一些前提条件。首先,您必须运行一个 Docker Swarm 集群。在这里,我们有一个由两个节点和一个主节点组成的 Docker Swarm 集群,它们都已配置了覆盖网络。您还需要一个服务发现服务,该服务可以被 Docker Swarm 集群访问。为此,您可以使用以下应用程序:
-
Consul:
www.consul.io/
-
Etcd:
coreos.com/etcd/
-
ZooKeeper:
zookeeper.apache.org/
在本章中,我们将使用 HashiCorp 的 Consul(hashicorp.com/
),并且我们还将通过 Docker Machine 在 DigitalOcean 上启动我们的集群。
启动发现
在第二章,介绍第一方工具,我们通过 Docker Hub 中的一次性令牌启动了我们的 Docker Swarm 集群。多主机网络的一个要求是持久化的键值存储,以便我们有一个永久且可访问的地方来存储有关集群的值,我们将在我们的示例集群中使用 Consul 来提供这个功能。
Consul 是由 HashiCorp 编写的开源工具,用于在基础设施中发现和配置服务。它提供了多个关键功能,包括服务发现、健康检查和键值存储,并且能够支持多数据中心。
要启动将运行 Consul 的 Docker 主机,运行以下命令:
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0 \
--digitalocean-region lon1 \
--digitalocean-size 512mb \
--digitalocean-private-networking \
service-discovery
你可能会注意到我们在docker-machine
命令中添加了一个额外的行,这样会启动具有私有网络的 DigitalOcean Droplet。一旦 Docker 主机启动完成,我们可以通过运行以下命令启动 Consul 服务:
docker $(docker-machine config service-discovery) run -d \
-p "8400:8400" \
-p "8500:8500" \
-h "consul" \
russmckendrick/consul agent -data-dir /data -server -bootstrap-expect 1 -ui-dir /ui -client=0.0.0.0
这将下载我的 Consul 容器镜像,现在也有一个官方镜像,可以在hub.docker.com/_/consul/
找到;然而,由于这个镜像是新的,可能不适用于前面的示例。
由于这是我们在此主机上需要运行的唯一命令,我们没有配置本地的 Docker 客户端以使用该主机;相反,我们是在运行时通过$(docker-machine config service-discovery)
传递配置。为了检查一切是否按预期运行,你可以运行以下命令:
docker $(docker-machine config service-discovery) ps
在这里,你应该看到一个正在运行的容器,类似于以下终端输出:
注意
在我们进一步推进之前,应该注意,使用-bootstrap-expect 1
标志启动 Consul 不应在生产环境中尝试。你应该考虑引入多个 Consul 主机。有关如何配置高度可用的 Consul 集群的更多信息,请参考以下网址,了解如何配置完整的 Consul 集群:
www.consul.io/docs/guides/bootstrapping.html
你还可以通过打开 Web 界面来了解 Docker 将会在 Consul 中存储哪些信息,方法是输入以下命令:
open http://$(docker-machine ip service-discovery):8500/ui
你应该看到一个几乎空白的 Consul 视图,如下图所示:
一旦启动了 Docker Swarm 集群并且服务发现容器正在运行并可以访问,我们将返回 Web 界面,开始启动其余的集群部分。
准备 Swarm
让我们开始启动 Docker Swarm 集群,首先是 Swarm 主节点。我们将其命名为chapter04-00
:
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0 \
--digitalocean-region lon1 \
--digitalocean-size 1gb \
--digitalocean-private-networking \
--swarm --swarm-master \
--swarm-discovery="consul://$(docker-machine ip service-discovery):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip service-discovery):8500" \
--engine-opt="cluster-advertise=eth1:2376" \
chapter04-00
如你所见,命令与第二章中的命令非常相似,介绍第一方工具;然而,我们这里提供了 Consul 安装的详细信息。我们通过使用docker-machine ip
命令传递service-discovery
主机的 IP 地址来做到这一点。
一旦 Swarm 主节点启动,我们将使用以下命令启动两个 Swarm 节点:
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0 \
--digitalocean-region lon1 \
--digitalocean-size 1gb \
--digitalocean-private-networking \
--swarm \
--swarm-discovery="consul://$(docker-machine ip service-discovery):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip service-discovery):8500" \
--engine-opt="cluster-advertise=eth1:2376" \
chapter04-01
对于第二个节点,我们将使用以下命令:
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0 \
--digitalocean-region lon1 \
--digitalocean-size 1gb \
--digitalocean-private-networking \
--swarm \
--swarm-discovery="consul://$(docker-machine ip service-discovery):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip service-discovery):8500" \
--engine-opt="cluster-advertise=eth1:2376" \
chapter04-02
现在我们已经启动了主节点和两个节点,让我们切换到环境中,并确保集群显示正确的主机数量:
eval $(docker-machine env --swarm chapter04-00)
docker info
运行docker info
时,你应该会看到类似于以下截图的内容:
所以,现在我们已经启动了集群,所有节点都能相互通信。接下来,我们将能够创建我们的覆盖网络。
添加覆盖网络
为了测试,我们将创建一个非常基础的网络并启动一个非常基础的容器。以下命令将创建覆盖网络,并且得益于 Consul 提供的服务发现,网络设置将分发到 Docker Swarm 集群中的每个节点:
docker network create --driver overlay --subnet=10.0.9.0/24 chapter04-overlay-network
如此,我们已经在集群中创建了一个名为chapter04-overlay-network
的覆盖网络,子网为10.0.9.0/24
。为了确保一切正常,你可以运行以下命令来列出集群中配置的网络:
docker network ls
你也可以通过运行以下命令来检查各个节点:
docker $(docker-machine config chapter04-01) network ls
docker $(docker-machine config chapter04-02) network ls
如你所见,每个节点都有自己的主机和桥接网络,这意味着如果你不想使用覆盖网络,完全可以不使用;不过,我们使用它,这样就可以启动一个容器并配置它使用我们新添加的网络。
使用覆盖网络
首先,我们将启动一个运行 NGINX 的容器:
docker run -itd \
--name=chapter04-web \
--net=chapter04-overlay-network \
-p 80:80 \
--env="constraint:node==chapter04-01" \
russmckendrick/nginx
如你所见,我们通过传递--net
标志来配置容器使用chapter04-overlay-network
。我们还确保容器在chapter04-01
节点上启动。接下来,让我们看看能否查看到 NGINX 容器提供的内容。
为此,我们将在第二个节点chapter04-02
上启动一个容器,并运行wget
来获取 NGINX 提供的页面:
docker run -it \
--rm \
--net=chapter04-overlay-network \
--env="constraint:node==chapter04-02" \
russmckendrick/base wget -q -O- http://chapter04-web
如果一切按计划进行,你将会看到命令返回Hello from NGINX
。我们也可以通过在第二个节点上运行以下命令来 ping NGINX 容器:
docker run -it \
--rm \
--net=chapter04-overlay-network \
--env="constraint:node==chapter04-02" \
russmckendrick/base ping -c 3 chapter04-web
你应该能看到一个 IP 地址,位于 10.0.9.0/24 子网范围内,正如下面的截图所示:
如果你想查看已经配置在chapter04-web
容器上的网络,可以运行以下命令:
docker exec chapter04-web ip addr
docker exec chapter04-web route -n
docker exec chapter04-web ping -c 3 google.com
你应该会看到类似于以下终端输出的内容:
最后,你可以通过运行以下命令在浏览器中访问容器:
open http://$(docker-machine ip chapter04-01)/
页面将会类似于以下截图所示:
虽然页面本身看起来并不复杂,但实际上后台有一些相当巧妙的操作你可能没有注意到,其中最大的一点是我们不再需要手动链接我们的容器。在前几章中,我们在启动多个容器时使用了 link 标志来将它们链接在一起。现在,我们在同一个 Overlay 网络中启动容器,Docker 假设这个网络中的所有容器都能相互通信,并且它会自动处理容器之间的链接。
Docker 还为容器配置了一个网关,以便能够默认将流量路由到我们的 Overlay 网络外部。如果你想创建一个仅限内部的网络,可以添加--internal
标志。
返回到 Consul
别忘了,在我们创建网络和启动容器时,服务发现容器一直在后台运行。返回到 Consul 的 Web 界面,你应该会注意到在Key/Value选项下,你会看到 Docker Swarm 集群内节点的列表:
点击查看,你还应该看到其他一些共享的值,例如网络相关的,这些都在 Docker Swarm 集群中共享:
在我们拆除 Docker Swarm 集群之前,让我们先看看如何使用 Docker Compose 启动 WordPress 栈。
创建多主机网络
如同前几章一样,我们将启动我们可靠的 WordPress 安装。我们将稍微有些不同,添加一些有趣的部分:
-
创建一个名为
wpoutside
的外部网络。这个网络将能够进行外部访问,我们的网站服务器将会在这里启动。 -
创建一个名为
wpinside
的内部网络。这个网络无法进行外部访问,但同一网络上的容器能够相互访问,我们将把网站服务器和数据库容器添加到这个网络中。 -
启动我们的网站服务器容器在一个节点上,数据库容器在第二个节点上。
在我们启动容器之前,我们应该终止chapter04-web
容器:
docker rm -f chapter04-web
现在,让我们创建两个覆盖网络:
docker network create --driver overlay --subnet=10.0.10.0/24 wpoutside
docker network create --driver overlay --internal --subnet=10.0.11.0/24 wpinside
如您所见,我们为网络分配了不同的子网,而对于wpinside
,我们传递了--internal
标志,这意味着该网络将没有外部网关。
现在,让我们来看一下我们的docker-compose.yml
文件:
version: '2'
services:
wordpress:
container_name: my-wordpress-app
image: wordpress
ports:
- "80:80"
networks:
- wpoutside
- wpinside
environment:
- "WORDPRESS_DB_HOST=mysql:3306"
- "WORDPRESS_DB_PASSWORD=password"
- "constraint:node==chapter04-01"
volumes:
- "uploads:/var/www/html/wp-content/uploads/"
mysql:
container_name: my-wordpress-database
image: mysql
networks:
- wpinside
environment:
- "MYSQL_ROOT_PASSWORD=password"
- "constraint:node==chapter04-02"
volumes:
- "database:/var/lib/mysql"
volumes:
uploads:
driver: local
database:
driver: local
networks:
wpoutside:
external: true
wpinside:
external: true
如您所见,我已经标出了自上一章以来文件中的更改。有趣的是,尽管可以在docker-compose.yml
文件中定义网络,但通过使用docker network create
命令设置网络,您将获得更多的控制权。为此,我们需要告诉 Docker Compose 使用为项目外部定义的网络。我们还使用标签将容器绑定到我们 Docker Swarm 集群中的主机。
现在我们已经创建了两个覆盖网络,您可以通过运行以下命令启动 WordPress 堆栈:
docker-compose up -d
您可以通过运行以下命令检查一切是否按预期启动:
docker-compose ps
为了确保容器在不同主机上启动,请运行以下命令并检查最后一列:
docker ps
要查看分配给容器的 IP 地址,请运行以下命令:
docker inspect my-wordpress-app | grep IPAddress
docker inspect my-wordpress-database | grep IPAddress
您应该会看到my-wordpress-app
的两个 IP 地址和my-wordpress-database
的单个 IP 地址:
在我们登录 WordPress 之前,可以尝试一些 ping 测试。首先,我们将在您的my-wordpress-app
容器上运行以下命令:
docker exec my-wordpress-app ping -c 3 google.com
docker exec my-wordpress-app ping -c 3 my-wordpress-database
对于第一条命令,您将看到返回的谷歌外部 IP 地址。对于第二条命令,您将获得my-wordpress-database
容器的 IP 地址,它位于我们为wpinside
覆盖网络定义的10.0.11.0/24
子网中:
在my-wordpress-database
上尝试类似命令应该会给您不同的结果,尝试运行以下命令:
docker exec my-wordpress-database ping -c 3 my-wordpress-app
docker exec my-wordpress-database ping -c 3 google.com
如您所见,ping my-wordpress-app
正常工作;但是,当您尝试 ping Google 时,会收到类似“网络无法访问”或其他错误的消息。这正是我们预期的结果,因为my-wordpress-database
没有外部网络访问权限,因此无法路由到www.google.com
:
最后,如果您想访问 WordPress,可以输入以下命令之一。首先,我们需要确认my-wordpress-app
容器启动在哪个主机上。要确认主机,请运行:
docker ps
然后,根据不同的主机,运行以下三条命令之一:
open http://$(docker-machine ip chapter04-00)/
open http://$(docker-machine ip chapter04-01)/
open http://$(docker-machine ip chapter04-02)/
您的浏览器将打开现在熟悉的 WordPress 安装页面。
在继续之前,您应该拆除 Docker Swarm 集群。为此,请运行以下命令:
docker-machine stop chapter04-00 chapter04-01 chapter04-02 service-discovery
docker-machine rm chapter04-00 chapter04-01 chapter04-02 service-discovery
总结多主机网络
尽管在 Docker 1.9 版本中,覆盖网络被视为生产就绪,但随着 Docker 1.10 版本的进步以及新的 Docker Compose v2 文件格式的推出,Docker 网络真的得到了更好的发展。
虽然覆盖网络功能已内置于 Docker 和 Swarm 中,正如我们在之前的示例中所看到的,它非常强大。当与我们在第三章中介绍的第三方卷插件、卷插件以及 Docker Swarm 结合使用时,我们可以开始构建高可用的部署。
编织网络
接下来,我们将看看 Weave Net 和 Weaveworks 的 Scope。这是最初的 Docker 网络工具之一,其核心是一个成熟的软件定义网络服务。
Weave Net 的描述如下:
"Weave Net 创建了一个容器 SDN,可以跨任何公共和私有云、虚拟机以及裸机运行。容器 SDN 可以承载任何二层和三层流量,包括多播。如果它可以通过以太网运行,您就可以在 Weave Net 上运行。"
事实上,Weave 提供了两个驱动,如下所示:
-
Weave Mesh 是一个本地作用域驱动,它在不需要集群存储的情况下运行。它可以用来创建跨非集群机器的网络。通过这种方式,您可以获得一个名为 Weave 的单一网络,覆盖您启动 Weave 的所有机器。
-
Weave 像 Docker 自己的覆盖驱动一样,是一个全局作用域驱动。这意味着它可以与 Docker Swarm 和 Docker Compose 一起使用,因此,您需要启动一个集群存储。
首先,我们来看一下 Weave 驱动及其如何与 Docker Swarm 一起使用,然后我们将看看如何使用 Weavemesh 驱动。
再次配置集群
像 Docker 的多主机网络一样,我们需要启动一个服务发现实例和我们的 Swarm 集群。让我们使用 Docker Machine 启动服务发现主机:
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0 \
--digitalocean-region lon1 \
--digitalocean-size 512mb \
--digitalocean-private-networking \
service-discovery
这次,我们不需要启用 Consul web 界面,因此运行以下命令:
docker $(docker-machine config service-discovery) run -d \
-p "8400:8400" \
-p "8500:8500" \
-h "consul" \
russmckendrick/consul agent -data-dir /data -server -bootstrap-expect 1 -client=0.0.0.0
现在启动 Docker Swarm 集群,首先是主节点:
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0 \
--digitalocean-region lon1 \
--digitalocean-size 1gb \
--digitalocean-private-networking \
--swarm --swarm-master \
--swarm-discovery="consul://$(docker-machine ip service-discovery):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip service-discovery):8500" \
--engine-opt="cluster-advertise=eth1:2376" \
chapter04-00
然后我们将启动我们的第一个节点:
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0 \
--digitalocean-region lon1 \
--digitalocean-size 1gb \
--digitalocean-private-networking \
--swarm \
--swarm-discovery="consul://$(docker-machine ip service-discovery):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip service-discovery):8500" \
--engine-opt="cluster-advertise=eth1:2376" \
chapter04-01
最后,我们将启动第二个节点:
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0 \
--digitalocean-region lon1 \
--digitalocean-size 1gb \
--digitalocean-private-networking \
--swarm \
--swarm-discovery="consul://$(docker-machine ip service-discovery):8500" \
--engine-opt="cluster-store=consul://$(docker-machine ip service-discovery):8500" \
--engine-opt="cluster-advertise=eth1:2376" \
chapter04-02
为了检查一切是否按预期工作,运行以下命令切换我们的本地 Docker 客户端连接到 Swarm 集群,并检查三个节点是否可见:
eval $(docker-machine env --swarm chapter04-00)
docker info
安装和配置 Weave
现在我们的集群已经启动并运行,我们可以安装和配置 Weave。安装 Weave 非常简单,您只需下载二进制文件并给予正确的权限。在 Swarm 主节点上使用docker-machine ssh
连接到主机并运行install
命令来完成此操作:
docker-machine ssh chapter04-00 'curl -L git.io/weave -o /usr/local/bin/weave; chmod a+x /usr/local/bin/weave'
接下来,我们启动 Weave,再次使用docker-machine ssh
,我们可以运行以下命令:
docker-machine ssh chapter04-00 weave launch --init-peer-count 3
您会注意到,Weave 从 Docker Hub 部署了三个容器,它们如下所示:
-
weaveworks/weaveexec
-
weaveworks/weave
-
weaveworks/plugin
此外,我们告诉 Weave 预期有三个节点加入集群,通过传递--init-peer-count 3
标志来实现,这基本上就是我们在第一个集群节点上配置 Weave 所需做的全部。
接下来,我们需要在其他两个集群节点上安装 Weave,仍然使用 docker-machine ssh
命令运行以下内容:
docker-machine ssh chapter04-01 'curl -L git.io/weave -o /usr/local/bin/weave; chmod a+x /usr/local/bin/weave'
docker-machine ssh chapter04-01 weave launch --init-peer-count 3
既然 Weave 已经在节点上启动并运行,我们需要告诉它连接到 Swarm 主节点上运行的 Weave 安装。为此,请运行以下命令:
docker-machine ssh chapter04-01 weave connect "$(docker-machine ip chapter04-00)"
然后在我们最后一个集群节点上,我们将运行以下命令:
docker-machine ssh chapter04-02 'curl -L git.io/weave -o /usr/local/bin/weave; chmod a+x /usr/local/bin/weave'
docker-machine ssh chapter04-02 weave launch --init-peer-count 3
docker-machine ssh chapter04-02 weave connect "$(docker-machine ip chapter04-00)"
一旦 Swarm 集群中的所有三个节点都安装并配置了 Weave,我们将运行以下命令,以确保所有三个节点能够相互通信:
docker-machine ssh chapter04-00 weave status
该命令应该返回确认信息,显示有三个对等节点以及六个已建立的连接,并附带其他安装信息,如以下截图所示:
现在我们已经确认一切按预期工作,我们将使用以下命令列出 Docker 中的网络:
docker network ls
根据以下终端会话,你应该会看到集群中每个节点都有一个名为 weave
的 weavemesh
网络;我们稍后会进一步讨论这点:
Docker Compose 和 Weave
所以,让我们启动我们的 WordPress 安装。Docker Compose 文件看起来与 Overlay 网络的文件略有不同:
version: '2'
services:
wordpress:
container_name: "my-wordpress-app"
image: wordpress
ports:
- "80:80"
environment:
- "WORDPRESS_DB_HOST=mysql.weave.local:3306"
- "WORDPRESS_DB_PASSWORD=password"
- "constraint:node==chapter04-01"
hostname: "wordpress.weave.local"
dns: "172.17.0.1"
dns_search: "weave.local"
volumes:
- "uploads:/var/www/html/wp-content/uploads/"
mysql:
container_name: "my-wordpress-database"
image: mysql
environment:
- "MYSQL_ROOT_PASSWORD=password"
- "constraint:node==chapter04-02"
hostname: "mysql.weave.local"
dns: "172.17.0.1"
dns_search: "weave.local"
volumes:
- "database:/var/lib/mysql"
volumes:
uploads:
driver: local
database:
driver: local
networks:
default:
driver: weave
我突出显示了 Overlay Docker Compose 文件中的一些更改:首先,我们将定义一个主机名并提供 DNS 服务器和搜索域。为了获得 dns
和 dns_search
键的正确值,你可以运行以下命令,让 Weave 告诉你它已配置的内容:
docker-machine ssh chapter04-00 weave dns-args
如你所见,在我的情况下,它返回了172.17.0.1
和weave.local
:
此外,对于从 WordPress 容器到数据库容器的 MySQL 连接,我们也在使用内部 DNS 名称。
我们还让 Docker Compose 为我们创建一个使用 Weave 驱动程序的网络,这将添加一个以项目命名的单一网络。Docker Compose 从 Docker Compose 文件所在的文件夹中获取项目名称,在我的例子中,它是一个名为 wordpress
的文件夹。
要启动你的容器并检查它们是否按预期运行,请运行以下命令:
docker-compose up -d
docker-compose ps
docker ps
你应该会看到类似于以下终端输出的内容:
如果你真的想要,你可以通过运行以下命令访问你的 WordPress 安装:
open http://$(docker-machine ip chapter04-01)/
在后台有一些 Docker 的多主机网络无法提供的功能,例如内部 DNS。Weave 有自己的内部 DNS 系统,你可以在其中注册你的容器,正如我们在 Docker Compose 文件中为两个容器提供的记录详细信息所示。运行以下命令:
docker-machine ssh chapter04-00 weave status dns
它将显示 Weave 配置的所有 DNS 记录。在我的情况下,它看起来像以下截图:
Weave Scope
当我们的三节点 Swarm 集群启动并运行时,快速安装 Scope。Scope 是一个用于可视化你的容器和主机的工具。我们将只在本地安装它,但 Weave Works 会提供基于云的服务,可以在 scope.weave.works/
找到(在编写本书时,它还处于私有测试阶段)。
类似于我们安装 Weave Net 的方式,我们将使用 docker-machine ssh
命令来下载二进制文件并启动和配置服务。
我们首先在 Swarm 主节点上编写代码:
docker-machine ssh chapter04-00 'curl -L git.io/scope -o /usr/local/bin/scope; chmod a+x /usr/local/bin/scope'
docker-machine ssh chapter04-00 scope launch
然后,我们将为剩下的两个节点编写代码:
docker-machine ssh chapter04-01 'curl -L git.io/scope -o /usr/local/bin/scope; chmod a+x /usr/local/bin/scope'
docker-machine ssh chapter04-01 scope launch $(docker-machine ip chapter04-00)
docker-machine ssh chapter04-02 'curl -L git.io/scope -o /usr/local/bin/scope; chmod a+x /usr/local/bin/scope'
docker-machine ssh chapter04-02 scope launch $(docker-machine ip chapter04-00)
如你所见,在剩下的两个节点上,我们正在告诉 Scope 连接到在 Swarm 主节点上运行的 Scope 实例。
现在 Scope 已经安装,运行以下命令在浏览器中打开它:
open http://$(docker-machine ip chapter04-00):4040/
当你的浏览器打开时,你将看到一个关于你 Swarm 集群和正在运行的容器的可视化表示。
我在这里不打算深入讲解 Scope,因为目前它与网络配置关系不大,先浏览一下你的集群,看看它是如何连接在一起的。我的界面看起来类似于下面的截图:
取消 Swarm
如你所见,虽然 Weave 是一个功能强大的 SDN,但它的配置非常简单。然而,复制 Docker 提供的多主机网络仅仅是它的一个小技巧。
在开始查看 Weavemesh 网络驱动之前,让我们关闭我们的 Swarm 集群并终止主机:
docker-machine stop chapter04-00 chapter04-01 chapter04-02 service-discovery
docker-machine rm chapter04-00 chapter04-01 chapter04-02 service-discovery
在继续之前,登录到你的 DigitalOcean 控制面板,确保没有标记为 chapter04
的机器正在运行,记住,无论你是否使用这些机器,都会按小时收费。
Weavemesh 驱动
我们已经了解了如何将 Weave Net 与 Docker Swarm 集群一起使用来创建多主机网络,现在让我们来看看第二个 Weave 网络驱动——Weavemesh。正如你可能记得的那样,当我们第一次安装 Weave Net 时,系统会自动创建一个名为 "weave" 的网络,并使用 "weavemesh" 驱动程序在我们集群的每个节点上配置。
这一次,让我们使用 Docker Machine 在 DigitalOcean 上启动两个独立的 Docker 主机。为了增加趣味性,我们将在伦敦启动一台主机,另一个则在纽约市。由于这些将作为独立主机运行,我们不需要启动键值存储,也不需要配置 Docker Swarm。
首先,输入以下命令来启动一个位于伦敦的主机:
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0 \
--digitalocean-region lon1 \
--digitalocean-size 1gb \
mesh-london
然后,下面的命令是用来启动另一个位于纽约市的主机。
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0 \
--digitalocean-region nyc2 \
--digitalocean-size 1gb \
mesh-nyc
现在我们的两台 Docker 主机都已启动并运行,接下来让我们安装并配置 Weave:
docker-machine ssh mesh-london 'curl -L git.io/weave -o /usr/local/bin/weave; chmod a+x /usr/local/bin/weave'
docker-machine ssh mesh-london weave launch --password 3UnFh4jhahFC
如你所见,这次我们告诉 Weave 启动时使用密码。这个标志将启用我们两个主机之间网络层的加密。现在我们已经配置了伦敦主机,让我们配置纽约市的主机,并让它与伦敦主机进行通信:
docker-machine ssh mesh-nyc 'curl -L git.io/weave -o /usr/local/bin/weave; chmod a+x /usr/local/bin/weave'
docker-machine ssh mesh-nyc weave launch --password 3UnFh4jhahFC
docker-machine ssh mesh-nyc weave connect "$(docker-machine ip mesh-london)"
现在我们已经在两台主机上配置了 Weave,我们可以通过运行以下命令来检查 Weave 的状态:
docker-machine ssh mesh-nyc weave status
如你所见,从下面的终端输出可以看出,已启用加密,并且我们在 Weave 网络中有两个对等节点:
所以,让我们看看 Weave 的绝招。我们将从启动我们的 NGINX 容器开始,保持简单:
docker $(docker-machine config mesh-nyc) run -itd \
--name=nginx \
--net=weave \
--hostname="nginx.weave.local" \
--dns="172.17.0.1" \
--dns-search="weave.local" \
russmckendrick/nginx
现在我们可以检查容器是否已启动并正在运行:
docker $(docker-machine config mesh-nyc) ps
让我们检查它是否在端口 80 上响应:
docker $(docker-machine config mesh-london) run -it \
--rm \
--net=weave \
--dns="172.17.0.1" \
--dns-search="weave.local" \
russmckendrick/base wget -q -O- http://nginx.weave.local
最后,让我们进行一次 ping 测试:
docker $(docker-machine config mesh-london) run -it \
--rm \
--net=weave \
--dns="172.17.0.1" \
--dns-search="weave.local" \
russmckendrick/base ping -c 3 nginx.weave.local
你的终端会话应该看起来像下面的截图:
从表面上看,这个测试似乎没有什么特别的;然而,如果你仔细查看我们使用的命令,你会看到 weavemesh 驱动程序的强大功能。
首先,当我们在纽约市的 Docker 主机上启动 NGINX 容器时,我们没有发布任何端口,这意味着端口 80 只在我们附加的 weave 网络上可用。
其次,当我们在端口 80 上进行检查并进行 ping 测试时,是在伦敦的 Docker 主机上进行的。我们临时启动了一个基本容器,将其附加到 weave
网络,并配置它使用 Weave DNS 服务,这样它就可以解析 nginx.weave.local
域名。
让我们再次进行测试,这次使用本地虚拟机:
docker-machine create -d virtualbox mesh-local
现在,按照我们在其他两个 Docker 主机上所做的步骤安装 Weave:
docker-machine ssh mesh-local 'sudo curl -L git.io/weave -o /usr/local/bin/weave; sudo chmod a+x /usr/local/bin/weave'
docker-machine ssh mesh-local sudo weave launch --password 3UnFh4jhahFC
docker-machine ssh mesh-local sudo weave connect "$(docker-machine ip mesh-london)"
然后再次运行测试:
docker $(docker-machine config mesh-local) run -it \
--rm \
--net=weave \
--dns="172.17.0.1" \
--dns-search="weave.local" \
russmckendrick/base wget -q -O- http://nginx.weave.local
运行 ping 测试,如下所示:
docker $(docker-machine config mesh-local) run -it \
--rm \
--net=weave \
--dns="172.17.0.1" \
--dns-search="weave.local" \
russmckendrick/base ping -c 3 nginx.weave.local
如你所见,它工作正常!
现在,我们的 Weavemesh 网络中有三台 Docker 主机,它们都可以互相通信。为了证明这一点,我们将进行最后一次测试。让我们在本地 Docker 主机上启动一个容器,并尝试从纽约市主机进行测试。
在我们的本地 Docker 主机上创建一个名为 vm.weave.local
的 NGINX 容器:
docker $(docker-machine config mesh-local) run -itd \
--name=vm \
--net=weave \
--hostname="vm.weave.local" \
--dns="172.17.0.1" \
--dns-search="weave.local" \
russmckendrick/nginx
然后尝试从纽约市的 Docker 主机连接到端口 80 并 ping 新容器:
docker $(docker-machine config mesh-nyc) run -it \
--rm \
--net=weave \
--dns="172.17.0.1" \
--dns-search="weave.local" \
russmckendrick/base wget -q -O- http://vm.weave.local
docker $(docker-machine config mesh-nyc) run -it \
--rm \
--net=weave \
--dns="172.17.0.1" \
--dns-search="weave.local" \
russmckendrick/base ping -c 3 vm.weave.local
我的终端会话看起来像下面的截图:
现在,由于没有 Docker Swarm 集群的常量,我们也可以开始执行一些只有在 Swarm 外部才可用的任务。
首先,在启动容器后将其连接到 Weave 网络,让我们在伦敦的 Docker 主机上启动一个名为 lonely
的 NGINX 容器:
docker $(docker-machine config mesh-london) run -itd \
--name=lonely \
russmckendrick/nginx
现在,让我们连接到伦敦的 Docker 主机并将容器连接到 weave 网络:
docker-machine ssh mesh-london weave attach lonely
当你运行命令时,它会返回一个 IP 地址。这将是我们容器的新 IP 地址;在我的例子中,它是 10.40.0.0。让我们从纽约市和本地 Docker 主机运行我们的测试:
docker $(docker-machine config mesh-nyc) run -it \
--rm \
--net=weave \
--dns="172.17.0.1" \
--dns-search="weave.local" \
russmckendrick/base wget -q -O- 10.40.0.0
docker $(docker-machine config mesh-local) run -it \
--rm \
--net=weave \
--dns="172.17.0.1" \
--dns-search="weave.local" \
russmckendrick/base ping -c 3 10.40.0.0
你的终端会话应该类似于下面的截图:
既然我们的容器已接入网络,我们可以通过运行以下命令手动为主机添加 DNS:
docker-machine ssh mesh-london weave dns-add lonely -h lonely.weave.local
如你所见,我们现在可以通过lonely.weave.local
从我们的纽约市 Docker 主机访问端口 80:
docker $(docker-machine config mesh-nyc) run -it \
--rm \
--net=weave \
--dns="172.17.0.1" \
--dns-search="weave.local" \
russmckendrick/base wget -q -O- lonely.weave.local
唯一的缺点是,无法轻松地为我们已连接到 "weave" 网络的主机添加 DNS 解析。
既然我们已经完成了 Docker 主机的配置,接下来让我们终止它们,以避免不必要的费用:
docker-machine stop mesh-local mesh-london mesh-nyc
docker-machine rm mesh-local mesh-london mesh-nyc
再次提醒,请检查你的 DigitalOcean 控制面板,确保主机已正确终止。
Weave 总结
正如你所看到的,我也已经提到过,Weave 是一个极其强大的软件定义网络,配置起来非常简单。根据我的经验,这是一个难得的组合,因为大多数 SDN 解决方案的安装、配置和维护都非常复杂。
我们只是初步接触了 "weave" 和 "weavemesh" 驱动的可能性。有关完整的功能列表以及一些高级用例的说明,请参考docs.weave.works/weave/latest_release/features.html
。
总结
在本章中,我们探讨了三种不同的网络驱动,它们都为你的基本 Docker 安装增添了非常强大的功能。与卷驱动一起,这些驱动真正扩展了 Docker,使你能够运行大规模的容错容器集群。
就我个人而言,当我第一次安装 Weave 并开始在不同托管提供商的 Docker 主机上轻松地与容器进行通信时,我简直是惊呆了。
在下一章,我们将探讨如何着手创建你自己的扩展。
第五章:构建你自己的插件
除了提供核心工具外,Docker 还记录了一个 API,允许核心 Docker 引擎与第三方开发者编写的插件服务进行通信。目前,这个 API 允许你将自己的存储和网络引擎接入 Docker。
这可能看起来像是将你限制在一个非常小众的插件集合中,确实如此。然而,Docker 作出这个决定是有充分理由的。
让我们来看一下我们在前几章已经安装的一些插件;然而,我们不会介绍它们的功能,而是看看它们背后的操作过程。
第三方插件
Docker 文档网站上关于插件的第一页列出了许多第三方插件。如前所述,让我们了解一下我们已经在第三章,卷插件和第四章,网络插件中安装并使用的插件背后的操作。
Convoy
Convoy 是我们在第三章中查看的第一个第三方插件,卷插件。为了安装它,我们在 DigitalOcean 启动了一个 Docker 主机,因为我们需要一个比 Docker Machine 偏好的 Boot2Docker 操作系统更完整的底层操作系统。
为了安装 Convoy,我们从 GitHub 下载了一个发布文件。这个 tar 压缩包包含了运行 Convoy 所需的静态二进制文件,一旦静态二进制文件到位,我们创建了一个 Docker 插件文件夹,并添加了一个符号链接到 Convoy 在首次执行时创建的套接字文件。
然后我们继续配置了一个我们在卷上创建的回环设备。接着,我们指示 Convoy 使用新创建的卷,通过启动我们下载的 Convoy 静态二进制文件将 Convoy 作为守护进程运行。
注意
在多任务计算机操作系统中,守护进程是一个作为后台进程运行的计算机程序,而不是直接由交互式用户控制:
en.wikipedia.org/wiki/Daemon(computing)
。
就 Docker 而言,当使用--volume-driver=convoy
标志启动容器时,每个请求将简单地将与卷相关的任何任务交给守护进程化的 Convoy 进程处理。
如果你回顾一下第三章中的Convoy部分,卷插件,你会注意到我们与 Convoy 的所有交互都使用convoy
命令而不是docker
命令,实际上,Convoy 客户端使用的是与我们符号链接到Docker 插件
文件夹的同一个套接字文件。
REX-Ray
接下来,我们安装了 REX-Ray。为此,我们运行了一个命令,该命令从dl.bintray.com/emccode/rexray/install
下载并执行了一个 bash 脚本。
该脚本会判断您正在运行的操作系统,然后下载并安装 DEB 或 RPM 文件。正如您可能已经猜到的,这些软件包会安装适合您操作系统的静态二进制文件。
REX-Ray 更进一步,还通过安装 init、upstart 或 systemd 服务脚本来启动守护进程,这意味着您可以像管理 Docker 主机上的其他服务一样启动和停止它。
再次说明,一旦我们安装了 REX-Ray,我们与该工具的唯一交互方式就是使用 rexray
命令。
Flocker
Flocker 更进一步,而不是安装安装脚本,我们使用 Cluster HQ 提供的 AWS CloudFormation 模板来为我们引导环境。
该程序完成了显而易见的任务:启动 Docker 主机、设置安全组,并安装和配置 Docker 和 Flocker。
Flocker 比 Convoy 和 REX-Ray 更进一步,通过安装一个与远程托管的 Web API(卷中心)交互的代理。
如本章所述,Flocker 在卷插件概念出现之前就已经存在。因此,许多与 Flocker 的交互是在 Docker 之外进行的;实际上,Cluster HQ 编写了他们自己的 Docker 封装程序,以便在 Docker 内部选项出现之前,您就可以轻松创建 Flocker 卷。
Weave
这是我们查看的唯一第三方网络插件。像 Flocker 一样,Weave 在 Docker 推出插件功能之前就已经存在。
Weave 与我们查看的其他第三方工具略有不同。在 Weave 中,下载的实际上是一个 Bash 脚本,而不是静态二进制文件。
注意
该脚本用于配置主机,并从 Weaveworks Docker Hub 帐户下载容器,您可以在 hub.docker.com/u/weaveworks/
找到该帐户。
脚本启动并配置容器,给予足够的权限以便与主机机器交互。该脚本还负责通过 docker exec
命令向运行中的容器发送命令,并在主机上配置 iptables
。
插件之间的共性
如您所见,正如您所经历的,这些插件都有脚本和二进制文件,它们是 Docker 本身之外的外部文件。
它们几乎都是用与 Docker 相同的语言编写的:
插件 | 语言 |
---|---|
Convoy | Go |
REX-Ray | Go |
Flocker | Python |
Weave | Go |
大多数服务都是用 Go 编写的,唯一的例外是 Flocker,它主要是用 Python 编写的:
Go 语言简洁、清晰、有效率,其并发机制使得编写高效利用多核和联网机器的程序变得容易,而其新颖的类型系统则使得灵活且模块化的程序构建成为可能。Go 语言快速编译为机器代码,同时具备垃圾回收的便利性和运行时反射的强大功能。它是一种快速的静态类型编译语言,使用起来却像是一种动态类型解释语言。
golang.org/
。
了解插件
到目前为止,我们已经确定,所有我们安装的插件实际上与 Docker 没有直接关系,那么插件到底做什么呢?
Docker 将插件描述为:
“Docker 插件是外部进程扩展,能够为 Docker 引擎添加功能。”
这正是我们在安装第三方工具时所看到的,它们都作为独立的守护进程与 Docker 并行运行。
假设我们将在本章的其余部分创建一个名为mobyfs
的卷插件。mobyfs 插件是一个虚构的服务,它是用 Go 语言编写的,并作为一个守护进程运行。
发现
通常,插件会安装在与 Docker 二进制文件相同的主机上。我们可以通过在/run/docker/plugins
目录下创建以下文件(如果是 Unix 套接字文件),或者在/etc/docker/plugins
或/usr/lib/docker/plugins
目录下创建(如果是其他两种文件)来将 mobyfs 插件注册到 Docker 中:
-
mobyfs.sock
-
mobyfs.spec
-
mobyfs.json
使用 Unix 套接字文件的插件必须与 Docker 安装在同一主机上。使用.spec
或.json
文件的插件,如果你的守护进程支持 TCP 连接,可以在外部主机上运行。
如果你使用的是.spec 文件,那么文件只需包含一个 URL,指向 TCP 主机和端口或本地套接字文件。以下三种示例都是有效的:
tcp://192.168.1.1:8080
tcp://localhost:8080
unix:///other.sock
如果你想使用.json
文件,它的内容应类似于以下代码:
{
"Name": "mobyfs",
"Addr": "https:// 192.168.1.1:8080",
"TLSConfig": {
"InsecureSkipVerify": false,
"CAFile": "/usr/shared/docker/certs/example-ca.pem",
"CertFile": "/usr/shared/docker/certs/example-cert.pem",
"KeyFile": "/usr/shared/docker/certs/example-key.pem",
}
}
JSON 文件中的TLSConfig
部分是可选的;然而,如果你将服务运行在 Docker 主机之外,我建议使用 HTTPS 进行 Docker 与插件之间的通信。
启动顺序
理想情况下,插件服务应在 Docker 之前启动。如果你正在运行的主机安装了systemd
,则可以通过使用类似以下的systemd
服务文件来实现这一点,文件应命名为mobyfs.service
:
[Unit]
Description= mobyfs
Before=docker.service
[Service]
EnvironmentFile=/etc/mobyfs/mobyfs.env
ExecStart=/usr/bin/mobyfs start -p 8080
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
[Install]
WantedBy=docker.service
这将确保你的插件服务始终在主 Docker 服务之前启动。
如果你将插件服务托管在外部主机上,可能需要重启 Docker 才能让 Docker 开始与插件服务通信。
你可以将插件打包在容器内。为了避免 Docker 必须在插件服务之前启动,每个激活请求将在 30 秒内尝试多次。
这将为容器提供足够的时间来启动,并在绑定自己到容器的端口之前运行插件服务的任何引导过程。
激活
现在插件服务已经启动,我们需要告诉 Docker 在调用插件服务时应该将请求发送到哪里。根据我们的示例,服务是一个卷插件,我们应该运行类似于以下命令的内容:
docker run -ti -v volumename:/data --volume-driver=mobyfs russmckendrick/base bash
这将把我们已经在插件服务中配置的 volumename
卷挂载到容器中的 /data
,该容器运行我的基础容器镜像,并将我们连接到一个 shell。
当调用 mobyfs 卷驱动程序时,Docker 将在我们在 发现 部分中讨论的三个插件目录中进行搜索。默认情况下,Docker 将始终先查找套接字文件,然后是 .spec
或 .json
文件。插件名称必须与文件扩展名前的文件名匹配。如果不匹配,Docker 将无法识别该插件。
API 调用
一旦插件被调用,Docker 守护进程将通过 HTTP 使用 RPC 风格的 JSON 向插件服务发送 POST 请求,使用 .spec
或 .json
文件中定义的套接字文件或 URL。
这意味着你的插件服务必须实现一个 HTTP 服务器,并将其绑定到你在发现部分中定义的套接字或端口。
Docker 发出的第一个请求将是 /Plugin.Activate
。你的插件服务必须对三种响应之一作出回应。由于 mobyfs 是一个卷插件,响应将如下所示:
{
"Implements": ["VolumeDriver"]
}
如果它是一个网络驱动程序,那么我们的插件服务应给出的响应将如下所示:
{
"Implements": ["NetworkDriver"]
}
插件服务的最终响应如下所示:
{
"Implements": ["authz"]
}
任何其他响应都将被拒绝,激活将失败。现在 Docker 已经激活了插件,它将继续根据调用 /Plugin.Activate
时收到的响应,向插件服务发送 POST 请求。
编写你的插件服务
如前节所述,Docker 将通过发出 HTTP 调用与插件服务进行交互。这些调用的文档可以在以下页面中找到:
Docker 还提供了一个 SDK,作为 Go 帮助程序的集合,这些可以在以下 URL 找到:
github.com/docker/go-plugins-helpers
每个帮助程序都附带示例以及开源项目的链接,这些项目提供了如何实现该帮助程序的进一步示例。
这些 API 请求不应与 Docker 远程 API 混淆,Docker 远程 API 的文档可在以下网址查看:
docs.docker.com/engine/reference/api/docker_remote_api/
这是 API,它允许您的应用与 Docker 进行交互,而不是 Docker 与您的应用进行交互。
总结
正如您所看到的,我们只讨论了 Docker 如何与您编写的插件服务进行交互,并没有涉及如何实际编写插件服务。
这样做的原因是由于我们需要涵盖的插件服务,我们还需要以下功能:
-
使用 Go 编写
-
能够作为守护进程运行
-
包含一个绑定到 Unix 套接字或 TCP 端口的 HTTP 服务器
-
能够接受并回答 Docker 守护进程向其发出的请求
-
将 Docker 发出的 API 请求转化为文件系统或网络服务
如您所想,这本身就有可能成为一本完整的书。
此外,构建自己的插件是一个相当大的工程,因为您已经需要编写服务的基础部分。虽然似乎有很多 Docker 插件,但在 GitHub 上搜索 Docker 插件只会返回一些几十个已经使用 Docker 插件 API 编写的插件。
其余返回的项目都是与 Docker 远程 API 通信的第三方服务工具或插件(例如 Jenkins、Maven 等)。
在下一章,我们将探讨第三方工具,以便在使用 Docker Machine 之外扩展您的基础设施。
第六章:扩展你的基础设施
在第二章,介绍第一方工具中,我们介绍了 Docker 提供的用于扩展核心 Docker 引擎功能的工具。在本章中,我们将介绍一些第三方工具,这些工具扩展了管理 Docker 配置、构建和启动容器的方式。我们将讨论的工具如下:
-
Puppet:
puppetlabs.com/
-
Ansible:
www.ansible.com/docker/
-
Vagrant:
docs.vagrantup.com/v2/docker/
对于每个工具,我们将讨论如何安装、配置和使用它们与 Docker 一起使用。在讨论如何使用这些工具之前,让我们先讨论一下为什么我们会想要使用它们。
为什么要使用这些工具?
到目前为止,我们一直在关注那些使用主 Docker 客户端或使用 Docker 和其他第三方提供的工具来支持主 Docker 客户端的工具。
很长一段时间,这些工具所拥有的功能在 Docker 支持产品中并不存在。例如,如果你想启动一个 Docker 主机,你不能仅仅使用 Docker Machine,而是必须使用像 Vagrant 这样的工具来启动虚拟机(本地或在云中),然后使用 bash 脚本、Puppet 或 Ansible 来安装 Docker。
一旦你启动了 Docker 主机,你就可以使用这些工具将容器放置在主机上,因为当时还没有 Docker Swarm 或 Docker Compose(记住,Docker Compose 最初是一个名为 Fig 的第三方工具)。
所以,虽然 Docker 慢慢发布了他们自己的工具,但一些第三方选项实际上更为成熟,并且有一个相当活跃的社区支持它们。
让我们从 Puppet 开始。
Puppetize all the things
很久以前,在以下的把所有东西容器化迷因开始频繁出现在人们的演示文稿中之前:
人们也在谈论关于 Puppet 的同样问题。那么,Puppet 是什么?为什么你要把它应用于所有事物?
Puppet Labs(Puppet 的开发者)将 Puppet 描述为:
“使用 Puppet,你可以定义 IT 基础设施的状态,Puppet 会自动强制执行所需的状态。Puppet 自动化了软件交付过程的每一步,从物理和虚拟机器的配置到编排和报告;从早期的代码开发到测试、生产发布和更新。”
在 Puppet 等工具之前,作为系统管理员的工作有时会变得相当繁琐:如果你没有在处理问题,就得自己编写脚本来引导已经建好的服务器,或者更糟糕的是,你需要从内部 Wiki 中复制粘贴命令来安装和配置你的软件堆栈。
服务器很快就会与最初的安装配置脱节,当它们出现故障时(所有服务器最终都会故障),事情可能会变得非常复杂、棘手、可怕、糟糕,甚至是所有这些情况迅速发生。
这就是 Puppet 发挥作用的地方;你定义所需的服务器配置,Puppet 为你完成繁重的工作,确保你的配置不仅被应用,而且还会被持续维护。
例如,如果我有几个服务器位于负载均衡器后面,提供我的 PHP 网站服务,那么确保这些服务器配置一致非常重要,这意味着它们都应该具备以下内容:
-
相同的 NGINX 或 Apache 配置
-
相同版本的 PHP 以及相同的配置
-
安装相同版本的 PHP 模块
在 Puppet 之前,我必须确保不仅保留一个用于初始安装的脚本,而且还必须小心手动地将相同的配置更改应用到所有服务器,或者编写一个脚本来同步集群中的更改。
我还必须确保任何有权访问服务器的人都遵循我制定的流程和操作程序,以便在我的负载均衡 Web 服务器之间保持一致性。
如果没有这些,我就会开始出现配置漂移,或者更糟的是,每 x 次请求中,可能有一次是从运行着与其他服务器不同的代码库/配置的服务器上提供的。
使用 Puppet,如果我需要运行最新版本的 PHP 5.6,因为我的应用程序在 PHP 7 下无法正常工作,那么我可以使用以下定义来确保满足我的需求:
package { 'php' :
ensure => '5.6',
}
这将确保 php
包被安装,并且版本保持在 5.6,我可以将这个单一配置应用到我的所有 Web 服务器上。
那么,这与 Docker 有什么关系呢?
Docker 和 Puppet
在 Docker Machine、Docker Compose 和 Docker Swarm 之前,我使用 Puppet 来引导和管理我的 Docker 主机和容器。让我们来看看 Gareth Rushgrove 编写的优秀 Docker Puppet 模块。
首先,我们需要一个虚拟机来工作。在前面的章节中,我们一直在使用 Docker Machine 启动虚拟机,以便运行我们的容器。
然而,正如我们希望 Puppet 管理 Docker 的安装以及我们将使用 Vagrant 启动本地虚拟机的容器一样,令人困惑的是,我们稍后将在本章中也提到 Vagrant,因此在这里我们不做过多详细讲解。
首先,你需要确保已经安装了 Vagrant,你可以从 www.vagrantup.com/
获取最新版本,并且可以在 www.vagrantup.com/docs/getting-started/
找到安装指南。
一旦安装了 Vagrant,你可以通过运行以下命令,使用 VirtualBox 启动一个 Ubuntu 14.04 虚拟服务器:
mkdir ubuntu && cd ubuntu/
vagrant init ubuntu/trusty64; vagrant up --provider VirtualBox
这将下载并启动虚拟服务器,将所有内容存储在 ubuntu
文件夹中。它还会使用 /vagrant
路径将 ubuntu
文件夹挂载为文件系统共享:
现在我们已经让虚拟服务器启动并运行,让我们连接到它并安装 Puppet 代理:
vagrant ssh
sudo su -
curl -fsS https://raw.githubusercontent.com/russmckendrick/puppet-install/master/ubuntu | bash
你应该看到类似于以下的终端会话:
现在我们已经安装了 Puppet 代理,最后一步是从 Puppet Forge 安装 Docker 模块:
puppet module install garethr-docker
你可能会看到以下终端会话中的警告信息;不必担心这些,它们只是为了告知你即将进行的 Puppet 更改:
此时,值得指出的是,我们还没有实际安装 Docker,所以现在通过运行我们的第一个 Puppet 清单来安装它。在你的本地机器上,在 ubuntu
文件夹中创建一个名为 docker.pp
的文件,文件内容应如下所示:
include 'docker'
docker::image { 'russmckendrick/base': }
docker::run { 'helloworld':
image => 'russmckendrick/base',
command => '/bin/sh -c "while true; do echo hello world; sleep 1; done"',
}
当我们使用 puppet apply
运行这个清单时,Puppet 会知道我们需要安装 Docker,才能下载 russmckendrick/base
镜像并启动 helloworld
容器。
回到我们的虚拟机,运行以下命令应用清单:
puppet apply /vagrant/docker.pp
你会看到命令输出大量内容,如以下截图所示:
首先发生的事情是 Puppet 会编译一个清单,这本质上是一个任务清单,列出了它需要完成的所有任务,以便应用我们在清单文件中定义的配置。然后,Puppet 会执行这些任务。你应该能看到 Puppet:
-
添加官方 Docker APT 仓库
-
执行
apt
更新以初始化新仓库 -
安装 Docker 及其依赖
-
下载
russmckendrick/base
镜像 -
启动
helloworld
容器
让我们通过确认 Docker 版本、查看下载的镜像、检查正在运行的容器,最后连接到 helloworld
容器来检查是否成功:
docker --version
docker images
docker ps
docker attach helloworld
要从容器中分离,按下键盘上的 Ctrl + C。这不仅会将提示符返回到虚拟机,还会停止 helloworld
容器:
docker ps -a
你可以看到我在运行命令时获得的输出,如以下终端会话所示:
那么如果我们再次应用这个清单会发生什么呢?让我们通过第二次运行 puppet apply /vagrant/docker.pp
来看看。
这次你应该看到的输出会少很多,实际上,除了警告信息,你应该看到的唯一输出是确认helloworld
容器已经开始备份:
现在我们已经了解了如何启动一些基本的东西,接下来让我们部署 WordPress 安装。首先,默认情况下,我们的虚拟机配置相对简单,因此让我们先移除虚拟机并启动一个更复杂的配置。
要移除虚拟机,请在终端中输入 exit,直到你返回到本地 PC;到达后,输入以下命令:
vagrant destroy
一旦你按下 Enter,系统会提示 你确定要销毁 'default' 虚拟机吗?,回答 yes,虚拟机将被关闭并移除。
接下来,替换 ubuntu
文件夹中名为 Vagrantfile
的文件的全部内容:
# -*- mode: ruby -*-
# vi: set ft=ruby :
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.network "private_network", ip: "192.168.33.10"
HOSTNAME = 'docker'
DOMAIN = 'media-glass.es'
Vagrant.require_version '>= 1.7.0'
config.ssh.insert_key = false
config.vm.host_name = HOSTNAME + '.' + DOMAIN
config.vm.provider "VirtualBox" do |v|
v.memory = 2024
v.cpus = 2
end
config.vm.provider "vmware_fusion" do |v|
v.vmx["memsize"] = "2024"
v.vmx["numvcpus"] = "2"
end
$script = <<SCRIPT
sudo sh -c 'curl -fsS https://raw.githubusercontent.com/russmckendrick/puppet-install/master/ubuntu | bash'
sudo puppet module install garethr-docker
SCRIPT
config.vm.provision "shell",
inline: $script
end
你还可以在本书的 GitHub 仓库中找到该文件的副本,仓库地址是:github.com/russmckendrick/extending-docker/blob/master/chapter06/puppet-docker/Vagrantfile
。
一旦 Vagrantfile
配置好,重新运行 vagrant up
,虚拟机将会启动。
这台虚拟机和我们之前启动的虚拟机的不同之处在于,它的 IP 地址是192.168.33.10
,仅能从本地 PC 访问。同时,Vagrantfile
还会运行安装 Puppet 和 Docker Puppet 模块的命令。
在虚拟机启动时,将以下 Puppet 清单文件放入你的 ubuntu
文件夹,并命名为 wordpress.pp
:
include 'docker'
docker::image { 'wordpress': }
docker::image { 'mysql': }
docker::run { 'wordpress':
image => 'wordpress',
ports => ['80:80'],
links => ['mysql:mysql'],
}
docker::run { 'mysql':
image => 'mysql',
env => ['MYSQL_ROOT_PASSWORD=password', 'FOO2=BAR2'],
}
正如你所见,格式本身类似于我们在第二章,介绍第一方工具中用于启动 WordPress 安装的 Docker Compose 文件。虚拟机启动后,连接到它,并运行以下命令以应用 wordpress.pp
清单:
vagrant ssh
sudo puppet apply /vagrant/wordpress.pp
如前所述,你将会看到相当多的输出:
一旦清单应用完毕,你应该可以通过浏览器访问 http:// 192.168.33.10/
或使用以下 URL:docker.media-glass.es/
,这个 URL 会解析到 Vagrantfile
中配置的 IP 地址,且仅在虚拟机启动并应用清单后才可访问。
从这里开始,你可以像在其他章节中一样安装 WordPress。完成后,别忘了使用vagrant destroy
命令销毁你的虚拟机,因为它会很高兴地在后台占用资源。
所以,到了这里,你已经获得了一个非常基础的关于如何将 Puppet 和 Docker 一起运行的实用介绍。
一个更高级的 Puppet 示例
到目前为止,我们一直在单一的虚拟机上运行 Puppet,但这并不是它的强项所在。
Puppet 的优势在于当你部署 Puppet Master 服务器并让你的主机上的 Puppet Agent 与 Master 进行通信时。在这里,你能够精确地定义你希望主机的配置。例如,以下图示展示了一个 Puppet Master 服务器控制四个 Docker 节点:
在这个例子中,我们可以在 Puppet 主服务器上为每个主机创建一个 Puppet 清单,同时还可以有一个用于配置所有四个节点共有配置的清单。
在这个例子中,我已经在每个节点上安装了 Weave,你可以查看 Puppet Forge 网站 forge.puppetlabs.com/
,那里有一个名为 tayzlor/weave
的模块,允许你管理 Weave。这个模块和 garethr/docker
一起,可以帮助你完成以下任务:
-
在每个节点上安装 Docker
-
在每个节点上安装 Weave
-
在所有四个节点之间创建一个 Weave 网络
-
管理每个节点上的镜像
-
在每个节点上启动容器,并配置它们使用 Weave 网络
默认情况下,每个节点上的 Puppet agent 会每 15 分钟回调到 Puppet 主服务器;当它回调时,它会处理适用于该节点的清单。如果有任何更改,这些更改将在 Puppet Agent 运行时应用;如果清单没有更改,则不会执行任何操作。
另外,Puppet 配置(包括清单文件)非常适合使用源代码控制进行管理,这样你可以创建一些非常有用的工作流程。
这种配置的唯一缺点是它并不能替代 Docker Swarm,因为所有有关容器启动位置的逻辑都在每个清单文件中手动定义。并不是说不能使用 Puppet 启动一个 Swarm 集群,你可以,只是需要更多的工作。
我们不会详细讲解这个例子,因为在这一章中我们还有四个工具要介绍,Puppetlabs 网站上有很多资源可以参考:
-
Puppet 开源文档:
docs.puppetlabs.com/puppet/
你可以找到更多关于我提到的两个 Puppet 模块的详细信息:
-
Docker 模块:
forge.puppetlabs.com/garethr/docker/
-
Weave 模块:
forge.puppetlabs.com/tayzlor/weave/
关于 Puppet 的最后一点说明
在本章的下一部分,我们将讨论 Ansible,我猜大多数人认为它和 Puppet 做的工作完全相同。虽然这两者之间确实有很多重叠,但我认为 Ansible 更擅长作为一个编排工具,而 Puppet 更擅长做配置管理。
由于 Puppet 是一个非常优秀的配置管理工具,很多人会有将 Puppet Agent 打包进容器的冲动,将它作为镜像构建过程的一部分,或者甚至在容器启动时进行实时配置。
尽量避免这样做,因为这可能会给你的容器增加不必要的负担,并引入额外的进程。记住,在理想的情况下,你的容器应该只运行一个进程,并且一启动就能工作。
使用 Ansible 进行编排
我猜很多人会期待本章的这一部分开篇讲 Ansible 与 Puppet 的对比。事实上,正如前一部分结尾提到的,虽然这两种工具有很多交集,但它们的优势在于完成两种不同的工作。
它们的工作方式也完全不同。我们现在不深入细节,直接跳过,安装 Ansible,并使用 Ansible playbook 启动我们的 WordPress 容器吧。
准备工作
注意
请注意,如果因为任何原因你无法完成本章这一部分,我已经录制了一个视频教程,展示了当你启动 Ansible playbook 时会发生什么,视频可以在 asciinema.org/a/39537
找到。
在启动容器之前,我们需要做几件事。第一件事是安装 Ansible。
如果你使用的是 OS X,我建议使用 Homebrew 安装 Ansible。Homebrew 可以在 brew.sh/
找到,并且可以通过以下命令安装:
/usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
跟随屏幕上的提示操作后,你应该能够使用以下命令安装 Ansible:
brew install ansible
现在 Ansible 安装好了,我们需要安装一个特定版本的 DigitalOcean Python 库。为此,我们需要使用 pip
命令。如果你还没有安装 pip
,你需要运行:
sudo easy_install pip
现在 pip
已经安装好了,运行以下命令来安装我们所需的正确版本的 Python 库:
sudo pip install dopy==0.3.5
最后你需要的就是你的 DigitalOcean 密钥名称。我们将要运行的 Ansible playbook 会为你创建一个,并在没有预先配置的情况下将其上传,如果你已经有了,可以跳过这部分。
如果你已经有了与 DigitalOcean 账户关联的密钥,那么你需要提供密钥名称来启动两个实例并连接到它们。
要找出这个信息,请登录到 cloud.digitalocean.com/
的 DigitalOcean 控制面板,并点击屏幕右上方的 齿轮图标
,在弹出的菜单中点击 设置 按钮。一旦设置页面加载完毕,点击 安全性 按钮,你应该会看到一个 SSH 密钥的列表,记下你想要使用的密钥名称:
在上面的例子中,我的 SSH 密钥被创意性地命名为 Russ Home
。
是时候获取我们将要运行的 Ansible playbook 了。代码可以在本书 GitHub 仓库中的 chapter06/docker-ansible
文件夹中找到,完整的 URL 如下:
github.com/russmckendrick/extending-docker/tree/master/chapter06/docker-ansible
下载 playbook 后,打开终端并进入 docker-ansible
文件夹。进入后,运行以下命令,并将 DigitalOcean API 替换为你自己的:
echo 'do_api_token: "sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0"' > group_vars/do.yml
echo 'ssh_key_name: "Your Key Name"' >> group_vars/do.yml
现在我们可以运行 playbook 了,但在此之前,请记住,此 playbook 将连接到你的 DigitalOcean 账户并启动两个实例。
要启动 playbook,运行以下命令并等待:
ansible-playbook -i hosts site.yml
完整的过程大约需要几分钟,但你最终应该会在你的 DigitalOcean 账户中启动两个 Ubuntu 14.04 Droplet。每个 droplet 都会安装最新版本的 Docker 和 Weave,Weave 将被配置为使得这两个主机可以相互通信。
一个 droplet 将运行我们的 WordPress 容器,第二个将运行我们的 MySQL 容器,两个容器将通过跨主机的 Weave 网络相互通信。
一旦任务完成,你应该会看到类似下面的截图:
如你所见,在我的案例中,我可以在浏览器中访问 http://46.101.4.247
来开始 WordPress 安装。
如果由于某种原因,部分安装失败,例如有时 droplets 启动可能会稍微慢一点,并且在 Ansible 尝试通过 SSH 连接时无法访问它们,请不要担心,你可以使用以下命令重新运行 Ansible playbook:
ansible-playbook -i hosts site.yml
Ansible 还会再次执行整个 playbook,这一次,它会跳过任何已经创建或操作过的内容。
如果你没有按照这个示例操作,或者遇到问题,我已经录制了整个启动 playbook 的过程,并再次运行的过程,你可以在 asciinema.org/a/39537
上查看。
playbook
playbook 包含了很多部分,如以下文件夹和文件列表所示:
├── ansible.cfg
├── group_vars
│ ├── do.yml
│ └── environment.yml
├── hosts
├── roles
│ ├── docker-install
│ │ └── tasks
│ │ └── main.yml
│ ├── docker-mysql
│ │ └── tasks
│ │ └── main.yml
│ ├── docker-wordpress
│ │ └── tasks
│ │ └── main.yml
│ ├── droplet
│ │ ├── tasks
│ │ │ └── main.yml
│ │ └── templates
│ │ └── dyn.yml.j2
│ ├── weave-connect
│ │ └── tasks
│ │ └── main.yml
│ └── weave-install
│ └── tasks
│ └── main.yml
└── site.yml
我们在启动 playbook 时调用的主要文件是 site.yml
文件,该文件定义了在 roles 文件夹中定义的任务执行顺序。让我们来看一下这个文件的内容以及被调用的角色。
第一部分
该文件本身分为四个部分,以下第一部分处理从本地机器连接到 DigitalOcean 的 API 并启动两个 Droplet:
- name: "Provision two droplets in DigitalOcean"
hosts: localhost
connection: local
gather_facts: True
vars_files:
- group_vars/environment.yml
- group_vars/do.yml
roles:
- droplet
它加载了主 environment.yml
变量文件,在这里我们定义了 droplet 启动所在的区域、droplet 的名称、要使用的大小,以及应该启动的镜像。
它还加载了包含你 DigitalOcean API 密钥和 SSH 密钥名称的do.yml
文件。如果你查看droplet
文件夹中的角色任务文件,你会看到在启动两个 droplet 的同时,它还创建了以下三个主机组:
-
dockerhosts
:这个组包含两个 droplet -
dockerhost01
:这个组包含我们的第一个 droplet -
dockerhost02
:这个组包含第二个 droplet
在此阶段采取的最后一个动作是写入一个文件到 group_vars
文件夹,其中包含我们两个 droplet 的公共 IP 地址。
第二部分
site.yml
文件的下一部分处理在 dockerhosts
组中的 droplet 上安装一些基本的前提条件、Docker 和 Weave:
- name: "Install Docker & Weave on our two DigitalOcean hosts"
hosts: dockerhosts
remote_user: root
gather_facts: False
vars_files:
- group_vars/environment.yml
roles:
- docker-install
- weave-install
第一个角色处理 Docker 的安装,我们来看看该角色任务文件中的内容。
首先,我们将使用 apt
包管理器安装 curl,因为稍后我们需要用到它:
- name: install curl
apt: pkg=curl update_cache=yes
一旦安装了 curl,我们将开始配置官方的 Docker APT 仓库,首先添加该仓库的密钥:
- name: add docker apt keys
apt_key: keyserver=p80.pool.sks-keyservers.net id=58118E89F3A912897C070ADBF76221572C52609D
然后,我们将添加实际的仓库:
- name: update apt
apt_repository: repo='deb https://apt.dockerproject.org/repo ubuntu-trusty main' state=present
一旦添加了仓库,我们可以实际安装 Docker,确保在安装软件包之前更新缓存的仓库列表:
- name: install Docker
apt: pkg=docker-engine update_cache=yes
现在 Docker 已安装,我们需要确保 Docker 守护进程已启动:
- name: start Docker
service: name=docker state=started
现在我们需要安装 Ansible 用来与我们主机上的 Docker 守护进程交互的工具,像 Ansible 一样,这也是一个 Python 程序。为了确保可以安装它,我们需要确保 pip
(Python 包管理器)已安装:
- name: install pip
apt:
pkg: "{{ item }}"
state: installed
with_items:
- python-dev
- python-pip
现在我们知道 pip 已安装,我们可以安装 docker-py
包:
- name: install docker-py
pip:
name: docker-py
这个包是一个由 Docker 提供的 Python 编写的 Docker 客户端。有关该客户端的更多详细信息,请访问 github.com/docker/docker-py
。
这结束了在 site.yml
文件第二部分中调用的第一个角色。现在 Docker 已安装,是时候安装 Weave 了,这由 weave-install
任务处理。
首先,我们从 environment.yml
文件中定义的 URL 下载 weave 二进制文件到文件系统路径,该路径也在 environment.yml
文件中定义:
- name: download and install weave binary
get_url: url={{ weave_url }} dest={{ weave_bin }}
一旦我们下载了二进制文件,我们需要检查该文件的正确读、写和执行权限,以便能够执行它:
- name: setup permissions on weave binary
file: path={{ weave_bin }} mode="u+rx,g+rx,o+rwx"
最后,我们需要启动 weave 并为其传递一个密码以启用加密,密码也在environment.yml
文件中定义:
- name: download weave containers and launch with password
command: weave launch --password {{ weave_password}}
ignore_errors: true
如你所见,在这一部分任务的最后,我们指示 Ansible 忽略此处产生的任何错误。这是因为,如果 playbook 第二次启动并且 weave 已经在运行,它会提示说 weave 路由器已经激活。此时,playbook 将停止执行,因为 Ansible 会将此消息视为一个严重错误。
由于这个原因,我们必须告诉 Ansible 忽略它认为这是一个关键错误,以便 playbook 能够继续进行。
第三部分
site.yml
文件的下一部分在启动构成我们 WordPress 安装的容器之前执行最后一部分配置。所有这些角色都在我们的第一个 droplet 上运行:
- name: "Connect the two Weave hosts and start MySQL container"
hosts: dockerhost01
remote_user: root
gather_facts: False
vars_files:
- group_vars/environment.yml
roles:
- weave-connect
- docker-mysql
第一个角色被称为,将两个主机上的两个 weave 网络连接在一起:
- include_vars: group_vars/dyn.yml
- name: download weave containers and launch with password
command: weave connect {{ docker_host_02 }}
正如您所见,在这里首次加载包含我们两个 droplet IP 地址的变量文件,并用于获取第二个 droplet 的 IP 地址;这个名为 dyn.yml
的文件是由最初启动两个 droplet 的角色创建的。
一旦我们有了第二个 droplet 的 IP 地址,执行 weave connect
命令,完成 weave 网络的配置。现在我们可以启动容器了。
我们需要启动的第一个容器是数据库容器:
- name: start mysql container
docker:
name: my-wordpress-database
image: mysql
state: started
net: weave
dns: ["172.17.0.1"]
hostname: mysql.weave.local
env:
MYSQL_ROOT_PASSWORD: password
volumes:
- "database:/var/lib/mysql/"
正如您所见,这与 Docker Compose 文件非常类似的语法;然而,可能存在细微差异,因此请在 Ansible 核心模块文档站点上仔细检查 Docker 页面,以确保您使用的是正确的语法。
一旦 my-wordpress-database
容器启动,意味着我们在 dockerhost01
上需要执行的所有任务已经完成。
第四部分
site.yml
文件的最后一部分连接到我们的第二个 droplet,然后启动 WordPress 容器:
- name: "Start the Wordpress container"
hosts: dockerhost02
remote_user: root
gather_facts: False
roles:
- docker-wordpress
所有这个角色所做的就是再次启动 WordPress 容器,文件与 Docker Compose 文件非常相似:
- include_vars: group_vars/dyn.yml
- name: start wordpress container
docker:
name: my-wordpress-app
image: wordpress
state: started
net: weave
dns: ["172.17.0.1"]
hostname: wordpress.weave.local
ports:
- "80:80"
env:
WORDPRESS_DB_HOST: mysql.weave.local:3306
WORDPRESS_DB_PASSWORD: password
volumes:
- "uploads:/var/www/html/wp-content/uploads/"
- debug: msg="You should be able to see a WordPress installation screen by going to http://{{ docker_host_02 }}"
最后的调试行在 playbook 运行结束时打印包含第二个 droplet IP 地址的消息。
Ansible 和 Puppet
与 Puppet 类似,使用像我们讨论过的这种 playbook 的 Ansible 可以用作 Docker Machine 和 Docker Compose 的替代品。
然而,您可能已经注意到的一件事是,与 Puppet 不同,我们没有在目标机器上安装代理。
当您运行 Ansible playbook 时,它会在本地编译,然后通过 SSH 推送编译后的脚本到目标服务器上,然后执行。
这也是为什么在 playbook 运行期间,我们必须在两个 droplet 上安装 Docker Python 库的原因之一,没有这个库,编译后的 playbook 将无法启动这两个容器。
另一个工具之间的一个重要区别是,Ansible 按照 playbook 中定义的顺序执行任务。
我们讨论的 Puppet 示例并不复杂到足以真正展示为何在运行 Puppet 清单时可能会成为问题,但 Puppet 使用的是事件一致性概念,这意味着可能需要几次清单运行才能应用您的配置。
的确,可以在 Puppet 清单中添加要求,例如,在执行 ABC 后需要执行 XYZ。但是,如果你的清单非常庞大,这可能会导致性能问题;另外,你可能会发现自己处于清单完全无法正常工作的情况,因为 Puppet 无法按照你定义的顺序成功执行清单。
这就是为什么在我看来,Ansible 在编排方面要比 Puppet 更加优秀的原因。
正是像这种情况,你所定义的任务必须按精确的顺序执行,而不是由你使用的工具决定最有效的任务执行方式。
对我来说,这就是你不应该抱着“我需要选择一个工具并且只用它来做所有事”的态度去处理任何任务的原因。你应该总是选择适合你想做的工作的工具。
对于我们在本章中讨论的许多工具,可能都可以这样说;我们不应将工具仅仅从“这个与那个”的角度进行评估,而应该问“这个或那个”,甚至是“这个和那个”,而不是自我限制。
Vagrant(再一次)
正如我们在本章前面已经发现的那样,Vagrant 可以作为虚拟机管理器来使用。我们已经使用它通过 VirtualBox 在本地机器上启动了一个 Ubuntu 14.04 实例;然而,如果我们愿意,我们也可以选择使用 VMware Fusion、Amazon Web Services、DigitalOcean,甚至是 OpenStack 来实现这一点。
像 Puppet 和 Ansible 一样,当 Docker 最初发布时,关于 Vagrant 与 Docker 的比较文章层出不穷。事实上,当 Stack Overflow 上有人提问时,Vagrant 和 Docker 的作者都参与了讨论。你可以在stackoverflow.com/questions/16647069/should-i-use-vagrant-or-docker-for-creating-an-isolated-environment
查看完整的讨论。
那么,Vagrant 可以以哪些方式支持 Docker 呢?我们将要探讨的有两种主要方式。第一种是提供者(provisioner)。
使用 Vagrant 进行配置
当我们使用 Puppet 时,我们通过 Vagrant 使用 VirtualBox 本地启动了 Ubuntu 14.04;在此过程中,我们使用了 Shell 提供者来安装 Puppet 并部署 Docker Puppet 模块。Vagrant 提供了以下几种提供者:
-
File:这会将文件复制到 Vagrant 主机上。
-
Shell:这会将 bash 脚本编译/复制到主机上并执行它们。
-
Ansible:这会在主机上或针对主机运行 Ansible 剧本。
-
Chef 和 Puppet:你可以使用 Chef 或 Puppet 通过大约十种不同的方式来提供 Vagrant 主机。
-
Docker:这是我们将用来在 Vagrant 主机上提供容器的工具。
Vagrantfile
看起来与我们用于部署 Puppet WordPress 示例的文件非常相似:
# -*- mode: ruby -*-
# vi: set ft=ruby :
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu/trusty64"
config.vm.network "private_network", ip: "192.168.33.10"
HOSTNAME = 'docker'
DOMAIN = 'media-glass.es'
Vagrant.require_version '>= 1.7.0'
config.ssh.insert_key = false
config.vm.host_name = HOSTNAME + '.' + DOMAIN
config.vm.provider "VirtualBox" do |v|
v.memory = 2024
v.cpus = 2
end
config.vm.provider "vmware_fusion" do |v|
v.vmx["memsize"] = "2024"
v.vmx["numvcpus"] = "2"
end
config.vm.provision "docker" do |d|
d.run "mysql",
image: "mysql",
args: "-e 'MYSQL_ROOT_PASSWORD=password'"
d.run "wordpress",
image: "wordpress",
args: "-p 80:80 --link mysql:mysql -e WORDPRESS_DB_PASSWORD=password"
end
end
如你所见,这将下载(如果你尚未安装的话)并启动一个 Ubuntu 14.04 服务器,然后配置两个容器,一个是 WordPress,一个是 MySQL。
要启动主机,运行以下命令:
vagrant up --provider VirtualBox
你应该会看到类似以下的终端输出:
你还可以运行以下命令来打开浏览器并进入 WordPress 安装屏幕(记住:我们已经使用固定的本地 IP 地址启动了 Vagrant 主机,这意味着以下 URL 应该会解析到你的本地安装):
open http://docker.media-glass.es/
你可能已经注意到当我们启动 Vagrant 主机时发生的一件事:我们不需要向 Vagrant 提供任何安装 Docker 的命令;它为我们处理了这一切。
此外,我们必须先启动 MySQL 容器,再启动 WordPress 容器。这是因为我们已经将 WordPress 容器与 MySQL 容器链接。如果我们尝试先启动 WordPress 容器,系统会给出错误提示,告诉我们正在尝试连接一个不存在的链接。
如你从以下终端输出中看到的,你可以使用 vagrant ssh
命令连接到你的 Vagrant 主机:
你可能还会注意到安装的 Docker 版本不是最新的;这是因为 Vagrant 安装的是操作系统默认仓库中的版本,而不是 Docker 在其仓库中提供的最新版本。
Vagrant Docker 提供者
如我所提到的,你可以通过两种方式在 Vagrant 中使用 Docker:我们刚才看到的是一种配置器方式,第二种是使用提供者方式。
那么,什么是提供者呢?我们在本章中已经两次使用了提供者,当我们启动 Docker 主机时就用了提供者。提供者是一个虚拟机进程、管理器或 API,Vagrant 可以连接到它,并从中启动虚拟机。
Vagrant 内置了以下提供者:
-
VirtualBox
-
Docker
-
Hyper-V
还有一个由作者提供的商业插件,它添加了以下提供者:
- VMware Fusion 和 Workstation
最后,Vagrant 支持自定义提供者,例如 Amazon Web Services、libvirt,甚至 LXC 等。你可以在 vagrant-lists.github.io/
找到完整的自定义提供者和其他 Vagrant 插件列表。
显然,如果你使用的是 OS X,那么你将无法原生使用 Docker 提供者;然而,Vagrant 会为你处理这个问题。让我们来看看使用 Docker 提供者而不是配置器启动一个 NGINX 容器。
Vagrantfile
看起来与我们之前使用的稍有不同:
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.define "boot2docker", autostart: false do |dockerhost|
dockerhost.vm.box = "russmckendrick/boot2docker"
dockerhost.nfs.functional = false
dockerhost.vm.network :forwarded_port, guest: 80, host: 9999
dockerhost.ssh.shell = "sh"
dockerhost.ssh.username = "docker"
dockerhost.ssh.password = "tcuser"
dockerhost.ssh.insert_key = false
end
config.vm.define "nginx", primary: true do |v|
v.vm.provider "docker" do |d|
d.vagrant_vagrantfile = "./Vagrantfile"
d.vagrant_machine = "boot2docker"
d.image = "russmckendrick/nginx"
d.name = "nginx"
d.ports = ["80:80"]
end
end
end
如你所见,它被分为两部分:一部分是 Boot2Docker 虚拟机,另一部分是容器本身。如果你运行 vagrant up
,你将看到类似以下的终端输出:
如你所见,由于我使用的是 OS X,Vagrant 知道我可以原生运行 Docker,因此它会使用 Vagrantfile
的第一部分并启动一个 Boot2Docker 实例。Boot2Docker 是支持 Docker Machine 默认驱动的轻量级 Linux 发行版。
下载完 Boot2Docker Vagrant Box 后,它会启动虚拟机,并将虚拟机的 22
端口映射到我们本地 PC 的 2222
端口,以便我们可以通过 SSH 访问。此外,按照 Vagrantfile
中的定义,虚拟机的 80
端口被映射到本地 PC 的 9999
端口。
值得注意的是,如果我在安装了 Docker 的 Linux PC 上运行此操作,那么这一步骤将会被跳过,Vagrant 会利用我本地的 Docker 安装。
现在 Boot2Docker 已启动,可以运行 Vagrantfile
的第二部分。如果像我一样,Vagrant 已下载并启动了 Boot2Docker Vagrant Box,那么它会要求输入密码;这是因为我们没有与 Boot2Docker 虚拟机交换密钥。密码是 tcuser
。
输入密码后,Vagrant 会从 hub.docker.com/r/russmckendrick/nginx/
下载 NGINX 镜像并启动容器,打开 80
端口。
容器启动后,你应该能够通过 http://localhost:9999/
访问 NGINX 欢迎页面。
如果你愿意,你可以通过 SSH 连接到 Boot2Docker 虚拟机,因为 Vagrant 主要管理容器,而不是 Boot2Docker 虚拟机。你需要使用以下命令:
ssh docker@localhost -p2222
同样,由于我们没有交换密钥,你将需要输入密码 tcuser
。然后你应该会看到如下信息:
一旦通过 SSH 连接,你将能够在本地运行 Docker 命令。最后,要终止容器和虚拟机,可以在与 Vagrantfile
相同的文件夹中运行以下命令,你将看到类似如下的输出:
vagrant destroy
这会提示你是否确认要移除容器和虚拟机,然后你需要对这两个问题都回答“是”。
你一定注意到,在讲解 Docker 提供者时,我们没有提到 WordPress 示例。原因在于,我认为 Docker 提供者的功能现在基本上是多余的,尤其是它有很多限制,这些限制可以通过使用提供程序或其他工具轻松克服。
其中一个限制是它只能使用端口映射;我们不能为虚拟机分配 IP 地址。如果我们这样做,它会悄无声息地失败,并从虚拟机恢复到主机 PC 的端口映射。
此外,当启动容器时,其提供的功能并不像我们本章中讨论的其他工具那样符合 Docker 的最新版本和功能。
因此,我建议你在考虑使用 Vagrant 时,使用 provisioner 而不是 provider。
镜像打包
到目前为止,我们一直在从 Docker Hub 下载预构建镜像来进行测试。接下来,我们将着眼于创建自己的镜像。在我们深入了解如何使用第三方工具创建镜像之前,应该先快速了解一下如何在 Docker 中构建镜像。
一个应用程序
在我们开始构建自己的镜像之前,实际上我们应该有一个应用程序来“烘焙”进镜像。我猜想你可能已经对一遍又一遍地进行 WordPress 安装感到厌倦了。我们将探索一些完全不同的东西。
因此,我们将构建一个安装了 Moby Counter 的镜像。Moby Counter 是由 Kai Davenport 编写的一个应用程序,他这样描述它:
"一个小型应用程序,用于演示如何在 docker-compose 应用程序中保持状态。"
该应用程序在浏览器中运行,并将在你点击的页面上添加 Docker 徽标,目的是它使用 Redis 或 Postgres 后端来存储 Docker 徽标的数量及其位置,这演示了如何在像我们在第三章中看到的 Volume 插件 这样的卷上持久化数据。你可以在 github.com/binocarlos/moby-counter/
找到该应用程序的 GitHub 仓库。
Docker 方式
既然我们对即将发布的应用程序有所了解,那么让我们来看看如何使用 Docker 本身构建镜像。
本章的代码可以从本书附带的 GitHub 仓库中获取;你可以在 github.com/russmckendrick/extending-docker/tree/master/chapter06/images/docker
找到它。
基本构建的 Dockerfile
非常简单:
FROM russmckendrick/nodejs
ADD . /srv/app
WORKDIR /srv/app
RUN npm install
EXPOSE 80
ENTRYPOINT ["node", "index.js"]
当我们运行构建时,它将从 Docker Hub 下载 russmckendrick/nodejs
镜像;正如你可能已经猜到的那样,这个镜像中安装了 NodeJS。
一旦镜像下载完成,Docker 将启动容器并添加当前工作目录的内容,该目录包含 Moby Counter 代码。然后,它会将工作目录切换到代码上传的位置 /srv/app
。
接着,它将通过执行 npm install
命令来安装运行应用程序所需的先决条件;由于我们已经设置了工作目录,所有命令将在该目录下运行,这意味着将使用 package.json
文件。
随附 Dockerfile
的还有一个 Docker Compose 文件,这个文件启动了 Moby Counter 镜像的构建,下载官方的 Redis 镜像,并启动这两个容器,将它们连接起来。
在我们进行之前,我们需要启动一台机器来运行构建;为此,运行以下命令启动一个基于 VirtualBox 的本地 Docker 主机:
docker-machine create --driver "VirtualBox" chapter06
现在 Docker 主机已启动,运行以下命令将本地 Docker 客户端配置为直接与其通信:
eval $(docker-machine env chapter06)
现在你已经准备好主机并配置了客户端,运行以下命令来构建镜像并启动应用程序:
docker-compose up -d
当你运行命令时,应该能在终端中看到类似以下的输出:
现在应用程序已经启动,应该能够通过运行以下命令打开浏览器:
open http://$(docker-machine ip chapter06)/
你将看到一个页面,页面上写着点击以添加标志,如果你点击页面,Docker 标志会开始出现。如果你点击刷新,你添加的标志会保留下来,并且它们的位置会存储在 Redis 数据库中。
要停止容器并将其删除,运行以下命令:
docker-compose stop
docker-compose rm
在我们探讨使用 Docker 构建容器镜像的优缺点之前,先来看一个第三方替代方案。
使用 Packer 构建
Packer 是由 Mitchell Hashimoto 编写的,来自 Hashicorp,与 Vagrant 的作者相同。因此,我们将使用的术语之间有很多相似之处。
Packer 网站可能是对该工具最好的描述:
“Packer 是一个开源工具,用于从单一源配置创建多个平台的相同机器镜像。Packer 轻量,支持所有主要操作系统,且性能优异,能够并行为多个平台创建机器镜像。Packer 并不替代 Chef 或 Puppet 这样的配置管理工具。事实上,在构建镜像时,Packer 可以使用 Chef 或 Puppet 等工具在镜像中安装软件。”
我自 Packer 发布以来就开始使用它来为 Vagrant 和公共云构建镜像。
你可以从 www.packer.io/downloads.html
下载 Packer,或者如果你已安装 Homebrew,可以运行以下命令:
brew install packer
现在你已经安装了 Packer,我们来看看一个配置文件。Packer 配置文件都是用 JSON 定义的。
注意
JavaScript 对象表示法 (JSON) 是一种轻量级的数据交换格式。它易于人类读取和编写,也便于机器解析和生成。
以下文件几乎完全做了我们之前的 Dockerfile
所做的工作:
{
"builders":[{
"type": "docker",
"image": "russmckendrick/nodejs",
"export_path": "mobycounter.tar"
}],
"provisioners":[
{
"type": "file",
"source": "app",
"destination": "/srv"
},
{
"type": "file",
"source": "npmrc",
"destination": "/etc/npmrc"
},
{
"type": "shell",
"inline": [
"cd /srv/app",
"npm install"
]
}
]
}
再次提醒,构建镜像所需的所有文件,以及用于运行它的 Docker Compose 文件,都可以在 GitHub 仓库中找到,链接是 github.com/russmckendrick/extending-docker/tree/master/chapter06/images/packer
。
我们将不使用 Docker Compose 文件来构建镜像,而是需要运行 packer,然后导入镜像文件。要开始构建,请运行以下命令:
packer build docker.json
你应该在终端中看到以下内容:
一旦 Packer 完成构建镜像,它会将镜像副本保存在你启动 Packer 构建命令的文件夹中;在我们的案例中,镜像文件叫做 mobycounter.tar
。
要导入镜像以供使用,请运行以下命令:
docker import mobycounter.tar mobycounter
这将导入镜像并将其命名为 mobycounter
;你可以运行以下命令来检查镜像是否可用:
docker images
你应该看到类似这样的内容:
一旦你确认镜像已经导入并命名为 mobycounter
,可以通过运行以下命令启动一个容器:
docker-compose up -d
再次强调,你可以通过运行以下命令,打开浏览器并开始点击来放置 logo:
open http://$(docker-machine ip chapter06)/
虽然看起来差异不大,但让我们看看背后到底发生了什么。
Packer 与 Docker Build 的对比
在我们详细讨论两种构建镜像的方法之间的差异之前,让我们再试一次运行 Packer。
不过这次,让我们尝试减少镜像的大小:与其使用已经预装了 nodejs 的 russmckendrick/nodejs
镜像,不如使用构建这个镜像的基础镜像 russmckendrick/base
。
这个镜像仅安装了 bash;通过 Packer 安装 NodeJS 和应用程序:
{
"builders":[{
"type": "docker",
"image": "russmckendrick/base",
"export_path": "mobycounter-small.tar"
}],
"provisioners":[
{
"type": "file",
"source": "app",
"destination": "/srv"
},
{
"type": "file",
"source": "npmrc",
"destination": "/etc/npmrc"
},
{
"type": "shell",
"inline": [
"apk update",
"apk add --update nodejs",
"npm -g install npm",
"cd /srv/app",
"npm install",
"rm -rf /var/cache/apk/**/",
"npm cache clean"
]
}
]
}
如你所见,我们在 shell 提供程序中添加了一些命令;这些命令使用 Alpine Linux 的包管理器执行更新、安装 nodejs、配置应用程序,最后清理 apk 和 npm 缓存。
如果你愿意,可以使用以下命令构建镜像:
packer build docker-small.json
这将为我们留下两个镜像文件。我还在容器运行时,通过以下命令导出了我们使用 Dockerfile
构建的容器副本:
docker export docker_web_1 > docker_web.tar
我现在有三个镜像文件,三个都在运行相同的应用程序,并且安装了相同的软件堆栈,尽可能使用相同的命令。如以下文件大小列表所示,镜像大小是有差异的:
-
Dockerfile(使用
russmckendrick/nodejs
)= 52 MB -
Packer(使用
russmckendrick/nodejs
)= 47 MB -
Packer(使用 packer 安装完整堆栈)= 40 MB
12 MB 可能看起来不多,但当你处理的是一个只有 52 MB 的镜像时,这样的节省还是相当可观的。
那么,为什么会有差异呢?让我们先讨论 Docker 镜像的工作方式。
它们本质上是由基于基础镜像上层的更改层组成的。当我们使用 Dockerfile
构建第一个镜像时,你可能注意到每一行 Dockerfile
都生成了构建过程中的一个不同步骤。
每个步骤实际上是 Docker 启动一个新的文件系统层来存储该构建步骤的更改。例如,当我们的Dockerfile
运行时,我们有六个文件系统层:
FROM russmckendrick/nodejs
ADD . /srv/app
WORKDIR /srv/app
RUN npm install
EXPOSE 80
ENTRYPOINT ["node", "index.js"]
第一层包含基础操作系统以及 NodeJS 安装所在的层,第二层包含应用程序本身的文件。
第三层仅包含设置workdir
变量的元数据;接下来是包含应用程序的 NodeJS 依赖项的层。第五层和第六层仅包含配置暴露端口和“入口点”的元数据。
由于每一层实际上是镜像文件中的一个单独的归档文件,我们还需为这些归档文件承担额外的开销。
更好的示例是查看 Docker Hub 上一些最受欢迎的镜像,这些镜像可以通过 ImageLayers 网站找到,网址为imagelayers.io/
。
该网站是 Century Link Labs 提供的一个工具(labs.ctl.io/
),用于可视化通过Dockerfile
构建的 Docker 镜像。
从以下屏幕截图可以看到,一些官方镜像非常复杂,并且也相当大:
你可以通过以下网址查看上一页:
imagelayers.io/?images=java:latest,golang:latest,node:latest,python:latest,php:latest,ruby:latest
。
即使官方镜像因为 Docker 聘请了 Alpine Linux 的创始人并将官方镜像迁移到更小的基础操作系统而变得更小(查看以下黑客新闻帖子获取更多信息news.ycombinator.com/item?id=11000827
),这也不会改变每个镜像所需的层数。同样值得注意的是,每个镜像最多可以包含 127 层。
那么 Packer 做得有什么不同呢?它不是为每个步骤创建一个单独的文件系统层,而是仅生成两个层:第一个层是你定义的基础镜像,第二个层则包含其他所有内容——这就是我们节省空间的地方。
使用 Packer 相较于 Dockfiles 的另一个优点是你可以重用脚本。想象一下,你在本地开发时使用了 Docker,但当你需要进入生产环境时,出于某种原因,你必须在容器化的虚拟机上启动。使用 Packer,你可以做到这一点,因为你可以使用与开发容器相同的构建脚本来引导虚拟机。
如我之前提到的,我已经使用 Packer 一段时间了,它帮助我极大地提高了效率,可以使用一个工具针对不同平台构建相同的构建脚本。这个方法带来的持续性非常值得学习 Packer 这类工具的初期投入,因为从长远来看,你将节省大量时间;它还帮助消除了我们在第一章 扩展 Docker 简介中讨论过的“在开发环境中有效”的误区。
使用这种方法有一些缺点,可能会让一些人却步。
在我看来,最大的一个问题是,虽然你可以将最终镜像自动推送到 Docker Hub,但无法将其添加为自动构建。
这意味着,尽管该镜像可能对用户开放下载,但由于人们无法看到具体添加了哪些内容,因此它可能不被认为是可信的。
接下来是缺乏对元数据的支持——配置运行时选项(例如暴露端口和容器启动时默认执行的命令)的功能当前不受支持。
尽管这可以被看作是一个缺点,但通过在 Docker Compose 文件中定义你在 Dockerfile
中定义的内容,或者通过 docker run
命令直接传递信息,可以轻松克服。
镜像摘要
总结一下,如果你需要构建不仅仅是容器镜像,还需要针对不同平台进行构建,那么 Packer 正是你需要的工具。如果你只是需要构建容器镜像,那么坚持使用 Dockerfile
构建可能会更好。
我们在本章中看过的其他一些工具,例如 Ansible 和 Puppet,也支持通过对 Dockerfile
执行 docker build
命令来构建镜像,因此有很多方法可以将其集成到你的工作流程中,这将引导我们进入下一个要讨论的工具:Jenkins。
在继续之前,让我们快速检查一下你是否没有运行任何 Docker 主机。为此,运行以下命令检查是否有 Docker 主机,并将其移除:
docker-machine ls
docker-machine rm <name-of-host>
不要忘记只移除你在本书中跟随操作时使用的主机;不要移除任何你用于自己项目的主机!
使用 Jenkins 提供 Docker 服务
Jenkins 是一个相当庞大的主题,很难在单章的小节中全部覆盖,因此本教程将非常基础,仅涉及构建和启动容器。
另外需要注意的是,我将介绍 Jenkins 2.0;在撰写本文时,首个 beta 版本刚刚发布,这意味着虽然随着主题等的改进,某些细节可能会有所变化,但所有功能和基本功能已基本确定。
我们选择讲解 Jenkins 2.0 而不是 Jenkins 1.x 分支的原因是,因为在 Jenkins 看来,Docker 现在已经是一个一级公民,这意味着它完全支持并接受 Docker 的工作方式。关于 Jenkins 2.0 当前状态的完整概述,可以在 jenkins.io/2.0/
查阅。
那么,什么是 Jenkins 呢?Jenkins 是一款用 Java 编写的开源持续集成工具,它有很多用途。
就我个人而言,我对于 Jenkins 的接触相当晚;由于我是从运维背景过来的,一直把它当作一个用于运行单元测试的工具忽视了;然而,随着我逐渐深入到编排和自动化的工作中,我发现确实需要一个工具,能够根据单元测试的结果来执行任务。
正如我之前提到的,我不会详细讲解 Jenkins 的测试部分;关于这个功能,有很多资源可以参考,例如以下内容:
-
精通 Jenkins,作者:Jonathan McAllister
-
Jenkins 持续集成手册,作者:Alan Mark Berg
这些都可以从 www.packtpub.com/
获取。
准备环境
与其在本地运行,我们不如启动一个 DigitalOcean 虚拟机,并在那里安装 Jenkins。首先,我们需要使用 Docker Machine 启动虚拟机:
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0 \
--digitalocean-region lon1 \
--digitalocean-size 1gb \
jenkins
一旦虚拟机启动,我们就不需要配置本地 Docker 客户端与虚拟机进行通信了,因为 Jenkins 会处理与 Docker 相关的所有操作。
因为我们需要 Jenkins 来运行 Docker,所以我们需要直接在虚拟机上安装 Jenkins,而不是作为容器来运行;首先,我们需要通过 SSH 登录到虚拟机。为此,请运行以下命令:
docker-machine ssh jenkins
现在,在这个虚拟机上,我们需要安装 Docker Compose、Jenkins 以及所有相关的前置条件。让我们从安装 Docker Compose 开始。我已经写了一个快速脚本来完成这个操作,执行以下命令即可运行:
curl -fsS https://raw.githubusercontent.com/russmckendrick/docker-install/master/install-compose | bash
现在我们已经安装了 Docker Compose,接下来是安装 Jenkins。由于版本 2 当前还在 beta 阶段,因此它尚未进入任何主要的软件仓库;不过,它有一个 DEB 包可以使用。
为了安装它,我们需要下载本地副本并运行以下命令:
apt-get install gdebi-core
这将安装 gdebi
工具,接着我们将使用它来安装 Jenkins 及其依赖项:
wget http://pkg.jenkins-ci.org/debian-rc/binary/jenkins_2.0_all.deb
gdebi jenkins_2.0_all.deb
现在 Jenkins 已经安装好了,我们需要将 Jenkins 用户添加到 Docker 组中,以便该用户可以与 Docker 进行交互:
usermod -aG docker jenkins
最后,为了确保 Jenkins 能够识别到它已经被加入到组中,我们需要使用以下命令重启它:
/etc/init.d/jenkins restart
现在,你可以打开浏览器来完成安装:
open http://$(docker-machine ip jenkins):8080/
当你的浏览器打开时,你应该看到如下界面:
出于安全原因,在启动 Jenkins 容器时,会生成一个随机字符串;在继续安装之前,Jenkins 会要求你确认该字符串是什么。你可以通过运行此命令来找出它:
less /var/lib/jenkins/secrets/initialAdminPassword
你可以按Q键退出less
。
这一功能非常受欢迎,因为如果一开始没有正确地保护你的 Jenkins 安装,可能会产生严重的后果,正如我发现的那样——第三方劫持了我一直忘记关闭的一个测试 Jenkins 1.x 安装版本——哎呀!
输入初始管理员密码后,点击继续按钮。
下一页将会询问你希望安装哪些插件:
对于我们的目的,只需点击安装推荐插件,该选项已高亮显示。下一页将展示推荐插件的安装进度:
完成安装需要一两分钟。安装完成后,系统将要求你创建一个 Jenkins 用户:
正如我之前提到的,确保一开始就保护好你的 Jenkins 安装非常重要,所以我建议你不要跳过这一步。填写完所需的信息后,点击保存并完成按钮。如果一切顺利,你将看到以下页面:
现在你需要做的就是点击开始使用 Jenkins,然后你将会登录并进入起始页面,界面如下所示:
这个安装过程是 Jenkins 2 带来的许多改进之一;以前,你需要先安装 Jenkins,然后手动完成几个向导和程序来同时确保安全性和配置软件,正如我之前提到的,如果没有做好这些,可能会带来严重后果。
设置的最后一步是安装 CloudBees Docker Pipeline 插件;为此,请点击左侧菜单中的管理 Jenkins按钮,然后点击管理插件按钮。
由于这是一个新安装,你可能会看到关于插件更新的消息。忽略重启 Jenkins 的请求;我们将在安装过程中完成这一操作。
主屏幕上有四个标签;点击可用按钮,你将看到所有 Jenkins 插件的列表。
在主屏幕的右上角,有一个标为筛选的搜索框。在这里输入Docker Pipeline
,你应该会看到一个结果。勾选安装框,然后点击现在下载并在重启后安装按钮。
重新启动 Jenkins 需要一两分钟;重启后,你将被提示使用安装过程中提供的凭据重新登录。
现在你已经安装并配置好 Jenkins,接下来是添加我们的管道。为此,我们需要一个应用程序来添加。
创建应用程序
以下 GitHub 仓库提供了一个基于 Moby Counter 的示例应用程序:https://github.com/russmckendrick/jenkins-docker-example/tree/master
。主页如下所示:
在我们添加应用程序之前,最好先 fork 代码,因为我们稍后将对代码库进行更改。为此,请点击屏幕右上角的Fork按钮。系统将询问你希望将仓库 fork 到哪里。fork 完成后,记下仓库的 URL。
由于我拥有这个仓库,我无法将其 fork。为此,我创建了一个名为jenkins-pipeline
的副本,因此你将在接下来的部分中看到对此的引用。
创建管道
现在 Jenkins 已经配置好,我们也有一个包含应用程序的 GitHub 仓库,接下来我们想要部署。是时候撸起袖子,在 Jenkins 中配置管道了。
首先,在主页点击创建新作业按钮,你将进入一个包含多个选项的页面,在顶部框中输入管道的名称。
我将我的命名为Docker Pipeline
,然后点击Pipeline按钮。你应该会看到一个小框,底部有一个写着OK的按钮,点击OK按钮以创建管道,这将带你进入配置界面:
你现在将处于管道配置界面,正如你所看到的,有很多选项。我们将保持非常简单,只添加一个管道脚本。脚本看起来类似于以下代码:
node {
stage 'Checkout'
git url: 'https://github.com/russmckendrick/jenkins-pipeline.git'
stage 'build'
docker.build('mobycounter')
stage 'deploy'
sh './deploy.sh'
}
在你将脚本添加到配置页面的 Pipeline 部分之前,请将 Git URL 替换为你自己仓库的 URL。其他所有选项保持不变,点击Save按钮:
就这样,我们的管道已经配置好。我们告诉 Jenkins 每次触发构建时执行以下三项任务:
-
Checkout:这将从你的 GitHub 仓库下载我们应用程序的最新代码。
-
Build:这使用 GitHub 仓库中的
Dockerfile
来构建Mobycounter
镜像。 -
Deploy:这运行一个脚本,清除任何当前正在运行的容器,然后使用包含的 Docker Compose 文件重新启动应用程序。在启动 Redis 时,Docker Compose 文件使用内建的 volume 驱动程序来处理
/data
,这意味着 Docker 图标的位置将在容器重新启动之间保持不变。
要触发构建,请点击左侧菜单中的立即构建按钮。如果一切顺利,你应该会看到类似以下截图的内容:
如你所见,所有三个任务都顺利执行。你应该能够通过打开浏览器并使用以下命令查看应用程序:
open http://$(docker-machine ip jenkins)/
放置一些标志来测试一切是否按预期工作,完成了,你已经使用 Jenkins 部署了你的应用:
等一下——出现了问题!正如你可能已经注意到的,页面标题是错误的。
让我们来修复这个问题。为此,请进入你的 GitHub 仓库中的以下页面:your-github-repo
| src
| client
| index.html
。在这里,点击编辑按钮。进入编辑页面后,在 <title>
和 </title>
标签之间更新标题,然后点击提交更改按钮。
现在你已经更新了应用程序代码,回到 Jenkins,再次点击立即构建。这将触发第二次构建,部署我们在 GitHub 中所做的更改。
如前截图中第二个浏览器标签所示,我们的应用程序标题已经更改,第二次构建也成功了。如果你刷新应用程序窗口,你应该能看到标题已更新,Docker 标志也仍在你放置的位置。
还需要注意的是,第二次构建确认了我们的初始构建和当前构建之间有一个提交差异。此外,构建本身所需的时间比原始构建少;这是因为 Docker 不需要第二次下载基础镜像。
你可以通过将鼠标悬停在你想查看日志的阶段上,并点击日志链接来查看每个任务的日志。这会弹出一个对话框,显示该任务的日志:
你还可以通过点击左侧菜单中的构建编号,例如 #2
,然后点击控制台输出按钮,查看每次构建的完整控制台输出:
这在你的构建有错误时非常有用。尝试点击一些选项,如Docker 指纹和更改,查看每次构建过程中记录的其他信息。
返回到 Jenkins 的主页面,你应该能够看到一个快速的构建概览。你还应该能看到管道旁边有一个太阳图标,这意味着一切正常。
如果第二次构建有问题怎么办?假设在我们编辑页面标题时在 Dockerfile 中犯了语法错误,会发生什么呢?
Jenkins 会从 GitHub 检查更新文件,启动更新镜像的构建,检测到错误后失败。由于这一阶段会产生错误,因此部署阶段不会执行,这意味着我们的应用程序仍将以当前状态运行,错误的标题也会保留。
这正是 Jenkins 的强大之处,如果你在代码和部署管道中配置了足够的测试,你可以防止任何可能影响服务的变更被部署,而且它还记录了足够的信息,当出现错误时,它能成为一个极为宝贵的资源,帮助你追踪问题。
Jenkins 总结
正如你可能注意到的,我们在 Jenkins 的讨论中只是触及了冰山一角,许多功能我们并未覆盖,因为它超出了本书的范围。
然而,从我们所讨论的内容中,我希望你能看到使用像 Jenkins 这样的持续集成和部署平台的价值,它可以帮助你构建和部署容器及代码。如果你部署任何类型的代码,千万不要像我一样迟到了,考虑使用 Jenkins 来协助你,不要等到你部署了一个严重影响应用程序的错误之后才后悔。
总结
本章中我们所讨论的所有工具有一个共同点,那就是它们都迅速发展,提供对 Docker 的支持,填补了核心 Docker 工具集中缺失的功能空白。
在过去的 12 个月里,Docker 的快速发展意味着其中一些工具可能不再是必需的。
然而,由于这些工具在 Docker 之外提供了广泛的功能,这意味着它们仍然可以成为你日常工作流程中非常有价值的一部分,即使 Docker 只是你所使用的技术之一。
使用本章中的工具有一个不足之处,那就是它们缺乏对容器部署位置的智能决策,你仍然需要指示工具去将容器 A 放置在 Docker 主机 Z 上。
在我们的下一章中,我们将会介绍调度程序,它们会根据主机的可用性、利用率以及其他规则(例如不要将容器 A 与容器 B 部署在同一主机上)来决定将容器部署到哪里,这意味着你不再局限于固定的基础设施。
第七章:调度器的介绍
在本章中,我们将介绍几种不同的调度器,它们能够在您自己的基础设施以及公共云基础设施上启动容器。首先,我们将查看两种不同的调度器,两个调度器我们都将用来在亚马逊云服务(Amazon Web Services)上启动集群。这两个调度器如下:
-
Kubernetes:
kubernetes.io/
-
Amazon ECS:
aws.amazon.com/ecs/
接下来,我们将介绍一个提供自己调度器并支持其他调度器的工具:
- Rancher:
rancher.com/
让我们直接深入了解 Kubernetes。
入门 Kubernetes
Kubernetes 是一个开源工具,最初由 Google 开发。它被描述为:
“一个用于自动化部署、操作和扩展容器化应用程序的工具。它将组成应用程序的容器分组为逻辑单元,以便于管理和发现。Kubernetes 基于 Google 在过去十多年中运行生产工作负载的经验,并结合了社区中最佳的理念和实践。”
www.kubernetes.io
虽然它不是 Google 用来在内部部署容器的确切工具,但它从零开始构建,提供相同的功能。Google 也在慢慢过渡,计划在内部自己使用 Kubernetes。它基于以下三个原则进行设计:
-
行星级扩展:Kubernetes 在与 Google 每周运行数十亿个容器的相同原则下设计,能够在不增加运维团队的情况下扩展。
-
永不停止成长:无论是在本地进行测试还是运行全球企业,Kubernetes 的灵活性都会随着您的需求增长,以便无论需求多么复杂,都能始终如一且轻松地交付您的应用程序。
-
随处运行:Kubernetes 是开源的,您可以自由选择在本地、混合或公共云基础设施上使用,让您轻松地将工作负载迁移到对您来说重要的地方。
开箱即用,它配备了一个相当成熟的功能集:
-
自动装箱:这是该工具的核心,一个强大的调度器,基于当前集群节点上消耗的资源来决定在哪里启动容器。
-
水平扩展:这使您能够扩展应用程序,无论是手动扩展还是基于 CPU 使用率自动扩展。
-
自我修复:您可以配置状态检查;如果容器未通过检查,它将重新启动,并且资源可用时会重新部署。
-
负载均衡与服务发现:Kubernetes 允许您将容器附加到服务上,这些服务可以将您的容器暴露到本地或外部。
-
存储编排:Kubernetes 开箱即用地支持多种后端存储模块,包括 Google Cloud Platform、AWS 以及 NFS、iSCSI、Gluster 和 Flocker 等服务。
-
机密和配置管理:这允许你将 API 密钥等机密信息部署并更新到容器中,而无需暴露它们或重建容器镜像。
我们可以讨论更多的功能;但与其一一介绍这些功能,不如直接进入正题,安装一个 Kubernetes 集群。
安装 Kubernetes
正如 Kubernetes 官网所提示的,安装 Kubernetes 的方式有很多种。很多文档都提到了 Google 自家的公有云;然而,我们并不打算引入第三方公有云,而是要将 Kubernetes 集群部署到 Amazon Web Services 上。
在开始 Kubernetes 安装之前,我们需要确保已安装并配置了 AWS 命令行工具。
注意
AWS 命令行工具 (CLI) 是一个统一的工具,用于管理 AWS 服务。只需下载并配置一个工具,你就可以通过命令行控制多个 AWS 服务,并通过脚本实现自动化:
由于我们在前几章已经多次使用了 Homebrew,因此我们将使用它来安装工具。只需运行以下命令:
brew install awscli
工具安装完成后,你可以通过运行以下命令来配置这些工具:
aws configure
系统将提示你输入以下四项信息:
-
AWS 访问密钥 ID
-
AWS 密钥访问密钥
-
默认区域名称
-
默认输出格式
你应该已经从第二章《引入第一方工具》中获取了 AWS 访问密钥和密钥访问密钥。对于默认区域名称
,我使用了eu-west-1
(这是离我最近的区域),并将默认输出格式
保持为None
:
现在我们已经安装并配置了 AWS 命令行工具,接下来可以安装 Kubernetes 命令行工具。这是一个二进制文件,可以让你与 Kubernetes 集群进行交互,就像本地 Docker 客户端连接远程 Docker 引擎一样。可以通过 Homebrew 安装,运行以下命令即可:
brew install kubernetes-cli
一旦安装完成,我们不需要再配置工具,因为接下来我们运行的 Kubernetes 主部署脚本会自动处理这些配置。
现在我们已经具备了启动和与 AWS Kubernetes 集群交互所需的工具,可以开始部署集群本身了。
在开始安装之前,我们需要让安装脚本了解一些关于我们希望在哪里启动集群以及我们希望集群的规模的信息,这些信息将作为环境变量传递给安装脚本。
首先,我希望它在欧洲启动:
export KUBE_AWS_ZONE=eu-west-1c
export AWS_S3_REGION=eu-west-1
另外,我需要两个节点:
export NUM_NODES=2
最后,我们需要指示安装脚本,我们希望在 Amazon Web Services 中启动 Kubernetes:
export KUBERNETES_PROVIDER=aws
现在我们已经告诉安装程序我们希望在何处启动 Kubernetes 集群,是时候实际启动它了。为此,请运行以下命令:
curl -sS https://get.k8s.io | bash
这将下载安装程序和最新的 Kubernetes 代码库,然后启动我们的集群。这个过程本身可能需要八到十五分钟,具体取决于你的网络连接。
如果你不想自己运行这个安装过程,你可以在以下网址查看 Kubernetes 集群在 Amazon Web Services 上部署的录制视频:
一旦安装脚本完成,你将获得访问你的 Kubernetes 集群的信息,你还应该能够运行以下命令来获取集群内节点的列表:
kubectl get nodes
这应该返回类似于以下截图的内容:
此外,如果你打开了 AWS 控制台,你应该会看到一个新的专用于 Kubernetes 的 VPC 已被创建:
你还会看到三个 EC2 实例已经启动到 Kubernetes 的 VPC 中:
在我们开始将应用程序部署到 Kubernetes 集群之前,最后需要注意的一点是集群的用户名和密码凭据。
正如你在安装过程中看到的那样,这些信息存储在 Kubernetes CLI 配置文件的最底部,你可以通过运行以下命令来获取这些信息:
tail -3 ~/.kube/config
现在我们的 Kubernetes 集群已经启动,并且我们可以使用命令行工具访问它,我们可以开始启动应用程序。
启动我们的第一个 Kubernetes 应用程序
首先,我们将启动一个非常基础的 NGINX 容器集群,集群中的每个容器将提供一个简单的图形,并在页面上显示其主机名。你可以在 Docker Hub 上找到该容器的镜像,网址是hub.docker.com/r/russmckendrick/cluster/
。
就像我们在前面章节中查看的许多工具一样,Kubernetes 使用 YAML 格式来定义文件。我们将要部署到集群中的文件是以下文件:
apiVersion: v1
kind: ReplicationController
metadata:
name: nginxcluster
spec:
replicas: 5
selector:
app: nginxcluster
template:
metadata:
name: nginxcluster
labels:
app: nginxcluster
spec:
containers:
- name: nginxcluster
image: russmckendrick/cluster
ports:
- containerPort: 80
我们将文件命名为nginxcluster.yaml
。要启动它,请运行以下命令:
kubectl create -f nginxcluster.yaml
一旦启动,你可以通过运行以下命令查看活动的 pod:
kubectl get pods
你可能会发现需要多次运行kubectl
get pods
命令,确保一切正常运行:
现在你的 pod 已经启动并运行,我们需要将它们暴露出去,这样就可以通过浏览器访问集群了。为此,我们需要创建一个服务。要查看当前的服务,输入以下命令:
kubectl get services
你应该只看到主要的 Kubernetes 服务。当我们启动 pod 时,我们定义了一个副本控制器,这是管理 pod 数量的过程。要查看副本控制器,运行以下命令:
kubectl get rc
你应该能看到 nginxcluster 控制器,并且在期望和当前列中都有五个 pod。现在,我们已经确认我们的副本控制器处于活动状态,并且注册了期望数量的 pod,接下来让我们创建服务并通过运行以下命令将 pod 暴露到外部:
kubectl expose rc nginxcluster --port=80 --type=LoadBalancer
现在,如果你再次运行get services
命令,你应该能看到我们的新服务:
kubectl get services
你的终端会话应该类似于以下截图:
很好,现在你已经将 pod 暴露到互联网。但你可能注意到集群的 IP 地址是内部地址,那你该如何访问你的集群呢?
由于我们在 Amazon Web Services 上运行 Kubernetes 集群,当你暴露服务时,Kubernetes 会向 AWS 发出 API 调用并启动一个弹性负载均衡器。你可以通过运行以下命令获取负载均衡器的 URL:
kubectl describe service nginxcluster
如你所见,在我的情况下,负载均衡器可以通过http:// af92913bcf98a11e5841c0a7f321c3b2-1182773033.eu-west-1.elb.amazonaws.com/
访问。
在浏览器中打开负载均衡器 URL 可以看到我们的容器页面:
最后,如果你打开 AWS 控制台,你应该能够看到 Kubernetes 创建的弹性负载均衡器:
一个高级示例
让我们尝试做一些比启动几个相同实例并进行负载均衡更高级的操作。
在以下示例中,我们将启动我们的 WordPress 堆栈。这次我们将挂载弹性块存储卷来存储我们的 MySQL 数据库和 WordPress 文件:
“Amazon Elastic Block Store (Amazon EBS) 为在 AWS 云中与 Amazon EC2 实例一起使用的持久性块级存储卷提供服务。每个 Amazon EBS 卷会自动在其可用区内进行复制,以保护您免受组件故障的影响,提供高可用性和持久性。Amazon EBS 卷提供了运行工作负载所需的一致性和低延迟性能。使用 Amazon EBS,您可以在几分钟内扩展或缩减您的使用量——所有这些都只需为您配置的部分支付低廉的费用。” -
aws.amazon.com/ebs/
创建卷
在启动我们的 Pod 和服务之前,我们需要创建两个将附加到 Pod 的 EBS 卷。由于我们已经安装并配置了 AWS 命令行接口,因此我们将使用它来创建卷,而不是登录控制台并通过 GUI 创建。
要创建这两个卷,只需运行以下命令两次,确保更新可用区,以匹配您的 Kubernetes 集群配置启动的位置:
aws ec2 create-volume --availability-zone eu-west-1c --size 10 --volume-type gp2
每次运行该命令时,您将获得一个返回的 JSON 块,其中包含在创建卷时生成的所有元数据:
记录下每个卷的 VolumeId,当我们创建 MySQL 和 WordPress Pod 时,您将需要知道这些 ID。
启动 MySQL
现在我们已经创建了卷,接下来可以启动 MySQL Pod 和服务。首先,从 Pod 定义开始,确保在文件的底部添加一个卷 ID:
apiVersion: v1
kind: Pod
metadata:
name: mysql
labels:
name: mysql
spec:
containers:
- resources:
image: russmckendrick/mariadb
name: mysql
env:
- name: MYSQL_ROOT_PASSWORD
value: yourpassword
ports:
- containerPort: 3306
name: mysql
volumeMounts:
- name: mysql-persistent-storage
mountPath: /var/lib/mysql
volumes:
- name: mysql-persistent-storage
awsElasticBlockStore:
volumeID:<insert your volume id here>
fsType: ext4
如您所见,这与我们第一个 Kubernetes 应用程序非常相似,只不过这次我们仅创建了一个 Pod,而不是带有复制控制器的 Pod。
如您所见,我已将我的卷 ID 添加到文件的底部;在启动 Pod 时,您需要添加您自己的卷 ID。
我将文件命名为mysql.yaml
,因此要启动它,我们需要运行以下命令:
kubectl create -f mysql.yaml
Kubernetes 将在尝试启动 Pod 之前验证mysql.yaml
文件;如果出现任何错误,请检查缩进是否正确:
现在应该已经启动了 Pod;但是,您可能需要检查它是否成功启动。运行以下命令查看 Pod 的状态:
kubectl get pods
如果您看到 Pod 的状态为Pending
,就像我一样,您可能会想知道发生了什么?幸运的是,您可以通过使用describe
命令,轻松获取关于我们尝试启动的 Pod 的更多信息:
kubectl describe pod mysql
这将打印出有关 Pod 的所有信息,从以下终端输出可以看出,我们的集群容量不足,无法启动 Pod:
我们可以通过运行以下命令,删除之前的 Pods 和服务来释放一些资源:
kubectl delete rc nginxcluster
kubectl delete service nginxcluster
当你运行命令删除 nginxcluster
后,mysql Pod 应该会在几秒钟内自动启动:
现在 Pod 已经启动,我们需要附加一个服务,使得 3306
端口能够暴露。与之前使用 kubectl
命令不同,我们将使用一个名为 mysql-service.yaml
的第二个文件:
apiVersion: v1
kind: Service
metadata:
labels:
name: mysql
name: mysql
spec:
ports:
- port: 3306
selector:
name: mysql
要启动服务,只需运行以下命令:
kubectl create -f mysql-service.yaml
现在我们已经启动了 MySQL Pod 和服务,是时候启动实际的 WordPress 容器了。
启动 WordPress
和 MySQL Pod 及服务一样,我们将使用两个文件来启动我们的 WordPress 容器。第一个文件是 Pod 配置:
apiVersion: v1
kind: Pod
metadata:
name: wordpress
labels:
name: wordpress
spec:
containers:
- image: wordpress
name: wordpress
env:
- name: WORDPRESS_DB_PASSWORD
value: yourpassword
ports:
- containerPort: 80
name: wordpress
volumeMounts:
- name: wordpress-persistent-storage
mountPath: /var/www/html
volumes:
- name: wordpress-persistent-storage
awsElasticBlockStore:
volumeID: <insert your volume id here>
fsType: ext4
由于 EBS 卷一次只能附加到一个设备,记得在此使用你创建的第二个 EBS 卷。调用 wordpress.yaml
文件并使用以下命令启动:
kubectl create -f wordpress.yaml
然后等待 Pod 启动:
由于我们已经删除了 nginxcluster
,应该有足够的资源直接启动 Pod,这意味着你不应该遇到任何错误。
尽管 Pod 应该已经在运行,但最好检查容器是否没有问题地启动。为此,运行以下命令:
kubectl logs wordpress
这应该会打印出容器日志,你将看到类似以下截图的内容:
既然 Pod 已经启动,且 WordPress 看起来已按预期引导完成,我们应该启动服务。和 nginxcluster
一样,这将创建一个弹性负载均衡器。服务定义文件类似于以下代码:
apiVersion: v1
kind: Service
metadata:
labels:
name: wpfrontend
name: wpfrontend
spec:
ports:
- port: 80
selector:
name: wordpress
type: LoadBalancer
要启动它,运行以下命令:
kubectl create -f wordpress-service.yaml
启动后,检查是否已创建服务,并通过运行以下命令获取弹性负载均衡器的详细信息:
kubectl get services
kubectl describe service wpfrontend
当我运行命令时,我得到了以下输出:
几分钟后,你应该能够访问弹性负载均衡器的 URL,正如预期的那样,你会看到一个 WordPress 安装界面:
如同我们在第三章中讲解 卷插件 时所做的那样,完成安装,登录并附加一张图片到 Hello World
帖子。
现在 WordPress 网站已启动并运行,让我们尝试删除 wordpress Pod 并重新启动它。首先,记录下容器 ID:
kubectl describe pod wordpress | grep "Container ID"
然后删除 Pod 并重新启动:
kubectl delete pod wordpress
kubectl create -f wordpress.yaml
再次检查容器 ID,以确保我们有一个不同的 ID:
kubectl describe pod wordpress | grep "Container ID"
访问你的 WordPress 网站时,你应该会看到一切就如你离开时的状态:
如果我们愿意,我们也可以对 MySQL Pod 执行相同的操作,而我们的数据会保持原样,因为它存储在 EBS 卷中。
让我们通过运行以下命令来删除 WordPress 应用的 Pod 和服务:
kubectl delete pod wordpress
kubectl delete pod mysql
kubectl delete service wpfrontend
kubectl delete service mysql
这样我们就可以为本章的下一部分准备一个干净的 Kubernetes 集群。
支持工具
你可能会想,为什么我们一开始部署 Kubernetes 集群时还要抓取用户名和密码,因为到目前为止我们还没用到它。让我们来看看作为 Kubernetes 集群一部分部署的支持工具。
当你首次部署 Kubernetes 集群时,屏幕上会打印出一系列 URL,我们将在本节中使用这些。如果你没有记下这些 URL,不用担心,你可以通过运行以下命令获取所有支持工具的 URL:
kubectl cluster-info
这将列出你 Kubernetes 集群各个部分的 URL:
你需要用户名和密码才能查看其中的一些工具,如果你手头没有这些信息,可以通过运行以下命令获取:
tail -3 ~/.kube/config
Kubernetes 仪表盘
首先,让我们来看一下 Kubernetes 仪表盘。你可以通过在浏览器中输入 Kubernetes-dashboard 的 URL 来访问它。进入后,根据浏览器的不同,你可能会收到证书的警告,接受这些警告后,你将看到登录提示。在这里输入用户名和密码。登录后,你将看到如下屏幕:
让我们通过 UI 部署 NGINX Cluster 应用。为此,点击 部署应用 并输入以下内容:
-
应用名称 =
nginx-cluster
-
容器镜像 =
russmckendrick/cluster
-
Pod 数量 =
5
-
端口 = 留空
-
端口 =
80
-
目标端口 =
80
-
勾选 将服务暴露到外部
点击 部署 后,你将返回到概览页面:
在这里,你可以点击 nginx-cluster,进入概览页面:
如你所见,这里提供了 Pod 和服务的所有详细信息,包括 CPU 和内存利用率,以及 Elastic Load Balancer 的链接。点击该链接将带你进入镜像的默认集群页面以及容器的主机名。
让我们保持 nginx-cluster 运行,以便查看下一个工具。
Grafana
接下来我们要打开的 URL 是 Grafana;访问该 URL 后,你应该会看到一个比较暗且大部分为空的页面。
Grafana 是记录我们在 Kubernetes 仪表盘中看到的所有指标的工具。让我们来看看集群的统计信息。为此,点击 集群 仪表盘:
如你所见,这里为我们提供了一个系统监控工具应显示的所有指标的细分。向下滚动,你可以看到:
-
CPU 使用情况
-
内存使用情况
-
网络使用情况
-
文件系统使用情况
无论是整体还是单个节点,你都可以通过点击Pods仪表板查看 Pods 的详细信息。由于 Grafana 从运行中的 InfluxDB Pod 获取数据,而该 Pod 自我们首次启动 Kubernetes 集群以来一直在运行,因此你可以查看每个已启动 Pod 的指标,即使它当前并未运行。以下是我们在安装 WordPress 时启动的mysql
Pod 的 Pod 指标:
我建议你四处浏览,查看一些其他 Pod 的指标。
ELK
最后一个我们要查看的工具是自我们首次启动 Kubernetes 集群以来一直在后台运行的 ELK 栈。ELK 栈是以下三种不同工具的集合:
-
Elasticsearch:
www.elastic.co/products/elasticsearch
-
Logstash:
www.elastic.co/products/logstash
-
Kibana:
www.elastic.co/products/kibana
它们共同构成了一个强大的中央日志平台。
当我们在本章节前面运行以下命令时(请注意,由于我们已经移除了 WordPress Pod,你将无法再次运行该命令):
kubectl logs wordpress
对于我们的wordpress
Pod,显示的日志文件条目实际上是从 Elasticsearch Pod 读取的。Elasticsearch 自带一个名为 Kibana 的仪表板。让我们打开 Kibana 的 URL。
当你首次打开 Kibana 时,系统会要求你配置一个索引模式。为此,只需从下拉框中选择时间字段名称,并点击创建按钮:
一旦索引模式创建完成,点击顶部菜单中的Discover链接。你将看到 Logstash 安装在每个节点上,并将日志数据发送到 Elasticsearch 后的所有日志数据概览:
如你所见,有大量的数据被记录;事实上,当我查看时,仅 15 分钟内就记录了 4,918 条消息。这里有很多数据,我建议你点击查看并尝试一些搜索,以了解记录了什么内容。
为了让你了解每个日志条目是什么样的,下面是我其中一个 nginx-cluster Pod 的日志条目:
剩余的集群工具
我们尚未在浏览器中打开的剩余集群工具如下:
-
Kubernetes
-
Heapster
-
KubeDNS
-
InfluxDB
这些都是 API 端点,因此你不会看到除 API 响应之外的任何内容,它们是 Kubernetes 内部用来管理和调度集群的。
销毁集群
由于集群位于你的亚马逊云服务账户中的按需计费实例上,我们应当考虑删除该集群;为此,让我们通过运行以下命令重新输入我们首次部署 Kubernetes 集群时使用的原始配置:
export KUBE_AWS_ZONE=eu-west-1c
export AWS_S3_REGION=eu-west-1
export NUM_NODES=2
export KUBERNETES_PROVIDER=aws
然后,从你首次部署 Kubernetes 集群的相同位置,运行以下命令:
./kubernetes/cluster/kube-down.sh
这将连接到 AWS API,并开始拆除所有与 Kubernetes 一起启动的实例、配置和其他资源。
该过程需要几分钟时间,请勿中断,否则可能会留下会产生费用的资源,这些资源将继续在你的亚马逊云服务账户中运行:
我还建议登录到你的亚马逊云服务控制台,移除我们为 WordPress 安装创建的未附加 EBS 卷,以及任何带有 Kubernetes 标签的 S3 存储桶,因为这些也会产生费用。
回顾
Kubernetes 像 Docker 一样,自首次公开发布以来已经成熟了很多。每一次发布都让部署和管理变得更容易,并且不会对功能集产生负面影响。
作为一个为容器提供调度的解决方案,它无与伦比,而且由于它不依赖于任何特定的供应商,你可以轻松将其部署到除了亚马逊云服务外的其他供应商,例如谷歌的云平台,那里它被视为一等公民。也可以在本地的裸金属服务器或虚拟服务器上进行部署,确保它遵循 Docker 的“构建一次,部署到任何地方”的理念。
此外,它能够适应每个平台上可用的技术;例如,如果你需要持久存储,正如前面提到的,你有多个选项可供选择。
最后,就像过去 18 个月的 Docker 一样,Kubernetes 也有一个相当统一的平台,多个厂商,如谷歌、微软和红帽,都支持并将其作为其产品的一部分使用。
亚马逊 EC2 容器服务(ECS)
我们接下来要了解的工具是亚马逊的弹性容器服务(Elastic Container Service)。亚马逊给出的描述如下:
“Amazon EC2 容器服务 (ECS) 是一个高度可扩展、高性能的容器管理服务,支持 Docker 容器,并允许您轻松地在 Amazon EC2 实例的托管集群上运行应用程序。Amazon ECS 消除了您需要安装、操作和扩展自己的集群管理基础设施的需求。通过简单的 API 调用,您可以启动和停止启用 Docker 的应用程序,查询集群的完整状态,并访问许多熟悉的功能,如安全组、弹性负载均衡、EBS 卷和 IAM 角色。您可以使用 Amazon ECS 根据资源需求和可用性要求调度容器在集群中的位置。您还可以集成您自己的调度器或第三方调度器,以满足业务或应用程序的特定需求。” -
aws.amazon.com/ecs/
亚马逊提供自有容器服务并不令人意外。毕竟,如果您遵循亚马逊的最佳实践,您会发现自己已经将每个 EC2 实例当作容器来处理。
当我将应用程序部署到亚马逊 Web 服务时,我总是尽力确保构建并部署生产就绪的镜像,并确保所有由应用程序写入的数据都发送到共享源,因为实例可能会因扩展事件随时终止。
为了支持这种方法,亚马逊提供了广泛的服务,例如:
-
弹性负载均衡 (ELB):这是一个高可用且可扩展的负载均衡器。
-
Amazon 弹性块存储 (EBS):为您的计算资源提供持久化块级存储卷。
-
自动扩展:这会自动扩展 EC2 资源的规模,帮助您管理流量高峰和应用程序中的故障。
-
Amazon 关系数据库服务 (RDS):这是一个高可用的数据库即服务,支持 MySQL、Postgres 和 Microsoft SQL。
所有这些都是为了帮助您消除亚马逊托管应用程序中的所有单点故障。
此外,由于亚马逊的所有服务都是基于 API 驱动的,因此他们将支持 Docker 容器扩展并不算太难。
在控制台中启动 ECS。
我将使用 AWS 控制台来启动我的 ECS 集群。由于我的 AWS 账户已经很久,因此某些步骤可能会有所不同。为了适应这种情况,我将会在 AWS 的较新区域启动我的集群。
一旦您登录到 AWS 控制台 console.aws.amazon.com/
,确保您所在的区域是您希望启动 ECS 集群的区域,然后点击 EC2 容器服务 链接,位于 服务 下拉菜单中。
由于这是您第一次启动 ECS 集群,您将看到一个关于该服务的概述视频。
点击 开始使用,将引导您进入向导,帮助我们启动第一个集群。
首先,你将被提示创建任务定义。这相当于创建一个 Docker Compose 文件。在这里,你将定义希望运行的容器镜像及其允许消耗的资源,比如内存和 CPU。你还将在此处将主机端口映射到容器端口。
现在,我们将使用默认设置,等集群启动并运行后再来看如何启动我们自己的容器。按照以下截图填写详细信息,然后点击下一步:
现在任务已经定义,我们需要将其附加到一个服务。这允许我们创建一个任务组,最初将是console-sample-app-static
任务的三个副本,并将它们注册到弹性负载均衡器。按照以下截图填写详细信息,然后点击下一步按钮:
现在我们已经定义了服务,接下来需要为其选择一个启动位置。这时,EC2 实例就发挥作用了,也是你仍需付费的地方。尽管 Amazon EC2 容器服务的设置是免费的,但你将为交付集群计算资源所使用的资源付费。这些将是你的标准 EC2 实例费用。按照以下截图填写详细信息,然后点击审查并启动:
在启动之前,你将有机会重新检查在 AWS 账户中配置的所有内容,这是你放弃启动 ECS 集群的最后机会。如果你对一切都满意,点击启动实例并运行服务按钮:
现在你将看到的是正在发生的概览。通常,完成这些任务大约需要 10 分钟。后台正在执行以下操作:
-
创建一个可以访问 ECS 服务的 IAM 角色
-
为你的集群创建一个 VPC,以便启动
-
创建一个启动配置,运行一个经过 ECS 优化的 Amazon Linux AMI,并使用 ECS IAM 角色
-
将新创建的启动配置附加到自动扩展组,并按照你定义的实例数量进行配置
-
在控制台内创建 ECS 集群、任务和服务
-
等待由自动扩展组启动的 EC2 实例启动并与 ECS 服务注册
-
在新创建的 ECS 集群上运行服务
-
创建一个弹性负载均衡器并将你的服务注册到该负载均衡器
你可以在其 AWS Marketplace 页面上找到有关 Amazon ECS 优化的 Amazon Linux AMI 的更多信息,网址为aws.amazon.com/marketplace/pp/B00U6QTYI2/ref=srh_res_product_title?ie=UTF8&sr=0-2&qid=1460291696921
。此镜像是 Amazon Linux 的精简版,仅在 Docker 上运行。
一旦完成所有设置,你将获得前往新创建的服务的选项。你应该看到类似于下图的界面:
如你所见,我们有三个正在运行的任务和一个负载均衡器。
现在让我们创建自己的任务和服务。从前面的服务视图中,点击Update按钮并将所需的数量从三改为零,这将停止任务并允许我们删除服务。为此,点击default按钮进入集群视图,然后删除该服务。
由于sample-webapp
服务已经被移除,点击Task Definitions按钮,然后点击Create new task definition按钮。在打开的页面中,点击Add container按钮并填写以下详细信息:
-
Container name:
cluster
-
Image:
russmckendrick/cluster
-
Maximum memory (MB):
32
-
Port mappings:
80
(Host port)80
(Container port)tcp
(Protocol)
其他项可以保留默认值:
填写完成后,点击Add按钮。这将带你回到Create a Task Definition屏幕,填写任务定义名称,我们称之为our-awesome-cluster
,然后点击Create按钮:
现在我们已经定义了新的任务,接下来需要创建一个服务来将其附加到任务上。点击Clusters标签,然后点击default集群,你应该看到类似于下图的界面:
点击Services标签中的Create按钮。在这个页面中,填写以下信息:
-
Task Definition:
our-awesome-cluster:1
-
Cluster:
default
-
Service name:
Our-Awesome-Cluster
-
Number of tasks:
3
-
Minimum healthy percent:
50
-
Maximum percent:
200
此外,在Optional configurations部分,点击Configure ELB按钮,并使用原本为sample-webapp
服务配置的弹性负载均衡器:
填写完信息后,点击Create Service按钮。如果一切顺利,你应该看到类似于下图的页面:
点击View Service将为你提供类似于我们首次看到的Sample-Webapp
服务的概览:
现在只剩下做的就是点击负载均衡器名称,你将进入 ELB 概览页面;从这里,你将能够获取 ELB 的 URL,将其放入浏览器中应该能展示我们的集群应用:
点击刷新几次,你应该会看到容器的主机名发生变化,表明我们正在不同的容器之间进行负载均衡。
与其再启动更多实例,不如终止我们的集群。为此,进入 AWS 控制台顶部的EC2服务菜单。
在这里,向下滚动到位于左侧菜单底部的自动扩展组。在这里,移除自动扩展组,然后移除启动配置。这将终止我们 ECS 集群中的三个 EC2 实例。
一旦实例被终止,点击负载均衡器,然后终止弹性负载均衡器(Elastic Load Balancer)。
最后,返回到EC2 容器服务,通过点击x删除默认集群。这将移除我们启动 ECS 集群时创建的剩余资源。
回顾
如你所见,亚马逊的 EC2 容器服务可以通过基于 Web 的 AWS 控制台运行。虽然也有命令行工具可用,但这里不再介绍它们。你可能会问,为什么呢?
好吧,尽管亚马逊所构建的服务产品已经相当完整,但它仍然给人一种处于早期 Alpha 阶段的产品感。亚马逊 ECS 优化版的 Amazon Linux AMI 中所提供的 Docker 版本相当陈旧。必须在默认堆栈之外启动实例的过程显得非常笨重。它与亚马逊提供的一些支持服务的集成也是一个非常手动的过程,使得它感觉不够完善。还有一种感觉就是你对它的控制权不大。
就个人而言,我认为这个服务具有很大的潜力;然而,在过去的 12 个月里,许多替代产品已经发布,并且开发进展更快,这意味着与我们正在查看的其他服务相比,亚马逊的 ECS 服务显得陈旧且过时。
Rancher
Rancher 是一个相对较新的玩家,撰写本书时,它刚刚发布了 1.0 版本。Rancher Labs(开发者)将 Rancher(平台)描述为:
"一个开源软件平台,实现了一个专门为在生产中运行容器而构建的基础设施。Docker 容器作为一种日益流行的应用工作负载,在网络、存储、负载均衡、安全性、服务发现和资源管理等基础设施服务中创造了新的需求。"
Rancher 从任何公共或私有云中以 Linux 主机的形式接收原始计算资源。每个 Linux 主机可以是虚拟机或物理机。Rancher 对每个主机的要求仅限于 CPU、内存、本地磁盘存储和网络连接。从 Rancher 的角度来看,来自云服务提供商的虚拟机实例和托管在合租数据中心设施中的裸金属服务器是不可区分的。" -
docs.rancher.com/rancher/
Rancher Labs 还提供了 RancherOS——一个轻量级的 Linux 发行版,将整个操作系统作为 Docker 容器运行。我们将在下一章中讨论这个。
安装 Rancher
Rancher 需要一个主机来运行,所以让我们使用 Docker Machine 在 DigitalOcean 上启动一台服务器:
docker-machine create \
--driver digitalocean \
--digitalocean-access-token sdnjkjdfgkjb345kjdgljknqwetkjwhgoih314rjkwergoiyu34rjkherglkhrg0 \
--digitalocean-region lon1 \
--digitalocean-size 1gb \
rancher
Rancher 以容器形式运行,因此我们不使用 SSH 连接到新启动的 Docker 主机,而是配置本地客户端连接到主机,然后启动 Rancher:
eval $(docker-machine env rancher)
docker run -d --restart=always -p 8080:8080 rancher/server
就这样,Rancher 很快就会启动并运行。你可以查看日志,随时关注 Rancher 何时准备好。
首先,通过运行以下命令检查 Rancher 容器的名称:
docker ps
在我的情况下,它是jolly_hodgkin
,现在运行以下命令:
docker logs -f <name of your container>
你应该能看到很多日志文件条目滚动经过,过一会儿,日志停止写入。这是 Rancher 准备好的标志,你可以登录到 Web 界面。为此,运行以下命令打开浏览器:
open http://$(docker-machine ip rancher):8080/
打开后,你应该能看到类似于以下截图的内容:
如你所见,我们已直接登录。由于这个页面可通过公共 IP 地址访问,我们最好对安装进行安全加固。这就是为什么在顶部菜单中的管理员旁边会出现红色警告图标的原因。
加固你的 Rancher 安装
由于我没有配置 Active Directory 服务器,我将使用 GitHub 来对我的 Rancher 安装进行身份验证。就像安装过程本身一样,Rancher Labs 使这个过程变得非常简单。首先,点击顶部菜单中的管理员,然后在次级菜单中点击访问控制,你将进入一个页面,页面上列出了配置 Rancher 以使用 GitHub 作为身份验证后端所需的所有信息。
对我而言,这个屏幕看起来类似于以下图片:
由于我拥有的是标准的 GitHub 账户,而不是企业版安装,我所需要做的就是点击链接,它会带我到一个页面,在那里我可以注册我的 Rancher 安装。
这要求输入几项信息,所有这些信息都显示在以下页面中:
填写完信息后,我点击了注册应用按钮。应用注册成功后,我被引导到一个页面,页面上显示了客户端 ID 和客户端密钥:
我将这些参数输入到 Rancher 页面上的相应框中,然后点击用 GitHub 认证。这时弹出了一个 GitHub 授权窗口,要求我授权该应用。点击授权应用按钮后,Rancher 界面刷新并让我登录,正如下面的截图所示,我的应用现在已经安全:
现在我们已经配置了认证,您应该重新退出并重新登录,以确保一切按预期工作,然后再继续下一步。为此,请点击页面右上角的头像,并点击登出。
您将立即被带到以下页面:
点击用 GitHub 认证以重新登录。
那么,我们为什么要退出然后再重新登录呢?接下来,我们将为 Rancher 安装提供 DigitalOcean API 密钥,以便它可以启动主机。如果在添加此 API 密钥之前没有保护我们的安装,意味着任何人都可以偶然发现我们的 Rancher 安装并开始按他们的意愿启动主机。这,正如你可以想象的那样,可能会非常昂贵。
Cattle 集群
Rancher 支持三种不同的调度程序,我们已经在本章和上一章中查看了其中的两种。从我们的 Rancher 安装中,我们将能够启动 Docker Swarm 集群、Kubernetes 集群,以及 Rancher 集群。
在本章的这一部分,我们将研究 Rancher 集群。这里使用的调度程序叫做 Cattle。它也是默认的调度程序,所以我们不需要进行配置,我们只需要添加一些主机即可。
如前一节所提到的,我们将要在 DigitalOcean 中启动我们的主机;为此,请点击首页“添加第一个主机”部分中的添加主机。
您将被带到一个页面,页面顶部列出了多个托管提供商,点击 DigitalOcean,然后输入以下详细信息:
-
数量:我希望启动三个主机,因此将滑块拖动到
3
。 -
名称:这是主机在我的 DigitalOcean 控制面板中显示的名称。
-
描述:一个简短的描述,附加到每个主机。
-
访问令牌:这是我的 API 令牌,您应该在第二章中获得属于您的令牌,第方工具。
-
镜像:目前仅支持 Ubuntu 14.04x64。
-
大小:这是您希望启动的主机的大小。别忘了,主机越大,主机在线时您支付的费用就越高。
-
区域:您希望在哪个 DigitalOcean 数据中心启动主机?
我将其余的选项保持在默认设置:
当我对所填写的内容满意后,我点击了创建按钮。然后,Rancher 使用 DigitalOcean API 启动了我的主机。
要检查主机的状态,你应该点击顶部菜单中的基础设施,然后在次级菜单中选择主机。
在这里,你应该看到你正在部署的主机及其状态,状态正在实时更新。你应该会看到以下信息:
-
主机已经启动
-
Docker 正在安装并配置
-
Rancher 代理正在安装并配置
最终,你的三个主机都显示为活动状态:
你看,完成了,这是你第一个 Cattle 集群。如你所见,到目前为止,在 Rancher 中安装、配置和保护我们的第一个集群非常简单。接下来,我们需要部署我们的容器。
部署集群应用程序
根据前两个调度程序,接下来我们来看看如何部署我们的基本集群应用程序。为此,点击顶部菜单中的应用程序选项卡,然后点击添加服务。有一个从目录添加的选项,我们将在启动自己的应用程序时查看这个选项。
在添加服务页面,输入以下信息:
-
规模:
在每个主机上始终运行此容器的一个实例
-
名称:
MyClusterApp
-
描述:
我真的很棒的集群应用
-
选择镜像:
russmckendrick/cluster
-
端口映射: 在私有端口框中添加端口
80
的端口映射
现在,保持其余表单的默认值,点击创建按钮。
几分钟后,你应该能看到你的服务处于活动状态,点击服务名称将带你进入一个显示所有在该服务中运行的容器的详细信息页面:
现在,我们的容器已经在运行,我们真的需要能够访问它们。要配置负载均衡器,点击堆栈,然后点击默认服务上的下拉箭头:
从下拉菜单中选择添加负载均衡器将带你进入一个类似我们添加集群应用的界面。
填写以下信息:
-
规模:
运行 1 个容器
-
名称:
ClusterLoadBalancer
-
描述:
我的集群应用的负载均衡器
-
监听 端口:
源 IP/端口 80 默认目标端口 80
-
目标 服务:
MyClusterApp
点击保存按钮并等待服务启动。你将被带回到你启动的服务列表中,点击负载均衡器名称旁的信息图标将在屏幕底部打开一个信息面板。从这里,点击端口部分列出的 IP 地址:
你的浏览器应该会打开我们熟悉的集群应用程序页面。
刷新几次应该会改变你所连接容器的主机名。
后台发生了什么?
Rancher 的一个优势是,后台有很多任务、配置和进程在运行,这些都被一个直观且易于使用的 Web 界面隐藏起来。
为了了解正在发生的情况,让我们浏览一下界面。首先,点击顶部菜单中的Infrastructure,然后点击Hosts。
如你所见,现在列出了正在运行的容器;在我们的默认堆栈容器旁边,每台主机上都运行着一个网络代理容器:
这些容器通过 iptables 在我们三台主机之间形成了一个网络,实现了容器间的跨主机连接。
注意
iptables 是一个用户空间的应用程序,允许系统管理员配置由 Linux 内核防火墙(通过不同的 Netfilter 模块实现)提供的表,以及它所存储的链和规则:
en.wikipedia.org/wiki/Iptables
为了确认这一点,点击副菜单中的Containers按钮。你将看到当前正在运行的容器列表,列表中应该包括三个运行我们集群应用程序的容器。
记下Default_MyClusterApp_2的 IP 地址(在我的例子中是10.42.220.91
),然后点击Default_MyClusterApp_1。
你将被带到一个页面,提供有关容器的 CPU、内存、网络和存储使用情况的实时信息:
如你所见,容器目前活跃在我的第一台 Rancher 主机上。让我们通过连接到容器获取更多信息。在页面右上角,显示Running的地方,有一个带有三个点的图标,点击它,然后从下拉菜单中选择Execute Shell。
这将会在你的浏览器中打开一个终端,连接到正在运行的容器。尝试输入以下命令之一:
ps aux
hostname
cat /etc/*release
此外,当我们打开 Shell 时,让我们 ping 一下托管在我们另一台主机上的第二个容器(确保你将 IP 地址替换为之前记录下的那个):
ping -c 2 10.42.220.91
如你所见,尽管它位于我们集群中的不同主机上,我们仍然能够无问题地 ping 通它:
另一个有用的功能是健康检查。让我们为我们的服务配置健康检查,然后模拟一个错误。
点击顶部菜单中的Applications,然后点击我们默认堆栈旁边的+,这将弹出一个服务列表,显示堆栈的组成部分。点击MyClusterApp服务以进入概览页面。
从这里,像我们访问容器 shell 一样,点击右上角的三个点图标,靠近活动处。然后从下拉菜单中选择升级,这将带我们进入一个精简版本的页面,我们之前填写了该页面来创建初始服务。
在此页面的底部有几个标签,点击健康检查并填写以下信息:
-
健康检查:
HTTP 响应 2xx/3xx
-
HTTP 请求:
/index.html
-
端口:
80
-
当不健康时:
重新创建
保留其余设置不变,然后点击升级按钮。你将被带回默认堆栈中的服务列表,在MyClusterApp服务旁边会显示正在升级。
在升级过程中,Rancher 已经用新配置重新启动了我们的容器。它是逐个进行的,这意味着从用户浏览我们的应用的角度来看,没有任何停机时间。
你也可能注意到它显示有六个容器,同时堆栈处于降级状态;为了解决这个问题,点击MyClusterApp服务以查看容器列表。
如你所见,其中三个处于停止状态。要移除它们,点击完成升级按钮,位于降级旁边,这样就会移除停止的容器,并将我们恢复到停止状态。
现在我们已经进行了健康检查,确保每个容器都在提供网页,接下来我们来停止 NGINX 并观察会发生什么。
为此,点击我们的任意一个容器,然后通过从下拉菜单中选择执行 Shell来打开控制台。
由于我们的容器是以受监督的方式运行来管理容器内的进程,我们只需运行以下命令来停止 NGINX:
supervisorctl stop nginx
接下来我们需要终止 NGINX 进程;为此,通过运行以下代码查找进程 ID:
ps aux
在我的案例中,PID 是 12 和 13,因此要终止它们,我将运行以下命令:
kill 12 13
这将停止 NGINX,但容器仍然保持运行。几秒钟后,你会注意到后台的统计数据消失了:
然后你的控制台会关闭,留下类似以下截图的内容:
返回到 MyClusterApp 服务的容器列表,你会注意到有一个新的Default_MyClusterApp_2容器在不同的 IP 地址下运行:
Rancher 完全按照我们指示的操作进行,如果任何一个容器的 80 端口超过六秒钟没有响应,它必须连续失败三次检查(每次检查间隔 2000 毫秒),然后移除该容器,并用新的容器替换它。
目录
我敢肯定,你应该已经点击了顶部菜单中的Catalog项,它列出了你可以在 Rancher 中启动的所有预构建堆栈。让我们来看一下如何使用目录项启动 WordPress。为此,点击Catalog并向下滚动到底部,你会看到一个 WordPress 的条目。
WordPress
点击查看详细信息将带你进入一个屏幕,在那里你可以添加一个 WordPress 堆栈。它只要求你提供堆栈的名称和描述,填写这些信息后,点击启动。
这将启动两个容器,一个运行 MariaDB,另一个运行 WordPress 容器。这些容器使用我们在本书中一直使用的 Docker Hub 中的相同镜像。
如果你点击次级菜单中的Stacks,然后展开两个堆栈。当 WordPress 堆栈激活后,你可以点击显示wordpress旁边的信息图标。像之前一样,这将显示一个 IP 地址,你可以通过这个地址访问你的 WordPress 安装:
点击它会打开一个新的浏览器窗口,你将看到一个非常熟悉的 WordPress 安装屏幕。
同样,Rancher 在这里做了一些有趣的事情。记住我们总共有三个主机。这里的一个主机正在运行一个作为我们ClusterApp负载均衡器的容器,这个容器绑定了端口 80。
默认情况下,WordPress 目录堆栈启动 WordPress 容器,并将主机的端口 80 映射到容器的端口 80。在没有任何提示的情况下,Rancher 意识到我们的一个主机已经绑定了端口 80 的服务,因此它没有尝试在这里启动 WordPress 容器,而是选择了下一个没有绑定端口 80 服务的主机,并在该主机上启动了 WordPress 容器。
这是 Rancher 在后台执行任务的另一个例子,目的是最大限度地利用你已启动的资源。
存储
到目前为止,Rancher 运行得很好,让我们看看如何为我们的安装添加一些共享存储。DigitalOcean 不提供的一项服务是块存储,因此我们需要使用集群文件系统,因为我们不希望在应用程序中引入单点故障。
注意
Gluster FS 是一个可扩展的网络文件系统。通过使用常见的现成硬件,你可以为媒体流媒体、数据分析以及其他数据和带宽密集型任务创建大型分布式存储解决方案:
正如你在浏览目录时可能注意到的,目录中有几个存储项,我们将使用 GlusterFS 提供我们的分布式存储:
一旦我们的 Gluster 集群启动并运行,我们将使用 Convoy 将其暴露给我们的容器。在此之前,我们需要启动 GlusterFS。为此,点击查看详情,然后点击Gluster FS目录项。
您将进入一个表单,详细说明将配置的内容和方式。为了我们的目的,您可以保持所有设置不变,并点击页面底部的启动按钮。
启动过程需要几分钟。当完成时,您将看到总共创建了 12 个容器。其中六个容器正在运行,其他六个容器标记为已启动。无需担心,这些容器作为运行容器的卷。
现在,我们已经让 Gluster FS 集群启动并运行,接下来我们需要启动 Convoy 并让它知道关于 Gluster FS 集群的事情。返回目录页面,点击查看详情,然后点击Convoy Gluster FS条目。
由于我们在启动 Gluster FS 集群时保持了默认选项和名称,因此在这里我们可以将所有内容保持为默认,只需从 Gluster FS 服务下拉菜单中选择我们的 Gluster FS 集群即可。
一旦您做出选择并点击启动,下载并启动convoy-gluster
容器的过程不会太长。一旦完成,您应该会看到四个容器在运行。正如您可能已经注意到的那样,系统的新图标出现在次级菜单的堆栈旁边,这里就是您将找到Convoy Gluster
堆栈的地方:
所以,我们现在已经准备好分布式存储了。在使用之前,让我们再看一下另一个目录项。
集群数据库
我们并不希望将数据库存储在共享或不信任的文件系统上,目录中的另一个项启动了一个 MariaDB Galera Cluster。
注意
MySQL 的 Galera Cluster 是基于同步复制的真正的多主集群。Galera Cluster 是一种易于使用、高可用的解决方案,提供高系统正常运行时间、无数据丢失和未来扩展的可扩展性:
集群将位于负载均衡器后面,这意味着您的数据库请求将始终被定向到一个活跃的主数据库服务器。如前所述,点击查看详情,然后在Galera Cluster项中填写您希望集群配置的数据库凭证。这些凭证如下所示:
-
MySQL 根密码
-
MySQL 数据库名称
-
MySQL 数据库用户
-
MySQL 数据库密码
填写完毕后,点击启动按钮。集群需要几分钟时间启动。启动后,它将包含 13 个容器,这些容器组成了集群和负载均衡器。
再次查看 WordPress
现在,我们已经配置了集群文件系统,并且集群数据库也已准备好,让我们再次看看如何启动 WordPress。
为此,请从顶部菜单点击应用程序,然后确保你在堆栈页面,点击新建堆栈。
在这里,给它命名为 WordPress
,然后点击创建,接着点击添加服务。你需要填写以下信息:
-
规模:
运行 1 个容器(我们稍后会扩展)
-
名称:
WordPress
-
描述:
我的 WordPress 集群
-
选择镜像:
wordpress
-
端口映射:
将公共端口留空,并在私有端口添加 80
-
服务链接:目标服务应为你的
galera-lb
,作为名称为galera-lb
接下来,我们需要在底部的标签选项中输入以下详细信息:
命令:
-
环境变量:添加以下变量:
-
变量 =
WORDPRESS_DB_HOST
-
值 =
galera-lb
-
变量 =
WORDPRESS_DB_NAME
-
值 = 在设置 Galera 时创建的数据库名称
-
变量 =
WORDPRESS_DB_USER
-
值 = 在设置 Galera 时创建的用户
-
变量 =
WORDPRESS_DB_PASSWORD
-
值 = 在设置 Galera 时创建的用户密码
-
卷:
-
添加一个卷为
wpcontent:/var/www/html/wp-content/
-
卷驱动:convoy-gluster
然后点击启动按钮。下载并启动容器需要一分钟,启动后,状态应会变为“活动”。当服务健康后,点击“添加服务”旁边的下拉菜单,添加一个负载均衡器:
-
名称:
WordPressLB
-
描述:
我的 WordPress 负载均衡器
-
源 IP/端口:
80
-
默认目标端口:
80
-
目标服务:
WordPress
一旦添加了负载均衡器,点击负载均衡器服务旁的信息图标获取 IP 地址,在浏览器中打开它,然后执行 WordPress 安装,并添加如其他章节中所示的特色图像。
现在我们有一个运行中的 WordPress 容器,并且通过负载均衡器和 Gluster FS 存储,能够在不同主机之间迁移,保持相同的 IP 地址和内容,后端数据库具有高度可用性。
DNS
我最后要介绍的目录项是 DNS 管理器之一。这些项的作用是自动与 DNS 提供商的 API 连接,并为你启动的每个堆栈和服务创建 DNS 记录。由于我使用 Route53 管理我的 DNS 记录,我在目录屏幕上点击了查看详情,进入了Route53 DNS 堆栈。
在配置选项部分,我输入了以下信息:
-
AWS 访问密钥:我的访问密钥,用户必须有权限访问 Route53
-
AWS 密钥:与上述访问密钥配套的密钥
-
AWS 区域:我想使用的区域
-
托管区域:我想使用的区域是
mckendrick.io
,所以我在这里输入了它 -
TTL:我将其保持为默认值
299 秒
,如果你希望更快更新 DNS,可以将其设置为60 秒
然后我点击了启动按钮。几分钟后,我检查了 Route53 控制面板中的托管区,服务已经自动连接并为我已运行的堆栈和服务创建了以下记录。
DNS 条目按以下方式格式化:
<service>.<stack>.<environment>.<hosted zone>
所以在我的情况下,我有以下条目:
-
clusterloadbalancer.default.default.mckendrick.io
-
myclusterapp.default.default.mckendrick.io
由于myclusterapp
包含三个容器,所以向条目中添加了三个 IP 地址,以便轮询 DNS 会将流量指向每个容器:
DNS 目录项的另一个优点是它们会自动更新,这意味着如果我们将一个容器移动到不同的主机,容器的 DNS 会自动更新以反映新的 IP 地址。
Docker & Rancher Compose
你可能还注意到的另一件事是,当你添加堆栈时,Rancher 会给你两个框,你可以在其中输入 Docker 和 Rancher Compose 文件的内容。
到目前为止,我们一直通过 Web 界面手动创建服务,对于我们已经构建的每个堆栈,你都有查看它作为配置文件的选项。
在以下截图中,我们查看的是我们集群应用程序堆栈的 Docker 和 Rancher Compose 文件。要获得这个视图,点击活动旁边的图标:
这个功能允许你将堆栈传输给其他 Rancher 用户。以下文件的内容将展示给你,以便你可以在自己的 Rancher 安装上尝试。
Docker Compose
这是一个标准版本的 Docker Compose 文件,传递了作为标签的 Rancher 设置:
ClusterLoadBalancer:
ports:
- 80:80
tty: true
image: rancher/load-balancer-service
links:
- MyClusterApp:MyClusterApp
stdin_open: true
MyClusterApp:
ports:
- 60036:80/tcp
log_driver: ''
labels:
io.rancher.scheduler.global: 'true'
io.rancher.container.pull_image: always
tty: true
log_opt: {}
image: russmckendrick/cluster
stdin_open: true
Rancher Compose
Rancher Compose 文件将 Docker Compose 文件中定义的容器包装成 Rancher 服务,如你所见,我们在这里为负载均衡器和集群容器定义了健康检查:
ClusterLoadBalancer:
scale: 1
load_balancer_config:
haproxy_config: {}
health_check:
port: 42
interval: 2000
unhealthy_threshold: 3
healthy_threshold: 2
response_timeout: 2000
MyClusterApp:
health_check:
port: 80
interval: 2000
initializing_timeout: 60000
unhealthy_threshold: 3
strategy: recreate
request_line: GET "/index.html" "HTTP/1.0"
healthy_threshold: 2
response_timeout: 2000
Rancher Compose 也是一个命令行工具的名称,可以本地安装并与 Rancher 安装进行交互。由于命令行复制了我们已经讨论的功能,我在这里不再详细讲解;不过,如果你想尝试,关于它的完整细节可以在官方 Rancher 文档中找到,地址是docs.rancher.com/rancher/rancher-compose/
。
回到我们开始的地方
我们将使用 Rancher 做的最后一项任务是启动一个 DigitalOcean 中的 Kubernetes 集群。如本章开始时提到的,Rancher 不仅管理自己的 Cattle 集群,还管理 Kubernetes 和 Swarm 集群。
要创建一个 Kubernetes 集群,点击显示环境的下拉菜单,在你的头像下方,然后点击添加环境:
在该页面,你将被要求选择要为环境使用哪个容器编排工具,给它命名,以及最终谁可以访问它。
选择 Kubernetes,填写剩余信息,并点击 创建 按钮。创建第二个环境后,你将能够在 环境 下拉菜单中进行切换。
类似于我们第一次启动 Rancher 时,我们需要添加一些主机来组成我们的 Kubernetes 集群。为此,点击 添加主机,然后按照之前的方式输入详细信息,唯一不同的是这次要将它们命名为 Kubernetes,而不是 Rancher。
然后你将被带到一个看起来像下面截图的页面:
安装完成大约需要 10 分钟。完成后,你将进入一个熟悉的 Rancher 页面;不过,你现在会在次级菜单中看到 服务、RCS、Pods 和 kubectl。
点击 kubectl 会带你到一个页面,在该页面上你可以在浏览器中运行 kubectl 命令,并且你将获得一个下载 kubectl 配置文件的选项,这样你就可以从本地机器与 Kubernetes 进行交互:
另一个你会注意到的变化是加载了不同的目录,这是因为 Docker 和 Rancher Compose 文件无法与 Kubernetes 一起使用:
随时可以像本章第一部分那样启动服务,或者使用目录中的项目来创建服务。
删除主机
到这个时候,你将在 DigitalOcean 上启动大约七个实例。随着本章的结束,你应该终止所有这些机器,这样你就不会为未使用的资源付费。
我建议通过 DigitalOcean 控制面板来进行操作,而不是通过 Rancher,这样你可以百分百确定 Droplets 已成功关机并删除,这样就不会再为它们付费。
总结 Rancher
正如你所看到的,Rancher 不仅是一个极其强大的开源软件,它还是一个非常用户友好且精心打磨的工具。
我们这里只触及了 Rancher 的一些功能,例如,你可以将主机划分到不同的提供商中,以创建你自己的区域,还有一个完整的 API 让你可以通过自己的应用程序与 Rancher 交互,此外还有完整的命令行接口。
作为一个 1.0 版本,它的功能非常丰富且稳定。在我使用的过程中,我没看到它出现任何问题。
如果你需要一个能够让你启动自己的集群并为终端用户(如开发者)提供直观界面的工具,那么 Rancher 将是一个天作之合。
总结
我们讨论的这三种工具并不是唯一的调度器,实际上还有一些其他的工具,比如以下几种:
-
Nomad:
www.nomadproject.io/
-
Marathon:
mesosphere.github.io/marathon/
所有这些调度器都有各自的要求、复杂性和使用场景。
如果你在一年前问我,在我们本章讨论的三个调度器中,我会推荐哪一个,我会说是亚马逊的 EC2 容器服务。Kubernetes 会排在第二,而我可能不会提到 Rancher。
在过去的 12 个月里,Kubernetes 在安装服务的复杂性方面做出了巨大的简化,消除了其最大障碍,使得更多人能够采用它,而正如我们所展示的,Rancher 进一步简化了这一复杂性。
不幸的是,与其他工具相比,这使得 EC2 容器服务在配置和操作上显得更加复杂,尤其是因为 Kubernetes 和 Rancher 都支持在亚马逊 Web 服务上启动主机,并能够利用亚马逊公共云提供的众多支持服务。
在我们接下来的最后一章中,我们将回顾之前章节中讨论过的所有工具,我们还将提出一些使用场景,并讨论在使用这些工具时需要考虑的安全问题。
第八章:安全性、挑战与结论
在本章的最后,我们将回顾本书中介绍的所有工具,并回答以下问题:
-
这些工具如何影响你 Docker 安装的安全性?
-
它们如何协同工作,并且什么时候应该使用?
-
这些工具可以解决哪些问题和挑战?
容器的安全性
到目前为止,我们相当愉快地从 Docker Hub 拉取镜像,而没有太多考虑是谁创建的,或者实际安装了什么。由于我们一直在创建临时环境来启动容器,这也没带来太多担忧。
随着我们向生产环境过渡并解决开发过程中遇到的问题,了解你正在安装的内容变得越来越重要。
在前面的章节中,我们使用了以下容器镜像:
-
WordPress:
hub.docker.com/_/wordpress/
-
MySQL:
hub.docker.com/_/mysql/
-
MariaDB:
hub.docker.com/_/mariadb/
这三张镜像都被归类为官方镜像,不仅遵循了文档化标准,还在每次拉取请求时进行了同行评审。
然后是来自我自己 Docker Hub 账户的三张镜像:
在我们查看官方镜像之前,先看一下我自己在 Docker Hub 账户中的 Consul 镜像,了解为什么它是值得信任的。
Docker Hub
在这里,我们将看一下可以从 Docker Hub 下载的三种类型的镜像。
我选择专注于 Docker Hub,而不是私有注册表,因为我们在前几章中查看的所有工具都来自 Docker Hub,而且你或你的最终用户也更可能将 Docker Hub 作为他们的主要镜像资源。
Dockerfile
Consul 容器镜像是通过一个 Dockerfile 构建的,这个 Dockerfile 可以公开访问,我的 GitHub 账户上也有。与推送镜像不同,稍后在本章中会讨论,这意味着你可以清楚地看到为了构建该镜像所采取的具体操作。
首先,我们使用russmckendrick/base
镜像作为起始点。再次强调,这个镜像的 Dockerfile 是公开可用的,所以让我们现在来看一下:
### Dockerfile
#
# See https://github.com/russmckendrick/docker
#
FROM alpine:latest
MAINTAINER Russ McKendrick <russ@mckendrick.io>
RUN apk update && apk upgrade && \
apk add ca-certificates bash && \
rm -rf /var/cache/apk/*
正如你所看到的,这个操作的作用是:
-
使用官方 Alpine Linux 镜像的最新版本
-
执行
apk update
,然后执行apk upgrade
,以确保所有软件包都已更新 -
安装
ca-certificates
和bash
软件包 -
清理升级和安装软件包后遗留的任何残余物
现在我们已经知道基础镜像是什么样子了,接下来我们来看看 Consul 容器的 Dockerfile:
### Dockerfile
#
# See https://github.com/russmckendrick/docker
#
FROM russmckendrick/base:latest
MAINTAINER Russ McKendrick <russ@mckendrick.io>
ENV CONSUL_VERSION 0.6.4
ENV CONSUL_SHA256 abdf0e1856292468e2c9971420d73b805e93888e006c76324ae39416edcf0627
ENV CONSUL_UI_SHA256 5f8841b51e0e3e2eb1f1dc66a47310ae42b0448e77df14c83bb49e0e0d5fa4b7
RUN apk add --update 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 \
&& cd /tmp \
&& wget -O ui.zip https://releases.hashicorp.com/consul/${CONSUL_VERSION}/consul_${CONSUL_VERSION}_web_ui.zip \
&& echo "$CONSUL_UI_SHA256 *ui.zip" | sha256sum -c - \
&& unzip ui.zip \
&& mkdir -p /ui \
&& mv * /ui \
&& 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", "-ui-dir", "/ui", "-client=0.0.0.0"]
如你所见,这个 Dockerfile 里面有更多的内容:
-
我们将定义使用最新版本的
russmckendrick/base
作为我们的基础镜像。 -
然后,我们将设置三个环境变量。首先是我们想要下载的 Consul 版本,然后是文件的校验和,我们将从第三方网站获取它。
-
然后,我们将使用 APK 包管理器安装
wget
二进制文件。 -
接下来,我们将从 HashiCorp 网站下载 Consul 二进制文件,注意我们是通过 HTTPS 下载,并且运行
sha256sum
命令检查下载的文件是否被篡改。如果文件没有通过这个测试,构建将会失败。 -
一旦确认 zip 文件是正确的,我们解压它并将二进制文件复制到正确的位置。
-
然后,我们将对 Consul Web 界面执行相同的操作。
-
最后,我们将配置一些容器启动时的默认行为,例如暴露正确的端口、入口点和默认命令。
所有这些意味着,在决定下载使用该镜像的容器之前,你可以确切看到安装了什么,以及镜像是如何配置的。
官方镜像
官方标记的镜像数量略超过 100 个。你可以在 Docker Hub 中查看这些镜像,网址为hub.docker.com/explore/
。官方镜像很容易辨识,因为它们前面没有用户名,例如,以下是官方 NGINX 镜像和我自己镜像的 docker pull 命令:
docker pull nginx
docker pull russmckendrick/nginx
如你所见,顶部的那个是官方镜像。
很多官方镜像是由上游提供商维护的,例如,CentOS、Debian 和 Jenkins 镜像是由各自项目的成员维护的:
-
github.com/docker-library/official-images/blob/master/library/centos
-
github.com/docker-library/official-images/blob/master/library/debian
-
github.com/docker-library/official-images/blob/master/library/jenkins
此外,每个提交的拉取请求都有一个审核流程。这有助于确保每个官方镜像都是一致的,并且在构建时考虑到安全性。
另一个关于官方镜像需要注意的重要点是,任何官方镜像都不能从非官方镜像派生或依赖于非官方镜像。这意味着,非官方镜像的内容不应以任何方式进入官方镜像。
官方镜像的构建标准以及官方镜像维护者的要求,详细说明可以在 Docker Library GitHub 页面找到,地址是 github.com/docker-library/official-images/
。
Docker Hub 的缺点之一是它有时会非常慢,我指的是真正的慢。过去 12 个月中情况有所改善,但有时 Docker 的构建系统会出现大量积压,导致你的构建被排队。
只有在需要触发构建并希望它立即可用时,这才是一个问题。如果你需要在别人注意到之前快速修复应用程序 bug,可能就会遇到这种情况。
推送的镜像
最后,有一个显而易见的问题,就是那些已经从用户推送到 Docker Hub 的完整镜像。
个人来说,我尽量避免将完整镜像推送到我的 Docker Hub 账户,因为这些镜像通常是我不推荐使用的,那么为什么我会期望其他用户使用它们呢?
由于这些镜像并非通过发布的 Dockerfile 构建,因此很难了解它们所遵循的标准以及具体包含了什么内容。
Docker 通过引入内容信任机制来解决这个问题,内容信任会在镜像推送到 Docker Hub 之前,用发布者的私钥对镜像进行签名。当你下载镜像时,Docker 引擎会使用发布者的公钥来验证镜像的内容是否与发布者的原意完全一致。
这有助于确保镜像在从发布者到你运行容器的整个过程中没有被篡改。
关于内容信任的更多信息可以在 docs.docker.com/engine/security/trust/content_trust/
找到。
如果你使用 Docker Hub 发布包含专有应用程序或代码库的私有镜像,并且这些内容不希望公开,那么这个功能非常有用。
然而,对于公开可用的镜像,我总会质疑为什么镜像要被推送到 Docker Hub,而不是通过 Dockerfile 来构建。
Docker Cloud
自从我开始写这本书以来,Docker 推出了名为 Docker Cloud 的商业服务。该服务被描述为 Docker 提供的用于 Docker 容器管理和部署的托管服务。
你可以通过以下网址了解该服务的详细信息:
那么,为什么在讨论安全时要提到这项服务呢?好吧,2016 年 5 月,Docker 宣布他们将增加一个安全扫描功能,截至本书撰写时,这项功能是免费的。
这个功能适用于你在 Docker Hub 上托管的私有仓库,这意味着你推送的任何镜像都可以进行扫描。
该服务对你的镜像执行静态分析,寻找已安装二进制文件中的已知漏洞。
例如,在第六章,扩展你的基础设施中,我们使用 Packer 创建了一个镜像,我的本地机器上还保留着这个镜像的旧版本,所以我将其推送到私人 Docker Hub 仓库,并利用 Docker Cloud 和 Docker Security Scanning 的免费试用。
如下结果所示,该服务在镜像中发现了三个严重漏洞:
这意味着是时候更新我的基础镜像和正在使用的 NodeJS 版本了。
有关该服务及其工作原理的更多细节,请参见以下公告博客文章:
blog.docker.com/2016/05/docker-security-scanning/
这个服务有一些替代方案,比如:
-
Clair:
github.com/coreos/clair
-
Banyan Collector:
github.com/banyanops/collector
-
Docker 安全基准:
github.com/docker/docker-bench-security
然而,刚刚推出的 Docker 服务是最简单的入门方式,因为它已经与其他 Docker 服务深度集成。
私人注册表
记住,你可以使用私人注册表分发 Docker 镜像。如果你必须将应用程序的代码打包到镜像中,我建议采用这种方法。
私人注册表是一种资源,允许你推送和拉取镜像;通常它仅对网络中的受信主机开放,不对公众开放。
私人注册表不允许你托管自动化构建,并且目前不支持内容信任,这就是它们被部署在私人或封闭网络中的原因。
有关托管私人注册表的更多信息,请参阅官方文档:docs.docker.com/registry/
。
挑战
那么,为什么我们要研究扩展核心 Docker 引擎呢?以下是我们在前几章中所讨论的工具可以用于增值或解决潜在问题的一些场景。
开发
很久以前,在第一章,扩展 Docker 简介中,我们看到了开发中运行良好,现在是运维问题的梗图,以及它如今依然令人担忧地相关。容器在很大程度上解决了这个问题;事实上,许多人认为 Docker 是一个伟大的统一者。
然而,如果开发者没有一种简单的方式将这些工具融入到日常工作中,那么你就无法解决由梗图所揭示的问题。
可以帮助开发人员开始在本地使用 Docker 作为开发过程的第一步的工具如下:
-
Docker 工具箱
-
Docker Machine
-
Vagrant
结合最近发布的、目前处于私人测试版的 Docker 原生版本,适用于 OS X 和 Windows,更多详情可以在blog.docker.com/2016/03/docker-for-mac-windows-beta/
的公告博客中找到。
此外,根据你现有的工作流程,你还可以使用以下工具将容器引入到现有的工作流程中:
-
Ansible
-
Jenkins
-
Packer
-
Puppet
Staging
根据你的需求,你可以结合 Docker Compose 使用以下插件,创建一个具有多主机网络和存储的基本预发布环境:
-
Convoy
-
Docker 覆盖网络
-
Docker 卷
-
Flocker
-
REX-Ray
-
Weave
你还可以使用这些工具,方便你控制容器在预发布环境中的部署位置:
-
Ansible
-
Docker Swarm
-
Jenkins
-
Puppet
-
Rancher
此外,你的开发人员可以在一定程度上访问这些工具,以便通过持续集成工具、网页界面或命令行部署测试版本。
生产
再次,你可以使用以下插件,通过 Docker Compose 创建一个基本的生产就绪环境:
-
Convoy
-
Docker 覆盖网络
-
Docker 卷
-
Flocker
-
REX-Ray
-
Weave
然而,你可能希望你的生产环境能够更好地应对故障、扩展事件,并自动将容器注册到 DNS 和负载均衡器等服务中:
-
Ansible
-
Amazon ECS
-
Docker Swarm
-
Kubernetes
-
Puppet
-
Rancher
所有这些列出的工具应该被视为生产就绪。然而,由于 Puppet 和 Ansible 在调度方面的功能有限,只有在你将 Docker 引入到现有的 Puppet 或 Ansible 管理的环境中时,才应真正考虑使用它们。
如果我希望你从本书中学到一点,那就是使用 Docker 时,不必是“一刀切”的解决方案。
正如我们讨论的那样,Docker 和第三方提供的工具可以让你将容器从单一主机扩展到可能达到数百或数千台主机。
总结
在前面的章节中,我们体验了将这些工具组合使用的方式。
例如,我们一直在使用 Docker 存储和网络插件,通过 Docker 自身提供的工具(即 Docker Compose 和 Docker Swarm),以及 Kubernetes 和 Rancher,来创建一个高可用的 WordPress 安装。
我们还通过 Docker Machine、Ansible,以及 Kubernetes 和 Rancher 等工具部署了底层的 Docker 基础设施。
然后,我们部署了各种第一方和第三方插件,帮助解决存储、网络和负载均衡等功能,以充分利用我们所部署的环境,例如亚马逊 Web 服务和 DigitalOcean。
我们所查看的所有工具都与核心 Docker 引擎相辅相成,在大多数情况下,几乎不需要对你的 Docker 镜像进行任何更改就可以开始使用插件或第三方工具。
所有这些意味着,无论你是使用公共云、自己的虚拟机、裸机服务器,还是仅仅使用本地笔记本,都可以相对轻松地构建一个高可用且易于使用的平台来部署应用程序,并根据开发人员、应用程序和自己的需求进行定制,同时确保在开发中能够正常工作的应用程序也能在生产环境中正常运行。