KVM-虚拟化秘籍-全-
KVM 虚拟化秘籍(全)
原文:
annas-archive.org/md5/6cf3a96b9a2129694cfeb31febe7ce37
译者:飞龙
前言
现代大多数云部署的基础是某种虚拟化技术,例如基于内核的虚拟机(KVM)。KVM 自 2.6.20 版本起就成为主流 Linux 内核的一部分,该版本发布于 2007 年 2 月,自那时起,KVM 得到了广泛的系统应用。
虚拟化不仅提供了充分利用服务器资源的方式,还允许更大的多租户支持,并能在同一系统上运行各种工作负载。
OpenStack 云操作系统将 KVM 作为其默认计算驱动程序,提供了一种集中管理虚拟机生命周期的方式:从创建、调整大小、迁移到暂停和终止。
本书介绍了 KVM 及其如何以最有效的方式构建和管理虚拟机。与 Docker 等容器化解决方案不同,KVM 旨在运行整个操作系统,而不是单个进程。尽管容器化有其优势,完全虚拟化通过在客户操作系统和主机之间加入虚拟机监控程序层提供了额外的安全性,并且通过运行不同的客户操作系统内核(客户实例的内核崩溃不会导致整个主机宕机)或完全不同的操作系统提供了额外的稳定性。
本书采取了一种直接且务实的逐步方法——你将学习如何创建自定义客户镜像,安装和配置 QEMU 和 libvirt,调整实例大小和迁移实例,部署监控,并使用 OpenStack 和 Python 为客户提供服务。
本书涵盖的内容
第一章,QEMU 和 KVM 入门, 提供了安装和配置 QEMU、创建和管理磁盘镜像以及使用 qemu-system 工具运行虚拟机的食谱。
第二章,使用 libvirt 管理 KVM, 涵盖了使用 libvirt 安装、配置和运行 KVM 实例所需的一切内容。你将学习所需的包和工具,以及如何使用 XML 定义文件配置虚拟机的不同方式。在本章结束时,你将拥有一个运行 KVM 实例的 Linux 系统。
第三章,使用 libvirt 进行 KVM 网络配置, 将介绍如何使用 Linux 桥接和 Open vSwitch 工作,并演示如何使用 NAT、桥接和 PCI 直通网络连接 KVM 实例。
第四章,迁移 KVM 实例, 将展示如何进行运行中的 KVM 虚拟机的离线和在线迁移的示例。
第五章,KVM 虚拟机的监控和备份, 将展示如何使用 Sensu 和 Uchiwa 部署完整的监控系统,并演示如何创建快照以用作备份。
第六章,使用 OpenStack 部署 KVM 实例,演示了如何使用 OpenStack 配置 KVM 实例。它首先介绍了构成 OpenStack 的各种组件,以及如何使用 LXC Nova 驱动程序自动配置虚拟机。
第七章,使用 Python 构建和管理 KVM 实例,将展示使用 Python libvirt 库来构建、启动和管理 KVM 实例生命周期的教程。我们还将看到如何构建一个简单的 RESTful API 来与 KVM 配合使用。
第八章,KVM 性能调优,展示了如何调整宿主操作系统,以优化 I/O、CPU、内存和网络利用率。所示的示例也可以根据 KVM 实例的工作负载在其中使用。
本书所需的工具
跟随本书教程并运行示例需要具备初级 Linux 和命令行知识。要完全理解并能够运行第七章,使用 Python 构建和管理 KVM 实例中的示例,需要一些 Python 经验。
本书中的大多数教程已经在支持虚拟化的裸机服务器和最新版本的 Ubuntu Linux 上进行过测试。
本书适合谁
本书适合任何对 KVM 虚拟化感兴趣的人——从希望深入了解如何在大规模生产环境中部署和管理 KVM 的 Linux 管理员,到需要快速轻松地在隔离的客户机中原型化代码的软件开发人员。对于那些想从头到尾阅读本书并尝试所有示例的人来说,DevOps 工程师可能是最合适的职位名称。
章节
本书中,你会发现几个经常出现的标题(准备工作、如何操作、如何工作、还有更多内容、另见)。
为了清晰地指导完成一个教程,我们使用如下的几个部分:
准备工作
本节告诉你可以期待本教程中的内容,并描述了如何设置任何软件或进行任何预备设置。
如何操作…
本节包含了完成本教程所需的步骤。
如何工作…
本节通常包含对上一节内容的详细解释。
还有更多内容…
本节包含有关该教程的附加信息,以帮助读者更好地理解教程内容。
另见
本节提供了有助于本教程的其他有用信息的链接。
约定
本书中,你将看到一些文本样式,用于区分不同类型的信息。以下是这些样式的一些示例,并附上它们的含义解释。
文本中的代码词汇、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 账号都以如下方式显示:“使用 qemu-img
管理磁盘镜像。”
代码块如下所示:
import libvirt
from bottle import run, request, get, post, HTTPResponse
def libvirtConnect():
try:
conn = libvirt.open('qemu:///system')
except libvirt.libvirtError:
conn = None
return conn
任何命令行输入或输出都以如下格式书写:
root@kvm:~# apt-get update
新术语和重要词汇以粗体显示。你在屏幕上看到的词汇,如在菜单或对话框中的内容,都会以如下方式出现在文本中:“KVM 实例的 memory_check 现在已显示在 Uchiwa 仪表板中。”
警告或重要说明通常以框的形式显示,如下所示。
小贴士和技巧通常像这样显示。
读者反馈
我们始终欢迎读者反馈。让我们知道你对本书的看法——你喜欢或不喜欢什么。读者的反馈对我们非常重要,它帮助我们开发出你真正能从中受益的书籍。
要向我们提供一般反馈,请直接通过电子邮件发送至 feedback@packtpub.com
,并在邮件主题中提及书名。
如果你在某个领域具有专业知识,并且有兴趣为书籍撰写或贡献内容,请参阅我们的作者指南,链接为 www.packtpub.com/authors。
客户支持
现在,你已经是一本 Packt 书籍的骄傲拥有者,我们有很多帮助你最大限度利用购买内容的资源。
下载示例代码
你可以从你的账户下载本书的示例代码文件,链接为 www.packtpub.com
。如果你是从其他地方购买的本书,可以访问 www.packtpub.com/support
并注册,以便将文件直接发送到你的电子邮箱。
你可以通过以下步骤下载代码文件:
-
使用你的电子邮件地址和密码登录或注册我们的网站。
-
将鼠标指针悬停在页面顶部的 SUPPORT 标签上。
-
点击 Code Downloads & Errata。
-
在搜索框中输入书名。
-
选择你想要下载代码文件的书籍。
-
从下拉菜单中选择你购买这本书的地方。
-
点击 Code Download。
你也可以通过点击本书网页上的 Code Files 按钮下载代码文件。可以通过在搜索框中输入书名来访问此页面。请注意,你需要登录 Packt 账户。
文件下载完成后,请确保使用最新版的解压软件解压文件夹:
-
Windows 下的 WinRAR / 7-Zip
-
Mac 下的 Zipeg / iZip / UnRarX
-
Linux 下的 7-Zip / PeaZip
本书的代码包也托管在 GitHub 上,链接为 github.com/PacktPublishing/KVM-Virtualization-Cookbook
。我们还提供了来自我们丰富书籍和视频目录的其他代码包,链接为 github.com/PacktPublishing/
。快来查看吧!
下载本书的彩色图片
我们还为您提供了一份包含本书中使用的截图/图表的彩色图片的 PDF 文件。这些彩色图片将帮助您更好地理解输出中的变化。您可以从www.packtpub.com/sites/default/files/downloads/KVMVirtualizationCookbook_ColorImages.pdf.
下载该文件。
勘误
虽然我们已尽最大努力确保内容的准确性,但错误还是会发生。如果您在我们的书籍中发现错误——无论是文字错误还是代码错误——我们将非常感激您能向我们报告。通过这样做,您不仅可以帮助其他读者避免困惑,还可以帮助我们改进后续版本的内容。如果您发现任何勘误,请访问www.packtpub.com/submit-errata
,选择您的书籍,点击“勘误提交表单”链接,并输入勘误的详细信息。一旦您的勘误得到验证,您的提交将被接受,勘误将上传到我们的网站或添加到该书籍的勘误部分的现有勘误列表中。
若要查看之前提交的勘误表,请访问www.packtpub.com/books/content/support
,并在搜索框中输入书名。所需的信息将在“勘误”部分显示。
盗版
网络上存在版权材料盗版问题,涉及所有媒体。在 Packt,我们非常重视保护我们的版权和许可证。如果您在互联网上遇到我们作品的任何非法复制品,请立即向我们提供位置地址或网站名称,以便我们采取措施。
请通过copyright@packtpub.com
与我们联系,并提供涉嫌盗版材料的链接。
我们感谢您在保护我们的作者和确保我们能为您带来有价值的内容方面的帮助。
问题
如果您在本书的任何方面遇到问题,您可以通过questions@packtpub.com
与我们联系,我们将尽力解决问题。
第一章:开始使用 QEMU 和 KVM
本章将涵盖以下主题:
-
安装和配置 QEMU
-
使用 qemu-img 管理磁盘镜像
-
使用 qemu-nbd 准备 OS 安装的镜像
-
使用 debootstrap 在镜像上安装自定义操作系统
-
调整镜像大小
-
使用预先存在的镜像
-
使用 qemu-system-* 运行虚拟机
-
启动具有 KVM 支持的 QEMU VM
-
通过 VNC 连接到运行中的实例
介绍
快速模拟器(QEMU)是 QEMU/KVM 虚拟化技术套件的主要组成部分。它提供硬件虚拟化和处理器仿真。QEMU 在用户空间运行,在无需内核驱动的情况下仍能提供快速系统仿真。QEMU 支持两种操作模式:
-
完全系统仿真,其中 QEMU 仿真整个计算机系统,包括 CPU 类型和外设
-
用户模式仿真,其中 QEMU 可以在不同 CPU 架构上本地运行已编译的进程
在本书中,我们将专注于使用由 基于内核的虚拟机(KVM)虚拟化程序提供的硬件加速支持进行完整系统仿真。
在本章中,我们将从在 Linux 上安装 QEMU 开始,然后探讨构建、管理和使用虚拟实例的磁盘镜像的各种示例。接着,我们将深入了解如何在完整系统仿真模式下运行 QEMU,使用提供的二进制文件。我们将看到使用 KVM 内核模块加速 QEMU 进程的示例。最后,我们将详细介绍如何使用 VNC 客户端连接我们之前启动的虚拟机。
安装和配置 QEMU
在本教程中,我们将介绍如何使用提供的发行版软件包在单个服务器上安装 QEMU。对于生产环境,我们建议使用预编译的、打包的 QEMU 版本,以便更轻松和一致地部署。但是,我们将看到一个示例,展示如何从源代码编译 QEMU,以备后续可能需要打包的特定版本。
准备就绪
根据你的 Linux 发行版,软件包名称和安装命令会有所不同。你可以使用系统的软件包管理器,如 apt
、dnf
或 yum
,搜索包含 QEMU 字符串的软件包,并了解适用于你特定 Linux 变种的可用选项。可以从官方 QEMU 项目网站下载源代码:www.qemu-project.org/download/#source
。
如何做到...
执行以下步骤,在 Ubuntu/Debian 和 RHEL/CentOS 发行版上从软件包安装 QEMU:
- 在 Ubuntu/Debian 发行版中,更新你的软件包索引:
root@kvm:~# apt-get update
- 安装软件包:
root@kvm:~# apt-get install -y qemu
- 在 CentOS/RHEL 发行版上执行:
root@kvm:~# yum install qemu-kvm
要从源代码安装,请执行以下操作:
- 首先下载存档:
root@kvm:~#cd /usr/src && wget
http://download.qemu-project.org/qemu-2.8.0.tar.xz
- 从存档中提取文件:
root@kvm:/usr/src# tar xvJf qemu-2.8.0.tar.xz && cd qemu- 2.8.0
- 配置和编译源代码:
root@kvm:/usr/src/qemu-2.8.0# ./configure
root@kvm:/usr/src/qemu-2.8.0# make && make install
工作原理...
安装 QEMU 非常简单,正如我们刚才看到的。让我们看一下 Ubuntu 上安装的 QEMU 元包是什么样的:
root@kvm:~# dpkg --list | grep qemu
ii ipxe-qemu 1.0.0+git-20150424.a25a16d-1ubuntu1 all PXE boot firmware - ROM images for qemu
ii qemu 1:2.5+dfsg-5ubuntu10.8 amd64 fast processor emulator
ii qemu-block-extra:amd64 1:2.5+dfsg-5ubuntu10.8 amd64 extra block backend modules for qemu-system and qemu-utils
ii qemu-slof 20151103+dfsg-1ubuntu1 all Slimline Open Firmware -- QEMU PowerPC version
ii qemu-system 1:2.5+dfsg-5ubuntu10.8 amd64 QEMU full system emulation binaries
ii qemu-system-arm 1:2.5+dfsg-5ubuntu10.8 amd64 QEMU full system emulation binaries (arm)
ii qemu-system-common 1:2.5+dfsg-5ubuntu10.8 amd64 QEMU full system emulation binaries (common files)
ii qemu-system-mips 1:2.5+dfsg-5ubuntu10.8 amd64 QEMU full system emulation binaries (mips)
ii qemu-system-misc 1:2.5+dfsg-5ubuntu10.8 amd64 QEMU full system emulation binaries (miscelaneous)
ii qemu-system-ppc 1:2.5+dfsg-5ubuntu10.8 amd64 QEMU full system emulation binaries (ppc)
ii qemu-system-sparc 1:2.5+dfsg-5ubuntu10.8 amd64 QEMU full system emulation binaries (sparc)
ii qemu-system-x86 1:2.5+dfsg-5ubuntu10.8 amd64 QEMU full system emulation binaries (x86)
ii qemu-user 1:2.5+dfsg-5ubuntu10.8 amd64 QEMU user mode emulation binaries
ii qemu-user-binfmt 1:2.5+dfsg-5ubuntu10.8 amd64 QEMU user mode binfmt registration for qemu-user
ii qemu-utils 1:2.5+dfsg-5ubuntu10.8 amd64 QEMU utilities
root@kvm:~#
从前面的输出中,我们可以看到涉及的包并不多。如果你感兴趣,可以阅读每个包的描述,了解每个包提供的功能。
值得一提的是,所有之前提到的包提供的二进制文件都以 QEMU 为前缀。你可以使用 Tab 补全来查看可用的可执行文件列表:
root@kvm:~# qemu-
qemu-aarch64 qemu-io qemu-mips64el qemu-ppc64 qemu-sparc32plus qemu-system-lm32 qemu-system-mipsel qemu-system-sh4 qemu-system-xtensa
qemu-alpha qemu-m68k qemu-mipsel qemu-ppc64abi32 qemu-sparc64 qemu-system-m68k qemu-system-moxie qemu-system-sh4eb qemu-system-xtensaeb
qemu-arm qemu-make-debian-root qemu-mipsn32 qemu-ppc64le qemu-system-aarch64 qemu-system-microblaze qemu-system-or32 qemu-system-sparc qemu-tilegx
qemu-armeb qemu-microblaze qemu-mipsn32el qemu-s390x qemu-system-alpha qemu-system-microblazeel qemu-system-ppc qemu-system-sparc64 qemu-unicore32
qemu-cris qemu-microblazeel qemu-nbd qemu-sh4 qemu-system-arm qemu-system-mips qemu-system-ppc64 qemu-system-tricore qemu-x86_64
qemu-i386 qemu-mips qemu-or32 qemu-sh4eb qemu-system-cris qemu-system-mips64 qemu-system-ppc64le qemu-system-unicore32
qemu-img qemu-mips64 qemu-ppc qemu-sparc qemu-system-i386 qemu-system-mips64el qemu-system-ppcemb qemu-system-x86_64
root@kvm:~#
我们可以看到,每种 CPU 架构类型都有一个可以仿真的单独可执行文件。
使用 qemu-img 管理磁盘镜像
要运行虚拟机,QEMU 需要镜像来存储客机操作系统的文件系统。镜像本身是一种文件类型,表示虚拟磁盘上的客机文件系统。QEMU 支持多种镜像,并提供了创建和管理这些镜像的工具。在本节中,我们将使用 qemu-img
工具创建一个空白的磁盘镜像。
准备工作
要使用此方法,我们需要安装 qemu-img
工具。如果你按照第一步的指示操作,应该已经安装好了。要检查你的 Linux 发行版支持哪些镜像类型,可以运行以下命令:
root@kvm:~# qemu-img -h | grep Supported
Supported formats: bochs vvfat rbd vpc parallels tftp ftp ftps raw https qcow dmg http qcow2 quorum null-aio cloop vdi iscsi null-co vhdx blkverify file vmdk host_cdrom blkdebug host_device sheepdog qed nbd
root@kvm:~#
从前面的输出中,我们可以看到,在我们使用的测试系统中,有许多受支持的镜像。确保你的 QEMU 版本支持 raw 镜像格式,因为它是默认格式,我们将在本节中使用它。最常用的镜像格式之一是 qcow2
,它支持写时复制、压缩、加密和快照功能。我们将在后续的教程中使用这个格式。
请注意,尽管 QEMU 支持多种格式,但这并不意味着你可以在它们上运行虚拟机。然而,qemu-img
可以用于将不同的镜像转换为 raw 和 qcow2
格式。为了获得最佳性能,建议使用 raw 或 qcow2
镜像格式。
如何操作...
执行以下步骤以创建一个指定大小的空白 raw 镜像,并验证该文件是否已在主机上创建:
- 创建一个名为
debian.img
、大小为 10 GB 的 raw 镜像:
root@kvm:~# qemu-img create -f raw debian.img 10G
Formatting 'debian.img', fmt=raw size=10737418240
root@kvm:~#
- 检查文件是否已创建:
root@kvm:~# ls -lah debian.img
-rw-r--r-- 1 root root 10G Feb 10 16:58 debian.img
root@kvm:~#
- 检查文件类型:
root@kvm:~# file -s debian.img
debian.img: data
root@kvm:~#
- 获取有关镜像的更多信息:
root@kvm:~# qemu-img info debian.img
image: debian.img
file format: raw
virtual size: 10G (10737418240 bytes)
disk size: 0
root@kvm:~#
工作原理...
qemu-img
工具允许我们创建、转换和修改客机镜像。
在第 1 步中,我们使用了 -f
标志来指定镜像格式;在本例中是 raw
,以及要创建的镜像名称和大小(以 GB 为单位)。
在第 4 步中,我们使用了 info
子命令来获取有关现有镜像的更多信息。注意磁盘大小目前显示为零,这是因为这是一个空白镜像,没有包含文件系统。我们将在下一节中创建文件系统。
还有更多内容...
在本节中,我们列出了 QEMU 支持的磁盘镜像格式。以下是你可能会遇到的几种常见格式的简要描述:
-
raw
:原始磁盘映像格式。这是默认格式,且通常是最快的基于文件的格式。如果你为此映像格式创建一个支持“孔”的文件系统,例如 EXT3,那么只有包含数据的扇区才会占用空间。raw 映像的主要缺点是缺乏特性,使其非常适合用于测试和快速原型开发。 -
qcow2
:正如我们在上一节提到的,这是功能最丰富的格式之一。它支持虚拟机快照、压缩和加密,但性能略有降低。 -
qcow
:这是一个较老的 QEMU 映像格式,支持备份文件、紧凑型映像文件、加密和压缩。 -
dmg
:这是 Mac 磁盘映像格式。Mac 磁盘映像提供了安全的密码保护和压缩,通常用于分发软件,而不是运行虚拟机。 -
nbd
:网络块设备,通常用于访问远程存储设备。 -
vdi
:这种磁盘格式由 Oracle VirtualBox 软件使用,可以在各种 CPU 平台上运行虚拟机。 -
vmdk
:这是 VMware 的磁盘映像格式,其中一个虚拟硬盘可以跨多个文件。 -
vhdx
:Microsoft Hyper-V 使用这种映像格式。它提供了大容量存储、在断电时的数据损坏保护以及对大磁盘映像的读写优化。
在本书中,我们将使用raw
和qcow2
磁盘格式,因为它们提供了最佳的性能和工具集来运行和操作它们。
使用 qemu-nbd 准备操作系统安装映像
在前一个配方中,我们创建了一个空白的 raw 映像。在这个配方中,我们将对其进行分区并创建文件系统,为完整的客户操作系统安装做准备。在创建分区和文件系统时,你应考虑虚拟实例可能产生的负载类型。如果虚拟机中运行的应用程序是 IO 密集型的,可能考虑为映像文件系统使用 XFS。对于本配方,我们将使用 EXT4,因为大多数 Linux 发行版默认支持它。
准备就绪
对于本配方,我们将使用以下工具:
-
qemu-nbd
-
sfdisk
-
nbd
内核模块 -
mkfs
大多数 Linux 发行版应该已经安装了相关工具。如果没有,请参考你所使用的发行版文档了解如何安装这些工具。
如何操作...
执行下述步骤,分区并在空白映像上创建文件系统:
- 加载
nbd
内核模块:
root@kvm:~# modprobe nbd
root@kvm:~#
- 使用
qemu-nbd
工具,将空白映像文件与/dev/nbd0
块设备关联:
root@kvm:~# qemu-nbd --format=raw --connect=/dev/nbd0 debian.img
root@kvm:~#
- 在块设备上创建两个分区,一个用于交换分区,另一个作为客户操作系统的根分区:
root@kvm:~# sfdisk /dev/nbd0 << EOF
>,1024,82
>;
>EOF Checking that no-one is using this disk right now ...
OK
Disk /dev/nbd0: cannot get geometry
Disk /dev/nbd0: 1305 cylinders, 255 heads, 63 sectors/track
sfdisk: ERROR: sector 0 does not have an msdos signature
/dev/nbd0: unrecognized partition table type
Old situation:
No partitions found
New situation:
Units = cylinders of 8225280 bytes, blocks of 1024 bytes, counting from 0
Device Boot Start End #cyls #blocks Id System
/dev/nbd0p1 0+ 1023 1024- 8225279+ 82 Linux swap / Solaris
/dev/nbd0p2 1024 1304 281 2257132+ 83 Linux
/dev/nbd0p3 0 - 0 0 0 Empty
/dev/nbd0p4 0 - 0 0 0 Empty
Warning: no primary partition is marked bootable (active)
This does not matter for LILO, but the DOS MBR will not boot this disk.
Successfully wrote the new partition table
Re-reading the partition table ...
If you created or changed a DOS partition, /dev/foo7, say, then use dd(1)
to zero the first 512 bytes: dd if=/dev/zero of=/dev/foo7 bs=512 count=1
(See fdisk(8).)
root@kvm:~#
- 分区后列出可用的块设备:
root@kvm:~# ls -la /dev/nbd0*
brw-rw---- 1 root disk 43, 0 Feb 10 18:24 /dev/nbd0
brw-rw---- 1 root disk 43, 1 Feb 10 18:24 /dev/nbd0p1
brw-rw---- 1 root disk 43, 2 Feb 10 18:24 /dev/nbd0p2
root@kvm:~#
- 创建交换分区:
root@kvm:~# mkswap /dev/nbd0p1
Setting up swapspace version 1, size = 508 KiB (520192 bytes)
no label, UUID=c246fe39-1bc5-4978-967c-806264771d69
root@kvm:~#
- 在根分区上创建 EXT4 文件系统:
root@kvm:~# mkfs.ext4 /dev/nbd0p2
mke2fs 1.42.13 (17-May-2015)
Discarding device blocks: failed - Input/output error
Creating filesystem with 2620928 4k blocks and 655360 inodes
Filesystem UUID: 2ffa23de-579a-45ad-abbc-2a179de67f11
Superblock backups stored on blocks:
32768, 98304, 163840, 229376, 294912, 819200, 884736, 1605632
Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done
root@kvm:~#
它是如何工作的...
我们利用nbd
内核模块提供的功能,允许我们使用qemu-nbd
工具将原始镜像文件关联到块设备。要获取更多关于内核模块的信息,运行以下代码:
root@kvm:~# modinfo nbd
filename: /lib/modules/4.4.0-62-generic/kernel/drivers/block/nbd.ko
license: GPL
description: Network Block Device
srcversion: C67096AF2AE3C738DBE0B7E
depends:
intree: Y
vermagic: 4.4.0-62-generic SMP mod_unload modversions
parm: nbds_max:number of network block devices to initialize (default: 16) (int)
parm: max_part:number of partitions per device (default: 0) (int)
root@kvm:~#
我们可以通过运行以下命令检查在步骤 2 中创建的块设备元数据:
root@kvm:~# file -s /dev/nbd0
/dev/nbd0: x86 boot sector
root@kvm:~#
在步骤 3 中创建两个新分区后,镜像文件的类型发生了变化。让我们再次检查它:
root@kvm:~# file -s debian.img
debian.img: x86 boot sector
root@kvm:~#
我们选择使用sfdisk
工具来创建分区,但如果你更喜欢,也可以使用fdisk
工具进行交互式操作,最终结果是一样的。
现在我们有一个包含两个分区和文件系统的镜像文件,我们可以继续按照下一个配方安装客户操作系统。
使用 debootstrap 在镜像上安装自定义操作系统
在这个配方中,我们将使用debootstrap
工具,在之前准备好的原始镜像上安装 Debian 发行版。debootstrap
命令用于使用特定的公共镜像引导基本的 Debian 系统。通过本配方操作完成后,我们应该得到一个包含完整 Linux 发行版的镜像,准备好在 QEMU 中运行。
准备工作
为了完成这个配方,我们需要以下工具:
-
上一配方中创建的块设备
-
debootstrap
工具 -
chroot
工具
为确保交换区和根块设备仍然存在于系统中,运行以下命令:
root@kvm:~# ls -la /dev/nbd0* brw-rw---- 1 root disk 43, 0 Feb 10 18:24 /dev/nbd0
brw-rw---- 1 root disk 43, 1 Feb 10 18:24 /dev/nbd0p1
brw-rw---- 1 root disk 43, 2 Feb 10 18:24 /dev/nbd0p2
root@kvm:~#
如果情况不是这样,请参考使用 qemu-nbd 为操作系统安装准备镜像配方,了解如何将原始镜像与/deb/nbd0
块设备关联。
如果你的系统中没有安装debootstrap
工具,可以执行以下代码来安装:
root@kvm:~# apt install -y debootstrap
...
Setting up debootstrap (1.0.78+nmu1ubuntu1.2) ...
root@kvm:~#
如何执行...
按照以下步骤安装一个新的 Debian Linux 发行版到原始镜像上:
- 挂载网络块设备(NBD)上的根分区,并确保它已经成功挂载:
root@kvm:~# mount /dev/nbd0p2 /mnt/
root@kvm:~# mount | grep mnt
/dev/nbd0p2 on /mnt type ext4 (rw)
root@kvm:~#
- 从指定的公共仓库在挂载在
/mnt
的根分区上安装最新的稳定 Debian 发行版:
root@kvm:~# debootstrap --arch=amd64 --include="openssh-server vim" stable /mnt/ http://httpredir.debian.org/debian/
...
I: Base system installed successfully.
root@kvm:~#
- 通过列出挂载位置的所有文件,确保根文件系统已创建:
root@kvm:~# ls -lah /mnt/
total 100K drwxr-xr-x 22 root root 4.0K Feb 10 17:19 .
drwxr-xr-x 23 root root 4.0K Feb 10 15:29 ..
drwxr-xr-x 2 root root 4.0K Feb 10 17:19 bin
drwxr-xr-x 2 root root 4.0K Dec 28 17:42 boot
drwxr-xr-x 4 root root 4.0K Feb 10 17:18 dev
drwxr-xr-x 55 root root 4.0K Feb 10 17:19 etc
drwxr-xr-x 2 root root 4.0K Dec 28 17:42 home
drwxr-xr-x 12 root root 4.0K Feb 10 17:19 lib
drwxr-xr-x 2 root root 4.0K Feb 10 17:18 lib64
drwx------ 2 root root 16K Feb 10 17:06 lost+found
drwxr-xr-x 2 root root 4.0K Feb 10 17:18 media
drwxr-xr-x 2 root root 4.0K Feb 10 17:18 mnt
drwxr-xr-x 2 root root 4.0K Feb 10 17:18 opt
drwxr-xr-x 2 root root 4.0K Dec 28 17:42 proc
drwx------ 2 root root 4.0K Feb 10 17:18 root
drwxr-xr-x 4 root root 4.0K Feb 10 17:19 run
drwxr-xr-x 2 root root 4.0K Feb 10 17:19 sbin
drwxr-xr-x 2 root root 4.0K Feb 10 17:18 srv
drwxr-xr-x 2 root root 4.0K Apr 6 2015 sys
drwxrwxrwt 2 root root 4.0K Feb 10 17:18 tmp
drwxr-xr-x 10 root root 4.0K Feb 10 17:18 usr
drwxr-xr-x 11 root root 4.0K Feb 10 17:18 var
root@kvm:~#
- 将主机的设备目录绑定并挂载到镜像文件系统中:
root@kvm:~# mount --bind /dev/ /mnt/dev
root@kvm:~#
- 确保现在
nbd
设备已经出现在挂载位置:
root@kvm:~# ls -la /mnt/dev/ | grep nbd0
brw-rw---- 1 root disk 43, 0 Feb 10 18:24 nbd0
brw-rw---- 1 root disk 43, 1 Feb 10 18:26 nbd0p1
brw-rw---- 1 root disk 43, 2 Feb 10 18:26 nbd0p2
root@kvm:~#
- 更改目录命名空间为镜像的根文件系统,并确保操作成功:
root@kvm:~# chroot /mnt/
root@kvm:/# pwd
/
root@kvm:/#
- 在 chroot 环境中检查发行版版本:
root@kvm:/# cat /etc/debian_version
8.7
root@kvm:/#
- 在 chroot 环境中挂载
proc
和sysfs
虚拟文件系统:
root@kvm:/# mount -t proc none /proc
root@kvm:/# mount -t sysfs none /sys
root@kvm:/#
- 在仍然处于 chroot 位置时,安装 Debian 内核元包和
grub2
工具:
root@kvm:/# apt-get install -y --force-yes linux-image-amd64 grub2
如果被要求选择目标设备以供 GRUB 安装,请不要选择任何设备,直接继续。
- 在根设备上安装 GRUB:
root@kvm:/# grub-install /dev/nbd0 --force
Installing for i386-pc platform.
grub-install: warning: this msdos-style partition label has no post-MBR gap; embedding won't be possible.
grub-install: warning: Embedding is not possible. GRUB can only be installed in this setup by using blocklists. However, blocklists are UNRELIABLE and their use is discouraged..
Installation finished. No error reported.
root@kvm:/#
- 更新 GRUB 配置和
initrd
镜像:
root@kvm:/# update-grub2
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-3.16.0-4-amd64
Found initrd image: /boot/initrd.img-3.16.0-4-amd64
done
root@kvm:/#
- 更改客户操作系统的根密码:
root@kvm:/# passwd
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
root@kvm:/#
- 允许访问新客户操作系统中的伪终端:
root@kvm:/# echo "pts/0" >> /etc/securetty
root@kvm:/#
- 将
systemd
的运行级别更改为multi-user
级别:
root@kvm:/# systemctl set-default multi-user.target
Created symlink from /etc/systemd/system/default.target to /lib/systemd/system/multi-user.target.
root@kvm:/#
- 将根挂载点添加到
fstab
文件中,以便在重启时能够持久化:
root@kvm:/# echo "/dev/sda2 / ext4 defaults,discard 0 0" > /etc/fstab
- 现在我们已经完成对以下文件系统的使用,请卸载它们:
root@kvm:/# umount /proc/ /sys/ /dev/
- 退出 chroot 环境:
root@kvm:/# exit
exit
root@kvm:~#
- 在与原始镜像关联的块设备的根分区上安装 GRUB:
root@kvm:~# grub-install /dev/nbd0 --root-directory=/mnt --modules="biosdisk part_msdos" --force
Installing for i386-pc platform.
grub-install: warning: this msdos-style partition label has no post-MBR gap; embedding won't be possible.
grub-install: warning: Embedding is not possible. GRUB can only be installed in this setup by using blocklists. However, blocklists are UNRELIABLE and their use is discouraged..
Installation finished. No error reported.
root@kvm:~#
- 更新 GRUB 配置文件,以反映客户机镜像的正确块设备:
root@kvm:~# sed -i 's/nbd0p2/sda2/g' /mnt/boot/grub/grub.cfg
root@kvm:~#
- 卸载
nbd0
设备:
root@kvm:~# umount /mnt
root@kvm:~#
- 解除
nbd0
设备与原始镜像的关联:
root@kvm:~# qemu-nbd --disconnect /dev/nbd0
/dev/nbd0 disconnected
root@kvm:~#
它是如何工作的……
前一部分进行了很多操作,现在让我们一步步回顾命令并详细说明到底做了什么以及为什么这样做。
在第 1 步中,我们将之前创建的根分区挂载到 /dev/nbd0p2
设备的 /mnt
目录,以便使用它。挂载后,在第 2 步中,我们使用挂载点作为目标,在该设备上安装了整个 Debian 发行版。
为了在镜像的根分区上安装 GRUB 启动加载器,在第 4 步中我们将主机文件系统的 /dev
目录绑定并挂载到镜像文件系统的 /mnt
目录下。
然后在第 6 步中,我们使用 chroot 工具将我们的目录命名空间更改为 /mnt
,以便执行操作,因为我们已经直接进入新操作系统。
在第 8 步中,我们在镜像内挂载了 proc
和 sysfs
虚拟文件系统,因为 GRUB 启动加载器工具需要它们。
在第 9 步中,我们安装了内核源代码和 GRUB 工具,为在启动分区上安装启动加载器做准备,并在第 10 步中安装了启动加载器。
在第 11 步中,生成了 GRUB 配置文件,并更新了启动 RAM 磁盘镜像。
在第 12、13 和 14 步中,我们更改了根密码,并确保可以访问伪终端,以便稍后登录虚拟机并将运行级别从默认的图形界面更改为多用户模式。
由于在安装 Debian 操作系统后,fstab
文件为空,我们必须添加根挂载点,否则虚拟机将无法启动。这项工作在第 15 步完成。
在第 16 和 17 步中,我们进行了清理,卸载了之前挂载的文件系统,并退出了 chroot 环境。
在第 18 步回到主机文件系统时,我们通过指定镜像的挂载位置,在 nbd0
设备上安装了 GRUB。
在第 19 步中,我们将 GRUB 配置设备名称更新为 sda2
,因为一旦启动虚拟机,这个名称将出现在虚拟机内。nbd0p2
名称仅在主机操作系统上原始镜像与网络块设备关联时存在。从虚拟机的角度来看,我们创建的镜像中的第二个分区默认命名为 sda2
。
最后,在第 20 和第 21 步中,我们通过删除挂载点并解除原始镜像与网络块设备 nbd0
的关联来进行了一些清理。
调整镜像大小
在本教程中,我们将展示如何调整现有原始镜像、其上的分区以及分区上方的文件系统的大小。我们将使用在之前教程中创建的原始镜像,其中包含一个交换分区和一个使用 EXT4 文件系统格式化的根分区。
准备就绪
对于本教程,我们将使用以下工具:
-
qemu-img
-
losetup
-
tune2fs
-
e2fsck
-
kpartx
-
fdisk
-
resize2fs
除了kpartx
外,大多数工具应该已经在 Ubuntu 上安装。若要安装它,请运行以下命令:
root@kvm:~# apt install kpartx
如何操作……
接下来的步骤演示了如何为我们之前创建的原始镜像添加额外的空间,扩展根分区并调整文件系统大小。通过本教程,原始镜像的文件系统大小应该从10G
变更为20G
。
- 获取镜像的当前大小:
root@kvm:~# qemu-img info debian.img
image: debian.img
file format: raw
virtual size: 10G (10737418240 bytes)
disk size: 848M
root@kvm:~#
- 向镜像中添加额外的 10 GB:
root@kvm:~# qemu-img resize -f raw debian.img +10GB
Image resized.
root@kvm:~#
请注意,并非所有的镜像类型都支持调整大小。为了调整此类镜像的大小,您需要先使用qemu-img
转换命令将其转换为原始镜像。
- 检查镜像的新大小:
root@kvm:~# qemu-img info debian.img
image: debian.img
file format: raw
virtual size: 20G (21474836480 bytes)
disk size: 848M
root@kvm:~#
- 打印第一个未使用的循环设备的名称:
root@kvm:~# losetup -f
/dev/loop0
root@kvm:~#
- 将第一个未使用的循环设备与原始镜像文件关联:
root@kvm:~# losetup /dev/loop1 debian.img
root@kvm:~#
- 从关联的循环设备读取分区信息,并创建设备映射:
root@kvm:~# kpartx -av /dev/loop1
add map loop1p1 (252:0): 0 1024 linear 7:1 2048
add map loop1p2 (252:1): 0 20967424 linear 7:1 4096
root@kvm:~#
- 检查新的设备映射,表示原始镜像中的分区:
root@kvm:~# ls -la /dev/mapper
total 0
drwxr-xr-x 2 root root 100 Mar 9 19:10 .
drwxr-xr-x 20 root root 4760 Mar 9 19:10 ..
crw------- 1 root root 10, 236 Feb 10 23:25 control
lrwxrwxrwx 1 root root 7 Mar 9 19:10 loop1p1
lrwxrwxrwx 1 root root 7 Mar 9 19:10 loop1p2
root@kvm:~#
- 获取根分区映射的一些信息:
root@kvm:~# tune2fs -l /dev/mapper/loop1p2
tune2fs 1.42.13 (17-May-2015)
Filesystem volume name: <none>
Last mounted on: /
Filesystem UUID: 96a73752-489a-435c-8aa0-8c5d1aba3e5f
Filesystem magic number: 0xEF53
Filesystem revision #: 1 (dynamic)
Filesystem features: has_journal ext_attr resize_inode dir_index filetype needs_recovery extent flex_bg sparse_super large_file huge_file uninit_bg dir_nlink extra_isize Filesystem flags: signed_directory_hash
Default mount options: user_xattr acl
Filesystem state: clean
Errors behavior: Continue
Filesystem OS type: Linux
Inode count: 655360
Block count: 2620928
Reserved block count: 131046
Free blocks: 2362078
Free inodes: 634148
First block: 0
Block size: 4096
Fragment size: 4096
Reserved GDT blocks: 639
Blocks per group: 32768
Fragments per group: 32768
Inodes per group: 8192
Inode blocks per group: 512
Flex block group size: 16
Filesystem created: Fri Feb 10 23:29:01 2017
Last mount time: Thu Mar 9 19:09:25 2017
Last write time: Thu Mar 9 19:08:23 2017
Mount count: 12
Maximum mount count: -1
Last checked: Fri Feb 10 23:29:01 2017
Check interval: 0 (<none>)
Lifetime writes: 1621 MB
Reserved blocks uid: 0 (user root)
Reserved blocks gid: 0 (group root)
First inode: 11
Inode size: 256
Required extra isize: 28
Desired extra isize: 28
Journal inode: 8
Default directory hash: half_md4
Directory Hash Seed: f101cccc-944e-4773-8644-91ebf4bd4f2d
Journal backup: inode blocks
root@kvm:~#
- 检查映射设备上根分区的文件系统:
root@kvm:~# e2fsck /dev/mapper/loop1p2
e2fsck 1.42.13 (17-May-2015)
/dev/mapper/loop1p2: recovering journal Setting free blocks count to 2362045 (was 2362078) /dev/mapper/loop1p2: clean, 21212/655360 files, 258883/2620928 blocks
root@kvm:~#
- 从根分区设备中移除日志:
root@kvm:~# tune2fs -O ^has_journal /dev/mapper/loop1p2
tune2fs 1.42.13 (17-May-2015)
root@kvm:~#
- 确保日志功能已被移除:
root@kvm:~# tune2fs -l /dev/mapper/loop1p2 | grep "features"
Filesystem features: ext_attr resize_inode dir_index filetype extent flex_bg sparse_super large_file huge_file uninit_bg dir_nlink extra_isize
root@kvm:~#
- 移除分区映射:
root@kvm:~# kpartx -dv /dev/loop1
del devmap : loop1p2
del devmap : loop1p1
root@kvm:~#
- 从镜像中分离循环设备:
root@kvm:~# losetup -d /dev/loop1
root@kvm:~#
- 将原始镜像与网络块设备关联:
root@kvm:~# qemu-nbd --format=raw --connect=/dev/nbd0 debian.img root@kvm:~#
- 使用
fdisk
列出可用的分区,然后删除根分区,重新创建它并写入更改:
root@kvm:~# fdisk /dev/nbd0
Command (m for help): p
Disk /dev/nbd0: 21.5 GB, 21474836480 bytes
255 heads, 63 sectors/track, 2610 cylinders, total 41943040 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000
Device Boot Start End Blocks Id System
/dev/nbd0p1 1 16450559 8225279+ 82 Linux swap / Solaris
/dev/nbd0p2 16450560 20964824 2257132+ 83 Linux
Command (m for help): d
Partition number (1-4): 2
Command (m for help): n
Partition type:
p primary (1 primary, 0 extended, 3 free)
e extended
Select (default p): p
Partition number (1-4, default 2): 2
First sector (16450560-41943039, default 16450560):
Using default value 16450560
Last sector, +sectors or +size{K,M,G} (16450560-41943039, default 41943039):
Using default value 41943039
Command (m for help): w
The partition table has been altered!
Calling ioctl() to re-read partition table.
Syncing disks.
root@kvm:~#
- 将第一个未使用的循环设备与原始镜像文件关联,如步骤 5 中所做:
root@kvm:~# losetup /dev/loop1 debian.img
- 从关联的循环设备读取分区信息,并创建设备映射:
root@kvm:~# kpartx -av /dev/loop1
add map loop1p1 (252:2): 0 1024 linear 7:1 2048
add map loop1p2 (252:3): 0 41938944 linear 7:1 4096
root@kvm:~#
- 分区完成后,执行文件系统检查:
root@kvm:~# e2fsck -f /dev/mapper/loop1p2
e2fsck 1.42.13 (17-May-2015)
Pass 1: Checking inodes, blocks, and sizes
Pass 2: Checking directory structure
Pass 3: Checking directory connectivity
Pass 4: Checking reference counts
Pass 5: Checking group summary information
/dev/mapper/loop1p2: 21212/655360 files (0.2% non-contiguous), 226115/2620928 blocks
root@kvm:~#
- 调整映射设备上根分区的文件系统大小:
root@kvm:~# resize2fs /dev/nbd0p2
resize2fs 1.42.13 (17-May-2015)
Resizing the filesystem on /dev/mapper/loop1p2 to 5242368 (4k) blocks.
The filesystem on /dev/mapper/loop1p2 is now 5242368 (4k) blocks long.
root@kvm:~#
- 创建文件系统日志,因为我们之前已经移除了它:
root@kvm:~# tune2fs -j /dev/mapper/loop1p2
tune2fs 1.42.13 (17-May-2015)
Creating journal inode: done
root@kvm:~#
- 移除设备映射:
root@kvm:~# kpartx -dv /dev/loop1
del devmap : loop1p2
del devmap : loop1p1
root@kvm:~# losetup -d /dev/loop1
root@kvm:~#
工作原理……
为虚拟机调整镜像大小可能会稍微复杂一些,正如我们在上一部分的所有步骤中看到的那样。当同一个镜像中有多个 Linux 分区时,事情可能会变得更复杂,尤其是当我们不使用逻辑卷管理(LVM)时。让我们逐步解释我们之前运行的所有命令,并详细说明为什么要运行这些命令,以及它们的作用。
在步骤 1 中,我们确认了镜像的当前大小为 10 GB。
在步骤 2 中,我们在镜像的末尾添加了 10 GB,并在步骤 3 中确认了新镜像的大小。
回想一下,我们之前创建的镜像包含两个分区,交换分区和根分区。我们需要一种方法来单独操作它们。特别是,我们希望将第 2 步中增加的额外空间分配给根分区。为此,我们需要将其暴露为一个块设备,以便我们可以通过标准的磁盘和文件系统工具轻松操作它。我们在第 5 步中使用 losetup
命令完成了这一操作,结果是在镜像与名为 /dev/loop1
的新块设备之间建立了映射。在第 6 步中,我们将各个分区暴露为两个新的设备映射。/dev/mapper/loop1p2
是根分区,我们希望将未使用的磁盘空间附加到该分区。
在我们能够调整 loop 设备上的分区大小之前,我们需要检查其文件系统的完整性,这就是我们在第 9 步中所做的。由于我们使用的是日志文件系统,在调整大小之前,我们需要先移除日志。我们在第 10 步中执行了这一步,并确保在第 11 步中运行 tune2fs
命令后,has_journal
属性没有显示。
现在,我们需要直接操作主块设备,而不是单独的分区。在第 12 和 13 步中,我们移除了映射,并在第 14 步中使用 qemu-nbd
命令将镜像文件与一个新的块设备关联起来。新的 /dev/nbd0
块设备现在代表了整个虚拟机的磁盘,并且是原始镜像内部的直接映射。我们可以像操作任何其他常规磁盘一样使用这个块设备,最重要的是,我们可以使用 fdisk
等工具来检查和操作其上的分区。
在第 15 步中,我们使用 fdisk
工具删除并重新创建根分区。这不会销毁任何文件系统数据,但会更改元数据,将之前增加的额外空间分配为根分区的一部分。
既然块设备的所有磁盘空间都分配给了根分区,我们需要扩展其上的文件系统。我们通过首先像之前一样重新创建各个分区映射来做到这一点,从而直接暴露根分区,以便我们再次对其进行操作。我们在步骤 16 和 17 中完成了这一步骤。
在第 18 和 19 步中,我们检查了根文件系统的完整性,然后将其调整为根分区上最大可用的磁盘空间。
最后,在步骤 20 中,我们再次移除映射。现在,镜像、镜像内的根分区以及 Linux 分区上方的 EXT4 文件系统都已调整为 20 GB。
你可以通过启动一个新的 QEMU 实例并使用该镜像来检查新的根分区大小。我们将在本章的另一个配方中详细操作。
使用现有镜像
在 使用 debootstrap 安装自定义操作系统到镜像 食谱中,我们演示了如何使用 debootstrap
命令将 Debian 安装到我们构建的镜像中。大多数 Linux 供应商提供了针对各种架构的其发行版的预构建镜像。也有可安装的镜像,供手动安装客操作系统使用。在这个食谱中,我们将演示如何获取和检查已经构建的 CentOS 和 Debian 镜像。在后续的食谱中,我们将展示如何使用这些镜像启动 QEMU/KVM 实例。
准备工作
对于本食谱,我们需要在主操作系统上安装 QEMU。有关如何安装 QEMU 的说明,请参阅本章中的 安装和配置 QEMU 食谱。我们还需要 wget
工具从上游公共仓库下载镜像。
如何操作……
要获取用于 QEMU 和 KVM 的 Debian Wheezy 镜像,请执行以下操作:
- 使用
wget
下载镜像:
root@kvm:~tmp# wget https://people.debian.org/~aurel32/qemu/amd64/debian_wheezy_amd64_standard.qcow2
--2017-03-09 22:07:20-- 2 Resolving people.debian.org (people.debian.org)... 2001:41c8:1000:21::21:30, 5.153.231.30 Connecting to people.debian.org (people.debian.org)|2001:41c8:1000:21::21:30|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 267064832 (255M) Saving to: ‘debian_wheezy_amd64_standard.qcow2’ debian_wheezy_amd64_standard.qcow2 100% . [===================================>] 254.69M 35.8MB/s in 8.3s 2017-03-09 22:07:29 (30.9 MB/s) - ‘debian_wheezy_amd64_standard.qcow2’ saved [267064832/267064832]
root@kvm:~#
- 检查镜像的类型:
root@kvm:~# qemu-img info debian_wheezy_amd64_standard.qcow2
image: debian_wheezy_amd64_standard.qcow2
file format: qcow2
virtual size: 25G (26843545600 bytes)
disk size: 261M
cluster_size: 65536
Format specific information:
compat: 1.1
lazy refcounts: false
refcount bits: 16
corrupt: false
root@kvm:~#
要下载 CentOS 镜像,请运行以下命令:
- 使用 wget 下载镜像:
root@kvm:/tmp# wget https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2 --2017-03-09 22:11:34-- https://cloud.centos.org/centos/7/images/CentOS-7-x86_64-GenericCloud.qcow2 Resolving cloud.centos.org (cloud.centos.org)... 2604:4500::2a8a, 136.243.75.209 Connecting to cloud.centos.org (cloud.centos.org)|2604:4500::2a8a|:443... connected. HTTP request sent, awaiting response... 200 OK Length: 1361182720 (1.3G) Saving to: ‘CentOS-7-x86_64-GenericCloud.qcow2’
CentOS-7-x86_64-GenericCloud.qcow2 100%[=========================================>] 1.27G 22.3MB/s in 54s
2017-03-09 22:12:29 (24.0 MB/s) - ‘CentOS-7-x86_64-GenericCloud.qcow2’ saved [1361182720/1361182720]
FINISHED --2017-03-09 22:12:29-- Total wall clock time: 54s Downloaded: 1 files, 1.3G in 54s (24.0 MB/s)
root@kvm:/tmp#
- 检查镜像的类型:
root@kvm:~# qemu-img info CentOS-7-x86_64-GenericCloud.qcow2
image: CentOS-7-x86_64-GenericCloud.qcow2
file format: qcow2
virtual size: 8.0G (8589934592 bytes)
disk size: 1.3G
cluster_size: 65536
Format specific information:
compat: 0.10
refcount bits: 16
root@kvm:~#
它是如何工作的……
互联网上有许多公共仓库提供各种类型的镜像,最常见的是 qcow2
格式,供 QEMU/KVM 使用。在前面的章节中,我们使用了官方的 CentOS 仓库来获取镜像,并且还有一个包含已构建 Debian 镜像的仓库。
两个镜像都采用 qcow2
格式,正如我们在步骤 2 中确认的那样。
还有更多……
到目前为止,我们只了解了如何构建、检查、操作和下载镜像。在接下来的食谱中,我们将重点介绍如何实际使用这些镜像来启动 QEMU/KVM 实例。
另请参阅
查看以下链接以获取有关可用的预构建镜像的更多信息:
-
官方 Ubuntu 镜像:
uec-images.ubuntu.com/releases/
-
官方 CentOS 镜像:
cloud.centos.org/centos/
-
官方 Debian 镜像:
cdimage.debian.org/cdimage/openstack/
-
官方 Fedora 镜像:
alt.fedoraproject.org/cloud/
-
官方 openSUSE 镜像:
download.opensuse.org/repositories/Cloud:/Images:/
使用 qemu-system-* 运行虚拟机
在这个食谱中,我们将演示如何使用 QEMU 启动虚拟机。QEMU 提供的二进制文件可以通过使用定制的或预构建的客操作系统镜像来模拟不同的 CPU 架构。
如果您完成了 安装和配置 QEMU 食谱,您应该已经拥有包含以下二进制文件的主机:
root@kvm:~# ls -la /usr/bin/qemu-system-*
-rwxr-xr-x 1 root root 8868848 Jan 25 12:49 /usr/bin/qemu-system-aarch64
-rwxr-xr-x 1 root root 7020544 Jan 25 12:49 /usr/bin/qemu-system-alpha
-rwxr-xr-x 1 root root 8700784 Jan 25 12:49 /usr/bin/qemu-system-arm
-rwxr-xr-x 1 root root 3671488 Jan 25 12:49 /usr/bin/qemu-system-cris
-rwxr-xr-x 1 root root 8363680 Jan 25 12:49 /usr/bin/qemu-system-i386
-rwxr-xr-x 1 root root 3636640 Jan 25 12:49 /usr/bin/qemu-system-lm32
-rwxr-xr-x 1 root root 6982528 Jan 25 12:49 /usr/bin/qemu-system-m68k
-rwxr-xr-x 1 root root 3652224 Jan 25 12:49 /usr/bin/qemu-system-microblaze
-rwxr-xr-x 1 root root 3652224 Jan 25 12:49 /usr/bin/qemu-system-microblazeel
-rwxr-xr-x 1 root root 8132992 Jan 25 12:49 /usr/bin/qemu-system-mips
-rwxr-xr-x 1 root root 8356672 Jan 25 12:49 /usr/bin/qemu-system-mips64
-rwxr-xr-x 1 root root 8374336 Jan 25 12:49 /usr/bin/qemu-system-mips64el
-rwxr-xr-x 1 root root 8128896 Jan 25 12:49 /usr/bin/qemu-system-mipsel
-rwxr-xr-x 1 root root 3578592 Jan 25 12:49 /usr/bin/qemu-system-moxie
-rwxr-xr-x 1 root root 3570848 Jan 25 12:49 /usr/bin/qemu-system-or32
-rwxr-xr-x 1 root root 8701760 Jan 25 12:49 /usr/bin/qemu-system-ppc
-rwxr-xr-x 1 root root 9048000 Jan 25 12:49 /usr/bin/qemu-system-ppc64
lrwxrwxrwx 1 root root 17 Jan 25 12:49 /usr/bin/qemu-system-ppc64le -> qemu-system-ppc64
-rwxr-xr-x 1 root root 8463680 Jan 25 12:49 /usr/bin/qemu-system-ppcemb
-rwxr-xr-x 1 root root 6894528 Jan 25 12:49 /usr/bin/qemu-system-sh4
-rwxr-xr-x 1 root root 6898624 Jan 25 12:49 /usr/bin/qemu-system-sh4eb
-rwxr-xr-x 1 root root 4032000 Jan 25 12:49 /usr/bin/qemu-system-sparc
-rwxr-xr-x 1 root root 7201696 Jan 25 12:49 /usr/bin/qemu-system-sparc64
-rwxr-xr-x 1 root root 3704704 Jan 25 12:49 /usr/bin/qemu-system-tricore
-rwxr-xr-x 1 root root 3554912 Jan 25 12:49 /usr/bin/qemu-system-unicore32
-rwxr-xr-x 1 root root 8418656 Jan 25 12:49 /usr/bin/qemu-system-x86_64
-rwxr-xr-x 1 root root 3653024 Jan 25 12:49 /usr/bin/qemu-system-xtensa
-rwxr-xr-x 1 root root 3642752 Jan 25 12:49 /usr/bin/qemu-system-xtensaeb
root@kvm:~#
每个命令可以启动一个特定 CPU 架构的 QEMU 仿真实例。在本食谱中,我们将使用 qemu-system-x86_64
工具。
准备工作
要完成此食谱,您将需要以下内容:
-
QEMU 二进制文件,根据安装和配置 QEMU的步骤提供。
-
我们在使用 debootstrap 在镜像上安装自定义操作系统的步骤中构建的自定义原始 Debian 镜像。
-
我们在使用现有镜像的步骤中下载的 CentOS
qcow2
镜像。
让我们来看看 QEMU 在主机系统上支持的 CPU 架构:
root@kvm:~# qemu-system-x86_64 --cpu help
x86 qemu64 QEMU Virtual CPU version 2.5+
x86 phenom AMD Phenom(tm) 9550 Quad-Core Processor
x86 core2duo Intel(R) Core(TM)2 Duo CPU T7700 @ 2.40GHz
x86 kvm64 Common KVM processor
x86 qemu32 QEMU Virtual CPU version 2.5+
x86 kvm32 Common 32-bit KVM processor
x86 coreduo Genuine Intel(R) CPU T2600 @ 2.16GHz
x86 486
x86 pentium
x86 pentium2
x86 pentium3
x86 athlon QEMU Virtual CPU version 2.5+
x86 n270 Intel(R) Atom(TM) CPU N270 @ 1.60GHz
x86 Conroe Intel Celeron_4x0 (Conroe/Merom Class Core 2)
x86 Penryn Intel Core 2 Duo P9xxx (Penryn Class Core 2)
x86 Nehalem Intel Core i7 9xx (Nehalem Class Core i7)
x86 Westmere Westmere E56xx/L56xx/X56xx (Nehalem-C)
x86 SandyBridge Intel Xeon E312xx (Sandy Bridge)
x86 IvyBridge Intel Xeon E3-12xx v2 (Ivy Bridge)
x86 Haswell-noTSX Intel Core Processor (Haswell, no TSX)
x86 Haswell Intel Core Processor (Haswell)
x86 Broadwell-noTSX Intel Core Processor (Broadwell, no TSX)
x86 Broadwell Intel Core Processor (Broadwell)
x86 Opteron_G1 AMD Opteron 240 (Gen 1 Class Opteron)
x86 Opteron_G2 AMD Opteron 22xx (Gen 2 Class Opteron)
x86 Opteron_G3 AMD Opteron 23xx (Gen 3 Class Opteron)
x86 Opteron_G4 AMD Opteron 62xx class CPU
x86 Opteron_G5 AMD Opteron 63xx class CPU
x86 host KVM processor with all supported host features (only available in KVM mode)
Recognized CPUID flags:
fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 pn clflush ds acpi mmx fxsr sse sse2 ss ht tm ia64 pbe
pni|sse3 pclmulqdq|pclmuldq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 cid fma cx16 xtpr pdcm pcid dca sse4.1|sse4_1 sse4.2|sse4_2 x2apic movbe popcnt tsc-deadline aes xsave osxsave avx f16c rdrand hypervisor
fsgsbase tsc_adjust bmi1 hle avx2 smep bmi2 erms invpcid rtm mpx avx512f rdseed adx smap pcommit clflushopt clwb avx512pf avx512er avx512cd
syscall nx|xd mmxext fxsr_opt|ffxsr pdpe1gb rdtscp lm|i64 3dnowext 3dnow
lahf_lm cmp_legacy svm extapic cr8legacy abm sse4a misalignsse 3dnowprefetch osvw ibs xop skinit wdt lwp fma4 tce nodeid_msr tbm topoext perfctr_core perfctr_nb
invtsc
xstore xstore-en xcrypt xcrypt-en ace2 ace2-en phe phe-en pmm pmm-en
kvmclock kvm_nopiodelay kvm_mmu kvmclock kvm_asyncpf kvm_steal_time kvm_pv_eoi kvm_pv_unhalt kvmclock-stable-bit
npt lbrv svm_lock nrip_save tsc_scale vmcb_clean flushbyasid decodeassists pause_filter pfthreshold
xsaveopt xsavec xgetbv1 xsaves
arat
root@kvm:~#
从前面的输出中,我们可以看到可以作为参数传递给 -cpu
标志以在我们的虚拟机内模拟该 CPU 类型的 CPU 列表。
如何操作...
要使用 qemu-system
实用程序启动新的虚拟机,请执行以下步骤:
- 使用
x86_64
CPU 架构启动新的 QEMU 虚拟机:
root@kvm:~# qemu-system-x86_64 -name debian -vnc 146.20.141.254:0 -cpu Nehalem -m 1024 -drive format=raw,index=2,file=debian.img -daemonize
root@kvm:~#
- 确保实例正在运行:
root@kvm:~# pgrep -lfa qemu
3527 qemu-system-x86_64 -name debian -vnc 146.20.141.254:0 -m 1024 -drive format=raw,index=2,file=debian.img -daemonize
root@kvm:~#
- 终止 Debian QEMU 实例:
root@kvm:~# pkill qemu
root@kvm:~#
- 使用预构建的 CentOS 镜像启动新的 QEMU 实例:
root@kvm:~# qemu-system-x86_64 -vnc 146.20.141.254:0 -m 1024 -hda CentOS-7-x86_64-GenericCloud.qcow2 -daemonize
root@kvm:~#
- 确保实例正在运行:
root@kvm:~# pgrep -lfa qemu
3546 qemu-system-x86_64 -vnc 146.20.141.254:0 -m 1024 -hda CentOS-7-x86_64-GenericCloud.qcow2 -daemonize
root@kvm:~#
- 终止 CentOS QEMU 实例:
root@kvm:~# pkill qemu
root@kvm:~#
确保将 -vnc
参数中的 IP 地址替换为主机机器的 IP 地址。
工作原理...
如何启动带有 QEMU/KVM 的虚拟机取决于镜像类型以及镜像内部分区的结构。
我们使用了两种不同的镜像类型和不同的分区方案来演示这个概念。
在第 1 步中,我们使用 qemu-system-x86_64
命令模拟了 x86_64
CPU 架构,具体地,我们传递了 -cpu Nehalem
标志,模拟 Nehalem CPU 模型。我们将主机的 IP 地址作为参数传递给 -vnc
标志。这启动了 VM 中的 VNC 服务器,以便稍后使用 VNC 客户端连接到 QEMU 实例。我们使用 -m
标志指定要分配给实例的内存量,此处为 1GB。我们告知 QEMU 我们将使用带有 format=raw
选项的原始镜像,并使用 file=debian.img
参数指定实际镜像的名称和位置。
请记住,这个原始镜像包含两个分区,第二个分区包含引导加载程序所在的根文件系统。这点非常重要,因为我们需要指定客户操作系统应从哪个分区索引加载。我们使用 index=2
标志来实现这一点。最后,我们使用 -daemonize
参数将 QEMU 进程放到后台。
在第 4 步中,我们启动了另一个 QEMU 实例,这次使用了之前下载的 qcow2
CentOS 镜像。这次我们不需要指定从哪个分区引导,因为大多数预构建的镜像使用第一个分区,或者只有一个分区。我们还使用了 -hda
标志而不是 -drive
参数,只是为了演示这两个选项可以产生相同的结果。-hda
标志告诉 QEMU 实例的第一个磁盘应该从其后跟的文件名加载。
使用 KVM 支持启动 QEMU VM
在本食谱中,我们将启动一个带有 KVM 加速的 QEMU 虚拟机。基于内核的虚拟化技术(KVM)是针对支持虚拟化扩展的 CPU 架构的完全虚拟化技术。对于英特尔处理器,这是 Intel VT,对于 AMD CPU,这是 AMD-V 硬件扩展。KVM 的主要部分包括两个可加载的内核模块,一个是kvm.ko
,提供主要的虚拟化功能,另一个是处理器特定的内核模块,分别是kvm-intel.ko
和kvm-amd.ko
,针对主要的 CPU 供应商。
QEMU 是用于创建虚拟机的用户空间组件,而 KVM 则位于内核空间。如果您完成了使用 qemu-system 运行虚拟机食谱,您可能会注意到,运行 KVM 虚拟机与运行非加速的 QEMU 实例之间的区别,仅仅是一个命令行选项。
准备工作
要启动 KVM 实例,您需要以下内容:
-
在执行安装和配置 QEMU食谱之后提供的 QEMU 二进制文件
-
我们在使用 debootstrap 安装自定义操作系统食谱中构建的自定义 Debian 镜像
-
支持虚拟化的处理器
-
KVM 内核模块
要检查您的 CPU 是否支持虚拟化,请运行以下代码:
root@kvm:~# cat /proc/cpuinfo | egrep "vmx|svm" | uniq
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm epb tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid cqm xsaveopt cqm_llc cqm_occup_llc dtherm arat pln pts
root@kvm:~#
vmx
(对于 Intel)或svm
(对于 AMD)标志的存在表明您的 CPU 支持虚拟化扩展。
cpuinfo
命令输出中的标志仅仅表示您的处理器支持虚拟化;然而,确保该功能在系统的 BIOS 中启用,否则 KVM 实例将无法启动。
要手动加载 KVM 内核模块并确保其已加载,请运行以下代码:
root@kvm:~# modprobe kvm
root@kvm:~# lsmod | grep kvm
kvm 455843 0
root@kvm:~#
如何操作...
要启动一个 KVM 实例,请确保它正在运行,最后终止它,请执行以下操作:
- 启动一个带有 KVM 支持的 QEMU 实例:
root@kvm:~# qemu-system-x86_64 -name debian -vnc 146.20.141.254:0 -m 1024 -drive format=raw,index=2,file=debian.img -enable-kvm -daemonize
root@kvm:~#
- 确保实例正在运行:
root@kvm:~# pgrep -lfa qemu
4895 qemu-system-x86_64 -name debian -vnc 146.20.141.254:0 -m 1024 -drive format=raw,index=2,file=debian.img -enable-kvm -daemonize
root@kvm:~#
- 终止实例:
root@kvm:~# pkill qemu
root@kvm:~#
它是如何工作的...
要启动 QEMU/KVM 虚拟机,我们与在安装和配置 QEMU食谱中执行的操作的不同之处,仅仅是传递-enable-kvm
标志给qemu-system-x86_64
命令。
在步骤 1 中,我们使用-name
标志为虚拟机指定了一个名称,使用-vnc
标志提供了物理主机的 IP 地址,从而启用了虚拟实例的 VNC 访问,使用-m
标志分配了 1 GB 内存,使用index=2
参数指定了启动加载程序所在的分区、镜像格式和名称,最后使用-enable-kvm
参数启用了 KVM 硬件加速,并使用-daemonize
标志将进程转为后台运行。
在步骤 2 中,我们确保实例正在运行,并在步骤 3 中将其终止。
还有更多...
作为直接运行 qemu-system
-* 命令的替代方案,在 Ubuntu 系统上有一个 qemu-kvm
包,它提供了 /usr/bin/kvm
二进制文件。这个文件是 qemu-system-x86_64
命令的封装,它会自动传递 -enable-kvm
参数。
要安装该软件包并使用 kvm
命令,请运行以下命令:
root@kvm:~# apt install qemu-kvm
...
root@kvm:~# kvm -name debian -vnc 146.20.141.254:0 -cpu Nehalem -m 1024 -drive format=raw,index=2,file=debian.img -daemonize
root@kvm:~# pgrep -lfa qemu
25343 qemu-system-x86_64 -enable-kvm -name debian -vnc 146.20.141.254:0 -cpu Nehalem -m 1024 -drive format=raw,index=2,file=debian.img -daemonize
root@kvm:~#
您可能已经注意到,启动和停止 QEMU/KVM 实例是一个相对手动的过程,尤其是需要手动终止实例进程才能停止它。在第二章,使用 libvirt 管理 KVM 中,我们将带您一步步完成一系列食谱,使得管理 KVM 虚拟机的生命周期更加容易,借助 libvirt 包提供的用户空间工具。
连接到正在运行的实例并使用 VNC
在本食谱中,我们将使用 VNC 客户端连接到一个正在运行的 KVM 实例。连接后,我们将登录并检查实例的 CPU 类型和可用内存。我们已经在前面的食谱中介绍了如何启动带有 VNC 支持的 QEMU/KVM 实例,但我们将再次演示,以防您没有通读本书。
虚拟网络计算 (VNC) 使用 远程帧缓冲 (RFB) 协议来远程控制另一台系统。它将远程计算机的屏幕转发回客户端,允许完全的键盘和鼠标控制。
有许多不同的 VNC 客户端和服务器实现,但在本食谱中,我们将使用名为 chicken of the VNC 的 macOS 版本。您可以从sourceforge.net/projects/cotvnc/
下载客户端。
准备就绪
为了完成本食谱,您需要以下内容:
-
按照 安装并配置 QEMU 食谱操作后提供的 QEMU 二进制文件
-
我们在 使用 debootstrap 安装自定义操作系统到镜像 食谱中构建的自定义原始 Debian 镜像
-
一个支持虚拟化的处理器
-
加载的 KVM 内核模块
-
上一步中提到的 VNC 客户端 chicken 已经安装。
如何操作...
- 启动一个新的 KVM 加速的
qemu
实例:
root@kvm:~# qemu-system-x86_64 -name debian -vnc 146.20.141.254:0 -cpu Nehalem -m 1024 -drive format=raw,index=2,file=debian.img -daemonize
root@kvm:~#
- 确保实例正在运行:
root@kvm:~# pgrep -lfa qemu
4987 qemu-system-x86_64 -name debian -vnc 146.20.141.254:0 -cpu Nehalem -m 1024 -drive format=raw,index=2,file=debian.img -daemonize
root@kvm:~#
- 启动 VNC 客户端,并连接到步骤 1 中指定的 IP 地址和显示端口的 VNC 服务器:
VNC 登录界面
- 使用 root 用户登录到实例,然后检查如图所示的 CPU 类型和可用内存:
VNC 会话
它是如何工作的...
在步骤 1 中,我们启动了一个新的 QEMU 实例,并启用了 KVM 加速,同时在指定的 IP 地址和显示端口上启用了 VNC 服务器。我们还指定了可用的内存大小和 CPU 型号。
在步骤 4 中,我们使用 root 用户和在构建镜像时创建的密码登录了实例,然后通过运行 lscpu
命令获取了 CPU 信息。注意 CPU 模型名称与我们在启动虚拟机时使用 -cpu
标志指定的内容相匹配。接下来,我们使用 free
命令检查了分配的内存,这也与我们之前通过 -m
参数指定的内容相符。
第二章:使用 libvirt 管理 KVM
本章将涵盖以下主题:
-
安装和配置 libvirt
-
定义 KVM 实例
-
启动、停止和删除 KVM 实例
-
检查和编辑 KVM 配置
-
使用 virt-install 构建新的 KVM 实例并使用控制台
-
在 KVM 中管理 CPU 和内存资源
-
将块设备附加到虚拟机
-
在运行中的虚拟机与宿主操作系统之间共享目录
-
自动启动 KVM 实例
-
使用存储池
-
管理存储卷
-
管理机密信息
介绍
在上一章中,我们展示了使用 QEMU 工具集和 KVM 内核模块来配置虚拟机的示例。QEMU 命令适合快速启动虚拟实例;然而,它们没有提供配置和管理虚拟机生命周期的简便方法。
本章我们将使用 libvirt 工具集。Libvirt 提供了各种用户空间命令和语言绑定,用于构建、配置、启动、停止、迁移、终止虚拟机,并执行其他管理功能。它支持不同的虚拟化技术,如 QEMU/KVM、XEN 和 LXC 容器。
我们将从安装和配置 libvirt 工具开始,然后继续创建使用 libvirt 支持的 XML 配置文件的虚拟机,并探索该工具包提供的许多功能,以便管理 KVM 实例的生命周期。本章中的所有示例都将以构建高可用、多租户环境为背景。
安装和配置 libvirt
在本示例中,我们将从你选择的 Linux 发行版提供的软件包中安装 libvirt,并查看可用于配置的配置文件和选项。和其他生产环境工具一样,我们推荐在生产环境中使用软件包,以便于部署和保持一致性;然而,如果你所在 Linux 发行商的包版本较旧,也可以选择从源码编译最新版本。
准备工作
根据你的 Linux 发行版,软件包名称和安装命令会有所不同。你可以使用系统的包管理器,如 apt
、dnf
或 yum
来搜索包含 libvirt
字符串的包,并了解适用于你特定 Linux 版本的可用软件包。源代码可以从官方的 libvirt 项目网站下载:www.qemu-project.org/download/#source
。
如何操作...
从软件包和源代码安装 libvirt,请按照以下步骤操作:
- 在 Ubuntu 上,通过运行以下命令安装软件包:
root@kvm:~# apt update && apt install libvirt-binroot@kvm:~#
- 通过执行以下命令确保
libvirt
守护进程正在运行:
root@kvm:~# pgrep -lfa libvirtd 36667 /usr/sbin/libvirtd root@kvm:~#
- 检查默认配置:
root@kvm:~# cat /etc/libvirt/libvirtd.conf | grep -vi "#"
| sed '/^$/d'
unix_sock_group = "libvirtd" unix_sock_ro_perms = "0777"
unix_sock_rw_perms = "0770"
auth_unix_ro = "none"
auth_unix_rw = "none"
root@kvm:~#
- 通过编辑
qemu
配置文件禁用 QEMU 中的安全驱动:
root@kvm:~# vim /etc/libvirt/qemu.conf
...
security_driver = "none"
...
root@kvm:~#
- 重启
libvirt
守护进程:
root@kvm:~# /etc/init.d/libvirt-bin restart
libvirt-bin stop/waiting
libvirt-bin start/running, process 1158
root@kvm:~#
根据你的 Linux 发行版,libvirt
服务的名称可能会有所不同。在 RHEL/CentOS 上,服务名称是 libvirtd
;要重启它,可以运行 service libvirtd restart
。
- 检查
libvirt
目录中的所有配置文件:
root@kvm:~# ls -la /etc/libvirt/
total 76
drwxr-xr-x 5 root root 4096 Mar 22 14:27 .
drwxr-xr-x 90 root root 4096 Mar 21 23:17 ..
drwxr-xr-x 2 root root 4096 Feb 5 2016 hooks
-rw-r--r-- 1 root root 518 Feb 5 2016 libvirt.conf
-rw-r--r-- 1 root root 13527 Feb 5 2016 libvirtd.conf
-rw-r--r-- 1 root root 1176 Feb 5 2016 lxc.conf
drwxr-xr-x 2 root root 4096 Mar 21 23:16 nwfilter
drwxr-xr-x 3 root root 4096 Mar 21 23:57 qemu
-rw------- 1 root root 16953 Mar 21 23:18 qemu.conf
-rw-r--r-- 1 root root 2170 Feb 5 2016 qemu-lockd.conf
-rw-r--r-- 1 root root 2213 Feb 5 2016 virtlockd.conf
-rw-r--r-- 1 root root 1217 Feb 5 2016 virt-login-shell.conf
root@kvm:~#
它是如何工作的...
在第 1 步中,我们在 Ubuntu 上安装了该软件包。安装后,后安装脚本启动了 libvirt
守护进程。我们在第 2 步中验证了这一点。
在第 3 步中,我们检查了服务端守护进程 libvirtd
的主要配置文件。该进程在主机操作系统上运行,管理虚拟机的任务,如配置、生命周期管理、迁移、存储和网络等,我们将在本章稍后看到。我们安装的软件包提供的用户空间工具通过发送请求到本地 Unix 域套接字与守护进程进行通信。第 3 步中看到的默认选项足以满足本章中的食谱需求,但配置文件相当庞大。我们建议你仔细阅读它,并熟悉其中的其他配置选项。该文件的文档非常完善。
在第 4 步中,我们禁用了 QEMU 的安全驱动程序。默认情况下,在 RHEL/CentOS 系统上,QEMU 被配置为使用 SELinux。Ubuntu 发行版使用 AppArmor。为了简化,我们在此步骤中禁用了该功能;然而,在生产环境中,你应该利用强制访问控制系统(如 SELinux)提供的额外安全性。
对 libvirt
配置文件的任何更改都需要重启。我们在第 5 步中重启了 libvirt
服务。
我们需要熟悉一些重要的配置文件,这些文件在第 6 步中列出了:
-
libvirt.conf
是我们将在本食谱中使用的virsh
命令的客户端配置文件。我们可以在其中指定 URI 别名。默认设置应该已经足够。 -
libvirtd.conf
是服务端配置文件,正如我们在第 3 步中看到的。它提供了各种安全选项、请求限制和日志控制。对于本书的目的,默认设置已经足够。 -
qemu.conf
是 QEMU 驱动程序的主要配置文件,它是libvirt
使用的。我们可以配置诸如 VNC 服务器地址、在第 4 步中看到的安全驱动程序,以及 QEMU 进程的用户和组等选项。 -
一旦我们创建了 QEMU/KVM 虚拟机,
/etc/libvirt/qemu/
目录将包含该实例的 XML 配置定义,正如我们在接下来的食谱中将看到的那样。 -
最后,
/etc/libvirt/qemu/networks/
目录包含了用于网络配置的文件。我们将在本章稍后更详细地探讨这些文件。
定义 KVM 实例
在这个配方中,我们将通过创建一个简单的 XML 配置文件来定义虚拟实例,该文件可以被libvirt
用于构建虚拟机。我们将描述一些 XML 架构块,并展示如何使用virt-install
命令生成 XML 定义文件,而不是手动编写。
准备工作
对于这个配方,我们需要以下内容:
-
按照第一章“安装和配置 QEMU”配方中所述提供的 QEMU 二进制文件。
-
我们在上一章的使用 debootstrap 安装自定义操作系统配方中构建的自定义原始 Debian 镜像。
你可以使用自己的虚拟机镜像或从互联网上下载一个,正如我们在第一章“使用现有镜像”配方中展示的那样,开始使用 QEMU 和 KVM。
如何操作...
要定义一个新的 KVM 虚拟机,请运行这里概述的命令:
- 列出主机操作系统上的所有虚拟机:
root@kvm:~# virsh list --all
Id Name State
----------------------------------------------------
root@kvm:~#
- 创建以下 XML 定义文件:
root@kvm:~# cat kvm1.xml
<domain type='kvm' id='1'>
<name>kvm1</name>
<memory unit='KiB'>1048576</memory>
<vcpu placement='static'>1</vcpu>
<os>
<type arch='x86_64' machine='pc-i440fx-trusty'>hvm</type>
<boot dev='hd'/>
</os>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='raw'/>
<source file='/tmp/debian.img'/>
<target dev='hda' bus='ide'/>
<alias name='ide0-0-0'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
<interface type='network'>
<source network='default'/>
<target dev='vnet0'/>
<model type='rtl8139'/>
<alias name='net0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</interface>
<graphics type='vnc' port='5900' autoport='yes' listen='146.20.141.158'>
<listen type='address' address='146.20.141.158'/>
</graphics>
</devices>
<seclabel type='none'/>
</domain>
root@kvm:~#
- 定义虚拟机:
root@kvm:~# virsh define kvm1.xml
Domain kvm1 defined from kvm1.xml
root@kvm:~#
- 列出所有状态下的实例:
root@kvm:~# virsh list --all
Id Name State
----------------------------------------------------
- kvm1 shut off
root@kvm:~#
它是如何工作的...
在步骤 1 中,我们使用了virsh
命令并提供了所有参数,以列出所有活动和非活动的实例。正如预期的那样,我们一开始没有定义任何实例。
在步骤 2 中,我们为新的 KVM 实例创建了一个定义文件。我们使用了可用 XML 架构属性的小部分来设置以下选项:
-
XML 文件的根元素是所有虚拟机定义所必需的,命名为domain。它有两个属性——
type
和id
。我们将kvm
指定为type
,并将id
设置为1
,因为这是我们的第一台 KVM 虚拟机。所有其他属性都在 domain 根元素下定义。 -
我们通过
name
属性指定了实例的名称。 -
memory 属性定义了分配给虚拟机的可用内存,在我们的案例中是 1GB。
-
vcpu
元素定义了为客户操作系统分配的虚拟 CPU 的最大数量。我们将其指定为1
,并使用了可选属性 placement,指示 CPU 的放置模式;在本例中,使用的是静态模式。静态放置表示虚拟实例将被固定在所有可用的物理 CPU 上。 -
OS 元素通过使用 type 元素定义虚拟机的架构。
hvm
选项表示我们将使用完全虚拟化,这将是 KVM,正如我们之前看到的 domain 类型属性所指定的那样。我们通过<boot dev>
元素指定虚拟机启动的设备。 -
接下来的三个元素指定了当客户请求关闭电源、重启或发生崩溃时需要采取的操作。在我们的示例中,当客户操作系统关闭电源时,虚拟机将被销毁,当客户重启或崩溃时,虚拟机将重新启动。
-
XML 定义文件中最大的部分是设备部分,我们使用各种 XML 元素来描述提供给来宾操作系统的设备。emulator 元素指定了模拟器二进制文件的路径。我们将使用在 第一章 开始使用 QEMU 和 KVM 中使用的相同的 QEMU 模拟器二进制文件
qemu-system-x86_64
。在设备属性的最后几个部分,我们定义了我们使用的虚拟磁盘类型,在本例中是我们在前一章构建的原始镜像。以类似的方式,我们描述了来宾操作系统中应该启动的 VNC 服务器和网络接口。
有了 config
文件后,我们在第 3 步中定义了实例,使用的是我们之前在 /tmp
创建的镜像。
一旦新实例被定义,它默认不会自动启动。我们可以看到在第 4 步中,新的实例状态是 shut off
。
有关所有可用 XML 元素及其属性的信息,请参阅官方文档:libvirt.org/formatdomain.html
。
还有更多内容...
通过编写 XML 文件来配置虚拟机可能会非常繁琐且容易出错。创建虚拟机的一个更简单的方法是使用 virt-install
工具,这可以从现有镜像或安装介质(可以是物理的、虚拟的或网络位置)创建虚拟机。让我们看一个使用该工具创建相同 KVM 实例的例子。
- 我们从安装包开始:
root@kvm:~# apt install virtinst
...
root@kvm:~#
- 接下来,我们通过调用
virt-install
命令来定义并启动新的实例(如果已经存在相同名称的实例,你需要先销毁并取消定义它):
root@kvm:~# virt-install --name kvm1 --ram 1024 --disk path=/tmp/debian.img,format=raw --graphics vnc,listen=146.20.141.158 --noautoconsole --hvm --import
Starting install...
Creating domain... | 0 B 00:00
Domain creation completed. You can restart your domain by running:
virsh --connect qemu:///system start kvm1
root@kvm:~#
- 新的虚拟机现在已经定义并启动。为了确认,执行以下命令:
root@kvm:~# virsh list --all
Id Name State
----------------------------------------------------
10 kvm1 running
root@kvm:~#
- 我们可以看到通过运行以下代码自动生成的虚拟机定义文件:
root@kvm:~# cat /etc/libvirt/qemu/kvm1.xml
<!--
WARNING: THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE
OVERWRITTEN AND LOST. Changes to this xml configuration should be made using:
virsh edit kvm1
or other application using the libvirt API.
-->
<domain type='kvm'>
<name>kvm1</name>
<uuid>c3892cbf-812a-2448-7ad2-098ea8381066</uuid>
<memory unit='KiB'>1048576</memory>
<currentMemory unit='KiB'>1048576</currentMemory>
<vcpu placement='static'>1</vcpu>
<os>
<type arch='x86_64' machine='pc-i440fx-trusty'>hvm</type>
<boot dev='hd'/>
</os>
<features>
<acpi/>
<apic/>
<pae/>
</features>
<clock offset='utc'/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='raw'/>
<source file='/tmp/debian.img'/>
<target dev='hda' bus='ide'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
<controller type='usb' index='0'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
</controller>
<controller type='pci' index='0' model='pci-root'/>
<controller type='ide' index='0'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
</controller>
<interface type='network'>
<mac address='52:54:00:59:e3:4e'/>
<source network='default'/>
<model type='rtl8139'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</interface>
<serial type='pty'>
<target port='0'/>
</serial>
<console type='pty'>
<target type='serial' port='0'/>
</console>
<input type='mouse' bus='ps2'/>
<input type='keyboard' bus='ps2'/>
<graphics type='vnc' port='-1' autoport='yes' listen='146.20.141.158'>
<listen type='address' address='146.20.141.158'/>
</graphics>
<video>
<model type='cirrus' vram='9216' heads='1'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
</video>
<memballoon model='virtio'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
</memballoon>
</devices>
</domain>
root@kvm:~#
启动、停止和移除 KVM 实例
在前面的教程中,我们看到如何通过手动编写 XML 定义文件或使用 virt-install
工具来定义新的 KVM 虚拟机实例。
如果从 XML 文件定义新的实例,默认情况下实例不会自动启动。在本教程中,我们将看到如何启动之前配置的实例。
准备工作
在这个教程中,我们需要以下内容:
-
在遵循 安装和配置 QEMU 教程后提供的 QEMU 二进制文件,通过 第一章 开始使用 QEMU 和 KVM 获得。
-
我们在 使用 debootstrap 安装自定义操作系统 教程中构建的自定义原始 Debian 镜像。
-
完成 安装和配置 libvirt 教程后提供的 virsh 工具。
-
在 定义 KVM 实例 教程中定义的实例处于
shut off
状态。
如何操作...
以下步骤概述了使用virsh
命令列出、启动和停止 KVM 实例的过程:
- 列出所有状态下的实例:
root@kvm:~# virsh list --all
Id Name State
----------------------------------------------------
- kvm1 shut off
root@kvm:~#
- 启动新定义的实例并验证其状态:
root@kvm:~# virsh start kvm1
Domain kvm1 started
root@kvm:~#
root@kvm:~# virsh list --all
Id Name State
----------------------------------------------------
1 kvm1 running
root@kvm:~#
- 检查虚拟机的运行进程:
root@kvm:~# pgrep -lfa qemu
1686 /usr/bin/qemu-system-x86_64 -name kvm1 -S -machine pc-i440fx-trusty,accel=kvm,usb=off -m 1024 -realtime mlock=off -smp 1,sockets=1,cores=1,threads=1 -uuid a9dfd1a1-7dd1-098e-a926-db9526785a9e -no-user-config -nodefaults -chardev socket,id=charmonitor,path=/var/lib/libvirt/qemu/kvm1.monitor,server,nowait -mon chardev=charmonitor,id=monitor,mode=control -rtc base=utc -no-shutdown -boot strict=on -device piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2 -drive file=/tmp/debian.img,if=none,id=drive-ide0-0-0,format=raw -device ide-hd,bus=ide.0,unit=0,drive=drive-ide0-0-0,id=ide0-0-0,bootindex=1 -netdev tap,fd=24,id=hostnet0 -device rtl8139,netdev=hostnet0,id=net0,mac=52:54:00:ce:dd:f2,bus=pci.0,addr=0x3 -chardev pty,id=charserial0 -device isa-serial,chardev=charserial0,id=serial0 -vnc 146.20.141.158:0 -device cirrus-vga,id=video0,bus=pci.0,addr=0x2 -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x4
root@kvm:~#
- 终止虚拟机并确保其状态从运行中变为
关闭
:
root@kvm:~# virsh destroy kvm1
Domain kvm1 destroyed
root@kvm:~# virsh list --all
Id Name State
----------------------------------------------------
- kvm1 shut off
root@kvm:~#
- 删除实例定义:
root@kvm:~# virsh undefine kvm1
Domain kvm1 has been undefined
root@kvm:~# virsh list --all
Id Name State
----------------------------------------------------
root@kvm:~#
它是如何工作的...
在步骤 1 中,我们列出了所有定义的实例,无论其状态如何。从输出中,我们可以看到我们当前有一个在早前的教程中定义的实例。
在步骤 2 中,我们启动了虚拟机并确保其状态已变为运行。
如果你完成了第一章中的使用 qemu-system-运行虚拟机教程,你可能会注意到该虚拟机的 XML 定义与我们用于启动 QEMU 实例的所有命令行选项非常相似。我们可以看到在步骤 3 中启动新实例的相似性。主要区别在于libvirt
传递给 QEMU 可执行文件的参数更多。
最后,在步骤 4 和 5 中,我们停止了虚拟机并移除了其定义文件。然而,我们用来做虚拟机的原始镜像仍然可用,并且可以再次使用。
检查和编辑 KVM 配置
在本教程中,我们将使用virsh
工具来检查和编辑现有虚拟机的配置。如前所述,一旦定义并启动了 KVM 实例,libvirt
会在/etc/libvirt/qemu/
目录中创建 XML 定义文件。我们可以将来宾配置转储到磁盘,以便检查或备份。使用virsh
命令,我们还可以在原地执行配置更新,正如本教程稍后所示。
准备工作
本教程中,我们需要以下内容:
-
按照第一章中的安装与配置 QEMU教程提供的 QEMU 二进制文件,开始使用 QEMU 和 KVM
-
我们在上一章的使用 debootstrap 安装自定义操作系统中构建的自定义 Debian 镜像,或任何其他虚拟机镜像,格式可以是
raw
或qcow2
-
完成安装与配置 libvirt教程后提供的
virsh
工具 -
一个正在运行的
libvirt
KVM 实例
如何操作...
以下步骤概述了检查和编辑 KVM 实例的 XML 定义的过程:
- 确保你有一个运行中的 KVM 实例和 libvirt,如果没有,请按照之前教程中的步骤进行操作:
root@kvm:~# virsh list
Id Name State
----------------------------------------------------
11 kvm1 running
root@kvm:~#
- 将实例配置文件输出到标准输出(stdout)。有关 stdout 的更多信息,请参考以下链接:
en.wikipedia.org/wiki/Standard_streams
root@kvm:~# virsh dumpxml kvm1
<domain type='kvm' id='11'>
<name>kvm1</name>
<uuid>9eb9a2e9-abb2-54c5-5cb3-dc86728e70fc</uuid>
<memory unit='KiB'>1048576</memory>
<currentMemory unit='KiB'>1048576</currentMemory>
<vcpu placement='static'>1</vcpu>
<resource>
<partition>/machine</partition>
</resource>
<os>
<type arch='x86_64' machine='pc-i440fx-trusty'>hvm</type>
<boot dev='hd'/>
</os>
<features>
<acpi/>
<apic/>
<pae/>
</features>
<clock offset='utc'/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='raw'/>
<source file='/tmp/debian.img'/>
<target dev='hda' bus='ide'/>
<alias name='ide0-0-0'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
<controller type='usb' index='0'>
<alias name='usb0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
</controller>
<controller type='pci' index='0' model='pci-root'>
<alias name='pci.0'/>
</controller>
<controller type='ide' index='0'>
<alias name='ide0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
</controller>
<interface type='network'>
<mac address='52:54:00:d1:70:df'/>
<source network='default'/>
<target dev='vnet0'/>
<model type='rtl8139'/>
<alias name='net0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</interface>
<serial type='pty'>
<source path='/dev/pts/0'/>
<target port='0'/>
<alias name='serial0'/>
</serial>
<console type='pty' tty='/dev/pts/0'>
<source path='/dev/pts/0'/>
<target type='serial' port='0'/>
<alias name='serial0'/>
</console>
<input type='mouse' bus='ps2'/>
<input type='keyboard' bus='ps2'/>
<graphics type='vnc' port='5900' autoport='yes' listen='146.20.141.158'>
<listen type='address' address='146.20.141.158'/>
</graphics>
<video>
<model type='cirrus' vram='9216' heads='1'/>
<alias name='video0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
</video>
<memballoon model='virtio'>
<alias name='balloon0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
</memballoon>
</devices>
<seclabel type='none'/>
</domain>
root@kvm:~#
- 将配置保存到新文件中,如下所示:
root@kvm:~# virsh dumpxml kvm1 > kvm1.xml
root@kvm:~# head kvm1.xml
<domain type='kvm' id='11'>
<name>kvm1</name>
<uuid>9eb9a2e9-abb2-54c5-5cb3-dc86728e70fc</uuid>
<memory unit='KiB'>1048576</memory>
<currentMemory unit='KiB'>1048576</currentMemory>
<vcpu placement='static'>1</vcpu>
<resource>
<partition>/machine</partition>
</resource>
<os>
root@kvm:~#
- 在原地编辑配置并更改虚拟机的可用内存:
root@kvm:~# virsh edit kvm1
Domain kvm1 XML configuration edited.
root@kvm:~#
它是如何工作的...
Libvirt 提供了两种主要方式来操作虚拟实例的配置定义。我们可以像第 2 步和第 3 步那样从现有实例中导出配置,或者像第 4 步那样在原地编辑 XML 定义。
将当前配置保存到文件是一种方便的备份 VM 定义的方式。它还提供了一种通过编辑保存的文件并仅更改虚拟机的名称和 ID 来定义新实例的方法。然后,我们可以使用该文件在同一主机或不同主机上启动一个新的虚拟机,前提是文件系统或镜像也可用。在以后的食谱中,我们将看到使用libvirt
迁移和备份虚拟机的示例。
在原地进行更改时,如第 4 步所示,将使用默认系统$EDITOR
。进入编辑模式后,请注意 XML 文件包含有关虚拟实例当前状态的信息。<uuid>
和<currentMemory>
属性就是这样的例子。如果你想更改虚拟机的可用内存,在更新<memory>
属性后,可能需要删除<currentMemory>
段。如果编辑时出现任何问题,libvirt 将显示错误消息并提供以下选项:
root@kvm:~# virsh edit kvm1
error: XML error: current memory '1048576k' exceeds maximum '524288k'
Failed. Try again? [y,n,f,?]:n
Domain kvm1 XML configuration not changed.
root@kvm:~#
还请记住,如果你想从现有实例的转储中创建一个新实例,你需要更改<name>
并删除<uuid>
属性,因为后者会在定义新实例时自动生成。
使用 virt-install 构建新的 KVM 实例并使用控制台
在第一章《QEMU 与 KVM 入门》中的连接到运行中的实例(使用 VNC)一节,你学习了如何连接到运行 VNC 服务器的 QEMU/KVM 虚拟机。这是连接到正在安装或启动过程中的实例的一种非常好的方式,以便与其进行交互。
到目前为止,我们一直在使用之前创建的自定义原始镜像,其中包含 Debian 操作系统的安装。回顾第一章,《QEMU 与 KVM 入门》,我们使用了debootstrap
命令在镜像文件内安装操作系统。在这个食谱中,我们将使用virt-install
工具安装一个新的 Linux 发行版,使用提供的上游互联网存储库作为安装源,然后使用virsh
命令连接到运行中的实例,使用控制台。
准备就绪
对于这个食谱,我们需要以下内容:
-
virsh
命令 -
virt-install
命令 -
需要互联网连接以下载安装文件
如何操作...
要构建一个新的 KVM 实例并通过控制台连接到它,执行以下步骤:
- 使用官方 Debian 存储库安装新的 KVM 虚拟机:
root@kvm:~# virt-install --name kvm1 --ram 1024 --extra-args="text console=tty0 utf8 console=ttyS0,115200" --graphics vnc,listen=146.20.141.158 --hvm --location=http://ftp.us.debian.org/debian/dists/stable/main/installer-amd64/ --disk path=/tmp/kvm1.img,size=8
Retrieving file MANIFEST... | 3.3 kB 00:00 ...
Retrieving file linux... | 6.0 MB 00:00 ...
Retrieving file initrd.gz... | 29 MB 00:00 ...
Creating storage file kvm1.img | 8.0 GB 00:00
WARNING Unable to connect to graphical console: virt-viewer not installed. Please install the 'virt-viewer' package.
Domain installation still in progress. You can reconnect to
the console to complete the installation process.
root@kvm:~#
- 运行以下代码连接到控制台以完成安装:
root@kvm:~# virsh console kvm1
Connected to domain kvm1
Escape character is ^]
- 一旦连接到控制台,你应该看到类似于此处的屏幕:
连接后,通过 virsh 控制台命令看到的控制台输出
-
按照文本菜单提示完成安装。
-
启动新配置的虚拟机:
root@kvm:~# virsh start kvm1
Domain kvm1 started
root@kvm:~#
- 使用你喜欢的 VNC 客户端连接到实例,使用你在安装过程中第 3 步创建的用户名和密码登录,并通过运行以下命令启用串行控制台访问:
root@debian:~# systemctl enable serial-getty@ttyS0.service
root@debian:~# systemctl start serial-getty@ttyS0.service
root@debian:~#
- 关闭 VNC 会话,并通过主机操作系统使用
virsh
连接到虚拟实例:
root@kvm:~# virsh console kvm1
Connected to domain kvm1
Escape character is ^]
Debian GNU/Linux 8 debian ttyS0
debian login: root
Password:
Last login: Wed Mar 22 16:38:10 CDT 2017 on tty1
Linux debian 3.16.0-4-amd64 #1 SMP Debian 3.16.39-1+deb8u2 (2017-03-07) x86_64
The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
root@debian:~# free -m
total used free shared buffers cached
Mem: 1000 98 902 4 9 43
-/+ buffers/cache: 44 956
Swap: 382 0 382
root@debian:~#
-
使用 Ctrl + ] 键组合断开与控制台的连接。
-
检查安装后创建的
image
文件:
root@kvm:~# qemu-img info /tmp/kvm1.img
image: /tmp/kvm1.img
file format: raw
virtual size: 8.0G (8589934592 bytes)
disk size: 1.9G
root@kvm:~#
如果你没有在 KVM 机器的发行版上使用基于 systemd 的 init
系统,则为了允许访问实例的串行控制台,你需要编辑 /etc/securetty
或 /etc/inittab
文件。
它是如何工作的...
在这个配方中发生了很多事情,所以让我们更详细地回顾一下所有步骤。
在第 1 步中,我们使用 virt-install
工具启动了一个新的 KVM 实例的安装过程。我们通过 --extra-args
参数指定在安装过程中启用串行控制台。我们还使用 --location
标志告诉 libvirt
安装文件的位置,以便安装最新的 Debian 发行版。然后我们指定了将包含来宾操作系统文件系统的映像文件的位置和大小。由于该文件尚不存在,virt-install
创建了一个原始映像,如第 9 步所示。
启用了控制台访问后,我们能够在第 2 步连接到控制台,并在第 3 和第 4 步完成安装过程。
安装完成后,控制台会话终止,新 KVM 实例准备启动。我们在第 5 步启动了实例。
为了启用串行端口上的控制台访问,我们首先使用 VNC 客户端连接到正在运行的虚拟机,并在第 6 步指示 systemd 启动控制台服务。
启用控制台访问后,我们能够在第 7 步使用 virsh 工具连接到串行控制台。
完成所有这些后,我们现在有两种方法可以连接到正在运行的 KVM 实例,分别是通过 VNC 或控制台。
在接下来的配方中,我们将启用来宾操作系统的网络功能,并提供第三种使用 SSH 连接的方法。
管理 KVM 中的 CPU 和内存资源
可以通过编辑虚拟机的 XML 定义或使用 libvirt
工具集来更改分配的内存量或 CPU 数量。在这个配方中,我们将查看如何更改 KVM 实例的内存和 CPU 数量的示例。
准备工作
对于这个配方,我们将需要以下内容:
-
一个正在运行的 KVM 实例,分配了 1 GB 内存和 1 个 CPU,并启用了控制台访问
-
libvirt
包 -
一个至少有 4 GB 可用内存和最低 4 个 CPU 的来宾操作系统
如何操作...
要检查并更新分配给虚拟机的内存和 CPU 资源,请按照这里概述的过程进行:
- 获取正在运行的实例的内存统计信息:
root@kvm:~# virsh dommemstat kvm1
actual 1048576
swap_in 0
rss 333644
root@kvm:~#
- 将虚拟机的可用内存更新为 2 GB:
root@kvm:~# virsh setmem kvm1 --size 1049000
root@kvm:~#
- 停止正在运行的实例:
root@kvm:~# virsh destroy kvm1
Domain kvm1 destroyed
root@kvm:~#
- 将最大可用内存设置为 2 GB:
root@kvm:~# virsh setmaxmem kvm1 --size 2097152
root@kvm:~#
- 启动实例:
root@kvm:~# virsh start kvm1
Domain kvm1 started
root@kvm:~#
- 检查当前分配的内存:
root@kvm:~# virsh dommemstat kvm1
actual 2097152
swap_in 0
rss 214408
root@kvm:~#
- 连接到 KVM 实例并检查来宾操作系统中的内存:
root@kvm:~# virsh console kvm1
Connected to domain kvm1
Escape character is ^]
Debian GNU/Linux 8 debian ttyS0
debian login: root
Password:
...
root@debian:~# free -m
total used free shared buffers cached
Mem: 2010 93 1917 5 8 40
-/+ buffers/cache: 43 1966
Swap: 382 0 3 82
root@debian:~#
root@kvm:~#
- 检查实例 XML 定义中的内存设置:
root@kvm:~# virsh dumpxml kvm1 | grep memory
<memory unit='KiB'>2097152</memory>
root@kvm:~#
- 获取来宾 CPU 的信息:
root@kvm:~# virsh vcpuinfo kvm1
VCPU: 0
CPU: 29
State: running
CPU time: 9.7s
CPU Affinity: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
root@kvm:~#
- 列出来宾操作系统使用的虚拟 CPU 数量:
root@kvm:~# virsh vcpucount kvm1
maximum config 1
maximum live 1
current config 1
current live 1
root@kvm:~#
- 将虚拟机分配的 CPU 数量更改为 4:
root@kvm:~# virsh edit kvm1
...
<vcpu placement='static'>4</vcpu>
...
Domain kvm1 XML configuration edited.
root@kvm:~#
- 确保 CPU 数量更新生效:
root@kvm:~# virsh vcpucount kvm1
maximum config 4
maximum live 4
current config 4
current live 4
root@kvm:~# virsh dumpxml kvm1 | grep -i cpu
<vcpu placement='static'>4</vcpu>
root@kvm:~#
工作原理...
在第 1 步中,我们收集了正在运行的 KVM 实例的内存统计信息。从输出中,我们可以看到虚拟机配置了 1GB 的内存(由实际参数指示),并且当前使用了 333644 KB 的内存。
在第 2 步中,我们将可用内存更新为 2 GB,并在第 4 步中继续更新可以分配给实例的最大内存。为了执行此操作,必须先停止实例,如第 3 步所示。
在第 6、7 和 8 步中,我们通过首先调用dommemstat
子命令、然后连接到虚拟机的控制台,最后通过转储实例定义检查当前配置,确保了更新的生效。
virsh
命令提供了几个子命令来检查正在运行的虚拟机的 CPU 状态。在第 9 步和第 10 步中,我们列出了kvm1
实例的虚拟 CPU 数量,在本例中为一个,并显示了当前的状态、负载和亲和性。
最后,在第 11 步和第 12 步中,我们更新了实例的 XML 定义,分配了四个 CPU 并列出了新的数量。
还有更多...
在本教程中,我们使用了virsh
命令的多个子命令,通过一行命令来执行。这在我们需要通过脚本运行命令时特别有用。virsh
命令还提供了一个交互式终端,这样可以减少一些输入,并提供上下文帮助。要启动虚拟化交互式终端,请运行以下代码:
root@kvm:~# virsh
Welcome to virsh, the virtualization interactive terminal.
Type: 'help' for help with commands
'quit' to quit
virsh #
输入help
将列出所有可用的子命令及其简短描述。要获取某个特定子命令的更多信息,请输入:
virsh # help vcpucount
NAME
vcpucount - domain vcpu counts
SYNOPSIS
vcpucount <domain> [--maximum] [--active] [--live] [--config] [--current] [--guest]
DESCRIPTION
Returns the number of virtual CPUs used by the domain.
OPTIONS
[--domain] <string> domain name, id or uuid
--maximum get maximum count of vcpus
--active get number of currently active vcpus
--live get value from running domain
--config get value to be used on next boot
--current get value according to current domain state
--guest retrieve vcpu count from the guest instead of the hypervisor
virsh #
本教程中我们执行的所有步骤都可以在交互式终端中完成。
将块设备附加到虚拟机
在本教程中,我们将探讨几种向 KVM 实例添加新块设备的方法。新的块设备可以进行分区、格式化,并作为常规块设备在来宾操作系统内使用。我们可以将磁盘附加到正在运行的实例,也可以通过离线创建单独块设备的 XML 定义将其持久化添加。从主机操作系统,可以将任何类型的块设备文件呈现给来宾,包括 iSCSI 目标、LVM 逻辑卷或镜像文件。
准备工作
本教程需要以下内容:
-
一个具有控制台访问权限的正在运行的 KVM 实例
-
dd
实用工具
如何操作...
要将新的块设备附加到 KVM 来宾中,运行以下命令:
- 创建一个新的 1GB 镜像文件:
root@kvm:~# dd if=/dev/zero of=/tmp/new_disk.img bs=1M count=1024
1024+0 records in
1024+0 records out
1073741824 bytes (1.1 GB) copied, 0.670831 s, 1.6 GB/s
root@kvm:~#
- 将文件作为新磁盘附加到 KVM 实例:
root@kvm:~# virsh attach-disk kvm1 /tmp/new_disk.img vda --live
Disk attached successfully
root@kvm:~#
- 通过控制台连接到 KVM 实例:
root@kvm:~# virsh console kvm1
Connected to domain kvm1
Escape character is ^]
Debian GNU/Linux 8 debian ttyS0
debian login: root
Password:
...
root@debian:~#
- 打印内核环形缓冲区并检查新的块设备:
root@debian:~# dmesg | grep vda
[ 3664.134978] sd 2:0:2:0: [vda] 2097152 512-byte logical blocks: (1.07 GB/1.00 GiB)
[ 3664.135248] sd 2:0:2:0: [vda] Write Protect is off
[ 3664.135251] sd 2:0:2:0: [vda] Mode Sense: 63 00 00 08
[ 3664.135340] sd 2:0:2:0: [vda] Write cache: enabled, read cache: enabled, doesn't support DPO or FUA
[ 3664.138254] vda: unknown partition table
[ 3664.139008] sd 2:0:2:0: [vda] Attached SCSI disk
root@debian:~#
- 检查新块设备:
root@debian:~# fdisk -l /dev/vda
Disk /dev/vda: 1 GiB, 1073741824 bytes, 2097152 sectors
Units: sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
root@debian:~#
- 从主机操作系统中导出实例配置:
root@kvm:~# virsh dumpxml kvm1
<domain type='kvm' id='23'>
...
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='raw'/>
<source file='/tmp/kvm1.img'/>
<target dev='hda' bus='ide'/>
<alias name='ide0-0-0'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
<disk type='file' device='disk'>
<driver name='qemu' type='raw'/>
<source file='/tmp/new_disk.img'/>
<target dev='vda' bus='scsi'/>
<alias name='scsi0-0-2'/>
<address type='drive' controller='0' bus='0' target='0' unit='2'/>
</disk>
</devices>
</domain>
root@kvm:~#
- 获取新磁盘的信息:
root@kvm:~# virsh domblkstat kvm1 vda
vda rd_req 119
vda rd_bytes 487424
vda wr_req 0
vda wr_bytes 0
vda flush_operations 0
vda rd_total_times 29149092
vda wr_total_times 0
vda flush_total_times 0
root@kvm:~#
- 卸载磁盘:
root@kvm:~# virsh detach-disk kvm1 vda --live
Disk detached successfully
root@kvm:~#
- 复制或创建一个新的原始镜像:
root@kvm:~# cp /tmp/new_disk.img /tmp/other_disk.img
root@kvm:~#
- 写入以下
config
文件:
root@kvm:~# cat other_disk.xml
<disk type='file' device='disk'>
<driver name='qemu' type='raw' cache='none'/>
<source file='/tmp/other_disk.img'/>
<target dev='vdb'/>
</disk>
root@kvm:~#
- 附加新设备:
root@kvm:~# virsh attach-device kvm1 --live other_disk.xml
Device attached successfully
root@kvm:~#
- 卸载块设备:
root@kvm:~# virsh detach-device kvm1 other_disk.xml --live
Device detached successfully
root@kvm:~#
工作原理...
向运行中的 KVM 实例附加更多磁盘非常有用,尤其是在来宾操作系统中使用 LVM 时,因为这可以扩展逻辑卷,从而在运行时添加更多磁盘空间。Libvirt 提供了两种不同的方法,正如我们在前面的步骤中所看到的。我们可以使用virsh attach-disk
命令,传递镜像文件的位置和新块设备的名称,正如我们在第 2 步中看到的那样。
在第 1 步中,我们使用dd
命令创建了一个新的原始镜像;然而,我们也可以使用 qemu-img 工具,正如我们在第一章《QEMU 和 KVM 入门》中的管理磁盘镜像与 qemu-img一节所看到的那样。
在第 2 步中附加新磁盘后,在第 3、4 和 5 步中,我们连接到虚拟机并验证了新块设备确实存在。这一点在第 6 步的实例 XML 定义中也有所体现。
若要在虚拟机重启后使新设备可用并保持 XML 定义的更改,请将--persist
选项传递给virsh attach-disk
命令。
在第 7 步中,我们显示了有关新磁盘的一些信息。这些数据非常有用,可以用来监控块设备的读写请求,而无需连接到虚拟实例。
在第 8 步中,我们从运行中的 KVM 实例中卸载了磁盘。如果此时导出实例定义,你会发现额外的磁盘已不再存在。
附加块设备的另一种方式在第 10 步中展示。我们首先创建一个新的 XML 文件,其中包含要附加的块设备定义。请注意,这个定义与第 6 步中的输出非常相似。
在第 11 步中,我们再次卸载了新设备。请注意,我们必须指定相同的设备 XML 定义文件才能进行此操作。
一旦磁盘在来宾操作系统中可见,我们可以将其作为常规块设备使用,可以对其进行分区、创建文件系统并挂载。
在运行中的虚拟机与主机操作系统之间共享目录
在前一个步骤中,我们展示了如何将磁盘附加到运行中的 KVM 实例的两个示例。在本步骤中,我们将从主机操作系统共享一个目录,并使其在虚拟机中可用。然而,我们只能在实例停止后执行此操作。如果你一直跟随本教程,你应该已经有一个可以使用的 libvirt KVM 实例。
准备工作
本步骤的前提条件如下:
-
停止的 libvirt KVM 实例并可通过控制台访问
-
配备
9p
和virtio
内核模块的来宾操作系统(大多数 Linux 发行版默认包含)
如何操作...
要将宿主操作系统的目录共享给 KVM 来宾系统,请执行以下命令:
- 在宿主操作系统上创建一个新目录并添加文件:
root@kvm:~# mkdir /tmp/shared
root@kvm:~# touch /tmp/shared/file
root@kvm:~#
- 将以下定义添加到已停止的 KVM 实例中:
root@kvm:~# virsh edit kvm1
...
<devices>
...
<filesystem type='mount' accessmode='passthrough'>
<source dir='/tmp/shared'/>
<target dir='tmp_shared'/>
</filesystem>
...
</devices>
...
Domain kvm1 XML configuration edited.
root@kvm:~#
- 启动虚拟机:
root@kvm:~# virsh start kvm1
Domain kvm1 started
root@kvm:~#
- 按如下方式连接到控制台:
root@kvm:~# virsh console kvm1
Connected to domain kvm1
Escape character is ^]
Debian GNU/Linux 8 debian ttyS0
debian login: root
Password:
...
root@debian:~#
- 确保加载
9p
和virtio
内核模块:
root@debian:~# lsmod | grep 9p 9pnet_virtio 17006 0
9pnet 61632 1 9pnet_virtio
virtio_ring 17513 3 virtio_pci,virtio_balloon,9pnet_virtio
virtio 13058 3 virtio_pci,virtio_balloon,9pnet_virtio
root@debian:~#
- 将共享目录挂载到
/mnt
:
root@debian:~# mount -t 9p -o trans=virtio tmp_shared /mnt
root@debian:~#
- 列出新的挂载:
root@debian:~# mount | grep tmp_shared
tmp_shared on /mnt type 9p (rw,relatime,sync,dirsync,trans=virtio)
root@debian:~#
- 确保在宿主操作系统中可以看到共享文件:
root@debian:~# ls -la /mnt/
total 8
drwxr-xr-x 2 root root 4096 Mar 23 11:25 .
drwxr-xr-x 22 root root 4096 Mar 22 16:28 ..
-rw-r--r-- 1 root root 0 Mar 23 11:25 file
root@debian:~#
它是如何工作的...
让我们一步步走过这些步骤,看看在前面章节中完成了哪些操作的详细内容。
在步骤 1 中,我们创建了一个目录和一个文件,希望与来宾操作系统共享。然后,在已停止的 KVM 实例上,我们在步骤 2 中添加了新的 <filesystem>
定义。我们使用了挂载类型,因为我们要挂载一个目录,并指定了 accessmode
,它指定了访问共享资源的安全模式。有三种访问模式:
-
passthrough
:这是默认模式,使用来宾操作系统内用户的权限访问共享目录 -
mapped
:在此模式下,共享目录及其文件将使用来自宿主操作系统的 QEMU 用户权限进行访问 -
squash
:此模式类似于透传模式;然而,像chmod
等特权操作的失败会被忽略
在新定义生效后,我们在步骤 3 中启动虚拟机,并在步骤 4 中连接到它。
在我们使用的 Debian 虚拟机上,所需的内核模块在虚拟机启动时已被加载。如果你的虚拟机没有自动加载,请通过以下命令加载这些模块:
root@debian:~# modprobe 9p virtio
root@debian:~#
主要操作发生在步骤 6,在这里我们挂载共享目录,并确保它已经成功挂载,后续步骤中的文件也已经到位。
还有更多...
在本章中,我们一直在使用 libvirt
工具集和库提供的 virsh
命令启动 KVM 虚拟机。如果你在启动来宾虚拟机后查看进程树,你会发现 virsh
命令实际上会调用 /usr/bin/qemu-system-x86_64
二进制文件。如果你还记得 第一章* 中的 使用 qemu-system- 启动虚拟机 的食谱,开始使用 QEMU 和 KVM;这正是我们用来启动 QEMU/KVM 虚拟机的工具。
请注意当我们在本教程中启动 KVM 实例时,libvirt
守护进程启动的过程:
root@kvm:~# pgrep -lfa qemu
6233 /usr/bin/qemu-system-x86_64 -name kvm1 -S -machine pc-i440fx-trusty,accel=kvm,usb=off -m 2048 -realtime mlock=off -smp 2,sockets=2,cores=1,threads=1 -uuid 6ad84d8a-229d-d1f6-ecfc-d29a25fcfa03 -no-user-config -nodefaults -chardev socket,id=charmonitor,path=/var/lib/libvirt/qemu/kvm1.monitor,server,nowait -mon chardev=charmonitor,id=monitor,mode=control -rtc base=utc -no-shutdown -boot strict=on -device piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2 -device lsi,id=scsi0,bus=pci.0,addr=0x5 -drive file=/tmp/kvm1.img,if=none,id=drive-ide0-0-0,format=raw -device ide-hd,bus=ide.0,unit=0,drive=drive-ide0-0-0,id=ide0-0-0,bootindex=1 -fsdev local,security_model=passthrough,id=fsdev-fs0,path=/tmp/shared -device virtio-9p-pci,id=fs0,fsdev=fsdev-fs0,mount_tag=tmp_shared,bus=pci.0,addr=0x6 -netdev tap,fd=25,id=hostnet0 -device rtl8139,netdev=hostnet0,id=net0,mac=52:54:00:c5:c8:9d,bus=pci.0,addr=0x3 -chardev pty,id=charserial0 -device isa-serial,chardev=charserial0,id=serial0 -vnc 146.20.141.158:0 -device cirrus-vga,id=video0,bus=pci.0,addr=0x2 -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x4
root@kvm:~#
我们可以不使用 libvirt,而是通过以下命令直接启动一个新的来宾操作系统,并使用与本教程中相同的共享目录,只需确保先停止我们之前启动的 libvirt
实例:
root@kvm:~# qemu-system-x86_64 -name debian -fsdev local,id=tmp,path=/tmp/shared,security_model=passthrough -device virtio-9p-pci,fsdev=tmp,mount_tag=tmp_shared -enable-kvm -usbdevice tablet -vnc 146.20.141.158:0 -m 1024 -drive format=raw,file=/tmp/kvm1.img -daemonize
root@kvm:~#
你应该能够使用你的 VNC 客户端连接到来宾系统并执行相同的步骤来挂载共享目录,正如我们之前所做的那样。
自动启动 KVM 实例
一旦 KVM 实例被定义并启动,它将一直运行,直到宿主操作系统启动。一旦宿主操作系统重启,通过 libvirt 构建的实例将在宿主操作系统启动并且libvirt
守护进程运行时不会自动启动。在这个食谱中,我们将改变这种行为,并确保在libvirt
守护进程启动时虚拟实例也能启动。
准备工作
对于此食谱,我们将需要一个通过 libvirt 构建的单个 KVM 实例。
如何操作...
要配置 KVM 客户机以便在服务器或libvirtd
重启后自动启动,请运行以下命令:
- 启用虚拟机
autostart
:
root@kvm:~# virsh autostart kvm1
Domain kvm1 marked as autostarted
root@kvm:~#
- 获取实例的信息:
root@kvm:~# virsh dominfo kvm1
Id: 31
Name: kvm1
UUID: 6ad84d8a-229d-d1f6-ecfc-d29a25fcfa03
OS Type: hvm
State: running
CPU(s): 2
CPU time: 10.9s
Max memory: 2097152 KiB
Used memory: 1048576 KiB
Persistent: yes
Autostart: enable
Managed save: no
Security model: none
Security DOI: 0
root@kvm:~#
- 停止运行中的实例并确保它处于
关闭
状态:
root@kvm:~# virsh destroy kvm1
Domain kvm1 destroyed
root@kvm:~# virsh list --all
Id Name State
----------------------------------------------------
- kvm1 shut off
root@kvm:~#
- 停止
libvirt
守护进程并确保它没有运行:
root@kvm:~# /etc/init.d/libvirt-bin stop
libvirt-bin stop/waiting
root@kvm:~# pgrep -lfa libvirtd
root@kvm:~#
- 重新启动
libvirt
守护进程:
root@kvm:~# /etc/init.d/libvirt-bin start
libvirt-bin start/running, process 6639
root@kvm:~#
- 列出所有正在运行的实例:
root@kvm:~# virsh list --all
Id Name State
----------------------------------------------------
2 kvm1 running
root@kvm:~#
- 禁用
autostart
选项:
root@kvm:~# virsh autostart kvm1 --disable
Domain kvm1 unmarked as autostarted
root@kvm:~#
- 验证更改:
root@kvm:~# virsh dominfo kvm1 | grep -i autostart
Autostart: disable
root@kvm:~#
它是如何工作的...
在这个简单的食谱中,我们启用了 libvirt 控制的 KVM 实例的autostart
功能。
在第 1 步中,我们启用了autostart
并在第 2 步中验证了它已经启用。
接下来,为了模拟服务器重启,我们首先在第 3 步中停止运行中的实例,并在第 4 步中停止libvirt
守护进程。
在第 5 步中,我们重新启动了libvirt
守护进程,并观察到它也启动了虚拟机,如第 6 步所示。
最后,在第 7 步和第 8 步中,我们禁用了autostart
功能,并确保它确实已被禁用。
与存储池的工作
Libvirt 提供了一种集中式的方式来管理实例卷(无论是镜像文件还是目录),通过定义存储池。存储池是卷的集合,然后可以将这些卷分配给虚拟机,用来托管它们的文件系统或作为额外的块设备。使用存储池的主要好处是,libvirt 能够以集中方式向虚拟机呈现并管理指定的存储类型。
截至目前,以下存储池后端是可用的:
-
目录后端
-
本地文件系统后端
-
网络文件系统后端
-
逻辑后端
-
磁盘后端
-
iSCSI 后端
-
SCSI 后端
-
多路径后端
-
RADOS 块设备后端
-
Sheepdog 后端
-
Gluster 后端
-
ZFS 后端
-
Virtuozzo 存储后端
在这个食谱中,我们将创建一个目录支持的存储池,将现有镜像移动到该池中,然后使用该存储池和卷来配置一个新的 KVM 实例。
准备工作
对于这个食谱,我们将需要以下内容:
-
我们在使用 virt-install 和控制台构建新的 KVM 实例食谱中创建的 Debian 原始镜像
-
libvirt
软件包
如何操作...
以下步骤演示了如何创建一个新的存储池,检查它,并将其分配给虚拟机:
- 复制我们在本章使用 virt-install 和控制台构建新的 KVM 实例食谱中创建的 Debian 原始镜像文件:
root@kvm:~# cp /tmp/kvm1.img /var/lib/libvirt/images/
root@kvm:~#
- 创建以下存储池定义:
root@kvm:~# cat file_storage_pool.xml
<pool type="dir">
<name>file_virtimages</name>
<target>
<path>/var/lib/libvirt/images</path>
</target>
</pool>
root@kvm:~#
- 定义新的存储池:
root@kvm:~# virsh pool-define file_storage_pool.xml
Pool file_virtimages defined from file_storage_pool.xml
root@kvm:~#
- 列出所有存储池:
root@kvm:~# virsh pool-list --all
Name State Autostart
-------------------------------------------
file_virtimages inactive no
root@kvm:~#
- 启动新的存储池并确保它处于活动状态:
root@kvm:~# virsh pool-start file_virtimages
Pool file_virtimages started
root@kvm:~# virsh pool-list --all
Name State Autostart
-------------------------------------------
file_virtimages active no
root@kvm:~#
- 启用存储池的
autostart
功能:
root@kvm:~# virsh pool-autostart file_virtimages
Pool file_virtimages marked as autostarted
root@kvm:~# virsh pool-list --all
Name State Autostart
-------------------------------------------
file_virtimages active yes
root@kvm:~#
- 获取有关存储池的更多信息:
root@kvm:~# virsh pool-info file_virtimages
Name: file_virtimages
UUID: d51d500b-8885-4c26-8000-2ae46ffe9018
State: running
Persistent: yes
Autostart: yes
Capacity: 219.87 GiB
Allocation: 7.99 GiB
Available: 211.88 GiB
root@kvm:~#
- 列出所有属于存储池的卷:
root@kvm:~# virsh vol-list file_virtimages
Name Path
----------------------------------------------------
kvm1.img /var/lib/libvirt/images/kvm1.img
root@kvm:~#
- 获取卷的相关信息:
root@kvm:~# virsh vol-info /var/lib/libvirt/images/kvm1.img
Name: kvm1.img
Type: file
Capacity: 8.00 GiB
Allocation: 1.87 GiB
root@kvm:~#
- 使用存储池和卷启动新的 KVM 实例,并确保其正在运行:
root@kvm:~# virt-install --name kvm1 --ram 1024 --graphics vnc,listen=146.20.141.158 --hvm --disk vol=file_virtimages/kvm1.img --import
Starting install...
Creating domain... | 0 B 00:00
Domain creation completed. You can restart your domain by running:
virsh --connect qemu:///system start kvm1
root@kvm:~#
root@kvm:~# virsh list --all
Id Name State
----------------------------------------------------
3 kvm1 running
root@kvm:~#
它是如何工作的...
我们从本书前面安装的 Debian 操作系统镜像开始这份食谱;不过,如果你没有该镜像,完全可以使用一个empty
、raw
或qcow2
镜像,添加到存储池中,并在其上安装虚拟机操作系统,几乎不需要更改食谱步骤。
在第 1 步中,我们将 VM 镜像复制到默认的 libvirt 存储池位置/var/lib/libvirt/images/
,但你也可以创建自己的目录,位置并不重要,只要在存储池配置文件中定义即可。我们在第 2 步完成这项工作。
在第 3 步中,我们定义新的存储池,通过指定名称、目标目录和池的类型,这里是一个目录后端池。然后在第 4 步继续列出新的池。请注意,一旦定义了存储池,我们仍然需要启动它,就像从 XML 文件定义新的 KVM 实例一样。默认情况下,autostart
选项在新存储池上是禁用的。
在第 5 步中,我们启动存储池并确保它处于活动状态。然后在第 6 步中,我们继续启用autostart
功能,以便在主机服务器重启时,卷能够被使用。
尽管不是强制性的,但我们在第 7 步检查存储池及其卷提供的元数据。请注意,分配字段显示了存储池中卷所占用的空间。目前我们有一个具有该精确大小的原始镜像。
在第 8 步中,我们列出所有属于新存储池的卷,并在第 9 步获取该单个卷的更多信息。
最后在第 10 步,我们通过将存储池和卷的名称传递给卷磁盘类型,启动一个新的 KVM 实例。
还有更多内容...
让我们来看一个稍微复杂一点的示例,通过定义一个 iSCSI 支持的池来使用存储池。
创建 iSCSI 目标并在发起服务器上登录超出了本食谱的范围,因此我们假设你已经准备好一个可以从远程服务器使用的 iSCSI 目标。新的存储池定义如下:
<pool type='iscsi'>
<name>iscsi_virtimages</name>
<source>
<host name='iscsi-target.linux-admins.net'/>
<device path='iqn.2004-04.ubuntu:ubuntu16:iscsi.libvirtkvm'/>
</source>
<target>
<path>/dev/disk/by-path</path>
</target>
</pool>
该文件与目录支持的存储池非常相似,主要区别在于以下属性:
-
<host>
属性指定正在导出 iSCSI LUN 的 iSCSI 目标服务器的主机名。 -
<device>
指定我们将登录的 iSCSI LUN 的名称。 -
一旦新的 iSCSI 块设备登录,它将出现在
<path>
指定的位置,在大多数 Linux 发行版中,位于/dev/disk/by-path
目录下。
我们像在食谱中的步骤 3 和步骤 5 一样定义并启动新的存储池。一旦存储池激活,libvirt 会记录远程 iSCSI 目标 LUN。我们可以像往常一样列出可用的 iSCSI 卷:
root@kvm:~# virsh vol-list iscsi_virtimages
Name Path
-----------------------------------------
10.0.0.1 /dev/disk/by-path/ip-10.184.226.106:3260-iscsi-iqn.2004-04.ubuntu:ubuntu16:iscsi.libvirtkvm-lun-1
root@kvm:~#
要使用 iSCSI 卷作为来宾操作系统文件系统的目标来启动新的安装过程,运行以下代码:
root@kvm:~# virt-install --name kvm1 --ram 1024 --extra-args="text console=tty0 utf8 console=ttyS0,115200" --graphics vnc,listen=146.20.141.158 --hvm --location=http://ftp.us.debian.org/debian/dists/stable/main/installer-amd64/ --disk vol=iscsi_virtimages/10.0.0.1
Starting install...
...
root@kvm:~#
有关其他后端类型的 XML 定义的更多信息,请参见 libvirt.org/storage.html
。
管理卷
在前一个食谱中,我们看到了如何创建新的存储池,向其中添加卷,并使用该卷创建新的 KVM 实例。在这个食谱中,我们将专注于操作已存在存储池中的卷。严格来说,我们不需要使用存储池和卷来构建虚拟机。我们可以使用其他工具来管理和操作虚拟实例镜像,例如 qemu-img
工具。使用卷只是为了方便集中存储各种后端类型的存储库。
准备工作
这个食谱的主要要求是需要有一个已存在的、目录后端的存储池。如果你跳过了前一个食谱,现在是时候创建一个新的了,因为我们将使用它来操作卷。
如何操作...
要创建、检查并分配卷到实例,运行以下命令:
- 列出可用的存储池:
root@kvm:~# virsh pool-list --all
Name State Autostart
-------------------------------------------
file_virtimages active yes
root@kvm:~#
- 列出存储池中可用的卷:
root@kvm:~# virsh vol-list file_virtimages
Name Path
--------------------------------------------------------------------
kvm1.img /var/lib/libvirt/images/kvm1.img
root@kvm:~#
- 创建一个指定大小的新卷:
root@kvm:~# virsh vol-create-as file_virtimages new_volume.img 9G
Vol new_volume.img created
root@kvm:~#
- 列出文件系统中的卷:
root@kvm:~# ls -lah /var/lib/libvirt/images/
total 11G
drwx--x--x 2 root root 4.0K Mar 23 20:38 .
drwxr-xr-x 8 root root 4.0K Mar 21 23:16 ..
-rwxr-xr-x 1 libvirt-qemu kvm 8.0G Mar 23 20:23 kvm1.img
-rw------- 1 root root 9.0G Mar 23 20:38 new_volume.img
root@kvm:~#
- 获取有关新卷的信息:
root@kvm:~# qemu-img info /var/lib/libvirt/images/new_volume.img
image: /var/lib/libvirt/images/new_volume.img
file format: raw
virtual size: 9.0G (9663676416 bytes)
disk size: 9.0G
root@kvm:~#
- 使用
virsh
命令获取更多信息:
root@kvm:~# virsh vol-info new_volume.img --pool file_virtimages
Name: new_volume.img
Type: file
Capacity: 9.00 GiB
Allocation: 9.00 GiB
root@kvm:~#
- 转储卷配置:
root@kvm:~# virsh vol-dumpxml new_volume.img --pool file_virtimages
<volume type='file'>
<name>new_volume.img</name>
<key>/var/lib/libvirt/images/new_volume.img</key>
<source>
</source>
<capacity unit='bytes'>9663676416</capacity>
<allocation unit='bytes'>9663680512</allocation>
<target>
<path>/var/lib/libvirt/images/new_volume.img</path>
<format type='raw'/>
<permissions>
<mode>0600</mode>
<owner>0</owner>
<group>0</group>
</permissions>
<timestamps>
<atime>1490301514.446004048</atime>
<mtime>1490301483.698003615</mtime>
<ctime>1490301483.702003615</ctime>
</timestamps>
</target>
</volume>
root@kvm:~#
- 调整卷的大小并显示新的大小:
root@kvm:~# virsh vol-resize new_volume.img 10G --pool file_virtimages
Size of volume 'new_volume.img' successfully changed to 10G
root@kvm:~# virsh vol-info new_volume.img --pool file_virtimages
Name: new_volume.img
Type: file
Capacity: 10.00 GiB
Allocation: 9.00 GiB
root@kvm:~#
- 删除卷并列出存储池中所有可用卷:
root@kvm:~# virsh vol-delete new_volume.img --pool file_virtimages
Vol new_volume.img deleted
root@kvm:~# virsh vol-list file_virtimages
Name Path
------------------------------------------------------------------
kvm1.img /var/lib/libvirt/images/kvm1.img
root@kvm:~#
- 克隆现有卷:
root@kvm:~# virsh vol-clone kvm1.img kvm2.img --pool file_virtimages
Vol kvm2.img cloned from kvm1.img
root@kvm:~# virsh vol-list file_virtimages
Name Path
--------------------------------------------------------------------
kvm1.img /var/lib/libvirt/images/kvm1.img
kvm2.img /var/lib/libvirt/images/kvm2.img
root@kvm:~#
工作原理...
我们从在上一食谱中创建的 file_virtimages
存储池开始这个食谱。在步骤 1 中,我们列出了所有存储池以确认这一点。在步骤 2 中,我们看到我们的存储池包含一个卷。这里没有惊讶,因为我们在本章的上一个食谱中创建了它。
在步骤 3 中,我们通过指定卷的名称、大小以及我们希望它属于的存储池来创建一个新卷。由于这是一个目录支持的存储池,我们可以在步骤 4 中看到该卷作为一个原始镜像文件。
在步骤 5 和步骤 6 中,我们收集了更多关于新卷的信息。我们可以看到它是原始格式的,因此默认是一个稀疏镜像。稀疏镜像不会分配所有磁盘空间,并随着数据写入的增多而增长。
在步骤 7 中,我们转储了卷的定义。我们可以稍后使用 virsh vol-create
命令用它来定义一个新卷。
Libvirt 提供了一种方便的方式来调整现有镜像的大小。这就是我们在步骤 8 中所做的——我们将镜像大小调整为 10 GB。现在我们可以看到分配的大小小于容量;这是因为该镜像是原始格式的。
最后,在第 9 步中,我们删除了镜像,尽管我们本可以使用它来安装一个新的虚拟机,正如 使用存储池 这一食谱所示。
在最后一步,我们使用现有的 Debian 镜像并从中创建了一个克隆卷。使用克隆的卷启动虚拟机将导致生成与我们克隆的卷相同的 KVM 实例。结合实例定义的转储,这是备份 KVM 实例的绝佳方式,只要你将卷镜像文件和 XML 定义文件存储到远程位置。我们将在后续的食谱中探讨如何备份 KVM 实例。
管理密钥
Libvirt 提供了一个 API 来创建、存储和使用密钥。密钥是包含敏感信息(如密码)的对象,可以与不同的卷后端类型关联。回想一下 使用存储池 这一食谱,我们从远程 iSCSI 目标创建了一个 iSCSI 存储池和卷,并将其用作 KVM 客户机的镜像。在生产环境中,iSCSI 目标通常会使用 CHAP 认证。在本食谱中,我们将创建一个密钥,用于与 iSCSI 卷一起使用。
准备工作
对于本食谱,我们需要以下内容:
-
一个带有 iSCSI 支持卷的存储池
-
libvirt
包
如何操作...
使用 libvirt 定义和列出密钥,请按照以下步骤操作:
- 列出所有可用的密钥:
root@kvm:~# virsh secret-list
UUID Usage
-------------------------------------------------------------------
root@kvm:~#
- 创建以下密钥定义:
root@kvm:~# cat volume_secret.xml
<secret ephemeral='no'>
<description>Passphrase for the iSCSI iscsi-target.linux-admins.net target server</description>
<usage type='iscsi'>
<target>iscsi_secret</target>
</usage>
</secret>
root@kvm:~#
- 创建密钥并确保它已成功创建:
root@kvm:~# virsh secret-define volume_secret.xml
Secret 7ad1c208-c2c5-4723-8dc5-e2f4f576101a created
root@kvm:~# virsh secret-list
UUID Usage
-----------------------------------------------------------------
7ad1c208-c2c5-4723-8dc5-e2f4f576101a iscsi iscsi_secret
root@kvm:~#
- 为密钥设置一个值:
root@kvm:~# virsh secret-set-value 7ad1c208-c2c5-4723-8dc5-e2f4f576101a $(echo "some_password" | base64)
Secret value set
root@kvm:~#
- 创建一个新的 iSCSI 存储池定义文件:
root@kvm:~# cat iscsi.xml
<pool type='iscsi'>
<name>iscsi_virtimages</name>
<source>
<host name='iscsi-target.linux-admins.net'/>
<device path='iqn.2004-04.ubuntu:ubuntu16:iscsi.libvirtkvm'/>
<auth type='chap' username='iscsi_user'>
<secret usage='iscsi_secret'/>
</auth>
</source>
<target>
<path>/dev/disk/by-path</path>
</target>
</pool>
root@kvm:~#
如何运作...
在第 1 步中,我们列出了 libvirt 所知道的所有可用密钥。由于我们尚未创建任何密钥,因此列表为空。
在第 2 步中,我们创建了密钥的 XML 定义。我们用于定义密钥的 XML 元素包括:
-
<secret>
根元素,带有一个可选的ephemeral
属性,告诉libvirt
密码应仅存储在内存中(如果设置为 yes)。 -
<description>
属性,包含一个任意描述。 -
<usage>
元素指定密钥的用途及其类型。在本示例中,type
属性设置为 iSCSI。其他可用的类型包括volume
、ceph
和tls
。type
属性是必填项。 -
<target>
元素指定了一个任意名称,用于 iSCSI 存储池定义中。
配置文件就绪后,我们在第 3 步中创建密钥。如果操作成功,libvirt 会返回一个 UUID,用于标识该密钥。
在第 4 步中,我们为密钥设置了一个值,通过 base64 编码 some_password
字符串,这是我们希望用于存储池卷的 iSCSI 目标密码。
最后,在第 5 步中,我们在 iSCSI 池定义的<source>
部分下添加<auth>
属性。请注意,我们希望 iSCSI 卷使用的密钥在<secret usage='iscsi_secret'/>
属性中进行了指定。Libvirt 现在可以使用iscsi_secret
名称来定位它存储的实际密码。
第三章:使用 libvirt 进行 KVM 网络配置
在本章中,我们将涵盖以下主题:
-
Linux 桥接
-
Open vSwitch
-
配置 NAT 转发网络
-
配置桥接网络
-
配置 PCI 直通网络
-
操作网络接口
简介
使用 libvirt,我们可以为 KVM 客户机定义不同的网络类型,使用已熟悉的 XML 定义语法,以及virsh
和virt-install
用户空间工具。在本章中,我们将部署三种不同的网络类型,探索网络 XML 格式,并展示如何定义和操作 KVM 实例的虚拟接口的示例。
为了能够将虚拟机连接到主机操作系统或彼此之间,我们将使用 Linux 桥接和Open vSwitch(OVS)守护进程、用户空间工具和内核模块。这两种软件桥接技术都非常擅长以一致且易于操作的方式创建软件定义网络(SDN),无论其复杂性如何。Linux 桥接和 OVS 都充当桥接/交换机,KVM 客户机的虚拟接口可以连接到这些设备。
了解这些内容后,让我们开始深入学习 Linux 中的软件桥接。
Linux 桥接
Linux 桥接是一个软件层 2 设备,提供了物理桥接设备的一些功能。它可以在 KVM 客户机、主机操作系统和运行在其他服务器或网络上的虚拟机之间转发数据帧。Linux 桥接由两个组件组成——一个用户空间管理工具,我们将在本示例中使用它,以及一个内核模块,负责将多个以太网段连接在一起。每个我们创建的软件桥接可以附加多个端口,网络流量可以从这些端口转发进出。在创建 KVM 实例时,我们可以将与之关联的虚拟接口连接到桥接上,这类似于将一根网络电缆从物理服务器的网卡插入到桥接/交换机设备中。作为一个层 2 设备,Linux 桥接使用 MAC 地址,并维护一个内核结构,以内容可寻址存储器(CAM)表的形式跟踪端口和关联的 MAC 地址。
在这个示例中,我们将创建一个新的 Linux 桥接,并使用brctl
工具来操作它。
准备工作
对于本示例,我们需要以下内容:
- 启用了
802.1d Ethernet
桥接选项的最新 Linux 内核
要检查你的内核是否编译了这些特性,或者是否作为内核模块暴露,请运行以下命令:
root@kvm:~# cat /boot/config-`uname -r` | grep -i bridg
# PC-card bridges
CONFIG_BRIDGE_NETFILTER=y
CONFIG_NF_TABLES_BRIDGE=m
CONFIG_BRIDGE_NF_EBTABLES=m
CONFIG_BRIDGE_EBT_BROUTE=m
CONFIG_BRIDGE_EBT_T_FILTER=m
CONFIG_BRIDGE_EBT_T_NAT=m
CONFIG_BRIDGE_EBT_802_3=m
CONFIG_BRIDGE_EBT_AMONG=m
CONFIG_BRIDGE_EBT_ARP=m
CONFIG_BRIDGE_EBT_IP=m
CONFIG_BRIDGE_EBT_IP6=m
CONFIG_BRIDGE_EBT_LIMIT=m
CONFIG_BRIDGE_EBT_MARK=m
CONFIG_BRIDGE_EBT_PKTTYPE=m
CONFIG_BRIDGE_EBT_STP=m
CONFIG_BRIDGE_EBT_VLAN=m
CONFIG_BRIDGE_EBT_ARPREPLY=m
CONFIG_BRIDGE_EBT_DNAT=m
CONFIG_BRIDGE_EBT_MARK_T=m
CONFIG_BRIDGE_EBT_REDIRECT=m
CONFIG_BRIDGE_EBT_SNAT=m
CONFIG_BRIDGE_EBT_LOG=m
# CONFIG_BRIDGE_EBT_ULOG is not set
CONFIG_BRIDGE_EBT_NFLOG=m
CONFIG_BRIDGE=m
CONFIG_BRIDGE_IGMP_SNOOPING=y
CONFIG_BRIDGE_VLAN_FILTERING=y
CONFIG_SSB_B43_PCI_BRIDGE=y
CONFIG_DVB_DDBRIDGE=m
CONFIG_EDAC_SBRIDGE=m
# VME Bridge Drivers
root@kvm:~#
bridge
内核模块
要验证模块是否已加载并获取有关其版本和功能的更多信息,请执行以下命令:
root@kvm:~# lsmod | grep bridge
bridge 110925 0
stp 12976 2 garp,bridge
llc 14552 3 stp,garp,bridge
root@kvm:~#
root@kvm:~# modinfo bridge
filename: /lib/modules/3.13.0-107-generic/kernel/net/bridge/bridge.ko
alias: rtnl-link-bridge
version: 2.3
license: GPL
srcversion: 49D4B615F0B11CA696D8623
depends: stp,llc
intree: Y
vermagic: 3.13.0-107-generic SMP mod_unload modversions
signer: Magrathea: Glacier signing key
sig_key: E1:07:B2:8D:F0:77:39:2F:D6:2D:FD:D7:92:BF:3B:1D:BD:57:0C:D8
sig_hashalgo: sha512
root@kvm:~#
-
提供创建和操作 Linux 桥接的工具的
bridge-utils
包 -
使用 libvirt 或 QEMU 工具,或使用前几章中的现有 KVM 实例创建新的 KVM 客户机的能力
如何操作…
要创建、列出并操作一个新的 Linux 桥接,请按照以下步骤操作:
- 如果尚未安装,请安装 Linux 桥接包:
root@kvm:~# apt install bridge-utils
- 使用来自 第一章、QEMU 和 KVM 入门章节中的 使用 debootstrap 安装自定义操作系统镜像 配方的原始镜像构建新的 KVM 实例,如果你不是通读本书:
root@kvm:~# virt-install --name kvm1 --ram 1024 --disk path=/tmp/debian.img,format=raw --graphics vnc,listen=146.20.141.158 --noautoconsole --hvm --import
Starting install...
Creating domain... | 0 B 00:00
Domain creation completed. You can restart your domain by running:
virsh --connect qemu:///system start kvm1
root@kvm:~#
- 列出所有可用的桥接设备:
root@kvm:~# brctl show
bridge name bridge id STP enabled interfaces
virbr0 8000.fe5400559bd6 yes vnet0
root@kvm:~#
- 将虚拟桥接关闭,删除它,并确保它已被删除:
root@kvm:~# ifconfig virbr0 down
root@kvm:~# brctl delbr virbr0
root@kvm:~# brctl show
bridge name bridge id STP enabled interfaces
root@kvm:~#
- 创建一个新的桥接并启用它:
root@kvm:~# brctl addbr virbr0
root@kvm:~# brctl show
bridge name bridge id STP enabled interfaces
virbr0 8000.000000000000 no
root@kvm:~# ifconfig virbr0 up
root@kvm:~#
- 为桥接分配一个 IP 地址:
root@kvm:~# ip addr add 192.168.122.1 dev virbr0
root@kvm:~# ip addr show virbr0
39: virbr0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default
link/ether 32:7d:3f:80:d7:c6 brd ff:ff:ff:ff:ff:ff
inet 192.168.122.1/32 scope global virbr0
valid_lft forever preferred_lft forever
inet6 fe80::307d:3fff:fe80:d7c6/64 scope link
valid_lft forever preferred_lft forever
root@kvm:~#
- 列出主机操作系统上的虚拟接口:
root@kvm:~# ip a s | grep vnet
38: vnet0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UNKNOWN group default qlen 500
root@kvm:~#
- 将虚拟接口
vnet0
添加到桥接中:
root@kvm:~# brctl addif virbr0 vnet0
root@kvm:~# brctl show virbr0
bridge name bridge id STP enabled interfaces
virbr0 8000.fe5400559bd6 no vnet0
root@kvm:~#
- 启用 生成树协议 (STP) 并获取更多信息:
root@kvm:~# brctl stp virbr0 on
root@kvm:~# brctl showstp virbr0
virbr0
bridge id 8000.fe5400559bd6
designated root 8000.fe5400559bd6
root port 0 path cost 0
max age 20.00 bridge max age 20.00
hello time 2.00 bridge hello time 2.00
forward delay 15.00 bridge forward delay 15.00
ageing time 300.00
hello timer 0.26 tcn timer 0.00
topology change timer 0.00 gc timer 90.89
flags
vnet0 (1)
port id 8001 state forwarding
designated root 8000.fe5400559bd6 path cost 100
designated bridge 8000.fe5400559bd6 message age timer 0.00
designated port 8001 forward delay timer 0.00
designated cost 0 hold timer 0.00
flags
root@kvm:~#
- 从 KVM 实例内部,启用接口,请求 IP 地址,并测试与主机操作系统的连接:
root@debian:~# ifconfig eth0 up
root@debian:~# dhclient eth0
root@debian:~# ip a s eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 52:54:00:55:9b:d6 brd ff:ff:ff:ff:ff:ff
inet 192.168.122.92/24 brd 192.168.122.255 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe55:9bd6/64 scope link
valid_lft forever preferred_lft forever
root@debian:~#
root@debian:~# ping 192.168.122.1 -c 3
PING 192.168.122.1 (192.168.122.1) 56(84) bytes of data.
64 bytes from 192.168.122.1: icmp_seq=1 ttl=64 time=0.276 ms
64 bytes from 192.168.122.1: icmp_seq=2 ttl=64 time=0.226 ms
64 bytes from 192.168.122.1: icmp_seq=3 ttl=64 time=0.259 ms
--- 192.168.122.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.226/0.253/0.276/0.027 ms
root@debian:~#
它是如何工作的……
当我们第一次安装并启动 libvirt
守护进程时,自动发生了几件事:
-
创建了一个新的 Linux 桥接,名称和 IP 地址在
/etc/libvirt/qemu/networks/default.xml
配置文件中定义。 -
dnsmasq
服务启动,并根据/var/lib/libvirt/dnsmasq/default.conf
文件中的配置进行设置。
让我们检查默认的 libvirt
桥接配置:
root@kvm:~# cat /etc/libvirt/qemu/networks/default.xml
<network>
<name>default</name>
<bridge name="virbr0"/>
<forward/>
<ip address="192.168.122.1" netmask="255.255.255.0">
<dhcp>
<range start="192.168.122.2" end="192.168.122.254"/>
</dhcp>
</ip>
</network>
root@kvm:~#
这是 libvirt 为我们创建的默认网络,指定了桥接名称、IP 地址以及 DHCP 服务器启动时使用的 IP 范围。我们将在本章稍后详细讨论 libvirt 网络;不过,我们在这里展示它,帮助你理解所有 IP 地址和桥接名称的来源。
我们可以通过运行以下命令查看主机操作系统上运行的 DHCP 服务器及其配置文件:
root@kvm:~# pgrep -lfa dnsmasq
38983 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf
root@kvm:~# cat /var/lib/libvirt/dnsmasq/default.conf
##WARNING: THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE
##OVERWRITTEN AND LOST. Changes to this configuration should be made using:
## virsh net-edit default
## or other application using the libvirt API.
##
## dnsmasq conf file created by libvirt
strict-order
user=libvirt-dnsmasq
pid-file=/var/run/libvirt/network/default.pid
except-interface=lo
bind-dynamic
interface=virbr0
dhcp-range=192.168.122.2,192.168.122.254
dhcp-no-override
dhcp-leasefile=/var/lib/libvirt/dnsmasq/default.leases
dhcp-lease-max=253
dhcp-hostsfile=/var/lib/libvirt/dnsmasq/default.hostsfile
addn-hosts=/var/lib/libvirt/dnsmasq/default.addnhosts
root@kvm:~#
从之前的配置文件中,可以看到 DHCP 服务的 IP 地址范围和虚拟桥接的名称与我们刚才看到的默认 libvirt 网络文件中的配置相匹配。
牢记这些内容,让我们回顾一下我们之前执行的所有操作:
在第 1 步中,我们安装了用户空间工具 brctl
,它用于创建、配置和检查 Linux 内核中的 Linux 桥接配置。
在第 2 步中,我们使用包含客户操作系统的自定义原始镜像创建了一个新的 KVM 实例。如果你已经完成了前面章节中的示例,这一步不需要执行。
在第 3 步中,我们调用了 bridge
工具列出所有可用的桥接设备。从输出中可以看到,目前有一个桥接,名为 virbr0
,是 libvirt 自动创建的。注意,在接口列下,我们可以看到 vnet0
接口。这是我们启动 KVM 实例时暴露给主机操作系统的虚拟网卡。这意味着虚拟机连接到了主机桥接。
在第 4 步中,我们首先将桥接关闭以便删除它,然后再次使用 brctl
命令删除该桥接,并确保它不再出现在主机操作系统中。
在第 5 步中,我们重新创建了桥接器并将其重新启用。我们这样做是为了演示创建新桥接器所需的步骤。
在第 6 步中,我们将相同的 IP 地址重新分配给桥接器,并列出了该地址。
在第 7 步和第 8 步中,我们列出了主机操作系统上的所有虚拟接口。由于当前服务器上只运行了一个 KVM 客户机,我们只看到了一个虚拟接口,即vnet0
。然后我们继续将虚拟网卡添加/连接到桥接器。
在第 9 步中,我们启用了桥接上的 STP。STP 是一种二层协议,有助于防止在存在冗余网络路径时出现网络环路。它在更大、更复杂的网络拓扑中尤其有用,在这种拓扑中,多个桥接器相互连接。
最后,在第 10 步中,我们通过控制台连接到 KVM 客户机,列出其接口配置,并确保我们可以在主机操作系统上 ping 到桥接器。为此,我们需要通过ifconfig eth0 up
命令启动客户机内部的网络接口,然后通过运行dhclient eth0
命令从主机上的dnsmasq
服务器获取 IP 地址。
还有更多内容...
还有一些其他有用的命令可以在 Linux 桥接器上使用。
我们已经知道,桥接器是根据其中包含的 MAC 地址转发数据帧的。要查看桥接器已知的 MAC 地址表,请运行以下命令:
root@kvm:~# brctl showmacs virbr0
port no mac addr is local? ageing timer
1 52:54:00:55:9b:d6 no 268.02
1 fe:54:00:55:9b:d6 yes 0.00
root@kvm:~#
从前面的输出中,我们可以看到桥接器在其唯一的端口上记录了两个 MAC 地址。第一个记录是非本地地址,属于 KVM 实例内部的网络接口。我们可以通过如下方式连接到 KVM 客户机来确认这一点:
root@kvm:~# virsh console kvm1
Connected to domain kvm1
Escape character is ^]
root@debian:~# ip a s eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 52:54:00:55:9b:d6 brd ff:ff:ff:ff:ff:ff
inet6 fe80::5054:ff:fe55:9bd6/64 scope link
valid_lft forever preferred_lft forever
root@debian:~#
第二个 MAC 地址是桥接器本身的地址,也是虚拟接口的 MAC 地址,该接口属于 KVM 虚拟机,并暴露给主机操作系统。要确认这一点,请运行以下命令:
root@kvm:~# ifconfig | grep "fe:54:00:55:9b:d6"
virbr0 Link encap:Ethernet HWaddr fe:54:00:55:9b:d6
vnet0 Link encap:Ethernet HWaddr fe:54:00:55:9b:d6
root@kvm:~#
当桥接器在其某个端口看到数据帧时,它会记录下时间,然后在一段时间内如果没有再次看到相同的 MAC 地址,它将从其 CAM 表中删除该记录。我们可以通过执行以下命令来设置桥接器在过期 MAC 地址条目之前的时间限制(以秒为单位):
root@kvm:~# brctl setageing virbr0 600
root@kvm:~#
brctl
命令有很好的文档说明;要列出所有可用的子命令,只需运行该命令而不带任何参数:
root@kvm:~# brctl
Usage: brctl [commands]
commands:
addbr <bridge> add bridge
delbr <bridge> delete bridge
addif <bridge> <device> add interface to bridge
delif <bridge> <device> delete interface from bridge
hairpin <bridge> <port> {on|off} turn hairpin on/off
setageing <bridge> <time> set ageing time
setbridgeprio <bridge> <prio> set bridge priority
setfd <bridge> <time> set bridge forward delay
sethello <bridge> <time> set hello time
setmaxage <bridge> <time> set max message age
setpathcost <bridge> <port> <cost> set path cost
setportprio <bridge> <port> <prio> set port priority
show [ <bridge> ] show a list of bridges
showmacs <bridge> show a list of mac addrs
showstp <bridge> show bridge stp info
stp <bridge> {on|off} turn stp on/off
root@kvm:~#
大多数 Linux 发行版都打包了brctl
工具,这也是我们在本教程中使用的工具。然而,为了使用最新版本,或者如果你的发行版没有该工具的包,我们可以通过克隆项目并使用git
构建工具的最新版本,然后进行配置和编译:
root@kvm:~# cd /usr/src/
root@kvm:/usr/src# apt-get update && apt-get install build-essential automake pkg-config git
root@kvm:/usr/src# git clone git://git.kernel.org/pub/scm/linux/kernel/git/shemminger/bridge-utils.git
Cloning into 'bridge-utils'...
remote: Counting objects: 654, done.
remote: Total 654 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (654/654), 131.72 KiB | 198.00 KiB/s, done.
Resolving deltas: 100% (425/425), done.
Checking connectivity... done.
root@kvm:/usr/src# cd bridge-utils/
root@kvm:/usr/src/bridge-utils# autoconf
root@kvm:/usr/src/bridge-utils# ./configure && make && make install
root@kvm:/usr/src/bridge-utils# brctl --version
bridge-utils, 1.5
root@kvm:/usr/src/bridge-utils#
从前面的输出中,我们可以看到我们首先克隆了git
仓库中的bridge-utils
项目,然后编译了源代码。
在 RedHat/CentOS 主机上,过程类似:
[root@centos ~]# cd /usr/src/
[root@centos src]#
[root@centos src]# yum groupinstall "Development tools"
[root@centos src]# git clone git://git.kernel.org/pub/scm/linux/kernel/git/shemminger/bridge-utils.git
Cloning into 'bridge-utils'...
remote: Counting objects: 654, done.
remote: Total 654 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (654/654), 131.72 KiB | 198.00 KiB/s, done.
Resolving deltas: 100% (425/425), done.
Checking connectivity... done.
[root@centos src]# cd bridge-utils
[root@centos bridge-utils]# autoconf
[root@centos bridge-utils]# ./configure && make && make install
[root@centos bridge-utils]# brctl --version
bridge-utils, 1.5
[root@centos bridge-utils]#
Open vSwitch
OVS 是另一种可以用来创建各种虚拟网络拓扑并将 KVM 实例连接到其上的软件桥接/交换设备。OVS 可以替代 Linux 桥接,并提供广泛的功能集,包括策略路由、访问控制列表 (ACLs)、服务质量 (QoS) 管控、流量监控、流量管理、VLAN 标签、GRE 隧道等功能。
在本教程中,我们将安装、配置并使用 OVS 桥接,将 KVM 实例连接到宿主操作系统,方式类似于我们在上一教程中使用 Linux 桥接所做的操作。
准备工作
为了使本教程能够顺利进行,我们需要确保以下几点:
-
如果存在,Linux 桥接将被删除,并安装 OVS
-
我们至少有一个 KVM 实例在运行
如何操作...
创建新的 OVS 桥接并连接 KVM 客户机的虚拟接口,按照以下步骤操作:
- 如果存在,删除现有的 Linux 桥接:
root@kvm:~# brctl show
bridge name bridge id STP enabled interfaces
virbr0 8000.fe5400559bd6 yes vnet0
root@kvm:~# ifconfig virbr0 down
root@kvm:~# brctl delbr virbr0
root@kvm:~# brctl show
bridge name bridge id STP enabled interfaces
root@kvm:~#
在某些 Linux 发行版中,使用 OVS 之前,卸载 Linux 桥接的内核模块是有帮助的。为此,执行 root@kvm:/usr/src# modprobe -r bridge
。
- 在 Ubuntu 上安装 OVS 软件包:
root@kvm:~# apt-get install openvswitch-switch
...
Setting up openvswitch-common (2.0.2-0ubuntu0.14.04.3) ...
Setting up openvswitch-switch (2.0.2-0ubuntu0.14.04.3) ...
openvswitch-switch start/running
...
root@kvm:~#
- 确保 OVS 进程正在运行:
root@kvm:~# pgrep -lfa switch
22255 ovsdb-server /etc/openvswitch/conf.db -vconsole:emer -vsyslog:err -vfile:info --remote=punix:/var/run/openvswitch/db.sock --private-key=db:Open_vSwitch,SSL,private_key --certificate=db:Open_vSwitch,SSL,certificate --bootstrap-ca-cert=db:Open_vSwitch,SSL,ca_cert --no-chdir --log-file=/var/log/openvswitch/ovsdb-server.log --pidfile=/var/run/openvswitch/ovsdb-server.pid --detach --monitor
22264 ovs-vswitchd: monitoring pid 22265 (healthy)
22265 ovs-vswitchd unix:/var/run/openvswitch/db.sock -vconsole:emer -vsyslog:err -vfile:info --mlockall --no-chdir --log-file=/var/log/openvswitch/ovs-vswitchd.log --pidfile=/var/run/openvswitch/ovs-vswitchd.pid --detach --monitor
root@kvm:~#
- 确保 OVS 内核模块已加载:
root@kvm:~# lsmod | grep switch
openvswitch 70989 0
gre 13796 1 openvswitch
vxlan 37611 1 openvswitch
libcrc32c 12644 1 openvswitch
root@kvm:~#
- 列出可用的 OVS 交换机:
root@kvm:~# ovs-vsctl show
e5164e3e-7897-4717-b766-eae1918077b0
ovs_version: "2.0.2"
root@kvm:~#
- 创建新的 OVS 交换机:
root@kvm:~# ovs-vsctl add-br virbr1
root@kvm:~# ovs-vsctl show
e5164e3e-7897-4717-b766-eae1918077b0
Bridge "virbr1"
Port "virbr1"
Interface "virbr1"
type: internal
ovs_version: "2.0.2"
root@kvm:~#
- 将正在运行的 KVM 实例的接口添加到 OVS 交换机:
root@kvm:~# ovs-vsctl add-port virbr1 vnet0
root@kvm:~# ovs-vsctl show
e5164e3e-7897-4717-b766-eae1918077b0
Bridge "virbr1"
Port "virbr1"
Interface "virbr1"
type: internal
Port "vnet0"
Interface "vnet0"
ovs_version: "2.0.2"
root@kvm:~#
- 在 OVS 交换机上配置 IP 地址:
root@kvm:~# ip addr add 192.168.122.1/24 dev virbr1
root@kvm:~# ip addr show virbr1
41: virbr1: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default
link/ether b2:52:e0:73:89:4e brd ff:ff:ff:ff:ff:ff
inet 192.168.122.1/24 scope global virbr1
valid_lft forever preferred_lft forever
inet6 fe80::b0a8:c2ff:fed4:bb3f/64 scope link
valid_lft forever preferred_lft forever
root@kvm:~#
- 在 KVM 客户机内配置 IP 地址,并确保可以连接到宿主操作系统(如果镜像没有控制台访问权限,则配置并使用 VNC 连接):
root@kvm:~# virsh console kvm1
Connected to domain kvm1
Escape character is ^]
root@debian:~# ifconfig eth0 up && ip addr add 192.168.122.210/24 dev eth0
root@debian:~# ip addr show eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 52:54:00:55:9b:d6 brd ff:ff:ff:ff:ff:ff
inet 192.168.122.210/24 scope global eth0
valid_lft forever preferred_lft forever
inet6 fe80::5054:ff:fe55:9bd6/64 scope link
valid_lft forever preferred_lft forever
root@debian:~# ping 192.168.122.1
PING 192.168.122.1 (192.168.122.1) 56(84) bytes of data.
64 bytes from 192.168.122.1: icmp_seq=1 ttl=64 time=0.711 ms
64 bytes from 192.168.122.1: icmp_seq=2 ttl=64 time=0.394 ms
64 bytes from 192.168.122.1: icmp_seq=3 ttl=64 time=0.243 ms
^C
--- 192.168.122.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2001ms
rtt min/avg/max/mdev = 0.243/0.449/0.711/0.195 ms
root@debian:~#
工作原理...
为了简化设置并避免冲突,最好先删除 Linux 桥接,然后再创建新的 OVS 交换机。我们在第 1 步中删除了桥接,并选择性地卸载了内核模块。
在第 2 步中,我们安装了 OVS 软件包,该软件包还启动了负责在宿主操作系统上创建和修改桥接/交换机的主要 OVS 守护进程 ovs-vswitchd
。
在第 4 步中,我们确保 OVS 内核模块已经加载,并在第 5 步中列出了宿主上所有可用的 OVS 交换机。
在步骤 6 和 7 中,我们创建一个新的 OVS 交换机并将 KVM 虚拟接口添加到该交换机。
安装软件包后启动的 ovsdb
-server 进程,如第 3 步中的输出所示,是一个数据库引擎,通过 JSON 远程过程调用 (RPC) 与主要 OVS 守护进程进行通信。ovsdb
服务器进程存储了信息,如交换机网络流、端口和 QoS
等。你可以通过以下命令查询数据库:
root@kvm:~# ovsdb-client list-dbs
Open_vSwitch
root@kvm:~# ovsdb-client list-tables
Table
-------------------------
Port
Manager
Bridge
Interface
SSL
IPFIX
Open_vSwitch
Queue
NetFlow
Mirror
QoS
Controller
Flow_Table
sFlow
Flow_Sample_Collector_Set
root@kvm:~# ovsdb-client dump Open_vSwitch
...
Port table
_uuid bond_downdelay bond_fake_iface bond_mode bond_updelay external_ids fake_bridge interfaces lacp mac name other_config qos statistics status tag trunks vlan_mode
------------------------------------ -------------- --------------- --------- ------------ ------------ ----------- -------------------------------------- ---- --- -------- ------------ --- ---------- ------ --- ------ ---------
9b4b743d-66b2-4779-9dd8-404b3aa55e18 0 false [] 0 {} false [e7ed4e2b-a73c-46c7-adeb-a203be56587c] [] [] "virbr1" {} [] {} {} [] [] []
f2a033aa-9072-4be3-808e-6e0fce67ce7b 0 false [] 0 {} false [86a10eed-698f-4ccc-b3b7-dd20c13e3ee3] [] [] "vnet0" {} [] {} {} [] [] []
...
root@kvm:~#
从前面的输出中可以看到,新的交换机 virbr1
和端口 vnet0
现在出现在查询 OVS 数据库时。
在步骤 8 和 9 中,我们为 OVS 交换机和 KVM 客户机分配了 IP 地址,并确保能够从虚拟机内部访问宿主桥接。
还有更多...
OVS 是一个相当复杂的软件交换机;在这个步骤中,我们只是粗略了解了它。接下来的几个步骤中,我们将结合使用 Linux 桥接和 OVS,只需在 libvirt 中做些许配置更改,我们将在操作中指出这些变化。
要从 OVS 开关中移除 KVM 虚拟接口,请执行以下命令:
root@kvm:~# ovs-vsctl del-port virbr1 vnet0
root@kvm:~#
要完全删除 OVS 开关,请运行以下命令:
root@kvm:~# ovs-vsctl del-br virbr1 && ovs-vsctl show
e5164e3e-7897-4717-b766-eae1918077b0
ovs_version: "2.0.2"
root@kvm:~#
要了解有关 OVS 的更多信息,请访问该项目网站:openvswitch.org/
。
配置 NAT 转发网络
当 libvirt
守护进程启动时,它会创建一个默认网络,该网络在 /etc/libvirt/qemu/networks/default.xml
配置文件中定义。当创建一个新的 KVM 客户机而没有指定任何网络选项时,它将使用默认网络与主机操作系统和其他客户机以及网络进行通信。默认的 libvirt
网络使用 网络地址转换(NAT)方法。NAT 通过修改 IP 数据报头中的 IP 地址来将一个 IP 地址空间映射到另一个地址空间。这在主机操作系统提供一个 IP 地址并允许多个客户机共享该地址以建立外部连接时特别有用。虚拟机的 IP 地址本质上会被转换为主机机器的 IP 地址。
默认的 NAT 转发网络定义并设置了一个 Linux 桥接,供客户机连接。在这个步骤中,我们将探索默认的 NAT 网络并了解用于定义它的 XML 属性。接着,我们将创建一个新的 NAT 网络,并将 KVM 客户机连接到它。
准备工作
本步骤中,我们需要以下内容:
-
一台安装了 libvirt 并正在运行守护进程的 Linux 主机。
-
主机操作系统上已安装
iptables
和iproute2
包。如果你是通过包管理安装 libvirt,那么iptables
和iproute2
很可能作为libvirt
的依赖包一起安装。如果你是从源代码编译安装 libvirt,可能需要手动安装这些包。 -
一个运行中的 KVM 实例。
如何操作...
要配置新的 NAT 网络并将 KVM 实例连接到它,请运行以下命令:
- 列出所有可用的网络:
root@kvm:~# virsh net-list --all
Name State Autostart Persistent
----------------------------------------------------------
default active yes yes
root@kvm:~#
- 导出默认网络的配置:
root@kvm:~# virsh net-dumpxml default
<network connections='1'>
<name>default</name>
<uuid>2ab5d22c-5928-4304-920e-bc43b8731bcf</uuid>
<forward mode='nat'>
<nat>
<port start='1024' end='65535'/>
</nat>
</forward>
<bridge name='virbr0' stp='on' delay='0'/>
<ip address='192.168.122.1' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.122.2' end='192.168.122.254'/>
</dhcp>
</ip>
</network>
root@kvm:~#
- 与默认网络的 XML 定义文件进行比较:
root@kvm:~# cat /etc/libvirt/qemu/networks/default.xml
<network>
<name>default</name>
<bridge name="virbr0"/>
<forward/>
<ip address="192.168.122.1" netmask="255.255.255.0">
<dhcp>
<range start="192.168.122.2" end="192.168.122.254"/>
</dhcp>
</ip>
</network>
root@kvm:~#
- 列出主机上所有运行中的实例:
root@kvm:~# virsh list --all
Id Name State
----------------------------------------------------
3 kvm1 running
root@kvm:~#
- 确保 KVM 实例连接到默认的 Linux 桥接网络:
root@kvm:~# brctl show
bridge name bridge id STP enabled interfaces
virbr0 8000.fe5400559bd6 yes vnet0
root@kvm:~#
- 创建一个新的 NAT 网络定义:
root@kvm:~# cat nat_net.xml
<network>
<name>nat_net</name>
<bridge name="virbr1"/>
<forward/>
<ip address="10.10.10.1" netmask="255.255.255.0">
<dhcp>
<range start="10.10.10.2" end="10.10.10.254"/>
</dhcp>
</ip>
</network>
root@kvm:~#
- 定义新网络:
root@kvm:~# virsh net-define nat_net.xml
Network nat_net defined from nat_net.xml
root@kvm:~# virsh net-list --all
Name State Autostart Persistent
----------------------------------------------------------
default active yes yes
nat_net inactive no yes
root@kvm:~#
- 启动新网络并启用自动启动:
root@kvm:~# virsh net-start nat_net
Network nat_net started
root@kvm:~# virsh net-autostart nat_net
Network nat_net marked as autostarted
root@kvm:~# virsh net-list
Name State Autostart Persistent
----------------------------------------------------------
default active yes yes
nat_net active yes yes
root@kvm:~#
- 获取有关新网络的更多信息:
root@kvm:~# virsh net-info nat_net
Name: nat_net
UUID: fba2ca2b-8ca7-4dbb-beee-14799ee04bc3
Active: yes
Persistent: yes
Autostart: yes
Bridge: virbr1
root@kvm:~#
- 编辑
kvm1
实例的 XML 定义,并更改源网络的名称:
root@kvm:~# virsh edit kvm1
...
<interface type='network'>
...
<source network='nat_net'/>
...
</interface>
...
Domain kvm1 XML configuration edited.
root@kvm:~#
- 重启 KVM 客户机:
root@kvm:~# virsh destroy kvm1
Domain kvm1 destroyed
root@kvm:~# virsh start kvm1
Domain kvm1 started
root@kvm:~#
- 列出主机上的所有软件桥接:
root@kvm:~# brctl show
bridge name bridge id STP enabled interfaces
virbr0 8000.000000000000 yes
virbr1 8000.525400ba8e2c yes virbr1-nic
vnet0
root@kvm:~#
- 连接到 KVM 实例并检查
eth0
接口的 IP 地址,确保可以连接到主机桥接(如果镜像未配置控制台访问,请改用 VNC 客户端):
root@kvm:~# virsh console kvm1
Connected to domain kvm1
Escape character is ^]
Debian GNU/Linux 8 debian ttyS0
debian login: root
Password:
...
root@debian:~# ip a s eth0 | grep inet
inet 10.10.10.92/24 brd 10.10.10.255 scope global eth0
inet6 fe80::5054:ff:fe55:9bd6/64 scope link
root@debian:~# ifconfig eth0 up && dhclient eth0
root@debian:~# ping 10.10.10.1 -c 3
PING 10.10.10.1 (10.10.10.1) 56(84) bytes of data.
64 bytes from 10.10.10.1: icmp_seq=1 ttl=64 time=0.313 ms
64 bytes from 10.10.10.1: icmp_seq=2 ttl=64 time=0.136 ms
64 bytes from 10.10.10.1: icmp_seq=3 ttl=64 time=0.253 ms
--- 10.10.10.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2000ms
rtt min/avg/max/mdev = 0.136/0.234/0.313/0.073 ms
root@debian:~#
- 在主机操作系统上,检查正在运行的 DHCP 服务:
root@kvm:~# pgrep -lfa dnsmasq
38983 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf
40098 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/nat_net.conf
root@kvm:~#
- 检查新桥接接口的 IP:
root@kvm:~# ip a s virbr1
43: virbr1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default
link/ether 52:54:00:ba:8e:2c brd ff:ff:ff:ff:ff:ff
inet 10.10.10.1/24 brd 10.10.10.255 scope global virbr1
valid_lft forever preferred_lft forever
root@kvm:~#
- 列出
iptables
规则以供 NAT 表使用:
root@kvm:~# iptables -L -n -t nat
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
Chain INPUT (policy ACCEPT)
target prot opt source destination
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
RETURN all -- 10.10.10.0/24 224.0.0.0/24
RETURN all -- 10.10.10.0/24 255.255.255.255
MASQUERADE tcp -- 10.10.10.0/24 !10.10.10.0/24 masq ports: 1024-65535
MASQUERADE udp -- 10.10.10.0/24 !10.10.10.0/24 masq ports: 1024-65535
MASQUERADE all -- 10.10.10.0/24 !10.10.10.0/24
RETURN all -- 192.168.122.0/24 224.0.0.0/24
RETURN all -- 192.168.122.0/24 255.255.255.255
MASQUERADE tcp -- 192.168.122.0/24 !192.168.122.0/24 masq ports: 1024-65535
MASQUERADE udp -- 192.168.122.0/24 !192.168.122.0/24 masq ports: 1024-65535
MASQUERADE all -- 192.168.122.0/24 !192.168.122.0/24
RETURN all -- 192.168.122.0/24 224.0.0.0/24
RETURN all -- 192.168.122.0/24 255.255.255.255
MASQUERADE tcp -- 192.168.122.0/24 !192.168.122.0/24 masq ports: 1024-65535
MASQUERADE udp -- 192.168.122.0/24 !192.168.122.0/24 masq ports: 1024-65535
MASQUERADE all -- 192.168.122.0/24 !192.168.122.0/24
root@kvm:~#
它是如何工作的……
我们从第一步开始,列出主机操作系统上所有可用的网络。从virsh
命令的输出中可以看到,当前只运行了一个默认网络。
在第二步中,我们检查了默认网络的配置。XML 定义使用了以下属性:
-
<network>
属性是根元素,指示 libvirt 我们正在定义一个网络。 -
<name>
元素指定网络的名称,且该名称必须唯一。 -
<uuid>
属性提供了虚拟网络的全球唯一标识符,若省略该属性,它将自动生成。 -
<forward>
元素及其模式属性将网络定义为连接到主机网络堆栈,使用 NAT。如果缺少此元素,libvirt 将创建一个隔离网络。 -
<nat>
子元素进一步定义了主机执行 NAT 时将使用的<port>
范围。 -
<bridge>
元素指定要创建的桥接、其名称和 STP 选项。 -
<ip>
属性定义了 DHCP 服务器为来宾虚拟机分配地址的 IP 范围。
在第三步中,我们查看该默认网络的配置文件。注意,有些属性缺失。Libvirt 会自动生成某些属性,并在适当的地方分配默认值。
在第 4 步和第 5 步中,我们确保有一个运行实例连接到默认的 Linux 桥接。
在第 6 步中,我们使用默认网络作为模板创建新的网络定义。我们更改了网络的名称并定义了新的 IP 范围。
准备好新的网络定义文件后,在第 7 步和第 8 步中,我们定义了新网络,启动它,并确保在libvirt
守护进程启动时它会自动启动,以防服务器重启。
在第 9 步获取了有关新创建的网络的更多信息后,我们继续在第 10 步编辑 KVM 来宾的 XML 定义。为了让虚拟机成为新网络的一部分,我们只需更新<source network>
元素。
在第 11 步重新启动 KVM 来宾后,我们继续在第 12 步列出主机操作系统上所有可用的软件桥接。请注意,现在我们有了两个桥接,其中新的桥接连接了虚拟机的虚拟接口vnet0
。
然后,我们连接到运行中的 KVM 来宾,确保其 eth0 网络接口从主机上的 DHCP 服务器获取到了 IP 地址,且该 IP 属于我们先前配置的地址范围。我们还通过ping
命令确保能够连接到主机桥接。
返回主机操作系统,在第 14 步和第 15 步中,我们检查了正在运行的 DHCP 服务。注意,从pgrep
命令的输出中,我们现在有两个dnsmasq
进程在运行:每个网络各一个。
NAT 转发是通过设置 iptables 规则来实现的,如第 18 步所示。每次我们定义并启动一个新的 NAT 网络时,libvirt 都会在 iptables 中创建所需的规则。从第 18 步的输出中,我们可以观察到有两组 NAT 规则,每个运行的 NAT 网络都有一组。
配置桥接网络
使用完全桥接后,我们可以直接将 KVM 客户机连接到主机网络,而不使用 NAT。然而,这种设置需要为每个虚拟机分配一个属于主机子网的 IP 地址。如果你无法分配这么多 IP 地址,可以考虑使用之前提到的 配置 NAT 转发网络 的设置。在这种网络模式下,虚拟机仍然使用主机操作系统的桥接来进行连接;然而,桥接会控制将用于虚拟机的物理接口。
准备工作
对于这个配方,我们需要以下内容:
-
至少有两个物理接口的服务器
-
使用 libvirt 配置和启动 KVM 实例的能力
-
一个正在运行的 KVM 实例
如何操作...
要定义一个新的桥接网络并将客户机连接到它,请按照以下步骤操作:
- 关闭我们将要桥接的接口:
root@kvm:~# ifdown eth1
root@kvm:~#
- 如果你的主机操作系统是 Debian/Ubuntu,请编辑主机上的网络配置文件,并用以下内容替换
eth1
块:
root@kvm:~# vim /etc/network/interfaces
...
auto virbr2
iface virbr2 inet static
address 192.168.1.2
netmask 255.255.255.0
network 192.168.1.0
broadcast 192.168.1.255
gateway 192.168.1.1
bridge_ports eth1
bridge_stp on
bridge_maxwait 0
...
root@kvm:~#
- 如果使用 RedHat/CentOS 发行版,请编辑以下两个文件:
root@kvm:~# cat /etc/sysconfig/ifcfg-eth1
DEVICE=eth1
NAME=eth1
NM_CONTROLLED=yes
ONBOOT=yes
TYPE=Ethernet
BRIDGE=virbr2
root@kvm:~# cat /etc/sysconfig/ifcfg-bridge_net
DEVICE=virbr2
NAME=virbr2
NM_CONTROLLED=yes
ONBOOT=yes
TYPE=Bridge
STP=on
IPADDR=192.168.1.2
NETMASK=255.255.255.0
GATEWAY=192.168.1.1
root@kvm:~#
- 启动新接口:
root@kvm:~# ifup virbr2
root@kvm:~#
- 禁止将来自客户机虚拟机的包发送到
iptables
:
root@kvm:~# sysctl -w net.bridge.bridge-nf-call-iptables=0
net.bridge.bridge-nf-call-iptables = 0
root@kvm:~# sysctl -w net.bridge.bridge-nf-call-iptables=0
net.bridge.bridge-nf-call-iptables = 0
root@kvm:~# sysctl -w net.bridge.bridge-nf-call-arptables=0
net.bridge.bridge-nf-call-arptables = 0
root@kvm:~#
- 列出主机上的所有桥接:
root@kvm:~# # brctl show
bridge name bridge id STP enabled interfaces
virbr0 8000.000000000000 yes
virbr2 8000.000a0ac60210 yes eth1
root@kvm:~#
- 编辑 KVM 实例的 XML 定义:
root@kvm:~# virsh edit kvm1
...
<interface type='bridge'>
<source bridge='virbr2'/>
</interface>
...
Domain kvm1 XML configuration edited.
root@kvm:~#
- 重启 KVM 实例:
root@kvm:~# virsh destroy kvm1
Domain kvm1 destroyed
root@kvm:~# virsh start kvm1
Domain kvm1 started
root@kvm:~#
它是如何工作的...
要设置桥接网络,在第 1 步和第 2 步中,我们首先将物理接口(本例中为 eth1
)关闭,以便将其奴役(使其成为我们将要创建的新桥的一部分)。然后,我们创建网络配置,指定新的桥接和将成为该桥接一部分的物理接口。这样,实际上将物理接口上配置的子网映射到桥接上。如果你的服务器只有一个网络接口,仍然可以将其奴役。然而,你将需要另一种连接到服务器的方式,因为一旦关闭主接口,你将失去连接,且通过 SSH 连接进行故障排除可能变得不可能。
一旦新桥接配置完成,我们将在第 3 步启动它。
在第 4 步中,我们指示内核不要对连接到 Linux 桥接的虚拟客户机发出的任何流量应用 iptable 规则,因为我们不使用任何 NAT 规则。
在新接口启动后,我们可以在第 5 步看到桥接和附加到它的物理接口。
在步骤 6 中,我们编辑kvm1
实例的 XML 定义,在其中指定我们希望使用的网络类型;对于本教程,这是桥接网络。如果你还记得配置 NAT 转发网络教程,我们使用的是网络类型而非桥接,并且我们指定了libvirt
网络名称,而不是桥接名称。
最后,在步骤 7 中重新启动 KVM 实例后,客户操作系统现在应该能够在不使用 NAT 的情况下访问属于同一子网的其他实例。
配置 PCI 直通网络
KVM 虚拟化管理程序支持将主机操作系统中的 PCI 设备直接附加到虚拟机上。我们可以利用这个功能将网络接口直接附加到客户操作系统,而无需使用 NAT 或软件桥接。
在本教程中,我们将连接支持 SR-IOV 单根 I/O 虚拟化(SR-IOV)的网络接口卡(NIC),将其从主机附加到 KVM 客户机。SR-IOV 是一种规范,允许外设组件互连快速(PCIe)设备表现为多个独立的物理设备,可以在同一主机上的多个虚拟机之间共享,绕过虚拟化层,从而实现原生网络速度。像 Amazon AWS 这样的云服务提供商通过 API 调用将这一功能暴露给其 EC2 计算实例。
准备工作
为了完成本教程,我们需要以下内容:
-
支持 SR-IOV 的 NIC 的物理主机
-
具有物理服务器连接的
802.1Qbh
兼容交换机 -
配备 Intel VT-d 或 AMD IOMMU 扩展的 CPU
-
安装了
libvirt
的 Linux 主机,准备好部署 KVM 实例
操作步骤...
设置新的 PCI 直通网络,按照以下步骤进行:
- 枚举主机操作系统上的所有设备:
root@kvm:~# virsh nodedev-list --tree
computer
|
+- net_lo_00_00_00_00_00_00
+- net_ovs_system_0a_c6_62_34_19_b4
+- net_virbr1_nic_52_54_00_ba_8e_2c
+- net_vnet0_fe_54_00_55_9b_d6
...
|
+- pci_0000_00_03_0
| |
| +- pci_0000_03_00_0
| | |
| | +- net_eth0_58_20_b1_00_b8_61
| |
| +- pci_0000_03_00_1
| |
| +- net_eth1_58_20_b1_00_b8_61
|
...
root@kvm:~#
- 列出所有 PCI
以太网
适配器:
root@kvm:~# lspci | grep Ethernet
03:00.0 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)
03:00.1 Ethernet controller: Intel Corporation 82599ES 10-Gigabit SFI/SFP+ Network Connection (rev 01)
root@kvm:~#
- 获取关于
eth1
设备使用的 NIC 的更多信息:
root@kvm:~# virsh nodedev-dumpxml pci_0000_03_00_1
<device>
<name>pci_0000_03_00_1</name>
<path>/sys/devices/pci0000:00/0000:00:03.0/0000:03:00.1</path>
<parent>pci_0000_00_03_0</parent>
<driver>
<name>ixgbe</name>
</driver>
<capability type='pci'>
<domain>0</domain>
<bus>3</bus>
<slot>0</slot>
<function>1</function>
<product id='0x10fb'>82599ES 10-Gigabit SFI/SFP+ Network Connection</product>
<vendor id='0x8086'>Intel Corporation</vendor>
</capability>
</device>
root@kvm:~#
- 将域、总线、插槽和功能值转换为十六进制:
root@kvm:~# printf %x 0
0
root@kvm:~# printf %x 3
3
root@kvm:~# printf %x 0
0
root@kvm:~# printf %x 1
1
root@kvm:~#
- 创建新的
libvirt
网络定义文件:
root@kvm:~# cat passthrough_net.xml
<network>
<name>passthrough_net</name>
<forward mode='hostdev' managed='yes'>
<pf dev='eth1'/>
</forward>
</network>
root@kvm:~#
- 定义、启动并启用新的
libvirt
网络的自动启动:
root@kvm:~# virsh net-define passthrough_net.xml
Network passthrough_net defined from passthrough_net.xml
root@kvm:~# virsh net-start passthrough_net
Network passthrough_nett started
root@kvm:~# virsh net-autostart passthrough_net
Network passthrough_net marked as autostarted
root@kvm:~# virsh net-list
Name State Autostart Persistent
----------------------------------------------------------
default active yes yes
passthrough_net active yes yes
root@kvm:~#
- 编辑 KVM 客户机的 XML 定义:
root@kvm:~# virsh edit kvm1
...
<devices>
...
<interface type='hostdev' managed='yes'>
<source>
<address type='pci' domain='0x0' bus='0x00' slot='0x07' function='0x0'/>
</source>
<virtualport type='802.1Qbh' />
</interface>
<interface type='network'>
<source network='passthrough_net'>
</interface>
...
</devices>
...
Domain kvm1 XML configuration edited.
root@kvm:~#
- 重启 KVM 实例:
root@kvm:~# virsh destroy kvm1
Domain kvm1 destroyed
root@kvm:~# virsh start kvm1
Domain kvm1 started
root@kvm:~#
- 列出 SR-IOV NIC 提供的虚拟功能(VFs):
root@kvm:~# virsh net-dumpxml passthrough_net
<network connections='1'>
<name>passthrough_net</name>
<uuid>a4233231-d353-a112-3422-3451ac78623a</uuid>
<forward mode='hostdev' managed='yes'>
<pf dev='eth1'/>
<address type='pci' domain='0x0000' bus='0x02' slot='0x10' function='0x1'/>
<address type='pci' domain='0x0000' bus='0x02' slot='0x10' function='0x3'/>
<address type='pci' domain='0x0000' bus='0x02' slot='0x10' function='0x5'/>
<address type='pci' domain='0x0000' bus='0x02' slot='0x10' function='0x7'/>
<address type='pci' domain='0x0000' bus='0x02' slot='0x11' function='0x1'/>
<address type='pci' domain='0x0000' bus='0x02' slot='0x11' function='0x3'/>
<address type='pci' domain='0x0000' bus='0x02' slot='0x11' function='0x5'/>
</forward>
</network>
root@kvm:~#
工作原理...
为了将主机操作系统中的 PCI NIC 直接附加到客户虚拟机,我们首先需要收集一些关于设备的硬件信息,比如域、总线、插槽和功能 ID。在步骤 1 中,我们收集了主机服务器上所有可用设备的信息。我们希望使用 eth1 网络接口作为本例中的设备,因此我们从输出中记下了唯一的 PCI 标识符——pci_0000_03_00_1
。
为了确认这是我们希望暴露给客户机的 NIC,我们在步骤 2 中列出了所有 PCI 设备。从输出中可以看到,PCI ID 是相同的03:00.1
。
使用第 1 步中的 PCI ID,我们继续在第 3 步收集有关 NIC 的更多信息。请注意,0000_03_00_1 ID
被分解为域 ID、总线 ID、插槽 ID 和功能 ID,如 XML 属性所示。我们将在第 7 步使用这些 ID;但是,我们首先需要将它们转换为十六进制格式,这将在第 4 步完成。
在第 5 和第 6 步中,我们为我们的虚拟机定义了一个新的libvirt
网络,启动网络,并启用自动启动,以防主机服务器重新启动。如果你完成了本章中的其他食谱,你应该已经熟悉我们刚刚创建的网络 XML 定义文件中的大多数属性。<forward>
属性中定义的hostdev
模式指示libvirt
新网络将使用 PCI 直通。<forward>
属性中指定的managed=yes
参数告诉libvirt
在将 PCI 设备传递给虚拟机之前,首先将其从主机中分离,并在虚拟机终止后重新将其附加回主机。最后,<pf>
子元素指定将被虚拟化并呈现给虚拟机的物理接口。
有关可用 XML 属性的更多信息,请参考libvirt.org/formatdomain.html
。
在第 7 步中,我们编辑 KVM 实例的 XML 定义,指定在第 3 步中获取的 PCI ID,并定义一个将使用我们在第 5 和第 6 步中创建的新的 PCI 直通网络的接口。
我们在第 8 步重启 KVM 实例,最后验证物理 PCI NIC 设备现在已成为我们之前定义的新直通网络的一部分。请注意,出现了多个 PCI 类型的设备。这是因为我们使用的 PCI 直通设备支持 SR-IOV。所有将使用此网络的 KVM 虚拟机现在都可以通过分配其中一个列出的虚拟 PCI 设备来直接使用主机的 NIC。
操作网络接口
Libvirt 提供了一种方便的方式,通过已经熟悉的 XML 定义语法来管理主机上的网络接口。我们可以使用virsh
命令来定义、配置和删除 Linux 桥接,并获取有关现有网络接口的更多信息,就像你在本章中已经看到的那样。
在本食谱中,我们将定义一个新的 Linux 桥接、创建它,并最终使用virsh
将其删除。如果你还记得之前的食谱,我们可以通过brctl
等工具来操作 Linux 桥接。然而,使用 libvirt,我们有一种通过编写定义文件并使用 API 绑定来以编程方式控制它的方法,正如我们在第七章《使用 Python 构建和管理 KVM 实例》中将看到的那样。
准备工作
对于这个食谱,我们需要以下材料:
-
主机上安装了
libvirt
包 -
具有桥接内核模块的 Linux 主机
操作步骤...
要使用 libvirt 创建新的桥接接口,请运行以下命令:
- 创建一个新的桥接接口配置文件:
root@kvm:~# cat test_bridge.xml
<interface type='bridge' name='test_bridge'>
<start mode="onboot"/>
<protocol family='ipv4'>
<ip address='192.168.1.100' prefix='24'/>
</protocol>
<bridge>
<interface type='ethernet' name='vnet0'>
<mac address='fe:54:00:55:9b:d6'/>
</interface>
</bridge>
</interface>
root@kvm:~#
- 定义新的接口:
root@kvm:~# virsh iface-define test_bridge.xml
Interface test_bridge defined from test_bridge.xml
root@kvm:~#
- 列出所有 libvirt 知道的接口:
root@kvm:~# virsh iface-list --all
Name State MAC Address
---------------------------------------------------
bond0 active 58:20:b1:00:b8:61
bond0.129 active bc:76:4e:20:10:6b
bond0.229 active bc:76:4e:20:17:7e
eth0 active 58:20:b1:00:b8:61
eth1 active 58:20:b1:00:b8:61
lo active 00:00:00:00:00:00
test_bridge inactive
root@kvm:~#
- 启动新的桥接接口:
root@kvm:~# virsh iface-start test_bridge
Interface test_bridge started
root@kvm:~# virsh iface-list --all | grep test_bridge
test_bridge active 4a:1e:48:e1:e7:de
root@kvm:~#
- 列出主机上的所有桥接设备:
root@kvm:~# brctl show
bridge name bridge id STP enabled interfaces
test_bridge 8000.000000000000 no
virbr0 8000.000000000000 yes
virbr1 8000.525400ba8e2c yes virbr1-nic
vnet0
root@kvm:~#
- 检查新桥接的活动网络配置:
root@kvm:~# ip a s test_bridge
46: test_bridge: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default
link/ether 4a:1e:48:e1:e7:de brd ff:ff:ff:ff:ff:ff
inet 192.168.1.100/24 brd 192.168.1.255 scope global test_bridge
valid_lft forever preferred_lft forever
inet6 fe80::481e:48ff:fee1:e7de/64 scope link
valid_lft forever preferred_lft forever
root@kvm:~#
- 获取桥接的 MAC 地址:
root@kvm:~# virsh iface-mac test_bridge
4a:1e:48:e1:e7:de
root@kvm:~#
- 通过提供 MAC 地址获取桥接的名称:
root@kvm:~# virsh iface-name 4a:1e:48:e1:e7:de
test_bridge
root@kvm:~#
- 销毁接口,方法如下:
root@kvm:~# virsh iface-destroy test_bridge
Interface test_bridge destroyed
root@kvm:~# virsh iface-list --all | grep test_bridge
test_bridge inactive
root@kvm:~# virsh iface-undefine test_bridge
Interface test_bridge undefined
root@kvm:~# virsh iface-list --all | grep test_bridge
root@kvm:~#
它是如何工作的...
在第 1 步中,我们为新的网络接口编写 XML 定义。我们指定桥接(bridge)作为类型,为接口指定一个 IP 地址,并可选地指定一个 MAC 地址。
在第 2 步和第 3 步中,我们定义新的桥接接口并列出它。定义接口并不会自动使其生效,因此我们在第 4 步中激活它。
启动桥接会在主机上创建实际的接口,如第 5 步所示。
在第 6 步中,我们确认分配给桥接的 IP 地址和 MAC 地址确实是我们在第 1 步中指定的。
在第 7 步和第 8 步中,我们使用 virsh
工具获得接口的名称和 MAC 地址,最后在第 9 步中,我们移除 bridge
接口。
第四章:迁移 KVM 实例
在本章中,我们将演示以下 libvirt KVM 迁移概念:
-
使用 iSCSI 存储池进行手动离线迁移
-
使用 GlusterFS 共享卷进行手动离线迁移
-
使用共享存储通过 virsh 命令进行在线迁移
-
使用 virsh 命令和本地镜像进行离线迁移
-
使用 virsh 命令和本地镜像进行在线迁移
介绍
迁移 KVM 实例是将客户虚拟机的内存、CPU 状态以及与之附加的虚拟化设备的状态发送到不同服务器的过程。迁移 KVM 实例是一个相对复杂的过程,具体取决于虚拟机使用的后端存储(即目录、镜像文件、iSCSI 卷、共享存储或存储池)、网络基础设施和附加到客户机的块设备数量。就 libvirt 而言,以下是两种迁移类型:
-
离线迁移涉及实例的停机。它的工作方式是先暂停客户虚拟机,然后将客户虚拟机的内存镜像复制到目标虚拟化主机。然后,KVM 虚拟机在目标主机上恢复运行。如果虚拟机的文件系统不在共享存储中,它还需要被移动到目标服务器。
-
实时迁移通过在没有感知停机的情况下移动实例的当前状态来工作,保持内存和 CPU 寄存器状态不变。
广义来说,离线迁移涉及以下步骤:
-
停止实例
-
将其 XML 定义导出到文件中
-
将客户文件系统镜像复制到目标服务器(如果未使用共享存储)
-
在目标主机上定义实例并启动它
相比之下,在线迁移需要共享存储,如 NFS 或 GlusterFS,避免了将客户机文件系统传输到目标服务器的需求。迁移的速度取决于源实例的内存更新/写入频率、内存的大小,以及源主机和目标主机之间的可用网络带宽。
实时迁移遵循以下过程:
-
原始虚拟机在内存内容传输到目标主机时继续运行
-
Libvirt 监控已传输内存页的任何变化,如果它们已被更新,则会重新传输它们
-
一旦内存内容传输到目标主机,原始实例将被暂停,目标主机上的新实例将被恢复
在本章中,我们将通过存储池的帮助,使用 iSCSI 和 GlusterFS 执行离线和实时迁移。
使用 iSCSI 存储池进行手动离线迁移
在此方案中,我们将设置一个 iSCSI 目标,配置一个存储池,并使用附加的 iSCSI 块设备作为后端卷创建一个新的 KVM 实例。接着,我们将执行实例的手动离线迁移到新主机。
准备工作
对于此方案,我们需要以下内容:
-
两台已安装并配置了
libvirt
和qemu
的服务器,分别命名为kvm1
和kvm2
。这两台主机必须能够通过 SSH 密钥和简短的主机名互相连接。 -
一台带有可用块设备的服务器,该块设备将作为 iSCSI 目标导出,并且可以从两台
libvirt
服务器访问。如果没有可用块设备,请参阅此食谱中的 更多信息... 部分,了解如何使用常规文件创建一个。此食谱中的 iSCSI 目标服务器名称是iscsi_target
。 -
需要连接到 Linux 仓库以安装客户机操作系统。
如何操作……
要执行使用 iSCSI 存储池的 KVM 客户机的手动脱机迁移,请按照以下步骤进行:
- 在 iSCSI 目标主机上安装
iscsitarget
包和内核模块包:
root@iscsi_target:~# apt-get update && apt-get install iscsitarget iscsitarget-dkms
- 启用目标功能:
root@iscsi_target:~# sed -i 's/ISCSITARGET_ENABLE=false/ISCSITARGET_ENABLE=true/g' /etc/default/iscsitarget
root@iscsi_target:~# cat /etc/default/iscsitarget
ISCSITARGET_ENABLE=true
ISCSITARGET_MAX_SLEEP=3
# ietd options
# See ietd(8) for details
ISCSITARGET_OPTIONS=""
root@iscsi_target:~#
- 配置块设备以通过 iSCSI 导出:
root@iscsi_target:~# cat /etc/iet/ietd.conf
Target iqn.2001-04.com.example:kvm
Lun 0 Path=/dev/loop1,Type=fileio
Alias kvm_lun
root@iscsi_target:~#
用您要通过 iSCSI 导出的块设备替换 /dev/loop1
设备。
- 重启 iSCSI 目标服务:
root@iscsi_target:~# /etc/init.d/iscsitarget restart
* Removing iSCSI enterprise target devices: [ OK ]
* Stopping iSCSI enterprise target service: [ OK ]
* Removing iSCSI enterprise target modules: [ OK ]
* Starting iSCSI enterprise target service [ OK ]
root@iscsi_target:~#
- 在两个
libvirt
主机上安装 iSCSI 启动器:
root@kvm1/2:~# apt-get update && apt-get install open-iscsi
- 在两个
libvirt
服务器上启用 iSCSI 启动器服务并启动它:
root@kvm1/2:~# sed -i 's/node.startup = manual/node.startup = automatic/g' /etc/iscsi/iscsid.conf
root@kvm1/2:~# /etc/init.d/open-iscsi restart
- 从两个
libvirt
启动器主机上,通过查询 iSCSI 目标服务器,列出可用的 iSCSI 卷:
root@kvm1/2:~# iscsiadm -m discovery -t sendtargets -p iscsi_target
10.184.226.74:3260,1 iqn.2001-04.com.example:kvm
172.99.88.246:3260,1 iqn.2001-04.com.example:kvm
192.168.122.1:3260,1 iqn.2001-04.com.example:kvm
root@kvm:~#
- 在其中一台
libvirt
服务器上创建新的 iSCSI 存储池:
root@kvm1:~# cat iscsi_pool.xml
<pool type="iscsi">
<name>iscsi_pool</name>
<source>
<host name="iscsi_target.example.com"/>
<device path="iqn.2001-04.com.example:kvm"/>
</source>
<target>
<path>/dev/disk/by-path</path>
</target>
</pool>
root@kvm1:~# virsh pool-define iscsi_pool.xml
Pool iscsi_pool defined from iscsi_pool.xml
root@kvm1:~# virsh pool-list --all
Name State Autostart
-------------------------------------------
iscsi_pool inactive no
root@kvm1:~#
确保将 iSCSI 目标服务器的主机名替换为适合您环境的主机名。指定 iSCSI 目标主机时,可以使用主机名和 IP 地址。
- 启动新的 iSCSI 存储池:
root@kvm1:~# virsh pool-start iscsi_pool
Pool iscsi_pool started
root@kvm1:~# virsh pool-list --all
Name State Autostart
-------------------------------------------
iscsi_pool active no
root@kvm1:~#
- 列出来自存储池的可用 iSCSI 卷并获取更多信息:
root@kvm1:~# virsh vol-list --pool iscsi_pool
Name Path
------------------------------------------------------------------------------
unit:0:0:0 /dev/disk/by-path/ip-10.184.22.74:3260-iscsi-iqn.2001-04.com.example:kvm-lun-0
root@kvm1:~# virsh vol-info unit:0:0:0 --pool iscsi_pool
Name: unit:0:0:0
Type: block
Capacity: 10.00 GiB
Allocation: 10.00 GiB
root@kvm1:~#
- 列出 iSCSI 会话及其相关的块设备:
root@kvm1:~# iscsiadm -m session
tcp: [5] 10.184.226.74:3260,1 iqn.2001-04.com.example:kvm
root@kvm1:~# ls -la /dev/disk/by-path/
total 0
drwxr-xr-x 2 root root 100 Apr 12 16:24 .
drwxr-xr-x 6 root root 120 Mar 21 22:14 ..
lrwxrwxrwx 1 root root 9 Apr 12 16:24 ip-10.184.22.74:3260-iscsi-iqn.2001-04.com.example:kvm-lun-0 -> ../../sdf
root@kvm1:~#
- 检查 iSCSI 块设备的分区方案:
root@kvm1:~# fdisk -l /dev/disk/by-path/ip-10.184.22.74\:3260-iscsi-iqn.2001-04.com.example\:kvm-lun-0
Disk /dev/disk/by-path/ip-10.184.22.74:3260-iscsi-iqn.2001-04.com.example:kvm-lun-0: 10.7 GB, 10737418240 bytes
64 heads, 32 sectors/track, 10240 cylinders, total 20971520 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000
Disk /dev/disk/by-path/ip-10.184.22.74:3260-iscsi-iqn.2001-04.com.example:kvm-lun-0 doesn't contain a valid partition table
root@kvm1:~#
- 使用 iSCSI 卷和存储池安装新的 KVM 客户机:
root@kvm1:~# virt-install --name iscsi_kvm --ram 1024 --extra-args="text console=tty0 utf8 console=ttyS0,115200" --graphics vnc,listen=0.0.0.0 --hvm --location=http://ftp.us.debian.org/debian/dists/stable/main/installer-amd64/ --disk vol=iscsi_pool/unit:0:0:0
Starting install...
Retrieving file MANIFEST... | 3.3 kB 00:00 ...
Retrieving file linux...
...
root@kvm1:~# virsh console iscsi_kvm
...
Requesting system reboot
[ 305.315002] reboot: Restarting system
root@kvm1:~#
- 刷新分区表列表并检查安装后的新块设备:
root@kvm1:~# partprobe
root@kvm1:~# ls -la /dev/disk/by-path/
total 0
drwxr-xr-x 2 root root 160 Apr 12 16:36 .
drwxr-xr-x 6 root root 120 Mar 21 22:14 ..
lrwxrwxrwx 1 root root 9 Apr 12 16:36 ip-10.184.22.74:3260-iscsi-iqn.2001-04.com.example:kvm-lun-0 -> ../../sdf
lrwxrwxrwx 1 root root 10 Apr 12 16:36 ip-10.184.22.74:3260-iscsi-iqn.2001-04.com.example:kvm-lun-0-part1 -> ../../sdf1
lrwxrwxrwx 1 root root 10 Apr 12 16:36 ip-10.184.22.74:3260-iscsi-iqn.2001-04.com.example:kvm-lun-0-part2 -> ../../sdf2
lrwxrwxrwx 1 root root 10 Apr 12 16:36 ip-10.184.22.74:3260-iscsi-iqn.2001-04.com.example:kvm-lun-0-part5 -> ../../sdf5
root@kvm1:~# fdisk -l /dev/sdf
Disk /dev/sdf: 10.7 GB, 10737418240 bytes
255 heads, 63 sectors/track, 1305 cylinders, total 20971520 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x37eb1540
Device Boot Start End Blocks Id System
/dev/sdf1 * 2048 20013055 10005504 83 Linux
/dev/sdf2 20015102 20969471 477185 5 Extended
/dev/sdf5 20015104 20969471 477184 82 Linux swap / Solaris
root@kvm1:~#
- 启动新的 KVM 客户机并确保它正在运行,并且可以通过 VNC 客户端连接到它:
root@kvm1:~# virsh start iscsi_kvm
Domain iscsi_kvm started
root@kvm1:~# virsh list --all
Id Name State
----------------------------------------------------
19 iscsi_kvm running
root@kvm1:~#
- 若要手动迁移实例到新主机,首先停止虚拟机和 iSCSI 存储池:
root@kvm1:~# virsh destroy iscsi_kvm
Domain iscsi_kvm destroyed
root@kvm1:~# virsh pool-destroy iscsi_pool
Pool iscsi_pool destroyed
root@kvm1:~# iscsiadm -m session
iscsiadm: No active sessions.
root@kvm1:~#
- 将 KVM 实例的 XML 配置转储到文件并检查:
root@kvm1:~# virsh dumpxml iscsi_kvm > iscsi_kvm.xml
root@kvm1:~# cat iscsi_kvm.xml
<domain type='kvm'>
<name>iscsi_kvm</name>
<uuid>306e05ed-e398-ef33-d6e2-3708e90b89a6</uuid>
<memory unit='KiB'>1048576</memory>
<currentMemory unit='KiB'>1048576</currentMemory>
<vcpu placement='static'>1</vcpu>
<os>
<type arch='x86_64' machine='pc-i440fx-trusty'>hvm</type>
<boot dev='hd'/>
</os>
<features>
<acpi/>
<apic/>
<pae/>
</features>
<clock offset='utc'/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='block' device='disk'>
<driver name='qemu' type='raw'/>
<source dev='/dev/disk/by-path/ip-10.184.22.74:3260-iscsi-iqn.2001-04.com.example:kvm-lun-0'/>
<target dev='hda' bus='ide'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
<controller type='usb' index='0'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
</controller>
<controller type='pci' index='0' model='pci-root'/>
<controller type='ide' index='0'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
</controller>
<interface type='network'>
<mac address='52:54:00:8b:b8:e3'/>
<source network='default'/>
<model type='rtl8139'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</interface>
<serial type='pty'>
<target port='0'/>
</serial>
<console type='pty'>
<target type='serial' port='0'/>
</console>
<input type='mouse' bus='ps2'/>
<input type='keyboard' bus='ps2'/>
<graphics type='vnc' port='-1' autoport='yes' listen='0.0.0.0'>
<listen type='address' address='0.0.0.0'/>
</graphics>
<video>
<model type='cirrus' vram='9216' heads='1'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
</video>
<memballoon model='virtio'>
<address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
</memballoon>
</devices>
</domain>
root@kvm1:~#
- 从
kvm1
主机到kvm2
主机远程创建 iSCSI 存储池:
root@kvm1:~# virsh --connect qemu+ssh://kvm2/system pool-define iscsi_pool.xml
Pool iscsi_pool defined from iscsi_pool.xml
root@kvm1:~#
如果您在两个 KVM 主机之间未使用 SSH 密钥连接,系统会提示您在执行 libvirt
命令之前提供密码。我们建议在迁移的 libvirt
主机上使用 SSH 密钥。
- 在
kvm2
服务器上远程启动 iSCSI 存储池并确保它正在运行:
root@kvm1:~# virsh --connect qemu+ssh://kvm2/system pool-start iscsi_pool
Pool iscsi_pool started
root@kvm1:~# virsh --connect qemu+ssh://kvm2/system pool-list --all
Name State Autostart
-------------------------------------------
iscsi_pool active no
root@kvm1:~#
您也可以通过 SSH 登录到 kvm2
服务器,执行所有存储池和卷操作。我们远程操作是为了演示这一概念。
- 从源主机远程列出
kvm2
节点上的可用 iSCSI 卷:
root@kvm1:~# virsh --connect qemu+ssh://kvm2/system vol-list --pool iscsi_pool
Name Path
--------------------------------------------------------------------
unit:0:0:0 /dev/disk/by-path/ip-10.184.22.74:3260-iscsi-iqn.2001-04.com.example:kvm-lun-0
root@kvm1:~#
- SSH 登录到第二台 KVM 服务器,并确保 iSCSI 块设备现在在主机操作系统中可用:
root@kvm2:~# iscsiadm -m session
tcp: [3] 10.184.226.74:3260,1 iqn.2001-04.com.example:kvm
root@kvm2:~# ls -la /dev/disk/by-path/
total 0
drwxr-xr-x 2 root root 120 Apr 12 17:44 .
drwxr-xr-x 6 root root 120 Apr 12 17:44 ..
lrwxrwxrwx 1 root root 9 Apr 12 17:44 ip-10.184.22.74:3260-iscsi-iqn.2001-04.com.example:kvm-lun-0 -> ../../sdc
lrwxrwxrwx 1 root root 10 Apr 12 17:44 ip-10.184.22.74:3260-iscsi-iqn.2001-04.com.example:kvm-lun-0-part1 -> ../../sdc1
lrwxrwxrwx 1 root root 10 Apr 12 17:44 ip-10.184.22.74:3260-iscsi-iqn.2001-04.com.example:kvm-lun-0-part2 -> ../../sdc2
lrwxrwxrwx 1 root root 10 Apr 12 17:44 ip-10.184.22.74:3260-iscsi-iqn.2001-04.com.example:kvm-lun-0-part5 -> ../../sdc5
root@kvm2:~#
- 通过远程定义 KVM 实例并在目标主机上启动它来完成迁移:
root@kvm1:~# virsh --connect qemu+ssh://kvm2/system define iscsi_kvm.xml
Domain iscsi_kvm defined from iscsi_kvm.xml
root@kvm1:~# virsh --connect qemu+ssh://kvm2/system list --all
Id Name State
----------------------------------------------------
- iscsi_kvm shut off
root@kvm:~# virsh --connect qemu+ssh://kvm2/system start iscsi_kvm
Domain iscsi_kvm started
root@kvm:~# virsh --connect qemu+ssh://kvm2/system list --all
Id Name State
----------------------------------------------------
3 iscsi_kvm running
root@kvm1:~#
它是如何工作的……
在本配方中,我们演示了如何手动执行 KVM 实例的离线迁移,从一个主机迁移到另一个主机,使用 iSCSI 存储池。在本章稍后的使用 virsh 命令进行在线迁移配方中,我们将使用相同的 iSCSI 存储池和实例,通过 virsh
命令执行实时迁移,从而避免实例的停机时间。
让我们一步步探索,并更详细地了解如何完成手动离线迁移。
我们从将要提供 iSCSI 目标的服务器开始,首先在步骤 1 中安装所需的 iSCSI 目标服务器软件包。
在步骤 2 中,我们启用了 iSCSI 目标功能,使服务器能够通过 iSCSI 协议导出块设备。
在步骤 3 中,我们为 iSCSI 目标设备指定了一个标识符(iSCSI 合格名称)iqn.2001-04.com.example:kvm
,该设备将供启动器使用。我们在这个示例中使用的是 /dev/loop1
块设备。iSCSI 合格名称的格式为 iqn.yyyy-mm.naming-authority:unique name,其中:
-
iqn:这是 iSCSI 合格名称标识符
-
yyyy-mm:这是命名机构成立的年份和月份
-
命名机构:这通常是命名机构的互联网域名或服务器域名的反向语法
-
唯一名称:这是你想要使用的任何名称
有关 iSCSI 和它使用的命名方案的更多信息,请参阅 en.wikipedia.org/wiki/ISCSI
。
在目标定义完成后,我们在步骤 4 中重启了服务器上的 iSCSI 服务。
在步骤 5 和 6 中,我们在两个 KVM 节点上安装并配置了 iSCSI 启动器服务,在步骤 7 中,我们请求所有可用的 iSCSI 目标。在步骤 8 和 9 中,我们定义并启动了一个新的基于 iSCSI 的存储池。如果你已经完成了 第二章中的使用存储池配方,你会发现存储池定义的语法非常熟悉,使用 libvirt 管理 KVM。
在创建 iSCSI 存储池后,我们继续在步骤 10 中列出该存储池中的卷。请注意,当我们启动存储池时,它登录了 iSCSI 目标,导致在 /dev/disk/by-path/
目录下出现了一个新的块设备,正如我们在步骤 11 中进一步看到的那样。我们现在可以在本地使用这个块设备来安装新的 Linux 操作系统。在步骤 12 中,我们可以看到提供给主机操作系统的 iSCSI 块设备尚未包含任何分区。
随着新的块设备出现,我们继续在步骤 13 中创建一个新的 KVM 实例,指定存储池和卷作为安装目标。操作系统安装完成后,我们现在可以看到 iSCSI 块设备上有多个分区(见步骤 14)。接着,我们在步骤 15 中启动新的客户机。
现在我们有一个使用 iSCSI 块设备的运行 KVM 实例,我们可以继续进行从 kvm1
主机到 kvm2
主机的离线手动迁移。
我们通过首先在第 16 步停止运行的 KVM 实例及其关联的存储池来启动迁移过程。然后在第 17 步中,我们将 KVM 客户端的 XML 配置导出到文件中。我们将使用它来在目标服务器上定义客户机。我们有几个选项:我们可以将文件复制到目标服务器并在那里定义实例,或者我们可以从原始主机远程执行此操作。
在第 18 和 19 步中,我们从原始主机到目标主机远程创建 iSCSI 存储池。我们本来也可以登录到目标主机并执行相同的操作,结果也一样。这里的重点是,我们可以使用 qemu+ssh
连接字符串通过 SSH 远程连接到其他 qemu 实例。在第 20 和 21 步中,我们确保相同的 iSCSI 卷已成功登录到目标主机。
最后,在第 22 步中,我们使用第 17 步中导出的 XML 配置定义目标主机上的实例,并启动它。因为我们使用的是相同的 XML 定义文件和包含客户操作系统文件系统的相同 iSCSI 块设备,所以我们现在在新服务器上创建了完全相同的实例,从而完成了离线迁移。
还有更多…
如果 iSCSI 目标服务器没有可用的块设备可以导出,我们可以通过按照此处列出的步骤使用常规文件创建一个新的块设备:
- 创建一个给定大小的新映像文件:
root@iscsi_target:~# truncate --size 10G xvdb.img
root@iscsi_target:~# file -s xvdb.img
xvdb.img: data
root@kvmiscsi_target:~# qemu-img info xvdb.img
image: xvdb.img
file format: raw
virtual size: 10G (10737418240 bytes)
disk size: 0
root@iscsi_target:~#
- 确保环回内核模块已编译(或使用
modprobe loop
加载它),并找到第一个可用的环回设备供使用:
root@iscsi_target:~# grep 'loop' /lib/modules/`uname -r`/modules.builtin
kernel/drivers/block/loop.ko
root@iscsi_target:~# losetup --find
/dev/loop0
root@iscsi_target:~#
- 将原始文件映像与第一个可用的环回设备关联:
root@iscsi_target:~# losetup /dev/loop0 xvdb.img
root@iscsi_target:~# losetup --all
/dev/loop0: [10300]:263347 (/root/xvdb.img)
root@iscsi_target:~#
在第 1 步中,我们使用 truncate
命令创建一个新映像文件。
在第 2 步中,我们列出第一个可用的块设备,并在第 3 步中将其与第 1 步中创建的原始映像文件关联。结果是一个新的块设备 /dev/loop0
可供我们用于 iSCSI 导出。
使用 GlusterFS 共享卷的手动离线迁移
在 使用 iSCSI 存储池的手动离线迁移 食谱中,我们创建了一个 iSCSI 存储池并在执行手动离线迁移时使用了它。使用存储池时,我们可以将共享存储的操作委托给 libvirt,而无需手动登录/退出 iSCSI 目标等。这在我们执行带有 virsh
命令的实时迁移时尤其有用,正如我们在下一个食谱中将看到的那样。尽管使用存储池不是必须的,但它简化并集中管理后端卷。
在这个食谱中,我们将使用 GlusterFS 网络文件系统来演示手动迁移 KVM 实例的另一种方法,这次不使用存储池。
GlusterFS 有以下两个组件:
-
服务器组件:这运行
GlusterFS
守护进程,并将本地块设备命名为 砖块 作为卷导出,客户端组件可以挂载这些卷 -
客户端组件:这通过 TCP/IP 连接到 GlusterFS 集群,并可以挂载导出的卷
有以下三种类型的卷:
-
分布式:这些是将文件分布到整个集群的卷
-
复制:这些是跨两个或更多节点在存储集群中复制数据的卷
-
条带化:这些是跨多个存储节点的条带文件
为了实现高可用性,我们将使用两个 GFS 节点,使用复制卷(两个砖块包含相同的数据)。
准备就绪
为了完成这个食谱,我们将使用以下内容:
-
两台将托管 GlusterFS 共享文件系统的服务器。
-
运行
libvirt
和qemu
的两台主机,将用于迁移 KVM 客户机。 -
所有服务器应该能够通过主机名相互通信。
-
托管共享卷的两台服务器应该有一个块设备可用,作为 GlusterFS 的砖块。如果没有可用的块设备,请参考本章中 使用 iSCSI 存储池进行手动离线迁移 处的 更多内容... 部分,了解如何使用常规文件创建块设备。
-
连接到 Linux 仓库以安装客户机操作系统。
如何操作...
要使用共享的 GlusterFS 后端存储迁移 KVM 客户机,请运行以下命令:
- 在两台将托管共享卷的服务器上,安装 GlusterFS:
root@glusterfs1/2:~# apt-get update && apt-get install glusterfs-server
- 从一个 GlusterFS 节点,探测另一个节点以形成集群:
root@glusterfs1:~# gluster peer status
peer status: No peers present
root@glusterfs1:~# gluster peer probe glusterfs2
peer probe: success
root@glusterfs1:~#
- 验证 GlusterFS 节点是否互相识别:
root@glusterfs1:~# gluster peer status
Number of Peers: 1
Hostname: glusterfs2
Port: 24007
Uuid: 923d152d-df3b-4dfd-9def-18dbebf2b76a
State: Peer in Cluster (Connected)
root@glusterfs1:~#
- 在两台 GlusterFS 主机上,创建将用作 GlusterFS 砖块的块设备文件系统并挂载它们:
root@glusterfs1/2:~# mkfs.ext4 /dev/loop5
...
Allocating group tables: done
Writing inode tables: done
Creating journal (32768 blocks): done
Writing superblocks and filesystem accounting information: done
root@glusterfs1/2:~# mount /dev/loop5 /mnt/
root@glusterfs1/2:~# mkdir /mnt/bricks
root@glusterfs1/2:~#
确保将块设备名称替换为适合你系统的名称。
- 从其中一个 GlusterFS 节点,使用来自两台服务器的砖块创建复制存储卷,然后列出它:
root@glusterfs1:~# gluster volume create kvm_gfs replica 2 transport tcp glusterfs1:/mnt/bricks/gfs1 glusterfs2:/mnt/bricks/gfs2
volume create: kvm_gfs: success: please start the volume to access data
root@glusterfs1:~# gluster volume list
kvm_gfs
root@glusterfs1:~#
- 从其中一台 GlusterFS 主机,启动新的卷并获取更多信息:
root@glusterfs1:~# gluster volume start kvm_gfs
volume start: kvm_gfs: success
root@glusterfs1:~# gluster volume info
Volume Name: kvm_gfs
Type: Replicate
Volume ID: 69823a48-8b1b-469f-b06a-14ef6f33a6f5
Status: Started
Number of Bricks: 1 x 2 = 2
Transport-type: tcp
Bricks:
Brick1: glusterfs1:/mnt/bricks/gfs1
Brick2: glusterfs2:/mnt/bricks/gfs2
root@glusterfs1:~#
- 在两个
libvirt
节点上,安装 GlusterFS 客户端并挂载将用于托管 KVM 镜像的 GlusterFS 卷:
root@kvm1/2:~# apt-get update && apt-get install glusterfs-client
root@kvm1/2:~# mkdir /tmp/kvm_gfs
root@kvm1/2:~# mount -t glusterfs glusterfs1:/kvm_gfs /tmp/kvm_gfs
root@kvm1/2:~#
挂载 GlusterFS 卷时,你可以指定集群中的任一节点。在前面的示例中,我们从 glusterfs1
节点挂载。
- 在其中一个
libvirt
节点上,创建一个新的 KVM 实例,使用挂载的 GlusterFS 卷:
root@kvm1:~# virt-install --name kvm_gfs --ram 1024 --extra-args="text console=tty0 utf8 console=ttyS0,115200" --graphics vnc,listen=0.0.0.0 --hvm --location=http://ftp.us.debian.org/debian/dists/stable/main/installer-amd64/ --disk /tmp/kvm_gfs/gluster_kvm.img,size=5
...
root@kvm1:~#
- 确保两个
libvirt
节点可以看到客户机镜像:
root@kvm1/2:~# ls -al /tmp/kvm_gfs/
total 1820300
drwxr-xr-x 3 root root 4096 Apr 13 14:48 .
drwxrwxrwt 6 root root 4096 Apr 13 15:00 ..
-rwxr-xr-x 1 root root 5368709120 Apr 13 14:59 gluster_kvm.img
root@kvm1/2:~#
- 要手动迁移 KVM 实例从一个
libvirt
节点到另一个,首先停止实例并导出其 XML 定义:
root@kvm1:~# virsh destroy kvm_gfs
Domain kvm_gfs destroyed
root@kvm1:~# virsh dumpxml kvm_gfs > kvm_gfs.xml
root@kvm1:~#
- 从源
libvirt
节点,在目标主机上定义实例:
root@kvm1:~# virsh --connect qemu+ssh://kvm2/system define kvm_gfs.xml
Domain kvm_gfs defined from kvm_gfs.xml
root@kvm1:~# virsh --connect qemu+ssh://kvm2/system list --all
Id Name State
----------------------------------------------------
- kvm_gfs shut off
root@kvm1:~#
- 在目标主机上启动 KVM 实例以完成迁移:
root@kvm2:~# virsh start kvm_gfs
Domain kvm_gfs started
root@kvm2:~#
我们还可以使用 qemu+ssh
连接从源主机启动目标主机上的 KVM 实例,如下所示:
root@kvm1:~# virsh --connect qemu+ssh://kvm2/system start kvm_gfs
它是如何工作的...
我们首先在步骤 1 中在两个服务器上安装 GlusterFS 服务端包。然后,在步骤 2 中,我们通过从第一个 GlusterFS 节点发送探测来形成集群。如果探测成功,我们将在步骤 3 中进一步获取集群信息。在步骤 4 中,我们通过在 GlusterFS 服务器上创建文件系统并挂载它们来准备块设备。挂载后,块设备将包含形成虚拟复制卷的砖块,供 GlusterFS 导出。
在步骤 5 中,我们在其中一个节点上创建新的复制卷(这将影响整个集群,且只需从一个 GlusterFS 节点运行)。我们指定类型为复制,使用 TCP 协议,并指定将要使用的砖块位置。卷创建完成后,在步骤 6 中我们启动它,并获取更多信息。注意,从卷信息的输出中,我们可以看到正在使用的砖块数量以及它们在集群中的位置。
在步骤 7 中,我们在两个libvirt
服务器上安装 GlusterFS 客户端组件,并挂载 GFS 卷。现在,两个 KVM 主机共享同一物理托管在 GlusterFS 节点上的复制存储。我们将使用该共享存储来托管新的 KVM 镜像文件。
在步骤 8 中,我们继续安装新的 KVM 实例,使用在前一步挂载的 GlusterFS 卷。安装完成后,我们在步骤 9 中验证两个libvirt
服务器是否可以看到新的 KVM 镜像。
我们在步骤 10 中开始手动迁移,首先停止正在运行的 KVM 实例,然后将其配置保存到磁盘。在步骤 11 中,我们使用 XML 转储远程定义 KVM 客户端,并验证它是否已成功在目标主机上定义。最后,我们在目标服务器上启动 KVM 实例,完成迁移。
使用 virsh 命令进行的在线迁移和共享存储
virsh
命令提供了一个迁移参数,我们可以用它来在主机之间迁移 KVM 实例。在前面的两个配方中,我们了解了如何手动迁移实例并停机。在本配方中,我们将对使用 iSCSI 存储池或本章前面提到的 GlusterFS 共享卷的实例执行在线迁移。
如果你记得,在线迁移只有在客户端文件系统位于某种共享介质上时才有效,例如 NFS、iSCSI、GlusterFS,或者如果我们先将镜像文件复制到所有节点,并在使用virsh migrate
时带上--copy-storage-all
选项,就像我们将在本章后面看到的那样。
准备工作
为了完成本配方,我们需要以下内容:
-
两个
libvirt
主机之间具有共享存储。如果你已经完成了本章前面的配方,你可以使用我们创建的 iSCSI 存储池和正在使用它的 KVM 实例,或者使用带有 KVM 客户端的 GFS 共享存储。 -
两个
libvirt
主机应能够使用短主机名相互通信。
如何操作...
要执行使用共享存储的实时迁移,请执行此处列出的操作:
- 确保我们之前创建的 iSCSI KVM 实例在源主机上运行:
root@kvm1:~# virsh list --all
Id Name State
----------------------------------------------------
26 iscsi_kvm running
root@kvm1:~#
- 将实例实时迁移到第二个
libvirt
服务器(目标节点应已配置 iSCSI 存储池)。如果此操作失败,请参阅本食谱的 还有更多内容... 部分以获取故障排除提示:
root@kvm1:~# virsh migrate --live iscsi_kvm qemu+ssh://kvm2/system
root@kvm1:~#
- 确保 KVM 实例已在源主机上停止,并在目标服务器上启动:
root@kvm1:~# virsh list --all
Id Name State
----------------------------------------------------
- iscsi_kvm shut off
root@kvm1:~# virsh --connect qemu+ssh://kvm2/system list --all
Id Name State
----------------------------------------------------
10 iscsi_kvm running
root@kvm1:~#
- 要将实例迁移回原服务器,从
kvm2
节点执行以下命令:
root@kvm2:~# virsh migrate --live iscsi_kvm qemu+ssh://kvm1/system
root@kvm2:~# virsh list --all
Id Name State
----------------------------------------------------
root@kvm2:~# virsh --connect qemu+ssh://kvm1/system list --all
Id Name State
----------------------------------------------------
28 iscsi_kvm running
root@kvm2:~#
它是如何工作的...
当迁移一个使用共享存储的 KVM 实例时,例如本示例中的 iSCSI 存储池,一旦我们启动带有 migrate --live
参数的迁移,libvirt 会自动处理退出原主机的 iSCSI 会话并登录到目标服务器,从而使包含虚拟机文件系统的块设备在目标服务器上可用,而无需复制所有数据。你可能已经注意到,迁移只用了几秒钟,因为迁移的唯一数据是运行中的虚拟机在源主机上的内存页。
还有更多内容...
根据你运行该操作的 Linux 发行版和服务器类型(物理机或云实例),你可能会遇到一些常见错误,当尝试迁移实例时。
错误:错误:不安全的迁移:如果磁盘使用的缓存设置不为 none,迁移可能会导致数据损坏。
解决方案:编辑你正在尝试迁移的实例的 XML 定义,并更新块设备的驱动程序部分,添加 cache=none
属性:
root@kvm1:~# virsh edit iscsi_kvm
...
<devices>
...
<disk type='block' device='disk'>
<driver name='qemu' type='raw' *cache='none'*/>
<source dev='/dev/disk/by-path/ip-10.184.22.74:3260-iscsi-iqn.2001-04.com.example:kvm-lun-0'/>
<target dev='hda' bus='ide'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
...
</devices>
错误:错误:内部错误:尝试将虚拟机迁移到相同的主机 02000100-0300-0400-0005-000600070008
。
解决方案:某些服务器,通常是虚拟化的,可能会返回相同的系统 UUID,这会导致迁移失败。要查看是否是这种情况,请在源机器和目标机器上运行以下命令:
root@kvm1/2:~# virsh sysinfo | grep -B5 -A3 uuid
<system>
<entry name='manufacturer'>FOXCONN</entry>
<entry name='product'>CL7100</entry>
<entry name='version'>PVT1-X05</entry>
<entry name='serial'>2M2542Z069</entry>
<entry name='uuid'>*02000100-0300-0400-0005-000600070008*</entry>
<entry name='sku'>NULL</entry>
<entry name='family'>Intel Grantley EP</entry>
</system>
root@kvm1/2:~#
如果两个服务器的 UUID 相同,请编辑 libvirt
配置文件并分配一个唯一的 UUID,然后重启 libvirt
:
root@kvm2:~# vim /etc/libvirt/libvirtd.conf
...
host_uuid = "02000100-0300-0400-0006-000600070008"
...
root@kvm2:~# /etc/init.d/libvirt-bin restart
libvirt-bin stop/waiting
libvirt-bin start/running, process 12167
root@kvm2:~#
错误:错误:无法解析地址 kvm2.localdomain
服务 49152:名称或服务无法识别。
解决方案:这表明 libvirt
无法解析实例的主机名。确保主机名不会解析为 localhost,并且你可以使用主机名而不是服务器的 IP 地址,在源主机和目标主机之间进行 ping 或 SSH 连接。以下是两个 libvirt
节点的工作主机文件示例:
root@kvm1:~# cat /etc/hosts
127.0.0.1 localhost
10.184.226.106 kvm1.example.com kvm1
10.184.226.74 kvm2.example.com kvm2
root@kvm1:~#
root@kvm2:~# cat /etc/hosts
127.0.0.1 localhost
10.184.226.106 kvm1.example.com kvm1
10.184.226.74 kvm2.example.com kvm2
root@kvm2:~#
你可以通过检查以下日志来获取更多关于实例操作的信息:
root@kvm1:~# cat /var/log/libvirt/libvirtd.log
...
2017-04-12 19:26:02.297+0000: 33149: error : virCommandWait:2399 : internal error: Child process (/usr/bin/iscsiadm --mode session) unexpected exit status 21
...
root@kvm1:~# cat /var/log/libvirt/qemu/iscsi_kvm.log
...
2017-04-13 17:59:48.040+0000: starting up
LC_ALL=C PATH=/usr/local/sbin:/usr/local/bin:/usr/bin:/usr/sbin:/sbin:/bin QEMU_AUDIO_DRV=none /usr/bin/qemu-system-x86_64 -name iscsi_kvm -S -machine pc-i440fx-trusty,accel=kvm,usb=off -m 1024 -realtime mlock=off -smp 1,sockets=1,cores=1,threads=1 -uuid 306e05ed-e398-ef33-d6e2-3708e90b89a6 -no-user-config -nodefaults -chardev socket,id=charmonitor,path=/var/lib/libvirt/qemu/iscsi_kvm.monitor,server,nowait -mon chardev=charmonitor,id=monitor,mode=control -rtc base=utc -no-shutdown -boot strict=on -device piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2 -drive file=/dev/disk/by-path/ip-10.184.226.74:3260-iscsi-iqn.2001-04.com.example:kvm-lun-0,if=none,id=drive-ide0-0-0,format=raw,cache=none -device ide-hd,bus=ide.0,unit=0,drive=drive-ide0-0-0,id=ide0-0-0,bootindex=1 -netdev tap,fd=24,id=hostnet0 -device rtl8139,netdev=hostnet0,id=net0,mac=52:54:00:8b:b8:e3,bus=pci.0,addr=0x3 -chardev pty,id=charserial0 -device isa-serial,chardev=charserial0,id=serial0 -vnc 0.0.0.0:0 -device cirrus-vga,id=video0,bus=pci.0,addr=0x2 -incoming tcp:[::]:49153 -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x4
char device redirected to /dev/pts/0 (label charserial0)
qemu: terminating on signal 15 from pid 33148
2017-04-13 18:34:49.684+0000: shutting down
...
root@kvm1:~#
使用 virsh 命令和本地镜像进行离线迁移
使用 virsh 执行离线迁移时不需要共享存储;但是我们需要为新主机提供客户机文件系统(通过复制镜像文件等)。离线迁移会传输实例定义,而不会在目标主机上启动客户机,也不会停止源主机上的客户机。在这个示例中,我们将使用 virsh 命令对运行中的 KVM 客户机进行离线迁移,使用其文件系统的镜像文件。
准备工作
对于这个简单的示例,我们需要以下内容:
- 两台
libvirt
主机和一个运行中的 KVM 实例。如果你的主机上没有其中之一,可以使用本地镜像文件安装并启动一个新的客户机虚拟机:
root@kvm:~# virt-install --name kvm_no_sharedfs --ram 1024 --extra-args="text console=tty0 utf8 console=ttyS0,115200" --graphics vnc,listen=0.0.0.0 --hvm --location=http://ftp.us.debian.org/debian/dists/stable/main/installer-amd64/ --disk /tmp/kvm_no_sharedfs.img,size=5
- 两台主机应该能够通过主机名互相通信。
如何操作...
要使用 virsh
命令执行离线迁移,请运行以下命令:
- 确保我们有一个运行中的 KVM 实例:
root@kvm1:~# virsh list --all
Id Name State
----------------------------------------------------
26 kvm_no_sharedfs running
root@kvm1:~#
- 使用离线模式迁移实例。如果此操作出错,请参阅 更多... 部分中的 使用 virsh 命令进行在线迁移 示例,获取故障排除提示:
root@kvm1:~# virsh migrate --offline --persistent kvm_no_sharedfs qemu+ssh://kvm2/system
root@kvm1:~#
- 与实时迁移不同,源实例仍在运行,而目标实例已停止:
root@kvm1:~# virsh list --all
Id Name State
----------------------------------------------------
29 kvm_no_sharedfs running
root@kvm1:~# virsh --connect qemu+ssh://kvm2/system list --all
Id Name State
----------------------------------------------------
- kvm_no_sharedfs shut off
root@kvm1:~#
它是如何工作的...
离线迁移相当简单;virsh
命令将定义文件从目标主机传输到目标并定义实例。原始的 KVM 客户机保持运行状态。为了启动迁移后的实例,首先需要将其镜像文件传输到目标主机,并且文件路径必须与源服务器上的路径完全一致。与直接转储 XML 文件并在目标主机上定义它的主要区别在于,libvirt
会对目标 XML 文件进行更新,比如分配新的 UUID。
在前面提到的示例中,唯一的两个新标志是离线标志和持久标志。前者指定了离线类型的迁移,后者将域设置为在目标主机上持久存在。
使用 virsh 命令和本地镜像进行在线迁移
在这个示例中,我们将进行一个实时迁移运行中的实例,且没有共享存储。
准备工作
对于这个示例,我们需要以下内容:
-
两台
libvirt
服务器,运行中的 KVM 实例,使用本地镜像文件。我们将使用在之前的示例中构建的 KVM 客户机,使用 virsh 命令和本地镜像进行离线迁移。 -
两台服务器必须能够通过主机名互相通信。
如何操作...
要迁移没有共享存储的实例,请按照以下步骤操作:
- 确保 KVM 客户机正在运行:
root@kvm1:~# virsh list --all
Id Name State
----------------------------------------------------
33 kvm_no_sharedfs running
root@kvm1:~#
- 查找镜像文件的位置:
root@kvm1:~# virsh dumpxml kvm_no_sharedfs | grep "source file"
<source file='/tmp/kvm_no_sharedfs.img'/>
root@kvm1:~#
- 将镜像文件传输到目标主机:
root@kvm1:~# scp /tmp/kvm_no_sharedfs.img kvm2:/tmp/
kvm_no_sharedfs.img 100% 5120MB 243.8MB/s 00:21
root@kvm1:~#
- 迁移实例并确保它在目标主机上运行:
root@kvm1:~# virsh migrate --live --persistent --verbose --copy-storage-all kvm_no_sharedfs qemu+ssh://kvm2/system
Migration: [100 %]
root@kvm1:~# virsh list --all
Id Name State
----------------------------------------------------
- kvm_no_sharedfs shut off
root@kvm1:~# virsh --connect qemu+ssh://kvm2/system list --all
Id Name State
----------------------------------------------------
17 kvm_no_sharedfs running
root@kvm1:~#
- 从目标主机迁移实例,使用增量镜像传输:
root@kvm2:~# virsh migrate --live --persistent --verbose --copy-storage-inc kvm_no_sharedfs qemu+ssh://kvm/system
Migration: [100 %]
root@kvm2:~#
它是如何工作的...
在步骤 1 中确保源实例处于运行状态后,我们将镜像文件传输到目标文件,并确保它与源实例在步骤 3 中的位置完全相同。镜像文件就位后,我们可以执行实时迁移,这将在步骤 4 中完成,随后回到步骤 5。
我们到目前为止还没有使用的两个新参数是--copy-storage-all
和copy-storage-inc.
。第一个参数指示libvirt
将整个镜像文件传输到目标位置,而第二个则执行增量传输,仅复制已更改的数据,从而减少传输时间。
第五章:KVM 虚拟机的监控与备份
在本章中,我们将覆盖以下主题:
-
使用 libvirt 收集资源使用情况
-
使用 Sensu 监控 KVM 实例
-
使用 tar 和 rsync 进行简单的 KVM 备份
-
创建快照
-
列出快照
-
检查快照
-
编辑快照
-
恢复快照
-
删除快照
介绍
不言而喻,监控和备份生产环境中的 KVM 实例对于满足正常运行 服务级别协议(SLA)以及满足高可用性和性能要求至关重要。虚拟机的监控和备份与物理服务器的监控和备份并没有太大不同。在某些情况下,备份虚拟机的单个镜像文件或创建快照比备份运行在物理服务器上的操作系统文件系统更为方便。
在本章中,我们将展示如何收集实时 KVM 实例的资源使用指标,并使用像 Sensu 这样的工具监控资源使用情况并在预定义的阈值上进行告警。随后,我们将专注于使用像 rsync
这样的工具备份 KVM 客户机的不同方法,以及使用 virsh
命令创建和管理快照。
使用 libvirt 收集资源使用情况
监控虚拟机的第一步是熟悉用于收集我们希望后续告警的子系统指标的工具。在本配方中,我们将专注于 KVM 客户机的 CPU、内存和块设备使用情况。我们还将学习如何使用 QEMU 监控套接字和 QEMU 客户机代理。
Libvirt 提供了一组调用,virsh
命令利用这些调用来收集指定客户机/域的资源使用信息。我们将在本章稍后的 使用 Sensu 监控 KVM 实例 配方中监控并告警这些收集到的信息。
准备工作
本配方中,我们需要以下内容:
-
已安装并配置 libvirt 的服务器
-
正在运行的 KVM 实例
如何操作...
要收集运行中的实例或虚拟化主机的各种资源使用信息,请执行以下操作:
- 获取虚拟化主机 CPU 使用情况的信息:
root@kvm:~# virsh nodecpustats --percent
usage: 0.0%
user: 0.0%
system: 0.0%
idle: 100.0%
iowait: 0.0%
root@kvm:~#
- 收集虚拟化主机内存使用情况的信息:
root@kvm:~# virsh nodememstats
total : 131918328 KiB
free : 103633700 KiB
buffers: 195532 KiB
cached : 25874840 KiB
root@kvm:~#
- 检查 KVM 实例的状态:
root@kvm:~# virsh domstate kvm1
running
root@kvm:~#
- 获取 KVM 实例分配的虚拟 CPU(vCPU)数量:
root@kvm:~# virsh vcpucount --current kvm1 --live
1
root@kvm:~#
- 收集虚拟机虚拟 CPU 的详细信息:
root@kvm:~# virsh vcpuinfo kvm1
VCPU: 0
CPU: 29
State: running
CPU time: 118.8s
CPU Affinity: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
root@kvm:~#
- 收集关于客户机虚拟 CPU 定时器的信息:
root@kvm:~# virsh cpu-stats --total kvm1
Total:
cpu_time 175.003045493 seconds
user_time 2.610000000 seconds
system_time 7.510000000 seconds
root@kvm:~#
- 收集虚拟机的一般信息:
root@kvm:~# virsh dominfo kvm1
Id: 30
Name: kvm1
UUID: bd167199-c1c4-de7e-4996-43a7f197e565 OS Type: hvm
State: running
CPU(s): 1
CPU time: 175.6s Max memory: 1048576 KiB
Used memory: 1048576 KiB
Persistent: yes
Autostart: disable Managed save: no
Security model: none
Security DOI: 0
root@kvm:~#
- 收集虚拟机的内存使用情况:
root@kvm:~# virsh dommemstat --live kvm1
actual 1048576
swap_in 0
rss 252684
root@kvm:~#
- 获取与 KVM 实例相关的块设备信息:
root@kvm:~# virsh domblklist kvm1
Target Source
------------------------------------------------
hda /var/lib/libvirt/images/kvm1.img
root@kvm:~#
- 获取虚拟机块设备的大小信息:
root@kvm:~# virsh domblkinfo --device hda kvm1
Capacity: 8589934592
Allocation: 2012381184
Physical: 2012381184
root@kvm:~#
- 获取 KVM 实例的任何块设备错误:
root@kvm:~# virsh domblkerror kvm1
No errors found
root@kvm:~#
- 打印 KVM 客户机的块设备统计信息:
root@kvm:~# virsh domblkstat --device hda --human kvm1
Device: hda
number of read operations: 42053
number of bytes read: 106145280
number of write operations: 10648
number of bytes written: 96768000
number of flush operations: 4044
total duration of reads (ns): 833974071
total duration of writes (ns): 1180545967
total duration of flushes (ns): 3458623200
root@kvm:~#
工作原理...
在本教程中,我们从主机操作系统收集各种虚拟化主机和来宾资源利用率信息。在以后的教程中,我们将利用这些信息在监控系统中设置警报,并根据设定的标准和阈值触发相应操作。让我们更详细地回顾一下我们之前执行的步骤。
我们从第 1 步开始收集关于虚拟化主机/主机操作系统的 CPU 利用率信息。接着,在第 2 步收集物理主机的内存利用率信息。请注意,我们也可以使用其他 Linux 命令来完成这项工作,而不是使用virsh
,但它有助于展示这个概念。
监控 KVM 实例的状态非常重要,以防虚拟机意外终止或在服务器重启后没有自动启动。在第 3 步中,我们获取虚拟机的当前状态。
在第 4、5 和 6 步中,我们收集了关于虚拟机来宾的虚拟 CPU 的信息。我们可以看到分配的 CPU 数量以及其他有用的信息,例如 CPU 花费在内核和用户空间代码上的时间。
在第 7 步中,我们收集了有关虚拟机的更多一般信息;特别值得关注的是我们可以设置警报阈值的总内存和已用内存量。
在第 8 步中,我们获取关于 KVM 实例的内存利用率的信息。我们可以在输出中看到总内存、交换内存和常驻内存的使用情况。
在第 9 步中,我们列出了附加到虚拟机的块设备,并在第 10 步中使用这些信息获取设备容量。如果块设备存在任何错误,第 11 步的命令输出会显示出来,我们可以利用这些信息触发监控警报。
监控附加到 KVM 实例的块设备的性能,可以使用第 12 步命令的输出。
还有更多内容...
当我们使用 libvirt 创建虚拟机时,启动的 QEMU 进程会暴露一个监控套接字,我们可以连接到该套接字并收集关于来宾的信息。
让我们看看我们一直在使用的 KVM 实例的情况:
- 获取来宾实例的进程信息:
root@kvm:~# pgrep -lfa kvm1
32332 /usr/bin/qemu-system-x86_64 -name kvm1 -S -machine pc- i440fx-trusty,accel=kvm,usb=off -m 1024 -realtime mlock=off -smp 1,sockets=1,cores=1,threads=1 -uuid bd167199-c1c4-de7e-4996-43a7f197e565 -no-user-config -nodefaults -chardev socket,id=charmonitor,path=/var/lib/libvirt/qemu/kvm1.monitor,server,nowait -mon chardev=charmonitor,id=monitor,mode=control -rtc base=utc -no-shutdown -boot strict=on -device piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2 -drive file=/var/lib/libvirt/images/kvm1.img,if=none,id=drive-ide0-0-0,format=raw -device ide-hd,bus=ide.0,unit=0,drive=drive-ide0-0-0,id=ide0-0-0,bootindex=1 -netdev tap,fd=24,id=hostnet0 -device rtl8139,netdev=hostnet0,id=net0,mac=52:54:00:55:9b:d6,bus=pci.0,addr=0x3 -chardev pty,id=charserial0 -device isa-serial,chardev=charserial0,id=serial0 -vnc 146.20.141.158:0 -device cirrus-vga,id=video0,bus=pci.0,addr=0x2 -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x4
root@kvm:~#
从前面的输出中注意到传递给 QEMU 进程的*-*chardev socket,id=charmonitor,path=/var/lib/libvirt/qemu/kvm1.monitor
和-mon chardev=charmonitor,id=monitor,mode=control
参数。
- 我们可以通过两种方式访问这个套接字,一种是使用
nc
和socat
等工具连接,另一种是通过virsh
命令,具体如下:
root@kvm:~# virsh qemu-monitor-command kvm1 --hmp "info"
info balloon -- show balloon information
info block [-v] [device] -- show info of one block device or all block devices (and details of images with -v option)
info block-jobs -- show progress of ongoing block device operations
info blockstats -- show block device statistics
info capture -- show capture information
info chardev -- show the character devices
info cpus -- show infos for each CPU
info cpustats -- show CPU statistics
info history -- show the command line history
info irq -- show the interrupts statistics (if available)
info jit -- show dynamic compiler info
info kvm -- show KVM information
info mem -- show the active virtual memory mappings
info mice -- show which guest mouse is receiving events
info migrate -- show migration status
info migrate_cache_size -- show current migration xbzrle cache size
info migrate_capabilities -- show current migration capabilities
info mtree -- show memory tree
info name -- show the current VM name
info network -- show the network state
info numa -- show NUMA information
info pci -- show PCI info
info pcmcia -- show guest PCMCIA status
info pic -- show i8259 (PIC) state
info profile -- show profiling information
info qdm -- show qdev device model list
info qtree -- show device tree
info registers -- show the cpu registers
info roms -- show roms
info snapshots -- show the currently saved VM snapshots
info spice -- show the spice server status
info status -- show the current VM status (running|paused)
info tlb -- show virtual to physical memory mappings
info tpm -- show the TPM device
info trace-events -- show available trace-events & their state
info usb -- show guest USB devices
info usbhost -- show host USB devices
info usernet -- show user network stack connection states
info uuid -- show the current VM UUID
info version -- show the version of QEMU
info vnc -- show the vnc server status
root@kvm:~#
- 要获取关于 KVM 实例网络接口的信息,我们可以运行以下代码:
root@kvm:~# virsh qemu-monitor-command kvm1 --hmp "info network"
net0: index=0,type=nic,model=rtl8139,macaddr=52:54:00:55:9b:d6
\ hostnet0: index=0,type=tap,fd=24
root@kvm:~#
QEMU 提供了一个来宾代理守护进程,可以在 KVM 实例内启动,然后从主机操作系统连接。我们可以从主机直接收集额外的数据或更新虚拟机的某些设置。
让我们看一下安装和使用 QEMU 来宾代理的示例:
- 创建所需目录,该目录将包含 libvirt 主机和运行在 KVM 来宾内的来宾代理之间的通信套接字:
root@kvm:~# mkdir -p /var/lib/libvirt/qemu/channel/target
root@kvm:~# chown libvirt-qemu:kvm
/var/lib/libvirt/qemu/channel/ -R
root@kvm:~#
- 编辑运行中的虚拟机配置,并在
<devices>
部分下添加以下定义:
root@kvm:~# virsh edit kvm1
...
<devices>
...
<channel type='unix'>
<source mode='bind' path='/var/lib/libvirt/qemu/channel/target/kvm1.org.qemu.guest_agent.0'/>
<target type='virtio' name='org.qemu.guest_agent.0'/>
<alias name='channel0'/>
<address type='virtio-serial' controller='0' bus='0' port='1'/>
</channel>
...
</devices>
...
Domain kvm1 XML configuration edited.
root@kvm:~#
- 重启 KVM 实例:
root@kvm:~# virsh destroy kvm1
Domain kvm1 destroyed
root@kvm:~# virsh start kvm1
Domain kvm1 started
root@kvm:~#
- 连接到虚拟机并安装和启动 QEMU 客户机代理,如下所示:
root@kvm:~# virsh console kvm1
Connected to domain kvm1
Escape character is ^]
root@debian:~# apt update && apt install qemu-guest-agent
...
root@debian:~# service qemu-guest-agent start
root@debian:~# service qemu-guest-agent status
● qemu-guest-agent.service - LSB: QEMU Guest Agent startup script
Loaded: loaded (/etc/init.d/qemu-guest-agent)
Active: active (running) since Wed 2017-04-19 14:44:08 CDT; 33min ago
Process: 397 ExecStart=/etc/init.d/qemu-guest-agent start (code=exited, status=0/SUCCESS)
CGroup: /system.slice/qemu-guest-agent.service
└─425 /usr/sbin/qemu-ga --daemonize -m virtio-serial -p /dev/virti...
Apr 19 14:44:08 debian systemd[1]: Starting LSB: QEMU Guest Agent startup s.....
Apr 19 14:44:08 debian systemd[1]: Started LSB: QEMU Guest Agent startup script.
Hint: Some lines were ellipsized, use -l to show in full.
root@debian:~#
- 回到主机,我们可以看到新的套接字文件:
root@kvm:~# ls -la /var/lib/libvirt/qemu/channel/target/
total 8
drwxr-xr-x 2 libvirt-qemu kvm 4096 Apr 19 19:43 .
drwxr-xr-x 3 libvirt-qemu kvm 4096 Apr 19 19:43 ..
srwxr-xr-x 1 libvirt-qemu kvm 0 Apr 19 19:43 kvm1.org.qemu.guest_agent.0
root@kvm:~#
- 让我们从主机连接到客户机代理并列出客户机代理接受的可用命令:
root@kvm:~# virsh qemu-agent-command kvm1 --pretty --cmd '{"execute": "guest-info"}'
{
"return": {
"version": "2.1.2",
"supported_commands": [
{
"enabled": true,
"name": "guest-set-vcpus",
"success-response": true
},
{
"enabled": true,
"name": "guest-get-vcpus",
"success-response": true
},
{
"enabled": true,
"name": "guest-network-get-interfaces",
"success-response": true
},
...
]
}
}
root@kvm:~#
- 通过运行以下代码获取关于客户机 vCPU 的信息:
root@kvm:~# virsh qemu-agent-command kvm1 --pretty --cmd '{"execute": "guest-get-vcpus"}'
{
"return": [
{
"online": true,
"can-offline": false,
"logical-id": 0
}
]
}
root@kvm:~#
使用监控和客户端代理套接字提供了一种额外的方式,可以收集关于在 libvirt 主机上运行的虚拟机的更多信息,之后我们可以将这些信息添加为监控检查。
使用 Sensu 监控 KVM 实例
Sensu 是一个完整的监控解决方案,采用客户端-服务器模型;服务器在 Rabbitmq 服务提供的消息队列中发布检查。客户端/代理订阅队列中的主题,并在它们运行的主机上执行指定的检查。状态和历史数据存储在 Redis 服务器中。
在本篇教程中,我们将把 Sensu 服务器、Rabbitmq 消息队列和 Redis 服务器安装在同一主机上,编写一个简单的监控检查,使用我们从使用 libvirt 收集资源使用情况教程中获取的信息,并在 KVM 客户机中安装 Sensu 代理。
准备工作
对于本篇教程,我们需要以下内容:
-
安装并运行 libvirt 的 Linux 主机
-
在 libvirt 主机上运行的 KVM 实例
-
KVM 实例与 libvirt 主机之间的网络连接
如何操作...
要设置新的 Sensu 部署并定义各种监控检查,请执行以下步骤:
- 安装 Redis 服务器并确保它响应请求:
root@kvm:~# apt-get install -y redis-server
...
root@kvm:~# redis-cli ping
PONG
root@kvm:~#
- 安装 Rabbitmq 服务器:
root@kvm:~# apt-get install -y rabbitmq-server
...
root@kvm:~#
- 创建 Sensu 代理将订阅的虚拟主机,以及 Rabbitmq 客户端的凭证和权限:
root@kvm:~# rabbitmqctl add_vhost /sensu
Creating vhost "/sensu" ...
...done.
root@kvm:~# rabbitmqctl add_user sensu secret
Creating user "sensu" ...
...done.
root@kvm:~# rabbitmqctl set_permissions -p /sensu sensu ".*" ".*" ".*"
Setting permissions for user "sensu" in vhost "/sensu" ...
...done.
root@kvm:~#
- 添加 Sensu 上游仓库及其密钥,然后安装 Sensu 包:
root@kvm:~# wget -q https://sensu.global.ssl.fastly.net/apt/pubkey.gpg -O- | apt-key add -
OK
root@kvm:~# echo "deb https://sensu.global.ssl.fastly.net/apt sensu main" | tee /etc/apt/sources.list.d/sensu.list
deb https://sensu.global.ssl.fastly.net/apt sensu main
root@kvm:~# apt-get update
...
root@kvm:~# apt-get install -y sensu
...
root@kvm:~#
- Sensu 使用基于 JSON 的配置文件进行配置。创建 Sensu API 配置文件:
root@kvm:/etc/sensu/conf.d# cat api.json
{
"api": {
"host": "localhost",
"bind": "0.0.0.0",
"port": 4567
}
}
root@kvm:/etc/sensu/conf.d#
- 配置 Sensu 的传输类型;我们在此部署中使用 Rabbitmq:
root@kvm:/etc/sensu/conf.d# cat transport.json
{
"transport": {
"name": "rabbitmq",
"reconnect_on_error": true
}
}
root@kvm:/etc/sensu/conf.d#
- 配置 Rabbitmq 服务接受连接的位置、虚拟主机和凭证:
root@kvm:/etc/sensu/conf.d# cat rabbitmq.json
{
"rabbitmq": {
"host": "0.0.0.0",
"port": 5672,
"vhost": "/sensu",
"user": "sensu",
"password": "secret"
}
}
root@kvm:/etc/sensu/conf.d#
- 指定 Redis 服务监听的主机和端口:
root@kvm:/etc/sensu/conf.d# cat redis.json
{
"redis": {
"host": "localhost",
"port": 6379
}
}
root@kvm:/etc/sensu/conf.d#
- 配置 Sensu 客户端:
root@kvm:/etc/sensu/conf.d# cat client.json
{
"client": {
"name": "ubuntu",
"address": "127.0.0.1",
"subscriptions": [
"base"
],
"socket": {
"bind": "127.0.0.1",
"port": 3030
}
}
}
root@kvm:/etc/sensu/conf.d#
有关 Sensu 的更多信息,请参见 sensuapp.org/docs/
。
- 安装 Sensu 的 Web 前端,名为 Uchiwa:
root@kvm:/etc/sensu/conf.d# apt-get install -y uchiwa
...
root@kvm:/etc/sensu/conf.d
- 配置 Uchiwa 前端:
root@kvm:/etc/sensu/conf.d# cat /etc/sensu/uchiwa.json
{
"sensu": [
{
"name": "KVM guests",
"host": "localhost",
"ssl": false,
"port": 4567,
"path": "",
"timeout": 5000
}
],
"uchiwa": {
"port": 3000,
"stats": 10,
"refresh": 10000
}
}
root@kvm:/etc/sensu/conf.d#
- 启动 Sensu 服务器、API、客户端和前端组件:
root@kvm:/etc/sensu/conf.d# /etc/init.d/sensu-server start
* Starting sensu-server [ OK ]
root@kvm:/etc/sensu/conf.d# /etc/init.d/sensu-api start
* Starting sensu-api [ OK ]
root@kvm:/etc/sensu/conf.d# /etc/init.d/sensu-client start
* Starting sensu-client [ OK ]
root@kvm:/etc/sensu/conf.d# /etc/init.d/uchiwa restart
uchiwa started.
root@kvm:/etc/sensu/conf.d#
- 连接到 KVM 实例控制台;安装并配置 Sensu 客户端:
root@kvm:/etc/sensu/conf.d# virsh console kvm1
Connected to domain kvm1
Escape character is ^]
root@debian:~# wget -q https://sensu.global.ssl.fastly.net/apt/pubkey.gpg -O- | apt-key add -
OK
root@debian:~# echo "deb https://sensu.global.ssl.fastly.net/apt sensu main" | tee /etc/apt/sources.list.d/sensu.list
deb https://sensu.global.ssl.fastly.net/apt sensu main
root@debian:~# apt install apt-transport-https
root@debian:~# apt update && apt install sensu
...
root@debian:~# cd /etc/sensu/conf.d/
root@debian:/etc/sensu/conf.d# cat client.json
{
"client": {
"name": "monitor_kvm",
"address": "10.10.10.92",
"subscriptions": ["base"]
}
}
root@debian:/etc/sensu/conf.d# cat rabbitmq.json
{
"rabbitmq": {
"host": "10.10.10.1",
"port": 5672,
"vhost": "/sensu",
"user": "sensu",
"password": "secret"
}
}
root@debian:/etc/sensu/conf.d# cat transport.json
{
"transport": {
"name": "rabbitmq",
"reconnect_on_error": true
}
}
root@debian:/etc/sensu/conf.d#
将客户端的 IP 地址替换为 KVM 实例中配置的 IP 地址。更新 Rabbitmq 服务器的 IP 地址为主机桥接上配置的 IP 地址。确保 KVM 客户机能够 ping 通主机操作系统上的桥接 IP。
- 启动 Sensu 客户端:
root@debian:/etc/sensu/conf.d# /etc/init.d/sensu-client start
Starting sensu-client:.
root@debian:/etc/sensu/conf.d#
- 连接到 Uchiwa 界面,确保主机 Sensu 客户端和 KVM 客户端 Sensu 客户端都列在 CLIENTS 部分:
显示已连接客户端的 Uchiwa 前端
- 在仍然连接到 KVM 客户端时,从 gem 仓库安装内存检查并进行测试:
root@debian:/etc/sensu/conf.d# apt install rubygems
...
root@debian:/etc/sensu/conf.d# gem search sensu | grep plugins | grep memory
sensu-plugins-memory (0.0.2)
sensu-plugins-memory-checks (2.1.0)
root@debian:/etc/sensu/conf.d#
root@debian:/etc/sensu/conf.d# gem install sensu-plugins-memory-checks
...
root@debian:/etc/sensu/conf.d# /etc/init.d/sensu-client restart
configuration is valid
Stopping sensu-client:.
Starting sensu-client:.
oot@debian:/etc/sensu/conf.d# /usr/local/bin/check-memory-percent.rb -w 80 -c 9
MEM OK - system memory usage: 11%
root@debian:/etc/sensu/conf.d#
- 返回主机操作系统,定义 KVM 客户端的新内存检查:
root@kvm:/etc/sensu/conf.d# cat check_memory.json
{
"checks": {
"memory_check": {
"command": "/usr/local/bin/check-memory-percent.rb -w 80 -c 90",
"subscribers": ["base"],
"handlers": ["default"],
"interval": 300
}
}
}
root@kvm:/etc/sensu/conf.d#
- 重启 Sensu 组件:
root@kvm:/etc/sensu/conf.d# /etc/init.d/uchiwa restart Killing uchiwa (pid 15350) with SIGTERM
Waiting uchiwa (pid 15350) to die...
Waiting uchiwa (pid 15350) to die...
uchiwa stopped.
uchiwa started.
root@kvm:/etc/sensu/conf.d# /etc/init.d/sensu-server restart
configuration is valid
* Stopping sensu-server [ OK ]
* Starting sensu-server [ OK ]
root@kvm:/etc/sensu/conf.d# /etc/init.d/sensu-api restart
configuration is valid
* Stopping sensu-api [ OK ]
* Starting sensu-api [ OK ]
root@kvm:/etc/sensu/conf.d#
- KVM 实例的 memory_check 现在显示在 Uchiwa 仪表板中:
显示 KVM 客户端内存检查的 Uchiwa 前端
它是如何工作的...
在上一节中,我们安装了 Sensu 服务器及其运行所需的所有基础组件,并将其部署在虚拟化主机上。然后我们在 KVM 实例内安装了客户端,安装了内存 ruby 检查,并在主机上定义了它。现在让我们更详细地审视这些步骤。
在步骤 1 中,我们安装 Redis 服务器并确保它接受连接。Redis 是一个键值存储服务,Sensu 用它来存储有关检查、当前状态和连接客户端的历史信息。
在配置好 Redis 服务器后,我们继续在步骤 2 到 9 中安装并配置 Rabbitmq。Rabbitmq 是一个符合 高级消息队列协议(AMQP)标准的消息总线。Sensu 服务器和客户端从队列中生成和消费消息,以触发监控操作。
尽管不是必须的,但在步骤 10 和 11 中,我们安装并配置了名为 Uchiwa 的 Sensu 服务器的 Web 前端。我们可以使用 Web 界面检查我们监控的 KVM 客户端的不同检查状态。
在步骤 13 中,我们在 KVM 客户端实例内安装了 Sensu 客户端,并在步骤 16 中安装了来自 gem 的内存监控脚本。监控脚本可以使用任何语言编写(此处使用 RUBY),只要它返回 Sensu 所期望的错误代码。在下一节中,我们将从头开始使用 Bash 编写一个新的检查。
还有更多...
在上一节中,我们看到了如何在 KVM 实例中使用 ruby 检查并监控内存使用情况的示例。Sensu 提供了独立的检查,可以从 Sensu 客户端触发,独立于 Sensu 服务器的调度机制。让我们使用这个功能,编写一个简单的 Bash 检查脚本,该脚本将从主机操作系统运行,而不是从 KVM 客户端运行,并使用 virsh
命令检查 KVM 实例的状态:
- 使用
sensu-client
执行检查的自定义脚本编写独立检查定义:
root@kvm:/etc/sensu/conf.d# cat check_kvm_instance_status.json
{
"checks": {
"check_kvm_instance_status": {
"command": "check_kvm_instance_status.sh -n kvm1",
"standalone": true,
"subscribers": ["base"],
"interval": 60
}
}
}
root@kvm:/etc/sensu/conf.d#
- 在 Sensu 的
plugins
目录中,编写这个简单的 Bash 脚本:
root@kvm:/etc/sensu/conf.d# cd ../plugins/
root@kvm:/etc/sensu/plugins# cat check_kvm_instance_status.sh
#!/bin/bash
# Checks if a KVM instance is running
usage()
{
echo "Usage: `basename $0` -n|--name kvm1"
exit 2
}
sanity_check()
{
if [ "$INSTANCE_NAME" == "" ]
then
usage
fi
}
report_result()
{
if [ "$INSTANCE_STATE" == "shut off" ]
then
echo "CRITICAL - KVM instance $INSTANCE_NAME is not running"
exit 2
else
echo "OK - KVM instance $INSTANCE_NAME is running"
exit 0
fi
}
check_instance_state()
{
declare -g INSTANCE_STATE="shut off"
INSTANCE_STATE=$(sudo /usr/bin/virsh domstate $INSTANCE_NAME)
}
main()
{
sanity_check
check_instance_state
report_result
}
while [[ $# > 1 ]]
do
key=$1
case $key in
-n|--name)
INSTANCE_NAME=$2
shift
;;
*)
usage
;;
esac
shift
done
main
root@kvm:/etc/sensu/plugins#
- 使脚本可执行,在
sudoers
文件中添加 Sensu 用户,并通过执行它来测试检查:
root@kvm:/etc/sensu/plugins# chmod u+x check_kvm_instance_status.sh
root@kvm:/etc/sensu/plugins# chown sensu:sensu check_kvm_instance_status.sh
root@kvm:/etc/sensu/plugins# echo "sensu ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/sensu
root@kvm:/etc/sensu/plugins# sudo -u sensu ./check_kvm_instance_status.sh --name kvm1
OK - KVM instance kvm1 is running
root@kvm:/etc/sensu/plugins# virsh destroy kvm1
Domain kvm1 destroyed
root@kvm:/etc/sensu/plugins# sudo -u sensu ./check_kvm_instance_status.sh --name kvm1
CRITICAL - KVM instance kvm1 is not running
root@kvm:/etc/sensu/plugins# virsh start kvm1
Domain kvm1 started
root@kvm:/etc/sensu/plugins#
- 重启主机上的 Sensu 客户端;检查日志和 Uchiwa 仪表板,查看新的独立检查:
root@kvm:/etc/sensu/conf.d# /etc/init.d/sensu-client restart
configuration is valid
* Stopping sensu-client [ OK ]
* Starting sensu-client [ OK ]
root@kvm:/etc/sensu/conf.d# cat /var/log/sensu/sensu-client.log | grep check_kvm_instance_status
{"timestamp":"2017-04-20T17:37:48.409805+0000","level":"warn","message":"loading config file","file":"/etc/sensu/conf.d/check_kvm_instance_status.json"}
{"timestamp":"2017-04-20T17:38:16.746861+0000","level":"info","message":"publishing check result","payload":{"client":"ubuntu","check":{"command":"check_kvm_instance_status.sh -n kvm1","standalone":true,"subscribers":["base"],"interval":60,"name":"check_kvm_instance_status","issued":1492709896,"executed":1492709896,"duration":0.016,"output":"OK - KVM instance kvm1 is running\n","status":0}}}
root@kvm:/etc/sensu/conf.d#
显示独立实例检查的 Uchiwa 前端
使用来自libvirt 资源使用集合的示例,你现在应该能够编写多种由监控系统 Sensu 执行的检查,这些检查可以在虚拟化主机或 KVM 客户机内运行。
有关 Sensu 如何在触发警报时执行脚本的更多信息,请参考官方文档中的handlers部分:sensuapp.org/docs/latest/reference/handlers.html
。
使用 tar 和 rsync 进行简单的 KVM 备份
在这个步骤中,我们将使用tar
和rsync
创建 KVM 实例的备份并将其存储在远程服务器上。这是备份 KVM 实例的最简单方法。在接下来的几个步骤中,我们将创建快照并将其作为冷备份使用。
准备工作
对于这个极其简单的步骤,我们需要:
-
一个运行中的 libvirt 主机,使用镜像文件作为其后备存储
-
tar
和rsync
Linux 工具 -
用于传输备份的远程服务器
如何操作…
要使用tar
和rsync
备份虚拟机,请执行以下步骤:
- 创建备份目录并切换到该目录:
root@kvm:~# mkdir backup_kvm1 && cd backup_kvm1
root@kvm:~/backup_kvm1#
- 查找 KVM 客户机镜像文件的位置:
root@kvm:~/backup_kvm1# virsh dumpxml kvm1 | grep "source file"
<source file='/var/lib/libvirt/images/kvm1.img'/>
root@kvm:~/backup_kvm1#
- 将当前实例配置保存到磁盘:
root@kvm:~/backup_kvm1# virsh dumpxml kvm1 > kvm1.xml
root@kvm:~/backup_kvm1#
- 停止 KVM 客户机并将镜像文件复制到备份目录:
root@kvm:~/backup_kvm1# virsh destroy kvm1
Domain kvm1 destroyed
root@kvm:~/backup_kvm1# cp /var/lib/libvirt/images/kvm1.img .
root@kvm:~/backup_kvm1# ls -lah
total 2.4G
drwxr-xr-x 2 root root 4.0K Apr 20 18:37 .
drwx------ 7 root root 4.0K Apr 20 18:36 ..
-rwxr-xr-x 1 root root 8.0G Apr 20 18:37 kvm1.img
-rw-r--r-- 1 root root 3.0K Apr 20 18:36 kvm1.xml
root@kvm:~/backup_kvm1#
- 为虚拟机的配置和镜像文件创建一个单独的存档:
root@kvm:~/backup_kvm1# tar jcvf kvm1_backup.tar.bz .
./
./kvm1.img
./kvm1.xml
root@kvm:~/backup_kvm1# rm kvm1.img kvm1.xml
root@kvm:~/backup_kvm1#
- 将备份存档传输到远程服务器:
root@kvm:~/backup_kvm1# rsync -vaz kvm1_backup.tar.bz kvm2:/tmp
sending incremental file list
kvm1_backup.tar.bz
sent 842,977,610 bytes received 35 bytes 26,761,195.08 bytes/sec
total size is 845,671,214 speedup is 1.00
root@kvm:~/backup_kvm1#
- 要从备份中恢复,请登录远程服务器并提取存档:
root@kvm2:~# cd /tmp/
root@kvm2:/tmp# tar jxfv kvm1_backup.tar.bz
./
./kvm1.img
./kvm1.xml
root@kvm2:/tmp#
- 将镜像文件复制到配置的位置并定义实例:
root@kvm2:/tmp# cp kvm1.img /var/lib/libvirt/images/
root@kvm2:/tmp# virsh define kvm1.xml
Domain kvm1 defined from kvm1.xml
root@kvm2:/tmp# virsh list --all | grep kvm1
- kvm1 shut off
root@kvm2:/tmp#
它是如何工作的…
在第 1 步创建备份目录后,我们在第 3 步将当前的客户机定义保存到磁盘。在第 4 步,停止虚拟机后,我们将其镜像文件复制到备份目录。在第 5 步,我们创建一个 bzip2 压缩的数据文件,并在第 6 步将其传输到远程服务器。
在远程服务器上,我们在第 7 步解压存档,并将原始镜像文件复制到实例的 XML 定义所期望的位置,然后在第 8 步定义实例。
注意,为了在将镜像文件复制到备份目录时保持数据的一致性和完整性,我们必须先停止 KVM 客户机。
创建快照
虚拟机快照在特定时间点保留正在运行或已停止实例的当前状态。以后可以用来从该点恢复实例。快照可以作为备份或作为构建新虚拟机的模板,新虚拟机将是原始实例的副本。
为了利用快照,支持存储必须首先支持它。如果你还记得在第一章,QEMU 和 KVM 入门中的使用 qemu-img 管理磁盘镜像配方,我们为 KVM 客户机创建了一个原始镜像类型。在这个配方中,我们将使用 QEMU Copy-On-Write (QCOW2) 镜像格式作为 KVM 实例的支持存储,因为原始镜像格式不支持快照。
使用 QCOW2 镜像格式,我们可以创建一个包含客户操作系统以及虚拟机所需一切的基础镜像,然后在原始基础镜像之上创建几个写时复制叠加磁盘镜像。这些新的叠加镜像可以立即在新的虚拟机中使用,通过创建指向新镜像的 XML 定义文件。
在继续进行 libvirt 快照操作之前,让我们看一个使用 QEMU 创建镜像叠加的示例:
- 要收集 QCOW2 镜像的信息,我们可以使用
qemu-img
工具:
root@kvm:~# qemu-img info kvm1.qcow2
image: kvm1.qcow2
file format: qcow2
virtual size: 8.0G (8589934592 bytes)
disk size: 2.4G
cluster_size: 65536
Format specific information:
compat: 1.1
lazy refcounts: false
root@kvm:~#
要将现有的原始镜像转换为 QCOW2,运行:
root@kvm:~# qemu-img convert -f raw -O qcow2 /var/lib/libvirt/images/kvm1.img /var/lib/libvirt/images/kvm1.qcow2
- 让我们基于前面的 qcow2 镜像创建一个新的叠加镜像:
root@kvm:~# qemu-img create -f qcow2 -b /var/lib/libvirt/images/kvm1_copy.qcow2 /var/lib/libvirt/images/kvm1_copy_2.qcow2
Formatting '/var/lib/libvirt/images/kvm1_copy_2.qcow2', fmt=qcow2 size=8589934592 backing_file='/var/lib/libvirt/images/kvm1_copy.qcow2' encryption=off cluster_size=65536 lazy_refcounts=off
root@kvm:~#
- 获取新叠加镜像的信息,现在显示它所基于的支持文件:
root@kvm:~# qemu-img info kvm1_copy.qcow2
image: kvm1_copy.qcow2
file format: qcow2
virtual size: 8.0G (8589934592 bytes)
disk size: 196K
cluster_size: 65536
backing file: /var/lib/libvirt/images/kvm1.qcow2
Format specific information:
compat: 1.1
lazy refcounts: false
root@kvm:~#
- 我们可以从前一个叠加文件创建一个新的叠加文件:
root@kvm:~# qemu-img create -f qcow2 -b /var/lib/libvirt/images/kvm1_copy.qcow2 /var/lib/libvirt/images/kvm1_copy_2.qcow2
Formatting '/var/lib/libvirt/images/kvm1_copy_2.qcow2', fmt=qcow2 size=8589934592 backing_file='/var/lib/libvirt/images/kvm1_copy.qcow2' encryption=off cluster_size=65536 lazy_refcounts=off
root@kvm:~# qemu-img info /var/lib/libvirt/images/kvm1_copy_2.qcow2
image: /var/lib/libvirt/images/kvm1_copy_2.qcow2
file format: qcow2
virtual size: 8.0G (8589934592 bytes)
disk size: 196K
cluster_size: 65536
backing file: /var/lib/libvirt/images/kvm1_copy.qcow2
Format specific information:
compat: 1.1
lazy refcounts: false
root@kvm:~#
- 让我们列出最后一个叠加文件的整个镜像链:
root@kvm:~# qemu-img info --backing-chain /var/lib/libvirt/images/kvm1_copy_2.qcow2
image: /var/lib/libvirt/images/kvm1_copy_2.qcow2
file format: qcow2
virtual size: 8.0G (8589934592 bytes)
disk size: 196K
cluster_size: 65536
backing file: /var/lib/libvirt/images/kvm1_copy.qcow2
Format specific information:
compat: 1.1
lazy refcounts: false
image: /var/lib/libvirt/images/kvm1_copy.qcow2
file format: qcow2
virtual size: 8.0G (8589934592 bytes)
disk size: 196K
cluster_size: 65536
backing file: /var/lib/libvirt/images/kvm1.qcow2
Format specific information:
compat: 1.1
lazy refcounts: false
image: /var/lib/libvirt/images/kvm1.qcow2
file format: qcow2
virtual size: 8.0G (8589934592 bytes)
disk size: 2.4G
cluster_size: 65536
Format specific information:
compat: 1.1
lazy refcounts: false
root@kvm:~#
Libvirt 使用 QCOW2 镜像格式的能力创建叠加快照链,这些快照可以用作备份或新虚拟机的模板。一旦创建了叠加,原始基础镜像将被视为只读。对基础镜像的修改(在此示例中,kvm1.qcow2
和 kvm1_copy.qcow2
,因为它们都是 kvm1_copy_2.qcow2
镜像的基础镜像)是不推荐的。以下是我们之前创建的叠加镜像文件链的示意图:
叠加的 QCOW2 镜像链,每一个都作为下一个的基础镜像
准备工作
对于这个配方,我们需要以下内容:
-
一个没有附加快照的现有 QCOW2 镜像的 libvirt 主机
-
一个正在运行的 KVM 实例
-
QEMU 工具集
如何操作...
创建一个新的 KVM 快照,请按以下步骤操作:
- 创建正在运行实例的内部快照:
root@kvm:~# virsh snapshot-create kvm1
Domain snapshot 1492797458 created
root@kvm:~#
- 检查新快照配置的位置:
root@kvm:~# ls -la /var/lib/libvirt/qemu/snapshot/
total 12
drwxr-xr-x 3 libvirt-qemu kvm 4096 Apr 21 14:05 .
drwxr-x--- 6 libvirt-qemu kvm 4096 Apr 21 14:04 ..
drwxr-xr-x 2 root root 4096 Apr 21 17:57 kvm1
root@kvm:~# ls -la /var/lib/libvirt/qemu/snapshot/kvm1/
total 12
drwxr-xr-x 2 root root 4096 Apr 21 17:57 .
drwxr-xr-x 3 libvirt-qemu kvm 4096 Apr 21 14:05 ..
-rw------- 1 root root 3089 Apr 21 17:57 1492797458.xml
root@kvm:~#
- 检查快照的 XML 定义:
root@kvm:~# cat /var/lib/libvirt/qemu/snapshot/kvm1/1492797458.xml
<!--
WARNING: THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE
OVERWRITTEN AND LOST. Changes to this xml configuration should be made using:
virsh snapshot-edit
or other application using the libvirt API.
-->
<domainsnapshot>
<name>1492797458</name>
<state>running</state>
<creationTime>1492797458</creationTime>
<memory snapshot='internal'/>
<disks>
<disk name='hda' snapshot='internal'/>
</disks>
<domain type='kvm'>
<name>kvm1</name>
...
</domain>
<active>1</active>
</domainsnapshot>
root@kvm:~#
- 收集基础镜像的信息:
root@kvm:~# qemu-img info /var/lib/libvirt/images/kvm1.qcow2
image: /var/lib/libvirt/images/kvm1.qcow2
file format: qcow2
virtual size: 8.0G (8589934592 bytes)
disk size: 2.4G
cluster_size: 65536
Snapshot list:
ID TAG VM SIZE DATE VM CLOCK
1 1492797458 155M 2017-04-21 17:57:38 03:41:16.790
Format specific information:
compat: 1.1
lazy refcounts: false
root@kvm:~#
- 获取虚拟机上磁盘设备的信息:
root@kvm:~# virsh domblklist kvm1
Target Source
------------------------------------------------
hda /var/lib/libvirt/images/kvm1.qcow2
root@kvm:~#
- 创建一个外部的,仅磁盘的快照:
root@kvm:~# virsh snapshot-create-as kvm1 kvm1_ext_snapshot "Disk only external snapshot for kvm1" --disk-only --diskspec hda,snapshot=external,file=/var/lib/libvirt/images/kvm1_disk_external.qcow2
Domain snapshot kvm1_ext_snapshot created
root@kvm:~#
- 获取外部快照的信息:
root@kvm:~# qemu-img info /var/lib/libvirt/images/kvm1_disk_external.qcow2
image: /var/lib/libvirt/images/kvm1_disk_external.qcow2
file format: qcow2
virtual size: 8.0G (8589934592 bytes)
disk size: 196K
cluster_size: 65536
backing file: /var/lib/libvirt/images/kvm1.qcow2
backing file format: qcow2
Format specific information:
compat: 1.1
lazy refcounts: false
root@kvm:~#
它是如何工作的…
有两种主要类型的快照:
-
内部快照:基础镜像文件本身包含保存的状态以及虚拟机的所有后续更改
-
外部快照:基础镜像将包含虚拟机的保存状态,从而成为一个只读的基础镜像,并创建一个新的叠加镜像来跟踪未来的任何更改。
两种类型的快照可以仅在虚拟机的磁盘或内存上执行,无论虚拟机是在线还是已停止。
在之前的步骤 1 中,我们创建了虚拟机的一个内部快照。快照完成后,只有一个镜像文件:原始镜像,现在包含了快照。我们可以在步骤 4 的 快照列表 部分看到这个镜像是一个快照。
在步骤 6 中,我们执行一个外部的仅磁盘快照,通过指定虚拟机磁盘、名称和快照的存储位置来完成。请注意,快照后,会创建一个新镜像文件来跟踪任何后续的更改。我们将在步骤 7 中检查该文件。请注意,基础文件是原始的 qcow2 镜像。
要执行磁盘快照,libvirt 利用 QEMU 功能,就像我们在创建叠加镜像时看到的 qemu-img
命令一样。
我们现在可以将快照保存为备份,或者使用它们启动新的虚拟机。在接下来的示例中,我们将看到如何使用和操作这些快照。
列出快照
在之前的示例中,我们为同一个 KVM 实例创建了两个快照:一个内部快照和一个仅磁盘的外部快照。在这个示例中,我们将学习如何列出现有的快照。
准备工作
对于此示例,我们需要:
-
配有 QEMU 工具集的 libvirt 主机
-
一个正在运行的 KVM 实例
-
我们在 创建快照 示例中创建的快照
如何执行...
要列出所有现有的快照,请按照以下步骤操作:
- 列出指定 KVM 实例的所有快照:
root@kvm:~# virsh snapshot-list kvm1
Name Creation Time State
------------------------------------------------------------
1492797458 2017-04-21 17:57:38 +0000 running
kvm1_ext_snapshot 2017-04-21 18:08:49 +0000 disk-snapshot
root@kvm:~#
- 仅列出基于磁盘的快照:
root@kvm:~# virsh snapshot-list --disk-only kvm1
Name Creation Time State
------------------------------------------------------------
kvm1_ext_snapshot 2017-04-21 18:08:49 +0000 disk-snapshot
root@kvm:~#
- 仅列出内部快照:
root@kvm:~# virsh snapshot-list --internal kvm1
Name Creation Time State
------------------------------------------------------------
1492797458 2017-04-21 17:57:38 +0000 running
root@kvm:~#
- 仅列出外部快照:
root@kvm:~# virsh snapshot-list --external kvm1
Name Creation Time State
------------------------------------------------------------
kvm1_ext_snapshot 2017-04-21 18:08:49 +0000 disk-snapshot
root@kvm:~#
- 以层次树格式列出所有镜像:
root@kvm:~# virsh snapshot-list --tree kvm1
1492797458
|
+- kvm1_ext_snapshot
root@kvm:~#
它是如何工作的...
我们使用了多功能的 virsh snapshot-list
命令来列出指定虚拟机的所有内部和外部快照。请注意,我们如何可以通过直接在镜像文件上使用 qemu-img
命令获取类似的信息,就像我们在本章之前看到的那样。然而,libvirt 提供的列出快照的 API 调用要方便得多。在下一章中,我们将看到如何使用 libvirt 的 Python 绑定来操作 KVM 实例及其快照。
检查快照
在这个简短的示例中,我们将看到如何获取现有虚拟机快照的更多信息。
准备工作
对于此示例,我们需要以下内容:
-
配有 QEMU 工具集的 libvirt 主机
-
我们在 创建快照 示例中创建的快照
如何执行...
要检查一个快照,请运行以下命令:
- 列出指定 KVM 实例的所有可用快照:
root@kvm:~# virsh snapshot-list kvm1
Name Creation Time State
------------------------------------------------------------
1492797458 2017-04-21 17:57:38 +0000 running
kvm1_ext_snapshot 2017-04-21 18:08:49 +0000 disk-snapshot
root@kvm:~#
- 获取正在运行的快照的信息:
root@kvm:~# virsh snapshot-info kvm1 --snapshotname 1492797458
Name: 1492797458
Domain: kvm1
Current: no
State: running
Location: internal
Parent: -
Children: 1
Descendants: 1
Metadata: yes
root@kvm:~#
- 获取磁盘快照的信息:
root@kvm:~# virsh snapshot-info kvm1 --snapshotname kvm1_ext_snapshot
Name: kvm1_ext_snapshot
Domain: kvm1
Current: yes
State: disk-snapshot
Location: external
Parent: 1492797458
Children: 0
Descendants: 0
Metadata: yes
root@kvm:~#
- 导出磁盘快照的 XML 配置:
root@kvm:~# virsh snapshot-dumpxml kvm1 --snapshotname kvm1_ext_snapshot --security-info
<domainsnapshot>
<name>kvm1_ext_snapshot</name>
<description>Disk only external snapshot for kvm1</description>
<state>disk-snapshot</state>
<parent>
<name>1492797458</name>
</parent>
<creationTime>1492798129</creationTime>
<memory snapshot='no'/>
<disks>
<disk name='hda' snapshot='external' type='file'>
<driver type='qcow2'/>
<source file='/var/lib/libvirt/images/kvm1_disk_external.qcow2'/>
</disk>
</disks>
<domain type='kvm'>
<name>kvm1</name>
...
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='qcow2'/>
<source file='/var/lib/libvirt/images/kvm1.qcow2'/>
<target dev='hda' bus='ide'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
...
</devices>
<seclabel type='none'/>
</domain>
</domainsnapshot>
工作原理...
在第 1 步中,我们列出 kvm1 虚拟机的所有可用快照。在第 2 步和第 3 步中,我们获取有关这些快照的信息。特别需要注意的是Parent
和Children
字段,它们显示了快照的层级关系。
在第 4 步中,我们检查 KVM 来宾的 XML 定义以及仅包含磁盘的外部快照。我们可以观察到snapshot='external'
类型和使用<source file='/var/lib/libvirt/images/kvm1_disk_external.qcow2'/>
标记指定的基础镜像位置。
编辑快照
在本教程中,我们将编辑现有快照的 XML 定义并检查更改。
准备工作
本教程中,我们将需要以下内容:
-
配备 QEMU 工具集的 libvirt 主机
-
我们在创建快照章节中创建的快照
操作步骤...
要编辑快照,请运行以下命令:
- 列出指定 KVM 实例的所有可用快照:
root@kvm:~# virsh snapshot-list kvm1
Name Creation Time State
------------------------------------------------------------
1492797458 2017-04-21 17:57:38 +0000 running
kvm1_ext_snapshot 2017-04-21 18:08:49 +0000 disk-snapshot
root@kvm:~#
- 编辑磁盘快照并更改其名称和描述:
root@kvm:~# virsh snapshot-edit kvm1 --snapshotname kvm1_ext_snapshot --rename
<domainsnapshot>
<name>kvm1_ext_snapshot_renamed</name>
<description>Disk only external snapshot for kvm1</description>
...
root@kvm:~#
- 更新后的快照列表:
root@kvm:~# virsh snapshot-list kvm1
Name Creation Time State
------------------------------------------------------------
1492797458 2017-04-21 17:57:38 +0000 running
kvm1_ext_snapshot_renamed 2017-04-21 18:08:49 +0000 disk-snapshot
root@kvm:~#
工作原理...
Libvirt 提供了一种编辑虚拟机快照定义的方法。我们可以更改各种 XML 属性,如快照名称、描述或镜像文件的位置。在第 1 步中,我们列出指定 KVM 实例的所有可用快照,然后继续更新磁盘镜像的名称和描述。最后,在第 3 步中,我们可以看到更改后的外部快照名称。
恢复快照
在本教程中,我们将创建一个正在运行的实例的内部快照,进行一次更改,然后使用该快照恢复回原始实例状态。
准备工作
本教程中,我们将需要以下内容:
-
带有现有 QCOW2 镜像的 libvirt 主机
-
使用 QCOW2 镜像的正在运行的 KVM 实例
-
QEMU 工具集
操作步骤...
要将 KVM 实例的状态恢复到旧状态,请从现有快照中运行以下命令:
- 连接到 KVM 实例并创建一个新文件:
root@kvm:~# virsh console kvm1
Connected to domain kvm1
Escape character is ^]
root@debian:~# touch SNAPSHOT
root@debian:~#
root@kvm:~#
- 创建虚拟机的内部快照:
root@kvm:~# virsh snapshot-create kvm1
Domain snapshot 1492802417 created
root@kvm:~#
- 获取关于快照的信息:
root@kvm:~# virsh snapshot-info kvm1 --snapshotname 1492802417
Name: 1492802417
Domain: kvm1
Current: yes
State: running
Location: internal
Parent: 1492797458
Children: 0
Descendants: 0
Metadata: yes
root@kvm:~#
- 重新连接到虚拟机并删除第 1 步中创建的文件:
root@kvm:~# virsh console kvm1
Connected to domain kvm1
Escape character is ^]
root@debian:~# rm -f SNAPSHOT
root@debian:~#
root@kvm:~#
- 从最新的快照恢复实例:
root@kvm:~# virsh snapshot-revert kvm1 --snapshotname 1492802417
root@kvm:~#
- 连接到虚拟机并确认我们在上一步删除的文件是否已重新存在:
root@kvm:~# virsh console kvm1
Connected to domain kvm1
Escape character is ^]
root@debian:~# ls -la SNAPSHOT
-rw-r--r-- 1 root root 0 Apr 21 14:08 SNAPSHOT
root@debian:~#
root@kvm:~#
工作原理...
在第 1 步中,我们通过控制台连接到 KVM 实例并创建一个空文件。我们将使用该文件跟踪虚拟机上的更改。在第 2 步中,我们创建一个内部快照,并在第 3 步中获取更多信息。在第 4 步中,我们再次连接到 KVM 来宾并删除该文件。在第 5 步中,我们从快照恢复,确认实例的状态确实已恢复到快照之前,如我们之前创建的原始文件所示。
删除快照
在这个快速指南中,我们将使用 libvirt 删除之前在创建快照章节中创建的快照。
准备工作
对于本教程,我们只需要以下内容:
-
配备 QEMU 工具集的 libvirt 主机
-
我们在创建快照教程中创建的快照
如何执行……
要删除快照,请按照以下步骤操作:
- 列出主机上所有的快照:
root@kvm:~# virsh snapshot-list kvm1
Name Creation Time State
------------------------------------------------------------
1492797458 2017-04-21 17:57:38 +0000 running
1492802417 2017-04-21 19:20:17 +0000 running
kvm1_ext_snapshot_renamed 2017-04-21 18:08:49 +0000 disk-snapshot
root@kvm:~#
- 基于创建时间删除最新的快照:
root@kvm:~# virsh snapshot-delete kvm1 --snapshotname 1492802417
Domain snapshot 1492802417 deleted
root@kvm:~#
- 列出剩余的快照:
root@kvm:~# virsh snapshot-list kvm1
Name Creation Time State
------------------------------------------------------------
1492797458 2017-04-21 17:57:38 +0000 running
kvm1_ext_snapshot_renamed 2017-04-21 18:08:49 +0000 disk-snapshot
root@kvm:~#
- 删除最新的快照:
root@kvm:~# virsh snapshot-delete kvm1 --current
Domain snapshot 1492797458 deleted
root@kvm:~#
它是如何工作的……
在第 1 步中,我们列出主机操作系统上的所有快照。然后,在第 2 步中,我们删除最新的快照,指定其名称。在第 3 步中,我们验证该快照确实已被删除。最后,在第 4 步中,我们通过指定--current 标志删除最新的镜像。
请注意,为了删除或恢复外部快照,您需要一个版本高于 1.2.2 的 libvirt。如果您的 Linux 发行版在其软件库中没有提供更新的版本,您将需要从源代码编译 libvirt。
第六章:使用 OpenStack 部署 KVM 实例
在本章中,我们将涵盖以下主题:
-
为 OpenStack 部署准备主机
-
安装和配置 OpenStack Keystone 身份认证服务
-
安装和配置 OpenStack Glance 镜像服务
-
安装和配置 OpenStack Nova 计算服务
-
安装和配置 OpenStack Neutron 网络服务
-
使用 OpenStack 构建和检查 KVM 实例
-
停止 OpenStack 中的 KVM 实例
-
终止 OpenStack 中的 KVM 实例
介绍
OpenStack 是一个云操作系统,它简化了虚拟机或容器的部署和管理,以可扩展且高可用的方式进行操作。它在计算资源池(物理或虚拟服务器)上运行,并提供智能调度机制,选择合适的主机来构建或迁移虚拟机。
OpenStack 允许更轻松地管理虚拟镜像,并提供集中式的创建和管理软件定义网络的方式。它与各种外部和内部项目集成,以提供用户和服务认证。OpenStack 的模块化设计允许根据需要添加和移除服务,在最小化的生产环境部署中,可能仅由两个项目(镜像服务和计算服务)组成。
以下图表显示了不断增长的 OpenStack 项目列表及其之间的相互作用:
OpenStack 组件及其相互作用
在本章中,我们将使用 OpenStack Newton 版本中的 Keystone、Glance、Nova 和 Neutron 项目,在 Ubuntu Xenial 16.04 服务器上创建一个简单的 OpenStack 部署,使用两台计算节点。
获取有关 OpenStack 项目的更多信息,请访问 www.openstack.org/software/
。
为 OpenStack 部署准备主机
在本指南中,我们将安装 OpenStack 所依赖的基础设施组件,如数据库服务器、消息队列和缓存服务。我们将在本章中使用的项目依赖这些服务进行通信和持久化存储。
准备工作
本指南中,我们将需要以下组件:
-
一台具有强大虚拟化能力的 Ubuntu 服务器
-
需要访问互联网以安装软件包
如何操作…
为了简化部署并专注于 OpenStack 的配置方面,我们将使用一台物理服务器来托管所有服务。在生产环境中,通常会将每个服务分配到各自的服务器集群上,以实现可扩展性和高可用性。通过本章中概述的步骤,你应该能够在多台主机上部署所有服务,只需根据需要替换配置文件中的 IP 地址和主机名。
- 更新主机并安装 Newton 版本的软件包仓库:
root@controller:~# apt install software-properties-common
root@controller:~# add-apt-repository cloud-archive:newton
root@controller:~# apt update && apt dist-upgrade
root@controller:~# reboot
root@controller:~# apt install python-openstackclient
- 安装 MariaDB 数据库服务器:
root@controller:~# apt install mariadb-server python-pymysql
root@controller:~# cat /etc/mysql/mariadb.conf.d/99-openstack.cnf
[mysqld]
bind-address = 10.208.130.36
default-storage-engine = innodb
innodb_file_per_table
max_connections = 4096
collation-server = utf8_general_ci
character-set-server = utf8
root@controller:~#
根据你的主机,替换数据库服务器绑定的网络接口的 IP 地址。
- 重启服务并确保安装安全:
root@controller:~# service mysql restart
root@controller:~# mysql_secure_installation
为了简便起见,我们将在本章中使用 lxcpassword
作为所有服务的密码。
- 安装 RabbitMQ 消息服务,创建一个新用户、密码并设置权限:
root@controller:~# apt install rabbitmq-server
root@controller:~# rabbitmqctl add_user openstack lxcpassword
Creating user "openstack" ...
root@controller:~# rabbitmqctl set_permissions openstack ".*" ".*" ".*"
Setting permissions for user "openstack" in vhost "/" ...
root@controller:~#
- 安装并配置
memcached
服务:
root@controller:~# apt install memcached python-memcache
root@controller:~# sed -i 's/127.0.0.1/10.208.130.36/g'
/etc/memcached.conf
root@controller:~# cat /etc/memcached.conf | grep -vi "#" | sed
'/^$/d'
-d
logfile /var/log/memcached.log
-m 64
-p 11211
-u memcache
-l 10.208.130.36
root@controller:~# service memcached restart
root@controller:~#
它是如何工作的……
OpenStack 使用 SQL 数据库,如 Mysql/MariaDB/Percona,来存储关于其服务的信息。在接下来的教程中,我们将为 Keystone、Glance、Nova 和 Neutron 项目创建数据库。我们在第 1 到第 3 步安装和配置 MariaDB。
我们在第 4 步安装并配置的消息队列提供了一种集中方式,让各个服务通过生成和消费消息来相互通信。OpenStack 支持几种不同的消息总线实现,如 RabbitMQ、Qpid 和 ZeroMQ。
身份服务 Keystone 使用 memcached
守护进程缓存认证令牌。我们将在第 5 步安装并配置它。
安装和配置 OpenStack Keystone 身份服务
Keystone 项目提供的身份服务是一个集中点,用于管理认证和授权,其他 OpenStack 组件,如 Nova 计算和镜像服务 Glance,都会使用它。Keystone 还维护着一个服务目录,用户可以通过查询定位服务和它们提供的端点。
在这个教程中,我们将安装并配置 Keystone,创建两个项目(服务的所有权单元),并将用户和角色分配给这些项目。
准备工作
对于这个教程,我们将需要以下内容:
-
一台具有卓越虚拟化能力的 Ubuntu 服务器
-
访问互联网以安装软件包
-
如 为 OpenStack 部署准备主机 中所述,安装并配置数据库服务器、消息队列和
memcached
。
如何操作……
为了安装、配置、创建新项目、用户角色和凭据,请按照这里提供的步骤顺序执行:
- 创建 keystone 数据库并授予 keystone 用户权限:
root@controller:~# mysql -u root -plxcpassword
MariaDB [(none)]> CREATE DATABASE keystone;
Query OK, 1 row affected (0.01 sec)
MariaDB [(none)]> GRANT ALL PRIVILEGES ON keystone.* TO
'keystone'@'localhost' IDENTIFIED BY 'lxcpassword';
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> GRANT ALL PRIVILEGES ON keystone.* TO
'keystone'@'%' IDENTIFIED BY 'lxcpassword';
Query OK, 0 rows affected (0.01 sec)
MariaDB [(none)]> exit
Bye
root@controller:~#
- 从我们之前配置的仓库中安装身份服务 Keystone:
root@controller:~# apt install keystone
- 创建以下最小化的 Keystone 配置:
root@controller:~# cat /etc/keystone/keystone.conf
[DEFAULT]
log_dir = /var/log/keystone
[assignment]
[auth]
[cache]
[catalog]
[cors]
[cors.subdomain]
[credential]
[database]
connection = mysql+pymysql://keystone:lxcpassword@controller/keystone
[domain_config]
[endpoint_filter]
[endpoint_policy]
[eventlet_server]
[federation]
[fernet_tokens]
[identity]
[identity_mapping]
[kvs]
[ldap]
[matchmaker_redis]
[memcache]
[oauth1]
[os_inherit]
[oslo_messaging_amqp]
[oslo_messaging_notifications]
[oslo_messaging_rabbit]
[oslo_messaging_zmq]
[oslo_middleware]
[oslo_policy]
[paste_deploy]
[policy]
[profiler]
[resource]
[revoke]
[role]
[saml]
[security_compliance]
[shadow_users]
[signing]
[token]
provider = fernet
[tokenless_auth]
[trust]
[extra_headers]
Distribution = Ubuntu
root@controller:~#
- 填充 Keystone 数据库:
root@controller:~# su -s /bin/sh -c "keystone-manage db_sync" keystone
...
root@controller:~#
- 初始化 Fernet 密钥存储库:
root@controller:~# keystone-manage fernet_setup --keystone-user keystone --keystone-group keystone
root@controller:~# keystone-manage credential_setup --keystone-user keystone --keystone-group keystone
root@controller:~#
- 启动 Keystone 服务:
root@controller:~# keystone-manage bootstrap --bootstrap-password lxcpassword --bootstrap-admin-url http://controller:35357/v3/ --bootstrap-internal-url http://controller:35357/v3/ --bootstrap-public-url http://controller:5000/v3/ --bootstrap-region-id RegionOne
root@controller:~#
- 在 Apache 中添加以下配置,并重启服务:
root@controller:~# cat /etc/apache2/apache2.conf
...
ServerName controller
...
root@controller:~# service apache2 restart
- 删除与 Keystone 一起打包的默认 SQLite 数据库:
root@controller:~# rm -f /var/lib/keystone/keystone.db
- 通过定义以下环境变量来创建一个管理员帐户:
root@controller:~# export OS_USERNAME=admin
root@controller:~# export OS_PASSWORD=lxcpassword
root@controller:~# export OS_PROJECT_NAME=admin
root@controller:~# export OS_USER_DOMAIN_NAME=default
root@controller:~# export OS_PROJECT_DOMAIN_NAME=default
root@controller:~# export OS_AUTH_URL=http://controller:35357/v3
root@controller:~# export OS_IDENTITY_API_VERSION=3
root@controller:~#
- 在 Keystone 中为服务创建一个项目并列出它:
root@controller:~# openstack project create --domain default --description "KVM Project" service
+-------------+-----------------------------------+
| Field | Value |
+-------------+-----------------------------------+
| description | KVM Project |
| domain_id | default |
| enabled | True |
| id | 9a1a863fe41b42b2955b313f2cca0ef0 |
| is_domain | False |
| name | service |
| parent_id | default |
+-------------+-----------------------------------+
root@controller:~# openstack project list
+----------------------------------+---------+
| ID | Name |
+----------------------------------+---------+
| 06f4e2d7e384474781803395b24b3af2 | admin |
| 9a1a863fe41b42b2955b313f2cca0ef0 | service |
+----------------------------------+---------+
root@controller:~#
- 创建一个无特权的项目和一个用户,这个用户将被常规客户端使用,而不是 OpenStack 服务:
root@controller:~# openstack project create --domain default --description "KVM User Project" kvm
+-------------+----------------------------------+
| Field | Value |
+-------------+----------------------------------+
| description | KVM User Project |
| domain_id | default |
| enabled | True |
| id | eb9cdc2c2b4e4f098f2d104752970d52 |
| is_domain | False |
| name | kvm |
| parent_id | default |
+-------------+----------------------------------+
root@controller:~#
root@controller:~# openstack user create --domain default --password-prompt kvm
User Password:
Repeat User Password:
+---------------------+----------------------------------+
| Field | Value |
+---------------------+----------------------------------+
| domain_id | default |
| enabled | True |
| id | 1e83e0c8ca194f2e9d8161eb61d21030 |
| name | kvm |
| password_expires_at | None |
+---------------------+----------------------------------+
root@controller:~#
- 创建一个用户角色并将其与 KVM 项目和用户关联:
root@controller:~# openstack role create user
+-----------+----------------------------------+
| Field | Value |
+-----------+----------------------------------+
| domain_id | None |
| id | 331c0b61e9784112874627264f03a058 |
| name | user |
+-----------+----------------------------------+
root@controller:~# openstack role add --project kvm --user kvm user
root@controller:~#
- 配置 Web 服务网关接口 (WSGI) 中间件管道以供 Keystone 使用:
root@controller:~# cat /etc/keystone/keystone-paste.ini
# Keystone PasteDeploy configuration file.
[filter:debug]
use = egg:oslo.middleware#debug
[filter:request_id]
use = egg:oslo.middleware#request_id
[filter:build_auth_context]
use = egg:keystone#build_auth_context
[filter:token_auth]
use = egg:keystone#token_auth
[filter:admin_token_auth]
use = egg:keystone#admin_token_auth
[filter:json_body]
use = egg:keystone#json_body
[filter:cors]
use = egg:oslo.middleware#cors
oslo_config_project = keystone
[filter:http_proxy_to_wsgi]
use = egg:oslo.middleware#http_proxy_to_wsgi
[filter:ec2_extension]
use = egg:keystone#ec2_extension
[filter:ec2_extension_v3]
use = egg:keystone#ec2_extension_v3
[filter:s3_extension]
use = egg:keystone#s3_extension
[filter:url_normalize]
use = egg:keystone#url_normalize
[filter:sizelimit]
use = egg:oslo.middleware#sizelimit
[filter:osprofiler]
use = egg:osprofiler#osprofiler
[app:public_service]
use = egg:keystone#public_service
[app:service_v3]
use = egg:keystone#service_v3
[app:admin_service]
use = egg:keystone#admin_service
[pipeline:public_api]
pipeline = cors sizelimit http_proxy_to_wsgi osprofiler url_normalize request_id build_auth_context token_auth json_body ec2_extension public_service
[pipeline:admin_api]
pipeline = cors sizelimit http_proxy_to_wsgi osprofiler url_normalize request_id build_auth_context token_auth json_body ec2_extension s3_extension admin_service
[pipeline:api_v3]
pipeline = cors sizelimit http_proxy_to_wsgi osprofiler url_normalize request_id build_auth_context token_auth json_body ec2_extension_v3 s3_extension service_v3
[app:public_version_service]
use = egg:keystone#public_version_service
[app:admin_version_service]
use = egg:keystone#admin_version_service
[pipeline:public_version_api]
pipeline = cors sizelimit osprofiler url_normalize public_version_service
[pipeline:admin_version_api]
pipeline = cors sizelimit osprofiler url_normalize admin_version_service
[composite:main]
use = egg:Paste#urlmap
/v2.0 = public_api
/v3 = api_v3
/ = public_version_api
[composite:admin]
use = egg:Paste#urlmap
/v2.0 = admin_api
/v3 = api_v3
/ = admin_version_api
root@controller:~#
- 为管理员和 KVM 用户请求令牌:
root@controller:~# openstack --os-auth-url http://controller:35357/v3 --os-project-domain-name default --os-user-domain-name default --os-project-name admin --os-username admin token issue
+------------+----------------------------------+
| Field | Value |
+------------+----------------------------------+
| expires | 2017-04-26 18:29:03+00:00 |
| id | gAAAAABZMIdwefsdfB8e4rFk5IALgM4U |
| project_id | 123c1e6f33584dd1876c0a34249a6e11 |
| user_id | cc14c5dbbd654c438e52d38efaf4f1a6 |
+------------+----------------------------------+
root@controller:~# openstack --os-auth-url http://controller:5000/v3 --os-project-domain-name default --os-user-domain-name default --os-project-name kvm --os-username kvm token issue
+------------+----------------------------------+
| Field | Value |
+------------+----------------------------------+
| expires | 2017-04-26 18:29:52+00:00 |
| id | gAAAAABZANkQmInUifl6Up_PzdH_9OHd |
| project_id | 10a92eccbad9439d9e56c4edda6b211f |
| user_id | a186b226ed1e4717b25bb978f2bc9958 |
+------------+----------------------------------+
root@controller:~#
- 创建包含管理员和用户凭证的文件:
root@controller:~# cat rc.admin
export OS_PROJECT_DOMAIN_NAME=default
export OS_USER_DOMAIN_NAME=default
export OS_PROJECT_NAME=admin
export OS_USERNAME=admin
export OS_PASSWORD=lxcpassword
export OS_AUTH_URL=http://controller:35357/v3
export OS_IDENTITY_API_VERSION=3
export OS_IMAGE_API_VERSION=2
root@controller:~#
root@controller:~# cat rc.kvm
export OS_PROJECT_DOMAIN_NAME=default
export OS_USER_DOMAIN_NAME=default
export OS_PROJECT_NAME=kvm
export OS_USERNAME=kvm
export OS_PASSWORD=lxcpassword
export OS_AUTH_URL=http://controller:5000/v3
export OS_IDENTITY_API_VERSION=3
export OS_IMAGE_API_VERSION=2
root@controller:~#
- 源代码管理员凭证文件:
root @ controller: ~ #. rc.admin
root @ controller: ~ #
- 为管理员用户请求认证令牌:
root@controller:~# openstack token issue
+------------+----------------------------------+
| Field | Value |
+------------+----------------------------------+
| expires | 2017-04-26 18:30:41+00:00 |
| id | gAAAAABZANlBdsu-DTmz6ME2Z8JFKjJM |
| project_id | 123c1e6f33584dd1876c0a34249a6e11 |
| user_id | cc14c5dbbd654c438e52d38efaf4f1a6 |
+------------+----------------------------------+
root@controller:~#
它是如何工作的…
我们首先在步骤 1 中在 MariaDB 中创建 Keystone 数据库,并授予必要的用户权限。在步骤 2 中,我们安装 Keystone 包。
在步骤 3 中,我们为服务创建配置文件。正如从输出中看到的,大多数选项已被省略,假定使用默认值。
在步骤 4 中,我们运行一个脚本,通过创建数据库架构来填充 Keystone 数据库。
Keystone 使用令牌来认证和授权用户及服务。可用的令牌格式包括 UUID、PKI 和 Fernet 令牌。对于本次部署,我们将使用 Fernet 令牌。Fernet 令牌不需要在后端存储中持久化。在步骤 5 中,我们初始化 Fernet 密钥库。
有关可用身份令牌的更多信息,请参考 docs.openstack.org/admin-guide/identity-tokens.html
。
在步骤 6 中,我们引导 Keystone,并在步骤 7 中更新 Apache 配置,在步骤 8 中进行一些清理。
在步骤 9 中,我们导出一个包含 Keystone 用户、密码和端点的环境变量列表。
在步骤 10 中,我们在 Keystone 中创建第一个项目,其他服务将使用该项目。项目代表所有权单元,其中所有资源都属于该项目。在步骤 11 和 12 中,我们创建一个无特权的项目及相关用户。
在步骤 13 中,我们为 Keystone 配置 WSGI 中间件管道。
在步骤 14 中,我们为管理员和 KVM 用户请求并获取令牌,在步骤 15 中,我们创建两个环境变量文件,以便在需要在用户之间切换时使用。
在步骤 16 和 17 中,我们源管理员凭证和项目端点,并获得授权令牌。
安装和配置 OpenStack Glance 镜像服务
Glance 镜像服务提供一个 API,供我们用于发现、注册和获取虚拟机镜像。稍后当我们使用 Nova 计算来构建新的 KVM 实例时,Nova 服务会向 Glance 发送请求以获取所需的镜像类型。
在本教程中,我们将安装 Glance 并注册一个新的 Ubuntu 镜像。
准备工作
对于本教程,我们将需要以下内容:
-
配备优秀虚拟化能力的 Ubuntu 服务器
-
具有安装包的网络访问权限
-
安装和配置好的数据库服务器、消息队列和
memcached
,如在 为 OpenStack 部署准备主机 教程中所述。 -
我们在 安装和配置 OpenStack Keystone 身份服务 教程中部署的 Keystone 服务
如何操作…
要安装、配置并在 Glance 注册图像,请按照以下步骤操作:
- 创建 Glance 数据库和用户:
root@controller:~# mysql -u root -plxcpassword
MariaDB [(none)]> CREATE DATABASE glance;
Query OK, 1 row affected (0.00 sec)
MariaDB [(none)]> GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'localhost' IDENTIFIED BY 'lxcpassword';
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> GRANT ALL PRIVILEGES ON glance.* TO 'glance'@'%' IDENTIFIED BY 'lxcpassword';
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> exit
Bye
root@controller:~#
- 创建 Glance 用户并将其添加到管理员角色:
root@controller:~# openstack user create --domain default --password-prompt glance
User Password:
Repeat User Password:
+---------------------+----------------------------------+
| Field | Value |
+---------------------+----------------------------------+
| domain_id | default |
| enabled | True |
| id | e566c6e2012148daa374cd68077b38df |
| name | glance |
| password_expires_at | None |
+---------------------+----------------------------------+
root@controller:~# openstack role add --project service --user glance admin
root@controller:~#
- 创建 Glance 服务定义:
root@controller:~# openstack service create --name glance --description "OpenStack Image" image
+-------------+----------------------------------+
| Field | Value |
+-------------+----------------------------------+
| description | OpenStack Image |
| enabled | True |
| id | d4d42a586551461c8b445b927f2144e1 |
| name | glance |
| type | image |
+-------------+----------------------------------+
root@controller:~#
- 在 Keystone 中创建 Glance API 端点:
root@controller:~# openstack endpoint create --region RegionOne image public http://controller:9292
+--------------+----------------------------------+
| Field | Value |
+--------------+----------------------------------+
| enabled | True |
| id | 618af0c845194f508752f230364d6e0e |
| interface | public |
| region | RegionOne |
| region_id | RegionOne |
| service_id | d4d42a586551461c8b445b927f2144e1 |
| service_name | glance |
| service_type | image |
| url | http://controller:9292 |
+--------------+----------------------------------+
root@controller:~# openstack endpoint create --region RegionOne image internal http://controller:9292
+--------------+----------------------------------+
| Field | Value |
+--------------+----------------------------------+
| enabled | True |
| id | 991a1b03f7194139b98bafe19acf3518 |
| interface | internal |
| region | RegionOne |
| region_id | RegionOne |
| service_id | d4d42a586551461c8b445b927f2144e1 |
| service_name | glance |
| service_type | image |
| url | http://controller:9292 |
+--------------+----------------------------------+
root@controller:~# openstack endpoint create --region RegionOne image admin http://controller:9292
+--------------+----------------------------------+
| Field | Value |
+--------------+----------------------------------+
| enabled | True |
| id | 991a1b03f7194139b98bafe19acf3322 |
| interface | admin |
| region | RegionOne |
| region_id | RegionOne |
| service_id | d4d42a586551461c8b445b927f2144e1 |
| service_name | glance |
| service_type | image |
| url | http://controller:9292 |
+--------------+----------------------------------+
root@controller:~#
- 安装 Glance 服务:
root@controller:~# apt install glance
- 配置服务:
root@controller:~# cat /etc/glance/glance-api.conf
[DEFAULT]
[cors]
[cors.subdomain]
[database]
connection = mysql+pymysql://glance:lxcpassword@controller/glance
[glance_store]
stores = file,http
default_store = file
filesystem_store_datadir = /var/lib/glance/images/
[image_format]
disk_formats = ami,ari,aki,vhd,vhdx,vmdk,raw,qcow2,vdi,iso,root-tar
[keystone_authtoken]
auth_uri = http://controller:5000
auth_url = http://controller:35357
memcached_servers = controller:11211
auth_type = password
project_domain_name = default
user_domain_name = default
project_name = service
username = glance
password = lxcpassword
[matchmaker_redis]
[oslo_concurrency]
[oslo_messaging_amqp]
[oslo_messaging_notifications]
[oslo_messaging_rabbit]
[oslo_messaging_zmq]
[oslo_middleware]
[oslo_policy]
[paste_deploy]
flavor = keystone
[profiler]
[store_type_location_strategy]
[task]
[taskflow_executor]
root@controller:~#
root@controller:~# cat /etc/glance/glance-registry.conf
[DEFAULT]
[database]
connection = mysql+pymysql://glance:lxcpassword@controller/glance
[keystone_authtoken]
auth_uri = http://controller:5000
auth_url = http://controller:35357
memcached_servers = controller:11211
auth_type = password
project_domain_name = default
user_domain_name = default
project_name = service
username = glance
password = lxcpassword
[matchmaker_redis]
[oslo_messaging_amqp]
[oslo_messaging_notifications]
[oslo_messaging_rabbit]
[oslo_messaging_zmq]
[oslo_policy]
[paste_deploy]
flavor = keystone
[profiler]
root@controller:~#
- 填充 Glance 数据库:
root@controller:~# su -s /bin/sh -c "glance-manage db_sync" glance
...
root@controller:~#
- 启动 Glance 服务守护进程:
root@controller:~# service glance-registry restart
root@controller:~# service glance-api restart
root@controller:~#
- 下载 Ubuntu 发行版的 QCOW2 图像:
root@controller:~# wget https://uec-images.ubuntu.com/releases/16.04/release-20170330/ubuntu-16.04-server-cloudimg-amd64-disk1.img
Saving to: ‘ubuntu-16.04-server-cloudimg-amd64-disk1.img’
ubuntu-16.04-server-cloudimg-amd64-disk1.img 100%[===================================================>] 309.75M 31.1MB/s in 13s
2017-04-26 17:40:21 (24.5 MB/s) - ‘ubuntu-16.04-server-cloudimg-amd64-disk1.img’ saved [324796416/324796416]
root@controller:~#
- 将图像添加到 Glance 服务:
root@controller:~# openstack image create "ubuntu_16.04" --file ubuntu-16.04-server-cloudimg-amd64-disk1.img --disk-format qcow2 --container-format bare --public
+------------------+------------------------------------------------------+
| Field | Value |
+------------------+------------------------------------------------------+
| checksum | 87b0b7a4b03dd0bb2177d5cc02c80720 |
| container_format | bare |
| created_at | 2017-04-26T17:41:44Z |
| disk_format | qcow2 |
| file | /v2/images/abce08d2-2f9f-4545-a414-32019d41c0cd/file |
| id | abce08d2-2f9f-4545-a414-32019d41c0cd |
| min_disk | 0 |
| min_ram | 0 |
| name | ubuntu_16.04 |
| owner | 123c1e6f33584dd1876c0a34249a6e11 |
| protected | False |
| schema | /v2/schemas/image |
| size | 324796416 |
| status | active |
| tags | |
| updated_at | 2017-04-26T17:41:45Z |
| virtual_size | None |
| visibility | public |
+------------------+------------------------------------------------------+
root@controller:~#
- 列出可用图像及其在文件系统上的位置:
root@controller:~# openstack image list
+--------------------------------------+--------------+--------+
| ID | Name | Status |
+--------------------------------------+--------------+--------+
| abce08d2-2f9f-4545-a414-32019d41c0cd | ubuntu_16.04 | active |
+--------------------------------------+--------------+--------+
root@controller:~# ls -lah /var/lib/glance/images/
drwxr-xr-x 2 glance glance 4.0K Apr 26 17:51 .
drwxr-xr-x 4 glance glance 4.0K Apr 26 17:32 ..
-rw-r----- 1 glance glance 310M Apr 26 17:41 abce08d2-2f9f-4545-a414-32019d41c0cd
root@controller:~# qemu-img info /var/lib/glance/images/abce08d2-2f9f-4545-a414-32019d41c0cd
image: /var/lib/glance/images/abce08d2-2f9f-4545-a414-32019d41c0cd
file format: qcow2
virtual size: 2.2G (2361393152 bytes)
disk size: 310M
cluster_size: 65536
Format specific information:
compat: 0.10
refcount bits: 16
root@controller:~#
它是如何工作的...
我们首先在第 1 步中在 MariaDB 中创建 Glance 数据库。
在第 2 步和第 3 步中,我们为 Glance 项目创建用户、角色和服务。在第 4 步中,我们在 Keystone 中定义 Glance API 服务端点。Nova 服务和 OpenStack 工具可以使用这些端点查询 Glance 可用的图像。
在第 5 步中,我们安装 Glance 包,并在第 6 步中创建一个最小配置文件。
然后我们在第 7 步中创建数据库模式,通过执行 glance-manage
Python 脚本并在第 8 步中重启 Glance 服务。
在第 9 步中,我们下载一个 QCOW2 Ubuntu 图像,并在第 10 步中将其添加到 Glance 注册表。
最后,在步骤 11 中,我们列出新添加的图像并在主机文件系统上查看它。
安装和配置 OpenStack Nova 计算服务
OpenStack 计算服务,代号 Nova,管理计算资源池及其上运行的虚拟机。Nova 是一组服务,用于创建和管理虚拟机的生命周期。我们将使用 Nova 创建、检查、停止、删除和迁移 KVM 实例。
获取有关各种 Nova 服务的更多信息,请参考:docs.openstack.org/developer/nova/
。
在本教程中,我们将安装和配置以下 Nova 组件:
-
nova-api
:这是通过 RESTful API 接受并响应用户请求的服务。我们将在创建、运行、停止和迁移 KVM 实例时使用它。 -
nova-scheduler
:这是根据过滤器(如可用内存、磁盘和 CPU 资源)决定在哪里配置实例的服务。 -
nova-compute
:这是在计算主机上运行的服务,负责管理 KVM 实例的生命周期,从配置到删除。 -
nova-conductor
:这是位于我们之前创建的 Nova 数据库和nova-compute
服务之间的服务。 -
nova-consoleauth
:这是授权用户使用各种控制台连接虚拟机的服务。 -
nova-novncproxy
:这是提供访问运行 VNC 实例的服务。
准备工作
对于本教程,我们需要:
-
一台具有强大虚拟化能力的 Ubuntu 服务器
-
安装软件包时需要访问互联网
-
按照为 OpenStack 部署准备主机中的说明,安装并配置数据库服务器、消息队列和
memcached
。 -
我们在安装和配置 OpenStack Keystone 身份服务中部署的 Keystone 服务。
-
我们在安装和配置 OpenStack Glance 镜像服务中部署的 Glance 服务。
如何做...
要安装和配置之前提到的 Nova 服务,请执行以下步骤:
- 在 MariaDB 中创建 Nova 数据库和用户:
root@controller:~# mysql -u root -plxcpassword
MariaDB [(none)]> CREATE DATABASE nova_api;
Query OK, 1 row affected (0.00 sec)
MariaDB [(none)]> CREATE DATABASE nova;
Query OK, 1 row affected (0.00 sec)
MariaDB [(none)]> GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'localhost' IDENTIFIED BY 'lxcpassword';
Query OK, 0 rows affected (0.03 sec)
MariaDB [(none)]> GRANT ALL PRIVILEGES ON nova_api.* TO 'nova'@'%' IDENTIFIED BY 'lxcpassword';
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'localhost' IDENTIFIED BY 'lxcpassword';
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> GRANT ALL PRIVILEGES ON nova.* TO 'nova'@'%' IDENTIFIED BY 'lxcpassword';
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> exit
Bye
root@controller:~#
- 创建 Nova 用户,并将其添加到身份服务中的管理员角色:
root@controller:~# openstack user create --domain default --password-prompt nova
User Password:
Repeat User Password:
+---------------------+----------------------------------+
| Field | Value |
+---------------------+----------------------------------+
| domain_id | default |
| enabled | True |
| id | 038aa8840aca449dbd3e653c5d2c5a08 |
| name | nova |
| password_expires_at | None |
+---------------------+----------------------------------+
root@controller:~# openstack role add --project service --user nova admin
root@controller:~#
- 创建 Nova 服务和端点:
root@controller:~# openstack service create --name nova --description "OpenStack Compute" compute
+-------------+----------------------------------+
| Field | Value |
+-------------+----------------------------------+
| description | OpenStack Compute |
| enabled | True |
| id | 04132edd7f654f56ba0cc23ac182c9aa |
| name | nova |
| type | compute |
+-------------+----------------------------------+
root@controller:~# openstack endpoint create --region RegionOne compute public http://controller:8774/v2.1/%(tenant_id)s
+--------------+-------------------------------------------+
| Field | Value |
+--------------+-------------------------------------------+
| enabled | True |
| id | 5fc54236c324412db135dff88807e820 |
| interface | public |
| region | RegionOne |
| region_id | RegionOne |
| service_id | 04132edd7f654f56ba0cc23ac182c9aa |
| service_name | nova |
| service_type | compute |
| url | http://controller:8774/v2.1/%(tenant_id)s |
+--------------+-------------------------------------------+
root@controller:~# openstack endpoint create --region RegionOne compute internal http://controller:8774/v2.1/%(tenant_id)s
+--------------+-------------------------------------------+
| Field | Value |
+--------------+-------------------------------------------+
| enabled | True |
| id | a0f623ed345e4bdb8fced929b7fe6b3f |
| interface | internal |
| region | RegionOne |
| region_id | RegionOne |
| service_id | 04132edd7f654f56ba0cc23ac182c9aa |
| service_name | nova |
| service_type | compute |
| url | http://controller:8774/v2.1/%(tenant_id)s |
+--------------+-------------------------------------------+
root@controller:~# openstack endpoint create --region RegionOne compute admin http://controller:8774/v2.1/%(tenant_id)s
+--------------+-------------------------------------------+
| Field | Value |
+--------------+-------------------------------------------+
| enabled | True |
| id | 3964db0d281545acbaa6c18abc44a216 |
| interface | admin |
| region | RegionOne |
| region_id | RegionOne |
| service_id | 04132edd7f654f56ba0cc23ac182c9aa |
| service_name | nova |
| service_type | compute |
| url | http://controller:8774/v2.1/%(tenant_id)s |
+--------------+-------------------------------------------+
root@controller:~#
- 安装 Nova 软件包,它将提供 API、指挥器、控制台和调度程序服务:
root@controller:~# apt install nova-api nova-conductor nova-consoleauth nova-novncproxy nova-scheduler
- 创建 Nova 配置文件:
root@controller:~# cat /etc/nova/nova.conf
[DEFAULT]
dhcpbridge_flagfile=/etc/nova/nova.conf
dhcpbridge=/usr/bin/nova-dhcpbridge
log-dir=/var/log/nova
state_path=/var/lib/nova
force_dhcp_release=True
verbose=True
ec2_private_dns_show_ip=True
enabled_apis=osapi_compute,metadata
transport_url = rabbit://openstack:lxcpassword@controller
auth_strategy = keystone
my_ip = 10.208.132.45
use_neutron = True
firewall_driver = nova.virt.firewall.NoopFirewallDriver
[database]
connection = mysql+pymysql://nova:lxcpassword@controller/nova
[api_database]
connection = mysql+pymysql://nova:lxcpassword@controller/nova_api
[oslo_concurrency]
lock_path = /var/lib/nova/tmp
[libvirt]
use_virtio_for_bridges=True
[wsgi]
api_paste_config=/etc/nova/api-paste.ini
[keystone_authtoken]
auth_uri = http://controller:5000
auth_url = http://controller:35357
memcached_servers = controller:11211
auth_type = password
project_domain_name = default
user_domain_name = default
project_name = service
username = nova
password = lxcpassword
[vnc]
vncserver_listen = $my_ip
vncserver_proxyclient_address = $my_ip
[glance]
api_servers = http://controller:9292
root@controller:~#
- 创建数据库表:
root@controller:~# su -s /bin/sh -c "nova-manage api_db sync" nova
...
root@controller:~# su -s /bin/sh -c "nova-manage db sync" nova
...
root@controller:~#
- 启动 Nova 服务:
root@controller:~# service nova-api restart
root@controller:~# service nova-consoleauth restart
root@controller:~# service nova-scheduler restart
root@controller:~# service nova-conductor restart
root@controller:~# service nova-novncproxy restart
root@controller:~#
- 安装
nova-compute
服务,它将配置 KVM 实例:
root@controller:~# apt install nova-compute
- 按如下方式更新 Nova 配置文件:
root@controller:~# cat /etc/nova/nova.conf
[DEFAULT]
dhcpbridge_flagfile=/etc/nova/nova.conf
dhcpbridge=/usr/bin/nova-dhcpbridge
log-dir=/var/log/nova
state_path=/var/lib/nova
force_dhcp_release=True
verbose=True
ec2_private_dns_show_ip=True
enabled_apis=osapi_compute,metadata
transport_url = rabbit://openstack:lxcpassword@controller
auth_strategy = keystone
my_ip = 10.208.132.45
use_neutron = True
firewall_driver = nova.virt.firewall.NoopFirewallDriver
compute_driver = libvirt.LibvirtDriver
[database]
connection = mysql+pymysql://nova:lxcpassword@controller/nova
[api_database]
connection = mysql+pymysql://nova:lxcpassword@controller/nova_api
[oslo_concurrency]
lock_path = /var/lib/nova/tmp
[libvirt]
use_virtio_for_bridges=True
[wsgi]
api_paste_config=/etc/nova/api-paste.ini
[keystone_authtoken]
auth_uri = http://controller:5000
auth_url = http://controller:35357
memcached_servers = controller:11211
auth_type = password
project_domain_name = default
user_domain_name = default
project_name = service
username = nova
password = lxcpassword
[vnc]
enabled = True
vncserver_listen = $my_ip
vncserver_proxyclient_address = $my_ip
novncproxy_base_url = http://controller:6080/vnc_auto.html
[glance]
api_servers = http://controller:9292
root@controller:~#
- 指定要使用的虚拟化驱动程序:
root@controller:~# cat /etc/nova/nova-compute.conf
[DEFAULT]
compute_driver=libvirt.LibvirtDriver
[libvirt]
virt_type=kvm
root@controller:~#
- 重启
nova-compute
服务并列出可用服务:
root@controller:~# service nova-compute restart
root@controller:~# openstack compute service list
+----+------------------+------------+----------+---------+-------+----------------------+
| ID | Binary | Host | Zone | Status | State | Updated At |
+----+------------------+------------+----------+---------+-------+----------------------+
| 8 | nova-consoleauth | controller | internal | enabled | up | 2017-04-26T17:58 |
| 9 | nova-scheduler | controller | internal | enabled | up | 2017-04-26T17:58 |
| 10 | nova-conductor | controller | internal | enabled | up | 2017-04-26T17:58 |
| 15 | nova-compute | controller | nova | enabled | up | None |
+----+------------------+------------+----------+---------+-------+----------------------+
root@controller:~# pgrep -lf nova | uniq -f1
14110 nova-consoleaut
14176 nova-conductor
14239 nova-novncproxy
20877 nova-api
20994 nova-scheduler
21065 nova-compute
root@controller:~#
它是如何工作的...
在步骤 1 和 2 中,我们在 MariaDB 中创建了 Nova 数据库和用户。在步骤 3 中,我们创建了可以用来发送 API 调用的服务和端点。
在步骤 4 和 5 中,我们安装了 Nova 服务的软件包并创建了一个简单的配置文件。
在步骤 6 中,我们创建了数据库表模式,并在步骤 7 中启动了 Nova 服务。
对于这个示例部署,我们使用单个节点来运行所有感兴趣的 OpenStack 服务。不过,您也可以使用第二个节点专门运行nova-compute
服务来配置 KVM 虚拟机。在步骤 8 中,我们安装nova-compute
服务,更新配置文件,并在步骤 9 和 10 中检查nova-compute
服务的外部配置。
我们通过确保所有 Nova 服务都已配置并在步骤 11 中运行来完成此任务。
安装和配置 OpenStack Neutron 网络服务
OpenStack Neutron 项目提供网络服务来管理虚拟实例之间的网络连接。它负责设置虚拟接口、配置软件桥接、创建路由和管理 IP 地址。
有关各种 Neutron 服务的更多信息,请参阅docs.openstack.org/security-guide/networking/architecture.html
。
在这个配方中,我们将安装和配置以下 Neutron 组件:
-
neutron-server
:这是提供 API 的服务,用于动态请求和配置虚拟网络。 -
neutron-plugin-ml2
:这是一个框架,使得可以使用各种网络技术,例如我们在前面章节中看到的 Linux Bridge、Open vSwitch、GRE 和 VXLAN。 -
neutron-linuxbridge-agent
:这是提供 Linux Bridge 插件代理的服务。 -
neutron-l3-agent
:这是执行软件定义网络之间转发和 NAT 功能的守护进程,通过创建虚拟路由器实现。 -
neutron-dhcp-agent
:此服务控制 DHCP 守护进程,负责为计算节点上运行的实例分配 IP 地址 -
neutron-metadata-agent
:此服务将实例元数据传递给 Neutron
在早期的食谱中,我们手动配置并使用了 Linux 桥接和 Open vSwitch,之后将网络管理委托给了 libvirt。OpenStack Neutron 与 libvirt 集成,并通过暴露 API 调用进一步自动化此过程,其他服务如 Nova 可利用这些 API。
准备工作
对于此食谱,我们需要:
-
一台具有强大虚拟化能力的 Ubuntu 服务器
-
获取互联网访问以进行软件包安装
-
如为 OpenStack 部署准备主机食谱中所述,安装并配置了数据库服务器、消息队列和
memcached
: -
我们在安装和配置 OpenStack Keystone 身份服务食谱中部署的 Keystone 服务
-
我们在安装和配置 OpenStack Nova 计算服务食谱中配置的 Nova 服务
如何操作...
要安装、配置并创建一个由 Neutron 管理的网络,请执行以下步骤:
- 创建 Neutron 数据库:
root@controller:~# mysql -u root -plxcpassword
MariaDB [(none)]> CREATE DATABASE neutron;
Query OK, 1 row affected (0.00 sec)
MariaDB [(none)]> GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'localhost' IDENTIFIED BY 'lxcpassword';
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> GRANT ALL PRIVILEGES ON neutron.* TO 'neutron'@'%' IDENTIFIED BY 'lxcpassword';
Query OK, 0 rows affected (0.00 sec)
MariaDB [(none)]> exit
Bye
root@controller:~#
- 创建 Neutron 用户并将其添加到 Keystone 中的管理员角色:
root@controller:~# openstack user create --domain default --password-prompt neutron
User Password:
Repeat User Password:
+---------------------+----------------------------------+
| Field | Value |
+---------------------+----------------------------------+
| domain_id | default |
| enabled | True |
| id | 02934ad74c94461482b95fff32d36894 |
| name | neutron |
| password_expires_at | None |
+---------------------+----------------------------------+
root@controller:~# openstack role add --project service --user neutron admin
root@controller:~#
- 创建 Neutron 服务和端点:
root@controller:~# openstack service create --name neutron --description "OpenStack Networking" network
+-------------+----------------------------------+
| Field | Value |
+-------------+----------------------------------+
| description | OpenStack Networking |
| enabled | True |
| id | 24b32d32d4b54e3ab2d785a1817b8e7e |
| name | neutron |
| type | network |
+-------------+----------------------------------+
root@controller:~# openstack endpoint create --region RegionOne network public http://controller:9696
+--------------+----------------------------------+
| Field | Value |
+--------------+----------------------------------+
| enabled | True |
| id | 544821d511e04847869fc601f2ebf0f7 |
| interface | public |
| region | RegionOne |
| region_id | RegionOne |
| service_id | 24b32d32d4b54e3ab2d785a1817b8e7e |
| service_name | neutron |
| service_type | network |
| url | http://controller:9696 |
+--------------+----------------------------------+
root@controller:~# openstack endpoint create --region RegionOne network internal http://controller:9696
+--------------+----------------------------------+
| Field | Value |
+--------------+----------------------------------+
| enabled | True |
| id | 05e276ec603f424f85be8705ce7fe86a |
| interface | internal |
| region | RegionOne |
| region_id | RegionOne |
| service_id | 24b32d32d4b54e3ab2d785a1817b8e7e |
| service_name | neutron |
| service_type | network |
| url | http://controller:9696 |
+--------------+----------------------------------+
root@controller:~# openstack endpoint create --region RegionOne network admin http://controller:9696
+--------------+----------------------------------+
| Field | Value |
+--------------+----------------------------------+
| enabled | True |
| id | 836b4309186146fb9143544490cd0bc1 |
| interface | admin |
| region | RegionOne |
| region_id | RegionOne |
| service_id | 24b32d32d4b54e3ab2d785a1817b8e7e |
| service_name | neutron |
| service_type | network |
| url | http://controller:9696 |
+--------------+----------------------------------+
root@controller:~#
- 安装 Neutron 包:
root@controller:~# apt install neutron-server neutron-plugin-ml2 neutron-linuxbridge-agent neutron-l3-agent neutron-dhcp-agent neutron-metadata-agent
...
root@controller:~#
- 创建 Neutron 配置文件:
root@controller:~# cat /etc/neutron/neutron.conf
[DEFAULT]
core_plugin = ml2
service_plugins = router
allow_overlapping_ips = True
transport_url = rabbit://openstack:lxcpassword@controller
auth_strategy = keystone
notify_nova_on_port_status_changes = True
notify_nova_on_port_data_changes = True
[agent]
root_helper = sudo /usr/bin/neutron-rootwrap /etc/neutron/rootwrap.conf
[cors]
[cors.subdomain]
[database]
connection = mysql+pymysql://neutron:lxcpassword@controller/neutron
[keystone_authtoken]
auth_uri = http://controller:5000
auth_url = http://controller:35357
memcached_servers = controller:11211
auth_type = password
project_domain_name = default
user_domain_name = default
project_name = service
username = neutron
password = lxcpassword
[matchmaker_redis]
[nova]
auth_url = http://controller:35357
auth_type = password
project_domain_name = default
user_domain_name = default
region_name = RegionOne
project_name = service
username = nova
password = lxcpassword
[oslo_concurrency]
[oslo_messaging_amqp]
[oslo_messaging_notifications]
[oslo_messaging_rabbit]
[oslo_messaging_zmq]
[oslo_policy]
[qos]
[quotas]
[ssl]
root@controller:~#
- 定义我们将与 Neutron 一起使用的网络类型和扩展:
root@controller:~# cat /etc/neutron/plugins/ml2/ml2_conf.ini
[DEFAULT]
[ml2]
type_drivers = flat,vlan,vxlan
tenant_network_types = vxlan
mechanism_drivers = linuxbridge,l2population
extension_drivers = port_security
[ml2_type_flat]
flat_networks = provider
[ml2_type_geneve]
[ml2_type_gre]
[ml2_type_vlan]
[ml2_type_vxlan]
vni_ranges = 1:1000
[securitygroup]
enable_ipset = True
root@controller:~#
- 定义将添加到软件桥接器中的接口,以及桥接器将绑定的 IP 地址,根据需要替换 IP 地址和接口名称(此示例中为
eth1
):
root@controller:~# cat /etc/neutron/plugins/ml2/linuxbridge_agent.ini
[DEFAULT]
[agent]
[linux_bridge]
physical_interface_mappings = provider:eth1
[securitygroup]
enable_security_group = True
firewall_driver = neutron.agent.linux.iptables_firewall.IptablesFirewallDriver
[vxlan]
enable_vxlan = True
local_ip = 10.208.132.45
l2_population = True
root@controller:~#
- 按如下方式配置 Layer 3 代理:
root@controller:~# cat /etc/neutron/l3_agent.ini
[DEFAULT]
interface_driver = neutron.agent.linux.interface.BridgeInterfaceDriver
[AGENT]
root@controller:~#
- 配置 DHCP 代理:
root@controller:~# cat /etc/neutron/dhcp_agent.ini
[DEFAULT]
interface_driver = neutron.agent.linux.interface.BridgeInterfaceDriver
dhcp_driver = neutron.agent.linux.dhcp.Dnsmasq
enable_isolated_metadata = True
[AGENT]
root@controller:~#
- 为元数据代理创建配置:
root@controller:~# cat /etc/neutron/metadata_agent.ini [DEFAULT]
nova_metadata_ip = controller
metadata_proxy_shared_secret = lxcpassword
[AGENT]
[cache]
root@controller:~#
- 更新 Nova 服务的配置文件,以包含 Neutron。以下是一个完全简化的工作示例:
root@controller:~# cat /etc/nova/nova.conf
[DEFAULT]
dhcpbridge_flagfile=/etc/nova/nova.conf
dhcpbridge=/usr/bin/nova-dhcpbridge
log-dir=/var/log/nova
state_path=/var/lib/nova
force_dhcp_release=True
verbose=True
ec2_private_dns_show_ip=True
enabled_apis=osapi_compute,metadata
transport_url = rabbit://openstack:lxcpassword@controller
auth_strategy = keystone
my_ip = 10.208.132.45
use_neutron = True
firewall_driver = nova.virt.firewall.NoopFirewallDriver
compute_driver = libvirt.LibvirtDriver
scheduler_default_filters = RetryFilter, AvailabilityZoneFilter, RamFilter, ComputeFilter, ComputeCapabilitiesFilter, ImagePropertiesFilter, ServerGroupAntiAffinityFilter, ServerGroupAffinityFilter
[database]
connection = mysql+pymysql://nova:lxcpassword@controller/nova
[api_database]
connection = mysql+pymysql://nova:lxcpassword@controller/nova_api
[oslo_concurrency]
lock_path = /var/lib/nova/tmp
[libvirt]
use_virtio_for_bridges=True
[wsgi]
api_paste_config=/etc/nova/api-paste.ini
[keystone_authtoken]
auth_uri = http://controller:5000
auth_url = http://controller:35357
memcached_servers = controller:11211
auth_type = password
project_domain_name = default
user_domain_name = default
project_name = service
username = nova
password = lxcpassword
[vnc]
enabled = True
vncserver_listen = $my_ip
vncserver_proxyclient_address = $my_ip
novncproxy_base_url = http://controller:6080/vnc_auto.html
[glance]
api_servers = http://controller:9292
[libvirt]
virt_type = kvm
[neutron]
url = http://controller:9696
auth_url = http://controller:35357
auth_type = password
project_domain_name = default
user_domain_name = default
region_name = RegionOne
project_name = service
username = neutron
password = lxcpassword
service_metadata_proxy = True
metadata_proxy_shared_secret = lxcpassword
root@controller:~#
- 填充 Neutron 数据库:
root@controller:~# su -s /bin/sh -c "neutron-db-manage --config-file /etc/neutron/neutron.conf --config-file /etc/neutron/plugins/ml2/ml2_conf.ini upgrade head" neutron
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
Running upgrade for neutron ...
INFO [alembic.runtime.migration] Context impl MySQLImpl.
INFO [alembic.runtime.migration] Will assume non-transactional DDL.
INFO [alembic.runtime.migration] Running upgrade -> kilo, kilo_initial
...
root@controller:~#
- 重启所有 Neutron 服务和 Nova:
root@controller:~# service neutron-server restart
root@controller:~# service neutron-linuxbridge-agent restart
root@controller:~# service neutron-dhcp-agent restart
root@controller:~# service neutron-metadata-agent restart
root@controller:~# service neutron-l3-agent restart
root@controller:~# service nova-api restart
root@controller:~# service nova-compute restart
root@controller:~#
- 验证 Neutron 服务是否已注册:
root@controller:~# openstack network agent list
+--------------------------------------+--------------------+------+-------------------+-------+-------+---------------------------+
| ID | Agent Type | Host | Availability Zone | Alive | State | Binary |
+--------------------------------------+--------------------+------+-------------------+-------+-------+---------------------------+
| 9242d71d-de25-4b3e-8aa8-62691ef72001 | Linux bridge agent | kvm2 | None | True | UP | neutron-linuxbridge-agent |
| 92b601de-06df-4b10-88c7-8f27bc48f6ab | L3 agent | kvm2 | nova | True | UP | neutron-l3-agent |
| d249f986-9b26-4c5d-8ea5-311daf3b395d | DHCP agent | kvm2 | nova | True | UP | neutron-dhcp-agent |
| f3cac79b-a7c3-4672-b846-9268f2d58706 | Metadata agent | kvm2 | None | True | UP | neutron-metadata-agent |
+--------------------------------------+--------------------+------+-------------------+-------+-------+---------------------------+
root@controller:~#
- 创建一个新网络:
root@controller:~# openstack network create nat
+---------------------------+--------------------------------------+
| Field | Value |
+---------------------------+--------------------------------------+
| admin_state_up | UP |
| availability_zone_hints | |
| availability_zones | |
| created_at | 2017-04-26T18:17:24Z |
| description | |
| headers | |
| id | b7ccb514-21fc-4ced-b74f-026e7e358bba |
| ipv4_address_scope | None |
| ipv6_address_scope | None |
| mtu | 1450 |
| name | nat |
| port_security_enabled | True |
| project_id | 123c1e6f33584dd1876c0a34249a6e11 |
| project_id | 123c1e6f33584dd1876c0a34249a6e11 |
| provider:network_type | vxlan |
| provider:physical_network | None |
| provider:segmentation_id | 37 |
| revision_number | 3 |
| router:external | Internal |
| shared | False |
| status | ACTIVE |
| subnets | |
| tags | [] |
| updated_at | 2017-04-26T18:17:24Z |
+---------------------------+--------------------------------------+
root@controller:~#
- 定义 DNS 服务器、默认网关和将分配给客户机的子网范围:
root@controller:~# openstack subnet create --network nat --dns-nameserver 8.8.8.8 --gateway 192.168.0.1 --subnet-range 192.168.0.0/24 nat
+-------------------+--------------------------------------+
| Field | Value |
+-------------------+--------------------------------------+
| allocation_pools | 192.168.0.2-192.168.0.254 |
| cidr | 192.168.0.0/24 |
| created_at | 2017-04-26T18:17:41Z |
| description | |
| dns_nameservers | 8.8.8.8 |
| enable_dhcp | True |
| gateway_ip | 192.168.0.1 |
| headers | |
| host_routes | |
| id | 296250a7-f241-4f84-adbb-64a45c943094 |
| ip_version | 4 |
| ipv6_address_mode | None |
| ipv6_ra_mode | None |
| name | nat |
| network_id | b7ccb514-21fc-4ced-b74f-026e7e358bba |
| project_id | 123c1e6f33584dd1876c0a34249a6e11 |
| project_id | 123c1e6f33584dd1876c0a34249a6e11 |
| revision_number | 2 |
| service_types | [] |
| subnetpool_id | None |
| updated_at | 2017-04-26T18:17:41Z |
+-------------------+--------------------------------------+
root@controller:~#
- 更新 Neutron 中的子网信息:
root@controller:~# neutron net-update nat --router:external
Updated network: nat
root@controller:~#
- 创建一个新的软件路由器:
root@controller:~# openstack router create router
+-------------------------+--------------------------------------+
| Field | Value |
+-------------------------+--------------------------------------+
| admin_state_up | UP |
| availability_zone_hints | |
| availability_zones | |
| created_at | 2017-04-26T18:18:05Z |
| description | |
| external_gateway_info | null |
| flavor_id | None |
| headers | |
| id | f9cd8c96-a53c-4585-ad21-0e409f3b4d70 |
| name | router |
| project_id | 10a92eccbad9439d9e56c4edda6b211f |
| project_id | 10a92eccbad9439d9e56c4edda6b211f |
| revision_number | 3 |
| routes | |
| status | ACTIVE |
| updated_at | 2017-04-26T18:18:05Z |
+-------------------------+--------------------------------------+
root@controller:~#
- 作为管理员用户,将我们之前创建的子网添加为路由器的接口:
root@controller:~# . rc.admin
root@controller:~# neutron router-interface-add router nat
Added interface 2e1e2fd3-1819-489b-a21f-7005862f9de7 to router router.
root@controller:~#
- 列出 Neutron 创建的网络命名空间:
root@controller:~# ip netns
qrouter-f9cd8c96-a53c-4585-ad21-0e409f3b4d70
qdhcp-b7ccb514-21fc-4ced-b74f-026e7e358bba
root@controller:~#
- 列出软件路由器上的端口:
root@controller:~# neutron router-port-list router
+--------------------------------------+------+-------------------+------------------------------------------------------------------------------------+
| id | name | mac_address | fixed_ips |
+--------------------------------------+------+-------------------+------------------------------------------------------------------------------------+
| 2e1e2fd3-1819-489b-a21f-7005862f9de7 | | fa:16:3e:0e:db:14 | {"subnet_id": "296250a7-f241-4f84-adbb-64a45c943094", "ip_address": "192.168.0.1"} |
+--------------------------------------+------+-------------------+------------------------------------------------------------------------------------+
root@controller:~#
- 列出 Neutron 网络,并确保我们之前创建的网络已存在:
root@controller:~# openstack network list
+--------------------------------------+------+--------------------------------------+
| ID | Name | Subnets |
+--------------------------------------+------+--------------------------------------+
| b7ccb514-21fc-4ced-b74f-026e7e358bba | nat | 296250a7-f241-4f84-adbb-64a45c943094 |
+--------------------------------------+------+--------------------------------------+
root@controller:~#
它是如何工作的...
我们通过在第 1 步中为 Neutron 创建一个新的数据库开始本教程。接着,我们创建 Neutron 服务的用户并将其添加到服务的管理员角色。在第 2 步和第 3 步中,我们定义将供 Nova 使用的服务端点。在第 4 步和第 5 步中,我们安装 Neutron 软件包并创建一个基础配置文件。在第 6 步中,我们选择 VXLAN 类型的网络用于本例部署。在第 7 步至第 10 步中,我们配置桥接代理、3 层代理、DHCP 代理和元数据代理。
在第 11 步中,我们更新 Nova 配置文件,添加关于 Neutron 服务的部分。在第 12 步中,我们创建数据库模式,并在第 13 步中重启所有 Neutron 服务,包括 nova-api
和 nova-compute
。
在第 14 步,我们验证 Neutron 服务是否已注册,并继续在第 15 步创建一个新网络。
在第 18 步,我们定义一个新的软件路由器。在第 19 步中,我们将之前创建的子网添加到路由器中,然后在第 21 步中验证新的路由配置。
第 22 步确保我们之前定义的网络是活动的。
使用 OpenStack 构建和检查 KVM 实例
在本教程中,我们将使用之前部署的 OpenStack 基础设施构建我们的第一个 KVM 实例。构建新 KVM 实例包括以下步骤:
-
我们向
nova-api
服务发送 API 请求。 -
nova-api
服务向 nova-scheduler 服务请求目标计算主机。 -
nova-scheduler
根据配置的过滤器(如可用内存、磁盘和 CPU 使用情况)选择一个可用的计算主机。 -
一旦
nova-scheduler
选择了一个合适的主机,选定主机上的nova-compute
服务会向 Glance 仓库请求镜像(如果镜像尚未缓存)。镜像传输到新服务器后,nova-compute
会构建新的 KVM 实例。
准备就绪
对于本教程,我们需要以下内容:
-
数据库服务器、消息队列和
memcached
已安装并配置,具体内容请参见 为 OpenStack 部署准备主机 章节。 -
Glance 服务和可用镜像。有关如何部署 Glance 并添加新镜像的信息,请参见 安装和配置 OpenStack Glance 镜像服务 章节。
-
我们在 安装和配置 OpenStack Keystone 身份服务 章节中部署的 Keystone 服务。
-
在 安装和配置 OpenStack Nova 计算服务 章节中配置的 Nova 服务。
-
在 安装和配置 OpenStack Neutron 网络服务 章节中部署的 Neutron 服务。
如何操作...
使用 OpenStack 命令行接口 (CLI) 构建一个新的 KVM 实例,请执行以下步骤:
- 确保我们有一个可用的 Glance 镜像:
root@controller:~# openstack image list
+--------------------------------------+--------------+--------+
| ID | Name | Status |
+--------------------------------------+--------------+--------+
| abce08d2-2f9f-4545-a414-32019d41c0cd | ubuntu_16.04 | active |
+--------------------------------------+--------------+--------+
root@controller:~#
- 创建一个新的实例类型:
root@controller:~# openstack flavor create --id 0 --vcpus 1 --ram 1024 --disk 5000 kvm.medium
+----------------------------+------------+
| Field | Value |
+----------------------------+------------+
| OS-FLV-DISABLED:disabled | False |
| OS-FLV-EXT-DATA:ephemeral | 0 |
| disk | 5000 |
| id | 0 |
| name | kvm.medium |
| os-flavor-access:is_public | True |
| properties | |
| ram | 1024 |
| rxtx_factor | 1.0 |
| swap | |
| vcpus | 1 |
+----------------------------+------------+
root@controller:~#
root@controller:~# openstack flavor list
+----+------------+------+------+-----------+-------+-----------+
| ID | Name | RAM | Disk | Ephemeral | VCPUs | Is Public |
+----+------------+------+------+-----------+-------+-----------+
| 0 | kvm.medium | 1024 | 5000 | 0 | 1 | True |
+----+------------+------+------+-----------+-------+-----------+
root@controller:~#
- 创建一个新的 SSH 密钥对:
root@controller:~# openstack keypair create --public-key ~/.ssh/kvm_rsa.pub kvmkey
+-------------+-------------------------------------------------+
| Field | Value |
+-------------+-------------------------------------------------+
| fingerprint | e9:7e:e6:05:8b:a4:31:c3:5e:41:65:0e:29:23:eb:2a |
| name | kvmkey |
| user_id | cc14c5dbbd654c438e52d38efaf4f1a6 |
+-------------+-------------------------------------------------+
root@controller:~# openstack keypair list
+--------+-------------------------------------------------+
| Name | Fingerprint |
+--------+-------------------------------------------------+
| kvmkey | e9:7e:e6:05:8b:a4:31:c3:5e:41:65:0e:29:23:eb:2a |
+--------+-------------------------------------------------+
root@controller:~#
- 定义允许 SSH 和 ICMP 访问的安全组规则:
root@controller:~# openstack security group rule create --proto icmp default
+-------------------+--------------------------------------+
| Field | Value |
+-------------------+--------------------------------------+
| created_at | 2017-04-26T18:17:13Z |
| description | |
| direction | ingress |
| ethertype | IPv4 |
| headers | |
| id | ca28501a-1b3b-448f-8c1b-0fa6f9fa9263 |
| port_range_max | None |
| port_range_min | None |
| project_id | 123c1e6f33584dd1876c0a34249a6e11 |
| project_id | 123c1e6f33584dd1876c0a34249a6e11 |
| protocol | icmp |
| remote_group_id | None |
| remote_ip_prefix | 0.0.0.0/0 |
| revision_number | 1 |
| security_group_id | 050b8174-d961-4706-ab63-1cdd2a25fbdd |
| updated_at | 2017-04-26T18:17:13Z |
+-------------------+--------------------------------------+
root@controller:~#
root@controller:~# openstack security group rule create --proto tcp --dst-port 22 default
+-------------------+--------------------------------------+
| Field | Value |
+-------------------+--------------------------------------+
| created_at | 2017-04-26T18:17:18Z |
| description | |
| direction | ingress |
| ethertype | IPv4 |
| headers | |
| id | 334130c3-42b2-4f1b-aba6-c46e91ad203e |
| port_range_max | 22 |
| port_range_min | 22 |
| project_id | 123c1e6f33584dd1876c0a34249a6e11 |
| project_id | 123c1e6f33584dd1876c0a34249a6e11 |
| protocol | tcp |
| remote_group_id | None |
| remote_ip_prefix | 0.0.0.0/0 |
| revision_number | 1 |
| security_group_id | 050b8174-d961-4706-ab63-1cdd2a25fbdd |
| updated_at | 2017-04-26T18:17:18Z |
+-------------------+--------------------------------------+
root@controller:~#
- 列出之前定义的可用网络:
root@controller:~# openstack network list
+--------------------------------------+------+--------------------------------------+
| ID | Name | Subnets |
+--------------------------------------+------+--------------------------------------+
| b7ccb514-21fc-4ced-b74f-026e7e358bba | nat | 296250a7-f241-4f84-adbb-64a45c943094 |
+--------------------------------------+------+--------------------------------------+
root@controller:~#
- 创建一个新的 KVM 实例并列出其状态:
root@controller:~# openstack server create --flavor kvm.medium --image ubuntu_16.04 --nic net-id=b7ccb514-21fc-4ced-b74f-026e7e358bba --security-group default --key-name kvmkey ubuntu_instance
+--------------------------------------+-------------------------------------------+
| Field | Value |
+--------------------------------------+-------------------------------------------+
| OS-DCF:diskConfig | MANUAL |
| OS-EXT-AZ:availability_zone | |
| OS-EXT-SRV-ATTR:host | None |
| OS-EXT-SRV-ATTR:hypervisor_hostname | None |
| OS-EXT-SRV-ATTR:instance_name | |
| OS-EXT-STS:power_state | NOSTATE |
| OS-EXT-STS:task_state | scheduling |
| OS-EXT-STS:vm_state | building |
| OS-SRV-USG:launched_at | None |
| OS-SRV-USG:terminated_at | None |
| accessIPv4 | |
| accessIPv6 | |
| addresses | |
| adminPass | Z23yEuDLBjLe |
| config_drive | |
| created | 2017-04-26T19:11:23Z |
| flavor | kvm.medium (0) |
| hostId | |
| id | 0f4745b1-9d4b-4e8a-82f7-9eaa1f9bb08f |
| image | ubuntu_16.04 (abce08d2-a414-32019d41c0cd) |
| key_name | kvmkey |
| name | ubuntu_instance |
| os-extended-volumes:volumes_attached | [] |
| progress | 0 |
| project_id | 123c1e6f33584dd1876c0a34249a6e11 |
| properties | |
| security_groups | [{u'name': u'default'}] |
| status | BUILD |
| updated | 2017-04-26T19:11:23Z |
| user_id | cc14c5dbbd654c438e52d38efaf4f1a6 |
+--------------------------------------+-------------------------------------------+
root@controller:~# openstack server list
+---------------------+-----------------+---------+------------------+--------------+
| ID | Name | Status | Networks | Image Name |
+---------------------+-----------------+---------+------------------+--------------+
| 0f4745b1-...-9bb08f | ubuntu_instance | BUILD | nat=192.168.0.11 | ubuntu_16.04 |
+---------------------+-----------------+---------+------------------+--------------+
root@controller:~#
- 确保容器已成功启动:
root@controller:~# pgrep -lfa qemu
23388 /usr/bin/qemu-system-x86_64 -name instance-00000005 -S -machine pc-i440fx-xenial,accel=kvm,usb=off -cpu Haswell-noTSX,+abm,+pdpe1gb,+rdrand,+f16c,+osxsave,+dca,+pdcm,+xtpr,+tm2,+est,+smx,+vmx,+ds_cpl,+monitor,+dtes64,+pbe,+tm,+ht,+ss,+acpi,+ds,+vme -m 1024 -realtime mlock=off -smp 1,sockets=1,cores=1,threads=1 -uuid 0f4745b1-9d4b-4e8a-82f7-9eaa1f9bb08f -smbios type=1,manufacturer=OpenStack Foundation,product=OpenStack Nova,version=14.0.4,serial=6d6366d9-4569-6233-dad6-4927587cc79f,uuid=0f4745b1-9d4b-4e8a-82f7-9eaa1f9bb08f,family=Virtual Machine -no-user-config -nodefaults -chardev socket,id=charmonitor,path=/var/lib/libvirt/qemu/domain-instance-00000005/monitor.sock,server,nowait -mon chardev=charmonitor,id=monitor,mode=control -rtc base=utc,driftfix=slew -global kvm-pit.lost_tick_policy=discard -no-hpet -no-shutdown -boot strict=on -device piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2 -drive file=/var/lib/nova/instances/0f4745b1-9d4b-4e8a-82f7-9eaa1f9bb08f/disk,format=qcow2,if=none,id=drive-virtio-disk0,cache=none -device virtio-blk-pci,scsi=off,bus=pci.0,addr=0x4,drive=drive-virtio-disk0,id=virtio-disk0,bootindex=1 -netdev tap,fd=26,id=hostnet0,vhost=on,vhostfd=28 -device virtio-net-pci,netdev=hostnet0,id=net0,mac=fa:16:3e:3c:c0:0f,bus=pci.0,addr=0x3 -chardev file,id=charserial0,path=/var/lib/nova/instances/0f4745b1-9d4b-4e8a-82f7-9eaa1f9bb08f/console.log -device isa-serial,chardev=charserial0,id=serial0 -chardev pty,id=charserial1 -device isa-serial,chardev=charserial1,id=serial1 -device usb-tablet,id=input0 -vnc 0.0.0.0:0 -k en-us -device cirrus-vga,id=video0,bus=pci.0,addr=0x2 -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x5 -msg timestamp=on
root@controller:~# openstack server list
+---------------------------+-----------------+--------+------------------+--------------+
| ID | Name | Status | Networks | Image Name |
+---------------------------+-----------------+--------+------------------+--------------+
| 0f4745b1-...-9eaa1f9bb08f | ubuntu_instance | ACTIVE | nat=192.168.0.11 | ubuntu_16.04 |
+---------------------------+-----------------+--------+------------------+--------------+
root@controller:~#
- 检查 KVM 实例:
root@controller:~# openstack server show ubuntu_instance
+--------------------------------------+-------------------------------------------+
| Field | Value |
+--------------------------------------+-------------------------------------------+
| OS-DCF:diskConfig | MANUAL |
| OS-EXT-AZ:availability_zone | nova |
| OS-EXT-SRV-ATTR:host | controller |
| OS-EXT-SRV-ATTR:hypervisor_hostname | controller |
| OS-EXT-SRV-ATTR:instance_name | instance-00000001 |
| OS-EXT-STS:power_state | Running |
| OS-EXT-STS:task_state | None |
| OS-EXT-STS:vm_state | active |
| OS-SRV-USG:launched_at | 2017-04-26T19:11:37.000000 |
| OS-SRV-USG:terminated_at | None |
| accessIPv4 | |
| accessIPv6 | |
| addresses | nat=192.168.0.11 |
| config_drive | |
| created | 2017-04-26T19:11:23Z |
| flavor | kvm.medium (0) |
| hostId | c8d0t2jgdlkasdjg0iu4kjdg3o43045t |
| id | 0f4745b1-9d4b-4e8a-82f7-9eaa1f9bb08f |
| image | ubuntu_16.04 (abce08d2-a414-32019d41c0cd) |
| key_name | kvmkey |
| name | ubuntu_instance |
| os-extended-volumes:volumes_attached | [] |
| progress | 0 |
| project_id | 123c1e6f33584dd1876c0a34249a6e11 |
| properties | |
| security_groups | [{u'name': u'default'}] |
| status | ACTIVE |
| updated | 2017-04-26T19:11:23Z |
| user_id | cc14c5dbbd654c438e52d38efaf4f1a6 |
+--------------------------------------+-------------------------------------------+
root@controller:~#
它是如何工作的……
我们通过确保有可用的 Glance 镜像可以选择来开始本食谱。在第 1 步中,我们列出了 Glance 中所有可用的镜像。在第 2 步中,我们创建了一个新的实例规格;我们为新的实例类型指定了分配的 CPU、内存和磁盘资源。在第 3 步中,虽然不是强制性的,但我们创建了一个新的 SSH 密钥对,稍后可以用来 SSH 登录到新实例。在第 4 步中,我们创建了两个新的安全组规则,允许 SSH 和 ICMP 流量。如果我们希望能够 ping 和 SSH 到新实例,这将非常方便。在创建实例之前,我们需要列出 Neutron 中的可用网络,这些网络将成为客户机的一部分;我们在第 5 步中完成了这一步。
在所有先前的先决条件都已就绪后,我们在第 6 步中创建了一个新的 KVM 实例,指定了实例规格、Glance 镜像、网络、安全组和 SSH 密钥。然后,我们继续列出该实例的状态。注意,任务状态显示为 scheduling,这意味着 nova-scheduler
正在选择一个主机来部署该实例,状态是 BUILD。由于我们在这个示例部署中只使用一个主机,实例将在同一计算服务器上部署。从构建命令的输出中,我们还可以看到分配给新实例的 IP 地址。
在第 7 步中,我们可以看到新的实例已成功创建,其状态现在显示为 ACTIVE,且已启动新的 QEMU 进程。
最后,在第 8 步中,我们检查正在运行的实例;请注意,电源状态字段现在显示为 Running,状态字段显示为 active。
使用 OpenStack 停止 KVM 实例
在这个简短的食谱中,我们将使用熟悉的 openstack
命令语法停止上一个食谱中我们创建的正在运行的 KVM 实例。
准备工作
对于这个食谱,我们需要以下内容:
-
一个已安装并配置的数据库服务器、消息队列和
memcached
,如 为 OpenStack 部署准备主机 食谱中所述。 -
一个可用镜像的 Glance 服务。有关如何部署 Glance 并添加新镜像的信息,请参阅 安装和配置 OpenStack Glance 镜像服务 食谱。
-
我们在 安装和配置 OpenStack Keystone 身份服务 食谱中部署的 Keystone 服务。
-
我们在 安装和配置 OpenStack Nova 计算服务 食谱中配置的 Nova 服务。
-
我们在 安装和配置 OpenStack Neutron 网络服务 食谱中部署的 Neutron 服务。
-
一个由 OpenStack 部署的正在运行的 KVM 实例。
如何操作……
要使用 OpenStack 停止正在运行的 KVM 客户机,请执行以下简单步骤:
- 列出已部署的 OpenStack 实例:
root@controller:~# openstack server list
+---------------------------+-----------------+--------+------------------+--------------+
| ID | Name | Status | Networks | Image Name |
+---------------------------+-----------------+--------+------------------+--------------+
| 0f4745b1-...-9eaa1f9bb08f | ubuntu_instance | ACTIVE | nat=192.168.0.11 | ubuntu_16.04 |
+---------------------------+-----------------+--------+------------------+--------------+
root@controller:~#
- 停止实例:
root@controller:~# openstack server stop ubuntu_instance
root@controller:~#
- 使用 libvirt 列出 KVM 客户机:
root@controller:~# virsh list --all
Id Name State
----------------------------------------------------
- instance-00000001 shut off
root@controller:~#
- 确保实例的 QEMU 进程已终止:
root@controller:~# pgrep -lfa qemu
root@controller:~#
- 检查 KVM 来宾的状态:
root@controller:~# openstack server list
+---------------------------+-----------------+--------+------------------+--------------+
| ID | Name | Status | Networks | Image Name |
+---------------------------+-----------------+--------+------------------+--------------+
| 0f4745b1-...-9eaa1f9bb08f | ubuntu_instance | SHUTOFF| nat=192.168.0.11 | ubuntu_16.04 |
+---------------------------+-----------------+--------+------------------+--------------+
root@controller:~#
它是如何工作的...
我们从第 1 步开始,列出由 OpenStack 配置的可用 KVM 实例。在第 2 步中,我们通过指定实例的名称来停止实例。请注意,我们也可以使用实例 ID 来停止它。由于 OpenStack 使用 libvirt 管理 KVM 实例的生命周期,在第 3 步中,我们看到该实例确实已经被销毁。在第 4 步中,我们确保该实例的 QEMU 进程也已经终止。在最后一步,我们可以看到实例的状态现在是 SHUTOFF,而不是 ACTIVE。处于此状态的实例可以通过执行以下命令重新启动:
root@controller:~# openstack server start ubuntu_instance
root@controller:~# openstack server list
+---------------------------+-----------------+--------+------------------+--------------+
| ID | Name | Status | Networks | Image Name |
+---------------------------+-----------------+--------+------------------+--------------+
| 0f4745b1-...-9eaa1f9bb08f | ubuntu_instance | ACTIVE | nat=192.168.0.11 | ubuntu_16.04 |
+---------------------------+-----------------+--------+------------------+--------------+
root@controller:~#
使用 OpenStack 终止 KVM 实例
在本食谱中,我们将终止一个由 OpenStack 配置的 KVM 实例。终止实例会通过 libvirt 将其取消定义,释放分配的 CPU、内存和磁盘资源回到可用资源池,并将其 IP 地址在 Neutron 数据库中标记为可用。
准备就绪
对于这个食谱,我们需要以下内容:
-
已安装并配置数据库服务器、消息队列和
memcached
,如为 OpenStack 部署准备主机食谱中所述。 -
可用镜像的 Glance 服务。有关如何部署 Glance 并添加新镜像的更多信息,请参考安装和配置 OpenStack Glance 镜像服务食谱。
-
我们在安装和配置 OpenStack Keystone 身份服务食谱中部署的 Keystone 服务。
-
我们在安装和配置 OpenStack Nova 计算服务食谱中配置的 Nova 服务。
-
在安装和配置 OpenStack Neutron 网络服务食谱中部署的 Neutron 服务。
-
一个正在运行的 KVM 实例,由 OpenStack 配置。
如何操作...
要终止一个正在运行的实例,请执行以下步骤:
- 获取要终止的实例的名称或 ID:
root@controller:~# openstack server start ubuntu_instance
root@controller:~# openstack server list
+---------------------------+-----------------+--------+------------------+--------------+
| ID | Name | Status | Networks | Image Name |
+---------------------------+-----------------+--------+------------------+--------------+
| 0f4745b1-...-9eaa1f9bb08f | ubuntu_instance | ACTIVE | nat=192.168.0.11 | ubuntu_16.04 |
+---------------------------+-----------------+--------+------------------+--------------+
root@controller:~#
- 通过提供名称删除实例:
root@controller:~# openstack server delete ubuntu_instance
root@controller:~#
- 确保实例已被取消定义:
root@controller:~# openstack server list
root@controller:~# virsh list --all
Id Name State
----------------------------------------------------
root@controller:~#
- 检查
nova-api
、neutron-server
和nova-compute
的日志:
root@controller:~# cat /var/log/nova/nova-api.log | grep -i delete
2017-05-04 15:30:07.733 20915 INFO nova.osapi_compute.wsgi.server [req-54dbe80f-9942-43d8-949a-d80daa2440a9 cc14c5dbbd654c438e52d38efaf4f1a6 123c1e6f33584dd1876c0a34249a6e11 - default default] 10.184.226.74 "DELETE /v2.1/123c1e6f33584dd1876c0a34249a6e11/servers/0f4745b1-9d4b-4e8a-82f7-9eaa1f9bb08f HTTP/1.1" status: 204 len: 339 time: 0.1859989
root@controller:~#
root@controller:~# cat /var/log/neutron/neutron-server.log | grep -i delete
2017-05-04 15:30:08.402 17910 INFO neutron.wsgi [req-5c9674d6-c596-4b17-b975-54625ac7adb2 cc14c5dbbd654c438e52d38efaf4f1a6 123c1e6f33584dd1876c0a34249a6e11 - - -] 10.184.226.74 - - [04/May/2017 15:30:08] "DELETE /v2.0/ports/fdaf6ea1-b76a-4895-a028-db15831132fa.json HTTP/1.1" 204 173 0.320351
root@controller:~#
root@controller:~# cat /var/log/nova/nova-compute.log
...
2017-05-04 15:30:07.747 21065 INFO nova.compute.manager [req-54dbe80f-9942-43d8-949a-d80daa2440a9 cc14c5dbbd654c438e52d38efaf4f1a6 123c1e6f33584dd1876c0a34249a6e11 - - -] [instance: 0f4745b1-9d4b-4e8a-82f7-9eaa1f9bb08f] Terminating instance
2017-05-04 15:30:07.952 21065 INFO nova.virt.libvirt.driver [-] [instance: 0f4745b1-9d4b-4e8a-82f7-9eaa1f9bb08f] Instance destroyed successfully.
2017-05-04 15:30:07.953 21065 INFO os_vif [req-54dbe80f-9942-43d8-949a-d80daa2440a9 cc14c5dbbd654c438e52d38efaf4f1a6 123c1e6f33584dd1876c0a34249a6e11 - - -] Successfully unplugged vif VIFBridge(active=True,address=fa:16:3e:3c:c0:0f,bridge_name='brqb7ccb514-21',has_traffic_filtering=True,id=fdaf6ea1-b76a-4895-a028-db15831132fa,network=Network(b7ccb514-21fc-4ced-b74f-026e7e358bba),plugin='linux_bridge',port_profile=<?>,preserve_on_delete=False,vif_name='tapfdaf6ea1-b7')
2017-05-04 15:30:07.970 21065 INFO nova.virt.libvirt.driver [req-54dbe80f-9942-43d8-949a-d80daa2440a9 cc14c5dbbd654c438e52d38efaf4f1a6 123c1e6f33584dd1876c0a34249a6e11 - - -] [instance: 0f4745b1-9d4b-4e8a-82f7-9eaa1f9bb08f] Deleting instance files /var/lib/nova/instances/0f4745b1-9d4b-4e8a-82f7-9eaa1f9bb08f_del
2017-05-04 15:30:07.974 21065 INFO nova.virt.libvirt.driver [req-54dbe80f-9942-43d8-949a-d80daa2440a9 cc14c5dbbd654c438e52d38efaf4f1a6 123c1e6f33584dd1876c0a34249a6e11 - - -] [instance: 0f4745b1-9d4b-4e8a-82f7-9eaa1f9bb08f] Deletion of /var/lib/nova/instances/0f4745b1-9d4b-4e8a-82f7-9eaa1f9bb08f_del complete
...
root@controller:~#
它是如何工作的...
在第 1 步中,我们通过列出 Nova 所知道的所有实例,并记录下我们想要删除的实例的名称。
在第 2 步中,我们通过指定实例的名称来删除该实例。请注意,我们也可以使用实例的 ID 来删除。在第 3 步中,我们确认该实例已被 libvirt 取消定义,并且在 OpenStack 中不再可用。
在第 4 步中,我们可以看到发送到nova-api
、neutron-server
和nova-compute
服务的 API 调用及其采取的操作。
第七章:使用 Python 构建和管理 KVM 实例
在本章中,我们将讨论以下主题:
-
安装和使用 Python libvirt 库
-
使用 Python 定义 KVM 实例
-
使用 Python 启动、停止和删除 KVM 实例
-
使用 Python 检查 KVM 实例
-
使用 libvirt 和 Bottle 构建一个简单的 REST API 服务器
介绍
libvirt
库提供了一个虚拟化无关的接口,用于控制 KVM(及其他技术,如 XEN 和 LXC)实例的完整生命周期。通过 Python 绑定,我们可以定义、启动、销毁和删除虚拟机,以及 virsh
用户空间工具实现的任何其他功能。实际上,我们可以通过运行以下命令看到 virsh
命令使用了多个 libvirt 共享库:
root@kvm:~# ldd /usr/bin/virsh | grep libvirt
libvirt-lxc.so.0 => /usr/lib/x86_64-linux-gnu/libvirt-lxc.so.0 (0x00007fd050d88000)
libvirt-qemu.so.0 => /usr/lib/x86_64-linux-gnu/libvirt-qemu.so.0 (0x00007fd050b84000)
libvirt.so.0 => /usr/lib/x86_64-linux-gnu/libvirt.so.0 (0x00007fd050394000)
root@kvm:~#
Python libvirt 模块还提供了监控和报告 CPU、内存、存储和网络资源使用情况的方法,具体功能取决于所使用的虚拟化驱动程序类型。
在本章中,我们将使用 Python libvirt API 的一个小子集来定义、启动、检查和停止 KVM 实例。
要查看 Python libvirt 模块提供的函数、类和方法的完整列表,请执行:
root@kvm:~# pydoc libvirt
安装和使用 Python libvirt 库
在这个食谱中,我们将安装 Python libvirt 模块及其依赖项,创建一个新的虚拟环境,并安装用于交互计算的 iPython
命令行工具。
准备工作
在这个食谱中,我们需要以下内容:
-
一台安装并配置了 libvirt 和 QEMU 的 Ubuntu 主机
-
我们在第一章《使用 QEMU 和 KVM 入门》的使用 debootstrap 安装自定义操作系统到镜像食谱中构建的
debian.img
原始镜像文件 -
Python 2.7 解释器,通常由
python2.7
包提供
如何操作...
要安装 Python libvirt 模块、iPython
工具,并为我们的测试创建新的虚拟环境,请按照以下步骤操作:
- 安装 Python 开发包
pip
和virtualenv
:
root@kvm:~# apt-get install python-pip python-dev pkg-config build-essential autoconf libvirt-dev
root@kvm:~# pip install virtualenv
Downloading/unpacking virtualenv
Downloading virtualenv-15.1.0-py2.py3-none-any.whl (1.8MB): 1.8MB downloaded
Installing collected packages: virtualenv
Successfully installed virtualenv
Cleaning up...
root@kvm:~#
- 创建一个新的 Python 虚拟环境并激活它:
root@kvm:~# mkdir kvm_python
root@kvm:~# virtualenv kvm_python/
New python executable in /root/kvm_python/bin/python
Installing setuptools, pip, wheel...done.
root@kvm:~# source kvm_python/bin/activate
(kvm_python) root@kvm:~# cd kvm_python/
(kvm_python) root@kvm:~/kvm_python# ls -la
total 28
drwxr-xr-x 6 root root 4096 May 9 17:28 .
drwx------ 8 root root 4096 May 9 17:28 ..
drwxr-xr-x 2 root root 4096 May 9 17:28 bin
drwxr-xr-x 2 root root 4096 May 9 17:28 include
drwxr-xr-x 3 root root 4096 May 9 17:28 lib
drwxr-xr-x 2 root root 4096 May 9 17:28 local
-rw-r--r-- 1 root root 60 May 9 17:28 pip-selfcheck.json
(kvm_python) root@kvm:~/kvm_python#
- 安装 libvirt 模块:
(kvm_python) root@kvm:~/kvm_python# pip install libvirt-python
Collecting libvirt-python
Using cached libvirt-python-3.3.0.tar.gz
Building wheels for collected packages: libvirt-python
Running setup.py bdist_wheel for libvirt-python ... done
Stored in directory: /root/.cache/pip/wheels/67/f0/5c/c939bf8fcce5387a36efca53eab34ba8e94a28f244fd1757c1
Successfully built libvirt-python
Installing collected packages: libvirt-python
Successfully installed libvirt-python-3.3.0
(kvm_python) root@kvm:~/kvm_python# pip freeze
appdirs==1.4.3
libvirt-python==3.3.0
packaging==16.8
pyparsing==2.2.0
six==1.10.0
(kvm_python) root@kvm:~/kvm_python# python --version
Python 2.7.6
(kvm_python) root@kvm:~/kvm_python#
- 安装
iPython
并启动它:
(kvm_python) root@kvm:~/kvm_python# apt-get install ipython
...
(kvm_python) root@kvm:~/kvm_python# ipython
Python 2.7.6 (default, Oct 26 2016, 20:30:19)
Type "copyright", "credits" or "license" for more information.
IPython 1.2.1 -- An enhanced Interactive Python.
? -> Introduction and overview of IPython's features.
%quickref -> Quick reference.
help -> Python's own help system.
object? -> Details about 'object', use 'object??' for extra details.
In [1]:
它是如何工作的...
我们从第 1 步开始安装依赖包。由于我们将使用 Python 虚拟环境进行开发,因此也会安装 virtualenv
包。Python libvirt 模块将在虚拟环境中使用 pip
包管理器安装,因为我们不想让宿主机安装额外的包。
在第 2 步中,我们创建并激活一个新的 Python 虚拟环境,在第 3 步中安装 Python libvirt 模块。
最后,在第 4 步中,我们安装并启动 iPython
开发工具,整个章节中我们将使用该工具。
使用 Python 定义 KVM 实例
在本食谱中,我们将使用在前一个食谱中安装的 Python libvirt 模块来定义一个新的 KVM 实例。我们将使用虚拟环境和 iPython
开发工具进行以下示例。
准备就绪
对于这个食谱,我们将需要以下内容:
-
一台已安装并配置了 libvirt 和 QEMU 的 Ubuntu 主机
-
我们在 第一章 使用 debootstrap 安装自定义操作系统 中构建的
debian.img
原始镜像文件,开始使用 QEMU 和 KVM -
Python 2.7、
iPython
工具和我们在本章 安装并使用 Python libvirt 库 中创建的虚拟环境
如何执行...
要使用 Python libvirt 模块定义新的 KVM 实例,请遵循以下说明:
- 在 iPython 解释器中,导入
libvirt
模块:
In [1]: import libvirt
In [2]:
- 创建实例定义字符串:
In [2]: xmlconfig = """
<domain type='kvm' id='1'>
<name>kvm_python</name>
<memory unit='KiB'>1048576</memory>
<currentMemory unit='KiB'>1048576</currentMemory>
<vcpu placement='static'>1</vcpu>
<resource>
<partition>/machine</partition>
</resource>
<os>
<type arch='x86_64' machine='pc-i440fx-trusty'>hvm</type>
<boot dev='hd'/>
</os>
<features>
<acpi/>
<apic/>
<pae/>
</features>
<clock offset='utc'/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='raw'/>
<source file='/tmp/debian.img'/>
<backingStore/>
<target dev='hda' bus='ide'/>
<alias name='ide0-0-0'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
<controller type='usb' index='0'>
<alias name='usb'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
</controller>
<controller type='pci' index='0' model='pci-root'>
<alias name='pci.0'/>
</controller>
<controller type='ide' index='0'>
<alias name='ide'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
</controller>
<interface type='network'>
<mac address='52:54:00:da:02:01'/>
<source network='default' bridge='virbr0'/>
<target dev='vnet0'/>
<model type='rtl8139'/>
<alias name='net0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</interface>
<serial type='pty'>
<source path='/dev/pts/5'/>
<target port='0'/>
<alias name='serial0'/>
</serial>
<console type='pty' tty='/dev/pts/5'>
<source path='/dev/pts/5'/>
<target type='serial' port='0'/>
<alias name='serial0'/>
</console>
<input type='mouse' bus='ps2'/>
<input type='keyboard' bus='ps2'/>
<graphics type='vnc' port='5900' autoport='yes' listen='0.0.0.0'>
<listen type='address' address='0.0.0.0'/>
</graphics>
<video>
<model type='cirrus' vram='16384' heads='1'/>
<alias name='video0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
</video>
<memballoon model='virtio'>
<alias name='balloon0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
</memballoon>
</devices>
</domain>
"""
In [3]:
- 获取到虚拟化主机的连接:
In [3]: conn = libvirt.open('qemu:///system')
In [4]:
- 定义新实例,但不启动它:
In [4]: instance = conn.defineXML(xmlconfig)
In [5]:
- 列出主机上已定义的实例:
In [5]: instances = conn.listDefinedDomains()
In [6]: print 'Defined instances: {}'.format(instances)
Defined instances: ['kvm_python']
In [7]:
- 使用
virsh
命令确保实例已经定义:
(kvm_python) root@kvm:~/kvm_python# virsh list --all
Id Name State
----------------------------------------------------
- kvm_python shut off
(kvm_python) root@kvm:~/kvm_python#
它是如何工作的...
在本食谱中,我们使用的是在 第一章 开始使用 QEMU 和 KVM 中创建的现有原始 Debian 镜像,用于定义 KVM 实例。
在步骤 1 中,我们导入了 libvirt 包,并定义了新的 KVM 实例。在步骤 2 中,我们将 XML 格式的字符串赋值给 xmlconfig
变量。注意,该定义包含了新实例的名称和镜像文件的位置。
在步骤 3 中,我们获得了连接对象,并将其赋值给 conn
变量。现在,我们可以使用可用方法来定义 KVM 客户机。
要列出 iPython 中某个对象的所有可用方法,请输入变量名并后跟 *.*
,然后按 Tab 键两次:
在 [7] 中:conn.
显示所有 117 个可能性?(y 或 n)
conn.allocPages
conn.getURI
conn.nodeDeviceLookupByName
conn.baselineCPU
conn.getVersion
conn.nodeDeviceLookupSCSIHostByWWN
conn.c_pointer
conn.interfaceDefineXML
conn.numOfDefinedDomains
conn.interfaceLookupByMACString
conn.numOfDefinedInterfaces
...
在 [7] 中:conn.
要获取某个方法的帮助,可以在方法后附加问号字符:
在 [7] 中:conn.defineXML?
类型:实例方法
字符串形式:<绑定方法 virConnect.defineXML,位于 <libvirt.virConnect 对象,地址 0x7fc5e57dc350>>
文件:/root/kvm_python/lib/python2.7/site-packages/libvirt.py
定义:conn.defineXML(self, xml)
文档字符串:
定义一个域,但不启动它。
此定义是持久的,直到显式取消定义为止
virDomainUndefine()。该域的先前定义将会被
如果已经存在,则会被覆盖。
某些虚拟机管理程序可能会阻止此操作,如果当前有
在一个临时域上进行块复制操作,且其 ID 与
如果正在定义域,使用virDomainBlockJobAbort()
来
首先停止块复制操作。
在资源释放后应使用virDomainFree
来释放资源
不再需要该域对象。
在[7]:
在第 4 步中,我们在libvirt.virConnect
连接对象上使用defineXML()
方法,传入 XML 定义字符串,并将其赋值给实例变量。我们可以通过运行以下命令查看新对象的类型:
In [7]: type(instance)
Out[7]: libvirt.virDomain
In [8]:
在第 5 步中,我们通过使用listDefinedDomains()
方法列出主机上的已定义实例,并在第 6 步中使用virsh
命令确认结果。
还有更多内容...
让我们为前面的 Python 代码添加一些简单的错误检查,并将所有内容写入一个新文件。在后续的示例中,我们将继续向这个文件添加内容:
(kvm_python) root@kvm:~/kvm_python# cat kvm.py
import libvirt
xmlconfig = """
<domain type='kvm' id='1'>
<name>kvm_python</name>
<memory unit='KiB'>1048576</memory>
<currentMemory unit='KiB'>1048576</currentMemory>
<vcpu placement='static'>1</vcpu>
<resource>
<partition>/machine</partition>
</resource>
<os>
<type arch='x86_64' machine='pc-i440fx-trusty'>hvm</type>
<boot dev='hd'/>
</os>
<features>
<acpi/>
<apic/>
<pae/>
</features>
<clock offset='utc'/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='raw'/>
<source file='/tmp/debian.img'/>
<backingStore/>
<target dev='hda' bus='ide'/>
<alias name='ide0-0-0'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
<controller type='usb' index='0'>
<alias name='usb'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
</controller>
<controller type='pci' index='0' model='pci-root'>
<alias name='pci.0'/>
</controller>
<controller type='ide' index='0'>
<alias name='ide'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
</controller>
<interface type='network'>
<mac address='52:54:00:da:02:01'/>
<source network='default' bridge='virbr0'/>
<target dev='vnet0'/>
<model type='rtl8139'/>
<alias name='net0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</interface>
<serial type='pty'>
<source path='/dev/pts/5'/>
<target port='0'/>
<alias name='serial0'/>
</serial>
<console type='pty' tty='/dev/pts/5'>
<source path='/dev/pts/5'/>
<target type='serial' port='0'/>
<alias name='serial0'/>
</console>
<input type='mouse' bus='ps2'/>
<input type='keyboard' bus='ps2'/>
<graphics type='vnc' port='5900' autoport='yes' listen='0.0.0.0'>
<listen type='address' address='0.0.0.0'/>
</graphics>
<video>
<model type='cirrus' vram='16384' heads='1'/>
<alias name='video0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
</video>
<memballoon model='virtio'>
<alias name='balloon0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
</memballoon>
</devices>
</domain>
"""
conn = libvirt.open('qemu:///system')
if conn == None:
print 'Failed to connecto to the hypervizor'
exit(1)
instance = conn.defineXML(xmlconfig)
if instance == None:
print 'Failed to define the instance'
exit(1)
instances = conn.listDefinedDomains()
print 'Defined instances: {}'.format(instances)
conn.close()
(kvm_python) root@kvm:~/kvm_python#
要执行脚本,请确保首先未定义python_kmv
实例,然后运行:
(kvm_python) root@kvm:~/kvm_python# python kvm.py
Defined instances: ['kvm_python']
(kvm_python) root@kvm:~/kvm_python#
使用 Python 启动、停止和删除 KVM 实例
在这个示例中,我们将使用在上一示例中定义的实例对象上的create()
方法启动它,并使用destroy()
方法停止它。
要获取有关create()
方法的更多信息,请运行:
In [1]: instance.create?
Type: instancemethod
String Form:<bound method virDomain.create of <libvirt.virDomain object at 0x7fc5d9b97d90>>
File: /root/kvm_python/lib/python2.7/site-packages/libvirt.py
Definition: instance.create(self)
Docstring:
Launch a defined domain. If the call succeeds the domain moves from the
defined to the running domains pools. The domain will be paused only
if restoring from managed state created from a paused domain. For more
control, see virDomainCreateWithFlags().
In [2]:
准备就绪
对于这个示例,我们将需要以下内容:
-
一台安装并配置了 libvirt 和 QEMU 的 Ubuntu 主机
-
我们在第一章中的使用 debootstrap 安装自定义操作系统的镜像示例中构建的
debian.img
原始镜像文件,开始使用 QEMU 和 KVM -
Python 2.7、iPython 工具和我们在本章的安装和使用 Python libvirt 库示例中创建的虚拟环境
-
我们在本章的使用 Python 定义 KVM 实例示例中创建的实例对象
如何做...
要启动之前定义的 KVM 实例,获取其状态并最终停止它,可以使用以下 Python 代码:
- 在实例对象上调用
create()
方法:
In [1]: instance.create()
Out[1]: 0
In [2]:
- 通过调用实例对象上的
isActive()
方法,确保实例处于运行状态:
In [2]: instance.isActive()
Out[2]: 1
In [3]:
- 检查从主机操作系统获取的 KVM 实例状态:
(kvm_python) root@kvm:~/kvm_python# virsh list --all
Id Name State
----------------------------------------------------
5 kvm_python running
(kvm_python) root@kvm:~/kvm_python#
- 使用
destroy()
方法停止实例:
In [3]: instance.destroy()
Out[3]: 0
In [4]:
- 确保实例已被销毁:
In [4]: instance.isActive()
Out[4]: 0
In [5]:
- 删除实例并列出所有已定义的来宾:
In [5]: instance.undefine()
Out[5]: 0
In [6]: conn.listDefinedDomains()
Out[6]: []
In [7]:
它是如何工作的...
在第 1 步中,我们调用create()
方法启动定义的实例。如果成功,来宾系统将从关闭状态过渡到运行状态,正如我们在第 3 步命令的输出中看到的那样。在第 2 步中,我们使用isActive()
方法来检查实例的状态。输出为 1 表示实例正在运行。
在第 4 步中,我们使用destroy()
方法停止实例,并在第 5 步中进行确认。
最后在第 6 步中,我们使用undefine()
方法删除实例,并通过listDefinedDomains()
调用列出所有已定义的实例。
还有更多内容...
让我们将新代码添加到我们在使用 Python 定义 KVM 实例配方中开始的 Python 脚本中。更新后的脚本应该如下所示:
(kvm_python) root@kvm:~/kvm_python# cat kvm.py
import libvirt
import time
xmlconfig = """
<domain type='kvm' id='1'>
<name>kvm_python</name>
<memory unit='KiB'>1048576</memory>
<currentMemory unit='KiB'>1048576</currentMemory>
<vcpu placement='static'>1</vcpu>
<resource>
<partition>/machine</partition>
</resource>
<os>
<type arch='x86_64' machine='pc-i440fx-trusty'>hvm</type>
<boot dev='hd'/>
</os>
<features>
<acpi/>
<apic/>
<pae/>
</features>
<clock offset='utc'/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='raw'/>
<source file='/tmp/debian.img'/>
<backingStore/>
<target dev='hda' bus='ide'/>
<alias name='ide0-0-0'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
<controller type='usb' index='0'>
<alias name='usb'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
</controller>
<controller type='pci' index='0' model='pci-root'>
<alias name='pci.0'/>
</controller>
<controller type='ide' index='0'>
<alias name='ide'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
</controller>
<interface type='network'>
<mac address='52:54:00:da:02:01'/>
<source network='default' bridge='virbr0'/>
<target dev='vnet0'/>
<model type='rtl8139'/>
<alias name='net0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</interface>
<serial type='pty'>
<source path='/dev/pts/5'/>
<target port='0'/>
<alias name='serial0'/>
</serial>
<console type='pty' tty='/dev/pts/5'>
<source path='/dev/pts/5'/>
<target type='serial' port='0'/>
<alias name='serial0'/>
</console>
<input type='mouse' bus='ps2'/>
<input type='keyboard' bus='ps2'/>
<graphics type='vnc' port='5900' autoport='yes' listen='0.0.0.0'>
<listen type='address' address='0.0.0.0'/>
</graphics>
<video>
<model type='cirrus' vram='16384' heads='1'/>
<alias name='video0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
</video>
<memballoon model='virtio'>
<alias name='balloon0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
</memballoon>
</devices>
</domain>
"""
conn = libvirt.open('qemu:///system')
if conn == None:
print 'Failed to connecto to the hypervizor'
exit(1)
instance = conn.defineXML(xmlconfig)
if instance == None:
print 'Failed to define the instance'
exit(1)
instances = conn.listDefinedDomains()
print 'Defined instances: {}'.format(instances)
time.sleep(5)
if instance.create() < 0:
print 'Failed to start the {} instance'.format(instance.name())
exit(1)
if instance.isActive():
print 'The instance {} is running'.format(instance.name())
else:
print 'The instance {} is not running'.format(instance.name())
time.sleep(5)
if instance.destroy() < 0:
print 'Failed to stop the {} instance'.format(instance.name())
exit(1)
else:
print 'The instance {} has been destroyed'.format(instance.name())
if instance.undefine() < 0:
print 'Failed to remove the {} instance'.format(instance.name())
exit(1)
else:
print 'The instance {} has been undefined'.format(instance.name())
conn.close()
(kvm_python) root@kvm:~/kvm_python#
启动脚本时,应该定义一个新的实例,启动它,停止它,最后删除它:
(kvm_python) root@kvm:~/kvm_python# python kvm.py
Defined instances: ['kvm1', 'kvm_python']
The instance kvm_python is running
The instance kvm_python has been destroyed
The instance kvm_python has been undefined
(kvm_python) root@kvm:~/kvm_python#
在前面的脚本中,我们使用了instance.name()
方法来获取 KVM 客户机的名称并打印它。我们还通过调用conn.close()
关闭与虚拟化管理程序的连接,从而进行清理。
使用 Python 检查 KVM 实例
在这个配方中,我们将收集实例信息,使用libvirt.virDomain
类中的方法。
欲了解更多关于 libvirt Python API 的信息,请参考官方文档:libvirt.org/docs/libvirt-appdev-guide-python/en-US/html/index.html
。
准备工作
对于这个配方,我们将需要以下内容:
-
一个安装并配置了 libvirt 和 QEMU 的 Ubuntu 主机
-
我们在第一章的使用 debootstrap 安装自定义操作系统到镜像配方中构建的
debian.img
原始镜像文件,开始使用 QEMU 和 KVM -
Python 2.7、
iPython
工具和我们在本章的安装并使用 Python libvirt 库配方中创建的虚拟环境 -
我们在本章的使用 Python 定义 KVM 实例配方中创建的
instance
对象,代表 KVM 客户机
如何操作...
要收集关于运行实例的 CPU、内存和状态信息,可以使用以下 Python 方法:
- 获取实例的名称:
In [1]: instance.name()
Out[1]: 'kvm_python'
In [2]:
- 确保实例正在运行:
In [2]: instance.isActive()
Out[2]: 1
In [3]:
- 收集 KVM 实例的资源统计信息:
In [3]: instance.info()
Out[3]: [1, 1048576L, 1048576L, 1, 10910000000L]
In [4]:
- 获取分配给实例的最大物理内存:
In [4]: instance.maxMemory()
Out[4]: 1048576L
In [5]:
- 提取实例的 CPU 统计信息:
In [5]: instance.getCPUStats(1)
Out[5]:
[{'cpu_time': 10911545901L,
'system_time': 1760000000L,
'user_time': 1560000000L}]
In [6]:
- 检查虚拟机是否使用了硬件加速:
In [6]: instance.OSType()
Out[6]: 'hvm'
In [7]:
- 收集实例状态:
In [82]: state, reason = instance.state()
In [83]: if state == libvirt.VIR_DOMAIN_NOSTATE:
....: print('The state is nostate')
....: elif state == libvirt.VIR_DOMAIN_RUNNING:
....: print('The state is running')
....: elif state == libvirt.VIR_DOMAIN_BLOCKED:
....: print('The state is blocked')
....: elif state == libvirt.VIR_DOMAIN_PAUSED:
....: print('The state is paused')
....: elif state == libvirt.VIR_DOMAIN_SHUTDOWN:
....: print('The state is shutdown')
....: elif state == libvirt.VIR_DOMAIN_SHUTOFF:
....: print('The state is shutoff')
....: elif state == libvirt.VIR_DOMAIN_CRASHED:
....: print('The state is crashed')
....: elif state == libvirt.VIR_DOMAIN_PMSUSPENDED:
....: print('The state is suspended')
....: else:
....: print('The state is unknown')
....:
The state is running
In [84]:
它是如何工作的...
在本配方中,我们使用了libvirt.virDomain
类中的一些新方法。让我们更详细地了解它们的作用,然后将它们添加到我们在使用 Python 定义 KVM 实例配方中开始的简单kvm.py
Python 脚本中。
在第 1 步和第 2 步中,我们获取了 KVM 实例的名称,并确保它处于运行状态。
在第 3 步中,我们收集了以下实例信息,并以 Python 列表的形式返回:
-
state:实例的状态,如在
libvirt.org/html/libvirt-libvirt-domain.html#virDomainState
中的virDomainState枚举类型中定义 -
maxMemory:客户机使用的最大内存
-
memory:实例当前使用的内存量
-
nbVirtCPU:分配的虚拟 CPU 数量
-
cpuTime:实例使用的时间(以纳秒为单位)
在第 4 步中,我们收集了分配给实例的内存。注意它如何与第 3 步函数的输出匹配。
在第 5 步,我们收集关于来宾实例 CPU 的信息。我们可以看到 CPU、系统和用户时间。
第 6 步中,OSType()
方法的 hvm
输出表示来宾操作系统设计为在裸机上运行,要求完全虚拟化,如 KVM。
在本食谱的最后一步,我们调用 state()
方法以返回当前实例状态。
还有更多...
让我们通过一个完整的示例脚本结束这一章,脚本包含了我们至今使用的所有方法:
(kvm_python) root@kvm:~/kvm_python# cat kvm.py
import libvirt
import time
def main():
xmlconfig = """
<domain type='kvm' id='1'>
<name>kvm_python</name>
<memory unit='KiB'>1048576</memory>
<currentMemory unit='KiB'>1048576</currentMemory>
<vcpu placement='static'>1</vcpu>
<resource>
<partition>/machine</partition>
</resource>
<os>
<type arch='x86_64' machine='pc-i440fx-trusty'>hvm</type>
<boot dev='hd'/>
</os>
<features>
<acpi/>
<apic/>
<pae/>
</features>
<clock offset='utc'/>
<on_poweroff>destroy</on_poweroff>
<on_reboot>restart</on_reboot>
<on_crash>restart</on_crash>
<devices>
<emulator>/usr/bin/qemu-system-x86_64</emulator>
<disk type='file' device='disk'>
<driver name='qemu' type='raw'/>
<source file='/tmp/debian.img'/>
<backingStore/>
<target dev='hda' bus='ide'/>
<alias name='ide0-0-0'/>
<address type='drive' controller='0' bus='0' target='0' unit='0'/>
</disk>
<controller type='usb' index='0'>
<alias name='usb'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x2'/>
</controller>
<controller type='pci' index='0' model='pci-root'>
<alias name='pci.0'/>
</controller>
<controller type='ide' index='0'>
<alias name='ide'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x01' function='0x1'/>
</controller>
<interface type='network'>
<mac address='52:54:00:da:02:01'/>
<source network='default' bridge='virbr0'/>
<target dev='vnet0'/>
<model type='rtl8139'/>
<alias name='net0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x03' function='0x0'/>
</interface>
<serial type='pty'>
<source path='/dev/pts/5'/>
<target port='0'/>
<alias name='serial0'/>
</serial>
<console type='pty' tty='/dev/pts/5'>
<source path='/dev/pts/5'/>
<target type='serial' port='0'/>
<alias name='serial0'/>
</console>
<input type='mouse' bus='ps2'/>
<input type='keyboard' bus='ps2'/>
<graphics type='vnc' port='5900' autoport='yes' listen='0.0.0.0'>
<listen type='address' address='0.0.0.0'/>
</graphics>
<video>
<model type='cirrus' vram='16384' heads='1'/>
<alias name='video0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/>
</video>
<memballoon model='virtio'>
<alias name='balloon0'/>
<address type='pci' domain='0x0000' bus='0x00' slot='0x04' function='0x0'/>
</memballoon>
</devices>
</domain>
"""
conn = libvirt.open('qemu:///system')
if conn == None:
print 'Failed to connecto to the hypervizor'
exit(1)
instance = conn.defineXML(xmlconfig)
if instance == None:
print 'Failed to define the instance'
exit(1)
instances = conn.listDefinedDomains()
print 'Defined instances: {}'.format(instances)
time.sleep(5)
if instance.create() < 0:
print 'Failed to start the {} instance'.format(instance.name())
exit(1)
if instance.isActive():
print 'The instance {} is running'.format(instance.name())
else:
print 'The instance {} is not running'.format(instance.name())
print 'The instance state, max memory, current memory, CPUs and time is {}'.format(instance.info())
print 'The CPU, system and user times are {}'.format(instance.getCPUStats(1))
print 'The OS type for the {} instance is {}'.format(instance.name(), instance.OSType())
time.sleep(5)
if instance.destroy() < 0:
print 'Failed to stop the {} instance'.format(instance.name())
exit(1)
else:
print 'The instance {} has been destroyed'.format(instance.name())
if instance.undefine() < 0:
print 'Failed to remove the {} instance'.format(instance.name())
exit(1)
else:
print 'The instance {} has been undefined'.format(instance.name())
conn.close()
if __name__ == "__main__":
main()
(kvm_python) root@kvm:~/kvm_python#
执行后将提供以下输出,假设 kvm_python
实例已先被取消定义:
(kvm_python) root@kvm:~/kvm_python# python kvm.py
Defined instances: ['kvm_python']
The instance kvm_python is running
The instance state, max memory, current memory, CPUs and time is [1, 1048576L, 1048576L, 1, 40000000L]
The CPU, system and user times are [{'cpu_time': 42349077L, 'system_time': 0L, 'user_time': 30000000L}]
The OS type for the kvm_python instance is hvm
The instance kvm_python has been destroyed
The instance kvm_python has been undefined
(kvm_python) root@kvm:~/kvm_python#
使用 libvirt 和 bottle 构建一个简单的 REST API 服务器
在本食谱中,我们将使用之前食谱中提到的所有 libvirt 方法,构建一个简单的 RESTful API 服务器,利用 Python 的 bottle 微框架。
Bottle 被描述为一个快速且简单的 Web Server Gateway Interface(WSGI)微型 Web 框架,用于 Python,作为一个单一的模块文件发布。
有关 bottle 微框架的更多信息,请访问官方网站:bottlepy.org/docs/dev/
。
我们实现的简单 API 服务器将接受以下请求:
-
list:
get
方法,用于列出所有已定义的 libvirt 实例。 -
define:
post
方法,用于定义一个新的 KVM 实例。我们将在 POST 请求的头部提供 XML 定义。 -
start:
post
方法,用于启动实例。实例的名称将在请求头中提供。 -
stop:
post
方法,用于停止一个 KVM 实例。 -
undefine:
post
方法,用于删除实例。
准备中
对于本食谱,我们将需要以下内容:
-
一台已安装并配置好 libvirt 和 QEMU 的 Ubuntu 主机
-
我们在 使用 debootstrap 安装自定义操作系统到镜像 中构建的
debian.img
原始镜像文件,来自 第一章,QEMU 和 KVM 入门。 -
Python 2.7 和我们在本章 安装并使用 Python libvirt 库 中创建的虚拟环境
-
curl
命令行工具用于通过 URL 语法传输数据,通常由 curl 包提供
如何操作...
以下步骤描述了如何安装 bottle 模块及其 Python 编写的简单 RESTful API 服务器:
- 安装
bottle
模块:
(kvm_python) root@kvm:~/kvm_python# pip install bottle
Collecting bottle
...
Downloading bottle-0.12.13.tar.gz (70kB)
100% |████████████████████████████████| 71kB 4.5MB/s
...
Successfully installed bottle-0.12.13
(kvm_python) root@kvm:~/kvm_python#
- 创建一个新文件,导入 libvirt 和 bottle 模块,并编写 libvirt 连接方法:
(kvm_python) root@kvm:~/kvm_python# vim kvm_api.py
import libvirt
from bottle import run, request, get, post, HTTPResponse
def libvirtConnect():
try:
conn = libvirt.open('qemu:///system')
except libvirt.libvirtError:
conn = None
return conn
- 实现
/define
API 路由和功能:
def defineKVMInstance(template):
conn = libvirtConnect()
if conn == None:
return HTTPResponse(status=500, body='Error defining instance\n')
else:
try:
conn.defineXML(template)
return HTTPResponse(status=200, body='Instance defined\n')
except libvirt.libvirtError:
return HTTPResponse(status=500, body='Error defining instance\n')
@post('/define')
def build():
template = str(request.headers.get('X-KVM-Definition'))
status = defineKVMInstance(template)
return status
- 实现
/undefine
API 路由和功能:
def undefineKVMInstance(name):
conn = libvirtConnect()
if conn == None:
return HTTPResponse(status=500, body='Error undefining instance\n')
else:
try:
instance = conn.lookupByName(name)
instance.undefine()
return HTTPResponse(status=200, body='Instance undefined\n')
except libvirt.libvirtError:
return HTTPResponse(status=500, body='Error undefining instance\n')
@post('/undefine')
def build():
name = str(request.headers.get('X-KVM-Name'))
status = undefineKVMInstance(name)
return status
- 实现
/start
API 路由和功能:
def startKVMInstance(name):
conn = libvirtConnect()
if conn == None:
return HTTPResponse(status=500, body='Error starting instance\n')
else:
try:
instance = conn.lookupByName(name)
instance.create()
return HTTPResponse(status=200, body='Instance started\n')
except libvirt.libvirtError:
return HTTPResponse(status=500, body='Error starting instance\n')
@post('/start')
def build():
name = str(request.headers.get('X-KVM-Name'))
status = startKVMInstance(name)
return status
- 实现
/stop
API 路由和功能:
def stopKVMInstance(name):
conn = libvirtConnect()
if conn == None:
return HTTPResponse(status=500, body='Error stopping instance\n')
else:
try:
instance = conn.lookupByName(name)
instance.destroy()
return HTTPResponse(status=200, body='Instance stopped\n')
except libvirt.libvirtError:
return HTTPResponse(status=500, body='Error stopping instance\n')
@post('/stop')
def build():
name = str(request.headers.get('X-KVM-Name'))
status = stopKVMInstance(name)
return status
- 实现
/list
API 路由和功能:
def getLibvirtInstances():
conn = libvirtConnect()
if conn == None:
return HTTPResponse(status=500, body='Error listing instances\n')
else:
try:
instances = conn.listDefinedDomains()
return instances
except libvirt.libvirtError:
return HTTPResponse(status=500, body='Error listing instances\n')
@get('/list')
def list():
kvm_list = getLibvirtInstances()
return "List of KVM instances: {}\n".format(kvm_list)
- 执行时调用
run()
方法以启动 WSGI 服务器:
run(host='localhost', port=8080, debug=True)
它是如何工作的...
让我们更详细地看一下代码。首先,将前面的更改保存到一个文件中并执行脚本:
(kvm_python) root@kvm:~/kvm_python# python kvm_api.py
Bottle v0.12.13 server starting up (using WSGIRefServer())...
Listening on http://localhost:8080/
Hit Ctrl-C to quit.
在一个单独的终端中,定义一个新的实例,并传递以下 XML 定义作为头信息:
(kvm_python) root@kvm:~/kvm_python# curl -s -i -XPOST localhost:8080/define --header "X-KVM-Definition: <domain type='kvm'><name>kvm_api</name><memory unit='KiB'>1048576</memory><vcpu >1</vcpu><os><type arch='x86_64' machine='pc-i440fx-trusty'>hvm</type></os><devices><emulator>/usr/bin/qemu-system-x86_64</emulator><disk type='file' device='disk'><driver name='qemu' type='raw'/><source file='/tmp/debian.img'/><target dev='hda' bus='ide'/></disk><interface type='network'><mac address='52:54:00:da:02:01'/><source network='default' bridge='virbr0'/><target dev='vnet0'/></interface><graphics type='vnc' port='5900' autoport='yes' listen='0.0.0.0'><listen type='address' address='0.0.0.0'/></graphics></devices></domain>"
HTTP/1.0 200 OK
Date: Fri, 12 May 2017 20:29:14 GMT
Server: WSGIServer/0.1 Python/2.7.6
Content-Length: 17
Content-Type: text/html; charset=UTF-8
Instance defined
(kvm_python) root@kvm:~/kvm_python#
我们正在使用在第一章《开始使用 QEMU 和 KVM》中创建的原始 Debian 镜像。XML 定义应该也很熟悉;我们在本章的大部分配方中都使用了它。
我们现在应该已经定义了一个新的 KVM 实例。让我们使用/list
路由列出所有实例,并使用virsh
命令确认:
(kvm_python) root@kvm:~/kvm_python# curl localhost:8080/list
List of KVM instances: ['kvm_api']
(kvm_python) root@kvm:~/kvm_python# virsh list --all
Id Name State
----------------------------------------------------
- kvm_api shut off
(kvm_python) root@kvm:~/kvm_python#
现在我们已经定义了一个实例,让我们使用/start
路由启动它,并确保它在运行:
(kvm_python) root@kvm:~/kvm_python# curl -s -i -XPOST localhost:8080/start --header "X-KVM-Name: kvm_api"
HTTP/1.0 200 OK
Date: Fri, 12 May 2017 20:29:38 GMT
Server: WSGIServer/0.1 Python/2.7.6
Content-Length: 17
Content-Type: text/html; charset=UTF-8
Instance started
(kvm_python) root@kvm:~/kvm_python# virsh list --all
Id Name State
----------------------------------------------------
1 kvm_api running
(kvm_python) root@kvm:~/kvm_python#
要停止实例并完全删除它,我们使用脚本中的/stop
和/undefine
路由:
(kvm_python) root@kvm:~/kvm_python# curl -s -i -XPOST localhost:8080/stop --header "X-KVM-Name: kvm_api"
HTTP/1.0 200 OK
Date: Fri, 12 May 2017 20:29:52 GMT
Server: WSGIServer/0.1 Python/2.7.6
Content-Length: 17
Content-Type: text/html; charset=UTF-8
Instance stopped
(kvm_python) root@kvm:~/kvm_python#
(kvm_python) root@kvm:~/kvm_python# virsh list --all
Id Name State
----------------------------------------------------
- kvm_api shut off
(kvm_python) root@kvm:~/kvm_python#
(kvm_python) root@kvm:~/kvm_python# curl -s -i -XPOST localhost:8080/undefine --header "X-KVM-Name: kvm_api"
HTTP/1.0 200 OK
Date: Fri, 12 May 2017 20:30:09 GMT
Server: WSGIServer/0.1 Python/2.7.6
Content-Length: 19
Content-Type: text/html; charset=UTF-8
Instance undefined
(kvm_python) root@kvm:~/kvm_python#
(kvm_python) root@kvm:~/kvm_python# virsh list --all
Id Name State
----------------------------------------------------
(kvm_python) root@kvm:~/kvm_python#
让我们更详细地了解代码。
在第 1 步中,我们在 Python 虚拟环境中安装了 bottle 模块。
在第 2 步中导入 libvirt 和 bottle 包后,我们定义了libvirtConnect()
方法。我们的程序中的函数将使用它连接到虚拟机管理程序。
在第 3 步中,我们实现了/define
路由及其功能。@post
装饰器将以下函数的代码与 URL 路径关联。在我们的示例中,/define
路由绑定到build()
函数。将/define
路由传递给 curl 命令将执行该函数,该函数又会调用defineKVMInstance()
方法来定义实例。
我们在第 4 步、第 5 步和第 6 步中使用相同的代码模式来启动、停止和取消定义实例。
在第 7 步中,我们使用@get
装饰器实现一个函数,用于列出主机上所有已定义的实例。
在第 8 步中,我们使用run
类,它提供了我们用来启动内置服务器的run()
方法。在我们的示例中,服务器将在本地主机(localhost)上监听,端口为8080
。
正如我们之前看到的,执行脚本将在8080
端口启动一个监听套接字,我们可以通过curl
命令与之交互。
还有更多...
完整的代码实现如下:
import libvirt
from bottle import run, request, get, post, HTTPResponse
def libvirtConnect():
try:
conn = libvirt.open('qemu:///system')
except libvirt.libvirtError:
conn = None
return conn
def getLibvirtInstances():
conn = libvirtConnect()
if conn == None:
return HTTPResponse(status=500, body='Error listing instances\n')
else:
try:
instances = conn.listDefinedDomains()
return instances
except libvirt.libvirtError:
return HTTPResponse(status=500, body='Error listing instances\n')
def defineKVMInstance(template):
conn = libvirtConnect()
if conn == None:
return HTTPResponse(status=500, body='Error defining instance\n')
else:
try:
conn.defineXML(template)
return HTTPResponse(status=200, body='Instance defined\n')
except libvirt.libvirtError:
return HTTPResponse(status=500, body='Error defining instance\n')
def undefineKVMInstance(name):
conn = libvirtConnect()
if conn == None:
return HTTPResponse(status=500, body='Error undefining instance\n')
else:
try:
instance = conn.lookupByName(name)
instance.undefine()
return HTTPResponse(status=200, body='Instance undefined\n')
except libvirt.libvirtError:
return HTTPResponse(status=500, body='Error undefining instance\n')
def startKVMInstance(name):
conn = libvirtConnect()
if conn == None:
return HTTPResponse(status=500, body='Error starting instance\n')
else:
try:
instance = conn.lookupByName(name)
instance.create()
return HTTPResponse(status=200, body='Instance started\n')
except libvirt.libvirtError:
return HTTPResponse(status=500, body='Error starting instance\n')
def stopKVMInstance(name):
conn = libvirtConnect()
if conn == None:
return HTTPResponse(status=500, body='Error stopping instance\n')
else:
try:
instance = conn.lookupByName(name)
instance.destroy()
return HTTPResponse(status=200, body='Instance stopped\n')
except libvirt.libvirtError:
return HTTPResponse(status=500, body='Error stopping instance\n')
@post('/define')
def build():
template = str(request.headers.get('X-KVM-Definition'))
status = defineKVMInstance(template)
return status
@post('/undefine')
def build():
name = str(request.headers.get('X-KVM-Name'))
status = undefineKVMInstance(name)
return status
@get('/list')
def list():
kvm_list = getLibvirtInstances()
return "List of KVM instances: {}\n".format(kvm_list)
@post('/start')
def build():
name = str(request.headers.get('X-KVM-Name'))
status = startKVMInstance(name)
return status
@post('/stop')
def build():
name = str(request.headers.get('X-KVM-Name'))
status = stopKVMInstance(name)
return status
run(host='localhost', port=8080, debug=True)
第八章:针对 KVM 性能的内核调优
本章将介绍以下性能调优方法:
-
针对低 I/O 延迟调优内核
-
KVM 客户机的内存调优
-
CPU 性能选项
-
使用 libvirt 进行 NUMA 调优
-
针对网络性能调优内核
引言
本章将探讨一些配置选项和工具,这些可以帮助提升宿主操作系统及其上运行的 KVM 实例的性能。
在运行 KVM 虚拟机时,重要的是要理解,从宿主的角度来看,它们是普通的进程。我们可以通过检查虚拟化管理程序上的进程树,看到 KVM 客户机是 Linux 进程:
root@kvm:~# virsh list
Id Name State
----------------------------------------------------
16 kvm running
root@kvm:~# pgrep -lfa qemu
19913 /usr/bin/qemu-system-x86_64 -name kvm -S -machine pc-i440fx-trusty,accel=kvm,usb=off -m 1024 -realtime mlock=off -smp 1,sockets=1,cores=1,threads=1 -uuid 283c6653-9981-9396-efb4-fb864d87f769 -no-user-config -nodefaults -chardev socket,id=charmonitor,path=/var/lib/libvirt/qemu/domain-kvm/monitor.sock,server,nowait -mon chardev=charmonitor,id=monitor,mode=control -rtc base=utc -no-shutdown -boot strict=on -device piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2 -drive file=/tmp/debian.img,format=raw,if=none,id=drive-ide0-0-0 -device ide-hd,bus=ide.0,unit=0,drive=drive-ide0-0-0,id=ide0-0-0,bootindex=1 -netdev tap,fd=26,id=hostnet0 -device rtl8139,netdev=hostnet0,id=net0,mac=52:54:00:2f:df:93,bus=pci.0,addr=0x3 -chardev pty,id=charserial0 -device isa-serial,chardev=charserial0,id=serial0 -vnc 0.0.0.0:0 -device cirrus-vga,id=video0,bus=pci.0,addr=0x2 -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x4 -msg timestamp=on
root@kvm:~#
分配给 KVM 客户机的虚拟 CPU 是 Linux 线程,由宿主调度程序管理:
root@kvm:~# ps -eLf
UID PID PPID LWP C NLWP STIME TTY TIME CMD
...
libvirt+ 19913 1 19913 0 3 14:02 ? 00:00:00 /usr/bin/qemu-system-x86_64 -name kvm -S -machine pc-i440fx-trusty,accel=kvm,usb=off -m 1024 -realtime mlock=off -smp 1,sockets=1,cores=1,threads=1 -uuid 283c6653-9981-9396-efb4-fb864d87f769 -no-user-config -nodefaul
libvirt+ 19913 1 19914 0 3 14:02 ? 00:00:08 /usr/bin/qemu-system-x86_64 -name kvm -S -machine pc-i440fx-trusty,accel=kvm,usb=off -m 1024 -realtime mlock=off -smp 1,sockets=1,cores=1,threads=1 -uuid 283c6653-9981-9396-efb4-fb864d87f769 -no-user-config -nodefaul
libvirt+ 19913 1 19917 0 3 14:02 ? 00:00:00 /usr/bin/qemu-system-x86_64 -name kvm -S -machine pc-i440fx-trusty,accel=kvm,usb=off -m 1024 -realtime mlock=off -smp 1,sockets=1,cores=1,threads=1 -uuid 283c6653-9981-9396-efb4-fb864d87f769 -no-user-config -nodefaul
...
root@kvm:~#
根据 I/O 调度程序的类型、libvirt
网络驱动程序以及内存配置的不同,虚拟机的性能可能会有很大差异。在对上述组件进行任何更改之前,了解客户操作系统将执行的工作类型非常重要。为内存密集型工作调优宿主机和客户操作系统与为 I/O 或 CPU 密集型负载调优是不同的。
因为所有 KVM 实例只是普通的 Linux 进程,所以 QEMU 驱动程序可以应用以下任何 控制组(cgroup)控制器:cpuset
、cpu
、memory
、blkio
和设备控制器。使用 cgroup 控制器可以对允许的 CPU、内存和 I/O 资源进行更细粒度的控制,正如我们在接下来的方法中将更详细地看到的那样。
在调优和优化任何系统时,也许最重要的一点是,在进行任何调整之前,首先要确定性能基准。通过测量子系统(如内存或 I/O)的基准性能,进行小幅增量调整,然后再次测量这些变化的影响,必要时重复此过程,直到达到预期效果。
本章中的方法旨在为读者提供一个起点,帮助了解可以在宿主机和虚拟机上进行哪些调优,以提高性能,或考虑在同一宿主机/虚拟机上运行不同工作负载以及多租户环境的副作用。所有资源的调整应根据工作负载类型、硬件设置和其他变量来进行。
针对低 I/O 延迟调优内核
本章将介绍一些通过选择 I/O 调度程序和使用 Linux 控制组调优块 I/O 来优化磁盘性能的技巧,适用于虚拟客体和宿主机。
在宿主操作系统和 KVM 实例中,有三种 I/O 调度程序可以选择:
-
noop
:这是最简单的内核调度程序之一,它通过将所有传入的 I/O 请求插入一个简单的 FIFO(先进先出)队列来工作。当宿主操作系统在运行多个虚拟机时不应该尝试重新排序 I/O 请求时,这个调度程序非常有用。 -
deadline
:该调度器对所有 I/O 操作施加一个截止期限,以防止请求饿死,并优先考虑读请求,因为进程通常会在读操作上阻塞。 -
cfq
:完全公平队列(CFQ)的主要目标是最大化整体 CPU 利用率,同时提供更好的交互性能。
在主机和客户机上选择正确的 I/O 调度器很大程度上取决于工作负载和底层硬件存储。
一般来说,为客户机操作系统选择noop
调度器允许主机虚拟化管理程序更好地优化 I/O 请求,因为它能感知来自虚拟客户机的所有请求。然而,如果 KVM 虚拟机的底层存储是 iSCSI 卷或其他远程存储(如 GlusterFS),使用 deadline 调度器可能会获得更好的结果。
在大多数现代 Linux 内核中,deadline
调度器是默认的,它可能足以满足运行多个 KVM 虚拟机的主机需求。和任何系统调优一样,修改主机和客机操作系统的调度器时需要进行测试。
准备工作
本指南需要以下内容:
-
一台安装并配置了 libvirt 和 QEMU 的 Ubuntu 主机
-
一个正在运行的 KVM 虚拟机
它是如何实现的...
要更改主机和 KVM 实例上的 I/O 调度器并设置 I/O 权重,请执行以下步骤:
- 在主机操作系统上,列出当前使用的 I/O 调度器,替换为适合您系统的块设备:
root@kvm:~# cat /sys/block/sda/queue/scheduler
noop deadline [cfq]
root@kvm:~#
- 按需更改 I/O 调度器并确保它正在使用,可以运行以下命令:
root@kvm:~# echo deadline > /sys/block/sda/queue/scheduler
root@kvm:~# cat /sys/block/sda/queue/scheduler
noop [deadline] cfq
root@kvm:~#
- 为了使更改在服务器重启后仍然有效,请将以下行添加到 GRUB 默认配置文件中并更新:
root@kvm:~# echo 'GRUB_CMDLINE_LINUX="elevator=deadline"' >> /etc/default/grub
root@kvm:~# tail -1 /etc/default/grub
GRUB_CMDLINE_LINUX="elevator=deadline"
root@kvm:~# update-grub2
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-3.13.0-107-generic
Found initrd image: /boot/initrd.img-3.13.0-107-generic
done
root@kvm:~# cat /boot/grub/grub.cfg | grep elevator
linux /boot/vmlinuz-3.13.0-107-generic root=/dev/md126p1 ro elevator=deadline rd.fstab=no acpi=noirq noapic cgroup_enable=memory swapaccount=1 quiet
linux /boot/vmlinuz-3.13.0-107-generic root=/dev/md126p1 ro elevator=deadline rd.fstab=no acpi=noirq noapic cgroup_enable=memory swapaccount=1 quiet
linux /boot/vmlinuz-3.13.0-107-generic root=/dev/md126p1 ro recovery nomodeset elevator=deadline
root@kvm:~#
- 对于 KVM 实例,持久化设置 noop I/O 调度器:
root@kvm:~# virsh console kvm1
Connected to domain kvm1
Escape character is ^]
root@kvm1:~# echo 'GRUB_CMDLINE_LINUX="elevator=noop"' >> /etc/default/grub
root@kvm1:~# tail -1 /etc/default/grub
GRUB_CMDLINE_LINUX="elevator=noop"
root@kvm1:~# update-grub2
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-3.13.0-107-generic
Found initrd image: /boot/initrd.img-3.13.0-107-generic
done
root@kvm1:~# cat /boot/grub/grub.cfg | grep elevator
linux /boot/vmlinuz-3.13.0-107-generic root=/dev/md126p1 ro elevator=noop rd.fstab=no acpi=noirq noapic cgroup_enable=memory swapaccount=1 quiet
linux /boot/vmlinuz-3.13.0-107-generic root=/dev/md126p1 ro elevator=noop rd.fstab=no acpi=noirq noapic cgroup_enable=memory swapaccount=1 quiet
linux /boot/vmlinuz-3.13.0-107-generic root=/dev/md126p1 ro recovery nomodeset elevator=noop
root@kvm1:~#
- 使用
blkio
cgroup 控制器为 KVM 实例设置权重为 100:
root@kvm:~# virsh blkiotune --weight 100 kvm
root@kvm:~# virsh blkiotune kvm
weight : 100
device_weight :
device_read_iops_sec:
device_write_iops_sec:
device_read_bytes_sec:
device_write_bytes_sec:
root@kvm:~#
- 查找主机上的
cgroup
目录层级:
root@kvm:~# mount | grep cgroup
none on /sys/fs/cgroup type tmpfs (rw)
systemd on /sys/fs/cgroup/systemd type cgroup (rw,noexec,nosuid,nodev,none,name=systemd)
root@kvm:~#
- 确保 KVM 实例的 cgroup 包含我们之前在
blkio
控制器上设置的权重:
root@kvm:~# cat /sys/fs/cgroup/blkio/machine/kvm.libvirt-qemu/blkio.weight
100
root@kvm:~#
有关 Linux cgroup 工作原理的详细解释,请参考 Packt 出版的《LXC 容器化》一书,链接:www.packtpub.com/virtualization-and-cloud/containerization-lxc
。
它是如何工作的...
我们可以通过检查/sys
虚拟文件系统中的scheduler
文件来查看内核当前使用的 I/O 调度器。在第 1 步中,我们看到它是cfq
调度器。接下来,在第 2 步中我们将修改正在运行的系统的 I/O 调度器。请记住,像这样按需更改调度器不会在服务器重启后保持有效。在第 3 步和第 4 步中,我们修改 GRUB 配置文件,将新的调度器信息添加到内核启动指令中。现在重启服务器或虚拟机时将会选择新的 I/O 调度器。
如果在同一主机上运行多个虚拟机,根据特定标准(如时间段和虚拟机工作负载)为其中一些虚拟机分配更高的 I/O 优先级可能会很有用。在第 5 步中,我们使用 blkio
cgroup 控制器为 KVM 客户机设置一个权重。较低的权重将获得更好的 I/O 优先级。在第 6 步和第 7 步中,我们可以看到正确的 cgroup
层次结构已经创建,并且 blkio.weight
文件包含了我们通过 virsh
命令设置的新权重。
KVM 客户机的内存调优
在 KVM 客户机的内存调优方面,根据虚拟机的工作负载,提供的选项很少。一个这样的选项是 Linux 的 HugePages。
大多数 Linux 主机默认按 4 KB 的段(称为页面)来分配内存。然而,内核可以使用更大的页面大小。使用 HugePages(大于 4 KB 的页面)可以通过增加 CPU 缓存命中率来提高性能,从而加速事务的旁路缓冲区(TLB)的访问。TLB 是一种内存缓存,用于存储虚拟内存到物理地址的最近转换,以便快速检索。
在本教程中,我们将启用并设置虚拟化主机和 KVM 客户机上的 HugePages,然后检查 virsh
命令提供的调优选项。
准备工作
对于本教程,我们将需要以下内容:
-
一个安装并配置了 libvirt 和 QEMU 的 Ubuntu 主机
-
一个正在运行的 KVM 虚拟机
如何操作...
要在虚拟化主机和 KVM 客户机上启用和设置 HugePages,并使用 virsh
命令设置各种内存选项,请按照以下步骤操作:
- 检查主机操作系统上当前的 HugePages 设置:
root@kvm:~# cat /proc/meminfo | grep -i huge
AnonHugePages: 509952 kB
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
root@kvm:~#
- 连接到 KVM 客户机并检查当前的 HugePages 设置:
root@kvm1:~# cat /proc/meminfo | grep -i huge
HugePages_Total: 0
HugePages_Free: 0
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
root@kvm1:~#
- 将虚拟化主机上 HugePages 池的大小从 0 增加到 25000,并验证以下内容:
root@kvm:~# sysctl vm.nr_hugepages=25000
vm.nr_hugepages = 25000
root@kvm:~# cat /proc/meminfo | grep -i huge
AnonHugePages: 446464 kB
HugePages_Total: 25000
HugePages_Free: 24484
HugePages_Rsvd: 0
HugePages_Surp: 0
Hugepagesize: 2048 kB
root@kvm:~# cat /proc/sys/vm/nr_hugepages
25000
root@kvm:~#
- 检查虚拟化主机 CPU 是否支持 2 MB 和 1 GB 的 HugePages 大小:
root@kvm:~# cat /proc/cpuinfo | egrep -i "pse|pdpe1" | tail -1
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc aperfmperf eagerfpu pni pclmulqdq dtes64 monitor ds_cpl vmx smx est tm2 ssse3 fma cx16 xtpr pdcm pcid dca sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm arat epb xsaveopt pln pts dtherm tpr_shadow vnmi flexpriority ept vpid fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid
root@kvm:~#
- 通过编辑默认的 GRUB 配置并重启,设置 1 GB 的 HugePages 大小:
root@kvm:~# cat /etc/default/grub
...
GRUB_CMDLINE_LINUX_DEFAULT="rd.fstab=no acpi=noirq noapic cgroup_enable=memory swapaccount=1 quiet hugepagesz=1GB hugepages=1"
...
root@kvm:~# update-grub
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-3.13.0-107-generic
Found initrd image: /boot/initrd.img-3.13.0-107-generic
done
root@kvm:~# cat /boot/grub/grub.cfg | grep -i huge
linux /boot/vmlinuz-3.13.0-107-generic root=/dev/md126p1 ro elevator=deadline rd.fstab=no acpi=noirq noapic cgroup_enable=memory swapaccount=1 quiet hugepagesz=1GB hugepages=1
linux /boot/vmlinuz-3.13.0-107-generic root=/dev/md126p1 ro elevator=deadline rd.fstab=no acpi=noirq noapic cgroup_enable=memory swapaccount=1 quiet hugepagesz=1GB hugepages=1
root@kvm:~# reboot
- 安装 HugePages 包:
root@kvm:~# apt-get install hugepages
- 检查当前的 HugePages 大小:
root@kvm:~# hugeadm --pool-list
Size Minimum Current Maximum Default
2097152 25000 25000 25000 *
root@kvm:~#
- 为 KVM 启用 HugePages 支持:
root@kvm:~# sed -i 's/KVM_HUGEPAGES=0/KVM_HUGEPAGES=1/g' /etc/default/qemu-kvm
root@kvm:~# root@kvm:~# /etc/init.d/libvirt-bin restart
libvirt-bin stop/waiting
libvirt-bin start/running, process 16257
root@kvm:~#
- 在主机操作系统上挂载 HugeTable 虚拟文件系统:
root@kvm:~# mkdir /hugepages
root@kvm:~# echo "hugetlbfs /hugepages hugetlbfs mode=1770,gid=2021 0 0" >> /etc/fstab
root@kvm:~# mount -a
root@kvm:~# mount | grep hugepages
hugetlbfs on /hugepages type hugetlbfs (rw,mode=1770,gid=2021)
root@kvm:~#
- 编辑 KVM 客户机的配置并启用 HugePages:
root@kvm:~# virsh destroy kvm1
Domain kvm1 destroyed
root@kvm:~# virsh edit kvm1
...
<memoryBacking>
<hugepages/>
</memoryBacking>
...
Domain kvm1 XML configuration edited.
root@kvm:~# virsh start kvm1
Domain kvm1 started
root@kvm:~#
如果在启动 KVM 实例时看到以下错误:error: internal error: hugetlbfs filesystem is not mounted or disabled by administrator config
,请确保在第 9 步中成功挂载了 HugePages 虚拟文件系统。
如果在启动 KVM 实例时看到以下错误:error: internal error: process exited while connecting to monitor: file_ram_alloc: can't mmap RAM pages: Cannot allocate memory
,则需要在第 3 步中增加 HugePages 池的大小。
- 更新 KVM 实例的内存硬限制并进行验证,方法如下:
root@kvm:~# virsh memtune kvm1
hard_limit : unlimited
soft_limit : unlimited
swap_hard_limit: unlimited
root@kvm:~# virsh memtune kvm1 --hard-limit 2GB
root@kvm:~# virsh memtune kvm1
hard_limit : 1953125
soft_limit : unlimited
swap_hard_limit: unlimited
root@kvm:~#
工作原理...
Libvirt 和 KVM 支持并利用 HugePages。请注意,并非每个工作负载都能从大于默认大小的页面中受益。运行数据库和内存绑定的 KVM 实例是良好的使用场景。像往常一样,在启用此功能之前,请先在虚拟机内测量应用程序的性能,以确保它能从 HugePages 中受益。
在本配方中,我们在主机和客户操作系统上启用了 HugePages,并为客户操作系统设置了可用内存的硬性限制。让我们更详细地了解这些步骤。
在步骤 1 和 2 中,我们检查 HugePages 的当前状态。从输出中我们可以看到,目前没有分配 HugePages 池,HugePages_Total
字段表明当前 HugePages 的大小为 2 MB。
在步骤 3 中,我们将 HugePages 池的大小增加到 25000。此更改是按需的,并且不会在服务器重启后保持。如果希望其持久化,可以将其添加到 /etc/sysctl.conf
文件中。
为了使用 HugePages 特性,我们需要确保主机服务器的 CPU 支持此功能,正如在步骤 4 中显示的 pse
和 pdpe1
标志所示。
在步骤 5 中,我们配置 GRUB 启动加载程序以启动支持 HugePages 的内核,并设置大小为 1 GB。
尽管我们可以直接操作 /proc
虚拟文件系统暴露的文件,但在步骤 6 中,我们安装了 HugePages 包,它提供了一些有用的用户空间工具,用于列出和管理各种内存设置。在步骤 7 中,我们使用 hugeadm
命令列出了 HugePages 池的大小。
为了启用 KVM 的 HugePages 支持,我们在步骤 8 中更新了 /etc/default/qemu-kvm
文件,在步骤 9 中挂载了虚拟文件系统,最后通过为 <memoryBacking>
对象添加 <hugepages/>
字段重新配置 KVM 虚拟机以使用 HugePages。
Libvirt 提供了一种方便的方式来管理 KVM 客户机分配的内存量。在步骤 11 中,我们为 kvm1
虚拟机设置了 2 GB 的硬性限制。
CPU 性能选项
有几种方法可以控制 CPU 分配以及 KVM 虚拟机的可用 CPU 周期——通过使用 cgroups 和 libvirt 提供的 CPU 钉扎和亲和性功能,我们将在本配方中探讨这些内容。CPU 亲和性是一种调度器属性,它将进程与主机操作系统上给定的一组 CPU 连接。
在使用 libvirt 配置虚拟机时,默认行为是将客户机分配到任何可用的 CPU 核心。在某些情况下,非均匀内存访问(NUMA)就是一个需要为每个 KVM 实例指定一个核心的好例子(正如我们将在下一个食谱中看到的那样)。因此,为虚拟机分配指定的 CPU 核心会更好。由于每个 KVM 虚拟机都是一个内核进程(在我们的示例中,更具体地说是 qemu-system-x86_64
),我们可以使用 taskset
或 virsh
命令等工具来实现这一点。我们还可以使用 cgroups 的 CPU 子系统来管理 CPU 周期分配,从而对每个虚拟机的 CPU 资源利用进行更细粒度的控制。
准备工作
本食谱中,我们需要以下内容:
-
安装并配置了 libvirt 和 QEMU 的 Ubuntu 主机
-
运行中的 KVM 虚拟机
如何操作...
要将 KVM 虚拟机固定到特定 CPU 并更改 CPU 共享,请执行以下操作:
- 获取关于主机上可用 CPU 核心的信息:
root@kvm:~# virsh nodeinfo
CPU model: x86_64
CPU(s): 40
CPU frequency: 2593 MHz
CPU socket(s): 1
Core(s) per socket: 10
Thread(s) per core: 2
NUMA cell(s): 2
Memory size: 131918328 KiB
root@kvm:~#
- 获取关于 KVM 客户机的 CPU 分配信息:
root@kvm:~# virsh vcpuinfo kvm1
VCPU: 0
CPU: 2
State: running
CPU time: 9.1s
CPU Affinity: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
root@kvm:~#
- 将 KVM 实例的 CPU(
VCPU: 0
)固定到第一个虚拟机 CPU(CPU: 0
)并显示结果:
root@kvm:~# virsh vcpupin kvm1 0 0 --live
root@kvm:~# virsh vcpuinfo kvm1
VCPU: 0
CPU: 0
State: running
CPU time: 9.3s
CPU Affinity: y---------------------------------------
root@kvm:~#
- 列出分配给 KVM 实例的运行时共享数:
root@kvm:~# virsh schedinfo kvm1
Scheduler : posix
cpu_shares : 1024
vcpu_period : 100000
vcpu_quota : -1
emulator_period: 100000
emulator_quota : -1
root@kvm:~#
- 修改运行中虚拟机的当前 CPU 权重:
root@kvm:~# virsh schedinfo kvm cpu_shares=512
Scheduler : posix
cpu_shares : 512
vcpu_period : 100000
vcpu_quota : -1
emulator_period: 100000
emulator_quota : -1
root@kvm:~#
- 检查 CPU cgroups 子系统中的 CPU 共享:
root@kvm:~# cat /sys/fs/cgroup/cpu/machine/kvm1.libvirt-qemu/cpu.shares
512
root@kvm:~#
- 检查更新后的 XML 实例定义:
root@kvm:~# virsh dumpxml kvm1
...
<vcpu placement='static'>1</vcpu>
<cputune>
<shares>512</shares>
<vcpupin vcpu='0' cpuset='0'/>
</cputune>
...
root@kvm:~#
它是如何工作的...
我们首先收集关于主机上可用的 CPU 资源信息。从步骤 1 的输出中,我们可以看到主机操作系统在一个插槽上有 40 个 CPU。
在步骤 2 中,我们收集虚拟机 CPU 及其与主机 CPU 的亲和性信息。在这个示例中,KVM 客户机有一个虚拟 CPU,通过 VCPU: 0
记录表示,并且与所有 40 个虚拟机处理器相关联,如 CPU Affinity: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy
字段所示。
在步骤 3 中,我们将虚拟 CPU 固定/绑定到主机上的第一个物理处理器。注意亲和性输出的变化:CPU Affinity: y---------------------------------------
。
从步骤 4 中 virsh
命令的输出中,我们可以观察到分配给 KVM 实例的 CPU 共享数被设置为 1024。这个值是一个比率,意味着如果另一个客户机的共享数为 512,它将比一个共享数为 1024 的实例少一倍的 CPU 运行时间。我们将在步骤 5 中减少该值。
在步骤 6 和 7 中,我们确认了 CPU 共享在主机操作系统的 CPU cgroup 子系统中正确设置。如前所述,CPU 共享是通过 cgroups 配置的,可以通过 virsh
命令直接调整,或者通过 libvirt 提供的功能进行调整。
使用 libvirt 进行 NUMA 调优
NUMA 是一种技术,它允许将系统内存分为多个区域,也称为节点。NUMA 节点随后分配给特定的 CPU 或插槽。与传统的单一内存访问方式不同,在该方式下每个 CPU/核心可以访问所有内存而不考虑其局部性,通常会导致较大的延迟,NUMA 限制的进程可以访问本地 CPU 执行的内存。通常,这比访问连接到远程 CPU 的内存要快得多。
Libvirt 使用 libnuma
库为虚拟机启用 NUMA 功能,如下所示:
root@kvm:~# ldd /usr/sbin/libvirtd | grep numa
libnuma.so.1 => /usr/lib/x86_64-linux-gnu/libnuma.so.1 (0x00007fd12d49e000)
root@kvm:~#
Libvirt NUMA 支持以下内存分配策略来将虚拟机分配到 NUMA 节点:
-
strict: 如果内存无法分配到目标节点,分配将失败
-
interleave: 内存页面以轮询方式分配
-
preferred: 该策略允许虚拟化主机在指定节点的内存不足时从其他节点提供内存
在本教程中,我们将启用 KVM 实例的 NUMA 访问,并探索其对整体系统性能的影响。
准备就绪
对于本教程,我们将需要以下内容:
-
一台安装并配置了 libvirt 和 QEMU 的 Ubuntu 主机
-
一台正在运行的 KVM 虚拟机
-
numastat
工具
如何操作……
要使用严格 NUMA 策略使 KVM 虚拟机在特定的 NUMA 节点和 CPU 上运行,请执行以下步骤:
- 安装
numactl
包并检查虚拟化主机的硬件配置:
root@kvm:~# apt-get install numactl
...
root@kvm:~# numactl --hardware
available: 2 nodes (0-1)
node 0 cpus: 0 1 2 3 4 5 6 7 8 9 20 21 22 23 24 25 26 27 28 29
node 0 size: 64317 MB
node 0 free: 3173 MB
node 1 cpus: 10 11 12 13 14 15 16 17 18 19 30 31 32 33 34 35 36 37 38 39
node 1 size: 64509 MB
node 1 free: 31401 MB
node distances:
node 0 1
0: 10 21
1: 21 10
root@kvm:~#
- 显示当前 KVM 客户机的 NUMA 布局:
root@kvm:~# numastat -c kvm1
Per-node process memory usage (in MBs) for PID 22395 (qemu-system-x86)
Node 0 Node 1 Total
------ ------ -----
Huge 0 0 0
Heap 1 1 2
Stack 2 2 4
Private 39 21 59
------- ------ ------ -----
Total 42 23 65
root@kvm:~#
- 编辑 XML 实例定义,将内存模式设置为严格,并选择第二个 NUMA 节点(索引从 0 开始,因此第二个 NUMA 节点标记为 1),然后重启客户机:
root@kvm:~# virsh edit kvm1
...
<vcpu placement='static' cpuset='10-11'>2</vcpu>
<numatune>
<memory mode='strict' nodeset='1'/>
</numatune>
...
Domain kvm1 XML configuration edited.
root@kvm:~# virsh destroy kvm1
Domain kvm1 destroyed
root@kvm:~# virsh start kvm1
Domain kvm1 started
root@kvm:~#
- 获取 KVM 实例的 NUMA 参数:
root@kvm:~# virsh numatune kvm1
numa_mode : strict
numa_nodeset : 1
root@kvm:~#
- 打印当前虚拟 CPU 的亲和性:
root@kvm:~# virsh vcpuinfo kvm1
VCPU: 0
CPU: 11
State: running
CPU time: 8.4s
CPU Affinity: ----------yy----------------------------
VCPU: 1
CPU: 10
State: running
CPU time: 0.3s
CPU Affinity: ----------yy----------------------------
root@kvm:~#
- 打印 KVM 实例的 NUMA 节点布局:
root@kvm:~# numastat -c kvm1
Per-node process memory usage (in MBs) for PID 22395 (qemu-system-x86)
Node 0 Node 1 Total
------ ------ -----
Huge 0 0 0
Heap 0 3 3
Stack 0 2 2
Private 0 174 174
------- ------ ------ -----
Total 0 179 179
root@kvm:~#
它是如何工作的……
我们首先检查主机操作系统上的 NUMA 设置。从步骤 1 中 numactl
命令的输出中,我们可以观察到虚拟化主机有两个 NUMA 节点:节点 0 和节点 1。每个节点管理一组 CPU。在这种情况下,NUMA 节点 1 包含 CPU 10 到 19 和 CPU 30 到 39,并拥有 64 GB 的内存。这意味着 64 GB 的 RAM 将本地化到这些 CPU 上,并且从这些 CPU 访问内存的速度要比从节点 0 的 CPU 访问内存的速度快得多。为了改善 KVM 客户机的内存访问延迟,我们需要将分配给虚拟机的虚拟 CPU 固定到属于同一 NUMA 节点的 CPU 上。
在步骤 2 中,我们可以看到 KVM 实例使用来自两个 NUMA 节点的内存,这并不理想。
在步骤 3 中,我们编辑了虚拟机的 XML 定义,并使用cpuset='10-11'
参数将虚拟机固定在第 10 和第 11 个 CPU 上,这些 CPU 属于 NUMA 节点 1。我们还使用<memory mode='strict' nodeset='1'/>
参数指定了严格的 NUMA 节点以及第二个 NUMA 节点。
在重启实例后,在步骤 4 中,我们确认 KVM 虚拟机现在正在使用严格的 NUMA 模式在节点 1 上运行。我们还确认 CPU 绑定确实符合步骤 5 中指定的内容。请注意,CPU 亲和性已在 CPU 亲和性元素的第 10 和第 11 个元素上标记。
从步骤 6 的输出中,我们可以看到 KVM 虚拟机现在仅使用 NUMA 节点 1 的内存,符合预期。
如果你在 NUMA 调整前后运行一个内存密集型应用并进行测试,你很可能会看到,当访问 KVM 虚拟机内的大量内存时,得益于 NUMA 提供的 CPU 和内存本地性,性能有显著提升。
还有更多内容...
在本教程中,我们展示了如何通过编辑虚拟机的 XML 定义手动将 KVM 进程分配到 NUMA 节点。像 RHEL/CentOS 7 和 Ubuntu 16.04 这样的某些 Linux 发行版提供了numad
(NUMA 守护进程)服务,旨在通过监控当前的内存拓扑,自动平衡 NUMA 节点之间的进程:
- 在 Ubuntu 16.04 上安装该服务,请运行:
root@kvm:~# lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description: Ubuntu 16.04.2 LTS
Release: 16.04
Codename: xenial
root@kvm2:~# apt install numad
...
root@kvm:~#
- 要启动该服务,请执行以下代码:
root@kvm:~# service numad start
root@kvm2:~# pgrep -lfa numad
12601 /usr/bin/numad -i 15
root@kvm:~#
- 要使用
numad
管理特定的 KVM 虚拟机,传递 KVM 实例的进程 ID:
root@kvm:~# numad -S 0 -p $(pidof qemu-system-x86_64)
root@kvm:~#
- 该服务将记录任何 NUMA 重平衡尝试:
root@kvm:~# tail /var/log/numad.log
Thu May 25 21:06:42 2017: Changing THP scan time in /sys/kernel/mm/transparent_hugepage/khugepaged/scan_sleep_millisecs from 10000 to 1000 ms.
Thu May 25 21:06:42 2017: Registering numad version 20150602 PID 12601
Thu May 25 21:09:25 2017: Adding PID 4601 to inclusion PID list
Thu May 25 21:09:25 2017: Scanning only explicit PID list processes
root@kvm:~#
numad
服务在 OpenStack 计算节点中非常有用,手动进行 NUMA 平衡可能过于繁琐。
调优内核以提升网络性能
大多数现代 Linux 内核已针对各种网络工作负载进行了足够的调优。一些发行版提供预定义的调优服务(例如,Red Hat/CentOS 的tuned
),包括基于服务器角色的配置文件集合。
在深入探讨如何调优虚拟化管理程序之前,让我们回顾一下典型 Linux 主机在数据传输和接收过程中所执行的步骤:
-
应用程序首先将数据写入套接字,然后这些数据被放入发送缓冲区。
-
内核将数据封装成协议数据单元(PDU)。
-
然后,PDU 被移至每个设备的发送队列。
-
网络接口卡(NIC)驱动程序将从发送队列中取出 PDU 并将其复制到 NIC。
-
NIC 发送数据并触发硬件中断。
-
在通信通道的另一端,NIC 接收帧,将其复制到接收缓冲区,并触发硬中断。
-
内核则处理该中断并触发软中断来处理数据包。
-
最后,内核处理软中断,将数据包上送至 TCP/IP 栈进行解封装,并将其放入接收缓冲区,供进程读取。
在本教程中,我们将探讨一些调整 Linux 内核的最佳实践,这通常能在多租户 KVM 主机上提高网络性能。
请确保在进行任何配置更改之前,通过先测量主机性能来建立基线。进行小范围的增量更改后,再次测量其影响。
本教程中的示例并非旨在直接复制粘贴,而是用于帮助理解可能的正面或负面影响。在使用这些示例时,请作为调优指南参考——实际值必须根据服务器类型和整个环境仔细考虑。
准备工作
对于本教程,我们将需要以下内容:
-
一台已安装并配置了 libvirt 和 QEMU 的 Ubuntu 主机
-
一个正在运行的 KVM 虚拟机
如何操作...
为了调整内核以获得更好的网络性能,请执行以下步骤(有关内核可调项的更多信息,请阅读工作原理...部分):
- 增加最大 TCP 发送和接收套接字缓冲区大小:
root@kvm:~# sysctl net.core.rmem_max
net.core.rmem_max = 212992
root@kvm:~# sysctl net.core.wmem_max
net.core.wmem_max = 212992
root@kvm:~# sysctl net.core.rmem_max=33554432 net.core.rmem_max = 33554432 root@kvm:~# sysctl net.core.wmem_max=33554432 net.core.wmem_max = 33554432 root@kvm:~#
- 增加 TCP 缓冲区限制:最小、默认和最大字节数。对于 1 GE 网卡,将最大值设置为 16 MB,对于 10 GE 网卡,设置为 32 MB 或 54 MB:
root@kvm:~# sysctl net.ipv4.tcp_rmem
net.ipv4.tcp_rmem = 4096 87380 6291456
root@kvm:~# sysctl net.ipv4.tcp_wmem
net.ipv4.tcp_wmem = 4096 16384 4194304
root@kvm:~# sysctl net.ipv4.tcp_rmem="4096 87380 33554432"
net.ipv4.tcp_rmem = 4096 87380 33554432
root@kvm:~# sysctl net.ipv4.tcp_wmem="4096 65536 33554432"
net.ipv4.tcp_wmem = 4096 65536 33554432
root@kvm:~#
- 确保启用 TCP 窗口扩展:
root@kvm:~# sysctl net.ipv4.tcp_window_scaling
net.ipv4.tcp_window_scaling = 1
root@kvm:~#
- 为了提高 1 GB 网卡或更大网卡的 TCP 吞吐量,请增加网络接口的发送队列长度。对于往返时延(RTT)大于 50 ms 的路径,建议设置为 5000-10000:
root@kvm:~# ifconfig eth0 txqueuelen 5000
root@kvm:~#
- 减少
tcp_fin_timeout
值:
root@kvm:~# sysctl net.ipv4.tcp_fin_timeout
net.ipv4.tcp_fin_timeout = 60
root@kvm:~# sysctl net.ipv4.tcp_fin_timeout=30
net.ipv4.tcp_fin_timeout = 30
root@kvm:~#
- 降低
tcp_keepalive_intvl
值:
root@kvm:~# sysctl net.ipv4.tcp_keepalive_intvl
net.ipv4.tcp_keepalive_intvl = 75
root@kvm:~# sysctl net.ipv4.tcp_keepalive_intvl=30
net.ipv4.tcp_keepalive_intvl = 30
root@kvm:~#
- 启用
TIME_WAIT
套接字的快速回收。默认值为 0(禁用):
root@kvm:~# sysctl net.ipv4.tcp_tw_recycle
net.ipv4.tcp_tw_recycle = 0
root@kvm:~# sysctl net.ipv4.tcp_tw_recycle=1
net.ipv4.tcp_tw_recycle = 1
root@kvm:~#
- 启用
TIME_WAIT
状态下套接字的重用,以便为新连接使用。默认值为 0(禁用):
root@kvm:~# sysctl net.ipv4.tcp_tw_reuse
net.ipv4.tcp_tw_reuse = 0
root@kvm:~# sysctl net.ipv4.tcp_tw_reuse=1
net.ipv4.tcp_tw_reuse = 1
root@kvm:~#
- 从内核版本 2.6.13 开始,Linux 支持可插拔的拥塞控制算法。所使用的拥塞控制算法通过
sysctl
变量net.ipv4.tcp_congestion_control
设置,Ubuntu 的默认值为 bic/cubic。要查看内核中可用的拥塞控制算法列表(如果您运行的是 2.6.20 或更高版本),请运行以下命令:
root@kvm:~# sysctl net.ipv4.tcp_available_congestion_control
net.ipv4.tcp_available_congestion_control = cubic reno
root@kvm:~#
- 要启用更多可插拔的拥塞控制算法,请加载内核模块:
root@kvm:~# modprobe tcp_htcp
root@kvm:~# modprobe tcp_bic
root@kvm:~# modprobe tcp_vegas
root@kvm:~# modprobe tcp_westwood
root@kvm:~# sysctl net.ipv4.tcp_available_congestion_control
net.ipv4.tcp_available_congestion_control = cubic reno htcp bic vegas westwood
root@kvm:~#
- 对于长且快速的路径,通常最好使用 cubic 或 htcp 算法。Cubic 是许多 Linux 发行版的默认算法,但如果在您的系统中不是默认值,您可以执行以下操作:
root@kvm:~# sysctl net.ipv4.tcp_congestion_control
net.ipv4.tcp_congestion_control = cubic
root@kvm:~#
- 如果虚拟机监控程序(Hypervisor)被 SYN 连接淹没,以下选项可能有助于减少影响:
root@kvm:~# sysctl net.ipv4.tcp_max_syn_backlog
net.ipv4.tcp_max_syn_backlog = 2048
root@kvm:~# sysctl net.ipv4.tcp_max_syn_backlog=16384
net.ipv4.tcp_max_syn_backlog = 16384
root@kvm:~# sysctl net.ipv4.tcp_synack_retries
net.ipv4.tcp_synack_retries = 5
root@kvm:~# sysctl net.ipv4.tcp_synack_retries=1
net.ipv4.tcp_synack_retries = 1
root@kvm:~#
- 拥有足够的可用文件描述符非常重要,因为 Linux 上的几乎所有东西都是文件。每个网络连接都使用一个文件描述符/套接字。要检查当前的最大和可用文件描述符,请运行以下代码:
root@kvm:~# sysctl fs.file-nr
fs.file-nr = 1280 0 13110746
root@kvm:~#
- 要增加最大文件描述符,请执行以下操作:
root@kvm:~# sysctl fs.file-max=10000000
fs.file-max = 10000000
root@kvm:~# sysctl fs.file-nr
fs.file-nr = 1280 0 10000000
root@kvm:~#
- 如果你的虚拟机监控程序使用有状态的 iptables 规则,
nf_conntrack
内核模块可能会因连接跟踪耗尽内存而导致错误日志记录:nf_conntrack: table full, dropping packet
。为了提高这个限制并分配更多内存,你需要计算每个连接占用多少 RAM。你可以通过/proc/slabinfo
文件获取此信息。nf_conntrack
条目展示了活动条目、每个对象的大小以及每个 slab 中可以容纳多少个对象(每个 slab 通常会占用一个或多个内核页,通常是 4K,如果没有使用 HugePages 的话)。考虑到内核页的开销,你可以从slabinfo
中看到每个nf_conntrack
对象大约占用 316 字节(不同系统上可能有所不同)。因此,要跟踪 100 万个连接,你大约需要分配 316 MB 的内存:
root@kvm:~# sysctl net.netfilter.nf_conntrack_count
net.netfilter.nf_conntrack_count = 23
root@kvm:~# sysctl net.netfilter.nf_conntrack_max
net.netfilter.nf_conntrack_max = 65536
root@kvm:~# sysctl -w net.netfilter.nf_conntrack_max=1000000
net.netfilter.nf_conntrack_max = 1000000
root@kvm:~# echo 250000 > /sys/module/nf_conntrack/parameters/hashsize # hashsize = nf_conntrack_max / 4
root@kvm:~#
它是如何工作的...
在第 1 步中,我们增加了最大发送和接收套接字缓冲区的大小。这将为 TCP 栈分配更多内存,但在内存较大且有大量 TCP 连接的服务器上,确保缓冲区大小足够。选择默认值的一个好起点是带宽延迟积(BDP),它是基于测得的延迟,例如,将链路带宽与某个主机的平均往返时间相乘。
在第 2 步中,我们增加了 TCP 用于调节发送缓冲区大小的最小、默认和最大字节数。TCP 会根据默认值动态调整发送缓冲区的大小。
在第 3 步中,我们确保启用了窗口缩放。TCP 窗口缩放会自动增大接收窗口的大小。
有关窗口缩放的更多信息,请参考en.wikipedia.org/wiki/TCP_window_scale_option
。
在第 5 步中,我们减少了tcp_fin_timeout
值,该值指定在强制关闭套接字之前等待最后一个 FIN 包的秒数。在第 6 和第 7 步中,我们减少了 TCP 保持活动探测的时间间隔,并加速了TIME_WAIT
状态下套接字的回收。
为了帮助回忆,以下图示展示了连接可能处于的各种 TCP 状态:
TCP 状态图
在第 8 步中,我们仅对新连接启用TIME_WAIT
状态下的套接字重用。在有大量 KVM 实例的主机上,这可能会显著影响新连接的建立速度。
在第 9 和第 10 步中,我们启用了各种拥塞控制算法。选择哪种拥塞控制算法是在内核构建时确定的。在第 11 步中,我们选择了 cubic 算法,其中窗口是自上次拥塞事件以来时间的立方函数,拐点设置为该事件发生前的窗口大小。
更多关于网络拥塞避免算法的信息,请参考en.wikipedia.org/wiki/TCP_congestion_control
。
在遇到大量 SYN 请求的系统中,通过调整尚未收到连接客户端确认的排队连接请求的最大数量,可以使用tcp_max_syn_backlog
和tcp_synack_retries
选项进行优化。这一操作我们在第 12 步完成。
在第 13 和第 14 步中,我们增加了系统上的最大文件描述符数量。当系统中有大量网络连接时,这有助于处理,因为每个连接都需要一个文件描述符。
在最后一步,我们有nf_conntrack_max
选项。如果我们使用nf_conntrack
内核模块在虚拟机监控器上跟踪连接,这个选项是非常有用的。