精通-KVM-虚拟化-全-

精通 KVM 虚拟化(全)

原文:zh.annas-archive.org/md5/937685F0CEE189D5B83741D8ADA1BFEE

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

《精通 KVM 虚拟化》是一本应该让你在阅读本书的过程中从零到英雄的书。这本书是 KVM 所提供的一切的大集合,适用于 DevOps 和普通系统管理人员,以及开发人员。我们希望通过阅读本书,你能够理解 KVM 的内部工作原理,以及更高级的概念和中间的一切。无论你是刚刚开始接触 KVM 虚拟化,还是已经很熟悉,你都应该在本书的页面上找到一些有价值的信息。

这本书适合谁

本书适用于 Linux 初学者和专业人士,因为它并不一定需要事先对 Linux 有高级知识。随着你阅读本书,我们会带你走向成功——这是学习过程的一个组成部分。如果你对 KVM、OpenStack、ELK Stack、Eucalyptus 或 AWS 感兴趣,我们都有涵盖。

本书涵盖的内容

第一章理解 Linux 虚拟化,讨论了不同类型的虚拟化、Hypervisor 类型和 Linux 虚拟化概念(Xen 和 KVM)。在本章中,我们试图从高层次的角度解释 Linux 虚拟化的一些基础知识以及它如何适应云环境。

第二章KVM 作为虚拟化解决方案,从讨论虚拟化概念和虚拟化环境的需求开始,解释了虚拟化的基本硬件和软件方面,以及虚拟化的各种方法。在本章中,我们开始讨论 KVM 和 libvirt,这些概念将贯穿本书始终。

第三章安装 KVM Hypervisor、libvirt 和 oVirt,扩展了第二章,引入了一些新概念,包括 oVirt,这是一个可以用来管理我们虚拟化 Linux 基础设施的 GUI。我们将带你了解硬件是否兼容 KVM 的过程,介绍一些虚拟机部署的基本命令,然后解释在相同场景中如何使用 oVirt。

第四章Libvirt 网络,解释了 libvirt 如何与各种网络概念交互——不同模式下的虚拟交换机、如何使用 CLI 工具管理 libvirt 网络、TAP 和 TUN 设备、Linux 桥接和 Open vSwitch。之后,我们通过使用 SR-IOV 讨论了更极端的网络示例,这是一个应该让我们获得最低延迟和最高吞吐量的概念,在每一毫秒都很重要的情况下使用。

第五章Libvirt 存储,是一个重要章节,因为存储概念在构建虚拟化和云环境时非常重要。我们讨论了 KVM 支持的每种存储类型——本地存储池、NFS、iSCSI、SAN、Ceph、Gluster、多路径和冗余、虚拟磁盘类型等等。我们还为你展示了存储的未来——包括 NVMe 和 NVMeoF 等技术。

第六章虚拟显示设备和协议,讨论了各种虚拟机显示类型、远程协议,包括 VNC 和 Spice,以及 NoVNC,它确保了显示的可移植性,因为我们可以在 Web 浏览器中使用 NoVNC 来使用虚拟机控制台。

第七章虚拟机:安装、配置和生命周期管理,介绍了部署和配置 KVM 虚拟机的其他方法,以及迁移过程,这对任何类型的生产环境都非常重要。

第八章《创建和修改 VM 磁盘、模板和快照》,讨论了各种虚拟机镜像类型、虚拟机模板化过程、快照的使用以及在使用快照时的一些用例和最佳实践。它还作为下一章的介绍,在下一章中,我们将以更加流畅的方式使用模板化和虚拟机磁盘来定制虚拟机引导后使用cloud-initcloudbase-init

第九章《使用 cloud-init 自定义虚拟机》,讨论了云环境中最基本的概念之一-如何在虚拟机镜像/模板引导后进行定制。Cloud-init 在几乎所有的云环境中用于进行引导后的 Linux 虚拟机配置,我们解释了它的工作原理以及如何在您的环境中使其工作。

第十章《自动化 Windows 客户端部署和定制化》,是第九章的延续,重点关注 Microsoft Windows 虚拟机模板化和引导后定制化。为此,我们使用了 cloudbase-init,这个概念基本上与 cloud-init 相同,但仅适用于基于 Microsoft 的操作系统。

第十一章《Ansible 和编排自动化脚本》,带领我们踏上 Ansible 之旅的第一部分-部署 AWX 和 Ansible,并描述如何在基于 KVM 的环境中使用这些概念。这只是现代 IT 中使用的 Ansible 使用模型之一,因为整个 DevOps 和基础设施即代码的故事在全球范围内得到了更多的关注。

第十二章《使用 OpenStack 扩展 KVM》,讨论了基于 KVM 构建云环境的过程。当使用 KVM 时,OpenStack 是交付这一点的标准方法。在本章中,我们讨论了所有 OpenStack 构建块和服务,如何从头开始部署它,并描述了如何在生产环境中使用它。

第十三章《使用 AWS 扩展 KVM》,带领我们走向使用公共和混合云概念的旅程,使用 Amazon Web Services(AWS)。与几乎所有其他章节一样,这是一个非常实践性的章节,您也可以用它来对 AWS 有所了解,这对于在本章末使用 Eucalyptus 部署混合云基础设施至关重要。

第十四章《监控 KVM 虚拟化平台》,介绍了通过 Elasticsearch、Logstash、Kibana(ELK)堆栈进行监控的非常流行的概念。它还带领您完成了设置和集成 ELK 堆栈与您的 KVM 基础设施的整个过程,一直到最终结果-使用仪表板和 UI 来监视您的基于 KVM 的环境。

第十五章《KVM 虚拟机性能调优和优化》,讨论了在基于 KVM 的环境中调优和优化的各种方法,通过解构所有基础设施设计原则并将其正确使用。我们在这里涵盖了许多高级主题- NUMA、KSM、CPU 和内存性能、CPU 绑定、VirtIO 的调优以及块和网络设备。

第十六章KVM 平台故障排除指南,从基础知识开始-故障排除 KVM 服务和日志记录,并解释了 KVM 和 oVirt、Ansible 和 OpenStack、Eucalyptus 和 AWS 的各种故障排除方法。这些都是我们在撰写本书时在生产环境中遇到的真实问题。在本章中,我们基本上讨论了与本书的每一章相关的问题,包括与快照和模板相关的问题。

充分利用本书

我们假设您至少具有基本的 Linux 知识和安装虚拟机的先验经验作为本书的先决条件。

实际代码演示

本书的实际代码演示视频可在bit.ly/32IHMdO上观看。

下载彩色图像

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

使用的约定

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

文本中的代码:表示文本中的代码单词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 句柄。这是一个例子:“我们需要做的就是取消注释配置文件中定义的一个管道,该文件位于/etc/logstash文件夹中。”

代码块设置如下:

<memoryBacking>
    <locked/>
</memoryBacking>

当我们希望引起您对代码块的特定部分的注意时,相关行或项目将以粗体显示:

POWER TTWU_QUEUE NO_FORCE_SD_OVERLAP RT_RUNTIME_SHARE NO_LB_MIN NUMA 
NUMA_FAVOUR_HIGHER NO_NUMA_RESIST_LOWER

粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词会在文本中以这种方式出现。这是一个例子:“在您点击刷新按钮之后,新数据应该出现在页面上。”

提示或重要说明

出现在这样。

联系我们

我们始终欢迎读者的反馈。

一般反馈:如果您对本书的任何方面有疑问,请在邮件主题中提及书名,并发送电子邮件至 customercare@packtpub.com 与我们联系。

勘误:尽管我们已经尽最大努力确保内容的准确性,但错误确实会发生。如果您在本书中发现错误,我们将不胜感激。请访问www.packtpub.com/support/errata,选择您的书籍,点击勘误提交表单链接,并输入详细信息。

盗版:如果您在互联网上发现我们作品的任何非法副本,请向我们提供位置地址或网站名称,我们将不胜感激。请通过 copyright@packt.com 与我们联系,并提供材料链接。

如果您有兴趣成为作者:如果您在某个专题上有专业知识,并且有兴趣撰写或为一本书做出贡献,请访问authors.packtpub.com

评论

请留下评论。阅读并使用本书后,为什么不在购买书籍的网站上留下评论呢?潜在读者可以看到并使用您的客观意见来做出购买决定,我们在 Packt 可以了解您对我们产品的看法,我们的作者也可以看到您对他们书籍的反馈。谢谢!

有关 Packt 的更多信息,请访问packt.com

第一部分:KVM 虚拟化基础

本部分为您提供了对 Linux 虚拟化中盛行的技术以及其优于其他虚拟化解决方案的见解。我们将讨论重要的数据结构以及 libvirt、QEMU 和 KVM 的内部实现。

本书的这部分包括以下章节:

  • 第一章理解 Linux 虚拟化

  • 第二章KVM 作为虚拟化解决方案

第一章:理解 Linux 虚拟化

虚拟化是一项技术,它引发了 IT 整合的重大技术转变,提供了更有效地利用资源的方式,云作为虚拟化的一个更综合、自动化和编排的版本,重点不仅在于虚拟机,还在于其他附加服务。本书共有 16 章,所有这些章节都已经安排好,以涵盖基于内核的虚拟机(KVM)虚拟化的所有重要方面。我们将从 KVM 的基本主题开始,如虚拟化概念的历史和 Linux 虚拟化,然后继续查看 KVM 的高级主题,如自动化、编排、虚拟网络、存储和故障排除。本章将为您提供有关 Linux 虚拟化中主导技术及其优势的见解。

在本章中,我们将涵盖以下主题:

  • Linux 虚拟化及其基本概念

  • 虚拟化的类型

  • Hypervisor/VMM

  • 开源虚拟化项目

  • Linux 虚拟化在云中为您提供了什么

Linux 虚拟化及其起源

虚拟化是一种概念,它创建虚拟化资源并将其映射到物理资源。这个过程可以使用特定的硬件功能(通过某种分区控制器进行分区)或软件功能(hypervisor)来完成。因此,举个例子,如果你有一台物理 PC 服务器,有 16 个核心运行一个 hypervisor,你可以轻松地创建一个或多个每个有两个核心的虚拟机并启动它们。关于你可以启动多少虚拟机的限制是基于供应商的。例如,如果你运行 Red Hat Enterprise Virtualization v4.x(基于 KVM 的裸机 hypervisor),你可以使用高达 768 个逻辑 CPU 核心或线程(你可以在access.redhat.com/articles/906543上阅读更多信息)。无论如何,hypervisor 将是首选,它将尽可能有效地管理,以便所有虚拟机工作负载尽可能多地占用 CPU 时间。

我清楚地记得在 2004 年写了我的第一篇关于虚拟化的文章。AMD 在 2003 年推出了它的第一款消费级 64 位 CPU(Athlon 64,Opteron),这让我有点困惑。英特尔还有点犹豫是否推出 64 位 CPU - 缺乏 64 位微软 Windows 操作系统可能也有一些关系。Linux 已经支持 64 位,但这是 PC 市场即将迎来许多新事物的黎明。虚拟化本身并不是一个革命性的想法,因为其他公司已经有非 x86 产品可以进行几十年的虚拟化(例如,IBM CP-40 及其 S/360-40,从 1967 年开始)。但对于 PC 市场来说,这肯定是一个新的想法,当时市场正处于许多事情同时发生的奇怪阶段。从 64 位 CPU 切换到多核 CPU,然后从 DDR1 切换到 DDR2,再从 PCI/ISA/AGP 切换到 PCI Express,你可以想象,这是一个具有挑战性的时期。

具体来说,我记得曾经想过各种可能性 - 在一个操作系统上运行另一个操作系统,然后在其上运行另外几个操作系统是多么酷。在出版行业工作,你可以想象这将为任何人的工作流程提供多少优势,我记得当时真的很兴奋。

15 年左右的发展之后,我们现在在虚拟化解决方案方面有了一个竞争激烈的市场 - 红帽公司的 KVM,微软的 Hyper-V,VMware 的 ESXi,甲骨文的 Oracle VM,谷歌和其他关键参与者为用户和市场主导地位而争夺。这导致了各种云解决方案的发展,如 EC2,AWS,Office 365,Azure,vCloud Director 和 vRealize Automation,用于各种类型的云服务。总的来说,对于 IT 来说,这是一个非常富有成效的 15 年,你不觉得吗?

然而,回到 2003 年 10 月,随着 IT 行业发生的种种变化,有一个对于这本书和 Linux 虚拟化来说非常重要的变化:第一个针对 x86 架构的开源 Hypervisor——Xen的推出。它支持各种 CPU 架构(Itanium、x86、x86_64 和 ARM),可以运行各种操作系统,包括 Windows、Linux、Solaris 和一些 BSD 的变种。它仍然作为一种虚拟化解决方案存在并且备受一些供应商的青睐,比如 Citrix(XenServer)和 Oracle(Oracle VM)。我们稍后会在本章节中详细介绍 Xen 的更多技术细节。

在开源市场中最大的企业参与者 Red Hat 在其 Red Hat Enterprise Linux 5 的最初版本中包含了 Xen 虚拟化,该版本于 2007 年发布。但 Xen 和 Red Hat 并不是天作之合,尽管 Red Hat 在其 Red Hat Enterprise Linux 5 发行版中搭载了 Xen,但在 2010 年的 Red Hat Enterprise Linux 6 中,Red Hat 转向了KVM,这在当时是一个非常冒险的举动。实际上,从之前的版本 5.3/5.4 开始,即 2009 年发布的两个版本,从 Xen 迁移到 KVM 的整个过程就已经开始了。要把事情放到背景中,当时 KVM 还是一个相当年轻的项目,只有几年的历史。但为什么会发生这种情况有很多合理的原因,从Xen 不在主线内核中,KVM 在,到政治原因(Red Hat 希望对 Xen 的开发拥有更多的影响力,而这种影响力随着时间的推移而逐渐减弱)。

从技术上讲,KVM 采用了一种不同的模块化方法,将 Linux 内核转换为支持的 CPU 架构的完全功能的 Hypervisor。当我们说支持的 CPU 架构时,我们指的是 KVM 虚拟化的基本要求——CPU 需要支持硬件虚拟化扩展,即 AMD-V 或 Intel VT。简单来说,你真的需要非常努力才能找到一个不支持这些扩展的现代 CPU。例如,如果你在服务器或台式电脑上使用的是英特尔 CPU,那么首批支持硬件虚拟化扩展的 CPU 可以追溯到 2006 年(Xeon LV)和 2008 年(Core i7 920)。同样,我们稍后会在本章节和下一章节中详细介绍 KVM 的更多技术细节,并对 KVM 和 Xen 进行比较。

虚拟化类型

有各种类型的虚拟化解决方案,它们都针对不同的用例,并且取决于我们虚拟化的硬件或软件堆栈的不同部分,也就是在虚拟化什么。值得注意的是,从如何虚拟化的角度来看,也有不同类型的虚拟化——包括分区、完全虚拟化、半虚拟化、混合虚拟化或基于容器的虚拟化。

因此,让我们首先介绍今天 IT 领域中基于在虚拟化什么的五种不同类型的虚拟化:

  • 桌面虚拟化(虚拟桌面基础设施(VDI)):这被许多企业公司使用,并在许多场景中提供巨大优势,因为用户不依赖于用于访问其桌面系统的特定设备。他们可以从手机、平板电脑或计算机连接,并且通常可以从任何地方连接到他们的虚拟桌面,就像他们坐在工作场所使用硬件计算机一样。优势包括更容易的集中管理和监控,更简化的更新工作流程(您可以在 VDI 解决方案中更新数百台虚拟机的基础映像,并在维护时间重新链接到数百台虚拟机),简化的部署流程(不再需要在台式机、工作站或笔记本电脑上进行物理安装,以及集中应用程序管理的可能性),以及更容易管理合规性和安全相关选项。

  • 服务器虚拟化:这是今天绝大多数 IT 公司使用的技术。它提供了与常规物理服务器相比更好的服务器虚拟机整合,同时在常规物理服务器上提供了许多其他操作优势-更容易备份,更节能,在服务器之间移动工作负载更自由等。

  • 应用程序虚拟化:通常使用一些流式传输/远程协议技术来实现,例如 Microsoft App-V,或者一些可以将应用程序打包成可以挂载到虚拟机并进行一致设置和交付选项的卷的解决方案,例如 VMware App Volumes。

  • 网络虚拟化(以及更广泛的基于云的概念称为软件定义网络(SDN)):这是一种创建独立于物理网络设备(如交换机)的虚拟网络的技术。在更大的范围上,SDN 是网络虚拟化理念的延伸,可以跨越多个站点、位置或数据中心。在 SDN 的概念中,整个网络配置都是在软件中完成的,而不一定需要特定的物理网络配置。网络虚拟化的最大优势在于,您可以轻松管理跨多个位置的复杂网络,而无需对网络数据路径上的所有物理设备进行大规模的物理网络重新配置。这个概念将在第四章《libvirt 网络》和第十二章《使用 OpenStack 扩展 KVM》中进行解释。

  • 存储虚拟化(以及一个更新的概念软件定义存储(SDS)):这是一种技术,它通过将汇集的物理存储设备创建为虚拟存储设备,我们可以将其作为单个存储设备进行集中管理。这意味着我们正在创建某种抽象层,将存储设备的内部功能与计算机、应用程序和其他类型的资源隔离开来。作为其延伸,SDS 通过从底层硬件抽象控制和管理平面来解耦存储软件堆栈,以及为虚拟机和应用程序提供不同类型的存储资源(块、文件和基于对象的资源)。

如果你看看这些虚拟化解决方案并大规模扩展它们(提示:云),那么你会意识到你需要各种工具和解决方案来有效地管理不断增长的基础设施,因此开发了各种自动化和编排工具。本书后面将介绍其中一些工具,如第十一章中的 Ansible,编排和自动化的 Ansible。暂时来说,我们只能说,你不能仅依靠标准实用程序(脚本,命令,甚至 GUI 工具)来管理包含数千个虚拟机的环境。你肯定需要一个更加程序化、API 驱动的方法,与虚拟化解决方案紧密集成,因此开发了 OpenStack、OpenShift、Ansible 和Elasticsearch,Logstash,KibanaELK)堆栈,我们将在第十四章中介绍使用 ELK 堆栈监视 KVM 虚拟化平台

如果我们谈论如何虚拟化虚拟机作为一个对象,有不同类型的虚拟化:

  • 分区:这是一种虚拟化类型,其中 CPU 被分成不同的部分,每个部分作为一个独立的系统。这种虚拟化解决方案将服务器隔离成分区,每个分区可以运行一个单独的操作系统(例如IBM 逻辑分区(LPARs))。

  • 完全虚拟化:在完全虚拟化中,使用虚拟机来模拟常规硬件,而不知道它被虚拟化的事实。这是出于兼容性原因 - 我们不必修改要在虚拟机中运行的客户操作系统。我们可以为此使用软件和硬件的方法。

基于软件:使用二进制转换来虚拟执行敏感指令集,同时使用软件来模拟硬件,这会增加开销并影响可扩展性。

基于硬件:从方程式中去除二进制转换,同时与 CPU 的虚拟化功能(AMD-V,Intel VT)进行接口,这意味着指令集直接在主机 CPU 上执行。这就是 KVM 所做的(以及其他流行的超级监视程序,如 ESXi,Hyper-V 和 Xen)。

  • 半虚拟化:这是一种虚拟化类型,其中客户操作系统了解自己被虚拟化的事实,并且需要进行修改,以及其驱动程序,以便它可以在虚拟化解决方案之上运行。同时,它不需要 CPU 虚拟化扩展来运行虚拟机。例如,Xen 可以作为半虚拟化解决方案工作。

  • 混合虚拟化:这是一种使用完全虚拟化和半虚拟化最大优点的虚拟化类型 - 客户操作系统可以无需修改地运行(完全),并且我们可以将额外的半虚拟化驱动程序插入虚拟机以处理虚拟机工作的某些特定方面(通常是 I/O 密集型内存工作负载)。Xen 和 ESXi 也可以以混合虚拟化模式工作。

  • 基于容器的虚拟化:这是一种应用虚拟化类型,使用容器。容器是一个对象,它打包了一个应用程序及其所有依赖项,以便应用程序可以进行扩展和快速部署,而无需虚拟机或超级监视程序。请记住,有些技术可以同时作为超级监视程序和容器主机运行。这种技术的一些例子包括 Docker 和 Podman(Red Hat Enterprise Linux 8 中 Docker 的替代品)。

接下来,我们将学习如何使用超级监视程序。

使用超级监视程序/虚拟机管理器

正如其名称所示,虚拟机管理器(VMM)或虚拟机监视器是负责监视和控制虚拟机或客户操作系统的软件。虚拟机监视器/VMM 负责确保不同的虚拟化管理任务,例如提供虚拟硬件、虚拟机生命周期管理、迁移虚拟机、实时分配资源、定义虚拟机管理策略等。虚拟机监视器/VMM 还负责有效地控制物理平台资源,例如内存转换和 I/O 映射。虚拟化软件的主要优势之一是其能够在同一物理系统或硬件上运行多个客户操作系统。这些多个客户系统可以是相同的操作系统或不同的操作系统。例如,可以在同一物理系统上运行多个 Linux 客户系统作为客户。VMM 负责为这些客户操作系统分配所请求的资源。系统硬件,例如处理器、内存等,必须根据它们的配置分配给这些客户操作系统,而 VMM 可以负责这项任务。因此,VMM 是虚拟化环境中的关键组件。

就类型而言,我们可以将虚拟机监视器分类为类型 1 或类型 2。

类型 1 和类型 2 虚拟机监视器

虚拟机监视器主要根据其在系统中的位置或者换句话说,基础操作系统是否存在于系统中,被归类为类型 1 或类型 2 虚拟机监视器。但是并没有关于类型 1 和类型 2 虚拟机监视器的明确或标准定义。如果 VMM/虚拟机监视器直接在硬件顶部运行,通常被认为是类型 1 虚拟机监视器。如果存在操作系统,并且如果 VMM/虚拟机监视器作为一个独立层运行,它将被视为类型 2 虚拟机监视器。再次强调,这个概念存在争议,并且没有标准定义。类型 1 虚拟机监视器直接与系统硬件交互;它不需要任何主机操作系统。您可以直接在裸机系统上安装它,并使其准备好托管虚拟机。类型 1 虚拟机监视器也被称为裸机嵌入式本地虚拟机监视器。oVirt-node、VMware ESXi/vSphere 和Red Hat Enterprise Virtualization HypervisorRHEV-H)是类型 1 Linux 虚拟机监视器的示例。以下图表提供了类型 1 虚拟机监视器设计概念的说明:

图 1.1 - 类型 1 虚拟机监视器设计

图 1.1 - 类型 1 虚拟机监视器设计

以下是类型 1 虚拟机监视器的优点:

  • 易于安装和配置

  • 体积小;优化以将大部分物理资源提供给托管的客户(虚拟机)

  • 生成的开销较小,因为它只带有运行虚拟机所需的应用程序

  • 更安全,因为一个客户系统中的问题不会影响运行在虚拟机监视器上的其他客户系统

然而,类型 1 虚拟机监视器不利于定制。通常,当您尝试在其上安装任何第三方应用程序或驱动程序时,会有一些限制。

另一方面,类型 2 虚拟机监视器位于操作系统之上,允许您进行多项自定义。类型 2 虚拟机监视器也被称为依赖于主机操作系统进行操作的托管虚拟机监视器。类型 2 虚拟机监视器的主要优点是广泛的硬件支持,因为底层主机操作系统控制硬件访问。以下图表提供了类型 2 虚拟机监视器设计概念的说明:

图 1.2 - 类型 2 虚拟机监视器设计

](https://gitee.com/OpenDocCN/freelearn-linux-zh/raw/master/docs/ms-kvm-vrt/img/B14834_01_02.jpg)

图 1.2 - 类型 2 虚拟机监视器设计

我们何时使用类型 1 和类型 2 的 hypervisor?这主要取决于我们是否已经在服务器上运行了一个想要部署虚拟机的操作系统。例如,如果我们已经在工作站上运行 Linux 桌面,我们可能不会格式化工作站并安装 hypervisor – 这根本没有意义。这是类型 2 hypervisor 的一个很好的用例。众所周知的类型 2 hypervisors 包括 VMware Player、Workstation、Fusion 和 Oracle VirtualBox。另一方面,如果我们专门打算创建一个用于托管虚拟机的服务器,那么这就是类型 1 hypervisor 的领域。

开源虚拟化项目

以下表格是 Linux 中的开源虚拟化项目列表:

图 1.3 – Linux 中的开源虚拟化项目

图 1.3 – Linux 中的开源虚拟化项目

在接下来的章节中,我们将讨论 Xen 和 KVM,它们是 Linux 中领先的开源虚拟化解决方案。

Xen

Xen 起源于剑桥大学的一个研究项目。Xen 的首次公开发布是在 2003 年。后来,剑桥大学这个项目的领导者 Ian Pratt 与同样来自剑桥大学的 Simon Crosby 共同创立了一家名为 XenSource 的公司。该公司开始以开源方式开发该项目。2013 年 4 月 15 日,Xen 项目被移至 Linux 基金会作为一个协作项目。Linux 基金会为 Xen 项目推出了一个新的商标,以区别于旧的 Xen 商标的任何商业用途。有关此更多详细信息,请访问xenproject.org/

Xen hypervisor 已经移植到多个处理器系列,如 Intel IA-32/64、x86_64、PowerPC、ARM、MIPS 等。

Xen 的核心概念有四个主要构建块:

  • Xen hypervisor:Xen 的一个组成部分,处理物理硬件和虚拟机之间的互联。它处理所有中断、时间、CPU 和内存请求以及硬件交互。

  • Dom0:Xen 的控制域,控制虚拟机的环境。其中的主要部分称为 QEMU,这是一款通过二进制转换来模拟常规计算机系统的软件。

  • 管理工具:我们用来管理整个 Xen 环境的命令行工具和 GUI 工具。

  • 虚拟机(非特权域,DomU):我们在 Xen 上运行的客户。

如下图所示,Dom0 是一个完全独立的实体,控制其他虚拟机,而其他所有虚拟机都快乐地堆叠在一起,使用由 hypervisor 提供的系统资源:

图 1.4 – Xen

图 1.4 – Xen

我们稍后会提到的一些管理工具实际上可以与 Xen 虚拟机一起使用。例如,virsh命令可以轻松连接和管理 Xen 主机。另一方面,oVirt 是围绕 KVM 虚拟化设计的,这绝对不是管理基于 Xen 的环境的首选解决方案。

KVM

KVM 代表了最新一代的开源虚拟化。该项目的目标是创建一个现代的 hypervisor,借鉴了以前技术的经验,并利用了今天可用的现代硬件(VT-x、AMD-V 等)。

KVM 只是在安装 KVM 内核模块时将 Linux 内核转换为 hypervisor。然而,由于标准 Linux 内核是 hypervisor,它受益于对标准内核的更改(内存支持,调度程序等)。对这些 Linux 组件的优化,例如 3.1 内核中的调度程序,4.20+内核中嵌套虚拟化的改进,用于缓解 Spectre 攻击的新功能,支持 AMD 安全加密虚拟化,4/5.x 内核中的 Intel iGPU 直通等,都有利于 hypervisor(主机操作系统)和 Linux 客户操作系统。对于 I/O 仿真,KVM 使用一个用户空间软件 QEMU;这是一个进行硬件仿真的用户空间程序。

QEMU 模拟处理器和一长串外围设备,如磁盘、网络、VGA、PCI、USB、串行/并行端口等,以构建一个完整的虚拟硬件,可以在其上安装客户操作系统。这种仿真由 KVM 提供动力。

Linux 虚拟化在云中为您提供了什么

云是过去 10 年左右几乎所有与 IT 相关的讨论中的一个流行词。如果我们回顾一下云的历史,我们可能会意识到亚马逊是云市场中的第一个关键参与者,2006 年发布了 Amazon Web Services(AWS)和 Amazon Elastic Compute Cloud(EC2)。Google Cloud Platform 于 2008 年发布,Microsoft Azure 于 2010 年发布。就基础设施即服务(IaaS)云模型而言,这些是目前最大的 IaaS 云提供商,尽管还有其他一些(IBM Cloud,VMware Cloud on AWS,Oracle Cloud 和阿里云等)。如果你浏览这个列表,你很快就会意识到大多数这些云平台都是基于 Linux 的(举个例子,亚马逊使用 Xen 和 KVM,而 Google Cloud 使用 KVM 虚拟化)。

目前,有三个主要的开源云项目使用 Linux 虚拟化来构建私有和/或混合云的 IaaS 解决方案:

  • OpenStack:一个完全开源的云操作系统,由几个开源子项目组成,提供了创建 IaaS 云的所有构建块。KVM(Linux 虚拟化)是 OpenStack 部署中使用最多(并且得到最好支持)的 hypervisor。它由供应商不可知的 OpenStack 基金会管理。如何使用 KVM 构建 OpenStack 云将在《第十二章》中详细解释,使用 OpenStack 扩展 KVM

  • CloudStack 这是另一个开源的 Apache 软件基金会(ASF)控制的云项目,用于构建和管理高度可扩展的多租户 IaaS 云,并且完全兼容 EC2/S3 API。虽然它支持所有顶级 Linux hypervisors,但大多数 CloudStack 用户选择 Xen,因为它与 CloudStack 紧密集成。

  • Eucalyptus:这是一种与 AWS 兼容的私有云软件,供组织使用以减少其公共云成本并恢复对安全性和性能的控制。它支持 Xen 和 KVM 作为计算资源提供者。

在讨论 OpenStack 时,除了我们在本章中迄今讨论的技术细节之外,还有其他重要的问题需要考虑。当今 IT 中最重要的概念之一实际上是能够运行一个包括各种类型解决方案(如虚拟化解决方案)的环境(纯虚拟化的环境,或云环境),并使用一种能够同时与不同解决方案一起工作的管理层。让我们以 OpenStack 为例。如果你浏览 OpenStack 文档,你很快就会意识到 OpenStack 支持 10 多种不同的虚拟化解决方案,包括以下内容:

  • KVM

  • Xen(通过 libvirt)

  • LXC(Linux 容器)

  • Microsoft Hyper-V

  • VMware ESXi

  • Citrix XenServer

  • 用户模式 Linux(UML)

  • PowerVM(IBM Power 5-9 平台)

  • Virtuozzo(超融合解决方案,可以使用虚拟机、存储和容器)

  • z/VM(IBM Z 和 IBM LinuxONE 服务器的虚拟化解决方案)

这就引出了可能跨越不同 CPU 架构、不同的 hypervisors 和其他技术(如 hypervisors)的多云环境,所有这些都在同一个管理工具集下。这只是您可以使用 OpenStack 做的一件事。我们将在本书的后面回到 OpenStack 这个主题,具体来说是在第十二章使用 OpenStack 扩展 KVM

总结

在本章中,我们介绍了虚拟化及其不同类型的基础知识。牢记虚拟化在当今大规模 IT 世界中的重要性是有益的,因为了解这些概念如何联系在一起形成更大的画面——大型虚拟化环境和云环境是很重要的。基于云的技术将在后面更详细地介绍——把我们目前提到的内容当作一个开端;正餐还在后头。但下一章属于我们书中的主角——KVM hypervisor 及其相关实用程序。

问题

  1. 存在哪些类型的 hypervisors?

  2. 什么是容器?

  3. 什么是基于容器的虚拟化?

  4. 什么是 OpenStack?

进一步阅读

有关本章内容的更多信息,请参考以下链接:

第二章:KVM 作为虚拟化解决方案

在本章中,我们将讨论虚拟化作为一个概念以及通过 libvirt、Quick Emulator(QEMU)和 KVM 的实现。实际上,如果我们想解释虚拟化是如何工作的,以及为什么 KVM 虚拟化是 21 世纪 IT 的一个基本部分,我们必须从多核 CPU 和虚拟化的技术背景开始解释;而这是不可能做到的,如果不深入研究 CPU 和操作系统的理论,这样我们才能了解到我们真正想要的东西——虚拟化监视器是什么,以及虚拟化实际上是如何工作的。

在本章中,我们将涵盖以下主题:

  • 虚拟化作为一个概念

  • libvirt、QEMU 和 KVM 的内部工作

  • 所有这些如何相互通信以提供虚拟化

虚拟化作为一个概念

虚拟化是一种将硬件与软件解耦的计算方法。它提供了更好、更高效和更具程序性的资源分配和共享方法,用于运行操作系统和应用程序的虚拟机。

如果我们将过去的传统物理计算与虚拟化进行比较,我们可以说通过虚拟化,我们有可能在同一台硬件设备(同一台物理服务器)上运行多个客户操作系统(多个虚拟服务器)。如果我们使用类型 1 虚拟机监视器(在第一章《理解 Linux 虚拟化》中有解释),这意味着虚拟机监视器将负责让虚拟服务器访问物理硬件。这是因为有多个虚拟服务器使用与同一台物理服务器上的其他虚拟服务器相同的硬件。这通常由某种调度算法支持,该算法在虚拟机监视器中以编程方式实现,以便我们可以从同一台物理服务器中获得更高的效率。

虚拟化与物理环境

让我们试着将这两种方法可视化——物理和虚拟。在物理服务器中,我们直接在服务器硬件上安装操作系统,并在操作系统上运行应用程序。下图显示了这种方法的工作原理:

图 2.1 – 物理服务器

图 2.1 – 物理服务器

在虚拟化的世界中,我们运行一个虚拟机监视器(如 KVM),以及在该虚拟机监视器上运行的虚拟机。在这些虚拟机内部,我们运行相同的操作系统和应用程序,就像在物理服务器上一样。虚拟化的方法如下图所示:

图 2.2 – 虚拟机监视器和两个虚拟机

图 2.2 – 虚拟机监视器和两个虚拟机

仍然存在各种情况,需要使用物理方法。例如,全球范围内仍然有成千上万的应用程序在物理服务器上运行,因为这些服务器无法虚拟化。它们无法虚拟化的原因各不相同。例如,最常见的原因实际上是最简单的原因——也许这些应用程序正在运行不受虚拟化软件供应商支持的操作系统上。这意味着您无法虚拟化该操作系统/应用程序组合,因为该操作系统不支持某些虚拟化硬件,最常见的是网络或存储适配器。相同的一般思想也适用于云——将事物移动到云中并不总是最好的想法,我们将在本书后面描述。

为什么虚拟化如此重要?

今天我们运行的许多应用程序都不会很好地扩展(增加更多的 CPU、内存或其他资源)-它们只是没有以这种方式编程,或者不能被严重并行化。这意味着如果一个应用程序不能充分利用其所拥有的所有资源,服务器将会有很多“空闲空间”-这一次,我们不是在谈论磁盘的空闲空间;我们实际上是在指“计算”空闲空间,即 CPU 和内存级别的空闲空间。这意味着我们没有充分利用我们为其付费的服务器的能力-我们的意图是让它完全使用,而不是部分使用。

效率和编程方法的重要性还有其他原因。事实是,在 2003 年至 2005 年这段时间,当一切都是关于 CPU 频率的炫耀权利(等于 CPU 速度)时,英特尔和 AMD 在单核 CPU 的概念发展方面遇到了瓶颈。他们无法在 CPU 上塞入更多的附加元素(无论是用于执行还是缓存),或者提高单核的速度,而不严重损害 CPU 的电流供应方式。这意味着,最终,这种方法会损害 CPU 和运行它的整个系统的可靠性。如果您想了解更多信息,我们建议您搜索有关英特尔 NetBurst 架构 CPU(例如 Prescott 核心)和它们的年轻兄弟奔腾 D(Smithfield 核心)的文章,后者基本上是将两个 Prescott 核心粘合在一起,以便最终结果是双核 CPU。一个非常非常热的双核 CPU。

在那之前的几代中,英特尔和 AMD 尝试并测试了其他技术,例如“让系统拥有多个执行单元”的原则。例如,我们有英特尔奔腾 Pro 双插槽系统和 AMD Opteron 双插槽和四插槽系统。当我们开始讨论虚拟化的一些非常重要的方面时,我们将在本书的后面回到这些内容(例如,非统一内存访问(NUMA))。

因此,无论从哪个角度来看,2005 年 PC CPU 开始获得多个核心(AMD 是第一个推出服务器多核 CPU 的厂商,而英特尔是第一个推出桌面多核 CPU 的厂商)是唯一合理的前进方式。这些核心更小,更高效(耗电更少),通常是更好的长期方法。当然,这意味着如果微软和甲骨文等公司想要使用他们的应用程序并获得多核服务器的好处,操作系统和应用程序必须进行大量重写。

总之,对于基于 PC 的服务器来说,从 CPU 的角度来看,转向多核 CPU 是开始朝着我们今天所熟悉和喜爱的虚拟化概念努力的一个合适的时刻。

与这些发展并行的是,CPU 还有其他增加-例如,可以处理特定类型操作的额外 CPU 寄存器。很多人听说过 MMX、SSE、SSE2、SSE3、SSE4.x、AVX、AVX2、AES 等指令集。这些今天也都非常重要,因为它们给了我们将某些指令类型“卸载”到特定 CPU 寄存器的可能性。这意味着这些指令不必在 CPU 上作为一般的串行设备运行,执行这些任务更慢。相反,这些指令可以发送到专门用于这些指令的 CPU 寄存器。可以将其视为在 CPU 芯片上拥有单独的小加速器,可以运行软件堆栈的某些部分而不会占用通用 CPU 管道。其中之一是英特尔的虚拟机扩展(VMX),或者 AMD 虚拟化(AMD-V),它们都使我们能够为其各自的平台提供全面的、基于硬件的虚拟化支持。

虚拟化的硬件要求

在 PC 上引入基于软件的虚拟化后,硬件和软件方面都取得了很大的发展。最终结果——正如我们在前一章中提到的——是 CPU 具有了更多的功能和性能。这导致了对硬件辅助虚拟化的大力推动,这在理论上看起来是更快速和更先进的方式。举个例子,在 2003 年至 2006 年期间有很多 CPU 不支持硬件辅助虚拟化,比如英特尔奔腾 4、奔腾 D,以及 AMD Athlon、Turion、Duron 等。直到 2006 年,英特尔和 AMD 才在其各自的 CPU 上更广泛地提供硬件辅助虚拟化作为一项功能。此外,64 位 CPU 也需要一些时间,而在 32 位架构上几乎没有兴趣运行硬件辅助虚拟化。这主要原因是您无法分配超过 4GB 的内存,这严重限制了虚拟化作为概念的范围。

牢记所有这些,这些是我们今天必须遵守的要求,以便我们可以运行具有完全硬件辅助虚拟化支持的现代虚拟化监控程序:

  • 二级地址转换,快速虚拟化索引,扩展页表(SLAT/RVI/EPT)支持:这是一个虚拟化监控程序使用的 CPU 技术,以便它可以拥有虚拟到物理内存地址的映射。虚拟机在虚拟内存空间中运行,可以分散在物理内存的各个位置,因此通过使用 SLAT/EPT 等额外的映射(通过额外的 TLB 实现),可以减少内存访问的延迟。如果没有这样的技术,我们将不得不访问计算机内存的物理地址,这将是混乱、不安全和延迟敏感的。为了避免混淆,EPT 是英特尔 CPU 中 SLAT 技术的名称(AMD 使用 RVI 术语,而英特尔使用 EPT 术语)。

  • 英特尔 VT 或 AMD-V 支持:如果英特尔 CPU 具有 VT(或 AMD CPU 具有 AMD-V),这意味着它支持硬件虚拟化扩展和完全虚拟化。

  • 长模式支持,这意味着 CPU 支持 64 位。没有 64 位架构,虚拟化基本上是无用的,因为您只能为虚拟机提供 4GB 的内存(这是 32 位架构的限制)。通过使用 64 位架构,我们可以分配更多的内存(取决于我们使用的 CPU),这意味着更多的机会为虚拟机提供内存,否则在 21 世纪的 IT 空间中整个虚拟化概念将毫无意义。

  • 具有输入/输出内存管理单元(IOMMU)虚拟化的可能性(例如 AMD-Vi、英特尔 VT-d 和 ARM 上的第 2 阶段表),这意味着我们允许虚拟机直接访问外围硬件(显卡、存储控制器、网络设备等)。此功能必须在 CPU 和主板芯片组/固件方面都启用。

  • 进行单根输入输出虚拟化(SR/IOV)的可能性,这使我们能够直接将 PCI Express 设备(例如以太网端口)转发到多个虚拟机。SR-IOV 的关键方面是其通过称为虚拟功能(VFs)的功能,能够将一个物理设备与多个虚拟机共享。此功能需要硬件和驱动程序支持。

  • PCI passthrough 的可能性,意味着我们可以将连接到服务器主板的 PCI Express 连接卡(例如,显卡)呈现给虚拟机,就好像该卡是通过称为“物理功能”(PFs)的功能直接连接到虚拟机一样。这意味着绕过连接通常会经过的各种 Hypervisor 级别。

  • 可信平台模块(TPM)支持,通常作为额外的主板芯片实现。使用 TPM 在安全方面有很多优势,因为它可以用于提供加密支持(即创建、保存和保护加密密钥的使用)。在 Linux 世界中,围绕 KVM 虚拟化使用 TPM 引起了相当大的轰动,这导致英特尔在 2018 年夏天开源了 TPM2 堆栈。

在讨论 SR-IOV 和 PCI passthrough 时,请确保注意核心功能,称为 PF 和 VF。这两个关键词将更容易记住设备是如何(直接或通过 Hypervisor)转发到各自的虚拟机的位置(在物理或虚拟级别)和方式。这些功能对企业空间非常重要,也适用于一些特定场景。举个例子,如果没有这些功能,就无法使用工作站级虚拟机来运行 AutoCAD 和类似的应用程序。这是因为 CPU 上的集成显卡速度太慢了。这时你就需要在服务器上添加 GPU,这样你就可以使用 Hypervisor 将整个 GPU 或其部分转发到一个虚拟机或多个虚拟机。

在系统内存方面,也有各种要考虑的主题。AMD 在 Athlon 64 中开始将内存控制器集成到 CPU 中,这是在英特尔之前的几年(英特尔首次在 2008 年推出的 Nehalem CPU 核心中实现了这一点)。将内存控制器集成到 CPU 中意味着当 CPU 访问内存进行内存 I/O 操作时,系统的延迟更低。在此之前,内存控制器集成到了所谓的 NorthBridge 芯片中,这是系统主板上的一个独立芯片,负责所有快速总线和内存。但这意味着额外的延迟,特别是当您尝试将这一原则扩展到多插槽、多核 CPU 时。此外,随着 Athlon 64 在 Socket 939 上的推出,AMD 转向了双通道内存架构,这在桌面和服务器市场上现在是一个熟悉的主题。三通道和四通道内存控制器已成为服务器的事实标准。一些最新的英特尔至强 CPU 支持六通道内存控制器,AMD EPYC CPU 也支持八通道内存控制器。这对整体内存带宽和延迟有着巨大的影响,反过来又对物理和虚拟服务器上内存敏感应用程序的速度有着巨大的影响。

为什么这很重要?通道越多,延迟越低,CPU 到内存的带宽就越大。这对今天 IT 空间中许多工作负载(例如数据库)非常有吸引力。

虚拟化的软件要求

现在我们已经涵盖了虚拟化的基本硬件方面,让我们转向虚拟化的软件方面。为了做到这一点,我们必须涵盖计算机科学中的一些行话。话虽如此,让我们从一个叫做保护环的东西开始。在计算机科学中,存在着各种分层的保护域/特权环。这些是保护数据或故障的机制,基于在访问计算机系统资源时强制执行的安全性。这些保护域有助于计算机系统的安全。通过将这些保护环想象成指令区域,我们可以通过以下图表来表示它们:

图 2.3 – 保护环(来源:https://en.wikipedia.org/wiki/Protection_ring)

图 2.3 – 保护环(来源:en.wikipedia.org/wiki/Protection_ring

如前图所示,保护环从最特权到最不特权的顺序编号。环 0 是最特权的级别,直接与物理硬件交互,比如 CPU 和内存。这些特权环保护了资源,比如内存、I/O 端口和 CPU 指令。环 1 和环 2 大多数情况下是未使用的。大多数通用系统只使用两个环,即使它们运行的硬件提供了更多的 CPU 模式。两个主要的 CPU 模式是内核模式和用户模式,这也与进程执行的方式有关。您可以在此链接中了解更多信息:access.redhat.com/sites/default/files/attachments/processstates_20120831.pdf 从操作系统的角度来看,环 0 被称为内核模式/监管模式,环 3 是用户模式。正如您可能已经猜到的那样,应用程序在环 3 中运行。

像 Linux 和 Windows 这样的操作系统使用监管/内核和用户模式。这种模式几乎无法在没有调用内核或没有内核帮助的情况下对外部世界做任何事情,因为它对内存、CPU 和 I/O 端口的访问受到限制。内核可以在特权模式下运行,这意味着它可以在环 0 上运行。为了执行专门的功能,用户模式代码(在环 3 中运行的所有应用程序)必须对监管模式甚至内核空间执行系统调用,操作系统的受信任代码将执行所需的任务并将执行返回到用户空间。简而言之,在正常环境中,操作系统在环 0 中运行。它需要最高的特权级别来进行资源管理并提供对硬件的访问。以下图表解释了这一点:

图 2.4 – 系统调用到监管模式

图 2.4 – 系统调用到监管模式

环 0 以上的环在处理器模式下运行未受保护的指令。虚拟机监视器(VMM)需要访问主机的内存、CPU 和 I/O 设备。由于只有在环 0 中运行的代码被允许执行这些操作,它需要在最特权的环,即环 0 中运行,并且必须放置在内核旁边。没有特定的硬件虚拟化支持,虚拟机监视器或 VMM 在环 0 中运行;这基本上阻止了虚拟机的操作系统在环 0 中运行。因此,虚拟机的操作系统必须驻留在环 1 中。安装在虚拟机中的操作系统也希望访问所有资源,因为它不知道虚拟化层;为了实现这一点,它必须在环 0 中运行,类似于 VMM。由于一次只能运行一个内核在环 0 中,客户操作系统必须在另一个权限较低的环中运行,或者必须修改为在用户模式下运行。

这导致了引入了一些虚拟化方法,称为全虚拟化和半虚拟化,我们之前提到过。现在,让我们尝试以更加技术化的方式来解释它们。

全虚拟化

在全虚拟化中,特权指令被模拟以克服客户操作系统在 ring 1 中运行和 VMM 在 ring 0 中运行所产生的限制。全虚拟化是在第一代 x86 VMM 中实现的。它依赖于诸如二进制翻译之类的技术来陷阱和虚拟化某些敏感和不可虚拟化的指令的执行。也就是说,在二进制翻译中,一些系统调用被解释并动态重写。以下图表描述了客户操作系统如何通过 ring 1 访问主机计算机硬件以获取特权指令,以及如何在不涉及 ring 1 的情况下执行非特权指令:

图 2.5 – 二进制翻译

图 2.5 – 二进制翻译

采用这种方法,关键指令被发现(在运行时静态或动态地)并在 VMM 中被替换为陷阱,这些陷阱将在软件中被模拟。与在本地虚拟化架构上运行的虚拟机相比,二进制翻译可能会产生较大的性能开销。这可以从以下图表中看出:

图 2.6 – 全虚拟化

图 2.6 – 全虚拟化

然而,正如前面的图表所示,当我们使用全虚拟化时,我们可以使用未经修改的客户操作系统。这意味着我们不必修改客户内核以使其在 VMM 上运行。当客户内核执行特权操作时,VMM 提供 CPU 仿真来处理和修改受保护的 CPU 操作。然而,正如我们之前提到的,与另一种虚拟化模式——称为半虚拟化相比,这会导致性能开销。

半虚拟化

在半虚拟化中,客户操作系统需要被修改以允许这些指令访问 ring 0。换句话说,操作系统需要被修改以在 VMM/虚拟机监控程序和客户之间通过后端(超级调用)路径进行通信:

图 2.7 – 半虚拟化

图 2.7 – 半虚拟化

半虚拟化(en.wikipedia.org/wiki/Paravirtualization)是一种技术,其中虚拟机监控程序提供一个 API,而客户虚拟机的操作系统调用该 API,这需要对主机操作系统进行修改。特权指令调用与 VMM 提供的 API 函数进行交换。在这种情况下,修改后的客户操作系统可以在 ring 0 中运行。

正如您所看到的,根据这种技术,客户内核被修改为在 VMM 上运行。换句话说,客户内核知道自己已被虚拟化。应该在 ring 0 中运行的特权指令/操作已被称为超级调用的调用所取代,这些调用与 VMM 进行通信。这些超级调用调用 VMM,以便它代表客户内核执行任务。由于客户内核可以通过超级调用直接与 VMM 通信,因此与全虚拟化相比,这种技术具有更高的性能。然而,这需要一个专门的客户内核,它知道半虚拟化并具有所需的软件支持。

半虚拟化和全虚拟化的概念曾经是一种常见的虚拟化方式,但并不是最佳的、可管理的方式。这就是硬件辅助虚拟化发挥作用的地方,我们将在下一节中描述。

硬件辅助虚拟化

英特尔和 AMD 意识到全虚拟化和半虚拟化是 x86 架构上虚拟化的主要挑战(由于本书的范围限于 x86 架构,我们将主要讨论这里的架构的演变),由于性能开销和设计和维护解决方案的复杂性。英特尔和 AMD 分别创建了 x86 架构的新处理器扩展,称为 Intel VT-x 和 AMD-V。在 Itanium 架构上,硬件辅助虚拟化被称为 VT-i。硬件辅助虚拟化是一种平台虚拟化方法,旨在有效利用硬件能力进行全虚拟化。各种供应商将这项技术称为不同的名称,包括加速虚拟化、硬件虚拟机和本机虚拟化。

为了更好地支持虚拟化,英特尔和 AMD 分别引入了虚拟化技术VT)和安全虚拟机SVM),作为 IA-32 指令集的扩展。这些扩展允许 VMM/超级监视程序运行期望在内核模式下运行的客户操作系统,在较低特权级别的环境中。硬件辅助虚拟化不仅提出了新的指令,还引入了一个新的特权访问级别,称为环 -1,超级监视程序/VMM 可以在其中运行。因此,客户虚拟机可以在环 0 中运行。有了硬件辅助虚拟化,操作系统可以直接访问资源,而无需任何仿真或操作系统修改。超级监视程序或 VMM 现在可以在新引入的特权级别环 -1 中运行,客户操作系统在环 0 中运行。此外,硬件辅助虚拟化使 VMM/超级监视程序放松,需要执行的工作量较少,从而减少了性能开销。可以用以下图表描述直接在环 -1 中运行的能力:

图 2.8 – 硬件辅助虚拟化

图 2.8 – 硬件辅助虚拟化

简单来说,这种虚拟化感知硬件为我们提供了构建 VMM 的支持,并确保了客户操作系统的隔离。这有助于我们实现更好的性能,并避免设计虚拟化解决方案的复杂性。现代虚拟化技术利用这一特性来提供虚拟化。一个例子是 KVM,我们将在本书中详细讨论。

现在我们已经涵盖了虚拟化的硬件和软件方面,让我们看看所有这些如何适用于 KVM 作为一种虚拟化技术。

libvirt、QEMU 和 KVM 的内部工作

libvirt、QEMU 和 KVM 的交互是本书涵盖的完整虚拟化功能的关键。它们是 Linux 虚拟化拼图中最重要的部分,每个都有自己的作用。让我们描述一下它们的作用以及它们如何相互作用。

libvirt

在使用 KVM 时,您最有可能首先接触到其主要的virsh。请记住,您可以通过 libvirt 管理远程超级监视程序,因此您不仅限于本地超级监视程序。这就是为什么 virt-manager 有一个额外的参数叫做--connect。libvirt 也是各种其他 KVM 管理工具的一部分,比如 oVirt(www.ovirt.org),我们将在下一章中讨论。

libvirt 库的目标是提供一个通用和稳定的层,用于管理在 hypervisor 上运行的虚拟机。简而言之,作为一个管理层,它负责提供执行管理任务的 API,如虚拟机的提供、创建、修改、监视、控制、迁移等。在 Linux 中,您会注意到一些进程是守护进程。libvirt 进程也是守护进程,称为libvirtd。与任何其他守护进程一样,libvirtd在请求时为其客户端提供服务。让我们试着理解当一个 libvirt 客户端,如virsh或 virt-manager,从libvirtd请求服务时到底发生了什么。根据客户端传递的连接 URI(在下一节中讨论),libvirtd打开到 hypervisor 的连接。这就是客户端的virsh或 virt-manager 要求libvirtd开始与 hypervisor 通信的方式。在本书的范围内,我们的目标是研究 KVM 虚拟化技术。因此,最好将其视为 QEMU/KVM hypervisor,而不是讨论来自libvirtd的其他 hypervisor 通信。当您看到 QEMU/KVM 作为底层 hypervisor 名称而不是 QEMU 或 KVM 时,您可能会有点困惑。但不用担心-一切都会在适当的时候变得清晰。QEMU 和 KVM 之间的连接将在接下来的章节中讨论。现在,只需知道有一个 hypervisor 同时使用 QEMU 和 KVM 技术。

通过 virsh 连接到远程系统

一个远程连接的virsh二进制的简单命令行示例如下:

virsh --connect qemu+ssh://root@remoteserver.yourdomain.com/system list ––all

现在让我们来看看源代码。我们可以从 libvirt Git 存储库中获取 libvirt 源代码:

[root@kvmsource]# yum -y install git-core
[root@kvmsource]# git clone git://libvirt.org/libvirt.git

一旦克隆了 repo,您可以在 repo 中看到以下文件层次结构:

图 2.9 – 通过 Git 下载的 QEMU 源内容

图 2.9 – 通过 Git 下载的 QEMU 源内容

libvirt 代码基于 C 编程语言;然而,libvirt 在不同语言中有语言绑定,如C#JavaOCamlPerlPHPPythonRuby等。有关这些绑定的更多详细信息,请参考libvirt.org/bindings.html。源代码中的主要(和少数)目录是docsdaemonsrc等。libvirt 项目有很好的文档,并且文档可以在源代码存储库和libvirt.org上找到。

libvirt 使用基于驱动程序的架构,这使得 libvirt 能够与各种外部 hypervisors 进行通信。这意味着 libvirt 有内部驱动程序,用于与其他 hypervisors 和解决方案进行接口,如 LXC、Xen、QEMU、VirtualBox、Microsoft Hyper-V、bhyve(BSD hypervisor)、IBM PowerVM、OpenVZ(开放的基于容器的解决方案)等,如下图所示:

图 2.10 – 基于驱动程序的架构

图 2.10 – 基于驱动程序的架构

通过virsh命令连接到各种虚拟化解决方案可以让我们更多地使用virsh命令。这在混合环境中可能非常有用,比如如果您从同一系统连接到 KVM 和 XEN hypervisors。

与前面的图一样,当客户端在初始化库时传递virsh --connect QEMU://xxxx/system时,这个公共 API 在后台使用内部驱动程序。是的,在 libvirt 中有不同类别的驱动程序实现。例如,有hypervisorinterfacenetworknodeDevicenwfiltersecretstorage等。请参考 libvirt 源代码中的driver.h了解与不同驱动程序相关的驱动程序数据结构和其他函数。

以以下示例为例:

struct _virConnectDriver {
    virHypervisorDriverPtr hypervisorDriver;
    virInterfaceDriverPtr interfaceDriver;
    virNetworkDriverPtr networkDriver;
    virNodeDeviceDriverPtr nodeDeviceDriver;
    virNWFilterDriverPtr nwfilterDriver;
    virSecretDriverPtr secretDriver;
    virStorageDriverPtr storageDriver;
     };

struct字段是不言自明的,传达了每个字段成员代表的驱动类型。正如你可能已经猜到的那样,重要的或主要的驱动之一是 hypervisor 驱动,它是 libvirt 支持的不同 hypervisor 的驱动实现。这些驱动被归类为README和 libvirt 源代码):

  • bhyve: BSD hypervisor

  • esx/: 使用 vSphere API over SOAP 的 VMware ESX 和 GSX 支持

  • hyperv/: 使用 WinRM 的 Microsoft Hyper-V 支持

  • lxc/: Linux 本地容器

  • openvz/: 使用 CLI 工具的 OpenVZ 容器

  • phyp/: 使用 SSH 上的 CLI 工具的 IBM Power Hypervisor

  • qemu/: 使用 QEMU CLI/monitor 的 QEMU/KVM

  • remote/: 通用 libvirt 本机 RPC 客户端

  • test/: 用于测试的模拟驱动

  • uml/: 用户模式 Linux

  • vbox/: 使用本机 API 的 VirtualBox

  • vmware/: 使用vmrun工具的 VMware Workstation 和 Player

  • xen/: 使用超级调用、XenD SEXPR 和 XenStore 的 Xen

  • xenapi: 使用libxenserver的 Xen

之前我们提到了还有次级驱动程序。并非所有,但一些次级驱动程序(见下文)被几个 hypervisor 共享。目前,这些次级驱动程序被 LXC、OpenVZ、QEMU、UML 和 Xen 驱动程序使用。ESX、Hyper-V、Power Hypervisor、Remote、Test 和 VirtualBox 驱动程序都直接实现了次级驱动程序。

次级驱动程序的示例包括以下内容:

  • cpu/: CPU 特性管理

  • interface/: 主机网络接口管理

  • network/: 虚拟 NAT 网络

  • nwfilter/: 网络流量过滤规则

  • node_device/: 主机设备枚举

  • secret/: 密钥管理

  • security/: 强制访问控制驱动

  • storage/: 存储管理驱动

libvirt 在常规管理操作中扮演着重要角色,比如创建和管理虚拟机(客户域)。还需要使用其他次级驱动程序来执行这些操作,比如接口设置、防火墙规则、存储管理和 API 的一般配置。以下内容来自libvirt.org/api.html

在设备上,应用程序获取了一个 virConnectPtr 连接到 hypervisor,然后可以使用它来管理 hypervisor 的可用域和相关的虚拟化资源,比如存储和网络。所有这些都作为一流对象暴露,并连接到 hypervisor 连接(以及可用的节点或集群)。

以下图显示了 API 导出的五个主要对象及它们之间的连接:

图 2.11 – 导出的 API 对象及其通信

图 2.11 – 导出的 API 对象及其通信

让我们详细介绍一下 libvirt 代码中可用的主要对象。libvirt 内的大多数函数都使用这些对象进行操作:

  • virConnectPtr: 正如我们之前讨论的,libvirt 必须连接到一个 hypervisor 并执行操作。连接到 hypervisor 被表示为这个对象。这个对象是 libvirt API 中的核心对象之一。

  • virDomainPtr: 在 libvirt 代码中,虚拟机或客户系统通常被称为域。virDomainPtr代表一个活动/已定义的域/虚拟机对象。

  • virStorageVolPtr: 有不同的存储卷,暴露给域/客户系统。virStorageVolPtr通常代表其中一个存储卷。

  • virStoragePoolPtr: 导出的存储卷是存储池的一部分。这个对象代表存储池中的一个存储卷。

  • virNetworkPtr: 在 libvirt 中,我们可以定义不同的网络。一个单一的虚拟网络(活动/已定义状态)由virNetworkPtr对象表示。

现在你应该对 libvirt 实现的内部结构有一些了解;这可以进一步扩展:

图 2.12 – libvirt 源代码

图 2.12 – libvirt 源代码

我们感兴趣的是 QEMU/KVM。因此,让我们进一步探讨一下。在 libvirt 源代码存储库的src目录中,有一个用于 QEMU hypervisor 驱动程序实现代码的目录。请注意一些源文件,比如qemu_driver.c,它包含了用于管理 QEMU 客户端的核心驱动程序方法。

请参阅以下示例:

static virDrvOpenStatus qemuConnectOpen(virConnectPtr conn,
                                    virConnectAuthPtr auth ATTRIBUTE_UNUSED,
                                    unsigned int flags)

libvirt 使用不同的驱动程序代码来探测底层的 hypervisor/模拟器。在本书的背景下,libvirt 负责发现 QEMU/KVM 存在的组件是 QEMU 驱动程序代码。该驱动程序探测qemu-kvm二进制文件和/dev/kvm设备节点,以确认 KVM 完全虚拟化的硬件加速客户端是否可用。如果这些不可用,那么通过qemuqemu-system-x86_64qemu-system-mipsqemu-system-microblaze等二进制文件的存在来验证 QEMU 模拟器(无 KVM)的可能性。

验证可以在qemu_capabilities.c中看到:

from  (qemu_capabilities.c)
static int virQEMUCapsInitGuest ( ..,  .. ,  virArch hostarch,  virArch guestarch)
{
...
binary = virQEMUCapsFindBinaryForArch (hostarch, guestarch);
...
native_kvm = (hostarch == guestarch);
x86_32on64_kvm = (hostarch == VIR_ARCH_X86_64 &&  guestarch == VIR_ARCH_I686);
...
if (native_kvm || x86_32on64_kvm || arm_32on64_kvm || ppc64_kvm) {
    const char *kvmbins[] = {
        "/usr/libexec/qemu-kvm", /* RHEL */
        "qemu-kvm", /* Fedora */
        "kvm", /* Debian/Ubuntu */    …};
...
kvmbin = virFindFileInPath(kvmbins[i]); 
...
virQEMUCapsInitGuestFromBinary (caps, binary, qemubinCaps, kvmbin, kvmbinCaps,guestarch);                 
...
}

然后,KVM 启用如下代码片段所示:

int virQEMUCapsInitGuestFromBinary(..., *binary, qemubinCaps, *kvmbin, kvmbinCaps, guestarch)
{
……...
  if (virFileExists("/dev/kvm") && (virQEMUCapsGet(qemubinCaps, QEMU_CAPS_KVM) ||
      virQEMUCapsGet(qemubinCaps, QEMU_CAPS_ENABLE_KVM) ||     kvmbin))
      haskvm = true;

基本上,libvirt 的 QEMU 驱动程序正在寻找不同发行版和不同路径中的不同二进制文件 - 例如,在 RHEL/Fedora 中的qemu-kvm。此外,它根据主机和客户端的架构组合找到合适的 QEMU 二进制文件。如果找到了 QEMU 二进制文件和 KVM,那么 KVM 将完全虚拟化,并且硬件加速的客户端将可用。形成整个 QEMU-KVM 进程的命令行参数也是 libvirt 的责任。最后,在形成整个命令行参数和输入后,libvirt 调用exec()来创建一个 QEMU-KVM 进程。

util/vircommand.c
static int virExec(virCommandPtr cmd) {
…...
  if (cmd->env)
    execve(binary, cmd->args, cmd->env);
  else
    execv(binary, cmd->args);

在 KVM 领域,有一个误解,即 libvirt 直接使用 KVM 内核模块暴露的设备文件(/dev/kvm),并通过 KVM 的不同ioctl()函数调用来指示虚拟化。这确实是一个误解!正如前面提到的,libvirt 生成 QEMU-KVM 进程,而 QEMU 与 KVM 内核模块进行通信。简而言之,QEMU 通过不同的ioctl()向 KVM 进行通信,以便访问由 KVM 内核模块暴露的/dev/kvm设备文件。要创建一个虚拟机(例如virsh create),libvirt 所做的就是生成一个 QEMU 进程,然后 QEMU 创建虚拟机。请注意,libvirtd通过libvirtd为每个虚拟机启动一个单独的 QEMU-KVM 进程。虚拟机的属性(CPU 数量、内存大小、I/O 设备配置等)在/etc/libvirt/qemu目录中的单独的 XML 文件中定义。这些 XML 文件包含 QEMU-KVM 进程启动运行虚拟机所需的所有必要设置。libvirt 客户端通过libvirtd正在监听的AF_UNIX socket /var/run/libvirt/libvirt-sock发出请求。

我们列表上的下一个主题是 QEMU - 它是什么,它是如何工作的,以及它如何与 KVM 交互。

QEMU

QEMU 是由 FFmpeg 的创始人 Fabrice Bellard 编写的。它是一款免费软件,主要根据 GNU 的通用公共许可证GPL)许可。QEMU 是一款通用的开源机器模拟器和虚拟化软件。当用作机器模拟器时,QEMU 可以在不同的机器上(例如您自己的 PC)运行为一台机器(如 ARM 板)制作的操作系统和程序。

通过动态翻译,它实现了非常好的性能(参见www.qemu.org/)。让我重新表述前面的段落,并给出更具体的解释。QEMU 实际上是一个托管的 hypervisor/VMM,执行硬件虚拟化。你感到困惑吗?如果是这样,不要担心。当你通过本章的最后,特别是当你通过每个相关的组件并将这里使用的整个路径相关联起来执行虚拟化时,你会有一个更清晰的认识。QEMU 可以充当模拟器或虚拟化器。

QEMU 作为一个模拟器

在上一章中,我们讨论了二进制翻译。当 QEMU 作为模拟器运行时,它能够在不同的机器类型上运行为另一种机器类型制作的操作系统/程序。这是如何可能的?它只是使用了二进制翻译方法。在这种模式下,QEMU 通过动态二进制翻译技术模拟 CPU,并提供一组设备模型。因此,它能够运行具有不同架构的不同未修改的客户操作系统。这里需要二进制翻译,因为客户代码必须在主机 CPU 上执行。执行这项工作的二进制翻译器称为Tiny Code GeneratorTCG);它是一个即时JIT)编译器。它将为给定处理器编写的二进制代码转换为另一种形式的二进制代码(例如 ARM 在 X86 中),如下图所示(TCG 信息来自en.wikipedia.org/wiki/QEMU#Tiny_Code_Generator):

图 2.13 - QEMU 中的 TCG

图 2.13 - QEMU 中的 TCG

通过使用这种方法,QEMU 可以牺牲一点执行速度以获得更广泛的兼容性。要牢记的是,如今大多数环境都是基于不同的操作系统,这似乎是一个明智的折衷方案。

QEMU 作为虚拟化器

这是 QEMU 在主机 CPU 上直接执行客户代码,从而实现本机性能的模式。例如,在 Xen/KVM hypervisors 下工作时,QEMU 可以以这种模式运行。如果 KVM 是底层 hypervisor,QEMU 可以虚拟化嵌入式客户,如 Power PC、S390、x86 等。简而言之,QEMU 能够在不使用 KVM 的情况下使用上述的二进制翻译方法运行。与 KVM 启用的硬件加速虚拟化相比,这种执行速度会较慢。在任何模式下,无论是作为虚拟化器还是模拟器,QEMU 不仅仅是模拟处理器;它还模拟不同的外围设备,如磁盘、网络、VGA、PCI、串行和并行端口、USB 等。除了这种 I/O 设备模拟外,在与 KVM 一起工作时,QEMU-KVM 还创建和初始化虚拟机。如下图所示,它还为每个客户的虚拟 CPUvCPU)初始化不同的 POSIX 线程。它还提供了一个框架,用于在 QEMU-KVM 的用户模式地址空间内模拟虚拟机的物理地址空间:

图 2.14 - QEMU 作为虚拟化器

图 2.14 - QEMU 作为虚拟化器

为了在物理 CPU 中执行客户代码,QEMU 使用了 POSIX 线程。也就是说,客户 vCPU 在主机内核中作为 POSIX 线程执行。这本身带来了很多优势,因为在高层视图中,这些只是主机内核的一些进程。从另一个角度来看,QEMU 提供了 KVM hypervisor 的用户空间部分。QEMU 通过 KVM 内核模块运行客户代码。在与 KVM 一起工作时,QEMU 还进行 I/O 模拟、I/O 设备设置、实时迁移等。

QEMU 打开了由 KVM 内核模块暴露的设备文件(/dev/kvm),并对其执行ioctl()函数调用。请参考下一节关于 KVM 的内容,了解更多关于这些ioctl()函数调用的信息。总之,KVM 利用 QEMU 成为一个完整的 hypervisor。KVM 是处理器提供的硬件虚拟化扩展(VMX 或 SVM)的加速器或启用器,使它们与 CPU 架构紧密耦合。间接地,这表明虚拟系统也必须使用相同的架构来利用硬件虚拟化扩展/功能。一旦启用,它肯定会比其他技术(如二进制翻译)提供更好的性能。

我们的下一步是检查 QEMU 如何融入整个 KVM 故事中。

QEMU-KVM 内部

在我们开始研究 QEMU 内部之前,让我们克隆 QEMU Git 存储库:

# git clone git://git.qemu-project.org/qemu.git

一旦克隆完成,您可以在存储库内看到文件的层次结构,如下面的屏幕截图所示:

图 2.15 – QEMU 源代码

图 2.15 – QEMU 源代码

一些重要的数据结构和ioctl()函数调用构成了 QEMU 用户空间和 KVM 内核空间。一些重要的数据结构是KVMStateCPU{X86}StateMachineState等。在我们进一步探索内部之前,我想指出,详细介绍它们超出了本书的范围;但是,我会给出足够的指针来理解发生了什么,并提供更多的参考资料以供进一步解释。

数据结构

在这一部分,我们将讨论 QEMU 的一些重要数据结构。KVMState结构包含了 QEMU 中虚拟机表示的重要文件描述符。例如,它包含了虚拟机文件描述符,如下面的代码所示:

struct KVMState      ( kvm-all.c ) 
{           …..
  int fd;
  int vmfd;
  int coalesced_mmio;
    struct kvm_coalesced_mmio_ring *coalesced_mmio_ring; ….}

QEMU-KVM 维护着CPUX86State结构的列表,每个 vCPU 都有一个结构。通用寄存器的内容(以及 RSP 和 RIP)是CPUX86State的一部分:

struct CPUState {
…..
  int nr_cores;
  int nr_threads;
  …
  int kvm_fd;
           ….
  struct KVMState *kvm_state;
  struct kvm_run *kvm_run
}

此外,CPUX86State还查看标准寄存器以进行异常和中断处理:

typedef struct CPUX86State ( target/i386/cpu.h )
 {
  /* standard registers */
  target_ulong regs[CPU_NB_REGS];
….
  uint64_t system_time_msr;
  uint64_t wall_clock_msr;
…….
  /* exception/interrupt handling */
  int error_code;
  int exception_is_int;
…...
}

存在各种ioctl()函数调用:kvm_ioctl()kvm_vm_ioctl()kvm_vcpu_ioctl()kvm_device_ioctl()等。有关函数定义,请访问 QEMU 源代码存储库中的KVM-all.c。这些ioctl()函数调用基本上映射到系统 KVM、虚拟机和 vCPU 级别。这些ioctl()函数调用类似于由 KVM 分类的ioctl()函数调用。当我们深入研究 KVM 内部时,我们将讨论这一点。要访问由 KVM 内核模块公开的这些ioctl()函数调用,QEMU-KVM 必须打开/dev/kvm,并将结果文件描述符存储在KVMState->fd中:

  • kvm_ioctl():这些ioctl()函数调用主要在KVMState->fd参数上执行,其中KVMState->fd携带通过打开/dev/kvm获得的文件描述符,就像下面的例子一样:
kvm_ioctl(s, KVM_CHECK_EXTENSION, extension);
kvm_ioctl(s, KVM_CREATE_VM, type);
  • kvm_vm_ioctl():这些ioctl()函数调用主要在KVMState->vmfd参数上执行,就像下面的例子一样:
kvm_vm_ioctl(s, KVM_CREATE_VCPU, (void *)vcpu_id);
kvm_vm_ioctl(s, KVM_SET_USER_MEMORY_REGION, &mem);
  • kvm_vcpu_ioctl():这些ioctl()函数调用主要在CPUState->kvm_fd参数上执行,这是 KVM 的 vCPU 文件描述符,就像下面的例子一样:
kvm_vcpu_ioctl(cpu, KVM_RUN, 0);
  • kvm_device_ioctl():这些ioctl()函数调用主要在设备fd参数上执行,就像下面的例子一样:
kvm_device_ioctl(dev_fd, KVM_HAS_DEVICE_ATTR, &attribute) ? 0 : 1;

在考虑 QEMU KVM 通信时,kvm-all.c是一个重要的源文件之一。

现在,让我们继续看看 QEMU 在 KVM 虚拟化环境中如何创建和初始化虚拟机和 vCPU。

kvm_init()是打开 KVM 设备文件的函数,就像下面的代码所示,它还填充了KVMStatefd [1]vmfd [2]

static int kvm_init(MachineState *ms)
{ 
…..
KVMState *s;
      s = KVM_STATE(ms->accelerator);
    …
    s->vmfd = -1;
    s->fd = qemu_open("/dev/kvm", O_RDWR);   ----> [1]
    ..
     do {
          ret = kvm_ioctl(s, KVM_CREATE_VM, type); --->[2]
        } while (ret == -EINTR);
     s->vmfd = ret;
….
      ret = kvm_arch_init(ms, s);   ---> ( target-i386/kvm.c: ) 
.....
  }

如您在前面的代码中所看到的,带有KVM_CREATE_VM参数的ioctl()函数调用将返回vmfd。一旦 QEMU 有了fdvmfd,还必须填充一个文件描述符,即kvm_fdvcpu fd。让我们看看 QEMU 是如何填充这个的:

main() ->
              -> cpu_init(cpu_model);      [#define cpu_init(cpu_model) CPU(cpu_x86_init(cpu_model)) ]
                  ->cpu_x86_create()
         ->qemu_init_vcpu
                      ->qemu_kvm_start_vcpu()
               ->qemu_thread_create
        ->qemu_kvm_cpu_thread_fn()
          -> kvm_init_vcpu(CPUState *cpu)
int kvm_init_vcpu(CPUState *cpu)
{
  KVMState *s = kvm_state;
  ...
            ret = kvm_vm_ioctl(s, KVM_CREATE_VCPU, (void *)kvm_arch_vcpu_id(cpu));
  cpu->kvm_fd = ret;   --->   [vCPU fd]
  ..
  mmap_size = kvm_ioctl(s, KVM_GET_VCPU_MMAP_SIZE, 0);
cpu->kvm_run = mmap(NULL, mmap_size, PROT_READ | PROT_WRITE, MAP_SHARED,  cpu->kvm_fd, 0);  [3]
...
  ret = kvm_arch_init_vcpu(cpu);   [target-i386/kvm.c]
              …..
}

一些内存页面在 QEMU-KVM 进程和 KVM 内核模块之间共享。您可以在kvm_init_vcpu()函数中看到这样的映射。也要了解,在执行返回前述fds的这些ioctl()函数调用期间,Linux 内核会分配文件结构和相关的匿名节点。我们将在讨论 KVM 时稍后讨论内核部分。

我们已经看到 vCPU 是由 QEMU-KVM 创建的posix线程。为了运行客户代码,这些 vCPU 线程执行带有KVM_RUN参数的ioctl()函数调用,就像下面的代码所示:

int kvm_cpu_exec(CPUState *cpu) {
   struct kvm_run *run = cpu->kvm_run;
  ..
  run_ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0);
           ...
}

相同的函数kvm_cpu_exec()还定义了当控制从 KVM 带有VM exit返回到 QEMU-KVM 用户空间时需要采取的操作。尽管我们将在后面讨论 KVM 和 QEMU 如何相互通信以代表客户执行操作,但我在这里想提一下。KVM 是由供应商如 Intel 和 AMD 提供的硬件扩展的实现者,这些硬件扩展如 SVM 和 VMX。KVM 使用这些扩展在主机 CPU 上直接执行客户代码。然而,如果有一个事件 - 例如,在操作的一部分,客户内核代码访问由 QEMU 仿真的硬件设备寄存器 - 那么 KVM 必须退出返回到 QEMU 并传递控制。然后,QEMU 可以仿真操作的结果。有不同的退出原因,如下面的代码所示:

  switch (run->exit_reason) {
          case KVM_EXIT_IO:
            DPRINTF("handle_io\n");
             case KVM_EXIT_MMIO:
            DPRINTF("handle_mmio\n");
   case KVM_EXIT_IRQ_WINDOW_OPEN:
            DPRINTF("irq_window_open\n");
      case KVM_EXIT_SHUTDOWN:
            DPRINTF("shutdown\n");
     case KVM_EXIT_UNKNOWN:
    ...
   	  case KVM_EXIT_INTERNAL_ERROR:
    …
   	case KVM_EXIT_SYSTEM_EVENT:
            switch (run->system_event.type) {
              case KVM_SYSTEM_EVENT_SHUTDOWN:
        case KVM_SYSTEM_EVENT_RESET:
case KVM_SYSTEM_EVENT_CRASH:

现在我们了解了 QEMU-KVM 的内部情况,让我们讨论一下 QEMU 中的线程模型。

QEMU 中的线程模型

QEMU-KVM 是一个多线程、事件驱动(带有一个大锁)的应用程序。重要的线程如下:

  • 主线程

  • 虚拟磁盘 I/O 后端的工作线程

  • 每个 vCPU 一个线程

对于每个虚拟机,主机系统中都有一个运行中的 QEMU 进程。如果客户系统关闭,这个进程将被销毁/退出。除了 vCPU 线程,还有专用的 I/O 线程运行 select(2)事件循环来处理 I/O,比如网络数据包和磁盘 I/O 完成。I/O 线程也是由 QEMU 生成的。简而言之,情况将是这样的:

图 2.16 - KVM 客户端

图 2.16 - KVM 客户端

在我们进一步讨论之前,总是有一个关于客户系统的物理内存的问题:它在哪里?这是交易:客户 RAM 分配在 QEMU 进程的虚拟地址空间内,如前图所示。也就是说,客户的物理 RAM 位于 QEMU 进程地址空间内。

重要说明

有关线程的更多细节可以从blog.vmsplice.net/2011/03/qemu-internals-overall-architecutre-and-html?m=1的线程模型中获取。

事件循环线程也称为iothread。事件循环用于定时器、文件描述符监视等。main_loop_wait()是 QEMU 主事件循环线程。这个主事件循环线程负责主循环服务,包括文件描述符回调、底部回调和定时器(在qemu-timer.h中定义)。底部回调类似于立即执行的定时器,但开销较低,并且调度它们是无等待、线程安全和信号安全的。

在我们离开 QEMU 代码库之前,我想指出设备代码主要有两个部分。例如,目录块包含块设备代码的主机端,hw/block/包含设备仿真的代码。

KVM

有一个名为kvm.ko的通用内核模块,还有硬件内核模块,比如kvm-intel.ko(基于 Intel 的系统)和kvm-amd.ko(基于 AMD 的系统)。因此,KVM 将加载kvm-intel.ko(如果存在vmx标志)或kvm-amd.ko(如果存在svm标志)模块。这将使 Linux 内核成为一个 hypervisor,从而实现虚拟化。

KVM 向应用程序公开了一个名为/dev/kvm的设备文件,以便它们可以利用提供的ioctl()函数调用系统调用。QEMU 利用这个设备文件与 KVM 通信,并创建、初始化和管理虚拟机的内核模式上下文。

之前,我们提到 QEMU-KVM 用户空间将虚拟机的物理地址空间包含在 QEMU/KVM 的用户模式地址空间中,其中包括内存映射 I/O。KVM 帮助我们实现了这一点。有更多的事情可以通过 KVM 实现。以下是一些例子:

  • 对某些 I/O 设备的仿真;例如,通过 MMIO 对每个 CPU 的本地 APIC 和系统范围的 IOAPIC 进行仿真。

  • 某些特权指令的仿真(对系统寄存器 CR0、CR3 和 CR4 的读写)。

  • 通过VMENTRY执行客户代码并在VMEXIT处处理拦截事件。

  • 将事件(如虚拟中断和页错误)注入到虚拟机的执行流程中等。这也是借助 KVM 实现的。

KVM 不是一个完整的 hypervisor;然而,借助 QEMU 和仿真器(一个稍微修改过的用于 I/O 设备仿真和 BIOS 的 QEMU),它可以成为一个。KVM 需要硬件虚拟化能力的处理器才能运行。利用这些能力,KVM 将标准的 Linux 内核转变为一个 hypervisor。当 KVM 运行虚拟机时,每个虚拟机都是一个正常的 Linux 进程,显然可以由主机内核调度到 CPU 上运行,就像主机内核中存在的任何其他进程一样。在第一章《理解 Linux 虚拟化》中,我们讨论了不同的 CPU 执行模式。你可能还记得,主要有用户模式和内核/监管模式。KVM 是 Linux 内核中的一项虚拟化功能,它允许诸如 QEMU 之类的程序在主机 CPU 上直接执行客户代码。只有当目标架构得到主机 CPU 的支持时,才有可能实现这一点。

然而,KVM 引入了一个称为客户模式的模式。简而言之,客户模式允许我们执行客户系统代码。它可以运行客户用户或内核代码。借助虚拟化感知硬件的支持,KVM 虚拟化了进程状态、内存管理等。

从 CPU 的角度看虚拟化

借助其硬件虚拟化能力,处理器通过虚拟机控制结构(VMCS)和虚拟机控制块(VMCB)管理主机和客户操作系统的处理器状态,并代表虚拟化的操作系统管理 I/O 和中断。也就是说,引入这种类型的硬件后,诸如 CPU 指令拦截、寄存器读/写支持、内存管理支持(扩展页表(EPT)和嵌套分页表(NPT))、中断处理支持(APICv)、IOMMU 等任务都出现了。KVM 使用标准的 Linux 调度程序、内存管理和其他服务。简而言之,KVM 的作用是帮助用户空间程序利用硬件虚拟化能力。在这里,你可以把 QEMU 看作是一个用户空间程序,因为它被很好地集成到了不同的用例中。当我说硬件加速虚拟化时,我主要指的是英特尔 VT-X 和 AMD-Vs SVM。引入虚拟化技术处理器带来了一个额外的指令集,称为 VMX。

使用 Intel 的 VT-X,VMM 在 VMX 根操作模式下运行,而客户(未经修改的操作系统)在 VMX 非根操作模式下运行。这个 VMX 为 CPU 带来了额外的虚拟化特定指令,比如VMPTRLDVMPTRSTVMCLEARVMREADVMWRITEVMCALLVMLAUNCHVMRESUMEVMXOFFVMXONVMXON可以被VMXOFF禁用。为了执行客户代码,我们必须使用VMLAUNCH/VMRESUME指令并离开VMEXIT。但是等等,离开什么?这是从非根操作到根操作的过渡。显然,当我们进行这种过渡时,需要保存一些信息,以便以后可以获取。英特尔提供了一个结构来促进这种过渡,称为 VMCS;它处理了大部分虚拟化管理功能。例如,在VMEXIT的情况下,退出原因将被记录在这个结构内。那么,我们如何从这个结构中读取或写入?VMREADVMWRITE指令用于读取或写入相应的字段。

之前,我们讨论了 SLAT/EPT/AMD-Vi。没有 EPT,hypervisor 必须退出虚拟机执行地址转换,这会降低性能。正如我们在英特尔基于虚拟化的处理器的操作模式中所注意到的,AMD 的 SVM 也有一些操作模式,即主机模式和客户模式。显然,当处于客户模式时,某些指令可能会引起VMEXIT异常,这些异常会以特定于进入客户模式的方式进行处理。这里应该有一个等效的 VMCS 结构,它被称为 VMCB;正如前面讨论的,它包含了VMEXIT的原因。AMD 添加了八个新的指令操作码来支持 SVM。例如,VMRUN指令启动客户操作系统的操作,VMLOAD指令从 VMCB 加载处理器状态,VMSAVE指令将处理器状态保存到 VMCB。这就是为什么 AMD 引入了嵌套分页,这与英特尔的 EPT 类似。

当我们讨论硬件虚拟化扩展时,我们提到了 VMCS 和 VMCB。当我们考虑硬件加速虚拟化时,这些是重要的数据结构。这些控制块特别有助于VMEXIT场景。并非所有操作都可以允许给客户;与此同时,如果 hypervisor 代表客户执行所有操作也是困难的。虚拟机控制结构,如 VMCS 或 VMCB,控制了这种行为。一些操作允许给客户,例如更改阴影控制寄存器中的一些位,但其他操作则不允许。这显然提供了对客户允许和不允许执行的操作的精细控制。VMCS 控制结构还提供了对中断传递和异常的控制。之前我们说过VMEXIT的退出原因记录在 VMCS 中;它也包含一些关于它的数据。例如,如果写访问控制寄存器导致退出,有关源寄存器和目的寄存器的信息就记录在那里。

请注意 VMCS 或 VMCB 存储客户配置的具体信息,例如机器控制位和处理器寄存器设置。我建议您从源代码中检查结构定义。这些数据结构也被 hypervisor 用来定义在客户执行时监视的事件。这些事件可以被拦截。请注意这些结构位于主机内存中。在使用VMEXIT时,客户状态被保存在 VMCS 中。正如前面提到的,VMREAD指令从 VMCS 中读取指定字段,而VMWRITE指令将指定字段写入 VMCS。还要注意每个 vCPU 都有一个 VMCS 或 VMCB。这些控制结构是主机内存的一部分。vCPU 状态记录在这些控制结构中。

KVM API

如前所述,有三种主要类型的ioctl()函数调用。内核文档中提到了以下内容(您可以在www.kernel.org/doc/Documentation/virtual/kvm/api.txt中查看):

KVM API 由三组 ioctl 组成。KVM API 是一组用于控制虚拟机各个方面的 ioctl。这些 ioctl 属于三个类别:

  • 系统 ioctl:这些查询和设置影响整个 KVM 子系统的全局属性。此外,系统 ioctl 用于创建虚拟机。

  • 设备 ioctl:用于设备控制,从创建 VM 的同一上下文中执行。

  • VM ioctl:这些查询和设置影响整个虚拟机的属性,例如内存布局。此外,VM ioctl 用于创建虚拟 CPU(vCPU)。它从创建 VM 的同一进程(地址空间)运行 VM ioctl。

  • vCPU ioctl:这些查询和设置控制单个虚拟 CPU 操作的属性。它们从创建 vCPU 的同一线程运行 vCPU ioctl。

要了解 KVM 公开的ioctl()函数调用以及属于特定fd组的ioctl()函数调用的更多信息,请参考KVM.h

看下面的例子:

/*  ioctls for /dev/kvm fds: */
#define KVM_GET_API_VERSION     _IO(KVMIO,   0x00)
#define KVM_CREATE_VM           _IO(KVMIO,   0x01) /* returns a VM fd */
…..
/*  ioctls for VM fds */
#define KVM_SET_MEMORY_REGION   _IOW(KVMIO,  0x40, struct kvm_memory_region)
#define KVM_CREATE_VCPU         _IO(KVMIO,   0x41)
…
/* ioctls for vcpu fds  */
#define KVM_RUN                   _IO(KVMIO,   0x80)
#define KVM_GET_REGS            _IOR(KVMIO,  0x81, struct kvm_regs)
#define KVM_SET_REGS            _IOW(KVMIO,  0x82, struct kvm_regs)

现在让我们讨论匿名 inode 和文件结构。

匿名 inode 和文件结构

之前,当我们讨论 QEMU 时,我们说 Linux 内核分配文件结构并设置它的f_ops和匿名 inode。让我们看一下kvm_main.c文件:

static struct file_operations kvm_chardev_ops = {
      .unlocked_ioctl = kvm_dev_ioctl,
      .llseek         = noop_llseek,
      KVM_COMPAT(kvm_dev_ioctl),
};
 kvm_dev_ioctl () 
    switch (ioctl) {
          case KVM_GET_API_VERSION:
              if (arg)
                    	goto out;
              r = KVM_API_VERSION;
              break;
          case KVM_CREATE_VM:
              r = kvm_dev_ioctl_create_vm(arg);
              break;
          case KVM_CHECK_EXTENSION:
              r = kvm_vm_ioctl_check_extension_generic(NULL, arg);
              break;
          case KVM_GET_VCPU_MMAP_SIZE:
  .    …..
}

kvm_chardev_fops一样,还有kvm_vm_fopskvm_vcpu_fops

static struct file_operations kvm_vm_fops = {
        .release        = kvm_vm_release,
        .unlocked_ioctl = kvm_vm_ioctl,
…..
        .llseek         = noop_llseek,
};
static struct file_operations kvm_vcpu_fops = {
      .release        = kvm_vcpu_release,
      .unlocked_ioctl = kvm_vcpu_ioctl,
….
      .mmap           = kvm_vcpu_mmap,
      .llseek         = noop_llseek,
};

inode 分配可能如下所示:

      anon_inode_getfd(name, &kvm_vcpu_fops, vcpu, O_RDWR | O_CLOEXEC);

现在让我们看一下数据结构。

数据结构

从 KVM 内核模块的角度来看,每个虚拟机都由一个kvm结构表示:

include/linux/kvm_host.h : 
struct kvm {
  ...
      struct mm_struct *mm; /* userspace tied to this vm */
           ...
      struct kvm_vcpu *vcpus[KVM_MAX_VCPUS];
          ....
      struct kvm_io_bus __rcu *buses[KVM_NR_BUSES];
….
      struct kvm_coalesced_mmio_ring *coalesced_mmio_ring;
  …..
}

正如你在前面的代码中看到的,kvm结构包含一个指向kvm_vcpu结构的指针数组,这些结构是 QEMU-KVM 用户空间中CPUX86State结构的对应物。kvm_vcpu结构包括一个通用部分和一个 x86 架构特定部分,其中包括寄存器内容:

struct kvm_vcpu {
  ...
      struct kvm *kvm;
      int cpu;
…..
      int vcpu_id;
  …..
   	struct kvm_run *run;
  …...
      struct kvm_vcpu_arch arch;
  …
}

kvm_vcpu结构的 x86 架构特定部分包含字段,可以在虚拟机退出后保存客户端寄存器状态,并且可以在虚拟机进入前加载客户端寄存器状态:

arch/x86/include/asm/kvm_host.h
struct kvm_vcpu_arch {
..
      unsigned long regs[NR_VCPU_REGS];
      unsigned long cr0;
      unsigned long cr0_guest_owned_bits;
      …..
   	struct kvm_lapic *apic;  /* kernel irqchip context */
   	..
struct kvm_mmu mmu;
..
struct kvm_pio_request pio;
void *pio_data;
..
      /* emulate context */
  struct x86_emulate_ctxt emulate_ctxt;
  ...
      int (*complete_userspace_io)(struct kvm_vcpu *vcpu);
  ….
}

正如你在前面的代码中看到的,kvm_vcpu有一个相关的kvm_run结构,用于 QEMU 用户空间和 KVM 内核模块之间的通信(通过pio_data),正如之前提到的。例如,在VMEXIT的情况下,为了满足虚拟硬件访问的仿真,KVM 必须返回到 QEMU 用户空间进程;KVM 将信息存储在kvm_run结构中供 QEMU 获取:

/include/uapi/linux/kvm.h:
/* for KVM_RUN, returned by mmap(vcpu_fd, offset=0) */
struct kvm_run {
        /* in */
...
        /* out */
...
        /* in (pre_kvm_run), out (post_kvm_run) */
...
      union {
              /* KVM_EXIT_UNKNOWN */
...
              /* KVM_EXIT_FAIL_ENTRY */
...
              /* KVM_EXIT_EXCEPTION */
...
              /* KVM_EXIT_IO */
struct {
#define KVM_EXIT_IO_IN  0
#define KVM_EXIT_IO_OUT 1
...
              } io;
...
}

kvm_run结构是一个重要的数据结构;正如你在前面的代码中看到的,union包含许多退出原因,比如KVM_EXIT_FAIL_ENTRYKVM_EXIT_IO等。

当我们讨论硬件虚拟化扩展时,我们提到了 VMCS 和 VMCB。当我们考虑硬件加速虚拟化时,这些是重要的数据结构。这些控制块在VMEXIT场景中特别有帮助。并非所有操作都可以允许给客户端;同时,如果 hypervisor 代表客户端做所有事情也很困难。虚拟机控制结构,比如 VMCS 或 VMCB,控制了行为。一些操作是允许给客户端的,比如改变阴影控制寄存器中的一些位,但其他的不行。这清楚地提供了对客户端允许和不允许做什么的精细控制。VMCS 控制结构还提供了对中断传递和异常的控制。之前,我们说VMEXIT的退出原因记录在 VMCS 中;它也包含了一些关于它的数据。例如,如果对控制寄存器的写访问导致了退出,关于源寄存器和目的寄存器的信息就记录在那里。

在我们深入讨论 vCPU 执行流程之前,让我们先看一些重要的数据结构。

Intel 特定的实现在vmx.c中,AMD 特定的实现在svm.c中,取决于我们拥有的硬件。正如你所看到的,下面的kvm_vcpuvcpu_vmx的一部分。kvm_vcpu结构主要分为通用部分和特定架构部分。通用部分包含所有支持的架构的共同数据,而特定架构部分 - 例如,x86 架构特定(客户端保存的通用寄存器)部分包含特定于特定架构的数据。正如之前讨论的,kvm_vCPUskvm_runpio_data与用户空间共享。

vcpu_vmxvcpu_svm结构(下面提到)有一个kvm_vcpu结构,其中包括一个 x86 架构特定部分(struct 'kvm_vcpu_arch')和一个通用部分,并且相应地指向vmcsvmcb结构。让我们先检查 Intel(vmx)结构:

vcpu_vmx structure
struct vcpu_vmx {
      struct kvm_vcpu     *vcpu;
        ...
      struct loaded_vmcs  vmcs01;
     struct loaded_vmcs   *loaded_vmcs;
    ….
    }

同样,让我们接下来检查 AMD(svm)结构:

vcpu_svm structure
struct vcpu_svm {
        struct kvm_vcpu *vcpu;
        …
struct vmcb *vmcb;
….
    }

vcpu_vmxvcpu_svm结构是通过以下代码路径分配的:

kvm_arch_vcpu_create()
   	   ->kvm_x86_ops->vcpu_create
                 ->vcpu_create()  [.vcpu_create = svm_create_vcpu, .vcpu_create = vmx_create_vcpu,]

请注意,VMCS 或 VMCB 存储客户端配置的具体信息,例如机器控制位和处理器寄存器设置。我建议您从源代码中检查结构定义。这些数据结构也被 hypervisor 用于定义在客户端执行时要监视的事件。这些事件可以被拦截,这些结构位于主机内存中。在VMEXIT时,客户状态被保存在 VMCS 中。如前所述,VMREAD指令从 VMCS 中读取字段,而VMWRITE指令将字段写入其中。还要注意,每个 vCPU 都有一个 VMCS 或 VMCB。这些控制结构是主机内存的一部分。vCPU 状态记录在这些控制结构中。

vCPU 的执行流程

最后,我们进入了 vCPU 执行流程,这有助于我们整合一切并了解底层发生了什么。

希望您没有忘记 QEMU 为客户端的 vCPU 创建了一个 POSIX 线程和ioctl(),它负责运行 CPU 并具有KVM_RUN arg (#define KVM_RUN _IO(KVMIO, 0x80))。vCPU 线程执行ioctl(.., KVM_RUN, ...)来运行客户端代码。由于这些是 POSIX 线程,Linux 内核可以像系统中的任何其他进程/线程一样调度这些线程。

让我们看看它是如何工作的:

Qemu-kvm User Space:
kvm_init_vcpu ()
    kvm_arch_init_vcpu()
       qemu_init_vcpu()
          qemu_kvm_start_vcpu()
             qemu_kvm_cpu_thread_fn()
    while (1) {
        if (cpu_can_run(cpu)) {
                r = kvm_cpu_exec(cpu);
                      }
        }
kvm_cpu_exec (CPUState *cpu)
    ->       run_ret = kvm_vcpu_ioctl(cpu, KVM_RUN, 0);

根据底层架构和硬件,KVM 内核模块初始化了不同的结构之一,其中之一是vmx_x86_ops/svm_x86_ops(由kvm-intelkvm-amd模块拥有)。它定义了在 vCPU 处于上下文时需要执行的不同操作。KVM 利用kvm_x86_ops向量来根据加载到硬件的 KVM 模块(kvm-intelkvm-amd)指向这些向量中的任何一个。run指针定义了在客户端 vCPU 运行时需要执行的函数,而handle_exit定义了在VMEXIT时需要执行的操作。让我们检查 Intel(vmx)结构:

static struct kvm_x86_ops vmx_x86_ops = {
    ...
      .vcpu_create = vmx_create_vcpu,
      .run = vmx_vcpu_run,
      .handle_exit = vmx_handle_exit,
…
}

现在,让我们看看 AMD(svm)结构:

static struct kvm_x86_ops svm_x86_ops = {
      .vcpu_create = svm_create_vcpu,
       .run = svm_vcpu_run,
      .handle_exit = handle_exit,
..
}

run指针分别指向vmx_vcpu_runsvm_vcpu_runsvm_vcpu_runvmx_vcpu_run函数负责保存 KVM 主机寄存器,加载客户端操作系统寄存器和SVM_VMLOAD指令。我们在vcpu run时通过syscall进入内核时,走过了 QEMU KVM 用户空间代码的执行。然后,按照文件操作结构,它调用kvm_vcpu_ioctl();这定义了根据ioctl()函数调用定义的操作:

static long kvm_vcpu_ioctl(struct file *file,
                         unsigned int ioctl, unsigned long arg)  {
      switch (ioctl) {
        case KVM_RUN:
    ….
           kvm_arch_vcpu_ioctl_run(vcpu, vcpu->run);
        ->vcpu_load
            -> vmx_vcpu_load
                 ->vcpu_run(vcpu);
        ->vcpu_enter_guest
                             ->vmx_vcpu_run
                     ….
}

我们将通过vcpu_run()来了解如何到达vmx_vcpu_runsvm_vcpu_run

static int vcpu_run(struct kvm_vcpu *vcpu) {
….
      for (;;) {
              if (kvm_vcpu_running(vcpu)) {
                        r = vcpu_enter_guest(vcpu);
                } else {
                        r = vcpu_block(kvm, vcpu);
              }

一旦进入vcpu_enter_guest(),您可以看到在 KVM 中进入客户模式时发生的一些重要调用:

static int vcpu_enter_guest(struct kvm_vcpu *vcpu) {
...
      kvm_x86_ops.prepare_guest_switch(vcpu);
      vcpu->mode = IN_GUEST_MODE;
      __kvm_guest_enter();
      kvm_x86_ops->run(vcpu);
                             [vmx_vcpu_run or svm_vcpu_run ]
      vcpu->mode = OUTSIDE_GUEST_MODE;
      kvm_guest_exit();
      r = kvm_x86_ops->handle_exit(vcpu);
                             [vmx_handle_exit or handle_exit ]
…
}

您可以从vcpu_enter_guest()函数中看到VMENTRYVMEXIT的高级图像。也就是说,VMENTRY[vmx_vcpu_run 或 svm_vcpu_run])只是一个在 CPU 中执行的客户操作系统;在这个阶段可能会发生不同的拦截事件,导致VMEXIT。如果发生这种情况,任何vmx_handle_exithandle_exit函数调用都将开始查看此退出原因。我们已经在前面的部分讨论了VMEXIT的原因。一旦发生VMEXIT,就会分析退出原因并相应地采取行动。

vmx_handle_exit()是负责处理退出原因的函数:

static int vmx_handle_exit(struct kvm_vcpu *vcpu, , fastpath_t exit_fastpath)
{
….. }
static int (*const kvm_vmx_exit_handlers[])(struct kvm_vcpu *vcpu) = {
      [EXIT_REASON_EXCEPTION_NMI]         = handle_exception,
      [EXIT_REASON_EXTERNAL_INTERRUPT]    = handle_external_interrupt,
      [EXIT_REASON_TRIPLE_FAULT]          = handle_triple_fault,
      [EXIT_REASON_IO_INSTRUCTION]        = handle_io,
      [EXIT_REASON_CR_ACCESS]             = handle_cr,
      [EXIT_REASON_VMCALL]                = handle_vmcall,
      [EXIT_REASON_VMCLEAR]               = handle_vmclear,
      [EXIT_REASON_VMLAUNCH]            	= handle_vmlaunch,
…
}

kvm_vmx_exit_handlers[]是虚拟机退出处理程序的表,由exit reason索引。类似于 Intel,svm代码有handle_exit()

static int handle_exit(struct kvm_vcpu *vcpu, fastpath_t exit_fastpath)
{
      struct vcpu_svm *svm = to_svm(vcpu);
      struct kvm_run *kvm_run = vcpu->run;
      u32 exit_code = svm->vmcb->control.exit_code;
….
      return svm_exit_handlersexit_code;
}

handle_exit()svm_exit_handler数组,如下一节所示。

如果需要,KVM 必须回退到用户空间(QEMU)来执行仿真,因为一些指令必须在 QEMU 模拟的设备上执行。例如,为了模拟 I/O 端口访问,控制权转移到用户空间(QEMU):

kvm-all.c:
static int (*const svm_exit_handlers[])(struct vcpu_svm *svm) = {
      [SVM_EXIT_READ_CR0]                   = cr_interception,
      [SVM_EXIT_READ_CR3]                   = cr_interception,
      [SVM_EXIT_READ_CR4]                   = cr_interception,
….
}
switch (run->exit_reason) {
        case KVM_EXIT_IO:
              DPRINTF("handle_io\n");
                /* Called outside BQL */
              kvm_handle_io(run->io.port, attrs,
                            (uint8_t *)run + run->io.data_offset,
                          run->io.direction,
                      	    run->io.size,
                      	    run->io.count);
              ret = 0;
            break;

本章内容涉及源代码较多。有时,深入挖掘和检查源代码是理解某些工作原理的唯一途径。希望本章成功做到了这一点。

总结

在本章中,我们讨论了 KVM 及其在 Linux 虚拟化中的主要合作伙伴 libvirt 和 QEMU 的内部工作原理。我们讨论了各种类型的虚拟化——二进制翻译、完全虚拟化、半虚拟化和硬件辅助虚拟化。我们查看了一些内核、QEMU 和 libvirt 的源代码,以了解它们之间的相互作用。这使我们具备了必要的技术知识,以便理解本书中将要介绍的主题——从创建虚拟机和虚拟网络到将虚拟化理念扩展到云概念。理解这些概念也将使您更容易理解虚拟化的关键目标——如何正确设计物理和虚拟基础设施,这将逐渐在本书中作为一个概念介绍。现在我们已经了解了虚拟化的基本工作原理,是时候转向更实际的主题了——如何部署 KVM hypervisor、管理工具和 oVirt。我们将在下一章中进行介绍。

问题

  1. 什么是半虚拟化?

  2. 什么是完全虚拟化?

  3. 什么是硬件辅助虚拟化?

  4. libvirt 的主要目标是什么?

  5. KVM 的作用是什么?QEMU 呢?

进一步阅读

请参考以下链接,了解本章涵盖的更多信息:

第二部分:用于虚拟机管理的 libvirt 和 ovirt

在本书的这一部分,您将完全了解如何使用 libvirt 安装、配置和管理 KVM hypervisor。您将获得关于 KVM 基础设施组件的高级知识,如网络、存储和虚拟硬件配置。作为学习过程的一部分,您还将全面了解虚拟机生命周期管理和虚拟机迁移技术,以及虚拟机磁盘管理。在第二部分结束时,您将熟悉 libvirt 命令行管理工具virsh和 GUI 工具virt-manager

本书的这一部分包括以下章节:

  • 第三章,安装 KVM Hypervisor、libvirt 和 ovirt

  • 第四章,Libvirt 网络

  • 第五章,Libvirt 存储

  • 第六章,虚拟显示设备和协议

  • 第七章,虚拟机安装、配置和生命周期管理

  • 第八章,创建和修改虚拟机磁盘、模板和快照

第三章:安装 KVM Hypervisor、libvirt 和 oVirt

本章为您提供了对我们书籍主题的深入了解,即内核虚拟机KVM)及其管理工具 libvirt 和 oVirt。我们还将学习如何使用基本的 CentOS 8 部署从头开始完整安装这些工具。您会发现这是一个非常重要的主题,因为有时您可能没有安装所有必要的实用程序,特别是 oVirt,因为它是整体软件堆栈的一个完全独立的部分,也是 KVM 的免费管理平台。由于 oVirt 有很多组成部分 - 基于 Python 的守护程序和支持实用程序、库和 GUI 前端 - 我们将包括一步一步的指南,以确保您可以轻松安装 oVirt。

在本章中,我们将涵盖以下主题:

  • 熟悉 QEMU 和 libvirt

  • 熟悉 oVirt

  • 安装 QEMU、libvirt 和 oVirt

  • 使用 QEMU 和 libvirt 启动虚拟机

让我们开始吧!

熟悉 QEMU 和 libvirt

第二章KVM 作为虚拟化解决方案中,我们开始讨论 KVM、QEMU 和各种其他实用程序,可以用来管理基于 KVM 的虚拟化平台。作为机器模拟器,QEMU 将被用于在任何支持的平台上创建和运行我们的虚拟机 - 无论是作为模拟器还是虚拟化器。我们将把时间集中在第二种范式上,即使用 QEMU 作为虚拟化器。这意味着我们将能够直接在其下方的硬件 CPU 上执行我们的虚拟机代码,这意味着本地或接近本地的性能和更少的开销。

要牢记整体 KVM 堆栈是作为一个模块构建的,因此 QEMU 也采用了模块化的方法并不足为奇。多年来,这一点一直是 Linux 世界的核心原则,进一步提高了我们使用物理资源的效率。

当我们将 libvirt 作为 QEMU 的管理平台时,我们可以访问一些很酷的新实用程序,比如virsh命令,我们可以用它来进行虚拟机管理、虚拟网络管理等等。我们将在本书的后面讨论一些实用程序(例如 oVirt),它们使用 libvirt 作为标准化的库和实用程序,使其 GUI 魔术成为可能 - 基本上,它们使用 libvirt 作为 API。我们还可以访问其他命令,用于各种目的。例如,我们将使用一个名为virt-host-validate的命令来检查我们的服务器是否与 KVM 兼容。

熟悉 oVirt

请记住,大多数 Linux 系统管理员所做的工作是通过命令行工具、libvirt 和 KVM 完成的。它们为我们提供了一套良好的工具,可以在命令行中完成我们需要的一切,正如我们将在本章的后面部分看到的那样。但同时,我们也将对基于 GUI 的管理有所了解,因为我们稍后将简要讨论虚拟机管理器。

然而,这仍然无法涵盖这样一种情况,即您拥有大量基于 KVM 的主机、数百台虚拟机、数十个相互连接的虚拟网络,以及一整个机架的存储设备,您需要将其集成到您的 KVM 环境中。使用上述实用程序只会在您扩展环境时给您带来痛苦。这主要原因相当简单-我们仍然没有引入任何一种集中式软件包来管理基于 KVM 的环境。当我们说集中式时,我们指的是字面意义上-我们需要一种可以连接到多个虚拟化程序并管理它们所有功能的软件解决方案,包括网络、存储、内存和 CPU,或者我们有时所说的虚拟化的四大支柱。这种软件最好有某种 GUI 界面,我们可以从中集中管理我们所有的 KVM 资源,因为-嗯-我们都是人类。我们中有相当多的人更喜欢图片而不是文本,更喜欢交互而不是仅限于文本管理,尤其是在规模化时。

这就是 oVirt 项目的用武之地。oVirt 是一个用于管理我们的 KVM 环境的开源平台。它是一个基于 GUI 的工具,在后台有很多运行部件-引擎在基于 Java 的 WildFly 服务器上运行(以前被称为 JBoss),前端使用 GWT 工具包等。但它们都是为了实现一件事-让我们能够从一个集中的、基于 Web 的管理控制台管理基于 KVM 的环境。

从管理的角度来看,oVirt 有两个主要的构建模块-引擎(我们可以通过 GUI 界面连接到)和其代理(用于与主机通信)。让我们简要描述它们的功能。

oVirt 引擎是一个集中式服务,可用于执行虚拟化环境中所需的任何操作-管理虚拟机、移动它们、创建镜像、存储管理、虚拟网络管理等。此服务用于管理 oVirt 主机,并且为此,它需要与主机上的某些东西进行通信。这就是 oVirt 代理(vdsm)发挥作用的地方。

oVirt 引擎的一些可用高级功能包括以下内容:

  • 虚拟机的实时迁移

  • 图像管理

  • 虚拟机的导入和导出(OVF 格式)

  • 虚拟到虚拟转换(V2V)

  • 高可用性(在集群中的剩余主机上重新启动虚拟机)

  • 资源监控

显然,我们需要在主机上部署 oVirt 代理和相关实用程序,这些主机将成为我们环境的主要部分,我们将在其中托管一切-虚拟机、模板、虚拟网络等。为此,oVirt 使用了一种特定的基于代理的机制,通过一个名为 vdsm 的代理。这是一个我们将部署到我们的 CentOS 8 主机上的代理,以便我们可以将它们添加到 oVirt 的清单中,进而意味着我们可以通过使用 oVirt 引擎 GUI 来管理它们。Vdsm 是一个基于 Python 的代理,oVirt 引擎使用它可以直接与 KVM 主机通信,然后 vdsm 可以与本地安装的 libvirt 引擎进行通信以执行所有必要的操作。它还用于配置目的,因为主机需要配置为在 oVirt 环境中使用,以配置虚拟网络、存储管理和访问等。此外,vdsm 还具有内存过量管理器(MOM)集成,以便它可以有效地管理我们虚拟化主机上的内存。

以图形方式来看,oVirt 的架构如下所示:

图 3.1- oVirt 架构(来源:http://ovirt.org)

图 3.1- oVirt 架构(来源:http://ovirt.org

我们将在下一章中介绍如何安装 oVirt。如果您曾听说过或使用过一个名为 Red Hat Enterprise Virtualization 的产品,那么它可能看起来非常熟悉。

安装 QEMU、libvirt 和 oVirt

让我们从一些基本信息开始讨论安装 QEMU、libvirt 和 oVirt:

  • 我们将在本书中的所有内容中使用 CentOS 8(除了一些仅支持 CentOS 7 的部分,因为在撰写本书时,CentOS 7 是最后一个受支持的版本)。

  • 我们的默认安装配置文件始终是带 GUI 的服务器,前提是我们将覆盖几乎在本书中要做的所有事情的 GUI 和文本模式实用程序。

  • 我们需要在默认的带 GUI 的服务器安装之上手动安装所有内容,以便我们有一个完整的,一步一步的指南来完成所有操作。

  • 本书中涵盖的所有示例都可以安装在一台具有 16 个物理核心和 64GB 内存的单个物理服务器上。如果您修改一些数字(分配给虚拟机的核心数、分配给某些虚拟机的内存量等),您可以使用一台 6 核笔记本电脑和 16GB 内存来完成这些操作,前提是您不会一直运行所有虚拟机。如果在完成本章后关闭虚拟机,并在下一章中启动必要的虚拟机,那么这样做是可以的。在我们的情况下,我们使用了一台 HP ProLiant DL380p Gen8,这是一台易于找到的二手服务器 - 价格也相当便宜。

在完成了服务器的基本安装后 - 选择安装配置文件、分配网络配置和 root 密码,并添加额外用户(如果需要) - 我们面临着一个无法进行虚拟化的系统,因为它没有运行 KVM 虚拟机所需的所有必要工具。因此,我们要做的第一件事是简单安装必要的模块和基本应用程序,以便检查我们的服务器是否与 KVM 兼容。因此,请以管理员用户身份登录到服务器并发出以下命令:

yum module install virt
dnf install qemu-img qemu-kvm libvirt libvirt-client virt-manager virt-install virt-viewer -y

我们还需要告诉内核我们将使用 IOMMU。这可以通过编辑/etc/default/grub文件来实现,找到GRUB_CMDLINE_LINUX并在该行的末尾添加一条语句:

intel_iommu=on

在添加该行之前不要忘记添加一个空格。下一步是重新启动,所以我们需要执行:

systemctl reboot

通过发出这些命令,我们安装了运行基于 KVM 的虚拟机所需的所有必要库和二进制文件,以及使用 virt-manager(GUI libvirt 管理实用程序)来管理我们的 KVM 虚拟化服务器。

此外,通过添加 IOMMU 配置,我们确保我们的主机看到 IOMMU,并在使用virt-host-validate命令时不会抛出错误

之后,让我们通过发出以下命令来检查我们的主机是否与所有必要的 KVM 要求兼容:

virt-host-validate

此命令经过多次测试,以确定我们的服务器是否兼容。我们应该得到这样的输出:

图 3.2 - virt-host-validate 输出

图 3.2 - virt-host-validate 输出

这表明我们的服务器已准备好用于 KVM。因此,下一步是,既然所有必要的 QEMU/libvirt 实用程序都已安装,我们要进行一些预检查,以查看我们安装的所有内容是否部署正确,并且是否像应该那样工作。我们将运行virsh net-listvirsh list命令来执行此操作,如下面的屏幕截图所示:

图 3.3 - 测试 KVM 虚拟网络并列出可用的虚拟机

图 3.3 - 测试 KVM 虚拟网络并列出可用的虚拟机

通过使用这两个命令,我们检查了我们的虚拟化主机是否有正确配置的默认虚拟网络交换机/桥(关于这一点,我们将在下一章中详细介绍),以及我们是否有任何正在运行的虚拟机。我们有默认的桥接和没有虚拟机,所以一切都是正常的。

在 KVM 中安装第一个虚拟机

现在我们可以开始使用我们的 KVM 虚拟化服务器来运行虚拟机。让我们从在我们的主机上部署一个虚拟机开始。为此,我们将一个 CentOS 8.0 ISO 文件复制到我们的本地文件夹/var/lib/libvirt/images,我们将使用它来创建我们的第一个虚拟机。我们可以通过使用以下命令从命令行执行此操作:

virt-install --virt-type=kvm --name MasteringKVM01 --vcpus 2 --ram 4096 --os-variant=rhel8.0 --cdrom=/var/lib/libvirt/images/ CentOS-8-x86_64-1905-dvd1.iso  --network=default --graphics vnc --disk size=16

这里有一些可能有点令人困惑的参数。让我们从--os-variant参数开始,该参数描述了您想要使用virt-install命令安装的客户操作系统。如果您想获取受支持的客户操作系统列表,请运行以下命令:

osinfo-query os

--network参数与我们的默认虚拟桥有关(我们之前提到过这一点)。我们绝对希望我们的虚拟机能够连接到网络,因此我们选择了这个参数,以确保它在开箱即用时能够连接到网络。

在启动virt-install命令后,我们应该会看到一个 VNC 控制台窗口,以便跟随安装过程。然后我们可以选择使用的语言、键盘、时间和日期,以及安装目的地(点击所选磁盘,然后在左上角按完成)。我们还可以通过转到网络和主机名,点击关闭按钮,选择完成(然后会切换到打开位置),并将我们的虚拟机连接到底层网络桥(默认)来激活网络。之后,我们可以按开始安装,让安装过程完成。在等待过程中,我们可以点击Root 密码为我们的管理用户分配一个 root 密码。

如果所有这些对您来说似乎有点像手工劳动,我们能理解您的痛苦。想象一下不得不部署数十个虚拟机并点击所有这些设置。我们已经不再处于 19 世纪,所以一定有更简单的方法来做这件事。

自动化虚拟机安装

到目前为止,以更自动的方式执行这些操作的最简单和最简单的方法是创建和使用一个称为kickstart文件。kickstart 文件基本上是一个文本配置文件,我们可以使用它来配置服务器的所有部署设置,无论我们是在谈论物理服务器还是虚拟服务器。唯一的注意事项是 kickstart 文件需要预先准备并广泛可用-无论是在网络(web)上还是在本地磁盘上。还有其他支持的选项,但这些是最常用的选项。

为了我们的目的,我们将使用一个在网络上(通过 Web 服务器)可用的 kickstart 文件,但我们将对其进行一些编辑,以使其可用,并将其留在我们的网络上,以便virt-install可以使用它。

当我们安装物理服务器时,作为安装过程的一部分(称为anaconda),一个名为anaconda-ks.cfg的文件被保存在我们的/root目录中。这是一个 kickstart 文件,其中包含了我们的物理服务器的完整部署配置,我们可以以此为基础创建一个新的虚拟机的 kickstart 文件。

在 CentOS 7 中执行这个最简单的方法是部署一个名为system-config-kickstart的实用程序,在 CentOS 8 中不再可用。在access.redhat.com/labs/kickstartconfig/有一个在线替代实用程序称为 Kickstart Generator,但您需要拥有 Red Hat Customer Portal 帐户。因此,如果您没有,您只能使用文本编辑现有的 kickstart 文件。这并不是很困难,但可能需要一些努力。我们需要正确配置的最重要的设置与我们将从中安装虚拟机的位置有关-是在网络上还是从本地目录(就像我们在第一个virt-install示例中所做的那样,使用本地磁盘上的 CentOS ISO)。如果我们将在服务器上本地存储 ISO 文件,则这是一个简单的配置。首先,我们将部署 Apache Web 服务器,以便我们可以在线托管我们的 kickstart 文件(稍后会派上用场)。因此,我们需要以下命令:

dnf install httpd 
systemctl start httpd
systemctl enable httpd
cp /root/anaconda-ks.cfg /var/www/html/ks.cfg
chmod 644 /var/www/html/ks.cfg

在开始部署过程之前,使用 vi 编辑器(或您喜欢的任何其他编辑器)编辑我们的 kickstart 文件(/var/www/html/ks.cfg)中的第一行配置,该配置类似于ignoredisk --only-use=sda,改为ignoredisk --only-use=vda。这是因为虚拟 KVM 机器不使用sd*设备命名,而是使用vd命名。这样任何管理员在连接到服务器后就可以更容易地弄清楚他们是在管理物理服务器还是虚拟服务器。

通过编辑 kickstart 文件并使用这些命令,我们安装并启动了httpd(Apache Web 服务器)。然后,我们永久启动它,以便在每次服务器重启后都启动它。然后,我们将默认的 kickstart 文件(anaconda-ks.cfg)复制到 Apache 的DocumentRoot目录(Apache 提供文件的目录),并更改权限,以便 Apache 在客户端请求时实际读取该文件。在我们的示例中,将使用它的客户端virt-install命令。我们用来说明这个特性的服务器的 IP 地址是10.10.48.1,这是我们将用于 kickstart URL 的地址。请注意,默认的 KVM 桥使用 IP 地址192.168.122.1,您可以使用ip命令轻松检查:

ip addr show virbr0

此外,可能需要更改一些防火墙设置,以便在物理服务器上成功获取 kickstart 文件(接受 HTTP 连接)。因此,让我们尝试一下。在这个和以下的示例中,要特别注意--vcpus参数(虚拟机的虚拟 CPU 核心数),因为您可能需要根据自己的环境进行更改。换句话说,如果您没有 4 个核心,请确保降低核心数量。我们只是以此作为示例:

virt-install --virt-type=kvm --name=MasteringKVM02 --ram=4096 --vcpus=4 --os-variant=rhel8.0 --location=/var/lib/libvirt/images/ CentOS-8-x86_64-1905-dvd1.iso --network=default --graphics vnc --disk size=16 -x "ks=http://10.10.48.1/ks.cfg"

重要提示

请注意我们更改的参数。在这里,我们必须使用--location参数,而不是--cdrom参数,因为我们正在将 kickstart 配置注入到引导过程中(必须以这种方式执行)。

部署过程完成后,我们应该在服务器上有两个名为MasteringKVM01MasteringKVM02的完全功能的虚拟机,准备用于我们未来的演示。第二个虚拟机(MasteringKVM02)的根密码与第一个虚拟机相同,因为我们除了虚拟磁盘选项之外,没有更改 kickstart 文件中的任何内容。因此,在部署后,我们可以使用MasteringKVM01机器的根用户名和密码登录到我们的MasteringKVM02机器。

如果我们想进一步发展,我们可以创建一个带有循环的 shell 脚本,该循环将使用索引自动为虚拟机提供唯一名称。我们可以通过使用for循环及其计数器轻松实现这一点:

#!/bin/bash
for counter in {1..5}
do 
	echo "deploying VM $counter"
virt-install --virt-type=kvm --name=LoopVM$counter --ram=4096 --vcpus=4 --os-variant=rhel8.0 --location=/var/lib/libvirt/images/CentOS-8-x86_64-1905-dvd1.iso --network=default --graphics vnc --disk size=16 -x "ks=http://10.10.48.1/ks.cfg"
done

当我们执行此脚本(不要忘记将其chmod755!)时,我们应该会得到 10 个名为LoopVM1-LoopVM5的虚拟机,所有设置都相同,包括相同的 root 密码。

如果我们使用 GUI 服务器安装,我们可以使用 GUI 实用程序来管理我们的 KVM 服务器。其中一个实用程序称为virtual,点击虚拟机管理器,然后开始使用它。虚拟机管理器的外观如下:

图 3.4 – 虚拟机管理器

图 3.4 – 虚拟机管理器

现在我们已经介绍了基本的命令行实用程序(virshvirt-install),并且有一个非常简单易用的 GUI 应用程序(虚拟机管理器),让我们从这个角度转移一下,思考一下我们对 oVirt 和管理大量主机、虚拟机、网络和存储设备的看法。因此,现在让我们讨论如何安装 oVirt,然后我们将使用它来以更集中的方式管理基于 KVM 的环境。

安装 oVirt

安装 oVirt 有不同的方法。我们可以将其部署为自托管引擎(通过 Cockpit Web 界面或 CLI),也可以通过基于软件包的安装将其部署为独立应用程序。让我们以第二种方式为例-在虚拟机中进行独立安装。我们将安装分为两部分:

  1. 安装 oVirt 引擎进行集中管理

  2. 在我们的基于 CentOS 8 的主机上部署 oVirt 代理

首先,让我们处理 oVirt 引擎部署。部署足够简单,人们通常使用一个虚拟机来实现这一目的。请记住,CentOS 8 不支持 oVirt,在我们的 CentOS 8 虚拟机中,我们需要输入一些命令:

yum install https://resources.ovirt.org/pub/yum-repo/ovirt-release44.rpm
yum -y module enable javapackages-tools pki-deps postgresql:12
yum -y update
yum -y install ovirt-engine

再次强调,这只是安装部分;我们还没有进行任何配置。所以,这是我们的逻辑下一步。我们需要启动一个名为engine-setup的 shell 应用程序,它将询问我们大约 20 个问题。它们相当描述性,引擎设置直接提供了解释,所以这些是我们在测试环境中使用的设置(在您的环境中 FQDN 将不同):

图 3.5 – oVirt 配置设置

图 3.5 – oVirt 配置设置

在输入OK后,引擎设置将开始。最终结果应该看起来像这样:

图 3.6 – oVirt 引擎设置摘要

图 3.6 – oVirt 引擎设置摘要

现在,我们应该能够通过使用 Web 浏览器并将其指向安装摘要中提到的 URL 来登录到我们的 oVirt 引擎。在安装过程中,我们被要求为admin@internal用户提供密码-这是我们将用来管理环境的 oVirt 管理用户。oVirt Web 界面足够简单易用,目前我们只需要登录到管理门户(在尝试登录之前,oVirt 引擎 Web GUI 上直接提供了一个链接)。登录后,我们应该会看到 oVirt GUI:

图 3.7 – oVirt 引擎管理门户

图 3.7 – oVirt 引擎管理门户

屏幕左侧有各种选项卡-仪表板计算网络存储管理-每一个都有特定的用途:

  • 仪表板:默认的着陆页面。它包含最重要的信息,环境健康状态的可视化表示,以及一些基本信息,包括我们正在管理的虚拟数据中心的数量、集群、主机、数据存储域等等。

  • 计算:我们转到此页面以管理主机、虚拟机、模板、池、数据中心和集群。

  • 网络:我们转到此页面以管理我们的虚拟网络和配置文件。

  • 存储:我们可以在此页面上管理存储资源,包括磁盘、卷、域和数据中心。

  • 管理:用于管理用户、配额等。

我们将在第七章中处理更多与 oVirt 相关的操作,虚拟机-安装、配置和生命周期管理,这是关于 oVirt 的全部内容。但目前,让我们保持 oVirt 引擎运行,以便以后再次使用它,并在基于 KVM 的虚拟化环境中进行日常操作。

使用 QEMU 和 libvirt 启动虚拟机

部署完成后,我们可以开始管理我们的虚拟机。我们将以MasteringKVM01MasteringKVM02为例。让我们使用virsh命令和start关键字来启动它们:

图 3.8 - 使用 virsh start 命令

图 3.8 - 使用 virsh start 命令

假设我们从 shell 脚本示例中创建了所有五台虚拟机,并且将它们保持开机状态。我们可以通过发出简单的virsh list命令轻松检查它们的状态:

图 3.9 - 使用 virsh list 命令

图 3.9 - 使用 virsh list 命令

如果我们想要优雅地关闭MasteringKVM01虚拟机,可以使用virsh shutdown命令:

图 3.10 - 使用 virsh shutdown 命令

图 3.10 - 使用 virsh shutdown 命令

如果我们想要强制关闭MasteringKVM02虚拟机,可以使用virsh destroy命令:

图 3.11 - 使用 virsh destroy 命令

图 3.11 - 使用 virsh destroy 命令

如果我们想要完全删除虚拟机(例如MasteringKVM02),通常需要先关闭它(优雅或强制),然后使用virsh undefine命令:

图 3.12 - 使用 virsh destroy 和 undefine 命令

图 3.12 - 使用 virsh destroy 和 undefine 命令

请注意,您实际上可以先执行virsh undefine,然后再执行destroy,最终结果将是相同的。但是,这可能违反了预期行为,即您首先关闭对象,然后再删除它。

我们刚刚学会了如何使用virsh命令来管理虚拟机 - 启动和停止 - 强制和优雅。当我们开始扩展对virsh命令的使用知识时,这将会很有用,在接下来的章节中,我们将学习如何管理 KVM 网络和存储。

我们也可以从 GUI 中完成所有这些操作。您可能还记得,在本章的前面,我们安装了一个名为virt-manager的软件包。实际上,这是一个用于管理 KVM 主机的 GUI 应用程序。让我们使用它来进一步操作我们的虚拟机。这是virt-manager的基本 GUI 界面:

图 3.13 - virt-manager GUI - 我们可以看到已注册的虚拟机并开始管理它们

图 3.13 - virt-manager GUI - 我们可以看到已注册的虚拟机列表并开始管理它们

如果我们想对虚拟机进行常规操作 - 启动、重启、关闭、关闭电源 - 我们只需要右键单击它,并从菜单中选择该选项。要使所有操作可见,首先我们必须启动虚拟机;否则,只有四个操作可用,而可用的七个操作中,MasteringKVM01的列表将变得更大:

图 3.14 - virt-manager 选项 - 在虚拟机上电后,我们现在可以使用更多选项

图 3.14 - virt-manager 选项 - 在虚拟机上电后,我们现在可以使用更多选项

我们将在本书中的各种操作中使用virt-manager,所以请确保您熟悉它。在许多情况下,它将使我们的管理工作变得更加容易。

总结

在本章中,我们为本书剩余章节中要做的几乎所有事情奠定了一些基本的基础和先决条件。我们学会了如何安装 KVM 和 libvirt 堆栈。我们还学会了如何部署 oVirt 作为管理我们的 KVM 主机的 GUI 工具。

接下来的几章将带领我们走向更加技术化的方向,我们将涵盖网络和存储概念。为了做到这一点,我们将不得不退一步,学习或复习我们之前关于网络和存储的知识,因为这些对于虚拟化,特别是云计算来说是非常重要的概念。

问题

  1. 我们如何验证我们的主机是否与 KVM 要求兼容?

  2. oVirt 的默认登陆页面是什么?

  3. 我们可以使用哪个命令从命令行管理虚拟机?

  4. 我们可以使用哪个命令从命令行部署虚拟机?

进一步阅读

请参考以下链接,了解本章涵盖的更多信息:

第四章:Libvirt 网络

了解虚拟网络如何工作对于虚拟化非常重要。很难证明在没有虚拟网络的情况下,我们可以承担与拥有多个虚拟机的虚拟化主机相关的成本。想象一下,在虚拟化网络中有多个虚拟机,并购买网络卡,以便每个虚拟机都可以拥有自己专用的物理网络端口。通过实施虚拟网络,我们也以更可管理的方式整合了网络,无论是从管理还是成本的角度来看。

本章为您提供了对虚拟化网络和基于 Linux 的网络概念的整体概念。我们还将讨论物理和虚拟网络概念,尝试比较它们,并找出它们之间的相似之处和不同之处。本章还涵盖了虚拟交换的概念,用于主机概念和跨主机概念,以及一些更高级的主题。这些主题包括单根输入/输出虚拟化,它允许对某些场景的硬件采用更直接的方法。随着我们开始讨论云覆盖网络,我们将在本书的后面回顾一些网络概念。这是因为基本的网络概念对于大型云环境来说并不够可扩展。

在本章中,我们将涵盖以下主题:

  • 理解物理和虚拟网络

  • 使用 TAP/TUN

  • 实施 Linux 桥接

  • 配置 Open vSwitch

  • 了解和配置 SR-IOV

  • 理解 macvtap

  • 让我们开始吧!

理解物理和虚拟网络

让我们思考一下网络。这是当今大多数系统管理员都相当了解的一个主题。这可能不是我们认为的那么高的水平,但是-如果我们试图找到一个系统管理领域,我们会发现最大的共同知识水平,那就是网络。

那么,问题出在哪里呢?

实际上,没有什么。如果我们真正理解物理网络,那么虚拟网络对我们来说将是小菜一碟。剧透警告:它是一样的。如果我们不理解,它将很快暴露出来,因为没有绕过它的办法。随着环境的发展和通常的增长,问题会越来越大,因为它们变得越大,它们将产生越多的问题,您将花费更多的时间处于调试模式。

话虽如此,如果您对基于 VMware 或 Microsoft 的虚拟网络在技术层面上有很好的掌握,那么这些概念对您来说都是非常相似的。

说到这一点,虚拟网络到底是怎么回事?实际上,这是关于理解事情发生的地方,方式和原因。这是因为从物理上讲,虚拟网络与物理网络完全相同。从逻辑上讲,有一些差异更多地与事物的拓扑有关,而不是原则或工程方面的事物。这通常会让人们有点困惑-有一些奇怪的基于软件的对象,它们与大多数人已经习惯通过我们喜爱的基于 CLI 或 GUI 的实用程序来管理的物理对象做着相同的工作。

首先,让我们介绍虚拟化网络的基本构建块-虚拟交换机。虚拟交换机基本上是一个基于软件的第 2 层交换机,您可以使用它来做两件事:

  • 将您的虚拟机连接到它。

  • 使用其上行将它们连接到物理服务器卡,以便您可以将这些物理网络卡连接到物理交换机。

因此,让我们从虚拟机的角度来看为什么我们需要这些虚拟交换机。正如我们之前提到的,我们使用虚拟交换机将虚拟机连接到它。为什么呢?如果没有一种软件对象坐在我们的物理网络卡和虚拟机之间,我们会有一个大问题 - 我们只能连接我们有物理网络端口的虚拟机到我们的物理网络,这是不可容忍的。首先,这违反了虚拟化的一些基本原则,如效率和整合,其次,这将花费很多。想象一下在您的服务器上有 20 台虚拟机。这意味着,如果没有虚拟交换机,您至少需要 20 个物理网络端口连接到物理网络。此外,您实际上还会在物理交换机上使用 20 个物理端口,这将是一场灾难。

因此,通过在虚拟机和物理网络端口之间引入虚拟交换机,我们同时解决了两个问题 - 我们减少了每台服务器所需的物理网络适配器数量,减少了我们需要用来连接虚拟机到网络的物理交换机端口数量。我们实际上还可以说我们解决了第三个问题 - 效率 - 因为有许多情况下,一个物理网络卡可以处理连接到虚拟交换机的 20 台虚拟机的上行流量。具体来说,我们的环境中有很大一部分并不消耗大量网络流量,对于这些情况,虚拟网络只是非常高效的。

虚拟网络

现在,为了使虚拟交换机能够连接到虚拟机上的某个东西,我们必须有一个对象来连接 - 这个对象被称为虚拟网络接口卡,通常称为 vNIC。每次您配置一个虚拟机与虚拟网络卡,您都赋予它连接到使用物理网络卡作为上行连接到物理交换机的虚拟交换机的能力。

当然,这种方法也存在一些潜在的缺点。例如,如果您有 50 台虚拟机连接到使用相同物理网络卡作为上行的同一个虚拟交换机,而该上行失败(由于网络卡问题、电缆问题、交换机端口问题或交换机问题),您的 50 台虚拟机将无法访问物理网络。我们如何解决这个问题?通过实施更好的设计,并遵循我们在物理网络上也会使用的基本设计原则。具体来说,我们会使用多个物理上行连接到同一个虚拟交换机。

Linux 有很多不同类型的网络接口,大约有 20 种不同类型,其中一些如下:

  • Bridge: 用于(虚拟机)网络的第 2 层接口。

  • Bond: 用于将网络接口组合成单个接口(用于平衡和故障转移原因)成为一个逻辑接口。

  • Team: 与绑定不同,团队合作不会创建一个逻辑接口,但仍然可以进行平衡和故障转移。

  • MACVLAN: 在第二层上在单个物理接口上创建多个 MAC 地址(创建子接口)。

  • IPVLAN: 与 MACVLAN 不同,IPVLAN 使用相同的 MAC 地址并在第 3 层上进行复用。

  • MACVTAP/IPVTAP: 新的驱动程序,应该通过将 TUN、TAP 和桥接组合为单个模块来简化虚拟网络。

  • VXLAN: 一种常用的云覆盖网络概念,我们将在第十二章中详细描述,使用 OpenStack 扩展 KVM

  • VETH: 一种可以用于本地隧道的虚拟以太网接口。

  • IPOIB: Infiniband 上的 IP。随着 Infiniband 在 HPC/低延迟网络中的普及,Linux 内核也支持这种类型的网络。

还有很多其他的。在这些网络接口类型之上,还有大约 10 种隧道接口类型,其中一些如下:

  • GRETAP,GRE:用于封装第 2 层和第 3 层协议的通用路由封装协议。

  • GENEVE:云覆盖网络的融合协议,旨在将 VXLAN、GRE 等融合为一个。这就是为什么它受到 Open vSwitch、VMware NSX 和其他产品的支持。

  • IPIP:通过公共网络连接内部 IPv4 子网的 IP 隧道。

  • SIT:用于在 IPv4 上互连孤立的 IPv6 网络的简单互联网翻译。

  • ip6tnl:IPv4/6 隧道通过 IPv6 隧道接口。

  • IP6GRE,IP6GRETAP 等。

理解所有这些内容是一个相当复杂和繁琐的过程,因此在本书中,我们只会关注对虚拟化和(本书后面的内容)云非常重要的接口类型。这就是为什么我们将在第十二章中讨论 VXLAN 和 GENEVE 覆盖网络,因为我们需要牢牢掌握软件定义网络SDN)。

因此,具体来说,在本章的一部分中,我们将涵盖 TAP/TUN、桥接、Open vSwitch 和 macvtap 接口,因为这些基本上是 KVM 虚拟化最重要的网络概念。

但在深入研究之前,让我们解释一些适用于 KVM/libvirt 网络和其他虚拟化产品的基本虚拟网络概念(例如,VMware 的托管虚拟化产品,如 Workstation 或 Player,使用相同的概念)。当您开始配置 libvirt 网络时,您可以在 NAT、路由和隔离之间进行选择。让我们讨论一下这些网络模式的作用。

Libvirt NAT 网络

对于我们想要连接到互联网的所有设备(例如192.168.0.0/24),我们需要一个 NAT 网络类型。

现在,让我们将其转换为虚拟化网络示例。在我们的虚拟机场景中,这意味着我们的虚拟机可以通过主机的 IP 地址与连接到物理网络的任何内容进行通信,但反之则不行。要使某物能够与我们的虚拟机在 NAT 交换机后面进行通信,我们的虚拟机必须启动该通信(或者我们必须设置某种端口转发,但这不是重点)。

以下图表可能更好地解释了我们正在谈论的内容:

图 4.1 - libvirt NAT 模式下的网络

图 4.1 - libvirt NAT 模式下的网络

从虚拟机的角度来看,它愉快地坐在一个完全独立的网络段中(因此有192.168.122.210220 IP 地址),并使用虚拟网络交换机作为访问外部网络的网关。它不必担心任何额外的路由,因为这就是我们使用 NAT 的原因之一-简化端点路由。

Libvirt 路由网络

第二种网络类型是路由网络,基本上意味着我们的虚拟机通过虚拟交换机直接连接到物理网络。这意味着我们的虚拟机与物理主机处于相同的第 2/3 层网络中。这种类型的网络连接经常被使用,因为通常情况下,没有必要在环境中访问虚拟机时使用单独的 NAT 网络。在某种程度上,这只会使一切变得更加复杂,特别是因为您必须配置路由以了解您用于虚拟机的 NAT 网络。在使用路由模式时,虚拟机位于与下一个物理设备相同的网络段中。以下图表对路由网络有很好的解释:

图 4.2 - libvirt 路由模式下的网络

图 4.2 - libvirt 路由模式下的网络

现在我们已经介绍了最常用的两种虚拟机网络场景,是时候介绍第三种了,这种情况似乎有点模糊。如果我们配置一个没有上行(这意味着它没有物理网络卡连接到它)的虚拟交换机,那么该虚拟交换机根本无法将流量发送到物理网络。剩下的只是在该交换机本身的限制内进行通信,因此称为隔离。让我们现在创建这个难以捉摸的隔离网络。

Libvirt 隔离网络

在这种情况下,连接到同一隔离交换机的虚拟机可以彼此通信,但它们无法与它们运行的主机之外的任何东西通信。我们之前用“模糊”一词来描述这种情况,但实际上并不是 - 在某些方面,这实际上是一种隔离特定类型的流量的理想方式,以至于它甚至不会到达物理网络。

这样想吧 - 假设你有一个托管 Web 服务器的虚拟机,例如运行 WordPress。您创建了两个虚拟交换机:一个运行路由网络(直接连接到物理网络),另一个是隔离的。然后,您可以为 WordPress 虚拟机配置两个虚拟网络卡,第一个连接到路由虚拟交换机,第二个连接到隔离虚拟交换机。WordPress 需要一个数据库,所以您创建另一个虚拟机并配置它仅使用内部虚拟交换机。然后,您使用该隔离虚拟交换机来隔离Web 服务器和数据库服务器之间的流量,以便 WordPress 通过该交换机连接到数据库服务器。通过这样配置虚拟机基础设施,您得到了什么?您有一个双层应用程序,而该 Web 应用程序的最重要部分(数据库)无法从外部访问。看起来并不是一个坏主意,对吧?

隔离的虚拟网络在许多其他与安全相关的场景中使用,但这只是一个我们可以轻松识别的示例场景。

让我们用图表描述我们的隔离网络:

图 4.3 - libvirt 隔离模式下的网络

图 4.3 - libvirt 隔离模式下的网络

本书的上一章(第三章安装 KVM Hypervisor、libvirt 和 ovirt)提到了默认网络,我们说我们稍后会谈论这个。现在似乎是一个合适的时机,因为现在我们已经有足够的信息来描述默认网络配置是什么。

当我们像在第三章中所做的那样安装所有必要的 KVM 库和实用程序,安装 KVM Hypervisor、libvirt 和 oVirt,默认的虚拟交换机会被自动配置。这样做的原因很简单 - 预先配置一些东西更加用户友好,这样用户就可以开始创建虚拟机并将它们连接到默认网络,而不是期望用户也配置这一点。VMware 的 vSphere hypervisor 也是如此(默认交换机称为 vSwitch0),Hyper-V 在部署过程中要求我们配置第一个虚拟交换机(实际上我们可以跳过并稍后配置)。因此,这只是一个众所周知的、标准化的、已建立的场景,使我们能够更快地开始创建我们的虚拟机。

默认虚拟交换机以 NAT 模式工作,DHCP 服务器处于活动状态,再次,这样做的原因很简单 - 客户操作系统默认预配置了 DHCP 网络配置,这意味着我们刚刚创建的虚拟机将轮询网络以获取必要的 IP 配置。这样,虚拟机就可以获得所有必要的网络配置,我们可以立即开始使用它。

以下图表显示了默认的 KVM 网络的功能:

图 4.4 - libvirt 默认 NAT 模式网络

图 4.4 - libvirt 默认 NAT 模式网络

现在,让我们学习如何从 shell 和 GUI 中配置这些类型的虚拟网络概念。我们将把这个过程视为一个需要按顺序完成的过程:

  1. 让我们首先将默认网络配置导出为 XML,以便我们可以将其用作创建新网络的模板:图 4.5 - 导出默认虚拟网络配置

图 4.5 - 导出默认虚拟网络配置

  1. 现在,让我们将该文件复制到一个名为packtnat.xml的新文件中,编辑它,然后使用它来创建一个新的 NAT 虚拟网络。然而,在这之前,我们需要生成两样东西 - 一个新的对象 UUID(用于我们的新网络)和一个唯一的 MAC 地址。可以使用uuidgen命令从 shell 中生成一个新的 UUID,但生成 MAC 地址有点棘手。因此,我们可以使用红帽网站上提供的标准红帽建议方法:access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/virtualization_administration_guide/sect-virtualization-tips_and_tricks-generating_a_new_unique_mac_address。通过使用该 URL 上可用的第一段代码,创建一个新的 MAC 地址(例如,00:16:3e:27:21:c1)。

通过使用yum命令,安装 python2:

virbr1). Now, we can complete the configuration of our new virtual machine network XML file:![Figure 4.6 – New NAT network configuration    ](https://gitee.com/OpenDocCN/freelearn-linux-zh/raw/master/docs/ms-kvm-vrt/img/B14834_04_06.jpg)Figure 4.6 – New NAT network configurationThe next step is importing this configuration.
  1. 现在,我们可以使用virsh命令导入该配置并创建我们的新虚拟网络,启动该网络并使其永久可用,并检查是否一切加载正确:
virsh net-define packtnat.xml
virsh net-start packtnat
virsh net-autostart packtnat
virsh net-list

鉴于我们没有删除默认虚拟网络,最后一个命令应该给我们以下输出:

图 4.7 - 使用 virsh net-list 检查 KVM 主机上有哪些虚拟网络

图 4.7 - 使用 virsh net-list 检查 KVM 主机上有哪些虚拟网络

现在,让我们创建另外两个虚拟网络 - 一个桥接网络和一个隔离网络。同样,让我们使用文件作为模板来创建这两个网络。请记住,为了能够创建一个桥接网络,我们需要一个物理网络适配器,因此我们需要在服务器上有一个可用的物理适配器。在我们的服务器上,该接口被称为ens224,而名为ens192的接口被默认的 libvirt 网络使用。因此,让我们创建两个配置文件,分别称为packtro.xml(用于我们的路由网络)和packtiso.xml(用于我们的隔离网络):

图 4.8 - libvirt 路由网络定义

图 4.8 - libvirt 路由网络定义

在这个特定的配置中,我们使用ens224作为路由虚拟网络的上行链路,该虚拟网络将使用与ens224连接的物理网络相同的子网(192.168.2.0/24):

图 4.9 - libvirt 隔离网络定义

图 4.9 - libvirt 隔离网络定义

为了确保万无一失,我们也可以使用虚拟机管理器 GUI 来配置所有这些,因为该应用程序也有一个用于创建虚拟网络的向导。但是当我们谈论更大的环境时,导入 XML 是一个更简单的过程,即使我们忘记了很多 KVM 虚拟化主机根本没有安装 GUI。

到目前为止,我们已经讨论了整体主机级别的虚拟网络。然而,还有一种不同的方法来处理这个主题 - 使用虚拟机作为我们可以向其添加虚拟网络适配器并将其连接到虚拟网络的对象。我们可以使用virsh来实现这一目的。因此,举个例子,我们可以将名为MasteringKVM01的虚拟机连接到一个隔离的虚拟网络:

virsh attach-interface --domain MasteringKVM01 --source isolated --type network --model virtio --config --live

还有其他概念允许虚拟机连接到物理网络,其中一些我们将在本章后面讨论(如 SR-IOV)。然而,现在我们已经介绍了通过虚拟交换/桥接将虚拟机连接到物理网络的基本方法,我们需要变得更加技术化。问题是,在连接虚拟机到虚拟交换中涉及更多的概念,比如 TAP 和 TUN,我们将在接下来的部分中进行介绍。

使用 TAP 和 TUN 设备进行用户空间网络连接

第一章理解 Linux 虚拟化中,我们使用virt-host-validate命令对主机的 KVM 虚拟化准备情况进行了一些预检查。作为该过程的一部分,一些检查包括检查以下设备是否存在:

  • /dev/kvm:KVM 驱动程序在主机上创建了一个/dev/kvm字符设备,以便为虚拟机提供直接硬件访问。没有这个设备意味着虚拟机将无法访问物理硬件,尽管它在 BIOS 中已启用,这将显著降低虚拟机的性能。

  • /dev/vhost-net:在主机上将创建/dev/vhost-net字符设备。该设备用作配置vhost-net实例的接口。没有这个设备会显著降低虚拟机的网络性能。

  • /dev/net/tun:这是另一个用于创建 TUN/TAP 设备以为虚拟机提供网络连接的字符特殊设备。TUN/TAP 设备将在以后的章节中详细解释。现在只需理解,拥有一个字符设备对于 KVM 虚拟化正常工作是很重要的。

让我们专注于最后一个设备,TUN 设备,通常会伴随着一个 TAP 设备。

到目前为止,我们所涵盖的所有概念都包括与物理网络卡的某种连接,隔离的虚拟网络是一个例外。但即使是隔离的虚拟网络对于我们的虚拟机来说也只是一个虚拟网络。当我们需要在用户空间进行通信时会发生什么,比如在服务器上运行的应用之间?将它们通过某种虚拟交换概念或常规桥接连接起来将会带来额外的开销。这就是 TUN/TAP 设备的作用,为用户空间程序提供数据包流。很容易,应用程序可以打开/dev/net/tun并使用ioctl()函数在内核中注册一个网络设备,然后它会呈现为一个 tunXX 或 tapXX 设备。当应用程序关闭文件时,它创建的网络设备和路由会消失(如内核tuntap.txt文档中所述)。因此,这只是 Linux 操作系统支持的一种虚拟网络接口类型,可以向其添加 IP 地址和路由,以便应用程序的流量可以通过它路由,而不是通过常规网络设备。

TUN 通过创建通信隧道来模拟 L3 设备,类似于点对点隧道。当 tuntap 驱动程序配置为 tun 模式时,它会被激活。激活后,从描述符(配置它的应用程序)接收到的任何数据都将以常规 IP 数据包的形式传输(作为最常用的情况)。同样,当发送数据时,它会被写入 TUN 设备作为常规 IP 数据包。这种类型的接口有时用于测试、开发和模拟调试目的。

TAP 接口基本上模拟 L2 以太网设备。当 tuntap 驱动程序以 tap 模式配置时,它会被激活。当您激活它时,与 TUN 接口(第 3 层)不同,您会获得第 2 层原始以太网数据包,包括 ARP/RARP 数据包和其他所有内容。基本上,我们谈论的是虚拟化的第 2 层以太网连接。

这些概念(特别是 TAP)也可用于 libvirt/QEMU,因为通过使用这些类型的配置,我们可以从主机到虚拟机创建连接 - 例如,没有 libvirt 桥/交换机。我们实际上可以配置 TUN/TAP 接口的所有必要细节,然后通过使用kvm-qemu选项将虚拟机连接到这些接口。因此,这是一个在虚拟化世界中有其位置的相当有趣的概念。当我们开始创建 Linux 桥接时,这尤其有趣。

实施 Linux 桥接

让我们创建一个桥接,然后将 TAP 设备添加到其中。在这样做之前,我们必须确保桥接模块已加载到内核中。让我们开始吧:

  1. 如果未加载,请使用modprobe bridge加载模块:
tester:

brctl show 命令将列出服务器上所有可用的桥接以及一些基本信息,例如桥接的 ID、生成树协议(STP)状态以及连接到其上的接口。在这里,测试器桥没有任何接口连接到其虚拟端口。


  1. Linux 桥接也将显示为网络设备。要查看桥接测试器的网络详细信息,请使用ip命令:
ifconfig to check and configure the network settings for a Linux bridge; ifconfig is relatively easy to read and understand but not as feature-rich as the ip command:

ifconfig tester

测试器:flags=4098<BROADCAST,MULTICAST>mtu 1500

ether26:84:f2:f8:09:e0txqueuelen 1000(以太网)

RX 数据包 0 字节 0(0.0 B)

RX 错误 0 丢弃 0 超限 0 帧 0

TX 数据包 0 字节 0(0.0 B)

TX 错误 0 丢弃 0 超限 0 载波 0 冲突 0


The Linux bridge tester is now ready. Let's create and add a TAP device to it. 
  1. 首先,检查 TUN/TAP 设备模块是否加载到内核中。如果没有,您已经知道该怎么做:
vm-vnic:

测试器和名为 vm-vnic 的 tap 设备。让我们将 vm-vnic 添加到 tester:

# brctl addif tester vm-vnic
# brctl show
bridge name bridge id STP enabled interfaces
tester 8000.460a80dd627d no vm-vnic

在这里,您可以看到vm-vnic是添加到tester桥的接口。现在,vm-vnic可以作为您的虚拟机和tester桥之间的接口,从而使虚拟机能够与添加到此桥的其他虚拟机进行通信:

图 4.10 - 连接到虚拟交换机(桥接)的虚拟机

](https://gitee.com/OpenDocCN/freelearn-linux-zh/raw/master/docs/ms-kvm-vrt/img/B14834_04_10.jpg)

图 4.10 - 连接到虚拟交换机(桥接)的虚拟机

您可能还需要删除在上一个过程中创建的所有对象和配置。让我们通过命令行逐步进行:

  1. 首先,我们需要从tester桥中删除vm-vnic tap 设备:
vm-vnic has been removed from the bridge, remove the tap device using the ip command:

ip tuntap del dev vm-vnic mode tap


  1. 然后,删除测试器桥:
# brctl delbr tester

这些是 libvirt 在后端执行的相同步骤,用于启用或禁用虚拟机的网络。在继续之前,我们希望您彻底了解此过程。现在我们已经介绍了 Linux 桥接,是时候转向一个更高级的概念,称为 Open vSwitch。

配置 Open vSwitch

想象一下,你在一家小公司工作,有三到四个 KVM 主机,几个网络附加存储设备来托管他们的 15 台虚拟机,并且你从一开始就被公司雇佣。因此,您已经见证了一切 - 公司购买了一些服务器、网络交换机、电缆和存储设备,并且您是建立该环境的一小部分人员团队。经过 2 年的过程,您已经意识到一切都运作正常,维护简单,并且没有给您带来太多烦恼。

现在,想象一下,你的一个朋友在一家拥有 400 个 KVM 主机和近 2000 台虚拟机的大型企业公司工作,他们需要管理的工作与你在你的小公司的舒适椅子上所做的工作相同。

你认为你的朋友能否通过使用与你相同的工具来管理他或她的环境?使用 XML 文件进行网络交换机配置,从可引导的 USB 驱动器部署服务器,手动配置一切,并有时间这样做?这对你来说可能吗?

在第二种情况中有两个基本问题:

  • 环境的规模:这一点更为明显。由于环境的规模,您需要一种在中央进行管理的概念,而不是在主机级别进行管理,比如我们迄今讨论过的虚拟交换机。

  • 公司政策:这些通常规定尽可能从配置标准化中获得的一些合规性。现在,我们可以同意我们可以通过 Ansible,Puppet 或类似工具脚本化一些配置更新,但有什么用呢?每次我们需要对 KVM 网络进行更改时,我们都必须创建新的配置文件,新的流程和新的工作簿。大公司对此持负面态度。

所以,我们需要的是一个可以跨越多个主机并提供配置一致性的集中式网络对象。在这种情况下,配置一致性为我们带来了巨大的优势 - 我们在这种类型的对象中引入的每个更改都将被复制到所有属于这个集中式网络对象的主机。换句话说,我们需要的是Open vSwitchOVS)。对于那些更熟悉基于 VMware 的网络的人来说,我们可以使用一个近似的隐喻 - 对于基于 KVM 的环境,Open vSwitch 类似于 vSphere 分布式交换机对于基于 VMware 的环境。

在技术方面,OVS 支持以下内容:

  • VLAN 隔离(IEEE 802.1Q)

  • 流量过滤

  • 具有或不具有 LACP 的 NIC 绑定

  • 各种覆盖网络 - VXLAN,GENEVE,GRE,STT 等

  • 802.1ag 支持

  • Netflow,sFlow 等

  • (R)SPAN

  • OpenFlow

  • OVSDB

  • 流量排队和整形

  • Linux,FreeBSD,NetBSD,Windows 和 Citrix 支持(以及其他许多)

现在我们已经列出了一些支持的技术,让我们讨论一下 Open vSwitch 的工作方式。

首先,让我们谈谈 Open vSwitch 的架构。 Open vSwitch 的实现分为两部分:Open vSwitch 内核模块(数据平面)和用户空间工具(控制平面)。由于传入的数据包必须尽快处理,因此 Open vSwitch 的数据平面被推到了内核空间:

图 4.11 - Open vSwitch 架构

图 4.11 - Open vSwitch 架构

数据路径(OVS 内核模块)使用 netlink 套接字与 vswitchd 守护程序进行交互,在本地系统上实现和管理任意数量的 OVS 交换机。

Open vSwitch 没有特定的 SDN 控制器用于管理目的,类似于 VMware 的 vSphere 分布式交换机和 NSX,它们有 vCenter 和各种 NSX 组件来管理它们的功能。在 OVS 中,重点是使用其他人的 SDN 控制器,然后使用 OpenFlow 协议与 ovs-vswitchd 进行交互。ovsdb-server 维护交换机表数据库,外部客户端可以使用 JSON-RPC 与 ovsdb-server 进行通信;JSON 是数据格式。ovsdb 数据库目前包含大约 13 个表,并且此数据库在重新启动时是持久的。

Open vSwitch 有两种模式:正常模式和流模式。本章将主要集中讨论如何在独立/正常模式下启动连接到 Open vSwitch 桥的 KVM VM,并简要介绍使用 OpenDaylight 控制器的流模式:

  • 正常模式:交换和转发由 OVS 桥处理。在这种模式下,OVS 充当 L2 学习交换机。当为目标配置多个覆盖网络而不是操纵交换机流时,此模式特别有用。

  • ctl命令。此模式允许更高级别的抽象和自动化;SDN 控制器公开了 REST API。我们的应用程序可以利用此 API 直接操纵桥接的流量以满足网络需求。

让我们继续实际操作,学习如何在 CentOS 8 上安装 Open vSwitch:

  1. 我们必须做的第一件事是告诉系统使用适当的存储库。在这种情况下,我们需要启用名为epelcentos-release-openstack-train的存储库。我们可以通过使用一些yum命令来实现:
yum -y install epel-release
yum -y install centos-release-openstack-train
  1. 下一步将从 Red Hat 的存储库安装openvswitch
dnf install openvswitch -y
  1. 安装过程完成后,我们需要通过启动和启用 Open vSwitch 服务并运行ovs-vsctl -V命令来检查一切是否正常工作:
2.11.0 and DB schema 7.16.1.
  1. 现在我们已经成功安装并启动了 Open vSwitch,现在是时候对其进行配置了。让我们选择一个部署方案,在该方案中,我们将使用 Open vSwitch 作为虚拟机的新虚拟交换机。在我们的服务器中,我们还有另一个名为ens256的物理接口,我们将使用它作为 Open vSwitch 虚拟交换机的上行。我们还将清除 ens256 的配置,为我们的 OVS 配置 IP 地址,并使用以下命令启动 OVS:
ovs-vsctl add-br ovs-br0
ip addr flush dev ens256
ip addr add 10.10.10.1/24 dev ovs-br0
ovs-vsctl add-port ovs-br0 ens256
ip link set dev ovs-br0 up
  1. 现在一切都配置好了,但还没有持久化,我们需要使配置持久化。这意味着配置一些网络接口配置文件。因此,转到/etc/sysconfig/network-scripts并创建两个文件。将其中一个命名为ifcfg-ens256(用于我们的上行接口):
ifcfg-ovs-br0 (for our OVS):

DEVICE=ovs-br0

DEVICETYPE=ovs

TYPE=OVSBridge

BOOTPROTO=static

IPADDR=10.10.10.1

NETMASK=255.255.255.0

GATEWAY=10.10.10.254

ONBOOT=yes


  1. 我们不是为了展示而配置所有这些,因此我们需要确保我们的 KVM 虚拟机也能够使用它。这意味着我们需要创建一个将使用 OVS 的 KVM 虚拟网络。幸运的是,我们之前已经处理过 KVM 虚拟网络 XML 文件(查看Libvirt 隔离网络部分),因此这不会成为问题。让我们将我们的网络命名为packtovs,其对应的 XML 文件命名为packtovs.xml。它应该包含以下内容:
<network>
<name>packtovs</name>
<forward mode='bridge'/>
<bridge name='ovs-br0'/>
<virtualport type='openvswitch'/>
</network>

因此,现在,当我们在 XML 文件中有一个虚拟网络定义时,我们可以执行我们通常的操作,即定义、启动和自动启动网络:

virsh net-define packtovs.xml
virsh net-start packtovs
virsh net-autostart packtovs

如果我们在创建虚拟网络时保持一切不变,那么virsh net-list的输出应该是这样的:

图 4.12–成功的 OVS 配置和 OVS+KVM 配置

图 4.12–成功的 OVS 配置和 OVS+KVM 配置

现在剩下的就是将 VM 连接到我们新定义的基于 OVS 的网络packtovs,然后我们就可以放心了。或者,我们可以创建一个新的,并使用我们在第三章中获得的知识预先将其连接到特定接口。因此,让我们发出以下命令,其中只有两个更改的参数(--name--network):

virt-install --virt-type=kvm --name MasteringKVM03 --vcpus 2 --ram 4096 --os-variant=rhel8.0 --cdrom=/var/lib/libvirt/images/CentOS-8-x86_64-1905-dvd1.iso --network network:packtovs --graphics vnc --disk size=16

虚拟机安装完成后,我们连接到基于 OVS 的packtovs虚拟网络,并且我们的虚拟机可以使用它。假设需要进行额外配置,并且我们收到了一个请求,要求标记来自该虚拟机的流量为VLAN ID 5。启动虚拟机并使用以下一组命令:

ovs-vsctl list-ports ovs-br0
ens256
vnet0

此命令告诉我们,我们正在使用ens256端口作为上行,并且我们的虚拟机MasteringKVM03正在使用虚拟vnet0网络端口。我们可以使用以下命令对该端口应用 VLAN 标记:

ovs-vsctl set port vnet0 tag=5

由于 OVS 的管理和管理是通过 CLI 完成的,我们需要注意一些与 OVS 管理相关的附加命令。因此,以下是一些常用的 OVS CLI 管理命令:

  • #ovs-vsctl show:一个非常方便和经常使用的命令。它告诉我们交换机当前运行的配置是什么。

  • #ovs-vsctl list-br:列出在 Open vSwitch 上配置的桥接。

  • #ovs-vsctl list-ports <bridge>:显示BRIDGE上所有端口的名称。

  • #ovs-vsctl list interface <bridge>:显示BRIDGE上所有接口的名称。

  • #ovs-vsctl add-br <bridge>:在交换机数据库中创建一个桥接。

  • #ovs-vsctl add-port <bridge> : <interface>:将接口(物理或虚拟)绑定到 Open vSwitch 桥接。

  • #ovs-ofctl 和 ovs-dpctl:这两个命令用于管理和监视流条目。您了解到 OVS 管理两种流:OpenFlows 和 Datapath。第一种是在控制平面中管理的,而第二种是基于内核的流。

  • #ovs-ofctl:这是针对 OpenFlow 模块的,而ovs-dpctl则是针对内核模块的。

以下示例是每个命令的最常用选项:

  • #ovs-ofctl show <BRIDGE>:显示有关交换机的简要信息,包括端口号到端口名称的映射。

  • #ovs-ofctl dump-flows <Bridge>:检查 OpenFlow 表。

  • #ovs-dpctl show:打印有关交换机上存在的所有逻辑数据路径(称为桥接)的基本信息。

  • #ovs-dpctl dump-flows:显示在数据路径中缓存的流。

  • ovs-appctl:此命令提供了一种向运行中的 Open vSwitch 发送命令并收集ovs-ofctl命令未直接暴露的信息的方法。这是 OpenFlow 故障排除的瑞士军刀。

  • #ovs-appctl bridge/dumpflows <br>:检查流表并为同一主机上的 VM 提供直接连接。

  • #ovs-appctl fdb/show <br>:列出学习到的 MAC/VLAN 对。

此外,您还可以始终使用ovs-vsctl show命令获取有关 OVS 交换机配置的信息:

图 4.13 – ovs-vsctl 显示输出

图 4.13 – ovs-vsctl 显示输出

我们将在第十二章中回到 Open vSwitch 的主题,使用 OpenStack 扩展 KVM,当我们更深入地讨论跨多个主机跨 Open vSwitch 的情况时,特别是在考虑到我们希望能够跨多个主机和站点扩展我们的云覆盖网络(基于 GENEVE、VXLAN、GRE 或类似协议)的情况。

其他 Open vSwitch 用例

正如您可能想象的那样,Open vSwitch 不仅仅是 libvirt 或 OpenStack 的一个方便的概念——它也可以用于各种其他场景。让我们描述其中的一个,因为对于研究 VMware NSX 或 NSX-T 集成的人来说,这可能很重要。

让我们在这里只描述一些基本术语和关系。VMware 的 NSX 是一种基于 SDN 的技术,可用于各种用例:

  • 连接数据中心,跨数据中心边界扩展云覆盖网络。

  • 各种灾难恢复场景。NSX 可以在灾难恢复、多站点环境以及与各种外部服务和设备集成方面提供大量帮助(Palo Alto PANs)。

  • 一致的微分段,跨站点,在虚拟机网络卡级别上以正确的方式完成。

  • 出于安全目的,从不同类型的支持的 VPN 技术连接站点和终端用户,到分布式防火墙、客户端内省选项(防病毒和反恶意软件)、网络内省选项(IDS/IPS)等各种灾难恢复场景。

  • 用于负载平衡,直到第 7 层,具有 SSL 卸载、会话持久性、高可用性、应用规则等。

是的,VMware 对 SDN(NSX)和 Open vSwitch 的看法在市场上看起来像是竞争技术,但实际上,有很多客户希望同时使用两者。这就是 VMware 与 OpenStack 集成以及 NSX 与基于 Linux 的 KVM 主机集成(通过使用 Open vSwitch 和额外的代理)非常方便的地方。再进一步解释一下这些观点 - NSX 有一些需要广泛使用基于 Open vSwitch 的技术 - 通过 Open vSwitch 数据库进行硬件 VTEP 集成,通过使用 Open vSwitch/NSX 集成将 GENEVE 网络扩展到 KVM 主机,等等。

想象一下,你在为一个服务提供商工作 - 一个云服务提供商,一个 ISP;基本上,任何具有大量网络分割的大型网络的公司。有很多服务提供商使用 VMware 的 vCloud Director 为最终用户和公司提供云服务。然而,由于市场需求,这些环境通常需要扩展到包括 AWS(通过公共云进行额外基础设施增长场景)或 OpenStack(创建混合云场景)。如果我们没有可能在这些解决方案之间实现互操作性,那么就没有办法同时使用这些提供。但从网络的角度来看,这个网络背景是 NSX 或 NSX-T(实际上使用了 Open vSwitch)。

多云环境的未来已经很清楚多年了,这些类型的集成将带来更多的客户;他们将希望在他们的云服务设计中利用这些选项。未来的发展也很可能包括(并且已经部分包括)与 Docker、Kubernetes 和/或 OpenShift 的集成,以便能够在同一环境中管理容器。

还有一些更极端的例子使用硬件 - 在我们的例子中,我们谈论的是以分区方式使用 PCI Express 总线上的网络卡。目前,我们对这个概念 SR-IOV 的解释将局限于网络卡,但当我们开始讨论在虚拟机中使用分区 GPU 时,我们将在第六章中扩展相同的概念,虚拟显示设备和协议。因此,让我们讨论一下在支持它的 Intel 网络卡上使用 SR-IOV 的实际例子。

理解和使用 SR-IOV

SR-IOV 的概念是我们在第二章中已经提到的,KVM 作为虚拟化解决方案。通过利用 SR-IOV,我们可以将 PCI 资源(例如,网络卡)分区为虚拟 PCI 功能,并将它们注入到虚拟机中。如果我们将这个概念用于网络卡,通常是出于一个目的 - 那就是我们可以避免使用操作系统内核和网络堆栈,同时访问虚拟机中的网络接口卡。为了能够做到这一点,我们需要硬件支持,因此我们需要检查我们的网络卡是否实际支持它。在物理服务器上,我们可以使用lspci命令提取有关我们的 PCI 设备的属性信息,然后使用grep命令将Single Root I/O Virtualization作为一个字符串来尝试查看我们是否有兼容的设备。这是我们服务器的一个例子:

图 4.14 – 检查我们的系统是否兼容 SR-IOV

图 4.14 – 检查我们的系统是否兼容 SR-IOV

重要说明

在配置 SR-IOV 时要小心。您需要具有支持它的服务器、支持它的设备,并且必须确保您在 BIOS 中打开了 SR-IOV 功能。然后,您需要记住,有些服务器只分配了特定的插槽用于 SR-IOV。我们使用的服务器(HP Proliant DL380p G8)将三个 PCI-Express 插槽分配给 CPU1,但是 SR-IOV 仅在插槽#1 中起作用。当我们将我们的卡连接到插槽#2 或#3 时,我们收到了一个 BIOS 消息,指出 SR-IOV 在该插槽中不起作用,并且我们应该将我们的卡移动到支持 SR-IOV 的插槽。因此,请务必彻底阅读服务器的文档,并将 SR-IOV 兼容设备连接到正确的 PCI-Express 插槽。

在这种特定情况下,这是一个具有两个端口的英特尔 10 千兆网络适配器,我们可以使用它来执行 SR-IOV。该过程并不那么困难,它要求我们完成以下步骤:

  1. 从先前的模块中解绑。

  2. 将其注册到 Linux 内核堆栈中可用的 vfio-pci 模块。

  3. 配置将使用它的客户端。

因此,您要做的是通过使用modprobe -r卸载网卡当前正在使用的模块。然后,您会再次加载它,但是通过分配一个附加参数。在我们特定的服务器上,我们使用的英特尔双端口适配器(X540-AT2)被分配给了ens1f0ens1f1网络设备。因此,让我们以ens1f0作为启动时 SR-IOV 配置的示例:

  1. 我们需要做的第一件事(作为一个一般概念)是找出我们的网卡正在使用哪个内核模块。为此,我们需要发出以下命令:
modinfo command (we're only interested in the parm part of the output):

在这里使用 ixgbe 模块,我们可以执行以下操作:

modprobe -r ixgbe
modprobe ixgbe max_vfs=4

  1. 然后,我们可以使用modprobe系统通过在/etc/modprobe.d中创建一个名为(例如)ixgbe.conf的文件,并向其中添加以下行来使这些更改在重新启动时保持永久:
options ixgbe max_vfs=4

这将为我们提供最多四个虚拟功能,我们可以在虚拟机内使用。现在,我们需要解决的下一个问题是如何在服务器启动时激活 SR-IOV。这里涉及了相当多的步骤,所以让我们开始吧:

  1. 我们需要将iommuvfs参数添加到默认内核引导行和默认内核配置中。因此,首先打开/etc/default/grub并编辑GRUB_CMDLINE_LINUX行,添加intel_iommu=on(如果您使用的是 AMD 系统,则添加amd_iommu=on)和ixgbe.max_vfs=4

  2. 我们需要重新配置grub以使用此更改,因此我们需要使用以下命令:

grub2-mkconfig -o /boot/grub2/grub.cfg
  1. 有时,即使这样还不够,因此我们需要配置必要的内核参数,例如虚拟功能的最大数量和服务器上要使用的iommu参数。这导致我们使用以下命令:
grubby --update-kernel=ALL --args="intel_iommu=on ixgbe.max_vfs=4"

重新启动后,我们应该能够看到我们的虚拟功能。输入以下命令:

lspci -nn | grep "Virtual Function"

我们应该得到以下类似的输出:

图 4.15 - 检查虚拟功能可见性

图 4.15 - 检查虚拟功能可见性

我们应该能够从 libvirt 中看到这些虚拟功能,并且我们可以通过virsh命令进行检查。让我们尝试一下(我们使用grep 04,因为我们的设备 ID 以 04 开头,这在前面的图像中可见;我们将缩小输出以仅包含重要条目):

virsh nodedev-list | grep 04 
……
pci_0000_04_00_0
pci_0000_04_00_1
pci_0000_04_10_0
pci_0000_04_10_1
pci_0000_04_10_2
pci_0000_04_10_3
pci_0000_04_10_4
pci_0000_04_10_5
pci_0000_04_10_6
pci_0000_04_10_7

前两个设备是我们的物理功能。其余的八个设备(两个端口乘以四个功能)是我们的虚拟设备(从pci_0000_04_10_0pci_0000_04_10_7)。现在,让我们使用virsh nodedev-dumpxml pci_0000_04_10_0命令来转储该设备的信息:

图 4.16 - 从 virsh 的角度查看虚拟功能信息

图 4.16 - 从 virsh 的角度查看虚拟功能信息

因此,如果我们有一个正在运行的虚拟机,我们想要重新配置以使用此功能,我们需要创建一个 XML 文件,其定义看起来像这样(让我们称其为packtsriov.xml):

<interface type='hostdev' managed='yes' >
    <source>
    <address type='pci' domain='0x0000' bus='0x04' slot='0x10' function='0x0'>
    </address>
    </source>
</interface>

当然,域、总线、插槽和功能需要准确指向我们的 VF。然后,我们可以使用virsh命令将该设备附加到我们的虚拟机(例如MasteringKVM03):

virsh attach-device MasteringKVM03 packtsriov.xml --config

当我们使用virsh dumpxml时,现在应该看到输出的一部分以<driver name='vfio'/>开头,以及我们在上一步中配置的所有信息(地址类型、域、总线、插槽、功能)。我们的虚拟机应该没有问题使用这个虚拟功能作为网络卡。

现在,是时候介绍另一个在 KVM 网络中非常有用的概念了:macvtap。这是一个较新的驱动程序,应该通过一个模块完全消除 tun/tap 和桥接驱动程序来简化我们的虚拟化网络。

理解 macvtap

这个模块的工作方式类似于 tap 和 macvlan 模块的组合。我们已经解释了 tap 模块的功能。macvlan 模块使我们能够创建虚拟网络,这些网络固定在物理网络接口上(通常,我们称这个接口为lower接口或设备)。结合 tap 和 macvlan 使我们能够在虚拟以太网端口聚合器VEPA)、桥接、私有和透传四种不同的操作模式之间进行选择。

如果我们使用 VEPA 模式(默认模式),物理交换机必须通过支持hairpin模式(也称为反射中继)来支持 VEPA。当一个lower设备从 VEPA 模式 macvlan 接收数据时,这个流量总是发送到上游设备,这意味着流量总是通过外部交换机进行传输。这种模式的优势在于虚拟机之间的网络流量在外部网络上变得可见,这对于各种原因可能是有用的。您可以查看以下一系列图表中的网络流量是如何工作的:

图 4.17 – macvtap VEPA 模式,流量被强制发送到外部网络

图 4.17 – macvtap VEPA 模式,流量被强制发送到外部网络

在私有模式下,它类似于 VEPA,因为所有的东西都会发送到外部交换机,但与 VEPA 不同的是,只有通过外部路由器或交换机发送的流量才会被传送。如果您想要将连接到端点的虚拟机相互隔离,但不隔离外部网络,可以使用这种模式。如果这听起来非常像私有 VLAN 场景,那么您是完全正确的:

图 4.18 – macvtap 在私有模式下,用于内部网络隔离

图 4.18 – macvtap 在私有模式下,用于内部网络隔离

在桥接模式下,接收到的数据在您的 macvlan 上,应该发送到同一较低设备上的另一个 macvlan,直接发送到目标,而不是外部发送,然后路由返回。这与 VMware NSX 在虚拟机应该在不同的 VXLAN 网络上进行通信时所做的非常相似,但是在同一主机上:

图 4.19 – macvtap 在桥接模式下,提供一种内部路由

图 4.19 – macvtap 在桥接模式下,提供一种内部路由

在透传模式下,我们基本上在谈论 SR-IOV 场景,我们将 VF 或物理设备直接传递给 macvtap 接口。关键区别在于单个网络接口只能传递给单个客户(1:1 关系):

图 4.20 – macvtap 在透传模式下

图 4.20 – macvtap 在透传模式下

第十二章使用 OpenStack 扩展 KVM第十三章使用 AWS 扩展 KVM中,我们将描述为什么虚拟化和覆盖网络(VXLAN、GRE、GENEVE)对于云网络非常重要,因为我们将我们的本地 KVM 环境扩展到云端,无论是通过 OpenStack 还是 AWS。

总结

在本章中,我们介绍了 KVM 中虚拟化网络的基础知识,并解释了为什么虚拟化网络是虚拟化的重要组成部分。我们深入研究了配置文件及其选项,因为这将是在较大环境中进行管理的首选方法,特别是在谈论虚拟化网络时。

请特别注意我们在本章中讨论的所有配置步骤,特别是与使用 virsh 命令来操作网络配置和配置 Open vSwitch 和 SR-IOV 相关的部分。基于 SR-IOV 的概念在延迟敏感的环境中被广泛使用,以提供具有最低可能开销和延迟的网络服务,这就是为什么这个原则对于与金融和银行业相关的各种企业环境非常重要。

既然我们已经涵盖了所有必要的网络场景(其中一些将在本书的后面重新讨论),现在是时候开始考虑虚拟化世界的下一个重要主题了。我们已经讨论了 CPU 和内存,以及网络,这意味着我们剩下了虚拟化的第四支柱:存储。我们将在下一章中讨论这个主题。

问题

  1. 为什么虚拟交换机同时接受来自多个虚拟机的连接是重要的?

  2. 虚拟交换机在 NAT 模式下是如何工作的?

  3. 虚拟交换机在路由模式下是如何工作的?

  4. Open vSwitch 是什么,我们可以在虚拟化和云环境中用它来做什么?

  5. 描述 TAP 和 TUN 接口之间的区别。

进一步阅读

有关本章内容的更多信息,请参考以下链接:

第五章:Libvirt 存储

这一章节为您提供了 KVM 使用存储的见解。具体来说,我们将涵盖运行虚拟机的主机内部存储和共享存储。不要让术语在这里让您困惑——在虚拟化和云技术中,术语共享存储表示多个虚拟化程序可以访问的存储空间。正如我们稍后将解释的那样,实现这一点的三种最常见方式是使用块级、共享级或对象级存储。我们将以 NFS 作为共享级存储的示例,以Internet Small Computer System InterfaceiSCSI)和Fiber ChannelFC)作为块级存储的示例。在对象级存储方面,我们将使用 Ceph。GlusterFS 如今也被广泛使用,因此我们也会确保涵盖到它。为了将所有内容整合到一个易于使用和管理的框架中,我们将讨论一些可能在练习和创建测试环境时对您有所帮助的开源项目。

在本章中,我们将涵盖以下主题:

  • 存储介绍

  • 存储池

  • NFS 存储

  • iSCSI 和 SAN 存储

  • 存储冗余和多路径处理

  • Gluster 和 Ceph 作为 KVM 的存储后端

  • 虚拟磁盘映像和格式以及基本的 KVM 存储操作

  • 存储的最新发展—NVMe 和 NVMeOF

存储介绍

与网络不同,大多数 IT 人员至少对网络有基本的了解,而存储往往是完全不同的。简而言之,是的,它往往更加复杂。涉及许多参数、不同的技术,而且……坦率地说,有许多不同类型的配置选项和强制执行这些选项的人。还有很多问题。以下是其中一些:

  • 我们应该为每个存储设备配置一个 NFS 共享还是两个?

  • 我们应该为每个存储设备创建一个 iSCSI 目标还是两个?

  • 我们应该创建一个 FC 目标还是两个?

  • 每个逻辑单元号LUN)每个目标应该有多少个?

  • 我们应该使用何种集群大小?

  • 我们应该如何进行多路径处理?

  • 我们应该使用块级还是共享级存储?

  • 我们应该使用块级还是对象级存储?

  • 我们应该选择哪种技术或解决方案?

  • 我们应该如何配置缓存?

  • 我们应该如何配置分区或掩码?

  • 我们应该使用多少个交换机?

  • 我们应该在存储级别使用某种集群技术吗?

正如你所看到的,问题不断堆积,而我们几乎只是触及了表面,因为还有关于使用哪种文件系统、使用哪种物理控制器来访问存储以及使用何种类型的布线等问题——这些问题变成了一个包含许多潜在答案的大杂烩。更糟糕的是,许多答案都可能是正确的,而不仅仅是其中一个。

让我们先把基本的数学问题解决掉。在企业级环境中,共享存储通常是环境中最昂贵的部分,同时也可能对虚拟机性能产生最显著的负面影响,同时又是该环境中最过度订阅的资源。让我们想一想这个问题——每个开机的虚拟机都会不断地向我们的存储设备发送 I/O 操作。如果我们在单个存储设备上运行了 500 台虚拟机,那么我们是不是对存储设备要求过高了?

与此同时,某种共享存储概念是虚拟化环境的关键支柱。基本原则非常简单——有许多高级功能可以通过共享存储更好地发挥作用。此外,如果有共享存储可用,许多操作会更快。更重要的是,如果我们的虚拟机存储和执行位置不在同一地方,那么高可用性的简单选项就有很多。

作为一个额外的好处,如果我们正确设计共享存储环境,我们可以轻松避免单点故障SPOF)的情况。在企业级环境中,避免 SPOF 是关键设计原则之一。但是当我们开始将交换机、适配器和控制器添加到购买清单上时,我们的经理或客户通常会开始头痛。我们谈论性能和风险管理,而他们谈论价格。我们谈论他们的数据库和应用程序需要适当的 I/O 和带宽供应,而他们觉得你可以凭空产生这些。只需挥动魔术棒,我们就有了:无限的存储性能。

但是,你的客户肯定会试图强加给你的最好的、我们永远喜欢的苹果和橙子比较是这样的……“我的闪亮新款 1TB NVMe SSD 笔记本电脑的 IOPS 比你的 5 万美元的存储设备多 1000 倍,性能比你的存储设备多 5 倍,而成本却少 100 倍!你根本不知道你在做什么!”

如果你曾经有过这种经历,我们为你感到难过。很少会有这么多关于盒子里的一块硬件的讨论和争论。但它是一个如此重要的盒子里的硬件,这是一场很好的争论。因此,让我们解释一些 libvirt 在存储访问方面使用的关键概念,以及如何利用我们的知识从我们的存储系统和使用它的 libvirt 中尽可能多地提取性能。

在本章中,我们基本上将通过安装和配置示例涵盖几乎所有这些存储类型。每一种都有自己的用例,但一般来说,你将要选择你要使用的是什么。

因此,让我们开始我们通过这些支持的协议的旅程,并学习如何配置它们。在我们讨论存储池之后,我们将讨论 NFS,这是一种典型的虚拟机存储的共享级协议。然后,我们将转向块级协议,如 iSCSI 和 FC。然后,我们将转向冗余和多路径,以增加我们存储设备的可用性和带宽。我们还将讨论不太常见的文件系统(如 Ceph、Gluster 和 GFS)在 KVM 虚拟化中的各种用例。我们还将讨论当前的事实趋势的新发展。

存储池

当你第一次开始使用存储设备时,即使它们是更便宜的盒子,你也会面临一些选择。他们会要求你进行一些配置——选择 RAID 级别、配置热备份、SSD 缓存……这是一个过程。同样的过程也适用于从头开始构建数据中心或扩展现有数据中心的情况。你必须配置存储才能使用它。

当涉及到存储时,虚拟化管理程序有点“挑剔”,因为它们支持一些存储类型,而不支持一些存储类型。例如,微软的 Hyper-V 支持 SMB 共享用于虚拟机存储,但实际上不支持 NFS 存储用于虚拟机存储。VMware 的 vSphere Hypervisor 支持 NFS,但不支持 SMB。原因很简单——一家开发虚拟化管理程序的公司选择并验证其虚拟化管理程序将支持的技术。然后,各种 HBA/控制器供应商(英特尔、Mellanox、QLogic 等)开发该虚拟化管理程序的驱动程序,存储供应商决定他们的存储设备将支持哪些存储协议。

从 CentOS 的角度来看,有许多不同类型的存储池得到支持。以下是其中一些:

  • 基于逻辑卷管理器LVM)的存储池

  • 基于目录的存储池

  • 基于分区的存储池

  • 基于 GlusterFS 的存储池

  • 基于 iSCSI 的存储池

  • 基于磁盘的存储池

  • 基于 HBA 的存储池,使用 SCSI 设备

从 libvirt 的角度来看,存储池可以是 libvirt 管理的目录、存储设备或文件。这导致了 10 多种不同的存储池类型,你将在下一节中看到。从虚拟机的角度来看,libvirt 管理虚拟机存储,虚拟机使用它来存储数据。

另一方面,oVirt 看待事情有所不同,因为它有自己的服务与 libvirt 合作,从数据中心的角度提供集中的存储管理。数据中心的角度可能听起来有点奇怪。但想想看——数据中心是一种更高级的对象,你可以在其中看到所有的资源。数据中心使用存储虚拟化平台为我们提供虚拟化所需的所有服务——虚拟机、虚拟网络、存储域等。基本上,从数据中心的角度来看,你可以看到所有属于该数据中心成员的主机上发生了什么。然而,从主机级别来看,你无法看到另一个主机上发生了什么。从管理和安全的角度来看,这是一个完全合乎逻辑的层次结构。

oVirt 可以集中管理这些不同类型的存储池(随着时间的推移,列表可能会变得更长或更短):

  • 网络文件系统NFS

  • 并行 NFSpNFS

  • iSCSI

  • FC

  • 本地存储(直接连接到 KVM 主机)

  • GlusterFS 导出

  • 符合 POSIX 的文件系统

让我们先搞清一些术语:

  • Brtfs是一种文件系统,支持快照、RAID 和类似 LVM 的功能、压缩、碎片整理、在线调整大小以及许多其他高级功能。在发现其 RAID5/6 很容易导致数据丢失后,它被弃用了。

  • ZFS是一种文件系统,支持 Brtfs 的所有功能,还支持读写缓存。

CentOS 有一种新的处理存储池的方式。虽然仍处于技术预览阶段,但通过这个名为Stratis的新工具进行完整配置是值得的。基本上,几年前,Red Hat 最终放弃了推动 Brtfs 用于未来版本的想法,开始致力于 Stratis。如果你曾经使用过 ZFS,那么这可能是类似的——一套易于管理的、类似 ZFS 的卷管理工具,Red Hat 可以在未来的发布中支持。此外,就像 ZFS 一样,基于 Stratis 的池可以使用缓存;因此,如果你有一块 SSD 想要专门用于池缓存,你也可以做到。如果你一直期待 Red Hat 支持 ZFS,那么有一个基本的 Red Hat 政策阻碍了这一点。具体来说,ZFS 不是 Linux 内核的一部分,主要是因为许可证的原因。Red Hat 对这些情况有一个政策——如果它不是内核的一部分(上游),那么他们就不提供也不支持。就目前而言,这不会很快发生。这些政策也反映在了 CentOS 中。

本地存储池

另一方面,Stratis 现在就可以使用。我们将使用它来管理我们的本地存储,创建存储池。创建池需要我们事先设置分区或磁盘。创建池后,我们可以在其上创建卷。我们只需要非常小心一件事——虽然 Stratis 可以管理 XFS 文件系统,但我们不应该直接从文件系统级别对 Stratis 管理的 XFS 文件系统进行更改。例如,不要使用基于 XFS 的命令直接重新配置或重新格式化基于 Stratis 的 XFS 文件系统,因为这会在系统上造成混乱。

Stratis 支持各种不同类型的块存储设备:

  • 硬盘和固态硬盘

  • iSCSI LUNs

  • LVM

  • LUKS

  • MD RAID

  • 设备映射器多路径

  • NVMe 设备

让我们从头开始安装 Stratis,以便我们可以使用它。我们使用以下命令:

yum -y install stratisd stratis-cli
systemctl enable --now stratisd

第一条命令安装了 Stratis 服务和相应的命令行实用程序。第二条命令将启动并启用 Stratis 服务。

现在,我们将通过一个完整的示例来介绍如何使用 Stratis 来配置您的存储设备。我们将介绍这种分层方法的一个示例。因此,我们将按照以下步骤进行:

  • 使用 MD RAID 创建软件 RAID10 +备用

  • 从 MD RAID 设备创建一个 Stratis 池。

  • 向池中添加缓存设备以使用 Stratis 的缓存功能。

  • 创建一个 Stratis 文件系统并将其挂载在我们的本地服务器上

这里的前提很简单——通过 MD RAID 的软件 RAID10+备用将近似于常规的生产方法,其中您将有某种硬件 RAID 控制器向系统呈现单个块设备。我们将向池中添加缓存设备以验证缓存功能,因为这是我们在使用 ZFS 时很可能会做的事情。然后,我们将在该池上创建一个文件系统,并通过以下命令将其挂载到本地目录:

mdadm --create /dev/md0 --verbose --level=10 --raid-devices=4 /dev/sdb /dev/sdc /dev/sdd /dev/sde --spare-devices=1 /dev/sdf2
stratis pool create PacktStratisPool01 /dev/md0
stratis pool add-cache PacktStratisPool01 /dev/sdg
stratis pool add-cache PacktStratisPool01 /dev/sdg
stratis fs create PackStratisPool01 PacktStratisXFS01
mkdir /mnt/packtStratisXFS01
mount /stratis/PacktStratisPool01/PacktStratisXFS01 /mnt/packtStratisXFS01

这个挂载的文件系统是 XFS 格式的。然后我们可以通过 NFS 导出轻松地使用这个文件系统,这正是我们将在 NFS 存储课程中要做的。但现在,这只是一个使用 Stratis 创建池的示例。

我们已经介绍了本地存储池的一些基础知识,这使我们更接近我们下一个主题,即如何从 libvirt 的角度使用存储池。因此,这将是我们下一个主题。

Libvirt 存储池

Libvirt 管理自己的存储池,这是出于一个目的——为虚拟机磁盘和相关数据提供不同的存储池。考虑到 libvirt 使用底层操作系统支持的内容,它支持多种不同的存储池类型并不奇怪。一幅图值千言,这里有一个从 virt-manager 创建 libvirt 存储池的截图:

图 5.1 - libvirt 支持的不同存储池类型

图 5.1 - libvirt 支持的不同存储池类型

libvirt 已经预先定义了一个默认存储池,这是本地服务器上的一个目录存储池。此默认池位于/var/lib/libvirt/images目录中。这代表了我们将保存所有本地安装的虚拟机的数据的默认位置。

在接下来的几节中,我们将创建各种不同类型的存储池——基于 NFS 的存储池,基于 iSCSI 和 FC 的存储池,以及 Gluster 和 Ceph 存储池:全方位的。我们还将解释何时使用每一种存储池,因为涉及到不同的使用模型。

NFS 存储池

作为协议,NFS 自 80 年代中期以来就存在。最初由 Sun Microsystems 开发为共享文件的协议,直到今天仍在使用。实际上,它仍在不断发展,这对于一项如此“古老”的技术来说是相当令人惊讶的。例如,NFS 4.2 版本于 2016 年发布。在这个版本中,NFS 得到了很大的更新,例如以下内容:

  • 服务器端复制:通过在 NFS 服务器之间直接进行克隆操作,显著提高了克隆操作的速度

  • 稀疏文件和空间保留:增强了 NFS 处理具有未分配块的文件的方式,同时关注容量,以便在需要写入数据时保证空间可用性

  • 应用程序数据块支持:一项帮助与文件作为块设备(磁盘)工作的应用程序的功能

  • 更好的 pNFS 实现

v4.2 中还有其他一些增强的部分,但目前这已经足够了。您可以在 IETF 的 RFC 7862 文档中找到更多关于此的信息(tools.ietf.org/html/rfc7862)。我们将专注于 NFS v4.2 的实现,因为这是 NFS 目前提供的最好的版本。它也恰好是 CentOS 8 支持的默认 NFS 版本。

我们首先要做的事情是安装必要的软件包。我们将使用以下命令来实现这一点:

yum -y install nfs-utils
systemctl enable --now nfs-server

第一条命令安装了运行 NFS 服务器所需的实用程序。第二条命令将启动它并永久启用它,以便在重新启动后 NFS 服务可用。

我们接下来的任务是配置我们将通过 NFS 服务器共享的内容。为此,我们需要导出一个目录,并使其在网络上对我们的客户端可用。NFS 使用一个配置文件/etc/exports来实现这个目的。假设我们想要创建一个名为/exports的目录,然后将其共享给我们在192.168.159.0/255.255.255.0网络中的客户端,并且我们希望允许他们在该共享上写入数据。我们的/etc/exports文件应该如下所示:

/mnt/packtStratisXFS01	192.168.159.0/24(rw)
exportfs -r

这些配置选项告诉我们的 NFS 服务器要导出哪个目录(/exports),导出到哪些客户端(192.168.159.0/24),以及使用哪些选项(rw表示读写)。

其他可用选项包括以下内容:

  • ro:只读模式。

  • sync:同步 I/O 操作。

  • root_squash:来自UID 0GID 0的所有 I/O 操作都映射到可配置的匿名 UID 和 GID(anonuidanongid选项)。

  • all_squash:来自任何 UID 和 GID 的所有 I/O 操作都映射到匿名 UID 和 GID(anonuidanongid选项)。

  • no_root_squash:来自UID 0GID 0的所有 I/O 操作都映射到UID 0GID 0

如果您需要将多个选项应用到导出的目录中,可以在它们之间用逗号添加,如下所示:

/mnt/packtStratisXFS01	192.168.159.0/24(rw,sync,root_squash)

您可以使用完全合格的域名或短主机名(如果它们可以通过 DNS 或任何其他机制解析)。此外,如果您不喜欢使用前缀(24),您可以使用常规的网络掩码,如下所示:

/mnt/packtStratisXFS01 192.168.159.0/255.255.255.0(rw,root_squash)

现在我们已经配置了 NFS 服务器,让我们看看我们将如何配置 libvirt 来使用该服务器作为存储池。和往常一样,有几种方法可以做到这一点。我们可以只创建一个包含池定义的 XML 文件,并使用virsh pool-define --file命令将其导入到我们的 KVM 主机中。以下是该配置文件的示例:

图 5.2 - NFS 池的 XML 配置文件示例

图 5.2 - NFS 池的 XML 配置文件示例

让我们解释一下这些配置选项:

  • 池类型netfs表示我们将使用 NFS 文件共享。

  • name:池名称,因为 libvirt 使用池作为命名对象,就像虚拟网络一样。

  • host:我们正在连接的 NFS 服务器的地址。

  • dir path:我们在 NFS 服务器上通过/etc/exports配置的 NFS 导出路径。

  • path:我们的 KVM 主机上的本地目录,该 NFS 共享将被挂载到该目录。

  • permissions:用于挂载此文件系统的权限。

  • ownergroup:用于挂载目的的 UID 和 GID(这就是为什么我们之前使用no_root_squash选项导出文件夹的原因)。

  • label:此文件夹的 SELinux 标签-我们将在第十六章KVM 平台故障排除指南中讨论这个问题。

如果我们愿意,我们本可以通过虚拟机管理器 GUI 轻松地完成相同的事情。首先,我们需要选择正确的类型(NFS 池),并给它起一个名字:

图 5.3 - 选择 NFS 池类型并给它命名

图 5.3 - 选择 NFS 池类型并给它命名

点击前进后,我们可以进入最后的配置步骤,需要告诉向导我们从哪个服务器挂载我们的 NFS 共享:

图 5.4–配置 NFS 服务器选项

图 5.4–配置 NFS 服务器选项

当我们完成输入这些配置选项(主机名源路径)后,我们可以点击完成,这意味着退出向导。此外,我们之前的配置屏幕,只包含默认存储池,现在也列出了我们新配置的存储池:

图 5.5–新配置的 NFS 存储池在列表中可见

图 5.5–新配置的 NFS 存储池在列表中可见

我们何时在 libvirt 中使用基于 NFS 的存储池,以及为什么?基本上,我们可以很好地用它们来存储安装映像的任何相关内容——ISO 文件、虚拟软盘文件、虚拟机文件等等。

请记住,尽管似乎 NFS 在企业环境中几乎已经消失了一段时间,但 NFS 仍然存在。实际上,随着 NFS 4.1、4.2 和 pNFS 的引入,它在市场上的未来实际上看起来比几年前更好。这是一个非常熟悉的协议,有着非常悠久的历史,在许多场景中仍然具有竞争力。如果您熟悉 VMware 虚拟化技术,VMware 在 ESXi 6.0 中引入了一种称为虚拟卷的技术。这是一种基于对象的存储技术,可以同时使用基于块和 NFS 的协议作为其基础,这对于某些场景来说是一个非常引人注目的用例。但现在,让我们转向块级技术,比如 iSCSI 和 FC。

iSCSI 和 SAN 存储

长期以来,使用 iSCSI 进行虚拟机存储一直是常规做法。即使考虑到 iSCSI 并不是处理存储的最有效方式这一事实,它仍然被广泛接受,你会发现它无处不在。效率受到两个原因的影响:

  • iSCSI 将 SCSI 命令封装成常规 IP 数据包,这意味着 IP 数据包有一个相当大的头部,这意味着分段和开销,这意味着效率较低。

  • 更糟糕的是,它是基于 TCP 的,这意味着有序号和重传,这可能导致排队和延迟,而且环境越大,你通常会感觉到这些影响对虚拟机性能的影响越大。

也就是说,它基于以太网堆栈,使得部署基于 iSCSI 的解决方案更容易,同时也提供了一些独特的挑战。例如,有时很难向客户解释,在虚拟机流量和 iSCSI 流量使用相同的网络交换机并不是最好的主意。更糟糕的是,客户有时会因为渴望节省金钱而无法理解他们正在违背自己的最佳利益。特别是在涉及网络带宽时。我们大多数人都曾经历过这种情况,试图回答客户的问题,比如“但我们已经有了千兆以太网交换机,为什么你需要比这更快的东西呢?”

事实是,对于 iSCSI 的复杂性来说,更多就意味着更多。在磁盘/缓存/控制器方面拥有更快的速度,以及在网络方面拥有更多的带宽,就有更多的机会创建一个更快的存储系统。所有这些都可能对我们的虚拟机性能产生重大影响。正如您将在存储冗余和多路径部分中看到的那样,您实际上可以自己构建一个非常好的存储系统——无论是对于 iSCSI 还是 FC。当您尝试创建某种测试实验室/环境来发展您的 KVM 虚拟化技能时,这可能会非常有用。您可以将这些知识应用到其他虚拟化环境中。

iSCSI 和 FC 架构非常相似 - 它们都需要一个目标(iSCSI 目标和 FC 目标)和一个发起者(iSCS 发起者和 FC 发起者)。在这个术语中,目标是服务器组件,发起者是客户端组件。简单地说,发起者连接到目标以访问通过该目标呈现的块存储。然后,我们可以使用发起者的身份来限制发起者在目标上能够看到的内容。这就是当比较 iSCSI 和 FC 时术语开始有点不同的地方。

在 iSCSI 中,发起者的身份可以由四个不同的属性来定义。它们如下:

  • iSCSI 合格名称IQN):这是所有发起者和目标在 iSCSI 通信中都具有的唯一名称。我们可以将其与常规以太网网络中的 MAC 或 IP 地址进行比较。您可以这样想 - 对于以太网网络来说,IQN 就是 iSCSI 的 MAC 或 IP 地址。

  • IP 地址:每个发起者都有一个不同的 IP 地址,用于连接到目标。

  • MAC 地址:每个发起者在第 2 层都有一个不同的 MAC 地址。

  • 完全合格的域名FQDN):这代表了服务器的名称,它是由 DNS 服务解析的。

从 iSCSI 目标的角度来看 - 根据其实现方式 - 您可以使用这些属性中的任何一个来创建一个配置,该配置将告诉 iSCSI 目标可以使用哪些 IQN、IP 地址、MAC 地址或 FQDN 来连接到它。这就是所谓的掩码,因为我们可以通过使用这些身份并将它们与 LUN 配对来掩盖发起者在 iSCSI 目标上可以看到的内容。LUN 只是我们通过 iSCSI 目标向发起者导出的原始块容量。LUN 通常是索引编号的,通常从 0 开始。每个 LUN 编号代表发起者可以连接到的不同存储容量。

例如,我们可以有一个 iSCSI 目标,其中包含三个不同的 LUN - LUN0,容量为 20 GB,LUN1,容量为 40 GB,和LUN2,容量为 60 GB。这些都将托管在同一存储系统的 iSCSI 目标上。然后,我们可以配置 iSCSI 目标以接受一个 IQN 来查看所有 LUN,另一个 IQN 只能看到LUN1,另一个 IQN 只能看到LUN1LUN2。这实际上就是我们现在要配置的。

让我们从配置 iSCSI 目标服务开始。为此,我们需要安装targetcli软件包,并配置服务(称为target)运行:

yum -y install targetcli
systemctl enable --now target

要注意防火墙配置;您可能需要配置它以允许在端口3260/tcp上进行连接,这是 iSCSI 目标门户使用的端口。因此,如果您的防火墙已启动,请输入以下命令:

firewall-cmd --permanent --add-port=3260/tcp ; firewall-cmd --reload

在 Linux 上,关于使用什么存储后端的 iSCSI 有三种可能性。我们可以使用常规文件系统(如 XFS)、块设备(硬盘)或 LVM。所以,这正是我们要做的。我们的情景将如下所示:

  • LUN0(20 GB):基于 XFS 的文件系统,位于/dev/sdb设备上

  • LUN1(40 GB):硬盘驱动器,位于/dev/sdc设备上

  • LUN2(60 GB):LVM,位于/dev/sdd设备上

因此,在安装必要的软件包并配置目标服务和防火墙之后,我们应该开始配置我们的 iSCSI 目标。我们只需启动targetcli命令并检查状态,因为我们刚刚开始这个过程,状态应该是空白的:

图 5.6 - targetcli 的起点 - 空配置

图 5.6 - targetcli 的起点 - 空配置

让我们从逐步的过程开始:

  1. 因此,让我们配置基于 XFS 的文件系统,并配置LUN0文件映像保存在那里。首先,我们需要对磁盘进行分区(在我们的情况下是/dev/sdb):图 5.7 - 为 XFS 文件系统分区

图 5.7 - 为 XFS 文件系统分区/dev/sdb

  1. 接下来是格式化这个分区,创建并使用一个名为/LUN0的目录来挂载这个文件系统,并提供我们的LUN0镜像,我们将在接下来的步骤中进行配置:图 5.8 - 格式化 XFS 文件系统,创建目录,并将其挂载到该目录

图 5.8 - 格式化 XFS 文件系统,创建目录,并将其挂载到该目录

  1. 下一步是配置targetcli,使其创建LUN0并为LUN0分配一个镜像文件,该文件将保存在/LUN0目录中。首先,我们需要启动targetcli命令:图 5.9 - 创建 iSCSI 目标,LUN0,并将其作为文件托管

图 5.9 - 创建 iSCSI 目标,LUN0,并将其作为文件托管

  1. 接下来,让我们配置一个基于块设备的 LUN 后端— LUN2—它将使用/dev/sdc1(使用前面的示例创建分区)并检查当前状态:

图 5.10 - 创建 LUN1,直接从块设备托管

图 5.10 - 创建 LUN1,直接从块设备托管

因此,LUN0LUN1及其各自的后端现在已配置完成。让我们通过配置 LVM 来完成这些事情:

  1. 首先,我们将准备 LVM 的物理卷,从该卷创建一个卷组,并显示有关该卷组的所有信息,以便我们可以看到我们有多少空间可用于LUN2图 5.11 - 为 LVM 配置物理卷,构建卷组,并显示有关该卷组的信息和显示有关该卷组的信息

图 5.11 - 为 LVM 配置物理卷,构建卷组,并显示有关该卷组的信息

  1. 下一步是实际创建逻辑卷,这将是我们 iSCSI 目标中LUN2的块存储设备后端。我们可以从vgdisplay输出中看到我们有 15,359 个 4MB 块可用,所以让我们用它来创建我们的逻辑卷,称为LUN2。转到targetcli并配置LUN2的必要设置:图 5.12 - 使用 LVM 后端配置 LUN2

图 5.12 - 使用 LVM 后端配置 LUN2

  1. 让我们停在这里,转而转到 KVM 主机(iSCSI 发起者)的配置。首先,我们需要安装 iSCSI 发起者,这是一个名为iscsi-initiator-utils的软件包的一部分。因此,让我们使用yum命令来安装它:
yum -y install iscsi-initiator-utils
  1. 接下来,我们需要配置我们发起者的 IQN。通常我们希望这个名称能让人联想到主机名,所以,看到我们主机的 FQDN 是PacktStratis01,我们将使用它来配置 IQN。为了做到这一点,我们需要编辑/etc/iscsi/initiatorname.iscsi文件并配置InitiatorName选项。例如,让我们将其设置为iqn.2019-12.com.packt:PacktStratis01/etc/iscsi/initiatorname.iscsi文件的内容应该如下所示:
InitiatorName=iqn.2019-12.com.packt:PacktStratis01
  1. 现在这已经配置好了,让我们回到 iSCSI 目标并创建一个访问控制列表ACL)。ACL 将允许我们的 KVM 主机发起者连接到 iSCSI 目标门户:图 5.13 - 创建 ACL,以便 KVM 主机的发起者可以连接到 iSCSI 目标

图 5.13 - 创建 ACL,以便 KVM 主机的发起者可以连接到 iSCSI 目标

  1. 接下来,我们需要将我们预先创建的基于文件和基于块的设备发布到 iSCSI 目标 LUNs。因此,我们需要这样做:

图 5.14 - 将我们的基于文件和基于块的设备添加到 iSCSI 目标 LUNs 0、1 和 2

图 5.14 - 将我们的基于文件和基于块的设备添加到 iSCSI 目标 LUNs 0、1 和 2

最终结果应该如下所示:

图 5.15 - 最终结果

图 5.15 - 最终结果

此时,一切都已配置好。我们需要回到我们的 KVM 主机,并定义一个将使用这些 LUN 的存储池。做到这一点最简单的方法是使用一个 XML 配置文件来定义池。因此,这是我们的示例配置 XML 文件;我们将称其为iSCSIPool.xml

<pool type='iscsi'>
  <name>MyiSCSIPool</name>
  <source>
    <host name='192.168.159.145'/>
    <device path='iqn.2003-01.org.linux-iscsi.packtiscsi01.x8664:sn.7b3c2efdbb11'/>
  </source>
  <initiator>
   <iqn name='iqn.2019-12.com.packt:PacktStratis01' />
</initiator>
  <target>
    <path>/dev/disk/by-path</path>
  </target>
</pool>

让我们一步一步地解释这个文件:

  • 池类型= 'iscsi':我们告诉 libvirt 这是一个 iSCSI 池。

  • 名称:池名称。

  • 主机名:iSCSI 目标的 IP 地址。

  • 设备路径:iSCSI 目标的 IQN。

  • 发起者部分的 IQN 名称:发起者的 IQN。

  • 目标路径:iSCSI 目标的 LUN 将被挂载的位置。

现在,我们所要做的就是定义、启动和自动启动我们的新的基于 iSCSI 的 KVM 存储池:

virsh pool-define --file iSCSIPool.xml
virsh pool-start --pool MyiSCSIPool
virsh pool-autostart --pool MyiSCSIPool

配置的目标路径部分可以通过virsh轻松检查。如果我们在 KVM 主机上输入以下命令,我们将得到刚刚配置的MyiSCSIPool池中可用 LUN 的列表:

virsh vol-list --pool MyiSCSIPool

我们对此命令得到以下结果:

图 5.16 - 我们 iSCSI 池 LUN 的运行时名称

图 5.16 - 我们 iSCSI 池 LUN 的运行时名称

如果这个输出让你有点想起 VMware vSphere Hypervisor 存储运行时名称,那么你肯定是对的。当我们开始部署我们的虚拟机时,我们将能够在第七章虚拟机-安装、配置和生命周期管理中使用这些存储池。

存储冗余和多路径

冗余是 IT 的关键词之一,任何单个组件的故障都可能对公司或其客户造成重大问题。避免 SPOF 的一般设计原则是我们应该始终坚持的。归根结底,没有任何网络适配器、电缆、交换机、路由器或存储控制器会永远工作。因此,将冗余计算到我们的设计中有助于我们的 IT 环境在其正常生命周期内。

同时,冗余可以与多路径结合,以确保更高的吞吐量。例如,当我们将物理主机连接到具有每个四个 FC 端口的两个控制器的 FC 存储时,我们可以使用四条路径(如果存储是主备的)或八条路径(如果是主动-主动的)连接到从存储设备导出给主机的相同 LUN(s)。这为我们提供了多种额外的 LUN 访问选项,除了在故障情况下为我们提供更多的可用性外。

让一个普通的 KVM 主机执行,例如 iSCSI 多路径,是相当复杂的。在文档方面存在多个配置问题和空白点,这种配置的支持性是值得怀疑的。然而,有一些使用 KVM 的产品可以直接支持,比如 oVirt(我们之前介绍过)和Red Hat 企业虚拟化 HypervisorRHEV-H)。因此,让我们在 iSCSI 的例子中使用 oVirt。

在你这样做之前,请确保你已经完成了以下工作:

  • 您的 Hypervisor 主机已添加到 oVirt 清单中。

  • 您的 Hypervisor 主机有两个额外的网络卡,独立于管理网络。

  • iSCSI 存储在与两个额外的 Hypervisor 网络卡相同的 L2 网络中有两个额外的网络卡。

  • iSCSI 存储已经配置好,至少有一个目标和一个 LUN 已经配置好,这样就能使 Hypervisor 主机连接到它。

因此,当我们在 oVirt 中进行这项工作时,有一些事情是我们需要做的。首先,从网络的角度来看,为存储创建一些存储网络是一个好主意。在我们的情况下,我们将为 iSCSI 分配两个网络,并将它们称为iSCSI01iSCSI02。我们需要打开 oVirt 管理面板,悬停在iSCSI01(第一个)上,取消选中iSCSI02网络:

图 5.17 - 配置 iSCSI 绑定网络

图 5.17 - 配置 iSCSI 绑定网络

下一步是将这些网络分配给主机网络适配器。转到compute/hosts,双击您添加到 oVirt 清单的主机,选择第二个网络接口上的iSCSI01和第三个网络接口上的iSCSI02。第一个网络接口已被 oVirt 管理网络占用。它应该看起来像这样:

图 5.18 - 将虚拟网络分配给 hypervisor 的物理适配器

图 5.18 - 将虚拟网络分配给 hypervisor 的物理适配器

在关闭窗口之前,请确保单击iSCSI01iSCSI02上的铅笔图标,为这两个虚拟网络设置 IP 地址。分配可以将您连接到相同或不同子网上的 iSCSI 存储的网络配置:

图 5.19 - 在数据中心级别创建 iSCSI 绑定

图 5.19 - 在数据中心级别创建 iSCSI 绑定

您刚刚配置了一个 iSCSI 绑定。我们配置的最后一部分是启用它。同样,在 oVirt GUI 中,转到计算 | 数据中心,双击选择您的数据中心,然后转到iSCSI 多路径选项卡:

图 5.20 - 在数据中心级别配置 iSCSI 多路径

图 5.20 - 在数据中心级别配置 iSCSI 多路径

在弹出窗口的顶部部分单击iSCSI01iSCSI02网络,然后在底部单击 iSCSI 目标。

现在我们已经介绍了存储池、NFS 和 iSCSI 的基础知识,我们可以继续使用标准的开源方式部署存储基础设施,即使用 Gluster 和/或 Ceph。

Gluster 和 Ceph 作为 KVM 的存储后端

还有其他高级类型的文件系统可以用作 libvirt 存储后端。因此,让我们现在讨论其中的两种 - Gluster 和 Ceph。稍后,我们还将检查 libvirt 如何与 GFS2 一起使用。

Gluster

Gluster 是一个经常用于高可用性场景的分布式文件系统。它相对于其他文件系统的主要优势在于,它是可扩展的,可以使用复制和快照,可以在任何服务器上工作,并且可用作共享存储的基础,例如通过 NFS 和 SMB。它是由一家名为 Gluster Inc.的公司开发的,该公司于 2011 年被 RedHat 收购。然而,与 Ceph 不同,它是一个文件存储服务,而 Ceph 提供对象为基础的存储。基于对象的存储对于基于块的设备意味着直接的二进制存储,直接到 LUN。这里没有涉及文件系统,理论上意味着由于没有文件系统、文件系统表和其他可能减慢 I/O 过程的构造,因此开销更小。

让我们首先配置 Gluster 以展示其在 libvirt 中的用途。在生产中,这意味着安装至少三台 Gluster 服务器,以便我们可以实现高可用性。Gluster 配置非常简单,在我们的示例中,我们将创建三台 CentOS 7 机器,用于托管 Gluster 文件系统。然后,我们将在我们的 hypervisor 主机上挂载该文件系统,并将其用作本地目录。我们可以直接从 libvirt 使用 GlusterFS,但是实现方式并不像通过 gluster 客户端服务使用它、将其挂载为本地目录并直接在 libvirt 中使用它作为目录池那样精致。

我们的配置将如下所示:

图 5.21 - 我们 Gluster 集群的基本设置

图 5.21 - 我们 Gluster 集群的基本设置

因此,让我们投入生产。在配置 Gluster 并将其暴露给我们的 KVM 主机之前,我们必须在所有服务器上发出一系列大量的命令。让我们从gluster1开始。首先,我们将进行系统范围的更新和重启,以准备 Gluster 安装的核心操作系统。在所有三台 CentOS 7 服务器上输入以下命令:

yum -y install epel-release*
yum -y install centos-release-gluster7.noarch
yum -y update
yum -y install glusterfs-server
systemctl reboot

然后,我们可以开始部署必要的存储库和软件包,格式化磁盘,配置防火墙等。在所有服务器上输入以下命令:

mkfs.xfs /dev/sdb
mkdir /gluster/bricks/1 -p
echo '/dev/sdb /gluster/bricks/1 xfs defaults 0 0' >> /etc/fstab
mount -a
mkdir /gluster/bricks/1/brick
systemctl disable firewalld
systemctl stop firewalld
systemctl start glusterd
systemctl enable glusterd

我们还需要进行一些网络配置。如果这三台服务器可以相互解析,那将是很好的,这意味着要么配置一个 DNS 服务器,要么在我们的/etc/hosts文件中添加几行。我们选择后者。将以下行添加到您的/etc/hosts文件中:

192.168.159.147 gluster1
192.168.159.148 gluster2
192.168.159.149 gluster3

在配置的下一部分,我们只需登录到第一台服务器,并将其用作我们的 Gluster 基础设施的事实管理服务器。输入以下命令:

gluster peer probe gluster1
gluster peer probe gluster2
gluster peer probe gluster3
gluster peer status

前三个命令应该让您得到peer probe: success状态。第三个应该返回类似于这样的输出:

图 5.22 - 确认 Gluster 服务器成功对等

图 5.22 - 确认 Gluster 服务器成功对等

现在配置的这一部分已经完成,我们可以创建一个 Gluster 分布式文件系统。我们可以通过输入以下命令序列来实现这一点:

gluster volume create kvmgluster replica 3 \ gluster1:/gluster/bricks/1/brick gluster2:/gluster/bricks/1/brick \ gluster3:/gluster/bricks/1/brick 
gluster volume start kvmgluster
gluster volume set kvmgluster auth.allow 192.168.159.0/24
gluster volume set kvmgluster allow-insecure on
gluster volume set kvmgluster storage.owner-uid 107
gluster volume set kvmgluster storage.owner-gid 107

然后,我们可以将 Gluster 挂载为 NFS 目录进行测试。例如,我们可以为所有成员主机(gluster1gluster2gluster3)创建一个名为kvmgluster的分布式命名空间。我们可以通过使用以下命令来实现这一点:

echo 'localhost:/kvmgluster /mnt glusterfs \ defaults,_netdev,backupvolfile-server=localhost 0 0' >> /etc/fstab
mount.glusterfs localhost:/kvmgluster /mnt

Gluster 部分现在已经准备就绪,所以我们需要回到我们的 KVM 主机,并通过输入以下命令将 Gluster 文件系统挂载到它上面:

wget \ https://download.gluster.org/pub/gluster/glusterfs/6/LATEST/CentOS/gl\ usterfs-rhel8.repo -P /etc/yum.repos.d
yum install glusterfs glusterfs-fuse attr -y
mount -t glusterfs -o context="system_u:object_r:virt_image_t:s0" \ gluster1:/kvmgluster /var/lib/libvirt/images/GlusterFS 

我们必须密切关注服务器和客户端上的 Gluster 版本,这就是为什么我们下载了 CentOS 8 的 Gluster 存储库信息(我们正在 KVM 服务器上使用它),并安装了必要的 Gluster 客户端软件包。这使我们能够使用最后一个命令挂载文件系统。

现在我们已经完成了配置,我们只需要将这个目录作为 libvirt 存储池添加进去。让我们通过使用一个包含以下条目的存储池定义的 XML 文件来做到这一点:

<pool type='dir'>
  <name>glusterfs-pool</name>
  <target>
    <path>/var/lib/libvirt/images/GlusterFS</path>
    <permissions>
      <mode>0755</mode>
      <owner>107</owner>
      <group>107</group>
      <label>system_u:object_r:virt_image_t:s0</label>
    </permissions>
  </target>
</pool> 

假设我们将这个文件保存在当前目录,并且文件名为gluster.xml。我们可以通过使用以下virsh命令将其导入并在 libvirt 中启动:

virsh pool-define --file gluster.xml
virsh pool-start --pool glusterfs-pool
virsh pool-autostart --pool glusterfs-pool

我们应该在启动时自动挂载这个存储池,以便 libvirt 可以使用它。因此,我们需要将以下行添加到/etc/fstab中:

gluster1:/kvmgluster       /var/lib/libvirt/images/GlusterFS \ glusterfs   defaults,_netdev  0  0

使用基于目录的方法使我们能够避免 libvirt(及其 GUI 界面virt-manager)在 Gluster 存储池方面存在的两个问题:

  • 我们可以使用 Gluster 的故障转移功能,这将由我们直接安装的 Gluster 实用程序自动管理,因为 libvirt 目前还不支持它们。

  • 我们将避免手动创建虚拟机磁盘,这是 libvirt 对 Gluster 支持的另一个限制,而基于目录的存储池则可以无任何问题地支持它。

我们提到故障转移似乎有点奇怪,因为似乎我们没有将它作为任何之前步骤的一部分进行配置。实际上,我们已经配置了。当我们发出最后一个挂载命令时,我们使用了 Gluster 的内置模块来建立与第一个Gluster 服务器的连接。这反过来意味着在建立这个连接之后,我们得到了关于整个 Gluster 池的所有细节,我们配置了它以便它托管在三台服务器上。如果发生任何故障—我们可以很容易地模拟—这个连接将继续工作。例如,我们可以通过关闭任何一个 Gluster 服务器来模拟这种情况—比如gluster1。您会看到我们挂载 Gluster 目录的本地目录仍然可以工作,即使gluster1已经关闭。让我们看看它的运行情况(默认超时时间为 42 秒):

图 5.23 - Gluster 故障转移工作;第一个节点已经关闭,但我们仍然能够获取我们的文件

图 5.23 - Gluster 故障转移工作;第一个节点已经关闭,但我们仍然能够获取我们的文件

如果我们想更积极一些,可以通过在任何一个 Gluster 服务器上发出以下命令来将此超时期缩短到——例如——2 秒:

gluster volume set kvmgluster network.ping-timeout number

number部分是以秒为单位的,通过分配一个较低的数字,我们可以直接影响故障切换过程的积极性。

因此,现在一切都配置好了,我们可以开始使用 Gluster 池部署虚拟机,我们将在第七章中进一步讨论,虚拟机-安装、配置和生命周期管理

鉴于 Gluster 是一个基于文件的后端,可以用于 libvirt,自然而然地需要描述如何使用高级块级和对象级存储后端。这就是 Ceph 的用武之地,所以让我们现在来处理这个问题。

Ceph

Ceph 可以作为文件、块和对象存储。但在大多数情况下,我们通常将其用作块或对象存储。同样,这是一款设计用于任何服务器(或虚拟机)的开源软件。在其核心,Ceph 运行一个名为可控复制下可扩展哈希CRUSH)的算法。该算法试图以伪随机的方式在对象设备之间分发数据,在 Ceph 中,它由一个集群映射(CRUSH 映射)管理。我们可以通过添加更多节点轻松扩展 Ceph,这将以最小的方式重新分发数据,以确保尽可能少的复制。

一个名为可靠自主分布式对象存储RADOS)的内部 Ceph 组件用于快照、复制和薄配置。这是一个由加利福尼亚大学开发的开源项目。

在架构上,Ceph 有三个主要服务:

  • ceph-mon:用于集群监控、CRUSH 映射和对象存储守护程序OSD)映射。

  • ceph-osd:处理实际数据存储、复制和恢复。至少需要两个节点;出于集群化的原因,我们将使用三个。

  • ceph-mds:元数据服务器,在 Ceph 需要文件系统访问时使用。

根据最佳实践,确保您始终在设计 Ceph 环境时牢记关键原则——所有数据节点需要具有相同的配置。这意味着相同数量的内存、相同的存储控制器(如果可能的话,不要使用 RAID 控制器,只使用普通的 HBA 而不带 RAID 固件)、相同的磁盘等。这是确保您的环境中 Ceph 性能保持恒定水平的唯一方法。

Ceph 的一个非常重要的方面是数据放置和放置组的工作原理。放置组为我们提供了将创建的对象分割并以最佳方式放置在 OSD 中的机会。换句话说,我们配置的放置组数量越大,我们将获得的平衡就越好。

因此,让我们从头开始配置 Ceph。我们将再次遵循最佳实践,并使用五台服务器部署 Ceph——一台用于管理,一台用于监控,三个 OSD。

我们的配置将如下所示:

图 5.24 - 我们基础设施的基本 Ceph 配置

图 5.24 - 我们基础设施的基本 Ceph 配置

确保这些主机可以通过 DNS 或/etc/hosts相互解析,并配置它们都使用相同的 NTP 源。确保通过以下方式更新所有主机:

yum -y update; reboot

此外,请确保您以root用户身份在所有主机上输入以下命令。让我们从部署软件包、创建管理员用户并赋予他们sudo权限开始:

rpm -Uhv http://download.ceph.com/rpm-jewel/el7/noarch/ceph-release-1-1.el7.noarch.rpm
yum -y install ceph-deploy ceph ceph-radosgw
useradd cephadmin
echo "cephadmin:ceph123" | chpasswd
echo "cephadmin ALL = (root) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/cephadmin
chmod 0440 /etc/sudoers.d/cephadmin

对于这个演示来说,禁用 SELinux 会让我们的生活更轻松,摆脱防火墙也是如此。

sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
systemctl stop firewalld
systemctl disable firewalld
systemctl mask firewalld

让我们将主机名添加到/etc/hosts中,以便我们更容易进行管理:

echo "192.168.159.150 ceph-admin" >> /etc/hosts
echo "192.168.159.151 ceph-monitor" >> /etc/hosts
echo "192.168.159.152 ceph-osd1" >> /etc/hosts
echo "192.168.159.153 ceph-osd2" >> /etc/hosts
echo "192.168.159.154 ceph-osd3" >> /etc/hosts

更改最后的echo部分以适应您的环境 - 主机名和 IP 地址。我们只是在这里举例说明。下一步是确保我们可以使用我们的管理主机连接到所有主机。最简单的方法是使用 SSH 密钥。因此,在ceph-admin上,以 root 身份登录并输入ssh-keygen命令,然后一直按Enter键。它应该看起来像这样:

图 5.25-为 Ceph 设置目的为 root 生成 SSH 密钥

图 5.25-为 Ceph 设置目的为 root 生成 SSH 密钥

我们还需要将此密钥复制到所有主机。因此,再次在ceph-admin上,使用ssh-copy-id将密钥复制到所有主机:

ssh-copy-id cephadmin@ceph-admin
ssh-copy-id cephadmin@ceph-monitor
ssh-copy-id cephadmin@ceph-osd1
ssh-copy-id cephadmin@ceph-osd2
ssh-copy-id cephadmin@ceph-osd3

当 SSH 询问您时,请接受所有密钥,并使用我们在较早步骤中选择的ceph123作为密码。完成所有这些之后,在我们开始部署 Ceph 之前,ceph-admin还有最后一步要做 - 我们必须配置 SSH 以使用cephadmin用户作为默认用户登录到所有主机。我们将通过以 root 身份转到ceph-admin上的.ssh目录,并创建一个名为config的文件,并添加以下内容来完成这一步:

Host ceph-admin
        Hostname ceph-admin
        User cephadmin
Host ceph-monitor
        Hostname ceph-monitor
        User cephadmin
Host ceph-osd1
        Hostname ceph-osd1
        User cephadmin
Host ceph-osd2
        Hostname ceph-osd2
        User cephadmin
Host ceph-osd3
        Hostname ceph-osd3
        User cephadmin

那是一个很长的预配置,不是吗?现在是时候真正开始部署 Ceph 了。第一步是配置ceph-monitor。因此,在ceph-admin上输入以下命令:

cd /root
mkdir cluster
cd cluster
ceph-deploy new ceph-monitor

由于我们选择了一个配置,其中有三个 OSD,我们需要配置 Ceph 以便使用这另外两个主机。因此,在cluster目录中,编辑名为ceph.conf的文件,并在末尾添加以下两行:

public network = 192.168.159.0/24
osd pool default size = 2

这将确保我们只能使用我们的示例网络(192.168.159.0/24)进行 Ceph,并且我们在原始的基础上有两个额外的 OSD。

现在一切准备就绪,我们必须发出一系列命令来配置 Ceph。因此,再次在ceph-admin上输入以下命令:

ceph-deploy install ceph-admin ceph-monitor ceph-osd1 ceph-osd2 ceph-osd3
ceph-deploy mon create-initial
ceph-deploy gatherkeys ceph-monitor
ceph-deploy disk list ceph-osd1 ceph-osd2 ceph-osd3
ceph-deploy disk zap ceph-osd1:/dev/sdb  ceph-osd2:/dev/sdb  ceph-osd3:/dev/sdb
ceph-deploy osd prepare ceph-osd1:/dev/sdb ceph-osd2:/dev/sdb ceph-osd3:/dev/sdb
ceph-deploy osd activate ceph-osd1:/dev/sdb1 ceph-osd2:/dev/sdb1 ceph-osd3:/dev/sdb1

让我们逐一描述这些命令:

  • 第一条命令启动实际的部署过程 - 用于管理、监视和 OSD 节点的安装所有必要的软件包。

  • 第二个和第三个命令配置监视主机,以便它准备好接受外部连接。

  • 这两个磁盘命令都是关于磁盘准备 - Ceph 将清除我们分配给它的磁盘(每个 OSD 主机的/dev/sdb)并在上面创建两个分区,一个用于 Ceph 数据,一个用于 Ceph 日志。

  • 最后两个命令准备这些文件系统供使用并激活 Ceph。如果您的ceph-deploy脚本在任何时候停止,请检查您的 DNS 和/etc/hostsfirewalld配置,因为问题通常出现在那里。

我们需要将 Ceph 暴露给我们的 KVM 主机,这意味着我们需要进行一些额外的配置。我们将 Ceph 公开为对象池给我们的 KVM 主机,因此我们需要创建一个池。让我们称之为KVMpool。连接到ceph-admin,并发出以下命令:

ceph osd pool create KVMpool 128 128

此命令将创建一个名为KVMpool的池,其中包含 128 个放置组。

下一步涉及从安全角度接近 Ceph。我们不希望任何人连接到这个池,因此我们将为 Ceph 创建一个用于身份验证的密钥,我们将在 KVM 主机上用于身份验证。我们通过输入以下命令来做到这一点:

ceph auth get-or-create client.KVMpool mon 'allow r' osd 'allow rwx pool=KVMpool'

它将向我们抛出一个状态消息,类似于这样:

key = AQB9p8RdqS09CBAA1DHsiZJbehb7ZBffhfmFJQ==

然后我们可以切换到 KVM 主机,在那里我们需要做两件事:

  • 定义一个秘密 - 一个将 libvirt 链接到 Ceph 用户的对象 - 通过这样做,我们将创建一个带有其通用唯一标识符UUID)的秘密对象。

  • 在定义 Ceph 存储池时,使用该秘密的 UUID 将其与 Ceph 密钥进行关联。

完成这两个步骤的最简单方法是使用两个 libvirt 的 XML 配置文件。因此,让我们创建这两个文件。让我们称第一个为secret.xml,以下是其内容:

   <secret ephemeral='no' private='no'>
   <usage type='ceph'>
     <name>client.KVMpool secret</name>
   </usage>
</secret>

确保您保存并导入此 XML 文件,输入以下命令:

virsh secret-define --file secret.xml

按下Enter键后,此命令将抛出一个 UUID。请将该 UUID 复制并粘贴到一个安全的地方,因为我们将需要它用于池 XML 文件。在我们的环境中,这个第一个virsh命令抛出了以下输出:

Secret 95b1ed29-16aa-4e95-9917-c2cd4f3b2791 created

我们需要为这个秘密分配一个值,这样当 libvirt 尝试使用这个秘密时,它就知道要使用哪个密码。这实际上是我们在 Ceph 级别创建的密码,当我们使用ceph auth get-create时,它会给我们抛出密钥。因此,现在我们既有秘密 UUID 又有 Ceph 密钥,我们可以将它们结合起来创建一个完整的认证对象。在 KVM 主机上,我们需要输入以下命令:

virsh secret-set-value 95b1ed29-16aa-4e95-9917-c2cd4f3b2791 AQB9p8RdqS09CBAA1DHsiZJbehb7ZBffhfmFJQ==

现在,我们可以创建 Ceph 池文件。让我们把配置文件命名为ceph.xml,以下是它的内容:

   <pool type="rbd">
     <source>
       <name>KVMpool</name>
       <host name='192.168.159.151' port='6789'/>
       <auth username='KVMpool' type='ceph'>
         <secret uuid='95b1ed29-16aa-4e95-9917-c2cd4f3b2791'/>
       </auth>
     </source>
   </pool>

因此,上一步的 UUID 被用于这个文件中,用来引用哪个秘密(身份)将被用于 Ceph 池访问。现在,如果我们想要永久使用它(在 KVM 主机重新启动后),我们需要执行标准程序——导入池,启动它,并自动启动它。因此,让我们在 KVM 主机上使用以下命令序列来执行:

virsh pool-define --file ceph.xml
virsh pool-start KVMpool
virsh pool-autostart KVMpool
virsh pool-list --details

最后一个命令应该产生类似于这样的输出:

图 5.26-检查我们的池的状态;Ceph 池已配置并准备好使用

图 5.26-检查我们的池的状态;Ceph 池已配置并准备好使用

现在,Ceph 对象池对我们的 KVM 主机可用,我们可以在其上安装虚拟机。我们将在第七章中再次进行这项工作——虚拟机-安装、配置和生命周期管理

虚拟磁盘镜像和格式以及基本的 KVM 存储操作

磁盘镜像是存储在主机文件系统上的标准文件。它们很大,作为客人的虚拟硬盘。您可以使用dd命令创建这样的文件,如下所示:

# dd if=/dev/zero of=/vms/dbvm_disk2.img bs=1G count=10

以下是这个命令的翻译:

从输入文件(if/dev/zero(几乎无限的零)复制数据(dd)到输出文件(of/vms/dbvm_disk2.img(磁盘镜像),使用 1G 大小的块(bs = 块大小),并重复这个操作(count)只一次(10)。

重要提示:

dd被认为是一个耗费资源的命令。它可能会在主机系统上引起 I/O 问题,因此最好先检查主机系统的可用空闲内存和 I/O 状态,然后再运行它。如果系统已经加载,降低块大小到 MB,并增加计数以匹配您想要的文件大小(使用bs=1Mcount=10000,而不是bs=1Gcount=10)。

/vms/dbvm_disk2.img是前面命令的结果。该镜像现在已经预分配了 10GB,并准备好与客人一起使用,无论是作为引导磁盘还是第二个磁盘。同样,您也可以创建薄配置的磁盘镜像。预分配和薄配置(稀疏)是磁盘分配方法,或者您也可以称之为格式:

  • 预分配:预分配的虚拟磁盘在创建时立即分配空间。这通常意味着比薄配置的虚拟磁盘写入速度更快。

  • dd命令中的seek选项,如下所示:

dd if=/dev/zero of=/vms/dbvm_disk2_seek.imgbs=1G seek=10 count=0

每种方法都有其优缺点。如果您正在寻求 I/O 性能,选择预分配格式,但如果您有非 I/O 密集型负载,请选择薄配置。

现在,您可能想知道如何识别某个虚拟磁盘使用了什么磁盘分配方法。有一个很好的实用程序可以找出这一点:qemu-img。这个命令允许您读取虚拟镜像的元数据。它还支持创建新的磁盘和执行低级格式转换。

获取镜像信息

qemu-img命令的info参数显示有关磁盘镜像的信息,包括镜像的绝对路径、文件格式和虚拟和磁盘大小。通过从 QEMU 的角度查看虚拟磁盘大小,并将其与磁盘上的镜像文件大小进行比较,您可以轻松地确定正在使用的磁盘分配策略。例如,让我们看一下我们创建的两个磁盘镜像:

# qemu-img info /vms/dbvm_disk2.img
image: /vms/dbvm_disk2.img
file format: raw
virtual size: 10G (10737418240 bytes)
disk size: 10G
# qemu-img info /vms/dbvm_disk2_seek.img
image: /vms/dbvm_disk2_seek.img
file format: raw
virtual size: 10G (10737418240 bytes)
disk size: 10M

查看两个磁盘的“磁盘大小”行。对于/vms/dbvm_disk2.img,显示为10G,而对于/vms/dbvm_disk2_seek.img,显示为10M MiB。这种差异是因为第二个磁盘使用了薄配置格式。虚拟大小是客户看到的,磁盘大小是磁盘在主机上保留的空间。如果两个大小相同,这意味着磁盘是预分配的。差异意味着磁盘使用了薄配置格式。现在,让我们将磁盘镜像附加到虚拟机;您可以使用virt-manager或 CLI 替代方案virsh进行附加。

使用 virt-manager 附加磁盘

从主机系统的图形桌面环境启动 virt-manager。也可以使用 SSH 远程启动,如以下命令所示:

ssh -X host's address
[remotehost]# virt-manager

那么,让我们使用虚拟机管理器将磁盘附加到虚拟机:

  1. 在虚拟机管理器的主窗口中,选择要添加辅助磁盘的虚拟机。

  2. 转到虚拟硬件详细信息窗口,然后单击对话框底部左侧的“添加硬件”按钮。

  3. 在“添加新虚拟硬件”中,选择“存储”,然后选择“为虚拟机创建磁盘镜像”按钮和虚拟磁盘大小,如下面的屏幕截图所示:图 5.27 - 在 virt-manager 中添加虚拟磁盘

图 5.27 - 在 virt-manager 中添加虚拟磁盘

  1. 如果要附加先前创建的dbvm_disk2.img镜像,选择/vms目录中的dbvm_disk2.img文件或在本地存储池中找到它,然后选择它并单击/dev/sdb)或磁盘分区(/dev/sdb1)或 LVM 逻辑卷。我们可以使用任何先前配置的存储池来存储此镜像,无论是作为文件还是对象,还是直接到块设备。

  2. 点击virsh命令。

使用 virt-manager 创建虚拟磁盘非常简单——只需点击几下鼠标并输入一些内容。现在,让我们看看如何通过命令行来做到这一点,即使用virsh

使用 virsh 附加磁盘

virsh是 virt-manager 的非常强大的命令行替代品。您可以在几秒钟内执行一个动作,而通过 virt-manager 等图形界面可能需要几分钟。它提供了attach-disk选项,用于将新的磁盘设备附加到虚拟机。与attach-disk一起提供了许多开关:

attach-disk domain source target [[[--live] [--config] | [--current]] | [--persistent]] [--targetbusbus] [--driver driver] [--subdriversubdriver] [--iothreadiothread] [--cache cache] [--type type] [--mode mode] [--sourcetypesourcetype] [--serial serial] [--wwnwwn] [--rawio] [--address address] [--multifunction] [--print-xml]

然而,在正常情况下,以下内容足以对虚拟机执行热添加磁盘附加:

# virsh attach-disk CentOS8 /vms/dbvm_disk2.img vdb --live --config

在这里,CentOS8是执行磁盘附加的虚拟机。然后是磁盘镜像的路径。vdb是目标磁盘名称,在宿主操作系统中可见。--live表示在虚拟机运行时执行操作,--config表示在重新启动后持久地附加它。不添加--config开关将使磁盘仅在重新启动前附加。

重要提示:

热插拔支持:在 Linux 宿主操作系统中加载acpiphp内核模块以识别热添加的磁盘;acpiphp提供传统的热插拔支持,而pciehp提供本地的热插拔支持。pciehp依赖于acpiphp。加载acpiphp将自动加载pciehp作为依赖项。

您可以使用virsh domblklist <vm_name>命令快速识别附加到虚拟机的 vDisks 数量。以下是一个示例:

# virsh domblklist CentOS8 --details
Type Device Target Source
------------------------------------------------
file disk vda /var/lib/libvirt/images/fedora21.qcow2
file disk vdb /vms/dbvm_disk2_seek.img

这清楚地表明连接到虚拟机的两个 vDisks 都是文件映像。它们分别显示为客户操作系统的vdavdb,并且在主机系统上的磁盘映像路径的最后一列中可见。

接下来,我们将看到如何创建 ISO 库。

创建 ISO 镜像库

虚拟机上的客户操作系统虽然可以通过将主机的 CD/DVD 驱动器传递到虚拟机来从物理媒体安装,但这并不是最有效的方法。从 DVD 驱动器读取比从硬盘读取 ISO 文件慢,因此更好的方法是将用于安装操作系统和虚拟机应用程序的 ISO 文件(或逻辑 CD)存储在基于文件的存储池中,并创建 ISO 镜像库。

要创建 ISO 镜像库,可以使用 virt-manager 或virsh命令。让我们看看如何使用virsh命令创建 ISO 镜像库:

  1. 首先,在主机系统上创建一个目录来存储.iso镜像:
# mkdir /iso
  1. 设置正确的权限。它应该由 root 用户拥有,权限设置为700。如果 SELinux 处于强制模式,则需要设置以下上下文:
# chmod 700 /iso
# semanage fcontext -a -t virt_image_t "/iso(/.*)?"
  1. 使用virsh命令定义 ISO 镜像库,如下面的代码块所示:
iso_library to demonstrate how to create a storage pool that will hold ISO images, but you are free to use any name you wish.
  1. 验证是否已创建池(ISO 镜像库):
# virsh pool-info iso_library
Name: iso_library
UUID: 959309c8-846d-41dd-80db-7a6e204f320e
State: running
Persistent: yes
Autostart: no
Capacity: 49.09 GiB
Allocation: 8.45 GiB
Available: 40.64 GiB
  1. 现在可以将.iso镜像复制或移动到/iso_lib目录中。

  2. .iso文件复制到/iso_lib目录后,刷新池,然后检查其内容:

# virsh pool-refresh iso_library
Pool iso_library refreshed
# virsh vol-list iso_library
Name Path
------------------------------------------------------------------
------------
CentOS8-Everything.iso /iso/CentOS8-Everything.iso
CentOS7-EVerything.iso /iso/CentOS7-Everything.iso
RHEL8.iso /iso/RHEL8.iso
Win8.iso /iso/Win8.iso
  1. 这将列出存储在目录中的所有 ISO 镜像,以及它们的路径。这些 ISO 镜像现在可以直接与虚拟机一起用于客户操作系统的安装、软件安装或升级。

在今天的企业中,创建 ISO 镜像库是一种事实上的规范。最好有一个集中的地方存放所有的 ISO 镜像,并且如果需要在不同位置进行同步(例如rsync),这样做会更容易。

删除存储池

删除存储池相当简单。请注意,删除存储域不会删除任何文件/块设备。它只是将存储从 virt-manager 中断开。文件/块设备必须手动删除。

我们可以通过 virt-manager 或使用virsh命令删除存储池。让我们首先看看如何通过 virt-manager 进行操作:

图 5.28–删除存储池

图 5.28–删除存储池

首先,选择红色停止按钮停止池,然后单击带有X的红色圆圈以删除池。

如果要使用virsh,那就更简单了。假设我们要删除上一个截图中名为MyNFSpool的存储池。只需输入以下命令:

virsh pool-destroy MyNFSpool
virsh pool-undefine MyNFSpool

创建存储池后的下一个逻辑步骤是创建存储卷。从逻辑上讲,存储卷将存储池划分为较小的部分。现在让我们学习如何做到这一点。

创建存储卷

存储卷是在存储池之上创建的,并作为虚拟磁盘附加到虚拟机。为了创建存储卷,启动存储管理控制台,导航到 virt-manager,然后单击编辑 | 连接详细信息 | 存储,并选择要创建新卷的存储池。单击创建新卷按钮(+):

图 5.29–为虚拟机创建存储卷

图 5.29–为虚拟机创建存储卷

接下来,提供新卷的名称,选择磁盘分配格式,并单击virsh命令。libvirt 支持几种磁盘格式(rawcowqcowqcow2qedvmdk)。使用适合您环境的磁盘格式,并在最大容量分配字段中设置适当的大小,以决定您是否希望选择预分配的磁盘分配或薄置备。如果在qcow2格式中保持磁盘大小不变,则不支持厚磁盘分配方法。

在[第八章](B14834_08_Final_ASB_ePub.xhtml#_idTextAnchor143)创建和修改 VM 磁盘、模板和快照中,详细解释了所有磁盘格式。现在,只需了解qcow2是为 KVM 虚拟化专门设计的磁盘格式。它支持创建内部快照所需的高级功能。

使用 virsh 命令创建卷

使用virsh命令创建卷的语法如下:

# virsh vol-create-as dedicated_storage vm_vol1 10G

这里,dedicated_storage是存储池,vm_vol1是卷名称,10 GB 是大小。

# virsh vol-info --pool dedicated_storage vm_vol1
Name: vm_vol1
Type: file
Capacity: 1.00 GiB
Allocation: 1.00 GiB

virsh命令和参数用于创建存储卷,几乎不管它是在哪种类型的存储池上创建的,都几乎相同。只需输入适当的输入以使用--pool开关。现在,让我们看看如何使用virsh命令删除卷。

使用 virsh 命令删除卷

使用virsh命令删除卷的语法如下:

# virsh vol-delete dedicated_storage vm_vol2

执行此命令将从dedicated_storage存储池中删除vm_vol2卷。

我们存储之旅的下一步是展望未来,因为本章提到的所有概念多年来都广为人知,甚至有些已经有几十年的历史了。存储世界正在改变,朝着新的有趣方向发展,让我们稍微讨论一下。

存储的最新发展 - NVMe 和 NVMeOF

在过去的 20 年左右,就技术而言,存储世界最大的颠覆是固态硬盘SSD)的引入。现在,我们知道很多人已经习惯在他们的计算机上使用它们 - 笔记本电脑、工作站,无论我们使用哪种类型的设备。但是,我们正在讨论虚拟化的存储和企业存储概念,这意味着我们常规的 SATA SSD 不够用。尽管很多人在中档存储设备和/或手工制作的存储设备中使用它们来托管 ZFS 池(用于缓存),但这些概念在最新一代存储设备中有了自己的生命。这些设备从根本上改变了技术的工作方式,并在现代 IT 历史的某些部分进行了重塑,包括使用的协议、速度有多快、延迟有多低,以及它们如何处理存储分层 - 分层是一个区分不同存储设备或它们的存储池的概念,通常是速度的能力。

让我们简要解释一下我们正在讨论的内容,通过一个存储世界的发展方向的例子。除此之外,存储世界正在带动虚拟化、云和 HPC 世界一起前进,因此这些概念并不离奇。它们已经存在于现成的存储设备中,您今天就可以购买到。

SSD 的引入显著改变了我们访问存储设备的方式。这一切都关乎性能和延迟,而像高级主机控制器接口AHCI)这样的旧概念,我们今天市场上仍在积极使用,已经不足以处理 SSD 的性能。AHCI 是常规硬盘(机械硬盘或常规磁头)通过软件与 SATA 设备通信的标准方式。然而,关键部分是硬盘,这意味着圆柱、磁头扇区—这些 SSD 根本没有,因为它们不会旋转,也不需要那种范式。这意味着必须创建另一个标准,以便我们可以更本地地使用 SSD。这就是非易失性内存扩展NVMe)的全部内容—弥合 SSD 的能力和实际能力之间的差距,而不使用从 SATA 到 AHCI 到 PCI Express(等等)的转换。

SSD 的快速发展速度和 NVMe 的整合使企业存储取得了巨大的进步。这意味着必须发明新的控制器、新的软件和完全新的架构来支持这种范式转变。随着越来越多的存储设备为各种目的集成 NVMe—主要是用于缓存,然后也用于存储容量—变得清楚的是,还有其他问题需要解决。其中第一个问题是我们将如何连接提供如此巨大能力的存储设备到我们的虚拟化、云或 HPC 环境。

在过去的 10 年左右,许多人争论说 FC 将从市场上消失,许多公司对不同的标准进行了押注—iSCSI、iSCSI over RDMA、NFS over RDMA 等。这背后的推理似乎足够坚实:

  • FC 很昂贵——它需要单独的物理交换机、单独的布线和单独的控制器,所有这些都需要花费大量的钱。

  • 涉及许可证—当你购买一个拥有 40 个 FC 端口的 Brocade 交换机时,并不意味着你可以立即使用所有端口,因为需要许可证来获取更多端口(8 端口、16 端口等)。

  • FC 存储设备昂贵,并且通常需要更昂贵的磁盘(带有 FC 连接器)。

  • 配置 FC 需要广泛的知识和/或培训,因为你不能简单地去配置一堆 FC 交换机给一个企业级公司,而不知道概念和交换机供应商的 CLI,还要知道企业的需求。

  • 作为一种协议,FC 加速发展以达到新的速度的能力一直很差。简单来说,在 FC 从 8 Gbit/s 加速到 32 Gbit/s 的时间内,以太网从 1 Gbit/s 加速到 25、40、50 和 100 Gbit/s 的带宽。已经有关于 400 Gbit/s 以太网的讨论,也有第一个支持该标准的设备。这通常会让客户感到担忧,因为更高的数字意味着更好的吞吐量,至少在大多数人的想法中是这样。

但市场上现在发生的事情告诉我们一个完全不同的故事—不仅 FC 回来了,而且它回来了有使命。企业存储公司已经接受了这一点,并开始推出具有疯狂性能水平的存储设备(首先是 NVMe SSD 的帮助)。这种性能需要转移到我们的虚拟化、云和 HPC 环境中,这需要最佳的协议,以实现最低的延迟、设计、质量和可靠性,而 FC 具备所有这些。

这导致了第二阶段,NVMe SSD 不仅被用作缓存设备,而且也被用作容量设备。

请注意,目前存储内存/存储互连市场上正在酝酿一场大战。有多种不同的标准试图与英特尔的快速路径互连QPI)竞争,这项技术已经在英特尔 CPU 中使用了十多年。如果这是你感兴趣的话题,本章末尾有一个链接,在进一步阅读部分,你可以找到更多信息。基本上,QPI 是一种点对点互连技术,具有低延迟和高带宽,是当今服务器的核心。具体来说,它处理 CPU 之间、CPU 和内存、CPU 和芯片组等之间的通信。这是英特尔在摆脱前端总线FSB)和芯片组集成内存控制器后开发的技术。FSB 是一个在内存和 I/O 请求之间共享的总线。这种方法具有更高的延迟,不易扩展,带宽较低,并且在内存和 I/O 端发生大量 I/O 的情况下存在问题。在切换到内存控制器成为 CPU 的一部分的架构后(因此,内存直接连接到它),对于英特尔最终转向这种概念是至关重要的。

如果你更熟悉 AMD CPU,QPI 对英特尔来说就像内置内存控制器的 CPU 上的 HyperTransport 总线对 AMD CPU 来说一样。

随着 NVMe SSD 变得更快,PCI Express 标准也需要更新,这就是为什么最新版本(PCIe 4.0 - 最新产品最近开始发货)如此受期待的原因。但现在,焦点已经转移到需要解决的另外两个问题。让我们简要描述一下:

  • 第一个问题很简单。对于普通计算机用户,在 99%或更多的情况下,一两个 NVMe SSD 就足够了。实际上,普通计算机用户需要更快的 PCIe 总线的唯一真正原因是为了更快的显卡。但对于存储制造商来说,情况完全不同。他们希望生产企业存储设备,其中将有 20、30、50、100、500 个 NVMe SSD 在一个存储系统中-他们希望现在就能做到这一点,因为 SSD 作为一种技术已经成熟并且广泛可用。

  • 第二个问题更为复杂。更令人沮丧的是,最新一代的 SSD(例如基于英特尔 Optane 的 SSD)可以提供更低的延迟和更高的吞吐量。随着技术的发展,这种情况只会变得更糟(更低的延迟,更高的吞吐量)。对于今天的服务-虚拟化、云和 HPC-存储系统能够处理我们可能投入其中的任何负载是至关重要的。这些技术在存储设备变得更快的程度上是真正的游戏改变者,只要互连能够处理它(QPI、FC 等)。从英特尔 Optane 衍生出的两个概念-存储级内存SCM)和持久内存PM)是存储公司和客户希望快速采用到他们的存储系统中的最新技术。

  • 第三个问题是如何将所有这些带宽和 I/O 能力传输到使用它们的服务器和基础设施。这就是为什么创建了NVMe over FabricsNVMe-OF)的概念,试图在存储基础设施堆栈上工作,使 NVMe 对其消费者更加高效和快速。

从概念上看,几十年来,RAM 样的内存是我们拥有的最快、最低延迟的技术。逻辑上,我们正在尽可能地将工作负载转移到 RAM。想想内存数据库(如 Microsoft SQL、SAP Hana 和 Oracle)。它们已经存在多年了。

这些技术从根本上改变了我们对存储的看法。基本上,我们不再讨论基于技术(SSD 与 SAS 与 SATA)或纯粹速度的存储分层,因为速度是不容置疑的。最新的存储技术讨论存储分层是基于延迟。原因非常简单——假设你是一个存储公司,你建立了一个使用 50 个 SCM SSD 作为容量的存储系统。对于缓存,唯一合理的技术将是 RAM,数百 GB 的 RAM。你能够在这样的设备上使用存储分层的唯一方法就是通过在软件中模拟它,通过创建额外的技术来产生基于排队、处理缓存(RAM)中的优先级和类似概念的分层式服务。为什么?因为如果你使用相同的 SCM SSD 作为容量,并且它们提供相同的速度和 I/O,你就无法基于技术或能力进行分层。

让我们通过使用一个可用的存储系统来进一步解释这一点。最好的设备来阐明我们的观点是戴尔/EMC 的 PowerMax 系列存储设备。如果你用 NVMe 和 SCM SSD 装载它们,最大型号(8000)可以扩展到 1500 万 IOPS(!),350GB/s 吞吐量,低于 100 微秒的延迟,容量高达 4PB。想一想这些数字。然后再加上另一个数字——在前端,它可以有高达 256 个 FC/FICON/iSCSI 端口。就在最近,戴尔/EMC 发布了新的 32 Gbit/s FC 模块。较小的 PowerMax 型号(2000)可以做到 750 万 IOPS,低于 100 微秒的延迟,并扩展到 1PB。它还可以做所有通常的 EMC 功能——复制、压缩、去重、快照、NAS 功能等等。所以,这不仅仅是市场宣传;这些设备已经存在,并被企业客户使用:

图 3.30 – PowerMax 2000 – 看起来很小,但功能强大

图 3.30 – PowerMax 2000 – 看起来很小,但功能强大

这些对于未来非常重要,因为越来越多的制造商生产类似的设备(它们正在途中)。我们完全期待基于 KVM 的世界在大规模环境中采用这些概念,特别是对于具有 OpenStack 和 OpenShift 基础设施的情况。

总结

在本章中,我们介绍并配置了 libvirt 的各种开源存储概念。我们还讨论了行业标准的方法,比如 iSCSI 和 NFS,因为它们经常在不基于 KVM 的基础设施中使用。例如,基于 VMware vSphere 的环境可以使用 FC、iSCSI 和 NFS,而基于 Microsoft 的环境只能使用 FC 和 iSCSI,从我们在本章中涵盖的主题列表中选择。

下一章将涵盖与虚拟显示设备和协议相关的主题。我们将深入介绍 VNC 和 SPICE 协议。我们还将描述其他用于虚拟机连接的协议。所有这些将帮助我们理解我们在过去三章中涵盖的与虚拟机一起工作所需的完整基础知识栈。

问题

  1. 什么是存储池?

  2. NFS 存储如何与 libvirt 一起工作?

  3. iSCSI 如何与 libvirt 一起工作?

  4. 我们如何在存储连接上实现冗余?

  5. 除了 NFS 和 iSCSI,我们可以用什么来作为虚拟机存储?

  6. 我们可以使用哪种存储后端来进行基于对象的存储与 libvirt 的连接?

  7. 我们如何创建一个虚拟磁盘映像以供 KVM 虚拟机使用?

  8. 使用 NVMe SSD 和 SCM 设备如何改变我们创建存储层的方式?

  9. 为虚拟化、云和 HPC 环境提供零层存储服务的基本问题是什么?

进一步阅读

有关本章涵盖内容的更多信息,请参考以下链接:

第六章:虚拟显示设备和协议

在本章中,我们将讨论通过虚拟图形卡和协议访问虚拟机的方式。我们可以在虚拟机中使用近 10 种可用的虚拟显示适配器,并且有多种可用的协议和应用程序可以用来访问我们的虚拟机。除了 SSH 和任何一般的基于控制台的访问,市场上还有各种协议可供我们使用来访问虚拟机的控制台,如 VNC、SPICE 和 noVNC。

在基于 Microsoft 的环境中,我们倾向于使用远程桌面协议RDP)。如果我们谈论虚拟桌面基础设施VDI),那么甚至有更多的协议可用 - PC over IPPCoIP)、VMware Blast 等等。其中一些技术提供了额外的功能,如更大的色深、加密、音频和文件系统重定向、打印机重定向、带宽管理以及 USB 和其他端口重定向。这些是当今云计算世界中远程桌面体验的关键技术。

所有这些意味着我们必须花更多的时间和精力去了解各种显示设备和协议,以及如何配置和使用它们。我们不希望出现这样的情况,即因为选择了错误的虚拟显示设备而无法看到虚拟机的显示,或者尝试打开控制台查看虚拟机内容时控制台无法打开的情况。

在本章中,我们将涵盖以下主题:

  • 使用虚拟机显示设备

  • 讨论远程显示协议

  • 使用 VNC 显示协议

  • 使用 SPICE 显示协议

  • 使用 NoVNC 实现显示可移植性

  • 让我们开始吧!

使用虚拟机显示设备

为了使虚拟机上的图形工作,QEMU 需要为其虚拟机提供两个组件:虚拟图形适配器和从客户端访问图形的方法或协议。让我们讨论这两个概念,从虚拟图形适配器开始。最新版本的 QEMU 有八种不同类型的虚拟/仿真图形适配器。所有这些都有一些相似之处和差异,这些差异可能是在功能和/或支持的分辨率方面,或者其他更多技术细节方面。因此,让我们描述它们,并看看我们将为特定虚拟图形卡偏爱哪些用例:

  • tcx:一种 SUN TCX 虚拟图形卡,可用于旧的 SUN 操作系统。

  • cirrus:一种基于旧的 Cirrus Logic GD5446 VGA 芯片的虚拟图形卡。它可以与 Windows 95 之后的任何客户操作系统一起使用。

  • std:一种标准的 VGA 卡,可用于 Windows XP 之后的客户操作系统的高分辨率模式。

  • vmware:VMware 的 SVGA 图形适配器,在 Linux 客户操作系统中需要额外的驱动程序和 Windows 操作系统中需要安装 VMware Tools。

  • QXL:事实上的标准半虚拟图形卡,当我们使用 SPICE 远程显示协议时需要使用,我们稍后将在本章中详细介绍。这个虚拟图形卡的旧版本称为 QXL VGA,它缺少一些更高级的功能,但提供更低的开销(使用更少的内存)。

  • Virtio:一种基于 virgl 项目的半虚拟 3D 虚拟图形卡,为 QEMU 客户操作系统提供 3D 加速。它有两种不同的类型(VGA 和 gpu)。virtio-vga 通常用于需要多显示器支持和 OpenGL 硬件加速的情况。virtio-gpu 版本没有内置的标准 VGA 兼容模式。

  • cg3:一种虚拟图形卡,可用于较旧的基于 SPARC 的客户操作系统。

  • none:禁用客户操作系统中的图形卡。

在配置虚拟机时,您可以在启动或创建虚拟机时选择这些选项。在 CentOS 8 中,分配给新创建的虚拟机的默认虚拟图形卡是QXL,如下面的新虚拟机配置的屏幕截图所示:

图 6.1 - 客户操作系统的默认虚拟图形卡 - QXL

图 6.1 - 客户操作系统的默认虚拟图形卡 - QXL

此外,默认情况下,我们可以为任何给定的虚拟机选择这三种类型的虚拟图形卡,因为这些通常已经预先安装在为虚拟化配置的任何 Linux 服务器上:

  • QXL

  • VGA

  • Virtio

在 KVM 虚拟化中运行的一些新操作系统不应该使用旧的图形卡适配器,原因有很多。例如,自从 Red Hat Enterprise Linux/CentOS 7 以来,有一个建议不要为 Windows 10 和 Windows Server 2016 使用 cirrus 虚拟图形卡。原因是虚拟机的不稳定性,以及 - 例如 - 您无法使用 cirrus 虚拟图形卡进行全高清分辨率显示。以防万一您开始安装这些客户操作系统,请确保您使用 QXL 视频图形卡,因为它提供了最佳性能和与 SPICE 远程显示协议的兼容性。

从理论上讲,您仍然可以为一些非常老的客户操作系统(旧的 Windows NT,如 4.0 和旧的客户操作系统,如 Windows XP)使用 cirrus 虚拟图形卡,但仅限于此。对于其他所有情况,最好使用 std 或 QXL 驱动程序,因为它们提供了最佳的性能和加速支持。此外,这些虚拟图形卡还提供更高的显示分辨率。

QEMU 还提供了一些其他虚拟图形卡,例如各种片上系统SoC)设备的嵌入式驱动程序,ati vga,bochs 等。其中一些经常被使用,比如 SoCs - 只需记住世界上所有的树莓派和 BBC Micro:bits。这些新的虚拟图形选项还通过物联网IoT)得到进一步扩展。因此,有很多很好的理由让我们密切关注这个市场空间中发生的事情。

让我们通过一个例子来展示这一点。假设我们想创建一个新的虚拟机,并为其分配一组自定义参数,以便我们访问其虚拟显示。如果您还记得第三章安装 KVM Hypervisor、libvirt 和 ovirt,我们讨论了各种 libvirt 管理命令(virshvirt-install),并使用virt-install创建了一些虚拟机和一些自定义参数。让我们在这些基础上添加一些内容,并使用一个类似的例子:

virt-install --virt-type=kvm --name MasteringKVM01 --vcpus 2  --ram 4096 --os-variant=rhel8.0 --/iso/CentOS-8-x86_64-1905-dvd1.iso --network=default --video=vga --graphics vnc,password=Packt123 --disk size=16

以下是将要发生的事情:

图 6.2 - 创建了一个带有 VGA 虚拟图形卡的 KVM 虚拟机。在这里,VNC 要求指定密码

图 6.2 - 创建了一个带有 VGA 虚拟图形卡的 KVM 虚拟机。在这里,VNC 要求指定密码

在我们输入密码(Packt123,如在 virt-install 配置选项中指定的那样)之后,我们面对这个屏幕:

图 6.3 - VGA 显示适配器及其低默认(640x480)初始分辨率 - 对于在 80 年代长大的我们来说是一个熟悉的分辨率

图 6.3 - VGA 显示适配器及其低默认(640x480)初始分辨率 - 对于在 80 年代长大的我们来说是一个熟悉的分辨率

也就是说,我们只是用这个作为一个例子,来展示如何向virt-install命令添加一个高级选项 - 具体来说,如何使用特定的虚拟图形卡安装虚拟机。

还有其他更高级的概念,即使用我们在计算机或服务器上安装的真实图形卡,将它们的功能直接转发给虚拟机。这对于 VDI 等概念非常重要,正如我们之前提到的。让我们讨论一下这些概念,并使用一些真实世界的例子和比较来理解大规模 VDI 解决方案的复杂性。

VDI 场景中的物理和虚拟图形卡

正如我们在《第一章》中讨论的那样,《理解 Linux 虚拟化》,VDI 是一个利用虚拟化范式来为客户端操作系统提供服务的概念。这意味着最终用户通过运行客户端操作系统(例如 Windows 8.1、Windows 10 或 Linux Mint)直接连接到他们的虚拟机,这些虚拟机要么是专门为他们保留的,要么是共享的,这意味着多个用户可以访问相同的虚拟机并通过额外的 VDI 功能访问它们的数据

现在,如果我们谈论大多数商业用户,他们只需要我们开玩笑称之为打字机的东西。这种使用模式涉及用户使用客户端操作系统阅读和撰写文件、电子邮件和浏览互联网。对于这些用例,如果我们要使用任何供应商的解决方案(VMware 的 Horizon、Citrix 的 Xen Desktop 或微软基于远程桌面服务的 VDI 解决方案),我们可以使用其中任何一个。

然而,有一个很大的但是。如果场景包括数百名需要访问 2D 和/或 3D 视频加速的用户会发生什么?如果我们正在为一个创建设计的公司设计 VDI 解决方案——比如建筑、管道、石油和天然气以及视频制作?基于 CPU 和软件虚拟图形卡的 VDI 解决方案在这种情况下将毫无作为,特别是在大规模情况下。这就是 Xen Desktop 和 Horizon 在技术水平上要更加功能丰富的地方。而且,说实话,基于 KVM 的方法在显示选项方面并不逊色,只是在一些其他企业级功能上稍显不足,我们将在后面的章节中讨论这些功能,比如《第十二章》,使用 OpenStack 扩展 KVM

基本上,我们可以使用三个概念来获得虚拟机的图形卡性能:

  • 我们可以使用基于 CPU 的软件渲染器。

  • 我们可以为特定的虚拟机保留一个 GPU(PCI 直通)。

  • 我们可以分区一个 GPU,这样我们可以在多个虚拟机中使用它。

仅使用 VMware Horizon 解决方案作为比喻,这些解决方案将被称为 CPU 渲染、虚拟直接图形加速vDGA)和虚拟共享图形加速vSGA)。或者在 Citrix 中,我们会谈论 HDX 3D Pro。在 CentOS 8 中,我们在共享图形卡方案中谈论中介设备

如果我们谈论 PCI 直通,它绝对能提供最佳性能,因为你可以使用 PCI-Express 图形卡,直接转发给虚拟机,在客户操作系统内安装本机驱动程序,并完全拥有图形卡。但这会带来四个问题:

  • 你只能将 PCI-Express 图形卡转发给一个虚拟机。

  • 由于服务器在升级方面可能存在限制,例如,你不能像在一台物理服务器上那样运行 50 个虚拟机,因为你无法在单个服务器上放置 50 个图形卡——无论是从物理上还是从 PCI-Express 插槽上来看,通常情况下在一个典型的 2U 机架服务器上最多只有六个。

  • 如果你使用刀片服务器(例如,HP c7000),情况会更糟,因为如果你要使用额外的图形卡,那么每个刀片机箱的服务器密度将减半,因为这些卡只能安装在双高刀片上。

  • 如果您要将任何这类解决方案扩展到数百个虚拟桌面,甚至更糟的是数千个虚拟桌面,您将花费大量资金。

如果我们谈论的是一种共享方法,即将物理图形卡分区,以便在多个虚拟机中使用它,那么这将产生另一组问题:

  • 在选择要使用的图形卡方面,您的选择要受到更多限制,因为可能只有大约 20 种图形卡支持这种使用模式(其中一些包括 NVIDIA GRID、Quadro、Tesla 卡以及几张 AMD 和英特尔卡)。

  • 如果您与四、八、十六或三十二个虚拟机共享同一块图形卡,您必须意识到您的性能会降低,因为您正在与多个虚拟机共享同一块 GPU。

  • 与 DirectX、OpenGL、CUDA 和视频编码卸载的兼容性可能不如您期望的那样好,您可能会被迫使用这些标准的较旧版本。

  • 可能会涉及额外的许可证,这取决于供应商和解决方案。

我们列表上的下一个主题是如何更高级地使用 GPU - 通过使用 GPU 分区概念将 GPU 的部分提供给多个虚拟机。让我们解释一下这是如何工作的,并通过使用 NVIDIA GPU 作为示例来配置它。

使用 NVIDIA vGPU 作为示例的 GPU 分区

让我们使用一个示例来看看我们如何使用分区我们的 GPU(NVIDIA vGPU)与我们基于 KVM 的虚拟机。这个过程与我们在第四章中讨论的 SR-IOV 过程非常相似,Libvirt Networking,在那里我们使用受支持的英特尔网络卡将虚拟功能呈现给我们的 CentOS 主机,然后通过将它们用作 KVM 虚拟桥的上行链路来呈现给我们的虚拟机。

首先,我们需要检查我们有哪种类型的显卡,它必须是受支持的(在我们的情况下,我们使用的是 Tesla P4)。让我们使用lshw命令来检查我们的显示设备,它应该看起来类似于这样:

# yum -y install lshw
# lshw -C display
*-display
       description: 3D controller
       product: GP104GL [Tesla P4]
       vendor: NVIDIA Corporation
       physical id: 0
       bus info: pci@0000:01:00.0
       version: a0
       width: 64 bits
       clock: 33MHz
       capabilities: pm msi pciexpress cap_list
       configuration: driver=vfio-pci latency=0
       resources: irq:15 memory:f6000000-f6ffffff memory:e0000000-efffffff memory:f0000000-f1ffffff

这个命令的输出告诉我们我们有一个支持 3D 的 GPU - 具体来说,是基于 NVIDIA GP104GL 的产品。它告诉我们这个设备已经在使用vfio-pci驱动程序。这个驱动程序是虚拟化功能VF)的本机 SR-IOV 驱动程序。这些功能是 SR-IOV 功能的核心。我们将使用这个 SR-IOV 兼容的 GPU 来描述这一点。

我们需要做的第一件事 - 我们所有的 NVIDIA GPU 用户多年来一直在做的事情 - 是将 nouveau 驱动程序列入黑名单,因为它会妨碍我们。如果我们要永久使用 GPU 分区,我们需要永久地这样做,这样在服务器启动时就不会加载它。但要警告一下 - 这有时会导致意外行为,比如服务器启动时没有任何输出而没有任何真正的原因。因此,我们需要为modprobe创建一个配置文件,将 nouveau 驱动程序列入黑名单。让我们在/etc/modprobe.d目录中创建一个名为nouveauoff.conf的文件,内容如下:

blacklist nouveau
options nouveau modeset 0

然后,我们需要强制服务器重新创建在服务器启动时加载的initrd映像,并重新启动服务器以使更改生效。我们将使用dracut命令来执行此操作,然后是常规的reboot命令:

# dracut –-regenerate-all –force
# systemctl reboot

重新启动后,让我们检查 NVIDIA 图形卡的vfio驱动程序是否已加载,如果已加载,请检查 vGPU 管理器服务:

# lsmod | grep nvidia | grep vfio
nvidia_vgpu_vfio 45011 0
nvidia 14248203 10 nvidia_vgpu_vfio
mdev 22078 2 vfio_mdev,nvidia_vgpu_vfio
vfio 34373 3 vfio_mdev,nvidia_vgpu_vfio,vfio_iommu_type1
# systemctl status nvidia-vgpu-mgr
vidia-vgpu-mgr.service - NVIDIA vGPU Manager Daemon
   Loaded: loaded (/usr/lib/systemd/system/nvidia-vgpu-mgr.service; enabled; vendor preset: disabled)
   Active: active (running) since Thu 2019-12-12 20:17:36 CET; 0h 3min ago
 Main PID: 1327 (nvidia-vgpu-mgr)

我们需要创建一个 UUID,我们将使用它来向 KVM 虚拟机呈现我们的虚拟功能。我们将使用uuidgen命令来执行此操作:

uuidgen
c7802054-3b97-4e18-86a7-3d68dff2594d

现在,让我们使用这个 UUID 来为将共享我们的 GPU 的虚拟机。为此,我们需要创建一个 XML 模板文件,然后以复制粘贴的方式将其添加到我们虚拟机的现有 XML 文件中。让我们称之为vsga.xml

<hostdev mode='subsystem' type='mdev' managed='no' model='vfio-pci'>
  <source>
    <address uuid='c7802054-3b97-4e18-86a7-3d68dff2594d'/>
  </source>
</hostdev>

使用这些设置作为模板,只需将完整内容复制粘贴到任何虚拟机的 XML 文件中,您希望访问我们共享的 GPU。

我们需要讨论的下一个概念是 SR-IOV 的完全相反,其中我们将设备切片成多个部分,以将这些部分呈现给虚拟机。在 GPU 直通中,我们将整个设备直接呈现给一个对象,即一个虚拟机。让我们学习如何配置它。

GPU PCI 直通

与每个高级功能一样,启用 GPU PCI 直通需要按顺序完成多个步骤。通过按照正确的顺序执行这些步骤,我们直接将这个硬件设备呈现给虚拟机。让我们解释这些配置步骤并执行它们:

  1. 要启用 GPU PCI 直通,我们需要在服务器的 BIOS 中配置和启用 IOMMU,然后在 Linux 发行版中启用。我们使用基于 Intel 的服务器,因此我们需要向/etc/default/grub文件中添加iommu选项,如下截图所示:图 6.4 - 向 GRUB 文件添加 intel_iommu iommu=pt 选项

图 6.4 - 向 GRUB 文件添加 intel_iommu iommu=pt 选项

  1. 下一步是重新配置 GRUB 配置并重新启动它,可以通过输入以下命令来实现:
# grub2-mkconfig -o /etc/grub2.cfg
# systemctl reboot
  1. 重新启动主机后,我们需要获取一些信息 - 具体来说,是关于我们要转发到虚拟机的 GPU 设备的 ID 信息。让我们这样做:

图 6.5 - 使用 lspci 显示相关配置信息

在我们的用例中,我们希望将 Quadro 2000 卡转发到我们的虚拟机,因为我们正在使用 GT740 连接我们的显示器,而 Quadro 卡目前没有任何工作负载或连接。因此,我们需要记下两个数字;即0000:05:00.010de:0dd8

我们将需要这两个 ID 继续前进,每个 ID 用于定义我们要使用的设备和位置。

  1. 下一步是向我们的主机操作系统解释,它不会为自己使用这个 PCI Express 设备(Quadro 卡)。为了做到这一点,我们需要再次更改 GRUB 配置,并向同一文件(/etc/defaults/grub)添加另一个参数:
# grub2-mkconfig -o /etc/grub2.cfg
# systemctl reboot

这一步标志着物理服务器配置的结束。现在,我们可以继续进行下一阶段的过程,即如何在虚拟机中使用现在完全配置的 PCI 直通设备。

  1. 让我们通过使用virsh nodedev-dumpxml命令检查是否一切都正确完成了,检查 PCI 设备 ID:图 6.7 - 检查 KVM 堆栈是否能看到我们的 PCIe 设备

图 6.7 - 检查 KVM 堆栈是否能看到我们的 PCIe 设备

在这里,我们可以看到 QEMU 看到了两个功能:0x10x00x1功能实际上是 GPU 设备的音频芯片,我们不会在我们的过程中使用它。我们只需要0x0功能,即 GPU 本身。这意味着我们需要屏蔽它。我们可以通过使用以下命令来实现:

图 6.8 - 分离 0x1 设备,以便它不能用于直通

图 6.8 - 分离 0x1 设备,以便它不能用于直通

  1. 现在,让我们通过 PCI 直通将 GPU 添加到我们的虚拟机。为此,我们使用了一个名为MasteringKVM03的新安装的虚拟机,但您可以使用任何您想要的虚拟机。我们需要创建一个 XML 文件,QEMU 将使用它来知道要添加到虚拟机的设备。之后,我们需要关闭机器并将该 XML 文件导入到我们的虚拟机中。在我们的情况下,XML 文件将如下所示:图 6.9 - 用于 KVM 的 GPU PCI 直通定义的 XML 文件

图 6.9 - 用于 KVM 的 GPU PCI 直通定义的 XML 文件

  1. 下一步是将这个 XML 文件附加到MasteringKVM03虚拟机上。我们可以使用virsh attach-device命令来实现这一点:图 6.10-将 XML 文件导入域/虚拟机

图 6.10-将 XML 文件导入域/虚拟机

  1. 在上一步之后,我们可以启动虚拟机,登录,并检查虚拟机是否看到了我们的 GPU:

图 6.11-检查虚拟机中 GPU 的可见性

图 6.11-检查虚拟机中 GPU 的可见性

下一个合乎逻辑的步骤将是为 Linux 安装这张卡的 NVIDIA 驱动程序,这样我们就可以自由地将其用作我们的独立 GPU。

现在,让我们继续讨论与远程显示协议相关的另一个重要主题。在本章的前一部分中,我们也围绕这个主题打转了一下,但现在我们要正面对待它。

讨论远程显示协议

正如我们之前提到的,有不同的虚拟化解决方案,因此访问虚拟机的方法也是多种多样的。如果你看一下虚拟机的历史,你会发现有许多不同的显示协议来解决这个特定的问题。因此,让我们稍微讨论一下这段历史。

远程显示协议历史

会有人对这个前提提出异议,但远程协议最初是文本协议。无论你怎么看,串行、文本模式终端在微软、苹果和基于 UNIX 的世界中的 X Windows 或任何类似 GUI 的东西出现之前就已经存在了。此外,你无法否认的是 telnet 和 rlogin 协议也用于访问远程显示。恰巧我们通过 telnet 和 rlogin 访问的远程显示是基于文本的显示。同样的情况也适用于 SSH。串行终端、文本控制台和 telnet、rlogin 等基于文本的协议是一些最常用的起点,可以追溯到上世纪 70 年代。

20 世纪 70 年代末是计算机历史上的重要时刻,因为当时有许多尝试为大量人群开始大规模生产个人计算机(例如,1977 年的 Apple II)。在 20 世纪 80 年代,人们开始更多地使用个人计算机,任何 Amiga、Commodore、Atari、Spectrum 或 Amstrad 的粉丝都会告诉你。请记住,真正的、公开可用的基于 GUI 的操作系统直到 Xerox Star(1981)和 Apple Lisa(1983)才开始出现。第一个广泛可用的基于苹果的 GUI 操作系统是 1984 年的 Mac OS System 1.0。大多数其他先前提到的计算机都在使用基于文本的操作系统。即使是那个时代的游戏(以及很多年后的游戏)看起来都像是手绘的。Amiga 的 Workbench 1.0 于 1985 年发布,其 GUI 和颜色使用模型使其领先于时代。然而,1985 年可能会因为另一件事而被记住-这是第一个微软 Windows 操作系统(v1.0)发布的年份。后来,它变成了 Windows 2.0(1987)、Windows 3.0(1990)、Windows 3.1(1992),到那时微软已经开始在操作系统世界中掀起风暴。是的,其他制造商也有其他操作系统:

  • 苹果:Mac OS System 7 (1991)

  • IBM: OS/2 v1 (1988), v1.2 (1989), v2.0 (1992), Warp 4 (1996)

所有这些与 1995 年发生的大风暴相比只是一个小点。那一年,微软推出了 Windows 95。这是微软首个能够默认启动到 GUI 的客户端操作系统,因为之前的版本都是从命令行启动的。然后是 Windows 98 和 XP,这意味着微软获得了更多的市场份额。后来的故事可能非常熟悉,包括 Vista、Windows 7、Windows 8 和 Windows 10。

这个故事的重点不是教你有关操作系统历史本身的知识。它是关于注意到趋势,这足够简单。我们从命令行中的文本界面开始(例如,IBM 和 MS DOS,早期的 Windows,Linux,UNIX,Amiga,Atari 等)。然后,我们慢慢地转向更加视觉化的界面(GUI)。随着网络、GPU、CPU 和监控技术的进步,我们已经达到了一个阶段,我们希望拥有一个闪亮的、4K 分辨率的显示器,4 兆像素的分辨率,低延迟,强大的 CPU 性能,出色的颜色以及特定的用户体验。这种用户体验需要是即时的,而且我们使用本地操作系统或远程操作系统(VDI、云或其他背景技术)并不重要。

这意味着除了我们刚提到的所有硬件组件之外,还需要开发其他(软件)组件。具体来说,需要开发的是高质量的远程显示协议,这些协议现在必须能够扩展到基于浏览器的使用模型。人们不想被迫安装额外的应用程序(客户端)来访问他们的远程资源。

远程显示协议的类型

让我们只提一下目前市场上非常活跃的一些协议:

  • Microsoft 远程桌面协议/Remote FX:由远程桌面连接使用,这种多通道协议允许我们连接到基于 Microsoft 的虚拟机。

  • VNC:Virtual Network Computing 的缩写,这是一个远程桌面共享系统,用于传输鼠标和键盘事件以访问远程机器。

  • SPICE:独立计算环境的简单协议的缩写,这是另一种远程显示协议,可用于访问远程机器。它是由 Qumranet 开发的,后来被 Red Hat 收购。

如果我们进一步扩展我们的协议列表,用于 VDI 的协议,那么列表将进一步增加:

  • Teradici PCoIP(PC over IP):基于 UDP 的 VDI 协议,我们可以使用它来访问 VMware、Citrix 和基于 Microsoft 的 VDI 解决方案上的虚拟机

  • VMware Blast Extreme:VMware 针对 VMware Horizon 基于 VDI 解决方案的 PcoIP 的答案

  • Citrix HDX:Citrix 用于虚拟桌面的协议。

当然,还有其他可用但使用较少且不太重要的协议,例如以下内容:

  • Colorado CodeCraft

  • OpenText Exceed TurboX

  • NoMachine

  • FreeNX

  • Apache Guacamole

  • Chrome 远程桌面

  • Miranex

常规远程协议和完整功能的 VDI 协议之间的主要区别与附加功能有关。例如,在 PCoIP、Blast Extreme 和 HDX 上,您可以微调带宽设置,控制 USB 和打印机重定向(手动或通过策略集中控制),使用多媒体重定向(以卸载媒体解码),Flash 重定向(以卸载 Flash),客户端驱动器重定向,串口重定向等等。例如,您无法在 VNC 或远程桌面上执行其中一些操作。

话虽如此,让我们讨论一下开源世界中最常见的两种:VNC 和 SPICE。

使用 VNC 显示协议

当通过 libvirt 启用 VNC 图形服务器时,QEMU 将将图形输出重定向到其内置的 VNC 服务器实现。VNC 服务器将监听 VNC 客户端可以连接的网络端口。

以下屏幕截图显示了如何添加 VNC 图形服务器。只需转到虚拟机管理器,打开虚拟机的设置,然后转到左侧的显示 Spice选项卡:

图 6.12 - 用于 KVM 虚拟机的 VNC 配置

图 6.12 - 用于 KVM 虚拟机的 VNC 配置

添加 VNC 图形时,您将看到前面截图中显示的选项:

  • 类型:图形服务器的类型。这里是VNC 服务器

  • 地址:VNC 服务器监听地址。它可以是全部、本地主机或 IP 地址。默认情况下,它是仅本地主机

  • 端口:VNC 服务器监听端口。您可以选择自动,其中 libvirt 根据可用性定义端口,或者您可以自己定义一个。确保它不会产生冲突。

  • 密码:保护 VNC 访问的密码。

  • virt-xml命令行工具。

例如,让我们向名为PacktGPUPass的虚拟机添加 VNC 图形,然后修改其 VNC 监听 IP 为192.168.122.1

# virt-xml MasteringKVM03 --add-device --graphics type=vnc
# virt-xml MasteringKVM03 --edit --graphics listen=192.168.122.1

这是在PacktVM01 XML 配置文件中的外观:

<graphics type='vnc' port='-1' autoport='yes' listen='192.168.122.1'>
    <listen type='address' address='192.168.122.1'/>
</graphics>

您还可以使用virsh编辑PacktGPUPass并单独更改参数。

为什么使用 VNC?

当您在局域网上访问虚拟机或直接从控制台访问 VM 时,可以使用 VNC。使用 VNC 在公共网络上暴露虚拟机不是一个好主意,因为连接没有加密。如果虚拟机是没有安装 GUI 的服务器,VNC 是一个不错的选择。另一个支持 VNC 的点是客户端的可用性。您可以从任何操作系统平台访问虚拟机,因为该平台将有适用于该平台的 VNC 查看器。

使用 SPICE 显示协议

与 KVM 一样,独立计算环境的简单协议SPICE)是进入开源虚拟化技术的最佳创新之一。它推动了开源虚拟化技术向大规模虚拟桌面基础设施VDI)的实施。

重要说明

Qumranet 最初在 2007 年将 SPICE 作为闭源代码库开发。Red Hat,Inc.在 2008 年收购了 Qumranet,并于 2009 年 12 月决定在开源许可下发布代码并将协议视为开放标准。

SPICE 是 Linux 上唯一可用的开源解决方案,可以实现双向音频。它具有高质量的 2D 渲染能力,可以利用客户端系统的视频卡。SPICE 还支持多个高清监视器、加密、智能卡身份验证、压缩和网络上传输的 USB。有关完整的功能列表,您可以访问www.spice-space.org/features.html。如果您是开发人员,并且想了解 SPICE 的内部情况,请访问www.spice-space.org/documentation.html。如果您计划进行 VDI 或安装需要 GUI 的虚拟机,SPICE 是您的最佳选择。

在某些较旧的虚拟机上,SPICE 可能与一些较旧的虚拟机不兼容,因为它们不支持 QXL。在这些情况下,您可以将 SPICE 与其他通用虚拟视频卡一起使用。

现在,让我们学习如何向我们的虚拟机添加 SPICE 图形服务器。这可以被认为是开源世界中性能最佳的虚拟显示协议。

添加 SPICE 图形服务器

Libvirt 现在选择 SPICE 作为大多数虚拟机安装的默认图形服务器。您必须按照我们之前提到的 VNC 相同的程序来添加 SPICE 图形服务器。只需在下拉菜单中将 VNC 更改为 SPICE。在这里,您将获得一个额外的选项来选择TLS 端口,因为 SPICE 支持加密:

图 6.13–KVM 虚拟机的 SPICE 配置

图 6.13–KVM 虚拟机的 SPICE 配置

要进入此配置窗口,只需编辑虚拟机的设置。转到显示 Spice选项,并从下拉菜单中选择Spice 服务器。所有其他选项都是可选的,因此您不一定需要进行任何其他配置。

完成了上述步骤后,我们已经涵盖了有关显示协议的所有必要主题。现在让我们讨论一下我们可以使用的各种方法来访问虚拟机控制台。

访问虚拟机控制台的方法

有多种方法可以连接到虚拟机控制台。如果您的环境具有完整的图形用户界面访问权限,那么最简单的方法就是使用 virt-manager 控制台本身。virt-viewer是另一个工具,可以让您访问虚拟机控制台。如果您尝试从远程位置访问虚拟机控制台,则此工具非常有用。在以下示例中,我们将连接到具有 IP192.168.122.1的远程 hypervisor。连接通过 SSH 会话进行隧道传输,并且是安全的。

第一步是在客户端系统和 hypervisor 之间建立一个无密码的身份验证系统:

  1. 在客户端机器上,使用以下代码:
# ssh-keygen
# ssh-copy-id root@192.168.122.1
# virt-viewer -c qemu+ssh://root@192.168.122.1/system

您将看到 hypervisor 上可用的虚拟机列表。选择要访问的虚拟机,如下截图所示:

图 6.14 - 用于虚拟机访问的 virt-viewer 选择菜单

图 6.14 - 用于虚拟机访问的 virt-viewer 选择菜单

  1. 要直接连接到 VM 的控制台,请使用以下命令:
virsh – to be more specific, virsh console vm_name. This needs some additional configuration inside the virtual machine OS, as described in the following steps.
  1. 如果您的 Linux 发行版使用 GRUB(而不是 GRUB2),请将以下行附加到/boot/grub/grub.conf中现有的引导内核行,并关闭虚拟机:
console=tty0 console=ttyS0,115200

如果您的 Linux 发行版使用 GRUB2,则步骤会变得有点复杂。请注意,以下命令已在 Fedora 22 虚拟机上进行了测试。对于其他发行版,配置 GRUB2 的步骤可能会有所不同,尽管 GRUB 配置文件所需的更改应保持不变:

# cat /etc/default/grub (only relevant variables are shown)
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="rd.lvm.lv=fedora/swap rd.lvm.lv=fedora/root rhgb quiet"

更改后的配置如下:

# cat /etc/default/grub (only relevant variables are shown)
GRUB_TERMINAL_OUTPUT="serial console"
GRUB_CMDLINE_LINUX="rd.lvm.lv=fedora/swap rd.lvm.lv=fedora/root console=tty0 console=ttyS0"
# grub2-mkconfig -o /boot/grub2/grub.cfg
  1. 现在,关闭虚拟机。然后使用virsh再次启动它:
# virsh shutdown PacktGPUPass
# virsh start PacktGPUPass --console
  1. 运行以下命令以连接到已启动的虚拟机控制台:
# virsh console PacktGPUPass

您也可以从远程客户端执行此操作,如下所示:

# virsh -c qemu+ssh://root@192.168.122.1/system console PacktGPUPass
Connected to domain PacktGPUPass:
Escape character is ^]

在某些情况下,我们发现控制台命令卡在^]。要解决此问题,请多次按Enter键以查看登录提示。有时,当您想要捕获用于故障排除目的的引导消息时,配置文本控制台非常有用。使用ctrl +]退出控制台。

我们的下一个主题将带我们进入 noVNC 的世界,这是另一种基于 VNC 的协议,它比常规VNC 具有一些主要优势。现在让我们讨论这些优势以及 noVNC 的实现。

使用 noVNC 实现显示可移植性

所有这些显示协议都依赖于能够访问某种类型的客户端应用程序和/或附加软件支持,这将使我们能够访问虚拟机控制台。但是当我们无法访问所有这些附加功能时会发生什么?当我们只能以文本模式访问我们的环境时,但我们仍然希望以基于 GUI 的方式管理对我们的虚拟机的连接时会发生什么?

输入 noVNC,这是一个基于 HTML5 的 VNC 客户端,您可以通过兼容 HTML5 的 Web 浏览器使用,这只是对市场上几乎每个Web 浏览器的花哨说法。它支持所有最流行的浏览器,包括移动浏览器,以及许多其他功能,例如以下内容:

  • 剪贴板复制粘贴

  • 支持分辨率缩放和调整大小

  • 它在 MPL 2.0 许可下免费

  • 安装它相当容易,并支持身份验证,并且可以通过 HTTPS 轻松实现安全性

如果要使 noVNC 工作,您需要两样东西:

  • 已配置为接受 VNC 连接的虚拟机,最好进行了一些配置 - 例如设置了密码和正确设置的网络接口以连接到虚拟机。您可以自由使用tigervnc-server,将其配置为接受特定用户的连接 - 例如 - 在端口5901上,并使用该端口和服务器的 IP 地址进行客户端连接。

  • 在客户端计算机上安装 noVNC,您可以从 EPEL 存储库下载,也可以作为zip/tar.gz软件包直接从 Web 浏览器运行。要安装它,我们需要输入以下一系列命令:

yum -y install novnc
cd /etc/pki/tls/certs
openssl req -x509 -nodes -newkey rsa:2048 -keyout /etc/pki/tls/certs/nv.pem -out /etc/pki/tls/certs/nv.pem -days 365
websockify -D --web=/usr/share/novnc --cert=/etc/pki/tls/certs/nv.pem 6080 localhost:5901 

最终结果将看起来像这样:

图 6.15 - noVNC 控制台配置屏幕

在这里,我们可以使用我们的 VNC 服务器密码来访问特定的控制台。输入密码后,我们会得到这个:

图 6.16 - noVNC 控制台实际操作 - 我们可以看到虚拟机控制台并使用它来处理我们的虚拟机

图 6.16 - noVNC 控制台实际操作 - 我们可以看到虚拟机控制台并使用它来处理我们的虚拟机

我们也可以在 oVirt 中使用所有这些选项。在安装 oVirt 时,我们只需要在 engine-setup 阶段选择一个额外的选项:

--otopi-environment="OVESETUP_CONFIG/websocketProxyConfig=bool:True"

此选项将使 oVirt 能够使用 noVNC 作为远程显示客户端,除了现有的 SPICE 和 VNC。

让我们看一个在 oVirt 中配置虚拟机的示例,几乎包括了本章讨论的所有选项。特别注意监视器配置选项:

图 6.17 - oVirt 还支持本章讨论的所有设备

图 6.17 - oVirt 还支持本章讨论的所有设备

如果我们点击图形协议子菜单,我们将得到使用 SPICE、VNC、noVNC 和各种组合的选项。此外,在屏幕底部,我们还有可用的选项,用于我们想要在远程显示中看到的显示器数量。如果我们想要一个高性能的多显示远程控制台,这可能非常有用。

鉴于 noVNC 已经集成到 noVNC 中,您可以将其视为未来的迹象。从这个角度来看 - IT 中与管理应用程序相关的一切已经稳步地转移到基于 Web 的应用程序多年了。同样的事情发生在虚拟机控制台上也是合乎逻辑的。其他供应商的解决方案也已经实施了这一点,因此在这里使用 noVNC 不应该是一个大惊喜。

总结

在本章中,我们涵盖了虚拟显示设备和用于显示虚拟机数据的协议。我们还深入研究了 GPU 共享和 GPU 直通的世界,这是大规模运行 VDI 的虚拟化环境中的重要概念。我们讨论了这些情景的一些好处和缺点,因为它们往往相当复杂,需要大量资源,包括财政资源。想象一下,为 100 台虚拟机进行 2D/3D 加速的 PCI 直通。这实际上需要购买 100 张显卡,这在财务上是一个很大的要求。在我们讨论的其他主题中,我们讨论了可以用于控制台访问我们的虚拟机的各种显示协议和选项。

在下一章中,我们将带您了解一些常规虚拟机操作 - 安装、配置和生命周期管理,包括讨论快照和虚拟机迁移。

问题

  1. 我们可以使用哪些类型的虚拟机显示设备?

  2. 使用 QXL 虚拟显示设备与 VGA 相比的主要好处是什么?

  3. GPU 共享的好处和缺点是什么?

  4. GPU PCI 直通的好处是什么?

  5. SPICE 相对于 VNC 的主要优势是什么?

  6. 为什么要使用 noVNC?

进一步阅读

有关本章内容的更多信息,请参考以下链接:

第七章:虚拟机:安装、配置和生命周期管理

在本章中,我们将讨论安装和配置virt-managervirt-install、oVirt 的不同方式,并建立在前几章中获得的知识基础上。然后,我们将对虚拟机迁移进行详细讨论,这是虚拟化的最基本方面之一,因为几乎无法想象在没有迁移选项的情况下使用虚拟化。为了能够为虚拟机迁移配置我们的环境,我们还将使用第四章中讨论的主题,Libvirt 网络,以及第五章中讨论的主题,Libvirt 存储,因为虚拟机迁移需要满足一些先决条件。

在本章中,我们将涵盖以下主题:

  • 使用virt-manager创建新的虚拟机,使用virt命令

  • 使用 oVirt 创建新的虚拟机

  • 配置您的虚拟机

  • 向虚拟机添加和删除虚拟硬件

  • 迁移虚拟机

使用 virt-manager 创建新的虚拟机

virt-manager(用于管理虚拟机的图形界面工具)和virt-install(用于管理虚拟机的命令行实用程序)是virt-*命令堆栈中最常用的实用程序之一,非常有用。

让我们从virt-manager及其熟悉的图形界面开始。

使用 virt-manager

virt-manager是管理 KVM 虚拟机的首选图形界面实用程序。它非常直观和易于使用,尽管在功能上有点欠缺,我们稍后会描述一下。这是主virt-manager窗口:

图 7.1 - 主 virt-manager 窗口

图 7.1 - 主 virt-manager 窗口

从这个屏幕截图中,我们已经可以看到在此服务器上安装了三个虚拟机。我们可以使用顶级菜单(文件编辑查看帮助)进一步配置我们的 KVM 服务器和/或虚拟机,以及连接到网络上的其他 KVM 主机,如下面的屏幕截图所示:

图 7.2 - 使用“添加连接...”选项连接到其他 KVM 主机

图 7.2 - 使用“添加连接...”选项连接到其他 KVM 主机

选择virt-manager后。该过程如下截图所示:

图 7.3 - 连接到远程 KVM 主机

图 7.3 - 连接到远程 KVM 主机

此时,您可以通过右键单击主机名并选择新建来自由在远程 KVM 主机上安装虚拟机,如果选择这样做,如下截图所示:

图 7.4 - 在远程 KVM 主机上创建新的虚拟机

图 7.4 - 在远程 KVM 主机上创建新的虚拟机

由于此向导与在本地服务器上安装虚拟机的向导相同,我们将一次性涵盖这两种情况。新建虚拟机向导的第一步是选择您要从哪里安装虚拟机。如下截图所示,有四个可用选项:

图 7.5 - 选择引导介质

图 7.5 - 选择引导介质

选择如下:

  • 如果您已经在本地计算机上(或作为物理设备)有一个国际标准化组织ISO)文件可用,请选择第一个选项。

  • 如果您想从网络安装,请选择第二个选项。

  • 如果您在环境中设置了预引导执行环境PXE)引导,并且可以从网络引导您的虚拟机安装,请选择第三个选项。

  • 如果您有一个虚拟机磁盘,并且只想将其作为底层定义为虚拟机,请选择第四个选项。

通常,我们谈论网络安装(第二个选项)或 PXE 引导网络安装(第三个选项),因为这些是生产中最常见的用例。原因非常简单 - 没有理由在 ISO 文件上浪费本地磁盘空间,而这些文件现在相当大。例如,CentOS 8 v1905 ISO 文件大约为 8 GB。如果需要能够安装多个操作系统,甚至是这些操作系统的多个版本,最好使用一种仅用于 ISO 文件的集中存储空间。

在基于 VMware ESX 集成ESXi)的基础设施中,人们通常使用 ISO 数据存储或内容库来实现此功能。在基于 Microsoft Hyper-V 的基础设施中,人们通常拥有一个用于 VM 安装所需 ISO 文件的服务器消息块SMB)文件共享。每台主机都拷贝一个操作系统 ISO 文件是毫无意义的,因此一种共享的方法更加方便,也是一个很好的节省空间的机制。

假设我们正在从网络(超文本传输协议HTTP)、超文本传输安全协议HTTPS)或文件传输协议FTP))安装 VM。我们需要一些东西来继续,如下所示:

  • 一个8.x.x目录,然后转到BaseOS/x86_64/os

  • 显然,需要一个功能正常的互联网连接,尽可能快,因为我们将从前面的 URL 下载所有必要的安装包。

  • 可选地,我们可以展开URL 选项三角形,并使用内核行的附加选项,最常见的是使用类似以下内容的 kickstart 选项:

ks=http://kickstart_file_url/file.ks

因此,让我们输入如下内容:

图 7.6 - URL 和客户操作系统选择

图 7.6 - URL 和客户操作系统选择

请注意,我们手动选择的virt-manager目前不认识我们指定的 URL 中的 CentOS 8(1905)作为客户操作系统。如果操作系统在当前识别的操作系统列表中,我们可以只需选择从安装媒体/源自动检测复选框,有时需要多次重新检查和取消检查才能使其正常工作。

点击前进按钮后,我们需要为此 VM 设置内存和中央处理单元CPU)设置。同样,您可以选择两种不同的方向,如下所示:

  • 选择最少的资源(例如,1 个虚拟 CPUvCPU)和 1GB 内存),然后根据需要更改。

  • 选择适量的资源(例如,2 个 vCPU 和 4GB 内存),并考虑特定的用途。例如,如果此 VM 的预期用途是文件服务器,如果添加 16 个 vCPU 和 64GB 内存,性能将不会很好,但在其他用例中可能会适用。

下一步是配置 VM 存储。如下截图所示,有两个可用选项:

图 7.7 - 配置 VM 存储

图 7.7 - 配置 VM 存储

为 VM 选择一个合适的存储设备非常重要,因为如果你不这样做,将来可能会遇到各种问题。例如,如果你在生产环境中将 VM 放在错误的存储设备上,你将不得不将该 VM 的存储迁移到另一个存储设备,这是一个繁琐且耗时的过程,会对你的 VM 产生一些不好的副作用,特别是如果你的源或目标存储设备上有大量的 VM 在运行。首先,它会严重影响它们的性能。然后,如果你的环境中有一些动态工作负载管理机制,它可能会触发基础设施中的额外 VM 或 VM 存储移动。像 VMware 的分布式资源调度器DRS)/存储 DRS,带有System Center Operations ManagerSCOM)集成的 Hyper-V 性能和资源优化,以及 oVirt/Red Hat Enterprise Virtualization 集群调度策略等功能就是这样做的。因此,采用三思而后行的策略可能是正确的方法。

如果你选择第一个可用选项,virt-manager将在其默认位置创建一个 VM 硬盘——在/var/lib/libvirt/images目录中。确保你有足够的空间来存放你的 VM 硬盘。假设我们在/var/lib/libvirt/images目录及其底层分区中有 8GB 的可用空间。如果我们保持前面截图中的一切不变,我们会收到一个错误消息,因为我们试图在只有 8GB 可用的本地磁盘上创建一个 10GB 的文件。

在我们点击virt-manager之后,在安装过程之前自定义配置,并选择 VM 将使用的虚拟网络。我们将在本章稍后讨论 VM 的硬件定制。当你点击完成时,如下截图所示,你的 VM 将准备好部署,并且在我们安装操作系统后使用:

图 7.8 – 最终 virt-manager 配置步骤

图 7.8 – 最终 virt-manager 配置步骤

使用virt-manager创建一些 VM 绝对不是一项困难的任务,但在现实生产环境中,你不一定会在服务器上找到 GUI。因此,我们的逻辑下一个任务是了解命令行工具来管理 VM——具体来说是virt-*命令。让我们接着做。

使用 virt-*命令

如前所述,我们需要学习一些新的命令来掌握基本 VM 管理任务。为了这个特定的目的,我们有一堆virt-*命令。让我们简要地介绍一些最重要的命令,并学习如何使用它们。

virt-viewer

由于我们之前已经大量使用了virt-install命令(查看第三章安装基于内核的虚拟机(KVM)超级监视器,libvirt 和 ovirt,我们使用这个命令安装了相当多的 VM),我们将覆盖剩下的命令。

让我们从virt-viewer开始,因为我们之前使用过这个应用程序。每次我们在virt-viewer中双击一个虚拟机,我们就打开了一个虚拟机控制台,这恰好是这个过程背后的virt-viewer。但是如果我们想要从 shell 中使用virt-viewer——就像人们经常做的那样——我们需要一些关于它的更多信息。所以,让我们举几个例子。

首先,让我们通过运行以下命令连接到一个名为MasteringKVM01的本地 KVM,它位于我们当前以root连接的主机上:

# virt-viewer --connect qemu:///system MasteringKVM01

我们还可以以kiosk模式连接到 VM,这意味着当我们关闭连接的 VM 时,virt-viewer也会关闭。要做到这一点,我们将运行以下命令:

# virt-viewer --connect qemu:///system MasteringKVM01 --kiosk --kiosk-quit on-disconnect

如果我们需要连接到远程主机,我们也可以使用virt-viewer,但我们需要一些额外的选项。连接到远程系统的最常见方式是通过 SSH,所以我们可以这样做:

# virt-viewer --connect qemu+ssh://username@remote-host/system VirtualMachineName

如果我们配置了 SSH 密钥并将它们复制到username@remote-host,这个前面的命令就不会要求我们输入密码。但如果没有,它将会要求我们输入密码两次——一次是建立与 hypervisor 的连接,另一次是建立与 VM Virtual Network Computing (VNC) 会话的连接。

virt-xml

我们列表中的下一个命令行实用程序是virt-xml。我们可以使用它与virt-install命令行选项来更改 VM 配置。让我们从一个基本的例子开始——让我们只是为 VM 启用引导菜单,如下所示:

# virt-xml MasgteringKVM04 --edit --boot bootmenu=on

然后,让我们向 VM 添加一个薄配置的磁盘,分三步——首先,创建磁盘本身,然后将其附加到 VM,并检查一切是否正常工作。输出可以在下面的截图中看到:

图 7.9 – 向 VM 添加一个薄配置 QEMU 写时复制(qcow2)格式的虚拟磁盘

图 7.9 – 向 VM 添加一个薄配置 QEMU 写时复制(qcow2)格式的虚拟磁盘

正如我们所看到的,virt-xml非常有用。通过使用它,我们向我们的 VM 添加了另一个虚拟磁盘,这是它可以做的最简单的事情之一。我们可以使用它向现有的 VM 部署任何额外的 VM 硬件。我们还可以使用它编辑 VM 配置,在较大的环境中特别方便,特别是当你必须对这样的过程进行脚本化和自动化时。

virt-clone

现在让我们通过几个例子来检查virt-clone。假设我们只是想要一种快速简单的方式来克隆现有的 VM 而不需要任何额外的麻烦。我们可以这样做:

# virt-clone --original VirtualMachineName --auto-clone

结果,这将产生一个名为VirtualMachineName-clone的 VM,我们可以立即开始使用。让我们看看这个过程,如下所示:

图 7.10 – 使用 virt-clone 创建 VM 克隆

图 7.10 – 使用 virt-clone 创建 VM 克隆

让我们看看如何使这个更加定制化。通过使用virt-clone,我们将创建一个名为MasteringKVM05的 VM,克隆一个名为MasteringKVM04的 VM,并且我们还将自定义虚拟磁盘名称,如下面的截图所示:

图 7.11 – 自定义 VM 创建:自定义 VM 名称和虚拟硬盘文件名

图 7.11 – 自定义 VM 创建:自定义 VM 名称和虚拟硬盘文件名

在现实生活中,有时需要将 VM 从一种虚拟化技术转换为另一种。其中大部分工作实际上是将 VM 磁盘格式从一种格式转换为另一种格式。这就是virt-convert的工作原理。让我们学习一下它是如何工作的。

qemu-img

现在让我们看看如何将一个虚拟磁盘转换为另一种格式,以及如何将一个 VM 配置文件从一种虚拟化方法转换为另一种。我们将使用一个空的 VMware VM 作为源,并将其vmdk虚拟磁盘和.vmx文件转换为新格式,如下面的截图所示:

图 7.12 – 将 VMware 虚拟磁盘转换为 KVM 的 qcow2 格式

图 7.12 – 将 VMware 虚拟磁盘转换为 KVM 的 qcow2 格式

如果我们面对需要在这些平台之间移动或转换 VM 的项目,我们需要确保使用这些实用程序,因为它们易于使用和理解,只需要一点时间。例如,如果我们有一个 1 qcow2格式,所以我们必须耐心等待。此外,我们需要随时准备好编辑vmx配置文件,因为从vmxkvm格式的转换过程并不是 100%顺利,正如我们可能期望的那样。在这个过程中,会创建一个新的配置文件。KVM VM 配置文件的默认目录是/etc/libvirt/qemu,我们可以轻松地看到virsh列表输出。

在 CentOS 8 中还有一些新的实用工具,这些工具将使我们更容易管理不仅本地服务器还有 VM。Cockpit web 界面就是其中之一——它具有在 KVM 主机上进行基本 VM 管理的功能。我们只需要通过 Web 浏览器连接到它,我们在第三章中提到过这个 Web 应用程序,安装基于内核的 VM(KVM)Hypervisor,libvirt 和 ovirt,当讨论 oVirt 设备的部署时。因此,让我们通过使用 Cockpit 来熟悉 VM 管理。

使用 Cockpit 创建新的 VM

要使用 Cockpit 管理我们的服务器及其 VM,我们需要安装和启动 Cockpit 及其附加包。让我们从那开始,如下所示:

yum -y install cockpit*
systemctl enable --now cockpit.socket

在此之后,我们可以启动 Firefox 并将其指向https://kvm-host:9090/,因为这是 Cockpit 可以访问的默认端口,并使用 root 密码登录为root,这将给我们以下用户界面UI):

图 7.14 – Cockpit web 控制台,我们可以用它来部署 VM

图 7.14 – Cockpit web 控制台,我们可以用它来部署 VM

在上一步中,当我们安装了cockpit*时,我们还安装了cockpit-machines,这是 Cockpit web 控制台的一个插件,它使我们能够在 Cockpit web 控制台中管理libvirt VM。因此,在我们点击VMs后,我们可以轻松地看到我们以前安装的所有 VM,打开它们的配置,并通过简单的向导安装新的 VM,如下面的屏幕截图所示:

图 7.15 – Cockpit VM 管理

图 7.15 – Cockpit VM 管理

VM 安装向导非常简单——我们只需要为我们的新 VM 配置基本设置,然后我们就可以开始安装,如下所示:

图 7.16 – 从 Cockpit web 控制台安装 KVM VM

图 7.16 – 从 Cockpit web 控制台安装 KVM VM

现在我们已经了解了如何本地安装 VM——意味着没有某种集中管理应用程序,让我们回过头来看看如何通过 oVirt 安装 VM。

使用 oVirt 创建新的 VM

如果我们将主机添加到 oVirt,当我们登录时,我们可以转到Compute-VMs,并通过简单的向导开始部署 VM。因此,在该菜单中点击New按钮后,我们可以这样做,然后我们将被带到以下屏幕:

图 7.17 – oVirt 中的新 VM 向导

图 7.17 – oVirt 中的新 VM 向导

考虑到 oVirt 是 KVM 主机的集中管理解决方案,与在 KVM 主机上进行本地 VM 安装相比,我们有大量的额外选项——我们可以选择一个将托管此 VM 的集群;我们可以使用模板,配置优化和实例类型,配置高可用性HA),资源分配,引导选项...基本上,这就是我们开玩笑称之为选项麻痹,尽管这对我们自己有利,因为集中化解决方案总是与任何一种本地解决方案有些不同。

至少,我们将不得不配置一般的 VM 属性——名称、操作系统和 VM 网络接口。然后,我们将转到System选项卡,在那里我们将配置内存大小和虚拟 CPU 数量,如下面的屏幕截图所示:

图 7.18 – 选择 VM 配置:虚拟 CPU 和内存

图 7.18 – 选择 VM 配置:虚拟 CPU 和内存

我们肯定会想要配置引导选项——连接 CD/ISO,添加虚拟硬盘,并配置引导顺序,如下面的屏幕截图所示:

图 7.19 – 在 oVirt 中配置 VM 引导选项

图 7.19 – 在 oVirt 中配置 VM 引导选项

我们可以使用sysprepcloud-init来自定义 VM 的安装后设置,我们将在第九章中讨论,使用 cloud-init 自定义 VM

以下是 oVirt 中基本的配置外观:

图 7.20-从 oVirt 安装 KVM VM:确保选择正确的启动选项

图 7.20-从 oVirt 安装 KVM VM:确保选择正确的启动选项

实际上,如果您管理的环境有两到三个以上的 KVM 主机,您会希望使用某种集中式实用程序来管理它们。oVirt 非常适合这一点,所以不要跳过它。

现在我们已经以各种不同的方式完成了整个部署过程,是时候考虑 VM 配置了。请记住,VM 是一个具有许多重要属性的对象,例如虚拟 CPU 的数量、内存量、虚拟网络卡等,因此学习如何自定义 VM 设置非常重要。所以,让我们把它作为下一个主题。

配置您的 VM

当我们使用virt-manager时,如果您一直进行到最后一步,您可以选择一个有趣的选项,即在安装前自定义配置选项。如果您在安装后检查 VM 配置,也可以访问相同的配置窗口。因此,无论我们选择哪种方式,我们都将面临为分配给我们刚创建的 VM 的每个 VM 硬件设备的全面配置选项,如下截图所示:

图 7.21-VM 配置选项

图 7.21-VM 配置选项

例如,如果我们在左侧点击CPU选项,您将看到可用 CPU 的数量(当前和最大分配),还将看到一些非常高级的选项,例如CPU 拓扑插槽/核心/线程),它使我们能够配置特定的非均匀内存访问NUMA)配置选项。这就是该配置窗口的样子:

图 7.22-VM CPU 配置

图 7.22-VM CPU 配置

这是 VM 配置的非常重要部分,特别是如果您正在设计一个承载大量虚拟服务器的环境。此外,如果虚拟化服务器承载输入/输出I/O)密集型应用程序,例如数据库,这一点变得更加重要。如果您想了解更多信息,可以在本章末尾的进一步阅读部分中查看链接,它将为您提供有关 VM 设计的大量额外信息。

然后,如果我们打开virt-*命令。这是virt-manager 内存配置选项的外观:

图 7.23-VM 内存配置

图 7.23-VM 内存配置

virt-manager中最重要的配置选项集之一位于启动选项子菜单中,如下截图所示:

图 7.24-VM 启动配置选项

图 7.24-VM 启动配置选项

在那里,您可以做两件非常重要的事情,如下所示:

  • 选择此 VM 在主机启动时自动启动

  • 启用启动菜单并选择启动设备和启动设备优先级

就配置选项而言,virt-manager中功能最丰富的配置菜单是虚拟存储菜单,即我们的情况下的VirtIO Disk 1。如果我们点击它,我们将得到以下配置选项的选择:

图 7.25-配置 VM 硬盘和存储控制器选项

图 7.25-配置 VM 硬盘和存储控制器选项

让我们看看其中一些配置选项的重要性,如下所示:

  • 磁盘总线 - 这里通常有五个选项,VirtIO是默认(也是最好的)选项。与 Vmware、ESXi 和 Hyper-V 一样,KVM 有不同的虚拟存储控制器可用。例如,VMware 有 BusLogic、LSI Logic、Paravirtual 和其他类型的虚拟存储控制器,而 Hyper-V 有集成驱动电子学IDE)和小型计算机系统接口SCSI)控制器。此选项定义了 VM 在其客户操作系统中将看到的存储控制器。

  • qcow2rawdd类型格式)。最常见的选项是qcow2,因为它为 VM 管理提供了最大的灵活性 - 例如,它支持薄配置和快照。

  • 缓存模式 - 有六种类型:writethroughwritebackdirectsyncunsafenonedefault。这些模式解释了从 VM 发起的 I/O 如何从 VM 下面的存储层写入数据。例如,如果我们使用writethrough,I/O 会被缓存在 KVM 主机上,并且也会通过写入到 VM 磁盘。另一方面,如果我们使用none,主机上没有缓存(除了磁盘writeback缓存),数据直接写入 VM 磁盘。不同的模式有不同的优缺点,但通常来说,none是 VM 管理的最佳选择。您可以在进一步阅读部分了解更多信息。

  • IO模式 - 有两种模式:nativethreads。根据此设置,VM I/O 将通过内核异步 I/O 或用户空间中的线程池进行写入(这是默认值)。当使用qcow2格式时,通常认为threads模式更好,因为qcow2格式首先分配扇区,然后写入它们,这将占用分配给 VM 的 vCPU,并直接影响 I/O 性能。

  • 丢弃模式 - 这里有两种可用模式,称为忽略取消映射。如果选择取消映射,当您从 VM 中删除文件(这会转换为qcow2 VM 磁盘文件中的可用空间),qcow2 VM 磁盘文件将缩小以反映新释放的容量。取决于您使用的 Linux 发行版、内核和内核补丁以及快速仿真器QEMU)版本,此功能可能仅适用于 SCSI 磁盘总线。它支持 QEMU 版本 4.0+。

  • 检测零 - 有三种可用模式:关闭打开取消映射。如果您选择取消映射,零写入将被转换为取消映射操作(如丢弃模式中所解释的)。如果将其设置为打开,操作系统的零写入将被转换为特定的零写入命令。

在任何给定 VM 的寿命期内,有很大的机会我们会重新配置它。无论是添加还是删除虚拟硬件(当然,通常是添加),这是 VM 生命周期的一个重要方面。因此,让我们学习如何管理它。

从 VM 添加和删除虚拟硬件

通过使用 VM 配置屏幕,我们可以轻松添加额外的硬件,或者删除硬件。例如,如果我们点击左下角的添加硬件按钮,我们可以轻松添加一个设备 - 比如,一个虚拟网络卡。以下截图说明了这个过程:

图 7.26 - 点击“添加硬件”后,我们可以选择要要添加到我们的 VM 的虚拟硬件设备

图 7.26 - 点击“添加硬件”后,我们可以选择要添加到虚拟机的虚拟硬件设备

另一方面,如果我们选择一个虚拟硬件设备(例如Sound ich6)并按下随后出现的删除按钮,我们也可以删除这个虚拟硬件设备,确认我们要这样做后,如下截图所示:

图 7.27 - 删除 VM 硬件设备的过程:在左侧并单击删除

图 7.27 – 删除虚拟机硬件设备的流程:在左侧选择它,然后单击删除

正如您所看到的,添加和删除虚拟机硬件就像 123 一样简单。我们之前确实提到过这个话题,当时我们正在处理虚拟网络和存储(第四章Libvirt 网络),但那里,我们使用了 shell 命令和 XML 文件定义。如果您想了解更多,请查看这些示例。

虚拟化的关键在于灵活性,能够在我们的环境中将虚拟机放置在任何给定的主机上是其中的重要部分。考虑到这一点,虚拟机迁移是虚拟化中可以用作营销海报的功能之一,它有许多优势。虚拟机迁移到底是什么?这就是我们接下来要学习的内容。

迁移虚拟机

简单来说,迁移使您能够将虚拟机从一台物理机器移动到另一台物理机器,几乎没有或没有任何停机时间。我们还可以移动虚拟机存储,这是一种资源密集型的操作,需要仔细规划,并且—如果可能—在工作时间之后执行,以便它不会像可能影响其他虚拟机的性能那样影响其他虚拟机的性能。

有各种不同类型的迁移,如下:

  • 离线(冷)

  • 在线(实时)

  • 暂停迁移

还有各种不同类型的在线迁移,具体取决于您要移动的内容,如下:

  • 虚拟机的计算部分(将虚拟机从一个 KVM 主机移动到另一个 KVM 主机)

  • 虚拟机的存储部分(将虚拟机文件从一个存储池移动到另一个存储池)

  • 两者(同时将虚拟机从主机迁移到主机和从存储池迁移到存储池)

如果您只是使用普通的 KVM 主机,与使用 oVirt 或 Red Hat 企业虚拟化相比,支持的迁移场景有一些差异。如果您想进行实时存储迁移,您不能直接在 KVM 主机上执行,但如果虚拟机已关闭,则可以轻松执行。如果您需要进行实时存储迁移,您将需要使用 oVirt 或 Red Hat 企业虚拟化。

我们还讨论了单根输入输出虚拟化SR-IOV)、外围组件互连PCI)设备透传、虚拟图形处理单元vGPUs)等概念(在第二章中,KVM 作为虚拟化解决方案,以及第四章中,Libvirt 网络)。在 CentOS 8 中,您不能对具有这些选项之一分配给运行中的虚拟机的虚拟机进行实时迁移。

无论用例是什么,我们都需要意识到迁移需要以root用户或属于libvirt用户组的用户(Red Hat 所称的系统与用户libvirt会话)执行。

虚拟机迁移是一个有价值的工具的原因有很多。有些原因很明显,而其他原因则不那么明显。让我们尝试解释虚拟机迁移的不同用例和其好处。

虚拟机迁移的好处

虚拟机实时迁移的最重要的好处如下:

  • 增加的正常运行时间和减少的停机时间—精心设计的虚拟化环境将为您的应用程序提供最大的正常运行时间。

  • 节约能源,走向绿色—您可以根据虚拟机的负载和使用情况在非工作时间将它们合并到较少的虚拟化主机上。一旦虚拟机迁移完成,您可以关闭未使用的虚拟化主机。

  • 通过在不同的虚拟化主机之间移动您的虚拟机,轻松进行硬件/软件升级过程—一旦您有能力在不同的物理服务器之间自由移动您的虚拟机,好处是无穷无尽的。

虚拟机迁移需要适当的规划。迁移有一些基本要求。让我们逐一看看它们。

生产环境的迁移要求如下:

  • VM 应该使用在共享存储上创建的存储池。

  • 存储池的名称和虚拟磁盘的路径应该在两个超级主机(源和目标超级主机)上保持相同。

查看第四章Libvirt 网络,以及第五章Libvirt 存储,以便回顾如何使用共享存储创建存储池。

这里总是有一些适用的规则。这些规则相当简单,所以我们需要在开始迁移过程之前学习它们。它们如下:

  • 可以使用在非共享存储上创建的存储池进行实时存储迁移。您只需要保持相同的存储池名称和文件位置,但在生产环境中仍建议使用共享存储。

  • 如果连接到使用光纤通道FC)、Internet 小型计算机系统接口iSCSI)、逻辑卷管理器LVM)等的 VM 的未管理虚拟磁盘,则相同的存储应该在两个超级主机上都可用。

  • VM 使用的虚拟网络应该在两个超级主机上都可用。

  • 为网络通信配置的桥接应该在两个超级主机上都可用。

  • 如果超级主机上的libvirtqemu-kvm的主要版本不同,迁移可能会失败,但您应该能够将运行在具有较低版本libvirtqemu-kvm的超级主机上的 VM 迁移到具有这些软件包较高版本的超级主机上,而不会出现任何问题。

  • 源和目标超级主机上的时间应该同步。强烈建议您使用相同的网络时间协议NTP)或精密时间协议PTP)服务器同步超级主机。

  • 重要的是系统使用/etc/hosts将无法工作。您应该能够使用host命令解析主机名。

在为 VM 迁移规划环境时,我们需要牢记一些先决条件。在大多数情况下,这些先决条件对所有虚拟化解决方案都是相同的。让我们讨论这些先决条件,以及如何为 VM 迁移设置环境。

设置环境

让我们构建环境来进行 VM 迁移 - 离线和实时迁移。以下图表描述了两个标准的 KVM 虚拟化主机,运行具有共享存储的 VM:

图 7.28 - 共享存储上的 VM

图 7.28 - 共享存储上的 VM

我们首先通过设置共享存储来开始。在本例中,我们使用libvirt

我们将在 CentOS 8 服务器上创建一个 NFS 共享。它将托管在/testvms目录中,我们将通过 NFS 导出它。服务器的名称是nfs-01。(在我们的情况下,nfs-01的 IP 地址是192.168.159.134

  1. 第一步是从nfs-01创建和导出/testvms目录,并关闭 SELinux(查看第五章Libvirt 存储,Ceph 部分以了解如何):
# mkdir /testvms
# echo '/testvms *(rw,sync,no_root_squash)' >> /etc/exports
  1. 然后,通过执行以下代码在防火墙中允许 NFS 服务:
# firewall-cmd --get-active-zones
public
interfaces: ens33
# firewall-cmd --zone=public --add-service=nfs
# firewall-cmd --zone=public --list-all
  1. 启动 NFS 服务,如下所示:
# systemctl start rpcbind nfs-server
# systemctl enable rpcbind nfs-server
# showmount -e
  1. 确认共享是否可以从您的 KVM 超级主机访问。在我们的情况下,它是PacktPhy01PacktPhy02。运行以下代码:
# mount 192.168.159.134:/testvms /mnt
  1. 如果挂载失败,请重新配置 NFS 服务器上的防火墙并重新检查挂载。可以使用以下命令完成:
firewall-cmd --permanent --zone=public --add-service=nfs
firewall-cmd --permanent --zone=public --add-service=mountd
firewall-cmd --permanent --zone=public --add-service=rpc-bind
firewall-cmd -- reload
  1. 验证了两个超级主机的 NFS 挂载点后,卸载卷,如下所示:
# umount /mnt
  1. PacktPhy01PacktPhy02上创建名为testvms的存储池,如下所示:
# mkdir -p /var/lib/libvirt/images/testvms/
# virsh pool-define-as --name testvms --type netfs --source-host 192.168.159.134 --source-path /testvms --target /var/lib/libvirt/images/testvms/
# virsh pool-start testvms
# virsh pool-autostart testvms

testvms存储池现在在两个超级主机上创建并启动。

在下一个示例中,我们将隔离迁移和 VM 流量。特别是在生产环境中,如果您进行大量迁移,强烈建议您进行此隔离,因为它将把这个要求严格的过程转移到一个单独的网络接口,从而释放其他拥挤的网络接口。因此,这样做有两个主要原因,如下所示:

  • PacktPhy01PacktPhy02上的ens192接口用于迁移以及管理任务。它们有一个 IP 地址,并连接到网络交换机。使用PacktPhy01PacktPhy02上的ens224创建了一个br1桥。br1没有分配 IP 地址,专门用于 VM 流量(连接到 VM 的交换机的上行)。它也连接到一个(物理)网络交换机。

  • 安全原因:出于安全原因,建议您将管理网络和虚拟网络隔离。您不希望用户干扰您的管理网络,您可以在其中访问您的虚拟化程序并进行管理。

我们将讨论三种最重要的场景——离线迁移、非实时迁移(挂起)和实时迁移(在线)。然后,我们将讨论存储迁移作为一个需要额外规划和考虑的单独场景。

离线迁移

正如名称所示,在离线迁移期间,VM 的状态将被关闭或挂起。然后在目标主机上恢复或启动 VM。在这种迁移模型中,libvirt只会将 VM 的 XML 配置文件从源 KVM 主机复制到目标 KVM 主机。它还假定您在目标地点已经创建并准备好使用相同的共享存储池。在迁移过程的第一步中,您需要在参与的 KVM 虚拟化程序上设置双向无密码 SSH 身份验证。在我们的示例中,它们被称为PacktPhy01PacktPhy02

在接下来的练习中,暂时禁用安全增强型 LinuxSELinux)。

/etc/sysconfig/selinux中,使用您喜欢的编辑器修改以下代码行:

SELINUX=enforcing

需要修改如下:

SELINUX=permissive

同样,在命令行中,作为root,我们需要临时将 SELinux 模式设置为宽松模式,如下所示:

# setenforce 0

PacktPhy01上,作为root,运行以下命令:

# ssh-keygen
# ssh-copy-id root@PacktPhy02

PacktPhy02上,作为root,运行以下命令:

# ssh-keygen
# ssh-copy-id root@PacktPhy01

现在您应该能够以root身份登录到这两个虚拟化程序,而无需输入密码。

让我们对已经安装的MasteringKVM01进行离线迁移,从PacktPhy01迁移到PacktPhy02。迁移命令的一般格式看起来类似于以下内容:

# virsh migrate migration-type options name-of-the-vm-destination-uri

PacktPhy01上,运行以下代码:

[PacktPhy01] # virsh migrate --offline --verbose –-persistent MasteringKVM01 qemu+ssh://PacktPhy02/system
Migration: [100 %]

PacktPhy02上,运行以下代码:

[PacktPhy02] # virsh list --all
# virsh list --all
Id Name State
----------------------------------------------------
- MasteringKVM01 shut off
[PacktPhy02] # virsh start MasteringKVM01
Domain MasteringKVM01 started

当 VM 在共享存储上,并且您在其中一个主机上遇到了一些问题时,您也可以手动在另一个主机上注册 VM。这意味着在您修复了初始问题的主机上,同一个 VM 可能会在两个虚拟化程序上注册。这是在没有像 oVirt 这样的集中管理平台的情况下手动管理 KVM 主机时会发生的情况。那么,如果您处于这种情况下会发生什么呢?让我们讨论这种情况。

如果我意外地在两个虚拟化程序上启动 VM 会怎么样?

意外地在两个虚拟化程序上启动 VM 可能是系统管理员的噩梦。这可能导致 VM 文件系统损坏,特别是当 VM 内部的文件系统不是集群感知时。libvirt的开发人员考虑到了这一点,并提出了一个锁定机制。事实上,他们提出了两种锁定机制。启用这些锁定机制将防止 VM 同时在两个虚拟化程序上启动。

两个锁定机制如下:

  • lockdlockd利用了POSIX fcntl()的咨询锁定功能。它由virtlockd守护程序启动。它需要一个共享文件系统(最好是 NFS),可供共享相同存储池的所有主机访问。

  • sanlock:这是 oVirt 项目使用的。它使用磁盘paxos算法来维护持续更新的租约。

对于仅使用libvirt的实现,我们更喜欢lockd而不是sanlock。最好在 oVirt 中使用sanlock

启用 lockd

对于符合 POSIX 标准的基于镜像的存储池,您可以通过取消注释/etc/libvirt/qemu.conf中的以下命令或在两个虚拟化程序上启用lockd

lock_manager = "lockd" 

现在,在两个虚拟化程序上启用并启动virtlockd服务。另外,在两个虚拟化程序上重新启动libvirtd,如下所示:

# systemctl enable virtlockd; systemctl start virtlockd
# systemctl restart libvirtd
# systemctl status virtlockd

PacktPhy02上启动MasteringKVM01,如下所示:

[root@PacktPhy02] # virsh start MasteringKVM01
Domain MasteringKVM01 started

PacktPhy01上启动相同的MasteringKVM01虚拟机,如下所示:

[root@PacktPhy01] # virsh start MasteringKVM01
error: Failed to start domain MasteringKVM01
error: resource busy: Lockspace resource '/var/lib/libvirt/images/ testvms/MasteringKVM01.qcow2' is locked

启用lockd的另一种方法是使用磁盘文件路径的哈希。锁保存在通过 NFS 或类似共享导出到虚拟化程序的共享目录中。当您有通过多路径创建和附加的虚拟磁盘时,这是非常有用的,在这种情况下无法使用fcntl()。我们建议您使用下面详细介绍的方法来启用锁定。

在 NFS 服务器上运行以下代码(确保您首先不要从此 NFS 服务器运行任何虚拟机!):

mkdir /flockd
# echo "/flockd *(rw,no_root_squash)" >> /etc/exports
# systemctl restart nfs-server
# showmount -e
Export list for :
/flockd *
/testvms *

/etc/fstab中为两个虚拟化程序添加以下代码,并输入其余命令:

# echo "192.168.159.134:/flockd /var/lib/libvirt/lockd/flockd nfs rsize=8192,wsize=8192,timeo=14,intr,sync" >> /etc/fstab
# mkdir -p /var/lib/libvirt/lockd/flockd
# mount -a
# echo 'file_lockspace_dir = "/var/lib/libvirt/lockd/flockd"' >> /etc/libvirt/qemu-lockd.conf

重新启动两个虚拟化程序,并在重新启动后验证libvirtdvirtlockd守护程序在两个虚拟化程序上是否正确启动,如下所示:

[root@PacktPhy01 ~]# virsh start MasteringKVM01
Domain MasteringKVM01 started
[root@PacktPhy02 flockd]# ls
36b8377a5b0cc272a5b4e50929623191c027543c4facb1c6f3c35bacaa745 5ef
51e3ed692fdf92ad54c6f234f742bb00d4787912a8a674fb5550b1b826343 dd6

MasteringKVM01有两个虚拟磁盘,一个是从 NFS 存储池创建的,另一个是直接从 LUN 创建的。如果我们尝试在PacktPhy02虚拟化程序主机上启动它,MasteringKVM01将无法启动,如下面的代码片段所示:

[root@PacktPhy02 ~]# virsh start MasteringKVM01
error: Failed to start domain MasteringKVM01
error: resource busy: Lockspace resource '51e3ed692fdf92ad54c6f234f742bb00d4787912a8a674fb5550b1b82634 3dd6' is locked

当使用可以跨多个主机系统可见的 LVM 卷时,最好基于libvirt对 LVM 执行基于 UUID 的锁定:

lvm_lockspace_dir = "/var/lib/libvirt/lockd/lvmvolumes"

当使用可以跨多个主机系统可见的 SCSI 卷时,最好基于每个卷关联的 UUID 进行锁定,而不是它们的路径。设置以下路径会导致libvirt对 SCSI 执行基于 UUID 的锁定:

scsi_lockspace_dir = "/var/lib/libvirt/lockd/scsivolumes"

file_lockspace_dir一样,前面的目录也应该与虚拟化程序共享。

重要提示

如果由于锁定错误而无法启动虚拟机,只需确保它们没有在任何地方运行,然后删除锁定文件。然后再次启动虚拟机。我们在lockd主题上偏离了一点。让我们回到迁移。

实时或在线迁移

在这种类型的迁移中,虚拟机在运行在源主机上的同时迁移到目标主机。这个过程对正在使用虚拟机的用户是不可见的。他们甚至不会知道他们正在使用的虚拟机在他们使用时已经被迁移到另一个主机。实时迁移是使虚拟化如此受欢迎的主要功能之一。

KVM 中的迁移实现不需要虚拟机的任何支持。这意味着您可以实时迁移任何虚拟机,而不管它们使用的操作系统是什么。KVM 实时迁移的一个独特特性是它几乎完全与硬件无关。您应该能够在具有Advanced Micro DevicesAMD)处理器的虚拟化程序上实时迁移运行在 Intel 处理器上的虚拟机。

我们并不是说这在 100%的情况下都会奏效,或者我们以任何方式推荐拥有这种混合环境,但在大多数情况下,这是可能的。

在我们开始这个过程之前,让我们深入了解一下在幕后发生了什么。当我们进行实时迁移时,我们正在移动一个正在被用户访问的活动虚拟机。这意味着用户在进行实时迁移时不应该感受到虚拟机可用性的任何中断。

即使这些过程对系统管理员不可见,活迁移是一个包含五个阶段的复杂过程。一旦发出 VM 迁移操作,libvirt将会完成必要的工作。VM 迁移经历的阶段如下所述:

  1. libvirtSLibvirt)将与目的地libvirtDLibvirt)联系,并提供将要进行实时传输的 VM 的详细信息。DLibvirt将将此信息传递给底层的 QEMU,并提供相关选项以启用实时迁移。QEMU 将通过在pause模式下启动 VM 并开始侦听来自DLibvirt的连接到目的地 TCP 端口的实际实时迁移过程。

  2. 在目的地处于pause模式。

b)一次性将 VM 使用的所有内存传输到目的地。传输速度取决于网络带宽。假设 VM 使用 10 migrate-setmaxdowntime,单位为毫秒。

  1. 停止源主机上的虚拟机:一旦脏页的数量达到所述阈值,QEMU 将停止源主机上的虚拟机。它还将同步虚拟磁盘。

  2. 传输 VM 状态:在此阶段,QEMU 将尽快将 VM 的虚拟设备状态和剩余的脏页传输到目的地。我们无法在此阶段限制带宽。

  3. 继续 VM:在目的地,VM 将从暂停状态恢复。虚拟网络接口控制器NICs)变为活动状态,桥接将发送自由地址解析协议ARPs)以宣布更改。在收到桥接的通知后,网络交换机将更新各自的 ARP 缓存,并开始将 VM 的数据转发到新的 hypervisor。

请注意,步骤 3、4 和 5将在毫秒内完成。如果发生错误,QEMU 将中止迁移,VM 将继续在源 hypervisor 上运行。在整个迁移过程中,来自两个参与的 hypervisor 的libvirt服务将监视迁移过程。

我们的 VM 称为MasteringKVM01,现在安全地在PacktPhy01上运行,并启用了lockd。我们将要将MasteringKVM01实施活迁移到PacktPhy02

我们需要打开用于迁移的必要 TCP 端口。您只需要在目的地服务器上执行此操作,但最好在整个环境中执行此操作,以便以后不必逐个微观管理这些配置更改。基本上,您需要使用以下firewall-cmd命令为默认区域(在我们的情况下是public区域)在所有参与的 hypervisor 上打开端口:

# firewall-cmd --zone=public --add-port=49152-49216/tcp --permanent

检查两台服务器上的名称解析,如下所示:

[root@PacktPhy01 ~] # host PacktPhy01
PacktPhy01 has address 192.168.159.136
[root@PacktPhy01 ~] # host PacktPhy02
PacktPhy02 has address 192.168.159.135
[root@PacktPhy02 ~] # host PacktPhy01
PacktPhy01 has address 192.168.159.136
[root@PacktPhy02 ~] # host PacktPhy02
PacktPhy02 has address 192.168.159.135

检查和验证所有附加的虚拟磁盘是否在目的地上可用,路径相同,并且存储池名称相同。这也适用于附加的未管理(iSCSI 和 FC LUN 等)虚拟磁盘。

检查和验证目的地可用的 VM 所使用的所有网络桥接和虚拟网络。之后,我们可以通过运行以下代码开始迁移过程:

# virsh migrate --live MasteringKVM01 qemu+ssh://PacktPhy02/system --verbose --persistent
Migration: [100 %]

我们的 VM 只使用 4,096 --persistent选项是可选的,但我们建议添加这个选项。

这是迁移过程中ping的输出(10.10.48.24MasteringKVM01的 IP 地址):

# ping 10.10.48.24
PING 10.10.48.24 (10.10.48.24) 56(84) bytes of data.
64 bytes from 10.10.48.24: icmp_seq=12 ttl=64 time=0.338 ms
64 bytes from 10.10.48.24: icmp_seq=13 ttl=64 time=3.10 ms
64 bytes from 10.10.48.24: icmp_seq=14 ttl=64 time=0.574 ms
64 bytes from 10.10.48.24: icmp_seq=15 ttl=64 time=2.73 ms
64 bytes from 10.10.48.24: icmp_seq=16 ttl=64 time=0.612 ms
--- 10.10.48.24 ping statistics ---
17 packets transmitted, 17 received, 0% packet loss, time 16003ms
rtt min/avg/max/mdev = 0.338/0.828/3.101/0.777 ms

如果收到以下错误消息,请将附加的虚拟磁盘上的cache更改为none

# virsh migrate --live MasteringKVM01 qemu+ssh://PacktPhy02/system --verbose
error: Unsafe migration: Migration may lead to data corruption if disks use cache != none
# virt-xml MasteringKVM01 --edit --disk target=vda,cache=none

target是要更改缓存的磁盘。您可以通过运行以下命令找到目标名称:

virsh dumpxml MasteringKVM01

在执行活迁移时,您可以尝试一些其他选项,如下所示:

  • --未定义域:用于从 KVM 主机中删除 KVM 域的选项。

  • --暂停域:暂停 KVM 域,即暂停 KVM 域,直到我们恢复它。

  • --compressed:当我们进行虚拟机迁移时,此选项使我们能够压缩内存。这将意味着更快的迁移过程,基于–comp-methods参数。

  • --abort-on-error:如果迁移过程出现错误,它会自动停止。这是一个安全的默认选项,因为它将有助于在迁移过程中发生任何类型的损坏的情况下。

  • --unsafe:这个选项有点像–abort-on-error选项的反面。这个选项会不惜一切代价进行迁移,即使出现错误、数据损坏或其他意外情况。对于这个选项要非常小心,不要经常使用,或者在任何您想要确保虚拟机数据一致性的情况下使用。

您可以在 RHEL 7—虚拟化部署和管理指南中阅读更多关于这些选项的信息(您可以在本章末尾的进一步阅读部分找到链接)。此外,virsh命令还支持以下选项:

  • virsh migrate-setmaxdowntime <domain>:在迁移虚拟机时,不可避免地会有时候虚拟机会短暂不可用。这可能发生,例如,因为交接过程,当我们将虚拟机从一个主机迁移到另一个主机时,我们刚好到达状态平衡点(也就是说,源主机和目标主机具有相同的虚拟机内容,并准备好从源主机清除源虚拟机并在目标主机上运行)。基本上,源虚拟机被暂停和终止,目标主机虚拟机被取消暂停并继续。通过使用这个命令,KVM 堆栈试图估计这个停止阶段将持续多长时间。这是一个可行的选择,特别是对于非常繁忙的虚拟机,因此在迁移过程中它们的内存内容会发生很大变化。

  • virsh migrate-setspeed <domain> bandwidth:我们可以将这个选项视为准服务质量QoS)选项。通过使用它,我们可以设置以 MiB/s 为单位的迁移过程中的带宽量。如果我们的网络很忙,这是一个非常好的选择(例如,如果我们在同一物理网络上有多个虚拟局域网VLANs),并且由于此原因有带宽限制)。较低的数字会减慢迁移过程。

  • virsh migrate-getspeed <domain>:我们可以将这个选项视为migrate-setspeed命令的获取信息选项,以检查我们为virsh migrate-setspeed命令分配了哪些设置。

正如您所看到的,从技术角度来看,迁移是一个复杂的过程,有多种不同类型和大量额外的配置选项,可以用于管理目的。尽管如此,它仍然是虚拟化环境中非常重要的功能,很难想象在没有它的情况下工作。

摘要

在本章中,我们涵盖了创建虚拟机和配置虚拟机硬件的不同方法。我们还详细介绍了虚拟机迁移,以及在线和离线虚拟机迁移。在下一章中,我们将学习虚拟机磁盘、虚拟机模板和快照。了解这些概念非常重要,因为它们将使您在管理虚拟化环境时更加轻松。

问题

  1. 我们可以使用哪些命令行工具来在libvirt中部署虚拟机?

  2. 我们可以使用哪些图形界面工具来在libvirt中部署虚拟机?

  3. 在配置我们的虚拟机时,我们应该注意哪些配置方面?

  4. 在线和离线虚拟机迁移有什么区别?

  5. 虚拟机迁移和虚拟机存储迁移有什么区别?

  6. 我们如何为迁移过程配置带宽?

进一步阅读

请参考以下链接,了解本章涵盖的更多信息:

第八章:创建和修改 VM 磁盘、模板和快照

这一章代表了本书第二部分的结束,我们在这一部分专注于各种libvirt功能——安装libvirt网络和存储,虚拟设备和显示协议,安装虚拟机VMs)并配置它们……所有这些都是为了为本书的下一部分做准备,下一部分将涉及自动化、定制和编排。为了让我们能够学习这些概念,我们现在必须把焦点转移到 VM 及其高级操作上——修改、模板化、使用快照等。本书的后面经常会提到这些主题中的一些,这些主题在生产环境中出于各种业务原因可能会更有价值。让我们深入研究并涵盖它们。

在本章中,我们将涵盖以下主题:

  • 使用libguestfs工具修改 VM 映像

  • VM 模板化

  • virt-buildervirt-builder存储库

  • 快照

  • 在使用快照时的用例和最佳实践

使用libguestfs工具修改 VM 映像

随着本书重点转向扩展,我们不得不在本书的这一部分结束时介绍一系列命令,这些命令将在我们开始构建更大的环境时非常有用。对于更大的环境,我们确实需要各种自动化、定制和编排工具,我们将在下一章开始讨论这些工具。但首先,我们必须专注于我们已经掌握的各种定制工具。这些命令行实用程序对于许多不同类型的操作都非常有帮助,从guestfish(用于访问和修改 VM 文件)到virt-p2vvirt-sysprep(在模板化和克隆之前sysprep VM)。因此,让我们以工程方式逐步接触这些实用程序的主题。

libguestfs是一个用于处理 VM 磁盘的命令行实用程序库。该库包括大约 30 个不同的命令,其中一些包括在以下列表中:

  • guestfish

  • virt-builder

  • virt-builder-repository

  • virt-copy-in

  • virt-copy-out

  • virt-customize

  • virt-df

  • virt-edit

  • virt-filesystems

  • virt-rescue

  • virt-sparsify

  • virt-sysprep

  • virt-v2v

  • virt-p2v

我们将从五个最重要的命令开始——virt-v2vvirt-p2vvirt-copy-invirt-customizeguestfish。在我们讨论 VM 模板化时,我们将涵盖virt-sysprep,并且本章的一个单独部分专门介绍了virt-builder,因此我们暂时跳过这些命令。

virt-v2v

假设您有一个基于 Hyper-V、Xen 或 VMware 的 VM,并且希望将其转换为 KVM、oVirt、Red Hat Enterprise Virtualization 或 OpenStack。我们将以 VMware 为例,将其转换为由libvirt实用程序管理的 KVM VM。由于 VMware 平台的 6.0+版本(无论是在ESX 集成ESXi)hypervisor 方面还是在 vCenter 服务器和插件方面)引入了一些更改,将 VM 导出并转换为 KVM 机器将非常耗时——无论是使用 vCenter 服务器还是 ESXi 主机作为源。因此,将 VMware VM 转换为 KVM VM 的最简单方法如下:

  1. 在 vCenter 或 ESXi 主机中关闭 VM。

  2. 将 VM 导出为Downloads目录。

  3. code.vmware.com/web/tool/4.3.0/ovf安装 VMware OVFtool实用程序。

  4. 将导出的 VM 文件移动到OVFtool安装文件夹。

  5. 将 VM 以 OVF 格式转换为Open Virtualization ApplianceOVA)格式。

我们需要OVFtool的原因相当令人失望——似乎 VMware 删除了直接导出 OVA 文件的选项。幸运的是,OVFtool适用于基于 Windows、Linux 和 OS X 的平台,因此您不会在使用它时遇到麻烦。以下是该过程的最后一步:

图 8.1 - 使用 OVFtool 将 OVF 转换为 OVA 模板格式

图 8.1 - 使用 OVFtool 将 OVF 转换为 OVA 模板格式

完成后,我们可以轻松地将v2v.ova文件上传到我们的 KVM 主机,并在ova文件目录中键入以下命令:

virt-v2v -i ova v2v.ova -of qcow2 -o libvirt -n default

-of-o选项指定输出格式(qcow2 libvirt 映像),-n确保 VM 连接到默认虚拟网络。

如果您需要将 Hyper-V VM 转换为 KVM,可以这样做:

virt-v2v -i disk /location/of/virtualmachinedisk.vhdx -o local -of qcow2 -os /var/lib/libvirt/images

确保您正确指定了 VM 磁盘位置。 -o local-os /var/lib/libvirt/images 选项确保转换后的磁盘映像被保存在指定目录中(KVM 默认映像目录)。

还有其他类型的 VM 转换过程,例如将物理机器转换为虚拟机。让我们现在来介绍一下。

virt-p2v

现在我们已经介绍了virt-v2v,让我们转而介绍virt-p2v。基本上,virt-v2vvirt-p2v执行的工作似乎相似,但virt-p2v的目的是将物理机器转换为VM。从技术上讲,这是有很大不同的,因为使用virt-v2v,我们可以直接访问管理服务器和 hypervisor,并在转换 VM 时(或通过 OVA 模板)进行转换。对于物理机器,没有管理机器可以提供某种支持或应用程序编程接口API)来执行转换过程。我们必须直接攻击物理机器。在 IT 的实际世界中,通常通过某种代理或附加应用程序来完成这一点。

举个例子,如果您想将物理 Windows 机器转换为基于 VMware 的 VM,您需要在需要转换的系统上安装 VMware vCenter Converter Standalone。然后,您需要选择正确的操作模式,并将完整的转换过程流式传输到 vCenter/ESXi。这确实效果很好,但是举个例子,RedHat 的方法有点不同。它使用引导介质来转换物理服务器。因此,在使用此转换过程之前,您必须登录到客户门户(位于access.redhat.com/downloads/content/479/ver=/rhel---8/8.0/x86_64/product-software)以使用virt-p2vvirt-p2v-make-disk实用程序创建映像。但是,virt-p2v-make-disk实用程序使用virt-builder,我们稍后将在本章的另一部分中详细介绍。因此,让我们暂时搁置这个讨论,因为我们很快将全力回来。

作为一个旁注,在此命令的支持目的地列表中,我们可以使用 Red Hat 企业虚拟化、OpenStack 或 KVM/libvirt。在支持的架构方面,virt-p2v仅支持基于 x86_64 的平台,并且仅在 RHEL/CentOS 7 和 8 上使用。在计划进行 P2V 转换时,请记住这一点。

guestfish

本章介绍的最后一个实用程序是guestfish。这是一个非常重要的实用程序,它使您能够对实际的 VM 文件系统进行各种高级操作。我们还可以使用它进行不同类型的转换,例如,将tar.gz转换为虚拟磁盘映像;将虚拟磁盘映像从ext4文件系统转换为ext4文件系统;等等。我们将向您展示如何使用它来打开 VM 映像文件并进行一些操作。

第一个例子是一个非常常见的例子——您已经准备好了一个带有完整 VM 的qcow2镜像;客户操作系统已安装;一切都已配置好;您准备将该 VM 文件复制到其他地方以便重复使用;然后……您记得您没有根据某些规范配置根密码。假设这是您为客户做的事情,该客户对初始根密码有特定的要求。这对客户来说更容易——他们不需要通过电子邮件收到您发送的密码;他们只需要记住一个密码;并且,在收到镜像后,它将用于创建 VM。在创建并运行 VM 之后,根密码将被更改为根据安全实践使用的客户密码。

因此,基本上,第一个例子是一个人类的例子——忘记做某事,然后想要修复,但(在这种情况下)不实际运行 VM,因为这可能会改变很多设置,特别是如果您的qcow2镜像是为 VM 模板化而创建的,那么您绝对不希望启动该 VM 来修复某些东西。关于这一点,我们将在本章的下一部分详细介绍。

这是guestfish的一个理想用例。假设我们的qcow2镜像名为template.qcow2。让我们将根密码更改为其他内容——例如,packt123。首先,我们需要该密码的哈希。最简单的方法是使用带有-6选项的openssl(相当于 SHA512 加密),如下面的截图所示:

图 8.2 – 使用 openssl 创建基于 SHA512 的密码哈希

图 8.2 – 使用 openssl 创建基于 SHA512 的密码哈希

现在我们有了哈希,我们可以挂载和编辑我们的镜像,如下所示:

图 8.3 – 使用 guestfish 编辑我们的 qcow2 VM 镜像中的根密码

图 8.3 – 使用 guestfish 编辑我们的 qcow2 VM 镜像中的根密码

我们输入的 Shell 命令用于直接访问图像(无需涉及libvirt)并以读写模式挂载我们的图像。然后,我们启动了我们的会话(guestfish run命令),检查图像中存在哪些文件系统(list-filesystems),并将文件系统挂载到根文件夹上。在倒数第二步中,我们将根密码的哈希更改为由openssl创建的哈希。exit命令关闭我们的guestfish会话并保存更改。

您可以使用类似的原理——例如——从/etc/ssh目录中删除遗忘的sshd密钥,删除用户ssh目录等。该过程如下截图所示:

图 8.4 – 使用 virt-customize 在 qcow2 镜像内执行命令

图 8.4 – 使用 virt-customize 在 qcow2 镜像内执行命令

第二个例子也非常有用,因为它涉及到下一章中涵盖的一个主题(cloud-init),通常用于通过操纵 VM 实例的早期初始化来配置云 VM。此外,从更广泛的角度来看,您可以使用这个guestfish示例来操纵 VM 镜像内部的服务配置。因此,假设我们的 VM 镜像被配置为自动启动cloud-init服务。出于某种原因,我们希望禁用该服务——例如,为了调试cloud-init配置中的错误。如果我们没有能力操纵qcow镜像内容,我们将不得不启动该 VM,使用systemctl禁用该服务,然后——也许——执行整个过程来重新封装该 VM,如果这是一个 VM 模板的话。因此,让我们使用guestfish来达到相同的目的,如下所示:

图 8.5 – 使用 guestfish 在 VM 启动时禁用 cloud-init 服务

图 8.5 – 使用 guestfish 在 VM 启动时禁用 cloud-init 服务

重要提示

在这个例子中要小心,因为通常我们会在ln -sf之间使用空格字符。但在我们的guestfish示例中不是这样—它需要使用空格。

最后,假设我们需要将文件复制到我们的镜像。例如,我们需要将本地的/etc/resolv.conf文件复制到镜像中,因为我们忘记为此目的配置我们的virt-copy-in命令,如下截图所示:

图 8.6 - 使用 virt-copy-in 将文件复制到我们的镜像

图 8.6 - 使用 virt-copy-in 将文件复制到我们的镜像

我们在本章的这一部分涵盖的主题对接下来的内容非常重要,即讨论创建虚拟机模板。

VM 模板化

虚拟机最常见的用例之一是创建虚拟机模板。因此,假设我们需要创建一个将用作模板的虚拟机。我们在这里字面上使用术语模板,就像我们可以为 Word、Excel、PowerPoint 等使用模板一样,因为虚拟机模板存在的原因与此相同—为了让我们拥有一个熟悉的预配置工作环境,以便我们不需要从头开始。在虚拟机模板的情况下,我们谈论的是不从头安装虚拟机客户操作系统,这是一个巨大的时间节省。想象一下,如果你得到一个任务,需要为某种测试环境部署 500 个虚拟机,以测试某种东西在扩展时的工作情况。即使考虑到你可以并行安装,也会花费数周时间。

虚拟机需要被视为对象,它们具有某些属性特性。从外部的角度来看(即从libvirt的角度),虚拟机有一个名称、一个虚拟磁盘、一个虚拟中央处理单元(CPU)和内存配置、连接到虚拟交换机等等。我们在第七章中涵盖了这个主题,VM:安装、配置和生命周期管理。也就是说,我们没有涉及虚拟机内部的主题。从这个角度来看(基本上是从客户操作系统的角度),虚拟机也有一些属性—安装的客户操作系统版本、Internet Protocol(IP)配置、虚拟局域网(VLAN)配置...之后,这取决于基于哪个操作系统的家族虚拟机。因此,我们需要考虑以下内容:

  • 如果我们谈论基于 Microsoft Windows 的虚拟机,我们必须考虑服务和软件配置,注册表配置和许可证配置。

  • 如果我们谈论基于 Linux 的虚拟机,我们必须考虑服务和软件配置,安全外壳(SSH)密钥配置,许可证配置等等。

甚至可以更具体。例如,为基于 Ubuntu 的虚拟机准备模板与为基于 CentOS 8 的虚拟机准备模板是不同的。为了正确创建这些模板,我们需要学习一些基本程序,然后每次创建虚拟机模板时都可以重复使用。

考虑这个例子:假设你希望创建四个 Apache Web 服务器来托管你的 Web 应用程序。通常,使用传统的手动安装方法,你首先必须创建四个具有特定硬件配置的虚拟机,逐个在每个虚拟机上安装操作系统,然后使用yum或其他软件安装方法下载并安装所需的 Apache 软件包。这是一项耗时的工作,因为你将主要进行重复的工作。但是使用模板方法,可以在较短的时间内完成。为什么?因为你将绕过操作系统安装和其他配置任务,直接从包含预配置操作系统镜像的模板中生成虚拟机,其中包含所有所需的 Web 服务器软件包,准备供使用。

以下截图显示了手动安装方法涉及的步骤。您可以清楚地看到步骤 2-5只是在所有四个 VM 上执行的重复任务,它们将占用大部分时间来准备您的 Apache Web 服务器:

图 8.7 - 不使用 VM 模板安装四个 Apache Web 服务器

图 8.7 - 不使用 VM 模板安装四个 Apache Web 服务器

现在,看看通过简单地遵循步骤 1-5一次,创建一个模板,然后使用它部署四个相同的 VM,步骤数量是如何大幅减少的。这将为您节省大量时间。您可以在以下图表中看到差异:

图 8.8 - 使用 VM 模板安装四个 Apache Web 服务器

图 8.8 - 使用 VM 模板安装四个 Apache Web 服务器

然而,这并不是全部。实际上从步骤 3步骤 4(从创建模板到部署 VM1-4)有不同的方式,其中包括完全克隆过程或链接克隆过程,详细介绍如下:

  • 完全克隆:使用完全克隆机制部署的 VM 将创建 VM 的完整副本,问题在于它将使用与原始 VM 相同的容量。

  • 链接克隆:使用薄克隆机制部署的 VM 将模板镜像作为只读模式的基础镜像,并链接一个额外的写时复制(COW)镜像来存储新生成的数据。这种配置方法在云和虚拟桌面基础设施(VDI)环境中被广泛使用,因为它可以节省大量磁盘空间。请记住,快速存储容量是非常昂贵的,因此在这方面的任何优化都将节省大量资金。链接克隆还会对性能产生影响,我们稍后会讨论一下。

现在,让我们看看模板是如何工作的。

使用模板

在本节中,您将学习如何使用virt-manager中可用的virt-clone选项创建 Windows 和 Linux VM 的模板。虽然virt-clone实用程序最初并不是用于创建模板,但当与virt-sysprep和其他操作系统封装实用程序一起使用时,它可以实现这一目的。请注意,克隆和主镜像之间存在差异。克隆镜像只是一个 VM,而主镜像是可以用于部署数百甚至数千个新 VM 的 VM 副本。

创建模板

模板是通过将 VM 转换为模板来创建的。实际上,这是一个包括以下步骤的三步过程:

  1. 安装和定制 VM,包括所有所需的软件,这将成为模板或基础镜像。

  2. 删除所有系统特定属性以确保 VM 的唯一性 - 我们需要处理 SSH 主机密钥、网络配置、用户帐户、媒体访问控制(MAC)地址、许可信息等。

  3. 通过在名称前加上模板前缀将 VM 标记为模板。一些虚拟化技术对此有特殊的 VM 文件类型(例如 VMware 的.vmtx文件),这实际上意味着您不必重命名 VM 来标记它为模板。

要了解实际的过程,让我们创建两个模板并从中部署一个 VM。我们的两个模板将是以下内容:

  • 具有完整 Linux、Apache、MySQL 和 PHP(LAMP)堆栈的 CentOS 8 VM

  • 具有 SQL Server Express 的 Windows Server 2019 VM

让我们继续创建这些模板。

示例 1 - 准备一个带有完整 LAMP 堆栈的 CentOS 8 模板

CentOS 的安装对我们来说应该是一个熟悉的主题,所以我们只会专注于 LAMP 堆栈的AMP部分和模板部分。因此,我们的过程将如下所示:

  1. 创建一个 VM 并在其上安装 CentOS 8,使用您喜欢的安装方法。保持最小化,因为这个 VM 将被用作为为此示例创建的模板的基础。

  2. 通过 SSH 进入或接管虚拟机并安装 LAMP 堆栈。以下是一个脚本,用于在操作系统安装完成后在 CentOS 8 上安装 LAMP 堆栈所需的一切。让我们从软件包安装开始,如下所示:

yum -y update
yum -y install httpd httpd-tools mod_ssl
systemctl start httpd
systemctl enable httpd
yum -y install mariadb-server mariadb
yum install -y php php-fpm php-mysqlnd php-opcache php-gd php-xml php-mbstring libguestfs*

在软件安装完成后,让我们进行一些服务配置——启动所有必要的服务并启用它们,并重新配置防火墙以允许连接,如下所示:

systemctl start mariadb
systemctl enable mariadb
systemctl start php-fpm
systemctl enable php-fpm
firewall-cmd --permanent --zone=public --add-service=http
firewall-cmd --permanent --zone=public --add-service=https
systemctl reload firewalld

我们还需要配置一些与目录所有权相关的安全设置,例如 Apache Web 服务器的安全增强型 LinuxSELinux)配置。让我们像这样进行下一步操作:

chown apache:apache /var/www/html -R
semanage fcontext -a -t httpd_sys_content_t "/var/www/html(/.*)?"
restorecon -vvFR /var/www/html
setsebool -P httpd_execmem 1
  1. 完成此操作后,我们需要配置 MariaDB,因为我们必须为数据库管理用户设置某种 MariaDB 根密码并配置基本设置。这通常是通过 MariaDB 软件包提供的mysql_secure_installation脚本完成的。因此,这是我们的下一步,如下面的代码片段所示:
mysql_secure_installation script, it is going to ask us a series of questions, as illustrated in the following screenshot:![Figure 8.9 – First part of MariaDB setup: assigning a root password that is empty after installation    ](https://gitee.com/OpenDocCN/freelearn-linux-zh/raw/master/docs/ms-kvm-vrt/img/B14834_08_09.jpg)Figure 8.9 – First part of MariaDB setup: assigning a root password that is empty after installationAfter assigning a root password for the MariaDB database, the next steps are more related to housekeeping—removing anonymous users, disallowing remote login, and so on. Here's what that part of wizard looks like:![Figure 8.10 – Housekeeping: anonymous users, root login setup, test database data removal    ](https://gitee.com/OpenDocCN/freelearn-linux-zh/raw/master/docs/ms-kvm-vrt/img/B14834_08_10.jpg)Figure 8.10 – Housekeeping: anonymous users, root login setup, test database data removalWe installed all the necessary services—Apache, MariaDB—and all the necessary additional packages (PHP, `sample index.html` file and place it in `/var/www/html`), but we're not going to do that right now. In production environments, we'd just copy web page contents to that directory and be done with it.
  1. 现在,必需的 LAMP 设置已按我们的要求配置好,关闭虚拟机并运行virt-sysprep命令进行封存。如果要过期根密码(即在下次登录时强制更改根密码),请输入以下命令:
passwd --expire root

我们的测试虚拟机名为 LAMP,主机名为PacktTemplate,因此以下是必要的步骤,通过一行命令呈现:

virsh shutdown LAMP; sleep 10; virsh list

我们的 LAMP 虚拟机现在已准备好重新配置为模板。为此,我们将使用virt-sysprep命令。

什么是 virt-sysprep?

这是libguestfs-tools-c软件包提供的命令行实用程序,用于简化 Linux 虚拟机的封存和通用化过程。它会自动删除系统特定信息,使克隆可以从中创建。virt-sysprep可用于添加一些额外的配置位和部分,例如用户、组、SSH 密钥等。

有两种方法可以针对 Linux 虚拟机调用virt-sysprep:使用-d-a选项。第一个选项指向预期的客户端,使用其名称或virt-sysprep命令,即使客户端未在libvirt中定义。

执行virt-sysprep命令后,它会执行一系列的sysprep操作,通过从中删除系统特定信息使虚拟机镜像变得干净。如果您想了解此命令在后台的工作原理,请在命令中添加--verbose选项。该过程可以在以下截图中看到:

图 8.11 – virt-sysprep 在虚拟机上发挥魔力

图 8.11 – virt-sysprep 在虚拟机上发挥魔力

默认情况下,virt-sysprep执行超过 30 个操作。您还可以选择要使用的特定 sysprep 操作。要获取所有可用操作的列表,请运行virt-sysprep --list-operation命令。默认操作用星号标记。您可以使用--operations开关更改默认操作,后跟逗号分隔的要使用的操作列表。请参阅以下示例:

图 8.12 – 使用 virt-sysprep 自定义在模板虚拟机上执行的操作

图 8.12 – 使用 virt-sysprep 自定义在模板虚拟机上执行的操作

请注意,这一次它只执行了ssh-hostkeysudev-persistentnet操作,而不是典型的操作。您可以自行决定在模板中进行多少清理工作。

现在,我们可以通过在名称前添加template来将此虚拟机标记为模板。甚至可以在从libvirt中取消定义虚拟机之前备份其可扩展标记语言XML)文件。

重要提示

确保从现在开始,此虚拟机永远不要启动;否则,它将丢失所有 sysprep 操作,甚至可能导致使用薄方法部署的虚拟机出现问题。

要重命名虚拟机,请使用virsh domrename作为 root 用户,如下所示:

# virsh domrename LAMP LAMP-Template

LAMP-Template,我们的模板,现在已准备好用于未来的克隆过程。您可以使用以下命令检查其设置:

# virsh dominfo LAMP-Template

最终结果应该是这样的:

图 8.13 - 在我们的模板 VM 上使用 virsh dominfo

图 8.13 - 在我们的模板 VM 上使用 virsh dominfo

下一个示例将是关于准备一个预安装了 Microsoft 结构化查询语言SQL)数据库的 Windows Server 2019 模板 - 这是我们许多人在环境中需要使用的常见用例。让我们看看我们如何做到这一点。

示例 2 - 准备带有 Microsoft SQL 数据库的 Windows Server 2019 模板

virt-sysprep 不适用于 Windows 客户端,而且很少有可能在短时间内添加支持。因此,为了通用化 Windows 机器,我们需要访问 Windows 系统并直接运行sysprep

sysprep工具是一个用于从 Windows 映像中删除特定系统数据的本机 Windows 实用程序。要了解有关此实用程序的更多信息,请参阅本文:docs.microsoft.com/en-us/windows-hardware/manufacture/desktop/sysprep--generalize--a-windows-installation

我们的模板准备过程将如下进行:

  1. 创建一个 VM 并在其上安装 Windows Server 2019 操作系统。我们的 VM 将被称为WS2019SQL

  2. 安装 Microsoft SQL Express 软件,并在配置好后,重新启动 VM 并启动sysprep应用程序。sysprep.exe文件位于C:\Windows\System32\sysprep目录中。通过在运行框中输入sysprep并双击sysprep.exe来导航到那里。

  3. 系统清理操作下,选择进入系统的 OOBE并勾选通用化复选框,如果您想进行系统标识号SID)重建,如下截图所示:图 8.14 - 小心使用 sysprep 选项;OOBE,通用化,并且强烈建议使用关闭选项

图 8.14 - 小心使用 sysprep 选项;OOBE,通用化和关闭选项是强烈建议的

  1. 在那之后,sysprep过程将开始,并在完成后关闭。

  2. 使用与我们在 LAMP 模板上使用的相同过程来重命名 VM,如下所示:

# virsh domrename WS2019SQL WS2019SQL-Template

同样,我们可以使用dominfo选项来检查我们新创建的模板的基本信息,如下所示:

# virsh dominfo WS2019SQL-Template

重要提示

在将来更新模板时要小心 - 您需要运行它们,更新它们,并重新密封它们。对于 Linux 发行版,这样做不会有太多问题。但是,对于 Microsoft Windows sysprep(启动模板 VM,更新,sysprep,并在将来重复)将使您陷入sysprep会抛出错误的情况。因此,这里还有另一种思路可以使用。您可以像我们在本章的这一部分中所做的那样执行整个过程,但不要sysprep它。这样,您可以轻松更新 VM,然后克隆它,然后sysprep它。这将节省您大量时间。

接下来,我们将看到如何从模板部署 VM。

从模板部署 VM

在前一节中,我们创建了两个模板映像;第一个模板映像仍然在libvirt中定义为VM,并命名为LAMP-Template,第二个称为WS2019SQL-Template。我们现在将使用这两个 VM 模板来从中部署新的 VM。

使用完全克隆部署 VM

执行以下步骤使用克隆配置来部署 VM:

  1. 打开 VM 管理器(virt-manager),然后选择LAMP-Template VM。右键单击它,然后选择克隆选项,这将打开克隆虚拟机窗口,如下截图所示:图 8.15 - 从 VM 管理器克隆 VM

图 8.15 - 从 VM 管理器克隆 VM

  1. 为生成的 VM 提供名称并跳过所有其他选项。单击克隆按钮开始部署。等待克隆操作完成。

  2. 完成后,您新部署的 VM 已经准备好使用,您可以开始使用它。您可以在以下截图中看到该过程的输出:

图 8.16-已创建完整克隆(LAMP01)

图 8.16-已创建完整克隆(LAMP01)

由于我们之前的操作,LAMP01 VM 是从LAMP-Template部署的,但由于我们使用了完整克隆方法,它们是独立的,即使您删除LAMP-Template,它们也会正常运行。

我们还可以使用链接克隆,这将通过创建一个锚定到基本映像的 VM 来节省大量的磁盘空间。让我们接下来做这个。

使用链接克隆部署 VM

执行以下步骤,使用链接克隆方法开始 VM 部署:

  1. 使用/var/lib/libvirt/images/WS2019SQL.qcow2作为后备文件创建两个新的qcow2图像,如下所示:
# qemu-img create -b /var/lib/libvirt/images/WS2019SQL.qcow2 -f qcow2 /var/lib/libvirt/images/LinkedVM1.qcow2
# qemu-img create -b /var/lib/libvirt/images/WS2019SQL.qcow2 -f qcow2 /var/lib/libvirt/images/LinkedVM2.qcow2
  1. 验证新创建的qcow2图像的后备文件属性是否正确指向/var/lib/libvirt/images/WS2019SQL.qcow2图像,使用qemu-img命令。这三个步骤的最终结果应该如下所示:图 8.17-创建链接克隆图像

图 8.17-创建链接克隆图像

  1. 现在,使用virsh命令将模板 VM 配置转储到两个 XML 文件中。我们这样做两次,以便我们有两个 VM 定义。在更改了一些参数后,我们将它们导入为两个新的 VM,如下所示:
virsh dumpxml WS2019SQL-Template > /root/SQL1.xml
virsh dumpxml WS2019SQL-Template > /root/SQL2.xml
  1. 使用uuidgen -r命令生成两个随机 UUID。我们将需要它们用于我们的 VM。该过程可以在以下截图中看到:图 8.18-为我们的 VM 生成两个新的 UUID

图 8.18-为我们的 VM 生成两个新的 UUID

  1. 通过为它们分配新的 VM 名称和 UUID 编辑SQL1.xmlSQL2.xml文件。这一步是强制性的,因为 VM 必须具有唯一的名称和 UUID。让我们将第一个 XML 文件中的名称更改为SQL1,将第二个 XML 文件中的名称更改为SQL2。我们可以通过更改<name></name>语句来实现这一点。然后,在SQL1.xmlSQL2.xml<uuid></uuid>语句中复制并粘贴我们使用uuidgen命令创建的 UUID。因此,配置文件中这两行的相关条目应该如下所示:图 8.19-更改其各自 XML 配置文件中的 VM 名称和 UUID

图 8.19-更改其各自 XML 配置文件中的 VM 名称和 UUID

  1. 我们需要更改SQL1SQL2镜像文件中虚拟磁盘的位置。在这些配置文件后面找到.qcow2文件的条目,并更改它们,使其使用我们在步骤 1中创建的文件的绝对路径,如下所示:图 8.20-更改 VM 镜像位置,使其指向新创建的链接克隆图像

图 8.20-更改 VM 镜像位置,使其指向新创建的链接克隆图像

  1. 现在,使用virsh create命令将这两个 XML 文件作为 VM 定义导入,如下所示:图 8.21-从 XML 定义文件创建两个新的 VM

图 8.21-从 XML 定义文件创建两个新的 VM

  1. 使用virsh命令验证它们是否已定义和运行,如下所示:图 8.22-两个新的 VM 已经启动

图 8.22-两个新的 VM 已经启动

  1. VM 已经启动,所以我们现在可以检查我们链接克隆过程的最终结果。这两个 VM 的虚拟磁盘应该相当小,因为它们都使用相同的基本镜像。让我们检查客户磁盘映像大小-请注意在以下截图中,LinkedVM1.qcowLinkedVM2.qcow文件的大小大约是其基本映像的 50 倍小:

图 8.23 – 链接克隆部署的结果:基础镜像,小增量镜像

图 8.23 – 链接克隆部署的结果:基础镜像,小增量镜像

这应该提供了大量关于使用链接克隆过程的示例和信息。不要走得太远(在单个基础镜像上创建许多链接克隆),你应该没问题。但现在,是时候转到我们的下一个主题了,那就是关于virt-builder。如果你想快速部署 VM 而不实际安装它们,virt-builder的概念非常重要。我们可以使用virt-builder存储库来实现这一点。让我们学习如何做到这一点。

virt-builder 和 virt-builder 存储库

libguestfs软件包中最重要的工具之一是virt-builder。假设你真的不想从头构建一个 VM,要么是因为你没有时间,要么是因为你根本不想麻烦。我们将以 CentOS 8 为例,尽管现在支持的发行版列表大约有 50 个(发行版及其子版本),如你在以下截图中所见:

图 8.24 – virt-builder 支持的操作系统和 CentOS 发行版

图 8.24 – virt-builder 支持的操作系统和 CentOS 发行版

在我们的测试场景中,我们需要尽快创建一个 CentOS 8 镜像,并从该镜像创建一个 VM。到目前为止,部署 VM 的所有方式都是基于从头安装、克隆或模板化的想法。这些要么是从零开始,要么是先部署模板,然后再进行配置的机制。如果还有其他方法呢?

virt-builder为我们提供了一种方法。通过发出几个简单的命令,我们可以导入一个 CentOS 8 镜像,将其导入到 KVM,并启动它。让我们继续,如下所示:

  1. 首先,让我们使用virt-builder下载一个具有指定参数的 CentOS 8 镜像,如下所示:图 8.25 – 使用 virt-builder 获取 CentOS 8.0 镜像并检查其大小

图 8.25 – 使用 virt-builder 获取 CentOS 8.0 镜像并检查其大小

  1. 一个合乎逻辑的下一步是进行virt-install,所以,我们开始吧:图 8.26 – 配置、部署和添加到本地 KVM 虚拟化管理程序的新 VM

图 8.26 – 配置、部署和添加到本地 KVM 虚拟化管理程序的新 VM

  1. 如果你觉得这很酷,让我们继续扩展。假设我们想要获取一个virt-builder镜像,向该镜像添加一个名为Virtualization Hostyum软件包组,并且在此过程中添加 root 的 SSH 密钥。我们会这样做:

图 8.27 – 添加虚拟化主机

图 8.27 – 添加虚拟化主机

实际上,这真的非常酷,它让我们的生活变得更加轻松,为我们做了很多工作,而且以一种非常简单的方式完成了,它也适用于微软 Windows 操作系统。此外,我们可以使用自定义的virt-builder存储库下载特定的虚拟机,以满足我们自己的需求,接下来我们将学习如何做到这一点。

virt-builder 存储库

显然,有一些预定义的virt-builder存储库(libguestfs.org/是其中之一),但我们也可以创建自己的存储库。如果我们转到/etc/virt-builder/repos.d目录,我们会看到那里有几个文件(libguestfs.conf及其密钥等)。我们可以轻松地创建自己的额外配置文件,以反映我们的本地或远程virt-builder存储库。假设我们想创建一个本地virt-builder存储库。让我们在/etc/virt-builder/repos.d目录中创建一个名为local.conf的配置文件,内容如下:

[local]
uri=file:///root/virt-builder/index

然后,将镜像复制或移动到/root/virt-builder目录(我们将使用在上一步中创建的centos-8.0.img文件,通过使用xz命令将其转换为xz格式),并在该目录中创建一个名为index的文件,内容如下:

[Packt01]
name=PacktCentOS8
osinfo=centos8.0
arch=x86_64
file=centos-8.0.img.xz
checksum=ccb4d840f5eb77d7d0ffbc4241fbf4d21fcc1acdd3679 c13174194810b17dc472566f6a29dba3a8992c1958b4698b6197e6a1689882 b67c1bc4d7de6738e947f
format=raw
size=8589934592
compressed_size=1220175252
notes=CentOS8 with KVM and SSH

一些解释。checksum是使用centos-8.0.img.xz上的sha512sum命令计算的。sizecompressed_size是原始和 XZd 文件的实际大小。之后,如果我们发出virt-builder --list |more命令,我们应该会得到如下所示的内容:

图 8.28-我们成功将图像添加到本地 virt-builder 存储库

图 8.28-我们成功将图像添加到本地 virt-builder 存储库

您可以清楚地看到我们的Packt01映像位于列表的顶部,我们可以轻松使用它来部署新的 VM。通过使用额外的存储库,我们可以极大地增强我们的工作流程,并重复使用我们现有的 VM 和模板来部署任意数量的 VM。想象一下,这与virt-builder的自定义选项结合使用,对于 OpenStack、Amazon Web ServicesAWS)等云服务有何作用。

我们列表上的下一个主题与快照有关,这是一个非常有价值但被误用的 VM 概念。有时,您在 IT 中有一些概念,既可以是好的,也可以是坏的,快照通常是其中的嫌疑犯。让我们解释一下快照的全部内容。

快照

VM 快照是系统在特定时间点的基于文件的表示。快照包括配置和磁盘数据。通过快照,您可以将 VM 恢复到某个时间点,这意味着通过对 VM 进行快照,您可以保留其状态,并在将来需要时轻松恢复到该状态。

快照有许多用途,例如在进行可能具有破坏性操作之前保存 VM 的状态。例如,假设您想要对现有的 Web 服务器 VM 进行一些更改,目前它正在正常运行,但您不确定您计划进行的更改是否会起作用或会破坏某些内容。在这种情况下,您可以在执行预期的配置更改之前对 VM 进行快照,如果出现问题,您可以通过恢复快照轻松恢复到 VM 的先前工作状态。

libvirt支持拍摄实时快照。您可以在客户机运行时对 VM 进行快照。但是,如果 VM 上有任何输入/输出I/O)密集型应用程序正在运行,建议首先关闭或暂停客户机,以确保干净的快照。

libvirt客户端主要有两类快照:内部和外部;每种都有其自己的优点和局限,如下所述:

  • qcow2文件。快照前后的位存储在单个磁盘中,从而提供更大的灵活性。virt-manager提供了一个图形管理实用程序来管理内部快照。以下是内部快照的限制:

a) 仅支持qcow2格式

b) 在拍摄快照时 VM 被暂停

c) 无法与 LVM 存储池一起使用

  • 外部快照:外部快照基于 COW 概念。当快照被拍摄时,原始磁盘映像变为只读,并创建一个新的覆盖磁盘映像以容纳客户写入,如下图所示:

图 8.29-快照概念

图 8.29-快照概念

覆盖磁盘映像最初创建为0字节的长度,它可以增长到原始磁盘的大小。覆盖磁盘映像始终为qcow2。但是,外部快照可以与任何基本磁盘映像一起使用。您可以对原始磁盘映像、qcow2或任何其他libvirt支持的磁盘映像格式进行外部快照。但是,目前尚无图形用户界面GUI)支持外部快照,因此与内部快照相比,管理起来更昂贵。

使用内部快照

在本节中,您将学习如何为 VM 创建、删除和恢复内部快照(离线/在线)。您还将学习如何使用virt-manager来管理内部快照。

内部快照仅适用于qcow2磁盘映像,因此首先确保要为其创建快照的虚拟机使用qcow2格式作为基础磁盘映像。如果不是,请使用qemu-img命令将其转换为qcow2格式。内部快照是磁盘快照和虚拟机内存状态的组合——这是一种可以在需要时轻松恢复的检查点。

我在这里使用LAMP01虚拟机作为示例来演示内部快照。LAMP01虚拟机位于本地文件系统支持的存储池上,并具有qcow2映像作为虚拟磁盘。以下命令列出了与虚拟机关联的快照:

# virsh snapshot-list LAMP01
Name Creation Time State
-------------------------------------------------

可以看到,目前与虚拟机关联的快照不存在;LAMP01 virsh snapshot-list命令列出了给定虚拟机的所有可用快照。默认信息包括快照名称、创建时间和域状态。通过向snapshot-list命令传递附加选项,可以列出许多其他与快照相关的信息。

创建第一个内部快照

在 KVM 主机上为虚拟机创建内部快照的最简单和首选方法是通过virsh命令。virsh具有一系列选项来创建和管理快照,如下所示:

  • snapshot-create: 使用 XML 文件创建快照

  • snapshot-create-as: 使用参数列表创建快照

  • snapshot-current: 获取或设置当前快照

  • snapshot-delete: 删除虚拟机快照

  • snapshot-dumpxml: 以 XML 格式转储快照配置

  • snapshot-edit: 编辑快照的 XML

  • snapshot-info: 获取快照信息

  • snapshot-list: 列出虚拟机快照

  • snapshot-parent: 获取快照的父名称

  • snapshot-revert: 将虚拟机恢复到特定快照

以下是创建快照的简单示例。运行以下命令将为LAMP01虚拟机创建一个内部快照:

# virsh snapshot-create LAMP01
Domain snapshot 1439949985 created

默认情况下,新创建的快照会以唯一编号作为名称。要创建具有自定义名称和描述的快照,请使用snapshot-create-as命令。这两个命令之间的区别在于后者允许将配置参数作为参数传递,而前者不允许。它只接受 XML 文件作为输入。在本章中,我们使用snapshot-create-as,因为它更方便和易于使用。

使用自定义名称和描述创建内部快照

要为LAMP01虚拟机创建一个名称为快照 1且描述为第一个快照的内部快照,请键入以下命令:

# virsh snapshot-create-as LAMP01 --name "Snapshot 1" --description "First snapshot" --atomic

使用--atomic选项指定,libvirt将确保如果快照操作成功或失败,不会发生任何更改。建议始终使用--atomic选项以避免在进行快照时发生任何损坏。现在,检查这里的snapshot-list输出:

# virsh snapshot-list LAMP01
Name Creation Time State
----------------------------------------------------
Snapshot1 2020-02-05 09:00:13 +0230 running

我们的第一个快照已准备就绪,现在我们可以使用它来恢复虚拟机的状态,如果将来出现问题。此快照是在虚拟机处于运行状态时拍摄的。快照创建所需的时间取决于虚拟机的内存量以及客户端在那个时间修改内存的活动程度。

请注意,虚拟机在创建快照时会进入暂停模式;因此,建议在虚拟机未运行时进行快照。从已关闭的虚拟机中进行快照可以确保数据完整性。

创建多个快照

我们可以根据需要继续创建更多快照。例如,如果我们创建两个额外的快照,使总数达到三个,那么snapshot-list的输出将如下所示:

# virsh snapshot-list LAMP01 --parent
Name Creation Time State Parent
--------------------------------------------------------------------
Snapshot1 2020-02-05 09:00:13 +0230 running (null)
Snapshot2 2020-02-05 09:00:43 +0230 running Snapshot1
Snapshot3 2020-02-05 09:01:00 +0230 shutoff Snapshot2

在这里,我们使用了--parent开关,它打印出快照的父-子关系。第一个快照的父级是(null),这意味着它直接在磁盘映像上创建,Snapshot1Snapshot2的父级,Snapshot2Snapshot3的父级。这有助于我们了解快照的顺序。使用--tree选项还可以获得类似树状的快照视图,如下所示:

# virsh snapshot-list LAMP01 --tree
Snapshot1
   |
  +- Snapshot2
       |
      +- Snapshot3

现在,检查state列,它告诉我们特定快照是在线还是离线。在前面的示例中,第一个和第二个快照是在 VM 运行时拍摄的,而第三个是在 VM 关闭时拍摄的。

恢复到关闭状态的快照将导致 VM 关闭。您还可以使用qemu-img命令实用程序获取有关内部快照的更多信息-例如,快照大小,快照标记等。在以下示例输出中,您可以看到名为LAMP01.qcow2的磁盘具有三个具有不同标记的快照。这还向您展示了特定快照的创建日期和时间:

# qemu-img info /var/lib/libvirt/qemu/LAMP01.qcow2
image: /var/lib/libvirt/qemu/LAMP01.qcow2
file format: qcow2
virtual size: 8.0G (8589934592 bytes)
disk size: 1.6G
cluster_size: 65536
Snapshot list:
ID TAG VM SIZE DATE VM CLOCK
1 1439951249 220M 2020-02-05 09:57:29 00:09:36.885
2 Snapshot1 204M 2020-02-05 09:00:13 00:01:21.284
3 Snapshot2 204M 2020-02-05 09:00:43 00:01:47.308
4 Snapshot3 0 2020-02-05 09:01:00 00:00:00.000

这也可以用来使用check开关检查qcow2镜像的完整性,如下所示:

# qemu-img check /var/lib/libvirt/qemu/LAMP01.qcow2
No errors were found on the image.

如果镜像中发生了任何损坏,上述命令将抛出错误。一旦在qcow2镜像中检测到错误,就应立即对 VM 进行备份。

恢复内部快照

拍摄快照的主要目的是在需要时恢复 VM 的干净/工作状态。让我们举个例子。假设在拍摄 VM 的Snapshot3之后,您安装了一个搞乱了整个系统配置的应用程序。在这种情况下,VM 可以轻松地恢复到创建Snapshot3时的状态。要恢复到快照,请使用snapshot-revert命令,如下所示:

# virsh snapshot-revert <vm-name> --snapshotname "Snapshot1"

如果要恢复到一个关闭的快照,那么您将不得不手动启动 VM。使用virsh snapshot-revert命令的--running开关可以使其自动启动。

删除内部快照

一旦确定不再需要快照,就可以删除它以节省空间。要删除 VM 的快照,请使用snapshot-delete命令。根据我们之前的示例,让我们删除第二个快照,如下所示:

# virsh snapshot-list LAMP01
Name Creation Time State
------------------------------------------------------
Snapshot1 2020-02-05 09:00:13 +0230 running
Snapshot2 2020-02-05 09:00:43 +0230 running
Snapshot3 2020-02-05 09:01:00 +0230 shutoff
Snapshot4 2020-02-18 03:28:36 +0230 shutoff
# virsh snapshot-delete LAMP01 Snapshot 2
Domain snapshot Snapshot2 deleted
# virsh snapshot-list LAMP01
Name Creation Time State
------------------------------------------------------
Snapshot1 2020-02-05 09:00:13 +0230 running
Snapshot3 2020-02-05 09:00:43 +0230 running
Snapshot4 2020-02-05 10:17:00 +0230 shutoff

现在让我们看看如何使用virt-manager执行这些程序,这是我们的 VM 管理的 GUI 实用程序。

使用 virt-manager 管理快照

正如您所期望的那样,virt-manager具有用于创建和管理 VM 快照的用户界面。目前,它仅适用于qcow2镜像,但很快也将支持原始镜像。使用virt-manager拍摄快照实际上非常简单;要开始,请打开 VM Manager 并单击要拍摄快照的 VM。

快照用户界面按钮(在下面的屏幕截图中用红色标记)出现在工具栏上;只有当 VM 使用qcow2磁盘时,此按钮才会被激活:

图 8.30-使用 virt-manager 快照

图 8.30-使用 virt-manager 快照

然后,如果我们想要拍摄快照,只需使用+按钮,这将打开一个简单的向导,以便我们可以为快照命名和描述,如下面的屏幕截图所示:

图 8.31-创建快照向导

图 8.31-创建快照向导

接下来,让我们看看如何使用外部磁盘快照,这是一种更快,更现代(尽管不太成熟)的 KVM/VM 快照概念。请记住,外部快照将会一直存在,因为它们具有对于现代生产环境非常重要的更多功能。

使用外部磁盘快照

您在上一节中了解了内部快照。内部快照非常简单,易于创建和管理。现在,让我们探索外部快照。外部快照主要涉及overlay_imagebacking_file。基本上,它将backing_file转换为只读状态,并开始在overlay_image上写入。这两个图像描述如下:

  • backing_file:VM 的原始磁盘图像(只读)

  • overlay_image:快照图像(可写)

如果出现问题,您可以简单地丢弃overlay_image图像,然后回到原始状态。

使用外部磁盘快照,backing_file图像可以是任何磁盘图像(rawqcow;甚至vmdk),而不像内部快照只支持qcow2图像格式。

创建外部磁盘快照

我们在这里使用WS2019SQL-Template VM 作为示例来演示外部快照。此 VM 位于名为vmstore1的文件系统存储池中,并具有充当虚拟磁盘的原始图像。以下代码片段提供了有关此 VM 的详细信息:

# virsh domblklist WS2019SQL-Template --details
Type Device Target Source
------------------------------------------------
file disk vda /var/lib/libvirt/images/WS2019SQL-Template.img

让我们看看如何创建此 VM 的外部快照,如下所示:

  1. 通过执行以下代码检查要对其进行快照的 VM 是否正在运行:
# virsh list
Id Name State
-----------------------------------------
4 WS2019SQL-Template running

您可以在 VM 运行时或关闭时进行外部快照。支持在线和离线快照方法。

  1. 通过virsh创建 VM 快照,如下所示:
--disk-only parameter creates a disk snapshot. This is used for integrity and to avoid any possible corruption.
  1. 现在,检查snapshot-list输出,如下所示:
# virsh snapshot-list WS2019SQL-Template
Name Creation Time State
----------------------------------------------------------
snapshot1 2020-02-10 10:21:38 +0230 disk-snapshot
  1. 现在,快照已经创建,但它只是磁盘状态的快照;内存内容没有被存储,如下截图所示:
# virsh snapshot-info WS2019SQL-Template snapshot1
Name: snapshot1
Domain: WS2019SQL-Template
Current: no
State: disk-snapshot
Location: external <<
Parent: -
Children: 1
Descendants: 1
Metadata: yes
  1. 现在,再次列出与 VM 关联的所有块设备,如下所示:
image /var/lib/libvirt/images/WS2019SQL-Template.snapshot1 snapshot, as follows:

/var/lib/libvirt/images/WS2019SQL-Template.img。


  1. 这表明新的image /var/lib/libvirt/images/WS2019SQL-Template.snapshot1快照现在是原始镜像/var/lib/libvirt/images/WS2019SQL-Template.img的读/写快照;对WS2019SQL-Template.snapshot1所做的任何更改都不会反映在WS2019SQL-Template.img中。

重要说明

/var/lib/libvirt/images/WS2019SQL-Template.img是支持文件(原始磁盘)。

/var/lib/libvirt/images/WS2019SQL-Template.snapshot1是新创建的叠加图像,现在所有写操作都在此进行。

  1. 现在,让我们创建另一个快照:
# virsh snapshot-create-as WS2019SQL-Template snapshot2 --description "Second Snapshot" --disk-only --atomic
Domain snapshot snapshot2 created
# virsh domblklist WS2019SQL-Template --details
Type Device Target Source
------------------------------------------------
file disk vda /snapshot_store/WS2019SQL-Template.snapshot2

在这里,我们使用了--diskspec选项在所需位置创建快照。该选项需要以disk[,snapshot=type][,driver=type][,file=name]格式进行格式化。使用的参数表示如下:

  • disk:在virsh domblklist <vm_name>中显示的目标磁盘。

  • snapshot:内部或外部。

  • driverlibvirt

  • file:要创建结果快照磁盘的位置路径。您可以使用任何位置;只需确保已设置适当的权限。

让我们再创建一个快照,如下所示:

# virsh snapshot-create-as WS2019SQL-Template snapshot3 --description "Third Snapshot" --disk-only --quiesce
Domain snapshot snapshot3 created

请注意,这次我添加了一个选项:--quiesce。我们将在下一节讨论这个。

什么是 quiesce?

Quiesce 是一个文件系统冻结(fsfreeze/fsthaw)机制。这将使客户文件系统处于一致状态。如果不执行此步骤,等待写入磁盘的任何内容都不会包含在快照中。此外,在快照过程中进行的任何更改可能会损坏图像。为了解决这个问题,需要在客户机上安装并运行qemu-guest代理。快照创建将失败并显示错误,如下所示:

error: Guest agent is not responding: Guest agent not available for now

在进行快照时,始终使用此选项以确保安全。客户工具安装在第五章Libvirt Storage中进行了介绍;如果尚未安装,您可能需要重新查看并在 VM 中安装客户代理。

到目前为止,我们已经创建了三个快照。让我们看看它们如何连接在一起,以了解外部快照链是如何形成的,如下所示:

  1. 列出与 VM 关联的所有快照,如下所示:
# virsh snapshot-list WS2019SQL-Template
Name Creation Time State
----------------------------------------------------------
snapshot1 2020-02-10 10:21:38 +0230 disk-snapshot
snapshot2 2020-02-10 11:51:04 +0230 disk-snapshot
snapshot3 2020-02-10 11:55:23 +0230 disk-snapshot
  1. 通过运行以下代码检查虚拟机的当前活动(读/写)磁盘/快照:
# virsh domblklist WS2019SQL-Template
Target Source
------------------------------------------------
vda /snapshot_store/WS2019SQL-Template.snapshot3
  1. 您可以使用 qemu-img 提供的 --backing-chain 选项枚举当前活动(读/写)快照的支持文件链。--backing-chain 将向我们显示磁盘镜像链中父子关系的整个树。有关更多描述,请参考以下代码片段:
# qemu-img info --backing-chain /snapshot_store/WS2019SQL-Template.snapshot3|grep backing
backing file: /snapshot_store/WS2019SQL-Template.snapshot2
backing file format: qcow2
backing file: /var/lib/libvirt/images/WS2019SQL-Template.snapshot1
backing file format: qcow2
backing file: /var/lib/libvirt/images/WS2019SQL-Template.img
backing file format: raw

从前面的细节中,我们可以看到链是以以下方式形成的:

图 8.32 – 我们示例虚拟机的快照链

图 8.32 – 我们示例虚拟机的快照链

因此,它必须按照以下方式读取:snapshot3snapshot2 作为其支持文件;snapshot2snapshot1 作为其支持文件;snapshot1 有基础镜像作为其支持文件。目前,snapshot3 是当前活动的快照,即发生实时客户写入的地方。

恢复到外部快照

在一些较旧的 RHEL/CentOS 版本中,libvirt 对外部快照的支持是不完整的,甚至在 RHEL/CentOS 7.5 中也是如此。快照可以在线或离线创建,在 RHEL/CentOS 8.0 中,在快照处理方式方面发生了重大变化。首先,Red Hat 现在建议使用外部快照。此外,引用 Red Hat 的话:

在 RHEL 8 中不支持创建或加载运行中虚拟机的快照,也称为实时快照。此外,请注意,在 RHEL 8 中不建议使用非实时虚拟机快照。因此,支持创建或加载关闭的虚拟机快照,但 Red Hat 建议不要使用它。

需要注意的是,virt-manager 仍不支持外部快照,正如以下截图所示,以及我们几页前创建这些快照时,从未有选择外部快照作为快照类型的选项:

图 8.33 – 从 virt-manager 和 libvirt 命令创建的所有快照没有额外选项的是内部快照

图 8.33 – 从 virt-manager 和 libvirt 命令创建的所有快照,没有额外选项的是内部快照

现在,我们还使用 WS2019SQL-Template 虚拟机并在其上创建了外部快照,因此情况有所不同。让我们检查一下,如下所示:

图 8.34 – WS2019SQL-Template 有外部快照

图 8.34 – WS2019SQL-Template 有外部快照

我们可以采取的下一步是恢复到先前的状态—例如,snapshot3。我们可以轻松地通过使用 virsh snapshot-revert 命令从 shell 中执行此操作,如下所示:

# virsh snapshot-revert WS2019SQL-Template --snapshotname "snapshot3"
error: unsupported configuration: revert to external snapshot not supported yet

这是否意味着一旦为虚拟机创建了外部磁盘快照,就无法恢复到该快照?不—不是这样的;您肯定可以恢复到快照,但没有 libvirt 支持来完成这一点。您将不得不通过操纵域 XML 文件来手动恢复。

WS2019SQL-Template 虚拟机为例,它有三个关联的快照,如下所示:

virsh snapshot-list WS2019SQL-Template
Name Creation Time State
------------------------------------------------------------
snapshot1 2020-02-10 10:21:38 +0230 disk-snapshot
snapshot2 2020-02-10 11:51:04 +0230 disk-snapshot
snapshot3 2020-02-10 11:55:23 +0230 disk-snapshot

假设您想要恢复到 snapshot2。解决方案是关闭虚拟机(是的—关闭/关机是强制性的),并编辑其 XML 文件,将磁盘映像指向 snapshot2 作为引导映像,如下所示:

  1. 找到与 snapshot2 关联的磁盘映像。我们需要图像的绝对路径。您可以简单地查看存储池并获取路径,但最好的选择是检查快照 XML 文件。如何?从 virsh 命令获取帮助,如下所示:
# virsh snapshot-dumpxml WS2019SQL-Template --snapshotname snapshot2 | grep
'source file' | head -1
<source file='/snapshot_store/WS2019SQL-Template.snapshot2'/>
  1. /snapshot_store/WS2019SQL-Template.snapshot2 是与 snapshot2 相关的文件。验证它是否完好,并且与 backing_file 正确连接,如下所示:
backing_file is correctly pointing to the snapshot1 disk. All good. If an error is detected in the qcow2 image, use the -r leaks/all parameter. It may help repair the inconsistencies, but this isn't guaranteed. Check this excerpt from the qemu-img man page:
  1. 使用 qemu-img 的 -r 开关尝试修复发现的任何不一致性

  2. 在检查期间。-r leaks 仅修复集群泄漏,而 -r all 修复所有

  3. 错误的类型,选择错误修复或隐藏的风险更高

  4. 已经发生的损坏。

让我们检查有关此快照的信息,如下所示:

# qemu-img info /snapshot_store/WS2019SQL-Template.snapshot2 | grep backing
backing file: /var/lib/libvirt/images/WS2019SQL-Template.snapshot1
backing file format: qcow2
  1. 现在是操作 XML 文件的时候了。您可以从 VM 中删除当前附加的磁盘和add /snapshot_store/WS2019SQL-Template.snapshot2。或者,手动编辑 VM 的 XML 文件并修改磁盘路径。其中一个更好的选择是使用virt-xml命令,如下所示:
WS2019SQL-Template.snapshot2 as the boot disk for the VM; you can verify that by executing the following command:

virt-xml 命令。请参阅其手册以熟悉它。它也可以在脚本中使用。


  1. 启动 VM,您将回到snapshot2被拍摄时的状态。类似地,您可以在需要时恢复到snapshot1或基本镜像。

我们列表中的下一个主题是删除外部磁盘快照,正如我们提到的那样,这有点复杂。让我们看看接下来我们如何做到这一点。

删除外部磁盘快照

删除外部快照有些棘手。外部快照不能像内部快照那样直接删除。它首先需要手动与基本层或向活动层合并,然后才能删除。有两种在线合并快照的实时块操作,如下所示:

  • blockcommit:将数据与基本层合并。使用此合并机制,您可以将叠加图像合并到后备文件中。这是最快的快照合并方法,因为叠加图像可能比后备图像小。

  • blockpull:向活动层合并数据。使用此合并机制,您可以将数据从backing_file合并到叠加图像。结果文件将始终以qcow2格式存在。

接下来,我们将阅读有关使用blockcommit合并外部快照的信息。

使用blockcommit合并外部快照

我们创建了一个名为VM1的新 VM,它有一个名为vm1.img的基本镜像(原始),有四个外部快照。/var/lib/libvirt/images/vm1.snap4是活动快照图像,实时写入发生在这里;其余的处于只读模式。我们的目标是删除与此 VM 相关的所有快照,操作如下:

  1. 列出当前正在使用的活动磁盘镜像,如下所示:
the /var/lib/libvirt/images/vm1.snap4 image is the currently active image on which all writes are occurring.
  1. 现在,枚举/var/lib/libvirt/images/vm1.snap4的后备文件链,如下所示:
# qemu-img info --backing-chain /var/lib/libvirt/images/vm1.snap4 | grep backing
backing file: /var/lib/libvirt/images/vm1.snap3
backing file format: qcow2
backing file: /var/lib/libvirt/images/vm1.snap2
backing file format: qcow2
backing file: /var/lib/libvirt/images/vm1.snap1
backing file format: qcow2
backing file: /var/lib/libvirt/images/vm1.img
backing file format: raw
  1. 是时候将所有快照图像合并到基本图像中了,如下所示:
# virsh blockcommit VM1 hda --verbose --pivot --active
Block Commit: [100 %]
Successfully pivoted
4\. Now, check the current active block device in use:
# virsh domblklist VM1
Target Source
--------------------------
hda /var/lib/libvirt/images/vm1.img

请注意,当前活动的块设备现在是基本镜像,所有写入都切换到它,这意味着我们成功地将快照图像合并到基本镜像中。但是以下代码片段中的snapshot-list输出显示仍然有与 VM 相关的快照:

# virsh snapshot-list VM1
Name Creation Time State
-----------------------------------------------------
snap1 2020-02-12 09:10:56 +0230 shutoff
snap2 2020-02-12 09:11:03 +0230 shutoff
snap3 2020-02-12 09:11:09 +0230 shutoff
snap4 2020-02-12 09:11:17 +0230 shutoff

如果您想摆脱这个问题,您需要删除适当的元数据并删除快照图像。正如前面提到的,libvirt不完全支持外部快照。目前,它只能合并图像,但没有自动删除快照元数据和叠加图像文件的支持。这必须手动完成。要删除快照元数据,请运行以下代码:

# virsh snapshot-delete VM1 snap1 --children --metadata
# virsh snapshot-list VM1
Name Creation Time State

在这个例子中,我们学习了如何使用blockcommit方法合并外部快照。接下来让我们学习如何使用blockpull方法合并外部快照。

使用blockpull合并外部快照

我们创建了一个名为VM2的新 VM,它有一个名为vm2.img的基本镜像(原始),只有一个外部快照。快照磁盘是活动镜像,可以进行实时写入,而基本镜像处于只读模式。我们的目标是删除与此 VM 相关的快照。操作如下:

  1. 列出当前正在使用的活动磁盘镜像,如下所示:
/var/lib/libvirt/images/vm2.snap1 image is the currently active image on which all writes are occurring.
  1. 现在,枚举/var/lib/libvirt/imagesvar/lib/libvirt/images/vm2.snap1的后备文件链,如下所示:
# qemu-img info --backing-chain /var/lib/libvirt/images/vm2.snap1 | grep backing
backing file: /var/lib/libvirt/images/vm1.img
backing file format: raw
  1. 将基本镜像合并到快照镜像(从基本镜像到叠加图像合并),如下所示:
/var/lib/libvirt/images/vm2.snap1. It got considerably larger because we pulled the base_image and merged it into the snapshot image to get a single file.
  1. 现在,您可以按以下方式删除base_image和快照元数据:
# virsh snapshot-delete VM2 snap1 --metadata

我们在 VM 运行状态下运行了合并和快照删除任务,没有任何停机时间。blockcommitblockpull也可以用于从快照链中删除特定的快照。查看virsh的 man 页面以获取更多信息并尝试自己操作。在本章的进一步阅读部分中,你还会找到一些额外的链接,所以确保你仔细阅读它们。

在使用快照时的用例和最佳实践

我们提到在 IT 世界中关于快照存在着一种爱恨交织的关系。让我们讨论一下在使用快照时的原因和一些常识的最佳实践,如下所示:

  • 当你拍摄 VM 快照时,你正在创建 VM 磁盘的新增量副本,qemu2,或者一个原始文件,然后你正在写入该增量。因此,你写入的数据越多,提交和合并回父级的时间就越长。是的——你最终需要提交快照,但不建议你在 VM 上附加快照进入生产环境。

  • 快照不是备份;它们只是在特定时间点拍摄的状态图片,你可以在需要时恢复到该状态。因此,不要将其作为直接备份过程的依赖。为此,你应该实施备份基础设施和策略。

  • 不要将带有快照的 VM 保留很长时间。一旦你验证了不再需要恢复到快照拍摄时的状态,立即合并并删除快照。

  • 尽可能使用外部快照。与内部快照相比,外部快照的损坏几率要低得多。

  • 限制快照数量。连续拍摄多个快照而没有任何清理可能会影响 VM 和主机的性能,因为qemu将不得不遍历快照链中的每个图像来从base_image读取新文件。

  • 在拍摄快照之前在 VM 中安装 Guest Agent。通过来宾内的支持,快照过程中的某些操作可以得到改进。

  • 在拍摄快照时始终使用--quiesce--atomic选项。

如果你使用这些最佳实践,我们建议你使用快照来获益。它们会让你的生活变得更轻松,并为你提供一个可以回到的点,而不会带来所有的问题和麻烦。

总结

在本章中,你学会了如何使用libguestfs实用程序来修改 VM 磁盘,创建模板和管理快照。我们还研究了virt-builder和各种为我们的 VM 提供方法,因为这些是现实世界中最常见的场景之一。在下一章中,我们将更多地了解在大量部署 VM 时的概念(提示:云服务),这就是关于cloud-init的一切。

问题

  1. 我们为什么需要修改 VM 磁盘?

  2. 我们如何将 VM 转换为 KVM?

  3. 我们为什么使用 VM 模板?

  4. 我们如何创建基于 Linux 的模板?

  5. 我们如何创建基于 Microsoft Windows 的模板?

  6. 你知道哪些从模板部署的克隆机制?它们之间有什么区别?

  7. 我们为什么使用virt-builder

  8. 我们为什么使用快照?

  9. 使用快照的最佳实践是什么?

进一步阅读

有关本章内容的更多信息,请参考以下链接:

第三部分:KVM 虚拟机的自动化、定制和编排

在本书的这一部分,您将完全了解如何使用cloud-initcloudbase-init来定制 KVM 虚拟机。本部分还涵盖了如何利用 Ansible 的自动化能力来管理和编排 KVM 基础架构。

本书的这一部分包括以下章节:

  • 第九章, 使用 cloud-init 自定义虚拟机

  • 第十章, 自动化的 Windows 客户端部署和定制

  • 第十一章, Ansible 和编排自动化脚本

第九章:使用云初始化自定义虚拟机

定制虚拟机通常看起来很简单 - 从模板克隆它;启动;点击几个下一步按钮(或文本标签);创建一些用户、密码和组;配置网络设置... 这对于一两台虚拟机可能有效。但如果我们需要部署两三百台虚拟机并对它们进行配置呢?突然间,我们面临着一项庞大的任务 - 如果我们手动完成所有工作,这项任务将容易出现错误。我们在做这些事情的时候浪费了宝贵的时间,而不是以更简化、自动化的方式进行配置。这就是云初始化派上用场的地方,因为它可以定制我们的虚拟机,在它们上安装软件,并且可以在首次和后续虚拟机启动时进行。因此,让我们讨论一下云初始化以及它如何为你的大规模配置噩梦带来价值。

在本章中,我们将涵盖以下主题:

  • 虚拟机定制的需求是什么?

  • 理解云初始化

  • 云初始化架构

  • 如何在启动时安装和配置云初始化

  • 云初始化镜像

  • 云初始化数据源

  • 向云初始化传递元数据和用户数据

  • 如何使用云配置脚本与云初始化

虚拟机定制的需求是什么?

一旦你真正开始使用虚拟机并学会如何掌握它们,你会注意到一件事似乎经常发生:虚拟机部署。由于一切都很容易配置和部署,你会开始为几乎任何事情创建新的虚拟机实例,有时甚至只是为了检查特定应用程序是否在特定操作系统版本上运行。这让作为开发人员和系统管理员的生活变得更加轻松,但也带来了一系列问题。其中最困难的之一是模板管理。即使你有一小组不同的服务器和相对较少的不同配置,事情也会开始累积起来,如果你决定通过 KVM 以正常方式管理模板,组合的数量将很快变得太大。

你很快会面临的另一个问题是兼容性。当你离开你选择的 Linux 发行版,需要部署另一个具有自己规则和部署策略的 Linux 发行版时,事情就会变得复杂起来。通常,最大的问题是系统定制。在网络设置和主机名方面,网络上的每台计算机都应该有自己独特的身份。使用 DHCP 网络配置的模板可以解决其中一个问题,但这远远不足以简化事情。例如,我们可以在 CentOS / RHEL 和兼容的 Linux 发行版上使用 Kickstart。Kickstart 是一种在部署时配置系统的方法,如果你使用这些特定的发行版,这可能是快速部署物理或虚拟机的最佳方式。另一方面,Kickstart 会使你的部署比应该更慢,因为它使用一个配置文件,使我们能够向干净的安装添加软件和配置。

基本上,它用我们之前定义的设置填充了额外的配置提示。这意味着我们基本上每次需要部署新的虚拟机时都在进行完整的安装,并创建一个完整的系统。

主要问题是其他发行版不使用 Kickstart。有类似的系统可以实现无人值守安装。Debian 和 Ubuntu 使用一个叫做preseed的工具/系统,并且能够在某些部分支持 Kickstart,SuSe 使用 AutoYaST,甚至有一些工具提供某种跨平台功能。其中一个叫做Fully Automated InstallFAI)的工具能够自动安装甚至在线重新配置不同的 Linux 发行版。但这仍然不能解决我们所有的问题。在虚拟化的动态世界中,主要目标是尽快部署并尽可能自动化,因为我们在从生产环境中移除虚拟机时也倾向于使用相同的灵活性。

想象一下:你需要创建一个单一的应用部署,以测试你的新应用在不同的 Linux 发行版上的情况。你所有未来的虚拟机都需要有一个主机名的唯一标识符,一个部署的 SSH 身份,可以通过 Ansible 进行远程管理,当然还有你的应用。你的应用有三个依赖项——两个以可以通过 Ansible 部署的软件包形式存在,但其中一个依赖于正在使用的 Linux 发行版,并且必须为该特定的 Linux 发行版进行定制。为了使事情更加真实,你预计你将不时地重复这个测试,并且每次你都需要重建你的依赖项。

你可以创建这种环境的几种方式。一种方法是简单地手动安装所有服务器并创建模板。这意味着手动配置一切,然后创建将要部署的虚拟机模板。如果我们打算部署到超过几个 Linux 发行版,这是很多工作。一旦发行版升级,这将变得更加繁重,因为我们从中部署的所有模板必须经常升级,通常在不同的时间点。这意味着我们可以手动更新所有虚拟机模板,或者在每个模板上执行安装后升级。这是很多工作,而且非常慢。再加上这样一个事实,即这样的测试可能涉及在新旧版本的虚拟机模板上运行你的测试应用。除此之外,我们还需要解决为每个部署的 Linux 发行版定制我们的网络设置的问题。当然,这也意味着我们的虚拟机模板远非通用。过一段时间,我们将会为每个测试周期拥有数十个虚拟机模板。

解决这个问题的另一种方法可以是使用像 Ansible 这样的系统——我们从虚拟机模板部署所有系统,然后通过 Ansible 进行定制。这更好——Ansible 就是为这样的场景设计的,但这意味着我们必须首先创建能够支持 Ansible 部署的虚拟机模板,带有实现的 SSH 密钥和 Ansible 运行所需的其他一切。

这两种方法都无法解决的一个问题是大规模部署机器。这就是为什么设计了一个叫做 cloud-init 的框架。

理解 cloud-init

我们需要在技术上更深入一些,以了解 cloud-init 是什么,以及了解它的局限性是什么。因为我们正在谈论一种使用简单配置文件完全自动重新配置系统的方式,这意味着有些事情需要事先准备好,以使这个复杂的过程更加用户友好。

我们已经在第八章中提到了虚拟机模板,创建和修改 VM 磁盘、模板和快照。在这里,我们谈论的是一个特别配置的模板,它具有阅读、理解和部署我们将在文件中提供的配置所需的所有元素。这意味着这个特定的镜像必须提前准备好,是整个系统中最复杂的部分。

幸运的是,cloud-init 镜像可以预先下载并进行配置,我们唯一需要知道的是我们想要使用哪个发行版。我们在本书中提到的所有发行版(CentOS 7 或 8、Debian、Ubuntu 和 Red Hat Enterprise Linux 7 和 8)都有我们可以使用的镜像。其中一些甚至有基本操作系统的不同版本可用,因此如果需要,我们可以使用它们。请注意,安装的 cloud-init 版本可能会有所不同,特别是在旧版本的镜像上。

为什么这个镜像很重要?因为它被准备好可以检测其运行的云系统,确定是否应该使用 cloud-init 或者禁用它,然后读取并执行系统本身的配置。

理解 cloud-init 架构

Cloud-init 使用引导阶段的概念,因为它需要对系统在引导期间发生的事情进行精细和细粒度的控制。当然,使用 cloud-init 的前提是有一个 cloud-init 镜像。从cloudinit.readthedocs.io提供的文档中,我们可以了解到 cloud-init 引导有五个阶段:

  • 存在/etc/cloud/cloud-init.diabled。有关本章中的所有内容和其他内容的更多信息,请阅读文档(从cloudinit.readthedocs.io/en/latest/topics/boot.html开始),因为它包含了关于 cloud-init 支持的开关和不同选项的更多详细信息。

  • 名为cloud-init-local.servicesystemd服务,它将尽快运行并阻塞网络直到完成。在 cloud-init 初始化中经常使用阻塞服务和目标的概念,原因很简单 - 以确保系统稳定性。由于 cloud-init 过程修改了系统的许多核心设置,我们不能让通常的启动脚本运行并创建可能覆盖 cloud-init 创建的配置的并行配置。

  • cloud-init.service。这是主要服务,将启动之前配置好的网络,并尝试配置我们在数据文件中安排的一切。这通常包括获取我们配置中指定的所有文件,提取它们,并执行其他准备任务。如果指定了这样的配置更改,磁盘也将在此阶段进行格式化和分区。还将创建挂载点,包括那些动态的和特定于特定云平台的挂载点。

  • yum_reposapt模块),添加 SSH 密钥(ssh-import-id模块),并执行类似的任务,为下一阶段做准备,我们实际上可以在这个阶段使用在此阶段完成的配置。

  • 最终阶段是系统引导的一部分,运行可能属于用户空间的东西 - 安装软件包、配置管理插件部署和执行可能的用户脚本。

所有这些都完成后,系统将完全配置好并运行。

这种方法的主要优势,尽管看起来复杂,是在云中只存储一个镜像,然后创建简单的配置文件,只覆盖vanilla默认配置和我们需要的配置之间的差异。镜像也可以相对较小,因为它们不包含太多面向最终用户的软件包。

Cloud-init 经常被用作部署许多将由编排系统(如 Puppet 或 Ansible)管理的机器的第一阶段,因为它提供了一种创建包括连接到每个实例的方式的工作配置的方法。每个阶段都使用 YAML 作为其主要数据语法,几乎所有内容都只是不同选项和变量的列表,这些选项和变量会被转换为配置信息。由于我们正在配置一个系统,我们还可以在配置中包含几乎任何其他类型的文件——一旦我们可以在配置系统时运行 shell 脚本,一切皆有可能。

为什么所有这些如此重要?

cloud-init 源自一个简单的想法:创建一个单一模板,定义您计划使用的操作系统的基本内容。然后,我们创建一个单独的、特殊格式的数据文件,其中包含定制数据,然后在运行时将这两者组合起来,以创建在需要时的新实例。您甚至可以通过使用模板作为基础镜像,然后创建不同的系统作为差异镜像,稍微改善一下,以便在几分钟而不是几小时内部署。

cloud-init 的构想是尽可能多地支持多平台,并包括尽可能多的操作系统。目前,它支持以下操作系统:

  • Ubuntu

  • SLES/openSUSE

  • RHEL/CentOS

  • Fedora

  • Gentoo Linux

  • Debian

  • Arch Linux

  • FreeBSD

我们列举了所有的发行版,但正如其名称所示,cloud-init 也是“云感知”的,这意味着 cloud-init 能够自动检测并使用几乎任何云环境。在任何硬件或云上运行任何发行版总是可能的,即使没有类似 cloud-init 这样的东西,但由于想要创建一个平台无关的配置,可以在任何云上部署而无需重新配置,我们的系统需要自动考虑不同云基础设施之间的任何差异。此外,即使 cloud-init 并非专门为裸金属部署而设计,或者更准确地说,即使它的设计远不止于此,它也可以用于裸金属部署。

重要提示

云感知意味着 cloud-init 为我们提供了进行部署后检查和配置更改的工具,这是另一个极其有用的选项。

这一切听起来比应该更加理论化。实际上,一旦开始使用 cloud-init 并学会如何配置它,您将开始创建一个几乎完全独立于您所使用的云基础设施的虚拟机基础架构。在本书中,我们使用 KVM 作为主要的虚拟化基础设施,但 cloud-init 可以与任何其他云环境一起使用,通常无需任何修改。cloud-init 最初是为了在 Amazon AWS 上实现简单部署而设计的,但它早已超越了这种限制。

此外,cloud-init 知晓不同发行版之间的所有细微差别,因此您在配置文件中设置的所有内容都将被转换为特定发行版用于完成特定任务的内容。在这方面,cloud-init 的行为很像 Ansible——实质上,您定义需要做什么,而不是如何做,cloud-init 会接管并实现它。

在启动时安装和配置 cloud-init。

我们在本章中要讨论的主要内容是如何使 cloud-init 运行,并在部署机器时将其所有部分放在正确的位置,但这只是揭示了 cloud-init 实际工作原理的一部分。您需要理解的是,cloud-init 作为一个服务运行,配置系统,并按照我们告诉它的方式进行操作。系统启动后,我们可以连接到它并查看已完成的工作,以及如何完成的,并分析日志。这可能与完全自动化部署的想法相悖,但这是有原因的-无论我们做什么,总会有可能需要调试系统或执行一些后安装任务,这些任务也可以自动化。

使用 cloud-init 并不仅仅局限于调试。系统启动后,系统会生成大量关于启动方式、系统实际的云配置以及定制方面的数据。然后您的应用程序和脚本可以依赖这些数据,并用它来运行和检测某些配置和部署参数。请看这个例子,取自在 Microsoft Azure 上运行 Ubuntu 的虚拟机:

图 9.1-启动时 cloud-init 输出的一部分

图 9.1-启动时 cloud-init 输出的一部分

cloud-init 实际上在启动时显示这个(根据 cloud-init 配置文件的不同,可能还有更多内容),然后将所有这些输出放入其日志文件中。因此,我们在额外信息方面得到了很好的覆盖。

我们 cloud-init 之旅的下一步是讨论 cloud-init 图片,因为这些是我们需要使 cloud-init 工作的东西。让我们现在来做这件事。

Cloud-init 图片

为了在启动时使用 cloud-init,我们首先需要一个云镜像。在其核心,它基本上是一个半安装的系统,其中包含专门设计的脚本,支持 cloud-init 安装。在所有发行版上,这些脚本都是 cloud-init 包的一部分,但是镜像通常比这更加准备就绪,因为它们试图在大小和安装便利性之间找到一个平衡点。

在我们的示例中,我们将使用以下网址提供的图片:

在我们将要处理的所有示例中,主要意图是展示系统如何在两种完全不同的架构上运行,几乎没有或没有最小的修改。

在正常情况下,获取镜像就是您需要能够运行 cloud-init 的一切。其他一切都由数据文件处理。

例如,这些是 CentOS 发行版的一些可用图片:

图 9.2-为 CentOS 提供的丰富的 cloud-init 图片

图 9.2-为 CentOS 提供的丰富的 cloud-init 图片

注意,几乎所有发行版的发布都包含了图片,因此我们不仅可以在最新版本上测试我们的系统,还可以在所有其他可用版本上进行测试。我们可以自由使用所有这些图片,这正是我们稍后将要做的事情,当我们开始使用我们的示例时。

Cloud-init 数据源

让我们稍微谈谈数据文件。到目前为止,我们已经泛泛地提到它们,并且我们有充分的理由这样做。使 cloud-init 脱颖而出的一件事是它支持不同的获取安装信息和如何安装的方式。我们称这些配置文件为数据源,它们可以分为两大类 - 用户数据元数据。我们将在本章中更详细地讨论每一个,但作为早期介绍,让我们说一下,用户作为配置的一部分创建的所有东西,包括 YAML 文件、脚本、配置文件,可能还有其他要放在系统上的文件,比如用户数据的应用程序和依赖项。元数据通常直接来自云提供商或用于标识机器。

它包含实例数据、主机名、网络名称和其他在部署时可能有用的云特定细节。我们可以在引导过程中使用这两种类型的数据,并且将这样做。我们放入的所有内容都将在运行时存储在/run/cloud-init/instance-data.json中的大型 JSON 存储中,或作为实际机器配置的一部分。这个文件的一个很好的例子是主机名,作为最终将成为个别机器上实际主机名的元数据的一部分。这个文件由 cloud-init 填充,并且可以通过命令行或直接访问。

在创建任何配置文件时,我们可以使用任何可用的文件格式,并且如果需要,我们可以压缩文件 - cloud-init 将在运行之前对其进行解压缩。如果我们需要将实际文件传递到配置中,尽管有一个限制 - 文件需要编码为文本并放入 YAML 文件中的变量中,以便在我们正在配置的系统上稍后使用和写入。就像 cloud-init 一样,YAML 语法是声明性的 - 这是一个重要的事情要记住。

现在,让我们学习如何将元数据和用户数据传递给 cloud-init。

将元数据和用户数据传递给 cloud-init

在我们的示例中,我们将创建一个文件,它实质上是一个.iso镜像,并且会像连接到引导机器的 CD-ROM 一样行为。Cloud-init 知道如何处理这种情况,并且会挂载文件,提取所有脚本,并按照预定顺序运行它们,就像我们在解释引导顺序是已经提到的那样(在本章前面的理解 cloud-init 架构部分中检查)。

基本上,我们需要做的是创建一个镜像,将其连接到云模板,并在模板内部提供所有数据文件给 cloud-init 脚本。这是一个三步过程:

  1. 我们必须创建保存配置信息的文件。

  2. 我们必须创建一个包含文件数据的镜像,并将其放在正确的位置。

  3. 我们需要在引导时将镜像与模板关联起来。

最复杂的部分是定义在引导时如何配置以及需要配置什么。所有这些都是在运行给定发行版的云工具软件包的机器上完成的。

在这一点上,我们需要指出在所有发行版中用于启用 cloud-init 支持的两种不同软件包:

  • cloud-init - 包含使计算机能够在引导过程中重新配置自身的一切必要内容,如果遇到云初始化配置

  • cloud-utils - 用于创建要应用于云镜像的配置

这些软件包之间的主要区别是我们安装它们的计算机。cloud-init是要安装在我们正在配置的计算机上的,并且是部署镜像的一部分。cloud-utils是用于在将创建配置的计算机上使用的软件包。

在本章的所有示例和所有配置步骤中,实际上我们在引用两台不同的计算机/服务器:一台可以被视为主要计算机,而我们在本章中使用的计算机-除非另有说明-是我们用来创建 cloud-init 部署配置的计算机。这不是将使用此配置进行配置的计算机,而只是我们用作工作站准备文件的计算机。

在这种简化的环境中,这是运行整个 KVM 虚拟化并用于创建和部署虚拟机的同一台计算机。在正常设置中,我们可能会在工作站上创建我们的配置,并部署到某种基于 KVM 的主机或集群。在这种情况下,我们在本章中呈现的每个步骤基本上保持不变;唯一的区别是我们部署的位置,以及第一次启动时调用虚拟机的方式。

我们还将注意到,一些虚拟化环境,如 OpenStack、oVirt 或 RHEV-M,有直接的方式与启用了 cloud-init 的模板进行通信。其中一些甚至允许您在首次启动时直接从 GUI 重新配置机器,但这超出了本书的范围。

我们列表中的下一个主题是 cloud-init 模块。Cloud-init 使用模块的原因是为了扩展其在虚拟机引导阶段可以采取的可用操作范围。有数十个可用的 cloud-init 模块-SSHyumapt、设置hostnamepasswordlocale和创建用户和组等。让我们看看如何使用它们。

使用 cloud-init 模块

在创建配置文件时,在 cloud-init 中,几乎与任何其他软件抽象层一样,我们正在处理将我们更多或少通用的配置需求(例如需要安装此软件包)转换为特定系统上的实际 shell 命令的模块。这是通过模块完成的。模块是将不同功能分解为较小组的逻辑单元,并使我们能够使用不同的命令。您可以在以下链接中查看所有可用模块的列表:cloudinit.readthedocs.io/en/latest/topics/modules.html。这是一个相当长的列表,这将进一步显示出 cloud-init 的开发程度。

从列表中我们可以看到,例如Disk setupLocale等一些模块是完全独立于平台的,而例如Puppet等一些模块则设计用于与特定软件解决方案及其配置一起使用,而一些则特定于特定发行版或一组发行版,如Yum Add RepoApt Configure

这似乎破坏了完全与发行版无关的部署一切的想法,但您必须记住两件事-cloud-init 首先是与云无关的,而不是与发行版无关的,并且发行版有时具有太不同的东西,无法用任何简单的解决方案解决。因此,云-init 解决了足够多的问题以便有用,并且同时尽量不制造新问题。

重要提示

我们不会逐个处理特定模块,因为这将使本章变得太长,并可能使其成为一本独立的书。如果您打算使用 cloud-init,请参阅模块文档,因为它将提供您所需的所有最新信息。

如何使用 cloud-init 的 cloud-config 脚本的示例

首先,您需要下载云镜像并调整大小,以确保在安装所有文件后磁盘大小足够大,可以容纳您计划放入所创建的机器中的所有文件。在这些示例中,我们将使用两个镜像,一个用于 CentOS,另一个用于 Ubuntu 服务器。我们可以看到我们使用的 CentOS 镜像大小为 8 GB,我们将其扩大到 10 GB。请注意,磁盘上的实际大小不会达到 10 GB;我们只是允许镜像增长到这个大小。

我们将从互联网上获取 Ubuntu 镜像后,对其进行相同操作。Ubuntu 还会每天发布其分布的云版本,适用于所有支持的版本。主要区别在于 Ubuntu 创建的镜像在满时设计为 2.2 GB。我们从cloud.centos.org下载了一个镜像;现在让我们获取一些关于它的信息:

图 9.3 - Cloud-init 镜像大小

图 9.3 - Cloud-init 镜像大小

请注意磁盘上的实际大小不同- qemu-img给出的是 679 MB 和 2.2 GB,而实际磁盘使用量大约为 330 MB 和 680 MB:

图 9.4 - 通过 qemu-img 测得的镜像大小与实际虚拟镜像大小不同

图 9.4 - 通过 qemu-img 测得的镜像大小与实际虚拟镜像大小不同

现在我们可以对这些镜像进行一些日常管理任务-扩大它们,将它们移动到 KVM 的正确目录,将它们用作基础镜像,然后通过 cloud-init 进行自定义:

  1. 让我们把这些镜像变大,这样我们就可以为未来的容量需求(和实践)做好准备:图 9.5 - 通过 qemu-img 将 Ubuntu 和 CentOS 的最大镜像大小增加到 10 GB

图 9.5 - 通过 qemu-img 将 Ubuntu 和 CentOS 的最大镜像大小增加到 10 GB

扩大我们的镜像后,请注意磁盘上的大小几乎没有变化:

图 9.6 - 实际磁盘使用量只有轻微变化

图 9.6 - 实际磁盘使用量只有轻微变化

下一步是准备我们的环境以进行云镜像过程,以便我们可以启用 cloud-init 发挥其作用。

  1. 我们将使用的镜像将存储在/var/lib/libvirt/images中:图 9.7 - 将镜像移动到 KVM 默认系统目录

图 9.7 - 将镜像移动到 KVM 默认系统目录

我们将以最简单的方式创建我们的第一个云启用部署,只需重新分区磁盘并创建一个带有单个 SSH 密钥的用户。密钥属于主机的根目录,因此在 cloud-init 完成后我们可以直接登录到部署的机器上。

此外,我们将通过运行以下命令将我们的镜像用作基础镜像:

图 9.8 - 创建用于部署的镜像磁盘

图 9.8 - 创建用于部署的镜像磁盘

现在镜像已经准备好了。下一步是开始 cloud-init 配置。

  1. 首先,创建一个本地元数据文件,并在其中放入新的虚拟机名称。

  2. 文件将被命名为meta-data,我们将使用local-hostname来设置名称:

cloud. This user will not be able to log in using a password since we are not creating one, but we will enable login using SSH keys associated with the local root account, which we will create by using the ssh-keygen command. This is just an example SSH key, and SSH key that you're going to use might be different. So, as root, go through the following procedure:![Figure 9.10 – SSH keygen procedure done, SSH keys are present and accounted for    ](https://gitee.com/OpenDocCN/freelearn-linux-zh/raw/master/docs/ms-kvm-vrt/img/B14834_09_10.jpg)Figure 9.10 – SSH keygen procedure done, SSH keys are present and accounted forKeys are stored in the local `.ssh` directory, so we just need to copy them. When we are doing cloud deployments, we usually use this method of authentication, but cloud-init enables us to define any method of user authentication. It all depends on what we are trying to do and whether there are security policies in place that enforce one authentication method over another.In the cloud environments, we will rarely define users that are able to log in with a password, but for example, if we are deploying bare-metal machines for workstations, we will probably create users that use normal passwords. When we create a configuration file like this, it is standard practice to use hashes of passwords instead of literal cleartext passwords. The directive you are looking for is probably `passwd:` followed by a string containing the hash of a password. Next, we configured `sudo`. Our user needs to have root permissions since there are no other users defined for this machine. This means they need to be a member of the `sudo` group and have to have the right permissions defined in the `sudoers` file. Since this is a common setting, we only need to declare the variables, and cloud-init is going to put the settings in the right files. We will also define a user shell. In this file, we can also define all the other users' settings available on Linux, a feature that is intended to help deploy user computers. If you need any of those features, check the documentation available here: [`cloudinit.readthedocs.io/en/latest/topics/modules.html#users-and-groups`](https://cloudinit.readthedocs.io/en/latest/topics/modules.html#users-and-groups). All the extended user information fields are supported. The last thing we are doing is using the `runcmd` directive to define what will happen after the installation finishes, in the last stage. In order to permit the user to log in, we need to put them on the list of allowed users in the `sshd` and we need to restart the service. Now we are ready for our first deployment. 
  1. 我们的目录中有三个文件:一个使用云模板的基本文件的硬盘,一个包含仅对我们的部署至关重要的最小信息的meta-data文件,以及user-data,其中包含我们用户的定义。我们甚至没有尝试安装或复制任何东西;这个安装尽可能地简化,但在正常环境中,这是一个常规的起点,因为很多部署只是为了让我们的机器上线,然后通过其他工具完成其余的安装。让我们进入下一步。

我们需要一种方法来连接我们刚刚创建的文件和配置与虚拟机。通常有几种方法可以做到这一点。最简单的方法通常是生成一个包含文件的.iso文件。然后在创建机器时,我们只需将文件挂载为虚拟 CD-ROM。在启动时,cloud-init 将自动查找文件。

另一种方法是将文件托管在网络上的某个地方,并在需要时获取它们。也可以结合这两种策略。我们稍后会讨论这一点,但让我们先完成我们的部署。本地的.iso映像是我们将在这次部署中采用的方式。有一个名为genisoimage的工具(由同名的软件包提供),对此非常有用(以下命令是一行命令):

genisoimage -output deploy-1-cidata.iso -volid cidata -joliet -rock user-data meta-data

我们在这里做的是创建一个仿真的 CD-ROM 映像,它将遵循 ISO9660/Joliet 标准和 Rock Ridge 扩展。如果你不知道我们刚才说了什么,就忽略这一切,这样想一想 - 我们正在创建一个将保存我们的元数据和用户数据并呈现为 CD-ROM 的文件:

 图 9.11 - 创建 ISO 映像

图 9.11 - 创建 ISO 映像

最后,我们将得到类似于这样的东西:

图 9.12 - ISO 已创建,我们准备开始云初始化部署

图 9.12 - ISO 已创建,我们准备开始云初始化部署

请注意,图像是在部署后拍摄的,因此磁盘的大小可能会根据您的配置而大不相同。这就是所有需要的准备工作。剩下的就是启动我们的虚拟机。

现在,让我们开始我们的部署。

第一次部署

我们将使用命令行部署我们的虚拟机:

virt-install --connect qemu:///system --virt-type kvm --name deploy-1 --ram 2048 --vcpus=1 --os-type linux --os-variant generic --disk path=/var/lib/libvirt/images/deploy-1/centos1.qcow2,format=qcow2 --disk /var/lib/libvirt/images/deploy-1/deploy-1-cidata.iso,device=cdrom --import --network network=default --noautoconsole

尽管看起来可能很复杂,但如果你在阅读了本书之前的章节后来到这一部分,那么你应该已经见过了。我们正在使用 KVM,为我们的域(虚拟机)创建一个名称,我们将给它 1 个 CPU 和 2GB 的 RAM。我们还告诉 KVM 我们正在安装一个通用的 Linux 系统。我们已经创建了我们的硬盘,所以我们正在将它挂载为我们的主要驱动器,并且我们也正在挂载我们的.iso文件作为 CD-ROM。最后,我们将连接我们的虚拟机到默认网络:

图 9.13 - 部署和测试一个经过云初始化定制的虚拟机

部署可能需要一两分钟。一旦机器启动,它将获得 IP 地址,我们可以使用预定义的密钥通过 SSH 连接到它。唯一没有自动化的是自动接受新启动的机器的指纹。

现在,是时候看看当我们启动机器时发生了什么。Cloud-init 在/var/log生成了一个名为cloud-init.log的日志。文件会相当大,你会注意到的第一件事是日志设置为提供调试信息,所以几乎所有内容都会被记录:

图 9.14 - cloud-init.log 文件,用于检查 cloud-init 对操作系统的操作

图 9.14 - cloud-init.log 文件,用于检查 cloud-init 对操作系统的操作

另一件事是在表面以下完全自动发生了多少。由于这是 CentOS,cloud-init 必须实时处理 SELinux 安全上下文,因此很多信息只是这样。还有很多探测和测试正在进行。Cloud-init 必须确定运行环境是什么以及它正在哪种类型的云下运行。如果在启动过程中发生了任何与 cloud-init 有关的事情,这是第一个要查看的地方。

现在让我们通过使用第二个(Ubuntu)映像来部署我们的第二个虚拟机。这就是 cloud-init 真正发挥作用的地方 - 它可以与各种 Linux(和*BSD)发行版一起工作,无论它们是什么。我们现在可以测试一下。

第二次部署

下一个明显的步骤是创建另一个虚拟机,但为了证明一点,我们将使用 Ubuntu Server(Bionic)作为我们的镜像:

图 9.15-为另一个基于 cloud-init 的虚拟机部署准备我们的环境

图 9.15-为另一个基于 cloud-init 的虚拟机部署准备我们的环境

我们需要做什么?我们需要将meta-datauser-data都复制到新文件夹中。我们需要编辑元数据文件,因为其中包含主机名,我们希望我们的新机器有一个不同的主机名。至于user-data,它将与我们的第一个虚拟机完全相同。然后我们需要创建一个新的磁盘并调整其大小:

图 9.16-为部署目的扩展我们的虚拟机镜像

图 9.16-为部署目的扩展我们的虚拟机镜像

我们正在从下载的镜像创建虚拟机,并且在运行镜像时允许更多空间。最后一步是启动虚拟机:

图 9.17-使用 cloud-init 部署我们的第二个虚拟机

图 9.17-使用 cloud-init 部署我们的第二个虚拟机

命令行几乎完全相同,只是名称不同:

virt-install --connect qemu:///system --virt-type kvm --name deploy-2 --ram 2048 --vcpus=1 --os-type linux --os-variant generic --disk path=/var/lib/libvirt/images/deploy-2/bionic.qcow2,format=qcow2 --disk /var/lib/libvirt/images/deploy-2/deploy-2-cidata.iso,device=cdrom --import --network network=default –noautoconsole

现在让我们检查 IP 地址:

图 9.18-检查虚拟机 IP 地址

图 9.18-检查虚拟机 IP 地址

我们可以看到两台机器都已经启动并运行。现在进行大测试-我们能连接吗?让我们使用SSH命令尝试:

图 9.19-使用 SSH 验证我们是否可以连接到虚拟机

图 9.19-使用 SSH 验证我们是否可以连接到虚拟机

正如我们所看到的,连接到我们的虚拟机没有任何问题。

还有一件事是检查部署日志。请注意,由于我们正在运行 Ubuntu,因此没有提到配置 SELinux:

图 9.20-Ubuntu cloud-init 日志文件中没有提到 SELinux

图 9.20-Ubuntu cloud-init 日志文件中没有提到 SELinux

只是为了好玩,让我们以一个变通的方式进行另一个部署-让我们使用一个模块来部署一个软件包。

第三次部署

让我们部署另一个镜像。在这种情况下,我们创建另一个 CentOS 7,但这次我们是安装(而不是启动httpd,以展示这种类型的配置如何工作。再次,步骤足够简单:创建一个目录,复制元数据和用户数据文件,修改文件,创建.iso文件,创建磁盘,运行机器。

这次我们正在向配置添加另一部分(packages),以便我们可以告诉cloud-init 我们需要安装一个软件包(httpd):

图 9.21-用于第三次虚拟机部署的 Cloud-init 配置文件

图 9.21-用于第三次虚拟机部署的 Cloud-init 配置文件

由于所有步骤多多少少都是相同的,我们得到了相同的结果-成功:

图 9.22-重复第三个虚拟机的部署过程

图 9.22-重复第三个虚拟机的部署过程

我们应该等一会儿,以便虚拟机部署完成。之后,让我们登录并检查镜像是否正确部署。我们要求在部署过程中安装httpd。是吗?

图 9.23-检查 httpd 是否已安装但未启动

图 9.23-检查 httpd 是否已安装但未启动

我们可以看到一切都如预期完成。我们没有要求启动服务,因此它是按默认设置安装的,并且默认情况下是禁用和停止的。

安装后

cloud-init 的预期用途是配置机器并创建一个能够实现进一步配置或直接部署到生产环境的环境。但是,为了实现这一点,cloud-init 有很多选项,我们甚至还没有提到。由于我们有一个正在运行的实例,我们可以浏览一下在新启动的虚拟机中可以找到的最重要和最有用的东西。

首先要检查的是/run/cloud-init文件夹:

图 9.24 - /run/cloud-init 文件夹内容

图 9.24 - /run/cloud-init 文件夹内容

所有在运行时创建的内容都写在这里,并且对用户可用。我们的演示机器是在本地 KVM hypervisor 下运行的,因此 cloud-init 没有检测到云,并且因此无法提供有关云的更多数据,但我们可以看到一些有趣的细节。第一个是两个名为enablednetwork-config-ready的文件。它们都是空的,但非常重要。它们存在的事实表明 cloud-init 已启用,并且网络已配置并且正在工作。如果文件不存在,那么出了问题,我们需要返回并进行调试。有关调试的更多信息可以在cloudinit.readthedocs.io/en/latest/topics/debugging.html找到。

results.json文件保存了这个特定实例的元数据。status.json更集中于整个过程运行时发生了什么,并提供了关于可能的错误、配置系统不同部分所花费的时间以及是否完成的信息。

这两个文件都旨在帮助配置和编排,而且,虽然这些文件中的一些内容只对 cloud-init 重要,但检测和与不同的云环境进行交互的能力是其他编排工具可以使用的。文件只是其中的一部分。

这个方案的另一个重要部分是名为cloud-init的命令行实用程序。要从中获取信息,我们首先需要登录到我们创建的机器上。我们将展示由相同文件创建的机器之间的差异,并同时展示不同发行版之间的相似之处和不同之处。

在我们开始讨论之前,请注意,与所有 Linux 软件一样,cloud-init 有不同的版本。CentOS 7 镜像使用的是一个旧版本,0.7.9:

图 9.25 - CentOS 上的 cloud-init 版本 - 相当旧

图 9.25 - CentOS cloud-init 版本 - 相当旧

Ubuntu 带有一个更新的版本,19.3:

图 9.26 - Ubuntu 上的 cloud-init 版本 - 最新

图 9.26 - Ubuntu 上的 cloud-init 版本 - 最新

在你惊慌之前,情况并不像看起来那么糟。Cloud-init 在几年前决定切换了其版本系统,因此在 0.7.9 之后是 17.1。发生了许多变化,其中大部分直接与 cloud-init 命令和配置文件相关。这意味着部署将会成功,但我们部署后会有很多问题。可能最明显的区别是当我们运行cloud-init --help时。对于 Ubuntu 来说,它看起来是这样的:

图 2.27 - Ubuntu 上的 Cloud-init 功能

图 2.27 - Ubuntu 上的 Cloud-init 功能

实际上,CentOS 缺少了很多东西,其中一些完全缺失:

图 9.28 - CentOS 上的 Cloud-init 功能

图 9.28 - CentOS 上的 Cloud-init 功能

由于我们的示例共有三个运行实例 - 一个 Ubuntu 和两个 CentOS 虚拟机 - 让我们尝试手动升级到 CentOS 上可用的最新稳定版本的 cloud-init。我们可以使用常规的yum update命令来实现,结果如下:

图 9.29 - 经过一段时间的 yum update 后,cloud-init 功能的最新列表

图 9.29 - 经过一段 yum 更新后,cloud-init 功能的最新列表

正如我们所看到的,这将使事情变得更加容易。

我们不会过多地详细介绍 cloud-init CLI 工具,因为像这样的书籍中有太多的信息可用,而且正如我们所看到的,新功能正在迅速添加。你可以通过浏览cloudinit.readthedocs.io/en/latest/topics/cli.html自由地查看额外的选项。事实上,它们添加得非常快,以至于有一个devel选项,其中包含了正在积极开发中的新功能。一旦完成,它们就会成为独立的命令。

有两个命令你需要了解,它们都提供了关于引导过程和引导系统状态的大量信息。第一个是cloud-init analyze。它有两个非常有用的子命令:blameshow

恰如其名的blame实际上是一个工具,返回了在引导过程中 cloud-init 执行不同过程时花费了多少时间。例如,我们可以看到在 Ubuntu 上配置grub和处理文件系统是最慢的操作:

图 9.30 - 检查 cloud-init 过程的时间消耗

图 9.30 - 检查 cloud-init 过程的时间消耗

我们部署的第三个虚拟机使用了 CentOS 镜像,并添加了httpd。从某种程度上来说,这是 cloud-init 过程中发生的最慢的事情:

图 9.31 - 检查时间消耗 - cloud-init 部署必要的 httpd 包花费了相当长的时间使用 cloud-init 部署必要的 httpd 包

图 9.31 - 检查时间消耗 - cloud-init 部署必要的 httpd 包花费了相当长的时间

这样的工具使得优化部署变得更加容易。在我们的特定情况下,几乎没有意义,因为我们部署了几乎没有更改默认配置的简单机器,但能够理解部署为什么慢是一件有用的,如果不是必不可少的事情。

另一个有用的功能是能够看到实际引导虚拟机所花费的时间:

图 9.32 - 检查引导时间

图 9.32 - 检查引导时间

我们将以一个查询结束这一部分 - cloud-init query使你能够从服务中请求信息,并以可用的结构化格式获取它,然后进行解析:

图 9.33 - 查询 cloud-init 信息

图 9.33 - 查询 cloud-init 信息

使用了几个小时后,cloud-init 成为系统管理员不可或缺的工具之一。当然,它的本质意味着它更适合于我们这些必须在云环境中工作的人,因为它最擅长的是从脚本快速无痛地部署机器。但即使你不是在使用云技术,快速创建实例进行测试,然后无痛地删除它们的能力,是每个管理员都需要的。

总结

在本章中,我们介绍了 cloud-init,它的架构以及在更大的部署场景中的好处,其中配置一致性和灵活性至关重要。再加上我们不再手动完成所有事情的范式变化 - 我们有一个工具来为我们完成 - 这是我们部署流程的一个很好的补充。确保你尝试使用它,因为它将使你的生活变得更加轻松,同时为你使用云虚拟机做好准备,而在那里,cloud-init 被广泛使用。

在下一章中,我们将学习如何将这种用法模型扩展到 Windows 虚拟机,使用 cloudbase-init。

问题

  1. 使用 CentOS 7 和 Ubuntu 基础 cloud-init 镜像重新创建我们的设置。

  2. 使用相同的基础镜像创建一个 Ubuntu 和两个 CentOS 实例。

  3. 使用 Ubuntu 作为基础镜像添加第四个虚拟机。

  4. 尝试使用其他发行版作为基础镜像,而不更改任何配置文件。试试 FreeBSD。

  5. 不要使用 SSH 密钥,使用预定义密码。这样更安全还是更不安全?

  6. 创建一个脚本,使用 cloud-init 和一个基础镜像创建 10 个相同的机器实例。

  7. 您能找到任何理由,为什么使用分发本地的安装方式而不是使用 cloud-init 会更有利吗?

进一步阅读

请参考以下链接,了解本章涵盖的更多信息:

第十章:自动化 Windows 客户端部署和自定义

现在,我们已经介绍了在 KVM 中部署基于 Linux 的虚拟机VMs)的不同方法,是时候将我们的重点转移到 Microsoft Windows 了。具体来说,我们将专注于在 KVM 上运行的 Windows Server 2019 机器,并涵盖部署和自定义 Windows Server 2019 虚拟机的先决条件和不同场景。本书不是基于虚拟桌面基础设施VDI)和桌面操作系统的想法,这需要与虚拟化服务器操作系统完全不同的场景、方法和技术实施。

在本章中,我们将涵盖以下主题:

  • 在 KVM 上创建 Windows 虚拟机的先决条件

  • 使用virt-install实用程序创建 Windows 虚拟机

  • 使用cloudbase-init自定义 Windows 虚拟机

  • cloudbase-init自定义示例

  • 解决常见的cloudbase-init自定义问题

在 KVM 上创建 Windows 虚拟机的先决条件

在 KVM 虚拟化上启动客户操作系统的安装时,我们总是有相同的起点。我们需要以下之一:

  • 具有操作系统安装的 ISO 文件

  • 具有虚拟机模板的镜像

  • 一个现有的虚拟机进行克隆和重新配置

让我们从头开始。在本章中,我们将创建一个 Windows Server 2019 虚拟机。版本选择是为了与市场上最新发布的微软服务器操作系统保持联系。我们的目标是部署一个 Windows Server 2019 虚拟机模板,以便以后用于更多部署和cloudbase-init,而此安装过程的选择工具将是virt-install。如果您需要安装旧版本(2016 或 2012),您需要知道两个事实:

  • 它们在 CentOS 8 上得到了支持。

  • 安装过程与我们的 Windows Server 2019 虚拟机将是相同的。

如果您想使用虚拟机管理器部署 Windows Server 2019,请确保正确配置虚拟机。这包括为客户操作系统安装选择正确的 ISO 文件,并连接另一个虚拟 CD-ROM 以安装virtio-win驱动程序,以便您可以在安装过程中安装它们。确保您的虚拟机在本地 KVM 主机上有足够的磁盘空间(建议为 60 GB+),并且有足够的性能来运行。从两个虚拟 CPU 和 4 GB 内存开始,因为这很容易以后更改。

我们场景的下一步是创建一个 Windows 虚拟机,我们将在本章中使用cloudbase-init进行自定义。在真实的生产环境中,我们需要尽可能多地在其中进行配置-驱动程序安装、Windows 更新、常用应用程序等。所以,让我们首先做这个。

使用virt-install实用程序创建 Windows 虚拟机

我们需要做的第一件事是确保我们已经准备好安装virtio-win驱动程序-如果没有安装,虚拟机将无法正常工作。因此,让我们首先安装libguestfs软件包和virtio-win软件包,以防您的服务器上尚未安装它们:

yum –y install virtio-win libguestfs*

然后,是时候开始部署我们的虚拟机了。以下是我们的设置:

  • Windows Server 2019 ISO 位于/iso/windows-server-2019.iso

  • virtio-win ISO 文件位于默认系统文件夹/usr/share/virtio-win/virtio-win.iso

  • 我们将创建一个位于默认系统文件夹/var/lib/libvirt/images的 60 GB 虚拟磁盘。

现在,让我们开始安装过程:

virt-install --name WS2019 --memory=4096 --vcpus 2 --cpu host --video qxl --features=hyperv_relaxed=on,hyperv_spinlocks=on,hyperv_vapic=on --clock hypervclock_present=yes --disk /var/lib/libvirt/images/WS2019.qcow2,format=qcow2,bus=virtio,cache=none,size=60 --cdrom /iso/windows-server-2019.iso --disk /usr/share/virtio-win/virtio-win.iso,device=cdrom --vnc --os-type=windows --os-variant=win2k19 --accelerate --noapic 

安装过程开始时,我们必须点击virtio-win驱动程序。确保取消选中隐藏与此计算机硬件不兼容的驱动程序复选框。然后,逐个添加以下驱动程序,从指定目录中选择并用鼠标选择它们:

  • AMD64\2k19Red Hat VirtIO SCSI 控制器

  • Balloon\2k19\amd64VirtIO 气球驱动程序

  • NetKVM\2k19\AMD64Red Hat VirtIO 以太网适配器

  • qemufwcfg\2k19\amd64QEMU FWCfg 设备

  • qemupciserial\2k19\amd64QEMU 串行 PCI 卡

  • vioinput\2k19\amd64VirtIO 输入驱动程序VirtIO 输入驱动程序助手;选择它们两个。

  • viorng\2k19\amd64VirtIO RNG 设备

  • vioscsi\2k19\amd64Red Hat VirtIO SCSI 直通控制器

  • vioserial\2k19\amd64VirtIO 串行驱动程序

  • viostor\2k19\amd64Red Hat VirtIO SCSI 控制器

之后,点击下一步,等待安装过程完成。

您可能会问自己:为什么我们在安装过程的早期就这样微观管理,而不是稍后再做呢?答案有两个方面-如果我们稍后再做,我们会遇到以下问题:

  • 有可能-至少对于某些操作系统-在安装开始之前我们不会加载所有必要的驱动程序,这可能意味着安装会崩溃。

  • 我们会在设备管理器中看到大量的黄色感叹号,这通常会让人感到恼火。

部署后,我们的设备管理器很满意,安装成功了:

图 10.1-操作系统和所有驱动程序从一开始就安装

图 10.1-操作系统和所有驱动程序从一开始就安装

安装后唯一强烈建议的事情是,在启动 VM 后从virtio-win.iso安装客户代理。您会在虚拟 CD-ROM 中的guest-agent目录中找到一个.exe文件,只需点击下一步按钮,直到安装完成。

现在我们的 VM 已经准备好,我们需要开始考虑定制。特别是大规模的定制,这是云中 VM 部署的正常使用模式。这就是为什么我们需要使用cloudbase-init,这是我们的下一步。

使用 cloudbase-init 自定义 Windows VM

如果您有机会阅读第九章使用 cloud-init 自定义虚拟机,我们讨论了一个工具叫做cloud-init。我们使用它来进行客户操作系统定制,特别是针对 Linux 机器。cloud-init在基于 Linux 的环境中被广泛使用,特别是在基于 Linux 的云中,用于执行云 VM 的初始化和配置。

cloudbase-init的理念是一样的,但它针对的是 Windows 客户操作系统。它的基本服务在我们启动 Windows 客户操作系统实例时启动,然后阅读配置信息并进行配置/初始化。我们稍后将展示一些cloudbase-init操作的例子。

cloudbase-init能做什么?功能列表相当长,因为cloudbase-init的核心是模块化的,所以它提供了许多插件和解释器,可以用于扩展其功能:

  • 它可以执行自定义命令和脚本,最常见的是用 PowerShell 编写,尽管也支持常规的 CMD 脚本。

  • 它可以与 PowerShell 远程和Windows 远程管理WinRM)服务一起工作。

  • 它可以管理和配置磁盘,例如进行卷扩展。

  • 它可以进行基本的管理,包括以下内容:

a) 创建用户和密码

b) 设置主机名

c) 配置静态网络

d) 配置 MTU 大小

e) 分配许可证

f) 使用公钥

g) 同步时钟

我们之前提到过,我们的 Windows Server 2019 虚拟机将用于cloudbase-init定制,所以这是我们接下来要讨论的主题。让我们为cloudbase-init准备我们的虚拟机。我们将通过下载cloudbase-init安装程序并安装来实现这一点。我们可以通过将我们的互联网浏览器指向cloudbase-init.readthedocs.io/en/latest/intro.html#download来找到cloudbase-init安装程序。安装非常简单,可以以常规 GUI 方式或静默方式工作。如果您习惯使用 Windows Server Core 或更喜欢静默安装,可以使用 MSI 安装程序进行静默安装,方法是使用以下命令:

msiexec /i CloudbaseInitSetup.msi /qn /l*v log.txt

确保您检查cloudbase-init文档,以获取更多配置选项,因为安装程序支持额外的运行时选项。它位于cloudbase-init.readthedocs.io/en/latest/

让我们使用 GUI 安装程序,因为它更简单易用,特别是对于第一次使用的用户。首先,安装程序将要求您同意许可协议和安装位置 – 就是通常的东西。然后,我们将得到以下选项屏幕:

图 10.2 – 基本配置屏幕

图 10.2 – 基本配置屏幕

它要求我们允许使用特定未来用户创建cloudbase-init配置文件(cloudbase-init-unattend.confcloudbase-init.conf)。这个用户将是本地Administrators组的成员,并且将在我们开始使用新镜像时用于登录。这将反映在我们的两个配置文件中,因此如果我们在这里选择Admin,那么将创建该用户。它还要求我们是否希望cloudbase-init服务作为LocalSystem服务运行,我们选择这样做是为了使整个过程更容易。原因非常简单 – 这是我们可以给予cloudbase-init服务的最高权限级别,以便它可以执行其操作。翻译:cloudbase-init服务将作为LocalSystem服务账户运行,该账户对所有本地系统资源具有无限访问权限。

最后一个配置屏幕将要求我们运行 sysprep。通常,我们不会检查cloudbase-init定制文件并在此之后运行 sysprep。因此,请保持以下窗口打开:

图 10.3 – 完成 cloudbase-init 安装向导

图 10.3 – 完成 cloudbase-init 安装向导

现在cloudbase-init服务已安装和配置好,让我们创建一个定制文件,通过使用cloudbase-init来配置这个虚拟机。同样,请确保保持此配置屏幕打开(完成设置向导),以便我们在完成创建cloudbase-init配置时可以轻松开始整个过程。

cloudbase-init 定制示例

安装过程完成后,在我们的安装位置将创建一个包含一组文件的目录。例如,在我们的虚拟机中,创建了一个名为c:\Program Files\Cloudbase Solutions\Cloudbase-init\的目录,它具有以下一组子目录:

  • bin:一些二进制文件安装的位置,例如elevatebsdtarmcopymdir等。

  • conf:我们将要处理的三个主要配置文件的位置,稍后会讨论。

  • LocalScripts:PowerShell 和类似脚本的默认位置,我们希望在启动后运行。

  • Log:默认情况下,我们将存储cloudbase-init日志文件的位置,以便我们可以调试任何问题。

  • Python:本地安装 Python 的位置,以便我们也可以使用 Python 进行脚本编写。

让我们专注于包含我们配置文件的conf目录:

  • cloudbase-init.conf

  • cloudbase-init-unattend.conf

  • unattend.xml

cloudbase-init的工作方式相当简单 - 它在 Windows sysprep 阶段使用unattend.xml文件来执行cloudbase-init,并使用cloudbase-init-unattend.conf配置文件。默认的cloudbase-init-unattend.conf配置文件非常易读,我们可以使用cloudbase-init项目提供的示例,逐步解释默认配置文件:

[DEFAULT]
# Name of the user that will get created, group for that user
username=Admin
groups=Administrators
firstlogonbehaviour=no
inject_user_password=true  # Use password from the metadata (not random).

配置文件的下一部分是关于设备 - 具体来说,是要检查哪些设备可能有配置驱动器(元数据):

config_drive_raw_hhd=true
config_drive_cdrom=true
# Path to tar implementation from Ubuntu.
bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe
mtools_path= C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\

我们还需要配置一些日志记录的设置:

# Logging level
verbose=true
debug=true
# Where to store logs
logdir=C:\Program Files (x86)\Cloudbase Solutions\Cloudbase-Init\log\
logfile=cloudbase-init-unattend.log
default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN
logging_serial_port_settings=

配置文件的下一部分是关于网络的,因此我们将在我们的示例中使用 DHCP 获取所有网络设置:

# Use DHCP to get all network and NTP settings
mtu_use_dhcp_config=true
ntp_use_dhcp_config=true

我们需要配置脚本所在的位置,这些脚本可以作为cloudbase-init过程的一部分使用:

# Location of scripts to be started during the process
local_scripts_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\LocalScripts\

配置文件的最后一部分是关于要加载的服务和插件,以及一些全局设置,例如是否允许cloudbase-init服务重新启动系统,以及我们将如何处理cloudbase-init关闭过程(false=优雅服务关闭):

# Services for loading
metadata_services=cloudbaseinit.metadata.services.configdrive.ConfigDriveService, cloudbaseinit.metadata.services.httpservice.HttpService,
cloudbaseinit.metadata.services.ec2service.EC2Service,
cloudbaseinit.metadata.services.maasservice.MaaSHttpService
# Plugins to load
plugins=cloudbaseinit.plugins.common.mtu.MTUPlugin,
        cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin
# Miscellaneous.
allow_reboot=false    # allow the service to reboot the system
stop_service_on_exit=false

让我们从一开始就澄清一些事情。默认配置文件已经包含了一些已弃用的设置,您很快就会发现。特别是像verboselogdirlogfile这样的设置在此版本中已经被弃用,您可以从以下截图中看到,cloudbase-init正在抱怨这些选项:

图 10.4 - cloudbase-init 抱怨其自己的默认配置文件选项

图 10.4 - cloudbase-init 抱怨其自己的默认配置文件选项

如果我们想要使用默认配置文件启动cloudbase-init进行 sysprep,实际上我们将得到一个非常好配置的虚拟机 - 它将被 sysprep,它将重置管理员密码并要求我们在第一次登录时更改密码,并删除现有的管理员用户及其目录。因此,在执行此操作之前,我们需要确保将我们的管理员用户设置和数据(文档,安装程序,下载等)保存在安全的地方。此外,默认配置文件不会默认重新启动虚拟机,这可能会让您感到困惑。我们需要手动重新启动虚拟机,以便整个过程可以开始。

cloud-initcloudbase-init一起工作的最简单方法是写下一个需要在虚拟机初始化过程中完成的场景。因此,我们将这样做 - 选择我们想要配置的一大堆设置,并相应地创建一个自定义文件。以下是我们的设置:

  • 我们希望我们的虚拟机在cloudbase-init过程后要求我们更改密码。

  • 我们希望我们的虚拟机从 DHCP 获取所有的网络设置(IP 地址,子网掩码,网关,DNS 服务器和 NTP)。

  • 我们希望对虚拟机进行 sysprep,以使其对每个场景和策略都是唯一的。

因此,让我们创建一个cloudbase-init-unattend.conf配置文件来为我们执行此操作。配置文件的第一部分取自默认配置文件:

[DEFAULT]
username=Admin
groups=Administrators
inject_user_password=true
config_drive_raw_hhd=true
config_drive_cdrom=true
config_drive_vfat=true
bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe
mtools_path= C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\
debug=true
default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN
logging_serial_port_settings=
mtu_use_dhcp_config=true
ntp_use_dhcp_config=true

我们决定使用 PowerShell 进行所有脚本编写,因此我们为我们的 PowerShell 脚本创建了一个单独的目录:

local_scripts_path=C:\PS1

文件的其余部分也只是从默认配置文件中复制过来的:

metadata_services=cloudbaseinit.metadata.services.base.EmptyMetadataService
plugins=cloudbaseinit.plugins.common.mtu.MTUPlugin,
        cloudbaseinit.plugins.common.sethostname.SetHostNamePlugin, cloudbaseinit.plugins.common.localscripts.LocalScriptsPlugin,cloudbaseinit.plugins.common.userdata.UserDataPlugin
allow_reboot=false    
stop_service_on_exit=false

至于cloudbase-init.conf文件,我们唯一做的更改是选择正确的本地脚本路径(稍后将提到的原因),因为我们将在下一个示例中使用此路径:

[DEFAULT]
username=Admin
groups=Administrators
inject_user_password=true
config_drive_raw_hhd=true
config_drive_cdrom=true
config_drive_vfat=true

此外,我们默认的配置文件包含了tarmtools和调试的路径:

bsdtar_path=C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\bsdtar.exe
mtools_path= C:\Program Files\Cloudbase Solutions\Cloudbase-Init\bin\
debug=true

配置文件的这一部分也是从默认配置文件中获取的,我们只更改了local_scripts_path,以便将其设置为我们用于填充 PowerShell 脚本的目录:

first_logon_behaviour=no
default_log_levels=comtypes=INFO,suds=INFO,iso8601=WARN
logging_serial_port_settings=
mtu_use_dhcp_config=true
ntp_use_dhcp_config=true
local_scripts_path=C:\PS1

然后,我们可以返回到cloudbase-init安装屏幕,选中 sysprep 选项,然后单击完成。启动 sysprep 过程并完成后,这就是最终结果:

图 10.5 - 当我们按“登录”时,我们将被要求更改管理员密码

图 10.5 - 当我们按“登录”时,我们将被要求更改管理员密码

现在,让我们再进一步,稍微复杂一些。假设您想执行相同的过程,但还要添加一些额外的 PowerShell 代码来进行一些额外的配置。考虑以下示例:

  • 它应该创建另外两个名为packt1packt2的本地用户,预定义密码设置为Pa$$w0rd

  • 它应该创建一个名为students的新本地组,并将packt1packt2添加为成员。

  • 它应该将主机名设置为Server1

使我们能够执行此操作的 PowerShell 代码应具有以下内容:

Set-ExecutionPolicy -ExecutionPolicy Unrestricted -Force
$password = "Pa$$w0rd" | ConvertTo-SecureString -AsPlainText -Force
New-LocalUser -name "packt1" -Password $password
New-LocalUser -name "packt2" -Password $password
New-LocalGroup -name "Students"
Add-LocalGroupMember -group "Students" -Member "packt1","packt2"
Rename-Computer -NewName "Server1" -Restart

看一下脚本本身,这就是它的作用:

  • 将 PowerShell 执行策略设置为无限制,以便我们的主机不会停止我们的脚本执行,这是默认情况下会发生的。

  • 从纯文本字符串(Pa$$w0rd)创建一个密码变量,将其转换为安全字符串,我们可以将其与New-LocalUser PowerShell 命令一起使用来创建本地用户。

  • New-LocalUser是一个 PowerShell 命令,用于创建本地用户。强制参数包括用户名和密码,这就是为什么我们创建了一个安全字符串。

  • New-LocalGroup是一个 PowerShell 命令,用于创建本地组。

  • Add-LocalGroupMember是一个 PowerShell 命令,允许我们创建一个新的本地组并向其中添加成员。

  • Rename-Computer是一个 PowerShell 命令,用于更改 Windows 计算机的主机名。

我们还需要以某种方式从cloudbase-init中调用此代码,因此我们需要将此代码添加为脚本。最常见的是,在cloudbase-init安装文件夹中使用名为LocalScripts的目录。让我们将此脚本命名为userdata.ps1,将先前提到的内容保存到文件夹中,如.conf文件中定义的那样(c:\PS1),并在文件顶部添加一个cloudbase-init参数:

# ps1
$password = "Pa$$w0rd" | ConvertTo-SecureString -AsPlainText -Force
New-LocalUser -name "packt1" -Password $password
New-LocalUser -name "packt2" -Password $password
New-LocalGroup -name "Students"
Add-LocalGroupMember -group "Students" -Member "packt1","packt2"
Rename-Computer -NewName "Server1" –Restart

再次启动cloudbase-init过程,可以通过启动cloudbase-init安装向导并按照之前的示例进行操作来实现,以下是用户方面的最终结果:

图 10.6 - 创建了 packt1 和 packt2 用户,并将其添加到我们的 PowerShell 脚本创建的组中

图 10.6 - 创建了 packt1 和 packt2 用户,并将其添加到我们的 PowerShell 脚本创建的组中

我们可以清楚地看到创建了packt1packt2用户,以及一个名为Students的组。然后,我们可以看到Students组有两个成员 - packt1packt2。此外,在设置服务器名称方面,我们有以下内容:

图 10.7 - Slika 1。通过 PowerShell 脚本更改服务器名称也有效

图 10.7 - Slika 1。通过 PowerShell 脚本更改服务器名称也有效

使用cloudbase-init确实并不简单,需要在时间和摆弄方面进行一些投资。但之后,它将使我们的工作变得更加容易 - 不再被迫一遍又一遍地执行这些平凡的任务应该是一个足够的奖励,这就是为什么我们需要稍微谈谈故障排除。我们相信,随着您增加cloudbase-init的使用量,您一定会遇到这些问题。

排除常见的 cloudbase-init 自定义问题

坦率地说,您可以自由地说cloudbase-init文档并不是那么好。找到如何执行 PowerShell 或 Python 代码的示例实际上是相当困难的,而官方页面在这方面并没有提供任何帮助。因此,让我们讨论一些在使用cloudbase-init时经常发生的常见错误。

尽管这似乎有些违反直觉,但我们在使用最新的开发版本而不是最新的稳定版本时取得了更大的成功。我们不太确定问题出在哪里,但最新的开发版本(在撰写本文时,这是版本 0.9.12.dev125)对我们来说一开始就可以使用。使用版本 0.9.11 时,我们在启动 PowerShell 脚本时遇到了很大的问题。

除了这些问题,当您开始了解cloudbase-init时,还会遇到其他问题。第一个是重启循环。这个问题非常常见,几乎总是因为两个原因:

  • 配置文件中的错误 - 模块或选项的名称错误输入,或类似的错误

  • cloudbase-init过程中调用的一些外部文件(位置或语法)出现错误

在配置文件中犯错误是经常发生的事情,这会使cloudbase-init陷入一个奇怪的状态,最终会变成这样:

图 10.8 - 配置错误

图 10.8 - 配置错误

我们已经多次看到这种情况。真正的问题在于有时需要等待数小时,有时需要循环多次重启,但这不仅仅是一个常规的重启循环。似乎cloudbase-init正在做某些事情 - CMD 已经启动,屏幕上没有错误,但它一直在做某些事情,然后以这种方式完成。

您可能遇到的其他问题更加挑剔 - 例如,当cloudbase-init在 sysprep/cloudbase-init过程中无法重置密码时。如果您手动更改了cloudbase-init服务使用的帐户密码(因此,使用LocalSystem是一个更好的主意),就会发生这种情况。这将导致整个cloudbase-init过程失败,其中的一部分可能是无法重置密码。

还有一个更加隐晦的原因可能会导致这种情况发生 - 有时我们会使用services.msc控制台手动管理系统服务,并且会有意地禁用我们不立即识别的服务。如果将cloudbase-init服务设置为禁用,它将在其过程中失败。这些服务需要具有自动启动优先级,并且不应手动重新配置为禁用。

重置密码失败也可能是因为某些安全策略 - 例如,如果密码不够复杂。这就是为什么我们在 PowerShell 脚本中使用了更复杂的密码,因为我们大多数系统工程师很早就学到了这个教训。

此外,有时公司会制定不同的安全策略,这可能导致某些管理应用程序(例如软件清单)停止cloudbase-init服务或完全卸载它。

我们可能遇到的最令人沮丧的错误是cloudbase-init进程无法从指定文件夹启动脚本。在花费数小时完善您的 Python、bash、cmd 或 PowerShell 脚本后,需要将其添加到定制过程中,看到这种情况发生总是令人发狂。为了能够使用这些脚本,我们需要使用一个能够调用外部脚本并执行它的特定插件。这就是为什么我们通常使用UserDataPlugin - 无论是出于执行还是调试的原因 - 因为它可以执行所有这些脚本类型并给我们一个错误值,然后我们可以用于调试目的。

最后一件事 - 确保不要直接将 PowerShell 代码插入到conf文件夹中的cloudbase-init配置文件中。你只会得到一个重启循环作为回报,所以要小心。

总结

在本章中,我们讨论了 Windows VM 的定制化,这是与 Linux VM 定制化同样重要的话题。甚至更重要,考虑到市场份额和许多人在云环境中使用 Windows。

现在我们已经涵盖了与 VM、模板化和定制化相关的所有基础知识,是时候介绍一种与cloud-initcloudbase-init互补的附加定制化方法了。因此,下一章将介绍基于 Ansible 的方法。

问题

  1. 我们需要安装哪些驱动程序到 Windows 客户操作系统中,以便在 KVM 虚拟化程序上创建 Windows 模板?

  2. 我们需要安装哪个代理程序到 Windows 客户操作系统中,以便更好地查看 VM 的性能数据?

  3. sysprep 是什么?

  4. cloudbase-init用于什么?

  5. cloudbase-init的常规用例是什么?

进一步阅读

请参考以下链接获取更多信息:

第十一章:Ansible 和编排自动化

Ansible 已经成为当今开源社区的事实标准,因为它提供了很多功能,同时对您和您的基础设施要求很少。在基于内核的虚拟机KVM)中使用 Ansible 也是很有意义的,特别是当您考虑到更大的环境。无论您只是想要简单地配置 KVM 主机(安装 libvirt 和相关软件),还是想要统一配置主机上的 KVM 网络 - Ansible 对于这两者都非常有价值。例如,在本章中,我们将使用 Ansible 来部署托管在 KVM 虚拟机内的虚拟机和多层应用程序,这在更大的环境中是非常常见的用例。然后,我们将转向更加严谨的主题,结合 Ansible 和 cloud-init,因为它们在应用时间轴和完成方式上有所不同。Cloud-init 是一种理想的自动化初始虚拟机配置的方式(主机名、网络和 SSH 密钥)。然后,我们通常转向 Ansible,以便在初始配置后执行额外的编排 - 添加软件包,对系统进行更大的更改等等。让我们看看如何在 KVM 中使用 Ansible 和 cloud-init。

在这一章中,我们将涵盖以下主题:

  • 理解 Ansible

  • 使用kvm_libvirt模块来配置虚拟机

  • 使用 Ansible 和 cloud-init 进行自动化和编排

  • 在 KVM 虚拟机上编排多层应用程序部署

  • 通过示例学习,包括如何在 KVM 中使用 Ansible 的各种示例

让我们开始吧!

理解 Ansible

一个称职管理员的主要职责之一是尽可能自动化自己的工作。有一句话说,你必须手动做一次所有的事情。如果你必须再做一次,你可能会对此感到恼火,第三次你必须做的时候,你会自动化这个过程。当我们谈论自动化时,它可能意味着很多不同的东西。

让我们通过一个例子来解释这个问题和解决方案,因为这是描述问题和解决方案最方便的方式。假设你在一家公司工作,需要部署 50 台 Web 服务器来托管一个 Web 应用程序,具有标准配置。标准配置包括需要安装的软件包、需要配置的服务和网络设置、需要配置的防火墙规则,以及需要从网络共享复制到虚拟机内部的本地磁盘上的文件,以便我们可以通过 Web 服务器提供这些文件。你将如何实现这一点?

有三种基本的方法:

  • 手动做所有事情。这将花费很多时间,也会有很多机会出错,因为毕竟我们是人类,会犯错误(故意的)。

  • 尝试通过部署 50 台虚拟机来自动化流程,然后将整个配置方面放入脚本中,这可以成为自动安装过程的一部分(例如,kickstart)。

  • 尝试通过部署一个包含所有组件的单个虚拟机模板来自动化流程。这意味着我们只需要从虚拟机模板部署这 50 台虚拟机,并进行一些定制,以确保我们的虚拟机准备好使用。

有不同类型的自动化可用。纯脚本编写是其中之一,它涉及将需要多次运行的所有内容创建成脚本。做了多年工作的管理员通常都有一堆有用的脚本。优秀的管理员通常至少懂一种编程语言,即使他们不愿承认,因为作为管理员意味着在别人弄坏东西后需要修复,有时需要相当多的编程。

因此,如果你考虑通过脚本进行自动化,我们完全同意这是可行的。但问题在于,你将花费多少时间来覆盖脚本的每一个方面,以确保脚本始终正常工作。此外,如果脚本出现问题,你将不得不进行大量的手动劳动来修复它,而没有任何真正的方法在之前不成功的配置之上进行额外的修正。

这就是基于过程的工具如 Ansible 派上用场的地方。Ansible 生成模块,将其推送到端点(在我们的示例中是虚拟机),将我们的对象带到期望的状态。如果你来自 Microsoft PowerShell 世界,是的,Ansible 和 PowerShell 期望状态配置DSC)基本上是在尝试做同样的事情。它们只是以不同的方式去做。所以,让我们讨论这些不同的自动化过程,看看 Ansible 在其中的定位。

自动化方法

总的来说,所有这些都适用于管理系统及其部件,安装应用程序,并且通常照顾已安装系统内部的事务。这可以被认为是一种的管理方法,因为它通常处理的是服务,而不是服务器。与此同时,这种自动化明显地专注于单个服务器或少量服务器,因为它的扩展性不强。如果我们需要处理多个服务器,使用常规脚本会带来新的问题。因为脚本更难以扩展到多个服务器上(而在 Ansible 中很容易),我们需要考虑很多额外的变量(不同的 SSH 密钥、主机名和 IP 地址)。

如果一个脚本不够,那么我们就必须使用多个脚本,这就产生了一个新问题,即脚本管理。想想看 - 当我们需要在脚本中做一些更改时会发生什么?我们如何确保所有服务器上的所有实例都使用相同的版本,特别是如果服务器 IP 地址不是顺序的呢?因此,总之,虽然旧的和经过测试,这种自动化方式有严重的缺点。

在 DevOps 社区中,还有另一种自动化方式正在受到关注 - 大写字母 A 的自动化。这是一种在不同机器上自动化系统操作的方式 - 甚至在不同操作系统上也是如此。有一些自动化系统可以实现这一点,它们基本上可以分为两组:使用代理的系统和无代理系统。

使用代理的系统

使用代理的系统更常见,因为它们比无代理系统有一些优势。首要的优势是它们能够跟踪不仅需要进行的更改,还能跟踪用户对系统所做的更改。这种更改跟踪意味着我们可以跟踪系统上发生的事情并采取适当的行动。

几乎所有的工作方式都是一样的。一个小应用程序 - 称为代理 - 安装在我们需要监视的系统上。应用程序安装完成后,它连接或允许来自中央服务器的连接,中央服务器处理自动化的所有事务。由于你正在阅读这篇文章,你可能对这样的系统很熟悉。这样的系统有很多,很有可能你已经遇到过其中的一个。为了理解这个原理,看一下下面的图表:

图 11.1 - 管理平台需要代理连接到需要编排和自动化的对象

图 11.1 - 管理平台需要代理连接到需要编排和自动化的对象

在这些系统中,代理有双重目的。它们在这里运行需要在本地运行的任何东西,并不断监视系统的变化。这种变化跟踪能力可以通过不同的方式实现,但结果是相似的 - 中央系统将知道发生了什么变化以及以何种方式发生了变化。变化跟踪在部署中是一件重要的事情,因为它能够实时进行合规性检查,并防止由未经授权的变化引起的许多问题。

无代理系统

无代理系统的行为不同。在需要管理的系统上没有安装任何东西;相反,中央服务器(或服务器)使用某种命令和控制通道执行所有操作。在 Windows 上,这可能是PowerShellWinRM或类似的东西,而在 Linux 上,通常是SSH或其他远程执行框架。中央服务器创建一个任务,然后通过远程通道执行,通常以脚本的形式在目标系统上复制并启动。这就是这个原则的样子:

图 11.2 - 管理平台不需要代理连接需要编排和自动化的对象

图 11.2 - 管理平台不需要代理连接需要编排和自动化的对象

无论其类型如何,这些系统通常被称为自动化或配置管理系统,尽管这两者是两个事实上的标准,但在现实中它们被不加区分地使用。在撰写本文时,最受欢迎的两个是 Puppet 和 Ansible,尽管还有其他的(Chef、SaltStack 等)。

在本章中,我们将介绍 Ansible,因为它易于学习,无代理,并且在互联网上有大量用户。

Ansible 介绍

Ansible 是一个 IT 自动化引擎 - 有些人称其为自动化框架 - 它使管理员能够自动化配置、配置管理和许多系统管理员可能需要完成的日常任务。

关于 Ansible 最简单(也太过简化)的思考方式是,它是一组复杂的脚本,旨在以大规模的方式(无论是在复杂性还是它可以控制的系统数量方面)完成管理任务。Ansible 运行在一个安装了 Ansible 系统所有部分的简单服务器上。它不需要在所控制的机器上安装任何东西。可以说 Ansible 完全无代理,并且为了实现其目标,它使用不同的方式连接到远程系统并向其推送小型脚本。

这也意味着 Ansible 无法检测所控制系统上的变化;完全取决于我们创建的配置脚本来控制如果某些情况不如预期发生时会发生什么。

在做其他事情之前,我们需要定义一些东西 - 我们可以将其视为构建块或模块。Ansible 喜欢称自己为一个根本简单的 IT 引擎,它只有几个使其能够工作的这些构建块。

首先,它有清单 - 定义了某个任务将在哪些主机上执行的主机列表。主机在一个简单的文本文件中定义,可以是一个简单的包含每行一个主机的直接列表,也可以是一个在 Ansible 执行任务时创建的动态清单。我们将在展示它们如何使用时更详细地介绍这些内容。要记住的是,主机在文本文件中定义,没有涉及数据库(尽管可以有),主机可以被分组,这是一个你会广泛使用的功能。

其次,有一个称为play的概念,我们将其定义为 Ansible 在目标主机上运行的一组不同任务。我们通常使用一个 playbook 来启动一个 play,这是 Ansible 层次结构中的另一种对象。

就 playbooks 而言,将它们视为一项政策或一组任务/操作,这些任务/操作需要在特定系统上执行某些操作或达到某种状态。Playbooks 也是文本文件,专门设计为可读性强,由人类创建。Playbooks 用于定义配置或更准确地说是声明配置。它们可以包含以有序方式启动不同任务的步骤。这些步骤称为 plays,因此得名 playbook。Ansible 文档对此有所帮助,将 plays 比作体育比赛中提供的任务清单,并需要进行记录,但同时可能不会被调用。在这里需要理解的重要一点是,我们的 playbooks 可以在其中包含决策逻辑。

Ansible 拼图的第四个重要部分是它的模块。将模块视为在您试图控制的机器上执行的小程序,以实现某些目标。Ansible 软件包中包含了数百个模块,它们可以单独使用或在您的 playbooks 中使用。

模块允许我们完成任务,其中一些模块是严格声明性的。其他模块返回数据,要么作为模块执行的任务的结果,要么作为模块通过称为事实收集的过程从运行中的系统获取的显式数据。这个过程基于一个称为gather_facts的模块。收集关于系统的正确事实是我们开始开发自己的 playbooks 后可以做的最重要的事情之一。

以下架构显示了所有这些部分如何一起工作:

图 11.3 - Ansible 架构 - Python API 和 SSH 连接

图 11.3 - Ansible 架构 - Python API 和 SSH 连接

在 IT 领域工作的人普遍认为,通过 Ansible 进行管理比通过其他工具更容易,因为它不需要您在设置或 playbook 开发上浪费几天的时间。然而,不要误解:您必须学习如何使用 YAML 语法来广泛使用 Ansible。也就是说,如果您对基于 GUI 的方法感兴趣,您可以考虑购买 Red Hat Ansible Tower。

Ansible Tower 是一个基于 GUI 的实用工具,您可以使用它来管理基于 Ansible 的环境。这最初是一个名为 AWX 的项目,今天仍然非常活跃。但是 AWX 发布的方式与 Ansible Tower 发布的方式有一些关键区别。主要区别在于 Ansible Tower 使用特定的发布版本,而 AWX 采用了更像是 OpenStack 的方法 - 一个项目在快速前进并经常发布新版本。

正如 Red Hat 在www.ansible.com/products/awx-project/faq上明确说明的那样:

“Ansible Tower 是通过选择 AWX 的特定版本、加固以支持长期可维护性,并将其作为 Ansible Tower 产品提供给客户而生产的。”

基本上,AWX 是一个社区支持的项目,而 Red Hat 直接支持 Ansible Tower。以下是来自 Ansible AWX 的屏幕截图,这样您就可以看到 GUI 的样子:

图 11.4 - Ansible AWX GUI for Ansible

图 11.4 - Ansible AWX GUI for Ansible

还有其他可用于 Ansible 的 GUI,例如 Rundeck、Semaphore 等。但不知何故,AWX 似乎是那些不想为 Ansible Tower 支付额外费用的用户最合乎逻辑的选择。在继续进行常规的 Ansible 部署和使用之前,让我们花点时间来研究 AWX。

部署和使用 AWX

AWX 被宣布为一个开源项目,为开发人员提供对 Ansible Tower 的访问,无需许可证。与几乎所有其他红帽项目一样,这个项目也旨在弥合付费产品和社区驱动项目之间的差距,后者在较小的规模上具有几乎所有所需的功能,但没有为企业客户提供的所有功能。但这并不意味着 AWX 在任何方面都是一个项目。它构建了 Ansible 的功能,并启用了一个简单的 GUI,帮助您在 Ansible 部署中运行所有内容。

我们这里没有足够的空间来演示它的外观和用途,所以我们只会介绍安装和部署最简单的场景。

谈到 AWX 时,我们需要知道的最重要的地址是github.com/ansible/awx。这是项目所在的地方。最新的信息在这里,在readme.md中,在 GitHub 页面上显示。如果您不熟悉从 GitHub 克隆,不用担心-我们基本上只是从一个特殊的源复制,这将使您只能复制自上次获取文件版本以来发生变化的内容。这意味着为了更新到新版本,您只需要再次使用完全相同的命令克隆一次。

在 GitHub 页面上,有一个直接链接到我们将要遵循的安装说明。请记住,这次部署是从头开始的,因此我们需要再次构建我们的演示机器,并安装所有缺少的东西。

我们需要做的第一件事是获取必要的 AWX 文件。让我们将 GitHub 存储库克隆到我们的本地磁盘上:

图 11.5-Git 克隆 AWX 文件

图 11.5-Git 克隆 AWX 文件

请注意,我们使用 13.0.0 作为版本号,因为这是撰写时 AWX 的当前版本。

然后,我们需要解决一些依赖关系。显然,AWX 需要 Ansible、Python 和 Git,但除此之外,我们还需要支持 Docker,并且我们需要 GNU Make 来准备一些文件。我们还需要一个环境来运行我们的虚拟机。在本教程中,我们选择了 Docker,因此我们将使用 Docker Compose。

此外,这也是一个好地方提到,我们的机器至少需要 4GB 的 RAM 和 20GB 的空间才能运行 AWX。这与我们习惯使用的低占用空间有所不同,但这是有道理的,因为 AWX 不仅仅是一堆脚本。让我们从安装先决条件开始。

Docker 是我们将安装的第一个软件。我们在这里使用 CentOS 8,因此 Docker 不再是默认软件包的一部分。因此,我们需要添加存储库,然后安装 Docker 引擎。我们将使用-ce软件包,代表 Community Edition。我们还将使用--nobest选项来安装 Docker-如果没有此选项,CentOS 将报告我们缺少一些依赖项:

图 11.6-在 CentOS 8 上部署 docker-ce 软件包

图 11.6-在 CentOS 8 上部署 docker-ce 软件包

之后,我们需要运行以下命令:

dnf install docker-ce -y --nobest

总体结果应该看起来像这样。请注意,您特定安装的每个软件包的版本可能会有所不同。这是正常的,因为软件包一直在变化:

图 11.7-启动和启用 Docker 服务

图 11.7-启动和启用 Docker 服务

然后,我们将使用以下命令安装 Ansible 本身:

dnf install ansible

如果您正在运行完全干净的 CentOS 8 安装,可能需要在可用 Ansible 之前安装epel-release

接下来是 Python。仅使用dnf命令不会安装 Python,因为我们将不得不提供我们想要的 Python 版本。为此,我们会做这样的事情:

图 11.8-安装 Python;在这种情况下,版本 3.8

图 11.8-安装 Python;在这种情况下,版本 3.8

之后,我们将使用 pip 安装 Python 的 Docker 组件。只需输入pip3 install docker,您需要的一切都将被安装。

我们还需要安装make包:

图 11.9-部署 GNU Make

图 11.9-部署 GNU Make

现在,是时候进行 Docker Compose 部分了。我们需要运行pip3 install docker-compose命令来安装 Python 部分,以及以下命令来安装 docker-compose:

curl -L https://github.com/docker/compose/releases/download/1.25.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose

这个命令将从 GitHub 获取必要的安装文件,并使用必要的输入参数(通过执行uname命令)来启动 docker-compose 的安装过程。

我们知道这是很多依赖关系,但是 AWX 在内部是一个非常复杂的系统。然而,在表面上,事情并不那么复杂。在我们进行最后的安装之前,我们需要验证我们的防火墙是否已停止并且已禁用。我们正在创建一个演示环境,firewalld将阻止容器之间的通信。一旦系统运行起来,我们可以稍后解决这个问题。

一旦一切都运行起来,安装 AWX 就很简单。只需转到awx/installer目录并运行以下命令:

ansible-playbook -i inventory -e docker_registry_password=password install.yml

安装应该需要几分钟。结果应该是一个以以下内容结尾的长列表:

PLAY RECAP *********************************************************************
localhost  : ok=16   changed=8    unreachable=0    failed=0    skipped=86   rescued=0    ignored=0   

这意味着本地 AWX 环境已成功部署。

现在,有趣的部分开始了。AWX 由四个小的 Docker 图像组成。为了使其工作,所有这些图像都需要配置和运行。您可以使用docker psdocker logs -t awx_task来查看它们。

第一个命令列出了部署的所有图像,以及它们的状态:

图 11.10-检查拉取和启动的 docker 图像

图 11.10-检查拉取和启动的 docker 图像

第二个命令向我们显示了awx_task机器正在创建的所有日志。这些是整个系统的主要日志。一段时间后,初始配置将完成:

图 11.11-检查 awx_task 日志

图 11.11-检查 awx_task 日志

将有大量的日志记录,您将不得不使用Ctrl + C来中断此命令。

在整个过程之后,我们可以将我们的 Web 浏览器指向http://localhost。我们应该会看到一个看起来像这样的屏幕:

图 11.12-AWX 默认登录屏幕

图 11.12-AWX 默认登录屏幕

默认用户名是admin,密码是password。成功登录后,我们应该会看到以下 UI:

图 11.13-登录后的初始 AWX 仪表板

图 11.13-登录后的初始 AWX 仪表板

这里有很多东西需要学习,所以我们只会简单介绍一下基础知识。基本上,AWX 代表的是 Ansible 的智能 GUI。如果我们快速打开模板(在窗口的左侧)并查看演示模板,我们就可以看到这一点:

图 11.14-在 AWX 中使用演示模板

图 11.14-在 AWX 中使用演示模板

我们将在本章的下一部分更加熟悉这里看到的内容,当我们部署 Ansible 时。所有这些属性都是 Ansible playbook 的不同部分,包括 playbook 本身、清单、使用的凭据以及使使用 Ansible 变得更容易的其他一些东西。如果我们向下滚动一点,那里应该有三个按钮。按job

图 11.15-通过单击启动按钮,我们可以启动我们的模板作业

图 11.15 – 通过单击启动按钮,我们可以启动我们的模板作业

这个想法是我们可以创建模板并随时运行它们。运行它们后,运行的结果将出现在作业下(在窗口左侧的第二个项目中找到):

图 11.16 – 模板作业详情

图 11.16 – 模板作业详情

作业的详细信息基本上是发生了什么,何时发生,以及使用了哪些 Ansible 元素的总结。我们还可以看到我们刚刚运行的 playbook 的实际结果:

图 11.17 – 检查演示作业模板的文本输出

图 11.17 – 检查演示作业模板的文本输出

AWX 的真正作用是自动化自动化。它使您能够在使用 Ansible 时更加高效,因为它为 Ansible 使用的不同文件提供了一个更直观的界面。它还使您能够跟踪已完成的工作及其时间,以及结果是什么。所有这些都可以使用 Ansible CLI 实现,但 AWX 在我们控制整个过程的同时节省了大量精力。

当然,因为本章的目标是使用 Ansible,这意味着我们需要部署所有必要的软件包,以便我们可以使用它。因此,让我们继续进行我们的 Ansible 过程的下一个阶段,并部署 Ansible。

部署 Ansible

在所有设计用于编排和系统管理的类似应用程序中,Ansible 可能是最简单的安装。由于它在管理的系统上不需要代理,因此安装仅限于一台机器 - 将运行所有脚本和 playbook 的机器。默认情况下,Ansible 使用 SSH 连接到机器,因此其使用的唯一先决条件是我们的远程系统上有一个正在运行的 SSH 服务器。

除此之外,没有数据库(Ansible 使用文本文件),没有守护程序(Ansible 按需运行),也没有管理 Ansible 本身的必要。由于没有后台运行任何东西,因此可以轻松升级 Ansible - 唯一可能改变的是 playbook 的结构方式,而这可以很容易地修复。Ansible 基于 Python 编程语言,但其结构比标准 Python 程序更简单。配置文件和 playbook 要么是简单的文本文件,要么是 YAML 格式的文本文件,YAML 是用于定义数据结构的文件格式。学习 YAML 超出了本章的范围,因此我们只是假设您了解简单的数据结构。我们将使用的 YAML 文件示例足够简单,几乎不需要解释,但如果需要,我们会提供解释。

安装可以简单地运行以下命令:

yum install ansible 

您可以以 root 用户身份运行此命令,也可以使用以下命令:

apt install ansible 

选择取决于您的发行版(Red Hat/CentOS 或 Ubuntu/Debian)。更多信息可以在 Ansible 网站上找到docs.ansible.com/

RHEL8 用户首先需要启用包含 Ansible RPM 的存储库。在撰写本文时,可以通过运行以下命令来实现:

sudo subscription-manager repos --enable ansible-2.8-for-rhel-8-x86_64-rpms

运行上述命令后,使用以下代码:

dnf install ansible

这就是安装 Ansible 所需的全部内容。

有一件事可能会让您感到惊讶,那就是安装的大小:它确实如此小(约 20 MB),并且会根据需要安装 Python 依赖项。

安装了 Ansible 的机器也被称为控制节点。它必须安装在 Linux 主机上,因为 Windows 不支持这个角色。Ansible 控制节点可以在虚拟机内运行。

我们控制的机器称为受控节点,默认情况下,它们是通过SSH协议控制的 Linux 系统。有一些模块和插件可以扩展到 Windows 和 macOS 操作系统,以及其他通信渠道。当你开始阅读 Ansible 文档时,你会注意到大多数支持多个架构的模块都有清晰的说明,关于如何在不同的操作系统上完成相同的任务。

我们可以使用/etc/ansible/ansible来配置 Ansible 的设置。这个文件包含了定义默认值的参数,并且本身包含了很多被注释掉的行,但包含了 Ansible 用于工作的所有默认值。除非我们改变了什么,否则这些值就是 Ansible 要使用的值。让我们实际使用 Ansible 来看看所有这些是如何配合在一起的。在我们的场景中,我们将使用 Ansible 来通过其内置模块配置虚拟机。

使用 kvm_libvirt 模块配置虚拟机

一个你可能会或可能不会包括的设置是定义 SSH 如何用于连接 Ansible 将要配置的机器。在我们这样做之前,我们需要花一点时间来谈谈安全和 Ansible。与几乎所有与 Linux(或*nix一般)相关的事物一样,Ansible 不是一个集成的系统,而是依赖于已经存在的不同服务。为了连接到它管理的系统并执行命令,Ansible 依赖于SSH(在 Linux 中)或其他系统,如 Windows 上的WinRMPowerShell。我们将在这里专注于 Linux,但请记住,关于 Ansible 的相当多的信息是完全与系统无关的。

SSH是一个简单但非常强大的协议,它允许我们通过安全通道传输数据(安全 FTP,SFTP 等)并在远程主机上执行命令(SSH)。Ansible 直接使用 SSH 连接,然后执行命令和传输文件。当然,这意味着为了使 Ansible 工作,SSH 至关重要。

使用SSH连接时需要记住的几件事:

  • 第一个是密钥指纹,从 Ansible 控制节点(服务器)看到的。在首次建立连接时,SSH要求用户验证和接受远程系统呈现的密钥。这旨在防止中间人攻击,并且在日常使用中是一个很好的策略。但如果我们处于必须配置新安装系统的位置,所有它们都将要求我们接受它们的密钥。这是耗时且复杂的,一旦我们开始使用 playbooks,就很难做到,所以你可能会开始的第一个 playbook 是禁用密钥检查和登录到机器。当然,这只应该在受控环境中使用,因为这会降低整个 Ansible 系统的安全性。

  • 第二件你需要知道的事情是 Ansible 作为一个普通用户运行。话虽如此,也许我们不想以当前用户连接到远程系统。Ansible 通过在单独的计算机或组上设置一个变量来解决这个问题,该变量指示系统将用于连接到这台特定计算机的用户名。连接后,Ansible 允许我们以完全不同的用户身份在远程系统上执行命令。这是一个常用的功能,因为它使我们能够完全重新配置机器并像在控制台上一样更改用户。

  • 第三件我们需要记住的事情是密钥 - SSH可以通过交互式身份验证登录,意思是通过密码或使用一次交换的预共享密钥来建立 SSH 会话。还有ssh-agent,它可以用于身份验证会话。

虽然我们可以在清单文件(或特殊的密钥库)中使用固定密码,但这是一个坏主意。幸运的是,Ansible 使我们能够脚本化许多事情,包括将密钥复制到远程系统。这意味着我们将有一些 playbooks 来自动部署新系统,并且这些将使我们能够控制它们进行进一步的配置。

总之,部署系统的 Ansible 步骤可能会像这样开始:

  1. 安装核心系统,并确保SSHD正在运行。

  2. 定义一个在系统上具有管理员权限的用户。

  3. 从控制节点运行一个播放列表,将建立初始连接并将本地的SSH密钥复制到远程位置。

  4. 使用适当的 playbooks 来安全地重新配置系统,而无需在本地存储密码。

现在,让我们深入了解一下。

每个合理的管理者都会告诉你,为了做任何事情,你需要定义问题的范围。在自动化中,这意味着定义 Ansible 将要处理的系统。这是通过位于/etc/Ansible的清单文件hosts完成的。

Hosts可以被分组或单独命名。在文本格式中,可以这样写:

[servers]
srv1.local
srv2.local
srv3.local
[workstations]
wrk1.local
wrk2.local
wrk3.local

计算机可以同时属于多个组,组也可以是嵌套的。

我们在这里使用的格式是纯文本。让我们用 YAML 来重写这个:

All:
  Servers:
     Hosts:
	Srv1.local:
Srv2.local:
Srv3.local:
 Workstations:
     Hosts:
	Wrk1.local:
Wrk2.local:
Wrk3.local:
Production:
   Hosts:
	Srv1.local:
	Workstations:

重要提示

我们创建了另一个名为 Production 的组,其中包含所有工作站和一个服务器。

任何不属于默认或标准配置的内容都可以作为变量单独包含在主机定义或组定义中。每个 Ansible 命令都有一些方式可以在配置或清单中部分或完全覆盖所有项目的灵活性。

清单支持主机定义中的范围。我们之前的示例可以写成如下形式:

[servers]
Srv[1:3].local
[workstations]
Wrk[1:3].local

这也适用于字符,所以如果我们需要定义名为srvasrvbsrvcsrvd的服务器,我们可以通过以下方式来说明:

srv[a:d]

也可以使用 IP 范围。因此,例如,10.0.0.0/24将被写成如下形式:

10.0.0.[1:254]

还有两个预定义的默认组也可以使用:allungrouped。顾名思义,如果我们在 playbook 中引用all,它将在清单中的每台服务器上运行。Ungrouped将仅引用那些不属于任何组的系统。

未分组的引用在设置新计算机时特别有用-如果它们不属于任何组,我们可以将它们视为,并设置它们加入特定的组。

这些组是隐式定义的,无需重新配置它们,甚至在清单文件中提及它们。

我们提到清单文件可以包含变量。当我们需要在计算机组、用户、密码或特定于该组的设置中定义一个属性时,变量是有用的。假设我们想要定义一个将在servers组上使用的用户:

  1. 首先,我们定义一个组:
[servers]
srv[1:3].local
  1. 然后,我们定义将用于整个组的变量:
[servers:vars]
ansible_user=Ansibleuser
ansible_connection=ssh

当要求执行 playbook 时,这将使用名为Ansibleuser的用户使用SSH进行连接。

重要提示

请注意,密码不在此处出现,如果密码没有单独提及或密钥在此之前没有交换,此 playbook 将失败。有关变量及其使用的更多信息,请参阅 Ansible 文档。

现在我们已经创建了我们的第一个实用的 Ansible 任务,是时候谈谈如何让 Ansible 一次执行多个任务了,同时使用更客观的方法。能够创建单个任务或一对任务,并通过一个称为playbook的概念将它们组合起来是非常重要的。

使用 playbooks

一旦我们决定如何连接到我们打算管理的机器,并且一旦我们创建了清单,我们就可以开始实际使用 Ansible 来做一些有用的事情。这就是 playbooks 开始变得有意义的地方。

在我们的示例中,我们配置了四台 CentOS7 系统,为它们分配了连续的地址范围内的地址10.0.0.110.0.0.4,并将它们用于一切。

Ansible 已安装在 IP 地址为10.0.0.1的系统上,但正如我们已经说过的,这完全是任意的。Ansible 在用作控制节点的系统上占用空间很小,并且只要它与我们打算管理的网络的其余部分有连接,就可以安装在任何系统上。我们只是选择了我们小型网络中的第一台计算机。还要注意的一件事是,控制节点可以通过 Ansible 自身进行控制。这很有用,但同时也不是一个明智的做法。根据您的设置,您不仅要测试 playbooks,还要测试部署到其他机器之前的单个命令-在控制服务器上进行这样的操作是不明智的。

现在 Ansible 已安装,我们可以尝试使用它做一些事情。Ansible 有两种不同的运行方式。一种是运行 playbook,其中包含要执行的任务。另一种方式是使用单个任务,有时称为临时执行。无论哪种方式都有使用 Ansible 的原因- playbooks 是我们的主要工具,你可能会大部分时间使用它们。但临时执行也有其优势,特别是如果我们有兴趣做一些我们需要一次完成的事情,但是要跨多台服务器进行。一个典型的例子是使用一个简单的命令来检查已安装应用程序的版本或应用程序状态。如果我们需要检查某些东西,我们不会编写一个 playbook。

为了查看一切是否正常工作,我们将从简单地使用 ping 开始,以检查机器是否在线。

Ansible 喜欢称自己为根本简单的自动化,我们要做的第一件事就证明了这一点。

我们将使用一个名为 ping 的模块,该模块尝试连接到主机,验证它是否可以在本地 Python 环境中运行,并在一切正常时返回一条消息。不要将此模块与 Linux 中的ping命令混淆;我们不是通过网络进行 ping 操作;我们只是从控制节点向我们试图控制的服务器进行ping。我们将使用一个简单的ansible命令来 ping 所有已定义的主机,发出以下命令:

ansible all -m ping

运行上述命令的结果如下:

图 11.18-我们的第一个 Ansible 模块-ping,检查 Python 并报告其状态

图 11.18-我们的第一个 Ansible 模块-ping,检查 Python 并报告其状态

我们在这里做的是运行一个名为ansible all -m ping的单个命令。

ansible是可用的最简单的命令,运行单个任务。all参数表示在清单中的所有主机上运行它,-m用于调用将要运行的模块。

这个特定的模块没有参数或选项,所以我们只需要运行它以获得结果。结果本身很有趣;它是以 YAML 格式呈现的,并包含除命令结果之外的一些内容。

如果我们仔细看一下,我们会发现 Ansible 为清单中的每个主机返回了一个结果。我们可以看到的第一件事是命令的最终结果-SUCCESS表示任务本身顺利运行。之后,我们可以看到一个数组形式的数据-ansible_facts包含模块返回的信息,在编写 playbooks 时被广泛使用。以这种方式返回的数据可能会有所不同。在下一节中,我们将展示一个更大的数据集,但在这种特殊情况下,显示的唯一内容是 Python 解释器的位置。之后,我们有changed变量,这是一个有趣的变量。

当 Ansible 运行时,它会尝试检测它是否正确运行以及是否已更改系统状态。在这个特定的任务中,运行的命令只是提供信息,并不会更改系统上的任何内容,因此系统状态没有改变。

换句话说,这意味着运行的任何命令都没有安装或更改系统上的任何内容。在以后需要检查某些东西是否已安装或未安装(例如服务)时,状态将更有意义。

我们可以看到的最后一个变量是ping命令的返回。它简单地声明pong,因为这是模块在一切设置正确时给出的正确答案。

让我们做类似的事情,但这次带有一个参数,比如我们希望在远程主机上执行的临时命令。因此,请输入以下命令:

ansible all -m shell -a "hostname"

以下是输出:

图 11.19 - 使用 Ansible 在 Ansible 目标上显式执行特定命令

图 11.19 - 使用 Ansible 在 Ansible 目标上显式执行特定命令

在这里,我们调用了另一个名为shell的模块。它只是将给定的参数作为 shell 命令运行。返回的是本地主机名。这在功能上与我们使用SSH连接到清单中的每个主机,执行命令,然后注销的操作是一样的。

对于 Ansible 可以做的简单演示来说,这是可以的,但让我们做一些更复杂的事情。我们将使用一个特定于 CentOS/Red Hat 的名为yum的模块来检查我们的主机上是否安装了 Web 服务器。我们要检查的 Web 服务器将是lighttpd,因为我们想要轻量级的东西。

当我们谈到状态时,我们触及了一个起初有点令人困惑,但一旦开始使用就非常有用的概念。当调用这样的命令时,我们正在声明一个期望的状态,因此如果状态不是我们要求的状态,系统本身将发生变化。这意味着,在这个例子中,我们实际上并不是在测试lighttpd是否已安装 - 我们是在告诉 Ansible 去检查它,如果它没有安装就安装它。即使这也不完全正确 - 该模块接受两个参数:服务的名称和它应该处于的状态。如果我们检查的系统状态与调用模块时发送的状态相同,我们将得到changed: false,因为没有发生任何变化。但是,如果系统的状态不同,Ansible 将使系统的当前状态与我们请求的状态相同。

为了证明这一点,我们将看看服务在 Ansible 术语中是安装或不存在的。请键入以下命令:

ansible all -m yum -a "name=lighttpd state=absent" 

这是运行前述命令后应该得到的结果:

图 11.20 - 使用 Ansible 检查服务状态

图 11.20 - 使用 Ansible 检查服务状态

然后,我们可以说我们希望它出现在系统上。Ansible 将根据需要安装服务:

图 11.21 - 在所有 Ansible 目标上使用 yum install 命令

图 11.21 - 在所有 Ansible 目标上使用 yum install 命令

在这里,我们可以看到 Ansible 只是检查并安装了服务,因为它之前不存在。它还为我们提供了其他有用的信息,比如系统上做了什么更改以及它执行的命令的输出。信息以变量数组的形式提供;这通常意味着我们需要进行一些字符串操作,以使其看起来更好。

现在,让我们再次运行该命令:

ansible all -m yum -a "name=lighttpd state=absent" 

这应该是结果:

图 11.22 - 在服务安装后使用 Ansible 检查服务状态

图 11.22 - 在服务安装后使用 Ansible 检查服务状态

正如我们所看到的,这里没有任何变化,因为服务已安装。

这些只是一些起步示例,以便我们能够稍微了解一下 Ansible。现在,让我们扩展一下,并创建一个 Ansible playbook,它将在我们预定义的一组主机上安装 KVM。

安装 KVM

现在,让我们创建我们的第一个 playbook,并使用它在所有主机上安装 KVM。对于我们的 playbook,我们使用了 GitHub 存储库中的一个很好的例子,由 Jared Bloomer 创建,我们稍微修改了一下,因为我们已经配置了我们的选项和清单。原始文件可在github.com/jbloomer/Ansible---Install-KVM-on-CentOS-7.git找到。

这个 playbook 将展示我们需要了解的关于自动化简单任务的一切。我们选择了这个特定的例子,因为它不仅展示了自动化的工作原理,还展示了如何创建单独的任务并在不同的 playbook 中重用它们。使用公共存储库的一个额外好处是你将始终获得最新版本,但它可能与这里呈现的版本有很大不同:

  1. 首先,我们创建了我们的主要 playbook – 将被调用的那个 – 并命名为installkvm.yaml图 11.23–检查虚拟化支持并安装 KVM 的主要 Ansible playbook

图 11.23–检查虚拟化支持并安装 KVM 的主要 Ansible playbook

正如我们所看到的,这是一个简单的声明,所以让我们逐行分析一下。首先,我们有 playbook 名称,一个可以包含我们想要的任何内容的字符串:

hosts变量定义了这个 playbook 将在清单的哪一部分上执行 – 在我们的情况下,是所有主机。我们可以在运行时覆盖这一点(以及所有其他变量),但将 playbook 限制在我们需要控制的主机上是有帮助的。在我们的特定情况下,这实际上是我们清单中的所有主机,但在生产中,我们可能会有多个主机组。

下一个变量是执行任务的用户的名称。我们在这里做的事情在生产中是不推荐的,因为我们使用超级用户帐户来执行任务。Ansible 完全能够使用非特权帐户并在需要时提升权限,但就像所有演示一样,我们会犯错误,这样你就不必犯错误,所有这些都是为了更容易理解事情。

现在是实际执行我们任务的部分。在 Ansible 中,我们为系统声明角色。在我们的例子中,有两个角色。角色实际上只是要执行的任务,这将导致系统处于某种状态。在我们的第一个角色中,我们将检查系统是否支持虚拟化,然后在第二个角色中,我们将在所有支持虚拟化的系统上安装 KVM 服务。

  1. 当我们从 GitHub 下载脚本时,它创建了一些文件夹。在名为roles的文件夹中,有两个子文件夹,每个文件夹都包含一个文件;一个叫做checkVirtualization,另一个叫做installKVM

你可能已经看到这是怎么回事了。首先,让我们看看checkVirtualization包含什么:

图 11.24–通过 lscpu 命令检查 CPU 虚拟化

图 11.24–通过 lscpu 命令检查 CPU 虚拟化

这个任务只是调用一个 shell 命令,并尝试使用grep来查找包含 CPU 虚拟化参数的行。如果找不到,它就会失败。

  1. 现在,让我们看看另一个任务:图 11.25–用于安装必要的 libvirt 软件包的 Ansible 任务

图 11.25–用于安装必要的 libvirt 软件包的 Ansible 任务

第一部分是一个简单的循环,如果它们不存在,将安装五个不同的软件包。我们在这里使用的是包模块,这与我们在第一次演示中如何安装软件包的方法不同。我们在本章早些时候使用的模块称为yum,它是特定于 CentOS 作为发行版的。package模块是一个通用模块,将转换为特定发行版使用的任何软件包管理器。一旦我们安装了所有需要的软件包,我们需要确保libvirtd已启用并已启动。

我们使用一个简单的循环来遍历我们正在安装的所有软件包。这不是必需的,但这比复制和粘贴单个命令更好,因为它使我们需要的软件包列表更加可读。

然后,作为任务的最后一部分,我们验证 KVM 是否已加载。

正如我们所看到的,playbook 的语法很简单。即使是对脚本编程只有少量知识的人也可以轻松阅读。我们甚至可以说,对 Linux 命令行工作原理的深刻理解更为重要。

  1. 为了运行 playbook,我们使用ansible-playbook命令,后面跟着 playbook 的名称。在我们的情况下,我们将使用ansible-playbook main.yaml命令。这里是结果:图 11.26 - 交互式 Ansible playbook 监控

图 11.26 - 交互式 Ansible playbook 监控

  1. 在这里,我们可以看到 Ansible 将在每个主机上做的每一项更改进行了详细的拆分。最终结果是成功的:图 11.27 - Ansible playbook 报告

图 11.27 - Ansible playbook 报告

现在,让我们检查我们新安装的 KVM 集群是否正常工作。

  1. 我们将启动virsh并列出集群各部分的活动 VM:

图 11.28 - 使用 Ansible 检查所有 Ansible 目标上的虚拟机

图 11.28 - 使用 Ansible 检查所有 Ansible 目标上的虚拟机

完成了这个简单的练习后,我们在四台机器上都运行了 KVM,并且可以从一个地方控制它们。但是我们的主机上还没有运行任何 VM。接下来,我们将向您展示如何在 KVM 环境中创建一个 CentOS 安装,但我们将使用最基本的方法 - virsh

我们将做两件事:首先,我们将从互联网上下载一个 CentOS 的最小 ISO 镜像。然后,我们将调用virsh。本书将向您展示完成此任务的不同方法;从互联网上下载是最慢的方法之一:

  1. 像往常一样,Ansible 有一个专门用于下载文件的模块。它期望的参数是文件所在的 URL 和保存文件的位置:图 11.29 - 在 Ansible playbook 中下载文件

图 11.29 - 在 Ansible playbook 中下载文件

  1. 运行 playbook 后,我们需要检查文件是否已经下载:图 11.30 - 状态检查 - 检查文件是否已经下载到我们的目标

图 11.30 - 状态检查 - 检查文件是否已经下载到我们的目标

  1. 由于我们没有自动化这个过程,而是创建了一个单独的任务,我们将在本地 shell 中运行它。要运行此命令,可以使用类似以下的命令:
ansible all -m shell -a "virt-install --name=COS7Core --ram=2048 --vcpus=4 --cdrom=/var/lib/libvirt/boot/CentOS-7-x86_64-Minimal-1810.iso --os-type=linux --os-variant=rhel7 --disk path=/var/lib/libvirt/images/cos7vm.dsk,size=6"
  1. 没有 kickstart 文件或其他类型的预配置,这个 VM 是没有意义的,因为我们将无法连接到它,甚至无法完成安装。在下一个任务中,我们将使用 cloud-init 来解决这个问题。

现在,我们可以检查一切是否都正常工作:

图 11.31 - 使用 Ansible 检查我们的所有 VM 是否正在运行

图 11.31 - 使用 Ansible 检查我们的所有 VM 是否正在运行

在这里,我们可以看到所有的 KVM 都在运行,并且每个 KVM 都有自己的虚拟机在线并运行。

现在,我们将清除我们的 KVM 集群,并重新开始,但这次使用不同的配置:我们将部署 CentOS 的云版本,并使用 cloud-init 重新配置它。

使用 Ansible 和 cloud-init 进行自动化和编排

Cloud-init是私有和混合云环境中机器部署的更受欢迎的方式之一。这是因为它使机器能够快速重新配置,以便启用足够的功能,使它们能够连接到诸如 Ansible 之类的编排环境。

更多细节可以在cloud-init.io找到,但简而言之,cloud-init 是一个工具,可以创建特殊文件,可以与 VM 模板结合,以便快速部署它们。cloud-init 和无人值守安装脚本之间的主要区别在于,cloud-init 更多或更少地与发行版无关,并且更容易使用脚本工具进行更改。这意味着在部署过程中工作量更少,从部署开始到机器在线并工作的时间更短。在 CentOS 上,可以使用 kickstart 文件来实现这一点,但这远不及 cloud-init 灵活。

Cloud-init 使用两个单独的部分工作:一个是我们正在部署的操作系统的分发文件。这不是通常的 OS 安装文件,而是一个特别配置的机器模板,旨在用作 cloud-init 镜像。

系统的另一部分是配置文件,它是从一个包含机器配置的特殊 YAML 文本文件中编译 - 或者更准确地说,打包出来的。这个配置很小,非常适合网络传输。

这两个部分旨在作为一个整体用于创建多个相同虚拟机实例。

它的工作方式很简单:

  1. 首先,我们分发一个完全相同的机器模板,用于创建我们将要创建的所有机器。这意味着有一个主模板副本,并且可以从中创建所有实例。

  2. 然后,我们将模板与使用 cloud-init 创建的一个特制文件配对。我们的模板,无论使用的操作系统是什么,都能够理解可以在 cloud-init 文件中设置的不同指令,并将被重新配置。这可以根据需要重复进行。

让我们更简化一下:如果我们需要使用无人值守安装文件创建具有四种不同角色的 100 台服务器,我们将不得不启动 100 个镜像,并等待它们逐个完成所有安装步骤。然后,我们需要为我们需要的任务重新配置它们。使用 cloud-init,我们在 100 个实例中启动一个镜像,但系统只需要几秒钟就能启动,因为它已经安装好了。只需要关键信息将其上线,之后我们可以接管并使用 Ansible 完全配置它。

我们不会过多讨论 cloud-init 的配置;我们需要的一切都在这个例子中:

图 11.32 - 使用 cloud-init 进行附加配置

图 11.32 - 使用 cloud-init 进行附加配置

像往常一样,我们将一步一步地解释发生了什么。我们从一开始就可以看到它使用直接的 YAML 表示法,与 Ansible 相同。第一个指令是为了确保我们的机器已更新,因为它可以自动更新云实例上的软件包。

然后,我们正在配置用户。我们将创建一个名为ansible的用户,他将属于wheel组。

Lock_passwd表示我们将允许使用密码登录。如果没有配置任何内容,则默认情况下只允许使用SSH密钥登录,并完全禁用密码登录。

然后,我们有哈希格式的密码。根据发行版的不同,可以以不同的方式创建这个哈希。在这里不要放置明文密码。

然后,我们有一个 shell,这个用户将能够使用,如果需要向/etc/sudoers文件添加内容。在这种情况下,我们给予这个用户对系统的完全控制。

最后一件事可能是最重要的。这是我们系统上的公共SSH密钥。它用于授权用户登录时使用。这里可以有多个密钥,并且它们将最终出现在SSHD配置中,以便用户可以进行无密码登录。

这里有很多变量和指令可以使用,因此请查阅cloud-config文档以获取更多信息。

创建完这个文件后,我们需要将其转换为一个.iso文件,该文件将用于安装。执行此操作的命令是cloud-localds。我们将我们的 YAML 文件用作一个参数,.iso文件用作另一个参数。

运行cloud-localds config.iso config.yaml之后,我们准备开始部署。

我们需要的下一件事是 CentOS 的云镜像。正如我们之前提到的,这是一种专门设计用于这个特定目的的特殊镜像。

我们将从cloud.centos.org/centos/7/images获取它。

这里有很多文件,表示 CentOS 镜像的所有可用版本。如果您需要特定版本,请注意表示镜像发布的月/年的数字。还要注意,镜像有两种类型 - 压缩和未压缩。

镜像以qcow2格式存在,旨在作为云盘使用。

在我们的例子中,在 Ansible 机器上,我们创建了一个名为/clouddeploy的新目录,并将两个文件保存在其中:一个包含 OS 云镜像的文件和使用cloud-init创建的config.iso

图 11.33 - 检查目录内容

图 11.33 - 检查目录内容

现在剩下的就是创建一个部署这些内容的 playbook。让我们按照以下步骤进行:

  1. 首先,我们将复制云镜像和我们的配置到我们的 KVM 主机上。之后,我们将创建一个机器,并启动它:图 11.34 - 将下载所需镜像、配置 cloud-init 并启动 VM 部署过程的 playbook

图 11.34 - 将下载所需镜像、配置 cloud-init 并启动 VM 部署过程的 playbook

由于这是我们的第一个复杂的 playbook,我们需要解释一些事情。在每个 play 或 task 中,有一些重要的事情。名称用于简化运行 playbook;这是 playbook 运行时将显示的内容。这个名称应该足够解释性,以帮助理解,但不要太长以避免混乱。

在名称之后,我们有每个任务的业务部分 - 被调用的模块的名称。在我们的例子中,我们使用了三个不同的模块:copycommandvirtcopy用于在主机之间复制文件,command在远程机器上执行命令,virt包含控制虚拟环境所需的命令和状态。

阅读时您会注意到copy看起来很奇怪;src表示本地目录,而dest表示远程目录。这是有意设计的。为了简化事情,copy在本地机器(运行 Ansible 的控制节点)和远程机器(正在配置的机器)之间工作。如果目录不存在,将会创建目录,并且copy将应用适当的权限。

之后,我们将运行一个命令,该命令将处理本地文件并创建一个虚拟机。这里的一个重要事情是,我们基本上运行了我们复制的镜像;模板在控制节点上。同时,这节省了磁盘空间和部署时间 - 没有必要将机器从本地复制到远程磁盘,然后再次在远程机器上复制;一旦镜像在那里,我们就可以运行它。

回到重要的部分 – 本地安装。我们正在创建一个具有 1GB RAM 和一个 CPU 的机器,使用我们刚刚复制的磁盘映像。我们还将config.iso文件作为虚拟 CD/DVD 附加。然后,我们导入此映像并不使用图形终端。

  1. 最后一个任务是在远程 KVM 主机上启动 VM。我们将使用以下命令来执行:
ansible-playbook installvms.yaml

如果一切正常运行,我们应该看到类似于这样的东西:

图 11.35 – 检查我们的安装过程

图 11.35 – 检查我们的安装过程

我们也可以使用命令行来检查:

ansible cloudhosts -m shell -a "virsh list –all"

此命令的输出应该看起来像这样:

图 11.36 – 检查我们的虚拟机

图 11.36 – 检查我们的虚拟机

让我们再检查两件事 – 网络和机器状态。输入以下命令:

ansible cloudhosts -m shell -a "virsh net-dhcp-leases –-network default"

我们应该得到类似于这样的东西:

图 11.37 – 检查我们的 VM 网络连接和网络配置

图 11.37 – 检查我们的 VM 网络连接和网络配置

这验证了我们的机器是否正常运行,并且它们连接到本地 KVM 实例的本地网络。在本书的其他地方,我们将更详细地处理 KVM 网络,因此重新配置机器以使用公共网络应该很容易,无论是通过在 KVM 上桥接适配器,还是通过创建一个跨主机的独立虚拟网络。

我们想要展示的另一件事是所有主机的机器状态。重点是,这次我们不是使用 shell 模块;相反,我们依靠virt模块来显示如何从命令行使用它。这里只有一个细微的区别。当我们调用 shell(或command)模块时,我们正在调用将被调用的参数。这些模块基本上只是在远程机器上生成另一个进程,并使用我们提供的参数运行它。

相比之下,virt模块以变量声明作为其参数,因为我们正在使用command=info运行virt。在使用 Ansible 时,您会注意到,有时变量只是状态。如果我们想要启动特定的实例,我们只需添加state=running,以及一个适当的名称,Ansible 会确保虚拟机正在运行。让我们输入以下命令:

ansible cloudhosts -m virt -a "command=info"

以下是预期的输出:

图 11.38 – 使用 virt 模块与 Ansible

图 11.38 – 使用 virt 模块与 Ansible

我们还没有涵盖的一件事是如何安装多层应用程序。将定义推到最小的极端,我们将使用简单的 playbook 安装 LAMP 服务器。

在 KVM VM 上编排多层应用程序部署

现在,让我们学习如何安装多层应用程序。将定义推到最小的极端,我们将使用简单的 Ansible playbook 安装 LAMP 服务器。

需要完成的任务非常简单 – 我们需要安装 Apache、MySQL 和 PHP。LAMP 的L部分已经安装好了,所以我们不会再次进行安装。

困难的部分是软件包名称:在我们的演示机器上,我们使用 CentOS7 作为操作系统,其软件包名称有些不同。Apache 被称为httpdmysql被替换为与 MySQL 兼容的另一个引擎mariaDB。PHP 幸运地与其他发行版上的相同。我们还需要另一个名为python2-PyMySQL的软件包(名称区分大小写)以使我们的 playbook 工作。

接下来要做的事情是通过启动所有服务并创建最简单的.php脚本来测试安装。之后,我们将创建一个数据库和一个将使用它的用户。需要警告的是,在本章中,我们专注于 Ansible 的基础知识,因为 Ansible 太复杂,无法在一本书的一章中涵盖。此外,我们假设了很多事情,我们最大的假设是我们正在创建的演示系统并不打算用于生产。特别是这个 playbook 缺少一个重要的步骤:创建一个 root 密码。不要在未设置 SQL 密码的情况下投入生产。

还有一件事:我们的脚本假设在我们的 playbook 运行的目录中有一个名为index.php的文件,并且该文件将被复制到远程系统中:

图 11.39 - Ansible LAMP playbook

图 11.39 - Ansible LAMP playbook

正如我们所看到的,没有发生复杂的事情,只是一系列简单的步骤。我们的.php文件如下所示:

图 11.40 - 测试 PHP 是否正常工作

图 11.40 - 测试 PHP 是否正常工作

事情不可能比这更简单了。在正常的部署场景中,我们在 web 服务器目录中会有更复杂的东西,比如 WordPress 或 Joomla 安装,甚至是自定义应用程序。唯一需要改变的是被复制的文件(或一组文件)和数据库的位置。我们的文件只是打印有关本地.php安装的信息:

图 11.41 - 使用 Web 浏览器检查 PHP 在 Apache 上是否正常工作以及之前配置的 PHP 文件

图 11.41 - 使用 Web 浏览器检查 PHP 在 Apache 上是否正常工作和之前配置的 PHP 文件

Ansible 比我们在本章中展示的要复杂得多,因此我们强烈建议您进行进一步阅读和学习。我们在这里所做的只是如何在多个主机上安装 KVM 并使用命令行一次性控制它们的最简单示例。Ansible 最擅长的是节省我们的时间 - 想象一下有几百个 hypervisor 并且必须部署成千上万台服务器。使用 playbooks 和一些预配置的镜像,我们不仅可以配置 KVM 来运行我们的机器,还可以重新配置机器上的任何东西。唯一真正的先决条件是运行的 SSH 服务器和一个能够使我们对机器进行分组的清单。

通过示例学习 - 使用 Ansible 与 KVM 的各种示例

现在我们已经介绍了简单和更复杂的 Ansible 任务,让我们考虑如何使用 Ansible 来进一步提高我们的配置技能和整体合规性,基于某种政策。以下是一些我们将留给您作为练习的事项:

  • 任务 1:

我们配置并运行了每个 KVM 主机上的一台机器。创建一个将形成一对主机的 playbook - 一个运行网站,另一个运行数据库。您可以使用任何开源 CMS 来实现这一点。

  • 任务 2:

使用 Ansible 和virt-net模块重新配置网络,以便整个集群可以通信。KVM 接受网络的.xml配置,virt-net可以读取和写入 XML。提示:如果感到困惑,请使用单独的 RHEL8 机器在 GUI 中创建一个虚拟网络,然后使用virsh net-dumpxml语法将虚拟网络配置输出到标准输出,然后可以将其用作模板。

  • 任务 3:

使用ansiblevirsh自动启动您在主机上创建/导入的特定 VM。

  • 任务 4:

根据我们的 LAMP 部署 playbook,通过以下方式改进它:

a)创建一个可以在远程机器上运行的 playbook。

b)创建一个将在不同服务器上安装不同角色的 playbook。

c)创建一个部署更复杂应用程序(如 WordPress)的 playbook。

如果您成功解决了这五个任务,那么恭喜您——您正在成为一个可以使用大写字母A的自动化管理员。

摘要

在本章中,我们讨论了 Ansible——一个用于编排和自动化的简单工具。它可以在开源和基于 Microsoft 的环境中使用,因为它本身支持这两种环境。可以通过 SSH 密钥访问开源系统,而可以通过 WinRM 和 PowerShell 访问 Microsoft 操作系统。我们学到了许多关于简单的 Ansible 任务和更复杂的任务,因为部署托管在多个虚拟机上的多层应用程序并不是一件容易的事情——特别是如果您手动解决问题。即使在多个主机上部署 KVM hypervisor 也可能需要相当长的时间,但我们成功地用一个简单的 Ansible playbook 解决了这个问题。请注意,我们只需要大约 20 行配置来做到这一点,而由此带来的好处是我们可以轻松地将数百个主机添加为此 Ansible playbook 的目标。

下一章将带我们进入云服务的世界——具体来说是 OpenStack——在那里我们的 Ansible 知识将对大规模虚拟机配置非常有用,因为使用任何手动工具都无法配置所有的云虚拟机。除此之外,我们将通过集成 OpenStack 和 Ansible 来扩展我们对 Ansible 的了解,以便我们可以同时使用这两个平台来做它们擅长的事情——管理云环境和配置其可消耗资源。

问题

  1. 什么是 Ansible?

  2. Ansible playbook 的作用是什么?

  3. Ansible 使用哪种通信协议连接到其目标?

  4. 什么是 AWX?

  5. 什么是 Ansible Tower?

进一步阅读

有关本章内容的更多信息,请参考以下链接:

第四部分:可扩展性、监控、性能调优和故障排除

在本书的这部分,您将学习基于 KVM 的虚拟机和 hypervisor 的可扩展性、监控、高级性能调优和故障排除。

本书的这部分包括以下章节:

  • 第十二章,使用 OpenStack 扩展 KVM

  • 第十三章,使用 AWS 扩展 KVM

  • 第十四章,监控 KVM 虚拟化平台

  • 第十五章,KVM 虚拟机性能调优和优化

  • 第十六章,KVM 平台故障排除指南

第十二章:使用 OpenStack 扩展 KVM

能够虚拟化一台机器是一件大事,但有时,仅仅虚拟化是不够的。问题在于如何给予个人用户工具,使他们可以在需要时虚拟化他们需要的任何东西。如果我们将以用户为中心的方法与虚拟化相结合,我们将得到一个需要能够连接到 KVM 作为虚拟化机制(不仅仅是 KVM)并使用户能够在自助环境中通过 Web 浏览器获取其虚拟机并自动配置的系统。OpenStack 增加了一件事情,因为它完全免费并且完全基于开源技术。由于其复杂性,配置这样的系统是一个大问题,在本章中,我们将向您展示 - 或者更准确地说,指向您 - 关于是否需要这样的系统的正确方向。

在本章中,我们将涵盖以下主题:

  • 介绍 OpenStack

  • 软件定义网络

  • OpenStack 组件

  • 额外的 OpenStack 用例

  • 配置 OpenStack 环境

  • 将 OpenStack 与 Ansible 集成

  • 让我们开始吧!

介绍 OpenStack

根据其自己的说法,OpenStack是一个云操作系统,用于控制大量不同的资源,以提供基础设施即服务IaaS)和编排的所有基本服务。

但这意味着什么?OpenStack 旨在完全控制数据中心中的所有资源,并提供对可以用于部署其自身和第三方服务的任何资源的集中管理和直接控制。基本上,对于我们在本书中提到的每项服务,在整个 OpenStack 景观中都有一个可以使用该服务的地方。

OpenStack 本身由几个不同的相互连接的服务或服务部分组成,每个都有自己的功能集,每个都有自己的 API,可以完全控制该服务。在本书的这一部分,我们将尝试解释 OpenStack 的不同部分的功能,它们如何相互连接,它们提供的服务以及如何利用这些服务来获得优势。

OpenStack 存在的原因是因为需要一个开源的云计算平台,可以创建独立于任何商业云平台的公共和私有云。OpenStack 的所有部分都是开源的,并且根据 Apache 许可证 2.0 发布。该软件是由大型混合个人和大型云提供商创建的。有趣的是,第一个主要版本的发布是 NASA(美国政府机构)和 Rackspace Technology(美国大型托管公司)合并其内部存储和计算基础设施解决方案的结果。这些发布后来被指定为 Nova 和 Swift,并且我们将在后面更详细地介绍它们。

你将注意到关于 OpenStack 的第一件事是它的服务,因为没有单一的OpenStack服务,而是一整套服务。名称OpenStack直接来自这个概念,因为它正确地将 OpenStack 识别为一个作为服务的开源组件,这些服务又被分组成功能集。

一旦我们了解到我们正在谈论自主服务,我们还需要了解 OpenStack 中的服务是按其功能分组的,并且某些功能下有不止一个专门的服务。我们将尽量在本章中涵盖尽可能多的不同服务,但是其中有太多的服务,甚至无法在这里提及所有。所有文档和白皮书都可以在openstack.org找到,我们强烈建议您查阅其中任何未在此处提及的内容,甚至对我们提到但在您阅读时可能已经发生变化的内容也要查阅。

我们需要澄清的最后一件事是命名——OpenStack 中的每个服务都有其项目名称,并且在文档中以该名称来引用。乍一看,这可能看起来令人困惑,因为其中一些名称与特定服务在整个项目中的具体功能完全无关,但一旦你开始使用 OpenStack,使用名称而不是官方指示符来表示功能会更容易。例如,Swift。Swift 的全名是OpenStack 对象存储,但在文档或其实施中很少提到。其他服务或在 OpenStack 下的项目,如 Nova、Ironic、Neutron、Keystone 以及其他 20 多个不同的服务也是如此。

如果你暂时离开 OpenStack,那么你需要考虑云服务的本质。云服务的本质就是扩展——无论是计算资源、存储、网络、API 等。但是,就像生活中的一切一样,随着事物的扩展,你会遇到问题。这些问题有它们自己的名称解决方案。所以,让我们讨论一下这些问题。

云服务提供商可扩展性的基本问题可以分为三组需要大规模解决的问题:

  • 计算问题(计算=CPU+内存能力):这些问题解决起来相当简单——如果你需要更多的 CPU 和内存能力,你就购买更多的服务器,这意味着更多的 CPU 和内存。如果你需要服务质量/服务级别协议SLA)类型的概念,我们可以引入计算资源池的概念,这样我们可以根据需要切割计算并在我们的客户之间分配这些资源。无论我们的客户是私人还是购买云服务的公司,这都无关紧要。在云技术中,我们称我们的客户为租户

  • 存储问题:随着云环境的扩展,存储容量、管理、监控以及性能方面变得非常混乱。性能问题有一些最常用的变量——读写吞吐量和读写 IOPS。当你将环境从 100 个主机扩展到 1,000 个或更多时,性能瓶颈将成为一个难以解决的主要问题。因此,存储问题可以通过增加额外的存储设备和容量来解决,但它比计算问题更复杂,需要更多的配置和资金。记住,每个虚拟机对其他虚拟机的性能有统计上的影响,虚拟机越多,这种熵就越大。这是存储基础设施中最难管理的过程。

  • A无法与租户B的网络流量通信。与此同时,你仍然需要提供一个能够让租户拥有多个网络(通常在非云基础设施中通过 VLAN 实现)并在这些网络之间进行路由的能力,如果这是租户需要的话。

这个网络问题是一个基于技术的可扩展性问题,因为 VLAN 背后的技术在 VLAN 数量成为可扩展性问题之前已经标准化了多年。

让我们继续通过解释云环境的最基本主题来了解 OpenStack,即通过软件定义网络SDN)扩展云网络。这样做的原因非常简单——没有 SDN 概念,云对于客户来说就不够可扩展,这将是一个完全的停滞。所以,系好你的安全带,让我们进行 SDN 入门。

软件定义网络

云的一个直接的故事——至少表面上是这样——应该是关于云网络的故事。为了理解这个故事应该有多简单,我们只需要看一个数字,那个数字就是VLAN 1

所以,基本上,在现实场景中,我们剩下了 4,093 个单独的逻辑网络,这可能已经足够用于任何公司的内部基础设施。然而,对于公共云提供商来说,这远远不够。同样的问题也适用于使用混合云类型服务的公共云提供商,例如将他们的计算能力扩展到云端。

所以,让我们稍微关注一下这个网络问题。从云用户的角度来看,数据隐私对我们来说至关重要。如果从云提供商的角度来看这个问题,那么我们希望我们的网络隔离问题对我们的租户来说不成问题。这就是云服务在更基本层面上的全部意义——无论技术背景的复杂性如何,用户都必须能够以尽可能用户友好的方式访问所有必要的服务。让我们通过一个例子来解释这一点。

如果我们的公共云环境中有 5,000 个不同的客户(租户)会发生什么?如果每个租户都需要拥有五个或更多的逻辑网络会发生什么?我们很快意识到我们有一个大问题,因为云环境需要被分隔、隔离和围栏起来。出于安全和隐私原因,它们需要在网络层面相互分隔。然而,如果租户需要那种服务,它们也需要可路由。除此之外,我们需要能够扩展,以便在需要超过 5,000 或 50,000 个隔离网络的情况下不会困扰我们。再次回到我们之前的观点——大约 4,000 个 VLAN 根本不够用。

我们之所以说这应该是一个简单的故事,是有原因的。我们中的工程师们将这些情况看得很黑白分明——我们专注于问题并试图找到解决方案。解决方案似乎相当简单——我们需要扩展 12 位 VLAN ID 字段,以便我们可以拥有更多可用的逻辑网络。这有多难呢?

事实证明,这是非常困难的。如果历史教会我们任何东西,那就是各种不同的利益、公司和技术会在 IT 技术方面争夺多年的顶级地位。只需想想 DVD+R、DVD-R、DVD+RW、DVD-RW、DVD-RAM 等旧日的情形。简化一下,当云网络的初始标准被引入时,同样的情况也发生了。我们通常将这些网络技术称为云覆盖网络技术。这些技术是 SDN 的基础,它描述了云网络在全球、集中管理层面上的工作方式。市场上有多种标准来解决这个问题——VXLAN、GRE、STT、NVGRE、NVO3 等等。

实际上,没有必要一一解释它们。我们将采取更简单的方式——我们将描述其中一个在今天环境中最有价值的标准(VXLAN),然后转向被认为是明天统一标准的东西(GENEVE)。

首先,让我们定义什么是叠加网络。当我们谈论叠加网络时,我们指的是建立在同一基础设施中另一个网络之上的网络。叠加网络背后的想法很简单 - 我们需要将网络的物理部分与逻辑部分分开。如果我们想要以绝对方式进行配置(在 CLI 中配置物理交换机、路由器等,而不需要花费大量时间),我们也可以这样做。如果我们不想以这种方式做,而且仍然希望直接与我们的物理网络环境一起工作,我们需要在整体方案中添加一层可编程性。然后,如果我们愿意,我们可以与我们的物理设备进行交互,并向它们推送网络配置,以实现更自上而下的方法。如果我们以这种方式做事情,我们将需要更多来自我们的硬件设备的支持,以满足能力和兼容性方面的要求。

现在我们已经描述了什么是网络叠加,让我们谈谈 VXLAN,这是最重要的叠加网络标准之一。它还作为开发其他网络叠加标准(如 GENEVE)的基础,因此 - 您可能会想象到 - 了解其工作原理非常重要。

了解 VXLAN

让我们从令人困惑的部分开始。VXLAN(IETF RFC 7348)是一种可扩展的叠加网络标准,使我们能够在 Layer 3 网络中聚合和隧道多个 Layer 2 网络。它是如何做到的呢?通过在 Layer 3 数据包内封装 Layer 2 数据包。在传输协议方面,默认情况下使用 UDP,在端口4789上(稍后会详细介绍)。在 VXLAN 实现的特殊请求方面 - 只要您的物理网络支持 MTU 1600,您就可以轻松实现 VXLAN 作为云叠加解决方案。您可以购买的几乎所有交换机(除了廉价的家用交换机,但我们在这里谈论的是企业)都支持巨型帧,这意味着我们可以使用 MTU 9000 并完成。

从封装的角度来看,让我们看看它是什么样子的:

图 12.1 – VXLAN 帧封装

图 12.1 – VXLAN 帧封装

更简单地说,VXLAN 使用两个 VXLAN 端点(称为 VTEP;即 VXLAN 隧道端点)之间的隧道,检查VXLAN 网络标识符VNIs),以便它们可以决定数据包的去向。

如果这看起来很复杂,那就不用担心 - 我们可以简化这个过程。从 VXLAN 的角度来看,VNI 与 VLAN ID 对 VLAN 的作用是一样的。它是一个唯一的网络标识符。区别只是大小 - VNI 字段有 24 位,而 VLAN 有 12 位。这意味着我们有 2²⁴ 个 VNI,而 VLAN 有 2¹² 个。因此,就网络隔离而言,VXLAN 是 VLAN 的平方。

为什么 VXLAN 使用 UDP?

在设计叠加网络时,通常希望尽量减少延迟。此外,您不希望引入任何形式的开销。当您考虑这两个基本设计原则,并将其与 VXLAN 隧道 Layer 2 流量封装在 Layer 3 内(无论流量是单播、组播还是广播)的事实相结合时,这实际上意味着我们应该使用 UDP。无论如何,TCP 的两种方法 - 三次握手和重传 - 都会妨碍这些基本设计原则。简而言之,TCP 对于 VXLAN 来说太复杂了,因为这意味着在规模上会产生太多的开销和延迟。

就 VTEP 而言,只需将它们想象成两个接口(以软件或硬件实现),可以根据 VNI 封装和解封流量。从技术角度来看,VTEP 将各种租户的虚拟机和设备映射到 VXLAN 段(由 VXLAN 支持的隔离网络),执行包检查,并根据 VNI 封装/解封网络流量。让我们借助以下图表描述这种通信:

图 12.2-单播模式下的 VTEP

在我们基于开源的云基础设施中,我们将使用 OpenStack Neutron 或 Open vSwitch 来实现云覆盖网络,后者是一个免费的开源分布式交换机,支持几乎所有你能想到的网络协议,包括前面提到的 VXLAN、STT、GENEVE 和 GRE 覆盖网络。

此外,在云网络中有一种绅士协议,即在大多数情况下不使用 VXLANs 从1-4999。这样做的原因很简单-因为我们仍然希望以简单且不易出错的方式保留 VLAN 的保留范围为0-4095。换句话说,按设计,我们将网络 ID 0-4095 留给 VLAN,并以 VNI 5000 开始 VXLAN,这样很容易区分两者。在 1670 万个 VXLAN 支持的网络中,不使用 5000 个 VXLAN 支持的网络并不是为了良好的工程实践而做出的太大牺牲。

VXLAN 的简单性、可扩展性和可扩展性也意味着更多真正有用的使用模型,例如以下内容:

  • 在站点之间拉伸第 2 层:这是关于云网络的最常见问题之一,我们将很快描述。

  • 第 2 层桥接:将 VLAN 桥接到云覆盖网络(如 VXLAN)在我们将用户引入我们的云服务时非常有用,因为他们可以直接连接到我们的云网络。此外,当我们想要将硬件设备(例如物理数据库服务器或物理设备)插入 VXLAN 时,这种使用模型也被广泛使用。如果没有第 2 层桥接,想象一下我们将会有多么痛苦。我们所有运行 Oracle 数据库设备的客户将无法将他们的物理服务器连接到我们基于云的基础设施。

  • 各种卸载技术:包括负载平衡、防病毒、漏洞和恶意软件扫描、防火墙、IDS、IPS 集成等。所有这些技术使我们能够拥有简单管理概念的有用、安全的环境。

我们提到在站点之间拉伸第 2 层是一个基本问题,因此很明显我们需要讨论它。我们将在下一节讨论。如果没有解决这个问题的方法,你几乎没有机会有效地创建多个数据中心的云基础设施。

在站点之间拉伸第 2 层

云提供商面临的最常见一组问题之一是如何在站点或大陆之间扩展其环境。在过去,当我们没有诸如 VXLAN 之类的概念时,我们被迫使用某种第 2 层 VPN 或基于 MPLS 的技术。这些类型的服务非常昂贵,有时,我们的服务提供商并不完全满意我们的给我 MPLS给我第 2 层访问的要求。如果我们在同一句中提到组播这个词,他们会更不高兴,这在过去是一组经常使用的技术标准。因此,具备通过第 3 层传输第 2 层的能力从根本上改变了这种对话。基本上,如果你有能力在站点之间创建基于第 3 层的 VPN(你几乎总是可以做到的),你就不必再讨论这个问题。此外,这显著降低了这些类型基础设施连接的价格。

考虑以下基于组播的示例:

图 12.3-在组播模式下跨站扩展 VXLAN 段

图 12.3-在组播模式下跨站扩展 VXLAN 段

让我们假设这个图表的左侧是第一个站点,右侧是第二个站点。从VM1的角度来看,VM4在其他远程站点并不重要,因为它的段(VXLAN 5001)跨越这些站点。怎么做?只要底层主机可以通过 VXLAN 传输网络相互通信(通常也通过管理网络),第一个站点的 VTEP 可以与第二个站点的 VTEP 进行通信。这意味着在一个站点由 VXLAN 段支持的虚拟机可以通过上述的第 2 层到第 3 层封装与另一个站点中相同的 VXLAN 段进行通信。这是解决一个复杂且昂贵问题的一个非常简单而优雅的方法。

我们提到,作为一种技术,VXLAN 作为开发其他标准的基础,其中最重要的是 GENEVE。随着大多数制造商朝着 GENEVE 兼容性迈进,VXLAN 将慢慢消失。让我们讨论一下 GENEVE 协议的目的,以及它如何成为云覆盖网络的标准

理解 GENEVE

我们之前提到的基本问题是,云覆盖网络中的历史重演了很多次。不同的标准,不同的固件,不同的制造商支持一种标准而不是另一种,所有这些标准都非常相似,但仍然不兼容。这就是为什么 VMware、微软、红帽和英特尔提出了 GENEVE,这是一个新的云覆盖标准,只定义封装数据格式,而不干涉这些技术的控制平面,这些技术在根本上是不同的。例如,VXLAN 使用 24 位字段宽度的 VNI,而 STT 使用 64 位。因此,GENEVE 标准不提出固定的字段大小,因为你不可能知道未来会发生什么。此外,从现有用户群的角度来看,我们仍然可以愉快地使用我们的 VXLAN,因为我们不认为它们会受到未来 GENEVE 部署的影响。

让我们看看 GENEVE 标头是什么样的:

图 12.4-GENEVE 云覆盖网络标头

图 12.4-GENEVE 云覆盖网络标头

GENEVE 的作者从其他一些标准(BGP、IS-IS 和 LLDP)中学到了一些东西,并认为做正确的事情的关键是可扩展性。这就是为什么它被 Linux 社区在 Open vSwitch 和 VMware 在 NSX-T 中采用。自 Windows Server 2016 以来,VXLAN 也被支持为Hyper-V 网络虚拟化HNV)的网络覆盖技术。总的来说,GENEVE 和 VXLAN 似乎是两种肯定会留下来的技术-从 OpenStack 的角度来看,两者都得到了很好的支持。

现在我们已经解决了云的最基本问题-云网络-我们可以回过头来讨论 OpenStack。具体来说,我们下一个主题与 OpenStack 组件有关-从 Nova 到 Glance,然后到 Swift,以及其他组件。所以,让我们开始吧。

OpenStack 组件

当 OpenStack 最初作为一个项目形成时,它是从两种不同的服务设计的:

  • 一个计算服务,旨在管理和运行虚拟机本身

  • 一个旨在进行大规模对象存储的存储服务

这些服务现在被称为 OpenStack Compute 或Nova,以及 OpenStack Object Store 或Swift。这些服务后来又加入了Glance或 OpenStack 镜像服务,旨在简化与磁盘映像的工作。此外,在我们的 SDN 入门之后,我们需要讨论 OpenStack Neutron,OpenStack 的网络即服务NaaS)组件。

以下图表显示了 OpenStack 的组件:

图 12.5-OpenStack 的概念架构(来源:https://docs.openstack.org/)

图 12.5-OpenStack 的概念架构(来源:https://docs.openstack.org/)

我们将按照没有特定顺序进行介绍,并包括其他重要的服务。让我们从Swift开始。

Swift

我们需要谈论的第一个服务是 Swift。为此,我们将从 OpenStack 官方文档中获取项目的定义,并解析它,以尝试解释这个项目实现了哪些服务,以及它的用途。Swift 网站 (docs.openstack.org/swift/latest/)中陈述了以下内容:

"Swift 是一个高度可用的、分布式的、最终一致的对象/大块存储。组织可以使用 Swift 高效、安全、廉价地存储大量数据。它专为规模而构建,并针对整个数据集的耐用性、可用性和并发性进行了优化。Swift 非常适合存储可以无限增长的非结构化数据。"

读完这段话后,我们需要指出一些可能对你来说完全新的事情。首先,我们谈论的是以一种特定的方式存储数据,这在计算中并不常见,除非你使用过非结构化数据存储。非结构化并不意味着这种存储数据的方式缺乏结构;在这个上下文中,它意味着我们定义数据的结构,但服务本身不关心我们的结构,而是依赖于对象的概念来存储我们的数据。这种方式的一个结果是,我们存储在 Swift 中的数据不能直接通过任何文件系统或我们习惯通过机器操作文件的其他方式直接访问。相反,我们是以对象的方式操作数据,必须使用 Swift 提供的 API 来获取数据对象。我们的数据存储在大块或对象中,系统本身只是标记和存储以确保可用性和访问速度。我们应该知道我们的数据的内部结构以及如何解析它。另一方面,由于这种方法,Swift 可以以惊人的速度处理任意数量的数据,并且以一种几乎不可能使用普通的经典数据库实现的方式进行水平扩展。

值得一提的是,这项服务提供了高度可用、分布式和最终一致的存储。这意味着,首先,优先级是数据分布和高可用性,这两点在云中非常重要。一致性在此之后,但最终会实现。一旦你开始使用这项服务,你就会明白这意味着什么。在几乎所有通常的情况下,数据被读取而很少被写入,这根本不值得考虑,但也有一些情况下,这可能改变我们需要思考如何提供服务的方式。文档中陈述了以下内容:

"因为对象存储中的每个副本都是独立运行的,客户端通常只需要大多数节点简单响应即可认为操作成功,瞬时故障如网络分区可能会迅速导致副本发散。这些差异最终由异步的点对点复制进程协调一致。复制进程遍历其本地文件系统,并以一种平衡负载的方式同时执行操作。"

我们可以粗略地翻译一下。假设你有一个三节点的 Swift 集群。在这种情况下,Swift 对象在PUT操作至少在两个节点上确认已完成后,才会对客户端可用。因此,如果你的目标是创建一个低延迟、同步的 Swift 存储复制,那么还有其他解决方案可供选择。

在搁置了有关 Swift 提供的所有抽象承诺之后,让我们进一步详细讨论一下。高可用性和分布是使用“区域”概念以及将相同数据写入多个存储服务器的直接结果。区域只是一种简单的逻辑划分我们可用的存储资源的方式,并决定我们愿意提供的隔离类型以及我们需要的冗余类型。我们可以按照服务器本身、机架、数据中心内的服务器集、跨不同数据中心的组以及任何这些的组合来对服务器进行分组。一切都取决于可用资源的数量以及我们需要和想要的数据冗余和可用性,当然还有伴随我们配置的成本。

根据我们拥有的资源,我们应该根据它将我们的存储系统配置为它将保存多少副本以及我们准备使用多少区域。Swift 中特定数据对象的副本被称为副本,目前,最佳实践要求至少在不少于五个区域中拥有至少三个副本。

区域可以是服务器或一组服务器,如果我们正确配置了一切,失去任何一个区域对数据的可用性或分布都不会产生影响。由于区域可以小到一个服务器,大到任意数量的数据中心,我们构建区域的方式对系统对任何故障和变化的反应有巨大影响。副本也是如此。在推荐的方案中,配置的副本数量比区域数量少,因此只有一些区域将持有这些副本。这意味着系统必须平衡数据的写入方式,以均匀分布数据和负载,包括数据的写入和读取负载。同时,我们构建区域的方式将对成本产生巨大影响-冗余在服务器和存储硬件方面具有实际成本,而增加副本和区域会对我们 OpenStack 安装需要分配多少存储和计算能力提出额外要求。能够正确做到这一点是数据中心架构师必须解决的最大问题。

现在,我们需要回到最终一致性的概念。在这个背景下,最终一致性意味着数据将被写入 Swift 存储,并且对象将被更新,但系统将无法完全同时将所有数据写入所有副本中。Swift 将尽快协调差异,并意识到这些变化,因此为尝试读取它们的任何人提供对象的新版本。由于系统的某些部分失败而导致数据不一致的情况存在,但它们应被视为系统的异常状态,并需要修复,而不是系统被设计为忽略它们。

Swift 守护程序

接下来,我们需要讨论 Swift 在架构方面的设计。数据通过三个独立的逻辑守护程序进行管理:

  • Swift-account用于管理包含所有定义的对象存储服务帐户的 SQL 数据库。它的主要任务是读取和写入所有其他服务需要的数据,主要是为了验证和查找适当的身份验证和其他数据。

  • Swift-container是另一个数据库进程,但它严格用于将数据映射到容器中,这是一种类似于 AWS buckets的逻辑结构。这可以包括任意数量的对象,它们被分组在一起。

  • Swift-object管理到实际对象的映射,并跟踪对象本身的位置和可用性。

所有这些守护程序都负责数据,并确保一切都被正确映射和复制。数据被架构的另一层使用:表示层。

当用户想要使用任何数据对象时,首先需要通过一个令牌进行身份验证,这个令牌可以是外部提供的,也可以是由 Swift 内部的身份验证系统创建的。之后,编排数据检索的主要过程是 Swift-proxy,它处理与处理数据的三个守护程序的通信。只要用户提供了有效的令牌,就可以将数据对象交付给用户请求。

这只是关于 Swift 工作原理的最简要的概述。为了理解这一点,你不仅需要阅读文档,还需要使用某种系统来执行低级对象的检索和存储到 Swift 中。

如果没有编排服务,云服务就无法扩展或高效使用,这就是为什么我们需要讨论我们列表上的下一个服务 - Nova

Nova

另一个重要的服务或项目是 Nova - 一个编排服务,用于在大规模上提供计算实例的供应和管理。它的基本作用是允许我们使用 API 结构直接分配、创建、重新配置和删除或销毁虚拟服务器。以下是 Nova 服务结构的逻辑图:

图 12.6 - Nova 服务的逻辑结构(openstack.org)

图 12.6 - Nova 服务的逻辑结构(openstack.org)

大部分 Nova 是一个非常复杂的分布式系统,几乎完全由 Python 编写,包括一些执行编排部分的工作脚本和一个接收和执行 API 调用的网关服务。API 也是基于 Python 的;它是一个Web 服务器网关接口WSGI)兼容的应用程序,用于处理调用。而 WSGI 则是定义 Web 应用程序和服务器应该如何交换数据和命令的标准。这意味着理论上,任何能够使用 WSGI 标准的系统也可以与这个服务建立通信。

除了这个多方面的编排解决方案,还有另外两个服务是 Nova 的核心 - 数据库和消息队列。这两者都不是基于 Python 的。我们将首先讨论消息传递和数据库。

几乎所有分布式系统都必须依赖队列来执行它们的任务。消息需要被转发到一个中央位置,这将使所有守护程序能够执行它们的任务,使用正确的消息传递和排队系统对于系统的速度和可靠性至关重要。Nova 目前使用 RabbitMQ,这是一个高度可扩展和可用的系统。使用这样一个生产就绪的系统意味着不仅有工具来调试系统本身,还有很多报告工具可用于直接查询消息队列。

使用消息队列的主要目的是完全解耦任何客户端和服务器,并在不同客户端之间提供异步通信。关于实际消息传递的工作有很多要说的,但在本章中,我们将只是引用官方文档docs.openstack.org/nova/latest/,因为我们不是在谈论服务器上的一些功能,而是一个完全独立的软件堆栈。

数据库负责保存当前正在执行的任务的所有状态数据,并使 API 能够返回有关 Nova 不同部分当前状态的信息。

总的来说,系统包括以下内容:

  • nova-api实际上是指这个守护程序,有时称其为 API、控制器或云控制器。我们需要更详细地解释一下 Nova,以便理解将 nova-api 称为控制器是错误的,但由于守护程序中存在一个名为 CloudController 的类,许多用户将这个守护程序误认为是整个分布式系统。

nova-api 是一个强大的系统,因为它可以自行处理和整理一些 API 调用,从数据库获取数据并找出需要做什么。在更常见的情况下,nova-api 将只是启动一个任务,并以消息的形式转发给 Nova 内的其他守护程序。

  • 另一个重要的守护程序是调度程序。它的主要功能是浏览队列,并确定特定请求应该在何时何地运行。这听起来足够简单,但鉴于系统可能的复杂性,这个“何时何地”可能导致性能的极端增益或损失。为了解决这个问题,我们可以选择调度程序在选择执行请求的正确位置时如何做出决策。用户可以选择编写自己的请求,也可以使用预定的请求。

如果我们选择 Nova 提供的存储卷,我们有三种选择:

a) 简单调度程序根据主机的负载确定请求将在哪里运行 - 它将监视所有主机,并尝试分配在特定时间片内负载最小的主机。

b) Chance是默认的调度方式。顾名思义,这是最简单的算法 - 从列表中随机选择一个主机并给出请求。

c) 区域调度也会随机选择主机,但是会在区域内进行选择。

现在,我们将看一下workers,实际执行请求的守护程序。这些守护程序有三个 - 网络、存储和计算。

  • nova-network负责网络。它将执行与网络相关的队列中的任何任务,并根据需要创建接口和规则。它还负责 IP 地址分配;它将分配固定和动态分配的地址,并处理外部和内部网络。实例通常使用一个或多个固定 IP 来实现管理和连接,这些通常是本地地址。还有浮动地址用于从外部进行连接。自 2016 年 OpenStack Newton 版本发布以来,这项服务已经过时,尽管在一些传统配置中仍然可以使用。

  • nova-volume 处理存储卷,或者更准确地说,处理数据存储与任何实例连接的所有方式。这包括诸如 iSCSI 和 AoE 之类的标准,这些标准旨在封装已知的常见协议,以及诸如 Sheepdog、LeftHand 和 RBD 之类的提供者,这些提供者涵盖了与开源和闭源存储系统(如 CEPH 或 HP LeftHand)的连接。

  • nova-compute必须适应不同的虚拟化技术和完全不同的平台。它还需要能够动态分配和释放资源。主要使用 libvirt 进行 VM 管理,直接支持 KVM 创建和删除新实例。这就是本章存在的原因,因为 nova-compute 使用 libvirt 启动 KVM 机器是配置 OpenStack 的最常见方式,但对不同技术的支持范围更广。libvirt 接口还支持 Xen、QEMU、LXC 和用户模式 Linux(UML),通过不同的 API,nova-compute 可以支持 Citrix、XCP、VMware ESX/ESXi vSphere 和 Microsoft Hyper-V。这使得 Nova 能够从一个中央 API 控制所有当前使用的企业虚拟化解决方案。

作为一个旁注,nova-conductor 用于处理需要对对象、调整大小和数据库/代理访问进行任何转换的请求。

我们列表中的下一个服务是Glance - 这是对虚拟机部署非常重要的服务,因为我们希望从图像中进行部署。现在让我们讨论一下 Glance。

Glance

起初,为云磁盘图像管理单独设置一个服务似乎没有多大意义,但是在扩展任何基础架构时,图像管理将成为需要 API 解决的问题。Glance 基本上具有这种双重身份-它可以用于直接操作 VM 图像并将它们存储在数据块中,但同时也可以用于在处理大量图像时完全自动地编排许多任务。

Glance 在内部结构方面相对简单,因为它包括图像信息数据库,使用 Swift(或类似服务)的图像存储以及将所有内容粘合在一起的 API。数据库有时被称为注册表,它基本上提供了有关给定图像的信息。图像本身可以存储在不同类型的存储器上,可以是来自 Swift(作为 blob)的 HTTP 服务器上或文件系统(如 NFS)上。

Glance 对其使用的图像存储类型完全不加限制,因此 NFS 是完全可以的,并且使得实施 OpenStack 变得更加容易,但是在扩展 OpenStack 时,可以使用 Swift 和 Amazon S3。

当考虑 Glance 在大型 OpenStack 拼图中所属的位置时,我们可以将其描述为 Nova 用来查找和实例化图像的服务。Glance 本身使用 Swift(或任何其他存储)来存储图像。由于我们处理多种架构,因此需要支持图像的许多不同文件格式,而 Glance 并不令人失望。每种受不同虚拟化引擎支持的磁盘格式都受到 Glance 的支持。这包括无结构格式,如raw和结构化格式,如 VHD、VMDK、qcow2、VDI ISO 和 AMI。例如,作为图像容器的 OVF 也受到支持。

Glance 可能拥有所有 API 中最简单的 API,使其甚至可以使用 curl 从命令行查询服务器,并使用 JSON 作为消息的格式。

我们将以一条小提示直接从 Nova 文档中结束本节:它明确指出 OpenStack 中的一切都设计为水平可扩展,但是在任何时候,计算节点的数量应该明显多于任何其他类型。这实际上是有道理的-计算节点负责接受并处理请求。您将需要的存储节点数量将取决于您的使用场景,而 Glance 的数量将不可避免地取决于 Swift 可用的功能和资源。

排队中的下一个服务是Horizon - 一个 OpenStack 的人类可读GUI 仪表板,我们在那里消耗大量的 OpenStack 可视信息。

地平线

在相当详细地解释了使 OpenStack 能够以某种方式做到它所做的核心服务之后,我们需要解决用户交互的问题。在本章的几乎每一段中,我们都提到 API 和脚本接口作为与 OpenStack 通信和编排的方式。虽然这完全是真实的,并且是管理大规模部署的常规方式,但 OpenStack 还具有一个非常有用的界面,可以作为浏览器中的 Web 服务使用。这个项目的名称是 Horizon,它的唯一目的是为用户提供一种与所有服务进行交互的方式,称为仪表板。用户还可以重新配置 OpenStack 安装中的大多数(如果不是全部)内容,包括安全性、网络、访问权限、用户、容器、卷以及 OpenStack 安装中存在的其他所有内容。

Horizon 还支持插件和可插拔面板。Horizon 有一个活跃的插件市场,旨在扩展其功能,甚至比它已经具有的功能更进一步。如果这对您的特定情况仍然不够,您可以使用 Angular 创建自己的插件,并让它们在 Horizon 中运行。

可插拔面板也是一个不错的主意 - 在不改变任何默认设置的情况下,用户或一组用户可以改变仪表板的外观,并获得更多(或更少)呈现给他们的信息。所有这些都需要一点编码;更改是在配置文件中进行的,但主要的是 Horizon 系统本身支持这样的定制模型。在我们讨论安装 OpenStack 和创建 OpenStack 实例的配置 OpenStack 环境部分时,您可以了解更多关于界面本身和用户可用的功能。

正如您所知,没有名称解析,网络实际上无法正常工作,这就是为什么 OpenStack 有一个名为Designate的服务。我们接下来会简要讨论 Designate。

指定

任何使用任何类型网络的系统都必须至少有某种形式的名称解析服务,如本地或远程 DNS 或类似的机制。

Designate 是一项服务,试图在 OpenStack 中整合DNSaaS概念。当连接到 Nova 和 Neutron 时,它将尝试保持有关所有主机和基础设施详细信息的最新记录。

云的另一个非常重要的方面是我们如何管理身份。为此,OpenStack 有一个名为Keystone的服务。我们接下来会讨论它的作用。

Keystone

身份管理在云计算中非常重要,因为在部署大规模基础设施时,不仅需要一种方式来扩展资源,还需要一种方式来扩展用户管理。简单的用户访问资源的列表不再是一个选择,主要是因为我们不再谈论简单的用户。相反,我们谈论包含成千上万用户的域,由组和角色分隔 - 我们谈论多种登录和提供身份验证和授权的方式。当然,这也可能涉及多种身份验证标准,以及多个专门的系统。

出于这些原因,用户管理是 OpenStack 中名为 Keystone 的一个独立项目/服务。

Keystone 支持简单的用户管理和用户、组和角色的创建,但它也支持 LDAP、Oauth、OpenID Connect、SAML 和 SQL 数据库身份验证,并且有自己的 API,可以支持用户管理的各种场景。Keystone 是一个独立的世界,在本书中,我们将把它视为一个简单的用户提供者。然而,它可以是更多,可能需要根据情况进行大量配置。好消息是,一旦安装好,你很少需要考虑 OpenStack 的这一部分。

我们列表上的下一个服务是Neutron,这是 OpenStack 中(云)网络的 API/后端。

Neutron

OpenStack Neutron 是一个基于 API 的服务,旨在提供一个简单且可扩展的云网络概念,作为 OpenStack 旧版本中称为Quantum服务的发展。在这项服务之前,网络是由 nova-network 管理的,正如我们提到的,这是一个已经过时的解决方案,而 Neutron 正是这一变化的原因。Neutron 与我们已经讨论过的一些服务集成 - Nova、Horizon 和 Keystone。作为一个独立的概念,我们可以部署 Neutron 到一个单独的服务器,然后就可以使用 Neutron API。这让人想起了 VMware 在 NSX 中使用 NSX Controller 概念的做法。

当我们部署 neutron-server 时,一个托管 API 的基于 Web 的服务会与 Neutron 插件后台连接,以便我们可以对我们的 Neutron 管理的云网络进行网络更改。在架构方面,它有以下服务:

  • 用于持久存储的数据库

  • neutron-server

  • 外部代理(插件)和驱动程序

在插件方面,它有很多,但这里是一个简短的列表:

  • Open vSwitch

  • Cisco UCS/Nexus

  • Brocade Neutron 插件

  • IBM SDN-VE

  • VMware NSX

  • Juniper OpenContrail

  • Linux bridging

  • ML2

  • 还有很多其他的

大多数这些插件名称都是逻辑的,所以你不会有任何问题理解它们的作用。但我们想特别提到其中一个插件,即Modular Layer 2ML2)插件。

通过使用 ML2 插件,OpenStack Neutron 可以连接到各种第 2 层后端 - VLAN、GRE、VXLAN 等。它还使 Neutron 摆脱了 Open vSwitch 和 Linux 桥插件作为其基本插件(现在已经过时)。这些插件被认为对于 Neutron 的模块化架构来说过于庞大,自 2013 年 Havana 发布以来,ML2 已完全取代了它们。今天,ML2 有许多基于供应商的插件用于集成。正如前面的列表所示,Arista、Cisco、Avaya、HP、IBM、Mellanox 和 VMware 都有基于 ML2 的 OpenStack 插件。

关于网络类别,Neutron 支持两种:

  • 提供者网络:由 OpenStack 管理员创建,用于物理级别的外部连接,通常由平面(未标记)或 VLAN(802.1q 标记)概念支持。这些网络是共享的,因为租户使用它们来访问他们的私有基础设施在混合云模型中或访问互联网。此外,这些网络描述了底层和覆盖网络的交互方式,以及它们的映射关系。

  • 租户网络自助服务网络项目网络:这些网络是由用户/租户及其管理员创建的,以便他们可以连接他们需要的任何形状或形式的虚拟资源和网络。这些网络是隔离的,通常由 GRE 或 VXLAN 等网络覆盖层支持,因为这就是租户网络的整个目的。

租户网络通常使用某种 SNAT 机制来访问外部网络,这项服务通常通过虚拟路由器实现。同样的概念也适用于其他云技术,如 VMware NSX-v 和 NSX-t,以及由网络控制器支持的 Microsoft Hyper-V SDN 技术。

在网络类型方面,Neutron 支持多种类型:

  • Local:允许我们在同一主机内进行通信。

  • Flat:未标记的虚拟网络。

  • VLAN:一个 802.1Q VLAN 标记的虚拟网络。

  • GRE,VXLAN,GENEVE:根据网络覆盖技术,我们选择这些网络后端。

现在我们已经介绍了 OpenStack 的使用模型、思想和服务,让我们讨论一下 OpenStack 可以被用于的其他方式。正如你所想象的,OpenStack - 作为它所是的东西 - 非常适合在许多非标准场景中使用。接下来我们将讨论这些非明显的场景。

其他 OpenStack 使用案例

OpenStack 在docs.openstack.org上有很多非常详细的文档。其中一个更有用的主题是架构和设计示例,它们都解释了使用场景和使用 OpenStack 基础设施解决特定场景的思想。当我们部署我们的测试 OpenStack 时,我们将讨论两种不同的边缘情况,但有些事情需要说一下关于配置和运行 OpenStack 安装。

OpenStack 是一个复杂的系统,不仅涵盖计算和存储,还涉及大量的网络和支持基础设施。当你意识到即使文档也被整齐地分成了管理、架构、运维、安全和虚拟机镜像指南时,你会首先注意到这一点。每个主题实际上都可以成为一本书的主题,指南涵盖的许多内容既是经验,又是最佳实践建议,也是基于最佳猜测的假设的一部分。

所有这些用例大致上有一些共同之处。首先,在设计云时,你必须尽早获取关于可能的负载和客户的所有信息,甚至在第一台服务器启动之前。这将使你能够计划不仅需要多少服务器,还有它们的位置、计算与存储节点的比例、网络拓扑、能源需求以及所有其他需要深思熟虑的事情,以创建一个可行的解决方案。

在部署 OpenStack 时,我们通常是出于以下三个原因之一部署大规模企业解决方案:

  • 测试和学习:也许我们需要学习如何配置新的安装,或者在接近生产系统之前需要测试新的计算节点。因此,我们需要一个小型的 OpenStack 环境,也许是一个单独的服务器,如果有需要的话可以扩展。在实践中,这个系统应该能够支持一个用户和几个实例。这些实例通常不会成为你关注的焦点;它们只是为了让你能够探索系统的所有其他功能而存在。部署这样的系统通常是使用本章描述的方式完成的——使用一个现成的脚本来安装和配置一切,这样我们就可以专注于我们实际正在处理的部分。

  • 我们需要一个暂存或预生产环境:通常,这意味着我们需要支持生产团队,让他们有一个安全的工作环境,或者我们正在尝试保留一个单独的测试环境,用于存储和运行实例,然后再推入生产环境。

拥有这样的环境是明确建议的,即使你还没有拥有它,因为它使你和你的团队能够在不担心破坏生产环境的情况下进行实验。缺点是,这种安装需要一个环境,必须为用户和他们的实例提供一些资源。这意味着我们不能只用一台服务器。相反,我们将不得不创建一个云,至少在某些部分,它要和生产环境一样强大。部署这样的安装基本上与生产部署相同,因为一旦它上线,从你的角度来看,这个环境将只是生产中的另一个系统。即使我们称其为预生产或测试,如果系统崩溃,你的用户必然会打电话抱怨。这与生产环境发生的情况相同;你必须计划停机时间,安排升级,并尽力使其尽可能地运行良好。

  • 用于生产:这种方式要求另一种方式——维护。在创建实际的生产云环境时,您需要设计良好,然后仔细监视系统以便能够应对问题。从用户的角度来看,云是一种灵活的东西,因为它们提供了扩展和简单的配置,但作为云管理员意味着您需要通过准备好备用资源来启用这些配置更改。同时,您需要注意您的设备、服务器、存储、网络和其他一切,以便能够在用户看到问题之前发现问题。交换机是否故障转移?计算节点是否都正常运行?由于故障而导致磁盘性能下降了吗?在一个精心配置的系统中,这些事情中的每一件都对用户几乎没有或没有影响,但如果我们在处理问题时不主动,复合错误很快就会导致系统崩溃。

在两种不同的场景中区分了单个服务器和完整安装之后,我们将两者都进行一遍。单个服务器将使用脚本手动完成,而多服务器将使用 Ansible playbooks 完成。

现在我们已经相当详细地介绍了 OpenStack,是时候开始使用它了。让我们从一些小事情开始(测试一个小环境),以便为生产环境提供一个常规的 OpenStack 环境,然后讨论如何将 OpenStack 与 Ansible 集成。在下一章中,当我们开始讨论将 KVM 扩展到 Amazon AWS 时,我们将重新讨论 OpenStack。

为 OpenStack 创建一个 Packstack 演示环境

如果您只需要一个概念验证POC),有一种非常简单的方法可以安装 OpenStack。我们将使用Packstack,因为这是最简单的方法。通过在 CentOS 7 上使用 Packstack 安装,您将能够在大约 15 分钟内配置 OpenStack。一切都始于一系列简单的命令:

yum update -y
yum install -y centos-release-openstack-train
yum update -y
yum install -y openstack-packstack
packstack --allinone

随着过程经历各种阶段,您将看到各种消息,例如以下消息,这些消息非常好,因为您可以实时查看发生的事情,具有相当不错的详细程度:

图 12.7 – 欣赏 Packstack 的安装详细程度

图 12.7 – 欣赏 Packstack 的安装详细程度

安装完成后,您将获得一个类似于此的报告屏幕:

图 12.8 – 成功的 Packstack 安装

图 12.8 – 成功的 Packstack 安装

安装程序已成功完成,并且它向我们发出了关于NetworkManager和内核更新的警告,这意味着我们需要重新启动系统。重新启动并检查/root/keystonerc_admin文件以获取我们的用户名和密码后,Packstack 已经准备就绪,我们可以通过使用上一个屏幕输出中提到的 URL(http://IP_or_hostname_where_PackStack_is_deployed/dashboard)进行登录:

图 12.9 – Packstack UI

图 12.9 – Packstack UI

还有一些额外的配置需要完成,如 Packstack 文档中所述wiki.openstack.org/wiki/Packstack。如果您要使用外部网络,您需要一个没有NetworkManager的静态 IP 地址,并且您可能要配置firewalld或完全停止它。除此之外,您可以开始将其用作演示环境。

为 OpenStack 环境进行配置

在创建第一个 OpenStack 配置时,最简单且最困难的任务之一将是配置。基本上有两种方法可以选择:一种是在精心准备的硬件配置中逐个安装服务,而另一种是只需使用 OpenStack 网站上的单服务器安装指南,创建一台作为测试平台的单台机器。在本章中,我们所做的一切都是在这样的实例中创建的,但在学习如何安装系统之前,我们需要了解其中的区别。

OpenStack 是一个云操作系统,其主要思想是使我们能够使用多台服务器和其他设备创建一个一致、易于配置的云,可以通过 API 或 Web 服务器从中心点进行管理。OpenStack 的部署规模和类型可以从运行所有内容的单个服务器到跨多个数据中心集成的数千台服务器和存储单元。OpenStack 在大规模部署方面没有问题;通常唯一的限制因素是我们尝试创建的环境的成本和其他要求。

我们多次提到了可扩展性,这正是 OpenStack 在两方面的闪光之处。令人惊奇的是,它不仅可以轻松扩展,而且还可以缩小规模。一个适用于单个用户的安装可以在单台机器上完成 - 甚至在单台机器内的单个虚拟机上完成 - 因此您将能够在笔记本电脑的虚拟环境中拥有自己的云。这对于测试很好,但其他用途不大。

在创建工作、可扩展的云时,遵循特定角色和服务的指南和推荐配置要求是前进的唯一途径,显然这也是在需要创建生产环境时的最佳选择。话虽如此,在单台机器和一千台服务器的安装之间,有很多方式可以塑造和重新设计您的基础架构,以支持您特定的用例场景。

让我们首先快速地在另一个虚拟机内进行安装,这是可以在更快的主机上在 10 分钟内完成的任务。对于我们的平台,我们决定安装 Ubuntu 18.04.3 LTS,以便将主机系统保持到最小。关于我们尝试做的事情,Ubuntu 的整个指南都可以在docs.openstack.org/devstack/latest/guides/single-machine.html上找到。

我们必须指出的一件事是,OpenStack 网站提供了一些不同安装方案的指南,无论是在虚拟还是裸机硬件上,它们都非常容易遵循,因为文档直截了当。一旦您手动完成了一些步骤,还有一个简单的安装脚本可以处理一切。

硬件要求要小心。有一些很好的资源可供参考。从这里开始:docs.openstack.org/newton/install-guide-rdo/overview.html#figure-hwreqs

逐步安装 OpenStack

我们需要做的第一件事是创建一个将安装整个系统的用户。由于许多事情都需要系统范围的权限,这个用户需要具有sudo权限。

以 root 用户或通过sudo创建用户:

useradd -s /bin/bash -d /opt/stack -m stack
chmod 755 /opt/stack

接下来我们需要做的是允许这个用户使用sudo

echo "stack ALL=(ALL) NOPASSWD: ALL" >> /etc/sudoers

我们还需要安装git并切换到我们新创建的用户:

图 12.10 - 安装 git,部署 OpenStack 的第一步

图 12.10 - 安装 git,部署 OpenStack 的第一步

现在是有趣的部分。我们将克隆(复制最新版本的)devstack,这是安装脚本,将为我们提供在此机器上运行和使用 OpenStack 所需的一切:

图 12.11 - 使用 git 克隆 devstack

图 12.11 - 使用 git 克隆 devstack

现在需要一点配置。在我们刚刚克隆的目录中的samples目录中,有一个名为local.conf的文件。使用它来配置安装程序需要的所有内容。网络是必须手动配置的一件事 - 不仅是本地网络,它是将您连接到互联网的网络,还有内部网络地址空间,它将用于 OpenStack 实例之间需要执行的所有操作。还需要设置不同服务的不同密码。所有这些都可以在示例文件中阅读到。关于如何精确配置这些的说明既可以在我们之前给出的网址上找到,也可以在文件本身中找到:

图 12.12 - 安装程序配置

图 12.12 - 安装程序配置

安装过程中可能会出现一些问题,因此由于以下原因,安装可能会出现两次中断:

  • /opt/stack/.cache的所有权是root:root,而不是stack:stack。在运行安装程序之前,请更正此所有权;

  • 安装程序问题(已知问题),因为它无法安装一个组件然后失败。解决方案相当简单 - 需要更改 inc 目录中名为 python 的文件中的一行。在撰写本文时,该文件的第 192 行需要从$cmd_pip $upgrade \更改为$cmd_pip $upgrade --ignore-installed \

最后,在收集了所有数据并修改了文件之后,我们确定了这个配置:

图 12.13 - 示例配置

图 12.13 - 示例配置

这些参数中大多数是可以理解的,但让我们先讨论其中的两个:FLOATING_RANGEFIXED_RANGEFLOATING_RANGE参数告诉我们的 OpenStack 安装将使用哪个网络范围用于私有网络。另一方面,FIXED_RANGE是 OpenStack 配置的虚拟机将使用的网络范围。基本上,在 OpenStack 环境中配置的虚拟机将从FIXED_RANGE获得内部地址。如果虚拟机也需要从外部世界可用,我们将从FLOATING_RANGE分配一个网络地址。请注意FIXED_RANGE,因为它不应该与您的环境中现有的网络范围匹配。

我们从指南中所给出的内容中做出的一个改变是将 Swift 安装中的副本数减少到一个。这样做会使我们失去冗余,但会减少存储空间并加快速度。在生产环境中不要这样做。

根据您的配置,您可能还需要在文件中设置HOST_IP地址变量。在这里,将其设置为您当前的 IP 地址。

然后运行./stack.sh

运行脚本后,一个非常冗长的安装过程将开始,并在屏幕上输出大量行。等待它完成 - 这将需要一段时间并从互联网下载大量文件。最后,它将给出一个安装摘要,看起来像这样:

图 12.14 - 安装摘要

图 12.14 - 安装摘要

完成后,如果一切正常,您应该在本地机器上拥有一个完整运行的 OpenStack 版本。为了验证这一点,使用 Web 浏览器连接到您的机器;应该出现一个欢迎界面:

图 12.15 - OpenStack 登录界面

图 12.15 - OpenStack 登录界面

使用安装后您机器上写的凭据登录(默认管理员名称是admin,密码是在安装服务时在local.conf中设置的密码),您将会看到一个显示云统计信息的屏幕。您看到的屏幕实际上是一个 Horizon 仪表板,是提供您一目了然的有关云的所有信息的主要屏幕。

OpenStack 管理

查看 Horizon 左上角,我们可以看到默认配置的三个不同部分。第一个 - 项目 - 包括有关默认实例及其性能的所有内容。在这里,您可以创建新实例、管理图像,并处理服务器组。我们的云只是一个核心安装,所以我们只有一个服务器和两个定义的区域,这意味着我们没有安装服务器组:

图 12.16 – 基本的 Horizon 仪表板

图 12.16 – 基本的 Horizon 仪表板

首先,让我们创建一个快速实例来展示如何完成这个过程:

图 12.17 – 创建实例

图 12.17 – 创建实例

按照以下步骤创建一个实例:

  1. 转到屏幕的最右侧的启动实例。将打开一个窗口,让您可以为 OpenStack 提供创建新 VM 实例所需的所有信息:图 12.18 – 启动实例向导

图 12.18 – 启动实例向导

  1. 在下一个屏幕上,您需要提供图像来源。我们已经提到 glances - 这些图像来自 Glance 存储,并且可以是图像快照、现成的卷,或卷快照。如果需要的话,我们也可以创建一个持久图像。您会注意到,与几乎任何其他部署过程相比,有两个不同之处。首先,我们默认使用现成的图像,因为已经为我们提供了一个。另一个重要的事情是能够创建一个新的持久卷来存储我们的数据,或者在完成图像后将其删除,或者根本不创建它。

选择您在公共存储库中分配的一个图像;它应该与以下截图中显示的类似。CirrOS 是 OpenStack 提供的一个测试图像。它是一个被设计为尽可能小并且能够轻松测试整个云基础架构的最小 Linux 发行版,但尽可能不显眼。CirrOS 基本上是一个操作系统占位符。当然,我们需要点击启动实例按钮进入下一步:

图 12.19 – 选择实例来源

图 12.19 – 选择实例来源

  1. 创建新镜像的下一个重要部分是选择规格。这是 OpenStack 中另一个奇怪命名的东西。规格是某些资源的组合,基本上为新实例创建了一个计算、内存和存储模板。我们可以选择从具有最少 64MB RAM 和 1 vCPU 的实例开始,一直到我们的基础设施可以提供的程度:图 12.20 – 选择实例规格

图 12.20 – 选择实例规格

在这个特定的示例中,我们将选择cirros256,这是一种基本上设计为为我们的测试系统提供尽可能少的资源的规格。

  1. 我们实际上需要选择的最后一件事是网络连接。我们需要设置实例在运行时可以使用的所有适配器。由于这是一个简单的测试,我们将使用我们拥有的两个适配器,即内部和外部适配器。它们被称为publicshared

图 12.21 – 实例网络配置

图 12.21 – 实例网络配置

现在,我们可以启动我们的实例,它将能够引导。一旦您点击启动实例按钮,创建新实例可能需要不到一分钟。在实例部署过程中,显示其当前进度和实例状态的屏幕将自动更新。

一旦完成,我们的实例将准备就绪:

图 12.22 - 实例已准备好

图 12.22 - 实例已准备好

我们将快速创建另一个实例,然后创建一个快照,以便向您展示镜像管理的工作原理。如果您点击实例列表右侧的创建快照按钮,Horizon 将创建一个快照,并立即将您放入用于镜像管理的界面:

图 12.23 - 图像

图 12.23 - 图像

现在,我们有两个不同的快照:一个是启动镜像,另一个是正在运行的镜像的实际快照。到目前为止,一切都很简单。那么从快照创建实例呢?只需点击一下!您需要做的就是点击右侧的启动实例按钮,然后按照创建新实例的向导进行操作。

我们短暂示例的实例创建的最终结果应该是这样的:

图 12.24 - 新实例创建完成

图 12.24 - 新实例创建完成

我们可以看到有关我们实例的所有信息,它们的 IP 地址是什么,它们的规格(这转化为为特定实例分配了多少资源),镜像正在运行的可用区域,以及当前实例状态的信息。我们接下来要检查的是左侧的选项卡。当我们创建实例时,我们告诉 OpenStack 为第一个实例创建一个永久卷。如果我们现在点击,我们应该会看到卷在一个数字名称下:

图 12.25 - 卷

图 12.25 - 卷

从这个屏幕上,我们现在可以对卷进行快照,重新连接到不同的实例,甚至将其上传为镜像到存储库。

左侧的第三个选项卡名为网络,包含有关当前配置设置的更多信息。

如果我们点击网络拓扑选项卡,我们将得到当前运行网络的整个网络拓扑,显示在简单的图形显示中。我们可以选择拓扑图形,两者基本上代表相同的东西:

图 12.26 - 网络拓扑

图 12.26 - 网络拓扑

如果我们需要创建另一个网络或更改网络矩阵中的任何内容,我们可以在这里进行。我们认为这真的很适合管理员使用,而且也很适合文档使用。这两点使我们下一个主题 - 日常管理 - 变得更加容易。

日常管理

我们或多或少已经完成了与项目数据中心日常任务管理有关的最重要选项。如果我们点击名为管理的选项卡,我们会注意到我们打开的菜单结构看起来很像项目下的菜单结构。这是因为现在我们正在查看与云基础设施有关的管理任务,而不是与我们特定逻辑数据中心的基础设施有关,但这两者都存在相同的构建模块。然而,如果我们 - 例如 - 打开计算,会出现完全不同的选项集:

图 12.27 - 不同的可用配置选项

图 12.27 - 不同的可用配置选项

界面的这一部分用于完全管理构成我们基础设施的部分,以及定义我们在数据中心工作时可以使用的不同事物。当以用户身份登录时,我们可以添加和删除虚拟机,配置网络,并使用资源,但要上线资源,添加新的 hypervisors,定义规格,以及执行这些完全改变基础设施的任务,我们需要被分配管理角色。一些功能重叠,例如界面的管理部分和用户特定部分,它们都可以控制实例。然而,管理部分具有所有这些功能,用户可以调整其一组命令,例如无法删除或创建新实例。

管理视图使我们能够更直接地监视我们的节点,不仅通过它们提供的服务,还可以通过关于特定主机和其上使用的资源的原始数据:

图 12.28 - 数据中心中可用的 hypervisors

图 12.28 - 数据中心中可用的 hypervisors

我们的数据中心只有一个 hypervisor,但我们可以看到其上物理可用资源的数量,以及当前设置在这一特定时刻使用的这些资源的份额。

规格也是 OpenStack 整体的重要组成部分。我们已经提到它们是预定义的资源预设集,形成实例将在其上运行的平台。我们的测试设置已经定义了一些规格,但我们可以删除此设置中提供的规格,并创建符合我们需求的新规格。

由于云的目的是优化资源管理,规格在这一概念中起着重要作用。在规划和设计方面,创建规格并不是一项容易的任务。首先,它需要深入了解在给定的硬件平台上可能发生的事情,甚至存在多少和什么样的计算资源,以及如何充分利用它。因此,我们需要妥善规划和设计。另一件事是,我们实际上需要了解我们正在为何种负载做准备。它是内存密集型的吗?我们是否有许多需要简单配置的节点的小服务?我们是否需要大量的计算能力和/或大量的存储?这些问题的答案不仅能让我们创建客户想要的东西,还能创建让用户充分利用我们基础设施的规格。

基本思想是创建规格,为个别用户提供足够的资源,以满足其工作需求。在拥有 10 个实例的部署中,这并不明显,但一旦我们遇到成千上万个实例,一个总是留下 10%存储空间未使用的规格将迅速消耗我们的资源,并限制我们为更多用户提供服务的能力。在规划和设计我们的环境中,找到我们拥有的资源和我们以特定方式提供给用户使用之间的平衡可能是最困难的任务:

图 12.29 - 创建规格向导

图 12.29 - 创建规格向导

创建规格是一项简单的任务。我们需要做以下事情:

  1. 给它一个名称;ID 将自动分配。

  2. 为我们的规格设置 vCPU 和 RAM 的数量。

  3. 选择基本磁盘的大小,以及一个临时磁盘,不包括在任何快照中,并在虚拟机终止时被删除。

  4. 选择交换空间的大小。

  5. 选择 RX/TX 因子,以便我们可以在网络级别创建一些 QoS。一些规格将需要比其他规格更高的网络流量优先级。

OpenStack 允许一个项目拥有多个规格,并且一个规格属于不同的项目。现在我们已经了解了这一点,让我们与用户身份一起工作,并为他们分配一些对象。

身份管理

左侧最后一个选项卡是身份,负责处理用户、角色和项目。在这里,我们不仅要配置我们的用户名,还要配置用户角色、组和用户可以使用的项目:

图 12.30 - 用户、组和角色管理

图 12.30 - 用户、组和角色管理

我们不会深入讨论用户是如何管理和安装的,只是涵盖用户管理的基础知识。与往常一样,OpenStack 网站上的原始文档是学习更多知识的好地方。确保您查看此链接:docs.openstack.org/keystone/pike/admin/cli-manage-projects-users-and-roles.html

简而言之,一旦您创建了一个项目,您需要定义哪些用户能够查看和处理特定项目。为了简化管理,用户也可以成为组的一部分,然后您可以将整个组分配给一个项目。

这种结构的重点是使管理员不仅限制用户可以管理的内容,还限制特定项目可用的资源数量。让我们举个例子。如果我们转到项目并编辑一个现有项目(或创建一个新项目),我们将在配置菜单中看到一个名为配额的选项卡,它看起来像这样:

图 12.31 - 默认项目的配额

图 12.31 - 默认项目的配额

一旦您创建了一个项目,您可以将所有资源分配为配额的形式。这种分配限制了特定实例组的最大可用资源。用户无法查看整个系统;他们只能看到和利用通过项目可用的资源。如果用户是多个项目的一部分,他们可以根据其在项目中的角色创建、删除和管理实例,并且他们可用的资源是特定于项目的。

接下来,我们将讨论 OpenStack/Ansible 集成,以及这两个概念共同工作的一些潜在用例。请记住,OpenStack 环境越大,我们将为它们找到的用例就越多。

将 OpenStack 与 Ansible 集成

处理任何大规模应用程序都不容易,如果没有正确的工具,可能会变得不可能。OpenStack 为我们提供了许多直接编排和管理大规模水平部署的方式,但有时这还不够。幸运的是,在我们的工具库中,我们还有另一个工具 - Ansible。在第十一章用于编排和自动化的 Ansible中,我们介绍了一些其他使用 Ansible 部署和配置单个机器的较小方式,因此我们不会回到那里。相反,我们将专注于 Ansible 在 OpenStack 环境中的优势。

有一件事我们必须搞清楚,那就是在 OpenStack 环境中使用 Ansible 可以基于两种非常不同的场景。一种是使用 Ansible 来处理部署的实例,这在所有其他云或裸金属部署中看起来几乎是一样的。作为大量实例的管理员,您创建一个管理节点,它只是一个启用 Python 的服务器,添加了 Ansible 软件包和 playbooks。之后,您整理部署清单,准备好管理您的实例。这种情况不是本章的重点。

我们在这里讨论的是使用 Ansible 来管理云本身。这意味着我们不是在 OpenStack 云内部部署实例;我们是为 OpenStack 本身部署计算和存储节点。

我们所说的环境有时被称为OpenStack-AnsibleOSA),并且足够常见,以至于有自己的部署指南,位于以下 URL:docs.openstack.org/project-deploy-guide/openstack-ansible/latest/

在 OpenStack-Ansible 中,最小安装的要求要比单个 VM 或单台机器上的要求大得多。这不仅是因为系统需要所有资源;还有需要使用的工具和背后的哲学。

让我们快速了解 Ansible 在 OpenStack 方面的含义:

  • 一旦配置完成,它就能快速部署任何类型的资源,无论是存储还是计算。

  • 它确保您在过程中没有忘记配置某些内容。在部署单个服务器时,您必须确保一切正常,并且配置错误易于发现,但在部署多个节点时,错误可能会潜入并降低系统的性能,而没有人注意到。避免这种情况的正常部署实践是安装清单,但 Ansible 比那更好。

  • 更简化的配置更改。有时,我们需要对整个系统或其中的一部分应用配置更改。如果没有脚本化,这可能会很令人沮丧。

因此,说了这么多,让我们快速浏览一下docs.openstack.org/openstack-ansible/latest/,看看官方文档对如何部署和使用 Ansible 和 OpenStack 的建议。

OpenStack 在 Ansible 方面为管理员提供了什么?最简单的答案是 playbooks 和 roles。

要使用 Ansible 部署 OpenStack,基本上需要创建一个部署主机,然后使用 Ansible 部署整个 OpenStack 系统。整个工作流程大致如下:

  1. 准备部署主机

  2. 准备目标主机

  3. 为部署配置 Ansible

  4. 运行 playbooks 并让它们安装所有内容

  5. 检查 OpenStack 是否正确安装

当我们谈论部署和目标主机时,我们需要明确区分:部署主机是一个单一实体,包含 Ansible、脚本、playbooks、角色和所有支持的部分。目标主机是实际将成为 OpenStack 云一部分的服务器。

安装的要求很简单:

  • 操作系统应该是 Debian、Ubuntu CentOS 或 openSUSE(实验性)的最小安装,具有最新的内核和完整的更新。

  • 系统还应该运行 Python 2.7,启用 SSH 访问并进行公钥身份验证,并启用 NTP 时间同步。这涵盖了部署主机。

  • 不同类型的节点也有通常的建议。计算节点必须支持硬件辅助虚拟化,但这是一个明显的要求。

  • 还有一个应该不言而喻的要求,那就是使用多核处理器,尽可能多的核心,以加快某些服务的运行速度。

磁盘要求真的取决于你。OpenStack 建议尽可能使用快速磁盘,建议在 RAID 中使用 SSD 驱动器,并为块存储提供大型磁盘池。

  • 基础设施节点有不同于其他类型节点的要求,因为它们运行一些随时间增长并且需要至少 100 GB 空间的数据库。基础设施还以容器形式运行其服务,因此它将以与其他计算节点不同的方式消耗资源。

部署指南还建议运行日志主机,因为所有服务都会创建日志。推荐的磁盘空间至少为 50 GB,但在生产环境中,这将迅速增长数量级。

OpenStack 需要一个快速、稳定的网络才能正常工作。由于 OpenStack 中的所有内容都依赖于网络,建议使用任何可能加快网络访问速度的解决方案,包括使用 10G 和绑定接口。安装部署服务器是整个过程的第一步,因此我们将在下一步进行。

安装 Ansible 部署服务器

我们的部署服务器需要及时更新所有升级,并安装 Python、gitntpsudossh支持。安装所需的软件包后,您需要配置ssh密钥以便能够登录到目标主机。这是 Ansible 的要求,也是一种利用安全性和便利性的最佳实践。

网络很简单-我们的部署主机必须与所有其他主机保持连接。部署主机还应安装在网络的 L2 上,该网络专为容器管理而设计。

然后,应该克隆存储库:

# git clone -b 20.0.0 https://opendev.org/openstack/openstack-ansible /opt/openstack-ansible

接下来,需要运行一个 Ansible 引导脚本:

# scripts/bootstrap-ansible.sh

这就完成了准备 Ansible 部署服务器的工作。现在,我们需要准备要用于 OpenStack 的目标计算机。目标计算机目前支持 Ubuntu Server(18.04)LTS、CentOS 7 和 openSUSE 42.x(在撰写本文时,仍然没有 CentOS 8 支持)。您可以使用这些系统中的任何一个。对于每个系统,都有一个有用的指南,可以帮助您快速上手:docs.openstack.org/project-deploy-guide/openstack-ansible/latest/deploymenthost.html。我们将解释一般步骤以便您轻松安装,但实际上,只需从www.openstack.org/复制并粘贴已发布的命令即可。

无论您决定在哪个系统上运行,都必须完全更新系统更新。之后,安装linux-image-extra软件包(如果适用于您的内核),并安装bridge-utilsdebootstrapifenslavelsoflvm2chronyopenssh-serversudotcpdumpvlan和 Python 软件包。还要启用绑定和 VLAN 接口。所有这些东西可能适用于您的系统,也可能不适用,因此如果某些内容已安装或配置,只需跳过即可。

chrony.conf中配置 NTP 时间同步,以在整个部署中同步时间。您可以使用任何时间源,但为了系统正常工作,时间必须同步。

现在,配置ssh密钥。Ansible 将使用ssh和基于密钥的身份验证进行部署。只需将部署机器上适当用户的公钥复制到/root/.ssh/authorized_keys。通过从部署主机登录到目标机器来测试此设置。如果一切正常,您应该能够无需任何密码或其他提示登录。还要注意,部署主机上的 root 用户是管理所有内容的默认用户,并且他们必须提前生成他们的ssh密钥,因为它们不仅用于目标主机,还用于系统中运行的所有不同服务的所有容器。在开始配置系统时,这些密钥必须存在。

对于存储节点,请注意 LVM 卷将在本地磁盘上创建,从而覆盖任何现有配置。网络配置将自动完成;您只需确保 Ansible 能够连接到目标机器即可。

下一步是配置我们的 Ansible 清单,以便我们可以使用它。让我们现在就做。

配置 Ansible 清单

在我们运行 Ansible playbooks 之前,我们需要完成配置 Ansible 清单,以便将系统指向应该安装的主机。我们将引用docs.openstack.org/project-deploy-guide/openstack-ansible/queens/configure.html上提供的原文:

  1. 将/opt/openstack-ansible/etc/openstack_deploy 目录的内容复制到

/etc/openstack_deploy 目录。

  1. 切换到/etc/openstack_deploy 目录。

  2. 将 openstack_user_config.yml.example 文件复制到

/etc/openstack_deploy/openstack_user_config.yml。

  1. 回顾 openstack_user_config.yml 文件并对部署进行更改

你的 OpenStack 环境。

进入配置文件后,审查所有选项。Openstack_user_config.yml定义了哪些主机运行哪些服务和节点。在承诺安装之前,请查看前一段提到的文档。

网上突出的一件事是install_method。你可以选择源或发行版。每种方法都有其优缺点:

  • 源是最简单的安装方式,因为它直接从 OpenStack 官方网站的源代码中完成,并包含与所有系统兼容的环境。

  • 发行版方法是根据你正在安装的特定发行版定制的,使用已知可行且稳定的特定软件包。这样做的主要缺点是更新速度会慢得多,因为不仅需要部署 OpenStack,还需要关于发行版上所有软件包的信息,并且需要验证该设置。因此,预计在升级到并到达发行版安装之间会有很长的等待时间。安装后,您必须选择您的首选项;没有从一个选择切换到另一个的机制。

你需要做的最后一件事是打开user_secrets.yml文件,并为所有服务分配密码。您可以创建自己的密码,也可以使用专门为此目的提供的脚本。

运行 Ansible playbooks

在我们进行部署过程时,我们需要启动一些 Ansible playbooks。我们需要按照以下顺序使用这三个提供的 playbooks:

  • setup-hosts.yml:我们用来在 OpenStack 主机上提供必要服务的初始 Ansible playbook。

  • setup-infrastructure.yml:部署一些其他服务的 Ansible playbook,如 RabbitMQ、仓库服务器、Memcached 等等。

  • setup-openstack.yml:部署剩余服务的 Ansible playbook——Glance、Cinder、Nova、Keystone、Heat、Neutron、Horizon 等等。

所有这些 Ansible playbooks 都需要成功完成,以便我们可以将 Ansible 与 Openstack 集成。所以,唯一剩下的就是运行 Ansible playbooks。我们需要从以下命令开始:

# openstack-ansible setup-hosts.yml

您可以在/opt/openstack-ansible/playbooks中找到适当的文件。现在,运行剩下的设置:

# openstack-ansible setup-infrastructure.yml
# openstack-ansible setup-openstack.yml

所有 playbooks 都应该在没有不可达或失败的情况下完成。有了这个——恭喜!你刚刚安装了 OpenStack。

总结

在本章中,我们花了很多时间描述了 OpenStack 的架构和内部工作原理。我们讨论了软件定义的网络及其挑战,以及不同的 OpenStack 服务,如 Nova、Swift、Glance 等等。然后,我们转向实际问题,比如部署 Packstack(让我们称之为 OpenStack 的概念验证),以及完整的 OpenStack。在本章的最后部分,我们讨论了 OpenStack-Ansible 集成以及在更大的环境中可能对我们意味着什么。

现在我们已经涵盖了私有云方面,是时候扩展我们的环境,将其扩展到更公共混合的方式。在基于 KVM 的基础设施中,这通常意味着连接到 AWS,将您的工作负载转移到那里(公共云)。如果我们讨论混合类型的云功能,则必须介绍一个名为 Eucalyptus 的应用程序。关于如何和为什么,请查看下一章。

问题

  1. VLAN 作为云覆盖技术的主要问题是什么?

  2. 目前云市场上使用的云覆盖网络类型有哪些?

  3. VXLAN 是如何工作的?

  4. 跨多个站点延伸 Layer 2 网络的一些常见问题是什么?

  5. 什么是 OpenStack?

  6. OpenStack 的架构组件是什么?

  7. 什么是 OpenStack Nova?

  8. 什么是 OpenStack Swift?

  9. 什么是 OpenStack Glance?

  10. 什么是 OpenStack Horizon?

  11. OpenStack 的 flavors 是什么?

  12. 什么是 OpenStack Neutron?

进一步阅读

请参考以下链接,了解本章涵盖的更多信息:

第十三章:使用 AWS 扩展 KVM

如果你仔细观察,虚拟化是一个棘手的问题 - 模拟完整的计算机以便能够在其上运行操作系统是复杂的。出于明显的原因,将这些虚拟机放入云中甚至更加困难。之后,事情真的开始变得混乱。从概念上讲,创建能够按需运行的机器集群更加复杂,基于必须同时运行的机器数量之多。此外,不仅需要创建模拟计算机,还需要创建支持更大规模部署的所有网络和基础设施。创建一个全球云 - 不仅运行数百万台机器,而且几乎无处不在,甚至在地球上最偏远的地方 - 是少有公司尝试过的任务,只有少数公司成功。本章将总体介绍这些大型云提供商,然后介绍亚马逊作为其中最大的云提供商。我们的主要想法是介绍亚马逊的运作方式,它与本书中涵盖的其他主题的关系,以及如何在现实世界的真实机器上使用亚马逊提供的服务。

亚马逊网络服务AWS)是一组独特的工具、服务和基础设施,可以在非常大规模上实现云服务,规模之大几乎难以理解。当我们谈论成千上万的站点使用数百万台服务器运行数十亿的应用程序时,甚至列举这些事物都成为一个大问题,管理是一件不仅可以跨越单一章节,而且可能需要多本书的事情。我们将尝试向您介绍 AWS 云的最重要的服务和部分,并尝试解释它们可以用于什么以及何时使用。

在本章中,我们将涵盖以下主题:

  • AWS 简介

  • 为 AWS 准备和转换虚拟机

  • 使用 Eucalyptus 构建混合 KVM 云

AWS 简介

在谈论云服务时,AWS 几乎不需要介绍,尽管很少有人真正了解整个亚马逊云系统有多么庞大和复杂。完全可以肯定的是,目前它无疑是地球上最大和最常用的服务。

在我们做任何其他事情之前,我们需要谈谈 AWS 为什么如此重要,不仅是因为它对互联网的影响,还因为它对任何试图提供某种规模的任务的影响。

正如我们在本书中已经做了几次,我们将从 AWS 云的基本前提开始 - 提供一个广泛可扩展和分布式的解决方案,涵盖互联网上执行任何类型的工作负载的所有可能场景。

在本书的几乎每个其他地方,我们提到云时,我们都谈到了扩展,但当我们试图描述 AWS 能够扩展时,我们谈论的可能是地球上最大的和最常用的容量和可扩展性提供商之一。

目前,互联网上有四个真正大的云提供商:AWS、微软 Azure、谷歌云平台和阿里巴巴。由于所有数字出于商业原因是保密的,它们可以提供的服务器数量和纯粹的容量是分析师试图估计,或更频繁地是猜测的,但必须是以百万计。

接近云

尽管在表面上它们现在在争夺同一个云市场,但所有参与者都来自不同的背景,即使现在他们使用基础设施的方式也大不相同。在这个市场上,亚马逊是第一家,它在 IT 领域似乎几乎不可思议地领先了大约 6 年。亚马逊于 2006 年推出了其亚马逊网络服务,但它在几年前就开始了该服务的开发。甚至有一篇博客文章提到了 2004 年发布的该服务。 AWS 的想法基本上是在亚马逊意识到它拥有一个在市场上无与伦比的庞大基础设施后构想出来的,通过扩大基础设施并将其作为服务提供,它可以获利。当时,他们拥有的基础设施被用来提供 Amazon.com 服务。

这个想法与市场上的任何东西都不同。人们习惯于在数据中心中拥有共同的计算机,并能够租用云中的服务器,但租用他们需要的堆栈的一部分而不是整个硬件基础设施的概念是新的。 AWS 提供的第一个重要服务非常简单,甚至被命名为简单存储服务S3),以及弹性计算云EC2)。 S3 基本上是云支持的存储,为那些能够支付的人提供了几乎无限的存储资源,几乎与今天的方式相同。 EC2 提供了计算资源。

在接下来的 6 年里,提供的服务扩展到了内容传送网络CDN)等等,而竞争对手仍在努力理解云的实际含义。

我们稍后会回到 AWS 提供的服务,但只在提到它们最终在这个每年价值数百亿美元的市场中得到的竞争之后。

微软意识到,它需要建立基础设施来支持自己和客户,这是在 2000 年代后期的某个时候。微软已经有自己的业务支持基础设施来运行公司,但当时并没有向普通公众提供公共服务。这一切在 2010 年微软 Azure推出后发生了变化。最初它被称为Windows Azure,主要用于为微软及其合作伙伴提供服务,主要是在 Windows 上。 微软很快意识到,仅在云中提供微软操作系统将会让他们失去很多客户,因此还提供了 Linux 作为可安装和使用的操作系统。

Azure 现在作为一个公开可用的云运行,但它的很大一部分仍然被微软及其服务所使用,尤其是Office 365和大量的微软培训和营销解决方案。

另一方面,谷歌以不同的方式进入市场。他们也意识到云服务的需求,但将他们与云的第一次接触限制在提供一个名为App Engine的单一服务上,这是在 2008 年。这是一个面向 Web 开发者社区的服务,谷歌甚至为该服务的有限使用提供了 10,000 个免费许可证。在本质上,这项服务以及之后推出的几乎所有服务都是基于这样的前提,即网络需要能够让开发人员快速部署可能会扩展或可能不会工作的东西。因此,免费提供意味着很多开发人员倾向于仅用于简单测试。

谷歌现在也提供了大量的服务,但当你从外部看实际的服务和定价时,似乎谷歌创建了它的云来租用其数据中心中可用的额外容量。

多云

回顾几年前,Azure 和 Google Cloud Platform 都有一个可行的云服务,但与 AWS 提供的服务相比,它们的服务根本不匹配。AWS 是最大的参与者,无论是市场份额还是人们的心目中。Azure 被认为更多是面向微软的,尽管其上运行的服务器有一半以上是基于 Linux 的,而 Google 则不被视为竞争对手;他们的云看起来更像是一个副业而不是一个可行的云运行提案。

然后出现了多云。这个想法很简单 - 不要使用单一的云来部署您的服务;使用多个云提供商来提供数据冗余可用性故障转移,最重要的是成本降低和灵活性。这可能听起来很奇怪,但在使用云服务时最大的成本之一是将数据从中取出。通常情况下,将数据上传到云中,或者在服务器上部署数据,要么是免费的,要么成本极低,这是有道理的,因为如果您在线上有大量数据,您更有可能在这个特定的云上使用更多的服务。一旦您需要提取数据,成本就会变得很高。这是有意为之的,它使用户被锁定在云提供商那里。一旦您上传了数据,将其保留在服务器上而不尝试离线处理它要便宜得多。但在谈论多云时,不仅要考虑数据,服务也是方程式的一部分。

为什么要使用多云策略?

许多公司(我们必须强调,多云用户大多是大公司,因为涉及到成本)害怕被锁定在特定的平台或技术上。其中最大的问题之一是,如果平台发生了如此大的变化,以至于公司必须重新设计其基础设施,会发生什么?想象一下,您是一家价值数十亿美元的公司,为数十万自己的用户运行企业应用程序。您选择云的原因通常是为了降低资本支出并能够扩展您的服务。您决定选择其中一个大型提供商。突然间,您的提供商决定要改变技术,并将逐步淘汰基础设施的某些部分。在这种情况下,通常意味着您当前的设置将变得更加昂贵,或者您将失去一些可用功能的一部分。幸运的是,这些事情通常会延伸数年,因为没有一个理智的云提供商会在一夜之间进行战略性的改变。

但是变化就是变化,作为一家公司,您有选择 - 留在提供商那里并面对系统价格大幅上涨的后果 - 或者重新设计系统,这也将花费金钱,并可能需要数年才能完成 - 有时甚至数十年。

因此,许多公司选择了一种非常保守的策略 - 设计一个可以在任何云上运行的系统,这意味着使用所有可用技术的最低公共分母。这也意味着该系统可以在几天内从一个云迁移到另一个云。其中一些公司甚至决定同时在不同的云上运行系统。这是一种极端保守的方法,但它是有效的。

使用多云策略的另一个重要原因与我们刚才提到的完全相反。有时,想法是使用特定的云服务或服务来完成非常专业的任务。这意味着选择来自不同提供商的不同服务来执行不同的任务,但要尽可能高效地完成。从长远来看,这也意味着不时地必须更换提供商和系统,但如果公司使用的核心系统是以此为考虑而设计的,这种方法也是有好处的。

Shadow IT

还有另一种方式,公司可以成为一个多云环境,甚至不知道它,这通常被称为“影子 IT”。如果公司没有严格的安全政策和规定,一些员工可能会开始使用公司未提供的服务。这可能是云存储容器、视频会议系统或邮件列表提供商。在大公司中,甚至可能是整个部门开始使用来自不同云提供商的东西,甚至没有意识到。突然之间,公司数据存储在公司 IT 范围之外或无法覆盖的服务器上。

这种现象的一个更好的例子是 COVID-19 病毒全球大流行期间视频会议服务的使用方式发生了变化。几乎所有公司都有一个成熟的通信系统,通常是覆盖整个公司的消息系统。然后,一夜之间,大流行将所有工人都留在家中。由于沟通是公司运营的关键,每个人决定在一周内全球切换到视频和音频会议。接下来发生的事情可能会成为畅销书的主题。大多数公司试图坚持他们的解决方案,但几乎普遍地,这一尝试在第一天就失败了,要么是因为服务太基本或太过时,无法用作音频和视频会议解决方案,要么是因为服务没有设计用于如此大量的呼叫和请求而崩溃。

人们想要协调,所以突然之间一切都成了可能。每一个视频会议解决方案突然都成了候选。公司、部门和团队开始尝试不同的会议系统,云提供商很快意识到了这个机会——几乎所有服务立即对于规模可观的部门来说都是免费的,有些情况下,甚至允许多达 150 人参加会议。

由于需求激增,很多服务崩溃了,但大型提供商大多能够扩展到所需的规模以保持一切运转。

由于大流行是全球性的,很多人决定他们也需要一种与家人交流的方式。因此,个人用户开始在家使用不同的服务,当他们决定某种服务有效时,他们也在工作中使用它。在几天内,公司变成了一个多云环境,人们使用一个提供商进行通信,另一个提供商进行电子邮件,第三个提供商进行存储,第四个提供商进行备份。变化如此之快,以至于有时 IT 在系统上线后几天才被告知变化,而人们已经在使用它们。

这种变化如此巨大,以至于在我们写这本书的时候,我们甚至无法预测有多少这些服务将成为公司工具组合的常规部分,一旦用户意识到某些东西比公司提供的软件更好用。这些服务通过能够在这样的重大灾难中持续工作进一步证明了这一点,因此公司范围的软件使用政策对于阻止这种混乱的多云方法能做的事情是有限的。

市场份额

每当提到云计算公司和服务时,每个人都会提到它们的市场份额。我们也需要解决这一点,因为我们说过我们在谈论“最大的”或“第二大”的。在多云成为一种趋势之前,市场份额基本上分为亚马逊 AWS 占据最大市场份额;微软 Azure 居于遥远的第二位;其次是谷歌和一大群“小”提供商,如阿里巴巴、甲骨文、IBM 等。

一旦多云成为一种趋势,最大的问题就是如何确定谁拥有最大的实际市场份额。所有大公司开始使用不同的云服务提供商,而简单地尝试累加提供商的市场份额变得困难。从不同的调查中可以清楚地看出,亚马逊仍然是领先的提供商,但公司们慢慢开始与亚马逊服务一起使用其他提供商。

这意味着,目前,AWS 仍然是首选的云服务提供商,但选择本身不再仅限于单一提供商。人们也在使用其他提供商。

大型基础设施但没有服务

有时,试图划分市场份额还有另一个必须考虑的观点。如果我们谈论云服务提供商,通常认为我们在谈论拥有最大基础设施来支持云服务的公司。有时,实际上,我们实际上在比较那些在市场上拥有最大服务组合的公司。我们指的是什么?

有一个明显的公司拥有庞大的云存在,但几乎完全使用自己的基础设施来提供自己的内容 - Facebook。尽管很难通过服务器数量、数据中心或任何其他指标来比较基础设施的规模,因为这些数字是严格保密的,但 Facebook 的基础设施规模与 AWS 相当。真正的区别在于,这种基础设施不会为第三方提供服务,实际上,它从来就不是为此而设计的;Facebook 创建的一切都是为了支持自身,包括选择数据中心的位置、配置和部署硬件以及创建软件。Facebook 不会突然变成另一个 AWS;它太大了。可用的基础设施并不总是与云市场份额相关。

定价

另一个我们必须涵盖的话题,即使只是提及,就是定价。本书中几乎每次提到云都是技术性的。我们比较了可能有意义的每一个指标,从IOPS,通过GHz,到网络端的PPS,但云不仅仅是一个技术问题 - 当你必须将其投入使用时,有人必须为此付费。

定价是云世界中的热门话题,因为竞争激烈。所有云服务提供商都有他们的定价策略,折扣和特别交易几乎成为常态,所有这些使得理解定价变成一场噩梦,特别是对于那些对所有不同模式都是新手的人。有一点是确定的,所有提供商都会说他们只会按照你的使用量收费,但是定义他们实际指的是什么可能会成为一个大问题。

在开始规划部署成本时,你应该首先停下来,尝试定义你需要什么,你需要多少,以及你是否正在以云的方式使用它。迄今为止最常见的错误是认为云在任何形式上都类似于使用普通服务器。人们首先注意到的是特定实例在特定数据中心运行特定配置的价格。价格通常要么是实例的月费用,并且通常是按比例计算的,因此你只支付你使用的部分,要么价格是以不同的时间单位给出 - 每天、每小时,甚至每秒。这应该是你的第一个线索:你支付使用实例的费用,因此为了降低成本,不要让你的实例一直运行。这也意味着你的实例必须被设计成可以根据需求快速启动和关闭,因此使用安装单个或多个服务器并一直运行它们的标准方法在这里不一定是一个好选择。

在选择实例时,选项实在太多,这里无法一一列举。所有的云提供商都有自己关于人们需求的想法,所以你不仅可以选择简单的东西,比如处理器数量或内存量,还可以获得预安装的操作系统,以及各种各样的存储和网络选项。存储是一个特别复杂的话题,我们在这里只是简单地触及一下,并且稍后再提及。所有的云提供商都提供两种东西 - 一些用于连接到实例的存储,以及一些用作服务的存储。一个给定的提供商提供的东西可能取决于你尝试请求的实例,你尝试在其中请求的数据中心,以及其他一些因素。预期你将不得不平衡三件事:容量、定价和速度。当然,在这里,我们谈论的是实例存储。作为服务的存储更加复杂,你不仅需要考虑定价和容量,还需要考虑其他因素,比如延迟、带宽等等。

例如,AWS 让你可以选择各种服务,从数据库存储、文件存储和长期备份存储,到不同类型的块和对象存储。为了最佳地使用这些服务,你需要首先了解提供了什么,以什么方式提供,涉及了哪些不同的成本,以及如何利用它们。

你会很快注意到的另一件事是,当云提供商说一切都是服务时,他们是认真的。完全有可能运行一个应用程序而没有一个单独的服务器实例。通过将不同的服务拼接在一起可以完成任务,这是有意设计的。这创造了一个极其灵活的基础设施,一个可以快速、轻松扩展的基础设施,但不仅需要一种不同的编写代码的方式,还需要一种完全不同的思维方式来设计你需要的解决方案。如果你没有经验,找一个专家,因为这是你解决方案的根本问题。它必须在云上运行,而不是在云中的虚拟机上运行。

我们给你的建议很简单 - 多读文档。所有的提供商都有出色的资源,可以让你了解他们的服务提供了什么,以及如何提供,但这些数千页的文档不会告诉你它与竞争对手相比如何,更重要的是,连接服务的最佳方式是什么。在支付云服务时,预期你会偶尔犯错并为此付费。这就是为什么在部署服务时使用按需付费选项是有用的 - 如果你犯了一个错误,你不会产生巨额账单;你的基础设施将会停止。

谈到定价时要提及的另一件事是,一切都有一点成本,但给定配置的综合价格可能会很高。任何额外的资源都会花钱。服务器之间的内部链接外部 IP 地址防火墙负载均衡器虚拟交换机,这些通常在设计基础设施时我们甚至都不会考虑的东西,但一旦我们在云中需要它们,它们就会变得昂贵。另一个要预料的事情是,一些服务有不同的上下文 - 例如,如果你在实例之间传输数据或者传输到外部世界,网络带宽的价格可能会不同。存储也是一样 - 正如我们在本章前面提到的,大多数提供商在存储和从云中获取数据时会向你收取不同的价格。

数据中心

在本章中,我们已经几次提到了数据中心,重要的是我们谈一下它们。数据中心是云基础设施的核心,而且方式远不止你所想的那样。当我们谈论大量服务器时,我们提到我们通常将它们分组放入机架,并将机架放入数据中心。你可能知道,数据中心本质上是一组带有所有服务器需要的基础设施的机架,无论是在电源和数据方面,还是在冷却、保护和保持服务器运行所需的其他方面。它们还需要被逻辑地划分为我们通常称之为“故障域”的“风险区域”,以便我们可以避免与“我们把所有东西都部署在一个机架上”或“我们把所有东西都部署在一个物理服务器上”的各种风险相关的问题。

在任何情况下,数据中心都是复杂的基础设施元素,因为它需要一系列因素的组合才能高效、安全和冗余。将一组服务器放入机架中是相当容易的,但提供冷却和电源却不是一件简单的任务。此外,如果你想让你的服务器工作,冷却、电源和数据都必须是冗余的,而且所有这些都需要保护,不仅免受火灾、洪水、地震和人为破坏,而且真正运行数据中心的成本可能很高。当然,运行几百台服务器的数据中心并不像运行数千台甚至数万台的数据中心那样复杂,而且随着设施规模的增加,价格也会上涨。此外,拥有多个数据中心会在连接它们时产生额外的基础设施挑战,因此成本会不断增加。

现在将这个成本乘以一百,因为这是每个云提供商在世界各地保留的数据中心数量。一些中心很小,一些很大,但游戏的名字很简单——网络。为了成为一个真正的全球提供商,所有这些中心都必须有一个数据中心,或者至少有几台服务器,尽可能靠近你。如果你正在世界上几乎任何一个大国家的大城市中阅读这篇文章,很可能在你的 100 英里半径范围内有一个亚马逊、微软或谷歌拥有的服务器。所有提供商都努力在每个大城市的每个国家至少有一个数据中心,因为这可以让他们极快地提供一系列服务。这个概念被称为接触点POC),意味着当连接到提供商的云时,你只需要到达最近的服务器,之后云将确保你的服务尽可能快速。

但是当我们谈论实际属于亚马逊或其他公司的数据中心时,我们仍然在处理大规模的运营。在这里,数字通常是以百计计算的,它们的位置也是秘密的,主要是出于安全原因。它们都有一些共同点。它们是高度自动化的运营,位于主要电源、主要冷却源或主要数据中心附近。理想情况下,它们应该被放置在一个同时具备这三种条件的地方,但通常这是不可能的。

位置是关键。

不同的公司有不同的策略,因为选择一个建立数据中心的好地方可以节省大量成本。有些公司甚至走向了极端。例如,微软有一个完全被浸入海洋中以便进行冷却的数据中心。

为特定用户提供服务时,你通常关心的是速度和延迟,这意味着你希望你的服务器或服务在离用户最近的数据中心运行。为此,所有云提供商都会根据地理位置划分他们的数据中心,这样管理员就可以在互联网的最佳部分部署他们的服务。但与此同时,这也会带来资源的典型问题 - 地球上有一些地方可用的数据中心很少,但人口密集,也有一些地方则相反。这反过来直接影响资源的价格。当我们谈到定价时,我们提到了不同的标准;现在我们可以再添加一个 - 地点。数据中心的位置通常被称为“区域”。这意味着 AWS,或者其他任何提供商,都不会告诉你他们的数据中心的位置,而是会说“在这个区域的用户最适合使用这组服务器”。作为用户,你不知道特定服务器在哪里,而是只关心提供商给你的区域。你可以在这里找到服务区域的名称和代码:

图 13.1 - AWS 上使用的配置名称的服务区域

图 13.1 - AWS 上使用的配置名称的服务区域

选择一个需求量大的地区提供的服务可能会很昂贵,而选择其他地方的服务器可能会便宜得多。这就是云的美妙之处 - 你可以使用适合你和你的预算的服务。

有时价格和速度并不是最重要的事情。例如,法律框架,比如 GDPR,欧洲关于个人数据收集、处理和移动的法规,基本上规定欧洲公司必须使用欧洲的数据中心,因为它们受到这项法规的保护。在这种情况下使用美国地区可能意味着公司可能会承担法律责任(除非运行这个云服务的公司是其他允许这样做的框架的一部分,比如隐私盾)。

AWS 服务

我们需要谈一下 AWS 在服务方面提供了什么,因为理解服务是能够适当使用云的一件事。在 AWS 上,所有可用的服务都按照它们的目的分成了不同的组。由于 AWS 有数百种服务,AWS 管理控制台,也就是你登录后会看到的第一个页面,一开始可能会让人望而生畏。

你可能会使用 AWS 免费套餐进行学习,所以第一步是实际开设一个 AWS 免费账户。就我个人而言,我使用了自己的个人账户。对于免费账户,我们需要使用以下网址:aws.amazon.com/free/,并按照流程进行。它只需要一些信息,比如电子邮件地址、密码和 AWS 账户名称。它还会要求你提供信用卡信息,以确保你不会滥用 AWS 账户。

注册后,我们可以登录并进入 AWS 仪表板。看一下这个屏幕截图:

图 13.2 - 亚马逊服务

图 13.2 - 亚马逊服务

这里的每一样东西都是一个链接,它们都指向不同的服务或带有子服务的页面。此外,这个屏幕截图只显示了所有可用服务的大约三分之一。在这本书中覆盖它们所有是没有意义的;我们只会使用其中三个来展示 AWS 如何连接到我们的 KVM 基础设施,但一旦你掌握了它,你就会慢慢开始理解一切是如何连接的,以及在特定时刻应该使用什么。真正有帮助的是 AWS 有很好的文档,所有不同的服务都有提供向导,帮助你找到你要找的东西。

在本章中,我们将使用这三项服务:IAMEC2S3

当然,所有这些都是缩写,但其他服务只是使用项目名称,比如CloudFrontGlobal Accelerator。无论如何,您的首要任务应该是开始使用它们,而不仅仅是阅读它们;一旦您开始使用,理解结构就会变得更容易。

在本章中,我们使用了一个免费账户,几乎所有的操作都是免费的,因此您没有理由不尝试使用 AWS 基础设施。AWS 尽其所能在那里提供帮助,因此如果您在控制台页面上向下滚动,您会发现这些有用的图标:

图 13.3 - 一些 AWS 向导、文档和视频 - 都非常有用

](https://gitee.com/OpenDocCN/freelearn-linux-zh/raw/master/docs/ms-kvm-vrt/img/B14834_13_03.jpg)

图 13.3 - 一些 AWS 向导、文档和视频 - 都非常有用

所有这些都是简单的场景,可以让您在几分钟内免费运行起来。亚马逊意识到,云的首次用户对所有选择感到不知所措,因此他们试图在几分钟内让您的第一台机器运行起来,以向您展示它有多么容易。

让我们先了解一下我们将要使用的服务,我们将通过一个场景来做到这一点。我们想要将在本地 KVM 安装中运行的机器迁移到亚马逊 AWS 中。我们将逐步完成整个过程,但首先需要了解我们需要什么。显然,第一件事是能够在云中运行虚拟机。在 AWS 宇宙中,这就是 EC2 或亚马逊弹性计算云。

EC2

EC2 是少数几个真正的核心服务之一,基本上在 AWS 云中运行所有的东西。它是整个基础设施的可扩展计算能力提供者。它使得可以运行不同的实例或虚拟计算环境,使用各种存储、内存、CPU 和网络配置,并且还提供这些实例所需的一切,包括安全性、存储卷、区域、IP 地址和虚拟网络。其中一些服务也可以单独使用,以应对更复杂的场景,例如,存在许多不同的存储选项,但实例的核心功能由 EC2 提供。

S3

这项服务的全名实际上是亚马逊简单存储服务,因此有了亚马逊 S3这个名字。其想法是为您提供存储和检索任意数量的数据的能力,随时使用提供的一种或多种方法。我们将使用的最重要的概念是S3 存储桶。存储桶是一个逻辑存储单元,使您能够对您存储的对象进行分组。可以将其视为一个存储容器的名称,稍后您将使用它来存储任何东西。您可以随意命名存储桶,但有一点我们必须指出 - 存储桶的名称必须是全局唯一的。这意味着当您命名一个存储桶时,它必须具有一个在任何地区中都没有重复的名称。这确保了您的存储桶将具有唯一的名称,但也意味着尝试创建一个听起来很普通的名称,比如bucket1storage可能不会起作用。

创建存储桶后,您可以使用 Web、CLI 或 API 从中上传和下载数据。由于我们谈论的是一个全球系统,我们还必须指出,数据存储在您创建存储桶时指定的区域,并且会一直保留在那里,除非您指定要进行某种形式的多区域冗余。在部署存储桶时要记住这一点,因为一旦您开始使用存储桶中的数据,您的用户或实例需要获取数据,延迟可能会成为一个问题。由于法律和隐私问题,除非您明确指定,数据永远不会离开您指定的区域。

一个存储桶可以存储任意数量的对象,但每个账户的存储桶数量限制为 100 个。如果这不够用,您可以请求(并支付)将该限制提高到 1,000 个存储桶。

此外,仔细研究存储和移动数据的其他不同选项 - 有不同类型的存储,可能符合或不符合您的需求和预算,例如,S3 Glacier,它提供了更便宜的存储大量数据的选项,但如果需要取出数据则会很昂贵。

IAM

AWS 身份 和访问管理 (IAM) 是我们需要使用的服务,因为它可以为所有对象和服务提供访问管理和权限。在我们的示例中,我们将使用它来创建策略、用户和角色,以完成我们的任务。

其他服务

简单地提及 AWS 提供的所有服务是不可能的。我们只提到了必要的服务,并试图指引您朝正确的方向。您需要尝试并了解您的使用场景是什么,以及如何配置满足您特定需求的内容。

到目前为止,我们已经解释了 AWS 是什么以及它有多复杂。我们还提到了平台上最常用的部分,并开始解释它们的功能是什么。随着我们实际将一台机器从本地环境迁移到 AWS,我们将对此进行扩展。这将是我们的下一个任务。

为 AWS 准备和转换虚拟机

如果您在 Google 上搜索,从 KVM 迁移机器到 AWS 是很容易的,所需的只是按照此链接上的说明进行操作:docs.amazonaws.cn/en_us/vm-import/latest/userguide/vm-import-ug.pdf

如果您真的尝试去做,您会很快明白,只有对 AWS 工作方式有基本的了解,您就无法按照说明进行操作。这就是为什么我们选择以迁移一台机器作为使用 AWS 的示例,快速在云中创建一个可工作的虚拟机。

我们想要做什么?

让我们定义一下我们正在做的事情 - 我们决定将我们的一台机器迁移到 AWS 云中。现在,我们的机器正在本地的 KVM 服务器上运行,我们希望尽快将其运行在 AWS 上。

我们必须强调的第一件事是,这里没有实时迁移选项。没有简单的工具可以直接将 KVM 机器迁移到 AWS。我们需要一步一步地进行,并且机器需要处于关闭状态。在快速查阅文档后,我们制定了一个计划。基本上,我们需要做的是以下内容:

  1. 停止我们的虚拟机。

  2. 将机器转换为与 AWS 中使用的导入工具兼容的格式。

  3. 安装所需的 AWS 工具。

  4. 创建一个能够进行迁移的账户。

  5. 检查我们的工具是否正常工作。

  6. 创建一个 S3 存储桶。

  7. 将包含我们机器的文件上传到存储桶中。

  8. 将机器导入 EC2。

  9. 等待转换完成。

  10. 准备机器启动。

  11. 在云中启动机器。

因此,让我们开始着手做这件事:

  1. 一个很好的开始是查看我们工作站上的机器。我们将迁移名为deploy-1的机器来测试我们的 AWS 迁移。它是一个基本的 CentOS 7 安装,运行在使用相同 Linux 发行版的主机上。因此,我们显然需要有权限:图 13.4 - 选择迁移过程中的虚拟机

图 13.4 - 选择迁移过程中的虚拟机

接下来要做的是停止机器 - 我们无法迁移正在运行的机器,因为我们需要转换机器正在使用的卷,以使其与 EC2 上的导入工具兼容。

  1. docs.aws.amazon.com/vm-import/latest/userguide/vmimport-image-import.html提供的文档中指出:

“当将 VM 作为镜像导入时,可以导入以下格式的磁盘:Open Virtualization Archive (OVA)、Virtual Machine Disk (VMDK)、Virtual Hard Disk (VHD/VHDX)和原始格式。在某些虚拟化环境中,你可以导出为 Open Virtualization Format (OVF),它通常包括一个或多个 VMDK、VHD 或 VHDX 文件,然后将文件打包成一个 OVA 文件。”

在我们的特定情况下,我们将使用.raw格式,因为它与导入工具兼容,并且相对简单地从 KVM 使用的.qcow2格式转换为这种格式。

一旦我们的机器停止了,我们需要进行转换。找到磁盘上的镜像,并使用qemu-img进行转换。唯一的参数是文件;转换器通过检测扩展名来理解它需要做什么:

图 13.5 – 将 qcow2 镜像转换为原始镜像格式

图 13.5 – 将 qcow2 镜像转换为原始镜像格式

我们只需要转换包含系统磁盘镜像的镜像文件;其他数据被留在了 VM 的安装之外。我们需要记住,我们正在转换为一个没有压缩的格式,所以你的文件大小可能会显著增加:

图 13.6 – 转换过程和相应的容量变化

图 13.6 – 转换过程和相应的容量变化

我们可以看到我们的文件从 42MB 增加到 8GB,只是因为我们不得不删除qcow2为数据存储提供的高级功能。免费层只提供 5GB 的存储空间,所以请确保相应地配置原始镜像的大小。

我们接下来明显的步骤是将这个镜像上传到云端,因为转换是在那里完成的。在这里,你可以使用不同的方法,无论是 GUI 还是 CLI(API 也是可能的,但对于这个简单的任务来说太复杂了)。

  1. AWS 有一个 CLI 工具,可以方便地与服务一起工作。这是一个简单的命令行工具,兼容大多数,如果不是所有,你能想到的操作系统:图 13.7 – 下载和解压 AWS CLI

图 13.7 – 下载和解压 AWS CLI

我们正在使用curl下载文件,并使用-o选项来指定输出文件的名称。显然,我们需要解压 ZIP 文件以便使用它。工具的安装过程也在文档中有提及。我们正在谈论一个简单的下载,之后我们需要提取工具。由于没有安装程序,工具不会出现在我们的路径中,所以从现在开始,我们需要通过绝对路径引用它。

在我们使用 AWS CLI 之前,我们需要对其进行配置。这个工具必须知道它将如何连接到云端,它将使用哪个用户,并且必须拥有所有的权限,以便工具能够将数据上传到 AWS,然后导入并转换为 EC2 镜像。由于我们还没有配置好,让我们切换到 AWS 的 GUI 并配置我们需要的东西。

重要提示

从现在开始,如果截图中的某些东西看起来被编辑了,那可能确实是。为了使事情能够无缝地运行,AWS 在屏幕上有很多个人和账户数据。

  1. 我们将进入Administrator,一个名为administrators的组,并为它们应用适当的权限。然后我们将把用户加入到这个组中。在第一个屏幕上,你可以选择AdministratorAccess策略作为示例。这个策略非常重要,因为它允许我们给我们正在创建的Administrators组赋予所有可用的权限。现在选择可以稍后用于身份管理的tags

  2. 标记可以通过几乎任何东西来完成 – 名字、邮箱、职位,或者你需要的任何东西。我们将把这个留空:

图 13.15 – 添加标签

图 13.15 – 添加标签

让我们回顾一下我们到目前为止配置的内容。我们的用户是我们刚刚创建的组的成员,他们在登录后必须立即重置密码:

图 13.16 - 通过组、策略和标记选项审查用户配置

图 13.16 - 通过组、策略和标记选项审查用户配置

接受这些并添加用户。您应该会看到一个令人放心的绿色消息框,其中包含有关刚刚发生的所有相关细节。还有一个直接链接到控制台的管理访问,所以您可以与新用户共享:

图 13.17 - 用户创建成功

图 13.17 - 用户创建成功

用户创建后,我们需要启用他们的访问密钥。这是使用不同命令行实用程序的正常概念。它使我们能够为应用程序提供一种作为特定用户执行某些操作的方式,而不提供应用程序的用户名或密码。同时,我们可以为每个应用程序提供自己的密钥,因此当我们想要撤销访问权限时,我们只需禁用密钥。

点击屏幕中间的创建访问密钥

图 13.18 - 创建访问密钥

图 13.18 - 创建访问密钥

关于这个密钥需要说几件事。有两个字段 - 一个是密钥本身,即访问密钥 ID,另一个是密钥的秘密部分,即秘密访问密钥。就安全性而言,这与为特定用户设置用户名和密码完全相同。您只有一次机会查看和下载密钥,之后就会消失。这是因为我们在这里处理的是散列信息,AWS 存储您的密钥,而是它们的散列。这意味着如果您没有保存密钥,就无法检索密钥。这也意味着如果有人获取了密钥,比如从截图中读取,他们可以将自己标识为拥有该密钥的用户。好处是您可以创建任意多的密钥,并且撤销它们只是删除它们的问题。因此,请将密钥保存在安全的地方:

图 13.19 - 访问密钥已成功创建

图 13.19 - 访问密钥已成功创建

我们现在暂时完成了 GUI。让我们回去安装 AWS CLI:

  1. 我们只需要启动安装脚本并让它完成其工作。这可以通过在aws目录中启动名为install的文件来完成:图 13.20 - 安装 AWS CLI

图 13.20 - 安装 AWS CLI

记住我们说过的绝对路径吗?aws命令不在用户路径中;我们需要直接调用它。使用configure作为参数。然后,使用我们在上一步中保存的密钥的两部分。从现在开始,我们使用 AWS CLI 给出的每个命令都被解释为在云上刚刚创建的用户Administrator下运行。

下一步是在 S3 上创建一个存储桶。有两种方法可以做到这一点。我们可以通过我们新配置的 CLI 来做,或者我们可以使用 GUI。我们将采取“漂亮”的方式,使用 GUI 来展示它的外观和行为。

  1. 在控制台中选择 S3 作为服务。右上角有一个标有importkvm的按钮,但选择一个不同的名称。确保记下region下拉菜单 - 这是 AWS 资源将被创建的位置。记住名称必须是唯一的;如果购买了这本书的每个人都尝试使用这个名称,只有第一个人会成功。有趣的事实是:如果到你读到这里的时候,我们还没有删除这个存储桶,没有人将能够创建另一个同名的存储桶,只有读到这句话的人会明白为什么。这个向导在屏幕空间方面非常大,可能无法适应单个书页,所以让我们将其分成两部分:

图 13.21 – 创建 S3 存储桶的向导-选择存储桶名称和区域

图 13.21 – 创建 S3 存储桶的向导-选择存储桶名称和区域

这个向导的第二部分与设置相关:

图 13.22 – 存储桶设置

图 13.22 – 存储桶设置

不要更改对公共的访问-实际上没有必要;除了您之外,没有人会需要访问这个特定的存储桶和其中的文件。默认情况下,此选项已预先选择,我们应该保持不变。这应该是最终结果:

图 13.23 – S3 存储桶创建成功

图 13.23 – S3 存储桶创建成功

好了,做完这些,现在是等待下一个命令完成的时候了。在下一步中,我们将使用 AWS CLI 将.raw文件复制到 S3。

重要提示

根据账户类型的不同,从这一点开始,我们可能需要为创建的一些服务付费,因为它们可能会超出您账户上启用的免费套餐。如果您没有启用任何昂贵的东西,那么应该没问题,但是始终要查看您的成本管理仪表板,并确保您仍然处于盈利状态。

将图像上传到 EC2

接下来的步骤是将图像上传到 EC2,以便我们实际上可以将该图像作为虚拟机运行。让我们开始上传过程-这就是我们首先安装 AWS CLI 实用程序的原因:

  1. 使用以下参数使用 AWS CLI:图 13.24 – 使用 AWS CLI 将虚拟机原始图像复制到 S3 存储桶

图 13.24 – 使用 AWS CLI 将虚拟机原始图像复制到 S3 存储桶

这就是最终结果。由于我们谈论的是 8GB 的数据,您将不得不等待一段时间,具体取决于您的上传速度。AWS CLI 的语法非常简单。您可以使用大多数您知道的 Unix 命令,lscp都能正常工作。唯一要记住的是以以下格式给出您的存储桶名称作为目的地:s3://<bucketname>

  1. 之后,我们执行ls命令-它将返回存储桶名称,但我们可以通过使用存储桶名称来列出它们的内容。在这个例子中,您还可以看到我们从创建存储桶到传输文件大约花了 15 分钟的时间:图 13.25 – 传输文件

图 13.25 – 传输文件

现在开始有趣的部分。我们需要将机器导入 EC2。为此,我们需要在进行转换之前完成一些事情。问题与权限有关-AWS 服务默认情况下无法相互通信。因此,您必须为它们中的每一个明确授予权限以进行导入。实质上,您必须让 EC2 与 S3 通信并从存储桶中获取文件。

  1. 为了上传目的,我们将介绍另一个 AWS 概念-.json文件。AWS 中的许多东西都以.json格式存储,包括所有的设置。由于 GUI 很少被使用,这是传输数据和设置的最快方式,因此我们也必须使用它。我们需要的第一个文件是trust-policy.json,我们将使用它来创建一个角色,该角色将使数据能够从 S3 存储桶中读取:
trust-policy.json, and get the preceding code typed in. Do not change anything. The next one up is the file named role-policy.json. This one has some changes that you have to make. Take a closer look inside the file and find the lines where we mention our bucket name (importkvm). Delete our name and put the name of your bucket instead: 

{

"Version":"2012-10-17",

"Statement":[

{

"Effect":"Allow",

"Action":[

"s3:GetBucketLocation",

"s3:ListBucket",

"s3:GetObject"

],

"Resource":[

"arn:aws:s3:::importkvm"

"arn:aws:s3:::importkvm/*"

],

},

{

"Effect":"Allow",

"Action":[

"s3:GetObject",

"s3:GetBucketLocation",

"s3:ListBucket",

"s3:GetBucketAcl",

"s3:PutObject"

],

"Resource":[

"arn:aws:s3:::importkvm"

"arn:aws:s3:::importkvm/*"

],

},

{

"Effect":"Allow",

"Action":[

"ec2:ModifySnapshotAttribute",

"ec2:CopySnapshot",

"ec2:RegisterImage",

"ec2:Describe*"

],

"Resource":"*"

}

]

}


Now it's time to put it all together and finally upload our virtual machine to AWS.
  1. 执行这两个命令,忽略格式中发生的任何事情-它们都是一行命令,文件名是命令的最后部分:
.json file that will describe to EC2 what we are actually importing and what to do with it. 
  1. 我们正在创建的文件需要如下所示:
[
 {
 "Description": "Test deployment", 
 "Format": "raw", 
 "Userbucket": {
 "S3Bucket": "importkvm",
 "S3Key": "deploy1.raw"
 }
]

如您所见,文件中没有什么特别之处,但是当您创建自己的版本时,请注意使用您的名称作为存储桶和存储在存储桶中的磁盘映像的名称。随意命名文件,并使用该名称调用导入过程:

图 13.27 - 最后一步 - 将虚拟机部署到 AWS

图 13.27 - 最后一步 - 将虚拟机部署到 AWS

现在等待过程完成。在这一步中发生的是导入和转换图像以及您上传的操作系统。AWS 不会按原样运行您的图像;系统会进行一些更改,以确保您的图像可以在基础设施上运行。一些用户也会收到一些更改,但稍后再说。

任务将在后台运行,并在完成时不会通知您;您需要自行检查。幸运的是,AWS CLI 中有一个可以使用的命令叫做describe-import-image-tasks,这是输出:

图 13.28 - 检查我们的上传过程的状态

图 13.28 - 检查我们的上传过程的状态

这意味着我们成功导入了我们的机器。太棒了!但是机器还没有运行。现在它已经变成了一个叫做Amazon Machine ImageAMI)的东西。让我们看看如何使用它:

  1. 转到您的 EC2 控制台。您应该能够在左侧的AMI下找到图像:图 13.29 - 我们的 AMI 已成功上传,并且可以在 EC2 控制台中看到

图 13.29 - 我们的 AMI 已成功上传,并且可以在 EC2 控制台中看到

现在点击大蓝色的启动按钮。在实例运行之前,您需要完成几个步骤,但我们几乎已经完成了。首先,您需要选择您的实例类型。这意味着根据您的需求选择适合您的配置,根据您需要的一切(CPU、内存和存储)。

  1. 如果您使用的是不拥挤的地区,您应该能够启动一个通常称为t2.micro免费层实例类型,并且标记清晰。在您的免费账户中,您有足够的处理信用来使您能够完全免费运行这台机器:图 13.30 - 选择实例类型

图 13.30 - 选择实例类型

现在是一些安全性问题。亚马逊更改了您的机器,并已实施了无密码登录到管理员帐户,使用密钥对。由于我们还没有密钥,我们还需要创建密钥对。

  1. EC2 将把这个密钥放入您刚刚创建的机器上的适当帐户中(所有帐户),因此您可以无需使用密码登录。如果您选择这样做,将生成密钥对,但亚马逊不会存储它 - 您需要自行存储:

图 13.31 - 选择现有密钥或创建新密钥

图 13.31 - 选择现有密钥或创建新密钥

就是这样,您的虚拟机现在应该需要几分钟来启动。只需等待确认窗口。一旦准备就绪,通过上下文菜单连接到它。点击右下角的查看实例,您将进入实例列表。

要连接,您需要使用提供给您的密钥对,并且需要一个ssh客户端。或者,您可以使用 AWS 提供的嵌入式ssh。无论哪种情况,您都需要机器的外部地址,AWS 也提供了这个,以及简单的说明:

图 13.32 - 连接到您的实例说明

图 13.32 - 连接到您的实例说明

因此,回到我们的工作站,我们可以使用前一个截图中提到的ssh命令来连接到我们新启动的实例:

图 13.33 - 通过 SSH 连接到我们的实例

图 13.33 - 通过 SSH 连接到我们的实例

就是这样。你已经成功连接到你的机器。你甚至可以让它继续运行。但要注意,如果你的账户或服务是默认开启的或没有密码 - 毕竟,你已经从你安全的家庭沙盒中取出一个虚拟机,并将其放在了庞大而危险的互联网上。最后一件事:在你的虚拟机运行后,删除存储桶中的文件以节省资源(和金钱)。转换后,这个文件就不再需要了。

我们接下来要讨论的话题是如何通过使用名为尤加利林的应用程序将我们的本地云环境扩展到混合云环境。这是一个非常流行的过程,许多企业公司在扩展基础设施超出本地基础设施时都会经历。此外,当需要时,这也提供了可伸缩性方面的好处 - 例如,当公司需要扩展其测试环境以便对员工正在开发的应用进行负载测试时。让我们看看通过尤加利林和 AWS 如何实现。

使用尤加利林构建混合 KVM 云

尤加利林是一个奇怪的东西,我们并不是指植物。作为一个项目,它旨在弥合私有云服务和 AWS 之间的差距,尤加利林试图在本地环境中重新创建几乎所有 AWS 功能。运行它几乎就像拥有一个与 AWS 兼容的小型本地云,而且它使用的几乎是与 AWS 相同的命令。它甚至使用与 AWS 相同的名称,因此它可以与存储桶等进行交互。这是有意为之,并得到了亚马逊的同意。拥有这样的环境对每个人来说都是一件好事,因为它为开发人员和公司部署和测试他们的实例创造了一个安全的空间。

尤加利林由几个部分组成:

图 13.34 - 尤加利林架构(http://eucalyptus.cloud,官方文档)

从图表中可以看出,尤加利林具有高度可伸缩性。

可用区是可以容纳由集群控制器控制的多个节点的一个部分。区域然后组合成云本身,由云控制器控制。与所有这些相关联的是用户服务部分,它实现了用户与整个尤加利林堆栈之间的交互。

总的来说,尤加利林使用了五个组件,有时会根据图表中的名称来指代它们,有时会根据它们的项目名称来指代,就像 OpenStack 一样:

  • 云控制器CLC)是系统的中心点。它提供 EC2 和 Web 界面,并将每个任务路由到自己。它负责调度、资源分配和计费。每个云都有一个这样的控制器。

  • 集群控制器CC)负责管理每个单独的节点,控制虚拟机及其执行。每个可用区都运行一个。

  • 存储控制器SC)负责提供块级存储,并在集群内为实例和快照提供支持。它类似于 AWS 的 EBS 存储。

  • 节点控制器NC)托管实例及其端点。每个节点都运行一个。

  • eucanetd是尤加利林用来管理云网络的服务,因为我们正在谈论将本地网络扩展到 AWS 云的问题。

当你了解尤加利林时,你会注意到它具有广泛的功能。它可以做到以下几点:

  • 与卷、实例、密钥对、快照、存储桶、镜像、网络对象、标签和 IAM 一起工作。

  • 与负载均衡器一起工作。

  • 作为 AWS 集成工具与 AWS 合作。

这些只是您开始 Eucalyptus 之旅时值得一提的一些功能。Eucalyptus 还有一个名为Euca2ools的额外命令行界面,作为所有主要 Linux 发行版的软件包提供。Euca2ools 是一个额外的工具,提供了 AWS 和 Eucalyptus 之间的完整 API 和 CLI 兼容性。这意味着您可以使用一个工具来管理两者,并执行混合云迁移。该工具是用 Python 编写的,因此它基本上是与平台无关的。如果您想了解更多关于这个接口的信息,请确保您访问wiki.debian.org/euca2ools

如何安装?

如果您正在安装测试机器并且遵循说明,那么安装 Eucalyptus 很容易,我们将在本书的最后一章第十六章中描述,KVM 平台的故障排除指南,这是处理 KVM 故障排除的章节。我们将要做的就是安装一个单机,它将容纳所有节点和整个云的一部分。当然,这远远不足以满足生产环境的需求,因此在 Eucalyptus 网站上,有单机一切都做的指南,以及安装生产级云的指南。请确保您查看以下链接:docs.eucalyptus.cloud/eucalyptus/4.4.5/install-guide-4.4.5.pdf

安装很简单——只需提供一个至少有 120GB 磁盘空间和 16GB 内存的最小安装的 CentOS 7 系统。这是最低要求。如果低于这些要求,您将遇到两种问题:

  • 如果您尝试在内存小于 16GB 的计算机上安装,安装可能会失败。

  • 然而,安装将在磁盘大小小于最低建议的机器上成功,但一旦开始安装部署图像,您几乎立即就会耗尽磁盘空间。

对于生产环境,一切都会改变——存储的最低要求是 160GB,或者对于将运行 Walrus 和 SC 服务的节点,需要 500GB 的存储。节点必须在裸金属上运行;不支持嵌套虚拟化。或者更准确地说,它可以工作,但会抵消云所能提供的任何积极效果。

说了这么多,我们在开始安装之前还有一点要提醒一下——检查新版本的可用性,并且要记住,很可能有比我们在本书中使用的版本更新的版本。

重要提示

在撰写本文时,当前版本是 4.4.5,版本 5 正在积极开发中,即将发布。

安装了基本操作系统后——它必须是一个没有图形界面的核心系统,现在是时候进行实际的 Eucalyptus 安装了。整个系统都是使用FastStart安装的,所以我们唯一需要做的就是从互联网上运行安装程序。链接很贴心地放在了项目的以下 URL 的首页上——eucalyptus.cloud

Eucalyptus 安装成功的一些先决条件:

  • 您必须连接到互联网。这种方式无法进行本地安装,因为一切都是动态下载的。

  • 您还必须有一些 IP 地址可供系统在安装时使用。最低要求是 10 个,它们将随着云一起安装。安装程序将要求范围,并尝试在没有干预的情况下完成一切。

  • 唯一的其他先决条件是一个可用的 DNS 和一些时间。

让我们通过以下命令开始安装:

# bash <(curl -Ls https://eucalyptus.cloud/install) 

如果您是第一次看到安装,它看起来很奇怪。它有点让我们想起了上世纪 90 年代我们使用的一些基于文本的游戏和服务(MUD,IRC):

图 13.35 – Eucalyptus 文本模式安装

图 13.35 - Eucalyptus 文本模式安装

屏幕上的信息将告诉您要遵循哪个日志,如果您想查看实际发生的事情;否则,您可以查看安装程序并等待屏幕上的茶变冷。老实说,在一台体面的机器上,安装可能需要大约 15 分钟,如果您安装所有软件包,则可能需要多 10 分钟。

安装后,Eucalyptus 将为您提供一组默认凭据:

  • 桉树

  • 管理员

  • 密码

重要提示

如果当前的安装程序出现问题,后续问题的解决方案在此 CiA 视频中: <video_URL>。已知存在一些问题,可能在本书上市之前解决,也可能不会解决。确保在安装之前检查eucalyptus.cloud和文档。

信息区分大小写。安装完成后,您可以使用 Web 浏览器连接到该机器并登录。您将使用的 IP 地址是刚刚安装的机器的 IP 地址:

图 13.36 - 桉树登录屏幕

图 13.36 - 桉树登录屏幕

系统安装完成后,在新安装的系统的控制台上,您将看到一条指示,要求运行系统上包含的主教程。教程本身是了解系统外观、关键概念以及如何使用命令行的好方法。您可能遇到的唯一问题是,教程是一组具有一些信息硬编码的脚本。您会立即注意到的一件事是,除非您修复它们,否则指向图像模板的云版本的链接将无法工作 - 链接指向已过期的地址。这很容易解决,但会让您措手不及。

另一方面,也许在您阅读此文时,问题已经解决。关于如何执行此操作以及其所有部分的教程以纯文本模式提供在 Eucalyptus 运行的机器上。它在 GUI 中不可用:

图 13.37 - 启动文本模式 Eucalyptus 主教程

图 13.37 - 启动文本模式 Eucalyptus 主教程

教程在外观上非常基本,但我们喜欢它,因为它为我们提供了对 Eucalyptus 提供的一切的简短但重要的概述:

图 13.38 - 使用主教程学习如何配置 Eucalyptus

图 13.38 - 使用主教程学习如何配置 Eucalyptus

正如您所看到的,一切都有详细说明,因此您可以在短时间内真正学习关键概念。浏览教程-它非常值得。一旦启动系统,您可以从命令行下载一些新的模板图像。此脚本也是从 Web 启动的,并且以大字体写在官方网站上,文字位于以下 URL 的首页上(确保您向下滚动一点)- www.eucalyptus.cloud/:

图 13.39 - 下载图像到我们的 Eucalyptus 云

图 13.39 - 下载图像到我们的 Eucalyptus 云

将此复制粘贴到根提示符中,不久将出现一个菜单,可让您下载可能使用的图像。这是我们见过的模板安装中最简单和最可靠的之一,除了它们包含在初始下载中:

图 13.40 - 简单菜单要求我们选择要安装的图像

图 13.40 - 简单菜单要求我们选择要安装的图像

一次选择一个,它们将包含在图像列表中。

现在让我们切换到 Web 界面,看看它是如何工作的。使用上面写的凭据登录。您将看到一个设计精良的仪表板。右侧是最常用的功能组。左侧是保留给包含所有服务链接的菜单。一旦您将鼠标移开,菜单将自动隐藏,只留下最基本的图标:

图 13.41 - Eucalyptus 管理控制台

图 13.41 - Eucalyptus 管理控制台

我们已经讨论了这个页面上的大部分内容 - 只需查看与 AWS 相关的本章内容,您就会非常熟悉。让我们尝试使用这个控制台。我们将启动一个新实例,只是为了感受一下 Eucalyptus 的工作方式:

  1. 在服务堆栈的左侧有一个诱人的绿色按钮标有启动实例 - 点击它。将出现系统上可用的镜像列表。我们已经使用脚本抓取了一些云镜像,所以有可供选择的内容:图 13.42 - 选择要在 Eucalyptus 云中运行的镜像

图 13.42 - 选择要在 Eucalyptus 云中运行的镜像

我们选择从云镜像中运行 Ubuntu。在选择完您想要的镜像后,从下拉菜单中选择启动。一个新窗口将打开,允许您创建虚拟机。在下拉菜单或实例类型中,我们选择了一个看起来足够强大以运行我们的 Ubuntu 的机器,但基本上,任何具有 1GB 以上 RAM 的实例都可以很好地运行。由于我们只准备了一个实例,所以没有太多要改变:

图 13.43 - 启动新实例向导

图 13.43 - 启动新实例向导

下一个配置屏幕与安全有关。

  1. 我们可以选择使用在 Eucalyptus 云上创建的默认密钥对,也可以创建一个新的。密钥的公共部分仅存储在 Eucalyptus 中,因此只有在安装时下载了密钥,我们才能使用这个密钥对进行身份验证。创建密钥的过程与用于 AWS 的过程完全相同:图 13.44 - 安全配置 - 选择密钥和 IAM 角色

图 13.44 - 安全配置 - 选择密钥和 IAM 角色

点击启动实例按钮后,您的机器应该启动。出于测试目的,我们之前已经启动了另一台机器,所以现在我们有两台正在运行:

图 13.45 - Eucalyptus 云中启动的一对实例

图 13.45 - Eucalyptus 云中启动的一对实例

下一步是尝试创建一个存储桶。

  1. 创建存储桶很容易,并且看起来与 AWS 让您可以做的非常相似,因为 Eucalyptus 试图尽可能与 AWS 相似:

图 13.46 - 创建存储桶

图 13.46 - 创建存储桶

由于 Eucalyptus 不像 AWS 那样复杂,特别是在策略和安全方面,存储桶的安全选项卡较小,但具有一些非常强大的工具,如下面的屏幕截图所示:

图 13.47 - 存储桶安全配置

图 13.47 - 存储桶安全配置

现在我们已经安装、配置并使用了 Eucalyptus,是时候转向本章的下一个主题,即将我们基于 Eucalyptus 的云扩展到 AWS。

使用 Eucalyptus 控制 AWS

您还记得最初显示登录凭据的初始屏幕吗?我们提到您也可以登录到 AWS。从 Eucalyptus 控制台注销并转到登录屏幕。这次点击登录到 AWS

图 13.48 - 通过 Eucalyptus 登录 AWS

图 13.48 - 通过 Eucalyptus 登录 AWS

尝试使用我们在上传图像到 EC2部分创建的身份验证密钥,或者为 AWS IAM 中的管理员用户创建一个新的密钥。将其复制并粘贴到 AWS 凭据中,您将拥有一个完全可用的界面,连接到您的 AWS 帐户。您的仪表板看起来几乎一样,但会显示您的 AWS 帐户的状态:

图 13.49 - Eucalyptus 管理控制台用于 AWS

图 13.49 - Eucalyptus 管理控制台用于 AWS

让我们检查一下我们是否能看到我们的存储桶:

图 13.50 - 检查我们的 AWS 存储桶

图 13.50 - 检查我们的 AWS 存储桶

请注意,我们不仅看到了我们用来测试 AWS KVM 导入的存储桶,还看到了我们运行的区域,在右上角。您的帐户由其密钥名称给出,而不是实际用户;这仅仅是因为我们实际上是以编程方式登录的。我们点击的每个东西都被转换为 API 调用,然后返回的数据被解析并显示给用户。

我们当前停止的实例也在这里,但请记住,只有在您选择最初导入实例的区域时才会看到它。在我们的情况下,它是美国西部,所以我们的实例就在那里:

图 13.51 - 检查我们的 AWS 实例

图 13.51 - 检查我们的 AWS 实例

正如您可能已经注意到的那样,Eucalyptus 是一个多方面的工具,能够为我们提供混合云服务。基本上,Eucalyptus 的关键点之一是它使您达到了与 AWS 兼容的水平。因此,如果您开始将其用作私有解决方案,并且在将来的某个时候开始考虑转移到 AWS,Eucalyptus 已经为您考虑到了。对于基于 KVM 的虚拟机来说,它是一个事实上的标准解决方案。

我们将在这里结束 AWS 集成。毕竟,本章的重点是让您看到 Eucalyptus 如何连接到 AWS。您可能会发现,这个界面缺少 AWS 具有的功能,但同时可能已经足够控制基本的中型基础设施-从一个地方控制存储桶、镜像和实例。经过测试 5.0 beta 1 版本后,我们可以明确告诉您,完整的 5.0 版本一推出应该是相当大的升级。beta 版本已经有许多额外的选项,我们对完整版本的发布感到非常兴奋。

总结

在本章中,我们涵盖了许多主题。我们将 AWS 作为云解决方案进行了介绍,并对其进行了一些很酷的操作-我们转换了我们的虚拟机,以便我们可以在其中运行,并确保一切正常。然后我们转向 Eucalyptus,以查看我们如何将其用作本地云环境的管理应用程序,以及如何将其用于扩展我们的现有环境到 AWS。

下一章将带我们进入使用 ELK 堆栈监视 KVM 虚拟化的世界。这是一个非常重要的话题,特别是随着公司和基础设施的规模增长-您无法通过手动监视所有可能的服务来跟上 IT 服务的有机增长。ELK 堆栈将帮助您解决这个问题-您将在下一章中了解到它有多大帮助。

问题

  1. AWS 是什么?

  2. EC2、S3 和 IAM 是什么?

  3. S3 存储桶是什么?

  4. 我们如何将虚拟机迁移到 AWS?

  5. 我们使用哪个工具将原始图像上传到 AWS?

  6. 我们如何作为用户向 AWS 进行身份验证?

  7. Eucalyptus 是什么?

  8. Eucalyptus 中的关键服务是什么?

  9. 可用区是什么?故障域是什么?

  10. 为虚拟化、云和 HPC 环境提供 Tier-0 存储服务的基本问题是什么?

进一步阅读

有关本章涵盖内容的更多信息,请参考以下链接:

第十四章:监视 KVM 虚拟化平台

当你从只有几个对象需要管理的环境(例如 KVM 主机)转移到需要管理数百个对象的环境时,你会开始问自己一些非常重要的问题。其中一个最突出的问题是,“我要如何在不做大量手动工作并且有一些 GUI 报告选项的情况下监视我的数百个对象?”这个问题的答案就是 Elasticsearch、Logstash、Kibana(ELK)堆栈。在本章中,我们将看到这些软件解决方案对你和基于 KVM 的环境能做些什么。

这些晦涩的名字背后是一些技术,它们可以解决在运行多个服务器时可能遇到的许多问题。虽然你可以运行 ELK 堆栈来监视一个服务,但这样做是没有意义的。本章提供的建议和解决方案适用于涉及多个设备和服务器的所有项目,不仅仅是在 KVM 上运行的项目,而是任何能够产生任何类型日志的项目。我们将从如何监视 KVM 作为虚拟化平台的基础知识开始。然后,我们将继续讨论 ELK 堆栈,包括其构建模块和安装,然后再进行高级配置和定制。

在本章中,我们将涵盖以下主题:

  • 监视 KVM 虚拟化平台

  • 开源 ELK 解决方案简介

  • 设置和集成 ELK 堆栈

  • 配置数据收集器和聚合器

  • 创建自定义利用报告

  • 让我们开始吧!

监视 KVM 虚拟化平台

当我们谈论运行任何类型的处理系统时,我们很快就会遇到监视和确保我们的系统在给定的一组参数内运行的问题。

当我们创建一个运行工作负载的系统时,它将不可避免地产生一些关于正在发生的一切的数据。这些数据的范围几乎是无限的——一个只在线而没有单个“有用”任务运行的服务器将创建一些日志或服务数据,比如已使用的内存量、正在启动或停止的服务、剩余的磁盘空间量、连接和断开的设备等。

当我们开始运行任何有用的任务时,日志只会变得越来越大。

拥有一个良好且详细的日志意味着我们可以找到系统当前的运行情况;它是否正常运行,我们是否需要做一些事情让它运行得更好?如果发生了意外情况,日志可以帮助我们确定实际出了什么问题,并指引我们找到解决方案的方向。正确配置的日志甚至可以帮助我们在问题开始制造麻烦之前发现错误。

假设你有一个系统,每周都在变得越来越慢。让我们进一步假设我们的问题出在我们在系统上安装的应用程序的内存分配上。但也让我们假设这个内存分配是不固定的,而是随着使用系统的用户数量而变化的。如果你在任何时间点看一下,你可能会注意到用户数量和分配的内存。但如果你只在不同的时间测量,你会很难理解内存和用户数量之间有什么样的相关性——分配的内存量是否与用户数量成线性关系,还是呈指数增长?如果我们能看到 100 个用户使用 100MB 内存,那是否意味着 1000 个用户将使用 1000MB?

但假设我们正在记录内存量和用户数量在等间隔时间内的情况。

我们并没有做什么复杂的事情;每隔几秒,我们都会记录测量时间、分配的内存量和使用系统的用户数量。我们正在创建一个称为数据点的数据集。使用数据点与我们在前面的例子中所做的没有什么不同,但一旦我们有了数据集,我们就可以进行趋势分析。基本上,我们可以分析不同的时间段,比较用户数量以及他们实际使用的内存量。即使我们现在没有问题,这将为我们提供有关我们的系统实际如何使用内存以及我们何时出现问题的重要信息。

这种方法甚至可以帮助我们找到和解决那些不明显的问题,比如每个月备份花费太长时间才能完成,而其他时间都正常工作。这种能力使我们能够发现趋势并分析数据和系统性能,这就是日志记录的全部意义。

简而言之,任何类型的监控都归结为两件事:从我们试图监控的事物中收集数据,然后分析这些数据。

监控可以是在线的,也可以是离线的。在线监控在我们试图创建某种警报系统或者试图建立能够响应过程变化的自我纠正系统时非常有用。然后,我们可以尝试纠正问题或关闭或重新启动系统。在线监控通常由运维团队使用,以确保一切运行顺利,并记录系统可能出现的问题。

离线监控要复杂得多。离线监控使我们能够将所有数据收集到日志中,稍后分析这些日志,并推断趋势,找出如何改进系统。但事实是,它总是延迟的,因为离线方法要求我们下载然后分析日志。这就是为什么我们更喜欢实时日志摄入,这是需要在线完成的事情。这就是为什么学习 ELK 堆栈如此重要。

通过将所有这些小组件-实时日志摄入、搜索、分析和报告-组合成一个更大的堆栈,ELK 使我们能够更容易地实时监控我们的环境。让我们来学习一下。

开源 ELK 解决方案简介

我们之前提到 ELK 代表 Elasticsearch、Logstash 和 Kibana,因为这三个应用程序或系统是完整监控和报告解决方案的基本组成部分。每个部分都有自己的目的和功能-Logstash 将所有数据收集到一个一致的数据库中,Elasticsearch 能够快速浏览 Logstash 存储的所有数据,而 Kibana 则将搜索结果转化为信息丰富且视觉上吸引人的内容。说了这么多,ELK 最近更改了其名称。尽管它仍然被称为 ELK 堆栈,几乎整个互联网都会这样称呼它,但 ELK 堆栈现在被称为 Elastic Stack,原因是在撰写本文时,堆栈中还包括另一个第四个组件。这个组件被称为 Beats,它代表整个系统的重要补充。

但让我们从头开始,尝试以其创建者描述的方式描述整个系统。

Elasticsearch

首先创建并在社区中获得关注的组件是 Elasticsearch,它被创建为一个灵活、可扩展的系统,用于索引和搜索大型数据集。Elasticsearch 被用于成千上万种不同的用途,包括在文档、网站或日志中搜索特定内容。它的主要卖点和许多人开始使用它的原因是它既灵活又可扩展,同时速度极快。

当我们考虑搜索时,我们通常会考虑创建某种查询,然后等待数据库给我们一些形式的答案。在复杂的搜索中,问题通常是等待,因为不断调整我们的查询并等待它们产生结果是很累人的。由于许多现代数据科学依赖于非结构化数据的概念,这意味着我们需要搜索的许多数据没有固定的结构,或者根本没有结构,因此在这些数据池中创建快速搜索的方法是一个棘手的问题。

想象一下,你需要在图书馆里找到一本特定的书。再想象一下,你没有所有书籍、作者、出版信息和其他一切正常图书馆都有的数据库;你只被允许搜索所有的书籍。

拥有一种能够识别这些书中的模式并告诉您答案的工具,比如谁写了这本书?在所有超过 200 页的书中 KVM 被提到了多少次?是一件非常有用的事情。这就是一个好的搜索解决方案所做的事情。

如果我们想要快速有效地管理一个集群或多个集群的物理和虚拟服务器,那么能够搜索运行 Apache web 服务器并且在某个 IP 地址请求的某个页面上出现问题的机器是至关重要的。

当我们监控系统信息时,即使是单个数据点,比如跨数百台主机的内存分配,也是一个问题,即使是呈现这些数据也是一个问题,而在实时搜索中查找这些数据几乎是不可能的,没有正确的工具。

Elasticsearch 确实做到了这一点:它为我们快速浏览大量几乎没有结构的数据创造了一种方式,然后得出有意义的结果。Elasticsearch 的不同之处在于其可扩展性,这意味着您可以使用它在笔记本电脑上创建搜索查询,然后在多节点实例上运行这些查询,搜索 PB 级的数据。

Elasticsearch 也很快,这不仅节省时间。能够更快地获取搜索结果的能力使您能够通过创建和修改查询,然后理解其结果来更多地了解您的数据。

由于这只是对 ELK 实际做的事情的简单介绍,我们将转到下一个组件 Logstash,并稍后回到搜索。

Logstash

Logstash 有一个简单的目的。它旨在能够消化任意数量的生成数据的日志和事件,并将它们存储以备将来使用。存储后,它可以以多种格式导出,如电子邮件、文件、HTTP 等。

Logstash 的重要之处在于它的多功能性,可以接受不同的输入流。它不仅限于使用日志;甚至可以接受诸如 Twitter feeds 之类的东西。

Kibana

旧 ELK 堆栈的最后一部分是 Kibana。如果 Logstash 是存储,Elasticsearch 是计算,那么 Kibana 就是输出引擎。简而言之,Kibana 是一种利用 Elasticsearch 查询结果创建视觉上令人印象深刻且高度可定制布局的方法。尽管 Kibana 的输出通常是某种仪表板,但其输出可以是许多东西,取决于用户创建新布局和可视化数据的能力。说了这么多,不要害怕-互联网提供了几乎每种想象的场景的至少部分,如果不是全部的解决方案。

接下来,我们将介绍 ELK 堆栈的基本安装,展示它的功能,指导您正确的方向,并演示最受欢迎的beats之一-metricbeat

使用 ELK 堆栈在许多方面与运行服务器相同-你需要做什么取决于你实际想要实现什么;只需几分钟就可以让 ELK 堆栈运行起来,但真正的努力却是从那时开始。

当然,为了充分理解 ELK 堆栈在实际环境中的使用方式,我们需要先部署和设置它。我们接下来就要做这件事。

建立和集成 ELK 堆栈

值得庆幸的是,几乎我们需要安装的所有东西都已经由 Elasticsearch 团队准备好了。除了 Java,其他所有东西都在他们的网站上得到了很好的整理和记录。

您需要做的第一件事是安装 Java - ELK 依赖于 Java 运行,因此我们需要安装它。Java 有两种不同的安装候选项:来自 Oracle 的官方安装和开源的 OpenJDK。由于我们试图留在开源生态系统中,我们将安装 OpenJDK。在本书中,我们使用 CentOS 8 作为我们的平台,因此将广泛使用yum软件包管理器。

让我们从先决条件软件包开始。我们安装 Java 所需的唯一先决条件软件包是java-11-OpenJDK-devel软件包(用当前的 OpenJDK 版本替换“11”)。因此,在这里,我们需要运行以下命令:

yum install java-11-openjdk-devel

发出该命令后,您应该会得到如下结果:

图 14.1 - 安装主要先决条件之一 - Java

图 14.1 - 安装主要先决条件之一 - Java

安装完成后,我们可以通过运行以下命令来验证安装是否成功以及 Java 是否正常工作:

java -version

这是预期的输出:

图 14.2 - 检查 Java 的版本

图 14.2 - 检查 Java 的版本

输出应该是当前的 Java 版本和没有错误。除了验证 Java 是否正常工作之外,这一步还很重要,以便验证 Java 的路径是否设置正确 - 如果您在其他发行版上运行,可能需要手动设置路径。

现在 Java 已经安装并准备就绪,我们可以继续安装 ELK 堆栈。下一步是配置 Elasticsearch 和其他服务的安装源:

  1. 我们需要在/etc/yum.repos.d/中创建一个名为elasticsearch.repo的文件,其中将包含有关我们存储库的所有信息:
[Elasticsearch-7.x]
name=Elasticsearch repository for 7.x packages
baseurl=https://artifacts.elastic.co/packages/7.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-Elasticsearch
enabled=1
autorefresh=1
type=rpm-md

保存文件。这里重要的是仓库是 GPG 签名的,所以我们需要导入它的密钥并应用它,以便在下载时可以验证软件包。

您要安装的文件不是免费软件。Elasticsearch 有两个不同的免费版本和一个付费订阅模型。使用此存储库中的文件将获得基于订阅的安装,该安装将在basic模式下运行,该模式是免费的。在撰写本文时,Elastic 有四种订阅模型 - 一种是基于 Apache License 2.0 的开源模型,免费;其余的都是闭源的,但提供额外的功能。目前,这些订阅被命名为 Basic、Gold 和 Platinum。Basic 是免费的,而其他模型需要每月付费订阅。

你肯定会问为什么你应该选择开源而不是 Basic,或者反过来,因为它们都是免费的。虽然它们都有相同的核心,但 Basic 更先进,因为它提供了核心安全功能和更多在日常使用中可能很重要的东西,特别是如果你追求 Kibana 可视化。

  1. 让我们继续安装并导入必要的 GPG 密钥:
rpm --import https://artifacts.elastic.co/GPG-KEY-elasticsearch
  1. 现在,我们准备在系统端进行一些维护工作,并获取存储库系统中的所有更改:
elasticsearch by running this command:

elasticsearch 或任何其他服务都不会自动启动或启用。我们必须为它们中的每一个手动执行此操作。现在让我们来做这件事。


  1. 启动和启用服务的过程是标准的,对所有三个服务都是相同的:
sudo systemctl daemon-reload 
sudo systemctl enable elasticsearch.service 
sudo systemctl start elasticsearch.service 
sudo systemctl status elasticsearch.service 
sudo yum install kibana
sudo systemctl status kibana.service 
sudo systemctl enable kibana.service 
sudo systemctl start kibana.service 
sudo yum install logstash
sudo systemctl start logstash.service 
sudo systemctl enable logstash.service 

最后要做的就是安装beats,这些服务通常安装在受监视的服务器上,并且可以配置为创建和发送系统上的重要指标。现在让我们来做这件事。

  1. 为了演示目的,我们将安装它们所有,尽管我们不会使用它们所有:
sudo yum install filebeat metricbeat packetbeat heartbeat-elastic auditbeat

完成后,我们应该有一个功能齐全的系统。让我们快速回顾一下。

Kibana 和 Elasticsearch 都作为 Web 服务运行在不同的端口上。我们将通过 Web 浏览器与 Kibana 进行交互(使用 URL http://localhost:9200http://localhost:5601),因为这是可视化发生的地方:

图 14.3 – 检查 Elasticsearch 服务

图 14.3 – 检查 Elasticsearch 服务

现在,我们可以在端口5601上连接到 Kibana:

图 14.4 – 成功连接到 Kibana

图 14.4 – 成功连接到 Kibana

部署过程已经成功完成。我们的下一个逻辑步骤将是创建一个工作流程。让我们现在就做。

工作流程

在本节中,我们将建立一个工作流程 – 我们将创建日志和指标,这些日志和指标将被摄入 Logstash,通过 Elasticsearch 查询,然后在 Kibana 中进行可视化呈现。

默认情况下,Kibana 运行在端口5601上,这可以在配置中更改。

但这对我意味着什么?这对 KVM 意味着什么?

使用 Elastic Stack 的最大卖点是灵活性和简单的展示方式。无论我们在几十个 KVM 主机内运行一台、10 台还是 1,000 台机器,我们都可以在生产中以相同的方式对待它们,并建立稳定的监控工作流程。使用极其简单的脚本,我们可以创建完全定制的指标并快速显示它们,我们可以观察趋势,甚至可以创建一个几乎实时的监控系统。所有这些,基本上都是免费的。

让我们创建一个简单的监视器,用于为运行 ELK 的主机系统转储系统指标。我们已经安装了 Metricbeat,所以唯一剩下的就是配置服务将数据发送到 Elasticsearch。数据发送到 Elasticsearch,而不是 Logstash,这仅仅是因为服务之间的相互操作方式。可以将数据同时发送到 Logstash 和 Elasticsearch,因此我们需要在这里做一点快速的解释。

Logstash 从定义上来说是一个存储发送到它的数据的服务。Elasticsearch 搜索该数据并与 Logstash 通信。如果我们将数据发送到 Logstash,我们并没有做错什么;我们只是为以后的分析转储数据。但是,将数据发送到 Elasticsearch 还给了我们一个功能 – 我们不仅可以发送数据,还可以以模板的形式发送关于数据的信息。

另一方面,Logstash 具有在接收数据后并在数据存储之前执行数据转换的能力,因此,如果我们需要执行诸如解析 GeoIP 信息、更改主机名称等操作,我们可能会将 Logstash 作为我们的主要目的地。牢记这一点,不要设置 Metricbeat 以便将数据同时发送到 Elasticsearch 和 Logstash;否则,您将只会在数据库中获得重复的数据。

使用 ELK 很简单,我们已经在安装过程中毫不费力地进行了这一步。当我们开始分析数据时,真正的问题就开始了。即使是来自 Metricbeat 的简单且完全格式良好的数据,也可能很难可视化,特别是如果我们是第一次这样做的话。为 Elasticsearch 和 Kibana 准备的预制模板可以节省大量时间。

看一下以下的屏幕截图:

图 14.5 – Metricbeat 仪表板

图 14.5 – Metricbeat 仪表板

只需不到 10 分钟的设置,就可以获得一个像这样的完整仪表板。让我们一步一步地进行。

我们已经安装了 Metricbeat,只需要配置它,但在此之前,我们需要配置 Logstash。我们只需要定义一个pipeline

那么,数据如何被转换?

到目前为止,我们还没有详细介绍 Logstash 的功能,但是为了创建我们的第一组数据,我们需要了解 Logstash 的一些内部工作原理。Logstash 使用管道的概念来定义数据在接收到数据后发生的情况,以及在将数据发送到 Elasticsearch 之前。

每个管道都有两个必需的元素和一个可选的元素:

  • 输入始终是管道中的第一个,并且旨在从源接收数据。

  • 输出是管道中的最后一个元素,并且输出数据。

  • 过滤器是一个可选元素,位于输入和输出之间,以便根据我们可以定义的规则修改数据。

所有这些元素都可以从插件列表中选择,以便我们创建一个针对特定目的调整的最佳管道。让我们一步一步地进行。

我们需要做的就是取消注释配置文件中定义的一个管道,该文件位于/etc/logstash文件夹中。

整个堆栈使用 YAML 作为配置文件结构的标准,因此每个配置文件都以.yml扩展名结尾。这一点很重要,以便理解所有没有此扩展名的文件都在这里作为样本或某种配置模板;只有带有.yml扩展名的文件才会被解析。

要配置 Logstash,只需打开logstash.yml并取消注释与第一个管道main相关的所有行。我们不需要做其他事情。该文件本身位于/etc/logstash文件夹中,在您进行这些更改后应该看起来像这样:

图 14.6 - logstash.yml 文件

图 14.6 - logstash.yml 文件

我们需要做的下一件事是配置 Metricbeat。

配置数据收集器和聚合器

在之前的步骤中,我们成功部署了 Metricbeat。现在,我们需要开始实际的配置。因此,让我们一步一步地进行配置过程:

  1. 转到/etc/metricbeat并打开metricbeat.yml

取消注释定义elasticsearch为 Metricbeat 目标的行。现在,我们需要改变一件事。找到包含以下内容的行:

setup.dashboards.enabled: false 

将前面的行更改为以下内容:

setup.dashboards.enabled: true

我们需要这样做来加载仪表板,以便我们可以使用它们。

  1. 其余的配置都是通过命令行完成的。Metricbeat 有几个可以运行的命令,但最重要的是以下命令:
metricbeat setup

这个命令将通过初始设置。设置的这一部分可能是整个初始配置中最重要的事情 - 将仪表板模板推送到 Kibana。这些模板将使您能够在几次点击之内启动和运行,而不是学习如何进行可视化和从头开始配置。您最终将不得不这样做,但是对于这个示例,我们希望尽快让事情运行起来。

  1. 您现在需要的另一个命令是以下命令:
logstash and kvm:

metricbeat 模块启用 kvm

metricbeat 模块启用 logstash


logstash模块的命名令人困惑,因为它并不打算将数据推送到 Logstash;相反,它的主要目的是报告 Logstash 服务,并使您能够通过 Logstash 监视它。听起来困惑吗?让我们重新表达一下:这个模块使 Logstash 能够监视自己。或者更准确地说,它使 beats 能够监视 Elastic 堆栈的一部分。

KVM 模块是一个模板,将使您能够收集不同的与 KVM 相关的指标。

就是这样。作为预防措施,键入以下命令以检查 Metricbeat 的配置:

metricbeat test config 

如果前面的命令运行正常,请使用以下命令启动 Metricbeat 服务:

systemctl start metricbeat 

您现在有一个正在运行的服务,正在收集您主机上的数据 - 与运行 KVM 并将数据转储到 Elasticsearch 的主机相同。这是至关重要的,因为我们将使用所有这些数据来创建可视化和仪表板。

在 Kibana 中创建图表

现在,在浏览器中使用localhost:5601作为地址打开 Kibana。屏幕左侧应该有一个基于图标的菜单。转到堆栈管理,查看Elasticsearch 索引管理

应该有一个名为metricbeat-的活动索引。在这个特定的例子中,将是 metricbeat 的当前版本和日志文件中第一个条目的日期。这完全是任意的,只是一个默认值,确保您知道此实例何时启动。

与此名称相同的一行中应该有一些数字:我们感兴趣的是文档计数 - 数据库持有的对象数量。暂时来说,如果不是零,我们就没问题。

现在,转到仪表板页面,打开Metricbeat 系统概览 ECS仪表板。它将显示许多可视化小部件,代表 CPU、内存、磁盘和网络使用情况:

图 14.7 - ECS 仪表板概览

图 14.7 - ECS 仪表板概览

现在,您可以点击“最近 15 分钟”。在您点击刷新按钮之后,新数据应该显示在页面上。

有了这些,您现在已经了解了足够的关于 Kibana 的知识,但我们仍然无法可视化 KVM 数据。下一步是创建一个覆盖这一点的仪表板。

但在我们这样做之前,想想我们到目前为止学到的东西可以做什么。您不仅可以监视安装了 KVM 堆栈的本地系统,还可以监视任何能够运行 Metricbeat 的系统。您唯一需要知道的是 ELK 堆栈的 IP 地址,以便您可以向其发送数据。Kibana 将自动处理来自不同系统的所有不同数据的可视化,我们稍后将看到。

创建自定义利用率报告

自版本 7 以来,Elastic stack 引入了强制性检查,旨在确保最低安全性和功能合规性,特别是一旦我们开始在生产中使用 ELK。

乍一看,这些检查可能会让您困惑 - 我们引导您完成的安装将正常工作,突然间,当您尝试配置一些设置时,一切都会失败。这是有意的。

在以前的版本中,这些检查是执行的,但如果错过或配置错误了配置项,它们会被标记为警告。从第 7 版开始,如果系统处于生产状态且配置不正确,这些检查将触发错误。这种状态自动意味着如果配置不正确,您的安装将无法工作。

ELK 有两种不同的操作模式:开发生产。在第一次安装时,假定您处于开发模式,因此大多数功能都可以直接使用。

一旦进入生产模式,事情就会发生很大变化 - 安全设置和其他配置选项需要明确设置,以使堆栈正常运行。

诀窍在于没有明确的模式更改 - 与之相关的生产设置和检查是由配置中的一些设置触发的。这个想法是一旦您重新配置了一些可能从安全角度重要的东西,您需要正确地重新配置一切。这将防止您忘记一些在生产中可能成为大问题的东西,并迫使您至少有一个稳定的配置作为起点。有一个关闭检查的开关,但在任何情况下都不建议使用。

需要注意的主要事项是绑定接口——默认安装将所有内容绑定到localhost或本地环回接口,这对于生产完全没问题。一旦您的 Elasticsearch 能够形成集群,并且可以通过简单地重新配置 HTTP 和传输通信的网络地址来触发,您必须注意检查并重新配置整个系统以使其正常工作。请参阅 https://www.elastic.co/上提供的文档以获取更多信息,从www.elastic.co/guide/index.html开始。

例如,在 Elastic stack 中配置集群以及所有相关内容远远超出了本书的范围——我们将在我们的配置中保持在单节点集群的范围内。这个解决方案专门针对可以使用单个节点或更准确地说,覆盖堆栈所有功能的单个机器实例的情况而创建的。在正常部署中,您将在集群中运行 Elastic stack,但实施细节将由您的配置和其需求决定。

我们需要警告您两个关键点——防火墙和 SELinux 设置由您决定。所有服务使用标准的 TCP 进行通信。不要忘记,为了使服务运行,网络必须正确配置。

既然我们已经解决了这个问题,让我们回答一个简单的问题:要使 Elastic stack 与多个服务器一起工作,我们需要做些什么?让我们逐步讨论这种情况。

Elasticsearch

转到配置文件(/etc/elasticsearch/elasticsearch.yml)并在发现部分中添加一行:

discovery.type: single-node

使用此部分不是强制性的,但在以后必须返回到配置时会有所帮助。

此选项将告诉 Elasticsearch,您的集群中只有一个节点,并且它将使 Elasticsearch 忽略与集群及其网络相关的所有检查。此设置还将使此节点自动成为主节点,因为 Elasticsearch 依赖于具有控制集群中所有内容的主节点。

更改network.host:下的设置,使其指向 Elasticsearch 将要在其上可用的接口的 IP 地址。默认情况下,它指向 localhost,并且从网络上不可见。

重新启动 Elasticsearch 服务,并确保它正在运行且没有生成错误:

sudo systemctl restart elasticsearch.service 

一旦它正常工作,检查服务是否从本地机器正常运行。最简单的方法是这样做:

curl -XGET <ip_address>:9200 

响应应该是一个以.json格式的文本,包含有关服务器的信息。

重要提示

Elastic stack 有三(或四)部分或服务。在我们所有的示例中,其中三个(Logstash、Elasticsearch 和 Kibana)都在同一台服务器上运行,因此不需要额外的配置来适应网络通信。在正常配置中,这些服务可能会在独立的服务器上运行,并且根据我们尝试监视的服务的工作负载和配置,可能会运行多个实例。

Logstash

Logstash 的默认安装文件名为logstash-sample.conf,位于/etc/logstash文件夹中。这包含了一个简单的 Logstash 管道,用于在 Logstash 作为 beats 的主要目的地时使用。稍后我们会讨论这一点,但目前,将此文件复制到/etc/logstash/conf.d/logstash.conf,并在刚刚复制的文件中更改 Elasticsearch 服务器的地址。它应该看起来像这样:

hosts => ["http://localhost:9200"]. 

localhost更改为服务器的正确 IP 地址。这将使 Logstash 侦听端口5044并将数据转发到 Elasticsearch。重新启动服务并验证其运行:

sudo systemctl restart logstash.service 

现在,让我们学习如何配置 Kibana。

Kibana

Kibana 也有一些需要更改的设置,但在这样做时,有几件事情需要记住:

  • 单独来说,Kibana 是一个通过 HTTP 协议(或根据配置的情况是 HTTPS)提供可视化和数据的服务。

  • 同时,Kibana 使用 Elasticsearch 作为其后端,以便获取和处理数据。这意味着我们必须关心两个 IP 地址:

a) 第一个是用于显示 Kibana 页面的地址。默认情况下,这是 localhost 的端口5601

b) 另一个 IP 地址是将处理查询的 Elasticsearh 服务。这个的默认值也是 localhost,但它需要更改为 Elasticsearch 服务器的 IP 地址。

包含配置详细信息的文件是/etc/kibana/kibana.yml,您至少需要进行以下更改:

  • server.host:这需要指向 Kibana 将拥有其页面的 IP 地址。

  • elasticsearch.hosts:这需要指向将执行查询的主机(或集群,或多个主机)。

重新启动服务,就可以了。现在,登录 Kibana 并测试一切是否正常。

为了让您更加熟悉 Kibana,我们将尝试建立一些基本的系统监控,并展示如何监控多个主机。我们将配置两个beats:Metricbeat 和 Filebeat。

我们已经配置了 Metricbeat,但是它是为本地主机配置的,所以让我们先解决这个问题。在/etc/metricbeat/metricbeat.yml文件中,重新配置输出以将数据发送到elasticsearch地址。您只需要更改主机 IP 地址,因为其他所有内容保持不变:

# Array of hosts to connect to
Hosts: ["Your-host-IP-address:9200"]

确保将Your-host-IP-address更改为您正在使用的 IP 地址。

配置 filebeat 基本上是相同的;我们需要使用/etc/filebeat/filebeat.yml进行配置。由于所有的 beats 都使用相同的概念,filebeat 和 metricbeat(以及其他 beats)都使用模块来提供功能。在两者中,核心模块都被命名为system,因此在 filebeat 中使用以下命令启用它:

filebeat modules enable system

使用以下命令进行 metricbeat:

metricbeat modules enable system

我们之前提到过,在第一个示例中,但是您可以通过运行以下命令来测试您的配置:

filebeat test config

您还可以使用以下命令:

metricbeat test config

两个 beats 都应该显示配置为ok

此外,您还可以检查输出设置,这将显示输出设置实际上是什么以及它们是如何工作的。如果您只使用本书来配置系统,您应该会收到一个警告,提醒您连接没有 TLS 保护,但除此之外,输出应该在配置文件中设置的 IP 地址上工作。

要测试输出,请使用以下命令:

filebeat test output

您还可以使用以下命令:

metricbeat test output

对于您打算监视的每个系统都要重复所有这些步骤。在我们的示例中,我们有两个系统:一个正在运行 KVM,另一个正在运行 Kibana。我们还在另一个系统上设置了 Kibana,以测试 syslog 以及它通知我们注意到的问题的方式。

我们需要配置 filebeat 和 metricbeat 以将数据发送到 Kibana。我们将编辑filebeat.ymlmetricbeat.yml文件,通过更改两个文件的以下部分来实现这一目的:

setup.kibana
   host: "Your-Kibana-Host-IP:5601" 

在运行 beats 之前,在新安装中,您需要将仪表板上传到 Kibana。您只需要为每个 Kibana 安装执行一次此操作,并且您只需要从要监视的系统中的一个系统执行此操作 - 模板将起作用,无论它们是从哪个系统上传的;它们只会处理进入 Elasticsearch 的数据。

要做到这一点,请使用以下命令:

filebeat setup

您还需要使用以下命令:

metricbeat setup

这将需要几秒钟甚至一分钟,这取决于您的服务器和客户端。一旦它说它创建了仪表板,它将显示所有创建的仪表板和设置。

现在,您几乎可以开始查看 Kibana 将显示的所有数据了:

图 14.8 - 来自 Kibana 仪表板的摘录

在我们开始之前,还有一件事情您需要了解有关时间和时间戳。右上角的日期/时间选择器将让您选择自己的时间跨度或预定义的间隔之一:

图 14.9-日期/时间选择器

图 14.9-日期/时间选择器

重要提示

始终记住显示的时间是从您访问 Kibana 的浏览器/机器的时区本地时间。

日志中的所有时间戳都是发送日志的机器的本地时间。Kibana 将尝试匹配时区并转换生成的时间戳,但如果您监视的机器上的实际时间设置不匹配,那么在尝试建立事件时间线时就会出现问题。

假设您已经运行了 filebeat 和 metricbeat。您可以用这些做什么?事实证明,很多:

  • 首先要做的是发现您的数据中有什么。在 Kibana 中按下发现按钮(看起来像一个小指南针)。如果一切正常,右侧应该显示一些数据。

  • 在您刚刚单击的图标右侧,将填充一个垂直空间,其中包含 Kibana 从数据中获取的所有属性。如果您没有看到任何内容或缺少某些内容,请记住您选择的时间跨度会缩小在此视图中显示的数据。尝试重新调整间隔到最近 24 小时最近 30 天

一旦属性列表显示出来,您可以快速确定每个属性在您刚刚选择的数据中出现了多少次-只需单击任何属性并选择可视化。还要注意,一旦单击属性,Kibana 会显示最近 500 条记录中前五个不同的值。如果您需要知道例如哪些主机显示数据,或者有多少不同的操作系统版本,这是一个非常有用的工具。

特定属性的可视化只是一个开始-注意一旦悬停在属性名称上,一个名为添加的按钮会出现?尝试单击它。右侧将开始形成一个表,其中只包含您选择的属性,按时间戳排序。默认情况下,这些值不会自动刷新,因此时间戳将被固定。您可以选择任意数量的属性,并保存此列表或稍后打开它。

我们需要查看的下一件事是单独的可视化。我们不会详细介绍,但您可以使用预定义的可视化类型从数据集中创建自己的可视化。同时,您不仅限于使用预定义的内容-还可以使用 JSON 和脚本进行更多的自定义。

我们需要了解的下一件事是仪表板。

根据特定数据集,或者更准确地说,根据您正在监视的特定一组机器,其中一些将具有仅涵盖特定机器执行或具有的属性。一个例子是 AWS 上的虚拟机-它们将具有一些仅在 AWS 上下文中有用的信息。这在我们的配置中并不重要,但您需要了解数据中可能存在一些特定机器集的唯一属性。首先,选择一个系统指标;要么选择System Navigation ECS用于 metricbeat,要么选择Dashboards ECS用于 filebeat。

这些仪表板以多种方式显示有关系统的大量信息。尝试点击并查看您能推断出什么。

metricbeat 仪表板更侧重于运行系统并关注内存和 CPU 分配情况。您可以单击并过滤大量信息,并以不同的方式呈现。以下是 metricbeat 的屏幕截图,以便您对其外观有一个大致的了解:

图 14.10-metricbeat 仪表板

图 14.10-metricbeat 仪表板

filebeat 仪表板更多地面向分析发生了什么并建立趋势。让我们从 filebeat 仪表板中的一些摘录开始检查,首先是 syslog 条目部分:

图 14.11 - filebeat syslog 条目部分

图 14.11 - filebeat syslog 条目部分

乍一看,您可以注意到一些事情。我们正在显示两个系统的数据,而且数据是部分的,因为它覆盖了我们设置的一部分时间间隔。此外,我们可以看到一些进程比其他进程更频繁地运行并生成日志。即使我们对特定系统一无所知,我们现在也可以看到一些进程出现在日志中,而它们可能不应该出现:

图 14.12 - filebeat 交互式圆环图

图 14.12 - filebeat 交互式圆环图

让我们来看看setroubleshoot。点击进程名称。在打开的窗口中,点击放大镜。这将隔离只有这个进程,并在屏幕底部显示它的日志。

我们可以快速看到setroubleshoot在哪个主机上 - 包括频率和原因 - 写入日志。这是发现潜在问题的快速方法。在这种特殊情况下,显然应该在这个系统上采取一些行动,重新配置 SELinux,因为它生成异常并阻止一些应用程序访问文件。

让我们沿着垂直导航栏继续,并指出一些其他有趣的功能。

从上到下,下一个重要功能是Canvas - 它使我们能够使用我们正在收集的数据创建实时演示文稿。界面类似于其他演示程序所期望的,但重点是直接在幻灯片中使用数据并几乎实时生成幻灯片。

接下来是地图。这是 7.0 版本的一个新功能,它允许我们创建数据的地理演示。

机器学习是下一个功能 - 它使您能够操纵数据并使用它来“训练”过滤器,并从中创建管道。

基础设施也很有趣 - 当我们提到仪表板时,我们谈论的是灵活性和定制。基础设施是一个模块,它使我们能够以最小的努力进行实时监控并观察重要的指标。您可以将重要数据显示为表格、气球状界面或图表。数据可以被平均或以其他方式呈现,所有这些都是通过一个非常直观的界面完成的。

Heartbeat 是另一个高度专业化的仪表板 - 正如其名称所示,这是跟踪和报告正常运行时间数据的最简单方法,并迅速注意到是否有什么东西已经离线。库存主机需要安装 Heartbeat 服务,以便监视我们打算监视的每个系统。

SIEM值得更深入的解释:如果我们把仪表板看作是多功能的,那么 SIEM 恰恰相反;它被创建为能够跟踪所有可以归类为安全相关的系统上的所有事件。当搜索 IP、网络事件、源、目的地、网络流和所有其他数据时,这个模块将解析数据,并创建简单易懂的报告,报告了您正在监视的机器上发生了什么。它甚至提供异常检测,这是一项付费功能,需要最高付费等级才能使用。

堆栈监视器是另一个值得注意的仪表板,因为它使您实际上可以看到 Elastic 堆栈的所有不同部分发生了什么。它将显示所有服务的状态、它们的资源分配和许可证状态。日志功能特别有用,因为它跟踪堆栈生成了多少种类型的日志,并且如果有任何问题,它可以快速指出问题。

该模块还为服务生成统计信息,使我们能够了解系统如何进行优化。

管理,底部的最后一个图标已经提到过了 - 它使集群及其部分的管理成为可能。这是我们可以看到是否有我们期望的任何索引,数据是否流入,我们是否可以优化某些内容等的地方。这也是我们可以管理许可证并创建系统配置快照的地方。

ELK 和 KVM

最后但同样重要的是,让我们创建一个系统仪表,它将向我们显示来自 KVM hypervisor 的参数,然后以几种方式进行可视化。这需要运行的 KVM hypervisor,安装了 KVM 模块的 metricbeat 和支持从 metricbeat 接收数据的 Elastic stack 配置。让我们详细了解 ELK 针对这个特定用例的配置:

  1. 首先,转到 hypervisor 并打开virsh shell。列出所有可用的域,选择一个域,并使用dommemstat –-domain <domain_name>命令。

结果应该是这样的:

图 14.13 – 用于域的 dommemtest

图 14.13 – 用于域的 dommemtest

  1. 打开 Kibana 并登录,转到metric*作为我们正在使用的索引。左侧列应该填充有 metricbeat 发送到此 Kibana 实例的数据集中的属性。现在,查看属性并选择其中的一些:图 14.14 – 在 Kibana 中选择属性

图 14.14 – 在 Kibana 中选择属性

您可以使用一个按钮选择字段,只要将鼠标光标悬停在任何字段上,该按钮就会显示出来。取消选择它们也是一样的:

图 14.15 – 在 Kibana 中添加属性

图 14.15 – 在 Kibana 中添加属性

  1. 现在,让我们坚持我们选择的那些。在列的右侧,形成了一个表,其中只包含您选择的字段,使您能够检查系统正在接收的数据。您可能需要向下滚动以查看实际信息,因为此表将显示接收到的所有数据,其中至少有一个具有值的项目。由于其中一个字段始终是时间戳,因此将有许多行不包含任何对我们的分析有用的数据:图 14.16 – 检查所选字段

图 14.16 – 检查所选字段

在这里我们可以看到的是,我们得到了在监视服务器上运行命令行的结果相同的数据。

我们需要的是一种方法来使用这些结果作为数据来显示我们的图表。点击dommemstat。保存搜索。

  1. 现在,让我们构建一个仪表,它将向我们显示实时数据和一个值的快速可视化。转到area图。然后,在下一个屏幕上,找到并选择我们的数据源:图 14.18 – 选择可视化源

图 14.18 – 选择可视化源

这将创建一个窗口,左侧显示所有设置,右侧显示最终结果。目前,我们看到的东西毫无意义,因此让我们配置我们需要的内容以显示我们的数据。有几种方法可以实现我们想要的内容:我们将使用直方图和过滤器快速显示我们的未使用内存随时间的变化。

  1. 我们将配置y轴以显示kvm.dommemstat.stat.value的平均数据,这是保存我们数据的属性。选择kvm.dommemstat.stat.value作为我们正在聚合的字段。如果需要,您可以创建自定义标签:图 18.19 – 选择度量属性

图 18.19 – 选择度量属性

这还不对,我们需要添加一个时间戳以查看我们的数据随时间的变化。我们需要在x轴上添加日期直方图类型并使用它:

图 14.20 – 选择聚合类型

图 14.20 - 选择聚合类型

  1. 在完成此可视化之前,我们需要添加一个过滤器。从 KVM metricbeat 模块接收的数据的问题是,它使用一个属性来保存不同的数据 - 如果我们想知道我们正在显示的文件中的数字实际上意味着什么,我们需要从kvm.dommemstat.stat.name中读取其名称。为了实现这一点,只需创建一个名为kvm.dommemstat.stat.name:"unused"的过滤器。

在我们刷新可视化后,我们的数据应该正确显示在右侧:

图 14.21 - 正确的可视化

  1. 我们需要使用“保存”按钮保存此可视化,为其命名,以便以后能够找到它,并重复此过程,但是不是过滤“未使用”,而是过滤“可用”。将所有设置保持与第一个可视化相同。

让我们构建一个仪表板。打开仪表板选项卡,然后在第一个屏幕上单击添加新仪表板。现在,将我们的两个可视化添加到此仪表板。您只需要找到正确的可视化并单击它;它将显示在仪表板上。

因此,我们有几个简单的仪表板正在运行:

图 14.22 - 显示可用内存的完成仪表板

图 14.22 - 显示可用内存的完成仪表板

第二个仪表板 - 在 UI 中,实际上就在第一个仪表板旁边 - 是未使用的内存仪表板:

图 14.23 - 显示未使用内存的完成仪表板

图 14.23 - 显示未使用内存的完成仪表板

  1. 保存此仪表板,以便以后使用。仪表板的所有元素都可以自定义,并且仪表板可以包含任意数量的可视化。Kibana 让您几乎可以自定义您看到的一切,并将大量数据组合在一个屏幕上进行轻松监视。我们只需要更改一件事情,使其成为一个良好的监控仪表板,那就是使其自动刷新。单击屏幕右侧的日历图标,并选择自动刷新间隔。我们决定使用5 秒

图 14.24 - 选择与时间相关的参数

图 14.24 - 选择与时间相关的参数

现在我们已经完成了这个,我们可以反思一下,构建这个仪表板真的非常简单和容易。这只花了我们几分钟,而且很容易阅读。想象一下,以文本模式查看数百兆字节的日志文件与这种方式相比。真的没有可比性,因为我们能够使用先前部署的 ELK 堆栈来监视有关 KVM 的信息,这正是本章的重点。

总结

Kibana 让您可以创建自定义仪表板,可以并排显示不同机器的数据,因此 KVM 只是我们的许多选项之一。根据您的需求,您可以显示,例如,KVM 超级管理程序的磁盘使用情况以及运行在其上的所有主机,或者其他一些指标。弹性堆栈是一个灵活的工具,但与所有事物一样,它需要时间来掌握。本章仅涵盖了弹性配置的基础知识,因此我们强烈建议在此主题上进行进一步阅读 - 除了 KVM 之外,ELK 可以用于监视几乎所有产生任何类型数据的东西。

下一章将全面讨论 KVM 虚拟机的性能调优和优化,这是我们并没有真正涉及的一个主题。有很多内容需要讨论 - 虚拟机计算大小、性能优化、磁盘、存储访问和多路径、优化内核和虚拟机设置等等。所有这些主题在我们的环境变得越来越大时将变得更加重要。

问题

  1. 我们使用 metricbeat 做什么?

  2. 我们为什么使用 Kibana?

  3. 在安装 ELK 堆栈之前的基本先决条件是什么?

  4. 我们如何向 Kibana 添加数据?

进一步阅读

有关本章涵盖的内容的更多信息,请参考以下链接:

第十五章:KVM VM 性能调优和优化

当我们思考虚拟化时,总会有一些问题不断出现。其中一些可能很简单,比如我们从虚拟化中能得到什么?它是否简化了事情?备份是否更容易?但是一旦我们使用虚拟化一段时间后,也会出现更复杂的问题。我们如何在计算层面加速?有没有更多的优化方法?我们可以调整什么来从存储或网络中获得更快的速度?我们可以引入一些配置更改,使我们能够在不在其中投入大量资金的情况下从现有基础设施中获得更多?

这就是为什么性能调优和优化对我们的虚拟化环境如此重要。正如我们将在本章中发现的那样,有许多不同的参数需要考虑-特别是如果我们从一开始就没有正确设计事物,这通常是情况。因此,我们将首先涵盖设计的主题,解释为什么它不应该只是一个纯粹的试错过程,然后继续通过不同的设备和子系统来解构这种思维过程。

在本章中,我们将涵盖以下主题:

  • 调整 VM CPU 和内存性能- NUMA

  • 内核同页合并

  • Virtio 设备调优

  • 块 I/O 调优

  • 网络 I/O 调优

一切都关乎设计

在我们生活的许多其他方面,我们不断重复一些基本模式。在 IT 中,我们通常也会这样做。当我们刚开始做某件事时,通常我们并不擅长。例如,当我们开始进行任何一种运动训练时,通常不如我们坚持几年后的水平。当我们开始音乐训练时,通常在参加音乐学校几年后我们会更擅长。同样的原则也适用于 IT-当我们开始从事 IT 时,我们远不如随着时间和主要是经验的积累变得更加擅长。

我们人类在学习过程中很擅长在智力防御方面设置障碍。我们很擅长说“我会通过我的错误学习”-而且我们通常会将其与“别打扰我”结合起来。

事实是-已经有这么多的知识存在,不去利用它就太愚蠢了。已经有这么多人经历了与我们相同或类似的过程;不利用这种经验来谋取我们的利益将是毫无意义的。此外,为什么要浪费时间在这个“我会通过我的错误学习”的事情上,当我们可以从比我们经验更丰富的人那里学到更多呢?

当我们开始使用虚拟化时,通常会从小处开始。例如,我们开始安装托管虚拟化解决方案,如 VMware Player,Oracle VirtualBox 或类似的解决方案。随着时间的推移,我们会转向具有一对虚拟机(VMs)的 hypervisor。随着我们周围的基础设施增长,我们开始遵循线性模式,试图使基础设施的工作方式与以前小型时相同,这是一个错误。IT 中没有任何线性的东西-增长、成本、管理所花费的时间...绝对没有。实际上,解构这一点非常简单-随着环境的增长,存在更多的相互依赖关系,这意味着一件事会影响另一件事,进而影响另一件事,依此类推。这种无尽的影响矩阵是人们经常忘记的东西,特别是在设计阶段。

重要提示:

这很简单:线性设计将使你一事无成,而正确的设计是性能调优的基础,这样在性能调优方面就要做的工作就少得多了。

在本书的早期(在第二章中,KVM 作为虚拟化解决方案),我们提到了非一致性内存访问NUMA)。具体来说,我们提到了 NUMA 配置选项是 VM 配置的一个非常重要的部分,特别是如果你正在设计一个承载大量虚拟化服务器的环境。让我们用一些例子来进一步阐述这一点。这些例子将为我们提供一个很好的基础,以从一个“里程高度”的视角来看待性能调优和优化中最大的问题,并描述如何使用良好的设计原则来摆脱许多不同类型的麻烦。我们故意使用微软的解决方案作为例子 - 不是因为我们对使用它们有宗教信仰,而是因为一个简单的事实。我们有很多广泛可用的文档,我们可以利用它们 - 设计文档,最佳实践,简短的文章等。所以,让我们使用它们。

通用硬件设计

假设你刚开始设计你的新虚拟化环境。当你今天从你的渠道合作伙伴那里订购服务器时,无论他们是谁,你需要从一个很长的列表中选择一个型号。品牌并不重要 - 有很多型号供选择。你可以选择 1U(所谓的“披萨盒”)服务器,大多数情况下有一个或两个 CPU,具体取决于型号。然后,你可以选择 2U 服务器,3U 服务器……列表呈指数级增长。假设你选择了一个带有一个 CPU 的 2U 服务器。

在下一步中,你选择内存的数量 - 比如 96GB 或 128GB。你下订单,几天或几周后,你的服务器就送到了。你打开它,然后意识到一些事情 - 所有的 RAM 都连接到 CPU1 的内存通道。你把它放在你的内存库里,忘记它,然后继续下一个阶段。

然后,问题变成了一些非常普通设置的微观管理。服务器的 BIOS 版本,hypervisor 级别的驱动程序和 BIOS 设置(电源管理,C 状态,Turbo Boost,超线程,各种与内存相关的设置,不允许核心关闭自己等)对我们在 hypervisor 上运行的 VM 的性能有着巨大的影响。因此,最佳实践肯定是首先检查我们的硬件是否有任何更新的 BIOS/固件版本,并检查制造商和其他相关文档,以确保 BIOS 设置尽可能优化。然后,只有在这之后,我们才能开始勾选一些物理和部署程序 - 在机架中部署我们的服务器,安装操作系统和我们需要的一切,然后开始使用它。

假设过了一段时间,你意识到需要进行一些升级,并订购了一些 PCI Express 卡 - 两个单端口光纤通道 8 Gbit/s 主机适配器,两个单端口 10 Gbit/s 以太网卡,以及两个 PCI Express NVMe 固态硬盘。例如,通过订购这些卡,你想增加一些功能 - 访问光纤通道存储,并通过将这两个功能从 1 Gbit/s 切换到 10 Gbit/s 网络来加快备份过程和虚拟机迁移的速度。你下订单,几天或几周后,你的新 PCI Express 卡送到了。你打开它们,关闭服务器,将其从机架中取出,并安装这些卡。2U服务器通常有空间可以安装两甚至三个 PCI Express 延长线卡,用于连接额外的 PCI Express 设备。假设你使用第一个 PCI Express 延长线来部署前两张卡 - 光纤通道控制器和 10 Gbit/s 以太网卡。然后,注意到你没有足够的 PCI Express 连接器将所有东西连接到第一个 PCI Express 延长线,你使用第二个 PCI Express 延长线来安装你的两个 PCI Express NVMe 固态硬盘。你将所有东西固定好,关闭服务器盖,将服务器放回机架,并重新开机。然后,你回到笔记本电脑上,试图格式化 PCI Express NVMe 固态硬盘并将其用于新的虚拟机存储,却发现服务器无法识别这些固态硬盘。你问自己 - 这到底是怎么回事?我的服务器出了问题吗?

图 15.1 - 适用于 DL380p G8 的 PCI Express 延长线 - 您必须将 PCI Express 卡插入其插槽将 PCI Express 卡插入其插槽

图 15.1 - 适用于 DL380p G8 的 PCI Express 延长线 - 您必须将 PCI Express 卡插入其插槽

你给销售代表打电话,告诉他们你认为服务器出现故障,因为它无法识别这些新的固态硬盘。销售代表将你转接到售前技术支持;你听到对面传来一阵轻笑,然后得到以下信息:“嗯,你看,你不能那样做。如果你想在服务器上使用第二个 PCI Express 延长线,你必须在第二个 CPU 插槽中安装一个 CPU 套件(CPU 加散热器),以及为第二个 CPU 安装内存。订购这两样东西,安装到你的服务器上,你的 PCI Express NVMe 固态硬盘就能正常工作了。”

你结束了电话会话,脑海中留下了一个问号 - 这到底是怎么回事?为什么我需要连接第二个 CPU 和内存到其内存控制器才能使用一些 PCI Express 卡?

这实际上与两件事有关:

  • 你不能在未安装的 CPU 上使用内存插槽,因为该内存需要内存控制器,而内存控制器位于 CPU 内部。

  • 你不能在未安装的 CPU 上使用 PCI Express,因为连接 PCI Express 延长线卡到 CPU 的 PCI Express 通道并不一定由芯片组提供 - CPU 也可以用于 PCI Express 通道,而且通常是这样,特别是对于最快速的连接,你一会儿就会了解到。

我们知道这很令人困惑;我们能感受到你的痛苦,因为我们也曾经历过。不幸的是,你必须再和我们待一会儿,因为情况会变得更加混乱。

第四章中,Libvirt 网络,我们学习了如何通过使用 Intel X540-AT2 网络控制器来配置 SR-IOV。我们提到在配置 SR-IOV 时我们使用了 HP ProLiant DL380p G8 服务器,所以让我们在这里也使用该服务器作为我们的示例。如果您查看该服务器的规格,您会注意到它使用了Intel C600芯片组。然后,如果您前往 Intel 的 ARK 网站(ark.intel.com)并搜索有关 C600 的信息,您会注意到它有五个不同的版本(C602、C602J、C604、C606 和 C608),但其中最耐人寻味的部分是所有这些版本都只支持 8 条 PCI Express 2.0 通道。考虑到服务器规格清楚地说明该服务器支持 PCI Express 3.0,这变得非常令人困惑。这是怎么回事,这里使用了什么样的诡计?是的,PCI Express 3.0 卡几乎总是可以以 PCI Express 2.0 的速度工作,但最好不要直接说这台服务器支持 PCI Express 3.0,然后发现它通过提供 PCI Express 2.0 级别的性能(每个 PCI Express 通道的速度减慢一倍)来支持它。

只有当您查看 HP ProLiant DL380p G8 QuickSpecs 文档并找到该文档的特定部分(扩展槽部分,其中描述了三种不同类型的 PCI Express 扩展槽)时,我们才能找到我们实际需要的所有信息。让我们使用所有 PCI Express 扩展槽的详细信息作为参考和解释。基本上,主要扩展槽由处理器 1 提供两个 PCI Express v3.0 槽(x16 加 x8),第三个槽(PCI Express 2.0 x8)由芯片组提供。对于可选扩展槽,它表示所有槽都由 CPU 提供(x16 加 x8 乘以 2)。实际上,有一些型号可以有三个 PCI Express 扩展槽,对于第三个扩展槽,所有 PCI Express 通道(x16 乘以 2)也由处理器 2 提供。

这一切都非常重要。对于许多情景来说,这是性能瓶颈的一个巨大因素,这也是为什么我们的示例围绕着两个 PCI Express NVMe 固态硬盘的想法展开的。我们希望与您一起完成整个旅程。

因此,此时我们可以就我们示例服务器的实际标准硬件设计进行有根据的讨论。如果我们的意图是将这些 PCI Express NVMe 固态硬盘用作我们的 VM 的本地存储,那么大多数人会将这视为优先事项。这意味着我们绝对希望将这些设备连接到 PCI Express 3.0 槽,以避免受到 PCI Express 2.0 速度的限制。如果我们有两个 CPU,我们可能最好在我们的 PCI Express 扩展槽的第一个 PCI Express 槽中使用这个特定目的。原因很简单-它们是PCI Express 3.0 兼容,并且它们是由 CPU 提供。再次强调,这是非常重要的-这意味着它们是直接连接到 CPU,而不需要通过芯片组的额外延迟。因为,归根结底,CPU 是一切的中心枢纽,从 VM 到固态硬盘再返回的数据都将通过 CPU。从设计的角度来看,我们应该绝对利用我们知道的这一点,并将我们的 PCI Express NVMe 固态硬盘本地连接到我们的 CPU。

下一步与光纤通道控制器和 10 Gbit/s 以太网控制器有关。大部分 8 Gbit/s 光纤通道控制器都兼容 PCI Express 2.0。同样的情况也适用于 10 Gbit/s 以太网适配器。因此,这又是一个优先考虑的问题。如果您从我们的示例服务器大量使用光纤通道存储,那么逻辑推断您会希望将新的光纤通道控制器放在尽可能快的位置。这将是我们的两个 PCI Express 扩展槽中的第二个 PCI Express 槽。同样,第二个 PCI Express 槽都由 CPU 提供-处理器 1 和处理器 2。现在,我们只剩下 10 Gbit/s 以太网适配器。在我们的示例场景中,我们说我们将使用这些适配器进行备份和 VM 迁移。如果通过芯片组上的网络适配器进行备份,备份不会受到太大影响。VM 迁移可能对此有些敏感。因此,您将第一个 10 Gbit/s 以太网适配器连接到主扩展槽上的第三个 PCI Express 槽(用于备份,由芯片组提供)。然后,您还将第二个 10 Gbit/s 以太网适配器连接到次级扩展槽上的第三个 PCI Express 槽(由处理器 2 提供的 PCI Express 通道)。

我们刚刚开始讨论硬件方面的设计问题,现在我们已经有了如此丰富的信息要处理。现在让我们继续进行我们设计的第二阶段-与 VM 设计相关。具体来说,我们将讨论如何从头开始创建正确设计的新 VM。但是,如果我们要这样做,我们需要知道这个 VM 将为哪个应用程序创建。为此,我们将创建一个场景。我们将使用正在创建的 VM 来托管运行 Windows Server 2019 的 VM 上的 Microsoft SQL 数据库集群中的一个节点。当然,该 VM 将安装在 KVM 主机上。这是客户交给我们的任务。由于我们已经完成了一般的硬件设计,现在我们将专注于 VM 设计。

VM 设计

创建 VM 很容易-我们只需转到virt-manager,点击几次,就可以了。oVirt、RedHat Enterprise Virtualization Manager、OpenStack、VMware 和 Microsoft 虚拟化解决方案也是如此……几乎在任何地方都是一样的。问题在于正确设计 VM。具体来说,问题在于创建一个将预先调整为在非常高的水平上运行应用程序的 VM,然后只留下一小部分配置步骤,我们可以在服务器或 VM 端采取来提高性能-前提是后续的大部分优化过程将在操作系统或应用程序级别完成。

因此,人们通常以两种方式之一开始创建 VM-要么从头开始创建 VM 并向 VM 添加XYZ数量的资源,要么使用模板,正如我们在第八章中解释的那样,创建和修改 VM 磁盘、模板和快照,这将节省大量时间。无论我们使用哪种方式,都会为我们的 VM 配置一定数量的资源。然后,我们记住我们将使用这个 VM(SQL),所以我们将 CPU 的数量增加到,例如,四个,内存的数量增加到 16 GB。我们将该 VM 放在服务器的本地存储中,启动它,并开始部署更新,配置网络,重新启动,通常准备 VM 进行最终安装步骤,即实际安装我们的应用程序(SQL Server 2016)和一些相关更新。完成后,我们开始创建我们的数据库,并继续进行需要完成的下一组任务。

接下来,让我们从设计和调优的角度来看这个过程。

调整 VM CPU 和内存性能

上述过程中有一些非常直接的问题。有些只是工程问题,而有些则更多是程序问题。让我们讨论一下:

  • 在 IT 领域几乎没有一刀切的解决方案。每个客户的每个虚拟机都有不同的情况,并处于不同的环境中,包括不同的设备、服务器等等。不要试图加快流程以给人留下印象,因为这肯定会在以后成为问题。

  • 当你完成部署后,停下来。学会深呼吸,停下来思考一秒钟,或者等一个小时甚至一天。记住你设计虚拟机的目的。

  • 在允许虚拟机投入生产使用之前,检查其配置。虚拟 CPU 的数量、内存、存储位置、网络选项、驱动程序、软件更新——一切都要检查。

  • 在安装阶段或模板阶段之前,可以进行大量的预配置。如果你正在将现有环境迁移到新环境,收集有关旧环境的信息。了解数据库的大小、使用的存储以及人们对其数据库服务器和使用它们的应用程序的性能满意程度。

在整个过程结束时,学会对你所做的与 IT 相关的工作采取高层视角。从质量保证的角度来看,IT 应该是一种高度结构化的、程序化的工作。如果你以前做过某事,学会记录你在安装过程中所做的事情和你所做的更改。文档——就目前而言——是 IT 的最大软肋之一。撰写文档将使你在未来面对相同(较少)或类似(更多)的情况时更容易重复这个过程。向伟人学习——举个例子,如果贝多芬没有详细记录他日复一日所做的事情,我们对他的了解就会少得多。是的,他生于 1770 年,今年将是他诞辰 250 周年,那是很久以前的事了,但这并不意味着 250 年前的惯例是不好的。

现在,你的虚拟机已经配置并投入生产使用,几天或几周后,你会接到公司的电话,他们会问为什么性能并不那么好。为什么它的工作效果不像物理服务器一样?

作为一个经验法则,当你在寻找 Microsoft SQL 的性能问题时,它们大致可以分为四类:

  • 你的 SQL 数据库受内存限制。

  • 你的 SQL 数据库受存储限制。

  • 你的 SQL 数据库配置错误。

  • 你的 SQL 数据库受 CPU 限制。

根据我们的经验,第一和第二类问题很容易占据 SQL 性能问题的 80-85%。第三类可能占 10%,而最后一类相对较少,但仍然会发生。记住,从基础设施的角度来看,当你设计数据库虚拟机时,你应该首先查看虚拟机内存和存储配置,因为它们是最常见的原因。问题会逐渐积累并不断恶化。具体来说,导致 SQL 虚拟机性能不佳的一些最常见的关键原因是内存位置、从 CPU 角度看待它,以及存储问题——延迟/IOPS 和带宽成为问题。所以,让我们逐一描述这些问题。

我们需要解决的第一个问题与——有趣的是——地理有关。对于数据库来说,将其内存内容尽可能靠近分配给其虚拟机的 CPU 核心非常重要。这就是 NUMA 的意义所在。我们可以通过一些配置轻松地解决这个特定问题。假设我们选择我们的虚拟机使用四个虚拟 CPU。我们的测试服务器配备了英特尔至强 E5-2660v2 处理器,每个处理器都有 10 个物理核心。考虑到我们的服务器有两个这样的至强处理器,我们总共有 20 个核心可供使用。

我们有两个基本问题要回答:

  • 我们的虚拟机的四个核心如何与下面的 20 个物理核心相关?

  • 这与 VM 的内存有什么关系,我们如何优化它?

对这两个问题的答案是取决于我们的配置。默认情况下,我们的 VM 可能会从两个物理处理器中使用两个核心,并在内存方面分布在两者之间或 3+1。这些配置示例都不好。你想要的是将所有虚拟 CPU 核心放在一个物理处理器上,并且你希望这些虚拟 CPU 核心使用与这四个物理核心相连的内存 - 直接连接到基础物理处理器的内存控制器。我们刚刚描述的是 NUMA 背后的基本思想 - 为你的 VM 提供作为本地内存的构建计算块的节点(由 CPU 核心组成)。

如果可能的话,你希望为该 VM 保留所有内存,这样它就不会在 VM 之外的某个地方交换。在 KVM 中,VM 之外将在 KVM 主机交换空间中。始终访问真正的 RAM 内存是性能和 SLA 相关的配置选项。如果 VM 使用一些基础交换分区作为其内存,它的性能将不同。请记住,交换通常是在某种本地 RAID 阵列、SD 卡或类似介质上进行的,与真正的 RAM 内存相比,这些介质在带宽和延迟方面慢得多。如果你想对此做出高层次的陈述 - 不惜一切代价避免 KVM 主机上的内存过度承诺。对于 CPU 也是如此,这是任何其他虚拟化解决方案上常用的最佳实践,而不仅仅是在 KVM 上。

此外,对于关键资源,比如数据库 VM,将 vCPU 固定到特定的物理核心确实是有意义的。这意味着我们可以使用特定的物理核心来运行一个 VM,并且我们应该配置在同一主机上运行的其他 VM使用这些核心。这样,我们就可以保留这些 CPU 核心专门用于单个 VM,从而配置一切以获得最大性能,不受运行在物理服务器上的其他 VM 的影响。

是的,有时经理和公司所有者不会喜欢你因为这个最佳实践(好像是你的错),因为这需要适当的规划和足够的资源。但这是他们必须接受的事情 - 或者不接受,看他们的选择。我们的工作是尽可能使 IT 系统运行得最好。

VM 设计有其基本原则,如 CPU 和内存设计、NUMA 配置、配置设备、存储和网络配置等。让我们逐步地逐个讨论所有这些主题,从一个高级的基于 CPU 的功能开始,如果正确使用,它确实可以帮助我们的系统尽可能地运行得最好 - CPU 固定。

CPU 固定

CPU 固定只是设置 vCPU 和主机物理 CPU 核心之间亲和性的过程,以便 vCPU 只在该物理 CPU 核心上执行。我们可以使用virsh vcpupin命令将 vCPU 绑定到物理 CPU 核心或物理 CPU 核心的子集。

在进行 vCPU 固定时有一些最佳实践:

  • 如果客人 vCPU 的数量超过单个 NUMA 节点的 CPU 数量,则不要使用默认的固定选项。

  • 如果物理 CPU 分布在不同的 NUMA 节点上,最好创建多个虚拟机,并将每个虚拟机的 vCPU 固定到同一 NUMA 节点中的物理 CPU。这是因为访问不同的 NUMA 节点,或者跨多个 NUMA 节点运行,对性能有负面影响,特别是对于内存密集型应用程序。

让我们来看看 vCPU 固定的步骤:

  1. 执行virsh nodeinfo以收集有关主机 CPU 配置的详细信息:图 15.2 - 关于我们的 KVM 节点的信息

图 15.2 - 关于我们的 KVM 节点的信息

  1. 下一步是通过执行virsh capabilities命令并检查标记为<topology>的部分来获取 CPU 拓扑结构:图 15.3 – 具有所有可见物理 CPU 核心的 virsh capabilities 输出

图 15.3 – 具有所有可见物理 CPU 核心的 virsh capabilities 输出

一旦我们确定了主机的拓扑结构,下一步就是开始固定 vCPU。

  1. 让我们首先检查名为SQLForNuma的客户端的当前亲和力或固定配置,该客户端有四个 vCPU:图 15.4 – 检查默认的 vcpupin 设置

图 15.4 – 检查默认的 vcpupin 设置

让我们通过使用 CPU 固定来改变这一点。

  1. 让我们将vCPU0固定到物理核心 0,vCPU1固定到物理核心 1,vCPU2固定到物理核心 2,vCPU3固定到物理核心 3:图 15.5 – 配置 CPU 固定

图 15.5 – 配置 CPU 固定

通过使用virsh vcpupin,我们改变了此 VM 的固定虚拟 CPU 分配。

  1. 让我们在此 VM 上使用virsh dumpxml来检查配置更改:

图 15.6 – CPU 固定 VM 配置更改

图 15.6 – CPU 固定 VM 配置更改

注意virsh命令中列出的 CPU 亲和力以及运行客户端的 XML 转储中的<cputune>标记。正如 XML 标记所说,这属于客户端的 CPU 调整部分。还可以配置一组物理 CPU 用于特定 vCPU,而不是单个物理 CPU。

有几件事情要记住。 vCPU 固定可以提高性能;但是,这取决于主机配置和系统上的其他设置。确保进行足够的测试并验证设置。

您还可以使用virsh vcpuinfo来验证固定。virsh vcpuinfo命令的输出如下:

图 15.7 – 用于我们的 VM 的 virsh vcpuinfo

图 15.7 – 用于我们的 VM 的 virsh vcpuinfo

如果我们在繁忙的主机上进行此操作,将会产生后果。有时,我们可能无法启动我们的 SQL 机器,因为这些设置。因此,为了更大的利益(SQL VM 能够工作而不是不想启动),我们可以将内存模式配置从strict更改为interleavepreferred,这将放宽对于为此 VM 严格使用本地内存的坚持。

现在让我们探讨内存调整选项,因为这是下一个逻辑要讨论的事情。

内存调整

对于大多数环境来说,内存都是宝贵的资源,不是吗?因此,应通过调整来实现对内存的有效使用。优化 KVM 内存性能的第一条规则是在设置期间不要为客户端分配比其使用的资源更多的资源。

我们将更详细地讨论以下内容:

  • 内存分配

  • 内存调整

  • 内存支持

让我们从解释如何为虚拟系统或客户端配置内存分配开始。

内存分配

为了使分配过程简单,我们将再次考虑virt-manager libvirt 客户端。内存分配可以从以下截图中显示的窗口中完成:

图 15.8 – VM 内存选项

图 15.8 – VM 内存选项

正如您在前面的截图中所看到的,有两个主要选项:当前分配最大分配

  • 最大分配:客户端的运行时最大内存分配。这是客户端在运行时可以分配的最大内存。

  • 当前分配:客户端始终使用的内存量。出于内存球形原因,我们可以将此值设置为低于最大值。

virsh命令可用于调整这些参数。相关的virsh命令选项是setmemsetmaxmem

内存调整

内存调整选项添加在客户端配置文件的<memtune>下。

其他内存调整选项可以在libvirt.org/formatdomain.html#elementsMemoryTuning找到。

管理员可以手动配置客户机的内存设置。如果省略了<memtune>配置,那么默认的内存设置将适用于客户机。这里使用的virsh命令如下:

# virsh memtune <virtual_machine> --parameter size parameter

它可以具有以下任何值;这个最佳实践在 man 页面中有很好的记录:

--hard-limit       The maximum memory the guest can use.
--soft-limit       The memory limit to enforce during memory contention.
--swap-hard-limit  The maximum memory plus swap the guest can use.  This has to be more than hard-limit value provided. 
--min-guarantee    The guaranteed minimum memory allocation for the guest.

可以获取为memtune参数设置的默认/当前值,如下所示:

图 15.9 - 检查 VM 的 memtune 设置

图 15.9 - 检查 VM 的 memtune 设置

在设置hard_limit时,不应将此值设置得太低。这可能导致虚拟机被内核终止。这就是为什么确定虚拟机(或任何其他进程)的正确资源量是一个设计问题。有时,正确设计东西似乎就像黑暗艺术一样。

要了解如何设置这些参数,请参阅以下截图中memtune命令的帮助输出:

图 15.10 - 检查 virsh 帮助 memtune

图 15.10 - 检查 virsh 帮助 memtune

在我们讨论了内存分配和调整之后,最后一个选项是内存后备。

内存后备

以下是内存后备的客户机 XML 表示:

<domain>     ...
  <memoryBacking>
    <hugepages>
    <page size="1" unit="G" nodeset="0-3,5"/>
    <page size="2" unit="M" nodeset="4"/>
    </hugepages>
    <nosharepages/>
    <locked/>
</memoryBacking>     ...
  </domain>

您可能已经注意到内存后备有三个主要选项:lockednosharepageshugepages。让我们逐一介绍它们,从locked开始。

locked

在 KVM 虚拟化中,客户机内存位于 KVM 主机中的qemu-kvm进程的进程地址空间中。这些客户机内存页面可以根据主机的需求随时被 Linux 内核交换出去,这就是locked可以帮助的地方。如果将客户机的内存后备选项设置为locked,主机将不会交换属于虚拟系统或客户机的内存页面。当启用此选项时,主机系统内存中的虚拟内存页面将被锁定:

<memoryBacking>
    <locked/>
</memoryBacking>

我们需要使用<memtune>来设置hard_limit。计算很简单 - 我们需要为客户机加上开销的内存量。

nosharepages

以下是来自客户机配置文件的nosharepages的 XML 表示:

<memoryBacking>
    <nosharepages/>
</memoryBacking>

有不同的机制可以在内存页面相同的情况下实现内存共享。诸如nosharepages选项之类的技术指示了虚拟化程序禁用此客户机的共享页面 - 也就是说,设置此选项将阻止主机在客户机之间进行内存去重。

hugepages

第三个也是最后一个选项是hugepages,可以用 XML 格式表示如下:

<memoryBacking>
</hugepages>
</memoryBacking>

HugePages 是在 Linux 内核中引入的,以改善内存管理的性能。内存以称为页面的块进行管理。不同的架构(i386、ia64)支持不同的页面大小。对于 x86 CPU(4 KB 内存页面)来说,我们不一定要使用默认设置,因为我们可以使用更大的内存页面(2 MB 到 1 GB),这个功能称为 HugePages。CPU 的一个部分称为内存管理单元MMU)通过使用列表来管理这些页面。页面通过页表引用,并且每个页面在页表中都有一个引用。当系统想要处理大量内存时,主要有两种选项。其中一种涉及增加硬件 MMU 中的页表条目数。第二种方法是增加默认页面大小。如果我们选择增加页表条目的第一种方法,那么成本就会很高。

处理大量内存的第二种更有效的方法是使用 HugePages 或通过使用 HugePages 增加页面大小。每台服务器的不同内存量意味着需要不同的页面大小。默认值对大多数情况都可以,而巨大的内存页面(例如 1 GB)在我们有大量内存(数百 GB 甚至 TB)时更有效。这意味着在引用内存页面方面需要更少的管理工作,而实际上花费更多时间获取这些内存页面的内容,这可能会导致显著的性能提升。大多数已知的 Linux 发行版都可以使用 HugePages 来管理大量内存。进程可以使用 HugePages 内存支持通过增加 CPU 缓存命中来提高性能,这是在第二章中解释的,KVM 作为虚拟化解决方案。您已经知道,客户系统只是 Linux 系统中的进程,因此 KVM 客户也有资格执行相同的操作。

在我们继续之前,我们还应该提到MADV_HUGEPAGE区域(以避免消耗更多的内存资源),或者在整个系统中启用。系统中配置 THP 有三个主要选项:alwaysmadvisenever

# cat/sys/kernel/mm/transparent_hugepage/enabled [always] madvise never

从前面的输出中,我们可以看到我们服务器中当前的 THP 设置为madvise。其他选项可以通过使用以下命令之一来启用:

echo always >/sys/kernel/mm/transparent_hugepage/enabled
echo madvise >/sys/kernel/mm/transparent_hugepage/enabled
echo never >/sys/kernel/mm/transparent_hugepage/enabled 

简而言之,这些值的含义如下:

  • always:始终使用 THP。

  • madvise:仅在MADV_HUGEPAGE中使用 HugePages。

  • never:禁用该功能。

THP 会自动优化性能设置。通过将内存用作缓存,我们可以获得性能优势。当 THP 存在时,可以使用静态 HugePages,或者换句话说,THP 不会阻止使用静态方法。如果我们不配置 KVM hypervisor 来使用静态 HugePages,它将使用 4 KB 的透明 HugePages。使用 HugePages 来管理 KVM 客户端内存的优势在于,用于页表的内存更少,TLB 缺失减少;显然,这会提高性能。但请记住,当为客户端内存使用 HugePages 时,您将无法再交换或收缩客户端内存。

让我们快速看一下如何在 KVM 设置中使用静态 HugePages。首先,让我们检查当前的系统配置 - 很明显,这个系统中的 HugePages 大小目前设置为 2 MB:

图 15.11 - 检查 HugePages 设置

图 15.11 - 检查 HugePages 设置

我们主要讨论以 HugePages 开头的所有属性,但值得一提的是AnonHugePages属性。AnonHugePages属性告诉我们系统级别上当前 THP 的使用情况。

现在,让我们配置 KVM 以使用自定义的 HugePages 大小:

  1. 通过运行以下命令查看当前显式的hugepages值或从sysfs中获取它,如下所示:
#  cat /proc/sys/vm/nr_hugepages
0
  1. 我们还可以使用sysctl -a |grep huge命令:图 15.12 - sysctl hugepages 设置

图 15.12 - sysctl hugepages 设置

  1. 由于 HugePage 大小为 2 MB,我们可以按 2 MB 的增量设置 hugepages。要将 hugepages 的数量设置为 2,000,请使用以下命令:
# echo 2000 > /proc/sys/vm/nr_hugepages

巨大页面分配的总内存不能被不了解巨大页面的应用程序使用 - 也就是说,如果你过度分配了巨大页面,主机系统的正常操作可能会受到影响。在我们的例子中,2048*2 MB 将等于 4096 MB 的内存,在进行此配置时我们应该有这么多可用的内存。

  1. 我们需要告诉系统这种类型的配置实际上是可以的,并配置/etc/security/limits.conf以反映这一点。否则,系统可能会拒绝给我们访问 2,048 个 2 MB 内存的巨大页面。我们需要在该文件中添加两行:
<value> parameter will depend on the configuration we want to do. If we want to configure everything according to our 2048*2 MB example, <value> would be 4,194,304 (or 4096*1024).
  1. 要使其持久化,您可以使用以下命令:
# sysctl -w vm.nr_hugepages=<number of hugepages>
  1. 然后,挂载fs大页,重新配置虚拟机,并重新启动主机:
 # mount -t hugetlbfs hugetlbfs /dev/hugepages

通过在虚拟机配置文件中添加以下设置来重新配置已配置大页的虚拟机:

<memoryBacking>
</hugepages>
</ memoryBacking> 

现在是关闭虚拟机并重新启动主机的时候。在虚拟机内执行以下操作:

# systemctl poweroff

在主机上,执行以下操作:

# systemctl reboot

主机重新启动并重新启动虚拟机后,它现在将开始使用大页。

下一个主题与在多个虚拟机之间共享内存内容有关,称为 KSM。这项技术被广泛用于节省内存。在任何给定时刻,当虚拟化主机上启动多个虚拟机时,这些虚拟机有很大的统计机会具有相同的内存内容块(它们具有相同的内容)。然后,没有理由多次存储相同的内容。通常,我们将 KSM 称为应用于内存的去重复过程。让我们学习如何使用和配置 KSM。

熟悉 KSM

KSM 是一个允许不同进程之间共享相同页面的功能。我们可能会认为相同的页面存在是由于某些原因,例如,如果有多个进程从相同的二进制文件生成,或者类似的情况。但实际上并没有这样的规则。KSM 扫描这些相同的内存页面,并合并一个写时复制的共享页面。写时复制是一种机制,当试图更改一个被多个进程共享的内存区域时,请求更改的进程会得到一个新的副本,并将更改保存在其中。

尽管所有进程都可以访问合并的写时复制共享页面,但每当一个进程尝试更改内容(向该页面写入)时,该进程都会得到一个带有所有更改的新副本。到目前为止,您可能已经了解到,通过使用 KSM,我们可以减少物理内存消耗。在 KVM 环境中,这确实可以增加价值,因为客户端系统是系统中的qemu-kvm进程,并且所有虚拟机进程很可能具有大量相似的内存。

为了使 KSM 工作,进程/应用程序必须向 KSM 注册其内存页面。在 KVM 环境中,KSM 允许客户端共享相同的内存页面,从而提高内存消耗。这可能是某种应用程序数据、库或其他经常使用的内容。这个共享的页面或内存被标记为“写时复制”。简而言之,KSM 避免了内存重复,当 KVM 环境中存在相似的客户端操作系统时,它非常有用。

通过使用预测理论,KSM 可以提供增强的内存速度和利用率。大多数情况下,这些共享数据存储在缓存或主内存中,这会导致 KVM 客户端的缓存未命中减少。此外,KSM 可以减少客户端内存占用,从某种程度上允许用户在 KVM 设置中进行内存超额分配,从而提供更大的资源利用率。然而,我们必须记住,KSM 需要更多的 CPU 资源来识别重复页面并执行共享/合并等任务。

之前,我们提到进程必须标记页面,以表明它们是 KSM 操作的合格候选者。这种标记可以由基于MADV_MERGEABLE标志的进程完成,我们将在下一节中讨论。您可以在madvise手册页中了解有关此标志的用法:

# man 2 madvise
MADV_MERGEABLE (since Linux 2.6.32)
Enable Kernel Samepage Merging (KSM) for the pages in the range specified by addr and length. The kernel regularly scans those areas of user memory that have been marked as mergeable, looking for pages with identical content.  These are replaced by a single write-protected page (that is automatically copied if a process later wants to update the content of the page).  KSM merges only private anonymous pages (see mmap(2)).
The KSM feature is intended for applications that generate many instances of the same data (e.g., virtualization systems such as KVM).  It can consume a lot of processing   power; use with care.  See the Linux kernel source file Documentation/ vm/ksm.txt for more details.
The MADV_MERGEABLE and MADV_UNMERGEABLE operations are available only if the kernel was configured with CONFIG_KSM.

因此,内核必须配置 KSM,如下所示:

图 15.13 - 检查 KSM 设置

图 15.13 - 检查 KSM 设置

KSM 作为qemu-kvm软件包的一部分部署。可以从sysfs文件系统中的/sys目录中获取有关 KSM 服务的信息。在这个位置有不同的文件,反映了当前 KSM 的状态。这些文件由内核动态更新,并且它有 KSM 使用和统计的精确记录:

图 15.14 - sysfs 中的 KSM 设置

图 15.14 - sysfs 中的 KSM 设置

在接下来的部分中,我们将讨论ksmtuned服务及其配置变量。由于ksmtuned是一个控制 KSM 的服务,其配置变量类似于我们在sysfs文件系统中看到的文件。有关更多详细信息,请查看www.kernel.org/doc/html/latest/admin-guide/mm/ksm.html

还可以使用virsh命令调整这些参数。virsh node-memory-tune命令可以为我们完成这项工作。例如,以下命令指定在共享内存服务进入休眠之前要扫描的页面数:

# virsh node-memory-tune --shm-pages-to-scan number

与任何其他服务一样,ksmtuned服务也有日志存储在日志文件/var/log/ksmtuned中。如果我们在/etc/ksmtuned.conf中添加DEBUG=1,我们将从任何类型的 KSM 调整操作中获得日志记录。有关更多详细信息,请参阅www.kernel.org/doc/Documentation/vm/ksm.txt

一旦我们启动了 KSM 服务,如下所示,您可以观察值的变化,具体取决于 KSM 服务的操作:

# systemctl start ksm

然后我们可以像这样检查ksm服务的状态:

图 15.15 - ksm 服务命令和 ps 命令输出

图 15.15 - ksm 服务命令和 ps 命令输出

一旦 KSM 服务启动并且我们的主机上有多个虚拟机正在运行,我们可以使用以下命令多次查询sysfs来检查变化:

cat /sys/kernel/mm/ksm/*

让我们更详细地探讨ksmtuned服务。ksmtuned服务被设计成经历一系列动作并调整 KSM。这个动作循环会不断地工作。每当创建或销毁一个客户系统时,libvirt 都会通知ksmtuned服务。

/etc/ksmtuned.conf文件是ksmtuned服务的配置文件。以下是可用的配置参数的简要说明。您可以看到这些配置参数与sysfs中的 KSM 文件相匹配:

# Configuration file for ksmtuned.
# How long ksmtuned should sleep between tuning adjustments
# KSM_MONITOR_INTERVAL=60
# Millisecond sleep between ksm scans for 16Gb server.
# Smaller servers sleep more, bigger sleep less.
# KSM_SLEEP_MSEC=10
# KSM_NPAGES_BOOST - is added to the `npages` value, when `free memory` is less than `thres`. 
# KSM_NPAGES_BOOST=300
# KSM_NPAGES_DECAY - is the value given is subtracted to the `npages` value, when `free memory` is greater than `thres`. 
# KSM_NPAGES_DECAY=-50
# KSM_NPAGES_MIN - is the lower limit for the `npages` value.
# KSM_NPAGES_MIN=64
# KSM_NPAGES_MAX - is the upper limit for the `npages` value.
# KSM_NPAGES_MAX=1250
# KSM_THRES_COEF - is the RAM percentage to be calculated in parameter `thres`.
# KSM_THRES_COEF=20
# KSM_THRES_CONST - If this is a low memory system, and the `thres` value is less than `KSM_THRES_CONST`, then reset `thres` value to `KSM_THRES_CONST` value.
# KSM_THRES_CONST=2048

KSM 旨在提高性能并允许内存超额分配。在大多数环境中,它都能够实现这一目的;然而,在某些设置或环境中,KSM 可能会引入性能开销 - 例如,如果您有一些虚拟机在启动时具有相似的内存内容,然后进行大量的内存密集型操作。这将会导致问题,因为 KSM 首先会非常努力地减少内存占用,然后浪费时间来处理多个虚拟机之间的所有内存内容差异。此外,有人担心 KSM 可能打开一个潜在的渠道,可能被用于在客户之间泄露信息,这在过去几年中已经有充分的记录。如果您有这些担忧,或者如果您看到/经历 KSM 没有帮助提高工作负载的性能,可以将其禁用。

要禁用 KSM,通过执行以下命令停止系统中的ksmtunedksm服务:

# systemctl stop ksm
# systemctl stop ksmtuned

我们已经研究了 CPU 和内存的不同调优选项。接下来我们需要讨论的下一个重要主题是 NUMA 配置,其中 CPU 和内存配置成为更大故事或背景的一部分。

使用 NUMA 调整 CPU 和内存

在我们开始为 NUMA 可用系统调整 CPU 和内存之前,让我们看看 NUMA 是什么以及它是如何工作的。

将 NUMA 视为一个系统,其中有多个系统总线,每个总线为一小组处理器和关联内存提供服务。每组处理器都有自己的内存,可能还有自己的 I/O 通道。可能无法阻止或阻止运行的 VM 跨越这些组。这些组中的每一个称为NUMA 节点

在这个概念中,如果一个进程/线程在一个 NUMA 节点上运行,同一节点上的内存称为本地内存,而驻留在不同节点上的内存称为外部/远程内存。这种实现与对称多处理系统SMP)不同,SMP 中所有内存的访问时间对所有 CPU 都是相同的,因为内存访问是通过一个集中的总线进行的。

讨论 NUMA 的一个重要主题是 NUMA 比率。NUMA 比率是衡量 CPU 访问本地内存相对于访问远程/外部内存的速度的指标。例如,如果 NUMA 比率为 2.0,则 CPU 访问远程内存的时间是访问本地内存的两倍。如果 NUMA 比率为 1,这意味着我们正在使用 SMP。比率越大,VM 内存操作在获取必要数据(或保存数据)之前必须支付的延迟成本(开销)就越大。在更深入地探讨调优之前,让我们讨论一下系统的 NUMA 拓扑。显示当前 NUMA 拓扑的最简单方法之一是通过numactl命令:

图 15.16 - numactl -H 输出

图 15.16 - numactl -H 输出

前面的numactl输出表明系统中有 10 个 CPU,它们属于单个 NUMA 节点。它还列出了与每个 NUMA 节点关联的内存和节点距离。当我们讨论 CPU 固定时,我们使用virsh功能显示了系统的拓扑结构。要获得 NUMA 拓扑的图形视图,可以使用一个名为lstopo的命令,该命令在基于 CentOS-/Red Hat 的系统中与hwloc软件包一起提供:

图 15.17 - 使用 lstopo 命令可视化 NUMA 拓扑

图 15.17 - 使用 lstopo 命令可视化 NUMA 拓扑

此截图还显示了与 NUMA 节点关联的 PCI 设备。例如,ens*(网络接口)设备连接到 NUMA 节点 0。一旦我们了解了系统的 NUMA 拓扑,就可以开始调整它,特别是针对 KVM 虚拟化设置。

NUMA 内存分配策略

通过修改 VM XML 配置文件,我们可以进行 NUMA 调优。调优 NUMA 引入了一个名为numatune的新元素标签:

<domain>   ...
    <numatune>
      <memory mode="strict" nodeset="1-4,³"/>
    </numatune>   ...
</domain>

这也可以通过virsh命令进行配置,如下所示:

图 15.18 - 使用 virsh numatune 配置 NUMA 设置

图 15.18 - 使用 virsh numatune 配置 NUMA 设置

此标签的 XML 表示如下:

<domain>
 …
  <numatune>
    <memory mode="strict" nodeset="1-4,³"/>
    <memnode cellid="0" mode="strict" nodeset="1"/>
    <memnode cellid="2" mode="preferred" nodeset="2"/>
  </numatune>   ...
</domain>

尽管名为numatune的元素是可选的,但它是用来通过控制域进程的 NUMA 策略来调整 NUMA 主机性能的。此可选元素的主要子标签是memorynodeset。有关这些子标签的一些说明如下:

  • memory:此元素描述了 NUMA 节点上的内存分配过程。有三种策略来管理 NUMA 节点的内存分配:

a) Strict:当虚拟机尝试分配内存并且内存不可用时,分配将失败。

b) Interleave:在 NUMA 节点之间进行循环分配。

c) Preferred:虚拟机尝试从首选节点分配内存。如果该节点没有足够的内存,它可以从剩余的 NUMA 节点分配内存。

  • nodeset:指定服务器上可用的 NUMA 节点列表。

这里的一个重要属性是placement,在以下 URL 中有解释 - libvirt.org/formatdomain.html

"属性放置可用于指示域进程的内存放置模式,其值可以是"static"或"auto",默认为 vCPU 的放置,或者如果指定了 nodeset,则为"static"。"auto"表示域进程将仅从查询 numad 返回的建议 nodeset 分配内存,如果指定了属性 nodeset 的值将被忽略。如果 vCPU 的放置是'auto',并且未指定 numatune,则将隐式添加一个默认的 numatune,其放置为'auto',模式为'strict'。"

我们需要小心这些声明,因为有适用的继承规则。例如,如果我们指定了<nodeset>元素,<numatune><vcpu>元素默认为相同的值。因此,我们绝对可以配置不同的 CPU 和内存调优选项,但也要意识到这些选项可以被继承。

在考虑 NUMA 上下文中的 CPU 固定时,还有一些要考虑的事情。我们在本章的早些时候讨论了 CPU 固定的基础,因为它可以为我们的虚拟机提供更好、更可预测的性能,并且可以提高缓存效率。举个例子,假设我们想尽可能快地运行一个虚拟机。明智的做法是在固定 CPU 核心的 CPU 插槽上运行它,这样可以在最快的存储上运行,这将在 PCI Express 总线上。如果我们没有使用 NVMe SSD 本地运行虚拟机,我们可以使用存储控制器来实现相同的效果。但是,如果我们用来访问虚拟机存储的存储控制器物理连接到另一个 CPU 插槽,那将导致延迟。对于延迟敏感的应用程序,这将意味着性能大幅下降。

然而,我们也需要意识到另一个极端——如果我们进行过多的固定,将来可能会产生其他问题。例如,如果我们的服务器在架构上不同(具有相同数量的核心和内存),迁移虚拟机可能会变得棘手。我们可能会出现这样的情况,即迁移过程中将 CPU 核心固定到目标服务器上不存在的核心。因此,我们在配置环境时总是需要小心,以免走得太远。

我们列表上的下一个主题是emulatorpin,它可以用来将我们的qemu-kvm模拟器固定到特定的 CPU 核心,以便它不影响我们虚拟机核心的性能。让我们学习如何配置它。

理解 emulatorpin

emulatorpin选项也属于 CPU 调优类别。其 XML 表示如下:

<domain>   ...
    <cputune>     …..       <emulatorpin cpuset="1-3"/>      …..
    </cputune>   ...
</domain>

emulatorpin元素是可选的,用于将模拟器(qemu-kvm)固定到主机物理 CPU。这不包括来自 VM 的 vCPU 或 IO 线程。如果省略此项,模拟器将默认固定到主机系统的所有物理 CPU。

重要提示:

请注意,当您调整支持 NUMA 的系统时,应该一起配置<vcpupin><numatune><emulatorpin>,以实现最佳的确定性性能。

在我们离开这一部分之前,还有一些事情需要涵盖:客户端系统 NUMA 拓扑和 NUMA 的大页内存支持。

可以使用<numa>元素在客户端 XML 配置中指定客户端 NUMA 拓扑结构;有些人称之为虚拟 NUMA:

  <cpu>      ...
    <numa>
      <cell id='0' cpus='0-3' memory='512000' unit='KiB'/>
      <cell id='1' cpus='4-7' memory='512000' unit='KiB' />     </numa>         ...
</cpu>

cell id元素告诉虚拟机使用哪个 NUMA 节点,而cpus元素配置特定的核心(或核心)。memory元素为每个节点分配内存量。每个 NUMA 节点都以数字索引,从0开始。

之前,我们讨论了memorybacking元素,可以在客户端配置中指定使用大页。当 NUMA 存在于设置中时,nodeset属性可用于配置每个 NUMA 节点的特定大页大小,这可能会很有用,因为它将给定客户端的 NUMA 节点与某些大页大小联系起来:

<memoryBacking>
    <hugepages>
      <page size="1" unit="G" nodeset="0-2,4"/>
      <page size="4" unit="M" nodeset="3"/>
    </hugepages>
</memoryBacking>

这种配置可以优化内存性能,因为客户端 NUMA 节点可以根据需要移动到主机 NUMA 节点,同时客户端可以继续使用主机分配的大页。

NUMA 调整还必须考虑 PCI 设备的 NUMA 节点局部性,特别是当从主机向客户端传递 PCI 设备时。如果相关的 PCI 设备隶属于远程 NUMA 节点,这可能会影响数据传输,从而影响性能。

显示 NUMA 拓扑和 PCI 设备关联的最简单方法是使用我们之前讨论过的lstopo命令。同样命令的非图形形式也可以用来发现这个配置。请参考前面的章节。

KSM 和 NUMA

我们在前面的章节中详细讨论了 KSM。KSM 是 NUMA 感知的,它可以管理发生在多个 NUMA 节点上的 KSM 进程。如果你还记得,当我们从 sysfs 获取 KSM 条目时,遇到了一个名为merge_across_nodesysfs条目。这是我们可以用来管理这个进程的参数:

#  cat /sys/kernel/mm/ksm/merge_across_nodes
1

如果这个参数设置为0,KSM 只会合并来自同一 NUMA 节点的内存页面。如果设置为1(就像这里的情况一样),它将跨越 NUMA 节点进行合并。这意味着运行在远程 NUMA 节点上的 VM CPU 在访问合并的 KSM 页面时会遇到延迟。

显然,你知道客户端 XML 条目(memorybacking元素)用于要求虚拟机监视程序为客户端禁用共享页面。如果你不记得了,请参考内存调整部分,了解这个元素的详细信息。尽管我们可以手动配置 NUMA,但还有一种叫做自动 NUMA 平衡的东西。我们之前提到过它,但让我们看看这个概念涉及了什么。

自动 NUMA 平衡

自动 NUMA 平衡的主要目的是提高在 NUMA 感知系统中运行的不同应用程序的性能。其设计背后的策略很简单:如果一个应用程序使用本地内存到 vCPU 所在的 NUMA 节点,它将具有更好的性能。通过使用自动 NUMA 平衡,KVM 尝试将 vCPU 移动到本地(尽可能多)的内存地址,以便 vCPU 使用。这一切都是在自动 NUMA 平衡激活时由内核自动完成的。当在具有 NUMA 属性的硬件上引导时,将启用自动 NUMA 平衡。主要条件或标准如下:

  • numactl --hardware:显示多个节点

  • cat /sys/kernel/debug/sched_features:在标志中显示 NUMA

为了说明第二点,看下面的代码块:

#  cat /sys/kernel/debug/sched_features
GENTLE_FAIR_SLEEPERS START_DEBIT NO_NEXT_BUDDY LAST_BUDDY CACHE_HOT_BUDDY 
WAKEUP_PREEMPTION ARCH_POWER NO_HRTICK NO_DOUBLE_TICK LB_BIAS NONTASK_
POWER TTWU_QUEUE NO_FORCE_SD_OVERLAP RT_RUNTIME_SHARE NO_LB_MIN NUMA 
NUMA_FAVOUR_HIGHER NO_NUMA_RESIST_LOWER

我们可以通过以下方法检查系统中是否启用了这个功能:

#  cat /proc/sys/kernel/numa_balancing
1

显然,我们可以通过以下方式禁用自动 NUMA 平衡:

# echo 0 > /proc/sys/kernel/numa_balancing

自动 NUMA 平衡机制基于多种算法和数据结构。这种方法的内部基于以下内容:

  • NUMA 提示页面错误

  • NUMA 页面迁移

  • 伪交错

  • 故障统计

  • 任务放置

  • 任务分组

KVM 客户端的最佳实践或建议之一是将其资源限制在单个 NUMA 节点上的资源量。简而言之,这可以避免将 VMs 不必要地分割到 NUMA 节点上,从而降低性能。让我们从检查当前的 NUMA 配置开始。有多种可用的选项来执行此操作。让我们从numactl命令、NUMA 守护程序和numastat开始,然后再回到使用一个众所周知的命令virsh

numactl 命令

确认 NUMA 可用性的第一个选项使用numactl命令,如下所示:

图 15.19 - numactl 硬件输出

图 15.19 - numactl 硬件输出

这只列出一个节点。即使这表明了 NUMA 的不可用性,也可以通过运行以下命令进行进一步澄清:

# cat /sys/kernel/debug/sched_features

如果系统不支持 NUMA,这将列出 NUMA 标志。

通常,不要使虚拟机于单个 NUMA 节点所能提供的范围。即使 NUMA 可用,vCPU 也绑定到 NUMA 节点,而不是特定的物理 CPU。

了解 numad 和 numastat

numad手册页中指出:

numad 是一个守护程序,用于控制具有 NUMA 拓扑的系统上 CPU 和内存的有效使用。

numad也被称为自动numad手册页中指出:

"numad 是一个用户级守护程序,为具有 NUMA 拓扑的系统上的 CPU 和内存的有效使用提供放置建议和进程管理。"

numad是一个系统守护程序,用于监视 NUMA 拓扑和资源使用情况。它将尝试定位进程以实现有效的 NUMA 局部性和亲和性,并根据系统条件的变化进行动态调整。numad还提供指导,以帮助管理应用程序为其进程的 CPU 和内存资源进行初始手动绑定。请注意,numad主要用于服务器整合环境,可能在同一服务器系统上运行多个应用程序或多个虚拟客户机。当进程可以在系统的 NUMA 节点子集中定位时,numad可能会产生积极的影响。例如,如果整个系统专用于大型内存数据库应用程序,特别是如果内存访问可能保持不可预测,numad可能不会提高性能。

为了根据 NUMA 拓扑自动调整和对齐 CPU 和内存资源,我们需要运行numad。要将numad用作可执行文件,只需运行以下命令:

# numad

您可以检查是否已启动如下所示:

图 15.20 - 检查 numad 是否处于活动状态

图 15.20 - 检查 numad 是否处于活动状态

一旦执行numad二进制文件,它将开始对齐,如下截图所示。在我们的系统中,我们有以下正在运行的虚拟机:

图 15.21 - 列出正在运行的虚拟机

图 15.21 - 列出正在运行的虚拟机

您可以使用numastat命令来监视运行numad服务之前和之后的差异,该命令将通过以下命令持续运行:

# numad -i 0

我们可以随时停止它,但这不会改变numad配置的 NUMA 亲和状态。现在让我们转到numastat

numactl软件包提供numactl二进制/命令,numad软件包提供numad二进制/命令:

图 15.22 - qemu-kvm 进程的 numastat 命令输出

图 15.22 - qemu-kvm 进程的 numastat 命令输出

重要提示:

在将虚拟机移至生产环境之前,必须对我们使用的众多内存调整选项进行全面测试。

在我们转到下一个主题之前,我们想提醒您本章前面提到的一个观点。迁移具有固定资源的虚拟机可能会很复杂,因为您必须在目标主机上拥有某种兼容资源(及其数量)。例如,目标主机的 NUMA 拓扑结构不必与源主机的 NUMA 拓扑结构对齐。在调整 KVM 环境时,您应考虑这一事实。自动 NUMA 平衡可能在一定程度上有所帮助,减少手动固定客户资源的需求。

Virtio 设备调整

在虚拟化世界中,总是将其与裸机系统进行比较。半虚拟化驱动程序增强了客户机的性能,并尝试保持接近裸机的性能。建议为完全虚拟化的客户机使用半虚拟化驱动程序,特别是当客户机运行 I/O 密集型任务和应用程序时。lguest。Virtio 被引入以实现 IO 虚拟化的一种通用框架。

简而言之,当我们使用半虚拟化驱动程序时,VM 操作系统知道其下面有一个 hypervisor,因此使用前端驱动程序来访问它。前端驱动程序是客户系统的一部分。当存在模拟设备并且有人想要为这些设备实现后端驱动程序时,hypervisor 会执行此工作。前端和后端驱动程序通过基于 virtio 的路径进行通信。 Virtio 驱动程序是 KVM 用作半虚拟化设备驱动程序的。基本架构如下:

图 15.23 – Virtio 架构

图 15.23 – Virtio 架构

主要有两层(virt 队列和虚拟环)来支持客户和 hypervisor 之间的通信。

Virt 队列虚拟环vring)是 virtio 中的传输机制实现。 Virt 队列(virtio)是连接前端和后端驱动程序的队列接口。每个 virtio 设备都有自己的 virt 队列,并且来自客户系统的请求被放入这些 virt 队列中。每个 virt 队列都有自己的环,称为 vring,这是 QEMU 和客户之间映射内存的地方。在 KVM 客户中有不同的 virtio 驱动程序可供使用。

这些设备在 QEMU 中是模拟的,驱动程序是 Linux 内核的一部分,或者是 Windows 客户的额外软件包。以下是一些设备/驱动程序对的示例:

  • virtio-net:virtio 网络设备是一个虚拟以太网卡。virtio-net为此提供驱动程序。

  • virtio-blk:virtio 块设备是一个简单的虚拟块设备(即磁盘)。virtio-blk为虚拟块设备提供块设备驱动程序。

  • virtio-balloon:virtio 内存气球设备是用于管理客户内存的设备。

  • virtio-scsi:virtio SCSI 主机设备将一个或多个磁盘组合在一起,并允许使用 SCSI 协议与它们通信。

  • virtio-console:virtio 控制台设备是用于客户和主机用户空间之间的数据输入和输出的简单设备。

  • virtio-rng:virtio 熵设备为客户提供高质量的随机性,并供客户使用等等。

一般来说,您应该在 KVM 设置中使用这些 virtio 设备以获得更好的性能。

块 I/O 调整

回到基础知识-VM 的虚拟磁盘可以是块设备,也可以是镜像文件。为了获得更好的 VM 性能,首选基于块设备的虚拟磁盘,而不是位于远程文件系统(如 NFS、GlusterFS 等)上的镜像文件。但是,我们不能忽视文件后端有助于 virt 管理员更好地管理客户磁盘,并且在某些情况下非常有帮助。根据我们的经验,我们注意到大多数用户在性能不是太大问题时使用磁盘镜像文件。请记住,可以附加到 VM 的虚拟磁盘的总数有限。同时,可以混合和使用块设备和文件,并将它们用作同一客户的存储磁盘,没有限制。

客户将虚拟磁盘视为其存储。当客户操作系统内的应用程序将数据写入客户系统的本地存储时,它必须通过几个层。也就是说,这个 I/O 请求必须通过存储上的文件系统和客户操作系统的 I/O 子系统。之后,qemu-kvm进程将其从客户操作系统传递给 hypervisor。一旦 I/O 在 hypervisor 的范围内,它就开始像主机操作系统中运行的任何其他应用程序一样处理 I/O。在这里,您可以看到 I/O 必须通过的层数,以完成 I/O 操作。因此,块设备后端的性能优于镜像文件后端。

以下是我们对磁盘后端和基于文件或镜像的虚拟磁盘的观察:

  • 文件镜像是主机文件系统的一部分,并且与块设备后端相比,它对 I/O 操作创建了额外的资源需求。

  • 使用稀疏镜像文件有助于超额分配主机存储,但使用它会降低虚拟磁盘的性能。

  • 在使用磁盘镜像文件时,不正确的分区可能会导致不必要的 I/O 操作。在这里,我们提到了标准分区单元的对齐。

在本章的开头,我们讨论了 virtio 驱动程序,它可以提供更好的性能。因此,建议在配置磁盘时使用 virtio 磁盘总线,而不是 IDE 总线。virtio_blk驱动程序使用 virtio API 为存储 I/O 设备提供高性能,从而提高存储性能,特别是在大型企业存储系统中。我们在第五章中讨论了可用的不同存储格式;然而,主要的是rawqcow格式。当使用raw格式时将获得最佳性能。当使用qcow时,格式层会带来一些性能开销。因为格式层有时必须执行一些操作,例如,如果要扩展qcow镜像,它必须分配新的簇等。但是,如果要使用快照等功能,qcow将是一个选择。这些额外的功能是由镜像格式qcow提供的。一些性能比较可以在www.Linux-kvm.org/page/Qcow2找到。

有三种可以考虑的 I/O 调优选项,我们在第七章中讨论过,虚拟机-安装、配置和生命周期管理

  • 缓存模式

  • I/O 模式

  • I/O 调优

让我们简要地浏览一些 XML 设置,以便我们可以在我们的虚拟机上实施它们。

缓存选项设置可以在虚拟机 XML 中反映如下:

<disk type='file' device='disk'>
<driver name='qemu' type='raw' cache='writeback'/> 

I/O 模式配置的 XML 表示类似于以下内容:

<disk type='file' device='disk'>
<driver name='qemu' type='raw' io='threads'/> 

在 I/O 调优方面,还有一些额外的说明:

  • 可能需要限制每个虚拟机的磁盘 I/O,特别是在我们的设置中存在多个虚拟机时。

  • 如果一个虚拟机通过生成的磁盘 I/O 数量使主机系统繁忙(嘈杂的邻居问题),这对其他虚拟机是不公平的。

一般来说,系统/虚拟管理员有责任确保所有正在运行的虚拟机获得足够的资源来工作,换句话说,这就是服务质量QOS)。

虽然磁盘 I/O 并不是唯一需要考虑以保证 QoS 的资源,但它也很重要。调整 I/O 可以防止虚拟机系统垄断共享资源并降低在同一主机上运行的其他虚拟机的性能。这确实是一个要求,特别是当主机系统正在执行virsh blkdeviotune命令时。可以使用该命令设置的不同选项如下所示:

图 15.24-摘自 virsh blkdeviotune –help 命令

图 15.24-摘自 virsh blkdeviotune –help 命令

关于参数的详细信息,如total-bytes-secread-bytes-secwritebytes-sectotal-iops-sec等,可以从前面的命令输出中很容易理解。它们也在virsh命令手册页中有记录。

例如,要限制名为SQLForNuma的 VM 上的vdb磁盘的 I/O 操作为每秒 200 次,吞吐量为每秒 50MB,运行以下命令:

# virsh blkdeviotune SQLForNuma vdb --total-iops-sec 200 --total-bytes-sec 52428800

接下来,我们将看一下网络 I/O 调优。

网络 I/O 调优

在大多数 KVM 环境中,我们看到的是来自客户机的所有网络流量都会经过单一的网络路径。不会有任何流量隔离,这会导致大多数 KVM 设置中的拥塞。作为网络调优的第一步,我们建议尝试不同的网络或专用网络用于管理、备份或实时迁移。但是,当您有多个网络接口用于流量时,请尽量避免多个网络接口用于相同的网络或段。如果这种情况确实存在,请应用一些常见的网络调优设置;例如,使用arp_filter来控制 ARP Flux。当虚拟机具有多个网络接口并且正在使用它们积极地回复 ARP 请求时,就会发生 ARP Flux,因此我们应该执行以下操作:

echo 1 > /proc/sys/net/ipv4/conf/all/arp_filter 

然后,您需要编辑/etc/sysctl.conf以使此设置持久化。

有关 ARP Flux 的更多信息,请参阅linux-ip.net/html/ether-arp.html#ether-arp-flux

在驱动程序级别可以进行额外的调优;也就是说,现在我们知道 virtio 驱动程序与模拟设备 API 相比提供了更好的性能。因此,显然,应考虑在客户系统中使用virtio_net驱动程序。当我们使用virtio_net驱动程序时,它在qemu中有一个后端驱动程序来处理来自客户网络的通信。即使这样性能更好,该领域的一些增强引入了一个称为vhost_net的新驱动程序,为 KVM 提供了内核中的 virtio 设备。尽管 vhost 是一个可以被不同驱动程序使用的常见框架,但网络驱动程序vhost_net是最早的驱动程序之一。以下图表将使这一点更清晰:

图 15.25 – vhost_net 架构

图 15.25 – vhost_net 架构

正如您可能已经注意到的那样,通过新的通信路径,上下文切换的数量确实减少了。好消息是,支持 vhost 不需要在客户系统中进行额外的配置,因为前端驱动程序没有变化。

vhost_net减少了复制操作,降低了延迟和 CPU 使用率,从而提高了性能。首先,系统中必须加载名为vhost_net的内核模块(请参阅下一节中的屏幕截图)。由于这是主机系统中的字符设备,它在主机上创建了一个名为/dev/vhost-net的设备文件。

如何打开它

当使用-netdev tap,vhost=on启动 QEMU 时,它将通过使用ioctl()调用来实例化vhost-net接口。此初始化过程将qemuvhost-net实例绑定在一起,以及其他操作,如特性协商等等:

图 15.26 – 检查 vhost 内核模块

图 15.26 – 检查 vhost 内核模块

vhost_net模块可用的参数之一是experimental_ zcopytx。它是做什么的?此参数控制称为桥接零拷贝传输的内容。让我们看看这意味着什么(如www.google.com/patents/US20110126195所述):

“用于在虚拟化环境中提供零拷贝传输的系统包括一个接收与客户应用程序相关的数据包的客户操作系统(OS)请求的超级监视程序,其中数据包位于客户 OS 的缓冲区或客户应用程序的缓冲区中,并且在网络堆栈处理期间创建了至少部分标头。超级监视程序进一步向网络设备驱动程序发送请求,以通过网络设备将数据包传输到网络上,其中请求标识了位于客户 OS 的缓冲区或客户应用程序的缓冲区中的数据包,并且超级监视程序避免将数据包复制到超级监视程序缓冲区。”

如果您的环境使用大数据包大小,则配置此参数可能会产生显着影响。当客户端与外部网络通信时,通过配置此参数可以减少主机 CPU 开销。这不会影响以下情况的性能:

  • 客户端到客户端的通信

  • 客户端到主机的通信

  • 小数据包工作负载

此外,通过启用多队列virtio-net也可以获得性能改进。有关更多信息,请访问fedoraproject.org/wiki/Features/MQ_virtio_net

使用virtio-net时的一个瓶颈是其单个 RX 和 TX 队列。即使有更多的 vCPUs,网络吞吐量也受到此限制的影响。virtio-net是一种单队列类型的队列,因此开发了多队列virtio-net。在引入此选项之前,虚拟网卡无法利用 Linux 内核中可用的多队列支持。

通过在前端和后端驱动程序中引入多队列支持来解决这个瓶颈。这也有助于客户端使用更多 vCPUs 进行扩展。要启动具有两个队列的客户端,可以将queues参数指定为tapvirtio-net,如下所示:

# qemu-kvm -netdev tap,queues=2,... -device virtio-net-pci,queues=2,...

等效的客户端 XML 如下:

<interface type='network'>
    <source network='default'/>
    <model type='virtio'/>
    <driver name='vhost' queues='M'/>
</interface>

在这里,M可以是18,因为内核支持多达八个队列的多队列 tap 设备。一旦为qemu配置了这个参数,在客户端内部,我们需要使用ethtool命令启用多队列支持。通过ethtool启用多队列(其中K的值从1M),如下所示:

# ethtool -L eth0 combined 'K'

您可以查看以下链接,了解多队列virtio-net何时提供最大的性能优势:access.redhat.com/documentation/en-us/red_hat_enterprise_linux/7/html/virtualization_tuning_and_optimization_guide/sect-virtualization_tuning_optimization_guide-networking-techniques

不要盲目使用上述 URL 中提到的选项-请测试对您的设置的影响,因为在这种情况下,即使网络吞吐量令人印象深刻,CPU 消耗也会更大。

KVM 客户端时间保持最佳实践

有不同的时间保持机制。其中最著名的技术之一是网络时间协议NTP)。通过使用 NTP,我们可以使时钟同步到很高的精度,即使在具有抖动(可变延迟)的网络上也可以。在虚拟化环境中需要考虑的一件事是,客户端时间应与 hypervisor/host 同步,因为它会影响很多客户端操作,如果它们不同步可能会导致不可预测的结果。

有不同的方法可以实现时间同步,但这取决于您的设置。我们已经看到人们使用 NTP,使用hwclock –s从硬件时钟设置系统时钟等。这里需要考虑的第一件事是尝试使 KVM 主机时间同步和稳定。您可以使用类似 NTP 的协议来实现这一点。一旦设置好,客户端时间必须保持同步。尽管有不同的机制可以做到这一点,但最好的选择是使用kvm-clock

kvm-clock

kvm-clock也被称为虚拟化感知(半虚拟化)时钟设备。当使用kvm-clock时,客户端询问宿主机当前时间,保证了稳定和准确的时间记录。这一功能是通过客户端注册一个页面并与宿主机共享地址来实现的。这是客户端和宿主机之间的共享页面。宿主机不断更新此页面,除非被要求停止。客户端可以在需要时间信息时简单地读取此页面。但请注意,宿主机应支持kvm-clock供客户端使用。有关更多详细信息,您可以查看lkml.org/lkml/2010/4/15/355

默认情况下,大多数较新的 Linux 发行版都使用kvm_clock通过以下方法在客户端进行配置:

[root@kvmguest ]$  cat /sys/devices/system/clocksource/clocksource0/current_clocksource
tsc

您还可以在 Linux 上使用ntpdchrony作为时钟源,这需要最少的配置。在您的 Linux 虚拟机中,编辑/etc/ntpd.conf/etc/chronyd.conf,并修改server配置行,将其指向 NTP 服务器的 IP 地址。然后,只需启用并启动您正在使用的服务(这里我们使用chrony作为示例):

systemctl enable chronyd
systemctl start chronyd

还有另一种比较新的协议被大力推广用于时间同步,称为ntpdchronyd。它使用网络接口上的时间戳和外部来源以及计算机的系统时钟进行同步。

安装所有必要的先决条件只需要一个yum命令来启用和启动服务:

yum -y install linuxptp
systemctl enable ptp4l
systemctl start ptp4l

默认情况下,ptp4l服务将使用/etc/sysconfig/ptp4l配置文件,通常绑定到第一个网络接口。如果您想使用其他网络接口,最简单的方法就是编辑配置文件,更改接口名称,然后通过systemctl重新启动服务。

现在,从 VM 的角度来看,我们可以通过进行一些配置来帮助它们进行时间同步。我们可以将ptp_kvm模块添加到全局 KVM 主机配置中,这将使我们的 PTP 作为服务可用于chronyd作为时钟源。这样,我们就不必进行大量额外的配置。因此,只需将ptp_kvm作为字符串添加到默认的 KVM 配置中,如下所示:

echo ptp_kvm > /etc/modules-load.d/kvm-chrony.conf
modprobe ptp_kvm

通过这样做,将在/dev目录中创建一个ptp设备,然后我们可以将其用作chrony的时间源。将以下行添加到/etc/chrony.conf并重新启动chronyd

refclock PHC /dev/ptp0 poll 3 dpoll -2 offset 0
systemctl restart chronyd

通过使用 API 调用,所有 Linux 虚拟机都能从运行它们的物理主机获取时间。

现在,我们已经涵盖了大量关于 VM 配置选项的性能调整和优化,现在是时候最终摆脱所有这些微小的步骤,关注更大的画面。到目前为止,我们所涵盖的所有 VM 设计(与 CPU、内存、NUMA、virtio、块、网络和时间配置相关)只有在我们使用它时才重要。回到我们最初的场景——一个 SQL VM——让我们看看我们将如何根据我们将在其上运行的软件来正确配置我们的 VM。

基于软件的设计

还记得我们最初的场景吗,涉及一个应该是 Microsoft SQL Server 集群中的节点的基于 Windows Server 2019 的 VM 吗?我们在调整方面涵盖了很多设置,但还有更多要做——要做的事情更多。我们需要问一些问题。我们越早问这些问题越好,因为它们将对我们的设计产生关键影响。

我们可能会问的一些问题如下:

  • 对不起,亲爱的客户,当您说集群时,您具体指的是什么,因为有不同的 SQL Server 集群方法学?

  • 您有哪些 SQL 许可证,或者您打算购买哪些?

  • 您需要主动-主动、主动-被动、备份解决方案还是其他什么?

  • 这是一个单站点还是多站点集群?

  • 您确切需要哪些 SQL 功能?

  • 你有哪些许可证,你愿意为它们花多少钱?

  • 你的应用程序是否能够与 SQL 集群一起工作(例如,在多站点场景中)?

  • 你有什么样的存储系统?

  • 你的存储系统可以提供多少 IOPS?

  • 你的存储的延迟如何?

  • 你有不同层次的存储子系统吗?

  • 这些层次的服务水平在 IOPS 和延迟方面是多少?

  • 如果你有多个存储层,我们是否可以根据最佳实践创建 SQL VMs,例如将数据文件和日志文件放在单独的虚拟磁盘上?

  • 你有足够的磁盘容量来满足你的需求吗?

这些只是许可、集群和存储相关的问题,它们不会消失。我们需要毫不犹豫地提出这些问题,并在部署之前得到真实的答案。我们刚刚提到了 14 个问题,但实际上还有更多。

此外,我们需要考虑 VM 设计的其他方面。询问一些问题是明智的,比如:

  • 你可以为 SQL VM 提供多少内存?

  • 你有哪些服务器,它们使用哪些处理器,每个插槽有多少内存?

  • 你是否正在使用任何最新一代技术,比如持久内存?

  • 你有关于你正在为这个 SQL 基础架构设计的规模和/或查询量的任何信息吗?

  • 在这个项目中,金钱是一个重要的决定因素吗(因为它将影响许多设计决策,因为 SQL 是按核心许可的)?还有标准与企业定价的问题。

这一系列问题实际上指向了 VM 设计中非常重要的一部分,与内存、内存位置、CPU 和内存之间的关系以及数据库设计中最基本的问题之一——延迟有关。其中很大一部分与正确的 VM 存储设计有关——正确的存储控制器、存储系统、缓存设置等,以及 VM 计算设计——这一切都与 NUMA 有关。我们在本章中解释了所有这些设置。因此,为了正确配置我们的 SQL VM,这里是我们应该遵循的高级步骤清单:

  • 配置具有正确 NUMA 设置和本地内存的 VM。出于许可原因,从四个 vCPU 开始,然后找出是否需要更多(例如,如果你的 VM 变得受限于 CPU,你将从性能图表和基于 SQL 的性能监控工具中看到)。

  • 如果你想保留 CPU 容量,利用 CPU 固定,以便物理服务器 CPU 上的特定 CPU 核心始终用于 SQL VM,而且只用于它。将其他 VM 隔离到剩余核心。

  • 为 SQL VM 保留内存,以防止交换,因为只有使用真正的 RAM 内存才能保证性能平稳,不受到嘈杂的邻居的影响。

  • 如有必要,为每个 VM 配置 KSM,并避免在 SQL VM 上使用它,因为它可能引入延迟。在设计阶段,确保购买尽可能多的 RAM 内存,以免内存成为问题,因为如果服务器没有足够的内存,这将是一个非常昂贵的性能问题。绝对不要过度分配内存。

  • 配置具有多个虚拟硬盘的 VM,并将这些硬盘放在可以提供所需服务水平的存储中,包括延迟、开销和缓存。记住,操作系统磁盘不一定需要写缓存,但数据库和日志磁盘将受益于它。

  • 使用主机到存储设备的单独物理连接,并调整存储以尽可能提高性能。不要过度订阅——无论是在链路级别(太多 VM 通过相同的基础设施连接到相同的存储设备)还是数据存储级别(不要将一个数据存储放在一个存储设备上并将所有 VM 存储在其中,因为这会对性能产生负面影响——隔离工作负载,通过多个链接创建多个目标,并使用掩码和分区)。

  • 配置多路径、负载平衡和故障转移-以获得尽可能多的存储性能,同时也具有冗余性。

  • 安装正确的 virtio 驱动程序,如有必要使用 vhost 驱动程序或 SR-IOV,并尽量减少每个级别的开销。

  • 调整 VM 客户操作系统-关闭不必要的服务,将电源配置文件切换到“高性能”(大多数 Microsoft 操作系统出于某种原因将电源配置文件设置为“平衡”模式)。调整 BIOS 设置并检查固件和操作系统更新-从上到下的所有内容。做笔记,测量,进行基准测试,并在更新和更改配置时使用以前的基准测试作为基线,以便了解自己的方向。

  • 在使用 iSCSI 时,配置巨帧,因为在大多数情况下,这将对存储性能产生积极影响,并确保您查看存储设备供应商的文档以了解相关的最佳实践。

本章的要点是-不要仅仅因为客户要求安装应用程序而盲目安装。这将在以后困扰你,并且解决任何问题和投诉将会更加困难。花时间并且做对。通过阅读文档来为整个过程做好准备,因为文档是广泛可用的。

总结

在本章中,我们深入探讨了 KVM 性能调优和优化的领域。我们讨论了许多不同的技术,从简单的 CPU 固定到更复杂的 NUMA 和适当的 NUMA 配置。不要被吓到,因为学习设计是一个过程,正确设计是一门可以通过学习和经验不断改进的技艺。想想这样的事情-当建筑师设计世界上最高的摩天大楼时,他们是否每次建造新的最高建筑物时都将目标进一步推进?

在下一章-本书的最后一章中-我们将讨论如何排除环境中的故障。这至少部分与本章相关,因为我们将排除一些与性能相关的问题。在切换到故障排除章节之前,多次阅读本章对您的整体学习过程将非常有益。

问题

  1. 什么是 CPU 固定?

  2. KSM 是做什么的?

  3. 我们如何增强块设备的性能?

  4. 我们如何调整网络设备的性能?

  5. 我们如何在虚拟化环境中同步时钟?

  6. 我们如何配置 NUMA?

  7. 我们如何配置 NUMA 和 KSM 一起工作?

进一步阅读

有关更多信息,请参考以下链接:

第十六章:KVM 平台的故障排除指南

如果您从《第一章》《理解 Linux 虚拟化》一直跟着这本书走到现在,那么您会知道在这本书中我们一起经历了很多 - 数百页的概念和实际方面,包括配置示例、文件和命令 - 一切。大约 700 页。到目前为止,我们几乎完全忽略了故障排除作为旅程的一部分。我们并不是基于 Linux 中一切都只是“工作”,我们根本没有任何问题,而且我们在阅读全书的过程中达到了一种“涅槃”的状态。

这是一段充满各种问题的旅程。其中一些不值一提,因为它们是我们自己的错误。我们犯的错误(而且您肯定也会犯更多)大多来自于我们输入错误(在命令或配置文件中)。基本上,人类在 IT 中扮演着重要角色。但其中一些问题确实令人沮丧。例如,实施 SR-IOV 需要很多时间,因为我们不得不在硬件、软件和配置层面找到不同类型的问题才能使其正常工作。oVirt 相当古怪,我们很快会解释。Eucalyptus 有点有趣,姑且这么说。尽管我们之前经常使用它,但 cloudbase-init 真的很复杂,需要我们花费很多时间和注意力,结果证明这并不是由于我们做了什么 - 它只是 cloudbase-init 的版本。但总的来说,这进一步证明了我们上一章的一个普遍观点 - 在书籍、文章和博客文章中阅读各种 IT 主题,是一个确实不错的方法,可以从一开始就正确配置很多东西。但即便如此,您仍然需要一些故障排除来使一切完美。

一旦安装了一个服务并开始使用它,一切都很棒和令人惊讶,但第一次很少会这样。我们在本书中使用的一切实际上都是为了测试不同的配置并抓取必要的截图而安装的,但与此同时,我们也希望确保它们实际上可以以更有结构、程序化的方式安装和配置。

因此,让我们从与服务、软件包和日志记录相关的一些简单事物开始。然后,我们将继续介绍更高级的故障排除概念和工具,通过我们一路涵盖的各种示例进行描述。

在本章中,我们将涵盖以下主题:

  • 验证 KVM 服务状态

  • KVM 服务日志记录

  • 启用调试模式日志记录

  • 高级故障排除工具

  • KVM 问题的最佳实践

验证 KVM 服务状态

我们将从最简单的示例开始 - 验证 KVM 服务状态及其对主机配置的一些正常影响。

在《第三章》《安装 KVM Hypervisor、libvirt 和 ovirt》中,我们通过安装virt module并使用dnf命令部署各种软件包,对整个 KVM 堆栈进行了基本安装。有几个原因可能导致这不是一个好主意:

  • 许多服务器、台式机、工作站和笔记本电脑在出厂时都预先配置为关闭 BIOS 中的虚拟化。如果您使用基于英特尔的 CPU,请确保找到所有基于 VT 的选项并启用它们(VT、VT-d、VT I/O)。如果您使用基于 AMD 的 CPU,请确保打开 AMD-V。您可以进行一个简单的测试来检查虚拟化是否已启用。如果您启动任何 Linux 实时发行版,进入 shell 并输入以下命令:
cat /proc/cpuinfo | egrep "vmx|svm"

如果您已经安装了 Linux 主机和我们在《第三章》《安装 KVM Hypervisor、libvirt 和 ovirt》中提到的适当软件包,您也可以使用以下命令:

virt-host-validate

如果您从这个命令中没有得到任何输出,那么您的系统要么不支持虚拟化(可能性较小),要么没有打开虚拟化功能。确保您检查您的 BIOS 设置。

  • 您的网络配置和/或软件包存储库配置可能没有正确设置。正如我们在本章中反复强调的,请从最简单的事情开始——不要试图找出某些超级复杂的原因,为什么某些东西不起作用。保持简单。对于网络测试,请尝试使用ping命令对一些知名服务器进行测试,比如 google.com。对于存储库问题,请确保您检查/etc/yum.repos.d目录。尝试使用yum clean allyum update命令。存储库问题更有可能发生在一些其他发行版而不是 CentOS/Red Hat 上,但仍然可能发生。

  • 部署过程成功完成后,请确保使用以下命令启动和启用 KVM 服务:

libvirt-guests service, and then we get very surprised after we reboot our host. The result of libvirt-guests not being enabled is simple. When started, it suspends your virtual machines when you initiate shutdown and resumes them on the next boot. In other words, if you don't enable them, your virtual machines won't resume after the next reboot. Also, check out its configuration file, /etc/sysconfig/libvirt-guests. It's a simple text configuration file that enables you to configure at least three very important settings: ON_SHUTDOWN, ON_BOOT, and START_DELAY. Let's explain these:a) By using the `ON_SHUTDOWN` setting, we can select what happens with the virtual machine when we shut down your host since it accepts values such as `shutdown` and `suspend`.b) The `ON_BOOT` option does the opposite – it tells `libvirtd` whether it needs to start all the virtual machines on host boot, whatever their autostart settings are. It accepts values such as `start` and `ignore`.c) The third option, `START_DELAY`, allows you to set a timeout value (in seconds) between multiple virtual machine power-on actions while the host is booting. It accepts numeric values, with `0` being the value for parallel startup and *all other (positive) numbers* being the number of seconds it waits before it starts the next virtual machine.

考虑到这一点,至少有三件事需要记住:

  • 确保这两个服务实际上正在运行,通过输入以下命令:
libvirtd needs to be started for us to be able to create or run a KVM virtual machine.
  • 如果您正在配置更高级的设置,比如 SR-IOV,请确保阅读服务器的手册,选择一个与 SR-IOV 兼容的正确插槽。此外,请确保您有兼容的 PCI Express 卡和正确配置的 BIOS。否则,您将无法使其工作。

  • 当您启动 libvirt 服务时,它通常会带有某种预定义的防火墙配置。记住这一点,以防您决定禁用 libvirt 服务,因为防火墙规则几乎总是还在那里。这可能需要一些额外的配置。

您故障排除旅程的下一步将是检查一些日志文件。有很多选择——KVM 有自己的,oVirt 有自己的,Eucalyptus 也有自己的,ELK 等等。因此,请确保您对这些服务非常了解,以便您可以检查正确的日志文件,以解决您要解决的情况。让我们从 KVM 服务日志开始。

KVM 服务日志

在讨论 KVM 服务日志时,有一些位置是我们需要注意的:

  • 假设您以 root 用户身份登录到 GUI,并启动了 virt-manager。这意味着您在/root/.cache/virt-manager目录中有一个virt-manager.log文件。它非常冗长,所以在阅读时请耐心等待。

  • /etc/libvirt/libvirtd.conf文件是 libvirtd 的配置文件,包含了许多有趣的选项,但一些最重要的选项实际上位于文件的末尾,并与审计相关。您可以选择注释掉的选项(audit_levelaudit_logging)来满足您的需求。

  • /var/log/libvirt/qemu目录包含了在我们的 KVM 主机上创建的所有虚拟机的日志和旋转日志。

此外,请务必查看一个名为auvirt的命令。它非常方便,因为它可以告诉您有关 KVM 主机上的虚拟机的基本信息——仍然存在和/或成功运行的虚拟机,以及我们尝试安装但失败的虚拟机。它从审计日志中获取数据,您也可以使用它来显示我们需要的特定虚拟机的信息。它还有一个非常调试级别的选项叫做--all-events,如果您想检查关于 KVM 主机上的任何虚拟机的每一个细节。

启用调试模式日志记录

在 KVM 中还有另一种日志记录方法:配置调试日志记录。在我们刚提到的 libvirtd 配置文件中,有其他设置可以用来配置这个选项。因此,如果我们滚动到Logging controls部分,这些是我们可以使用的设置:

  • log_level

  • log_filters

  • log_outputs

让我们一步一步地解释它们。第一个选项 - log_level - 描述了日志详细程度。自从 libvirt 版本 4.4.0 起,此选项已被弃用。在文件的日志控制部分中,还有额外的文档硬编码到文件中,以使事情变得更容易。对于这个特定的选项,文档中是这样说的:

图 16.1 - libvirtd.conf 中的日志控制

图 16.1 - libvirtd.conf 中的日志控制

人们通常会看到此输出的第一部分(日志级别描述),转到最后一行(Iog_level),将其设置为 1,保存,重新启动libvirtd服务,然后完成。问题在于中间的文本部分。它明确表示journald进行速率限制,以便它不会被来自一个服务的日志淹没,并指示我们改用log_filters设置。

那么让我们这样做 - 让我们使用log_filters。在配置文件的稍低处,有一个看起来像这样的部分:

图 16.2 - libvirtd.conf 中的日志过滤器选项

图 16.2 - libvirtd.conf 中的日志过滤器选项

这为我们提供了各种选项,我们可以使用不同的日志选项来设置不同的对象类型,这很棒。它为我们提供了增加我们感兴趣的事物的详细程度的选项,同时将其他对象类型的详细程度保持在最低水平。我们需要做的是删除最后一行的注释部分(#log_filters="1:qemu 1:libvirt 4:object 4:json 4:event 1:util"应该变成log_filters="1:qemu 1:libvirt 4:object 4:json 4:event 1:util"),并配置其设置以使其符合我们的要求。

第三个选项涉及我们想要将调试日志输出文件放在哪里:

图 16.3 - libvirtd.conf 中的日志输出选项

图 16.3 - libvirtd.conf 中的日志输出选项

重要提示

更改任何这些设置后,我们需要确保通过键入systemctl restart libvirtd命令来重新启动libvirtd服务。

如果我们只对客户端日志感兴趣,我们需要设置一个名为LIBVIRT_LOG_OUTPUTS的环境变量为类似于这样的内容(假设我们想要 DEBUG 级别的日志):

export LIBVIRT_LOG_OUTPUTS="1:file:/var/log/libvirt_guests.log"

所有这些选项都有效,直到下一次libvirtd服务重启,这对于永久设置非常方便。但是,当我们需要临时调试而不需要使用永久配置时,还有一个运行时选项可以使用。这就是为什么我们有一个名为virt-admin的命令。我们可以使用它来设置我们自己的设置。例如,让我们看看如何使用它来获取我们当前的设置,然后如何使用它来设置临时设置:

图 16.4 - Runtime libvirtd 调试选项

图 16.4 - Runtime libvirtd 调试选项

我们还可以通过发出以下命令来删除这些设置:

virt-admin daemon-log-filters ""

这绝对是在我们完成调试后推荐的事情。我们不想把我们的日志空间用于无用的事情。

就纯粹调试虚拟机而言 - 除了这些日志选项之外 - 我们还可以使用串行控制台仿真来连接到虚拟机控制台。如果我们无法以其他方式访问虚拟机,尤其是在我们的环境中不使用 GUI 的情况下,这通常是在生产环境中的情况。访问控制台可以按以下方式完成:

virsh console kvm_domain_name

在上述命令中,kvm_domain_name是我们想要通过串行控制台连接的虚拟机的名称。

高级故障排除工具

根据主题 - 网络、硬件和软件问题,或特定应用程序问题 - 我们可以使用不同的工具来排除我们环境中的问题。让我们简要地回顾一些这些方法,同时牢记本书的章节,以便我们排除故障:

  • oVirt 问题

  • 快照和模板问题

  • 虚拟机定制问题

  • Ansible 问题

  • OpenStack 问题

  • Eucalyptus 和 AWS 组合问题

  • ELK 堆栈问题

有趣的是,当我们处理 KVM 虚拟化时,通常不会遇到网络问题。这些都有很好的文档支持——从 KVM 桥接到 open vSwitch——只需要按照文档进行操作。唯一的例外是与防火墙规则相关的问题,特别是在处理 oVirt 和远程数据库连接时,同时保持最小的安全性。如果你对此感兴趣,请确保查看以下链接:www.ovirt.org/documentation/installing_ovirt_as_a_standalone_manager_with_remote_databases/#dns-requirements_SM_remoteDB_deploy

在文章的后面有一个大表格描述了每个端口用于什么以及它们使用的协议。此外,还有一个需要在 oVirt 主机级别配置的端口表。如果你要将 oVirt 投入生产,我们建议你使用这篇文章。

oVirt

在处理 oVirt 时,我们经常遇到两个常见问题:

  • 安装问题:当我们在引擎设置中输入安装选项并正确配置事物时,我们需要放慢速度。

  • 更新问题:这些问题可能与不正确地更新 oVirt 或底层系统有关。

安装问题通常很容易排除故障,因为它们通常发生在我们刚开始部署 oVirt 时。这意味着我们可以奢侈地停止安装过程,从头开始。其他一切都会变得太混乱和复杂。

然而,更新问题值得特别一提。让我们处理 oVirt 更新问题的两个子集并对它们进行更详细的解释。

更新 oVirt Engine 本身需要做的事情是我们大多数人都不喜欢做的——阅读大量的文档。我们需要首先检查我们正在运行的 oVirt 版本。例如,如果我们正在运行 4.3.0 版本,想要升级到 4.3.7 版本,这是一个相对简单的次要更新路径。我们需要首先备份我们的 oVirt 数据库:

engine-backup --mode=backup --file=backupfile1 --log=backup.log

我们这样做只是作为一种预防措施。然后,如果以后出现问题,我们可以使用以下命令:

engine-backup --mode=restore --log=backup.log --file=backupfile1 --provision-db --provision-dwh-db --no-restore-permissions 

如果你没有部署 DWH 服务及其数据库,可以忽略--provision-dwh-db选项。然后,我们可以执行标准程序:

engine-upgrade-check
yum update ovirt\*setup\*
engine-setup

这应该大约需要 10 分钟,不会造成任何伤害。但最好还是小心为妙,在这之前备份数据库。

然而,如果我们从旧版本的 oVirt 迁移到最新版本——比如从 4.0.0 版本、4.1.0 版本或 4.2.0 版本迁移到 4.3.7 版本——那就是一个完全不同的过程。我们需要去 ovirt.org 网站阅读文档。例如,假设我们要从 4.0 升级到 4.3。ovirt.org 上有描述所有这些过程的文档。你可以从这里开始:www.ovirt.org/documentation/upgrade_guide/

这将给我们大约 20 个子步骤,我们需要完成才能成功升级。请小心和耐心,因为这些步骤是按照非常清晰的顺序编写的,需要按照这种方式实施。

现在我们已经涵盖了升级方面的 oVirt 故障排除,让我们深入研究操作系统和软件包升级,因为这是一个完全不同的讨论,需要考虑的事情更多。

考虑到 oVirt 有自己的先决条件,从 CPU、内存和存储需求到防火墙和存储库需求,我们不能盲目地使用系统范围的命令,比如以下命令:

yum -y update

我们不能指望 oVirt 对此感到满意。它不会,这在生产环境和写作本书时都发生过很多次。我们需要检查将部署哪些软件包,并检查它们是否与 oVirt 存在某种相互依赖关系。如果有这样的软件包,你需要确保按照本章前面提到的进行引擎备份过程。这将避免给你带来很多问题。

不仅 oVirt Engine 可能会出问题-更新 oVirt 库存中的 KVM 主机也可能会相当戏剧化。oVirt 引擎或我们的手动安装程序部署在主机上的 oVirt 代理(vdsm)及其组件,也有它们自己的相互依赖关系,可能会受到系统范围内的yum -y update命令的影响。因此,在接受升级之前,要先拉手刹,因为它可能会带来很多痛苦。确保你检查vdsm日志(通常位于/var/log/vdsm目录中)。当你试图解释vdsm出了什么问题时,这些日志文件非常有帮助。

oVirt 和 KVM 存储问题

我们遇到的大多数存储问题通常与 LUN 或共享向主机的呈现有关。特别是当你处理块存储(光纤通道或 iSCSI)时,我们需要确保不要从主机中排除或屏蔽 LUN,否则主机将看不到它。相同的原则也适用于 NFS 共享、Gluster、CEPH 或我们正在使用的任何其他类型的存储。

除了这些预配置问题之外,最常见的问题与故障转移有关-即路径指向存储设备失败的情况。这时,如果我们扩展了存储或存储网络基础设施一点-增加了额外的适配器、额外的交换机、配置了多路径(MPIO)等,我们会感到非常高兴。确保你查阅存储设备供应商的文档,并按照特定存储设备的最佳实践进行操作。相信我们说的- iSCSI 存储配置及其默认设置与配置光纤通道存储有天壤之别,特别是涉及多路径时。例如,使用 iSCSI 的 MPIO,如果正确配置,它会更加愉快和敏捷。你将在本章末尾的进一步阅读部分找到更多关于这个过程的细节。

如果你正在使用基于 IP 的存储,请确保通向存储设备的多个路径使用单独的 IP 子网,因为其他一切都是一个坏主意。类似 LACP 的技术和 iSCSI 不应该出现在同一个句子中,否则你将会对一个不适用于存储连接并且正常工作的技术进行故障排除,而你却认为它没有正常工作。我们需要知道我们在进行故障排除的是什么;否则,故障排除就毫无意义。为 iSCSI 创建 LACP 等同于仍然使用一个路径进行 iSCSI 连接,这意味着浪费了网络连接,除了在故障转移的情况下,它并没有被主动使用。而你实际上并不需要 LACP 或类似的技术。唯一的例外可能是刀片服务器,因为在刀片上的升级选项确实有限。但即使是这样,解决我们需要更多带宽从主机到存储的问题的方法是获得更快的网络或光纤通道适配器。

快照和模板的问题-虚拟机定制

老实说,多年来在各种虚拟化技术上工作,涵盖了 Citrix、微软、VMware、Oracle 和 Red Hat,我们看到了许多不同的快照问题。但只有当你开始在企业 IT 中工作,看到操作、安全和备份程序有多么复杂时,你才意识到创建快照这样一个简单的过程可能有多危险。

我们见过以下情况:

  • 备份应用程序不想启动,因为虚拟机有快照(常见情况)。

  • 快照不想删除和组装。

  • 多个快照不想删除和组装。

  • 快照因古怪的原因使虚拟机崩溃。

  • 快照因为存储空间不足而使虚拟机崩溃(常见情况)。

  • 快照使虚拟机中运行的应用程序崩溃,因为该应用程序不知道如何在快照之前进行整理,进入脏状态(VSS,同步问题)。

  • 快照被轻微滥用,发生了一些事情,我们需要进行故障排除

  • 快照被严重滥用,总是发生一些事情,我们需要进行故障排除

这种情况比预期的要频繁得多,因为人们确实倾向于在获得许可的情况下大量使用快照。我们曾看到在生产环境中运行的虚拟机拥有 20 多个快照,人们抱怨它们运行缓慢。在这种情况下,您只能深呼吸,深呼吸,耸耸肩,然后问:“你期望什么,20 多个快照会增加虚拟机的速度吗”?

在所有这些问题中,帮助我们度过所有这些问题的是三个基本原则:

  • 真正了解在任何给定技术上快照的工作原理。

  • 确保每次我们甚至考虑使用快照时,首先检查虚拟机所在数据存储中的可用存储空间量,然后检查虚拟机是否已经有快照。

  • 不断重复这句口头禅:“快照不是备份”,一遍又一遍地对我们的所有客户进行灌输,并用额外的文章和链接向他们解释为什么他们需要停止使用快照,即使这意味着拒绝某人甚至拍摄快照的许可。

实际上,最后一个情况已经成为我们遇到的许多环境中的事实政策。我们甚至看到一些公司在处理快照时实施了一项明确的政策,规定公司政策是在有限的时间内最多拥有一两个快照。例如,在 VMware 环境中,您可以分配一个虚拟机高级属性,设置最大快照数为 1(使用名为snapshot.maxSnapshots的属性)。在 KVM 中,您将不得不针对这些情况使用基于存储的快照,并希望存储系统具有基于策略的功能来设置快照数量。然而,在许多环境中,这有点违背了使用基于存储的快照的想法。

模板化和虚拟机定制是另一个完全不同的故障排除世界。模板化很少会引起问题,除了我们在[第八章](B14834_08_Final_ASB_ePub.xhtml#_idTextAnchor143)中提到的警告之外,创建和修改 VM 磁盘、模板和快照,与在 Windows 机器上串行使用sysprep相关的警告。创建 Linux 模板现在非常简单,人们使用virt-sysprepsys-unconfig或自定义脚本来执行。但与虚拟机定制相关的下一步是完全不同的事情。特别是在使用 cloudbase-init 时,因为多年来 cloud-init 一直是预配置 Linux 虚拟机在云环境中的标准方法。

以下是一个简短的列表,列出了我们在 cloudbase-init 中遇到的一些问题:

  • Cloudbase-init 由于“无法加载用户配置文件:设备尚未准备好”而失败。

  • 域加入不可靠。

  • 网络设置期间出现错误。

  • 通过 cloudbase-init 重置 Windows 密码。

  • 使 cloudbase-init 从指定目录执行 PowerShell 脚本。

这些和其他问题的绝大多数都与 cloudbase-init 的文档非常糟糕有关。它确实有一些配置文件示例,但其中大部分更多与 API 或编程方法相关,而不是通过示例来解释如何创建某种配置。此外,我们在第十章中提到的不同版本也存在各种问题,自动化 Windows 客户端部署和自定义。然后,我们选择了一个预发布版本,它可以直接使用配置文件,而在稳定版本上无法正常工作。但总的来说,我们在尝试使其正常工作时遇到的最大问题是与 PowerShell 正确配合。如果我们能够正确执行 PowerShell 代码,我们几乎可以在 Windows 系统上配置任何我们想要的东西,所以这是一个大问题。有时,它不想从 Windows 系统磁盘上的随机目录执行 PowerShell 脚本。

确保您在本书中使用示例作为起点。我们故意将第十章中的示例尽可能简单化,其中包括执行的 PowerShell 代码。之后,展开你的翅膀,做任何需要做的事情。当您使用基于 Microsoft 的解决方案时,无论是本地还是混合解决方案,PowerShell 都会使一切变得更容易和更自然。

使用 Ansible 和 OpenStack 的问题

我们与 Ansible 和 OpenStack 的第一次互动发生在多年前 - Ansible 于 2012 年推出,OpenStack 于 2010 年推出。我们一直认为它们都是非常酷的工具,尽管存在一些问题。其中一些小问题与开发速度快(OpenStack)有关,大量的错误从一个版本到另一个版本得到解决。

在 Ansible 方面,我们与人们进行了大量的争论 - 一天,主题与“我们习惯使用 Puppet,为什么需要 Ansible?!”有关;第二天是“啊,这个语法太复杂了”;第三天是其他事情,还有其他事情……通常都与 Ansible 架构更简单,语法在一开始至少更复杂有关。对于 Ansible 来说,一切都取决于语法,我们相信您要么已经知道,要么很快就会发现。

故障排除 Ansible playbook 通常是一个过程,有 95%的机会是我们在配置文件中拼写错误或输入错误。我们谈论的是您已经有机会使用 Ansible 一段时间的初始阶段。确保重新检查 Ansible 命令的输出,并将其用于此目的。在这方面,它真的很出色。您不需要进行复杂的配置(例如libvirtd)来从执行的过程和 playbook 中获得可用的输出。这使我们的工作变得更加容易。

故障排除 OpenStack 是完全不同的一桩麻烦。有一些充分记录的 OpenStack 问题,这些问题也可能与特定设备有关。让我们举一个例子 - 查看以下链接,了解在使用 NetApp 存储时出现的问题:netapp-openstack-dev.github.io/openstack-docs/stein/appendices/section_common-problems.html

以下是一些例子:

  • 创建卷失败

  • 克隆卷失败

  • 卷附加失败

  • 卷上传到镜像操作失败

  • 卷备份和/或恢复失败

然后,例如,查看这些链接:

正如你可能已经推断出的那样,OpenStack 在存储方面非常挑剔。这就是为什么存储公司通常为他们自己的存储设备创建参考架构,以在基于 OpenStack 的环境中使用。查看 HPE 和戴尔 EMC 的这两份文件,作为这种方法的良好示例:

最后要警告的是,最难克服的障碍与 OpenStack 版本升级有关。关于这个主题,我们可以告诉你很多恐怖故事。话虽如此,我们在这里也有部分责任,因为我们作为用户部署了各种第三方模块和实用程序(基于供应商的插件、分支、未经测试的解决方案等),忘记使用它们,然后当升级过程失败时,我们真的感到惊讶和恐惧。这可以追溯到我们在整本书中一直讨论的关于记录环境的多个讨论。这是一个我们将在本章稍后再次讨论的主题。

依赖关系

每个管理员都完全意识到几乎每个服务都有一些依赖关系 - 要么是依赖于运行此特定服务的服务,要么是我们的服务需要工作的服务。在处理软件包时,依赖关系也是一个重要因素 - 软件包管理器的整个目的是严格注意需要安装的内容以及依赖它的内容,以便我们的系统正常工作。

大多数管理员犯的错误是忘记了,在更大的系统中,依赖关系可能延伸到多个系统、集群,甚至数据中心。

每个涵盖 OpenStack 的课程都有一个专门的课程,介绍如何启动、停止和验证不同的 OpenStack 服务。其原因很简单 - OpenStack 通常在大量节点上运行(数百,有时数千)。一些服务必须在每个节点上运行,一些服务需要一组节点,一些服务在每个节点实例上都是重复的,一些服务只能存在一个实例。

了解每个服务的基础知识以及它如何融入整个 OpenStack 架构不仅在安装整个系统时至关重要,而且在调试为什么某些东西在 OpenStack 上不起作用时也是最重要的。至少阅读一次文档以“串联起点”。同样,在本章末尾的“进一步阅读”部分包含了指向 OpenStack 正确方向的链接。

OpenStack 是那些在文档中包括“如何正确地重新启动运行 X 的机器?”的系统之一。其原因就像整个系统一样复杂 - 系统的每个部分既有它所依赖的东西,也有一些依赖它的东西 - 如果某些东西出了问题,你不仅需要了解系统的这个特定部分是如何工作的,还需要了解它如何影响其他一切。但是在所有这些中有一线希望 - 在一个正确配置的系统中,很多东西都是冗余的,所以有时,修复某些东西最简单的方法是重新安装它。

这可能总结了整个故障排除的故事 - 试图修复一个简单的系统通常比修复一个复杂的系统更复杂和耗时。了解它们各自的工作原理是最重要的部分。

故障排除 Eucalyptus

说我们开始安装过程后一切都按照手册进行是不真实的 - 大部分是,我们相当肯定,如果您按照我们记录的步骤进行,您最终会得到一个可用的服务或系统,但在任何时候,都会有事情可能会出错。这时候你需要做的是最难以想象的事情 - 故障排除。但你该怎么做呢?信不信由你,有一种更或多或少系统化的方法可以让你解决几乎任何问题,不仅仅是 KVM/OpenStack/AWS/Eucalyptus 相关的问题。

收集信息

在我们做任何事情之前,我们需要做一些研究。这是大多数人做错事情的时刻,因为显而易见的答案是去互联网上搜索问题。看看这个屏幕截图:

图 16.5 - Eucalyptus 日志,第一部分 - 清晰、简洁、易读 - Eucalyptus 中已完成的每个过程在日志中清晰可见

图 16.5 - Eucalyptus 日志,第一部分 - 清晰、简洁、易读 - Eucalyptus 中已完成的每个过程在日志中清晰可见

如果你还没有注意到,互联网上充满了几乎任何想象问题的现成解决方案,其中很多是错误的。这是因为有两个原因:大多数解决方案的人并不了解问题是什么,所以一旦他们找到了解决他们特定问题的任何解决方案,他们就停止解决它。换句话说 - 许多 IT 人员试图将从 A 点(问题)到 B 点(解决方案)的路径描绘成一束激光束 - 超级平坦,最短可能的路径,沿途没有障碍。一切都很好,设计得很干净,旨在在激光束原则停止工作时干扰我们的故障排除思维过程。这是因为,在 IT 领域,事情很少那么简单。

例如,由 DNS 配置错误引起的任何问题。其中大多数可以通过在hosts文件中创建一个条目来“解决”。这个解决方案通常有效,但同时在几乎任何层面上都是错误的。这个解决方案只在一个机器上解决了问题 - 那个特定的主机文件所在的机器。而 DNS 仍然配置错误;我们只是创建了一个快速的、未记录的解决方法,它在我们的特定情况下有效。每台其他有相同问题的机器都需要以这种方式进行修补,而我们的修复可能会在未来产生更多问题的真正可能性。

真正的解决方案显然是找到问题的根源并解决 DNS 的问题,但在互联网上很少有这样的解决方案。这主要是因为互联网上的大多数评论者对许多服务不熟悉,快速修复基本上是他们能够应用的唯一方法。

互联网大部分错误的另一个原因是因为著名的“重新安装解决了问题”的解决方案。Linux 在这方面的记录更好,因为使用它的人不太倾向于通过擦除和重新安装系统来解决所有问题,但你会发现大多数 Windows 问题的解决方案至少会有一个简单的“重新安装解决了问题”。与仅仅给出一个随机的解决方案作为总是有效的解决方案相比,这种“重新安装”方法要糟糕得多。这不仅意味着你将浪费大量时间重新安装所有东西;它还意味着你的问题最终可能会或可能不会得到解决,这取决于问题实际上是什么。

因此,我们将给出的第一个简短建议是,“不要盲目相信互联网”。

好了,但你实际上应该做什么呢?让我们来看看:

  1. 收集关于问题的信息。阅读错误消息,阅读日志(如果应用程序有日志),并尽可能打开调试模式。获取一些实际数据。找出是什么导致了崩溃,以及是如何崩溃的,以及是什么问题导致了崩溃。看一下下面的截图:图 16.6 - 桉树日志,第二部分 - 再次,干净,清晰,易于阅读 - 有关更新内容和位置的信息消息

图 16.6 - 桉树日志,第二部分 - 再次,干净,清晰,易于阅读 - 有关更新内容和位置的信息消息

  1. 阅读文档。你正在尝试的事情是否得到支持?功能系统的先决条件是什么?你是否遗漏了什么?缓存磁盘?一些内存?你特定系统的依赖基本服务?一个库或额外的软件包的依赖?固件升级?

有时,你会遇到一个更大的问题,特别是在文档写得很糟糕的情况下 - 可能会在次要提到一些关键的系统依赖项,并可能导致整个系统崩溃。例如,外部识别服务 - 也许你的目录使用了错误的字符集,导致当特定用户以特定方式使用时系统崩溃。始终确保你了解你的系统是如何相互连接的。

接下来,检查你的系统。如果你正在安装一个新系统,请检查先决条件。你有足够的磁盘空间内存吗?你的应用程序需要的所有服务是否都可用并正常工作?

搜索互联网。我们之前提到过,互联网对所有可能的问题都有一个简单而不正确的解决方案,但通常也会在错误的解决方案中隐藏着正确的解决方案。当你武装自己拥有关于你特定系统和特定问题的大量数据后,互联网很快就会成为你的朋友。因为你了解问题是什么,你将能够理解提供给你的解决方案中哪些是错误的。

现在,让我们谈谈我们故意在安装桉树时制造的一个现实世界的问题,只是为了向你展示文档有多重要。

我们在第十三章中向你展示了如何安装桉树,使用 AWS 扩展 KVM - 我们不仅经历了安装过程,还学会了如何使用这个令人惊叹的服务。如果你想学习一些关于如何不做的东西,继续阅读。我们将向你展示一个故意创建的桉树安装失败的场景,因为我们创造性地忘记了一些我们知道需要做的步骤。让我们这样说 - 我们表现得像人类一样,使用了浏览文档的方法,而不是实际坐下来阅读文档。这听起来熟悉吗?

安装桉树应该是一项简单的任务,因为它的安装本质上是一个应用脚本的练习。桉树甚至在项目的首页上都这样说:只需运行这个脚本

但事实要复杂得多 - 桉树确实可以只使用这个脚本进行安装,但必须满足一些先决条件。当然,在你急于测试新服务时,你可能会忽略阅读文档,就像我们一样,因为我们已经有了桉树的经验。

我们配置了系统,启动了安装,然后遇到了问题。在确认了初始配置步骤后,我们的安装失败了,错误消息显示无法解析特定地址:192.168.1.1.nip.io

DNS 是 IT 基础设施中的主要问题之一,我们迅速开始调试 - 我们想要看到的第一件事是这个特定地址是什么。实际上,在 IT 中有一句话 - 总是 DNS。它看起来像一个本地地址,所以我们开始 ping 它,看起来还好。但为什么 DNS 甚至与 IP 地址有关?DNS 应该解析域名,而不是 IP 地址。然后,我们转向文档,但没有得到太多信息。我们唯一发现的是 DNS 必须为整个系统工作。

然后,是时候尝试调试 DNS 了。首先,我们尝试从我们正在安装的机器上解析它。DNS 返回超时。我们在另一台机器上尝试了一下,得到了我们没有预期的响应 - 127.0.0.1.nip.io解析为127.0.0.1,这意味着本地主机。基本上,我们向互联网上的 DNS 请求一个地址,它将我们引导到我们的本地系统。

所以,我们遇到了一个我们不理解的错误,一个解析为我们没有预期的 IP 地址的地址,以及两个不同的系统对相同命令表现出完全不同的行为。我们把注意力转向我们正在安装的机器,意识到它配置错误 - 没有配置 DNS。这台机器不仅无法解析我们奇怪的 IP 地址,而且无法解析任何东西。

我们通过指向正确的 DNS 服务器来解决了这个问题。然后,按照真正的 IT 方式,我们重新启动了安装,以便我们能够继续进行这一部分,一切都还好,或者看起来是这样。但发生了什么?为什么一个本地服务解析如此奇怪的名称,为什么它们会被解析?

我们转向互联网,看了一下我们神秘名称末尾的域名。我们发现服务nip.io实际上就是我们观察到的事情 - 当要求返回本地子网范围内 IP 地址形成的特定名称时(由RFC 1918定义),它返回相同的 IP。

我们接下来的问题是 - 为什么?

经过更多阅读,您会意识到这里的诀窍是什么 - 桉树使用 DNS 名称与其所有组件进行通信。作者非常明智地选择不将单个地址硬编码到应用程序中,因此系统的所有服务和节点都必须具有真实的 DNS 注册名称。在正常的多节点,多服务器安装中,这就像魅力一样 - 每个服务器和每个节点首先都要在适当的 DNS 服务器上注册,桉树将尝试解析它们以便与机器通信。

我们正在安装一个拥有所有服务的单个机器,这使得安装更容易,但节点没有单独的名称,甚至我们的机器可能没有在 DNS 中注册。因此,安装程序做了一个小技巧。它将本地 IP 地址转换为完全有效的域名,并确保我们可以解析它们。

所以,现在我们知道发生了什么(解析过程不起作用)以及为什么会发生(我们的 DNS 服务器设置出了问题),但我们也理解了为什么首先需要 DNS。

这带我们到下一个点 - 不要假设任何事情

在我们排除故障并跟进我们的 DNS 问题时,我们的安装崩溃了。桉树是一个复杂的系统,它的安装是一件相当复杂的事情 - 它会自动更新您运行它的机器,然后安装似乎有成千上万的软件包,然后下载,配置和运行一小部分镜像和虚拟软件包。为了保持整洁,用户看不到发生的一切,只能看到最重要的部分。安装程序甚至有一个漂亮的 ASCII 图形屏幕让你忙碌。一切都还好,但突然之间,我们的安装完全崩溃了。我们得到的只是一个看起来像属于 Python 语言的巨大堆栈跟踪。我们重新运行了安装,但它再次失败了。

此时的问题是,我们不知道为什么会发生这一切,因为安装要求对 CentOS 7 进行最小安装。我们在虚拟机上运行测试,并确实进行了最小安装。

我们重新尝试从头安装。重新安装整个机器只花了几分钟,然后我们重新尝试安装。结果是一样的 - 一个失败的安装,让我们得到了一个无法使用的系统。但有一个可能的解决方案 - 或者更准确地说,是了解发生了什么。

与 IT 宇宙中所有伟大的安装程序一样,这个安装程序也为这种可能性准备了特别的东西:一个日志文件。看一下下面的截图:

图 16.7 - 当你不阅读 Eucalyptus 的文档时,安装过程会花费很长时间。然后还需要更多的时间...还有更多...

图 16.7 - 当你不阅读 Eucalyptus 的文档时,安装过程会花费很长时间。然后还需要更多的时间...还有更多...

这是安装屏幕。我们看不到关于正在发生的事情的任何真实信息,但从顶部的第三行中可以找到最重要的线索 - 日志文件的位置。为了防止屏幕被信息淹没,安装程序显示了这个非常漂亮的 figlet-coffee 图形(所有在 20 世纪 90 年代和 2000 年代使用 IRC 的人现在可能会微笑),但也将正在发生的一切都转储到日志中。这里的一切,是指每个命令、每个输入和每个输出。这使得调试变得容易 - 我们只需要滚动到文件的末尾,然后尝试从那个点开始向后查看是什么出了问题。一旦我们这样做了,解决方案就很简单 - 我们忘记为机器分配足够的内存。我们给了它 8GB 的 RAM,而官方上应该至少有 16GB 才能顺利运行。有报道称有机器只有 8GB 的 RAM 也能运行,但这完全没有意义 - 毕竟我们正在运行一个虚拟化环境。

AWS 及其冗长,这并不有助于解决问题

我们想提到的另一件事是 AWS 以及如何对其进行故障排除。AWS 是一个了不起的服务,但它有一个巨大的问题 - 就是它的规模。有这么多服务、组件和服务部分,你需要使用它们来在 AWS 上运行一些东西,简单的任务可能会变得非常复杂。我们的情景涉及尝试建立一个我们用作示例的 EC2 实例。

这个任务相对简单,演示了一个简单的问题可以有一个简单的解决方案,同时也可能完全不明显。

让我们回到我们试图做的事情。我们有一台机器在本地磁盘上。我们必须将它转移到云端,然后创建一个正在运行的虚拟机。这可能是最简单的事情之一。

为此,我们创建了一个 S3 存储桶,并将我们的机器从本地机器转移到了云端。但在我们尝试运行机器后,我们得到的只是一个错误。

像 AWS 这样的服务最大的问题是它的庞大,没有办法一次性理解所有东西 - 你必须一块一块地建立你的知识。所以,我们回到了文档。AWS 有两种文档 - 广泛的帮助,涵盖了每个服务的每个命令和每个选项,以及引导示例。帮助是令人惊叹的,但如果你不知道自己在找什么,它将毫无用处。这种形式的帮助只有在你对概念有基本的理解时才有效。如果你是第一次做某事,或者你遇到了以前没有遇到过的问题,我们建议你找到一个你正在尝试做的任务的示例,并进行练习。

在我们的情况下,这很奇怪,因为我们只需要运行一个简单的命令。但我们的导入仍然失败。在我们碰头几个小时后,我们决定表现得像我们什么都不知道,然后做了如何将 VM 导入 AWS?的示例。一切都很顺利。然后,我们尝试导入我们自己的机器;那不起作用。命令被复制/粘贴,但仍然不起作用。

然后我们意识到最重要的一点是我们需要注意细节。如果没有正确实施和执行这种思维方式,我们将给自己带来一系列问题。

注意细节

长话短说,我们做错的是错误配置了身份服务。在 AWS 等云环境中,每个服务都作为独立的域运行,与其他服务完全分开。当需要执行某项任务时,执行任务的服务必须具有某种授权。有一个服务负责处理这个问题-IAM-每个服务的每个请求的明显默认值是拒绝一切。一旦我们决定需要做什么,我们就有责任配置适当的访问和授权。我们知道这一点,所以我们为 EC2 访问 S3 中的文件创建了所有角色和权限。尽管这听起来可能有些奇怪,但我们必须授予我们正在使用的服务获取我们上传的文件的权限。如果你是新手,你可能期望这是自动的,但事实并非如此。

查看以下摘录,这是 AWS 预定义的角色列表中的一小部分。请记住,完整列表要长得多,我们只是触及了所有可用角色的表面。这些只是以字母A开头的角色:

图 16.8 - AWS 预定义角色

图 16.8 - AWS 预定义角色

我们错误配置的是角色的名称-为了将 VM 导入 EC2 实例,需要有一个名为vmimport的安全角色授予 EC2 正确的权限。我们匆忙配置了一个名为importvm的角色。当我们完成示例时,我们粘贴了示例,一切都很好,但一旦我们开始使用我们的安全设置,EC2 就无法完成其工作。因此,始终检查产品文档并仔细阅读

解决 ELK 堆栈的问题

ELK 堆栈可以有效地用于监视我们的环境。它确实需要一些手动操作,额外的配置,并且需要有点诡计,但它仍然可以提供报告、自动报告、通过电子邮件发送报告以及许多其他有价值的东西。

开箱即用,你不能直接发送报告-你需要再做一些调查。你可以使用 Watcher,但你需要的大部分功能是商业化的,所以你需要在上面花一些钱。还有其他一些方法:

这里还有一个建议:你可以通过rsyslog来集中日志,因为这是一个内置功能。如果你创建一个集中式日志服务器,那里有免费的应用程序可以浏览日志文件(例如 Adiscon LogAnalyzer)。如果处理 ELK 似乎有点难以应付,但你意识到你需要一些东西,那就从这样的东西开始。它非常容易安装和配置,并提供一个带有正则表达式支持的免费 Web 界面,以便你可以浏览日志条目。

KVM 问题的故障排除最佳实践

在解决 KVM 问题时,有一些常识最佳实践。让我们列出其中一些:

  • 保持简单,进行配置:部署了 50 个 OpenStack 主机跨三个子网在一个站点中的情况有什么好处呢?仅仅因为你可以将子网划分到 IP 范围的极限并不意味着你应该这样做。仅仅因为你的服务器有八个可用连接并不意味着你应该将它们全部连接到 iSCSI 存储。考虑端到端的配置(例如,iSCSI 网络的巨帧配置)。简单的配置几乎总是意味着更简单的故障排除。

  • 在故障排除中保持简单:不要首先追求超复杂的场景。从简单开始。从日志文件开始。检查那里写了什么。随着时间的推移,使用你的直觉,因为它会发展,你将能够信任它。

  • 使用监控工具,如 ELK 堆栈:使用某种工具不断监视你的环境。投资一些大屏幕显示器,将其连接到一台独立的计算机,挂在墙上,并花时间为你的环境配置重要的仪表板。

  • 使用报告工具创建关于环境状态的多个自动报告:例如,Kibana 支持以 PDF 格式生成报告。当你监视你的环境时,你会注意到你的环境中一些更敏感的部分,比如存储。监视可用空间的数量。监视路径活动和从主机到存储的网络连接断开。创建报告并自动发送到你的电子邮件。那里有很多选择,所以利用它们。

  • 在配置环境时创建笔记:如果没有别的,至少这样做,这样你就有了一些起点和/或未来的参考,因为经常会有许多即兴的更改。当记笔记的过程结束时,创建文档。

  • 创建文档:使其永久,可读,并尽可能简单。不要记住事情,把事情写下来。使写下一切成为一项使命,并试图在你周围传播这种文化。

习惯于随时拥有大量的<插入你最喜欢的饮料>,并且经常熬夜,如果你想成为 IT 管理员、工程师或 DevOps 工程师。咖啡、百事可乐、可口可乐、柠檬汁、橙汁……无论什么能让你的智力活力充沛。有时,学会暂时远离问题。解决方案通常会在你思考与工作完全相反的事情时在你脑海中闪现。

最后,记住在工作时要尽量享受乐趣。否则,与 KVM 或任何其他 IT 解决方案一起工作的整个过程只会成为无休止的挫败感。挫折从来不是一件有趣的事情。我们更喜欢对着计算机或服务器大喊大叫。这是一种治疗方法。

总结

在本章中,我们试图描述一些基本的故障排除步骤,这些步骤通常适用于故障排除 KVM。我们还讨论了在处理本书的各种主题时我们不得不处理的一些问题——Eucalyptus,OpenStack,ELK 堆栈,cloudbase-init,存储等。这些问题大多是由于错误配置引起的,但也有一些严重缺乏文档的问题。无论发生什么,不要放弃。排除故障,让它工作,并在你做到时庆祝。

问题

  1. 在部署 KVM 堆栈之前,我们需要检查什么?

  2. 在部署 KVM 堆栈后,我们需要配置什么,以确保虚拟机在重新启动后能够运行?

  3. 我们如何检查 KVM 客户端日志文件?

  4. 我们如何永久打开和配置 KVM 调试日志?

  5. 我们如何在运行时打开和配置 KVM 调试日志?

  6. 解决 oVirt 安装问题的最佳方法是什么?

  7. 解决 oVirt 的小版本和大版本升级问题的最佳方法是什么?

  8. 管理 oVirt Engine 和主机更新的最佳方法是什么?

  9. 我们为什么需要小心快照?

  10. 模板和 cloudbase-init 的常见问题是什么?

  11. 安装 Eucalyptus 时我们的第一步应该是什么?

  12. 我们可以使用 ELK 堆栈的哪些高级监控和报告功能?

  13. 在故障排除基于 KVM 的环境时有哪些最佳实践?

更多阅读

有关本章涵盖内容的更多信息,请参考以下链接:

posted @ 2024-05-16 19:13  绝不原创的飞龙  阅读(318)  评论(0)    收藏  举报