精通-Linux-管理第二版-全-
精通 Linux 管理第二版(全)
原文:
annas-archive.org/md5/b0d103ad273f12a8dd45f31bf23f85fb译者:飞龙
前言
掌握 Linux 系统管理提供了现代服务器和云管理技术的终极覆盖。
技术以空前的速度发展,Linux 及相关技术处于创新的最前沿。这使得跟上步伐并学习新事物变得非常困难。当前的 Linux 管理员不仅需要了解 Linux,还需要掌握容器化和云技术,这对于未来的 DevOps 专家至关重要。
Linux 是驱动几乎所有事物的操作系统,从物联网到个人计算机,再到服务器,它是所有云技术的基础。通过命令行的强大功能,Linux 使你能够掌握云计算。
你将从学习命令行、处理文件、进程、用户、软件包和文件系统开始,然后将学习如何管理网络服务和加强安全性,最后,你将学习云计算、容器和编排技术。你将学会如何在命令行工作,学习最重要的 Linux 命令,并掌握用户、进程和服务的管理。你还将学习如何使用 iptables 来加强 Linux 安全性。最后,你将学习如何使用容器、虚拟化技术、虚拟机、Ansible 和 Kubernetes,并学会如何在 AWS 和 Azure 上部署 Linux。本书结束时,你将完全掌握 Linux,并且能够从裸机到云端以纯粹的 DevOps 方式自信地使用 Linux。
本书适合的读者
本书适合那些想要理解 Linux 系统管理基础知识以及现代概念的 Linux 管理员。希望将知识拓展到 Linux 操作系统的 Windows 系统管理员也会从本书中受益。
本书的内容
第一章,安装 Linux,向你展示了如何在物理硬件(裸机)和 Windows 中的虚拟机上安装 Linux。由于我们面向的是未来的 Linux 系统管理员,本书大部分时间将使用命令行,并且很少提及 GUI。未来的 Linux 专业人员将学习如何安装 Linux 以及引导过程的工作原理。
第二章,Linux Shell 与文件系统,教你如何使用命令行,并介绍了 Linux 中最常用的命令。你将学习基本命令的结构、Linux 文件系统的组织方式、Linux 操作系统的结构以及文件的结构。在本章结束时,你还将学会如何使用 VI/VIM,这是 Linux 中广泛使用的命令行文本编辑器之一。
第三章,Linux 软件管理,解释了如何使用特定的软件管理命令,软件包在不同 Linux 发行版中的工作原理,以及如何构建自己的软件包。
第四章,管理用户和组,向您展示如何在 Linux 中管理用户账户。这是 Linux 系统管理员应该掌握的最重要任务之一。您将了解一般概念,用于用户管理的特定文件,以及如何管理账户。本章结束时,您将了解如何处理权限及其更改,并理解特殊权限和属性。
第五章,处理进程、守护进程和信号,探讨了 Linux 中的进程、信号和服务。您将学习如何管理它们,如何使用它们,以及它们之间的区别。
第六章,处理磁盘和文件系统,教您如何管理磁盘和文件系统,在 Linux 中理解存储,使用逻辑卷管理(LVM)系统,以及如何挂载和分区。
第七章,Linux 网络,讨论了 Linux 中的网络工作原理,包括关键概念以及如何从命令行和图形界面配置您的网络。
第八章,Linux Shell 脚本编程,向您展示如何在 Linux 中创建和使用 Bash shell 脚本进行任务自动化。这对任何系统管理员都是非常宝贵的资产。
第九章,Linux 安全,深入探讨了 Linux 安全的高级主题。您将学习如何使用 SELinux 和 AppArmor。
第十章,灾难恢复、诊断和故障排除,向您展示了如何在灾难恢复场景中进行系统备份和恢复。此外,您还将学习如何诊断和排除各种常见问题。
第十一章,虚拟机操作,描述了如何在 Linux 上设置和使用 KVM 虚拟机。
第十二章,使用 Docker 管理容器,介绍了容器的概念,并讨论如何使用特定于 Docker 的工具来部署您的应用程序。
第十三章,配置 Linux 服务器,向您展示如何配置不同类型的 Linux 服务器,包括域名系统(DNS)、动态主机配置协议(DHCP)、安全外壳(SSH)、Samba文件共享服务器和网络文件系统(NFS)。这是任何优秀的 Linux 系统管理员的核心基础之一。
第十四章,云计算简介,涵盖了云计算的基础知识。您将了解核心技术,如基础设施即服务(IaaS)、平台即服务(PaaS)、容器即服务(CaaS)、DevOps以及云管理工具。
第十五章,使用 AWS 和 Azure 部署至云端,解释了如何将 Linux 部署到 AWS 和 Azure。
第十六章,使用 Kubernetes 部署应用程序,教您如何使用 Kubernetes 监控和保护您的部署,以及如何管理容器和网络。您将了解 Kubernetes 的概念及其多样的社区方法。
第十七章,使用 Ansible 进行基础设施和自动化,介绍了 Ansible 的配置方法,以及如何管理 playbook、模块和服务器。本章结束时,您将成为自动化的大师。
要充分利用本书
您需要 Ubuntu Linux LTS 或 Debian Linux 才能执行本书中的示例。不需要 Linux 的先前知识。
如果您正在使用本书的数字版本,建议您自行输入代码或从书籍的 GitHub 存储库中访问代码(下一节中提供链接)。这样做将有助于避免与复制粘贴代码相关的任何潜在错误。
下载示例代码文件
您可以从 GitHub 下载本书的示例代码文件,地址为github.com/PacktPublishing/Mastering-Linux-Administration-Second-Edition。如果代码有更新,将会在 GitHub 存储库中更新。
我们还有来自我们丰富书籍和视频目录的其他代码包,可在github.com/PacktPublishing/获取。快来看看吧!
使用的约定
本书中使用了多种文本约定。
文本中的代码:表示文本中的代码字词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 句柄。例如:“要检查二进制deb包的内容,可以使用ar命令。”
代码块设置如下:
spec:
replicas: 1
任何命令行输入或输出如下所示:
$ sudo zypper search nmap
当我们希望引起您对代码块或命令的特别关注时,相关的行或条目会以粗体显示:
uid=1004(alex2) gid=1100(admin) groups=1100(admin),1200(developers),1300(devops),1400(managers)
粗体:表示一个新术语、重要词汇或屏幕上看到的词汇。例如,菜单或对话框中的词语会显示为粗体。例如:“导航至虚拟机,选择您的实例,并在可用性 + 缩放下点击大小。”
提示或重要笔记
将呈现如此。
联系我们
我们始终欢迎读者的反馈。
一般反馈:如果您对本书的任何方面有疑问,请发送电子邮件至 customercare@packtpub.com,并在主题中提及书名。
勘误表:虽然我们已经尽力确保内容的准确性,但难免会出现错误。如果您在本书中发现错误,我们非常感激您能向我们报告。请访问www.packtpub.com/support/errata并填写表单。
盗版:如果您在互联网上发现任何非法的我们作品的复制品,感谢您提供相关网址或网站名称。请通过 copyright@packt.com 联系我们并附上该资料的链接。
如果你有兴趣成为作者:如果你擅长某个领域并有兴趣撰写或参与撰写书籍,请访问authors.packtpub.com。
分享您的想法
一旦您阅读完掌握 Linux 管理,我们非常希望听到您的想法!请点击这里直接前往亚马逊书籍评论页面并分享您的反馈。
您的评论对我们以及技术社区至关重要,它将帮助我们确保提供优质的内容。
下载本书的免费 PDF 版本
感谢您购买本书!
您是否喜欢在旅途中阅读,但无法随身携带印刷版书籍?
您的电子书购买是否与您选择的设备不兼容?
不用担心,现在每本 Packt 书籍都附带免费的无 DRM 保护的 PDF 版本。
随时随地在任何设备上阅读。在您最喜爱的技术书籍中搜索、复制并粘贴代码,直接应用到您的程序中。
福利不仅仅如此,您还可以独家获得折扣、新闻通讯以及每日送达的精彩免费内容。
按照以下简单步骤即可获得福利:
- 扫描二维码或访问以下链接

packt.link/free-ebook/9781837630691
-
提交您的购买凭证
-
就这些!我们会直接将您的免费 PDF 和其他福利发送到您的邮箱。
第一部分:基本 Linux 管理
在本部分中,您将掌握 Linux 命令行和基本的管理任务,例如用户管理、软件包管理、文件管理、服务管理、进程管理、信号处理以及磁盘管理。
本部分包含以下章节:
-
第一章,安装 Linux
-
第二章,Linux Shell 与文件系统
-
第三章,Linux 软件管理
-
第四章,管理用户与组
-
第五章,处理进程、守护进程与信号
第一章:安装 Linux
近年来,Linux作为服务器和桌面计算平台的首选操作系统得到了显著的增长。从企业级服务器、大规模云基础设施,到个人工作站和小型家用设备,Linux 已成为广泛应用平台。
Linux 的普及,现在可能比以往任何时候都更加重要,凸显了系统管理员和开发者日益增长的管理技能需求。在本书中,我们以实践为导向,讲解了 Linux 管理基础,面向现代的系统管理员、DevOps 团队成员以及开发者。
在第二版中,我们将采用略有不同的方式来安装 Linux。由于本书面向的是更高级的读者,我们将不再像第一版那样详细讨论操作系统安装的基本内容。信息已经更新,涵盖了截至 2023 年初与操作系统版本相关的最重要方面。
在本章中,我们将指导你完成 Linux 的安装过程,无论是在物理硬件(裸金属)上,还是使用虚拟机(VM)。
本章中涉及的主题包括:
-
介绍 Linux 操作系统
-
安装 Linux – 基础
-
启用 Windows 子系统 Linux
-
安装 Linux – 高级阶段
-
Linux 发行版 – 实践指南
技术要求
我们将在本章中使用以下平台和技术:
-
Linux 发行版:Ubuntu
-
VM 虚拟机监控程序:Oracle VM VirtualBox、VMware Workstation Player 和 Hyper-V
-
VM 主机平台:Windows 11(同样适用于 macOS)
介绍 Linux 操作系统
Linux 是由芬兰赫尔辛基的计算机科学学生 Linus Torvalds 于 1991 年创建的一个相对现代的操作系统。最初发布时为免费和开源平台,禁止商业重新分发,Linux 最终在 1992 年采用了 GNU 通用公共许可证(GPL)模型。这一举措对其在开发者社区和商业企业中的广泛采用起到了重要作用。需要注意的是,自由软件基金会社区特别称 Linux 操作系统(或发行版)为 GNU/Linux,以强调 GNU 对自由软件的重要性。
最初为英特尔 x86 处理器架构设计,Linux 现已被移植到多种平台,成为目前使用最广泛的操作系统之一。Linux 的起源可以看作是对其强大前身 Unix 的开源替代方案的诞生。这个系统是 AT&T 贝尔实验室的 Ken Thompson 和 Dennis Ritchie 于 1969 年开发的商业级操作系统。
探索 Linux 发行版
Linux 操作系统通常被称为发行版。一个 Linux 发行版,或称distro,是一个操作系统安装包(通常是 ISO 镜像),它包含了工具、库以及安装在 Linux内核之上的附加软件包。内核是计算机硬件与进程之间的核心接口,负责控制两者之间的通信,并尽可能高效地管理底层资源。
与 Linux 内核捆绑的软件集合通常包括引导加载程序、shell、包管理系统、图形用户界面以及各种软件实用工具和应用程序。
以下图示是通用 Linux 发行版架构的简化示意图:

图 1.1 – 简化版的通用 Linux 架构
目前有数百个 Linux 发行版可供选择。其中最古老且 arguably 最受欢迎的包括Debian、Fedora、openSUSE、Arch Linux和Slackware,此外还有许多其他 Linux 发行版要么基于它们,要么从它们派生。部分这些发行版被分为商业平台和社区支持平台。
重要提示
在编写本书第二版时,CentOS 已经转变为滚动更新版,并成为未来Red Hat Enterprise Linux(RHEL)版本的基础。它的位置被其他使用 RHEL 二进制文件的免费社区发行版所取代。其中,Rocky Linux是一个很好的例子,我们将在本书中对其进行引用。另一个基于 RHEL 的社区发行版是AlmaLinux。
不同的 Linux 发行版之间的一个关键区别是它们使用的包管理系统和相关的 Linux 包格式。我们将在第三章中详细讨论这个话题。现在,重点是根据我们的需求选择合适的 Linux 发行版。但在做出决定之前,你首先需要了解一些最常用的发行版。因此,在接下来的部分,我们将简要介绍一些 Linux 发行版。
常见的 Linux 发行版
本节总结了在编写本版时最流行和最常见的 Linux 发行版,重点介绍它们的包管理器类型。大多数这些发行版是免费的开源平台。如果有商业级变体,会特别注明:
-
Fedora、CentOS Stream 和 RHEL:CentOS 及其衍生版本使用Red Hat 包管理器(RPM)作为包管理器。CentOS Stream,现在是一个滚动更新的发行版,基于开源的 Fedora 项目。它适用于服务器和工作站。RHEL 是从 CentOS Stream 派生出的商业级版本,旨在成为一个具有长期支持的稳定平台。使用 RHEL 二进制文件的社区发行版是 Rocky Linux。
-
Debian:Debian 及其大多数衍生版的包管理器是Debian 包(DPKG)。与 Linux Mint 或 Ubuntu 等其他 Linux 发行版相比,Debian 的发布速度较慢,但它相对更加稳定。
-
Ubuntu:Ubuntu 使用高级包工具(APT)和 DKPG 作为包管理器。Ubuntu 是最流行的 Linux 发行版之一,每 6 个月发布一次,并且每隔一年发布一次更稳定的长期支持(LTS)版本。
-
Linux Mint:Linux Mint 也使用 APT 作为包管理器。Linux Mint 基于 Ubuntu 构建,主要适用于桌面使用,内存使用比 Ubuntu 更低(使用 Cinnamon 桌面环境,相比 Ubuntu 的 GNOME)。还有一个基于 Debian 直接构建的 Linux Mint 版本,称为Linux Mint Debian Edition(LMDE)。
-
openSUSE:openSUSE 使用RPM、又一个设置工具(YaST)和Zypper作为包管理器。openSUSE 有两个版本:一个叫做 Tumbleweed,是一个滚动发布版,属于领先的 Linux 发行版;另一个是 Leap,一个常规发布版,使用与 SUSE Linux Enterprise 相同的代码库。两个版本都适用于桌面和服务器环境。SUSE Linux Enterprise Server 是一个商业级平台。在 Ubuntu 崛起之前,openSUSE 被认为是最用户友好的桌面 Linux 发行版之一。
重要提示
本书的重点主要是介绍在社区和商业部署中广泛使用的 Linux 发行版,例如Ubuntu、Fedora/Rocky Linux和openSUSE。本书中的大部分示例适用于任何 Linux 发行版。我们会明确说明在给定的示例或场景中使用的是哪一个。
现在你已经了解了关于最常见的 Linux 发行版的一些信息,在接下来的部分,我们将给你一些选择 Linux 发行版的提示。
选择一个 Linux 发行版
选择一个 Linux 发行版涉及多个方面,基于各种功能需求。全面的分析远远超出了本章的范围。然而,考虑到一些基本要点可能有助于做出正确的决定:
-
平台:在选择 Linux 发行版时,选择服务器、桌面或嵌入式平台可能是最重要的决策之一。Linux 服务器平台和嵌入式系统通常配置有核心操作系统服务和特定应用所需的基本组件(如网络、HTTP、FTP、SSH 和电子邮件),主要是出于性能和优化的考虑。另一方面,Linux 桌面工作站预装了大量软件包,包括图形用户界面,以提供更加友好的用户体验。一些 Linux 发行版有服务器和桌面版本(例如 Ubuntu、Fedora 和 openSUSE),但大多数发行版提供的是最小操作系统,需要进一步配置(例如 Rocky Linux 和 Debian)。通常,这些发行版是 Linux 服务器平台的良好候选者。还有一些专门为桌面使用设计的 Linux 发行版,如 elementary OS、Pop!_OS 或 Deepin。对于嵌入式系统,我们有高度优化的 Linux 发行版,如 Raspbian 和 OpenWRT,可以适应硬件资源有限的小型设备。
-
基础设施:如今,我们看到从硬件和本地(本地数据中心)到虚拟化管理程序、容器和云基础设施的各种应用和服务器平台部署。选择 Linux 发行版时,应考虑这些类型的部署所涉及的资源和成本。例如,多 CPU、大内存且一般需要较大资源占用的 Linux 实例在云端或 虚拟专用服务器(VPS)托管基础设施中运行可能会更昂贵。轻量级的 Linux 发行版占用较少资源,并且在容器化工作负载和服务的环境中更容易扩展(例如,使用 Kubernetes 和 Docker)。大多数 Linux 发行版现在都提供适用于主要公共云服务提供商的云镜像(例如,Amazon AWS、Microsoft Azure 和 Google Compute Engine)。各种 Linux 发行版的 Docker 容器镜像可以在 Docker Hub (
hub.docker.com) 下载。有些 Docker 镜像比其他镜像大(重)。例如,Ubuntu Server 的 Docker 镜像比 Alpine Linux 的 Docker 镜像要重得多,这可能会在选择某个发行版时影响决策。此外,为了应对容器化工作流程和服务的相对新变化,一些 Linux 发行版提供了精简或优化版本的操作系统,以支持底层应用基础设施。例如,Fedora 提供了 Fedora CoreOS(用于容器化工作流程)和 Fedora IoT(用于物联网生态系统)。 -
性能:可以说,所有 Linux 发行版都可以在 CPU、GPU、内存和存储方面进行调整,以达到高性能基准。性能应该与平台和所选应用程序紧密关联。例如,邮件后台在树莓派上运行效果不会很好,而媒体流服务器则能正常运行(附加一些外部存储)。调整性能的配置工作也应当考虑在内。Rocky Linux、Debian、openSUSE、Fedora 和 Ubuntu 都提供了经过合理优化的服务器和桌面版本,适合各自的用途。服务器版本可以通过将软件包限制为应用程序所需的基本部分,轻松地根据特定的应用程序或服务进行定制。为了进一步提升性能,有些人会选择重新编译一个轻量级的 Linux 发行版(例如,Gentoo),以通过针对特定子系统(例如,网络栈或用户权限)的内核编译优化来提高性能。与任何其他标准一样,基于某些应用程序或平台性能选择 Linux 发行版是一种平衡行为,大多数时候,常见的 Linux 发行版都能表现出色。
-
安全性:在考虑安全性时,我们必须牢记,系统的安全性取决于其最薄弱的环节。一个不安全的应用程序或系统组件会使整个系统面临风险。因此,Linux 发行版的安全性应该根据相关应用程序和平台环境进行严格审查。我们可以讨论一下作为桌面工作站的 Linux 发行版的桌面安全性,例如,用户浏览互联网、下载媒体、安装各种软件包和运行不同应用程序时的安全性。所有这些操作的安全处理(防止恶意软件、病毒和入侵)将成为系统安全性的重要指标。有些 Linux 发行版专门针对应用程序的安全性和隔离进行了高度优化,非常适合桌面使用:Qubes OS、Kali Linux、Whonix、Tails 和 Parrot Security OS。其中一些发行版是为了渗透测试和安全研究而开发的。
另一方面,我们可能需要考虑 Linux 服务器发行版的服务器安全问题。在这种情况下,定期进行操作系统更新,包括最新的仓库、软件包和组件,将大大增强系统的安全性。移除未使用的面向网络的服务,并配置更严格的防火墙规则,是进一步减少潜在攻击面的重要步骤。大多数 Linux 发行版都配备了所需的工具和服务,支持此类重新配置。选择一个频繁且稳定升级或发布周期的发行版,通常是确保平台安全的首要条件(例如,Rocky Linux、RHEL、Ubuntu LTS 或 SUSE Enterprise Linux)。
- 可靠性:具有激进发布周期且每个版本添加大量新代码的 Linux 发行版通常不太稳定。对于此类发行版,选择一个稳定版本至关重要。以 Fedora 为例,它具有快速的发布周期,是进展最快的 Linux 平台之一。然而,我们不应听信那些声称 Fedora 或其他类似的快速发展的 Linux 发行版(如 openSUSE Tumbleweed)不可靠的谣言。别忘了,一些最可靠的 Linux 发行版,如 RHEL 和 SUSE Linux Enterprise (SLE),分别源自 Fedora 和 openSUSE。
选择一个 Linux 发行版没有所谓的“魔法公式”。在大多数情况下,平台的选择(无论是服务器、桌面还是物联网)与个人偏好结合,决定了要选择哪种 Linux 发行版。在生产级环境中,之前列出的许多标准变得至关重要,我们所选择的 Linux 平台的可选方案会缩小为几个行业认可的解决方案。
重要提示
本书的重点主要是广泛应用于社区和商业部署的 Linux 发行版,如 Ubuntu、Fedora/Rocky Linux 和 openSUSE。尽管如此,本书中的大部分示例同样适用于任何 Linux 发行版。我们会明确指出在具体示例或场景中使用的发行版。
现在你已经了解了什么是 Linux 发行版,以及最常用的几种发行版及其使用场景,在接下来的两部分中,我们将介绍 Linux 安装的基本和高级方面。
安装 Linux – 基础知识
本节作为快速指南,介绍了任意 Linux 发行版的基本安装方法。为了提供实际示例和具体的指导,我们使用了 Ubuntu。我们还简要地回顾了托管 Linux 安装的不同环境。目前,混合云基础设施的趋势正在兴起,这种基础设施结合了本地数据中心和公共云部署,其中 Linux 主机可以是裸金属系统、虚拟机监控器、虚拟机或 Docker 容器。
在大多数情况下,进行 Linux 安装时所遵循的相同原则适用。有关 Docker 容器化 Linux 部署的详细信息,请参见第十三章。
在接下来的章节中,我们将向您展示如何在裸机和 Windows 11 主机上使用不同的虚拟机管理程序安装 Linux,以及使用 WSL 安装。安装在 macOS 主机上的过程与在 Windows 上使用虚拟机管理程序安装基本相同,我们不会涉及这一部分内容。
如何在裸机上安装 Linux
本节描述了在裸机上安装 Linux 所需的基本步骤。我们使用“裸机”一词指的是笔记本、台式机、工作站和服务器等硬件。简而言之,主要步骤是下载 ISO 镜像、创建可启动媒体、尝试使用 live 模式,最后进行安装。
这里使用的步骤同样适用于虚拟机安装,正如接下来的章节所示。
第 1 步 – 下载
我们首先下载选择的 Linux 发行版。大多数发行版通常可以在发行版官网以 ISO 格式下载。例如,我们可以在ubuntu.com/download/desktop下载 Ubuntu Desktop。
使用 ISO 镜像,在下一步中我们可以创建 Linux 安装所需的可启动媒体。我们还可以使用 ISO 镜像在虚拟机中安装 Linux,如下一节所示。
第 2 步 – 创建可启动媒体
当我们在 PC 台式机或工作站(裸机)系统上安装 Linux 时,可启动的 Linux 媒体通常是 CD/DVD 或 USB 设备。如果手头有 DVD 可写光驱,我们可以简单地将 Linux 发行版 ISO 刻录到 DVD 上。但由于现代计算机,特别是笔记本电脑,通常不配备任何类型的 CD 或 DVD 驱动器,因此更常见的可启动媒体选择是 USB 驱动器。
重要提示
还有第三种可能性是使用预启动执行环境(PXE)引导服务器。PXE(发音为pixie)是一个客户端-服务器环境,其中一个支持 PXE 的客户端(PC/BIOS)通过局域网或广域网从支持 PXE 的服务器加载并启动软件包。PXE 消除了物理启动设备(CD/DVD、USB)的需求,并减少了安装开销,特别是对于大量客户端和操作系统。深入探讨 PXE 内部机制超出了本章的范围,但我们将在本章末尾简要介绍如何通过 PXE 在 Linux 安装中使用它。学习更多关于 PXE 的好起点是en.wikipedia.org/wiki/Preboot_Execution_Environment。
一个相对简单的方法是通过使用像 UNetbootin (unetbootin.github.io) 或 Balena Etcher (www.balena.io/etcher) 这样的工具来制作一个包含我们所选 Linux 发行版的可启动 USB 驱动器。UNetbootin 和 Etcher 都是跨平台工具,可以在 Windows、Linux 和 macOS 上运行。
在这个示例中,我们将使用 Balena Etcher 在 Windows 上创建可启动的 USB 驱动器:

图 1.2 – 使用 Balena Etcher 创建可启动的 USB 驱动器
以下是使用 Balena Etcher 创建 Ubuntu Desktop 可启动 USB 驱动器的基本步骤。我们假设 Ubuntu Desktop 的 ISO 镜像已经下载,且 Etcher 已安装(在我们的例子中是 Windows 11):
-
选择你所选择的 Linux 发行版的 ISO 文件。
-
选择 USB 目标磁盘。
-
使用你选择的 ISO 镜像刷新之前选定的磁盘。
该过程应当花费几分钟,USB 驱动器就会准备好。现在,让我们看看如何使用这个可启动媒体。
步骤 3 – 在 live 模式下尝试
这一步是可选的。
大多数 Linux 发行版提供了作为 live 媒体下载的 ISO 镜像。我们之所以说“大多数”,是因为并非所有发行版都提供这个选项,至少不是默认提供。然而,提供 live 媒体作为默认选项的有 Ubuntu 和 Fedora。
一旦我们用自己选择的 Linux 发行版创建了可启动媒体,就可以运行该 Linux 平台的 live 环境,而无需实际安装它。换句话说,我们可以在决定是否安装之前评估和测试该 Linux 发行版。live Linux 操作系统被加载到我们电脑的系统内存(RAM)中,而不会使用任何磁盘存储。我们需要确保电脑有足够的 RAM 来容纳我们 Linux 发行版所需的最低内存。
当从可启动媒体启动 PC 时,我们需要确保 BIOS 中的启动顺序已设置为优先读取我们的驱动器。在 Mac 上,我们需要在重启时听到启动提示音后立即按下 Option 键,然后选择我们的 USB 驱动器启动。在 PC 上,确保进入 BIOS 界面并选择正确的启动设备。根据不同的系统,你可能需要在按下 Enter 后按下 F2、F10、F12 或 F1 键,或者通常是 Delete 键。在某些特定情况下,可能会分配其他 功能 键来执行此操作。你需要按下的键通常会在初始启动画面的底部进行说明。
重启后,我们的 Linux 发行版的第一个启动画面应该会给出运行 live 模式的选项,如下图所示为 Ubuntu Desktop (尝试 Ubuntu):

图 1.3 – 选择 Ubuntu Desktop 的 live 模式
接下来,让我们看看使用启动媒体进行 Linux 发行版安装的过程。
步骤 4 – 执行安装
我们通过从 步骤 2 中创建的启动媒体启动 PC,开始安装 Linux 发行版。为了确保系统可以从外部设备启动,有时我们需要在 BIOS 中更改启动顺序,特别是当我们从 USB 驱动器启动时。请按照前述步骤选择正确的启动驱动器。
在接下来的部分,我们展示了使用 Ubuntu 的 ISO 镜像进行安装的过程。我们选择了 Ubuntu 的桌面版和服务器版,并重点介绍了它们之间的主要区别。作为对比,Rocky Linux 和 CentOS Stream 都只有一种版本,本质上是一个服务器平台,并提供可选的图形用户界面。类似地,openSUSE 提供了一个安装介质,适用于桌面和服务器安装。而 Fedora 则提供不同的安装介质,分别适用于桌面和服务器。
我们现在将引导您完成在虚拟机中安装 Linux 的过程。
虚拟机中的 Linux
在 安装 Linux 部分的每个子部分中,我们还将提供如何为相关的 Linux 平台准备虚拟机环境的简要指南。
虚拟机(VM)是物理机器的隔离软件抽象。虚拟机部署在 hypervisor 之上,hypervisor 提供虚拟机的运行时配置和资源管理。以下是几个常用的通用 hypervisor:
-
Oracle VM VirtualBox (
www.virtualbox.org) -
VMware Workstation (
www.vmware.com/products/workstation-pro.html) -
Hyper-V(仅在 Windows Pro、Enterprise 或 Education 版本中提供)
这两个 hypervisors 是跨平台虚拟化应用,支持在 Windows、macOS 和 Linux 上运行,适用于 Intel 和 AMD 处理器架构。后者仅在 Windows Pro 版本、10 和 11 上可用。
重要提示
在撰写本书时,Apple Silicon Macs 的 hypervisor 仅由 VMware Player 和 Parallels 提供。Oracle VirtualBox 对 高级 RISC 机器(ARM)架构仍处于预览阶段。来自 VMware 和 Parallels 的这两款解决方案是 macOS 上的付费软件,因此您需要购买它们才能使用。
在虚拟机上安装 Linux 与在物理机器上安装的区别很小。显著的区别与虚拟机的大小和配置步骤有关,确保满足 Linux 发行版的最低系统要求。因此,在接下来的部分中,我们将在 Windows 下的 VMware Workstation 上安装 Ubuntu。
请注意,在 macOS 下使用 VMware Player 安装 Linux 非常相似,我们在本书的本版中不会重复此过程。macOS 功能在本书的第一版中有所讨论,但鉴于 Apple Silicon 平台上虚拟化程序的可用性有限,我们决定在本版中跳过该部分。关于在裸机 Apple Silicon Mac 上使用 Linux,您可以访问 Asahi Linux,这是一个旨在将功能完整的 Linux 发行版带到 Apple Silicon 计算机上的项目。Asahi Linux 可以在asahilinux.org/找到。
在接下来的部分,我们简要说明 Ubuntu Server LTS 的安装。如果我们计划在虚拟机中安装 Ubuntu,需要进行一些前期的虚拟机环境配置步骤。否则,我们将直接进入安装部分。
使用 VMware Workstation 配置虚拟机
在接下来的步骤中,我们将使用 VMware Workstation 在 Windows 11 上创建一个基于 Ubuntu Server 的虚拟机。撰写时,版本 17 的软件可供免费和商业使用。
-
初始化虚拟化程序后的第一步是点击创建新虚拟机。这将打开一个新窗口,显示新的虚拟机向导,您可以选择要安装的 Linux 发行版的 ISO 镜像。
-
点击浏览,然后从您的硬盘或下载目标中打开镜像文件。
-
点击
UbuntuServer 22.04.1。 -
点击下一步。在随后的窗口中,您需要为虚拟机指定最大磁盘大小。默认情况下,它被设置为 20 GB,这是为 Ubuntu Server 推荐的大小。我们将保持原样。
-
再次点击下一步后,将显示一个包含虚拟机设置的窗口。默认情况下,虚拟化程序为虚拟机提供 2 个 CPU 核心和 4 GB 的内存。您可以点击自定义硬件按钮来更改默认设置,具体取决于您的硬件资源。一般来说,我们建议您的系统至少拥有 16 GB 的内存和 8 核 CPU,以便创建合适大小的虚拟机。当一切设置完成后,点击窗口右下角的关闭按钮。您现在回到主向导窗口。
-
点击完成以完成设置并创建和初始化虚拟机。在下图中,您可以看到新创建的虚拟机,正在 VMware 中运行。

图 1.4 – 在新虚拟机上启动和安装 Linux
安装过程
以下是 Ubuntu Server LTS 的正常安装过程,按照初始启动进入设置模式后进行:
-
初始欢迎屏幕会提示选择您偏好的语言。选择您喜欢的语言并按下键盘上的Enter键。
-
如果有可用的安装程序更新,您可能会被提示进行更新。您可以选择更新安装程序或继续不更新。如果出现提示,我们选择更新安装程序。
-
如果没有可用的更新,接下来的屏幕会提示您选择键盘布局。请根据需要进行选择。在我们的例子中,选择的是英语。选择Done并按Enter键。
-
接下来的提示要求您从以下选项中选择安装基础:Ubuntu Server和Ubuntu Server (minimized)。您还可以选择搜索第三方驱动程序。我们选择Ubuntu Server并选择第三方驱动程序选项。您可以使用Tab键或Arrow键在屏幕上的选项之间移动。要选择一个选项,请按Space键。选择Done并按Enter键。
-
接下来的屏幕将显示网络连接。如果默认设置适合您,按Enter键进入下一个设置界面。
-
系统会询问您关于代理配置的问题。如果您不需要此设置,直接按Enter键进入下一个界面。
-
系统将要求您配置默认的 Ubuntu镜像用于存储库档案。根据您的位置进行编辑,或者保持安装程序提供的默认设置。按Enter键。
-
接下来的屏幕提示您配置存储和分区。我们将使用整个 20 GB 磁盘并采用默认设置,所以选择Done并按Enter键。
-
提供了一个存储配置总结。如果一切符合您的要求,只需按Enter键。
-
会弹出一个警告,询问您是否确认满意当前设置并愿意继续安装。按Enter键。
-
接下来的屏幕会要求您输入个人资料信息,包括您的姓名、服务器名称、用户名和密码。设置好后进入下一个界面。
-
系统会询问您是否要安装 openSSH 服务器。选择安装 openSSH 的选项。如果您有任何想要导入的 SSH 密钥,可以在此提供。完成后,进入下一个界面。
-
系统将提示您选择并安装
docker、microk8s、powershell、nextcloud和livepatch。选择符合需求的组件,然后继续到下一个界面。 -
安装过程开始了。可能需要几分钟时间。耐心等待,直到操作系统安装完成并出现重启选项。
重启后,登录界面会出现,您可以通过 VMware Workstation 在 Windows 11 中使用新的 Ubuntu Server 虚拟机。现在我们已经完成了 Ubuntu Server 的安装。
安装其他发行版的过程与安装 Ubuntu 非常相似。当安装桌面版本时,会提供图形用户界面。在上述示例中,由于我们安装的是专为服务器设计的操作系统,因此没有图形用户界面,仅有一个最小的基于文本的界面。
我们不会逐步指导其他发行版的安装过程,但我们会展示 Rocky Linux 的安装界面:

图 1.5 – Rocky Linux 安装 GUI
到目前为止,我们已经学习了如何进行 Linux 的基本安装。在此过程中,我们为安装媒体创建了一个可启动的 USB 闪存驱动器,这在 Linux PC 平台安装中最常用。我们简要介绍了在 Windows 11 上使用 VMware Workstation hypervisor 进行 VM 特定的 Linux 环境。
在接下来的部分中,我们将学习如何在 Windows 平台上安装和运行 Linux 发行版,而无需使用独立的虚拟化程序,而是使用 Windows 子系统来实现。
使用 Hyper-V 进行虚拟机配置
在接下来的步骤中,我们将基于 Ubuntu Server 创建一个 VM,使用 Windows 11 Pro 上提供的 Microsoft Hyper-V 解决方案。
第一步是激活 Hyper-V hypervisor,因为默认情况下它未激活。为此,我们需要进入Windows 功能,并选中Hyper-V复选框,如下图所示。激活后需要重新启动。

图 1.6 – 在 Windows 11 Pro 上激活 Hyper-V
要创建新的虚拟机,您需要启动Hyper-V 管理器。该应用程序具有三窗格界面。在右侧的操作窗格中,您应该看到新建选项。单击它并选择虚拟机…选项。这将打开一个新窗口,您可以按以下步骤配置新虚拟机:
-
设置名称和位置;我们将其命名为
Ubuntu,并保留默认位置。点击下一步。 -
设置虚拟机的生成。您有两个选项,第一代和第二代。第二个选项适用于基于 UEFI 的 BIOS 和网络安装(PXE)。我们将选择第一代并点击下一步。
-
指定内存量。默认情况下,最小设置为 4GB,并选择了动态内存选项。我们将保留默认设置。点击下一步。
-
通过从下拉菜单中选择适当选项配置网络。您有三个选项:未连接,默认交换机,WSL;我们将选择默认交换机并点击下一步。
-
配置虚拟硬盘,设置大小和位置。点击下一步。
-
在接下来的窗口中,您可以选择立即或稍后安装操作系统。我们将从我们选择的位置选择 Ubuntu Desktop ISO 映像,然后点击下一步。
-
以下窗口显示了 VM 配置的摘要。您可以从此处返回更改任何内容。完成后,点击完成按钮,虚拟机将被创建。
以下截图显示了在 Hyper-V 内运行的新 Ubuntu 虚拟机:

图 1.7 – 使用 Hyper-V 创建新的 Ubuntu 虚拟机
安装过程与上一节中展示的相似,因此我们在这里不再重复。在下一节中,我们将使用另一种虚拟化管理程序,这次来自 Oracle。
使用 Oracle 的 VirtualBox 进行虚拟机配置
Oracle 的 VirtualBox 是一款免费的跨平台软件,支持 Windows、macOS 和 Linux。我们将向您展示如何在 Windows 11 上创建一个 Linux 虚拟机。我们假设您已经安装了 VirtualBox。启动后,您会看到一个用户友好的界面。以下步骤将指导您创建一个新的虚拟机。我们将以 Fedora Workstation 为例:
-
点击新建图标以开始创建新的虚拟机。此操作将打开一个新窗口,您需要在其中提供虚拟机的名称、操作系统类型和 ISO 文件的位置。此新窗口默认处于引导模式。您可以在窗口右下方选择专家模式,这样可以让您在创建过程中有更多的控制权。
-
提供所有需要的信息。在我们的例子中,我们将使用 Fedora,所以我们将它命名为
Fedora。指向 ISO 文件的位置,操作系统类型会自动更改。如果您处于专家模式,会有一些额外的自动隐藏部分,如无人值守安装、硬件和硬盘选项。 -
由于我们正在安装 Fedora,因此无人值守安装部分是灰色的(见图 1.11)。此选项仅被少数操作系统支持(如 Ubuntu、RHEL、Oracle Linux 和 Windows)。
-
在硬件部分,我们将提供虚拟机所需的系统内存和处理器数量。根据您的硬件资源进行选择,但请记住,每个操作系统都有特定的系统要求。在我们的例子中,我们选择 4 GB 的内存和 2 个虚拟 CPU。
-
硬盘部分是您选择虚拟机硬盘空间的地方。再次根据您的资源进行选择,但请记住,满足特定系统要求所需的最小空间是必须的。在我们看来,应该提供至少 20 GB 的硬盘空间。选择虚拟硬盘的位置并点击完成。
-
虚拟机将被创建,窗口会关闭,将您带回到初始的 VirtualBox 窗口。在这里,您将看到关于虚拟机的所有相关信息。要启动虚拟机,只需点击启动按钮(带有大绿色右箭头的那个按钮)。
-
一个新的窗口将会显示虚拟机。
以下截图展示了 VirtualBox 中的虚拟机创建窗口:

图 1.8 – VirtualBox 界面
正如您所见,使用所有三个主要虚拟化管理程序(来自 VMware、Oracle 和 Microsoft)创建 Linux 虚拟机是非常直接且相对简单的。无论使用哪个解决方案,安装 Linux 虚拟机的过程都是相同的。
除了虚拟机配置外,微软 Windows 还提供了一种相对较新的运行 Linux 的方式,那就是通过使用 Windows 子系统 Linux。在下一节中,我们将展示如何操作。
启用 Windows 子系统 Linux
软件开发人员和系统管理员经常面临一个艰难的选择,即为特定的工作或环境需求选择合适的硬件和操作系统平台。过去,Windows 专业人士经常发现一些标准开发工具、框架或服务器组件虽然在 Linux 或 macOS 平台上可用,但在 Windows 上缺乏原生支持。Windows 子系统 Linux(WSL)试图弥补这一差距。
WSL 是一个 Windows 平台特性,提供了本地的 GNU/Linux 运行时环境,并与 Windows 桌面环境兼容,适用于 Windows 10 和 11 版本。WSL 使得可以无缝地在 Windows 内核之上部署和集成选定的 Linux 发行版,而无需专用虚拟化程序。在启用 WSL 后,你可以轻松安装并运行 Linux,像运行本地 Windows 应用一样。
重要提示
如果没有 WSL,我们只能通过使用独立的虚拟化程序,例如 Hyper-V、Oracle VM VirtualBox 或 VMware Workstation,来在 Windows 平台上部署和运行 Linux 发行版。而 WSL 消除了对专用虚拟化程序的需求。撰写本文时,WSL 是一个内嵌虚拟化程序的 Windows 内核扩展。
在本节中,我们提供了启用 WSL 并在 Windows 上运行 Ubuntu 发行版所需的步骤。从 Windows 11 版本 21H2 和 Windows 10 版本 21H2、22H2 开始,WSL 默认可以通过 Windows 商店获取,因此无需使用命令行进行安装和设置。请进入 Microsoft Store,搜索 WSL。从显示的列表中,选择下图所示的应用:

图 1.9 – 来自 Windows 商店的 WSL 应用
安装 WSL 后,你只需要安装任何可用的 Linux 发行版。如果你尝试打开刚安装的 WSL 应用,终端将显示一条消息,提示 WSL 没有安装任何发行版。这意味着你需要返回到 Microsoft Store 搜索并安装一个发行版。例如,如果你在 Microsoft Store 中搜索 Linux,你会看到包括 SUSE Linux Enterprise Server、Oracle Linux、Kali Linux、Ubuntu LTS、Debian 和 openSUSE Leap 等结果。
重要提示
请确保在 Windows 中启用了 Hyper-V,因为它是负责运行 WSL 的服务。要启用它,请进入Windows 功能,从列表中选择Hyper-V,然后点击确定。安装必要的组件后,系统需要重启。Hyper-V 默认在 Windows 11 Pro、Enterprise 和 Education 版本中可用,但在家庭版中不可用。
现在,您可以从 Microsoft Store 安装 Linux 发行版。我们将尝试使用 Ubuntu 进行演示。安装完成后,您可以打开应用程序,在命令行中创建用户,并开始使用它 – 就是这么简单。要打开新的 Linux 发行版,请在搜索栏中输入其名称,按 Enter 键,然后在 Windows 终端应用程序中直接打开了带有 Linux 发行版的新终端窗口,如下屏幕截图所示:

图 1.10 – 使用 WSL 在 Windows 终端窗口中运行的 Ubuntu
此外,您将可以直接从 文件资源管理器 中访问分布的文件系统。以下屏幕截图显示了从 文件资源管理器 中访问的 Ubuntu 文件系统:

图 1.11 – Windows 11 中文件资源管理器中的 Ubuntu 文件系统
WSL 让越来越多的 Windows 专业人士迅速采用 Linux。正如本节所示,WSL 相对容易配置,而且使用 WSL 时,无需专用的虚拟化软件来运行 Linux 实例。
到目前为止,您已经学会了如何使用三种不同的虚拟化软件在 Windows 中的虚拟机上安装 Linux,包括 VMware Workstation、Microsoft 自家的 Hyper-V 以及 Oracle 的 VirtualBox。正如我们之前所述,macOS 上的安装过程非常类似,因此我们没有必要再多花篇幅介绍,因为这只会重复输出。在 macOS 上,VMware Fusion 的界面与 Windows 中使用的界面类似,只有细微的差别。
在裸金属上安装类似;唯一的区别在于您需要物理访问目标机器。正如本章开头所述,还有一种安装 Linux 的方法,那就是通过网络。这是一个更高级的任务,需要更加注意细节以及基本的网络知识。同时,理解 Linux 的启动过程是必需的。
下一节中,我们将提供有关网络安装过程的详细信息。
安装 Linux – 进阶阶段
在本节中,我们将涵盖安装 Linux 的更高级别方面。正如我们在之前的章节中看到的,裸金属和虚拟机的安装都需要直接访问指定的机器。但是,如果我们无法访问该位置怎么办?或者需要设置的机器太多,手动完成任务将会非常繁琐,甚至是不可行的?
在一个有数十台或数百台机器的企业环境中安装 Linux,可以通过网络启动的方式在自动化环境中完成。正如我们之前所说,关于网络启动技术的详细概述超出了本书的范围;然而,我们将描述这一过程并向你展示其中最重要的方面,因为目前没有哪些书籍深入讨论这个话题。
但首先,为了更好地理解网络启动是如何工作的,我们先简要看一下 Linux 启动过程。
Linux 启动过程
Linux 是如何启动的?我们将为你提供一个全面的过程概述,而不涉及过多细节。
当你第一次启动你的 Linux 操作系统的计算机或虚拟机时,BIOS(或启动固件)开始加载并启动引导加载程序。BIOS 有特定的配置,并由制造商加载到主板上的内存芯片中(在物理计算机的情况下,而非虚拟机)。BIOS 包含关于硬件的信息,并具有控制外设(如键盘和显示器)的能力。它还包含操作系统的信息和引导加载程序的位置。这些信息中的一部分是由用户控制的,可以根据用户的需求进行更改,例如引导顺序或密码保护。BIOS 还控制着 网络接口控制器 (NICs) 和所有外部端口,包括 USB 和显示端口。但这就是它能做的一切,因为它需要引导加载程序来进一步启动磁盘上存在的任何操作系统。
BIOS 的更新版是 统一可扩展固件接口 (UEFI)。它具有旧版 BIOS 的优点,但提供了更多互动界面,并更好地支持新操作系统。然而,缺点是缺乏第三方软件供应商的支持。
另外还有 安全启动,这是为了提供额外的操作系统和运行软件的安全性而引入的功能。一些 Linux 发行版支持它,但并非所有发行版都支持。安全启动使用数字签名来证明操作系统的真实性。为了支持安全启动,操作系统开发人员必须为该软件获得有效的证书,并在启动时进行验证,以证明系统是有效的且没有被篡改。
现在我们了解了 BIOS、UEFI 和安全启动,接下来让我们来了解引导加载程序。完成 开机自检 (POST) 后,将访问 引导加载程序 来加载操作系统。POST 是在启动时进行的一系列测试,确保硬件完全正常。什么是引导加载程序?它是硬件和操作系统之间的桥梁。它存储在可启动存储设备的引导扇区中。它可以是一个分区,也可以是存储介质的第一个块。
Linux 使用的引导加载程序是大统一引导加载程序(GRUB)。它负责加载操作系统的内核。内核是 Linux 的核心组件,负责所有软件组件、驱动程序、服务和硬件集成。所有这些组成了我们所说的用户空间。正是 GRUB 具备支持网络启动的能力。
本节提供的信息足以让你掌握 Linux 的启动过程。接下来我们将详细介绍如何使用网络启动来安装 Linux。
PXE 网络启动说明
在本章早些时候我们提到了PXE(发音为pixie)启动选项。PXE 到底是什么呢?它是一项使用不同网络协议进行网络启动的服务。它基于四十年前引入的不同协议和标准,旨在定义迫切需要的网络启动互操作性,也被称为网络引导程序(NBP)。
PXE 所依赖的协议是简单文件传输协议(TFTP)、动态主机配置协议(DHCP)以及使用超文本传输协议(HTTP)的 UDP/IP 协议栈。这三者构成了 PXE 的应用程序接口基础。如今,市面上大多数网络卡已经预装了 PXE 固件,这使得 PXE 成为许多架构上网络启动的标准。有关最新 PXE 版本 2.1 的更多信息,请访问以下链接:web.archive.org/web/20110524083740/http://download.intel.com/design/archives/wfm/downloads/pxespec.pdf。
要使 PXE 工作,我们需要在网络上设置一个 PXE 服务器。这台机器将根据网络上的客户端请求提供必要的启动文件。为此,PXE 服务器至少需要安装一个 DHCP 和一个 TFTP 服务器。此外,必须安装一个网络文件系统(NFS)服务器,因为该协议用于网络文件共享,并且在现代 Linux 操作系统中使用。
在我们进一步详细讨论之前,让我们先了解一下网络启动的工作原理。PXE 依赖于客户端/服务器环境,其中不同的机器配备了支持 PXE 的网卡。PXE 环境的网络配置被开发成不会干扰现有的网络配置。由于需要 DHCP 和 TFTP,PXE 环境确保不会干扰来自本地网络的非 PXE 路由器的现有 DHCP 配置。这是一个为企业环境精心设计的方案。
在一个基本场景中,所有客户端都设置好 PXE 启动(几乎每台计算机的 BIOS 都提供此选项)后,网卡会通过网络发送 DHCP 请求,以便找到本地 PXE 服务器。为了能正确回应这些请求,PXE 使用一种代理 DHCP 方式,将 TFTP 服务器的 IP 和掩码信息返回给启用 PXE 的客户端。这样,它就不会干扰本地网络的 DHCP 服务器。
设置 PXE 服务器超出了本章的范围,但有关它是什么及其工作原理的有用信息仍然相关,可以在ubuntu.com/server/docs/install/netboot-amd64和www.redhat.com/sysadmin/pxe-boot-uefi找到。不过,如何实际设置 DHCP 服务器等更多详细内容,请参阅第十三章。
要使 PXE 服务器正常工作,必须按照所遵循的安装根目录进行一些特定步骤。可以选择多种选项,比如使用 iPXE(开源网络引导固件)、cloud-init(特定于 Ubuntu)或 kickstart(适用于基于 Fedora 的系统)。尽管如此,设置 DHCP、TFTP 和 NFS 服务器是必需的,而 DNS 服务器则是可选的(关于如何设置这些服务器的详细信息请参考第十三章)。
如你所见,后续会介绍这些细节,因此这里不做详细讲解。本章是引言,旨在让你熟悉不同的 Linux 安装方式,在书中的后续章节中,我们将逐步构建这个基础,帮助你准备好迎接更高级的内容。
在接下来的部分中,我们将给出一些根据特定需求使用不同 Linux 发行版的场景。我们将展示我们认为在不同案例研究中适用的发行版和应用程序。请记住,安装应用程序和使用包管理器的相关内容将在第三章中详细讨论。
Linux 发行版 – 实用指南
以下用例灵感来自现实中的问题,主要来源于作者在系统管理和软件工程领域的个人经验。每个场景都呈现了选择合适 Linux 发行版来应对不同任务的挑战。
案例研究 – 开发工作站
本案例研究基于以下从软件开发人员角度出发的场景:
我是一名后端/前端开发人员,主要使用 Java、Node.js、Python 和 Golang 编程,并以 IntelliJ 和 VS Code 作为主要 IDE。我开发环境广泛使用 Docker 容器(包括构建和部署),并且偶尔使用虚拟机(通过 VirtualBox)进行本地代码的部署和测试。我需要一个强大且多功能的开发平台。
在决定选择哪个 Linux 发行版之前,让我们先看看功能性和系统要求:
-
功能性要求:要求是一个相对强大的日常开发平台,可以是 PC/桌面或笔记本电脑。开发者依赖本地资源来部署和测试代码(例如,Docker 容器和虚拟机),如果在外出时,可能会经常处于离线(飞行模式)环境中。
-
系统要求:系统将主要使用 Linux 桌面环境和窗口管理器,集成开发环境(IDE)和终端窗口之间需要频繁切换。IDE、Docker、虚拟机管理器(VirtualBox)和工具所需的软件包应当可以轻松从开源或商业供应商那里获得,理想情况下,始终保持最新,并且需要最小的安装和定制工作。
选择 Linux 发行版
这里选择的 Linux 发行版是 Ubuntu Desktop 长期支持版(LTS)平台。Ubuntu LTS 相对稳定,几乎可以在任何硬件平台上运行,并且大部分硬件驱动程序都保持最新。所需应用和工具的软件包通常可用且稳定,并且会频繁更新。Ubuntu LTS 是一个企业级、经济高效且安全的操作系统,适合组织和家庭用户。
除了 Ubuntu,Fedora 和 openSUSE 同样适合用作开发者工作站。选择它们取决于你需要基于 Debian 还是 Red Hat/SUSE 的生态系统,以及是否需要更新的包。
案例研究 – 安全的 Web 服务器
该案例研究基于以下场景,从 DevOps 工程师的角度出发:
我需要一个稳健的平台,运行一个安全、相对轻量且企业级的 Web 服务器。该 Web 服务器处理 HTTP/SSL 请求,在将请求路由到其他后台 Web 服务器、网站和 API 端点之前先卸载 SSL。无需负载均衡功能。
让我们看看该案例研究中的 功能性要求。当涉及到开源、安全且企业级的 Web 服务器时,常见的选择通常是 NGINX、Apache HTTP Server、Node.js、Apache Tomcat 和 lighttpd。在不深入选择某一 Web 服务器的细节之前,我们假设选择了 Apache HTTP Server。它拥有先进的 SSL/TLS 支持,卓越的性能,并且配置相对简单。
我们可以在 VPS 环境、本地(本地数据中心)或公共云中部署该 Web 服务器。部署形式为虚拟机或 Docker 容器。我们需要一个相对低占用空间、企业级的 Linux 平台。
选择 Linux 发行版
我们选择的 Linux 发行版是Rocky Linux 或 AlmaLinux。通常,这两个发行版与 Apache HTTP Server 非常匹配。它们相对轻量,只有最基本的服务器组件和操作系统网络堆栈。Rocky 和 Alma 都可以广泛作为 VPS 部署模板,从私人和公共云提供商处获得。我们的 Apache HTTP Server 可以作为 Docker 容器运行在 Rocky Linux 或 AlmaLinux 上,因为我们可能需要水平扩展到多个 Web 服务器实例。有关设置 Web 服务器的更多细节,请参见 第十三章。
用例 – 个人博客
本案例研究基于以下场景,从软件工程师和博客作者的角度出发:
我想创建一个软件工程博客。我将使用 Ghost 博客平台,运行在 Node.js 上,MySQL 作为后台数据库。我正在寻找由主要云服务提供商提供的托管虚拟专用服务器(VPS)解决方案。我将自行安装、维护和管理相关平台。我应该使用哪个 Linux 发行版?
让我们讨论一下这个用例的功能需求。我们需要一个自主管理的公开托管虚拟专用服务器(VPS)解决方案。相关的托管成本是一个敏感问题。此外,所需软件包的维护应该相对简单。我们预计会有频繁的更新,包括 Linux 平台本身。
选择 Linux 发行版
我们在这种情况下选择的 Linux 发行版要么是Debian Stable,要么是Ubuntu Server LTS。如前所述,Ubuntu 是一个强大、安全且适合企业级使用的 Linux 发行版。Debian 同样稳定,并且为应用程序提供了不错的选择。平台的维护和管理工作并不繁重。所需的软件包——Node.js、Ghost 和 MySQL——都可以轻松获取,并且得到了良好的维护。Ubuntu Server 的占用空间相对较小。我们可以在 Ubuntu 系统的要求范围内轻松运行所需的软件堆栈用于博客,因此托管成本是合理的。
用例 – 媒体服务器
本案例研究基于以下场景,从家庭影院爱好者的角度出发:
我有一个中等大小的电影(个人 DVD/Blu-ray 备份)、视频、照片和其他媒体的收藏,存储在网络附加存储(NAS)上。NAS 有自己的媒体服务器,但流媒体性能相当差。我使用 Plex 作为媒体播放器系统,Plex Media Server 作为后台。那么,我应该使用哪个 Linux 平台?
根据这个描述,让我们来确定该用例的系统要求。一个媒体服务器的关键系统要求包括速度(以确保高质量、流畅的流媒体体验)、安全性和稳定性。相关的软件包和流媒体编解码器需要频繁更新,因此平台维护任务和升级相当频繁。该平台托管在本地 PC 桌面系统上,通常具备充足的内存和计算能力。媒体内容通过局域网(LAN)从 NAS 进行流式传输,内容通过 NFS 共享提供。
选择 Linux 发行版
Debian 和 Ubuntu 都是优秀的媒体服务器平台选择。Debian 的稳定版本被 Linux 社区认为是非常可靠且坚如磐石,尽管它有些过时。两者都具备先进的网络和安全功能,但选择这两者的一个决定性因素是 Plex Media Server 为 Debian 提供了 ARM 兼容的包。Ubuntu 的媒体服务器包仅适用于 Intel/AMD 平台。如果我们拥有一个小型 ARM 处理器的设备,那么 Debian 将是正确的选择。否则,Ubuntu LTS 也能很好地满足我们的需求。
现在你已经了解了不同的使用场景,是时候选择适合你的 Linux 发行版并开始体验了。在本章中,我们为你提供了大量信息,这些信息将在你开始 Linux 之旅时变得非常宝贵。
总结
在本章中,我们学习了 Linux 发行版,并重点讲解了如何根据需求选择合适的平台,并执行相关的安装步骤。
本章的重点是 Ubuntu 发行版。为了实践,我们介绍了运行 Linux 的虚拟机环境。我们还简单涉及了 Windows 领域,探讨了 WSL,这是 Linux 作为本地 Windows 应用程序的现代抽象。
通过本章学到的技能,我们希望你能更好地理解如何根据需求选择不同版本的 Linux 发行版。你已经学会了如何在多种平台上安装和配置 Linux。你将在本书接下来的章节中使用这些技能,但最重要的是,你现在能够快速部署并测试自己选择的 Linux 发行版。
从下一章开始,我们将更详细地探讨各种 Linux 子系统、组件、服务和应用程序。第二章,Linux Shell 和 文件系统,将使你熟悉 Linux 文件系统的内部结构和相关工具。
问题
这里有几个问题和思维实验,供你思考,部分基于本章所学的技能,其他则在书籍后面的部分揭示:
- 如果我们同时部署并运行了大量的 Linux 虚拟机实例或发行版,我们该如何更容易地管理它们?
提示:使用Vagrant,一个用于构建和管理虚拟机环境的工具。
- 我们可以在 WSL 中运行多个 Linux 实例吗?
提示:我们可以。
进一步阅读
以下是一些可以帮助你完成 Linux 安装任务的 Packt 出版书籍:
-
Linux 基础,作者:Oliver Pelz
-
精通 Ubuntu 服务器 – 第四版,作者:Jay LaCroix
-
精通 Linux 管理 – 第一版,作者:Alexandru Calcatinge 和 Julian Balog
第二章:Linux shell 和文件系统
理解 Linux 文件系统、文件管理 基础知识,以及 Linux shell 和 命令行界面 (CLI) 的基本知识,对于现代 Linux 专业人员来说至关重要。
在本章中,您将学习如何使用 Linux shell 和一些 Linux 中最常用的命令。您将了解一个基本的 Linux 命令的结构,以及 Linux 文件系统的组织方式。我们将探索处理文件和目录的各种命令。过程中,我们还将介绍最常用的命令行文本编辑器。希望通过本章的学习,您能够熟练使用 Linux CLI,为以后更深入的学习做好准备。本章将为使用 Linux shell 打下基础,关于 shell 的更多信息,请访问 第八章,Linux Shell 脚本编写。
我们将涵盖以下主要主题:
-
介绍 Linux shell
-
Linux 文件系统
-
操作文件和目录
-
使用文本编辑器创建和编辑文件
技术要求
本章需要在标准 Linux 发行版的工作环境中进行安装,支持服务器、桌面、PC 或 虚拟机 (VM) 平台。我们的示例和案例研究使用 Ubuntu 和 Fedora 平台,但所探讨的命令和示例同样适用于任何其他 Linux 发行版。
介绍 Linux shell
Linux 起源于 Unix 操作系统,其中一个主要优势是命令行界面。以前,这被称为 shell。在 sh 命令中,shell 是一个程序,它有两个流:输入流 和 输出流。输入是用户输入的命令,而输出是该命令的结果或其解释。换句话说,shell 是用户与机器之间的主要接口。
主要的 Linux 发行版中的默认 shell 称为 Bash,它是 Bourne Again Shell 的缩写,命名来源于 UNIX 中原始 shell 的创建者 Steve Bourne。除了 Bash,Linux 中还提供了其他几种 shell,如 ksh、tcsh 和 zsh。在本章及全书中,我们将主要讨论 Bash shell,因为它是现代 Linux 发行版中最广泛使用的 shell。
重要提示
Debian、Ubuntu、Fedora、CentOS Stream、RHEL、openSUSE、SLE 和 Linux Mint 等发行版默认使用 Bash Shell。其他一些发行版,如 Kali Linux,已默认切换到 zsh。Manjaro 在某些版本中提供 zsh。对于使用 macOS 的用户,你应该知道 zsh 已成为默认 Shell 已有几年了。不过,你可以在 Linux 上安装任何你想要的 Shell,并将其设置为默认 Shell。一般来说,Shell 功能非常相似,因为它们做的是相同的事情,但在可用性和功能上有所不同。如果你对某个特定的 Shell 感兴趣,可以随意使用它,并测试与其他 Shell 的差异。
每个用户都可以分配一个 Shell。系统中的不同用户可以使用不同的 Shell。检查默认 Shell 的一种方法是访问 /etc/passwd 文件。关于这个文件和用户账户的更多细节将在 第四章中讨论,管理用户和组。现在,重要的是知道如何查找默认的 Shell。在此文件中,每行的最后字符表示该用户的默认 Shell。/etc/passwd 文件列出了每个用户的信息,包括 进程标识号 (PID)、组标识号 (GID)、用户名、主目录和基本 Shell。
要查看每个用户的默认 Shell,请使用你用户的用户名执行以下命令(在我们的例子中是 packt):
cat /etc/passwd | grep packt
输出应该是 /etc/passwd 文件内容的列表。根据你系统中用户的数量,你将看到所有用户的信息,每个用户占一行。查看 当前 Shell 的一个更简单的方法是运行以下命令:
echo $0
这展示了究竟是什么在运行你的命令,在命令行界面(CLI)的情况下,就是 Shell。$0 部分是 Ubuntu 和 Debian 上的 echo $0 命令:

图 2.1 – 查看正在运行的 Shell 的命令
如你所见,运行 echo $0 命令给出的输出不同,但传递的信息是一样的:正在运行的 Shell 是 Bash。如果你有其他喜欢的 Shell,你可以很容易地为你的用户分配另一个 Shell,前提是你已经安装了它。不过,如果你了解 Bash,那么你将能够轻松使用其他所有可用的 Shell。
重要说明
Linux Shell 是区分大小写的。这意味着你在命令行中输入的每个命令都必须遵循这一规则。例如,之前使用的 cat 命令是小写的。如果你输入 Cat 或 CAT,Shell 将无法识别它是一个有效的命令。这个规则同样适用于文件路径。你会注意到,默认的目录在你的主目录中使用大写字母作为首字母,例如 ~/Documents、~/Downloads 等等。这些名称与 ~/documents 或 ~/downloads 是不同的。
在本章中,您将学习如何使用 Linux 命令和终端,同时了解其文件系统。关于软件管理,您将在第三章中学习,因此现在讲解如何安装新终端会让我们提前讨论。我们希望您能够逐步稳步地建立 Linux 知识,因此我们将在下一章向您展示如何安装新终端。现在,Bash 已经足够了,我们将在全书中使用它。
如果您想查看系统上已安装的所有终端,可以运行以下命令:
cat /etc/shells
在我们的案例中,Ubuntu Server 22.04.2 LTS 中所有已安装的终端(默认设置)如下图所示:

图 2.2 – Ubuntu 默认提供的终端
这将显示所有已安装的终端。您可以使用这些终端中的任何一个,或者像我们将在第三章中所示的那样安装新的终端。此外,在第四章中,当我们处理用户帐户时,您将学习如何更改用户的终端。
下一节将介绍终端连接类型。
建立终端连接
我们可以通过两种不同的方式连接到终端:tty和pts。tty代表电传打字机,它是计算机早期使用的一种终端类型。这种连接被视为本地连接,其端口是与计算机直接连接的。用户与计算机之间的链接通常通过键盘来实现,键盘被认为是本地终端设备。
pts连接是通过 SSH 或 Telnet 类型的连接生成的。它的名称代表ssh或xterm,是pty的从属设备。
在下一节中,我们将进一步探讨 Linux 中可用的虚拟终端连接。
虚拟控制台/终端
终端被认为是一个管理输入字符串(即命令)与其他 I/O 设备(如键盘和屏幕)之间的设备。还有伪终端,它是模拟终端,行为与经典终端相同。不同之处在于,它不会直接与设备交互,因为这一切都由 Linux 内核模拟,并将 I/O 传输给一个叫做 shell 的程序。
tty1、tty2、tty3、tty4、tty5和tty6,分别在您的计算机上。
我们将以 Ubuntu 22.04.2 LTS Server 虚拟机安装为例进行解释,但在 Rocky Linux 中也是相同的。启动虚拟机后,提示输入用户名和密码时,屏幕上的第一行将显示类似以下内容:
Ubuntu 22.04.2 LTS neptune tty1
如果你按下任何前述的键盘组合,你将看到终端从 tty1 切换到其他 tty 实例。例如,如果你按下 Ctrl + Alt + F6,你将看到以下内容:
Ubuntu 22.04.2 LTS neptune tty6
因为我们使用的是 Ubuntu 的服务器版本,所以没有安装图形界面。但是,如果你使用的是桌面版,你将能够使用 Ctrl + Alt + F7 进入 X 图形 模式。例如,neptune 字符串是我们为虚拟机起的名字。
如果你无法使用前述的键盘组合,可以使用专门的命令来更改虚拟终端。这个命令叫做 chvt,其语法是 chvt N。虽然我们还没有讨论 shell 命令,但我们会给你展示如何使用它们以及其他相关命令的示例。此操作只能由管理员账户执行,或者通过 sudo 使用。简而言之,sudo 代表 superuser do,它允许任何用户以管理员权限或其他用户的权限运行程序(有关更多细节,请参阅 第四章,管理用户 和组)。
在接下来的示例中,我们将使用 Ubuntu 向你展示如何更改虚拟终端。首先,我们将查看当前使用的是哪个虚拟终端,然后在不使用 Ctrl + Alt + Fn 键的情况下切换到另一个虚拟终端。
who 命令将显示当前登录计算机的用户信息。在我们的例子中,由于我们是通过 SSH 连接到虚拟机,它将显示用户 packt 当前正在使用伪终端零(pts/0):
packt pts/0 2023-02-28 10:45 (192.168.122.1)
如果我们直接在虚拟机的控制台中运行相同的命令,输出将是以下内容:
packt pts/0 2023-02-28 10:45 (192.168.122.1)
packt tty1 2023-02-28 10:50
它显示用户同时连接到虚拟终端 1(tty1)和通过 SSH 从宿主操作系统连接到虚拟机(pts/0)。
现在,通过使用 chvt 命令,我们将向你展示如何切换到第六个虚拟终端。运行 sudo chvt 6 后,你将被提示输入密码,并立即切换到第六个虚拟终端。再次运行 who 将显示所有已登录的用户及其使用的虚拟终端。在我们的例子中将是 pts/0、tty2 和 tty6。请注意,你的输出可能不同,因为虚拟终端的编号不同。
现在我们知道了有哪些类型的 shell 连接,接下来我们将在下一节学习 shell 提示符的相关内容。
命令行提示符
命令行提示符 或 shell 提示符 是你输入命令的地方。通常,命令提示符会显示用户名、主机名、当前工作目录和一个表示正在运行 shell 的用户类型的符号。
这是来自 Ubuntu 22.04.2 LTS 服务器版的示例(与 Debian 类似):
packt@saturn:~$
这是来自 Fedora 37 服务器的示例(与 Rocky Linux、RHEL 或 AlmaLinux 类似):
[packt@localhost ~]$
这是对提示符的简短解释:
-
packt是当前登录用户的用户名 -
saturn和localhost是主机名 -
~代表家目录(它被称为波浪符) -
$表示用户是一个普通用户(当您以管理员身份登录时,符号会变成井号#)
此外,当使用 openSUSE 时,您会注意到提示符与 Ubuntu/Debian 和 Fedora/RHEL 的提示符不同。以下是运行 Leap 15.4 服务器版时的提示符示例:
packt@localhost:~>
如您所见,没有美元符号 ($) 或井号 (#),只有大于符号 (>)。刚开始可能会有些困惑,但当您使用 root 用户时,符号最终会变成井号 (#)。以下是一个示例:
localhost:/home/packt #
接下来,让我们看看 shell 命令类型。
Shell 命令类型
Shell 使用 type 命令。例如,您可以检查 cd(更改目录)是什么类型的命令:
packt@neptune:~$ type cd
cd is a shell builtin
输出显示 cd 命令是一个内部命令,它内置在 shell 中。如果你感兴趣,你可以通过在命令名前加上 type 来了解我们将在接下来的章节中展示的其他命令类型。接下来我们可以通过以下图像查看更多示例:

图 2.3 – Linux 中不同类型的命令
现在您知道一些 Linux 命令的类型,让我们分析命令的结构并了解其组成部分。
解释命令结构
我们已经使用过一些命令,但我们没有解释 Linux 命令的结构。现在我们将为您讲解,以便您能够理解如何使用这些命令。简而言之,Unix 和 Linux 命令有以下形式:
-
命令的名称
-
命令的选项
-
命令的参数
在 shell 中,您将看到类似以下的结构:
command [-option(s)] [argument(s)]
一个合适的例子是使用 ls 命令(ls 来自 list)。这个命令是 Linux 中最常用的命令之一,它用于列出文件和目录,并且可以与选项和参数一起使用。
我们可以使用最简单的 ls 命令,既不加选项也不加参数。它会列出当前工作目录(pwd)的内容。在我们的例子中,它是家目录,在 shell 提示符中由 ~ 波浪符表示(见 图 2.10)。
带有 -l 选项(小写 L)的 ls 命令使用长格式列出,提供关于当前工作目录(pwd)中文件和目录的更多信息:

图 2.4 – 使用 ls 命令结合选项和属性
在前面的示例中,我们使用了 ls -l ~/Documents/ 来显示 ~/Documents 目录的内容。这里展示了一种使用命令结合选项和属性的方式,而不需要将当前工作目录切换到 ~/Documents。
在接下来的部分中,我们将向你展示如何使用 Linux 中默认提供的手册页面。
查阅手册
每位 Linux 系统管理员的好伙伴就是手册。每个 Linux 命令都有一个手册页面,向用户提供有关其使用、选项和属性的详细信息。如果你知道想要了解的命令,直接使用 man 命令进行查阅。例如,要查看 ls 命令的手册,你可以使用 man ls。
手册将命令信息组织成不同的章节,每个章节都按约定命名,在所有发行版中都是一样的。简要地说,这些章节包括 name、synopsis、configuration、description、options、exit status、return value、errors、environment、files、versions、conforming to、notes、bugs、example、authors、copyright 和 see also。
类似于手册页面,几乎所有的 Linux 命令都有一个 -help 选项。你可以利用这个选项来进行快速查阅。
要了解更多关于 help 和 man 页面的信息,你可以查阅每个命令的帮助或手册页面。尝试以下命令:
$ man man
$ help help
当你使用手册时,请记住它并不是一本逐步操作的指南。它是技术文档,刚开始时可能会让人感到困惑。我们的建议是尽可能多地使用 man 页面。在你去互联网搜索任何内容之前,先试着阅读手册。这将是一个很好的练习,并且你将很快熟练掌握 Linux 命令。
把手册当作你的朋友,类似于你在高中或大学时使用的教科书。它将在你最需要的时候提供第一手信息。如果你考虑到在某些情况下无法访问互联网,也无法使用搜索引擎,内建的手册将是你最好的伙伴。学会利用它的功能,充分发挥它的优势。
在接下来的部分中,你将了解 Linux 文件系统。
Linux 文件系统
/ (root) 文件系统和另一个 /home 文件系统。或者,可能只有一个文件系统包含所有的文件系统。
通常来说,按每个分区使用一个文件系统被认为是良好的做法,它有助于逻辑上的维护和管理。由于 Linux 中的一切都是文件,硬盘、DVD 驱动器、USB 设备和软盘驱动器等物理设备也被视作文件。在本节中,你将了解目录结构,如何操作文件,以及一些非常有用的命令行编辑技巧。
目录结构
Linux 在文件系统的基础处使用`/`)。从此位置开始,所有的分支(目录)都向文件系统扩展。
文件系统层次标准(FHS)定义了类似 Unix 系统文件系统的结构。然而,Linux 文件系统还包含一些标准未定义的目录。
从命令行探索 Linux 文件系统
可以通过使用tree命令自由地探索文件系统。在 Fedora Linux 中,tree已预安装,但如果你使用 Ubuntu,需要使用以下命令进行安装:
$ sudo apt install tree
不要害怕探索文件系统,因为单纯浏览是不会造成任何损害的。你可以使用ls命令列出目录的内容,但tree提供了不同的图形化显示。以下图像展示了两者输出的区别:

图 2.5 – 比较ls -la命令和tree -a命令的输出
tree命令有不同的选项,你可以通过手册了解它们。我们将使用tree命令并调用-L选项,这告诉命令应该向下查看多少级目录,最后的属性则指定从哪个目录开始。在我们的示例中,命令将从root目录开始,向下查看一级,该目录由正斜杠作为参数表示(见图 2.12):
$ tree -L 1 /
通过使用tree命令开始探索目录结构,如下所示:

图 2.6 – 在 Ubuntu 上使用带选项和参数的tree命令
重要提示
请记住,某些你即将打开的目录中将包含大量文件和/或其他目录,这将使你的终端窗口变得杂乱无章。
以下是几乎所有版本的 Linux 上都存在的目录。以下是 Linux 根文件系统的快速概览:
-
/:根目录。所有其他目录的根目录。 -
/bin:必要的命令二进制文件。存储二进制程序的位置。 -
/boot:引导加载程序的静态文件。存储内核、引导加载程序和initramfs的位置。 -
/dev:设备文件。指向设备设备节点,是内核设备列表。 -
/etc:特定主机的系统配置。系统的必要配置文件、启动时加载的脚本、crontab、fstab设备存储表、passwd用户帐户文件。 -
/home:用户的主目录。存储用户文件的位置。 -
/lib:必要的共享库和内核模块。共享库类似于 Windows 中的动态链接库(DLL)文件。 -
/media:可移动媒体的挂载点。用于外部设备和 USB 外部媒体。 -
/mnt:用于临时挂载文件系统的挂载点。用于旧版系统。 -
/opt:附加应用程序软件包。安装可选软件的位置。 -
/proc:由内核管理的虚拟文件系统。一个包含系统所需文件的特殊目录结构。 -
/sbin:必要的系统二进制文件。系统运行所需的重要程序。 -
/srv:本系统提供的服务的数据。 -
/tmp:临时文件。 -
/usr:次级层级。Linux 中最大的目录,包含普通系统用户所需的支持文件:-
/usr/bin– 系统可执行文件 -
/usr/lib– 来自/usr/bin的共享库 -
/usr/local– 源代码编译的程序,不包括在发行版中 -
/usr/sbin– 特定的系统管理程序 -
/usr/share– 程序在/usr/bin中共享的数据,例如配置文件、图标、壁纸或音频文件 -
/usr/share/doc– 系统范围文件的文档
-
-
/var:可变数据。只有用户可修改的数据存储在这里,例如数据库、打印缓存文件、用户邮件等;/var/log– 包含记录系统活动的日志文件
接下来,我们将学习如何操作这些文件和目录。
操作文件和目录
记住,在 Linux 中一切都是文件。目录也是文件。因此,了解如何操作它们是至关重要的。在 Linux 中操作文件意味着使用多个命令来执行基本的文件和目录操作,如文件查看、文件创建、文件定位、文件属性和链接。一些命令虽然在此不会详细介绍,但与文件密切相关,接下来的章节将涵盖它们。
理解文件路径
FHS 中的每个文件都有一个路径。路径是文件位置的易读表示。在 Linux 中,所有文件都存储在根目录下,使用 FHS 作为标准来组织它们。系统内文件和目录之间的关系通过正斜杠字符(/)表示。在计算机历史上,正斜杠被用作描述地址的符号。路径实际上是文件的地址。
Linux 中有两种路径类型,相对路径和绝对路径。绝对路径总是以根目录开始,并沿着系统的分支直到所需的文件。相对路径总是指向当前工作目录(pwd),并表示到该目录的相对路径。因此,相对路径总是相对于当前工作目录的路径。
例如,我们从主目录引用一个现有的文件,名为 poem。如果我们在主目录内,并且 pwd 命令显示为 packt 的主目录,那么名为 poem 的文件的绝对路径将如下所示:
/home/packt/poem
如果我们想使用 cat 命令查看该文件的内容,例如,我们可以使用带有绝对路径的命令:
cat /home/packt/poem
同一个文件的相对路径是相对于 pwd 的,因此在我们的例子中,如果我们已经在主目录内,使用 cat 命令将是这样的:
cat poem
绝对路径在操作文件时非常有用。经过一段时间的实践,你将会学会如何访问最常用的文件路径。例如,你需要学习的一个文件路径是passwd文件,它位于/etc目录中。因此,当你引用它时,你将使用它的绝对路径/etc/passwd。如果使用相对路径来引用该文件,则意味着你要么已经在其父目录中,要么在 FHS 中某个靠近的地方。
使用相对路径涉及到了解两个用于操作 FHS(文件层次结构)的特殊字符。第一个特殊字符是点(.),它表示当前目录。另一个是两个连续的点(..),它表示当前目录的父目录。在使用相对路径时,确保始终检查你所在的目录。使用pwd命令可以显示你当前的工作目录。
使用相对路径的一个好例子是,当你已经在父目录中,并且需要引用它时。如果你需要查看系统中的账户列表,而这些信息存储在passwd文件中,你可以通过相对路径引用它。在这个例子中,我们处在我们的主目录中:

图 2.7 – 使用文件的相对路径
在这里,我们首先通过pwd命令检查当前的工作目录,输出将是我们的主目录路径,即/home/packt。接着,我们尝试从主目录直接显示passwd文件的内容,使用的是cat命令,但输出将是一个错误信息,表示在主目录中没有这样的文件或目录。我们使用了相对路径,而相对路径始终是相对于当前的工作目录,因此出现了错误。接下来,我们使用了两个连续点的特殊字符来通过相对路径引用该文件。在这种情况下,命令是cat ../../etc/passwd。
提示
始终使用键盘上的Tab键进行自动补全,并检查你输入的路径是否正确。在前面的例子中,我们输入了../../etc并按下了Tab,它自动补全为一个斜杠。然后,我们输入了文件名的前两个字母并再次按下Tab。这时,它展示了/etc目录中所有以pa开头的文件列表。看到passwd文件在其中后,我们确认路径正确,于是再输入两个s字符并按下Tab。这时,命令就自动完成了,我们按下Enter/Return键来执行命令。
最终命令中的路径是相对于我们的主目录的,它的翻译如下:将文件与名为 passwd 的文件连接,该文件位于 /etc 目录中,该目录位于我们当前目录(主目录)的父目录(第一个两个点)和父目录的父目录(第二个两个点)中。因此,/etc/passwd 的绝对路径被转换为相对于我们主目录的相对路径,如下所示:../../etc/passwd。
接下来,我们将学习 Linux 中的基本文件操作。
基本文件操作
作为系统管理员,你每天都会操作文件。这包括创建、复制、移动、列出、删除、链接等操作。这些操作的基本命令在本章中已经讨论过,现在是时候深入了解它们的使用、选项和属性了。接下来的章节将详细介绍一些更高级的命令。
创建文件
有时候你需要使用 touch 命令。当你使用它时,它会创建一个新的文件,文件所有者是你,且文件大小为零,因为它是一个空文件。
在下面的示例中,我们将在 ~/``packt/ 目录下创建一个名为 new-report 的新文件:

图 2.8 – 使用 touch 命令创建和修改文件
touch 命令还用于更改文件的修改时间,而不更改文件本身。请注意,当我们首次创建 new-report 文件时的初始时间与使用 touch 命令之后的时间之间的差异。你还可以通过使用 touch 命令的 -a 选项来更改访问时间。默认情况下,ls 命令的长格式列表仅显示修改/创建时间。如果你想查看访问时间,可以使用 - time 选项中的 atime 参数。请参见下面的示例:

图 2.9 – 使用 touch 命令修改访问时间
修改、创建和访问时间戳非常有用,特别是在使用 find 等命令时。它们为你提供了更 细致 的搜索模式。我们将在未来的章节中通过更多示例回到这个命令。
也可以通过使用重定向和 echo 命令来创建文件。echo 是一个将作为参数传递的字符串打印到标准输出(屏幕)的命令。echo 命令的输出可以通过使用输出重定向直接写入文件:

图 2.10 – 使用 echo 命令与输出重定向
在前面的示例中,我们将 echo 命令的文本重定向到演示文件中。该文件最初并不存在,因此由命令自动创建。第一个 echo 命令使用 > 操作符向文件中添加了一行内容。第二个 echo 命令使用 >> 操作符将新的一行文本追加到文件末尾。
列出文件
我们之前已经用过一些 ls 命令的示例,所以你对它有一定的了解。我们将 -l 选项作为命令结构的示例进行讲解,因此这里不再赘述。我们将探讨一些新的选项,这些选项对于这个基本而有用的命令非常重要:
-
ls -lh:-l选项以扩展格式列出文件,而-h选项以人类可读的格式显示文件大小,单位为千字节或兆字节,而不是字节。 -
ls -la:-a选项显示所有文件,包括隐藏文件。与-l选项结合使用时,输出将是一个包含所有文件及其详细信息的列表。 -
ls -ltr:-t选项按修改时间排序文件,最新的文件排在最前面。-r选项则是将排序顺序反转。 -
ls –lS:-S选项按文件大小排序,最大的文件排在最前面。 -
ls -R:-R选项以递归模式显示当前目录或指定目录的内容。
一个常用的列出文件和目录的方法是 ls -la 命令。让我们在这里详细看看它,尽管我们将在第四章《用户与组管理》中详细讨论这个问题。
我们在家目录中使用长列表的一种示例可以在图 2.11中看到,当我们将 ls -la 的输出与 tree 命令的输出进行比较时。以下是一个简短的示例:
total 48
drwxr-x--- 5 packt packt 4096 Mar 2 14:44 .
drwxr-xr-x 3 root root 4096 Feb 27 08:58 ..
-rw-rw-r-- 1 packt packt 55 Mar 2 14:46 art-file
-rw------- 1 packt packt 1039 Mar 1 18:56 .bash_history
在输出中,命令后的第一行显示了目录中块的数量。之后,每一行代表一个文件或子目录,并显示以下详细信息:
-
第一个字符表示文件类型:
d代表目录,:代表文件,l代表链接,c代表字符设备,b代表块设备。 -
接下来的九个字符表示文件的权限(详细内容见第四章《用户与组管理》)。
-
该文件的硬链接(请参阅本章中的处理链接小节)。
-
文件所有者的 PID 和 GID(详细内容见第四章《用户与组管理》)。
-
文件的大小(该数字取决于是否采用人类可读格式)。
-
文件的最后修改时间。
-
文件或目录的名称
前两行分别是对自身(点)和父目录(第二行中的两个点)的引用。
下一部分将教你如何复制和移动文件。
复制和移动文件
在 Linux 中,cp 命令用于复制文件。mv 命令用于在文件系统中移动文件。这个命令也可以用来重命名文件。
要复制文件,你可以使用最简单的 cp 命令:
cp source_file_path destination_file_path
这里,source_file_path 是要复制的文件名,destination_file_path 是目标文件名。你也可以复制一个已存在目录中的多个文件。如果目标目录不存在,shell 会提示你目标不是一个目录。
现在让我们来看看这些命令的一些变体:
cp -a:-a选项以递归模式复制整个目录层次结构,同时保留所有的属性和链接。在以下示例中,我们使用-a选项将之前在主目录中创建的dir1目录完整复制到新创建的backup_dir1目录中:

图 2.11 – 使用带有 -a 选项的复制命令
-
cp -r:这个选项类似于-a,但它不保留文件属性,仅保留符号链接。 -
cp -p:-p选项保留文件的权限和时间戳。否则,使用最简单形式的cp命令进行复制时,文件副本将归当前用户所有,并且时间戳为你进行复制操作时的时间。 -
cp -R:-R选项允许你递归复制一个目录。在以下示例中,我们将使用ls命令显示~/packt/目录的内容,然后使用cp -R命令将/files目录的内容复制到/new-files目录。/new-files目录之前并不存在。cp -R命令创建了它:

图 2.12 – 使用 cp -R 命令
移动文件是通过 mv 命令完成的。它用于将文件和目录从一个位置移动到另一个位置,或者用于重命名文件。以下是一个示例,我们使用 mv 命令将 files1 重命名为 old-files1:mv files1 old-files1。
还有许多其他选项,你可以通过查阅手册页面来了解它们。随时可以探索它们,并将它们应用到你的日常任务中。
处理链接
链接在 Linux 中是一个非常有用的选项。它们可以用来保护原始文件,或者作为一种工具,避免需要多个文件的独立副本。可以将它视为为同一文件创建不同名称的工具。
ln 命令可用于创建两种类型的链接:
-
符号链接
-
硬链接
这两种链接是不同类型的文件,指向原始文件。符号链接是一个指向原始文件的物理文件;它们是链接的并且具有相同的内容。此外,符号链接可以跨越不同的文件系统和物理介质,这意味着它可以链接到存储在其他驱动器或分区上的原始文件,且文件系统类型不同。使用的命令如下:
ln -s [original_filename] [link_filename]
下面是一个示例,我们列出了~/packt目录的内容,然后使用ln -s命令为new-report文件创建了一个符号链接,并再次列出了目录内容:

图 2.13 – 使用符号链接
你可以看到,创建的链接名为new-report-link,并且通过->箭头的视觉表示,指向了原始文件。你还可以看出两个文件之间的大小差异,链接和原始文件的权限也不同。这表明它们是两个不同的物理文件。为了进一步确认它们是不同的物理文件,你可以使用ls -i命令查看new-report和new-report-link的 inode 不同:

图 2.14 – 比较符号链接和原始文件的 inode
如果你想知道链接指向哪里,而又不想使用ls -l,可以使用readlink命令。它在 Ubuntu 和 CentOS 中都可用。该命令的输出将只是符号链接所指向的文件名。它只在符号链接的情况下有效:
packt@neptune:~$ readlink new-report-link
new-report
在前面的示例中,你可以看到输出显示new-report-link文件是指向名为new-report的文件的符号链接。
相比之下,ln命令如果没有任何选项:
ln [original-file] [linked-file]
在以下示例中,我们为new-report文件创建了一个硬链接,并将其命名为new-report-ln:

图 2.15 – 使用硬链接
在输出中,你会看到它们具有相同的大小和相同的 inode,且在使用echo和输出重定向更改原始文件后,两个文件都能看到这些更改。这两个文件与符号链接的表示方式不同。它们在你的目录列表中会显示为两个不同的文件,且没有任何视觉标识来显示哪个文件被指向。实质上,硬链接是与原始文件的 inode 相连接的。你可以将其视为文件的新名称,类似于但不完全相同于重命名文件。
删除文件
在 Linux 中,删除文件可以使用rm命令。在最简单的形式下,rm命令是没有选项的。如果你想更精确地控制删除方式,可以使用-i、-f和-r选项:
rm -i:此选项启用交互模式,在删除前会要求你确认:

图 2.16 – 交互式删除文件
在前面的示例中,我们使用 -i 选项删除了 art-file。在要求互动时,你有两个选择。你可以通过输入 y (是)来批准该操作,或者输入 n (否)来取消该操作。
rm -f:-f选项会强制删除文件,无需用户互动:

图 2.17 – 强制删除文件
我们使用 rm -f 命令删除了之前创建的 new-report-link 文件。该命令没有要求我们的确认,直接删除了文件。
rm -r:此选项以递归模式删除文件,用于删除多个文件和目录。例如,我们将尝试删除new-files目录。使用最简单的rm命令时,输出会显示错误,表示无法删除目录。但当与-r选项一起使用时,目录会立即被删除:

图 2.18 – 递归删除目录
重要提示
我们建议使用 rm 命令时要格外小心。最具破坏性的方法是使用 rm -rf。这将毫不警告地删除文件、目录及任何内容。请注意,一旦执行了此操作,无法恢复,后果将是不可挽回的。
大多数情况下,删除文件是单向操作,无法回头。这使得删除文件的过程非常重要,在删除之前进行备份可以帮你避免许多不必要的压力。
创建目录
在 Linux 中,你可以使用 mkdir 命令创建新目录。在以下示例中,我们将创建一个名为 development 的新目录:
mkdir development
如果你想一次创建多个目录和子目录,你需要使用 -p 选项(p 来自父目录),如以下图所示:

图 2.19 – 创建父目录
在 Linux 中,目录也是文件,只是它们具有特殊的属性。它们对于组织文件系统至关重要。如需了解更多此有用工具的选项,欢迎查阅手册页面。
删除目录
Linux 中删除目录的命令是 rmdir。它默认只删除空目录。让我们看看如果尝试删除一个非空目录会发生什么:

图 2.20 – 使用 rmdir 命令
这是来自 shell 的一种预防性措施,因为删除一个非空目录可能会产生灾难性的后果,正如我们在使用rm命令时所看到的那样。rmdir命令不像rm那样有-i选项。使用rmdir命令删除目录的唯一方法是先手动删除目录中的文件。然而,前面展示的rm -r命令在删除目录时仍然有用且更具灵活性。
现在你已经知道如何在 Linux 中操作目录了,我们将继续展示文件查看命令。
文件查看命令
由于 Linux 中的一切都是文件,能够查看和操作文件内容是任何系统管理员的必备技能。在这一节中,我们将学习文件查看命令,因为几乎所有的文件都包含文本,某个时刻这些文本都应该是可读的。
cat命令
这个命令在本章之前的一些示例中已经使用过。它是concatenate(拼接)的缩写,用来将文件内容打印到屏幕上。我们在本章中已经多次使用了cat,这里再举一个例子。我们有两个现有文件,一个叫new-report,另一个叫users。让我们通过以下图示来展示如何使用cat:

图 2.21 – 使用cat命令的示例
在这个例子中,我们首先使用命令显示了仅一个文件new-report的内容。第二个命令则用来同时显示两个文件new-report和users的内容。cat命令将这两个文件的内容显示在屏幕上。两个文件都位于同一目录中,也就是用户的工作目录。如果你想拼接那些不在当前工作目录中的文件内容,你需要使用它们的绝对路径。
cat命令有多个可用选项,我们这里不作详细介绍,因为在大多数情况下,它最纯粹的形式是最常用的。更多详情请参阅手册页。
less命令
有时候,文件的内容太多,会占据多个屏幕,单纯用cat命令在终端上查看很困难。这时,less命令就派上用场了。它一次只显示一个屏幕的内容。一个屏幕显示多少内容,取决于你的终端窗口的大小。以/etc/passwd文件为例,它可能包含多行内容,无法完全显示在一个屏幕上。你可以使用以下命令:
$ less /etc/passwd
当你按下Enter时,文件的内容将显示在你的屏幕上。要在其中浏览,你可以使用以下按键:
-
空格键:向前移动一个屏幕
-
Enter: 向前移动一行
-
b: 向后移动一个屏幕
-
/: 进入搜索模式;在文件中向前搜索
-
?: 搜索模式;在文件中向后搜索
-
v: 使用默认编辑器编辑文件
-
g: 跳转到文件开头
-
Shift + g:跳转到文件末尾
-
q:退出输出。
less 命令有许多可以使用的选项。我们建议你查阅手册页,了解更多关于该命令的信息。
head 命令
当你只想将文本文件的开头(头部)打印到屏幕时,这个命令非常有用。默认情况下,它只会打印文件的前 10 行。你可以使用相同的 /etc/passwd 文件来进行 head 操作,并执行以下命令。观察发生的情况。它会打印前 10 行,然后退出命令,带你回到 shell 提示符:
head /etc/passwd
这个命令的一个有用选项是打印文件的多行或少行。为此,你可以使用 -n 参数,或者直接使用 – 后跟你想打印的行数。对于 /etc/passwd 文件,我们首先使用没有任何选项的 head 命令,然后使用带有行数参数的命令,如下图所示:

图 2.22 – 使用 head 命令
这个命令提供的许多其他选项对于作为系统管理员的工作非常有用,但我们在此不会涵盖它们。你可以自行探索。
tail 命令
tail 命令与 head 命令类似,只不过它默认打印文件的最后 10 行。你可以使用与 head 命令相同的 -n 参数,以查看文件末尾的特定行数。然而,tail 命令通常用于实时监视不断变化的日志文件。它可以在其他应用程序写入文件时打印文件的最后几行。例如,考虑以下行:
tail -f /var/log/syslog
使用 -f 选项将使该命令在写入 /var/log/syslog 文件时进行监视。它将有效地在屏幕上显示文件内容。-f 选项会导致 tail 命令在日志轮换时停止,这时应该使用 -F 选项。使用 -F 选项时,命令会在日志轮换期间继续显示输出。要退出该屏幕,你需要按 Ctrl + C 返回到 shell 提示符。以下是前述命令输出的示例:

图 2.23 – 使用 tail 命令进行实时日志文件观察
接下来,让我们了解如何在 Linux 中查看文件属性。
文件属性命令
有时,仅查看文件内容是不够的,你可能需要该文件的额外信息。还有其他一些实用的命令可以使用,我们将在接下来的部分中介绍它们。
stat 命令
stat 命令提供比 ls 命令更多的信息。下面的示例展示了对于同一文件,ls 和 stat 输出的对比:

图 2.24 – 使用 stat 命令
stat命令可以提供有关文件名、大小、块数、文件类型、inode、链接数、权限、UID 和 GID,以及atime、mtime和ctime的更多信息。如需了解更多信息,请参考 Linux 手册页。
文件命令
这个命令只是报告文件的类型。这里有一个文本文件和一个命令文件的示例:

图 2.25 – 使用 file 命令
Linux 不像其他操作系统那样依赖文件扩展名和类型。在这方面,file命令更侧重于根据文件内容来确定文件类型,而非其他。
配置文件所有权和权限的命令
在 Linux 中,使用chown命令。当设置组所有权时,你可以为该组中的所有人确定权限。这是通过使用chgrp命令来设置的。至于其他用户,指的是该系统中其他任何人,即那些没有创建文件、不是文件所有者并且不属于文件所有者所在组的人。其他用户也被称为“世界”。
除了设置用户所有权,系统还必须知道如何确定用户行为,而它是通过使用ls -l命令来实现的:

图 2.26 – 长格式列出输出
在前面的示例中,你会看到我们家目录中的文件有两种不同类型的权限。每行都有 12 个字符,分别用于特殊属性和权限。在这 12 个字符中,前 10 个被用于前面的示例。九个字符表示权限,第一个字符表示文件类型。权限有三个容易记住的缩写:
-
r表示读权限 -
w表示写权限 -
x表示执行权限 -
-表示没有权限
这九个字符被分为三个区域,每个区域由三个字符组成。前三个字符用于用户权限,接下来的三个字符用于组权限,最后三个字符代表其他权限,或称全局权限。
文件类型也有它们的代码,具体如下:
-
d:字母d表示这是一个目录 -
-:连字符表示这是一个文件 -
l:字母l表示这是一个符号链接 -
p:字母p表示这是一个命名管道;一个特殊文件,方便程序间的通信 -
s:字母s表示这是一个套接字,类似于管道,但具有双向和网络通信功能 -
b:字母b表示这是一个块设备;一个对应硬件设备的文件 -
c:字母c表示这是一个字符设备;类似于块设备
权限字符串是一个 10 位的字符串。第一位用于文件类型。接下来的九位通过分成 3 位一组来表示权限。每组由一个八进制数表示(因为八进制数有三位)。因此,权限是用二的幂来表示的:
-
read是 2 ^ 2(2 的幂),等于 4 -
write是 2 ^ 1(1 的幂),等于 2 -
execute是 2 ^ 0(零的幂),等于 1
在这方面,文件权限应按以下图示表示:

图 2.27 – 文件权限解释
在上面的图示中,权限以九个字符的字符串显示,正如你在 ls -la 输出中看到的那样。该行被分为三个部分,一个是用户/所有者的权限,一个是组的权限,另一个是其他/世界的权限。这些信息在前两行中展示。其他两行展示了权限的类型(read、write 和 execute),以及下文中提到的八进制数。
这非常有用,因为它将八进制表示与字符表示的权限关联起来。因此,如果你将一个权限表示为 rwx r-x 转换为八进制,依据前面的图示,你可以轻松地说它是 755。这是因为,对于第一个组,即所有者,你有所有权限(rwx),这转换为 4+2+1=7。对于第二个组,你只有两个权限是激活的,r 和 x,这转换为 4+1=5。最后,对于第三组,你也有两个权限是激活的,类似于第二组(r 和 x),这也转换为 4+1=5。现在你知道,该权限的八进制表示是 755。
作为练习,你应该尝试将以下权限转换为八进制:
-
rwx rwx -
rwx r-x -
rwx r-x - - - -
rwx - - - - - - -
rw-rw- rw- -
rw- rw- r - - -
rw- rw- - - - -
rw- r- -r- - -
rw- r- - - - - -
rw- - - - - - - -
r - - - - - - - -
重要说明
还有一些其他重要的命令,如 umask、chown、chmod 和 chgrp,分别用于更改或设置默认创建模式、所有者、模式(访问权限)和组。它们将在此简要介绍,因为它们涉及到设置文件的属性,但要了解更详细的描述,请参考第四章,用户和组的管理。
文件压缩、解压缩和归档的命令
在 Linux 中,标准的归档工具叫做 tar,即磁带归档。最初用于 Unix 中将文件写入外部磁带设备进行归档。如今,在 Linux 中,它也用于将文件以压缩格式写入文件。除了 tar 归档格式,其他常用的压缩归档格式包括 gzip 和 bzip,以及 Windows 系统中的流行格式 zip。现在我们来详细了解一下 tar 命令。
用于压缩和解压的 tar 命令
这个命令需要与选项一起使用,默认情况下不提供压缩功能。要使用压缩,我们需要使用特定的选项。以下是 tar 提供的一些最有用的选项:
-
tar -c:创建归档文件 -
tar -r:将文件追加到已有的归档文件中 -
tar -u:只将已更改的文件追加到现有的归档文件中 -
tar -A:将归档追加到另一个归档的末尾 -
tar -t:列出归档文件的内容 -
tar -x:提取归档文件内容 -
tar -z:使用gzip压缩归档文件 -
tar -j:使用bzip2压缩归档文件 -
tar -v:使用详细模式,在屏幕上打印额外的信息 -
tar -p:恢复提取文件的原始权限和所有权 -
tar -f:指定输出文件的名称
在你的日常任务中,你可能会需要将这些选项组合使用。
例如,要创建一个 files 目录的归档文件,我们可以将 -cvf 选项组合使用,示例如下:

图 2.28 – 使用 tar 命令
创建的归档文件未压缩。要使用压缩,我们需要添加 -z 或 -j 选项。接下来,我们将使用 -z 选项来进行 gzip 压缩。请参见以下示例,并比较两个归档文件的大小。一般来说,建议为此类文件使用扩展名:

图 2.29 – 使用 gzip 压缩 tar 文件
要解压一个 tar 归档文件,你可以使用 -x 选项(如本小节开头所示)。例如,我们可以解压我们在本小节前面创建的 files-archive.tar 文件,并使用 -C 选项指定解压后的文件存放目标目录。目标目录需要事先创建。我们将使用以下命令:
mkdir uncompressed-directory
tar -xvf files-archive.tar -C uncompressed-directory
这将从归档中提取文件,并将它们添加到未压缩目录中。例如,要解压一个 gzip 压缩的归档文件,如 files-archive-gzipped.tar.gz,我们将添加 -z 选项,并结合前面的命令使用,示例如下:
tar -xvzf files-archive-gzipped.tar.gz -C uncompressed-directory
就这样,现在你知道如何在 Linux 中归档和解压文件了。Linux 中还有其他有用的归档工具,但 tar 仍然是最常用的。你可以自由探索其他工具或 tar 的其他有用选项。
查找文件的命令
在 Linux 中查找文件是任何系统管理员的重要任务。由于 Linux 系统包含大量文件,查找文件可能是一个让人头疼的任务。不过,你手头有许多有用的工具,学会如何使用它们将是你最大的资产之一。在接下来的章节中,我们将讨论 locate、which、whereis 和 find 等命令。
locate 命令
locate 命令在 Ubuntu 中默认未安装。要安装它,请使用以下命令创建系统上所有文件位置的索引:
sudo apt install mlocate
因此,当你执行该命令时,它会在数据库中搜索你的文件。它使用 updatedb 命令作为辅助工具。
在开始使用 locate 命令之前,你应该先执行 updatedb 来更新位置数据库。更新后,你可以开始定位文件。在以下示例中,我们将定位任何文件,其名称中包含 new-report:

图 2.30 – 使用 locate 命令
如果我们搜索一个更通用的文件名,例如 presentation,输出会过长且不相关。以下是一个示例,我们使用了输出重定向到文件并使用 wc(字数统计)命令仅显示文件的行数、单词数和字节数:

图 2.31 – 使用 locate 命令并进行输出重定向和 wc 命令
在前面的输出中,结果文件有八行。这意味着找到了八个文件,它们的文件名中包含字符串 presentation。确切的数字是针对文件中的单词,因为路径之间没有空格,所以每一行都会被检测为一个单独的单词。此外,结果文件的大小是 663 字节。你可以尝试使用其他字符串。有关 locate 命令的更多选项,请参考 Linux 手册页面。
which 命令
该命令用于定位 shell 搜索路径中的可执行文件(程序或命令)。例如,要定位 ls 命令,输入以下内容:
packt@neptune:~$ which ls
/usr/bin/ls
你会看到输出是 ls 命令的路径:/usr/bin/ls。
现在尝试使用 cd 命令:
which cd
你会看到没有任何输出。这是因为 cd 命令是内建的 shell 命令,没有其他位置可以显示该命令。
whereis 命令
该命令仅查找可执行文件、文档文件和源代码文件。因此,它可能无法找到你想要的文件,请谨慎使用:

图 2.32 – 使用 whereis 命令
再次,cd 命令的输出没有显示任何相关内容,因为它是一个内建的 shell 命令。至于 ls 命令,输出显示了该命令本身的位置以及手册页面的位置。
find 命令
这个命令是 Linux 中最强大的命令之一。它可以根据特定的标准在目录和子目录中查找文件,具有超过 50 个选项。它的主要缺点是语法,因为它与其他 Linux 命令有所不同。学习 find 命令的最佳方法是通过示例。因此,我们将通过大量示例来展示这个命令,希望你能熟练掌握它的使用。要了解它的强大选项,请参考手册页。
以下是一些使用 find 命令的练习,我们认为这些对你非常有用。我们将提供使用的命令,但不会提供所有的输出结果,因为有些结果可能非常长。
-
在根目录下查找所有文件名中包含
e100字符串的文件,并将它们打印到标准输出:sudo find / -name e100 -print -
在根目录下查找所有文件名中包含
file字符串且类型为file的文件,并将结果打印到标准输出:sudo find / -name file -type f -print -
查找所有文件名中包含
print字符串的文件,仅在/opt、/usr和/var目录中查找:sudo find /opt /usr /var -name print -type f -print -
查找根目录中所有扩展名为
.conf的文件:sudo find / type f -name "*.conf" -
查找根目录中所有文件名中包含
file字符串且没有扩展名的文件:sudo find / -type f -name "file*.*" -
在根目录中查找所有具有以下扩展名的文件:
.c、.sh和.py,并将列表添加到名为findfile的文件中:sudo find / -type f \( -name "*.c" -o -name "*.sh" -o -name "*.py" \) > findfile -
在根目录中查找所有
.c扩展名的文件,对其进行排序,并将其添加到一个文件中:sudo find / -type f -name "*.c" -print | sort > findfile2 -
查找根目录中所有权限设置为
0664的文件:sudo find / -type f -perm 0664 -
查找根目录中所有所有者具有只读权限的文件:
sudo find / -type f -perm /u=r -
查找根目录中所有可执行的文件:
sudo find / -type f -perm /a=x -
查找根目录中所有在两天前修改过的文件:
sudo find / -type f -mtime 2 -
查找根目录中在过去两天内被访问过的所有文件:
sudo find / -type f -atime 2 -
查找所有在过去两到五天内修改过的文件:
sudo find / -type f -mtime +2 -mtime -5 -
查找所有在过去 10 分钟内修改过的文件:
sudo find / -type f -mmin -10 -
查找所有在过去 10 分钟内创建的文件:
sudo find / -type f -cmin -10 -
查找所有在过去 10 分钟内被访问过的文件:
sudo find / -type f -amin -10 -
查找所有大小为 5 MB 的文件:
sudo find / -type f -size 5M -
查找所有大小在 5 MB 到 10 MB 之间的文件:
sudo find / -type f -size +5M -size -10M -
查找所有空文件和空目录:
sudo find / -type f -empty sudo find / -type d -empty -
查找
/etc目录中所有最大的文件,并将前五个打印到标准输出。请注意,这个命令可能会占用大量系统资源。不要尝试对整个根目录执行此操作,因为可能会耗尽系统内存:sudo find /etc -type f -exec ls -l {} \; | sort -n -r | head -5 -
查找
/etc目录中最小的前五个文件:sudo find /etc -type f -exec ls -s {} \; | sort -n | head -5
随意尝试你想要的所有类型的 find 选项。这个命令非常灵活且强大,但使用时需要小心。
文本处理命令
grep、tee以及更强大的命令如sed和awk。不过,我们将在第八章中再次介绍这些命令,当时我们会向你展示如何创建和使用脚本。在本节中,我们只会给你一些如何在命令行中使用它们的提示。
grep命令
这是 Linux 中最强大的命令之一,也是非常实用的命令。它具有在文本文件中搜索字符串的能力,且有许多强大的选项:
-
grep -v:显示不符合搜索条件的行 -
grep -l:仅显示符合条件的文件名 -
grep -L:仅显示不符合条件的行 -
grep -c:一个计数器,显示符合条件的行数 -
grep -n:显示找到字符串的行号 -
grep -i:搜索不区分大小写 -
grep -r:在目录结构中递归搜索 -
grep -R:在目录结构中递归搜索并跟踪所有符号链接 -
grep -E:使用扩展正则表达式 -
grep -F:使用严格的字符串列表而不是正则表达式
以下是如何使用grep命令的一些示例:
-
查找最后一次使用
sudo命令的时间:sudo grep sudo /var/log/auth.log -
在
/etc目录中的文本文件中搜索packt字符串:grep -R packt /etc -
显示匹配结果所在的确切行:
grep -Rn packt /etc -
如果你不想看到每个文件中找到匹配项的文件名,可以使用
-h选项。然后,grep只会显示找到匹配项的行:grep -Rh packt /etc -
要仅显示找到匹配项的文件名,可以使用
-l:grep -Rl packt /etc
grep很可能与 Shell 管道一起使用。以下是一些示例:
-
如果你只想查看当前工作目录中的目录,可以将
ls命令的输出通过管道传给grep。在以下示例中,我们只列出了以字母d开头的行,这些行代表目录:ls -la | grep '^d' -
如果你想显示你的 CPU 型号,可以使用以下命令:
cat /proc/cpuinfo | grep -i 'Model'
作为 Linux 系统管理员,你会发现grep是你最亲密的朋友之一,所以不要害怕深入了解它的选项和隐藏的宝藏。
tee 命令
这个命令与cat命令非常相似。基本上,它做的事情是将标准输入复制到标准输出而不做任何更改,但它还会将这些内容复制到一个或多个文件中。
在以下示例中,我们使用wc命令来统计/etc/passwd/文件中的行数。我们将输出通过管道传给tee命令,并使用-a选项(如果文件已经存在,则追加),该命令将输出写入名为no-users的新文件,并同时打印到标准输出。然后,我们使用cat命令再次检查新文件的内容:

图 2.33 – 使用 tee 命令
tee 命令是文件操作命令中的“黑马”。虽然它非常强大,但其使用往往容易被忽视。尽管如此,我们鼓励你尽可能多地使用它的强大功能。
在接下来的章节中,我们将展示如何在 Linux 的命令行中使用文本编辑器。
使用文本编辑器创建和编辑文件
Linux 有多个命令行文本编辑器可供使用。包括 nano、Emacs 和 Vim 等等,它们是使用最多的。还有 Pico、JOE 和 ed 等编辑器,相比前面提到的,使用频率较低。我们将重点介绍 Vim,因为几乎可以确定你在使用的任何 Linux 系统中都会找到它。然而,目前的趋势是用 nano 取代 Vim 作为默认文本编辑器。例如,Ubuntu 默认没有安装 Vim,但 CentOS 安装了 Vim。Fedora 目前也在尝试将 nano 作为默认文本编辑器。因此,你可能需要学习 nano,但出于遗留原因,Vim 仍然是一个非常有用的工具。
使用 Vim 编辑文本文件
Vim 是 vi 的改进版,后者是 Unix 系统中的默认文本编辑器。Vim 是一个非常强大的编辑工具。这种强大伴随着许多可以简化工作的选项,可能让人感到不知所措。在这一小节中,我们将向你介绍文本编辑器的基本命令,足以帮助你熟练使用它。
Vim 是一个基于模式的编辑器,它的操作是围绕不同的模式组织的。简而言之,这些模式如下:
-
command模式是默认模式,等待执行命令。 -
insert模式是文本插入模式 -
replace模式是文本替换模式 -
search模式是用于搜索文档的特殊模式。
让我们来看一下如何在这些模式之间切换。
在不同模式之间切换
当你第一次打开 Vim 时,你将看到一个空白的编辑器,里面仅显示所用版本信息和一些帮助命令。你处于 command 模式。这意味着 Vim 正在等待命令的操作。
要激活 insert 模式,按下键盘上的 I 键。你将能够在光标当前位置开始插入文本。你也可以按 A 键(追加模式)开始在光标位置的右侧编辑。I 和 A 都会激活 insert 模式。要退出当前模式,按下 Esc 键,这样你就会回到 command 模式。
如果你打开一个已有文本的文件,在 command 模式下,你可以使用箭头键浏览文件。由于 Vim 继承了 vi 的工作流,你还可以使用 H(向左移动)、J(向下移动)、K(向上移动)和 L(向右移动)。这些是来自早期终端键盘的传统按键,那时的键盘没有独立的箭头键。
仍然处于 command 模式下(默认模式),你可以通过按键盘上的 R 来激活 replace 模式。在这个模式下,你可以替换光标所在位置的字符。
在command模式下,按下/键可以激活search模式。进入该模式后,你可以开始输入搜索字符串,然后按Enter键。
还有last line模式,或称为ex command模式。按下:键可以激活此模式。这是一个扩展模式,可以执行诸如w保存文件,q退出,或wq同时保存并退出等命令。
基本 Vim 命令
使用 Vim 意味着你需要熟练使用键盘快捷键来执行基本命令。我们将引导你访问 Vim 的文档页面(vimdoc.sourceforge.net/)查看所有可用的命令,并且我们会在下图中快速展示一些最有用的命令:

图 2.34 – 基本 Vim 命令
对于 Linux 新手来说,Vim 可能相当令人畏惧。如果你更喜欢其他编辑器也没有关系,因为有很多选择。现在,我们将向你展示 nano 的简要介绍。
nano 文本编辑器
Vim 是一个强大的文本编辑器,学会如何使用它对任何系统管理员来说都非常重要。然而,其他文本编辑器同样强大,且使用起来更加简单。
通过使用$EDITOR变量,这是.bashrc文件的情况。然而,在 Ubuntu 中,你可以通过以下命令查看系统上的默认编辑器:

图 2.35 – 检查 Ubuntu 上的默认文本编辑器
你可以通过在 Ubuntu/Debian 和 Fedora/Rocky Linux 或 openSUSE 上使用nano命令来调用 nano 编辑器。当你输入该命令时,nano 编辑器将打开,界面非常简洁,比 Vim 或 Emacs 等编辑器更容易使用。你可以随时使用你喜欢的文本编辑器。
总结
在这一章中,你学会了如何使用 Linux 中最常用的命令。你现在知道如何管理(创建、删除、复制和移动)文件,了解了文件系统的组织方式,学会了如何操作目录,并查看文件内容。你现在理解了 shell 和基本权限。你所学到的技能将帮助你管理任何 Linux 发行版中的文件,并编辑文本文件。你已经学会了如何使用 Vim,这是 Linux 中最广泛使用的命令行文本编辑器之一。这些技能将帮助你学习如何使用其他文本编辑器,如 nano 和 Emacs。你将在本书的几乎每一章中使用这些技能,并且在你作为系统管理员的日常工作中也会使用到它们。
在下一章中,你将学习如何管理软件包,包括如何在 Debian 和基于 Red Hat 的发行版中安装、删除和查询软件包。这项技能对任何管理员都很重要,必须成为任何基础培训的一部分。
问题
在我们的第二章中,我们介绍了 Linux 文件系统及基本命令,这些将构成整本书的基础。这里有一些问题供你测试自己的知识,并进行进一步练习:
- 创建一个压缩归档,包含
/etc目录下所有.``conf扩展名文件的命令是什么?
tar 命令,如本章所示。
- 列出
/etc目录下前五个文件并按尺寸降序排序的命令是什么?
find 命令与 sort 和 head 结合使用。
- 创建层级目录结构的命令是什么?
mkdir 命令,如本章所示。
- 查找根目录下具有三种不同扩展名的文件的命令是什么?
find 命令。
- 查找 Linux 中哪些命令设置了 设置所有者用户 ID(SUID)。
带有 -``perm 参数的 find 命令。
- 哪个命令用于创建一个包含 1,000 行随机生成的单词(每行一个单词)的文件?
shuf 命令(本章未展示)。
- 执行与之前相同的操作,但这次生成一个包含 1,000 个随机生成数字的文件。
for 循环。
- 如何查找上次使用
sudo的时间以及通过它执行的命令?
grep 命令。
进一步阅读
如需了解本章所涉及内容的更多信息,请参考以下 Packt 出版的书籍:
-
Linux 基础知识,作者:Oliver Pelz
-
掌握 Ubuntu 服务器 – 第四版,作者:Jay LaCroix
第三章:Linux 软件管理
软件管理是 Linux 系统管理中的一个重要方面,因为作为系统管理员,你在某个层面上必须与软件包打交道。掌握如何处理软件包是你完成本章节后的一项重要技能。
在本章节中,你将学习如何使用特定的软件管理命令,并了解软件包的工作方式,这取决于你选择的发行版。你将学习如何在现代 Linux 发行版上使用最新的Snap和Flatpak包类型。
在本章节中,我们将覆盖以下主要内容:
-
Linux 软件包类型
-
软件包管理
-
在 Linux 中安装新的桌面环境
技术要求
本章节不需要特别的技术要求,只需在系统上安装一个正常工作的 Linux。Ubuntu、Fedora(或AlmaLinux)以及openSUSE都适用于本章节的练习,因为我们将涵盖所有类型的包管理器。
Linux 软件包类型
正如你目前已经学到的,Linux 发行版包含一个内核和其上的应用程序。虽然默认情况下已经安装了许多应用程序,但确实会有需要安装新应用程序或删除不需要的应用程序的情况。
在 Linux 中,应用程序捆绑在仓库中。仓库是一个集中管理的地点,包含由开发者维护的软件包。这些包可能包含单独的应用程序或与操作系统相关的文件。每个 Linux 发行版都有几个官方仓库,但除了这些,你还可以添加一些新的仓库。添加仓库的方式特定于每个发行版,我们将在本章稍后的内容中详细介绍。
Linux 有几种类型的包可用。Ubuntu 使用deb包,因为它基于 Debian,而 Fedora(或 Rocky Linux 和 AlmaLinux)使用rpm包,因为它基于 RHEL。还有 openSUSE,它也使用rpm包,但最初基于 Slackware。除此之外,还有两种新型包类型最近被引入——由 Canonical 公司(Ubuntu 背后的公司)开发的 Snap 包,以及由 GNOME、Red Hat 和 Endless 等多个开发者和组织开发的 Flatpak 包。
DEB 和 RPM 包类型
DEB 和 RPM 是最古老的包类型,分别用于 Ubuntu 和 Fedora。即使是前面提到的两种新包类型(snap 和 flatpak)开始在 Linux 桌面上得到应用,它们依然被广泛使用。
这两种包类型都符合Linux 标准基础(LSB)规范。LSB 的最后一个版本是 5.0,于 2015 年发布。你可以在refspecs.linuxfoundation.org/lsb.shtml#PACKAGEFMT找到更多信息。
DEB 包的结构
DEB 包在 1993 年随 Debian 发行版首次推出,并自那时起一直在每个 Debian 和 Ubuntu 衍生版中使用。deb包是一个二进制包。这意味着它包含了程序本身的文件,以及其依赖项和元信息文件,所有内容都包含在一个归档文件中。
要查看二进制deb包的内容,你可以使用ar命令。它在 Ubuntu 22.04.2 LTS 中默认没有安装,因此你需要使用以下命令自行安装:
$ sudo apt install binutils
好了——你已经在 Ubuntu 上安装了一个软件包!现在,一旦ar被安装,你就可以查看任何deb包的内容。为了这个练习,我们已经下载了一个名为1password的密码管理器的deb包并查看了它的内容。要查询该包,执行以下步骤:
-
使用
wget命令;文件将被下载到当前工作目录:ar t 1password-latest.deb command to view the contents of the binary package. The t option will display a table of contents for the archive:

图 3.1 – 使用 ar 命令查看 deb 文件的内容
如你所见,输出列出了四个文件,其中两个是归档文件。你也可以使用ar命令调查这个包。
-
使用
ar x 1password-latest.deb命令将包的内容提取到当前工作目录:ls command to list the contents of your directory. You will see that the four files have been extracted and are ready to inspect. The debian-binary file is a text file that contains the version of the package file format, which in our case is 2.0\. You can concatenate the file to verify your package with the help of the following command:control.tar.gz 存档包含元信息包和在安装过程中或之前和之后运行的脚本,具体取决于情况。data.tar.xz 存档包含将在安装过程中提取的程序可执行文件和库。你可以使用以下命令检查其内容:
gpg signature file.
重要提示
gpg文件是一个使用 GNU Privacy Guard 加密的文件。它使用一种称为 OpenGPG(由 RFC4880 标准定义)的加密标准。它通常用于签署软件包文件,因为它为开发者提供了一种安全的方式来分发软件。有关此问题的更多信息,请阅读官方文档:www.openpgp.org/。
以下截图显示了这些命令的输出:

图 3.2 – deb 包的内容
每个包的元信息是一组文件,对于程序的运行至关重要。它们包含关于某些包的先决条件、所有依赖项、冲突和建议的信息。你可以使用与包装相关的命令自由探索包的所有组成部分。
现在我们知道了一个基于 Debian 的软件包由什么组成,接下来我们来看一下 Red Hat 软件包的组成部分。
RPM 包的结构
Red Hat 包管理器(RPM)包是由 Red Hat 开发的,广泛应用于 Fedora、CentOS、RHEL、AlmaLinux、Rocky Linux、SUSE 和 openSUSE 等系统。RPM 二进制包类似于 DEB 二进制包,因为它们也以归档格式进行打包。
让我们测试一下 1password 的rpm包,就像我们在上一节中测试 deb 包一样:
-
使用以下命令下载
rpm包:# wget https://downloads.1password.com/linux/rpm/stable/x86_64/1password-latest.rpm
如果你想使用相同的ar命令,你会发现对于 rpm 包,归档工具无法识别该文件格式。不过,还有其他更强大的工具可以使用。
-
我们将使用
rpm命令,它是指定的低级包管理器,专门用于 rpm 包。我们将使用-q(查询)、-p(包名)和-l(列表)选项:# rpm -qpl 1password-latest.rpm
输出与 deb 包相反,将是与应用程序相关的所有文件的列表,以及它们在你系统中的安装位置。
- 要查看包的元信息,运行带有
-q、-p和-i(安装)选项的rpm命令。以下是该命令输出的一个简短摘录:

图 3.3 – rpm 包的元信息
输出将包含应用程序的名称、版本、发行版、架构、安装日期、分组、大小、许可证、签名、源 RPM、构建日期和主机、URL、重定位和摘要等信息。
-
要查看该包在安装时需要哪些其他依赖项,可以运行相同的
rpm命令,附加-q、-p和--requires选项:$ rpm -qp --requires 1password-latest.rpm
输出如下所示的截图:

图 3.4 – 包要求
现在你已经了解了 Debian 和 Red Hat 包的概念以及它们包含的内容。DEB 和 RPM 包并不是 Linux 上唯一的包类型。它们可能是最常用和最广为人知的,但根据你选择的发行版,还有其他类型的包可用。此外,正如我们之前所提到的,现在有新的包类型可用于跨平台的 Linux 使用。这些新包被称为 flatpaks 和 snaps,我们将在接下来的章节中详细介绍它们。
snap 和 flatpak 包类型
Snap 和 Flatpak 是相对较新的包类型,被认为是 Linux 上应用程序的未来。它们都在隔离的容器中构建和运行应用程序,以提高安全性和可移植性。两者都旨在克服桌面应用程序安装的便捷性和可移植性的需求。
即使主要的 Linux 发行版拥有庞大的应用程序库,分发适用于众多 Linux 发行版的软件(每个发行版都有自己的包类型)也可能成为独立软件供应商(ISV)或社区维护者的一个严重问题。在这种情况下,snap 和 flatpak 便应运而生,旨在减轻软件分发的负担。
假设我们是独立软件开发商(ISV),目标是在 Linux 上开发我们的产品。一旦我们的软件有了新版本,我们需要至少创建两种类型的包,以便直接从我们的网站下载 —— 一个用于 Debian/Ubuntu/Mint 及其他衍生版的 .deb 包,一个用于 Fedora/RHEL/SUSE 及其他衍生版的 .rpm 包。
但如果我们想要克服这个问题,并使我们的应用程序能够跨多个 Linux 发行版使用,我们可以将其作为 flatpak 或 snap 进行分发。flatpak 包将通过Flathub提供,这是一个集中式的 flatpak 仓库,而 snap 包将通过 Snap Store 提供,这是一个集中式的 snap 仓库。无论哪种方式,都同样适用于我们的目标,即以最小的资源消耗和集中化的努力将应用程序分发到所有主要的 Linux 发行版上。
重要说明
这两种包类型都在努力克服 Linux 生态系统中包管理的整体碎片化问题。然而,这两种包有着不同的哲学,尽管它们的目标是解决相同的问题。Snap 作为一种新型的包出现,旨在支持 Canonical 的 Ubuntu 在物联网和服务器版本中的使用,而 flatpak 则源自于需要为 Linux 桌面应用程序提供一个统一的包类型。因此,flatpaks 仅适用于 Linux 的桌面版本,而在服务器或物联网版本中并不提供。随着这两种包的发展,越来越多的发行版开始默认提供它们,其中 flatpak 在默认提供的发行版数量上领先。另一方面,snap 主要在官方 Ubuntu 版本中默认提供,从 23.04 版本开始。flatpaks 在 Fedora、openSUSE、Pop!_OS、Linux Mint、KDE neon 和其他发行版中默认提供。
从这种情况中我们可以得出的结论是,Linux 的软件分发工作量比 Windows 或 macOS 的相同应用程序的打包要大。希望未来会有一个通用的 Linux 软件分发包,这样对用户和开发者来说都会更加有利。
snap 包的结构
snap 文件是一个SquashFS文件。这意味着它有自己的文件系统,封装在一个不可变的容器中。它有一个非常严格的环境,具有特定的隔离和限制规则。每个 snap 文件都有一个元信息目录,存储控制其行为的文件。
与 flatpaks 不同,snaps 不仅用于桌面应用程序,还广泛用于服务器和嵌入式应用程序。这是因为 Snap 起源于用于物联网和手机的 Ubuntu Snappy,这是 Canonical(Ubuntu 开发商)所推出的一个汇聚性努力的分发版本。
flatpak 包的结构
Flatpak 基于一种名为 OSTree 的技术。该技术由 GNOME 和 Red Hat 的开发者启动,现在在 Fedora Silverblue 中以 rpm-ostree 形式广泛使用。这是一个旨在与现有包管理系统并行工作的 Linux 升级系统。它的灵感来源于 Git,因为它的工作方式相似。可以将其视为操作系统级的版本控制系统。它使用内容寻址的对象存储,允许共享分支,并为操作系统提供事务性升级、回滚和快照选项。
目前,该项目已经更名为 btrfs 文件系统。
Flatpak 使用 libostree,它类似于 rpm-ostree,但仅用于桌面应用程序容器,并不涉及引导加载器管理。Flatpak 使用基于另一个名为 Bubblewrap 的项目的沙箱技术,允许非特权用户访问用户命名空间并使用容器功能。
Snaps 和 flatpaks 都完全支持图形化安装,但也提供了通过命令行进行更简便的安装和设置命令。在接下来的章节中,我们将专注于所有包类型的命令操作。
管理软件包
每个发行版都有自己的 rpm 命令,而高级工具包括 yum 和 dnf 命令。对于 openSUSE,另一个主要的基于 RPM 的发行版,低级工具是相同的 rpm 命令,但在高级工具方面,则使用 zypper 命令。对于基于 DEB 的发行版,低级命令是 dpkg,高级命令是 apt(或现在已弃用的 apt-get)。
Linux 中低级包管理器和高级包管理器的区别是什么?低级包管理器负责处理任何包操作的后端,并能够解包包、运行脚本和安装应用。高级包管理器负责依赖关系解析、安装和下载包(及一组包)以及元数据搜索。
管理 DEB 包
通常,对于任何发行版,包管理由管理员或具有 root 权限的用户(sudo)来处理。包管理包括任何类型的包操作,例如安装、搜索、下载和删除。对于所有这些操作,都有特定的 Linux 命令,我们将在接下来的章节中向你展示如何使用它们。
Ubuntu 和 Debian 的主要软件仓库
Ubuntu 的官方软件仓库包含约 60,000 个软件包,形式为二进制 .deb 包或 snap 包。系统仓库的配置存储在一个文件中,即 /etc/apt/sources.list 文件。Ubuntu 有四个主要的仓库,也称为包源,你可以在 sources.list 文件中看到它们的详细信息。这些仓库如下:
-
Main:包含由 Canonical 支持的自由和开源软件 -
Universe:包含由社区支持的自由和开源软件 -
Restricted:包含专有软件 -
Multiverse:包含受版权限制的软件
所有的仓库在/etc/apt/sources.list文件中默认启用。如果你想禁用其中的一些,可以根据需要编辑该文件。
在 Debian 中,仓库信息存储在相同的/etc/apt/sources.list文件中。唯一的区别是,它使用不同的名称来标识主软件包源,如下所示:
-
main:包含符合 Debian 自由软件准则的软件 -
contrib:可能符合或不符合自由软件准则,但不是主发行版的一部分的软件 -
non-free:非开源软件且不符合自由软件准则的软件
Debian 和 Ubuntu 的源文件非常相似,因为它们内部具有相同的信息结构。不同之处在于各自特定的包源部分。
两者都基于 Debian 的高级包管理工具(APT),因此我们将在接下来的章节中详细讲解。
与 APT 相关的命令
直到四年前,任何基于 Debian 的发行版都使用apt-get命令来实现软件包管理。从那时起,一个新的改进版命令apt诞生了(源自apt-get的缩写,因此提供了更集成的体验)。
在使用apt命令进行任何操作之前,你应该更新所有可用软件包的列表。你可以通过以下命令来完成此操作:
$ sudo apt update
前面命令的输出将显示是否有可用的更新。需要更新的软件包的数量会显示出来,并附有一个命令,若你想了解更多详情,可以执行该命令。
在继续之前,我们建议你使用apt --help命令,因为它会显示最常用的与 APT 相关的命令。输出如下截图所示:

图 3.5 – 最常用的 apt 命令
让我们更详细地了解其中的一些内容。
安装和删除软件包
基本的系统管理任务包括安装和删除软件包。在本节中,我们将向你展示如何使用apt命令安装和删除软件包。
要安装一个新的软件包,你可以使用apt install命令。我们在本章开始时谈到 DEB 包的结构时使用了此命令。记得当时我们必须安装ar命令作为替代品来检查.deb包。那时我们使用的命令是:
$ sudo apt install binutils
该命令在系统上安装了几个软件包,其中包括我们需要的用于完成操作的那个。apt命令还会自动安装任何必需的依赖项。
要删除一个包,你可以使用apt remove或apt purge命令。前者会删除已安装的软件包及所有由apt install命令安装的依赖项。后者将卸载软件包,就像apt remove一样,但也会删除所有由应用程序创建的配置文件。
在下面的例子中,我们正在移除之前通过apt的remove命令安装的binutils应用:
$ sudo apt remove binutils
输出将显示不再需要的软件包列表,并将其从系统中删除,系统会要求你确认是否继续。这是一个非常好的安全措施,它允许你审查将要删除的文件。如果你确信操作无误,可以在命令末尾添加-y参数,这样系统会自动回答命令中的任何问题为是。
然而,使用apt remove并不会移除所有与已删除应用相关的配置文件。为了查看系统上仍然存在的文件,你可以使用find命令。例如,要查看与binutils包相关但没有被删除的文件,可以使用以下命令:
sudo find / -type d -name *binutils 2>/dev/null
输出将显示目录(因此命令中使用了-type d选项),这些目录中仍然包含在删除包后残留的与binutils相关的文件。
另一个用于删除软件包及其所有相关配置文件的工具是apt purge。如果你想使用apt purge命令来代替apt remove,可以按如下方式使用它:
$ sudo apt purge binutils
输出类似于apt remove命令,显示将被删除的软件包以及将释放多少磁盘空间,并要求你确认是否继续操作。
重要提示
如果你打算使用apt purge来移除相同的包(在我们这个例子中是binutils),你将需要重新安装它,因为它是通过apt的remove命令被移除的。
apt remove命令也有一个purge选项,其效果与apt purge命令相同。语法如下:
sudo apt remove --purge [packagename]
如前所述,使用apt remove命令时,一些配置文件会被保留下来,以防操作是意外的,用户想要恢复到之前的配置。没有被remove命令删除的文件是一些小的用户配置文件,可以轻松恢复。如果操作不是意外的,并且你仍然想删除所有文件,你仍然可以使用apt purge命令来完成这个任务,方法是使用与已删除软件包相同的名称。
升级系统
时不时地,你需要进行系统升级,以确保你安装了所有最新的安全更新和补丁。在 Ubuntu 和 Debian 中,你始终需要使用两个不同的命令来完成这项任务。一个是 apt update,它将更新仓库列表,并确保获取所有可用的系统更新信息。另一个命令是 apt upgrade,它会升级包。你可以使用元字符将这两个命令一起执行:
$ sudo apt update; sudo apt upgrade
update 命令有时会显示哪些包不再需要,并给出类似以下的消息:
The following packages were automatically installed and are no longer required:
libfprint-2-tod1 libllvm9
Use 'sudo apt autoremove' to remove them.
升级完成后,你可以使用 sudo apt autoremove 命令来删除不再需要的包。autoremove 命令的输出将显示哪些包将被删除,以及将释放多少磁盘空间,并会请求你批准继续操作。
假设在我们使用 Ubuntu 的过程中,发布了一个新的发行版,我们希望使用它,因为它包含了我们使用的软件的更新版本。通过命令行,我们可以进行一次完整的发行版升级。执行此操作的命令如下:
$ sudo apt dist-upgrade
同样,我们也可以使用以下命令:
$ sudo apt full-upgrade
升级到较新版本的发行版应该是一个无故障的过程,但这并不是始终能保证的。一切取决于你的自定义配置。无论情况如何,我们建议你在升级到新版本之前,先进行完整的系统备份。
管理包信息
处理包时有时需要使用信息收集工具。仅仅安装和删除包是不够的。你需要搜索某些包以显示它们的详细信息,创建基于特定标准的列表,等等。
要搜索特定的包,你可以使用 apt search 命令。它会列出所有包,这些包的名称中包含搜索的字符串,也包括其他以不同方式使用该字符串的包。例如,我们来搜索 nmap 包:
$ sudo apt search nmap
输出将显示一个相当长的包列表,这些包以各种方式使用 nmap 字符串。你仍然需要上下滚动列表,找到你想要的包。为了获得更好的结果,你可以将输出通过管道传递给 grep 命令,但你会注意到一个警告,类似于下面截图所示:

图 3.6 – apt search 命令的输出
在警告之后,输出将显示一个包含 nmap 字符串的短包列表,其中有我们正在寻找的实际包,如 图 3.5 所示。
为了克服那个警告,你可以使用一个旧的命令 apt-cache search。执行此命令后,你将得到一个包的列表,但它的输出不会像 apt search 命令的输出那样详细:

图 3.7 – apt-cache 命令的输出
现在我们知道nmap包存在于 Ubuntu 的仓库中,我们可以通过使用apt的show命令显示更多详细信息,进一步研究它:
$ apt show nmap
输出将显示详细的描述,包括包的名称、版本、优先级、来源和部分、维护者、大小、依赖关系、建议的额外包、下载大小、APT 源和描述。
apt也有一个有用的list命令,可以根据某些条件列出包。例如,如果我们单独使用apt list命令,它将列出所有可用的包。但如果我们使用不同的选项,输出将是个性化的。
要显示已安装的包,我们可以使用-- installed选项:
$ sudo apt list --installed
要列出所有的包,请使用以下命令:
$ sudo apt list
为了比较的目的,我们将把每个输出重定向到不同的文件中,然后比较这两个文件。由于列表相当大,这样做是更容易查看两个输出之间的差异。我们现在将运行以下特定命令:
$ sudo apt list --installed > list-installed
$ sudo apt list > list
你可以使用ls -la命令比较两个结果文件,并观察大小的差异。你会看到list文件的大小明显大于list-installed文件。
还有其他方式可以比较这两个输出,我们希望你能通过自己探索来发现它们,作为本小节的练习。随时使用任何其他与 APT 相关的命令,并练习它们,直到熟悉它们的用法。APT 是一个强大的工具,每个系统管理员都需要知道如何使用它来维持一个可用且维护良好的 Linux 系统。可用性与所使用的应用程序以及它们的系统级优化紧密相关。
管理 RPM 包
RPM 包是 Fedora、AlmaLinux、Rocky Linux、RHEL 和 openSUSE/SLES 等 Linux 发行版的等效包。它们有专门的高级工具,包括dnf、yum和zypper。低级工具是rpm命令。
在 RHEL 中,默认的包管理器是Yellow Dog Updater, Modified(YUM),它基于Dandified YUM(DNF),这是 Fedora 中的默认包管理器。如果你同时使用 Fedora 和 RHEL,为了方便起见,你可以只使用其中一个,因为它们是相同的命令。为了保持一致性,我们将在本章的所有示例中使用 YUM。
YUM 是默认的高级管理工具。它可以安装、删除、更新、查询包,并解决依赖关系。YUM 可以管理从仓库或本地.rpm包安装的包。
Fedora/RHEL 基础发行版的主要仓库
仓库都由/etc/yum.repos.d/目录管理,配置文件位于/etc/yum.conf文件中。如果你列出repos目录,输出将类似于以下截图:

图 3.8 – RHEL 衍生版仓库
列出的所有这些文件包含关于仓库的关键信息,例如名称、镜像列表、gpg密钥的位置和启用状态。列出的所有都是官方仓库。
与 YUM 相关的命令
YUM 有许多命令和选项,但最常用的命令与软件包的安装、删除、搜索、信息查询、系统更新和仓库列出相关。
安装和删除软件包
要从 AlmaLinux/Rocky Linux(或 Fedora)的仓库中安装软件包,只需运行yum install命令。在以下示例中,我们将从命令行安装 GIMP 应用程序:
$ sudo yum install gimp
如果你已经下载了一个软件包并希望安装它,可以使用yum localinstall命令。这里,我们已经下载了 1password 的.rpm包:
wget https://downloads.1password.com/linux/rpm/stable/x86_64/1password-latest.rpm
然后,我们使用以下命令安装它:
sudo yum localinstall 1password-latest.rpm
localinstall命令会自动解决所需的依赖关系,并显示每个依赖项的来源(仓库)。
这是一个非常强大的命令,在某些情况下,使用rpm命令本身几乎变得多余。yum install和yum localinstall命令的主要区别在于后者能够解决本地下载包的依赖问题。前者会在活动的仓库中查找包,而后者则会在当前工作目录中查找要安装的包。
要从系统中删除一个软件包,请使用yum remove命令。我们将删除刚刚安装的 1password 软件包:
sudo yum remove 1password.x86_64
系统会询问你是否要删除应用程序安装的所有软件包。根据需要选择并继续。
重要说明
在 Fedora 或 RHEL 衍生版的命令对话框中,按下Enter或Return键的默认操作是N(即否),而在 Ubuntu 中,默认操作是设置为Y(即是)。这是一个预防性的安全措施,需要你额外的注意和干预。
输出与安装命令的输出非常相似,会显示如果你继续执行命令,将移除哪些软件包和依赖项。
如你所见,使用yum localinstall命令安装的所有依赖项将通过yum remove命令被移除。如果系统提示你继续,输入y并继续操作。
更新系统
要升级一个基于 Fedora/RHEL 的系统,可以使用yum upgrade命令。还有一个yum update命令,效果相同,通过更新已安装的软件包:
$ sudo yum upgrade
你可以使用-y选项自动回答命令的提问。
还有一个upgrade-minimal命令,它仅安装软件包的最新安全更新。
管理软件包信息
使用 yum 管理文件与使用 apt 管理文件非常相似。有很多命令可以使用,我们将详细介绍其中一些——我们认为最常用的那些命令。要了解更多这些命令及其用法,可以运行 yum --help。
要查看 yum 命令历史和管理的包概览,请使用以下命令:
$ sudo yum history
这将输出每个运行的 yum 命令、修改的包的数量以及执行操作的时间和日期,如以下示例所示:

图 3.9 – 使用 yum history 命令
要显示有关某个包的详细信息,我们有 yum info 命令。我们将查询 nmap 包,类似于我们在 Ubuntu 中所做的。在 CentOS 中,命令如下:

图 3.10 – 使用 yum info 命令
输出将显示包的名称、版本、发布、源、仓库和描述,与我们在 .deb 包中看到的非常相似。
要列出所有已安装的包或所有包,我们可以使用 yum 的 list 命令:
# yum list
要只查看已安装的包,请运行以下命令:
# yum list installed
如果我们将每个命令的输出重定向到特定文件,然后比较这两个文件,我们将看到它们之间的差异,类似于我们在 Ubuntu 中所做的。输出显示包的名称,接着是版本和发布号,以及它被安装的仓库。以下是一个简短的摘录:

图 3.11 – yum list installed 命令摘录
由于我们已经介绍了 DEB 和 RPM 文件中最常用的命令,我们并没有涵盖 openSUSE 和 SUSE SLE 中特定的包管理器 Zypper。我们将快速展示一些命令,让你熟悉 Zypper,并尝试一下 openSUSE。
使用 Zypper
在 openSUSE 中,Zypper 是包管理器,类似于 Debian/Ubuntu 和 Fedora/RHEL 中的 APT 和 DNF。以下部分涵盖了一些有用的命令。
安装和删除包
与使用 APT 和 DNF 类似,openSUSE 中的 Zypper 包管理器使用几乎相同的语法来安装和删除包。例如,我们将使用 zypper 命令安装 nmap。但首先,我们先在各自的仓库中搜索包名,看看它是否存在。我们将使用以下命令:
sudo zypper search nmap
此命令的输出是一个包含包名中有 nmap 字符串的包列表,后面跟着类型和摘要:

图 3.12 – 在 openSUSE 中使用 zypper search 命令
你会在列表的第一列看到S。它表示包的状态,如果该包已经安装,输出会有所不同。
从搜索输出中,我们可以看到 Nmap 应用程序的包名是nmap(它可能有不同的名称,这也是我们最初使用search命令的原因),因此我们将继续在系统上安装它。我们将使用zypper install命令来完成安装。
重要提示
在 openSUSE 中,你可以使用命令的简短版本。例如,可以使用zypper in代替zypper install,并跟上你要安装的包名。同样,zypper update可以用zypper up,dist-upgrade可以用dup,还可以将zypper remove简写为zypper rm。更多信息请查看手册页。
这里是安装nmap包的命令:
sudo zypper install nmap
另外,你可以使用以下命令:
sudo zypper in nmap
你可以在以下屏幕截图中看到输出:

图 3.13 – 使用 zypper in 命令
输出显示了将要安装的包。这里需要注意的是,Zypper 会自动处理依赖项,就像其他包管理器一样。除了nmap,还有两个库包准备安装。键入y继续安装,包将被安装。
现在,让我们再一次使用zypper search nmap命令,看看列表中关于nmap包的信息发生了什么变化:

图 3.14 – 使用 zypper search 检查 nmap 的状态
在输出中,你会看到列表的第一列在我们刚刚安装的nmap包前面有i+。这意味着该包及其依赖项已经安装。因此,如果你正在查找某个包并且它已经安装,你可以通过检查列表的第一列,也就是状态列,来判断。
现在,让我们移除我们已经安装过的相同包。我们将使用以下命令:
sudo zypper remove nmap
另外,我们可以使用以下命令:
sudo zypper rm nmap
输出显示在以下屏幕截图中:

图 3.15 – 使用 zypper remove 命令
这个命令的输出显示了将要被移除的包。如你所见,只有nmap包会被移除,其他一起安装的依赖包则不会被移除。要将它们与包一起移除,请在使用命令时加上--clean-deps参数。详情请参见下图:

图 3.16 – 移除依赖
现在你已经学会了如何使用zypper在 openSUSE 中安装和删除软件包,接下来让我们学习如何使用它来更新或升级整个系统。
升级和更新系统
在更新系统之前,你可能想查看哪些更新是可用的。为此,你可以使用以下命令:
zypper list-updates
该命令的输出将显示系统上所有可用的更新。要安装更新,请使用以下命令:
sudo zypper update
另一种方法是以下命令:
sudo zypper up
如果你在没有参数的情况下使用这些命令,就像我们刚才展示的那样,所有可用的更新都会被安装。你也可以通过在update命令中包含软件包名称作为参数来更新单独的软件包。
在 openSUSE 中,一些有用的命令用于为软件包添加和管理锁定,以防我们不希望它被更新或删除。让我们使用相同的nmap软件包来学习如何操作。如果你像我们在上一节中所做的那样删除了它,请重新安装它。我们将添加一个锁,检查这个锁,然后移除这个锁。
要为软件包添加锁定,请使用add-lock或zypper al命令。要查看系统中已锁定的软件包,可以使用zypper ll命令(列出锁定);要从软件包中移除锁定,可以使用zypper rl命令(移除锁定):

图 3.17 – 使用 Zypper 为软件包添加和移除锁
现在,让我们再次锁定nmap软件包并尝试删除它。你会看到该软件包不会被删除。首先,会询问你应该如何操作才能删除它。详情请参见下图:

图 3.18 – 尝试删除一个被锁定的包
更新过程很简单,你还学会了如何在 Zypper 中使用锁定选项来保护不同的软件包。现在你知道如何更新你的 openSUSE 系统了,接下来我们将在下节中学习如何查找特定软件包的信息。
管理软件包信息
如在使用 Ubuntu 和 Fedora 中的 APT 和 DNF 包管理器时所示,我们可以在 openSUSE 中使用 Zypper 获取软件包的信息。让我们使用与上一节相同的nmap软件包并获取更多关于它的信息。为此,我们将使用zypper info命令:
sudo zypper info nmap
如图 3.19所示,提供的信息与基于 Ubuntu 和 RHEL 的发行版类似。当我们卸载了nmap软件包时,输出中显示的信息会说明该软件包未安装。还有该软件包的更长描述,在下面的截图中没有包含:

图 3.19 – 使用 zypper info 命令
现在,让我们学习如何在 Linux 机器上管理 flatpaks 和 snaps。
使用 snap 和 flatpak 软件包
Snap 和 flatpak 是相对较新的包类型,广泛应用于各种 Linux 发行版。在本节中,我们将向你展示如何管理这些类型的包。对于 snap,我们将使用 Ubuntu 作为测试发行版,而对于 flatpak,我们将使用 Fedora,尽管只需做一点工作,这两种包类型也可以在任何一个发行版上运行。
在 Ubuntu 上管理 snap 包
Snap 在 Ubuntu 22.04.2 LTS 中默认安装。因此,你无需做任何事情来安装它。只需开始搜索你想要的包,并将其安装到系统中。我们将使用 Slack 应用程序来向你展示如何使用 snap 包。
搜索 snap 包
Slack 可以在 Snap Store 中找到,因此你可以安装它。为了确认,可以使用 snap find 命令进行搜索,如下例所示:
$ snap find "slack"
在命令输出中,你会看到许多包含 slack 字符串或与 Slack 应用相关的包,但列表中只有第一个是我们要寻找的。
重要提示
在任何 Linux 发行版中,来自不同包的两个应用程序可以共存,且通过不同的包管理器进行安装。例如,Slack 可以通过网站提供的 deb 文件安装,也可以通过 Snap Store 安装。
如果输出显示该包可用,我们可以继续在系统上安装它。
安装 snap 包
要安装 Slack 的 snap 包,我们可以使用 snap install 命令:

图 3.20 – 安装 Slack snap 包
接下来,让我们看看如何查看我们刚刚安装的 snap 包的更多信息。
Snap 包信息
如果你想了解更多关于该包的信息,可以使用 snap info 命令:
$ snap info slack
输出将显示与该包相关的信息,包括其名称、摘要、发布者、描述和 ID。显示的最后一项信息将是关于可用的 渠道,在我们 Slack 包的情况下如下所示:

图 3.21 – 显示 Slack 应用的 Snap 渠道
每个渠道包含有关特定版本的信息,知道选择哪个版本非常重要。默认情况下,install 命令会选择稳定版本,但如果你想选择不同的版本,可以在安装过程中使用 --channel 选项。在前面的示例中,我们使用了默认选项。
显示已安装的 snap 包
如果你想查看系统上已安装的 snap 包列表,可以使用 snap list 命令。尽管我们只在系统上安装了 Slack,但在输出中,你会看到还有很多其他应用程序已经安装。其中一些,如 core 和 snapd,是从发行版安装时默认安装的,是系统所需的:

图 3.22 – snap list 命令的输出
现在,我们将学习如何更新一个 snap 包。
更新一个 snap 包
Snaps 会自动更新。因此,你无需自己做任何事情。你能做的最少的就是检查是否有更新,并使用 snap refresh 命令加速安装,命令如下:
$ sudo snap refresh slack
更新后,如果你想回到之前使用的版本,可以使用 snap revert 命令,如以下示例所示:
$ sudo snap revert slack
在下一节中,我们将学习如何启用和禁用 snap 包。
启用或禁用 snap 包
如果我们决定暂时不使用某个应用程序,可以使用 snap disable 命令禁用该应用。如果我们决定重新使用该应用,可以使用 snap enable 命令再次启用它:

图 3.23 – 启用和禁用 snap 应用程序
记得使用sudo来启用和禁用 snap 应用程序。如果禁用不是你想要的操作,你可以完全移除该 snap。
移除 snap 包
在移除 snap 应用程序时,相关的配置文件、用户和数据也会被删除。你可以使用 snap remove 命令来执行此操作,如以下示例所示:
$ sudo snap remove slack
移除后,应用程序的内部用户、配置和系统数据将保存并保留 31 天。这些文件被称为快照,它们被归档并保存在 /var/lib/snapd/snapshots 目录下,包含以下类型的文件:一个 .json 文件,描述了该快照;一个 .tgz 文件,包含系统数据;以及特定的 .tgz 文件,包含每个系统的用户详细信息。以下是该目录的简短列出,展示了 Slack 的自动创建快照:

图 3.24 – 显示移除后的现有快照
如果你不想创建快照,可以在 snap remove 命令中使用 --purge 选项。对于使用大量数据的应用程序,这些快照可能会占用大量空间,并影响可用磁盘空间。要查看系统上保存的快照,可以使用 snap saved 命令:

图 3.25 – 显示已保存的快照
输出显示,在我们的案例中,列表中仅移除了一个应用程序,第一列显示的是快照的 ID (set)。如果你想删除一个快照,可以使用 snap forget 命令。我们可以使用以下命令删除 Slack 应用程序的快照:

图 3.26 – 使用 snap forget 命令删除快照
为了验证快照是否已被移除,我们再次使用了 snap saved 命令,如前面的图示所示。
Snaps 是多功能的包,使用起来非常方便。这种包类型是 Ubuntu 开发者的首选,但在其他发行版上并不常见。如果你想在 Ubuntu 以外的发行版上安装 snaps,可以参考 snapcraft.io/docs/installing-snapd 上的说明,测试它的全部功能。
现在,我们将测试另一个新兴的包:flatpaks。我们的测试发行版将是 Fedora,但请记住,flatpaks 也被 Ubuntu 系统的发行版(如 Linux Mint 和 elementary OS)以及基于 Debian 的发行版(如 PureOS 和 Endless OS)所支持。所有支持的 Linux 发行版的列表可以在 flatpak.org 找到。
在 Fedora Linux 上管理 flatpak 包
由于 flatpak 仅作为桌面应用程序提供,我们将以 Fedora Linux Workstation 为使用案例。在这种情况下,你可以在服务器上使用 RHEL/AlmaLinux/Rocky Linux,但在工作站上使用 Fedora。
与 snaps 类似,flatpaks 是在沙盒中运行的独立应用程序。每个 flatpak 包含应用程序所需的运行时和库。flatpaks 完全支持图形用户界面管理工具,并提供一整套命令,可以通过 flatpak 使用,这个命令本身还有其他一些内建命令用于包管理。要查看所有命令,可以使用以下命令:
$ flatpak --help
在以下章节中,我们将详细介绍一些广泛使用的 flatpak 包管理命令。但在此之前,先简单说明一下 flatpak 应用程序的命名方式及它们在命令行中的显示方式,以避免在这方面产生混淆。
每个应用程序都有一个类似于 com.company.App 的标识符。每一部分都旨在方便地识别应用程序及其开发者。最后一部分标识应用程序的名称,而前一部分标识开发该应用程序的实体。这是开发者发布和交付多个应用程序的一种简便方式。
添加 flatpak 仓库
如果你希望安装应用程序,必须设置仓库。flatpak 将仓库称为远程仓库,因此我们将使用这一术语来指代它们。
在我们的 Fedora 37 机器上,flatpak 已经安装,但我们需要添加 flathub 仓库。我们将通过 flatpak remote-add 命令来添加它,如以下示例所示:
$ sudo flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo
在这里,我们使用了 --if-not-exists 参数,如果仓库已存在,命令会停止执行,不会显示任何错误。仓库添加完成后,我们可以开始从中安装包,但必须先进行一次系统重启。
在 Fedora 37 及之前版本中,Flathub 仓库中的并非所有应用程序默认可用,但从版本 38 开始,开发者的目标是默认提供 Flathub 中的所有应用程序。让我们学习如何在 Fedora Workstation 上从 Flathub 安装应用程序。
安装 flatpak 应用程序
要安装一个包,我们需要知道它的名称。我们可以访问 flathub.org/home 并在那里搜索应用程序。我们将在网站上搜索一款名为 Open Broadcaster Software (OBS) 的软件,并按照提供的说明操作。我们可以点击右上角的 Install 按钮,也可以使用网页下半部分的命令。我们将使用以下命令:
$ sudo flatpak install flathub com.obsproject.Studio
在 flatpak 的最新版本(自版本 1.2 起)中,安装可以通过更简单的命令进行。在这种情况下,你只需要应用程序的名称,如下所示:
$ sudo flatpak install slack
结果与之前展示的第一个 install 命令相同。
管理 flatpak 应用程序
安装应用程序后,你可以通过以下命令在命令行中运行它:
$ flatpak run com.obsproject.Studio
如果你想更新所有应用程序和运行时,可以使用以下命令:
$ sudo flatpak update
要卸载一个 flatpak 包,只需运行 flatpak uninstall 命令:
$ sudo flatpak uninstall com.obsproject.Studio
要列出所有已安装的 flatpak 应用程序和运行时,可以使用 flatpak list 命令:

图 3.27 – flatpak list 命令的输出
要只查看已安装的应用程序,可以使用 --``app 参数:
$ flatpak list --app
这里显示的命令是 flatpak 包管理中最常用的命令。不用说,实际上还有许多其他命令,我们在这里不会一一覆盖,但你可以自由查阅并在系统上测试它们。要快速了解基本的 flatpak 命令,你可以参考以下链接:docs.flatpak.org/en/latest/flatpak-command-reference.html。
Flatpak 非常灵活,能够提供较新版本的应用程序。假设你想使用一个稳定的基础操作系统,但这样做的缺点是你默认获得的基础应用程序版本较旧。使用 flatpak 可以解决这个问题,让你访问较新版本的应用程序。随时浏览 Flathub 上的应用程序,并测试你认为有趣和有用的应用。
现在你已经知道如何通过命令行或图形用户界面在操作系统上安装新应用程序。除此之外,你还可以安装新的桌面环境。我们将在接下来的章节中展示如何操作。
在 Linux 中安装新的桌面环境
我们将继续以 Fedora 为例,但这里展示的命令同样适用于任何基于 RHEL 的发行版,如 AlmaLinux 或 Rocky Linux。
默认情况下,Fedora Workstation 使用 GNOME 作为桌面环境,但如果你想使用另一个桌面环境,比如 KDE,该怎么做呢?在展示如何操作之前,我们先为你提供一些关于 Linux 上可用的图形桌面环境的信息。
Linux 的核心在于选择,而在桌面环境(DEs)方面这一点尤为真实。Linux 上有许多种桌面环境可供选择,比如 GNOME、KDE、Xfce、LXDE、LXQT、Pantheon 等。最广泛使用的桌面环境是 GNOME、KDE 和 Xfce,其中前两者拥有最大社区。如果你想使用最新的 GNOME,你可以尝试像 Fedora、带有 GNOME 的 openSUSE Tumbleweed,或者 Arch Linux(或 Manjaro)这样的发行版。如果你想使用最佳的 KDE,可以尝试 KDE neon、带有 KDE 的 openSUSE Tumbleweed,或带有 KDE 的 Arch Linux(或 Manjaro)。对于 Xfce,你可以尝试 MX Linux(基于 Debian),它默认使用 Xfce,或者使用带有 Xfce 的 openSUSE。通常,最广泛使用的 Linux 发行版提供了不同桌面环境的变种,也叫做flavors(比如 Ubuntu)或spins(比如 Fedora)。RHEL 和 SUSE 的商业版本默认仅提供 GNOME。有关此处描述的桌面环境的更多信息,请参考以下网站:
-
要了解 KDE,请访问 www.kde.org
-
要了解 GNOME,请访问 www.gnome.org
-
要了解 Xfce,请访问 www.xfce.org
现在,让我们学习如何在默认的 Fedora Workstation 上安装一个不同的桌面环境。
在 Fedora Linux 上安装 KDE Plasma
在 Fedora 及其衍生发行版(以及 openSUSE)中,有一些应用程序组,这些组简化了安装大型应用及其依赖项的过程。当你计划安装多个应用作为一个更大组(就像一个桌面环境一样)时,这将非常有用。
要安装一个组,可以使用dnf install命令,并通过@符号和组名来调用该组。或者,你可以使用dnf groupinstall命令,并在引号内使用组名。
要查看 Fedora 仓库中可用的组,可以使用以下命令:
$ dnf group list --all | grep "KDE"
输出将是一个来自 Fedora 仓库的组列表,里面会包含KDE Plasma 工作区。要安装它,可以使用以下命令:
$ sudo dnf groupinstall "KDE Plasma Workspaces"
另外,你可以使用以下命令:
$ sudo dnf install @kde-desktop-environment
此命令将安装新的 KDE Plasma 环境,如下图所示:

图 3.28 – 安装 KDE Plasma 桌面环境
安装过程可能会花费一些时间,具体取决于你的网络连接速度。要开始使用 KDE Plasma 作为你的桌面环境,你需要退出当前会话。在登录屏幕上,选择你的用户,然后在右下角点击齿轮图标,在选项出现时选择Plasma。你将有两个选项,一个是Wayland,另一个是X11显示管理器:

图 3.29 – 在登录屏幕上选择 Plasma(Wayland)
Wayland 是较新的选项,可能在 KDE 中的支持不如在 GNOME 中的支持。你可以根据自己的偏好来选择。
现在,你可以登录到 Fedora Workstation 上的 KDE Plasma。以下截图显示了 KDE Plasma 中的信息中心应用程序,其中包含已安装版本和硬件的详细信息:

图 3.30 – Fedora 37 上 KDE Plasma 的信息中心
这样,你已经了解了如何在 Linux 中使用包管理工具,甚至如何安装新的桌面环境(DE)。这些知识足以让你开始在新的操作系统中进行一些探索。你可以安装新的应用程序,配置它们,并将你的发行版设置成你想要的样子。
总结
在本章中,你学习了如何在 Ubuntu、Fedora/AlmaLinux 和 openSUSE 中使用包管理工具,所学的技能将帮助你在任何 Linux 发行版中管理包。你学会了如何处理 .deb 和 .rpm 包,还学习了新的包管理方式,如 flatpaks 和 snaps。你在这里学到的技能将在本书的每一章中帮助你,甚至在你作为系统管理员的日常工作中,或者在闲暇时间享受 Linux 操作系统时,也会派上用场。
在下一章,我们将向你展示如何管理用户账户和权限,在那里你将了解一般概念和具体工具。
问题
现在你已经清楚了如何管理软件包,下面是一些练习,帮助你进一步巩固所学知识:
- 列出你系统中安装的所有包。
apt list --installed 命令。
- 在 Ubuntu 系统上添加对 flatpak 的支持。
提示:请参考 flatpak.org 上的文档。
- 测试其他发行版并使用它们的包管理工具。我们推荐你尝试 openSUSE,如果你感觉有信心,可以试试 Arch Linux。
深入阅读
关于本章内容的更多信息,请参考以下资源:
-
Linux 管理精通 – 第一版,Alexandru Calcatinge,Julian Balog
-
Snapcraft.io 官方文档:
snapcraft.io/docs -
Flatpak 文档:
docs.flatpak.org/en/latest/ -
openSUSE 官方文档:
doc.opensuse.org/
第四章:管理用户和用户组
Linux 是一个多用户、多任务的操作系统,这意味着多个用户可以同时访问操作系统,并共享平台资源,内核为每个用户并发独立地执行任务。Linux 提供了所需的隔离和安全机制,以防止多个用户访问或删除彼此的文件。
当多个用户访问系统时,权限将发挥作用。我们将学习如何使用 root 帐户,完全访问操作系统资源。
在这个过程中,我们将采取实践操作的方法来加深对关键概念的理解,通过实际示例进一步巩固所学内容。本章涵盖以下主题:
-
管理用户
-
管理用户组
-
管理权限
我们希望在本章结束时,你能熟练使用命令行工具来创建、修改和删除用户和用户组,同时熟练处理文件和目录权限。
让我们快速看一下本章的技术要求。
技术要求
你需要在 虚拟机(VM)或桌面平台上安装一个工作的 Linux 发行版。如果你还没有安装,可以参考 第一章,安装 Linux,它将引导你完成相关的过程。在本章中,我们将使用 Ubuntu 或 Fedora,但大部分命令和示例也适用于其他任何 Linux 平台。
管理用户
在这个上下文中,用户是指任何使用计算机或系统资源的人。最简单的形式下,Linux 用户 或 用户帐户 是通过一个名称和一个 唯一标识符(即 UID)来识别的。
从纯粹的技术角度来看,Linux 中有以下几种类型的用户:
-
普通(或常规)用户:通用的日常用户帐户,主要适用于个人使用和常见应用程序及文件管理任务,访问系统范围资源的权限有限。常规用户帐户通常具有 登录 shell 和 主目录。
-
root权限。因此,通过 web 服务器暴露的潜在漏洞将严格局限于相关系统帐户的操作范围。 -
root用户是超级用户的一个例子。
在 Linux 中,只有 root 用户或具有 sudo 权限的用户(sudoers)才能创建、修改或删除用户帐户。
理解 sudo
root 用户是 Linux 中的默认超级用户帐户,具有在系统上执行任何操作的能力。理想情况下,由于安全和保护原因,应该尽量避免以 root 身份在系统上操作。通过 sudo,Linux 提供了一种将普通用户帐户提升为超级用户权限的机制,并增加了安全层级。通过这种方式,通常使用 sudo 用户,而不是 root 用户。
sudo 是一个命令行工具,允许被授权的用户以超级用户或其他用户(取决于本地系统的安全策略)的权限执行命令。sudo 最初代表 superuser do,因为最初它仅作为超级用户使用,但后来扩展为支持不仅是超级用户,还有其他(受限)用户的身份模拟。因此,它也被称为 substitute user do。然而,由于它在 Linux 管理任务中的频繁使用,它通常仍然被看作是 superuser do。
大多数用于管理 Linux 用户的命令行工具需要 sudo 权限,除非相关任务是由 root 用户执行。如果我们想避免使用 root 上下文,在我们拥有超级用户权限的用户帐户之前,不能真正继续本章的其余内容——特别是创建用户。因此,让我们先解决这个“先有鸡还是先有蛋”的问题。
大多数 Linux 发行版在安装过程中除了 root 用户外,还会创建一个具有超级用户权限的附加用户帐户。如前所述,这是为了在进行高级操作时提供额外的安全保护。检查用户帐户是否具有 sudo 权限的最简单方法是,在终端中以相关用户帐户登录时运行以下命令:
sudo -v
根据 sudo 手册(man sudo),-v 选项会使 sudo 更新用户的缓存凭证,并在缓存凭证过期时重新认证用户。
如果用户(例如,julian)在本地机器上没有超级用户权限(例如,neptune),前面的命令将产生如下错误(或类似错误):
Sorry, user sudo command usually grants elevated permissions for a limited time. Ubuntu, for example, has a 15-minute sudo elevation span, after which time a sudo user would need to authenticate again. Subsequent invocations of sudo may not prompt for a password if done within the sudo cache credential timeout.
If we don’t have a default superuser account, we can always use the root context to create new users (see the next chapter) and elevate them to **sudoer** privileges. We’ll learn more about this in the *Creating a superuser* section, later in this chapter.
Now, let’s have a look at how to create, modify, and delete users.
Creating, modifying, and deleting users
In this section, we explore a few command-line tools and some common tasks for managing users. The example commands and procedures are shown for Ubuntu and Fedora, but the same principles apply to any other Linux distribution. Some user management `useradd` is not available on Alpine Linux, and `adduser` should be used instead). Please check the documentation of the Linux distribution of your choice for the equivalent commands.
Creating users
To create users, we can use either the `useradd` or the `adduser` command, although on some Linux distributions (for example, Debian or Ubuntu), the recommended way is to use the `adduser` command in favor of the low-level `useradd` utility. We’ll cover both in this section.
`adduser` is a Perl script using `useradd`—basically, a shim of the `useradd` command—with a user-friendly guided configuration. Both command-line tools are installed by default in Ubuntu and Fedora. Let’s take a brief look at each of these commands.
Creating users with useradd
The syntax for the `useradd` command is shown here:
useradd [OPTIONS] USER
In its simplest invocation, the following command creates a user account (`julian`):
sudo useradd julian
The user information is stored in a `/etc/passwd` file. Here’s the related user data for `julian`:
sudo cat /etc/passwd | grep julian
In our case, this is the output:

Figure 4.1 – The user record created with useradd
Let’s analyze the related user record. Each entry is delimited by a colon (`:`) and is listed here:
* `julian`: Username
* `x`: Encrypted password (password hash is stored in `/etc/shadow`)
* `1001`: The UID
* `1001`: The user **group** **ID** (**GID**)
* `::` The **General Electric Comprehensive Operating Supervisor** (**GECOS**) field—for example, display name (in our case, empty), explained later in this section
* `/home/julian`: User home folder
* `/bin/sh`: Default login shell for the user
Important note
The GECOS field is a string of comma-delimited attributes, reflecting general information about the user account (for example, real name, company, and phone number). In Linux, the GECOS field is the fifth field in a user record. See more information at [`en.wikipedia.org/wiki/Gecos_field`](https://en.wikipedia.org/wiki/Gecos_field).
We can also use the `getent` command to retrieve the preceding user information, as follows:
getent passwd julian
To view the UID (`uid`), GID (`gid`), and group membership associated with a user, we can use the `id` command, as follows:
id julian
This command gives us the following output:

Figure 4.2 – The UID information
With the simple invocation of `useradd`, the command creates the user (`julian`) with some immediate default values (as enumerated), while other user-related data is empty—for example, we have no full name or password specified for the user yet. Also, while the home directory has a default value (for example, `/home/julian`), the actual filesystem folder will not be created unless the `useradd` command is invoked with the `-m` or the `--create-home` option, as follows:
sudo useradd -m julian
Without a home directory, regular users would not have the ability to save their files in a private location on the system. On the other hand, some system accounts may not need a home directory since they don’t have a login shell. For example, a database server (for example, PostgreSQL) may run with a non-root system account (for example, `postgres`) that only needs access to database resources in specific locations (for example, `/var/lib/pgsql`), controlled via other permission mechanisms (for example, **Security-Enhanced** **Linux** (**SELinux**)).
For our regular user, if we also wanted to specify a full name (display name), the command would change to this:
sudo useradd -m -c "Julian" julian
The `-c, --comment` option parameter of `useradd` expects a *comment*, also known as the GECOS field (the fifth field in our user record), with multiple comma-separated values. In our case, we specify the full name (for example, `Julian`). For more information, check out the `useradd` manual (`man useradd`) or `useradd --help`.
The user still won’t have a password yet, and consequently, there would be no way for them to log in (for example, via a `julian`, we invoke the `passwd` command, like this:
sudo passwd julian
You can see the following output:

Figure 4.3 – Creating or changing the user password
The `passwd` command will prompt for the new user’s password. With the password set, there will be a new entry added to the `/etc/shadow` file. This file stores the secure password hashes (not the passwords!) for each user. Only superusers can access the content of this file. Here’s the command to retrieve the related information for the user `julian`:
sudo getent shadow julian
You can also use the following command:
sudo cat /etc/shadow | grep julian
The output of both commands is shown in the following screenshot:

Figure 4.4 – Information about the user from the shadow file
Once the password has been set, in normal circumstances, the user can log in to the system (via SSH or GUI). If the Linux distribution has a GUI, the new user will show up on the login screen.
As noted, with the `useradd` command, we have low-level granular control over how we create user accounts, but sometimes we may prefer a more user-friendly approach. Enter the `adduser` command.
Creating users with adduser
The `adduser` command is a Perl wrapper for `useradd`. The syntax for this command is shown here:
adduser [OPTIONS] USER
`sudo` may prompt for the superuser password. `adduser` will prompt for the new user’s password and other user-related information (as shown in *Figure 4**.5*).
Let’s create a new user account (`alex`) with `adduser`, as follows:
sudo adduser alex
The preceding command yields the following output:

Figure 4.5 – The adduser command
In Fedora, the preceding invocation of the `adduser` command will simply run without prompting the user for a password or any other information.
We can see the related user entry in `/etc/passwd` with `getent`, as follows:
getent passwd alex
The following is the output:

Figure 4.6 – Viewing user information with getent
In the preceding examples, we created a regular user account. Administrators or superusers can also elevate the privileges of a regular user to a superuser. Let’s see how in the following section.
Creating a superuser
When a regular user is given the power to run `sudo`, they become a superuser. Let’s assume we have a regular user created via any of the examples shown in the *Creating* *users* section.
Promoting the user to a superuser (or *sudoer*) requires a `sudo` group membership. In Linux, the `sudo` group is a reserved system group for users with elevated or `root` privileges. To make the user `julian` a sudoer, we simply need to add the user to the `sudo` group, like this (in Ubuntu):
sudo usermod -aG sudo julian
The `-aG` options of `usermod` instruct the command to append (`-a, --append`) the user to the specified group (`-G, --group`)—in our case, `sudo`.
To verify our user is now a sudoer, first make sure the related user information reflects the `sudo` membership by running the following command:
id julian
This gives us the following output:

Figure 4.7 – Looking for the sudo membership of a user
The output shows that the `sudo` group membership (GID) in the `groups` tag is `27(sudo)`.
To verify the `sudo` access for the user `julian`, run the following command:
su - julian
The preceding command prompts for the password of the user `julian`. A successful login would usually validate the superuser context. Alternatively, the user (`julian`) can run the `sudo -v` command in their terminal session to validate the `sudo` privileges. For more information on superuser privileges, see the *Understanding sudo* section earlier in the chapter.
With multiple users created, a system administrator may want to view or list all the users in the system. In the next section, we provide a few ways to accomplish this task.
Viewing users
There are a few ways for a superuser to view all users configured in the system. As previously noted, the user information is stored in the `/etc/passwd` and `/etc/shadow` files. Besides simply viewing these files, we can parse them and extract only the usernames with the following command:
cat /etc/passwd | cut -d: -f1 | less
Alternatively, we can parse the `/etc/shadow` file, like this:
sudo cat /etc/shadow | cut -d: -f1 | less
In the preceding commands, we read the content from the related files (with `cat`). Next, we piped the result to a delimiter-based parsing (with `cut`, on the `:` delimiter) and picked the first field `(-f1`). Finally, we chose a paginated display of the results, using the `less` command (to exit the command’s output, press *Q*).
Note the use of `sudo` for the `shadow` file since access is limited to superusers only, due to the sensitive nature of the password hash data. Alternatively, we can use the `getent` command to retrieve the user information.
The following command lists all the users configured in the system:
getent passwd
The preceding command reads the `/etc/passwd` file. Alternatively, we can retrieve the same information from `/etc/shadow`, as follows:
sudo getent shadow
For both commands, we can further pipe the `getent` output to `| cut -d: -f1` to list only the usernames, like this:
sudo getent shadow | cut -d: -f1 | less | column
The output will be similar to this:

Figure 4.8 – Viewing usernames
With new users created, administrators or superusers may want to change certain user-related information, such as password, password expiration, full name, or login shell. Next, we take a look at some of the most common ways to accomplish this task.
Modifying users
A superuser can run the `usermod` command to modify user settings, with the following syntax:
usermod [OPTIONS] USER
The examples in this section apply to a user we previously created (`julian`) with the simplest invocation of the `useradd` command. As noted in the previous section, the related user record in `/etc/passwd` has no full name for the user, and the user has no password either.
Let’s change the following settings for our user (`julian`):
* `Julian` (initially empty)
* `/local/julian` (from default `/home/julian`)
* `/bin/bash` (from default `/bin/sh`)
The command-line utility for changing all the preceding information is shown here:
sudo usermod -c "Julian" -d /local/julian -m -s /bin/bash julian
Here are the command options, briefly explained:
* `-c, --comment "Julian"`: The full username
* `-d, --home local/julian`: The user’s new home directory
* `-m, --move`: Move the content of the current home directory to the new location
* `-s, --shell /bin/sh`: The user login shell
The related change, retrieved with the `getent` command, is shown here:
getent passwd julian
We get the following output:

Figure 4.9 – The user changes reflected with getent
Here are a few more examples of changing user settings with the `usermod` command-line utility.
Changing the username
The `-l, --login` option parameter of `usermod` specifies a new login username. The following command changes the username from `julian` to `balog` (that is, first name to last name), as illustrated here:
sudo usermod -l "balog" julian
In a production environment, we may have to add to the preceding command, as we may also want to change the display name and the home directory of the user (for consistency reasons). In a previous example in the *Creating users with useradd* section, we showcased the `-d, --home` and `-m, --move` option parameters, which would accommodate such changes.
Locking or unlocking a user
A superuser or administrator may choose to temporarily or permanently lock a specific user with the `-L, --lock` option parameter of `usermod`, as follows:
sudo usermod -L julian
As a result of the preceding command, the login attempt for the user `julian` would be denied. Should the user try to SSH into the Linux machine, they would get a **Permission denied, please try again** error message. Also, the related username will be removed from the login screen if the Linux platform has a GUI.
To unlock the user, we invoke the `-U, --unlock` option parameter, as follows:
sudo usermod -U julian
The preceding command restores system access for the user.
For more information on the `usermod` utility, please check out the related documentation (`man usermod`) or the command-line help (`usermod --help`).
Although the recommended way of modifying user settings is via the `usermod` command-line utility, some users may find it easier to manually edit the `/etc/passwd` file. The following section shows you how.
Modifying users via /etc/passwd
A superuser can also manually edit the `/etc/passwd` file to modify user data by updating the relevant line. Although the editing can be done with a text editor of your choice (for example, `nano`), we recommend the use of the `vipw` command-line utility for a safer approach. `vipw` enables the required locks to prevent possible data corruption—for example, in case a superuser performs a change at the same time regular users change their password.
The following command initiates the editing of the `/etc/passwd` file by also prompting for the preferred text editor (for example, `nano` or `vim`):
sudo vipw
For example, we can change the settings for user `julian` by editing the following line:
julian❌1001:1001:Julian,,,:/home/julian:/bin/bash
The meaning of the colon (`:`)-separated fields was previously described in the *Creating users with useradd* section. Each of these fields can be manually altered in the `/etc/passwd` file, resulting in changes equivalent to the corresponding `usermod` invocation.
For more information on the `vipw` command-line utility, you can refer to the related system manual (`man vipw`).
Another relatively common administrative task for a user account is to change a password or set up a password expiration. Although `usermod` can change a user password via the `-p` or `--password` option, it requires an encrypted hash string (and not a cleartext password). Generating an encrypted password hash would be an extra step. An easier way is to use the `passwd` utility to change the password.
A superuser (administrator) can change the password of a user (for example, the user `julian`) with the following command:
sudo passwd julian
The output will ask for the new password for the respective user. To change the expiration time of a password (the password age), the `chage` command is used. For example, to set a 30-day password age for the user `julian`, we will use the following command:
sudo chage -M 30 julian
This will force the user `julian` to change their password every month. The password time availability is defined system-wide by the password policy. It is found inside the `/etc/login.defs` file, inside the `julian` as our example again):
sudo chage -d 0 julian
This command will force the user `julian` to enter their own password the first time they log in to the system.
Sometimes, administrators are required to remove specific users from the system. The next section shows a couple of ways of accomplishing this task.
Deleting users
The most common way to remove users from the system is to use the `userdel` command-line tool. The general syntax of the `userdel` command is shown here:
userdel [OPTIONS] USER
For example, to remove the user `julian`, a superuser would run the following command:
sudo userdel -f -r julian
Here are the command options used:
* `-f, --force`: Removes all files in the user’s home directory, even if not owned by the user
* `-r, --remove`: Removes the user’s home directory and mail spool
The `userdel` command removes the related user data from the system, including the user’s home directory (when invoked with the `-f` or `--force` option) and the related entries in the `/etc/passwd` and `/``etc/shadow` files.
There is also an alternative way, which could be handy in some odd cleanup scenarios. The next section shows how.
Deleting users via /etc/passwd and /etc/shadow
A superuser can edit the `/etc/passwd` and `/etc/shadow` files and manually remove the corresponding lines for the user (for example, `julian`). Please note that both files have to be edited for consistency and complete removal of the related user account.
Edit the `/etc/passwd` file using the `vipw` command-line utility, as follows:
sudo vipw
Remove the following line (for the user `julian`):
julian❌1001:1001:Julian,,,:/home/julian:/bin/bash
Next, edit the `/etc/shadow` file using the `-s` or `--shadow` option with `vipw`, as follows:
sudo vipw -s
Remove the following line (for the user `julian`):
julian:\(6\)xDdd7Eay/RKYjeTm$Sf.../:18519:0:99999:7:::
After editing the preceding files, a superuser may also need to remove the deleted user’s home directory, as follows:
sudo rm -rf /home/julian
For more information on the `userdel` utility, please check out the related documentation (`man userdel`) or the command-line help (`userdel --help`).
The user management concepts and commands learned so far apply exclusively to individual users in the system. When multiple users in the system have a common access level or permission attribute, they are collectively referred to as a group. Groups can be regarded as standalone organizational units we can create, modify, or delete. We can also define and alter user memberships associated with groups. The next section focuses on group management internals.
Managing groups
Linux uses groups to organize users. Simply put, a group is a collection of users sharing a common attribute. Examples of such groups could be *employees*, *developers*, *managers*, and so on. In Linux, a group is uniquely identified by a GID. Users within the same group share the same GID.
From a user’s perspective, there are two types of groups, outlined here:
* **Primary group**: The user’s initial (default) login group
* **Supplementary groups**: A list of groups the user is also a member of; also known as **secondary groups**
Every Linux user is a member of a primary group. A user can belong to multiple supplementary groups or no supplementary groups at all. In other words, there is one mandatory primary group associated with each Linux user, and a user can have multiple or no supplementary group memberships.
From a practical point of view, we can look at groups as a permissive context of collaboration for a select number of users. Imagine a *developers* group having access to developer-specific resources. Each user in this group has access to these resources. Users outside the *developers* group may not have access unless they authenticate with a group password if the group has one.
In the following section, we provide detailed examples of how to manage groups and set up group memberships for users. Most related commands require *superuser* or `sudo` privileges.
Creating, modifying, and deleting groups
While our primary focus remains on group administrative tasks, some related operations still involve user-related commands. Command-line utilities such as `groupadd`, `groupmod`, and `groupdel` are targeted strictly at creating, modifying, and deleting groups, respectively. On the other hand, the `useradd` and `usermod` commands carry group-specific options when associating users with groups. We’ll also introduce you to `gpasswd`, a command-line tool specializing in group administration, combining user- and group-related operations.
With this aspect in mind, let’s take a look at how to create, modify, and delete groups and how to manipulate group memberships for users.
Creating groups
To create a new group, a superuser invokes the `groupadd` command-line utility. Here’s the basic syntax of the related command:
groupadd [OPTIONS] GROUP
Let’s create a new group (`developers`), with default settings, as follows:
sudo groupadd developers
The group information is stored in the `/etc/group` file. Here’s the related data for the `developers` group:
cat /etc/group | grep developers
The command yields the following output:

Figure 4.10 – The group with default attributes
Let’s analyze the related group record. Each entry is delimited by a colon (`:`) and is listed here:
* `developers`: Group name
* `x`: Encrypted password (password hash is stored in `/etc/gshadow`)
* `1003`: GID
We can also use the `getent` command to retrieve the preceding group information, as follows:
getent group developers
A superuser may choose to create a group with a specific GID, using the `-g, --gid` option parameter with `groupadd`. For example, the following command creates the `developers` group (if it doesn’t exist) with a GID of `1200`:
sudo groupadd -g 1200 developers
For more information on the `groupadd` command-line utility, please refer to the related documentation (`man groupadd`).
Group-related data is stored in the `/etc/group` and `/etc/gshadow` files. The `/etc/group` file contains generic group membership information, while the `/etc/gshadow` file stores the encrypted password hashes for each group. Let’s take a brief look at group passwords.
Understanding group passwords
By default, a group doesn’t have a password when created with the simplest invocation of the `groupadd` command (for example, `groupadd developers`). Although `groupadd` supports an encrypted password (via the `-p, --password` option parameter), this would require an extra step to generate a secure password hash. There’s a better and simpler way to create a group password: by using the `gpasswd` command-line utility.
Important note
`gpasswd` is a command-line tool that helps with everyday group administration tasks.
The following command creates a password for the `developers` group:
sudo gpasswd developers
We get prompted to enter and re-enter a password, as illustrated here:

Figure 4.11 – Creating a password for the developers group
The purpose of a group password is to protect access to group resources. A group password is inherently insecure when shared among group members, yet a Linux administrator may choose to keep the group password private while group members collaborate unhindered within the group’s security context.
Here’s a quick explanation of how it works. When a member of a specific group (for example, `developers`) logs in to that group (using the `newgrp` command), the user is not prompted for the group password. When users who don’t belong to the group attempt to log in, they will be prompted for the group password.
In general, a group can have administrators, members, and a password. Members of a group who are the group’s administrators may use `gpasswd` without being prompted for a password, as long as they’re logged in to the group. Also, group administrators don’t need superuser privileges to perform group administrative tasks for a group they are the administrator of.
We’ll take a closer look at `gpasswd` in the next sections, where we further focus on group management tasks, as well as adding users to a group and removing users from a group. But for now, let’s keep our attention strictly at the group level and see how we can modify a user group.
Modifying groups
The most common way to modify the definition of a group is via the `groupmod` command-line utility. Here’s the basic syntax for the command:
groupmod [OPTIONS] GROUP
The most common operations when changing a group’s definition are related to the GID, group name, and group password. Let’s take a look at each of these changes. We assume our previously created group is named `developers`, with a GID of `1003`.
To change the GID to `1200`, a superuser invokes the `groupmod` command with the `-g, --gid` option parameter, as follows:
sudo groupmod -g developers to devops,我们调用-n, --new-name选项,如下所示:
sudo groupmod -n devops group with the following command:
getent group devops
The command yields the following output:

Figure 4.12 – Verifying the group changes
To change the group password for `devops`, the simplest way is to use `gpasswd`, as follows:
sudo gpasswd devops
We are prompted to enter and re-enter a password.
To remove the group password for `devops`, we invoke the `gpasswd` command with the `-r, --remove-password` option, as follows:
sudo gpasswd -r devops
As the command has no visible outcome or message, we will be prompted back to the shell:

Figure 4.13 – Setting a new group password and removing a group password
For more information on `groupmod` and `gpasswd`, refer to the system manuals of these utilities (`man groupmod` and `man gpasswd`), or simply invoke the `-h, --help` option for each.
Next, we look at how to delete groups.
Deleting groups
To delete groups, we use the `groupdel` command-line utility. The related syntax is shown here:
groupdel [OPTIONS] GROUP
By default, Linux enforces referential integrity between a primary group and the users associated with that primary group. We cannot delete a group that has been assigned as a primary group for some users before deleting the users of that primary group. In other words, by default, Linux doesn’t want to leave the users with dangling primary GIDs.
For example, when we first added the user `julian`, they were assigned automatically to the `julian` primary group. We then added the user to the `sudoers` group.
Let’s attempt to add the user `julian` to the `devops` group. A superuser may run the `usermod` command with the `-g, --gid` option parameter to *change* the primary group of a user. The command should be invoked for each user. Here’s an example of removing the user `julian` from the `julian` primary group. First, let’s get the current data for the user, as follows:
id julian
This is the output:

Figure 4.14 – Retrieving the current primary group for the user
Now, let us add the user `julian` to the `devops` group. The `-g, --gid` option parameter of the `usermod` command accepts both a *GID* and a group *name*. The specified group name must already be present in the system; otherwise, the command will fail. If we want to change the primary group (for example, to `devops`), we simply specify the group name in the `-g, --gid` option parameter, as follows:
sudo usermod -g devops julian
The output is shown in the following screenshot:

Figure 4.15 – Changing the primary group of the user
The result is that the user `julian` is now part of the `devops` group.
Now, let us attempt to delete the `devops` group, which is the primary group for the user `julian`. Attempting to delete the `devops` group results in an error, as can be seen in *Figure 4**.16* (the first command used). Therefore, we cannot delete a group that is not empty.
A superuser may choose to *force* the deletion of a primary group, invoking `groupdel` with the `-f, --force` option, but this would be ill advised. This is because the command would result in users with orphaned primary GIDs and a possible security hole in the system. The maintenance and removal of such users would also become problematic.
In order to be able to delete the `devops` group, we need to assign another group to the user `julian`. What we can do is assign it to the initial primary group called `julian`, and then attempt to delete the `devops` group, now that it is empty. First, let us assign the user `julian` to the `julian` group with the following command:
sudo usermod -g julian julian
At this point, it’s safe to delete the group (`devops`), as follows:
sudo groupdel devops
The outcome from the preceding commands is this:

Figure 4.16 – Successful attempt to delete the group
For more information on the `groupdel` command-line utility, check out the related system manual (`man groupdel`), or simply invoke `groupdel --help`.
Modifying groups via /etc/group
An administrator can also manually edit the `/etc/group` file to modify group data by updating the related line. Although the editing can be done with a text editor of your choice (for example, `nano`), we recommend the use of the `vigr` command-line utility for a safer approach. `vigr` is similar to `vipr` (for modifying `/etc/passwd`) and sets safety locks to prevent possible data corruption during concurrent changes of group data.
The following command opens the `/etc/group` file for editing by also prompting for the preferred text editor (for example, `nano` or `vim`):
sudo vigr
For example, we can change the settings for the `developers` group by editing the following line:
developers❌1200:julian,alex
When deleting groups using the `vigr` command, we’re also prompted to remove the corresponding entry in the group shadow file `(/etc/gshadow`). The related command invokes the `-s` or `--shadow` option, as illustrated here:
sudo vigr -s
For more information on the `vigr` utility, please refer to the related system manual (`man vigr`).
As with most Linux tasks, all the preceding tasks could have been accomplished in different ways. The commands chosen are the most common ones, but there might be cases when a different approach may prove more appropriate.
In the next section, we’ll take a glance at how to add users to primary and secondary groups and how to remove users from these groups.
Managing users in groups
So far, we’ve only created groups that have no users associated. There is not much use for empty user groups, so let’s add some users to them.
Adding users to a group
Before we start adding users to a group, let’s create a few groups. In the following example, we create the groups by also specifying their GID (via the `-g, --gid` option parameter of the `groupadd` command):
sudo groupadd -g 1100 admin
sudo groupadd -g 1200 developers
sudo groupadd -g 1300 devops
We can check the last groups created by using the following command:
cat /etc/group | tail -n 5
It will show us the last five lines of the `/etc/group` file. We can see the last five groups created.
Next, we create a couple of new users (`alex2` and `julian2` as we already have users `alex` and `julian`) and add them to some of the groups we just created. We’ll have the `admin` group set as the *primary group* for both users, while the `developers` and `devops` groups are defined as *secondary* (or *supplementary*) *groups*. The code can be seen here:
sudo useradd -g admin -G developers,devops alex2
sudo useradd -g admin -G developers,devops julian2
The `-g, --gid` option parameter of the `useradd` command specifies the (unique) primary group (`admin`). The `-G, --groups` option parameter provides a comma-separated list (without intervening spaces) of the secondary group names (`developers``,``devops`).
We can verify the group memberships for both users with the following commands:
id alex2
id julian2
The output is shown in the following screenshot:

Figure 4.17 – Assigning groups to new users
As we can see, the `gid` attribute shows the primary group membership: `gid=1100(admin)`. The `groups` attribute shows the supplementary (secondary) groups: `groups=1100(admin),1200(developers),1300(devops)`.
With users scattered across multiple groups, an administrator is sometimes confronted with the task of moving users between groups. The following section shows how to do this.
Moving and removing users across groups
Building upon the previous example, let’s assume the administrator wants to move (or add) the user `alex2` to a new secondary group called `managers`. Please note that, according to our previous examples, the user `alex2` has `admin` as the primary group and `developers`/`devops` as secondary groups (see the output of the `id alex2` command in *Figure 4**.18*).
Let’s create a `managers` group first, with GID `1400`. The code can be seen here:
sudo groupadd -g 1400 managers
Next, add our existing user, `alex2`, to the `managers` group. We use the `usermod` command with the `-G, --groups` option parameter to specify the secondary groups the user is associated with.
The simplest way to *append* a secondary group to a user is by invocation of the `-a, --append` option of the `usermod` command, as illustrated here:
sudo usermod -a -G managers alex2
The preceding command would preserve the existing secondary groups for the user `alex2` while adding the new `managers` group. Alternatively, we could run the following command:
sudo usermod -G developers,devops,managers alex2
In the preceding command, we specified multiple groups (with no intervening whitespace!).
Important note
We preserved the existing secondary groups (`developers`/`devops`) and *appended* to the comma-separated list the `managers` additional secondary group. If we only had the `managers` group specified, the user `alex2` would have been *removed* from the `developers` and `devops` secondary groups.
To verify whether the user `alex2` is now part of the `managers` group, run the following command:
id alex2
This is the output of the command:
uid=1004(alex2) gid=1100(admin) groups 属性(高亮显示)包括与管理组相关的条目:1400(managers)。
同样,如果我们想要移除用户alex2从developers和devops次要组,仅与managers次要组关联,我们将运行以下命令:
sudo usermod -G managers alex2
这是输出结果:

图 4.18 – 验证用户的次要组
groups标签现在显示主组admin(默认)和次要组managers。
删除用户alex2所有次要组的命令如下所示:
sudo usermod -G '' alex2
usermod命令的-G, --groups选项参数为空字符串(''),以确保用户没有关联任何次要组。我们可以通过以下命令验证用户alex2不再属于任何次要组:
id alex
这是输出结果:

图 4.19 – 验证用户没有次要组
如我们所见,groups标签仅包含1100(admin)主 GID,这是默认情况下每个用户都会显示的主组。
如果管理员选择将用户alex2从主组中移除或将其分配到不同的主组,他们必须运行usermod命令并使用-g, --gid选项参数,指定新的主组名称。每个用户都必须有一个主组,而且主组必须存在。
例如,要将用户alex2移到managers主组,管理员需要运行以下命令:
sudo usermod -g managers alex2
相关的用户数据可以使用以下命令获得:
id alex2
该命令输出如下:

图 4.20 – 验证用户已被分配到新的主组
用户记录中的gid属性在图 4.21中反映了新的主组:gid=1400(managers)。
如果管理员选择为用户alex2配置没有特定主组的情况,他们必须首先创建一个专用的组(方便起见,命名为alex2),并确保该组的 GID 与用户alex2的 UID(1004)匹配,如下所示:
sudo groupadd -g 1004 alex2
现在,我们可以通过指定刚才创建的专属主组(alex2)来将用户alex2从当前主组(managers)中移除,命令如下:
sudo usermod -g alex2 alex2
相关的用户记录变为:
id alex2
这是输出:

图 4.21 – 验证用户已从主组中移除
用户记录的gid属性反映了专属主组(与用户匹配):gid=1004(alex2)。我们的用户不再属于任何其他主组。
添加、移动和删除用户在组之间的操作可能成为 Linux 管理员日益棘手的任务。随时了解哪些用户属于哪些组,是非常有价值的信息,既用于报告目的,也用于用户自动化工作流。以下部分提供了一些查看用户和组数据的命令。
查看用户和组
本节将提供一些可能有用的命令,用于检索组和组成员信息。在进入具体命令之前,我们应当记住,组信息存储在/etc/group和/etc/gshadow文件中。两者中,前者包含我们最感兴趣的信息。
我们可以解析/etc/group文件来检索所有组,方法如下:
cat /etc/group | cut -d: -f1 | column | less
该命令产生以下输出:

图 4.22 – 检索所有组名
类似的命令可以使用getent,我们可以像这样使用:
getent group | cut -d: -f1 | column | less
上述命令的输出与图 4.22中显示的输出完全相同。我们可以使用以下命令检索单个组(例如,developers)的信息:
getent group developers
这是输出:

图 4.23 – 检索单个组的信息
上述命令的输出还显示了developers组的成员(julian2)。
要列出某个特定用户是哪些组的成员,我们可以使用groups命令。例如,以下命令列出用户alex是哪些组的成员:
groups alex
这是命令输出:

图 4.24 – 检索用户的组成员信息
上一个命令的输出显示了用户alex的组,首先是主组(alex)。
用户可以使用groups命令行工具检索自己的组成员信息,而无需指定组名。以下命令在用户packt的终端会话中执行,该用户也是管理员(超级用户):
groups
该命令的输出如下:

图 4.25 – 当前用户的组
还有许多其他方法和命令可以检索与用户和组相关的信息。我们希望前面的示例能为您提供一些关于如何查找这些信息的基本思路。
接下来,让我们看看用户如何切换或登录到特定的组。
组登录会话
当用户登录系统时,组成员身份上下文会自动设置为用户的主组。一旦用户登录,任何用户发起的任务(如创建文件或运行程序)都将与用户的主组成员权限相关联。用户还可以选择访问自己所属的其他组中的资源(即附加或次要组)。要切换组上下文或以新组成员身份登录,用户可以调用 newgrp 命令行工具。
newgrp 命令的基本语法如下:
newgrp GROUP
在以下示例中,我们假设用户(julian)是多个组的成员——admin 为主组,developers/devops 为次要组:
id julian
输出如下:

图 4.26 – 一个拥有多个组成员身份的用户
让我们暂时以 julian 用户身份进行操作。目前我们以 packt 用户登录。要切换到 julian 用户,我们将使用以下命令:
su julian
请记住,用户 julian 需要设置密码以进行身份验证。
当以 julian 用户登录时,默认的登录会话具有以下用户和组上下文:
whoami
在我们的例子中,输出如下:

图 4.27 – 获取当前用户
whoami 命令提供当前的 UID(有关该命令的更多细节,请使用 man whoami 或 whoami --help),输出如下:
groups
输出如下:

图 4.28 – 获取当前用户的组信息
groups 命令显示当前用户所属的所有组(有关该命令的更多细节,请使用 man groups 或 groups --help)。
用户还可以通过调用 id 命令查看其 ID(用户和 GID),输出如下:
id
输出如下:

图 4.29 – 查看当前用户和 GID 信息
id 命令有多种调用方式,可以提供当前用户和组会话的信息。以下命令(带有 -g, --group 选项)检索当前用户的组会话 ID:
id -g
1100
在我们的案例中,上述命令显示 1100——即用户的主要组对应的 GID,这个组是 admin(见 图 4.30 中的 gid 属性)。登录时,默认的组会话总是与用户对应的主要组。如果用户创建文件,举例来说,文件的权限属性将反映主要组的 ID。在 管理权限 部分中,我们将更详细地查看文件权限。
现在,让我们将当前用户的组会话切换为 developers,如下所示:
newgrp developers
当前组会话如下:
id -g
1200
GID 对应于 developers 的次要 GID,如 图 4.30 中的 groups 属性所显示的那样:1200(developers)。如果用户现在创建任何文件,相关的文件权限属性将具有 developers GID:

图 4.30 – 切换组会话
如果用户尝试登录到他们不是成员的组(例如 managers),newgrp 命令会提示输入 managers 组的密码:
newgrp managers
如果我们的用户拥有 managers 组的密码,或者他们是超级用户,则该组的登录尝试会成功。否则,用户将无法访问 managers 组的资源。
在此,我们总结管理用户和组的话题。本节中使用的相关行政任务示例无疑是全面的。在许多情况下,可以使用不同的命令或方法来实现相同的结果。
到目前为止,你应该已经相对熟练地管理用户和组,并且能够熟练使用各种命令行工具来操作相关的更改。用户和组是以关联的方式进行管理的,其中用户属于某个组,或者组与用户相关联。我们还学到,创建和管理用户及组需要超级用户权限。在 Linux 中,用户数据存储在 /etc/passwd 和 /etc/shadow 文件中,而组信息则存储在 /etc/group 和 /etc/gshadow 文件中。除了使用专用的命令行工具外,用户和组也可以通过手动编辑这些文件来修改。
接下来,我们将关注多用户组环境中的安全性和隔离上下文。在 Linux 中,相关功能通过一个系统级访问层来完成,该访问层控制特定用户和组对文件和目录的读、写和执行权限。
接下来的部分将探讨与这些权限相关的管理和行政任务。
管理权限
Linux 的一个关键原则是允许多个用户同时访问系统并执行独立任务。这个多用户、多任务的环境通过权限来控制其顺畅运行。Linux 内核提供了一个强大的框架,支撑着底层的安全性和隔离模型。在用户层面,专用的工具和命令行实用程序帮助 Linux 用户和系统管理员处理相关的权限管理任务。
对于一些 Linux 用户,尤其是初学者,Linux 权限有时可能会显得困惑。本节旨在解开一些关于 Linux 中文件和目录权限的关键概念。你将了解访问文件和目录的基本权限权利——读取、写入和执行权限。我们将探讨一些必要的管理任务,包括使用系统级命令行工具查看和更改权限。
本节讨论的大多数主题应当与用户和组密切相关。相关的表达可以简单到用户可以读取或更新文件,一个组可以访问这些文件和目录,或者用户可以执行 这个程序。
从基础开始,介绍文件和目录权限。
文件和目录权限
在 Linux 中,权限可以看作是对文件或目录的操作权利或特权。基本的权利,或称权限属性,如下所示:
-
读取:文件的读取权限允许用户查看文件内容。对于目录,读取权限允许用户列出目录中的内容。
-
写入:文件的写入权限允许用户修改文件内容。对于目录,写入权限允许用户通过添加、删除或重命名文件来修改目录内容。
-
cd命令)。
首先,我们来看看如何显示文件和目录的权限。
查看权限
查看文件或目录权限的最常用方法是使用ls命令行工具。该命令的基本语法如下:
ls [OPTIONS] FILE|DIRECTORY
以下是使用ls命令查看/etc/passwd文件权限的一个示例:
ls -l /etc/passwd
该命令的输出如下:
-rw-r--r-- 1 root root 2010 Mar 9 08:57 /etc/passwd
ls命令的-l选项提供了详细的输出,使用长列表格式,根据ls文档(man ls)。
让我们分析一下输出结果,如下所示:
-rw-r--r-- 1 root root 2010 Mar 9 08:57 /etc/passwd
我们有九个部分,用空格分隔(分隔符)。这些如下所示:
-
-rw-r--r--:文件访问权限 -
1:硬链接数量 -
root:文件的所有者用户 -
root:文件的所属组 -
2010:文件的大小 -
Mar:文件创建的月份 -
9:文件创建的日期 -
08:57:文件创建的时间 -
/etc/passwd:文件名
让我们来看看文件访问权限字段(-rw-r--r--)。文件访问权限定义为一个 10 个字符的字段,按如下方式分组:
-
第一个字符(属性)保留用于文件类型(参见文件 类型部分)。
-
接下来的 9 个字符表示一个 9 位字段,定义了有效权限,分为 3 组每组 3 个属性(位):用户所有者权限、组所有者权限和所有其他用户权限(参见权限 属性部分)。
让我们看看文件类型属性。
文件类型属性
这里列出了文件类型属性:
-
d:目录 -
-:常规文件 -
l:符号链接 -
p:命名管道—一种特殊文件,便于程序之间的通信 -
s:套接字—类似管道,但具有双向网络通信 -
b:块设备—与硬件设备对应的文件 -
c:字符设备—类似块设备
让我们更仔细地看看权限属性。
权限属性
如前所述,访问权限由一个 9 位字段表示,分为 3 组,每组 3 位,定义如下:
-
位 1-3:用户所有者权限
-
位 4-6:组所有者权限
-
位 7-9:所有其他用户(或世界)的权限
每个权限属性都是与相关的 3 位序列的二进制表示中的一个位标志。它们可以表示为字符或等效的数值,也就是八进制值,具体取决于它们所代表的位的范围。
这里是权限属性及其相应的八进制值:
-
r:读取权限;2 ^ 2 =4(位 2 设置) -
w:写入权限:2 ^ 1 =2(位 1 设置) -
x:执行权限:2 ^ 0 =1(位 0 设置) -
-:无权限:0(没有位被设置)
得到的校对数字也被称为文件权限的八进制值(参见文件权限示例部分)。以下是文件权限属性的示例:

图 4.31 – 文件权限属性
接下来,让我们来看一些例子。
文件权限示例
现在,让我们回头看看/etc/passwd的文件访问权限:-rw-r--r--,如下所示:
-
-:第一个字符(字节)表示文件类型(在我们的例子中是常规文件) -
rw-:接下来的三字符序列表示用户所有者权限;(在我们的例子中,读(r);写(w);八进制值 =4(r) +2(w) =6(rw)) -
r--:接下来的 3 字节序列定义了组所有者权限(在我们的例子中,读取(r);八进制值 =4(r)) -
r--:最后三个字符表示系统中所有其他用户的权限(在我们的例子中,读取(r);八进制值 =4(r))
根据前面的信息,/etc/passwd 文件的访问权限的八进制值为 644。另外,我们也可以通过 stat 命令查询八进制值,方法如下:
stat --format '%a' /etc/passwd
该命令的输出结果如下:
644
stat 命令显示文件或文件系统的状态。--format 选项指定以八进制格式('%a')输出访问权限。
这里有几个访问权限的例子,带有相应的八进制值和描述。三个字符的权限序列有意用空格分隔,以便更清晰地查看。前导的文件类型已省略:
-
rwx(777):所有用户,包括所有者、组和世界,均可读写执行 -
rwx r-x(755):所有用户可读执行;文件所有者具有写入权限 -
rwx r-x ---(750):所有者和组可读执行;所有者具有写入权限,其他用户没有访问权限 -
rwx --- ---(700):所有者可读写执行;其他人没有权限 -
rw- rw- rw-(666):所有用户可读写;没有执行权限 -
rw- rw- r--(664):所有者和组可读写;其他用户可读 -
rw- rw- ---(660):所有者和组可读写;其他没有权限 -
rw- r-- r--(644):所有者可读写;组和其他用户可读 -
rw- r-- ---(640):所有者可读写;组可读;其他没有权限 -
rw- --- ---(600):所有者可读写;组和其他没有权限 -
r-- --- ---(400):所有者可读;其他没有权限
读取、写入和执行是最常见的文件访问权限类型。到目前为止,我们主要关注权限类型及其表示方法。在接下来的部分,我们将探讨一些用于更改权限的命令行工具。
更改权限
修改文件和目录的访问权限是常见的 Linux 管理任务。在本节中,我们将了解一些在更改文件和目录的权限及所有权时非常方便的命令行工具。这些工具在任何现代 Linux 发行版中都会预安装,且其使用方式在大多数 Linux 平台中类似。
使用 chmod
chmod 命令是 change mode 的缩写,用于设置文件和目录的访问权限。chmod 命令可以由当前用户(所有者)和超级用户使用。
更改权限可以通过两种模式完成:相对模式和绝对模式。让我们来看一下它们的区别。
在相对模式下使用 chmod
在 相对 模式下更改权限可能是两者中最简单的一种。记住以下几点很重要:
-
我们改变权限的对象:
u= 用户(所有者),g= 组,o= 其他 -
我们如何改变权限:
+= 添加,-= 删除,== 恢复原状 -
我们更改哪个权限:
r= 读取,w= 写入,x= 执行
让我们来探讨几个在相对模式下使用 chmod 的例子。
在我们的第一个示例中,我们想要为所有其他(o)用户(世界)向myfile添加写(w)权限,如下所示:
chmod o+w myfile
相关的命令行输出如下所示:

图 4.32 – 为所有其他用户设置写权限
在下一个示例中,我们将为当前用户所有者(u)删除myfile的读取(r)和写入(w)权限,如下所示:
chmod u-rw myfile
命令行输出如下所示:

图 4.33 – 删除所有者的读写权限
在前面的例子中,我们没有使用sudo,因为我们以当前文件所有者(packt)的身份执行操作。
在以下示例中,我们假设myfile对所有人都有读取、写入和执行权限。然后,我们执行以下更改:
-
删除所有者(
u)的读取(r)权限 -
删除所有者(
u)和组(g)的写入(w)权限 -
为所有其他用户(
o)删除读取(r)、写入(w)和执行(x)权限
下面的代码片段展示了这一点:
chmod u-r,ug-w,o-rwx myfile
命令行输出如下所示:

图 4.34 – 以相对模式调用 chmod 的一个相对复杂的示例
接下来,让我们看看另一种更改权限的方法:通过指定与访问权限相对应的八进制数字,使用chmod命令行工具进行绝对模式的操作。
使用绝对模式的 chmod
chmod一次性更改所有权限属性,使用八进制数字。这种方法被称为绝对模式,因为它通过直接分配与访问权限相对应的八进制值来更改权限,而不参考现有的权限。
以下是有效权限对应的八进制值简要列表:
-
7rwx:读取、写入和执行 -
6rw-:读和写 -
5r-w:读取和执行 -
4r--:读取 -
3-wx:写和执行 -
2-``w-:写入 -
1--``x:执行 -
0---:无权限
在以下示例中,我们将myfile的权限更改为所有人可读(r)、可写(w)和可执行(x):
chmod 777 myfile
相关的更改通过以下命令行输出来展示:

图 4.35 – 以绝对模式调用 chmod
欲了解有关chmod命令的更多信息,请参阅相关文档(man chmod)。
现在让我们来看下一个命令行工具,它专门用于文件和目录所有权的更改。
使用 chown
chown 命令(即 更改所有者)用于设置文件和目录的所有权。通常,chmod 命令只能在 超级用户 权限下运行(也就是由 sudoer 执行)。普通用户只能更改其文件的 组 所有权,而且只有在他们是目标组的成员时才行。
chown 命令的语法如下所示:
chown [OPTIONS] [OWNER][:[GROUP]] FILE
通常,我们使用 chown 命令同时设置用户 和 组所有权,例如如下所示:
sudo chown julian:developers myfile
相关的命令行输出如下所示:

图 4.36 – 简单调用 chown 命令
chown 最常见的用途之一是以 递归模式 调用,使用 -R, --recursive 选项。以下示例将 mydir(目录)中所有文件的所有权权限从最初由 root 拥有更改为 julian:
sudo chown -R julian:julian mydir/
相关的更改显示在以下命令行输出中:

图 4.37 – 以递归模式调用 ls 和 chown
如需了解更多关于 chown 命令的信息,请参考相关文档(man chown)。
接下来,让我们简要了解一下专门用于更改组所有权的类似命令行工具。
使用 chgrp
chgrp 命令(即 更改组)用于更改文件和目录的 组 所有权。在 Linux 中,文件和目录通常属于一个用户(所有者)或一个组。我们可以使用 chown 命令行工具设置用户所有权,而组所有权则可以通过 chgrp 设置。
chgrp 的语法如下所示:
chgrp [OPTIONS] GROUP FILE
以下示例将 myfile 的组所有权更改为 developers 组:
sudo chgrp developers myfile
更改显示在以下输出中:

图 4.38 - 使用 chgrp 更改组所有权
由于当前用户(packt)不是 developers 组的管理员,所以下面的命令以超级用户权限(sudo)调用。
如需了解更多关于 chgrp 工具的信息,请参考工具的命令行帮助(chgrp --help)。
使用 umask
umask 命令用于查看或设置系统中的默认 文件模式掩码。文件模式表示用户创建的任何新文件和目录的默认权限。例如,Ubuntu 中的默认文件模式掩码如下所示:
-
0002用于普通用户 -
0022用于root用户
在 Linux 中的一般规则是,新的文件和目录的 默认权限 按照以下公式计算:
-
0666 – umask:对于普通用户创建的新文件 -
0777 – umask:对于普通用户创建的新目录
根据前述公式,在 Ubuntu 中,我们有以下默认权限:
-
文件(普通用户):
0666 – 0002 =0664 -
文件(
root):0666 – 0022 =0644 -
目录(普通用户):
0777 – 0002 =0775 -
目录(
root):0777 – 0022 =0755
在以下示例中,我们在 Ubuntu 上运行,创建了一个文件(myfile)和一个目录(mydir),使用的是普通用户(packt)的终端会话。然后,我们查询每个文件的 stat 命令,并验证默认权限是否与之前列出的普通用户权限(文件:664,目录:775)匹配。
让我们首先从默认文件权限开始,如下所示:
touch myfile2
stat --format '%a' myfile2
相关输出如下所示:
664
接下来,让我们验证默认目录权限,如下所示:
mkdir mydir2
stat --format '%a' mydir2
相关输出如下所示:
775
这是 Linux 系统中文件和目录的典型 umask 值列表:

图 4.39 – Linux 上典型的 umask 值
关于 umask 工具的更多信息,请参阅该工具的命令行帮助(umask --help)。
文件和目录权限对于安全环境至关重要。用户和进程应仅在由权限控制的隔离和安全约束内操作,以避免无意或故意干扰系统资源的使用和所有权。在一些情况下,特别是在用户伪装的情况下,访问权限可能涉及一些特殊的权限属性。让我们来看看它们。
特殊权限
在 Linux 中,文件和目录的所有权通常由创建它们的用户或用户组的 UID 和 GID 决定。相同的原则适用于应用程序和进程——它们由启动它们的用户所拥有。特殊权限旨在在需要时更改这种默认行为。
这是一些特殊权限标志及其相应的八进制值:
-
setuid:2 ^ 2 =4(位 2 设置) -
setgid:2 ^ 1 =2(位 1 设置) -
sticky:2 ^ 0 =1(位 0 设置)
当这些特殊位中的任何一个被设置时,访问权限的整体八进制数字将有一个额外的数字,前导(高位)数字对应于特殊权限的八进制值。
让我们看看这些特殊权限标志,并为每个标志提供示例。
setuid 权限
设置了 setuid 位时,当可执行文件启动时,它将以文件所有者的权限运行,而不是启动它的用户的权限。例如,如果可执行文件由 root 拥有并由普通用户启动,它将以 root 权限运行。setuid 权限在不当使用时可能会带来潜在的安全风险,或者当底层进程的漏洞被利用时。
在文件访问权限字段中,setuid 位可以有以下几种表示方式:
-
s替换 相应的可执行位(x)(当可执行位存在时) -
S(大写字母)表示不可执行文件
setuid 权限可以通过以下 chmod 命令设置(例如,对于 myscript.sh 可执行文件):
chmod u+s myscript.sh
结果文件权限如下所示(包括八进制值):-rwsrwxr-x (4775)。
下面是相关的命令行输出:

图 4.40 – setuid 权限
在前面的截图中,你可以看到权限的差异。在应用chmod命令之前,权限为-rwxrwxr-x,在应用了setuid权限的chmod命令之后,用户权限中包含一个s(表示setuid),变为-rwsrwxr-x。关于setuid的更多信息,请访问docs.oracle.com/cd/E19683-01/816-4883/secfile-69/index.html 或参考chmod命令行工具文档(man chmod)。
setgid 权限
虽然setuid控制用户伪装权限,但setgid对组伪装权限有类似的作用。
当一个可执行文件设置了setgid位时,它将使用文件所属组的权限运行,而不是启动它的用户的组权限。换句话说,进程的 GID 与文件的 GID 相同。
当setgid用于目录时,它会改变默认的所有权行为,使得在目录内创建的文件将继承父目录的组所有权,而不是与创建它们的用户相关联的组。这种行为在文件共享场景中是足够的,当文件可以由父目录所有组中的所有用户更改时。
setgid权限可以通过以下chmod命令设置(例如,对于myscript.sh可执行文件,应用setuid之前的原始文件):
chmod g+s myscript.shw
结果文件权限如下所示(包括八进制值):-rwxrwsr-x (2775)。
命令行输出如下所示:

图 4.41 – setgid 权限
关于setgid的更多信息,请访问en.wikipedia.org/wiki/Setuid 或参考chmod命令行工具文档(man chmod)。
sticky 权限
sticky位对文件没有影响。对于具有sticky权限的目录,只有目录的用户所有者或组所有者可以删除或重命名该目录中的文件。通过用户或组所有权具有写入权限的用户或组,不能删除或修改目录中的文件。sticky权限在目录由一个特权组拥有,并且该组成员共享对目录中文件的写入访问权限时非常有用。
sticky权限可以通过以下chmod命令设置(例如,对于mydir目录):
chmod +t mydir
结果目录权限如下所示(包括八进制值):drwxrwxr-t (1775)。
命令行输出如下所示:

图 4.42 – 粘滞权限
有关sticky的更多信息,请访问en.wikipedia.org/wiki/Setuid或查阅chmod命令行工具文档(man chmod)。
解读权限可能是一项艰巨的任务。本节旨在解开一些相关的复杂性,我们希望你在处理日常 Linux 管理任务中的文件和目录权限时会更加得心应手。
总结
在本章中,我们探讨了与 Linux 中用户和组管理相关的一些基本概念。我们了解了文件和目录权限,以及多用户环境中的不同访问级别。对于每个主要主题,我们重点介绍了基本的管理任务,提供了各种实际示例,并使用典型的命令行工具来执行日常的用户访问和权限管理操作。
管理用户和组,以及相关的文件系统权限,是 Linux 管理员必备的技能。我们希望,本章获得的知识能帮助你踏上成为熟练超级用户的道路。
在接下来的章节中,我们将继续探索 Linux 内部的掌握之旅,探讨进程、守护进程和进程间通信(IPC)机制。一个需要记住的重要方面是,进程和守护进程也由用户或组拥有。本章学习的技能将帮助我们在查看系统中任何时刻谁运行了什么时,顺利掌握相关领域。
问题
以下是总结本章主要内容的一些想法和问题:
- 什么是超级用户?
sudo
- 想想创建用户的命令行工具,你还能想到其他的吗?
adduser 和 useradd
-rw-rw-r—访问权限的八进制值是什么?
r,w,和 x 分别是:4,2 和 1
-
主组和辅助(附加)组之间有什么区别?
-
如何更改用户主目录的所有权?
-
你能在不删除用户的主目录的情况下将其从系统中移除吗?怎么做?
进一步阅读
以下是一些可以帮助你进行用户管理的 Packt 书籍:
-
Mastering Ubuntu Server – 第四版,Jay LaCroix
-
Red Hat Enterprise Linux 9 管理 – 第二版,Pablo Iranzo Gómez,Pedro Ibáñez Requena,Miguel Pérez Colino 和 Scott McCarty
第五章:工作与进程、守护进程和信号
Linux 是一种多任务操作系统。多个程序或任务可以并行运行,每个都有自己的身份、调度、内存空间、权限和系统资源。进程封装了任何这种程序的执行上下文。理解进程如何工作并相互通信是任何经验丰富的 Linux 系统管理员和开发人员必备的重要技能。
本章探讨了 Linux 进程背后的基本概念。我们将研究不同类型的进程,如前台和后台进程,特别强调守护进程作为一种特定类型的后台进程。我们将深入研究进程的解剖结构以及 Linux 中的各种进程间通信机制 —— 特别是信号。在此过程中,我们还将了解一些管理进程和守护进程、处理信号的基本命令行实用工具。我们还会在本书中首次介绍脚本,稍后在第八章,Linux Shell 脚本编程中详细描述。如果您在处理本章脚本时需要更多信息,请提前参阅第八章。
在本章中,我们将涵盖以下主题:
-
介绍进程
-
工作与进程
-
工作与守护进程
-
探索进程间通信
重要提示
在内容导航过程中,我们将偶尔提前引用信号,在本章后半部分正式介绍之前。在 Linux 中,信号几乎只与进程关联,因此我们首先熟悉进程的方法。然而,从某些进程内部省略信号会对理解进程如何工作造成不利影响。提到信号时,我们会指向相关部分以供进一步参考。我们希望这种方法能让您更好地掌握整体和进程、守护进程的内部工作方式。
在我们开始之前,让我们先看一下我们学习所需的基本要求。
技术要求
熟能生巧。通过手动运行本章中的命令和示例,您将更好地了解进程的工作原理。与本书中的任何章节一样,我们建议您在虚拟机或 PC 桌面平台上安装工作中的 Linux 发行版。我们将使用 Ubuntu 或 Fedora,但大多数命令和示例在任何其他 Linux 平台上都会类似。
介绍进程
进程代表程序的运行实例。通常,程序是一组指令和数据,编译为可执行单元。当程序运行时,将创建一个进程。换句话说,进程只是一个活动中的程序。进程执行特定任务,有时也被称为作业(或任务)。
创建或启动进程的方式有很多。在 Linux 中,每个命令都会启动一个进程。命令可以是终端会话中用户启动的任务、脚本或手动或自动调用的程序(可执行文件)。
通常,进程的创建方式以及它如何与系统(或用户)交互决定了它的进程类型。让我们更仔细地看一下 Linux 中的不同类型的进程。
理解进程类型
从高层次来看,Linux 中有两种主要的进程类型:
-
前台(交互式)
-
后台(非交互式或自动化)
交互式进程假定在进程生命周期内会进行某种用户交互。非交互式进程是无人值守的,这意味着它们要么是自动启动的(例如,在系统启动时),要么是通过作业调度程序在特定时间和日期安排运行的(例如,使用at和cron命令行工具)。
我们探索进程类型的方法主要围绕上述分类展开。关于进程定义,还有其他多种视角或分类法,但最终可以归结为前台进程或后台进程。
例如,批处理进程和守护进程本质上是后台进程。批处理进程是自动化的,因为它们不是由用户生成的,而是由调度任务触发的。守护进程是后台进程,通常在系统启动时启动并无限期运行。
还有父进程和子进程的概念。父进程可以创建其他从属的子进程。
我们将在接下来的章节中详细介绍这些类型(及其他类型)。让我们从关键的进程类型——前台进程和后台进程开始。
前台进程
stdout或stderr)或接受用户输入。前台进程的生命周期与终端会话(父进程)紧密相关。如果启动前台进程的用户在进程仍在运行时退出终端,该进程将被突然终止(通过父进程发送的SIGHUP信号;有关详细信息,请参阅信号部分中的进程间通信探索)。
前台进程的一个简单例子是调用系统参考手册(man)来查看某个 Linux 命令(例如,ps):
man ps
ps命令显示有关活动进程的信息。你将在处理进程章节中学习更多关于进程管理工具和命令行实用程序的内容。
一旦前台进程启动,用户提示符将被捕获并由新启动的进程界面控制。用户在交互式进程放弃对终端会话的控制之前,无法与初始命令提示符进行交互。
让我们看另一个前台进程的例子,这次调用一个长期运行的任务。以下命令(单行命令)会在显示任意消息的同时,运行一个无限循环,每几秒钟更新一次:
while true; do echo "Wait..."; sleep 5; done
只要命令没有被中断,用户将不会在终端中看到交互式提示符。使用Ctrl + C会停止(中断)相关前台进程的执行,并返回一个响应式命令提示符:

图 5.1 – 一个长时间运行的前台进程
重要提示
当你在前台进程运行时按下Ctrl + C,当前(父)终端会话会向运行中的进程发送SIGINT信号,从而中断前台进程。有关更多信息,请参阅信号部分。
如果我们希望在运行特定命令或脚本时保持终端会话中的交互式命令提示符,我们应当使用后台进程。
后台进程
后台进程—也称为非交互式进程或自动进程—独立于终端会话运行,不需要任何用户交互。用户可以在同一个终端会话中启动多个后台进程,而无需等待它们完成或退出。
后台进程通常是长时间运行的任务,不需要直接的用户监督。相关进程仍然可以在终端控制台中显示其输出,但这类后台任务通常将结果写入不同的文件中(例如日志文件)。
最简单的后台进程调用是在相关命令的末尾添加符号(&)。基于我们之前在前台进程部分中的示例,以下命令创建了一个后台进程,该进程运行一个无限循环,每隔几秒钟回显一个任意的消息:
while true; do echo "Wait..."; sleep 10; done &
请注意命令末尾的符号(&)。默认情况下,通过符号(&)调用的后台进程仍然会将输出(stdout和stderr)发送到控制台,如前所示。然而,终端会话保持交互式。在下图中,我们在之前的进程仍然运行时使用了echo命令:

图 5.2 – 运行后台进程
如前面的截图所示,后台进程被分配了983。在进程运行时,我们仍然可以控制终端会话并执行不同的命令,像这样:
echo "Interactive prompt..."
最终,我们可以使用kill命令强制终止该进程:
kill -9 983
上述命令杀死了我们的后台进程(PID 为983)。父终端会话通过命令中的-9参数发送的信号是SIGKILL(有关更多信息,请参见信号部分),用于终止该进程。
前台和后台进程通常都在用户的直接控制下。换句话说,这些进程是通过命令或脚本的调用手动创建或启动的。虽然如此,也有一些例外,特别是在批处理进程的情况下,这些进程会通过计划任务自动启动。
还有一类选择性的后台进程,它们会在系统启动时自动启动,并在关机时终止,无需用户干预。这些后台进程也被称为守护进程。
介绍守护进程
一个 root 用户(或其他)以相关的权限运行。
守护进程通常用于服务客户端请求或与其他前台或后台进程进行通信。以下是一些常见的守护进程示例,它们通常在大多数 Linux 平台上可用:
-
systemd:所有进程的父进程(以前称为init) -
crond:一个在后台运行任务的作业调度器 -
ftpd:一个处理客户端 FTP 请求的 FTP 服务器 -
httpd:一个处理客户端 HTTP 请求的 Web 服务器(Apache) -
sshd:一个处理 SSH 客户端请求的安全外壳服务器
通常,Linux 系统中的守护进程名称以 d 结尾,表示它是一个守护进程。守护进程由通常存储在 /etc/init.d/ 或 /lib/systemd/ 系统目录中的 Shell 脚本控制,这取决于 Linux 平台。例如,Ubuntu 将守护进程脚本文件存储在 /etc/init.d/ 中,而 Fedora 将它们存储在 /lib/systemd/ 中。这些守护进程文件的位置取决于 init 的平台实现,它是一个针对所有 Linux 进程的系统级服务管理器。
Linux 的 init 风格启动过程通常会在系统启动时调用这些 Shell 脚本。但是,通常通过服务控制命令也可以调用这些脚本,这些命令通常由拥有特权的系统用户执行,以管理特定守护进程的生命周期。换句话说,特权用户或系统管理员可以通过命令行界面来停止或启动某个特定的守护进程。此类命令会在执行相关操作的同时立即将用户的控制权交还给终端,并在后台执行相关任务。
让我们更深入地了解 init 进程。
init 进程
在本章中,我们将 init 称为 Linux 平台上的通用系统初始化引擎和服务管理器。多年来,Linux 发行版经历了各种 init 系统实现的演变,比如 SysV、upstart、OpenRC、systemd 和 runit。Linux 社区关于这些实现的优劣存在持续的辩论。现在,我们将 init 简单地看作一个系统进程,并简要了解它与其他进程的关系。
init(或 systemd 等)本质上是一个系统守护进程,它是 Linux 启动时最早启动的进程之一。相关的守护进程将持续在后台运行,直到系统关闭。init 是所有其他进程的根(父)进程,处于整个进程层次结构的树顶。换句话说,它是系统中所有进程的直接或间接祖先。
在 Linux 中,pstree 命令显示整个进程树,且在树的根部显示 init 进程——在我们的案例中是 systemd(在 Ubuntu 或 Fedora 上)。
上述命令的输出可以在以下截图中看到:

图 5.3 – init(systemd),所有进程的父进程
pstree 命令的输出展示了进程的层次结构,其中一些进程作为父进程出现,而其他进程则作为子进程出现。我们来看看父进程和子进程的类型以及它们之间的一些动态。
父进程和子进程
一个由父进程在终止时触发的 SIGHUP 信号(例如,通过 nohup 命令)。更多信息请参见 信号 部分。
在 Linux 中,除了 init 进程(及其变种)外,所有进程都是某个特定进程的子进程。终止子进程不会停止相关的父进程的运行。当子进程完成处理后,终止父进程的一个良好做法是让父进程本身退出。
有时进程会根据特定的计划无须人工干预地运行。没有用户交互的进程称为批处理进程。接下来,我们将讨论批处理进程。
批处理进程
at 和 cron。cron 更适合复杂的定时任务管理,而 at 是一个轻量级的工具,更适合一次性任务。对这些命令的详细研究超出了本章的范围。你可以参考相关的系统参考手册以获取更多信息(man at 和 man cron)。
我们将以孤儿进程和僵尸进程结束对进程类型的研究。
孤儿进程和僵尸进程
当子进程被终止时,相关的父进程会收到一个 SIGCHILD 信号。父进程可以继续执行其他任务,或选择生成另一个子进程。然而,也有可能父进程在相关子进程完成执行(或退出)之前就被终止。在这种情况下,子进程会变成 init 进程——所有进程的父进程——自动成为孤儿进程的新父进程。
ps 命令)。
僵尸进程和孤儿进程的主要区别在于,僵尸进程是已死(终止)的进程,而孤儿进程仍在运行。
当我们区分不同的进程类型及其行为时,相关信息的一个重要部分反映在进程本身的组成或数据结构中。在下一节中,我们将更深入地了解进程的构成,这主要通过 ps 命令行工具来体现——这是 Linux 系统上一个普通但非常有用的进程查看器。
进程的结构
本节中,我们将通过 ps 和 top 命令行工具,探讨 Linux 进程的一些常见属性。我们希望通过这些工具的实际应用,帮助你更好地理解进程的内部机制,至少从 Linux 管理员的角度来看。我们先简单了解一下这些命令。ps 命令显示系统进程的当前快照。此命令的语法如下:
ps [OPTIONS]
以下命令显示当前终端会话拥有的进程:
ps
前面的命令输出可以在下面的截图中看到:

图 5.4 – 显示当前 shell 拥有的进程
让我们来看看输出的顶部(标题)行中的每个字段,并解释它们在我们相关进程——也就是 bash 终端会话中的含义:
PID:在 Linux 中,每个进程都有一个由内核自动分配的PID值。PID值是一个正整数,并且始终保证是唯一的。
在我们的示例中,相关的进程是 bash(当前 shell),其 PID 为 171233。
-
TTY:TTY属性表示进程与之交互的终端类型。在我们的示例中,代表终端会话的bash进程的TTY类型是pts/0。pts代表/0表示相关终端会话的序列号。例如,额外的 SSH 会话将会有pts/1,依此类推。 -
TIME:TIME字段表示该进程所消耗的累计 CPU 使用时间(以[DD-]hh:mm:ss格式显示)。为什么在我们的示例中,bash进程的TIME是零(00:00:00)?我们可能在终端会话中运行了多个命令,但 CPU 使用时间仍然为零。这是因为 CPU 使用时间是针对每个命令所消耗的时间进行测量(并累积),而不是针对整个父终端会话。如果命令在几分之一秒内完成,那么它们所消耗的 CPU 时间不会在TIME字段中显示出显著的数值。 -
CMD:CMD字段代表命令,指示创建该进程的命令的名称或完整路径(包括参数)。对于常见的系统命令(例如,bash),CMD会显示命令名称,包括其参数。
到目前为止,我们探讨的进程属性代表了 Linux 进程的相对简单视图。但有时我们可能需要更多的信息。例如,以下命令提供了当前终端会话中运行进程的额外详细信息:
ps -l
-l 选项参数调用了所谓的 长格式 以显示 ps 的输出:

图 5.5 – 进程的更详细视图
以下是 ps 命令的一些更相关的输出字段:
-
F:进程标志(例如,0– 无,1– 已分叉,4– 超级用户权限) -
S:进程状态代码(例如,R– 运行中,S– 可中断休眠,等等) -
UID:进程的用户名或所有者(用户 ID) -
PID:进程 ID -
PPID:父进程的进程 ID -
PRI:进程的优先级(数字越大表示优先级越低) -
SZ:虚拟内存使用量
还有许多其他这样的属性,探索它们超出了本书的范围。如需更多信息,请参考 ps 系统参考手册(man ps)。
迄今为止,我们使用的 ps 命令示例只显示了当前终端会话所拥有的进程。我们认为这种方式有助于减少分析进程属性时的复杂度。
除了 ps,另一个常用命令是 top,它提供了系统中所有运行进程的实时(动态)视图。其语法如下:
top [OPTIONS]
ps 命令显示的许多进程输出字段也在 top 命令中有所体现,尽管有些字段的表示方式略有不同。让我们来看看 top 命令以及显示的输出字段的含义。以下命令显示正在运行进程的实时视图:
top
上述命令的输出可以在以下截图中看到:

图 5.6 – 当前进程的实时视图
下面是一些输出字段的简要说明:
-
USER:进程的用户名或所有者 -
PR:进程的优先级(数字越小表示优先级越高) -
NI:进程的 nice 值(一种动态/自适应的优先级) -
VIRT:虚拟内存大小(以 KB 为单位)– 进程使用的总内存 -
RES:常驻内存大小(以 KB 为单位)– 进程使用的物理内存(非交换内存) -
SHR:共享内存大小(以 KB 为单位)– 进程与其他进程共享的内存子集 -
S:进程的状态(例如,R– 运行中,S– 可中断休眠,I– 空闲,等等) -
%CPU:CPU 使用率(百分比) -
%MEM:RES内存使用率(百分比) -
COMMAND:命令名称或命令行
这些字段(以及更多字段)在 top 系统参考手册中有详细说明(man top)。
每天,Linux 管理任务经常使用基于上述字段的与进程相关的查询。处理进程部分将探讨ps和top命令的一些更常见用法,以及更多内容。
进程生命周期的一个重要方面是ps和top命令通过S字段提供有关进程状态的信息。让我们更仔细地查看这些状态。
进程状态
在其生命周期内,进程可能会根据情况改变状态。根据ps和top命令的S(状态)字段,Linux 进程可以具有以下任一状态:
-
D: 不可中断的睡眠 -
I: 空闲 -
R: 运行 -
S: 睡眠(可中断的睡眠) -
T: 被作业控制信号停止 -
t: 在跟踪期间被调试器停止 -
Z: 僵尸
在高层次上,可以将这些状态识别为以下过程状态:
-
R状态)或是空闲进程(I状态)。在 Linux 中,空闲进程是分配给系统中每个处理器(CPU)的特定任务,只有在相关 CPU 上没有其他进程运行时才会被调度运行。在空闲任务上花费的时间占了top命令报告的空闲时间。 -
S状态)和不可中断的睡眠(D状态)。可中断的睡眠可以通过特定的进程信号中断,从而产生进一步的进程执行。另一方面,不可中断的睡眠是一个进程被阻塞在系统调用中的状态(可能在等待某些硬件条件),它无法被中断。 -
T状态)或是调试信号(t状态)。 -
Z状态)- 它在没有被其父进程回收的情况下终止。僵尸进程本质上是系统进程表中已终止进程的死引用。这将在孤儿和僵尸进程部分详细讨论。
要总结我们对进程状态的分析,让我们来看一下 Linux 进程的生命周期。通常,进程从运行状态(R)开始,并在其父进程从僵尸状态(Z)中回收它后终止。下图提供了进程状态的简略视图及其之间可能的转换:

图 5.7 - Linux 进程的生命周期
现在我们已经介绍了进程,并为您提供了它们类型和结构的初步概念,我们准备与它们进行交互。在接下来的章节中,我们将探讨一些用于处理进程和守护程序的标准命令行实用工具。这些工具大多数操作输入和输出数据,我们在进程的解剖部分已经涵盖了这些。接下来我们将看看如何处理进程。
处理进程
本节作为通过资源丰富的命令行工具来管理进程的实用指南,这些工具用于日常的 Linux 管理任务。在之前的章节中我们提到了一些工具(例如 ps 和 top),当时我们讲解了特定进程的内部结构。在这里,我们将调用到目前为止我们收集的大部分知识,并通过一些实际的示例来实际应用它们。
让我们从 ps 命令开始——Linux 进程探测器。
使用 ps 命令
我们在进程结构部分描述了 ps 命令及其语法。以下命令显示当前系统中运行的一些进程:
ps -e | head
-e 选项(或 -A)选择系统中的所有进程。head 管道调用仅显示前几行(默认显示 10 行):

图 5.8 – 显示前几个进程
上述信息可能并不总是特别有用。也许我们想了解更多关于每个进程的内容,而不仅仅是 PID 或 CMD 字段在 ps 命令输出中的内容。(我们在进程结构部分描述了一些进程的属性)。
以下命令更详细地列出了当前用户拥有的进程:
ps -fU $(whoami)
-f 选项指定完整格式的列表,显示每个进程的更多详细信息。-U $(whoami) 选项指定当前用户(packt)作为我们想要检索的进程的实际用户(所有者)。换句话说,我们想列出我们拥有的所有进程:

图 5.9 – 显示当前用户拥有的进程
有时我们可能需要查找特定的进程,无论是为了监控目的还是对它们进行操作。让我们回顾之前的例子,其中我们展示了一个长期运行的进程,并将相关命令包装成一个简单的脚本。这个命令是一个简单的 while 循环,它会无限期地运行:
while true; do x=1; done
使用我们偏好的编辑器(例如 nano),我们可以创建一个脚本文件(例如 test.sh),并编写以下内容:

图 5.10 – 一个简单的测试脚本无限运行
我们可以使测试脚本可执行并将其作为后台进程运行:
chmod +x test.sh
./test.sh &
注意命令末尾的与号(&),它用于启动后台进程:

图 5.11 – 作为后台进程运行脚本
运行我们脚本的后台进程的进程 ID(PID)是 1094。假设我们想通过进程名称(test.sh)来查找我们的进程。为此,我们可以使用 ps 命令并加上 grep 管道:
ps -ef | grep test.sh
上述命令的输出可以在以下截图中看到:

图 5.12 – 使用 ps 命令通过名称查找进程
上面的输出显示我们的进程具有PID值为1094,CMD值为/bin/bash ./test.sh。CMD字段包含了我们脚本的完整命令调用,包括命令行参数。
我们需要注意的是,test.sh脚本的第一行包含#!/bin/bash,它提示操作系统调用bash来执行脚本。这一行也被称为CMD字段,在我们的例子中,命令是/bin/bash(根据 shebang 调用),相关的命令行参数是test.sh脚本。换句话说,bash执行了test.sh脚本。
上述ps命令的输出还包括了我们ps | grep命令的调用,这有些不相关。改进版的相同命令如下:
ps -ef | grep test.sh | grep -v grep
上述命令的输出可以在以下截图中看到:

图 5.13 – 使用 ps 命令通过名称查找进程(改进版)
grep -v grep管道用于过滤掉ps命令结果中不需要的grep调用。
如果我们想根据进程 ID(PID)查找进程,可以使用-p|--pid选项参数调用ps命令。例如,以下命令显示了有关我们的进程(PID为1094,运行test.sh脚本)的详细信息:

图 5.14 – 使用 ps 命令根据 PID 查找进程
-f选项显示详细的(长格式)进程信息。
ps命令还有许多其他用法,探索所有用法超出了本书的范围。我们在此列出的调用应该为你提供了基本的探索指南。有关更多信息,请参阅ps系统参考手册(man ps)。
使用 pstree 命令
pstree以层次化、树状的视图显示正在运行的进程。从某种程度上来说,pstree充当了ps命令的可视化工具。pstree命令输出的根节点是init进程,或者是命令中指定的PID值对应的进程。pstree命令的语法如下:
pstree [OPTIONS] [PID] [USER]
以下命令显示了当前终端会话的进程树:
pstree $(echo $$)
上述命令的输出可以在以下截图中看到:

图 5.15 – 当前终端会话的进程树
在前面的命令中,echo $$ 提供了当前终端会话的 PID 值。$$ 是一个 Bash 内建变量,包含了当前正在运行的 shell 的 PID 值。PID 值作为参数传递给 pstree 命令。要显示相关的 PID,我们可以使用 -``p|--show-pids 选项来调用 pstree 命令:
pstree -p $(echo $$)
上述命令的输出可以在以下截图中看到:

图 5.16 – 当前终端会话的进程树(及其 PID)
以下命令显示当前用户拥有的进程:
pstree $(whoami)
上述命令的输出可以在以下截图中看到:

图 5.17 – 当前用户拥有的进程树
更多关于 pstree 命令的信息,请参考相关系统参考手册(man pstree)。
使用 top 命令
在实时监控进程时,top 工具是 Linux 管理员最常使用的工具之一。相关的命令行语法如下:
top [OPTIONS]
以下命令显示系统中当前运行的所有进程,并实时更新(包括内存、CPU 使用情况等):
top
按 Q 会退出 top 命令。默认情况下,top 命令按 CPU 使用情况排序输出(显示在 %``CPU 字段/列中)。
我们还可以选择按其他字段对 top 命令的输出进行排序。在 top 正在运行时,按 Shift + F(F)启用交互模式。
使用方向键,我们可以选择按某一字段排序(例如,%MEM),然后按 S 设置新的字段,接着按 Q 退出交互模式。交互模式排序的替代方法是调用 top 命令的 -o 选项参数,指定排序字段。
例如,以下命令列出了按 CPU 使用排序的前 10 个进程:
top -b -o %CPU | head -n 17
类似地,以下命令列出了按 CPU 和内存使用排序的前 10 个进程:
top -b -o +%MEM | head -n 17
-b 选项参数指定批处理模式操作(而非默认的交互模式)。-o +%MEM 选项参数表示与默认的 %CPU 字段一起,附加的(+)排序字段是 %MEM。head -n 17 管道命令选择输出的前 17 行,包括 top 命令的七行头部:

图 5.18 – 按 CPU 和内存使用排序的前 10 个进程
以下命令列出了当前用户(packt)拥有的按 CPU 使用排序的前五个进程:
top -u $(whoami) -b -o %CPU | head -n 12
-u $(whoami) 选项参数指定了 top 命令的当前用户。
使用 top 命令时,我们也可以使用 -p PID 参数来监控特定进程。例如,以下命令监控我们的测试进程(PID 为 243436):
top -p 1094
上述命令的输出可以在以下截图中看到:

图 5.19 – 使用 top 命令监控特定 PID
我们可以选择在使用 top 命令时通过按 K 来杀死进程。此时,我们会被提示输入我们希望终止的进程的 PID:

图 5.20 – 使用 top 命令杀死进程
top 工具可以以许多创新的方式使用。我们希望本节提供的示例能够启发你根据特定需求探索更多用例。欲了解更多信息,请参考 top 命令的系统参考手册(man top)。
使用 kill 和 killall 命令
我们使用kill命令来终止进程。该命令的语法如下:
kill [OPTIONS] [ -s SIGNAL | -SIGNAL ] PID [...]
kill 命令向进程发送一个信号,尝试停止其执行。如果没有指定信号,系统会发送 SIGTERM(15)。信号可以通过其名称(不带 SIG 前缀)指定(例如,KILL 对应 SIGKILL),或者通过数值指定(例如,9 对应 SIGKILL)。
kill -l 和 kill -L 命令提供了可以在 Linux 中使用的信号的完整列表:

图 5.21 – Linux 信号
每个信号都有一个数值,如前述输出所示。例如,SIGKILL 对应 9。以下命令将杀死我们的测试进程(PID 为 243436):
kill -9 1094
以下命令也会与前面的命令做相同的操作:
kill -KILL 1094
在某些情况下,我们可能希望一次性杀死多个进程。此时,killall 命令将派上用场。killall 命令的语法如下:
killall [OPTIONS] [ -s SIGNAL | -SIGNAL ] NAME...
killall 向所有运行指定命令的进程发送信号。如果没有指定信号,则发送 SIGTERM(15)。信号可以通过信号名称(不带 SIG 前缀)指定(例如,TERM 对应 SIGTERM),或者通过数值指定(例如,15 对应 SIGTERM)。
例如,以下命令终止所有运行 test.sh 脚本的进程:
killall -e -TERM test.sh
上述命令的输出可以在以下截图中看到:

图 5.22 – 使用 killall 终止多个进程
杀死一个进程通常会从系统进程表中移除相关的引用。被终止的进程将不再出现在 ps、top 或类似命令的输出中。
欲了解更多关于 kill 和 killall 命令的信息,请参考相关的系统参考手册(man kill 和 man killall)。
使用 pgrep 和 pkill 命令
pgrep 和 pkill 是基于模式查找命令,用于探索和终止正在运行的进程。它们的语法如下:
pgrep [OPTIONS] PATTERN
pkill [OPTIONS] PATTERN
pgrep遍历当前进程并列出与选择模式或标准匹配的 PID。同样,pkill终止与选择标准匹配的进程。
以下命令会查找我们的测试进程(test.sh),并在找到相关进程时显示PID值。在使用以下命令之前,请重新启动该进程,因为我们在上一节中已经终止了它。这将导致一个不同的PID值:
pgrep -f test.sh
上述命令的输出可以在以下截图中看到:

图 5.23 – 使用 pgrep 根据名称查找 PID
-f|--full选项强制执行我们查找进程的完整名称匹配。我们可以将pgrep与ps命令结合使用,以获得关于进程的更多详细信息,像这样:
pgrep -f test.sh | xargs ps -fp
上述命令的输出可以在以下截图中看到:

图 5.24 – 链接 pgrep 和 ps 获取更多信息
在前面的单行命令中,我们将pgrep命令的输出(PID 为243436)通过管道传递给了ps命令,该命令使用了-f(长格式)和-p|--pid选项。-p选项的参数获取了管道传递的 PID 值。
xargs命令将来自pgrep命令的输入转换为ps命令的参数。因此,当从pgrep传递到ps时,第一个命令的输出会自动转换为第二个命令的参数。默认情况下,xargs读取标准输入。
要终止我们的test.sh进程,我们只需调用pkill命令,如下所示:
pkill -f test.sh
上述命令将静默地终止相关进程,这是基于-f|--full选项强制执行的完整名称查找。如果希望从pkill命令的操作中获得一些反馈,我们需要调用-e|--echo选项,像这样:
pkill -ef test.sh
上述命令的输出可以在以下截图中看到:

图 5.25 – 使用 pkill 按名称终止进程
有关更多信息,请参考pgrep和pkill的系统参考手册(man pgrep和man pkill)。
本节介绍了一些在日常 Linux 管理任务中经常使用的命令行工具,涉及到进程管理。请记住,在 Linux 中,大多数时候,有很多方法可以完成特定任务。我们希望本节中的示例能帮助你想出创意方法和技巧来处理进程。
接下来,我们将介绍一些与守护进程交互的常见方法。
与守护进程协作
正如在引言部分提到的,守护进程是背景进程中的一种特殊类型。因此,大多数用于操作进程的方法和技术同样适用于守护进程。然而,当涉及到管理(或控制)相关进程的生命周期时,有一些特定的命令是专门用于守护进程的。
正如在 Introducing daemons 部分提到的,守护进程由 shell 脚本控制,这些脚本通常存储在 /etc/init.d/ 或 /lib/systemd/ 系统目录中,具体取决于 Linux 平台。在老旧的 Linux 系统(例如 RHEL 6)和 Ubuntu(即使是在最新的发行版中),守护进程脚本文件存储在 /etc/init.d/ 中。而在 RHEL 7 / Ubuntu 18.04 及更新的平台中,这些文件通常存储在 /lib/systemd/ 中。你可以随意列出这两个目录的内容来查看其中的文件。
守护进程文件和守护进程命令行工具的位置在很大程度上取决于 init 初始化系统和服务管理器。在 The init process 部分,我们简要提到了在 Linux 发行版中存在的多种 init 系统。为了说明守护进程控制命令的使用,我们将探讨一种广泛应用于各种 Linux 平台的 init 系统——systemd。
使用 systemd 守护进程
init 系统的基本要求是在 Linux 内核启动时初始化并协调各种进程的启动和依赖关系。这些进程也被称为 init 引擎,它还在系统运行时控制服务和守护进程。
在过去几年中,大多数 Linux 平台已经将 systemd 作为默认的 init 引擎。由于其广泛的应用,熟悉 systemd 及其相关命令行工具至关重要。考虑到这一点,本节的主要重点是 systemctl——管理 systemd 守护进程的核心命令行工具。
systemctl 命令的语法如下:
systemctl [OPTIONS] [COMMAND] [UNITS...]
systemctl 命令触发的操作是针对单位(unit)的,这些单位是由 systemd 管理的系统资源。systemd 中定义了几种单位类型(例如,服务、挂载、套接字等)。每个单位都有一个对应的文件。这些文件类型可以通过相关文件名的后缀推断出来;例如,httpd.service 是 Apache 网络服务(守护进程)的服务单位文件。有关 systemd 单位的完整列表及详细说明,请参考 systemd.unit 系统参考手册(man systemd.unit)。
以下命令使守护进程(例如 httpd,Web 服务器)在启动时自启:
sudo systemctl enable httpd
通常,调用 systemctl 命令需要超级用户权限。我们应该注意到,在我们指定服务单位时,systemctl 并不需要 .service 后缀。以下的调用也是可以接受的:
sudo systemctl enable httpd.service
禁用 httpd 服务在启动时自启的命令如下:
sudo systemctl disable httpd
要查询 httpd 服务的状态,我们可以运行以下命令:
sudo systemctl status httpd
另外,我们可以使用以下命令检查 httpd 服务的状态:
sudo systemctl is-active httpd
以下命令停止或启动 httpd 服务:
sudo systemctl stop httpd
sudo systemctl start httpd
有关 systemctl 的更多信息,请参考相关的系统参考手册(man systemctl)。有关 systemd 内部结构的更多信息,请参考相应的参考手册(man systemd)。
处理进程和守护进程是日常 Linux 管理任务中的常见主题。掌握相关的命令行工具是任何有经验的用户的基本技能。然而,运行中的进程或守护进程也应当与其他进程或守护进程之间的关系一同考虑,后者可能是在本地或远程系统上运行的。进程之间如何相互通信对某些人来说可能是一个小谜题。我们将在下一部分中讨论这一点,解释进程间通信是如何工作的。
解释进程间通信
进程间通信(IPC)是通过共享机制或接口在进程之间进行交互的一种方式。在本节中,我们将以简要的理论方式探讨进程之间的各种通信机制。有关此问题的更多细节以及使用的一些机制,请参阅 第八章,Linux Shell 脚本。
Linux 进程通常可以通过以下接口共享数据并同步它们的操作:
-
共享存储(文件):在最简单的形式下,IPC 机制的共享存储可以是已保存到磁盘的简单文件。生产者写入文件,而消费者从同一文件中读取。在这个简单的用例中,显而易见的挑战是由于底层操作之间可能存在竞争条件,导致读/写操作的完整性问题。为了避免竞争条件,文件必须在写操作期间被锁定,以防止与其他读/写操作发生重叠。为了简化起见,我们不会在简单的示例中解决这个问题,但我们认为有必要指出这一点。
-
/dev/shm临时文件存储系统,使用系统的 RAM 作为其后备存储(即,RAM 磁盘)。
使用 /dev/shm 作为共享内存,我们可以重新使用前面提到的关于 共享存储 的生产者-消费者模型,在该模型中,我们只需将存储文件指向 /dev/shm/storage。
共享内存和共享存储 IPC 模型在处理大量数据时可能表现不佳,特别是海量数据流的情况下。替代方法是使用 IPC 通道,这可以通过管道、消息队列或套接字通信层来启用。
-
命名与未命名管道:未命名或匿名管道,也叫常规管道,将一个进程的输出传递到另一个进程的输入。使用我们的生产者-消费者模型,最简单的方式来说明未命名管道作为两个进程之间的 IPC 机制如下:
producer.sh | consumer.sh
上述代码的关键元素是管道(|)符号。管道的左侧生成输出,并直接传送到管道右侧供消费。
命名管道,也叫先进先出(FIFO),与传统的(未命名)管道类似,但在语义上有显著不同。未命名管道仅在相关进程运行时存在。然而,命名管道有后备存储,并且只要系统运行,它就会持续存在,而不管相关进程是否在运行。通常,命名管道表现得像一个文件,并且在不再使用时可以删除。
- 消息队列:消息队列是一种异步通信机制,通常用于分布式系统架构中。消息被写入并存储在队列中,直到被处理并最终删除。消息由生产者写入(发布),并且只会被处理一次,通常由单个消费者处理。从高层次来看,消息包含序列、有效载荷和类型。消息队列可以调节消息的获取(顺序)(例如,根据优先级或类型):

图 5.26 – 消息队列(简化视图)
消息队列的详细分析或其模拟实现并非简单之事,超出了本章的范围。大多数 Linux 平台上都有许多开源的消息队列实现(如 RabbitMQ、ActiveMQ、ZeroMQ、MQTT 等)。
基于消息队列和管道的 IPC 机制是单向的。一种进程写入数据,另一种进程读取数据。虽然有命名管道的双向实现,但其中的复杂性会对底层通信层产生负面影响。对于双向通信,你可以考虑使用基于套接字的 IPC 通道(在第八章中有详细介绍,Linux Shell 脚本)。
-
套接字:有两种基于 IPC 套接字的设施:
-
IPC 套接字:也叫做 Unix 域套接字,IPC 套接字使用本地文件作为套接字地址,允许在同一主机上的进程之间进行双向通信。
-
网络套接字:传输控制协议(TCP)和用户数据报协议(UDP)套接字。它们通过 TCP/UDP 网络扩展了 IPC 数据连接层,跨越本地机器。
-
除了显而易见的实现差异,IPC 套接字和网络套接字的数据通信通道行为相同。
两个套接字都配置为流式通信,支持双向通信,并模拟客户端/服务器模式。套接字的通信通道在任一端关闭之前保持活动,从而断开了 IPC 连接。
-
信号:在 Linux 中,信号是一种单向异步通知机制,用于响应特定的条件。信号可以朝以下任何一个方向传递:
-
从 Linux 内核到任意进程
-
从进程到进程
-
从进程到它自己
-
我们在本节开始时提到过,信号是另一种 IPC 机制。实际上,它们是一种有限的 IPC 形式,因为通过信号,进程可以相互协调同步。但信号不会携带任何数据负载。它们只是通知进程某些事件,进程可以选择针对这些事件采取特定的行动。
在下一节中,我们将详细讲解如何在 Linux 中处理信号。
处理信号
信号通常会提醒 Linux 进程某个特定事件的发生,例如,由内核触发的段错误(SIGSEGV),或者用户按下 Ctrl + C 中断执行(SIGINT)。在 Linux 中,进程通过信号进行控制。Linux 内核定义了几十种信号。每个信号都有一个对应的非零正整数值。
以下命令列出了在 Linux 系统中注册的所有信号:
kill -l
前一个命令的输出可以在图 5.21中看到。从输出中可以看出,SIGHUP 例如,信号值为 1,并且在终端会话退出时,它会传递给所有子进程。SIGKILL 的信号值为 9,并且最常用于终止进程。进程通常可以控制信号的处理方式,除了 SIGKILL(9)和 SIGSTOP(19),这两个信号总是分别终止或停止进程。
进程以以下两种方式之一处理信号:
-
执行信号所暗示的默认操作;例如,停止、终止、核心转储一个进程,或什么也不做。
-
执行一个自定义的操作(除了
SIGKILL和SIGSTOP)。在这种情况下,进程捕获信号并以特定方式处理它。
当一个程序实现一个自定义的信号处理程序时,通常会定义一个信号处理函数,来改变进程的执行,如下所示:
-
当信号被接收时,进程的执行会在当前指令处被中断
-
进程的执行会立即跳转到信号处理函数
-
信号处理函数运行
-
当信号处理函数退出时,进程将恢复执行,从之前中断的指令开始
以下是与信号相关的一些简要术语:
-
信号由产生它的进程触发
-
信号由处理它的进程捕获
-
如果进程有一个相应的空操作(no-op,NOOP)处理程序,信号将被忽略
-
如果进程在捕获到信号时执行特定操作,那么该信号就被处理了。
在所有信号中,SIGKILL和SIGSTOP是唯一无法被捕获或忽略的信号。
让我们探索几个信号处理的使用案例:
-
当内核发送
SIGKILL、SIGFPE(浮点异常)、SIGSEGV(段错误)、SIGTERM或类似的信号时,通常接收信号的进程会立即终止执行,并可能生成一个核心转储——用于调试目的的进程镜像。 -
当用户按下Ctrl + C时——这时会发送一个
SIGINT信号到进程。除非底层程序为SIGINT实现了特殊的处理程序,否则进程将终止。 -
使用
kill命令,我们可以根据进程的 PID 向任何进程发送信号。以下命令向 PID 为3741的终端会话发送SIGHUP信号:kill -HUP 3741
在前面的命令中,我们可以指定信号值(例如,1表示SIGHUP)或直接使用信号名称,而不带SIG前缀(例如,HUP表示SIGHUP)。
使用killall,我们可以向多个正在运行特定命令的进程发送信号(例如,test.sh)。以下命令终止所有运行test.sh脚本的进程,并将结果输出到控制台(通过-e选项):
killall -e -TERM test.sh
该命令的输出可以在图 5.22中看到。
Linux 进程和信号是一个庞大的领域。我们在这里提供的信息远非该主题的全面指南。我们希望这种简短的实践方法,展示了一些常见的使用案例,能激发你去解决并可能掌握更具挑战性的问题。
总结
对 Linux 进程和守护进程的详细研究可能是一个重大的工作。尽管有一些在这一主题上极为成功的著作,但相对简短的一章可能会相形见绌。然而,在本章中,我们尝试为我们考虑的每个内容披上一件现实、实用的外衣,以弥补我们在抽象或学术领域可能的不足。
到目前为止,我们希望你已经能够熟练地处理进程和守护进程。你目前所掌握的技能应包括对进程类型和内部机制的较好理解,以及对进程属性和状态的合理理解。我们特别关注了进程间通信机制,特别是信号。关于这些主题,我们将在第八章中进行更详细的探讨。现在,我们认为我们提供的信息足以帮助理解进程间通信的工作原理。
下一章将进一步探讨如何操作 Linux 磁盘和文件系统。我们将探讨 Linux 存储、磁盘分区和逻辑卷管理(LVM)概念。放心,到目前为止我们学到的一切将在接下来的章节中立即派上用场。
问题
如果你已经快速浏览了本章的某些部分,可能需要回顾一些关于 Linux 进程和守护进程的基本细节:
-
想一想几种进程类型。它们之间有什么不同?
-
想一想进程的构成。你能列出一些在检查进程时可能关注的基本进程属性(或者是
ps命令行输出中的字段)吗?
提示:除了 CPU、RAM 或磁盘使用情况之外,还有哪些对你来说是相关的?
-
你能想到几种进程状态以及它们之间的一些动态变化或可能的转换吗?
-
如果你正在寻找一个占用系统大部分 CPU 的进程,你会怎么做?
-
你能编写一个简单的脚本并将其作为一个长时间运行的后台进程吗?
提示:查看 第八章,我们将在那里教你如何创建和使用 shell 脚本。
- 列举至少四个你能想到的进程信号。你会在什么情况下触发这些信号?
kill -l 命令。欲了解更多信息,请阅读手册。
- 想一想几种进程间通信(IPC)机制。试着列出它们的优缺点。
提示:在 第八章 中的内容可能对你有所帮助。
进一步阅读
欲了解本章所涉及的更多信息,您可以参考以下 Packt 出版社的书籍:
-
Linux 管理最佳实践,作者:Scott Alan Miller
-
使用 systemd 简化 Linux 服务管理,作者:Donald A. Tevault
第二部分:高级 Linux 管理
在第二部分中,您将学习高级的 Linux 系统管理任务,包括磁盘操作、网络配置、强化 Linux 安全性,以及系统特定的故障排除和诊断。
本部分包含以下章节:
-
第六章,磁盘和文件系统管理
-
第七章,与 Linux 的网络连接
-
第八章,Linux Shell 脚本编写
-
第九章,Linux 安全性
-
第十章,灾难恢复、诊断与故障排除
第六章:使用磁盘和文件系统
在本章中,你将学习如何管理磁盘和文件系统,如何使用逻辑卷管理(LVM)系统,以及如何挂载和分区硬盘,并且深入了解 Linux 中的存储。你还将学习如何对磁盘进行分区和格式化,如何创建逻辑卷,并对文件系统类型有更深刻的理解。在本章中,我们将涵盖以下主要内容:
-
了解 Linux 中的设备
-
了解 Linux 中的文件系统类型
-
了解磁盘和分区
-
在 Linux 中介绍 LVM
技术要求
了解磁盘、分区和文件系统的基础知识是推荐的。没有其他特殊的技术要求,只需要你的系统上有一个正常运行的 Linux 安装。我们将在本章练习中主要使用 Ubuntu 或 Debian。即使你不使用 Debian 或 Ubuntu,本章中使用的所有命令都可以在任何 Linux 发行版上复制。
了解 Linux 中的设备
正如本书中多次提到的,Linux 中的一切都是文件。设备也不例外。设备文件是 Unix 和 Linux 操作系统中的特殊文件。这些特殊文件是设备驱动程序的接口,并且作为常规文件出现在文件系统中。
不再多说,我们来看看 Linux 的抽象层如何工作。这将为你提供硬件与软件如何相互关联和交织的概览。
Linux 抽象层
现在是讨论 Linux 系统抽象层以及设备如何融入整体架构的最佳时机。任何计算机通常都被组织为两个层级(或级别)——硬件层和软件层:
-
硬件层级:此层包含你计算机的硬件组件,如内存(RAM)、中央处理单元(CPU)和设备,包括磁盘、网络接口、端口和控制器。
-
软件层级:为了让这些硬件组件正常工作,操作系统(在本书中为 Linux)使用抽象层。这些层存在于内核中,内核是 Linux 的主要软件组件。在不深入更多信息的前提下,你只需知道,Linux 有这些层,负责访问低级资源,并为不同硬件组件提供特定的驱动程序。当计算机启动时,Linux 内核会从磁盘加载到系统内存(RAM)中。因此,在内存中将有两个独立的区域,称为内核空间和用户空间,这将构成软件层级:
-
内核是 Linux 操作系统的核心。内核驻留在内存(RAM)中,管理所有硬件组件。它是你 Linux 系统中软件与硬件之间的接口。
-
用户空间级别是执行用户进程的层次。正如在 第五章 《处理进程、守护进程和信号》 中所展示的,进程是程序的运行实例。
-
在这个宏大的体系结构中,设备在哪里呢?设备由 内核 来管理。总的来说,内核负责管理进程、系统调用、内存和设备。在处理设备时,内核管理 设备驱动程序,它们是硬件组件与软件之间的接口。所有设备只能在内核模式下访问,以确保更加安全和高效的操作。
这一切是如何运作的呢?实际上,内存(也就是 RAM)由用于临时存储信息的单元组成。这些单元被不同的程序访问,这些程序作为 CPU 和存储之间的中介。访问内存的速度非常高,以确保执行过程的流畅性。用户空间内的用户进程管理是内核的工作。内核确保没有任何进程会相互干扰。内核空间通常只由内核访问,但有时用户进程也需要访问这个空间。这是通过 系统调用 来实现的。系统调用是用户进程通过内核空间内的活动进程请求内核服务的一种方式,涉及的内容包括 输入/输出(I/O)请求,来与内部或外部设备进行交互。所有这些请求都将数据从 CPU 通过 RAM 传输,以完成任务。
在接下来的章节中,我们将介绍 Linux 中的命名规则以及设备文件的管理方式。
设备文件和命名规则
在了解了抽象层的工作原理后,你可能会想知道 Linux 如何管理设备。其实,Linux 是通过 用户空间 /dev(udev)来管理设备的,它是一个为内核提供服务的设备管理器。它通过 设备节点 来工作,设备节点是特殊的文件(也叫 设备文件),用于作为驱动程序的接口。
Linux 中的设备文件
udev 作为一个守护进程运行,监听内核发送的用户空间调用,因此它能够知道使用了哪些设备,以及如何使用这些设备。这个守护进程叫做 udevd,其配置文件目前存储在 /etc/udev/udev.conf 下。你可以通过运行以下命令来查看 /etc/udev/udev.conf 文件的内容:
cat /etc/udev/udev.conf
每个 Linux 发行版都有一套默认的规则来管理 udevd。这些规则通常存储在 /etc/udev/rules.d/ 目录下,正如下面的截图所示:

图 6.1 – udevd 规则位置
注意
内核通过 Netlink 套接字发送事件调用。Netlink 套接字是一种用于进程间通信的接口,适用于用户空间和内核空间的进程。
/dev 目录是用户进程与内核管理的设备之间的接口。如果你使用 ls -la /dev 命令,你会看到很多文件,每个文件都有不同的名称。如果你使用长格式列出文件,你会看到不同的文件类型。某些文件名以 b 和 c 开头,但根据你的系统,也可能会看到 p 和 s 开头的文件。以这些字母开头的文件是设备文件。以 b 开头的是块设备,而以 c 开头的是字符设备,如下所示的屏幕截图所示:

图 6.2 – /dev 目录中的设备文件
让我们看看磁盘设备如何在 /dev 目录中呈现。但首先,我们在以下提示中提供了有关工作环境的几句话。
重要提示
本书中的大部分练习将使用以行星名称为主机名的虚拟机,运行不同的基于 Linux 的操作系统。例如,neptune 运行的是 Ubuntu 22.04.2 LTS Server,因此当你在终端提示符中看到 neptune 主机名时,你就知道我们正在使用基于 Ubuntu 的系统。我们还使用了 jupiter,运行在 openSUSE 15.4 Leap 服务器上,saturn 运行在 Fedora 37 Workstation 上,venus 运行在 AlmaLinux 上,mars 运行在 Debian 11.6 服务器上。在虚拟机中,设备驱动程序的命名与裸机系统有所不同。我们将在接下来的部分讨论设备命名约定时提供详细信息。不过,对于一些示例(已做标记),我们也会使用我们的主工作站,运行的是 Debian 12 GNU/Linux。
如 图 6.3 所示,磁盘设备 sda 作为块设备表示。块设备具有固定大小,可以方便地进行索引。另一方面,字符设备可以通过数据流进行访问,因为它们不像块设备那样有固定的大小。例如,打印机作为字符设备表示。在下面的屏幕截图中,sg0 是一个 SCSI 通用设备,在我们的例子中没有分配给任何磁盘。我们使用了运行在 Debian GNU/Linux 上的主工作站,作为 sda 展示的设备是一个外部 USB 设备:

图 6.3 – /dev 目录中的磁盘驱动器
相比之下,当列出 neptune 虚拟机中的设备时,我们将看到如下所示的输出:

图 6.4 – 虚拟机中的虚拟设备
以 vdaX 表示的设备块是虚拟机中的虚拟设备。你将在第十一章中了解到更多关于虚拟机的内容,与虚拟机的工作。
但现在,让我们先了解一下 Linux 中的设备命名约定。
理解设备命名规范
Linux 使用一种设备命名规范,使得设备管理在整个 Linux 生态系统中变得更加容易和一致。udev 使用几种特定的命名方案,默认情况下将固定的名称分配给设备。这些名称是根据设备类别标准化的。例如,在命名网络设备时,内核使用来自固件、拓扑和位置等来源的信息。在基于 Red Hat 的系统中,有五种方案用于命名网络接口,我们鼓励你查看 Red Hat 客户门户官网文档:access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9。
在基于 Debian 的系统中,命名规范也类似,它是基于硬件总线名称来实现可预测性。这与所有现代 Linux 操作系统类似。
你还可以查看系统上哪些udev规则是活跃的。在基于 Debian 和 Red Hat 的发行版中,它们存储在 /lib/udev/rules.d/ 目录下。
在硬盘或外部硬盘方面,命名规范更加简化。以下是一些示例:
-
hda(主设备)、hdb(第一个通道上的从设备)、hdc(第二个通道上的主设备)、hdd(第二个通道上的从设备) -
nvme0(第一个设备控制器 – 字符设备)、nvme0n1(第一个命名空间 – 块设备),以及nvme0n1p1(第一个命名空间,第一个分区 – 块设备) -
mmcblk(用于使用 eMMC 芯片的 SD 卡)、mmcblk0(第一个设备),以及mmcblk0p1(第一个设备,第一个分区) -
sd(用于大容量存储设备)、sda(第一个注册设备)、sdb(第二个注册设备)、sdc(第三个注册设备),依此类推,以及sg(用于通用 SCSI 层 – 字符设备)
本章中我们最关注的设备是大容量存储设备。这些设备通常是硬盘驱动器(HDDs)或固态硬盘(SSDs),用于计算机内部存储数据。这些驱动器很可能会根据文件系统提供的特定结构被划分为多个分区。我们之前在本书的第二章《Linux Shell 和 文件系统》中简单讨论了文件系统,当时我们提到过 Linux 目录结构,但现在,是时候深入了解 Linux 中的文件系统类型了。
理解 Linux 中的文件系统类型
在谈论物理介质时,比如硬盘或外部硬盘时,我们并不是在讨论目录结构。这里我们讨论的是在格式化和/或分区时,在物理硬盘上创建的结构。这些结构根据其类型被称为文件系统,它们决定了文件在存储到硬盘时如何被管理。
存在多种类型的文件系统,其中一些是 Linux 生态系统的原生文件系统,而其他则不是,例如特定的 Windows 或 macOS 文件系统。在本节中,我们将仅描述 Linux 原生的文件系统。
在 Linux 中最常用的文件系统包括 Ext、Ext2、Ext3 和 Ext4,XFS 文件系统,ZFS 和 btrfs(简而言之,Ext4 是 Ext3 的最新版本,它在支持更大文件、碎片管理和性能方面进行了改进,比 Ext3 更好。Ext3 文件系统使用 32 位寻址,而 Ext4 使用 48 位寻址,因此支持最大 16 TB 的文件大小。它还支持无限子目录,因为 Ext3 仅支持 32k 个子目录。此外,Ext4 中还新增了对扩展时间戳的支持,提供额外的两位,以支持到公元 2446 年,并且支持在内核层面进行在线碎片整理。
尽管如此,Ext4 仍然不是一个真正的下一代文件系统;它更像是一个经过改进、值得信赖、稳健且稳定的 工作马,但在数据保护和完整性方面并未通过测试。它的日志系统不适合检测和修复数据损坏或降级问题。这就是为什么其他文件系统,如 XFS 和 ZFS,开始重新出现并被应用于 Red Hat Enterprise Linux 7 版本开始使用 XFS,以及从 Ubuntu 16.04 版本起使用 ZFS 的原因。
btrfs 的情况有些争议。它被认为是一个现代文件系统,但由于与其他文件系统相比存在多个性能问题,它仍然只作为单盘文件系统使用,并未在多盘卷管理器中得到广泛应用。它被用于 SUSE Linux Enterprise 和 openSUSE,但不再被 Red Hat 支持,并已被投票选为 Fedora 从版本 33 开始的未来默认文件系统。
以下是一些主要文件系统特性的详细信息:
-
Ext4文件系统从一开始就为 Linux 设计。尽管它正在被其他文件系统逐渐替代,但它仍然具有强大的功能。它提供了块大小选择,大小范围在 512 到 4,096 字节之间。它还具有 inode 保留功能,在创建目录时保留一些 inode,从而在创建新文件时提高性能。布局简单,
Ext4文件系统在此方面有所利用。在讨论中,我们将涉及以下几点:最大文件系统大小、使用fsck命令进行快速文件系统检查、为日志系统使用校验和提高可靠性,以及改进的时间戳使用。 -
ZFS:这个文件系统是在 Sun Microsystems 创建的,它将文件系统和逻辑卷管理器结合为一个解决方案。它于 2004 年发布,开发始于 2001 年,最初集成到 Solaris 操作系统中,后来被 Debian、FreeBSD 等使用(虽然不是默认的)。ZFS 是一个高度可扩展的 128 位系统,提供简单的管理、数据完整性、可扩展性和性能。该文件系统的开发通过 OpenZFS 开源项目进行。ZFS 采用了一种写时复制机制,与传统文件系统不同,从而提供了一种复杂的结构。有关 ZFS 的更多详细信息,我们推荐以下链接:
openzfs.github.io/openzfs-docs/Getting%20Started/index.html。 -
Ext4以及其他有竞争力的文件系统类型。其中之一是XFS。这个文件系统最初由 Silicon Graphics, Inc 创建,并在 IRIX 操作系统中使用。它最重要的设计元素是性能,因为它能够处理大型数据集。此外,它被设计为能够处理并行 I/O 任务,保证高 I/O 速率。该文件系统支持最大 16 EB,单个文件最大支持 8 EB。XFS有一个日志记录配额信息的功能,并支持在线维护任务,如碎片整理、扩展和恢复。还提供了备份和恢复的专用工具,包括xfsdump和xfsrestore。 -
btrfs仍在开发中,但它解决了现有文件系统的一些问题,包括缺少快照、池化、校验和和多设备跨越等功能。这些都是企业 Linux 环境中所需的功能。能够对文件系统进行快照并维护其内部框架以管理新的分区,使得btrfs在企业关键生态系统中成为一个可行的新兴选项。
还有一些我们没有在此讨论的文件系统,包括使用 cat /``proc/filesystems 命令查看的文件系统。
Linux 实现了一个特殊的软件系统,旨在运行文件系统的特定功能。它被称为虚拟文件系统,充当内核与文件系统类型和硬件之间的桥梁。因此,当应用程序想要打开文件时,操作通过虚拟文件系统作为抽象层传递:

图 6.5 – Linux 虚拟文件系统抽象层
基本的文件系统功能包括为分层目录结构提供命名空间、元数据结构作为逻辑基础、磁盘块使用情况、文件大小和访问信息,以及逻辑卷和分区的高级数据。每个文件系统还有一个应用程序编程接口(API)。因此,开发人员可以访问系统功能调用,利用特定的算法进行文件的创建、移动、删除,或进行索引、搜索和查找文件。此外,每个现代文件系统都提供了一个特殊的访问权限方案,用于确定用户访问文件的规则。
到目前为止,我们已经介绍了主要的 Linux 文件系统,包括EXT4、btrfs和XFS。在下一节中,我们将教授你 Linux 中磁盘和分区管理的基础知识。
理解磁盘和分区
理解磁盘和分区是任何系统管理员的关键技能。格式化和分区磁盘至关重要,从系统安装开始。了解系统上可用的硬件类型非常重要,因此必须掌握如何使用这些硬件。其中之一就是磁盘;我们来进一步了解一下。
常见的磁盘类型
磁盘是存储数据的硬件组件。它有多种类型,并使用不同的接口。主要的磁盘类型包括广为人知的旋转硬盘 HDD、SSD 和非易失性存储器 Express(NVMe)。SSD 和 NVMe 使用类似 RAM 的技术,具有比传统旋转硬盘更低的能耗和更高的传输速率。以下是使用的接口:
-
集成驱动电子(IDE):这是一种旧标准,广泛应用于具有较小传输速率的消费类硬件,现已被弃用。
-
串行先进技术附件(SATA):这取代了 IDE,具有最高 16 GB/s 的传输速率。
-
小型计算机系统接口(SCSI):这主要用于具有 RAID 配置的企业服务器,配备了复杂的硬件组件。
-
串行附加 SCSI(SAS):这是一种点对点串行协议接口,传输速率类似于 SATA。它主要用于企业环境,因为其可靠性。
-
通用串行总线(USB):这用于外部硬盘和存储设备。
每个磁盘都有特定的几何结构,包括磁头、磁道、柱面和扇区。在 Linux 系统中,要查看磁盘几何结构的信息,可以使用fdisk -l命令。
在我们的主要工作站上,我们有一个单独的 SSD 运行 Debian 12 GNU/Linux,并且一个 USB 设备插入其中一个端口。我们将运行以下命令以获取有关机器上驱动器的信息:
sudo fdisk -l
以下截图展示了fdisk命令输出的摘录,涉及两块驱动器:

图 6.6 – fdisk -l 命令输出显示磁盘信息
fdisk 工具的输出初看起来可能让人感到有些复杂,但请放心,我们会为你解释,使其从现在开始变得更容易理解。通过不指定特定分区作为参数来使用 fdisk 工具,所有 /proc/partitions 中可用的分区信息都会显示出来。在前面的截图中,你可以看到系统中有两个磁盘的详细信息:一块 1 TB 的 Lexar NM620 SSD 和一块 8 GB 的 USB 闪存驱动器。我们来解释一下如何显示 1 TB 硬盘的信息:
-
首先,你会看到
Disk model显示的是驱动器的名称,Units为扇区,每个扇区大小为 512 字节,Disklabel type为 GPT,Disk identifier是每个驱动器唯一的标识符。 -
接下来是磁盘上可用分区的表格。这个表有六列(有时是七列,比如在截图底部显示的 USB 闪存驱动器)。第一列有
Device标头,显示分区命名方案。第二列和第三列(在我们的例子中)显示分区的起始和结束扇区。第四列显示分区的总扇区数。第五列显示分区的大小(以人类可读的格式),最后一列显示文件系统的类型。
了解系统上磁盘设备的基本信息仅仅是操作 Linux 系统中磁盘和分区的起点。如果我们不对磁盘进行格式化和分区,它们只是大块的金属。因此,在下一节中,我们将教你什么是分区。
磁盘分区
一般来说,磁盘使用分区。要理解分区,了解磁盘的几何结构是至关重要的。这些传统的知识即使在处理 SSD 时仍然有用。分区是连续的扇区和/或磁道集合,它们可以有几种类型:主分区、扩展分区和逻辑分区。每个磁盘最多可以有 15 个分区。前四个分区将是主分区或扩展分区,其余的则是逻辑分区。此外,磁盘上只能有一个扩展分区,但它可以被划分为多个逻辑分区,直到达到最大数量为止。
分区类型
有两种主要的分区类型 – 0x0c 代表 FAT,0x07 代表 NTFS,0x83 代表 Linux 文件系统类型,0x82 代表交换分区(swap)。GPT 成为统一扩展固件接口(UEFI)标准的一部分,解决了 MBR 的一些问题,包括分区限制、寻址方式、只使用一个分区表副本等等。它支持最多 128 个分区,磁盘大小可以达到 75.6 泽字节(ZB)。
分区表
磁盘的 分区表 存储在磁盘的 MBR 中。MBR 是磁盘的前 512 字节。其中,分区表占 64 字节,存储在前 446 字节的记录之后。在 MBR 的末尾,有 2 字节称为扇区结束标记。前 446 字节保留给通常属于引导加载程序的代码。在 Linux 中,引导加载程序称为 GRand Unified Bootloader (GRUB)。
当您启动 Linux 系统时,引导加载程序会查找活动分区。单个磁盘上只能有一个活动分区。找到活动分区后,引导加载程序会加载相关项目。分区表有 4 个条目,每个条目的大小为 16 字节,每个条目属于系统中的某个可能的主分区。此外,每个条目包含有关 柱面/磁头/扇区 的起始地址、分区类型代码、柱面/磁头/扇区 的结束地址、起始扇区和分区内扇区的数量等信息。
分区命名
内核通过低级方式与磁盘交互。这是通过存储在 /dev 目录中的设备节点来完成的。设备节点使用简单的命名约定,告诉您哪个磁盘需要关注。查看 /dev 目录的内容,您可以看到所有可用的磁盘节点,也称为磁盘驱动器,参见本节前面的 图 6.2 和 图 6.3。简短的说明总是很有用,因此磁盘和分区的识别如下:
-
第一块硬盘始终是
/dev/sda(对于 SCSI 或 SATA 设备) -
第二块硬盘是
/dev/sdb,第三块是/dev/sdc,以此类推 -
第一个磁盘的第一个分区是
/dev/sda1 -
第二个磁盘的第一个分区是
/dev/sdb1 -
第二个磁盘的第二个分区是
/dev/sdb2,以此类推
我们指定在 SCSI 和 SATA 的情况下是这样的,接下来我们需要稍微详细地解释一下。内核会根据 SCSI 设备的 ID 号给出字母标识,例如 a、b 和 c,而不是根据硬件总线的位置来决定。
分区属性
要了解分区的属性,您可以使用 lsblk 命令。我们将在我们的 Debian 系统上运行它,如下图所示:

图 6.7 – lsblk 输出
lsblk 命令显示设备的名称(来自 sysfs 和 udev 数据库的节点名称)、主设备号和次设备号、设备的可拆卸状态(0 表示非可拆卸设备,1 表示可拆卸设备)、以人类可读格式显示的大小、只读状态(使用 0 表示非只读,1 表示只读)、设备类型以及设备的挂载点(如果有的话)。
现在我们对驱动器有了更多了解,接下来我们来学习如何修改磁盘的分区表。
分区表编辑器
在 Linux 中,管理分区表时有几种工具可以使用。最常用的工具包括以下几种:
-
fdisk:一个命令行分区编辑器,可能是最广泛使用的一个 -
Sfdisk:一个非交互式的分区编辑器,主要用于脚本编写 -
parted:GNU(GNU 是GNU's Not Unix的递归首字母缩写)分区操作软件 -
gparted:parted的图形界面
在这些工具中,我们只详细介绍如何使用fdisk,因为它是 Linux 中最广泛使用的命令行分区编辑器。它在 Ubuntu/Debian、RHEL/Fedora、openSUSE 以及许多其他发行版中都能找到。
在使用fdisk之前,我们希望查看操作系统已知的分区。如果你不确定刚刚完成的操作,可以使用cat命令查看/proc/partitions文件的内容:

图 6.8 – 列出/proc/partitions 文件
要使用fdisk,你必须是 root 用户。我们建议你在使用fdisk时要小心,因为它可能会损坏你现有的分区和磁盘。你可以在特定磁盘上使用fdisk,方法如下:

图 6.9 – 第一次使用 fdisk
你会注意到,在第一次使用fdisk时,会有警告提示,表示只有在你决定将更改写入磁盘时,才会对磁盘进行更改。系统还会提示你输入命令,并会显示m选项以供帮助。我们建议你始终使用帮助菜单,即使你已经知道最常用的命令。
当你输入m时,将显示fdisk的完整命令列表。你将看到用于管理分区、创建新引导记录、保存更改等选项。分区表编辑器是管理 Linux 磁盘的重要工具。如果你不知道如何格式化分区,使用它们是不完整的。在接下来的部分,我们将向你展示如何对磁盘进行分区。
创建和格式化分区
我们将使用fdisk工具在插入主工作站上的 USB 闪存驱动器(运行 Debian GNU/Linux)上创建一个新的分区表。我们将使用以下命令创建 MBR 分区表:
sudo fdisk /dev/sda
我们将使用o选项创建一个空的 MBR 分区表,然后使用w选项将更改保存到磁盘。命令的输出如下所示:

图 6.10 – 使用 fdisk 创建新的 MBR 分区表
到此为止,分区表已创建,但磁盘上尚未定义任何分区。仍然处于 fdisk 命令行界面时,您可以使用 v 选项验证新创建的分区表,使用 I 选项查看现有分区的信息。您将看到一些输出,表明尚未定义任何分区。因此,现在是时候设置一个新分区了。
要创建一个新分区,我们将使用以下系列选项:
-
使用
n选项启动创建过程 -
当提示创建主分区(
p)或扩展分区(e)类型时,使用p选项 -
输入分区号(使用默认值
1) -
输入第一个扇区(使用默认值
2048) -
输入最后一个扇区 – 如果您希望分区具有特定大小,可以使用 KB、MB、GB 等单位的大小值,或使用扇区值(默认是磁盘的最大大小)
-
如果提示删除任何签名,输入
Y以删除它们 -
w用于将更改写入磁盘
上一系列操作的输出显示在以下屏幕截图中:

图 6.11 – 使用 fdisk 创建新分区
到此为止,分区已创建,但尚未格式化。在学习如何格式化分区之前,我们先来学习如何备份分区表。
有时您需要备份并恢复您的 dd 工具。使用的命令如下:
sudo dd if=/dev/sda of=mbr-backup bs=512 count=1
这个程序非常有用且强大,因为它可以克隆磁盘或擦除数据。以下是一个展示命令输出的例子:

图 6.12 – 使用 dd 命令备份 MBR
dd 命令有一个清晰的语法。默认情况下,它使用标准输入和标准输出,但您可以通过指定新输入文件(使用 if 选项)和输出文件(使用 of 选项)来更改这些。我们将输入文件指定为要备份的磁盘的设备文件,并为备份输出文件指定了名称。我们还使用 bs 选项指定了块大小,并使用 count 选项指定了要读取的块数。
要恢复引导加载程序,我们可以使用 dd 命令,如下所示:
sudo dd if=~/mbr-backup of=/dev/sda bs=512 count=1
现在您已经学习了如何使用 dd 备份分区表,我们来格式化之前创建的分区。格式化分区的最常用程序是 mkfs。格式化分区也称为创建文件系统,因此该工具的名称就是这么来的。它有针对不同文件系统的特定工具,但都使用相同的前端工具。以下是 mkfs 支持的所有文件系统列表:

图 6.13 – 关于 mkfs 工具的详细信息
要将目标磁盘格式化为 Ext4 文件系统,我们将使用 mkfs 工具。执行的命令如下:
-
首先,我们将运行
fdisk工具,以确保正确选择最大磁盘。运行以下命令:sudo fdisk -l -
然后,极其小心地检查输出并选择正确的磁盘名称。
-
现在我们已经知道要操作的磁盘,接下来我们将使用
mkfs将其格式化为Ext4文件系统。输出如下所示:

图 6.14 – 使用 mkfs 格式化 Ext4 分区
使用mkfs时,有多个选项可供选择。要创建一个Ext4类型的分区,你可以使用图 6.14中显示的命令,也可以使用-t选项后跟文件系统类型。你还可以使用-v选项以获取更详细的输出,使用-c选项扫描坏道并创建文件系统。如果你想在命令中直接添加分区标签,还可以使用-L选项。以下是创建一个名为newpartition的Ext4文件系统分区的示例:
sudo mkfs -t ext4 -v -c -L newpartition /dev/sda
一旦分区格式化完成,建议检查其是否存在错误。类似于mkfs,有一个名为fsck的工具。这是一个有时会在异常关机后或按设定间隔自动运行的工具。它针对最常用的文件系统有特定的程序,就像mkfs一样。以下是运行fsck时显示的输出。运行后,它会显示是否有任何问题。在以下截图中,输出显示检查分区时没有发现错误:

图 6.15 – 使用 fsck 检查分区
创建分区后,需要将其挂载;否则,无法使用。
重要提示
挂载是 Linux 以及任何其他操作系统中的一项重要操作。通过挂载,你为操作系统提供了访问磁盘资源的方式,使其看起来就像使用本地磁盘一样。在 Linux 中,挂载的外部磁盘会链接到挂载点,即本地文件系统上的一个目录。挂载点对于 POSIX 兼容的操作系统(如 Linux)至关重要。挂载磁盘后,操作系统可以通过挂载点访问整个磁盘。有关挂载的更多信息,请访问docs.oracle.com/cd/E19455-01/805-7228/6j6q7ueup/index.html。
每个分区将在现有的文件系统结构中挂载。挂载可以在树结构的任何位置进行。每个文件系统都会挂载在某些目录下,这些目录是在目录结构内部创建的。我们将在下一节中探讨挂载和卸载分区的过程。
挂载和卸载分区
mount和umount。要查看某个分区是否已挂载,你可以简单地输入mount并查看输出,该输出可能会非常庞大。你可以使用grep来筛选它:
mount | grep /dev/sda
我们在输出中查找 /dev/sda,但未显示出来。这意味着该驱动器未挂载。
为了挂载它,我们需要创建一个新的目录。为了简便起见,我们将展示从创建目录到挂载并使用分区所需的所有步骤:
-
创建一个新的目录来挂载分区。在我们的例子中,我们在
/home/alexandru目录下创建了一个名为USB的新目录:mkdir USB -
使用以下命令挂载分区:
mbr-backup file we created a few steps back to the newly mounted USB memory stick using the following command:sudo cp mbr-backup USB/
以下是前面列表中所有命令的输出:

图 6.16 – 挂载外部存储设备
mount 命令需要超级用户权限。如果你尝试在没有 sudo 的情况下挂载外部 USB 设备,你将看到以下消息:

图 6.17 – 未使用 sudo 执行 mount 命令时的错误
mount 工具有许多可用选项。使用帮助菜单可以查看它的所有功能。现在分区已经挂载,你可以开始使用它。如果你想卸载它,可以使用 umount 工具。你可以按照以下方式使用它:
sudo umount /dev/sda
卸载文件系统时,如果该分区仍在使用中,你可能会收到错误信息。被使用意味着该文件系统中的某些程序仍在内存中运行,并正在使用该分区中的文件。因此,你必须首先关闭所有正在运行的应用程序,如果其他进程正在使用该文件系统,你也需要终止它们。有时,文件系统忙碌的原因起初并不明显,你可以使用 lsof 命令来查看哪些文件正在被打开和使用:
sudo lsof | grep /dev/sda
挂载文件系统只会使其在系统关闭或重启之前可用。如果你希望更改具有持久性,你需要相应地编辑 /etc/fstab 文件。首先,用你喜欢的文本编辑器打开该文件:
sudo nano /etc/fstab
添加一行与以下内容类似的新行:
/dev/sda /mnt/sdb ext4 defaults 0 0
/etc/fstab 文件是文件系统表的配置文件。它包含了一组用于控制文件系统使用方式的规则。通过大大减少可能的错误,它简化了每次使用时手动挂载和卸载每个磁盘的需求。该表有六列结构,每列对应特定的参数。参数的顺序是固定的,必须按正确顺序排列才能正常工作:
-
设备名称:可以使用 UUID 或已挂载设备名称
-
挂载点:设备所在的目录,或者设备将被挂载的目录
-
文件系统类型:所使用的文件系统类型
-
选项:显示的选项,如果有多个选项,则用逗号分隔
-
0= 不进行备份,1= 执行 dump 工具备份 -
0= 不进行fsck文件系统检查,1代表根文件系统,2代表其他分区
通过更新/etc/fstab文件,挂载将变为永久性的,不会受到任何关机或系统重启的影响。通常,/etc/fstab文件只存储内部硬盘分区和文件系统的信息。外部硬盘或 USB 驱动器会通过内核的硬件抽象层(HAL)自动挂载到/media下。
到目前为止,你应该已经掌握了在 Linux 中管理分区的方法,但还有一种我们尚未讨论的分区类型:交换分区。在下一节中,我们将介绍交换在 Linux 中的工作原理。
交换分区
Linux 使用了一个健壮的交换(swap)实现。当物理内存满时,虚拟内存会通过交换使用硬盘空间。这些额外的空间可以提供给那些没有使用完所有分配内存的程序,或者在内存压力很大时使用。交换通常是通过一个或多个专用分区来完成的,因为 Linux 允许多个交换区域。推荐的交换空间大小至少是系统总内存的大小。要查看系统上实际使用的交换空间,可以连接/proc/swaps文件或使用free命令来查看交换的利用率,如下图所示:

图 6.18 – 检查当前使用的交换空间
如果系统没有设置交换空间,你可以将一个分区格式化为交换空间并激活它。执行此操作的命令如下:
mkswap /dev/sda1
swapon /dev/sda1
操作系统会将文件内容缓存到内存中,以尽量避免使用交换空间。这是因为内存的工作速度比硬盘或硬盘驱动器要快得多。只有在可用内存有限时才会使用交换空间。然而,内核使用的内存永远不会被交换;只有用户空间使用的内存才会被交换。这保证了内核的数据完整性。请参考我们在第五章中使用的工具,查看 Linux 中的内存使用情况。
文件系统和分区是任何磁盘管理任务的基础,但管理员仍然需要克服一些难题,这可以通过使用逻辑卷来解决。这就是为什么在下一节中,我们将介绍 LVM。
在 Linux 中介绍 LVM
你们中的一些人可能已经听说过LVM。对于那些不知道它是什么的人,我们将在本节简要说明。假设你的硬盘空间已满。你总是可以将数据迁移到一个更大的硬盘并替换小的那个,但这需要系统重启并导致不必要的停机。为了解决这个问题,你可以考虑使用 LVM,它提供了更大的灵活性和效率。通过使用 LVM,你可以在仍在使用的情况下,向现有的卷组中添加更多的物理硬盘。这样仍然可以将数据迁移到新硬盘,但无需停机——一切都可以在文件系统在线的情况下完成。
在 Linux 中用于 LVM 管理的工具包括pvcreate、vgcreate、vgdisplay、lvcreate、lvextend和lvdisplay。让我们来学习如何使用它们。
由于我们还没有配置 LVM 的系统,我们将展示如何在另一个系统上创建新的 LVM 卷,该系统有两个内部硬盘:一个上安装了操作系统,另一个是空闲的。我们将使用 Debian GNU/Linux,但这些命令对于任何其他基于 Linux 的操作系统都是相同的。
按照以下步骤创建 LVM 卷:
-
使用
fdisk命令验证可用磁盘的名称(你也可以使用lsblk来完成这一步):/dev/sda. -
使用
pvcreate命令创建 LVM 物理卷:wipefs utility. The output is shown in the following screenshot:

图 6.19 – 使用 pvcreate 创建 LVM 物理卷
-
使用
vgcreate命令创建一个新的卷组,将新物理卷添加到其中:vgdisplay command:

图 6.20 – 创建并查看新卷的详细信息
- 现在,让我们使用
lvcreate从卷组中使用一些可用空间创建一个逻辑卷。使用-n选项为逻辑卷添加名称,使用-L选项设置大小(我们创建了一个名为projects的 5 GB 逻辑卷):

图 6.21 – 使用 lvcreate 创建逻辑卷
-
检查逻辑卷是否存在:
sudo ls /dev/mapper/newvolume-projects -
新创建的设备只有在使用已知的文件系统格式化并挂载之后才能使用,方法与普通分区相同。首先,让我们格式化新卷:

图 6.22 – 将新逻辑卷格式化为 Ext4 文件系统
- 现在,是时候挂载逻辑卷了。首先,创建一个新的目录,并将逻辑卷挂载到该目录中。然后,使用
df命令检查其大小:

图 6.23 – 挂载逻辑卷
-
到目前为止实施的所有更改都不是永久性的。为了使它们永久生效,你需要编辑
/etc/fstab文件,并在文件中添加以下内容:vgdisplay command to see the following details:lvextend 命令。我们将把初始大小扩展 5 GB,总共为 10 GB。以下是一个示例:

图 6.24 – 使用 lvextend 扩展逻辑卷
- 现在,使用
resize2fs调整文件系统的大小以适应逻辑卷的新大小,并使用df检查大小:

图 6.25 – 使用 resize2fs 调整逻辑卷的大小,并使用 df 检查大小
LVM 是一个高级主题,对于任何 Linux 系统管理员来说,都必不可少。我们在这一部分提供的简单操作示例只展示了你需要使用 LVM 的基本操作。如果需要,你可以深入研究这个主题。
在接下来的部分中,我们将讨论更多高级的 LVM 主题,包括如何创建完整的文件系统快照。
LVM 快照
什么是 LVM 快照?它是 LVM 逻辑卷的冻结实例。更具体来说,它使用 写时复制 技术。这项技术会监控现有卷的每一个块,当块发生变化时,由于新的写入,该块的值将被复制到快照卷中。
快照会持续不断地创建并即时生效,直到它们被删除为止。这样,你可以从任何快照创建备份。由于快照会不断变化,受写时复制技术的影响,在创建快照时,应该提前考虑快照的大小。如果可能的话,考虑在快照存在期间,数据将会发生多少变化。一旦快照满了,它将会被自动禁用。
创建一个新的快照
要创建一个新的快照,可以使用 lvcreate 命令,并加上 -s 选项。你还可以使用 -L 选项指定大小,使用 -n 选项为快照命名,示例如下:

图 6.26 – 使用 lvcreate 命令创建 LVM 快照
在前面的命令中,我们设置了 5 GB 的大小并使用了名称 linux-snapshot-01。命令的最后部分指定了我们为其创建快照的卷的目标位置。要列出新创建的快照,可以使用 lvs 命令:

图 6.27 – 列出可用的卷和新创建的快照
要了解更多关于逻辑卷的信息,请运行lvdisplay命令。输出将显示所有卷的信息,其中你会看到我们刚刚创建的快照。
当我们创建快照时,给它设置了 5 GB 的大小。现在,我们想将其扩展到源卷的大小,即 10 GB。我们将使用 lvextend 命令来实现这一点:

图 6.28 – 将快照从 5 GB 扩展到 10 GB
如前面的截图所示,快照卷使用的名称与我们使用的名称不同。尽管我们为快照卷使用了名称 linux-snapshot-01,但如果我们列出 /dev/mapper/ 目录,看到的名称实际上会包含两个额外的连字符。这是用来表示逻辑卷文件的命名约定。
现在你已经知道如何创建快照,让我们来学习如何恢复快照。
恢复快照
要恢复一个快照,首先,你需要卸载文件系统。要卸载,我们将使用umount命令:
sudo umount /home/alexandru/LVM
然后,我们可以使用lvconvert命令恢复快照。在快照合并到源后,我们可以使用lvs命令检查这一点。两个命令的输出如以下截图所示:

图 6.29 – 恢复和检查快照
合并后,快照会被自动移除。
我们现在已经涵盖了 Linux 中 LVM 的所有基础知识。LVM 比普通的磁盘分区更为复杂,可能会让许多人感到望而却步,但在需要时它能够展现其优势。然而,它也有一些缺点——例如,在灾难恢复或硬件故障发生时,它可能增加不必要的复杂性。但撇开这些不谈,它仍然值得学习。
总结
管理文件系统和磁盘是任何 Linux 系统管理员的重要任务。了解 Linux 中设备的管理方式,以及如何格式化和分区磁盘,是非常必要的。此外,学习 LVM 也很重要,因为它提供了一种灵活的分区管理方式。
掌握这些技能将为你提供任何基础管理任务的坚实基础。在接下来的章节中,我们将向你介绍 Linux 中广阔的网络管理领域。
问题
如果你成功浏览了本章的部分内容,你可能想回顾一下关于 Linux 文件系统和磁盘管理的一些重要细节:
-
想到另一个工具用于处理磁盘并安装它。
使用
parted并从命令行使用它。你也可以从 GUI 使用 GParted。 -
尝试使用 Disks(在 GNOME 中)和 KDE Partition Manager(在 KDE 中),并将命令行界面并排使用。
提示:保持两个应用程序打开,并将命令行工具并排使用。尝试在保持 GUI 应用程序打开的同时从命令行格式化和挂载磁盘。
-
使用不同的文件系统格式化新的分区。
使用
btrfs替代ext4。 -
探索你的文件系统和磁盘。
lsblk、df和fdisk。
进一步阅读
如需了解本章中涵盖的更多信息,请参考以下 Packt 出版的书籍:
-
Linux 管理最佳实践,作者:Scott Alan Miller
-
Mastering Ubuntu Server – 第四版,作者:Jay LaCroix
第七章:与 Linux 的网络连接
Linux 网络是一个广阔的领域。在过去几十年里,关于 Linux 网络管理内部机制的书籍和参考资料数不胜数。有时,仅仅是理解基本概念,对初学者和高级用户来说都会感到不小的压力。本章提供了一个相对简洁的 Linux 网络概述,重点介绍网络通信层、套接字和端口、网络服务和协议以及网络安全。
我们希望本章内容既能为初学者提供一个舒适的 Linux 网络基本概念入门,也能为高级 Linux 管理员提供一个很好的复习资料。
在本章中,我们将涵盖以下主题:
-
探索基础网络——重点介绍计算机网络、网络模型、协议、网络地址和端口。我们还将介绍一些使用命令行终端配置 Linux 网络设置的实际操作。
-
使用网络服务——介绍在 Linux 上运行的常见网络服务器。
-
理解网络安全。
技术要求
在本章中,我们将一定程度上使用 Linux 命令行。建议安装一个工作中的 Linux 发行版,可以是在 虚拟机(VM)或桌面平台上。如果你还没有安装,请返回到第一章,安装 Linux,它将引导你完成安装过程。本章中大多数命令和示例使用的是 Ubuntu 和 Fedora,但同样适用于其他任何 Linux 平台。
探索基础网络
今天,几乎无法想象一台没有连接到某种网络或互联网的计算机。我们的日益增长的在线存在、云计算、移动通信和 物联网(IoT)都离不开为底层数据流量提供服务的高度分布式、高速、可扩展的网络;然而,现代互联网背后的基本网络原理已有几十年历史。网络和通信范式将继续发展,但一些原始的基础元素和概念仍将对塑造未来通信的基石产生持久影响。
本节将介绍其中的一些网络基础知识,并希望能激发你进一步探索的兴趣。让我们从计算机网络开始。
计算机网络
计算机网络是指通过物理介质(电缆、无线、光纤)连接的两台或更多计算机(或节点),并通过标准的协议集进行相互通信。从宏观角度看,网络通信基础设施包括计算机、设备、交换机、路由器、以太网或光纤电缆、无线环境以及各种网络设备。
除了物理连接和布置外,网络还通过网络拓扑、层级和相关的数据流定义了逻辑布局。一个逻辑网络层次结构的例子是非军事区(DMZ)、防火墙和内部网络的三层结构。DMZ 是组织面向外部的网络,具有额外的安全层来防御公共互联网。防火墙控制着 DMZ 与内部网络之间的网络流量。
网络设备的标识由以下几个方面决定:
-
网络地址:这些有助于通过通信协议定位网络上的节点,例如互联网协议(IP)(有关 IP 的更多内容,请参见本章后面的TCP/IP 协议部分)
-
主机名:这些是与设备相关的用户友好的标签,比网络地址更容易记住
一种常见的分类标准是考虑计算机网络的规模和扩展。让我们来介绍一下局域网(LANs)和广域网(WANs):
-
局域网(LANs):LAN 表示一组连接并位于单一物理位置的设备,例如私人住宅、学校或办公室。LAN 可以是任何规模,从只有少数设备的家庭网络,到拥有成千上万用户和计算机的大型企业网络。
无论网络的规模如何,LAN 的基本特征是它将设备连接在一个有限的区域内。LAN 的例子包括单户住宅的家庭网络或你所在的咖啡店的免费无线服务。
关于 LAN 的更多信息,请参见
www.cisco.com/c/en/us/products/switches/what-is-a-lan-local-area-network.html。当计算机网络跨越多个区域或多个互联的 LAN 时,WAN 开始发挥作用。
-
广域网(WANs):WAN 通常是一个由多个或分布式的局域网(LAN)相互通信的网络。从这个角度看,我们可以将互联网视为全球最大的 WAN。WAN 的一个例子是跨国公司在全球各地地理分布的办公地点的计算机网络。有些 WAN 是由服务提供商构建的,供全球各地的企业和机构租用。
WAN 有几种变体,具体取决于它们的类型、范围和用途。WAN 的典型例子包括个人区域网络(PANs)、城市区域网络(MANs)和云或互联网区域****网络(IANs)。
关于 WAN 的更多信息,请参见
www.cisco.com/c/en/us/products/switches/what-is-a-wan-wide-area-network.html。
我们认为,对于基础网络原理的适当介绍应当包括简要介绍支配网络通信的理论模型。接下来,我们将讨论这一点。
OSI 模型
开放系统互联(OSI)模型是一个理论性的表示,描述了计算机系统之间通过网络进行多层次通信的机制。OSI 模型于 1983 年由国际标准化组织(ISO)提出,旨在为不同计算机系统之间的通信提供标准。
我们可以将 OSI 模型视为网络通信的通用框架。如以下图所示,OSI 模型定义了七层协议栈,指引着通信流的方向:

图 7.1 – OSI 模型
在前述图所示的分层视图中,通信流从上至下(在发送端)或从下至上(在接收端)移动。在详细查看每一层之前,我们先简要解释一下 OSI 模型以及数据封装和解封装的工作原理。
OSI 模型中的数据封装和解封装
在使用网络将数据从一台计算机传输到另一台计算机时,需要遵循一些特定的规则。在 OSI 模型中,网络数据流有两种不同的传输方式。一种是从第 7 层到第 1 层的下行传输,这被称为数据封装。另一种是从第 1 层到第 7 层的上行传输,这被称为数据解封装。数据封装表示从一台计算机向另一台计算机发送数据的过程,而数据解封装表示接收数据的过程。让我们更详细地了解这些过程:
-
封装:在发送数据时,来自一台计算机的数据会被转换为可以通过网络发送的格式,并且在经过协议栈的每一层时,数据会附加额外的信息。应用层(第 7 层)是用户直接与应用程序交互的地方。接着,数据会通过表示层(第 6 层)和会话层(第 5 层),在这些层中,数据被转化为可用的格式。在传输层(第 4 层),数据被分割成较小的块(段),并获得一个新的 TCP 头部。在网络层(第 3 层),数据被称为数据包,获得一个 IP 头部,并被发送到数据链路层(第 2 层),在此它被称为帧,并包含 TCP 和 IP 头部。在第 2 层,每个帧都会接收到源地址和目的地址的硬件信息(媒体访问控制(MAC)地址),以及网络层中将要使用的协议的信息(由逻辑链路控制(LLC)数据通信协议创建)。此时,添加了一个新的字段,称为帧校验序列(FCS),用于检查错误。接着,帧会被传递到物理层(第 1 层)。
-
去封装:当数据接收时,过程是相同的,但顺序是相反的。从物理层(第一层)开始,首先发生同步,然后帧通过数据链路层(第二层),在那里进行错误检查,通过验证 FCS 字段。这一过程称为循环冗余检查(CRC)。现在是数据包的数据,经过所有其他层。此时,在封装过程中添加的头信息被去除,直到它们到达上层并准备在目标计算机上使用。以下图形提供了详细的解释:

图 7.2 – OSI 模型中的封装与去封装
让我们详细看一下这些层,并描述它们在塑造网络通信中的功能。
物理层
物理层(或第一层)由连接设备并提供通信服务的网络设备或基础设施组成,例如电缆、无线或光学环境、连接器和交换机。该层处理原始比特流与通信媒介(包括电信号、无线电信号或光学信号)之间的转换,同时调节相应的比特率控制。
在物理层上运行的协议示例包括以太网、Universal Serial Bus(USB)和数字用户线(DSL)。
数据链路层
数据链路层(或第二层)在网络中建立两个直接连接设备之间可靠的数据流,无论是作为广域网中的相邻节点,还是局域网中的设备。数据链路层的责任之一是流量控制,适应物理层的通信速度。在接收设备上,数据链路层修正源自物理层的通信错误。数据链路层由以下子系统组成:
-
媒体访问控制(MAC):此子系统使用 MAC 地址来标识和连接网络中的设备。它还控制设备访问权限,以在网络上传输和接收数据。
-
逻辑链路控制(LLC):该子系统识别并封装网络层协议,并在传输或接收数据时执行错误检查和帧同步。
数据链路层控制的协议数据单元也被称为帧。帧是数据传输单元,充当单个网络数据包的容器。网络数据包在下一个 OSI 层(网络层)中被处理。当多个设备同时访问同一物理层时,可能会发生帧碰撞。数据链路层协议可以检测并恢复此类碰撞,并进一步减少或防止它们的发生。
例如,还有以太网帧,这些是为 MAC 实现定义的封装数据。原始的 IEEE 802.3 以太网格式、802.3 子网络访问协议 (SNAP) 和以太网 II(扩展)帧格式也可用。
另一个数据链路协议的例子是 点对点协议 (PPP),这是一种用于高速宽带通信网络的二进制网络协议。
网络层
网络层(或 第 3 层)发现网络上设备之间的最佳通信路径(或路由)。该层使用基于参与数据交换设备 IP 地址的路由机制,将数据包从源端传送到目标端。
在发送端,网络层将源自 传输层 的数据段拆分成网络包。在接收端,数据帧从下层(数据链路层)重新组装成数据包。
一个在网络层运行的协议是 互联网控制消息协议 (ICMP)。ICMP 被网络设备用于诊断网络通信问题。当请求的端点不可用时,ICMP 会通过发送诸如 目标网络不可达、定时器过期、源路由失败 等消息来报告错误。
传输层
传输层(或 第 4 层)处理 数据段 或 数据报。该层主要负责将数据从源传输到目标,并保证特定的 服务质量 (QoS)。在发送端,来自上层(会话层)的数据被拆分成数据段;在接收端,传输层将从下层(网络层)接收到的数据包重新组装成数据段。
传输层通过流量控制和错误控制功能保持数据传输的可靠性。流量控制功能调整具有不同连接速度的端点之间的数据传输速率,以避免发送方压倒接收方。当接收到的数据不正确时,错误控制功能可能会请求数据重传。
传输层协议的例子包括 传输控制协议 (TCP) 和 用户数据报协议 (UDP).
会话层
会话层(或 第 5 层)控制设备间通信连接通道(或会话)的生命周期。在这一层,会话或网络连接通常由网络地址、套接字和端口定义。我们将在 套接字和端口 以及 IP 地址 部分解释这些概念。会话层负责通信通道或会话内数据传输的完整性。例如,如果会话被中断,数据传输会从先前的检查点恢复。
一些典型的会话层协议包括远程过程调用(RPC)协议,通常用于进程间通信,以及网络基础输入输出系统(NetBIOS),这是一个文件共享和名称解析协议。
表示层
表示层(或第六层)充当应用层和会话层之间的数据转换层。在发送端,这一层会将数据格式化为独立于系统的表示形式,然后发送到网络上。在接收端,表示层将数据转化为适合应用的格式。此类转换的例子包括加密与解密、压缩与解压缩、编码与解码,以及序列化与反序列化。
通常,表示层和应用层之间没有实质性的区别,主要是由于各种数据格式与其使用应用之间的相对紧密耦合。标准数据表示格式包括美国信息交换标准代码(ASCII)、可扩展标记语言(XML)、JavaScript 对象表示法(JSON)、联合图像专家组(JPEG)、ZIP 等。
应用层
应用层(或第七层)是离 OSI 模型中最终用户最近的一层。该层以某种有意义的方式收集或提供应用数据的输入或输出。该层并不包含或运行应用程序本身。相反,它充当应用程序、实现通信组件与底层网络之间的抽象。与应用层交互的典型应用程序包括网页浏览器和电子邮件客户端。
第七层协议的几个例子包括 DNS 协议、超文本传输协议(HTTP)、文件传输协议(FTP)以及电子邮件消息协议,如邮局协议(POP)、互联网消息访问协议(IMAP)和简单邮件传输协议(SMTP)。
在结束之前,我们需要注意,OSI 模型是一个通用的网络通信层次模型,并为网络通信的工作原理提供了理论指导。类似的,但更具实际应用的网络协议栈模型是 TCP/IP 模型。这两种模型在网络设计、实现、故障排除和诊断方面都非常有用。OSI 模型使网络操作员能够很好地理解完整的网络协议栈,从物理媒介到应用层,每一层都有协议数据单元(PDU)和通信内部结构。然而,TCP/IP 模型相对简化,将一些 OSI 模型的层次合并为一层,并且采用了一种更为以协议为中心的网络通信方式。我们将在下一节中详细探讨这一点。
TCP/IP 网络协议栈模型
TCP/IP 模型是对 OSI 网络堆栈的四层解释,其中一些等效的 OSI 层被合并,如下图所示:

图 7.3 – OSI 和 TCP/IP 模型
按时间顺序,TCP/IP 模型比 OSI 模型更早。它最初由美国 国防部(DoD)提出,作为由 国防高级研究计划局(DARPA)开发的一个互联网络项目的一部分。这个项目最终成为了现代互联网的雏形。
TCP/IP 模型的各个层次封装了与其对应的 OSI 层相似的功能。以下是 TCP/IP 模型中各层的总结。
网络接口层
网络接口层负责通过物理介质(如电缆、无线或光纤)传输数据。该层操作的网络协议包括以太网、令牌环和帧中继。该层对应于 OSI 模型中的 物理层和数据链路层 的组合。
网络层
网络层提供 无连接 的数据传输服务,允许网络节点之间进行数据交换。无连接协议描述了一种网络通信模式,其中发送方将数据传输给接收方,而无需双方事先建立连接。该层负责在发送端将数据拆解为网络数据包,并在接收端将其重新组装。网络层通过路由功能来识别网络节点之间的最佳路径。该层与 OSI 模型中的 网络层 对应。
传输层
传输层(也称为 传输层 或 主机到主机层)负责保持连接网络节点之间的通信会话。传输层实现了错误检测和修正机制,以确保端点之间可靠的数据传输。该层与 OSI 模型中的 传输层 相对应。
应用层
应用层提供了软件应用程序与底层网络之间的数据通信抽象。该层对应于 OSI 模型中的 会话层、表示层和应用层 的组合。
如本章前面所讨论的,TCP/IP 模型是一个以协议为中心的网络堆栈表示模型。该模型通过逐步定义和开发用于互联网通信的网络协议,为互联网奠定了基础。这些协议统称为 IP 套件。以下部分描述了一些最常见的网络协议。
TCP/IP 协议
在本节中,我们将描述一些广泛使用的网络协议。这里提供的参考内容不应被视为全面的指南。TCP/IP 协议数量庞大,全面研究超出了本章的范围。然而,有一些值得探索的协议,它们在日常网络通信和管理工作流中经常被使用。
以下列表简要描述了每个 TCP/IP 协议及其相关的请求评论(RFC)标识符。RFC 代表详细的技术文档——在我们这种情况下是协议的文档——通常由互联网工程任务组(IETF)编写。欲了解更多有关 RFC 的信息,请访问www.ietf.org/standards/rfcs/。以下是最常用的协议:
-
IP:IP(RFC 791)通过固定长度的地址(也称为 IP 地址)标识网络节点。IP 地址将在下一节中更详细地描述。IP 协议使用数据报作为数据传输单元,并提供大数据报的分片和重组功能,以适应小数据包网络(并避免传输延迟)。IP 协议还提供路由功能,用于查找网络节点之间的最佳数据传输路径。IP 在 OSI 模型的网络层(Layer 3)中运行。
-
ARP:地址解析协议(ARP)(RFC 826)由 IP 协议用于将 IP 网络地址(具体来说是IP 版本 4或IPv4)映射到数据链路协议使用的设备 MAC 地址。ARP 在 OSI 模型的数据链路层(Layer 2)中运行。
-
NDP:邻居发现协议(NDP)(RFC 4861)类似于 ARP 协议,也控制IP 版本 6(IPv6)地址映射。NDP 在 OSI 模型的数据链路层(Layer 2)中运行。
-
ICMP:ICMP(RFC 792)是一个用于检查传输问题的支持协议。当设备或节点在给定超时时间内不可达时,ICMP 会报告错误。ICMP 在 OSI 模型的网络层(Layer 3)中运行。
-
TCP:TCP(RFC 793)是一种面向连接的、高度可靠的通信协议。TCP 要求节点之间建立逻辑连接(如握手)后,才能启动数据交换。TCP 在 OSI 模型的传输层(Layer 4)中运行。
-
UDP:UDP(RFC 768)是一种无连接的通信协议。与 TCP 相比,UDP 没有握手机制。因此,使用 UDP 时无法保证数据传输的可靠性。它也被称为尽力而为协议。UDP 使用数据报作为数据传输单元,适用于对错误检查不严格要求的网络通信。UDP 在 OSI 模型的传输层(Layer 4)中运行。
-
动态主机配置协议(DHCP):DHCP(RFC 2131)提供了一个框架,用于请求和传递 TCP/IP 网络上设备所需的主机配置信息。DHCP 实现了可重用 IP 地址的自动(动态)分配和其他配置选项。DHCP 被视为 OSI 模型中的应用层协议(第 7 层),但初始的 DHCP 发现机制在数据链路层(第 2 层)操作。
-
dns.google.com)转换为 IP 地址(例如8.8.8.8)。DNS 协议在 OSI 模型中操作于应用层(第 7 层)。 -
HTTP:HTTP(RFC 2616)是互联网的车辆语言。HTTP 是一种基于请求和响应的无状态应用级协议,用于客户端应用程序(例如浏览器)和服务器端点(例如 Web 服务器)之间的通信。HTTP 支持从文本到图像和视频流等多种数据格式。HTTP 在 OSI 模型中操作于应用层(第 7 层)。
-
FTP:FTP(RFC 959)是一个用于从 FTP 服务器请求文件传输的标准协议。FTP 在 OSI 模型中操作于应用层(第 7 层)。
-
TELNET:终端网络协议(TELNET)(RFC 854)是一种应用层协议,提供客户端和服务器机器之间的双向文本导向网络通信,使用虚拟终端连接。TELNET 在 OSI 模型中操作于应用层(第 7 层)。
-
SSH:安全外壳协议(SSH)(RFC 4253)是一种安全的应用层协议,封装强加密和加密主机认证。SSH 使用客户端和服务器机器之间的虚拟终端连接。SSH 在 OSI 模型中操作于应用层(第 7 层)。
-
SMTP:SMTP(RFC 5321)是用于在电子邮件客户端(例如 Outlook)和电子邮件服务器(例如 Exchange Server)之间发送和接收电子邮件的应用层协议。SMTP 支持强加密和主机认证。SMTP 在 OSI 模型中操作于应用层(第 7 层)。
-
SNMP:简单网络管理协议(SNMP)(RFC 1157)用于远程设备管理和监控。SNMP 在 OSI 模型中操作于应用层(第 7 层)。
-
NTP:网络时间协议(NTP)(RFC 5905)是用于在网络中多台机器之间同步系统时钟的互联网协议。NTP 在 OSI 模型中操作于应用层(第 7 层)。
大多数之前列举的互联网协议使用 IP 协议来识别参与通信的设备。网络上的设备通过 IP 地址唯一标识。让我们更仔细地检查这些网络地址。
IP 地址
IP 地址是网络中设备的固定长度唯一标识符(UID)。设备基于 IP 地址来定位并与彼此通信。IP 地址的概念非常类似于住宅的邮政地址,通过地址来发送邮件或包裹到目的地。
最初,IP 将 IP 地址定义为一个 32 位数字,称为IPv4 地址。随着互联网的发展,网络中的 IP 地址总数已被耗尽。为了解决这个问题,IP 协议的一个新版本设计了一个 128 位的 IP 地址编号方案。一个 128 位的 IP 地址也被称为IPv6 地址。
在接下来的几节中,我们将更深入地了解在 IP 地址中起重要作用的网络构造,例如 IPv4 和 IPv6 地址格式、网络类别、子网和广播地址。
IPv4 地址
一个.)。这四组数字中的每个数字是一个介于0和255之间的整数。一个 IPv4 地址的例子是192.168.1.53。
下图展示了 IPv4 地址的二进制表示:

图 7.4 – 网络类别
IPv4 地址空间的总数限制为 4,294,967,296(2³²)个地址(大约 40 亿)。其中,大约 1800 万个地址保留用于特殊目的(例如,私有网络),约有 2.7 亿个是组播地址。
组播地址是一组 IP 地址的逻辑标识符。如需了解更多关于组播地址的信息,请参考 RFC 6308(tools.ietf.org/html/rfc6308)。
网络类别
在互联网早期,IPv4 地址中的最高位字节(第一组)表示网络号。随后的字节进一步表示网络层次结构和子网,最低位字节标识设备本身。这个方案很快证明不足以满足网络层次结构和分隔的需求,因为它仅允许 256(2⁸)个网络,由 IPv4 地址的首字节表示。随着额外网络的加入,每个网络都有自己的身份,IP 地址规范需要进行特别修订,以适应标准模型。1981 年推出的 Classful Network 规范通过根据地址的前 4 位将 IPv4 地址空间划分为五个类别,解决了这个问题,具体如下面的图所示:

图 7.5 – 网络类别
如需了解更多关于网络类别的信息,请参考 RFC 870(tools.ietf.org/html/rfc870)。在上图中,最后一列指定了每个网络类别的默认子网掩码。接下来我们将查看子网(或子网络)。
子网
子网络(或子网)是 IP 网络的逻辑子划分。子网的引入是为了识别属于同一网络的设备。处于同一网络的设备的 IP 地址具有相同的最高有效位组。子网定义将 IP 地址逻辑上划分为两个字段:网络标识符和主机标识符。子网的数字表示称为子网掩码或网掩码。下图提供了网络标识符和主机标识符的示例:

图 7.6 – 带有网络和主机标识符的子网
对于我们的 IPv4 地址(192.168.1.53),我们可以设计出一个网络标识符192.168.1,其中主机标识符是53。生成的子网掩码如下所示:
192.168.1.0
我们丢弃了子网掩码中的最低有效组,代表主机标识符(53),并将其替换为0。这里的0表示子网中的起始地址。换句话说,子网中允许任何主机标识符值在0到255的范围内。例如,192.168.1.92是192.168.1.0网络中的一个有效(并被接受的)IP 地址。
子网的另一种表示方法使用所谓的/和前缀的位长度。在我们的例子中,192.168.1.0子网的 CIDR 表示法如下:
192.168.1.0/24
网络地址中的前三组构成3 x 8 = 24位,因此采用/24表示法。
通常,它以100开始,以125结束。让我们看看如何实现这一点。
首先,让我们看看192.168.1.100的二进制表示:
11000000.10101000.00000001.100). Remember that we want the network to start with 100 and end with 125\. This means that the closest binary value to the reserved 99 addresses that would not be permitted in our subnet is *96 = 64 + 32*. The equivalent binary value for it is as follows:
- 这些位将被添加到网络地址(192.168.1)中已经保留的 24 位中,总共占用27 = 24 + 3位。以下是等效的表示:
11111111.11111111.11111111.11100000
因此,生成的子网掩码如下所示:
255.255.255.224
对应子网的 CIDR 表示法如下所示:
192.168.1.96/27
主机标识符组中的剩余五个位表示子网中可能的2⁵ = 32个地址,从97开始。这将限制最大主机标识符值为127 = 96 + 32 – 1(我们减去 1 是为了排除包含在 32 个地址总数中的起始数字 97)。在这 32 个地址的范围内,最后一个 IP 地址保留作为广播地址,如图所示:
192.168.1.127
广播地址是保留为网络或子网中的最大数字,在适用时。回到我们的例子,除去广播地址后,子网中最大主机 IP 地址如下所示:
192.168.1.126
你可以在RFC 1918中了解更多关于子网的内容 (tools.ietf.org/html/rfc1918)。由于我们提到了广播地址,接下来我们快速了解一下它。
广播地址
广播地址是在网络或子网中预留的 IP 地址,用于向属于该网络的所有设备传送集体消息(数据)。广播地址是网络或子网中的最后一个 IP 地址(如果适用)。
例如,192.168.1.0/24 网络的广播地址是 192.168.1.255。在上一节中的示例中,192.168.1.96/27 子网的广播地址是 192.168.1.127(127 = 96 + 32 – 1)。
如需了解更多有关广播地址的信息,请参阅 www.sciencedirect.com/topics/computer-science/broadcast-address。
IPv6 地址
IPv6 地址是一个 128 位(16 字节)的数字,通常表示为最多八组 2 字节(16 位)的数字,每组数字之间用冒号(:)分隔。每组数字是一个十六进制数字,值在 0000 和 FFFF 之间。下面是一个 IPv6 地址的示例:
2001:0b8d:8a52:0000:0000:8b2d:0240:7235
上述 IPv6 地址的等效表示如下:
2001:b8d:8a52::8b2d:240:7235/64
在第二种表示法中,前导零被省略,所有零组(0000:0000)被压缩成一个空组(::)。末尾的/64符号表示的是1和128。
在我们的例子中,前缀长度为 64(4 x 16)位,子网看起来是这样的:
2001:b8d:8a52::
子网表示的是前四个组(2001、0b8d、8a52 和 0000),这将总共产生4 x 16 = 64位。在简化表示的 IPv6 子网中,前导零被省略,所有零组被压缩成 ::。
IPv6 的子网划分与 IPv4 非常相似。我们在这里不会详细讨论,因为相关概念已在IPv4 地址部分介绍。有关 IPv6 的更多信息,请参阅RFC 2460(tools.ietf.org/html/rfc2460)。
现在,既然你已经熟悉了 IP 地址,接下来介绍一些与 IP 地址的软件实现相关的网络构造——即套接字和端口。
套接字和端口
套接字是一个用于表示网络节点的软件数据结构,供通信使用。虽然它是一个编程概念,但在 Linux 中,网络套接字最终是一个文件描述符,通过网络应用程序编程接口(API)进行控制。套接字被应用程序进程用于数据的发送和接收。应用程序可以创建和删除套接字。套接字不能在创建它的进程生命周期之外处于活动状态(发送或接收数据)。
网络套接字在 OSI 模型的传输层操作。套接字连接有两个端点——发送方和接收方。发送方和接收方都有一个 IP 地址。因此,套接字数据结构中的一个关键部分是拥有该套接字的端点的IP 地址。
两个端点通过网络进程使用这些套接字来创建和管理它们的套接字。发送方和接收方可以达成共识,使用多个连接来交换数据。有些连接甚至可能并行运行。我们如何区分这些套接字连接?单独的 IP 地址是不够的,这时 端口 就发挥了作用。
0 和 65535。通常,0 和 1024 之间的端口分配给系统上使用最频繁的服务。这些端口也被称为 知名端口。以下是一些知名端口及其相关网络服务的示例:
-
25: SMTP -
21: FTP -
22: SSH -
53: DNS -
67,68: DHCP(客户端 =68,服务器 =67) -
80: HTTP -
443: HTTP Secure(HTTPS)
1024 以上的端口号供一般使用,也称为 临时端口。
端口总是与 IP 地址关联。最终,套接字是 IP 地址和端口的组合。有关网络套接字的更多信息,请参阅 RFC 147(tools.ietf.org/html/rfc147)。有关常见端口的信息,请参阅 RFC 1340(tools.ietf.org/html/rfc1340)。
现在,让我们运用迄今为止所学的知识,看看如何配置 Linux 中的本地网络栈。
Linux 网络配置
本节描述了适用于 Ubuntu 和 Fedora 平台的 TCP/IP 网络配置,使用的是它们目前发布的最新版本。相同的概念适用于大多数 Linux 发行版,尽管其中一些网络配置工具和文件可能有所不同。
我们可以使用 ip 命令行工具来检索系统当前的 IP 地址,方法如下:
ip addr show
下面是输出的一个示例:

图 7.7 – 使用 ip 命令检索当前的 IP 地址
我们在这里突出显示了一些相关信息,例如网络接口 ID(2: enp1s0)和带有子网前缀的 IP 地址(192.168.122.117/24)。
接下来我们来看 Ubuntu 的网络配置。在编写本书时,Ubuntu 的发布版本是 22.04.2 LTS。
Ubuntu 网络配置
Ubuntu 22.04 提供了 netplan 命令行工具,以便轻松进行网络配置。netplan 使用一个配置文件,该文件位于 /etc/netplan/ 目录中,我们可以使用以下命令访问它:
ls /etc/netplan/
在我们的例子中,配置文件是 00-installer-config.yaml。
更改网络配置涉及编辑 netplan YAML 配置文件。作为良好的实践,我们应始终在更改之前备份当前的配置文件。更改网络配置通常包括设置动态或静态 IP 地址。我们将在接下来的几个部分中展示如何配置这两种类型的 IP 地址。我们将首先介绍动态 IP 地址配置。
动态 IP 配置
要启用动态(DHCP)IP 地址,我们必须编辑 netplan 配置文件,并将所选网络接口(我们选择的是 ens33)的 dhcp4 属性设置为 true(如 图 7.8 所示)。使用你喜欢的文本编辑器(在我们这里是 nano)打开 00-installer-config.yaml 文件:
sudo nano /etc/netplan/00-installer-config.yaml
以下是相关的配置摘录,并标出了关键点:

图 7.8 – 在 netplan 配置中启用 DHCP
保存配置文件后,我们可以使用以下命令测试相关更改:
sudo netplan try
我们将获得以下响应:

图 7.9 – 测试并接受 netplan 配置更改
netplan 关键字验证新的配置并提示用户接受更改。以下命令将当前更改应用到系统中:
sudo netplan apply
接下来,我们将使用 netplan 配置静态 IP 地址。
静态 IP 配置
要设置网络接口的静态 IP 地址,我们首先编辑 netplan 配置 YAML 文件,内容如下:
sudo nano /etc/netplan/00-installer-config.yaml
这是一个静态 IP 地址 192.168.122.22/24 的配置示例:

图 7.10 – 使用 netplan 配置静态 IP 示例
保存配置后,我们可以像在 动态 IP 部分中那样,使用以下命令测试并接受配置,更改并应用:
sudo netplan try
sudo netplan apply
有关 netplan 命令行工具的更多信息,请参见 netplan --help 或相关系统手册(man netplan)。
接下来我们将看看 Fedora 的网络配置。本文写作时,当前发布的 Fedora 版本为 37。
Fedora/RHEL 网络配置
从 Fedora 33 和 RHEL 9 开始,网络配置文件 不再 存放在 /etc/sysconfig/network-scripts/ 目录下。要了解有关新配置选项的更多信息,请阅读以下文件:
cat /etc/sysconfig/network-scripts/readme-ifcfg-rh.txt
在 Fedora/RHEL 中,配置网络的首选方法是使用 nmcli 工具。此位置已经被弃用,并且在 Fedora 中不再被 NetworkManager 使用;它仍然可以使用,但我们不推荐使用它。新的 NetworkManager 密钥文件存储在 /etc/NetworkManager/system-connections/ 目录中。
让我们使用一些基本的 nmcli 命令查看有关连接的信息。要了解 nmcli,请阅读相关的手册页。首先,让我们使用以下命令查找有关我们活动连接的信息:
nmcli connection show
输出将显示有关连接名称、UUID、类型和使用的设备的基本信息。以下截图显示了我们在 Fedora 37 虚拟机上的相关信息:

图 7.11 – 使用 nmcli 查看连接信息
与 Ubuntu 类似,在使用 Fedora/RHEL 时,更改网络配置通常涉及设置动态或静态 IP 地址。我们将在接下来的章节中展示如何配置这两种类型的 IP 地址。让我们先看看动态 IP 地址配置。
动态 IP 配置
要使用 ncmli 配置动态 IP 地址,我们可以运行以下命令:
sudo nmcli connection modify 'Wired connection 1' ipv4.method auto
ipv4.method auto 指令启用 DHCP。该命令没有输出;执行后,您将返回到提示符。您可以通过查看 /etc/NetworkManager/system-connections/ 目录来检查命令是否生效。在我们的案例中,目录内会有一个新的密钥文件。它与我们的连接名称相同。以下是一个摘录:

图 7.12 – 新的配置密钥文件
接下来,我们将配置静态 IP 地址。
静态 IP 配置
要使用 ncmli 执行等效的静态 IP 地址更改,我们需要运行多个命令。首先,我们必须设置静态 IP 地址,如下所示:
sudo nmcli connection modify 'Wired connection 1' ipv4.address 192.168.122.3/24
如果之前没有配置静态 IP 地址,我们建议在继续下一步之前保存前面的更改。可以使用以下代码保存这些更改:
sudo nmcli connection down 'Wired connection 1'
sudo nmcli connection up 'Wired connection 1'
接下来,我们必须设置网关和 DNS IP 地址,如下所示:
sudo nmcli connection modify 'Wired connection 1' ipv4.gateway 192.168.122.1
sudo nmcli connection modify 'Wired connection 1' ipv4.dns 8.8.8.8
最后,我们必须使用以下代码禁用 DHCP:
sudo nmcli connection modify 'Wired connection 1' ipv4.method manual
在进行这些更改后,我们需要使用以下代码重新启动 'Wired connection 1' 网络接口:
sudo nmcli connection down 'Wired connection 1'
sudo nmcli connection up 'Wired connection 1'
现在,让我们查看我们执行的所有命令的结果。重新启动连接后,我们来检查新的 IP 地址和网络密钥文件的内容。下图显示了我们通过使用 ip addr show 命令分配给系统的新的 IP(192.168.122.3):

图 7.13 – 检查新的 IP 地址
现在,让我们查看网络密钥文件的内容,看看为静态 IP 配置所做的更改。请记住,文件的位置是/etc/NetworkManager/system-connections/:

图 7.14 – 静态 IP 地址的新密钥文件配置
nmcli 工具是一个功能强大且有用的工具。在本章末尾,我们将为你提供一些有用的链接,帮助你进一步了解它。接下来,我们将看看如何在 openSUSE 上配置网络服务。
openSUSE 网络配置
openSUSE 提供了几种网络配置工具:Wicked 和 NetworkManager。根据官方的 SUSE 文档,Wicked 适用于各种类型的机器,从服务器到笔记本电脑和工作站,而 NetworkManager 仅用于笔记本电脑和工作站的配置,不适用于服务器配置。然而,在 openSUSE Leap 中,默认情况下,无论是桌面配置还是服务器配置,都会使用 Wicked,而笔记本电脑配置则默认使用 NetworkManager。
例如,在我们的主工作站(笔记本电脑)上,如果我们想查看 openSUSE Leap 默认运行的是哪个服务,可以使用以下命令:
sudo systemctl status network
输出会显示正在运行的服务,在我们的情况下,它是 NetworkManager:

图 7.15 – 检查哪个网络服务在 openSUSE 中运行
当在 openSUSE Leap 服务器虚拟机中运行相同命令时,结果会有所不同。输出显示 Wicked 默认运行。以下截图展示了一个示例:

图 7.16 – Wicked 在 openSUSE 服务器中运行
因此,我们将在本节中所有的示例都在 openSUSE Leap 服务器虚拟机上进行,从而使用 Wicked 作为默认的网络配置工具。在下一节中,我们将配置 openSUSE 机器上的动态 IP。
动态 IP 配置
在进行任何配置之前,让我们检查一下活动连接和设备。我们可以通过使用以下命令来完成此操作:
wicked show all
输出将显示所有活动设备。以下截图展示了我们机器上输出的摘录:

图 7.17 – 有关活动设备的信息
有两个活动连接,一个是回环(lo),另一个是以太网端口(eth0)。我们将仅显示与 eth0 相关的信息。Wicked 在 openSUSE 中存储配置文件的位置是 /etc/sysconfig/network。如果我们列出该目录内容,我们会看到其中已经有现有连接的配置文件:

图 7.18 – Wicked 配置文件的位置
在我们的案例中,可能你也会遇到相同的情况,感兴趣的文件叫做 ifcfg-eth0;我们可以用文本编辑器打开它,或者将其连接输出。让我们来看看文件的内容:

图 7.19 – 配置文件提供的信息
如前面的截图所示,提供的信息相对较少,但仍然相关。为了获得更详细的输出,我们可以使用以下命令:
sudo wicked show-config
这将直接向显示器提供更多相关信息。以下截图展示了我们输出的摘录,包含详细的 IPv4 DHCP 信息:

图 7.20 – Wicked 提供的详细信息
为了更好地理解该输出,我们建议您阅读config文件,并且如果有的话,查看dhcp文件,这些文件位于/etc/sysconfig/network目录中。它们提供了有关配置网络设备所需的特定变量和默认参数的信息。
安装操作系统时,通常默认设置动态 IP 地址。如果您的系统未配置动态 IP 地址,您只需要在/etc/sysconfig/network目录中创建一个配置文件,并根据您用来连接网络的设备为其命名,例如ifcfg-eth0。在该文件中,您只需提供三行内容,如图 7.19所示。以下命令是执行本段操作所需的:
-
使用
ipaddr命令检查您的设备名称:/etc/sysconfig/network directory:sudo nano /etc/sysconfig/network/ifcfg-eth0
- 提供 DHCP 配置的相关信息:
BOOTPROTO='dhcp' STARTMODE='auto' ZONE=public- 重启 Wicked 服务:
ping command. Here’s an example:ping google.com
在接下来的部分中,我们将展示如何设置静态 IP 配置。
静态 IP 配置
要设置静态 IP 配置,您需要手动为配置文件提供变量。这些文件与上一部分中介绍的文件相同。文件的路径是/etc/sysconfig/network。例如,您可以为eth0设备连接创建一个新文件,并提供所需的信息。让我们看一下使用我们 openSUSE Leap 服务器虚拟机的示例。但在此之前,我们建议您打开ifcfg工具的手册页,因为它们提供了有关本次操作所需变量的有价值信息。
因此,我们将按如下方式设置ifcfg配置文件:
- 首先,我们将检查 IP 地址和网络设备名称;在我们的案例中,动态分配的 IP 为
192.168.122.146,设备名称为eth0:

图 7.21 – IP 和设备信息
-
进入
/etc/sysconfig/network目录并编辑ifcfg-eth0文件;我们将使用以下变量进行静态 IP 配置:-
BOOTPROTO='static':这允许我们使用由IPADDR变量提供的固定 IP 地址 -
STARTMODE='auto':该接口将在启动时自动启用 -
IPADDR='192.168.122.144':我们为机器选择的 IP 地址 -
ZONE='public':firewalld工具使用的区域 -
PREFIXLEN='24':IPADDR变量中的位数
它将如下所示:
-

图 7.22 – 静态 IP 配置的新变量
-
保存更改到新文件,并重启 Wicked 守护进程:
sudo systemctl restart wickedd.service- 启用接口,以便更改生效:
ip addr show command again to check for the new IP address:

图 7.23 – 新分配给 eth0 的 IP 地址
到目前为止,你已经了解了如何在所有主要的 Linux 发行版中配置网络设备,并使用它们首选的工具。我们仅仅触及了这个话题的表面,但已经提供了足够的信息,帮助你开始在 Linux 中操作网络接口。更多信息,请随时阅读操作系统自带的手册页。在下一节中,我们将讨论主机名配置的问题。
主机名配置
要在 Linux 机器上获取当前主机名,我们可以使用hostname或hostnamectl命令,如下所示:
hostname
更改主机名的最便捷方法是使用hostnamectl命令。我们可以使用该命令的set-hostname参数将主机名更改为earth:
sudo hostnamectl set-hostname earth
让我们再次使用hostname命令来验证主机名的更改。你也可以使用hostnamectl命令来验证主机名。与hostname命令相比,hostnamectl命令的输出提供了更详细的信息,如下图所示:

图 7.24 – 使用不同命令获取当前主机名
另外,我们可以使用hostname命令来临时更改主机名,如下所示:
sudo hostname jupiter
然而,这个更改在重启后不会生效,除非我们还在/etc/hostname和/etc/hosts文件中更改主机名。在编辑这两个文件时,按照需要更改主机名。以下截图显示了命令的顺序:

图 7.25 – /etc/hostname 和 /etc/hosts 文件
在重新配置主机名后,通常需要注销再登录才能反映更改。主机名在一致的网络管理中非常重要,网络上的每个系统都应该设置相关的主机名。在接下来的章节中,你将了解 Linux 中的网络服务。
使用网络服务
在这一节中,我们将列出 Linux 上最常见的网络服务。并非所有这里提到的服务都在你选择的 Linux 平台上默认安装或启用。第九章,保护 Linux,和 第十章,灾难恢复、诊断和故障排除,将深入探讨如何安装和配置其中一些服务。本节的重点仍然是这些网络服务是什么,它们如何工作,以及它们使用哪些网络协议进行通信。
网络服务通常是一个系统进程,实施应用层(OSI 第 7 层)功能,用于数据通信目的。网络服务通常设计为对等或客户端-服务器架构。
在对等网络中,多个网络节点各自运行自己同等特权的网络服务实例,同时共享和交换一组公共数据。例如,一个 DNS 服务器网络,所有服务器共享并更新它们的域名记录。
客户端-服务器网络通常涉及一个或多个服务器节点和多个客户端与这些服务器进行通信。SSH 就是一个客户端-服务器网络服务的例子。SSH 客户端通过安全的终端会话连接到远程 SSH 服务器,可能是为了远程管理目的。
以下各小节简要描述了网络服务,我们鼓励你在第十三章或本章末推荐的其他相关标题中探索与这些网络服务相关的主题。我们从 DHCP 服务器开始。
DHCP 服务器
DHCP 服务器使用 DHCP 协议,使网络上的设备能够请求动态分配的 IP 地址。DHCP 协议在本章前面的TCP/IP 协议部分中有简要描述。
请求 DHCP 服务的计算机或设备会向网络发送一个广播消息(或查询),以定位 DHCP 服务器,后者提供所请求的 IP 地址和其他信息。DHCP 客户端(设备)与服务器之间的通信使用 DHCP 协议。
DHCP 协议的初始发现工作流在客户端和服务器之间发生在数据链路层(第 2 层)中。由于第 2 层使用网络帧作为 PDU,DHCP 发现包无法跨越本地网络边界。换句话说,DHCP 客户端只能与本地DHCP 服务器发起通信。
在初始的握手(在第 2 层)之后,DHCP 转向 UDP 作为其传输协议,使用数据报套接字(第 4 层)。由于 UDP 是无连接协议,DHCP 客户端和服务器在没有事先安排的情况下交换消息。因此,客户端和服务器的两个端点都需要一个众所周知的 DHCP 通信端口用于双向数据交换。这些端口是68(用于 DHCP 服务器)和67(用于 DHCP 客户端)。
DHCP 服务器维护一系列 IP 地址和其他客户端配置信息(如 MAC 地址和域服务器地址),用于网络上请求 DHCP 服务的每个设备。
DHCP 服务器使用 租赁机制 来动态分配 IP 地址。租赁一个 IP 地址受 租期 的制约,租期可以是有限的或无限的。当 IP 地址的租期到期时,DHCP 服务器可能会根据请求将其重新分配给其他客户端。设备通过定期向 DHCP 服务器请求 续租 来保持其动态 IP 地址。若未及时续租,设备的动态 IP 地址可能会丧失。延迟(或租期后)发出的 DHCP 请求可能会导致获取一个新的 IP 地址,尤其是在之前的地址已经被 DHCP 服务器分配的情况下。
从 Linux 机器查询 DHCP 服务器的一个简单方法是执行以下命令:
ip route
这是前面命令的输出:

图 7.26 – 查询 DHCP 信息的 IP 路由
输出的第一行提供了 DHCP 服务器(192.168.122.1)。
第十三章,配置 Linux 服务器,将进一步介绍安装和配置 DHCP 服务器的实际细节。
如需了解有关 DHCP 的更多信息,请参考 RFC 2131(tools.ietf.org/html/rfc2131)。
DNS 服务器
一个 wikipedia.org)将被转换为一个 IP 地址(例如 208.80.154.224)。名称解析协议是 DNS,在本章之前的 TCP/IP 协议 部分有简要描述。在一个 DNS 管理的 TCP/IP 网络中,计算机和设备可以通过主机名进行识别和相互通信,而不仅仅是通过 IP 地址。
作为一个合理的类比,DNS 非常像一本地址簿。主机名比 IP 地址更容易记住。即使在一个局部网络中,只有几台计算机和设备连接,单纯使用 IP 地址识别(或记住)任何主机也会相当困难。互联网依赖于一个全球分布的 DNS 服务器网络。
DNS 服务器有四种不同类型:递归服务器、根服务器、顶级域(TLD)服务器和权威服务器。所有这些 DNS 服务器类型共同协作,将你带入浏览器中所体验到的互联网。
递归 DNS 服务器是一个解析器,帮助你查找你搜索的网站的目标(IP)。当你执行查询操作时,递归 DNS 服务器会连接到不同的 DNS 服务器,以找到你正在寻找的 IP 地址,并以网站的形式将其返回给你。递归 DNS 查询更快速,因为它们缓存了每一个执行过的查询。在递归类型的查询中,DNS 服务器会调用自身并进行递归,同时仍然将请求发送给另一个 DNS 服务器以找到答案。
相比之下,迭代 DNS查询是由每个 DNS 服务器直接完成的,不使用缓存。例如,在一次迭代查询中,每个 DNS 服务器都会响应另一个 DNS 服务器的地址,直到其中一个服务器有与查询的主机名匹配的 IP 地址,并将结果返回给客户端。有关 DNS 服务器类型的更多详情,请查看以下 Cloudflare 学习方案:www.cloudflare.com/learning/dns/what-is-dns/。
DNS 服务器维护(并可能共享)一个/etc/resolv.conf文件。
要查询管理本地计算机的 DNS 服务器,我们可以通过运行以下代码查询/etc/resolv.conf文件:
cat /etc/resolv.conf | grep nameserver
前面的代码返回以下输出:

图 7.27 – 使用/etc/resolv.conf 查询 DNS 服务器
查询网络中任意主机的名称服务器数据的一个简单方法是使用nslookup工具。如果您的系统上没有安装nslookup工具,您可以按照此处概述的命令进行安装。
在 Ubuntu/Debian 上,运行以下命令:
sudo apt install dnsutils
在 Fedora 上,运行以下命令:
sudo dnf install bind-utils
例如,要查询本地网络中名为neptune.local的计算机的名称服务器信息,可以运行以下命令:
nslookup neptune.local
输出如下所示:

图 7.28 – 使用 nslookup 查询名称服务器信息
我们也可以通过交互式方式使用nslookup工具。例如,要查询wikipedia.org的名称服务器信息,我们只需运行以下命令:
nslookup
然后,在交互式提示符下,我们必须输入wikipedia.org,如下所示:

图 7.29 – 使用 nslookup 工具进行交互式查询
要退出交互式 Shell 模式,请按Ctrl + C。以下是前面输出信息的简要说明:
-
127.0.0.53)和端口(53)的本地 DNS 服务器 -
wikipedia.org) -
91.198.174.192)和 IPv6(2620:0:862:ed1a::1)地址对应于查询域(wikipedia.org)
nslookup也能够进行反向 DNS 搜索,方法是提供一个 IP 地址。以下命令检索与 IP 地址8.8.8.8对应的名称服务器(dns.google):
nslookup 8.8.8.8
前面的命令返回以下输出:

图 7.30 – 使用 nslookup 进行反向 DNS 搜索
要获取有关nslookup工具的更多信息,您可以参考nslookup的系统参考手册(man nslookup)。
另外,我们可以使用 dig 命令行工具。如果你的系统上没有安装 dig 工具,你可以通过在 Ubuntu/Debian 上安装 dnsutils 包或在 Fedora 平台上安装 bind-utils 来安装它们。安装包的相关命令之前已经通过 nslookup 展示过。
例如,以下命令检索 google.com 域的名称服务器信息:
dig google.com
这是结果(请查看突出显示的 ANSWER SECTION):

图 7.31 – 使用 dig 进行 DNS 查询
要使用 dig 执行反向 DNS 查询,我们必须指定 -x 选项,然后是 IP 地址(例如,8.8.4.4),如下所示:
dig -x 8.8.4.4
该命令将输出以下内容(请查看突出显示的 ANSWER SECTION):

图 7.32 – 使用 dig 进行反向 DNS 查询
有关 dig 命令行工具的更多信息,请参阅相关系统手册(man dig)。
DNS 协议在 OSI 模型中工作于应用层(第 7 层)。标准 DNS 服务的知名端口是 53。
第八章,Linux Shell 脚本,将详细介绍安装和配置 DNS 服务器的实际细节。有关 DNS 的更多信息,请参阅 RFC 1035 (www.ietf.org/rfc/rfc1035.txt)。
DHCP 和 DNS 网络服务可以说是最接近 TCP/IP 网络栈的,同时在计算机或设备连接到网络时发挥着至关重要的作用。毕竟,没有正确的 IP 地址和名称解析,就无法进行网络通信。
分布式网络和相关应用服务器不仅仅是由 DNS 和 DHCP 服务器执行的纯粹网络管理栈。在接下来的章节中,我们将快速浏览一些在分布式 Linux 系统上运行的最相关的应用服务器。
认证服务器
独立的 Linux 系统通常使用默认的认证机制,其中用户凭据存储在本地文件系统中(如 /etc/passwd 和 /etc/shadow)。我们在 第四章,用户和组管理 中探讨了相关的用户认证内部机制。然而,当我们将认证范围扩展到本地机器之外时——例如,访问文件或邮件服务器——将用户凭据在远程和本地主机之间共享将成为一个严重的安全问题。
理想情况下,我们应该在整个网络中拥有一个由安全认证服务器处理的集中式认证端点。用户凭据在用户访问远程系统资源之前,应使用强大的加密机制进行验证。
让我们考虑如何安全地访问任意文件服务器上的网络共享。假设该访问要求 Active Directory (AD) 用户认证。将相关的挂载(共享)本地创建在用户的客户端机器上时,将提示输入用户凭证。文件服务器(代表客户端)向认证服务器发出认证请求。如果认证成功,服务器共享将对客户端可用。下图表示客户端与服务器之间的简单远程认证流程,使用 轻量级目录访问协议 (LDAP) 认证端点:

图 7.33 – 使用 LDAP 的认证工作流
下面是一些标准的安全认证平台示例(适用于 Linux):
-
Kerberos (
web.mit.edu/kerberos/) -
LDAP (
www.redhat.com/en/topics/security/what-is-ldap-authentication) -
远程认证拨号用户服务 (RADIUS) (
freeradius.org/documentation/) -
Diameter (
www.f5.com/glossary/diameter-protocol) -
终端访问控制访问控制系统 (TACACS+) (
datatracker.ietf.org/doc/rfc8907/)
可以使用 OpenLDAP 配置 Linux LDAP 认证服务器,这在本书的第一版中有讲解。
在本节中,我们通过一个使用文件服务器的示例说明了认证工作流。为了保持主题的连贯性,接下来我们将探讨网络文件共享服务。
文件共享
在常见的网络术语中,文件共享表示客户端机器能够 挂载 并访问属于服务器的远程文件系统,就像访问本地文件一样。运行在客户端机器上的应用程序可以直接访问服务器上的共享文件。例如,文本编辑器可以加载并修改远程文件,然后将其保存回同一远程位置,整个过程无缝且透明。底层的远程过程——远程文件系统像本地文件系统一样工作——是通过文件共享服务和协议实现的。
对于每种文件共享网络协议,都有一个对应的客户端-服务器文件共享平台。虽然大多数网络文件服务器(和客户端)都有跨平台实现,但某些操作系统平台更适合特定的文件共享协议,正如我们在接下来的子节中将看到的那样。选择不同的文件服务器实现和协议,最终是关于兼容性、安全性和性能的问题。
下面是一些最常见的文件共享协议,并对每种协议做了简要描述:
-
服务器消息块(SMB):SMB 协议提供网络发现和文件及打印机共享服务。SMB 还支持网络上的进程间通信。SMB 是一个相对较老的协议,由国际商业机器公司(IBM)在 1980 年代开发。最终,微软接管并通过多次修订(SMB 1.0、2.0、2.1、3.0、3.0.2 和 3.1.1)对其进行了相当大的修改,形成了当前的版本。
-
通用互联网文件系统(CIFS):该协议是 SMB 协议的一个特定实现。由于底层协议相似,SMB 客户端可以与 CIFS 服务器通信,反之亦然。虽然 SMB 和 CIFS 在使用上是相同的,但它们在文件锁定、批处理以及最终的性能上有着明显的不同。除了旧系统外,如今 CIFS 很少使用。应始终优先选择 SMB,特别是 SMB 2 或 SMB 3 的较新版本。
-
Samba:与 CIFS 类似,Samba 是 SMB 协议的另一种实现。Samba 为各种服务器平台上的 Windows 客户端提供文件和打印共享服务。换句话说,Windows 客户端可以像与 Windows 服务器通信一样,顺畅地访问 Linux Samba 服务器上的目录、文件和打印机。
从版本 4 开始,Samba 原生支持 Microsoft AD 和 Windows NT 域。实质上,Linux Samba 服务器可以充当 Windows AD 网络中的域控制器。因此,Windows 域中的用户凭证可以透明地在 Linux 服务器上使用,而无需重新创建,并手动与 AD 用户保持同步。
-
网络文件系统(NFS):该协议由 Sun Microsystems 开发,基本上与 SMB 的原理相同——通过网络访问文件,就像它们是本地的一样。NFS 与 CIFS 或 SMB 不兼容,这意味着 NFS 客户端不能直接与 SMB 服务器通信,反之亦然。
-
苹果文件协议(AFP):AFP 是由苹果设计的专有文件共享协议,专门在 macOS 网络环境中运行。我们应该注意到,除了 AFP,macOS 系统还支持标准的文件共享协议,如 SMB 和 NFS。
大多数情况下,NFS 是 Linux 网络中首选的文件共享协议。对于混合网络环境——例如 Windows、Linux 和 macOS 的互操作性——Samba 和 SMB 最适合用于文件共享。
一些文件共享协议(如 SMB)还支持打印共享,并被打印服务器使用。接下来,我们将更详细地看看打印共享。
打印服务器
打印服务器(或打印机服务器)通过使用打印协议将打印机连接到网络中的客户端计算机或移动设备。打印协议负责以下通过网络进行远程打印的任务:
-
发现打印机或打印服务器
-
查询打印机状态
-
发送、接收、排队或取消打印任务
-
查询打印任务状态
常见的打印协议包括以下几种:
-
行式打印守护进程(LPD)协议
-
通用协议,如 SMB 和 TELNET
-
无线打印,如苹果的 AirPrint
-
互联网打印协议,如 Google Cloud Print
在通用打印协议中,SMB(也是一种文件共享协议)在文件共享部分已作介绍。TELNET 通信协议则在远程 访问部分有详细描述。
文件和打印共享服务主要涉及在网络上的计算机之间共享文档,既可以是数字文档也可以是打印文档。涉及到交换文档时,其他网络服务将发挥作用,如文件传输和电子邮件服务。接下来我们将讨论文件传输。
文件传输
FTP 是一种标准网络协议,用于在网络中的计算机之间传输文件。FTP 在客户端-服务器环境中运行,其中 FTP 客户端发起与 FTP 服务器的远程连接,文件可以双向传输。FTP 维护一个 21 端口,用于在客户端和服务器之间交换命令。数据连接专门用于数据传输,并且通过控制连接在客户端和服务器之间协商。数据连接通常涉及临时端口进行入站流量,在实际数据传输期间保持开启,传输完成后立即关闭。
FTP 在以下两种模式之一中协商数据连接:
-
向 FTP 服务器发送
PORT命令,表示客户端主动提供入站数据连接的端口号 -
向 FTP 服务器发送
PASV命令,表示客户端被动地等待服务器提供入站数据连接的端口号
由于数据连接的动态特性,FTP 在防火墙配置方面是一个相对复杂的协议。控制连接端口通常是众所周知的(例如,不安全的 FTP 的 21 端口),但数据连接源自于不同的端口(通常是 20),而在接收端,入站套接字在预配置的临时端口范围内(1024 到 65535)打开。
FTP 通常通过以下方法之一安全实现:
-
990 -
22。有关 SSH 协议和客户端-服务器连接的更多信息,请参阅本章稍后的SSH部分中的远程访问内容。
接下来,我们将了解邮件服务器以及其底层的电子邮件交换协议。
邮件服务器
邮件服务器(或 电子邮件服务器)负责通过网络传送电子邮件。邮件服务器可以在同一网络(域)内的客户端(用户)之间交换电子邮件——例如在一个公司或组织内部——或者将电子邮件传递给其他邮件服务器,可能超出本地网络,例如互联网。
一个电子邮件交换通常涉及以下参与者:
-
一个电子邮件客户端应用程序(如 Outlook 或 Gmail)
-
一个或多个邮件服务器(如 Exchange 或 Gmail 服务器)
-
电子邮件交换中涉及的收件人——一个发件人和一个或多个接收者
-
一个电子邮件协议,用于控制电子邮件客户端与邮件服务器之间的通信
最常用的电子邮件协议是POP3、IMAP和SMTP。让我们详细了解每个协议。
POP3
POP 版本 3(POP3)是一种标准的电子邮件协议,用于将电子邮件从远程邮件服务器接收并下载到本地电子邮件客户端。使用 POP3,电子邮件可以离线阅读。下载后,电子邮件通常会从 POP3 服务器中删除,从而节省空间。现代 POP3 邮件客户端-服务器实现(如 Gmail、Outlook 等)也可以选择保留邮件副本在服务器上。当用户从多个地点(客户端应用程序)访问电子邮件时,将邮件保留在 POP3 服务器上变得非常重要。
默认的 POP3 端口在此列出:
-
110: 用于不安全(非加密的)POP3 连接 -
995: 用于安全的 POP3,使用 SSL/TLS 加密
POP3 是一种相对较旧的电子邮件协议,并不总是适用于现代的电子邮件通信。当用户从多个设备访问电子邮件时,IMAP 是一个更好的选择。接下来,我们将看一下 IMAP 电子邮件协议。
IMAP
IMAP 是一种标准的电子邮件协议,用于访问远程 IMAP 邮件服务器上的电子邮件。使用 IMAP,电子邮件始终保留在邮件服务器上,而电子邮件的副本可供 IMAP 客户端使用。用户可以在多个设备上访问电子邮件,每个设备上都有 IMAP 客户端应用程序。
默认的 IMAP 端口在此列出:
-
143: 用于不安全(非加密的)IMAP 连接 -
993: 用于安全的 IMAP,使用 SSL/TLS 加密
POP3 和 IMAP 都是接收电子邮件的标准协议。要发送电子邮件,则需要使用 SMTP。接下来,我们将了解 SMTP 电子邮件协议。
SMTP
SMTP 是一种标准的电子邮件协议,用于通过网络或互联网发送电子邮件。
默认的 SMTP 端口在此列出:
-
25: 用于不安全(非加密的)SMTP 连接 -
465或587: 用于安全的 SMTP,使用 SSL/TLS 加密
在使用或实施本节中描述的任何标准电子邮件协议时,始终建议使用对应的安全实现,并且最好使用最新的 TLS 加密(如果可能)。POP3、IMAP 和 SMTP 也支持用户身份验证,这是一个附加的安全层——在商业或企业级环境中也建议使用。
为了了解 SMTP 协议如何操作,我们将通过一些初步步骤来启动与 Google Gmail SMTP 服务器的 SMTP 握手。
我们将首先通过 openssl 命令使用安全(TLS)连接连接到 Gmail SMTP 服务器,具体如下:
openssl s_client -starttls smtp -connect smtp.gmail.com:587
在这里,我们调用了 openssl 命令,模拟了一个客户端(s_client),启动了一个 TLS SMTP 连接(-starttls smtp),并通过端口 587 连接到远程 Gmail SMTP 服务器(-connect smtp.gmail.com:587)。
Gmail SMTP 服务器响应一个相对较长的 TLS 握手块,最终返回以下代码:

图 7.34 – 与 Gmail SMTP 服务器的初始 TLS 握手
在 openssl 命令的交互式提示符下,我们使用 HELO 命令(准确拼写)启动 SMTP 通信。HELO 命令向服务器问候。它是一个特定的 SMTP 命令,用于启动客户端和服务器之间的 SMTP 连接。还有一个 EHLO 变体,用于 ESMTP 服务扩展。Google 期望以下 HELO 问候:
HELO hellogoogle
随后进行另一次握手,最终返回 250 smtp.gmail.com at your service,如图所示:

图 7.35 – Gmail SMTP 服务器已准备好进行通信
接下来,Gmail SMTP 服务器要求通过 AUTH LOGIN SMTP 命令进行身份验证。我们不再深入探讨,但这里需要强调的一点是,SMTP 协议在客户端与服务器之间遵循明文命令序列。因此,采用安全(加密)SMTP 通信通道并使用 TLS 非常重要。其他电子邮件协议(POP3 和 IMAP)也同样适用。
到目前为止,我们已经涵盖了多个网络服务,其中一些跨越多个网络甚至互联网。网络数据包携带数据和目的地址在负载中,但也有用于通信端点之间的同步信号,主要是区分发送和接收的工作流程。网络数据包的同步基于时间戳。如果没有网络节点之间高度准确的时间同步,可靠的网络通信将无法实现。接下来,我们将讨论网络时间服务器。
NTP 服务器
NTP 是一种标准的网络协议,用于网络中计算机之间的时钟同步。NTP 尝试将参与计算机的系统时钟同步到协调世界时(UTC)——世界时间参考标准,精度通常在几毫秒之内。
NTP 协议的实现通常假设客户端-服务器模型。NTP 服务器通过广播或发送更新的时间戳数据报到客户端,作为网络上的时间源。NTP 服务器根据全球公认的准确时间服务器不断调整其系统时钟,使用专门的算法来减轻网络延迟。
在我们选择的 Linux 平台上检查 NTP 同步状态的一个相对简单的方法是使用 ntpstat 工具。ntpstat 可能在我们的系统中未默认安装。在 Ubuntu 上,我们可以使用以下命令进行安装:
sudo apt install ntpstat
在 Fedora 上,我们可以使用以下命令安装 ntpstat:
sudo dnf install ntpstat
ntpstat 需要本地运行的 NTP 服务器。要设置本地 NTP 服务器,你需要执行以下操作(此处所有示例均为 Ubuntu 22.04.2 LTS):
-
使用以下命令安装
ntp包:ntp service’s status:ntp 服务:
sudo systemctl enable ntp- 修改防火墙设置:
ntpdate package:ntp 服务:
sudo systemctl restart ntp
在安装 ntp 工具之前,请考虑到 Ubuntu 默认使用名为 timesyncd 的工具,而不是 ntpd。当安装 ntpd 后,默认工具将被禁用。
要查询 NTP 同步状态,我们可以运行以下命令:
ntpstat
这是输出结果:

图 7.36 – 查询 NTP 同步状态,使用 ntpstat
ntpstat 提供了系统与之同步的 NTP 服务器的 IP 地址(31.209.85.242),同步边际(29 毫秒),以及时间更新轮询间隔(64 秒)。要了解更多关于 NTP 服务器的信息,我们可以使用以下命令 dig 它的 IP 地址:
dig -x 31.209.85.242
看起来它是一个 lwlcom 时间服务器(ntp1.lwlcom.net),如下所示:

图 7.37 – 查询 NTP 同步状态,使用 ntpstat
NTP 客户端-服务器通信使用 UDP 作为传输协议,端口号为 123。第九章,Securing Linux 中有一节专门介绍如何安装和配置 NTP 服务器。有关 NTP 的更多信息,请参见 en.wikipedia.org/wiki/Network_Time_Protocol。
至此,我们对网络服务器和协议的简短探索也已经结束。日常的 Linux 管理任务通常需要某种形式的远程访问。远程访问计算机的方式有很多种。下一部分将描述一些最常见的远程访问设施和相关的网络协议。
远程访问
大多数 Linux 网络服务提供相对有限的 远程管理 接口,它们的管理 命令行接口 (CLI) 工具主要在运行服务的同一系统上本地操作。因此,相关的管理任务假定有本地终端访问权限。有时无法直接访问系统控制台。在这种情况下,远程访问服务器将发挥作用,通过远程机器启用虚拟终端登录会话。
让我们看看一些最常见的远程访问服务和应用程序。
SSH
SSH 可能是最流行的远程访问安全登录协议。SSH 使用强加密,并结合用户认证机制,实现客户端和服务器之间的安全通信。SSH 服务器安装和配置相对简单,第十三章 中的 配置 Linux 服务器 部分专门描述了相关步骤。SSH 的默认网络端口是 22。
SSH 支持以下认证类型:
-
公钥认证
-
密码认证
-
键盘交互认证
以下章节简要描述了这些形式的 SSH 认证。
公钥认证
公钥认证(或 SSH 密钥认证)可以说是最常见的 SSH 认证类型。
重要提示
本节将交替使用 公钥 和 SSH 密钥 这两个术语,主要是为了反映 Linux 社区中相关的 SSH 认证命名法。
SSH 密钥认证机制使用 证书/密钥 对 — 通过 ssh-keygen 工具,使用标准的加密算法,如 Rivest–Shamir–Adleman 算法(RSA)或 数字签名 算法(DSA)。
SSH 公钥认证支持 基于用户的认证 或 基于主机的认证 模型。这两种模型的区别在于所涉及证书/密钥对的所有权。使用客户端认证时,每个用户都有一个证书/密钥对用于 SSH 访问。另一方面,主机认证每个系统(主机)只涉及一个证书/密钥对。
以下章节中将说明并解释两种 SSH 密钥认证模型。这两种模型的基本 SSH 握手和认证工作流是相同的:
-
首先,SSH 客户端生成一个安全的证书/密钥对,并将其公钥分享给 SSH 服务器。这是启用公钥认证的单次操作。
-
当客户端发起 SSH 握手时,服务器请求客户端的公钥,并将其与服务器允许的公钥进行验证。如果匹配,则 SSH 握手成功,服务器将公钥分享给客户端,SSH 会话建立。
-
进一步的客户端-服务器通信遵循标准的加密/解密工作流。客户端使用其私钥对数据进行加密,而服务器使用客户端的公钥对数据进行解密。在响应客户端时,服务器使用自己的私钥加密数据,客户端则使用服务器的公钥解密数据。
SSH 公钥认证也被称为 无密码认证,并且在自动化脚本中经常使用,在这些脚本中,命令通过多个远程 SSH 连接执行,而无需提示输入密码。
让我们更深入地了解基于用户和基于主机的公钥认证机制:
- 基于用户的认证:这是最常见的 SSH 公钥认证机制。根据这种模型,每个连接到远程 SSH 服务器的用户都有自己的 SSH 密钥。同一主机(或域)上的多个用户帐户将有不同的 SSH 密钥,每个密钥都能访问远程 SSH 服务器,如下图所示:

图 7.38 – 基于用户的密钥认证
- 基于主机的认证:这是另一种 SSH 公钥认证形式,每个系统(主机)使用一个 SSH 密钥来连接远程 SSH 服务器,如下图所示:

图 7.39 – 基于主机的密钥认证
使用基于主机的认证时,底层的 SSH 密钥只能认证来自单一客户端主机的 SSH 会话。基于主机的认证允许来自同一主机的多个用户连接到远程 SSH 服务器。如果用户尝试从与 SSH 服务器允许的主机不同的机器上使用基于主机的 SSH 密钥,访问将被拒绝。
有时,SSH 访问使用两种公钥认证方式的混合——基于用户和主机的认证——这种方法为 SSH 访问提供了更高的安全性。
当安全性要求不高时,更简单的 SSH 认证机制可能更合适。密码认证就是其中一种机制。
密码认证
/etc/passwd) 或选择在 SSH 服务器配置中定义的用户帐户(/etc/ssh/sshd_config)。在第九章中,Securing Linux 对此进行了进一步详细阐述。
除了本地认证,SSH 还可以利用远程认证方法,如 Kerberos、LDAP、RADIUS 等。在这种情况下,SSH 服务器将用户认证委托给远程认证服务器,正如本章前面的认证服务器部分所描述的那样。
密码认证需要用户交互或某种自动化方式来提供所需的凭证。另一种类似的认证机制是接下来的键盘交互认证。
键盘交互认证
键盘交互认证基于 SSH 客户端(用户)和 SSH 服务器之间多轮挑战-响应对话。这种对话是明文形式的问答交换,服务器可以要求用户完成多个挑战。从某种意义上说,密码认证是一种单一挑战交互式 认证机制。
这种认证方法的交互性含义可能会让我们认为用户交互是实现该方法的必要条件。其实不然。基于键盘交互的认证也可以用于实现基于自定义协议的认证机制,其中底层的消息交换将被建模为认证协议。
在继续讨论其他远程访问协议之前,我们应该提到 SSH 的广泛使用,因为它具有安全性、灵活性和高性能。然而,在特定场景下,SSH 连接可能并不总是可行或足够。此时,TELNET 可能会派上用场。接下来我们将深入了解它。
TELNET
TELNET 是一个用于双向网络通信的应用层协议,它使用明文 CLI 与远程主机进行交互。从历史上看,TELNET 是最早的远程连接协议之一,但它始终缺乏安全实现。最终,SSH 成为了从一台计算机登录到另一台计算机的标准方式,但在某些应用层协议的故障排除(如 Web 或邮件服务器通信)中,TELNET 相对于 SSH 具有一定的优势。你将在第九章中了解更多关于如何使用 TELNET 的内容,加固 Linux。
TELNET 和 SSH 是基于命令行的远程访问接口。有时,可能需要通过图形用户界面(GUI)直接连接到远程计算机的桌面。接下来我们将讨论桌面共享。
VNC
虚拟网络计算(VNC)是一个桌面共享平台,允许用户访问和控制远程计算机的图形用户界面(GUI)。VNC 是一个跨平台的客户端-服务器应用程序。例如,在 Linux 机器上运行的 VNC 服务器可以允许多个在 Windows 或 macOS 系统上运行的 VNC 客户端访问桌面。VNC 网络通信使用远程帧缓冲区(RFB)协议,该协议由RFC 6143定义。设置 VNC 服务器相对简单。VNC 假定存在图形桌面系统。更多细节将会在第十三章中提供,配置 Linux 服务器。
本节结束后,我们将介绍网络服务和协议。我们尝试涵盖了有关通用网络服务器和应用程序的最常见概念,这些服务器和应用程序大多以客户端-服务器或分布式方式运行。对于每个网络服务器,我们描述了相关的网络协议和一些涉及的内部机制。第九章,加固 Linux,以及第十三章,配置 Linux 服务器,将展示一些这些网络服务器的实际应用。
在下一节中,我们将重点讨论网络安全的内部机制。
理解网络安全
网络安全代表了防止、监控和保护计算机网络免受未经授权访问的过程、行动和政策。网络安全范式涵盖了各种各样的技术、工具和实践。以下是一些重要的范式:
-
访问控制:基于用户认证和授权机制选择性地限制访问。访问控制的例子包括用户、组和权限。相关概念已在第四章《管理用户和组》中讨论过。
-
应用安全:保护和保障服务器和终端用户应用程序(电子邮件、Web 和移动应用)。应用安全的例子包括增强型 Linux 安全(SELinux)、强加密连接、防病毒和反恶意软件程序。我们将在第十章《灾难恢复、诊断和故障排除》中介绍SELinux。
-
终端安全:保护和保障网络中的服务器和终端用户设备(智能手机、笔记本电脑和台式电脑)。终端安全的例子包括防火墙和各种入侵检测机制。我们将在第十章《灾难恢复、诊断和故障排除》中讨论防火墙。
-
网络分段:将计算机网络划分为更小的段或虚拟局域网(VLAN)。这与子网划分不同,后者是通过地址对网络进行逻辑分割。
-
VPN:通过安全加密隧道从公共网络或互联网访问公司网络。我们将在第九章《保护 Linux》和第十三章《配置 Linux 服务器》中更详细地探讨 VPN。
在日常的 Linux 管理中,设置网络安全边界应该始终遵循前面列举的范式,大致按顺序进行。从访问控制机制开始,最后到 VPN,保护网络采用的是一种由内而外的方法,从本地系统和网络到防火墙、VLAN 和 VPN。
摘要
本章提供了 Linux 网络基本原理的相对简明的视图。我们了解了网络通信层和协议、IP 地址方案、TCP/IP 配置、常见的网络应用服务器以及 VPN。对网络范式的良好掌握将使 Linux 管理员能够更全面地了解分布式系统及其应用端点之间的通信。
本章中涵盖的一些理论方面将在第十三章,配置 Linux 服务器中进行实际演练,我们将重点关注网络服务器的实际实现。第十章,灾难恢复、诊断与故障排除,将进一步探讨网络安全的内部机制和 Linux 防火墙的实际应用。我们到目前为止所学的一切,将为接下来的章节提供良好的基础。
下一章将介绍 Linux Shell 脚本,你将学习最常见的 Shell 功能,以及如何使用决策、循环、变量、数组和函数。
问题
这是一个快速小测,旨在概述并测试本章中涵盖的一些基本概念:
-
OSI 模型与 TCP/IP 模型有何区别?
提示:图 7.2可能会有所帮助。
-
想想几种 TCP/IP 协议,并试着看看它们在你熟悉的一些网络管理任务或应用程序中是如何运作的。
-
HTTP 协议在网络层的哪个层级上运行?DNS 呢?
提示:它们都在相同的层级上操作。
-
IP 地址
192.168.0.1属于哪个网络类别?提示:参见图 7.5。
-
对应网络掩码
255.255.0.0的网络前缀是什么?提示:再次查看图 7.5。
-
如何使用
nmcli工具配置静态 IP 地址?connection modify。 -
如何更改 Linux 机器的主机名?
hostnamectl工具。 -
POP3 和 IMAP 电子邮件协议之间有什么区别?
-
SSH 的基于主机认证与基于用户的 SSH 密钥认证有何不同?
-
SSH 和 TELNET 之间有什么区别?
进一步阅读
有关本章所涵盖内容的更多信息,请参阅以下 Packt 出版物:
-
《Linux 管理最佳实践》,作者:Scott Alan Miller
-
《面向网络专业人员的 Linux》,作者:Rob VandenBrink
第八章:Linux Shell 脚本编写
了解如何使用 Linux Shell 编程基础和命令行界面(CLI)对现代 Linux 专业人员至关重要。
在本章中,你将学习如何利用 Linux Shell 的编程功能来自动化 Linux 中的不同任务。你将了解基本 Linux Shell 脚本的结构,以及它是如何组织和执行的。我们将探索之前章节中已经介绍的大多数命令,特别是那些用于操作文件和目录以及输入输出重定向的命令。过程中,我们将引导你学习编写脚本、Shell 编程的结构和复杂性,以及如何使用 sed 和 gawk 等专用工具。我们希望到本章结束时,你能熟练地在日常工作流中使用脚本,并为未来更高级的探索做好准备。
我们将涵盖以下主要主题:
-
介绍 Shell 特性
-
Shell 脚本的结构
-
决策、循环、变量、数组和函数
-
使用
sed和 (g)awk
技术要求
本章需要在标准的 Linux 发行版上进行安装并正常运行,无论是服务器、桌面、PC 还是虚拟机(VM)。我们的示例和案例研究主要使用 Ubuntu/Debian 和 RHEL/Fedora 平台,但我们将探讨的命令和示例同样适用于其他 Linux 发行版,如 openSUSE/SLE。
探索 Linux Shell
回到第二章,Linux Shell 和文件系统,我们通过探索可用的虚拟控制台、命令类型和文件系统介绍了 Shell。这为我们在本章要探索的内容提供了一个良好的基础。到目前为止,通过本书所展示的内容,你已经熟练掌握了命令行的使用;你了解了一些在 Linux 中最常用和最有用的命令,我们探讨了文件操作、软件包、用户和磁盘管理,直到网络管理。所有这些知识将在本章中得到应用,我们将探讨高级 Shell 特性、Shell 变量、正则表达式,以及如何利用 Bash Shell 强大的编程和自动化功能。
在下一部分,我们将开始探索 Shell 的高级特性。
Bash Shell 特性
Shell 不仅执行命令,还有许多其他特性,使得系统管理员在命令行中的工作更加轻松。这些特性包括使用 PATH 和别名。
在继续之前,让我们了解一下 shell 所基于的标准的历史。早在 UNIX 作为操作系统出现时,便出现了对标准化需求的呼声,用以监督不同版本的系统。因此,电气与电子工程师协会(IEEE)创建了可移植操作系统接口(POSIX),它是一个包含多种标准的体系,旨在确保不同操作系统之间的兼容性。因此,UNIX 和 Linux 以及 macOS(基于 Darwin,macOS 的内核来源于 UNIX)、AIX、HP-UX 和 Oracle Solaris 都是符合 POSIX 标准的。POSIX 为 C 语言 API、文件格式定义、目录结构、环境变量定义、区域设置、字符集和正则表达式等提供了不同的标准。
在了解了这些历史背景后,我们继续。在下一节中,我们将向你展示如何使用 shell 通配符和元字符。
通配符和元字符
在 Linux 中,通配符用于匹配文件名。主要有三种类型的通配符:
-
*): 这个符号用于匹配零个或多个字符 -
?): 这个符号用于匹配一个单独的字符 -
[ ]): 这个符号用于匹配方括号内的任意字符
元字符是用于 Linux 和任何基于 UNIX 的系统中的特殊字符。以下是这些元字符:

图 8.1 – 元字符列表
让我们看两个使用元字符进行命令替换的示例。我们将一个命令的输出作为另一个命令的输入。这个过程可以通过两种方式实现,如下图所示:

图 8.2 – 命令执行与替换示例
上述示例的目的是向你展示如何在 shell 中使用命令替换,但我们也许应该进一步解释所用命令的作用。我们使用了两个命令:echo 和 date。我们将 date 命令的输出嵌入到 echo 命令的输出中。echo 命令是 Linux 中最简单的命令之一,它将引号中的消息输出到标准输出。在我们的例子中,这个消息还包括了 date 命令的输出,显示系统当前日期,格式如图所示。
你还可以将两个或多个命令组合起来,在 Linux 中我们使用管道符来实现这一点。管道符将第一个命令的输出作为第二个命令的输入,依此类推,具体取决于你使用了多少个管道符。
在下面的示例中,我们使用 ls -l /etc 命令对 /etc 目录的内容进行长格式列出;然后将其通过管道传递给 less 命令。请按照以下代码使用:
ls -l /etc | less
less 命令一次显示一页内容,允许你查看所有内容。你可以使用箭头键或翻页键(Page Up 和 Page Down)在输出中导航,查看 /etc 目录的所有内容。
管道和命令替换将非常有用,特别是当你处理复杂命令或编写脚本时,正如你在本章后面学习如何创建和使用脚本时会看到的那样。
现在,让我们执行一些命令序列。然后,我们将使用元字符来分组命令并将输出重定向到一个文件。所有这些内容将在以下截图中显示:

图 8.3 – 命令序列执行示例
如你在前面的输出中所看到的,第一行执行的两个命令可以很容易地通过大括号进行分组,并且它们的输出可以重定向到一个文件中。
我们使用了三种元字符——命令执行顺序 (;)、用于分组命令的括号,以及输出重定向 (>) 将输出重定向到文件。该文件最初并不存在,只有在命令执行时才会创建。我们使用的最后一个命令是 cat 命令,它连接了新创建文件的内容。
使用的前两个命令是 who,它将当前登录的用户信息打印到标准输出,和 pwd,它打印出我们所在的当前工作目录的位置。接下来的部分,我们将展示如何在 Shell 中使用大括号扩展。
大括号扩展
大括号也可以用来扩展命令的参数。大括号不仅限于文件名,不像通配符那样,它们可以与任何类型的字符串一起使用。在这些大括号中,你可以使用单个字符串、一个序列或由逗号分隔的多个字符串。
在这一部分,我们将展示一些使用这种扩展类型的示例。首先,我们将在当前工作目录中使用 report 和 new-report,并希望一次性删除它们。我们可以使用以下命令:
rm {report,new-report}
要创建多个文件(例如五个文件),它们共享部分名称,如 file1、file2、… filen,我们可以使用以下命令:
touch file{1..5}
以下截图显示了这两个命令的输出:

图 8.4 – 使用大括号扩展的示例
重要提示
大括号扩展是一个强大的工具,它为任何系统管理员的工作流增添了灵活性和强大功能。比如在学习如何编写脚本时,它将非常有用。
现在我们已经创建了这些文件,你应该能很容易地弄明白如何使用大括号扩展一次删除多个文件。将以下命令输入到你的控制台中,看看会发生什么:
rm file{1..5}
它将删除我们之前创建的所有五个文件。使用 ls 命令查看当前工作目录的内容。
在接下来的章节中,我们将讨论 shell 命令别名,它们是什么以及如何使用它们。
shell 的别名
Linux shell 支持 ll,这是 ls -alF 的缩写。你也可以定义自己的别名,可以将它们设置为临时的或永久的,类似于变量。在以下示例中,我们更改了 ll 命令的别名:

图 8.5 – 更改命令的别名
这个修改只是临时的,重新启动或 shell 重启后将恢复为默认版本。如果你想使其永久生效,应该编辑 ~/.bashrc 文件,并将之前创建的别名添加到该文件中。为此,使用你喜欢的文本编辑器打开该文件,并将你在终端中使用的行添加到文件中。保存文件并执行它。同时,最佳实践是将这些行添加到一个名为 .bash_aliases 的新文件中。你可以查看 .bashrc 文件的默认内容,了解如何使用别名。
重要说明
.bashrc 文件是一个隐藏的脚本文件,包含不同的终端会话配置。此外,该文件还可以包含不同的函数,帮助用户完成重复任务。它会在用户登录时自动执行,也可以通过使用 source .bashrc 命令手动执行。
在接下来的章节中,我们将展示什么是 shell 变量以及如何使用它们。
Bash shell 变量
Bash shell 使用不同类型的变量,和你在任何编程语言中使用它们的方式相同。Bash shell 有一些内置变量和间接变量,并且还允许你定义自己的变量。
Linux 有两种主要的 shell 变量类型:全局变量和局部变量。它们对于所有的 Linux 发行版通常是相同的,但也有一些例外。你需要查阅你发行版的文档,以了解任何特定的环境变量修改。
我们将带你了解 Linux 中最常用的变量,从内置变量开始。
内置 shell 变量
以下是一些标准内置变量的简短列表:
-
HOME: 用户的主目录(例如,/home/packt) -
LOGNAME: 用户的登录名(例如,packt) -
PWD: 当前的工作目录 -
OLDPWD: shell 的上一个工作目录 -
PATH: shell 的搜索路径(由冒号分隔的目录列表) -
SHELL: shell 的路径 -
USER: 用户的登录名 -
TERM: 终端的类型
在 shell 中调用变量时,只需在变量名的前面加上美元符号 $。以下是一个简短的示例,展示了如何使用我们刚才列出的变量:

图 8.6 – 从 shell 调用变量
你还可以像下面的例子一样赋值自己的 shell 变量。在这里,我们将 sysadmin 字符串赋值给一个名为 MYVAR 的新变量,然后将其打印到标准输出:
MYVAR=sysadmin; echo $MYVAR
本节开头列出的变量只是 shell 中默认提供的所有变量的一部分。要查看所有 shell 变量,可以使用 printenv 命令。如果列表太长,可以将其重定向到文件中。在下面的例子中,你的变量列表存储在 shell_variables 文件中,你可以通过串联或在文本编辑器(如 Vim)中编辑来查看它:
printenv > ~/shell_variables
在这里,我们使用波浪号符号(~)来指定已登录用户的主目录。Shell 变量仅在 shell 内部可用。如果你希望某些变量能被 shell 运行的其他程序所知道,必须通过使用 export 命令将其导出。一旦变量从 shell 导出,它就成为 环境变量。
Shell 的搜索路径
PATH 变量是 Linux 中一个重要的变量。它帮助 shell 知道所有程序的位置。当你在 Bash shell 中输入命令时,它首先必须通过 Linux 文件系统搜索该命令。有些目录已经列在 PATH 变量中,但你也可以添加新的目录。根据你的操作方式,添加的内容可以是临时的或永久的。要使一个目录的路径临时可用,只需将其添加到 PATH 变量中。在下面的例子中,我们将 /home/packt 目录添加到 PATH 中:

图 8.7 – 添加新位置到 PATH
要使任何更改永久生效,我们必须在名为 ~/.bash_profile 或 ~/.bashrc 的文件中修改 PATH 变量。
重要提示
一些发行版,如 openSUSE,会在用户的主目录中添加一个额外的 bin 目录。这是你可以放置希望 shell 执行的文件的地方,例如脚本文件。
Shell 的 $PATH 变量很重要,特别是在使用脚本时,因为你通常需要在一个 shell 已知的目录中创建脚本。在下一节中,我们将向你展示如何创建你的第一个 Bash 脚本。
Shell 脚本基础
我们已经介绍了 Linux 命令行、shell 变量、通配符和元字符的关键内容。现在,我们将开始探索什么是脚本,如何创建它们以及如何在 Linux CLI 中使用它们。我们将不使用图形用户界面,仅使用 CLI,这是我们在前面章节中主要使用的。让我们从一些关于 shell 脚本的基本但重要的概念开始。
首先,让我们了解什么是 脚本。如果我们在字典中查找这个术语的意思,答案是脚本是一系列由计算机执行的指令,主要用于自动化特定任务。指令也可以很容易地看作是命令。因此,一系列由 shell 执行的命令可以被视为一个脚本。这是一个非常基础的脚本,但它仍然是一个脚本。让我们看看如何创建一个脚本文件。
创建一个 shell 脚本文件
编写脚本最合适的方法是将它们创建为一个文件,为了清晰起见,我们使用 .sh 扩展名。然而,这并不是强制性的,因为在 Linux 中,文件不像 Windows 那样使用扩展名。使文件被认为是脚本的独特特征是该文件中的 第一行 文本。对于 Bash shell 脚本,这一行的格式如下:
#!/bin/bash
当文件被打开并执行时,第一行告诉 shell 解释器它正在处理一个脚本文件,在我们的例子中,这个脚本将由 Bash shell 执行。如果你使用的是不同的 shell,这第一行会指向它。在 shell 脚本文件中,井号(#)表示注释行,除了这一行,它与感叹号(!)结合使用,指向 shell 的解释器。#! 的组合也被称为 shebang。
让我们创建一个基本的脚本文件。我们将使用以下代码:
#!/bin/bash
whoami
who
date
uptime
在这里,我们在第一个基本脚本中使用了四个不同的命令。我们为每个命令使用了单独的一行,但也可以用另一种方式来编写:将所有命令写在同一行并使用分号分隔它们。然而,为了清晰起见,使用不同的行会更有用。
现在我们有了第一个脚本,让我们运行它。我们在主目录下创建了名为 basic-script.sh 的脚本文件。让我们尝试通过在命令行中简单地调用它的名称来运行它:
$ basic-script.sh
我们将看到一个错误提示,如下图所示:

图 8.8 – 运行新脚本时出现的错误
你可能会想知道为什么会出现这个错误。是因为 shell 不知道你的脚本,它在 PATH 变量中找不到它。正如你从前一节中记得的那样,PATH 是 shell 用来查找特定文件位置并执行的变量。为了克服这个错误,我们有两个选择:
-
我们可以将脚本所在目录添加到 shell 的路径中
-
在命令行调用脚本时,我们可以使用相对路径或绝对路径
我们将使用第二种方法,因为它更方便。然而,你可以使用第一种方法作为一个很好的练习,尝试将你的目录位置添加到 shell 的 PATH 变量中。让我们通过指定位置来调用脚本文件:
$ ./basic-script.sh
我们会遇到另一个错误,这次是不同的错误,显示 Permission denied。这是因为我们没有权限执行该文件。当我们在 Ubuntu 机器内创建文件时,由于默认的 umask 值,该文件只为文件所有者和其组提供了读写权限。为了改变这一点,我们需要使用以下命令使文件可执行:
$ chmod u+x basic-script.sh
设置了文件的可执行权限之后,我们可以再次运行它,这次脚本将被执行。输出将显示脚本内的每个命令都被执行了:

图 8.9 – 运行可执行脚本文件
如前面的截图所示,脚本已被执行,输出显示了每个命令的结果。第一行显示用户名(whoami 命令的输出),第二行显示已登录用户的信息(who 命令的输出),第三行显示当前日期的信息(date 命令的输出),最后一行显示当前会话的信息(uptime 命令的输出)。
重要说明
一般来说,当编写脚本时,我们建议尽可能使用注释来详细说明您选择的每个变量和参数。这被认为是一种良好的编程实践,可以使您的代码编写更加愉快和相关。记录您的编码步骤将使您的脚本在以后更容易阅读,无论是您自己还是其他任何可能遇到您代码的人。在本书中使用的示例中,我们并未使用太多注释,这是由于页面数量的限制。尽管如此,我们鼓励您使用它们。
有了这些知识,您就知道如何创建一个 shell 脚本文件以及如何执行它。现在让我们继续更高级的话题。在接下来的部分,我们将向您展示如何在您的脚本中使用变量。
shell 脚本中的变量
在本章的开头,我们向您介绍了变量。现在是时候学习如何在脚本内使用它们了。回顾一下,让我们看看 Linux 中使用哪些类型的变量。我们可以使用 printenv 和/或 set 命令。我们在本章的内置 shell 变量部分列出了一些最常用的变量。
理解命名规范
在 Linux shell 内,系统的环境变量仅使用大写字母。因此,在创建用户指定的变量时,应遵循相关的命名考虑因素。在这方面,没有一个适用于所有情况的命名约定。但您应考虑变量名区分大小写且应最多20 个字符的长度。赋值给变量的方法是使用等号(=)符号。
如果你打算在变量名称中只使用大写字母,你应该考虑到这可能带来的灾难性后果,因为环境变量只使用大写字母。我们建议你在创建 shell 中的变量名称时,遵循以下规则之一:
-
仅使用小写字母、下划线和数字
-
将变量名中的单词首字母大写
重要提示
在考虑变量名长度时,尽量避免使用过长的名称;可以使用简洁且相关的名称或缩写。这样会让你的脚本更易于阅读和理解。
现在,让我们学习如何在 shell 中定义和使用第一个变量。我们将在下一节中探讨这个内容。
定义和使用变量
让我们创建一个名为user-script.sh的新文件,通过使用环境变量来显示相关的用户信息。创建文件并输入相关代码后,我们将使其可执行,然后运行它。以下是相关命令:

图 8.10 – 在脚本中使用环境变量
在前面的图中,我们使用了两种不同的方式来显示信息。使用双引号时,环境变量会被插入到引号字符串中,并且它的值会显示在输出中。请记住,使用单引号时,变量的值不会传递给 shell 解释器。我们使用了四个不同的环境变量来显示用户信息。它们分别是UID、USER、HOME和BASH。这是一种非常基本且直接的在脚本中使用 shell 变量的方式。
你还可以使用自己的变量,而不仅仅是 shell 提供的那些。Shell 解释器的一个非常有用的功能是它能够自动确定变量所使用的数据类型。你还应该知道,脚本中定义的变量值仅在 shell 运行时有效,之后会丢失。让我们创建一个新的 shell 脚本,并这次使用我们自己的变量。以下是输出:

图 8.11 – 一个使用用户定义变量的基本脚本
在这里,我们创建了一个名为user-variables.sh的文件,并定义了两个变量,一个叫value,其值为25,另一个叫product,其值为Shirt。当我们在echo命令中调用这些变量时,我们使用了与环境变量相同的调用符号。
现在你已经知道如何命名、定义和使用变量,让我们继续深入学习更高级的主题。在下一节中,我们将展示如何在 shell 脚本中使用数学表达式。
在 shell 脚本中使用数学表达式
Shell 是一种编程语言,因此它具有用于处理数字的内建功能。Bash Shell 提供了expr命令,用于执行各种数学运算。要了解所有受支持的运算,请访问expr命令的内部手册页面,我们在这里不会完全列出:
man expr
然而,我们将展示如何使用expr命令提供的某些操作。请记住,正如手册中所述,你需要使用反斜杠字符(\)来转义一些expr命令使用的字符,否则它们会被 Shell 误解。让我们创建一个新的脚本文件并进行一些基本的数学运算。我们的新文件名为math.sh,可以使用以下代码创建:

图 8.12 – 在脚本中使用 expr 命令
除了expr命令外,我们还可以使用方括号作为一种更简单的数学运算方式。让我们修改前面的脚本,替换掉expr命令:

图 8.13 – 使用方括号进行数学运算
如前面的示例所示,我们使用了整数值。你可以尝试为vat变量使用浮动点值,例如,你会看到运行脚本时会显示错误。这是因为 Shell 只支持整数运算。为了克服这个限制,有一些解决方法,其中最可行的是使用 Bash 计算器,或者bc命令。
重要提示
如果你希望在 Shell 中完全支持浮动点运算,可能需要考虑使用Z shell(Zsh)。它在一些 Linux 发行版(如 Manjaro 和 Kali Linux)以及 macOS 中默认安装。你也可以在你的发行版中安装它。
让我们看看如何在我们的math.sh脚本中使用bc命令。我们将按照以下方式修改代码:

图 8.14 – 使用 bc 命令进行浮动点运算
在前面的示例中,我们将一个浮动点值赋给了vat变量,并使用bc命令来计算浮动点总数。我们可以像这样在变量中使用bc命令:
var=$(echo "option; expression" | bc)
option值为bc提供了选项。在我们的例子中,我们使用了一个名为decs(这是我们选择的一个任意名称)的变量来指定我们想要提供的小数位数。expression参数指定了我们使用的操作,在我们的例子中是加法。我们将echo命令的输出通过管道传递给bc命令,结果被赋值给了var变量。
创建 Bash Shell 脚本不仅仅是进行数学运算或执行一系列连续的 Shell 命令。有时候,脚本内部需要根据输入和期望的输出做出决策。这就是特定编程结构介入的地方。我们将在下一节中详细讲解这些结构。
使用编程结构
在本节中,我们将向你展示如何使用条件语句和循环语句。在编写高级 Shell 脚本时,它们非常有用。我们还将展示如何使用数组,如何在脚本中读取输入,以及如何格式化和打印输出数据。
在 Bash 中使用数组
我们在前面的章节中展示了如何使用变量。现在,是时候提升我们的技能,展示如何利用filename1、filename2、filename3 …… filenameN,我们可以创建一个数组来存储所有的文件名。如果你熟悉其他编程语言,数组对你来说可能已经很熟悉。但如果你不了解其他编程语言,不用担心,因为 Bash 提供了一种简单的数组使用方法。
让我们从一个简单的例子开始。假设我们需要处理不同的用户名。为了避免为每个用户名使用不同的变量,我们可以使用索引数组:
usernames=("paul" "janet" "mike" "john" "anna" "martha")
数组中的元素从索引号0(零)开始。这一点很重要,尤其是在需要访问数组内容时。如果我们想访问用户名数组中的第三个元素(即"mike"字符串),我们必须使用以下代码:
echo ${usernames[2]}
输出将是mike(没有引号)。要打印整个数组,可以使用以下代码:
echo ${usernames[*]} or echo ${usernames[@]}
要打印数组的大小,也就是元素的数量,可以使用以下代码:
echo ${#usernames[@]} or echo ${#usernames[*]}
在我们的例子中,数组中有六个元素,输出将是6。
假设我们需要将一个新的用户名("alex")添加到数组中。有不同的方法来添加它。如果我们只想将其添加到数组末尾,而不指定位置,我们可以使用以下代码:
usernames+=("alex")
新的用户名将被附加到数组的末尾。此时,数组中的名字是无序的,我们需要将它们按字母顺序排列。我们将在使用循环语句部分介绍这一点,之后我们将教你如何进行输出格式化以及使用不同的条件语句和循环语句。
另外,我们也可以通过使用以下代码,在数组中的特定位置(比如位置 2)添加一个新元素(例如"zack"):
usernames[1]="zack"
到目前为止,我们只在数组中使用了字符串作为示例。你也可以在索引数组中使用整数。创建数组的内置命令是declare。要创建一个索引数组,可以使用以下命令:
declare -a array_name
你还可以使用以下命令创建关联数组:
declare -A array_name
关联数组是基于键值对的元素。以下是一个关联数组声明的示例:
declare -A linux_distros=( [KDE]="openSUSE" [GNOME]="Fedora" [Xfce]="Debian" [Cinnamon]="Mint" )
方括号内展示的是用于映射值的键。双引号内的是值。要打印这些值,可以使用与索引数组相同的命令:
echo ${linux_distros[@]}
要打印键,请使用以下命令:
echo ${!linux_distros[@]}
索引数组与关联数组之间的主要区别在于,索引数组是基于索引值的,每个元素在数组中都有一个特定的索引位置,而关联数组则使用特定的键来映射值。
数组是 Bash 中重要的数据结构,稍后我们将在本章讨论循环语句时使用它们。但首先,让我们学习如何在脚本中读取输入数据,以及如何格式化输出数据。在下一节中,我们将展示如何从标准输入读取数据。
读取输入数据
默认情况下,Shell 从标准输入(即键盘)读取输入数据。要从标准输入读取数据,可以使用 read 命令。此命令会读取所有输入数据,直到提供新的行。每当你按下键盘上的 Enter 键时,就会发生这种情况。
使用 read 命令时,可以提供一个或多个变量。如果使用更多的变量,通过标准输入提供的每个单词将被分配给一个变量。以下是一个示例:

图 8.15 – 使用 read 命令进行标准输入
在前面的截图中,我们使用了 read 命令,并为四个变量 a、b、c 和 d 提供了值。当我们首次输入数据时,只为第一个变量提供了值,这意味着在输入第一个单词后,我们按下了 Enter 键。因此,a 只有一个值,而其他变量没有值。当我们第二次使用 read 命令时,为每个变量提供了值,在输入完单词 Thursday 后按下 Enter 键。这样,每个变量都接收了相应的值。read 命令有多个选项可用,但你需要阅读手册来了解它们的详细信息。与从标准输入提供值类似,read 命令还可以通过重定向从文件接收输入。例如,如果我们有一个名为 week-days 的文件,可以将其内容重定向到 read 命令:

图 8.16 – 使用 read 命令与文件重定向
read 命令用于在创建脚本时读取输入。我们已经向你展示了如何在命令行中使用该命令,但在本章稍后我们将回到这一部分,讨论更高级的脚本。在接下来的章节中,我们将介绍输出数据格式。
格式化输出数据
在 Linux 中,标准输出默认会定向到显示器。对于这个任务,你将有两个可以使用的命令。其中一个是我们在本书中广泛使用的 echo 命令。另一个命令是 printf,我们将在本节中讲解它的使用。
printf 命令类似于 C 编程语言中使用的命令。快速查阅 printf 命令的手册会向我们展示它应该使用的形式:
printf FORMAT [ARGUMENT] …
命令的所有参数会根据提供的格式字符串进行打印。格式控制符可以包含常规字符或转义序列,这些序列由反斜杠和字母组成。转义序列在可用的手册中有清晰的说明。简而言之,一些常用的序列如下:
-
\``b:退格符 -
\``e:转义 -
\f:换页符 -
\n:新行 -
\r:回车符 -
\t:水平制表符 -
\v:垂直制表符
使用反斜杠与printf时,应该通过使用双引号或另一个反斜杠从 shell 中进行转义。有关更多详细信息,请参考手册。
除了转义序列,printf 命令还具有格式化符。以下是一些常见格式化符所代表的含义:
-
%s:这是一个字符串格式化符,用于基本的字符串输出 -
%b:这是一个字符串格式化符,它允许解释转义序列 -
%d:这是一个整数格式化符,用于整数值 -
%f:这与整数格式化符类似,但用于浮点值 -
%x:用于整数的十六进制值和输出填充
现在你已经了解了 printf 命令的基础知识,让我们看一些如何使用它的示例。
在下图中,我们使用 printf 格式化符来展示使用与不使用格式化符之间的区别:

图 8.17 – printf 命令的基本用法
如下所示,使用默认设置时,printf 会打印双引号内的字符串,但行尾不会换行。使用 %s 格式化符时,命令打印作为参数提供的字符串,并将其解释为字符——在我们的例子中,就是双引号之间的字符串。使用 \n 会在每个字符串后创建一个新行。
现在我们在脚本中使用 printf 命令。以下是一个使用 %s 格式化符的 printf 示例。请注意,由于输出类似于使用双引号,因此我们使用单引号来调用该命令:

图 8.18 – 在脚本中使用 printf
前面的图示展示了一个脚本,它从标准输入设备读取两个变量,并将这两个变量显示到标准输出(显示器)上。我们使用了字符串格式符(%s)和换行符转义序列(\n)。现在,让我们深入探讨输出格式化。在接下来的示例中,我们将使用新的制表符转义序列(\t)以及换行符转义序列(\n)和字符串格式符。请看以下图示,看看格式化是如何工作的:

图 8.19 – 使用制表符和换行符转义序列
我们刚刚展示的示例使用了转义序列来模拟输出的表格格式化。我们可以通过使用一个包含比之前更复杂格式符的脚本来实现这一点。在以下示例中,我们使用了字符串(%s)、整数(%d)和浮点数(%f)格式符来格式化表格输出:

图 8.20 – 使用复杂格式符和转义序列进行表格格式化
让我们详细解释一下我们的 format-output.sh 脚本。我们有一个名为 separator 的变量,用于在标题和内容之间打印出图形边框。接下来,我们有 header 和 format 变量,每个都使用格式符和序列来进行格式化。header 以换行符(\n)开始,后跟一个宽度为 10 字符、左对齐的字符串格式符(%-10s),接着是一个宽度为 8 字符、右对齐的格式符(%8s),一个宽度为 10 字符、右对齐的格式符(%10s),以及一个宽度为 11 字符、右对齐的格式符(%11s),最后以换行符结束(\n)。format 变量用于通过两个字符串列(使用 %s 格式符)、一个整数(%d)和一个浮点数(%f)格式符来格式化表格的内容。整数值用于产品 ID,浮点值用于价格。我们使用 %08d 格式符来打印 ID,这意味着输出将是宽度为 8 字符。前面的 0 表示任何空格都会被零填充。这确保了即使 ID 号码少于八位数,剩余的宽度也会用零填充,以保持所需的 8 字符宽度。结果显示在前面截图的下方,我们有一个包含产品、ID、位置和价格的表格。
因此,printf 是一个非常多功能且强大的工具,可以在你的脚本中带来很好的效果。现在你已经掌握了 Bash 脚本中输入和输出数据格式化的基本工具,我们可以继续探讨其他有用和重要的结构。
在接下来的部分,我们将讨论退出状态。
理解退出状态和测试结构
为了使用条件和循环语句,我们需要理解 $?。问号的位置是一个整数,表示命令的状态。例如,如果命令执行成功,问号的值将是 0(零),参数将显示为 $0。如果命令执行不成功,问号的值可能是从 1 到 255 之间的任何值。通常,错误代码是 1,因此参数将是 $1。这些也被称为退出代码。
除了退出代码,测试结构在 Bash 的条件和循环语句中也很重要。这些测试结构通常是前述语句的构建模块。它们被视为 Shell 的关键字,表示方式如下:
-
[[ ]]:双方括号用于测试命令的真假状态;它也可以对正则表达式执行操作 -
(( )):双括号用于算术运算 -
test:该关键字用于评估表达式,如字符串、整数和文件属性
重要提示
Bash 的语法要求在括号前后使用空格——例如,[ operation ]。
在条件语句中,还会使用一些其他类型的操作符进行测试。我们来看一些常用的条件操作符,用于整数测试:
-
-eg:等于检查操作符 -
-ne:不等于操作符 -
-lt:小于操作符 -
-le:小于或等于操作符 -
-gt:大于操作符 -
-ge:大于或等于操作符
此外,Linux shell 脚本中还有参数操作符。$0 变量代表用于运行脚本的命令,而 $1 到 $n 代表传递给命令的第一个到第 n 个参数。例如,$1 代表第一个参数,$2 代表第二个参数,依此类推,$n 代表命令的第 n 个参数。
除了本节展示的操作符,基本的数学运算符在条件语句中也会被使用。
也有用于字符串的测试结构:
-
=:测试字符串是否相同(==也可以接受) -
!=:测试字符串是否不相同 -
\<和\>:小于和大于符号用于字符串比较,但必须进行转义(我们已经使用了反斜杠字符)
也有用于文件类型的测试操作符,它们以选项的形式出现:
-
-f:测试是否为常规文件 -
-d:测试是否为目录 -
-h或-L:测试是否为符号链接 -
-e:测试文件是否存在
这里是一些用于测试的其他操作符:
-
-a:逻辑“与”操作符 -
-o:逻辑“或”操作符 -
-z:检查是否输入了字符串
测试操作符既复杂又实用,因此学习它们非常重要。
在下一节中,我们将讨论 Bash 的条件语句。
使用条件 if 语句
就像其他编程语言一样,Bash 也有条件执行语句,如if-then-fi、if-then-else-fi和嵌套if,以及条件操作符,如&&(与)和||(或)。我们将在本节中展示如何使用它们。
我们将在本节中展示所有形式的if语句(if-then、if-then-else和嵌套if):
-
if-then-fi语句在最常见的形式中具有以下语法:if [condition] then commands fi这个 Bash
if语句在if关键字后运行condition。如果命令成功完成,即其退出状态为零,那么它将执行then关键字后列出的命令。 -
if-then-else-fi语句与if-then语句类似,具有以下语法:if [condition] then commands else commands fi与更简单的
if-then语句类似,condition会被执行,并且根据其退出状态,结果会有所不同。如果它成功完成,then关键字后的命令将被执行;但是如果有其他退出状态(非零),则else关键字后的命令将被执行。这为你提供了更多的选择和替代方案,具体取决于条件的结果。有些情况需要在单个if-then命令内部检查更多条件,因此可以使用嵌套的if语句来实现。
现在,让我们讨论一些在if语句中使用的基本条件参数,并附上一些示例:
- 在这个例子中,我们将检查用户输入的数字是偶数还是奇数。脚本将使用
read命令获取用户输入;然后,它将检查该数字除以二的余数是否为零。通过这种方式,它确定数字是奇数还是偶数。我们将在这个例子中使用if-then-else语句,并结合read和printf命令。以下截图展示了代码和执行输出:

图 8.21 – 确定偶数或奇数的脚本
- 以下脚本通过使用
test -f操作符检查用户输入的文件名是否确实是一个文件。我们将输入要检查的文件的绝对路径。该脚本名为testing_file.sh,其代码如下所示:

图 8.22 – 检查输入的文件名是否为文件
运行此脚本时,系统会提示你提供现有(或不存在)文件的文件名和完整路径。让我们进行一些测试。你将看到下图所示的输出。我们测试了在三种不同情况下脚本是否正常工作:当我们不提供文件名时,当我们输入正确的文件名时,以及当我们输入错误的文件名时:

图 8.23 – 运行文件检查脚本
既然我们已经涵盖了if条件语句的基本用法,让我们继续学习其他类型的语句,如循环语句。
使用循环语句
在 Bash 中,for、while 和 until 命令,我们将在本节中向您展示如何使用它们。循环语句用于需要重复处理的情况,例如循环执行多个命令,直到满足某个条件。
使用 for 语句
和任何其他编程语言一样,当需要执行重复任务时,就会出现迭代的需求。这意味着一些命令需要重复执行,直到满足条件。这在 Bash 中与其他编程语言类似,其中一个可以使用的命令就是for命令。它的结构如下:
for var in list
do
commands
done
如果你之前没有接触过这样的语句,我们将帮助你理解它的含义。通过var参数提供的变量会在每次迭代中被赋予list参数中提供的一系列值。在迭代开始时,变量会被设置为列表中的当前(或起始)值。每次迭代将使用列表中的另一个值,直到列表中的最后一项。列表中的项数将决定迭代的次数。在每次迭代中,commands块中的命令将被执行。这是我们描述的基本循环。
让我们看看for命令的一些基本用法。我们将遍历一个静态声明的数组。这意味着我们不会使用用户的输入;相反,我们将在脚本中直接指定数组。我们将使用一个临时变量(或计数器)i来遍历数组的整个长度。我们使用${array[@]}来指定数组的长度。当计数器(i)达到该长度时,循环将停止。下图展示了脚本的代码、运行该脚本的命令以及输出。请记住,我们提供了一个已经排序的数组。这不是排序算法:

图 8.24 – 使用 for 语句遍历数组
在下面的示例中,我们将重新讨论数组,并在for语句中使用其中的一些。这次,我们将展示如何对数组进行排序。我们将使用我们已经学习过的大部分结构,例如输入读取、输出格式化、数组和for语句。
重要提示
排序算法超出了本书的范围。我们只使用一种排序方法(冒泡排序)来展示如何使用数组和for语句,以及 Bash 的强大功能。然而,如果你计划在使用 Shell 时进行任何严肃的编程,我们建议你使用其他更适合这类操作的编程语言,例如 Python。Python 非常灵活,可以成功用于许多管理任务。
让我们回到排序问题上。假设我们有一个随机数组,我们希望对其进行排序。我们将使用一个只包含整数元素的数组。为了让事情更有趣,我们将提示用户从标准输入中输入数组元素。下图展示了脚本的代码:

图 8.25 – 使用冒泡排序算法对数组进行排序
让我们解释一下代码。我们使用了一个变量n来指定数组的长度。然后,我们用这个变量遍历用户提供的所有数字。在第一个for语句中,我们遍历所有数字,并小心地通过每一步增加计数器(使用I变量)(i++结构)。用户提供的数字随后存储在一个名为num的数组中。当排序开始时,我们使用两个嵌套的for语句和一个if语句。我们使用了一个新的计数器j,用于存储数组中新的连续数字的值。if语句比较数组中连续两个数字哪个更大,从而在数组的前两个元素之间进行交换。为了进行交换,我们使用一个临时计数器k来保持较大数字的值,以便交换这两个正在比较的数字。循环在所有数字都被遍历后结束。最终的for语句打印出新排序后的数组内容。
用户输入和命令输出如下所示:

图 8.26 – 显示我们的排序脚本的输入和输出
在上面的例子中,冒泡排序的工作原理如下:
-
第一步是比较数组中的前两个元素,即
45和24,看看哪个更大;45大于24,所以新的数组将是24 4556(该算法在45和24之间进行交换)。 -
第二步是比较下两个元素,它们现在是
45和56(因为45大于24,现在排在第二位;由于45不大于56,它们的位置将保持不变)。 -
第三步是再对所有元素进行一次遍历,并继续进行比较。
重要提示
冒泡排序并不是一种高效的排序算法,但它是一个很好的示例,展示了如何使用数组、for和if语句、用户输入和输出格式化。有关冒泡排序算法或其他任何类型排序算法的更多信息,建议您进行深入的在线搜索,或者阅读本章结尾的进一步阅读部分中提供的书目。
现在你知道如何使用for语句了,接下来我们来看看while语句。
使用while语句
while 循环与 for 循环类似,不同之处在于它某种程度上也结合了 if 语句。只要条件为真,循环就会执行命令。其语法如下:
while condition
do
commands
done
每次开始一次迭代时,都会测试条件。如果条件保持为真,退出状态为零,命令将继续执行直到条件状态发生变化。我们来看一些例子。
在以下脚本中,我们使用 while 语句以降序遍历数字列表:

图 8.27 – 使用 while 语句
while 语句会评估 [ $max -gt 0 ] 条件,并在条件为假时停止迭代。这意味着,只要你提供的数字大于 (-gt) 零,命令就会执行。while 循环内部的命令只是通过每次迭代减少数字。否则,你将陷入无限循环。因此,while 循环每执行一次,max 变量的值就会减少 1。我们用两个值进行了测试,分别是 10 和 30;你可以在图 8.27中看到输出结果。
while 语句非常有用且直观,它是对 for 语句的一个重要补充。现在,让我们来看一下 until 语句。
使用 until 语句
这种循环结构与 while 相反。它使用一个从一开始就是假的条件,只有当条件保持为假时,结构内的命令才会被执行。其语法如下:
until condition
do
commands
done
快速示例,让我们将之前的 while 循环用 until 语句重新做一次。以下是代码:

图 8.28 – 一个 until 语句的示例
你能找出 until 循环和 while 循环之间的区别吗?在 until 循环中,迭代会继续直到变量的值等于零,[ $max -eq 0 ]。until 循环内部的命令与 while 循环中的命令相同,唯一不同的是条件。输出结果,正如你预期的那样,和使用 while 循环时是一样的。
在我们继续学习更高级的编程结构之前,我们将提供一些关于使用特定关键字控制循环执行的基本信息。
退出循环语句
Bash 中用于退出循环的两个命令是 break 和 continue。它们非常简单直观。每当你想退出一个循环时,可以使用其中一个。让我们使用一个简单的脚本,遍历一系列整数,直到达到指定值并退出迭代。以下是代码:

图 8.29 – 使用 break 命令退出循环
在执行时,系统会要求用户输入序列的最大值和break的值。for循环会遍历序列,直到达到break的值,然后退出循环。我们使用break命令退出循环,并从零开始迭代。你可以尝试使用不同的值来测试结果。以下是使用最大序列值为10、break值为5时的输出:

图 8.30 – 使用 break 脚本示例的输出
如你所见,循环在达到 5 后退出。然而,它显示了 5 的值,并没有跳过它。这个问题可以解决(这不一定是问题,更像是算法设计的决策)。让我们看一下以下代码:

图 8.31 – 优化后的中断循环
为了让中断值不显示,我们将echo $i命令移到条件语句之后。这样可以防止脚本显示用户提供的中断值。两种用例都是有效的,并且提供相同的输出,因此输出打印命令的位置只与你的需求相关。
在下面的示例中,我们将展示如何使用continue命令退出循环。这个算法与break示例中使用的类似,只是这次用户提供的值不会显示在输出中。当满足条件时,break命令会退出循环,而continue命令则会跳过条件后面的其余命令执行,继续进行下一次循环。这种区别足以让你理解break和continue之间的差异。让我们看看使用continue命令的相同脚本的代码:

图 8.32 – 在循环中使用 continue 命令
如你所见,代码与前一个示例中使用的类似。唯一的区别是使用了continue命令,而不是break。现在,让我们看看获得的输出:

图 8.33 – 使用 continue 命令时的输出
如前图所示,数字 5 没有显示,这意味着当使用continue命令时,循环跳过了它。
现在你已经掌握了不少 Linux 脚本编写的知识。你知道如何使用变量、数组、if 和循环语句,甚至如何退出循环。在下一节中,我们将向你展示如何使用更高级的编程结构,如函数。
使用函数
与大多数编程语言一样,function关键字后跟函数名,如以下语法所示:
function name {
commands
}
另外,你可以在函数名称后面使用括号,如下所示的语法:
name() {
commands
}
语法中的名称是一个独特的名称,函数将在整个脚本中使用。命令由一个或多个 Shell 命令表示,这些命令会按照它们出现的顺序由函数执行。简而言之,你可以将函数看作是脚本中的脚本。让我们来看一些示例。
有些冗余的风险,我们将使用之前创建的一个脚本并将其作为函数使用,先给你展示一下函数是如何工作的。我们将使用排序数字的脚本,并将它变成一个函数。但在此之前,让我们先给你一些关于函数的注意事项。
重要提示
函数必须在调用之前创建。调用函数是指在脚本中运行它。如果你在调用函数之后创建它,脚本会给你一个错误,因为 Bash 是一个单次解释器。一个好的做法是将函数创建在脚本的开头。这样,脚本中任何需要使用它们的地方,它们都会随时可用。
所以,这就是它——我们的第一个函数显示在下面的截图中。如你所见,首先,我们创建了sorting函数,然后调用了它:

图 8.34 – 运行我们的第一个函数
到此为止,你已经知道如何在 Shell 脚本中创建和调用函数,但这仅仅是个开始。我们将尽力为你提供所有关于函数的必要信息,这样你就能在脚本中使用它们。如果你想了解更多信息,请参考进一步 阅读部分中的标题。
在下一节中,我们将带你了解不同的函数功能,如输出、变量和数组处理。
高级函数功能
正如之前所说,Bash shell 中的每个函数本身就像一个脚本。这意味着它可以像脚本一样管理变量、数组和输出。在本节中,我们将展示如何在函数中使用变量、数组和输出。
在函数中使用变量
两种类型的变量(全局变量和局部变量)都可以在函数内使用。让我们概述一下这两种变量之间的区别。全局变量在整个系统中都是可见且可用的,而局部变量仅在声明它们的函数内部可用。默认情况下,Bash 中的所有变量都被定义为全局变量,包括在函数内定义的变量。要在函数内声明局部变量,我们可以使用local关键字。让我们看一个基本的例子,来理解这一点是如何工作的:

图 8.35 – 显示局部变量和全局变量在函数中的作用
上述图展示了变量是如何声明的。例如,首先我们声明了 var1='1' 和 var2='2' 变量。默认情况下,它们被设置为全局变量。然后,在 var_function 函数内,我们修改了这两个变量的值,但其中一个变量我们使用了 local 关键字来定义它为函数内局部变量。对于另一个变量,我们没有使用相同的关键字——我们按函数外的定义方式进行定义。因此,当函数运行后打印变量到标准输出时,只有 var2 会保留函数内给定的值,而 var1 由于在函数内部是局部定义的,因此其值只在函数内有效。
在函数中使用数组
与变量不同,函数中的数组有些问题。需要考虑两种情况:一种是当你需要将数组从脚本传递到函数时,另一种是当你需要将数组从函数返回到脚本时。在第一种情况下,你不能将数组作为函数参数传递,因为函数只会使用数组的第一个值。因此,便捷的做法是将数组拆开,然后在函数内部重新构建它,尽管这听起来不实际,且经验证明确实不太实用。在第二种情况下,处理数组的方式与第一种情况类似,因为函数会按照正确的顺序输出值,而脚本则会将这些值重新组合成数组。来看一个例子:

图 8.36 – 在函数中使用数组
在前面的例子中,我们使用了两个函数,每个函数分别处理了前面小节开头描述的两种情况。脚本中的 test_function_1 函数展示了我们如何将数组元素传递给函数。test_function_2 函数展示了如何从函数中返回数组。下面是输出结果:

图 8.37 – 在函数中使用数组的输出
到此为止,你已经学习了如何创建脚本,以及如何使用数组、变量、编程结构和函数。现在,是时候学习如何在命令行和脚本中同时使用 sed 和 (g)awk 了。
使用 sed 和 (g)awk 命令
sed 和 (g)awk 都是用于处理文本文件的高级工具。sed 是流编辑器,而 awk 是一种编程语言。我们还使用 gawk 这个术语(因此括号内的 g 字母)是因为它是 awk 的 GNU 实现,提供了更多的功能和扩展。让我们学习如何在命令行中使用这两者。
在命令行中使用 sed
sed 不仅仅是一个简单的命令。它是一个数据流编辑器,基于预先提供的一组严格规则编辑文件。根据这些规则,命令逐行读取文件,然后对文件中的数据进行操作。sed 是一个非交互式流编辑器,它基于脚本进行更改,因此非常适合一次编辑多个文件或执行繁琐的重复任务。sed 命令的一般语法如下:
sed OPTIONS… [SCRIPT] [FILE…]
sed 命令使用不同的脚本子命令,其中一个常用的子命令用于文本替换。这里有许多其他用法,我们不会在此讨论,但如果你想了解更多关于 sed 工具的信息,网上和书籍中有很多很好的资料。例如,以下链接可能会有所帮助:www.ibm.com/docs/en/aix/7.2?topic=s-sed-command。
文本替换常用的语法如下:
sed 's/regex/replacement/flag'
以下是一些 sed 最常见用法的示例:
- 在文本文件中将一个名称替换为另一个名称。以此示例,我们将在家目录中使用一个名为
poem的新文件。在该文件中,我们生成了一首随机诗歌。任务是将文件中的Jane替换为Elane。字母g作为命令的标志,指定操作应为全局操作——即应应用于整个文本文件。结果如下:

图 8.38 – 使用 sed 命令替换文本文件中的字符串
如果你使用 cat 命令检查原始文件,你会发现 sed 只将更改后的名称结果输出到标准输出,并没有对原始文件进行任何更改。要使更改永久生效,你必须使用 -i 属性。
- 在以下示例中,我们将在每行的开头添加新空格,并将输出重定向到一个新文件。我们使用的是之前的
poem文件。文件的开头由^字符表示:

图 8.39 – 使用 sed 添加空格
- 我们将使用
sed仅显示poem文件中的第二行,并显示除了第 2 行之外的所有行:

图 8.40 – 使用 sed 显示文件中的特定行
- 让我们仅显示文件中的第 4 行到第 6 行——在我们的例子中,是
/etc/passwd文件:

图 8.41 – 使用 sed 显示文本文件中的特定行数
-
这是一个更实际的练习。我们将显示 Ubuntu 中
/etc/apt/sources.list的内容,排除注释行。为此,请使用以下命令:#) character, represented by the ^# characters inside the command. Those are the comments inside the file. We also use the g flag to specify that the operation is global for that file. The following is part of the output provided by the command. Use it on your Ubuntu system and analyze your output as well:

图 8.42 – 使用 sed 仅显示没有注释的行
在以下小节中,我们将探讨命令行中的awk命令。
从命令行使用 awk
awk不仅仅是一个简单的命令——它是一个模式匹配语言。它是一个完整的编程语言,是 PERL 的基础。它用于从文本文件中提取数据,语法类似于 C。它将文件视为由字段和记录组成。awk命令的一般结构如下:
awk '/search pattern 1/ {actions} /search pattern 2/ {actions}' file
awk的真正强大之处超出了本章的范围,因此我们将仅展示一个简单的例子,它可能对未来的系统管理员有所帮助。
作为示例,我们将生成一个包含所有已安装包名称的列表。我们只想打印每个包的名称,而不显示其他所有细节。为此,我们将使用以下命令:
sudo dpkg -l | awk '{print $2}' > package-list
此命令仅显示已安装的包的名称。以下是输出:

图 8.43 – 使用 awk 生成包名称列表
通常,要查看 Ubuntu 中已安装的包,我们会运行dpkg -l命令。在前面的示例中,我们将该命令的输出通过管道传递给awk命令,后者打印了dpkg -l输出的第二列(字段)('{print $2}')。然后,我们将所有内容重定向到一个名为package-list的新文件,并使用tail命令查看新创建文件的最后 10 行。
sed和awk都是非常强大的工具,我们仅仅触及了它们能做的一部分。请随意深入了解这两个极具潜力的工具。
使用脚本展示进程间通信
producer.sh和consumer.sh),从而模拟生产者和消费者进程。我们希望使用这样简单的模型仍然能为现实世界应用提供合理的类比。
现在,让我们来看看共享存储、命名管道和无名管道,以及我们在第五章中介绍过的套接字 IPC 机制,但没有详细讨论。
共享存储
在它最简单的形式下,storage文件。
在这个简单的用例中,显而易见的挑战是由于潜在的竞争条件,读写操作的完整性受到影响。为了避免竞争条件,在写操作期间必须对文件进行锁定,以防止与其他读或写操作发生重叠。为了简化问题,我们在我们的简单示例中不解决这个问题,但我们认为值得提及。
在我们的示例中,生产者每5秒将一组新的数据(10个随机 UUID 字符串)写入storage文件。以下截图展示了生产者的脚本:

图 8.44 – 生产者脚本(使用共享存储)
消费者每秒钟读取一次 storage 文件的内容。下图显示了消费者的脚本:

图 8.45 – 消费者脚本(使用共享存储)
运行两个脚本时,生产者生成随机字符串并将其写入 storage 文件,而消费者从同一个文件中读取生产者的输出。输出如下截图所示:

图 8.46 – 生产者(左)和消费者(右)通过共享存储进行通信
接下来,我们将向您展示无名管道是如何工作的。
无名管道
无名管道或匿名管道,也称为常规管道,将一个进程的输出传递到另一个进程的输入。以我们的生产者-消费者模型为例,最简单的方式来说明无名管道作为进程间通信机制的使用方式是:
producer.sh | consumer.sh
前述示意图中的关键元素是管道符号 (|)。管道的左侧产生一个输出,并直接传递给管道右侧进行消费。为了适应匿名管道 IPC 层,我们将创建两个新的脚本,分别叫做 producer2.sh 和 consumer2.sh。代码如下截图所示:

图 8.47 – 生产者 2(左)和消费者 2(右)脚本(使用无名管道)
在我们修改后的实现中,producer2.sh 向控制台打印一些数据(10 个随机的 UUID 字符串)。consumer2.sh 读取并显示通过 /dev/stdin 管道传来的数据,或者在管道为空时显示输入的参数。consumer2.sh 脚本中的 第 6 行 检查 /dev/stdin 中是否有管道数据(fd0 对应的值为 0):
if [ -t 0 ]
生产者-消费者通信的输出如下截图所示:

图 8.48 – 生产者通过无名管道将数据传递给消费者
输出清晰地显示了消费者进程打印的数据。(请注意 "Consumer data:" 标题前面的 UUID 字符串。)
IPC 匿名管道的一个问题是,生产者和消费者之间传输的数据并没有通过任何存储层持久化。如果生产者或消费者进程被终止,管道会消失,底层数据也会丢失。命名管道解决了这个问题,接下来我们将在下一节中展示。
命名管道
命名管道,也称为先进先出(FIFO),与传统的(无名)管道相似,但在语义上有很大不同。无名管道仅在相关进程运行时存在。然而,命名管道具有持久存储,且只要系统处于运行状态,它就会一直存在,无论与相关 IPC 通道连接的进程是否正在运行。
通常,命名管道充当一个文件,当不再使用时可以删除它。让我们修改生产者和消费者脚本,以便使用命名管道作为它们的 IPC 通道:

图 8.49 – producer3(左)和 consumer3(右)脚本(使用命名管道)
命名管道是 pipe.fifo(两个脚本中的 第 3 行)。当生产者或消费者启动时,会创建管道文件(如果该文件尚不存在)(第 6 行)。相关命令是 mkfifo(更多信息请参见 man mkfifo)。
生产者每秒向命名管道写入一个随机 UUID(producer3.sh 中的 第 14 行),消费者立即读取它(consumer3.sh 中的 第 10 行 到 第 12 行):

图 8.50 – producer3(左)和 consumer3(右)通过命名管道进行通信
我们以任意顺序启动了两个脚本——生产者和消费者。一段时间后,我们停止(中断)了消费者(步骤 1)。生产者继续运行,但自动停止向管道发送数据。然后,我们重新启动了消费者,生产者立即恢复向管道发送数据。过了一段时间后,我们停止了生产者(步骤 2)。这时,消费者变得空闲。重新启动生产者后,两者恢复正常操作,数据开始通过命名管道流动。这个工作流展示了命名管道的持久性和弹性,无论生产者或消费者进程的运行状态如何。
命名管道本质上是队列,数据按先进先出的顺序排队和出队。当两个以上的进程在 IPC 命名管道通道上进行通信时,FIFO 方法可能不合适,特别是当某些进程需要更高优先级的数据处理时。接下来,我们将向您展示套接字的工作原理。
套接字
有两种类型的 IPC 基于套接字的设施:
-
IPC 套接字:也称为 Unix 域套接字
-
网络套接字:传输控制协议(TCP)和用户数据报协议(UDP)套接字
IPC 套接字使用本地文件作为套接字地址,并支持同一主机上进程之间的双向通信。另一方面,网络套接字通过 TCP/UDP 网络扩展 IPC 数据连接层,使其超越本地机器。除了显而易见的实现差异外,IPC 套接字和网络套接字的数据通信通道行为相同。
两种套接字都配置为流,支持双向通信,并模拟客户端/服务器模式。只要任一端没有关闭,套接字的通信通道就会保持激活状态,从而中断 IPC 连接。
让我们调整我们的生产者-消费者模型来模拟 IPC 套接字(Unix 域套接字)数据连接层。我们将使用 netcat 来处理底层客户端/服务器 IPC 套接字的连接。netcat 是一个强大的网络工具,用于通过 TCP、UDP 和 ICP 套接字连接来读写数据。如果你的 Linux 发行版默认没有安装 netcat,你可以按如下方式安装:
在 Ubuntu/Debian 上,使用以下命令:
sudo apt install netcat
在 Fedora/RHEL 上,使用以下命令:
sudo dnf install nmap
欲了解更多关于 netcat 的信息,请参考相关的系统参考手册(man netcat)。
在以下示例中,我们将使用 producer4.sh 文件和 consumer4.sh 文件。每个文件的代码如下所示:

图 8.51 – 生产者 4(左)和消费者 4(右)脚本(使用 IPC 套接字)
生产者通过使用 IPC 套接字(producer4.sh 的最后一行)启动 netcat 监听端点,充当服务器角色:
nc -lU "${SOCKET}"
-l 选项表示监听(服务器)模式,而 -U "${SOCKET}" 选项参数指定了 IPC 套接字类型(Unix 域套接字)。消费者通过类似的命令连接到 netcat 服务器端点(consumer4.sh 的最后一行)。生产者和消费者都使用相同的(共享的)IPC 套接字文件描述符(/var/tmp/ipc.sock)进行通信。
生产者每秒向消费者发送随机 UUID 字符串(producer4.sh 中的 while-do-done 结构)。相关输出通过 tee 命令捕获到 stdout,然后被传输到 netcat:

图 8.52 – 生产者 4(左)和消费者 4(右)通过 IPC 套接字通信
消费者接收所有由生产者生成的消息(UUID)。为了证明消费者在监听,我们首先启动了消费者脚本,这时会生成两个错误,只要生产者没有通过套接字发送数据。一旦启动生产者脚本,消费者便开始接收数据。当我们中断生产者时,消费者立即停止接收数据。
在我们的生产者-消费者模型中,我们使用了netcat作为 IPC 套接字通信层。或者,我们也可以使用socat,这是一个类似的网络工具。在接下来的部分,我们将展示一个快速示例,使用脚本执行特定的 Linux 管理任务。作为额外内容,我们还将向你展示如何从源代码构建应用程序。
管理任务的脚本编写
在 Linux 操作系统中编写脚本主要在于帮助处理琐碎的管理任务。这样,你可以通过自动化工作流程,使工作变得更加轻松和愉快。Shell 脚本有很多应用场景,我们这里只提供一个简单的例子,希望它能帮助你理解脚本如何用于系统管理任务。
在接下来的部分,我们将展示如何从源代码构建一个特定发行版的包。这本应在第四章中向你展示,但当时你还不知道如何创建脚本。本章中,我们只使用了 Ubuntu 22.04.2 LTS 作为示例。在接下来的部分,我们将使用 Fedora 37 Server Edition 作为示例。
为系统管理任务创建脚本
在本节中,我们将展示几个用于管理任务的脚本。正如我们在本章开头所说,Shell 脚本是由一系列的 Bash 命令组成,当文件运行时,这些命令会依次执行。在接下来的子节中,我们将创建两个脚本来完成两项不同的管理任务。
更新脚本示例
这个脚本将使用dnf update命令在指定时间更新系统。它是一个非常简单的脚本,仅执行一个简单的命令并向标准输出显示一些消息。记住,这次我们将使用 Fedora Server 发行版。最简单的方式是在脚本中运行以下命令:
sudo dnf update -y
问题在于它需要输入sudo密码。这违背了自动化的目的,因为用户必须手动输入密码。让我们学习如何解决这个问题。首先,让我们看一下脚本的代码:

图 8.53 – 一个简单的更新脚本
现在,如果你希望命令在没有sudo密码的情况下运行,你需要编辑/etc/sudoers文件,并修改/添加一些内容。
重要提示
请考虑到这种方法会对系统产生广泛的影响,而不仅仅是你想运行的脚本。它不被认为是一种安全的措施,我们建议你在任何生产系统中使用时要特别小心并谨慎考虑。
移除以下行中%wheel前的注释:
%wheel ALL=(ALL) NOPASSWD: ALL
这将为所有在 wheel 组中的用户提供免密码的操作。这应该包括您正在使用的用户。如果它不在wheel组中,您将无法使用sudo。在我们的情况下,packt用户位于wheel组中。要测试它是否有效,您可以以常规用户身份运行该脚本,看看是否会提示输入sudo密码。
现在,我们需要将脚本调度到某个特定时间运行。不要忘记使您的脚本文件可执行,以便能够运行它。由于我们使用的是服务器发行版,我们假设这台机器是全天候运行的,所以在启动时运行毫无意义。此外,我们希望确保系统始终保持最新状态。为了调度脚本,我们将使用cron和crontab。我们将在下一节中向您展示它们的工作原理。
在 Linux 中使用 cron 调度脚本
使用crontab一开始可能看起来很吓人,但一旦你了解它,你会发现它非常有用。使用cron作业时,可能最令人害怕的部分就是定义过程,尤其是cron日期在/etc/crontab文件中提供:

图 8.54 – /etc/crontab 中 cron 作业定义的示例
我们认为前面的截图是自解释的。现在,让我们为更新脚本设置一个新的cron作业。我们将使用crontab -e命令。此命令将使用默认的 shell 文本编辑器,在我们的情况下(在 Fedora 中)是 Vim。运行crontab -e命令将启动一个新的 Vim 实例,您应该在其中编写作业。以下是我们的代码:
00 23 * * 0 packt /home/packt/update_script.sh
让我们解释一下。在前面的示例中,我们将新的更新脚本文件设置为每周日的 23:00 运行。运行该脚本的用户是packt。
创建完代码行后,您可以使用crontab -l命令检查cron作业是否已创建。如果您想查看某个用户的cron作业,可以使用以下命令:
sudo crontab -u packt -l
这里,packt是我们的用户。以下代码片段展示了代码,它可以在crontab中通过将输出和错误信息重定向到/dev/null来增强。这在我们不想看到命令输出或如果出现任何错误而不需要看到它们时非常有用。该行将修改如下:
00 23 * * 0 packt /home/packt/update_script.sh > /dev/null 2>&1
crontab是一个强大的工具,当您需要在 Linux 中调度任务时,它会非常有帮助。然而,它并不是唯一可用的工具。您可以随意探索其他可用的工具,例如at命令。接下来,我们将介绍另一个用于备份文件的简单脚本。
备份脚本示例
脚本的另一个常见用途是备份文件。我们将为此任务创建一个新的脚本,并根据我们的需求进行调度。脚本的代码如下截图所示:

图 8.55 – 一个简单的备份脚本
这段代码用于将 /home/packt 目录备份到 /mnt/backup 目录。/home/packt 的内容将通过 tar 命令打包,并使用当前日期和主机名作为文件名保存为 .tgz 文件。
重要提示
使用日期格式可能一开始让人觉得困难和令人生畏,但学会在脚本中使用它将会非常有价值。更多信息请使用date --help命令。
在下一个小节中,我们将向您展示如何在 Bash 中创建一个简单的随机密码生成器。
一个随机密码生成脚本
保持您的在线或本地账户安全至关重要,因此应该遵循使用安全密码的通用规则。我们将为您提供一个生成随机密码的脚本示例。
在 Linux 中有许多可用的密码生成器,但我们认为如果您自己创建一个小脚本会很有趣。比如,Bash 中有一个默认的密码生成应用程序 pwmake。不过,我们将创建我们自己的密码生成脚本。我们将使用 openssl 和一种叫做 base64 的编码机制。有关更多信息,请访问developer.mozilla.org/en-US/docs/Glossary/Base64。请记住,Linux 中还有许多其他生成随机字符的方法,其中一种广泛使用的是 /dev/urandom 伪随机数生成器。您可以自由探索更多的方式。与此同时,下面是我们密码生成脚本的代码:

图 8.56 – 密码生成脚本
运行脚本时,系统会提示您提供所需的字符数和要生成的密码数量:

图 8.57 – 运行密码生成脚本的输出
脚本编写是一项极其宝贵的技能,您应该掌握。我们刚刚触及了 shell 脚本的表面,但本章中的信息应该为您提供一个良好的开端,使您可以开始开发系统管理员脚本。
在下一个小节中,我们将向您展示如何将脚本打包成可以在 Linux 命令行上安装的完整应用程序。这是对第三章的补充,您在其中学习了 Linux 包管理。
打包脚本
Bash 脚本和其他软件一样,可以作为平台特定的包与 Linux 发行版一起部署。就像打包任何其他软件代码一样,你也可以打包一段 Shell 脚本代码。在这一部分,作为你在 第三章 中获得的知识的补充,我们将向你展示如何打包一个 Bash 脚本。正如前面所说,这里提供的信息也可以轻松地用于其他软件源(通常在 Linux 中使用 C/C++、Python、Rust、Java 或 Go)。在下一个小节中,我们将向你展示如何为基于 RHEL 的发行版创建 RPM 包。我们将在示例中使用 Fedora Linux 37。
从源代码创建 RPM 包
为了创建一个 RPM 包,我们将使用我们在前一部分开发的密码生成器脚本。在详细介绍之前,我们将简要说明编程语言类型以及 Bash 的位置。
重要提示
通常,软件程序是使用人类可读的源代码开发的。需要将这些代码转换为机器代码,以便计算机能够理解它。编程语言有几种大型的通用类型:解释型(如 Python 或 Bash——它是一种命令行语言,但仍被认为是解释型语言)和编译型(如 C/C++、Java 和 Go)是广泛使用的类型。Bash 源代码按行执行,而不需要事先编译成特定的机器代码。有关更多信息,请查看 en.wikipedia.org/wiki/List_of_programming_languages_by_type。
通常,软件源代码以压缩档案(.tar.gz 文件或 tarballs)的形式分发,然后打包成 RPM 包。档案通常包含源代码文件和许可证文件。该许可证文件提供了有关软件分发许可证类型的信息。在 自由和开源软件(FOSS)的情况下,许可证通常是 GPLv3 或 GLPLv3,但也使用其他类型,如 MIT 许可证、Apache 许可证和 BSD 许可证。
重要提示
有关 FOSS 许可证类型的详细信息,请查看 en.wikipedia.org/wiki/Comparison_of_free_and_open-source_software_licenses。
创建 RPM 包的必要步骤如下:
-
passgen-0.1。 -
将
LICENSE.txt放置在新创建的目录中。我们将使用 GPLv3 开源许可证。要添加到文件中的文本可以在www.gnu.org/licenses网站的 如何将这些条款应用于您的新程序 部分找到。 -
将
passgen_script.sh文件放入新目录,并将其名称更改为passgen-0.1.sh。 -
使用
install命令将passgen-0.1.sh脚本安装到文件系统层次结构标准$PATH中。我们使用install命令,因为我们的简单 Bash 脚本不需要任何依赖项:passgen script without using the full path. -
使用以下命令生成
tar.gz压缩档案:rpmdevtools package. We can install it using the following command:sudo dnf install rpmdevtools
This will install all additional packages and dependencies to set up the packaging workspace. -
设置工作区:为了设置工作区,我们将运行以下应用程序:
rpmbuild directory and sub-directories inside your home directory. The new rpmbuild directory has the following structure:

图 8.58 – rpmbuild 的目录结构
BUILD 目录包含有关构建过程的信息;RPMS 目录包含二进制 RPM;SOURCES 目录包含压缩源代码的 tarball 文件;SPECS 目录包含 SPEC 文件;SRPMS 目录包含源 RPM。
-
使用以下命令(在
passgen-0.1目录中)进入~/rpmbuild/SOURCES目录:SPEC file by running the rpmdev-newspec command from inside the ~/rpmbuild/SPECS directory:进入
~/rpmbuild/SPECS/目录后,你会看到创建了一个名为 passgen.spec 的新文件。新的SPEC文件包含自动生成的行,定义了特定的 RPM 宏。我们不会详细讲解宏,但你可以在rpm-software-management.github.io/rpm/manual/macros.html上找到更多信息。由于 Bash 是一种解释型语言,某些来自SPEC文件的默认规范是不需要的,例如BuildRequires,它已被删除。对于%build部分,我们没有提供任何信息,因为 Bash 不需要任何特定的内容。以下截图展示了SPEC文件:

图 8.59 – SPEC 文件的条目
-
SPEC文件,但也有其他使用案例,例如从rpmbuild构建 RPM,但选项不同。在构建源 RPM 时,我们将使用-bs选项,在从源重建 RPM 时,我们将使用--rebuild选项,而在从源构建二进制 RPM 时,我们将使用-bb选项。在我们的案例中,我们将从源创建一个二进制文件,并使用以下命令:.rpm file was created. We can check this at ~/rpmbuild/RPMS/, where we will have a new directory based on the CPU architecture. In our case, we will have a new noarch directory that contains the .rpm file for our password generator. The following is the output:

图 8.60 – 新的 RPM 二进制包
在这里,你正在从一个 Bash 脚本文件构建你的第一个 RPM 二进制包!你可以为你可能开发的任何类型的源文件执行此操作。构建二进制包并不是一项非常困难的任务,只要有关于 SPEC 文件中使用的宏的良好文档,你就可以顺利进行。关于此事项的更多可用文档信息可以在进一步阅读部分找到。
摘要
在本章中,你学习了如何在 Linux 中创建 Bash 脚本。你现在了解了 shell 变量和脚本变量,以及如何在脚本中使用编程结构,如循环、条件语句和数组。你还进一步理解了进程间通信的工作原理。你学到的技能将帮助你在任何 Linux 发行版中创建脚本。当你编写脚本时,你也会将文本编辑器的知识付诸实践。你在 Bash 脚本编写方面学到的技能也会在你作为系统管理员的日常工作中得以应用。
在下一章中,你将学习如何管理 Linux 中的安全性、访问控制机制、AppArmor 和 SELinux 以及防火墙。这项技能对于任何管理员都至关重要,必须成为任何高级培训的一部分。
问题
在本章中,我们讨论了 Linux Bash 脚本编写。以下是一些问题,供你测试知识并进行进一步练习:
-
什么是数组,如何在 Bash 脚本中使用?是否可以有混合类型?
提示:想一想它们的类型,如何定义,以及如何使用它们。
-
什么是无限循环?
提示:这是你不太希望使用的内容,但如果需要,应该知道如何逃避它。
-
Bash 脚本中可以使用多少种类型的循环?
提示:没有很多,但比条件循环要多。
-
了解如何构建 DEB 二进制包。
提示:使用来自 Debian 的示例,可以在
www.debian.org/doc/manuals/maint-guide/build.en.html和wiki.debian.org/HowToPackageForDebian中找到。
深入阅读
有关本章内容的更多信息,请参考以下资源:
-
Linux 管理最佳实践,由 Scott Alan Miller 编写,Packt 出版
-
Linux 命令行与 Shell 脚本技巧,由 Vedran Dakic 和 Jasmin Redzepagic 编写,Packt 出版
-
来自
access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/packaging_and_distributing_software/index、docs.fedoraproject.org/en-US/packaging-guidelines/RPMMacros/(RPM 宏),以及 rpm.org 的官方文档。
第九章:保护 Linux
保护 Linux 机器通常是一项平衡工作。最终目标本质上是防止数据被未授权访问。虽然有很多方法可以实现这一目标,但我们应当采用那些提供最大保护且最具效率的系统管理方法。评估攻击面和漏洞面(无论是内外部)通常是一个良好的起点。剩下的工作就是建立围栏并穿上盔甲——既不过高,也不过重。外部围栏是 网络防火墙。在内部系统层面,我们构建 应用安全策略。本章介绍了这两者,尽管平衡的艺术将由你来掌握。
在本章的第一部分,我们将讨论 访问控制机制(ACMs)及相关的安全模块——安全增强 Linux(SELinux)和 AppArmor。第二部分将探讨数据包过滤框架和防火墙解决方案。
完成本章后,你将熟悉用于设计和管理应用安全框架和防火墙的工具,这是保护 Linux 系统的第一步坚实基础。
下面是本章将要讨论的主题概述:
-
理解 Linux 安全—Linux 内核中可用的 ACM 概述
-
介绍 SELinux—深入了解用于管理访问控制策略的 Linux 内核安全框架
-
介绍 AppArmor—一个相对较新的安全模块,它基于安全配置文件控制应用程序的功能
-
使用防火墙—全面概述防火墙模块,包括
iptables、nftables、firewalld和ufw
技术要求
本章涉及多个主题,其中一些内容将通过大量命令行操作进行讲解。我们建议使用 Fedora 和 Ubuntu 平台,并通过终端或 SSH 访问。由于可能会影响防火墙规则的更改方式,直接访问控制台是更为理想的选择。
理解 Linux 安全
保护计算机系统或网络的一个重要考虑因素是系统管理员控制用户和进程如何访问各类资源(如文件、设备和接口)的方法,这些资源遍布各个系统。Linux 内核提供了几种这样的机制,统称为 访问控制机制(ACMs)。我们简要介绍它们:
-
自主访问控制(DAC)是与文件系统对象(包括文件、目录和设备)相关的典型 ACM。此类访问权限由对象的所有者在管理权限时自行决定。DAC 根据用户和组的身份(即 主体)来控制对 对象 的访问。根据主体的访问权限,他们还可以将权限传递给其他主体——例如,管理员可以管理普通用户的权限。
-
访问控制列表(ACLs)提供了对哪些主体(如用户和组)可以访问特定文件系统对象(如文件和目录)的控制。
-
强制访问控制(MAC)为主体提供不同的访问控制级别,以管理它们所拥有的对象。与 DAC 不同,DAC 中用户对自己拥有的文件系统对象拥有完全控制权,而 MAC 为所有文件系统对象添加了额外的标签或类别。因此,主体必须具备访问这些类别的适当权限,才能与被标记为该类别的对象进行交互。MAC 由SELinux在Red Hat Enterprise Linux(RHEL)/Fedora 上执行,而在 Ubuntu/Debian/openSUSE 上则由AppArmor执行。
-
基于角色的访问控制(RBAC)是基于权限的文件系统对象访问控制的替代方法。系统管理员根据某些业务或功能标准为特定文件系统对象分配角色,而不是使用权限。角色可以基于某些业务或功能标准,并可能对对象有不同的访问级别。
与 DAC 或 MAC 不同,在 DAC 或 MAC 中,主体对对象的访问严格依赖于权限,而 RBAC 模型是基于 MAC 或 DAC 的一种逻辑抽象,主体必须先是特定组或角色的成员,才能与对象进行交互。
-
多级安全(MLS)是一种特定的 MAC 方案,其中主体是进程,对象是文件、套接字以及其他类似的系统资源。
-
多类别安全(MCS)是 SELinux 的改进版本,允许用户为文件打上类别标签。MCS 重用了 SELinux 中许多 MLS 框架的内容。
总结一下我们对 ACM 的简短讨论,我们应该指出,我们在第四章,管理用户和组,特别是在管理 权限部分,讨论了 DAC 和 ACL 的一些内部机制。
接下来,我们将关注 SELinux——MAC 实现中的重要组成部分。
介绍 SELinux
SELinux是 Linux 内核中的一个安全框架,用于管理系统资源的访问控制策略。它支持前一节中描述的 MAC、RBAC 和 MLS 模型的组合。SELinux 是一组内核空间的安全模块和用户空间的命令行工具,它为系统管理员提供了一个机制,可以控制谁可以访问什么资源。SELinux 还旨在保护系统免受可能的配置错误和潜在受损进程的影响。
SELinux 由国家安全局(NSA)作为一组Linux 安全模块(LSM)和内核更新一起引入。SELinux 最终于 2000 年发布给开源社区,并从 2003 年开始成为 Linux 2.6 内核系列的一部分。
那么,SELinux 是如何工作的呢?我们将在下一节中讨论这个问题。我们将使用 Fedora 37 服务器版作为所有示例的操作系统。
使用 SELinux
SELinux 使用安全策略来定义系统中应用程序、进程和文件的各种访问控制级别。安全策略是一组规则,描述了什么可以或不能被访问。
SELinux 操作基于主体和对象。当一个特定的应用程序或进程(主体)请求访问一个文件(对象)时,SELinux 会检查请求中涉及的所需权限并执行相关的访问控制。主体和对象的权限存储在一个称为访问向量缓存(AVC)的查找表中。AVC 是基于SELinux 策略数据库生成的。
一个典型的 SELinux 策略包含以下资源(文件),每个文件反映了安全策略的特定方面:
-
类型强制:对于策略已授予或拒绝的操作(例如读取或写入文件访问权限)
-
接口:策略与之交互的应用程序接口(例如日志记录)
-
文件上下文:与策略相关的系统资源(例如日志文件)
这些策略文件通过 SELinux 构建工具汇编在一起,生成一个特定的安全策略。该策略被加载到内核中,添加到 SELinux 策略数据库,并在不重启系统的情况下生效。
在创建 SELinux 策略时,我们通常首先在permissive模式下测试它们,在该模式下,违规行为会被记录,但仍然允许。当违规发生时,SELinux 工具集中的 audit2allow 工具会提供帮助。我们使用 audit2allow 产生的日志痕迹来创建政策所需的额外规则,以考虑合法的访问权限。SELinux 违规行为会被记录在/var/log/messages中,并以avc: denied为前缀。
在我们学习如何创建和管理 SELinux 安全策略之前,让我们先看一些管理和控制 SELinux 的高级操作,用于日常管理任务。
理解 SELinux 模式
SELinux 在系统中要么是启用的,要么是禁用的。当启用时,它以以下模式之一运行:
-
enforcing:SELinux 有效地监控和控制安全策略。在 RHEL/Fedora 中,默认启用此模式。 -
permissive:安全策略被积极监控,但不执行访问控制。策略违规会被记录在/var/log/messages中。
当 SELinux 被禁用时,安全策略既不被监控也不被执行。以下命令用于检索系统上 SELinux 的当前状态:
sestatus
输出如下:

图 9.1 – 获取 SELinux 当前状态
当 SELinux 启用时,以下命令用于检索当前模式:
getenforce
在permissive模式下,我们得到enforcing的输出。
要从 enforcing 模式切换到 permissive 模式,我们可以运行以下命令:
sudo setenforce 0
在这种情况下,getenforce 命令会显示 permissive。要切换回 enforcing 模式,可以运行以下命令:
sudo setenforce 1
SELinux 模式也可以通过编辑 /etc/selinux/config 中的 SELINUX 值来设置。配置文件中记录了可能的值。
重要提示
手动编辑 SELinux 配置文件需要重启系统才能使更改生效。
启用 SELinux 后,系统管理员可以通过修改 /etc/selinux/config 中的 SELINUXTYPE 值,选择以下 SELinux 策略级别:targeted、minimum 和 mls。相应的值在配置文件中有详细说明。
重要提示
默认的 SELinux 策略设置是 targeted,通常建议不更改此设置,除非是 mls。
在启用 targeted 策略后,只有那些专门配置为使用 SELinux 安全策略的进程才能在受限(或限制)域中运行。这些进程通常包括系统守护进程(如 dhcpd 和 sshd)以及知名的服务器应用程序(如 Apache 和 PostgreSQL)。所有其他(非目标)进程不受限制,并通常被标记为 unconfined_t 域类型。
要完全禁用 SELinux,我们可以使用我们选择的文本编辑器(如 sudo nano /etc/selinux/config)编辑 /etc/selinux/config 文件,并进行以下更改:
SELINUX=disabled
或者,我们可以运行以下命令,将 SELinux 模式从 enforcing 更改为 disabled:
sudo sed -i 's/SELINUX=enforcing/SELINUX=disabled/g' /etc/selinux/config
我们可以使用以下命令检索当前配置:
cat /etc/selinux/config
在 SELinux 被禁用的情况下,我们将获得以下输出(摘录):

图 9.2 – 禁用 SELinux
我们需要重启系统才能使更改生效:
sudo systemctl reboot
接下来,让我们通过介绍SELinux 上下文来查看访问控制决策是如何做出的。
理解 SELinux 上下文
启用 SELinux 后,进程和文件会被标记上一个包含附加 SELinux 特定信息的上下文,如用户、角色、类型和级别(可选)。该上下文数据用于 SELinux 的访问控制决策。
SELinux 为 ls、ps 等命令添加了 -Z 选项,从而显示文件系统对象、进程等的安全上下文。
让我们创建一个任意文件并检查相关的 SELinux 上下文:
touch afile
ls -Z afile
输出如下:

图 9.3 – 显示文件的 SELinux 上下文
SELinux 上下文具有以下格式——由冒号(:)分隔的四个字段的序列:
USER:ROLE:TYPE:LEVEL
现在,让我们来看看 SELinux 上下文字段:
-
semanage工具是policycoreutils包的一部分,您可能需要在系统上安装此包:sudo semanage login -l命令的输出如下。输出可能因系统不同而略有差异:

图 9.4 – 显示 SELinux 用户映射
若要获取更多关于 semanage 命令行工具的信息,可以参考相关的系统参考(man semanage,man semanage-login)。
-
SELinux 角色:SELinux 角色是 RBAC 安全模型的一部分,实质上是 RBAC 属性。在 SELinux 上下文层次结构中,用户被授权角色,角色被授权类型或域。在 SELinux 上下文术语中,类型指的是文件系统对象类型,域指的是进程类型(参见此列表中的 SELinux 类型)。
以 Linux 进程为例,SELinux 角色作为域和 SELinux 用户之间的中介访问层。一个 可访问 的角色决定了哪个域(即进程)可以通过该角色进行访问。最终,这一机制控制了进程可以访问哪些对象类型,从而最小化权限提升攻击的攻击面。
-
SELinux 类型:SELinux 类型是 SELinux 类型强制的一种属性——一种 MAC 安全构造。对于 SELinux 类型,我们将域称为进程类型,将类型称为文件系统对象类型。SELinux 安全策略控制特定类型如何相互访问——无论是域到类型的访问,还是域到域的交互。
-
unclassified、confidential、secret和top-secret,如果级别不同,则以low-high表示,或者如果级别相同,则仅为low。例如,s0-s0等同于s0。每个级别代表一个 敏感性-类别 对,其中类别是可选的。当指定类别时,级别定义为sensitivity:category-set;否则,仅定义为sensitivity。
我们现在已经熟悉 SELinux 上下文。接下来,我们将看到 SELinux 用户上下文的实际应用。
SELinux 用户上下文
以下命令显示与当前用户相关的 SELinux 上下文:
id -Z
在我们的例子中,输出如下:

图 9.5 – 显示当前用户的 SELinux 上下文
在 RHEL/Fedora 中,Linux 用户默认是 unconfined(不受限制)的,具有以下上下文字段:
-
unconfined_u:用户身份 -
unconfined_r:角色 -
unconfined_t:域关联 -
s0-s0:MLS 范围(相当于s0) -
c0.c1023:类别集,表示所有类别(从c0到c1023)
接下来,我们将研究进程的 SELinux 上下文。
SELinux 进程上下文
以下命令显示当前 SSH 进程的 SELinux 上下文:
ps -eZ | grep sshd
命令输出如下:

图 9.6 – 显示与 SSH 相关进程的 SELinux 上下文
从输出结果中,我们可以推断第一行指的是运行着sshd服务器进程,使用了system_u用户身份,system_r角色,以及sshd_t域亲和性。第二行指的是当前用户的 SSH 会话,因此使用了unconfined上下文。系统守护程序通常与system_u用户和system_r角色相关联。
在总结本节关于 SELinux 上下文的内容之前,我们将详细讨论 SELinux 域过渡的相对常见情景,即一个域中的进程访问另一个域中的对象(或进程)。
SELinux 域过渡
假设一个 SELinux 保护的进程在一个域中请求访问另一个域中的对象(或另一个进程),SELinux 域过渡 就会发挥作用。除非存在允许相关域过渡的特定安全策略,否则 SELinux 会拒绝访问。
一个从一个域过渡到另一个域的 SELinux 保护进程会调用新域的entrypoint类型。SELinux 会评估相关的入口点权限,并决定是否允许发起进程进入新的域。
为了说明域过渡情景,我们将以使用passwd实用程序更改用户密码为简单案例。相关操作涉及passwd进程与/etc/shadow(可能还包括/etc/gshadow)文件之间的交互。当用户输入(和重新输入)密码时,passwd将会对用户密码进行哈希处理并存储在/etc/shadow中。
让我们检查涉及的 SELinux 域亲和性:
ls -Z /usr/bin/passwd
ls -Z /etc/shadow
相应的输出如下:

图 9.7 – 比较域亲和性上下文
passwd实用程序标记为passwd_exec_t类型,而/etc/shadow标记为shadow_t。必须存在特定的安全策略链,允许相关域从passwd_exec_t过渡到shadow_t;否则,passwd将无法正常工作。
让我们验证我们的假设。我们将使用sesearch工具查询我们假设的安全策略。默认情况下,Fedora 未安装该命令实用程序,因此您首先必须安装setools-console包。使用以下命令:
sudo dnf install setools-console -y
安装完软件包后,我们可以使用以下命令:
sudo sesearch -s passwd_t -t shadow_t -p write --allow
下面简要解释上述命令:
-
sesearch:搜索 SELinux 策略数据库 -
-s passwd_t:查找源类型或角色为passwd_t的策略规则 -
-t shadow_t:查找目标类型或角色为shadow_t的策略规则 -
-p write:查找具有写权限的策略规则 -
--allow:查找允许查询权限(用-p指定)的策略规则
上述命令的输出如下:

图 9.8 – 查询 SELinux 策略
在这里,我们可以看到append create权限,正如我们正确假设的那样。
我们为什么选择 passwd_t 源类型,而不是 passwd_exec_t?根据定义,域类型对应于 可执行文件 类型 passwd_exec_t 的类型是 passwd_t。如果我们不确定谁拥有对 shadow_t 文件类型的写权限,我们可以简单地在 sesearch 查询中排除源类型(-s passwd_t),并解析输出(例如,使用 grep passwd)。
使用 sesearch 工具查询安全策略非常方便。还有一些类似的工具可以用于故障排除或管理 SELinux 配置和策略。一个最为著名的 SELinux 命令行工具是 semanage,它用于管理 SELinux 策略。我们将在 管理 SELinux 策略 部分进行详细探讨。但首先,让我们看看创建 SELinux 安全策略的必要步骤。
创建 SELinux 安全策略
在本节的示例中,我们将使用一个用 C 语言开发的程序。这意味着我们需要编译它,这与我们在上一章所做的不同。为了能够编译 C 代码,我们需要在 Fedora 系统上安装 gcc,可以使用以下命令:
sudo dnf install gcc
现在,假设我们有一个名为 packtd 的守护进程,并且我们需要确保它可以访问 /var/log/messages。为了说明,守护进程有一个简单的实现:定期打开 /var/log/messages 文件进行写操作。使用您喜欢的文本编辑器(例如 nano)将以下内容(C 代码)添加到一个文件中。我们将该文件命名为 packtd.c:

图 9.9 – 一个简单的守护进程定期检查日志
让我们编译并构建 packtd.c 以生成相关的二进制可执行文件(packtd):
gcc -o packtd packtd.c
以下是编译源代码所使用命令的结果:

图 9.10 – 编译 C 源代码
现在源代码已经编译完成,我们准备继续创建 packtd 守护进程和所需的 SELinux 安全策略的步骤。这个练习对于 SELinux 管理和创建 systemd 守护进程同样有用。如果需要复习守护进程的相关内容,请参考 第五章。现在,让我们讨论创建守护进程和安全策略的步骤。
安装守护进程
首先,我们必须为 packtd 守护进程创建一个 systemd 单元文件。您可以使用您喜欢的文本编辑器(如 nano)来创建该文件。我们将这个文件命名为 packtd.service:

图 9.11 – packtd 守护进程文件
将我们创建的文件复制到相应的位置,如将 packtd 复制到 /usr/local/bin,将 packtd.service 复制到 /usr/lib/systemd/system/:
sudo cp packtd /usr/local/bin/
sudo cp packtd.service /usr/lib/systemd/system/
此时,我们已准备好启动我们的 packtd 守护进程:
sudo systemctl start packtd
sudo systemctl status packtd
让我们确认packtd守护进程还没有被 SELinux 限制或约束:
ps -efZ | grep packtd | grep -v grep
ps的-Z选项参数用于获取进程的 SELinux 上下文。
所有这些命令的输出如下:

图 9.12 – packtd 守护进程的运行状态和限制状态
unconfined_service_t安全属性表明packtd没有受到 SELinux 的限制。
接下来,我们将为packtd守护进程生成安全策略文件。
生成策略文件
要为packtd构建安全策略,我们需要生成相关的策略文件。构建安全策略的 SELinux 工具是sepolicy。此外,打包最终的安全策略二进制文件需要使用rpm-build工具。这些命令行工具在你的系统上可能默认不可用,因此你可能需要使用以下命令安装相关的包:
sudo dnf install -y policycoreutils-devel rpm-build
我们将使用下一个命令为packtd生成策略文件(无需超级用户权限):
sepolicy generate --init /usr/local/bin/packtd
输出如下:

图 9.13 – 使用 sepolicy 生成策略文件
稍作停顿,查看上面的截图。你会看到五个新文件已在你的主目录中创建。记住这一点,因为我们将在设置过程中使用这些文件。接下来,我们需要重新构建系统策略,以便它包含自定义的packtd策略模块。
构建安全策略
要构建安全策略,我们现在将使用在上一步中创建的packtd.sh构建脚本(有关详细信息,请参见图 9.13)。以下命令需要超级用户权限,因为它将新创建的策略安装到系统中:
sudo ./packtd.sh
构建过程相对较长,输出如下(摘录):

图 9.14 – 为 packtd 构建安全策略
请注意,构建脚本通过使用restorecon命令(在前面的输出中已突出显示)为packtd重新设置默认的SELinux安全上下文。现在我们已经构建了安全策略,接下来准备验证相关权限。
验证安全策略
首先,我们需要重启packtd守护进程,以应用策略更改:
sudo systemctl restart packtd
packtd进程现在应该反映出新的 SELinux 安全上下文:
ps -efZ | grep packtd | grep -v grep
输出显示了我们的安全上下文的新标签(packtd_t):

图 9.15 – packtd 的新安全策略
由于 SELinux 现在控制着我们的packtd守护进程,我们应该能在/var/log/messages中看到相关的审计日志,其中 SELinux 记录了系统的活动。让我们查看审计日志,寻找任何权限问题。以下命令通过ausearch工具获取AVC消息类型的最新事件:
sudo ausearch -m AVC -ts recent
我们将立即注意到,packtd 无法读取/写入 /var/log/messages:

图 9.16 – 不允许 packtd 读写
为了进一步查询 packtd 所需的权限,我们将把 ausearch 的输出输入到 audit2allow,这是一个生成所需安全策略存根的工具:
sudo ausearch -m AVC -ts recent | audit2allow -R
输出提供了我们需要的代码宏:

图 9.17 – 查询 packtd 缺失的权限
audit2allow 的 -R(--reference)选项会启动一个存根生成任务,有时可能会导致不准确或不完整的结果。在这种情况下,可能需要几次迭代来更新、重建并验证相关的安全策略。让我们按照之前屏幕截图中显示的输出所建议的进行必要的更改。我们将编辑之前生成的类型强制文件(packtd.te),并按照 audit2allow 输出的指示,精确地添加这些行(复制/粘贴)。文件的内容如下图所示(摘录)。我们需要添加的行已被突出显示:

图 9.18 – 编辑 packtd.te 文件
保存文件后,我们需要重新构建安全策略,重启 packtd 守护进程,并验证审计日志。我们将在整体流程中重述最后三个步骤:
sudo ./packtd.sh
sudo systemctl restart packtd
sudo ausearch -m AVC -ts recent | audit2allow -R
这一次,SELinux 审计应该是干净的:

图 9.19 – 解决 packtd 的权限问题
有时,ausearch 刷新其recent缓冲区可能需要一些时间。或者,我们可以指定一个起始时间戳来进行分析,例如在我们更新安全策略后,使用一个相对较新的时间戳:
sudo ausearch --start 04/19/2023 '17:30:00' | audit2allow -R
现在我们已经创建了自己的 SELinux 安全策略,让我们了解如何管理它。
管理 SELinux 策略
SELinux 提供了几种管理安全策略和模块的工具,其中一些将在SELinux 问题故障排除部分中简要介绍。虽然详细探讨这些工具超出了本章的范围,但我们将简要使用 semanage 来回顾一些涉及安全策略管理的使用案例。
semanage 命令的一般语法如下:
semanage TARGET [OPTIONS]
TARGET 通常表示用于策略定义的特定命名空间(例如,login、user、port、fcontext、boolean、permissive 等)。让我们看几个例子,以了解 semanage 是如何工作的。
在自定义端口上启用安全绑定
假设我们想为自定义 SSH 端口启用 SELinux,而不是默认的 22。我们可以使用以下命令检索当前 SSH 端口上的安全记录(标签):
sudo semanage port -l | grep ssh
对于默认配置,我们将获得如图 9.20中第 2 行所示的输出。
如果我们想在不同的端口(比如2222)上启用 SSH,首先,我们需要配置相关的服务(sshd)以便监听不同的端口(如第十三章所示)。我们这里不再深入讨论这些细节。这里,我们需要通过以下命令启用新端口上的安全绑定:
sudo semanage port -a -t ssh_port_t -p tcp 2222
下面是对前面命令的简要说明:
-
-a(--add):为给定类型添加一个新的记录(标签) -
-t ssh_port_t:对象的 SELinux 类型 -
-p tcp:与端口相关的网络协议
由于之前的命令和默认配置的输出,新的ssh_port_t类型的安全策略如下所示:

图 9.20 – 查询并更改 SSH 端口的 SELinux 安全标签
我们可以考虑删除旧的安全标签(针对端口22),但如果我们禁用端口22,其实也没有太大影响。如果我们想删除端口的安全记录,可以使用以下命令:
sudo semanage port -d -p tcp 22
我们使用了-d(--delete)选项来移除相关的安全标签。要查看我们semanage port策略的本地自定义设置,我们可以调用-C(--locallist)选项:
sudo semanage port -l -C
有关semanage port的更多信息,请参考相关的系统手册(man semanage port)。接下来,我们将了解如何修改特定服务器应用程序的安全权限。
修改目标服务的安全权限
semanage使用boolean命名空间来切换目标服务的特定功能的开关。目标服务是具有内建 SELinux 保护的守护进程。在以下示例中,我们想要启用 FTP over HTTP 连接。默认情况下,Apache(httpd)的此安全功能是关闭的。第十三章展示了httpd服务器的安装。让我们通过以下命令查询相关的httpd安全策略:
sudo semanage boolean -l | grep httpd | grep ftp
我们得到以下输出:

图 9.21 – 查询与 FTP 相关的 httpd 策略
如我们所见,相关的功能——httpd_enable_ftp_server——默认是关闭的。current和persisted的状态当前都是关闭的:(off, off)。我们可以通过以下命令启用它:
sudo semanage boolean -m --on httpd_enable_ftp_server
要查看semanage boolean策略的本地自定义设置,我们可以调用-C(--locallist)选项:
sudo semanage boolean -l -C
新配置现在如下所示:

图 9.22 – 启用 FTP over HTTP 的安全策略
在前面的示例中,我们使用了-m(--modify)选项与semanage boolean命令一起,来切换httpd_enable_ftp_server功能。
有关 semanage boolean 的更多信息,您可以参考相关的系统参考资料(man semanage boolean)。现在,让我们学习如何修改特定服务器应用程序的安全上下文。
为目标服务修改安全上下文
在此示例中,我们希望保护存储在本地系统自定义位置的 SSH 密钥。由于我们正在针对与文件系统相关的安全策略,因此将使用 semanage 的 fcontext(文件上下文)命名空间。
以下命令查询 sshd 的文件上下文安全设置:
sudo semanage fcontext -l | grep sshd
以下是输出中的相关摘录:

图 9.23 – SSH 密钥的安全上下文
以下命令还将 /etc/ssh/keys/ 路径添加到与 sshd_key_t 上下文类型相关的安全位置:
sudo semanage fcontext -a -t sshd_key_t '/etc/ssh/keys(/.*)?'
'/etc/ssh/keys(/.*)?' 正则表达式匹配 /etc/ssh/keys/ 目录中的所有文件,包括任何嵌套级别的子目录。要查看 semanage fcontext 策略的本地自定义设置,我们可以调用 -C(--locallist)选项:
sudo semanage fcontext -l -CWe should see our new security context:

图 9.24 – 我们的 SSH 密钥的修改后的安全上下文
我们还应初始化 /etc/ssh/keys 目录的文件系统安全上下文(如果我们已经创建了该目录;否则,将会出现错误消息):
sudo restorecon -r /etc/ssh/keys
restorecon 是一个 SELinux 工具,用于将默认安全上下文恢复到文件系统对象。-r(或 -R)选项指定对相关路径执行递归操作。
有关 semanage fcontext 的更多信息,您可以参考相关的系统参考资料(man semanage fcontext)。接下来,我们将研究如何启用特定服务器应用程序的 permissive 模式。
为目标服务启用宽松模式
在本章早些时候,我们创建了一个自定义守护进程(packtd)并为其配置了安全策略。请参阅本章早些时候的创建 SELinux 安全策略部分。在整个过程中,我们能够运行并测试 packtd,而不会因不符合 SELinux 策略而导致守护进程被 SELinux 关闭。我们的 Linux 系统以 enforcing 模式(默认)运行 SELinux,并且不是宽松模式。有关 enforcing 和 permissive 模式的更多信息,请参阅理解 SELinux 模式部分。
默认情况下,SELinux 对系统中任何未定位的类型都是宽松的。这里的未定位指的是尚未强制进入限制性(或受限)模式的域(类型)。
当我们为 packtd 守护进程构建安全策略时,我们让相关的 SELinux 构建工具生成默认的类型强制文件(packt.te)和其他资源。快速查看 packt.te 文件显示我们的 packtd_t 类型是 permissive:
cat packt.te
以下是文件中的相关摘录:

图 9.25 – packtd_t 域是宽松的
因此,packtd_t 域本质上是宽松的。限制 packtd 的唯一方法是从 packtd.te 文件中删除 permissive 行,并重建相关的安全策略。我们将把这个作为练习留给你。我们在这里要表达的是,展示一个可能存在问题的——在我们案例中是 permissive——域,可以通过使用 semanage permissive 命令来管理 permissive 类型,从而捕获它。
要管理单个目标的 permissive 模式,我们可以使用带有 permissive 命名空间的 semanage 命令。以下命令列出当前所有处于 permissive 模式的域(类型):
sudo semanage permissive -l
在我们的案例中,我们有一个内置的 packtd_t 域,它是 permissive:

图 9.26 – 显示宽松类型
一般来说,默认的 SELinux 配置不太可能有任何 permissive 类型。
我们可以使用 semanage permissive 命令,在测试或排查特定功能时,临时将受限域设置为 permissive 模式。例如,以下命令将 Apache (httpd) 守护进程设置为 permissive 模式:
sudo semanage permissive -a httpd_t
当我们查询 permissive 类型时,得到以下结果:

图 9.27 – 自定义宽松类型
使用 semanage permissive 命令将域或类型设为宽松状态后,会显示为 自定义 宽松类型。
要将 httpd_t 域恢复到受限(限制)状态,我们可以使用带有 -d(--delete)选项的 semanage permissive 命令:
sudo semanage permissive -d httpd_t
该命令的输出如下面所示:

图 9.28 – 恢复宽松类型
请注意,我们不能使用 semanage 命令来限制内置的 permissive 类型。如前所述,packtd_t 域本质上是宽松的,无法被限制。
到此为止,我们已经对 SELinux 安全策略的内部机制有了基本的了解。接下来,我们将转向一些更高层次的操作,用于在日常管理任务中管理和控制 SELinux。
排查 SELinux 问题
即使在我们相对简短的 SELinux 探索过程中,我们也使用了少数工具和方法来检查安全策略的内部机制,以及主体(用户和进程)与对象(文件)之间的访问控制。SELinux 的问题通常归结为操作被拒绝,无论是在特定主体之间,还是在主体与某些对象之间。与 SELinux 相关的问题并不总是显而易见或容易排查,但了解可以帮助的工具已经是解决这些问题的一个良好开端。
下面是一些这些工具的简要说明:
-
/var/log/messages:包含 SELinux 访问控制跟踪和策略违规的日志文件 -
audit2allow:从与被拒绝操作相关的日志跟踪生成 SELinux 策略规则 -
audit2why:提供 SELinux 审计消息的用户友好型翻译,用于策略违规的分析 -
ausearch:查询/var/log/messages中的策略违规记录 -
ls -Z:列出文件系统对象及其对应的 SELinux 上下文 -
ps -Z:列出进程及其相应的 SELinux 上下文 -
restorecon:恢复文件系统对象的默认 SELinux 上下文 -
seinfo:提供有关 SELinux 安全策略的一般信息 -
semanage:管理和提供 SELinux 策略的见解 -
semodule:管理 SELinux 策略模块 -
sepolicy:检查 SELinux 策略 -
sesearch:查询 SELinux 策略数据库
对于这些工具中的大多数,都会有相应的系统参考(如man sesearch),提供关于如何使用这些工具的详细信息。除此之外,你还可以探索 SELinux 提供的广泛文档。方法如下。
访问 SELinux 文档
SELinux 有大量的文档,可以通过 RHEL/Fedora 可安装的软件包获取,或者在线访问access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/html/using_selinux/index(适用于 RHEL 9)。
以下命令安装 RHEL 9 系统上的 SELinux 文档:
sudo dnf install -y selinux-policy-doc.noarch
你可以通过以下命令浏览特定的 SELinux 主题(例如):
man -k selinux | grep httpd
SELinux 是 Linux 内核中最为成熟且高度可定制的安全框架之一。然而,它相对广泛的领域和固有的复杂性可能让很多人感到不知所措。有时,即使是经验丰富的系统管理员,选择一个 Linux 发行版时,也可能会因其底层安全模块的不同而有所犹豫。SELinux 通常在 RHEL/Fedora 平台上可用,但它在SUSE Linux Enterprise(SLE)上也可以选择使用。谷歌的 Android 也支持 SELinux,而 Debian 也提供了它作为一个选项。另一个相对轻量且高效的安全框架是AppArmor,它默认在 Ubuntu、Debian 和 openSUSE 上可用。我们将在下一节深入探讨它。
介绍 AppArmor
AppArmor是一个基于 MAC 模型的 Linux 安全模块,限制应用程序对有限资源的访问。AppArmor 使用基于安全配置文件的访问控制机制,这些配置文件已经被加载到 Linux 内核中。每个配置文件包含一系列访问各种系统资源的规则。AppArmor 可以配置为强制访问控制,或仅对访问控制违规行为发出警告。
AppArmor 通过防止已知和未知的漏洞被利用,主动保护应用程序和操作系统资源免受内外部威胁,包括零日攻击。
自 2.6.36 版本起,AppArmor 已被集成到主线 Linux 内核中,并且目前与 Ubuntu、Debian、openSUSE 及类似发行版一起发布。
在接下来的章节中,我们将使用 Ubuntu Server 22.04 LTS 环境来展示一些 AppArmor 的实际示例。大多数相关的命令行工具在任何安装了 AppArmor 的平台上都可以正常工作。
使用 AppArmor
AppArmor 命令行工具通常需要超级用户权限。以下命令检查 AppArmor 的当前状态:
sudo aa-status
下面是命令输出的摘录:

图 9.29 – 获取 AppArmor 的状态
aa-status(或apparmor_status)命令提供当前加载的所有 AppArmor 配置文件的完整列表(前面未显示)。我们接下来将查看 AppArmor 配置文件。
介绍 AppArmor 配置文件
使用 AppArmor 时,进程通过配置文件被限制(或约束)。AppArmor 配置文件在系统启动时加载,并以enforce模式或complain模式运行。接下来,我们将详细探讨这些模式:
-
enforce模式:AppArmor 会阻止在enforce模式下运行的应用程序执行受限操作。访问违规会通过syslog中的日志条目进行信号提示。默认情况下,Ubuntu 会以enforce模式加载应用程序配置文件。 -
complain模式:运行在complain模式中的应用程序可以执行受限操作,同时 AppArmor 会为相关违规行为创建日志条目。complain模式非常适合测试 AppArmor 配置文件。潜在的错误或访问违规可以在切换到enforce模式之前被捕获并修复。
记住这些介绍性说明后,让我们创建一个带有 AppArmor 配置文件的简单应用程序。
创建配置文件
在这一节中,我们将创建一个由 AppArmor 保护的简单应用程序。我们希望这个练习能帮助你对 AppArmor 的内部工作有一个合理的了解。我们将这个应用命名为appackt,它将是一个简单的脚本,创建一个文件,向其中写入内容,然后删除文件。目标是让 AppArmor 阻止我们的应用访问本地系统中的任何其他路径。为了理解这一点,可以将其视为简单的日志回收。
这是appackt脚本,请原谅它的简陋实现:

图 9.30 – appackt 脚本
我们假设log目录已经存在,并与脚本位于同一位置:
mkdir ./log
让我们使脚本可执行并运行它:
chmod a+x appackt
./appackt
输出如下:

图 9.31 – appackt 脚本的输出
现在,让我们开始使用 AppArmor 保护并强制执行我们的脚本。在开始之前,我们需要安装apparmor-utils包——AppArmor 工具集:
sudo apt install -y apparmor-utils
我们将使用几个工具来帮助创建配置文件:
-
aa-genprof:生成一个 AppArmor 安全配置文件 -
aa-logprof:更新一个 AppArmor 安全配置文件
我们使用aa-genprof在运行时监控我们的应用程序,并让 AppArmor 了解它。在此过程中,我们将被提示确认并选择在特定情况下需要的行为。
一旦配置文件创建完成,我们将使用aa-logprof工具进行进一步调整,在complain模式下测试,如果发生任何违规行为。
我们从aa-genprof开始。我们需要两个终端:一个用于aa-genprof监控会话(在终端 1中),另一个用于运行我们的脚本(在终端 2中)。
我们将从终端 1开始,并运行以下命令:
sudo aa-genprof ./appackt
有一个提示正在等待我们。接下来,在终端 1的提示等待时,我们将切换到终端 2并运行以下命令:
./appackt
现在,我们必须回到终端 1并回答由aa-genprof发出的提示,如下所示(详细输出见图 9.32):
-
S(Scan) -
/usr/bin/touch、/usr/bin/date、/usr/bin/cat和/usr/bin/rm该提示请求对运行我们应用程序的所有进程授予执行权限。
回答:
I(Inherit) -
/dev/tty、/home/packt/log/appackt和/etc/ld.so.cache该提示请求应用程序对不同文件进行读/写权限。
回答:
A(Allow) -
S(Save)

图 9.32 – 运行 aa-genprof 并设置配置文件
此时,我们已经完成了使用aa-genprof的扫描,并且可以用F(Finish)回答最后一个提示:

图 9.33 – 完成扫描
现在,我们的应用程序(appackt)在enforce模式下受到 AppArmor 的强制保护(默认情况下)。
对于接下来的步骤,我们只需要一个终端窗口。如果需要,我们将运行aa-logprof命令进一步调整appackt的安全配置文件:
sudo aa-logprof
我们将再次收到几个类似的提示,要求进一步的权限,这些权限可能是我们的脚本或其他应用程序所需要的。提示会在Inherit和Allow之间交替,根据情况作出选择。这里不再详细说明,因为这超出了本书的范围。不过,到目前为止,你应该已经对这些提示及其含义有了大致的了解。尽管如此,建议你始终思考所请求的权限,并根据需要采取相应的行动。
我们可能需要运行aa-logprof命令几次,因为每次迭代都会发现并处理新的权限,这取决于由我们的脚本生成的子进程等。最终,appackt脚本将成功运行。
在之前描述的迭代过程中,我们可能会在 AppArmor 数据库中留下几个未知或孤立的条目,它们是我们之前尝试保护应用程序时的遗留物:

图 9.34 – 迭代过程的残留物
它们将根据我们应用程序的路径(/home/packt/appackt)命名。我们可以使用以下命令清理这些条目:
sudo aa-remove-unknown
现在我们可以验证我们的应用程序确实受到 AppArmor 的保护:
sudo aa-status
输出的相关摘录如下:

图 9.35 – appackt 处于投诉模式
我们的应用程序(/home/packt/appackt)如预期般显示在 enforce 模式下。
接下来,我们需要验证我们的应用程序是否符合 AppArmor 强制执行的安全策略。让我们编辑 appackt 脚本并更改 图 9.35 中 第 6 行 的 LOG_FILE 路径为以下内容:
LOG_FILE="./log to logs. Let’s create a logs directory and run our app:
mkdir logs
./appackt
The preceding output suggests that `appackt` is attempting to access a path outside the permitted boundaries by AppArmor, thus validating our profile:

Figure 9.36 – appackt acting outside security boundaries
Let’s revert the preceding changes and have the `appackt` script act normally. Let’s assume that our app is not yet running in `enforce` mode (but ours is already). We can change it to `enforce` profile mode with the following command:
sudo aa-enforce /home/packt/appackt
We can verify that our application is indeed running in `enforce` mode with the following command:
sudo aa-status
If we wanted to make further adjustments to our application and then test it with the related changes, we would have to change the profile mode to `complain` and then reiterate the steps described earlier in this section. The following command sets the application profile to `complain` mode:
sudo aa-complain /home/packt/appackt
AppArmor profiles are plain text files stored in the `/etc/apparmor.d/` directory. Creating or modifying AppArmor profiles usually involves manually editing the corresponding files or the procedure described in this section using the `aa-genprof` and `aa-logprof` tools.
Next, let’s look at how to disable or enable AppArmor application profiles.
Disabling and enabling profiles
Sometimes, we may want to disable a problematic application profile while working on a better version. Here’s how we do this.
First, we need to locate the application profile we want to disable (for example, `appackt`). The related file is in the `/etc/apparmor.d/` directory and it’s named according to its full path, with dots (`.`) instead of slashes (`/`). In our case, the file is `/etc/apparmor.d/home.packt.appackt`, as seen in the following screenshot:

Figure 9.37 – Location of the AppArmor profile for appackt
To disable the profile, we must run the following commands:
sudo ln -s /etc/apparmor.d/home.packt.appackt /etc/apparmor.d/disable/
sudo apparmor_parser -R /etc/apparmor.d/home.packt.appackt
If we run the `aa-status` command, we won’t see our `appackt` profile anymore. The related profile is still present in the filesystem, at `/etc/apparmor.d/disable/home.packt.appackt`.
In this situation, the `appackt` script is not enforced by any restrictions. To re-enable the related security profile, we can run the following commands:
sudo rm /etc/apparmor.d/disable/home.packt.appackt
sudo apparmor_parser -r /etc/apparmor.d/home.packt.appackt
The `appackt` profile should now show up in the `aa-status` output as running in `enforce` mode. All the previous commands and their output are shown in the following screenshot:

Figure 9.38 – Disabling and enabling an AppArmor profile
To disable or enable the profile, we used the `apparmor_parser` command, besides the related filesystem operations. This utility assists with loading (`-r`, `--replace`) or unloading (`-R`, `--remove`) security profiles to and from the kernel.
Deleting AppArmor security profiles is functionally equivalent to disabling them. We can also choose to remove the related file from the filesystem altogether. If we delete a profile without removing it from the kernel first (with `apparmor_parser -R`), we can use the `aa-remove-unknown` command to clean up orphaned entries.
Let’s conclude our relatively brief study of AppArmor internals with some final thoughts.
Final considerations
Working with AppArmor is relatively easier than SELinux, especially when it comes to generating security policies or switching back and forth between permissive and non-permissive mode. SELinux can only toggle the permissive context for the entire system, while AppArmor does it at the application level. On the other hand, there might be no choice between the two, as some major Linux distributions either support one or the other. AppArmor is used on Debian, Ubuntu, and openSUSE, while SELinux runs on RHEL/Fedora and SLE. Theoretically, you can always try to port the related kernel modules across distros, but that’s not a trivial task.
As a final note, we should reiterate that in the big picture of Linux security, SELinux and AppArmor are ACMs that act locally on a system, at the application level. When it comes to securing applications and computer systems from the outside world, firewalls come into play. We’ll look at firewalls next.
Working with firewalls
Traditionally, a **firewall** is a network security device that’s placed between two networks. It monitors the network traffic and controls access to these networks. Generally speaking, a firewall protects a local network from unwanted intrusion or attacks from the outside. But a firewall can also block unsolicited locally originated traffic targeting the public internet. Technically, a firewall allows or blocks incoming and outgoing network traffic based on specific security rules.
For example, a firewall can block all but a select set of inbound networking protocols (such as SSH and HTTP/HTTPS). It may also block all but approved hosts within the local network from establishing specific outbound connections, such as allowing outbound **Simple Mail Transfer Protocol** (**SMTP**) connections that originated exclusively from the local email servers.
The following diagram shows a simple firewall deployment regulating traffic between a local network and the internet:

Figure 9.39 – A simple firewall diagram
The outgoing security rules prevent bad actors, such as compromised computers and untrustworthy individuals, from directing attacks on the public internet. The resulting protection benefits external networks, but it’s ultimately essential for the organization as well. Thwarting hostile actions from the local network avoids them being flagged by **internet service providers** (**ISPs**) for unruly internet traffic.
Configuring a firewall usually requires a default security policy acting at a global scope, and then configuring specific exceptions to this general rule, based on port numbers (protocols), IP addresses, and other criteria.
In the following sections, we’ll explore various firewall implementations and firewall managers. First, let’s take a brief look under the hood at how a firewall monitors and controls network traffic by introducing the Linux firewall chain.
Understanding the firewall chain
At a high level, the TCP/IP stack in the Linux kernel usually performs the following workflows:
* Receives data from an application (process), serializes the data into network packets, and transmits the packets to a network destination, based on the respective IP address and port
* Receives data from the network, deserializes the network packets into application data, and delivers the application data to a process
Ideally, in these workflows, the Linux kernel shouldn’t alter the network data in any specific way apart from shaping it due to TCP/IP protocols. However, with distributed and possibly insecure network environments, the data may need further scrutiny. The kernel should provide the necessary hooks to filter and alter the data packets further based on various criteria. This is where firewalls and other network security and intrusion detection tools come into play. They adapt to the kernel’s TCP/IP packet filtering interface and perform the required monitoring and control of network packets. The blueprint of the Linux kernel’s network packet filtering procedure is also known as the **firewall chain** or **firewalling chain**:

Figure 9.40 – The Linux firewall chain
When the incoming data enters the firewall packet filtering chain, a routing decision is made, depending on the packet’s destination. Based on that routing decision, the packet can follow either the **INPUT** chain (for localhost) or the **FORWARD** chain (for a remote host). These chains may alter the incoming data in various ways via hooks that are implemented by network security tools or firewalls. By default, the kernel won’t change the packets traversing the chains.
The **INPUT** chain ultimately feeds the packets into the **local application process** consuming the data. These local applications are usually user-space processes, such as network clients (for example, web browsers, SSH, and email clients) or network servers (for example, web and email servers). They may also include kernel space processes, such as the kernel’s **Network File** **System** (**NFS**).
Both the **FORWARD** chain and the **local processes** route the data packets into the **OUTPUT** chain before placing them on the network.
Any of the chains can filter packets based on specific criteria, such as the following:
* The source or destination IP address
* The source or destination port
* The network interface involved in the data transaction
Each chain has a set of security rules that are matched against the input packet. If a rule matches, the kernel routes the data packet to the **target** specified by the rule. Some predefined targets include the following:
* `ACCEPT`: Accepts the data packet for further processing
* `REJECT`: Rejects the data packet
* `DROP`: Ignores the data packet
* `QUEUE`: Passes the data packet to a user-space process
* `RETURN`: Stops processing the data packet and passes the data back to the previous chain
For a full list of predefined targets, please refer to the `iptables-extensions` system reference (`man iptables-extensions`).
In the following sections, we’ll explore some of the most common network security frameworks and tools based on the kernel’s networking stack and firewall chain. We’ll start with Netfilter—the Linux kernel’s packet filtering system. Next, we’ll look at `iptables`—the traditional interface for configuring Netfilter. `iptables` is a highly configurable and flexible firewall solution. Then, we’ll briefly cover `nftables`, a tool that implements most of the complex functionality of `iptables` and wraps it into a relatively easy-to-use command-line interface. Finally, we’ll take a step away from the kernel’s immediate proximity of packet filtering frameworks and look at `firewalld` (RHEL/Fedora) and `ufw` (Debian/Ubuntu), two user-friendly frontends for configuring Linux firewalls on major Linux distros.
Let’s start our journey with Netfilter.
Introducing Netfilter
**Netfilter** is a packet filtering framework in the Linux kernel that provides highly customizable handlers (or hooks) to control networking-related operations. These operations include the following:
* Accepting or rejecting packets
* Packet routing and forwarding
* **Network address translation** and **network address port** **translation** (**NAT**/**NAPT**)
Applications that implement the Netfilter framework use a set of callback functions built around hooks registered with kernel modules that manipulate the networking stack. These callback functions are further mapped to security rules and profiles, which control the behavior of every packet traversing the networking chain.
Firewall applications are first-class citizens of Netfilter framework implementations. Consequently, a good understanding of Netfilter hooks will help Linux power users and administrators create reliable firewall rules and policies.
We’ll have a brief look at these Netfilter hooks next.
Netfilter hooks
As packets traverse the various chains in the networking stack, Netfilter triggers events for the kernel modules that are registered with the corresponding **hooks**. These events result in notifications in the module or packet filtering application (for example, the firewall) implementing the hooks. Next, the application takes control of the packet based on specific rules.
There are five Netfilter hooks available for packet filtering applications. Each corresponds to a networking chain, as illustrated in *Figure 9**.40*:
* `NF_IP_PRE_ROUTING`: Triggered by incoming traffic upon entering the network stack and before any routing decisions are made about where to send the packet
* `NF_IP_LOCAL_IN`: Triggered after routing an incoming packet when the packet has a `localhost` destination
* `NF_IP_FORWARD`: Triggered after routing an incoming packet when the packet has a remote host destination
* `NF_IP_LOCAL_OUT`: Triggered by locally initiated outbound traffic entering the network stack
* `NF_IP_POST_ROUTING`: Triggered by outgoing or forwarded traffic, immediately after routing it and just before it exits the network stack
Kernel modules or applications registered with Netfilter hooks must provide a priority number to determine the order the modules are called in when the hook is triggered. This mechanism allows us to deterministically order multiple modules (or multiple instances of the same module) that have been registered with a specific hook. When a registered module is done processing a packet, it provides a decision to the Netfilter framework about what should be done with the packet.
The Netfilter framework’s design and implementation is a community-driven collaborative project part of the **Free and Open Source Software** (**FOSS**) movement. As a good starting point for the Netfilter project, you may refer to [`www.netfilter.org/`](http://www.netfilter.org/).
One of the most well-known implementations of Netfilter is `iptables`—a widely used firewall management tool that shares a direct interface with the Netfilter packet filtering framework. A practical examination of `iptables` would further reveal the functional aspects of Netfilter. Let’s explore `iptables` next.
Working with iptables
`iptables` is a relatively low-level Linux firewall solution and command-line utility that uses Netfilter chains to control network traffic. `iptables` operates with `iptables` also uses `iptables` defines the following tables:
* `filter`: The default table, which is used when we’re deciding if packets should be allowed to traverse specific chains (`INPUT`, `FORWARD,` `OUTPUT`).
* `nat`: Used with packets that require a source or destination address/port translation. The table operates on the following chains: `PREROUTING`, `INPUT`, `OUTPUT`, and `POSTROUTING`.
* `mangle`: Used with specialized packet alterations involving IP headers, such as `PREROUTING`, `INPUT`, `FORWARD`, `OUTPUT`, and `POSTROUTING`.
* `raw`: Used when we’re disabling connection tracking (`NOTRACK`) on specific packets, mainly for stateless processing and performance optimization purposes. The table relates to the `PREROUTING` and `OUTPUT` chains.
* `security`: Used for MAC when packets are subject to SELinux policy constraints. The table interacts with the `INPUT`, `FORWARD`, and `OUTPUT` chains.
The following diagram summarizes the tables with the corresponding chains supported in `iptables`:

Figure 9.41 – Tables and chains in iptables
The chain traversal order of the packets in the kernel’s networking stack is as follows:
* Incoming packets with localhost destination: `PREROUTING` | `INPUT`
* Incoming packets with remote host destination: `PREROUTING` | `FORWARD` | `POSTROUTING`
* Locally generated packets (by application processes): `OUTPUT` | `POSTROUTING`
Now that we’re familiar with some introductory concepts, we can tackle a few practical examples to understand how `iptables` works.
The following examples use a Fedora 37 system, but they should work on every major Linux distribution. Please note that starting with RHEL 7, the default firewall management application is `firewalld` (discussed in the *Using firewall managers* section later in this chapter). If you want to use `iptables`, first, you need to disable `firewalld`:
sudo systemctl stop firewalld
sudo systemctl disable firewalld
sudo systemctl mask firewalld
Next, install the `iptables-services` package (on Fedora):
sudo dnf install iptables-services
The output of the preceding commands is shown here:

Figure 9.42 – Disabling firewalld and installing iptables on Fedora
Important note
On Ubuntu, you must install `iptables` using `sudo apt` `install iptables`.
Now, let’s start configuring `iptables`.
Configuring iptables
The `iptables` command requires superuser privileges. First, let’s check the current `iptables` configuration. The general syntax for retrieving the rules in a chain for a specific table is as follows:
sudo iptables -L [CHAIN] [-t TABLE]
The `-L` (`--list`) option lists the rules in a *chain*. The `-t` (`--table`) option specifies a table. The `CHAIN` and `TABLE` parameters are optional. If the `CHAIN` option is omitted, *all* chains and their related rules are considered within a table. When no `TABLE` option is specified, the `filter` table is assumed. Thus, the following command lists all the chains and rules for the `filter` table:
sudo iptables -L
On a system with a default firewall configuration, the output is as follows:

Figure 9.43 – Listing the current configuration in iptables
We can be more specific—for example, by listing all the `INPUT` chain rules for the `nat` table with the following command:
sudo iptables -L INPUT -t nat
The `-t` (`--table`) option parameter is only required when `iptables` operations target something other than the default `filter` table.
Important note
Unless the `-t` (`--table`) option parameter is specified, `iptables` assumes the `filter` table by default.
When you’re designing firewall rules from a clean slate, the following steps are generally recommended:
1. Flush any remnants in the current firewall configuration.
2. Set up a default firewall policy.
3. Create firewall rules, making sure the more specific (or restrictive) rules are placed first.
4. Save the configuration.
Let’s briefly look at each of the preceding steps by creating a sample firewall configuration using the `filter` table:
1. `filter` table’s chains (`INPUT`, `FORWARD`, and `OUTPUT`):
```
sudo iptables -F INPUT
sudo iptables -F FORWARD
启用 -v (--verbose) 选项的 iptables 命令。输出如下:
```

Figure 9.44 – Flushing existing configuration in iptables
Important note
The flushing operation will delete all the rules in a specific chain. Please take into consideration that this kind of operation will disable your firewall. This can also lock you out of an SSH connection if you are using one, thus be careful when using the flushing operation.
1. `iptables` allows all packets to pass through the networking (firewall) chain. A secure firewall configuration should use `DROP` as the default target for the relevant chains:
```
sudo iptables -P INPUT DROP
sudo iptables -P FORWARD DROP
-P (--policy) 选项参数将特定链(例如 INPUT)的策略设置为给定目标(例如 DROP)。DROP 目标使系统优雅地忽略所有数据包。此时,如果我们保存我们的防火墙配置,系统将不接受任何传入或传出的数据包。因此,我们应小心使用 SSH 或在没有直接控制台访问的情况下,避免无意中断开对系统的访问。
```
2. `192.168.0.0/24`):
```
sudo iptables -A INPUT -p tcp --dport 22 -m state \
--state NEW,ESTABLISHED -s 192.168.0.0/24 -j ACCEPT
sudo iptables -A OUTPUT -p tcp --sport 22 -m state \
-A INPUT:指定要追加规则的链(例如 INPUT)
```
3. `-p tcp`: The networking protocol (for example, TCP or UDP) transporting the packets4. `--dport 22`: The destination port of the packets5. `--sport 22`: The source port of the packets6. `-m state`: The packet property we want to match (for example, `state`)7. `--state NEW,ESTABLISHED`: The state(s) of the packet to match8. `-s 192.168.0.0/24`: The source IP address/mask originating the packets9. `-j ACCEPT`: The target or what to do with the packets (such as `ACCEPT`, `DROP`, `REJECT`, and so on)
We used two commands to enable SSH access. The first allows incoming SSH traffic (`--dport 22`) for new and existing connections (`-m state --state NEW,ESTABLISHED`). The second command enables SSH response traffic (`--sport 22`) for existing connections (`-m state –``state ESTABLISHED`).
Similarly, the following commands enable HTTPS traffic:
sudo iptables -A INPUT -p tcp --dport 443 -m state \
--state NEW,ESTABLISHED -j ACCEPT
sudo iptables -A OUTPUT -p tcp --sport 443 -m state \
--state ESTABLISHED,RELATED -j ACCEPT
To enable DNS traffic, we need to use the following commands:
sudo iptables -A INPUT -p udp --dport 53 -j ACCEPT
iptables 选项参数,请参考以下系统参考手册:* iptables (man iptables)* iptables-extensions (man iptables-extensions) iptables 配置,我们必须运行以下命令:
sudo service iptables save
输出如下:

Figure 9.45 – Saving the iptables configuration
We can also dump the current configuration to a file (such as `iptables.config`) for later use with the following command:
-f (--file) 选项参数指定保存(备份)iptables 配置的文件。我们可以稍后使用以下命令恢复已保存的 iptables 配置:
iptables backup configuration file.
Exploring more complex rules and topics with `iptables` is beyond the scope of this chapter. The examples we’ve presented so far, accompanied by the theoretical introduction of `iptables`, should be a good start for everyone to explore more advanced configurations. Also, tools such as newly emerging `nftables` are getting a lot of traction in some of the more recent versions of Linux distributions, and firewall management tools such as `ufw` and `firewalld` are used out of the box in distros such as Fedora, RHEL, openSUSE or Ubuntu.
Next, we’ll look at `nftables`, a relatively new framework that was designed and developed by the *Netfilter project*, built to eventually replace `iptables`.
Introducing nftables
`nftables` is a successor of `iptables`. `nftables` is a firewall management framework that supports packet filtering, NAT, and various packet shaping operations. `nftables` offers notable improvements in terms of features, convenience, and performance over previous packet filtering tools, such as the following:
* Lookup tables instead of linear processing of rules
* Rules are applied individually instead of processing a complete ruleset
* A unified framework for IPv4 and IPv6 protocols
* No protocol-specific extensions
The functional principles behind `nftables` generally follow the design patterns presented in earlier sections about firewall networking chains—that is, Netfilter and `iptables`. Just as with `iptables`, `nftables` uses tables to store chains. Each chain contains a set of rules for packet filtering actions.
`nftables` is the default packet filtering framework in Debian and RHEL/Fedora Linux distributions, replacing the old `iptables` (and related) tools. The command-line interface for manipulating the `nftables` configuration is `nft`. Yet some users prefer to use a more user-friendly frontend instead, such as `firewalld` (which recently added backend support for `nftables`). RHEL/Fedora, for example, uses `firewalld` as its default firewall management solution.
In this section, we’ll show a few examples of how to use `nftables` and the related command-line utilities to perform simple firewall configuration tasks. For this purpose, we’ll take an RHEL/Fedora distribution where we’ll disable `firewalld`. Let’s have a quick look at the preparatory steps required to run the examples in this section.
Prerequisites for our examples
If you have an RHEL 7 system, `nftables` is not installed by default. You can install it with the following command:
sudo yum install -y nftables
The examples in this section use a Fedora 37 distribution. To directly configure `nftables`, we need to disable `firewalld` and potentially `iptables` (if you ran the examples in the *Working with iptables* section). The steps for disabling `firewalld` were shown at the beginning of the *Configuring* *iptables* section.
Also, if you have `iptables` enabled, you need to stop and disable the related service with the following commands:
sudo systemctl stop iptables
sudo systemctl disable iptables
Next, we need to enable and start `nftables`:
sudo systemctl enable nftables
sudo systemctl start nftables
We can check the status of `nftables` with the following command:
sudo systemctl status nftables
The running status of `nftables` should be `active`, as seen here:

Figure 9.46 – Checking the status of nftables
At this point, we are ready to configure `nftables`. Let’s work with a few examples in the next section.
Working with nftables
`ntftables` loads its configuration from `/etc/sysconfig/nftables.conf`. We can display the content of the configuration file with the following command:
sudo cat /etc/sysconfig/nftables.conf
A default `nftables` configuration has no active entries in `nftables.conf`, except for a few comments:

Figure 9.47 – The default nftables configuration file
As the comments suggest, we have a few options for changing the `nftables` configuration:
* Directly edit the `nftables.conf` file
* Manually edit the `/etc/nftables/main.nft` configuration file, then uncomment the related line in `nftables.conf`
* Use the `nft` command-line utility to edit the rules and then dump the current configuration into `nftables.conf`
Regardless of the approach taken, we need to reload the updated configuration by restarting the `nftables` service. In this section, we’ll use `nft` command-line examples to change the `nftables` configuration. Power users usually write `nft` configuration scripts, but it’s best to learn the basic steps first.
The following command displays all the rules in the current configuration:
sudo nft list ruleset
Your system may already have some default rules set up. You may choose to do a backup of the related configuration (for example, `/etc/sysconfig/nftables.conf` and `/etc/nftables/main.nft`) before proceeding with the next steps.
The following command will flush any preexisting rules:
sudo nft flush ruleset
At this point, we have an empty configuration. Let’s design a simple firewall that accepts SSH, HTTP, and HTTPS traffic, blocking anything else.
Accepting SSH, HTTP, and HTTPS traffic
First, we need to create a table and a chain. The following command creates a table named `packt_table`:
sudo nft add table inet packt_chain within packt_table:
sudo nft add chain inet packt_table packt_chain. Allow SSH, HTTP, and HTTPS access with the following rule:
sudo nft add rule inet packt_table packt_chain tcp dport {ping}:
sudo nft add rule inet packt_table packt_chain ip protocol icmp accept
最后,我们将拒绝其他所有流量:
sudo nft add rule inet packt_table packt_chain reject with icmp type port-unreachable
现在,让我们看看我们的新配置:
sudo nft list ruleset
输出如下:

图 9.48 – 使用 nftables 的简单防火墙配置
输出建议我们为输入链(packt_chain)设置以下配置:
-
允许目标端口
22、80和443上的 TCP 流量(tcp dport { 22, 80, 443 }accept) -
允许
ping请求(ip protocolicmp accept) -
拒绝其他所有流量(
meta nfprotoipv4 reject)
接下来,我们将把当前配置保存到/etc/nftables/packt.nft:
sudo nft list ruleset | sudo tee /etc/nftables/packt.nft
最后,我们将通过添加以下行,将当前的nftables配置指向/etc/nftables/packt.nft,在/etc/sysconfig/nftables.conf文件中:
include "/etc/nftables/packt.nft"
我们将使用vim(或你选择的编辑器)进行此更改:
sudo vim /etc/sysconfig/nftables.conf
新的nftables.conf文件现在包含对我们的packt.nft配置文件的引用:

图 9.49 – 在 nftables.conf 中包含新的配置
以下命令重新加载新的nftables配置:
sudo systemctl restart nftables
完成此练习后,您可以快速编写一个脚本,使用nft list ruleset命令的输出配置nftables。事实上,我们刚刚用/etc/nftables/packt.nft配置文件做了这件事。
至此,我们将结束对数据包过滤框架及相关命令行工具的讨论。它们使高级用户能够对底层网络链路和规则的每个功能方面进行精细控制。然而,一些 Linux 管理员可能会觉得使用这些工具过于复杂,而转向相对简单的防火墙管理工具。因此,接下来,我们将介绍几种本地的 Linux 防火墙管理工具,它们提供了更简化且用户友好的命令行界面,用于配置和管理防火墙。
使用防火墙管理工具
防火墙管理工具是命令行工具,具有相对易于使用的防火墙安全规则配置界面。通常,这些工具需要超级用户权限,它们对 Linux 系统管理员来说是非常有价值的工具。
在接下来的章节中,我们将介绍两个在现代 Linux 发行版中广泛使用的最常见的防火墙管理工具:
-
firewalld:在 RHEL/Fedora 平台上 -
ufw:在 Ubuntu/Debian 平台上
防火墙管理工具类似于其他网络安全工具(如iptables、Netfilter 和nftables),主要的区别在于它们为防火墙安全提供了更简化的用户体验。使用防火墙管理工具的一个重要好处是,操作各种安全配置更改时,无需重新启动网络守护进程。
让我们从 firewalld 开始,这是 RHEL/Fedora 的默认防火墙管理器。
使用 firewalld
firewalld 是多种 Linux 发行版的默认防火墙管理工具,包括以下发行版:
-
RHEL 7(及更新版本)
-
openSUSE 15(及更新版本)
-
Fedora 18(及更新版本)
在 RHEL 上,如果没有安装 firewalld,我们可以使用以下命令进行安装:
sudo yum install -y firewalld
您还可能需要使用以下命令在启动时启用 firewalld 守护进程:
sudo systemctl enable firewalld
如果您使用的是与执行先前示例时相同的系统(如 iptables 和 nftables),请记得我们在 Fedora 发行版中一开始需要禁用 firewalld。现在,是时候重新启用它了。我们将使用以下命令进行操作:

图 9.50 – 重新启用 firewalld
firewalld 提供了一组用于不同任务的命令行工具:
-
firewall-cmd:firewalld的主要命令行工具 -
firewall-offline-cmd:用于在firewalld离线(未运行)时进行配置 -
firewall-config:用于配置firewalld的图形用户界面工具 -
firewall-applet:一个系统托盘应用程序,用于提供有关firewalld的基本信息(如运行状态、连接等)
在本节中,我们将查看一些使用 firewall-cmd 工具的实际示例。对于其他工具,您可以参考相关的系统参考手册(如 man firewall-config)以获取更多信息。
firewalld(以及 firewalld-cmd,其实也是)通过一些关键概念来操作,这些概念与监控和控制网络数据包相关:区域、规则 和 目标。
理解 firewalld 区域
firewalld 配置。被 firewalld 监控的网络数据包,如果它与区域关联的网络接口或 IP 地址/子网掩码匹配,则属于该区域。以下命令列出了预定义区域的名称:
sudo firewall-cmd --get-zones
要获取当前已配置的所有区域的详细信息,我们可以运行以下命令:
sudo firewall-cmd --list-all-zones
下面是相关输出的摘录:

图 9.51 – 列出 firewalld 区域
上述输出展示了 Fedora 37 Server Edition 中的默认(活动)区域及其属性,接下来将对其中的一些进行解释。与接口和源相关的区域被称为 活动区域。以下命令用于检索活动区域:
sudo firewall-cmd --get-active-zones
在我们的例子中,输出结果如下:

图 9.52 – firewalld 活动区域
接口 代表附加到本地主机的网络适配器。活动接口被分配到默认区域或用户定义的区域。一个接口不能同时分配到多个区域。
来源是传入的 IP 地址或地址范围,也可以分配给区域。单个来源或多个重叠的 IP 地址范围不能分配到多个区域。如果这样做,将会导致未定义的行为,因为不清楚哪个规则在相关区域中具有优先权。
默认情况下,firewalld 将所有网络接口分配到 public 区域,而不将任何来源与其关联。此外,默认情况下,public 是唯一的活动区域,也是默认区域。以下命令显示默认区域:
sudo firewall-cmd --get-default-zone
默认输出如下:

图 9.53 – 显示 firewalld 中的默认区域
为区域指定来源是可选的。因此,对于每个数据包,将会有一个与之匹配的网络接口区域。然而,并不一定会有一个匹配来源的区域。这个范式将在匹配规则的评估顺序中起到重要作用。我们将在理解规则优先级部分讨论相关内容。但首先,让我们先了解一下 firewalld 规则。
理解 firewalld 规则
firewalld 配置表示控制与特定区域相关的数据包的配置设置。通常,规则会根据一些预定义的标准来决定是否接受或拒绝数据包。
例如,要阻止 FedoraServer 区域的 ping 请求(ICMP 协议),我们可以添加以下 rich-rule 属性:
sudo firewall-cmd --zone=FedoraServer --add-rich-rule='rule protocol value="icmp" reject'
我们可以使用以下命令检索 FedoraServer 区域信息:
sudo firewall-cmd --info-zone=public
rich-rule 属性反映了更新后的配置:

图 9.54 – 使用 firewalld 获取 FedoraServer 区域配置
此时,我们的主机将不再响应 ping(ICMP)请求。我们可以使用以下命令移除刚刚添加的 rich-rule 属性:
sudo firewall-cmd --zone=FedoraServer --remove-rich-rule='rule protocol value="icmp" reject'
或者,我们可以使用以下命令启用 ICMP 访问:
sudo firewall-cmd --zone=FedoraServer --add-rich-rule='rule protocol value="icmp" accept'
请注意,未使用 --permanent 选项的 firewall-cmd 工具所做的更改是临时性的,在系统或 firewalld 重启后不会保留。
当没有定义或匹配的 rich 规则时,firewalld 使用该区域的目标来控制数据包的行为。接下来,我们来看一下目标。
理解 firewalld 目标
当数据包匹配特定区域时,firewalld 会根据该区域的 rich 规则控制数据包的行为。如果没有定义 rich 规则,或者没有任何 rich 规则匹配数据包,则数据包的行为最终由与该区域关联的 目标 决定。可能的目标值如下:
-
ACCEPT:接受数据包 -
REJECT:拒绝数据包并返回拒绝回复 -
DROP:丢弃数据包,不进行回复 -
default:遵循firewalld的默认行为
区域、规则和目标是 firewalld 在分析和处理数据包时使用的关键配置元素。数据包通过区域进行匹配,然后通过规则或目标进行处理。由于区域的双重性质——基于网络接口和 IP 地址/范围来源——firewalld 在计算匹配标准时遵循特定的顺序(或优先级)。接下来我们将详细讨论这个问题。
理解规则优先级
让我们首先定义一些术语。我们将与接口相关的区域称为 接口区域。与源相关的区域称为 源区域。由于区域可以同时分配接口和源,因此一个区域可以充当接口区域、源区域或两者。
firewalld 按照以下顺序处理数据包:
-
它检查相应的源区域。最多只会有一个这样的区域(因为源只能与一个区域关联)。如果匹配,数据包会按照与该区域关联的规则或目标进行处理。否则,数据包分析会作为下一步进行。
-
它检查相应的接口区域。总是会有且仅有一个这样的区域。如果匹配,则数据包会按照该区域的规则或目标进行处理。否则,数据包验证会作为下一步进行。
假设 firewalld 的默认目标是——它接受 ICMP 数据包并拒绝其他所有数据包。从前面验证工作流的关键点是,源区域优先于接口区域。多区域 firewalld 配置的典型设计模式定义了以下区域:
-
特权源区域:来自特定 IP 地址的提升系统访问权限
-
限制性接口区域:仅限其他人访问
让我们探讨一些使用 firewall-cmd 工具的其他可能有用的示例。
以下命令显示防火墙中启用的服务:
sudo firewall-cmd --list-services
以下命令启用 HTTPS 访问(端口 443):
sudo firewall-cmd --zone=FedoraServer --add-service=https
要添加用户定义的服务或端口(例如,8443),可以运行以下命令:
sudo firewall-cmd --zone=FedoraServer --add-port=8443/tcp
以下命令列出防火墙中打开的端口:
sudo firewall-cmd --list-ports
如果在没有 --permanent 选项的情况下调用 firewall-cmd 命令,将会导致暂时性更改,这些更改在系统(或 firewalld)重启后不会保留。要重新加载之前保存的(永久)firewalld 配置,可以运行以下命令:
sudo firewall-cmd --reload
如需了解有关 firewalld 的更多信息,请参阅相关的系统参考(man firewalld)或 www.firewalld.org。
使用 ufw
ufw 是 Ubuntu 中的默认防火墙管理工具。ufw 为 iptables 和 Netfilter 提供了一个相对简单的管理框架,并且提供了一个易于使用的命令行接口来操作防火墙。
让我们看几个使用 ufw 的例子。请注意,ufw 命令行工具需要超级用户权限。以下命令报告 ufw 的状态:
sudo ufw status
默认情况下,ufw 是 inactive(禁用)的。我们可以使用以下命令启用 ufw:
sudo ufw enable
启用防火墙或执行可能影响您访问系统的更改时,请始终小心。默认情况下,启用时,ufw 将阻止所有传入访问,除非是 ping(ICMP)请求。如果您通过 SSH 登录,您可能会收到提示,警告您在启用 ufw 时可能会断开 SSH 连接。为了安全起见,您可能希望按 n(否)取消之前的操作,并首先在防火墙中启用 SSH 访问:
sudo ufw allow ssh
如果已经启用了 SSH 访问,输出会显示相关的安全规则不会被添加。如果没有,规则将被添加。
此时,您可以安全启用 ufw,无需担心当前或现有的 SSH 连接会中断。在启用 ufw 后,我们会看到与之前相同的提示,但这次我们可以按 y(是)继续。
要查看防火墙的详细状态,您可以运行以下命令:
sudo ufw status verbose
运行所有这些命令的输出如下面的截图所示:

图 9.55 – 启用 ufw,允许 ssh,并显示 ufw 的详细状态
始终建议检查防火墙设置,以确保不会意外允许对系统的访问。
我们可以使用以下命令列出当前的应用程序安全配置文件:
sudo ufw app list
在我们的案例中,只有 OpenSSH 可用,它是在我们之前允许 SSH 连接时激活的。
让我们添加其他服务,如 Apache 和 nginx 使用的 HTTP(端口 80)和 HTTPS(端口 443)。这可以通过几种不同的方式实现。我们可以使用端口号(80,443),也可以使用服务名称作为替代方法(http,https),或者可以直接指定 Web 服务器名称(Apache Full,Nginx Full)。详细信息请参见图 9.56。
要移除特定服务的访问(例如 HTTP),我们可以运行以下命令:
sudo ufw deny http
输出显示已添加新规则。随后的详细状态检查将显示对端口 80/tcp 的访问已被拒绝。然而,结果状态有些复杂。在下面的截图中,我们可以看到添加和删除之前讨论的命令的输出:

图 9.56 – 在 ufw 中添加和拒绝规则
为了按照正确的顺序恢复规则,首先让我们获取规则列表的编号输出:
sudo ufw status numbered
输出产生了以下结果:

图 9.57 – ufw 中的规则编号列表
规则的顺序由序号建议。始终将更具体(限制性)的规则放在前面。在添加或更改规则时,您可能需要删除旧条目或重新排列其顺序,以确保规则被正确放置和评估。
或者,我们可以使用 insert 选项在特定位置添加规则。例如,以下命令将 80/tcp DENY 规则放在第二个位置:
sudo ufw insert 2 deny http
让我们再看几个使用 ufw 的例子。以下命令为来自特定源地址范围(192.168.0.0/24)的所有协议(any)启用 SSH 访问(端口 22):
sudo ufw allow from 192.168.0.0/24 to any port 22
以下命令启用 ufw 日志记录:
sudo ufw logging on
相应的日志踪迹通常位于 /var/log/syslog:
grep -i ufw /var/log/syslog
要禁用 ufw 日志记录,请运行以下命令:
sudo ufw logging off
以下命令将 ufw 恢复为系统的默认设置:
sudo ufw reset
上述命令会移除所有规则并禁用 ufw。
如果想了解更多关于 ufw 的信息,您可以访问 UFW Community Help 维基页面 help.ubuntu.com/community/UFW 或相关的系统参考(man ufw)。
与低级别的包过滤工具(例如 Netfilter、iptables 和 nftables)相比,像 ufw 和 firewalld 这样的防火墙管理工具可能对某些 Linux 管理员更具吸引力。选择一个工具而非另一个的理由,除了平台的考虑外,还与脚本和自动化能力相关。一些高级用户可能会认为 nft 命令行工具是设计防火墙规则的首选工具,因为 nftables 提供了更精细的控制。其他用户则可能倾向于使用 iptables,尤其是在较旧的遗留平台上。最终,这是个人选择或偏好的问题,因为所有这些工具都能大致相同程度地配置和管理防火墙。
让我们以一些最后的考虑来结束本章内容。
总结
本章内容相对较多,可能会让人感到有些压倒性。一个关键的收获应该是集中关注框架(模块)。如果我们讨论的是防火墙,应该关注诸如 iptables、Netfilter 和 nftables 等包过滤框架。对于访问控制,我们有 SELinux 和 AppArmor 等安全模块。我们已经讨论了它们的一些优缺点。可能决定 Linux 发行版选择的关键点是 AppArmor 与 SELinux 的选择。一个可能比另一个更快速,而相关的管理工作量则取决于具体情况。例如,选择 AppArmor 会将主要的 Linux 发行版缩小到 Ubuntu、Debian 和 openSUSE。发行版的选择反过来会影响可用的防火墙管理解决方案,等等。
掌握应用安全框架和防火墙管理工具将帮助您以最小的努力保护系统安全。就像任何典型的 Linux 系统管理任务一样,确保系统安全有很多种方法。我们希望您能够在本章所介绍的探索性知识和工具基础上,做出平衡的决策,以确保系统安全。
下一章将通过介绍灾难恢复(DR)、诊断和故障排除实践,为您的系统提供更高的安全性和保护。
练习
这里有一个关于本章所涵盖的一些基本概念的小测试:
-
列举出至少几个在 Linux 中使用的 ACM(访问控制模块)。
提示:DAC、ACL、MAC、RBAC、MLS、MCS
-
列举 SELinux 安全上下文的字段。
提示:用户、角色、类型、级别
-
SELinux 中的域是什么?
提示:分配给进程的类型
-
你能想到 SELinux 和 AppArmor 在执行安全策略方面的显著区别吗?
提示:SELinux 使用基于文件标签的策略,而 AppArmor 使用基于路径的安全策略。
-
我们如何在
enforce和complain模式之间切换 AppArmor 应用程序配置文件?aa-enforce和aa-complain -
你能想到 Linux 内核网络栈中的多少个链?
提示:图 9.41 可能对你有所帮助。
-
RHEL/Fedora 的默认防火墙管理解决方案是什么?Ubuntu 又是如何处理的?
firewalld(Fedora)和ufw(Ubuntu)
进一步阅读
请参考以下 Packt 出版的书籍,了解本章所涵盖主题的更多信息:
-
《精通 Linux 安全与加固 – 第二版》 由 Donald A. Tevault 编写,Packt 出版
-
《实用 Linux 安全 Cookbook – 第二版》 由 Tajinder Kalsi 编写,Packt 出版
第十章:灾难恢复、诊断和故障排除
在本章中,您将学习如何在灾难恢复场景中进行系统备份和恢复,以及如何诊断和排除故障常见的一系列问题。这些是每个 Linux 系统管理员都需要掌握的技能,只有掌握了这些技能,他们才能为停电、盗窃或硬件故障等最坏情况做好准备。全球 IT 的支柱运行在 Linux 上,我们需要为生活中任何突发事件做好准备。
在本章中,我们将涵盖以下主要主题:
-
灾难恢复规划
-
系统备份与恢复
-
介绍常用的 Linux 诊断工具来进行故障排除
技术要求
本章不需要任何特殊的技术要求,只需系统中有 Linux 操作系统的正常安装,或者在本地网络中有两台不同的工作系统来进行一些示例。Ubuntu 和 Fedora 都非常适合本章的练习,但在本章中,我们将使用 Ubuntu 22.04.2 LTS 服务器版和桌面版。
灾难恢复规划
管理风险是每个企业或个人的重要资产。对于参与系统管理的每个人来说,这项责任是巨大的。对于所有企业而言,风险管理应成为更广泛的风险管理策略的一部分。IT 领域中存在各种风险,从直接影响数据中心或企业位置的自然灾害,到网络安全威胁。过去十年,IT 在公司内部的影响力呈指数级增长。如今,没有任何活动不涉及某种形式的 IT 操作,无论是在小型企业、大型公司、政府机构,还是健康和教育公共部门,仅举几例。每项活动都有其独特性,因此需要进行特定类型的评估。不幸的是,在信息安全领域,风险管理已发展成一种“放之四海而皆准”的实践,依赖于 IT 管理实施的检查清单。在我们开始学习如何评估风险之前,先简要介绍一下风险管理。
风险管理简介
什么是风险管理?简而言之,它包括一系列具体操作,旨在减轻任何可能影响企业整体持续性的威胁。风险管理过程对每个 IT 部门来说至关重要。
风险管理框架最初在美国出现,源于联邦信息系统现代化法案(FISMA)法律,该法律于 2002 年开始实施。此时,美国国家标准与技术研究院(NIST)开始为所有美国政府机构创建新的网络安全评估标准和方法。因此,安全认证和合规性对每一个在企业和政府领域自视为有竞争力的 Linux 发行版提供商至关重要。与之前讨论的美国认证机构类似,英国和俄罗斯也有其他机构开发特定的安全认证。在这方面,所有主要的 Linux 发行版,如 Red Hat、SUSE 和 Canonical,都获得了 NIST、英国国家网络安全中心(NCSC)或俄罗斯技术与出口控制联邦服务(FSTEC)的认证。
根据 NIST SP 800-37r2 的风险管理框架(详见官方 NIST 网站:csrc.nist.gov/publications/detail/sp/800-37/rev-2/final),有七个步骤,从为框架的执行做准备开始,到每天监控组织系统。我们将不会详细讨论这些步骤,而是在本章结束时提供 NIST 官方文档的链接。简而言之,风险管理框架包括多个重要的分支,如下所示:
-
库存:对所有本地可用系统进行全面清单梳理,并列出所有软件解决方案
-
系统分类:评估每种数据类型在可用性、完整性和保密性方面的影响级别
-
安全控制:涉及数百个计算机系统安全的详细程序——NIST 安全控制汇编可以在 SP800-53r4 中找到(以下是官方 NIST 网站链接:
csrc.nist.gov/publications/detail/sp/800-53/rev-4/final) -
风险评估:一系列步骤,包括威胁源识别、漏洞识别、影响评估、信息共享、风险监控和定期更新
-
系统安全计划:基于每项安全控制的报告,评估未来行动,包括实施及其有效性
-
认证、批准、评估和授权:审查安全评估并突出安全问题及有效解决方案的过程,这些内容将在未来的行动计划中详细说明
-
行动计划:用于跟踪安全弱点并应用正确响应程序的工具
信息技术领域存在许多类型的风险,包括硬件故障、软件错误、垃圾邮件和病毒、人为错误,以及自然灾害(火灾、洪水、地震、飓风等)。还有一些更具犯罪性质的风险,包括安全漏洞、员工不诚实、企业间谍活动或任何可以被视为网络犯罪的行为。这些风险可以通过实施适当的风险管理策略来解决。作为基础,这样的策略应该包括五个明确的步骤:
-
识别风险:识别可能影响你正在进行的 IT 操作的威胁和漏洞。
-
分析风险:根据深入研究判断风险的大小。
-
评估风险:评估风险可能对你的运营产生的影响;立即采取行动,根据风险的影响做出响应。这要求在你运营的各个层级采取实际行动。
-
应对风险:启动你的灾难恢复计划(DRPs),结合预防和缓解策略。
-
监控与审查风险:触发一个严密的监控与审查策略,将确保所有 IT 团队知道如何应对风险,并且具备隔离风险的工具和能力,保障公司基础设施的安全。
风险评估对于任何企业都极其重要,IT 管理层应当非常重视。现在我们已经探讨了一些风险管理的概念,接下来该解释一下它到底是什么。
风险计算
风险评估,也称为风险计算或风险分析,指的是寻找并计算可能威胁和漏洞的解决方案。以下是你在讨论风险影响时应了解的一些基本术语:
-
年损失预期(ALE)定义了 1 年内预计的损失。
-
单次损失预期(SLE)表示在任何给定时刻预期的损失金额。
-
年发生率(ARO)是指某个风险事件在 1 年内发生的可能性。
-
风险计算公式是 SLE x ARO = ALE。公式中的每个元素都有一个货币值,因此最终结果也以货币值表示。这个公式是非常有用的。
-
平均故障间隔时间(MTBF)用于衡量预期的可修复故障之间的时间间隔。
-
平均故障时间(MTTF)是指系统在发生无法修复的故障之前能够运行的平均时间。
-
平均修复时间(MTTR)衡量修复受影响系统所需的时间。
-
恢复时间目标(RTO)表示为停机分配的最大时间。
-
恢复点目标(RPO)定义了系统需要恢复的时间。
了解这些术语将帮助你理解风险评估,以便在需要时能够进行有据可依的评估。风险评估基于两种主要的行动类型(或更准确地说,是战略):
-
主动措施:
-
风险回避:基于风险识别并找到快速解决方案以避免其发生。
-
风险缓解:基于采取措施减少可能风险发生的概率。
-
风险转移:将风险的可能结果转移给外部实体。
-
风险威慑:基于特定的系统和政策,应该使任何攻击者不敢利用该系统。
-
-
非主动措施:
- 风险接受:如果其他主动措施可能超过由风险带来的损害成本,则接受该风险。
这里描述的策略可以应用于与传统本地计算相关的风险,但如今,云计算正在缓慢而稳步地接管世界。那么,这些风险策略如何应用于云计算呢?在云计算中,你使用第三方的基础设施,但使用的是你自己的数据。尽管我们将在第十四章中开始讨论云中的 Linux,计算简介,但有些概念我们现在就可以介绍。如前所述,云计算将基础设施操作从本地环境转移到更大的参与者,如 Amazon、Microsoft 或 Google。这通常可以视为外包。这意味着,当你在本地运行服务时,一些曾是威胁的风险现在已经转移到了第三方。
现在有三个主要的云计算范式已经成为技术媒体的流行词:
-
软件即服务(SaaS):这是一种为寻求减少 IT 成本并依赖软件订阅的公司提供的软件解决方案。一些 SaaS 解决方案的例子包括Slack、Microsoft 365、Google Apps、Dropbox等。
-
平台即服务(PaaS):通过使用他人的基础设施、运行时和依赖项向客户提供软件应用程序的方式也被称为应用平台。这可以是在公共云、私有云或混合解决方案上。一些 PaaS 的例子包括Microsoft Azure、AWS Lambda、Google App Engine、SAP Cloud Platform、Heroku和Red Hat OpenShift。
-
基础设施即服务(IaaS):这些是在线运行并提供高级应用程序编程接口(APIs)的服务。一个著名的例子是OpenStack。
关于所有这些技术的详细信息将提供在第十四章,《计算简短介绍》中,但为了本章的目的,我们已经提供了足够的信息。关于云计算的主要风险与数据集成和兼容性相关。这些是你必须克服的风险,因为其他大多数风险已经不再是你的关注点,因为它们已经转移给了管理基础设施的第三方。
风险计算可以通过不同的方式进行管理,取决于公司使用的 IT 场景。当你使用本地部署场景并管理所有组件时,风险评估变得相当具有挑战性。当你使用 IaaS、PaaS 和 SaaS 场景时,风险评估变得较为轻松,因为责任逐步转移到外部实体。
风险评估应该始终被任何关心其网络和系统安全的个人以及任何 IT 经理认真对待。这时,灾难恢复计划(DRP)便开始发挥作用。一个好的 DRP 和策略的基础是进行有效的风险评估。
设计灾难恢复计划(DRP)
灾难恢复计划(DRP)围绕在事件发生时应采取的步骤进行结构化。在大多数情况下,DRP 是业务连续性计划的一部分。它决定了公司如何在一个正常运作的基础设施下继续运营。
每个灾难恢复计划(DRP)需要从准确的硬件清单开始,然后是软件应用清单和单独的数据清单。其中最重要的是设计一个策略,备份所有使用的信息。
在使用的硬件方面,必须有明确的标准化硬件政策。这将确保故障硬件能够轻松更换。这样的政策确保了所有设备的正常运行并进行了优化。标准化硬件肯定有良好的驱动程序支持,这在 Linux 世界中非常重要。然而,使用标准化硬件会极大地限制自带设备(BYOD)等做法,因为员工只需使用雇主提供的硬件。使用标准化硬件意味着使用由公司 IT 部门设置和配置的特定软件应用,用户的输入受到限制。
IT 部门的责任重大,它在设计IT 恢复策略作为灾难恢复计划(DRP)的一部分时,起着重要作用。停机时间和数据丢失的关键容忍度应该根据最小可接受的 RPO 和 RTO 来定义。
确定角色,即谁负责什么,是制定良好 DRP 的另一个关键步骤。这样,实施计划的响应时间将大大缩短,每个人都会知道在出现风险时自己的责任。在这种情况下,拥有良好的沟通策略至关重要。为每个组织层级强制执行清晰的流程,将提供清晰的沟通、集中的决策和备份人员的继任计划。
DRP(灾难恢复计划)需要至少每年彻底测试两次,以证明其效率。计划外的停机和故障可能会对企业造成负面影响,无论是在本地环境还是在任何多云环境中。为最坏的情况做好准备非常重要。因此,在接下来的章节中,我们将向您展示一些最好的工具和实践,用于备份和恢复 Linux 系统。
备份和恢复系统
灾难可能随时发生,风险无处不在。在这方面,备份系统至关重要,并且需要定期进行。通常,预防比从数据丢失中恢复更为有效,而从中吸取教训的代价往往是沉重的。
备份和恢复需要基于深思熟虑的策略,并且需要考虑 RTO 和 RPO 因素。RTO 应该回答一些基本问题,比如恢复丢失数据的速度以及这将如何影响业务运营,而 RPO 应该回答像是您能承受丢失多少数据的问题。
备份有不同的类型和方法。以下是一些例子:

图 10.1 – 备份方法和类型
进行备份时,请记住以下规则:
-
321 规则意味着您应始终拥有至少三份数据副本,其中两份保存在不同介质上并存放在不同地点,而一份备份始终保存在异地(不同地理位置)。这也被称为三分法则;它可以适应任何情况,例如 312、322、311 或 323。
-
备份检查非常重要,但通常被忽视。它检查数据的完整性和有效性。
-
清晰且有文档记录的备份策略和流程对所有使用相同实践的 IT 团队成员都很有帮助。
在下一部分,我们将介绍一些知名的全 Linux 系统备份工具,从操作系统内部集成的工具到同样适用于家庭和企业使用的第三方解决方案。
磁盘克隆解决方案
一个好的备份选项是克隆整个硬盘或包含敏感数据的多个分区。Linux 提供了许多多功能的工具来完成此任务。其中包括dd命令、ddrescue命令和Relax-and-Recover(ReaR)软件工具。接下来的章节中我们将详细介绍这些工具。
dd 命令
最著名的磁盘备份命令之一是 dd 命令。我们在第六章,磁盘与文件系统操作中讨论过此命令。让我们回顾一下它在备份和恢复场景中的使用方式。dd 命令用于按块复制数据,不管文件系统类型如何,从源设备到目标设备。
让我们学习如何克隆整个磁盘。我们系统上有一台虚拟机,配有一个 20 GB 的硬盘,我们想将其备份到一个 128 GB 的 USB 闪存驱动器上。我们将向您展示的操作同样适用于裸机环境。
首先,我们将运行 sudo fdisk -l 命令,以验证磁盘大小是否正确。输出将显示有关本地驱动器和 USB 闪存驱动器等信息,具体取决于您的系统。
现在我们已经知道了磁盘的大小,并且源磁盘能够适配目标磁盘,我们将继续克隆整个虚拟磁盘。我们将源磁盘 /dev/vda 克隆到目标磁盘 /dev/sda(该操作可能需要一些时间):

图 10.2 – 使用 dd 克隆整个硬盘
前面命令中显示的选项如下:
-
if=/dev/vda代表输入文件,在我们的例子中是源硬盘 -
of=/dev/sda代表输出文件,即目标 USB 驱动器 -
conv=noerror代表允许命令在出现错误时继续执行的指令 -
sync代表一个指令,它将输入错误的块填充为零,以便数据偏移始终保持同步 -
status=progress显示关于传输过程的统计信息
请记住,这个操作可能需要一段时间才能完成。在我们的系统上,它花费了 200 分钟才完成。我们在操作开始时拍摄了前面的截图。接下来的部分,我们将向您展示如何使用 ddrescue。
ddrescue 命令
ddrescue 命令是另一个可以用来克隆磁盘的工具。此工具从一个设备或文件复制到另一个设备或文件,第一次会尽量只复制良好健康的部分。如果您的磁盘出现故障,您可能需要使用 ddrescue 两次,因为第一次它只会复制好的扇区,并将错误映射到目标文件。第二次,它只会复制坏的扇区,因此最好添加多个读取尝试的选项,以确保操作的准确性。
在 Ubuntu 中,ddrescue 工具默认并未安装。要安装它,请使用以下 apt 命令:
sudo apt install gddrescue
我们将使用与之前相同的系统,克隆相同的硬盘。命令如下:
sudo ddrescue -n /dev/vda /dev/sda rescue.map --force
输出结果如下:

图 10.3 – 使用 ddrescue 克隆硬盘
我们使用了 ddrescue 命令并加上 --force 选项,确保目标上的所有内容都会被覆盖。这个操作也非常耗时,所以请准备好等待较长时间。在我们的情况下,完成这一步几乎花了 1 小时。接下来,我们将向你展示如何使用另一个有用的工具——ReaR 实用工具。
使用 ReaR
ReaR 是一个强大的灾难恢复和系统迁移工具,使用 Bash 编写。它被像 RHEL 和 SLES 这样的企业级发行版使用,也可以安装在 Ubuntu 上。它的设计目标是易于使用和设置。它与本地引导加载程序集成,支持 cron 调度程序,或像Nagios这样的监控工具。有关此工具的更多细节,请访问官方网站:relax-and-recover.org/about/。
在 Ubuntu 上安装它,使用以下命令:
sudo apt install rear
安装完软件包后,你需要知道主配置文件的位置,即 /etc/rear/local.conf,所有的配置选项都应写在其中。ReaR 默认生成 ISO 文件,但它也支持将 Samba(CIFS)、USB 和 NFS 作为备份目标。接下来,我们将展示如何使用 ReaR 备份到本地 NFS 服务器。
使用 ReaR 备份到本地 NFS 服务器
举个例子,我们将向你展示如何备份到 NFS 服务器。根据技术要求部分,你需要至少在网络上有两台系统可用:一台机器上设置了 NFS 服务器(作为备份服务器),另一台作为需要备份的生产机器。ReaR 应该安装在这两台机器上。请执行以下步骤:
-
首先,我们必须根据需要配置 NFS 服务器(相关操作在 第十三章,配置 Linux 服务器中有介绍)。关于如何设置 NFS 服务器的详细信息,请参考 第十三章,这里我们仅做简要说明。NFS 的配置文件是
/etc/exports,它存储了共享位置的信息。在添加任何新的 ReaR 备份共享位置之前,先添加一个新目录。我们将使用/home/export/目录作为 NFS 设置的目录。在这个目录内,我们将为 ReaR 备份创建一个新的目录。创建新目录的命令如下:root, ReaR will not have permission to write the backup to this location. Change the ownership using the following command:使用你喜欢的编辑器打开
/etc/exports文件,并为备份目录添加新行。该行应该类似如下:/home/export/rear 192.168.124.0/24(rw,sync,no_subtree_check)使用你本地网络的 IP 范围,而不是我们使用的那个。
-
一旦新增了新行,重启 NFS 服务并使用
-``s选项运行exportfs命令:/etc/rear/local.conf file and add the lines shown in the following output. Use your own system’s IP address, not the one we used. The code should look like the following:OUTPUT=ISO
OUTPUT_URL=nfs://192.168.124.112/home/export/rear
BACKUP=NETFS
OUTPUT:可引导镜像类型,在我们这里是 ISO
-
OUTPUT_URL:备份目标,可以是 NFS、CIFS、FTP、RSYNC 或文件 -
BACKUP:所使用的备份方法,在我们的案例中是NETFS,这是 ReaR 的默认方法 -
BACKUP_URL:备份目标的位置 -
现在,使用
-v和-d选项运行mkbackup命令:sudo rear -v -d mkbackup输出将很大,因此我们在这里不展示它。该命令将需要较长时间才能完成。一旦完成,你可以检查 NFS 目录以查看其输出。备份应该就在其中。
在 NFS 服务器上写入了多个文件。其中名为 rear-neptune.iso 的文件是实际的备份文件,将在需要恢复系统时使用。另外,还有一个名为 backup.tar.gz 的文件,包含了我们本地机器上的所有文件。
重要说明
ReaR 的命名约定如下。名称将由术语 rear- 开头,后接系统的主机名以及 .iso 扩展名。我们的系统主机名是 neptune,因此备份文件在我们的情况下被命名为 rear-neptune.iso。
一旦备份已写入 NFS 服务器,你将能够使用 USB 磁盘或带有 ISO 镜像的 DVD 来恢复系统,这个 ISO 镜像已写入 NFS 服务器。
使用 ReaR 进行备份到 USB
还有一个直接备份到 USB 磁盘的选项。以下是需要遵循的步骤:
-
将磁盘插入 USB 端口并使用以下命令格式化它:
sudo rear format /dev/sda该命令将需要较长时间才能完成。输出如下:

图 10.4 – 使用 ReaR 格式化 USB 磁盘
-
现在,我们需要修改
/etc/rear/local.conf文件并进行调整,使其使用 USB 作为备份目标。我们将添加的新的行应该如下所示:OUTPUT=USB BACKUP_URL="usb:///dev/disk/by-label/REAR-000" -
为了理解最后一行代码,你可以运行以下命令:
/dev/disk/by-label/REAR-000 is a link to /dev/sda1 (in our case):

图 10.5 – 从 ReaR 配置文件中检查 URL 位置
-
为了将系统备份到 USB 磁盘,运行以下命令:
tar.gz files will be on the USB drive. -
为了恢复系统,你需要从 USB 驱动器启动并选择第一个选项,即
Recover "hostname",其中"hostname"是你备份的计算机的主机名。
系统备份与恢复是两个非常重要的任务,应该是任何 Linux 系统管理员的必备技能。掌握这些任务的执行方法,可以为公司和客户节省数据、时间和金钱。最小化停机时间并提供快速、有效的响应,应该是每位首席技术官(CTO)桌面上最重要的资产。备份和恢复策略应该始终建立在良好的缓解实践基础上。从这个角度来看,一个强大的诊断工具集和故障排除知识将对每个系统管理员始终有帮助。这就是为什么在接下来的章节中,我们将展示一些 Linux 中最佳的诊断工具。
介绍常用的 Linux 诊断工具用于故障排除
Linux 的开放性是其最优点之一。这为各种任务提供了大量可以使用的解决方案。因此,许多诊断工具可供 Linux 系统管理员使用。根据你想要诊断系统的哪个部分,有多种工具可供选择。故障排除本质上是基于特定工具生成的诊断信息来解决问题。为了减少需要覆盖的诊断工具数量,我们将在本节中将问题缩小到以下几个类别:
-
启动问题
-
一般系统问题
-
网络问题
-
硬件问题
每个类别都有特定的诊断工具。我们将首先展示一些最常用的工具。
故障排除工具
要理解可能影响启动过程的问题,了解启动过程的工作原理非常重要。我们还没有详细介绍这一点,所以请注意接下来我们会告诉你的内容。
启动过程
所有主要的 Linux 发行版,如 Ubuntu、OpenSUSE、Debian、Fedora 和 RHEL,都将 systemd 作为默认的初始化系统。在 GRUB2 初始化和 systemd 启动实施之前,Linux 启动过程还有几个阶段。
启动顺序如下:
-
基本输入输出系统 (BIOS) 开机自检 (POST)
-
GRUB2 启动加载程序初始化
-
GNU/Linux 内核初始化
-
systemd init系统初始化
BIOS 自检(POST)是特定于硬件初始化和测试的过程,对于每台 PC 来说都是类似的,无论它是使用 Linux 还是 Windows。BIOS 会确保 PC 内部的每个硬件组件都正常工作。如果 BIOS 无法启动,通常是硬件故障或不兼容问题。BIOS 会搜索磁盘的启动记录,如 主引导记录 (MBR) 或 GUID 分区表 (GPT),并将其加载到内存中。
GRUB2 初始化是 Linux 开始工作的地方。这是系统将内核加载到内存中的阶段。如果有多个操作系统可用,它可以在不同的内核之间进行选择。内核一旦被加载到内存中,它就控制了整个启动过程。
内核是一个自解压存档。解压后,它会加载到内存中,并启动 init 系统,这是 Linux 中所有其他进程的父进程。
init 系统,称为 systemd,通过挂载文件系统并访问所有可用的配置文件来启动。
在启动过程中,可能会出现一些问题。在接下来的章节中,我们将讨论如果灾难发生,且启动加载程序无法启动时应该怎么办。
修复 GRUB2
如果 GRUB2 损坏,你将无法访问系统。此时需要进行 GRUB 修复。在这个阶段,一个可启动的 USB 驱动器将为你提供帮助。以下步骤是一个可能算作实验的练习。在我们的案例中,我们有一张 Ubuntu 22.04 LTS 桌面版的启动盘,我们将以此作为示例。然而,你可以使用任何你喜欢的 Linux 发行版,不一定非得是 Ubuntu。关键是你需要一个安装了 Linux 的可启动 USB 驱动器。以下是你需要遵循的步骤:
-
插入 Ubuntu 22.04 的启动盘并启动系统。
-
打开 BIOS,选择可启动磁盘作为主启动设备,并重新启动计算机。
-
选择尝试 Ubuntu选项。
-
进入 Ubuntu 实例后,打开终端,输入
sudo fdisk -l命令,检查你的磁盘和分区。 -
选择安装了 GRUB2 的磁盘,并使用以下命令(使用系统提供的磁盘名称,不要直接复制/粘贴我们的示例):
sudo mount -t ext4 /dev/sda1 /mnt -
使用以下命令安装 GRUB2:
sudo chroot /mnt grub-install /dev/sda grub-install –recheck /dev/sda update-grub -
使用以下命令卸载分区:
exit sudo unmount /mnt -
重启计算机。
处理引导加载程序非常敏感。请注意所有细节,并小心你输入的所有命令。如果不小心,一切可能都会出错。在接下来的部分,我们将向你展示一些用于诊断常见系统问题的工具。
故障排除常见系统问题的工具
系统问题可能有不同的类型和复杂度。知道如何使用工具来处理它们至关重要。在这一部分,我们将介绍 Linux 发行版提供的默认工具。任何 Linux 系统管理员都需要具备基本的故障排除知识,因为在常规操作过程中,问题是不可避免的。
一般系统问题可能意味着什么呢?基本上,这些问题涉及磁盘空间、内存使用、系统负载和运行中的进程。
与磁盘相关的问题的命令
磁盘,无论是 HDD 还是 SSD,都是系统的重要组成部分。它们为你的数据、文件和软件提供必要的空间,包括操作系统。我们不会讨论与硬件相关的问题,因为这将在本章的未来部分“故障排除硬件问题的工具”中进行探讨。相反,我们将讨论与磁盘空间相关的问题。最常见的诊断工具已经安装在任何 Linux 系统中,它们通过以下命令表示:
-
du:显示文件和目录磁盘空间使用情况的工具 -
df:显示目录磁盘使用情况的工具
以下是使用df工具并带有-h(人类可读)选项的示例:
df -h
如果其中一块磁盘的空间不足,它会在输出中显示出来。虽然在我们的案例中这不是问题,但这个工具仍然很有用,可以帮助你找出哪个可用磁盘的空闲空间出现了问题。
当磁盘已满或接近满时,可以应用几种修复方法。如果必须删除某些文件,我们建议您删除 /home 目录中的文件。尽量不要删除重要的系统文件。以下是一些故障排除可用空间问题的建议:
-
使用
rm命令删除不必要的文件(可选择性地,谨慎使用-rf选项)或使用rmdir命令 -
使用
rsync命令将文件移动到外部驱动器(或云端) -
查找
/home目录中使用最多空间的目录
以下是使用du工具查找 /home 目录中最大目录的示例。我们使用了两个管道,将 du 命令的输出传递给 sort 命令,最后再传递给 head 命令并使用 5 选项(因为我们只想显示五个最大目录,而不是所有的目录):

图 10.6 – 查找你 /home 目录中最大的目录
另一个故障排除场景是与使用的 inode 数量有关,而不是磁盘上的空间。在这种情况下,您可以使用df -i命令查看是否用尽了 inode:

图 10.7 – inode 使用统计
前面的截图显示了关于系统中 inode 使用的基本信息。你会看到不同文件系统中 inode 的总数,已使用的 inode 数量(以数字和百分比表示),以及空闲的 inode 数量。
除了这里显示的命令,这些命令是每个 Linux 发行版的默认命令外,还有许多其他开源工具可以处理磁盘空间问题,例如pydf、parted、sfdisk、iostat以及基于 GUI 的GParted应用程序。
在接下来的部分,我们将展示如何使用命令来验证可能的内存问题。
内存使用问题的命令
free 命令可以在任何主要发行版中使用。在以下示例中,我们将使用 -h 选项来获取人类可读的输出:

图 10.8 – 在 Linux 中使用 free 命令
如前面的截图所示,使用free命令(带有-h选项以便输出人类可读格式)会显示以下内容:
-
total:总内存 -
used:已使用的内存,通过总内存减去缓冲区、缓存和空闲内存计算得出 -
free:空闲或未使用的内存 -
shared:tmpfs使用的内存 -
buff/cache:内核缓冲区和页面缓存使用的内存 -
available:为新应用程序提供的可用内存
这样,你可以找到与更高内存使用相关的具体问题。定期检查服务器的内存使用情况对于确保资源高效使用至关重要。
另一种检查内存使用情况的方法是使用 top 命令,如下图所示:

图 10.9 – 使用 top 命令检查内存使用情况
使用 top 命令时,屏幕上有多个可用的部分。输出是动态的,意味着它会不断变化,实时显示系统中正在运行的进程的信息。memory 部分显示有关总内存使用情况的信息,以及空闲和缓存内存。所有信息默认以兆字节(MB)为单位显示,便于阅读和理解。
另一个显示内存(以及其他有价值的系统信息)的命令是 vmstat:

图 10.10 – 使用不带选项的 vmstat 命令
默认情况下,vmstat 显示有关进程、内存、交换、磁盘和 CPU 使用情况的信息。内存信息从第二列开始显示,并包含以下详细信息:
-
swpd: 正在使用的虚拟内存量 -
free: 内存中剩余的可用空间 -
buff: 用于缓冲的内存量 -
cache: 用于缓存的内存量
vmstat 命令有多个可用的选项。要了解所有选项以及输出中的各个列所代表的含义,可以使用以下命令访问手册中的相应页面:
man vmstat
可以与 vmstat 一起使用的选项有 -a 和 -s,通过使用 vmstat -a,输出将显示活动内存和非活动内存:

图 10.11 – 使用 vmstat -a 显示活动内存和非活动内存
使用 vmstat -s 命令将显示详细的内存、CPU 和磁盘统计信息。
本节讨论的所有命令对于排查内存问题非常重要。可能还有其他可以使用的命令,但这些命令是任何 Linux 发行版上默认提供的命令。
然而,还有一个值得在本节中提及的命令:sar 命令。可以通过 sysstat 软件包在 Ubuntu 中安装该命令。因此,可以使用以下命令安装该软件包:
sudo apt install sysstat
安装完包后,为了能够使用sar命令显示系统内存使用的详细统计数据,你需要启用sysstat服务。它需要处于活动状态才能收集数据。默认情况下,服务每 10 分钟运行一次,并将日志保存在/var/log/sysstat/saXX目录中。每个目录的命名是以服务运行的日期命名的。例如,如果我们在 4 月 25 日运行sar命令,服务会在/var/log/sysstat/sa25中查找数据。我们在启动服务之前运行了sar命令,并且发生了错误。因此,要启用数据收集,首先需要启动并启用sysstat服务,然后再运行应用程序。使用sar命令,你可以实时生成不同的报告。例如,如果我们想每两秒生成一次内存报告,可以使用-r选项:

图 10.12 – 启动和启用服务并运行 sar
服务名称是sadc,它使用sysstat作为包和服务的名称。
重要提示
在系统重启的情况下,服务可能不会默认重新启动,即使之前的命令已执行。为了解决这个问题,在 Ubuntu 上,你应编辑/etc/default/sysstat文件,并将ENABLED的状态从false更改为true。
在图 10.12中显示的输出将每两秒写一行,连续五次,最后显示一行平均值。它是一个强大的工具,不仅仅可以用于内存统计,还有 CPU 和磁盘统计选项。
总体而言,本节我们涵盖了用于排查内存问题的最重要工具。在下一节中,我们将介绍用于解决系统负载问题的工具。
系统负载问题的命令
与我们在前面几节中讨论的类似,本节将讨论top命令,这是我们在判断系统卡顿时最常用的工具。其他工具,如vmstat和sar,也可以用于 CPU 和系统负载故障排除。
排查系统负载的一个基本命令是uptime:

图 10.13 – 使用 uptime 检查系统负载
uptime的输出显示了三个值,代表了 1、5 和 15 分钟的负载均值。负载均值可以为你提供一个关于系统进程状态的合理图像。
如果你有一个单核 CPU 系统,负载均值为1意味着该 CPU 正在全负荷运行。如果数值更高,表示负载远远超过 CPU 的处理能力,这可能会给系统带来很大压力。因此,进程执行将需要更长的时间,系统的整体性能也会受到影响。
高负载平均值意味着有些应用程序同时运行多个线程。然而,某些负载问题不仅是过载 CPU 的结果 – 它们可能是 CPU 负载、磁盘 I/O 负载和内存负载的综合效果。在这种情况下,用于故障排除系统负载问题的瑞士军刀是top命令。top 命令的输出根据系统负载实时变化。
默认情况下,top按 CPU 使用情况对进程进行排序。它以交互模式运行,有时屏幕上的输出不易看清。您可以将输出重定向到文件,并使用-b选项以批处理模式运行命令。此模式仅更新指定次数的命令。要以批处理模式运行top,请运行以下命令:
top -b -n 1 | tee top-command-output
对于经验不足的 Linux 用户来说,top 命令可能有点吓人。输出如下截图所示:

图 10.14 – top 命令的输出
让我们来看看输出的含义:
-
us:用户 CPU 时间 -
sy:系统 CPU 时间 -
ni:优先级较低的 CPU 时间 -
id:空闲 CPU 时间 -
wa:输入/输出等待时间 -
hi:CPU 硬件中断时间 -
si:CPU 软件中断时间 -
st:CPU 窃取时间
另一个用于故障排除 CPU 使用率和硬盘输入/输出时间的工具是iostat:

图 10.15 – iostat 的输出
CPU 统计信息类似于先前显示的top命令的输出。I/O 统计信息显示在 CPU 统计信息下方,以下是每列代表的内容:
-
tps:每秒传输到设备的次数(I/O 请求) -
kB_read/s:从设备读取的数据量(以块数 – 千字节表示) -
kB_wrtn/s:写入到设备的数据量(以块数 – 千字节表示) -
kB_dscd/s:设备丢弃的数据量(以千字节为单位) -
kB_read:读取的总块数 -
kB_wrtn:写入的总块数 -
kB_dscd:丢弃的总块数
要了解有关iostat命令的更多详细信息,请使用以下命令阅读相应的手册页面:
man iostat
除了iostat命令,您还可以使用另一个称为iotop的命令。它在 Ubuntu 上默认情况下未安装,但您可以使用以下命令安装它:
sudo apt install iotop
安装完成后,您需要sudo权限来运行它:
sudo iotop
输出如下截图所示:

图 10.16 – 运行 iotop 命令
您还可以运行sysstat服务来解决系统负载问题,类似于我们用它来解决内存问题。
默认情况下,sar将输出当前日期的 CPU 统计信息:

图 10.17 – 运行 sar 进行 CPU 负载故障排除
在前面的截图中,sar命令运行了五次,每次间隔两秒。此时,我们的本地网络服务器负载不重,但你可以想象,如果在负载较重的服务器上运行该命令,输出会有所不同。正如我们在前一节中提到的,sar命令有多个选项,在寻找潜在问题的解决方案时可能会非常有用。运行man sar命令可以查看包含所有可用选项的手册页面。
还有许多其他工具可以用于一般的系统故障排除。我们在本节中展示的工具仅仅是这一主题的冰山一角。如果你有需要,可以搜索更多为系统故障排除设计的工具。否则,本文所介绍的工具已经足够让你生成一份关于可能系统问题的有效报告。
网络特定的问题将在下一节中讲解。
用于故障排除网络问题的工具
由于网络的复杂性,问题经常会出现。网络是日常生活中不可或缺的一部分。我们无处不在使用它们,从无线智能手表到智能手机,再到电脑,甚至到云端。所有的一切都在全球范围内连接,以便让我们的生活更加便捷,也让系统管理员的生活稍微复杂一些。在这个互联的世界中,事情很容易出问题,因此网络问题需要进行故障排除。
故障排除网络问题几乎占据了系统管理员 80%的工作——可能甚至更多。这个数字并没有官方研究数据支持,而是基于实际操作经验的洞察。由于大多数服务器和云端问题与网络有关,优化的网络意味着减少停机时间,并且能够让客户和系统管理员更开心。
本节中将介绍的工具是所有主要 Linux 发行版的默认工具。所有这些工具都在第七章《Linux 网络》和第九章《保护 Linux》中讨论过,或者将在第十三章《配置 Linux 服务器》中讨论。因此,我们只会从问题解决的角度再次提及它们。让我们从具体的 TCP/IP 层次来逐步分解应该使用的工具。你还记得 TCP/IP 模型有多少层吗?一共有五层,我们将从第 1 层开始。作为一种良好的实践,故障排除网络时,最好从应用层开始,逐层深入到物理层。
诊断物理层(第 1 层)
最基本的测试工具之一,也是大多数系统管理员最先使用的工具就是ping命令。其名称来源于ping命令的-c选项。输出如下:

图 10.18 – 使用 ping 命令进行基本测试
Ping 命令向目标发送简单的 ICMP 数据包(在我们的例子中是google.com),并等待响应。一旦收到响应且没有丢包,说明一切正常。ping命令可以用于测试本地网络系统的连接,也可以用于测试远程网络。它是用于测试和排除可能问题的第一个工具。
有时,简单的ping命令测试不足以解决问题。在这种情况下,另一个多功能的命令是ip命令。你可以使用它来检查物理层是否有任何问题:
ip link show
你将看到如下输出:

图 10.19 – 使用 ip 命令显示物理接口的状态
在前面的截图中,你可以看到以太网接口运行正常(state UP)。由于我们运行的是虚拟机,没有无线连接。如果我们使用的是笔记本电脑,举个例子,无线连接会显示出来,类似于wlp0s20f3。
如果某个接口无法正常工作,例如无线接口,前面命令的输出会显示state DOWN。在我们的例子中,我们将使用以下命令来启用无线接口:
ip link set wlp0s20f3 up
执行后,你可以再次运行ip命令来检查接口的状态:
ip link show
如果你直接访问裸机系统,比如一台服务器,你可以直接检查电缆是否连接。如果你恰好使用的是无线连接(不推荐),你需要使用ip命令。
另一个用于第一层的有用工具是ethtool。在 Ubuntu 22.04.2 LTS 中,它默认安装。要检查以太网接口,可以使用以下命令,输入连接的名称(你在使用ip命令时看到过):
ethtool enp1s0
通过使用ethtool,我们可以检查连接是否协商了正确的速度。在命令的输出中(我们这里不展示),你会看到系统成功协商了一个 1,000 Mbps 的全双工连接,例如(在你的情况下可能会不同)。在下一部分,我们将展示如何诊断第二层栈。
诊断数据链路层(第二层)
TCP/IP 栈的第二层包括ip命令和arp命令。arp命令来自net-tools包。首先,安装该包,使用以下命令:
sudo apt install net-tools
要检查 ARP 表中的条目,可以使用带有-a(所有)选项的arp命令。由于我们运行的是虚拟机,我们只有一个条目。为了比较,我们在主机系统上运行相同的命令,这时有三个条目,一个是无线连接,一个是有线连接,还有一个是 KVM 使用的虚拟桥接连接。以下是一个示例:

图 10.20 – 使用 arp 命令映射 ARP 条目
arp 命令的输出将显示所有连接的设备,包含它们的 IP 地址和 MAC 地址的详细信息。请注意,出于隐私原因,MAC 地址已被模糊处理。
类似于 arp 命令,你可以使用 ip neighbor show 命令,如下图所示。我们将使用我们的主机系统,而不是虚拟机:

图 10.21 – 使用 ip 命令列出 ARP 条目
ip 命令可用于从 ARP 列表中删除条目,方法如下:
ip neighbor delete 192.168.124.112 dev virbr0
我们使用命令删除了虚拟桥接连接,使用其 IP 地址和名称从列表中删除,如图 10.21所示。
arp 和 ip 命令的输出类似。它们是强大的命令,对于排查可能的第 2 层问题非常有用。在下一部分中,我们将向你展示如何诊断第 3 层堆栈。
诊断互联网层(第 3 层)
在互联网层(第 3 层),我们仅使用 IP 地址。我们已经了解了这里要使用的工具,如 ip 命令、ping 命令、traceroute 命令和 nslookup 命令。由于我们已经讨论过 ip 和 ping 命令,这里将仅讨论如何使用 traceroute 和 nslookup。traceroute 命令在 Ubuntu 中默认没有安装。你需要使用以下命令安装它:
sudo apt install traceroute
nslookup 包在 Ubuntu 中默认可用。首先,要检查路由表,查看不同路由的网关列表,我们可以使用 ip route show 命令:

图 10.22 – 使用 “ip route show” 命令显示路由表
ip route show 命令显示了默认网关。若它缺失或配置错误,则可能是一个问题。
traceroute 工具用于检查从源到目的地的流量路径。以下输出显示了数据包从我们本地网关到 Google 服务器的路径:

图 10.23 – 使用 traceroute 进行路径追踪
数据包在发送和返回源时通常不会走相同的路由。数据包被发送到网关进行处理,然后按某条路由发送到目的地。当数据包超过本地网络时,它们的路由可能会被 traceroute 工具不准确地表示,因为它依赖的数据包可能会被路径上的多个网关过滤(ICMP TTL 超时的数据包通常会被过滤)。
类似于 traceroute,还有一个更新的工具叫做 tracepath。它在 Ubuntu 上默认安装,是 traceroute 的替代品。traceroute 和 tracepath 默认都使用 UDP 端口进行跟踪。tracepath 命令还可以与 -n 选项一起使用,以显示 IP 地址而不是主机名。以下是一个示例:

图 10.24 – 使用 tracepath 命令
进一步检查网络问题可能会导致 DNS 解析错误,导致主机只能通过 IP 地址访问,而无法通过主机名访问。要排查此问题,您可以使用 nslookup 命令,并结合 ping 命令进行诊断,即使这不是一个第 3 层的协议。
如果 nslookup 命令与 ping 命令输出的 IP 地址相同,那么一切正常。如果输出中出现不同的 IP 地址,那么您的主机配置可能存在问题。在下一部分中,我们将向您展示如何诊断第 4 层和第 5 层的协议栈。
诊断传输层和应用层(第 4 层和第 5 层)
最后的两层,第 4 层(ss 命令,其中 ss 是 套接字统计 的缩写)。
ss 命令是 netstat 的最近替代品,用于查看所有网络套接字的列表。因此,列表可能会非常长,您可以使用多个命令选项来缩短它。
例如,您可以使用 -t 选项仅查看 TCP 套接字,使用 -u 选项查看 UDP 套接字,使用 -x 查看 Unix 套接字。因此,要查看 TCP 和 UDP 套接字信息,我们将使用带有 -t 选项的 ss 命令。此外,要查看系统上所有的监听套接字,您可以使用 -l 选项。将其与 -u 和 -t 选项结合使用,将显示系统上所有的 UDP 和 TCP 监听套接字。以下是从更长列表中截取的一部分:

图 10.25 – 使用 ss 命令列出 TCP 和 UDP 套接字以及所有监听套接字
ss 命令在网络故障排除中非常重要,尤其是当您想要验证可用套接字及其处于 LISTEN 状态时。另一个用途是检查 TIME_WAIT 状态,您可以使用如下截图中的命令:

图 10.26 – 使用 ss 命令显示 TIME_WAIT 端口
我们使用了 ss 命令,搭配 -o 选项和 state time-wait 参数。当一个套接字关闭一段短时间时,会进入 TIME_WAIT 状态。
如需获取更多关于此状态的信息,您可以访问以下链接:vincent.bernat.ch/en/blog/2014-tcp-time-wait-state-linux。
ss工具在系统管理员的工具箱中不可或缺。第 5 层,应用层,包含应用程序使用的协议,我们将记住像动态主机配置协议(DHCP)、超文本传输协议(HTTP)和文件传输协议(FTP)这样的协议。由于诊断第 5 层主要是一个应用程序故障排除过程,本节将不涉及此内容。
在下一节中,我们将讨论硬件故障排除。
故障排除硬件问题的工具
故障排除硬件问题的第一步是检查你的硬件。查看系统硬件详细信息的一个非常好的工具是dmidecode命令。此命令用于以人类可读的格式读取每个硬件组件的详细信息。每个硬件都有一个特定的 DMI 代码,取决于它的类型。该代码特定于系统管理基础输入/输出系统(SMBIOS)。SMBIOS 使用 45 个代码。有关这些代码的更多信息,可以访问www.thegeekstuff.com/2008/11/how-to-get-hardware-information-on-linux-using-dmidecode-command/。
要查看系统内存的详细信息,可以使用带有-t选项(来自TYPE)和代码17的dmidecode命令,这对应于 SMBIOS 中的内存设备代码。以下是我们虚拟机中的一个示例:

图 10.27 – 使用 dmidecode 查看内存信息
要查看其他硬件组件的详细信息,请使用带有特定代码的命令。
其他快速故障排除工具包括lspci、lsblk和lscpu等命令。lsblk命令的输出显示系统中正在使用的磁盘和分区信息。lscpu命令将显示有关 CPU 的详细信息。
同时,在故障排除硬件问题时,快速查看内核日志可能会很有帮助。要做到这一点,请使用dmesg命令。你可以使用dmesg | more命令来更好地控制输出。
正如你在本节中看到的,硬件故障排除与其他类型的故障排除同样重要且具有挑战性。解决硬件相关的问题是任何系统管理员工作的重要组成部分。这包括不断检查硬件组件,替换故障部件,并确保它们顺利运行。
总结
在本章中,我们强调了灾难恢复计划、备份与恢复策略以及排除各种系统问题的重要性。每个系统管理员都应能够在灾难发生时将自己的知识付诸实践。不同类型的故障最终会影响正在运行的服务器,因此应尽快找到解决方案,以确保最小的停机时间并防止数据丢失。
本章是本书高级 Linux 管理部分的高潮。在下一章中,我们将向你介绍服务器管理,重点讲解 KVM 虚拟机管理、Docker 容器和不同类型的 Linux 服务器配置。
问题
故障排除是解决问题的最佳方式。在我们深入服务器部分之前,先测试一下你的故障排除知识:
-
尝试为你的私有网络或小型企业草拟一份灾难恢复计划(DRP)。
-
使用 321 规则备份你的整个系统。
-
查找系统中使用 CPU 最多的前 10 个进程。
-
查找系统中使用内存最多的前 10 个进程。
延伸阅读
-
Ubuntu LTS 官方文档:
ubuntu.com/server/docs -
RHEL 官方文档:
access.redhat.com/documentation/en-us/red_hat_enterprise_linux/9/ -
SUSE 官方文档:
documentation.suse.com/
第三部分:服务器管理
在这一部分,你将通过设置不同类型的服务器来学习高级 Linux 服务器管理任务,并学习如何处理和管理虚拟机和 Docker 容器。
本部分包括以下章节:
-
第十一章,与虚拟机的工作
-
第十二章,使用 Docker 管理容器
-
第十三章,配置 Linux 服务器
第十一章:使用虚拟机
本章中,你将了解 Linux 上的虚拟机(VMs)。首先,你将了解虚拟化的工作原理以及如何创建和使用虚拟机。你将学习 Linux 上最广泛使用的虚拟化和虚拟机监控技术之一——基于内核的虚拟机(KVM)。本章的主题将为你迎接 Linux 的未来做好准备,因为它是现代云技术的基础。如果你希望在不断变化的技术环境中保持与时俱进,本章将是你旅程中的一个重要起点。
本章将涵盖以下主要主题:
-
Linux 上的虚拟化简介
-
理解 Linux KVM
-
使用基本的 KVM 命令
-
高级 KVM 管理
-
使用 cloud-init 配置虚拟机
-
使用 SSH 的公钥认证
技术要求
不需要特殊的技术要求,只需系统上有一个正常工作的 Linux 安装。我们将主要使用 Debian GNU/Linux 12 作为示例,但也会展示如何在 Fedora 和 openSUSE 中安装 KVM。
Linux 上的虚拟化简介
虚拟化是一种更有效利用计算机硬件的方式。它本质上是一个抽象层,利用计算机的资源。在本节中,你将学习虚拟机的类型、它们如何在 Linux 上工作以及如何部署和管理它们。
资源使用效率
虚拟化使用的抽象层是一个软件层,允许更高效地使用计算机的所有组件。这样可以更好地利用物理机器的所有能力和资源。
在深入探讨虚拟化之前,让我们先给你一个例子。在我们的测试实验室中,我们有几台物理机器,以笔记本电脑和小型桌面计算机(Intel NUC)为主,这些机器用作服务器。每台系统都拥有足够的资源,足以运行我们需要的服务。例如,我们性能最差的系统是一台第五代 Intel NUC,搭载 Intel i3 处理器,四个处理核心,16GB 内存,以及一台第七代 Intel NUC,搭载四核 Intel Pentium 处理器和 12GB 内存。这两台系统有足够的资源,可以通过使用虚拟机更加高效地利用。
对于在本地网络上运行本地 Web 服务或任何类型的服务器,资源可以轻松地在多个虚拟机之间进行分配。例如,每个物理系统可以托管四个不同的虚拟机,每个虚拟机使用一个 CPU 核心,至少 2GB 内存和所有必要的存储容量。这样,一台机器将像四台不同的机器一样工作。这比使用独立机器处理单独任务更加高效。
在以下图示中,我们对比了单台计算机的负载与相同负载分配到多个虚拟机之间的情况。以这种方式使用相同的硬件资源更加高效:

图 11.1 – 单台计算机使用与使用多个虚拟机的对比
然而,正如我们将在宿主操作系统上使用虚拟机监控器一样,我们必须为操作系统保留一些资源,因此虚拟机的数量会较少。以下是虚拟机在宿主操作系统上工作的示意图:

图 11.2 – 虚拟化在宿主操作系统上的工作原理
上图展示了虚拟化在宿主操作系统上使用时的工作原理。如我们将在接下来的章节中看到的,这并不是唯一的虚拟化类型。
需要注意的是,效率不仅仅与使用的硬件资源相关。数据中心中硬件高效使用的一个重要方面是提高能源效率和减少碳足迹。在这方面,虚拟化在改变数据中心内部服务器使用模式方面已经发挥了几十年的重要作用。总体而言,虚拟化和容器化在应对气候变化的斗争中扮演着重要角色。
在接下来的章节中,我们将简要介绍虚拟机监控器和虚拟机。
虚拟机监控器介绍
虚拟化所基于的软件层称为虚拟机监控器。物理资源被划分并作为虚拟计算机使用,更广为人知的是虚拟机(VM)。通过使用虚拟机,物理硬件的局限性通过模拟过程得以克服。这具有许多优点,使得硬件可以更高效地使用。
重要说明
模拟过程本质上是一个模仿过程,通过该过程,一款软件复制(或模仿)另一个系统的功能。在我们的案例中,虚拟机监控器(虚拟化软件层)模拟硬件的使用,就好像它是一个完全不同的系统。这使得计算机拥有的硬件资源能够更加高效地利用。
虚拟机监控器可以运行在现有操作系统上(类型 2),也可以直接运行在裸金属硬件上(类型 1)。对于这两种类型的虚拟机监控器,特别是在 Linux 上,有各种可用的解决方案。对于 Linux 操作系统,每种类型的示例如下:
-
运行在宿主操作系统之上的虚拟机监控器(类型 2)示例包括 Oracle VirtualBox 和 VMware Workstation/Fusion。
-
直接运行在裸金属硬件上的虚拟机监控器(类型 1)示例包括 Citrix Xen Server 和 VMware ESXi。
-
KVM 通常被归类为裸金属虚拟机监控器(类型 1),而其底层系统是一个完整的操作系统,因此它也同时被归类为宿主虚拟机监控器(类型 2)。
在本章中,我们将专门使用 KVM 作为首选虚拟机监控器。
了解 Linux KVM
虚拟机类似于一台独立的计算机。它是一种基于软件的模拟器,可以访问宿主计算机的资源。它使用宿主计算机的 CPU、内存、存储、网络接口和端口。不仅如此,它还是一个具有与物理计算机相同功能的虚拟环境;也被视为一台虚拟计算机。
每个虚拟机的资源由虚拟化技术管理。它可以在现有虚拟机之间重新分配资源或创建新的虚拟机。虚拟机之间相互隔离,并与宿主计算机隔离。由于多个虚拟机可以存在于一台计算机上,每个虚拟机可以使用不同的客户操作系统。例如,如果你使用的是 Windows 计算机并想尝试 Linux,一个流行的解决方案是创建一个虚拟机来运行你想尝试的 Linux 发行版。Mac 用户也是一样。安装在虚拟机中的操作系统与直接安装在硬件上的操作系统运行方式相似。用户体验可能因虚拟化技术的不同而有所不同,资源效率和响应时间也可能不同。根据我们的经验,我们更倾向于使用 KVM 来运行虚拟机,而不是使用其他虚拟化技术,主要因为其全面的命令行界面(CLI)。不过,使用场景因用户不同而有所不同。
选择虚拟化技术
在本章中,我们选择了 KVM 虚拟化技术。作为一个可选方案,如果你使用 GNOME 桌面环境,你可以使用 GNOME Boxes。由于 KVM 和 GNOME Boxes 都可以直接从 Linux 仓库获取,我们认为它们是 Linux 新手的较好选择。KVM 和 GNOME Boxes 共享部分 libvirt 和 qemu 代码(将在下一节详细介绍),在这方面,我们认为它们都是相同的虚拟化技术,即 KVM。
在第一章,安装 Linux 中,你第一次接触了使用虚拟化技术来设置 Linux 虚拟机的方式。我们展示了如何使用 VMware 解决方案和 VirtualBox 来设置 Linux 虚拟机。那时提供的细节对于任何用户来说都应该足够,无论他们是经验丰富的用户还是新手。VirtualBox 具有一些功能,使其成为虚拟化技术的一个不错的选择,但在我们看来,它仍然缺乏 KVM 的精细度。在下一节中,我们将带你深入了解 KVM。
使用 KVM 虚拟化技术
KVM 是一个开源虚拟化项目,适用于所有主要的 Linux 发行版。它是一种现代虚拟化技术,使用特定的内核模块来充分利用 Linux 内核所提供的所有优势,包括内存支持、调度器、嵌套虚拟化、GPU 直通等。
KVM 详细介绍 – QEMU 和 libvirt
KVM 使用 libvirt。KVM 与 libvirt 的接口,特别是在 GNOME 中,是 virt-manager。libvirt 的命令行界面(CLI)叫做 virsh。
libvirt API 提供了一个通用的库用于管理虚拟机(VM)。它是用于虚拟机创建、修改和配置的管理层。它作为一个名为libvirtd的守护进程在后台运行,处理客户端请求时与虚拟化管理程序的连接。
QEMU 既是一个仿真器,也是一个虚拟化工具。当作为仿真器使用时,QEMU 使用动态二进制翻译方法来操作。这意味着它可以在宿主机上使用不同类型的操作系统,即使这些操作系统是为不同架构设计的。动态二进制翻译用于基于软件的虚拟化,其中硬件被仿真以在虚拟化环境中执行指令。通过这种方式,QEMU 仿真机器的 CPU,使用一种特定的二进制翻译方法,叫做Tiny Code Generator(TCG),它将二进制代码转换为不同架构的机器代码。
当作为虚拟化工具使用时,QEMU 使用被称为基于硬件的虚拟化的方法,在这种情况下不使用二进制翻译,因为指令直接在宿主机的 CPU 上执行。软件虚拟化与硬件辅助虚拟化之间的差异如下面的图所示:

图 11.3 – 软件虚拟化与硬件辅助虚拟化的比较
如图所示,在使用软件虚拟化和硬件辅助虚拟化时,指令有不同的执行路径。在软件辅助虚拟化中,当使用动态二进制翻译时,用户的非特权指令会直接发送到硬件,而来宾操作系统的特权指令则先发送到虚拟化管理程序,再发送到硬件。在硬件辅助虚拟化中,用户的非特权指令会先发送到虚拟化管理程序,再发送到硬件,而来宾操作系统的特权指令与软件辅助虚拟化中的路径相同。这确保了来宾操作系统的隔离性,从而实现了更好的性能和更少的复杂性。
在接下来的部分,我们将向你展示如何在 Debian 12 机器上安装和配置 QEMU。我们认为 Debian 是一个足够轻量级的发行版,提供了虚拟化主机操作系统所需的稳定性。一些命令也可以在 Ubuntu 上复制。
在主要的 Linux 发行版上安装虚拟化管理程序
安装 QEMU 是一个简单的任务。你需要做的就是运行你的发行版的包安装工具,并指定一些包名称。在我们的示例中,我们将向你展示如何在主要的 Linux 发行版(如 Debian/Ubuntu、Fedora 和 openSUSE)上安装 QEMU:
-
在 Debian/Ubuntu Linux 上安装
运行以下命令:
sudo apt install qemu-kvm libvirt-clients libvirt-daemon-system bridge-utils virtinst libvirt-daemon virt-manager -
在 Fedora Linux 上安装
运行以下命令:
sudo dnf group install --with-optional virtualization -
在 openSUSE Linux 上安装
运行以下命令:
sudo zypper install -t pattern kvm_server kvm_tools sudo zypper install libvirt-daemon
一旦所有必要的软件包安装完成,你可以使用以下命令启用并启动libvirtd守护进程(适用于本节中展示的所有 Linux 发行版):
sudo systemctl start libvirtd
sudo systemctl enable libvirtd
安装完软件包并启动和启用守护进程后,下一步安全的操作是检查你的机器是否符合 KVM 的要求。要做到这一点,可以使用virt-host-validate命令,以 root 用户身份或通过sudo来运行。我们在一台 Debian GNU/Linux 12 主机上运行该命令,但它也可以在其他 Linux 发行版上使用:
sudo virt-host-validate
一旦命令运行,你可能会收到一些关于 QEMU 或Linux 容器(LXC)的错误或警告——LXC 是一种用于运行隔离系统的技术,类似于 KVM 的工作方式——这些错误或警告取决于你的系统(更多关于 LXC 的内容可以参见第十二章)。在我们的案例中,输出显示了一个关于 LXC 兼容性的错误,如下图所示:

图 11.4 – 运行主机验证程序
然而,这个错误不会限制我们使用libvirt和 QEMU,因此我们不打算在这里解决它。
在确认没有关于 QEMU 的兼容性问题后,我们可以继续通过 CLI 创建我们的第一个虚拟机。因此,我们将开始使用 KVM 特定的命令。
使用基本的 KVM 命令
在使用 KVM 时,最先使用的命令之一是用于创建虚拟机(VM)的命令。其他命令,如接下来的章节所示,是用于启动、停止、删除或暂停已有虚拟机的命令。
使用命令行创建虚拟机
在使用libvirt创建第一个虚拟机之前,我们必须检查并确认我们的默认桥接网络配置是否已创建。我们可以通过使用以下命令来验证:
sudo virsh net-list
该命令显示是否已创建默认桥接配置以及它是否正在运行。在我们的案例中,桥接连接没有运行,因此我们需要自己进行设置。用于启动默认桥接网络的命令如下:
sudo virsh net-start default
一旦它启动,网络桥接未设置为自动启动,因此我们将使用以下命令将其设置为自动启动:
sudo virsh net-autostart default
现在,输出结果如下:

图 11.5 – 启用默认桥接连接
现在,默认的桥接连接已启用并设置为自动启动,我们可以创建我们的第一个虚拟机。创建虚拟机的步骤如下:
-
首先,我们需要下载操作系统的镜像文件,以便在虚拟机内使用。以我们的示例为例,我们将创建一个新的虚拟机,使用 Ubuntu 22.04.2 LTS 服务器版。我们可以使用以下命令下载 ISO 镜像:
/var/lib/libvirt/images. -
一旦 Ubuntu 镜像下载完成,我们将使用
virt-install命令在主机系统上创建第一个虚拟机。我们将创建一个虚拟机,使用的单个virt-install命令如下(以 root 身份运行):--virt-type: Type of the new VM -
--name:新虚拟机的名称 -
--memory:虚拟机使用的内存量 -
--vcpus:新虚拟机使用的虚拟 CPU 数量 -
--disk size:使用的存储量 -
--os-variant:来宾操作系统的类型 -
--network:使用的桥接网络 -
--cdrom:来宾操作系统 ISO 文件的位置
该命令将启动一个新的 virt-viewer 窗口,开始操作系统安装过程。同样,通过使用带有 –graphics=vnc 参数的命令,virt-install 将启动 virt-viewer,这是使用 VNC 协议显示图形控制台的默认工具。
仅仅知道如何创建虚拟机对系统管理员来说是不够的。这就是为什么在接下来的部分,我们将向您展示一些用于管理虚拟机的基本工具。
基本虚拟机管理
使用 CLI 时,基本的 VM 操作可以通过 virsh 命令完成,或者在使用图形用户界面时,可以使用虚拟机管理器。在接下来的部分,我们将展示在 CLI 中使用的基本命令。
要列出现有的虚拟机来宾,请使用 virsh list 命令:
sudo virsh list
请注意,列出虚拟机并非任何人都可以执行的操作。这就是为什么需要注意以下说明的原因。
重要说明
当尝试列出现有的来宾虚拟机时,普通用户无法获得有效的输出。您需要以 root 用户身份登录,或者使用 sudo 来查看虚拟机列表。
以下截图显示了一些用于管理虚拟机的基本命令及其输出:

图 11.6 – 虚拟机管理命令
以下是对您在前面图中看到的命令的简要说明。要更改虚拟机的状态,例如启动、停止和暂停,可以使用以下命令:
-
sudo virshdestroy ubuntu-vm1 -
sudo virshreboot ubuntu-vm1 -
sudo virshsuspend ubuntu-vm1 -
sudo virshstart ubuntu-vm1 -
sudo virshresume ubuntu-vm1 -
sudo virshundefine ubuntu-vm1
有关 virsh 可用的所有选项,请使用以下命令查看手册页:
man virsh
用于管理虚拟机的命令行工具功能强大,提供了多种选项。如果我们考虑到系统管理员大多数时候会使用 CLI 而不是 GUI,那么能够使用命令行工具就显得尤为重要。
在接下来的部分,我们将向您展示一些高级的 KVM 管理实践。
高级 KVM 管理
使用 KVM 不仅仅是创建虚拟机并启动或停止它们。虚拟机管理可以更加复杂,从虚拟机自动化安装、存储和资源管理,到虚拟机编排。这些话题有些超出了本书的范围,但我们仍然会向您展示如何在 Linux 系统上掌握虚拟机管理。
到目前为止,我们只有一台虚拟机。为了本章节的练习,我们将创建另外两台虚拟机,所有虚拟机都运行与第一台虚拟机相同的 Ubuntu 操作系统。我们将使用以下命令创建 ubuntu-vm2 和 ubuntu-vm3 两台虚拟机:
-
对于
ubuntu-vm2:ubuntu-vm3:sudo virt-install --virt-type=kvm --name ubuntu-vm3 --vcpus=2 --memory=2048 --os-variant=ubuntufocal --cdrom=/var/lib/libvirt/images/ubuntu-22.04.2-live-server-amd64.iso --network=default --disk size=20 --noautoconsole
现在,我们系统上已经运行了三台虚拟机,可以开始管理它们了。在接下来的章节中,我们将向你展示如何找出虚拟机的 IP 地址以及如何连接到虚拟机。
连接到虚拟机
大多数情况下,我们希望从终端连接到正在运行的虚拟机,而不是使用虚拟机管理器提供的集成控制台。为了能够做到这一点,我们需要知道虚拟机的 IP 地址。简单运行 ip neighbor 命令可以显示我们本地网络上的所有 IP 地址,但这并不能提供我们需要的相关信息,比如虚拟机的名称。
在我们的系统上,运行 ip neighbor 命令时,输出如下:

图 11.7 – 查看本地网络上的 IP 地址
从输出中可以看到,三个 IP 地址来自由 KVM 设置的默认虚拟网络(virbr0)。这是我们得知虚拟机使用的 IP 地址的第一条信息。但是哪个 IP 地址对应哪台虚拟机呢?为了找出更多信息,我们将使用以下命令:
sudo virsh list --all
上面的命令用于列出所有现有的虚拟机。输出(如 图 11.8 所示)显示了虚拟机的名称。为了查看与每台虚拟机关联的 IP 地址,我们将使用以下命令:
sudo virsh domifaddr [vm name]
[vm name] 代表 virsh list 命令输出中的虚拟机名称之一。在下图中,你可以看到之前命令的输出:

图 11.8 – 显示虚拟机的 IP 地址
现在我们知道了每台虚拟机的 IP 地址,可以使用 SSH 连接到任何一台虚拟机(关于安装和配置 SSH,请参考 第十三章)。考虑到我们已经在主机系统和目标虚拟机上安装了 openSSH,使用 SSH 连接的最简单方法如下:
ssh packt@192.168.122.129
在之前的命令中,我们使用了 ssh 命令,并指定了用户(在我们的案例中是 packt)和虚拟机的 IP 地址(在我们的案例中是 192.168.122.129,即之前创建的 ubuntu-vm1)。提示(如下图所示)要求你确认,并将密钥保存到已知主机列表中,然后连接到该机器:

图 11.9 – 通过 SSH 连接到虚拟机
另一种连接虚拟机的方法是使用 virt-viewer 命令:
virt-viewer --connect qemu:///system ubuntu-vm1
此命令将使用virt-viewer工具打开一个新的控制台窗口,并连接到您指定的虚拟机(在我们的案例中,仍然是ubuntu-vm1),而无需使用 SSH 协议:

图 11.10 – 使用 virt-viewer 连接虚拟机
重要提示
连接在您发起命令的终端内保持活动状态。因此,如果您按Ctrl + C,连接将被终止,新的控制台窗口将关闭。请注意,只有连接会被终止,而虚拟机仍然会继续运行。
我们已经展示了如何使用命令行创建虚拟机、进行基本管理以及连接虚拟机。然而,您也可以使用 GUI 工具。所有现代 Linux 发行版,如果使用 GNOME 作为桌面环境,都至少会提供两个有用的工具:虚拟机管理器和 GNOME Boxes。前者只是libvirt的 GUI,而后者是基于 QEMU/KVM 技术,在 GNOME 环境中即时创建虚拟机的简易方式。我们将让您自己探索这些 GUI 工具,因为它们非常直观,不难使用。您可以开始使用虚拟机管理器创建新的虚拟机。在下一节中,我们将展示如何克隆虚拟机。
克隆虚拟机
我们已经在主机系统上创建了三个不同的虚拟机。然而,有时您可能希望克隆现有的虚拟机,而不是创建新的虚拟机。
在开始克隆虚拟机之前,我们需要停止或挂起它。我们将使用suspend或shutdown命令来执行此操作。我们将停止其中一台虚拟机,如下所示:
sudo virsh shutdown ubuntu-vm1
此命令将关闭ubuntu-vm1虚拟机。为了克隆它,我们将使用virt-clone命令。假设我们想将克隆命名为ubuntu-vm1-clone1。我们将使用以下命令:
sudo virt-clone --original ubuntu-vm1 --name ubuntu-vm1-clone1 --auto-clone
命令的输出如下所示:

图 11.11 – 使用 virt-clone 克隆虚拟机
现在克隆已经创建,我们可以使用virsh start命令启动它。克隆虚拟机还会传输所有原虚拟机的配置,包括 vCPU 数量、内存、桥接网络配置、相同的 MAC 地址,甚至相同的 IP 地址。这可能会成为一个真正的麻烦,需要解决。
解决此问题的一种方法是直接连接到虚拟机的控制台(不是通过 SSH)并运行ip addr show命令。这将使 DHCP 客户端自动为主机分配 IP 地址。在下一节中,我们将展示另一种使用虚拟机模板管理克隆的有效方法。
创建虚拟机模板
解决上一节中描述的问题的另一种有效方法是在克隆之前先创建虚拟机模板。通过创建模板,您可以确保所有配置文件不会持久化,包括 MAC 和 IP 配置、用户设置或 SSH 主机密钥。
创建模板,请按照以下步骤操作:
-
我们将使用
virt-sysprep工具。在 Debian 12 中,我们将使用以下命令安装包含virt-sysprep的libguestfs-tools工具:sudo apt install libguestfs-tools -
现在工具已经安装好,我们将用它来创建模板。但首先,我们将创建一台运行 Ubuntu 的新虚拟机,并将其用作模板。我们将使用以下命令来创建新虚拟机:
sudo virt-install --virt-type=kvm --name ubuntu-template --vcpus=2 --memory=2048 --os-variant=ubuntufocal --cdrom=/var/lib/libvirt/images/ubuntu-22.04.2-live-server-amd64.iso --network=default --disk size=20 –noautoconsole -
完成操作系统安装后,确保其已更新所有可用的包。
-
只有在确认虚拟机已关闭后才能继续操作。作为预防措施,你可以先将文件复制并更名:
virt-sysprep utility:virt-sysprep命令正在通过重置所有可能已创建的配置文件来准备虚拟机。以下是输出的摘录:

图 11.12 – 使用 virt-sysprep 创建模板
-
现在模板已经准备好,你可以选择以下任一操作:
-
使用
virsh undefine命令取消定义该域。此命令将删除虚拟机的配置,但会保留它创建的qcow2文件,这样你在创建新虚拟机时就可以使用它。 -
保留虚拟机(在我们的例子中是名为
ubuntu-template的虚拟机),并按计划将其用作克隆模板。
选择权在你,但我们倾向于选择第二个选项,因为它已经配置好了,因此使用起来更加简单。当只使用
qcow2文件时,你仍然需要先配置虚拟机(设置 CPU、内存、网络等)才能使用它。 -
现在你已经知道如何克隆虚拟机以及如何创建模板,接下来让我们看看管理虚拟机的其他方法。在下一部分中,我们将向你展示如何从命令行获取你所使用的虚拟机的信息。
获取虚拟机和主机资源信息
当你在命令行工作时,一些信息不像使用 GUI 工具时那样明显。为了查看我们是否还有足够的资源来创建新虚拟机,我们需要使用 virsh nodeinfo 命令来获取主机的信息:

图 11.13 – 使用 nodeinfo 命令查找主机信息
在我们的例子中,如前面的图片所示,主机有 16 个 vCPU 和 48 GB 的 RAM,这意味着我们仍然有足够的资源来创建一些新的虚拟机。我们知道,在创建虚拟机时,我们为每台虚拟机分配了 2 个 vCPU 和 2 GB 的内存。现在我们有五台虚拟机(如下面的图片所示),这意味着我们使用了 16 个 vCPU 中的 10 个和 48 GB RAM 中的 10 GB:

图 11.14 – 列出现有的虚拟机
但如果我们不知道现有虚拟机使用了多少资源呢?有一个命令可以帮助我们,它叫做 virsh dominfo。让我们查看其中一台虚拟机正在使用的资源,例如 ubuntu-vm1:

图 11.15 – 显示虚拟机的资源使用情况
在前面的图像中,您可以看到我们的虚拟机使用了 2 个 vCPU 和 2GB 的 RAM。您可以检查每个虚拟机的资源使用情况。除了 vCPU 和 RAM,您还可以管理现有虚拟机的虚拟磁盘。要查看虚拟机的磁盘使用情况,您可以使用virt-df命令:

图 11.16 – 显示虚拟机的磁盘使用情况
我们使用了-d选项来显示libvirt域客机,使用了-h选项来以人类可读的格式显示结果。virt-df命令类似于df命令(参见 第六章)。
了解资源的使用情况是管理现有资源的第一步。在接下来的部分,我们将向您展示如何更改虚拟机使用的资源量。
管理虚拟机资源使用
如前所示,了解虚拟机使用了多少资源非常重要。如果资源不足,您需要能够修改已经使用的资源。您可以使用工具来修改现有虚拟机使用的 vCPU 和 RAM。例如,我们将把ubuntu-vm1-clone1虚拟机的资源从 2 个 vCPU 更改为 1 个 vCPU,并将 2GB 的 RAM 更改为 1GB 的 RAM。我们将使用的命令是virsh setvcpus,具体操作如下:

图 11.17 – 更改虚拟机的 vCPU 数量
我们还可以使用virsh setmem和virsh setmaxmem命令更改使用的 RAM 数量:

图 11.18 – 更改虚拟机使用的内存
现在,我们可以使用virsh dominfo命令检查ubuntu-vm1-clone1虚拟机的资源,如下图所示:

图 11.19 – 检查虚拟机的资源
如您所见,虚拟机所使用的资源已经根据您的新设置进行了更改。现在,您已经知道如何管理 KVM,这是 Linux 系统管理员必备的技能。在接下来的部分,我们将向您展示如何使用cloud-init自动化 KVM 虚拟机的配置。
使用 cloud-init 配置虚拟机
当您只处理一个虚拟机时,事情可以相对简单。但当我们需要创建数百个虚拟机时,手动创建就变得令人望而生畏。你可以用来执行这类任务的一个有用工具是cloud-init。另一个适合这种任务的工具是Ansible(更多关于 Ansible 的内容请见第十七章)。在本节中,我们只讨论 cloud-init。它是由 Canonical 开发的,用作在云平台上配置虚拟机实例的工具,并且是用 Python 编写的。目前,它被视为云镜像配置的行业标准。在下一小节中,我们将简要解释 cloud-init 的工作原理。
理解 cloud-init 的工作原理
根据官方的 cloud-init 文档,它基于多个配置源、特定的启动阶段、用户数据格式、供应商数据和实例元数据。启动阶段的概念是 cloud-init 架构特有的,因为它在启动过程的特定阶段配置整个实例。它提供了一种管理完全工作的实例的方法,这些实例已经配置了网络、启动顺序和本地配置文件。
Cloud-init 可在大多数常用的 Linux 发行版上使用,例如 Ubuntu、Debian、Red Hat Enterprise Linux、Fedora、SUSE 和 openSUSE。我们将使用其中一台 Debian 12 系统作为主机,并在其上安装和配置 cloud-init。我们将在下一个小节中向您展示如何操作。
安装和配置 cloud-init
即使它的主要目的是用于云部署,cloud-init 也可以在本地使用。我们将使用它在本地系统上部署虚拟机。使用 cloud-init 的前提是您的系统上已经安装了虚拟化管理程序,例如 KVM。对于客户机镜像,cloud-init 使用特定的云镜像,这些镜像几乎可以从每个 Linux 发行版提供商那里获得。例如,我们计划部署 Ubuntu 虚拟机,因此我们需要 Ubuntu 优化的云镜像,可以在cloud-images.ubuntu.com/找到。接下来,让我们看看为部署准备镜像所需的步骤:
-
首先,我们将安装
cloud-image-utils附加包,然后安装cloud-init包:sudo apt install cloud-image-utils cloud-init -
下一步是为新的云镜像创建一个新目录:
mkdir local-cloud-images && cd local-cloud-images -
下一步是下载云镜像:
wget https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img -
然后,我们获取云镜像文件的详细信息:
qemu-img info jammy-server-cloudimg-amd64.img -
接下来,我们调整镜像大小:
.qcow2 base image:libvirt 目录:
meta-data) and another file for user data (called user-data). They are /var/lib/libvirt/image/) to store the new configuration files. We will use the following commands as root:meta-data 文件暂时不会被填充。我们将首先编辑 user-data 文件。
重要提示
在这一点上,我们需要一对 SSH 密钥来与我们计划创建的新虚拟机连接。由于我们还没有展示如何使用 SSH 密钥,我们将在下一小节中提供相关信息。请先阅读使用 SSH 的公钥认证部分,然后返回此处,我们将继续配置我们的 cloud-init 文件。
让我们创建 user-data 文件并添加以下信息:

图 11.20 – user-data 文件内容
-
在完成
user-data文件编辑后,我们可以继续创建一个包含配置文件的磁盘镜像。我们将使用之前安装的cloud-image-utils包中的cloud-localds命令:user-data file we created. The preparations are finished and we can start deploying. We will deploy our VM using the following command:sudo virt-install --name vm01 --virt-type kvm --vcpus 1 --memory 2048 --disk path=/var/lib/libvirt/images/ubuntu.qcow2,device=disk --disk path=/var/lib/libvirt/images/cloud-init/ubuntu-provisioning.qcow2,device=cdrom --os-type linux --os-variant generic --import --network network=default --noautoconsole
-
如果遇到与网络激活相关的错误,可能需要使用以下命令来激活默认网络:
virt-install command, the output will be as shown as follows:

图 11.21 – 创建新的虚拟机
- 我们现在已经使用 cloud-init 部署了一台新虚拟机。可以通过使用
sudo virsh list命令或通过虚拟管理器 GUI 来验证虚拟机是否正在运行。我们将验证虚拟机的运行状态,找出它的 IP 地址并通过 SSH 连接到它。我们将使用以下命令:sudo virsh list查看虚拟机状态,sudo virsh domifaddr vm01查找其 IP 地址,以及ssh packt@192.168.122.32连接到它。输出如下截图所示:

图 11.22 – 使用 SSH 连接到新虚拟机
我们已经成功创建并通过 cloud-init 连接到一台新虚拟机。在完成本节内容后,您现在具备了使用 cloud-init 部署虚拟机的能力。然而,我们仅仅触及了 cloud-init 的表面,因此,如果您希望了解更多内容,请查阅官方文档或本章结尾的 进一步阅读 部分提供的书籍。
在下一节中,我们将展示如何使用 SSH 进行公钥认证。
使用 SSH 进行公钥认证
.ssh 目录位于用户的主目录中。要生成一对新的密钥,您需要使用 ssh-keygen 命令。
它可以与选项一起使用,其中最相关的选项包括:-t 用于指定加密算法类型,-b 用于指定位数。未指定选项时,ssh-keygen 命令将使用 RSA 加密算法和 3,072 位的密钥。以下是直接使用该命令时的输出:

图 11.23 – 使用 ssh-keygen 创建一对 SSH 密钥
如前所述,两个密钥存储在 .ssh 目录中。其中一个名为 id_rsa,另一个名为 id_rsa.pub。对于我们的使用案例——配置 cloud-init,我们将需要使用公钥。因此,我们需要将 id_rsa.pub 文件的内容合并并复制该密钥。在我们的情况下,内容如下:

图 11.24 – SSH 公钥
然而,如果我们需要使用这些密钥连接到云实例的虚拟私人服务器或虚拟机,则需要安全地将公钥复制到该机器或实例。为此,我们将使用 ssh-copy-id 命令。使用该命令时,我们需要提供用户名以及目标机器的 IP 地址或主机名。例如,如果我们需要将 SSH 公钥复制到 IP 为 192.168.122.48,用户名为 packt 的虚拟机,我们将使用以下命令:
ssh-copy-id packt@192.168.122.48
如何安装和配置 SSH 服务器的更多细节将在第十三章中提供。这里展示的信息足以完成我们的 cloud-init 任务。
虚拟化是计算中的一个重要组成部分,它提供了充分利用现代系统所提供的巨大计算能力所需的技术。它使你能够最大化硬件技术的投资回报。
总结
在本章中,我们强调了虚拟化在 Linux 系统中的重要性。我们向你展示了如何使用 KVM 创建和管理虚拟机。你现在知道如何克隆、模板化和管理虚拟机的资源;并且了解虚拟化的工作原理,以及如何在 Linux 上安装 QEMU/KVM 虚拟机监控程序。有了这些技能,你可以无畏地开始你的虚拟化之路。
在下一章中,我们将向你介绍 Docker 容器技术。
练习
这是一个简短的测试,涵盖了本章介绍的一些基本概念:
-
列举并描述虚拟化监控程序的类型。
-
通过在多个 Linux 主机上安装虚拟化监控程序进行实践。
-
验证您的虚拟化监控程序是否正常工作。
virt-host-validate。 -
你能想到主要虚拟化监控程序之间的显著差异吗?
提示:例如,测试 KVM 和 VirtualBox,并进行比较。
-
如何查找虚拟机的 IP 地址?
virshdomifaddr命令。
进一步阅读
要了解本章讨论的主题,您可以参考以下 Packt 图书:
-
掌握 KVM 虚拟化 – 第二版,Vedran Dakic、Humble Devassy Chirammal、Prasad Mukhedkar、Anil Vettathu
-
KVM 虚拟化食谱,Konstantin Ivanov
有关 cloud-init 内部工作原理的详细信息,请访问官方网站:cloudinit.readthedocs.io/en/latest/explanation/index.html。
第十二章:使用 Docker 管理容器
在本章中,你将学习创建和管理容器的最知名工具之一——Docker。本章的内容将为你准备 Linux 的未来,因为它是每种现代云技术的基础。如果你希望在不断变化的技术环境中保持更新,本章将是你旅程的关键起点。
本章我们将覆盖以下主要内容:
-
理解 Linux 容器
-
使用 Docker
-
使用 Dockerfile
-
使用 Docker 部署容器化应用程序
技术要求
本章没有特定的技术要求,只需在系统上安装一个正常工作的 Linux 系统。Ubuntu/Debian 和 Fedora/RHEL 都适合本章的练习。我们将使用 Debian GNU/Linux 12 作为大多数示例,但在合适的地方,我们会讨论 Fedora Linux 安装和使用的具体细节。
理解 Linux 容器
正如我们在上一章中演示的那样,虚拟化有两种主要类型:虚拟机(VM)-基础的和 容器-基础的。在上一章中,我们讨论了基于虚拟机的虚拟化,现在是时候解释容器是什么了。从基本的概念层面来看,容器与虚拟机类似。它们有相似的目的——提供一个隔离的环境来运行——但在许多方面,它们的不同之处让它们几乎无法被称为相似。让我们更详细地比较这两个概念。
容器与虚拟机的比较
如你所知,虚拟机(VM)模拟了机器的硬件,并将其作为多个机器来使用。而容器则不复制物理机器的硬件,它们并不模拟任何东西。
容器与操作系统内核共享基础操作系统,并共享特定应用程序运行所需的库和二进制文件。应用程序被包含在容器内,与系统的其他部分隔离开来。它们还与主机共享网络接口,以提供与虚拟机相似的连接性。
容器运行在 容器引擎 上。容器引擎提供操作系统级别的虚拟化,用于部署和测试应用程序,仅使用所需的库和依赖项。通过这种方式,容器确保应用程序能够在任何机器上运行,并提供开发者预期的相同行为。以下是容器和虚拟机的视觉对比:

图 12.1 – 容器与虚拟机的对比(总体示意图)
正如你所看到的,容器仅使用 用户空间,共享底层的操作系统架构。
从历史上看,容器化已经存在了一段时间。自 1982 年起,Unix 操作系统的 chroot 工具就被用于容器化。
在 Linux 上,一些最新和最常用的工具是 Linux 容器(LXC),其中 LXD 是其更新版和扩展版,推出于 2008 年,而 Docker 则于 2013 年发布。为什么是 LXC/LXD 命名法?因为 LXC 是容器领域中的“老大哥”,LXD 是其更新版和重新设计的版本。
在下一节中,我们将剖析底层容器技术。
了解底层容器技术
如前所述,LXC 是最早的容器形式之一,早在 12 年前就已推出。更新的容器形式,以及改变整个容器领域并引发 DevOps 热潮的容器(更多内容请参见 第十四章),被称为 Docker。容器不像虚拟机监控器那样抽象硬件层。它们使用特定的用户空间接口,利用内核的技术来隔离特定的资源。通过使用 Linux 容器,你可以在不使用不同内核的情况下复制一个默认的 Linux 系统,就像使用虚拟机一样。
当 LXC 首次出现时,它吸引人的地方在于它支持多种编程语言的 API,包括 Python 3、Go、Ruby 和 Haskell。因此,尽管 LXC 现在不再那么流行,但它仍然值得了解。Docker 已经接管了容器引擎的主导地位。我们在示例中不会使用 LXC/LXD,但我们仍会讨论它以确保向后兼容。截止到本书写作时,LXC 支持两个版本,版本 4.0 支持到 2025 年 6 月,版本 5.0 支持到 2027 年 6 月。
根据开发者的说法,LXC 使用特性来创建一个尽可能接近默认 Linux 安装的隔离环境。在它使用的内核技术中,我们可以提到最重要的技术——即 Linux 中任何容器的骨干:内核 命名空间 和 cgroups。除此之外,还有 chroot 和针对 AppArmor 和 SELinux 的安全配置文件。
现在让我们解释 Linux 容器所使用的这些基本特性。
Linux 命名空间
什么是 Linux 命名空间?简而言之,命名空间是内核全局系统资源,负责容器提供的隔离。命名空间将全局系统资源包装在一个抽象层内。这个过程使得运行在命名空间中的应用进程误以为它正在使用的资源是属于它自己的。命名空间在内核内提供逻辑层级的隔离,同时也为正在运行的进程提供可见性。
为了更好地理解命名空间的工作原理,可以想象 Linux 系统中的任何用户以及他们如何查看不同的系统资源和进程。作为一个用户,你可以看到全局系统资源、正在运行的进程、其他用户和内核模块等。这样的透明度在将容器用作操作系统级虚拟化环境时可能是有害的。由于容器无法提供虚拟机的封装和仿真级别,容器引擎必须以某种方式克服这一点,内核的低级虚拟化机制以命名空间和 cgroups 的形式存在。
在 Linux 内核中有几种类型的命名空间,下面简要描述它们:
-
挂载:它们限制单个命名空间内可用文件系统挂载点的可见性,以便该命名空间中的进程可以看到文件系统列表;进程可以拥有自己的根文件系统和不同的私有或共享挂载。
-
Unix 时间共享(UTS):它隔离系统的主机名和域名。
-
进程间通信(IPC):这允许进程拥有自己的 IPC 共享内存、队列和信号量。
-
进程标识:这允许将进程 ID(PIDs)映射,其中 PID 为 1 的进程(进程树的根)可以分出一个新的树,具有自己的根进程;PID 命名空间中的进程只能看到同一 PID 命名空间中的进程。
-
网络:在网络协议层面的抽象;网络命名空间中的进程拥有独立的网络栈、私有网络接口、路由表、套接字和 iptables 规则。
-
用户:这允许映射 UID 和 GID,包括将 UID 为 0 的 root 视为非特权用户。
-
cgroup:cgroup 命名空间中的进程可以看到相对于命名空间根的文件系统路径。
可以使用 Linux 中的lsns命令查看命名空间。以下是该命令输出的摘录:

图 12.2 – 使用 lsns 查看可用的命名空间
在接下来的部分,我们将详细介绍 cgroups,它是容器的第二个主要构建模块。
Linux cgroups
什么是 cgroups?它们的名字来源于控制组,是内核功能,用于限制和管理进程的资源分配。Cgroups 控制内存、CPU、I/O 和网络的使用。它们提供了一种机制,确定特定的任务集,限制进程可以使用的资源量。它们基于层次结构的概念。每个子组都会继承其父组的属性,并且一个系统中可以同时存在多个 cgroups 层次结构。
Cgroups 和命名空间的结合创造了容器所依赖的隔离。通过使用 cgroups 和命名空间,可以为每个容器分别分配和管理资源。与虚拟机相比,容器更轻量且以隔离实体的形式运行。
如前所述,使用的容器有两种类型,LXC 和 Docker。我们已经讨论过 LXC,接下来我们将介绍 Docker 是什么。
理解 Docker
Docker,类似于 LXC/LXD,基于多种技术,其中包括内核命名空间和 cgroups。Docker 是一个用于开发和部署应用程序的平台。Docker 平台为容器提供了安全运行的基础设施。Docker 容器是轻量级实体,直接在主机的内核上运行。该平台提供了创建和管理隔离的容器化应用程序的工具等功能。因此,容器是应用开发、测试和分发的基本单元。当应用程序准备好投入生产并适合部署时,可以将其作为容器或编排服务进行发布(我们将在第十六章中讨论编排,使用 Kubernetes 部署应用程序)。
在以下图示中,我们将展示 Docker 架构如何工作:

图 12.3 – Docker 架构
让我们解释一下前面的图示。Docker 使用 Linux 内核中提供的命名空间和 cgroups,并分为两个主要组件:
- 将
runc和containerd移交给Cloud Native Computing Foundation,使得更多的组织能够参与其中。以下是展示 Docker 架构细节的图示,核心组件——Docker 引擎和容器运行时——在图中有详细展示:

图 12.4 – Docker 架构细节
-
Docker 引擎:该引擎分为dockerd守护进程、API接口和命令行接口(CLI)。Docker 引擎包含 API 接口和 dockerd 守护进程,而容器运行时有两个主要组件——用于命名空间和 cgroups 管理的containerd守护进程和runc。除了前述组件,Docker 还使用许多其他组件来运行和部署 Docker 容器。Docker 采用客户端-服务器架构,工作流程涉及一个主机(即服务器守护进程)、一个客户端和一个注册中心。主机包含镜像和容器(从注册中心下载),客户端提供管理容器所需的命令。
这些组件的工作流程如下:
-
dockerd 守护进程监听 API 请求以管理服务和对象(如镜像、容器、网络和卷)。
-
客户端是用户通过 API 与守护进程交互的方式。
-
镜像存储库用于存储镜像,Docker Hub 是一个公共的注册中心,任何人都可以自由使用。除此之外,还有可以使用的私有注册中心。
以下是 Docker 工作流的图示,展示了客户端组件、API 和守护进程:
-

图 12.5 – Docker 工作流
Docker 对初学者来说可能看起来很难,甚至让人感到束手无策。所有这些相互配合的不同组件、各种新的类型以及特定的工作流程都非常复杂。你读完这一部分后就能理解 Docker 是如何工作的了吗?当然不能。学习 Docker 的过程才刚刚开始。拥有一个坚实的基础以构建你的 Docker 知识是至关重要的。这就是为什么在接下来的部分,我们将向你展示如何使用 Docker。
使用 Docker
本节的练习将使用安装在一台具有 2 个虚拟 CPU 和 2 GB 内存的虚拟机上的 Debian GNU/Linux 12。但在开始安装 Docker 之前,我们先详细了解一下 Docker 作为一个实体是如何运作的,以帮助我们确定选择哪个版本适合我们的使用场景。
选择哪个 Docker 版本?
为了确保业务的可行性,Docker 背后的公司(即 Docker 公司)提供了一系列围绕其主要产品 Docker 的产品。过去,它有两个不同的产品版本,分别是 Docker Community Edition(CE)和 Docker Enterprise Edition(EE)。在这两者中,只有 EE 版本负责 Docker 的收入。
最近,Docker 产品组合发展出了不同的产品和服务,如Docker Personal、Docker Pro、Docker Team 和 Docker Business。其中,只有 Docker Personal 可以免费使用;其他三个产品则是基于订阅的。Docker Personal 适合个人开发者、教育和开源社区使用,但有一些限制(可以在这里查看:www.docker.com/products/personal/)。使用 Docker Personal 意味着需要拥有一个 Docker 用户账户,并且它包括适用于 Linux(以及所有其他主要平台)的 Docker Desktop 和服务器端的 Docker Engine。
在服务器端,Docker Engine 提供了 .deb 和 .rpm 包格式,适用于 Ubuntu、Debian、Fedora 和 CentOS。在我们的示例中,我们将使用 Docker Personal。在接下来的部分,我们将展示如何安装 Docker。
安装 Docker
根据你选择的 Linux 发行版版本,官方仓库中的包可能会过时。不过,你有两个选择:一个是使用我们 Linux 发行版自带的官方包,另一个是从 Docker 官方网站下载最新版本。
由于我们使用的是全新的系统,且没有之前安装 Docker,因此无需担心旧版本软件和新版本可能存在的不兼容问题。我们将使用 Docker 的 apt 仓库,这样可以确保我们的软件包版本始终是最新的。请记住,我们使用的是 Debian 主机。
安装 Docker 的过程如下:
-
首先,使用以下命令添加 Docker 仓库所需的证书:
sudo apt update -y && sudo apt install ca-certificates curl gnupg -
为了使用官方 Docker 仓库,您需要添加 Docker 的 GPG 密钥。为此,使用以下命令:
sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod a+r /etc/apt/keyrings/docker.gpg -
设置安装我们想要的 Docker 版本所需的仓库:
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null -
下一步是更新仓库列表。更新后,您应该看到官方 Docker 仓库。使用以下命令:
sudo apt update -y -
安装 Docker 软件包。对于我们的情况,我们将使用以下命令安装可用的最新软件包:
sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -
要验证您是否从官方 Docker 仓库而非 Debian 仓库安装了软件包,请运行以下命令:
apt-cache policy docker-ce如果输出显示源来自 docker.com 网站,这意味着源仓库是官方的 Docker 仓库:

图 12.6 – 验证源仓库
-
检查 Docker 守护进程的状态。它应该在安装完成后立即启动:
docker is created. In order to be able to use Docker, your user should be added to the docker group. The existing groups in Linux are inside the /etc/group file. You can list the last lines (new groups are appended at the end of the file) to see the docker group as the last one created:tail /etc/group
You can either add your existing user or create a new one. We will add our already existing user. Add the user with the following command:sudo usermod -aG docker ${USER}
-
添加用户后,请注销并重新登录,然后使用以下命令检查您是否已被添加到新组中:
groups -
您已完成 Docker 的安装。现在可以启用 Docker 守护进程,使其在系统启动时自动启动:
sudo systemctl enable docker
安装 Docker 只是第一步。接下来让我们探索一下我们可以用它做什么。在接下来的部分,您将了解 Docker 中可用的命令。
使用一些 Docker 命令
使用 Docker 就是要使用它的命令行接口(CLI)。它有大量可用的子命令。如果您想查看所有命令,应该运行docker --help命令。显示的命令分为两大类:
-
第一组显示了管理命令
-
第二组显示了常规命令
本节中我们不会讨论所有命令,我们将只关注一些您入门 Docker 所需的命令。
在学习任何命令之前,我们先做一个测试,看看安装是否成功。我们将使用 docker run 命令检查是否能够访问 Docker Hub 并运行容器。我们的测试命令如下:
sudo docker run hello-world
此命令从 Docker Hub 下载一个镜像并将其作为容器运行。以下是输出的截图:

图 12.7 – 运行第一个 docker run 命令
上面的截图不言自明,这是 Docker 团队做得非常好的一个方面。它使用清晰易懂的语言解释了命令在后台执行的操作。通过运行 docker run 命令,你不仅了解了工作流,还能验证安装是否成功。同时,这也是你将经常使用的基本 Docker 命令之一。
现在,让我们更深入地挖掘,搜索 Docker Hub 上可用的其他镜像。我们来搜索一个可以用来运行容器的 Ubuntu 镜像。为了搜索该镜像,我们将使用 docker search 命令:
docker search ubuntu
该命令的输出应列出 Docker Hub 中所有可用的 Ubuntu 镜像:

图 12.8 – 搜索 Ubuntu 镜像
如你所见,输出有五列:
-
NAME:第一列显示镜像的名称
-
DESCRIPTION:第二列显示镜像的描述,即简短的文本信息,介绍特定镜像的内容
-
STARS:第三列显示该镜像的星级数(基于用户评价的流行程度)
-
OFFICIAL:第四列显示该镜像是否为公司支持的官方镜像
-
AUTOMATED:第五列显示该镜像是否包含自动化脚本
一旦找到你需要的镜像,可以使用 docker pull 命令将其下载到你的系统上。让我们从前面截图中显示的列表中下载第一个镜像,即 ubuntu。我们将使用以下命令:
docker pull ubuntu
使用此命令,ubuntu 镜像将被下载到你本地计算机上。现在,可以使用此镜像来运行容器。要列出已经在计算机上可用的镜像,可以运行 docker images 命令:

图 12.9 – 运行 docker images 命令
请注意 Ubuntu Docker 镜像的体积较小。你可能会想,为什么它这么小?这是因为 Docker 镜像仅包含运行所需的基础和最小软件包。这使得基于该镜像运行的容器在资源使用上非常高效。
本节中展示的几个命令是使用 Docker 所需的最基础命令。现在你已经知道如何下载镜像,让我们来看看如何管理 Docker 容器。
管理 Docker 容器
在本节中,我们将学习如何运行、列出、启动、停止和删除 Docker 容器,并且如何管理网络。
运行容器
我们将使用刚刚下载的 Ubuntu 镜像。为了运行它,我们将使用带有两个参数的 docker run 命令,-i 表示交互式输出,-t 表示启动伪 TTY,这将为我们提供交互式访问 shell:
docker run -it ubuntu
你会注意到命令提示符会发生变化。现在它会包含容器的 ID。默认情况下,用户是 root 用户。基本上,你现在处于一个 Ubuntu 镜像中,因此你可以像使用任何 Ubuntu 命令行一样使用它。你可以更新仓库,安装所需的应用程序,删除不必要的应用程序,等等。你对容器镜像所做的任何更改都会保留在容器内部。要退出容器,只需输入exit。现在,我们将向你展示如何列出容器,但在此之前,我们建议你不要关闭当前运行 Ubuntu 容器的终端。
列出容器
你可以在系统中打开一个新的终端,并使用docker ps命令检查当前活跃的 Docker 容器数量。
在命令的输出中,你将看到正在另一个终端中运行的容器的 ID。还会有关于容器内运行的命令和创建时间的详细信息。
docker ps命令有几个参数可以使用:
-
如果你想查看所有活动和非活动的容器,可以使用
docker ps -a命令。 -
如果你想查看最近创建的容器,可以使用
docker ps -l命令。
以下是三种变体的docker ps命令的输出:

图 12.10 – 使用 docker ps 命令列出容器
在输出中,你还会看到容器的名称,如amazing_hopper或recursing_murdock。这些是守护进程自动分配给容器的随机名称。现在,我们将学习如何启动、停止和删除正在运行的容器。
启动、停止和删除运行中的容器
在管理容器时(例如启动和停止容器),你可以通过容器的 ID 或 Docker 分配的名称来引用它们。现在,让我们展示如何启动、停止和删除容器。
要启动 Docker 容器,使用docker start命令,然后输入容器的名称或 ID。以下是一个例子:
docker start amazing_hopper
要停止容器,使用docker stop命令,然后输入容器的名称或 ID。以下是一个例子:
docker stop amazing_hopper
在我们的案例中,名为amazing_hopper的 Ubuntu 容器已经在运行,因此start命令不会做任何事情。但stop命令会停止容器。停止后,如果你运行docker ps命令,容器列表中将不再显示任何容器。
让我们来看看这两个命令的输出:

图 12.11 – 启动和停止容器
要删除一个容器,可以使用docker rm命令。例如,如果我们想删除初始的hello-world容器(在我们的案例中也叫做recursing_murdock),我们将使用以下命令:
docker rm recursing_murdock
一旦删除容器,所有未保存(提交)的更改将会丢失。让我们向你展示如何将容器中所做的更改提交到 Docker 镜像。这意味着你将保存容器的特定状态为新的 Docker 镜像。
重要提示
请注意,删除容器不会删除从 Docker Hub 下载的现有镜像。
假设你想在 Ubuntu 上开发、测试和部署一个 Python 应用程序。Ubuntu 的默认 Docker 镜像没有安装 Python。
接下来,我们将向你展示如何排查 Docker 网络问题以及如何提交新镜像。
Docker 网络与提交新镜像
本节练习的场景是,你希望通过安装你应用程序所需的 Python 包来修改现有的 Ubuntu 镜像。为此,我们按以下步骤操作:
- 首先,我们启动容器并检查是否安装了 Python:

图 12.12 – 在容器内检查 Python
-
我们检查了 Python 2 和 Python 3,但镜像中都没有安装任何版本。由于我们想使用最新版本的编程语言,我们将使用以下命令来安装 Python 3 支持(以 root 身份运行):
apt install python3这样做,你将第一次接触 Docker 网络,因为容器需要连接到官方 Ubuntu 软件源,以便下载和安装你所需要的包。对你来说,也可能出现与我们类似的情况:当尝试安装 Python 时,你会遇到如下截图中的错误:

图 12.13 – 尝试安装 Python 时的错误
这个错误显示包名为 python3 的软件包无法找到,这意味着我们的容器无法访问到软件源。一个快速的想法是可能 Docker 的网络出现了问题。为了解决这个问题,我们有一个有用的命令叫做 docker network。它用于管理 Docker 容器的网络连接。在我们的例子中,错误消息的原因可能是容器与网络之间缺少连接。在这种情况下,我们可以首先使用 docker network 的 ls 命令进行排查:
docker network ls
此命令将显示 Docker 正在使用的所有活动网络。在我们的例子中,当运行前述命令时,我们可以看到 Docker 有三个可用的网络,每个网络都有一个网络 ID 和分配给它的名称:

图 12.14 – 显示可用的网络
重要提示
本例中描述的问题可能不会出现在你的情况中。然而,这是一个很好的练习,可以向你展示如何在实际操作中使用 docker network 命令。
-
在开始解决问题之前,让我们再一次查看正在运行的容器以及它们的名称。由于我们重新启动了新的容器,它应该有另一个 ID 和名称。我们将使用以下命令:
docker ps command) to the bridge network. This will be done with the following command:docker network connect bridge [container_name]
-
运行该命令不会显示任何输出,但我们将通过运行该命令来更新容器内的仓库,以验证它是否有效:
apt update -y命令的输出显示,仓库可以从我们的容器访问,这意味着我们现在可以安装 Python。以下截图显示了输出的摘录:

图 12.15 – 网络连接正常工作的证明
通过将容器连接到网络,它将能够与同一网络上的其他容器进行通信。
-
现在,在进一步进行 Python 安装之前,我们想向您展示一个命令,您可以使用它在启动容器时自动将容器连接到网络:
bridge) and the container’s name. We used the -i option for interactive output, the -t option for opening a pseudo TTY, and the -d option for detaching the container and running in the background. -
现在,让我们继续进行 Python 的初步安装。我们可以再次运行以下命令:
apt install python3这次,命令不会再出现任何错误,它将继续进行安装。以下是命令输出的摘录:

图 12.16 – 在 Docker 容器内安装 Python 包
-
现在,安装了 Python 3 并对容器内使用的镜像进行了必要的修改后,我们可以将容器的实例保存为一个新的 Docker 镜像。为此,我们将使用以下命令:
-m option to add a comment that details our commit process and the -a option to specify the account user and the ID of the base image we used, in our case, the ID of the running container. The following screenshot shows a series of commands to help you to understand the process better:

图 12.17 – 本地提交的新图像
如前面的截图所示,我们首先使用 docker ps 命令查看正在运行的容器的 ID,然后使用 docker commit 命令(配合前述选项)将新镜像保存在本地。请注意,我们刚保存的镜像大小有所增加。安装 Python 3 使得最初的 Ubuntu 镜像大小增加了一倍多。最后使用的命令是 docker images,查看现有镜像,包括我们刚创建的镜像(ubuntu-python3)。
到现在为止,您已经学会了如何使用非常基本的 Docker 命令来打开、运行和保存容器。在下一节中,我们将向您介绍 Dockerfile 及构建容器镜像的过程。
使用 Dockerfile
在开始使用 Dockerfile 之前,让我们看看什么是 Dockerfile。它是一个文本文件,其中包含用户为 Docker 执行的指令,并遵循一些基本结构,如下所示:
INSTRUCTION arguments
Dockerfile 主要用于创建新的容器镜像。Docker 会根据用户在文件中提供的信息自动构建镜像。Dockerfile 中有一些定义关键字,这些关键字被称为指令,如下所示:
-
FROM:此指令必须是 Dockerfile 中的第一个指令,因为它告诉 Docker 你基于哪个镜像来构建。 -
LABEL:此指令添加更多信息,如描述或其他能够帮助描述你创建的新镜像的内容;此类指令的使用应加以限制。 -
RUN:这是与镜像进行直接交互的指令,编写在镜像内部运行的命令或脚本的位置。 -
ADD:此指令用于将文件传输到镜像内部;它将文件或目录复制到镜像的文件系统中。 -
COPY:此指令类似于ADD,也用于将文件或目录从源位置复制到镜像的文件系统中。 -
CMD:此指令在 Dockerfile 中只能出现一次,因为它为执行镜像时提供默认设置。 -
USER:此指令用于设置在执行命令时所使用的用户名;它可以在RUN或CMD指令中使用。 -
WORKDIR:此指令将为 Dockerfile 中的其他指令(如RUN、CMD、COPY、ADD或ENTRYPOINT)设置默认工作目录。 -
ENTRYPOINT:此指令用于配置作为可执行文件运行的容器。
这里列出的指令只是 Dockerfile 中通常使用的指令,并不代表所有可用的指令。你可以访问 docs.docker.com/engine/reference/builder/ 获取所有可用 Dockerfile 指令的完整列表。在下一节中,我们将展示如何使用 Dockerfile 构建容器镜像。
从 Dockerfile 构建容器镜像
在本节中,我们将创建一个用于构建新 Docker 容器镜像的 Dockerfile。让我们展示一下构建此练习的场景。与 Docker 网络和提交新镜像 部分中的练习类似,我们将为 Python 编程环境准备一个镜像。为了创建新的 Docker 镜像,我们首先需要创建 Dockerfile。以下是操作步骤:
-
使用以下命令在主目录内创建一个新目录:
mkdir ~/my_docker_images && cd ~/my_docker_images -
在新目录内创建一个新文件:
docker search debian command, the second image name on the output list will be the official Debian Linux image, called debian. We will use that. The contents of the Dockerfile are shown in the following screenshot:

图 12.18 – 创建 Dockerfile
-
现在 Dockerfile 已创建,我们将运行
docker build命令来创建新的 Docker 镜像。我们使用的命令如下:-f option to specify the Dockerfile name and the -t option to specify the name of the image we want to create, in our case pydeb. In the next screenshot, you will see the output of the docker build command, showing all the steps needed to build the image, as specified in the Dockerfile. The build was successful:

图 12.19 – 从 Dockerfile 构建新自定义镜像
我们可以通过使用 docker images 命令验证镜像是否已创建。正如前面的截图所示,新的 pydeb 镜像已成功创建。
-
我们可以使用新的镜像并通过以下命令创建一个新的容器:
pydeb images we just created. -
要验证容器是否在运行,打开一个新的终端窗口并运行
docker ps命令,如下所示:

图 12.20 – 基于我们自定义镜像的新容器
到现在为止,你已经对 Docker 有了足够的了解,足以在生产环境中自如地使用它。在接下来的章节中,我们将向你展示如何使用 Docker 部署一个非常基础的应用程序。我们将它做得非常简单,部署的应用将是一个基础的静态展示网站。
使用 Docker 部署容器化应用程序
到目前为止,我们已经向你展示了如何使用 Docker 以及如何管理容器。Docker 的功能远不止这些,但这些足以让你入门,并激发你进一步学习的兴趣。Docker 对开发者来说是一个极好的工具,因为它提供了一种简化的方式来部署应用程序,省去了复制开发环境的必要。在接下来的章节中,我们将展示如何使用 Docker 部署一个简单的网站。
使用 Docker 部署网站
要使用 Docker 部署网站,请按照以下步骤操作:
-
我们将使用一个从互联网上随机下载的免费网页模板(下载链接是
www.free-css.com/free-css-templates/page262/focus))。我们将从网站复制下载位置,并使用wget工具将文件下载到我们的主目录中:unzip command:将 docker_webapp 放入前一章节中创建的 ~/my_docker_images 目录中,并将提取的文件移动到其中。因此,在我们的案例中,新的位置将是:
webapp_dockerfile inside our present working directory. The contents of the Dockerfile are as follows:

图 12.21 – 新 Dockerfile 的内容
文件很简单,只有两行:
-
第一行,使用
FROM关键字,指定了我们将使用的基础镜像,它将是 Docker Hub 上提供的官方 NGINX 镜像。正如你将在 第十三章 中看到的那样,NGINX 是一种广泛使用的网页服务器类型。 -
第二行,使用
COPY关键字,指定了当前工作目录中的内容将被复制到新容器中的位置。
以下操作使用 docker build 命令构建 Docker 镜像:
docker build ~/my_docker_images/docker_webapp/focus -f webapp_dockerfile -t webapp
- 新的镜像已创建,因此我们现在可以使用
docker images命令检查它。在我们的情况下,输出如下:

图 12.22 – 新的 webapp Docker 镜像已创建
-
如输出所示,新的镜像
webapp已被创建,我们可以使用它启动一个新的容器。由于我们需要从外部访问该容器,因此我们需要打开特定的端口,这可以通过在docker run命令中使用-p参数来实现。我们可以指定单个端口或端口范围。在指定端口时,我们将同时为容器和主机指定端口。我们将使用-d参数来分离容器并在后台运行它。命令如下:docker run -it -d -p 8080:80 webapp输出如下:

图 12.23 – docker run命令的输出
我们将主机端口8080暴露到容器的端口80。我们本可以使用80端口,但在主机上,这个端口可能被其他服务占用。
- 现在,你可以通过访问网页浏览器并在地址栏中输入本地 IP 地址和端口
8080来访问新的容器化应用。由于我们使用的是虚拟机而不是主机,我们将指向虚拟机的 IP 地址,在我们的案例中是192.168.122.48,后面跟着8080端口。在下一个截图中,你将看到我们通过 Docker 部署的网站:

图 12.24 – 在我们的网页浏览器中运行 Web 应用
如你在前面的图片中看到的,网站可以通过 localhost 访问。要在虚拟私人服务器上部署网站,请访问第十三章。
摘要
在本章中,我们强调了容器化的重要性。我们向你展示了容器是什么,它们如何工作,以及它们为什么如此重要。容器是现代 DevOps 革命的基础,而你现在已经准备好使用它们了。我们还向你介绍了 Docker 及其基本命令,以便你能高效地使用它。现在,你已经准备好开始云计算之旅。虚拟化和容器技术是云计算和服务器技术的核心。
在下一章中,我们将向你展示如何安装和配置不同的基于 Linux 的服务器,如 Web 服务器、DNS 服务器、DHCP 服务器和邮件服务器。
问题
下面是一个简短的小测验,涵盖了本章中讨论的一些基本概念:
-
容器和虚拟机之间的主要区别是什么?
提示:重新查看图 12.1。
-
容器技术是如何工作的?
-
Docker 架构的两个主要组件是什么?
-
哪个 Docker 命令显示正在运行的容器?
-
哪个命令用于容器网络管理?
docker network命令的帮助。
进一步阅读
有关本章所涉及主题的更多信息,你可以参考以下 Packt 出版的书籍:
-
Docker 快速入门指南,Earl Waud
-
深入掌握 Docker(第四版),Russ McKendrick
-
使用 LXC 进行容器化,Konstantin Ivanov
-
开发者的 Docker Compose 必备指南,Emmanouil Gkatziouras
第十三章:配置 Linux 服务器
在本章中,你将学习如何设置不同类型的 Linux 服务器。这将包括域名系统(DNS)服务器、动态主机配置协议(DHCP)服务器、Samba 或 服务器消息块/通用互联网文件系统(SMB/CIFS)文件服务器,以及 网络文件系统(NFS)服务器。这些服务器在某种程度上都为万维网的骨干提供支持。虽然我们在这里不讨论,但你应当知道,你的计算机显示准确时间的原因是因为有一个良好实现的 网络时间协议(NTP)服务器。你能够进行在线购物和与朋友、同事之间的文件传输,得益于高效的 DHCP、Web 和文件服务器。配置这些为服务器提供动力的各种 Linux 服务,是任何 Linux 系统管理员的知识基础。在本书的本版本中,我们只会涵盖其中的一些 Linux 服务器,这些是我们认为当前最重要的主要 Linux 服务器。如需进一步了解,请参考本章末尾的进一步阅读部分。
本章将涵盖以下主要内容:
-
Linux 服务介绍
-
设置 SSH 服务器
-
设置 DNS 服务器
-
设置 DHCP 服务器
-
设置 NFS 服务器
-
设置 Samba 文件服务器
技术要求
需要具备基本的网络知识和 Linux 命令知识。你需要访问多个正常工作的系统,最好是本地系统或云端系统。如果这不可能,你也可以在本地使用虚拟机。此外,拥有一个可以使用的域名会很有帮助。本章的练习和示例将使用 Ubuntu Server 22.04.2 LTS 版本。但无论是 Fedora、RHEL、openSUSE 还是 Debian 等其他主要 Linux 发行版,都同样适用于本章所涉及的任务。
介绍 Linux 服务
你到目前为止所学的内容,能够轻松应用到任何运行 Linux 的工作站或台式机/笔记本上。我们已经深入探讨了一些高级网络主题,旨在帮助你顺利学习,成为一名资深的 Linux 系统管理员。接下来,我们将进入服务器领域,这是通向云计算的自然路径,后者将在本书的最后四章中详细讨论。
Linux 服务器与 Linux 工作站相比,是一个通过网络提供内容的系统。在此过程中,服务器为访问它的不同客户端提供硬件和软件资源。例如,每次你在浏览器中输入一个网站地址时,都会访问到一个服务器。那种类型的服务器是 Web 服务器。当你在工作场所通过网络打印时,你访问的是 打印服务器,而当你查看电子邮件时,你访问的是 邮件服务器。这些都是专门的系统,运行特定的软件(有时称为服务),为你这个客户端提供你所请求的数据。通常,服务器是非常强大的系统,拥有大量可供客户端使用的资源。
相比之下,工作站(这是另一种强大的硬件)通常用于个人工作,而不是用于网络上的客户端访问。工作站用于高强度工作,类似于任何普通的桌面或笔记本系统。考虑到到目前为止我们所揭示的一切,本章及后续章节的内容最适合服务器使用,但并不限于此。
你可能已经听说过设置不同的 Linux 服务器——比如 Web 服务器、文件服务器或邮件服务器——并且可能会想知道它们为什么会被这样称呼。它们并不代表实际的硬件服务器,而是基本上在 Linux 上运行的服务。那么,什么是 Linux 服务呢?这些是运行在后台的程序。在 Linux 世界中,这些服务被称为 init 进程,在我们讨论了进程、守护进程和信号,并且讲解了如何在 Linux 上管理它们时,这些内容出现在第五章《处理进程、守护进程和信号》中。所有进程的“母进程”是 init 进程,它是 Linux 启动时最先运行的进程之一。目前,Ubuntu(以及 CentOS、Fedora、openSUSE 等)的最新版本使用 systemd 作为默认的 init 进程。
我们将通过使用一些基本命令来刷新你对 Linux 上服务操作的记忆。如果你想了解更多信息,请参考第五章《处理进程、守护进程和信号》。
我们首先提醒你使用的命令是 ps 命令。我们将使用它来显示正在运行的 init 进程,如下所示:
ps -ef | less
我们在 Ubuntu 22.04.2 系统上的输出如以下截图所示:

图 13.1 – 使用 ps 命令显示 init 进程
通过使用 -e 标志,我们可以生成关于所有进程的信息,排除与内核相关的进程,而 -f 标志用于生成完整的进程列表。
在显示的进程列表中,第一个进程是 init 进程或 systemd。有时,在较旧的操作系统(如 Ubuntu 20.04 或 18.04)中,它会使用 init 名称以兼容旧版,但为了确保它确实是 systemd,你可以查看 init 的手册页以获取更多信息。当你在命令行输入 man init 时,显示的手册页是关于 systemd 的。作为所有服务的父进程,systemd 会并行启动所有运行中的进程,从而提高启动过程和服务响应的效率。要查看这些进程的效率,你可以运行 systemd-analyze 命令。
我们经常使用的一个命令,也是你应该已经知道的命令,是 systemctl 命令,它是用于在 Linux 上管理 systemd 服务(或守护进程)的主要命令行工具。
正如你所知道的,systemctl 调用的是 systemd。这些单元有多种类型,如服务、挂载、套接字等。要查看这些单元按启动所需时间的排序,可以使用 systemd-analyze blame 命令,如下图所示:

图 13.2 – systemd-analyze blame 命令
输出显示了不同类型的单元,如 service、mount 和 device。前面的截图仅是正在运行的单元的一部分。要了解更多关于 systemctl 命令的信息,可以随时查阅手册页,或者回到第五章刷新记忆。
这段简短的 Linux 服务介绍仅仅是第五章中相关部分的复习,足以帮助你开始着手配置和设置特定的 Linux 服务。在接下来的章节中,我们将向你展示如何管理 Linux 上一些最重要的服务,如 SSH、DNS、DHCP、NTP、Samba、NFS、web、文件传输协议(FTP)和 打印服务。现在,是时候卷起袖子,自己动手配置这些服务了。首先,我们将向你展示如何在 Ubuntu Linux 上设置 SSH 服务器。
设置 SSH
我们将在运行 Ubuntu Server 22.04.2 LTS 作为主操作系统的计算机上配置 SSH。在整本书中,我们多次使用 SSH 连接,并且在我们处理 cloud-init 时,在第十一章《与虚拟机的工作》一节中展示了如何创建 SSH 密钥对。这次,我们将向您展示如何安装OpenSSH,如何启用 SSH,以及如何修改其一些默认配置。
在 Ubuntu 上安装和配置 OpenSSH
为了使用 SSH,首先我们需要安装 openssh 包。在 Ubuntu 上,可以使用以下命令完成此操作:
sudo apt install openssh-server
该命令很可能已经安装在你的系统上。如果是这样,你可以跳过这一步,直接进入配置文件部分。
安装完成后,我们可以使用以下命令启动并启用openssh服务:
sudo systemctl enable ssh && sudo systemctl start ssh
对于 openSSH,我们可以操作的配置文件位于/etc/ssh/sshd_config。默认情况下,这个文件已经包含了很多信息,我们需要做的就是用文本编辑器打开它并开始修改可用的选项。根据我们要实现的目标,SSH 的基本最小配置包括以下内容:
-
修改远程根用户登录选项;这可以通过修改以下代码所在的行并相应设置选项来完成:
PermitRootLogin no在我们的案例中,我们将默认的
prohibit-password选项修改为no,以使得根用户无法通过 SSH 连接。 -
禁用 SSH 密码认证。可以通过将以下行更改为
no来完成:PasswordAuthentication no PermitEmptyPasswords no只有在你将密钥对复制到远程机器并确保可以使用它之后才执行此操作。否则,你将无法访问你的服务器。
-
要允许公钥认证,你需要取消注释以下行:
PubkeyAuthentication yes -
我们将快速展示在第十一章中使用的命令,以启用公钥认证,作为提醒:
ssh-keygen user, use the username from the remote server, and instead of host_IP, use the IP address of the remote server. Also, ssh-keygen can be used with several options, such as -b to specify the number of bits and -t to specify the algorithm type. After this, you can connect with the ssh user@IP command, which in our case is the following:ssh packt@192.168.0.113
这里展示的配置选项是开始使用 SSH 时的基本最小配置,适用于远程系统或虚拟机。但是,OpenSSH 是一个功能非常强大的工具,提供了很多你可以探索的选项。以下是两个可能对你有所帮助的链接:ubuntu.com/server/docs/service-openssh和www.openssh.com/manual.html。在接下来的部分中,我们将展示如何设置 DNS 服务器。
设置 DNS 服务器
最广泛使用的 DNS 服务之一是伯克利互联网域名系统 9(BIND 9)。你可以访问其官方网站:www.isc.org/bind/。在继续之前,让我们强调系统配置和目标。对于本节内容,我们将使用一台运行 Ubuntu Server 22.04.2 LTS 的计算机。在这台系统上,我们将创建两种类型的服务器,分别是缓存名称服务器和主名称服务器,你可以在本地网络上使用它们来管理主机名和私有 IP 地址。
重要提示
DNS 服务器有不同的类型,比如 权威、缓存 或 转发 类型;这些也称为功能类型。在这些类型中,缓存 DNS 服务器是始终响应客户端递归请求的服务器。此外,还有 关系服务器类型,如主 DNS 服务器和从 DNS 服务器。这些是权威类型,它们几乎相同,唯一的区别是主服务器和从服务器获取区域信息的位置不同。有关 DNS 的更多信息,你可以参考以下链接:www.digitalocean.com/community/tutorials/a-comparison-of-dns-server-types-how-to-choose-the-right-dns-configuration 和 www.digitalocean.com/community/tutorials/an-introduction-to-dns-terminology-components-and-concepts。
还有其他方法可以实现这一点,但为了向你展示 DNS 设置的基本原理,这个配置已经足够。如果你需要一个辅助服务器,你需要另一个备用系统,或者如果你使用 虚拟私人服务器(VPS),它们必须位于同一数据中心,并使用相同的私人网络。然而,在我们的案例中,我们将使用一个本地系统,在我们的小型私人网络中。
首先,我们将通过以下命令在 Ubuntu 中安装 bind9 包:
sudo apt install bind9 bind9utils bind9-doc
上述命令将安装运行 BIND9 所需的所有包。
一旦包安装完成,你可以测试它们以确认 BIND 是否按预期工作。为此,我们将使用 nslookup 命令,如下图所示,使用本地地址(或 环回 地址):

图 13.3 – 使用 nslookup 检查 BIND 是否工作
现在你可以开始设置服务,因为你可以看到它正在工作。首先,我们将配置一个缓存 DNS 服务。但在开始之前,我们建议你备份以下配置文件:/etc/bind/named.conf、/etc/bind/named.conf.options、/etc/hosts 和 /etc/resolv.conf。让我们来看看如何创建一个缓存服务器。
缓存 DNS 服务
BIND9 的默认行为是作为一个缓存服务器。这意味着设置它非常简单。我们只需稍微调整配置文件,以便它按照我们的需求工作:
-
在确认已安装的包能正常工作后,你还可以使用以下命令配置防火墙,以允许 BIND9 通过:
/etc/bind/named.conf.options file to add or delete different options. We will do that by opening it with the text editor. Inside the file, by default, are a few settings that have been set up, and a lot of commented lines with details on how to use the file. The // double slashes indicate that the respective lines are commented out. All the modifications will be done inside the options directive, between curly brackets, as it is the only existing directive by default.The first thing to do, as we will only use **IP version 4** (**IPv4**), is to comment out the following line by adding two slashes:// listen-on-v6 { any; };
-
你需要在
forwarders指令中添加一组 IP 地址。这一行告诉服务器在哪里查找以找到没有被本地缓存的地址。为了简化操作,我们将添加 Google 公共 DNS 服务器,但你也可以自由地将你的forwarders指令写成如下形式:forwarders { 8.8.8.8; 8.8.4.4; }; -
你还可以添加一条指令来定义
allow-query范围。这一行告诉服务器哪些网络可以进行 DNS 查询。你可以添加本地网络地址。在我们的案例中,它将是以下内容:allow-query { localhost; 192.168.0.0/24; }; -
还有一条
listen-on指令,你可以在其中指定 DNS 服务器将为哪些网络工作。这适用于 IPv4 地址,示例如下:listen-on { 192.168.0.0/24; }在添加选项后,你添加的代码应如下面的截图所示:

图 13.4 – 添加新选项后的/etc/bind/named.conf.options 最终格式
-
保存文件并退出编辑器。你可以使用
named-checkconf命令检查 BIND9 配置。如果没有输出,说明文件的配置是正确的。重新启动 BIND9 服务,并可选择检查其状态。 -
知道 BIND9 服务正常运行后,你可以通过任何其他网络上的计算机使用
nslookup命令来测试该服务,如下所示:

图 13.5 – 测试 BIND9 实现
我们使用了带有主机 IP 地址的命令,如前面的截图所示。输出结果显示,我们的测试机器上的 DNS 服务运行正常。该测试是通过同一网络上的本地 ThinkPad 进行的。
现在你已经有了一个在私有网络上工作的缓存 DNS 服务器。在下一节中,我们将展示如何创建主 DNS 服务器。
创建主 DNS 服务器
为了配置主 DNS 服务器,我们需要一个它将服务的域名。在本节中,我们将使用calcatinge.ro域名(当你在自己的系统上尝试时,请使用你拥有的域名)。我们需要为 BIND9 配置创建新区域,并将我们创建的区域信息添加到/etc/bind/named.conf.local文件中。现在,我们将为calcatinge.ro域名创建一个新的区域。
重要提示
DNS 区域是什么?简短的回答是,它是与负责维护它的实体相关联的域名空间的一部分。区域还提供了一种更细致的管理不同组件的方法。有关 DNS 区域的更多信息,请参考以下链接:ns1.com/resources/dns-zones-explained。
在以下截图中,你可以看到我们配置文件的内容,随后是每一行的详细说明:

图 13.6 – /etc/bind/named.conf.local 文件中的我们域的新区域
现在,让我们解释一下区域指令的内容:
-
首先,我们需要添加将由区域服务的域名
-
区域的
type设置为master,但还有其他类型可以使用,如slave、forward或hint -
file表示将要创建的实际区域文件的路径 -
在
allow-transfer列表中,设置了处理该区域的 DNS 服务器的 IP 地址 -
在
also-notify列表中,列出了将被通知有关区域更改的服务器的 IP 地址
现在,让我们为我们选择的域创建一个新区域:
- 以下截图显示了我们如何将
db.local文件复制为另一个名称并将其用于新区域:

图 13.7 – 为我们的域创建区域文件
- 下一步是创建区域文件,如关于
calcatinge.ro的区域指令所示。详情如前述截图所示,位置为/etc/bind/。使用db.local作为模板创建文件后,您可以使用您喜欢的文本编辑器打开它,并添加有关服务器 IP 和域名的信息。在以下截图中,您可以看到我们机器上为calcatinge.ro创建的区域文件:

图 13.8 – 区域文件信息
-
DNS 记录在文件的末尾引入。以下是文件内容的一些详细信息:
-
表格有一个特定格式,包含有关主机名(第一列)、类别(第二列)、DNS 记录类型(第三列)和值(最后一列)的详细信息。
-
对于主机名,我们输入了
@,这意味着记录条目的名称引用了文件中的区域名称。 -
类别是
IN,表示该网络是互联网。 -
DNS 记录类型有
A、NS、MX、CNAME、TXT和SOA。A表示域名的 IP 地址;NS表示 DNS 服务器的 IP 地址;MX是邮件服务器的地址;CNAME是别名(规范名称);TXT是自定义条目;SOA表示区域的权威名称服务器,包含管理员、序列号和刷新速率的详细信息。 -
最后一列的值通常由 IP 地址或主机名组成。
-
-
下一步是重启远程名称守护进程控制(RNDC),它是 BIND 中的一个控制工具,用于控制名称服务器。执行此操作的命令如下所示:
sudo rndc reload -
现在,您可以检查主 DNS 服务器是否正常工作。尝试从网络上的另一台系统使用
nslookup命令,命令如下:nslookup calcatinge.ro 192.168.0.113上述命令的输出将非常可能显示,具有指示的 IP 地址的系统上的本地 DNS 服务器有一个有效的区域文件。您的主要 DNS 服务器在本地网络上按预期工作。别忘了在命令中使用您的 IP。
创建一个辅助 DNS 服务器是个好主意,以防第一个服务器停止工作,这就是为什么我们将在接下来的部分展示如何设置第二个 DNS 服务器。
设置一个辅助 DNS 服务器
毫不奇怪,辅助 DNS 服务器应设置在与主服务器不同的硬件上,但在同一网络中。如果您在数据中心中进行此操作,可以使用与第一个服务器在同一网络中的 VPS。如果您打算在家庭私人网络中进行实验,确保有另一台系统可以使用。
我们将启动另一台也运行 Ubuntu Server 22.04.2 LTS 的 NUC 系统。我们需要知道它的 IP 地址,以便在配置中使用。我们的新系统的 IP 地址是192.168.0.140。这台第二台机器也需要安装和配置 BIND9。在设置辅助服务器之前,您需要先修改主 DNS 服务器的配置。
修改主服务器配置文件
要修改主 DNS 服务器的配置,并允许它将区域详情发送到辅助服务器,请按照以下步骤操作:
- 您需要打开
/etc/bind/named.conf.local配置文件,并在其中添加一些新行。我们将在allow-transfer和also-notify指令内添加第二台服务器的 IP 地址,如下图所示:

图 13.9 – 添加辅助 DNS 服务器的 IP 地址
-
保存文件并重启 BIND9 服务。
-
还需要打开
/etc/bind/named.conf.options配置文件,并添加一个访问列表参数(acl "trusted"),列出网络上所有被接受的 IP 地址。在我们的例子中,主服务器的地址是192.168.0.113,而辅助服务器的地址是192.168.0.140。请将其添加到已存在的options指令之前。 -
在
options指令块内,注释下面,我们还添加以下指令:

图 13.10 – 在/etc/bind/named.conf.options 文件中添加新指令
-
配置已完成,我们将使用
systemctl命令重启 BIND9 服务,如下所示:sudo systemctl restart bind9.service
在继续之前,让我们了解一下所使用的指令。recursion指令具有布尔值(yes | no),定义是否允许服务器进行递归查询和缓存。默认值是yes,因此服务器将要求通过解决所有尝试来执行 DNS 查询递归。
重要提示
递归 也被称为 递归查询,在 DNS 中指的是一种解决名称解析的方法。递归模式表示计算机通过首先查询本地缓存数据和本地 DNS 服务器来查找 FQDN。请求是明确的,需要一个精确的答案,而提供该答案的责任在于 DNS 服务器。因此,计算机发起的查询,即 DNS 客户端向 DNS 服务器发起的查询,是一个递归查询。
allow-recursion 指令引用了一个匹配客户端地址的列表,在我们的案例中,这些地址通过 listen-on 指令指定服务器监听的 IP 地址。同时,我们还使用了 allow-transfer 指令,提供了允许传输区域信息的主机列表(在我们的案例中,没有主机)。有关配置选项的更多信息,请参考以下链接:bind9.readthedocs.io/en/latest/reference.html#。
接下来,我们来学习如何配置辅助服务器。
设置辅助服务器
如前所述,在辅助服务器上,您还需要安装 BIND9。安装完成后,您需要按照以下步骤操作:
- 进入
/etc/bind/named.conf.options文件,在已经存在的options指令之前,添加以下行到acl指令中:

图 13.11 – 在辅助服务器上添加 acl 指令
- 同时,在
options指令内部添加以下行:

图 13.12 – 在选项内部添加新指令
-
现在编辑
/etc/bind/named.conf.local文件并添加所需的区域,但这次使用secondary类型,而不是在主 DNS 服务器上使用的master类型。以下是两个
/etc/bind/named.conf.local文件的对比。左边是主 DNS 服务器的文件,右边是辅助 DNS 服务器的文件:

图 13.13 – 主服务器(左)和辅助服务器(右)的配置文件
-
现在我们可以重新启动 BIND9 服务,并确保防火墙允许第二台服务器上的 DNS 连接,使用以下命令:
sudo ufw allow Bind9 && sudo systemctl restart bind9
现在,您已经设置并启动了两台 DNS 服务器,一台是主服务器,另一台是辅助服务器。在接下来的章节中,我们将向您展示如何设置本地 DHCP 服务器。
设置 DHCP 服务器
DHCP 是一种网络服务,用于为网络中的主机分配 IP 地址。该服务由服务器启用,主机无需进行任何控制。最常见的是,DHCP 服务器为客户端提供 IP 地址和子网掩码、默认网关 IP 地址以及 DNS 服务器的 IP 地址。
要在 Ubuntu 上安装 DHCP 服务,请使用以下命令:
sudo apt install isc-dhcp-server
作为测试系统,我们将使用在上一节中安装了 DNS 服务的相同系统。安装完成后,我们将配置两个特定的文件。在像我们这样的 Ubuntu 系统中,默认配置将设置在/etc/dhcp/dhcpd.conf文件中,而网络接口的配置则会在/etc/default/isc-dhcp-server文件中进行:
- 我们首先将向您展示如何设置一个基本的本地 DHCP 服务器。在这方面,我们将通过添加 IP 地址池来修改
/etc/dhcp/dhcpd.conf文件。您可以取消注释文件中已存在的某个subnet指令,也可以添加一个新的,就像我们所做的那样。我们现有的子网是192.168.0.0/24,我们将为这个新的 DHCP 服务器添加一个新的子网,具体操作如以下截图所示(有关网络的基础知识,请参考第七章,Linux 网络):

图 13.14 – 在 /etc/dhcp/dhcpd.conf 中定义一个新的子网
-
在相同的
/etc/dhcp/dhcpd.conf文件中,您可以取消注释那一行authoritative;。authoritativeDHCP 子句确保服务器会自动解决网络上的任何无效 IP 地址,并为每个新注册的设备分配一个新的有效 IP 地址,而无需用户手动干预。 -
一旦修改了选项,您必须在
/etc/default/isc-dhcp-server文件中指定网络接口名称。这是为了让服务器知道使用哪个网络设备。为此,使用您喜欢的编辑器打开该文件并添加接口名称。如果您不记得接口名称,可以运行ip addr show命令并选择相应的接口。在我们的案例中,我们使用的系统既有以太网接口,也有无线接口,我们将选择以太网接口作为 DHCP 服务器的接口,即enp0s25。在/etc/default/isc-dhcp-server文件中,按以下方式添加接口:INTERFACESv4="enp0s25" -
然后,保存文件的更改并使用以下命令重新启动 DHCP 服务:
sudo systemctl restart isc-dhcp-server.service
现在,您在所选系统上有一个正常工作的 DHCP 服务器。DHCP 服务器为您管理本地网络提供了一些优势,但有时您可能不需要创建一个新的,因为所有的网络路由器都提供了一个开箱即用的 DHCP 服务。
重要提示
为了避免冲突,您可能希望将新的 DHCP 服务器与本地网络路由器隔离开来。您的网络路由器已经有一个功能完整的 DHCP 服务器,它可能会与新的 DHCP 服务器发生冲突。在大多数情况下,isc-dhcp-server.service会给出一个错误,表示它无法连接到任何接口。
考虑到前面的说明,当我们通过以下命令检查 DHCP 服务是否正在运行时:
sudo systemctl status isc-dhcp-server.service
我们收到了一个错误,如以下截图所示:

图 13.15 – 运行 DHCP 服务时出错
一旦你将机器从本地网络中隔离出来,并将其用作唯一的 DHCP 服务器,服务将按预期运行。
在接下来的部分中,我们将向你展示如何在本地网络上设置 NFS 服务器。
设置 NFS 服务器
NFS 是一个分布式文件系统,用于通过网络共享文件。为了展示它是如何工作的,我们将在网络中的一台机器上设置 NFS 服务器。我们将使用 Ubuntu 22.04.2 LTS 作为 NFS 服务器的基础。有关 NFS 的更深入理论信息,请参考第七章,与 Linux 的网络连接。
NFS 文件系统类型在任何 Linux 和/或 Unix 环境中都得到支持,Windows 也支持 NFS,但有一些限制。对于主要使用 Windows 客户端环境的情况,我们建议改用 Samba/通用互联网文件系统(CIFS)协议。此外,对于那些关心隐私和安全的人,请记住,NFS 协议本身不加密,因此数据传输默认情况下不受保护。
安装并配置 NFS 服务器
在我们的网络中,我们将使用一台 Ubuntu 机器作为服务器,并展示如何从另一台 Linux 客户端访问文件。首先,让我们安装并配置服务器,具体步骤如下:
-
我们将使用以下命令安装
nfs-kernel-server软件包:systemctl command, and then we will check its status. The commands are the following:sudo systemctl start nfs-kernel-server.service
sudo systemctl enable nfs-kernel-server.service
为所有网络上的客户端设置
/home目录,或者你也可以从一开始就创建一个专用的共享目录。你可以从根目录开始创建一个新目录,也可以在特定目录下创建共享目录,例如/var、/mnt或/srv—这完全取决于你。我们将使用以下命令在/home目录下创建一个新的目录/home/export/shares(如果你想按照这个命令执行,请确保已经进入/home目录):777 as we will not use Lightweight Directory Access Protocol (LDAP) authentication on the following example:/etc/exports 文件。NFS 有三个配置文件(/etc/default/nfs-kernel-server、/etc/default/nfs-common 和/etc/exports),但我们只会更改其中一个。在下面的截图中,你将看到/etc/default 目录下的两个文件以及/etc/exports 配置文件的默认内容:

图 13.16 – /etc/default 目录中的配置文件以及/etc/exports 配置文件的内容
使用您喜欢的文本编辑器打开/etc/exports文件,并根据您的配置进行编辑。在文件中,我们将为每个共享目录添加一行。在此之前,您可能已经注意到文件中有两种指令:一种是针对 NFS 版本 2 和 3 的,另一种是针对版本 4 的。关于这些版本之间的差异,建议您参考以下文档:archive.fosdem.org/2018/schedule/event/nfs3_to_nfs4/attachments/slides/2702/export/events/attachments/nfs3_to_nfs4/slides/2702/FOSDEM_Presentation_Final_pdf.pdf。
-
现在让我们开始编辑配置文件。我们将添加一行,包含目录、客户端的 IP 地址和配置选项。一般语法如下:
/first/path/to/files IP(options)[ IP(options) IP(options)] /second/path/to/files IP(options)[ IP(options) IP(options)]我们只共享一个目录,因此只添加了一行。如果您希望网络上的所有客户端都能访问共享,您可以添加子网类(例如,
192.168.0.0/24)。或者,如果您只希望特定的客户端访问共享,您应该添加它们的 IP 地址。更多的客户端可以在同一行添加,IP 地址之间用空格分隔。您可以添加很多选项,完整的选项列表可以参考linux.die.net/man/5/exports或者使用man exports命令查看本地手册。在我们的文件中,我们添加了以下选项:-
rw表示既可以读取也可以写入 -
sync强制将更改写入磁盘(不过这样会降低速度) -
no_subtree_check用于防止子树检查,主要是检查文件在请求前是否仍然可用
简而言之,这就是我们添加到文件中的那一行(请注意,这只是一行,其中
/home/export/shares和 IP 地址之间有空格,而 IP 地址和括号之间没有空格):/home/export/shares 192.168.0.0/24(rw,sync,no_subtree_check) -
-
保存并关闭文件后,使用以下命令重启服务:
sudo systemctl restart nfs-kernel-server.service -
然后,使用以下命令应用配置:
-a options export all directories without specifying a path. -
一旦服务重启并运行,您可以设置防火墙以允许 NFS 访问。为此,知道 NFS 默认使用
2049端口是非常有用的。由于我们允许网络中的所有系统访问共享目录,我们将向防火墙添加以下新规则:sudo ufw allow nfs -
现在新防火墙规则已经添加完毕,我们可以运行以下命令来确保它按照我们的需求运行:
sudo ufw status -
如果您的防火墙没有在运行,您可以使用以下命令来激活它:
ufw status command is shown in the following screenshot:

图 13.17 – 显示允许 NFS 共享的新防火墙规则
在前面的截图中,你可以看到在使用 sudo ufw allow nfs 命令添加新规则后,端口 2049 被添加到了允许规则的列表中。
服务器的基本配置现在已经完成。为了访问文件,你还需要配置客户端。我们将在下一部分向你展示如何操作。
配置 NFS 客户端
作为客户端,我们将使用另一台系统,即运行 Debian GNU/Linux 12 Bookworm 的笔记本。首先,我们需要使用以下命令在客户端安装 NFS:
sudo apt install nfs-common
现在所需的软件包也已安装在客户端,我们可以在客户端上创建目录来挂载共享。我们将在客户端创建一个新目录,用于挂载来自服务器的共享。这个新目录将是 /home/shares。我们使用以下命令创建它:
sudo mkdir /home/shares
现在新目录已创建,我们可以从服务器挂载该位置:
sudo mount 192.168.0.113:/home/export/shares /home/shares
使用前面的命令,我们通过 mount 命令将服务器上的共享挂载到客户端。我们将服务器上的位置作为第一个参数,客户端上的位置作为第二个参数。我们还可以使用 df -h 命令检查一切是否顺利。新挂载的内容会在 df 命令的输出中最后显示。以下是一个截图,展示了创建、挂载并检查新共享目录所使用的命令:

图 13.18 – 在客户端上挂载新共享目录
到此为止,我们已经完成了 NFS 共享的设置。接下来,我们需要测试配置,确保它能够正常工作。
测试 NFS 设置
一旦服务器和客户端的设置完成,你可以测试一切是否如预期般工作。在我们的测试中,我们使用服务器上的 packt 用户创建了几个名为 testing_files 的文件,并且在客户端机器上的常规用户 alexandru 下创建了一个名为 file 的文件。以下是显示我们在 Debian 本地系统上的 /home/shares 目录内容的输出:

图 13.19 – 在本地客户端上测试 NFS
就这样,NFS 服务器和客户端都运行良好。你可以使用客户端上的图形用户界面(GUI)(如前面的截图所示),也可以使用命令行界面(CLI)访问 NFS 共享。
在下一部分,我们将展示如何配置一个可以通过网络访问的 Samba/CIFS 共享。
设置 Samba 文件服务器
Samba 服务器允许您通过网络共享文件,客户端可以使用不同的操作系统,例如 Windows、macOS 和 Linux。在本节中,我们将在 Ubuntu 22.04.2 LTS 上设置 Samba 服务器,并从网络上的不同操作系统访问共享。SMB/CIFS 协议是由微软开发的;有关更多详细信息,您可以访问他们的开发者页面docs.microsoft.com/en-us/windows/win32/fileio/microsoft-smb-protocol-and-cifs-protocol-overview。关于 SMB/CIFS 协议的更多信息也可以在第七章 与 Linux 网络互联中找到。在接下来的子章节中,我们将向您展示如何在本地网络上安装和配置它。
安装和配置 Samba
安装过程包含以下步骤:
-
首先,我们将使用以下命令在系统上安装 Samba:
sudo apt install samba -
安装 Samba 后,我们可以检查服务是否按预期运行。我们将执行以下命令来检查:
/home directory using the mkdir command, as follows:/etc/samba/smb.conf。该配置文件有两个主要部分:一个是包含一般配置设置的[global]部分,另一个是配置共享行为的[shares]部分。一个安全的做法是在开始修改之前备份原始配置文件。我们将使用以下命令来执行此操作:
smb.conf. Inside the configuration file, we will first create the [global] section with directives, as shown in the following screenshot:

图 13.20 – smb.conf 全局指令
让我们简要解释一下前面截图中显示的内容:
-
第一行设置了服务器名称,在我们的情况下是本地 Samba 文件服务器。
-
第二行设置了
workgroup名称,在我们的情况下是 Windows 的默认名称WORKGROUP。 -
第三行确定不需要拥有 Samba 用户账户就能访问共享(将游客映射为
Bad User)。 -
允许游客用户在第四行进行访问。
-
接着,我们设置 Samba 使用的接口,在我们的情况下将是以太网接口(
eno1)和回环接口(lo)(使用ip addr show或ip link命令检查确切的接口名称)。 -
我们将设置
server role和允许的主机(来自本地池)。 -
最后一行设置了主机名检查的顺序,使用广播(
bcast)方法。
- 现在我们将创建
[shares]指令,并为共享添加必要的配置选项。我们将添加有关服务器上共享目录的详细信息、用户详细信息以及权限设置。请参见以下截图了解详细信息:

图 13.21 – 在 shares.conf 文件中添加信息
让我们简要解释一下此指令的内容:
-
我们首先为共享设置一个名称。
-
然后,我们设置本地共享目录的路径。
-
接下来,我们设置目录和文件的默认权限。
-
我们还设置了默认的掩码值。
-
最后,我们将共享设置为公共的、友好的游客用户并允许写入
-
修改完成后,我们用以下命令重启服务:
sudo systemctl restart smbd.service -
此外,我们通过以下命令调整防火墙规则,允许 Samba 访问:
testparm command to test that the configuration has been done correctly, with no errors, as follows:

图 13.22 – 测试 Samba 配置
输出显示 Loaded services file OK,这意味着配置文件没有语法错误。
系统重启并配置好防火墙后,我们可以继续为可以访问共享的用户设置 Samba 密码。在接下来的部分,我们将创建新的 Samba 用户和组。
创建 Samba 用户
每个 Samba 服务器都需要有特定的用户可以访问共享的目录和文件。这些用户既需要是 Samba 用户,也需要是系统用户,因为这是用户能够进行身份验证并读取和写入系统文件的必要条件。假设你需要为你的家庭或小型企业组创建一个本地共享。
通过为使用 Samba 共享创建专用的本地用户,你不需要让它们充当实际用户,因为它们只需要能够访问共享。然而,本地 Samba 用户需要是本地系统用户。在我们的案例中,我们将使用本地的 packt 用户来为 Samba 创建新用户:
-
我们使用以下命令将
packt本地用户添加到 Samba:packt is our primary user, it is also the owner of the samba_shares directory created at the beginning of this section. We can change that with the following command:使用以下命令的 alex:
sudo adduser alex -
然后我们用以下命令将其添加到 Samba:
smb.conf file and add the users we would like to give permissions to Samba, such as packt and alex users. Let us open the /etc/samba/smb.conf file and add the following line inside the [samba shares] directive:valid users = @packt @alex
在测试新用户访问 Samba 之前,我们需要授予 alex 用户访问共享目录的权限。为此,我们按照以下步骤操作:
-
我们通过以下命令在 Ubuntu 中添加
acl软件包:setfacl command to set read, write, and execute permissions for the /home/packt/samba_shares directory. For this, we use the following command:sudo setfacl -R -m "u:alex:rwx" /home/packt/samba_shares
在接下来的部分,我们将展示如何从网络中的不同系统访问 Samba 共享。
访问 Samba 共享
在你的网络中,你可以从 Linux、macOS 或 Windows 访问共享文件。为了增加一些挑战——值得一个 Linux 大师的挑战!——我们将展示如何仅使用 CLI 在 Linux 中访问 Samba 共享。我们将让你自己探索如何通过 GUI 或 Windows/macOS 客户端访问它们。要通过 CLI 访问共享,请使用 smbclient 工具。
在 Linux 系统中,你需要先安装 Samba 客户端 smbclient。我们假设你使用的是 Ubuntu 或 Debian Linux 客户端,但其他 Linux 发行版的步骤也类似。在 Ubuntu/Debian 上,首先使用以下命令安装 Samba 客户端,但在此之前确保你的软件库已更新:
sudo apt install smbclient
如果你正在运行 Fedora/RHEL 客户端,请使用以下命令安装 Samba 客户端:
sudo dnf install samba-client
例如,让我们从本地机器之一访问 packt 用户的共享。记住,这些共享文件位于运行 Ubuntu 的本地服务器上。我们将使用服务器的本地 IP 地址,并运行以下代码:

图 13.23 – 从 Linux CLI 客户端访问共享文件
在上面的截图中,你可以看到 Samba 访问成功,用户 packt 成功访问了服务器上的 Samba 共享文件。我们使用了 -U 选项,后跟用户名,来指定我们正在连接的用户名。位置使用服务器的本地 IP 地址,后跟 Samba samba shares 名称。此外,如果我们想查看服务器上可用的 Samba 服务,可以使用 -L 选项与 smbclient 命令,命令如下:

图 13.24 – 列出 Samba 服务器上的可用服务
这里是我们关于配置 Linux 服务器的本章的结尾。展示的服务器被认为是任何 Linux 系统管理员需要了解的重要和相关服务。然而,由于篇幅限制,许多其他类型的 Linux 服务器没有覆盖。尽管如此,你可以在线找到大量资源。作为基础,你可以从 RHEL、Ubuntu 或 Debian 的官方文档开始,这些文档涵盖了大多数你需要了解的 Linux 服务器类型。一个有用的服务器类型是 Web 服务器,了解如何配置它将非常有用。随时探索任何其他你认为相关的资源,可能未包含在进一步阅读列表中。
总结
在本章中,我们介绍了为 Linux 配置最知名服务的安装和配置过程。了解如何配置本章中描述的所有服务器——从 DNS 到 DHCP、Apache 和打印服务器——是任何 Linux 管理员的基本要求。
通过本章的学习,你了解了如何为任何 Linux 服务器提供基本服务。你学习了如何使用 Apache 包设置和配置 Web 服务器;如何为小型办公室或家庭办公室提供网络打印服务;如何运行 FTP 服务器并通过 TCP 共享文件;如何使用 Samba/CIFS 协议与 Windows 客户端共享文件;如何使用 NFS 文件共享协议在 Unix 和 Linux 系统之间共享文件;如何设置 NTP 以显示准确时间;以及如何配置 DNS 和本地 DHCP 服务器。简而言之,你在本章中学到了很多内容,然而我们只是 Linux 服务器管理的皮毛。
在下一章中,我们将向你介绍云技术。
问题
现在你已经清楚地了解了如何管理 Linux 中一些最广泛使用的服务,以下是一些可以进一步促进你学习的练习:
-
尝试使用 VPS 运行本章中详细描述的所有服务,而不是在本地网络上。
-
尝试在 Ubuntu 上设置 LEMP 堆栈。
-
使用 Fedora 或基于 RHEL 的发行版测试本章中描述的所有服务。
进一步阅读
有关本章所涵盖主题的更多信息,请参考以下链接:
-
官方 Ubuntu 文档:
ubuntu.com/server/docs -
NGINX 官方文档:
docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/ -
DigitalOcean 官方文档:
www.digitalocean.com/community/tutorials/how-to-install-the-apache-web-server-on-ubuntu-22-04
第四部分:云计算管理
在第四部分,你将学习与云计算相关的高级概念。到本部分结束时,你将熟练掌握使用 Kubernetes 和 Ansible 等特定工具,并能将 Linux 部署到 AWS 和 Azure 云平台。
本部分包括以下章节:
-
第十四章,计算简短介绍
-
第十五章,使用 AWS 和 Azure 部署到云端
-
第十六章,使用 Kubernetes 部署应用程序
-
第十七章,使用 Ansible 进行基础设施和自动化
第十四章:云计算简要介绍
在本章中,你将学习云计算的基础知识,并了解云基础设施技术的核心基础。你将学习诸如基础设施即服务(IaaS)、平台即服务(PaaS)、软件即服务(SaaS)以及容器即服务(CaaS)等“即服务”解决方案。你将了解云标准、开发与运维(DevOps)、持续集成/持续部署(CI/CD)以及微服务的基本概念。掌握云计算的基础知识,将帮助你了解 AWS、Azure 及其他云解决方案。到本章结束时,我们还将介绍Ansible和Kubernetes等技术。本章将为接下来三章提供简明的理论介绍,为你掌握云计算相关的实践知识奠定基础。
本章将涵盖以下主要内容:
-
云技术简介
-
介绍 IaaS 解决方案
-
介绍 PaaS 解决方案
-
介绍 CaaS 解决方案
-
介绍 DevOps
-
探索云管理工具
技术要求
本章为纯理论内容,不需要特别的技术要求。你所需要的只是学习云技术的热情。
云技术简介
“云计算”这一术语,或者简化后的“云”一词,如今已经出现在每个科技爱好者或信息技术(IT)专业人士的词汇中。即便你完全不涉及 IT 行业,也很可能会听到(甚至使用)“云”这一术语。今天的计算环境正在以飞快的速度发生变化,而这种变化的顶峰便是云计算及其背后的技术。根据文献资料,“云计算”一词首次出现在 1996 年的 Compaq 商业计划书中(www.technologyreview.com/2011/10/31/257406/who-coined-cloud-computing/)。
云计算是一个相对较古老的概念,尽管最初并未使用这个术语。从计算机的早期阶段起,云计算模型就已经在使用。例如,在 1950 年代,主机计算机就能通过不同的终端进行访问。这个模型与现代的云计算非常相似,云服务通过互联网托管并交付到各种终端设备,从桌面电脑到智能手机、平板电脑或笔记本电脑。这一模型基于的技术极为复杂,对于任何希望掌握这些技术的人来说,都至关重要。
你可能会想,为什么在一本《掌握 Linux 管理》的书籍中会专门有一章讲解云计算和相关技术。这是因为 Linux 在过去十年中已接管了云计算,正如 Linux 曾经接管了互联网和高性能计算领域一样。根据 TOP500 协会的数据,全球前 500 名超级计算机都运行在 Linux 系统上(top500.org/lists/top500/2020/11/)。云计算需要操作系统来运行,但不一定非得是 Linux。然而,Linux 在几乎 90%的公有云上运行(www.redhat.com/en/resources/state-of-linux-in-public-cloud-for-enterprises),这主要是因为其开源性质对公私部门的 IT 专业人员都很有吸引力。
在接下来的部分中,我们将探讨云计算标准的主题,并说明在部署或管理云实例时了解这些标准的好处。
探索云计算标准
在详细讨论云计算之前,首先让我们简要介绍一下什么是云标准,以及它们在当代云计算领域中的重要性。你可能知道,几乎所有广泛的信息与通信技术(ICT)领域活动都受到某种标准或规定的管理。
云计算并不是一个未知领域,你会惊讶于有多少协会、监管机构和组织参与了其标准和法规的制定。全面涵盖这些机构和标准超出了本书和本章的范围,但在接下来的部分中,我们将描述一些我们认为最重要和最相关的标准,以便你能了解它们在保持云计算和 Web 应用程序运行中的重要性。
国际标准化组织/国际电工委员会
两个最广为人知的标准机构是国际标准化组织(ISO)和国际电工委员会(IEC),它们目前在云计算和分布式平台方面已发布并正在制定 28 项标准。它们拥有一个联合工作组,负责制定针对特定云核心基础设施、消费者应用平台和服务的标准。这些标准由联合技术委员会 1(JTC 1)子委员会 38(SC38)负责,简称为ISO/IEC JTC 1/SC 38。
ISO/IEC 的标准示例如下:
-
云计算服务水平协议(SLA)框架:
-
ISO/IEC 19086-1:2016
-
ISO/IEC 19086-2:2018
-
ISO/IEC 19086-3:2017
-
-
云计算面向服务的架构(SOA)框架:
-
ISO/IEC 18384-1:2016
-
ISO/IEC 18384-2:2016
-
ISO/IEC 18384-3:2016
-
-
开放虚拟化格式(OVF)规范:
- ISO/IEC 17203:2017
-
云计算数据共享协议(DSA)框架:
- ISO/IEC CD 23751
-
分布式应用平台与服务(DAPS)技术原则:
- ISO/IEC TR 30102:2012
要详细了解这些标准,请访问www.iso.org/committee/601355/x/catalogue/。
云标准协调倡议
接下来是我们标准开发实体的列表,其中包括一个名为云标准协调(CSC)的倡议,该倡议由欧洲委员会(EC)与专业机构共同创建。
早在 2012 年,欧洲委员会(EC)与欧洲电信标准化协会(ETSI)合作,启动了 CSC 倡议,旨在制定云安全、互操作性和可移植性的标准和政策。该倡议分为两个阶段,第一阶段始于 2012 年,第二阶段始于 2015 年。第二阶段的最终报告(版本 2.1.1)已公开发布,具体如下:
-
云计算用户需求(ETSI SR 003 381)
-
标准与开源(ETSI SR 003 382)
-
互操作性与安全性(ETSI SR 003 391)
-
标准成熟度评估(ETSI SR 003 392)
有关每项标准的更多详细信息,请访问以下链接:csc.etsi.org/。
国家标准与技术研究院
列表继续列出一个在标准开发领域广为人知的实体:美国(US)国家标准与技术研究院(NIST)。这不会是你第一次在本书中看到 NIST。它是美国商务部下属的标准开发机构。NIST 的主要目标是美国政府机构内部的安全性和互操作性标准化,因此任何有兴趣为这些机构开发的人员应查看 NIST 的云文档。标准化云计算的 NIST 文档名为 NIST SP 500-291r2,可以在csrc.nist.gov/publications/nistpubs/800-145/SP800-145.pdf找到。
国际电信联盟
我们将以最古老的标准化组织之一来结束我们的短名单——如果不是最古老的标准化组织——它是联合国(UN)的一部分:国际电信联盟(ITU)。ITU 是联合国内的一个机构,主要致力于制定通信、网络和发展方面的标准。该机构成立于 1865 年,负责全球无线电频谱和卫星轨道分配等多项事务。它还负责使用摩尔斯电码作为标准通信手段。关于全球信息基础设施、互联网协议、下一代网络、物联网(IoT)和智能城市,ITU 提供了大量的标准和建议。若要查看这些内容,可以访问以下链接:www.itu.int/rec/T-REC-Y/en。为了缩小上述链接中的文档列表,可以使用文档代码从 Y.3505 到 Y.3531 找到一些特定的云计算文档。这些云计算标准由 ITU 内的研究组 13(SG13) 云计算联合协调活动(JCA-Cloud)开发。
除了本节描述的实体外,还有许多其他实体,包括以下内容:
-
云标准客户 委员会(CSCC)
-
分布式管理任务 组(DMTF)
-
结构化信息标准化组织(OASIS)
采用云计算标准的主要原因是为了方便云服务提供商(CSP)和客户的使用。两者都需要轻松访问数据,尤其是对于 CSP 和应用开发者而言,轻松访问数据意味着更高的灵活性和互操作性。然而,标准除了技术上要正确外,还需要一致性和持久性。根据文献,主要有两类标准团体:一种是通过实践建立的,另一种是由政府法规所规范的。第二类中的一个重要组成部分是应用程序编程接口(APIs)。应用框架、网络协议和 API 的标准化能保证每个参与者的成功。
通过 API 标准理解云计算
API 是一组协议、程序和函数:所有构建 Web 分布式应用所需的“砖块”。现代 API 起源于 21 世纪初,最早在 Roy Fielding 的博士论文中提出。在现代 API 出现之前,曾有基于可扩展标记语言(XML)的 SOA 标准和 简单对象访问协议 (SOAP)。现代 API 基于一种新的应用架构风格,称为 表现层状态转移(REST).
REST API 基于一系列架构风格、元素、连接器和视图,这些内容在 Roy Fielding 的论文中得到了清晰的描述。为了使 API 成为 RESTful,需要遵循六个指导性约束,它们如下:
-
统一的用户界面
-
客户端-服务器的明确划分
-
无状态操作
-
可缓存资源
-
服务器的分层系统
-
按需执行代码
遵循这些指导原则远不是遵循标准,但 REST 为开发者提供了一个高层的抽象层。除非它们被标准化,否则它们将始终保持为极好的原则,给开发者带来困惑和沮丧。
唯一成功将 REST API 标准化应用于云计算的组织是 DMTF,通过云基础设施管理接口(CIMI)模型和基于 RESTful 的超文本传输协议(HTTP)协议,在编码为 DSP0263 版本 2.0.0 的文档中进行标准化,该文档可以通过以下链接下载:www.dmtf.org/standards/cloud。
还有其他一些规范作为未来可能的标准出现,供开发者在设计 REST API 时使用。其中之一是OpenAPI 规范(OAS),这是一个行业标准,提供了一种与语言无关的 API 开发描述(文档可在 spec.openapis.org/oas/v3.0.3 获取),另一个是GraphQL,作为一种查询语言和服务器端运行时,支持多种编程语言,如 Python、JavaScript、Scala、Ruby 以及 PHP: 超文本 预处理器(PHP)。
REST 成为了首选的 API,因为它更容易理解、更加轻量、简单易写。它更高效,使用更少的带宽,支持多种数据格式,并以JavaScript 对象表示法(JSON)作为首选的数据格式。JSON 易于读取和写入,并且提供了不同语言编写的应用程序之间更好的互操作性,例如 JavaScript、Ruby、Python 和 Java。通过将 JSON 作为 API 的默认数据格式,它使得 API 既友好又可扩展,且与平台无关。
API 无处不在,无论是 Web 上还是云端,都是 SOA 和微服务的基础。例如,微服务通过 RESTful API 进行服务间的通信,提供了一个优化的架构来支持云分布式资源。
因此,如果你想掌握云计算技术,你应该愿意接受云计算标准。在接下来的部分,我们将讨论云的类型和架构。
理解云的架构
云的架构设计类似于建筑物的架构设计。云有一个主导的设计范式——即设计从一张干净的空白图纸开始,建筑师将不同的标准化组件组合在一起,以实现一个建筑设计。最终的结果基于特定的建筑风格。设计云的架构时也是如此。
云计算基于客户端-服务器、分层、无状态、基于网络的架构风格。REST API、SOA、微服务和 Web 技术都是构成云计算基础组件的核心。云的架构已由 NIST 定义(www.nist.gov/publications/nist-cloud-computing-reference-architecture)。
云计算背后的一些技术已在第十一章,与虚拟机一起工作,和第十二章,使用 Docker 管理容器中讨论过。实际上,虚拟化和容器是云计算的基础技术。
假设你需要部署多个 Linux 系统来运行应用程序。你首先要做的就是去找一个 CSP 并请求所需的系统。CSP 会根据你的需求,在其基础设施上创建虚拟机(VMs),并将它们放在同一个网络中,然后与你共享访问这些虚拟机的凭证。这样,你就能访问到你所需要的系统,并以订阅费用的形式支付,费用可以是按天、按月、按年或者按资源消耗计费。通常,这些 CSP 请求是通过提供商为满足用户需求而开发的特定 Web 界面进行的。
云计算所使用的所有技术都是基于虚拟机和容器的。在云内部,一切都被抽象化并自动化。接下来的部分,我们将为你提供有关云中可用服务类型的信息。
描述基础设施和服务的类型
无论是什么类型,每个云都有一个特定的架构,就像我们在前一部分展示的那样。它为云计算的基础提供了蓝图。云架构是云基础设施的基础,而基础设施又是云服务的基础。你看,一切是如何相互连接的?现在,让我们看看与云相关的基础设施和服务是什么。
云计算有四种主要的云基础设施类型,分别如下:
-
公共云:这些云运行在由提供商拥有的基础设施上,通常位于本地以外;最大的公共云提供商包括 AWS、Microsoft Azure 和 Google Cloud。
-
私有云:这些云专门为个人和具有隔离访问权限的群体运行;它们可以在本地或非本地硬件基础设施上提供。提供托管私有云或专用私有云。
-
混合云:这些是同时运行在连接环境中的私有云和公有云,具有可按需扩展的资源。
-
多云:这些是由多个提供商运行的多个云。
除了云基础设施类型外,还有四种主要的云 服务类型:
-
IaaS:在 IaaS 云服务类型下,云服务提供商管理所有硬件基础设施,如服务器和网络,以及数据的虚拟化和存储。基础设施由提供商拥有并租赁给用户;在这种情况下,用户需要管理操作系统、运行时、自动化、管理解决方案和容器,以及数据和应用程序。IaaS 是每个云计算服务的支柱,因为它提供了所有资源。
-
CaaS:这被认为是 IaaS 的子集;它具有与 IaaS 相同的优点,只不过基础是容器,而非虚拟机,更适合部署分布式系统和微服务架构。
-
PaaS:在 PaaS 云服务类型下,硬件基础设施、网络和软件平台由云服务提供商管理;用户管理和拥有数据和应用程序。
-
SaaS:在 SaaS 云服务类型下,云服务提供商管理和拥有硬件、网络、软件平台、管理和软件应用程序。这种类型的服务也以提供 Web 应用程序或移动应用程序而著称。
除了这些类型的服务外,还有一种服务我们应该纳入讨论:无服务器计算服务。与名称可能暗示的相反,无服务器计算仍然涉及服务器的使用,但运行它们的基础设施对用户不可见,用户在大多数情况下是开发人员。无服务器计算也被称为功能即服务(FaaS),它提供了一种按需执行模块化代码的方式,让开发人员能够动态更新他们的代码。此类服务的示例包括 AWS Lambda、Azure Functions 和 Google Cloud Functions。它类似于 SaaS;实际上,它介于 PaaS 和 SaaS 之间。它不需要基础设施管理,具有可扩展性,为应用开发人员提供了更快的市场进入方式,并且在资源使用方面高效。
现在您已经了解了云基础设施和服务的类型,您可能会想知道为什么您、您的企业或您认识的任何人应该迁移到云服务。首先,云计算是基于按需访问由云服务提供商(CSP)托管和管理的各种资源。这意味着基础设施由 CSP 拥有或管理,用户可以根据订阅费用访问这些资源。您应该迁移到云端吗?我们将在接下来的章节中讨论迁移到云端的优缺点。
了解云计算的关键特性
在决定是否迁移到云端是一个明智选择之前,您需要了解这样做的优缺点。云计算确实提供了一些基本特性,例如下列所列举的这些:
-
成本节省:通过基础设施的设置费用减少,这些现在由 CSP 管理;这让用户将注意力集中在应用程序开发和业务运营上。
-
速度、灵活性和资源访问:所有资源都可以在任何地方通过几次点击获得,随时随地(取决于网络连接和速度)。
-
可靠性:资源托管在不同的地点,通过提供良好的质量控制、灾难恢复(DR)政策和防损措施;维护由 CSP 负责,这意味着最终用户无需浪费时间和金钱进行此类操作。
除了之前列举的优点(关键特性),还有可能存在一些缺点,例如以下几点:
-
性能变化:性能可能会因选择的 CSP 不同而有所变化,但市场上没有大品牌(如 AWS、Azure 或 GCP)存在明显的性能问题。在这些情况下,性能受用户本地互联网速度的影响,因此这并非 CSP 的问题。不过,对于其他 CSP 来说,情况可能并非如此。
-
停机时间:停机时间可能是一个问题,但所有主要提供商都努力提供 99.9%的正常运行时间。如果灾难发生,问题通常会在几分钟内解决——或者在最坏的情况下,也会在几个小时内解决。
-
不可预测性:关于云服务提供商(CSP)及其市场存在的不可预测性,但请放心,没有任何大公司会很快退出市场。
因此,这些都不会成为任何想要迁移到云端的人的阻碍。在接下来的章节中,我们将向您介绍一些 IaaS 解决方案。
介绍 IaaS 解决方案
IaaS 是云计算的支柱。它提供按需访问计算、存储、网络等资源。云服务提供商(CSP)使用虚拟化技术提供 IaaS 解决方案。在本节中,我们将为您提供一些最常用的 IaaS 解决方案的信息。我们将介绍一些主要供应商,例如Amazon Elastic Compute Cloud(Amazon EC2)和 Microsoft Azure 虚拟机,以及作为可行方案的 DigitalOcean。我们还会涉及 OpenStack,为那些对其可能提供的功能感兴趣的人提供信息。
Amazon EC2
AWS 提供的 IaaS 解决方案称为 Amazon EC2。它为任何人提供了一个很好的基础设施解决方案,从低成本的计算实例到用于机器学习的高性能图形处理单元(GPU)。AWS 是 12 年前第一个提供 IaaS 解决方案的供应商,尽管经历了新冠疫情,它的表现依然比以往更好(www.statista.com/chart/18819/worldwide-market-share-of-leading-cloud-infrastructure-service-providers/)。
当开始使用 Amazon EC2 时,您需要完成几个步骤:
-
第一步是选择您的Amazon 机器镜像(AMI),它基本上是一个预配置的 Linux 或 Windows 镜像。对于 Linux,您可以从以下选项中选择:
-
Amazon Linux 2(基于 CentOS/Red Hat Enterprise Linux(RHEL))
-
RHEL 8/9
-
SUSE Linux Enterprise Server(SLES)
-
Ubuntu Server 20.04/22.04 长期 支持(LTS)
-
-
您需要从各种实例类型中进行选择。要了解更多关于 EC2 实例的信息,请访问
aws.amazon.com/ec2/instance-types/并查看每个实例的详细信息。例如,EC2 是唯一提供基于 Mac mini 的 Mac 实例的供应商。使用 Linux 时,您可以根据需要选择从低端实例到高性能实例。 -
Amazon 提供了一个弹性块存储(EBS)选项,支持固态硬盘(SSD)和磁介质。您可以根据需求选择自定义值。与其他选项相比,EC2 是一个灵活的解决方案。它具有易于使用和直观的界面,您只需为使用的时间和资源付费。在 第十五章中,将为您提供如何在 EC2 上部署的示例,使用 AWS 和 Azure 部署到云端。
Microsoft Azure 虚拟机
微软是云市场的第二大玩家,仅次于亚马逊。Azure 是他们的云计算产品名称。尽管是微软提供的,但 Linux 是 Azure 上使用最广泛的操作系统(www.zdnet.com/article/microsoft-developer-reveals-linux-is-now-more-used-on-azure-than-windows-server/)。
Azure 的 IaaS 产品被称为虚拟机,与亚马逊的产品类似,你可以在多个等级之间选择。微软产品的不同之处在于定价模型。他们有按需付费模式,或基于一到三年的预定实例。微软的界面与亚马逊完全不同,我们认为可能不像其竞争对手那么直观,但最终你会习惯的。
微软提供几种类型的虚拟机实例,从经济型可爆发虚拟机到强大的内存优化实例。按需付费模式按小时计费,这可能会增加服务的最终账单,所以根据你的需求谨慎选择。对于 Linux 发行版,你可以选择以下几种:CentOS、Debian、RHEL、SLES for SAP、openSUSE Leap 和 Ubuntu Server。
Azure 也提供了非常强大的 SaaS 产品,如果你使用其他 Azure 服务,这将是一个不错的选择。在第十五章,与 AWS 和 Azure 一起部署到云端中将为你提供如何部署到 Azure 的示例。
其他强大的 IaaS 解决方案
DigitalOcean 是云市场的另一个重要玩家,提供强大的 IaaS 解决方案。它具有直观的界面,帮助你在非常短的时间内创建云。它们称虚拟机为滴水,你只需几秒钟就能创建一个。你只需要做以下几件事:
-
选择镜像(Linux 发行版)
-
根据你的虚拟 CPU(vCPU)、内存和磁盘空间需求选择计划
-
添加存储块
-
选择你的数据中心区域、身份验证方式(密码或安全外壳(SSH)密钥)以及主机名
-
你还可以将滴水分配给你管理的特定项目。
DigitalOcean 的界面比其竞争对手更具吸引力,且更加用户友好。效仿 DigitalOcean,其他 IaaS 提供商——如Linode和Hetzner——也提供简洁友好的界面来创建虚拟服务器。
Linode 是云市场的另一个强大竞争者,提供强大的解决方案。他们的虚拟机被称为 Linodes。与 DigitalOcean 和 Azure 相比,Linode 的界面在易用性和外观上处于两者之间。
另一个强有力的竞争者,至少在欧洲市场,是来自德国的云提供商 Hetzner。他们在资源和成本之间提供了很好的平衡,且提供的解决方案与本节中提到的其他提供商类似。Hetzner 提供了一个类似于 DigitalOcean 的界面,非常易于探索,而且云实例会在几秒钟内部署完成。
类似于 DigitalOcean、Linode 和 Hetzner 等提供商,亚马逊也推出了一项相对较新的服务(始于 2017 年),名为Lightsail。这项服务的推出旨在为客户提供一种简单的方式,在云端部署虚拟专用服务器(VPSs)或虚拟机(VMs)。其界面与竞争对手的界面相似,但在其基础上增加了完整的亚马逊基础设施可靠性。Lightsail 提供了几种发行版,并附带应用程序包。通过 Lightsail 在 AWS 上部署变得更加简便。这是一个有用的工具,能够吸引那些寻求快速且安全的解决方案来交付 Web 应用的新用户。
还有其他可用的解决方案,例如谷歌的解决方案,名为 GCE,这是谷歌云平台(Google Cloud Platform,简称 GCP)的 IaaS 解决方案。GCP 的界面与 Azure 平台上的界面非常相似。
重要说明
使用 GCP 时,一个有趣的方面是,当你想删除一个项目时,操作不会立即生效,而是将在一个月后安排删除。如果删除操作不是出于故意,并且你需要恢复该项目,这可以视为一种安全保障。
在接下来的部分中,我们将详细介绍一些 PaaS 解决方案。
引入 PaaS 解决方案
PaaS 是另一种形式的云计算。与 IaaS 相比,PaaS 提供了硬件层以及应用层。硬件和软件都由 CSP 托管,客户无需管理它们。PaaS 解决方案的客户在大多数情况下是应用开发者。提供 PaaS 解决方案的 CSP 与提供 IaaS 解决方案的 CSP 大致相同。主要的 PaaS 提供商包括亚马逊、微软和谷歌。接下来的小节中,我们将讨论一些 PaaS 解决方案。
亚马逊弹性贝叶斯托克(Elastic Beanstalk)
亚马逊提供了弹性贝叶斯托克(Elastic Beanstalk)服务,其界面简洁明了。你可以创建一个示例应用或上传你自己的应用,Beanstalk 会处理其余的工作,从部署细节到负载均衡、扩展和监控。你只需选择要部署的 AWS EC2 硬件实例。
接下来,我们将讨论另一家主要竞争者的服务:谷歌应用引擎(Google App Engine)。
谷歌应用引擎(Google App Engine)
Google 的 PaaS 解决方案是 Google App Engine,这是一种完全托管的无服务器环境,使用起来相对简单,支持多种编程语言。Google App Engine 是一个可扩展的解决方案,具有自动安全更新、托管基础设施和监控功能。它提供连接到 Google Cloud 存储解决方案的功能,并支持 Go、Node.js、Python、.NET 和 Java 等所有主要的 Web 编程语言。Google 提供具有竞争力的定价,并且其界面与我们在 IaaS 产品中看到的类似。另一个主要的参与者是 DigitalOcean,我们接下来将讨论这个平台。
DigitalOcean App Platform
DigitalOcean 提供了一种形式为App Platform的 PaaS 解决方案。它提供了一个直观的界面,可以直接与您的 GitHub 或 GitLab 仓库连接,并且是一个完全托管的基础设施。DigitalOcean 与 Amazon 和 Google 等大厂处于同一水平,使用 App Platform 时,它管理基础设施、资源分配、数据库、应用运行时及其依赖项以及底层操作系统。它支持 Python、Node.js、Django、Go、React 和 Ruby 等流行的编程语言和框架。DigitalOcean App Platform 采用开放的云原生标准,具有自动代码分析、容器创建和编排等功能。这个解决方案的一个独特优势是免费的入门级套餐,可以部署最多三个静态网站。对于原型开发动态 Web 应用程序,有基础套餐,而对于市场上的专业应用程序部署,则提供专业套餐。DigitalOcean 的界面友好,可能对新手尤其具有吸引力。它们的定价也是一个优势。
开源 PaaS 解决方案
除了前面提到的提供商的即用型解决方案,还有由Cloud Foundry、Red Hat OpenShift、Heroku等提供的开源 PaaS 解决方案,我们在本节中不详细介绍这些。然而,前面提到的三者至少值得简要介绍一下,接下来我们将简要讨论它们:
-
Red Hat OpenShift:这是一个用于应用部署的容器平台。它的基础是一个 Linux 发行版(RHEL),配备容器运行时以及用于网络、注册、认证和监控的解决方案。OpenShift 旨在成为一个可行的、混合的 PaaS 解决方案,并且与 Kubernetes 进行了完全集成(Kubernetes 将在接下来的章节中简要介绍,介绍 CaaS 解决方案,并在第十六章中更详细地讨论,使用 Kubernetes 部署应用程序)。OpenShift 通过收购 CoreOS(后面将讨论)来提供一些独特的解决方案。新的 CoreOS Tectonic 容器平台与 OpenShift 合并,为用户带来两者的最佳优势。
-
Cloud Foundry:这是一个为企业级应用设计的云平台,作为 PaaS 解决方案提供。它是开源的,可以部署在不同的基础设施上,从本地部署到 IaaS 提供商,如 Google GCP、Amazon AWS、Azure 或 OpenStack。它提供多种开发者框架,并可选择 Cloud Foundry 认证的平台,如 Atos Cloud Foundry、IBM Cloud Foundry、SAP Cloud Platform、SUSE Cloud Application Platform 和 VMware Tanzu。
-
Heroku:Heroku 是 Salesforce 旗下的公司,平台作为一种创新的 PaaS 解决方案被开发出来。它基于一个名为 Dynos 的容器系统,使用由容器管理系统运行的基于 Linux 的容器,旨在实现可扩展性和敏捷性。它提供完全托管的数据服务,支持 Postgres、Redis、Apache Kafka 和 Heroku Runtime,后者是负责容器编排、扩展和配置管理的组件。Heroku 还支持多种编程语言,如 Node.js、Ruby、Python、Go、Scala、Clojure 和 Java。
PaaS 为开发者提供了许多解决方案,帮助他们创建和部署应用程序,同时减轻管理基础设施的负担。正如你现在可能已经了解的,这一节中描述的许多解决方案依赖于容器技术。因此,在下一节中,我们将详细介绍 IaaS 的一个子集——CaaS,并向你介绍容器编排和专门为容器设计的操作系统。
介绍 CaaS 解决方案
CaaS 是 IaaS 云服务模型的一个子集。它允许客户在提供商托管的基础设施之上使用单独的容器、集群和应用程序。CaaS 可以根据客户的需求,在本地或云端使用。在 CaaS 模型中,容器引擎和编排由云服务提供商(CSP)提供并管理。用户与容器的交互可以通过 API 或 Web 界面进行。提供商使用的容器编排平台——主要是 Kubernetes 和 Docker——是一个重要因素,也是不同解决方案之间的关键区别。
我们在第十一章《虚拟机工作》和第十二章《使用 Docker 管理容器》中已经涉及过容器(和虚拟机),但并没有详细介绍容器编排或专门针对容器的微操作系统。接下来,我们将为你提供更多相关细节。
介绍 Kubernetes 容器编排解决方案
Kubernetes 是一个由 Google 开发的开源项目,用于自动化部署和扩展容器化应用程序。它是用 Go 编程语言编写的。名称“ Kubernetes”来自希腊语,意思是船长或舵手。Kubernetes 是一个用于自动化容器管理的工具,并与基础设施抽象和服务监控相结合。
许多新手将 Kubernetes 和 Docker 混淆,反之亦然。它们是互补的工具,每个工具都有特定的用途。Docker 创建一个容器(就像一个盒子),你可以在其中部署你的应用,而 Kubernetes 则负责容器(或盒子)在应用打包并部署之后的管理。Kubernetes 提供了一系列对运行容器至关重要的服务,如服务发现和负载均衡、存储编排、自动备份和自愈以及隐私保护。Kubernetes 架构由多个组件组成,这些组件对于任何管理员来说都是至关重要的。我们将在下一节中为你详细讲解。
介绍 Kubernetes 组件
当你运行 Kubernetes 时,你主要管理的是宿主机集群,这些宿主机通常是运行 Linux 的容器。简而言之,这意味着当你运行 Kubernetes 时,你在运行集群。以下是 Kubernetes 中的基本组件列表:
-
cluster 是 Kubernetes 的核心,因为它的唯一目的是管理大量的集群。每个集群至少由一个控制平面和一个或多个节点组成,每个节点在 Pod 内运行容器。
-
control plane 由控制节点的过程组成。控制平面的组件如下:
-
kube-apiserver:这是控制平面前端的 API 服务器。
-
etcd:这是集群内所有数据的键值存储。
-
kube-scheduler:它寻找没有分配节点的 Pods,并将它们连接到节点上运行。
-
kube-controller-manager:它运行控制器进程,包括节点控制器、复制控制器、端点控制器和令牌控制器。
-
cloud-controller-manager:这是一个允许你将集群链接到云提供商 API 的工具;它包括节点控制器、路由控制器和服务控制器。
-
-
Nodes 是运行 Pod 所需服务的虚拟机或物理机器。节点组件在每个节点上运行,负责维护正在运行的 Pods。组件如下:
-
kube-proxy:它负责每个节点的网络规则。
-
kubelet:它确保每个容器都在一个 Pod 内运行。
-
-
Pods 是在集群中运行的不同容器的集合。它们是工作负载的组件。
Kubernetes 集群非常复杂,需要大量的实践和专注才能掌握。理解它的相关概念需要大量的练习和投入。不管它有多复杂,Kubernetes 并不是为你做所有的事情。你仍然需要选择容器运行时(支持的运行时有 Docker、containerd 和 Container Runtime Interface (CRI)-O)、CI/CD 工具、存储解决方案、访问控制和应用服务。
管理 Kubernetes 集群超出了本章的范围,但您将在 第十六章 中学习到这部分内容,即 使用 Kubernetes 部署应用程序。这部分简短的介绍是为了帮助您理解 Kubernetes 使用的概念和工具。
除了 Kubernetes 之外,还有其他一些容器编排工具,如 Docker Swarm、Apache Mesos 和 HashiCorp 的 Nomad。它们是非常强大的工具,被全球许多人使用。我们在这里不会详细介绍这些工具,但我们认为至少在容器编排部分的末尾列出它们是有用的。在接下来的章节中,我们将为您提供一些关于云中容器解决方案的信息。
在云中部署容器
您可以在云中使用容器编排解决方案,以下是实现这一目标的关键服务:
-
亚马逊弹性容器服务 (ECS):亚马逊 ECS 是一个完全托管的容器编排服务。它提供了一个可选的无服务器解决方案 (AWS Fargate),并由亚马逊的一些关键服务内部运行,确保该工具经过测试,足够安全,任何人都可以使用。
-
亚马逊弹性 Kubernetes 服务 (EKS):亚马逊还提供了一个用于编排 Kubernetes 应用的 EKS 服务。它基于 亚马逊 EKS Distro (EKS-D),这是由亚马逊开发的一个 Kubernetes 发行版,基于原始的开源 Kubernetes。通过使用 EKS-D,您可以在本地、亚马逊自己的 EC2 实例上或在 VMware vSphere 虚拟机上运行 Kubernetes。
-
谷歌 Kubernetes 引擎 (GKE):GKE 提供了预构建的部署模板,并基于 CPU 和内存使用情况进行 Pod 自动扩展。扩展可以跨多个池进行,GKE Sandbox 提供了增强的安全性。GKE Sandbox 通过保护主机内核和运行应用程序来提供额外的安全层。除了谷歌和亚马逊,微软也提供了强大的容器编排解决方案 —— AKS。
-
微软 Azure Kubernetes 服务 (AKS):AKS 是一种用于部署容器化应用集群的托管服务。与其他提供商一样,微软提供了一个完全托管的解决方案,处理资源维护和健康监控。AKS 节点使用 Azure 虚拟机运行,并支持不同的操作系统,如微软 Windows Server 镜像。它还提供免费升级到最新可用的 Kubernetes 镜像。在其他解决方案中,AKS 提供了支持 GPU 的节点、存储卷支持,并与微软自家的 Visual Studio Code 集成了特殊的开发工具。
在了解了部署 Kubernetes 到云端的几种解决方案,并学习了 Kubernetes 的主要组件及其工作原理后,接下来的章节将讨论微服务在云计算中的重要性。
引入微服务
微服务是一种应用交付的架构风格。随着时间的推移,应用交付从单体模型演变为去中心化模型,这一切都要归功于云技术的进步。从 2006 年 AWS 的历史性发布开始,到 2007 年 Heroku 和 2010 年 Vagrant 的推出,应用部署也开始发生变化,以利用新的云服务。应用从拥有单一的、庞大的单体代码库,转变为每个应用都能受益于不同服务集的模型。这使得代码库变得更加轻量,并依赖于不同的服务。
让我们来解释一下单体应用与微服务架构应用之间的区别:
-
单体应用模型将所有功能集成在一个进程中,并通过简单的复制在多个服务器上部署。相比之下,微服务架构假设应用的功能被拆分为不同的服务。这些服务会根据用户需求分布并扩展到不同的服务器上。
-
微服务架构采用基于模块的方式。每个模块对应一个特定的服务。服务相互独立,通过基于 HTTP 协议的 REST API 连接。这意味着每个应用功能可以用不同的语言进行开发,具体选择哪种语言取决于哪种语言更适合。这种模块化方式还可以利用新的容器技术。
-
微服务架构以快速交付复杂应用而著称。它没有技术或语言的锁定;它为每个服务和组件提供独立的扩展和更新,不会干扰其他正在运行的服务;并且具有防故障架构。微服务模型可以通过将现有的单体应用拆分为独立的模块化服务来适应这些应用。不需要重写整个应用,只需将整个代码库拆分为更小的部分。由于其模块化的方式,微服务非常适合 DevOps 和 CI/CD 实践。
在接下来的部分,我们将为您介绍 DevOps 的实践和工具。
引入 DevOps
DevOps是一种文化。它的名字来源于开发(Development)和运维(Operations)的结合,旨在通过实践和工具实现快速交付。DevOps 强调的是速度、敏捷性和时间。我们都知道“时间就是金钱”这一说法,这在 IT 行业尤为适用。能够快速交付服务和应用,往往是业务成功与在市场中被淘汰的关键。
DevOps 是一种不同团队之间合作的模型,这些团队参与交付服务和应用程序。这意味着整个生命周期,从开发、测试到部署和管理,都是由在每个阶段都同等参与的团队完成的。DevOps 模型假设没有团队在封闭的环境中操作,而是以透明的方式操作,以便实现成功所需的敏捷性。还有一种不同的 DevOps 模型,其中安全性和质量保证团队在开发周期中同样重要。这就是DevSecOps。
对于 DevOps 模型来说,自动化过程是通过特定工具创建的,这些自动化过程至关重要。敏捷性和速度的思维方式推动了与 DevOps 相关的一个新名词的兴起,那就是 CI/CD。CI/CD 思维方式确保每个开发步骤是连续的,没有中断。为了支持这种思维方式,出现了新的自动化工具。也许最广为人知的是开源自动化工具Jenkins。这是一个模块化工具,可以通过插件扩展。围绕该应用程序的生态系统相当庞大,有成百上千个插件可供选择。Jenkins 是用 Java 编写的,旨在自动化软件开发过程,从构建和测试,到交付。Jenkins 的一个重要特点是能够通过使用专门的插件来创建管道。管道是一个工具,它为应用程序生命周期中的 CD 提供自动化支持。Jenkins 可以在本地或云端使用,它也可以作为 SaaS 服务来使用。
DevOps 哲学不仅仅与应用程序部署有关;健康的 CD 和 CI 与基础设施的状态密切相关。在这方面,像 Ansible、Puppet 和 Chef 这样的云管理工具对于管理支持应用程序部署的基础设施非常有用。这就是为什么基础设施级别的配置和管理至关重要。在接下来的部分中,你将学习云基础设施管理。
探索云管理工具
当今的软件开发和部署依赖于大量的物理系统和虚拟机。管理所有与开发、测试和生产相关的环境是一项繁琐的任务,并且需要使用自动化工具。云基础设施管理中最广泛使用的解决方案是像Ansible、Puppet和Chef Infra这样的工具。所有这些配置管理工具都非常强大和可靠,我们将在第十七章,使用 Ansible 进行基础设施和自动化中,教你如何使用其中的一个工具:Ansible。尽管如此,我们将在本节中简要介绍它们。
Ansible
Ansible 是一个目前由 Red Hat 拥有的开源项目。它被认为是一个简单的自动化工具,用于各种操作,如应用部署、配置管理、云供应和服务编排。它是用 Python 开发的,使用节点的概念来定义系统类别,控制节点作为运行 Ansible 的主机,而不同的受管节点是由主机控制的其他机器。所有节点通过 SSH 连接,并通过一个名为 Ansible 模块 的应用进行控制。每个模块在受管节点上执行特定任务,任务完成后,该模块会从节点上移除。
模块的使用方式由 Ansible 的剧本决定。剧本使用 YAML 编写(YAML Ain’t Markup Language 的递归缩写),YAML 是一种主要用于配置文件的语言。Ansible 还使用了清单的概念,其中包含被管理节点的列表。运行节点上的命令时,可以根据清单中的列表,依据模式来应用命令。Ansible 会根据某一模式在每个节点或节点组上应用命令。Ansible 被认为是最简单的自动化工具之一。它支持 Linux/Unix 和 Windows 客户端机器,但主机必须是 Linux/Unix。
Puppet
Puppet 是最古老的自动化工具之一。Puppet 的架构与 Ansible 不同。它采用了主服务器和代理的概念。Puppet 使用基于 Ruby 编程语言的领域特定语言(DSL)编写基础设施代码。代码在主服务器上编写,传输到代理端,然后转换为在目标系统上执行的命令。Puppet 还有一个名为 Facter 的清单工具,用于存储关于代理的数据,如主机名、IP 地址和操作系统。存储的信息以清单的形式发送回主服务器,然后转换成名为目录的 JSON 文档。所有清单都保存在模块内,模块是用于特定任务的工具。每个模块包含以代码和数据形式的信息。这些数据是由名为 Hiera 的工具集中管理的。所有由 Puppet 生成的数据都存储在数据库中,并通过每个需要管理它的应用程序的 API 进行管理。与 Ansible 相比,Puppet 看起来要复杂得多。Puppet 的主服务器仅支持 Linux/Unix。
Chef Infra
Chef Infra 是另一种自动化工具。它采用客户端-服务器架构,使用烹饪书和食谱的概念。其主要组件如下:
-
Chef 服务器:这类似于一个中心枢纽,处理所有配置数据。它主要用于将烹饪书上传到 Chef 客户端。
-
Chef Client:这是一个安装在你所管理的基础设施中每个节点上的应用程序。
-
Chef Workstation:Chef Workstation 管理用于基础设施管理的食谱。
Chef Infra 使用与 Puppet 类似的基于 Ruby 的代码,称为 DSL。服务器需要安装在 Linux/Unix 上,客户端也支持 Windows。
本节中介绍的所有自动化和配置工具使用了不同的架构,但都做相同的事情,即提供一个抽象层来定义基础设施的期望状态。每个工具都有其独特之处,拥有各自的优缺点。Chef Infra 和 Puppet 可能由于其基于 Ruby/DSL 的代码而具有更陡峭的学习曲线,而 Ansible 由于其更简单的架构和使用 Python 编程语言,可能更易于上手。然而,无论选择哪个工具,你都不会出错。
总结
在本章中,我们通过展示一些最重要的概念、工具和解决方案,向你介绍了云计算。这些内容应该足够让你开始学习云技术,而云技术是一个非常广泛且复杂的主题。如需更多详细信息,请参阅进一步 阅读部分。
我们讨论了云标准,这是一个重要但常被忽视的话题,还讲解了主要的云类型和服务。现在你已经对每种“即服务”解决方案的含义有了大致了解,并知道它们之间的主要区别。你了解了最重要的解决方案,以及它们是如何由这个领域的主要玩家提供的:亚马逊、谷歌和微软。我们向你介绍了 Kubernetes 容器编排及其工作原理,讲解了 API、最小化的容器专用操作系统、DevOps 文化、微服务和基础设施自动化工具。你在本章中学到了很多内容,但请记住,所有这些主题只是云计算的冰山一角。
在下一章中,我们将向你介绍云部署的更实用的一面。你将学习如何在主要云平台(如 AWS、Azure 和 GCP)上部署 Linux。
进一步阅读
如果你想深入了解云技术,请查阅以下书籍:
-
《OpenStack 架构师指南 – 第二版》 由 Ben Silverman 和 Michael Solberg 编著,Packt 出版社
-
《学习 DevOps – 第二版》 由 Mikael Krief 编著,Packt 出版社
-
《通过模式与原则设计微服务架构 [视频]》 由 Mehmet Ozkaya 编著,Packt 出版社
-
《多云策略 – 云架构师第二版》 由 Jeroen Mulder 编著,Packt 出版社
-
《云原生无服务器解决方案架构》 由 Safeer CM 编著,Packt 出版社
第十五章:使用 AWS 和 Azure 部署到云
近年来,从本地计算平台到私有云和公共云的转变已经显著增加。在这个变化迅速且加速的世界里,在一个高度可扩展、高效且安全的基础设施中部署和运行应用程序对于全球各地的企业和组织至关重要。另一方面,与当前公共云的服务相比,维护本地计算资源所需的成本和专业知识几乎无法证明其合理性。大大小小的企业和团队都在越来越多地采用公共云服务,尽管大企业相对较慢采取行动。
云计算最好的隐喻之一就是应用服务随时可用。需要更多的应用资源吗?只需打开水龙头,按需配置虚拟机或实例,数量随意(水平扩展)。或者,对于某些实例,您可能需要更多的 CPU 或内存(垂直扩展)。当不再需要这些资源时,只需关闭 水龙头。
公共云服务以相对低廉的价格提供所有这些功能,消除了您在维护本地基础设施时可能面临的运营开销,这些基础设施需要容纳此类功能。
本章将介绍Amazon Web Service(AWS)和Microsoft Azure——两大主要公共云提供商——并提供一些有关如何在云中部署应用程序的实用指导。特别是,我们将重点介绍典型的云管理工作负载,使用 Web 管理控制台和命令行接口。
到本章结束时,您将学会如何使用 AWS 的 Web 控制台和 AWS CLI,以及 Azure 的 Web 门户和 Azure CLI,来管理您在这两个当今最流行的云提供商的云资源。您还将学会如何在云中创建和启动资源时做出明智的决策,在性能与成本之间找到合理的平衡。
我们希望 Linux 管理员——无论是新手还是经验丰富的专家——都能觉得本章内容相关且令人耳目一新。我们的重点纯粹是实践的,我们将探讨 AWS 和 Azure 的云工作负载。我们将避免对两者进行比较,因为这超出了本章的范围。为了让内容不至于枯燥,我们还会避免在描述 AWS 和 Azure 管理任务时追求完全对称。我们都知道 AWS 首先开辟了公共云领域的道路,其他主要云提供商随之而来,采纳并有时改进了底层的范式和工作流程。由于我们首先介绍 AWS,我们将在一些云资源配置概念(如区域和可用区(AZs))方面讲解得更为详细,这些在很多方面与 Azure 中的内容非常相似。
最后,我们将 AWS 或 Azure 的选择权留给你。我们为你提供了地图,道路由你来走。
以下是你将学习的一些关键主题:
-
使用 AWS EC2
-
使用 Microsoft Azure
技术要求
完成本章任务时,你需要以下内容:
-
如果你想跟随实际示例进行操作,需拥有 AWS 和 Azure 账户。两个云服务提供商都提供免费的订阅服务:
-
AWS 免费层:
aws.amazon.com/free -
Microsoft Azure 免费账户:
azure.microsoft.com/en-us/free/
-
-
一台你选择的 Linux 发行版的本地机器,用于安装和实验 AWS CLI 和 Azure CLI 工具。
-
一款现代的 web 浏览器(例如 Google Chrome 或 Mozilla Firefox),用于执行 AWS 和 Azure 的 web 控制台管理任务。你可以在任何平台上访问相关门户。
-
一台 Linux 命令行终端,具备中级水平的 shell 使用能力,以便运行 AWS 和 Azure CLI 命令。
重要提示
在操作过程中,请确保在完成实验后关闭你的 EC2 实例或 Azure 虚拟机。如果未关闭,可能会导致相对较高的账单产生。
现在,让我们从第一个候选者 AWS EC2 开始。
使用 AWS EC2
AWS 弹性计算云(EC2)是一种可扩展的计算基础设施,允许用户租用虚拟计算平台和服务来运行其云应用。近年来,AWS EC2 因其卓越的性能和可扩展性,以及相对具有成本效益的服务计划而获得了极大的普及。本节提供了一些基本功能知识,帮助你开始部署和管理运行应用程序的 AWS EC2 实例。特别地,我们将介绍 EC2 实例类型,特别是如何区分不同的配置和相关的定价层次,如何使用 SSH 连接并使用 SCP 来传输文件到 EC2 实例,如何使用 AWS CLI 进行操作。
到本节结束时,你将对 AWS EC2 有基本了解,并能选择、部署和管理你的 EC2 实例。
介绍并创建 AWS EC2 实例
AWS EC2 提供了多种实例类型,每种类型的配置、容量、定价和使用场景都有所不同。选择不同的 EC2 实例类型并非易事。本节将简要描述每种 EC2 实例类型,使用它们的优缺点,以及如何选择最具成本效益的解决方案。针对每种实例类型,我们将展示如何通过 AWS 控制台启动一个实例。
我们可以从两个角度来考虑 EC2 实例类型。决定使用哪种 EC2 实例时,必须同时考虑这两个因素。我们简要介绍一下这两种选择:
-
配置:您的 EC2 实例的容量和计算能力。每种 EC2 实例配置类型的主要区别在于计算能力,计算能力通过 vCPU(或 CPU)、内存(RAM)和存储(磁盘容量)来表示。
一些 EC2 实例类型还提供图形处理单元(GPU)或现场可编程门阵列(FPGA)计算能力。关于 EC2 实例配置类型的详细介绍超出了本章的范围。您可以访问
docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-types.html或aws.amazon.com/ec2/instance-types/探索相关信息。 -
type和region -
抢占实例:这些 EC2 实例的价格低于按需实例,且可供重新使用。
-
专用实例:这些 EC2 实例运行在为单一付款账户分配的虚拟私有云(VPC)中。
我们将在接下来的章节中介绍这些 EC2 实例类型。对于每种类型,我们将展示如何启动相应的实例。
然而,在创建实例之前,我们将先了解关于 EC2 实例的另一个关键概念——可用区(AZ)。
EC2 可用区(AZ)
AWS EC2 服务在全球多个地点提供,称为区域。以下是一些区域的例子:
-
us-west-2) -
ap-south-1)
EC2 在一个区域定义了多个可用区(AZ),这些可用区本质上是一个或多个数据中心。区域之间的基础设施完全隔离,以提供故障容忍和高可用性。如果一个区域不可用,只有该区域内的 EC2 实例无法访问。其他区域及其 EC2 实例将继续正常运行。
类似地,在提供高可用性和容错的 EC2 服务时,AZs 之间是相互连接的。启动一个 EC2 实例时,它会在 AWS 控制台中选定的当前区域创建。AWS EC2 管理员可以在管理 EC2 实例时在不同区域之间切换。只有所选区域内的实例才会在 EC2 管理控制台中显示。EC2 管理员通常会根据访问 EC2 实例的用户地理位置来选择一个区域。
现在我们已经对各种 EC2 实例类型有了初步了解,让我们来看看按需实例。
EC2 按需实例
AWS EC2 按需实例采用按需付费定价模式,按秒计费,无需长期合同。按需实例最适合用于实验性的不确定工作负载,尤其是在资源使用量不完全确定的情况下(例如在开发过程中)。与预留实例相比,这些按需实例的灵活性虽然较高,但价格也更贵。
让我们启动一个按需实例:
- 首先,我们必须登录到我们的 AWS 账户控制台,网址为
console.aws.amazon.com。下图显示了默认控制台视图,在这里你可以看到用户信息(1)、区域(2)以及可用的(或最近访问的)服务(3):

图 15.1 – AWS 管理控制台
- 接下来,我们必须从左侧列表中选择 EC2 服务,如上图所示。这将引导我们进入 EC2 仪表盘界面,如下所示:

图 15.2 – EC2 仪表盘
如上图所示,启动实例按钮将启动创建按需实例的逐步过程。这将引导我们进入一个新界面,我们可以在其中提供新实例的主要配置选项,如名称、操作系统、实例类型、登录密钥对、网络和存储设置。
- 接下来,我们需要选择一个名称和标签。首先,我们将为实例选择一个名称。在我们的案例中,我们将名称设置为
aws_packt_testing_1:

图 15.3 – 为新 EC2 实例命名
或者,我们可以添加一个新标签。为此,我们必须点击 env 并设置值为 packt。如果需要,我们可以为给定的实例添加多个标签(键值对):

图 15.4 – 向 EC2 实例添加标签
- 接下来,我们必须选择一个操作系统。我们将选择 Ubuntu 22.04 LTS 作为新 EC2 实例的 Linux 发行版,如下图所示:

图 15.5 – 为 EC2 实例选择操作系统
还有其他操作系统可供选择,包括 macOS、Microsoft Windows 和 Linux 发行版,如 Red Hat Enterprise Linux、SUSE Linux Enterprise 或 Debian Linux,以及 Amazon Linux 和 Ubuntu。
- 现在,我们可以选择一个实例类型。在这里,我们将根据我们的配置需求选择实例类型。在我们的案例中,我们将选择 t2.micro 类型,配有 1 个 vCPU 和 1 GB 内存:

图 15.6 – 选择实例类型 – t2.micro
- 在 网络设置 下,我们将暂时保持默认设置不变:

图 15.7 – 设置新 EC2 实例的网络
- 下一步是配置存储。默认情况下,EC2 实例提供一个 8 GB 的 SSD 卷,但在使用免费套餐时,你可以将其设置为 30 GB。我们将使用默认的 8 GB:

图 15.8 – 为 EC2 实例设置存储
- 启动新 EC2 实例的最后一步是查看提供的详细摘要,并决定是否需要进行其他更改。一旦新实例根据我们的需求进行定制,就可以通过点击启动实例按钮来创建它,如下图所示:

图 15.9 – 新 EC2 实例和启动实例按钮的总结
现在,我们已经准备好通过按下启动实例按钮来启动实例。或者,我们可以通过重新访问前面的步骤来继续进行其他配置,否则 EC2 将分配一些默认值。
- 启动 EC2 实例时,我们需要创建或选择一个证书密钥对,以进行远程 SSH 访问实例。此时,屏幕上会高亮显示密钥对部分,我们可以点击
packt_aws_key,选择RSA类型和.pem文件格式,然后点击右下角的创建密钥对按钮:

图 15.10 – 选择或创建用于 SSH 访问的证书密钥对
系统会自动提示你将新生成的.pem文件下载到本地机器上的某个位置。
-
将相关文件(
packt-ec2.pem)下载到本地机器的安全位置,在那里你可以使用ssh命令访问 EC2 实例:env: packt tag, we’ll get a view of the EC2 instance we just created:

图 15.11 – 处于运行状态的 EC2 实例
有关按需实例的更多信息,请访问 docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-on-demand-instances.html。
现在我们已经学习了启动按需实例的基础知识,让我们来看看预留实例。
EC2 预留实例
使用预留实例时,我们租用特定类型的 EC2 计算能力,租期为特定时间。这段时间被称为期限,可以是 1 年或 3 年的承诺。购买预留实例时需要提前设置的主要特征如图 15.12所示:
-
平台:例如,Linux。
-
租赁方式:运行在默认(共享)或专用硬件上。
-
提供类别:这些是可用的预留实例类型。选项如下:
-
标准:一个简单的预留实例,具有明确的一组选项
-
可转换:它允许进行特定的更改,例如修改实例类型(例如,从t2.large更改为t2.xlarge)
-
-
实例类型:例如,t2.large。
-
期限:例如,1 年。
-
支付选项:全部提前支付,部分提前支付,无提前支付。
在这些选项及其不同等级中,你的费用取决于涉及的云计算资源以及服务的持续时间。例如,如果你选择提前支付全部费用,你将获得比其他方式更好的折扣。从前面提到的选项中选择,最终是为了节省成本和提高灵活性。
购买保留实例的一个类比是 移动电话套餐 —— 你决定所有想要的选项,然后承诺一定时间。对于保留实例,你在做更改时的灵活性较小,但可以节省大量成本——有时与按需实例相比,最多可节省 75%。
要启动一个保留实例,进入 AWS 控制台中的 EC2 仪表板,在左侧面板的 实例 下选择 保留实例,然后点击 购买保留实例 按钮。以下是购买保留 EC2 实例的示例:

图 15.12 —— 购买保留 EC2 实例
关于 EC2 保留实例的更多信息,请访问 docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-reserved-instances.html。
我们已经了解到,保留实例是按需 EC2 实例的一个经济实惠的替代方案。现在,让我们进一步了解另一种通过使用 Spot 实例来降低成本的方法。
EC2 Spot 实例
Spot 实例是一个空闲实例,等待被租用。Spot 实例的空闲时间以及是否可供你使用,取决于 EC2 中所请求的容量的总体可用性,前提是相关成本不超过你愿意为 Spot 实例支付的金额。AWS 宣传的 Spot 实例相较于按需 EC2 定价,最多可享受 90% 的折扣。
使用 Spot 实例的主要警告是,当所需容量不再按最初商定的价格提供时,可能会出现 没有空位 的情况。在这种情况下,Spot 实例将停止(并可能会被租赁到其他地方)。AWS EC2 会在停止 Spot 实例前提供 2 分钟的预警。这个时间应当用于妥善关闭实例内运行的应用程序工作流。
Spot 实例最适合非关键任务,在这种情况下,应用处理可能会随时被中断,并可以稍后恢复,而不会造成重大损害或数据丢失。这类任务可能包括数据分析、批处理和可选任务。
要启动一个 Spot 实例,进入你的 EC2 仪表板,在左侧菜单中选择 Spot 请求。在 实例 下,点击 请求 Spot 实例,并按照 EC2 按需 实例 部分中描述的步骤操作。
启动抢占实例的详细说明超出了本章的范围。AWS EC2 控制台在描述和协助相关选项方面做得非常好。如需了解有关抢占实例的更多信息,请访问docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-spot-instances.html。
我们知道,默认情况下,EC2 实例运行在共享硬件上,这意味着多个 AWS 客户拥有的实例共享同一台机器(或虚拟机)。但如果你想拥有一个专用平台来运行你的 EC2 实例呢?接下来我们将介绍专用实例。
EC2 专用实例
特定企业要求应用程序在专用硬件上运行,并且不与任何人共享平台。AWS EC2 提供了专用主机和专用实例来满足这一需求。如你所料,专用实例的费用会比其他实例类型更高。那么,为什么我们要关注租赁这些实例呢?
有些企业——特别是在金融、健康和政府机构中——需要依法满足严格的合规要求来处理敏感数据,或者需要获取基于硬件的许可证来运行他们的应用程序。
如果没有专用主机,专用实例的 EC2 会保证你的应用程序运行在专门为你提供的虚拟化管理程序上,但不会强制要求固定的机器或硬件。换句话说,你的一些实例可能会运行在不同的物理主机上。选择专用主机和专用实例将始终确保提供一个完全专用的环境——包括虚拟化管理程序和主机——用于专门运行你的应用程序,不与其他 AWS 客户共享底层平台。
启动专用实例时,你可以按照以下步骤进行:
-
从本章前面描述的启动按需 EC2 实例的相同步骤开始,参见EC2 按需 实例部分。
-
你可以在启动过程中添加另一个步骤。为此,你必须打开高级详情下拉部分,并滚动到租用方式选项,在那里你必须选择专用 – 运行专用实例,如下面的截图所示:

图 15.13 – 启动专用 EC2 实例
- 如果你想在专用主机上运行专用实例,你必须先创建一个专用主机。为此,在EC2仪表板中,导航到实例 | 专用主机:

图 15.14 – 创建专用 EC2 主机
- 按照 EC2 向导的步骤,根据你的偏好分配专用主机。创建主机后,你可以按照之前的步骤启动专用实例,并选择 专用主机 – 在专用主机上启动此实例 作为 租用方式,如 步骤 2 所示。
有关专用主机的更多信息,请访问aws.amazon.com/ec2/dedicated-hosts/。关于专用实例的详细信息,请参见aws.amazon.com/ec2/pricing/dedicated-instances/。
我们将在这里结束 AWS EC2 实例类型的学习之旅。更多信息,请访问docs.aws.amazon.com/AWSEC2/latest/UserGuide/Instances.html。
接下来,我们将探讨 EC2 部署的两个关键功能之一,这些功能能够帮助你在部署和扩展 EC2 实例时变得更加高效和资源丰富——Amazon 机器镜像(AMIs)和放置组。本章我们将只讨论放置组。有关 AMI 的更多信息,请访问docs.aws.amazon.com/AWSEC2/latest/UserGuide/AMIs.html。放置组控制着实例如何在 EC2 基础设施中分布,以实现高可用性和优化工作负载。接下来我们将讨论这个内容。
介绍 AWS EC2 放置组
放置组允许你指定 EC2 实例在底层 EC2 硬件或虚拟机监控器上的放置方式,根据你的需求提供将实例分组或分开的策略。放置组是免费的。
有三种放置组可供选择。让我们快速浏览一下这些类型,并查看它们的使用场景:
-
集群放置组:在集群放置组中,实例被放置在同一个可用区(数据中心)内。它们最适合低延迟、高吞吐量的实例间通信,但不适用于与外部世界的通信。具有高性能计算或数据复制的应用程序会从集群放置中受益,但例如 web 服务器则不太适用。
-
分布式放置组:当你启动多个 EC2 实例时,它们可能会运行在同一台物理机器或虚拟机监控器上。如果单点故障(如硬件故障)对应用程序至关重要,这种情况可能不太理想。分布式放置组为实例之间提供硬件隔离。换句话说,如果你在分布式放置组中启动多个实例,系统保证它们将运行在不同的物理机器上。在罕见的 EC2 硬件故障情况下,只有一个实例会受到影响。
-
分区放置组:分区放置组将您的实例按逻辑方式分组(分区),并在分区之间提供硬件隔离,但不在实例级别进行隔离。我们可以将这种模式视为集群放置组和扩展放置组的混合体。当您在分区放置组内启动多个实例时,EC2 会尽力在分区之间均匀分配实例。例如,如果您有四个分区和 12 个实例,EC2 会将每个节点(分区)放置三个实例。我们可以将分区视为由多个实例组成的计算单元。在发生硬件故障时,隔离的分区实例仍然可以相互通信,但不能跨分区进行通信。分区放置组最多支持一个逻辑分区内的七个实例。
要创建一个放置组,请在EC2仪表板左侧菜单中导航至网络与安全 | 放置组,然后点击创建放置组按钮。在接下来的界面中,您必须为放置组选项指定一个名称,并设置一个放置策略值。您还可以选择添加标签(键值对),用于组织或标识您的放置组。完成后,点击创建 组按钮:

图 15.15 – 创建放置组
如需了解更多有关 EC2 放置组的信息,请访问docs.aws.amazon.com/AWSEC2/latest/UserGuide/placement-groups.html。
现在我们已经熟悉了各种 EC2 实例类型,接下来让我们看看如何使用我们的实例。
使用 AWS EC2 实例
在本节中,我们将简要介绍一些关于实例的基本操作和管理概念。首先,我们将了解 EC2 实例的生命周期。
EC2 实例的生命周期
在使用或管理 EC2 实例时,了解从启动到运行,再到休眠、关机或终止的过渡阶段非常重要。每个状态都会影响计费以及我们访问实例的方式:

图 15.16 – EC2 实例的生命周期
让我们详细了解一下:
-
PENDING状态对应于实例的启动和初始化阶段。
-
从PENDING到RUNNING的过渡并非总是立刻发生,实例内运行的应用程序可能需要一段时间才能响应。EC2 会在实例处于RUNNING状态时开始计费,直到过渡到STOPPED状态。
-
在RUNNING状态下,我们可以根据需要重启实例。在REBOOTING状态下,EC2 始终会在同一主机上重启实例,而停止并重新启动并不总是能保证实例仍在同一主机上。
-
在 停止(STOPPED) 状态下,我们将不再为实例收费,但与实例附加的任何额外存储(根卷除外)仍然会产生费用。
-
当实例不再需要时,我们可以选择 停止(STOPPING) 或 休眠(HIBERNATING) 状态。通过 休眠(HIBERNATING),我们避免了 待处理(PENDING) 状态启动时可能出现的延迟。如果我们不再使用实例,可以选择终止它。终止后,实例将不再产生任何费用。终止实例时,可能会在 EC2 仪表板中显示一段时间,直到其被永久移除。
我们可以使用 SSH 连接到处于运行状态的 EC2 实例。在下一节中,我们将向你展示如何操作。
连接到 AWS EC2 实例
一般来说,EC2 实例的目的是运行特定的应用程序或一组应用程序。相关平台的管理和维护通常需要终端访问。访问这些实例(或任何其他服务)的方式由 AWS 如何将可用服务区分为两个不同的概念来决定,这些概念类似于网络中的控制平面和数据平面。这些概念代表了如何访问 EC2 实例:
-
控制平面(或管理平面)访问:使用 AWS EC2 控制台和 SSH 终端,我们可以对 EC2 实例执行管理任务。
-
数据平面访问:运行在 EC2 实例上的应用程序也可能暴露其特定的端点(端口)以便与外部世界进行通信。EC2 使用安全组来控制相关的网络流量。
在本节中,我们将简要了解控制平面和数据平面访问。特别是,我们将讨论如何使用 SSH 连接到 EC2 实例以及如何使用 SCP 进行文件传输。
通过 SSH 连接到 EC2 实例
在控制平面访问模式下,使用 SSH 与我们的 EC2 实例连接允许我们像管理任何本地网络机器一样管理它。相关的 SSH 命令如下:
ssh -i SSH_KEY ec2-user@EC2_INSTANCE
让我们分解这个过程,以便更好地理解:
-
SSH_KEY代表我们在启动实例时创建并下载的本地系统上的私钥文件。更多详细信息请参见 EC2 按需实例 部分。 -
ec2-user是 EC2 默认分配给我们 AMI Linux 实例的用户。不同的 AMI 可能有不同的用户名来进行连接。你应该向你选择的 AMI 提供商查询默认的 SSH 用户名。例如,使用ec2-user,使用ubuntu,以及使用admin。 -
EC2_INSTANCE代表我们 EC2 实例的公共 IP 地址或 DNS 名称。你可以在 EC2 仪表板中找到此信息:

图 15.17 – EC2 实例的公共 IP 地址和 DNS 名称
在我们的例子中,SSH 命令如下:
ssh -i packt_aws_key.pem ubuntu@3.68.98.173
然而,在我们连接之前,我们需要为 private key 文件设置正确的权限,以确保它不会被公开查看:
chmod 400 packt_aws_key.pem
如果不这样做,在尝试连接时会出现未保护密钥文件的错误。
成功的 SSH 连接到我们的 EC2 实例会显示以下输出:

图 15.18 – 通过 SSH 连接到 EC2 实例
此时,我们可以像操作标准计算机一样与我们的 EC2 实例交互。
接下来,我们来看看如何从 EC2 实例传输文件。
使用 SCP 进行文件传输
要在数据平面访问模式下将文件传输到 EC2 实例,我们必须使用 scp 工具。scp 使用 安全复制协议 (SCP) 在网络主机之间安全地传输文件。
以下命令将本地文件(README.md)复制到我们的远程 EC2 实例(因此它必须在我们的机器上运行,而不是在 EC2 实例上):
scp -i packt_aws_key.pem README.md ubuntu@3.68.98.173:~/
文件被复制到 EC2 实例上 ubuntu 用户的主文件夹(/home/ubuntu)。从远程实例传输 README.md 文件到本地目录的反向操作如下:
scp -i packt_aws_key.pem ubuntu@3.68.98.173:~/README.md .
请注意,scp 命令的调用方式与 ssh 相似,我们通过 -i(身份文件)参数指定私钥文件(aws/packt-ec2.pem)。
接下来,我们将探讨另一个管理和扩展 EC2 实例的关键方面——存储卷。
使用 EC2 存储卷
存储卷是 EC2 实例中的设备挂载,提供额外的磁盘容量(需要额外费用)。例如,你可能需要更多的存储空间来缓存大文件或进行大量日志记录,或者你可能选择挂载网络附加存储以共享关键数据。你可以将 EC2 存储卷视为模块化硬盘。根据需求,你可以挂载或卸载它们。
EC2 提供两种类型的存储卷:
-
实例存储
-
弹性块存储 (EBS)
了解如何使用存储卷能帮助你在应用程序扩展时做出更好的决策。首先,让我们来看看实例存储卷。
介绍实例存储卷
实例存储卷是直接(物理)附加到 EC2 实例的磁盘。因此,你可以连接到实例的最大实例存储卷数量和大小受到实例类型的限制。例如,存储优化型 i3 实例最多可以附加 8 个 1.9 TB SSD 磁盘,而通用型 m5d 实例最多只能连接四个每个 900 GB 的磁盘。有关实例容量的更多信息,请参见 aws.amazon.com/ec2/instance-types/。
如果实例存储卷是根卷,即用于启动实例的操作系统平台卷,则不会产生额外费用。
并非所有 EC2 实例类型都支持实例存储卷。例如,通用型的 t2 实例类型仅支持 EBS 存储卷。另一方面,如果你希望扩展存储,超出实例存储允许的最大容量,你将必须使用 EBS 卷。接下来我们将讨论 EBS 卷。
介绍 EBS 卷
实例存储卷上的数据仅在你的 EC2 实例上持久化。如果你的实例停止、终止或发生故障,所有数据将丢失。要在 EC2 实例中存储和持久化关键数据,你必须选择 EBS。
EBS 卷是灵活且高性能的网络附加存储设备,既为根卷系统提供支持,也为你的 EC2 实例提供额外的卷挂载。一个 EBS 根卷一次只能附加到一个 EC2 实例,而一个 EC2 实例可以随时附加多个 EBS 卷。通过 多重附加,一个 EBS 卷也可以同时附加到多个 EC2 实例。有关 EBS 多重附加的更多信息,请参见 docs.aws.amazon.com/AWSEC2/latest/UserGuide/ebs-volumes-multi.html。
当你创建 EBS 卷时,它会在你实例的可用区(AZ)内自动复制,以最小化延迟和数据丢失。使用 EBS,你可以通过 Amazon CloudWatch 免费监控磁盘健康状况和统计信息。EBS 还支持加密数据存储,以满足最新的数据加密法规标准。
EC2 存储卷由 Amazon 的 简单存储服务 (S3) 或 弹性文件系统 (EFS) 基础设施支持。有关不同 EC2 存储类型的更多信息,请访问 docs.aws.amazon.com/AWSEC2/latest/UserGuide/Storage.html。
现在,让我们创建并配置一个 EBS 存储卷,并将其附加到我们的 EC2 实例。
配置 EBS 存储卷
以下是我们将遵循的步骤,首先是创建 EBS 卷:
- 在 EC2 仪表板中,转到左侧导航窗格中的 弹性块存储 下的 卷,然后点击顶部的 创建卷 按钮:

图 15.19 – 创建 EBS 卷
-
输入你选择的 卷类型、大小 和 可用区 的值。确保选择与你的 EC2 实例所在的 AZ 匹配的可用区。如果你希望从以前的 EC2 实例备份(快照)恢复该卷,你还可以包含一个 快照 ID 值。稍后我们将在本章中讨论使用 EBS 快照进行备份/恢复。
-
完成后点击 创建卷 按钮。如果一切顺利,你将看到一个 卷创建成功 的消息,其中会指定你的新 EBS 卷 ID。
-
点击 卷 ID 或从左侧导航窗格中选择卷,在 弹性块存储 和 卷 下。点击 操作 按钮并选择 附加卷:

图 15.20 – 将 EBS 卷附加到 EC2 实例
- 在下一个屏幕上,在 实例 字段中输入您的 EC2 实例 ID(或名称标签以搜索它):

图 15.21 – 输入 EC2 实例 ID 以附加卷
-
完成后,按下 附加卷 按钮。几秒钟后,EC2 将初始化您的 EBS 卷,状态 将变为 正在使用。卷设备现在已准备好,但我们需要格式化它以便可以使用。
-
让我们通过 SSH 连接到我们附加了卷的 EC2 实例:
lsblk command-line utility to list the block devices:lsblk
The output is as follows:

图 15.22 – 我们 EC2 实例中的本地卷
查看卷的大小,我们可以立即分辨出刚添加的那个 —— xvdf,大小为 1G。另一个卷(xvda)是我们 t3.micro 实例的原始根卷。
-
现在,让我们检查一下我们的新 EBS 卷(
xvdf)是否有文件系统:/dev/xvdf: data, meaning that the volume doesn’t have a filesystem yet. -
让我们使用
mkfs(创建文件系统)命令行工具在我们的卷上创建文件系统:-t (--type) parameter with an xfs filesystem type. XFS is a high-performance journaled filesystem that’s supported by most Linux distributions and is installed by default in some of them. -
如果我们使用以下命令检查文件系统,我们应该能看到文件系统的详细信息,而不是空白数据:
sudo file -s /dev/xvdf用于检查和创建新文件系统的所有命令的输出如以下截图所示:

图 15.23 – 创建一个新的文件系统
卷驱动器现在已格式化。
-
现在,让我们使它对我们的本地文件系统可访问。我们将把挂载点命名为
packt_drive,并在根目录中创建它:sudo mkdir /packt_drive /packt directory, we’re accessing the EBS volume:

图 15.24 – 访问 EBS 卷
使用 EBS 卷类似于使用 Linux 上的任何卷,因此您在本章中获得的所有知识在处理 Amazon EC2 实例时都将非常有用。现在,让我们看看如何卸载一个 EBS 卷。
卸载 EBS 卷
在卸载任何 EBS 卷之前,您需要先卸载它。在我们的案例中,由于我们已经在终端中并正在使用该卷,我们将输入以下命令来卸载我们正在使用的卷:
sudo umount -d /dev/xvdf
现在,卷已被卸载,我们可以前往 EC2 控制台,进入 卷,选择要卸载的相应 正在使用 的卷,点击右上角的 操作,选择 卸载卷。确认操作并等待卷被卸载。
我们现在进入了备份恢复程序的最后一步——也就是将包含快照的新卷附加到我们的 EC2 实例。
我们将在这里结束对 AWS EC2 控制台及相关管理操作的探讨。如需关于 EC2 的全面参考,请查看 Amazon EC2 文档:docs.aws.amazon.com/ec2。
到目前为止,我们展示的 EC2 管理任务完全是通过 AWS Web 控制台来完成的。如果你希望自动化你的 EC2 工作负载,你可能想采用 AWS CLI,我们接下来将了解它。
使用 AWS CLI
AWS CLI 是一个统一的工具,用于管理 AWS 资源。该工具的创建目的是让我们能够通过本地机器上的终端会话管理所有的 AWS 服务。与通过浏览器提供可视化工具的 AWS Web 控制台相比,AWS CLI(顾名思义)将所有功能集成在本地机器的终端程序中。AWS CLI 支持所有主要操作系统,如 Linux、macOS 和 Windows。在接下来的章节中,我们将展示如何在 Linux 本地机器上安装它。
安装 AWS CLI
要安装 AWS CLI,请访问aws.amazon.com/cli/。在撰写本文时,AWS CLI 的最新版本是 2。对于本章中的示例,我们将使用一台 Debian 机器来安装 AWS CLI,并遵循docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html#cliv2-linux-install上的说明。该资源链接提供了在所有主要操作系统上安装 AWS CLI 的说明,不仅仅是 Linux。
按照以下步骤在 Linux 上安装 AWS CLI:
-
我们将从下载 AWS CLI v2 包(
awscliv2.zip)开始:curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o "awscliv2.zip" -
接下来,我们将解压并安装 AWS CLI:
unzip awscliv2.zip aws command-line utility installed on our system. Let’s check the version:帮助:
aws help
要使用 aws 工具管理你的 AWS EC2 资源,首先需要配置本地环境,以与 AWS 端点建立所需的信任关系。接下来我们将进行配置。
配置 AWS CLI
要在本地机器上配置 AWS 环境,你需要设置 AWS 访问密钥。如果你已经有了密钥,可以跳过此部分:
-
AWS CLI 配置会要求输入你的AWS 访问密钥 ID和AWS 秘密访问密钥值。你可以通过登录到你的 AWS 账户来生成或检索这些密钥。
-
在 AWS Web 控制台的右上角选择你账户名旁边的下拉菜单,然后选择安全凭证。如果你还没有生成访问密钥,请进入访问密钥标签页,点击创建访问密钥按钮。你需要将你的 AWS 密钥 ID 和密钥保存在安全的地方,以备后用。
-
现在你已经有了这些密钥,可以继续在本地机器上配置 AWS 环境。要配置 AWS 环境,请运行以下命令:
aws configure上述命令会提示你输入一些信息,如下所示的输出:

图 15.25 – 配置本地 AWS CLI 环境
- 在 AWS CLI 配置向导中,设置
eu-central-1(在我们的案例中是法兰克福)。你可以选择输入你自己的区域,或者保持默认设置(None)。如果没有指定默认区域,每次调用aws命令时都需要输入该区域。
此时,我们已经准备好使用 AWS CLI。让我们从列出 EC2 实例开始。
查询 EC2 实例
以下命令提供有关 EC2 实例的详细信息:
aws ec2 describe-instances
上述命令提供了庞大的 JSON 输出(截图展示不下),包含我们在默认区域(eu-central-1)中的所有 EC2 实例的详细信息。或者,我们可以使用 --region 参数指定区域:
aws ec2 describe-instances --region eu-central-1
我们还可以更灵活地操作,只列出与特定键值标签匹配的 EC2 实例,比如我们之前用 env: packt 标签标记的实例,使用 --filters 参数:
aws ec2 describe-instances \
--filters "Name=tag-key,Values=env" \
--filters "Name=tag-value,Values=packt"
第一个--filters参数指定了键(tag-key=env),第二个则指向值(tag-value=packt)。
通过结合使用 aws 和 jq(JSON 查询)命令,我们可以提取所需的 JSON 字段。例如,以下命令列出了带有 env:packt 标签的 EC2 实例的 InstanceId、ImageId 和 BlockDeviceMappings 字段:
aws ec2 describe-instances \
--filters "Name=tag-key,Values=env" \
--filters "Name=tag-value,Values=packt" | \
jq '.Reservations[].Instances[] | { InstanceId, ImageId, BlockDeviceMappings }'
如果你的 Linux 系统上没有安装jq工具,可以使用以下命令进行安装:
sudo apt install -y jq # on Ubuntu/Debian
上述 aws 命令的输出如下:

图 15.26 – 查询 EC2 实例
注意输出 JSON 中的 DeviceName 属性,它只反映了一个块设备(这是 /dev/sdf,因为我们删除了之前附加的 EBS 卷)。
我们可以通过任何属性过滤 aws ec2 describe-instances 命令的输出。例如,以下命令通过 image-id AMI 过滤我们的 EC2 实例:
aws ec2 describe-instances \
--filters "Name=image-id versus ImageId. You have to keep this rule in mind when you write your filter queries.
Next, let’s plan to launch a new EC2 instance of the same AMI type with our current machine and the same security group.
Creating an EC2 instance
Let’s look at how we can create a new EC2 instance. To do this, we will need some prior information regarding the existing security group inside which we would like to create the new instance. The following command retrieves the security groups of the current instance (`i-0f8fe6aced634e71c`):
aws ec2 describe-instances \
--filters "Name=instance-id,Values=i-0f8fe6aced634e71c" \
--query "Reservations[].Instances[].SecurityGroups[]"
The output is as follows:

Figure 15.27 – Retrieving the security groups of an EC2 instance
To retrieve `GroupId` directly, we could run the following:
aws ec2 describe-instances \
--filters "Name=instance-id,Values=i-0f8fe6aced634e71c" \
--query "Reservations[].Instances[].SecurityGroups[].GroupId"
In this case, the output would only show `GroupId`. Here, we used the `--query` parameter to specify the exact JSON path for the field we’re looking for (`GroupId`):
Reservations[].Instances[].SecurityGroups[].GroupId
The use of the `--query` parameter somewhat resembles piping the output to the `jq` command, but it’s less versatile.
We also need the AMI ID. To obtain this, navigate to your AWS EC2 console and then, from the left-hand side pane, go to `ami-0faab6bdbac9486fb`.
To launch a new instance with the AMI type of our choice and the security group ID, we must use the `aws ec2` `run-instances` command:
aws ec2 run-instances --image-id ami-0faab6bdbac9486fb --count 1 --instance-type t3.micro --key-name packt_aws_key --security-group-ids sg-0aa7c8ef75503a9aa –placement AvailabilityZone=eu-central-1b
Here’s a brief explanation of the parameters:
* `image-id`: The AMI image ID (`ami-0faab6bdbac9486fb`); we’re using the same AMI type (*Ubuntu Linux*) as with the previous instance we created in the AWS EC2 web console
* `count`: The number of instances to launch (`1`)
* `instance-type`: The EC2 instance type (`t3.micro`)
* `key-name`: The name of the SSH private key file (`packt_aws_key`) to use when connecting to our new instance; we’re reusing the SSH key file we created with our first EC2 instance in the AWS web console
* `security-group-ids`: The security groups attached to our instance; we’re reusing the security group attached to our current instance (`sg-0aa7c8ef75503a9aa`)
* `--placement`: The AZ to place our instance in (`AvailabilityZone=eu-central-1b`)
The command should run successfully and you will see the new EC2 instance running in your console. Here’s a screenshot of our EC2 console and the two instances running:

Figure 15.28 – The new EC2 instance shown in the console
As you can see, our newly created EC2 instance has no name. Let’s add a name and some tags to it using the command line.
Naming and tagging an EC2 instance
The following command names our new instance as `aws_packt_testing_2`:
aws ec2 create-tags --resources i-091e2f515d15c3b0b --tags Key=Name,Value=aws_packt_testing_2
Now, we can add a tag to the new instance. Adding a name and a tag can be done in a single command, but for a better understanding, we’ve decided to use two different commands. Here’s how to add a tag:
aws ec2 create-tags \
--resources i-0e1692c9dfdf07a8d \
--tags Key=env,Value=packt
Now, let’s query the instance with the following command:
aws ec2 describe-instances \
--filters "Name=tag-key,Values=env" \
--filters "Name=tag-value,Values=packt" \
--query "Reservations[].Instances[].InstanceId"
The output shows that our two EC2 instances have the same tag (`packt`):

Figure 15.29 – Querying EC2 instance IDs by tag
If you check the EC2 console inside your browser, you will see the two instances. Now, the second instance has its new name shown (`aws_packt_testing_2`) and the **Instance ID** value for each one:

Figure 15.30 – Showing all instances in the EC2 console
Next, we’ll show you how to terminate an EC2 instance from the command line.
Terminating an EC2 instance
To terminate an EC2 instance, we can use the `aws ec2 terminate-instance` command. Note that terminating an instance results in the instance being deleted. We cannot restart a terminated instance. We could use the `aws ec2 stop-instances` command to stop our instance until later use.
The following command will terminate the instance with the ID of `i-091e2f515d15c3b0b`:
aws ec2 terminate-instances --instance-ids shutting-down (从先前的运行状态停止):

图 15.31 – 终止实例
实例最终会过渡到terminated状态,并且将不再在 AWS EC2 控制台中显示。AWS CLI 仍然会将其列出在实例列表中,直到 EC2 最终处理它。根据 AWS 的说法,终止的实例在终止后最多一个小时内仍然可能可见。在通过 AWS CLI 执行查询和管理操作时,最好丢弃处于terminated或shutting-down状态的实例。以下 EC2 控制台的屏幕截图显示第二个 EC2 实例处于terminated状态:

图 15.32 – 显示 EC2 控制台中的终止实例
这是我们结束关于 AWS EC2 之旅的地方。请注意,我们仅仅触及了 AWS 中云管理工作负载的表面。
本节中涵盖的主题提供了对 AWS EC2 云资源的基本理解,并帮助系统管理员在管理相关工作负载时做出更好的决策。高级用户可能会发现 AWS CLI 示例是自动化其 EC2 云管理工作流的一个良好起点。
现在,让我们将注意力转向我们的下一个公共云服务竞争者——Microsoft Azure。
使用 Microsoft Azure
Microsoft Azure,也称为Azure,是微软提供的公共云服务,用于在云中构建和部署应用服务。Azure 提供全面的、高度可扩展的基础设施即服务(IaaS),以相对低廉的成本,满足从小团队到大型商业企业、金融、医疗和政府机构等广泛用户和业务需求。
在本节中,我们将探索一些使用 Azure 的非常基本的部署工作流,如下所示:
-
创建 Linux 虚拟机
-
管理虚拟机大小
-
向虚拟机添加额外存储
-
使用 Azure CLI
一旦你创建了免费的 Azure 帐户,请访问portal.azure.com进入 Azure 门户。你可能希望启用左侧导航菜单的停靠视图,以便快速便捷地访问你的资源。在本章中,我们将使用停靠视图来进行屏幕截图。进入右上角的门户设置齿轮,选择停靠作为门户菜单的默认模式。
现在,让我们在 Azure 中创建我们的第一个资源——一台 Ubuntu 虚拟机。
创建和部署虚拟机
我们将遵循 Azure 门户中的资源向导,按步骤操作。让我们从第一步开始,即为虚拟机创建计算资源。
-
创建计算资源:首先,点击左侧导航菜单中的创建资源选项,或在主窗口中的Azure 服务下选择。这将引导我们进入 Azure 市场。在这里,我们可以搜索所需的资源。你可以搜索相关的关键词,或者根据所需的资源类型缩小选择范围。我们可以通过选择计算,然后从可用的热门市场产品选项中选择Ubuntu Server 22.04 LTS来缩小选择范围。你可以点击了解更多以查看镜像的详细描述,或点击创建。
当我们选择Ubuntu Server 22.04 LTS时,系统将引导我们完成配置和创建新虚拟机的过程。设置页面如下所示:

图 15.33 – 在 Azure 中创建虚拟机
在基础选项卡中,提供了订阅类型、资源组、区域、镜像和架构等信息。其他选项卡包括磁盘、网络、管理、监控、高级和标签。
-
packt-demo。如果我们之前创建了资源组,我们可以在这里指定它。 -
packt-ubuntu-demo,并将其放置在(欧洲)法国中部区域,该区域距离我们实例操作的地理位置最近。虚拟机的大小将直接影响相关费用。以下截图显示了目前已指定的资源组设置、虚拟机名称、区域、可用性选项、安全类型、镜像、虚拟机架构和大小的详细信息:

图 15.34 – 设置 Azure 虚拟机
另外,我们也可以通过选择查看所有镜像或查看所有大小来浏览不同的镜像和大小选项。Azure 还提供了一个在线工具定价计算器,可供查看各种资源的定价:azure.microsoft.com/en-us/pricing/calculator/。
packt和packt-ubuntu-demo_key。接下来,我们需要为我们的实例设置入站端口规则,以允许 SSH 访问。例如,如果我们的机器将运行 Web 服务器应用程序,我们还可以启用 HTTP 和 HTTPS 访问:

图 15.35 – 启用 SSH 身份验证并访问虚拟机
此时,我们准备好创建虚拟机了。向导可以引导我们进一步进行与实例相关的磁盘和网络配置的额外步骤。现在,我们将保持现状,不做更改。
- 验证并部署虚拟机:我们可以点击审查 + 创建按钮以启动验证过程。接下来,部署向导将验证我们的虚拟机配置。如果一切顺利,几秒钟后我们会看到一个验证通过的消息,显示产品详情和实例的每小时费用。点击创建后,我们同意相关的使用条款,虚拟机将很快被部署:

图 15.36 – 创建虚拟机
在这个过程中,我们将被提示下载 SSH 私钥以访问我们的实例:

图 15.37 – 下载 SSH 私钥以访问虚拟机
- 部署完成:如果部署成功完成,我们会收到一个简短的弹出消息,显示部署成功,并有一个转到资源按钮,将我们带到新虚拟机的页面:

图 15.38 – 成功部署虚拟机
如果点击相关下拉按钮,我们还会得到一个简短的部署详情报告:

图 15.39 – 部署详情
让我们快速浏览一下与虚拟机部署一起创建的每个资源:
-
packt-ubuntu-demo:虚拟机主机 -
packt-ubuntu-demo348:虚拟机的网络接口(或网络接口卡) -
packt-ubuntu-demo-ip:虚拟机的 IP 地址 -
packt-ubuntu-demo-vnet:与资源组(packt-demo)关联的虚拟网络 -
packt-ubuntu-demo-nsg:控制进出我们实例的网络安全组(NSG)
当实例被放置在现有资源组中时,Azure 会为每个虚拟机创建一组新的资源类型(前面提到的资源类型),但不会包括与资源组对应的虚拟网络。别忘了我们还创建了一个新的资源组(packt-demo),它不会在部署报告中显示。
让我们尝试连接到我们新创建的实例(packt-ubuntu-demo)。前往packt-ubuntu-demo)并在20.19.173.232中进行连接。
既然我们已经部署了虚拟机,我们希望确保能够通过 SSH 访问它。接下来我们就来做这件事。
通过 SSH 连接到虚拟机
在通过 SSH 连接到虚拟机之前,我们需要设置 SSH 私钥文件的权限,以确保它不会公开可见:
chmod 400 packt-ubuntu-demo_key.pem
接下来,我们必须使用以下命令连接到我们的 Azure Ubuntu 实例:
ssh -i packt-rhel.pem packt@20.19.173.232
我们必须使用在创建实例时指定的 SSH 密钥(packt-ubuntu-demo_key.pem)和管理员帐户(packt)。或者,您可以在虚拟机的概述选项卡上单击连接按钮,然后单击SSH。此操作将显示一个视图,您可以在其中查看前面的命令并将其复制粘贴到您的终端中。
成功连接到我们的 Ubuntu 实例应该会产生以下输出:

图 15.40 – 连接到 Azure 虚拟机
现在我们在 Azure 中创建了我们的第一个虚拟机,让我们来看看在虚拟机生命周期中执行的一些最常见的管理操作。
管理虚拟机
随着我们的应用程序的发展,托管应用程序的虚拟机所需的计算能力和容量也在增加。作为系统管理员,我们应该了解云资源的利用情况。Azure 提供了监视虚拟机健康和性能所需的必要工具。这些工具可以在虚拟机管理页面的监视选项卡上找到。
具有相对较少虚拟 CPU 和较少内存的小型虚拟机可能会对应用程序性能产生负面影响。另一方面,过大的实例会导致不必要的成本。在 Azure 中,调整虚拟机大小是一种常见的操作。让我们看看如何做到这一点。
更改虚拟机的大小
Azure 使得调整虚拟机大小变得相对容易。在门户中,转到虚拟机,选择您的实例,然后在可用性 + 规模下点击大小:

图 15.41 – 更改虚拟机的大小
我们的虚拟机(packt-ubuntu-demo)是B1s大小(1 vCPU,1 GB RAM)。我们可以选择将其大小调整到更低的B1ls容量(1 vCPU,0.5 GB RAM)。我们可以选择B1ls(1 vCPU,0.5 GB RAM)选项并点击调整大小按钮。降低实例的大小也将带来成本节约。在更改大小之前,最好停止虚拟机以避免实例内可能的数据不一致。
Azure 中虚拟化工作负载的显著特性之一是可以通过添加额外的数据磁盘来扩展(包括存储容量)。我们可以添加现有的数据磁盘或创建新的磁盘。
让我们看看如何向我们的虚拟机添加一个次要数据磁盘。
添加额外的存储空间
Azure 可以在不停止虚拟机的情况下动态地向实例添加磁盘。我们可以向虚拟机添加两种类型的磁盘:数据磁盘和托管磁盘。在本章中,我们将仅提供如何添加数据磁盘的信息。有关 Azure 磁盘类型的更多信息,请访问docs.microsoft.com/en-us/azure/virtual-machines/disks-types。
让我们学习如何添加数据磁盘:
-
在左侧导航菜单中,导航到虚拟机,选择你的实例,点击设置下的磁盘,然后点击创建并附加新磁盘。
-
在磁盘属性中,保留
packt-disk、存储类型和大小(例如4 GB)的值。完成后,点击应用:

图 15.42 – 添加新数据磁盘
此时,新磁盘已附加到我们的虚拟机,但磁盘尚未初始化文件系统。
-
通过 SSH 连接到我们的虚拟机:
ssh -i packt-rhel.pem packt@20.19.173.232- 列出当前的块设备:
sdc:

图 15.43 – 确定新数据磁盘的块设备
-
验证块设备是否为空:
/dev/sdc: data, meaning that the data disk doesn’t contain a filesystem yet.- 接下来,使用 XFS 文件系统初始化卷:
/packt) and mount the new volume:sudo mkdir /packt
sudo mount /dev/sdc /packt
现在,我们可以使用新的数据磁盘进行常规文件存储。
请注意,数据磁盘仅在虚拟机的生命周期内被持久化。当虚拟机被暂停、停止或终止时,数据磁盘将变为不可用。当虚拟机终止时,数据磁盘会永久丢失。对于持久化存储,我们需要使用托管磁盘,其行为类似于网络附加存储。有关更多详细信息,请参见本节开头的链接。
到目前为止,我们已经在 Azure 门户中执行了所有这些管理操作。但是如果你想使用脚本自动化云中的工作负载怎么办?这可以通过 Azure CLI 来实现。
使用 Azure CLI
Azure CLI 是一个专门的命令行界面,用于管理云中的资源。首先,让我们在我们选择的平台上安装 Azure CLI。按照docs.microsoft.com/en-us/cli/azure/install-azure-cli中的说明进行操作。我们将选择适用于 Linux 的 Azure CLI,并为了演示目的,在 Debian 机器上安装它。相关的安装说明可以在docs.microsoft.com/en-us/cli/azure/install-azure-cli-linux找到。在多种安装选项中,我们将使用以下命令:
curl -sL https://aka.ms/InstallAzureCLIDeb | sudo bash
安装完成后,我们可以使用az命令调用 Azure CLI:
az help
前面的命令会显示关于如何使用az工具的详细帮助信息。然而,在执行任何管理操作之前,我们需要使用我们的 Azure 凭据对 CLI 进行身份验证。以下命令将相应地设置本地的 Azure CLI 环境:
az login
我们将收到一条包含身份验证代码的消息,以及需要访问的 URL(microsoft.com/devicelogin)并输入该代码:

图 15.44 – 初始化 Azure CLI 环境
此时,我们已准备好使用 Azure CLI 进行管理操作。让我们在West US区域创建一个名为packt-dev的新资源组:
az group create --name packt-dev --location francecentral
前面的命令在成功创建资源组后将产生以下输出:

图 15.45 – 创建新的资源组
接下来,我们必须在刚刚创建的区域启动一个名为packt-ubuntu-dev的 Ubuntu 虚拟机:
az vm create --resource-group packt-dev --name packt-ubuntu-dev --image Ubuntu2204 --admin-username packt --generate-ssh-keys
让我们快速浏览一下前面提到的每个命令行选项:
-
resource-group:我们创建虚拟机的资源组名称(packt-dev) -
name:虚拟机的名称(packt-ubuntu-dev) -
image:要使用的 Linux 发行版(Ubuntu2204) -
admin-username:机器管理员账户的用户名(packt) -
generate-ssh-keys:这将生成一对新的 SSH 密钥,用于访问我们的虚拟机
前面的代码会产生以下输出:

图 15.46 – 创建新的虚拟机
如输出所示,SSH 密钥文件已自动生成并放置在本地机器的~/.ssh目录中,以允许通过 SSH 访问新创建的虚拟机。JSON 输出还提供了机器的公共 IP 地址:
"publicIpAddress": "51.103.100.132"
以下命令列出所有虚拟机:
az vm list
你也可以在浏览器中查看 Azure 门户,查看所有虚拟机,包括新创建的虚拟机:

图 15.47 – 在 Azure 门户中查看虚拟机
要获取关于特定虚拟机(packt-ubuntu-dev)的信息,请运行以下命令:
az vm show \
--resource-group packt-dev \
--name packt-ubuntu-dev), run the following command:
az vm redeploy \
--resource-group packt-dev \
--name packt-ubuntu-dev):
az vm delete \
--resource-group packt-dev \
--name az vm), we also need to specify the resource group the machine belongs to.
A comprehensive study of the Azure CLI is beyond the scope of this chapter. For detailed information, please visit the Azure CLI’s online documentation portal: [`docs.microsoft.com/en-us/cli/azure/`](https://docs.microsoft.com/en-us/cli/azure/).
This concludes our coverage of public cloud deployments with AWS and Azure. We have covered a vast domain and have merely skimmed the surface of cloud management workloads. We encourage you to build upon this preliminary knowledge and explore more, starting with the AWS and Azure cloud docs. The relevant links are mentioned in the *Further reading* section, together with other valuable resources.
Now, let’s summarize what you have learned so far about AWS and Azure.
Summary
AWS and Azure provide a roughly similar set of features for flexible compute capacity, storage, and networking, with pay-as-you-go pricing. They share the essential elements of a public cloud – elasticity, autoscaling, provisioning with self-service, security, and identity and access management. This chapter explored both cloud providers strictly from a practical vantage point, focusing on typical deployment and management aspects of everyday cloud administration tasks.
We covered topics such as launching and terminating a new instance or virtual machine. We also looked at resizing an instance to accommodate a higher or lower compute capacity and scaling the storage by creating and attaching additional block devices (volumes). Finally, we used CLI tools for scripting various cloud management workloads.
When working with AWS, we learned a few basic concepts about EC2 resources. Next, we looked at typical cloud management tasks, such as launching and managing instances, adding and configuring additional storage, and using EBS snapshots for disaster recovery. Finally, we explored the AWS CLI with hands-on examples of standard operations, including querying and launching EC2 instances, creating and adding additional storage to an instance, and terminating an instance.
At this point, you should be familiar with the AWS and Azure web administration consoles and CLI tools. You have learned the basics of some typical cloud management tasks and a few essential concepts about provisioning cloud resources. Overall, you’ve enabled a special skillset of modern-day Linux administrators by engaging in cloud-native administration workflows. Combined with the knowledge you’ve built so far, you are assembling a valuable Linux administration toolbelt for on-premises, public, and hybrid cloud systems management.
In the next chapter, we’ll take this further and introduce you to managing application deployments using containerized workflows and services with Kubernetes.
Questions
Let’s recap some of the concepts you’ve learned about in this chapter as a quiz:
1. What is an AZ?
2. Between a `t3.small` and a `t3.micro` AWS EC2 instance type, which one yields better performance?
3. You have launched an AWS EC2 instance in the `us-west-1a` AZ and plan to attach an EBS volume created in `us-west-1b`. Would this work?
4. What is the SSH command to connect to your AWS EC2 instance or Azure virtual machine?
5. What is the Azure CLI command for listing your virtual machines? How about the equivalent AWS CLI command?
6. What is the AWS CLI command for launching a new EC2 instance?
7. What is the Azure CLI command for deleting a virtual machine?
Further reading
Here are a few resources to further explore AWS and Azure cloud topics:
* AWS EC2: [`docs.aws.amazon.com/ec2/index.html`](https://docs.aws.amazon.com/ec2/index.html)
* Azure: [`docs.microsoft.com/en-us/azure`](https://docs.microsoft.com/en-us/azure)
* *AWS for System Administrators*, by Prashant Lakhera, Packt Publishing
* *Learning AWS – Second Edition*, by Aurobindo Sarkar and Amit Shah, Packt Publishing
* *Learning Microsoft Azure*, by Geoff Webber-Cross, Packt Publishing
* *Learning Microsoft Azure: A Hands-On Training [Video]*, by Vijay Saini, Packt Publishing
第十六章:使用 Kubernetes 部署应用程序
无论你是管理容器化应用程序的资深系统管理员,还是自动化应用编排工作流的 DevOps 工程师,Kubernetes 都可能是你首选的平台。本章将介绍 Kubernetes,并指导你完成构建和配置 Kubernetes 集群 的基本过程。我们将使用 Kubernetes 在一个安全且高可用的环境中运行和扩展一个简单的应用程序。你还将学会如何通过命令行界面(CLI)与 Kubernetes 进行交互。
到本章结束时,你将学会如何在本地环境中安装、配置和管理 Kubernetes 集群。我们还将向你展示如何使用 Kubernetes 部署和扩展应用程序。
以下是本章将要覆盖的主题的简要提纲:
-
介绍 Kubernetes 架构和 API 对象模型
-
安装和配置 Kubernetes
-
使用
kubectl命令行工具与 Kubernetes 交互并部署应用程序
技术要求
你应该熟悉 Linux 和一般的命令行界面(CLI)。如果你对TCP/IP 网络和Docker 容器有一定了解,这将大大简化你学习 Kubernetes 的过程。
你还需要以下内容:
-
一台本地桌面机器,安装你选择的 Linux 发行版,以便安装和实验本章中使用的 CLI 工具。我们将使用 Debian 和 Ubuntu LTS。
-
一台性能强大的桌面系统,至少拥有 8 个 CPU 核心和至少 16 GB 内存,将使你能够在桌面上复制所需的环境,因为我们将花费相对较多的篇幅讲解如何使用虚拟机构建 Kubernetes 集群。
-
一个桌面虚拟化管理程序。
现在,让我们一起开始探索 Kubernetes 的旅程。
介绍 Kubernetes
Kubernetes 是一个开源的容器编排工具,最初由谷歌开发。容器编排工具是自动管理(包括资源配置、部署和扩展)容器化应用程序的软件。如果一个应用程序使用了容器化的微服务,容器编排系统将提供以下功能:
-
弹性编排(自动扩展):这包括根据特定的需求和条件,自动启动和停止应用程序服务(容器)——例如,当请求数量增加时,启动多个 Web 服务器实例,最终当请求数量降到某个阈值以下时终止服务器。
-
工作负载管理:这涉及到将应用服务最佳地部署和分配到底层集群,以确保必要的依赖关系和冗余——例如,在每个集群节点上运行 Web 服务器端点,以保证高可用性。
-
基础设施抽象:这涉及提供容器运行时、网络和负载均衡功能——例如,将负载分配到多个 Web 服务器容器之间,并自动配置与数据库应用容器的底层网络连接。
-
声明式配置:这涉及描述并确保多层应用的期望状态——例如,只有在数据库后端运行且底层存储可用时,Web 服务器才应准备好处理请求。
一个经典的工作负载编排示例是视频点播流媒体服务。在一部受欢迎的新电视节目需求量很大的情况下,流媒体请求的数量将显著超过常规季节的平均水平。使用Kubernetes,我们可以根据流媒体会话的数量扩展 web 服务器的数量。我们还可以控制某些中间层组件的可能扩展,例如数据库实例(处理身份验证请求)和存储缓存(处理视频流)。当该电视节目过时且请求数量显著下降时,Kubernetes 会终止多余的实例,自动减少应用部署的占用空间,并因此降低底层成本。
以下是使用 Kubernetes 部署应用的一些关键好处:
-
快速部署:应用容器的创建和启动相对较快,使用声明式或命令式配置模型(我们将在本章后面“介绍 Kubernetes 对象模型”一节中看到)。
-
快速迭代:应用升级相对简单,底层基础设施只需无缝地替换相关容器。
-
快速恢复:如果应用崩溃或无法使用,Kubernetes 会通过替换相关容器自动将应用恢复到期望状态。
-
降低运营成本:Kubernetes 的容器化环境和基础设施抽象带来了最小的管理和维护工作,并且运行应用所需的资源相对较低。
现在我们已经介绍了 Kubernetes,让我们接下来看看它的基本操作原理。
理解 Kubernetes 架构
Kubernetes 工作模型的核心有三个主要概念:
-
声明式配置或期望状态:这个概念描述了整体应用状态和微服务,部署所需的容器和相关资源,包括网络、存储和负载均衡器,以实现应用的运行功能状态。
-
控制器或控制器循环:这会监控系统的期望状态,并在需要时采取纠正措施,例如替换失败的应用容器或为扩展工作负载添加额外资源。
-
API 对象模型:该模型表示期望状态的实际实现,使用各种配置对象以及这些对象之间的交互——即应用程序编程接口(API)
为了更好地理解 Kubernetes 的内部,我们需要更深入地了解 Kubernetes 对象模型和相关 API。此外,您可以查看图 16.1,以获取 Kubernetes(集群)架构的可视化解释。
介绍 Kubernetes 对象模型
Kubernetes 架构定义了一组表示系统期望状态的对象。在这个上下文中,对象是一个程序化术语,用来描述子系统的行为。多个对象通过 API 相互交互,随着时间推移塑造期望状态。换句话说,Kubernetes 对象模型是期望状态的程序化表示。
那么,Kubernetes 中的这些对象是什么呢?我们将简要列举一些更重要的对象,并在接下来的章节中进一步详细阐述它们:API 服务器、Pods、控制器、服务和存储。
我们使用这些 API 对象来配置系统的状态,使用声明式或命令式模型:
-
使用声明式配置模型,我们描述系统的状态,通常通过配置文件或清单(YAML 或 JSON 格式)。这样的配置可以包含并部署多个 API 对象,并将系统视为一个整体。
-
命令式配置模型使用单独的命令来配置和部署特定的 API 对象,通常作用于单一目标或子系统。
让我们先来看看 API 服务器——Kubernetes 对象模型中的核心部分。
介绍 API 服务器
API 服务器是 Kubernetes 对象模型的核心枢纽,作为管理端点,表示系统的期望状态。API 服务器通过 HTTP REST 接口暴露 JSON 负载。它可以通过两种方式访问:
-
内部:由其他 API 对象在内部访问
-
外部:通过配置和管理工作流在外部访问
API 服务器本质上是与 Kubernetes 集群交互的网关,既可以从外部也可以从内部访问。Kubernetes 集群是由不同节点组成的框架,节点运行容器化的应用程序。集群是 Kubernetes 自身的基本运行模式。关于 Kubernetes 集群的更多内容将在Kubernetes 集群解剖部分中提供。系统管理员通过 CLI 连接到 API 服务器端点,配置和管理 Kubernetes 集群。内部,Kubernetes API 对象连接到 API 服务器,以提供其状态的更新。作为回报,API 服务器可能会进一步调整 API 对象的内部配置,以实现期望状态。
API 对象是 Kubernetes 集群内部配置或期望状态的构建模块。接下来,我们将介绍一些这些 API 对象。
介绍 Pods
Pod 代表 Kubernetes 中的基本工作单元,作为单容器或多容器应用程序运行。Pod 也被称为 Kubernetes 中的调度单元。换句话说,同一 Pod 内的容器保证会一起部署在同一个集群节点上。
Pod 本质上代表了应用程序服务网格中的一个微服务(或服务)。以经典的 Web 应用为例,我们可能在集群中运行以下 Pod:
-
Web 服务器(Nginx)
-
身份认证(Vault)
-
数据库(PostgreSQL)
-
存储(NAS)
每个服务(或应用程序)都在其 Pod 中运行。相同应用程序的多个 Pod(例如,Web 服务器)组成一个ReplicaSet。我们将在引入 控制器 部分更详细地了解 ReplicaSets。
Pod 的一些基本特性如下:
-
它们具有短暂的特性。一旦 Pod 被终止,它就永远消失。在 Kubernetes 中,Pod 永远不会被重新部署。因此,除非 Pod 使用持久存储或本地卷来保存数据,否则它们的状态不会持久存在。
-
Pod 是一个最小单位——它们要么被部署,要么不被部署。对于单容器的 Pod,原子性几乎是理所当然的。对于多容器的 Pod,原子性意味着只有当每个容器都成功部署时,Pod 才会被部署。如果任何一个容器部署失败,Pod 就不会被部署,因此没有 Pod。如果运行中的多容器 Pod 中的某个容器失败,整个 Pod 将会被终止。
-
Kubernetes 使用探针(如存活性探针和就绪探针)来监控 Pod 中应用程序的健康状况。这是因为 Pod 可能已经被部署并运行,但这并不一定意味着 Pod 内的应用程序或服务是健康的。例如,Web 服务器 Pod 可能有一个探针检查特定的 URL,并根据响应来判断其健康状态。
Kubernetes 使用控制器跟踪 Pod 的状态。接下来我们来看看控制器。
引入控制器
Kubernetes 中的控制器是控制循环,负责保持系统处于期望状态,或通过不断监控集群的状态使系统更接近期望状态。例如,控制器可能会检测到一个 Pod 没有响应,并请求部署一个新的 Pod,同时终止旧的 Pod。我们来看看两种关键的控制器类型:
-
控制器还可以将特定类型的 Pod 添加到或从 Pod 副本集合中移除。这种控制器被称为ReplicaSets,它们的责任是根据应用程序当前的状态容纳特定数量的 Pod 副本。例如,假设一个应用程序需要三个 Web 服务器 Pod,而其中一个由于探针失败变得不可用。在这种情况下,ReplicaSet 控制器确保删除失败的 Pod,并用一个新的 Pod 替代它。
-
在 Kubernetes 中部署应用时,我们通常不会直接使用 ReplicaSet 来创建 Pod。我们使用
v1来创建几个运行应用版本 1 的 Pod,我们希望将它们升级到版本 2。记住,Pod 无法重新生成或升级。相反,我们将定义第二个 ReplicaSet(v2),创建版本 2 的 Pod。Deployment 控制器会拆除v1ReplicaSet 并启动v2。Kubernetes 会无缝地执行发布,几乎没有服务中断。Deployment 控制器管理v1和v2ReplicaSet 之间的过渡,并在需要时回滚过渡。
Kubernetes 中还有许多其他类型的控制器,我们鼓励你在 kubernetes.io/docs/concepts/workloads/controllers/ 上探索它们。
随着应用的扩展或终止,相关的 Pod 会被部署或移除。服务提供对动态且短暂的 Pod 世界的访问。接下来我们将讨论服务。
引入服务
服务提供对运行在 Pod 中应用的持久访问。服务的责任是通过将流量路由到相应的应用端点来确保 Pod 可访问。换句话说,服务通过 IP 地址、路由和 DNS 解析为与 Pod 的通信提供了网络抽象。由于 Pod 会根据系统的期望状态进行部署或终止,Kubernetes 会动态更新 Pod 的服务端点,几乎不会中断访问相关应用。随着用户和应用访问服务端点的持久 IP 地址,服务将确保路由信息是最新的,并且流量仅会路由到正在运行且健康的 Pod。服务还可以用于在 Pod 之间进行负载均衡,根据需求扩展或缩减 Pod 数量。
到目前为止,我们已经讨论了控制应用服务部署、访问和生命周期的 Kubernetes API 对象。那么,应用所需的持久数据呢?接下来我们将讨论 Kubernetes 存储。
引入存储
Kubernetes 为集群内运行的应用提供了多种存储类型。最常见的存储类型是 卷 和 持久卷。由于 Pod 的短暂性,通过卷在 Pod 中存储的应用数据会在 Pod 终止时丢失。持久卷是在 Kubernetes 集群级别定义和管理的,且独立于 Pod。需要持久状态的应用(Pod)会保留一个持久卷(特定大小),通过 持久卷声明 来使用。当使用持久卷的 Pod 终止时,替代旧 Pod 的新 Pod 会从持久卷中检索当前状态,并继续使用底层存储。
如需了解更多关于 Kubernetes 存储类型的信息,请参考 kubernetes.io/docs/concepts/storage/。
现在我们已经熟悉了 Kubernetes API 对象模型,让我们快速浏览一下 Kubernetes 集群的架构。
Kubernetes 集群的结构
一个 Kubernetes 集群由一个 控制平面(CP)节点和一个或多个 工作节点组成。下图展示了 Kubernetes 架构的高层次视图:

图 16.1 – Kubernetes 集群架构
前面图示中的 Kubernetes 集群组件被划分为工作节点和 CP 节点这两个主要部分。工作节点具有不同的组件,如容器运行时、kubelet 和 kube-proxy,而 CP 节点则有 API 服务器、控制器管理器和调度器。所有这些组件将在后续部分详细讨论。
首先,让我们详细看看前面图示中的 Kubernetes 集群节点,从 CP 节点开始。
介绍 Kubernetes CP
Kubernetes CP 提供了部署和编排应用工作负载所需的基本服务,并且运行在 Kubernetes 集群中的专用节点——CP 节点上。这个节点,也称为 主节点,实现了 Kubernetes 集群的核心组件,如资源调度和监控。它还是集群管理的主要访问点。以下是 CP 节点的关键子系统:
-
API 服务器:Kubernetes API 对象之间的中央通信枢纽;它还提供集群管理端点,可以通过命令行界面(CLI)或 Kubernetes Web 管理控制台(仪表板)访问。
-
调度器:根据资源分配和管理策略决定何时以及将哪些节点部署到 pod 上。
-
控制器管理器:它维护控制循环,监控并塑造系统的期望状态。
-
etcd:也称为 集群存储,这是一个高度可用的持久化数据库,维护 Kubernetes 集群及相关 API 对象的状态;etcd 中的信息以键值对的形式存储。
-
kubectl:用于管理和与 Kubernetes 集群交互的主要命令行工具;kubectl直接与 API 服务器通信,并可以远程连接到集群。
Kubernetes CP 的详细架构概述超出了本章的范围。你可以在 kubernetes.io/docs/concepts/architecture/ 上深入探索相关概念。
接下来,让我们简要了解一下 Kubernetes 节点——Kubernetes 集群的“工作马”。
介绍 Kubernetes 节点
在 Kubernetes 集群中,节点——也称为 工作节点——运行实际的应用程序 Pods 并维护它们的完整生命周期。节点提供 Kubernetes 的计算能力,并确保在部署和运行 Pods 时,工作负载均匀地分布在集群中。节点可以配置为物理(裸金属)或虚拟机(VM)。
让我们列举 Kubernetes 节点的关键元素:
-
Kubelet:处理调度器发送的 CP 请求,用于部署和启动应用程序 Pods;Kubelet 还监视节点和 Pod 的状态,将相关变化报告给 API 服务器。
-
Kube-Proxy:动态配置在 Pods 中运行的应用程序的虚拟网络环境;它负责路由网络流量、提供负载均衡,并维护 Services 和 Pods 的 IP 地址。
-
containerd和 Docker)
之前提到的所有服务都会在 Kubernetes 集群中的 每个 节点上运行,包括 CP 节点。这些 CP 中的组件是由专用 Pods 提供的,提供特定的 CP 服务,例如 DNS、Ingress(负载均衡)和 Dashboard(Web 控制台)。
想了解更多关于 Kubernetes 节点和相关架构概念的信息,请访问 kubernetes.io/docs/concepts/architecture/nodes/。
现在我们已经熟悉了一些关键概念和集群组件,让我们准备好安装和配置 Kubernetes。
安装和配置 Kubernetes
在安装或使用 Kubernetes 之前,你需要决定使用的基础设施,无论是本地还是公有云。其次,你需要在 基础设施即服务 (IaaS) 或 平台即服务 (PaaS) 模式之间做出选择。选择 IaaS 后,你需要自己安装、配置、管理和维护 Kubernetes 集群,无论是安装在物理机器(裸金属)还是虚拟机上。相关操作工作不简单,需要仔细考虑。如果你选择 PaaS 解决方案,所有主要公有云提供商都提供该服务,你将仅限于执行管理任务,而无需承担维护底层基础设施的负担。
本章我们将仅介绍 Kubernetes 的 IaaS 部署。对于 IaaS,我们将使用运行 Ubuntu 虚拟机的本地桌面环境。
对于本地安装,我们还可以选择 Kubernetes 的轻量级桌面版本或具有多个节点的完整集群。接下来让我们看看一些常见的 Kubernetes 桌面版本。
在桌面上安装 Kubernetes
如果你只是想尝试 Kubernetes,桌面版可能正合适。Kubernetes 的桌面版本通常在本地机器上部署一个单节点集群。根据你选择的平台,无论是 Windows、macOS 还是 Linux,你都有很多 Kubernetes 引擎可以选择。以下是其中的一些:
-
Docker Desktop (macOS, Windows):
www.docker.com/products/docker-desktop -
minikube (Linux, macOS, Windows):
minikube.sigs.k8s.io/docs/ -
Microk8s (Linux, macOS, Windows):
microk8s.io/ -
k3s (****Linux):
k3s.io/
在本节中,我们将展示如何安装 Microk8s,这是一款在写作时非常流行的 Kubernetes 桌面引擎。Microk8s 可以通过 Snap Store 安装。我们将使用 Debian 12 作为测试计算机的基础操作系统,以便从 Snap Store 安装 Microk8s。如果你没有安装snapd,你需要先进行安装。你需要使用以下命令来安装 Snap 守护进程:
sudo apt install snapd
如果你已经安装了snapd,你可以跳过这第一步。接下来的步骤是运行命令,安装运行 Snap Store 所需的snapd核心运行时环境:
sudo snap install core
只有在安装了snap之后,你才能使用以下命令安装microk8s:
sudo snap install microk8s --classic
成功安装 Microk8s 后应显示以下结果:

图 16.2 – 在 Linux 上安装 Microk8s
由于我们已经安装了snapd,因此没有运行之前列出的第一个命令。
为了在没有sudo权限的情况下访问 Microk8s CLI,你需要将本地用户账户添加到microk8s组中,并使用以下命令修复~/.kube目录的权限:
sudo usermod -aG microk8s $USER
sudo chown -f -R $USER ~/.kube
这些更改将在下次登录时生效,你可以在不使用sudo的情况下调用microk8s命令行工具。例如,以下命令显示该工具的帮助信息:
microk8s help
要查看本地单节点 Microk8s Kubernetes 集群的状态,我们运行以下命令:
microk8s status
如你所见,Microk8s 在 Debian 上的安装步骤直接且与 Ubuntu 上使用的步骤类似。
在下一部分,我们将展示如何在虚拟机上安装 Microk8s。这次我们将使用 Ubuntu 22.04 LTS 作为虚拟机的基础操作系统。
在虚拟机上安装 Kubernetes
在本节中,我们将通过在 Ubuntu 虚拟机上部署 Kubernetes 集群,模拟一个更接近真实世界的 Kubernetes 环境——虽然规模要小得多。你可以使用任何虚拟化管理程序,如 KVM、Oracle VirtualBox 或 VMware Fusion。我们将使用 KVM 作为首选的虚拟化管理程序。
我们将创建四个虚拟机,每个虚拟机配置 2 个 vCPU 核心、2 GB 内存和 20 GB 磁盘容量。你可以按照第一章中安装 Ubuntu部分的步骤,使用你选择的虚拟化管理程序进行操作。
在我们深入 Kubernetes 集群安装细节之前,先快速了解一下我们的实验环境。
准备实验环境
下面是我们虚拟机环境的规格:
-
虚拟化管理程序: VMware Fusion
-
Kubernetes 集群: 一个 CP 节点和三个工作节点
-
k8s-cp1:192.168.122.104 -
k8s-n1:192.168.122.146*k8s-n2:192.168.122.233*k8s-n3:192.168.122.163* 虚拟机: Ubuntu Server 22.04.3 LTS,2 个 vCPU,2 GB 内存,20 GB 硬盘*packt(所有节点),启用 SSH 访问
我们在每个虚拟机节点的 Ubuntu Server 安装向导中设置了用户名和主机名。并且在提示时,确保启用 OpenSSH 服务器。你的虚拟机 IP 地址可能与规格中的不同,但这不应该影响。你也可以选择为虚拟机使用静态 IP 地址。
为了简化集群内部的主机名解析,编辑每个节点上的 /etc/hosts 文件并添加相关记录。例如,我们在 CP 节点(k8s-cp1)上有如下的 /etc/hosts 文件:

图 16.3 – CP 节点(k8s-cp1)的 /etc/hosts 文件
在生产环境中,启用防火墙的集群节点,我们必须确保为接受集群内部的网络流量配置以下规则(参考 kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/):

图 16.4 – Kubernetes 集群节点使用的端口
以下各节假设你已经按照前述规格准备并运行了虚拟机。在继续执行下一步之前,你可以先拍摄一些虚拟机的初始快照。如果安装过程中出现任何问题,你可以恢复到初始状态并重新开始。
以下是我们安装 Kubernetes 集群的步骤:
-
禁用交换。
-
安装
containerd。 -
安装
kubelet、kubeadm和 Kubernetes 包。
我们需要在每个集群节点上执行这些步骤。相关命令也可以在 GitHub 上附带的章节源代码中找到。
让我们从第一步开始,禁用每个节点的内存交换。
禁用交换
swap 是在内存满时使用的磁盘空间(参考 github.com/kubernetes/kubernetes/issues/53533 获取更多详细信息)。Kubernetes 的 kubelet 包在 Linux 平台上无法与启用 swap 的系统一起使用。这意味着我们需要禁用所有节点上的 swap。
为了立即禁用 swap,我们需要在每个虚拟机上运行以下命令:
sudo swapoff -a
为了在系统重启后保持禁用 swap,我们需要在 /etc/fstab 中注释掉与 swap 相关的条目。你可以手动编辑 /etc/fstab,或者使用以下命令:
sudo sed -i '/\s*swap\s*/s/^\(.*\)$/# \1/g' /etc/fstab
你可能需要再次检查 /etc/fstab 中的所有 swap 条目是否被禁用:
cat /etc/fstab
我们可以在 /etc/fstab 文件中看到 swap 挂载点已被注释掉:

图 16.5 – 在 /etc/fstab 中禁用 swap 条目
请记住,在集群中的每个节点上运行上述命令。接下来,我们将着手安装 Kubernetes 容器运行时。
安装 containerd
containerd 是 Kubernetes 最近版本中默认的容器运行时。containerd 实现了 Kubernetes 容器引擎抽象层所需的 CRI。相关的安装过程并不简单,我们将在编写本文时,按照官方 Kubernetes 文档中的步骤进行操作,文档链接如下:kubernetes.io/docs/setup/production-environment/container-runtimes/。这些步骤可能会随时更改,因此请确保查看最新的安装流程。容器运行时需要在集群中的每个节点上安装。我们将从安装 CP 节点(名为 k8s-cp1)的所需组件开始,然后在其他节点上继续安装。
我们将首先安装一些 containerd 的先决条件:
-
首先,我们使用
modprobe启用br_netfilter和overlay内核模块:sudo modprobe br_netfilter sudo modprobe overlay -
我们还确保在系统重启时加载这些模块:
cat <<EOF | sudo tee /etc/modules-load.d/containerd.conf br_netfilter overlay sysctl parameters, also persisted across system reboots:cat <<EOF | sudo tee /etc/sysctl.d/containerd.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
-
我们希望上述更改立即生效,而无需重启系统:
sudo sysctl --system以下是显示前述命令的截图:

图 16.6 – 设置 containerd 先决条件
-
接下来,我们将通过运行以下命令验证
sysctl配置中是否启用了特定的系统变量:sudo sysctl net.bridge.bridge-nf-call-iptables net.bridge.bridge-nf-call-ip6tables net.ipv4.ip_forward命令的输出应如下所示:

图 16.7 – 验证 sysctl 配置中的系统变量
每个变量的值应为 1,正如前面截图所示。
-
现在,让我们确保在安装任何新软件包之前,
apt仓库是最新的:containerd:containerd 配置:
sudo mkdir -p /etc/containerd config.toml inside it. The output of the command is too large to show, but it will show you on the screen the automatically generated contents of the new file we created. -
我们需要稍微修改默认的
containerd配置,以使用systemd的cgroup驱动程序和容器运行时(runc)。此更改是必需的,因为底层平台(在本例中为 Ubuntu)使用systemd作为服务管理器。使用您选择的编辑器打开/etc/containerd/config.toml文件,如下所示:[plugins] section of the file):[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc]
-
然后,添加突出显示的行,并调整适当的缩进(这非常重要):
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] ... [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] SystemdCgroup = true以下是配置片段:

图 16.8 – 修改 containerd 配置
-
保存
/etc/containerd/config.toml文件并重启containerd:containerd Service, by using the following command:sudo systemctl status containerd
The output should show that the system is running and there are no issues.
安装并配置好 containerd 后,我们可以继续进行 Kubernetes 包的安装。
安装 Kubernetes 包
要安装 Kubernetes 包,我们将按照 kubernetes.io/docs/setup/production-environment/tools/kubeadm/install-kubeadm/ 上的步骤进行操作。此过程可能随时间变化,因此请确保查看最新的说明。接下来的步骤适用于 Debian 12 和 Ubuntu 22.04。我们开始:
-
我们首先安装 Kubernetes
apt仓库所需的包:apt repository GNU Privacy Guard (GPG) public signing key (for the latest Kubernetes 1.28 at the time of writing):向我们的系统添加 apt 仓库:
echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list -
让我们查看刚刚添加的新仓库中可用的包:
sudo apt update -y -
我们现在准备好安装 Kubernetes 包:
apt-mark hold command to pin the version of the Kubernetes packages, including containerd:containerd 和 kubelet 服务在系统启动时(重启)启用:
sudo systemctl enable containerd containerd Service first:containerd 应该处于活动状态并运行。以下是显示前面命令输出的截图:

图 16.9 – 固定 Kubernetes 包及 containerd 的运行状态
-
接下来,让我们检查
kubelet服务的状态:exited:

图 16.10 – kubelet 在没有集群配置时崩溃
如上图所示,kubelet 正在寻找 Kubernetes 集群,但集群尚未设置。我们可以看到,kubelet 尝试启动并激活自身,但由于无法找到所需的配置,它不断崩溃。
重要提示
在继续进行下一部分之前,请按照前面的步骤在所有集群节点上安装所需的 Kubernetes 包。
接下来,我们将使用 kubeadm 引导(初始化)Kubernetes 集群。
介绍 kubeadm
kubeadm 是一个用于创建 Kubernetes 集群的辅助工具,主要有两种调用方式:
-
kubeadm init:初始化或引导一个 Kubernetes 集群。 -
kubeadm join:将一个节点添加到 Kubernetes 集群中。
kubadm init [flags] 的默认调用 – 没有标志 – 执行以下任务:
-
kubeadm init确保我们在 CPU 和内存等系统资源方面具备最小要求,具有必要的用户权限,并且支持 CRI 合规的容器运行时。如果任何检查失败,kubeadm init会停止创建集群的执行。如果检查成功,kubeadm会继续执行下一步。 -
kubeadm init创建一个 Kubernetes 使用的自签名 CA,用于生成认证和运行集群内可信工作负载所需的证书。CA 文件存储在/etc/kubernetes/pki目录中,并在节点加入集群时分发到每个节点。 -
kubeadm init创建了启动集群所需的默认 kubeconfig 文件集。kubeconfig 文件存储在/etc/kubernetes/目录下。 -
kubelet守护进程。静态 pod 的示例包括 API 服务器、控制器管理器、调度器和 etcd。静态 pod 清单是描述 CP pod 的配置文件。kubeadm init在集群启动过程中生成静态 pod 清单。清单文件存储在/etc/kubernetes/manifests/目录下。kubelet服务监视此位置,当它找到一个清单文件时,会部署相应的静态 pod。 -
kubelet守护进程部署静态 pod,kubeadm查询kubelet以获取静态 pod 的状态。当静态 pod 启动并运行后,kubeadm init将继续进行下一阶段。 -
kubeadm init遵循 Kubernetes 最佳实践,通过污点处理 CP,避免用户 pod 在 CP 节点上运行。显而易见的原因是为了将 CP 资源专门保留给系统相关的工作负载。 -
kubeadm init生成一个启动令牌,可以与受信任的节点共享,以便加入集群。 -
kubeadm init创建并部署DNS和kube-proxy附加 pod。
Kubernetes 集群启动过程的各个阶段具有高度的可定制性。kubeadm init在没有额外参数的情况下运行所有前面的任务。或者,系统管理员可以使用不同的选项参数调用kubeadm命令,以控制并运行任何一个阶段。
有关kubeadm的更多信息,请使用以下命令查阅该工具的帮助文档:
kubeadm help
有关使用kubeadm启动 Kubernetes 集群的更多信息,包括安装、故障排除和组件定制,您可以参考官方的 Kubernetes 文档:kubernetes.io/docs/setup/production-environment/tools/kubeadm/。
在下一节中,我们将使用kubeadm启动 Kubernetes 集群,生成集群配置文件,然后调用kubeadm init以使用此配置。接下来,我们将通过创建 Kubernetes CP 节点来启动集群。
创建 Kubernetes CP 节点
为了创建 CP 节点,我们将使用一个名为kubeadm的网络和安全解决方案,之后,我们将使用不同的工具来应用配置。选择 Calico 是完全主观的,但它对于管理工作负载和组件之间的通信是必要的。有关 Calico 的更多信息,请访问以下链接:docs.tigera.io/calico/latest/about/。
命令将在我们的虚拟机环境中的k8s-cp1主机上执行。如主机名所示,我们选择k8s-cp1作为 Kubernetes 集群的 CP 节点。现在,让我们开始工作并配置我们的 Kubernetes CP 节点:
-
我们将首先下载用于覆盖网络的 Calico 清单。覆盖网络,也被称为软件定义网络(SDN),是一个逻辑网络层,它支持通过一个可能无法配置的物理网络,在 Pod 之间进行安全且无缝的网络通信。探索集群网络的内部结构超出了本章的范围,但我们鼓励你阅读更多内容,访问
kubernetes.io/docs/concepts/cluster-administration/networking/。你还会找到与 Calico 网络插件相关的参考资料。要下载相关清单,我们运行以下命令:calico.yaml file in the current directory (/home/packt/) that we’ll use with kubectl to configure pod networking later in the process. -
接下来,让我们使用文本编辑器打开
calico.yaml文件,并查找以下行(从 第 3672 行 开始):# - name: CALICO_IPV4POOL_CIDR CALICO_IPV4POOL_CIDR points to the network range associated with the pods. If the related subnet conflicts in any way with your local environment, you’ll have to change it here. We’ll leave the setting as is. -
接下来,我们将使用
kubeadm创建一个默认的集群配置文件。该配置文件描述了我们正在构建的 Kubernetes 集群的设置。我们将该文件命名为k8s-config.yaml:k8s-config.yaml file we just generated and mention a few changes that we’ll have to make. We will open it using the localAPIEndpoint.advertiseAddress configuration parameter – the IP address of the API server endpoint. The default value is 1.2.3.4, and we need to change it to the IP address of the VM running the CP node (k8s-cp1), in our case, 192.168.122.104. Refer to the *Preparing the lab environment* section earlier in this chapter. You’ll have to enter the IP address matching your environment:

图 16.11 – 修改 advertiseAddress 配置参数
- 我们需要进行的下一项更改是将
nodeRegistration.criSocket配置参数指向containerd套接字(/run/containerd/containerd.sock)和名称(k8s-cp1):

图 16.12 – 更改 criSocket 配置参数
- 接下来,我们更改
kubernetesVersion参数,以匹配我们的 Kubernetes 环境版本:

图 16.13 – 更改 kubernetesVersion 参数
默认值是 1.28.0,但我们的 Kubernetes 版本,通过以下命令检查,实际为 1.28.2:
kubeadm version
输出结果如下:

图 16.14 – 获取当前 Kubernetes 版本
-
我们对集群配置文件的最终修改是将
kubelet的cgroup驱动程序设置为systemd,以匹配containerd的cgroup驱动程序。请注意,systemd是底层平台的服务管理器(在 Ubuntu 中),因此需要将相关的服务控制权限交给 Kubernetes 守护进程。此配置块尚未出现在k8s-config.yaml中。我们可以手动将其添加到文件的末尾,或使用以下命令添加:cat <<EOF | cat >> k8s-config.yaml --- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd kubeadm init command with the --config option pointing to the cluster configuration file (k8s-config.yaml), and with the --cri-socket option parameter pointing to the containerd socket:sudo kubeadm init --config=k8s-config.yaml
The preceding command takes a couple of minutes to run. A successful bootstrap of the Kubernetes cluster completes with the following output:

图 16.15 – 成功启动 Kubernetes 集群
此时,我们的 Kubernetes 控制平面节点已经启动并正常运行。在输出中,我们突出显示了以下命令相关的摘录:
-
成功信息(1)
-
将当前用户配置为 Kubernetes 集群管理员(2)
-
将新节点加入 Kubernetes 集群(3)
我们建议花时间仔细查看完整的输出,并根据本章前面 介绍 kubeadm 部分的描述,识别与每个 kubeadm init 任务相关的信息。
-
接下来,为了将当前用户配置为 Kubernetes 集群管理员,我们运行以下命令:
mkdir -p ~/.kube sudo cp -i /etc/kubernetes/admin.conf ~/.kube/config sudo chown $(id -u):$(id -g) ~/.kube/config -
在集群正常运行后,让我们部署 Calico 网络清单,以创建 pod 网络:
kubectl command to list all the pods in the system:kubectl get pods --all-namespaces
The command yields the following output:

图 16.16 – 获取 Kubernetes 集群中的 pod
--all-namespaces 选项用于检索集群中所有资源组中的 pod。Kubernetes 使用 命名空间 来组织资源。目前,我们的集群中唯一运行的 pod 是 系统 pod,因为我们还没有部署任何 用户 pod。
-
以下命令用于检索集群中的当前节点:
k8s-cp1 as the only node configured in the Kubernetes cluster, running as a CP node:

图 16.17 – 列出 Kubernetes 集群中的当前节点
-
你可能还记得,在初始化 Kubernetes 集群之前,
kubelet服务一直在崩溃(并试图重启)。在集群正常运行后,kubelet守护进程的状态应为active和running:sudo systemctl status kubelet输出显示如下内容:

图 16.18 – 集群中的健康 kubelet
-
我们建议使用以下命令查看
/etc/kubernetes/manifests/目录中为每个集群组件创建的清单:ls /etc/kubernetes/manifests/输出显示了描述静态(系统)pod 的配置文件,分别对应 API 服务器、控制器管理器、调度器和 etcd:

图 16.19 – /etc/kubernetes/manifests/ 中的静态 pod 配置文件
-
你也可以查看
/etc/kubernetes中的 kubeconfig 文件:ls /etc/kubernetes/正如你可能还记得的,在本章前面 介绍 kubeadm 部分中,kubeconfig 文件被集群组件用于与 API 服务器通信和身份验证。
由于我们在本节中广泛使用了 kubectl 工具,你可以访问以下链接,查看官方文档,了解更多关于该工具的命令和选项:kubernetes.io/docs/reference/generated/kubectl/kubectl-commands。
接下来,让我们将工作节点添加到 Kubernetes 集群中。
将节点加入 Kubernetes 集群
如前所述,在将节点加入 Kubernetes 集群之前,您需要执行本章前面 准备实验环境 部分中描述的初步步骤。
要将一个节点加入集群,我们需要使用kubeadm join命令,该命令会在引导过程结束时由kubeadm init输出。请参考本章前面的创建 Kubernetes CP 节点部分。请注意,启动令牌在 24 小时后过期。如果忘记复制该命令,可以通过在 CP 节点(k8s-cp1)的终端中运行以下命令来检索相关信息。
要继续,按照以下步骤操作:
-
检索当前的引导令牌:
abcdef.0123456789abcdef):

图 16.20 – 获取当前的引导令牌
-
获取 CA 证书哈希值:
openssl x509 -pubkey \ -in /etc/kubernetes/pki/ca.crt | \ openssl rsa -pubin -outform der 2>/dev/null | \ openssl dgst -sha256 -hex | sed 's/^.* //'输出如下:

图 16.21 – 获取 CA 证书哈希值
-
你也可以通过以下命令生成新的引导令牌:
kubeadm join command with the required parameters:kubeadm token create --print-join-command
现在令牌已创建,我们可以继续执行接下来的引导步骤。
在接下来的步骤中,我们将使用引导过程结束时显示的初始令牌。因此,让我们切换到节点的命令行终端(k8s-n1),并运行以下命令:
-
确保调用
sudo,否则命令会因权限不足而失败:sudo kubeadm join 192.168.122.104:6443 \ --token abcdef.0123456789abcdef \ k8s-cp1):k8s-n1) 已加入集群:

图 16.22 – 新节点(k8s-n1)已加入集群
- 我们建议你重复加入其他两个集群节点(
k8s-n2和k8s-n3)的过程。在加入过程中,当新的节点正在部署 CP pod 时,如果你过快查询 CP 节点上的节点(k8s-cp1),你可能会暂时看到新的节点处于NotReady状态。这个过程需要一些时间。最终,所有三个节点应该在kubectl get nodes命令的输出中显示为Ready(在k8s-cp1上):

图 16.23 – 所有节点运行中的 Kubernetes 集群
我们现在已完成 Kubernetes 集群的安装,包含一个 CP 节点和三个工作节点。我们使用的是本地(本地虚拟机)环境,但相同的过程也适用于运行在私有或公共云中的托管 IaaS 解决方案。
在接下来的章节中,我们将探讨kubectl命令行工具,并使用它来创建和管理 Kubernetes 资源。然后,我们将研究如何使用命令式和声明式部署模型在 Kubernetes 中部署和扩展应用程序。
使用 Kubernetes
在本节中,我们将使用与 Kubernetes 集群交互的实际示例。由于我们将在很大程度上使用 kubectl CLI,我们将深入探讨其一些常见的使用模式。然后,我们将重点关注将应用程序部署到 Kubernetes 集群的过程。我们将使用在安装 Kubernetes 到 VM部分中建立的本地环境。
让我们首先仔细看看 kubectl 及其使用方法。
使用 kubectl
kubectl 是管理 Kubernetes 集群及其资源的主要工具。kubectl 使用 Kubernetes 的 REST API 与集群的 API 服务器端点进行通信。kubectl 命令的一般语法如下:
kubectl [command] [TYPE] [NAME] [flags]
通常,kubectl 命令执行 CRUD 操作 —— CRUD 代表 创建、读取、更新 和 删除 —— 针对 Kubernetes 资源,例如 Pod、Deployment 和 Service。
kubectl 的一个重要特性是命令输出格式,可以是 YAML、JSON 或纯文本。输出格式在创建或编辑应用程序部署清单时非常有用。我们可以将 kubectl 命令(例如创建资源)的 YAML 输出捕获到一个文件中。稍后,我们可以重用该清单文件,以声明的方式执行相同的操作(或操作序列)。这将引出 Kubernetes 的两种基本部署模式:
-
kubectl命令用于操作特定资源 -
kubectl apply命令,通常是通过一次调用操作一组资源
在本章稍后的应用程序部署部分中,我们将更详细地探讨这两种部署模式。目前,让我们回到继续探索 kubectl 命令。以下是一些最常见的 kubectl 命令的简短列表:
-
create,apply:这些命令以命令式/声明式方式创建资源 -
get:此命令读取资源 -
edit,set:这些命令更新资源或对象的特定特性 -
delete:此命令删除资源 -
run:此命令启动一个 Pod -
exec:此命令在 Pod 容器中执行命令 -
describe:此命令显示有关资源的详细信息 -
explain:此命令提供资源相关的文档 -
logs:此命令显示 Pod 容器中的日志
有几个常用的 kubectl 参数也值得一提:
-
--dry-run:此选项在不修改系统状态的情况下运行命令,同时仍提供如同正常执行时的输出 -
--output:此选项指定命令输出的各种格式:yaml、json和wide(以纯文本形式显示附加信息)
在接下来的章节中,我们将查看多个使用 kubectl 命令的示例。始终记住命令的一般模式:

图 16.24 – kubectl 的一般使用模式
我们建议你查看完整的kubectl命令参考文档,链接为kubernetes.io/docs/reference/kubectl/overview/。在你熟悉kubectl的同时,你也可以随时查看相关的备忘单,链接在这里:kubernetes.io/docs/reference/kubectl/cheatsheet/。
现在,让我们准备kubectl环境,以便与我们之前使用虚拟机构建的 Kubernetes 集群进行交互。如果你更倾向于在 CP 节点上使用kubectl,可以跳过下一节。
从本地机器连接到 Kubernetes 集群
在本节中,我们将配置在本地 Linux 桌面上运行的kubectl CLI,以便控制远程 Kubernetes 集群。我们的本地机器运行的是 Debian 12。
首先,我们需要在系统上安装kubectl。我们将参考kubernetes.io/docs/tasks/tools/install-kubectl-linux/中的安装说明。我们将使用以下命令下载最新的kubectl版本:
curl -LO "kubectl with the following command:
sudo install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl
Now that the package is installed, we can test the installation using the following command:
kubectl version --client
The output of the preceding commands is shown in the following screenshot:

Figure 16.25 – Installing kubectl locally on a Debian system
We want to add (merge) yet another cluster configuration to our environment. This time, we connect to an on-premises Kubernetes CP, and we’ll use `kubectl` to update kubeconfig. Here are the steps we’ll be taking:
1. We first copy kubeconfig from the CP node (`k8s-cp1`, `192.168.122.104`) to a temporary location `(/tmp/config.cp`):
```
scp packt@192.168.122.104:~/.kube/config /tmp/config.cp
```
2. Finally, we can move the new kubeconfig file to the new location:
```
mv /tmp/config.cp ~/.kube/config
```
3. Optionally, we can clean up the temporary files created in the process:
```
rm ~/.kube/config.old /tmp/config.cp
```
4. Let’s get a view of the current kubeconfig contexts:
```
kubernetes-admin@kubernetes) 和集群名称(kubernetes):
```

Figure 16.26 – The new kubeconfig contexts
1. For consistency, let’s change the on-premises cluster’s context name to `k8s-local` and make it the default context in our `kubectl` environment:
```
kubectl config rename-context \
kubernetes-admin@kubernetes \
k8s-local
kubectl 上下文变为 k8s-local,我们现在正在与本地 Kubernetes 集群(kubernetes)交互。以下是输出截图:
```

Figure 16.27 – The current context set to the on-premises Kubernetes cluster
Next, we look at some of the most common `kubectl` commands used with everyday Kubernetes administration tasks.
Working with kubectl
One of the first commands we run when connected to a Kubernetes cluster is the following:
kubectl cluster-info
The command shows the IP address and port of the API server listening on the CP node, among other information:

Figure 16.28 – The Kubernetes cluster information shown
The `cluster-info` command can also help to debug and diagnose cluster-related issues:
kubectl cluster-info dump
To get a detailed view of the cluster nodes, we run the following command:
kubectl get nodes --output=wide
The `--output=wide` (or `-o wide`) flag yields detailed information about cluster nodes. The output in the following illustration has been cropped to show it more clearly:

Figure 16.29 – Getting detailed information about cluster nodes
The following command retrieves the pods running in the default namespace:
kubectl get pods
As of now, we don’t have any user pods running, and the command returns the following:
默认命名空间中未找到资源。
To list all the pods, we append the `--all-namespaces` flag to the preceding command:
kubectl get pods --all-namespace
The output shows all pods running in the system. Since these are exclusively system pods, they are associated with the `kube-system` namespace:

Figure 16.30 – Getting all pods in the system
We would get the same output if we specified `kube-system` with the `--``namespace` flag:
kubectl get pods --namespace kube-system
For a comprehensive view of all resources running in the system, we run the following command:
kubectl get all --all-namespaces
So far, we have only mentioned some of the more common object types, such as nodes, pods, and Services. There are many others, and we can view them with the following command:
kubectl api-resources
The output includes the name of the API object types (such as `nodes`), their short name or alias (such as `no`), and whether they can be organized in namespaces (such as `false`):

Figure 16.31 – Getting all API object types (cropped)
Suppose you want to find out more about specific API objects, such as `nodes`. Here’s where the `explain` command comes in handy:
kubectl explain nodes
The output is as follows:

Figure 16.32 – Showing nodes’ detailed information (cropped)
The output provides detailed documentation about the `nodes` API object type, including the related API fields. One of the API fields is `apiVersion`, describing the versioning schema of an object. You may view the related documentation with the following command:
kubectl explain nodes.apiVersion
The output is as follows:

Figure 16.33 – Details about the apiVersion field (cropped)
We encourage you to use the `explain` command to learn about the various Kubernetes API object types in a cluster. Please note that the `explain` command provides documentation about *resource types*. It should not be confused with the `describe` command, which shows detailed information about the *resources* in the system.
The following commands display cluster node-related information about *all* nodes, and then node `k8s-n1` in particular:
kubectl describe nodes
kubectl describe nodes k8s-n1
For every `kubectl` command, you can invoke `--help` (or `-h`) to get context-specific help. Here are a few examples:
kubectl --help
kubectl config -h
kubectl get pods -h
The `kubectl` CLI is relatively rich in commands, and becoming proficient with it may take some time. Occasionally, you may find yourself looking for a specific command or remembering its correct spelling or use. The `auto-complete` bash for `kubectl` comes to the rescue. We’ll show you how to enable this next.
Enabling kubectl autocompletion
With `kubectl` autocompletion, you’ll get context-sensitive suggestions when you hit the *Tab* key twice while typing the `kubectl` commands.
The `kubectl` autocompletion feature depends on `bash-completion`. Most Linux platforms have `bash-completion` enabled by default. Otherwise, you’ll have to install the related package manually. On Ubuntu, for example, you install it with the following command:
sudo apt-get install -y bash-completion
Next, you need to source the `kubectl` autocompletion in your shell (or similar) profile:
echo "source <(kubectl completion bash)" >> ~/.bashrc
The changes will take effect on your next login to the terminal or immediately if you source the `bash` profile:
source ~/.bashrc
With the `kubectl` autocomplete active, you’ll get context-sensitive suggestions when you hit the *Tab* key twice while typing the command. For example, the following sequence provides all the available resources when you try to create one:
kubectl create [Tab][Tab]
When typing the `kubectl create` command and pressing the *Tab* key twice, the result will be a list of resources available for the command:

Figure 16.34 – Autocompletion use with kubectl
The `kubectl` autocompletion reaches every part of the syntax: command, resource (type and name), and flags.
Now that we know more about using the `kubectl` command, it’s time to turn our attention to deploying applications in Kubernetes.
Deploying applications
When we introduced the `kubectl` command and its usage pattern at the beginning of the *Using kubectl* section, we touched upon the two ways of creating application resources in Kubernetes: imperative and declarative.
We’ll look at both of these models closely in this section while deploying a simple web application. Let’s start with the imperative model first.
Working with imperative Deployments
As a quick refresher, with imperative Deployments, we follow a sequence of `kubectl` commands to create the required resources and get to the cluster’s desired state, such as running the application. Declarative Deployments accomplish the same, usually with a single `kubectl` `apply` command using a manifest file describing multiple resources.
Creating a Deployment
Let’s begin by creating a Deployment first. We’ll name our Deployment `packt`, based on a demo Nginx container we’re pulling from the public Docker registry (`docker.io/nginxdemos/hello`):
kubectl create deployment packt --image=nginxdemos/hello
The command output shows that our Deployment was created successfully:
deployment.apps/packt 已创建
We just created a Deployment with a ReplicaSet containing a single pod running a web server application. We should note that our application is managed by the controller manager within an app Deployment stack (`deployment.apps`). Alternatively, we could just deploy a simple application pod (`packt-web`) with the following command:
kubectl run pod/packt-web), 不是 Deployment 的一部分:
pod/packt-web created
我们稍后会看到,这个 Pod 不是 ReplicaSet 的一部分,因此不会由控制器管理器管理。
让我们通过查询 Pod 的详细信息来查看系统的状态:
kubectl get pods -o wide
让我们分析一下输出:

图 16.35 – 获取具有详细信息的应用程序 Pod
在上面的输出中,你可以看到一系列描述的命令,我们还可以看到 Pod 已启动并正在运行,且 Kubernetes 已将它们部署到不同的节点上:
-
packt-579bb9c999-rtvzr:在集群节点k8s-n1上 -
packt-web:在集群节点k8s-n3上
在不同节点上运行 pod 是由于 Kubernetes 集群内部的负载均衡和资源分配。
由控制器管理的应用程序 pod 是 packt-579bb9c999-rtvzr。Kubernetes 为我们管理的 pod 生成一个唯一的名称,将579bb9c999 和 rtvzr 附加到部署名称(packt)后面。Pod 模板哈希和 pod ID 在 ReplicaSet 中是唯一的。
相比之下,独立 pod(packt-web)保持原样,因为它不属于应用程序部署。让我们先描述两个 pod,以获取更多信息。我们先从托管的 pod 开始。别忘了使用 kubectl 自动补全(通过按Tab键两次):
kubectl describe pod packt-5dc77bb9bf-bnzsc
相关输出较大。以下是一些相关摘录:

图 16.36 – Pod 信息
相比之下,独立 pod(packt-web)的相同命令会稍有不同,不包含Controlled By字段:
kubectl describe pod packt-web
以下是相关摘录:

图 16.37 – 相关的 pod 信息
你也可以去集群中我们 pod 正在运行的任何节点,仔细查看相关容器。以节点k8s-n3(192.168.122.163)为例,其中我们独立的 pod(packt-web)正在运行。我们首先通过 SSH 连接到该节点的终端:
ssh packt@192.168.122.163
然后我们将使用 containerd 运行时来查询系统中的容器:
sudo crictl --runtime-endpoint unix:///run/containerd/containerd.sock ps
输出显示如下:

图 16.38 – 获取在集群节点上运行的容器
接下来,我们将展示如何访问在 pod 内运行的进程。
访问 pod 中的进程
让我们切换回本地(在本地机器上,而非虚拟机)kubectl环境,并运行以下命令以访问运行packt-web pod 的容器中的 shell:
kubectl exec -it packt-web -- /bin/sh
该命令会将我们带入容器内的交互式 shell 提示符。在这里,我们可以像通过终端登录到 packt-web 主机一样运行命令。该交互式会话是通过 -it 选项——交互式终端——或--interactive --tty选项产生的。
让我们运行一些命令,首先从进程探测器开始:
ps aux
这是来自输出的相关摘录,显示了在packt-web容器内运行的进程,以及一些在其中运行的命令:

图 16.39 – 在 packt-web 容器内运行的进程
我们还可以通过以下命令检索 IP 地址:
ifconfig | grep 'inet addr:' | cut -d: -f2 | awk '{print $1}' | grep -v '127.0.0.1'
输出显示了 pod 的 IP 地址(如图 16.39所示):
172.16.57.193
我们还可以通过以下命令检索主机名:
hostname
输出显示了 pod 名称(如图 16.39所示):
packt-web
让我们使用exit命令(如图 16.39所示)或按Ctrl + D离开容器 shell。通过kubectl的exec命令,我们可以在 Pod 内部运行任何进程,前提是相关进程存在。
接下来我们将通过测试packt-web应用程序 Pod 来进行实验。我们应该注意,在此时,访问packt-web的 Web 服务器端点的唯一方式是通过其内部 IP 地址。之前,我们使用了kubectl的get pods -o wide和describe命令来检索有关 Pod 的详细信息,包括 Pod 的 IP 地址。你也可以使用以下单行命令来获取 Pod 的 IP:
kubectl get pods packt-web -o jsonpath='{.status.podIP}{"\n"}'
在我们的例子中,该命令返回172.16.57.193。我们使用了-o jsonpath输出选项来指定特定字段的 JSON 查询,{.status.podIP}。记住,Pod 的 IP 地址仅在集群内的 Pod 网络(172.16.0.0/16)内可访问。以下是显示输出的截图:

图 16.40 – 测试应用程序 Pod
因此,我们需要使用curl命令探测packt-web端点,该命令需在 Pod 网络内运行。完成此任务的一个简便方法是运行一个安装了curl工具的测试 Pod:
-
以下命令运行一个名为
test的 Pod,基于curlimages/curl的 Docker 镜像:sleep command due to the Docker entry point of the corresponding image, which simply runs a curl command and then exits. Without sleep, the pod would keep coming up and crashing. With the sleep command, we delay the execution of the curl entry point to prevent the exit. The output is shown in the following screenshot:

图 16.41 – 在 Pod 上使用 curl 进行测试
-
现在,我们可以使用
testPod 运行一个简单的curl命令,目标是packt-webWeb 服务器端点:kubectl exec test -- curl http://172.16.57.193- 我们将获得一个 HTTP 响应以及一个相应的访问日志追踪(来自 Pod 中运行的 Nginx 服务器),记录该请求。输出的一个片段如下所示:

图 16.42 – 运行 curl 测试的响应
-
要查看
packt-webPod 的日志,我们运行以下命令:kubectl logs packt-web输出如下所示:

图 16.43 – packt-web Pod 的日志
-
packt-webPod 中的日志是由 Nginx 生成的,并被重定向到stdout和stderr。我们可以使用以下命令轻松验证这一点:kubectl exec packt-web -- ls -la /var/log/nginx输出显示了相关的符号链接:

图 16.44 – 相关的符号链接
-
当你使用完
testPod 后,可以通过以下命令删除它:kubectl delete pods test
现在我们已经在 Kubernetes 集群内部署了第一个应用程序,让我们来看看如何将相关端点暴露给全世界。接下来,我们将通过服务暴露部署。
将部署暴露为服务
现在,让我们回顾一下我们之前用来创建packt部署的命令。不要运行它,以下只是一个复习:
kubectl create deployment packt --image=nginxdemos/hello
该命令执行了以下序列:
-
它创建了一个部署(
packt)。 -
部署创建了一个 ReplicaSet(
packt-579bb9c999)。 -
ReplicaSet 创建了 pod(
packt-579bb9c999-rtvzr)。
我们可以通过以下命令进行验证:
kubectl get deployments -l app=packt
kubectl get replicasets -l app=packt
kubectl get pods -l app=packt
在前面的命令中,我们使用了 --label-columns (-l) 标志,通过 app=packt 标签过滤结果,表示 packt 部署的资源。
我们鼓励您使用 kubectl describe 命令仔细查看这些资源。不要忘记在输入命令时使用 kubectl 自动完成功能:
kubectl describe deployment packt | more
kubectl describe replicaset packt | more
kubectl describe pod packt-5dc77bb9bf-bnzsc | more
kubectl describe 命令在故障排除应用程序或 pod 部署时非常有用。检查相关输出中的 Events 部分,查找 pod 启动失败、错误(如果有的话)以及可能导致问题的线索。
现在我们已经在 Kubernetes 集群中部署了第一个应用程序,让我们看看如何将相关的端点暴露给全世界。
到目前为止,我们已经部署了一个应用程序(packt),它在一个 pod(packt-579bb9c999-rtvzr)中运行 Nginx Web 服务器,并在端口 80 上监听。如前所述,此时我们只能在 pod 网络内访问该 pod,这个网络仅对集群内部可见。在本节中,我们将展示如何将应用程序(或部署)暴露出去,以便外部世界能够访问。Kubernetes 使用 Service API 对象,包括 代理 和 选择器,将网络流量路由到部署中的应用 pod。接下来,您可以按照以下步骤进行操作:
-
以下命令为我们的部署(
packt)创建一个 Service:kubectl expose deployment packt \ --port=80 \ --target-port=80 \ --type=NodePort flag, the Service type would be ClusterIP by default, and the Service endpoint would only be accessible within the cluster.- 让我们更仔细地看一下我们的 Service(
packt):
10.105.111.243) and the ports the Service is listening on for TCP traffic (80:32664/TCP):* port `80`: Within the cluster* port `32664`: Outside the cluster, on any of the nodesWe should note that the cluster IP is only accessible within the cluster and not from the outside: - 让我们更仔细地看一下我们的 Service(

图 16.45 – 暴露 packt 部署的服务
此外,EXTERNAL-IP(<none>)不应与集群节点的 IP 地址混淆,后者是我们可以访问 Service 的位置。外部 IP 通常是由云提供商配置的负载均衡器的 IP 地址(可通过 --external-ip 标志进行配置)。
-
现在,我们应该能够通过将浏览器指向集群节点上的任何一个,访问集群外部的应用程序,端口为
32664。要获取集群节点的列表及其对应的 IP 地址和主机名,我们可以运行以下命令:kubectl get nodes -o jsonpath='{range .items[*]}{.status.addresses[*].address}{"\n"}'输出如下:

图 16.46 – 集群节点列表
-
让我们选择 CP 节点(
192.168.122.104/k8s-cp1),并在浏览器中输入以下地址:http://192.168.122.104:32664。浏览器发出的 Web 请求被定向到服务端点(
packt),它将相关的网络数据包路由到应用 pod(packt-579bb9c999-rtvzr)。packtWeb 应用响应一个简单的 Nginx172.16.215.65)和名称(packt-579bb9c999-rtvzr):

图 16.47 – 访问 packt 应用服务
-
为了验证网页上的信息是否准确,你可以运行以下
kubectl命令,检索类似的信息:kubectl get pod packt-579bb9c999-rtvzr -o jsonpath='{.status.podIP}{"\n"}{.metadata.name}{"\n"}'命令的输出将是 pod 的内部 IP 地址和名称,如下所示:

图 16.48 – 使用 kubectl 命令验证信息
假设我们有大量流量访问我们的应用,我们希望扩展控制 pod 的 ReplicaSet。在下一节中,我们将展示如何完成此任务。
扩展应用部署
当前,我们在 packt 部署中只有一个 pod。为了扩展应用部署,我们必须先获取运行副本的信息,以便将它们扩展到所需的数量并进行测试。以下是操作步骤:
-
为了获取关于运行副本数量的相关详细信息,我们运行以下命令:
kubectl describe deployment packt输出中相关的摘录如下:

图 16.49 – Pod 详情
-
让我们将
packt部署扩展到 10 个副本,使用以下命令:packt Deployment, we’ll see 10 pods running:kubectl get pods -l app=packt
The output is as follows:

图 16.50 – 扩展 Deployment 副本
-
传入的请求将通过负载均衡分配到我们的应用服务端点(
http://192.168.122.104:32664)的各个 pod。为了说明这一行为,我们可以使用curl或者命令行中的文本浏览器,以避免现代桌面浏览器的缓存优化。为了更好地展示,我们将使用Lynx,一个简单的文本浏览器。在我们的 Debian 12 桌面上,已经安装了该软件包。你可以通过以下命令安装它:sudo apt-get install -y lynx- 接下来,我们将 Lynx 指向我们的应用端点:
lynx 172.16.191.6:32081如果我们每隔几秒钟使用 Ctrl + R 刷新页面,我们会观察到服务器地址和名称会根据当前处理请求的 pod 发生变化:

图 16.51 – 在各个 pod 之间负载均衡请求
你可以通过输入 Q 然后按 Enter 来退出 Lynx 浏览器。
-
我们可以使用以下命令将我们的 Deployment(
packt)缩减为三个副本(或任何其他非零正整数):packt application pods, we can see the surplus pods terminating until only three pods are remaining:kubectl get pods -l app=packt
The output is as follows:

图 16.52 – 缩减到三个 pod
-
在结束我们的命令式部署之前,让我们清理迄今为止创建的所有资源:
kubectl delete service packt kubectl delete deployment packt kubectl delete pod packt-web- 下面的命令应显示一个干净的状态:
kubectl get all输出如下:

图 16.53 – 默认状态下的集群
在下一节中,我们将讨论如何在 Kubernetes 集群中声明性地部署资源和应用。
使用声明式部署
声明式部署的核心是清单文件。清单文件通常采用 YAML 格式,编写时通常会结合自动生成的代码和手动编辑。然后,使用 kubectl apply 命令部署清单:
kubectl apply -f MANIFEST
在 Kubernetes 中声明式部署资源涉及以下阶段:
-
创建清单文件
-
更新清单
-
验证清单
-
部署清单
-
在前述阶段之间进行迭代
为了说明声明式模型,我们以将一个简单的 Hello World web 应用程序部署到集群为例。结果将类似于我们之前使用命令式方法的做法。
所以,让我们从为我们的部署创建一个清单开始。
创建清单
当我们命令式地创建我们的 packt 部署时,我们使用了以下命令(先别运行!):
kubectl create deployment packt --image=nginxdemos/hello
以下命令将模拟相同的过程,而不会改变系统状态:
kubectl create deployment packt --image=nginxdemos/hello \
--dry-run=client --output=yaml
我们使用了以下附加选项(标志):
-
--dry-run=client:此命令在本地kubectl环境(客户端)中运行,不会修改系统状态 -
--output=yaml:此选项将命令输出格式化为 YAML
命令的输出如下:

图 16.54 – 模拟清单创建
我们可以使用前一个命令的输出分析系统中需要做的更改。然后我们可以将其重定向到一个文件(packt.yaml),作为我们部署清单的草稿:
kubectl create deployment packt --image=nginxdemos/hello \
--dry-run=client --output=yaml > packt.yaml. From here, we can edit the file to accommodate more complex configurations. For now, we’ll leave the manifest as is and proceed with the next stage in our declarative Deployment workflow.
Validating a manifest
Before deploying a manifest, we recommend validating the Deployment, especially if you edited the file manually. Editing mistakes can happen, particularly when working with complex YAML files with multiple indentation levels.
The following command validates the `packt.yaml` Deployment manifest:
kubectl apply -f packt.yaml --dry-run=client
A successful validation yields the following output:
deployment.apps/packt created (dry run)
If there are any errors, we should edit the manifest file and correct them prior to deployment. Our manifest looks good, so let’s go ahead and deploy it.
Deploying a manifest
To deploy the `packt.yaml` manifest, we use the following command:
kubectl apply -f packt.yaml
A successful Deployment shows the following message:
deployment.apps/packt created
We can check the deployed resources with the following command:
kubectl get all -l app=packt
The output shows that the `packt` Deployment resources created declaratively are up and running:

Figure 16.55 – The Deployment resources created declaratively
Next, we want to expose our Deployment using a Service.
Exposing the Deployment with a Service
We’ll repeat the preceding workflow by creating, validating, and deploying the Service manifest (`packt-svc.yaml`). For brevity, we simply enumerate the related commands:
1. Create the manifest file (`packt-svc.yaml`) for the Service exposing our Deployment (`packt`):
```
kubectl expose deployment packt \
--port=80 \
--target-port=80 \
--type=NodePort \
--dry-run=client --output=yaml > packt-svc.yaml
```
We explained the preceding command previously in the *Exposing Deployments as* *Services* section.
2. Next, we’ll validate the Service Deployment manifest:
```
kubectl apply -f packt-svc.yaml --dry-run=client
```
3. If the validation is successful, we deploy the Service manifest:
```
packt 资源:
```
packt application resources, including the Service endpoint (service/packt) listening on port 31380:
```
```

Figure 16.56 – The packt application resources deployed
1. Using a browser, `curl`, or Lynx, we can access our application by targeting any of the cluster nodes on port `31380`. Let’s use the CP node (`k8s-cp1`, `192.168.122.104`) by pointing our browser to `http://192.168.122.104:31380`:

Figure 16.57 – Accessing the packt application endpoint
If we want to change the existing configuration of a resource in our application Deployment, we can update the related manifest and redeploy it. In the next section, we’ll modify the Deployment to accommodate a scale-out scenario.
Updating a manifest
Suppose our application is taking a high number of requests, and we’d like to add more pods to our Deployment to handle the traffic. We need to change the `spec.replicas` configuration setting in the `pack.yaml` manifest:
1. Using your editor of choice, edit the `packt.yaml` file and locate the following configuration section:
```
spec:
replicas: 1
```
Change the value from `1` to `10` for additional application pods in the ReplicaSet controlled by the `packt` Deployment. The configuration becomes the following:
```
spec:
replicas: 10
```
2. Save the manifest file and redeploy with the following command:
```
packt 部署已重新配置:
```
packt resources in the cluster, we should see the new pods up and running:
```
packt 部署,包括在集群中部署的附加 pod:
```
```
```

Figure 16.58 – The additional pods added for application scale-out
We encourage you to test with the scale-out environment and verify the load balancing workload described in the *Scaling application Deployments* section earlier in this chapter.
1. Let’s scale back our Deployment to three pods, but this time by updating the related manifest on the fly with the following command:
```
kubectl edit deployment packt
```
The command will open our default editor in the system (**vi**) to make the desired change:

Figure 16.59 – Making Deployment changes on the fly
1. After saving and exiting the editor, we’ll get a message suggesting that our Deployment (`packt`) has been updated:
```
kubectl edit 不会反映在部署清单(packt.yaml)中。不过,相关的配置更改会保存在集群(etcd)中。
```
2. We can verify our updated Deployment with the help of the following command:
```
kubectl get deployment packt
```
The output now shows only three pods running in our Deployment:

Figure 16.60 – Showing the number of Deployments
1. Before wrapping up, let’s clean up our resources once again with the following commands to bring the cluster back to the default state:
```
kubectl delete service packt
kubectl delete deployment packt
```
We have shown you how to use Kubernetes on bare metal, and in the next section, we will briefly point you to some useful resources for using Kubernetes in the cloud.
Running Kubernetes in the cloud
Managed Kubernetes Services are fairly common among public cloud providers. Amazon **Elastic Kubernetes Service** (**EKS**), **Azure Kubernetes Services** (**AKS**), and **Google Kubernetes Engine** (**GKE**) are the major cloud offerings of Kubernetes at the time of this writing. In this section, we’ll not focus on any of these solutions, but we will provide you with solid resources on how to use Kubernetes in the cloud. For more advanced titles on this subject, please check the *Further reading* section of this chapter.
We should note that we just scratched the surface of deploying and managing Kubernetes clusters. Yet, here we are, at a significant milestone, where we deployed our first Kubernetes clusters on-premises. We have reached the end of this journey here, but we trust that you’ll take it to the next level and further explore the exciting domain of application Deployment and scaling with Kubernetes. Let’s now summarize what we have learned in this chapter.
Summary
We began this chapter with a high-level overview of the Kubernetes architecture and API object model, introducing the most common cluster resources, such as pods, Deployments, and Services. Next, we took on the relatively challenging task of building an on-premises Kubernetes cluster from scratch using VMs. We explored various CLI tools for managing Kubernetes cluster resources on-premises. At the high point of our journey, we focused on deploying and scaling applications in Kubernetes using imperative and declarative Deployment scenarios.
We believe that novice Linux administrators will benefit greatly from the material covered in this chapter and become more knowledgeable in managing resources across hybrid clouds and on-premises distributed environments, deploying applications at scale, and working with CLI tools. We believe that the structured information in this chapter will also help seasoned system administrators refresh some of their knowledge and skills in the areas covered.
It’s been a relatively long chapter, and we barely skimmed the surface of the related field. We encourage you to explore some resources captured in the *Further reading* section and strengthen your knowledge regarding some key areas of Kubernetes environments, such as networking, security, and scale.
In the next chapter, we’ll stay within the application deployment realm and look at **Ansible**, a platform for accelerating application delivery on-premises and in the cloud.
Questions
Here are a few questions for refreshing or pondering upon some of the concepts you’ve learned in this chapter:
1. Enumerate some of the essential Services of a Kubernetes CP node. How do the worker nodes differ?
2. What command did we use to bootstrap a Kubernetes cluster?
3. What is the difference between imperative and declarative Deployments in Kubernetes?
4. What is the `kubectl` command for deploying a pod? How about the command for creating a Deployment?
5. What is the `kubectl` command to access the shell within a pod container?
6. What is the `kubectl` command to query all resources related to a Deployment?
7. How do you scale out a Deployment in Kubernetes? Can you think of the different ways (commands) in which to accomplish the task?
8. How do you delete all resources related to a Deployment in Kubernetes?
Further reading
The following resources may help you to consolidate your knowledge of Kubernetes further:
* Kubernetes documentation online: [`kubernetes.io/docs/home/`](https://kubernetes.io/docs/home/)
* The `kubectl` cheat sheet: [`kubernetes.io/docs/reference/kubectl/cheatsheet/`](https://kubernetes.io/docs/reference/kubectl/cheatsheet/)
* *Kubernetes and Docker: The Container Masterclass [Video]*, *Cerulean Canvas*, Packt Publishing
* *Mastering Kubernetes – Third Edition*, Gigi Sayfan, Packt Publishing
The following is a short list of useful links for deploying Kubernetes on Azure, Amazon, and Google:
* Amazon EKS:
* [`docs.aws.amazon.com/eks/index.html`](https://docs.aws.amazon.com/eks/index.html)
* [`docs.aws.amazon.com/eks/latest/userguide/sample-deployment.html`](https://docs.aws.amazon.com/eks/latest/userguide/sample-deployment.html)
* AKS:
* [`azure.microsoft.com/en-us/services/kubernetes-service/`](https://azure.microsoft.com/en-us/services/kubernetes-service/)
* [`learn.microsoft.com/en-us/azure/aks/tutorial-kubernetes-deploy-cluster?tabs=azure-cli`](https://learn.microsoft.com/en-us/azure/aks/tutorial-kubernetes-deploy-cluster?tabs=azure-cli)
* [`learn.microsoft.com/en-us/azure/aks/learn/quick-kubernetes-deploy-portal?tabs=azure-cli`](https://learn.microsoft.com/en-us/azure/aks/learn/quick-kubernetes-deploy-portal?tabs=azure-cli)
* GKE:
* [`cloud.google.com/kubernetes-engine`](https://cloud.google.com/kubernetes-engine)
* [`cloud.google.com/build/docs/deploying-builds/deploy-gke`](https://cloud.google.com/build/docs/deploying-builds/deploy-gke)
* [`cloud.google.com/kubernetes-engine/docs/deploy-app-cluster`](https://cloud.google.com/kubernetes-engine/docs/deploy-app-cluster)
第十七章:使用 Ansible 进行基础设施和自动化管理
如果你日常的系统管理或开发工作涉及繁琐和重复的操作,Ansible 可以帮助你在节省宝贵时间的同时完成任务。Ansible 是一款用于自动化软件配置、配置管理和应用部署工作流的工具。Ansible 最初由 Michael DeHaan 在 2012 年开发,2015 年被 Red Hat 收购,并现在作为开源项目进行维护。
在本章中,你将学习 Ansible 的基本概念,以及一系列动手操作的示例。特别是,我们将探索以下主题:
-
介绍 Ansible 架构和配置管理
-
安装 Ansible
-
使用 Ansible
技术要求
首先,你应该对 Linux 命令行终端有一定的了解。中级的 Linux 知识将帮助你理解本章中实际示例的某些细节。你还应该熟练使用基于 Linux 的文本编辑器。
对于实际操作的示例,我们建议设置一个与我们使用的实验环境类似的实验室环境。为了复制这个环境,您的 CPU 至少需要 6 个物理核心和 6 个虚拟核心(共 12 个)。四核 CPU 带超线程功能是不够的。此外,所有主机上应安装 OpenSSH。在本章的设置实验环境部分,你将找到相关的配置说明。如果你没有配置实验环境,你仍然可以从本章与实际示例相关的详细解释中获益。
现在,让我们通过介绍 Ansible 的基本概念开始我们的学习之旅。
介绍 Ansible 架构和配置管理
在本章的介绍中,我们概述了 Ansible 的一个关键方面——它是一款自动化工作流的工具。几乎所有的 Linux 系统管理任务都可以使用 Ansible 来自动化。通过使用 Ansible CLI,我们可以执行简单的命令来更改系统的期望状态。通常,使用 Ansible 时,我们会在远程主机或一组主机上执行任务。
让我们用经典的包管理来说明。假设你正在管理一个包含一组 web 服务器的基础设施,并且计划在所有这些服务器上安装最新版本的 web 服务器应用程序(Nginx 或 Apache)。实现这一任务的一种方式是通过 SSH 连接到每个主机,执行相关的 shell 命令来安装最新的 web 服务器包。如果你的机器很多,这将是一项庞大的任务。你或许会认为可以写个脚本来自动化这个工作。这是可行的,但这样你又有了一项新工作;也就是维护脚本、修复可能的错误,随着基础设施的增长,添加新的功能。
在某些情况下,你可能需要管理多个主机上的用户、数据库或配置网络设置。很快,你就会需要一款瑞士军刀式的工具,拥有你宁愿免费获得而不是自己编写的功能。此时,Ansible 就派上用场了。凭借其丰富的模块——几乎涵盖了你能想象到的所有系统管理任务——Ansible 能够以最小的努力,并且在一个非常安全高效的方式下,远程配置、运行或部署你所选择的管理任务。
我们将通过简要了解 Ansible 架构来巩固这些初步的思路。
了解 Ansible 架构
Ansible 核心框架是用 Python 编写的。我们要提前提到的是,Ansible 采用了 无代理 架构。换句话说,它在 控制节点 上运行,向远程主机执行命令,无需在受管主机上安装远程端点或服务来与控制节点进行通信。至少,Ansible 通信的唯一要求是能够通过 SSH 连接到受管主机。然而,如果主机没有安装 Python 框架,那么 Ansible 的操作将仅限于运行脚本和原始 SSH 命令。绝大多数服务器操作系统平台默认已经安装了 Python。
Ansible 可以通过一个控制节点使用安全的 SSH 连接管理一组远程主机。下图展示了使用 Ansible 管理的基础设施的逻辑布局:

图 17.1 – 使用 Ansible 管理的基础设施的逻辑布局
生产级企业环境通常会包含一个 配置管理数据库 (CMDB),用于组织其 IT 基础设施资产。IT 基础设施资产的示例包括服务器、网络、服务和用户。虽然 CMDB 并不是 Ansible 架构的直接组成部分,但它描述了资产及其在受管基础设施中的关系,并且可以用于构建 Ansible 清单。
清单是 Ansible 控制节点上的本地存储——通常是 INI 或 YAML 文件——它描述了受管的 主机 或 主机组。清单可以从 CMDB 推断出来,也可以由系统管理员手动创建。
现在,让我们更详细地看看以下图示的高层次 Ansible 架构:

图 17.2 – Ansible 架构
上图展示了 Ansible 控制节点与私有或公共云基础设施中的受管主机之间的交互。以下是架构视图中所示区块的简要说明:
-
API 和核心框架:封装 Ansible 核心功能的主要库;Ansible 核心框架是用 Python 编写的
-
插件:扩展核心框架功能的附加库——例如,以下内容:
-
连接插件,如云连接器
-
测试插件,验证特定的响应数据
-
回调插件,用于响应事件
-
-
user模块用于管理用户 -
package模块用于管理软件包 -
清单:描述由 Ansible 命令和 Playbooks 目标的主机和主机组的 INI 或 YAML 文件* Playbooks:描述一组针对托管主机的任务的 Ansible 执行文件* 私有或公共云:托管在本地或各种云环境中的托管基础设施(例如 VMware、亚马逊网络服务(AWS)和 Azure)* 托管主机:由 Ansible 命令和 Playbooks 目标的服务器*
ansible、ansible-playbook、ansible-doc等* 用户:运行 Ansible 命令或 Playbooks 的管理员、高级用户和自动化用户进程
现在我们对 Ansible 架构有了基本的理解,接下来让我们看看是什么使得 Ansible 成为自动化管理工作流的一个优秀工具。接下来我们将介绍配置 管理的概念。
介绍配置管理
如果我们回顾过去,系统管理员通常管理的服务器数量相对较少,通过在每台主机上使用远程 shell 来运行日常管理任务。诸如复制文件、更新软件包和管理用户等相对简单的操作,可以轻松编写脚本并定期重复使用。随着应用程序和服务的激增,互联网的快速发展,现代本地和基于云的 IT 基础设施——支撑这些相关平台——已经显著增长。涉及的大量配置变更远远超过了一个管理员运行和维护少数脚本的能力。在这种情况下,配置管理应运而生。
在配置管理中,托管的主机和资产根据特定标准被分组为逻辑类别,如图 17.1所示。除了主机之外,管理其他资产最终还是要在托管这些资产的服务器上执行特定任务。配置管理清单是 Ansible 的清单文件,用于管理这些主机和资产。因此,Ansible 成为配置管理的端点。
使用 Ansible,我们可以运行单个一次性命令来执行特定任务,但通过 ansible-playbook 执行定期的维护和配置管理任务,可以实现更加高效的配置管理工作流,这在 IT 基础设施自动化中是一个常见的做法。我们将在本章稍后讨论 Ansible 的 Playbooks。
反复(或定期)对特定目标执行 Ansible 任务时,会引发由于重复操作导致的期望状态发生不必要变化的问题。这个问题引出了配置管理的一个核心方面——配置更改的幂等性。接下来,我们将看看什么是幂等性更改。
解释幂等操作
在配置管理中,操作是幂等的,当多次运行它时,结果与第一次运行时相同。从这个意义上讲,Ansible 是一个幂等的配置管理工具。
让我们解释一下幂等操作是如何工作的。假设我们有一个 Ansible 任务,用来创建一个用户。当任务第一次运行时,它会创建该用户。如果在第二次运行时用户已经被创建,那么这次操作将是一个空操作(no-op)。没有幂等性的话,后续运行相同任务时,由于尝试创建已存在的用户,将会产生错误。
我们应该注意到,Ansible 并不是市场上唯一的配置管理工具。还有Chef、Puppet 和 SaltStack 等平台。大多数这些平台已经被更大的企业收购,例如 SaltStack 被 VMware 收购,有些人可能会认为 Ansible 的成功归功于 Red Hat 将该项目开源。Ansible 似乎是当今最成功的配置管理平台。业界普遍认为,Ansible 提供了用户友好的体验、高可扩展性和企业级部署中负担得起的许可层次。
在介绍完基础概念后,让我们动手安装 Ansible,在选择的 Linux 平台上。
安装 Ansible
在本节中,我们将向你展示如何在控制节点上安装 Ansible。在 Linux 上,我们可以通过几种方式来安装 Ansible:
-
使用特定平台的软件包管理器(例如,Ubuntu/Debian 上的
apt) -
使用
pip,Python 包管理器
Ansible 社区推荐使用 pip 来安装 Ansible,因为它提供了 Ansible 的最新稳定版本。在本节中,我们将使用 Ubuntu 作为我们选择的发行版。对于所有主要操作系统平台的完整 Ansible 安装指南,请参阅在线文档:docs.ansible.com/ansible/latest/installation_guide/intro_installation.html。
在控制节点上,Ansible 需要 Python,因此在安装 Ansible 之前,我们需要确保系统中已安装 Python。
重要提示
Python 2 自 2020 年 1 月 1 日起不再受支持,请改用 Python 3.8(或更新版本)。
让我们首先在 Ubuntu 上安装 Ansible。
在 Ubuntu 上安装 Ansible
在 Ubuntu 22.04 LTS 中,默认安装了 Python 3。我们可以按照以下步骤继续安装 Ansible:
-
让我们首先通过以下命令检查 Python 3 的版本:
apt, we need to add the Ansible apt repository:sudo apt update
-
接下来,我们必须添加 Ansible PPA:
sudo apt install -y software-properties-common sudo apt-add-repository --update ppa:ansible/ansible -y -
现在,我们可以使用以下命令安装 Ansible 包:
sudo apt install ansible -y -
安装好 Ansible 后,我们可以检查其当前版本:
ansible --version在我们的例子中,前一条命令的相关输出摘录如下:
ansible [core 2.15.4]
接下来,我们将介绍如何使用 pip 安装 Ansible。
使用 pip 安装 Ansible
在使用 pip 安装 Ansible 之前,我们需要确保系统上已安装 Python。我们假设根据前一节提供的步骤,已经安装了 Python 3。在使用 pip 安装 Ansible 时,最好先卸载通过本地包管理器(如 apt)安装的任何版本的 Ansible。这将确保 pip 成功安装最新版本的 Ansible。继续安装之前,请按照以下步骤操作:
-
我们应当删除任何通过特定平台包管理器(例如
apt或yum)安装的现有版本的 Ansible。要卸载 Ubuntu 上的 Ansible,可以运行以下命令:pip is installed. The following command should provide the current version of pip:先使用 pip 安装器:
使用 pip 和 Ansible,执行以下命令:
python3 get-pip.py --user pip and Ansible for the current user (hence the --user option we used). -
如果你希望将 Ansible 全局安装在系统上,等效的命令如下:
sudo python3 get-pip.py sudo python3 -m pip install ansible -
安装完成后,你可能需要退出并重新登录终端,然后才能使用 Ansible。你可以使用以下命令检查安装的 Ansible 版本:
ansible --version在我们的例子中,输出显示如下:
ansible [core 2.15.4]
如你所见,通过使用 pip,你可以获得 Ansible 的最新版本(截至写作时)。因此,它是安装 Ansible 的推荐方法。
在我们的控制节点上安装了 Ansible 后,让我们看一些使用 Ansible 的实际示例。
使用 Ansible
在本节中,我们将广泛使用 Ansible CLI 工具来执行各种配置管理任务。为了展示实际操作示例,我们将使用自定义实验环境,并强烈建议你复制该环境以获得完整的配置管理体验。
本节的高层次大纲如下:
-
设置实验环境
-
配置 Ansible
-
使用 Ansible 临时命令
-
使用 Ansible playbook
-
使用 Jinja2 模板
-
使用 Ansible 角色
让我们从实验环境的概述开始。
设置实验环境
我们的实验环境使用 基于内核的虚拟机(KVM)作为虚拟环境的虚拟化管理程序,但任何其他虚拟化管理程序都可以使用。第十一章,与虚拟机的操作,详细描述了如何创建 Linux 虚拟机(VMs)。我们使用 Ubuntu Server LTS 部署了以下虚拟机,模拟现实世界中的配置管理基础设施:
-
neptune: Ansible 控制节点 -
ans-web1: Web 服务器 -
ans-web2: Web 服务器 -
ans-db1: 数据库服务器 -
ans-db2: 数据库服务器
所有虚拟机都已安装默认的服务器组件。在每个主机上,我们创建了一个名为packt的默认管理员用户,并启用了 SSH 访问。每台虚拟机将拥有 2 个 vCPU,2 GB 的 RAM,以及至少 20 GB 的磁盘空间。
现在,让我们简要描述这些虚拟机的设置,从托管主机开始。
设置托管主机
对于托管主机,完全启用从 Ansible 控制节点进行配置管理访问有几个关键要求:
-
它们必须安装并运行 OpenSSH 服务器
-
它们必须安装 Python
如技术要求部分所述,我们假设您的主机上已启用 OpenSSH。有关安装 Python 的信息,您可以按照安装 Ansible部分中描述的相关步骤进行操作。
重要提示
托管主机不需要在系统上安装 Ansible。
要设置每个虚拟机的主机名,可以运行以下命令(例如,对于ans-web1主机名):
sudo hostnamectl set-hostname ans-web1
我们还希望在托管主机上禁用sudo登录密码,以便在运行自动化脚本时促进无人值守的权限提升。如果不进行此更改,远程执行 Ansible 命令时将需要密码。
要禁用sudo登录密码,请使用以下命令编辑sudo配置文件:
sudo visudo
添加以下行并保存配置文件。如果用户名不同,请将packt替换为您的用户名:
packt ALL=(ALL) NOPASSWD:ALL
您需要在所有托管主机上进行此更改。
接下来,我们将查看 Ansible 控制节点的初始设置。
设置 Ansible 控制节点
Ansible neptune与ans-web1、ans-web2、ans-db1和ans-db2通过 Ansible 命令和剧本进行交互。为了方便起见,我们的示例将使用托管主机的主机名而不是 IP 地址。为了轻松实现这一点,我们在 Ansible 控制节点(neptune)的/etc/hosts文件中添加了以下条目:
127.0.0.1 neptune localhost
192.168.122.70 ans-web1
192.168.122.147 ans-web2
192.168.122.254 ans-db1
192.168.122.25 ans-db2
您需要根据虚拟机环境匹配主机名和 IP 地址。
接下来,我们必须在托管主机上安装 Ansible。使用本章早些时候描述的安装 Ansible部分中介绍的相关步骤,我们展示了如何在控制节点上安装 Ansible。在我们的案例中,我们按照使用 pip 安装 Ansible部分中的步骤操作,以便享受撰写时的最新 Ansible 版本。
最后,我们将设置 Ansible 控制节点与托管主机之间的 SSH 密钥认证。
设置 SSH 密钥认证
Ansible 使用 SSH 与托管主机通信。SSH 密钥认证机制使得远程 SSH 访问无需输入用户密码。要启用 SSH 密钥认证,请在 Ansible 控制主机(neptune)上运行以下命令。
使用以下命令生成一个安全的密钥对,并按照默认提示操作:
ssh-keygen
生成密钥对后,将相关的公钥复制到每个受管主机。你需要一次针对一个主机,并使用远程packt用户的密码进行身份验证。当提示时,接受 SSH 密钥交换:
ssh-copy-id -i ~/.ssh/id_rsa.pub packt@ans-web1
ssh-copy-id -i ~/.ssh/id_rsa.pub packt@ans-web2
ssh-copy-id -i ~/.ssh/id_rsa.pub packt@ans-db1
ssh-copy-id -i ~/.ssh/id_rsa.pub packt@ans-db2
现在,你应该能够从 Ansible 控制节点(neptune)通过 SSH 访问任何受管主机,而不需要输入密码。例如,要访问ans-web1,你可以用以下命令进行测试:
ssh packt@ans-web1
该命令将带你进入远程服务器(ans-web1)的终端。确保在继续执行下一步之前,回到 Ansible 控制节点(neptune)的终端。
我们现在准备好在控制节点上配置 Ansible。
配置 Ansible
本节将探讨与 Ansible 配置文件和清单相关的一些基本配置概念。通过使用配置文件及其内部的参数,我们可以改变 Ansible 的行为,例如特权升级、连接超时和默认清单文件路径。清单定义了受管主机,充当 Ansible 的 CMDB。
让我们首先看看 Ansible 配置文件。
创建 Ansible 配置文件
以下命令提供了一些关于我们的 Ansible 环境的有用信息,包括当前的配置文件:
ansible --version
以下是上述命令的完整输出:

图 17.3 – 默认的 Ansible 配置设置
默认的 Ansible 安装会将配置文件路径设置为/etc/ansible/ansible.cfg。正如你可能猜到的,默认的配置文件具有全局范围,这意味着当我们运行 Ansible 任务时,默认使用的是这个文件。
现在,让我们看看一些不同的场景以及如何解决这些问题:
-
如果在同一控制主机上有多个用户运行 Ansible 任务怎么办?我们的直觉告诉我们,每个用户可能会有自己的一套配置参数。Ansible 通过查找用户的主目录中的
~/.ansible.cfg文件来解决这个问题。让我们通过在用户(packt)的主目录中创建一个虚拟配置文件来验证这一行为:ansible --version command now yields the following config file path:

图 17.4 – 更改默认配置文件
换句话说,~/.ansible.cfg会优先于全局的/etc/ansible/ansible.cfg配置文件。
-
现在,假设我们的用户(
packt)创建了多个 Ansible 项目,其中一些管理本地主机,其他则与公共云资源交互。同样,我们可能需要一套不同的 Ansible 配置参数(例如连接超时和清单文件)。Ansible 通过在当前文件夹中查找./ansible.cfg文件来适应这种情况。让我们在新的
~/ansible/目录中创建一个虚拟的ansible.cfg文件:mkdir ~/ansible ~/ansible directory and invoking the ansible --version command shows the following config file:

图 17.5 – 更改当前目录和配置文件
我们本可以将项目目录命名为任何名字,不一定非得是/home/packt/ansible。Ansible 优先使用./ansible.cfg文件,而不是用户主目录中的~/.ansible.cfg配置文件。
-
最后,我们可能希望拥有一个不依赖于目录或来自 Ansible 命令的原始位置的配置文件的终极灵活性。这种功能在测试临时配置时非常有用,而不会更改主配置文件。为此,Ansible 会读取
ANSIBLE_CONFIG环境变量,以获取配置文件的路径。假设我们已经在
~/ansible项目文件夹中,并且已经定义了本地的ansible.cfg文件,现在让我们创建一个名为test.cfg的虚拟测试配置文件:cd ~/ansible touch test.cfg ANSIBLE_CONFIG=test.cfg ansible --version输出如下所示:

图 17.6 – 验证读取新配置文件
我们需要注意,配置文件应该始终具有.cfg扩展名,否则 Ansible 会忽略它。
下面是一个总结 Ansible 配置文件优先级顺序的列表,从高到低:
-
ANSIBLE_CONFIG环境变量 -
本地目录中的
./ansible.cfg文件 -
用户主目录中的
~/.ansible.cfg文件 -
/``etc/ansible/ansible.cfg
在我们的示例中,我们将依赖位于本地项目目录(~/ansible)中的ansible.cfg配置文件。现在让我们创建这个配置文件,并先将其留空:
mkdir ~/ansible
cd ~/ansible
touch ansible.cfg
在本章的其余部分,我们将从~/ansible文件夹运行我们的 Ansible 命令,除非我们另行指定。
除非我们在配置文件中明确定义(覆盖)配置参数,否则 Ansible 将假设使用系统默认值。我们将添加到配置文件中的属性之一是清单文件路径。但首先,我们需要创建一个清单。接下来的部分将向你展示如何操作。
创建 Ansible 清单
Ansible 清单是一个常规的INI或YAML文件,用于描述被管理的主机。它最简单的形式可能是一个平面列表,列出主机名或 IP 地址,但 Ansible 也可以将主机组织成组。Ansible 清单文件可以是静态的或动态的,具体取决于它们是手动创建和更新的,还是动态生成的。现在,我们将使用静态清单。
在我们的演示环境中,拥有两台 Web 服务器(ans-web1、ans-web2)和两台数据库服务器(ans-db1、ans-db2),我们可以在一个名为hosts的文件中定义以下清单(采用 INI 格式)(稍后在~/ansible中创建),如图 17.7所示:
[webservers]
ans-web1
ans-web2
[databases]
ans-db1
ans-db2
我们将主机分类为几个组,组名用括号表示;即 [webservers] 和 [databases]。如前所述,组是基于特定标准对主机的逻辑安排。主机可以属于多个组。组名是区分大小写的,应该始终以字母开头,并且不应包含连字符(-)或空格。
Ansible 有两个默认组:
-
all:清单中的每个主机 -
ungrouped:all中每个不是其他组成员的主机
我们还可以根据特定模式定义组。例如,以下组包括一系列主机名,主机名以 ans-web 开头,并以数字范围 1-2 结尾:
[webservers]
ans-web[1:2]
模式在我们管理大量主机时非常有用。例如,以下模式包括一个 IP 地址范围内的所有主机:
[all_servers]
172.16.191.[11:15]
范围定义为 [START:END],并包括从 START 到 END 的所有值。范围的示例有 [1:10]、[01:10] 和 [a-g]。
组也可以嵌套。换句话说,一个组可以包含其他组。这种嵌套通过 :children 后缀来描述。例如,我们可以定义一个 [platforms] 组,其中包含 [ubuntu] 和 [debian] 组(由于我们的所有虚拟机都在 Ubuntu 上运行,所以这只是为了解释目的):
[platforms:children]
ubuntu
debian
让我们将清单文件命名为 hosts。请注意,我们处在 ~/ansible 目录下。使用你选择的 Linux 编辑器,将以下内容添加到 hosts 文件中:

图 17.7 – INI 格式的清单文件
保存清单文件后,我们可以使用以下命令来验证它:
ansible-inventory -i ./hosts –-list --yaml
以下是命令参数的简要说明:
-
-i (--inventory):指定清单文件;即./hosts -
--list:列出当前清单,按 Ansible 读取的方式 -
--yaml:指定输出格式为 YAML
成功验证清单后,命令将显示等效的 YAML 输出(ansible-inventory 工具的默认输出格式是 JSON)。
到目前为止,我们已经使用 INI 格式表示了 Ansible 清单,但我们也可以使用 YAML 文件。以下屏幕截图显示了前面命令在 YAML 格式中的输出:

图 17.8 – YAML 格式清单输出
YAML 表示法可能有些挑战,尤其是在配置较大的情况下,因为它对缩进和格式有严格要求。在本章的其余部分,我们将继续使用 INI 格式的清单。
接下来,我们将指向我们的清单。编辑 ./ansible.cfg 配置文件,并添加以下行:

图 17.9 – 指向我们的清单文件
保存文件后,我们准备运行针对托管主机的 Ansible 命令或任务。我们可以通过两种方式执行 Ansible 配置管理任务:使用一次性 临时命令 或通过 Ansible 剧本(playbooks)。接下来我们将查看临时命令。
使用 Ansible 临时命令
临时命令执行一个单一的 Ansible 任务,并提供一种与我们管理的主机快速互动的方式。这些简单的操作在我们进行简单更改和执行测试时非常有用。
Ansible 临时命令的一般语法如下:
ansible [OPTIONS] -m MODULE -a ARGS PATTERN
上述命令使用 Ansible MODULE 在选择的主机上执行特定任务,基于 PATTERN 进行筛选。任务通过参数(ARGS)进行描述。你可能还记得,模块封装了特定的功能,如管理用户、软件包和服务。为了演示临时命令的使用,我们将使用一些最常见的 Ansible 模块来执行配置管理任务。我们从 Ansible ping 模块开始。
使用 ping 模块
最简单的临时命令之一是 Ansible ping 测试:
ansible -m ping all
该命令对所有托管主机执行一个快速测试,检查它们的 SSH 连接性并确保所需的 Python 模块已安装。以下是输出的摘录:

图 17.10 – 与托管主机的成功 ping 测试
输出表明命令执行成功(| SUCCESS),并且远程服务器响应我们的 ping 请求,返回了 "pong"("ping": "pong")。请注意,Ansible 的 ping 模块并不使用我们在排查网络问题时使用的 ping 命令,而只是一个需要 Python 的测试模块。
接下来,我们将使用 Ansible user 模块查看临时命令(ad hoc commands)。
使用用户模块
这是另一个临时命令的示例。这个命令检查特定用户(packt)是否存在于所有主机上:
ansible -m user -a "name=packt state=present" all
以下是成功检查时产生的输出摘录:

图 17.11 – 检查用户帐户是否存在
上述输出还表明,当检查用户帐户时,我们可以通过确保它们具有特定的用户和组 ID 来变得更加具体:
ansible -m user -a "name=packt state=present uid=1000 group=1000" all
我们可以将临时命令针对我们的库存的一个有限子集进行操作。例如,以下命令仅会对 web1 主机进行 Ansible 连接性 ping 测试:
ansible -m ping ans-web1
主机模式还可以包括通配符或组名。以下是一些示例:
ansible -m ping ans-web*
ansible -m ping webservers
接下来,我们来看一下可用的 Ansible 模块。在此之前,你可能想在 [defaults] 部分下将以下行添加到 ./ansible.cfg,以减少有关弃用模块的噪音:
deprecation_warnings = False
要列出 Ansible 中所有可用的模块,请运行以下命令:
ansible-doc --list
你可以搜索或使用grep命令查找特定模块的输出。要查看某个模块(例如user)的详细信息,你可以运行以下命令:
ansible-doc user
确保查看ansible-doc输出中的EXAMPLES部分,以获取特定模块的使用示例。你将看到如何在临时命令和剧本任务中使用该模块的实际例子。
此外,我们可以为不同的使用场景在 Ansible 主机上创建新用户。接下来,我们将向你展示如何创建一个新用户。
创建新用户
如果我们想在所有的 Web 服务器上创建一个新用户(webuser),可以通过以下临时命令执行相关操作:
ansible -bK -m user -a "name=webuser state=present" webservers
让我们解释一下命令的参数:
-
-b(--become):将执行上下文更改为sudo(root)。 -
-K(--ask-become-pass):提示输入远程主机上的sudo密码;所有受管主机使用相同的密码。 -
-m:指定 Ansible 模块(user) -
-a:指定user模块的参数为键值对;name=webuser表示用户名,而state=present在尝试创建用户之前检查该用户账户是否已存在。 -
webservers:操作目标的受管主机组
创建用户账户需要在远程主机上具有管理员(sudo)权限。使用-b(--become)选项会触发相关的权限提升,使 Ansible 命令以 sudoer 身份在远程系统上执行。
重要提示
默认情况下,Ansible 不启用sudo权限,你必须显式地设置-b(--become)标志。你也可以在 Ansible 配置文件中覆盖这一行为。
要默认启用无人值守的权限提升,请将以下行添加到ansible.cfg文件中:

图 17.12 – 添加权限提升规则
现在,使用临时命令时不再需要指定--b(--become)标志。
如果受管主机上的 sudoer 账户启用了sudo登录密码,我们需要将其提供给临时命令。此时,-K(--ask-become-pass)选项派上用场。结果,我们将看到以下提示要求输入密码:
BECOME password:
此密码在所有受命令影响的受管主机上使用。
如你所记得,我们已经在受管主机上禁用了sudo登录密码(见本章前面“设置实验环境”部分)。因此,我们可以重写之前的临时命令,而无需显式要求权限提升和相关密码:
ansible -m user -a "name=webuser state=present" webservers
关于权限提升存在一些安全隐患,Ansible 提供了机制来缓解相关风险。如需更多关于此主题的信息,可以参考docs.ansible.com/ansible/latest/user_guide/become.html。
上述命令将生成以下输出:

图 17.13 – 使用临时命令创建新用户
你可能注意到这里的输出文本被高亮显示,就像我们之前使用临时命令时一样。Ansible 会在输出文本表示管理主机的期望状态发生变化时进行高亮显示。如果你第二次运行相同的命令,输出将不会被高亮显示,表明自从用户账户已经创建后没有发生变化。在这里,我们可以看到 Ansible 的 幂等性操作。
使用前面的命令,我们创建了一个没有密码的用户,仅用于演示。如果我们想添加或修改密码怎么办呢?接下来,我们将向您展示如何操作。
添加或修改密码
浏览 user 模块文档(使用 ansible-doc user),我们可以在模块参数中使用密码字段,但 Ansible 只接受 passlib。我们可以通过以下命令在 Ansible 控制节点上安装它:
pip install passlib
您需要 Python 包管理器(pip)才能运行上述命令。如果您是通过 pip 安装的 Ansible,应该没有问题。否则,请按照 使用 pip 安装 Ansible 部分的说明下载并安装 pip。
安装 passlib 后,我们可以使用以下临时命令来创建或修改用户密码:
ansible webservers -m user \
-e "password=changeit!" \
-a "name=webuser \
update_password=always \
password={{ password | password_hash('sha512') }}"
以下是帮助设置用户密码的附加参数:
-
-e(--extra-vars): 以键值对的形式指定自定义变量;我们将自定义变量的值设置为password=changeit!。 -
update_password=always:如果密码与之前的不同,则更新密码。 -
password={{...}}:将密码设置为双大括号内表达式的值。 -
password | password_hash('sha512'):将password变量(changeit!)的值传递给password_hash()函数,从而生成 SHA-512 哈希值;password_hash()是我们之前安装的passlib模块的一部分。
该命令将 webuser 的密码设置为 changeit!,并演示了如何在临时命令中使用变量(password)。以下是相关输出:

图 17.14 – 使用临时命令更改用户密码
出于安全原因,Ansible 不会显示实际的密码。
现在,您可以尝试通过 webuser 账户 SSH 登录到任何一台 Web 服务器(web1 或 web2),并应能使用 changeit! 密码成功进行身份验证。
删除用户
要删除所有 Web 服务器上的 webuser 账户,我们可以运行以下临时命令:
ansible -m user -a "name=webuser state=absent remove=yes force=yes" webservers
state=absent 模块参数会触发删除 webuser 账户。remove 和 force 参数相当于 userdel -rf 命令,删除用户的主目录及其中的所有文件,即使这些文件不是该用户所有的。
相关输出如下:

图 17.15 – 使用临时命令删除用户账户
你可以安全地忽略输出中捕获的stderr和stderr_lines,因为这些信息无关紧要,因为用户之前并没有创建邮件队列。
接下来我们将查看package模块,并运行一些相关的临时命令。
使用package模块
以下命令安装webserver组:
ansible -m package -a "name=nginx state=present" webservers
以下是输出中的摘录:

图 17.16 – 在 Web 服务器上安装 nginx 软件包
我们使用类似的临时命令来安装databases组:
ansible -m package -a "name=mysql-server state=present" databases
以下是命令输出中的摘录:

图 17.17 – 在数据库服务器上安装 mysql-server 软件包
如果我们想要移除一个软件包,临时命令将类似,但会使用state=absent代替。
虽然package模块提供了一个跨平台的良好操作系统抽象,但某些包管理任务最好使用平台特定的包管理器来处理。接下来,我们将展示如何使用apt模块。
使用平台特定的包管理器
以下临时命令会在我们管理的环境中的所有 Ubuntu 机器上安装最新更新。由于所有的虚拟机都运行 Ubuntu,我们将仅在webservers组上运行这些命令。命令如下:
ansible -m apt -a "upgrade=dist update_cache=yes" ubuntu
如果我们愿意,我们可以在hosts文件中创建一个新的组,命名为[ubuntu],并将所有虚拟机添加到该组。如果我们有不同的操作系统,这样做会更方便,但对我们来说并非如此。
平台特定的包管理模块(如apt、yum等)具备与系统无关的package模块相同的所有功能,同时提供额外的操作系统专用功能。
接下来让我们看看service模块和几个相关的临时命令。
使用service模块
以下命令将在webservers组中的所有主机上重启nginx服务:
ansible -m service -a "name=nginx state=restarted" webservers
以下是输出中的相关摘录:

图 17.18 – 在 Web 服务器上重启 nginx 服务
同样,我们可以在所有数据库服务器上重启mysql服务,但有一个小技巧!在 Ubuntu 中,MySQL 服务的名称是mysql。当然,我们可以为每个主机指定适当的服务名称,但如果你有许多数据库服务器,这将是一项繁重的任务。另一种方法是,当目标是多个主机或组时,我们可以使用排除模式(用!表示)。
以下命令将在databases组中的所有主机上重启mysql服务,除了那些属于debian组的主机(如果我们有一个debian组的话):
ansible -m service -a "name=mysql state=restarted" 'databases:!debian'
类似地,我们可以通过以下临时命令重启 databases 组中所有主机上的 mysqld 服务,除了那些属于 ubuntu 组(如果我们有的话)的主机:
ansible -m service -a "name=mysqld state=restarted" 'databases:!ubuntu'
当你使用排除模式针对多个主机或组时,始终使用单引号('');否则,ansible 命令将会失败。
让我们来看最后一个 Ansible 模块及其相关的临时命令,它在升级场景中经常使用。
使用 reboot 模块
以下临时命令重启 webservers 组中的所有主机:
ansible -m reboot -a "reboot_timeout=3600" webservers
较慢的主机可能需要更长的时间来重启,尤其是在进行大规模升级时,因此重启超时时间增加到了 3600 秒。(默认超时时间为 600 秒。)
在我们的案例中,重启只用了几秒钟。输出如下:

图 17.19 – 重启 webservers 组
在本节中,我们展示了一些使用不同模块的临时命令示例。下一节将简要介绍一些最常用的 Ansible 模块以及如何进一步探索。
探索 Ansible 模块
Ansible 拥有一个庞大的模块库。正如我们之前提到的,您可以使用 ansible-doc --list 命令在命令行终端中浏览可用的 Ansible 模块。您还可以在线访问相同的信息,网址为 docs.ansible.com/ansible/2.9/modules/modules_by_category.html。
在线目录提供了按类别索引的模块,帮助你快速定位所需的模块。以下是一些在日常系统管理和配置管理任务中常用的典型模块:
-
apt:执行 APT 包管理 -
yum:执行 YUM 包管理 -
dnf:执行 DNF 包管理 -
users:管理用户*services:控制服务*reboot:重启机器*firewalld:执行防火墙管理*copy:将本地文件复制到托管主机*synchronize:使用rsync同步文件和目录*file:控制文件权限和属性*lineinfile:操作文本文件中的行*nmcli:控制网络设置*get_url:通过 HTTP、HTTPS 和 FTP 下载文件*uri:与 Web 服务和 API 端点进行交互*raw:通过 SSH 执行远程命令(这是一种不安全的做法);不需要在远程主机上安装 Python*command:使用 Python 的远程执行上下文安全地运行命令*shell:在托管主机上执行 shell 命令
我们应该注意,临时命令总是使用单一模块执行单一操作。这个特点是一个优势(用于快速变更),但也是一个限制。对于更复杂的配置管理任务,我们使用 Ansible 剧本。接下来的部分将带您了解编写和运行 Ansible 剧本的过程。
使用 Ansible 剧本
Ansible 剧本本质上是一个任务列表,这些任务会自动执行。Ansible 配置管理工作流主要由剧本驱动。更准确地说,剧本是一个包含一个或多个play的 YAML 文件,每个 play 都有一个任务列表,任务按列出的顺序执行。Play 是执行单元,它将在一组主机上运行相关任务,可以通过组标识符或模式进行选择。每个任务使用一个单一模块,执行针对远程主机的特定操作。你可以将任务视为一个简单的 Ansible 临时命令。由于大多数 Ansible 模块符合幂等执行环境,剧本也是幂等的。多次运行剧本总是会产生相同的结果。
编写良好的剧本可以将繁琐的管理任务和复杂的脚本替换为相对简单且易于维护的清单,从而运行易于重复且可预测的例程。
接下来我们将创建我们的第一个 Ansible 剧本。
创建一个简单的剧本。
我们将基于用于创建用户(webuser)的临时命令来构建剧本。快速回顾一下,命令如下:
ansible -m user -a "name=webuser state=present" webservers
在编写等效的剧本时,你可能会注意到一些与临时命令参数的相似之处。
在编辑剧本 YAML 文件时,请注意 YAML 格式规则。
-
仅使用空格字符进行缩进(禁止使用制表符)。
-
保持一致的缩进长度(例如,两个空格)。
-
层级结构中相同级别的项(例如,列表项)必须具有相同的缩进。
-
子项的缩进比父项多一个缩进级别。
现在,使用你喜欢的 Linux 编辑器,将以下行添加到 create-user.yml 文件中。确保将剧本创建在 ~/ansible 项目目录中,这里存放着我们的当前库存文件(hosts)和 Ansible 配置文件(ansible.cfg):

图 17.20 – 创建用户的简单剧本
让我们来看一下 create-user.yml 剧本中的每一行:
-
---:标记剧本文件的开始。 -
- name:描述 play 的名称;在一个剧本中我们可以有一个或多个 play。 -
hosts: webservers:指定目标主机为webservers组中的主机。 -
become: yes:为当前任务启用特权提升;如果你在 Ansible 配置文件中启用了无人值守特权提升(在[privileged_escalation]部分中设置become = True),则可以省略此行。 -
tasks:当前 play 中任务的列表。 -
- name:当前任务的名称;一个 play 中可以包含多个任务 -
user:当前任务所使用的模块 -
name: webuser:要创建的用户帐户的名称 -
state: present:创建用户时的期望状态——我们希望用户帐户在系统中存在
让我们运行 create-user.yml playbook:
ansible-playbook create-user.yml
这是我们在成功运行 playbook 后得到的输出:

图 17.21 – 运行 create-user.yml playbook
大多数 ansible-playbook 命令行选项与 ansible 命令类似。我们来看看其中的一些参数:
-
-i(--inventory):指定库存文件的路径 -
-b(--become):启用特权升级为sudo(root) -
-C(--check):进行干运行,不做任何更改并预测最终结果——这是验证 playbook 的有用选项 -
-l(--limit):将命令或 playbook 的作用限制为一部分被管理的主机 -
--syntax-check:验证 playbook 的语法而不进行任何更改;此选项仅适用于ansible-playbook命令
让我们尝试第二个 playbook,这次是用来删除用户的。我们将命名该 playbook 为 delete-user.yml 并添加以下内容:

图 17.22 – 删除用户的简单 playbook
现在,让我们运行这个 playbook:
ansible-playbook delete-user.yml
上述命令的输出如下:

图 17.23 – 将 delete-user.yml playbook 限制为 Ubuntu 主机组
接下来,我们将探索如何进一步简化我们的配置管理工作流,从在 playbook 中使用变量开始。
在 playbook 中使用变量
Ansible 提供了一个灵活多变的模型,用于在 playbook 和临时命令中处理变量。通过变量,我们本质上是在 参数化 一个 playbook,使其可重用或动态化。
以我们之前的 playbook 为例,创建一个用户。我们将用户名(webuser)硬编码到 playbook 中。如果我们想为另一个用户(例如 webadmin)创建帐户,我们无法重用这个 playbook,除非我们将相关任务添加到其中。但如果我们有很多用户,playbook 将按比例增长,导致维护变得更加困难。如果我们还想为每个用户指定密码呢?那么 playbook 的复杂性将会大大增加。
这就是 变量 发挥作用的地方。让我们首先了解变量是什么以及如何书写它们。
引入变量
我们可以用变量替代硬编码的值,使 playbook 变得动态。在伪代码的层面上,使用 playbook 创建具有特定 username 和 password 变量的用户的示例如下:
User = Playbook(username, password)
Ansible 中的变量用双括号括起来;例如,{{ username }}。让我们看看如何在剧本中利用变量。编辑我们在上一节中处理的 create-user.yml 剧本,并按以下方式调整:

图 17.24 – 在剧本中使用“username”变量
我们使用 {{ username }} 变量替代我们之前硬编码的值(webuser)。然后,我们将双括号用引号包裹起来,以避免与 YAML 字典表示法发生语法冲突。Ansible 中的变量名必须以字母开头,并且只能包含字母数字字符和下划线。
为变量设置值
接下来,我们将解释 如何 和 在哪里 设置变量值。Ansible 实现了一个层次化的模型来为变量分配值:
-
–extra-vars ansible-playbook命令行参数或./group_vars/all文件。 -
./group_vars目录中每个组命名的文件。 -
./host_vars目录中每个主机命名的文件。主机特定的变量也可以通过gather_facts指令获取。您可以在docs.ansible.com/ansible/latest/user_guide/playbooks_vars_facts.html#ansible-facts了解更多有关 Ansible 的信息。 -
在 play 或
include_vars任务中的vars指令。
在前面的编号列表中,变量值的优先级随着每个编号增加。换句话说,在 play 中定义的变量值将覆盖在主机、组或全局级别上指定的相同变量值。
例如,您可能还记得与 MySQL 服务名称在 Ubuntu 和 RHEL/Fedora 平台上的差异。Ubuntu 上的服务是 mysql,而 Fedora 上的服务是 mysqld。假设我们想要重新启动 databases 组中所有主机上的 MySQL 服务。假设我们的大部分数据库服务器运行的是 Ubuntu,我们可以在组级别定义一个 service 变量,如 service: mysql。我们在本地项目的 ./group_vars/databases 文件中设置该变量。然后,在我们控制服务状态的 play 中,当远程主机的操作系统平台为 Fedora 时,我们可以用 mysqld 覆盖 service 变量值。
让我们来看几个例子,来说明我们迄今为止关于变量放置和设置值的知识。在我们的 create-user.yml 剧本中,我们可以通过以下指令在剧本级别定义 username 变量:
vars:
username: webuser
这就是整个剧本的样子:

图 17.25 – 在剧本级别定义变量
使用以下命令运行我们的剧本:
ansible-playbook create-user.yml
输出的相关片段显示在下一个截图中:

图 17.26 – 使用变量创建用户的剧本
删除用户账户
要删除用户账户,我们可以重新调整之前的 delete-user.yml 文件,使其如下所示:

图 17.27 – 使用变量通过 playbook 删除用户
保存文件后,运行以下命令以删除所有 web 服务器上的 webuser 账户:
ansible-playbook delete-user.yml
上述命令运行后的相关输出如下:

图 17.28 – 使用变量通过 playbook 删除用户
优化我们的 playbook
我们可以进一步优化我们的 create-user 和 delete-user playbook。您可以按照以下步骤操作:
-
由于这个 play 只针对
webservers组,我们可以在./group_vars/webservers文件中定义一个username变量。这样,我们可以让 playbook 更加简洁。让我们从两个文件中移除变量定义。 -
接下来,在本地目录(
~/ansible)中创建一个./group_vars文件夹,并向名为webservers.yml的文件添加以下行:webservers so that it matches the group we’re targeting. However, we should prefer to use the .yml extension so that we’re consistent with the file’s YAML format. Ansible accepts both naming conventions. Here’s the current tree structure of our project directory:

图 17.29 – 包括 group_vars 文件夹的目录树
如果我们运行我们的 playbook,结果应该与之前的运行相同:
ansible-playbook create-user.yml
ansible-playbook delete-user.yml
-
现在,让我们在
create-userplaybook 中再添加一个变量:用户的password变量。您可能还记得我们为相同目的创建的临时命令。请参阅本章前面“使用 Ansible 临时命令”部分以了解更多信息。在
create-user.yml文件的user任务中,在与name相同的级别添加以下行:password: "{{ password | password_hash('sha512') }}" update_password: always您可能会注意到这些更改与相关的临时命令非常相似。更新后的 playbook 包含以下内容:

图 17.30 – 带有用户名和密码变量的 playbook
- 接下来,编辑
./group_vars/webservers.yml文件并添加password变量,值为changeit!。更新后的文件应包含以下内容:

图 17.31 – 在 webservers.yml 文件中添加新变量值
-
让我们运行 playbook:
webuser) and password (changeit!) by trying to SSH into one of the web servers (for example, ans-web1):在 web 服务器上使用以下命令删除 webuser 账户,恢复到初始状态:
create-user playbook to create a different user with a different password. Let’s name this user webadmin; we’ll set the password to changeme!. One way to accomplish this task is to use the -e (--extra-vars) option parameter with ansible-playbook:-e (--extra-vars) 选项参数接受一个 JSON 字符串,其中包含用户名和密码字段,以及相应的值。这些值将会 覆盖 在
./group_vars/webservers.yml文件中按组级别定义的相同变量值。 -
在继续执行下一步之前,让我们先移除
webuser和webadmin账户。让我们首先不带任何参数运行delete-userplaybook:webuser account. -
接下来,我们将使用
-e(--extra-vars) 选项参数来删除webadmin用户:ansible-playbook -e '{"username": "webadmin"}' delete-user.yml
使用 –extra-vars 结合我们的 create-user 和 delete-user playbook,我们可以通过手动运行 playbook 或在循环中运行 playbook 并提供包含所需变量的 JSON 数据来处理多个用户账户。虽然这种方法可以轻松地通过脚本化实现,Ansible 还提供了更多方式来改进我们的 playbook,利用循环任务的迭代功能。我们将在本章稍后探讨循环,首先,让我们使用 Ansible 的加密和解密功能更安全地管理我们的密码。
处理机密数据
Ansible 有一个专门用于管理机密信息的模块,叫做 Ansible Vault。使用 Ansible Vault,我们可以加密并存储敏感数据,例如在 playbook 中引用的变量和文件。Ansible Vault 本质上是一个受密码保护的安全键值数据存储。
要管理我们的机密数据,我们可以使用 ansible-vault 命令行工具。关于我们的 playbook,在创建用户并设置密码时,我们希望避免将密码以明文存储。目前它存储在 ./group_vars/webservers.yml 文件中。提醒一下,我们的 webservers.yml 文件包含以下内容:

图 17.32 – 存储在密码变量中的敏感数据
最后一行包含敏感数据;密码以明文显示。我们有几个选项可以用来保护我们的数据:
-
对
webservers.yml文件进行加密。如果我们选择加密webservers.yml文件,可能会带来加密非敏感数据(如用户名或其他通用信息)的额外开销。如果我们有很多用户,加密和解密非敏感数据会显得非常冗余。 -
仅加密
password变量。这对于单个用户来说完全可行,但随着用户数量的增长,我们将需要处理多个密码变量,每个变量都有自己的加密和解密。如果用户数量庞大,性能将再次成为问题。 -
将密码存储在一个单独的受保护文件中。理想情况下,我们应该有一个单独的文件来存储所有敏感数据。即使存储了多个密码,该文件也只会在运行 playbook 时解密一次。
我们将选择第三种方式,创建一个单独的文件来存储用户密码。
保护我们的数据
让我们看看需要遵循哪些步骤,以确保我们的数据安全:
- 我们将把文件命名为
passwords.yml(将其创建在~/ansible/目录中),并添加以下内容:

图 17.33 – 存储敏感数据的 passwords.yml 文件
-
我们添加了一个与
webuser用户名相关的 YAML 字典(或哈希)项,该项包含另一个作为键值对的字典:password: changeit!。其等效的 YAML 表示如下:webuser: { password: changeit! }这种方法将允许我们添加对应不同用户的密码,像这样:
webuser: { password: changeit! } webadmin: { password: changeme! }当我们在本节后面使用
password变量时,我们将解释这个数据结构背后的概念及其用途。 -
现在,由于我们将密码保存在一个不同的文件中,我们将从
webusers.yml中移除相应的条目。让我们使用comment变量添加其他与用户相关的信息。以下是我们的webusers.yml文件的样子:

图 17.34 – 存储非敏感用户数据的 webusers.yml 文件
-
接下来,让我们通过使用 Ansible Vault 对
passwords.yml文件进行加密来保护我们的机密:passwords.yml file with the following command:cat passwords.yml
The output for the preceding command shows the following:

图 17.35 – 加密后的 passwords.yml 文件
-
我们可以使用以下命令查看
passwords.yml文件的内容:ansible-vault view passwords.yml -
系统会提示您输入我们之前创建的 vault 密码。输出显示了与我们的受保护文件对应的简化 YAML 内容:

图 17.36 – 查看受保护文件的内容
-
如果您需要进行更改,可以使用以下命令编辑加密文件:
vi) to edit your changes. If you want to re-encrypt your protected file with a different password, you can run the following command:ansible-vault rekey passwords.yml
-
系统会提示您输入当前的 vault 密码,然后是新密码。
现在,让我们学习如何在 playbook 中引用机密。
在 playbook 中引用机密
要引用机密,请按照以下步骤操作:
- 首先,让我们确保可以从 vault 中读取密码。我们将创建一个新文件,内容如下。我们将其命名为
create-user-new.yml:

图 17.37 – 使用新 create-user-new.yml 文件调试 vault 访问
我们添加了一些任务:
-
include_vars(第 6 行-第 8 行): 从passwords.yml文件中读取变量 -
debug(第 10 行-第 12 行): 调试 playbook 并记录从 vault 读取的密码
这些任务都没有意识到 passwords.yml 文件是受保护的。第 12 行 是魔法发生的地方:
msg: "{{ vars[username]['password'] }}"
-
我们使用
vars[]字典在 playbook 中查询特定变量。vars[]是一个保留数据结构,用于存储所有通过vars和include_vars在 Ansible playbook 中创建的变量。我们可以根据username指定的键查询该字典:{{ vars[username] }}我们的 playbook 从
./group_vars/webservers.yml文件中获取username,其值为webuser。因此,vars[webuser]字典项从passwords.yml文件中读取相应条目:webuser: { password: changeit! } -
为了从相应的键值对中获取密码值,我们在
vars[username]字典中指定'password'键:{{ vars[username]['password'] }} -
让我们使用以下命令运行这个 playbook:
--ask-vault-pass option to let Ansible know that our playbook needs vault access. Without this option, we’ll get an error when running the playbook. Here’s the relevant output for our debug task:

图 17.38 – 剧本成功从保险库中读取机密
在这里,我们可以看到剧本成功地从保险库中检索到了密码。
- 让我们通过添加以下代码来完成
create-user-new.yml剧本:

图 17.39 – 使用从保险库中检索的密码创建用户的剧本
以下是当前实现的一些亮点:
-
我们添加了一个
vars块(password变量,位于剧本范围内)来从保险库中读取密码;我们在多个任务中重复使用了password变量。 -
include_vars任务(passwords.yml文件)。 -
debug任务(启用no_log: true(第 15 行),以避免在输出中记录敏感信息。当进行调试时,可以临时设置no_log: false。 -
user任务(password变量并哈希对应的值。出于安全原因,Ansibleuser模块需要这种哈希处理。我们还添加了一个comment字段,包含额外的用户信息。该字段映射到Linux 通用电气综合操作系统(GECOS)记录。有关详细信息,请参见 第四章 中的 用户和组管理 部分。
-
让我们使用以下命令运行剧本:
webuser record in /etc/passwd on the ans-web1 machine:tail -n 10 /etc/passwd
-
你应该在输出中看到以下行(你会注意到 GECOS 字段也被显示):
webuser:x:1001:1001:Regular web user:/home/webuser:/bin/sh
你可能希望在运行 ansible-playbook 命令时不提供保险库密码,因为 --ask-vault-pass 参数要求提供密码。这种功能在使用 Ansible Vault 时对于脚本或自动化工作流非常重要。为了在运行剧本时自动提供保险库密码,可以通过创建一个常规文本文件来实现,最好放在你的家目录中;例如,~/vault.pass。将保险库密码添加到此文件的一行中。然后,你可以选择以下任一选项来使用保险库密码文件:
-
创建以下环境变量:
ansible.cfg file’s [defaults] section:vault_password_file = ~/vault.pass
现在,你可以在没有 --ask-vault-pass 选项的情况下运行 create-user-new 剧本:
ansible-playbook create-user.yml
有时,使用单一保险库密码保护多个机密会引发安全问题。Ansible 通过保险库 ID 支持多个保险库密码。
使用保险库 ID
一个 passwords.yml 文件。假设我们希望使用保险库 ID 来保护这个文件。以下命令创建一个名为 passwords 的保险库 ID,并提示我们创建密码:
ansible-vault create --vault-id passwords@prompt passwords.yml
passwords 保险库 ID 保护 passwords.yml 文件。现在,假设我们还想保护一些与用户相关的 API 密钥。如果我们将这些机密存储在 apikeys.yml 文件中,以下命令将创建一个名为 apikeys 的相应保险库 ID:
ansible-vault create --vault-id apikeys@prompt apikeys.yml
在这里,我们创建了两个 Vault ID,每个 Vault ID 都有自己的密码,并保护不同的资源。
Vault ID 的好处如下:
-
它们提供了在管理机密时改进的安全上下文。如果某个 Vault ID 的密码被泄露,其他 Vault ID 保护的资源仍然是安全的。
-
通过 Vault ID,我们还可以利用不同的访问级别来管理 Vault 机密。例如,我们可以为相关的用户组定义
admin、dev和testVault ID。或者,我们可以有多个配置管理项目,每个项目都有自己的专用 Vault ID 和机密;例如,user-config、web-config和db-config。 -
您可以将一个 Vault ID 与多个机密关联。例如,以下命令创建了一个
user-configVault ID,用于保护passwords.yml和api-keys.yml文件:apikeys.yml file, which reads the corresponding vault ID password from the apikeys.pass file:将密码添加到剧本(create-users-new.yml)的命令如下:
ansible-playbook --vault-id passwords@passwords.pass create-users-new.yml
关于 Ansible Vault 的更多信息,您可以参考相关的在线文档:docs.ansible.com/ansible/latest/user_guide/vault.html。
到目前为止,我们已经创建了一个带密码的单用户帐户。如果我们想要添加多个用户,每个用户都有自己的密码,该怎么办呢?正如我们之前提到的,我们可以调用create-user剧本,并使用--extra-vars选项参数覆盖username和password变量。但这种方法效率不高,更别提维护的难度了。在接下来的部分,我们将向您展示如何在 Ansible 剧本中使用任务迭代。
使用循环
循环提供了一种在 Ansible 剧本中高效地重复执行任务的方法。Ansible 中有多种循环实现,我们可以根据它们的关键字或语法将其分类为以下几种:
-
loop:推荐的集合迭代方式 -
with_<lookup>:特定于集合的循环实现;例如,with_list、with_items和with_dict等
在本节中,我们将重点关注loop迭代(等同于with_list),它最适合用于简单的循环。让我们扩展之前的用例,并将其调整为创建多个用户。首先,我们通过快速比较带有循环和不带循环的重复任务来开始:
- 作为准备步骤,请确保
~/ansible是您的当前工作目录。此外,您可以删除./group_vars文件夹,因为我们不再使用它。现在,让我们创建两个剧本create-users1.yml和create-users2.yml,如下面的截图所示:

图 17.40 – 多任务与迭代任务的剧本
两个剧本都会创建三个用户:webuser、webadmin 和 webdev。create-users1剧本有三个不同的任务,每个任务负责创建一个用户。另一方面,create-users2实现了使用loop指令(第 15 行)的单一任务迭代:
loop: "{{ users }}"
循环遍历users列表中的项,这个列表被定义为第 6-9 行的play变量。user任务使用{{ item }}变量,在遍历列表时引用每个用户。
- 在运行这些剧本之前,我们还需要创建一个用于删除用户的剧本。我们将这个剧本命名为
delete-users2.yml,它的实现与create-users2.yml类似:

图 17.41 – 使用循环删除用户的剧本
-
现在,让我们运行
create-users1剧本,目标仅为ans-web1Web 服务器:ansible-playbook create-users1.yml --limit ans-web1在输出中,我们可以看到已经执行了三个任务,每个任务对应一个用户:

图 17.42 – 带有多个任务的create-users1剧本输出
-
让我们通过运行
delete-users2.yml剧本来删除用户:ansible-playbook delete-users2.yml --limit ans-web1输出如以下截图所示:

图 17.43 – 使用剧本删除用户
-
现在,让我们运行
create-users2剧本,再次仅目标为web1Web 服务器:ansible-playbook create-users2.yml --limit ans-web1这次输出展示了一个任务遍历所有用户:

图 17.44 – 带有单一任务迭代的create-users2剧本输出
两个剧本运行之间的差异非常明显:
-
第一个剧本为每个用户执行一个任务。虽然分叉任务并不是一个昂贵的操作,但可以想象,创建数百个用户会对 Ansible 运行时造成显著负担。
-
另一方面,第二个剧本运行一个单一任务,加载
user模块三次以创建每个用户。加载模块比运行任务消耗的资源要少得多。
有关循环的更多信息,您可以参考相关的在线文档:docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html。
现在我们已经知道如何实现一个简单的循环,我们将使我们的剧本更加简洁和可维护。
配置我们的剧本
除了优化我们的剧本外,我们还将尝试通过以可重用且安全的方式存储用户及其相关密码,来尽可能接近真实世界的场景。
我们将把 Web 用户的信息保存在users.yml文件中,相关的密码保存在users_passwords.yml文件中。以下是这两个文件及一些示例用户数据:

图 17.45 – users.yml 和 users_passwords.yml 文件
让我们更详细地看看这些文件:
-
users.yml文件包含一个包含单一键值对的字典:-
webusers -
username和comment元组
-
-
users_passwords.yml文件包含一个嵌套字典,其中包含多个键值对,如下所示:-
<username>(例如,webuser、webadmin等) -
password: <value>键值对
-
你可以使用 ansible-vault edit 命令更新 users_passwords.yml 文件,或者像我们一样创建一个新文件。或者,在你从头创建文件后,你必须按照之前在 处理秘密 部分描述的步骤对其进行加密。
新的 create-users.yml playbook 文件具有以下实现:

图 17.46 – 创建用户的 playbook
这些文件也可以在本书的 GitHub 仓库中找到,位于相关章节的文件夹中。让我们快速浏览一下 playbook 的实现。我们有三个任务:
-
Load users:从users.yml文件中读取 Web 用户信息,并将相关值存储在users字典中 -
Load passwords:从加密的passwords.yml文件中读取密码,并将相应的值存储在passwords字典中 -
Create user accounts:遍历users.webusers列表,对于每个项,使用相关参数创建一个用户账户;该任务根据item.username在passwords字典中执行密码查找
在运行 playbook 之前,使用以下命令加密 users_passwords.yml 文件:
ansible-vault encrypt users_passwords.yml
现在,使用以下命令运行 playbook:
ansible-playbook -–ask-vault-pass create-users.yml
这是输出结果:

图 17.47 – 运行 create-users.yml playbook
我们可以看到以下 playbook 任务在工作:
-
Gathering Facts:发现已管理主机和相关的系统变量(事实);我们将在本章稍后介绍 Ansible 事实 -
Load users:从users.yml文件中读取用户信息 -
Load passwords:从加密的passwords.yml文件中读取密码 -
Create user accounts:任务迭代循环创建用户
你可以使用之前在 处理秘密 部分介绍的方法来验证新用户账户。作为练习,使用与 create-users playbook 类似的实现来创建 delete-users.yml playbook。
现在,让我们看看如何改进我们的 playbook,并将其复用以在所有主机中无缝创建用户,包括 Web 服务器和数据库。我们将使用条件任务来实现这一功能。
运行条件任务
when 任务级别指令用于定义条件。
我们学习了变量以及如何在 playbook 中使用它们。Facts 和结果本质上是特定类型和用途的变量。接下来,我们将在条件任务的上下文中探讨这些变量。让我们从 facts 开始。
使用 Ansible facts
ansible_ 前缀。
下面是一些 Ansible facts 的示例:
-
ansible_distribution:操作系统发行版(例如,Ubuntu) -
ansible_all_ipv4_addresses:IPv4 地址 -
ansible_architecture:平台架构(例如,x86_64或i386) -
ansible_processor_cores:CPU 核心数 -
ansible_memfree_mb:可用内存(以 MB 为单位)
那么,如果我们没有明确为我们的主机创建分组,比如在 Ubuntu 和 Debian 系统中(或任何其他发行版)呢?在这种情况下,我们可以收集有关管理主机的 facts,检测它们的操作系统类型,并根据底层平台执行条件更新任务。让我们在 playbook 中使用 Ansible facts 实现这个功能。
我们将把我们的 playbook 命名为 install-updates.yml 并添加以下内容:

图 17.48 – install-updates.yml playbook
该 playbook 目标是所有主机,并有两个条件任务,基于 ansible_distribution fact:
-
Install Ubuntu system updates:基于ansible_distribution == "Ubuntu"条件,仅在 Ubuntu 主机上运行(第 9 行) -
Install Debian system updates:基于ansible_distribution == "Debian"条件,仅在 Debian 主机上运行(第 13 行)
让我们运行我们的 playbook:
ansible-playbook install-updates.yml
如果主机上有需要安装的更新,命令执行将花费相当长的时间。以下是相应的输出:

图 17.49 – 运行条件任务
在前面的输出中有三个任务:
-
Gathering Facts:默认的发现任务,由 playbook 执行,用于收集远程主机的信息 -
Install Ubuntu system updates:针对所有 Ubuntu 主机的条件任务 -
Install Debian system updates:针对所有 Ubuntu 主机跳过的条件任务,因为我们当前没有运行任何 Debian 主机
接下来,我们将查看如何在条件任务中使用 Ansible 的环境特定变量。
使用魔法变量
魔法变量 描述了本地 Ansible 环境及其相关的配置信息。以下是一些魔法变量的示例:
-
ansible_playhosts:当前 play 中活动主机的列表 -
group_names:当前主机所属的所有组的列表 -
vars:当前 play 中所有变量的字典 -
ansible_version:Ansible 版本
为了在使用条件任务时看到魔法变量的实际应用,我们将进一步改进create-users剧本,并在不同的主机组上创建特定的用户组。到目前为止,剧本仅在属于webservers组的主机(web1、web2)上创建用户。剧本在所有 Web 服务器上创建webuser、webadmin和webdev用户帐户。如果我们想在所有数据库服务器上创建类似的用户组——dbuser、dbadmin和dbdev呢?要实现这一点,可以按照以下步骤操作:
- 首先,将新用户帐户和密码分别添加到
users.yml和users_passwords.yml文件中。以下是添加数据库用户帐户和密码后的内容:

图 17.50 – users.yml 和 passwords.yml 文件
请注意,您可以使用ansible-vault edit命令编辑users_passwords.yml文件。或者,您也可以解密文件、编辑它,然后重新加密。
- 现在,让我们创建一个
create-users3剧本,包含所需的条件任务,以有选择性地处理两个组——webusers和databases。我们将创建一个名为create-users3.yml的新文件,内容如下:

图 17.51 – 带有条件任务的 create-users.yml 剧本
-
让我们运行这个剧本:
ansible-playbook -–ask-vault-pass create-users3.yml以下是输出的一个摘录,显示 Web 用户任务跳过数据库服务器,数据库用户任务跳过 Web 服务器,表明 Web 和数据库用户已经成功创建:

图 17.52 – Web 和数据库用户任务有选择性地运行
要查看 Ansible 的所有特殊变量列表,包括魔法变量,请访问docs.ansible.com/ansible/latest/reference_appendices/special_variables.html。有关事实和魔法变量的更多信息,请查看docs.ansible.com/ansible/latest/user_guide/playbooks_vars_facts.html中的在线文档。
接下来,我们将查看用于跟踪任务结果的变量,也称为注册变量。
使用注册变量
register指令用于将任务的输出捕获到变量中。使用注册变量的典型示例是收集任务结果以进行调试。在更复杂的工作流中,某些任务可能会根据先前任务的结果决定是否执行。
让我们考虑一个假设的使用案例。当我们新增用户并在所有服务器上创建不同帐户时,我们希望确保用户数量不会超过允许的最大数量。如果达到限制,我们可能会选择启动新服务器、重新分配用户等。你可以按照以下步骤进行操作:
- 让我们从创建一个名为
count-users.yml的剧本并输入以下内容开始:

图 17.53 – count-users.yml 剧本
我们在剧本中创建了以下任务:
-
Count all users:一个使用shell模块来统计所有用户的任务;我们通过捕获任务输出来注册count变量 -
Debug number of users:一个用于调试目的的简单任务,记录用户数量和最大允许数量 -
Detect limit:一个条件任务,在达到限制时运行;该任务检查count注册变量的值,并将其与max_allowed变量进行比较
第 17 行 在我们的剧本中需要进一步的解释。在这里,我们使用注册变量的实际标准输出,也就是 count.stdout。原始值是字符串类型,我们需要将其转换为整数,即 count.stdout | int。然后,我们将得到的数字与 max_allowed 进行比较。
-
让我们在只针对
ans-web1主机时运行剧本:ansible-playbook count-users.yml --limit ans-web1输出如下:

图 17.54 – 条件任务(检测限制)已执行
在这里,我们可以看到用户数量是 36,已超出最大限制 30。换句话说,Detect limit 任务按照预期执行。
-
现在,让我们编辑
count-users.yml剧本并做如下更改:max_allowed: 50 -
保存并重新运行剧本。这次,输出显示
Detect limit任务被跳过:

图 17.55 – 条件任务(检测限制)被跳过
要了解更多有关 Ansible 剧本中条件任务的信息,请访问docs.ansible.com/ansible/latest/user_guide/playbooks_conditionals.html。通过将条件任务与 Ansible 强大的事实和特殊变量结合使用,我们可以编写出功能强大的剧本并自动化各种系统管理操作。
在接下来的章节中,我们将探讨使我们的剧本更具可重用性和通用性的方法。接下来,我们将介绍动态配置模板。
使用 Jinja2 模板
最常见的配置管理任务之一是将文件复制到受管主机。Ansible 提供了 copy 模块来执行此类任务。一个典型的文件复制操作在 Ansible 剧本中的语法如下:
- copy:
src: motd
dest: /etc/motd
copy任务会将源文件(motd)复制到远程主机上的目标位置(/etc/motd)。虽然这种模型适用于将静态文件复制到多个主机,但它无法动态处理这些文件中的主机特定自定义内容。
以网络配置文件为例,该文件包含主机的 IP 地址。试图将此文件复制到所有主机上以配置相关的网络设置,可能会导致除了一个主机之外的所有主机都无法访问。理想情况下,网络配置文件应具有占位符来表示动态内容(例如,IP 地址),并根据目标主机适应文件内容。
为了实现这一功能,Ansible 提供了template模块,其template语法与copy非常相似:
- template:
src: motd.j2
dest: /etc/motd
在这种情况下,源是一个包含主机特定自定义内容的 Jinja2 模板文件(motd.j2)。在将文件复制到远程主机之前,Ansible 会读取 Jinja2 模板,并将动态内容替换为主机特定的数据。这个处理过程发生在 Ansible 控制节点上。
为了说明 Ansible 模板的一些好处和内部工作原理,我们将通过几个用例来创建每个用例的 Jinja2 模板。然后,我们将创建并运行相关的剧本,以展示模板的实际效果。
这里是我们将在本节中创建的两个模板:
-
motd): 用于向用户显示有关计划系统维护的定制消息 -
hosts): 用于在每个系统上生成自定义的/etc/hosts文件,其中包含其他管理主机的主机名记录
让我们从每日一条信息模板开始。
创建每日一条信息模板
在我们的介绍性说明中,我们以/etc/motd文件作为示例。在 Linux 系统中,当用户登录到终端时,系统会显示该文件的内容。假设你计划在星期四晚上升级你的 Web 服务器,并希望提醒用户即将发生的停机事件。你的motd信息可能是这样的:
This server will be down for maintenance on Thursday night.
这个消息没有什么特别的,motd文件可以通过一个简单的copy任务轻松部署。在大多数情况下,这样的消息可能就足够了,除非在某些特殊情况下,用户可能会对“这台服务器”具体是指哪一台感到困惑。你也可以考虑到,在美国的星期四晚上,世界另一端可能已经是星期五下午了,这样如果公告更具体一些会更好。
也许一个更好的信息会在ans-web1 Web 服务器上显示以下内容:
ans-web1 (172.16.191.12) will be down for maintenance on Thursday, April 8, 2021, between 2 - 3 AM (UTC-08:00).
在ans-web2 Web 服务器上,消息将反映相应的主机名和 IP 地址。理想情况下,模板应该在多个时区之间可复用,并且可以在全球分布的 Ansible 控制节点上运行剧本。让我们看看如何实现这样的模板(假设你当前的工作目录是~/ansible):
-
首先,在本地 Ansible 项目目录中创建一个
templates文件夹:./templates folder. -
使用您选择的 Linux 编辑器,在
./templates中创建一个名为motd.j2的文件,内容如下:

图 17.56 – motd.j2 模板文件
注意 Jinja2 语法中的一些特点:
-
注释被
{# ... #}包围 -
表达式由
{% ... %}包围 -
外部变量使用
{{ ... }}引用
下面是脚本的操作:
-
第 1 行-第 4 行 定义了一组初始本地变量,用于存储停机时间的边界:停机日期(
date)、起始时间(start_time)和结束时间(end_time)。 -
第 6 行 定义了我们用于起始时间和结束时间变量的输入日期时间格式(
fmt)。 -
第 7 行-第 8 行 构建与
start_time和end_time对应的datetime对象。这些 Pythondatetime对象根据我们在自定义消息中的需求进行格式化。 -
第 11 行 打印自定义消息,显示用户友好的时间输出和几个 Ansible 事实,具体包括显示消息的主机的
ansible_facts.fqdn和 IPv4 地址 (ansible_facts.default_ipv4.address)。
- 现在,让我们创建一个运行模板的 playbook。我们将命名该 playbook 为
update-motd.yml并添加以下内容:

图 17.57 – update-motd.yml playbook
template 模块读取并处理 motd.j2 文件,生成相关的动态内容,然后将文件以所需的权限复制到远程主机的 /etc/motd 中。
-
现在,我们准备好运行我们的 playbook:
ansible-playbook update-motd.yml命令应该会成功完成。以下是我们输出的截图:

图 17.58 – 运行 update-motd.yml playbook
-
您可以立即通过以下命令在任何主机(例如,
ans-web1)上验证motd消息:ans-web1 host and displays the content of the /etc/motd file:

图 17.59 – 远程 /etc/motd 文件的内容
-
我们也可以通过 SSH 连接到任何主机以验证
motd提示:ssh packt@ans-web1终端显示以下输出:

图 17.60 – 远程主机上的 motd 提示
- 现在我们知道如何编写和处理 Ansible 模板,接下来让我们改进
motd.j2,使其更加可重用。我们将通过替换硬编码的日期和时间本地变量为从 playbook 传递的输入变量来参数化模板。这样,我们可以在多个 playbook 中重用模板,并为维护提供不同的输入时间。以下是更新后的模板文件(motd.j2):

图 17.61 – 修改后的带有输入变量的 motd.j2 模板
相关的更改出现在第 1 行–第 2 行,我们使用date、start_time、end_time和utc输入变量构建datetime对象。注意本地变量 start_time_ 和 end_time_(后缀为_)与相应的输入变量 start_time 和 end_time 之间的区别。你可以选择任何符合 Ansible 规范的命名约定来命名这些变量。
- 现在,让我们来看一下我们修改过的 Playbook(
update-motd.yml):

图 17.62 - 修改过的update-motd.yml Playbook,带有变量
上述截图中突出了相关的变化,我们为motd.j2模板添加了变量作为输入。运行修改后的 Playbook 应与先前的实现产生相同的结果。相关的练习留给你自己去完成。
接下来,我们将看一下另一个基于模板的用例:通过模板更新所有服务器主机记录的/etc/hosts文件。
创建一个 hosts 文件模板
使用 Ansible 模板的另一个例子是通过使用 Jinja2 模板自动更新每台机器上的/etc/hosts文件。/etc/hosts文件包含了所有主机的数字 IP 地址和主机名,定期更新它是系统管理员的一项有用任务。我们将为hosts文件创建一个新的模板,并更新具体的 YAML 文件来访问新的模板文件。要创建hosts文件模板,请按照以下步骤操作:
- 首先,在
~/ansible/templates目录下创建一个新的模板文件,命名为hosts.j2。添加以下内容:

图 17.63 - hosts.j2模板文件
这是模板脚本的工作原理:
-
添加一个与当前主机对应的
localhost记录,引用 Ansible 特殊变量inventory_hostname。 -
在
groups['all']列表(特殊变量)中循环遍历所有主机。 -
检查当前循环中的主机是否与目标主机匹配,只有当主机不同时,才会执行下一步。
-
通过读取当前主机的默认 IPv4 地址(
default_ipv4.address),在相关的 Ansible facts(hostvars[host].ansible_facts)中添加新的主机记录。
- 现在,让我们创建一个
update-hosts.ymlPlaybook 文件,引用hosts.j2模板。添加以下内容:

图 17.64 - update-hosts.yml Playbook 文件
这个 Playbook 与update-motd.yml非常相似,它的目标是/etc/hosts文件。
-
当 Playbook 和模板文件准备好后,运行以下命令:
/etc/hosts file on any of the hosts (for example, ans-web1) by using the following command:ansible ans-web1 -a "cat /etc/hosts"
The output shows the expected host records:

图 17.65 - web1 上自动生成的/etc/hosts文件
-
你还可以通过 SSH 连接到其中一台主机(例如
ans-web1),然后通过主机名 ping 其他主机(例如ans-db2):ssh packt@ans-web1 ping response:

图 17.66 – 从一台主机到另一台主机的成功 ping 操作
这就结束了我们对 Ansible 模板的学习。然而,我们在本节中所涉及的内容仅仅触及了 Jinja2 模板强大功能和多样性的表面。我们强烈建议你探索相关的在线帮助资源,docs.ansible.com/ansible/latest/user_guide/playbooks_templating.html,以及本章结尾的进一步阅读部分提到的书目。
现在,我们将把注意力转向现代配置管理平台的另一个重要特性:为各种系统管理任务共享可重用和灵活的模块。Ansible 提供了一个高度易于访问和扩展的框架,以适应这一功能——Ansible 角色和Ansible Galaxy。在下一节中,我们将探讨自动化复用的角色。
创建 Ansible 角色
使用 Ansible 角色,你可以将自动化工作流打包成可重用的单元。角色本质上是一个包含 playbook 和其他资源的包,这些资源通过变量已被调整为特定配置。一个任意的 playbook 会通过提供所需的参数来调用一个角色,并像运行其他任务一样运行它。从功能上讲,角色封装了通用的配置管理行为,使它们可以在多个项目中重复使用,甚至与他人共享。
使用角色的主要好处如下:
-
封装功能提供了独立的打包形式,便于与他人共享。封装还支持关注点分离(SoC):多个 DevOps 和系统管理员可以并行开发角色。
-
角色可以使更大的自动化项目变得更易于管理。
在本节中,我们将描述创建角色的过程以及如何在示例 playbook 中使用它。在编写角色时,我们通常遵循以下步骤和实践:
-
创建或初始化角色目录结构。该目录以井然有序的方式包含角色所需的所有资源。
-
实现角色的内容。创建相关的 playbook、文件、模板等。
-
始终从简单的功能开始,逐步增加更高级的功能。在添加更多内容时,测试你的 playbook。
-
使你的实现尽可能通用。使用变量来暴露相关的自定义设置。
-
不要将机密信息存储在你的 playbook 或相关文件中。为它们提供输入参数。
-
创建一个简单的 playbook,运行你的角色作为一个虚拟 play。使用这个虚拟 playbook 来测试你的角色。
-
设计角色时要考虑用户体验。如果你认为它对社区有价值,可以让它更易用并与他人共享。
从高层次看,创建角色包括以下步骤:
-
初始化角色目录结构
-
编写角色内容
-
测试角色
我们将使用之前在使用 Ansible 剧本部分创建的create-users3.yml剧本作为创建角色的示例。我们将复制此文件并更名为create-users-role.yml,例如。在继续进行下一步之前,让我们在ansible.cfg文件(该文件位于~/ansible目录中)的[defaults]部分添加以下行:
roles_path = ~/ansible
这个配置参数设置了我们角色的默认位置。
现在,让我们开始初始化角色目录。
初始化角色目录结构
Ansible 对角色目录的文件夹结构有严格要求。目录必须与角色同名,例如create-users-role。我们可以手动创建此目录,也可以使用一个专门的命令行工具ansible-galaxy来管理角色。
要创建角色目录的骨架,请运行以下命令:
ansible-galaxy init create-users
命令完成后会显示以下消息:
- Role create-users was created successfully
您可以使用tree命令显示目录结构:
tree
您需要使用本地包管理器手动安装tree命令行工具。输出显示了我们角色的create-users-role目录结构:

图 17.67 – 创建用户角色目录
下面是对角色目录中每个文件夹和相应 YAML 文件的简要说明:
-
defaults/main.yml:角色的默认变量。它们在所有可用变量中优先级最低,可以被任何其他变量覆盖。 -
files:角色任务中引用的静态文件。 -
handlers/main.yml:角色使用的处理程序。处理程序是由其他任务触发的任务。您可以在docs.ansible.com/ansible/latest/user_guide/playbooks_handlers.html了解更多关于处理程序的信息。 -
README.md:解释角色的预期用途及如何使用它。 -
meta/main.yml:关于角色的附加信息,如作者、许可模型、平台以及对其他角色的依赖。 -
tasks/main.yml:角色执行的任务。 -
Templates:角色引用的模板文件。 -
tests/test.yml:用于测试角色的剧本。tests文件夹中也可能包含一个示例inventory文件。 -
vars/main.yml:角色内部使用的变量。这些变量具有较高的优先级,不应更改或覆盖。
现在我们已经熟悉了角色目录和相关的资源文件,让我们开始创建我们的第一个角色。
编写角色内容
从以前创建的剧本开始,并将其演化为角色是一种常见做法。我们将以create-users-role.yml剧本为基础。让我们重构这些文件,使它们更具通用性。
我们将在./create-users-role目录下创建两个新的文件,分别命名为users-role.yml和users_passwords-role.yml:

图 17.68 – users-role.yml 和 users_passwords-role.yml 文件
如你所见,我们重命名了示例用户帐户,并给它们赋予了更通用的名称。我们还将users-role.yml文件中的用户字典键名从webusers更改为list。记住,Ansible 要求在 YAML 文件中提供变量时,字典条目(键值对)需要是根级的。
让我们看看更新后的create-users-role.yml剧本:

图 17.69 – 修改后的 create-users-role.yml 文件
我们做了以下修改:
-
我们调整了
loop指令,将其从users.webusers改为users.list,因为users.yml文件中相关字典键名发生了变化。 -
我们重构了
include_vars文件引用,改为使用变量而非硬编码的文件名。 -
我们添加了一个
vars部分,其中users_file和passwords_file变量指向相应的 YAML 文件。
通过这些剧本中的更改,我们现在可以实施我们的角色。查看create-users-role角色目录,我们将执行以下操作:
-
将
create-users-role.yml中vars部分的变量复制/粘贴到defaults/main.yml中。 -
将
create-users-role.yml中的任务复制/粘贴到tasks/main.yml中。确保保持相对缩进。 -
创建一个简单的剧本,使用该角色。将
tests/test.yml文件用作你的测试剧本。将users-role.yml和users_passwords-role.yml复制/移动到tests/文件夹。
以下截图展示了所有这些更改:

图 17.70 – 我们在 create-users 角色目录中更改的文件
我们还建议更新create-users-role目录中的README.md文件,添加有关角色的目的和使用说明。你还应该提到需要有users-role.yml和users_passwords-role.yml文件,并包含相关的数据结构。这些文件的名称可以通过defaults/main.yml中的users_file和passwords_file变量进行更改。你还可以提供一些如何使用该角色的示例。我们还创建了一个额外的test2.yml剧本,使用一个任务来运行该角色:

图 17.71 – 使用任务运行角色
到此为止,我们已经完成了实现角色所需的更改。你可以选择删除 create-users-role 角色目录中所有空的或未使用的文件夹。
现在,让我们来测试我们的角色。
测试角色
为了测试我们的角色,我们将使用 tests/ 文件夹中的剧本,并通过以下命令运行它们:
ansible-playbook create-users/tests/test.yml
ansible-playbook create-users/tests/test2.yml
这两个命令应该都能成功完成。
这样,我们就提供了一个探索性视图,了解 Ansible 角色,它们是 Ansible 的一个强大功能,使现代系统管理员和 DevOps 能够从概念迅速过渡到实现,加速日常配置管理工作流的部署。
总结
在本章中,我们已经涵盖了 Ansible 的重要内容。由于本章范围有限,我们无法涵盖 Ansible 众多的功能。然而,我们尽力提供了平台的整体视图,从 Ansible 的架构原则到配置和使用临时命令以及剧本。你学习了如何设置 Ansible 环境,包含多个受管主机和一个控制节点,从而在高层次上模拟了一个真实的部署环境。你还熟悉了编写 Ansible 命令和脚本,用于典型的配置管理任务。本章中展示的大部分命令和剧本都与日常的管理操作非常相似。
无论你是系统管理员、DevOps 工程师,还是一位经验丰富的专业人士,或者正在朝着这个目标前进,我们希望本章能够为你在日常 Linux 管理任务和自动化工作流中带来新的启示。你在这里学到的工具和技术将为你脚本化和自动化日常管理任务中的更大部分工作打下良好的基础。
同样的结尾思考也适用于本书的整体内容。在学习和掌握一些最典型的 Linux 管理任务方面,无论是在本地环境还是云环境中,你已经走了很长一段路。
我们希望你在我们的旅程中获得了愉快的体验。
问题
让我们通过完成以下小测试来总结本章中学到的一些基本概念:
-
Ansible 中的幂等操作或命令是什么?
-
你想要设置与受管主机的免密码认证。你应该遵循哪些步骤?
-
检查与所有受管主机通信的临时命令是什么?
-
列举一些 Ansible 模块。试着想出一个配置管理场景,在其中你可以使用每个模块。
-
想象一个简单的剧本,它监控主机上可用的内存,并在内存超过给定阈值时通知你。
进一步阅读
这里有一些我们认为有助于深入了解 Ansible 内部结构的资源:
-
Ansible 文档:
docs.ansible.com/ -
Ansible 使用案例,来自 Red Hat:
www.ansible.com/use-cases -
深入了解 Ansible – 从初学者到专家 [视频],作者:James Spurin,Packt Publishing (
www.packtpub.com/product/dive-into-ansible-from-beginner-to-expert-in-ansible-video/9781801076937) -
实用 Ansible 2,作者:Daniel Oh,James Freeman,Fabio Alessandro Locati,Packt Publishing (
www.packtpub.com/product/practical-ansible-2/9781789807462)


浙公网安备 33010602011771号