原文:
annas-archive.org/md5/662046f678252f1c36403502957951fd
译者:飞龙
第五章:构建你自己的插件
除了提供核心工具外,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 服务上启动主机,并能够利用亚马逊公共云提供的众多支持服务。
在我们接下来的最后一章中,我们将回顾之前章节中讨论过的所有工具,我们还将提出一些使用场景,并讨论在使用这些工具时需要考虑的安全问题。