CoreOS-学习指南-全-

CoreOS 学习指南(全)

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

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

随着越来越多的应用程序向云端迁移并使用服务器虚拟化,快速部署用户应用和服务并确保其可靠性,借助合适的服务器部署服务并保证 SLA,变得尤为必要。特别是当这些服务具有动态特性时,情况更加复杂,这要求这些服务能够在一组节点上自动化配置和自动扩展。用户应用的编排不仅限于在正确的服务器或虚拟机上部署服务,更要扩展到提供跨这些服务的网络连接,从而实现基础设施即服务IaaS)。计算、网络和存储是云服务商在提供 IaaS 时必须管理的三大资源。目前,已有多种机制以更抽象的方式处理这些需求。有多个云编排框架可以管理计算、存储和网络资源。OpenStack、Cloud Stack 和 VMware vSphere 是一些能够编排这些资源池并提供 IaaS 的云平台。

虚拟机(VM)提供的服务器虚拟化需要在每个虚拟机上运行独立的操作系统实例,这会带来一定的开销。这降低了服务器上能够运行的虚拟机实例的数量,进而对运营成本产生重大影响。随着 Linux 命名空间、容器技术(如 docker 和 rkt)的流行,可以通过将应用服务部署在容器中而非虚拟机中,引入服务器虚拟化的另一层级。然而,容器或 Docker 在集群中部署服务、服务发现、服务参数管理、容器间的网络通信等方面需要一个编排和集群框架,CoreOS 就是为此目的而开发的。

CoreOS 是一个轻量级的云服务编排操作系统,基于 Google Chrome 开发。CoreOS 主要用于在一个节点集群上编排应用程序/服务。CoreOS 扩展了 Linux 提供的现有服务,使其能够在分布式集群上运行,而不仅限于单一节点。

本书涵盖的内容

第一章,CoreOS,另一个 Linux 发行版?,解释了容器、Docker 和 CoreOS 的高级架构基础。

第二章,设置 CoreOS 环境,将教你如何使用 Vagrant 和 VirtualBox 设置并运行 CoreOS 单机环境。它还包括如何创建和运行 docker 镜像,并熟悉重要的配置文件及其内容。

第三章, 创建 CoreOS 集群并管理集群,教你如何使用多台机器设置 CoreOS 集群。你还将学习如何发现机器以及如何在这些机器上调度服务。此外,你将了解如何使用 Fleet 启动和停止服务。

第四章, 使用用户定义约束管理服务,介绍了服务约束的概念,帮助在适当的成员上部署服务。

第五章, 在集群中发现正在运行的服务,解释了在集群中发现服务的需求和机制。你还将学习到两种广泛用于服务发现的重要工具:etcdctl 和 curl。

第六章, 服务链和跨服务的网络通信,解释了容器通信的重要性以及 CoreOS 和 Docker 提供的各种可能性来实现这种通信。

第七章, 使用 OVS 创建虚拟租户网络和服务链,解释了 OVS 在容器通信中的重要性以及 OVS 提供的各种优势。本章详细介绍了如何通过 OVS 将不同客户/租户在 CoreOS 集群中部署的服务进行链接/连接。

第八章, 下一步是什么?,涉及一些高级的 Docker 和 CoreOS 话题,并讨论了 CoreOS 的未来发展。

本书所需的内容

安装并启动一个示例 CoreOS 集群所需的软件如下。

本书适合的人群

本书适合云计算或企业管理员以及应用程序开发人员,尤其是那些希望学习 CoreOS 并在一组云服务器集群上部署云应用程序或微服务的人员。它还面向具有基本网络经验的管理员。您不需要具备 CoreOS 的任何知识。

约定

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

文本中的代码词汇、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 用户名以以下方式显示:“创建一个名为coreos-vagrant的目录,命令为git clone。”

一段代码如下所示:

[Unit]
Description=Example
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill busybox1
ExecStartPre=-/usr/bin/docker rm busybox1
ExecStartPre=/usr/bin/docker pull busybox
ExecStart=/usr/bin/docker run --name busybox1 busybox /bin/sh -c "while true; do echo Hello World; sleep 1; done"
ExecStop=/usr/bin/docker stop busybox1

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

weave status
...
 Service: dns
 Domain: weave.local.
 Upstream: 10.0.2.3
 TTL: 1
 Entries: 2

 Service: proxy
 Address: unix:///var/run/weave/weave.sock

新术语重要词汇以粗体显示。您在屏幕上看到的词语,例如菜单或对话框中的词语,以以下方式出现在文本中:“点击Datastore ISO File并从数据存储中选择上传的iso文件。”

注意

警告或重要提示会以如下框中的形式出现。

提示

提示和技巧以这种方式显示。

读者反馈

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

要向我们发送一般反馈,只需发送电子邮件至<feedback@packtpub.com>,并在邮件主题中提及本书标题。

如果您对某个领域有专业知识并且有兴趣撰写或参与书籍的编写,请参阅我们的作者指南:www.packtpub.com/authors

客户支持

现在,您是《Packt》书籍的骄傲拥有者,我们为您提供了一些帮助您最大化利用购买的内容。

下载本书的彩色图像

我们还为您提供了一份 PDF 文件,其中包含本书中使用的截图/图表的彩色图像。彩色图像将帮助您更好地理解输出中的变化。您可以从www.packtpub.com/sites/default/files/downloads/LearningCoreOS_ColorImages.pdf下载此文件。

勘误表

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

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

盗版

互联网盗版问题在所有媒体中都存在。我们在 Packt 非常重视保护我们的版权和许可。如果您在互联网上发现任何形式的非法复制品,请立即提供地址或网站名称,以便我们采取措施。

如果您发现涉嫌盗版的材料,请通过 <copyright@packtpub.com> 与我们联系,并提供相关链接。

我们感谢您在保护我们的作者和我们提供有价值内容方面的帮助。

问题

如果您在本书的任何方面遇到问题,可以通过 <questions@packtpub.com> 联系我们,我们将尽力解决问题。

第一章:CoreOS,另一种 Linux 发行版?

随着越来越多的应用程序迁移到云端并实现服务器虚拟化,显然需要在正确的服务器集群上快速、可靠地部署用户应用和服务,并确保提供服务水平协议(SLA)。当这些服务具有动态特性时,情况变得更加复杂,这就要求这些服务能够在一组节点上自动配置和自动扩展。用户应用的编排不仅仅限于在正确的服务器或虚拟机上部署服务,还扩展为在这些服务之间提供网络连接,从而提供基础设施即服务IaaS)。计算、网络和存储是云服务提供商为提供 IaaS 而需要管理的三种主要资源。目前,有多种机制可以更抽象地处理这些需求。有多个云编排框架可以管理计算、存储和网络资源。OpenStack、Cloud Stack 和 VMware vSphere 是一些执行这些资源池编排并提供 IaaS 的云平台。例如,OpenStack 中的 Nova 服务管理计算资源池并创建虚拟机;Neutron 服务提供必要的信息,以便在虚拟机之间提供虚拟网络连接;等等。

IaaS 云服务提供商应该按需向客户提供所有三种资源,并提供按需付费的模式。云服务提供商将这些资源作为一个资源池进行维护,并按需分配给客户。这为客户提供了灵活性,使他们可以根据业务需求启动和停止服务,从而节省运营支出(OPEX)。通常,在 IaaS 模型中,云服务提供商将这些资源提供为虚拟化资源,即计算使用虚拟机,网络使用虚拟网络,存储使用虚拟存储。运行在物理服务器/计算节点上的虚拟机监控器提供所需的虚拟化功能。

通常,当终端用户请求一个特定操作系统的 IaaS 服务时,云服务提供商会在其云服务器基础设施中创建一个新的虚拟机Virtual Machine),并安装用户所请求的操作系统。终端用户可以在这个虚拟机中安装他们的应用程序。当用户请求多个虚拟机时,云服务提供商还应提供这些虚拟机之间的必要网络连接,以便为这些虚拟机中运行的服务提供连接。云编排框架负责在集群中的可用计算节点之一上实例化虚拟机,并提供相关服务,如在这些虚拟机之间提供虚拟网络连接。一旦虚拟机被创建,像 Chef 或 Puppet 这样的配置管理工具可以用于在这些虚拟机上部署应用服务。从理论上讲,这种方法非常有效。

这种方法有三个主要问题:

  • 系统中的所有虚拟机都应该运行自己独立的操作系统副本,并具备独立的内存管理和虚拟设备驱动程序。任何部署在这些虚拟机上的应用程序或服务都将由虚拟机中运行的操作系统进行管理。当服务器上运行多个虚拟机时,每个虚拟机都运行单独的操作系统副本,这会导致 CPU 和内存的额外开销。此外,由于虚拟机需要运行自己的操作系统,因此启动/加载虚拟机所需的时间也非常长。

  • 该操作系统并不提供服务级虚拟化,即无法在一组虚拟机(VM)上运行服务或应用程序,这些虚拟机是集群的一部分。在虚拟机中运行的操作系统是一个通用操作系统,缺乏集群化概念,也无法在该集群上部署应用程序或服务。简而言之,操作系统提供的是机器级虚拟化,而非服务级虚拟化。

  • 从开发环境到生产环境部署一个服务/软件所需的管理工作非常繁重。这是因为每个软件包通常与其他软件存在依赖关系。软件包有成千上万种,每个软件包都有不同的配置集,并且大多数配置组合在性能和扩展性上都有依赖关系。

CoreOS 解决了所有这些问题。在了解 CoreOS 如何解决这些问题之前,我们先来简要介绍一下 CoreOS。

CoreOS 简介

CoreOS 是基于 Google 的 Chrome OS 的轻量级云服务编排操作系统。CoreOS 主要用于在节点集群上编排应用程序/服务。集群中的每个节点都运行 CoreOS,并且集群中的一个 CoreOS 节点将由 etcd 服务选举为主节点。集群中的所有节点都应该能够连接到主节点。系统中的所有从节点向主节点提供关于其系统内运行的服务列表以及配置参数的信息。为了做到这一点,我们可能需要配置 fleet 单元,以便当我们使用 fleetctl 命令启动 fleet 单元时,它应将其详细信息(如 IP 和端口)推送到 etcd 服务。主节点的责任是接收服务信息并将其发布到集群中的所有其他节点。在正常情况下,从节点不会相互交流关于服务可用性的信息。集群中所有节点上运行的 etcd 服务负责选举主节点。系统中的所有节点与主节点的 etcd 服务交互,以获取在所有其他节点上运行的服务和配置信息。以下图示展示了 CoreOS 集群架构,其中集群中的所有节点都运行 CoreOS,以及 CoreOS 的其他重要组件如 etcd、systemd 等。etcd 和 fleet 服务用于服务发现和集群管理。在此示例中,所有三个节点都配置有相同的集群 ID,以便这些节点都可以成为单个集群的一部分。一个节点无法成为多个集群的一部分。

CoreOS 简介

CoreOS 集群

所有应用程序或服务都作为 Linux 容器部署在 CoreOS 中。Linux 容器提供了一种轻量级的服务器虚拟化基础设施,而无需运行自己的操作系统或任何虚拟化程序。它使用主机操作系统提供的基于操作系统级别的虚拟化技术,使用命名空间的概念。这极大地改善了在物理服务器上运行的虚拟化实例的扩展性和性能。这解决了在虚拟机内部运行应用程序的第一个问题。

以下图示展示了在虚拟机内运行的应用程序与在 LXC 容器中运行的应用程序之间的区别。在以下图示中,虚拟机的虚拟化方式在虚拟机中安装了客户操作系统和主机操作系统。在基于 Linux 容器的实现中,容器没有独立的操作系统副本;相反,它使用主机操作系统提供的服务来处理所有与操作系统相关的功能。

CoreOS 简介

虚拟机与 Linux 容器的对比

CoreOS 扩展了 Linux 提供的现有服务,使其能够在分布式集群中工作,而不仅限于单一节点。例如,CoreOS 扩展了大多数 Linux 发行版提供的系统管理服务,使其能够在节点集群上启动、停止或重启应用程序/服务,而不是仅限于单个节点,并使用 fleet 工具进行管理。与仅在自身节点上运行应用程序不同,服务提交给 fleet,后者充当集群管理器,将服务实例化到集群中的任一节点上。通过应用约束,还可以在特定节点集合上启动容器。这解决了本章前面讨论的使用虚拟机的第二个问题。

CoreOS 使用 Docker/Rocket 作为容器,在 CoreOS 集群内部署服务。Docker 提供了一种简单的方法,将服务及其依赖模块打包成一个单一的单体镜像,可以从开发环境中直接迁移。在部署过程中,DevOps 人员只需从开发人员那里获取 Docker 容器,并可以直接将其部署到 CoreOS 节点,无需像构建编译环境、在目标平台上重新构建镜像等操作。这弥补了开发与服务部署之间的差距。它解决了本章前面讨论的使用虚拟机的第三个问题。

CoreOS 与其他 Linux 发行版

尽管 CoreOS 仍然是像 Fedora/Centos 这样的 Linux 发行版,但 CoreOS 与其他标准 Linux 发行版的关键区别如下:

  • CoreOS 并不设计用来直接运行任何应用程序或服务。任何要在 CoreOS 内部运行的应用程序都应该作为容器(可以是 Docker/Rocket)进行部署。因此,不可能在 CoreOS 中安装任何软件包,因此 CoreOS 没有像 yumapt 等这样的安装软件包。简而言之,CoreOS 是一个简化版的 Linux 发行版,没有内置的用户应用程序或库。

  • 大多数 Linux 发行版旨在作为主机操作系统运行,无论是在数据中心服务器中,还是在典型的桌面 PC 中。它们并非为了管理节点集群/云而开发;相反,它们是云的一部分,通常由其他云编排平台进行管理。然而,CoreOS 是一个为大规模服务器基础设施的管理而构建的 Linux 发行版,专注于集群管理。CoreOS 集群是由一组运行 CoreOS 并拥有相同集群 ID 的物理或虚拟机器组成。集群节点中运行的服务由 fleet 管理,fleet 是 CoreOS 的编排工具。传统 Linux 发行版中的软件更新通常是通过逐一更新软件包来完成的。然而,CoreOS 支持一种名为 快速补丁 的方案,其中整个 CoreOS 操作系统会一次性更新。CoreUpdate 程序用于在服务器、集群或整个数据中心中更新 CoreOS。

  • 与传统 Linux 发行版相比,CoreOS 极其轻量。

CoreOS 高级架构

CoreOS 集群中的节点包含以下主要组件:

  • etcd

  • systemd

  • fleet

  • Docker/Rocket 容器 CoreOS 高级架构

    CoreOS 高级架构

CoreOS 节点在集群中的所有节点上运行 etcd、systemd 和 fleet 服务。运行在所有节点上的 etcd 相互通信,并选举一个节点作为主节点。所有运行在节点内部的服务都会向该主节点进行广告,这使得 etcd 提供了一个服务发现机制。类似地,运行在不同节点上的 fleetd 维护着其服务池中不同节点上运行的服务列表,从而提供服务级别的编排。fleetctletcdctl 是分别用于配置 fleet 和 etcd 工具的命令行工具。

请参阅本章后续部分以详细了解每个组件的功能。

这些组件共同为 CoreOS 提供以下三大主要功能:

  • 服务发现

  • 集群管理

  • 容器管理

服务发现

在 CoreOS 环境中,所有用户应用程序都作为服务部署在容器内,这些容器可以是 Docker 容器或 Rocket 容器。由于不同的应用程序/服务作为独立的容器在 CoreOS 集群中运行,必然需要将每个节点提供的服务通告给集群中的所有节点。除了服务可用性之外,还要求每个服务向其他服务通告其配置参数。当服务紧密耦合并依赖于彼此时,这种服务通告变得尤为重要。例如,Web 服务需要知道数据库服务的详细信息,如连接字符串、数据库类型等。CoreOS 提供了一种方式,让每个服务使用 etcd 服务来通告其服务和配置信息。通告给 etcd 服务的数据将由主节点传递/通告给集群中的所有其他节点。

etcd

etcd 是一个分布式键值存储,用于在 CoreOS 集群中存储数据。etcd 服务用于将运行在某个节点上的服务发布给集群中的所有其他节点,以便集群内的所有服务发现其他服务及其配置信息。etcd 负责在集群节点中选举主节点。集群中的所有节点将其服务和配置信息发布到主节点的 etcd 服务,主节点再将这些信息提供给集群中的其他节点。

容器管理

CoreOS 构建模块的关键元素是一个容器,可以是 Docker 或 Rocket。CoreOS 的初始版本官方支持 Docker 作为运行 CoreOS 集群中任何服务应用程序的方式。在最近的版本中,CoreOS 支持一种新的容器机制,称为 Rocket,尽管 CoreOS 保持与 Docker 的向后兼容性。所有客户应用程序/服务都将作为容器部署在 CoreOS 集群中。当多个服务在同一服务器内为不同的客户运行时,必然需要将每个客户的执行环境隔离开来。通常,在基于虚拟机(VM)的环境中,每个客户将获得一个虚拟机,客户可以在该虚拟机中运行自己的服务,从而实现客户之间执行环境的完全隔离。容器也提供了一个轻量级的虚拟化环境,而无需运行虚拟机的单独副本。

Linux 容器

Linux 容器LXC)是由 Linux 内核提供的轻量级虚拟化环境,旨在提供系统级虚拟化,而无需运行虚拟机监控器(Hypervisor)。LXC 提供多个虚拟化环境,每个环境彼此隔离且不可见。因此,运行在某个 Linux 容器中的应用程序无法访问其他容器。

LXC 结合了三个主要概念来进行资源隔离,如下所示:

  • Cgroups

  • 命名空间

  • Chroot

以下图表详细说明了 LXC 及提供 LXC 支持所需的工具:

Linux Container

Linux 容器

Libvirt 是一个 'C' 库工具包,用于与 Linux 内核提供的虚拟化功能进行交互。它充当访问内核虚拟化层暴露的 API 的封装层。

cgroups

Linux cgroups 是内核提供的一项功能,用于限制进程或进程集合对系统资源的访问。Linux cgroup 提供了一种方式,可以为一组进程/任务保留或分配资源,如 CPU、系统内存、网络带宽等。管理员可以创建一个 cgroup,并为这些资源设置访问权限,然后将一个或多个进程绑定到这些组。这样可以对系统中不同进程的资源进行细粒度控制。下图对此进行了详细说明。左侧提到的资源被分组为两个不同的 cgroups,称为 cgroups-1 和 cgroups-2。task1 和 task2 被分配到 cgroups-1,这样只有为 cgroups-1 分配的资源才对 task1 和 task2 可用。

cgroups

Linux cgroups

管理 cgroups 包括以下步骤:

  1. 创建 cgroups。

  2. 根据问题陈述为 cgroup 分配资源限制。例如,如果管理员希望限制一个应用程序不消耗超过 50% 的 CPU,那么他可以相应地设置限制。

  3. 将进程添加到组中。

由于 cgroups 的创建和资源分配发生在应用程序上下文之外,作为 cgroup 一部分的应用程序将不会意识到 cgroups 及分配给该 cgroup 的资源级别。

命名空间

命名空间 是从 Linux 内核版本 2.6.23 开始引入的新特性,用于为一组进程提供资源抽象。在一个命名空间中的进程只能看到属于该命名空间的资源和进程。Linux 支持六种不同类型的命名空间抽象,具体如下:

  • PID/进程命名空间

  • 网络命名空间

  • 挂载命名空间

  • IPC 命名空间

  • 用户命名空间

  • UTS 命名空间

进程命名空间 提供了一种将进程从一个执行环境隔离到另一个执行环境的方法。属于一个命名空间的进程无法看到属于其他命名空间的进程。通常,在 Linux 操作系统中,所有进程都以树形结构维护,具有父子关系。该进程树的根节点是一个特殊的进程,称为 init 进程,其 process-id1。init 进程是系统中创建的第一个进程,随后创建的所有进程都将成为进程树中的子节点。进程命名空间引入了多个进程树,每个命名空间对应一棵进程树,从而提供了跨不同命名空间运行的进程之间的完全隔离。这也引入了一个概念:单个进程可以拥有两个不同的 pids:一个是全局上下文中的,另一个是命名空间上下文中的。这在下图中有详细说明。

在下图中,关于命名空间,所有进程都有两个进程 ID:一个是在命名空间上下文中的,另一个是在全局进程树中的。

命名空间

进程命名空间

网络命名空间 提供了操作系统为每个容器提供的网络栈的隔离。为每个命名空间隔离网络栈为在不同客户或容器之间运行多个相同服务(例如 Web 服务器)提供了一种方式。在下图中,连接到虚拟化管理程序的物理接口是系统中实际存在的物理接口。每个容器将被提供一个虚拟接口,该虚拟接口连接到虚拟化管理程序桥接进程。这个虚拟化管理程序桥接进程提供了容器间的互联,允许在一个容器中运行的应用程序与在另一个容器中运行的应用程序进行通信。

命名空间

网络命名空间

Chroot

Chroot 是 Linux 操作系统支持的一种操作,用于更改当前正在运行的进程的根目录,这会显著改变其子进程的根目录。更改根目录的应用程序将无法访问其他应用程序的根目录。Chroot 也称为 chroot 监狱

结合 Linux 内核中的 cgroups、命名空间和 chroot 功能,提供了一个复杂的虚拟化资源隔离框架,明确划分了系统中各个进程的数据和资源。

在 LXC 中,chroot 工具用于隔离文件系统,每个文件系统将被分配到一个容器中,容器提供了各自独立的根文件系统。容器中的每个进程将被分配到同一个 cgroup 中,每个 cgroup 拥有自己的资源,从而为容器提供资源隔离。

Docker

Docker 提供了一种便捷的方式,可以通过创建一个包含服务的单一对象,在任何 Linux 发行版中部署服务。随着服务的部署,所有依赖的服务也可以一同打包,并可以在任何基于 Linux 的服务器或虚拟机中部署。

Docker 在大多数方面与 LXC 相似。与 LXC 类似,Docker 是一种轻量级的服务器虚拟化基础设施,它将应用程序进程隔离运行,并进行资源隔离,如 CPU、内存、块 I/O、网络等。但除了隔离,Docker 提供了 “构建、分发和运行” 模型,其中任何应用程序及其依赖项都可以作为一个独立的虚拟化进程,在 Linux 操作系统提供的命名空间隔离中构建、分发和运行。

Docker 可以与以下任何云平台集成:Amazon Web Services、Google Cloud Platform、IBM Bluemix、Jelastic、Jenkins、Microsoft Azure、OpenStack Nova、OpenSVC,以及像 Ansible、CFEngine、Chef、Puppet、Salt 和 Vagrant 等配置工具。以下是 Docker 提供的主要功能。

Docker 的主要目标是支持微服务架构。在微服务架构中,一个单体应用将被分割成多个小的服务或应用(称为微服务),这些微服务可以独立部署在不同的主机上。每个微服务应当设计为执行特定的业务逻辑。在操作上,微服务之间应该有明确的边界,但每个微服务可能需要像之前描述的服务发现机制一样,暴露 API 给其他微服务。微服务的主要优点是快速开发和部署、易于调试,以及系统中不同组件的并行开发。微服务的一个主要优势是:根据复杂性、瓶颈、处理能力和可扩展性的需求,每个微服务都可以单独扩展。

Docker 与 LXC 对比

Docker 是为应用程序部署而设计的,而 LXC 是为机器部署而设计的。LXC 容器被视为一台机器,任何应用程序都可以在容器内部署和运行。Docker 的设计目的是运行特定的服务或应用程序,提供容器作为应用程序。然而,当一个应用程序或服务依赖于其他服务时,这些服务也可以与相同的 Docker 镜像一起打包。通常,Docker 容器并不会提供任何操作系统所提供的所有服务,如初始化系统、syslog、cron 等。由于 Docker 更专注于应用程序的部署,它提供了工具来创建 Docker 容器,并使用源代码部署服务。

Docker 容器设计为具有分层架构,每一层都包含来自上一版本的更改。分层架构使 Docker 能够维护完整容器的版本。就像任何典型的版本控制工具(如 Git/CVS),Docker 容器也是通过提交、回滚、版本跟踪、版本对比等操作来维护不同版本的。对 Docker 应用程序所做的任何更改都会以只读层的形式存在,直到它被提交。

Docker Hub 包含超过 14,000 个可用于各种著名服务的容器,可以非常容易地下载和部署。

Docker 提供了一种高效的机制来链接不同的 Docker 容器,从而提供良好的服务链路机制。不同的 Docker 容器可以通过以下不同机制相互连接:

  • Docker 链接

  • 使用 docker0 桥接

  • 使用 Docker 容器使用主机网络栈

每种机制都有其自身的优点。有关服务链路的更多信息,请参见第七章,使用 OVS 创建虚拟租户网络和服务链路

Docker 使用 libcontainer,直接访问内核的容器调用,而不是创建 LXC。

Docker 与 LXC 比较

Docker 与 LXC 的比较

Rocket

从历史上看,CoreOS 的主要目标是以轻量级容器的形式运行服务。Docker 的原则是与 CoreOS 服务需求对接,以简单和可组合的单元作为容器。后来,Docker 添加了越来越多的功能,使 Docker 容器提供比传统容器更多的功能,这些功能包括构建覆盖网络、用于启动集群云服务器的工具、构建镜像、运行和上传镜像等。这使得 Docker 更像一个平台,而不仅仅是一个简单的容器。

在上述场景下,CoreOS 开始研发一种新的 Docker 替代方案,目标包括:

  • 安全性

  • 可组合性

  • 速度

  • 镜像分发

CoreOS 宣布开发 Rocket 作为 Docker 的替代方案,以满足之前提到的需求。随着 Rocket 的开发,CoreOS 还开始着手开发应用容器规范(App Container Specification)。该规范解释了容器的特性,如镜像格式、运行环境、容器发现机制等。CoreOS 于 2014 年 12 月发布了 Rocket 的第一个版本,并同时发布了应用容器规范。

CoreOS 集群管理:

集群是将一组机器组合成一个单一逻辑系统(称为集群)的概念,以便应用程序可以部署到集群中的任何一台机器上。在 CoreOS 中,集群是 CoreOS 提供的主要特性之一,通过在机器集群上运行不同的服务/docker 容器来实现。历史上,在大多数 Linux 发行版中,服务可以通过 systemd 工具进行管理。CoreOS 通过使用 fleet 工具将 systemd 服务从单节点扩展到集群。CoreOS 选择 fleet 来编排 CoreOS 集群中的服务的主要原因如下:

  • 性能

  • 日志支持

  • 部署服务的丰富语法

只要集群中的所有节点彼此连接并可达,也可以将物理服务器与虚拟机组合在一起构建 CoreOS 集群。所有希望参与 CoreOS 集群的节点应运行具有相同集群 ID 的 CoreOS。

systemd

systemd 是一个初始化系统工具,用于停止、启动和重启任何 Linux 服务或用户程序。systemd 有两个主要术语或概念:unit 和 target。unit 是一个包含要启动的服务配置的文件,target 是一个分组机制,用于将多个服务分组,以便同时启动。

fleet

fleet 模拟集群中的所有节点,使它们成为单一的初始化系统或系统服务的一部分。fleet 在集群级别控制 systemd 服务,而不是在单个节点级别控制,这使得 fleet 可以管理集群中任何节点上的服务。fleet 不仅在集群内部实例化服务,还管理在集群中某个节点发生故障时,如何将服务从一个节点移动到另一个节点。因此,fleet 保证服务在集群中的任何一个节点上都能运行。fleet 还可以负责限制服务只能部署在集群中特定的节点或节点集合上。例如,如果集群中有十个节点,并且在这十个节点中,一个特定的服务(例如 Web 服务器)只需在三台服务器上部署,那么在 fleet 实例化服务时,可以强制实施此限制。这些限制可以通过提供有关如何在集群中分配这些作业的信息来施加。fleet 有两个主要术语或概念:引擎和代理。有关 systemd 和 fleet 的更多信息,请参阅《创建 CoreOS 集群和管理集群》章节。

CoreOS 和 OpenStack

CoreOS 是不是另一个像 OpenStack/CloudStack 那样的编排框架?不是,CoreOS 不是像 OpenStack/CloudStack 那样的独立编排框架。在大多数服务器编排框架中,框架位于托管云的外部。但在 CoreOS 中,编排框架与现有的业务解决方案一起存在。

OpenStack 是一种广泛使用的云计算软件平台,用于提供 IaaS。OpenStack 用于编排云的计算、存储和网络实体,而 CoreOS 用于服务编排。一旦计算、存储或网络实体被实例化,OpenStack 就不再参与这些虚拟机内部服务的实例化。

将 OpenStack 和 CoreOS 提供的编排功能结合起来,提供了强大的 IaaS,其中云服务提供商将在服务编排之前具有精细的控制。因此,CoreOS 可以与 OpenStack 共存,其中 OpenStack 可以实例化一组运行 CoreOS 实例的虚拟机,并形成一个 CoreOS 集群。也就是说,OpenStack 可用于创建作为基础设施的 CoreOS 集群。运行在虚拟机中的 CoreOS 形成一个集群,并在集群中的任何节点上实例化服务。

CoreOS 和 OpenStack

OpenStack 和 CoreOS

在前面的图示中,OpenStack 用于管理由三台服务器(server1、server2 和 server3)组成的服务器集群。当客户请求一组虚拟机时,OpenStack 会在这些服务器中的任何一台上创建所需的虚拟机,作为 IaaS 提供。借助 CoreOS,所有这些虚拟机都运行相同集群 ID 的 CoreOS 镜像,因此可以成为同一个集群的一部分。在前面的图示中,有两个 CoreOS 集群,每个集群分配给不同的客户。要在这些虚拟机上运行的服务/应用程序将由 CoreOS 的 fleet 服务实例化,该服务负责在集群中的任何虚拟机上实例化服务。在任何时候,OpenStack 都可以在集群内部实例化新的虚拟机,通过添加新的虚拟机来扩展集群容量,这些虚拟机运行相同集群 ID 的 CoreOS 镜像,并成为 CoreOS 运行新服务的候选虚拟机。

摘要

CoreOS 和 Docker 开创了一个新纪元,使在集群中部署服务更加简便,从而简化了应用程序的开发和部署过程。CoreOS 和 Docker 架起了开发服务与在生产环境中部署服务之间的桥梁,使服务器和服务的部署工作更轻松,工作量也减少。通过轻量级容器,CoreOS 提供了非常好的性能,并且为自动扩展应用程序提供了简便的方法,减少了操作员的负担。在本章中,我们了解了容器、Docker 的基础知识以及 CoreOS 的高层架构。

在接下来的几章中,我们将详细了解 CoreOS 的各个构建模块。

第二章:设置 CoreOS 环境

CoreOS 可以安装在多种平台上,如裸金属服务器、云提供商的虚拟机、物理服务器等。本章详细描述了如何在虚拟机上部署 CoreOS,帮助你启动第一个 CoreOS 环境。当在虚拟化环境中进行部署时,像 Vagrant 这样的工具在管理 CoreOS 虚拟机时非常有用。Vagrant 使得即使在单个笔记本电脑或工作站上,也能轻松设置多个节点的 CoreOS 环境,且配置最小化。Vagrant 支持 VirtualBox,这是一个常用的虚拟化应用程序。Vagrant 和 VirtualBox 都支持多种架构,如 Intel 或 AMD,以及多种操作系统,如 Windows、Linux、Solaris 和 Mac。

本章介绍了在 VirtualBox、VMware VSphere 上设置 CoreOS,以及以下内容:

  • VirtualBox 安装

  • Vagrant 介绍

  • VMware VSphere 上的 CoreOS 设置

  • Docker 介绍

提示

GIT 用于下载本章提到的所有所需软件。

安装 GIT

根据主机操作系统,从 www.vagrantup.com/downloads.html 下载最新版本的 GIT 安装包。下载完成后,启动安装程序。使用此方法安装 GIT 对 Mac 和 Windows 系统有帮助。对于所有 Linux 发行版,GIT 客户端可以通过其包管理器安装。例如,如果操作系统是 CentOS,可以使用包管理器 yum 安装 GIT。

安装 VirtualBox

根据主机操作系统和架构,从 www.virtualbox.org/wiki/Downloads 下载最新版本的 VirtualBox。下载完成后,启动安装程序。

安装过程中,继续使用默认选项。VirtualBox 安装过程中会重置主机机器的网络适配器,这将导致网络连接切换。安装成功后,安装程序将打印操作状态。

Vagrant 介绍

Vagrant 提供了一种机制来安装和配置开发、测试或生产环境。Vagrant 可以与多种虚拟化应用程序配合使用,如 VirtualBox、VMware、AWS 等。所有的安装、设置信息、配置和依赖项都保存在文件中,虚拟机可以通过简单的 Vagrant 命令进行配置并启动。这也有助于使用常见的脚本语言自动化机器的安装和配置过程。Vagrant 帮助创建一个在所有用户和部署中完全相同的环境。Vagrant 还提供了简单的命令来管理虚拟机。在 CoreOS 的上下文中,Vagrant 将帮助轻松创建具有相同环境的多个 CoreOS 集群节点。

安装 Vagrant

www.vagrantup.com/downloads下载并安装最新版本的 Vagrant。在安装过程中选择默认设置。

Vagrant 配置文件

Vagrant 配置文件包含虚拟机的配置和预配置信息。配置文件名为Vagrantfile,文件语法为Ruby。配置文件可以出现在任何目录级别,从当前工作目录开始。首先读取当前工作目录中的文件,然后是上一级目录(如果存在)中的文件,依此类推,直到根目录/。文件在读取时会合并。对于大多数配置参数,新的设置会覆盖旧的设置,只有少数参数会被追加。

一个Vagrantfile模板及其他相关文件可以从 GIT 仓库中克隆github.com/coreos/coreos-vagrant.git)。通过终端运行以下命令来克隆仓库。请注意,启动终端的过程可能因操作系统不同而有所不同。例如,在 Windows 中,运行 GIT 命令的终端是通过运行Git Bash启动的:

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

git clone之后,会创建一个目录coreos-vagrant。该目录除了包含与Git仓库相关的其他文件外,还包含Vagrantfileuser-data.sampleconfig.rb.sample文件。将user-data.sample重命名为user-data,并将config.rb.sample重命名为config.rb

git clone https://github.com/coreos/coreos-vagrant/
Cloning into 'coreos-vagrant'...
remote: Counting objects: 402, done.
remote: Total 402 (delta 0), reused 0 (delta 0), pack-reused 402
Receiving objects: 100% (402/402), 96.63 KiB | 31.00 KiB/s, done.
Resolving deltas: 100% (175/175), done.

cd coreos-vagrant/
ls
config.rb.sample*  CONTRIBUTING.md*  DCO*  LICENSE*  MAINTAINERS*  NOTICE*  README.md*  user-data.sample*  Vagrantfile*

Vagrantfile包含创建和配置 CoreOS 虚拟机的模板配置,使用的是 VirtualBox。Vagrantfile通过require指令包含了config.rb文件:

…
CONFIG = File.join(File.dirname(__FILE__), "config.rb")
….
if File.exist?(CONFIG)
 require CONFIG
end
…

…
CLOUD_CONFIG_PATH = File.join(File.dirname(__FILE__), "user-data")
…
 if File.exist?(CLOUD_CONFIG_PATH)
 config.vm.provision :file, :source => "#{CLOUD_CONFIG_PATH}", :destination => "/tmp/vagrantfile-user-data"
 config.vm.provision :shell, :inline => "mv /tmp/vagrantfile-user-data /var/lib/coreos-vagrant/", :privileged => true
 end
…

Cloud-config

cloud config文件是特殊的文件,在 CoreOS 系统启动或配置动态更新时,会被cloud-init进程执行。通常,cloud config 文件包含 docker 容器的各种操作系统级配置,例如网络配置、用户管理、systemd 单元等。对于 CoreOS 来说,user-datacloud-config文件的名称,并且位于vagrant文件夹的基础目录中。systemd units文件是包含关于进程信息的配置文件。

cloud-config文件使用 YAML 文件格式。一个cloud-config文件必须包含#cloud-config作为第一行,之后是一个关联数组,包含零个或多个以下键:

  • coreos:这个键提供了 CoreOS 提供的服务配置。接下来描述了其中一些重要服务的配置:

    • etc2:这个键替代了先前使用的etc服务。etc2的参数用于生成etcd2服务的 systemd 单元文件。一些etc2配置的重要参数包括:

      discovery:指定用于标识所有形成集群的 etcd 成员的唯一令牌。可以通过访问免费的发现服务 (discovery.etcd.io/new?sizhttp://e=<clustersize>) 来生成唯一令牌。当使用发现机制识别集群的 etcd 成员时(例如,所有节点的 IP 地址尚不清楚时),将使用此令牌。生成的令牌也称为发现 URL。发现服务通过存储已连接的 etcd 成员、集群大小和其他元数据,帮助集群之间相互连接,并通过每个成员提供的 initial-advertise-peer-urls 进行连接。有关如何形成 CoreOS 集群的更多信息,请参阅 第三章,创建你的 CoreOS 集群并管理集群

      initial-advertise-peer-urls:指定成员自己向集群广告的对等 URL。该 IP 应该对所有 etcd 成员可访问。根据可访问性,可以使用公共和/或私有 IP。

      advertise-client-urls:指定成员自己向集群广告的客户端 URL。该 IP 应该对所有 etcd 成员可访问。根据可访问性,可以使用公共和/或私有 IP。

      listen-client-urls:指定成员监听客户端流量的自我 URL 列表。所有广告的客户端 URL 应该是此配置的一部分。

      listen-peer-urls:指定成员监听对等流量的自我 URL 列表。所有广告的对等 URL 应该是此配置的一部分。

      在某些平台上,可以通过使用模板功能来自动化提供 IP。可以提供字段 $public_ipv4$private_ipv4,而无需提供实际的 IP 地址。

      $public_ipv4 是机器的公共 IPV4 地址的替代变量。

      $private_ipv4 是机器的私有 IPV4 地址的替代变量。

      以下是 cloud-config 文件中的示例 coreos 配置:

      #cloud-config
      coreos:
       etcd2:
       discovery: https://discovery.etcd.io/d54166dee3e709cf35b0d78913621df6
       # multi-region and multi-cloud deployments need to use $public_ipv4
       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:fleet 的参数用于为 fleet 服务生成环境变量。fleet 服务管理集群中容器的运行。fleet 配置的一些重要参数包括:

      etcd_servers:提供可以通过其访问 etcd 服务的 URL 列表。配置的 URL 应该是 etcd 服务的 listen-client-urls 之一。

      public_ip:应该与本地机器状态一起发布的 IP 地址。

      以下是 cloud-config 文件中的示例 fleet 配置:

      #cloud-config
       fleet:
       etcd_servers: http:// $public_ipv4:2379,http:// $public_ipv4:4001
       public-ip: $public_ipv4
      
      
    • flannel:flannel 的参数用于为 flannel 服务生成环境变量。flannel 服务提供容器之间的通信。

    • locksmith:locksmith 的参数用于生成 locksmith 服务的环境变量。locksmith 服务提供集群的重启管理。

    • update:这些参数用于操作与 CoreOS 实例更新相关的设置。

    • Units:这些参数指定系统启动后需要启动的 systemd 单元集。单元配置的一些重要参数包括:

      name:该参数指定服务的名称。

      command:该参数指定在单元上执行的命令:start、stop、reload、restart、try-restart、reload-or-restart、reload-or-try-restart。

      enable:此标志(true/false)指定是否忽略单元文件中的 Install 部分。

      drop-ins:此项包含单元的 drop-in 文件列表。每个单元信息集包含name,指定单元的 drop-in 文件,和content,这是表示单元 drop-in 文件的纯文本。

      以下是cloud-config文件中的单元配置示例:

      #cloud-config
       units:
       - name: etcd2.service
       command: start
       - name: fleet.service
       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
      
      
  • ssh_authorized_keys:此参数指定将被授权的核心用户的公共 SSH 密钥。

  • hostname:该参数指定成员的主机名。

  • users:该参数指定要在成员上创建或更新的用户列表。每个用户信息包含姓名、密码、主目录、shell 等。

  • write_files:该参数指定要在成员上创建的文件列表。每个文件信息包含路径、权限、所有者、内容等。

  • manage_etc_hosts:该参数指定本地名称解析的/etc/hosts文件的内容。目前仅支持 localhost。

config.rb 配置文件

该文件包含用于配置 CoreOS 集群的信息。此文件为Vagrantfile使用的参数提供配置值。Vagrantfile通过包含config.rb文件来访问配置。以下是参数:

  • $num_instances:此参数指定集群中节点的数量。

  • $shared_folders:该参数指定主机上共享文件夹的路径列表,以及成员上的相应路径。

  • $forwarded_ports:该参数指定从成员到主机的端口转发。

  • $vm_gui:此标志指定是否为成员设置 GUI。

  • $vm_memory:该参数指定成员的内存大小(以 MB 为单位)。

  • $vm_cpus:该参数指定要分配给成员的 CPU 数量。

  • $instance_name_prefix:该参数指定用于成员名称的前缀。

  • $update_channel:该参数指定 CoreOS 的更新渠道(如 alpha、beta 等)。

以下是一个config.rb文件示例:

$num_instances=1
$new_discovery_url="https://discovery.etcd.io/new?size=#{$num_instances}"

# To automatically replace the discovery token on 'vagrant up', uncomment
# the lines below:
#
#if File.exists?('user-data') && ARGV[0].eql?('up')
#  require 'open-uri'
#  require 'yaml'
#
#  token = open($new_discovery_url).read
#
#  data = YAML.load(IO.readlines('user-data')[1..-1].join)
#  if data['coreos'].key? 'etcd'
#    data['coreos']['etcd']['discovery'] = token
#  end
#  if data['coreos'].key? 'etcd2'
#    data['coreos']['etcd2']['discovery'] = token
#  end
#
#  # Fix for YAML.load() converting reboot-strategy from 'off' to false`
#  if data['coreos']['update'].key? 'reboot-strategy'
#     if data['coreos']['update']['reboot-strategy'] == false
#          data['coreos']['update']['reboot-strategy'] = 'off'
#       end
#  end
#
#  yaml = YAML.dump(data)
#  File.open('user-data', 'w') { |file| file.write("#cloud-config\n\n#{yaml}") }
#end

$instance_name_prefix="coreOS-learn"
$image_version = "current"
$update_channel='alpha'
$vm_gui = false
$vm_memory = 1024
$vm_cpus = 1
$shared_folders = {}
$forwarded_ports = {}

启动 CoreOS VM 使用 Vagrant

一旦 config.rbuser-config 文件更新了实际的配置参数,在包含配置文件的目录中执行命令 vagrant up 来启动 CoreOS 虚拟机镜像。成功执行 vagrant up 命令后,虚拟机环境中的 CoreOS 已准备好:

vagrant up
Bringing machine 'core-01' up with 'virtualbox' provider...
==> core-01: Checking if box 'coreos-alpha' is up to date...
==> core-01: Clearing any previously set forwarded ports...
==> core-01: Clearing any previously set network interfaces...
==> core-01: Preparing network interfaces based on configuration...
 core-01: Adapter 1: nat
 core-01: Adapter 2: hostonly
==> core-01: Forwarding ports...
 core-01: 22 => 2222 (adapter 1)
==> core-01: Running 'pre-boot' VM customizations...
==> core-01: Booting VM...
==> core-01: Waiting for machine to boot. This may take a few minutes...
 core-01: SSH address: 127.0.0.1:2222
 core-01: SSH username: core
 core-01: SSH auth method: private key
 core-01: Warning: Connection timeout. Retrying...
==> core-01: Machine booted and ready!
==> core-01: Setting hostname...
==> core-01: Configuring and enabling network interfaces...
==> core-01: Machine already provisioned. Run `vagrant provision` or use the `--provision`
==> core-01: flag to force provisioning. Provisioners marked to run always will still run.

vagrant status
Current machine states:

core-01                   running (virtualbox)

虚拟机正在运行。要停止此虚拟机,可以运行 vagrant halt 强制关闭它,或者可以运行 vagrant suspend 仅暂停虚拟机。在任何情况下,要重新启动它,只需运行 vagrant up

在 VMware vSphere 上设置 CoreOS

VMware vSphere 是一个服务器虚拟化平台,使用 VMware 的 ESX/ESXi 虚拟机管理程序。VMware vSphere 提供了完整的平台、工具集和虚拟化基础设施,以在裸金属上提供和管理虚拟机。VMware vSphere 由 VMware vCenter Server 和 VMware vSphere Client 组成。VMware vCenter Server 管理虚拟资源和物理资源。VMware vSphere Client 提供一个图形界面,用于在裸金属上安装和管理虚拟机。

安装 VMware vSphere Client

根据主机操作系统和架构,从 vsphereclient.vmware.com/vsphereclient/1/9/9/3/0/7/2/VMware-viclient-all-5.5.0-1993072.exe 下载 VMware vSphere Client 安装程序的最新版本。下载完成后,开始安装。安装过程中,继续使用默认选项。

安装完成后,打开 VMware vSphere Client 应用程序。这将打开一个新的图形界面。在 IP 地址 / 名称 字段中,输入 IP 地址/主机名,以直接管理单个主机。输入 vCenter Server 的 IP 地址/主机名,以管理多个主机。在 用户名密码 字段中,输入用户名和密码。

stable.release.core-os.net/amd64-usr/current/coreos_production_vmware_ova.ova 下载最新版本的 CoreOS 镜像。下载完成后,下一步是使用下载的 ova 文件创建虚拟机镜像。创建虚拟机镜像的步骤如下:

  1. 打开 VMware vSphere Client 应用程序。

  2. 输入之前提到的 IP 地址、用户名和密码。

  3. 点击 文件 菜单。

  4. 点击 部署 OVF 模板

  5. 这将打开一个新的向导。指定之前下载的 ova 文件的位置。点击 下一步

  6. 名称和位置 选项卡中指定虚拟机的名称和库存位置。

  7. 主机/集群 选项卡中指定此虚拟机要部署的主机/服务器。

  8. 存储 选项卡中指定虚拟机镜像应存储的位置。

  9. 磁盘格式 选项卡中指定磁盘格式。

  10. 点击 下一步。部署虚拟机镜像需要一些时间。安装 VMware vSphere Client

一旦虚拟机镜像在 VMware 服务器中部署完成,我们需要使用适当的cloud-config文件启动 CoreOS 虚拟机,该文件包含所需的配置属性。在 VMware vSphere 中,cloud-config文件应通过附加一个配置驱动(config-drive)来指定,配置驱动是一个带有config-2标签的iso文件,可以通过附加 CD-ROM 或新驱动来实现。以下是在基于 Linux 的操作系统中创建iso文件的命令:

  1. 创建一个文件夹,例如/tmp/new-drive/openstack/latest,如下所示:

    mkdir -p /tmp/new-drive/openstack/latest
    
    
  2. user_data文件(即cloud-config文件)复制到以下文件夹:

    cp user_data /tmp/new-drive/openstack/latest/user_data
    
    
  3. 使用以下命令mkisofs创建iso文件:

    mkisofs -R -V config-2 -o configdrive.iso /tmp/new-drive
    
    

一旦config-drive文件创建完成,执行以下步骤将config文件附加到虚拟机:

  1. iso镜像传输到运行 VMware vSphere Client 程序的机器。

  2. 打开 VMware vSphere Client。

  3. 点击 CoreOS 虚拟机,然后转到虚拟机的概述标签,如下图所示:安装 VMware vSphere 客户端

  4. 右键点击数据存储部分,然后点击浏览数据存储。这将打开一个名为数据存储浏览器的新窗口。

  5. 选择名为iso的文件夹。

  6. 点击上传文件到数据存储图标。

  7. 选择本地机器中的iso文件,并将该iso文件上传到数据存储安装 VMware vSphere 客户端

下一步是将iso文件附加为虚拟机的cloud-config文件。执行以下步骤:

  1. 转到CoreOS 虚拟机并右键点击。

  2. 点击属性

  3. 选择CD/DVD 驱动器 1

  4. 在右侧,选择设备状态已连接,并勾选开机时连接

  5. 点击数据存储 ISO 文件,然后从数据存储中选择上传的iso文件。安装 VMware vSphere 客户端

一旦iso文件上传并附加到虚拟机(VM)上,启动虚拟机。VMware 环境中的 CoreOS 虚拟机已经准备好。

Docker 简介

正如之前所述,Linux 容器在宿主操作系统内提供了一个轻量级或简化版本的操作系统。可以在 Linux 容器上安装应用程序,并可以移植到任何支持 Linux 容器的宿主操作系统上。用户无需为不同的目标机器发布不同的软件。可以在宿主操作系统上创建多个 Linux 容器,从而提供在同一台机器上独立运行多个软件实例的能力。想象一下软件使用大量端口和冗长的配置文件。在传统方法中,用户必须小心地为每个实例创建配置文件,以避免与其他实例冲突。而在 Linux 容器中,同一个配置文件适用于所有容器实例。这有助于更快的部署和更简单的操作。

Docker 容器主要是 Linux 容器,按设计只能运行单个应用程序。它们可以在任何安装了 Docker 的机器上运行。Docker 可以安装在运行不同操作系统(如 Linux 或 Mac)的各种机器上,从而使得应用程序具有可移植性。让我们了解一些 Docker 的概念。

镜像

Docker 镜像是一个只读的模板。通常,镜像包含操作系统快照,但 Docker 镜像可以包含任何内容,例如数据库、操作系统或应用程序。它们是只读的,并且可以在多个 Docker 容器之间共享。

镜像可以从头开始创建,或者从 Docker Hub 上的现有镜像创建。Docker Hub 是一个公共的 Docker Registry,托管着可以下载和使用的 Docker 镜像。我们还可以设置一个私有的 Docker 镜像库。

镜像具有唯一的 ID 和唯一的可读名称与标签对。例如,镜像可以被称为 ubuntu:latestubuntu:precisedjango:1.6django:1.7 等等。

Docker 使用 联合文件系统 来结合镜像层,形成一个单一的 Docker 镜像。联合文件系统允许不同文件系统中的文件和目录叠加在一个单一的文件系统上。一个 Docker 镜像从基础镜像开始,通常是一个标准的操作系统镜像,然后将其他镜像层附加到其上。每一层都提供比前一层更多的功能。在镜像发生变化时,仅需要提供影响的层,而不是整个镜像。

容器

容器 是从 Docker 镜像创建的。容器包含应用程序运行所需的一切,如用户文件、元数据、用户应用程序等。为了暴露容器提供的服务,Docker 允许暴露容器的特定端口。

如前所述,Docker 镜像是只读联合文件系统的层。当我们启动一个容器时,除了只读层外,还会在其上创建一个读写层,因为可能需要修改文件(例如)。当修改发生时,数据会同时出现在读写层和只读层中。这是为了确保容器中使用的镜像保持不变。这个读写层的作用范围仅限于容器存在期间。一旦容器被删除,读写层就会被销毁,而只读(未改变的)镜像将可以重新使用。提供了一种机制来管理容器内外的数据。它们还提供了一种机制来共享主机机器到容器的数据,从而使数据能够存在于容器外部。数据可以直接从主机文件夹或另一个容器共享。推荐创建一个仅数据的容器并从该容器共享数据。

链接

Docker 容器可以通过容器创建时创建的网络端口映射相互连接。这带来了一定的硬编码元素,因为端口是预配置的。容器链接也可以通过使用容器名称将源容器与目标容器链接来实现。Docker 通过环境变量和修改/etc/hosts文件将源容器的连接信息暴露给目标容器。环境变量以链接名称为前缀,并遵循命名约定,帮助目标容器识别接口详细信息(如使用的协议、IP 地址、端口等)。/etc/hosts文件会更新源容器的 IP 地址和容器名称作为主机名。

安装 Docker

Docker 也可以在各种平台、虚拟机和云服务提供商上安装。Docker 包含两个组件:

  • Docker 客户端:用户通过 Docker 客户端启动、停止和管理 Docker 容器。

  • Docker Daemon:Docker 客户端与 Docker Daemon 接口,实际启动、停止和管理 Docker 容器。Docker Daemon 只能在 Linux 机器上运行。所以,如果 Docker 安装在 Windows 或 Mac 上,Docker Daemon 会在 Linux 虚拟机内运行(例如,在 VirtualBox 中)。

创建 Docker 镜像有两种方式:

  • 使用 Docker File 和 Docker build命令

  • 使用来自 dockerhub 的预构建 docker 镜像

使用 Docker File 创建示例 Docker 镜像

在本节中,我们将学习如何通过 Docker File 创建 Docker 容器。Docker File 有明显的优点。Docker File 有助于自动化构建过程,可以对项目进行版本控制,并且内联注释为初学者和其他人提供帮助。

以下是使用 CentOS 基础镜像创建 docker 镜像的简单 Docker File:

$cat Dockerfile
FROM centos
CMD ["uname", "-a"]

$ docker build -t docker_uname .
Sending build context to Docker daemon 2.048 kB
Step 0 : FROM centos
 ---> 7322fbe74aa5
Step 1 : CMD uname -a
 ---> Using cache
 ---> 36d993cf27b9
Successfully built 36d993cf27b9

Docker File

Docker File 包含 Docker 用于构建镜像的指令。Docker File 的格式如下:

# Comment
INSTRUCTION arguments

指令按顺序执行。以#开头的行被视为注释,不会执行。环境变量也可以作为指令参数中的变量使用。一些重要的指令包括:

  • FROM:此指令设置 Docker 镜像的基础镜像。这是第一条指令。参数可以采用以下任一格式:

    FROM <image> 
    FROM <image>:<tag>
    FROM <image>@<digest>
    
    

    如果未提供tagdigest,则会选择最新的镜像。

  • RUN:此指令指定构建容器时要执行的命令。RUN指令的典型用途包括更新基础镜像的操作系统补丁、安装特定的软件包、更新系统配置等。每个命令都在当前镜像的基础上单独运行并被提交。提交的镜像然后用于下一步。参数可以采用以下任一格式:

    RUN <command> 
    
    

    在这种形式下,command将在 shell /bin/sh -c中执行。Shell /bin/sh -c是 Docker 的默认ENTRYPOINT

    RUN ["executable", "param1", "param2"]
    
    

    在这种格式下,command会直接执行,而不通过调用 shell。

  • ENTRYPOINT:这指定了 Docker 启动时的可执行文件及其相应的参数。启动 Docker 时传递的任何参数都会附加到ENTRYPOINT并执行。

    参数可以是以下任意一种格式:

    • ENTRYPOINT ["executable", "param1", "param2"]:在这种格式下,命令会直接执行,而不通过调用 shell。

    • ENTRYPOINT <command> <paramters>:在这种格式下,命令会在 shell /bin/sh -c内执行。

  • CMD:这指定了当 Docker 启动时要执行的容器的默认值(即可执行文件、shell 和命令行参数)。这与RUN不同,因为RUN指令仅在构建镜像时执行。

    参数可以是以下任意一种格式:

    • CMD ["executable","param1","param2"]:当没有提供ENTRYPOINT时使用这种格式。命令在这里会直接执行,而不经过 shell。

    • CMD ["param1","param2"]:当提供了默认命令的ENTRYPOINT时使用这种格式。这里提供的参数会附加到ENTRYPOINT上并执行。

    • CMD command param1 param2(shell 形式):在这种格式下,命令会在 shell /bin/sh -c内执行。

    只会执行一个CMD指令。如果提供了多个CMD指令,则使用最后一条指令。

  • EXPOSE:这指定了 Docker 监听的端口列表。该字段的格式为:EXPOSE <port> [<port>...]

  • VOLUME:这指定了容器内的挂载路径以及来自主机机器的外部目录或来自另一个容器的卷。该字段的格式为:VOLUME <directory> [<directory>…]

从 Docker Hub 拉取 Docker 镜像

Docker Hub是由 Docker 提供的社区驱动的 Docker 镜像托管服务,支持公共和私有内容。目前,Docker Hub 注册表中已有超过 100,000 个镜像。Docker 镜像可以直接从 Docker Hub 下载,而不需要通过 Docker File 构建。使用 docker pull命令可以直接从 Docker Hub 拉取镜像,格式如下:

docker pull centos

运行 Docker 镜像

让我们从 Docker 容器中运行一个已经存在的系统命令。这个 Docker 容器在本例中打印系统信息并退出:

$ docker run centos uname -a
Linux 3c954433a1e2 4.0.9-boot2docker #1 SMP Thu Aug 13 03:05:44 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux

run参数运行 Docker 容器。镜像名称为centos。在第一次运行时,如果本地机器上没有该镜像,将从公共镜像注册表 Docker Hub 下载最新版本的centos镜像。由于没有指定镜像的版本,因此选择了最新版本。如果需要安装特定版本,也可以提供,例如,centos:6.6。然后在容器内使用默认的ENTRYPOINT /bin/sh -c执行uname –a命令。命令执行完毕后,容器退出。

我们将创建一个更复杂的 Docker File 容器,该容器执行 RUN 指令,在基础镜像上安装一个软件包,并监听 TCP 连接:

$cat Dockerfile
FROM centos

# install ncat commad to be used for this demo during build. Ncat 
# is not part of standard package.
RUN ["yum", "-y", "install", "nc"]

# print machine ips
RUN ["cat", "/etc/hosts"]

# run the command ncat to listen on all IP address on port 12345
CMD ["ncat", "-vv", "-l", "0.0.0.0", "12345"]

$ docker build -t dock_ncat .
Sending build context to Docker daemon 3.072 kB
Step 0 : FROM centos
 ---> 7322fbe74aa5
Step 1 : RUN yum -y install nc
 ---> Using cache
 ---> 886063e43760
Step 2 : RUN cat /etc/hosts
 ---> Using cache
 ---> df623793d532
Step 3 : CMD ncat -vv -l 0.0.0.0 12345
 ---> Running in a0a5daa581b4
 ---> f8ad341c047e
Removing intermediate container a0a5daa581b4
Successfully built f8ad341c047e Removing intermediate container 6f8284dad1f8
Successfully built 3c60a690a2d7

$ docker run -p :12344:12345 dock_ncat
Ncat: Version 6.40 ( http://nmap.org/ncat )
Ncat: Listening on 0.0.0.0:12345
Ncat: Connection from 172.18.42.1.
Ncat: Connection from 172.18.42.1:58939.

容器的端口 12345 映射到主机的端口 12344。如果主机尝试连接到 12344,则会在容器上建立连接。

摘要

在本章中,我们成功地通过 Vagrant 和 VirtualBox 在单台机器上设置并运行了 CoreOS。我们还成功地创建并运行了 Docker 镜像。在此过程中,我们熟悉了重要的配置文件及其内容。

在下一章,我们将学习如何设置一个由多台机器组成的 CoreOS 集群。我们还将了解机器如何被发现,以及如何在这些机器上调度服务。

第三章:创建您的 CoreOS 集群并管理集群

本章涵盖 CoreOS 集群,提供有关集群概念和好处的信息。我们还将学习如何设置集群,并更详细地熟悉集群中涉及的所有服务。

本章涵盖以下主题:

  • 集群简介

  • 集群的原因与好处

  • CoreOS 集群

  • 创建一个 CoreOS 集群

  • 使用 etcd 进行发现

  • Systemd

  • 使用 fleet 进行服务部署和高可用性(HA)

集群简介

有两种方式可以扩展系统。一种是垂直扩展,即通过向机器添加更多硬件资源来实现。如果系统的内存需求增加,就增加更多内存;如果需要更多的处理能力,则将机器升级为使用更高端处理器或提供更多核心的机器。水平扩展是另一种将系统扩展到更高容量的方法。这意味着根据需要增加更多的机器,形成一个节点集群。这些节点集群协同工作提供服务。集群中的节点可能会有执行相同角色的应用程序,如池,或者它们可能执行不同的角色。

集群的原因与好处

系统的水平可扩展性受限于市场上可用的硬件资源。例如,将内存从 8 GB 扩展到 32 GB 或 64 GB 可能是具有成本效益的,因为许多产品可能普遍可用,但进一步增加内存可能会变得非常昂贵。类似地,扩展 CPU 也受到市场上系统配置的限制。进一步增加硬件能力并不会带来相等的性能提升,通常是较少的。

随着虚拟化和云服务的发展,购买和维护硬件的成本正在下降,使得垂直扩展、集群扩展或水平扩展变得更加有吸引力。通信网络的性能提升大大减少了集群中节点之间通信的延迟。集群有多种优点,例如:

  • 按需扩展:集群中的节点可以根据需要随时添加。我们可以从一个有规模的系统开始,随着容量的增加不断添加节点。

  • 动态扩展:大多数集群解决方案提供在运行时添加/删除节点的机制。因此,在进行集群修改时,整个系统仍然可以正常运行并提供服务。

  • 冗余:集群可以配置少量备用节点。在任何节点发生故障或在计划内或计划外的节点维护期间,这些备用节点可以被分配为故障节点或维护节点的角色,而不会影响服务容量。

了解集群的缺点也很重要,这样可以在设计系统时做出明智的决策。随着节点数量的增加,管理这些节点的复杂性也会增加。所有节点都需要进行监控和维护。软件也必须设计成能够在多个节点上运行。这就需要一个编排机制来协调集群中不同实例的应用。例如,负载均衡器将负载分配到工作节点,或作业序列化器用来同步和序列化跨节点的作业。

CoreOS 集群

第一章,CoreOS,另一个 Linux 发行版介绍了 CoreOS 集群架构。我们将在这里再次总结它。一个 CoreOS 成员或节点可以包含多个 Docker 容器。可以有多个 CoreOS 成员组成一个 CoreOS 集群。

CoreOS 使用 fleet 在初始化期间通过systemd调度和管理服务到 CoreOS 成员。这类似于在 Linux 机器上由systemd启动和管理服务。Linux 的systemd进程范围仅限于主机节点,而 CoreOS 的fleetd是完整 CoreOS 集群的初始化系统。

CoreOS 使用 etcd 进行节点发现和存储可在集群成员之间访问的配置项的键值对。

可以通过两种方式来设置集群:

  • etcd 运行在所有成员上:当集群的成员较少时,可以在所有运行服务的成员上运行 etcd,也称为工作节点。这种配置更简单,因为相同的cloud-config文件可以用于启动集群中的所有成员。

  • etcd 运行在少数成员上:当集群中的成员数量较多,通常大于十个时,建议将etcd和其他 CoreOS 集群服务专门运行在部分机器上。这使得为工作成员配置平台变得更加容易,因为它们专门用于提供服务。在这种情况下,需要两个cloud-config文件:一个用于 CoreOS 集群服务,包括 etcd,另一个用于工作节点或代理。

CoreOS 集群的设置相当简单。准备cloud-config文件并使用该文件启动成员。需要一些脚本知识来为每个成员重新生成配置文件。发现服务和 etcd 使用提供的发现令牌或静态令牌来形成集群,当成员被启动时。

集群发现

本节描述了 CoreOS 用于形成集群的各种发现机制。在本章中的示例中,以下是系统配置:

集群发现

静态发现

静态发现机制用于成员的 IP 地址已知的情况。IP 地址预先配置在cloud-config文件中。它们适用于集群规模较小的场景,通常可用于测试设置。配置大量硬编码 IP 将容易出错,且维护起来十分麻烦。

以下是用于通过静态发现创建集群的cloud-config文件:

#cloud-config

---
coreos:
  etcd2:
    name: core-01
    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
    initial-cluster-token: coreOS-static
    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
  units:
  - name: etcd2.service
    command: start
    enable: true

有两个新的字段之前没有讨论过。name字段提供成员的名称。这个名称还用于将成员与initial-cluster中的 URL 关联起来。initial-cluster字段提供集群中所有成员的名称和 URL。

提示

initial-cluster字段中提供的 IP 地址应包含静态 IP 地址。

为了创建前述的cloud-config文件,需要对所有希望加入集群的节点执行以下步骤。

Vagrantfile应该包含分配给每个成员的静态 IP 地址。如以下示例所示,IP 172.17.8.101分配给第一个成员,IP 172.17.8.102分配给第二个成员,依此类推:

...
      ip = "172.17.8.#{i+100}"
      config.vm.network :private_network, ip: ip
...

你可能注意到,cloud-config文件中仅包含一个成员的名称,但每个 CoreOS 虚拟机中的etcd服务的systemd unit文件应该包含其自己的成员名称。这需要在Vagrantfile中进行如下配置,以生成特定于每个成员的cloud-config文件。无需深入ruby的具体细节,以下代码修改每个成员的name参数并将其存储在单独的文件中。

生成的文件是user-data-1用于第一个成员,user-data-2用于第二个成员,以此类推。除了name字段,所有其他参数都来自提供的cloud-config文件。生成的文件在虚拟机启动时使用:

...
      if $share_home
        config.vm.synced_folder ENV['HOME'], ENV['HOME'], id: "home", :nfs => true, :mount_options => ['nolock,vers=3,udp']
      end

      if File.exist?(CLOUD_CONFIG_PATH)
 user_data_specific = "#{CLOUD_CONFIG_PATH}-#{i}"
 require 'yaml'
 data = YAML.load(IO.readlines(CLOUD_CONFIG_PATH)[1..-1].join)
 if data['coreos'].key? 'etcd2'
 data['coreos']['etcd2']['name'] = vm_name
 end
 yaml = YAML.dump(data)
 File.open(user_data_specific, 'w') { |file| file.write("#cloud-config\n\n#{yaml}") }
 config.vm.provision :file, :source => user_data_specific, :destination => "/tmp/vagrantfile-user-data"
        config.vm.provision :shell, :inline => "mv /tmp/vagrantfile-user-data /var/lib/coreos-vagrant/", :privileged => true
      end
...

config.rb文件中将$num_instances设置为3,并完成三成员集群的设置:

使用Vagrant up启动集群。启动成功后,我们可以看到集群的成员。

vagrant ssh core-01

etcdctl member list
7cc8bd52fa88d49: name=core-02 peerURLs=http://172.17.8.102:2380 clientURLs=http://172.17.8.102:2379
533d38560a602262: name=core-01 peerURLs=http://172.17.8.101:2380 clientURLs=http://172.17.8.101:2379
b8d2db3a5bf3d17d: name=core-03 peerURLs=http://172.17.8.103:2380 clientURLs=http://172.17.8.103:2379

etcdctl cluster-health
cluster is healthy
member 533d38560a602262 is healthy
member 7cc8bd52fa88d49 is healthy
member b8d2db3a5bf3d17d is healthy

etcd 发现

当成员的 IP 地址事先不知道或使用 DHCP 分配 IP 地址时,会使用etcd发现机制。发现可以有两种模式:公共模式和自定义模式。

如果集群可以访问公共 IP,可以使用公共发现服务discovery.etcd.io生成令牌并管理集群成员。访问网站discovery.etcd.io/new?size=<clustersize>并生成令牌。请注意,生成令牌时需要提供集群大小。

etcd discovery

通过取消注释以下行,可以在config.rb文件中自动生成令牌:

...
# To automatically replace the discovery token on 'vagrant up', uncomment
# the lines below:
#
if File.exists?('user-data') && ARGV[0].eql?('up')
 require 'open-uri'
 require 'yaml'

 token = open($new_discovery_url).read

 data = YAML.load(IO.readlines('user-data')[1..-1].join)

 if data['coreos'].key? 'etcd2'
 data['coreos']['etcd2']['discovery'] = token
 end

 yaml = YAML.dump(data)
 File.open('user-data', 'w') { |file| file.write("#cloud-config\n\n#{yaml}") }
end
...

以下是用于通过公共etcd发现创建集群的cloud-config文件:

#cloud-config

coreos:
  etcd2:
    discovery: https://discovery.etcd.io/<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
  units:
    - name: etcd2.service
      command: start
      enable: true

config.rb 文件中将 $num_instances 设置为 3,并完成三成员集群的设置。与静态发现相比,这是一个更简单的过程,且不需要在 Vagrantfile 中进行额外配置。

使用 Vagrant up 启动集群。成功启动后,我们可以看到集群的成员:

vagrant ssh core-01

etcdctl member list
466abd73fa498e31: name=5fd5fe90fef243a090cb2ee4cfac4d53 peerURLs=http://172.17.8.103:2380 clientURLs=http://172.17.8.103:2379
940245793b93afb3: name=43e78c85f5bb439f84badd8a5cb9f12b peerURLs=http://172.17.8.101:2380 clientURLs=http://172.17.8.101:2379
ea07891f96c6abfe: name=93c559a5c40d47c7917607a15d676b6d peerURLs=http://172.17.8.102:2380 clientURLs=http://172.17.8.102:2379

etcdctl cluster-health
cluster is healthy
member 466abd73fa498e31 is healthy
member 940245793b93afb3 is healthy
member ea07891f96c6abfe is healthy

除了使用公共发现外,还可以使用一个 etcd 实例作为发现服务来管理集群成员。一台 etcd 实例配置了令牌和集群实例数量,其他 etcd 实例使用该实例加入集群。

以下是用于通过公共 etcd 发现创建集群的 cloud-config 文件:

#cloud-config

coreos:
  etcd2:
    discovery: http://172.17.8.101:4001/v2/keys/discovery/40134540-b53c-46b3-b34f-33b4f0ae3a9c
    advertise-client-urls: http://$public_ipv4:2379
    initial-advertise-peer-urls: http://$private_ipv4:2380
    listen-client-urls: http://$public_ipv4:2379,http://$public_ipv4:4001
    listen-peer-urls: http://$private_ipv4:2380,http://$private_ipv4:7001
  units:
    - name: etcd2.service
      command: start
      enable: true

令牌可以通过 uuidgen Linux 命令生成。路径 v2/keys/discovery 是存储集群信息的地方。可以提供任何路径。第一台机器用作自定义发现节点。

运行在第一台机器上的 etcd 服务不需要发现令牌,因为它不会成为集群的一部分。这要求在 Vagrantfile 中进行以下配置,为第一台机器和其他机器分别生成 cloud-config 文件。以下代码修改了每个成员的 name 参数,移除了第一台机器的不需要的参数,并为每个成员单独存储文件。在以下示例中,不需要的参数设置为空;也可以删除这些参数:

...
      if $share_home
        config.vm.synced_folder ENV['HOME'], ENV['HOME'], id: "home", :nfs => true, :mount_options => ['nolock,vers=3,udp']
      end

      if File.exist?(CLOUD_CONFIG_PATH)
 user_data_specific = "#{CLOUD_CONFIG_PATH}-#{i}"
 require 'yaml'
 data = YAML.load(IO.readlines(CLOUD_CONFIG_PATH)[1..-1].join)
 if data['coreos'].key? 'etcd2'
 data['coreos']['etcd2']['name'] = vm_name
 end
 if i.equal? 1
 data['coreos']['etcd2']['discovery'] = nil
 data['coreos']['etcd2']['initial-advertise-peer-urls'] = nil
 data['coreos']['etcd2']['listen-peer-urls'] = nil
 end 
 yaml = YAML.dump(data)
 File.open(user_data_specific, 'w') { |file| file.write("#cloud-config\n\n#{yaml}") }
 config.vm.provision :file, :source => user_data_specific, :destination => "/tmp/vagrantfile-user-data"
        config.vm.provision :shell, :inline => "mv /tmp/vagrantfile-user-data /var/lib/coreos-vagrant/", :privileged => true
      end
...

config.rb 文件中将 $num_instances 设置为 3,然后使用 Vagrant up 启动集群。最初,集群的创建会失败,因为与发现令牌对应的节点数量未设置。将集群的节点数量设置为 2。发现令牌 URL 中提供的路径应与 URL 中提供的路径匹配。

vagrant ssh core-01
curl -X PUT http://172.17.8.101:4001/v2/keys/discovery/40134540-b53c-46b3-b34f-33b4f0ae3a9c/_config/size -d value=2
{"action":"set","node":{"key":"/discovery/40134540-b53c-46b3-b34f-33b4f0ae3a9c/_config/size","value":"2","modifiedIndex":3,"createdIndex":3}}

设置节点大小后,我们可以看到集群中的成员。这时,我们还需要提供 etcd 正在监听的端点信息,因为 cloud-config 文件包含的是特定的 IP 地址,而不是前面示例中的通配符 IP。

etcdctl --peers=http://172.17.8.102:4001 member list
36b2390cc35b7932: name=core-03 peerURLs=http://172.17.8.103:2380 clientURLs=http://172.17.8.103:2379
654398796d95b9a6: name=core-02 peerURLs=http://172.17.8.102:2380 clientURLs=http://172.17.8.102:2379

etcdctl --peers=http://172.17.8.102:4001 cluster-health
cluster is healthy
member 36b2390cc35b7932 is healthy
member 654398796d95b9a6 is healthy

DNS 发现

集群发现也可以通过 DNS SRV 记录来执行。请联系系统管理员创建 DNS SRV 记录,将主机名映射到服务。还应创建 DNS A 记录,将主机名映射到成员的 IP 地址。

必须通过 discovery-srv 参数提供包含发现 SRV 记录的 DNS 域名。以下 DNS SRV 记录按照列出的顺序进行查找:

  • _etcd-server-ssl._tcp.

  • _etcd-server._tcp.

如果找到 _etcd-server-ssl._tcp.<domain name>,则 etcd 将尝试通过 SSL 执行引导过程。

以下是需要创建的 SRV 和 DNS A 记录:

_etcd-server._tcp.testdomain.com. 300   IN      SRV     0       0       2380    CoreOS-01.testdomain.com.
_etcd-server._tcp.testdomain.com. 300   IN      SRV     0       0       2380    CoreOS-02.testdomain.com.
_etcd-server._tcp.testdomain.com. 300   IN      SRV     0       0       2380    CoreOS-03.testdomain.com.
CoreOS-01.testdomain.com.       300     IN      A       172.17.8.101
CoreOS-02.testdomain.com.       300     IN      A       172.17.8.102
CoreOS-03.testdomain.com.       300     IN      A       172.17.8.103

以下是用于通过公共 etcd 发现创建集群的 cloud-config 文件:

#cloud-config
coreos:
  etcd2:
    discovery-srv: testdomain.com
    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
    initial-cluster-token: etcd-cluster-1
    initial-cluster-state: new
  units:
  - name: etcd2.service
    command: start
    enable: true
write_files:
 - path: "/etc/resolv.conf"
 permissions: "0644"
 owner: "root"
 content: |
      nameserver 172.17.8.111

提示

cloud-config 文件包含额外的 section write-files,指向创建 SRV 和 A 记录的 DNS 服务器。

config.rb 文件中将 $num_instances 设置为 3,三成员集群的设置即完成。与静态发现相比,这是一个更简单的过程,并且在 Vagrantfile 中不需要额外的配置。

使用 Vagrant up 启动集群。成功启动后,我们可以看到集群的成员:

vagrant ssh core-01

etcdctl member list
13530017c40ce74f: name=5d0c2805e0944d43b03ef260fea20ae2 peerURLs=http://CoreOS-02.testdomain.com:2380 clientURLs=http://172.17.8.102:2379
25c0879f38e80fd0: name=26fed2d2c43b4901ad944d9912d071cb peerURLs=http://CoreOS-01.testdomain.com:2380 clientURLs=http://172.17.8.101:2379
3551738c55e6c3e4: name=39d95e1e69ae4bea97aed0ba5817241e peerURLs=http://CoreOS-03.testdomain.com:2380 clientURLs=http://172.17.8.103:2379

etcdctl cluster-health
member 13530017c40ce74f is healthy: got healthy result from http://172.17.8.102:2379
member 25c0879f38e80fd0 is healthy: got healthy result from http://172.17.8.101:2379
member 3551738c55e6c3e4 is healthy: got healthy result from http://172.17.8.103:2379
cluster is healthy

systemd

systemd 是一种初始化系统,绝大多数 Linux 发行版(包括 CoreOS)都采用它在启动时启动其他服务/守护进程。systemd 旨在并行运行启动服务所需的多个操作,从而实现更快的启动速度。systemd 管理服务、设备、套接字、磁盘挂载等,统称为单元(units)。systemd 对这些单元执行诸如启动、停止、启用和禁用等操作。每个单元都有一个相应的配置文件,称为 单元文件,其中包含执行每个操作所需的动作、对其他单元的依赖关系、执行的前置条件和后置条件等信息。

在本部分中,我们将了解如何使用单元文件配置服务,并对服务执行基本操作。让我们从理解单元文件的内容开始。

服务单元文件

单元文件嵌入在 cloud-config 文件中,CoreOS 会将信息原封不动地复制到相应的单元文件中。

单元名称必须是 string.suffixstring@instance.suffix 的形式,其中:

  • string 不能为空,只能包含字母数字字符以及 ':', '_', '.', '@', '-'

  • instance 可以为空,只能包含与 string 相同的有效字符。

  • suffix 必须是以下单元类型之一:servicesocketdevicemountautomounttimerpathservice 用于描述服务。

单元文件包含按部分组织的信息。每个部分包含一系列参数及其值。每个参数可以在一个部分中出现多次。部分和参数名称是区分大小写的。由于我们主要处理服务,我们将讨论与之相关的配置。以下是服务常用的几个重要部分名称:

  • [Unit] 部分:此部分不被 systemd 使用,包含有关服务的用户信息。Unit 部分的一些重要参数包括:

    • Description:这指定了服务的描述,例如名称、提供的服务等。

    • After:这指定了在启动此服务之前应该启动的服务名称。

    • Before:这指定了在启动此服务之后应该启动的服务名称。

  • [Service] 部分:此部分包含用于管理单元的配置。Service 部分的一些重要参数包括:

    • Type:这指定了服务的启动类型。类型可以是以下之一:simpleforkingoneshotdbusnotifyidle

      类型simple表示通过执行ExecStart中配置的命令启动服务,并继续进行其他单元文件处理。这是默认行为。

      类型fork表示父进程将分叉一个子进程,并在启动完成后退出。主进程退出后触发其他单元文件的处理。为了允许systemd在服务失败时采取恢复措施,如果提供服务的进程包含pid文件,可以使用PIDFile配置。

      类型oneshot表示通过执行ExecStart中配置的命令启动服务,等待该命令退出后再进行其他单元文件处理。RemainAfterExit可以用来指示服务在主进程退出后仍然是一个活动事件。

      类型notify表示通过执行ExecStart中配置的命令启动服务,等待使用sd_notify的通知来表示启动已完成。接收到通知后,systemd开始执行其他单元。

      类型dbus表示通过执行ExecStart中配置的命令启动服务,等待服务获取如BusName中指定的 D-bus 名称,然后继续进行其他单元文件处理。

    • TimeoutStartSec:此项指定systemd在启动服务时等待的时间,超过该时间后将标记服务启动失败。

    • ExecStartPre:此项可用于在启动服务前执行命令。此参数可以在该部分多次提供,用于在启动之前执行多个命令。该值包含命令的完整路径及命令的参数。该值前可以加上-,表示忽略命令失败并继续执行后续步骤。

    • ExecStart:此项指定执行启动服务命令的完整路径及其参数。如果命令路径前有短横线-,则接受非零退出状态,而不会将服务激活标记为失败。

    • ExecStartPost:此项可用于在启动服务后执行命令。此参数可以在该部分多次提供,用于在启动后执行多个命令。该值包含命令的完整路径及命令的参数。该值前可以加上-,表示忽略命令失败并继续执行后续步骤。

    • ExecStop:此项指定停止服务所需执行的命令。如果没有指定,服务停止时进程将立即被终止。

    • TimeoutStopSec:此项指定systemd在停止服务时等待的时间,超过该时间后将强制终止服务。

  • PIDFile:此项指定指向该服务 PID 文件的绝对文件名。systemd 在服务启动后读取守护进程主进程的 PID。如果服务关闭后该文件仍然存在,systemd 将删除该文件。

  • BusName:此项指定该服务可访问的 D-Bus 总线名称。对于 Type 设置为 dbus 的服务,此选项是必需的。

  • RemainAfterExit:此标志指定即使所有进程退出,服务是否仍应被视为活动的。默认为否。

启动和停止服务

systemd 提供了一个接口,可以使用 systemctl 命令来监控和管理服务。要启动服务,请使用服务名称调用 start 选项。要在重启后永久启动服务,请使用 enable 选项并指定服务名称。提供给 systemctl 命令的服务名称时,.service 可以省略:

systemctl enable crond
systemctl start crond

要停止服务,请使用服务名称调用 stop 选项:

systemctl stop crond

要检查服务状态,请使用服务名称调用 status 选项:

systemctl status crond
crond.service - Command Scheduler
 Loaded: loaded (/usr/lib/systemd/system/crond.service; enabled)
 Active: active (running) since Tue 2015-09-08 22:51:30 IST; 2s ago
 Main PID: 8225 (crond)
 CGroup: /system.slice/crond.service
 `-8225 /usr/sbin/crond -n
...

fleet

CoreOS 通过 fleet 将初始化系统扩展到集群中。fleet 模拟 CoreOS 集群中的所有节点作为一个单一的初始化系统或系统服务的一部分。fleet 在集群层面控制 systemd 服务,而不是在单个节点层面,这使得 fleet 能够在集群中的任何节点上管理服务。fleet 负责将单元/服务/容器调度到集群成员,处理通过重新调度到另一个成员来管理单元,并提供本地或远程监控和管理单元的接口。您无需担心成员与服务的耦合,因为 fleet 会为您处理这一切。单位文件保证在满足运行服务所需约束的所有集群上运行。单位文件不仅限于启动 Docker,尽管大多数情况下单位文件用于启动 Docker。有效的单位类型包括 .socket.mount 等。

架构概述

fleet 由两个主要组件组成:fleet agentfleet engine。这两个组件都是 fleetd 模块的一部分,将在所有集群节点上运行。引擎和代理组件都采用协调模型工作,在此模型中,两个组件都获取集群当前状态的快照,推导出期望的状态,并尝试模拟集群的推导状态。

fleet 使用 systemd 暴露的 D-Bus 接口。D-Bus 是 Linux 操作系统提供的 IPC 消息总线系统,提供一对一的消息方法和发布/订阅类型的消息通信。

提示

由于 fleet 是用 Go 语言编写的,fleet 使用 godbus,这是 D-Bus 的原生 GO 绑定库。

fleet 使用 godbussystemd 通信,以便向特定节点发送启动/停止单元的命令。它还使用 godbus 定期获取单元的当前状态。

引擎

fleetd 引擎负责根据约束条件(如有)做出集群节点间单元的调度决策。引擎通过与etcd通信来获取集群中单元和节点的当前状态。所有单元、单元的状态以及集群中的节点都存储在etcd数据存储中。

调度决策是及时进行的,或者是由etcd事件触发的。对账进程由etcd事件或时间周期触发,在该过程中,引擎会捕获集群的当前状态和目标状态的快照,其中包括集群中运行的所有单元的状态,以及集群中所有节点/代理的状态。根据集群的当前状态和目标状态,引擎采取必要的措施从当前状态过渡到目标状态,并将目标状态保存为当前状态。默认情况下,引擎使用最少负载调度算法,即选择负载较低的节点来运行新的单元。

Agent

Agent负责在节点中启动单元。一旦引擎选择了适合的节点来运行单元,该节点中的 agent 就负责启动该单元。为了启动单元,agent 通过 D-Bus 将启动或停止单元的命令发送给本地的 systemd 进程。agent 还负责将单元的状态发送到 etcd,稍后该状态会传达给引擎。与引擎类似,agent 还运行周期性对账进程,以计算单元文件的当前状态和目标状态,并采取必要的措施将状态转变为目标状态。

下图展示了如何通过 fleet 引擎将作业/单元调度到集群中的某个节点。当用户使用fleetctl start命令启动一个单元时,引擎会选择这个作业并将其添加到作业列表中。在该节点上运行的合格代理会代表节点竞标该作业。一旦引擎选中了合格代理,它就会将单元发送给代理进行部署。

Agent

fleetctl

fleetctl是 CoreOS 发行版提供的工具,用于与fleetd模块进行交互和管理。这类似于systemd中的systemctl,但是是用于 fleet 的。fleetctl可以在 CoreOS 集群中的一个节点上执行,也可以在一个不属于 CoreOS 集群的机器上执行。有多种机制可以运行fleetctl来管理 fleet 服务。

默认情况下,fleetctl直接与unix:///var/run/fleet.sock通信,这是本地主机机器的 Unix 域套接字。要覆盖并联系特定节点的 HTTP API,应该使用--endpoint选项,如下所示。--endpoint选项也可以通过FLEETCTL_ENDPOINT环境变量来提供:

fleetctl --endpoint http://<IP:PORT> list-units

当用户希望从外部机器执行fleetctl命令时,可以使用--tunnel选项,这为通过 SSH 将fleetctl命令隧道到集群中的一个节点提供了方式:

fleetctl --tunnel 10.0.0.1 list-machines

fleetctl包含用于启动、停止和销毁集群中单元的命令。下表列出了fleetctl提供的命令:

命令 描述 示例
fleetctl list-unit-files 列出集群中的所有单元。
$ fleetctl list-unit-files
UNIT            HASH    DSTATE   STATE    TMACHINE
myservice.service d4d81cf launched launched 85c0c595.../172.17.8.102
example.service   e56c91e launched launched 113f16a7.../172.17.8.103

|

fleetctl start 启动单元。
$ fleetctl start myservice.service
Unit myservice.service launched on d4d81cf.../172.17.8.102

|

fleetctl stop 停止单元。
$ fleetctl stop myservice.service
Unit myservice.service stopped on d4d81cf.../172.17.8.102

|

fleetctl load 在集群中调度单元,但不启动该单元。该单元将处于非活动状态。
$ fleetctl load example.service
Unit example.service loaded on 133f19a7.../172.17.8.103

|

fleetctl unload 从集群中取消调度单元。该单元将在fleetctl list-unit-files中可见,但不会有任何状态。
$ fleetctl load example.service

|

fleetctl submit 将单元引入集群。该单元将在fleetctl list-unit-files中可见,但不会有任何状态。
fleetctl submit example.service

|

fleetctl destroy 销毁命令停止单元并从集群中移除单元文件。
fleetctl destroy example.service

|

fleetctl status 获取单元状态。此命令通过 SSH 在运行指定单元的机器上调用systemctl命令。
$ fleetctl status example.service
example.service - Hello World
 Loaded: loaded (/run/systemd/system/example.service; enabled-runtime)
 Active: active (running) since Mon 2015-09-21 23:20:23 UTC; 1h 49min ago
 Main PID: 6972 (bash)
 CGroup: /system.slice/example.1.service
 ├─ 6973 /bin/bash -c while true; do echo "Hello, world"; sleep 1; done
 └─20381 sleep 1

|

fleetctl语法类似于systemctl,后者是systemd的管理接口。

标准(本地)单元和全局单元

全局单元是调度在所有成员上运行的单元。标准或本地单元是仅调度在某些机器上运行的单元。如果发生故障,这些单元将切换到集群中适合运行这些单元的其他成员上。

fleet 单元文件选项

单元文件格式与systemd的文件格式相同。fleet 通过添加一个额外的部分X-Fleet来扩展配置。该部分用于根据指定的约束,在特定成员上调度单元。X-Fleet部分的一些重要参数包括:

  • MachineID:指定单元必须执行的机器。机器 ID 可以通过/etc/machine-id文件获取,或通过fleetctl list-machines -l命令获取。此选项应谨慎使用,因为它违背了 fleet 的目的,允许将单元专门指向某台机器。

  • MachineOf:指示 fleet 在指定单元运行的成员上执行该单元。此选项可用于将运行在同一成员上的单元分组。

  • MachineMetadata:指示 fleet 在匹配指定元数据的成员上执行单元。如果提供多个元数据,则所有元数据都应匹配。为了匹配任意一个元数据,可以多次包含该参数。Metadata是为成员在cloud-config fleet 配置中提供的。

  • Conflicts:指示 fleet 不要在指定的运行单元上执行该单元。

  • Global:如果设置为 true,则该单元会在所有成员上执行。此外,如果配置了 MachineMetadata,则它只会在具有匹配元数据的成员上执行。如果提供了其他选项,则会使该单元配置无效。

在集群中实例化服务单元

我们已经了解了什么是 CoreOS 集群,如何形成一个集群,以及像 fleetfleetctl 这样的工具。现在,让我们看看如何在集群中的某个节点上使用 fleet 启动一个服务单元。如前所述,fleetctl 是 CoreOS 发行版提供的命令行工具,用于执行各种操作,比如启动服务、停止服务等。与 systemctl 类似,fleetctl 也需要一个服务文件来执行这些操作。让我们来看一个示例服务文件,并通过这个服务文件,看看 fleet 如何在集群中启动服务:

[Unit]
Description=Example
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill busybox1
ExecStartPre=-/usr/bin/docker rm busybox1
ExecStartPre=/usr/bin/docker pull busybox
ExecStart=/usr/bin/docker run --name busybox1 busybox /bin/sh -c "while true; do echo Hello World; sleep 1; done"
ExecStop=/usr/bin/docker stop busybox1

将上述文件保存为 example.service,然后在 CoreOS 机器上执行以下命令以启动集群中的服务:

$ fleetctl start example.service

$ fleetctl list-units
UNIT              MACHINE                 ACTIVE    SUB
example.service     d0ef0562.../10.0.0.3   active    running

$ fleetctl list-machines
MACHINE                                 IP          METADATA
159b2900-7f06-5d43-92da-daeeabb90d5a    10.0.0.1   -
50a69aa6-518d-4d81-ad3d-bfc4d146e996    10.0.0.2   -
d0ef0562-6a6f-1d80-b7e6-46e996bfc4d1    10.0.0.3   -

运行服务的一个主要要求是提供高可用性。为了提供高可用性服务,我们可能需要运行多个相同服务的实例。这些不同的实例应该运行在不同的节点上。为了为单元/服务提供高可用性,我们应该确保该服务的不同实例在集群中的不同节点上运行。这可以通过 CoreOS 中使用 conflicts 属性来实现。让我们看看这两个服务实例的服务文件,假设该服务为 redis.service

[Unit]
Description=My redis Frontend
After=docker.service
Requires=docker.service

[Service]
TimeoutStartSec=0
ExecStartPre=-/usr/bin/docker kill redis
ExecStartPre=-/usr/bin/docker rm redis
ExecStartPre docker pull dockerfile/redis
ExecStart docker run -d --name redis -p 6379:6379 dockerfile/redis
ExecStop=/usr/bin/docker stop redis

[X-Fleet]
Conflicts=redis@*.service

将此内容保存为 redis@1.serviceredis@2.service。服务文件中的冲突属性告知 fleet 不在同一节点上启动这两个服务:

$ fleetctl start redis@1
$ fleetctl start redis@2
$ fleetctl list-units
UNIT              MACHINE                 ACTIVE    SUB
redis@1.service  5a2686a6.../10.0.0.2   active    running
redis@2.service  259b18ff.../10.0.0.1   active    running

从节点故障中恢复

CoreOS 提供了一种内建机制,当节点故障或机器故障时,可以将单元从一个节点重新调度到另一个节点。集群中的所有节点都会向 fleet 领导者发送心跳消息。当某个节点未收到心跳消息时,该节点上运行的所有单元会被标记为在其他节点重新调度。fleet 引擎会识别合适的节点,并在合适的节点上启动单元。

概要

在本章中,我们了解了 CoreOS 集群以及成员如何使用集群发现加入集群。我们熟悉了用于启动大多数 Linux 系统中单元的初始化系统,以及 CoreOS 如何通过 fleet 服务将其扩展到多成员集群。我们学会了如何使用 fleet 在成员上启动和停止服务。

在下一章中,我们将进一步了解服务上的约束,这有助于 fleet 选择适合它运行的成员。

第四章:管理具有用户定义约束的服务

本章通过对服务施加约束,将 CoreOS 集群提升到一个新层次,确保它们仅在所需的成员上运行。

本章涵盖以下主题:

  • 使用元数据的预定义约束

  • 服务级亲和性/反亲和性

  • 节点级亲和性

  • 高可用性

服务约束简介

并非所有集群成员都会在部署中运行所有服务。有些可能运行业务逻辑服务,有些可能运行管理软件,还有一些可能运行日志或审计软件,等等。因此,集群管理软件必须提供控制服务部署的机制,以确保服务仅在满足特定属性的成员上运行。我们将学习 CoreOS 提供的控制部署的机制。

CoreOS 使用fleet服务在具有约束的成员上调度服务。单元文件配置选项帮助将服务定向到满足配置属性的特定成员或成员组。随后,我们还将学习如何将fleet服务集成到cloud-config文件中,并在docker容器内自动启动自定义服务。

使用元数据的预定义约束

该机制使得服务可以在具有与coreos.fleet部分的metadata参数中配置的匹配元数据的机器上运行。元数据可以用来描述成员的属性,如磁盘类型、区域、平台以及特殊的成员属性(如公开 IP 等)。由于它是以多个键值对的形式提供的,因此它在定义成员时提供了巨大的灵活性。

然后,元数据还可以用于将服务关联到在这些成员上运行的服务。例如,我们可以指定某个特定的服务应该在运行于特定区域和/或具有特定磁盘类型和/或具有特定成员类型(裸金属、云等)和/或具有特定提供商(机器供应商、云服务提供商等)的成员上运行。

在我们的示例中,我们将创建三个成员,每个成员都有自己的元数据,然后将服务绑定到与其属性匹配的元数据上运行。以下是设置:

使用元数据定义的预定义约束

以下是用于创建集群并在指定成员上运行服务的cloud-config文件:

#cloud-config
write_files:
 - path: /home/core/example_01.service
    owner: core:core
    permissions: 420
    content: |
      [Unit]
      Description=Example
      After=docker.service
      Requires=docker.service

 [X-Fleet]
 MachineMetadata=host=service_01

      [Service]
      TimeoutStartSec=0
      ExecStartPre=-/usr/bin/docker kill sampleserv_01
      ExecStartPre=-/usr/bin/docker rm sampleserv_01
      ExecStartPre=/usr/bin/docker pull busybox
      ExecStart=/usr/bin/docker run --name sampleserv_01 busybox /bin/sh -c "while true; do echo Test Service; sleep 300; done"
      ExecStop=/usr/bin/docker stop sampleserv_01

 - path: /home/core/example_02.service
    owner: core:core
    permissions: 420
    content: |
      [Unit]
      Description=Example
      After=docker.service
      Requires=docker.service

 [X-Fleet]
 MachineMetadata=host=service_02

      [Service]
      TimeoutStartSec=0
      ExecStartPre=-/usr/bin/docker kill sampleserv_02
      ExecStartPre=-/usr/bin/docker rm sampleserv_02
      ExecStartPre=/usr/bin/docker pull busybox
      ExecStart=/usr/bin/docker run --name sampleserv_02 busybox /bin/sh -c "while true; do echo Test Service; sleep 300; done"
      ExecStop=/usr/bin/docker stop sampleserv_02

 - path: /home/core/example_03.service
    owner: core:core
    permissions: 420
    content: |
      [Unit]
      Description=Example
      After=docker.service
      Requires=docker.service

 [X-Fleet]
 MachineMetadata=host=service_03

      [Service]
      TimeoutStartSec=0
      ExecStartPre=-/usr/bin/docker kill sampleserv_03
      ExecStartPre=-/usr/bin/docker rm sampleserv_03
      ExecStartPre=/usr/bin/docker pull busybox
      ExecStart=/usr/bin/docker run --name sampleserv_03 busybox /bin/sh -c "while true; do echo Test Service; sleep 300; done"
      ExecStop=/usr/bin/docker stop sampleserv_03

coreos:
  etcd2:
    name: core-03
    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
    initial-cluster-token: coreOS-static
    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
  fleet:
    public-ip: $public_ipv4
 metadata: host=service_01

  units:
  - name: etcd2.service
    command: start
    enable: true
 - name: fleet.service
 command: start
 enable: true
  - name: example_fleet_01.service
    command: start
    content: |
      [Service]
      Type=oneshot
      ExecStartPre=/bin/sh -c "sleep 10"
 ExecStart=/usr/bin/fleetctl start /home/core/example_01.service
  - name: example_fleet_02.service
    command: start
    content: |
      [Service]
      Type=oneshot
      ExecStartPre=/bin/sh -c "sleep 10"
 ExecStart=/usr/bin/fleetctl start /home/core/example_02.service
  - name: example_fleet_03.service
    command: start
    content: |
      [Service]
      Type=oneshot
      ExecStartPre=/bin/sh -c "sleep 10"
      ExecStart=/usr/bin/fleetctl start /home/core/example_03.service

write_files部分被添加到生成fleet的单元文件。创建了三个单元文件,每个服务只会在其中一个成员上运行。每个单元文件都添加了X-Fleet部分,添加了一个约束,要求它只能在具有特定元数据的机器上运行。

fleet部分已更新,以启动fleet并指定用于联系etcd2服务的 IP 地址。此外,添加了元数据参数,用于指定成员的元数据。需要进行仪表化操作,以为每个成员生成独立的元数据。静态集群的Vagrantfile在第三章中,创建您的 CoreOS 集群并管理集群,作为基础文件,并通过突出显示的仪表化修改每个成员的元数据。

...
      if File.exist?(CLOUD_CONFIG_PATH)
        user_data_specific = "#{CLOUD_CONFIG_PATH}-#{i}"
        require 'yaml'
        data = YAML.load(IO.readlines('user-data')[1..-1].join)
        if data['coreos'].key? 'etcd2'
          data['coreos']['etcd2']['name'] = vm_name
        end
 if data['coreos'].key? 'fleet'
 data['coreos']['fleet']['metadata'] = "host=service_%02d" % [i]
 end
        yaml = YAML.dump(data)
        File.open(user_data_specific, 'w') { |file| file.write("#cloud-config\n\n#{yaml}") }
        config.vm.provision :file, :source => user_data_specific, :destination => "/tmp/vagrantfile-user-data"
        config.vm.provision :shell, :inline => "mv /tmp/vagrantfile-user-data /var/lib/coreos-vagrant/", :privileged => true
      end
...

单元部分已更新,以启动fleet服务并包装oneshot服务,以在启动时调用fleetctl。然后,Fleetctl管理该服务。以下是事件的顺序:

  • 服务的单元文件/home/core/example_01.service/home/core/example_02.service/home/core/example_03.service在启动时创建。请注意,write_files保持在coreos部分之前,以确保在启动服务之前先创建文件。

  • 服务由在每个成员上运行的systemd启动。在oneshot服务example_01.serviceexample_02.serviceexample_03.service中添加了十秒钟的休眠,以便在使用fleetctl提交作业之前,初始化etcd2fleetd服务。

  • 然后,Fleetd协调并调度各个成员上的服务。

使用Vagrant up启动集群。启动成功后,我们可以看到集群中的成员以及各个成员上运行的服务。请注意,example_01.service在具有元数据service_01member 01上启动,example_02.service在具有元数据service_02member 01上启动,依此类推:

vagrant ssh core-01

fleetctl list-units
UNIT                    MACHINE                         ACTIVE  SUB
example_01.service      375bde8b.../172.17.8.101        active  running
example_02.service      2b6184e0.../172.17.8.102        active  running
example_03.service      e59919cc.../172.17.8.103        active  running
fleetctl list-machines
MACHINE         IP              METADATA
2b6184e0...     172.17.8.102    host=service_02
375bde8b...     172.17.8.101    host=service_01
e59919cc...     172.17.8.103    host=service_03

现在,让我们修改cloud-config文件,以创建另一个部署,在该部署中,example.service的每个实例都在每个成员上运行,同时成员 2 和成员 3 上也有相应的服务,和之前的示例相同。

使用元数据定义的预定义约束

现在,我们将查看之前准备的cloud-config文件中所需的修改。

fleetctl的单元文件进行了修改,以便为第一个服务创建一个模板单元。模板文件有助于从一个配置文件创建多个单元。在添加单元时,Fleet/systemd会查找名称完全匹配的配置文件。如果找不到这样的文件,则会使用带有@字符的同名文件。例如,要添加common@1文件,将使用common@.service

#cloud-config
write_files:
 - path: /home/core/common@.service
...

X-Fleet部分添加了附加的约束条件。MachineMetaData被更改为使用磁盘类型ssd。使用Vagrantfile工具的所有成员也添加了disk=ssd的元数据。这使得该服务适合在所有成员上运行。添加的Conflicts约束确保每台机器上仅运行一个实例。此约束意味着,如果某个服务已经在成员上运行,其他实例将不能在同一台机器上调度。请注意,服务名称使用了通配符以匹配任何实例编号。

...
      [X-Fleet]
 MachineMetadata=disk=ssd
 Conflicts=common@*.service
...

单元文件的服务部分已更新,以能够为任何实例启动服务。要在配置文件中引用实例字符串,可以使用%i占位符。%i会被启动时提供的实例号所替代。在此示例中,虽然没有使用此功能,但值得一提。

...

      [Service]
      TimeoutStartSec=0
      ExecStartPre=-/usr/bin/docker kill busybox
      ExecStartPre=-/usr/bin/docker rm busybox
      ExecStartPre=/usr/bin/docker pull busybox
      ExecStart=/usr/bin/docker run --name busybox /bin/sh -c "while true; do echo Test Service; sleep 300; done"
      ExecStop=/usr/bin/docker stop busybox
...

用于调用fleetctl的包装服务也已更新,以启动三个服务实例:

...
 - name: example_fleet1.service
    command: start
    content: |
      [Service]
      Type=oneshot
      ExecStartPre=/bin/sh -c "sleep 10"
 ExecStart=/usr/bin/fleetctl start /home/core/common@1.service
 - name: example_fleet2.service
...
 ExecStart=/usr/bin/fleetctl start /home/core/common@2.service
 - name: example_fleet3.service
...
 ExecStart=/usr/bin/fleetctl start /home/core/common@3.service
  - name: example_fleet_02.service
...

修改了Vagrantfile,并增加了以下配置,将disk=ssd的元数据添加到所有成员:

...
        if data['coreos'].key? 'fleet'
 data['coreos']['fleet']['metadata'] = "host=service_%02d,disk=ssd" % [i]
        end
...

使用Vagrant up启动集群。启动成功后,我们可以看到集群中的成员及其上运行的服务。请注意,common@.service在所有成员上都在运行,而example_02.serviceexample_03.service仅在各自的成员上实例化。

vagrant ssh core-01

fleetctl list-units
UNIT                    MACHINE                         ACTIVE  SUB
common@1.service       344d088c.../172.17.8.102        active  running
common@2.service       a5a4a7e5.../172.17.8.101        active  running
common@3.service       200545ed.../172.17.8.103        active  running
example_02.service      344d088c.../172.17.8.102        active  running
example_03.service      200545ed.../172.17.8.103        active  running

fleetctl list-machines
MACHINE         IP              METADATA
200545ed...     172.17.8.103    disk=ssd,host=service_03
344d088c...     172.17.8.102    disk=ssd,host=service_02
a5a4a7e5...     172.17.8.101    disk=ssd,host=service_01

在这个示例中,我们还涉及了基于机器上运行服务的约束条件。我们将在下一部分进一步讨论它。

服务级亲和性/反亲和性

这一机制使得将服务捆绑在一起,确保它们在同一成员上运行或反之亦然;也就是说,确保如果某个特定服务在成员上运行,当前服务就不会在该机器上调度。

在第二个示例中,通过使用元数据预定义约束,我们添加了一个Conflicts约束,以确保在成员上只启动一个服务实例。因此,该约束已应用于自服务名称。这也可以为其他服务添加。这确保了两个服务不会在同一成员上共存。为了理解这一点,我们将稍微修改示例,以确保common@.service不会与example_02.service一起运行。

服务级亲和性/反亲和性

common@.service的单元配置文件中,新增了另一个Conflicts参数,针对example_02.server。此外,coreosunits部分已修改,添加了example_fleet_02.service的条目,并且该条目位于example_fleet1.service之前。

...
      [X-Fleet]
      MachineMetadata=disk=ssd
      Conflicts=common@*.service
 Conflicts=example_02.service
...

使用Vagrant up启动集群。启动成功后,我们可以看到集群中的成员及其上运行的服务。请注意,common@.service在所有成员上都在运行,除了运行example_02.service的成员。

vagrant ssh core-01

fleetctl list-units
UNIT                    MACHINE                         ACTIVE  SUB
common@1.service        60b21422.../172.17.8.101        active  running
common@2.service        c8009511.../172.17.8.103        active  running
example_02.service      103f8f5a.../172.17.8.102        active  running
example_03.service      c8009511.../172.17.8.103        active  running

fleetctl list-machines
MACHINE         IP              METADATA
103f8f5a...     172.17.8.102    disk=ssd,host=service_02
60b21422...     172.17.8.101    disk=ssd,host=service_01
c8009511...     172.17.8.103    disk=ssd,host=service_03

现在让我们讨论一个反向使用案例:我们希望特定的服务与另一个服务一起在某个成员上运行。我们只会在 example_02.service 运行的地方启动通用服务。

服务级亲和性/反亲和性

X-Fleet 部分已更新,添加了新的参数 MachineOf。这确保了 common@.service 仅与 example_02.service 一起运行。

...
      [X-Fleet]
 MachineOf=example_02.service
...

使用 Vagrant up 启动集群。在启动成功后,我们可以看到集群中的成员以及在成员上运行的服务。请注意,common@1.service 仅在 example_02.service 运行的成员上运行。

vagrant ssh core-01

core@core-01 ~ $ fleetctl list-units
UNIT                    MACHINE                         ACTIVE  SUB
common@1.service        d119aafa.../172.17.8.102        active  running
example_02.service      d119aafa.../172.17.8.102        active  running
example_03.service      6f3da0a4.../172.17.8.103        active  running
core@core-01 ~ $
core@core-01 ~ $
core@core-01 ~ $ fleetctl list-machines
MACHINE         IP              METADATA
6f3da0a4...     172.17.8.103    disk=ssd,host=service_03
c88e05ba...     172.17.8.101    disk=ssd,host=service_01
d119aafa...     172.17.8.102    disk=ssd,host=service_02

节点级亲和性

该机制使用 systemd 生成的机器 ID 来调度服务。在成员安装时,systemd 会生成一个机器 ID,该 ID 在后续的系统启动中保持不变。节点级亲和性确保用户将服务目标定位到特定的成员上,而不是其他地方。在考虑到集群时,如果基于成员属性而非成员标识符来调度服务更为灵活,这个机制的使用就会受到限制。典型的使用场景包括运行服务以从机器收集特定数据,或者在测试成员上调度新服务来观察其行为。

以下是用来创建集群的 cloud-config 文件。该文件还会在主目录中创建一个服务单元文件,供 fleet 用于启动服务。

#cloud-config
write_files:
  - path: /home/core/example_test.service
    owner: core:core
    permissions: 420
    content: |
      [Unit]
      Description=Example
      After=docker.service
      Requires=docker.service

      [X-Fleet]
      MachineID=dummy

      [Service]
      TimeoutStartSec=0
      ExecStartPre=-/usr/bin/docker kill sampleserv_test
      ExecStartPre=-/usr/bin/docker rm sampleserv_test
      ExecStartPre=/usr/bin/docker pull busybox
      ExecStart=/usr/bin/docker run --name sampleserv_test busybox /bin/sh -c "while true; do echo Test Service; sleep 300; done"
      ExecStop=/usr/bin/docker stop sampleserv_test

...

  units:
  - name: etcd2.service
    command: start
    enable: true
  - name: fleet.service
    command: start
    enable: true

这个 cloud-config 文件有两个主要目的:启动 fleetd 服务并创建服务文件 /home/core/example_test.service

我们现在将查找集群中成员的机器 ID:

vagrant ssh core-01

core@core-01 ~ $ fleetctl list-machines -l
MACHINE                                 IP              METADATA
41b7574b33b0462c8e311ded39302a19        172.17.8.101    host=service_01
7e481484a52945d3ad369f68d2e46a77        172.17.8.103    host=service_03
f70fc5f45cdc49f99fc47757f6fe5ae6        172.17.8.102    host=service_02

修改服务文件,使得服务可以在机器 ID f70fc5f45cdc49f99fc47757f6fe5ae6 上实例化。这个机器 ID 可以是你选择的任何 ID。我们无法使用 Vagrant 自动化操作,因为机器 ID 在之前对我们是未知的。

core@core-01 ~ $ cat example_test.service
...

[X-Fleet]
MachineID=f70fc5f45cdc49f99fc47757f6fe5ae6

...

启动服务并检查它是否在所需的机器上运行:

core@core-01 ~ $ /usr/bin/fleetctl start /home/core/example_test.service
Unit example_test.service launched on f70fc5f4.../172.17.8.102
core@core-01 ~ $ fleetctl list-units
UNIT                    MACHINE                         ACTIVE          SUB
example_test.service      f70fc5f4.../172.17.8.102        active      running

高可用性

设计一个高度可用系统有两个关键原则。其一是避免单点故障;也就是说,当发生故障时,整个系统不应出现停机。例如,不应依赖单一进程、接口等。第二个原则是系统在发生故障时能够多快恢复,以便停机时间尽可能短。

Fleetd 通过允许在不同的成员上配置服务的多个实例,而不是在同一成员上配置多个实例,帮助设计一个高度可用的系统。这意味着单个成员的故障不会导致整个服务停机,但它仍然可以以减少的容量执行其应有的功能,直到恢复发生。一旦成员恢复,或者在编排应用检测到成员故障后启动了另一个成员,fleet 将自动将服务重新调度到新成员上。

总结

在本章中,我们理解了服务约束,这有助于将服务部署到合适的成员上。

在下一章中,我们将进一步了解如何发现运行在 CoreOS 集群中的服务。

第五章:发现运行在集群中的服务

当部署中有大量成员时,系统需要具备易于管理的特性,且尽量减少人工干预。人工干预往往会带来人为错误,使系统不稳定。设想一个场景:有一个负载均衡器,将 HTTP 流量分发到多个服务器。如果任何服务器宕机或上线,负载均衡器必须自动知道节点或服务的增加或删除,而无需人工干预,否则管理这样的部署将是一场噩梦。服务发现确保负载均衡器能够了解当前活跃的服务实例;基于此,它可以做出路由决策。

本章解释了发现运行在集群中的服务的需求和机制。

本章涵盖以下主题:

  • 服务发现的介绍和必要性

  • 服务发现机制

服务发现的介绍和必要性

在 CoreOS 环境中,所有用户应用程序都将作为服务部署在容器内。对于大多数应用程序,用户应用需要协同工作,因此需要一种机制来发现这些服务及其参数。通过 etcd 进行服务发现提供了一种将服务及其所需参数发布给系统中其他服务的方法。服务发现机制不仅对服务参数的发现有用,还涉及成员状态的检测(如新成员的加入、提供服务的成员的移除或成员宕机),以及服务状态(如提供应用的服务启动或停止)和服务参数(如服务提供的 IP 和端口、数据库连接端点等)。服务信息必须在所有成员之间始终可用,这意味着服务发现机制应通过多个成员进行复制,并始终可用,以避免单点故障。

服务发现机制

CoreOS 服务etcdfleetd提供的功能可以用于发现服务。下图解释了用于服务发现的典型机制:

服务发现机制

在前面的章节中,我们已经看到如何使用etcdfleetd来发现集群中的成员节点。etcd 服务不仅限于节点发现,还可以用于发现或发布与应用程序或服务相关的信息。本章接下来的部分将介绍如何使用 etcd 发布和发现与服务相关的信息。

集群中有两种类型的成员节点:前端服务节点和后端服务节点。

  • 前端服务处理所有的服务请求,并将请求路由到backend服务进行实际处理。这是任何高容量系统的简化但典型的架构。在前端服务节点中,将运行以下服务:

    • Discovery服务

    • etcd服务

    • fleetd服务

    • 前端或路由服务

  • 后端服务节点负责运行前端服务节点调度或路由的服务。在后端服务节点中,将运行以下服务:

    • Register服务:如果需要简单的服务发现,可以将其包含在后端服务单元文件中的ExeStartPost中。

    • etcd服务

    • fleetd服务

    • 后端或实际服务

fleetdetcd服务已经在前面的章节中详细讨论过。运行在后端节点的 Register 服务会更新服务信息到 etcd 键值存储,这些信息将发布到发现服务。运行在前端节点的发现服务用于通过 etcd 键值存储信息来发现后端成员的服务信息。

让我们一步步了解发现的完整流程:

  • 启动了Frontend成员。fleetd 服务启动并调度FrontendDiscovery服务。使用服务级亲和性来确保它们一起运行在同一个成员上。

  • Discovery服务使用etcd键值存储功能来查找后端成员信息。它还设置了一个监视,以便了解服务发现信息的任何变化。我们将在本章稍后了解如何读取、写入并设置 etcd 的监视。由于后端成员尚未启动,因此没有可用的服务信息,因此发现服务处于等待模式,等待服务信息的任何更新。

  • 启动了其中一个backend成员。fleetd服务再次启动并调度BackendRegister服务。这里同样使用服务级亲和性来确保它们一起运行在同一个成员上。Register 服务将服务信息更新到etcd键值存储中。这些服务信息对前端成员很有用。服务信息可以是端点信息,如 IP 和端口、服务类型或任何其他元数据,这些都对前端在调度请求时做出明智决定非常必要。设置数据的生存时间并定期重写数据到 etcd 上也很重要。为数据设置生存时间可确保当服务终止时,服务数据也会被移除。

  • 由于前端成员上的Discovery服务已经在 etcd 上设置了监视,它可以得知新的服务实例的增加。然后,它会更新前端服务,添加该服务实例。根据可用的信息,前端服务可以开始将传入的请求调度到该服务实例。

  • 一旦其他成员启动,服务发现会继续按前面步骤所述进行,前端服务将意识到更多的服务实例用于调度请求。

  • 现在,假设某个成员出现故障。如果服务信息在超时时间后没有再次更新,它会从 etcd 中删除。类似地,当成员恢复正常,但后端服务出现故障且无法再次启动时,fleetd 会关闭注册服务,因为它们是绑定在一起的。如果它们在成员上无法再次启动,服务参数将在超时时间后再次在 etcd 上过期。

  • 参数删除会被 Discovery 服务检测到,并将信息传递给 Frontend 服务。现在,Frontend 服务知道有一个服务实例被删除了。

请注意,这只是整个过程的概念性表示。并没有限制你的前端应用程序和发现服务必须是两个独立的应用或服务。你的前端应用程序也可能包含发现服务。当你使用第三方或现成的前端应用程序如 HAProxy 时,你可能需要编写一个简化的发现服务,或者你也可以使用 confd,这是另一个现成的发现应用程序。同样,后端服务和注册服务可以结合在一起,或者你可以使用另一个现成的应用程序 forest,直接更新 etcd,而无需编写注册服务。

etcd 操作

etcd 提供以下三种操作来操作键值存储:

  • etcd 写操作

  • etcd 读取

  • etcd 监听

CoreOS 提供了两种主要的接口来执行前述 etcd 操作:

  • etcdctl

  • 基于 REST 的接口。可以使用 cURL 来调用 REST API。

使用 etcdctl 的操作

etcdctletcd 的命令行客户端。使用 etcdctl,你可以读取、写入和监听 etcd 的键值存储。etcdctl 可以作为配置键值存储的独立工具使用,也可以在脚本中使用。etcdctl 会向 etcd 服务发送请求消息,并等待来自 etcd 的响应。etcdctl 可以返回以下任一返回码:

返回值 语义
0 成功
1 etcdctl 参数格式错误
2 无法连接到主机
3 认证失败(客户端证书被拒绝,CA 验证失败等)
4 来自 etcd 的 400 错误
5 来自 etcd 的 500 错误

使用 etcdctl 进行 etcd 写操作

etcd 写操作服务将被后端节点用于通过键值数据存储发布服务信息到前端服务。

使用 etcdctl 可以执行以下写操作。下表列出了 etcdctl 提供的命令选项,包括语法和示例:

操作 命令语法 示例
设置一个键的值 etcdctl set <key> <value> $ etcdctl set /foo/bar "foo bar"
设置一个带有过期时间(秒)的键值 etcdctl set <key> <value> –ttl $ etcdctl set /foo/bar "foo bar" –ttl 10
基于先前值有条件地设置键的值 etcdctl set /<key> <old-value> --swap-with-value <new-value> $ etcdctl set /foo/bar "foo bar" --swap-with-value "bar foo"
创建一个新键 etcdctl mk <key> <value> $ etcdctl mk /foo/bar "foo bar"
创建一个新目录 etcdctl mkdir <dir> $ etcdctl mkdir /foo/bar
更新一个键的值 etcdctl update <key> <value> $ etcdctl set /foo/bar "bar foo"
删除一个键 etcdctl rm <key> $ etcdctl rm /foo/bar
递归删除一个键及其所有子键 etcdctl rm <key> --recursive $ etcdctl rm /foo/bar –recursive
有条件地删除一个键 etcdctl rm <key> --with-value <value> $ etcdctl rm /foo/bar --with-value "foo bar"
删除一个目录 etcdctl rmdir <dir> $ etcdctl rmdir /foo/bar

etcd 使用 etcdctl 进行读取

etcd read服务将由前端节点用于通过键值数据存储来发现服务信息。

以下是使用etcdctl可以执行的读取操作。下表列出了etcdctl提供的命令选项、语法和示例:

操作 命令语法 示例
检索一个键值 etcdctl get <key> $ etcdctl get /foo/bar``foo bar
带有附加元数据的检索键值 etcdctl -o extended get <key> $ etcdctl -o extended get /foo/bar键:/foo/bar 修改索引:72TTL:0Etcd 索引:72Raft 索引:5611Raft 任期:1foo bar
创建一个新键 etcdctl mk <key> <value> $ etcdctl mk /foo/bar "foo bar"
列出目录 etcdctl ls $ etcdctl ls``/foo
递归列出目录 etcdctl ls –recursive $ etcdctl ls --recursive``/foo``/foo/bar

使用我们的testservices示例,以下命令用于通过etcdctl读取参数。请注意,我们在这里使用ls命令获取服务列表,然后查询特定的服务实例。

etcdctl ls /testservice/backend/1
/testservice/backend/1
/testservice/backend/2

etcdctl get /testservice/backend/2
172.17.8.102:55555

etcd 使用 etcdctl 进行监视

etcdwatch服务将由前端节点用于监控或观察键值数据存储中的任何变化。

以下是使用etcdctl可以执行的监视操作。下表列出了etcdctl提供的命令选项、语法和示例:

操作 命令语法 示例
监视键值的任何变化。 etcdctl watch <key> $ etcdctl watch /foo/bar
持续监控键值的任何变化。在此情况下,etcdctl 会一直挂起,直到按下Ctrl + C,并且当键值发生变化时,它会打印该值。 etcdctl watch <key> --forever $ etcdctl watch /foo/bar --forever``foo bar
持续监视密钥值的任何变化,并在密钥值发生变化时执行程序。 etcdctl exec-watch <key> --sh -c 要执行的程序 $ etcdctl exec-watch -- sh -c env &#124; grep ETCD

使用 etcdctl 的 etcd 示例

到目前为止,我们已经以理论方式了解了如何发布和发现服务参数。现在是时候进行一些实际操作了。让我们从熟悉用于发现的 etcd 键值存储功能开始,并通过一个名为testservices的服务示例来展示如何使用它,该服务发布了该服务运行的 IP 地址和端口号。

这里,testservices是添加所有新服务信息的目录。IP 是成员的 IP 地址,5555是服务运行的端口(此示例中选择的端口)。

以下是用于将 IP 地址和端口(使用 IP 作为键)写入的命令:

etcdctl set /testservices/ip '172.17.8.101:55555' –ttl 30

可选参数–ttl 30被添加,用于设置密钥的生命周期为 30 秒。

提示

在我们的示例中,我们选择展示如何将 IP 地址和端口号键写入到密钥存储中。请注意,了解服务运行的 IP 地址有多种方式。可以使用环境变量COREOS_PRIVATE_IPV4COREOS_PUBLIC_IPV4,或者使用ipconfig命令来查找为成员分配的 IP 地址。

要获取testservices发布的参数,请使用以下命令:

etcdctl get /testservices/ip 172.17.8.101:55555

要监视这些参数,应使用以下命令:

etcdctl watch /testservices/ip

以下是我们之前使用etcdctl查询的命令,用于写入条目。

etcdctl set /testservices/backend/1 172.17.8.101:55555 –ttl 30
etcdctl set /testservices/backend/2 172.17.8.102:55555 –ttl 30

使用 cURL 的操作

cURL,通常称为curl,是一个命令行工具,用于通过各种协议将数据传输到应用服务器或从服务器传输数据。cURL 支持多种协议,包括HTTPSHTTPFTPSFTPSCPTFTPSFTPDAPLDAPDICTTELNETIMAPFILEPOP3SMTPRTSP。它通常用于使用类似 URL 的语法获取或发送文件。像etcdctl一样,curl 也可以作为独立工具配置键值存储,或者在脚本中使用。所有可以使用etcdctl完成的操作,也可以使用 curl 完成。curl 还提供了更多操作来操作键值存储。curl 向 etcd 服务发送请求消息并等待响应。响应包含以下参数/属性:

  • 操作:操作字段表示发送的 curl 请求的类型。操作可以取值为get, set, create, delete, update, expire, watch等。

  • 节点:node字段表示键值存储的目录。它由键、值、createIndexmodifiedIndex组成。

    • key字段表示键值存储的键。

    • value字段表示键值存储的值。

    • 每个节点都有一个名为 index 的字段,每次更改 etcd 时,该字段的值会递增。createdIndex 字段则填充此索引。

    • modifiedIndex 还表示节点的索引。然而,它表示对该节点应用的操作次数,这会改变此键值存储的值。

curl set 命令的示例输出如下:

curl -L http://127.0.0.1:4001/v2/keys/foo/bar -XPUT -d value="foo bar"

{
 "action": "set",
 "node": {
 "createdIndex": 2,
 "key": "/foo/bar",
 "modifiedIndex": 2,
 "value": "foo bar"
 }
}

使用 curl 读取 etcd

以下是可以使用 curl 执行的读取操作。下表列出了 curl 提供的命令选项及其语法和示例:

操作 命令语法 示例

| 获取键值 | curl -L <URL> | curl -L http://127.0.0.1:4001/v2/keys/foo/bar

{
    "action": "get",
    "node": {
        "createdIndex": 2,
        "key": "/foo/bar",
        "modifiedIndex": 2,
        "value": "foo bar"
    }
}

|

| 递归地获取键值 | curl -L <URL> ? ?recursive=true&sorted=true | curl -L 'http://127.0.0.1:4001/v2/keys/foo/bar?recursive=true&sorted=true'

{
    "action": "get",
    "node": {
        "createdIndex": 2,
        "key": "/foo/bar",
        "modifiedIndex": 2,
        "value": "foo bar"
    }
}

|

使用 curl 写入 etcd

以下写入操作可以通过 curl 执行。下表列出了 curl 提供的命令选项及其语法和示例:

操作 命令语法 示例

| 设置键值 | curl –L <URL> -XPUT -d value=<value> | curl -L http://127.0.0.1:4001/v2/keys/foo/bar -XPUT -d value="foo bar"

{
    "action": "set",
    "node": {
        "createdIndex": 2,
        "key": "/foo/bar",
        "modifiedIndex": 2,
        "value": "foo bar "
    }
}

|

| 设置带过期时间的键值(秒) | curl –L <URL> -XPUT -d value=<value> -d ttl=<value> | curl -L http://127.0.0.1:4001/v2/keys/foo/bar -XPUT -d value="foo bar" –d ttl=5

{
    "action": "set",
    "node": {
        "createdIndex": 2,
        "expiration": "2015-12-04T12:11:11.824823581-08:00",
        "key": "/foo/bar",
        "modifiedIndex": 2,
        "ttl": 5,
        "value": "foo bar"
    }
}

|

| 更新键值 | curl –L <URL> -XPUT -d value=<value> | curl -L http://127.0.0.1:4001/v2/keys/foo/bar -XPUT -d value="foo bar2"

{
    "action": "set",
    "node": {
        "createdIndex": 3,
        "key": "/foo/bar",
        "modifiedIndex": 3,
        "value": "foo bar2 "
    },
    "prevNode": {
        "createdIndex": 2,
        "key": "/foo/bar",
        "modifiedIndex": 2,
        "value": "foo bar "
    }
}

|

| 删除键 | curl –L <URL> -XDELETE | curl -L http://127.0.0.1:4001/v2/keys/foo/bar -XDELETE

{
    "action": "delete",
    "node": {
        "createdIndex": 3,
        "key": "/foo/bar",
        "modifiedIndex": 3
    },
    "prevNode": {
        "createdIndex": 2,
        "key": "/foo/bar",
        "modifiedIndex": 2,
        "value": "foo bar "
    }
}

|

使用 curl 监听 etcd

以下是可以使用 curl 执行的监听操作。下表列出了 curl 提供的命令选项及其语法和示例:

操作 命令语法 示例
监听键值的任何变化 curl –L <URL>?wait=true curl -L http://127.0.0.1:4001/v2/keys/foo/bar?wait=true

使用 curl 的示例

让我们看看如何使用 curl 配合我们的 testservices,它希望发布此服务运行的 IP 地址和端口号。

在这里,testservices 是添加所有新服务信息的目录。IP 是成员的 IP 地址,5555 是服务运行的端口(在本示例中选择)。

以下是将服务添加到指定 IP 地址和端口的命令,键值为 IP:

curl -L http://127.0.0.1:4001/v2/keys/testservices/ip -XPUT -d value="'172.17.8.101:5555"

{
 "action": "set",
 "node": {
 "createdIndex": 3,
 "key": "/ testservices/ip ",
 "modifiedIndex": 3,
 "value": "'172.17.8.101:5555"
 }
}

要获取 testservices 发布的参数,应使用以下命令:

curl -L 'http://127.0.0.1:4001/v2/keys/testservices/ip'

{
 "action": "get",
 "node": {
 "createdIndex": 4,
 "key": "/ testservices/ip ",
 "modifiedIndex": 4,
 "value": "'172.17.8.101:5555"
 }
}

要监听这些参数,应该使用以下命令:

curl -L http://127.0.0.1:4001/v2/keys/ testservices/ip'?wait=true

HAProxy 和服务发现

在本节中,我们使用服务发现创建一个具有多个后端节点的 Web 服务,前端通过 HAProxy 进行负载均衡并处理服务请求。HAProxy 是一个常用于 TCP 和 HTTP 应用的负载均衡器。

让我们从理解一个典型的 HAProxy 配置开始。我们不会全面覆盖 HAProxy 配置,而是专注于与服务发现相关的配置:

frontend testloadbalancer
 bind *:80
 mode http
 balance roundrobin
 server testserver01 172.17.18.101:80 check
 server testserver02 172.17.18.102:80 check

该配置指示 HAProxy 绑定到端口 80 并以轮询方式将 HTTP 流量转发到服务器 172.17.18.10172.17.18.102。当我们获得每个后端服务器的信息时,可以静态配置 HAProxy,设置就会生效。但假设有一个场景,其中没有可用的 IP 信息。例如,当 IP 动态分配时,或者随着服务器流量的增加,节点数量不断增加。我们可以使用服务发现来动态地保持 HAProxy 更新,处理后端服务器的增删。接下来,我们将熟悉另一个工具——confd,我们将使用 confd 作为服务发现工具。confd 能够监视 etcd 键值存储,然后基于模板准备配置文件并将其复制到应用所需的位置,最后调用命令让应用重新加载配置。

confd 需要在目录 /etc/confd/templates 中一个模板应用配置文件,并在目录 /etc/confd/conf.d 中一个 confd 配置文件。

以下是 testconfd.toml 文件的配置,用于 confd:

[template]
src = "haproxy.cfg.tmpl"
dest = "/etc/haproxy/haproxy.cfg"
keys = [
  "/testservice/backend",
]
reload_cmd = "/usr/sbin/service haproxy reload"

该配置文件提到 HAProxy 模板文件名为 haproxy.cfg.tmpl。根据模板文件准备的配置文件必须复制到 /etc/haproxy/haproxy.cfg。配置文件还提到 etcd 键为 /testservice/backend。最后,它会调用命令来重新加载 HAProxy。

以下是我们之前见过的 HAProxy 配置文件 haproxy.cfg.tmpl 的模板文件内容:

frontend testloadbalancer
 bind *:80
 mode http
 balance roundrobin
 {{range $serveraddr := . testservice backend}}
 server {{Base $serveraddr.Key}} {{$serveraddr.Value}} check
 {{end}}

range 指令用于遍历 etcd 键,并为每个名称准备条目。所使用的 base 指令与 Linux 的 base-name 工具有些相似。对于本章前面提到的 etcd 键,对应的条目如下:

server 1 172.17.18.101:55555 check
server 2 172.17.18.102:55555 check

现在,关于后端服务器,我们可以添加命令(etcdctlcurl)通过 ExeStartPost 更新系统 IP 地址到 etcd。然后,confd 会在后端服务器启动时更新前端 HAProxy 配置。

摘要

在本章中,我们理解了发现服务的重要性,尤其在 CoreOS 环境中开发服务或应用时,如何发布服务及其参数,如何监控服务状态的变化。我们还了解了广泛用于服务发现的两个重要工具:etcdctl 和 curl,并且通过一些示例学习了它们的使用。

在下一章,我们将学习如何通过服务链机制使运行在 CoreOS 集群中的不同服务能够相互通信。

第六章:服务链路和跨服务网络

本章解释了在集群中链路不同服务的需求和机制。

本章涵盖以下主题:

  • 服务链路的介绍及其必要性

  • Docker 网络介绍

  • 使用 Flannel/Rudder 进行服务链路

  • 使用 Weave 进行服务链路

在上一章中,我们详细讨论了如何从其他服务发现运行在不同 CoreOS 实例中的服务。一旦服务被发现,一个或多个服务可能需要相互通信。本章解释了在 CoreOS 集群中链路不同服务的需求和机制。

服务链路的介绍及其必要性

由于 CoreOS 集群中的不同服务作为 docker/Rackt 容器部署,因此不可避免地需要提供一个机制,以便这些服务之间进行通信。这些服务可能运行在同一个 CoreOS 实例中,也可能跨不同的 CoreOS 实例运行。

一个例子是,当一个 Web 服务器部署在 CoreOS 集群的节点 1 上,而数据库服务部署在节点 2 上时。在这种情况下,数据库服务为 Web 服务器提供服务,我们可以称之为服务提供者。通过上一章中描述的服务发现机制,Web 服务器服务可能会发现数据库服务及其参数,例如连接字符串、IP 地址、端口号等。一旦这些信息被发现,Web 服务器可能需要与数据库服务交互,以便持久化存储一些信息或从持久化存储中获取信息。为了实现这一点,可能需要执行以下操作:

  • 在彼此之间建立网络连接

  • 使用服务提供者提供的服务

一切看起来都没问题。但在为容器提供网络连接时,会出现一些复杂性。我们来深入了解这些问题。在本章中,我们假设 CoreOS 实例中的服务都是作为 docker 容器部署的。

CoreOS 节点中的每个服务/ Docker 容器都分配一个 IP 地址。应用程序可以使用这个 IP 地址在容器内相互通信。这在服务都运行在同一个 CoreOS 节点时效果很好,因为同一 CoreOS 节点中的所有 Docker 实例或服务都会处于同一个网络中,并通过 docker0 桥接进行连接。当这些服务运行在不同的 CoreOS 节点上时,这些节点应该使用主机 CoreOS 提供的端口映射功能来访问目标容器。但在使用这种机制时,容器应该在发现服务中公布主机机器的 IP 地址。将主机 IP 推送到发现服务的一个选项是使用 ExecStartPost 选项,在 fleet 单元文件中进行配置。这样,容器就能访问主机 IP 地址。主机的 IP 地址和网络对于容器是不可用的,这允许其他外部实体提供此服务。

在了解 Flannel 和 Weave 等机制如何解决这个问题之前,让我们先看看 Docker 容器网络的详细信息。

Docker 网络简介

容器/服务有多种通信需求。CoreOS 和 Docker 共同应提供机制来满足以下所有需求:

  • 在同一 CoreOS 节点中的容器–容器通信

  • 容器与 CoreOS 主机的通信

  • 容器与外部世界的通信

  • 在不同 CoreOS 节点中的容器–容器通信

让我们在接下来的部分中探讨 CoreOS 如何为 Docker 容器提供这些功能。

容器–容器通信

本节详细描述了 CoreOS 和 Docker/容器技术提供的不同机制,用于在 Docker 的不同实例之间提供通信。以下是实现这种通信的多种方式:

  • Docker0 桥接与 veth 对

  • 使用链接

  • 使用共享网络栈

Docker0 桥接与 veth 对

Docker0 桥接是 Docker 创建的一个 Linux 桥接,用于提供不同 Docker 容器之间的通信。默认情况下,Docker 创建一个名为 docker0 的 Linux 桥接,提供 CoreOS 主机上所有 Docker 容器的连接。

提示

Docker0 桥接仅在实例化第一个容器实例时创建。后续的容器实例化不会创建新的桥接。

Veth 是一种可以在 Linux 内核内部用作虚拟链接的虚拟以太网设备。通常,veth 设备将成对创建(称为 veth 对),以在容器的不同实例之间提供连通性。当在 CoreOS 节点中实例化新的容器/服务时,将创建新的 veth 对。veth 对的一端附加到容器服务,另一端连接到 docker0 桥接。这些 docker0 桥接和 veth 对为在同一 CoreOS 节点上运行的不同容器提供连通性。

在以下图表中,docker1 和 docker2 容器通过 veth 对连接到 docker0 桥接,从而提供跨 Docker 容器的连通性。连接到 docker 实例的 veth 对的一端将作为 eth0 接口在 docker 实例内可见。可以配置此 eth0 接口的 IP 地址。用户可以配置 docker1 和 docker2 实例的 eth0 接口为相同的网络,以在它们之间提供连通性。

Docker0 桥接和 veth 对

使用 docker0 桥接进行容器之间的容器通信

Docker 实例通过具有 IP 地址范围从 172.17.51.1172.17.51.25 的虚拟子网附加到 docker0 桥接。由于 veth 对的 docker 端在同一范围内获取 IP,可能会导致在两个不同的服务器/虚拟机实例中,两个容器具有相同的 IP 地址。这可能导致在路由 IP 包时出现问题。

使用链接

这是在 Docker 容器之间提供通信的简单方式之一。Docker Link 是源容器和目标容器之间的单向通道/管道。docker 命令在实例化容器本身时提供了一种链接容器的方法。–link 选项用于此目的。Docker Link 只能用于在同一主机上运行的容器之间提供通信。

例如,如果 docker2 容器希望使用另一个容器 docker1 的网络堆栈,则启动 docker2 的命令如下:

/usr/bin/docker run --name docker2 –link docker1:docker1 ubuntu /bin/sh -c "while true; do echo Hello World; sleep 1; done"

使用链接

使用 Docker Link 进行容器之间的容器通信

使用常见的网络堆栈

在这种机制中,一个 Docker 容器将使用由另一个 Docker 容器的网络堆栈提供的网络堆栈,而不是拥有自己的网络堆栈。Docker 容器不会使用本书介绍部分中解释的网络命名空间构造,而是与另一个 Docker 共享网络命名空间。由于容器与另一个容器共享命名空间,一个容器中的任何应用程序都可以与另一个容器通信,就像两个 Docker 容器服务都在一个网络堆栈中作为应用程序运行一样。

docker 命令提供了一种在实例化容器时使用另一个 docker 的网络堆栈的方法。–net=container1 选项用于此目的。

例如,如果容器 cont_net1 想要使用另一个容器 b1 的网络堆栈,那么启动 cont_net1 的命令如下:

/usr/bin/docker run -d --name cont_net1 --net= cont_net1:b1 ubuntu /bin/sh -c "while true; do echo Hello World; sleep 1; done"

使用共同网络堆栈

使用共同网络堆栈的容器–容器通信

容器与 CoreOS 主机通信

除了容器之间的通信机制外,某些情况下,容器内运行的服务可能希望与 CoreOS 主机上运行的应用程序进行通信或交换信息。CoreOS 和 docker 提供了一些机制来实现这一点,使用以下机制:

  • 主机网络

  • docker0 桥接

主机网络

在这种机制下,docker 容器将使用 CoreOS 主机机器提供的网络堆栈,而不是拥有自己的网络堆栈。docker 容器不会使用本书介绍部分中解释的网络命名空间构造,而是与主机 CoreOS 操作系统共享网络命名空间。由于容器与主机 CoreOS 操作系统共享命名空间,CoreOS 主机中的任何应用程序都可以与 docker 容器通信,就像 docker 容器服务作为应用程序在 CoreOS 主机中运行一样。这是允许 docker 托管通信的其中一种简单机制。

docker 命令提供了一种在实例化容器时使用主机机器的网络堆栈的方法。–net=host 选项用于此目的。

例如,如果 docker1 容器想要使用主机 CoreOS 的网络堆栈,那么启动 docker1 的命令如下:

/usr/bin/docker run -d --name docker1 --net=host ubuntu_ftp vsftpd

主机网络

使用主机网络堆栈的容器–容器通信

docker0 桥接

docker0 桥接也可以用于在 docker 和主机操作系统之间提供通信。为此,主机操作系统中的一个接口应连接到 docker0 桥接,这样就能提供通信。下图详细说明了这一点,其中 CoreOS 主机机器的 eth1 接口也连接到 docker0 桥接,它提供与 docker 和广域网之间的连接。在这种情况下,docker1、docker2 的 eth0 接口和 eth1 接口应位于同一网络中,以提供 docker 与主机 CoreOS 之间的网络连接。

docker0 桥接

使用 docker0 桥接的容器–外部世界通信

容器与 CoreOS 外部世界通信

这是将服务作为微服务部署在 CoreOS 集群中的基本要求之一。运行在 CoreOS 集群中的服务(作为 docker)应该能够从外部世界访问,反之亦然。CoreOS 和 docker 提供以下机制来实现这一点:

  • 主机网络

  • 端口映射

  • 使用 docker0 桥接

主机网络

主机网络在前一节中已详细描述。由于容器与主机 CoreOS 操作系统共享命名空间,当主机 CoreOS 操作系统连接到广域网时,运行在容器中的服务也应该能够成为广域网网络的一部分。

docker命令提供了一种在实例化容器本身时使用主机机器网络堆栈的方法。为此,使用–net=host选项。

作为一个例子,如果 docker1 容器想要使用主机 CoreOS 的网络堆栈,那么启动 docker1 的命令如下:

/usr/bin/docker run -d --name docker1 --net=host ubuntu_ftp vsftpd

端口映射

这是最广泛使用的机制之一,用于将 docker 容器与外部世界进行通信。在这种机制中,主机机器上的端口号将映射到 docker 容器中的端口号。这里的端口指的是传输层端口,如 UDP 端口/TCP 端口。例如,如果用户在 docker 容器中部署了一个 web 服务器,他们可以将主机 CoreOS 操作系统中的HTTP端口(端口号80)映射到 docker 容器中的HTTP端口(端口号80)。因此,当 CoreOS 主机接收到 HTTP 请求时,它会将请求转发到容器,容器会处理这个 HTTP 请求。但与这种机制相关的一个主要挑战是,不能在多个 docker 容器中部署相同的服务,因为这会导致主机操作系统中的端口冲突。

端口映射

容器 - 使用端口映射进行外部世界通信

容器 - 在不同 CoreOS 节点中的容器间通信

我们已经看到 CoreOS 或 docker 如何从单节点的角度提供网络服务。随着服务在 CoreOS 集群中部署,需要提供不同 CoreOS 节点中运行的容器之间的通信。本章其余部分将详细讨论这一通信机制。以下是提供这一功能的多个工具:

  • Weave

  • Flannel/Rudder

  • 使用OVSOpenVSwitch

在本章中,我们将看到 Flannel 和 Weave 如何提供通信机制。在下一章中,我们将详细讨论 OVS 及其如何用于在不同容器之间提供通信。

Weave 简介

我们之前学到,运行在 Docker 中的应用程序无法知道主机机器的 IP 地址。因此,它们无法注册其 IP 地址以供服务使用,因为运行在主机外部的另一个容器必须使用主机 IP 地址来访问该服务。

如果将主机机器的 IP 地址作为环境变量传递,服务信息可以存储在etcd中,并由服务用户读取,如第五章中所示,“在集群中运行的服务的发现”。此方法要求应用程序代码了解如何发现服务。

Weave简化了服务发现并且做了更多。Weave 提供了一种机制,连接在 Docker 容器内运行的应用程序,无论它们部署在哪里。由于应用程序服务作为 Docker 容器运行,微服务之间的通信的便利性非常重要。

Weave 会自动在weaveDNS中注册命名的容器,因此服务或 docker 可以通过常规名称解析解析它们的名称来访问。这需要应用程序特定的代码,如gethostbynamegetaddrinfo,使用预定义的 Docker 名称用于服务,将名称解析为 IP 地址使用weaveDNS

Weave 建立了一个虚拟以太网交换机,连接所有 Docker 容器以及其中运行的服务或应用程序。Weave 建立了分配唯一 IP 地址的网络,每次容器启动时分配 IP 地址,并在它们关闭时释放 IP 地址。通过这种方式,启动 Docker 时不再需要显式导出端口,并且可以从任何地方访问服务,因此不再需要前端应用程序运行在公共网络上的主机上。还可以手动分配 IP 地址给容器,最终用于创建隔离子网。这使得能够将一组应用程序与另一组隔离开来。

Weave 与 Docker 集成简单,我们稍后在本章节中进行实际操作时会看到。Weave 还通过在需要通过公共或不受信任的网络连接 Docker 容器时加密流量来提供安全性。

Flannel/Rudder 简介

与 Weave 类似,Flannel也为容器分配 IP 地址,可通过创建覆盖网格网络进行容器到容器的通信。Flannel 在内部使用etcd存储分配的容器 IP 地址与主机 IP 地址之间的映射。它没有像 Weave 那样复杂的功能,并且可以在不需要 Weave 提供的其他功能集的情况下使用。例如,Flannel 不通过 DNS 提供自动服务发现,仍然需要应用程序编码或工具化来发现服务端点。

默认情况下,每个容器在/24子网中被分配一个 IP 地址。子网大小可以进行配置。Flannel 使用 UDP 封装流量以传输到目的地。

在后续章节中,我们将学习如何使用 Flannel。Flannel 之前被称为Rudder

CoreOSWeave与 Weave 集成非常简单。独立安装只需从代码库中拉取 Weave 脚本,并通过另一个命令设置并启动 Weave 路由器。

让我们手动运行一遍命令序列,然后通过cloud config进行安装和设置。

安装

Weave 可以通过使用wgetcurl获取脚本并安装到系统中。下载后,修改权限使其可执行。

/usr/bin/wget -N -P /opt/bin git.io/weave
/usr/bin/chmod +x /opt/bin/weave

设置 Weave

运行命令weave launch来设置并启动 Weave 路由器、Weave DNS 以及 Docker API 命令的代理(如docker run等)。该命令还会设置 Weave 网络。当第一次在机器上运行此命令时,将下载设置所需的Weave Docker镜像。

weave launch
Unable to find image 'weaveworks/weave:latest' locally
latest: Pulling from weaveworks/weave
4c25b19b8af6: Pulling fs layer
6498a5f7a259: Pulling fs layer
638a117dec98: Pulling fs layer
afebf09d0da1: Pulling fs layer
e5ac6ff68d75: Pulling fs layer
6498a5f7a259: Verifying Checksum
6498a5f7a259: Download complete
4c25b19b8af6: Verifying Checksum
4c25b19b8af6: Download complete
638a117dec98: Verifying Checksum
638a117dec98: Download complete
4c25b19b8af6: Pull complete
e5ac6ff68d75: Verifying Checksum
e5ac6ff68d75: Download complete
6498a5f7a259: Pull complete
638a117dec98: Pull complete
afebf09d0da1: Verifying Checksum
afebf09d0da1: Download complete
afebf09d0da1: Pull complete
e5ac6ff68d75: Pull complete
Digest: sha256:1a8565d24ef2b617619a482cbfe895f8fc27e7a4518ac18b9005ed7b4caa223f
Status: Downloaded newer image for weaveworks/weave:latest

要检查状态,可以使用status命令查看路由器状态。如果首次运行该命令,则会下载设置所需的 Weave Docker 镜像。

weave status
Unable to find image 'weaveworks/weaveexec:latest' locally
latest: Pulling from weaveworks/weaveexec
b6069e3f1ecc: Pull complete
326c397fb7ed: Pull complete
4d2b936d2fa5: Pull complete
16a356f92997: Pull complete
ae09ffb2bf28: Pull complete
14931fda689e: Pull complete
85d81711422f: Pull complete
16bfdc48cfb1: Pull complete
52bab2cc143b: Pull complete
82d8a8c031ec: Pull complete
6993b16a50ae: Pull complete
ee37b21b766d: Pull complete
3c16e5ee0357: Pull complete
77b8fe327374: Pull complete
23272d8d46c3: Pull complete
Digest: sha256:1d34246eb53f070f0e35ad13974367e2a4fee78039da74b8760a4eff49a9334f
Status: Downloaded newer image for weaveworks/weaveexec:latest
 Version: git-efd4fc4704ce

 Service: router
 Protocol: weave 1..2
 Name: 5a:62:3a:91:af:c5(core-01.testdomain.com)
 Encryption: disabled
 PeerDiscovery: enabled
 Targets: 0
 Connections: 0
 Peers: 1
 TrustedSubnets: none

 Service: ipam
 Status: idle
 Range: 10.32.0.0-10.47.255.255
 DefaultSubnet: 10.32.0.0/12

 Service: dns
 Domain: weave.local.
 Upstream: 10.0.2.3
 TTL: 1
 Entries: 0

 Service: proxy
 Address: unix:///var/run/weave/weave.sock

如果在容器启动时没有提供特定的 IP 地址,Weave 会从地址池中分配一个空闲 IP 给容器,并在容器退出时释放该地址(即标记为免费)。IP 地址池在集群中所有的 Weave 实例中共享。因此,在启动 Weave 时,必须提供集群中的所有成员或一个或多个成员。此外,还应包括一个参数,以告知 Weave 成员的数量。例如,如果有三个成员,IP 地址分别为172.17.8.101172.17.8.102172.17.8.103,则以下命令是正确的启动 Weave 并分配 IP 地址的方法。

选项一:

core@core-01 ~$ weave launch 172.17.8.102 172.17.8.103
core@core-02 ~$ weave launch 172.17.8.101 172.17.8.103
core@core-03 ~$ weave launch 172.17.8.101 172.17.8.102

选项二:

core@core-01 ~$ weave launch --init-peer-count 3
core@core-02 ~$ weave launch --init-peer-count 3 172.17.8.101
core@core-03 ~$ weave launch --init-peer-count 3 172.17.8.102

Weave 默认分配10.32.0.0/12范围内的 IP 地址,除非在启动时通过--ipalloc-range选项进行覆盖。例如,如果要使用的子网是10.1.0.0,大小为16,可以提供以下命令。相同的值应在所有成员中提供。

weave launch --ipalloc-range 10.1.0.0/16

Weave 提供了启用或禁用 Weave DNS 服务的选项。默认情况下,Weave DNS 服务是启用的。要禁用此服务,可以在运行 Weave 时提供--without-dns选项。Weave 在内存中维护所有主机的数据库。随着节点的加入,这个数据库不断建立。这个数据库在所有主机上都被维护,并且在主机之间进行复制。如果主机名位于.weave.local域中,则 Weave DNS 会将该名称与容器的 Weave IP 地址关联。当针对.weave.local域发起 DNS 查询时,Weave DNS 数据库将用于返回整个集群中该主机名所有容器的 IP 地址。当针对其他域名的名称发起 DNS 查询时,查询将发送到主机配置的名称服务器,从而遵循默认行为。

容器启动

可以通过使用 Weave 代理或不使用 Weave 代理来启动 Docker 容器。当使用 Weave 代理创建容器时,容器初始化会等待 Weave 网络接口变得可用,然后继续容器的进一步启动。容器的 IP 地址会被分配,并且容器将连接到 Weave 网络。

以下命令设置环境,使得 Docker 容器可以自动连接到 Weave 网络。可以使用常规的 Docker 命令来启动 Docker 容器:

eval "$(weave env)"
docker run ...

默认情况下,Weave 允许容器与集群中所有其他容器通信。可以通过提供一个子网范围来限制分配 IP 地址的范围。也可以提供多个子网。此外,还可以提供一个 IP 地址,分配给容器,作为自动分配 IP 地址的补充。也可以避免自动分配 IP 地址:

docker run -e WEAVE_CIDR=net:10.32.7.0/24 ...
docker run -e WEAVE_CIDR="net:10.32.1.0/24 net:10.32.8.0/24 ip:10.32.9.1/24" ...

可以通过使用命令weave run启动容器,而无需使用 Weave 代理。

以下是将用于说明两个 CoreOS 主机之间网络的设置。我们将在一个集群中实例化两个 CoreOS 成员,并在成员内部启动两个 docker 容器。docker 容器运行一个 busybox shell,以便我们可以运行网络命令并检查 IP 地址分配和对等容器的可达性。此设置演示了跨两个 CoreOS 主机之间的 docker 容器通信场景。

容器启动

Weave 设置

以下是用于创建设置的cloud-config文件。其他重用的配置文件来自第三章中的静态发现部分,创建你的 CoreOS 集群并管理集群。在config.rb文件中将$num_instances设置为2,因为我们只需要启动两个成员实例。

#cloud-config

---
write_files:
  - path: /etc/weave.core-01.testdomain.com.env
    permissions: 0644
    owner: root
    content: |
      WEAVE_LAUNCH_ARGS="172.17.8.102"
  - path: /etc/weave.core-02.testdomain.com.env
    permissions: 0644
    owner: root
    content: |
      WEAVE_LAUNCH_ARGS="172.17.8.101"

coreos:
  units:
    - name: 10-weave.network
      runtime: false
      content: |
        [Match]
        Type=bridge
        Name=weave*

        [Network]

    - name: install-weave.service
      command: start
      enable: true
      content: |
        [Unit]
        After=network-online.target
        After=docker.service
        Description=Install Weave
        Requires=network-online.target
        Requires=docker.service

        [Service]
        Type=oneshot
        RemainAfterExit=yes
        ExecStartPre=/usr/bin/wget -N -P /opt/bin git.io/weave 
        ExecStartPre=/usr/bin/chmod +x /opt/bin/weave
        ExecStart=/bin/echo Wave Installed

    - name: weave.service
      command: start
      enable: true
      content: |
        [Unit]
        After=install-weave.service
        Description=Weave Network
        Requires=install-weave.service

        [Service]
        Type=oneshot
        EnvironmentFile=/etc/weave.%H.env
        ExecStart=/opt/bin/weave launch $WEAVE_LAUNCH_ARGS

在我们启动成员中的容器并检查连接性之前,让我们详细了解一下cloud-config文件。

首先,我们使用write_files部分创建两个文件。它们将在启动 Weave 之前使用在各自的机器上。每个文件的名称中都包含主机名,因此在EnvironmentFile中使用%H时,会引用专门为该成员准备的文件。

单元文件10-weave.network被添加以允许 Weave 网络用于 DHCP 查询。默认情况下,使用docker0桥接。这是可选的,只有在 Weave 网络用于 DHCP 时才需要。

单元 install-weave.service 将 Weave 安装到成员上并设置所需的权限。由于 Weave 安装完成后服务已完成其任务,因此这是一个一次性服务。添加 After=network-online.target 确保在安装 Weave 之前网络已启动。这是必要的,以便可以从互联网下载软件包。

单元 weave.service 引用相应的环境文件并启动 Weave。

使用 Vagrant up 启动集群。启动后,集群中的节点将启动并启用 weave 网络:

vagrant ssh core-01
weave status

 Version: 1.4.2

 Service: router
 Protocol: weave 1..2
 Name: 96:63:f1:5a:ac:3a(core-01.testdomain.com)
 Encryption: disabled
 PeerDiscovery: enabled
 Targets: 0
 Connections: 1 (1 established)
 Peers: 2 (with 2 established connections)
 TrustedSubnets: none

 Service: ipam
 Status: idle
 Range: 10.32.0.0-10.47.255.255
 DefaultSubnet: 10.32.0.0/12

 Service: dns
 Domain: weave.local.
 Upstream: 10.0.2.3
 TTL: 1
 Entries: 0

 Service: proxy
 Address: unix:///var/run/weave/weave.sock

我们可以看到两个对等节点已经连接。我们还可以看到 DNS 和路由器服务已启用,并且使用的是默认设置。

现在我们将在每个成员上启动一个 Docker 容器,并在 busybox 上运行一个简单的 shell。以下命令将在两个成员上执行。注意,我们明确提供了 Docker 容器的名称。这将导致两个 .weave.local 域名的 DNS 条目。

vagrant ssh core-01
eval "$(weave env)"
/usr/bin/docker run --name=container2 -it busybox /bin/sh
/ # ifconfig –a
...
ethwe     Link encap:Ethernet  HWaddr E6:AA:25:26:EA:04
 inet addr:10.32.0.1  Bcast:0.0.0.0  Mask:255.240.0.0
 inet6 addr: fe80::e4aa:25ff:fe26:ea04/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1410  Metric:1
 RX packets:13 errors:0 dropped:0 overruns:0 frame:0
 TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:1000
 RX bytes:1385 (1.3 KiB)  TX bytes:620 (620.0 B)
...

vagrant ssh core-02
eval "$(weave env)"
/usr/bin/docker run --name=container2 -it busybox /bin/sh
/ # ifconfig -a
...
ethwe     Link encap:Ethernet  HWaddr DA:E1:B8:3A:39:FB
 inet addr:10.40.0.0  Bcast:0.0.0.0  Mask:255.240.0.0
 inet6 addr: fe80::d8e1:b8ff:fe3a:39fb/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1410  Metric:1
 RX packets:11 errors:0 dropped:0 overruns:0 frame:0
 TX packets:7 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:1000
 RX bytes:969 (969.0 B)  TX bytes:550 (550.0 B)
...

现在,ping 另一个容器的 IP 地址和主机名,以确保容器之间的网络和 DNS 服务正常工作。容器启动后,你还可以运行状态命令,检查是否已更新两个 DNS 条目。

weave status
...
 Service: dns
 Domain: weave.local.
 Upstream: 10.0.2.3
 TTL: 1
 Entries: 2

 Service: proxy
 Address: unix:///var/run/weave/weave.sock

将 Flannel 与 CoreOS 集成

Flannel 在每个主机上运行一个守护进程 flanneld,负责在配置的子网中分配一个空闲的 IP 地址。flanneld 会监控 etcd 中的信息,并使用配置的机制路由数据包。

虽然 flanneld 服务并不是 CoreOS 标准发行版的一部分,但当通过 cloud-config 启动 flanneld 服务时,CoreOS 会在其他初始化之前启动一个服务,从 docker 注册表中拉取 flanneldflanneld 作为 docker 容器存储在 CoreOS 企业注册表中。

用于 Weave 网络的相同设置也在此使用。请注意,对于 Flannel,主机名是无关紧要的。

以下是用于创建设置的 cloud-config 文件。其他配置文件来自 第三章 静态发现 部分,创建 CoreOS 集群并管理集群。在 config.rb 文件中将 $num_instances 设置为 2,因为我们只需要启动两个成员实例。

#cloud-config

---
coreos:
  etcd2:
    name: core-03
    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
    initial-cluster-token: coreOS-static
    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
  flannel:
    interface: $public_ipv4

  units:
  - name: etcd2.service
    command: start
    enable: true
  - 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

Vagrant 设置将配置 Flannel 应该使用的接口。这可以通过在 cloud-config 中提供以下配置来完成:

flannel:
    interface: $public_ipv4

flanneld 服务配置中添加了 ExecStartPre 指令,作为名为 50-network-config.conf 的插件文件。通过 ExecStartPreFlannel 配置被更新到 etcd 中。这对于 Flannel 的正常工作是必须的,因为它会在 /coreos.com/network/config 查找配置。以下是可以作为逗号分隔值提供给 etcd 的 Flannel 配置项:

  • Network:此字段指定将在所有 Flannel 网络中使用的子网。此字段是必需的。在前面的示例中,子网配置为10.1.0.0/16。将为每个主机在该子网内创建进一步的子网。

  • SubnetLen:此字段指定分配给每个主机的子网大小,以位为单位。此字段的值应小于或等于为网络提供的子网大小。如果未提供此字段,且Network字段的子网大小大于或等于24,则默认为24。如果Network字段的子网大小小于24且未配置此字段,则使用Network字段配置值减一。

  • SubnetMin:此字段指定子网分配开始的起始 IP 范围。如果未提供此字段,则默认为网络的第一个子网。

  • SubnetMax:此字段指定子网分配开始的结束 IP 范围。如果未提供此字段,则默认为网络的第一个子网。

  • Backend:此字段指定用于在主机之间发送流量的机制。支持的值包括udp, vxlan, host-gw等。如果未提供此字段,则使用udp。如果使用udp,则配置用于 UDP 的端口号。如果未提供端口,则使用默认端口 8285。如果主机之间要跨防火墙进行网络通信,则应允许此端口。

以下是 Flannel 的另一种示例配置,其中包含其他可选参数,且它们设置为各自的默认值:

ExecStartPre=/usr/bin/etcdctl set /coreos.com/network/config '{ "Network": "10.1.0.0/16", "SubnetLen": 24, "SubnetMin": "10.1.0.0", "SubnetMax": "10.1.255.0"}' 

使用Vagrant up启动集群。启动后,Flannel 将在主机上设置接口,集群就绪。

vagrant ssh core-01
ifconfig –a
...
flannel0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1472
 inet 10.1.35.0  netmask 255.255.0.0  destination 10.1.35.0
 unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 500  (UNSPEC)
 RX packets 0  bytes 0 (0.0 B)
 RX errors 0  dropped 0  overruns 0  frame 0
 TX packets 0  bytes 0 (0.0 B)
 TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

...

同样,我们可以看到 Flannel 在其他实例上也创建了接口。

vagrant ssh core-02
ifconfig –a
...
flannel0: flags=4305<UP,POINTOPOINT,RUNNING,NOARP,MULTICAST>  mtu 1472
 inet 10.1.27.0  netmask 255.255.0.0  destination 10.1.27.0
 unspec 00-00-00-00-00-00-00-00-00-00-00-00-00-00-00-00  txqueuelen 500  (UNSPEC)
 RX packets 0  bytes 0 (0.0 B)
 RX errors 0  dropped 0  overruns 0  frame 0
 TX packets 0  bytes 0 (0.0 B)
 TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0
...

Flannel 为 host1 设置子网10.1.35.0,为 host2 设置子网10.1.27.0,供容器使用。Flannel 在分配给主机之前会先决定可用的子网。现在,我们将在每个节点上启动一个Docker容器,并在busybox上运行一个简单的 shell。以下命令将在两个节点上执行:

vagrant ssh core-01
/usr/bin/docker run -it busybox /bin/sh
/ # ifconfig -a
eth0      Link encap:Ethernet  HWaddr 02:42:0A:01:23:03
 inet addr:10.1.35.3  Bcast:0.0.0.0  Mask:255.255.255.0
 inet6 addr: fe80::42:aff:fe01:2303/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1472  Metric:1
 RX packets:19 errors:0 dropped:0 overruns:0 frame:0
 TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:1611 (1.5 KiB)  TX bytes:508 (508.0 B)

...

vagrant ssh core-02
/usr/bin/docker run -it busybox /bin/sh
/ # ifconfig -a
eth0      Link encap:Ethernet  HWaddr 02:42:0A:01:1B:03
 inet addr:10.1.27.3  Bcast:0.0.0.0  Mask:255.255.255.0
 inet6 addr: fe80::42:aff:fe01:1b03/64 Scope:Link
 UP BROADCAST RUNNING MULTICAST  MTU:1472  Metric:1
 RX packets:21 errors:0 dropped:0 overruns:0 frame:0
 TX packets:9 errors:0 dropped:0 overruns:0 carrier:0
 collisions:0 txqueuelen:0
 RX bytes:1751 (1.7 KiB)  TX bytes:738 (738.0 B)
...

正如我们所看到的,容器已从相应主机的子网中分配了一个 IP 地址,并且可以从另一个容器 ping 通这些 IP 地址。这也说明,通过 Weave 和 Flannel 等附加工具,容器之间的通信变得更加简单,接近裸金属应用之间的通信方式。

总结

在本章中,我们已经了解了容器通信的重要性以及 CoreOS 和 docker 提供的各种通信可能性。在下一章中,我们将看到OVSOpenVSwitch)如何在底层网络上提供通信机制。除了 Flannel、Weave 和 OVS,还有其他机制如 pipework 可以在 CoreOS 和 docker 环境中提供网络。

第七章:使用 OVS 创建虚拟租户网络和服务链

在上一章中,我们看到如何将不同的服务通过 CoreOS 集群中的链接相互连接。本章详细描述了如何使用 OVS 将不同客户/租户在 CoreOS 集群中部署的服务链接/连接起来。

本章涵盖以下主题:

  • OpenVSwitch/OVS 介绍

  • 覆盖网络和基础网络介绍

  • 虚拟租户网络简介

  • 使用 OVS 的 Docker 网络

由于 OVS 是一个生产级别、广泛部署的软件交换机,具有广泛的功能集,我们将看看如何使用 OVS 提供服务链,这可以区分不同的客户服务。

OVS 介绍

OpenVSwitchOVS)是一个生产级别的开源虚拟交换机应用程序,可以在任何 Unix 类系统上运行。通常,OVS 用于虚拟化环境中,为在服务器内部运行的虚拟机/容器提供通信。OVS 充当一个软件交换机,在服务器内部的虚拟机之间提供二层连接。Linux Bridge 也可以用于提供虚拟机之间的通信。然而,OVS 提供了在典型的服务器虚拟化环境中所需的所有功能。以下图示展示了 OVS 如何提供服务器内虚拟机之间的连接:

OVS 介绍

在图示中,服务器中有三个虚拟机正在运行。每个虚拟机的虚拟网卡一端连接到Open vSwitch。在这里,Open vSwitch提供了服务器内所有虚拟机之间的连接。Open vSwitch还连接到物理网卡,以提供虚拟机与外部世界之间的通信。

OVS 通过使用VLAN和基于各种数据包头部的流量过滤,提供安全性,并通过提供流量隔离来确保网络安全。OVS 还提供了使用sFlowSPANRSPAN等协议监控服务器内虚拟机之间交换的包的方法。OVS 还支持具有流量排队和整形QoS(服务质量)以及OpenFlow支持。

OVS 架构概述

本节描述了 OVS 及其组件的高层架构概述。

OVS 架构概述

OVS 的主要组件如下:

  • ovs-vsctl:这是 OVS 提供的用于通过ovsdb-server配置和查询ovs-vswitchd守护进程的工具

  • ovs-appctl:这是用于管理 OVS 日志级别的工具

  • ovs-ofctl:这是 OVS 提供的用于管理交换机中 OpenFlow 条目的工具

  • ovs-dpctl:这是用于配置 OVS 数据路径的路径管理工具

  • ovsdb-server:这是存储 OVS 所有配置信息的数据库

  • ovs-vswitchd:这是 OVS 的交换模块,提供核心功能,如桥接、VLAN 隔离等。

  • Openvswitch.ko:这是处理流量快速切换和隧道传输的数据路径模块。

在 CoreOS 中使用 OVS 的优点

在 CoreOS 环境中,OVS 可以替代 docker0 桥接,并为不同 CoreOS 实例中的容器提供连接。docker0 桥接只能为同一 CoreOS 实例中运行的容器提供连接。然而,除了为同一 CoreOS 实例中的容器提供连接外,OVS 还可以为不同 CoreOS 实例中的容器提供连接。与上一章提到的其他技术相比,OVS 提供的关键优点如下:

  • 顾名思义,OpenVSwitch/OVS 执行数据从一个容器到其他容器的层 2 桥接/交换。它执行典型的层 2 处理,如洪泛、学习、转发、基于 VLAN 标签的流量隔离、使用生成树协议提供无环拓扑等。

  • OVS 支持隧道协议,例如 GRE、VxLAN 等。这些隧道协议用于在层 3 网络上传输层 2 流量。这些隧道用于为运行在不同 CoreOS 实例中的容器提供连接。VxLAN 协议在 RFC 7348 中有详细定义,而 GRE 协议则在 RFC 2784 中有详细定义。这些隧道为在物理基础网络上构建覆盖网络提供了虚拟基础设施。

  • OVS 还支持 OpenFlow 协议,可以通过外部 SDN 控制器(如 OpenDayLight 控制器、RYU 控制器、ONOS 控制器等)进行编程。这意味着 CoreOS 集群可以通过典型的 SDN 部署中的集中式控制器轻松管理。

在详细了解 OVS 如何提供跨容器连接并实现服务链条之前,我们需要了解一些核心概念和特性,如覆盖网络、基础网络和虚拟租户网络。

覆盖网络和基础网络简介

下图表示 OVS 在虚拟机环境中提供的典型服务:

介绍覆盖网络和基础网络

Server1 和 Server2 是两个物理服务器,客户应用程序部署在虚拟机(VM)内。每个服务器上有两个虚拟机,分别是 VM1 和 VM2。绿色虚拟机属于一个客户,橙色虚拟机属于另一个客户。每台服务器上都运行一个 OVS 实例。

在典型的虚拟化环境中,有两种类型的网络设备:软交换,它为虚拟化层提供连接;物理交换机,它为物理基础设施(如服务器、交换机和路由器)提供连接。

OVS 交换机为运行在服务器实例内的虚拟机/容器提供连接。这些服务器实例还通过物理方式相互连接,以便为所有服务器提供连接。

提供服务器连接的物理网络被称为底层网络。这个底层网络将拥有包括物理交换机和路由器在内的物理基础设施,提供服务器之间的连接。

现在,复杂性出现在如何为在服务器中运行的容器提供连接,以便与在不同服务器实例中运行的其他容器进行通信。解决这个问题有多种方法。一个主要且广泛部署的解决方案是使用 OVS 来提供覆盖网络。

正如这个术语所暗示的,覆盖网络是覆盖在另一个网络之上的网络。与物理底层网络不同,覆盖网络是虚拟网络,由虚拟链路组成,虚拟链路共享底层的物理网络(底层网络),允许容器/虚拟机的部署在不修改底层网络的情况下互相连接。这里的虚拟链路指的是提供 OVS 之间连接的隧道。OVS 支持多种隧道协议,常用的隧道包括 GRE 和 VxLAN。

覆盖网络的关键好处是:

  • 由于它是一个逻辑网络,因此可以很容易地创建和销毁覆盖网络,而无需对底层网络进行任何更改。要在两个节点之间创建覆盖网络,只需在节点之间创建一个隧道,而要销毁覆盖网络,则需要取消配置隧道接口。

  • 可以在节点之间创建多个覆盖网络。例如,可以根据在服务器实例中部署的客户数量创建多个覆盖网络。这为网络虚拟化提供了一种方式,类似于服务器虚拟化。让我们深入了解网络虚拟化的细节。

网络虚拟化简介

网络虚拟化是最近在网络行业中广泛讨论的话题之一。为了更好地理解网络虚拟化,可以想象服务器虚拟化,其中物理基础设施被逻辑上分隔成多个虚拟设备,每个虚拟设备分配给不同的容器以执行其工作负载。与服务器虚拟化类似,网络层也需要进行虚拟化,以便为不同的虚拟机/容器提供连接。

与服务器虚拟化类似,客户可以完全访问虚拟化的服务器基础设施,客户也可能希望虚拟化网络基础设施,以确保虚拟机或容器之间的数据流量安全。他们不希望其他人看到他们应用程序之间的数据交换,尤其是其他客户的虚拟机或容器。

网络虚拟化作为一个概念,在网络领域并不新鲜。现有网络通过 VLAN、VRF、L2VPN、L3VPN 等技术或概念实现网络虚拟化。这些网络虚拟化技术提供了一种机制,用于隔离一个客户与另一个客户的流量。VLAN 提供了一种基于 VLAN 标签逻辑隔离二层广播域的方法。

这些技术还定义了必要的协议支持,以便在不同客户之间实现重叠的地址空间。例如,使用 VRF 时,两个或更多客户可以在不同站点之间共享其 IP 地址。

然而,这些技术并没有在整个网络中提供真正的网络虚拟化。这些技术也有其自身的局限性。例如,1026 个 VLAN 限制了网络中租户的数量。对于 VPN 支持,可能需要像 MPLS 这样的协议,而 MPLS 通常在服务提供商网络中部署。

随着越来越多的运营商和云服务提供商部署软件定义网络SDN)和网络功能虚拟化NFV),有必要提供一种机制,以更好地提供网络虚拟化和流量隔离。

上一章中描述的覆盖网络可以提供一种有效的机制来隔离不同租户或客户之间的数据流量。由于可以在下层物理基础设施上布置多个覆盖网络(每个客户一个),我们应该能够提供不同客户流量之间所需的隔离。因此,覆盖网络基础设施提供了一种简便的方式来提供网络虚拟化。

要为客户或租户创建覆盖网络,我们需要在客户/租户的应用程序部署的所有节点之间创建一个隧道。OVS 有助于在不同的 OVS 实例之间创建隧道,从而支持创建 VTN 和下层网络。

回到之前的示意图,有两个客户,分别以绿色和橙色显示。两个客户的虚拟机都运行在 server1 和 server2 中。为了提供网络虚拟化并隔离这两个客户之间的流量,可以使用以下步骤:

  • 在 OVS 中为每个客户创建两个桥接实例,分别为 Greenbr 和 Orangebr。

  • 将虚拟机的虚拟网卡接口(veth)附加到相应的桥接实例。例如,绿色虚拟机的虚拟网卡应附加到 Greenbr,橙色虚拟机的虚拟网卡接口应附加到 Orangebr。

  • 在 server1 和 server2 之间创建两个隧道,例如Green_tunOrange_tun。这两个服务器实例可以位于同一网络中,也可以位于不同的网络中。如果它们位于不同的网络中,则应部署一个或多个路由器来提供这些服务器之间的物理连接。

    提示

    为了在两个节点之间创建隧道,这两个节点之间应该具有 IP 可达性。IP 可达性由底层网络提供。

  • 将这两个隧道附加到相应的桥接实例。

通过这些简单的步骤,可以为不同的客户创建虚拟网络。如下图所示:

网络虚拟化简介

OVS 中的 OpenFlow 支持

使用 OVS 的主要优势之一是它支持 OpenFlow 协议并支持基于流的交换。OpenFlow 是由 ONF 定义的协议,用于通过标准接口集中管理网络基础设施,控制器(传统上称为控制平面)与实际的数据转发实体(传统上称为数据平面)之间的接口。使网络能够集中编程,能让整个系统更加灵活和敏捷。

OpenFlow 承诺简化大型数据中心和服务器集群的配置方式,这些数据中心和服务器集群可以通过 OpenFlow 控制器进行集中管理。随着大型数据中心和服务器集群的出现,传统的控制平面和数据平面范式的改变变得尤为必要,转向基于流的交换,这种方式更加通用,并且可以适应不同的领域。软件定义网络(SDN)是网络领域中的一种新范式。

OpenFlow 规范定义了基于 OpenFlow 的网络中的三个不同组件,如下所示。

OpenFlow 交换机

一个 OpenFlow 交换机 由一个或多个流表、计量表、组表以及与外部控制器的 OpenFlow 通道组成。在包处理的查找或转发阶段,流表和组表用于将数据包转发到适当的端口,而计量表用于执行简单的 QoS 操作,如速率限制,以及更复杂的 QoS 操作,如 DiffServ 等。交换机与控制器通信,控制器通过 OpenFlow 协议使用 OpenFlow 消息管理交换机。

OpenFlow 控制器

一个 OpenFlow 控制器 通常通过 OpenFlow 通道远程管理一个或多个 OpenFlow 交换机。类似地,单个交换机可以由多个控制器管理,以提高可靠性和负载均衡。OpenFlow 控制器的作用类似于传统交换机或路由器的控制平面。控制器负责使用 OpenFlow 协议消息编程各种表格,如流表、组表和计量表,以在系统中提供网络连接或网络功能。

OpenFlow 通道

一个 OpenFlow 通道 用于在 OpenFlow 交换机和 OpenFlow 控制器之间交换 OpenFlow 消息。交换机必须能够通过启动与控制器的连接来创建 OpenFlow 通道:

OpenFlow 通道

使用 OVS,可以通过控制器以非常简单的配置集中管理整个 CoreOS 集群的覆盖网络。OVS 提供的 ofctl 实用工具有助于通过命令行参数编程流表,而无需由外部控制器进行控制。

在 CoreOS 中运行 OVS

在 CoreOS 环境中,有两种方法可以运行或安装 OVS:

  • 构建带有 OVS 的 CoreOS 镜像

  • 使用–net=host选项在 Docker 容器中运行 OVS

正如我们在第一章中已经看到的那样,CoreOS,另一个 Linux 发行版,在 CoreOS 中没有安装应用程序的方法。任何服务/应用程序都应部署在容器中。因此,运行 OVS 的简单方法是将 OVS 运行在 Docker 容器内。让我们看看如何在 CoreOS 中安装 OVS Docker。

已经有一个带有 OVS 的 docker 镜像(coreos-ovs)。可以从github.com/theojulienne/coreos-ovs的 GitHub 链接下载此 docker 镜像。使用以下cloud-config启动此容器:

#cloud-config

coreos:
 units:
 - name: docker.service
 command: start
 drop-ins:
 - name: 50-custom-bridge.conf
 content: |
 [Service]
 Environment='DOCKER_OPTS=--bip="10.0.11.0/8" --fixed-cidr="10.0.11.0/24"'
 - name: OVS.service
 command: start
 content: |
 [Unit]
 Description=Open vSwitch Bridge
 After=docker.service
 Requires=docker.service

 [Service]
 Restart=always
 ExecStartPre=/sbin/modprobe openvswitch
 ExecStartPre=/sbin/modprobe af_key
 ExecStartPre=-/usr/bin/docker run --name=openvswitch-cfg -v /opt/ovs/etc busybox true
 ExecStartPre=-/usr/bin/docker rm -f openvswitch
 ExecStartPre=/usr/bin/docker run -d --net=host --privileged --name=openvswitch --volumes-from=openvswitch-cfg theojulienne/coreos-ovs:latest
 ExecStart=/usr/bin/docker attach openvswitch
 ExecStartPost=/usr/bin/docker exec openvswitch /scripts/docker-attach

这将启动一个安装了 OVS 的 Docker 容器。同时,它会移除 docker0 桥接器的 IP 地址并将其分配给 OVS 桥接器(bridge0)。docker0 桥接器将作为链接连接到 bridge0。

由于我们使用了–net=host选项,我们在容器内执行的任何 OVS 命令都将导致修改主机操作系统的网络配置,即 CoreOS 网络堆栈。

本节详细描述了如何在两个不同的 CoreOS 实例中运行的 docker 容器之间提供虚拟租户网络。提供此解决方案有多种方法。我们将介绍提供此解决方案的两种最常见且简单的方法:

  • 将 docker0 桥接器连接到 OVS

  • 将容器的 veth 接口连接到 OVS

将 docker0 桥接器连接到 OVS

这是一种通过 OVS 提供不同容器之间连接的简单方法。在这种情况下,OVS 应通过 veth 接口连接到 docker0 桥接器(该桥接器已连接到所有容器)。有关 docker0 桥接器的更多详细信息以及它如何为系统中的容器提供连接,请参见前一章节。

Docker 桥接器通过内部连接到 OVS 桥接器。OVS 桥接器通过 GRE/VxLAN 隧道提供与其他 CoreOS 实例的连接。

将 docker0 桥接器连接到 OVS

以下详细描述了带有配置的逐步过程。此过程包含在两个 CoreOS 实例上的以下主要步骤:

  • 在集群中实例化 CoreOS 节点期间的配置

  • 在创建容器期间的配置

CoreOS 实例 1 中的配置

本节详细描述了在 CoreOS 节点 1 的 coreos-ovs Docker 上执行的操作,以提供此解决方案。

在集群中实例化 CoreOS 节点 1 时的配置

在 CoreOS 服务器启动时,必须启动 OVS,启动 OVS 的过程如下。请注意,执行 OVS 命令的方式取决于 OVS 是部署在 docker 容器内还是 CoreOS 主机实例中。不过在这两种情况下,使用的 OVS 命令列表是相同的:

  1. 使用以下命令运行 OVS 数据路径模块:

    sudo modprobe openvswitch
    
    
  2. 使用以下命令,使用默认的 schema 文件创建一个配置db

    sudo ovsdb-tool create /var/lib/openvswitch/conf.db /usr/share/openvswitch/vswitch.ovsschema
    
    
  3. 使用以下命令运行 OVS DB 服务器:

    sudo ovsdb-server /var/lib/openvswitch/conf.db --remote=punix:/var/run/openvswitch/db.sock --pidfile --detach --log-file
    
    
  4. 使用以下命令运行 OVS-VSCTL:

    sudo ovs-vsctl --no-wait init
    
    
  5. 使用以下命令运行 OVS switchd 守护进程:

    sudo ovs-vswitchd --pidfile --detach
    
    
  6. 创建桥接实例:

    sudo ovs-vsctl add-br br0
    
    
  7. 使用远程节点172.17.8.103创建 GRE 隧道。这里假设 CoreOS 实例 2 的 etho IP 是172.17.8.103

    sudo ovs-vsctl add-port br0 gre1 -- set Interface gre1 type=gre options:remote_ip=172.17.8.103 options:key=100
    
    

    提示

    每个隧道的密钥需要不同。

  8. 创建 veth 接口以提供 docker0 桥接和 OVS 之间的连接:

    • 创建 veth 对:

       ip link add tap1 type veth peer name tap2
      
      
    • 将 veth 对的一端附加到 docker0 桥接:

       sudo brctl addif docker0 tap1
      
      
    • 将 veth 对的另一端附加到 OVS:

       sudo ovs-vsctl add-port br0 tap2
      
      

在为 CoreOS 实例 1 创建容器时的配置

本节描述在 CoreOS 实例中创建新容器时需要进行的配置。

提示

默认情况下,容器的 eth0(veth 对的一端)接口附加在 docker0 桥接上,因此我们不需要显式地将容器的 veth 接口附加到 docker0 桥接上。

设置 docker 容器 eth0 接口的 IP 地址。无法在 docker 实例内部设置 docker 容器的 IP 地址。我们需要使用nsenter工具来完成此操作。请按照以下步骤进行操作:

  1. 执行以下命令并获取pid

    docker inspect --format {{.State.Pid}} <container_name_or_ID>
    
    
  2. 执行以下命令并获取pid

    sudo nsenter --target $PID --mount --uts --ipc --net --pid ifconfig eth0 50.0.0.1
    
    

CoreOS 实例 2 中的配置

本节详细描述在 CoreOS 节点 2 的 coreos-ovs Docker 上执行的操作,以提供此解决方案。

在集群中实例化 CoreOS 节点 2 时的配置

本节描述在 CoreOS 实例初始化过程中需要执行的操作列表。在初始化过程中,必须启动 OVS,启动 OVS 的过程如下:

  • 使用以下命令运行 OVS 数据路径模块:

    sudo modprobe openvswitch
    
    
  • 使用以下命令,使用默认的 schema 文件创建一个配置db

    sudo ovsdb-tool create /var/lib/openvswitch/conf.db /usr/share/openvswitch/vswitch.ovsschema
    
    
  • 使用以下命令运行 OVS DB 服务器:

    sudo ovsdb-server /var/lib/openvswitch/conf.db --remote=punix:/var/run/openvswitch/db.sock --pidfile --detach --log-file
    
    
  • 使用以下命令运行 OVS-VSCTL:

    sudo ovs-vsctl --no-wait init
    
    
  • 使用以下命令运行 OVS switchd 守护进程:

    sudo ovs-vswitchd --pidfile --detach
    
    
  • 创建桥接实例:

    sudo ovs-vsctl add-br br0
    
    
  • 使用远程节点172.17.8.101创建 GRE 隧道。这里假设 CoreOS 实例 1 的 etho IP 是172.17.8.101

    sudo ovs-vsctl add-port br0 gre1 -- set Interface gre1 type=gre options:remote_ip=172.17.8.101 options:key=100
    
    

    提示

    每个隧道的密钥需要不同。

  • 现在,我们需要创建一个 veth 接口来提供 docker0 桥接和 OVS 之间的连接:

    • 创建 veth 对:

       ip link add tap1 type veth peer name tap2
      
      
    • 将 veth 对的一端附加到 docker0 桥接:

       sudo brctl addif docker0 tap1
      
      
    • 将 veth 对的另一端附加到 OVS:

       sudo ovs-vsctl add-port br0 tap2
      
      

CoreOS 实例 2 中创建容器时的配置

本节描述了在 CoreOS 实例中创建新容器时需要进行的配置。

设置 Docker 容器的 eth0 接口的 IP 地址。无法在 Docker 实例内部设置 Docker 容器的 IP 地址。我们需要使用 nsenter 工具来实现。按照以下步骤操作:

  1. 执行以下命令并获取 pid

    docker inspect --format {{.State.Pid}} <container_name_or_ID>
    
    
  2. 执行以下命令并获取 pid

    sudo nsenter --target $PID --mount --uts --ipc --net --pid ifconfig eth0 50.0.0.2
    
    

现在你应该能够从在 CoreOS 实例 1 中运行的 Docker 容器 ping 到在 CoreOS 实例 2 中运行的 Docker 容器。这个解决方案的主要缺点是无法为租户提供虚拟网络。这是因为所有 Docker 容器都连接到 docker0 网桥,而该网桥又连接到 OVS。OVS 作为不同服务器实例之间提供通信的方式。

将容器的 veth 接口连接到 OVS

在这种情况下,CoreOS 实例中的所有 Docker 容器都直接连接到 OVS 网桥。OVS 内部将运行多个网桥实例,每个实例映射到不同的客户/租户。每个租户在系统中需要创建并配置一个新的网桥。在后续创建容器时(对于相同租户),容器的接口应连接到相应的网桥实例。OVS 网桥通过 GRE/VxLAN 隧道为其他 CoreOS 实例提供连接。

将容器的 veth 接口连接到 OVS

配置这种解决方案的逐步程序如下所述,主要包括以下步骤,这些步骤需要在两个 CoreOS 实例上执行:

  • 在集群中实例化 CoreOS 节点时的配置

  • 为租户创建第一个容器时的配置

  • 为租户创建后续容器时的配置

CoreOS 实例 1 中的配置

本节详细描述了在 CoreOS 节点 1 的 coreos-ovs Docker 中执行的操作,以提供该解决方案。

在集群中实例化 CoreOS 节点时的配置

在初始化期间,OVS 需要启动,启动 OVS 的程序如下。请注意,OVS 命令的执行方式取决于 OVS 是部署在 Docker 容器内还是 CoreOS 主机实例中。然而,在这两种情况下,所需使用的 OVS 命令列表没有变化。

  1. 使用以下命令运行 OVS 数据路径模块:

    sudo modprobe openvswitch
    
    
  2. 使用以下命令,使用默认模式文件创建配置 db

    sudo ovsdb-tool create /var/lib/openvswitch/conf.db /usr/share/openvswitch/vswitch.ovsschema
    
    
  3. 使用以下命令运行 OVS DB 服务器:

    sudo ovsdb-server /var/lib/openvswitch/conf.db --remote=punix:/var/run/openvswitch/db.sock --pidfile --detach --log-file
    
    
  4. 使用以下命令运行 OVS-VSCTL:

    sudo ovs-vsctl --no-wait init
    
    
  5. 使用以下命令运行 OVS switchd 守护进程:

    sudo ovs-vswitchd --pidfile --detach
    
    

为租户创建第一个容器时的配置

当为租户首次创建容器时,需要创建一个新的桥接,并将此容器连接到 OVS。具体的操作流程如下:

  1. 将 docker0 桥接实例关闭(docker 创建的默认桥接):

    sudo ip link set dev docker0 down
    
    
  2. 将为容器创建的虚拟接口从 docker0 桥接中分离。该虚拟接口的名称以 veth 开头:

    sudo brctl delif docker0 vethda0657c
    
    
  3. 为租户创建一个桥接实例:

    sudo ovs-vsctl add-br br0
    
    
  4. 在 Docker 中添加已创建的端口。此接口以 veth 开头:

    sudo ovs-vsctl add-port br0 vethda0657c
    
    
  5. 设置 Docker 容器的 eth0 接口的 IP 地址。不能在 Docker 实例内设置 Docker 容器的 IP 地址。我们需要使用 nsenter 工具来实现。按照以下步骤操作:

    • 执行以下命令并获取 pid

      docker inspect --format {{.State.Pid}} <container_name_or_ID>
      
      
    • 执行以下命令并获取 pid

      sudo nsenter --target $PID --mount --uts --ipc --net --pid ifconfig eth0 50.0.0.1
      
      
  6. 创建一个 GRE 隧道,远程节点 IP 为 172.17.8.103。此处假设 CoreOS 实例 2 的 eth0 IP 为 172.17.8.103

    sudo ovs-vsctl add-port br0 gre1 -- set Interface gre1 type=gre options:remote_ip=172.17.8.103 options:key=100
    
    

    提示

    每个隧道的密钥需要不同。

在为租户创建后续容器时的配置

本节描述了在 CoreOS 实例中创建后续容器时需要进行的配置。

  1. 在 Docker 中添加已创建的端口。此接口以 veth 开头:

    sudo ovs-vsctl add-port br0 veth640b626
    
    
  2. 创建一个 GRE 隧道,远程节点 IP 为 172.17.8.103

    sudo ovs-vsctl add-port br0 gre1 -- set Interface gre1 type=gre options:remote_ip=172.17.8.103 options:key=100
    
    

CoreOS 实例 2 中的配置

本节详细描述了在 CoreOS 节点 2 的 coreos-ovs Docker 中执行的操作,以提供此解决方案。

在集群中实例化 CoreOS 节点时的配置

本节描述了在初始化 CoreOS 实例时需要执行的操作列表。在初始化过程中,需要启动 OVS,启动 OVS 的步骤如下:

  1. 使用以下命令运行 OVS 数据路径模块:

    sudo modprobe openvswitch
    
    
  2. 使用默认模式文件通过以下命令创建配置 db

    sudo ovsdb-tool create /var/lib/openvswitch/conf.db /usr/share/openvswitch/vswitch.ovsschema
    
    
  3. 使用以下命令运行 OVS DB 服务器:

    sudo ovsdb-server /var/lib/openvswitch/conf.db --remote=punix:/var/run/openvswitch/db.sock --pidfile --detach --log-file
    
    
  4. 使用以下命令运行 OVS-VSCTL:

    sudo ovs-vsctl --no-wait init
    
    
  5. 使用以下命令运行 OVS switchd 守护进程:

    sudo ovs-vswitchd --pidfile --detach
    
    
  6. 创建一个桥接实例:

    sudo ovs-vsctl add-br br0
    
    
  7. 创建一个 GRE 隧道,远程节点 IP 为 172.17.8.101。此处假设 CoreOS 实例 1 的 eth0 IP 为 172.17.8.101

    sudo ovs-vsctl add-port br0 gre1 -- set Interface gre1 type=gre options:remote_ip=172.17.8.101 options:key=100
    
    

    提示

    每个隧道的密钥需要不同。

在为租户创建第一个容器时的配置

当为租户首次创建容器时,需要创建一个新的桥接,并将此容器连接到 OVS。具体的操作流程如下:

  1. 将 docker0 桥接实例关闭(docker 创建的默认桥接):

    sudo ip link set dev docker0 down
    
    
  2. 将为容器创建的虚拟接口从 docker0 桥接中分离。该虚拟接口的名称以 veth 开头:

    sudo brctl delif docker0 vethda0657c
    
    
  3. 为租户创建一个桥接实例:

    sudo ovs-vsctl add-br br0
    
    
  4. 在 Docker 中添加已创建的端口。此接口以 veth 开头:

    sudo ovs-vsctl add-port br0 vethda0657c
    
    
  5. 设置 Docker 容器 eth0 接口的 IP 地址。无法在 Docker 实例内设置 Docker 容器的 IP 地址。我们需要使用 nsenter 工具来完成此操作。请按照以下步骤操作:

    • 执行以下命令并获取pid

      docker inspect --format {{.State.Pid}} <container_name_or_ID>
      
      
    • 执行以下命令并获取pid

      sudo nsenter --target $PID --mount --uts --ipc --net --pid ifconfig eth0 50.0.0.1
      
      
  6. 创建一个 GRE 隧道,远程节点为172.17.8.103。这里假设 CoreOS 实例 2 的 eth0 IP 为172.17.8.103

    sudo ovs-vsctl add-port br0 gre1 -- set Interface gre1 type=gre options:remote_ip=172.17.8.103 options:key=100
    
    

    提示

    每个隧道的密钥需要不同。

为租户创建后续容器时的配置

本节描述了在 CoreOS 实例中创建后续容器时需要进行的配置。

  1. 添加在 Docker 中创建的端口。此接口以 veth 开头:

    sudo ovs-vsctl add-port br0 veth640b626
    
    
  2. 创建一个 GRE 隧道,远程节点为172.17.8.103

    sudo ovs-vsctl add-port br0 gre1 -- set Interface gre1 type=gre options:remote_ip=172.17.8.103 options:key=100
    
    

现在,您应该能够从在 CoreOS 实例 1 中运行的 Docker 容器 ping 到在 CoreOS 实例 2 中运行的 Docker 容器。这个解决方案的主要优点是,使用此方案可以提供虚拟租户网络。

循环问题

到目前为止,一切正常。然而,当集群中运行的 CoreOS 实例数量增加时,我们可能需要为每个客户/租户在 CoreOS 实例之间创建隧道网格。这将导致网络中出现环路,从而导致流量黑洞。让我们详细了解这个问题并讨论解决方案。

考虑一种拓扑结构,其中在 CoreOS 集群中运行着三个 CoreOS 实例。在这些实例中,绿色和橙色客户的应用程序作为容器进行部署。为了为每个客户提供 VTN,我们需要在这些 CoreOS 实例之间创建隧道。在这种情况下,我们需要为每个客户从每个 CoreOS 实例创建两个隧道。从 CoreOS 实例 1 开始,我们需要为每个客户创建两个隧道:一个指向 CoreOS 实例 2,另一个指向 CoreOS 实例 3。同样,从 CoreOS 实例 2 开始,我们需要创建两个隧道,依此类推。这将导致在客户的桥接实例中形成一个层 2 环路。

提示

在拓扑中创建完整网格所需的隧道总数为 2n-1,其中 n 是租户服务作为容器部署的 CoreOS 实例数量。

由于桥接实例是一个层 2 设备,这将导致在环路中多次转发相同的数据包:

循环问题

避免这个循环问题的一个简单方法是通过在 OVS 中运行生成树协议STP)。STP 已被定义并标准化为 IEEE 802.1D。STP 会根据不同的度量标准,识别出无环拓扑结构。一旦它识别出无环拓扑,它将阻塞一个或多个不属于无环拓扑的端口(在这种情况下,是隧道)。处于阻塞状态的端口将不会转发流量,从而避免了流量黑洞。

在上述拓扑中,当我们根据优先级或配置的桥接 ID 运行生成树时,STP 会阻止一个端口,在本例中阻止从 CoreOS 3 到 CoreOS 2 的端口:

环路问题

启用和配置 OVS 中生成树的命令列表如下:

  • 在桥接实例上启用生成树:

    ovs-vsctl set Bridge br0 stp_enable=true
    
    
  • 设置桥接优先级:

    ovs-vsctl set Bridge br0 other_config:stp-priority=0x7800
    
    
  • 设置端口的路径成本:

    ovs-vsctl set Port eth0 other_config:stp-path-cost=10
    
    

提示

桥接优先级和路径成本配置不是强制性配置。

需要在 OVS 的所有桥接实例上启用生成树,以避免网络中出现任何环路。

总结

在本章中,我们已经了解了 OVS 在容器通信中的重要性以及 OVS 提供的各种优势。由于容器通信有多种通信机制可供选择,在部署 CoreOS 集群时,基于优势、易用性和网络管理工具,您应该在部署中谨慎选择一种或多种通信机制。在下一章,我们将看到 CoreOS 的一些最新发展以及安全性、编排、容器数据卷管理等高级主题。

第八章. 接下来做什么?

在本章中,我们将讨论一些高级的 Docker 和 Core OS 话题,并且我们还将讨论 CoreOS 的未来发展。在大多数话题中,我们不会详细介绍如何使用或部署本章中提到的每个功能,而是会提供足够的信息,让你了解即将到来的新特性。

本章涵盖以下主题:

  • 容器安全性

  • 使用 CoreUpgrade 进行轻松升级

  • 使用 Dex 进行用户身份验证

  • Sysdig

  • 其他容器编排机制,如 Kubernetes、Apache Mesos 和 Swarm

  • Docker 数据卷管理

  • 开放容器项目

容器安全性

安全性是任何部署中的重要方面。应用程序、设备和网络应该具备安全性,以防止任何未经授权的访问。容器/docker 的部署也应该具备安全性,以防止未经授权的访问系统资源,尤其是那些为容器保留的资源。我们将了解 Docker 容器如何确保网络和资源隔离以及安全性。

Docker 使用命名空间将容器与主机上运行的其他容器隔离开来。参与提供安全性的有三个重要的命名空间:

  • 进程命名空间:每个 Linux 系统都有一个进程树,也就是说,有一个进程 ID 为 1 的 init 进程,它也被称为根进程。这个根进程会生成其他的守护进程和子进程。这些守护进程和进程也可以创建它们自己的子进程,如此类推。可以创建一个子命名空间,并将其中一个进程作为根进程。运行在子命名空间中的所有进程无法了解父命名空间的情况;因此,它们不能对父命名空间之外的进程执行任何操作(如信号操作)。

  • 网络命名空间:每个容器都有自己的网络接口,与主机接口不同。它们也有自己的环回接口。容器与外部世界通信的唯一方式是通过主机的桥接网络。桥接网络使得同一主机中运行的不同命名空间之间可以通信,或者与另一主机中的地址进行通信。这样可以确保网络栈对容器来说是独占的,从而使其运行自己的 IP、TCP、UDP 栈等。Docker 通过允许与另一个 Docker 进行通信(通过暴露端口或显式地创建与另一个容器的链接)来增加一层额外的安全性。

  • 资源命名空间:这确保了每个容器拥有其专用的资源,仅供自己使用。资源可以是专用的 RAM、处理器或带有自己文件系统的磁盘。这样可以确保容器的使用不会超出设定的限制,从而确保它不会侵占分配给另一个容器的资源。

下图展示了 Docker 容器提供的隔离性。正如我们所见,容器内运行的服务拥有自己的根进程、文件系统和接口,这些是操作系统通常会提供的功能。这些功能几乎存在于 Docker 用于提供隔离的所有 Linux 发行版中。

容器安全

隔离之后,让我们讨论一下安全性。Docker 以非特权模式启动容器。这意味着容器或容器内运行的应用程序仅拥有执行不需要根权限的操作的权限。例如,使用小于 1024 的端口(尽管非特权 Docker 可以使用 1024 以下和以上的端口)、修改 /etc 中的文件、挂载文件系统等。这样可以确保即使容器中的服务被攻击,它们也无法对主机造成损害,影响也仅限于该容器实例。允许的权限可以进行配置,可以根据容器的环境(受信任或不受信任)来设定严格或宽松的限制。

Docker 还建议保护对 Docker 守护进程的访问,因为它作为根用户在主机上运行。此外,Docker 还建议启用安全的 HTTP 连接,以便在需要远程管理容器时使用。此外,Linux 内核中内建的防火墙,如 SELinux,可以进一步限制 Docker,只允许使用特定的端口和协议(仅 TCP、仅 UDP 等)。同时,建议使用其他 Linux 安全工具和实用程序来保护和强化系统。

更新和补丁 – CoreUpdate

CoreUpdatePremium Managed Service 的一部分,面向需要支持和基于 SLA 的支持的企业客户,以防在部署过程中遇到问题。CoreUpdate 帮助监控集群健康、集群软件版本、管理更新和补丁部署。

CoreUpdate 提供了一个 Web 界面和命令行界面,用于查看每个 CoreOS 实例上运行的版本,并对它们进行升级计划。所有的 CoreOS 实例可以逻辑地分配到多个应用程序组中,升级可以单独管理。比如,它们可以配置为从不同的通道(如稳定版、测试版、开发版)获取升级/补丁。可以在不同的时间安排升级,并且可以有不同的元数据,例如,选择从哪个源获取升级/补丁包。在升级过程中,会显示升级进度,任何错误/信息/警告都会显示出来,以便采取纠正措施。

CoreUpdate 还提供了一个基于 HTTP 的 API,用于将软件管理与开发的应用程序集成。

Dex

我们每个人都曾经历过多种用户认证方式,比如在登录网站时、登录计算机时、登录社交网站时等等。有很多种认证系统,例如由系统管理员为 Linux 或 Microsoft Windows 管理的本地用户,企业范围的 Active Directory 或 LDAP,或者通过身份提供者如 Google、Outlook、Yahoo! 和 Facebook 等进行认证。

作为应用程序开发人员,Dex (github.com/coreos/dex) 通过提供基于标准的现成实现和适用于各种认证系统(包括本地认证)的连接器,解决了用户认证问题。这使得开发人员可以更加专注于业务逻辑,并且信任认证已经得到妥善处理。

由于 Dex 实现基于标准的 (OpenID Connect (OIDC) 核心规范),它是与语言无关的,因为接口已被很好地定义。只需使用符合 OIDC 的客户端库(对应编程语言),就可以开始使用。

有多种认证机制可以通过集成现成的连接器来使用。如果我们要类比的话,这就像是数据库连接器。目前,已有本地连接器和 OIDC 连接器,更多的连接器正在开发中。使用本地连接器时,用户可以通过 Dex 本地维护的认证数据库登录系统,如 Linux 用户 ID 和密码。使用 OIDC 连接器时,用户可以通过其他 OIDC 身份提供者(如 Google 或另一个 Dex 实例)进行认证,因为 Dex 本身就是一个 OIDC 身份提供者。

因此,如果你的系统需要认证,探索 Dex 可能会有帮助。

sysdig

我们都熟悉用于 Linux 的常见调试工具,这些工具可以用来监控和拍摄系统健康状态的快照。例如,如果我们想检查机器是否超负荷使用 CPU 或 RAM,我们会使用 topvmstat 等工具。如果我们需要捕获接口上的数据包,我们会使用 wiresharktcpdump。类似地,我们会使用 iostat 来监控系统的 IO 设备。

sysdig为监控上述所有系统资源提供了集成支持,同时还提供了更多功能。最重要的是,在我们的上下文中,它为容器提供了支持。我们知道,容器在宿主操作系统中运行在不同的命名空间中。因此,运行在容器内的进程对于本机工具(例如ps)也是可见的。在容器环境中,应用程序相关的信息存在两个层级:一个是宿主内核层级,例如,宿主内核看到的进程 ID;另一个是容器级别,例如容器内的进程 ID。所有本机 Linux 工具提供的是宿主内核视图,用户需要关联信息以找出哪些信息与容器相关,并按容器划分信息。为了获取容器应用程序视角下的信息,需要使用 Docker 接口/命令。sysdig解决了这个问题。

让我们通过实践来感受一下sysdig提供的信息。

第一步是安装并运行sysdig。当我们启动用于 sysdig 的 docker 容器时,会进入一个 shell 环境,在这里可以运行sysdig命令。

Vagrant ssh core-01
docker pull 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 -v /boot:/host/boot:ro -v /lib/modules:/host/lib/modules:ro -v /usr:/host/usr:ro sysdig/sysdig

使用以下命令以守护进程方式启动 Docker 容器:

/usr/bin/docker run –d --name busybox busybox /bin/sh -c "while true; do echo Hello World; sleep 60; done"

我们将运行一些示例命令来查找容器的特定信息。首先,我们将在另一个登录窗口使用docker ps以及使用sysdig列出机器上运行的容器:

docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
f71277abf37c        busybox             "/bin/sh -c 'while tr"   4 seconds ago       Up 3 seconds                            busybox
d21a39a0668f        sysdig/sysdig       "/docker-entrypoint.s"   5 minutes ago       Up 5 minutes                            sysdig

我们在这里看到,宿主机上运行了两个容器:一个是sysdig容器,另一个是我们启动的busybox容器。现在,我们将运行相应的sysdig命令:

sysdig -c lscontainers
container.type container.image container.name      container.id
-------------- --------------- ------------------- ------------
docker         busybox         busybox             f74777abf37c
docker         sysdig/sysdig   sysdig              d2da79a0668f

以下命令显示容器的累积 CPU 使用情况:

sysdig -c topcontainers_cpu

我们得到的输出如下:

sysdig

类似地,我们可以通过以下命令查看进程列表、其对应的容器和进程 ID(主机看到的和容器在全局级别看到的)。请注意,-pc标志表示所需的信息是容器上下文中的信息。相同的命令也可以通过提供容器名称来扩展,信息仅显示该容器的内容。

sysdig -pc -c topconns

我们得到的输出如下:

sysdig

到现在为止,您应该已经对sysdig的实用性有了一个大致的了解。与进程和 CPU 信息类似,它还可以提供其他许多功能,比如监控网络、网络 IO、磁盘使用、追踪流量等。并且,大部分监控也可以通过添加-pc开关在容器上下文中进行。

竞争性的容器编排机制

在本节中,我们将探讨当前市场上其他可用的容器编排机制。其中一些编排机制实际上可以与 CoreOS 编排机制互补。正如我们在第三章中已经看到的,创建你的 CoreOS 集群并管理集群,fleet 在 CoreOS 中充当集群管理器,并在集群中的任一节点上实例化 docker 单元/服务。让我们在本章中详细讨论其他编排机制。目前可用的一些主要容器编排机制如下:

  • Kubernetes

  • Apache Mesos

  • Swarm

Kubernetes

Kubernetes 是由 Google 开发的开源容器编排基础设施,用于在服务器集群中部署容器或一组容器。Kubernetes 提供了一种将一组容器作为一个逻辑服务进行部署的方式。这个容器组被称为 pod。除了提供应用程序或容器部署的机制外,Kubernetes 还提供了调度、更新、维护和扩展集群中容器的方式。

Kubernetes 操作的是 pod 而不是容器。一个 pod 可以包含一个容器或一组逻辑上相互关联的容器,如前所述。Kubernetes 由以下组件组成:

  • Kubernetes 主节点

  • Kubernetes 节点(Minion)

  • Kubernetes Pods

  • Kubernetes 服务

下图展示了 Kubernetes 的组件:

Kubernetes

Kubernetes 组件概述

Kubernetes 主节点

顾名思义,Kubernetes 主节点是控制集群中其他节点和 pod 的主节点。它是控制平面,并提供以下服务:

  • Pods 在服务器中的部署

  • 各种 Pods 的复制控制

  • 维护容器的状态

  • 提供用于从外部世界控制节点、Pods 等的 REST API

主 Kubernetes 运行 apiserver、controller manager,并可选地运行 kubelet 和代理服务器。

Kubernetes 节点

Kubernetes 节点也称为 Minion。用户应用程序作为容器或 docker 容器部署在 Minion 中。Kubernetes 节点托管着 Kubernetes 的重要服务,如 kubelet 和 kube-proxy。

Kubelet 负责在节点级别管理 Pods。它充当主要的节点代理。

kube-proxy 或 Kubernetes 网络代理是一个应用程序,负责管理 Kubernetes 节点中的服务。它还负责为在节点中运行的应用程序提供虚拟 IP。

Kubernetes Pods

Kubernetes Pods 是一组在逻辑上紧密耦合并运行在同一 Kubernetes 节点内的容器。属于同一 Pod 的容器共享存储、网络等资源。以下是一个 Pod 的表示:

apiVersion: v1
kind: Pod
metadata:
  name: backend-app
  labels:
    app: backend-app
    version: v1
    role=backend
spec:
  containers:
  - name: javaapp
    image: kingston/javaapp
    ports:
    - containerPort: 443
    volumeMounts:
    - mountPath: /volumes/logs
      name: logs
  - name: logapp
    image: kingston/logapp:v1.1.3
    ports:
    - containerPort: 9999
    volumeMounts:
    - mountPath: /logs
      name: logs
  - name: monitor
    image: kingston/monitor:v1.5.6
    ports:
    - containerPort: 1234

Kubernetes 服务

Kubernetes 服务 是在集群内运行的一组 Pods。服务提供了集群中任何类型的 Pods 所需的关键功能,如负载均衡、应用服务发现、便捷部署等。服务的描述以 JSON 格式表示如下:

{
    "kind": "Service",
    "apiVersion": "v1",
    "metadata": {
        "name": "Web Frontend Service"
    },
    "spec": {
        "selector": {
            "app": "webapp",
            "role": "frontend"
        },
        "ports": [
            {
                "name": "http"
                "protocol": "TCP",
                "port": 80,
                "targetPort": 80
            }
        ]
    }
}

现在,我们已经了解了 Kubernetes 的基础知识。接下来,让我们看看 Kubernetes 如何作为 CoreOS docker/Rackt 容器的编排框架使用。

CoreOS 和 Kubernetes

Kubernetes 也可以用于通过 etcd 分布式键值存储在 CoreOS 中提供高级集群级编排。由于 Kubernetes 是一个强大的容器编排工具,提供了典型部署所需的基本功能,如自动负载均衡、服务发现和容器复制,因此在 CoreOS 环境中,Kubernetes 可以作为容器编排框架使用。

CoreOS 集群中的一个节点可以作为 Kubernetes 主节点,在其中运行 apiserver 和 controller manager。CoreOS 集群中的所有其他节点可以作为 minion,在其中安装并运行 kubelet 和 kube-proxy。

Kubernetes 也可以通过 etcd 分布式键值存储,在 CoreOS 中提供高级集群级编排。

Apache-Mesos

Apache-Mesos 是为涉及数千个主机的大型集群开发的容器集群管理器。Mesos 提供一个分布式内核,跨集群中的不同节点运行,并提供 API 供应用程序管理资源,如内存、CPU、磁盘等,并调度这些资源。

Mesos 的主要组件如下:

  • Mesos 代理

  • Mesos 主节点

  • ZooKeeper

  • Mesos 框架Apache-Mesos

    Mesos 组件概述

Mesos 主节点

Mesos 主节点 守护进程运行在主节点上,管理所有的从节点或代理以及 Mesos 框架。主节点根据配置的调度策略(可以是严格优先或公平共享)来处理资源分配给各个框架。

Mesos 代理

Mesos 代理 负责运行实际的任务。代理会向主节点报告资源的可用性,主节点利用这些信息将特定任务或框架分配给代理运行。

ZooKeeper

在典型的 Mesos 部署中,会有多个主节点可用,以避免单点故障。在这种情况下,ZooKeeper 用于在可用主节点之间选举领导者。

Mesos 框架

Mesos 框架 是在 Mesos 代理节点上运行任务的框架。该框架由两个组件组成:一个注册到主节点的调度器和一个在从节点执行任务的执行器。主节点决定为框架分配的资源数量,并将其分配给框架。调度器从资源列表中选择所提供的资源。

Swarm

Swarm 是 Docker 提供的本地编排机制。像其他编排机制一样,Swarm 也由 Swarm 主节点和 Swarm 代理节点组成。

Swarm 主节点 负责将 Docker 容器编排到不同的 Swarm 代理节点。主节点将在集群中的一个或两个节点上运行,而 Swarm 代理节点 则在网络中的所有节点上运行。

Docker 数据卷管理

我们直到现在才讨论容器的一个重要方面——容器的数据卷管理。在这一部分,我们将了解一些容器数据卷管理的基本概念、数据卷管理中的主要问题及其解决方案。

如你所知,Docker 容器提供了两种不同的方式来管理数据卷:

  • 数据卷

  • 数据卷容器

前面提到的两种机制提供了多种方式来存储数据持久化卷,包括将主机目录挂载为数据卷、将主机文件挂载为数据卷等。这些方式在容器与集群中特定节点/服务器绑定时效果良好。

Docker 数据卷管理

Docker 数据卷管理

当容器从一个服务器迁移到另一个服务器时,数据卷也应该一起迁移。通常,当容器从一个节点迁移到另一个节点时,数据卷并不会随之迁移。这是因为 Docker/编排层分别管理容器和数据卷。

这就需要将这两个实体一起管理。Flocker 提供了一种同时管理 Docker 容器和 Docker 数据卷的方法。

Flocker 可以与容器编排机制如 Kubernetes 和 Mesos 一起使用。虽然与 CoreOS 的集成工作正在进行中,但已经有一些非生产环境的部署可以在 CoreOS 上使用。

Flocker 介绍

Flocker 是一个开源容器数据卷管理器,用于管理数据卷。在 Docker 中,数据卷绑定到单个服务器。然而,在 Flocker 中,数据卷(也称为数据集)是可移植的,因此可以与集群中的任何服务器一起使用。Flocker 管理 Docker 容器及其数据卷。因此,当容器从一个服务器移动到集群中的另一个服务器时,相应的数据卷也会一起移动。

Flocker 介绍

Flocker 集群架构

Flocker 集群架构包括以下组件/服务:

  • Flocker 控制服务

  • Flocker 代理

  • Docker 的 Flocker 插件

Flocker 控制服务

在 Kubernetes 中,我们有 Kubernetes master,同样,Flocker 控制服务充当主节点,并安装在集群中的单个节点上。它暴露 REST API 与外部应用进行接口。这是 Flocker 的大脑,使用户能够监控集群的状态。

Flocker 代理

Flocker 代理接收来自控制服务的命令,并确保 Flocker 代理的状态与期望状态一致。当本地状态与期望状态不匹配时,它会计算出所需的操作,以使本地状态与期望配置匹配。

Docker 的 Flocker 插件

Docker 的 Flocker 插件会部署容器以及数据卷,而无需担心数据卷位于集群中的哪个服务器。每当容器从一个服务器移动到另一个服务器时,插件会确保数据卷也一起迁移。这确保了数据卷在 Flocker 集群中的任何节点上运行。

开放容器项目

随着不同的容器技术的开发,出现了需要标准化容器格式的需求,以提供互操作性并定义容器的标准。为了实现这一目标,CoreOS 团队开始研究一种容器标准化机制,称为 App Container,以定义标准的容器镜像格式、运行时环境和发现协议,朝着标准化、可移植的应用程序运输容器目标迈进。

与此同时,开放容器项目OCP)由一大批行业领导者组成,旨在定义容器标准。开放容器项目由 Linux 基金会主办。CoreOS 的 App Container 也为 OCP 作出了贡献,OCP 项目的最新规范可以通过以下链接找到:github.com/opencontainers/specs

总结

由于 CoreOS 是一个年轻且极具潜力的操作系统,许多开发工作每天都在进行。其中一个最近的重大进展是,谷歌与 CoreOS 联合宣布了一项名为 Tectonic 的新项目,旨在提供完全基于容器的 IT 基础设施,利用 CoreOS 和 Kubernetes 的优势。Tectonic 是一个商业化的 Kubernetes 平台,将 CoreOS 堆栈与 Kubernetes 结合,为任何云环境提供类似谷歌的基础设施。像 Rackspace、Salesforce、MemSQL、Atlassian 和 Pivotal 的 Cloud Foundry 等公司已经部署了 CoreOS。CoreOS 的未来非常光明,因为它的目标是构建下一代 IT 基础设施,同时不增加复杂性。由于安全性是当前 IT 基础设施中的一个主要问题,CoreOS 的主要目标之一是让公司能够在任何环境中安全可靠地运行其应用,带来 CoreOS 光明的未来。

第九章:Index / 索引

A

  • Apache Mesos / Apache Mesos

    • about / Apache-Mesos

    • components / Apache-Mesos

    • Mesos master / Mesos 主节点

    • Mesos agent / Mesos 代理

    • ZooKeeper / ZooKeeper

    • Mesos frameworks / Mesos 框架

  • architectural overview / 架构概览

    • about / 架构概览

    • engine / 引擎

    • agent / 代理

B

  • backend service nodes / 后端服务节点

    • services, running / 服务发现机制

C

  • cgroups / 控制组

    • managing / 控制组
  • chroot

    • about / Chroot
  • chroot jail / chroot 监狱

    • about / Chroot
  • cloud config files / 云配置文件

    • about / 云配置
  • cloud config files, Vagrant

    • coreos key / 云配置

    • ssh_authorized_keys 参数 / 云配置

    • hostname / 云配置

    • users / 云配置

    • write_files / 云配置

    • manage_etc_hosts / 云配置

  • cluster / 集群

    • setting up / CoreOS 集群
  • clustering / 集群化

    • defining / 集群介绍

    • benefits / 集群的意义和优势

    • advantages / 集群的意义和优势

  • competitive container orchestration mechanism / 竞争性的容器编排机制

    • 关于 / 竞争性容器编排机制

    • Kubernetes / Kubernetes

    • CoreOS 和 Kubernetes / CoreOS 和 Kubernetes

  • config.rb 配置文件

    • 关于 / config.rb 配置文件

    • $num_instances 参数 / config.rb 配置文件

    • $shared_folders 参数 / config.rb 配置文件

    • $forwarded_ports 参数 / config.rb 配置文件

    • $vm_gui 参数 / config.rb 配置文件

    • $vm_memory 参数 / config.rb 配置文件

    • $vm_cpus 参数 / config.rb 配置文件

    • $instance_name_prefix 参数 / config.rb 配置文件

    • $update_channel 参数 / config.rb 配置文件

  • 配置

    • 在 CoreOS 实例 1 中 / CoreOS 实例 1 配置, CoreOS 实例 1 配置

    • 在集群中实例化 CoreOS 节点时 / 在集群中实例化 CoreOS 节点 1 时的配置, 在集群中实例化 CoreOS 节点时的配置, 在集群中实例化 CoreOS 节点时的配置

    • 在创建容器期间,针对 CoreOS 实例 1 / 在创建 CoreOS 实例 1 容器时的配置

    • 在 CoreOS 实例 2 中 / CoreOS 实例 2 中的配置, CoreOS 实例 2 中的配置

    • 在集群中实例化 CoreOS 节点 2 时 / 在集群中实例化 CoreOS 节点 2 时的配置

    • 在创建容器期间,针对 CoreOS 实例 2 / 在创建 CoreOS 实例 2 容器时的配置

    • 在创建第一个容器期间,针对租户 / 在为租户创建第一个容器时的配置, 在为租户创建第一个容器时的配置

    • 在创建后续容器期间,针对租户 / 在为租户创建后续容器时的配置, 在为租户创建后续容器时的配置

  • 容器间通信

    • 关于 / 容器间通信

    • Docker0 桥接 / Docker0 桥接和 veth 对

    • veth 对 / Docker0 桥接和 veth 对

    • 链接,使用 / 使用链接

    • 使用公共网络栈 / 使用公共网络栈

  • 容器间通信,在不同的 CoreOS 节点中

    • about / CoreOS 节点中容器之间的通信
  • container management

    • about / 容器管理

    • Linux Container (LXC) / Linux 容器

    • Docker / Docker

    • Docker, versus LXC / Docker 与 LXC 的比较

    • Rocket / Rocket

  • containers, Docker

    • about / 容器

    • volumes / 卷

  • container security

    • about / 容器安全
  • Container to CoreOS host communication

    • about / 容器与 CoreOS 主机通信

    • host networking / 主机网络

    • docker0 bridge / docker0 桥接

  • Container to CoreOS outside world communication

    • about / 容器与 CoreOS 外部世界通信

    • host networking / 主机网络

    • port mapping / 端口映射

  • CoreOS

    • defining / CoreOS 简介

    • about / CoreOS 简介

    • versus Linux distributions / CoreOS 与其他 Linux 发行版的比较

    • and OpenStack / CoreOS 与 OpenStack

    • setting up, on VMware vSphere / 在 VMware vSphere 上设置 CoreOS

    • using / 服务约束简介

    • Flannel, integrating with / 与 CoreOS 集成 Flannel

  • CoreOS 集群

    • 关于 / CoreOS 集群

    • 集群发现 / 集群发现

    • 静态发现 / 静态发现

    • etcd 发现 / etcd 发现

    • DNS 发现 / DNS 发现

    • 服务单元文件 / 服务单元文件

    • 服务,启动 / 启动和停止服务

    • 服务,停止 / 启动和停止服务

    • 架构概述 / 架构概述

    • fleetctl / fleetctl

    • 单元文件选项,为 fleet / 为 fleet 的单元文件选项

    • 服务单元,在集群中实例化 / 在集群中实例化服务单元

    • 恢复,来自节点故障 / 从节点故障恢复

  • CoreOS 集群管理

    • 关于 / CoreOS 集群管理:

    • systemd / systemd

    • fleet / fleet

  • CoreOS 高级架构

    • 关于 / CoreOS 高级架构

    • 服务发现 / 服务发现

    • 容器管理 / 容器管理

    • CoreOS 集群管理 / CoreOS 集群管理:

  • CoreOS 镜像

    • 参考 / 安装 VMware vSphere 客户端
  • coreos 密钥

    • 关于 / Cloud-config

    • etc2 key / Cloud-config

    • fleet parameter / Cloud-config

    • flannel / Cloud-config

    • locksmith / Cloud-config

    • update / Cloud-config

    • Units / Cloud-config

  • CoreUpdate

    • defining / 更新和修补程序 – CoreUpdate
  • CoreUpdate 程序

    • about / CoreOS 与其他 Linux 发行版的比较
  • cURL

    • used, for operations / 使用 cURL 的操作

    • about / 使用 cURL 的操作

    • protocols, defining / 使用 cURL 的操作

    • used, for etcd read / 使用 curl 进行 etcd 读取

    • used, for etcd write / 使用 curl 进行 etcd 写入

    • used, for etcd watch / 使用 curl 进行 etcd 监听

    • references / 使用 curl 进行 etcd 监听

    • example / 使用 curl 的示例

D

  • Dex

    • defining / Dex

    • URL / Dex

  • DNS discovery

    • about / DNS 发现

    • systemd / systemd

  • Docker

    • about / Docker, Docker 简介

    • image / 镜像

    • container / 容器

    • links / 链接

    • installing / 安装 Docker

    • 创建一个示例 Docker 镜像,使用 Docker 文件 / 使用 Docker 文件创建示例 Docker 镜像

  • Docker0 桥接

    • 关于 / Docker0 桥接和 veth 对
  • Docker 数据卷管理

    • 定义 / Docker 数据卷管理

    • Flocker / Flocker 介绍

  • Docker 文件

    • 关于 / Docker 文件
  • Docker Hub

    • 关于 / 镜像
  • Docker 镜像

    • 从 Docker Hub 拉取 / 从 Docker Hub 拉取 Docker 镜像

    • 运行 / 运行 Docker 镜像

  • Docker Link

    • 关于 / 使用 Link
  • Docker 网络

    • 定义 / Docker 网络介绍

    • 容器-容器通信 / 容器–容器通信

    • 容器到 CoreOS 主机通信 / 容器到 CoreOS 主机通信

    • 容器到 CoreOS 外部世界的通信 / 容器到 CoreOS 外部世界的通信

    • 容器-容器通信,跨不同的 CoreOS 节点 / 容器–容器通信在不同的 CoreOS 节点中

E

  • etc2 配置参数

    • 发现 / Cloud-config

    • initial-advertise-peer-urls / Cloud-config

    • advertise-client-urls / Cloud-config

    • listen-client-urls / Cloud-config

    • listen-peer-urls / Cloud-config

  • etcd / etcd

  • etcdctl

    • 用于操作 / 使用 etcdctl 的操作

    • 关于 / 使用 etcdctl 的操作

    • 用于 etcd 写操作 / 使用 etcdctl 进行 etcd 写操作

    • 用于 etcd 读取 / 使用 etcdctl 进行 etcd 读取

    • 用于 etcd 监视 / 使用 etcdctl 进行 etcd 监视

    • 例如,etcd / 使用 etcdctl 的 etcd 示例

  • etcd 发现

    • URL / etcd 发现

F

  • 快速补丁

    • 关于 / CoreOS 与其他 Linux 发行版
  • Flannel/Rudder

    • 定义 / Flannel/Rudder 介绍

    • 安装 / 安装

    • Weave,设置 / 设置 Weave

    • 容器启动 / 容器启动

    • 与 CoreOS 集成 / 将 Flannel 与 CoreOS 集成

  • fleet

    • 关于 / fleet/ fleet
  • fleet 代理

    • 关于 / 架构概览
  • fleetctl

    • 关于 / fleetctl

    • 命令 / fleetctl

    • 标准(本地)和全局单位 / 标准(本地)和全局单位

  • fleet 引擎

    • 关于 / 架构概览
  • Flocker

    • 定义 / Flocker 介绍

    • 控制服务 / Flocker 控制服务

    • 代理 / Flocker 代理

    • 插件,针对 Docker / Flocker 插件针对 Docker

  • Flocker 控制服务

    • 关于 / Flocker 控制服务
  • 前端服务节点

    • 服务,运行中 / 服务发现机制

G

  • GIT

    • 安装 / 安装 GIT

H

  • HAProxy

    • 和服务发现 / HAProxy 和服务发现
  • 高可用性

    • 定义 / 高可用性

K

  • Kubernetes

    • 关于 / Kubernetes

    • 组件 / Kubernetes

    • 主节点 / Kubernetes 主节点

    • 节点 / Kubernetes 节点

    • Pod / Kubernetes Pod

    • 服务 / Kubernetes 服务

    • 和 CoreOS / CoreOS 和 Kubernetes

  • Kubernetes 主节点 / Kubernetes 主节点

  • Kubernetes 节点 / Kubernetes 节点

  • Kubernetes Pod / Kubernetes Pod

  • Kubernetes 服务 / Kubernetes 服务

L

  • Linux 容器 (LXC)

    • 关于 / Linux 容器

    • cgroups / cgroups

    • 命名空间 / 命名空间

    • chroot / Chroot

  • 循环问题

    • 定义 / 循环问题

M

  • 成员节点

    • 前端服务节点 / 服务发现机制

    • 后端服务节点 / 服务发现机制

  • 元数据

    • 用于定义预定义约束 / 使用元数据定义预定义约束
  • 微服务

    • 优势 / Docker

N

  • 命名空间

    • 关于 / 容器安全

    • 进程命名空间 / 容器安全

    • 网络命名空间 / 容器安全

    • 资源命名空间 / 容器安全

  • 网络功能虚拟化(NFV)

    • 关于 / 网络虚拟化介绍
  • 网络命名空间

    • 关于 / 命名空间
  • 网络虚拟化

    • 定义 / 网络虚拟化介绍

    • 在 OVS 中的 OpenFlow 支持 / OVS 中的 OpenFlow 支持

  • 节点级亲和性

    • 定义 / 节点级亲和性

O

  • (OpenID Connect(OIDC)核心规范)

    • 关于 / Dex
  • 开放容器项目(OCP)

    • 关于 / 开放容器项目

    • URL / 开放容器项目

  • OpenFlow

    • 关于 / OVS 中的 OpenFlow 支持
  • OpenFlow 通道 / OpenFlow channel

  • OpenFlow 控制器 / OpenFlow controller

  • OVS 中的 OpenFlow 支持

    • OpenFlow 交换机 / OpenFlow switch

    • OpenFlow 控制器 / OpenFlow controller

    • OpenFlow 通道 / OpenFlow channel

  • OpenFlow 交换机 / OpenFlow 交换机

  • OpenStack

    • 和 CoreOS / CoreOS 与 OpenStack

    • 关于 / CoreOS 与 OpenStack

  • OpenVSwitch(OVS)

    • 定义 / OVS 介绍

    • 架构概述 / OVS 架构概述

    • 组件 / OVS 架构概述

    • 优势,在 CoreOS 中 / 在 CoreOS 中使用 OVS 的优势

    • 运行中,位于 CoreOS / 在 CoreOS 中运行 OVS

  • 操作,etcd

    • 定义 / etcd 操作
  • 覆盖

    • 定义 / 覆盖网络与基础网络介绍
  • 覆盖网络

    • 益处 / 覆盖网络与基础网络介绍
  • OVS(coreos-ovs)

    • URL / 在 CoreOS 中运行 OVS
  • OVS(OpenVSwitch)

    • 关于 / 容器 – 不同 CoreOS 节点之间的容器通信

P

  • 参数,舰队配置

    • etcd 服务器 / Cloud-config

    • 公共 IP / Cloud-config

  • 参数,单元配置

    • 名称 / Cloud-config

    • 命令 / Cloud-config

    • 启用 / Cloud-config

    • 插件 / Cloud-config

  • pod

    • 关于 / Kubernetes
  • 预定义约束

    • 定义,使用的元数据 / 使用元数据的预定义约束
  • 进程命名空间

    • 关于 / 命名空间

R

  • 根进程

    • 关于 / 容器安全

S

  • 服务

    • 启动 / 启动和停止服务

    • 停止 / 启动和停止服务

    • fleet / fleet

  • 服务链

    • 定义 / 服务链的简介与必要性

    • 服务链的需求 / 服务链的简介与必要性

  • 服务约束

    • 定义 / 服务约束简介
  • 服务发现

    • 机制 / 服务发现机制

    • 操作,etcd 的 / etcd 的操作

    • 和 HAProxy / HAProxy 与服务发现

  • 服务发现,CoreOS 高层架构

    • 关于 / 服务发现

    • etcd / etcd

  • 服务级别亲和性/反亲和性

    • 定义 / 服务级别亲和性/反亲和性
  • 软件定义网络(SDN)

    • 关于 / 网络虚拟化简介
  • 解决方案,OpenVSwitch (OVS)

    • 将 docker0 桥接连接到 OVS / 将 docker0 桥接连接到 OVS

    • veth 接口,连接到 / 将容器的 veth 接口连接到 OVS

  • 跨越树协议(STP)

    • 关于 / 环路问题

    • 命令 / 环路问题

  • Swarm

    • 定义 / Swarm
  • Swarm 代理

    • 关于 / Swarm
  • Swarm 主节点

    • 关于 / Swarm
  • sysdig

    • 定义 / sysdig

T

  • 目标

    • 关于 / systemd

U

  • 下层网络

    • 定义 / 覆盖和下层网络介绍
  • 联合文件系统 / 镜像

  • 单元

    • 关于 / systemd
  • 单元文件

    • 关于 / systemd

V

  • Vagrant

    • 关于 / Vagrant 介绍

    • 安装 / 安装 Vagrant

    • URL / 安装 Vagrant

    • 配置文件 / Vagrant 配置文件

    • 云配置文件 / Cloud-config

    • config.rb 配置文件 / config.rb 配置文件

    • 用于启动 CoreOS 虚拟机 / 使用 Vagrant 启动 CoreOS 虚拟机

  • VirtualBox

    • 安装 / 安装 VirtualBox
  • 虚拟以太网设备

    • 关于 / Docker0 桥接和 veth 对
  • VMware vSphere 客户端

    • 安装 / 安装 VMware vSphere 客户端

W

  • Weave

    • 定义 / Weave 介绍

    • 关于 / Weave 介绍

  • weaveDNS

    • 关于 / Weave 介绍
posted @ 2025-07-05 15:45  绝不原创的飞龙  阅读(148)  评论(0)    收藏  举报