精通-CoreOS-全-

精通 CoreOS(全)

原文:annas-archive.org/md5/bf5114c9f275a76bcf22aedc60035087

译者:飞龙

协议:CC BY-NC-SA 4.0

第一章

前言

像 Amazon 和 Google 这样的公共云服务提供商已经彻底改变了云技术,使得终端用户更容易使用。CoreOS 的目标是创建一个安全可靠的集群操作系统,并简化使用容器开发分布式应用程序的过程。CoreOS 的理念是将内核保持在最低限度,同时保持其安全性、可靠性和更新。除了少数关键服务,内核之上的所有内容都作为容器运行。容器革命性地改变了应用开发和部署的方式。尽管容器技术已存在多年,但 Docker 使得容器技术变得更加易于使用和分发,进而引发了这一技术的革命。

通过将其核心技术的关键组件开源,CoreOS 在 CoreOS 社区以及更广泛的 Linux 和云计算社区中赢得了大量追随者。通过 Tectonic,CoreOS 将所有开源技术与 Kubernetes 合并,推出了商业版解决方案。集群操作系统、容器和分布式应用程序部署都是相对较新的领域,这些领域正不断发展。CoreOS 处于这一技术的关键位置,这将激励你进一步了解它。

本书将介绍 CoreOS 内部机制以及围绕在 CoreOS 集群中部署基于容器的微服务的相关技术。

本书首先概述了 CoreOS 和分布式应用开发及其相关技术。你将首先学习如何在不同环境中安装 CoreOS,并使用 CoreOS 自动更新服务。接下来,书中将介绍管理和互联服务的核心 CoreOS 服务,以及网络和存储方面的考虑。之后,将介绍容器生态系统,包括 Docker 和 Rkt,并讲解容器编排技术。本书还涵盖了如何将 OpenStack、AWS 容器服务和 Google 容器引擎等流行的编排解决方案与 CoreOS 和 Docker 集成。最后,本书介绍了容器、CoreOS 以及分布式应用开发和部署的故障排除和生产环境考虑因素。

本书内容概述

第一章,CoreOS 概述,为您提供微服务、分布式应用开发概念、市场上现有容器操作系统的对比、容器基础、Docker 和 CoreOS 的概述。

第二章,搭建 CoreOS 实验环境,介绍如何在 Vagrant、Amazon AWS、Google GCE 和裸机上搭建 CoreOS 开发环境。

第三章,CoreOS 自动更新,涵盖了 CoreOS 发布周期、CoreOS 自动更新以及在集群中管理 CoreOS 更新的选项。

第四章,CoreOS 主要服务 —— Etcd、Systemd 和 Fleet,讲解了 CoreOS 关键服务 Etcd、Systemd 和 Fleet 的内部机制。对于每个服务,我们将介绍其安装、配置和应用。

第五章,CoreOS 网络和 Flannel 内部,讲解了容器网络的基础知识,重点是 CoreOS 如何通过 Flannel 实现容器网络。本章还涉及 Docker 网络和其他相关的容器网络技术。

第六章,CoreOS 存储管理,介绍了 CoreOS 基础文件系统和分区表、容器文件系统和容器数据卷。详细讲解了使用 GlusterFS 和 Flocker 实现容器数据持久化。

第七章,CoreOS 与容器的集成 —— Docker 和 Rkt,重点介绍了容器标准、高级 Docker 主题以及 Rkt 容器运行时的基础知识。本章的重点是 Docker 和 Rkt 如何与 CoreOS 集成。

第八章,容器编排,深入探讨了 Kubernetes、Docker 和 Swarm 的内部机制,并对市场上现有的编排解决方案进行了比较。还涵盖了 AWS 容器服务、Google 容器引擎和 Tectonic 等商业解决方案。

第九章,OpenStack 与容器和 CoreOS 的集成,提供了 OpenStack 概述以及 OpenStack 与容器和 CoreOS 集成的内容。还将介绍 OpenStack Magnum 和 Kuryr 项目的详细信息。

第十章,CoreOS 和容器 —— 故障排除与调试,讲解了 CoreOS 工具箱、Docker 远程 API 和日志记录。还将通过实践示例介绍容器监控工具(如 Sysdig 和 Cadvisor)以及容器日志记录工具(如 Logentries)。

第十一章,CoreOS 和容器 —— 生产环境考量,讨论了 CoreOS 和容器在生产环境中的应用考量,如服务发现、部署模式、CI/CD、自动化和安全性。同时还涵盖了 CoreOS 和 Docker 的发展路线图。

本书所需内容

  • 本地机器:Linux、Windows 或 Mac。

  • 虚拟化软件:Vagrant 和 Virtualbox,用于在本地机器上运行虚拟机。

  • 云账户:Google 云和 AWS 账户,用于在云中运行虚拟机。

  • 开源软件:CoreOS、Kubernetes、Docker、Weave、Calico、Flocker、GlusterFS、OpenStack、Sysdig、cadvisor、Ansible 和 LogEntries。这些开源软件在特定章节中有使用。

本书适用对象

如果您正在寻找部署 CoreOS 集群的方法,或者您已经拥有一个 CoreOS 集群并希望管理它以提高性能、安全性和扩展性,那么这本书非常适合您。本书为开发人员提供了足够的技术输入,帮助他们使用容器部署分布式应用程序,并为管理员提供了管理分布式 CoreOS 基础设施的指南。要跟进动手操作,您需要拥有 Google 和 AWS 云账户,并且能够在您的计算机上运行 CoreOS 虚拟机。

需要对公有云和私有云、容器、Docker、Linux 和 CoreOS 有基本了解。

约定

本书中,您将看到多种文本样式,用于区分不同类型的信息。以下是这些样式的示例及其含义解释。

文本中的代码词、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 用户名如下所示:“服务类型是最常见的类型,用于定义一个带有其依赖项的service。”

代码块如下所示:

ExecStart=/usr/bin/docker run --name hello busybox /bin/sh -c "while true; do echo Hello World; sleep 1; done" ExecStop=/usr/bin/docker stop hello [X-Fleet] Global=true

任何命令行输入或输出都如下所示:

core@core-01 /etc/systemd/system/multi-user.target.wants $ ls -la``lrwxrwxrwx 1 root root 34 Aug 12 13:25 hello1.service -> /etc/systemd/system/hello1.service

新术语和重要词汇以粗体显示。您在屏幕上看到的词汇,例如在菜单或对话框中,会以如下方式出现在文本中:“为了实现这一点,我们需要进入 AWS 控制台中的每个实例,选择Networking | change source/dest check | disable。”

注意

警告或重要说明会以框的形式出现。

小贴士

小贴士和技巧如下所示。

读者反馈

我们始终欢迎读者反馈。让我们知道您对这本书的看法——您喜欢或不喜欢的部分。读者的反馈对我们来说非常重要,因为它帮助我们开发出您真正能够从中受益的书籍。

若要向我们提供一般反馈,只需通过电子邮件发送 <``feedback@packtpub.com``>,并在邮件主题中提及书名。

如果您在某个领域有专长,并且有兴趣写作或为书籍做贡献,请查看我们的作者指南,网址:www.packtpub.com/authors

客户支持

现在您是 Packt 书籍的骄傲拥有者,我们为您准备了许多资源,以帮助您充分利用购买的书籍。

下载示例代码

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

勘误

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

若要查看之前提交的勘误,请访问www.packtpub.com/books/content/support,并在搜索框中输入书籍名称。所需的信息将出现在“勘误”部分。

盗版

网络上版权材料的盗版问题是一个持续存在的难题,涉及所有媒体。在 Packt,我们非常重视版权和许可证的保护。如果您在互联网上遇到我们作品的任何非法复制品,请立即向我们提供相关位置地址或网站名称,以便我们采取相应的措施。

请通过<``copyright@packtpub.com``>与我们联系,并提供涉嫌盗版内容的链接。

感谢您的帮助,帮助我们保护作者的权益,并使我们能够为您提供有价值的内容。

问题

如果您对本书的任何方面有问题,请通过<``questions@packtpub.com``>与我们联系,我们将尽力解决问题。

第二章

第一章 CoreOS 概述

CoreOS 是一个基于 Linux 的容器优化操作系统,用于在节点集群中部署分布式应用程序。除了提供一个安全的操作系统外,CoreOS 还提供如 etcdfleet 等服务,简化了基于容器的分布式应用程序部署。本章将为你提供微服务和分布式应用程序开发的概述,并介绍 CoreOS、容器和 Docker 的基本概念。微服务是一种软件应用程序开发风格,应用程序由多个小型、独立的服务组成,这些服务通过 API 进行相互通信。通过本章的学习,你将能够理解 CoreOS 和容器在微服务架构中的作用。

本章将覆盖以下主题:

  • 分布式应用程序开发——概述与组成部分

  • 当前可用的最简化容器优化操作系统的比较

  • 容器——技术与优势

  • Docker——架构与优势

  • CoreOS——架构与组件

  • CoreOS 组件概述——systemdetcdfleetflannelrkt

  • Docker 与 Rkt

  • 使用 Docker、Rkt 和 CoreOS 的分布式应用程序开发工作流

分布式应用程序开发

分布式应用程序开发涉及设计和编码基于微服务的应用程序,而不是创建单体应用程序。微服务架构中的每个独立服务都可以作为容器创建。分布式应用程序在容器出现之前就已经存在。容器为分布式应用程序中的每个独立服务提供了额外的隔离性和可移植性。下图展示了一个跨多个主机的基于微服务的应用程序示例:

分布式应用程序开发的组成部分

以下是分布式应用程序开发的主要组成部分。这假设分布式应用程序的各个服务是作为容器创建的:

  • 应用程序或微服务。

  • 云基础设施——公共(AWS、GCE 和 Digital Ocean)或私有。

  • 基础操作系统——CoreOS、Atomic、Rancher OS 等。

  • 分布式数据存储和服务发现——etcdconsulZookeeper

  • 负载均衡器——NGINX 和 HAProxy。

  • 容器运行时——Docker、Rkt 和 LXC。

  • 容器编排——Fleet、Kubernetes、Mesos 和 Docker Swarm。

  • 存储——本地或分布式存储。一些例子包括用于集群存储的 GlusterFS 和 Ceph,以及用于云存储的 AWS EBS。Flocker 即将推出的存储驱动插件承诺可以跨不同的存储机制工作。

  • 网络——使用基于云的网络,如 AWS VPC、CoreOS Flannel 或 Docker 网络。

  • 杂项——容器监控(cadvisor、Sysdig 和 Newrelic)和日志记录(Spout 和 Logentries)。

  • 更新策略,如滚动升级,用于更新微服务。

优势与劣势

以下是分布式应用程序开发的一些优势:

  • 每个微服务的应用程序开发人员可以独立工作。如有必要,不同的微服务甚至可以使用各自的编程语言。

  • 应用程序组件的复用性较高。不同的不相关项目可以使用相同的微服务。

  • 每个独立的服务可以进行横向扩展。每个微服务的 CPU 和内存使用可以适当调整。

  • 基础设施可以像管理牲畜一样处理,而不是像宠物一样,且无需区分每个单独的基础设施组件。

  • 应用程序可以部署在内部服务器或公共、私有或混合云中。

以下是微服务方法的一些问题:

  • 管理的微服务数量可能会非常庞大,这使得应用程序的管理变得复杂。

  • 调试可能变得非常困难。

  • 维护完整性和一致性较为困难,因此服务必须设计为能够处理故障。

  • 工具不断变化,因此需要保持与当前技术的更新。

一个极简的容器优化操作系统

这是一个新型操作系统类别,专为开发分布式应用程序而设计,近年来变得越来越流行。传统的基于 Linux 的操作系统对于容器部署来说过于庞大,且未能原生提供容器所需的服务。以下是一些容器优化操作系统的常见特点:

  • 操作系统需要极简,并且启动速度快。

  • 应该有自动化的更新策略。

  • 应用程序开发应该使用容器进行。

  • 应该内建冗余和集群功能

以下表格展示了四种常见容器优化操作系统的特性比较。其他操作系统,如 VMWare Photon 和 Mesos DCOS,未被包括在内。

特性 CoreOS Rancher OS Atomic Ubuntu snappy
公司 CoreOS Rancher Labs Red Hat Canonical
容器 Docker 和 Rkt Docker Docker Snappy 包和 Docker
成熟度 2013 年首次发布,比较成熟 2015 年初首次发布,较新 2015 年初首次发布,较新 2015 年初首次发布,较新
服务管理 Systemd 和 Fleet System docker 管理系统服务,用户 docker 管理用户容器 Systemd Systemd 和 Upstart
工具 Etcd、fleet 和 flannel Rancher 提供服务发现、负载均衡、DNS、存储和网络的工具 Flannel 和其他 RedHat 工具 Ubuntu 工具
编排 Kubernetes 和 Tectonic Rancher 自有编排和 Kubernetes Kubernetes、Atomic 应用和 Nulecule 也使用 Kubernetes 和其他编排工具
更新 自动,使用 A 和 B 分区 自动 自动,使用 rpm-os-tree 自动
注册表 Docker hub 和 Quay Docker hub Docker hub Docker hub
调试 工具箱 Rancher 自有工具和外部工具 RedHat 工具 Ubuntu 调试工具
安全性 SELinux 可以启用 有计划添加 SELinux 和 AppArmor 支持 SELinux 默认启用,额外的安全性 可以使用 AppArmor 安全配置文件

容器

容器在操作系统级别进行虚拟化,而虚拟机在硬件级别进行虚拟化。容器在单个主机上共享相同的内核。由于容器是轻量级的,单个主机上可以运行数百个容器。在基于微服务的设计中,采取的方式是将一个应用拆分为多个小的独立组件,并将每个组件作为容器运行。LXC、Docker 和 Rkt 是容器运行时实现的示例。

技术

以下是容器中使用的两个关键 Linux 内核技术:

  • 命名空间:它们对进程、网络、文件系统、用户等进行虚拟化。

  • cgroups:它们限制每组进程的 CPU、内存和 I/O 使用。

优势

以下是容器的一些主要优势:

  • 每个容器都与其他容器隔离。没有共享包管理、共享库等问题。

  • 与虚拟机相比,容器的占用空间更小,加载和运行速度更快。

  • 它们提供了高效的计算资源使用。

  • 它们可以在开发、测试和生产环境中无缝工作。这使得容器非常适合 DevOps。

Docker 架构概述

Docker 是一种容器运行时实现。尽管容器技术已经存在了相当长时间,但 Docker 通过使其更易于使用,彻底改变了容器技术。以下图片展示了 Docker 的主要组件(Docker 引擎、Docker CLI、Docker REST 和 Docker Hub)及其相互之间的交互:

以下是 Docker 架构的一些细节:

  • Docker 守护进程在每个安装并启动 Docker 的主机上运行。

  • Docker 通过 libcontainer 库使用 Linux 内核容器功能,如命名空间和 cgroups。

  • Docker 客户端可以在主机机器或外部运行,并通过 REST 接口与 Docker 守护进程通信。Docker 客户端还提供 CLI 接口。

  • Docker Hub 是 Docker 镜像的仓库。私有镜像和公共镜像都可以托管在 Docker Hub 仓库中。

  • Dockerfile 用于创建容器镜像。以下是一个示例 Dockerfile,用于创建一个启动 Apache Web 服务并暴露 80 端口的容器:

  • 截至版本 1.9,Docker 平台包括诸如 Swarm、Compose、Kitematic 和 Machine 等编排工具,以及本地网络和存储解决方案。Docker 遵循“包含电池的可插拔”方式进行编排、存储和网络管理,其中本地 Docker 解决方案可以与厂商插件进行替换。例如,Weave 可以作为外部网络插件,Flocker 可以作为外部存储插件,Kubernetes 可以作为外部编排插件。这些外部插件可以替代本地 Docker 解决方案。

Docker 的优势

以下是 Docker 的一些显著优势:

  • Docker 革新了容器打包和围绕容器的工具,帮助了应用开发者和基础设施管理员。

  • 部署和升级单个容器更加容易。

  • 它更适合微服务架构。

  • 它在所有 Linux 发行版上都能很好地运行,只要内核版本大于或等于 3.10。

  • 联合文件系统使得下载和保持不同版本的容器镜像更加快速。

  • 容器管理工具,如 Dockerfile、Docker 引擎 CLI、Machine、Compose 和 Swarm,使得容器管理更加简单。

  • Docker 提供了通过公共和私有注册表服务共享容器镜像的简单方法。

CoreOS

CoreOS 属于极简主义容器优化操作系统类别。CoreOS 是这一类别中的第一个操作系统,最近许多新操作系统也出现在同一类别中。CoreOS 的使命是提高互联网的安全性和可靠性。CoreOS 在这个领域是先驱,首次 alpha 版本发布于 2013 年 7 月。在过去两年中,网络、分布式存储、容器运行时、身份验证和安全性等领域发生了很多发展。CoreOS 被 PaaS 提供商(如 Dokku 和 Deis)、Web 应用开发公司,以及许多开发分布式应用的企业和服务提供商使用。

属性

以下是一些 CoreOS 的关键属性:

  • 内核非常小,启动速度非常快。

  • 基础操作系统及所有服务都开源。服务也可以在非 CoreOS 系统中独立使用。

  • 操作系统不提供包管理。库和包是使用容器开发的应用程序的一部分。

  • 它支持安全的大型服务器集群,可用于分布式应用开发。

  • 它基于 Google Chrome OS 的原则。

  • 容器运行时、SSH 和内核是主要组成部分。

  • 每个进程都由 systemd 管理。

  • Etcd、fleet 和 flannel 都是运行在内核之上的控制单元。

  • 它同时支持 Docker 和 Rkt 容器运行时。

  • 自动更新通过 A 和 B 分区提供。

  • Quay 注册表服务可以用来存储公共和私有的容器镜像。

  • CoreOS 发布渠道(稳定版、测试版和 alpha 版)用于控制发布周期。

  • 商业产品包括 Coreupdate 服务(商业管理和企业版 CoreOS 的一部分)、Quay 企业版和 Tectonic(CoreOS + Kubernetes)。

  • 它目前运行在 x86 处理器上。

优势

以下是 CoreOS 的一些显著优势:

  • 内核自动更新功能可以保护内核免受安全漏洞的影响。

  • CoreOS 的内存占用非常小。

  • CoreOS 机器的管理是在集群级别进行的,而不是在单个机器级别。

  • 它提供基于服务级别(使用 systemd)和节点级别(使用 fleet)的冗余。

  • Quay 为您提供了私有和公共容器仓库。该仓库可用于 Docker 和 Rkt 容器。

  • Fleet 用于基本的服务编排,Kubernetes 用于应用服务编排。

  • 它得到 AWS、GCE、Azure 和 DigitalOcean 等主要云服务提供商的支持。

  • 大多数 CoreOS 组件是开源的,客户可以选择适合其特定应用程序所需的工具组合。

支持的平台

以下是官方和社区支持的 CoreOS 平台。这不是完整列表。

注意

若要查看 CoreOS 支持的平台的完整列表,请参考此链接(coreos.com/os/docs/latest/)。

以下是官方支持的平台:

  • 云平台如 AWS、GCE、Microsoft Azure、DigitalOcean 和 OpenStack

  • PXE 无盘启动

  • Vagrant

以下是社区支持的平台:

  • CloudStack

  • VMware

CoreOS 组件

以下是 CoreOS 核心组件和 CoreOS 生态系统。如果包括自动化、管理和监控工具,生态系统可能会变得非常庞大,这些内容未在此列出。

  • 核心组件:内核、systemd、etcd、fleet、flannel 和 rkt

  • CoreOS 生态系统:Docker 和 Kubernetes

下图展示了 CoreOS 架构中的不同层次:

内核

CoreOS 在其发行版中使用最新的 Linux 内核。以下截图显示了在 CoreOS 稳定版 766.3.0 中运行的 Linux 内核版本:

Systemd

Systemd 是 CoreOS 使用的初始化系统,用于启动、停止和管理进程。SysVinit 是最古老的初始化系统之一。以下是 Unix 世界中常用的一些 init 系统:

  • Systemd:CoreOS 和 RedHat

  • Upstart:Ubuntu

  • Supervisord:Python 世界

以下是一些 init 系统执行的常见功能:

  • 它是第一个启动的进程

  • 它控制所有用户进程的顺序和执行

  • 它负责在进程死掉或挂起时重新启动它们

  • 它负责进程所有权和资源管理

以下是 systemd 的一些具体细节:

  • systemd 中的每个进程都运行在一个 cgroup 中,包括派生的进程。如果 systemd 服务被终止,所有与该服务相关的进程,包括派生的进程,都会被终止。这也为你提供了控制资源使用的良好方法。如果我们在 systemd 中运行容器,我们可以控制资源使用,即使容器包含多个进程。此外,如果我们在 systemd 中指定了restart选项,systemd 会自动重启死掉的容器。

  • Systemd 单元是在单台机器上运行和控制的。

  • 这些是一些 systemd 单元类型——服务、套接字、设备和挂载。

  • Service类型是最常见的类型,用于定义带有依赖项的服务。Socket类型用于将服务暴露给外部世界。例如,docker.service通过docker.socket将外部连接暴露给 Docker 引擎。套接字还可以用来将日志导出到外部机器。

  • systemctl命令行工具可以用来控制 Systemd 单元。

Systemd 单元

以下是 CoreOS 系统中的一些重要 systemd 单元。

Etcd2.service

以下是etcd2.service单元文件的示例:

以下是一些关于 etcd2 服务单元文件的细节:

  • 所有单元都有[Unit][Install]部分。还有一个类型特定的部分,例如服务单元的[Service]

  • Conflicts选项表示etcdetcd2可以运行,但不能同时运行。

  • Environment选项指定etcd2将使用的环境变量。%m单元说明符允许根据服务运行的位置自动获取机器 ID。

  • ExecStart选项指定要运行的可执行文件。

  • Restart选项指定服务是否可以重启。Restartsec选项指定服务重启的时间间隔。

  • LimitNoFILE指定文件数限制。

  • Install部分中的WantedBy选项指定此服务所属的组。分组机制允许 systemd 同时启动一组进程。

Fleet.service

以下是fleet.service单元文件的示例:

在前面的单元文件中,我们可以看到fleet.service有两个依赖项。etcd.Serviceetcd2.service被指定为依赖项,因为 Fleet 依赖它们来在不同节点的 Fleet 代理之间进行通信。fleet.socket套接字单元也被指定为依赖项,因为它被外部客户端用来与 Fleet 通信。

Docker.service

Docker 服务由以下组件组成:

  • Docker.service:此项启动 Docker 守护进程。

  • Docker.socket:此项允许 CoreOS 节点与 Docker 守护进程进行通信。

  • Docker-tcp.socket:此项允许与 Docker 守护进程进行通信,外部主机使用端口2375作为监听端口。

以下是启动 Docker 守护进程的docker.service单元文件:

以下 docker.socket 单元文件启动本地套接字流:

提示

下载示例代码

你可以从 www.packtpub.com 下载示例代码文件,适用于你购买的所有 Packt Publishing 图书。如果你购买了这本书...

以下 docker-tcp.socket 单元文件设置了一个用于远程客户端通信的监听套接字:

docker ps 命令使用 docker.socket,而 docker -H tcp://127.0.0.1:2375 ps 使用 docker-tcp.socket 单元与本地系统中运行的 Docker 守护进程进行通信。

启动简单 systemd 服务的过程

让我们启动一个简单的 hello1.service 单元,该单元运行一个 Docker busybox 容器,如下图所示:

以下是启动 hello1.service 的步骤:

  1. hello1.service 复制为 sudo 到 /etc/systemd/system

  2. 启用服务:

    sudo systemctl enable /etc/systemd/system/hello1.service

  3. 启动 hello1.service

    sudo systemctl start hello1.service

这将创建以下链接:

core@core-01 /etc/systemd/system/multi-user.target.wants $ ls -la``lrwxrwxrwx 1 root root 34 Aug 12 13:25 hello1.service -> /etc/systemd/system/hello1.service

现在,我们可以查看 hello1.service 的状态:

在前面的输出中,我们可以看到服务处于活动状态。最后,我们还可以看到 stdout,其中记录了回显输出。

让我们看看正在运行的 Docker 容器:

注意

在使用 systemd 启动 Docker 容器时,需要避免使用 -d 选项,因为它会阻止容器进程被 systemd 监控。更多详情请参考 coreos.com/os/docs/latest/getting-started-with-docker.html

演示 systemd 高可用性(HA)

在创建的 hello1.service 中,我们指定了两个选项:

Restart=always RestartSec=30s

这意味着如果服务由于某种原因退出,服务将在 30 秒后重启。

让我们停止 Docker hello1 容器:

服务在 30 秒后自动重启,如下图所示:

以下截图显示了 hello1 容器再次运行。从容器状态输出中,我们可以看到容器仅运行了一分钟:

我们还可以从与该服务相关的 systemd 日志中确认服务已重启。在以下输出中,我们可以看到服务退出并在 30 秒后重启:

Etcd

Etcd 是由 CoreOS 集群中所有机器使用的分布式键值存储,用于读写和交换数据。Etcd 使用 Raft 共识算法(raft.github.io/)来维护高可用性集群。Etcd 用于跨 CoreOS 机器共享配置和监控数据,并进行服务发现。所有其他 CoreOS 服务(如 Fleet 和 Flannel)都使用 etcd 作为分布式数据库。Etcd 也可以作为独立于 CoreOS 外部的服务使用。事实上,许多复杂的分布式应用项目,如 Kubernetes 和 Cloudfoundry,都使用 etcd 作为它们的分布式键值存储。etcdctl 实用程序是 etcd 的 CLI 前端。

以下是 etcd 的两个示例用例。

  • 服务发现:服务发现可用于在容器之间传递服务连接详细信息。让我们以 WordPress 应用为例,其中包含 WordPress 应用容器和 MySQL 数据库容器。如果某台机器有一个数据库容器,并希望通信其服务 IP 地址和端口号,它可以使用 etcd 写入相关键和数据;另一台主机上的 WordPress 容器可以使用键值来写入相应的数据库。

  • 配置共享:Fleet 主节点使用 etcd 与 Fleet 代理通信,以决定集群中的哪个节点将执行 Fleet 服务单元。

Etcd 发现

集群成员使用静态方法或动态方法自行发现。在静态方法中,我们需要在集群的每个节点中静态地列出所有邻居的 IP 地址。在动态方法中,我们使用发现令牌方法,从中央 etcd 服务器获取分布式令牌,并在集群的所有成员中使用此令牌,以便成员可以互相发现。

获取分布式令牌如下:

curl https://discovery.etcd.io/new?size=<size>

下面是获取集群大小为三的发现令牌的示例:

发现令牌功能由 CoreOS 托管,并作为 etcd 集群实现。

集群大小

最好将 etcd 集群大小设置为奇数,因为这样可以提供更好的容错性。以下表格显示了常见集群大小(最多五个)的大多数计数和容错性。集群大小为二时,我们无法确定大多数。

集群大小 大多数 容错性
1 1 0
3 2 1
4 3 1
5 3 2

大多数计数告诉我们需要多少节点才能使集群正常工作,而容错性告诉我们可以故障多少节点仍然保持集群运行。

Etcd 集群详细信息

以下截图显示了一个 3 节点 CoreOS 集群中的 Etcd 成员列表:

我们可以看到有三个成员作为 etcd 集群的一部分,具有它们的机器 ID、机器名称、IP 地址以及用于 etcd 服务器间和客户端间通信的端口号。

以下输出展示了 etcd 集群的健康状况:

在这里,我们可以看到 etcd 集群的所有三个成员都是健康的。

以下输出展示了带有集群领导者的 etcd 统计信息:

我们可以看到,成员 ID 与领导者 ID 匹配,41419684c778c117

以下输出展示了带有集群成员的 etcd 统计信息:

使用 etcd 进行简单的设置和获取操作

在下面的示例中,我们将/message1键设置为Book1值,然后稍后检索/message1键的值:

Fleet

Fleet 是一个集群管理器/调度器,用于控制集群级别的服务创建。就像 systemd 是节点的初始化系统一样,Fleet 是集群的初始化系统。Fleet 使用 etcd 进行节点间通信。

Fleet 架构

以下图像展示了 Fleet 架构的组件:

  • Fleet 采用主从模型,其中 Fleet 引擎扮演主角色,Fleet 代理扮演从角色。Fleet 引擎负责调度 Fleet 单元,而 Fleet 代理负责执行单元并将状态报告回 Fleet 引擎。

  • 在 CoreOS 集群中,使用 etcd 选举出一个主引擎。

  • 当用户启动 Fleet 服务时,每个代理都会竞标该服务。Fleet 使用一种非常简单的least-loaded调度算法,将单元调度到合适的节点。Fleet 单元还包含元数据,这些元数据有助于控制单元如何基于节点属性以及在特定节点上运行的其他服务来调度。

  • Fleet 代理处理单元并将其交给 systemd 执行。

  • 如果某个节点出现故障,将选举出一个新的 Fleet 引擎,并将该节点中的调度单元重新调度到新的节点。Systemd 提供了节点级别的高可用性(HA);Fleet 提供了集群级别的高可用性。

考虑到 CoreOS 和 Google 在 Kubernetes 项目中密切合作,一个常见的问题是,如果 Kubernetes 负责容器编排,那么 Fleet 的角色是什么?Fleet 通常用于使用 systemd 编排关键系统服务,而 Kubernetes 用于应用程序容器的编排。Kubernetes 由多个服务组成,如 kubelet 服务器、API 服务器、调度器和复制控制器,它们都作为 Fleet 单元运行。对于较小的部署,Fleet 也可以用于应用程序编排。

一个 Fleet 调度示例

以下是一个包含一些元数据的三节点 CoreOS 集群:

一个全局单元示例

一个全局单元在集群中的所有节点上执行相同的服务单元。

以下是一个示例helloglobal.service

[Unit] Description=My Service After=docker.service [Service] TimeoutStartSec=0 ExecStartPre=-/usr/bin/docker kill hello ExecStartPre=-/usr/bin/docker rm hello ExecStartPre=/usr/bin/docker pull busybox ExecStart=/usr/bin/docker run --name hello busybox /bin/sh -c "while true; do echo Hello World; sleep 1; done" ExecStop=/usr/bin/docker stop hello [X-Fleet] Global=true

让我们按如下方式执行单元:

我们可以看到相同的服务在所有三台节点上启动:

基于元数据的调度

假设我们有一个三节点 CoreOS 集群,具有以下元数据:

  • Node1(compute=web,rack=rack1)

  • Node2(compute=web,rack=rack2)

  • Node3(compute=db,rack=rack3)

我们使用了compute元数据来标识机器类型为webdb。我们使用了rack元数据来标识机架编号。节点的 Fleet 元数据可以在cloud-config的 Fleet 部分中指定。

让我们启动一个 Web 服务和一个数据库服务,每个服务都有相应的元数据,看看它们会被调度到哪里。

这是 Web 服务:

[Unit] Description=Apache web server service After=etcd.service After=docker.service [Service] TimeoutStartSec=0 KillMode=none EnvironmentFile=/etc/environment ExecStartPre=-/usr/bin/docker kill nginx ExecStartPre=-/usr/bin/docker rm nginx ExecStartPre=/usr/bin/docker pull nginx ExecStart=/usr/bin/docker run --name nginx -p ${COREOS_PUBLIC_IPV4}:8080:80 nginx ExecStop=/usr/bin/docker stop nginx [X-Fleet] MachineMetadata=compute=web

这是数据库服务:

[Unit] Description=Redis DB service After=etcd.service After=docker.service [Service] TimeoutStartSec=0 KillMode=none EnvironmentFile=/etc/environment ExecStartPre=-/usr/bin/docker kill redis ExecStartPre=-/usr/bin/docker rm redis ExecStartPre=/usr/bin/docker pull redis ExecStart=/usr/bin/docker run --name redis redis ExecStop=/usr/bin/docker stop redis [X-Fleet] MachineMetadata=compute=db

让我们使用 Fleet 启动服务:

如我们所见,nginxweb.serviceNode1上启动,nginxdb.serviceNode3上启动。这是因为Node1Node2web类型,而Node3db类型。

Fleet 高可用

当任何一个节点出现问题并且没有响应时,Fleet 会自动处理将服务单元调度到下一个合适的机器上。

从前面的例子来看,让我们重启Node1,它有nginxweb.service。该服务被调度到Node2而不是Node3,因为 Node2 有web元数据:

在前面的输出中,我们可以看到nginxweb.service被重新调度到Node2,而Node1在 Fleet 集群中不可见。

Flannel

Flannel 使用覆盖网络使不同主机上的容器能够相互通信。Flannel 不是基础 CoreOS 镜像的一部分,这是为了保持 CoreOS 镜像的最小化。当 Flannel 启动时,Flannel 容器镜像会从容器镜像仓库中获取。通常,Docker 守护进程在 Flannel 服务之后启动,以便容器能够获取 Flannel 分配的 IP 地址。这就出现了一个先有鸡还是先有蛋的问题,因为 Docker 是必要的,以便下载 Flannel 镜像。CoreOS 团队通过运行一个主 Docker 服务来解决这个问题,这个服务的唯一目的是下载 Flannel 容器。

以下图像展示了每个节点中的 Flannel 代理如何使用 etcd 进行通信:

以下是一些 Flannel 的内部实现:

  • Flannel 在没有中央服务器的情况下运行,使用 etcd 进行节点之间的通信。

  • 在启动 Flannel 时,我们需要提供一个配置文件,该文件包含集群要使用的 IP 子网以及后端协议方法(例如 UDP 和 VXLAN)。以下是一个指定子网范围和后端协议为 UDP 的示例配置:

  • 集群中的每个节点都会请求为该主机上创建的容器分配一个 IP 地址范围,并将该 IP 范围注册到 etcd。

  • 由于集群中的每个节点都知道为其他节点分配的 IP 地址范围,因此它知道如何访问集群中任何节点上创建的容器。

  • 当容器被创建时,容器会获得分配给该节点的 IP 地址范围内的一个 IP 地址。

  • 当容器需要跨主机通信时,Flannel 会根据选择的后端封装协议进行封装。Flannel 在目标节点中解封装数据包并将其交给容器。

  • 通过不使用基于端口的映射来进行容器间通信,Flannel 简化了容器之间的通信。

以下图像展示了使用 Flannel 进行容器到容器通信的数据路径:

一个 Flannel 服务单元

以下是一个 flannel 服务单元的示例,我们将 flannel 网络的 IP 范围设置为10.1.0.0/16

在一个三节点 etcd 集群中,以下是一个示例输出,显示每个节点选定的容器 IP 地址范围。每个节点请求一个 24 位掩码的 IP 范围。10.1.19.0/24由节点 A 选择,10.1.3.0/24由节点 B 选择,10.1.62.0/24由节点 C 选择:

Rkt

Rkt 是 CoreOS 开发的容器运行时。Rkt 没有守护进程,由 systemd 进行管理。Rkt 使用应用程序容器镜像(ACI)格式,遵循 APPC 规范(github.com/appc/spec)。Rkt 的执行分为三个阶段。采取这种方法是为了在需要时能够用不同的实现替换某些阶段。以下是 Rkt 执行的三个阶段的详细信息:

阶段 0:

这是容器执行的第一阶段。该阶段进行镜像发现、检索,并为第 1 和第 2 阶段设置文件系统。

第 1 阶段:

该阶段设置容器的执行环境,如容器命名空间、使用第 0 阶段设置的文件系统中的 cgroups。

第 2 阶段:

该阶段使用第 1 阶段设置的执行环境和第 0 阶段设置的文件系统来执行容器。

从 0.10.0 版本开始,Rkt 仍在积极开发中,尚未准备好用于生产环境。

CoreOS 集群架构

CoreOS 集群中的节点用于运行关键的 CoreOS 服务,如 etcd、fleet、Docker、systemd、flannel 和 journald,以及应用容器。为了避免关键服务和应用容器之间的资源争用,重要的是避免在同一主机上同时运行关键服务和应用容器。可以通过使用 Fleet 元数据来分离核心机器和工作机器,从而实现这种调度。以下是两种集群方法。

开发集群

以下图像展示了一个包含三台 CoreOS 节点的开发集群:

为了试用 CoreOS 和 etcd,我们可以从一个单节点集群开始。使用这种方法,不需要动态发现集群成员。一旦它工作正常,我们可以将集群规模扩展到三台或五台,以实现冗余。可以使用静态或动态发现方法来发现 CoreOS 成员。由于 CoreOS 的关键服务和应用容器运行在同一个集群中,这种方法可能会出现资源争用问题。

生产集群

以下图像展示了一个生产集群,包含三节点主集群和五节点工作集群:

我们可以拥有一个三节点或五节点的主集群来运行关键的 CoreOS 服务,然后有一个动态的工作集群来运行应用容器。主集群将运行 etcd、fleet 和其他关键服务。在工作节点中,etcd 将被设置为代理到主节点,以便工作节点可以通过主节点进行 etcd 通信。在工作节点中,fleet 也将设置为使用主节点中的 etcd。

Docker 与 Rkt

由于这是一个有争议的话题,我会尽量保持中立。

历史

CoreOS 团队启动了 Rkt 项目,原因如下:

  • 由于 Docker 运行时没有完全遵循容器清单规范,容器互操作性问题需要解决

  • 在 systemd 下运行 Docker 遇到了一些问题,因为 Docker 作为守护进程运行。

  • 容器镜像发现和镜像签名需要改进

  • 需要改进容器的安全模型

APPC 与 OCI

APPC (github.com/appc/spec) 和 OCI (github.com/opencontainers/specs) 定义了容器标准。

APPC 规范主要由 CoreOS 和少数其他社区成员推动。APPC 规范定义了以下内容:

  • 图像格式:打包和签名

  • 运行时:如何执行容器

  • 命名和共享:自动发现

Rkt、Kurma、Jetpack 等实现了 APPC。

OCI (www.opencontainers.org/) 是一个自 2015 年 4 月启动的开放容器倡议项目,其成员包括 Docker 和 CoreOS 等各大公司。Runc 是 OCI 的一个实现。以下图片展示了 APPC、OCI、Docker 和 Rkt 的关系:

当前状态

根据最新的发展,社区普遍一致认为应当有一个称为开放容器规范的通用容器规范。任何人都可以基于此规范开发容器运行时。这将允许容器镜像具备互操作性。Docker、Rkt 和 Odin 都是容器运行时的示例。

CoreOS 最初提出的 APPC 容器规范涵盖了容器管理的四个不同元素——打包、签名、命名(与他人共享容器)和运行时。根据最新的 CoreOS 博客更新(coreos.com/blog/making-sense-of-standards.html),APPC 和 OCI 仅在运行时上交汇,而 APPC 将继续专注于图像格式、签名和分发。Runc 是 OCI 的一个实现,Docker 使用了 Runc。

Docker 和 Rkt 之间的区别

以下是 Docker 和 Rkt 容器运行时之间的一些区别:

  • Docker 使用 LibContainer API 访问 Linux 内核容器功能,而 Rkt 使用 Systemd-nspawn API 访问 Linux 内核容器功能。以下图片说明了这一点:

  • Docker 需要一个守护程序来管理容器镜像、远程 API 和容器进程。Rkt 是无守护程序的,容器资源由 systemd 管理。这使得 Rkt 更好地集成到像 systemd 和 upstart 这样的初始化系统中。

  • Docker 拥有一套完整的平台来管理容器,如 Machine、Compose 和 Swarm。CoreOS 则会使用一些自己的工具,如 Flannel 用于网络,同时结合像 Kubernetes 这样的工具进行编排。

  • 相比于 Rkt,Docker 已经相当成熟并且可以用于生产。截至 Rkt 0.10.0 版本发布时,Rkt 还未准备好用于生产。

  • 对于容器镜像注册表,Docker 使用 Docker Hub,Rkt 使用 Quay。Quay 也有 Docker 镜像。

CoreOS 计划同时支持 Docker 和 Rkt,用户可以选择使用相应的容器运行时来运行他们的应用程序。

使用 Docker 和 CoreOS 进行分布式应用开发的工作流程

下面是使用 Docker 和 CoreOS 开发微服务的典型工作流程:

  • 选择需要容器化的应用程序。这可以是绿色领域(greenfield)应用程序,也可以是遗留应用程序。对于遗留应用程序,可能需要进行逆向工程,以拆分单体应用并容器化各个组件。

  • 为每个微服务创建一个 Dockerfile。Dockerfile 定义了如何从基础镜像创建容器镜像。Dockerfile 本身可以进行版本控制。

  • 将应用程序的无状态部分与有状态部分分离。对于有状态应用程序,需要决定存储策略。

  • 微服务之间需要互相通信,其中一些服务应能够对外访问。假设服务之间的基本网络连接可用,服务可以通过静态方式通过定义服务名称与 IP 地址及端口号的映射来进行通信,或者通过使用服务发现机制,在该机制中,服务可以动态发现并互相通信。

  • Docker 容器镜像需要存储在私有或公有仓库中,以便开发、QA 和生产团队可以共享。

  • 应用程序可以部署在私有云或公有云中。必须根据业务需求选择合适的基础设施。

  • 选择 CoreOS 集群的大小和架构。最好使基础设施具有动态可扩展性。

  • 为基本服务(如 etcd、fleet 和 flannel)编写 CoreOS 单元文件。

  • 确定存储策略——本地、分布式或云存储。

  • 对于较小应用程序的编排,可以使用 fleet。对于复杂的应用程序,将需要 Kubernetes 这类编排解决方案。

  • 对于生产集群,还需要制定合适的监控、日志记录和升级策略。

总结

本章我们介绍了 CoreOS、容器和 Docker 的基础知识,以及它们如何帮助分布式应用程序的开发和部署。这些技术仍在积极开发中,将彻底改变并创造一种新的软件开发和分发模式。我们将在接下来的章节中详细探讨每个主题。在下一章中,我们将介绍如何在 Vagrant 和公有云中设置 CoreOS 开发环境。

参考文献

进一步阅读和教程

第三章

第二章:设置 CoreOS 实验室

CoreOS 可以部署在裸机、虚拟机或云服务提供商如 Amazon AWS 或 Google GCE 上。在本章中,我们将介绍如何在 Vagrant、Amazon AWS、Google GCE 和裸机上设置 CoreOS 开发环境。这个开发环境将在接下来的所有章节中使用。

本章将涵盖以下主题:

  • CoreOS 的 Cloud-config

  • CoreOS 与 Vagrant

  • CoreOS 与 Amazon AWS

  • CoreOS 与 Google GCE

  • CoreOS 在裸机上的安装。

  • CoreOS 集群的基本调试

这里介绍了不同的 CoreOS 部署选项,原因如下:

  • 使用 Vagrant 和 Virtualbox 对于没有云账户的用户来说很有用。

  • 对于某些用户来说,使用本地机器可能不可行,因为虚拟机占用了大量资源,这时使用基于云的虚拟机是最佳选择。由于 AWS 和 GCE 是最流行的云提供商,因此我选择了这两者。

  • 对于传统的内部数据中心,裸机安装是首选。

  • 在本书的示例中,我使用了三种方法中的一种(Vagrant、AWS 和 GCE),根据某种方法的简便性、与其中一种方法的更好集成,或者由于某种方法存在问题。

Cloud-config

Cloud-config 是一种声明式配置文件格式,许多 Linux 发行版使用它来描述初始服务器配置。cloud-init 程序负责在服务器初始化期间解析cloud-config并适当地配置服务器。cloud-config文件为 CoreOS 节点提供了默认配置。

CoreOS 的 cloud-config 文件格式

coreos-cloudinit程序负责在启动时使用cloud-config文件进行 CoreOS 节点的默认配置。cloud-config文件使用 YAML 格式描述配置(www.yaml.org/)。CoreOS cloud-config 遵循cloud-config规范,并具有一些 CoreOS 特定的选项。链接,coreos.com/os/docs/latest/cloud-config.html,涵盖了 CoreOS cloud-config的详细信息。

cloud-config 的主要部分

以下是 CoreOS cloud-config YAML 文件中的主要部分:

  • CoreOS:

    • Etcd2:etcd2 的配置参数

    • Fleet:Fleet 的配置参数

    • Flannel:Flannel 的配置参数

    • Locksmith:Locksmith 的配置参数

    • 更新:自动更新的配置参数

    • 单元:需要启动的 Systemd 单元

  • ssh_authorized_keys:Core 用户的公钥

  • hostname:CoreOS 系统的主机名

  • users:附加的用户帐户和组详细信息

  • write_files:使用指定的用户数据创建文件

  • manage_etc_hosts:指定/etc/hosts的内容

CoreOS cloud-config 的示例

以下是一个单节点 CoreOS 集群的cloud-config文件示例:

`#cloud-config coreos:   etcd2:     # 静态集群     name: etcdserver     initial-cluster-token: etcd-cluster-1     initial-cluster: etcdserver=http://\(private_ipv4:2380     initial-cluster-state: new     advertise-client-urls: http://\)public_ipv4:2379     initial-advertise-peer-urls: http://\(private_ipv4:2380     # 监听官方端口和传统端口     # 如果您的应用程序不依赖于传统端口,则可以省略这些端口     listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001     listen-peer-urls: http://\)private_ipv4:2380,http://\(private_ipv4:7001   fleet:     public-ip: \)public_ipv4     metadata: "role=services"   flannel:     interface: $public_ipv4   update:       reboot-strategy: "etcd-lock"   units:     # 要使用 etcd2,注释掉上述服务并取消注释这些     # 注意:这需要包含 etcd2 的版本     - name: etcd2.service       command: start     - name: fleet.service       command: start     - name: flanneld.service       drop-ins:         - name: 50-network-config.conf           content: |             [Service]             ExecStartPre=/usr/bin/etcdctl set /coreos.com/network/config '{ "Network": "10.1.0.0/16" }'       command: start     - name: docker-tcp.socket       command: start       enable: true       content: |         [Unit]         Description=用于 API 的 Docker Socket         [Socket]         ListenStream=2375         Service=docker.service         BindIPv6Only=both         [Install]         WantedBy=sockets.target write_files:   - path: "/etc/motd"     permissions: "0644"     owner: "root"     content: |       --- 我的 CoreOS 集群 ---

以下是关于前面cloud-config的一些说明:

  • etcd2部分指定了etcd2服务的配置参数。在这个例子中,我们指定了启动etcd所需的参数,并在 CoreOS 节点上进行配置。public_ipv4private_ipv4环境变量将被替换为 CoreOS 节点的 IP 地址。由于只有一个节点,我们使用静态集群定义方式,而不是使用发现令牌。根据指定的参数,20-cloudinit.conf Drop-In 单元将在/run/systemd/system/etcd2.service.d目录下创建,包含以下环境变量:

    [Service] Environment="ETCD_ADVERTISE_CLIENT_URLS=http://172.17.8.101:2379" Environment="ETCD_INITIAL_ADVERTISE_PEER_URLS=http://172.17.8.101:2380" Environment="ETCD_INITIAL_CLUSTER=etcdserver=http://172.17.8.101:2380" Environment="ETCD_INITIAL_CLUSTER_STATE=new" Environment="ETCD_INITIAL_CLUSTER_TOKEN=etcd-cluster-1" Environment="ETCD_LISTEN_CLIENT_URLS=http://0.0.0.0:2379,http://0.0.0.0:4001" Environment="ETCD_LISTEN_PEER_URLS=http://172.17.8.101:2380,http://172.17.8.101:7001" Environment="ETCD_NAME=etcdserver"

  • fleet 部分指定 fleet 服务的配置参数,包括节点的任何元数据。20-cloudinit.conf Drop-In 单元将被创建在 /run/systemd/system/fleet.service.d,并包含以下环境变量:

    [Service] Environment="FLEET_METADATA=role=services" Environment="FLEET_PUBLIC_IP=172.17.8.101"

  • 更新部分指定了 CoreOS 节点的更新策略。它将在节点中更新为 /etc/coreos/update.conf

    GROUP=alpha REBOOT_STRATEGY=etcd-lock

  • units 部分启动 etcd2fleetflannel。对于 flannel,我们有一个 Drop-In 单元来更新用于创建容器的子网,该容器将与 Flannel 网络服务一起使用。50-network-config.conf Drop-In 单元将被创建在 /etc/systemd/system/flanneld.service.d

    [Service] ExecStartPre=/usr/bin/etcdctl set /coreos.com/network/config '{ "Network": "10.1.0.0/16" }'

  • 单元部分中的 docker-tcp.socket 是一个新的 systemd 单元,我们指定了允许通过端口 2375 暴露 docker 守护进程的服务内容。该单元将被创建在 /etc/systemd/system

  • write_files 部分可用于创建任何静态文件。例如,当用户登录时显示问候文本,我们可以使用 /etc/motd 来实现。问候消息将如下所示:

    Last login: Tue Sep 15 14:15:04 2015 from 10.0.2.2 --- My CoreOS Cluster --- core@core01 ~ $

Cloud-config 验证器

Cloud-config 使用 YAML 语法。YAML 是一种人类可读的数据序列化格式,并使用缩进和空格进行对齐。最好在使用 cloud-config YAML 配置文件之前进行验证。验证 CoreOS cloud-config 有两种方式。

一个托管的验证器

使用 CoreOS 提供的链接 (coreos.com/validate/) 来验证 cloud-config

下面是一个有效和无效的 cloud-config 示例,以及使用验证器的结果。

有效的 cloud-config

如下截图所示,验证器表示以下 cloud-config 是有效的:

无效的 cloud-config

这里,我们可以看到验证器已指定第 14 行缺少 -。YAML 使用空格进行分隔,所以我们需要确保空格的数量完全正确:

cloudinit 验证器

我们可以使用 CoreOS 中可用的 coreos-cloudinit --validate 选项来验证 cloud-config。让我们看一下下面的示例 cloud-config

当我们验证时,我们没有得到错误,正如以下截图所示:

现在,让我们尝试使用带有错误的相同 cloud-config。这里,在内容行中缺少了 |

当我们验证时,我们看到以下错误:

执行 cloud-config

有两个 cloud-config 文件在 CoreOS 启动过程中作为一部分运行:

  • 系统 cloud-config

  • 用户 cloud-config

系统的 cloud-config 由提供商(如 Vagrant 或 AWS)提供,并作为 CoreOS 提供商镜像的一部分嵌入其中。不同的提供商(如 Vagrant、AWS 和 GCE)在 /usr/share/oem/cloud-config.yaml 中有各自的 cloud-config。该 cloud-config 负责设置提供商特定的配置项,如网络、SSH 密钥、挂载选项等。coreos-cloudinit 程序首先执行系统 cloud-config,然后执行用户 cloud-config

根据提供商的不同,可以使用 config-drive 或内部用户数据服务提供用户的 cloud-config。Config-drive 是通过挂载一个只读分区,提供包含 cloud-config 的数据到主机机器上的通用方式。Rackspace 使用 config-drive 获取用户的 cloud-config,而 AWS 则使用其内部的用户数据服务来获取用户数据,不依赖 config-drive。在 Vagrant 环境中,Vagrantfile 负责将 cloud-config 复制到 CoreOS 虚拟机中。

使用 Vagrant 搭建 CoreOS 集群

Vagrant 可以在 Windows 或 Linux 上安装。以下是我用于 Vagrant CoreOS 的开发环境:

在本书中的一些示例中,我使用 Vagrant 在 Windows 笔记本上的 Virtualbox 上运行 Linux 虚拟机中的 CoreOS。

启动 Vagrant 环境的步骤

  1. 查看 coreos-vagrant 的代码库:

    git clone https://github.com/coreos/coreos-vagrant.git

  2. 复制 user-dataconfig.rb 的示例文件到 coreos-vagrant 目录中:

    cd coreos-vagrant``mv user-data.sample user-data``mv config.rb.sample config.rb

  3. 根据需要编辑 Vagrantfileuser-dataconfig.rb

  4. 启动 CoreOS 集群:

    Vagrant up

  5. SSH 到单独的节点:

    Vagrant ssh core-<id>

需要修改的重要文件

以下是需要修改的重要文件以及常见的修改项。

Vagrantfile

Vagrant 根据 Vagrantfile 中定义的配置来设置虚拟机环境。以下是在 CoreOS 环境中一些相关的功能:

  • 使用的 CoreOS 软件版本通过 update_channel 来指定。版本可以指定为 stable(稳定版)、beta(测试版)和 alpha(开发版)。关于 CoreOS 软件版本的更多细节,请参阅 第三章,CoreOS 自动更新。

  • 虚拟机的 CPU 和内存以及需要暴露的端口。

  • SSH 密钥管理。

用户数据

user-data 文件实际上是 cloud-config 文件,它指定了发现令牌、环境变量和默认启动的单元列表。Vagrant 会将 cloud-config 文件复制到虚拟机内的 /var/lib/coreos-vagrant/vagrantfile-user-data 位置。coreos-cloudinit 会在每次启动时读取 vagrantfile-user-data,并利用它来创建机器的用户数据文件。

Config.rb

config.rb 文件指定了 CoreOS 节点的数量。该文件还提供了一个选项,可以自动生成发现令牌。这里的一些选项与 Vagrantfile 中的设置重叠,比如镜像版本。

Vagrant – 一个包含动态发现功能的三节点集群

在这里,我们将创建一个三节点的 CoreOS 集群,每个节点上运行 etcd2fleet,并且节点之间将动态发现彼此。

生成发现令牌

当我们启动一个多节点的 CoreOS 集群时,需要有一个引导机制来发现集群成员。为此,我们生成一个令牌,并将集群中初始节点的数量作为参数传入。每个节点启动时都需要使用此发现令牌。Etcd 会使用发现令牌将所有具有相同令牌的节点作为初始集群的一部分。CoreOS 运行该服务,从其中央服务器提供发现令牌。

有两种生成发现令牌的方法:

从浏览器访问:https://discovery.etcd.io/new?size=3

使用 curl: curl https://discovery.etcd.io/new?size=3

以下是一个 curl 示例,包含一个生成的发现令牌。该令牌需要复制到 user-data

集群创建步骤

以下是包含我们在上一节中生成的更新发现令牌的 cloud-config 用户数据,以及必要的环境变量和服务单元。所有三个节点将使用此 cloud-config

#cloud-config coreos:   etcd2:     #为每个唯一集群从 https://discovery.etcd.io/new 生成一个新的令牌     discovery: https://discovery.etcd.io/9a6b7af06c8a677b4e5f76ae9ce0da9c     # 多区域和多云部署需要使用 $public_ipv4     advertise-client-urls: http://$public_ipv4:2379     initial-advertise-peer-urls: http://$private_ipv4:2380     # 在官方端口和传统端口上监听     # 如果你的应用程序不依赖于传统端口,可以省略它们     listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001     listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001   fleet:     public-ip: $public_ipv4   flannel:     interface: $public_ipv4   units:     # 注意:这需要一个包含 etcd2 的版本     - name: etcd2.service       command: start     - name: fleet.service       command: start

我们需要在 config.rb 文件中将 num_instances 更新为 3,然后执行 vagrant up

为了验证基本集群操作,我们可以检查以下输出,应该能够看到集群成员。

以下是etcdctl成员输出,显示了三个集群成员:

以下是 fleet 成员输出,显示了三个集群成员:

Vagrant – 一个具有静态发现的三节点集群

在这里,我们将创建一个三节点 CoreOS 集群,并使用静态方式指定其集群邻居。在动态发现方法中,我们需要使用发现令牌来发现集群成员。静态发现可以用于集群成员无法访问令牌服务器,并且集群成员的 IP 地址已知的场景。

执行以下步骤:

  1. 首先,我们需要通过分别对每个节点执行git clone来创建三个独立的 CoreOS Vagrant 实例。

  2. 必须为每个节点更新config.rb文件,并将num_instances设置为 1。

  3. 应该更新每个节点的 Vagrantfile,以便将 IP 地址静态分配为172.17.8.101core-01)、172.17.8.102core-02)和172.17.8.103core-03)。IP 地址应根据您的环境进行更新。

第一个节点的cloud-config用户数据如下:

#cloud-config coreos:   etcd2:     name: core-01     initial-advertise-peer-urls: http://172.17.8.101:2380     listen-peer-urls: http://172.17.8.101:2380     listen-client-urls: http://172.17.8.101:2379,http://127.0.0.1:2379     advertise-client-urls: http://172.17.8.101:2379     initial-cluster-token: etcd-cluster-1     initial-cluster: core-01=http://172.17.8.101:2380,core-02=http://172.17.8.102:2380,core-03=http://172.17.8.103:2380     initial-cluster-state: new   fleet:     public-ip: $public_ipv4   flannel:     interface: $public_ipv4   units:     - name: etcd2.service       command: start     - name: fleet.service       command: start

第二个节点的cloud-config用户数据如下:

#cloud-config coreos:   etcd2:     name: core-02     initial-advertise-peer-urls: http://172.17.8.102:2380     listen-peer-urls: http://172.17.8.102:2380     listen-client-urls: http://172.17.8.102:2379,http://127.0.0.1:2379     advertise-client-urls: http://172.17.8.102:2379     initial-cluster-token: etcd-cluster-1     initial-cluster: core-01=http://172.17.8.101:2380,core-02=http://172.17.8.102:2380,core-03=http://172.17.8.103:2380     initial-cluster-state: new   fleet:     public-ip: $public_ipv4   flannel:     interface: $public_ipv4   units:     - name: etcd2.service       command: start     - name: fleet.service       command: start

第三个节点的cloud-config用户数据如下:

#cloud-config coreos:   etcd2:     name: core-03     initial-advertise-peer-urls: http://172.17.8.103:2380     listen-peer-urls: http://172.17.8.103:2380     listen-client-urls: http://172.17.8.103:2379,http://127.0.0.1:2379     advertise-client-urls: http://172.17.8.103:2379     initial-cluster-token: etcd-cluster-1     initial-cluster: core-01=http://172.17.8.101:2380,core-02=http://172.17.8.102:2380,core-03=http://172.17.8.103:2380     initial-cluster-state: new   fleet:     public-ip: $public_ipv4   flannel:     interface: $public_ipv4   units:     - name: etcd2.service       command: start     - name: fleet.service       command: start

我们需要分别为每个节点执行vagrant up命令。我们应该在etcdctl member listfleetctl list-machines的输出中看到集群成员列表的更新。

Vagrant – 一个包含三个主节点和三个工作节点的生产集群

在第一章,CoreOS 概述中,我们介绍了 CoreOS 集群架构。生产集群有一组节点(称为主节点)用于运行关键服务,另一组节点(称为工作节点)用于运行应用服务。在本示例中,我们创建了三个主节点来运行etcd和其他关键服务,以及另外三个工作节点。工作节点中的 etcd 将代理到主节点。工作节点将用于用户创建的服务,而主节点将用于系统服务。这避免了资源争用。以下是创建过程所需的步骤:

  • 创建一个包含三节点主集群和三节点工作集群的 Vagrant 集群。

  • 更新 Vagrantfile,确保主节点和工作节点之间的 IP 地址范围不冲突。

  • 使用动态发现令牌的方法为三节点集群创建令牌,并更新主节点和工作节点的cloud-config用户数据,确保令牌相同。我们已经将令牌大小指定为3,因为工作节点不运行etcd

以下是主节点集群的用户数据:

#cloud-config coreos:   etcd2:     discovery: https://discovery.etcd.io/d49bac8527395e2a7346e694124c8222     advertise-client-urls: http://$public_ipv4:2379     initial-advertise-peer-urls: http://$private_ipv4:2380     # 在官方端口和遗留端口上监听     listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001     listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001   fleet:      metadata: "role=master"      public-ip: $public_ipv4   units:     - name: etcd2.service       command: start     - name: fleet.service       command: start

以下是工作节点集群的用户数据。主节点和工作节点的发现令牌需要相同:

#cloud-config coreos:   etcd2:     discovery: https://discovery.etcd.io/d49bac8527395e2a7346e694124c8222     advertise-client-urls: http://$public_ipv4:2379     initial-advertise-peer-urls: http://$private_ipv4:2380     # 在官方端口和传统端口上监听     listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001     listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001   fleet:      metadata: "role=worker"      public-ip: $public_ipv4   units:     - name: etcd2.service       command: start     - name: fleet.service       command: start

主节点和工作节点用户数据之间的唯一区别是在 fleet 中使用的元数据。在这个示例中,我们为主节点集群使用 role 设置为 master,为工作节点集群使用 role 设置为 worker

我们来看看 etcdctl 成员列表和 fleet 机器列表。以下输出在主节点和工作节点集群中是相同的。

etcdctl 成员的输出如下:

fleet 成员的输出如下:

以下是 journalctl –u etcd2.service 在工作节点上的输出,显示工作节点代理到主节点的情况:

使用 AWS 的 CoreOS 集群

亚马逊 AWS 为您提供公共云服务。CoreOS 可以在 AWS 提供的虚拟机(VM)上运行。以下是此设置的一些前提条件:

  • 您需要一个 AWS 账户。AWS 提供一年免费的试用账户。

  • 创建并下载密钥对。密钥对用于通过 SSH 连接到节点。

  • 可以通过 AWS 控制台(GUI 界面)或 AWS CLI 访问 AWS 接口。AWS CLI(aws.amazon.com/cli/)可以在 Windows 或 Linux 系统上安装。

以下是使用 AWS 创建 CoreOS 集群的两种方法。

AWS – 使用 Cloudformation 创建三节点集群

Cloudformation 是 AWS 的一个编排工具,用于管理包括计算、存储和网络在内的一组 AWS 资源。以下链接,s3.amazonaws.com/coreos.com/dist/aws/coreos-stable-hvm.template,提供了 CoreOS 集群的模板文件。以下是模板中的一些关键部分:

  • 基于区域的 AMI 镜像 ID

  • EC2 实例类型

  • 安全组配置

  • CoreOS 集群的大小,包括自动扩展的最小和最大大小

  • 要使用的初始 cloud-config

对于以下示例,我修改了模板,使用 t2.micro 代替 m3.medium 作为实例大小。以下 CLI 可用于使用 Cloudformation 创建一个三节点的 CoreOS 集群。下面命令中的 discovery token 需要使用为您的情况生成的令牌来更新:

aws cloudformation create-stack \     --stack-name coreos-test \     --template-body file://mycoreos-stable-hvm.template \     --capabilities CAPABILITY_IAM \     --tags Key=Name,Value=CoreOS \     --parameters \      ParameterKey=DiscoveryURL,ParameterValue="https://discovery.etcd.io/925755234ab82c1ef7bcfbbacdd8c088" \         ParameterKey=KeyPair,ParameterValue="keyname"

以下是使用 aws cloudformation list-stacks 成功创建堆栈后的输出:

在前面的步骤完成后,我们可以看到成员已经成功地被 etcdfleet 发现。

AWS – 使用 AWS CLI 创建一个三节点集群

以下是使用 AWS CLI 在 AWS 中创建 CoreOS 集群的一些前提条件:

  1. 从发现令牌服务创建一个三节点集群的令牌。

  2. 设置一个安全组,暴露 sshicmp23792380 端口。23792380 用于 etcd2 客户端与服务器以及服务器间的通信。

  3. 使用此链接(coreos.com/os/docs/latest/booting-on-ec2.html)根据你的 AWS 区域和更新频道来确定 AMI 镜像 ID。不同 AWS 区域的最新镜像 ID 会自动更新在此链接中。

以下 CLI 命令将创建一个三节点集群:

aws ec2 run-instances --image-id ami-85ada4b5 --count 3 --instance-type t2.micro --key-name "yourkey" --security-groups "coreos-test" --user-data file://cloud-config.yaml

这里的 ami-85ada4b5 镜像 ID 来自稳定更新频道。coreos-test 安全组暴露了需要外部访问的必要端口。

以下是我使用的 cloud-config

#cloud-config coreos:   etcd2:     # 指定 集群的初始大小,通过 ?size=X     discovery: https://discovery.etcd.io/47460367c9b15edffeb49de30cab9354     advertise-client-urls: http://$private_ipv4:2379,http://$private_ipv4:4001     initial-advertise-peer-urls: http://$private_ipv4:2380     listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001     listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001   units:     - name: etcd2.service       command: start     - name: fleet.service       command: start

以下输出显示了包含三节点的集群中的 etcd 成员列表和 fleet 成员列表:

相同的示例可以通过 AWS 控制台进行尝试,在这里我们可以从 GUI 中指定选项。

GCE 上的 CoreOS 集群

谷歌的 GCE 是另一家公共云服务提供商,类似于亚马逊 AWS。CoreOS 可以运行在 GCE 提供的虚拟机上。以下是此设置的一些前提条件:

  • 你需要一个 GCE 账户。GCE 提供 60 天的免费试用账户。

  • 可以通过 gcloud SDK 或 GCE GUI 控制台访问 GCE 资源。SDK 可以从 cloud.google.com/sdk/ 下载。

  • 需要在 GCE 中创建一个基础项目,所有资源都将托管在该项目下。

  • 需要创建一个安全令牌,用于 SSH 访问。

GCE – 使用 GCE CLI 创建一个三节点集群

以下是在 GCE 中创建 CoreOS 集群的一些前提条件:

  • 为一个三节点集群从发现令牌服务创建一个令牌。

  • 设置一个安全组,暴露端口 sshicmp2379238023792380 用于 etcd2 客户端到服务器以及服务器之间的通信。

  • 链接 coreos.com/os/docs/latest/booting-on-google-compute-engine.html 会自动更新,包含来自稳定、测试和 alpha 通道的最新 GCE CoreOS 版本。我们需要选择所需的镜像。

以下 CLI 可用于从稳定版本创建三节点 CoreOS GCE 集群:

gcloud compute instances create core1 core2 core3 --image https://www.googleapis.com/compute/v1/projects/coreos-cloud/global/images/coreos-stable-717-3-0-v20150710 --zone us-central1-a --machine-type n1-standard-1 --metadata-from-file user-data=cloud-config.yaml

以下是使用的 cloud-config.yaml 文件:

#cloud-config coreos:   etcd2:     # 指定 集群的初始大小,使用 ?size=X     discovery: https://discovery.etcd.io/46ad006905f767331a36bb2a4dbde3f5     advertise-client-urls: http://$private_ipv4:2379,http://$private_ipv4:4001     initial-advertise-peer-urls: http://$private_ipv4:2380     listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001     listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001   units:     - name: etcd2.service       command: start     - name: fleet.service       command: start

我们可以使用 gcloud compute ssh <nodeid> 通过 SSH 登录到任意节点。

以下输出显示集群已成功创建,etcdfleet 中的成员都已显示:

CoreOS 集群也可以通过 GCE 控制台 GUI 界面创建。

在裸机上安装 CoreOS

在裸机上安装 CoreOS 有两种方法:

  • CoreOS ISO 镜像

  • PXE 或 IPXE 启动

以下步骤介绍了使用 ISO 镜像在裸机上安装 CoreOS 的方法。

我通过将 CoreOS ISO 镜像安装到 Virtualbox CD 驱动器上进行了安装。如果我们将 ISO 镜像烧录到 CD 上然后在裸机上安装,过程应该是相同的。

以下是步骤总结:

  1. coreos.com/os/docs/latest/booting-with-iso.html 下载所需的稳定、测试和 alpha 版本的 ISO 镜像。

  2. 在 Virtualbox 中启动一个新的 Linux 机器,配置所需的 CPU、内存和网络设置,并将 ISO 镜像挂载到 IDE 驱动器上。

  3. 创建一个 SSH 密钥,使用 ssh-keygen 登录到 CoreOS 节点。

  4. 启动 Linux 机器后,使用 CoreOS 脚本将 CoreOS 安装到硬盘上,并提供必要的 cloud-config。这里使用的 cloud-config 与前面章节使用的类似,SSH 密钥需要手动更新。

  5. 从 Virtualbox 中移除 CD 驱动器并重新启动,这样就会从硬盘加载 CoreOS 镜像。

我使用的是稳定版本的 ISO 镜像 766.4.0。

以下截图展示了 Virtualbox 中初始存储挂载状态,ISO 镜像位于 IDE 驱动器上:

获取 cloud-config 的最简单方法是使用 wget。当我们从 CD 启动时,由于没有窗口管理器,无法进行复制粘贴。最简单的方式是将 cloud-config 放置在一个托管位置,并使用 wget 从该位置获取到节点上。SSH 密钥需要适当更新。

wget https://github.com/smakam/coreos/raw/master/single-node-cloudconfig.yml

将 CoreOS 安装到硬盘可以通过 CoreOS 提供的脚本完成:

sudo coreos-install -d /dev/sda -C stable -c ~/cloud-config.yaml

安装成功后,我们可以关机并移除 IDE 驱动器,这样系统就会从硬盘启动。以下截图展示了在 Virtualbox 中使用硬盘启动的存储选择:

节点启动后,我们可以通过 SSH 连接到节点,因为我们已经设置好了 SSH 密钥。以下输出显示了裸机上的 CoreOS 版本:

基本调试

以下是一些基本的调试工具和方法,用于调试 CoreOS 集群中的问题。

journalctl

Systemd-Journal 负责记录所有内核和 systemd 服务的日志。所有服务的日志文件都会存储在 /var/log/journal 中的集中位置。日志以二进制格式存储,这使得它易于转换成不同的格式。

这里有一些常见的例子,展示了如何使用 Journalctl:

  • Journalctl:这是列出所有来源的合并日志。

  • Journalctl –u etcd2.service:这是列出 etcd2.service 的日志。

  • Journalctl –u etcd2.service –f:这是以类似 tail –f 格式列出 etcd2.service 的日志。

  • Journalctl –u etcd2.service –n 100:这是列出最后 100 行日志。

  • Journalctl –u etcd2.service –no-pager:这是列出无分页的日志,适用于搜索。

  • Journalctl –p err –n 100:这是通过筛选日志列出所有 100 个错误。

  • journalctl -u etcd2.service --since today:这是列出今天的 etcd2.service 日志。

  • journalctl -u etcd2.service -o json-pretty:这是以 JSON 格式列出 etcd2.service 的日志。

systemctl

systemctl 工具可用于基本的系统 d 单元监控和故障排除。

以下示例显示了 systemdunit docker.service 的状态:

如果某个服务出现问题,我们可以停止并重新启动该服务。

以下命令将重新启动 docker.service:

sudo systemctl restart docker.service

当服务文件或环境变量发生更改时,我们需要执行以下命令来重新加载配置,以便更改生效,再重新启动服务:

sudo systemctl daemon-reload

以下命令有助于查看哪些单元已失败:

Systemctl --failed

Cloud-config

之前,我们已经看过如何预验证 cloud-config YAML 文件。如果出现运行时错误,可以使用 journalctl -b _EXE=/usr/bin/coreos-cloudinit 来检查。

如果在初始节点设置后对 cloud-config 用户数据进行了更改,可以执行以下步骤来激活新的配置:

  • 执行 vagrant reload --provision 以获取新的配置。

  • 新的 cloud-config 用户数据将保存在 /var/lib/coreos-vagrant,文件名为 vagrantfile-user-data。执行 sudo coreos-cloudinit --from-file vagrantfile-user-data 来更新新的配置。

从一个 CoreOS 节点登录到另一个 CoreOS 节点

有时,从集群中的某个 CoreOS 节点 SSH 登录到其他节点很有用。以下一组命令可用于转发 SSH 代理,我们可以使用它从其他节点进行 SSH 登录。有关 SSH 代理转发的更多信息,请参见 rabexc.org/posts/using-ssh-agent

eval `ssh-agent` ```ssh-add <key>(密钥是私钥)ssh -i core@ -A(密钥是私钥)`

在此之后,我们可以通过 SSH 连接到机器 ID 或特定的 Fleet 单元,如下图所示:

注意

注意:SSH 代理转发不安全,应仅用于调试。

重要文件和目录

了解这些文件和目录有助于调试问题:

  • systemd 单元文件位置 - /usr/lib64/systemd/system

  • 网络单元文件 - /usr/lib64/systemd/network

  • 用户编写的单元文件和附加项用于更改默认参数 - /etc/systemd/system。特定配置更改的附加项可以在特定服务目录下使用配置文件进行。例如,要修改 fleet 配置,可以创建 fleet.service.d 目录,并将配置文件放入该目录。

  • 用户编写的网络单元文件 - /etc/systemd/network

  • 运行时环境变量和各个组件的附加配置,如 etcdfleet- /run/systemd/system/

  • 包含 cloud-config 用户数据的 vagrantfile 用户数据文件,使用 Vagrant - /var/lib/coreos-vagrant

  • systemd-journald 会记录日志 - /var/log/journal

  • 与 Vagrant、AWS 和 GCE 等提供商相关的 cloud-config.yaml - /usr/share/oem。(CoreOS 首先执行此 cloud-config,然后执行用户提供的 cloud-config。)

  • 发布渠道和更新策略 - /etc/coreos/update.conf

  • 公共和私有 IP 地址(COREOS_PUBLIC_IPV4COREOS_PRIVATE_IPV4- /etc/environment

  • 特定 CoreOS 节点的机器 ID 为- /etc/machine-id

  • flannel 网络配置- /run/flannel/

常见错误及可能的解决方案

  • 对于云提供商上的 CoreOS,需要在虚拟机上打开 2379 和 2380 端口。2379 用于 etcd 客户端与服务器之间的通信,2380 用于 etcd 服务器之间的通信。

  • 每次为每个集群生成一个发现令牌,且不可共享。当共享过期的发现令牌时,成员将无法加入 etcd 集群。

  • 同时运行多个 CoreOS 集群与 Vagrant 可能会导致问题,因为 IP 范围重叠。应特别注意,以避免在集群间共享常见参数(如 IP 地址)。

  • 云配置 YAML 文件需要正确缩进。最好使用云配置验证器来检查问题。

  • 使用发现令牌时,CoreOS 节点需要具备互联网访问权限以连接到令牌服务。

  • 在创建发现令牌时,需要根据成员数量选择大小,并且所有成员需要是引导的一部分。如果没有所有成员,集群将无法形成。以后可以添加或删除成员。

总结

在本章中,我们介绍了 CoreOS 云配置的基础知识,并展示了如何使用 Vagrant、Amazon AWS、Google GCE 和裸金属设置 CoreOS 开发环境。我们还介绍了一些常见问题的基本调试步骤。正如本章所描述的,安装 CoreOS 在本地数据中心或云环境中是很容易的。建议在进入生产环境之前,在开发集群中进行部署尝试。下一章我们将介绍 CoreOS 自动更新的工作原理。

参考文献

进一步阅读和教程

第四章

第三章 CoreOS 自动更新

CoreOS 的使命之一是确保操作系统尽可能安全。实现这一目标的一种方法是保持操作系统更新到最新的补丁。CoreOS 自动更新方案为您提供了一个安全、可靠、强大的机制来推送更新。CoreOS 还提供了足够的控制,让用户根据自己的环境控制更新。

本章将涵盖以下主题:

  • CoreOS 发布周期

  • CoreOS 中使用的分区方案

  • CoreOS 自动更新基础设施

  • CoreOS 更新的配置

  • CoreOS 的 CoreUpdate 商业服务

本章中的所有示例将使用在 AWS 环境中的 CoreOS。文中还提到了一个关于 Vagrant CoreOS 更新的部分,讨论了 Vagrant 特定的 CoreOS 更新。

CoreOS 发布周期

Alpha、Beta 和 Stable 是 CoreOS 中的发布渠道。CoreOS 的发布按以下顺序通过每个渠道进行:Alpha -> Beta -> Stable。Alpha 渠道是开发渠道。Alpha 渠道中的 Alpha 发布在达到定义的质量标准后会晋升到 Beta 渠道,成为 Beta 发布。Beta 渠道中的 Beta 发布在达到生产质量后会晋升到 Stable 渠道,成为 Stable 发布。所有发布最初都是 Alpha,但晋升到 Beta 和 Stable 是基于测试结果进行的。

CoreOS 发布页面反映了最新的 Alpha、Beta 和 Stable 发布(coreos.com/releases/)。以下是截至 2015 年 8 月 19 日的最新发布:

主版本号(例如 766 在 766.3.0 中)是从 2013 年 7 月 13 日开始计算的天数,这一天是 CoreOS 的纪元。

由于 CoreOS 由多个系统组件组成,例如 etcd、fleet、flannel、Docker 和 RKT,每个发布将根据各个组件的稳定性有一个特定版本的系统组件。例如,以下是截至 CoreOS 版本 808.0.0 时,关键系统组件的版本:

以下命令可用于检查节点中的 CoreOS 版本。此节点运行的是 723.3.0 版本,当时是一个稳定版本:

以下命令可用于检查 CoreOS Linux 内核版本:

以下是 CoreOS 版本 723.3.0 中关键系统组件的版本:

CoreOS 中的分区表

分区表展示了操作系统维护的磁盘分区。以下图像展示了在使用 sudo cgpt show /dev/xvda 命令时,CoreOS 集群节点中的一个分区表:

以下截图展示了同一节点中的 df –k 输出:

以下是关于前两个输出的一些说明:

  • 总共有九个分区。关键分区是 USR-AUSR-BOEMROOT

  • 系统文件位于USR分区,用户文件位于ROOT分区,提供商相关的文件位于OEM分区。

  • USR分区挂载为只读,ROOT分区挂载为读写。

  • ROOT分区挂载为/USR-AUSR-B分区挂载为/usrOEM分区挂载为/usr/share/oem

  • 有两个/usr分区,USR-AUSR-B。默认情况下,系统启动时使用USR-A分区。当 CoreOS 更新完成后,根分区会被下载到USR-B,并通过持久性标志如prioritytriessuccessful,CoreOS 启动加载程序会在启动时选择合适的 USR 分区。在前面的例子中,USR-A分区的优先级设置为1USR-B分区的优先级设置为0,因此 CoreOS 启动加载程序选择了USR-A

我手动更新了操作系统,以下输出显示活动分区为USR-B,且USR-B的优先级较高。CoreOS 系统的手动更新可以使用以下“更新示例”部分指定的命令进行。现在,/usr目录指向/dev/xvda4,即USR-B,而之前指向的是/dev/xvda3,即USR-A

CoreOS 自动更新

CoreOS 依赖于自动更新机制保持操作系统的最新状态。以下是 CoreOS 更新的几个方面:

  • CoreOS 更新机制基于谷歌开源的 Omaha 协议(code.google.com/p/omaha/),该协议用于 Chrome 浏览器。

  • 可以使用 CoreOS 公共服务器或私有服务器作为镜像仓库。

  • 使用双分区方案进行更新,在更新时,次要分区会被更新,而主分区保持不变。重启时,主分区与次要分区之间会进行二进制交换。这保持了更新机制的稳定性。如果新镜像出现问题,CoreOS 会自动回滚到另一个分区中正常工作的镜像。

  • 镜像在每次更新时都会进行签名和验证。

以下截图显示了自动更新的步骤:

更新和重启服务

CoreOS 中有两个控制更新和重启的关键服务,它们是update-engine.servicelocksmithd.service

Update-engine.service

Update-engine.service负责定期检查指定的适当发布渠道中的更新。默认的更新检查会在重启后的 10 分钟或每小时一次的间隔内进行。

以下输出显示了update-engine.service的状态:

发布渠道在/etc/coreos/update.conf中指定。在以下节点中,选择的发布渠道为稳定版。发布渠道是通过cloud-config派生的:

Update-engine.service负责更新适当的分区,USR-AUSR-B。当前使用的分区不会被更改。

以下命令可以执行以触发手动更新:

update_engine_client -check_for_update

调试 update-engine.service

可以使用journalctl –u update-engine.service查看更新服务的日志。从日志中,我们可以识别奥马哈协议的请求和响应,并可以使用响应中的错误代码进行调试。

Locksmithd.service

Locksmithd.service负责使用选定的重启策略重启 CoreOS 节点。Locksmithd.service作为守护进程运行。

以下输出显示了locksmithd.service的状态:

Locksmith 策略

以下是 CoreOS 节点在新镜像更新后重启的四种可配置策略。

etcd-lock 方案

在此方案中,首先从etcd获取锁,然后执行重启。在多节点集群中,这种方式效果非常好,因为它防止所有节点同时重启,并保持集群的完整性。我们可以使用锁定计数机制控制可以同时重启的节点数。锁定最大计数指定可以同时获取锁的节点数。在三节点集群中,我们需要将锁定最大计数限制为1,但在五节点集群中,我们可以将锁定最大计数保持为2,这样最多允许两个节点同时获取锁并重启。

以下示例展示了在我们执行锁定和解锁操作时,锁定计数的变化情况:

重启

在此方案中,节点会立即重启,而无需从集群中获取锁。这在管理员手动控制升级的场景中非常有用。

尽力而为

在此方案中,首先检查etcd是否正在运行。如果etcd正在运行,则获取etcd锁并执行重启。否则,立即执行重启。这是前面提到的 etcd-lock 方案的变体。

关闭

这将导致 locksmithd 退出并不做任何操作。除非管理员希望精确控制升级,否则不应选择此选项。

Locksmith 组是在 locksmithd 版本 0.3.1 中引入的。通过组,我们可以将一组 CoreOS 节点进行分组,锁定将适用于该组。例如,假设我们有一个五节点集群,其中两个节点正在运行负载均衡器。如果我们将锁定最大计数设置为2,那么两个运行负载均衡器的节点可能会同时重启,在此期间我们可能会失去该服务。为避免此问题,我们可以为默认组和lb组设置不同的锁定最大计数。

在以下截图示例中,我们为默认组设置了 2 的锁定计数,并为 lb 组设置了 1 的锁定计数。组可以在启动 locksmithd service 时定义。要将 CoreOS 节点放入 locksmith 组,我们需要通过 --group 选项启动 locksmithd,或设置 LOCKSMITHD_GROUP 环境变量并重启 locksmithd 服务:

Locksmithctl

Locksmithctl 是一个用于控制 locksmith 的前端 CLI。通过这个工具,我们可以查看 locksmith 服务的状态、锁定和解锁组、设置 lock max 数量等。

调试 locksmithd.service

可以通过 journalctl –u locksmithd.service 检查此服务的日志。

设置更新选项

CoreOS 更新选项可以通过 cloud-config 或手动更改配置文件来设置。使用 cloud-config 时,更新选项作为节点配置的一部分在重启后进行配置。手动方法需要启动适当的更新服务才能使更改生效。手动方法主要用于调试。

使用 cloud-config

以下是一个示例 cloud-config,其中发布渠道组设置为 stable,而 locksmith 重启策略设置为 etcd-lock。(默认使用的服务器是 public.update.core-os.net/,因此此处未指定。)

#cloud-config coreos:   etcd2:     # 指定 集群的初始大小 ?size=X     discovery: https://discovery.etcd.io/eb32a1397bd087f84e65ab802b6aa2f7     advertise-client-urls: http://$private_ipv4:2379,http://$private_ipv4:4001     initial-advertise-peer-urls: http://$private_ipv4:2380     listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001     listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001   update:     reboot-strategy: "etcd-lock"     group: "stable"   units:     - name: etcd2.service       command: start     - name: fleet.service       command: start

在使用前述 cloud-config 启动集群后,我们可以检查 /etc/coreos/update.conf 是否已经更新了正确的参数:

手动配置

默认的重启策略是 best-effort。在以下节点中,未指定重启策略,因此使用的是 best-effort:

让我们将重启策略更改为 reboot,并在 /etc/coreos/update.conf 中进行修改。然后需要重启 locksmithd.service

如下日志所示,重启策略正在生效:

更新示例

我们可以在同一发布渠道内进行更新,也可以跨发布渠道进行更新。如果我们在同一发布渠道内进行更新,则节点会更新到该发布渠道的最新版本。如果我们跨发布渠道进行更新,则节点会更新到新发布渠道的最新版本。

在同一发布渠道内更新

让我们看看初始版本和重启策略。节点正在运行稳定版本 723.3.0,如下图所示:

查看 CoreOS 发布页面,最新的 STABLE 版本是 766.3.0。如果我们在 STABLE 渠道上进行更新,节点应该会更新到 766.3.0。

让我们通过以下命令手动触发更新:

update_engine_client -check_for_update

如果我们没有手动触发更新,update-engine仍会根据其定期检查自动进行更新。

以下来自update-engine.service的日志显示了 Omaha 向 CoreOS 公共映像服务器的请求:

以下来自 update-engine 的日志显示了来自 CoreOS 公共服务器的 Omaha 响应,提供了版本为 766.3.0 的映像:

更新成功后,以下信息将出现在节点的locksmithd.service中,表明该节点将使用新映像进行更新。新映像将被更新到非活动的 USR 分区:

以下是重启后的节点版本。我们可以看到版本已成功升级到 766.3.0:

从一个发布渠道更新到另一个发布渠道

我们可以通过更新/etc/coreos/update.conf来切换发布渠道。以下是步骤:

  • 将发布渠道组从stable更新到alpha,如以下截图所示:

  • 重启update-engine.service

    sudo systemctl restart update-engine

  • update-engine服务将在 10 分钟后检查更新。我们可以通过以下命令强制更新:

    update_engine_client -check_for_update

以下日志显示正在获取版本 808.0.0 的映像:

以下是节点重启后的版本。我们可以看到映像已升级到最新的 alpha 发布808.0.0

CoreUpdate

CoreUpdate 是 CoreOS 提供的一项商业服务,用于管理 CoreOS 集群的客户更新。以下是 CoreUpdate 服务提供的一些功能:

  • GUI 仪表盘为您提供了所有更新的摘要和详细视图。

  • 客户定制的映像服务器将根据每个客户的需求提供。

  • 可以创建服务器组,以便进行分组更新,并且可以对每个组进行速率限制。

  • 提供 HTTP API,以便 CoreUpdate 可以与客户现有的 DevOps 系统集成。

  • 映像可以托管在公共服务器或客户的本地服务器上。从安全角度来看,这非常有用,这样客户就不必担心开放防火墙。

  • Updateservicectl作为前端 CLI 提供。

Vagrant CoreOS 更新

如果 Vagrant 盒子已经下载,只有当该盒子更新时,新的 CoreOS 版本才会被更新。

即使我们在 Vagrantfile 中将版本从稳定版更改为 alpha 再到 beta,新的 CoreOS 版本在执行 vagrant reload --provision 时并未更新。只有在执行 vagrant destroy 并重新启动后,新的版本才会加载。我们可以通过 update-engine 直接在 CoreOS 节点上触发更新,并且不受 VBOX 版本的影响。

当 Vagrant CoreOS 未更新时,我们会收到以下消息:

要更新 Vagrant box 版本,我们可以执行 vagrant box update,如下图所示:

vagrant reload 命令或 vagrant reload --provision 命令无法帮助更新 CoreOS 版本。我们需要销毁并重新创建集群以获取最新版本。

总结

在本章中,我们涵盖了 CoreOS 更新的不同方面,包括 CoreOS 发布周期、控制 CoreOS 更新的服务,以及客户控制其集群更新策略的选项。CoreOS 更新机制简单、独特且强大,能够解决云计算中最大的安全问题。在下一章中,我们将详细介绍关键的 CoreOS 服务——systemdetcdfleet

参考资料

进一步阅读和教程

第五章

第四章 CoreOS 主要服务 – Etcd、Systemd 和 Fleet

本章将介绍 CoreOS 关键服务——Etcd、Systemd 和 Fleet 的内部工作。对于每个服务,我们将涵盖安装、配置以及它们的应用。CoreOS 默认包括 Etcd、Systemd 和 Fleet。这些服务也可以作为独立组件安装在任何 Linux 系统上。本章将涵盖以下主题:

  • Etcd——安装、访问方法、配置选项、使用案例、调优、集群管理、安全性、认证和调试

  • Systemd——单元类型、说明符、模板和特殊单元

  • Fleet——安装、访问方法、模板、调度、高可用性和调试

  • 使用 Etcd 和 Fleet 的服务发现选项

Etcd

Etcd 是一个分布式键值存储,CoreOS 集群中的所有机器都使用它来读写和交换数据。第一章中提供了 Etcd 的概述。本节将介绍 Etcd 的内部工作。

版本

Etcd 正在持续开发中,频繁发布版本以添加新功能并修复漏洞。以下是最近版本更新的主要内容:

  • 版本 2.0 是第一个稳定版本,于 2015 年 1 月发布。版本 2.0 之前的版本作为 etcd 提供,而版本 2.0 之后的版本在 CoreOS 节点中作为 etcd2 提供。

  • 版本 2.0 增加了 IANA 分配的端口 2379 用于客户端与服务器的通信,2380 用于服务器与服务器的通信。之前,端口 4001 用于客户端与服务器的通信,端口 7001 用于服务器与服务器的通信。

  • 版本 2.1 引入了认证和度量收集功能,这些功能处于实验模式中。

  • 截至 2015 年 9 月,最新版本为 2.2.0。

  • 一个实验性的 v3 API(一些示例包括多键读取、范围读取和二进制键)现在作为预览版可用,预计将在 2015 年 10 月底的 2.3.0 版本中正式发布。

本章中的所有示例均基于 etcd 版本 2.1.0 及以上版本。

安装

CoreOS 自带 etcd。etcd 和 etcd2 版本都包含在基础 CoreOS 镜像中。以下是 CoreOS alpha 镜像 779.0.0 中提供的 etcd 版本:

独立安装

Etcd 也可以安装在任何 Linux 机器上。以下是尝试在 Ubuntu 14.04 上安装 etcd 版本 2.2 的安装命令:

curl -L https://github.com/coreos/etcd/releases/download/v2.2.0/etcd-v2.2.0-linux-amd64.tar.gz -o etcd-v2.2.0-linux-amd64.tar.gz``tar xzvf etcd-v2.2.0-linux-amd64.tar.gz

以下示例展示了如何在独立模式下尝试使用 etcd。启动服务器时运行以下命令:

etcd -name etcdtest

现在,检查是否可以通过一些基本命令连接到 etcd 服务器:

etcdctl cluster-health

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

以下是使用 curl 接口进行简单的 set 和 get 操作的示例:

curl –L –X PUT http://127.0.0.1:2379/v2/keys/message -d value="hello"``curl –L http://127.0.0.1:2379/v2/keys/message

以下截图是前述命令的输出:

访问 etcd

可以通过 etcdctl CLI 或 REST API 访问 Etcd。这适用于独立的 etcd 以及 CoreOS 中的 etcd。下图展示了访问 etcd 的不同方式:

REST

可以通过 REST API 访问和修改 etcd 数据库。使用这种方式可以本地或远程访问 etcd 数据库。

以下示例展示了使用curl方法访问 CoreOS 节点以获取所有键:

curl –L http://localhost:2379/v2/keys/?recursive=true

以下截图是前述命令的输出:

以下示例展示了使用curl方法访问远程 CoreOS 节点以获取所有键:

curl –L http://172.17.8.101:2379/v2/keys/?recursive=true

以下截图是前述命令的输出:

Etcdctl

Etcdctl 是 REST 接口之上的 CLI 封装。Etcdctl 可以用于本地或远程访问。

以下示例展示了使用 etcdctl 方法访问 CoreOS 节点以获取所有键:

etcdctl ls / --recursive

以下截图是前述命令的输出:

以下示例展示了使用 etcdctl 方法访问远程 CoreOS 节点以获取所有键:

etcdctl --peers=http://172.17.8.101:2379 ls / --recursive

以下截图是前述命令的输出:

Etcd 配置

Etcd 配置参数可以用来修改 etcd 成员属性或集群范围属性。Etcd 选项可以通过命令行或使用环境变量进行设置。命令行设置会覆盖环境变量。以下是一些广泛的分类及其关键配置参数/环境变量:

  • 成员:名称、数据目录和心跳间隔

  • 集群:发现令牌和初始集群节点

  • 代理:代理开/关和代理间隔

  • 安全:证书和密钥

  • 日志:启用/禁用日志记录和日志级别

  • 实验性

以下是一个 etcd 调用示例,其中使用了一些前述配置参数:

etcd -name infra0 -data-dir infra0 --cacert=~/.etcd-ca/ca.crt -cert-file=/home/smakam14/infra0.crt -key-file=/home/smakam14/infra0.key.insecure -advertise-client-urls=https://192.168.56.104:2379 -listen-client-urls=https://192.168.56.104:2379

Etcd 环境变量也可以在cloud-config中指定。以下是一个cloud-config示例,用于指定 etcd 环境变量:

etcd2:     #为每个唯一集群生成一个新令牌,来自 https://discovery.etcd.io/new     discovery: https://discovery.etcd.io/d93c8c02eedadddd3cf14828f9bec01c     # 多区域和多云部署需要使用$public_ipv4     advertise-client-urls: http://$public_ipv4:2379     initial-advertise-peer-urls: http://$private_ipv4:2380     # 同时监听官方端口和遗留端口     listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001     listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001

从 cloud-config 中获取的 etcd2 环境变量存储在以下目录:/run/systemd/system/etcd2.service.d

如果环境变量发生更改,etcd2 服务需要重新启动。

可以在coreos.com/etcd/docs/latest/configuration.html找到完整的 etcd 配置参数和环境变量列表。

Etcd 操作

以下是一些可以使用 etcd 执行的主要操作示例:

  • 键值对的设置、获取和删除操作

  • 设置带有超时的键,其中该键会自动过期

  • 根据原子条件检查设置一个键

  • 隐藏键

  • 监视并等待键的变化

  • 创建有序的键

使用这些操作,etcd 可以用于各种分布式应用场景。以下是一个 TTL 使用场景的示例,其中我们检查 Apache 服务的存活性,并更新如 IP 地址和端口号等服务详情到 etcd,其他应用可以使用这些信息来判断服务是否正在运行。如果 Apache 服务停止,在这种情况下,etcd 中的键值对将在 30 秒后被删除:

## 测试服务是否可访问,然后注册有用的信息,如 IP 地址,端口 ExecStart=/bin/bash -c '\   while true; do \     curl -f ${COREOS_PUBLIC_IPV4}:%i; \     if [ $? -eq 0 ]; then \       etcdctl set /services/apachet/${COREOS_PUBLIC_IPV4} \'{"host": "%H", "ipv4_addr": ${COREOS_PUBLIC_IPV4}, "port": %i}\' --ttl 30; \     fi; \     sleep 20; \   done'

我们可以找到关于 etcd 节点的统计信息,以及与键相关的操作。

以下输出显示了 etcd 节点的统计信息:

curl http://127.0.0.1:2379/v2/stats/self | jq .

以下截图是前述命令的输出:

以下输出显示了 etcd 键的统计信息:

curl http://127.0.0.1:2379/v2/stats/store | jq .

以下截图是前述命令的输出:

Etcd 调优

以下是一些可以调整的 etcd 参数,以根据操作环境实现最佳集群性能:

  • 集群大小:较大的集群可以提供更好的冗余。大集群的缺点是更新可能需要较长时间。在第一章,CoreOS 概述中,我们看到了不同集群大小下的容错极限。

  • 心跳间隔:这是主节点向其跟随节点发送心跳消息的时间间隔。默认的心跳间隔为 100 毫秒。选择心跳间隔时需要根据节点之间 ping 的平均往返时间来决定。如果节点地理分布较广,往返时间会更长。建议的心跳间隔是平均往返时间的 0.5-1.5 倍。如果选择较小的心跳间隔,数据包的开销会更高;如果选择较大的心跳间隔,则需要更长的时间才能检测到领导者故障。心跳间隔可以通过 heartbeat-interval 参数在 etcd 命令行中设置,或者通过 ETCD_HEARTBEAT_INTERVAL 环境变量设置。

  • 选举超时:当跟随节点在选举超时值内未能接收到心跳消息时,它们会成为领导者节点。默认的选举超时为 1,000 毫秒。建议的选举超时值是心跳间隔的 10 倍。将选举超时设置得过低可能会导致错误的领导者选举。选举超时可以通过 election-timeout 参数在 etcd 命令行中设置,或者通过 ETCD_ELECTION_TIMEOUT 环境变量设置。

Etcd 代理

当工作节点需要使用主节点或主集群提供 etcd 服务时,会使用 etcd 代理。在这种情况下,所有来自工作节点的 etcd 请求都将被代理到主节点,主节点再回复工作节点。

假设我们有一个正在运行的三节点主集群,配置如下:

以下示例展示了作为工作节点并充当代理的第四个节点的 cloud-config 配置。在此,主集群成员是静态指定的:

#cloud-config coreos:   etcd2:     proxy: on     listen-client-urls: http://localhost:2379     initial-cluster: etcdserver=http://172.17.8.101:2380, http://172.17.8.102:2380, http://172.17.8.103:2380   fleet:     etcd_servers: "http://localhost:2379"     public-ip: $public_ipv4   units:     - name: etcd2.service       command: start     - name: fleet.service       command: start

在前面的 Etcd 配置部分中,我们启用了代理并指向了 etcd_server 集群。第四个节点需要使用上述 cloud-config 配置启动。

以下示例展示了作为代理并使用发现令牌的第四个节点的 cloud-config 配置。我们需要使用与三节点集群相同的发现令牌:

#cloud-config coreos:   etcd2:     proxy: on     # 使用与主节点相同的发现令牌,这些节点将代理到主节点     discovery: <your token>     # 监听官方端口和旧版端口     listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001   fleet:     etcd_servers: "http://localhost:2379"     public-ip: $public_ipv4   units:     - name: etcd2.service       command: start     - name: fleet.service       command: start

以下是新节点上的 etcd 成员输出。如我们所见,etcd 集群由三个节点组成,且新节点正在代理主 etcd 集群:

以下是新节点上的 Fleet 机器输出。如我们所见,共有四个节点,包括第四个工作节点和三节点的 etcd 集群:

向集群中添加和移除节点

有时我们需要从一个正在运行的 etcd 集群中添加或移除节点。本节将演示如何在运行中的 etcd 集群中添加和移除节点。

假设我们有一个三节点的工作集群,并且想要向集群中添加第四个节点。可以在任一三节点中的一台上执行以下命令,添加第四个节点的详细信息:

以下cloud-config可以用来启动新的第四节点:

#cloud-config coreos: etcd2: name: core-04 initial_cluster: "core-01=http://172.17.8.101:2380,core-02=http://172.17.8.102:2380,core-03=http://172.17.8.103:2380,core-04=http://172.17.8.104:2380" initial_cluster_state: existing advertise-client-urls: http://$public_ipv4:2379 initial-advertise-peer-urls: http://$private_ipv4:2380 # listen on both the official ports and the legacy ports # legacy ports can be omitted if your application doesn't depend on them listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001 listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001 fleet: public-ip: $public_ipv4 units: # Note: this requires a release that contains etcd2 - name: etcd2.service command: start - name: fleet.service command: start

从以下输出可以看出,新的节点已经成功添加:

可以使用以下命令移除之前添加的第四个节点:

现在我们来检查成员列表和集群健康状况。我们可以看到,三个节点是集群的一部分,第四个节点已被移除:

节点迁移与备份

节点迁移是处理节点和集群故障以及将集群复制到不同位置时所必需的。

为了备份 etcd 数据库,我们可以执行以下操作:

Sudo etcdctl backup --data-dir=/var/lib/etcd2 --backup-dir=/tmp/etcd2

这种方法允许我们在另一个集群中重新使用备份的 etcd 数据。在这种方法中,备份目录中的nodeidclusterid被覆盖,以防止无意中将新节点添加到旧集群。

为了保留节点 ID 和集群 ID,我们必须手动复制,且复制品可以用来重启服务。

以下是移动 etcd2 数据目录的步骤:

  1. 停止服务:

  2. /var/lib/etcd2的 etcd 数据目录复制到/tmp/etcd2_backup

  3. 使用新的数据目录/tmp/etcd2_backup手动启动 etcd2:

处理迁移有两种方法:

  • 添加一个新成员并移除旧成员。我们可以使用etcdctl member addetcdctl member remove

  • 复制 etcd 数据库,将其移动到新节点并更新。

第一种方法中新成员有不同的身份。第二种方法则可以让新节点保持相同的旧身份。在第一种方法中,不需要停止 etcd 服务,而在第二种方法中,在进行备份之前需要停止 etcd 服务。

Etcd 安全性

需要一个安全的 etcd 来确保客户端与服务器之间的通信以及服务器之间的通信是安全的。下图展示了提供 etcd 安全性所涉及的不同组件。证书颁发机构用于提供和验证 etcd 客户端到服务器以及服务器到服务器的通信证书:

证书颁发机构 – etcd-ca

证书颁发机构是一个可信的来源,用于向可信服务器颁发证书。除了使用标准的证书颁发机构(CA)外,etcd 还允许使用自定义 CA。Etcd-ca (github.com/coreos/etcd-ca) 是一个 GO 应用程序,可以作为测试目的使用的 CA。最近,etcd 已迁移至 CFSSL (github.com/cloudflare/cfssl) 作为官方证书工具。

安装 etcd-ca

我在运行 Ubuntu 14.04 的 Linux 虚拟机上使用以下步骤安装了 etcd-ca:

git clone https://github.com/coreos/etcd-ca``cd etcd-ca``./build

注意

注意:在安装 etcd-ca 之前,需要先安装 GO 应用程序。

设置 etcd-ca 需要以下三个步骤:

  1. 使用 etcd-ca 创建 CA。

  2. 创建服务器密钥。

  3. 创建客户端密钥。

以下命令etcd-ca init用于创建一个 CA。这是一个一次性的过程。以下截图展示了创建 CA 时的输出:

以下命令可以用于创建服务器证书:

etcd-ca new-cert -ip 172.17.8.101 core-01``etcd-ca sign core-01``etcd-ca chain core-01``etcd-ca export --insecure core-01 | tar xvf –

在上述命令中,172.17.8.101是 CoreOS 节点的 IP,core-01是节点名称。这些步骤将创建core-01.crtcore-01.key.insecure

以下命令可以用于创建客户端证书:

etcd-ca new-cert -ip 192.168.56.104 client``etcd-ca sign client``etcd-ca chain client``etcd-ca export --insecure client | tar xvf -

在上述命令中,192.168.56.104是客户端节点的 IP。这些步骤将创建client.crtclient.key.insecure

使用服务器证书实现 etcd 安全的客户端与服务器通信

客户端使用服务器证书来确保服务器的身份。以下命令使用在前一部分生成的服务器证书和密钥启动 etcd 服务器:

etcd2 -name core-01 --cert-file=/home/core/core-01.crt --key-file=/home/core/core-01.key --advertise-client-urls=https://172.17.8.101:2379 --listen-client-urls=https://172.17.8.101:2379

以下是一个示例,展示如何使用安全机制设置密钥并检索它:

curl --cacert /home/smakam14/.etcd-ca/ca.crt https://172.17.8.101:2379/v2/keys/foo -XPUT -d value=bar -v``curl --cacert /home/smakam14/.etcd-ca/ca.crt https://172.17.8.101:2379/v2/keys/foo

以下示例使用 etcdctl 进行密钥检索:

etcdctl --ca-file /home/smakam14/.etcd-ca/ca.crt --peers https://172.17.8.101:2379 get /foo

Etcd 使用服务器证书和客户端证书进行安全的客户端到服务器通信

在前面的示例中,只有服务器有证书。在这个示例中,我们将生成一个客户端证书,以便服务器验证客户端的身份。以下命令使用服务器证书和密钥启动 etcd 服务器,并启用客户端身份验证。服务器证书和密钥与前一部分生成的相同:

etcd2 -name core-01 --data-dir=core-01 -client-cert-auth -trusted-ca-file=/home/core/ca.crt -cert-file=/home/core/key.crt -key-file=/home/core/key.key -advertise-client-urls https://172.17.8.101:2379 -listen-client-urls https://172.17.8.101:2379

以下是一个示例,展示如何使用安全的客户端和服务器机制设置密钥并检索它。客户端证书和密钥与前一部分生成的相同:

curl --cacert /home/smakam14/.etcd-ca/ca.crt --cert /home/smakam14/client.crt --key /home/smakam14/client.key.insecure -L https://172.17.8.101:2379/v2/keys/foo -XPUT -d value=bar -v``curl --cacert /home/smakam14/.etcd-ca/ca.crt --cert /home/smakam14/client.crt --key /home/smakam14/client.key.insecure https://172.17.8.101:2379/v2/keys/foo

以下示例使用 etcdctl 进行密钥检索:

etcdctl --ca-file /home/smakam14/.etcd-ca/ca.crt --cert-file /home/smakam14/client.crt --key-file /home/smakam14/client.key.insecure --peers https://172.17.8.101:2379 get /foo

一个安全的 cloud-config

以下是一个示例的 cloud-config,它设置了 etcd 的安全环境变量以及所需的证书和密钥:

cloud-config write_files:  - path: /run/systemd/system/etcd2.service.d/30-configuration.conf    permissions: 0644    content: |      [Service]      Environment=ETCD_NAME=core-01      Environment=ETCD_VERBOSE=1      # Encryption      Environment=ETCD_CLIENT_CERT_AUTH=1      Environment=ETCD_TRUSTED_CA_FILE=/home/core/ca.crt      Environment=ETCD_CERT_FILE=/home/core/server.crt      Environment=ETCD_KEY_FILE=/home/core/server.key       - path: /home/core/ca.crt    permissions: 0644    content: |      -----BEGIN CERTIFICATE-----       -----END CERTIFICATE-----      - path: /home/core/server.crt    permissions: 0644    content: |      -----BEGIN CERTIFICATE-----      -----END CERTIFICATE-----  - path: /home/core/server.key    permissions: 0644    content: |      -----BEGIN RSA PRIVATE KEY-----      -----END RSA PRIVATE KEY----- coreos:  etcd2:     # Static cluster     initial-cluster-token: etcd-cluster-1     initial-cluster: core-01=http://$private_ipv4:2380     initial-cluster-state: new     advertise-client-urls: http://$public_ipv4:2379     initial-advertise-peer-urls: http://$private_ipv4:2380     # listen on both the official ports and the legacy ports     # legacy ports 可以省略,如果您的应用不依赖于它们     listen-client-urls: http://$public_ipv4:2379     listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001  units:    - name: etcd2.service      command: start

认证

在引入认证功能之前,etcd 数据库的访问没有任何限制。认证功能作为实验性功能在 etcd 2.1.0 中引入,并允许基于用户名和密码访问特定的密钥集。

认证涉及两个实体:

  • 用户:用户可以通过用户名和密码创建。在启用认证功能之前,必须先创建一个 root 用户。root 用户拥有更多的权限,可以添加用户和角色并分配角色权限。

  • 角色:角色用于限制对特定密钥或包含多个密钥的目录的访问。角色分配给用户,密钥的操作可以根据用户名进行。

要开始使用认证,我们需要先创建一个 root 用户,然后启用认证功能。

首先创建一个 root 用户,如下图所示:

按照以下方式启用认证:

以下示例演示了 etcd 认证。

让我们创建一个示例密钥集、用户和角色:

  1. 创建 /dir1/key1/dir2/key2 密钥。

  2. 创建一个仅能访问 /dir1/*role_dir1 角色。

  3. 创建一个仅能访问 /dir2/*role_dir2 角色。

  4. 创建 user1 并授予 role_dir1 角色。

  5. 创建 user2 并授予 role_dir2 角色。

此时,user1 只能访问 /dir1/*user2 只能访问 /dir2/*

以下是步骤的详细说明:

创建/dir1/key1/dir2/key2密钥:

创建一个仅能访问/dir1/*role_dir1角色:

创建一个仅能访问/dir2/*role_dir2角色:

创建user1并授予role_dir1角色:

创建user2并授予role_dir2角色:

现在,我们可以验证user1仅能访问/dir1/key1。如以下截图所示,user1无法访问/dir2/key1

类似地,user2仅能访问/dir2/key1

Etcd 调试

可以使用以下命令检查 Etcd 的日志文件:

Journalctl –u etcd2.service

默认日志级别设置为INFO。要进行更详细的日志记录,我们可以在环境文件中设置ETCD_DEBUG=1,或者使用-debug命令行选项。

有时,检查与 etcdctl CLI 命令关联的 curl 命令是很有用的。可以使用--debug选项实现这一点。以下是一个示例:

Systemd

第一章《CoreOS 概述》中提供了 Systemd 的概述。Systemd 是 CoreOS 使用的初始化系统,默认始终开启。在本节中,我们将深入了解一些 Systemd 的内部机制。

单元类型

单元描述了特定的任务及其依赖项和执行顺序。一些单元默认在 CoreOS 系统中启动。CoreOS 用户也可以启动自己的单元。系统启动的单元位于/usr/lib64/systemd/system,用户启动的单元位于/etc/systemd/system

以下是一些常见的单元类型:

  • 服务单元:用于启动特定的守护进程或进程。例如sshd.servicedocker.servicesshd.service单元启动 SSH 服务,docker.service启动 docker 守护进程。

  • 套接字单元:用于本地 IPC 或网络通信。例如systemd-journald.socketdocker.socket。每个套接字都有一个对应的服务来管理该套接字。例如,docker.service管理docker.socket。在docker.service中,docker.socket作为依赖项被提到。Docker.socket提供与 docker 引擎的远程连接。

  • 目标单元:主要用于将相关单元分组,以便它们可以一起启动。所有用户创建的服务都位于multi-user.target中。

  • 挂载单元:用于将磁盘挂载到文件系统。例如tmp.mountusr-share-oem.mount。以下是usr-share-oem.mount的相关部分,用于挂载/usr/share/oem

  • 定时器单元:这些单元会根据指定的时间间隔定期启动。例如update-engine-stub.timerlogrotate.timer。以下是update-engine-stub.timer的相关部分,其中每41 分钟调用一次update-engine-stub.service来检查 CoreOS 更新:

单元说明符

在编写 systemd 单元时,访问系统环境变量(如主机名、用户名、IP 地址等)非常有用,这样可以避免硬编码,并在不同系统间使用相同的 systemd 单元。为此,systemd 为您提供了单元说明符,这些是访问系统环境的快捷方式。

以下是一些常见的单元说明符:

  • %H:主机名

  • %m:机器 ID

  • %u:用户名

单元说明符的完整列表可以在www.freedesktop.org/software/systemd/man/systemd.unit.html#Specifiers找到。

以下服务示例演示了单元说明符的使用。在此示例中,我们在 ExecStartPre 中设置与不同说明符相关的键值对,并将其存储在 etcd 中。在 ExecStartPost 中,我们获取该键值对,并在最后进行清理:

[Unit] Description=My Service [Service] KillMode=none ExecStartPre=/usr/bin/etcdctl set hostname %H ; /usr/bin/etcdctl set machinename %m ; /usr/bin/etcdctl set bootid %b ; /usr/bin/etcdctl set unitname %n ; /usr/bin/etcdctl set username %u ExecStart=/bin/echo hello, set done, will echo and remove ExecStartPost=/usr/bin/etcdctl get hostname ; /usr/bin/etcdctl get machinename ; /usr/bin/etcdctl get bootid ; /usr/bin/etcdctl get unitname ; /usr/bin/etcdctl get username ; ExecStartPost=/usr/bin/etcdctl rm hostname ; /usr/bin/etcdctl rm machinename ; /usr/bin/etcdctl rm bootid ; /usr/bin/etcdctl rm unitname ; /usr/bin/etcdctl rm username ; [Install] WantedBy=multi-user.target

要执行此服务,必须使用 sudo 执行以下所有操作:

  1. /etc/systemd/system中创建unitspec.service文件,并使用前述内容。

  2. 使用systemctl enable unitspec.service启用服务。

  3. 使用systemctl start unitspec.service启动服务。

  4. 如果在此之后修改服务,需要在启动服务之前执行命令systemctl daemon-reload

以下是与服务相关的journalctl日志,我们可以看到键被设置和获取,以及相应的单元说明符值:

journalctl –u unitspec.service

单元模板

Systemd 单元可以作为模板创建,并且可以使用相同的模板单元基于模板的调用实例化多个单元。

模板创建为unitname@.service。模板的调用可以使用unitname@instanceid.service进行。在单元文件中,可以使用%p访问unit name,并使用%i访问instanceid

以下是一个示例模板文件,unitspec@.service

[Unit] Description=My Service [Service] ExecStartPre=/usr/bin/etcdctl set instance%i %i ; /usr/bin/etcdctl set prefix %p ExecStart=/bin/echo Demonstrate systemd template [Install] WantedBy=multi-user.target

要执行此服务,必须使用 sudo 执行以下所有操作:

  1. /etc/systemd/system中创建unitspec@.service文件。

  2. 使用systemctl enable unitspec@.service启用服务。

  3. 使用systemctl start unitspec@1.servicesystemctl start unitspec@2.service启动多个服务。

如果我们查看 etcd 内容,我们可以看到,实例值会根据单元名称中提供的%i参数进行更新,并创建instance1instance2键:

以下示例提供了一个更实际的实例化单元示例。它使用了一个模板 nginx 服务,nginx@.service,其中 web 服务的端口号是动态传递的:

[Unit] Description=Apache web server service After=etcd.service After=docker.service [Service] TimeoutStartSec=0 Restart=always EnvironmentFile=/etc/environment ExecStartPre=-/usr/bin/docker kill nginx%i ExecStartPre=-/usr/bin/docker rm nginx%i ExecStartPre=/usr/bin/docker pull nginx ExecStart=/usr/bin/docker run --name nginx%i -p ${COREOS_PUBLIC_IPV4}:%i:80 nginx ExecStop=/usr/bin/docker stop nginx%i [Install] WantedBy=multi-user.target

上述代码中使用了两个服务选项:

  • TimeoutStartSec:此选项指定启动服务所需的时间。如果服务在此时间内未启动,它将被终止。none参数禁用此选项,适用于下载大容器时。

  • Restart:此选项控制服务的重启性。在这里我们指定了always,以便在服务发生故障时重启该服务。

让我们使用以下命令创建该服务的两个实例:

Sudo systemctl enable nginx@.service``Sudo systemctl start nginx@8080.service``Sudo systemctl start nginx@8081.service

这将创建两个带有 nginx 服务的 docker 容器,第一个容器暴露端口8080,第二个容器暴露端口8081

让我们查看docker ps

让我们查看两个单元的状态。如以下截图所示,这些单元处于活动(运行)状态:

插入单元

插入单元用于在运行时更改系统单元的属性。创建插入单元有四种方法。

默认的 cloud-config 插入单元

cloud-config用户数据中指定的参数将自动配置为插入单元。例如,我们来看一下etcd2.service cloud-config

让我们查看etcd2.service的状态:

正如我们在上面的输出中看到的,默认的插入单元是20-cloudinit.conf

20-cloudinit.conf 将包含在 etcd2 cloud-config 中指定的作为环境变量的参数,如下图所示:

Cloud-config 自定义 drop-in 单元

我们可以将 drop-in 单元作为 cloud-config 的一部分进行指定。以下是 fleet.service drop-in 单元的示例,我们将默认的 Restart 参数从 Always 改为 No

当我们使用这个cloud-config时,norestart.conf drop-in 文件会自动创建,可以从 fleet.service 状态中看到这一点:

这个配置更改将使 fleet.service 无法重启。

运行时 drop-in 单元 – 特定参数

我们可以使用 drop-in 配置文件更改服务的特定属性。以下是 fleet.service 的服务部分,显示了默认参数:

这表示如果服务因某些错误死掉,需要在 10 秒内启动该服务。让我们通过杀死 Fleet 服务来检查重启是否有效。

我们可以通过如下方式杀死服务:

Sudo kill -9 <fleet pid>

以下是日志,显示了 Fleet 服务在 10 秒内重启,这是由于服务配置中指定的 Restartsec

为了证明运行时的 drop-in 配置更改,让我们创建一个配置文件,在其中禁用 Fleet 服务的重启。

/etc/systemd.system/system/fleet.service.d下创建 norestart.conf

现在,让我们重新加载 systemd 配置:

Sudo systemd daemon-reload

让我们检查一下现在 fleet.service 的状态:

我们可以看到,除了 20-cloudinit.conf,我们还拥有一个 norestart.conf drop-in 单元。

现在,如果我们杀死 Fleet 服务,它不会被重启,因为重启选项已通过 restart.conf drop-in 单元禁用。Fleet.service 会保持失败状态,如下图所示:

运行时 drop-in 单元 – 完整服务

通过这种方式,我们可以用自己的服务替换完整的系统服务。让我们通过在 /etc/systemd/system 中创建这个 fleet.service 文件来更改重启选项:

[Unit] Description=fleet daemon After=etcd.service After=etcd2.service Wants=fleet.socket After=fleet.socket [Service] ExecStart=/usr/bin/fleetd Restart=no [Install] WantedBy=multi-user.target

我们可以通过以下方式启动 fleet.service

Sudo systemctl start fleet.service

让我们查看 fleet.service 的状态:

从前面的输出中,我们可以看到 fleet.service 是从 /etc/systemd/system 加载的。

如果我们将这个选项(一个带有完整服务更改的掉入单元)与之前的选项(一个带有特定参数更改的掉入单元)进行比较,前一个选项提供了更改特定参数的灵活性,而不会触及原始设置。这使得在新版本的服务允许额外选项时,更容易处理升级。

网络单元

systemd-networkd 服务用于管理网络。系统配置的网络位于 /usr/lib64/systemd/network,用户配置的网络位于 /etcd/systemd/network。以下是一个示例的 Vagrant 配置的 systemd-network 文件,用于配置 eth1 的 IP 地址:

eth1 相关的 ifconfig 输出显示了 Vagrant 配置的 IP 地址,如以下截图所示:

作为示例,假设我们尝试更改 eth1 的 IP 地址。步骤如下:

  1. 停止 systemd-networkd.service

  2. 刷新 IP 地址。

  3. 设置一个新的 IP 地址。

我们将创建一个服务文件来刷新 eth1 的 IP 地址,并创建另一个网络文件来为 eth1 指定新的 IP 地址。

创建一个服务文件以刷新 eth1 的 IP 地址,如下所示。我们需要将其放置在 /etc/systemd/system/down-eth1.service 中。

[Unit] Description=eth1 flush [Service] Type=oneshot ExecStart=/usr/bin/ip link set eth1 down ExecStart=/usr/bin/ip addr flush dev eth1 [Install] WantedBy=multi-user.target

以下是指定 eth1 新地址的网络文件。我们需要将其放置在 /etc/systemd/network/40-eth1.network 中:

[Match] Name=eth1 [Network] Address=172.17.8.110/24 Gateway=172.17.8.1

更改 IP 地址的步骤如下:

  1. 通过 sudo systemctl stop systemd-networkd.service 停止 systemd-networkd 服务。

  2. 通过 sudo systemctl start down-eth1.service 来刷新 eth1 的 IP 地址。

  3. 通过 sudo systemctl start systemd-networkd.service 启动 systemd-networkd.service

如果我们现在查看 ifconfig 输出,应该能看到新的 IP 地址 172.17.8.110

Fleet

Fleet 是一个集群管理器/调度器,控制 CoreOS 集群级别的服务创建。我们可以将 Fleet 看作是集群的 Systemd。有关 Fleet 的概述,请参考 第一章,CoreOS 概述。Fleet 主要用于关键系统服务的编排,而其他编排解决方案如 Kubernetes 主要用于应用服务编排。Fleet 当前不再活跃开发,处于维护模式。

安装

Fleet 在 CoreOS 中默认安装并启动。以下是 CoreOS 稳定版 766.3.0 发布中的 Fleet 版本:

Fleet 也可以安装在独立的 Linux 机器上。Fleet 的发布版本可以在 github.com/coreos/fleet/releases 找到。

访问 Fleet

以下是访问 Fleet 的不同方式。

本地 fleetctl

fleetctl 命令在每个 CoreOS 节点中都存在,可以用于控制 Fleet 服务。

远程 fleetctl

fleetctl 命令可以通过指定端点参数来访问非本地的 CoreOS 节点。以下是示例:

Fleetctl --endpoint=http://172.17.8.101:2379 list-machines

远程 fleetctl 与 SSH 隧道

之前的示例没有使用任何认证。为了确保 fleetctl 访问的安全性,我们可以使用 SSH 认证方案。在这种模式下,必须将 CoreOS 节点的私钥添加到本地 SSH 认证代理。对于 Vagrant CoreOS 集群,私钥存储在 ~/.vagrant.d/insecure_private_key 中。对于 AWS CoreOS 集群,私钥可以在初始密钥创建时下载。

要将私钥添加到认证代理:

`` eval ssh-agent -s ```ssh-add `

现在,我们可以使用 fleetctl 通过安全的 SSH 访问 CoreOS 集群:

Fleetctl --tunnel=http://172.17.8.101 list-unit-files

远程 HTTP

默认情况下,远程 HTTP Fleet API 访问被禁用。

要启用远程访问,创建 .socket 文件以暴露 Fleet API 端口。以下是示例 Fleet 配置文件,用于暴露端口 49153 供外部 API 访问:

在创建远程 API 配置文件后,必须重启 systemd、fleet.socketfleet.service,以使其生效:

Sudo systemctl daemon-reload Sudo systemctl restart fleet.socket Sudo systemctl restart fleet.service

现在,我们可以访问远程 API。以下是使用 fleetctlcurl 的示例:

Fleetctl --endpoint=http://172.17.8.101:49153 list-units

以下输出展示了使用 Fleet HTTP API 的单元列表。以下 curl 输出已被截断,显示部分输出:

Curl –s http://172.17.8.101:49153/fleet/v1/units | jq .

使用 etcd 安全性

我们还可以使用安全的 etcd 方法来访问 Fleet。设置安全的 etcd 内容在 Etcd 安全部分中有说明。以下示例显示了带有服务器证书的 fleetctl 命令:

fleetctl --debug --ca-file ca.crt --endpoint=https://172.17.8.101:2379 list-machines

模板、调度和高可用性

Fleet 支持类似 systemd 的单元规范符和模板。单元规范符为服务文件提供快捷方式,而模板提供可重用的服务文件。前面关于 systemd 的部分详细介绍了单元规范符和模板。第一章,CoreOS 概述介绍了 Fleet 调度和高可用性的基础知识。

可以在 cloud-config 的 Fleet 部分指定节点的 Fleet 元数据。以下示例将 role 设置为 web 的 Fleet 节点元数据。元数据可用于 Fleet 服务文件中控制调度:

metadata: "role=services"

Fleet 使用非常简单的调度算法,X-fleet 选项用于在调度服务时指定约束条件。以下是可用的 X-fleet 选项:

  • MachineMetaData:服务根据匹配的元数据进行调度。

  • MachineId:服务根据指定的MachineId进行调度。

  • MachineOf:服务根据在同一节点上运行的其他服务进行调度。这可以用于将紧密耦合的服务调度到同一节点。

  • Conflict:此选项可用于避免将冲突的服务调度到同一节点。

  • Global:相同的服务会在集群的所有节点上进行调度。

以下示例使用单元标识符和模板,展示了 Fleet 调度和高可用性的实现。以下是该应用的一些细节:

  • 一个应用由 WordPress 容器和 MySQL 容器组成

  • WordPress 容器使用来自 MySQL 容器的数据库,并通过 Docker 容器链接进行连接

  • 容器之间的链接是通过--link选项完成的,并且只有当两个容器在同一主机上时才能工作

  • Fleet 的模板功能将用于通过单一的 WordPress 和 MySQL 模板启动多个服务,Fleet 的 X-fleet 约束功能将用于在同一主机上启动相关的容器

  • 当集群中的一个节点宕机时,Fleet 的 HA 机制会负责重新调度失败的单元,我们将在这个例子中看到它的工作原理

MySQL 模板服务如下:

[Unit] Description=app-mysql [Service] Restart=always RestartSec=5 ExecStartPre=-/usr/bin/docker kill mysql%i ExecStartPre=-/usr/bin/docker rm mysql%i ExecStartPre=/usr/bin/docker pull mysql ExecStart=/usr/bin/docker run --name mysql%i -e MYSQL_ROOT_PASSWORD=mysql mysql ExecStop=/usr/bin/docker stop mysql%i

WordPress 模板服务如下:

[Unit] Description=wordpress [Service] Restart=always RestartSec=15 ExecStartPre=-/usr/bin/docker kill wordpress%i ExecStartPre=-/usr/bin/docker rm wordpress%i ExecStartPre=/usr/bin/docker pull wordpress ExecStart=/usr/bin/docker run --name wordpress%i --link mysql%i:mysql wordpress ExecStop=/usr/bin/docker stop wordpress%i [X-Fleet] MachineOf=mysql@%i.service

以下是关于服务的一些说明:

  • 我们使用了%i作为实例标识符

  • WordPress 具有 X-fleet 约束,用于在同一节点调度对应的 MySQL 容器

第一步是提交服务:

下一步是加载每个服务实例:

让我们检查一下所有服务是否正在运行。从以下截图可以看到,我们有三个 WordPress 应用实例和相关的 MySQL 数据库:

为了演示高可用性(HA),我们将关闭 CoreOS 的node2,可以通过关闭该节点来实现。

如我们所见,当前集群中只有两个节点:

从以下新的服务输出中,我们可以看到,原先运行在node2上的服务已经迁移到了node3,因为node2不可用:

调试

可以使用fleetctl status检查 Fleet 服务的状态。以下是一个示例:

使用fleetctl journal可以检查 Fleet 服务的日志。以下是一个示例:

为了进行调试并获取与fleetctl命令对应的 REST API,我们可以使用--debug选项,如下所示:

服务发现

微服务是动态的,服务需要动态发现其他服务以查找 IP 地址、端口号以及有关服务的元数据。服务发现有多种方案,在本节中,我们将介绍几种使用 etcd 和 Fleet 进行服务发现的方案。在本书的后续章节中,我们将介绍更高级的服务发现选项。

简单的基于 etcd 的服务发现

下图展示了最简单的服务发现机制,其中一个服务将与服务相关的详细信息更新到 etcd,其他服务可以从 etcd 中访问这些信息:

以下是一个示例 Apache 服务,apacheupdateetcd@.service,它会在服务启动时更新 etcd 中的主机名和端口号:

[Unit] Description=My Advanced Service After=etcd2.service After=docker.service [Service] TimeoutStartSec=0 ExecStartPre=-/usr/bin/docker kill apache%i ExecStartPre=-/usr/bin/docker rm apache%i ExecStartPre=/usr/bin/docker pull coreos/apache ExecStart=/usr/bin/docker run --name apache%i -p %i:80 coreos/apache /usr/sbin/apache2ctl -D FOREGROUND ExecStartPost=/usr/bin/etcdctl set /domains/example.com/%H:%i running ExecStop=/usr/bin/docker stop apache%i ExecStopPost=/usr/bin/etcdctl rm /domains/example.com/%H:%i [X-Fleet] # Don't schedule on the same machine as other Apache instances X-Conflicts=apache*@*.service

让我们启动服务并创建两个实例:

现在,我们可以验证 etcd 是否已更新了两个服务的详细信息:

Sidekick 服务发现

在之前的方案中,无法知道服务启动后是否还在运行。下图展示了一个稍微高级一点的服务发现方案,其中一个 sidekick 服务会将服务的详细信息更新到 etcd 中:

Sidekick 容器的目的是监控主服务,并且只有当主服务处于活动状态时,才会更新 etcd。重要的是,Sidekick 容器需要与其监控的主服务运行在同一节点上。

以下是一个使用 Apache 服务的 Sidekick 示例以及为 Apache 服务配置的 sidekick。

以下是Apache.service单元文件:

[Unit] 描述=Apache Web 服务器服务,在端口 %i 上 # 需求 需要=etcd2.service 需要=docker.service 需要=apachet-discovery@%i.service # 依赖关系排序 在 etcd2.service 之后 在 docker.service 之后 在 apachet-discovery@%i.service 之前 [Service] # 让进程启动稍微延迟(用于首次运行的 Docker 容器) TimeoutStartSec=0 # 将 killmode 从"control-group"更改为"none",以使 Docker 正确移除 KillMode=none # 获取 CoreOS 环境变量 环境文件=/etc/environment # 启动前和启动 ## 带有"=-"的指令允许失败而不产生后果 ExecStartPre=-/usr/bin/docker kill apachet.%i ExecStartPre=-/usr/bin/docker rm apachet.%i ExecStartPre=/usr/bin/docker pull coreos/apache ExecStart=/usr/bin/docker run --name apachet.%i -p ${COREOS_PUBLIC_IPV4}:%i:80 coreos/apache /usr/sbin/apache2ctl -D FOREGROUND # 停止 ExecStop=/usr/bin/docker stop apachet.%i

以下是 Apache 侧 kick 服务单元文件:

[Unit] 描述=Apache Sidekick # 需求 需要=etcd2.service 需要=apachet@%i.service # 依赖关系排序和绑定 在 etcd2.service 之后 在 apachet@%i.service 之后 绑定到 apachet@%i.service [Service] # 获取 CoreOS 环境变量 环境文件=/etc/environment # 启动 ## 测试服务是否可访问,然后注册有用的信息 ExecStart=/bin/bash -c '\   while true; do \     curl -f ${COREOS_PUBLIC_IPV4}:%i; \     if [ $? -eq 0 ]; then \       etcdctl set /services/apachet/${COREOS_PUBLIC_IPV4} \'{"host": "%H", "ipv4_addr": ${COREOS_PUBLIC_IPV4}, "port": %i}\' --ttl 30; \     else \       etcdctl rm /services/apachet/${COREOS_PUBLIC_IPV4}; \     fi; \     sleep 20; \   done' # 停止 ExecStop=/usr/bin/etcdctl rm /services/apachet/${COREOS_PUBLIC_IPV4} [X-Fleet] # 在与相关的 Apache 服务相同的机器上调度 X-ConditionMachineOf=apachet@%i.service

上述 Sidekick 容器服务定期 ping 主服务并更新 etcd 输出。如果主服务无法访问,相关服务的详细信息将从 etcd 中删除。

让我们启动两个服务实例:

让我们查看 etcd 输出。从以下截图中可以看到,etcd 反映了 Apache 运行的两个节点:

让我们查看 node1 中的 docker 输出:

为了演示侧 kick 服务,让我们停止 docker 容器并检查侧 kick 服务是否更新 etcd,以删除相应的服务:

让我们检查一下单元的状态。从下面可以看到,apachet@2.service 已经失败,相关的侧 kick 服务 apachet-discovery@2.service 处于非活动状态。

从以下输出可以看到,apachet@2.service 的详细信息已经从 etcd 中删除:

ELB 服务发现

这是 Sidekick 发现的一个变体,在这种变体中,Sidekick 更新 IP 地址到负载均衡器,负载均衡器将 Web 查询重定向到活动节点。在本例中,我们将使用 AWS 弹性负载均衡器和 Quay 仓库中提供的 CoreOS elb-presence 容器。elb-presence 容器负责检查 nginx 容器的健康状态,并更新 AWS ELB 以使用容器的 IP 地址。

下图展示了这种方法的高级架构:

第一步是在 AWS 中创建 ELB,如下截图所示。这里我们使用 AWS CLI 创建了 ELB,名为 testlb

我们需要在 Sidekick 服务中使用之前截图创建的 testlb

以下是 nginx.service 单元文件:

[Unit] Description=nginx [Service] ExecStartPre=-/usr/bin/docker kill nginx-%i ExecStartPre=-/usr/bin/docker rm nginx-%i ExecStart=/usr/bin/docker run --rm --name nginx-%i -p 80:80 nginx ExecStop=/usr/bin/docker stop nginx-%i [X-Fleet] Conflicts=nginx@*.service

以下是更新 AWS ELB 基于 nginx.service 健康状态的 nginx Sidekick 服务:

[Unit] Description=nginx presence service BindsTo=nginx@%i.service [Service] ExecStartPre=-/usr/bin/docker kill nginx-presence-%i ExecStartPre=-/usr/bin/docker rm nginx-presence-%i ExecStart=/usr/bin/docker run --rm --name nginx-presence-%i -e AWS_ACCESS_KEY=<key> -e AWS_SECRET_KEY=<secretkey> -e AWS_REGION=us-west-2 -e ELB_NAME=testlb quay.io/coreos/elb-presence ExecStop=/usr/bin/docker stop nginx-presence-%i [X-Fleet] MachineOf=nginx@%i.service

创建两个服务实例后,以下是 Fleet 的状态:

正如我们在下面的截图中看到的,AWS ELB 已经注册了这两个实例,并且将在这两个实例之间进行负载均衡:

在这一点上,如果停止 nginx 服务的任何实例,Sidekick 服务将负责从 ELB 中删除此实例。

摘要

在本章中,我们详细介绍了 Etcd、Systemd 和 Fleet 的内部结构,并通过充分的实际示例让您能够熟悉配置和使用这些服务。通过开源关键服务的开发,CoreOS 鼓励在 CoreOS 之外使用这些服务。我们还介绍了使用 Etcd、Systemd 和 Fleet 的基本服务发现选项。在下一章中,我们将介绍容器网络和 Flannel。

参考文献

进一步阅读和教程

第六章

第五章 CoreOS 网络和 Flannel 内部结构

微服务增加了对大量容器以及容器跨主机互联的需求。为了实现这一目标,必须有一个健壮的容器网络方案。本章将介绍容器网络的基础知识,重点讨论 CoreOS 如何通过 Flannel 实现容器网络。还将涵盖 Docker 网络和其他相关的容器网络技术。本章将包括以下主题:

  • 容器网络基础

  • Flannel 内部结构

  • 使用 Vagrant、AWS 和 GCE 搭建的 CoreOS Flannel 集群

  • Docker 网络和实验性 Docker 网络

  • 使用 Weave 和 Calico 进行 Docker 网络连接

  • Kubernetes 网络

容器网络基础

以下是我们需要容器网络的原因:

  • 容器需要与外部世界进行通信。

  • 容器应该可以从外部世界访问,以便外部世界可以使用容器提供的服务。

  • 容器需要与主机进行通信。例如,可以共享卷。

  • 同一主机和不同主机之间应具有容器互联。例如,一个主机中的 WordPress 容器与另一个主机中的 MySQL 容器进行通信。

目前有多种解决方案可以实现容器之间的互联。这些解决方案相对较新,且正在积极开发中。在 Docker 1.8 版本之前,Docker 并没有提供原生的跨主机容器互联方案。Docker 1.9 版本引入了一种基于 Libnetwork 的方案,用于实现跨主机容器互联以及服务发现。CoreOS 在 CoreOS 集群中使用 Flannel 进行容器网络连接。还有一些项目,如 Weave 和 Calico,正在开发容器网络解决方案,计划成为任何容器运行时(如 Docker 或 Rkt)的网络容器插件。

Flannel

Flannel 是一个开源项目,提供 CoreOS 集群的容器网络解决方案。Flannel 也可以用于非 CoreOS 集群。Kubernetes 使用 Flannel 设置 Kubernetes pod 之间的网络连接。Flannel 为每个运行容器的主机分配一个独立的子网,并从该主机的子网中为容器分配一个独立的 IP 地址。在每个主机之间建立一个覆盖网络,使得不同主机上的容器可以互相通信。在第一章 CoreOS 概述中,我们提供了 Flannel 控制路径和数据路径的概述。本节将深入探讨 Flannel 的内部结构。

手动安装

Flannel 可以通过手动安装或使用 systemd 单元 flanneld.service 来安装。以下命令将在 CoreOS 节点上使用容器构建 Flannel 二进制文件。执行完以下命令后,Flannel 的 flanneld 二进制文件将在 /home/core/flannel/bin 中可用:

git clone https://github.com/coreos/flannel.git``docker run -v /home/core/flannel:/opt/flannel -i -t google/golang /bin/bash -c "cd /opt/flannel && ./build"

以下是我们在 CoreOS 节点中构建 flannel 后获得的 Flannel 版本:

使用 flanneld.service 进行安装

Flannel 在 CoreOS 中默认没有安装。这样做是为了保持 CoreOS 镜像的最小化。Docker 需要 flannel 来配置网络,而 flannel 需要 Docker 来下载 flannel 容器。为了避免这种“先有鸡还是先有蛋”的问题,CoreOS 中默认启动了early-docker.service,其主要目的是下载并启动 flannel 容器。一个常规的docker.service则启动 Docker 守护进程并使用 flannel 网络。

下图展示了flanneld.service中的序列,其中早期的 Docker 守护进程启动了 flannel 容器,进而启动了docker.service,并使用 flannel 创建的子网:

以下是flanneld.service的相关部分,它从 Quay 仓库下载 flannel 容器:

以下输出显示了早期 docker 运行的容器。Early-docker 只管理 Flannel:

以下是flanneld.service的相关部分,它更新了 Docker 选项以使用 flannel 创建的子网:

以下是flannel_docker_opts.env的内容——在我的案例中——flannel 启动后的内容。地址10.1.60.1/24是这个 CoreOS 节点为其容器选择的:

Docker 将作为docker.service的一部分启动,如下图所示,并使用上述环境文件:

控制路径

Flannel 中没有中央控制器,它使用 etcd 进行节点间的通信。CoreOS 集群中的每个节点都运行一个 flannel 代理,它们通过 etcd 进行相互通信。

作为启动 Flannel 服务的一部分,我们指定了可以由网络中各个节点使用的 Flannel 子网。这个子网注册在 etcd 中,确保集群中的每个 CoreOS 节点都能看到它。网络中的每个节点选择一个特定的子网范围,并通过 etcd 原子地进行注册。

以下是cloud-config的相关部分,它启动了flanneld.service并指定了 Flannel 的配置。在这里,我们指定了用于 flannel 的子网为10.1.0.0/16,并将封装类型指定为vxlan

上述配置将在节点中创建以下 etcd 键。它显示了10.1.0.0/16已分配给 flannel,供 CoreOS 集群使用,并且封装类型为vxlan

一旦每个节点获取了子网,该节点中启动的容器将从分配给该节点的 IP 地址池中获取 IP 地址。以下是每个节点的 etcd 子网分配情况。如我们所见,所有子网都位于之前与 etcd 配置的10.1.0.0/16范围内,且使用了 24 位掩码。每个主机的子网长度也可以作为 Flannel 配置选项进行控制:

我们来看一下在此节点中创建的 Flannel 接口的ifconfig。该 IP 地址属于10.1.0.0/16地址范围:

数据路径

Flannel 使用 Linux 桥接来封装使用 Flannel 配置中指定的覆盖协议的数据包。这使得同一主机内的容器之间以及跨主机的容器之间能够实现连通性。

以下是当前 Flannel 支持的主要后端,并在 JSON 配置文件中指定。JSON 配置文件可以在cloud-config的 Flannel 部分中指定:

  • UDP:在 UDP 封装中,来自容器的数据包会被封装到 UDP 中,默认端口号为8285。如果需要,我们可以更改端口号。

  • VXLAN:从封装开销的角度来看,VXLAN 相对于 UDP 更高效。默认情况下,端口8472用于 VXLAN 封装。如果我们希望使用 IANA 分配的 VXLAN 端口,则需要将端口字段指定为4789

  • AWS-VPC:适用于在 AWS VPC 云中使用 Flannel 的情况。与使用覆盖网络封装数据包不同,这种方法通过使用 VPC 路由表来跨容器进行通信。AWS 限制每个 VPC 路由表条目的数量为 50,因此在较大的集群中可能会出现问题。

以下是指定 Flannel 配置中 AWS 类型的示例:

  • GCE:适用于在 GCE 云中使用 Flannel 的情况。与使用覆盖网络封装数据包不同,这种方法通过使用 GCE 路由表来跨容器进行通信。GCE 限制每个 VPC 路由表条目的数量为100,因此在较大的集群中可能会出现问题。

以下是指定 Flannel 配置中 GCE 类型的示例:

我们在两台不同主机中创建容器,并使用 VXLAN 封装,检查连通性是否正常。以下示例使用启用了 Flannel 服务的 Vagrant CoreOS 集群。

Host1 中的配置:

我们来启动一个busybox容器:

我们来检查分配给容器的 IP 地址。该 IP 地址来自 Flannel 代理为此 CoreOS 节点分配的 IP 池。10.1.19.0/24分配给了host1,这个容器获得了10.1.19.2地址:

Host2 中的配置:

我们来启动一个busybox容器:

让我们检查一下分配给该容器的 IP 地址。这个 IP 地址来自 Flannel 代理为此 CoreOS 节点分配的 IP 池。10.1.1.0/24被分配给了host2,而这个容器获得了10.1.1.2的地址:

以下输出显示容器 1 和容器 2 之间的 ping 操作成功。这个 ping 包通过两个 CoreOS 节点传输,并使用 VXLAN 进行封装:

Flannel 作为 CNI 插件

如第一章《CoreOS 概述》所述,APPC 定义了一个容器规范,任何容器运行时都可以使用该规范。对于容器网络,APPC 定义了容器网络接口(CNI)规范。通过 CNI,容器网络功能可以作为插件实现。CNI 期望插件支持带有一组参数的 API,具体实现则交由插件完成。插件实现的 API 包括将容器添加到网络中和从网络中移除容器,且有一个定义好的参数列表。

这允许不同供应商实现网络插件,并且可以在不同的容器运行时之间重用插件。下图显示了 RKT 容器运行时、CNI 层和像 Flannel 这样的插件之间的关系。IPAM 插件用于为容器分配 IP 地址,这个功能被嵌套在初始网络插件中:

使用 Flannel 和 Docker 设置一个三节点 Vagrant CoreOS 集群

以下示例设置了一个三节点的 Vagrant CoreOS 集群,默认启用了etcdfleetflannel服务。在这个示例中,使用vxlan进行封装。以下是用于此配置的cloud-config

#cloud-config coreos:   etcd2:     discovery: <update this>     advertise-client-urls: http://$public_ipv4:2379     initial-advertise-peer-urls: http://$private_ipv4:2380     listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001     listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001   fleet:     public-ip: $public_ipv4   flannel:     interface: $public_ipv4   units:     - name: etcd2.service       command: start     - name: fleet.service       command: start     - name: flanneld.service       drop-ins:         - name: 50-network-config.conf           content: |             [Service]             ExecStartPre=/usr/bin/etcdctl set /coreos.com/network/config '{ "Network": "10.1.0.0/16", "Backend": {"Type": "vxlan"}}'       command: start

以下是此操作的步骤:

  1. 克隆 CoreOS Vagrant 仓库。

  2. config.rb中将实例数量更改为三个。

  3. cloud-config用户数据中更新发现令牌。

  4. 执行vagrant up来启动集群。

有关步骤的更多详细信息,请参考第二章,设置 CoreOS 实验室。我们可以通过在两个主机中启动busybox容器并检查容器间 ping 是否正常来测试容器的连接性。

使用 Flannel 和 RKT 设置三节点 CoreOS 集群

在这里,我们将使用 Flannel CNI 网络插件设置一个包含 RKT 容器的三节点 CoreOS 集群,以设置网络。这将允许跨主机的 RKT 容器彼此通信。

以下是使用的cloud-config

#cloud-config coreos:   etcd2:     discovery: <update token>     advertise-client-urls: http://$public_ipv4:2379     initial-advertise-peer-urls: http://$private_ipv4:2380     listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001     listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001   fleet:     public-ip: $public_ipv4   flannel:     interface: $public_ipv4   units:     - name: etcd2.service       command: start     - name: fleet.service       command: start     - name: flanneld.service       drop-ins:         - name: 50-network-config.conf           content: |             [Service]             ExecStartPre=/usr/bin/etcdctl set /coreos.com/network/config '{ "network": "10.1.0.0/16" }'       command: start # Rkt 配置 write_files:   - path: "/etc/rkt/net.d/10-containernet.conf"     permissions: "0644"     owner: "root"     content: |       {         "name": "containernet",         "type": "flannel"       }

/etc/rkt/net.d/10-containernet.conf文件将 CNI 插件类型设置为 Flannel,RKT 容器使用此插件。

以下是具体步骤:

  1. 克隆 CoreOS Vagrant 仓库。

  2. config.rb中将实例数更改为三。

  3. 更新cloud-config用户数据中的发现令牌。

  4. 执行vagrant up以启动集群。

让我们在node1中启动一个busybox容器:

busybox node1中的ifconfig输出如下:

node2中启动一个busybox容器:

busybox node2中的ifconfig输出如下:

以下截图显示了跨容器的成功 ping 输出:

注意

注意:不应在 RKT 容器中启动Docker.service,因为 Docker 桥接使用的地址与 Flannel 分配给 Docker 容器通信的地址相同。目前正在积极研究支持在 Flannel 上同时运行 Docker 和 RKT 容器。有关此主题的讨论,可以参考groups.google.com/forum/#!topic/coreos-user/Kl7ejtcRxbc

使用 Flannel 的 AWS 集群

Flannel 可以用来在 AWS 云中的 CoreOS 节点之间提供容器网络连接。在以下两个示例中,我们将使用 Flannel 创建一个三节点 CoreOS 集群,分别使用 VXLAN 和 AWS VPC 网络。此示例基于coreos.com/blog/introducing-flannel-0.5.0-with-aws-and-gce/中描述的过程。

使用 VXLAN 网络的 AWS 集群

以下是实现此目标的前提条件:

  1. 从发现令牌服务为三节点集群创建一个令牌。

  2. 设置一个安全组,暴露sshicmp237923808472端口。8472用于 VXLAN 封装。

  3. 根据您的 AWS 区域,使用此链接(coreos.com/os/docs/latest/booting-on-ec2.html)来确定 AMI 镜像 ID,并根据您的 AWS 区域和更新频道更新该链接。在以下示例中,我们将使用 ami-150c1425,它是最新的 815 alpha 镜像。

创建cloud-config-flannel-vxlan.yaml,其内容与前一节中为 Vagrant CoreOS 集群与 Flannel 和 Docker 所使用的内容相同。

使用以下 AWS CLI 启动三节点集群:

aws ec2 run-instances --image-id ami-85ada4b5 --count 3 --instance-type t2.micro --key-name "yourkey" --security-groups "coreos" --user-data

我们可以使用两个busybox容器在两个 CoreOS 节点上测试容器之间的连接性,如前面部分所述。

使用 AWS-VPC 的 AWS 集群

AWS VPC 为您提供了为在 AWS 中创建的实例创建自定义网络的选项。通过 AWS VPC,我们可以创建子网和路由表,并为实例配置自定义 IP 地址。

Flannel 支持aws-vpc封装类型。在使用此选项时,Flannel 会更新 VPC 路由表,通过为每个 VPC 创建一个基于分配给各个节点的容器 IP 地址的自定义路由表,在实例之间进行路由。从数据路径角度来看,不使用像 UDP 或 VXLAN 这样的封装方式。相反,AWS VPC 会根据 Flannel 配置的路由表,负责将数据包路由到适当的实例。

以下是创建集群的步骤:

  1. 为三节点集群创建一个发现令牌。

  2. 设置一个安全组,暴露sshicmp23792380端口。

  3. 使用此链接来确定 AMI 镜像 ID(coreos.com/os/docs/latest/booting-on-ec2.html)。在以下示例中,我们将使用ami-150c1425,它是最新的 815 alpha 镜像。

  4. 使用 VPC 向导创建一个 VPC,带有一个公共子网。以下图表显示了通过 AWS 控制台创建的 VPC:

  5. 从 AWS 控制台创建一个 IAM 策略demo-policy。此策略允许实例修改路由表:

    {     "Version": "2012-10-17",     "Statement": [     {             "Effect": "Allow",             "Action": [                 "ec2:CreateRoute",                 "ec2:DeleteRoute",                 "ec2:ReplaceRoute"             ],             "Resource": [                 "*"             ]     },     {             "Effect": "Allow",             "Action": [                 "ec2:DescribeRouteTables",                 "ec2:DescribeInstances"             ],             "Resource": "*"     }     ] }

  6. 创建一个 IAM 角色 demo-role,并将前面代码中创建的 demo-policy 与该角色关联。

  7. 创建 cloud-config-flannel-aws.yaml 文件,内容如下。我们将使用类型 aws-vpc,如下面的代码所示:

    Cloud-config-flannel-aws.yaml: #cloud-config coreos:   etcd2:     discovery: <your token>     advertise-client-urls: http://$private_ipv4:2379,http://$private_ipv4:4001     initial-advertise-peer-urls: http://$private_ipv4:2380     listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001     listen-peer-urls: http://$private_ipv4:2380   units:     - name: etcd2.service       command: start     - name: fleet.service       command: start     - name: flanneld.service       drop-ins:         - name: 50-network-config.conf           content: |             [Service]             ExecStartPre=/usr/bin/etcdctl set /coreos.com/network/config '{ "Network": "10.1.0.0/16" , "Backend": {"Type": "aws-vpc"}}'       command: start

创建一个三节点的 CoreOS 集群,并且配置一个安全组、IAM 角色、vpcid/subnetid安全组cloud-config 文件,如下所示:

aws ec2 run-instances --image-id ami-150c1425 --subnet subnet-a58fc5c0 --associate-public-ip-address --iam-instance-profile Name=demo-role --count 3 --security-group-ids sg-f22cb296 --instance-type t2.micro --key-name "smakam-oregon" --user-data file://cloud-config-flannel-aws.yaml

注意

注意:为了允许来自容器的流量(因为容器的 IP 地址由 Flannel 分配,而不是 AWS 分配),需要禁用源和目标检查。为此,您需要进入 AWS 控制台中的每个实例,选择网络 | 更改源/目标检查 | 禁用。

查看其中一个 CoreOS 节点上的 etcdctl 输出,我们可以看到分配给三节点集群中每个节点的子网。

Flannel 会自动更新 VPC 路由表,根据子网所在的实例 ID 路由前述子网。如果检查 VPC 路由表,可以看到以下路由,它们与 Flannel 创建的网络相匹配:

此时,我们可以使用前面章节中指定的两个 CoreOS 节点中的两个 busybox 容器来测试容器间的连接性。

使用 Flannel 的 GCE 集群

Flannel 可用于在 GCE 云中的 CoreOS 节点之间提供容器网络。在以下两个示例中,我们将使用 Flannel 和 VXLAN,以及 Flannel 和 GCE 网络创建一个三节点 CoreOS 集群。这些示例基于 coreos.com/blog/introducing-flannel-0.5.0-with-aws-and-gce/ 中描述的过程。

使用 VXLAN 网络的 GCE 集群

以下是此操作的前提条件:

  1. 从发现令牌服务创建三节点集群的令牌。

  2. 设置一个安全组,暴露 sshicmp237923808472 端口。8472 用于 VXLAN 封装。

  3. 使用以下链接来确定 AMI 镜像 ID (coreos.com/os/docs/latest/booting-on-google-compute-engine.html)。我们将在以下示例中使用 alpha 镜像 815。

创建 cloud-config-flannel-vxlan.yaml 文件,内容与前一部分中指定的 Vagrant CoreOS 集群的 Flannel 和 Docker 配置相同。

以下命令可用于在 GCE 中设置一个包含 Flannel 和 VXLAN 封装的三节点 CoreOS 集群:

gcloud compute instances create core1 core2 core3 --image https://www.googleapis.com/compute/v1/projects/coreos-cloud/global/images/coreos-alpha-815-0-0-v20150924 --zone us-central1-a --machine-type n1-standard-1 --tags coreos --metadata-from-file user-data=cloud-config-flannel-vxlan.yaml

可以通过在不同主机的容器之间进行 ping 测试来验证 Flannel 的控制和数据路径是否正常工作。

使用 GCE 网络的 GCE 集群

类似于 AWS VPC,Google 云也有其云网络服务,提供创建自定义子网、路由和 IP 地址的能力。

以下是使用 Flannel 和 GCE 网络创建三节点 CoreOS 集群的步骤:

  1. 从发现令牌服务创建三节点集群的令牌。

  2. 创建一个自定义网络 customnet,并设置防火墙规则,允许 TCP 端口 23792380。以下是我创建的自定义网络,子网为 10.10.0.0/16

  3. 创建 cloud-config-flannel-gce.yaml 文件,内容如下。将 Flannel 类型设置为 gce

    #cloud-config coreos: etcd2: discovery: <yourtoken> advertise-client-urls: http://$private_ipv4:2379,http://$private_ipv4:4001 initial-advertise-peer-urls: http://$private_ipv4:2380 listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001 listen-peer-urls: http://$private_ipv4:2380 units: - name: etcd2.service command: start - name: fleet.service command: start - name: flanneld.service drop-ins: - name: 50-network-config.conf content: | [Service] ExecStartPre=/usr/bin/etcdctl set /coreos.com/network/config '{ "Network": "10.1.0.0/16" , "Backend": {"Type": "gce"}}' command: start

  4. 创建三个 CoreOS 实例,使用 customnet 网络,启用 IP 转发,并为实例分配修改路由表的权限:

    gcloud compute instances create core1 core2 core3 --image https://www.googleapis.com/compute/v1/projects/coreos-cloud/global/images/coreos-alpha-815-0-0-v20150924 --zone us-central1-a --machine-type n1-standard-1 --network customnet --can-ip-forward --scopes compute-rw --metadata-from-file user-data=cloud-config-flannel-gce.yaml

以下是每个节点为容器创建的 Flannel 网络:

让我们来看看 GCE 中的路由表。如以下输出所示,Flannel 已经更新了 GCE 的容器网络路由表:

到此为止,我们应该已经能够跨节点连接容器。

实验性多租户网络

默认情况下,Flannel 创建一个单一的网络,所有节点都可以在该网络上相互通信。当有多个租户使用相同的网络时,这会带来安全风险。一种实现多租户网络的方式是使用多个 Flannel 实例来管理每个租户。这可能会变得难以配置。从版本 0.5.3 开始,Flannel 在实验模式下引入了多网络功能,在这种模式下,单个 Flannel 守护进程可以管理具有隔离性的多个网络。当集群中有多个租户时,使用多网络模式有助于隔离每个租户的流量。

以下是执行此操作的步骤:

  1. 为多个租户创建子网配置。可以通过在 etcd 中预留一个子网池来实现。以下示例设置了三个网络:bluegreenred,每个网络都有不同的子网:

    etcdctl set /coreos.com/network/blue/config '{ "Network": "10.1.0.0/16", "Backend": { "Type": "vxlan", "VNI": 1 } }'``etcdctl set /coreos.com/network/green/config '{ "Network": "10.2.0.0/16", "Backend": { "Type": "vxlan", "VNI": 2 } }'``etcdctl set /coreos.com/network/red/config '{ "Network": "10.3.0.0/16", "Backend": { "Type": "vxlan", "VNI": 3 } }'

  2. 启动 Flannel 代理并指定该 Flannel 代理需要加入的网络。这样可以为每个节点每个网络预留 IP 池。在这个例子中,我们已经启动了 flannel 代理,并使其成为所有三个网络(bluegreenred)的一部分:

    sudo flanneld --networks=blue,green,red &

Flannel 为这三个网络选择了三个子网范围,如下图所示。10.1.87.0/24分配给blue网络,10.2.4.0/24分配给green网络,10.3.93.0/24分配给red网络:

/run/flannel下可以看到多个网络,如下所示:

现在,我们可以启动带有适当租户网络的 Docker 或 Rkt 容器。此时,flanneld.service与多网络的自动集成尚未完成;这需要手动完成。

以下链接是关于此话题的相关 Google 讨论:

groups.google.com/forum/#!topic/coreos-user/EIF-yGNWkL4

实验性客户端-服务器网络

在默认的 Flannel 模式下,每个节点都有一个 flannel 代理,并且后端数据保存在etcd中。这样 Flannel 保持无状态。在这种模式下,每个 Flannel 节点都需要运行etcd。Flannel 客户端-服务器模式在以下场景中非常有用:

  • 只有主节点运行etcd,工作节点无需运行etcd。从性能和安全的角度来看,这一点非常有用。

  • 当使用其他后端(如 AWS)与 flannel 一起时,必须存储 AWS 密钥,并且在使用客户端-服务器模型时,密钥只需要在主节点中存在;从安全角度来看,这一点非常重要。

Flannel 客户端-服务器功能目前在 Flannel 版本 0.5.3 中处于实验模式。

下图描述了 Flannel 客户端-服务器网络中的不同组件之间的互联:

如果需要,我们可以使用安全的(HTTPS)通信方式,既可以从 flanneld 服务器到 etcd,也可以在 flanneld 客户端和服务器之间进行通信。

设置客户端-服务器 Flannel 网络

让我们从一个没有在任何节点上运行 Flannel 的三节点 CoreOS 集群开始。在node1上启动flanneld服务器和客户端,在node2node3上启动客户端。

按照以下截图启动 flannel 服务器:

按照以下截图启动 flannel 客户端:

必须指定eth1作为参数接口,因为eth0用作 NAT 接口,并且在所有节点中都相同,而eth1在各节点之间是唯一的:

node2node3中启动客户端后,让我们查看node1etcd的输出,显示了三个 CoreOS 节点获取的三个子网:

要手动启动docker.service,我们首先需要创建flannel_docker_opts.env,如下所示:

/usr/bin/docker run --net=host --rm -v /run:/run \`` quay.io/coreos/flannel:0.5.3 \`` /opt/bin/mk-docker-opts.sh -d /run/flannel_docker_opts.env –i

以下是创建的flannel_docker_opts.env文件:

现在,我们可以启动docker.service,它使用flannel_docker_opts.env中的环境变量。

启动docker.service

sudo systemctl start docker.service

如我们所见,docker 桥接获取了分配给此节点的 IP 地址范围:

此功能目前处于实验阶段。未来计划添加服务器故障转移功能。

Docker 网络

以下是用于在单个主机上互联容器的 Docker 网络模型:

每个容器都位于其自己的网络命名空间中,并通过主机上的 Linux 桥接与其他容器通信。有关 Docker 网络选项的更多详细信息,可以参见 docs.docker.com/engine/userguide/networking/dockernetworks/。以下是 Docker 1.9 版本提供的网络选项:

  • --net=bridge:这是 Docker 提供的默认选项,容器通过 veth 对接到 Linux docker 桥接。

  • --net=host:在此选项中,不会为容器创建新的网络命名空间,容器与主机共享相同的网络命名空间。

  • --net= (容器名称或 ID):在此选项中,新容器与在net选项中指定的容器共享相同的网络命名空间。(例如:sudo docker run -ti –name=ubuntu2 –net=container:ubuntu1 ubuntu:14.04 /bin/bash。这里,ubuntu2容器与ubuntu1容器共享相同的网络命名空间。)

  • --net=none:在此选项中,容器不会分配新的网络命名空间。此时仅会创建回环接口。此选项适用于我们希望为容器创建自定义网络选项或不需要连接的场景。

  • --net=overlay:此选项在 Docker 1.9 版本中添加,用于支持覆盖网络,使得跨主机的容器能够相互通信。

Docker 实验性网络

从 Docker 1.8 版本开始,Docker 没有本地解决方案来连接跨主机的容器。通过 Docker 实验性版本,我们可以使用 Docker 本地解决方案以及外部网络插件来连接跨主机的容器。

以下图示说明此情况:

以下是关于 Docker libnetwork 解决方案的一些说明:

  • 以前,Docker 运行时与网络模块集成,没有办法将它们分离。Libnetwork 是新的网络库,提供网络功能,并与 Core Docker 分离。Docker 1.7 版本已经包含了 libnetwork,并且从终端用户的角度来看是向后兼容的。

  • 驱动程序实现了 libnetwork 提供的 API。Docker 正在倾向于为网络、存储和编排等主要功能采用插件方法,Docker 提供的原生解决方案可以被其他供应商的技术替代,只要它们实现了通用库提供的 API。在这种情况下,Bridge 和 Overlay 是 Docker 的原生网络驱动程序,而远程驱动程序可以由第三方实现。现在已经有很多远程驱动程序可用,如 Weave 和 Calico。

Docker 实验性网络有以下概念:

  • Docker 容器通过端点或服务连接到网络。

  • 多个端点共享同一网络。换句话说,只有位于同一网络中的端点才能相互通信。

  • 在创建网络时,可以指定网络驱动程序。这可以是 Docker 提供的驱动程序,如 Overlay,或者是外部驱动程序,如 Weave 和 Calico。

  • Libnetwork 提供服务发现,容器可以发现同一网络中的其他端点。未来有计划将服务发现做成插件。服务之间可以通过服务名称而非 IP 地址进行通信。目前,Consul 用于服务发现;这可能以后会有所变化。

  • 使用共享存储,如etcdconsul,来确定属于同一集群的节点。

一个多网络用例

随着最新的 Docker 网络增强,容器可以是多个网络的一部分,只有在同一网络中的容器才能相互通信。为了说明这些概念,我们来看以下示例:

  1. 在后端网络be中设置两个 nginx 容器和一个 HAProxy 容器。

  2. 同样将 HAProxy 容器添加到前端网络fe中。

  3. 使用位于前端网络fe中的 busybox 容器连接到 HAProxy 容器。由于 busybox 容器位于fe网络中,而 nginx 容器位于be网络中,因此它们无法直接相互通信。

  4. HAProxy 容器将在两个 nginx 后端容器之间进行 Web 连接的负载均衡。

以下是命令详情:

创建febe网络:

docker network create be docker network create fe

be网络中创建两个 nginx 容器:

docker run --name nginx1 --net be -v ~/haproxy/nginx1.html:/usr/share/nginx/html/index.html -d nginx docker run --name nginx2 --net be -v ~/haproxy/nginx2.html:/usr/share/nginx/html/index.html -d nginx

be网络中创建haproxy

docker run -d --name haproxy --net be -v ~/haproxy/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg haproxy

haproxy 附加到 fe 网络:

docker network connect fe haproxy

fe 网络中创建一个 busybox 容器,访问 haproxy 网页:

docker run -it --rm --net fe busybox wget -qO- haproxy/index.html

如果我们多次尝试运行 busybox 容器,它将在 nginx1nginx2 网页服务器输出之间切换。

Docker 覆盖驱动程序

以下示例展示了如何使用 Docker 的实验性覆盖驱动程序实现多主机容器连接。我在以下示例中使用的是 Ubuntu 虚拟机,而不是 CoreOS,因为实验性的 Docker 覆盖驱动程序需要一个新的内核版本,而该版本在 CoreOS 中尚未提供。

以下图示展示了此示例中正在尝试的用例:

以下是步骤的总结:

  • 创建两台安装了实验性 Docker 的主机。

  • 在两台主机上安装 Consul,其中一台主机充当 consul 服务器。Consul 用于存储用于容器间通信的共享数据。

  • 在两个主机上启动 Docker,并使用 Consul 作为密钥存储机制。

  • 在两个主机上创建具有不同端点的容器,这些容器共享相同的网络。

第一步是创建两台安装了实验性 Docker 的主机。

以下一组命令使用 docker-machine 创建两台 Docker 主机。我们使用了带有自定义 ISO 镜像的 docker-machine 来安装实验性的 Docker:

docker-machine create -d virtualbox --virtualbox-boot2docker-url=http://sirile.github.io/files/boot2docker-1.9.iso dev1``docker-machine create -d virtualbox --virtualbox-boot2docker-url=http://sirile.github.io/files/boot2docker-1.9.iso dev2

在两个节点上安装 Consul。以下命令展示了如何下载并安装 Consul:

curl -OL https://dl.bintray.com/mitchellh/consul/0.5.2_linux_amd64.zip``unzip 0.5.2_linux_amd64.zip``sudo mv consul /usr/local/bin/

node1 上启动 consul 服务器并启动使用 consul 密钥存储的 Docker 守护进程:

以下一组命令在 node1 上启动 consul 服务器并使用 consul 代理启动 Docker 守护进程:

Docker-machine ssh dev1``consul agent -server -bootstrap -data-dir /tmp/consul -bind=192.168.99.100 &``sudo docker -d --kv-store=consul:localhost:8500 --label=com.docker.network.driver.overlay.bind_interface=eth1

node2 上启动 consul 代理和使用 consul 密钥存储的 Docker 守护进程:

以下一组命令启动在 node2 上运行的 consul 代理和 Docker 守护进程:

Docker-machine ssh dev2``consul agent -data-dir /tmp/consul -bind 192.168.99.101 &``consul join 192.168.99.100 &``sudo docker -d --kv-store=consul:localhost:8500 --label=com.docker.network.driver.overlay.bind_interface=eth1 --label=com.docker.network.driver.overlay.neighbor_ip=192.168.99.100

node1 上启动带有 svc1 服务、dev 网络和 overlay 驱动程序的容器:

docker run -i -t --publish-service=svc1.dev.overlay busybox

node2上使用svc2服务、dev网络和overlay驱动启动容器:

docker run -i -t --publish-service=svc2.dev.overlay busybox

如我们所见,我们能够成功从node1ping 到svc1svc2

注意

注意:overlay 驱动需要 Linux 内核版本为 3.16 或更高。

外部网络 Calico 插件

在这个示例中,我们将演示如何使用 Calico 作为 Docker libnetwork 的插件进行容器网络设置。最初,支持该功能的是实验性网络,后来在 Docker 1.9 版本中发布。有关 Calico 网络方法的更多细节,请参阅以下 Calico 网络部分。本示例基于github.com/projectcalico/calico-containers/blob/master/docs/calico-with-docker/docker-network-plugin/README.md。要为 Calico 设置 CoreOS Vagrant 集群,我们可以参考github.com/projectcalico/calico-containers/blob/master/docs/calico-with-docker/VagrantCoreOS.md中的过程。

在设置 Vagrant CoreOS 集群后,我们可以看到 CoreOS 集群的两个节点。我们应该确保etcd成功运行,如下所示:

以下是使 Calico 与 Docker 作为网络插件工作的步骤:

在两个节点上使用 libnetwork 选项启动 Calico:

sudo calicoctl node --libnetwork

我们应该在两个节点上看到以下 Docker 容器:

使用 Calico 驱动创建net1网络:

docker network create --driver=calico --subnet=192.168.0.0/24 net1

这会被复制到集群中的所有节点。以下是node2中的网络列表:

  • node1上使用net1网络创建容器 1:

    docker run --net net1 --name workload-A -tid busybox

  • node2上使用net1网络创建容器 2:

    docker run --net net1 --name workload-B -tid busybox

现在,我们可以按以下方式 ping 这两个容器:

Docker 1.9 更新

Docker 1.9 版本在 2015 年 10 月底发布,标志着实验性网络进入生产环境。在本章中,使用 Docker 1.8 实验性网络版本进行的 Docker 网络示例可能需要进行小幅修改。

随着 Docker 1.9 的发布,多主机网络已集成到 Docker Swarm 和 Compose 中。这使我们能够通过单个命令编排跨多个主机的多容器应用程序,且多主机容器网络将自动处理。

其他容器网络技术

Weave 和 Calico 是开源项目,它们为 Docker 开发容器网络技术。Kubernetes 是一个容器编排开源项目,具有特定的容器网络需求和实现。还有其他项目,如面向容器网络的 Cisco Contiv(github.com/contiv/netplugin)。像 Weave、Calico 和 Contiv 这样的容器技术,未来计划与 Rkt 容器运行时集成。

Weave 网络

Weaveworks 开发了一种提供容器网络的解决方案。以下是他们解决方案的一些细节:

  • Weave 在主机上创建 Weave 桥接和 Weave 路由器。

  • Weave 路由器通过主机与其他 Weave 路由器之间建立 TCP 和 UDP 连接。TCP 连接用于发现和协议相关的交换,UDP 用于数据封装。必要时可以进行加密。

  • Weave 桥接配置为嗅探需要跨主机发送并重定向到 Weave 路由器的包。对于本地交换,不使用 Weave 路由器。

  • Weave 的 Weavenet 产品为你提供容器连接。它们还提供 Weavescope,提供容器可视化,和 Weaverun,提供服务发现和负载均衡。

  • Weave 也可以作为 Docker 插件,与 Docker 1.9 版本集成。

下图展示了来自 Weave 的解决方案:

在 CoreOS 上运行 Weave 时,我使用了来自github.com/lukebond/coreos-vagrant-weave的 cloud-config。在以下示例中,我们将在两个 CoreOS 节点中创建容器,并使用 Weave 进行互相通信。在此示例中,我们没有使用 Docker Weave 插件,而是通过环境变量来实现 Docker 和 Weave 之间的通信。

以下是创建 Weave 集群的步骤:

  1. 克隆仓库(git clone https://github.com/lukebond/coreos-vagrant-weave.git)。

  2. config.rb中的实例数量更改为3

  3. 获取一个新的发现令牌,节点数量为 3,并将其更新到用户数据中。

  4. 执行vagrant up来启动集群。

cloud-config文件负责在每个节点上下载 Weave 代理并启动它们。

服务文件的以下部分下载 Weave 容器:

服务文件的以下部分启动 Weave 容器:

在每个节点上,我们可以看到启动的 Weave 容器:

在启动应用程序容器之前,我们需要设置环境变量,以便 Weave 可以拦截 Docker 命令并创建自己的网络。作为Weave.service启动的一部分,环境变量已经设置好。以下命令显示了节点中的设置:

如下所示,配置 Weave 环境:

让我们在两个 CoreOS 节点中启动 busybox 容器:

让我们看看在 CoreOS 节点 1 的 busybox 容器中创建的 Weave 接口:

让我们看看在 CoreOS 节点 2 的 busybox 容器中创建的 Weave 接口:

现在,我们可以在两个容器之间成功地进行 ping 操作。作为 Docker 1.9 的一部分,Weave 作为 Docker 网络插件可用,这使得配置变得更加简便。

Calico 网络

Calico 为 Docker 提供类似于 Weave 的容器网络解决方案。以下是 Calico 实现的一些细节:

  • Calico 直接在 L3 层提供容器网络,而不使用覆盖技术。

  • Calico 使用 BGP 进行路由分发。

  • Calico 有两个组件:BIRD,用于路由分发;FELIX,它是每个节点中的一个代理,负责发现和路由。

  • Calico 也作为 Docker 网络插件与 Docker 1.9 版本集成可用。

下图展示了 Calico 的数据路径:

在 CoreOS 上设置 Calico。

我按照 github.com/projectcalico/calico-containers/blob/master/docs/calico-with-docker/VagrantCoreOS.md 上的步骤设置了一个两节点的 CoreOS 集群。

第一步是检出该仓库:

git clone https://github.com/projectcalico/calico-docker.git

Calico 为 Docker 网络提供了三种方法:

  • Powerstrip:Calico 使用 HTTP 代理来监听 Docker 调用并设置网络。

  • 默认网络配置:Docker 容器默认没有网络设置。使用 Calico 后,网络端点会被添加,并配置网络。

  • Libnetwork:从 Docker 1.9 版本起,Calico 与 Docker libnetwork 集成。这将是长期解决方案。

在以下示例中,我们使用默认网络方法通过 Calico 设置容器之间的连接。

以下是使用 Calico 设置默认网络选项的步骤:

  1. 在所有节点上启动 calicoctl。

  2. 使用 --no-net 选项启动容器。

  3. 为每个容器附加 Calico 网络,并指定 IP 地址。

  4. 创建一个策略配置文件。配置文件设置了允许容器互相通信的策略。

  5. 为容器附加配置文件。

以下命令在 node1node2 上设置容器,并建立允许容器相互通信的策略。

node1 上执行以下命令:

docker run --net=none --name workload-A -tid busybox``sudo calicoctl container add workload-A 192.168.0.1``calicoctl profile add PROF_A_B``calicoctl container workload-A profile append PROF_A_B

这将启动 Docker 容器,附加 Calico 端点,并应用配置文件以允许容器之间的连接。

node2 上执行以下命令:

docker run --net=none --name workload-B -tid busybox``sudo calicoctl container add workload-B 192.168.0.2``calicoctl container workload-B profile append PROF_A_B

这将启动 Docker 容器,附加 Calico 终端节点,并应用与前面命令中相同的配置文件,以允许容器之间的连接。

现在,我们可以测试容器间的连接性:

Kubernetes 网络

Kubernetes 是一个容器编排服务。Kubernetes 是一个由 Google 主导的开源项目。我们将在下一章讨论容器编排中的 Kubernetes。在本章中,我们将介绍一些 Kubernetes 网络的基础知识。

以下是 Kubernetes 如何进行容器网络连接的一些细节:

  • Kubernetes 有一个概念叫做 Pods,它是多个紧密相关的容器的集合。例如,一个服务及其日志服务可以是一个单独的 Pod。另一个 Pod 的例子可以是一个服务和一个检查主服务健康状况的辅助服务。一个 Pod 及其相关的容器始终会被调度到同一台机器上。

  • 每个 Pod 都有一个 IP 地址。Pod 内的所有容器共享相同的 IP 地址。

  • Pod 内的容器共享相同的网络命名空间。为了让 Pod 内的容器能够通信,它们可以使用常规的基于进程的通信方式。

  • Pods 可以通过基于云网络的 VPC 方法或容器网络解决方案(如 Flannel、Weave 或 Calico)相互通信。

  • 由于 Pods 是短暂存在的,Kubernetes 有一个叫做服务(service)的单元。每个服务都有一个关联的虚拟 IP 地址和代理代理,这些代理运行在节点的负载均衡器上,并将流量引导到正确的 Pod。

以下是 Pods 和容器之间如何关联以及它们如何进行通信的示意图:

摘要

在本章中,我们介绍了不同的容器网络技术,重点是 CoreOS 中的容器网络。许多公司都在尝试解决这个容器网络问题。CNI 和 Flannel 已成为 CoreOS 的默认选项,而 Libnetwork 已成为 Docker 的默认选项。拥有标准化和可插拔的网络架构对行业有好处,因为它能实现互操作性。容器网络仍处于早期阶段,技术成熟还需要一些时间。在下一章中,我们将讨论 CoreOS 存储管理。

参考资料

进一步阅读和教程

第七章

第六章 CoreOS 存储管理

存储是分布式基础设施中的关键组件。容器技术的初步关注点是无状态容器,存储由传统技术如 NAS 和 SAN 管理。无状态容器通常是像 NGINX 和 Node.js 这样的 Web 应用程序,不需要持久化数据。近年来,越来越多的关注点转向有状态容器,并且许多新技术正在开发中以实现有状态容器。有状态容器是如 SQL 和 Redis 这样的数据库,数据需要持久化。CoreOS 和 Docker 与不同的存储技术集成得很好,并且在这个领域有很多积极的工作正在进行中。

本章将涵盖 CoreOS 存储的以下三个方面:

  • CoreOS 基础文件系统和分区表

  • 容器文件系统,由 Union 文件系统和写时复制(CoW)存储驱动组成

  • 用于共享数据持久化的容器数据卷,可以是本地的、分布式的或共享的外部存储

本章将涵盖以下主题:

  • CoreOS 文件系统及将 AWS EBS 和 NFS 存储挂载到 CoreOS 文件系统

  • 用于存储容器镜像的 Docker 容器文件系统,包含存储驱动程序和 Union 文件系统。

  • Docker 数据卷

  • 使用 Flocker、GlusterFS 和 Ceph 实现容器数据持久化

存储概念

以下是我们在本章及以后将使用的一些存储术语及其基本定义:

  • 本地存储:这是附加到本地主机的存储。一个例子是带有 ZFS 的本地硬盘。

  • 网络存储:这是通过网络访问的常见存储。可以是 SAN 或像 Ceph 和 GlusterFS 这样的集群存储。

  • 云存储:由云提供商提供的存储,例如 AWS EBS、OpenStack Cinder 和 Google 云存储。

  • 块存储:需要低延迟,通常用于操作系统相关的文件系统。一些例子包括 AWS EBS 和 OpenStack Cinder。

  • 对象存储:用于不可变存储项,其中延迟不是大问题。一些例子包括 AWS S3 和 OpenStack Swift。

  • NFS:这是一种分布式文件系统。可以在任何集群存储之上运行。

CoreOS 文件系统

我们在第三章中详细介绍了 CoreOS 的分区表,CoreOS 自动更新。以下截图展示了 AWS CoreOS 集群中的默认分区:

默认情况下,CoreOS 使用根分区作为容器文件系统。在前面的表格中,/dev/xvda9将用于存储容器镜像。

以下输出显示 Docker 在 AWS 上运行的 CoreOS 节点中使用 Ext4 文件系统和 Overlay 存储驱动:

为了获得额外的存储,可以在 CoreOS 中挂载外部存储。

挂载 AWS EBS 卷

Amazon Elastic Block Store (EBS) 为您提供持久的块级存储卷,可以与 AWS 云中的 Amazon EC2 实例一起使用。以下示例展示了如何向运行在 AWS 中的 CoreOS 节点添加一个额外的 EBS 卷,并将其用于容器文件系统。

将以下 cloud-config 文件重命名为 cloud-config-mntdocker.yml

#cloud-config coreos:   etcd2:     name: etcdserver     initial-cluster: etcdserver=http://$private_ipv4:2380     advertise-client-urls: http://$private_ipv4:2379,http://$private_ipv4:4001     initial-advertise-peer-urls: http://$private_ipv4:2380     listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001     listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001   units:     - name: etcd2.service       command: start     - name: fleet.service       command: start     - name: format-ephemeral.service       command: start       content: |         [Unit]         Description=格式化临时驱动器         After=dev-xvdf.device         Requires=dev-xvdf.device         [Service]         Type=oneshot         RemainAfterExit=yes         ExecStart=/usr/sbin/wipefs -f /dev/xvdf         ExecStart=/usr/sbin/mkfs.btrfs -f /dev/xvdf     - name: var-lib-docker.mount       command: start       content: |         [Unit]         Description=将临时存储挂载到/var/lib/docker         Requires=format-ephemeral.service         After=format-ephemeral.service         Before=docker.service         [Mount]         What=/dev/xvdf         Where=/var/lib/docker         Type=btrfs

以下是有关前面提到的 cloud-config 单元文件的一些详细信息:

  • Format-ephemeral 服务负责将文件系统格式化为 btrfs

  • Mount 服务负责在 docker.service 启动之前将新卷挂载到 /var/lib/docker

我们可以使用以下命令启动带有额外 EBS 卷的 CoreOS 节点,并使用前述 cloud-config

aws ec2 run-instances --image-id ami-85ada4b5 --count 1 --instance-type t2.micro --key-name "smakam-oregon" --security-groups "coreos-test" --user-data file://cloud-config-mntdocker.yaml --block-device-mappings "[{\"DeviceName\":\"/dev/sdf\",\"Ebs\":{\"DeleteOnTermination\":false,\"VolumeSize\":8,\"VolumeType\":\"gp2\"}}]"

前述命令创建了一个单节点 CoreOS 集群,且附加了一个 8 GB 的额外卷。该新卷以 btrfs 文件系统挂载为 /var/lib/docker/dev/sdf 目录被挂载到 CoreOS 系统中的 /dev/xvdf,因此挂载文件使用的是 /dev/xvdf

以下是节点中带有前述 cloud-config 配置的分区表:

如我们所见,/var/lib/docker 被挂载到一个新的 8 GB 分区上。

以下输出展示了 docker 文件系统正在使用我们请求的 btrfs 存储驱动:

挂载 NFS 存储

我们可以使用 NFS 在 CoreOS 节点上挂载一个卷。NFS 提供了一种共享存储机制,集群中的所有 CoreOS 节点都可以看到相同的数据。这种方法可以用于容器数据持久化,当容器在节点之间移动时。在以下示例中,我们在 Linux 服务器上运行 NFS 服务器,并在 Vagrant 环境中运行的 CoreOS 节点上挂载该卷。

以下是设置 CoreOS 节点上 NFS 挂载的步骤:

  1. 启动 NFS 服务器并导出要共享的目录。

  2. 设置 CoreOS 的cloud-config以启动rpc-statd.service。挂载服务还需要在cloud-config中启动,以便将必要的 NFS 目录挂载到本地目录。

设置 NFS 服务器

启动 NFS 服务器。我已将我的 Ubuntu 14.04 机器设置为 NFS 服务器。以下是我为设置 NFS 服务器所执行的步骤:

  1. 安装 NFS 服务器:

    sudo apt-get install nfs-kernel-server

  2. 创建具有适当所有者的 NFS 目录:

    sudo mkdir /var/nfs``sudo chown core /var/nfs (我已经创建了一个 core 用户)

  3. NFS目录导出到必要的节点。在我的例子中,172.17.8.[101-103]是 CoreOS 集群的 IP 地址。使用以下命令创建/etc/exports

    /var/nfs    172.17.8.101(rw,sync,no_root_squash,no_subtree_check)``/var/nfs    172.17.8.102(rw,sync,no_root_squash,no_subtree_check)``/var/nfs    172.17.8.103(rw,sync,no_root_squash,no_subtree_check)

  4. 启动 NFS 服务器:

    sudo exportfs -a``sudo service nfs-kernel-server start

    注意

    注意:NFS 对用户 ID(UID)和组 ID(GID)的检查非常敏感,除非正确设置,否则客户端机器无法进行写入访问。客户端用户的 UID 和 GID 必须与服务器中目录的 UID 和 GID 匹配。另一个选择是设置no_root_squash选项(如前面示例所示),以便客户端的 root 用户可以像服务器中的 UserID 一样进行修改。

如下命令所示,我们可以看到在完成必要的配置后导出的目录:

将 CoreOS 节点设置为 NFS 客户端

以下cloud-config可以用于在 CoreOS 集群的所有节点中挂载远程的/var/nfs/mnt/data

#cloud-config write-files:   - path: /etc/conf.d/nfs     permissions: '0644'     content: |       OPTS_RPC_MOUNTD="" coreos:   etcd2:     discovery: <yourtoken>     advertise-client-urls: http://$public_ipv4:2379     initial-advertise-peer-urls: http://$private_ipv4:2380     listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001     listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001   fleet:     public-ip: $public_ipv4   flannel:     interface: $public_ipv4   units:     - name: etcd2.service       command: start     - name: fleet.service       command: start     - name: rpc-statd.service       command: start       enable: true     - name: mnt-data.mount       command: start       content: |         [Mount]         What=172.17.8.110:/var/nfs         Where=/mnt/data         Type=nfs         Options=vers=3,sec=sys,noauto

在前面的配置中,cloud-config中,rpc-statd.service 是 NFS 客户端服务所必需的,mnt-data.mount 是将 NFS 卷挂载到/mnt/data本地目录所必需的。

以下输出来自已经执行 NFS 挂载的 CoreOS 节点之一。正如我们所看到的,NFS 挂载成功:

在此步骤之后,集群中的任何 CoreOS 节点都可以从/mnt/data进行读写。

容器文件系统

容器使用 CoW 文件系统存储容器镜像。以下是 CoW 文件系统的一些特性:

  • 多个用户/进程可以共享相同的数据,就像它们拥有自己的数据副本一样。

  • 如果数据由某个进程或用户更改,该进程/用户将仅在此时创建数据的新副本。

  • 多个运行中的容器共享同一组文件,直到对文件进行更改。这使得容器启动非常快速。

这些特性使得容器文件系统操作非常快速。Docker 支持多种能够进行 CoW 的存储驱动程序。每个操作系统选择一个默认的存储驱动程序。Docker 提供了一个选项,可以更改存储驱动程序。要更改存储驱动程序,我们需要在/etc/default/docker中指定存储驱动程序,并重新启动 Docker 守护进程:

DOCKER_OPTS="--storage-driver=<driver>"

主要支持的存储驱动程序包括aufsdevicemapperbtrfsoverlay。在更改存储驱动程序之前,我们需要确保存储驱动程序被 Docker 所安装的操作系统支持。

存储驱动程序

存储驱动程序负责管理文件系统。以下表格列出了 Docker 支持的主要存储驱动程序之间的差异:

属性 AUFS 设备映射器 BTRTS OverlayFS ZFS
文件/块 基于文件 基于块 基于文件 基于文件 基于文件
Linux 内核支持 不在主内核中 主内核中存在 主内核中存在 主内核中存在 > 3.18 不在主内核中
操作系统 Ubuntu 默认 Red Hat Red Hat Solaris
性能 不适合写入大文件;适用于 PaaS 场景 首次写入较慢 更新大量小文件可能导致低性能 比 AUFS 更好 占用大量内存

存储驱动程序需要根据工作负载类型、主 Linux 内核的可用性需求以及对特定存储驱动程序的熟悉程度来选择。

以下输出显示了在 Ubuntu 系统上运行的 Docker 使用的默认 AUFS 存储驱动程序:

以下输出显示了 Docker 在 CoreOS 节点上使用 Overlay 驱动程序。CoreOS 曾经使用 btrfs,但由于 btrfs 的稳定性问题,它们最近转向了 Overlay 驱动程序。

/var/lib/docker 目录是容器元数据和卷数据存储的位置。以下重要信息存储在这里:

  • 容器:容器元数据

  • 卷:主机卷

  • 存储驱动程序,如 aufs 和 device mapper:这些将包含差异和层

以下截图显示了在运行 Docker 的 Ubuntu 系统中的目录输出:

Docker 和 Union 文件系统

Docker 镜像利用 Union 文件系统创建一个由多个层组成的镜像。Union 文件系统使用了写时复制(CoW)技术。每一层就像是镜像的快照,记录了特定的变化。以下示例展示了一个 Ubuntu Docker 镜像的镜像层:

每一层显示了在基础层上执行的操作,以获得这层新内容。

为了说明层级结构,我们以这个基础的 Ubuntu 镜像为例,并使用以下 Dockerfile 创建一个新的容器镜像:

FROM ubuntu:14.04 MAINTAINER Sreenivas Makam <smxxxx@yahoo.com> # 安装 apache2 RUN apt-get install -y apache2 EXPOSE 80 ENTRYPOINT ["/usr/sbin/apache2ctl"] CMD ["-D", "FOREGROUND"]

构建一个新的 Docker 镜像:

docker build -t="smakam/apachetest"

让我们来看一下这个新截图的层:

前四个层是由 Dockerfile 创建的,而最后四个层是 Ubuntu 14.04 基础镜像的一部分。如果系统中已经有了 Ubuntu 14.04 镜像并尝试下载 smakam/apachetest,那么只会下载前四个层,因为其他层已经存在于主机中并且可以重用。这个层重用机制使得从 Docker Hub 下载 Docker 镜像更快,并且能够有效地存储 Docker 镜像在容器文件系统中。

容器数据

容器数据不是容器文件系统的一部分,而是存储在容器运行的主机文件系统中。容器数据可以用来存储需要频繁操作的数据,例如数据库。容器数据通常需要在多个容器之间共享。

Docker 卷

在容器中所做的更改会作为联合文件系统的一部分存储。如果我们希望将一些数据保存在容器范围之外,可以使用卷。卷存储在主机文件系统中,并在容器中挂载。当容器的更改被提交时,卷不会被提交,因为它们位于容器文件系统之外。卷可以用来与主机文件系统共享源代码、保持数据库等持久化数据、在容器之间共享数据,并充当容器的草稿板。对于需要频繁读写操作的应用(如数据库),卷比联合文件系统提供更好的性能。使用卷并不能保证容器数据的持久性。使用仅数据容器是一种保持持久性并在容器之间共享数据的方法。还有其他方法,比如使用共享和分布式存储来跨主机持久化容器数据。

容器卷

以下示例启动带有/data卷的 Redis 容器:

docker run -d --name redis -v /data redis

如果我们运行 Docker 以检查 Redis 容器,我们可以获得该容器挂载的卷的详细信息,如下截图所示:

Source目录是主机机器上的目录,Destination是容器中的目录。

带有主机挂载目录的卷

以下是通过挂载主机目录使用卷进行代码共享的示例:

docker run -d --name nginxpersist -v /home/core/local:/usr/share/nginx/html -p ${COREOS_PUBLIC_IPV4}:8080:80 nginx

如果我们执行docker inspect nginxpersist,我们可以看到主机目录和容器挂载目录:

在主机机器中,可以在/home/core/local位置进行代码开发,主机中的任何代码更改会自动反映在容器中。

由于主机目录在不同主机上可能不同,这使得容器无法移植,并且 Dockerfile 不支持主机挂载选项。

仅数据容器

Docker 支持功能强大的仅数据容器。多个容器可以继承来自仅数据容器的卷。与常规基于主机的卷挂载相比,使用仅数据容器的优势在于我们无需担心主机文件权限。另一个优势是仅数据容器可以在主机之间移动,并且一些最近的 Docker 卷插件能够在容器跨主机移动时处理卷数据的迁移。

以下示例展示了容器死亡并重启时,如何持久化卷。

我们创建一个名为redisvolume的卷容器,用于redis,并在redis1容器中使用这个卷。hellocounter容器统计网页点击次数,并使用redis容器来保持计数的持久性:

docker run -d --name redisvolume -v /data redis``docker run -d --name redis1 --volumes-from redisvolume redis``docker run -d --name hello1 --link redis1:redis -p 5000:5000 smakam/hellocounter python app.py

让我们查看正在运行的容器:

让我们通过 curl 多次访问 hellocounter 容器,如下图所示:

现在,让我们停止这个容器并使用以下命令重启另一个容器。新的 redis 容器 redis2 仍然使用相同的 redisvolume 容器:

docker stop redis1 hello1``docker rm redis1 hello1``docker run -d --name redis2 --volumes-from redisvolume redis``docker run -d --name hello2 --link redis2:redis -p 5001:5000 smakam/hellocounter python app.py

如果我们尝试通过端口 5001 访问 hellocounter 容器,我们会看到计数器从 6 开始,因为即使我们已经停止了该容器并重新启动了一个新的 redis 容器,之前的值 5 仍然保存在数据库中:

数据容器也可以用于在容器之间共享数据。一个示例用例是,网页容器写入日志文件,而日志处理容器处理该日志文件并将其导出到中央服务器。网页和日志容器都可以挂载同一个卷,一个容器写入该卷,另一个容器从该卷读取数据。

要备份我们创建的 redisvolume 容器数据,可以使用以下命令:

docker run --volumes-from redisvolume -v $(pwd):/backup ubuntu tar cvf /backup/backup.tar /data

这将从 redisvolume 中获取 /data,并使用 Ubuntu 容器将内容备份到当前主机目录中的 backup.tar

删除卷

作为删除容器的一部分,如果我们使用 docker rm –v 选项,卷将被自动删除。如果我们忘记使用 -v 选项,卷将会悬挂。这有一个缺点,即为卷分配的主机空间将被浪费且无法删除。

Docker 1.7 之前版本还没有原生的解决方案来处理悬挂的卷。有一些实验性的容器可用来清理悬挂的卷。我使用这个测试容器 martin/docker-cleanup-volumes 来清理悬挂的卷。首先,我们可以使用 dry-run 选项来确定悬挂的卷。以下是一个示例,显示了四个悬挂的卷和一个正在使用的卷:

如果我们去掉 dry-run 选项,悬挂的卷将被删除,如下图所示:

Docker 卷插件

类似于 Docker 的网络插件,卷插件扩展了 Docker 容器的存储功能。卷插件提供了高级存储功能,例如跨节点的卷持久化。下图展示了卷插件架构,其中卷驱动程序暴露了一组标准的 API,插件可以实现这些 API。GlusterFS、Flocker、Ceph 和其他几家公司提供 Docker 卷插件。与 Docker 网络插件不同,Docker 没有原生的卷插件,而是依赖外部供应商的插件:

Flocker

Docker 数据卷绑定到创建容器的单一节点。当容器在节点之间迁移时,数据卷不会随之迁移。Flocker 解决了将数据卷与容器一起迁移的问题。下图展示了 Flocker 架构中的所有重要组件:

以下是 Flocker 实现的一些内部细节:

  • Flocker 代理在每个节点上运行,负责与 Docker 守护进程和 Flocker 控制服务进行通信。

  • Flocker 控制服务负责管理卷以及 Flocker 集群。

  • 当前支持的后端存储包括 Amazon AWS EBS、Rackspace 块存储和 EMC ScaleIO。使用 ZFS 的本地存储目前处于实验性阶段。

  • REST API 和 Flocker CLI 都用于管理卷和 Docker 容器。

  • Docker 可以使用 Flocker 作为数据卷插件来管理卷。

  • Flocker 插件将负责管理数据卷,包括在容器跨主机迁移时迁移与容器关联的卷。

  • Flocker 将使用容器网络技术在主机之间进行通信——这可以是原生 Docker 网络,或者是 Docker 网络插件,如 Weave。

在接下来的三个示例中,我们将演示 Flocker 如何在不同环境下实现容器数据持久化。

使用 AWS EBS 作为后端的 Flocker 卷迁移

本示例将演示使用 AWS EBS 作为存储后端的数据持久化。在本示例中,我们将在 AWS 云中创建三个 Linux 节点。一个节点将作为 Flocker 主节点,运行控制服务,另外两个节点将运行 Flocker 代理,运行容器并挂载 EBS 存储。通过这些节点,我们将创建一个有状态容器,并演示容器迁移时的容器数据持久化。该示例将使用一个 hellocounter 容器,后端为 redis 容器,并演示当 redis 计数器跨主机迁移时的数据持久化。下图展示了主节点和代理节点如何与 EBS 后端关联:

我按照 Flocker 网页上提到的过程操作,参考了docs.clusterhq.com/en/1.4.0/labs/installer.html

以下是使用 AWS EBS 设置 Flocker 卷迁移的步骤总结:

  1. 创建运行 Docker 容器和 Flocker 服务的虚拟机需要拥有 AWS 账户。

  2. 对于执行前端 Flocker 命令,我们需要一台 Linux 主机。在我的例子中,是一台 Ubuntu 14.04 虚拟机。

  3. 使用 Flocker 脚本在 Linux 主机上安装 Flocker 前端工具。

  4. 使用 Flocker 脚本在控制节点上安装 Flocker 控制服务,并在从节点上安装 Flocker 代理。

  5. 此时,我们可以在从节点上创建带有数据卷的容器,并迁移容器,同时保持数据卷持久化。

以下是安装 Flocker 前端工具和 Flocker 控制服务与代理后的相关输出。

这是 Flocker 前端工具的版本:

Flocker 节点列表显示了将运行 Flocker 代理的两个 AWS 节点:

以下输出显示了 Flocker 卷列表。最初没有任何卷:

让我们查看主节点中的主要进程。以下截图展示了控制服务正在运行:

让我们查看从节点中的主要进程。在这里,我们可以看到 Flocker 代理和 Flocker Docker 插件正在运行:

让我们在特定的从节点上创建一个 hellocounter 容器,后端是 redis 容器,更新数据库中的计数器,然后移动容器,演示数据卷在容器移动时如何保持持久化。

让我们首先设置一些快捷方式:

NODE1="52.10.201.177" (此公共 IP 地址对应 flocker list-nodes 输出中显示的私有地址)``NODE2="52.25.14.152"``KEY="keylocation"

让我们在 node1 上启动 hellocontainerredis 容器:

ssh -i $KEY root@$NODE1 docker run -d -v demo:/data --volume-driver=flocker --name=redis redis:latest``ssh -i $KEY root@$NODE1 docker run -d -e USE_REDIS_HOST=redis --link redis:redis -p 80:5000 --name=hellocounter smakam/hellocounter

现在,让我们查看此时创建并附加的卷。100 GB 的 EBS 卷已附加到从节点 node1

从以下输出中,我们可以看到两个容器正在 node1 上运行:

现在,我们来创建一些数据库条目。当前计数器的值为 6,如下图所示:

现在,让我们移除 NODE1 上的容器,并在 NODE2 上创建 hellocounterredis 容器:

ssh -i $KEY root@$NODE1 docker stop hellocounter``ssh -i $KEY root@$NODE1 docker stop redis``ssh -i $KEY root@$NODE1 docker rm -f hellocounter``ssh -i $KEY root@$NODE1 docker rm -f redis``ssh -i $KEY root@$NODE2 docker run -d -v demo:/data --volume-driver=flocker --name=redis redis:latest``ssh -i $KEY root@$NODE2 docker run -d -e USE_REDIS_HOST=redis --link redis:redis -p 80:5000 --name=hellocounter smakam/hellocounter

正如我们所看到的,卷已迁移到第二个从节点:

让我们查看 node2 中的容器:

现在,让我们检查数据是否持久化:

从前面的输出可以看到,计数器值从先前的6开始,并递增到7,这表明当 redis 容器跨节点移动时,redis 数据库是持久化的。

使用 ZFS 后端的 Flocker 卷迁移

本示例将展示使用 ZFS 作为存储后端和 Vagrant Ubuntu 集群的数据显示持久化。ZFS 是一个开源文件系统,重点关注数据完整性、复制和性能。我按照docs.clusterhq.com/en/1.4.0/using/tutorial/vagrant-setup.html的流程,设置了一个两节点的 Vagrant Ubuntu Flocker 集群,并参照docs.clusterhq.com/en/1.4.0/using/tutorial/volumes.html尝试了一个示例应用程序,该应用程序支持容器迁移及相关的卷迁移。示例应用程序使用 MongoDB 容器进行数据存储,并演示了数据持久化。

以下是使用 ZFS 后端设置 Flocker 卷迁移的步骤总结:

  1. 在客户端机器中安装 Flocker 客户端工具和mongodb客户端。我的情况下,这是一个 Ubuntu 14.04 虚拟机。

  2. 创建一个两节点 Vagrant Ubuntu 集群。在集群设置过程中,Flocker 服务将在每个节点上启动,包括控制服务和代理服务。

  3. 启动 flocker-deploy 脚本,启动mongodb容器在node1上运行。

  4. 启动mongodb客户端并在node1中写入一些条目。

  5. 启动 flocker-deploy 脚本,将mongodb容器从node1迁移到node2

  6. 启动mongodb客户端到node2,检查数据是否被保留。

启动两节点 Vagrant 集群后,让我们检查相关的 Flocker 服务。

Node1上运行着 Flocker 控制服务和代理服务,如下图所示:

Node2仅运行 Flocker 代理服务,并由Node1管理,如下图所示:

让我们查看 Flocker 节点列表;这显示了两个节点:

按照以下步骤在node1上部署mongodb容器:

让我们查看卷列表。如我们所见,卷附加到node1

以下输出显示了node1中的容器:

让我们往mongodb中添加一些数据:

现在,让我们将容器重新部署到node2

让我们查看卷的输出。如我们所见,卷已移动到node2

从以下输出可以看到,mongodb内容,数据,得到了保留:

使用 AWS EBS 后端的 CoreOS 上的 Flocker

Flocker 最近已与 CoreOS 进行实验性集成,使用 AWS EBS 后端存储。我按照 https://github.com/clusterhq/flocker-coreosclusterhq.com/2015/09/01/flocker-runs-on-coreos/上的程序进行此示例。过程中,我遇到了一些问题,无法使 Flocker 工具的版本 1.4.0 与 CoreOS 节点配合使用。工具的 1.3.0 版本(docs.clusterhq.com/en/1.3.0/labs/installer.html)运行正常。

在这个例子中,我们演示了如何使用 Flocker 插件在 CoreOS 集群中通过 Docker 实现容器数据持久化。

以下是设置 AWS 上运行的 CoreOS 集群中的 Flocker 卷迁移的步骤总结:

  1. 使用 Flocker 指定的模板和新创建的发现令牌,通过 AWS Cloudformation 创建 CoreOS 集群。

  2. 创建包含节点 IP 和访问详情的cluster.yml文件。

  3. 启动 Flocker 脚本来配置 CoreOS 节点,包含 Flocker 控制服务和 Flocker 代理。Flocker 脚本还会处理将 CoreOS 节点中默认的 Docker 二进制文件替换为支持 Volume 插件的 Docker 二进制文件。

  4. 检查容器迁移是否能正常工作并保持数据持久化。

我使用了以下 Cloudformation 脚本,通过 Flocker 的模板文件创建 CoreOS 集群:

aws cloudformation create-stack --stack-name coreos-test1 --template-body file://coreos-stable-flocker-hvm.template --capabilities CAPABILITY_IAM --tags Key=Name,Value=CoreOS --parameters ParameterKey=DiscoveryURL,ParameterValue="your token" ParameterKey=KeyPair,ParameterValue="your keypair"

以下是包含三台节点的 CoreOS 集群的详细信息:

以下是已安装的旧版本和新版本 Docker。Docker 版本 1.8.3 支持 Volume 插件:

以下是节点中的 CoreOS 版本:

以下输出显示了 Flocker 节点列表,列出了三台运行 Flocker 的 CoreOS 节点:

我尝试了与上一部分中提到的相同的hellocounter示例,卷会在节点之间自动移动。以下输出显示了最初附加到node1的卷,后来作为容器迁移的一部分,移动到node2

这是附加到node1的卷:

这是附加到node2的卷:

根据 Flocker 文档,他们计划在某个时刻支持 CoreOS 上的 ZFS 后端,以允许我们使用本地存储而不是 AWS EBS。目前还不确定 CoreOS 是否会原生支持 ZFS。

Flocker 最近的新增功能

Flocker 在 2015 年 11 月新增了以下功能:

  • Flocker 卷中心(clusterhq.com/volumehub/)管理来自中心位置的所有 Flocker 卷。

  • Flocker dvol (clusterhq.com/dvol/) 为你提供类似 Git 的数据卷功能。这可以帮助管理数据库,如代码库。

GlusterFS

GlusterFS 是一个分布式文件系统,存储分布在多个节点上,并作为一个单一的单元呈现。GlusterFS 是一个开源项目,可以在任何类型的存储硬件上运行。Red Hat 已收购 Gluster,而 Gluster 是 GlusterFS 的发源地。以下是 GlusterFS 的一些特性:

  • 多台服务器及其关联的存储通过对等关系(peering)加入 GlusterFS 集群。

  • GlusterFS 可以在普通存储以及 SAN 上运行。

  • 通过避免使用中央元数据服务器并采用分布式哈希算法,GlusterFS 集群具备可扩展性,能够扩展为非常大的集群。

  • 从 GlusterFS 的角度来看,砖块(Bricks)是存储的最小组件。一个砖块由带有基础文件系统的存储磁盘上的挂载点组成。砖块绑定到单个服务器上。一个服务器可以拥有多个砖块。

  • 卷(Volumes)由多个砖块组成。卷作为挂载目录挂载到客户端设备上。

  • 主要的卷类型有分布式(distributed)、复制(replicated)和条带化(striped)。分布式卷类型允许将文件分布到多个砖块上。复制卷类型允许文件有多个副本,从冗余角度来看非常有用。条带化卷类型允许将一个大文件分割成多个较小的文件并将其分布到各个砖块上。

  • GlusterFS 支持多种访问方法来访问 GlusterFS 卷,包括基于 FUSE 的本地访问、SMB、NFS 和 REST。

下图展示了 GlusterFS 的不同层次结构:

设置 GlusterFS 集群

在以下示例中,我已经设置了一个由两台节点组成的 GlusterFS 3.5 集群,每台服务器都运行 Ubuntu 14.04 虚拟机。我还将 GlusterFS 服务器节点作为 GlusterFS 客户端使用。

以下是设置 GlusterFS 集群的步骤概览:

  1. 在两台节点上安装 GlusterFS 服务器,并在集群中的一个节点上安装客户端软件。

  2. GlusterFS 节点必须能够相互通信。我们可以设置 DNS 或者使用静态的 /etc/hosts 方法让节点之间进行通信。

  3. 如有需要,关闭防火墙,以便服务器能够相互通信。

  4. 设置 GlusterFS 服务器对等。

  5. 创建砖块。

  6. 在创建的砖块基础上创建卷。

  7. 在客户端机器上,将卷挂载到挂载点,并开始使用 GlusterFS。

以下命令需要在每台服务器上执行。这将安装 GlusterFS 服务器组件。此操作需要在两台节点上执行:

sudo apt-get install software-properties-common``sudo add-apt-repository ppa:gluster/glusterfs-3.5``sudo apt-get update``sudo apt-get install glusterfs-server

以下命令将安装 GlusterFS 客户端。仅在 node1 中需要此操作:

sudo apt-get install glusterfs-client

设置 /etc/hosts 以允许节点相互通信:

192.168.56.102 gluster1``192.168.56.101 gluster2

禁用防火墙:

sudo iptables -I INPUT -p all -s 192.168.56.102 -j ACCEPT``sudo iptables -I INPUT -p all -s 192.168.56.101 -j ACCEPT

创建复制卷并启动它:

sudo gluster volume create volume1 replica 2 transport tcp gluster1:/gluster-storage gluster2:/gluster-storage force (/gluster-storage 是每个节点中的砖块)``sudo gluster volume start volume1

在每个节点中设置服务器探针。以下命令适用于 Node1

sudo gluster peer probe gluster2

以下命令适用于 Node2

sudo gluster peer probe gluster1

对 GlusterFS 卷进行客户端挂载。这是在 Node1 中需要的操作:

sudo mkdir /storage-pool``sudo mount -t glusterfs gluster2:/volume1 /storage-pool

现在,让我们查看 Node1 中的 GlusterFS 集群状态和创建的卷:

现在,让我们看一下 Node2

看一下卷的详细信息。正如我们所见,volume1 被设置为复制卷类型,且在 gluster1gluster2 上有两个砖块:

以下输出显示了 df -k 输出中的客户端挂载点:

此时,我们可以从客户端挂载点 /storage-pool 读写内容。

为 CoreOS 集群设置 GlusterFS

通过设置 CoreOS 节点使用 GlusterFS 文件系统,容器卷可以使用 GlusterFS 存储卷相关数据。这使得容器能够在节点之间迁移并保持卷的持久性。此时,CoreOS 不支持本地 GlusterFS 客户端。使用 GlusterFS 的一种方法是通过 NFS 导出 GlusterFS 卷,并从 CoreOS 节点进行 NFS 挂载。

继续使用上一节中创建的 GlusterFS 集群,我们可以按照以下步骤在 GlusterFS 集群中启用 NFS:

sudo gluster volume set volume1 nfs.disable off

用于挂载 NFS 存储部分的 CoreOS cloud-config 也可以在这里使用。以下是挂载特定部分,我们在 CoreOS 节点的 /mnt/data 中挂载了 GlusterFS 卷 172.17.8.111:/volume1

- name: mnt-data.mount command: start content: | [Mount] What=172.17.8.111:/volume1 Where=/mnt/data Type=nfs Options=vers=3,sec=sys,noauto

我在 GlusterFS 卷 /volume1 中创建了一些文件,并能够从 CoreOS 节点进行读写。以下输出显示了 CoreOS 节点中的 /mnt/data 内容:

使用 Docker 卷插件访问 GlusterFS

使用 GlusterFS 卷插件(github.com/calavera/docker-volume-glusterfs)为 Docker,我们可以使用常规的 Docker 卷 CLI 创建和管理卷。

在以下示例中,我们将安装 GlusterFS Docker 卷插件,并创建一个持久化的hellocounter应用程序。我使用的是运行 GlusterFS 卷的相同 Ubuntu 14.04 虚拟机来运行 Docker。

以下是设置 Docker 卷插件所需的步骤:

  • Docker 实验性版本支持 GlusterFS 卷插件,因此需要下载实验性 Docker 版本。

  • GlusterFS Docker 卷插件需要下载并启动。需要安装 GO(golang.org/doc/install)以获取卷插件。

  • 使用 Docker 和 GlusterFS Docker 卷插件。为此,必须停止并重新启动 Docker 服务。

以下是在两个节点上运行的 Docker 实验性发布版本:

下载并启动 GlusterFS 卷插件:

go get github.com/calavera/docker-volume-glusterfs``sudo docker-volume-glusterfs -servers gluster1:gluster2 &

使用 GlusterFS 卷驱动启动redis容器,如下所示:

docker run -d -v volume1:/data --volume-driver=glusterfs --name=redis redis:latest

启动hellocounter容器并将其链接到redis容器:

docker run -d -e USE_REDIS_HOST=redis --link redis:redis -p 80:5000 --name=hellocounter smakam/hellocounter

通过访问计数器几次来更新它,如下图所示:

现在,停止node1中的容器,并在node2中启动它们。我们来看看node2中正在运行的容器:

如果我们现在访问hellocounter容器,我们可以看到计数器从3开始,因为先前的计数值已经被持久化:

Ceph

Ceph 为你提供类似于 GlusterFS 的分布式存储,并且是一个开源项目。Ceph 最初由 Inktank 开发,后来被 Red Hat 收购。以下是 Ceph 的一些特性:

  • Ceph 使用可靠的自适应分布式对象存储(RADOS)作为存储机制。其他存储访问机制如文件和块存储是基于 RADOS 实现的。

  • Ceph 和 GlusterFS 似乎具有类似的特性。根据 Red Hat 的说法,Ceph 更适合 OpenStack 集成,而 GlusterFS 则用于大数据分析,二者会有一些重叠。

  • Ceph 有两个关键组件。它们分别是 Monitor 和 OSD。Monitor 存储集群地图,而对象存储守护进程(OSD)是形成存储集群的单个存储节点。存储客户端和 OSD 都使用 CRUSH 算法将数据分布到集群中。

与 GlusterFS 相比,设置 Ceph 似乎稍微复杂一些,并且目前有活跃的工作正在进行,以使 Ceph 组件能够作为 Docker 容器运行,并且将 Ceph 与 CoreOS 集成。此外,还有工作在进行中,涉及 Ceph Docker 卷插件。

NFS

NFS 是一种分布式文件系统,它允许客户端计算机像本地存储一样访问网络存储。我们可以通过共享的 NFS 存储实现容器数据持久化。

使用 NFS 实现容器数据持久化。

本节将介绍一个使用 NFS 进行数据持久化的 Web 应用示例。以下是该应用的一些细节:

  • hellocounter.service单元启动一个容器,用来跟踪应用程序的网页访问次数。

  • Hellocounter.service使用redis.service容器来跟踪访问计数。

  • Redis 容器使用 NFS 存储来保存数据。

  • 当数据库容器因某种原因停止时,Fleet 会在集群中的另一个节点重启该容器,由于该服务使用 NFS 存储,计数值会被持久化。

以下图示显示了本节中使用的示例:

以下是前提条件和所需的步骤:

  1. 启动 NFS 服务器和一个三节点的 CoreOS 集群,将 NFS 数据挂载到如“挂载 NFS 存储”部分所述。

  2. 使用 Fleet 启动hellocounter.serviceredis.service,通过 X-fleet 属性控制容器的调度。hellocounter.service在所有节点上启动;redis.service在其中一个节点上启动。

Hellocounter@.service的代码如下:

[Unit] Description=hello counter with redis backend [Service] Restart=always RestartSec=15 ExecStartPre=-/usr/bin/docker kill %p%i ExecStartPre=-/usr/bin/docker rm %p%i ExecStartPre=/usr/bin/docker pull smakam/hellocounter ExecStart=/usr/bin/docker run --name %p%i -e SERVICE_NAME=redis -p 5000:5000 smakam/hellocounter python app.py ExecStop=/usr/bin/docker stop %p%i [X-Fleet] X-Conflicts=%p@*.service

Redis.service的代码如下:

[Unit] Description=app-redis [Service] Restart=always RestartSec=5 ExecStartPre=-/usr/bin/docker kill %p ExecStartPre=-/usr/bin/docker rm %p ExecStartPre=/usr/bin/docker pull redis ExecStart=/usr/bin/docker run --name redis -v /mnt/data/hellodata:/data redis ExecStop=/usr/bin/docker stop %p [X-Fleet] Conflicts=redis.service

我们启动三个hellocounter@.service实例和一个redis.service实例。下图显示了在 CoreOS 集群中运行的三个hellocounter服务实例和一个redis服务实例。

从前面的截图可以看到,hellocounter@2.serviceredis.service都在同一个节点 node3 上。

让我们尝试从node3访问网页服务几次,来检查计数值:

当前计数器的值为6,并存储在 NFS 中。

现在,让我们重启node3。从以下输出中可以看到,只剩下两台机器:

让我们看看正在运行的服务。从以下输出可以看出,redis.service已从node3移至node2

现在,让我们检查一下 node2 上的 Web 访问计数。正如我们从以下输出中看到的,计数从 7 开始,因为之前的计数在 node3 上设置为 6。这证明了容器数据已被持久化:

注意

注意:此示例不具备实际应用性,因为有多个独立运行的 Web 服务器实例。在一个更实际的示例中,负载均衡器将作为前端。此示例的目的是仅为了说明如何使用 NFS 实现容器数据持久化。

Docker 1.9 更新

Docker 1.9 添加了命名卷,这使得卷成为 Docker 中的一级公民。Docker 卷可以使用 docker volume 进行管理。

以下截图展示了 docker volume 的选项:

命名卷取代了之前用于跨容器共享卷的数据-only 容器。

以下一组命令展示了使用命名卷替代数据-only 容器的相同示例:

docker volume create --name redisvolume docker run -d --name redis1 -v redisvolume:/data redis docker run -d --name hello1 --link redis1:redis -p 5000:5000 smakam/hellocounter python app.py

在前面的示例中,我们创建了一个名为 redisvolume 的命名卷,该卷用于 redis1 容器。hellocounter 应用程序链接到 redis1 容器。

以下截图展示了 redis1 卷的信息:

使用命名卷的另一个优势是,我们不再需要担心之前存在的悬空卷问题。

摘要

在本章中,我们介绍了 CoreOS 系统中存储容器镜像和容器数据的不同存储选项。通过实际示例展示了 Flocker、GlusterFS、NFS 和 Docker 卷等技术以及它们与容器和 CoreOS 的集成。容器存储技术仍在发展中,并且需要一些时间才能成熟。业界普遍趋势是从昂贵的 SAN 技术转向本地和分布式存储。在下一章中,我们将讨论容器运行时 Docker 和 Rkt,以及它们如何与 CoreOS 集成。

参考文献

进一步阅读和教程

第八章

第七章:容器与 CoreOS 的集成 —— Docker 和 Rkt

容器彻底改变了应用程序开发和部署,是目前计算机行业的最大趋势。我们在本书的几乎所有章节中都提到了容器。在这一章中,我们将专注于容器标准、高级 Docker 话题、Rkt 容器运行时的基础以及这些主题如何与 CoreOS 集成。由于 Docker 已经相当成熟,我们在本章中只涉及了高级 Docker 话题。由于 Rkt 容器运行时仍在发展中,我们在本章中介绍了 Rkt 的基础内容。尽管 Docker 最初作为容器运行时起步,但它已经发展成一个容器平台,提供围绕容器的编排、网络、存储和安全解决方案。

本章将涵盖以下主题:

  • 容器标准——应用容器(appc)规范、开放容器倡议(OCI)、Libnetwork、容器网络接口(CNI)和云原生计算基金会(CNCF)

  • Docker 守护进程配置、Docker 注册表、Docker 镜像签名和基础 Docker 调试

  • Rkt 基础知识以及如何与镜像签名、systemd 和 Flannel 一起使用 Rkt

容器标准

标准是任何技术的重要组成部分。标准和规范允许来自不同供应商的产品和技术互相兼容。由于容器领域在过去 1-2 年的发展非常快速,标准和规范的关注度相对较少。近年来,行业一直在为容器运行时、容器网络和容器编排制定标准。在大多数情况下,规范发布时会附带运行时实现,这有助于更快地采用标准。以下是本节中涵盖的标准类别:

  • 容器镜像与运行时:APPC 和 OCI

  • 容器网络:Libnetwork 和 CNI

  • 容器编排:CNCF

应用容器规范

APPC 规范为你提供了一个标准,用于描述容器镜像格式、容器镜像发现、容器编组或 Pods,以及容器执行环境。实现 APPC 规范的不同容器运行时将能够互操作。APPC 规范主要由 CoreOS 和其他一些社区成员推动。Rkt、Kurma 和 Jetpack 是实现 APPC 的容器运行时的例子。以下是 APPC 中一些重要的组件。

容器镜像格式

这描述了容器镜像的布局、包含镜像详细信息的清单文件以及镜像签名。

应用容器镜像(ACI)是按照 APPC 规范创建的容器镜像。例如,nginx.aci 镜像是 nginx 容器的 ACI。为了理解容器镜像格式,让我们看看 nginx.aci APPC 镜像中包含的内容。以下命令将 nginx.aci 镜像的内容提取到 nginx 目录:(我们从稍后章节中将介绍的 docker2aci 工具获得了 nginx.aci 镜像。)

tar -xvf nginx.aci -C nginx

以下一组截图展示了 nginx.aci 镜像的基础布局和 rootfs 布局:

以下是 nginx.aci 清单中的一些重要部分。第一张截图显示了容器的名称和版本。第二张截图描述了暴露的端口、挂载点、环境变量等:

从上述截图中,我们可以看到 nginx ACI 镜像暴露了 80443 端口,并且它有挂载点 /var/cache/nginx。容器镜像的签名使用 GPG 完成(www.gnupg.org/)。GPG 是一种公钥加密实现,可用于消息加密以及使用公钥和私钥对进行镜像签名。

容器镜像发现

容器镜像发现描述了如何根据镜像名称查找容器镜像的位置。容器镜像使用 URL 格式。容器镜像发现描述了如何根据镜像名称查找容器镜像的位置。以下是使用的镜像格式:

https://{name}-{version}-{os}-{arch}.{ext}

简单发现

在这里,提到了完整的 URL 来检索 ACI 镜像。示例如下:

github.com/coreos/etcd/releases/download/v2.0.0/etcd-v2.0.0-linux-amd64.aci

Meta 发现

在这里,图像 URL 和公钥是通过使用嵌入在 HTTP 位置中的 meta 标签自动发现的。以下示例展示了如何从 meta 标签中检索 meta 标签和 ACI 图像,用于 CoreOS etcd 容器镜像。

第一步是检索 meta 标签。https://coreos.com/etcd 位置包含 ac-discovery meta 标签,该标签包含镜像位置,还有 ac-discovery-pubkeys meta 标签,其中包含公钥。

链接 coreos.com/etcd/ 包含以下可以作为 HTTP 请求检索的 meta 标签:

<meta name="ac-discovery" content="coreos.com/etcd https://github.com/coreos/etcd/releases/download/{version}/etcd-{version}-{os}-{arch}.{ext}"> <meta name="ac-discovery-pubkeys" content="coreos.com/etcd https://coreos.com/dist/pubkeys/aci-pubkeys.gpg">

使用上述 meta 标签内容,可以从以下位置检索容器镜像:

https://github.com/coreos/etcd/releases/download/{version}/etcd-{version}-{os}-{arch}.{ext}

可以从以下位置获取公钥:

https://coreos.com/dist/pubkeys/aci-pubkeys.gpg

应用容器执行器

应用容器执行器负责以下操作,以设置容器的运行时环境:

  • UUID 设置:这是包含多个容器的 Pod 的唯一 ID。UUID 会在元数据服务中注册,允许其他容器找到彼此。

  • 文件系统设置:在其自己的命名空间中创建文件系统。

  • 卷设置:这些是要挂载到容器的文件。

  • 网络设置:指定容器与主机及其他容器的网络连接。

  • 隔离器:用于控制容器的 CPU 和内存限制。

应用容器 Pod

Pods 的概念来自 Kubernetes,其中相关的容器被打包在一个 Pod 中。Pod 中的容器共享进程 PID、网络和 IPC 命名空间。除了单独的容器之外,还可以为 Pod 创建清单,以描述 Pod 的属性。

应用容器元数据服务

应用容器元数据服务是一个外部运行的服务,容器 Pod 可以注册关于 Pod 和应用的信息。Pod 可以使用此元数据服务查找其他 Pod 的信息,Pod 中的容器也可以通过此服务查找其他容器的信息。

APPC 工具

APPC 为您提供了创建、验证和转换 ACI 镜像的工具。

Actool

使用 Actool 进行 ACI 验证:

以下输出显示生成的 ACI 镜像busybox-latest.aci是一个有效的 APPC 镜像:

使用 Actool 进行 ACI 发现:

以下输出显示了 ACI 镜像的发现 URL 和公钥:

使用 Actool 检查清单:

以下输出显示了如何查看 ACI 镜像的清单:

Acbuild

Acbuild 工具用于构建 ACI 镜像。其概念类似于使用 Dockerfile 构建 Docker 容器镜像的方法,但 Acbuild 通过更好的与 Linux 工具(如 makefile、环境变量等)集成,提供了更大的灵活性来构建容器镜像。

以下是从 GO 可执行文件hello构建容器镜像的示例。在运行以下命令之前,我们需要静态链接当前目录中的hello可执行文件:

acbuild begin``acbuild set-name example.com/hello``acbuild copy hello /bin/hello``acbuild set-exec /bin/hello``acbuild port add www tcp 5000``acbuild label add version 0.0.1``acbuild annotation add authors "Sreenivas Makam<sxxxm@yahoo.com>"``acbuild write hello-0.0.1-linux-amd64.aci``acbuild end

如果我们运行前面的命令,它将创建一个 APPC 镜像hello-0.0.1-linux-amd64.aci,我们可以使用 Rkt 容器运行时运行该镜像。

以下是另一个示例,它类似于 Dockerfile 方法来构建 ACI 镜像。在这个示例中,我们以基础的 Ubuntu 镜像为基础,安装 Apache,并在容器中启动 Apache,以创建ubuntu-nginx.aci镜像:

acbuild begin``acbuild dependency add quay.io/fermayo/ubuntu``acbuild run -- apt-get update``acbuild run -- apt-get -y install nginx``acbuild set-exec -- /usr/sbin/nginx –g "daemon=off;"``acbuild set-name example.com/ubuntu-nginx``acbuild write ubuntu-nginx.aci``acbuild end

要运行 acbuild,系统中需要有 systemd-nspawn。CoreOS 节点默认包含此功能。以下是通过前面脚本创建的 APPC 镜像:

让我们使用 Rkt 启动容器:

以下是正在运行的容器状态:

Docker2aci

Docker2aci 工具用于将 Docker 容器转换为 ACI 格式。以下是一个示例,将 docker busybox容器转换为busybox.aci镜像:

开放容器倡议

OCI 是开放容器倡议(Open Container Initiative)开源项目,由 Docker 于 2015 年 4 月启动,并且有来自所有主要公司的成员,包括 Docker 和 CoreOS。OCI 定义了以下内容:

  • 容器镜像格式:这描述了文件系统包,以及config.json,它描述了容器的主机无关属性,以及runtime.json,它描述了容器的主机相关属性。

  • 运行时:这描述了如何使用命名空间和 cgroups 启动和停止容器。

Docker 的目标是遵循 OCI 规范来实现其容器运行时。

Runc

Runc 是 OCI 规范的实现。Docker 引擎使用 runc 在 Docker 中实现容器运行时。可以按照github.com/opencontainers/runc中的过程安装 runc。

以下过程可用于使用runc启动 Ubuntu 容器:

Docker pull Ubuntu``docker export $(docker create ubuntu) > ubuntu.tar``mkdir rootfs``tar -C rootfs -xf ubuntu.tar``runc spec

第一步拉取 Ubuntu Docker 容器。第二步将 Ubuntu 容器导出为文件系统。第三步和第四步将 Ubuntu 文件系统内容放入rootfs目录。最后一步生成config.jsonruntime.json

以下输出展示了使用runc启动的 Ubuntu 容器:

OCI 与 APPC 的关系

CoreOS 和一些其他社区成员创建了 APPC 规范,以标准化容器镜像格式,使容器在不同的实现之间互操作。

CoreOS 提出的原始 APPC 容器规范涵盖了容器管理的四个不同元素:打包、签名、命名(与他人共享容器)和运行时。Docker 也感受到互操作性的需求,并与包括 CoreOS 在内的其他社区成员共同创建了 OCI。当前,OCI 主要专注于打包和运行时,尽管未来可能会有所变化。尽管具体细节略有不同,APPC 和 OCI 的目标是相同的。这两个标准可能会在未来某个时刻合并为一个标准。

OCI 和 APPC 最新更新

根据 CoreOS 最新的博客更新(coreos.com/blog/making-sense-of-standards.html),APPC 和 OCI 仅在运行时相交,APPC 将继续关注镜像格式、签名和分发。

Libnetwork

Libnetwork 在第五章中简要介绍过,内容涉及 CoreOS 网络和 Flannel 内部结构。Libnetwork 是由 Docker 和其他一些社区成员发起的开源项目,目标如下:

  • 将网络作为库与容器运行时分离。

  • 提供同一主机以及跨主机的容器连接。

  • 网络实现将作为由驱动程序实现的插件进行。提供插件机制以便轻松添加新的第三方驱动程序。

  • 使用本地 IPAM 驱动程序和插件控制容器的 IP 地址分配。

Docker 使用 Libnetwork 提供容器网络。

Libnetwork 中有三个主要组件:

  • 沙箱:所有网络功能都封装在沙箱中。可以使用网络命名空间或类似功能来实现。

  • 端点:将沙箱附加到网络。

  • 网络:同一网络中的多个端点可以相互通信。

以下图显示了沙箱、端点和网络,以及两个容器如何通过这些结构相互通信:

Libnetwork 支持本地驱动程序,如 null、bridge 和 overlay。bridge 驱动程序可用于单一主机中的容器连接,overlay 驱动程序可用于跨主机的容器连接。也支持远程驱动程序,如 Weave 和 Calico。

CNI

CNI 在第五章中简要介绍过,内容涉及 CoreOS 网络和 Flannel 内部结构。

CNI 是由 CoreOS 和其他一些社区成员开发的开源容器网络接口项目,为容器提供作为可插拔和可扩展机制的网络功能。CoreOS 的容器运行时 Rkt 使用 CNI 来建立容器网络。Libnetwork 和 CNI 的目标几乎相同。

以下是关于 CNI 的一些说明:

  • CNI 接口调用 CNI 插件的 API 来设置容器网络。

  • CNI 插件负责为容器创建网络接口。

  • CNI 插件调用 IPAM 插件来为容器设置 IP 地址。

  • CNI 插件需要实现一个 API,用于容器网络的创建和删除。

  • 插件类型和参数被指定为一个 JSON 文件,容器运行时读取并设置该文件。

  • 可用的 CNI 插件包括桥接、macvlan、ipvlan 和 ptp。可用的 IPAM 插件包括 host-local 和 DHCP。CNI 插件和 IPAM 插件可以任意组合使用。

  • 也支持外部 CNI 插件,如 Flannel 和 Weave。外部插件重用桥接插件来设置最终的网络。

  • 以下是一个示例 JSON 配置,包含桥接 CNI 插件和主机本地 IPAM 插件,以及 IP 分配范围:

    {     "name": "mynet",     "type": "bridge",     "bridge": "mynet0",     "isGateway": true,     "ipMasq": true,     "ipam": {         "type": "host-local",         "subnet": "10.10.0.0/16"     } }

  • 以下是一个示例 JSON 配置,使用了 Flannel CNI 类型:

    {         "name": "containernet",         "type": "flannel" }

下图展示了 Rkt、CNI、CNI 插件和 IPAM 插件之间的关系:

Libnetwork 与 CNI 之间的关系

Libnetwork 和 CNI 的目标相似。Docker 使用 Libnetwork,而 CoreOS 使用 Rkt 并使用 CNI。Libnetwork 的覆盖驱动程序与 CNI 的 Flannel 驱动程序执行类似的操作。像 Weave 和 Calico 这样的外部插件的目标是与 Libnetwork 和 CNI 一起工作。

云原生计算基金会

CNCF 的目标是简化使用容器构建云原生应用的过程。CNCF 将使用容器周围的最佳开源技术,为基于微服务的分布式应用创建参考架构。CNCF 的初始目标是容器编排,集成工作重点放在 Kubernetes 与 Mesos 上。CNCF 将为微服务开发创建参考架构,帮助企业基于参考架构进行构建,而不是自行集成组件。根据最新的 CoreOS 博客(coreos.com/blog/making-sense-of-standards.html),CoreOS 将把 etcd、flannel 和 appc 捐赠给 CNCF。

Docker

尽管容器技术已经存在很长时间,Docker 通过使容器的创建和传输变得非常用户友好,彻底革新了容器技术。除了提供容器运行时,Docker 还为容器提供网络、存储和编排解决方案。对于这些解决方案中的大多数,Docker 提供了一个可插拔的模型,提供 Docker 原生解决方案,并且可以与任何第三方解决方案交换。这为客户提供了灵活性,允许他们使用自己已经熟悉的技术。

在第一章,CoreOS 概述中,我们讲解了 Docker 架构。由于 Docker 技术已经相当成熟,本章将只讲解高级 Docker 概念。

Docker 守护进程和外部连接

Docker 作为守护进程运行,默认情况下监听 Unix 套接字unix:///var/run/docker.sock。Docker 启动选项在/etc/default/docker中指定。

为了允许外部 Docker 客户端与 Docker 守护进程通信,需要在 Ubuntu 节点中执行以下步骤:

  1. 添加具有本地地址和端口号的 TCP 服务器:

    DOCKER_OPTS="-D -H unix:///var/run/docker.sock -H tcp://192.168.56.101:2376"

  2. 重启 Docker 守护进程:

    Sudo service docker restart

  3. 现在,我们可以看到 Docker 守护进程正在通过 IP 地址192.168.56.101和 TCP 端口号2376暴露外部连接:

我们可以按照以下方式从外部 Docker 客户端连接:

docker -H tcp://192.168.56.101:2376 ps

以下图片显示了 Apache 容器正在运行:

Dockerfile

Dockerfile 用于通过 Dockerfile 中的指定指令创建 Docker 容器镜像。通常,Dockerfile 以基础容器镜像开始,安装必要的应用程序,然后启动与容器相关的进程。

关于 Dockerfile 的最佳实践,您可以参考以下链接:

docs.docker.com/engine/articles/dockerfile_best-practices/

以下是一个示例 Dockerfile,用于从 Ubuntu 基础镜像创建一个 Apache 容器。此 Dockerfile 安装 Apache 包并将80端口暴露给外部世界:

FROM ubuntu:14.04 MAINTAINER Sreenivas Makam <sxxxm@yahoo.com> # 更新 RUN apt-get update # 安装 apache2 RUN apt-get install -y apache2 # 暴露必要端口 EXPOSE 80 # 启动应用程序 ENTRYPOINT ["/usr/sbin/apache2ctl"] CMD ["-D", "FOREGROUND"]

要创建 Docker 镜像,在包含上述 Dockerfile 的目录中执行以下命令。以下示例中,smakam/apache1是容器镜像的名称。容器镜像名称的默认约定是用户名/镜像名称:标签

docker build -t smakam/apache1 .

以下截图展示了创建的 Apache 容器镜像:

Docker 镜像仓库

Docker 镜像仓库用于从公共服务器位置保存和恢复 Docker 容器镜像。Docker 为存储容器镜像提供了三种可能的解决方案:

  • Docker Hub:这是由 Docker 公司提供的 Docker 镜像仓库服务,网址为hub.docker.com/。这是一个由 Docker 免费提供的服务。

  • Docker 注册表:这是一个开源项目(github.com/docker/distribution),允许客户在自己的本地环境中托管 Docker 注册表。最新的 Docker 注册表是版本 2.0。Docker 注册表 2.0 克服了 Docker 注册表 1.x 的一些不足,提供了更好的安全性和性能。

  • Docker Trusted Registry:这是 Docker 的商业实现(www.docker.com/docker-trusted-registry),增加了角色基于的用户身份验证、与 LDAP 等外部目录服务的集成、基于 GUI 的管理管理、技术支持等功能。Docker 注册表和 Docker Trusted Registry 都支持与 AWS、Azure 和 Swift 等外部存储驱动程序集成,以存储 Docker 镜像。

以下图展示了三种 Docker 镜像仓库类型:

Docker 镜像格式如下:

[REGISTRYHOST/][USERNAME/]NAME[:TAG]

  • REGISTRYHOST:注册表服务器地址

  • USERNAME:创建镜像的用户名

  • NAME:容器镜像的名称

  • TAG:容器镜像的版本

  • 除了 NAME 外,其他参数是可选的

例如,下面的命令将从 Docker Hub 拉取一个标准的 Ubuntu 容器镜像;registry-1.docker.io/library 是注册表主机,镜像名为 Ubuntu,标签为 latest

docker pull registry-1.docker.io/library/Ubuntu:latest

类似于 Docker 注册表,CoreOS 也有 Quay 注册表(quay.io/),用于存储 Docker 和 Rkt 镜像,它们提供了公共版和企业版。

创建你自己的 Docker 注册表

创建本地注册表以在特定公司或团队内部共享镜像是非常有用的。从安全角度来看,这一点尤其重要,因为这样就无需访问互联网即可访问注册表。Docker 注册表为你提供了身份验证、后端存储驱动程序(例如,S3、Azure 和 Swift)、日志记录等功能。

要启动本地注册表,使用以下命令:

docker run -d -p 5000:5000 --restart=always --name registry registry:2

以下截图展示了作为容器运行的注册表。注册表服务通过本地 5000 端口暴露:

注册表配置可以通过环境变量的方式在启动注册表容器时指定,或者使用包含配置的 YAML 文件并将该 YAML 文件挂载到容器中的 /etc/config/registry/config.yaml

以下命令组从 Docker Hub 拉取一个 busybox 容器,推送 busybox 容器到本地注册表,然后从本地注册表拉取它:

docker pull busybox``docker tag busybox localhost:5000/mybusybox``docker push localhost:5000/mybusybox``docker pull localhost:5000/mybusybox

以下截图显示了已从本地注册表拉取的mybusybox容器:

以下截图显示了从本地注册表实例化mybusybox容器的过程:

持续集成

当我们将 Docker 镜像推送到 Docker Hub 时,Dockerfile 不会被推送。为了将 Dockerfile 推送并用于自动化容器构建,我们需要将其与一个仓库管理工具(如 GitHub 或 Bitbucket)关联。具体步骤如下:

  1. 在 GitHub 或 Bitbucket 中创建一个账户。

  2. 从 Docker Hub,我们可以链接到 GitHub 或 Bitbucket。

  3. 将 Dockerfile 推送到 GitHub。

  4. 从 Docker Hub,当我们创建一个仓库时,选择自动构建,并选择 GitHub 中 Dockerfile 所在的位置。这将自动构建镜像。此外,当 Dockerfile 在 GitHub 上有变更并提交时,自动构建会被触发。

以下图表展示了使用 Dockerfile 的 CI 流程,从暂存到生产:

以下截图显示了在 GitHub 中创建 Dockerfile 后,Docker Hub 中的自动化构建创建过程。在以下示例中,Dockerfile 位于github.com/smakam/docker.git的 Apache 目录下:

当有任何更改时,自动构建容器镜像。以下截图显示了smakam/apacheauto的成功容器镜像构建日志:

以下截图显示了成功拉取smakam/apacheauto容器镜像:

Docker 内容信任

Docker 内容信任为您提供了一种机制,用于签署和发布 Docker 镜像,以确保拉取镜像的客户端能够确认该镜像来自受信任的来源,并且没有被中间人攻击篡改。以下是 Docker 内容信任的一些特性:

  • Docker 内容信任是 Notary 开源项目的实现(github.com/docker/notary)。Notary 开源项目基于 The Update Framework(TUF)项目(theupdateframework.github.io/)。TUF 提供了一种机制来保障软件更新的安全性。

  • 与 GPG 签名密钥的方法相比,TUF 具有一些独特的区分点。TUF 负责密钥的新鲜度,以确保客户端始终知道他们获得的是最新的内容。使用 TUF 的密钥轮换机制,可以更好地处理密钥泄露问题,客户端无需了解此过程。TUF 还为您提供了签署集合而不是单独软件的能力。

  • Notary 有四个密钥——Timestamp 密钥用于保持镜像的新鲜度,Snapshot 密钥用于签署镜像集合,Target 密钥用于定期签署镜像,Offline 密钥用于密钥轮换。

  • Docker 内容信任已随 Docker 版本 1.8 发布。默认选项为禁用信任,可以通过DOCKER_CONTENT_TRUST环境变量启用。在某个时刻,默认选项将变为保持启用信任。

以下图显示了 TUF、Notary 和 Docker 内容信任之间的关系:

以下是启用 Docker 内容信任的工作流程:

  • Docker 注册表需要支持 Docker 内容信任。Docker Hub 支持内容信任。Docker 受信任注册表和私有注册表尚不支持内容信任;此功能将在不久的将来添加。

  • 可以使用常规的 Docker 命令进行推送和拉取,并且特别注意不更改 Docker 命令。对于高级密钥管理,可以使用 Notary CLI。

  • 当发布者第一次使用docker push推送镜像时,需要输入根密钥和标记密钥的密码短语。其他所有密钥会自动生成。这些密钥需要安全存储。

  • 对于任何进一步的镜像发布,只需要标记密钥。

  • 客户端可以选择拉取签名或未签名的镜像。启用 Docker 信任时,如果尝试拉取未签名的镜像,客户端会收到错误提示。

推送安全镜像

首先,我们通过DOCKER_CONTENT_TRUST环境变量启用 Docker 内容信任。以下是启用 Docker 内容信任并首次发布镜像时的输出。这里,我们正在推送签名的smakam/mybusybox:v1容器:

拉取安全镜像

以下是我们从 Docker Hub 拉取相同安全镜像smakam/mybusybox:v1时的输出:

在没有安全性的情况下拉取相同的镜像

以下是我们尝试在没有 Docker 内容信任的情况下拉取相同镜像smakam/mybusybox:v1时的输出。在这种情况下,不会进行镜像验证:

以下是如果我们启用信任并尝试拉取未签名的 Docker 镜像时会得到的错误消息。由于smakam/hellocounter没有签名,并且我们已启用DOCKER_CONTENT_TRUST,因此会出现错误:

最近,Docker 启用了使用硬件密钥的内容信任(blog.docker.com/2015/11/docker-content-trust-yubikey/)。目前处于实验模式。

容器调试

以下是一些基本的容器调试方法。

日志

以下命令将显示容器日志。这是一个有用的调试工具。在第十章《CoreOS 与容器 - 故障排除与调试》中,您将学习如何从中央位置汇总和分析容器日志。

docker logs <containername or id>

登录到容器内

docker exec命令可以用于登录到容器。以下是一个示例:

可以从容器 shell 中执行常见的 Linux 命令。

容器属性

以下命令将显示容器属性,如挂载点、资源限制等:

docker inspect <containername or id>

容器进程

以下命令将显示容器中按 CPU 使用率排序的进程:

docker top <containername or id>

以下是redis容器的示例输出:

容器的 CPU 和内存使用情况

以下命令将显示容器的资源使用情况:

docker stats <containername or id>

以下是 Apache 容器的示例输出:

Rkt

Rkt 是 CoreOS 基于 APPC 规范的容器运行时。以下是与 Docker 相比 Rkt 的一些区别:

  • Rkt 是无守护进程的。容器在 Docker 守护进程重启时消失的问题在 Rkt 中不存在。

  • Rkt 与 systemd 集成良好,因此容器的资源限制可以轻松设置。

Rkt 执行有三个阶段:

  • Stage0:此阶段进行镜像发现和检索,并为阶段 1 和 2 设置文件系统。

  • Stage1:此阶段使用由stage0设置的文件系统来设置容器执行环境。Rkt 在此阶段使用 systemd-nspawn 来设置 cgroups、网络等。目标是使stage1可以被其他实现替换。

  • Stage2:这是实际执行容器 Pod 和应用程序本身,使用由stage1设置的执行环境和由stage0设置的文件系统。

以下示例展示了各个阶段。让我们使用 Rkt 启动 hello ACI 镜像:

sudo rkt --insecure-skip-verify run hello-0.0.1-linux-amd64.aci

以下展示了由stage0设置的stage1文件系统:

这里的清单展示了设置容器环境的 Rkt stage1 ACI:

以下展示了stage2文件系统:

这里的清单展示了 hello Rkt 容器镜像:

以下展示了 hello 应用程序的文件系统:

Rkt 应用程序在 CoreOS 基础镜像中可用。也可以按照 github.com/coreos/rkt 中的步骤在任何 Linux 系统上安装 Rkt。以下是在 Ubuntu 14.04 系统中运行的 Rkt 版本:

以下是 CoreOS alpha 镜像 815.0.0 中使用的 Rkt 和 APPC 版本:

基本命令

以下是一些基本命令,用于操作 Rkt 容器。

获取镜像

以下命令从仓库中以 ACI 格式获取容器镜像:

sudo rkt --insecure-skip-verify fetch docker://busybox

列出镜像

以下命令列出 Rkt 容器镜像:

sudo rkt image list

运行镜像

以下命令运行 Rkt 容器镜像:

sudo rkt run --insecure-skip-verify --interactive docker://busybox

默认情况下,签名验证是启用的;我们使用 skip-verify 选项禁用签名验证:

列出 pods

以下命令列出正在运行的 pods:

sudo rkt list pods

垃圾回收

以下截图展示了两个已退出的 Pod:

已退出的容器将定期进行垃圾回收。要强制进行垃圾回收,我们可以执行以下命令:

rkt gc --grace-period=0

现在,我们可以看到没有活动的 Pod:

删除镜像

以下命令删除本地的容器镜像:

sudo rkt image rm sha512-cf74c26d8d35555066dce70bd94f513b90cbef6e7e9c01ea0c971f4f6d689848

以下截图展示了如何使用 UUID 删除 busybox 镜像:

导出镜像

以下命令将 Docker 镜像转换为 ACI 格式:

sudo rkt image export nginx nginx.aci

含有卷挂载和端口转发的 nginx 容器

以下命令启动 nginx 容器,将容器端口 80 转发到主机端口 8080 并设置主机卷。卷目录和端口名称如清单文件中所指定:

sudo rkt run --insecure-skip-verify --private-net --port=80-tcp:8080 --volume volume-var-cache-nginx,kind=host,source=/home/core docker://nginx

以下截图展示了通过 nginx 容器和主机端口 8080 成功访问网页:

Pod 状态

以下命令列出特定 Pod 的状态,使用 UUID:

sudo rkt status 2b165196

Rkt 镜像签名

容器镜像签名允许我们验证镜像是否来自受信任的来源,并且没有被篡改。我使用了 github.com/coreos/rkt/blob/master/Documentation/signing-and-verification-guide.md 中的过程来签名 ACI 镜像,并使用 Rkt 运行签名镜像。

以下是一个示例 nginx.service systemd 单元文件:

  1. 生成密钥:

    gpg --batch --gen-key gpg-batch

    注意

    如果你看到这个错误信息,Not enough random bytes available. Please do some other work to give the OS a chance to collect more entropy!,可以通过以下的 rngd 工具来解决,该工具可以并行运行:

    apt-get install rng-tools``sudo rngd -r /dev/urandom

  2. 信任密钥:

    gpg --no-default-keyring --secret-keyring ./rkt.sec --keyring ./rkt.pub --edit-key 1FEEF0ED trust

  3. 导出公钥:

    gpg --no-default-keyring --armor \``--secret-keyring ./rkt.sec --keyring ./rkt.pub \``--export <email> > pubkeys.gpg

  4. 使用公钥签名镜像:

    gpg --no-default-keyring --armor \``--secret-keyring ./rkt.sec --keyring ./rkt.pub \``--output hello-0.0.1-linux-amd64.aci.asc \``--detach-sig hello-0.0.1-linux-amd64.aci

  5. 使用 ACI 镜像、公共密钥和签名托管 web 服务器。以下是我 web 服务器位置中的内容:

  6. 以下是 index.html 的内容:

    <head> <meta name="ac-discovery" content="example.com/hello http://example.com/hello-0.0.1-linux-amd64.aci"> <meta name="ac-discovery-pubkeys" content="example.com/hello http://example.com/pubkeys.gpg"> </head>

  7. 信任 web 服务器位置和密钥:

    sudo rkt trust --prefix=example.com/hello http://example.com/pubkeys.gpg --insecure-allow-http

  8. 修改 /etc/hostsexample.com 指向 localhost。

  9. 启动一个简单的 web 服务器:

    sudo python -m SimpleHTTPServer 80

现在,我们可以运行带有签名验证的 Rkt 镜像:

sudo rkt run --debug http://example.com/hello-0.0.1-linux-amd64.aci

以下截图展示了签名正在被验证。签名位置和公钥由托管的 web 服务器 example.com 提供:

Rkt 与 systemd

Systemd 为你提供了对进程管理的强大控制。Rkt pods 可以由 systemd 管理。通过 systemd,我们可以控制进程执行顺序、重启能力、资源限制等。

以下是一个 nginx.service systemd 单元文件的示例:

[Unit] Description=nginx [Service] # 资源限制 CPUShares=512 MemoryLimit=1G # 预取镜像 ExecStartPre=/usr/bin/rkt fetch --insecure-skip-verify docker://nginx ExecStart=/usr/bin/rkt run --insecure-skip-verify --private-net --port=80-tcp:8080 --volume volume-var-cache-nginx,kind=host,source=/home/core docker://nginx KillMode=mixed Restart=always

在之前的服务文件中,我们启动了 nginx 容器,并使用 systemd 构造限制了该 nginx.service 的 CPU 和内存使用。

要启动服务,必须将 nginx.service 放置在 /etc/systemd/system 目录中。可以通过以下命令启动服务:

Sudo systemctl start nginx.service

以下截图展示了 nginx.service 的状态:

为了展示与 systemd 集成的强大功能,让我们终止 Rkt nginx 进程并演示重启功能:

因为在 nginx.service 中启用了 restart,所以 systemd 会重新启动 nginx 容器。

从以下 journalctl 日志中,我们可以看到服务已被重新启动:

在以下截图中,我们可以看到 Rkt nginx 进程正在以不同的 PID 运行:

Rkt 与 Flannel

Rkt 使用 CNI 接口与 Flannel 插件进行通信,跨主机建立容器网络。

以下示例使用 Rkt 和 Flannel 配置一个三节点的 CoreOS 集群,用于容器网络。以下是必要的 cloud-config

#cloud-config coreos:   etcd2:     #为每个唯一集群从 https://discovery.etcd.io/new 生成一个新的令牌     discovery: <your token>     # 多区域和多云部署需要使用$public_ipv4     advertise-client-urls: http://$public_ipv4:2379     initial-advertise-peer-urls: http://$private_ipv4:2380     # 监听官方端口和传统端口     # 如果您的应用程序不依赖于传统端口,可以省略     listen-client-urls: http://0.0.0.0:2379,http://0.0.0.0:4001     listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001   fleet:     public-ip: $public_ipv4   flannel:     interface: $public_ipv4   units:     - name: etcd2.service       command: start     - name: fleet.service       command: start     - name: flanneld.service       drop-ins:         - name: 50-network-config.conf           content: |             [Service]             ExecStartPre=/usr/bin/etcdctl set /coreos.com/network/config '{ "network": "10.1.0.0/16" }'       command: start     - name: docker-tcp.socket       command: start       enable: true       content: |         [Unit]         Description=Docker Socket for the API         [Socket]         ListenStream=2375         Service=docker.service         BindIPv6Only=both         [Install]         WantedBy=sockets.target write_files:   - path: "/etc/rkt/net.d/10-containernet.conf"     permissions: "0644"     owner: "root"     content: |       {         "name": "containernet",         "type": "flannel"       }

/etc/rkt/net.d/10-containernet.conf文件将 CNI 插件类型设置为 Flannel。

Flannel 为每个主机分配了一个独立的子网,使用的是在 flannel 配置中指定的 IP 范围10.1.0.0/16

以下输出显示了在node1node2中分配的子网:

让我们在每个节点上创建 Rkt 容器,并检查容器间的连接性:

以下输出显示了core-01上的 busybox 容器获得了 IP 地址10.1.74.4,该 IP 地址位于为core-01分配的10.1.74.1/24范围内:

以下输出显示了core-03上的 busybox 容器获得了 IP 地址10.1.3.2,该 IP 地址位于为core-03分配的10.1.3.1/24范围内:

以下输出显示了从core-01上的容器 1 成功 ping 通core-03上的容器 2:

总结

在本章中,我们介绍了不同的容器标准,包括容器运行时、网络和编排。从行业角度来看,拥有这些标准对于互操作性非常重要。我们详细讨论了 Docker 和 Rkt 这两种容器运行时系统。对于 Docker,我们关注了高级概念,而对于 Rkt,我们介绍了基础知识,因为 Rkt 仍处于早期阶段。尽管 CoreOS 正在积极开发 Rkt,但 CoreOS 承诺在其操作系统中支持 Docker。接下来,Docker 和 Rkt 如何在 CoreOS 中一起运行以及客户如何采纳这两种容器运行时技术将是非常有趣的。在下一章中,我们将介绍容器编排。

参考资料

进一步阅读和教程

第九章

第八章 容器编排

随着容器成为现代应用开发和部署的基础,部署数百个或数千个容器到单一数据中心集群或数据中心集群变得必要。集群可以是本地集群或云端集群。为了在大规模下部署和管理容器,必须有一个良好的容器编排系统。

本章将覆盖以下主题:

  • 现代应用部署的基础

  • 使用 Kubernetes、Docker Swarm 和 Mesos 进行容器编排及其核心概念、安装和部署

  • 流行的编排解决方案比较

  • 使用 Docker Compose 进行应用定义

  • 打包的容器编排解决方案——AWS 容器服务、Google 容器引擎和 CoreOS Tectonic

现代应用部署

我们在第一章中介绍了微服务的基础,CoreOS 概述。在基于云的应用开发中,基础设施被视为“牲畜”而非“宠物”(www.slideshare.net/randybias/pets-vs-cattle-the-elastic-cloud-story)。这意味着基础设施通常是商品化的硬件,可能会出现故障,高可用性需要在应用层或应用编排层进行处理。高可用性可以通过负载均衡器和编排系统的组合来实现,这些系统监控服务的健康状况,采取必要的行动,例如在服务崩溃时重新启动服务。容器具有隔离和封装的优良特性,使得独立的团队能够作为容器开发各自的组件。

企业可以采用按需增长模式,根据需要扩展容器。在大规模管理数百或数千个容器时,必须使用容器编排系统。以下是容器编排系统的一些特点:

  • 它将不同的基础设施硬件视为一个集合,并将其表示为应用程序的单一资源

  • 它根据用户约束调度容器,并以最有效的方式利用基础设施

  • 它动态地扩展容器

  • 它保持服务的高可用性

应用程序定义与容器编排之间有着密切的关系。应用程序定义通常是一个清单文件,描述了应用程序中的容器以及容器暴露的服务。容器编排是基于应用程序定义进行的。容器编排器在资源上运行,这些资源可以是虚拟机或裸机。通常,容器运行的节点安装有容器优化的操作系统,例如 CoreOS、DCOS 和 Atomic。以下图像展示了应用程序定义、容器编排和容器优化节点之间的关系,并给出了每个类别中一些解决方案的示例:

容器编排

容器编排的基本要求是高效地将 M 个容器部署到 N 个计算资源中。

以下是容器编排系统应该解决的一些问题:

  • 它应该高效地调度容器,并为用户提供足够的控制,以便根据需要调整调度参数

  • 它应该提供跨集群的容器网络

  • 服务应该能够动态发现彼此

  • 编排系统应该能够处理服务故障

我们将在接下来的章节中介绍 Kubernetes、Docker Swarm 和 Mesos。Fleet 是 CoreOS 内部用于容器编排的工具。Fleet 功能非常简约,适合在 CoreOS 中部署关键系统服务。对于非常小的部署,如果需要,也可以使用 Fleet 进行容器编排。

Kubernetes

Kubernetes 是一个开源的容器编排平台。最初由谷歌发起,现在多个供应商正在共同参与这个开源项目。谷歌一直使用容器来开发和部署应用程序,且在其内部数据中心有一个叫 Borg 的系统(research.google.com/pubs/pub43438.html)用于集群管理。Kubernetes 借鉴了 Borg 的许多概念,并结合了现有的现代技术。Kubernetes 轻量级,能够在几乎所有环境中运行,目前在行业中有着广泛的应用。

Kubernetes 的概念

Kubernetes 有一些独特的概念,深入了解这些概念会对理解 Kubernetes 的架构非常有帮助。

Pods

Pods 是一组在同一个节点上调度并需要紧密协作的容器。Pod 中的所有容器共享 IPC 命名空间、网络命名空间、UTS 命名空间和 PID 命名空间。通过共享 IPC 命名空间,容器可以使用 IPC 机制相互通信。

通过共享网络命名空间,容器可以使用套接字相互通信,并且 Pod 中的所有容器共享一个 IP 地址。通过共享 UTS 命名空间,卷可以挂载到 Pod 上,所有容器都可以访问这些卷。以下是一些常见的应用部署模式与 Pods 结合使用的例子:

  • Sidecar 模式:一个例子是应用容器和日志容器,或者像 Git 同步器这样的应用同步容器。

  • Ambassador 模式:在这种模式下,应用容器和代理容器一起工作。当应用容器发生变化时,外部服务仍然可以像以前一样与代理容器通信。一个例子是一个带有 redis 代理的 redis 应用容器。

  • Adapter 模式:在这种模式下,存在一个应用容器和适配器容器,适配不同的环境。一个例子是一个日志容器,它作为适配器,随着不同云服务提供商的变化而变化,但适配器容器的接口保持不变。

Kubernetes 中最小的单元是 Pod,Kubernetes 负责调度 Pods。

以下是一个包含 NGINX 容器和 Git 助手容器的 Pod 定义示例:

apiVersion: v1 kind: Pod metadata:   name: www spec:   containers:   - name: nginx     image: nginx   - name: git-monitor     image: kubernetes/git-monitor     env:     - name: GIT_REPO       value: http://github.com/some/repo.git

网络

Kubernetes 采用每个 Pod 一个 IP 的方式。这种方式避免了容器共享主机 IP 地址时,使用 NAT 访问容器服务的痛苦。Pod 中的所有容器共享相同的 IP 地址。跨节点的 Pods 可以使用不同的技术进行通信,例如云提供商的云路由、Flannel、Weave、Calico 等。最终目标是将网络作为 Kubernetes 中的插件,用户可以根据需求选择插件。

服务

服务是 Kubernetes 提供的一种抽象,用于逻辑上组合提供相似功能的 Pods。通常,标签(Labels)作为选择器,用来从 Pods 创建服务。由于 Pods 是短暂的,Kubernetes 会创建一个服务对象,并为其分配一个永远保持不变的 IP 地址。Kubernetes 负责多个 Pod 的负载均衡。

以下是一个示例服务:

{     "kind": "Service",     "apiVersion": "v1",     "metadata": {         "name": "my-service"     },     "spec": {         "selector": {             "app": "MyApp"         },         "ports": [             {                 "protocol": "TCP",                 "port": 80,                 "targetPort": 9376             }         ]     } }

在前面的例子中,我们创建了一个my-service服务,将所有带有Myapp标签的 Pod 组合在一起。任何访问my-service服务的 IP 地址和端口号80的请求都会被负载均衡到所有带有Myapp标签的 Pods,并被重定向到端口号9376

根据服务类型,需要在内部或外部发现服务。内部发现的示例是 Web 服务需要与数据库服务通信。外部发现的示例是将 Web 服务暴露给外部世界。

对于内部服务发现,Kubernetes 提供两种选项:

  • 环境变量:创建新 Pod 时,可以从旧服务导入环境变量。这允许服务之间进行通信。此方法强制执行服务创建中的顺序。

  • DNS:每个服务都注册到 DNS 服务;通过此服务,新服务可以找到并与其他服务通信。Kubernetes 为此提供了kube-dns服务。

对于外部服务发现,Kubernetes 提供两种选项:

  • NodePort:通过此方法,Kubernetes 通过节点 IP 地址的特殊端口(30000-32767)公开服务。

  • 负载均衡器:通过此方法,Kubernetes 与云提供商交互以创建负载均衡器,将流量重定向到 Pod。目前此方法在 GCE 上可用。

Kubernetes 架构

以下图表显示了 Kubernetes 架构的不同软件组件及其相互交互方式:

以下是关于 Kubernetes 架构的一些注释:

  • 主节点托管 Kubernetes 控制服务。从节点运行 Pod 并由主节点管理。可以有多个主节点以实现冗余和扩展主服务。

  • 主节点运行关键服务,例如调度器、复制控制器和 API 服务器。从节点运行关键服务,例如 Kubelet 和 Kube-proxy。

  • 用户与 Kubernetes 的交互通过 Kubectl 进行,它使用标准的 Kubernetes 公开的 API。

  • Kubernetes 调度器负责根据 Pod 清单中指定的约束在节点上调度 Pod。

  • 复制控制器是维护 Pod 的高可用性和根据复制控制器清单创建多个 Pod 实例所必需的。

  • 主节点中的 API 服务器与每个从节点的 Kubelet 进行通信,以部署 Pod。

  • Kube-proxy 负责服务重定向和负载均衡流量到 Pod。

  • Etcd 用作所有节点之间通信的共享数据存储库。

  • DNS 用于服务发现。

Kubernetes 安装

Kubernetes 可以安装在裸金属、虚拟机或云提供商(如 AWS、GCE 和 Azure)上。我们可以根据这些系统中的任何操作系统来决定主机操作系统的选择。在本章中,所有示例都将使用 CoreOS 作为主机操作系统。由于 Kubernetes 由多个组件组成,如 API 服务器、调度器、复制控制器、kubectl 和 kubeproxy,这些组件分布在主节点和从节点之间,手动安装这些单独的组件会非常复杂。Kubernetes 及其用户提供了自动化某些节点设置和软件安装的脚本。截至 2015 年 10 月,Kubernetes 的最新稳定版本是 1.0.7,本章中的所有示例均基于 1.0.7 版本。

非 CoreOS Kubernetes 安装

对于非 CoreOS 基础的 Kubernetes 安装,过程很简单:

  1. github.com/kubernetes/kubernetes/releases找到所需的 Kubernetes 发布版本。

  2. 下载适当版本的kubernetes.tar.gz并解压。

  3. KUBERNETES_PROVIDER设置为以下之一(AWS、GCE、Vagrant 等)

  4. cluster目录中更改集群大小和任何其他配置参数。

  5. 运行cluster/kube-up.sh

Kubectl 安装

Kubectl 是与 Kubernetes 交互的 CLI 客户端。Kubectl 默认未安装。Kubectl 可以安装在客户端机器或 Kubernetes 主节点上。

可以使用以下命令安装 kubectl。需要确保 kubectl 版本与 Kubernetes 版本匹配:

ARCH=linux; wget https://storage.googleapis.com/kubernetes-release/release/v1.0.7/bin/$ARCH/amd64/kubectl

如果在客户端机器上已安装 Kubectl,我们可以使用以下命令将请求代理到 Kubernetes 主节点:

ssh -f -nNT -L 8080:127.0.0.1:8080 core@<control-external-ip>

Vagrant 安装

我使用了github.com/pires/kubernetes-vagrant-coreos-cluster中的过程,在 Vagrant 环境中使用 CoreOS 创建了一个 Kubernetes 集群。我最初在 Windows 上尝试过此操作。由于我遇到了github.com/pires/kubernetes-vagrant-coreos-cluster/issues/158中提到的问题,我转向了在 Ubuntu Linux 上运行的 Vagrant 环境。

以下是命令:

Git clone https://github.com/pires/kubernetes-vagrant-coreos-cluster.git``Cd coreos-container-platform-as-a-service/vagrant``Vagrant up

以下输出显示了两个运行中的 Kubernetes 节点:

GCE 安装

我使用了github.com/rimusz/coreos-multi-node-k8s-gce中的过程,在 GCE 中使用 CoreOS 创建了一个 Kubernetes 集群。

以下是命令:

git clone https://github.com/rimusz/coreos-multi-node-k8s-gce``cd coreos-multi-node-k8s-gce

settings 文件中,修改 projectzonenode count 以及其他必要的配置。

按照相同顺序运行以下三个脚本:

1-bootstrap_cluster.sh``2-get_k8s_fleet_etcd.sh``3-install_k8s_fleet_units.sh

以下输出展示了由三个节点组成的集群:

以下输出展示了 Kubernetes 客户端和服务端版本:

以下输出展示了运行在主节点和从节点上的 Kubernetes 服务:

本示例中使用的脚本通过 Fleet 协调 Kubernetes 服务。如前面图示所示,API 服务器、控制器和调度器运行在主节点上,而 kubelet 和代理运行在从节点上。每个从节点上都有三个副本的 kubelet 和 kube-proxy。

AWS 安装

我使用了 coreos.com/kubernetes/docs/latest/kubernetes-on-aws.html 中的流程在 AWS 上创建了运行 CoreOS 的 Kubernetes 集群。

第一部是安装 kube-aws 工具:

Git clone https://github.com/coreos/coreos-kubernetes/releases/download/v0.1.0/kube-aws-linux-amd64.tar.gz

解压并将 kube-aws 复制到可执行路径。确保 ~/.aws/credentials 已更新为你的凭证。

创建一个默认的 cluster.yaml 文件:

curl --silent --location https://raw.githubusercontent.com/coreos/coreos-kubernetes/master/multi-node/aws/cluster.yaml.example > cluster.yaml

修改 cluster.yaml 文件,填写你的 keynameregionexternaldnsnameexternaldnsname 只对外部访问重要。

要部署集群,我们可以执行以下操作:

Kube-aws up

以下输出展示了属于 Kubernetes 集群的两个节点:

Kubernetes 应用示例

以下图示展示了我们将用来说明前面章节中讨论的不同 Kubernetes 概念的留言本示例。此示例基于 kubernetes.io/v1.1/examples/guestbook/README.html 的参考:

以下是关于这个留言本应用的一些说明:

  • 该应用使用 php 前端与 redis 主从后端存储留言本数据库。

  • 前端 RC 创建了三个 kubernetes/example-guestbook-php-redis 容器实例。

  • Redis-master RC 创建了一个 redis 容器实例。

  • Redis-slave RC 创建了两个 kubernetes/redis-slave 容器实例。

在本示例中,我使用了上一节中创建的集群,该集群在 AWS 上运行 Kubernetes,使用 CoreOS。集群中有一个主节点和两个从节点。

让我们来看看这些节点:

在本示例中,Kubernetes 集群使用 flannel 跨 pods 进行通信。以下输出展示了分配给集群中每个节点的 flannel 子网:

以下是启动应用程序所需的命令:

kubectl create -f redis-master-controller.yaml``kubectl create --validate=false -f redis-master-service.yaml``kubectl create -f redis-slave-controller.yaml``kubectl create --validate=false -f redis-slave-service.yaml``kubectl create -f frontend-controller.yaml``kubectl create --validate=false -f frontend-service.yaml

让我们查看 Pod 列表:

上述输出显示了三个 php 前端实例,一个 Redis 主节点实例和两个 Redis 从节点实例。

让我们查看 RC 列表:

上述输出显示了每个 Pod 的副本数。前端有三个副本,redis-master有一个副本,redis-slave有两个副本,正如我们所要求的那样。

让我们看一下服务列表:

在上述输出中,我们可以看到组成留言簿应用程序的三个服务。

对于内部服务发现,本示例使用了 kube-dns。以下输出显示了正在运行的 kube-dns RC:

对于外部服务发现,我修改了示例以使用 NodePort 机制,其中一个内部端口被暴露。以下是新的 frontend-service.yaml 文件:

apiVersion: v1 kind: Service metadata: name: frontend labels: name: frontend spec: # 如果你的集群支持它,取消注释以下内容以自动创建 # 外部负载均衡 IP 用于前端服务。 type: NodePort ports: # 服务应该监听的端口 - port: 80 selector: name: frontend

以下是在启动 NodePort 类型的前端服务时的输出。输出显示该服务通过端口 30193 被暴露:

一旦我们通过 AWS 防火墙暴露端口 30193,就可以按以下方式访问留言簿应用程序:

让我们看看 Node1 中的应用程序容器:

让我们看看 Node2 中的应用程序容器:

上述输出包含了三个前端实例,一个 Redis 主节点实例和两个 Redis 从节点实例。

为了说明复制控制器如何维持 Pod 的副本数,我在一个节点中停止了留言簿前端 Docker 容器,如下图所示:

Kubernetes RC 检测到 Pod 未运行并重新启动了 Pod。这可以通过查看其中一个留言簿 Pod 的重启次数来看到,如下图所示:

为了进行一些基本的调试,我们可以登录到 Pod 或容器中。以下示例显示了如何进入 Pod:

上述输出显示了在留言簿 Pod 中的 IP 地址,这与分配给该节点的 Flannel 子网一致,正如本示例开头的 Flannel 输出所示。

另一个对调试有用的命令是 kubectl logs,如下所示:

Kubernetes 与 Rkt

默认情况下,Kubernetes 使用容器运行时 Docker。Kubernetes 的架构允许其他容器运行时,如 Rkt,与 Kubernetes 一起工作。目前正在进行积极的工作(github.com/kubernetes/kubernetes/tree/master/docs/getting-started-guides/rkt)以将 Kubernetes 与 Rkt 和 CoreOS 集成。

Kubernetes 1.1 更新

Kubernetes 于 2015 年 11 月发布了 1.1 版本(blog.kubernetes.io/2015/11/Kubernetes-1-1-Performance-upgrades-improved-tooling-and-a-growing-community.html)。1.1 版本的重要新增功能包括性能提升、自动扩展和用于批处理任务的作业对象。

Docker Swarm

Swarm 是 Docker 的原生编排解决方案。以下是 Docker Swarm 的一些特点:

  • 与其单独管理每个 Docker 节点,不如将整个集群作为一个整体来管理。

  • Swarm 内置调度器,负责决定容器在集群中的位置。Swarm 使用用户特定的约束条件和亲和性(docs.docker.com/swarm/scheduler/filter/)来决定容器的放置。约束条件可以是 CPU 和内存,而亲和性是将相关容器分组在一起的参数。Swarm 还提供将其调度器移除,并与其他调度器(如 Kubernetes)协作的选项。

以下图示展示了 Docker Swarm 架构:

以下是关于 Docker Swarm 架构的一些说明:

  • Swarm 主节点负责根据调度算法、约束条件和亲和性调度 Docker 容器。支持的算法有 spread、binpackrandom。默认算法是 spread。可以并行运行多个 Swarm 主节点,以提供高可用性。Spread 调度算法用于均匀分配工作负载。Binpack 调度算法则是在将任务调度到其他节点之前,先充分利用每个节点。

  • Swarm 代理在每个节点上运行,并与 Swarm 主节点通信。

  • 有多种方法可以让 Swarm 工作节点发现 Swarm 主节点。发现是必要的,因为 Swarm 主节点和代理程序运行在不同的节点上,并且 Swarm 代理不是由 Swarm 主节点启动的。Swarm 代理和 Swarm 主节点需要相互发现,才能了解它们属于同一个集群。可用的发现机制包括 Docker hub、Etcd、Consul 等。

  • Docker Swarm 与 Docker machine 集成,简化了 Docker 节点的创建。Docker Swarm 与 Docker compose 集成,用于多容器应用的编排。

  • 随着 Docker 1.9 版本的发布,Docker Swarm 与多主机 Docker 网络集成,使得跨主机调度的容器能够相互通信。

Docker Swarm 安装

本示例的前提是安装 Docker 1.8.1 和 Docker-machine 0.5.0。我使用docs.docker.com/swarm/install-w-machine/中的步骤创建了一个带有两个 Docker Swarm 代理节点的单个 Docker Swarm 主节点。以下是步骤:

  1. 创建发现令牌。

  2. 使用创建的发现令牌创建 Swarm 主节点。

  3. 使用创建的发现令牌创建两个 Swarm 代理节点。

通过将环境变量设置为swarm-master,如以下命令所示,我们可以使用常规的 Docker 命令控制 Docker Swarm 集群:

eval $(docker-machine env --swarm swarm-master)

我们来看一下 Swarm 集群中docker info的输出:

上述输出显示集群中有三个节点(一个主节点和两个代理节点),并且集群中正在运行四个容器。swarm-master节点有两个容器,而swarm-agent节点每个有一个容器。这些容器用于管理 Swarm 服务。应用容器只会在 Swarm 代理节点中调度。

我们来看一下主节点中的单个容器。这显示了主节点和代理服务正在运行:

我们来看一下运行在代理节点中的容器。这显示了正在运行的swarm agent

Docker Swarm 示例

为了演示 Docker Swarm 容器编排,我们先启动四个nginx容器:

docker run -d --name nginx1 nginx``docker run -d --name nginx2 nginx``docker run -d --name nginx3 nginx``docker run -d --name nginx4 nginx

从以下输出中,我们可以看到四个容器均匀分布在swarm-agent-00swarm-agent-01之间。这里使用了默认的spread调度策略:

以下输出显示了包括主节点和两个代理节点在内的整个集群的容器总数。总计八个容器,包括 Swarm 服务容器和 nginx 应用容器:

Mesos

Apache Mesos 是一个开源的集群软件。Mesosphere 的 DCOS 是 Apache Mesos 的商业版。Mesos 结合了集群操作系统(Clustering OS)和集群管理器(Cluster Manager)。集群操作系统负责将来自多个不同计算机的资源表示为一个统一的资源,以便可以在其上调度应用程序。集群管理器负责在集群中调度任务。相同的集群可以用于不同的工作负载,如 Hadoop 和 Spark。在 Mesos 中有两级调度。第一层调度负责在框架之间进行资源分配,框架则负责在该框架内调度任务。每个框架是一个应用程序类别,如 Hadoop、Spark 等。对于通用应用程序,最好的框架是 Marathon。Marathon 是一个分布式 INIT 和高可用(HA)系统,用于调度容器。Chronos 框架类似于 Cron 作业,适合运行需要定期执行的较短工作负载。Aurora 框架则为复杂任务提供了更加细粒度的控制。

以下图片展示了 Mesos 架构中的不同层次:

比较 Kubernetes、Docker Swarm 和 Mesos

尽管这些解决方案(Kubernetes、Docker Swarm 和 Mesos)都用于应用程序编排,但它们在方法和使用场景上存在很多差异。我尝试基于它们的最新版本总结这些差异。所有这些编排解决方案都在积极开发中,因此功能集可能会发生变化。此表格已更新至 2015 年 10 月:

特性 Kubernetes Docker Swarm Mesos
部署单元 Pods 容器 进程或容器
容器运行时 Docker 和 Rkt。 Docker。 Docker;Mesos 正在讨论与 Rkt 的集成。
网络 每个容器都有一个 IP 地址,并且可以使用外部网络插件。 最初,通过一个公共代理 IP 地址进行端口转发。使用 Docker 1.9 后,采用了 Overlay 网络和每个容器的 IP 地址,并且可以使用外部网络插件。 最初,通过一个公共代理 IP 地址进行端口转发。目前,使用每个容器的 IP 地址,并与 Calico 集成。
工作负载 同质工作负载。通过命名空间,可以创建多个虚拟集群。 同质工作负载。 可以并行运行多个框架,如 Marathon、Aurora 和 Hadoop。
服务发现 可以使用基于环境变量的发现或 Kube-dns 进行动态发现。 静态,修改 /etc/hosts。未来计划使用 DNS 方法。 基于 DNS 的方法,用于动态发现服务。
高可用性 使用复制控制器,服务具有高度可用性。服务扩展可以轻松完成。 服务的高可用性尚未实现。 框架处理服务的高可用性。例如,Marathon 具有Init.d系统来运行容器。
成熟度 相对较新。首次生产版本发布于几个月前。 相对较新。首次生产版本发布于几个月前。 稳定性较好,广泛应用于大型生产环境中。
复杂性 简单。 简单。 设置稍微有些困难。
使用场景 更适合同质化工作负载。 提供 Docker 前端界面,使得 Docker 用户无需学习新的管理界面也能轻松使用。 适合异质化工作负载。

Kubernetes 可以作为框架在 Mesos 之上运行。在这种情况下,Mesos 为 Kubernetes 提供一级调度,而 Kubernetes 则调度和管理已调度的应用程序。这个项目(github.com/mesosphere/kubernetes-mesos)致力于在 Mesos 之上运行 Kubernetes。

目前正在进行的工作是将 Docker Swarm 与 Kubernetes 集成,以便 Kubernetes 可以作为集群的调度器和过程管理器运行,同时用户仍然可以使用 Docker Swarm 的 Docker 界面来管理容器。

应用程序定义

当一个应用程序由多个容器组成时,使用单一的 JSON 或 YAML 文件表示每个容器的属性及其依赖关系非常有用,这样可以整体实例化应用程序,而不是单独实例化应用程序中的每个容器。应用程序定义文件负责定义多容器应用程序。Docker-compose 定义了应用程序文件和运行时,根据应用程序文件实例化容器。

Docker-compose

Docker-compose 为你提供了应用程序定义格式,当我们运行该工具时,Docker-compose 负责解析应用程序定义文件并实例化容器,处理所有依赖关系。

Docker-compose 具有以下优点和使用场景:

  • 它提供了一种简单的方法来指定包含多个容器及其约束和亲和性的应用程序清单

  • 它与 Dockerfile、Docker Swarm 和多主机网络集成良好

  • 相同的 compose 文件可以通过环境变量适应不同的环境

单节点应用程序

以下示例展示了如何构建一个包含 WordPress 和 MySQL 容器的多容器 WordPress 应用程序。

以下是定义容器及其属性的docker-compose.yml文件:

wordpress:   image: wordpress   ports:    - "8080:80"   environment:     WORDPRESS_DB_HOST: "composeword_mysql_1:3306"     WORDPRESS_DB_PASSWORD: mysql mysql:   image: mysql   environment:     MYSQL_ROOT_PASSWORD: mysql

以下命令展示了如何使用 docker-compose 启动应用:

docker-compose –p composeword –f docker-compose.yml up -d

以下是前面命令的输出:

容器名称以 -p 选项中指定的关键字为前缀。在上面的示例中,我们使用了 composeword_mysql_1 作为主机名,且 IP 地址是通过此容器动态生成并更新到 /etc/hosts 中。

以下输出显示了 Wordpress 应用运行的容器:

以下输出是 Wordpress 容器中 /etc/hosts 的输出,显示 MySQL 容器的 IP 地址已被动态更新:

一个多节点应用

我使用了 docs.docker.com/engine/userguide/networking/get-started-overlay/ 中的示例,通过 docker-compose 创建了一个跨多个节点的 Web 应用。在这个例子中,docker-compose 与 Docker Swarm 和 Docker 多主机网络集成。

这个示例的前提是需要有一个正常工作的 Docker Swarm 集群以及 Docker 版本 1.9+。

以下命令创建了多主机计数器应用。该应用有一个 Web 容器作为前端,一个 Mongo 容器作为后端。必须在 Swarm 集群中执行这些命令:

docker-compose –p counter –x-networking up -d

以下是前面命令的输出:

以下输出显示了作为本应用一部分创建的叠加网络 counter

以下输出显示了 Swarm 集群中运行的容器:

以下输出显示了 Swarm 集群信息,共有五个容器——其中三个是 Swarm 服务容器,两个是前述应用容器:

以下输出显示了工作中的 Web 应用:

打包的容器编排解决方案

部署一个大规模分布式微服务应用需要许多组件,以下是一些重要的组件:

  • 一个基础设施集群

  • 一个容器优化操作系统

  • 一个内置调度器、服务发现和网络功能的容器编排工具

  • 存储集成

  • 支持多租户能力并具备认证功能

  • 一个可以简化管理的 API 层次结构

像 Amazon 和 Google 这样的云服务提供商已经有了管理虚拟机的生态系统,他们的做法是将容器和容器编排集成到他们的 IaaS 产品中,以便容器能够与他们的其他工具良好配合。AWS 容器服务和 Google 容器引擎属于这一类。CoreOS 的重点是开发一个安全的容器优化操作系统,并为分布式应用程序开发提供开源工具。CoreOS 意识到,将他们的产品与 Kubernetes 集成将为客户提供一个集成的解决方案,Tectonic 提供了这个集成解决方案。

还有一些其他项目,如 OpenStack Magnum(github.com/openstack/magnum)和 Cisco 的 Mantl(mantl.io/),也属于此类托管容器编排服务。我们在本章没有涉及这些内容。

AWS 容器服务

AWS EC2 容器服务(ECS)是 AWS 提供的一种容器编排服务。以下是该服务的一些关键特性:

  • ECS 创建并管理容器启动的节点集群。用户只需指定集群的规模。

  • 容器健康状况通过运行在节点上的容器代理进行监控。容器代理与主节点通信,主节点负责做出所有与服务相关的决策。这确保了容器的高可用性。

  • ECS 负责在集群中调度容器。调度器 API 实现为插件,这使得与其他调度器(如 Marathon 和 Kubernetes)的集成成为可能。

  • ECS 与其他 AWS 服务如 Cloudformation、ELB、日志记录、Volume 管理等良好集成。

安装 ECS 及示例

ECS 可以通过 AWS 控制台、AWS CLI 或 ECS CLI 进行控制。以下示例中,我使用了 ECS CLI,ECS CLI 可以通过此链接的过程安装(docs.aws.amazon.com/AmazonECS/latest/developerguide/ECS_CLI_installation.html)。

我使用了以下示例(docs.aws.amazon.com/AmazonECS/latest/developerguide/ECS_CLI_tutorial.html)来创建一个包含两个容器(WordPress 和 MySQL)的 WordPress 应用程序,使用 compose YML 文件。

以下是步骤:

  1. 创建 ECS 集群。

  2. 将应用程序作为服务部署到集群上。

  3. 集群规模或服务规模可以根据需求动态调整。

以下命令显示 WordPress 应用程序的运行容器:

ecs-cli ps

以下是前述命令的输出:

我们可以使用 ecs-cli 命令来扩展应用程序。以下命令将每个容器的规模设置为二:

ecs-cli compose --file hello-world.yaml scale 2

以下输出显示了此时正在运行的容器。正如我们所看到的,容器已经扩展到两个:

注意

注意:MySQL 容器通常使用单一主节点和多个从节点进行扩展。

我们还可以登录到每个 AWS 节点并查看正在运行的容器。以下输出显示了其中一个 AWS 节点中的三个容器。两个是应用程序容器,第三个是 ECS 代理容器,它负责容器监控并与主节点通信:

注意

注意:要登录每个节点,我们需要使用ec2-user作为用户名,并使用创建集群时使用的私钥。

为了演示高可用性,我尝试停止容器或节点。由于容器代理监控每个节点中的容器,容器会被重新调度。

Google 容器引擎

Google 容器引擎是 Google 提供的集群管理和容器编排解决方案,构建在 Kubernetes 之上。与使用 Kubernetes 运行容器集群相比,GCE 有以下差异或优势:

  • 节点集群由 Google 容器引擎自动创建。用户只需指定集群大小以及 CPU 和内存需求。

  • Kubernetes 由多个独立的服务组成,如 API 服务器、调度器和代理等,这些服务需要安装才能使 Kubernetes 系统正常工作。Google 容器引擎负责创建带有适当服务的 Kubernetes 主节点,并在代理节点中安装其他 Kubernetes 服务。

  • Google 容器引擎与其他 Google 服务(如 VPC 网络、日志记录、自动扩展、负载均衡等)集成得很好。

  • 可以使用 Docker Hub、Google 容器注册表或本地注册表来存储容器镜像。

安装 GCE 和示例

cloud.google.com/container-engine/docs/before-you-begin中的步骤可用于安装 gcloud 容器组件和 kubectl。容器也可以使用 GCE 控制台进行管理。

我使用cloud.google.com/container-engine/docs/tutorials/guestbook中的步骤创建了一个包含三个服务的留言簿应用程序。这个应用程序与之前指定的 Kubernetes 应用程序示例中的应用程序相同。

以下是步骤:

  1. 创建一个具有所需集群大小的节点集群。这将自动创建一个 Kubernetes 主节点,并在节点上安装适当的代理服务。

  2. 使用复制控制器和服务文件部署应用程序。

  3. 集群可以根据需求动态调整大小。

以下是我创建的集群。该集群中有四个节点,数量由NUM_NODES指定:

以下命令显示了正在运行的服务,其中包括 frontend、redis-master 和 redis-slave。Kubernetes 服务也在主节点上运行:

Kubectl get services

以下是前一个命令的输出:

由于前端服务已与 GCE 负载均衡器集成,因此还有一个外部 IP 地址。通过外部 IP 地址可以访问 guestbook 服务。以下命令显示了与负载均衡器关联的端点列表:

Kubectl describe services frontend

以下是前一个命令的输出:

要调整集群大小,首先需要找到与集群关联的实例组并对其进行调整。以下命令显示了与 guestbook 关联的实例组:

使用实例组,我们可以按如下方式调整集群大小:

显示集群大小为四的初始输出是在调整集群大小后生成的。

我们可以登录到各个节点,并使用常规的 Docker 命令查看节点中启动的容器。在以下输出中,我们看到一个 redis-slave 实例和一个前端实例在该节点中运行。其他容器为 Kubernetes 基础设施容器:

CoreOS Tectonic

Tectonic 是 CoreOS 的商业产品,集成了 CoreOS 和 CoreOS 的开源组件(Etcd、Fleet、Flannel、Rkt 和 Dex)以及 Kubernetes。通过 Tectonic,CoreOS 将其其他商业产品如 CoreUpdate、Quay 仓库和 Enterprise CoreOS 集成到 Tectonic 中。

计划将 Kubernetes API 作为其在 Tectonic 中的原始形式公开。CoreOS 开源项目的开发将继续进行,并且最新的软件会更新到 Tectonic 中。

下图展示了 Tectonic 的不同组件:

Tectonic 为你提供分布式可信计算(DTM),在所有层级(包括硬件和软件)提供安全性。以下是一些独特的区别点:

  • 在固件级别,可以嵌入客户密钥,这允许客户验证系统中运行的所有软件。

  • 嵌入固件中的安全密钥可以验证引导加载程序以及 CoreOS。

  • 诸如 Rkt 这样的容器可以通过其镜像签名进行验证。

  • 可以使用嵌入在 CPU 主板中的 TPM 硬件模块,使日志防篡改。

总结

在本章中,我们讲解了容器编排的重要性,并深入介绍了流行的容器编排解决方案,如 Kubernetes、Docker Swarm 和 Mesos。许多公司提供集成的容器编排解决方案,我们也介绍了一些流行的解决方案,例如 AWS 容器服务、Google 容器引擎和 CoreOS Tectonic。对于本章介绍的所有技术,均提供了安装和示例,供您尝试使用。客户可以选择使用集成的容器编排解决方案,或者手动将编排解决方案集成到他们的基础设施中。影响选择的因素包括灵活性、与内部解决方案的集成以及成本。下一章我们将介绍 OpenStack 与容器和 CoreOS 的集成。

参考文献

进一步阅读和教程

第十章

第九章 OpenStack 与容器和 CoreOS 的集成

OpenStack 是一个开源云操作系统,用于管理公共云和私有云。它是一项相当成熟的技术,得到了大多数供应商的支持,并被广泛应用于各种生产部署中。在 OpenStack 环境中运行 CoreOS 将为 OpenStack 用户提供一个基于容器的微型操作系统,以便部署他们的分布式应用。容器编排与 OpenStack 的集成为 OpenStack 用户提供了一个统一的管理解决方案,用于管理虚拟机和容器。目前,OpenStack 中有多个项目正在进行,以将容器管理和容器网络与 OpenStack 集成。

本章将涉及以下内容:

  • OpenStack 概述

  • 在 OpenStack 中运行 CoreOS

  • 在 OpenStack 中运行容器的选项——Nova Docker 驱动、Heat Docker 插件和 Magnum

  • 使用 OpenStack Kuryr 和 Neutron 进行容器网络连接

OpenStack 概述

就像桌面或服务器操作系统管理与之相关的资源一样,云操作系统管理与云相关的资源。主要的云资源包括计算、存储和网络。计算包括服务器和与服务器相关的虚拟化管理程序,这些允许虚拟机的创建。存储包括本地存储、存储区域网络(SAN)和对象存储。

网络包括 VLAN、火墙、负载均衡器和路由器。云操作系统还负责其他基础设施相关的项目,如镜像管理、身份验证、安全、计费等。云操作系统还提供一些自动化特性,如弹性、自助式配置模型等。目前,市场上最流行的开源云操作系统是 OpenStack。OpenStack 得到了极大的支持,并且拥有强大的行业背书。

以下是一些关键的 OpenStack 服务:

  • Nova: 计算

  • Swift: 对象存储

  • Cinder: 块存储

  • Neutron: 网络

  • Glance: 镜像管理

  • Keystone: 身份验证

  • Heat: 编排

  • Ceilometer: 计量

  • Horizon: Web 界面

OpenStack 可以从 wiki.openstack.org/wiki/Get_OpenStack 下载。由于涉及多个组件,安装 OpenStack 是相当复杂的。类似于 Linux 供应商提供的 Linux 发行版,多个供应商也提供 OpenStack 发行版。尝试 OpenStack 的最佳方式是使用 Devstack (devstack.org/)。Devstack 提供了一种脚本化的安装方式,可以在笔记本或虚拟机上安装。Devstack 可用于创建单节点集群或多节点集群。

CoreOS 在 OpenStack 上

CoreOS 可以作为虚拟机在 OpenStack 上运行。CoreOS 的 OpenStack 镜像适用于 alpha、beta 和稳定版本。

在这里,我描述了在 Devstack 环境中运行的 OpenStack 上安装 CoreOS 的过程。该过程基于 CoreOS OpenStack 文档(coreos.com/os/docs/latest/booting-on-openstack.html)。

以下是步骤的总结:

  1. 在 Devstack 中获取运行的 OpenStack Kilo。在我的例子中,我在 Ubuntu 14.04 虚拟机中安装了 Devstack。

  2. 设置身份验证的密钥和 SSH 访问的安全组。

  3. 为虚拟机设置外部网络访问和 DNS。这是必要的,因为 CoreOS 节点需要通过令牌服务相互发现。

  4. 下载适当的 CoreOS 镜像并使用 Glance 服务上传到 OpenStack。

  5. 获取发现令牌并在用户数据配置文件中更新它。

  6. 使用自定义用户数据启动 CoreOS 实例,指定需要启动的服务以及要启动的实例数量。

获取 Devstack 中运行的 OpenStack Kilo

以下博客详细介绍了该过程:

sreeninet.wordpress.com/2015/02/21/openstack-juno-install-using-devstack/

这是我使用的local.conf文件:

[[local|localrc]] DEST=/opt/stack # 日志 LOGFILE=$DEST/logs/stack.sh.log VERBOSE=True SCREEN_LOGDIR=$DEST/logs/screen OFFLINE=True # 主机 #EDITME HOST_IP=<EDITME> # 网络 FIXED_RANGE=10.0.0.0/24 disable_service n-net enable_service q-svc enable_service q-agt enable_service q-dhcp enable_service q-meta enable_service q-l3 #ml2 Q_PLUGIN=ml2 Q_AGENT=openvswitch # vxlan Q_ML2_TENANT_NETWORK_TYPE=vxlan # 凭证 ADMIN_PASSWORD=openstack MYSQL_PASSWORD=openstack RABBIT_PASSWORD=openstack SERVICE_PASSWORD=openstack SERVICE_TOKEN=tokentoken # 调度器 enable_service n-sch SCHEDULER=nova.scheduler.chance.ChanceScheduler # vnc enable_service n-novnc enable_service n-cauth

设置密钥和安全组

以下是我用来创建密钥对并暴露虚拟机 SSH 和 ICMP 端口的命令:

nova keypair-add heattest > ~/Downloads/heattest.pem``nova secgroup-add-rule default icmp -1 -1 0.0.0.0/0``nova secgroup-add-rule default tcp 1 65535 0.0.0.0/0

设置外部网络访问

第一条命令设置虚拟机外部访问的 NAT 规则,第二条命令设置 DNS 服务器:

sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE``neutron subnet-update <subnet> --dns-nameservers list=true <dns address>

(使用nova subnet-list查找<subnet>,并从运行的主机获取<dns address>)。

下载 CoreOS 镜像并上传到 Glance

以下命令用于下载最新的 alpha 镜像并上传到 OpenStack glance:

wget http://alpha.release.core-os.net/amd64-usr/current/coreos_production_openstack_image.img.bz2``bunzip2 coreos_production_openstack_image.img.bz2``glance image-create --name CoreOS \`` --container-format bare \`` --disk-format qcow2 \`` --file coreos_production_openstack_image.img \`` --is-public True

以下是 glance image-list 输出,我们可以看到 CoreOS 镜像已上传至 Glance:

更新用于 CoreOS 的用户数据

我在使用默认用户数据启动 CoreOS 时遇到了一些问题,因为 CoreOS 在确定系统 IP 时遇到问题。我提出了一个案例(groups.google.com/forum/#!topic/coreos-user/STmEU6FGRB4),CoreOS 团队提供了一个示例用户数据,其中通过用户数据内的脚本来确定 IP 地址。

以下是我使用的用户数据:

#cloud-config write_files: - path: /tmp/ip.sh permissions: 0755 content: | #!/bin/sh get_ipv4() { IFACE="${1}" local ip while [ -z "${ip}" ]; do ip=$(ip -4 -o addr show dev "${IFACE}" scope global | gawk '{split ($4, out, "/"); print out[1]}') sleep .1 done echo "${ip}" } echo "IPV4_PUBLIC=$(get_ipv4 eth0)" > /run/metadata echo "IPV4_PRIVATE=$(get_ipv4 eth0)" >> /run/metadata coreos: units: - name: populate-ips.service command: start runtime: true content: | [Service] Type=oneshot ExecStart=/tmp/ip.sh - name: etcd2.service command: start runtime: true drop-ins: - name: custom.conf content: | [Unit] Requires=populate-ips.service After=populate-ips.service [Service] EnvironmentFile=/run/metadata ExecStart= ExecStart=/usr/bin/etcd2 --initial-advertise-peer-urls=http://${IPV4_PRIVATE}:2380 --listen-peer-urls=http://${IPV4_PRIVATE}:2380 --listen-client-urls=http://0.0.0.0:2379 --advertise-client-urls=http://${IPV4_PUBLIC}:2379 --discovery=https://discovery.etcd.io/0cbf57ced1c56ac028af8ce7e32264ba - name: fleet.service command: start

上述用户数据执行了以下操作:

  • populate-ips.service 单元文件用于更新 IP 地址。它手动读取 IP 并将其更新到 /run/metadata

  • 更新发现令牌,以便节点可以相互发现。

  • Etcd2 服务通过在 /run/metadata 中设置的 IP 地址启动。

  • Fleet 服务使用 fleet 单元文件启动。

以下命令用于使用上述用户数据启动两个 CoreOS 实例:

nova boot \``--user-data ./user-data1.yaml \``--image 8ae5223c-1742-47bf-9bb3-873374e61a64 \``--key-name heattest \``--flavor m1.coreos \``--num-instances 2 \``--security-groups default coreos

注意

注意:对于 CoreOS 实例,我使用了一个自定义规格 m1.coreos,配置为 1 vcpu、2 GB 内存和 10 GB 硬盘。如果这些资源要求没有满足,实例创建将失败。

让我们看看虚拟机的列表。我们可以在以下图片中看到两个 CoreOS 实例:

以下命令显示了在 OpenStack 中运行的 CoreOS 版本:

以下命令显示 etcd 成员列表:

以下命令显示了显示两个 CoreOS 节点的舰队机器:

OpenStack 和容器

尽管 OpenStack 已经支持虚拟机(VM)和裸金属(baremetal)一段时间,但容器对 OpenStack 来说还是相对较新的概念。OpenStack 最初的重点是将虚拟机编排扩展到容器管理。Nova Docker 驱动程序和 Heat Docker 插件就是这种方法的例子。但由于这种方式缺少了一些容器功能,它并没有得到广泛采用。OpenStack Magnum 项目解决了一些局限性,并将容器作为一种类虚拟机的第一类公民来管理。

Nova Docker 驱动程序

Nova 通常用于管理虚拟机。在这种方法中,Nova 驱动程序被扩展以启动 Docker 容器。

以下图表描述了架构:

以下是关于架构的一些说明:

  • Nova 已配置为使用 Nova Docker 驱动程序来管理容器。

  • Nova Docker 驱动程序通过 REST API 与 Docker 守护进程进行通信。

  • Docker 镜像被导入到 Glance 中,Nova Docker 驱动程序使用这些镜像来启动容器。

Nova Docker 驱动程序并不包含在主流的 OpenStack 安装中,必须单独安装。

安装 Nova 驱动程序

在以下示例中,我们将介绍 Nova Docker 驱动程序的安装和使用方法来创建容器。

以下是步骤摘要:

  1. 你需要有一个 Ubuntu 14.04 虚拟机。

  2. 安装 Docker。

  3. 安装 Nova Docker 插件。

  4. 执行 Devstack 堆叠。

  5. 安装 nova-docker rootwrap 过滤器。

  6. 创建 Docker 镜像并导出到 Glance。

  7. 从 Nova 启动 Docker 容器。

安装 Docker

以下是在我系统中 Docker 安装后运行的 Docker 版本:

安装 Nova Docker 插件

使用以下命令安装插件:

git clone -b stable/kilo https://github.com/stackforge/nova-docker.git``cd nova-docker``sudo pip install .

以下是安装后的 Docker 驱动程序版本:

Devstack 安装

我使用了稳定的 Kilo 版本,并配有以下 local.conf。这将配置 Nova 使用 Docker 驱动程序:

[[local|localrc]] # HOST HOST_IP=<EDITME> ADMIN_PASSWORD=openstack DATABASE_PASSWORD=$ADMIN_PASSWORD RABBIT_PASSWORD=$ADMIN_PASSWORD SERVICE_PASSWORD=$ADMIN_PASSWORD SERVICE_TOKEN=super-secret-admin-token VIRT_DRIVER=novadocker.virt.docker.DockerDriver # Logging VERBOSE=True DEST=$HOME/stack SCREEN_LOGDIR=$DEST/logs/screen SERVICE_DIR=$DEST/status DATA_DIR=$DEST/data LOGFILE=$DEST/logs/stack.sh.log LOGDIR=$DEST/logs OFFLINE=false # Networking FIXED_RANGE=10.0.0.0/24 # This enables Neutron disable_service n-net enable_service q-svc enable_service q-agt enable_service q-dhcp enable_service q-l3 enable_service q-meta # Introduce glance to docker images [[post-config|$GLANCE_API_CONF]] [DEFAULT] container_formats=ami,ari,aki,bare,ovf,ova,docker # Configure nova to use the nova-docker driver [[post-config|$NOVA_CONF]] [DEFAULT] compute_driver=novadocker.virt.docker.DockerDriver

要安装 nova-docker rootwrap 过滤器,请运行以下命令:

sudo cp nova-docker/etc/nova/rootwrap.d/docker.filters \`` /etc/nova/rootwrap.d/

要将 Docker 镜像上传到 Glance,请运行以下命令:

docker save nginx | glance image-create --is-public=True --container-format=docker --disk-format=raw --name nginx

让我们查看 Glance 镜像列表;我们可以看到 nginx 容器镜像:

现在,让我们创建 nginx 容器:

nova boot --flavor m1.small --image nginx nginxtest

让我们来看看 Nova 实例:

我们还可以使用 Docker 原生命令查看正在运行的容器:

Heat Docker 插件

以下是 Nova Docker 驱动当前无法完成的某些操作:

  • 传递环境变量

  • 容器链接

  • 指定卷

  • 编排和调度容器

这些缺失的功能对于容器来说非常重要且独特。Heat Docker 插件部分解决了这些问题,除了编排部分。

以下图示展示了 Heat Docker 编排架构:

以下是一些关于架构的说明:

  • Heat 使用 Heat Docker 插件与 Docker 进行通信。Docker 插件通过 REST API 与 Docker 引擎进行交互。

  • Heat 与 Docker 注册表之间没有直接交互。

  • 使用 Heat 编排脚本,我们可以使用 Docker 引擎的所有功能。此方法的缺点是没有将 Docker 与其他 OpenStack 模块直接集成。

安装 Heat 插件

我使用了sreeninet.wordpress.com/2015/06/14/openstack-and-docker-part-2/github.com/MarouenMechtri/Docker-containers-deployment-with-OpenStack-Heat中的步骤,将 OpenStack Heat 与 OpenStack Icehouse 中的 Docker 插件进行了集成。

使用 Heat 插件,我们可以在本地主机或由 OpenStack 创建的虚拟机上启动 Docker 容器。

我使用的是安装了 Icehouse 的 Ubuntu 14.04 虚拟机,通过 Devstack 进行配置。我按照前述链接中的程序安装了 Heat Docker 插件。

以下命令输出显示 Heat 插件在本地主机中安装成功:

$ heat resource-type-list | grep Docker``| DockerInc::Docker::Container

以下是一个 Heat 模板文件,用于在本地主机中启动 nginx 容器:

heat_template_version: 2013-05-23 description: >   Heat 模板,用于将 Docker 容器部署到现有主机 resources:   nginx-01:     type: DockerInc::Docker::Container     properties:       image: nginx       docker_endpoint: 'tcp://192.168.56.102:2376'

我们已将端点指定为本地主机的 IP 地址和 Docker 引擎端口号。

以下命令用于使用前述 Heat 模板创建容器:

heat stack-create -f ~/heat/docker_temp.yml nginxheat1

以下输出显示 Heat 堆栈安装已完成:

$ heat stack-list``+--------------------------------------+---------------+-----------------+----------------------+``| id | stack_name | stack_status | creation_time |``+--------------------------------------+---------------+-----------------+----------------------+``| d878d8c1-ce17-4f29-9203-febd37bd8b7d | nginxheat1 | CREATE_COMPLETE | 2015-06-14T13:27:54Z |``+--------------------------------------+---------------+-----------------+----------------------

以下输出显示容器在本地主机中的成功运行:

$ docker -H :2376 ps``CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES``624ff5de9240 nginx:latest "nginx -g 'daemon of 2 minutes ago Up 2 minutes 80/tcp, 443/tcp trusting_pasteur

我们可以通过将端点 IP 地址从本地主机更改为虚拟机的 IP 地址,使用 Heat 插件方法在 OpenStack 虚拟机上运行容器。

Magnum

在 Nova 驱动程序和 Heat 编排的情况下,容器在 OpenStack 中并不是一等公民,且这些方法并不容易管理容器的特性。Magnum 是 OpenStack 中正在开发的通用容器管理解决方案,用于管理 Docker 及其他容器技术。Magnum 目前支持 Kubernetes、Docker Swarm 和 Mesos 进行编排。未来将添加其他编排解决方案。Magnum 目前支持 Docker 容器。该架构允许其未来支持其他容器运行时,如 Rkt。Magnum 仍处于早期阶段,并在 OpenStack Liberty 版本中作为测试功能提供。

Magnum 架构

下图展示了 Magnum 中的不同层次:

以下是关于 Magnum 架构的一些说明:

  • Magnum 客户端与 Magnum API 服务器进行通信,后者再与 Magnum conductor 通信。Magnum conductor 负责与 Kubernetes、Docker Swarm 和 Heat 进行交互。

  • Heat 负责与其他 OpenStack 模块(如 Nova、Neutron、Keystone 和 Glance)进行交互。

  • Nova 用于在 Bay 中创建节点,这些节点可以运行不同的微型操作系统,如 CoreOS 和 Atomic。

OpenStack Magnum 使用以下构件:

  • Bay 模型:这是一个集群定义,描述集群的属性,例如节点规格、节点操作系统以及使用的编排引擎。以下是一个示例 Bay 模型模板,使用 m1.small 作为节点规格,fedora atomic 作为节点的基础操作系统,并使用 Kubernetes 作为编排引擎:

    magnum baymodel-create --name k8sbaymodel \`` --image-id fedora-21-atomic-5 \`` --keypair-id testkey \`` --external-network-id public \`` --dns-nameserver 8.8.8.8 \`` --flavor-id m1.small \`` --docker-volume-size 5 \`` --network-driver flannel \`` --coe kubernetes

  • Bay:Bays 是基于 Bay 模型实例化的,包含 Bay 中所需数量的节点。

  • 节点、Pod 和容器:节点是单独的虚拟机实例。Pod 是一组共享共同属性并一起调度的容器。容器在 Pod 内运行。

以下图示显示了 Bay 模型、Bay、节点、Pod 和容器之间的关系:

以下是使用 OpenStack Magnum 与本地编排解决方案(如 Kubernetes)相比的优势:

  • 对于已经使用 OpenStack 的客户,Magnum 提供了一个集成的解决方案。

  • OpenStack 在所有层级提供多租户功能。这一功能也可以扩展到容器。

  • OpenStack Magnum 允许与其他 OpenStack 模块(如 Neutron、Keystone、Glance、Swift 和 Cinder)进行交互。其中一些集成计划在未来实现。

  • 虚拟机和容器有不同的用途,它们很可能会共存。OpenStack 与 Magnum 项目提供了一个涵盖虚拟机和容器的编排解决方案,这使得它非常具有吸引力。

安装 Magnum

Magnum 可以通过以下过程进行安装:github.com/openstack/magnum/blob/master/doc/source/dev/quickstart.rst。以下是步骤的总结:

  1. 使用 Devstack 创建 OpenStack 开发环境,并启用 Magnum 服务。

  2. 默认情况下,Fedora Atomic 镜像会在 Devstack 安装过程中下载到 Glance。如果需要 CoreOS 镜像,我们需要手动将其下载到 Glance。

  3. 创建一个 Bay 模型。Bay 模型类似于一个模板,具有一组特定的参数,可以用来创建多个 Bay。在 Bay 模型中,我们可以指定 Bay 类型(当前支持的 Bay 类型有 Kubernetes 和 Swarm)、基础镜像类型(当前支持的基础镜像有 Fedora Atomic 和 CoreOS)、网络模型(Flannel)、实例大小等。

  4. 使用 Bay 模型作为模板创建 Bay。在创建 Bay 时,我们可以指定需要创建的节点数。节点是安装基础镜像的虚拟机。

  5. 使用 Kubernetes 或 Swarm 部署容器,并将其部署在创建的 Bay 上。Kubernetes 或 Swarm 会负责将容器调度到 Bay 中的不同节点。

    注意

    注意:建议避免在虚拟机中运行 Magnum。每个 Fedora 实例至少需要 1 或 2 GB 的内存和 8 GB 的硬盘空间,因此需要一台性能较强的机器。

使用 OpenStack Kuryr 进行容器网络配置

在本节中,我们将介绍如何使用 OpenStack Kuryr 项目通过 OpenStack Neutron 实现容器网络配置。

OpenStack Neutron

OpenStack Neutron 为 OpenStack 集群提供网络功能。以下是 OpenStack Neutron 的一些属性:

  • Neutron 通过 API 服务提供网络功能,后端或插件负责实现具体功能。

  • Neutron 可用于裸金属网络以及虚拟机网络。

  • Neutron 的基本构件包括 Neutron 网络、端口、子网和路由器。

  • 常见的 Neutron 后端包括 OVS、OVN 和 Linux 桥接。

  • Neutron 还提供了高级网络服务,如负载均衡服务、防火墙服务、路由服务和 VPN 服务。

容器与网络

我们在前面的章节中已详细介绍了容器网络配置的内容。常用的一些技术包括 Flannel、Docker Libnetwork、Weave 和 Calico。这些技术大多数使用 Overlay 网络来提供容器网络。

OpenStack Kuryr

OpenStack Kuryr 的目标是通过 Neutron 提供容器网络。考虑到 Neutron 是一个成熟的技术,Kuryr 旨在利用 Neutron 的努力,使 OpenStack 用户能够轻松采用容器技术。Kuryr 本身不是一种网络技术;它的目标是充当容器网络与虚拟机网络之间的桥梁,并增强 Neutron 以提供缺失的容器网络功能。

以下图示展示了 Docker 如何与 Neutron 配合使用,以及 Kuryr 的作用:

以下是一些关于 Kuryr 架构的说明:

  • Kuryr 作为 Docker libnetwork 插件实现。容器网络调用通过 Kuryr 映射到相应的 Neutron API 调用。

  • Neutron 使用 OVN、Midonet 和 Dragonflow 作为后端来实现 Neutron 调用。

以下是 OpenStack Kuryr 的一些优点:

  • 它为虚拟机和容器提供统一的网络解决方案。

  • 通过 Magnum 和 Kuryr,容器和虚拟机可以共享同一个编排系统。

  • 考虑到 Neutron 技术已经成熟,容器可以利用所有 Neutron 功能。

  • 在默认的容器网络配置下,当容器部署在虚拟机上时,会出现双重封装问题。容器网络执行第一层封装,虚拟机网络执行下一层封装。这可能会导致性能开销。使用 Kuryr,可以避免双重封装问题,因为容器和虚拟机共享同一网络。

  • Kuryr 可以与其他 OpenStack 组件良好集成,提供内置多租户支持的完整容器解决方案。

以下表格显示了 Neutron 和 Libnetwork 抽象之间的映射:

Neutron Libnetwork
Neutron 网络 网络
端口 端点
子网 IP 管理
插件 API (插入/拔出) 插件 API (加入/离开)

以下图表展示了 Kuryr 如何为容器、虚拟机和裸金属提供共同的网络解决方案:

以下图像展示了 Kuryr 在 Magnum 和容器编排项目中的作用:

Kuryr 的当前状态和发展路线

Kuryr 项目相当新,Mitaka 版本将是首个支持 Kuryr 的 OpenStack 版本。以下是与 Kuryr 相关的正在进行和未来的工作项目:

  • 向 Neutron 添加缺失的容器功能,例如端口转发、资源标记和服务发现。

  • 通过整合虚拟机和容器网络,解决嵌套容器问题。

  • 更好地与 OpenStack Magnum 和 Kolla 项目集成。

  • 当前集成主要集中在 Docker。也有与 Kubernetes 网络模型的集成计划。

总结

本章中,我们讨论了容器和 CoreOS 如何与 OpenStack 集成。由于 CoreOS 只允许以容器的形式运行应用程序,因此如果 OpenStack 支持容器编排,OpenStack 与 CoreOS 的集成将更加有用。尽管 Nova 驱动程序和 Heat 插件已经在 OpenStack 中添加了对容器的支持,但 Magnum 项目似乎是正确的解决方案,将容器视为 OpenStack 中的第一类公民。我们还讨论了如何通过 Kuryr 项目,利用 OpenStack Neutron 提供容器网络。OpenStack 容器集成相对较新,仍有很多工作正在进行,以完成这一集成。使用单一的编排软件来管理虚拟机和容器,可以实现更紧密的集成,并简化管理和调试能力。在下一章中,我们将介绍 CoreOS 的故障排除和调试。

参考文献

进一步阅读和教程

第十一章

第十章:CoreOS 与容器 - 故障排除与调试

CoreOS 和容器在故障排除方面带来了一些特殊挑战,但也有解决这个问题的方法。CoreOS 作为一个优化容器的操作系统,不支持包管理器,这就阻止了某些 Linux 调试工具的安装。这个问题可以通过使用 CoreOS 提供的名为 Toolbox 的工具,在容器中运行 Linux 工具来解决。容器在自己的命名空间中运行,而常规的 Linux 工具无法提供足够的信息来调试容器。这个问题可以通过诸如cadvisorsysdig等工具来解决。日志记录是另一个重要的调试系统级问题的工具,一些供应商如 LogEntries 正在尝试为容器解决这个问题。

在本章中,我们将讨论以下主题:

  • 使用 CoreOS Toolbox 和其他 CoreOS 实用工具来调试 CoreOS 系统

  • 使用 sysdigcadvisor 监控容器

  • Docker 远程 API 支持

  • Docker 日志驱动程序

  • 使用 LogEntries 进行中央容器日志监控

CoreOS Toolbox

由于 CoreOS 不支持包管理器,因此很难安装自定义调试工具来解决问题,例如 tcpdump、strace 等。CoreOS 提供了一个 Toolbox 脚本,它可以启动一个具有系统级权限的 Ubuntu 或 Fedora 容器,在其上我们可以运行 Linux 系统工具,如 tcpdump 来监控和调试 CoreOS 主机。

要启动 Toolbox,请从 CoreOS shell 运行 /usr/bin/toolbox

以下是 CoreOS 主机系统中的进程输出,显示 Toolbox 已以系统级权限启动:

Toolbox 默认使用 Fedora 镜像。以下输出显示了 Toolbox 容器内的 Fedora:

默认的 Fedora 镜像中没有 tcpdump。我通过 yum 安装了 tcpdump 并在 Toolbox 容器内部监控了 eth0 接口。这展示了 Toolbox 如何使用的一个示例。

要更改 CoreOS Toolbox 使用的默认 Linux 镜像,可以在 ~/.toolboxrc 中指定自定义镜像。

以下是一个 .toolboxrc 的示例,在该示例中,我们要求 Toolbox 使用 Ubuntu 镜像:

TOOLBOX_DOCKER_IMAGE=ubuntu TOOLBOX_DOCKER_TAG=14.04

如果我们在上述更改后启动 Toolbox,Toolbox 将启动一个具有系统级权限的 Ubuntu 镜像。以下是作为启动 Toolbox 一部分运行的 Ubuntu 镜像:

我们可以在 cloud-config 中指定镜像选择,以便 .toolboxrc 在容器启动时自动写入。以下是一个示例 cloud-config 部分,在该部分中我们指定了以 Ubuntu 作为默认 Toolbox 容器镜像的 .toolboxrc

-write_files:   - path: /home/core/.toolboxrc     owner: core     content: |       TOOLBOX_DOCKER_IMAGE=ubuntu       TOOLBOX_DOCKER_TAG=14.04

其他 CoreOS 调试工具

我们在第二章《设置 CoreOS 实验环境》的基础调试部分中介绍了 CoreOS 的基本调试。以下是一些可以使用的工具:

  • journalctl工具可以用来检查所有 systemd 服务的日志。

  • systemctl工具可以用来检查所有服务的状态。

  • cloud-config验证工具可以用来在与 CoreOS 一起使用之前验证cloud-config

  • 像 Etcd、Fleet、Flannel 和 Locksmith 这样的工具,具有自身的调试功能,可以在必要时启用。

容器监控

由于容器在自己的命名空间中运行,传统的 Linux 监控工具(如宿主系统中的 top、ps、tcpdump 和 lsof)无法帮助监控容器内或容器之间的活动。这使得容器的故障排查变得复杂。在讨论容器监控工具之前,让我们先看看需要监控的主要项目:

  • 容器及其内部运行的进程的 CPU 使用情况

  • 容器及其内部运行的进程的内存使用情况

  • 网络访问,包括所有的入站和出站连接

  • 容器执行的文件 I/O

以下是一些监控容器的方法:

  • 在容器中安装监控软件:这种做法违背了容器模型,容器运行的是单个微服务,而且不可扩展。

  • 在宿主机上安装监控软件,容器运行的机器:这种方法使得在像 CoreOS 这样的集群操作系统上安装专用软件变得困难,因为它们只允许以容器方式运行应用程序,并不允许在基础操作系统上安装软件。

  • 以容器形式安装带有系统级权限的监控软件:这是最推荐的做法。

Docker 提供了docker stats命令,可提供每个容器的基本 CPU、内存和 I/O 使用情况。我们在第七章《与 CoreOS 的容器集成——Docker 和 Rkt》中介绍了docker stats。Docker 命令提供的数据非常基础。市面上有很多开源和商业的容器监控工具,如 cadvisor、sysdig、Data dog、newrelic、Prometheus 和 Scout,它们提供了容器的更多可视化信息。在本章中,我们将介绍 cadvisor 和 sysdig。

Sysdig

Sysdig 是一个开源项目,提供 Linux 系统级的可视化,并原生支持容器。Sysdig 不仅可以用于宿主机监控,也可以用于容器监控。

以下图示展示了 Sysdig 架构:

以下是一些关于 Sysdig 架构的笔记:

  • Sysdig 可以监控宿主系统、虚拟机和容器。

  • Sysdig 可以监控不同的容器运行时环境,如 Docker、Rkt 和 LXC。

  • Sysdig 文档将 sysdig 称为strace + tcpdump + htop + iftop + lsof + 神奇的酱

  • Sysdig 探针是一个内核模块,需要安装在主机系统中才能进行监控。Sysdig 简化了此模块的安装,适用于常规 Linux 系统以及基于容器的操作系统,如 CoreOS 和 Rancher。

  • 由于 sysdig 直接监控所有内核系统调用,sysdig 提供比其他监控工具更详细的监控数据。

  • Sysdig 容器可以在主机系统上运行,监控主机进程以及在主机系统中运行的容器。

  • Sysdig 可以监控 CPU、内存、网络 IO 和文件 IO。Sysdig 提供各种选项来微调监控查询,以提供相关数据。

  • Sysdig 的开源版本包括 sysdig CLI 和 csysdig,后者具有基于 ncurses 的界面。Csysdig 类似于 htop,提供交互式的文本界面。

  • Sysdig Cloud 是 Sysdig 的商业版,将来自多个主机和容器的数据聚合到云端的一个位置,并可作为 SaaS 应用程序访问。Sysdig Cloud 可以通过云端访问,或安装在本地。

Sysdig 可以作为容器启动。以下命令展示了如何启动 sysdig 容器:

docker run -i -t --name sysdig --privileged -v /var/run/docker.sock:/host/var/run/docker.sock -v /dev:/host/dev -v /proc:/host/proc:ro sysdig/sysdig

有关 Sysdig 安装的更多详情,请参阅www.sysdig.org/install/。以下命令展示了在 CoreOS 系统中运行的 sysdig 容器:

Sysdig 示例

以下输出展示了在 CoreOS 系统中运行的一些容器列表,我们将在这些容器中尝试一些简单的 sysdig 命令:

以下命令显示了消耗 CPU 资源的前几个进程。输出列出了主机机器以及容器中的 PID。topprocs_cpu工具是一个 chisel。在 sysdig 术语中,每个 chisel 都是一个包含预定义任务的脚本:

sysdig -pc -c topprocs_cpu

以下截图为前述命令的输出:

以下命令列出了使用网络 IO 的前几个容器:

sysdig -pc -c topcontainers_net

以下截图为前述命令的输出:

以下命令列出了使用文件 IO 的前几个容器:

sysdig -c topcontainers_file

以下截图为前述命令的输出:

Sysdig spy 命令对于监控所有外部与主机或容器的交互非常有用。以下输出展示了我们在 nginx 容器中执行exec命令并在容器中执行ps命令时的结果:

sysdig -pc -c spy_users

以下截图为前述命令的输出:

前述输出展示了docker execps命令的执行结果。

Csysdig

Csysdig 是 Sysdig 的文本用户界面。Csysdig 实现为一个可定制的 Curses 用户界面。所有可以通过 sysdig 完成的操作,也可以通过 csysdig 完成。csysdig 用户界面可以定制,以显示不同的视图,并且输出可以根据不同的用户输入进行过滤。

可以使用以下命令启动 Csysdig:

Cssysdig –pc (pc 选项提供容器详情)

以下输出显示了 csysdig 中可能的不同视图:

以下输出列出了主机中运行的容器。这可以在容器视图中查看:

一旦我们选择了一个特定的容器,以下输出显示了在该容器中运行的进程:

Sysdig 云

Sysdig 云是 Sysdig 提供的商业解决方案,其中来自主机机器的 sysdig 数据被发送到中央服务器,来自不同主机的容器和主机监控数据在此汇总。Sysdig 云可以在 Sysdig 的服务器上运行,也可以作为本地解决方案运行。

Sysdig 云提供 15 天的试用期。我试用了 Sysdig 云试用版,并在 AWS 上运行的 CoreOS 集群中安装了 Sysdig。

以下是安装 Sysdig 云及其使用方法的步骤:

  1. 在 Sysdig 云上注册并创建一个在线账户。作为注册的一部分,Sysdig 会提供一个访问密钥。

  2. Sysdig 提供的访问密钥需要在主机机器上使用。Sysdig 会使用该访问密钥将属于同一账户的主机关联起来。

  3. 当在主机上启动 sysdig 时,sysdig 代理将与云中的 Sysdig 服务器通信,并导出监控数据。

  4. Sysdig 云也可以与 AWS 集成。如果我们提供 AWS 访问密钥,Sysdig 还可以自动拉取 AWS 虚拟机监控数据。

以下是启动 sysdig-agent 服务的 CoreOS 服务单元文件,该服务与 Sysdig 云进行通信。访问密钥需要适当填写。由于在 X-Fleet 中设置了 Global 选项,该单元将在 CoreOS 集群中的所有节点上启动 sysdig 云代理:

[Unit] Description=Sysdig 云代理 After=docker.service Requires=docker.service [Service] TimeoutStartSec=0 ExecStartPre=-/usr/bin/docker kill sysdig-agent ExecStartPre=-/usr/bin/docker rm sysdig-agent ExecStartPre=/usr/bin/docker pull sysdig/agent ExecStart=/usr/bin/docker run --name sysdig-agent --privileged --net host --pid host -e ACCESS_KEY=<access key> -e TAGS=[role:web,location:bangalore] -v /var/run/docker.sock:/host/var/run/docker.sock -v /dev:/host/dev -v /proc:/host/proc:ro -v /boot:/host/boot:ro sysdig/agent ExecStop=/usr/bin/docker stop sysdig-agent [X-Fleet] Global=true

以下是我的三节点 CoreOS 集群:

以下命令可用于在 CoreOS 机器上启动 sysdig 代理:

fleetctl start docker-sysdig.service

以下输出显示了在其中一个 CoreOS 节点中运行的 sysdig-agent 容器:

以下是 Sysdig 云中显示的注册主机及其运行的容器的输出。在这里,我们可以看到三台主机和每台主机上运行的容器,以及它们的 CPU、内存、网络 I/O 和文件 I/O:

以下输出显示了摘要视图:

以下输出显示了单个容器的仪表板输出及其相关进程。我们选择了 sysdig 容器进行以下输出:

Kubernetes 集成

Sysdig 最近增加了一项与 Kubernetes 集成的功能,Sysdig 可以识别 Kubernetes 的逻辑结构,如主节点、从节点、Pod、复制控制器、标签等。Sysdig 通过查询 Kubernetes API 服务器来获得这些信息。通过结合从容器和 Kubernetes API 服务器收集的数据,Sysdig 和 Sysdig 云可以在 Kubernetes 级别上对信息进行分组。例如,我们可以根据 Kubernetes pod 或复制控制器查看 CPU 和内存使用情况。Sysdig 计划未来与其他编排引擎(如 Mesos 和 Swarm)进行集成。Sysdig 还计划与其他容器运行时(如 Rkt)进行集成。

Cadvisor

Cadvisor 是 Google 推出的一款开源工具,用于监控容器及其运行的主机系统。Google 为其自己的容器系统开发了 cadvisor,之后将其支持扩展到 Docker 容器。

以下是一些关于 cadvisor 的说明:

  • 它监控主机系统和容器的 CPU、内存、网络和文件 I/O。

  • 它可以与 Docker 和其他容器运行时一起使用。

  • 它可以作为容器在主机系统中启动,且不需要对主机系统进行任何特殊更改。

  • cadvisor 容器启动了一个简单的 Web 服务器,我们可以通过它访问仪表板并使用简单的 GUI。

  • 它提供了用于编程访问的 REST API。

  • Cadvisor 仅存储短期历史记录。要维持历史记录,需要将 cadvisor 与 InfluxDB(influxdata.com/)和 Prometheus(prometheus.io/)等后端一起使用。

以下命令可用于启动 Docker cadvisor 容器:

docker run \ --volume=/:/rootfs:ro ` --volume=/var/run:/var/run:rw ` --volume=/sys:/sys:ro ` --volume=/var/lib/docker/:/var/lib/docker:ro ` --publish=8080:8080 ` --detach=true ` --name=cadvisor ` google/cadvisor:latest`

以下输出显示了在 CoreOS 系统中运行的 cadvisor 容器:

以下截图显示了容器的进程和 CPU 使用情况的 GUI 快照:

以下输出显示了 cadvisor 支持的 REST API 子类型:

以下是 cadvisor 提供的 REST API 示例,包括它们提供的详细信息。这个链接,github.com/google/cadvisor/blob/master/docs/api.md,提供了所有支持的 REST API 详情。以下所有命令的输出格式为 JSON。

以下命令提供主机详细信息:

curl -X GET http://172.17.8.102:8080/api/v1.3/machine | jq .

以下命令提供容器性能详细信息:

curl -X GET http://172.17.8.102:8080/api/v1.3/containers/ | jq .

以下命令提供 Docker 容器 nginx 性能详细信息:

curl -X GET http://172.17.8.102:8080/api/v1.3/docker/nginx | jq .

Cadvisor 提供的信息比 sysdig 少,因为 cadvisor 主要依赖 Docker 提供的统计信息。此外,cadvisor 提供的统计历史有限,因此需要将 cadvisor 与其他工具(如 Influxdb)集成,以保持更长的历史记录。

Docker 远程 API

Docker 远程 API 可以用于通过 REST API 访问 Docker 引擎。这可以用于程序化访问 Docker。

以下部分 CoreOS cloud-config 可用于启用 Docker 远程 API 并监听 TCP 端口 2375

    - name: docker-tcp.socket       command: start       enable: true       content: |         [Unit]         Description=Docker Socket for the API         [Socket]         ListenStream=2375         Service=docker.service         BindIPv6Only=both         [Install]         WantedBy=sockets.target

以下是一些访问 Docker 远程 API 的示例:

列出正在运行的容器:

docker -H tcp://172.17.8.102:2375 ps

以下截图是前述命令的输出:

列出容器镜像:

以下命令可用于以 JSON 格式列出容器镜像:

curl -X GET http://172.17.8.101:2375/images/json | jq .

以下截图是前述命令的输出:

列出 Docker 引擎详细信息:

以下命令相当于 docker info

curl -X GET http://172.17.8.101:2375/info | jq .

列出特定容器统计信息:

curl -X GET http://172.17.8.101:2375/containers/26b225ec6a8e/stats | jq .

列出 Docker 版本:

curl -X GET http://172.17.8.102:2375/version | jq .

列出 Docker 事件:

curl -X GET http://172.17.8.102:2375/events

以下命令删除特定的 busybox 容器:

curl -X DELETE http://172.17.8.102:2375/images/busybox

以下截图是前述命令的输出:

列出发送到 stdout 的特定容器日志:

容器 ID 作为参数指定用于以下命令:

curl -X GET http://172.17.8.101:2375/containers/5ab9abb4787e/logs?stdout=1

如果我们需要安全访问 Docker 远程 API,可以使用 TLS,Docker 守护进程支持此功能。

容器日志

当容器将输出发送到 stdout 或 stderr 时,必须记录这些日志。这对于监控错误和事件,以及维护容器应用程序的历史记录非常有用。使用容器时,日志记录存在一些特殊挑战:

  • 通常,容器运行的是微服务,我们不希望日志记录过程在容器内部运行,因为这违背了容器模型。

  • 对于微服务来说,一个应用程序可以拆分成多个容器,运行在不同的主机上。为了得出有意义的结论,必须从多个容器中收集日志。这就要求我们必须有一个中央日志服务器,而不是在容器运行的主机上进行容器监控。

我们在上一节中介绍了容器监控。当将容器日志与容器监控数据关联时,我们可以更好地理解系统,并轻松地缩小系统范围内的任何问题。

我发现以下两种方法在集中式容器日志记录中被广泛使用:

  • ELK 堆栈(Elastic 搜索、Logstash 和 Kibana):Elastic 搜索作为中央日志存储库,Logstash 作为代理导出容器数据,Kibana 作为日志记录的 GUI 前端。我在本章中没有介绍 ELK 堆栈。参考部分中的链接提供了有关为容器日志记录设置 ELK 堆栈的详细信息。

  • LogEntries:LogEntries 将容器代理、前端和中央日志服务器结合为一个集成解决方案。

还有一些其他工具,如 AWS Cloudwatch(aws.amazon.com/cloudwatch/)、Loggly(www.loggly.com)、Elastic(www.elastic.io/)和 Sematext Logsene(sematext.com/logsene/),它们提供容器日志记录功能。在使用 AWS Cloudwatch 进行容器监控时,我们可以根据 AWS 环境获得自定义钩子,并且它与其他云监控选项集成良好。

Docker 日志驱动程序

截至 Docker 1.7,Docker 支持以下日志驱动程序:

  • None:不进行日志记录。

  • Json-file:日志以 JSON 格式的文件存储。这是默认的日志记录选项。

  • Syslog:日志被发送到 syslog 服务器。

  • Journald:日志被发送到 journald 守护进程。Journald 与 systemd 集成。

  • Gelf:它将日志消息写入 GELF 端点,如 Graylog 或 Logstash。

  • Fluentd:它将日志消息写入 Fluentd。

  • Awslogs:这是 Amazon Cloudwatch 的日志记录驱动程序。

JSON-file 驱动程序

以下命令启动一个使用 json-file 日志驱动程序的 Docker 容器,最多保留 100 个文件,每个文件的大小不超过 1 MB:

docker run --name busyboxjsonlogger --log-driver=json-file --log-opt max-size=1m --log-opt max-file=100 -d busybox /bin/sh -c "while true; do echo hello world ; sleep 5 ; done"

在前述的 busyboxjsonlogger 容器中,我们持续发送 hello world 输出到标准输出。以下输出显示了 busyboxjsonloggerdocker logs 输出,其中我们可以看到 hello world 输出:

下面的命令可以执行以找出 json 日志文件的位置:

使用上述路径,我们可以直接转储 json 日志,提供额外的信息,如时间戳、流类型等:

Syslog 驱动程序

Syslog 驱动程序对将多个容器的消息汇总到运行 syslog 守护程序的单个服务器非常有用。

下面的命令可以用来启动 syslog 服务器作为容器。此命令将 syslog 服务器暴露给主机机器上的端口 5514

docker run -d -v /tmp:/var/log/syslog -p 5514:514/udp --name rsyslog voxxit/rsyslog

下面的命令可以用来启动一个带有 syslog 驱动程序选项的容器,将日志发送到前面指定的 syslog 服务器:

docker run --log-driver=syslog --log-opt syslog-address=udp://127.0.0.1:5514 --log-opt syslog-facility=daemon --log-opt tag="mylog" --name busyboxsysloglogger -d busybox /bin/sh -c "while true; do echo hello world ; sleep 5 ; done"

下面的输出显示了来自 syslog 服务器的 syslog:

docker exec rsyslog tail –f /var/log/messages

以下截图是前面命令的输出:

journald 驱动程序

journald 日志驱动程序将容器日志发送到 systemd 日志。可以使用 journalctl 命令检索日志条目。这在使用 journald 作为所有其他日志记录的 CoreOS 环境中运行良好。

下面的命令启动带有 journal 驱动程序的容器:

docker run --name busyboxjournallogger --log-driver=journald -d busybox /bin/sh -c "while true; do echo hello world ; sleep 5 ; done"

下面的命令显示以 CONTAINER_NAME 作为过滤器的 journalctl 日志:

journalctl CONTAINER_NAME=busyboxjournallogger

以下截图是前面命令的输出:

下面的命令显示以 JSON 格式显示的 journalctl 日志:

journalctl -o json CONTAINER_NAME=busyboxjournallogger --no-pager

以下截图是前面命令的输出:

日志条目

LogEntries 可用于收集运行容器的主机系统的日志,将其导出到中央日志服务器,并从中央服务器分析日志。以下图表描述了 LogEntries 容器架构的组件:

下面是 LogEntries 容器架构的一些注释:

  • LogEntries 容器在主机系统中运行。它使用 Docker API 收集容器统计信息、日志和事件,并将它们传输到中央服务器。

  • 基于令牌的系统可以用于聚合来自多个主机的容器日志。对于属于同一域的容器数据集,我们可以从 LogEntries 创建一个令牌,并在该域的每个主机中使用此令牌。每个主机中的 LogEntries 代理会使用该令牌与 LogEntries 服务器进行通信。LogEntries 会根据令牌聚合日志集。

  • 随着 LogEntries 收集容器统计信息,它还会显示一些容器监控数据,除了日志之外。

  • LogEntries 提供使用社区包的扩展功能。社区包提供了一种轻松共享搜索查询、标签、警报和小工具的方式。社区包遵循 JSON 结构,可以通过 LogEntries UI 轻松导入到 LogEntries 账户中。

  • LogEntries 提供免费和付费订阅。付费订阅提供额外的存储和企业级功能。

导出 CoreOS 日志

CoreOS 使用 journalctl 存储所有服务的日志。可以使用以下容器 (github.com/kelseyhightower/journal-2-logentries) 通过 SSL 将日志条目发送到 LogEntries 服务器。

以下是从 CoreOS 节点导出 journalctl 日志所需的步骤:

  1. 从 logentries 创建一个令牌。

  2. 在启动 journal-2-logentries 容器时,可以在服务文件中或 cloud-config 内部作为选项使用令牌。另一种选择是更新 etcd 中的令牌,CoreOS 集群中的所有节点都可以使用该令牌。

  3. 在 etcd 中更新令牌(例如,etcdctl set /logentries.com/token <token>)。

以下服务文件可以用于在集群的所有 CoreOS 节点上启动 journal-2-logentries 容器:

[Unit] Description=将 Systemd 日志转发到 logentries.com [Service] TimeoutStartSec=0 ExecStartPre=-/usr/bin/docker kill journal-2-logentries ExecStartPre=-/usr/bin/docker rm journal-2-logentries ExecStartPre=/usr/bin/docker pull quay.io/kelseyhightower/journal-2-logentries ExecStart=/usr/bin/bash -c \ "/usr/bin/docker run --name journal-2-logentries \ -v /run/journald.sock:/run/journald.sock \ -e LOGENTRIES_TOKEN=$(etcdctl get /logentries.com/token) \ quay.io/kelseyhightower/journal-2-logentries" [X-Fleet] Global=true

由于 logentries 容器使用 journald.sock,因此需要使用以下单元在 cloud-config 中导出该套接字:

    - name: systemd-journal-gatewayd.socket       command: start       enable: yes       content: |          [Unit]          Description=Journal 网关服务套接字          [Socket]          ListenStream=/var/run/journald.sock          Service=systemd-journal-gatewayd.service          [Install]          WantedBy=sockets.target

以下输出显示了在集群的所有 CoreOS 节点上运行的 journal-2-logentries 服务:

以下输出显示了在其中一个节点上运行的 journal-2-logentries 容器:

以下截图显示了 LogEntries 服务器前端的日志,包含来自 CoreOS 节点的日志条目:

容器日志

LogEntries 可用于导出容器日志、事件和统计信息。容器事件可以是容器启动、创建、停止和终止事件。容器日志是标准输出(stdout)和标准错误(stderr)日志。容器统计信息包括 CPU、内存、文件和网络 IO 相关的详细信息。

以下是从 CoreOS 节点导出容器统计信息和日志所需的步骤:

  1. 从 Logentries 创建一个令牌。

  2. 在服务文件中使用令牌,或者在启动 docker-logentries 容器时作为选项使用。另一种选择是将令牌更新到 etcd,CoreOS 集群中的所有节点都可以使用该令牌。

  3. 更新 etcd 中的令牌(例如,etcdctl set /logentries.com/token <token>)。

  4. 要查看 Docker 容器统计信息,需要使用 Docker 社区包。该包是一个 JSON 文件,可以从 community.logentries.com/packs/ 下载。以下说明 (logentries.com/doc/community-packs/) 可用于将 Docker 社区包导入 Logentries。

以下命令可用于启动 docker-logentries 容器:

docker run -v /var/run/docker.sock:/var/run/docker.sock logentries/docker-logentries -t <token>

以下服务文件可用于在所有 CoreOS 节点上启动 docker-logentries 容器:

[Unit] Description=将容器日志/统计信息转发到 logentries.com [Service] TimeoutStartSec=0 ExecStartPre=-/usr/bin/docker kill docker-logentries ExecStartPre=-/usr/bin/docker rm docker-logentries ExecStartPre=/usr/bin/docker pull logentries/docker-logentries ExecStart=/usr/bin/bash -c \ "/usr/bin/docker run --name docker-logentries \ -v /var/run/docker.sock:/var/run/docker.sock \ -e LOGENTRIES_TOKEN=$(etcdctl get /logentries.com/token) \ logentries/docker-logentries" [X-Fleet] Global=true

以下输出显示了 docker-logentries 服务在所有 CoreOS 节点上运行的情况:

以下输出显示了 docker-logentries 容器在其中一个节点上运行的情况:

我创建了多个容器,并停止并删除了一些容器,以生成不同的容器事件和日志。

以下输出显示了从 Docker 社区包接收到的仪表盘输出。仪表盘展示了容器事件的汇总以及容器监控数据:

以下输出显示了日志集视图,此外还展示了每个容器创建事件所创建的具体容器。在以下图片中,我们可以看到 Redis、WordPress 和 MySQL 容器的创建事件:

为了展示 LogEntries 的日志功能,我启动了以下容器,它会定期将 hello world 发送到标准输出(stdout):

docker run -d busybox /bin/sh -c "while true; do echo hello world; sleep 5; done"

以下输出显示了通过 busybox 容器名称过滤的日志,我们可以看到标准输出:

总结

在本章中,我们介绍了监控和调试 CoreOS 系统以及 Docker 容器的方法。监控工具需要将容器和主机系统作为一个整体来处理,而不是将其视为两个独立的实体,并能够提供容器视图和系统视图,同时关联这些数据。随着容器在多个主机上以数百或数千个实例部署,监控解决方案需要具备高度可扩展性。目前在调试和排除 CoreOS 系统及 Docker 容器故障方面有很多发展,许多公司都在努力解决这个问题。像 Sysdig 和 Logentries 这样的公司提供了很好的监控和日志解决方案。在下一章中,我们将讨论 CoreOS、Docker 容器和微服务的生产环境考虑事项。

参考文献

进一步阅读与教程

第十二章

第十一章。CoreOS 和容器 - 生产考虑因素

在开发环境和生产环境中运行应用程序和容器之间存在很大差异。生产环境面临一系列特殊挑战,主要涉及可伸缩性、高可用性、安全性和自动化。CoreOS 和 Docker 已解决了将应用程序从开发转移到生产中的重要挑战。本章将介绍微服务基础设施的生产考虑因素,包括部署、自动化和安全性。

本章将涵盖以下主题:

  • CoreOS 集群设计考虑因素

  • 分布式基础设施设计考虑因素 - 服务发现、部署模式、PaaS 和有状态与无状态容器

  • 安全考虑

  • 部署与自动化 - CI/CD 方法及使用 Ansible 进行自动化

  • CoreOS 和 Docker 路线图

  • 微服务基础设施 - 平台选择和解决方案提供者

CoreOS 集群设计考虑因素

集群规模和更新策略是 CoreOS 集群的重要设计考虑因素。

更新策略

CoreOS 的自动更新功能可保持集群节点安全和最新。CoreOS 提供多种更新机制以控制更新,用户可以根据自己的需求选择方法。我们在《第三章》(index_split_075.html#filepos216260),CoreOS 自动更新中详细介绍了更新策略的细节。一些客户更喜欢仅在维护窗口中进行更新,CoreOS 允许用户进行控制。

集群考虑因素

在选择 CoreOS 集群时需要考虑以下因素。我们在早期章节中已经涵盖了这些个别主题。

  • 集群规模:更大的集群规模提供更好的冗余性,但更新需要稍长时间。

  • 集群架构:我们需要根据集群用于开发还是生产选择架构。对于生产集群,首选方案是拥有一个小型主节点集群来运行关键服务,如 Etcd 和 Fleet,并且让工作节点指向主节点集群。工作节点仅用于运行应用容器。

  • Etcd 心跳和超时调整:这些参数值需要根据集群是本地还是地理分布式进行调整。

  • 节点备份与恢复:节点可能出现故障。需要定期备份。

  • 在集群中添加和移除节点:CoreOS 提供机制,可以动态添加和移除 Etcd 集群中的节点而不会丢失数据。这可以有机地扩展集群规模。

分布式基础架构设计考虑因素

在本节中,我们将涵盖一些未在早期章节中涉及的杂项基础设施设计考虑因素。

服务发现

微服务是动态的,服务发现指的是微服务如何动态地找到彼此。服务发现有三个组件:

  • 它会在服务启动时自动发现服务,并通过 DNS 使用服务名称访问服务

  • 它维护一个共享的服务数据库及其访问详情,可以从多个主机访问。

  • 它通过负载均衡器访问服务,并自动处理服务故障

使用容器编排系统(如 Kubernetes)时,服务发现会自动处理。对于没有编排系统的小型部署,我们可以通过使用独立工具手动进行。

我们在第四章中涵盖了服务发现部分,使用了 Sidekick 服务和 Etcd 来介绍 CoreOS 核心服务—Etcd、Systemd、Fleet。此方法未提供 DNS 查找。以下方法是另一种通过集成 DNS 来实现服务发现的方式。

使用 Registrator 和 Consul 进行服务发现

Consul(consul.io/)和 Gliderlabs 注册器(github.com/gliderlabs/docker-consul/tree/consul-0.4)结合使用提供自动服务发现和服务数据库。

下图展示了该模型:

以下几点展示了它是如何工作的:

  • Consul 提供服务发现、共享键值存储、基于 DNS 的服务查找以及服务健康监控

  • Gliderlabs 注册器监视 Docker 套接字,监听服务创建,并将注册信息通知 Consul

  • 由于 DNS 与 Consul 集成,服务可以通过服务名称访问

以下是在 Ubuntu Linux 机器上尝试此方法所需的步骤:

设置 Docker 守护进程使用 Docker 桥接 IP 作为其中一个 DNS 查找服务器。

将这一行添加到/etc/default/docker中:

DOCKER_OPTS="--dns 172.18.0.1 --dns 8.8.8.8 --dns-search service.consul"

重启 Docker 守护进程:

Sudo service docker restart

启动 Consul 服务器:

docker run -d -p 8400:8400 -p 8500:8500 -p 172.18.0.1:53:8600/udp -h node1 gliderlabs/consul-server -server --bootstrap

上述命令将端口8400暴露用于 rpc,8500用于 UI,8600用于 DNS。我们将 DNS 映射到 Docker 桥接 IP 地址(172.18.0.1),这样就可以直接从容器内部访问服务名称。在上一步中,我们将 Docker 桥接 IP 设置为 DNS 查找服务器之一。

启动 Gliderlabs 注册器:

docker run -d \`` --name=registrator \`` --net=host \`` --volume=/var/run/docker.sock:/tmp/docker.sock \`` gliderlabs/registrator:latest \`` consul://localhost:8500

在上述命令中,我们还指定了 Consul 的位置,以便注册器能够将服务注册到 Consul。

现在,让我们启动一些容器:

docker run -d -P --name=nginx nginx``docker run --name mysql -e MYSQL_ROOT_PASSWORD=mysql -d mysql``docker run --name wordpress --link mysql:mysql -d -P wordpress``docker run --name wordpress1 --link mysql:mysql -d -P wordpress

以下输出显示运行中的容器:

我们可以查看 Consul UI 来检查服务是否已注册。以下输出中可以看到 Consul、NGINX 和 WordPress 容器以及它们的 IP 地址和端口号:

我们可以通过跨容器访问服务,检查通过 DNS 名称查找服务是否正常工作。以下输出显示 NGINX 容器能够通过服务名称 wordpress 访问 WordPress 容器:

动态负载均衡

作为服务发现的一部分,负载均衡器应该能够自动找到活动的服务,并在活动实例之间进行负载均衡。例如,当启动了三个 Web 服务实例时,如果其中一个实例宕机,负载均衡器应该能够自动将该不活动实例从负载均衡列表中移除。

我发现以下两种方法对于实现这一目标非常有用。

使用 confd 和 nginx 进行负载均衡

www.digitalocean.com/community/tutorials/how-to-use-confd-and-etcd-to-dynamically-reconfigure-services-in-coreos 这一方法中,以下是步骤列表:

  1. Sidekick 服务将服务详情注册到 etcd

  2. Confd 监听 etctd 变更并更新 nginx.conf

  3. Nginx 负载均衡器根据 nginx.conf 中的条目进行负载均衡。

以下图示说明了使用 ETCD、HA DISCOVER 和 HAProxy 进行负载均衡:

使用 HAdiscover 和 HAproxy 进行负载均衡

adetante.github.io/articles/service-discovery-haproxy/ 这一方法中,以下是步骤列表:

  • Registrator 将服务详情注册到 etcd

  • HAdiscover 监听 etctd 变更并更新 haproxy.conf

  • HAproxy 基于 HAproxy 配置执行负载均衡。

以下图示说明了使用 ETCD、HA DISCOVER 和 NGINX 进行负载均衡:

部署模式

我们在第一章中讨论了微服务的优势。设计基于微服务的应用程序类似于面向对象编程,其中容器镜像可以比作类,容器可以比作对象。面向对象编程中有许多设计模式,指定如何将一个单体应用程序拆分为类,以及类如何与其他类协作。一些面向对象设计原则同样适用于微服务。

在第八章,容器编排中,我们讲解了 Kubernetes Pods 以及如何将紧密相关的容器分组到同一个 Pod 中。设计模式如侧车、代理和适配器模式被广泛用于创建 Pods。尽管这些设计模式是在 Kubernetes Pod 的上下文中提到的,但也可以在非 Kubernetes 系统中使用。

这个链接(blog.kubernetes.io/2015/06/the-distributed-system-toolkit-patterns.html)讲述了常见的 Kubernetes 组合模式。

以下是一些常见 Kubernetes 组合模式的更多细节。

侧车模式

在侧车模式中,两个依赖的容器完成一个单一的任务。

在下图中,健康检查容器监控 Web 容器并将结果更新到共享存储中,例如 ETCD,负载均衡器可以使用这些数据:

在下图中,Git 同步容器从 Git 服务器更新数据卷,Web 容器使用该数据卷来更新网页:

在下图中,Web 容器更新日志卷,日志容器读取该卷来更新中央日志服务器:

大使模式

大使模式用于当客户端容器需要访问不同类型的服务,而修改每个服务的客户端容器不高效时。代理容器将负责访问不同类型的服务,客户端容器只需要与代理容器通信。例如,redis 代理负责处理单个 redis 主节点场景,或有一个 redis 主节点和多个 redis 从节点的场景,而 redis 客户端则不需要了解 redis 服务的类型。

下图展示了 redis 客户端通过 redis 代理访问 redis 服务:

适配器模式

适配器模式是大使模式的反向。适配器模式的一个例子是服务容器暴露一个独立于服务中应用的公共接口。例如,一个监控或日志应用希望有一个公共接口来收集输入,而不考虑应用的类型。适配器容器负责将数据转换为监控或日志应用所期望的标准格式。

以下示例展示了一个监控/日志应用访问两个不同容器应用,每个应用都有自己的适配器:

滚动更新与金丝雀模式

这是当应用程序作为容器在多个服务器后面的负载均衡器后运行时使用的升级方式。在这种方法中,应用程序的升级会先在少数服务器上进行,然后根据客户的初步反馈,升级可以继续进行或撤回。

Kubernetes 支持使用金丝雀模式的滚动升级。在下面的示例中,我们将演示在 AWS 上的 CoreOS 集群上运行的 Kubernetes 如何使用金丝雀模式。在这里,我们将hello1-controller(其包含三个hello:v1容器副本)升级为hello2-controller,后者也包含三个hello:v2容器副本。

对于本示例,我们需要一个包含三个节点的 Kubernetes CoreOS 集群。安装说明可以在第八章《容器编排》中找到。

以下是一个包含一个主节点和两个工作节点的三节点集群:

以下是具有hello1容器镜像和三个副本的复制控制器hello1-controller.json

apiVersion: v1 kind: ReplicationController metadata: name: hello1 labels: name: hello spec: replicas: 3 selector: name: hello version: v1 template: metadata: labels: name: hello version: v1 spec: containers: - name: hello image: quay.io/kelseyhightower/hello:1.0.0 ports: - containerPort: 80

以下是使用hello1复制控制器的hello-s.json服务:

apiVersion: v1 kind: Service metadata: name: hello labels: name: hello spec: # 如果你的集群支持,请取消注释以下内容以自动创建 # 一个外部负载均衡 IP 给 hello 服务。 type: NodePort ports: # 该服务应提供的端口 - port: 80 selector: name: hello

让我们启动复制控制器和服务:

kubectl create -f hello1-controller.json kubectl create -f hello-s.json

让我们来查看正在运行的服务和 Pod:

我们来创建一个新的复制控制器,并执行金丝雀模式的滚动升级。以下是新的复制controllerhello2-controller.json,使用hello:2.0.0容器镜像:

apiVersion: v1 kind: ReplicationController metadata: name: hello2 labels: name: hello spec: replicas: 3 selector: name: hello version: v2 template: metadata: labels: name: hello version: v2 spec: containers: - name: hello image: quay.io/kelseyhightower/hello:2.0.0 ports: - containerPort: 80

以下命令执行滚动升级到hello2

kubectl rolling-update hello1 --update-period=10s -f hello2-controller.json

update-period参数指定每个 Pod 升级之间的时间间隔。

以下输出显示了每个 pod 如何从 hello1 升级到 hello2。最终,hello1 复制控制器被删除:

现在让我们来看一下正在运行的复制控制器。正如我们在以下输出中看到的,hello2 RC 正在运行,而 hello1 RC 已被删除:

Kubernetes 还支持回滚选项。如果在滚动升级过程中检测到问题,可以停止滚动升级,并使用 --rollback 选项进行回滚。

容器与 PaaS

传统上,服务架构有三种类型:

  • IaaS(基础设施即服务)

  • PaaS(平台即服务)

  • SaaS(软件即服务)

随着 Docker 的出现,PaaS 层变得有些难以定义。PaaS 服务商从一开始就将容器作为其基础技术。事实上,Docker 来源于 Dotcloud 公司,该公司曾提供 PaaS 服务。

以下图示描述了新型 PaaS 模式以及它们如何与传统 PaaS 模式和 IaaS 相结合:

以下是关于前述图示的一些注释,以及新型 PaaS 模式如何发展:

  • PaaS 通常用于简化应用程序部署,它允许应用程序开发人员仅专注于开发应用程序,而 PaaS 提供所需的基础设施服务,如高可用性、可扩展性和网络连接。PaaS 通常用于 Web 应用程序。

  • PaaS 通常以内部分部署为容器,尽管 PaaS 用户不需要了解这一点。

  • 尽管 PaaS 加速了应用程序的部署,但 PaaS 失去了灵活性。

  • 传统 PaaS 系统的示例包括 AWS Elastic Beanstalk、Google GAE、Openshift 和 Cloudfoundry。

  • 有一种新型的微型 PaaS,每个服务都以 Docker 容器的形式运行,这比传统的 PaaS 提供了更多的灵活性。示例有 Deis、Flynn 和 Tutum。Tutum 最近被 Docker 收购。

  • 使用 Docker 容器、如 Kubernetes 这样的容器编排系统和 CoreOS 等容器操作系统,客户可以更轻松地自行构建 PaaS 系统,从而获得最大的灵活性。亚马逊和谷歌都推出了容器服务,用户可以在其中运行自己的容器。用户也可以选择在自己的基础设施上构建容器服务。

有状态与无状态容器

无状态容器通常是 Web 应用程序,如 NGINX、Node.js 等。这些容器遵循 12-factor 应用开发方法论(12factor.net/)。这些容器可以进行水平扩展。有状态容器用于存储数据,如数据库在主机中的数据卷。常见的有状态容器包括 Redis、MySQL 和 MongoDB。在 第六章,《CoreOS 存储管理》中,我们讨论了容器数据持久化的选项。当有状态容器被迁移时,必须迁移与容器相关的数据。以下是迁移有状态容器的几种选项:

  • 使用类似 Flocker 的工具,当容器在主机之间迁移时,它负责处理数据卷和数据的迁移

  • 使用集群文件系统或 NFS,以便在多个主机之间共享相同的数据卷

如果实现有状态容器较为困难,另一种存储选择是将数据库与应用容器分离,并在专门的系统上运行。

安全性

以下是一些确保 CoreOS 集群安全的方法。

确保外部守护进程的安全

服务如 Etcd、Fleet 和 Docker 可以在外部访问。我们可以使用 TLS 以及客户端和服务器证书来保障客户端和服务器端的安全。在之前的章节中,我们讨论过一些关于单个服务的细节。如果我们使用 Kubernetes 这样的容器编排工具,需要确保 Kubernetes API 服务器采用 TLS 机制。

SELinux

SELinux 是 Linux 内核的一项特性,它允许在内核出现漏洞并导致黑客逃逸容器命名空间时,仍然能够隔离容器。CoreOS 808.0 版本开始支持 SELinux 集成。CoreOS 默认禁用 SELinux,但可以通过 coreos.com/os/docs/latest/selinux.html 中的过程启用它。存在一些限制,例如无法与 btrfs 文件系统以及共享数据卷的容器一起使用 SELinux。

容器镜像签名

Docker 支持使用 Docker 内容信任进行容器签名。Rkt 支持使用 GPG 进行镜像签名。通过这些方法,我们可以验证在 CoreOS 上运行的容器来自可靠的来源,并且容器镜像在传输过程中未被篡改。容器镜像签名的详细内容请参见 第七章,《与 CoreOS 的容器集成 – Docker 和 Rkt》。

部署与自动化

容器使得打包和运输软件变得简单,并且能够保证同一个容器在开发环境和生产环境中都能正常运行。将容器与良好的部署和自动化技术结合,将有助于更快速地部署软件。

持续集成与持续交付

传统的软件发布方法存在以下问题:

  • 软件发布周期间隔较长,导致新特性需要更长的时间才能到达客户。

  • 从开发阶段到生产阶段的大部分过程都是手动的。

  • 考虑到不同的部署场景,保证软件在所有环境和配置中都能正常运行是非常困难的。

容器已尝试缓解这些问题。通过使用微服务和容器方法,确保应用程序在开发和生产阶段的行为类似。对于基于容器的环境,过程自动化和适当的测试仍然是必要的。

持续集成(CI)指的是开发者完成单元测试和代码提交后,自动生成可执行文件或容器镜像的过程。

持续交付(CD)指的是将开发者构建的镜像,设置预发布环境以测试镜像,并成功地部署到生产环境的过程。

以下图示展示了 CI/CD 的步骤:

以下是上述图的一些注释:

  • 上述图中的第一行捕捉了 CI 的步骤,第二行则是 CD 的步骤。

  • CI 流程从开发人员在完成基本单元测试后提交代码开始。通常,GitHub 或 Bitbucket 被用作镜像仓库。

  • 镜像仓库和构建系统之间提供了钩子,可以在提交后自动触发构建。构建系统可以是像 Jenkins 这样的工具,它与不同的代码仓库集成。对于容器镜像,Dockerfiledocker build 将用于构建容器镜像。

  • 如果需要,在将镜像提交到镜像仓库之前,可以触发自动单元测试套件。

  • 构建本身需要在容器内进行,以消除对主机系统的依赖。

  • 镜像仓库可以是 Docker hub 或 Docker trusted registry 等容器镜像仓库。CoreOS 支持用于容器镜像的 Quay 仓库。

  • 一旦镜像推送到仓库,CD 过程就会自动触发。

  • 需要根据需要在预发布环境中设置不同的容器、存储和其他非容器软件。

  • QA 测试是在预发布环境中进行的。预发布环境必须尽可能接近生产环境。

  • 一旦 QA 测试成功,镜像就会部署到生产环境,如 AWS 或 GCE。如果是 PaaS 应用程序,则可以部署到 Cloudfoundry 等平台。

  • 有些公司提供集成的 CI/CD 解决方案,如 Codeship、CircleCI、Shippable 等。Docker 已发布一款名为 Universal Control Plane(UCP)的企业产品,专门针对 CD 部分。Jenkins 有 Docker 插件,用于在容器中构建镜像,并提供与 Docker hub 的集成。

  • 有不同的部署模式来进行升级。我们在早期章节中介绍了 Canary 部署模式。

Ansible 与 CoreOS 和 Docker 的集成

Ansible 是一个配置管理和自动化工具。Ansible 是一个非常流行的 DevOps 工具,与 Puppet 或 Chef 类似。Ansible 有一个独特的特性,即无需在设备端安装代理程序,这使其非常受欢迎。正在积极进行的工作是将 Ansible 与 CoreOS 和 Docker 集成。以下是一些集成可能性:

  • 使用 Ansible 管理 CoreOS 系统。由于 CoreOS 不预装 Python 且不能直接安装软件包,因此需要一些变通方法来使 Ansible 能够管理 CoreOS 系统。

  • Ansible 有一个 Docker 模块,简化了容器管理,如启动和停止容器以及控制容器属性。

  • 使用 Ansible 可以自动化 Docker 安装。除了自动化 Docker 安装外,Ansible 还可以管理其他主机基础设施,如日志记录、存储和网络。

  • Ansible 可以用来构建 Docker 镜像,而不是使用 Dockerfile。有一个docker_image模块(docs.ansible.com/ansible/docker_image_module.html),但建议不要使用它,因为其幂等性可能导致在某些情况下 Docker 镜像无法构建,这是一个问题。

使用 Ansible 管理 CoreOS

我按照coreos.com/blog/managing-coreos-with-ansible/的步骤来使用 Ansible 管理 CoreOS。由于 CoreOS 中没有包管理器,因此不能直接安装 Python。在github.com/defunctzombie/ansible-coreos-bootstrap中使用的方法是安装 PyPy,在用户目录中为 CoreOS 安装了一个简化的 Python 解释器,并让 Ansible 使用它。以下示例准备了 CoreOS 节点以由 Ansible 管理,并使用 Ansible 在节点中启动 Etcd 和 Fleet 服务。

以下是步骤:

  1. 在主机上安装 Ansible。在我的情况下,我在 Ubuntu 14.04 机器上运行的是 Ansible 1.9 版本。

  2. 创建 CoreOS 集群。

  3. 运行 CoreOS 引导角色,在 CoreOS 中安装 Python 解释器并更新系统 PATH 以使用它。Ansible 角色为特定任务创建 playbooks 的抽象。

  4. 运行 Ansible playbook 以启动 CoreOS 服务。Playbook 是一个 Ansible 任务列表。

设置 CoreOS 集群:

以下命令设置 CoreOS 集群。在本例中,创建了一个单节点集群:

git clone https://github.com/defunctzombie/coreos-ansible-example.git``cd coreos-ansible-example``vagrant up

设置无密码 SSH 访问:

使用以下命令设置无密码 SSH 访问。Ansible 需要无密码 SSH 访问。

./bin/generate_ssh_config

运行 CoreOS 引导角色:

以下命令使用 Ansible 角色 defunctzombie.coreos-bootstrap 设置一个包含 Python 的 CoreOS 节点:

ansible-galaxy install defunctzombie.coreos-bootstrap -p ./roles``ansible-playbook -i inventory/vagrant bootstrap.yml

我创建了以下 playbook 来启动 CoreOS 服务、Etcd2 和 Fleet:

//Coreos_services.yml: - name: CoreOS services   hosts: web   tasks:     - name: Start etcd2       service: name=etcd2.service state=started       sudo: true       sudo_user: root     - name: Start fleet       service: name=fleet.service state=started       sudo: true       sudo_user: root

在前面的 playbook 中,我们使用了 Ansible service 模块。Ansible 模块是执行特定任务的函数。Ansible 自带多个默认模块,用户可以扩展或编写自己的模块。

以下是我第一次启动 playbook 时的输出。清单文件包含了单个 CoreOS 节点的详细信息:

ansible-playbook –i inventory/vagrant  coreos-services.yml

.

以下是我再次运行相同 playbook 时的输出:

如我们所见,服务没有被重启,因为它们已经启动,并且 changed 变量未被设置。

以下输出展示了在 CoreOS 节点上运行的 Etcd2 和 Fleet 服务:

使用 Ansible 管理 Docker 容器

Ansible 为您提供了一个 Docker 模块(docs.ansible.com/ansible/docker_module.html),用于管理 Docker 容器。Docker 模块可以管理容器的生命周期,包括容器的启动和停止。由于 Ansible 模块是幂等的,我们可以在必要时仅拉取 Docker 镜像,并且仅在基础镜像发生变化时才重启容器。

以下是一个在我们之前运行 CoreOS 服务 playbook 的同一 CoreOS 节点上执行的 playbook。此 playbook 将在远程主机上安装 Docker 模块,并启动 NGINX 容器和包含 WordPress 和 MySQL 容器的 WordPress 服务:

//Coreos_containers.yml: - name: CoreOS Container   hosts: web   tasks:     - name: Install docker-py       pip: name=docker-py version=1.1.0     - name: pull container       raw: docker pull nginx     - name: launch nginx container       docker:         image: "nginx"         name: "example-nginx"         ports: "8080:80"         net: bridge         state: reloaded     - name: launch mysql container       docker:         image: mysql         name: mysql         pull: always         net: bridge         state: reloaded         env:             MYSQL_ROOT_PASSWORD: mysql     - name: launch wordpress container       docker:         image: wordpress         name: wordpress         pull: always         ports: 8000:80         net: bridge         state: reloaded         links:         - "mysql:mysql"

以下是第一次启动 playbook 时的输出:

以下截图显示了相同的 playbook 再次运行时的输出。正如我们所见,changed 标志未设置,因为所有容器都在运行,并且没有必要进行配置更改:

以下输出显示 CoreOS 节点中正在运行的容器:

注意

注意:Ansible Docker 模块中的 reloaded 标志应仅在基础镜像或配置标志发生变化时重启容器。我遇到了一个 bug,容器总是被重启。这里的链接 (github.com/ansible/ansible-modules-core/issues/1251) 说明了这个 bug。它的解决方法是指定 net 参数,正如我在前面的 playbook 中所做的那样。

reloadedpull 标志从 Ansible 1.9 版本开始可用。

Ansible 作为容器

提供了预安装了 Ansible 的公共容器镜像。这个链接,hub.docker.com/r/ansible/ubuntu14.04-ansible/,是一个预安装了 Ansible 的容器镜像示例。以下输出显示了正在运行的容器中的 Ansible 版本:

使用 Ansible 安装 Docker

Ansible 有一个角色(Roles)的概念,提供了一个良好的抽象来共享执行单一任务的 playbook 列表。Ansible 角色可以用于在 Linux 主机上安装 Docker。Ansible 角色保存在一个名为 Ansible Galaxy 的中央仓库中,可以供用户共享。Ansible Galaxy 类似于 Docker Hub,用于分享 Ansible 角色。

以下是需要执行的步骤:

  1. 从 Ansible Galaxy 本地安装 Ansible 角色。

  2. 创建一个包含此角色的 playbook 并运行它。

我使用这个 Galaxy 角色 (github.com/jamesdbloom/ansible-install-docker) 在我的 Ubuntu 节点上安装 Docker。Galaxy 中还有一些其他角色也能完成相同的任务。

使用以下命令安装角色:

ansible-galaxy install jamesdbloom.install-docker -p ./roles

创建一个包含角色的 install_docker1.yml playbook:

- name: install docker hosts: ubuntu gather_facts: True sudo: true roles: - jamesdbloom.install-docker

按如下方式运行 playbook:

ansible-playbook -i inventory/vagrant install_docker1.yml

以下是我的 inventory 文件:

## inventory 文件用于 vagrant 虚拟机``ubuntu-01 ansible_ssh_host=172.13.8.101``[ubuntu]``ubuntu-01``[ubuntu:vars]``ansible_ssh_user=vagrant

以下输出显示了 playbook 的输出:

Ansible-playbook –i inventory/vagrant install_docker1.yml

以下输出显示了使用前面的 playbook 在我的 Ubuntu 主机上安装的 Docker:

注意

注意:我遇到了重启 Docker 服务的问题。我通过 github.com/ansible/ansible-modules-core/issues/1170 中的过程解决了此问题,其中需要删除 init 文件。我在 Ansible 1.9.1 版本中遇到此问题,但在后来的 Ansible 版本中已经修复。

CoreOS 路线图

Ignition

Ignition (github.com/coreos/ignition) 项目正在开发中,用于设置初始的 CoreOS 文件系统,并解决了 coreos-cloudinit 一些问题。coreos-cloudinit 程序用于设置初始的 CoreOS 系统配置。以下是 coreos-cloudinit 一些已知的问题:

  • 动态环境变量的传递比较困难。这使得在 Openstack 环境和其他难以确定 IP 地址的环境中运行 CoreOS 变得困难。这个链接, groups.google.com/forum/#!topic/coreos-user/STmEU6FGRB4,描述了由于 IP 地址未被设置,cloud-config 服务在 Openstack 中无法正常工作的问题。

  • cloud-config 服务是串行处理的,我们不能指定依赖关系。

Ignition 在系统首次启动时运行一次,并写入必要的文件,如服务文件和配置数据。在第一次启动时,Ignition 从启动加载程序指定的特定位置读取配置。

Systemd 作为运行中的提供程序元数据服务文件的一部分,将创建 coreos-metadata.target,该文件将包含服务文件可以使用的必要环境变量。服务文件将指定此目标文件作为依赖关系,systemd 将处理这个依赖关系。

以下是一个示例 etcd2.service 文件,其中指定了 coreos-metadata.service 作为依赖关系。/run/metadata/coreos 环境文件将包含 COREOS_IPV4_PUBLIC,该变量将由 coreos-metadata.service 生成:

[Unit] Requires=coreos-metadata.service After=coreos-metadata.service [Service] EnvironmentFile=/run/metadata/coreos ExecStart= ExecStart=/usr/bin/etcd2 \     --advertise-client-urls=http://${COREOS_IPV4_PUBLIC}:2379 \     --initial-advertise-peer-urls=http://${COREOS_IPV4_LOCAL}:2380 \     --listen-client-urls=http://0.0.0.0:2379 \     --listen-peer-urls=http://${COREOS_IPV4_LOCAL}:2380 \     --initial-cluster=${ETCD_NAME}=http://${COREOS_IPV4_LOCAL}:2380

Ignition 将与 cloudinit 向后兼容。Ignition 还没有正式发布。

DEX

DEX 是一个由 CoreOS 启动的开源项目,用于身份管理,包括认证和授权。以下是 DEX 的一些特性:

  • DEX 使用 OpenID Connect (OIDC) (openid.net/connect/) 标准,构建在 OAuth 2.0 之上。OAuth 2.0 被 Google 用于登录他们的服务,如 Gmail。

  • DEX 使用连接器模块支持多个身份提供者。目前,DEX 支持使用本地服务器的本地连接器和像 Google 这样的 OIDC 连接器。

  • 有计划增加授权、用户管理以及多个其他连接器,如 LDAP 和 GitHub。

  • DEX 在 Tectonic 项目中用作身份提供者。

DEX 仍处于初期阶段,正在积极开发中。

Clair

Clair 是一个由 CoreOS 启动的开源项目,用于检测容器漏洞。以下是 Clair 的一些特点:

  • Clair 扫描存储在 Quay 容器仓库中的容器镜像,检测其中的漏洞。

  • 每个容器层包含该层中安装的包的信息,这些信息由相应的 Linux 包管理器提供。

  • Clair 通过查询与包管理器相关的文件,分析每个容器层,并将其与特定 Linux 发行版中的漏洞数据库进行比较,以检查该容器层是否存在漏洞。

  • Clair 会为每个容器层创建一个索引导向图,这加速了对共享层的多个容器镜像的分析。

  • Clair 当前支持 CentOS、Ubuntu 和 Debian Linux 发行版。

Clair 仍处于初期阶段,正在积极开发中。

Docker 路线图

Docker 已从提供容器运行时转变为容器平台。Docker 提供了围绕容器的开源解决方案和商业产品。

下图展示了截至 2015 年 11 月,围绕核心 Docker、安全性、编排、注册表和部署的不同 Docker 产品:

以下是 Docker 最近宣布的一些新项目。

Tutum

Tutum 使得构建、部署和管理容器化应用程序变得更加简单,并作为 SaaS 应用提供。一个应用程序可以是单容器应用或多容器应用。Tutum 与 Docker Hub 配合得很好。

UCP

UCP 是 Docker 提供的商业解决方案,用于提供本地容器部署解决方案。UCP 与 Docker 信任的注册表以及企业服务(如 LDAP 和基于角色的访问控制 RBAC)集成。UCP 还与 Docker 的所有其他服务(如网络、Compose 和 Swarm)集成。目前 UCP 正处于测试阶段。

Nautilus

该项目针对容器漏洞检测,类似于 CoreOS 的 Clair 项目。Nautilus 仍处于初期阶段。

微服务基础设施

在本节中,我们将概述微服务基础设施组件,并举例说明一些解决方案提供商。

平台选择

以下是一些微服务开发和部署过程中,客户需要做出的设计决策/平台选择。以下示例只是一个样本集,并未涵盖所有提供商。

IaaS vs PaaS:这个选择适用于本地数据中心以及云服务提供商。在前面的章节中,我们已经比较了容器和 PaaS 模型。在这里,权衡是灵活性与上市时间。

本地数据中心与云服务提供商:这主要是成本与时间的权衡。

基础操作系统:这里的选择是选择容器优化的操作系统,如 CoreOS、Rancher 或 Atomic,或传统的操作系统,如 Ubuntu 或 Fedora。对于纯微服务架构来说,容器优化的操作系统绝对值得追求。

VM 编排:VM 和容器有不同的用例,并将继续共存。在某些情况下,VM 将独立使用,或者容器将运行在 VM 之上。有一些开源解决方案,如 Openstack,以及来自 VMWare 的商业解决方案,用于 VM 编排。

容器运行时:这里的选择有 Docker、Rkt 或 LXC。

网络:容器编排系统(如 Kubernetes)通常集成了网络功能。由于网络支持是作为插件提供的,如果需要,可以用不同的实现进行替换。一些网络插件的例子包括 Weave、Calico 和 Contiv。

存储:我们需要评估专用存储与有状态容器之间的选择。有状态容器的选择包括 GlusterFS、Ceph 或 Flocker。

容器编排:这里的选择包括 Kubernetes、Docker Swarm、Mesos 等。

服务发现和 DNS:可以手动构建,使用前面部分提到的构建模块,或者选择诸如 Kubernetes 这样的容器编排系统,它已经集成了这一功能。

CI 和 CD:可以手动构建,或者使用来自 Codeship、CircleCI 或 Shippable 的打包解决方案。

监控和日志系统:例如 Sysdig 或 Logentries。我们在第十章,CoreOS 和容器 - 故障排除和调试中详细介绍了监控和日志。

解决方案提供商

正如我们在本书中所看到的,有许多硬件和软件组件构成了创建和部署微服务的基础设施。我们可以把每个组件看作是乐高积木,有许多方法将这些乐高积木组合在一起。客户有以下三种选择:

  • 自行集成所有基础设施组件

  • 选择集成这些组件并给出倾向性架构的解决方案提供商

  • 在前两个选项之间选择混合解决方案,我们可以选择参考架构,并根据特定需求替换几个组件

以下是一些商业和开源集成解决方案。此列表不全面,其中一些解决方案并未集成所有组件:

  • 来自 CoreOS 的 Tectonic Enterprise。

  • Google 容器服务

  • AWS 容器服务

  • Cisco 的 Mantl 项目

  • Openstack Magnum

总结

本章我们介绍了部署基于微服务的分布式基础设施的一些生产考量,包括 CoreOS、Docker 及其相关生态系统。谷歌、亚马逊和 Facebook 等云公司已经使用微服务和基于容器的技术相当长时间,并且他们根据自己的经验总结了最佳实践和坑点。

迄今为止的问题在于方法的重复以及缺乏共同的标准/方法。近年来的趋势是,这些公司以及许多初创公司(如 CoreOS 和 Docker)愿意开发技术并以开放的方式合作,帮助整个行业。开放源代码软件开发是这一趋势的重要推动力,许多大公司现在愿意以开放的方式开发软件。显然,围绕开源技术的商业解决方案将继续蓬勃发展,因为行业仍然需要通过盈利来生存。

容器技术和微服务是当前软件行业中最大的趋势。客户有很多选择,包括开源和商业解决方案。此时,需要将不同的技术/产品结合起来,创建一个完整的微服务基础设施解决方案。随着这些技术的成熟,具有可插拔架构的集成开源解决方案将在长期中占据优势。

参考文献

深入阅读和教程

posted @ 2025-07-05 15:46  绝不原创的飞龙  阅读(31)  评论(0)    收藏  举报