Linux-系统管理员指南-全-

Linux 系统管理员指南(全)

原文:annas-archive.org/md5/8edad8c003f147cbae447e08488fc4e6

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

Linux 已成为世界上最广泛使用的操作系统之一,广泛应用于从服务器到智能手机等各种设备。凭借其强大的安全性、稳定性和灵活性,Linux 已成为许多系统管理员管理其组织 IT 基础设施时的首选平台。

作为系统管理员,你需要负责管理你所在组织 IT 基础设施的日常操作。这包括从搭建和配置服务器、维护网络连接,到在问题出现时进行故障排除。为了有效地完成这些任务,你需要对 Linux 及其各种工具和实用程序有扎实的理解。

在全书中,你将找到许多现实世界的示例和动手练习,帮助你建立实际技能,并增强管理 Linux 系统的信心。你还将了解管理大规模 Linux 环境的最新工具和技术。

我们希望这本书能作为你探索 Linux 系统管理世界的宝贵资源。无论你是刚开始学习,还是希望加深你的知识和技能,Linux 系统管理员指南都将为你提供成功所需的知识和工具。

本书适用对象

无论你是刚接触 Linux,还是已经使用它多年,本书都将提供一个关于操作系统、其工具及最佳实践的全面概述。本书涵盖了从基本的 Linux 概念到更高级的主题,如服务器虚拟化、网络配置和系统安全等内容。

本书涵盖的内容

第一章了解 Linux,概述了 Linux 操作系统的基本情况。它讲述了 Linux 的历史、特性,以及它与其他操作系统(如 Windows 和 macOS)之间的差异。本章的目标是为你提供 Linux 及其关键概念的基础知识,帮助你有效使用该操作系统。

第二章Shell 及其命令,提供了如何使用特定命令和选项的示例,同时还涉及了更高级的主题,如 Shell 脚本、管道与重定向以及使用正则表达式。本章的目标是为你提供 Shell 和基本命令的扎实理解,使你能够高效地从命令行操作和管理 Linux 或类似 Unix 的系统。

第三章Linux 文件系统,讲解了系统使用的文件系统的结构和组织。章节开始时介绍了文件系统的层次结构,包括root目录及其子目录,如/bin/etc/home/usr/var。还介绍了文件系统中不同类型的文件和目录,包括常规文件、目录、符号链接和特殊文件,如设备文件。章节还讨论了文件权限和所有权,以及如何使用chmodchown等命令来修改它们。

第四章进程与进程控制,开始时概述了进程及其属性,包括进程 ID、父进程 ID 和进程状态。接着介绍了如何使用pstopkill等工具查看和管理正在运行的进程。理解这些概念可以帮助系统管理员优化系统性能,并排除与进程管理相关的问题。

第五章硬件发现,概述了典型计算机系统中不同类型的硬件组件,包括处理器、内存、存储设备和输入/输出设备。还介绍了操作系统如何使用dmesglspcilsusb等工具来检测和识别这些组件。

第六章基本系统设置,讲解了影响系统行为和性能的基本系统设置的配置。对于需要根据特定需求定制系统的系统管理员和用户来说,本章节至关重要。

第七章用户与组管理,开始时概述了用户账户和组及其属性,包括用户 ID、组 ID、主目录和 Shell 设置。接着介绍了如何使用useraddusermodgroupaddgroupmod等工具来创建和管理用户账户与组。

第八章软件安装与软件包仓库,介绍了如何在系统上安装和管理软件包。此章节对于系统管理员和需要安装、升级和管理软件包的用户至关重要,以满足他们的需求。

第九章网络配置与故障排除,开始时概述了网络配置及系统上可用的网络工具,如ifconfigipnetstat。接着介绍了如何配置网络接口、分配 IP 地址和子网掩码,并配置网络路由。本章节对于需要建立和维护网络连接及服务的系统管理员和用户至关重要。

第十章存储管理,概述了存储设备和文件系统及其属性,如设备名称、设备 ID 和挂载点。然后介绍了如何使用fdiskmkfsmount等工具创建、管理和挂载文件系统。其他涵盖的主题包括管理存储设备,如磁盘分区和格式化,以及管理逻辑卷管理器LVM)卷。理解这些概念以及如何管理它们对于确保可靠和安全的存储基础设施至关重要。本章对于需要管理存储资源(如硬盘、固态硬盘和网络附加存储)的系统管理员和用户至关重要。

第十一章日志配置与远程日志,包括配置日志转发和聚合,设置集中式日志服务器,并分析系统日志。理解这些概念以及如何管理它们对于确保可靠和安全的系统日志基础设施至关重要。

第十二章集中式身份验证,首先概述了系统中可用的身份验证和授权机制,如本地密码文件、轻量目录访问协议LDAP)和 Active Directory。然后介绍了如何使用可插拔身份验证模块PAM)和名称服务切换NSS)等工具配置和管理用户身份验证。还包括了如何配置和管理系统上的用户身份验证和授权。本章对于需要在多个系统之间管理用户访问权限和特权的系统管理员至关重要。

第十三章高可用性,包括配置和管理集群资源,如 IP 地址、网络接口和共享存储设备;配置和管理集群服务,如 Web 服务器、数据库和邮件服务器;以及监控和排除集群操作中的故障。理解这些概念以及如何管理它们对于确保关键应用和服务的高可用性和可靠性至关重要。

第十四章使用 Chef 自动化,概述了基础设施自动化和配置管理的概念,如幂等性、声明式方法以及基础设施即代码IaC)范式。然后介绍了如何使用 Chef 来自动化配置和管理系统,包括节点、食谱、食谱集和资源。

第十五章安全指南与最佳实践,介绍了如何实施安全措施和最佳实践。

为了最大化地从本书中获益

要跟随本书中的内容,你只需要一台 Linux 虚拟机或一台 Linux 机器

使用的约定

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

文本中的代码:表示文本中的代码字、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟网址、用户输入和 Twitter 用户名。示例:“例如,Apache HTTP 服务器的配置文件目录在 Red Hat Linux 衍生版中是/etc/httpd,而在 Debian 衍生版中是/etc/apache2。”

代码块如下所示:

global_defs {
    notification_email {
        admin@example.com
        webmaster@example.com
    }
    notification_email_from keepalived@example.com
    smtp_server 203.0.113.100
    smtp_connect_timeout 30
}

任何命令行输入或输出均按如下方式书写:

$ echo '#!/bin/bash' >> hello.sh
$ echo 'echo "hello world"' >> hello.sh
$ chmod +x ./hello.sh
$ ./hello.sh
hello world

粗体:表示一个新术语、一个重要单词或您在屏幕上看到的单词。例如,菜单或对话框中的单词以粗体显示。示例:“另一个是Red Hat 软件包管理器RPM),它与 rpm 工具一起使用,并由 Red Hat 开发。”

提示或重要注意事项

显示如下。

联系我们

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

一般反馈:如果您对本书的任何内容有疑问,请通过电子邮件联系我们 customercare@packtpub.com,并在邮件主题中注明书名。

勘误表:虽然我们已尽最大努力确保内容的准确性,但错误难免发生。如果您在本书中发现了错误,我们将非常感激您向我们报告。请访问 www.packtpub.com/support/errata 并填写表格。

盗版:如果您在互联网上遇到我们作品的任何非法复制形式,请提供相关位置地址或网站名称。请通过 copyright@packtpub.com 联系我们,并提供材料的链接。

如果您有兴趣成为作者:如果您在某个领域拥有专业知识并有兴趣写书或为书籍贡献内容,请访问 authors.packtpub.com

分享您的想法

阅读完 Linux for System Administrators 后,我们很想听听您的想法!请 点击此处直达亚马逊评论页面 并分享您的反馈。

您的评论对我们以及技术社区非常重要,并将帮助我们确保提供高质量的内容。

下载本书的免费 PDF 副本

感谢您购买本书!

您是否喜欢随时随地阅读,但又无法随身携带纸质书籍?

您的电子书购买是否与您选择的设备不兼容?

不用担心,现在购买每本 Packt 书籍都可以免费获得该书的 DRM-free PDF 版本。

在任何地方、任何设备上阅读。您可以直接从您最喜欢的技术书籍中搜索、复制和粘贴代码到您的应用程序中。

特权不止这些,您还可以获得独家折扣、新闻通讯,并每日通过电子邮件接收精彩的免费内容。

按照以下简单步骤享受福利:

  1. 扫描二维码或访问以下链接

packt.link/free-ebook/9781803247946

  1. 提交你的购买凭证

  2. 就这些!我们将直接通过电子邮件发送你的免费 PDF 和其他福利

第一部分:Linux 基础

对于初学者系统管理员来说,最高优先级是了解操作系统的组成部分以及如何与其互动。在本书的第一部分,你将学习到 Linux 的简史、Linux 系统在实际中的应用,以及 Linux 内核与更广泛的开源软件生态系统之间的关系。在本部分结束时,你将能够使用基本命令来导航系统,管理文件、进程和硬件设备。

本部分包含以下章节:

  • 第一章, 了解 Linux

  • 第二章, Shell 及其命令

  • 第三章, Linux 文件系统

  • 第四章, 进程与进程控制

  • 第五章, 硬件发现

第一章:了解 Linux

Linux 是一系列基于相同内核的操作系统。由于它是一个由独立开发的系统组成的家族,具有不同的设计原则、目标和实现细节,因此了解是什么使这种情况成为可能以及这些系统是如何构建的非常重要。在本章中,我们将讨论 Linux 发行版的概念、开源软件许可,并了解 Linux 系统的使用情况。我们将涵盖以下主题:

  • Linux 系统结构

  • 开源软件许可证

  • Linux 在现代世界的应用

Linux 系统结构

Linux 及其多个发行版对于初学者来说通常显得复杂。为了澄清这一点,让我们来看看操作系统的结构和发展。

Linux 内核和基于 Linux 的操作系统

当人们说Linux时,他们可能指的是不同的事物。从狭义上讲,Linux 是一个操作系统内核,它由 Linus Torvalds 在 90 年代初期创建,现在由一个庞大的国际社区开发和维护。然而,当人们说他们正在使用 Linux 时,通常指的是一系列使用该内核的操作系统,并通常(但并不总是)包含由 GNU 项目创建的一组系统库和工具,这也是为什么有些人坚持认为这类系统应当称为 GNU/Linux 的原因。

注意

GNU 项目是一个自由软件项目,由理查德·斯托曼于 1983 年发起。其目标是创建一个完全由自由软件组成的类 Unix 操作系统。GNU代表GNU 不是 Unix,这反映了该项目旨在为专有 Unix 操作系统提供自由软件替代品的目标。

要完全理解这种不寻常的情况是如何可能的,让我们简要讨论一下操作系统的历史。

内核与用户空间

最早的计算机计算能力非常低,因此它们一次只能在内存中运行一个程序,并且该程序对硬件拥有完全的控制权。随着计算能力的提高,多个用户可以同时使用同一台计算机并运行多个程序——这种思想被称为时间共享或多任务处理。共享计算机会运行一个称为“监控程序”的程序,该程序负责将资源分配给终端用户程序。一系列的监控程序和系统工具被称为操作系统。最早的时间共享系统采用了合作式多任务处理,程序预计会自行将控制权转交给监控程序。然而,如果程序中出现编程错误,导致程序进入死循环或将数据写入错误的内存地址,那么该程序可能会导致整个计算机崩溃,或破坏另一个程序(包括监控程序)的内存。

为了使多任务处理更加可靠,新一代硬件引入了保护机制,允许监控程序重新控制 CPU,强制终止那些试图向属于其他程序或监控程序自身的内存写入内容的程序。

这使得操作系统内核与用户空间程序之间实现了分离。最终用户程序在物理上不再能够直接控制硬件,也不能访问未明确分配给它们的内存。这些特权保留给内核——它包括进程调度器(其作用与旧版监控程序相同)和设备驱动程序。

在单个程序内部,程序员可以自由地组织他们的代码。然而,当多个独立开发的组件需要协同工作时,它们之间需要有一个明确定义的接口。由于现在没有人直接编写机器代码,因此对于现代系统,这意味着两个接口:应用程序编程接口API)和应用程序二进制接口ABI)。API 是为编写源代码并定义可以调用的函数名和这些函数的参数列表的程序员准备的。经过编译后,这些函数调用被转换为可执行代码,将参数加载到内存中的正确位置,并将控制权转交给被调用的代码——如何加载这些参数以及如何转交控制权由 ABI 定义。

用户空间程序与库之间的接口受其编写所用编程语言的深刻影响。

相反,内核与用户空间程序之间的接口更类似于硬件接口。它们完全独立于编程语言,使用软件中断或专用的系统调用 CPU 指令,而不是应用程序开发者熟悉的函数调用。

注意

在 Linux 中,系统调用是一种机制,允许用户级进程请求内核提供服务,内核是操作系统的核心。这些服务可以访问硬件设备、管理进程和线程、分配内存,并执行其他需要特权访问的低级任务。

这些接口也非常低级:例如,如果你想使用write()系统调用将字符串打印到标准输出,你必须始终指定写入多少字节——它没有字符串变量的概念,也没有确定其长度的约定。

因此,操作系统包含了一个或多个编程语言的标准库,这些库为最终用户程序提供了一个抽象层和稳定的 API。

大多数操作系统将内核、编程语言的标准库和通常由一个小组人员紧密协作开发的基本系统工具一起版本化并分发。在这种情况下,内核接口通常被视为纯粹的内部内容,且不保证保持稳定。

Linux 内核与 GNU 项目

Linux 的独特之处在于,它是为了提供一个现有操作系统用户空间部分的替代内核而开发的。该项目的创始人 Linus Torvalds 最初是为了改进 MINIX 的功能而开发它——MINIX 是一个简化版的类 Unix 操作系统,旨在用于教学而非生产环境。此后,Linus 一直在使用来自 GNU 项目的 GNU C 编译器和用户空间程序——这是 Richard Stallman 启动的项目,目标是创建一个完整的类 Unix 操作系统,既自由(如自由)又开源,从而让每个人都能使用、改进和重新分发。

当时,GNU 项目已经拥有了操作系统的所有用户空间部分,但没有一个可用的内核。还有其他开源 Unix 项目,但它们源自 BSD Unix 代码库,在 90 年代初,它们成为了版权侵权诉讼的目标。Linux 内核恰好在这个时候应运而生,因为 Linus Torvalds 和各个贡献者完全独立地开发了它,并以与 GNU 项目软件相同的许可证发布——即 GNU 通用公共许可证GPL)。因此,一套 GNU 软件包加上 Linux 内核,成为了完全开源操作系统的潜在基础。

然而,Linus Torvalds 并不是 GNU 项目的成员,Linux 内核依然独立于 自由软件基金会FSF)——它仅使用了 FSF 为 GNU 项目开发的许可证,但任何其他人也可以使用,许多人确实使用了。

因此,为了使新的 Linux 内核版本能够与 GNU C 库及其依赖的相关软件一起使用,开发者必须保持内核接口的稳定性。

GNU C 库并不是为了与特定内核配合工作而开发的——当这个项目启动时,并没有一个可用的 GNU 内核,GNU 软件通常运行在其他类 Unix 操作系统上。

因此,Linux 和 GNU 软件可以并且仍然可以一起使用,并以不同的组合方式使用。GNU 用户空间软件集也可以与仍处于实验阶段的 GNU 硬内核一起使用,其他操作系统也将其作为系统或附加软件。例如,Apple macOS 长时间使用 GNU Bash 作为其系统 shell,直到它被 zsh 替代。

Linux 内核接口的稳定性保证使其成为定制操作系统的有吸引力基础,这些操作系统可能与 Unix 完全不同——其中一些操作系统仅在内核之上运行单个程序。人们还为不同的编程语言创建了可与 Linux 一起使用的替代标准库,如 C 语言的 Musl 和 Bionic,它们使用更宽松的许可证并促进静态链接。但要理解这些许可证的差异,我们需要讨论软件许可证的概念。

开源软件许可证

软件许可证是版权持有者与软件接收者之间的协议。现代版权法旨在赋予作者对其作品的使用和分发的完全控制权——版权自作品固定在任何媒介上之时自动产生,且未经作者明确许可,任何人不得使用或复制该作品。因此,许可协议是必需的,它授权用户一些默认情况下仅作者拥有的权限。作者可以自由指定任何条件,许多个人和公司利用这一点来限制用户的行为——例如,仅允许非商业使用。许可协议通常也是在作者或版权持有者与特定个人之间达成的。

然而,在 1980 年代后期,程序员和律师提出了利用作者对作品的无限控制来确保任何人都能使用、分发和修改它们,而不是加以限制的想法。他们引入了公共许可证,授予每个人权限,而不仅仅是那些签署或以其他方式接受协议的人,并写了若干可供任何人应用于其软件的可重用许可证。这一概念被称为版权反转(copyleft)——即与版权相反的做法。这些许可证被称为开源许可证,因为它们明确允许软件源代码的分发与修改。

所有的经典许可证都诞生于那个时期:MIT 许可证、BSD 许可证、GNU GPL 和 GNU 较小/库通用公共许可证LGPL)。这些许可证没有限制用户在其下分发的软件的使用权。若有条件,亦仅适用于可执行文件和源代码的分发与修改。

允许性许可证和版权反转许可证

在分发方面,两种不同的思想流派在分发条件上有所不同。

允许性许可证的支持者认为,软件的接收者必须拥有对软件的绝对自由,包括将其纳入其他非开源软件中,或创建闭源衍生物。MIT 许可证和 BSD 许可证是典型的允许性开源许可证例子。

反对专有软件的支持者认为,保护开源软件免受试图挪用其作者作品并创建闭源衍生品的行为非常重要。GNU GPL 就是最纯粹的例子——如果有人分发 GPL 下的程序可执行文件或与 GPL 库链接的程序,他们必须也在该许可证下分发程序的源代码。这是最激进的做法,称为强制性反版权

允许将库链接到其他许可证下的程序,但要求库代码的修改也必须遵守相同许可证的许可证被称为弱反版权许可证。最常用的例子是 GNU LGPL。

专利授权、TiVo 化和 SaaS 问题

GNU GPL 是为了应对没有源代码分发的专有软件的兴起而创建的,这种软件阻止了最终用户对其进行改进并分享他们的改进。然而,软件行业正在发展,新的趋势正在出现,一些人认为这些趋势威胁到了开源软件的存在或财务可持续性。

其中一个威胁是专利恶意诉讼——在存在软件专利的司法管辖区内,恶意使用软件专利。对此,一些较新的许可证和旧许可证的新版本(如 Apache 许可证和 GNU GPLv3)引入了专利授权条款。该条款禁止软件源代码的贡献者对其用户提出专利诉讼。如果他们提出这样的法律威胁,他们的许可证将被撤销。

GPLv3 的一个更具争议的点是它试图保护用户在其硬件上运行修改版本的权利。通过数字签名和类似机制防止硬件运行自定义软件的做法有时被称为TiVo 化,这个术语源自一个基于 Linux 的数字视频录像机 TiVo,它是早期此类锁定机制的例子。尽管有些项目支持通过这一条款来加以防范,但对其他项目而言,GPLv3 条款成为不从 GPLv2 转换的理由——Linux 内核便是那些保持使用旧版 GPL 的项目之一。

最后,所有经典许可证都是在所有软件都部署在本地的时代编写的,而在现代,许多软件通过网络交付,其可执行文件对最终用户不可访问——这种方法被称为软件即服务SaaS)。由于 GPL 规定每个二进制可执行文件的接收者有权接收其源代码,它并不适用于 SaaS,因为用户从未收到任何可执行文件。这使得供应商能够在 GPL 下创建软件的修改版本,而无需与社区分享他们的改进。为了应对这一趋势,开发了几个许可证,例如 Affero GPL。

在过去的几年里,提供开源软件托管版本的大型科技公司开始被视为削弱项目维护者通过服务赚钱的能力,因为与有效的垄断竞争价格非常困难。对此,一些项目开始转向有使用限制的许可证,许多人认为这些许可证不再是开源许可证,尽管源代码仍然可以获取。此类许可证的未来仍是一个悬而未决的问题。

Linux 发行版

由于开源许可证下的软件可以自由修改和分发,这使得组合包含内核、系统库和实用程序以及应用软件的完整操作系统成为可能。由于开源许可证对使用没有限制,用户不需要为每个组件接受许可协议。

在 Linux 的早期,设置一个可用的 Linux 环境是一个复杂而繁琐的过程。为了简化这个过程,Linux 爱好者开始准备第一个发行版—一组包和脚本,用于自动化安装。许多早期的发行版,如 Softlanding Linux System 和 Yggdrasil,现在已经停止维护,但一些仍在维护中——Slackware Linux 就是一个显著的例子。

包管理器和软件包仓库

早期的发行版目标相对简单,即提供一个可以安装应用软件的基本系统。然而,后来的发行版开始重新思考软件安装的过程。随着开源软件项目数量的增加,以及光驱和互联网连接变得更加经济实惠,包含更多软件的发行版变得比以往更加可行。

然而,许多应用程序依赖于共享库或其他应用程序。传统上,安装包会包含所有依赖项,或者将依赖项管理交给用户。由于发行版由一个维护小组管理,开发人员提出了一个想法:在所有需要依赖的包之间共享依赖项,并在用户请求安装包时自动安装所有依赖项。这催生了包管理器和软件包仓库——一组以特殊格式存储的文件,包括编译后的二进制文件和元数据,如软件包版本及其依赖关系。

两种最流行的软件包格式及其相应的包管理器是在 90 年代中期开发的,并且至今仍在使用。其中一种是 DEB 格式,配合 dpkg 工具使用,由 Debian 开发。另一种是Red Hat 包管理器RPM),与 rpm 工具一起使用,由 Red Hat 开发。

dpkg 和 rpm 工具负责在本地机器上安装包文件。要安装一个包,用户需要获取该包本身以及它所依赖的所有包。为了自动化这个过程,发行版开发了高级包管理器,可以自动从在线仓库下载包,检查更新,搜索元数据等。这些高级包管理器通常依赖低级包管理器来管理安装。Debian 的高级包装工具APT)通常与 DEB 包一起使用,尽管技术上它也可以与 RPM 一起使用。主要使用 RPM 格式的高级包管理器更多:由 Red Hat 维护的 YUM 和 DNF,来自 openSUSE 的 zypper,以及为现已停用的 Mandrake Linux 开发的 urpmi,仍然被它的分支使用。

目前存在的许多发行版自 90 年代以来一直在积极维护,或者是从不同时间点分叉出来的。例如,Ubuntu Linux 在 2000 年代初期从 Debian GNU/Linux 分叉出来,而 Rocky Linux 是一个从 2021 年开始的 Red Hat Enterprise Linux 衍生版。

然而,完全独立的发行版偶尔也会出现。它们中的一些是特定用途的系统,具有经典通用发行版无法满足的需求。例如,OpenWrt 是一个基于 Linux 的系统,用于消费者路由器,最初是为 Linksys WRT-54G 设备开发的,因此得名。这类设备通常只有几兆字节的闪存空间,因此为它们设计的操作系统必须非常紧凑,而且还必须使用诸如 JFFS 等专门为 NAND 闪存驱动器设计的文件系统。

其他独立的发行版尝试不同的包管理和安装原则。例如,NixOS 和 GNU Guix 采用一种方法,允许用户在新包版本出现问题时恢复系统更新。

本书将重点介绍基于 Debian/Ubuntu 和 Red Hat 的系统,因为它们长时间以来一直是最受欢迎的发行版,并且仍然保持受欢迎。

发行版之间的差异

发行版之间的差异不仅仅体现在包管理器上。配置文件的位置可能不同,相同包的默认配置也可能有很大差异。例如,Apache HTTP 服务器的配置文件目录在 Red Hat Linux 衍生版上是/etc/httpd,而在 Debian 衍生版上是/etc/apache2

一些发行版也使用高级配置工具,你可能需要考虑它们。

软件的选择及其安装的便捷性也可能有所不同。Debian、Fedora 和许多其他发行版将桌面环境的选择交给用户,并且可以轻松地在同一系统上安装多个不同的桌面环境,这样你就可以在不同的登录会话中切换 GNOME3、KDE、MATE 或其他任何环境。相比之下,Ubuntu 系列发行版包括多个不同桌面环境的版本,并且期望如果你不喜欢其默认的选择(Unity 桌面环境),你应该使用 Kubuntu(KDE 桌面环境),而不是默认的 Ubuntu。最后,一些发行版带有自定义桌面环境,并且不支持其他桌面环境,例如 elementary OS。

然而,经验丰富的 Linux 用户通常可以找到任何发行版的使用方法。

Linux 在现代世界的使用

Linux 内核的开源特性及其对多种硬件架构的支持,使其成为定制操作系统的热门选择,而通用 Linux 发行版也在以往使用专有 Unix 系统的每个领域中找到了广泛的应用。

世界上最流行的基于 Linux 的操作系统是 Android。虽然大多数 Android 应用程序是为定制的运行时编写的,并且从未直接使用 Linux 内核的任何功能,但它仍然是一个 Linux 发行版。

网络设备通常通过 Web 图形用户界面或自定义命令行接口进行管理,但它们仍然经常在背后运行 Linux。这适用于消费者级的 Wi-Fi 路由器,也适用于高性能的企业和数据中心路由器及交换机。

通用 Linux 发行版也随处可见。得益于内建的虚拟机支持(通过 Xen 或 KVM 虚拟机监控器),Linux 驱动着最大的云计算平台,包括 Amazon EC2、Google Cloud Platform 和 DigitalOcean。许多来宾系统也是运行 Web 服务器、数据库系统以及其他许多应用程序的 Linux 机器。

Linux 还广泛应用于高性能计算:目前世界上最强大的超级计算机都在其控制节点和 I/O 节点上运行 Linux。

最后但同样重要的是,本章的作者在 Linux 桌面上敲下了这些文字。

总结

Linux 发行版是一个完整的操作系统,包括 Linux 内核以及由各种个人和公司开发的一组库和程序。Linux 内核和核心系统库并非由同一组开发人员开发,而是 Linux 内核提供了一个稳定的 ABI,允许任何人开发标准库,使编程语言能够在其之上运行。

开源许可证有不同的条件,但它们都允许任何人出于任何目的使用软件并分发其副本,这也是 Linux 发行版存在的前提。

不同的发行版使用不同的包管理和配置方法,但有经验的用户如果掌握了基础知识,可以很快学会如何使用新的发行版。如今,Linux 无处不在,从手机到最强大的超级计算机都有它的身影。

在下一章,我们将学习 Linux 系统上可用的各种 shell,以及基本命令。

第二章:Shell 及其命令

我们将在 shell 中做很多事情,比如安装软件包、创建新用户、创建目录、修改文件权限等。这些将是基础内容,但也是你首次与 shell 交互的机会,帮助你理解背后的工作原理并增加信心。为了提高我们在 shell 中的效率,我们将专门为它开设一章。

本章将涵盖以下主题:

  • 对 shell 的基本定义,以便理解其工作原理,包括对其功能的概述和对最常见 shell 的描述

  • 如何使用基本命令以熟悉 Linux(本章将使用 CentOS 8 版本)

  • 使用命令改变文件和目录所有权的基本概念

什么是 shell?

被称为shell的计算机软件使操作系统的服务可供用户或其他程序访问。

shell 是一个程序,接收命令并将其发送到操作系统进行处理,简单来说就是这样。在交互式会话中,用户可以选择从键盘输入命令,或者可以将命令写入可以重复使用的 shell 脚本。在过去的 Unix 类型系统(如 Linux)中,它是唯一可访问的用户界面UI)。如今,除了命令行界面CLI)如 shell 外,我们还有图形用户界面GUI)。

shell 的基本功能是启动系统中已经安装的命令行程序。它们还提供内建命令和脚本控制结构,如条件语句和循环。每个 shell 都有自己的实现方式。有些 shell 仍然支持 Bourne shell,这是由程序员 Steve Bourne 为早期 Unix 系统创建的原始 shell 之一,后来在csh/tcshzshfish等 shell 中得到了标准化,并故意使用不同的语法。

为了使用命令行 shell,用户必须了解命令、其调用语法以及 shell 特定脚本语言的基础知识。

Linux 用户可以使用多种 shell,包括以下几种:

  • sh:一种 POSIX 兼容的 Bourne shell。在现代的发行版中,它通常只是以兼容模式运行的Bourne Again ShellBash)。

  • csh/tcsh:这些来自伯克利软件分发BSD)Unix 系统家族,但也可在 Linux 上使用;它们的脚本语法类似于 C 语言,并与 Bourne shell 不兼容。

  • ksh:一种 Bourne shell 的衍生版本,曾经非常流行。

  • bash:Bash 是最常见的 Linux shell,由 GNU 项目创建。

  • zshfish:这些是高度可定制且功能丰富的 shell,与sh衍生版本故意不同,需要学习,但有着庞大的爱好者社区。

它们都具有相似的特性,但每个都有自己独特的属性。

本书中,我们将假设你正在使用 Bash,因为它是大多数 Linux 发行版的默认 Shell。

Unix Shell 和 Bash 命令语言都是由 Brian Fox 为 GNU 项目开发的。它们旨在作为 Bourne Shell 的自由软件替代品。自 1989 年推出以来,它一直是大多数 Linux 发行版的默认登录 Shell。Linus Torvalds 将 Bash 和GNU 编译器集合GCC)移植到 Linux,作为最初的应用之一。

Bash 具有以下特点:

  • Shell 会先检查命令是否是内建命令,如果不是,它会通过搜索一系列目录来查找程序。这个集合被称为搜索路径。通过在 Bash 中运行echo $PATH命令,你可以查看它。除了当前目录外,home目录及其子目录也包含在搜索路径中。你可以创建自己的程序,并通过输入其名称来调用它们。无论你在哪个目录中,只要程序存储在bin目录中,它都会被找到并启动。我们将在第三章中进一步了解 Linux 目录结构,Linux 文件系统

  • 与其他 Linux 程序一样,Shell 有一个当前目录与之关联。在查找文件时,基于 Linux 的程序从当前目录开始。要将当前目录移动到 Linux 文件系统中的另一个位置,请使用cd命令。当前工作目录通常可以在现代 Shell 的命令提示符中看到。要检查 Shell 的版本,运行echo $SHELL命令。你将看到类似于/bin/bash的输出。

  • 通过指定命令来执行命令。大多数 Linux 命令实际上就是 Shell 运行的程序。例如,以下ls命令扫描当前目录并列出文件名:ls -la

  • 命令通常包含参数字符串,例如文件名。例如,以下命令会切换到你home目录中的tmp目录。Shell 将波浪号字符解释为你的home目录:

    cd ~/tmp
    
  • 某些命令需要多个参数。例如,复制命令需要两个参数:要复制的文件和目标位置。以下示范了如何将file1复制到新文件file2

    cp file1 file2
    
  • 某些命令的标志或选项参数通常以-开头。标志会改变被调用应用程序的行为。当使用以下命令时,ls会按创建时间输出文件的详细列表:

    ls -lt
    
  • 通配符将由 Shell 扩展以匹配当前目录中的文件名。例如,要显示名为anything.sh的文件列表,可以输入以下命令:

    ls -l *.sh
    
  • catconcatenate的缩写。运行以下命令将显示一个或多个文件的内容,而无需打开它们进行编辑:

    cat /etc/passwd
    
  • Shell 具有将一个程序的输出数据通过管道传输到另一个程序输入的能力。|是管道符号。为了计算testfile.txt中的单词数量,我们可以将文件连接并将输出传递给wc程序,如下所示:

    cat testfile.txt | wc -w
    
    1198
    

或者,要计算testfile.txt文件中的行数,我们可以使用以下命令:

cat testfile.txt | wc -l
289
  • 你可以为你常用或难以输入的命令或命令组创建别名。例如,我们可以使用top10别名来查找当前目录中的前 10 个文件。head将仅显示前几行。别名是命令的快捷方式——例如,避免记住一个非常长的命令,你可以创建一个容易记住的别名。以下是一个示例:

    alias top10="du -hsx * | sort -rh | head -10"
    
  • 一些变量是预定义的,例如$HOME,它是你的home目录。要查看分配的变量列表,请输入以下命令:

    set
    
  • 手册man)页就像一本包含每个命令的说明和描述的手册。运行以下命令查看 Bash 的手册页:

    bash-3.2$ man bash
    
  • 可以编写 Shell 命令的脚本。这些脚本可以像已编译的程序一样被调用(即,只需命名它们)。例如,我们首先在/bin目录下创建一个包含以下内容的文件,以构建一个名为top10.sh的脚本,显示当前目录中最大的前 10 个文件:

    #! /bin/bash
    
    du -hsx * | sort -rh | head -10
    
  • 接下来,我们必须使用chmod命令使文件具有可执行权限,才能正常运行它:

    chmod +x ~/bin/top10.sh
    
    ./top10.sh
    

查看bash的手册页获取更多详细信息(键入man bash)。

在 Bash 的额外机制中,键盘上的上箭头键使你能够访问和修改过去的命令。当你按下上箭头键时,最近的命令会再次显示在终端上。要访问之前的命令,请再次按下上箭头键。按Enter键重新运行该命令。使用Delete键从命令末尾删除字符,或使用退格键移动光标并通过插入或删除字符来修改命令的内容。

通过使用history命令,你可以查看命令历史记录。

你可以通过按!和行号重新执行任何历史命令,例如,!345

现在你已经知道如何与 Shell 交互以及输入这些命令时发生了什么,在接下来的章节中,我们将尝试实践一些基本命令,让你在与终端交互时更加自信。

基本的 Shell 命令

下面是一些可能的命令概述。更多信息请查看每个命令的手册页。通过使用man命令,你可以在线查看这些内容。只需键入man,然后输入你希望查看的命令名称(例如,如果你想了解更多关于cat命令的信息,只需键入man cat):

  • pwd: pwd 命令可以用来确定你当前所在的目录。其名称是 / 的缩写。你可以在以下屏幕截图中看到 pwd 命令的使用:

图 2.1 – pwd 命令,显示工作目录

图 2.1 – pwd 命令,显示工作目录

  • mkdir: mkdir 是用于创建新目录的命令。你可以在命令行中输入 mkdir packt 来创建一个名为 packt 的目录。要列出已创建的目录,可以使用 ls –ld <directory_name> 命令。你可以在这里看到 mkdir 命令的使用:

图 2.2 – mkdir 命令

图 2.2 – mkdir 命令

  • rmdir: 要删除一个目录,使用 rmdir。不过,rmdir 只能用于删除空目录。要删除文件和目录,使用 rm -rf directoryname/(其中 –rf 将递归删除目录内的所有文件和子目录)。要检查目录是否已被删除,可以使用 ls –ld <directory_name> 命令。这里显示了 rmdir 命令的使用:

图 2.3 – rmdir 命令

图 2.3 – rmdir 命令

  • touch: 该命令最初的目的是将文件的修改日期设置为当前时间。但由于它在文件不存在时会创建一个文件,因此它常用于创建空文件。以下是一个示例:

    touch filename.txt
    
  • ls: 使用 ls 命令可以查看当前目录中的所有文件和目录。如果你想查看隐藏文件,可以使用 ls -a 命令。通过使用 ls -la 命令,你可以将所有文件和目录以列表的形式查看,如下所示:

图 2.4 – ls 命令

图 2.4 – ls 命令

  • cp: 要从命令行复制文件,使用 cp 命令。它需要两个参数:第一个指定要复制的文件的位置,第二个指定复制到的位置。可以是一个新文件夹或新文件(如果需要文件副本)。你可以在这里看到 cp 命令的使用:

图 2.5 – cp 和 ls 命令

图 2.5 – cp 和 ls 命令

  • mv: 你可以使用 mv 命令将文件或目录从一个位置移动到另一个位置,甚至重命名文件。例如,你可以通过运行以下命令将文件 file1.txt 重命名为 file2.txt

    mv file1.txt file2.txt
    
  • rm: rm 用于删除文件或目录,-r–f 参数用于递归删除目录(-r)或强制删除文件或目录(-f)。和往常一样,可以使用 man 命令查看所有可用选项。

  • locate: locate 命令在你忘记文件位置时非常有用。使用 -i 参数可以忽略大小写敏感。因此,如果你想找到一个名为 file1.txt 的文件,可以运行 locate -i file1.txt 命令。这相当于 Windows 中的搜索功能。

这些是一些基本命令,展示了如何列出文件、检查当前工作目录、创建目录、将文件复制到另一个文件等等。在接下来的部分,我们将使用一些更高级的命令。

中级 shell 命令

在上一部分,我们使用了一些基本命令来熟悉终端。在这一部分,我们将熟悉更多的高级命令,如下所示:

  • echoecho 命令允许你显示内容,可以将其添加到新文件或现有文件中,或替换文件内容。

  • 如果你想向现有文件中添加内容,可以使用 echo "content to be appended" >>file1.txt。或者,你可以使用 echo "this content will replace" > file1.txt 来替换文件中的内容。

你可以在这里看到 echo 命令的使用:

图 2.6 – echo 命令

图 2.6 – echo 命令

  • catcat 命令通常用于读取文件内容,如这里所示:

图 2.7 – cat 命令

图 2.7 – cat 命令

你可以使用 cat 命令并将输出追加到新文件中,使用 >>。这对于任何输出都适用——例如,你可以使用 ls –la >> files-directories.txtls –la 命令的结果重定向到文件中。

  • df:一个用于快速查看文件系统和所有挂载驱动器的优秀命令是 df 命令(代表disk-free)。你可以看到整体磁盘大小、已用空间、可用空间、利用率百分比以及磁盘挂载的分区。我建议配合 -h 参数使用,这样数据对人类更加可读。你在这里看到的数据是从文件系统级别或挂载点中提取的:

图 2.8 – df 命令

图 2.8 – df 命令

  • du:在适当使用时,du 命令(代表磁盘使用)效果非常好。当你需要知道特定目录或子目录的大小时,这个命令非常合适。它只报告在执行时提供的统计数据,并且操作是基于对象的。例如,你可以使用 du -sh /Documents 命令来查看 Linux 的 Documents 文件夹占用了多少磁盘空间。结合 -sh 标志使用时,这个命令可以提供给定项目的摘要,以人类可读的形式显示(包括目录和所有子目录)。你可以在这里看到 du 命令的使用:

图 2.9 – du 命令

图 2.9 – du 命令

  • unameuname 命令显示你的 Linux 发行版当前使用的操作系统信息。通过使用 uname -a 命令,可以打印出系统的大部分信息。它显示内核的发布日期、版本、处理器类型以及其他相关信息。你可以在这里看到 uname 命令的使用:

图 2.10 – uname 命令

图 2.10 – uname 命令

  • chmod:用于修改文件系统对象的特殊模式标志和访问权限的系统调用和命令称为 chmod。这些最初统称为其模式,chmod 这个名字是 change mode(更改模式)的首字母缩写(更多细节请参见 第七章用户和 组管理)。

假设你希望更改名为 file1.txt 的文件权限,使得以下操作成为可能:

  • 用户可以执行、读取和写入它

  • 组内成员可以读取和使用它

  • 其他人只能读取它

这个命令可以解决问题:

chmod u-rwx,g=rx,o=r file1.txt

在这个例子中使用的是符号权限表示法。ugo 分别代表 用户其他。字母 rwx 分别代表 读取写入执行,而等号(=)表示 准确地设置这些权限。各个授权类之间没有空格,只有逗号分隔它们。

这里是使用八进制权限表示法的等效命令:

chmod 754 file1.txt

在这里,数字 754 分别代表用户、组和其他权限,顺序如此。每个数字由 4210 组成,分别表示以下含义:

  • 4 代表 读取

  • 写入 的前缀是 2

  • 1 代表 执行

  • 0 代表 无权限

因此,7 由权限 4+2+1(读取、写入和执行)、5(读取、无写入和执行)和 4(读取、无写入和无执行)组成。

  • chown:要更改 Unix 和类 Unix 操作系统中系统文件和目录的所有者,请使用 chown 命令。这将把指定文件名或目录的所有权更改为用户(voxsteel)和组(voxsteel),如下所示:

    chown voxsteel:voxsteel <filename or directory name>
    

如果你是一个非特权用户,且希望修改你拥有的文件的组成员,可以使用 chgrp

  • chgrp:可以使用 chgrp 命令将文件系统对象的组更改为其所属的组,如下例所示。文件系统对象有三组访问权限:一个是所有者权限,一个是组权限,一个是其他用户权限:

    chgrp groupname <filename> (or directory name)
    

使用关键词搜索手册页可以帮助你找到命令,即使你已经忘记了它的名字。man -k 是用于此的语法。例如,在终端运行此命令,将列出特定于终端的命令。

摘要

重定向、Bash 历史、命令别名、命令行技巧等等是本章中涉及的更复杂的 Shell 命令相关概念。如果你在记忆过程中遇到困难,不用担心;一开始面对如此大量的信息时感到不知所措是很正常的。我已经使用 Linux 专业工作超过 15 年,但仍然没有掌握所有的知识。

在下一章,我们将讨论文件系统、它们之间的区别,以及主系统目录的结构和它们的用途。

第三章:Linux 文件系统

本章讨论的主题是文件和文件系统。Unix 的 一切皆文件 理念在 Linux 中延续,尽管这并非 100% 成立,但大多数 Linux 资源实际上是文件。

在本章中,我们将首先定义几个相关的概念。接着,我们将探讨 Linux 如何实现 一切皆文件 的概念。然后,我们将介绍内核用于报告正在运行的进程或连接的硬件的专用文件系统。接下来,我们将讨论普通文件和文件系统,这些通常与文档、数据和应用程序相关联。最后,我们将探讨标准的文件系统操作,并提供与其他可用替代方案的比较。了解每种文件系统类型的限制非常重要,这样你才能做出最佳决策。

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

  • 可用文件系统类型及其之间的差异

  • 目录树和标准目录

  • 如何挂载/卸载文件系统

什么是文件系统?

文件系统(有时也称为 文件系统)负责计算机中数据的存储和检索。如果没有文件系统,计算机硬盘上保存的所有数据将被混在一起,无法找到特定的文件。相反,使用文件系统后,数据被轻松分隔并通过为每一部分命名来标识。每一组数据被称为 文件,这个名字最初来自于使用纸质存储的信息系统。文件系统 是指用于处理数据名称和数据块组的组织框架和逻辑原则的名称。

实际上,Linux 提供了多种文件系统;如果你不确定该使用哪个文件系统,我们将提供一个详尽的 Linux 支持文件系统的列表。

Linux 文件系统有哪些类型

Linux 有多种文件系统可以选择,包括以下几种:

  • ext:专门为 Linux 构建的第一个文件系统叫做 ext,它是 扩展文件系统(extended filesystem)的缩写,发布于 1992 年。ext 开发者的主要目标是增加可编辑文件的最大大小,当时的限制为 64 MB。通过新创建的元数据结构,最大文件大小增大到了 2 GB。文件名的最大长度也同时增加到 255 字节。

  • ext2:这也被称为 第二扩展系统。ext2 由 Remy Card 开发,就像第一个文件系统一样,旨在取代 Linux 原始的扩展系统。它在存储容量和整体性能等方面引入了创新。最大文件大小显著增加到 2 TB,相较于上一版本的 2 GB。然而,文件名仍然保持为最多 255 字节。

  • ext3:ext3 是 ext2 的升级版,首次开发于 2001 年。2 TB 的最大文件大小没有变化,但 ext3 优于 ext2,因为它是一个日志记录文件系统。2 TB 的最大文件大小保持不变。这意味着如果计算机、硬盘或两者因为某种原因崩溃,或遇到断电等情况,文件可以通过重启后使用一个单独的日志进行修复和恢复,该日志包含崩溃前所做的更改。

ext3 支持三种日志记录级别:

  • 日志:在发生断电的情况下,文件系统通过将用户数据和元数据写入日志来确保有效的文件系统恢复。在三种 ext3 日志记录模式中,这是最慢的。这种日志模式降低了在 ext3 文件系统中对任何文件所做的更改丢失的可能性。

  • data=writeback 模式下,仅元数据更新会被记录到日志中。数据更新则直接写入磁盘上的相应位置,而不先写入日志。由于减少了日志记录数据的开销,这种方法能为写密集型工作负载提供更好的性能。

优点

  • 对于写密集型工作负载,性能得到提升,因为数据直接写入磁盘,避免了额外的日志记录开销。

  • 写入延迟较低,因为数据无需写入两次(一次到日志,再次到文件系统)。

缺点

  • 系统崩溃或断电时数据一致性降低。由于数据更新未被记录在日志中,崩溃时可能会发生数据丢失或不一致。

  • 在数据完整性至关重要的场景中(例如数据库),writeback 模式可能不是最佳选择,因为它优先考虑性能而非数据一致性。

  • 有序:该模式不会更新相关的文件系统元数据,而是先将文件数据的更改刷新到磁盘,然后再更新相关的文件系统元数据。这是 ext3 的默认日志记录模式。只有在断电时,正在写入磁盘的文件会消失,文件系统的架构不会受到损害。

  • ext4第四扩展文件系统,通常称为 ext4,发布于 2008 年。由于它克服了 ext3 存在的一些缺点,ext4 成为了大多数 Linux 发行版的默认文件系统。相比 ext3,ext4 支持显著更大的文件系统和单个文件大小。它可以处理最大 1 exabyte(1 EB)的文件系统和最大 16 terabytes(16 TB)的单个文件。此外,ext4 中的目录最多可以包含 64,000 个子目录(相比之下,ext3 为 32,000 个)。

在 ext4 中,范围(Extent)取代了固定块,成为数据分配的主要方法。范围在硬盘上的起始和结束位置用于描述该范围。由于能够用单个 inode 指针条目表示非常长的物理连续文件,因此表示大文件中所有数据位置所需的指针数量可以大大减少。

  • JFS:JFS 代表 日志文件系统。它是由 IBM 开发的 64 位文件系统。1990 年,JFS 的第一个版本(也称为 JFS1)推出,用于 IBM 的 AIX 操作系统。

电源中断后的数据恢复简单且可靠。此外,与其他文件系统相比,JFS 需要更少的 CPU 功率。

  • XFS:SGI 从 1990 年代初期开始开发 XFS,旨在将其作为其 IRIX 操作系统的文件系统。为了应对最具挑战性的计算难题,XFS 被设计为一种高性能的 64 位日志式文件系统。XFS 在大文件操作和高端硬件性能方面具有优势。在 SUSE Linux Enterprise Server 中,XFS 是数据分区的默认文件系统。

  • Btrfs:Chris Mason 创建了名为 Btrfs 的 写时复制COW)文件系统。它基于 Ohad Rodeh 的适用于写时复制的 B 树。Btrfs 是一种日志式文件系统,它通过将修改后的块写入新的区域而不是将其记录在日志中,来链接更改。新的更改在最后一次写入之前不会被提交。

  • 交换:当计算机的可用内存开始变低时,系统会使用一个被称为交换文件的文件,在固态硬盘或硬盘上生成临时存储空间。该文件会用一个新的部分替代暂停程序中的一部分内存,从而释放内存供其他进程使用。

计算机通过使用交换文件可以利用比物理安装的更多的内存。换句话说,它能够运行比单纯依赖物理内存所提供的有限资源更多的程序。

由于交换文件并不保存在计算机的实际内存中,我们可以将其视为一种虚拟内存。当计算机使用交换文件时,操作系统实际上是在自欺欺人,认为它拥有比实际更多的内存。

Linux 兼容多种文件系统,包括 FAT 和 NTFS 文件系统,这些文件系统是其他操作系统(如 Windows)的标准。嵌入式开发者可能会支持这些文件系统,尽管在大多数情况下,会使用 Linux 文件系统,如 ext4、XFS 或 Btrfs,来作为存储分区。更好地理解现有替代方案的优点,将有助于你为特定应用选择合适的文件系统。

高可扩展性

通过利用分配组,XFS 提供了卓越的可扩展性。

支持 XFS 文件系统的块设备在创建文件系统时被划分为八个或更多相同大小的线性区域,它们被称为分配组。每个分配组控制其自身的空闲磁盘空间和 inode。由于分配组之间相对独立,内核可以同时处理多个分配组。这一特点使得 XFS 具备了高度的可扩展性。这些自治的分配组自然满足了多处理器系统的需求。

高性能

XFS 通过有效管理磁盘空间来提供高性能。

在分配组内,B+树管理着空闲空间和 inode。通过使用 B+树,XFS 的效率和可扩展性得到了显著提升。XFS 通过将分配过程分为两步来延迟管理分配。待处理的事务保存在 RAM 中,并预留了必要的空间。数据存储的精确位置(以文件系统块为单位)仍然由 XFS 来决定。这个选择会被推迟到最后一刻。如果在 XFS 选择存储位置时数据已过时,某些短暂的临时数据可能永远无法写入磁盘。XFS 通过这种方式提高了写入性能并减少了文件系统的碎片化。在写入操作发生崩溃时,延迟分配文件系统比其他文件系统更可能遭遇数据丢失。

我的系统使用哪种文件系统?

如果你不确定你的发行版使用的是哪种文件系统,或者你只是想知道自己安装的是哪种文件系统,可以在终端使用一些巧妙的命令来查找。

还有其他方法可以完成这一任务,但我们将在这里展示最简单的一种,使用 df -T 命令。

图 3.1 – 用于确定正在使用的文件系统类型的命令

图 3.1 – 用于确定正在使用的文件系统类型的命令

在第二列标为 Type 的部分,你可以看到文件系统格式的描述。这时,你应该能够判断出你在 Linux 系统上挂载的是什么文件系统。

FUSE 文件系统

作为用户,在与用户空间中的文件和目录交互时,你不需要过多担心底层的实现。通常,进程通过系统调用与内核交互,从而读取或写入已挂载的文件系统。然而,你确实可以访问一些看起来不属于用户领域的数据,特别是 stat() 系统调用,它返回 inode 号和链接计数。

即使在你不维护文件系统时,是否需要担心 inode 编号、链接计数和其他实现细节?不需要(在大多数情况下)。这些信息提供给用户模式程序的主要目的是为了保持向后兼容。此外,这些文件系统内部细节并非每个 Linux 文件系统都有,因为它们并没有标准化。VFS 接口层负责确保系统调用始终返回 inode 编号和链接计数;然而,这些数字的值可能代表某些意义,也可能不代表任何意义。

在非传统文件系统上,您可能无法执行典型的 Unix 文件系统操作。例如,您无法在挂载的 VFAT 文件系统上使用ln命令创建硬链接,因为该文件系统的目录条目结构不支持硬链接的概念。由于用户空间提供的系统调用具有高度抽象,因此在 Linux 系统上操作文件无需了解底层实现的任何知识。此外,文件名的格式灵活,并且支持使用大小写混合的文件名;这两个特点使得支持其他层次结构样式的文件系统变得简单。

请记住,特定文件系统的支持不一定需要包含在内核中。举一个例子,内核在用户空间文件系统中的作用仅限于作为系统调用的传导者。

目录树和标准目录

要查看根目录的主要结构,只需使用以下命令:tree -L 1

图 3.2 – 查看目录结构树的命令

图 3.2 – 查看目录结构树的命令

为了更好地理解 Linux 文件系统的运作方式,让我们参考 图 3.2 中显示的 Linux 文件系统图,看看每个文件夹的作用。并非所有这里提到的文件夹或前面示例中的文件夹都会出现在每个 Linux 发行版中,但绝大多数都会:

  • /bin:大多数二进制文件保存在此位置,这个位置发音为bin,并且常常被 Linux 终端命令和一些基本实用工具使用,例如cd(切换目录)、pwd(打印当前目录)、mv(移动)等。

  • /boot:所有与 Linux 启动相关的文件都可以在此文件夹中找到。大多数人(包括我)将此目录保存在硬盘的单独分区上,特别是如果他们计划使用双启动。记住,即使 /boot 物理上位于不同的分区,Linux 仍然认为它位于 /boot

  • /dev:你的物理设备,例如硬盘、USB 驱动器和光盘,在这里挂载。此外,您的磁盘可能会有不同的分区,在这种情况下,您将看到/dev/sda1/dev/sda2,以此类推。

  • /etc:此目录存储配置文件。用户可以将配置文件保存在自己的 /home 文件夹中,这样只会影响该用户,而存放在 /etc 中的配置通常会影响系统上的所有用户。

  • /home:因为此目录包含你所有的个人信息,所以你将大部分时间都待在这里。/home/username 目录包含 DesktopDocumentsDownloadsPhotosVideos 等子目录。

  • /lib:此目录包含所有的库文件。在安装 Linux 发行版时,通常会下载以 lib- 开头的附加库文件。你的 Linux 程序的运行依赖于这些文件。

  • /media:这是挂载外部设备(如 USB 驱动器和 CD-ROM)的地方。不同的 Linux 发行版之间可能有所不同。

  • /mnt:此目录基本上是其他文件夹或磁盘的挂载点。虽然可以用于任何用途,但通常用于网络位置。

  • /opt:此目录包含计算机的附加软件,这些软件并未被你所使用的发行版中的软件包管理工具管理。

  • /proc进程文件夹包含各种文件,保存系统数据(记住,万物皆文件)。本质上,它为 Linux 内核——操作系统的核心——提供了一种与在 Linux 环境中运行的多个进程进行通信的机制。

  • /root:这是根用户的等效目录,通常被称为超级用户。除非你非常确信自己知道自己在做什么,否则不要随意操作此目录中的任何内容。

  • /sbin:此目录类似于/bin,但它包含的指令只能由root用户执行,有时也被称为超级用户

  • /tmp:临时文件存储在这里,通常在计算机关机时被删除,因此你无需像在 Windows 中那样手动删除它们。

  • /usr:此目录包含在用户之间共享的文件和工具。

  • /var:系统在运行过程中用于存储信息的文件通常位于 Linux 及其他类 Unix 操作系统中根目录的 /var 子文件夹中。

我们现在已经涵盖了根目录,但许多子目录也包含其他文件和文件夹。你可以从下面的图示中大致了解基本的文件系统树形结构:

图 3.3 – 基本的文件系统结构(来源:https://en.wikipedia.org/wiki/Unix_filesystem)

图 3.3 – 基本的文件系统结构(来源:en.wikipedia.org/wiki/Unix_filesystem

理解根目录结构将使你在 Linux 世界中生活更加轻松。

链接(硬链接和符号链接)

有两种替代方式来引用硬盘上的文件:硬链接和符号链接。文件系统通过不同的方式组织文件的位置,包括符号链接和硬链接。硬链接基本上指向文件的 inode,是该文件的同步副本。另一方面,符号链接直接指向文件,而文件再指向 inode,形成一个快捷方式。我们需要接下来讨论 inode,才能理解符号链接和硬链接是如何工作的。

什么是 inode?

Unix 风格的文件系统使用一种称为 inode 的数据结构来描述文件系统对象,如文件和目录。对象的数据属性和磁盘块位置存储在每个 inode 中。文件系统对象的属性可以包括元数据、所有者信息和权限信息。

inode 本质上是一个地址的数字等价物。操作系统可以通过 inode 获取有关文件的详细信息,包括权限、特权以及数据在硬盘上的精确位置。

什么是硬链接?

在计算机系统中,硬链接是一种特殊的链接,通过文件名直接指向特定的文件。即使文件名发生更改,硬链接仍然会继续指向原始文件,这与软链接不同。

在将目录项或文件链接到相同内存区域的两种方法进行比较时,硬链接更加可靠。与符号链接不同,硬链接防止文件被删除或移动。当多个硬链接指向同一个文件时,别名效应 可能会发生,文件会有多个标识符。专家一致认为,所有链接,无论是硬链接还是软链接,都是指针;然而,硬链接被认为比软链接更加永久。

什么是符号链接?

符号链接本质上是快捷方式,指向的是文件,而不是文件的 inode 值。此方法可以应用于目录,也可以用来引用位于不同硬盘和磁盘卷上的数据。如果原始文件被移动到其他文件夹,符号链接会被破坏或变成悬挂链接。这是因为符号链接指向的是原始文件,而不是文件的 inode 值。

因为符号链接指向原始文件,所以你对符号链接所做的任何更改,都应导致对实际文件的相应更改。

挂载和卸载文件系统

为了让计算机访问文件,文件系统必须被挂载。mount 命令将显示当前在系统上挂载(可用)的内容。

我创建了自己的 /data 文件夹,并将一个新的硬盘挂载到该文件夹:

图 3.4 – 显示挂载在 /data 上的文件系统的命令

图 3.4 – 显示挂载在 /data 上的文件系统的命令

要通过命令挂载文件系统,只需运行以下命令:

mount -t ext4 /dev/mapper/cs-home /data

为了在重启时自动挂载,你必须在/etc/fstab中定义这一条目。

如果你想挂载 CD-ROM,只需运行以下命令:

mount -t iso9660 /dev/cdrom /mnt/cdrom

若需更详细的信息,请查阅mount的手册页,或运行mount -h来获取帮助。

cd命令可以用来遍历你在挂载后创建的新可访问文件系统。

如何卸载文件系统

使用umount命令并指定挂载点或设备,你可以卸载(分离)文件系统。

例如,以下命令将卸载 CD-ROM:

umount /dev/cdrom

伪文件系统

进程信息伪文件系统是proc文件系统的另一种名称。它包含的是运行时的系统信息,而不是实际的文件(例如,系统内存、已挂载的设备、硬件配置等)。因此,它可以被看作是内核的命令和信息中心。该目录(/proc)被许多系统工具访问。lsmod命令列出内核加载的模块,lspci命令显示附加到 PCI 总线的设备。这两个命令分别等同于cat /proc/modulescat /proc/pci。类 Unix 操作系统(例如 Linux)中的常见伪文件系统示例如下:

  • 进程,最显著的用途

  • 内核信息和参数

  • 系统指标,例如 CPU 使用情况

进程

有关每个运行进程的所有信息都可以在/proc/pid文件中找到。这里展示了当前正在运行的一些 PID:

图 3.5 – 查看运行中的进程的命令

图 3.5 – 查看运行中的进程的命令

以 PID 1031为例,看看里面有什么:

图 3.6 – 查看 PID 1031 进程内部内容的命令

图 3.6 – 查看 PID 1031 进程内部内容的命令

最后,合成文件系统是一种提供类树形接口的文件系统,用于非文件对象,使它们看起来像是硬盘存储或长期存储文件系统中的普通文件。这种类型的文件系统也称为虚假文件系统。

内核和系统信息

/proc下的众多文件夹包含了大量关于内核和操作系统的知识。这里有太多内容无法一一列出,但我们会介绍一些,并简要说明它们包含的内容:

  • /proc/cpuinfo:关于 CPU 的信息

  • /proc/meminfo:关于物理内存的信息

  • /proc/vmstats:关于虚拟内存的信息

  • /proc/mounts:关于挂载信息

  • /proc/filesystems:关于已编译到内核中的文件系统以及当前加载的内核模块的信息

  • /proc/uptime:显示当前系统的运行时间

  • /proc/cmdline:内核命令行

CPU 使用情况

在评估一个系统的整体性能时,了解 CPU 时间的使用情况至关重要。了解如何通过命令行监控 Linux 中的 CPU 利用率是每个使用 Linux 的人必备的知识,无论是爱好者还是系统管理员。

用于此目的的最常见命令之一是top

图 3.7 – top 命令的输出

图 3.7 – top 命令的输出

系统的响应应该是所有活动任务的全面总结,用户、进程、CPU 使用情况和内存消耗都可以查看。

总结

在本章中,我们探讨了 Linux 中的文件和文件系统。文件系统是一种强大而多功能的方式,用于以层次化的方式组织对信息的访问。在 Linux 中,文件系统是众多技术和持续努力的重点。有些是开源的,但也有一系列的商业选项。

在下一章中,我们将讨论进程和进程控制。

第四章:进程与进程控制

操作系统的主要作用是让应用程序能够在计算机上运行并使用其资源。系统管理员的角色往往归结为确保正确的进程在运行,并在它们没有运行时进行诊断。因此,理解操作系统如何启动和处理进程,以及如何启动、停止和监控它们是非常重要的。

本章将涉及以下主题:

  • 可执行文件与进程

  • 进程终止、退出码和信号

  • 进程树

  • 进程查找与监控

可执行文件与进程

程序作为可执行文件分发。在许多历史操作系统中,程序是从文件中直接按字节加载到内存的。这种方式实现简单,但有许多局限性(最明显的是要求具有固定的内存布局以及无法存储任何元数据),因此后来的系统发明了可执行文件的特殊格式。

例如,如果我们用文件命令检查 Bourne Again ShellBash)可执行文件,我们会看到如下内容:

$ file /bin/bash
/bin/bash: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=9c4cb71fe5926100833643a8dd221ffb879477a5, for GNU/Linux 3.2.0, stripped

如果你使用的是除 Debian 或 Red Hat 衍生版外的其他 Linux 发行版(这些是本书的主要重点),并且前面的命令对你无效,你可以使用 which bash 查找 bash 可执行文件的位置,或者选择其他可执行文件,如 catls

ELF 代表 可执行与可链接格式。它是 Linux 和许多其他类 Unix 操作系统的默认可执行文件格式(LSB 意味着 Linux 标准基准)。ELF 文件存储程序的可执行代码——被加载到内存中供 CPU 执行的机器指令。然而,它也可以存储调试信息,例如机器指令与它们编译自的程序源代码行之间的关联。ELF 文件还可以与其他 ELF 文件进行链接,这些文件被称为共享库——包含可执行代码但不作为程序运行,只作为可重用函数的集合存在。

你可以使用 ldd 命令查看库链接信息:

$ ldd /bin/bash
    linux-vdso.so.1 (0x00007ffc06ddb000)
    libtinfo.so.6 => /lib64/libtinfo.so.6 (0x00007f30b7293000)
    libc.so.6 => /lib64/libc.so.6 (0x00007f30b7000000)
    /lib64/ld-linux-x86-64.so.2 (0x00007f30b743e000)

如果你对 libc.so.6 执行 file 命令,这是 C 编程语言的标准库,你会看到它也是一个 ELF 文件:

$ file /lib64/libc.so.6
/lib64/libc.so.6: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=6f5ce514a9e7f51e0247a527c3a41ed981c04458, for GNU/Linux 3.2.0, not stripped

最后,ELF 存储元数据,如目标操作系统和 CPU 架构。文件命令不会猜测前面示例中的文件是针对 x86-64 上的 Linux 的,而是直接从 ELF 文件头获取信息。

file /bin/bash 的输出中,你可能会注意到一个不寻常的字段——interpreter /lib64/ld-linux-x86-64.so.2。Bash 是用 C 语言编写的,而 C 是一种编译型语言,不需要任何解释器。实际上,那个可执行文件包含机器代码,Linux 内核知道如何加载 ELF 文件;如果它不能这样做,就无法加载那个 ld-linux 解释器,从而导致一个“先有鸡还是先有蛋”的问题。

ld-linux.so 的作用不是解释可执行文件本身,而是正确解析来自共享库的函数引用。如果你对它运行 file 命令,你会看到它是 static-pie linked 而不是 dynamically linked,与 /bin/bash 不同 —— static-pie 意味着 static position-independent executable(静态位置无关可执行文件):

$ file /lib64/ld-linux-x86-64.so.2
/lib64/ld-linux-x86-64.so.2: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), static-pie linked, BuildID[sha1]=8fa0bd5df5fa6fff60bb4cbdd753621d00f94dfc, with debug_info, not stripped

内核不了解程序的库函数依赖关系,只能直接加载静态链接的 ELF 可执行文件。为了加载动态链接的可执行文件,它依赖于 ld-linux.so 辅助程序,但它为此重新使用了一种通用的解释器关联机制,而不是为这种情况发明一个专门的机制。

使用解释性语言编写的程序,如 Python、Ruby 或 shell,首先需要加载实际的解释器。这通过以 #! 开头的 shebang 行 来指定。

你可以通过创建一个简单的 shell 脚本来亲自尝试一下:

$ echo '#!/bin/bash' >> hello.sh
$ echo 'echo "hello world"' >> hello.sh
$ chmod +x ./hello.sh
$ ./hello.sh
hello world

如果一个文件具有可执行位(+x)并以 shebang 行开头,内核将首先加载它的解释器(在这个例子中是/bin/bash),然后将可执行文件作为参数传递给它。

一旦一个可执行文件被加载,无论是由内核本身直接加载还是通过解释器的帮助,它就变成了一个正在运行的进程

进程终止和退出码

所有进程最终都必须终止,并且在许多情况下,进程执行无法继续,可能是由于其自身程序逻辑中的错误或环境问题(例如缺少文件)。用户也可能需要手动终止进程,无论是为了修改系统,还是为了防止一个行为异常的进程占用资源或干扰系统的正常运行。

在本节中,我们将学习如何检查已终止进程的退出码,以推测其终止的可能原因,以及如何与进程进行通信并强制它们终止。

退出码

大多数进程寿命较短 —— 它们完成工作后会自行终止。每个进程都有一个退出码 —— 一个数字值,表示它是正常退出还是由于错误终止。按惯例,零退出码表示成功,任何非零退出码表示错误。非零退出码没有标准的含义 —— 具体含义在程序和操作系统之间有所不同,许多程序遇到错误时会直接以 1 退出,无论错误是什么。

在 bash 中,你可以通过名为 $? 的特殊变量来查看上一个命令的退出码。有一对 Unix 命令,它们的唯一目的是分别以成功和错误代码退出,truefalse

$ true
$ echo $?
0
$ false
$ echo $?
1

大多数时候,程序自己设置它们的退出代码。例如,在 Shell 脚本中,你可以使用exit 1来表示错误。在 C 语言中,你可以在main()函数中使用return 1来达到相同的效果。对于那些可以通过脚本非交互式执行的程序来说,遇到错误时退出时必须使用非零代码;否则,脚本作者将无法知道他们的脚本步骤是否成功或失败。

所有标准命令都会这样做。例如,让我们尝试以普通用户身份在/etc/目录下创建一个文件,并查看它在$?变量中留下的内容:

$ touch /etc/my_file
touch: cannot touch '/etc/my_file': Permission denied
$ echo $?
1

退出代码的最简单用例是使用||&&操作符连接命令。它们可以在错误时成功时调用 —— 在cmd1 || cmd2中,如果cmd1失败(即以非零代码退出),Shell 会执行cmd2;在cmd1 && cmd2中,则相反——只有当cmd1成功(以零代码退出)时,cmd2才会被执行:

$ touch /etc/my_file || echo "Fail!"
touch: cannot touch '/etc/my_file': Permission denied
Fail!
$ touch /tmp/my_file && echo "Success!"
Success!

对于我们示例中的文件权限错误等错误,内核仅仅是没有按照程序的请求去执行,而是允许程序继续正常运行。原因在于,这种错误通常是由于用户输入不正确而发生的,而程序逻辑是正确的,因此程序需要能够处理这些错误并通知用户。然而,在其他情况下,内核会通过生成信号来中断进程。

信号

信号是进程执行过程中可能出现的一种特殊情况。POSIX 标准定义了许多信号。有些信号与特定的程序逻辑错误相关,例如SIGILL非法指令(例如尝试除以零时引发)— 或SIGSEV内存段错误(例如尝试读取或修改未分配给进程的内存)。其他信号则是由外部条件生成,用于强制进程处理这些信号,例如SIGPIPE,当网络套接字或本地管道被另一端关闭时会生成该信号。这些信号主要对软件开发人员有意义,但有些信号被设计为管理员的进程控制工具,如SIGINT(中断进程)、SIGTERM(请求进程清理其状态并终止)、以及SIGKILL(通知内核强制终止进程)。有人说信号是发送到进程的。这个术语对用户来说是一个不错的抽象,但实际上,当信号被生成时,是内核在执行控制,而不是进程。程序员可以预期某些信号,并为它们注册信号处理程序。例如,许多高级编程语言的解释器会处理SIGILL信号,并将其转换为异常,例如 Python 中的ZeroDivisionError。然而,如果程序员忘记或选择不为SIGILL注册处理程序,而程序又尝试除以零,那么内核会终止该进程。

如果你安装了 GNU 编译器集合 (GCC),你可以通过一个简单的 C 程序亲自验证:

$ echo "void main() { int x = 0 / 0; }" >> sigill.c
$ gcc -o sigill ./sigill.c
./sigill.c: In function 'main':
./sigill.c:1:25: warning: division by zero [-Wdiv-by-zero]
    1 | void main() { int x = 0 / 0; }
    |                         ^
$ ./sigill
Floating point exception (core dumped)
$ echo $?
136

GCC 友好地警告你程序有误,但如果你仍然尝试运行它,内核会强制终止它并将错误代码设置为非零值。

SIGILLSIGPIPE 这样的信号是无论用户意愿如何都会发生的,但也有一些信号类别是用户(或代表用户的进程)主动触发的。

kill 命令

发送信号给进程的命令叫做 kill。这个名字有些误导;它通常用于强制终止进程,但也可以发送其他信号。

为了说明其用法,让我们学习如何将一个进程发送到后台。在 bash 中,你可以通过在命令后面添加一个 & 符号来实现。使用 jobs 命令,你可以看到后台进程的列表,通过使用 fg <job number>,你可以将某个编号的任务带到前台。以下是如何将一个 cat 进程发送到后台,然后再将其带回前台:

$ cat &
[1] 22501
[1]+  Stopped                 cat
$ jobs
[1]+  Stopped                 cat
$ fg 1
cat
hello world
hello world
^C
$

当你按下 Ctrl + C 在 shell 中终止进程时,你实际上是在要求你的 shell 发送 SIGINT 信号——一个中断执行的信号。如果一个进程在后台运行,我们不能用 Ctrl + C 来中断它。然而,通过 kill,我们可以手动发送信号:

$ cat &
[1] 22739
[1]+  Stopped                 cat
$ kill 22739
$ jobs
[1]+  Stopped                 cat
$ fg 1
cat
Terminated

发生的事情是——当我们运行 cat & 时,shell 告诉我们它的 后台任务编号 (1) 和 22739,不过它也可以是任何数字。然后我们使用 kill 22739 向该进程发送信号,确实,当我们尝试将它带到前台时,shell 告诉我们它在后台时已经被终止。

默认情况下,kill 命令会发送 SIGTERM 信号。SIGINTSIGTERM 都可以被进程捕获或忽略。通过向进程发送这些信号,你请求它终止;一个行为规范的进程应该遵从并可能利用这个机会在终止之前完成当前任务——例如,完成写入文件的数据。这意味着,在 shell 中按下 Ctrl + C 组合键或使用默认选项的 kill 命令都不适合强制终止一个行为不当的进程。

要强制一个进程退出,你应该使用 kill -SIGKILL <PID>,或者它的数字等效命令 kill -9 <PID>。不过,这应该是你的最后手段,因为内核会立即结束该进程,这可能会导致其文件处于不一致的状态。

如果你运行 kill -l,你将看到一长串可用的信号列表:

$ kill -l
 1) SIGHUP     2) SIGINT     3) SIGQUIT     4) SIGILL     5) SIGTRAP
 6) SIGABRT     7) SIGBUS     8) SIGFPE     9) SIGKILL    10) SIGUSR1
11) SIGSEGV    12) SIGUSR2    13) SIGPIPE    14) SIGALRM    15) SIGTERM
16) SIGSTKFLT    17) SIGCHLD    18) SIGCONT    19) SIGSTOP    20) SIGTSTP
21) SIGTTIN    22) SIGTTOU    23) SIGURG    24) SIGXCPU    25) SIGXFSZ
26) SIGVTALRM    27) SIGPROF    28) SIGWINCH    29) SIGIO    30) SIGPWR
31) SIGSYS    34) SIGRTMIN    35) SIGRTMIN+1    36) SIGRTMIN+2    37) SIGRTMIN+3
38) SIGRTMIN+4    39) SIGRTMIN+5    40) SIGRTMIN+6    41) SIGRTMIN+7    42) SIGRTMIN+8
43) SIGRTMIN+9    44) SIGRTMIN+10    45) SIGRTMIN+11    46) SIGRTMIN+12    47) SIGRTMIN+13
48) SIGRTMIN+14    49) SIGRTMIN+15    50) SIGRTMAX-14    51) SIGRTMAX-13    52) SIGRTMAX-12
53) SIGRTMAX-11    54) SIGRTMAX-10    55) SIGRTMAX-9    56) SIGRTMAX-8    57) SIGRTMAX-7
58) SIGRTMAX-6    59) SIGRTMAX-5    60) SIGRTMAX-4    61) SIGRTMAX-3    62) SIGRTMAX-2
63) SIGRTMAX-1    64) SIGRTMAX

其中一些信号没有预定义的含义,而是特定于应用程序的。例如,在 SIGUSR1SIGUSR2 中,USR 代表 用户定义。大多数进程会忽略它们,但有些进程会用它们让系统管理员强制重新加载配置或执行其他操作。

现在,我们知道如何检查已终止进程的错误代码,并了解它们是正常退出还是失败退出。我们还学会了操作系统内核如何通过信号与进程通信,以及如何使用 kill 命令要求内核让进程退出或强制终止一个表现不正常的进程。现在,让我们学习如何探索正在运行的进程及它们之间的关系。

进程树

我们已经看到,shell 知道你运行的命令的 PID,并且可以在你按下Ctrl + C时发送信号终止它们。这意味着它对你要求它启动的进程有一定的控制权。事实上,所有你从 shell 启动的内容都成为该 shell 进程的子进程

shell 本身是一个子进程——如果你在 Linux 桌面上,它是你的终端模拟器的子进程;如果你通过 SSH 远程连接,它是 OpenSSH 守护进程的子进程。然而,所有进程是否有父进程,或者是否可以存在没有父进程的多个进程呢?

事实上,所有进程都有一个父进程,所有正在运行的进程关系形成一棵以单一根节点(PID = 1)为根的树。由于历史原因,所有进程的父进程通常被称为init 进程。在通用 Linux 发行版中,这个进程长期以来是 System V init,因此有了这个术语。

PID=1 的进程可以是任何东西。当你启动一个 Linux 系统时,你可以告诉它加载哪个可执行文件作为 PID=1。例如,一种将系统启动到救援模式的方法是将init=/bin/bash附加到 GRUB 命令行(但你最好使用发行版启动菜单中的内建救援选项,因为它可能会传递额外的有用参数)。这将使你的内核进入单用户 shell 会话,而不是启动正常的引导过程。一些仅将 Linux 用作硬件抽象层的嵌入式系统可能会启动自定义进程。但是,通常情况下,PID=1 的进程作为服务管理器。

System V init 作为事实标准的服务管理器已经存在了二十多年,但大多数现代发行版使用 systemd,而有些则选择其他替代旧版 System V init 的方案,如 OpenRC。

init 进程是唯一一个由内核直接启动的进程。其他所有进程都是由 init 进程启动的:登录管理器、SSH 守护进程、Web 服务器、数据库系统——所有你能想到的东西。你可以使用 pstree 命令查看完整的进程树。以下是一个小型 Web 服务器的进程树:

$ pstree
systemd─┬─NetworkManager───2*[{NetworkManager}]
     ├─2*[agetty]
     ├─auditd───{auditd}
     ├─chronyd
     ├─crond
     ├─dbus-broker-lau───dbus-broker
     ├─do-agent───5*[{do-agent}]
     ├─droplet-agent───8*[{droplet-agent}]
     ├─nginx───nginx
     ├─sshd───sshd───sshd───bash───pstree
     ├─systemd───(sd-pam)
     ├─systemd-homed
     ├─systemd-hostnam
     ├─systemd-journal
     ├─systemd-logind
     ├─systemd-oomd
     ├─systemd-resolve
     ├─systemd-udevd
     └─systemd-userdbd───3*[systemd-userwor]

在这里,你可以看到 pstree 是一个 bash 会话的子进程,而该 bash 会话本身是 sshd(一个 OpenSSH 进程)的子进程,而 sshd 又是 systemd 的子进程——进程树的根。

然而,大多数时候,你会关注寻找特定的进程及其资源使用情况。

进程搜索与监控

pstree命令是可视化所有运行中进程及其相互关系的好方法,但在实际操作中,大多数时候管理员都在寻找特定的进程,或者需要了解它们的资源使用情况,而不仅仅是它们的存在。让我们来学习一些用于这些任务的工具——ps命令用于搜索进程,top命令用于实时监控资源使用情况,以及这些工具所使用的底层内核接口。

ps命令

PS进程选择进程快照的缩写。它是一个允许你检索和过滤正在运行的进程信息的工具。

不带任何参数运行ps将得到一个非常有限的选择——只显示由你的用户运行并且附加到终端的进程(即不是那些关闭所有输入和输出,仅通过网络或本地套接字与其他进程通信的进程——通常是守护进程,但图形用户界面程序也可能有类似的行为)。

有些许令人困惑的是,ps本身总是出现在这样的列表中,因为当它收集这些信息时,它本身就是一个正在运行的进程:

$ ps
    PID TTY          TIME CMD
 771681 pts/0    00:00:00 bash
 771705 pts/0    00:00:00 ps

PID字段当然是进程标识符——是内核在进程启动时分配给每个进程的唯一数字。如果存在,TTY字段表示终端——它可以是一个真实的串行控制台(通常是ttyS*ttyUSB*),一个物理显示器上的虚拟控制台(tty*),或者与 SSH 连接或终端模拟器相关的纯虚拟伪终端(pts/*)。

CMD字段显示启动进程时使用的命令及其参数(如果有使用的话)。

ps命令有许多选项。你应该立刻了解的两个选项是ax——这些选项移除了由我拥有的具有 终端的限制。

查看系统上每个进程的常用命令是ps ax。我们来试试运行它:

$ ps ax
    PID TTY    STAT    TIME COMMAND
    1 ?        Ss    1:13 /usr/lib/systemd/systemd --switched-root --system --deserialize 30
    2 ?        S       0:01 [kthreadd]
    3 ?        I<      0:00 [rcu_gp]
    4 ?        I<      0:00 [rcu_par_gp]
               …
    509 ?      Ss      7:33 /usr/lib/systemd/systemd-journald
    529 ?      S       0:00 [jbd2/vda2-8]
    530 ?      I<      0:00 [ext4-rsv-conver]
    559 ?      Ss    226:16 /usr/lib/systemd/systemd-oomd
    561 ?      Ss      0:49 /usr/lib/systemd/systemd-userdbd
    562 ?      S<sl    1:17 /sbin/auditd
    589 ?      Ss      0:10 /usr/lib/systemd/systemd-homed
    590 ?      Ss      0:02 /usr/lib/systemd/systemd-logind

STAT字段告诉我们进程的状态。S状态意味着进程处于可中断的睡眠状态——它在等待外部事件。当前正在执行的进程会显示为R状态——运行状态。I状态是特别的;它表示空闲的内核线程。最令人担忧的状态是D——不可中断的睡眠状态。如果进程正在等待输入/输出操作完成,它就处于不可中断的睡眠状态。因此,如果有大量这样的进程,可能意味着输入/输出系统过载。

请注意,有一些带方括号的命令名称在pstree的输出中看不到。实际上,它们是内核服务,被设计成类似进程的形式,以便于监控,或者是出于其内部的原因。

如果你还想查看每个进程的所有者,可以在命令中添加u

$ ps axu
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root        1  0.0  0.8 108084  8340 ?        Ss   Apr01   1:13 /usr/lib/systemd/systemd --switched-root --system --deserialize 30
root        2  0.0  0.0      0     0 ?     S    Apr01   0:01 [kthreadd]

还有许多其他的选择和格式选项,您可以在官方文档中找到。

进程监控工具

ps命令提供了进程、CPU 和内存消耗的静态视图。如果你想找出导致 CPU 使用率峰值的原因,那就非常不方便——你需要碰运气,正好在峰值发生时运行ps。这就是人们编写持续监控进程并按资源消耗排序显示它们的工具的原因。

这种类型的工具中最古老的之一是top。它在 Linux 发行版的仓库中广泛提供,甚至可能在你的系统中默认安装。它显示一个交互式的进程列表,消耗最大资源的进程会自动排到最上面。

图 4.1 – top 命令输出

图 4.1 – top 命令输出

还有其他受它启发的工具,例如htop,它提供了不同的用户界面和附加功能。还有一些工具监控tophtop不监控的资源使用类型,例如iotop,这是一个监控进程输入/输出活动的工具。

/proc文件系统

最后,让我们检查最低级的进程信息接口——/proc文件系统。它是一个虚拟文件系统的例子,并很好地展示了 Unix 类操作系统广泛使用的一切皆文件原则。

对用户而言,/proc看起来像是一个包含文件的目录。这些文件中有些与进程无关,而是包含其他系统信息——例如,/proc/version包含正在运行的内核版本。以下是来自 Fedora 35 系统的示例:

$ cat /proc/version
Linux version 5.16.18-200.fc35.x86_64 (mockbuild@bkernel01.iad2.fedoraproject.org) (gcc (GCC) 11.2.1 20220127 (Red Hat 11.2.1-9), GNU ld version 2.37-10.fc35) #1 SMP PREEMPT Mon Mar 28 14:10:07 UTC 2022

然而,这些文件并不是磁盘上的文件,甚至连根用户也无法写入或删除它们。内核只是让它们看起来像文件,以便用户可以使用熟悉的工具来读取它们。

每当一个进程启动时,内核会添加一个名为/proc/<PID>/的子目录。例如,让我们来看一下init进程的目录:

$ sudo ls /proc/1/
arch_status  auxv     cmdline      cpu_resctrl_groups  environ  fdinfo    
latency   map_files  mountinfo     net     oom_adj       pagemap    
projid_map  schedstat  smaps      stat syscall         timers        wchan
attr         cgroup     comm       cpuset          exe      gid_map    
limits      maps         mounts     ns        oom_score       
patch_state    root        sessionid  smaps_rollup  statm   task         
timerslack_ns
autogroup    clear_refs  coredump_filter  cwd              fd       io    
loginuid  mem         mountstats  numa_maps  oom_score_adj  personality    
sched        setgroups  stack         status  timens_offsets  uid_map

这个接口对于最终用户来说大多数时候太低级了。例如,启动时传递给进程的环境变量可以在名为environ的文件中找到,但让我们试着读取它看看:

$ sudo cat /proc/1/environ
TERM=vt220BOOT_IMAGE=(hd0,gpt2)/vmlinuz-5.16.18-200.fc35.x86_64

该输出似乎是乱码——变量没有分隔符。实际上,这不是一个 bug;该文件只是以进程内存中的原始格式包含了进程环境数据,变量对之间由不可打印的空字节分隔,符合 C 编程语言的约定。

同样适用于/proc/1/cmdline——这个文件包含启动该进程时使用的完整命令:

$ sudo cat /proc/1/cmdline
/usr/lib/systemd/systemd--switched-root--system--deserialize30

然而,它是可以识别的,并且正是ps命令执行进程命令的地方;它只是将空字节替换为空格,以使其正确且易于阅读:

$ ps ax | grep systemd
   1 ?     Ss     1:14 /usr/lib/systemd/systemd --switched-root --system --deserialize 30

因此,了解原始的/proc接口是有好处的,但将其作为进程信息的来源是不现实的。像pspstree这样的工具可以以更易读的形式呈现它,并允许你进行过滤。然而,重要的是要理解这些工具除了使用/proc外,并不使用任何特殊的内核接口,在紧急情况下,你始终可以从/proc中获取相同的信息,甚至不需要任何工具。

摘要

在这一章中,我们学到了进程启动并不是一项简单的操作,即使是本地代码二进制文件也并非只是逐字节加载到内存中。我们学会了如何使用pstree命令探索进程树,如何使用kill强制终止或重新加载进程,以及如何检查和解释退出代码。

我们还学到了内核通过 POSIX 信号与运行中的进程进行通信,不同的信号有不同的含义,并且有比kill命令允许用户发送的更多信号。除了由用户或用户空间工具发送的SIGTERMSIGKILL,还有许多信号是内核用来指示程序错误和特殊条件的。其中包括SIGILL,用于表示程序尝试执行非法 CPU 指令,以及SIGPIPE,用于表示连接被另一方关闭的情况。

在下一章中,我们将学习如何发现和检查运行 Linux 的机器中安装的硬件。

第五章:硬件发现

了解如何找出操作系统运行在哪些硬件上,以及有哪些外设连接,是所有系统管理员必须具备的技能——至少,每个管理员都需要知道有多少个 CPU 核心,以及可以分配给应用程序的内存有多少。在本章中,我们将学习如何使用原始内核接口和工具来检索并解释关于 CPU、USB 外设和存储设备的信息。我们还将介绍一些专门针对具有 SMBIOS/DMI 接口的 x86 机器的工具。

在本章中,我们将讨论以下内容:

  • 如何发现 CPU 的数量、型号名称和特性

  • 如何发现 PCI、USB 和 SCSI 外设

  • 如何使用特定平台的工具从系统固件中检索详细的硬件信息

发现 CPU 型号和特性

中央处理器无疑是最重要的硬件组件之一,了解它的详细信息有很多原因。CPU 的型号名称或编号和频率是你首先查看的内容,用来了解机器的使用年限和整体性能。然而,实际上还有更多的细节信息常常是有用的。例如,CPU 核心的数量是很重要的,尤其是当你运行支持多个工作线程或进程的应用程序时(比如make -j2)。如果运行的进程数超过了 CPU 的数量,可能会导致应用程序变慢,因为一些进程最终需要等待可用的 CPU。因此,你可能需要运行较少的工作进程,以避免过度加载机器。

了解你的 CPU 是否支持特定的加速技术,如 AES-NI 或 Intel QuickAssist 也很重要。如果支持这些加速功能,启用后某些应用程序的性能可能会大幅提升。

不同平台上的特性发现

你需要记住的一点是,关于 CPU 信息的发现,主要是 CPU 本身的特性,而不是 Linux 的功能。如果 CPU 无法报告某些信息,那么通过软件来查找这些信息可能会非常困难,甚至不可能。

许多 ARM 和 MIPS 架构的 CPU 并不会报告它们的确切型号名称,因此唯一的办法可能就是打开机箱,查看芯片上的信息。

例如,流行的树莓派 4 单板计算机使用的是 ARM Cortex A72 CPU,但它并没有告诉你这一点。

pi@raspberrypi4   :~ $ cat /proc/cpuinfo
processor         : 0
model name        : ARMv7 Processor rev 3 (v7l)
BogoMIPS          : 108.00
Features          : half thumb fastmult vfp edsp neon vfpv3 tls vfpv4 idiva idivt vfpd32 lpae evtstrm crc32
CPU implementer   : 0x41
CPU architecture  : 7
CPU variant       : 0x0
CPU part          : 0xd08
CPU revision      : 3

我们将重点关注由 AMD 和 Intel 制造的 x86 架构 CPU,因为它们仍然是嵌入式系统市场以外最常见的 CPU,而且它们具有最广泛的特性报告功能。

/proc/cpuinfo 文件

当我们在第四章《进程与进程控制》中讨论进程控制时,我们谈到了/proc文件系统。这个文件系统是虚拟的——/proc下的文件并不实际存在于磁盘上,内核只是根据 Unix 哲学中的“一切皆文件”原则,像存储在文件中一样呈现这些信息。

除了有关运行进程的信息外,内核还使用/proc来获取关于 CPU 和内存的信息。包含 CPU 信息的文件名为/proc/cpuinfo,正如我们之前所看到的。

现在,让我们来看一下 Intel 机器上的那个文件:

$ cat /proc/cpuinfo
processor         : 0
vendor_id         : GenuineIntel
cpu family        : 6
model             : 44
model name        : Intel(R) Xeon(R) CPU            L5630  @ 2.13GHz
stepping          : 2
microcode         : 0x1a
cpu MHz           : 2128.000
cache size        : 12288 KB
physical id       : 0
siblings          : 1
core id           : 0
cpu cores         : 1
apicid            : 0
initial apicid    : 0
fpu               : yes
fpu_exception     : yes
cpuid level       : 11
wp                : yes
flags             : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts mmx fxsr sse sse2 syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts nopl xtopology tsc_reliable nonstop_tsc cpuid aperfmperf pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes hypervisor lahf_lm pti tsc_adjust dtherm ida arat
bugs              : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit
bogomips          : 4256.00
clflush size      : 64
cache_alignment   : 64
address sizes     : 42 bits physical, 48 bits virtual
power management  :
processor         : 1
vendor_id         : GenuineIntel
cpu family        : 6
model             : 44
model name        : Intel(R) Xeon(R) CPU            L5630  @ 2.13GHz
stepping          : 2
microcode         : 0x1a
cpu MHz           : 2128.000
cache size        : 12288 KB
physical id       : 2
siblings          : 1
core id           : 0
cpu cores         : 1
apicid            : 2
initial apicid    : 2
fpu               : yes
fpu_exception     : yes
cpuid level       : 11
wp                : yes
flags             : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts mmx fxsr sse sse2 syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts nopl xtopology tsc_reliable nonstop_tsc cpuid aperfmperf pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes hypervisor lahf_lm pti tsc_adjust dtherm ida arat
bugs              : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit
bogomips          : 4256.00
clflush size      : 64
cache_alignment   : 64
address sizes     : 42 bits physical, 48 bits virtual
power management  :

有些信息是显而易见的,不需要解释。型号是 Intel Xeon L5630,频率为 2.13 GHz,并且有 12 KB 的缓存。CPU 供应商名称看起来像GenuineIntelAuthenticAMD,是因为内部供应商字符串是以三个 32 位值存储的,共 12 字节。注意GenuineIntelAuthenticAMD都恰好是 12 个字符——它们被选中填充这 12 字节,以避免不同系统上对空字节的不同解释问题。

一个需要注意的字段是bogomips。它的名称暗示了每秒百万条指令MIPS)的性能指标,但这个值并不是衡量整体性能的有用指标。Linux 内核用它进行内部校准,你不应该用它来比较不同 CPU 的性能。

flags字段的信息密度最高,也是最难解读的。标志只是 Flags 寄存器中的一位,这些位的含义差异很大。有些标志甚至没有在硬件 CPU 中设置,例如,超管标志,它表示系统在虚拟机上运行(但其缺失并不意味着什么,因为并非所有超管都设置此标志)。

许多标志指示是否存在某个特性,虽然解读它们的性质和重要性需要熟悉供应商的术语。例如,来自 Raspberry Pi 输出的 Neon 特性是一组适用于 ARM CPU 的单指令多数据SIMD)指令,与 Intel 的 SSE 相当,常用于多媒体和科学应用。当不确定时,请查阅 CPU 供应商文档。

多处理器系统

市面上大多数 CPU 都包含多个 CPU 核心,而服务器主板通常有多个插槽。cpuinfo文件包含了确定 CPU 插槽和核心布局所需的所有信息,但也有一些注意事项。

你应该查看的字段以确定 CPU 插槽数量是physical id。注意,physical id值并不总是连续的,因此你不能仅仅查看最大值,而应该考虑所有存在的值。你可以通过将cpuinfo文件通过sortuniq命令管道处理来查找唯一的 ID:

$ cat /proc/cpuinfo | grep "physical id" | sort | uniq
physical id    : 0
physical id    : 2

在运行在 x86 硬件上的虚拟机中,所有超管程序呈现给它们的 CPU 都会显示得像是在不同的物理插槽中。例如,如果你有一台主机,配备单个四核 CPU,并创建了一个有两个虚拟 CPU 的虚拟机,它会看起来像是两个单核 CPU 位于不同的插槽中。

在许多非 x86 架构上,如 ARM,所有的 CPU 看起来都像是不同的物理 CPU,无论它们是不同的芯片,还是同一芯片上的不同核心。

在裸机 x86 机器上,你可以找到两个不同物理 ID 的cpuinfo条目,并进一步检查它们,以找出每个插槽中的核心数量。

请考虑这台运行 Linux 系统的笔记本电脑的cpuinfo条目:

processor         : 7
vendor_id         : GenuineIntel
cpu family        : 6
model             : 140
model name        : 11th Gen Intel(R) Core(TM) i5-1135G7 @ 2.40GHz
stepping          : 1
microcode         : 0xa4
cpu MHz           : 1029.707
cache size        : 8192 KB
physical id       : 0
siblings          : 8
core id           : 3
cpu cores         : 4
apicid            : 7
initial apicid    : 7
fpu               : yes
fpu_exception     : yes
cpuid level       : 27
wp                : yes
flags             : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts acpi mmx fxsr sse sse2 ss ht tm pbe syscall nx pdpe1gb rdtscp lm constant_tsc art arch_perfmon pebs bts rep_good nopl xtopology nonstop_tsc cpuid aperfmperf tsc_known_freq pni pclmulqdq dtes64 monitor ds_cpl vmx est tm2 ssse3 sdbg fma cx16 xtpr pdcm pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand lahf_lm abm 3dnowprefetch cpuid_fault epb cat_l2 invpcid_single cdp_l2 ssbd ibrs ibpb stibp ibrs_enhanced tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid rdt_a avx512f avx512dq rdseed adx smap avx512ifma clflushopt clwb intel_pt avx512cd sha_ni avx512bw avx512vl xsaveopt xsavec xgetbv1 xsaves split_lock_detect dtherm ida arat pln pts hwp hwp_notify hwp_act_window hwp_epp hwp_pkg_req avx512vbmi umip pku ospke avx512_vbmi2 gfni vaes vpclmulqdq avx512_vnni avx512_bitalg avx512_vpopcntdq rdpid movdiri movdir64b fsrm avx512_vp2intersect md_clear ibt flush_l1d arch_capabilities
vmx flags    : vnmi preemption_timer posted_intr invvpid ept_x_only ept_ad ept_1gb flexpriority apicv tsc_offset vtpr mtf vapic ept vpid unrestricted_guest vapic_reg vid ple pml ept_mode_based_exec tsc_scaling
bugs              : spectre_v1 spectre_v2 spec_store_bypass swapgs eibrs_pbrsb
bogomips          : 4838.40
clflush size      : 64
cache_alignment   : 64
address sizes     : 39 bits physical, 48 bits virtual
power management  :

cpu cores字段中可以看出,它是一个四核 CPU。

请注意,条目中有processor: 7,并且siblings字段设置为8。看起来系统有八个 CPU,即使它显然只有一个物理 CPU 芯片(所有条目都有physical id: 0),而每个条目中报告的核心数为4

这是由同时多线程技术(如 AMD SMT 和 Intel 超线程技术)引起的。这些技术允许单个 CPU 核心维持多个执行线程的状态,从而加速某些应用程序。如果每个核心同时支持两个线程,那么操作系统看起来就像机器的 CPU 核心数量是实际数量的两倍。因此,仅通过查看最高的/proc/cpuinfo条目编号,无法确定物理核心的数量,因此你需要更仔细地检查它。

GNU coreutils包中有一个名为nproc的工具,它的作用是输出系统中 CPU 核心的数量,如果你需要了解 CPU 的数量,它可以简化你的工作——例如,确定一个应用程序需要生成多少个工作进程或线程。然而,它没有考虑到同时多线程技术(SMT),并且如果启用了 SMT 技术,它会打印出虚拟核心的数量。如果你的应用程序需要物理核心的数量,不应依赖nproc的输出。以下是该机器上nproc输出的样子:

$ nproc
8

高级 CPU 发现工具

如你所见,读取/proc/cpuinfo文件可能是一项繁琐且复杂的任务。为此,人们创建了旨在简化这项任务的工具。该领域中最流行的工具是来自util-linux包的lscpu

它提供了一个更易读的输出:

$ lscpu
Architecture         : x86_64
CPU op-mode(s)       : 32-bit, 64-bit
Byte Order           : Little Endian
Address sizes        : 42 bits physical, 48 bits virtual
CPU(s)               : 2
On-line CPU(s) list  : 0,1
Thread(s) per core   : 1
Core(s) per socket   : 1
Socket(s)            : 2
NUMA node(s)         : 1
Vendor ID            : GenuineIntel
CPU family           : 6
Model                : 44
Model name           : Intel(R) Xeon(R) CPU            L5630  @ 2.13GHz
Stepping             : 2
CPU MHz              : 2128.000
BogoMIPS             : 4256.00
Hypervisor vendor    : VMware
Virtualization type  : full
L1d cache            : 32K
L1i cache            : 32K
L2 cache             : 256K
L3 cache             : 12288K
NUMA node0 CPU(s)    : 0,1
Flags                : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush dts mmx fxsr sse sse2 syscall nx rdtscp lm constant_tsc arch_perfmon pebs bts nopl xtopology tsc_reliable nonstop_tsc cpuid aperfmperf pni pclmulqdq ssse3 cx16 sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes hypervisor lahf_lm pti tsc_adjust dtherm ida arat

另一个优点是,你可以通过运行lscpu --json获得机器可读的 JSON 输出,并通过脚本分析它。

但请注意,lscpu没有考虑同时多线程问题(至少在版本 2.38 中是这样),如果启用了 AMD SMT 或 Intel 超线程技术,它会报告两倍的 CPU 数量。

如你所见,你可以直接从/proc文件系统获取关于 CPU 的许多信息,或者使用高层工具简化这个过程。然而,你应该始终记住一些解释的细微差别,比如核心数量可能被同时多线程技术膨胀的问题。

内存发现

发现内存的数量往往比发现 CPU 特性更为实际和重要。它是计划应用程序部署、选择交换分区大小以及估计是否需要安装更多内存时所必需的。

然而,内核提供的内存发现接口并不像 CPU 特征发现接口那样丰富。例如,单独通过内核接口无法得知系统有多少内存插槽,有多少已被使用,以及安装在这些插槽中的内存条的大小。至少在某些架构上,可以通过固件而不是内核获得这些信息,正如我们稍后在dmidecode部分中看到的那样。

此外,来自内核的信息可能会误导那些不熟悉 Linux 内核约定的初学者。首先,让我们看一下这些信息,然后讨论如何解读它们。

首先,我们将查看来自procps-ng包的free工具的输出。该工具有-m选项,可以以 MB 为单位显示内存量,而不是以 KiB 为单位,这样更容易阅读。

图 5.1 – free –m 输出

图 5.1 – free –m 输出

表面上看,它的输出似乎是自描述的。这里的总内存量大约是 16GB,其中13849MB 已被使用。然而,内核如何使用内存并非简单的问题。并非所有声明为已使用的内存都被用户应用程序使用——内核也使用内存来缓存磁盘数据,以加速输入输出操作。系统中未被任何进程使用的内存越多,Linux 用来缓存的内存就越多。当没有足够的完全未使用的内存分配给请求它的进程时,内核会驱逐一些缓存数据以释放空间。因此,即使系统运行的应用程序非常少,used列中显示的内存量也可能非常高,这可能给人一种系统内存不足的印象,实际上,应用程序仍有大量内存可用。用于磁盘缓存的内存量在buff/cache列中,而可供应用程序使用的内存量在available列中。

关于内存消耗的原始信息可以在/proc/meminfo文件中找到。free工具的输出是该文件信息的总结。

发现 PCI 设备

许多外设都连接到 PCI 总线。如今,这通常意味着 PCI Express (PCI-e) 而非更旧的 PCI 或 PCI-x 总线,但从软件的角度来看,它们都是 PCI 设备,不论它们连接的是哪一种总线。

内核通过 /sys/class/pci_bus 层次结构暴露这些信息,但手动读取这些文件将是一项非常耗时的工作,和 /proc/cpuinfo 不同,因此实际上,人们总是使用工具来处理它。最流行的工具是来自 pciutils 包的 lspci

这是一个示例输出:

$ sudo lspci
00:00.0 Host bridge: Intel Corporation 11th Gen Core Processor Host Bridge/DRAM Registers (rev 01)
00:02.0 VGA compatible controller: Intel Corporation TigerLake-LP GT2 [Iris Xe Graphics] (rev 01)
00:04.0 Signal processing controller: Intel Corporation TigerLake-LP Dynamic Tuning Processor Participant (rev 01)
00:06.0 PCI bridge: Intel Corporation 11th Gen Core Processor PCIe Controller (rev 01)
00:0a.0 Signal processing controller: Intel Corporation Tigerlake Telemetry Aggregator Driver (rev 01)
00:14.0 USB controller: Intel Corporation Tiger Lake-LP USB 3.2 Gen 2x1 xHCI Host Controller (rev 20)
00:14.2 RAM memory: Intel Corporation Tiger Lake-LP Shared SRAM (rev 20)
00:14.3 Network controller: Intel Corporation Wi-Fi 6 AX201 (rev 20)
00:15.0 Serial bus controller: Intel Corporation Tiger Lake-LP Serial IO I2C Controller #0 (rev 20)
00:15.1 Serial bus controller: Intel Corporation Tiger Lake-LP Serial IO I2C Controller #1 (rev 20)
00:16.0 Communication controller: Intel Corporation Tiger Lake-LP Management Engine Interface (rev 20)
00:17.0 SATA controller: Intel Corporation Tiger Lake-LP SATA Controller (rev 20)
00:1c.0 PCI bridge: Intel Corporation Device a0bc (rev 20)
00:1d.0 PCI bridge: Intel Corporation Tiger Lake-LP PCI Express Root Port #9 (rev 20)
00:1f.0 ISA bridge: Intel Corporation Tiger Lake-LP LPC Controller (rev 20)
00:1f.4 SMBus: Intel Corporation Tiger Lake-LP SMBus Controller (rev 20)
00:1f.5 Serial bus controller: Intel Corporation Tiger Lake-LP SPI Controller (rev 20)
01:00.0 3D controller: NVIDIA Corporation GP108M [GeForce MX330] (rev a1)
02:00.0 Non-Volatile memory controller: Samsung Electronics Co Ltd NVMe SSD Controller 980
03:00.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller (rev 15)

完全访问 PCI 总线信息仅对 root 用户开放,因此你应该使用 sudo 来运行 lspci。PCI 设备类别是标准化的,所以该工具不仅会告诉你设备的厂商和型号,还会告诉你它们的功能,如以太网控制器或 SATA 控制器。

其中一些名称可能会有些误导——例如,一个兼容 VGA 的控制器可能没有实际的 VGA 端口;如今,它几乎总是 DVI、HDMI 或 Thunderbolt/DisplayPort。

发现 USB 设备

要发现 USB 设备,可以使用来自 usbutils 包的 lsusb 工具。这个命令不需要 root 权限。以下是它的输出可能的样子:

$ lsusb
Bus 002 Device 001: ID 1d6b:0003 Linux Foundation 3.0 root hub
Bus 001 Device 005: ID 1bcf:2b98 Sunplus Innovation Technology Inc. Integrated_Webcam_HD
Bus 001 Device 036: ID 0b0e:0300 GN Netcom Jabra EVOLVE 20 MS
Bus 001 Device 035: ID 05ac:12a8 Apple, Inc. iPhone 5/5C/5S/6/SE
Bus 001 Device 006: ID 8087:0aaa Intel Corp. Bluetooth 9460/9560 Jefferson Peak (JfP)
Bus 001 Device 002: ID 047d:1020 Kensington Expert Mouse Trackball
Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub

尽管 USB 总线规范也包括标准化的设备类别,但 lsusb 默认并不会显示它们。这样做的一个原因是一个 USB 设备可能实现多个功能。例如,智能手机可以作为一个大容量存储设备(类似于 USB 闪存盘)用于常规文件传输,也可以作为数字相机,通过 图片传输协议 (PTP) 获取其中的照片。

你可以通过运行带有附加选项的 lsusb 来获取设备的详细信息。在之前的例子中,运行 lsusb -s 2 -v 会显示关于设备 002(一个 Kensington 滚珠轨迹球)的信息。

请注意,如果设备出现在 lsusb 的输出中,并不总是意味着它连接到了 USB 端口。板载设备也可能连接到 USB 总线。在前面的例子中,蓝牙控制器是笔记本电脑中的一项内部设备。

发现存储设备

存储设备可以连接到不同的总线,因此发现它们可能比较复杂。一个有用的命令是来自 util-linux 包的 lsblk(其名称代表列出块设备),可以用来发现所有看起来像存储设备的设备:

$ lsblk
NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINTS
zram0  251:0     0  959M  0 disk [SWAP]
vda    252:0     0   25G  0 disk
├─vda1 252:1     0    1M  0 part
├─vda2 252:2     0  500M  0 part /boot
├─vda3 252:3     0  100M  0 part /boot/efi
├─vda4 252:4     0    4M  0 part
└─vda5 252:5     0 24.4G  0 part /home
                                 /

需要注意的是,它会显示虚拟设备以及物理设备。例如,如果你通过虚拟循环设备挂载一个 ISO 镜像,它将作为存储设备显示——因为从用户的角度来看,它确实是一个存储设备:

$ sudo mount -t iso9660 -o ro,loop /tmp/some_image.iso /mnt/iso/
$ lsblk
NAME                                           MAJ:MIN RM   SIZE RO TYPE  MOUNTPOINTS
loop0                                            7:0     0   1.7G  1 loop  /mnt/tmp
├─loop0p1                                      259:4     0     30K  1 part
└─loop0p2                                      259:5     0   4.3M  1 part

如果你想发现物理设备,可以尝试使用同样通常叫做 lsscsi 的包中的 lsscsi 命令:

$ lsscsi
[N:0:5:1]     disk     PM991a NVMe Samsung 512GB__1           /dev/nvme0n1

一个令人困惑的部分是,原始并行 SCSI 总线的协议发现了许多新应用,并且仍然被广泛使用,尽管其原始硬件实现已经被更新的总线所替代。该工具的输出中会显示许多设备,包括 SATA、串行连接 SCSISAS)和 NVMe 驱动器,以及 USB 大容量存储设备。

相反,像 KVM 和 Xen 虚拟机中的 VirtIO 驱动器这样的半虚拟设备将不会出现在 lsscsi 输出中。总的来说,您可能需要依赖 lsusblsscsilsblk 的组合来获取完整的情况。一个优点是,这些命令都不需要 root 权限。关于 lsblk 的另一个好处是,您可以运行 lsblk --json 来获取机器可读的输出,并将其加载到脚本中。

高级发现工具

除了这些特定于某个总线或设备类型的工具外,还有一些工具可以帮助您发现系统中所有存在的硬件。

dmidecode

在 x86 系统上,您可以使用名为 dmidecode 的程序通过桌面管理接口(因此得名)从固件(BIOS/UEFI)中检索和查看信息。该接口也称为 SMBIOS。由于它是特定于 x86 PC 固件标准的,因此在其他架构的机器上(如 ARM 或 MIPS)无法使用,但在 x86 笔记本电脑、工作站和服务器上,它可以帮助您发现无法通过其他方式获得的信息,例如 RAM 插槽的数量。

一个缺点是,您需要 root 权限才能运行它。另一个需要注意的事项是,它会产生大量输出。由于完整的输出内容太长,不可能在书中重现,但作为参考,下面是它的输出开头部分(在此案例中是在 VMware 虚拟机中):

$ sudo dmidecode
# dmidecode 3.2
Getting SMBIOS data from sysfs.
SMBIOS 2.4 present.
556 structures occupying 27938 bytes.
Table at 0x000E0010.
Handle 0x0000, DMI type 0, 24 bytes
BIOS Information
    Vendor: Phoenix Technologies LTD
    Version: 6.00
    Release Date: 09/21/2015
    Address: 0xE99E0
    Runtime Size: 91680 bytes
    ROM Size: 64 kB
    Characteristics:
         ISA is supported
         PCI is supported
         PC Card (PCMCIA) is supported
         PNP is supported
         APM is supported
         BIOS is upgradeable
         BIOS shadowing is allowed
         ESCD support is available
         Boot from CD is supported
         Selectable boot is supported
         EDD is supported
         Print screen service is supported (int 5h)
         8042 keyboard services are supported (int 9h)
         Serial services are supported (int 14h)
         Printer services are supported (int 17h)
         CGA/mono video services are supported (int 10h)
         ACPI is supported
         Smart battery is supported
         BIOS boot specification is supported
         Function key-initiated network boot is supported
         Targeted content distribution is supported
    BIOS Revision: 4.6
    Firmware Revision: 0.0
Handle 0x0001, DMI type 1, 27 bytes
System Information
    Manufacturer: VMware, Inc.
    Product Name: VMware Virtual Platform
    Version: None
    Serial Number: VMware-56 4d 47 fd 6f 6f 20 30-f8 85 d6 08 bf 09 fe b5
    UUID: 564d47fd-6f6f-2030-f885-d608bf09feb5
    Wake-up Type: Power Switch
    SKU Number: Not Specified
    Family: Not Specified
Handle 0x0002, DMI type 2, 15 bytes
Base Board Information
    Manufacturer: Intel Corporation
    Product Name: 440BX Desktop Reference Platform
    Version: None
    Serial Number: None
    Asset Tag: Not Specified
    Features: None
    Location In Chassis: Not Specified
    Chassis Handle: 0x0000
    Type: Unknown
    Contained Object Handles: 0

正如您在此输出中看到的,机箱和主板都有精确的型号名称。与例如 USB 这样的设备不同,USB 协议本身包含允许设备向主机报告其名称和功能的特性,而查询主板名称没有跨平台的方式,但至少在大多数 x86 机器上,这些信息可以通过 DMI 接口获得。

lshw

另一个提供全面硬件报告的工具是 lshw(LiSt HardWare)。与 dmidecode 类似,它也可以使用 DMI 接口作为信息来源,但它尝试支持更多的硬件平台及其平台特定的硬件发现接口。

一个缺点是,流行的发行版默认没有安装该工具,因此您需要手动从软件库中安装它。它还需要 root 权限才能运行,因为它依赖于特权访问 DMI。

这是一个在 DigitalOcean 云平台的 KVM 虚拟机中运行的样本输出。它的输出也非常大,因此我们只包括其开头部分:

$ sudo lshw
my-host
     description: Computer
     product: Droplet
     vendor: DigitalOcean
     version: 20171212
     serial: 293265963
     width: 64 bits
     capabilities: smbios-2.4 dmi-2.4 vsyscall32
     configuration: boot=normal family=DigitalOcean_Droplet uuid=AE220510-4AD6-4B66-B49F-9D103C60BA5A
  *-core
        description: Motherboard
        physical id: 0
      *-firmware
           description: BIOS
           vendor: DigitalOcean
           physical id: 0
           version: 20171212
           date: 12/12/2017
           size: 96KiB
      *-cpu
           description: CPU
           product: DO-Regular
           vendor: Intel Corp.
           physical id: 401
           bus info: cpu@0
           slot: CPU 1
           size: 2GHz
           capacity: 2GHz
           width: 64 bits
           capabilities: fpu fpu_exception wp vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx rdtscp x86-64 constant_tsc arch_perfmon rep_good nopl cpuid tsc_known_freq pni pclmulqdq vmx ssse3 fma cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm abm cpuid_fault invpcid_single pti ssbd ibrs ibpb tpr_shadow vnmi flexpriority ept vpid ept_ad fsgsbase tsc_adjust bmi1 avx2 smep bmi2 erms invpcid xsaveopt md_clear
      *-memory
           description: System Memory
           physical id: 1000
           size: 1GiB
           capacity: 1GiB
           capabilities: ecc
           configuration: errordetection=multi-bit-ecc
         *-bank
              description: DIMM RAM
              physical id: 0
              slot: DIMM 0
              size: 1GiB
              width: 64 bits
      *-pci
           description: Host bridge
           product: 440FX - 82441FX PMC [Natoma]
           vendor: Intel Corporation
           physical id: 100
           bus info: pci@0000:00:00.0
           version: 02
           width: 32 bits
           clock: 33MHz
         *-isa
              description: ISA bridge
              product: 82371SB PIIX3 ISA [Natoma/Triton II]
              vendor: Intel Corporation
              physical id: 1
              bus info: pci@0000:00:01.0
              version: 00
              width: 32 bits
              clock: 33MHz
              capabilities: isa
              configuration: latency=0
         *-ide
              description: IDE interface
              product: 82371SB PIIX3 IDE [Natoma/Triton II]
              vendor: Intel Corporation
              physical id: 1.1
              bus info: pci@0000:00:01.1
              version: 00
              width: 32 bits
              clock: 33MHz
              capabilities: ide isa_compat_mode bus_master
              configuration: driver=ata_piix latency=0
              resources: irq:0 ioport:1f0(size=8) ioport:3f6 ioport:170(size=8) ioport:376 ioport:c160(size=16)
         *-usb
              description: USB controller
              product: 82371SB PIIX3 USB [Natoma/Triton II]
              vendor: Intel Corporation
              physical id: 1.2
              bus info: pci@0000:00:01.2
              version: 01
              width: 32 bits
              clock: 33MHz
              capabilities: uhci bus_master
              configuration: driver=uhci_hcd latency=0
              resources: irq:11 ioport:c0c0(size=32)
            *-usbhost
                 product: UHCI Host Controller
                 vendor: Linux 5.16.18-200.fc35.x86_64 uhci_hcd
                 physical id: 1
                 bus info: usb@1
                 logical name: usb1
                 version: 5.16
                 capabilities: usb-1.10
                 configuration: driver=hub slots=2 speed=12Mbit/s

如你所见,输出包括一些只能从 DMI 获取的信息(例如主板型号),以及我们在 lsusb 和其他工具的输出中看到的信息。从这个意义上讲,如果你想要一个详细和完整的所有安装硬件概览,lshw 可以替代它们。

总结

在本章中,我们了解到 Linux 内核可以收集有关系统硬件的大量信息并提供给用户。在紧急情况下,可以直接从内核中检索所有这些信息,方法是使用 /proc/sys 文件系统,并读取如 /proc/cpuinfo 等文件。

然而,高级工具如 lscpulsscsilsusb 可以使检索和分析信息变得更加容易。

还有一些平台特定的工具,例如用于 x86 PC 的 dmidecode,可以帮助你获取其他方法无法获取的更详细信息,例如内存插槽的数量。

在下一章,我们将学习如何配置基本系统设置。

第二部分:配置和修改 Linux 系统

本书的第二部分专注于管理独立的 Linux 系统。一旦你熟悉了与系统的交互,下一步就是学习如何配置它。在本部分,你将学习如何配置基本设置,例如系统主机名,如何创建和管理用户和组,如何从软件包文件或远程仓库安装附加软件,以及如何设置和调试网络连接与存储设备。

本部分包含以下章节:

  • 第六章基本系统设置

  • 第七章用户与组管理

  • 第八章软件安装与软件包仓库

  • 第九章网络配置与故障排除

  • 第十章存储管理

第六章:基本系统设置

Linux 是一个高度可定制的操作系统,它提供了大量的配置选项,允许用户根据其特定需求定制系统。在本章中,我们将探讨 Linux 中一些基本系统配置设置以及如何定制它们以改善系统性能、安全性和可用性。

在对配置文件进行任何更改之前,您应始终备份。在复制时,附加 .bak 扩展名以便知道这是一个备份副本。

在修改这些文件时,几乎可以肯定会出现错误。在进行任何更改之前,备份任何配置文件非常重要。

由于不可能涵盖 Linux 中的每个配置文件,我们将专注于最常见的配置而非:

  • hosts 配置文件

  • resolv 配置文件

  • network-scripts 配置文件

  • dhclient 配置文件

  • sysctl 配置文件

基本设置概述

Linux 具有各种基本设置,您可以配置以自定义系统的行为。这些设置通常位于配置文件中,它们可以影响操作系统的各个方面:

  • 在大多数现代 Linux 发行版中,timedatectl 命令。此命令允许用户设置系统的时区,日期和时间。

  • hostnamectl 命令。此命令允许用户设置主机名,静态 IP 地址和域名。

  • useraddgroupadd 命令用于分别创建新用户和组。usermodgroupmod 命令用于修改现有用户和组。

  • ifconfig 命令可用于配置网络接口,而 ip 命令可用于管理 IP 地址、路由和网络设备。NetworkManager 服务是 Linux 中管理网络连接和设置的常用工具。

  • 系统安全配置:Linux 以其强大的安全功能而闻名,许多系统配置设置都专注于增强系统安全性。一些关键的安全配置设置包括配置防火墙,管理用户权限,配置 安全增强 Linux (SELinux),以及设置系统审计和监控。

  • sysctl 命令或通过修改 /etc/sysctl.conf 文件。

例如,要增加系统允许的最大打开文件数,可以设置 fs.file-max 参数:

# Increase the maximum number of open files
sysctl -w fs.file-max=100000
  • CPU 缩放:Linux 提供了控制 CPU 频率和节能功能的 CPU 缩放机制。调整 CPU 缩放可以帮助在功耗效率和性能之间取得平衡。

例如,要将 CPU 管理器设置为性能模式(始终最大频率),可以使用 cpufreq-set 命令(可能需要安装 cpufrequtils):

cpufreq-set -r -g performance
  • I/O 调度器:Linux 内核提供了多种 I/O 调度器,每种调度器都针对特定的存储设备和访问模式设计。选择适合您存储的调度器可以提升 I/O 性能。

例如,要为特定的块设备(如 SSD)设置 I/O 调度器,可以使用以下命令:

echo "deadline" > /sys/block/sdX/queue/scheduler
  • 交换配置:当物理内存(RAM)已满时,会使用交换空间。然而,过度交换可能会显著影响性能。调整交换行为(swapiness)可以控制内存交换的倾向。

例如,要减少交换空间的使用(减少交换的激烈程度),可以在 /etc/sysctl.conf 中设置一个较低的值(例如,10):

vm.swappiness=10
  • 文件系统挂载选项:文件系统的挂载选项会根据使用情况影响性能。有些选项可以优化读写操作或增强数据安全性。

作为示例,对于一个 SSD 挂载的文件系统,您可以设置 noatime 选项,以避免更新访问时间戳,从而提高读取性能:

UUID=YOUR_UUID /mnt/ssd ext4 defaults,noatime 0 2
  • 网络设置:微调网络参数可以提升网络性能并减少延迟。

例如,要增加 TCP 缓冲区大小,可以在 /etc/sysctl.conf 中设置如下:

net.core.rmem_max = 16777216
net.core.wmem_max = 16777216

系统性能配置是一个迭代和适应的过程,需要深入了解系统的组件和工作负载。通过进行明智且有针对性的调整,系统管理员可以创建高效、稳定且响应迅速的系统,以满足用户和应用程序的需求。

注意

在进行更改之前,请记得备份配置文件,并在修改后彻底测试系统,以确保实现期望的性能提升。最佳设置可能会根据具体的硬件、工作负载和用户需求而有所不同。定期的监控和性能分析可以帮助您识别性能瓶颈,并进一步优化配置。

hosts 配置文件

Linux hosts 文件是一个简单的文本文件,用于将主机名映射到 IP 地址。它位于 /etc 目录中。该文件包含 IP 地址及其对应的主机名。当用户尝试访问一个主机名时,系统会检查 hosts 文件,以确定与该主机名关联的 IP 地址。

hosts 文件首先用于进行映射。这在某些情况下很有用,比如需要将特定的主机名重定向到另一个 IP 地址,或者在网站上线之前进行测试。

hosts 文件由多个数据列组成,列与列之间由空格分隔。第一列包含 IP 地址,第二列包含主机名。可以使用额外的列来指定主机名的别名。例如,hosts 文件中的以下一行将 192.168.1.80 的 IP 地址映射到 centos 主机名,并将 centos8 设置为别名:

图 6.1 – hosts 文件的内容

图 6.1 – hosts 文件的内容

例如,如果你想要通过 CentOS (192.168.1.80) 进行 SSH 连接,你无需记住 IP 地址,只需要知道你分配给该服务器的名称即可。

对于 ping 命令也是适用的。你只需记住名称,而不是 IP 地址。当使用 ping 时,这是一个示例:

ping centos8

你可以使用文本编辑器(如 nano 或 Vim)编辑 hosts 文件。需要注意的是,hosts 文件需要 root 权限才能编辑。因此,在编辑文件之前,你必须使用 sudo 命令或切换到 root 用户。

除了将主机名映射到 IP 地址外,hosts 文件还可以用于阻止访问特定网站。通过将主机名映射到回环地址(127.0.0.1),你可以有效地阻止访问该网站。例如,hosts 文件中的以下行将阻止访问 example.com 网站:

127.0.0.1 example.com

总结来说,Linux hosts 文件是一个简单而强大的工具,用于将主机名映射到 IP 地址并覆盖 DNS 解析过程。它可以用于将流量重定向到不同的 IP 地址、在网站上线之前进行测试以及阻止访问特定网站。了解如何使用 hosts 文件有助于你排查网络问题并提高系统的安全性。

resolv 配置文件

resolv.conf 文件是 Linux 系统中一个重要的配置文件,用于配置 DNS 解析设置。DNS 负责将人类可读的域名(例如 www.example.com)转换为计算机能够理解的 IP 地址。这一转换对于访问网站、服务和其他网络资源至关重要。该文件位于 /etc 目录下,包含系统用来解析域名的 DNS 服务器信息。

resolv.conf 文件被各种网络程序使用,包括系统解析库、Web 浏览器和电子邮件客户端。当用户尝试访问一个网站或使用主机名连接到远程服务器时,系统会使用 resolv.conf 文件来查找相应的 IP 地址。

resolv.conf 文件由多个指令组成,这些指令定义了要使用的 DNS 服务器以及要附加到主机名的搜索域。以下是 resolv.conf 文件中最常用的指令:

  • nameserver:此指令指定用于名称解析的 DNS 服务器的 IP 地址。你可以在文件中指定多个 nameserver,系统将按列表中的顺序使用它们。例如,resolv.conf 文件中的以下行指定了 IP 地址为 192.168.1.1 的 DNS 服务器:

图 6.2 – resolv.conf 文件内容

图 6.2 – resolv.conf 文件内容

  • search:该指令指定要附加到不完全限定的主机名的搜索域。例如,如果搜索域设置为 example.com,而用户尝试访问 www 主机名,则系统将尝试解析 www.example.com。你可以在文件中指定多个搜索域,系统将按列出的顺序使用它们。例如,resolv.conf 文件中的以下行指定了搜索域为 example.com

    search example.com
    
  • options:该指令指定用于名称解析的附加选项,如超时和重试值。例如,resolv.conf 文件中的以下行指定了 5 秒的超时和 2 次重试:

    options timeout:5 retries:2
    

你可以使用文本编辑器(如 nano 或 Vim)编辑 resolv.conf 文件。编辑时需要 root 权限。需要注意的是,resolv.conf 文件可能会被各种工具自动生成,如 动态主机配置协议DHCP)客户端或 NetworkManager 服务。因此,对该文件的任何更改可能会被这些工具覆盖。

总之,Linux 的 resolv.conf 文件是一个重要的配置文件,用于定义要使用的 DNS 服务器以及附加到主机名的搜索域。了解如何配置 resolv.conf 文件可以帮助你排查网络问题,并提高系统的性能和安全性。

网络脚本配置文件

在 Linux 中,network-scripts 用于配置网络接口。这些脚本位于 /etc/sysconfig/network-scripts 目录下,并定义每个接口的网络设置,如 IP 地址、网络掩码、网关和 DNS 服务器(网络文件配置路径特定于 CentOS)。

network-scripts 是用 Bash 编写的,并包含多个配置文件,每个文件对应一个特定的网络接口。最常用的配置文件是用于以太网接口的 ifcfg-ethX 和用于无线接口的 ifcfg-wlanX,其中 X 为接口编号。

ifcfg-ethX 配置文件包含以下参数:

  • DEVICE:该参数指定网络接口的名称,例如 DEVICE=eth0

  • BOOTPROTO:该参数指定接口是否使用 DHCP 或静态 IP 地址。如果使用 DHCP,则该参数的值设置为 dhcp。如果使用静态 IP 地址,则该参数的值设置为 static

  • IPADDR:该参数指定接口的 IP 地址。

  • NETMASK:该参数指定接口的网络掩码。

  • GATEWAY:该参数指定接口的默认网关。

  • DNS1DNS2DNS3:这些参数指定用于名称解析的 DNS 服务器的 IP 地址。

ifcfg-wlanX 配置文件包含类似的参数,但还包括一些额外的无线设置参数,如 ESSID 和加密方式。

你可以使用文本编辑器(如 nano 或 Vim)编辑network-scripts配置文件。需要注意的是,配置文件所做的更改不会生效,直到网络服务被重启。你可以通过运行service network restartsystemctl restart network命令来重启网络服务,具体取决于你使用的 Linux 发行版。

除了配置文件,network-scripts目录还包含在启动或停止网络服务时执行的脚本。这些脚本可用于执行额外的网络配置任务,例如设置虚拟接口或配置网络桥接。

总结来说,Linux 的network-scripts配置文件用于配置网络接口,并定义网络设置,如 IP 地址、子网掩码、网关和 DNS 服务器。了解如何配置这些文件可以帮助你排查网络问题,并提高系统的性能和安全性。

来自 CentOS 的network-scripts配置文件示例如下,该文件是自动生成的:

图 6.3 – 网络配置文件

图 6.3 – 网络配置文件

如你所见,DHCP 已启用,并且没有静态预留,网络卡的名称是enp0s31f6

如果我们想进行静态 IP 预留,应该像这样使用:

HWADDR=$SOMETHING
TYPE=Ethernet
BOOTPROTO=none // turns off DHCP
IPADDR=192.168.1.121 // set your IP
PREFIX=24 // subnet mask
GATEWAY=192.168.1.1
DNS1=1.1.1.2 // set your own DNS
DNS2=8.8.8.8
DNS3=8.8.4.4
DEFROUTE=yes
IPV4_FAILURE_FATAL=no
NAME=enp0s31f6
DEVICE=enp0s31f6
ONBOOT=yes // starts on boot

在 Ubuntu 17 中,网络配置通过 Netplan 完成,这是一个基于 YAML 的框架。你可以在这里进行所有的 DNS、网关、子网掩码和 IP 配置。

/etc/netplan是网络配置文件所在的位置。

使用 Netplan 配置网络接口的示例配置文件如下所示:

图 6.4 – Ubuntu Netplan 配置文件

图 6.4 – Ubuntu Netplan 配置文件

要使更改生效,请使用netplan apply命令重启网络。

dhclient 配置文件

Linux 中的dhclient配置文件位于/etc/dhcp/dhclient.conf。该文件由dhclient程序用于配置 Linux 系统上的 DHCP 客户端设置。

dhclient.conf文件包含各种配置选项,控制 DHCP 客户端与 DHCP 服务器的交互。以下是dhclient.conf文件中可以设置的一些常见配置选项:

  • dhclient.conf文件允许你为各种 DHCP 请求设置超时值。你可以为DHCPDISCOVERDHCPREQUESTDHCPACK消息设置超时值。

  • 租约时间:你可以设置 DHCP 客户端可以使用分配的 IP 地址的时间长度。

  • dhclient.conf文件允许你指定 DHCP 客户端应该使用的 DNS 服务器。

  • 主机名:你可以设置 DHCP 客户端在请求 IP 地址时使用的主机名。

  • 客户端标识符:你可以指定 DHCP 客户端在与 DHCP 服务器通信时使用的客户端标识符。

  • dhclient.conf 文件还允许你设置特定厂商的选项,用于配置各种网络设置。

需要注意的是,dhclient.conf 文件的语法可能会有所不同,这取决于所使用的 dhclient 版本。最好查阅你所使用的 dhclient 版本的文档,以确保使用正确的语法。

以下是 Ubuntu 中默认的 dhclient.conf 文件的样子:

图 6.5 – DHCP 配置文件

图 6.5 – DHCP 配置文件

总结来说,dhclient.conf 配置文件在管理系统的 DHCP 客户端行为中起着至关重要的作用。通过自定义 dhclient.conf 文件,管理员可以微调各种设置和选项,确保最佳的网络连接性和地址分配。

正确配置 dhclient.conf 可以显著提高网络的稳定性、安全性和性能。例如,管理员可以优先选择某些 DNS 服务器以加快解析速度,强制设置特定的租期以高效管理 IP 地址分配,并在主 DHCP 服务器不可用时设置备用机制。

sysctl 配置文件

Linux sysctl 配置文件位于 /etc/sysctl.conf。该文件用于在运行时配置内核参数。sysctl.conf 文件包含一组表示各种内核参数的键值对。

sysctl.conf 文件分为多个部分,每个部分包含一组键值对,代表特定的内核参数组。每个键值对由内核参数的名称及其值组成。该值可以是数字值或字符串。

以下是一些可以使用 sysctl.conf 文件配置的内核参数示例:

  • net.ipv4.ip_forward:该参数启用或禁用 IP 转发。值为 1 时启用 IP 转发,值为 0 时禁用它。

  • net.ipv4.tcp_syncookies:该参数启用或禁用 TCP SYN Cookies。值为 1 时启用 TCP SYN Cookies,值为 0 时禁用它们。

  • kernel.core_pattern:该参数指定用于命名 core dump 文件的模式。默认模式为 core,但你可以指定不同的模式。

  • kernel.shmmax:该参数指定共享内存段的最大大小(以字节为单位)。

  • vm.swappiness:该参数控制内核交换未使用的内存页到磁盘的程度。较高的值意味着内核会更积极地交换内存页,而较低的值则意味着内核交换内存页的行为会更为保守。

要应用对 sysctl.conf 文件所做的更改,您可以使用带有 -p 选项的 sysctl 命令,这将从文件中加载设置到内核中。需要注意的是,有些内核参数只能在启动时设置,不能通过 sysctl.conf 文件在运行时更改。

根据您的系统和内核,sysctl.conf 文件可能被称为或位于以下位置:

  • /etc/sysctl.d/*.conf

  • /run/sysctl.d/*.conf

  • /usr/local/lib/sysctl.d/*.conf

  • /usr/lib/sysctl.d/*.conf

  • /lib/sysctl.d/*.conf

  • /etc/sysctl.conf

Linux 通常会按照这个顺序读取这些文件。在其他文件夹中具有相同名称的剩余文件在它第一次发现具有有效条目的真实文件时将被忽略。

如果你不知道自己在做什么,那么玩弄 etc/sysctl.conf 文件可能会造成严重后果。您可以通过运行 sysctl --system 命令观察操作系统尝试加载的命令和文件以及它们的顺序。但请谨慎使用这个命令。这个命令会在您的操作系统上进行实际加载和操作,因为它不是一个干运行的命令,如果您不确定它是否应该执行操作,您将面临误配置设置的风险。

摘要

总之,Linux 提供了大量的配置设置,可以用来定制系统以满足特定的需求。了解如何配置基本的系统设置,比如系统时间、主机名、用户和组设置、网络设置、安全设置和性能设置,可以帮助提高系统的性能、安全性和可用性。通过定制这些设置,用户可以创建一个符合其特定要求并提高其生产力的 Linux 系统。

在下一章节中,我们将讨论用户和组管理。

第七章:用户和组管理

如果你管理 Linux 服务器,那么这些服务器的用户既可能是你最大的资产,也可能是你最大的头痛。你将在职业生涯中负责添加大量新用户、管理他们的密码、在他们离开组织时删除他们的账户,以及在网络上提供和撤销对资源的访问权限。即使是在你是唯一用户的服务器上,你仍然需要管理用户账户。因为即使是系统进程,也是在用户身份下运行的。如果你想高效管理 Linux 服务器,你还需要了解如何管理权限、实施密码策略,并限制能够执行管理员命令的人员数量。在本章中,我们将详细讲解这些内容,以帮助你扎实掌握如何管理用户及其消耗的资源。

在本章中,我们将学习如何执行以下操作:

  • 创建用户和组

  • 修改用户和组

  • 删除用户和组

  • 列出所有用户

  • 将用户添加到一个组

  • 将用户从一个组中移除

账户/组管理概述

作为一个多用户操作系统,Linux 允许多个用户同时在一台机器上登录和工作。请注意,绝不应让用户共享同一账户的登录信息。最好为需要访问机器的每个用户创建独立账户。

访问特定的系统资源,如目录和文件,可能需要由两个或更多用户共享。我们可以通过 Linux 的用户和组管理功能来实现这两个目标。

普通用户和根用户(超级用户)是 Linux 系统中的两类用户。

Linux 操作系统的一个基本组件是用户和组账户的管理。用户和组账户的自定义权限是通过每个用户使用不同的凭证登录操作系统来维护的。添加新用户需要特定的权限(超级用户权限);其他用户或组管理操作,如账户删除、账户更新以及组的添加和删除,也需要相同的权限。

这些操作通过以下命令来执行:

  • adduser: 向系统添加一个用户

  • userdel: 删除一个用户账户及相关文件

  • addgroup: 向系统添加一个组

  • delgroup: 从系统中删除一个组

  • usermod: 修改用户账户

  • chage: 此命令用于更改密码过期时间并查看用户密码过期信息

  • passwd: 此命令用于创建或更改用户账户的密码

  • sudo: 以其他用户身份运行一个或多个命令(通常通过运行 sudo su <用户名> 命令来获得超级用户权限)

与这些操作相关的文件包括/etc/passwd(用户信息)、/etc/shadow(加密密码)、/etc/group(组信息)和/etc/sudoerssudo配置)。

超级用户访问可以通过使用su命令切换为 root 用户,或者使用sudo su命令获取 root 权限来实现。

这些是用户账户信息的默认位置:

  • 用户账户属性:/etc/passwd

  • 用户密码属性:/etc/shadow

创建用户时,会同时创建一个具有相同用户名的组。每个用户都有一个主目录;对于 root 用户,它位于/root;对于其他所有用户,它位于/home/

/etc/passwd文件包含所有账户的详细信息。该文件具有以下结构,并为每个系统用户账户包含一个记录(字段由冒号分隔):

<username>:<x>:<UID>:<GID>:<Comment>:<Home directory>:<Default shell>

让我们仔细查看前面的代码:

  • <username><Comment>字段是不言自明的

  • x出现在第二个字段,表示在/etc/shadow中的加密密码,这是登录<username>所必需的

  • <UID><GID>条目包含整数,分别反映<usernameprimary>组 ID 和用户 ID

  • <Home directory> 显示当前用户的主目录的完整路径

  • 当用户登录系统时,<Default shell>是分配给他们的 shell

现在我们已经概述了用于管理账户/组的命令,让我们开始操作它们。

如何添加一个新账户

有两个命令,adduseruseradd,可以在 Linux 中用来创建新用户。这两个命令实现相同的功能(虽然方式不同),并且它们的名称非常相似,刚开始可能会让人感到困惑。我将首先介绍useradd命令,然后再说明adduser是如何不同的。你可能更喜欢后者,但我们稍后会讨论这个问题。

使用 useradd

如果你没有 root 权限,则需要sudo权限才能添加账户。这必须在/etc/sudoers中定义。

首先,这是一个可以使用的useradd命令的示例:

sudo  useradd -d /home/packt -m packt

我通过使用此命令设置了一个名为packt的新用户。我确认希望为该用户创建主目录,通过使用-d选项,并在接下来的命令中指定/home/packt作为用户的主目录。如果我没有使用-m参数,系统将不知道在过程中我希望创建主目录;那时,我就必须手动创建目录。最后,我宣布了我将用于新用户的用户名(在这种情况下是packt)。随着我们继续阅读本书,我们会遇到一些需要根权限才能执行的命令。前面的命令就是一个例子。我将为需要特定权限的命令加上sudo前缀,因为这是执行该命令的标准方式。当你看到这个前缀时,它意味着该命令只能通过根用户权限执行。你也可以通过以 root 身份登录(如果启用了 root 访问)或切换到 root 模式来执行这些命令。这两种方式都可以。然而,强烈建议你不要使用 root 账户登录,而是使用sudo。我之前已经解释过了。

以下命令可以用来为新创建的packt账户设置密码:

~ $sudo passwd packt
Changing password for user packt.
New password:
Retype new password:
passwd: all authentication tokens updated successfully.

另一个命令是adduser,它会自动为你创建一切,包括主目录和组,并会要求你设置密码。

使用 adduser

adduser命令是建立用户账户的另一种选择,正如前面提到的那样。当你第一次使用此命令时,它带来的不同之处(以及提供的便利性)应该会立即显现。执行adduser命令,并提供你要创建的新用户的用户名。试试看吧:

~ $sudo adduser packt2
Adding user `packt2' ...
Adding new group `packt2' (1004) ...
Adding new user `packt2' (1003) with group `packt2' ...
The home directory `/home/packt2' already exists.  Not copying from `/etc/skel'.
New password:
Retype new password:
passwd: password updated successfully
Changing the user information for packt2
Enter the new value, or press ENTER for the default
        Full Name []:
        Room Number []:
        Work Phone []:
        Home Phone []:
        Other []:
Is the information correct? [Y/n]

从结果来看,adduser命令为我们做了大量工作。该命令将文件从/etc/skel复制到新用户的主目录,并默认将用户的主目录设置为/home/packt2。用户账户也被分配了下一个可用的1004。事实上,adduseruseradd命令都从/etc/skel目录复制文件;然而,adduser命令在执行的任务上更加详细。

如何删除一个账户

当用户不再需要访问系统时,删除或禁用他们的账户是非常必要的,因为未管理的账户往往会成为安全隐患。这可以通过登录到系统的管理面板并选择userdel命令来删除用户账户。然而,在删除账户之前,有一个关键问题你应该自己问自己。你(或其他人)是否预计在某种程度上需要访问该用户的文件?绝大多数公司都有数据保存政策,规定了用户离职时如何处理其数据。这些文件有时会被复制并保存到档案中以供长期保存。经理、同事或新员工常常需要访问前用户的文件,可能是为了继续前用户在某个项目中的工作。开始管理用户之前,了解这项政策非常重要。如果你还没有一个明确描述用户离职时文件保留要求的政策,强烈建议你与管理团队合作制定这样的政策。使用userdel命令时,用户的主目录内容不会被删除,因为这种行为不是默认的。在这一节中,我们将通过执行以下命令将packt2从系统中删除:

sudo userdel packt2

在这种情况下,packt2用户的主文件夹将保留。

如果我们希望一次性删除该账户的主文件夹,我们需要使用–r参数:

sudo userdel –r packt2

在删除用户账户之前,请记得检查其主文件夹中的文件是否需要。删除后,如果没有备份,这些文件将无法恢复:

su - packt2
su: user packt2 does not exist

总之,在 Linux 中删除用户账户涉及备份数据、终止进程、将用户从组中移除、删除主目录、更新系统文件,并进行最终清理。通过遵循这些步骤,你可以在有效管理相关文件和权限的同时,安全地删除一个账户。

理解/etc/sudoers 文件

在这一节中,我们将看看如何使用之前创建的普通用户账户来进行用户管理操作。

我们必须在/etc/sudoers中为packt做一个特殊的权限条目,以便赋予它特殊的访问权限:

packt ALL=(ALL) ALL

让我们分析这一行的语法:

  • 首先,我们声明该规则适用于哪个用户(packt)。

  • 如果第一个ALL存在,那么所有使用相同/etc/sudoers文件的主机都将受到此规则的约束。由于不同机器之间不再共享同一文件,因此此术语现在指代当前主机。

  • 接下来,(ALL) ALL告知我们,任何用户都可以作为packt用户执行任何命令。从功能上讲,这与(root) ALL类似。

使用组来管理权限非常重要,因为这样会使工作变得更加轻松。想象一下,如果你只需将一个用户从sudo组中移除,而不是从 100 个不同的位置逐一删除该用户,那该有多简单。

切换用户

我们现在已经准备好开始使用packt帐户执行用户管理任务。要执行此操作,请使用su命令切换到该帐户。值得注意的是,如果你使用的是 CentOS 或类似的操作系统,今后你无需再使用 root 帐户:

su -l packt

我们可以通过使用sudo命令检查我们新创建的packt帐户的权限。我们来创建另一个名为packtdemo的帐户,好吗?

~ $sudo adduser packtdemo
Adding user `packtdemo' ...
Adding new group `packtdemo' (1005) ...
Adding new user `packtdemo' (1004) with group `packtdemo' ...
The home directory `/home/packtdemo' already exists.  Not copying from `/etc/skel'.
New password:
Retype new password:
passwd: password updated successfully
Changing the user information for packtdemo
Enter the new value, or press ENTER for the default
Full Name []:
Room Number []:
Work Phone []:
Home Phone []:
Other []:
Is the information correct? [Y/n]

对用户的主文件夹、默认 shell 以及为用户帐户添加描述的更改都可以通过usermod命令来完成。

最初,/etc/passwd文件看起来是这样的:

~ $grep packtdemo /etc/passwd
packtdemo:x:1004:1005:,,,:/home/packtdemo:/bin/bash

让我们尝试添加描述并更改 shell:

~ $sudo usermod --comment "Demo account for packt" --shell /bin/sh packtdemo
~ $grep packtdemo /etc/passwd
packtdemo:x:1004:1005:Demo account for packt:/home/packtdemo:/bin/sh

在与支持人员合作时,切换到另一个用户帐户通常非常有益(尤其是在排除权限问题时)。举个例子,假设有用户向你抱怨他们无法访问特定目录中的文件,或者无法执行某个命令。在这种情况下,你可以尝试通过登录到服务器,切换到遇到问题的用户帐户,然后尝试访问相关文件来复制这个问题。通过这种方式,你不仅可以亲自查看他们的问题,还能在向他们反馈之前测试你的解决方案是否解决了他们的问题。

你可以通过运行sudo susu –命令切换到 root 用户,或者直接使用su命令。

仅使用su命令可以切换到另一个用户,同时保持当前环境,而su –则模拟目标用户的完整登录环境,包括设置他们的主目录、环境变量,并启动一个新的登录 shell。选择这两个命令之一取决于你作为被切换用户需要完成的具体要求或任务。

管理帐户密码

如果你记得的话,passwd命令可以让我们更改当前登录系统的用户密码。此外,我们还可以通过在以 root 身份登录的情况下运行passwd命令并提供用户名来更改任何用户帐户的密码。不过,这只是该命令的功能之一。

锁定/解锁用户帐户

锁定和解锁用户帐户是passwd命令的一项功能,我们尚未讨论过。你可以使用这个命令来执行这两项操作。有很多不同的使用场景需要实现类似的功能。例如,如果某人将长期外出,你可能想锁定他们的帐户,以便在这段时间内其他用户无法访问。

当您想要锁定帐户时,使用-l选项。例如,要锁定packt用户的帐户,我们使用以下命令:

sudo passwd -l packt

按照以下方式解锁:

sudo passwd –u packt

设置密码过期

接下来,我们将讨论如何实际执行设置密码过期的步骤。

更具体地说,chage命令使我们能够做到这一点。我们可以使用chage来更改用户密码的有效期限,它还提供了一种比直接读取/etc/shadow文件查看当前密码过期信息更为用户友好的替代方法。通过给定用户名并使用chage命令的-l选项,我们可以查看相关信息:

sudo chage -l packt

并不需要以root身份或使用sudo命令运行chage。查看自己登录的密码过期信息不需要提升权限。但是,如果要访问除自己之外的任何用户帐户的信息,则需要使用sudo

~ $sudo chage -l packt

图 7.1 – 显示用户帐户的密码相关信息和过期策略

图 7.1 – 显示用户帐户的密码相关信息和过期策略

在输出中,我们能够查看诸如密码过期日期、在密码必须更改之前可以经过的最大天数等值。基本上,这与保存在/etc/shadow中的信息相同,但它以更易于理解的格式呈现。如果您想修改这些信息,chage工具仍然是您的最佳选择。我给出的第一个示例是一个相当典型的例子。您应该绝对要求新用户在创建用户帐户后第一次登录时重置密码。不幸的是,并非每个人都愿意做必要的操作。通过使用chage命令,您可以要求用户在第一次成功登录系统时更改密码。这是通过将他们的密码过期前天数更改为0来实现的,具体操作如下:

sudo chage –d 0 packt

与之前的输出结果相比,结果如下所示:

~ $sudo chage -l packt

图 7.2 – 显示用户帐户的密码相关信息和过期策略

图 7.2 – 显示用户帐户的密码相关信息和过期策略

以下指令允许您配置用户帐户,以便在经过特定天数后要求更换密码:

sudo chage -M 90 <username>

在上述代码中,我正在将用户帐户配置为在 90 天后失效,并要求在此时更换密码。当用户登录时,他们将在密码必须更改前的七天内收到警告通知。这是密码过期前七天会出现的消息:

图 7.3 – 显示用户账户的密码相关信息和过期策略

图 7.3 – 显示用户账户的密码相关信息和过期策略

出于安全原因,设置密码过期策略是一个好习惯。

组管理

现在我们已经了解如何创建新的用户账户,管理现有的用户账户,并在用户账户之间切换,接下来我们需要学习如何管理组。Linux 对组的实现与其他操作系统并没有太大不同,本质上执行着相同的功能。借助组来控制用户对服务器资源的访问会更加高效。你可以通过简单地将用户添加到某个资源(如文件或目录)所分配的组中,或者将其从该组中移除,从而授予或拒绝用户访问权限。通过将组分配给特定资源,便能实现这一目标。在 Linux 中,文件和目录都拥有一个用户和一个组来声明其所有权。使用 Linux 时,所有权是以一对一的方式分配的,意味着每个文件或目录只与一个用户和一个组相关联。你可以在 Linux 系统上通过列出某个目录中的项目来验证这一点,如下所示:

ls –l

图 7.4 – 列出文件夹的内容

图 7.4 – 列出文件夹的内容

如果你有兴趣了解当前在服务器上活跃的组,你只需要使用cat命令来读取/etc/group文件的内容。/etc/group文件与我们之前讲解过的/etc/passwd文件类似,存储了有关系统中已创建组的信息。你可以随时查看这个文件,文件路径为:

要创建一个组,使用sudo addgroup <``groupname>命令:

sudo addgroup packtgroup
Adding group `packtgroup' (GID 1006) ...
Done.

要查看文件内容,可以运行以下命令:

cat /etc/group
packtgroup:x:1006:
packt:x:1007:
packtdemo:x:1005:
packt2:x:1004:

每一行的结构如下:

<Group name>:<Group password>:<GID>:<Group members>

查看上面的语法,我们可以看到以下内容:

  • 该组的名称是<``Group name>

  • 如果<``Group password>旁边有一个x,则表示该组密码未使用

  • <Group members>是一个由逗号分隔的列表,列出了<``Group name>组的成员用户

使用sudo delgroup <groupname>命令来删除一个组。

每一行/etc/group文件中包含组信息,包含组名和与其相关联的用户账户:

packt:x:1007:
packtdemo:x:1005:
packt2:x:1004:

接下来,我们将查看usermod命令,它实际上允许你将用户与组关联,并且是我们下一个议题。usermod命令有多种用途,适用范围广泛(将用户添加到组只是其中的一种功能)。如果我们想将一个用户(packt)添加到我们的packtgroup组中,我们可以执行以下命令:

sudo usermod –aG packtgroup packt

-aG 选项用于将用户添加到特定的组中。-a 标志表示 追加,意味着用户将被添加到该组,而不会将其从其他已加入的组中移除。G 标志指定组名。

如果你想修改用户所属的主组,可以使用 -g 选项(注意这里使用小写字母 g 而不是大写字母 G,正如我们之前所做的):

sudo usermod –g <groupname> <username>

组是管理安全权限的最简单方式。想象一下,将一个用户从一个分配给 100 个资源的组中移除,而不是删除该用户 100 次。

权限

在 Linux 中,每个文件、目录和其他系统对象都有一个指定的所有者和组。这是系统安全性中最基本的方面,旨在防止用户之间的相互干扰。可以向所有者、组成员和其他用户授予不同的读取、写入或执行文件的权限。在 Linux 中,这通常被称为文件权限。

以下命令用于管理所有权和设置权限:

  • 使用 chmod 更改文件权限

  • 使用 chown 更改文件所有者

  • 使用 chgrp 更改组所有权

  • 使用 id 打印用户和组 ID

通常,创建文件的用户是其所有者,且与该所有者关联的组是其主组(至少在最初是这样)。让我们在 /tmp 目录下创建一个 testfile 文件作为示例:

$echo "This is a test file" >  testfile
$ls -l testfile
-rw-rw-r-- 1 packt packt 20 Feb  6 16:37 testfile

输出的第一个字符显示 testfile 是一个普通文件(即不是目录或其他类型的系统对象)。接下来的九个字符(分为三组,每组三个字符)显示系统所有者、组所有者和其他用户的读(r)、写(w)和执行(x)权限。

输出的前三个字符(rw-)表示文件所有者(用户 packt)可以读写该文件。接下来的三个字符表示组所有者的权限。最后一组字符(r—)意味着其他用户只能读取该文件;他们不能写入或执行该文件。

我们将使用 chmod 来修改文件的权限。符号表示法指示新的权限将应用于谁,必须在此命令后给出:

  • u 代表用户

  • g 代表组

  • o 代表所有其他用户

  • a 代表所有用户

权限类型如下:

  • +r 添加读权限

  • -r 移除读权限

  • +w 添加写权限

  • -w 移除写权限

  • +x 添加执行权限

  • -x 移除执行权限

  • +rw 添加读写权限

  • +rwx 添加读、写和执行权限

所有这些权限可以通过数字表示,如下所示:

读 – 4 :写 – 2 :执行 – 1

例如,chmod 754 testfile 将会为 testfile 设置 rwx 权限给所有者,rx 给组,只有 r 给其他用户:

ls -la testfile
-rwxr-xr--  1 voxsteel voxsteel  0 26 Jun 14:27 testfile

总结一下,授予正确的权限对于安全至关重要,并且可以避免造成任何不必要的损害。以下是查看权限的示例:

图 7.5 – 列出文件夹的内容及其权限

图 7.5 – 列出文件夹的内容及其权限

总结一下,授予正确的权限对于安全至关重要,以防止系统上的任何不必要的活动。

更改组

现在讨论chgrp命令,将packtgroup设为testfile的新所有者。命令执行后,我们指定组名和需要更改所有权的文件名(在这种情况下是testfile):

sudo chgrp packtgroup testfile

现在让我们检查用户packtdemo写入该文件的能力。应该会出现权限拒绝错误。我们可以为组设置相关权限,允许packtdemo写入该文件:

sudo chmod g+w testfile

然后再次使用usermod命令将账户添加到packtgroup,这次使用-aG组合选项,如下所示:

sudo usermod -aG packtgroup packtdemo

附加到组的缩写是-aG

目前,packtgroup被视为用户packtdemo的附属组或辅助组。当packtdemo下次登录时,更新后的访问权限将生效。

我们可以使用chown,后面跟着用户名和文件名,按此顺序操作,将packtdemo设为testfile的所有者,而不仅仅是将用户添加到packtgroup组:

sudo chown packtdemo testfile

请记住,前面提到的命令将阻止packt用户访问testfile,因为该账户不再是文件的所有者,也不是packtgroup组的成员。

除了典型的rwx文件权限外,setuidsetgid和粘性位是三个额外的权限,值得关注。让我们逐一查看它们。

任何用户如果在文件上设置了setuid位,就可以使用文件所有者的权限运行该可执行文件。

任何用户如果在文件上设置了setgid位,就可以使用该组的权限运行该可执行文件。

当这些特定权限被误用时,会带来安全风险。例如,如果任何用户被允许以超级用户权限运行命令,那么该用户就能够访问属于 root 及其他用户的文件。很容易理解,这可能会迅速对系统造成破坏:关键文件可能被删除,某些目录可能被完全清除,甚至硬件可能会出现异常。所有这些都可能是由一个恶意或粗心的人引起的。因此,setuidsetgid位必须非常小心地使用。

/usr/bin/passwd中,setuid位是必需的,并且是一个可接受的用例。虽然 root 拥有这个文件,但任何用户都可以使用它来更改自己的密码(但不能更改其他用户的密码)。

当在目录上设置粘滞位时,除非是文件的所有者、目录的所有者或 root 用户,否则任何人都不能删除该目录中的文件。这通常用于防止用户删除共享目录中其他用户的文件。

testfilesetuid位设置如下:

sudo chmod u+s testfile

testfilesetgid位设置如下:

sudo chmod g+s testfile

要创建一个名为packtdir的目录并设置粘滞位,可以使用以下命令:

sudo mkdir packtdir
sudo chmod +t packtdir

回到/etc/sudoers,我们还可以通过使用/etc/sudoers文件授予某个组中的每个用户超级用户权限。例如,以下命令指定属于packtgroup的用户被允许运行updatedb(更具体地说,是/usr/bin/updatedb):

/usr/bin/updatedb %packtgroup ALL=(ALL)

组名必须以%符号为前缀,这是组成员和单个用户之间唯一的区别。在这种情况下,命令别名也是适用的。

在命令行中简单地输入sudo -l并按Enter键,可以显示调用用户在/etc/sudoers中被允许的命令。

使用组可以节省大量时间。想象一下,给一个组分配运行某些命令的权限,而不是一个一个地分配给 100 个用户。

总结

在与 Linux 管理相关的领域,如系统管理和网络安全中,管理用户和权限是你经常需要做的事情。随着新用户加入组织,旧用户离开,这将成为你思维工具箱中的一部分。然而,即使你是唯一使用服务器的人,你仍然需要管理权限。这是因为如果进程被拒绝访问它们执行任务所需的资源,它们将无法正常运行。

在本章中,我们深入探讨了管理用户、组和权限的过程,并覆盖了许多内容。我们详细讲解了创建新用户、删除现有用户、分配权限以及使用sudo命令管理管理员访问权限的过程。将这些概念应用到你自己的服务器上。

在下一章中,我们将讨论软件安装和软件包仓库。

第八章:软件安装与软件包仓库

大多数 Linux 发行版默认只安装最基本的系统软件,并假设用户会稍后安装额外的软件。如今,在 Linux 上安装软件非常简单,得益于软件包仓库和高级软件包管理工具,这些工具可以通过网络搜索、下载和安装软件包。然而,了解软件包文件的内部组织结构以及软件包管理器的工作原理非常重要,因为这有助于管理员检查软件包、诊断问题,并在发生安装问题时进行修复。

本章将涵盖以下主题:

  • 软件安装、软件包和依赖关系

  • 软件包文件

  • 软件包仓库和高级软件包管理工具

  • 系统升级

软件安装、软件包和依赖关系

软件包的定义相当广泛。在计算机早期,计算机专门用于解决数学问题,大多数程序都是从头编写的,并且仅在特定计算机上运行,因此没有安装的需求,也就没有软件包的概念。在那之后的很长一段时间里,软件仍然与可执行文件同义。安装由单个可执行文件组成的软件产品非常简单——只需将其复制到目标计算机上。

这种软件今天仍然存在。例如,jq(一个用于从 JSON 文件中提取数据的流行工具)的维护者提供了独立的、静态链接的可执行文件,将程序和它所需的所有库合并成一个单一文件,可以在任何 Linux 系统上运行,因此任何用户都可以从其网站 (stedolan.github.io/jq/download/) 下载并开始使用。

然而,许多软件项目由多个可执行文件组成,并且通常还需要数据文件。例如,一个拼写检查程序,如Aspell,需要字典文件才能工作。在视频游戏中,可执行文件通常只是一个很小的部分,相比于游戏资产(如模型、纹理和声音文件)来说,它往往是最小的。为了使软件产品按预期工作,所有这些文件需要作为软件包一起分发和安装。

在早期的操作系统中,安装通常只是将软件产品的所有文件复制到一个目录中。这也是许多 Linux 系统中仍然存在的 /opt 目录的目的。为了安装一个假设的软件包 Foo 1.0,管理员可能会将其发布包(例如 foo_1.0.tar.gz)解压到 /opt/foo/ 目录(通常使用 tar --xf foo_1.0.tar.gz --directory /opt/foo/),并且 /opt/foo 目录中的结构将完全由 Foo 的应用开发人员定义,而不是由操作系统本身定义。

在现代操作系统中,仅仅复制文件通常是不够的,因为它不能将新安装的软件正确地集成到操作系统中。即使是提供单一控制台命令的软件包,也需要将其放置在一个已经在$PATH中的目录下,或者相应地修改$PATH环境变量。带有图形用户界面的软件还需要正确地将自己注册到桌面环境的应用菜单中,并可以选择性地创建桌面图标。

因此,操作系统开始要求软件包具有特定的结构。在 Microsoft Windows 和许多其他系统中,软件包通常仍然作为定制的可执行程序提供,这些程序会解压并复制文件,还会执行脚本将软件包注册到操作系统中。Linux 软件的开发者有时也会采用这种方法,并且有一些用于创建可执行安装程序的框架,例如makeself (makeself.io/)。

这种方法使软件包作者可以完全控制安装过程,但也有许多缺点。首先,通过这种方式安装的软件卸载并不总是可靠的。如果安装程序的开发者没有小心清理所有在安装时创建的文件,用户可能会留下需要手动删除的残余文件。

第二,每个软件包需要实现自己的更新检查和安装机制。在宽带互联网普及之前,软件更新通常通过软盘或 CD 等物理介质分发,这并不是一个大问题;但如今,大多数用户都有常规的互联网接入,他们能够受益于在线更新,许多软件产品,如网页浏览器,必须保持最新,以防止安全威胁。

第三,很少有程序是从零开始编写的,大多数程序都会大量使用第三方库,因此它们有依赖关系。确保库代码可供程序调用的一种方法是将其作为可执行文件的一部分。这种方法称为静态链接。它的优点是不会出现错误安装此类可执行文件的情况,但缺点是会大大增加内存和磁盘空间的消耗。由于这个原因,大多数程序都是与外部库文件动态链接的,但为了使它们正常工作,程序需要确保系统中存在所有所需库的正确版本的文件。如果每个软件包都是独立的并且负责自己的安装,那么唯一的方法就是将所有库文件与软件包捆绑在一起(在磁盘空间使用方面,这比静态链接仅略有改善),或者要求用户手动查找并安装所有库文件(这对用户来说非常不方便)。

对于不可重新分发且通常没有源代码形式的专有软件,通常唯一的选择是将所有依赖项捆绑在一起,因为可执行文件需要针对不同的库版本重新编译。不同的 Linux 发行版可能由于不同的发布周期和软件包含策略而包括不同版本的库(一些发行版专注于稳定性,只会包含较旧但经过验证的版本,而其他发行版则可能选择包含最新的库,即使它们没有经过充分测试)。

然而,对于那些可以修改并重新分发的源代码形式的开源软件,有更多的可能性可以重用库文件并创建一个统一的系统。为了使这一点成为可能,发行版维护者开发了现代包管理器。

包管理器

包管理器是负责安装、升级和卸载软件包的程序。在旧有方法中,软件开发人员负责安装过程,他们可以自由选择是将软件分发为解压并安装文件的可执行文件,还是分发为用户必须手动解压的归档文件,归档和压缩算法的选择也由开发人员决定。

相比之下,包管理器通常对软件包有非常精确的定义。为了使一个软件项目能够通过包管理器进行安装,其维护者必须创建一个包文件,遵循一组关于内部结构的指南。除了软件产品正常运行所需的文件,包文件还包含以特定格式存储的元数据。元数据文件包含有关包的信息,例如名称、版本、许可证和它所依赖的其他包列表。

包管理器限制了软件产品作者在安装时可以做的事情。例如,用户无法指定自定义的安装目录或选择不安装某些文件。

但由于包管理器完全控制安装过程且包内的每个文件都被记录,因此它们可以可靠地安装和卸载软件,并确保卸载后没有遗留文件。

一个更大的优势是,包管理器可以自动跟踪包的依赖关系,防止用户在所有依赖项安装之前安装某个包,或自动安装这些依赖项。

不同的 Linux 发行版使用不同的包管理器。其中一些使用两个不同的工具来管理软件的安装和升级:一个低级包管理器,负责处理包文件,和一个高级工具,用于自动下载包并管理升级。

从历史上看,这曾是主流做法:在宽带互联网普及之前,自动下载是不可行的,因此在 1990 年代开发的第一个软件包管理器只支持处理本地软件包文件。这个类别中最流行的两个项目是 rpm(由 Red Hat 开发)和 dpkg(由 Debian 项目开发)。为了实现网络上的自动安装和自动系统升级,它们需要与高级工具如 YUM、DNF、Zypper 或 高级软件包工具APT)结合使用。

一些在 2000 年代及以后的 Linux 发行版使用的是结合了两者功能的软件包管理器,能够同时处理本地软件包文件和远程源。

我们可以通过以下表格展示不同发行版中低级和高级软件包管理器的现状:

发行版 高级 软件包管理器 低级 软件包管理器
Debian APT dpkg
Ubuntu
Linux Mint
Fedora DNF rpm
Red Hat Enterprise Linux DNF, YUM
openSUSE, SUSE Linux Enterprise Server Zypper
Mageia DNF, urpmi
Arch Linux pacman
NixOS Nix
Guix Guix

表 8.1 – 不同 Linux 发行版使用的软件包管理器

我们将专注于最流行的低级软件包管理器:rpmdpkg。它们各自并没有本质上优于对方,但它们的软件包文件格式和软件包管理工具界面的设计选择是非常不同的。我们将对两者进行比较和对比。

软件包文件

按照惯例,软件包作为一种特殊格式的文件出现,文件中包含需要安装到系统中的软件项目文件以及软件包管理器的元数据,如文件校验和、依赖列表和软件包的系统要求(如 CPU 架构)。我们将查看 .rpm.deb 软件包文件,并比较它们的实现细节。

检查软件包文件

首先,我们将检查软件包文件,了解其中的内容,并学习如何检查和解压它们。

请注意,通常情况下,您不需要手动下载和解压软件包文件!我们这么做只是为了教学目的。

检查 Debian 软件包

我们将使用 GNU hello 软件包进行实验。GNU hello 是一个简单的示例程序,只会打印 hello world —— 它的真正目的是作为开发和打包实践的示例,向新开发者展示如何编写构建脚本、实现国际化等。

您可以在最新的不稳定版 Debian 中找到 GNU hello 软件包,链接:packages.debian.org/sid/hello

图 8.1 – Debian 软件包仓库网站上的 hello 软件包信息页面

图 8.1 – Debian 软件包仓库网站上的 hello 软件包信息页面

通过访问架构特定的下载链接,如 packages.debian.org/sid/amd64/hello/download,并从显示的截图中任何镜像站点下载包文件:

图 8.2 – Debian Sid 上 x86_64 机器的 hello 包下载页面

图 8.2 – Debian Sid 上 x86_64 机器的 hello 包下载页面

在写作时,最新版本是 2.10,因此我使用了这个链接,但不能保证未来版本能继续使用该链接:

$ wget http://ftp.us.debian.org/debian/pool/main/h/hello/hello_2.10-2_amd64.deb

使用 dpkg --info 命令,我们可以查看我们刚下载的包的信息:

$ dpkg --info ./hello_2.10-2_amd64.deb
new Debian package, version 2.0.
size 56132 bytes: control archive=1868 bytes.
Package: hello
Version: 2.10-2
Architecture: amd64
Maintainer: Santiago Vila <sanvila@debian.org>
Installed-Size: 280
Depends: libc6 (>= 2.14)
Conflicts: hello-traditional
Breaks: hello-debhelper (<< 2.9)
Replaces: hello-debhelper (<< 2.9), hello-traditional
Section: devel
Priority: optional
Homepage: http://www.gnu.org/software/hello/
Description: example package based on GNU hello
The GNU hello program produces a familiar, friendly greeting. It allows non-programmers to use a classic computer science tool which would otherwise be unavailable to them.
.
Seriously, though: this is an example of how to do a Debian package.
It is the Debian version of the GNU Project's `hello world' program (which is itself an example for the GNU Project).

dpkg 从哪里获取这些信息?注意输出中的 control archive=1868 bytes 部分。一个 Debian 包文件是一个 ar 压缩包,它由两个连接在一起的 tar 压缩包组成。我们当然可以使用 ar 工具提取它们,或者甚至用 dd(因为其格式简单,并且 dpkg 会告诉我们每个归档文件的字节长度),手动解压它们,但幸运的是,dpkg 提供了内建的功能来处理这些。使用 dpkg --controldpkg -e),我们可以提取控制归档——即包含包元数据的部分:

$ dpkg --control ./hello_2.10-2_amd64.deb

如果我们没有指定自定义输出目录,dpkg 会将其解压到一个名为 DEBIAN 的子目录中。它将包含两个文件:controlmd5sumscontrol 文件就是 dpkg --infodpkg -l)获取字段及其值的来源:

$ cat DEBIAN/control
Package: hello
Version: 2.10-2
Architecture: amd64
Maintainer: Santiago Vila <sanvila@debian.org>
Installed-Size: 280
Depends: libc6 (>= 2.14)
Conflicts: hello-traditional
Breaks: hello-debhelper (<< 2.9)
Replaces: hello-debhelper (<< 2.9), hello-traditional
Section: devel
Priority: optional
Homepage: http://www.gnu.org/software/hello/
Description: example package based on GNU hello
The GNU hello program produces a familiar, friendly greeting. It allows non-programmers to use a classic computer science tool which would otherwise be unavailable to them.
.
Seriously, though: this is an example of how to do a Debian package.
It is the Debian version of the GNU Project's `hello world' program (which is itself an example for the GNU Project).

md5sums 文件包含包内所有文件的哈希值:

$ cat DEBIAN/md5sums
6dc2cf418e6130569e0d3cfd2eae0b2e usr/bin/hello
9dbc678a728a0936b503c0aef4507f5d usr/share/doc/hello/NEWS.gz
1565f7f8f20ee557191040f63c1726ee
usr/share/doc/hello/changelog.Debian.gz
31aa50363c73b22626bd4e94faf90da2 usr/share/doc/hello/changelog.gz
bf4bad78d5cf6787c6512b69f29be7fa usr/share/doc/hello/copyright
c5162d14d046d9808bf12adac2885473 usr/share/info/hello.info.gz
0b430a48c9a900421c8d2a48e864a0a5 usr/share/locale/bg/LC_MESSAGES/hello.mo
0ac1eda691cf5773c29fb65ad5164228 usr/share/locale/ca/LC_MESSAGES/hello.mo
...
usr/share/locale/zh_TW/LC_MESSAGES/hello.mo
29b51e7fcc9c18e989a69e7870af6ba2 usr/share/man/man1/hello.1.gz

MD5 哈希算法已不再具备密码学安全性,不能用于保护文件和消息免受恶意修改。然而,在 Debian 包中,它仅用于防止意外的文件损坏,因此不构成安全问题。防止恶意修改是通过 GnuPG 数字签名来提供的,这些签名会被高级工具 APT 检查。

如果你只想列出数据归档中的文件,可以使用 dpkg --contentsdpkg -c):

$ dpkg --contents ./hello_2.10-2_amd64.deb
drwxr-xr-x root/root  0 2019-05-13 14:06 ./
drwxr-xr-x root/root 0 2019-05-13 14:06 ./usr/
drwxr-xr-x root/root 0 2019-05-13 14:06 ./usr/bin/
-rwxr-xr-x root/root31360 2019-05-13 14:06
./usr/bin/hello
drwxr-xr-x root/root 0 2019-05-13 14:06 
./usr/share/
drwxr-xr-x root/root 0 2019-05-13 14:06 
./usr/share/doc/
drwxr-xr-x root/root 0 2019-05-13 14:06 ./usr/share/doc/hello/
-rw-r--r-- root/root 1868 2014-11-16 06:51 ./usr/share/doc/hello/NEWS.gz
-rw-r--r-- root/root 4522 2019-05-13 14:06 ./usr/share/doc/hello/changelog.Debian.gz
-rw-r--r-- root/root 4493 2014-11-16 07:00 ./usr/share/doc/hello/changelog.gz
-rw-r--r-- root/root 2264 2019-05-13 13:00 ./usr/share/doc/hello/copyright
drwxr-xr-x root/root 0 2019-05-13 14:06 ./usr/share/info/
-rw-r--r-- root/root  11596 2019-05-13 14:06
./usr/share/info/hello.info.gz
drwxr-xr-x root/root 0 2019-05-13 14:06 ./usr/share/locale/
…

也可以使用 dpkg --extractdpkg -x)解压包文件的数据归档部分,但在这种情况下,你需要指定解压的位置。要解压到当前目录,可以使用点(.)快捷方式:

$ dpkg --extract ./hello_2.10-2_amd64.deb .

现在,让我们继续检查 RPM 包文件,并将其与我们在 Debian 包中看到的内容进行比较。

检查 RPM 包

我们将以 Fedora 为例,展示使用 RPM 包的分发版。要在网上查找 Fedora 存储库,可以访问 packages.fedoraproject.org/。在搜索框中输入包的名称(在本例中是 hello),即可看到包的信息页面。

图 8.3 – Fedora 包存储库网站上的 hello 包信息页面

图 8.3 – Fedora 包存储库网站上的 hello 包信息页面

Fedora 使用 Rawhide 作为最新不稳定版本的代号。在撰写本文时,正在开发的版本是 37。要查找软件包下载链接,请转到 Builds 标签页,然后在那里找到最新构建的链接。我们可以使用以下命令下载该软件包以进行检查:

$ wget https://kojipkgs.fedoraproject.org//packages/hello/2.10/8.fc37/x86_64/hello-2.10-8.fc37.x86_64.rpm

注意

我们提供了从 Fedora 37 直接链接到软件包的示例,但由于所有网址和软件包版本都可能发生变化,所以下面的命令不保证永远有效——如果你想下载软件包,请改为访问packages.fedoraproject.org 网站。

所有 RPM 查询都可以通过 --query 选项来执行。RPM 的一个优点是,所有查询选项都可以用于检查软件包文件或已安装的软件包,具体取决于参数。如果你只给出软件包名称,它将查找已安装的软件包,但如果指定软件包文件路径,它将检查该文件。

例如,使用 rpm --query --info,我们可以读取软件包的元数据。或者,该命令可以缩写为 rpm -qi

$ rpm --query --info ./hello-2.10-8.fc37.x86_64.rpm
Name        : hello
Version     : 2.10
Release     : 8.fc37
Architecture: x86_64
Install Date: (not installed)
Group       : Unspecified
Size        : 183537
License     : GPLv3+ and GFDL
Signature   : (none)
Source RPM  : hello-2.10-8.fc37.src.rpm
Build Date  : Thu 21 Jul 2022 03:15:21 PM IST
Build Host  : buildvm-x86-27.iad2.fedoraproject.org
Packager    : Fedora Project
Vendor      : Fedora Project
URL         : https://www.gnu.org/software/hello/
Bug URL     : https://bugz.fedoraproject.org/hello
Summary     : Prints a familiar, friendly greeting
Description :
The GNU Hello program produces a familiar, friendly greeting.
Yes, this is another implementation of the classic program that prints "Hello, world!" when you run it.
However, unlike the minimal version often seen, GNU Hello processes its argument list to modify its behavior, supports greetings in many languages, and so on. The primary purpose of GNU Hello is to demonstrate how to write other programs that do these things; it serves as a model for GNU coding standards and GNU maintainer practices.

使用 rpm --query --list(简写为 rpm -ql),我们可以获取软件包中包含的文件列表:

$ rpm --query --list ./hello-2.10-8.fc37.x86_64.rpm
/usr/bin/hello
/usr/lib/.build-id
/usr/lib/.build-id/d2
/usr/lib/.build-id/d2/847d989fd9b360c77ac8afbbb475415401fcab
/usr/share/info/hello.info.gz
/usr/share/licenses/hello
/usr/share/licenses/hello/COPYING
/usr/share/locale/bg/LC_MESSAGES/hello.mo
/usr/share/locale/ca/LC_MESSAGES/hello.mo
…
/usr/share/locale/zh_CN/LC_MESSAGES/hello.mo
/usr/share/locale/zh_TW/LC_MESSAGES/hello.mo
/usr/share/man/man1/hello.1.gz

将其与 dpkg --contents 的输出进行比较,后者列出了每个文件的 MD5 校验和。与 Debian 软件包不同,RPM 软件包不会为每个文件包含哈希值,而是为软件包归档文件包含一个单一的哈希值。

rpm 命令本身并未提供解包 RPM 软件包的选项。相反,它提供了两个工具来提取数据部分:rpm2cpiorpm2archive

与 Debian 软件包不同,RPM 软件包不会将元数据作为归档内的文件存储,而是使用自定义的 RPM 文件头来存储这些信息。该头之后的归档仅包含必须安装的文件。此外,虽然 dpkg 使用熟悉的 tar 归档格式将多个文件打包成一个,但 RPM 使用的是一个较不常见的 CPIO 格式。

cpio 命令较难使用。特别是,它需要用户输入每个必须包含在归档中的文件路径,因此不适合手动使用,并且只能合理地与其他工具(如 find)结合使用。由于这个原因,tar 更受欢迎,因为 TAR 归档工具可以通过一条命令轻松地打包整个目录,例如 tar cvf file.tar /path/to/directory。然而,CPIO 归档文件格式更容易实现,不同厂商的实现之间没有差异,而且对特殊文件(如链接和设备文件)有更好的支持,这也是一些项目选择在内部使用它的原因。那些项目包括 Linux 内核,它使用 CPIO 格式来处理初始 RAM 磁盘,以及 RPM,它使用 CPIO 格式来处理软件包文件。

幸运的是,rpm2archive 工具可以将 RPM 软件包转换为压缩的 tar 归档文件,因此你无需学习如何使用 cpio 来从 RPM 软件包中提取文件:

$ rpm2archive hello-2.10-8.fc37.x86_64.rpm
$ tar xvfz ./hello-2.10-8.fc37.x86_64.rpm.tgz
./usr/bin/hello
./usr/lib/.build-id/
./usr/lib/.build-id/d2/
./usr/lib/.build-id/d2/847d989fd9b360c77ac8afbbb475415401fcab
./usr/share/info/hello.info.gz
./usr/share/licenses/hello/
./usr/share/licenses/hello/COPYING
./usr/share/locale/bg/LC_MESSAGES/hello.mo
./usr/share/locale/ca/LC_MESSAGES/hello.mo
...

现在,让我们继续学习如何检查系统中已安装的软件包。

检查已安装的软件包

软件项目文件和我们在软件包文件中看到的元数据都会在软件包安装时保留在系统中。让我们学习如何访问已安装软件包的信息,并与我们在软件包文件中看到的内容进行比较。

列出所有已安装的软件包

dpkgrpm都提供了列出系统中所有已安装软件包的选项。由于这些列表即使在小型安装中也会非常长,因此你可能希望将它们与分页命令(如less)或过滤器(如headtailgrep)一起使用。

对于 Debian 系统,列出所有已安装软件包的命令是dpkg --list,或者简写为dpkg -l。默认情况下,列表按字母顺序排序:

$ dpkg -l | head
Desired=Unknown/Install/Remove/Purge/Hold
| Status=Not/Inst/Conf-files/Unpacked/halF-conf/Half-inst/trig-aWait/Trig-pend
|/ Err?=(none)/Reinst-required (Status,Err: uppercase=bad)
||/ Name                                 Version                             
Architecture Description
ii  adduser                              3.118                               
all          add and remove users and groups
ii  adwaita-icon-theme                   3.30.1-1                            
all          default icon theme of GNOME
ii  ansible                              2.9.27-1ppa~trusty                  
all          Ansible IT Automation
ii  apparmor                             2.13.2-10                           
amd64        user-space parser utility for AppArmor
ii  apt                                  1.8.2.3                             
amd64        commandline package manager

对于基于 RPM 的发行版,命令是rpm --query --all,或者rpm -qa。请注意,RPM 默认不按字母顺序排序该输出:

$ rpm --query --all | head
shared-mime-info-2.1-3.fc35.x86_64
xorg-x11-drv-vmware-13.2.1-16.fc35.x86_64
xorg-x11-drv-qxl-0.1.5-20.fc35.x86_64
irqbalance-1.7.0-8.fc35.x86_64
ipw2200-firmware-3.1-22.fc35.noarch
ipw2100-firmware-1.3-29.fc35.noarch
gpg-pubkey-9867c58f-601c49ca
gpg-pubkey-d651ff2e-5dadbbc1
gpg-pubkey-7fac5991-4615767f
gpg-pubkey-d38b4796-570c8cd3

如果你仅想检查某个软件包是否已安装,RPM 提供了一个选项,仅检查这一点,而不输出其他任何信息,即rpm --queryrpm -q):

$ rpm -q rpm
rpm-4.17.1-3.fc36.x86_64
$ rpm -q no-such-package
package no-such-package is not installed

Debian 系统上的等效命令是dpkg --statusdpkg -s),如果软件包已安装,它也会打印该软件包的信息:

$ dpkg -s dpkg
Package: dpkg
Essential: yes
Status: install ok installed
Priority: required
Section: admin
Installed-Size: 6693
Maintainer: Dpkg Developers <debian-dpkg@lists.debian.org>
Architecture: amd64
Multi-Arch: foreign
Version: 1.19.7
Depends: tar (>= 1.28-1)
Pre-Depends: libbz2-1.0, libc6 (>= 2.15), liblzma5 (>= 5.2.2), libselinux1 (>= 2.3), zlib1g (>= 1:1.1.4)
Suggests: apt, debsig-verify
Breaks: acidbase (<= 1.4.5-4), amule (<< 2.3.1+git1a369e47-3), beep (<< 1.3-4), im (<< 1:151-4), libapt-pkg5.0 (<< 1.7~b), libdpkg-perl (<< 1.18.11), lsb-base (<< 10.2019031300), netselect (<< 0.3.ds1-27), pconsole (<< 1.0-12), phpgacl (<< 3.3.7-7.3), pure-ftpd (<< 1.0.43-1), systemtap (<< 2.8-1), terminatorx (<< 4.0.1-1), xvt (<= 2.1-20.1)
Conffiles:
 /etc/alternatives/README 7be88b21f7e386c8d5a8790c2461c92b
 /etc/cron.daily/dpkg f20e10c12fb47903b8ec9d282491f4be
 /etc/dpkg/dpkg.cfg f4413ffb515f8f753624ae3bb365b81b
 /etc/logrotate.d/alternatives 5fe0af6ce1505fefdc158d9e5dbf6286
 /etc/logrotate.d/dpkg 9e25c8505966b5829785f34a548ae11f
Description: Debian package management system
This package provides the low-level infrastructure for handling the installation and removal of Debian software packages.
.
For Debian package development tools, install dpkg-dev.
Homepage: https://wiki.debian.org/Teams/Dpkg

如果某个软件包未安装,该命令会打印一个错误:

$ dpkg -s no-such-package
dpkg-query: package 'no-such-package' is not installed and no information is available
Use dpkg --info (= dpkg-deb --info) to examine archive files.

如果你想获取 RPM 系统中已安装软件包的信息,可以使用与我们检查软件包文件时相同的rpm -qi命令;只需给出软件包的名称,而不是文件路径:

$ rpm --query --info rpm
Name        : rpm
Version     : 4.17.1
Release     : 3.fc36
Architecture: x86_64
Install Date: Tue 09 Aug 2022 03:05:25 PM IST
Group       : Unspecified
Size        : 2945888
License     : GPLv2+
Signature   : RSA/SHA256, Tue 02 Aug 2022 02:11:12 PM IST,
Key ID 999f7cbf38ab71f4
Source RPM  : rpm-4.17.1-3.fc36.src.rpm
Build Date  : Tue 02 Aug 2022 01:31:56 PM IST
Build Host  : buildhw-x86-11.iad2.fedoraproject.org
Packager    : Fedora Project
Vendor      : Fedora Project
URL         : http://www.rpm.org/
Bug URL     : https://bugz.fedoraproject.org/rpm
Summary     : The RPM package management system
Description :
The RPM Package Manager (RPM) is a powerful command line driven package management system capable of installing, uninstalling, verifying, querying, and updating software packages. Each software package consists of an archive of files along with information about the package like its version, a description, etc.

如果你需要找出某个文件来自哪个软件包(或者它是否根本不属于任何软件包),也有相应的命令。对于 RPM,命令是rpm --query --filerpm -qf):

$ rpm --query --file /usr/bin/rpm
rpm-4.17.1-3.fc36.x86_64
$ touch /tmp/test-file
$ rpm --query --file /tmp/test-file 
file /tmp/test-file is not owned by any package

对于dpkg,命令是dpkg --searchdpkg -S):

$ dpkg -S /usr/bin/dpkg
dpkg: /usr/bin/dpkg
$ touch /tmp/test-file
$ dpkg -S /tmp/test-file 
dpkg-query: no path found matching pattern /tmp/test-file

使用 RPM,同样可以轻松列出属于某个软件包的所有文件,命令与我们之前列出软件包内容的命令相同:

$ rpm --query --list rpm
/etc/rpm
/usr/bin/rpm
/usr/bin/rpm2archive
/usr/bin/rpm2cpio
/usr/bin/rpmdb
/usr/bin/rpmkeys
/usr/bin/rpmquery
/usr/bin/rpmverify
…

对于dpkg,等效命令是dpkg --listfilesdpkg -L)。然而,dpkg还会列出该软件包文件或子目录所在的所有目录,甚至包括像/etc这样的顶层目录,而 RPM 只会显示因安装软件包而创建的文件和目录:

$ dpkg -L dpkg | head
/.
/etc
/etc/alternatives
/etc/alternatives/README
/etc/cron.daily
/etc/cron.daily/dpkg
/etc/dpkg
/etc/dpkg/dpkg.cfg
/etc/dpkg/dpkg.cfg.d
/etc/logrotate.d

然而,安装和移除软件包是比检查已安装软件包更为频繁的任务——让我们来学习如何执行这些操作。

安装和移除软件包文件

即使是dpkg的手册页也警告说,解压软件包到根目录并不是正确的安装方法。软件包的安装之所以不简单,仅仅将文件从软件包复制到正确的位置是远远不够的,原因有很多。

除了复制文件之外,包管理器还会在内部数据库中记录软件包及其文件——这就是为什么像dpkg --listfilesrpm --query --files这样的命令能够正常工作,也解释了为什么包管理器可以可靠地删除软件包而不会留下任何未使用的文件。

然而,包管理器也会包括一些保护机制,防止用户安装在系统上无法正常工作的包。例如,如果你尝试在 x86_64(Intel 或 AMD64)机器上安装一个为 ARM64 架构构建的软件包,会发生以下情况:

$ wget http://ftp.us.debian.org/debian/pool/main/h/hello/hello_2.10-2_arm64.deb
$ sudo dpkg --install ./hello_2.10-2_arm64.deb
dpkg: error processing archive ./hello_2.10-2_arm64.deb (--install):
package architecture (arm64) does not match system (amd64)
Errors were encountered while processing:
./hello_2.10-2_arm64.deb

它们还会保护系统免受删除不应删除的软件包的尝试,或者至少会警告用户删除的后果。由于它们跟踪包之间的所有依赖关系,它们可以强制删除那些在依赖的包被删除后会损坏的包,但默认情况下,如果其他包依赖于某个包,它们将拒绝删除该包。

在基于 Debian 的 Linux 发行版中,有一个概念叫做“基本软件包”,这些软件包会被保护,无法被删除。例如,如果你尝试删除 Bash,系统会报错,因为它是默认的系统 shell:

$ sudo dpkg --remove bash
dpkg: error processing package bash (--remove):
 this is an essential package; it should not be removed
Errors were encountered while processing:
 bash

如果一个包不是基本包,你将看到一份依赖包列表,显示哪些包阻止它被删除:

$ sudo dpkg --remove gcc
dpkg: dependency problems prevent removal of gcc:
musl-tools depends on gcc.
g++ depends on gcc (= 4:8.3.0-1).
dkms depends on gcc.
build-essential depends on gcc (>= 4:8.3).

RPM 也有类似的功能,它不会允许用户安装有未满足(或者在不同架构下是无法满足的)依赖关系的软件包,或者删除基本软件包。

这就是如果你尝试安装一个为不同架构构建的软件包时可能发生的情况:

$ sudo rpm --install hello-2.10-8.fc37.aarch64.rpm
error: Failed dependencies:
ld-linux-aarch64.so.1()(64bit) is needed by hello-2.10-8.fc37.aarch64
ld-linux-aarch64.so.1(GLIBC_2.17)(64bit) is needed by hello-2.10-8.fc37.aarch64

如果你尝试删除 rpm 本身,这就是你将得到的结果:

$ sudo rpm --erase rpm
error: Failed dependencies:
rpm is needed by (installed) policycoreutils-3.3-4.fc36.x86_64
rpm is needed by (installed) cmake-rpm-macros-3.22.2-1.fc36.noarch
rpm is needed by (installed) kde-filesystem-4-67.fc36.x86_64
rpm is needed by (installed) color-filesystem-1-28.fc36.noarch
rpm is needed by (installed) efi-srpm-macros-5-5.fc36.noarch
rpm is needed by (installed) lua-srpm-macros-1-6.fc36.noarch
rpm > 4.15.90-0 is needed by (installed) python3-rpm-generators-12-15.fc36.noarch
rpm = 4.17.1-3.fc36 is needed by (installed) rpm-libs-4.17.1-3.fc36.x86_64
rpm = 4.17.1-3.fc36 is needed by (installed) rpm-build-4.17.1-3.fc36.x86_64
rpm is needed by (installed) rpmautospec-rpm-macros-0.3.0-1.fc36.noarch
rpm is needed by (installed) python3-rpmautospec-0.3.0-1.fc36.noarch
rpm >= 4.9.0 is needed by (installed) createrepo_c-0.20.1-1.fc36.x86_64
rpm >= 4.15 is needed by (installed) fedora-gnat-project-common-3.15-4.fc36.noarch
rpm >= 4.11.0 is needed by (installed) redhat-rpm-config-222-1.fc36.noarch

然而,在现代系统中,无论是使用 rpm 还是 dpkg 安装或删除包,已经不再是常规操作,而是例外。虽然它们防止安装缺少依赖关系的软件包,但它们并不会自动安装这些依赖关系,因此尝试安装依赖关系很多的软件包是一项非常繁琐的任务。如果你要升级系统,你还必须逐一升级所有软件包及其依赖项。这也是包仓库和高级包管理器发明的主要原因。

包仓库和高级包管理器

Linux 发行版的在线包集合几乎与发行版本身一样存在了很久。它们节省了用户搜索编译包或从源代码构建软件的时间,但如果某个包有依赖关系,用户仍然需要逐一下载它们。

发行版维护者的下一步是为整个包集合创建一种机器可读的元数据格式,并提供一个工具来自动化该过程。由于每个包都包含关于其依赖关系的信息,在最简单的情况下,你只需下载所有依赖包。

事实上,这要复杂得多。包可能会冲突(例如,因为它们提供了同名的可执行文件),因此必须有防范措施来避免安装冲突的包。如果用户尝试安装一个来自仓库外的包,仓库可能没有正确版本的依赖包。现代高阶包管理器会检查这些以及其他许多潜在问题,因此大多数时候用户只需要说“我想安装某个包”,工具就会自动完成安装所需的所有操作。

包仓库

现代意义上的包仓库是一个包含机器可读索引的包集合。仓库索引包含有关每个包的信息。本质上,索引汇总了我们在使用dpkg --inforpm --query --info查看包时所见的包元数据文件。

关于分发包的信息的网站,如www.debian.org/distrib/packagespackages.fedoraproject.org/,实际上是从它们的包仓库索引生成的。当我们使用它们查找并下载包时,本质上是手动完成了高阶包管理器可以自动完成的任务。

每个发行版都有其官方仓库,流行发行版还存在社区或供应商支持的仓库。

基于 Debian 的发行版仓库

基于 Debian 的发行版中的仓库配置可以在/etc/apt/目录下找到。主文件是/etc/apt/sources.list,但在/etc/apt/sources.list.d中可能还有其他文件。

这是一个来自 Debian 10(Buster)系统的sources.list文件示例。请注意,除了远程仓库外,还可以指定指向本地媒体(如光盘驱动器)上的仓库的路径。如果您从 CD 或 DVD 安装 Debian 系统,安装程序将把它作为一个仓库条目添加,以便您以后可以从该光盘上安装其他包:

$ cat /etc/apt/sources.list
#deb cdrom:[Debian GNU/Linux 10.5.0 _Buster_ - Official amd64 NETINST 20200801-11:34]/ buster main
#deb cdrom:[Debian GNU/Linux 10.5.0 _Buster_ - Official amd64 NETINST 20200801-11:34]/ buster main
deb http://deb.debian.org/debian/ buster main
deb-src http://deb.debian.org/debian/ buster main
deb http://security.debian.org/debian-security buster/updates main
deb-src http://security.debian.org/debian-security buster/updates main
# buster-updates, previously known as 'volatile'
deb http://deb.debian.org/debian/ buster-updates main
deb-src http://deb.debian.org/debian/ buster-updates main

Debian 还使用不同的仓库组件,您可以启用或禁用它们。在这个例子中,只有main仓库被启用。main仓库包含基本包。除此之外,还有contribnon-free仓库。contrib仓库包含一些附加包,但这些包的维护不如main仓库中的包那样活跃。non-free仓库包含那些许可证不符合自由软件标准的包——这些包是可再分发的,但可能对修改或修改版本的分发有一定限制,或者没有源代码。

无论好坏,许多设备所需的固件文件都在非自由许可证下,因此实际上,您可能始终会在 Debian 仓库配置行中使用main contrib non-free,而不仅仅是main

请注意,发行版版本(在此为buster)在配置文件中是硬编码的,你需要手动更改它以升级到新的发行版版本。

仓库 URL 也会明确设置,通常是安装时选择的镜像站点。

RPM 包仓库

Fedora 和 Red Hat Enterprise Linux(及其衍生版,如 CentOS 和 Rocky Linux)将仓库文件保存在/etc/yum.repos.d/目录中。通常每个仓库对应一个文件。以下是一个基础 Fedora 仓库文件的示例:

$ cat /etc/yum.repos.d/fedora.repo
[fedora]
name=Fedora $releasever - $basearch
#baseurl=http://download.example/pub/fedora/linux/releases/$releasever/Everything/$basearch/os/
metalink=https://mirrors.fedoraproject.org/metalink?repo=fedora-$releasever&arch=$basearch
enabled=1
countme=1
metadata_expire=7d
repo_gpgcheck=0
type=rpm
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-$releasever-$basearch
skip_if_unavailable=False
[fedora-debuginfo]
name=Fedora $releasever - $basearch – Debug
#baseurl=http://download.example/pub/fedora/linux/releases/$releasever/Everything/$basearch/debug/tree/
metalink=https://mirrors.fedoraproject.org/metalink?repo=fedora-debug-$releasever&arch=$basearch
enabled=0
metadata_expire=7d
repo_gpgcheck=0
type=rpm
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-$releasever-$basearch
skip_if_unavailable=False
[fedora-source]
name=Fedora $releasever – Source
#baseurl=http://download.example/pub/fedora/linux/releases/$releasever/Everything/source/tree/
metalink=https://mirrors.fedoraproject.org/metalink?repo=fedora-source-$releasever&arch=$basearch
enabled=0
metadata_expire=7d
repo_gpgcheck=0
type=rpm
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-fedora-$releasever-$basearch
skip_if_unavailable=False

请注意,Fedora 版本并没有明确引用;相反,有一些占位符变量,如$releasever$basearch。这些高级管理工具会自动用安装系统中的数据替换这些变量。

还有一个选项可以让系统使用镜像列表而不是单一镜像,以确保可靠性和负载均衡。你可以在baseurl选项中指定一个特定的镜像,或者链接到 Metalink 协议以自动获取镜像列表。

高级包管理器

如我们之前讨论的,rpmdpkg都是在一个时代里开发的,那时大多数用户都无法通过网络自动下载软件包,因为大多数计算机的互联网连接都很慢且不稳定,通常通过拨号调制解调器连接。到 90 年代末,互联网连接速度显著提高,因此在线软件包仓库成为了一个逻辑上的可能性。

Debian 项目于 1998 年开发了其高级包管理器 APT,之后大多数基于 Debian 的发行版都在使用它。

基于 RPM 的发行版独立开发了多个高级包管理器。其原因之一是许多基于 RPM 的发行版是在 90 年代独立创建的,而大多数基于 Debian 的发行版是 Debian 本身的分支,创建时间较晚,并且是在 APT 引入后创建的。

由 Red Hat 公司自己维护的 Linux 发行版经历了多个高级包管理器的更替,最初使用的是up2date,该工具主要面向付费客户。后来,该工具被 YUM 取代,YUM 来自一个已经停用的名为 Yellow Dog Linux 的发行版。其名字最初代表的是 Yellow Dog Updater, Modified。后来,它被 DNF 替代——自 2010 年代中期起在 Fedora 中使用,2019 年在 Red Hat Enterprise Linux 第八版中也开始使用 DNF。较旧但仍然支持的 Red Hat Enterprise Linux 及其衍生版继续使用 YUM。幸运的是,虽然 YUM 和 DNF 在内部实现上有很大不同,但它们的用户界面几乎相同——当然,DNF 有一些新的功能。DNF 的名字并没有官方的含义,但最初是来源于DaNdiFied YUM

APT 和 YUM/DNF 的设计选择差异很大,切换时你需要意识到这些差异。让我们学习如何使用它们来安装软件包。

使用 YUM 或 DNF 安装和删除包

安装包的命令是 dnf install。安装包需要管理员权限,所以你需要记得用 sudo 来执行所有这类命令。

当你运行安装命令时,YUM 和 DNF 会自动从仓库下载包索引文件,因此你无需采取任何措施来确保你拥有最新的仓库元数据。这种方法的一个缺点是,有时你可能需要等待它下载这些元数据,在非常慢的连接上可能需要几分钟或更长时间。

然而,安装过程是完全自动化的。YUM 和 DNF 会显示一个将被下载和安装的包列表,以及它们需要的空间。如果你说 yes,它就会继续下载和安装:

$ sudo dnf install hello
Last metadata expiration check: 0:44:59 ago on Mon 17 Oct 2022 10:06:44 AM IST.
Dependencies resolved.
=====================================================================
 Package      Architecture  Version              Repository     Size
=====================================================================
Installing:
 hello        x86_64        2.10-7.fc36          fedora         70 k
Transaction Summary
=====================================================================
Install  1 Package
Total download size: 70 k
Installed size: 179 k
Is this ok [y/N]: y
Downloading Packages:
hello-2.10-7.fc36.x86_64.rpm         278 kB/s |  70 kB     00:00
---------------------------------------------------------------------
Total                                147 kB/s |  70 kB     00:00
Running transaction check
Transaction check succeeded.
Running transaction test
Transaction test succeeded.
Running transaction
Preparing        :                                             1/1 
Installing       : hello-2.10-7.fc36.x86_64                   1/1 
Running scriptlet: hello-2.10-7.fc36.x86_64                    1/1 
Verifying        : hello-2.10-7.fc36.x86_64                1/1
Installed:
  hello-2.10-7.fc36.x86_64
Complete!

删除包也很简单。只需运行 dnf remove hello

与 RPM 不同,DNF 和 YUM 确实有保护包的概念,因此它们会直接拒绝删除像 bashrpm 这样的关键包:

$ sudo dnf remove bash
Error:
 Problem: The operation would result in removing the following protected packages: dnf
(try to add '--skip-broken' to skip uninstallable packages)

这种防止用户错误操作的保护,是使用 YUM 或 DNF 删除包的一个重要原因,尽管技术上 RPM 足以完成这项任务。

使用 APT 安装和删除包

APT 使用不同的元数据下载方法:它将该操作留给用户。与 YUM 和 DNF 不同,APT 永远不会自动下载仓库索引,因此在新部署的基于 Debian 的系统中,尝试搜索或安装包时会出现 包未找到 错误。

注意

APT 包含用于不同目的的子工具:apt-get 用于仓库索引更新和包操作(如安装、删除或列出包文件),apt-cache 用于包搜索。在现代 Debian 版本中,你会发现一个名为 apt 的综合工具。你也可以使用一个名为 aptitude 的替代前端,我们稍后会讨论它。

在你安装任何东西之前,你需要强制下载仓库索引,方法是运行 apt-get updateapt update。其名称中的 update 仅指元数据更新,而不是包更新:

$ sudo apt-get update
Get:1 http://deb.debian.org/debian buster InRelease [122 kB]
Get:2 http://security.debian.org/debian-security buster/updates InRelease [34.8 kB]

                                             Get:3 http://deb.debian.org/debian buster-updates InRelease [56.6 kB]
...

之后,你可以使用 apt-cache searchapt search 来搜索包,并用 apt installapt-get install 来安装它们。

例如,使用以下命令,你可以从仓库中自动安装 hello 包,无需手动下载:

$ sudo apt install hello
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following NEW packages will be installed:
  hello
0 upgraded, 1 newly installed, 0 to remove and 124 not upgraded.
Need to get 56.1 kB of archives.
After this operation, 287 kB of additional disk space will be used.
Get:1 http://deb.debian.org/debian buster/main amd64 hello amd64 2.10-2 [56.1 kB]
Fetched 56.1 kB in 0s (1,385 kB/s)
Selecting previously unselected package hello.
(Reading database ... 113404 files and directories currently installed.)
Preparing to unpack .../hello_2.10-2_amd64.deb …
Unpacking hello (2.10-2) …
Setting up hello (2.10-2) …
Processing triggers for man-db (2.8.5-2) …
$ hello
Hello, world!

APT 还包括对移除关键包的保护。例如,尝试移除 bash 时,需要进行特殊确认:

$ sudo apt remove bash
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following packages will be REMOVED:
  bash
WARNING: The following essential packages will be removed.
This should NOT be done unless you know exactly what you are doing!
  bash
0 upgraded, 0 newly installed, 1 to remove and 124 not upgraded.
After this operation, 6,594 kB disk space will be freed.
You are about to do something potentially harmful.
To continue type in the phrase 'Yes, do as I say!'
 ?]

不用多说,除非你完全确认你所做的操作是安全的,否则不应这么做——例如,如果你已经确保每个用户使用的是 Bash 以外的 shell,且系统中没有任何脚本需要 Bash 而是需要 POSIX Bourne shell。

删除非关键包,如 hello,不会引发任何类似错误:

$ sudo apt-get remove hello
Reading package lists... Done
Building dependency tree... Done
Reading state information... Done
The following packages will be REMOVED:
  hello
0 upgraded, 0 newly installed, 1 to remove and 0 not upgraded.
After this operation, 284 kB disk space will be freed.
Do you want to continue? [Y/n] Y
(Reading database ... 72242 files and directories currently installed.)
Removing hello (2.10-3) ...
$ hello
bash: /usr/bin/hello: No such file or directory

还有一个apt-get purge命令,它不仅移除可执行文件和数据文件,还会移除与软件包相关的所有配置文件。大多数情况下,apt-get remove就足够了,但如果要删除像 Web 服务器这样的软件包,你可能更倾向于使用apt-get purge

搜索软件包

APT 和 YUM/DNF 都提供了搜索命令,因此如果你不知道软件包的确切名称,可以进行搜索。然而,它们会在每个字段中搜索一个模式,但默认只显示软件包名称和简短描述,因此搜索结果可能看起来很奇怪,并且包含一些与你请求无关的条目。

例如,我们在 Debian 系统中尝试搜索hello软件包:

$ apt-cache search hello
junior-system - Debian Jr. System tools
elpa-find-file-in-project - quick access to project files in Emacs
python-flask - micro web framework based on Werkzeug and Jinja2 - Python 2.7
...
libghc-lambdabot-misc-plugins-prof - Lambdabot miscellaneous plugins; profiling libraries
hello - example package based on GNU hello
hello-traditional - example package not using any helper package
iagno - popular Othello game for GNOME

有时,你可能需要搜索提供特定命令或库文件的软件包(例如,如果你遇到脚本错误,提示未找到某个文件)。

在 YUM 和 DNF 中,有一个内置选项:whatprovides。它支持精确的文件路径和通配符匹配。假设你想安装一个提供hello命令的软件包。命令的可执行文件通常位于某个bin/目录中,但我们并不知道它是在/bin/usr/bin/usr/sbin,还是其他地方。然而,我们可以搜索*/bin/hello来查找任何包含该名称的可执行文件。虽然这会包括一些无关的结果,但它会告诉我们我们想知道的内容:

$ dnf whatprovides '*/bin/hello'
hello-2.10-7.fc36.x86_64 : Prints a familiar, friendly greeting
Repo        : fedora
Matched from:
Filename    : /usr/bin/hello
rr-testsuite-5.5.0-3.fc36.x86_64 : Testsuite for checking rr functionality
Repo        : fedora
Matched from:
Filename    : /usr/lib64/rr/testsuite/obj/bin/hello
rr-testsuite-5.6.0-1.fc36.x86_64 : Testsuite for checking rr functionality
Repo        : updates
Matched from:
Filename    : /usr/lib64/rr/testsuite/obj/bin/hello

在基于 Debian 的系统中,事情就不那么简单了。你需要安装一个可选的apt-file工具(apt-get install apt-file),并运行apt-file update来下载额外的索引。

它也不支持通配符匹配,因此如果你不知道确切的路径,你需要提供一个与 Perl 兼容的正则表达式来进行搜索:

$ apt-file search --regexp '(.*)/bin/hello'
Searching, found 10 results so far ...    
hello: /usr/bin/hello                 
hello-traditional: /usr/bin/hello
libpion-dev: /usr/bin/helloserver
pvm-examples: /usr/bin/hello.pvm
pvm-examples: /usr/bin/hello_other

如你所见,YUM 和 DNF 开箱即用提供更多功能,而 APT 可能需要安装额外的软件包。不过,使用这些工具应该能够执行相同的搜索操作。

系统升级

高级包管理器的另一个优点是,它们可以自动升级整个系统(或者至少是从仓库安装的软件包)。

使用 YUM 或 DNF 升级系统

升级所有软件包的命令是dnf upgradeyum upgrade。要强制下载仓库索引,可以添加--refresh。在某些情况下,你还需要移除冲突或过时的软件包以完成升级;在这种情况下,你可能需要使用dnf upgrade --allowerasing

$ sudo dnf upgrade
Last metadata expiration check: 1:33:10 ago on Mon 17 Oct 2022 10:06:44 AM IST.
Dependencies resolved.
============================================================
Package                 Arch   Version         Repo     Size
============================================================
Installing:
kernel                  x86_64 5.19.15-201.fc36      updates  264 k
kernel-core             x86_64 5.19.15-201.fc36      updates   49 M
kernel-devel            x86_64 5.19.15-201.fc36      updates   16 M
kernel-modules          x86_64 5.19.15-201.fc36      updates   58 M
kernel-modules-extra    x86_64 5.19.15-201.fc36      updates  3.7 M
Upgrading:
amd-gpu-firmware        noarch 20221012-141.fc36     updates   15 M
ansible-srpm-macros     noarch 1-8.1.fc36            updates  8.5 k
appstream               x86_64 0.15.5-1.fc36         updates  668 k
bash                    x86_64 5.2.2-2.fc36          updates  1.8 M
...
Installing dependencies:
 iir1                    x86_64 1.9.3-1.fc36          updates   27 k
Removing:
kernel                  x86_64 5.19.11-200.fc36      @updates   0  
kernel-core             x86_64 5.19.11-200.fc36      @updates  92 M
kernel-devel            x86_64 5.19.11-200.fc36      @updates  65 M
kernel-modules          x86_64 5.19.11-200.fc36      @updates  57 M
kernel-modules-extra    x86_64 5.19.11-200.fc36      @updates 3.4 M
Removing dependent packages:
 kmod-VirtualBox-5.19.11-200.fc36.x86_64
                         x86_64 6.1.38-1.fc36         @@commandline
                                                               160 k
Transaction Summary
============================================================
Install   6 Packages
Upgrade  77 Packages
Remove    6 Packages
Total download size: 433 M
Is this ok [y/N]:

如果你选择yes,它将自动下载新版本的软件包,并用它们覆盖旧的软件包。

然而,升级 Fedora 系统到新发行版本需要一个不同的步骤。只有通过 DNF,并且需要从 Fedora 仓库获得的插件才能实现。将旧系统升级到 Fedora 36(2022 年当前版本)的命令序列如下:

$ sudo dnf upgrade --refresh
$ sudo dnf install dnf-plugin-system-upgrade
$ sudo dnf system-upgrade download --releasever=36
$ sudo dnf system-upgrade reboot

dnf system-upgrade download --releasever=36 命令将下载升级到新版本所需的所有软件包,并进行事务检查。在少数情况下,如果某些软件包在新 Fedora 版本中不可用,你需要将它们移除。如果检查成功,你可以通过 dnf system-upgrade reboot 启动升级过程——系统将以最小化环境启动以进行升级,完成后将正常启动到新的 Fedora 版本。

使用 APT 升级系统

APT 包含了不同功能的升级命令,而不是像 YUM 和 DNF 那样使用修饰符选项:

  • apt upgradeapt-get upgrade 仅在有新版本时才会升级已安装的软件包,但不会执行其他操作

  • apt full-upgradeapt-get dist-upgrade 可能会移除软件包,如果需要升级整个系统的话

大多数时候,你应该使用 apt-get dist-upgrade,因为当你在同一发行版版本内更新软件包时,软件包被移除的情况非常罕见;而如果你升级到一个新的发行版版本,就无法避免——在升级之前,你必须以某种方式移除这些软件包。

这就是一个典型的软件包更新示例:

$ sudo apt-get dist-upgrade
Reading package lists... Done
Building dependency tree       
Reading state information... Done
Calculating upgrade... Done
The following package was automatically installed and is no longer required:
  linux-image-4.19.0-10-amd64
Use 'sudo apt autoremove' to remove it.
The following NEW packages will be installed:
  linux-headers-4.19.0-22-amd64 linux-headers-4.19.0-22-common linux-image-4.19.0-22-amd64
The following packages will be upgraded:
  base-files bind9-host bzip2 curl ... open-vm-tools openjdk-11-jre openjdk-11-jre-headless
  openssl publicsuffix python-paramiko qemu-utils rsyslog tzdata unzip vim vim-common vim-runtime vim-tiny xxd xz-utils zlib1g zlib1g-dev
124 upgraded, 3 newly installed, 0 to remove and 0 not upgraded.
Need to get 186 MB of archives.
After this operation, 330 MB of additional disk space will be used.
Do you want to continue? [Y/n]

将基于 Debian 的系统升级到新发行版版本更为复杂。你需要查找新版本的代号,并在你的仓库配置文件中替换旧版本的代号。例如,如果你从 Debian 10 升级到 11,你需要将每个出现的 buster(Debian 10 的代号)替换为 bullseye(Debian 11 的代号),然后运行 apt-get dist-upgrade 并重启系统。

如你所见,系统级别的升级过程在 Red Hat 和基于 Debian 的发行版中概念上是相似的,尽管具体的命令和实现细节有所不同。

总结

在本章中,我们学习了流行 Linux 发行版中使用的低级和高级包管理工具。我们了解了如何使用 DNF 和 APT 安装和移除软件包,并如何执行系统升级。我们还学会了如何手动检查软件包文件并解释其内部结构——虽然这项任务不常见,但了解它对于深入理解软件包管理和软件包管理过程非常重要。然而,包管理工具提供了许多附加选项和功能,因此请确保阅读它们的文档。

在下一章,我们将学习 Linux 系统中的网络配置和调试。

进一步阅读

第九章:网络配置与调试

所有现代系统都联网,因此网络配置和故障排除是每个系统管理员的基本技能。在本章中,我们将了解 Linux 网络栈是如何工作的,以及如何使用相关工具来处理网络——包括通用工具和特定发行版工具。

本章将涵盖以下主题:

  • Linux 网络栈

  • Linux 中的网络接口和地址

  • 路由与邻居表

  • NetworkManager

  • 发行版特定的配置方法

  • 网络故障排除

Linux 网络栈

对于只通过应用程序与网络交互并通过图形用户界面配置网络访问的最终用户来说,他们操作系统的网络栈看起来像是一个单一的抽象。然而,对于管理员来说,理解其结构非常重要,因为栈的不同部分由不同的软件实现,并由不同的工具进行管理。

这与许多专有操作系统的 Linux 发行版形成对比,后者的绝大多数网络功能是内置的,且无法单独替换。在 Linux 发行版中,性能关键的功能由 Linux 内核本身实现,但许多其他功能,如通过 动态主机配置协议 (DHCP) 动态配置 IP 地址和路由,则由第三方工具完成,并且可能有多个竞争的实现。

也有不同的工具用于管理 Linux 内核的网络功能。内核允许用户空间进程通过 Netlink 协议套接字检索和更改其网络配置,从技术上讲,任何人都可以编写一个管理 IP 地址和路由的工具。实际上,有两套网络管理工具:遗留工具(ifconfigvconfigroutebrctl 等),这些工具仅保留以便兼容,并不支持内核网络栈的许多新特性;以及现代的 iproute2 工具包,它通过 iptc 工具提供对所有内核功能的访问。

有时,内核中可能存在多个相同或相似功能的实现。一个显著的例子是 Netfilter 防火墙子系统,它目前包含较旧的 iptables 框架和较新的 nftables 实现。

用户空间工具的不同实现可能是逐渐被更新替代方案所取代的遗留实现,或者也可能有多个具有不同设计目标的替代实现。例如,在 2022 年,ISC DHCP 服务器被其维护者宣布不再支持,并且他们开始从事更新的 Kea 项目。然而,Kea 并不是唯一的 ISC DHCP 服务器替代品。有人可能更愿意切换到其他项目。例如,小型网络可以从dnsmasq中受益,它将 DHCP 服务器与 DNS 转发和其他功能结合在一起,特别适合在硬件资源有限的小型办公室路由器上运行。

一些常用的 Linux 网络功能后端和管理工具总结如下表:

组件 实现 工具
以太网 Linux 内核 网络卡设置调整:ethtool MAC 地址设置,VLAN:iproute2(现代);vconfig(遗留)
Wi-Fi(数据帧处理和转发) Linux 内核 iw
Wi-Fi(认证和接入点功能) hostapd
IPv4 和 IPv6 路由 Linux 内核 iproute2(现代)ifconfigroute等(遗留)
桥接(软件交换机) Linux 内核 iproute2(现代);brctl(遗留)
QoS 和流量整形 Linux 内核 tciproute2的一部分)
IPsec(数据包加密和校验和计算) Linux 内核 iproute2
IPsec(IKE 会话管理) strongSwan, Openswan, Raccoon(遗留)...
DHCP 客户端 ISC DHCP, dhcpcd
DHCP 服务器 ISC DHCP, ISC Kea, dnsmasq

表 9.1 – Linux 网络栈组件

最后,还有一些高级管理工具,如 NetworkManager,它将多个工具和组件集中在一个用户界面下。我们先来了解网络栈的内核部分以及如何使用iproute2进行管理。然后,我们将看到如何通过不同发行版的高级工具简化和自动化管理。

Linux 中的网络接口和地址

网络接口是指能够承载地址的物理和虚拟网络连接的通用术语。物理网卡和内核视角下的网络接口之间并不是一对一的关系。一块有四个端口的网卡是一个单独的 PCI 设备,但其每个端口都被内核视为一个独立的网络连接。

也有虚拟链接。有些虚拟链接与物理网络端口相关联,但其他类型的虚拟链接则完全独立。例如,MACVLAN 链接允许管理员使用不同的 MAC 地址从某些 IP 地址发送流量。由于以太网接口按定义不能拥有多个 MAC 地址,Linux 通过在物理以太网端口上创建虚拟接口并为其分配不同的 MAC 和 IP 地址来解决这个问题。使用 802.1Q VLAN 或 802.3ad QinQ(嵌套 VLAN)复用以太网流量,也通过创建与特定底层链接绑定的虚拟链接来完成。

然而,诸如 IPIP 和 GRE 等隧道协议的接口并未绑定到任何底层链接。它们需要隧道端点地址,但这些地址可以属于任何接口。也有一些虚拟接口用于本地进程通信或分配必须通过任何物理接口可达的地址:

链接类型 与物理设备的关系
以太网,Wi-Fi 与物理卡或卡上的端口相关
802.1Q VLAN,802.3ad QinQ,MACVLAN 绑定到单个物理链接
IPIP,GRE,虚拟 纯虚拟

表 9.2 – 网络链接类型及其与物理设备的关系

在接下来的章节中,我们将学习如何检索网络接口的信息并配置它们。

发现物理网络设备

发现 Linux 系统中的所有物理网络设备可能是一个具有挑战性的任务。它们可以连接到不同的总线,包括 PCI 和 USB,这些总线使用不同的设备类别标识符。

请看以下来自笔记本的 PCI 设备列表:

$ lspci
00:00.0 Host bridge: Intel Corporation 11th Gen Core Processor Host Bridge/DRAM Registers (rev 01)
00:02.0 VGA compatible controller: Intel Corporation TigerLake-LP GT2 [Iris Xe Graphics] (rev 01)
00:04.0 Signal processing controller: Intel Corporation TigerLake-LP Dynamic Tuning Processor Participant (rev 01)
00:06.0 PCI bridge: Intel Corporation 11th Gen Core Processor PCIe Controller (rev 01)
...
00:14.3 Network controller: Intel Corporation Wi-Fi 6 AX201 (rev 20)
...
02:00.0 Non-Volatile memory controller: Samsung Electronics Co Ltd NVMe SSD Controller 980
03:00.0 Ethernet controller: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller (rev 15)

在这里,网络设备很容易通过视觉来识别。这里有一个 Wi-Fi 控制器(00:14.3)和一个以太网卡(03:00.0)。自动过滤掉所有非网络设备的内容稍微有点复杂。我们可以利用网络设备的 PCI 类别是02xx这一事实,并且可以通过lspci -nn命令在输出中包含设备类别号码:

$ lspci -nn | grep -E '\[02[0-9]+\]'
00:14.3 Network controller [0280]: Intel Corporation Wi-Fi 6 AX201 [8086:a0f0] (rev 20)
03:00.0 Ethernet controller [0200]: Realtek Semiconductor Co., Ltd. RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller [10ec:8168] (rev 15)

鉴于此,要查看所有网络设备,您需要同时查看 PCI 和 USB 设备列表,因此使用高级第三方工具如lshw会更好。使用lshw -class命令,您可以一步查看所有可用的网络设备:无论是有线的还是无线的,连接到任何总线的设备。它还显示有关设备的许多附加信息:

$ sudo lshw -class network
  *-network
       description: Wireless interface
       product: Wi-Fi 6 AX201
       vendor: Intel Corporation
       physical id: 14.3
       bus info: pci@0000:00:14.3
       logical name: wlp0s20f3
       version: 20
       serial: 12:15:81:65:d2:2e
       width: 64 bits
       clock: 33MHz
       capabilities: pm msi pciexpress msix bus_master cap_list ethernet physical wireless
       configuration: broadcast=yes driver=iwlwifi driverversion=5.19.16-200.fc36.x86_64 firmware=71.058653f6.0 QuZ-a0-jf-b0-71.u latency=0 link=no multicast=yes wireless=IEEE 802.11
       resources: iomemory:600-5ff irq:16 memory:6013038000-601303bfff
  *-network
       description: Ethernet interface
       product: RTL8111/8168/8411 PCI Express Gigabit Ethernet Controller
       vendor: Realtek Semiconductor Co., Ltd.
       physical id: 0
       bus info: pci@0000:03:00.0
       logical name: enp3s0
       version: 15
       serial: 60:18:95:39:ca:d0
       capacity: 1Gbit/s
       width: 64 bits
       clock: 33MHz
       capabilities: pm msi pciexpress msix bus_master cap_list ethernet physical tp mii 10bt 10bt-fd 100bt 100bt-fd 1000bt-fd autonegotiation
       configuration: autonegotiation=on broadcast=yes driver=r8169 driverversion=5.19.16-200.fc36.x86_64 firmware=rtl8168h-2_0.0.2 02/26/15 latency=0 link=no multicast=yes port=twisted pair
       resources: irq:16 ioport:3000(size=256) memory:72004000-72004fff memory:72000000-72003fff

如您所见,lshw的输出还包括逻辑接口名称,而不仅仅是总线地址。Linux 中的每个网络接口都有一个唯一的名称,但它们的名称并不完全由硬件类型和总线端口决定。让我们更详细地讨论接口命名的问题。

网络接口名称

Linux 内核并不赋予网络接口名称任何特殊的意义。在一些操作系统中,接口名称完全由内核决定,第一块以太网设备可能始终被命名为 Ethernet0,管理员也无法更改该名称。而在 Linux 中情况并非如此,名称可以是任意的。事实上,大多数发行版包含了一个用户空间助手,在启动时根据默认策略或自定义配置重命名网络接口。以前,最常见的助手是 udev,现在通常是 systemd-udevd

历史上,Ethernet 设备默认被命名为ethX,这是内核内建的命名方案。到 2020 年代,大多数发行版切换到 systemd 进行服务管理,并采用其可预测网络接口名称方案作为默认选项。

原始命名方案的问题在于内核的设备探测是非确定性的,因此在某些情况下,尤其是当新增网络卡或移除旧卡时,旧名称可能会被分配给不同的物理设备(例如,一块以前命名为 eth2 的卡可能会变成 eth1)。另一方面,如果一台机器只有一个网络接口,它就保证会被命名为 eth0

systemd 的命名方案是可预测的,因为网络接口的名称在重启和硬件更换后保证保持不变。这样做的代价是用户和脚本编写者不能对名称做出任何假设。即使一台机器只有一个网络卡,它也可能被命名为,例如,eno1(以太网网络,板载,第 1 个)或 enp3s0(以太网网络,PCI,插槽 3:0)。

可以通过在 GRUB 配置中将 net.ifnames=0 添加到内核命令行,或者执行以下命令来切换回原始的命名方案:

$ sudo ln -s /dev/null /etc/systemd/network/99-default.link

还可以通过创建 systemd 链接文件,永久地为某些网络接口分配自定义名称。

使用 ip 命令

在现代 Linux 发行版中,所有网络发现和设置都通过 iproute2 包中的工具或如 NetworkManager 等高级工具完成。我们将忽略遗留工具,如 ifconfig,并专注于 iproute2 中的 ip 工具。

尽管该工具名为 ip,但它的功能要广泛得多,并为内核网络栈中与网络接口、地址和路由相关的所有特性提供接口。

需要注意的是,在某些发行版中,例如 Fedora,那个工具可能安装在/sbin/usr/sbin等用于管理工具的目录下,并且这些目录通常不在普通用户的$PATH环境变量中。因此,从普通用户的 shell 中尝试执行该命令时,会出现命令未找到的错误,即便iproute2已安装。在这种情况下,你可能需要将/sbin添加到你的$PATH中,或者总是使用sudo ip来执行命令。更改网络设置的命令确实需要管理员权限,但查看网络设置的命令通常不需要。

请注意,你使用ip做出的更改仅在下一次重启之前保持有效,永久更改必须在特定发行版的配置文件中进行,或者添加到在启动时执行的脚本中。如果你在带有 NetworkManager 的桌面或笔记本电脑上进行实验,那么它可能会在 Wi-Fi 重新连接时覆盖你的更改。

发现和检查逻辑链路

要查看所有网络接口(包括物理接口和虚拟接口),可以运行ip link list或直接运行ip link。注意,ip允许缩写子命令和选项,因此你也可以写作ip li li,但为了更好的可读性,我们将在本章中使用完整形式:

$ ip link list
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 00:50:56:91:a2:b6 brd ff:ff:ff:ff:ff:ff

在这台虚拟机的输出中,我们看到回环设备(lo)和一张以系统 d 可预测网络接口命名规范命名的以太网卡(ens192)。

回环设备在每个 Linux 系统中都有。它的作用是使本地进程通过 IP 进行通信,并携带为此用途指定的地址:IPv4 的127.0.0.1/8和 IPv6 的::1/128

ens192以太网设备的输出包含更多数据。在link/ether字段中,你可以看到它的 MAC 地址(00:50:56:91:a2:b6)。

你可能还会注意到输出中似乎冗余的<...UP,LOWER_UP>state UP位。它们指的是该网络接口的不同状态:尖括号中的UP告诉我们该链路没有被管理员故意禁用,而state UP则表示实际状态(即它是否连接到其他网络设备—不论是物理电缆还是虚拟链路,虚拟机的情况也是如此)。

为了说明这一区别,让我们检查另一台机器上的物理网络,该网络没有连接到任何设备。要查看单个链路的信息,可以使用ip link show <name>

$ /sbin/ip link show enp3s0
2: enp3s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN mode DEFAULT group default qlen 1000
    link/ether 60:18:95:39:ca:d0 brd ff:ff:ff:ff:ff:ff

如你所见,尖括号中的状态是UP,但state DOWN告诉我们它并未激活,而NO-CARRIER解释了原因——它已经断开连接(尽管连接了设备,网络链路也可能会处于断开状态,例如由于与另一端的设置不匹配)。

现在,让我们禁用一个链路,看看故意禁用的链路是什么样的。你可以使用sudo ip link set dev <name> down来实现:

$ ip link show eth2
6: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
    link/ether 00:50:56:9b:18:ed brd ff:ff:ff:ff:ff:ff
$ sudo ip link set dev eth2 down
$ ip link show eth2
6: eth2: <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast state DOWN mode DEFAULT group default qlen 1000
    link/ether 00:50:56:9b:18:ed brd ff:ff:ff:ff:ff:ff

你可以看到,当链路被断开时,UP 标记从尖括号内的部分消失了,state 字段也变成了 DOWN

查看和更改以太网链路 MAC 地址

每个以太网和 Wi-Fi 卡都有一个全球唯一的、预烧录的 MAC 地址。为了确保如果它们连接到同一网络时不会发生冲突,制造商请求一块块的 MAC 地址,并跟踪他们分配给硬件产品的每个 MAC 地址,以确保每个地址不会被重复分配。

然而,最终用户可能有自己的原因来为网络接口分配 MAC 地址。例如,许多互联网服务提供商会注册用户路由器端口的第一个 MAC 地址,然后要求所有后续连接尝试使用相同的地址。如果用户更换或升级了路由器(或其中的网卡),通常更容易直接分配原端口的 MAC 地址,而不是请求 ISP 支持更新他们的记录。你可以使用 ip link set dev <name> address <MAC> 来更改 MAC 地址(直到下次重启)。

$ ip link show enp3s0
2: enp3s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN mode DEFAULT group default qlen 1000
    link/ether 60:18:95:39:ca:d0 brd ff:ff:ff:ff:ff:ff
$ sudo ip link set dev enp3s0 address de:ad:be:ef:ca:fe
$ /sbin/ip link show enp3s0
2: enp3s0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc fq_codel state DOWN mode DEFAULT group default qlen 1000
    link/ether de:ad:be:ef:ca:fe brd ff:ff:ff:ff:ff:ff permaddr 60:18:95:39:ca:d0

注意到 ip link show 现在显示的是新手动分配的 MAC 地址(de:ad:be:ef:ca:fe)。

虽然 ip 只显示内核用于发送以太网帧的 MAC 地址,但你可以通过 ethtool 来检索默认的、预烧录的地址。你可以使用 ethtool --show-permaddr 或其简短版本 ethtool -P,如下所示:

$ ethtool --show-permaddr enp3s0
Permanent address: 60:18:95:39:ca:d0
$ ethtool -P enp3s0
Permanent address: 60:18:95:39:ca:d0

即使更改 MAC 地址不是很常见的任务,了解如何更改也很有用。接下来,我们将学习如何管理 IP 地址。

查看和更改 IP 地址

查看和更改 IP 地址的命令与查看和更改链路和 MAC 地址的命令相似,但使用的是 address 命令族,而不是 link。要查看所有链路上的地址,你可以运行 ip address show、仅运行 ip address 或简写版本 ip a

$ ip address show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: ens192: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP group default qlen 1000
    link/ether 00:50:56:91:a2:b6 brd ff:ff:ff:ff:ff:ff
    inet 10.217.40.163/24 brd 10.217.40.255 scope global dynamic ens192
       valid_lft 58189sec preferred_lft 58189sec
    inet6 fe80::250:56ff:fe91:a2b6/64 scope link 
       valid_lft forever preferred_lft forever

你还可以将输出限制为仅显示一个接口,例如在 ip address show lo 中。

ip address show 的输出包括以太网和其他数据链路层接口的 MAC 地址,因此通常你可以使用该命令来代替 ip link list,除非你特别想从输出中排除 IP 地址。

可以使用 -4-6 选项仅显示 IPv4 或仅显示 IPv6 地址。我们可以在回环接口(lo)上演示,因为它保证有 IPv4 和 IPv6 地址(除非明确禁用 IPv6):

$ ip -4 address show lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
$ ip -6 address show lo
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 state UNKNOWN qlen 1000
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever

现在让我们来看一下如何添加和删除地址。为了进行安全实验,确保不会影响任何用于实际流量的网络接口,我们将创建一个虚拟接口。虚拟接口在概念上类似于回环接口。然而,同一系统中可以有多个虚拟接口,而回环接口只能有一个(这种情况是 Linux 独有的;许多其他操作系统允许多个回环接口,而不是使用不同类型的接口):

$ sudo ip link add name dummy1 type dummy
$ sudo ip link set dev dummy1 up
$ ip link list type dummy
16: dummy1: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/ether 9a:9c:10:42:a6:ea brd ff:ff:ff:ff:ff:ff

所有虚拟接口在 Linux 中默认处于 DOWN 状态,因此我们手动将 dummy1 链接置为启用状态。现在,它已准备好进行地址实验。

你可以使用 ip address add <addr> dev <name> 命令为网络接口分配一个地址。就像 MAC 地址一样,这些更改不会在重启后保存,因此这种方法仅适用于实验和故障排除会话。我们将使用来自 192.0.2.0/24 网络的地址——这是一个专门为示例和文档保留的网络:

$ sudo ip address add 192.0.2.1/24 dev dummy1
$ ip address show dev dummy1
16: dummy1: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether 9a:9c:10:42:a6:ea brd ff:ff:ff:ff:ff:ff
    inet 192.0.2.1/24 scope global dummy1
       valid_lft forever preferred_lft forever
    inet6 fe80::989c:10ff:fe42:a6ea/64 scope link 
       valid_lft forever preferred_lft forever

请注意,第二次执行 ip address add 命令并使用不同的地址时,原地址不会被替换,而是会添加第二个地址。在 Linux 中,单个网络接口上的地址数量没有限制;你可以任意添加多个地址。如果你想替换地址,可以先添加新地址,然后删除旧地址。让我们来看一下如何将 192.0.2.1 替换为 192.0.2.2

$ sudo ip address add 192.0.2.2/24 dev dummy1
$ sudo ip address show dummy1
16: dummy1: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether 9a:9c:10:42:a6:ea brd ff:ff:ff:ff:ff:ff
    inet 192.0.2.1/24 scope global dummy1
       valid_lft forever preferred_lft forever
    inet 192.0.2.2/24 scope global secondary dummy1
       valid_lft forever preferred_lft forever
$ sudo ip address delete 192.0.2.1/24 dev dummy1
$ sudo ip address show dummy1
16: dummy1: <BROADCAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default qlen 1000
    link/ether 9a:9c:10:42:a6:ea brd ff:ff:ff:ff:ff:ff
    inet 192.0.2.2/24 scope global dummy1
       valid_lft forever preferred_lft forever

也可以使用 sudo ip address flush dev <name> 命令一次性移除网络接口上的所有地址。

大多数时候,你将使用更高级的配置工具来配置 IP 地址,我们将在本章稍后讨论这些工具。然而,了解这些命令可以帮助你在故障排除会话或紧急配置更改期间验证地址配置并临时更改网络接口地址。

路由和邻居表

为了能够与网络上的其他主机通信,主机仅有地址是不够的。它还需要知道如何到达其他主机。现代网络使用分层协议栈,Linux 内核负责根据 OSI 模型的 数据链路 层和 网络 层。

在数据链路层,有以太网和 Wi-Fi 等协议——这两者都是多址广播网络,并要求在同一网络段内动态发现邻居。在数据链路层,主机通过其 MAC 地址来标识。数据链路层的直接通信仅在同一段内有效。如果网络层协议封装在数据链路层协议帧中并必须进一步传输,它将从原始帧中提取并重新封装到新帧中。

在数据链路层之上是网络层协议——IPv4 和 IPv6。IP 数据包可以发送到同一数据链路层段中的主机,也可以路由到其他网络,并且可能通过许多数据链路层连接到达目标。

然而,为了能够将 IP 数据包发送到另一台主机或路由器,内核需要在这些机器的 IP 地址和 MAC 地址之间建立关联,并维护这些关联的表格。

ARP 和 NDP 邻居表

IPv4 和 IPv6 协议有许多相似之处,但使用不同的邻居发现机制。较老的 IPv4 协议使用 地址解析协议ARP)来确定具有给定 IP 地址的主机的 MAC 地址。ARP 并非设计为可扩展的,而从 IPv4 的 32 位地址到 IPv6 的 128 位地址的转换要求开发新的协议,因此设计者利用这个机会修订了许多旧的假设并加入了许多新功能。最终的结果是 邻居发现协议NDP),与 ARP 不同,它允许主机发现路由器、动态配置公网地址,并检测地址冲突。

要查看 ARP 表,可以运行 ip -4 neighbor show。你也可以简化为 ip -4 neighborip -4 neigh。请注意,这些命令也支持英式拼写(neighbour),如果你更喜欢使用它的话。如果省略 -4-6,该命令将显示两个协议的条目,因此如果你的系统未配置 IPv6 或不想过滤,可以省略 -4

$ ip -4 neighbor show
10.217.32.199 dev eth1 lladdr 00:0c:29:62:27:03 REACHABLE
10.217.32.132 dev eth1  FAILED
10.217.32.111 dev eth1  FAILED
10.217.32.102 dev eth1 lladdr 00:50:56:85:d9:72 DELAY
10.217.32.201 dev eth1 lladdr 00:50:56:9b:dd:47 STALE
10.217.32.99 dev eth1 lladdr 00:0c:29:5f:92:1d REACHABLE
10.217.32.202 dev eth1 lladdr 00:50:56:9b:bc:28 STALE
10.217.32.117 dev eth1 lladdr 00:50:56:9b:7e:e3 REACHABLE

也可以过滤输出,仅显示一个网络接口的条目,例如,使用 ip -4 neighbor show dev eth1

用于 MAC 地址的字段称为 链路层地址lladdr)。原因是邻居表的输出格式对于多个数据链路层协议是相同的,而这些协议可能不会将其链路层地址称为 MAC 地址。还有一些情况,当 IPv4 主机的链路层地址本身就是一个 IPv4 地址时——例如,多点 GRE 隧道就是这样工作(它是动态多点 VPN 技术的一部分,此外还包括用于加密的 IPsec 和用于邻居发现的下一跳解析协议)。

每个关联是一个三元组而不是二元组:MAC 地址、IPv4 地址和网络接口。每个以太网接口属于其自己的数据链路层段,因此为了正确地发送 IP 数据包,系统需要知道从哪个网卡发送它。MAC 地址必须仅在同一段内唯一。

可以只显示具有特定状态的条目。例如,以下是如何仅查看最近发现或确认的地址关联:

ip neighbor show dev <name> nud reachable

过时的条目最终会从表中消失。当 IP 地址被移到具有不同 MAC 地址的机器上时,内核也最终会发现它。但是如果等待不可行,并且必须在最短的停机时间内移动 IP 地址,可以手动删除条目,并在看到该 IP 地址的流量后立即强制发出新的 ARP 请求。删除特定 IP 地址条目的命令如下:

sudo ip neighbor del dev <name> <IP address>

还有一个命令可以删除特定网络接口的所有条目:

ip neighbor flush dev <name>

尽管 ARP 和 NDP 协议的内部工作原理不同,我们讨论的所有命令都适用于 IPv4 和 IPv6 的邻居表。

路由和路由表

IPv4 和 IPv6 是路由协议,允许它们在由多个独立段组成的大型网络中使用,例如互联网。以太网网络段是平面的,并且没有分组 MAC 地址的机制:如果网络中有一百个主机,交换机必须维护一百条 MAC 地址表条目,每个主机也需要在其表中保持与所有需要通信的主机的 MAC 地址。该方法对最大网络规模有明显限制。它还使得不可能有多个路径通向网络的同一部分,因为数据链路层段中的所有主机必须直接通信。

相反,IP 网络被划分为子网,这些子网通过路由器连接——路由器是专门用于在主机之间转发数据包的设备(如今许多家庭/小型办公室和企业/服务提供商的路由器都运行 Linux)。IP 寻址架构最重要的特性是子网可以聚合。例如,如果一个网络内部由两个连续的子网组成,每个子网包含 32 个主机,比如192.0.2.0/27192.0.2.32/27,那么其他网络可以将它视为一个包含 64 个主机的单一网络——192.0.2.0/26

只连接到单一上游路由器(通常是互联网服务提供商)的主机和路由器,因而只能存储到整个 IP 范围的单一路由:对于 IPv4 是0.0.0.0/0,对于 IPv6 是::/0。这样的路由被称为默认路由

查看路由表和路由

让我们检查连接到单个路由器的 Linux 主机的路由表。你可以通过ip route show或者ip route来查看 IPv4 路由。与ip neigh不同,后者显示 IPv4 和 IPv6 邻居,除非使用-4-6进行过滤,该命令默认显示 IPv4 路由,需要-6选项来查看 IPv6 路由:

$ ip route show
default via 172.20.10.1 dev eth0 proto dhcp src 172.20.10.2 metric 100
172.20.10.0/28 dev eth0 proto kernel scope link src 172.20.10.2 metric 100

第一条条目是默认路由——指向0.0.0.0/0网络的路由,覆盖所有可能的 IPv4 地址。网关是172.20.10.1。出接口是eth0。从proto dhcp,我们可以推断它是从 DHCP 服务器接收到的。协议字段仅用于信息展示,内核不会使用它进行路由选择。内部,它是一个从0255的数字,其中一些数字被映射到/etc/iproute2/rt_protos配置文件中的协议名称。

第二条通向172.20.10.0/28网络的路由是一个连接路由,它仅仅告诉系统可以通过某个网络接口直接与某个子网中的主机通信。注意,它没有网关,只有接口字段(dev)。如果我们检查该机器上的 IPv4 地址,我们会看到它的地址是172.20.10.2/28

$ ip address show eth0
17: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP group default qlen 1000
    link/ether 4e:79:75:4e:61:9a brd ff:ff:ff:ff:ff:ff
    altname enp0s20f0u3u3c4i2
    inet 172.20.10.2/28 brd 172.20.10.15 scope global dynamic noprefixroute eth0
       valid_lft 65207sec preferred_lft 65207sec
    inet6 fe80::a487:4fbe:9961:ced2/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever

每当一个 IP 地址被添加到网络接口时,内核会计算它的子网地址并添加一条到该子网的路由。由于172.20.10.0/28子网比0.0.0.0/0子网小,因此该路由将用于与该子网的主机通信,而不是默认路由。这就是最长匹配规则

内核协议号保留用于标记这些没有管理员直接请求而创建的辅助路由。你可以通过运行以下命令查看某个特定协议的路由:

$ ip route show proto kernel 
172.20.10.0/28 dev eth0 scope link src 172.20.10.2 metric 100

内核还可以告诉你它将使用哪个路由来访问某个主机或网络。例如,如果你想知道它如何到达一个192.0.2.1地址的主机,可以运行以下命令:

$ ip route get 192.0.2.1
192.0.2.1 via 172.20.10.1 dev eth0 src 172.20.10.2 uid 1000 cache

由于这台机器只有一条默认路由,因此每个主机的答案都是其默认网关——172.20.10.1。然而,在具有多条连接到多个网络的路由器上,ip route get命令会非常有用。

配置路由

许多主机系统只是从 DHCP 获取它们的默认路由,但 Linux 内核中的路由功能要复杂得多。

使用ip工具配置路由的一个问题是,和所有通过这种方式配置的内容一样,这些路由只有在下次重启之前有效,前提是网络状况完美。另一个问题是,如果一个网络接口停止工作(比如物理网络卡由于断开电缆,或虚拟链路因协议重置导致),与该接口相关的所有路由都会被永久删除,并需要通过用户空间程序恢复。在企业和服务提供商路由器中,用户空间程序通常是一个路由协议栈服务,例如 Free Range Routing 或 BIRD。这些路由协议栈服务实现了动态路由协议,还帮助管理静态路由并在网络接口状态变化时保持它们的活动状态。在主机系统上,可能是 NetworkManager 或其他网络配置前端。然而,了解如何手动创建路由在需要实验或进行紧急修复时仍然是有用的。

要创建一条具有特定网关地址的静态路由,你可以使用以下命令:

ip route add <network> via <gateway>

让我们在一个虚拟接口上演示一下。首先,我们将创建一个虚拟接口,并为其分配192.0.2.1/24地址,以强制内核创建一条到192.0.2.0/24的连接路由,并为我们的虚拟网关腾出空间。我们将使用203.0.113.0/24进行实验——这是另一个保留用于示例和文档的网络,保证不会出现在公共互联网中,因此我们可以确保不会干扰任何真实流量:

$ sudo ip link add name dummy1 type dummy
$ sudo ip link set dev dummy1 up
$ sudo ip address add 192.0.2.1/24 dev dummy1

现在我们可以添加一条路由,验证它是否存在,并尝试将dummy1停用,看看会发生什么:

$ sudo ip route add 203.0.113.0/24 via 192.0.2.10
$ ip route
default via 172.20.10.1 dev eth0 proto dhcp src 172.20.10.2 metric 100  
172.20.10.0/28 dev eth0 proto kernel scope link src 172.20.10.2 metric 100 
192.0.2.0/24 dev dummy1 proto kernel scope link src 192.0.2.1 
203.0.113.0/24 via 192.0.2.10 dev dummy1
$ sudo ip link set dev dummy1 down
$ ip route
default via 172.20.10.1 dev eth0 proto dhcp src 172.20.10.2 metric 100  
172.20.10.0/28 dev eth0 proto kernel scope link src 172.20.10.2 metric 100

你可以看到,新添加的路由在其条目中自动包含了正确的网络接口:203.0.113.0/24 via 192.0.2.10 dev dummy1。内核检查了到192.0.2.10的路由——我们设置的网关地址——并发现它可以通过dummy1访问(至少名义上是这样)。

当我们关闭dummy1时,内核也移除了与之相关的连接路由192.0.2.0/24。这使得192.0.2.10网关变得不可达,因此内核也移除了所有因该变化而导致网关不可达的路由。内核通常不允许用户创建网关不可达的路由,并且能够检测递归路由(即,网关无法通过连接路由直接访问的路由)。但是,通过在命令中添加一个特殊的onlink标志,可以禁用该可达性检查,例如:

sudo ip route add 203.0.113.0/24 via 192.0.2.10 onlink

如果你连接到以太网交换机或其他类型的多接入网络,你必须在路由中指定网关,因为仅仅通过以太网设备发送数据包是不可能的——数据包中必须有目的地址,因为同一段网络中可能有多个主机。然而,有些网络接口是点对点的,并且另一端只有一个系统。物理点对点连接,如串行广域网连接,现在已经几乎灭绝,但虚拟点对点连接很常见。如果你通过 PPPoE 连接到互联网,你可以只用sudo ip route add 0.0.0.0/0 dev ppp0或类似命令创建默认路由——不需要指定网关地址。

如果你只有一条通向某个网络的路由并且想要删除它,你只需使用sudo ip route del <network>,如果你有多条通向同一网络的路由,你需要指定网关或接口,才能准确删除你想要删除的路由,例如sudo ip route del 203.0.113.0/24 via 192.0.2.10

当存在多条路由指向同一目的地时,有两种情况。首先,可以通过为其设置metric命令来创建备份路由。例如,如果我们创建一条metric100的路由,它会保留在路由表中,但在存在带有较低metric值的路由时不会被使用。如果某条路由消失,内核会自动开始使用备份路由。我们可以通过以下命令来演示:

$ sudo ip route add 203.0.113.0/24 via 192.0.2.10
$ sudo ip route add 203.0.113.0/24 via 192.0.2.20 metric 100
$ ip route
...
192.0.2.0/24 dev dummy1 proto kernel scope link src 192.0.2.1 
203.0.113.0/24 via 192.0.2.10 dev dummy1 
203.0.113.0/24 via 192.0.2.20 dev dummy1 metric 100 
$ ip route get 203.0.113.100
203.0.113.100 via 192.0.2.10 dev dummy1 src 192.0.2.1 uid 1000 cache
$ sudo ip route del 203.0.113.0/24 via 192.0.2.10
$ ip route get 203.0.113.100
203.0.113.100 via 192.0.2.20 dev dummy1 src 192.0.2.1 uid 1000 cache

其次,内核可以并行使用多条路由到相同的目的地进行负载均衡和冗余。如果不同路径的带宽不同,你可以为每个网关指定不同的权重,方法如下:

$ sudo ip route add 203.0.113.0/24 nexthop via 192.0.2.10 weight 1 nexthop via 192.0.2.20 weight 10
$ ip route 
...
192.0.2.0/24 dev dummy1 proto kernel scope link src 192.0.2.1 
203.0.113.0/24 
	nexthop via 192.0.2.10 dev dummy1 weight 1 
	nexthop via 192.0.2.20 dev dummy1 weight 10

请注意,这种负载均衡方法仅适用于具有直接路由连接的网络,因为属于同一连接的数据包将通过两条路径发送,而这些数据包的返回路径也是不可预测的。那些连接到多个 ISP,但使用 NAT 在所有内部网络主机之间共享公共 IPv4 地址的系统,需要更复杂的负载均衡配置,确保整个连接被平衡,并且每个数据包使用相同的外发路径,但这超出了本书的讨论范围。

还有一些特殊用途的路由,确保目标网络变得不可达。这些路由有两种类型:blackholeunreachable/prohibit/throw。两者都会使内核丢弃发送到特定网络的所有数据包,但 blackhole 路由会让它默默丢弃数据包,而另一种类型则还会向源主机发送一个 ICMP 错误。

blackhole 路由通常用作一种粗糙但高效的外出流量过滤器。这些路由可以用来阻止网络内部的主机与已知的恶意目标通信,例如僵尸网络的指挥与控制节点。在遭遇传入的 DDoS 攻击时,它们还可以用来在路由器处阻止流量,使其无法到达目标,这样你可以重新配置目标主机以提高性能,或者至少避免在攻击结束前使其过载。你可以通过 sudo ip route add blackhole <network> 来创建一个 blackhole 路由:

$ sudo ip route add blackhole 203.0.113.0/24 
$ ip route
... 
blackhole 203.0.113.0/24
$ ping 203.0.113.10
ping: connect: Invalid argument

如果一个网络被黑洞化,你将无法从本地机器连接到该网络中的任何主机。对于使用该机器作为路由器的主机来说,情况看起来像是它们的数据包没有收到任何回复。

另外三种路由类型(unreachablediscardthrow)不能用于 DDoS 保护,因为当数据包的目标与这些路由匹配时,内核不仅会丢弃数据包,还会生成一个 ICMP 数据包通知发送方他们的数据包未能到达目标,这样只会通过生成更多的流量让情况变得更糟。它们最适合用于公司内部网络,用于执行政策,且易于调试。如果你不希望你的主机向一个假设的 203.0.113.113 主机发送任何流量,你可以运行 sudo ip route add prohibit 203.0.113.113/32,任何尝试连接到该主机的人将收到一条消息,提示该主机被管理策略禁止访问(而使用 blackhole 路由时,客户端无法轻易判断这是否是政策问题或网络问题)。

如你所见,ip 命令提供了丰富的功能,用于配置和查看路由表及邻居表。手动配置路由并不是一项常见任务,但知道如何操作仍然很重要,而路由和邻居表的查询命令在日常的诊断和调试工作中非常有用。

NetworkManager

服务器和嵌入式设备通常具有固定的、静态分配的 IP 地址,但桌面计算机,尤其是笔记本电脑,可能需要动态连接到多种不同类型的网络。带有笔记本电脑的系统管理员可能需要连接到服务器机房中的有线以太网网络、办公室、家中以及公共场所(如咖啡馆)的 Wi-Fi 网络,还需要通过 VPN 隧道从家中连接到公司网络。由于许多笔记本电脑不再配备内置有线网络卡,因此可能需要使用 USB 以太网适配器,系统必须处理不仅是按需网络连接,还要处理热插拔网络设备。

通过配置文件和命令手动管理此类配置会很繁琐,因此人们创建了自动化的软件项目。这些项目依赖于常用的工具,如 ip 实用程序和第三方项目,如 strongSwan 或 xl2tpd 用于 VPN 连接,但将它们集成到统一的用户界面下,并包括一个事件处理机制,以应对热插拔硬件更改和用户连接到不同网络的请求。

最流行的解决方案是 NetworkManager 项目,该项目由 Red Hat 于 2004 年启动。如今,大多数 Linux 发行版至少在桌面安装中包含它。

使用 dnf search NetworkManager(在 Fedora 或 RHEL 上)或 apt-cache search NetworkManager(在基于 Debian 的发行版上),您将看到包含各种连接类型插件的包,这些插件从广泛使用且知名的 NetworkManager-wifiNetworkManager-openvpn 到不常见且实验性的 NetworkManager-iodine——一种通过将数据传输到 DNS 包中来绕过防火墙的解决方案。

它还有多个用户界面。您可以在桌面环境面板的托盘区域看到的网络小程序就是 NetworkManager 的用户界面。在 MATE 桌面和许多其他桌面环境中,您可以验证通过右键点击网络图标并选择 关于 菜单项,您将看到如下屏幕:

图 9.1 – NetworkManager 小程序版本信息对话框

图 9.1 – NetworkManager 小程序版本信息对话框

在右键菜单的 编辑连接 部分,您可以创建新连接或编辑现有连接:

图 9.2 – NetworkManager 连接编辑对话框

图 9.2 – NetworkManager 连接编辑对话框

最新版本的 NetworkManager 将连接配置文件保存在专用目录中,而旧版本则使用特定于发行版的格式。如果保存了一个连接,可以在 /etc/NetworkManager/system-connections 目录下找到其文件。请注意,这些文件对于没有特权的用户是不可读的。您可以按照以下方式查看办公室 Wi-Fi 连接的文件:

$ sudo cat /etc/NetworkManager/system-connections/Office\ Wi-Fi.nmconnection
[connection]
id=Office Wi-Fi
uuid=6ab1d913-bb4e-40dd-85a7-ae03c8b62f06
type=wifi
[wifi]
mode=infrastructure
ssid=Office Wi-Fi
[wifi-security]
key-mgmt=wpa-psk
psk=SomePassword
[ipv4]
may-fail=false
method=auto
[ipv6]
addr-gen-mode=stable-privacy
method=auto
[proxy]

还有一个适用于 NetworkManager 的文本模式界面,可以在无头机器上提供类似 GUI 的体验。它通常不会默认安装,但在 Fedora 上,可以通过安装 NetworkManager-tui 包来获得。

图 9.3 – nmtui,NetworkManager 的基于文本的界面

图 9.3 – nmtui,NetworkManager 的基于文本的界面

最后,nmcli 工具允许从命令行管理 NetworkManager 连接。如果您已经创建了一个连接,可以通过 nmcli connection up <name> 启动它(类似于 nmcli connection up "Office Wi-Fi"),或使用 nmcli connection down <name> 将其关闭。如果没有 nmtui 或图形桌面环境可用,它还提供了交互式的连接编辑功能。

如您所见,NetworkManager 使得管理典型的网络配置变得简单,尤其是在桌面机器上。接下来,我们将学习不使用 NetworkManager 的基于发行版的特定配置方法。

基于发行版的特定配置方法

NetworkManager 被许多发行版在桌面系统上使用,但许多 Linux 发行版也使用自定义的网络配置文件和脚本。一些发行版仍在使用这些方法,而其他系统则迁移到 NetworkManager,但仍保留旧的配置方法,作为替代或在长期支持的旧版中使用。

Debian

在 Debian 中,网络接口的配置文件是 /etc/network/interfaces。与 NetworkManager 的原生格式不同,这种方式允许将所有接口的配置保存在一个文件中。为了使配置更加模块化和易于阅读,可以将单个接口的配置文件存储在 /etc/network/interfaces.d/ 目录中。

接口配置也通过链接名称而非任意描述和 UUID 来识别。例如,这是如何为一个内建的以太网设备 eno1 设置静态地址用于 IPv4 和 IPv6 的示例:

iface eno1 inet static
      address 203.0.113.123/24
      gateway 203.0.113.1
iface eno1 inet6 static
      address 2001:db8:abcd::123/64
      gateway 2001:db8:abcd::1

您还可以在接口启用和禁用时执行自定义命令,使用 pre-upupdownpost-down 选项。例如,当 eno1 启动时,自动添加路由,可以运行以下命令:

iface eno1 inet static
      address 203.0.113.123/24
      gateway 203.0.113.1
      up ip route add 192.0.2.0/24 via 203.0.113.1

启用和禁用接口的工具分别命名为 ifupifdown。它们仅对特权用户可用,因此您需要使用 sudo 来运行它们,如 sudo ifup eno1sudo ifdown eno1

旧版基于 Red Hat 的发行版

Fedora 和 RHEL 8 及以上版本(以及其衍生版如 Rocky Linux)使用 NetworkManager 作为其网络配置系统。然而,直到 RHEL7,它们使用的是不同的系统。其配置文件目录是 /etc/sysconfig/network-scripts,每个接口都有自己的文件。例如,这是如何将 203.0.113.113/24 地址静态分配给一个内建以太网接口 eno1

$ cat /etc/sysconfig/network-scripts/ifcfg-eno1
DEVICE=eno1
BOOTPROTO=none
ONBOOT=yes
PREFIX=24
IPADDR=203.0.113.113

一种 Red Hat 特定的方法,通过使用 service network restart 命令重新读取并应用所有旧式网络配置。

如您所见,特定于分发版的方法在概念上是相似的,尽管配置语法和相同意义选项的名称可以大相径庭。如果有疑问,您应该始终查阅文档。现在我们已经介绍了最常见的网络配置方法,接下来我们还应该学习如何验证配置是否按预期工作。

网络故障排除

网络故障排除是一个广泛的主题。然而,大多数时候,专家使用的工具与每个新手可用的工具相同,而且这些工具在基本使用层面上并不难学会。新手和专家之间的主要区别在于他们如何解读输出结果并选择正确的选项。

使用 ping

ping 工具的名称来源于声呐的声音——声呐是一种利用声波脉冲来探测水下物体的设备。该命令通过发送 ICMP 数据包并等待回复,隐喻性地探测远程主机。声呐的隐喻有些牵强,因为声波脉冲是被动反射的,而 ICMP 数据包的交换需要远程主机的配合。

尽管如此,运行正确实现的网络栈的主机在收到回显请求时,应该会回复一个 ICMP 回显应答数据包。在最基本的层面上,ping 一台主机可以告诉你该主机是否在线,以及是否存在通向它的有效网络路径。

默认情况下,Linux 版本的 ping 将无限期地继续发送回显请求。这与其他一些版本(如 Windows)不同,后者默认在发送有限数量的数据包后终止。如果您希望 Linux 的 ping 采用这种行为,您可以使用 -c 来指定请求次数,例如 -c5,以发送五个请求,如下所示:

$ ping -c5 9.9.9.9
PING 9.9.9.9 (9.9.9.9) 56(84) bytes of data.
64 bytes from 9.9.9.9: icmp_seq=1 ttl=56 time=31.1 ms
64 bytes from 9.9.9.9: icmp_seq=2 ttl=56 time=24.8 ms
64 bytes from 9.9.9.9: icmp_seq=3 ttl=56 time=21.7 ms
64 bytes from 9.9.9.9: icmp_seq=4 ttl=56 time=115 ms
64 bytes from 9.9.9.9: icmp_seq=5 ttl=56 time=22.8 ms
--- 9.9.9.9 ping statistics ---
5 packets transmitted, 5 received, 0% packet loss, time 4004ms
rtt min/avg/max/mdev = 21.666/43.061/115.059/36.145 ms

如果 ICMP 回显请求成功到达目标且目标回复,则对这些数据包的回复将来自目标地址。

如果数据包无法到达目标主机,因为没有到达它的路由,您将收到来自路径上最后一个路由器的回复,该路由器无法将数据包继续转发,因为它找不到路由。例如,考虑从私有网络中 ping 一台主机时的输出:

$ ping 10.217.32.33
PING 10.217.32.33 (10.217.32.33) 56(84) bytes of data.
From 10.217.41.49 icmp_seq=1 Destination Host Unreachable
From 10.217.41.49 icmp_seq=2 Destination Host Unreachable

在这种情况下,接收到我们的 ICMP 回显请求数据包并未能继续转发它们的最后一个路由器地址是 10.217.41.49,所以如果我们想检查为什么该主机不可访问,那么这个路由器将是我们的起点。

使用 traceroute

ping 可以告诉你主机是否可访问,如果不可访问,则可以指出 ICMP 回显数据包在网络中到达的具体位置。然而,它不能告诉你网络路径的具体情况。

为了发现数据包通过网络的路径,你可以改用 traceroute 工具。简而言之,该工具发送具有故意较小 TTL(针对 IPv4)或跳数(针对 IPv6)的数据包,并记录迫使路由器丢弃测试数据包并记录其 ICMP 错误信息的过程,从而记录数据包的路径。

每个 IP 数据包都有一个字段,显示它已经在路由器之间转发了多少次。在 IPv4 中,该字段的名称为 0,表示数据包被丢弃。每当路由器因该原因丢弃数据包时,它会通过 ICMP TTL 超时消息通知发送方。

因此,如果你故意将数据包的 TTL 设置为 1,它必定会被路径上的第一个路由器丢弃。通过关于丢弃数据包的 ICMP 响应,你可以了解到该路由器的地址。通过逐步增加 TTL 值重复这一过程,你可以了解路径上的每个路由器——或者至少是每个合作的路由器,它会发送 ICMP TTL 超时的回复。

在公共互联网和其他大型网络中,解读traceroute输出结果时存在许多微妙之处。一些路由器可能根本不会生成 ICMP TTL 超时消息,或者仅在其负载较低时生成这些消息,因此路径可能会显示出空白(你会看到 * * * 符号,表示该路由器从未生成这些响应——traceroute 在放弃之前会尝试三次探测)。由于某些网络段使用多协议标签交换MPLS)而非 IP 路由,traceroute 显示的路径可能并非真实路径。然而,它仍然是一个有用的工具,在私有网络中,它的输出通常是实际路径。

以下是其输出的可能样式:

$ traceroute 10.217.32.179
traceroute to 10.217.32.179 (10.217.32.179), 30 hops max, 60 byte packets
1  10.217.133.1 (10.217.133.1)  86.830 ms  86.769 ms  98.345 ms
2  10.217.41.49 (10.217.41.49)  128.365 ms  128.359 ms  128.346 ms
3  10.217.32.179 (10.217.32.179)  134.007 ms  134.024 ms  134.017 ms

默认情况下,traceroute 会通过查找 PTR 记录来解析 IP 地址为域名。这可能会使其变慢,或者你可能希望查看原始地址。如果你更喜欢查看原始地址,可以通过 -n 选项禁用名称解析。

还有许多其他网络诊断工具。例如,像 nmap 这样的网络扫描器可以告诉你远程主机上正在运行哪些服务,并收集有关其操作系统和网络栈的信息。像 tcpdumpWireshark 这样的数据包捕获和分析工具可以帮助你获取网络流量的详细图景。然而,即使只有 pingtraceroute,你也可以验证网络设置是否正常工作,或者收集调试信息与同事或支持技术人员共享。

总结

在本章中,我们学习了 Linux 网络栈的组成部分、管理工具以及不同类型的物理和虚拟网络接口。我们学习了如何分配和查看地址、查看和创建路由、使用 NetworkManager 和特定发行版的方法配置网络设置,并执行基本的故障排除程序。

在下一章中,我们将学习如何管理存储设备。

第十章:存储管理

感觉我们的服务器永远不会有足够的空间来存储所有需要存储的内容。尽管硬盘的存储容量不断增加,高容量硬盘如今比以往任何时候都更加实惠,但我们的服务器很快就会填满所有可用空间。作为服务器管理员,我们最好的努力一直是订购尽可能多存储的机器,但现实是,即使是最精心规划的企业,最终也会用完空间。到某个时候,肯定需要为你的服务器增加额外的存储空间。然而,存储管理不仅仅是将已满的硬盘替换为空硬盘。如果你尽早开始使用逻辑卷管理器LVM)和其他类似技术,它们可以大大简化你的工作,因此提前规划非常重要。

本章中讨论的概念,包括 LVM 本身,将使你在管理服务器时拥有更大的自由。我还将解释一些在你处理服务器上的卷和存储时非常有用的其他概念。特别是,以下主题将会涵盖:

  • 在文件系统中创建新卷

  • 如何格式化和分区存储设备

  • 如何挂载和卸载卷

  • 学习如何使用/etc/fstab文件

  • 使用逻辑卷 管理器LVM

添加额外的存储卷

在某些时候,可能需要额外的服务器存储空间。通过安装更多的硬盘,可以扩展服务器的容量,这可以在独立机器上进行,或者通过使用云计算来实现。不管其名称是什么,在我们能够使用额外空间之前,我们需要弄清楚如何格式化和挂载设备。

使用 LVM(我们将在本章后面讲到),我们可以轻松地向现有卷中添加空间而不需要重新启动服务器。然而,在引入新设备时,必须遵循一套总体流程。以下是在升级系统存储容量时需要考虑的几个事项:

  • 你能告诉我你需要多少空间吗? 如果你的虚拟化环境的存储池中有足够的空闲空间,你可以创建任何大小的虚拟磁盘。

  • /dev/sdb1/dev/sdb2)。

  • 你希望为设备选择什么格式? 目前,ext4 是最广泛使用的文件系统。然而,根据不同任务(例如 XFS),你可能会做出其他选择。如果不确定,请使用 ext4,但也可以研究其他选择,看看是否有更适合你的需求的文件系统。ZFS 是一个额外的选择;然而,它比许多其他文件系统更新。你可能已经知道这一点,但新手 Linux 用户可能会对 文件系统 这个术语感到困惑,因为它在不同的情境下有不同的含义。当讨论正常 Linux 系统的文件和目录结构时,我们 Linux 管理员通常使用 文件系统 这个术语。另一方面,这个术语也可以指代与发行版兼容的磁盘格式(例如 ext4 文件系统)。

  • 你希望将它挂载在哪里? 由于新磁盘必须能够被系统以及可能的用户访问,因此你必须将它挂载(连接)到文件系统中的某个目录,用户或应用程序可以从该目录访问它。在本章中,我们还会介绍 LVM,通常情况下,你会希望将它添加到现有的存储组中。新的卷可以与任何你喜欢的目录一起使用,但我将在 格式化和分区存储设备 章节中介绍一些典型的目录。在 挂载和卸载卷 章节中,我们将详细讨论挂载和卸载过程。

让我们思考一下前两个问题的答案。为了确定你应该实施多少额外空间,你需要分析你的程序或公司的需求。对于实际的磁盘,你的选择仅限于购买哪种磁盘。使用虚拟磁盘时,你可以通过添加一个较小的磁盘来节省成本,以满足需求(你以后可以随时添加更多磁盘)。使用 LVM 与虚拟磁盘的主要优势是能够在不重启服务器的情况下增加文件系统的大小。如果你有一个 50 GB 的卷,并且希望将它做大,你可以创建两个 10 GB 的虚拟磁盘,并通过这种方式扩展它。

虽然 LVM 并不限于虚拟服务器,但它也可以用于物理服务器;然而,这样做可能需要重启,因为你需要打开机箱并物理连接硬盘。某些服务器可以添加或移除物理硬盘的功能称为 fdisk -l,你可以在终端中输入该命令。我们可以使用 fdisk 命令来查看新磁盘的名称,fdisk 更常用于创建和删除分区。如果你以 root 用户身份或使用 sudo 运行 fdisk -l 命令,你将获得以下详细信息:

sudo fdisk -l

图 10.1 – 列出所有磁盘

图 10.1 – 列出所有磁盘

现在我们已经学会了如何列出所有磁盘,接下来我们将学习如何格式化和分区存储设备。

格式化和分区存储设备

磁盘必须在使用前进行格式化。找到设备的名称对于格式化正确的磁盘是必要的。如果你阅读了上一节,你就会知道 Linux 发行版中的驱动器遵循预定的命名约定。因此,你应该熟悉新磁盘的设备名称。你可以使用sudo fdisk -l命令查看连接到服务器的存储设备信息,如前所述:

sudo fdisk -l

图 10.2 – 列出所有磁盘

图 10.2 – 列出所有磁盘

/dev/sdb设备是我新安装到服务器上的(见图 10.2)。我将在本章的练习中使用它。目前,它尚未分区。此时,很明显,在前面的例子中提到的存储设备/dev/sdb是全新的。为了避免丢失数据,我们必须小心,绝对不能格式化或重新分区错误的设备。由于/dev/sdb上没有任何分区(因为在我添加它之前这个磁盘并不存在),显然这是我们应该操作的磁盘。在此时,可以在其上创建一个或多个分区,距离实际使用它又近了一步。

使用带有sudo和设备名称选项的fdisk命令,我们可以对驱动器进行分区。以下是我用来访问磁盘/dev/sdb的命令:

sudo fdisk /dev/sdb

你会看到我没有指定分区号,因为fdisk直接操作磁盘(而且我们还没有创建任何分区)。在本节中,我将假设你可以访问一个尚未分区或完全可以擦除的驱动器。成功执行后,fdisk将显示一个介绍性消息并提示你操作。

要查看可用命令的列表,可以按键盘上的m键,如以下截图所示:

图 10.3 – 的帮助菜单

图 10.3 – fdisk的帮助菜单

在这里,我将演示创建新磁盘所需的步骤。需要了解的是,fdisk可能会对你的系统造成严重损害。在错误的硬盘上使用fdisk可能导致永久性数据丢失。管理员通常会熟练地使用像fdisk这样的工具,以至于使用它们变成了第二天性;然而,在执行任何此类指令之前,请再次检查你是否确实在操作正确的磁盘。分区表有两种类型 —— fdisk。使用 MBR 分区表时有一些限制。首先,MBR 限制最多只能有四个主分区。它还将磁盘使用限制为约 2 TB。如果你的磁盘小于 2 TB,应该没有问题,但大于 2 TB 的磁盘越来越常见。然而,GPT 并没有限制分区大小,因此,如果你有一个多 TB 大小的磁盘,那么 MBR 和 GPT 之间的选择已经为你决定了。此外,fdisk与 GPT 分区表一起使用时,允许你构建最多 128 个主分区。以下命令给出了如何操作特定硬盘/dev/sdb的选项:

图 10.4 – 创建新分区

图 10.4 – 创建新分区

我使用了 GPT,因此我输入了g

图 10.5 – 创建新的 GPT 分区表

图 10.5 – 创建新的 GPT 分区表

别忘了输入w以保存更改。

如果你犯了错误,可以再次运行fdisk。再次输入fdisk提示符后,如果你犯了错误或只是想重新开始,可以通过按g构建一个新的 GPT 布局,或者输入o来创建一个新的 MBR 布局。磁盘分区是一个两步过程,因此你需要重复之前的步骤。你可以自己多次尝试,直到掌握为止。

现在,使用fdisk -l /dev/sdb,我们可以看到有一个新分区/dev/sdb2,如下面的截图所示:

图 10.6 – 列出硬盘的分区

图 10.6 – 列出/dev/sdb硬盘的分区

既然我们已经学习了如何创建新分区,接下来我们将看到如何格式化它。

格式化新创建的分区

一旦你完成了磁盘分区的设计并对其满意,你的新分区就可以被格式化了。由于我已经分区了新硬盘,sudo fdisk -l的结果将会有所不同。

新分区/dev/sdb2已经创建,并反映在输出中。此时我们可以继续进行格式化操作。mkfs命令用于创建文件系统。为了执行此操作,必须使用正确的语法,即输入mkfs,后跟一个句点(.),然后是你希望创建的文件系统的名称。以这个代码为例,我们可以通过运行sudo mkfs.ext4 /dev/sdb2命令将/dev/sdb2格式化为ext4

图 10.7 – 格式化分区

图 10.7 – 格式化分区

记得格式化分区,否则它将无法使用。

挂载和卸载卷

在服务器上添加和格式化新的存储卷之后的下一步是挂载设备。mount 命令完成了这个任务。通过这个命令,你可以将一个可移动驱动器(或网络共享)连接到服务器硬盘上的一个目录。挂载需要一个干净的目录。为了挂载设备,你必须使用 mount 命令指定一个挂载目录,我们稍后将通过一个示例来实践这个过程。挂载额外存储非常简单,只需执行 mount 命令并选择一个当前未挂载或没有数据的目录。mount 命令通常需要 root 权限才能执行。然而,在大多数情况下,只有 root 用户应该挂载卷(尽管也有一种解决方法可以允许普通用户挂载卷;我们暂时不讨论这个问题)。由于需要一个目录来挂载这些卷,接下来我将展示如何使用以下命令创建一个名为 /usbpartition 的目录:

sudo mkdir /usbpartition
sudo mount /dev/sdb2 /usbpartition

使用前面的命令,我将 /dev/sdb2 设备挂载到 /usbpartition 目录作为示例。显然,你需要将 /dev/sdb2/usbpartition 替换为你自己选择的设备和目录。挂载通常需要 -t 选项来指定设备类型,但如果你忘记了服务器上安装了哪些设备,可以使用 fdisk -l 命令作为一个方便的提醒。由于我的磁盘是 ext4 格式化的,我应该使用以下带 -t 选项的 mount 命令:

sudo mount /dev/sdb2 -t ext4 /usbpartition

要检查是否正确挂载,可以使用以下命令:

mount –v | grep usbpartition

如果你已经完成了与某个卷的操作,可以使用 umount 命令卸载它(unmount 中的 n 被故意省略):

umount /usbpartition

使用 umount 命令可以将存储设备从文件系统中移除,执行此命令也要求你以 root 用户身份登录或使用 sudo。为了让此命令生效,卷必须先关闭。如果出现设备或资源忙碌的错误消息,那就说明卷仍在使用中。卸载之后,你可以通过运行 df -h 命令来验证文件系统是否已经不再挂载,并且可以注意到它不再返回任何结果。当设备是手动挂载时,服务器重启时它们会自动卸载。接下来的部分我将展示如何更新 /etc/fstab 文件,以便在服务器启动时自动挂载该卷。

更新 /etc/fstab 文件

一个重要的 Linux 系统文件是 /etc/fstab 文件。你可以通过编辑此文件在启动时手动挂载额外的卷。然而,这个文件的主要功能是挂载你的主文件系统,因此在修改它时,任何错误都会导致服务器无法启动(完全无法启动)。因此需要非常小心。

根文件系统的位置是通过读取/etc/fstab文件来确定的,该文件在系统启动时被读取。此文件还用于确定交换分区的位置,并在启动时进行挂载。系统将按顺序读取并挂载该文件中的每个挂载点。该文件可以用于自动挂载几乎任何类型的存储设备。你甚至可以安装 Windows 服务器网络共享。换句话说,它没有道德观,不会做出判断(除非你打错了字)。

这是一个/etc/fstab文件的示例:

图 10.8 – 文件示例

图 10.8 – /etc/fstab文件示例

编辑此文件时需要小心,因为不正确的设置可能导致系统无法启动或造成数据丢失。建议在进行任何更改之前备份该文件。

编辑/etc/fstab文件

正如我们之前提到的,任何手动挂载的设备在重启后不会自动挂载。

为了实现自动挂载,必须将设备添加到/etc/fstab文件中。

我在/etc/fstab文件中添加了一条记录,以便在启动时自动挂载/dev/sdb2/usbpartition/,如下图所示:

图 10.9 – 文件示例

图 10.9 – /etc/fstab文件示例

在我的机器上,第五列和第六列都为0,表示转储成功并且通过了检查。几乎总是设置为0,转储分区可以被备份程序检查,以查看文件系统是否需要备份(0表示不需要,1表示需要)。由于现在几乎没有人再使用这个功能,通常可以将其保留为0。文件系统会按照“通过”字段中指定的顺序进行检查。在系统崩溃或例行维护过程中,fsck工具会检查磁盘是否有文件系统问题。值可以是01。设置为0时,fsck将永远不会检查该分区。如果值为1,则会在其他任何操作之前检查该分区。

默认情况下,只有根用户可以修改/etc/fstab文件,因此在编辑时需要小心。错误修改此文件可能会导致系统启动过程或数据完整性出现严重问题。

使用 LVM

随着时间的推移,您所在组织的需求将不断发展。作为服务器管理员,我们不断努力在设置资源时考虑未来的扩展需求。不幸的是,预算限制和政策变动经常成为障碍。从长远来看,您会发现 LVM 是不可或缺的。Linux 在可扩展性和云部署方面的优势,归功于像 LVM 这样的技术。通过使用 LVM,您可以在不重新启动服务器的情况下扩展或收缩文件系统。考虑以下情境:假设您有一个关键任务应用程序在虚拟化的生产服务器上运行。可能在最初设置服务器时,您为该应用程序的数据目录分配了 300GB 的空间,认为它永远不会扩展到这么大的空间。随着公司发展,您的空间需求不仅增加了,还带来了危机。那么,您该怎么办?如果服务器最初配置为使用 LVM,那么添加一个新的存储卷,将其包含在 LVM 池中,并扩展分区,将无需重新启动。然而,如果没有 LVM,您就得为服务器安排停机时间,以便用传统方式添加更多存储,这可能需要几个小时。即便您的服务器不是虚拟化的,您仍然可以通过安装更多硬盘并将其保持待机状态来在线扩展文件系统,而无需使用它们。此外,如果服务器支持热插拔,您可以添加更多卷而不必关闭服务器。正因如此,我无法过分强调,在虚拟服务器的存储卷上尽可能使用 LVM 的重要性。再说一次,LVM 对于虚拟服务器上存储卷的设置至关重要。如果不使用,您将不得不花费周末的时间来添加磁盘,因为您在工作日已经耗尽了空间。

LVM 入门

Linux 的服务器安装程序允许你选择 LVM 作为全新服务器设置的卷管理器。然而,LVM 应该广泛用于存储卷,尤其是那些将容纳用户和应用程序数据的卷。如果你希望 Ubuntu 服务器的根文件系统能够利用 LVM 的功能,LVM 是一个不错的选择。在开始使用 LVM 之前,我们需要牢牢掌握卷组、物理卷和逻辑卷的概念。你打算与 LVM 解决方案一起使用的逻辑卷和物理卷会被组织到卷组中。简而言之,卷组是整个 LVM 架构的总称。你可以将它视为一个容器,用于存放磁盘。例如,vg-accounting卷组就是这种类型的一个示例。会计部门会使用这个卷组来存储他们的记录。它将包括实际的磁盘空间和这些用户将访问的虚拟磁盘空间。值得注意的是,你不必局限于使用单一卷组,而可以创建多个卷组,每个卷组都有自己的磁盘和卷。物理卷是属于卷组的磁盘,可以是真实的,也可以是虚拟的。一个vg-accounting卷组理论上可以有三个 500GB 的硬盘,每个硬盘都会被视为物理卷。请记住,尽管这些磁盘是虚拟的,但在 LVM 的上下文中,它们仍然被称为物理卷。物理卷是属于卷组的存储设备。最后,逻辑卷的概念类似于分区。与传统的分区不同,逻辑卷可以跨越多个物理磁盘。例如,逻辑卷可以通过三个 500GB 的磁盘来设置,总共可以获得 1500GB 的存储空间。当它被挂载时,它就像一个普通硬盘上的分区一样工作,使用户能够像使用任何其他磁盘一样轻松地保存和访问文件。当卷的空间用尽时,你可以通过添加新磁盘并扩展分区来扩展它。尽管它实际上可能由多个硬盘组成,但对用户来说,它将显示为一个单一的统一空间。物理卷可以按照任何对你有意义的方式进行细分。用户将与逻辑卷进行交互,而这些逻辑卷是由底层的物理卷创建的。

在尚未使用 LVM 的服务器上安装 LVM 需要至少一个未使用的卷和必要的软件包,而这些软件包可能已经或未安装在服务器上。以下命令将告诉你服务器上是否已安装所需的lvm2软件包:

对于 Debian,请使用以下命令:

sudo apt search lvm2 | grep installed

对于 Redhat,请使用以下命令:

sudo yum list installed|grep lvm2

下一步是统计我们所有的磁盘。我们已经多次使用 fdisk -l 命令来显示这些磁盘的列表。例如,我的服务器现在有 /dev/sdc。磁盘名称会根据硬件或虚拟化平台的不同而有所不同,因此你需要调整以下命令,以适应你的配置。首先,我们必须通过创建物理卷来准备每个磁盘以供 LVM 使用。需要记住的是,设置 LVM 并不包括格式化存储设备或使用 fdisk 配置它。在这种情况下,格式化会在稍后进行。为了开始将我们的磁盘配置为 LVM 使用,我们将使用 pvcreate 命令。因此,我们必须对每个计划使用的磁盘执行 pvcreate 命令。为了用我的 USB 磁盘设置 LVM,我将执行以下操作:

sudo pvcreate /dev/sdc

如果你想检查一切是否正确配置,可以通过以 root 身份或使用 sudo 运行 pvdisplay 命令查看服务器的物理卷列表:

图 10.10 – pvdisplay 命令输出

图 10.10 – pvdisplay 命令输出

由于这一页只能显示一个卷,因此截图中仅显示了这个卷。如果你向上滚动,将会看到更多来自 pvdisplay 命令的输出。我们有几个物理卷可用,但其中没有任何一个被放入卷组中。事实上,我们甚至还没有做一件简单的事情,那就是创建一个卷组。通过使用 vgcreate 命令,我们可以创建一个卷组,给它命名,并将我们的第一个磁盘添加到其中:

sudo vgcreate vg-packt /dev/sdc

此时,我正在创建一个名为 vg-packt 的卷组,并将创建的一个物理卷(/dev/sdc)分配给它。通过 sudo vgdisplay 命令,我们可以查看卷组的配置,包括它使用的磁盘数量(此时应该是 1):

现在,唯一需要做的就是创建一个逻辑卷并进行格式化。我们为卷组分配的磁盘空间可以全部使用,或者部分使用。以下是我将用于将新添加的虚拟磁盘在卷组中分区为一个 5 GB 逻辑卷的命令:

sudo lvcreate -n packtvol1 -L 5g vg-packt

虽然命令看起来很复杂,实际上它非常简单。为了清晰起见,我在这个示例中使用 -n 选项给我的逻辑卷命名为 packtvol1。我使用 -L 选项后跟 5g 来指定只分配 5 GB 的空间。该逻辑卷所属的卷组作为最后一项列出。要查看该卷的详细信息,可以使用 sudo lvdisplay 命令:

图 10.11 – lvdisplay 命令输出

图 10.11 – lvdisplay 命令输出

到目前为止,我们已经拥有了设置 LVM 所需的一切。像非 LVM 磁盘一样,在使用之前,我们必须格式化卷。

创建逻辑磁盘的格式

下一步是使用正确的格式来设置我们的逻辑卷。但是,为了确保格式化过程顺利进行,我们必须始终知道设备的名称。由于 LVM 的存在,这变得非常简单。你可以在输出中看到这一点(它位于图 10.11中的第三行,lvdisplay命令下提供)。让我们使用ext4文件系统来设置磁盘:

sudo mkfs.ext4 /dev/vg-packt/packtvol1

最后,这个存储设备可以像任何其他硬盘一样使用。我的将被挂载在/mnt/lvm/packtvol1,但你可以使用任何你喜欢的路径:

sudo mount /dev/vg-packt/packtvol1 /mnt/lvm/packtvol1

我们可以运行df -h来验证卷是否已挂载并显示正确的大小。在我们当前的 LVM 配置中,这没什么意义。我分配的 5 GB 可能不会持续太久,但我们有一些未使用的空间,可以好好利用:

~$ df –h | grep packtvol1
/dev/mapper/vg--packt-packtvol1   4.9GB   20M.  4.6GB  1%   /mnt/lvm/packtvol1

以下lvextend命令允许我扩展逻辑卷,以填充物理磁盘上的剩余空间:

sudo lvextend -n /dev/vg-packt/packtvol1 -l +100%FREE

图 10.12 – lvextend 命令

图 10.12 – lvextend 命令

更具体来说,前面的+100%FREE选项指定了我们希望将所有剩余空间分配给逻辑卷。在我的例子中,这只有 2.5 GB,因为我使用了一个 USB 驱动器作为演示。

我为我的逻辑卷指定的物理驱动器上的所有空间都被用完了。然而,要小心,因为如果我分配了多个物理卷,命令也会占用所有的空间,使得逻辑卷的大小等于所有磁盘上的空间总和。即使你不打算一直这样做,对我来说也没关系,因为我只有一个物理卷。你可以再次使用df -h工具来验证可用的存储空间:

~$ df –h | grep packtvol1
/dev/mapper/vg--packt-packtvol1   4.9GB   20M.  4.6GB  1%   /mnt/lvm/packtvol1

不幸的是,我们添加的额外卷空间没有反映出来。df仍然返回其输出中的旧卷大小。原因是,我们没有调整位于这个逻辑磁盘上的ext4文件系统的大小,尽管我们有一个更大的逻辑卷,并且它已经获得了所有空间。我们将使用resize2fs命令来完成这个操作:

sudo resize2fs /dev/mapper/vg--packt-packtvol1

图 10.13 – resize2fs 命令

图 10.13 – resize2fs 命令

现在,使用df -h命令,我们可以看到所有空间都已经分配完毕:

~$ df –h | grep packtvol1
/dev/mapper/vg--packt-packtvol1   7.3GB   23M.  6.9GB  1%   /mnt/lvm/packtvol1

在本节中,我们学习了如何使用逻辑卷以及如何扩展文件系统。

使用 LVM 删除卷

最后但同样重要的是,你可能想知道删除逻辑卷或卷组时会发生什么。lvremovevgremove命令正是为此而设计。尽管它们具有破坏性,但如果你需要删除逻辑卷或卷组时,这些命令会非常有用。以下语法将删除任何逻辑卷:

sudo lvremove vg-packt/packtvol1

lvremove命令传递你想要删除逻辑卷的卷组名称,并加上一个斜杠,这就足够了。要删除整个卷组,以下命令应该可以完成:

sudo vgremove vg-packt

即使你可能不会经常移除逻辑卷,仍然有一些命令可以帮助你在需要退役 LVM 组件时进行操作。希望你现在能理解 LVM 为何如此出色。

Linux 中的pvremove命令用于从 LVM 中移除物理卷PV)。在使用此命令之前,请确保要移除的 PV 不属于任何卷组,并且不包含任何活动的逻辑卷。否则,可能会发生数据丢失。

这项技术为你提供了前所未有的对服务器上数据的控制。Linux 在云端的多样性部分归功于 LVM 的适应性。如果你不熟悉 LVM,这些概念最初可能会显得陌生。然而,借助虚拟化,试验 LVM 是非常简单的。在你感到能够熟练地创建、编辑和删除卷组及逻辑卷之前,我建议你进行一些实践。那些最初不明显的概念,通过反复接触会变得清晰。

总结

保持平稳运行需要仔细的存储管理,因为文件系统满了会导致服务器停止工作。幸运的是,Linux 服务器提供了丰富的存储管理功能,其中一些功能是竞争系统所羡慕的。如果没有像 LVM 这样的创新,作为 Linux 服务器管理员的工作是不可能完成的。在本章中,我们深入研究了这些资源并学习了一些存储管理技巧。我们涵盖了广泛的主题,包括如何创建和管理分区、挂载和卸载卷、使用fstab文件、使用 LVM 以及检查磁盘使用情况。

在下一章,我们将讨论日志配置和远程日志。

第三部分:Linux 作为更大系统的一部分

所有现代 IT 基础设施由多个具有不同角色的机器组成,因此所有系统管理员都需要知道如何使基于 Linux 的系统协同工作。在本书的这一部分,你将学习如何在中央服务器上收集所有这些系统的日志消息,如何通过使用集中认证机制简化用户帐户和权限管理,如何创建具有自动故障转移和负载均衡的强大服务,并使用自动化工具同时管理多个系统。你还将学习如何保持系统的安全。

本部分包含以下章节:

  • 第十一章日志配置和远程日志

  • 第十二章集中认证

  • 第十三章高可用性

  • 第十四章使用 Chef 自动化

  • 第十五章安全指南和最佳实践

第十一章:日志配置和远程日志记录

日志记录是任何操作系统中一个重要的方面,包括 Linux。它提供了一种收集和分析系统事件和活动的方式,这对故障排除、监控和审计目的非常有用。在本章中,我们将探索 Linux 中日志配置和远程日志记录的不同方面。

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

  • 日志配置

  • 日志轮换

  • Journald

  • 日志转发

日志配置

Linux 使用 syslog 系统进行日志记录。syslog 守护进程收集来自系统不同部分的消息,并将其写入日志文件。syslog 配置文件通常位于 /etc/syslog.conf/etc/rsyslog.conf,具体取决于发行版。该文件包含指定哪些消息需要记录以及将其存储在哪个位置的规则。

有一个关键参数叫做 facility.severity,它是 Linux 中日志配置的关键部分。它允许你控制哪些日志消息应该被记录以及它们应该存储在哪里。设施和严重性可以通过数字或符号名称来指定。例如,以下规则将所有来自 auth 设施且严重性级别为警告或更高的消息记录到 /var/log/auth.log 文件中:

auth.warning /var/log/auth.log

配置文件中的目标部分指定日志消息的存储位置。目标可以是一个文件、一个远程主机或一个处理消息的程序。目标的语法如下:

target_type(target_options)

目标类型可以是以下之一:

  • file:将消息存储在本地文件中

  • remote:使用 syslog 协议将消息发送到远程主机

  • program:将消息发送到本地程序进行处理

例如,以下规则使用 syslog 协议将所有严重性级别为错误或更高的消息发送到 IP 地址为 192.168.1.100 的远程主机:

*.err @192.168.1.100

修改 syslog 配置文件后,必须重新启动 syslog 守护进程才能应用更改。重启 syslog 守护进程的命令根据发行版的不同而有所不同。例如,在 Ubuntu 上,命令如下:

sudo service rsyslog restart

日志消息的主要关注点是日志数据。换句话说,日志数据是日志消息背后的解释。如果你在网站上使用了图像、文件或其他资源,托管你网站的服务器可能会跟踪这些事实。通过查看日志数据,你可以了解谁访问了某个资源——在这个例子中,就是用户的用户名。

日志 这个术语是指一组日志消息的缩写,这些消息可以拼凑在一起为一个事件提供上下文。

日志文件中的每一项条目大致可以分类为以下几种之一:

  • 信息:这些消息故意含糊,旨在通知用户和管理员系统已经发生了积极的变化。例如,Cisco IOS 每次系统重启时会通知相关方。然而,需要注意的是,如果重启发生在不合适的时间,例如维护或工作时间之外,你可能需要引起关注。本书接下来的章节将教你应对这种情况所需的知识和技巧。

  • 调试:当运行应用程序代码出现问题时,系统会发送调试信息,帮助开发人员识别和解决问题。

  • 警告:当系统缺少或需要某些内容,但其缺失尚不足以阻止系统正常运行时,会发出警告。一些程序可能会在命令行上未收到预期的参数数量时向用户或操作员记录通知,尽管它们仍然可以正常运行。

  • 错误:在发生错误时,计算机系统会将相关信息存储在日志中,以供稍后分析。例如,如果操作系统无法将缓冲区同步到磁盘,它可能会生成错误日志。不幸的是,许多错误信息仅提供了问题的大致概述,通常需要进一步调查才能找出问题的根源。

  • 警报:警报的目的是引起你对一个重要事件的关注。在大多数情况下,通知来自与安全相关的设备和系统,尽管并非总是如此。所有进入网络的数据都可以通过放置在网络入口的入侵防御系统IPS)进行检查。它检查数据包中包含的信息,以决定是否启用某个网络连接。如果检测到潜在的恶意连接,IPS 可以采取几种预定的反应方式。该行为和决策将被记录。

接下来,我们将快速浏览传输和收集日志数据的过程。然后,我们将讨论什么是日志消息。

日志数据是如何发送和收集的?

发送和收集日志数据很容易。Syslog 是一种用于在计算机网络中发送和收集日志数据的协议。它是一种标准协议,允许不同的设备将日志消息发送到中央日志服务器或设备。

下面是它通常如何工作的:

  1. 一个设备生成日志消息。这可以是服务器、网络设备、应用程序或任何其他生成日志的设备。

  2. 设备使用 syslog 协议将日志消息发送到 syslog 服务器。syslog 服务器可以位于本地或云端。

  3. syslog 服务器接收到日志消息并进行处理。它可以将日志消息存储在文件或数据库中,或者将其转发到其他系统以供进一步分析。

  4. syslog 服务器还可以对其接收到的日志消息应用过滤器和规则。例如,它可以丢弃不相关的日志消息,或者在发生严重错误时发送警报。

  5. 系统管理员和分析人员可以访问存储在 syslog 服务器中的日志数据,用于故障排除、分析和报告。

以下是使用集中式日志收集器的一些优点:

  • 它是您所有日志消息的集中存储库

  • 日志可以存储在此处以供保存

  • 这是您可以检查所有服务器日志信息的地方

日志分析对于应用程序和服务器架构的健康至关重要,但如果数据分散在多个位置,可能会变得繁琐。为什么不使用一个集中式日志本,而不是一堆独立的日志本呢?Rsyslog 可能就是您一直在寻找的解决方案。

使用集中式日志系统,您可以将所有服务器和程序的日志收集到一个集中区域。此外,本教程将帮助您通过使用 rsyslog 配置在 Linux 节点上实现集中式日志记录。

本节旨在提供一个实际示例。

检查所有服务器上的 rsyslog 服务

一个高性能的日志处理系统——rsyslog,已经预安装并准备好在 Debian 和 RHEL 系统上使用。

rsyslog 改进了 syslog 协议,提供了更多现代化和可靠的功能。这些额外功能包括大量的输入和输出、模块化设计以及卓越的过滤能力。

截至本文编写时,rsyslog 的最新版本是 v8.2112.0。因此,您将验证 rsyslog 服务的状态以及安装在计算机上的 rsyslog 版本。这将确保 rsyslog 正在以其最新版本运行。

打开命令提示符,并使用以下sudo su命令获取所有服务器的控制权限。当系统提示时,输入您的密码。

在以下截图中,您会发现 Centos 8 默认安装了 rsyslog v8.2102.0:

图 11.1 – 检查 rsyslog 版本

图 11.1 – 检查 rsyslog 版本

通过运行systemctl status rsyslog命令来检查 rsyslog 服务的状态:

图 11.2 – 检查 rsyslog 服务状态

图 11.2 – 检查 rsyslog 服务状态

如您所见,服务处于活动状态并正在运行。

要检查多个服务器上的 rsyslog 服务状态,您可以使用配置管理工具,如 Ansible,或者编写一个简单的 bash 脚本来自动化该过程。以下是如何使用 bash 脚本检查所有服务器上 rsyslog 服务的示例:

  1. 创建一个名为servers.txt的文件,并将要检查的服务器列表逐行添加进去:

    server1.example.com
    
    server2.example.com
    
    server3.example.com
    
  2. 创建一个新的 bash 脚本,命名为check_rsyslog_service.sh,并添加以下代码:

    #!/bin/bash
    
    while read server;
    
    do
    
    echo "Checking rsyslog service on $server"
    
    ssh $server "systemctl status rsyslog" ; done < servers.txt
    
  3. 使脚本可执行:

    chmod +x check_rsyslog_service.sh
    
  4. 运行脚本:

    ./check_rsyslog_service.sh
    

脚本将遍历 servers.txt 中的服务器列表,并通过 SSH 执行 systemctl status rsyslog 命令来检查每台服务器上 rsyslog 服务的状态。输出结果将在终端中显示。你可以修改脚本,在服务器上执行其他操作,比如重启 rsyslog 服务或更新 rsyslog 配置。

配置 rsyslog 进行集中日志记录

在你将 rsyslog 更新到最新版本后,可以设置使用 central-rsyslog 服务器进行集中式日志记录。

集中日志记录设置是通过启用 rsyslog UDP 输入模块 imudp 并构建 rsyslog 模板来接收来自其他服务器的日志消息来创建的。imudp 输入模块允许通过 UDP 广播 syslog 消息,供 central-rsyslog 服务器接收。

在保存文件并关闭编辑器之前,请在 /etc/rsyslog.conf(rsyslog 配置文件)中启用以下截图所示的选项。

imudp 输入模块需要配置为使用默认的 UDP 端口 514 才能正常工作:

图 11.3 – imudp 模块配置

图 11.3 – imudp 模块配置

然后,创建一个新的 rsyslog 模板(/etc/rsyslog.d/50-remote-logs.conf),并粘贴在图 11.4中指示的配置。

以下 rsyslog 模板将允许你从其他服务器收集日志,并将其存储在 main-rsyslog 服务器上的 /var/log/remotelogs/ 目录中:

图 11.4 – 模板配置

图 11.4 – 模板配置

要建立一个新的日志目录(/var/log/remotelogs/),并将其归 root 用户和 adm 组所有,可以运行以下指令。这样,rsyslog 服务将能够在 /var/log/remotelogs 文件夹中创建日志:

mkdir -p /var/log/remotelogs

然后,更改 remotelogs 文件夹的所有权:

chown -R root:adm /var/log/remotelogs/

要检查 rsyslog 设置(/etc/rsyslog.conf/etc/rsyslog.d/50-remote-logs.conf),只需执行这里提供的 rsyslogd 命令:

图 11.5 – 检查语法

图 11.5 – 检查语法

在仔细检查设置后,你可以使用以下命令重启 rsyslog 服务:

systemctl restart rsyslog

启用了 imudp 输入模块的 rsyslog 服务已暴露了 syslog 协议的默认 UDP 端口 514。现在,主机可以通过将日志发送到 main-rsyslog 服务器与其通信:

systemctl restart rsyslog

你可以通过运行以下 ss 命令来再次确认你的端口是否已正确打开:

ss -tulpn

这是输出结果:

图 11.6 – 查看监听端口的命令

图 11.6 – 查看监听端口的命令

Syslog 是一种简单高效的协议,用于在分布式网络环境中收集和管理日志数据。它提供了一个集中存储日志的位置,这使得系统管理、监控和故障排除变得更加容易。

向集中式 rsyslog 服务器发送日志

您已经通过在main-rsyslog服务器上配置 syslog,迈出了简化日志处理的第一步。但是,您如何确认main-rsyslog服务器正在接收日志?日志可以通过激活并配置 rsyslog 输出模块(main-rsyslog)从远程客户端系统发送到main-rsyslog服务器。

在此示例中,client01机器使用 rsyslog 输出模块omfwd,将日志传输到main-rsyslog服务器。

要处理消息和日志,必须安装omfwd模块(它应该已经安装)。该模块可以与 rsyslog 模板结合使用。最后,模块通过 rsyslog 操作对象将数据通过 UDP 和 TCP 协议传输到指定的目的地。

设置客户端机器,使其能够向main-rsyslog服务器提交日志。

在您喜欢的文本编辑器中创建一个新的 rsyslog 配置文件(/etc/rsyslog.d/20-forward-logs.conf),并输入如图 11.7所示的设置。

使用SendRemote模板,日志消息在通过 UDP 协议发送到main-rsyslog服务器(192.168.1.111)之前会先进行格式化。在这种情况下,IP 地址应该替换为您主 rsyslog 服务器的 IP 地址:

图 11.7 – SendRemote 模板

图 11.7 – SendRemote 模板

上面的截图显示了日志转发模板文件的内容。

通过运行以下命令检查语法是否正确:

rsyslogd -N1 -f /etc/rsyslog.d/20-remote-logs.conf

通过运行sudo systemctl restart rsyslog命令重新启动 rsyslog,并检查 syslog 服务器是否正在接收来自客户端的日志。

日志轮换

日志轮换是 Linux 系统中一个至关重要的过程,用于高效管理日志文件。随着应用程序和服务随着时间生成日志数据,日志文件可能会显著增长,占用磁盘空间,并可能导致性能问题。日志轮换允许定期压缩、归档和删除旧的日志文件,确保系统保持可管理的日志历史记录。

在 Linux 中,日志轮换通常由名为logrotate的日志轮换工具处理。logrotate的配置文件位于/etc/logrotate.conf,并且包含对/etc/logrotate.d/目录中单独的日志轮换配置文件的引用。

以下是如何在 Linux 中配置日志轮换的分步指南:

  1. logrotate是预安装的。然而,如果您的系统没有此工具,您可以使用特定于您 Linux 发行版的包管理器进行安装。例如,在基于 Debian/Ubuntu 的系统中,您可以通过以下命令进行安装:

    sudo apt-get update
    
    sudo apt-get install logrotate
    
  2. 创建日志轮换配置文件:您可以为您的特定应用程序/服务创建一个新的日志轮换配置文件,或者使用默认的配置文件。建议为不同的应用程序创建单独的文件,以便于管理。

转到/etc/logrotate.d/目录并创建一个新的配置文件——例如,myapp_logrotate

sudo nano /etc/logrotate.d/myapp_logrotate
  1. logrotate配置文件遵循特定的语法。以下是一个基本示例:

    /path/to/your/logfile.log {
    
    rotate <N>     # Number of log files to keep before removal
    
    daily          # Frequency of rotation (daily, weekly, monthly, etc.)
    
    missingok      # Don't throw an error if the log file is missing
    
    notifempty     # Do not rotate an empty log file
    
    compress       # Compress the rotated log files using gzip
    
    create <mode> <user> <group> # Create new empty log file with specified permissions, user, and group
    
    }
    

/path/to/your/logfile.log替换为实际的日志文件路径。将<N>替换为在删除前要保留的日志文件数量(例如,rotate 7表示保留 7 天的日志)。将<mode><user><group>替换为适当的权限和新创建日志文件的所有权。

保存配置文件并退出文本编辑器。

  1. 如果logrotate配置没有错误,可以运行以下命令:

    sudo logrotate -d /etc/logrotate.d/myapp_logrotate
    

-d标志用于调试,它将显示logrotate会做的事情,但不会实际旋转日志文件。

  1. 执行手动日志旋转:一旦确认配置正确,您可以使用以下命令手动触发日志旋转:

    sudo logrotate /etc/logrotate.d/myapp_logrotate
    
  2. logrotate可以按常规间隔运行。您可以使用以下命令将条目添加到crontab中:

    sudo crontab -e
    

然后,添加以下行以在每天午夜运行logrotate

0 0 * * * /usr/sbin/logrotate /etc/logrotate.conf

保存crontab并退出文本编辑器。

现在,您的日志文件将根据配置设置自动旋转和归档。您可以在logrotate配置文件中调整旋转频率和其他选项,以满足您的具体需求。

Journald

日志是 systemd 的一部分。来自启用 systemd 的 Linux 机器各个部分的消息都会在这里收集。这包括来自内核和启动过程、syslog 以及其他服务的通知。

传统上,在 Linux 的启动过程中,操作系统的许多子系统和应用程序守护进程会将日志消息记录在文本文件中。每个子系统的消息会记录不同级别的详细信息。在故障排除时,管理员通常需要从多个文件中筛选出不同时间段的消息,并进行内容关联。日志记录功能通过集中记录所有系统和应用级消息消除了这个问题。

systemd-journald 守护进程负责日志。它从多个资源收集数据,并将收集到的消息插入到日志中。

当 systemd 使用内存中的日志时,日志文件会生成在/run/log/journal文件夹下。如果没有该目录,系统会创建一个。日志也可以使用持久化存储在/var/log/journal目录下生成;如果需要,systemd 会创建这个目录。如果此目录被销毁,日志将以非持久化方式写入/run/log/journal,systemd-journald 不会自动重新创建它。当守护进程重新启动时,目录会被重新创建。

journalctl命令对于调试服务和进程非常有用,因为它允许您查看和修改 systemd 日志。

接下来将介绍journalctl命令及其众多显示选项,以及如何查看 systemd 日志。由于每台机器有其独立的记录集,因此结果会有所不同。

要显示所有日志条目,使用不带任何选项的journalctl命令:

[voxsteel@centos8 ~]$ journalctl
-- Logs begin at Wed 2023-01-18 14:07:03 GMT, end at Wed 2023-01-18 14:09:39 GMT. --
Jan 18 14:07:03 centos8 kernel: microcode: microcode updated early to revision 0xd6, date = 2019-10-03Jan 18 14:07:03 centos8 kernel: Linux version 4.18.0-348.7.1.el8_5.x86_64 (mockbuild@kbuilder.bsys.centos.org) (gcc version 8.5.0 20210514 (Red Hat 8.5.0>Jan 18 14:07:03 centos8 kernel: Command line: BOOT_IMAGE=(hd1,gpt2)/vmlinuz-4.18.0-348.7.1.el8_5.x86_64 root=/dev/mapper/cl-root ro crashkernel=auto resu>Jan 18 14:07:03 centos8 kernel: x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'Jan 18 14:07:03 centos8 kernel: x86/fpu: Supporting XSAVE feature 0x002: 'SSE registers'Jan 18 14:07:03 centos8 kernel: x86/fpu: Supporting XSAVE feature 0x004: 'AVX registers'Jan 18 14:07:03 centos8 kernel: x86/fpu: Supporting XSAVE feature 0x008: 'MPX bounds registers'Jan 18 14:07:03 centos8 kernel: x86/fpu: Supporting XSAVE feature 0x010: 'MPX CSR'Jan 18 14:07:03 centos8 kernel: x86/fpu: xstate_offset[2]:  576, xstate_sizes[2]:  256Jan 18 14:07:03 centos8 kernel: x86/fpu: xstate_offset[3]:  832, xstate_sizes[3]:   64Jan 18 14:07:03 centos8 kernel: x86/fpu: xstate_offset[4]:  896, xstate_sizes[4]:   64Jan 18 14:07:03 centos8 kernel: x86/fpu: Enabled xstate features 0x1f, context size is 960 bytes, using 'compacted' format.Jan 18 14:07:03 centos8 kernel: BIOS-provided physical RAM map:Jan 18 14:07:03 centos8 kernel: BIOS-e820: [mem 0x0000000000000000-0x0000000000057fff] usableJan 18 14:07:03 centos8 kernel: BIOS-e820: [mem 0x0000000000058000-0x0000000000058fff] reserveJan 18 14:07:03 centos8 kernel: BIOS-e820: [mem 0x0000000000059000-0x000000000009dfff] usableJan 18 14:07:03 centos8 kernel: BIOS-e820: [mem 0x000000000009e000-0x000000000009efff] reservedJan 18 14:07:03 centos8 kernel: BIOS-e820: [mem 0x000000000009f000-0x000000000009ffff] usableJan 18 14:07:03 centos8 kernel: BIOS-e820: [mem 0x00000000000a0000-0x00000000000fffff] reservedJan 18 14:07:03 centos8 kernel: BIOS-e820: [mem 0x0000000000100000-0x00000000c70fafff] usableJan 18 14:07:03 centos8 kernel: BIOS-e820: [mem 0x00000000c70fb000-0x00000000c7c7efff] reservedJan 18 14:07:03 centos8 kernel: BIOS-e820: [mem 0x00000000c7c7f000-0x00000000c7e7efff] ACPI NVS

输出显示日志数据的时间范围。列中包含以下按从左到右的顺序排列的数据:

  • 日期和时间

  • 主机

  • 日志来源

  • 日志消息

要显示与当前启动相关的日志,使用 -b 标签,如下所示:

[voxsteel@centos8 ~]$ journalctl -b
-- Logs begin at Wed 2023-01-18 14:07:03 GMT, end at Wed 2023-01-18 16:36:10 GMT. --
Jan 18 14:07:03 centos8 kernel: microcode: microcode updated early to revision 0xd6, date = 2019-10-03
Jan 18 14:07:03 centos8 kernel: Linux version 4.18.0-348.7.1.el8_5.x86_64 (mockbuild@kbuilder.bsys.centos.org) (gcc version 8.5.0 20210514 (Red Hat 8.5.0-4) (GCC)>
Jan 18 14:07:03 centos8 kernel: Command line: BOOT_IMAGE=(hd1,gpt2)/vmlinuz-4.18.0-348.7.1.el8_5.x86_64 root=/dev/mapper/cl-root ro crashkernel=auto resume=/dev/m>
Jan 18 14:07:03 centos8 kernel: x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'
Jan 18 14:07:03 centos8 kernel: x86/fpu: Enabled xstate features 0x1f, context size is 960 bytes, using 'compacted' format.
Jan 18 14:07:03 centos8 kernel: BIOS-provided physical RAM map:
Jan 18 14:07:03 centos8 kernel: BIOS-e820: [mem 0x0000000000000000-0x0000000000057fff] usable
Jan 18 14:07:03 centos8 kernel: BIOS-e820: [mem 0x0000000000058000-0x0000000000058fff] reserved
Jan 18 14:07:03 centos8 kernel: BIOS-e820: [mem 0x00000000c7eff000-0x00000000c7efffff] usable
Jan 18 14:07:03 centos8 kernel: BIOS-e820: [mem 0x00000000c7f00000-0x00000000cc7fffff] reserved

如果你想查看过去 10 分钟内的日志,例如,可以使用 journalctl -S "10 minutes ago"

[voxsteel@centos8 ~]$ journalctl -S "10 minutes ago"
-- Logs begin at Wed 2023-01-18 14:07:03 GMT, end at Wed 2023-01-18 16:38:00 GMT. --
Jan 18 16:31:49 centos8 systemd[1]: run-docker-runtime\x2drunc-moby-586ec0d1511775a767ac92e0bc680e5ca772a18e59e31f9e358f9632834faede-runc.ucvxT5.mount: Succeeded.
Jan 18 16:32:54 centos8 dbus-daemon[1048]: [system] Activating service name='org.fedoraproject.Setroubleshootd' requested by ':1.30' (uid=0 pid=987 comm="/usr/sbi>
Jan 18 16:32:54 centos8 dbus-daemon[1048]: [system] Successfully activated service 'org.fedoraproject.Setroubleshootd'
Jan 18 16:32:55 centos8 setroubleshoot[45450]: AnalyzeThread.run(): Cancel pending alarm
Jan 18 16:32:55 centos8 dbus-daemon[1048]: [system] Activating service name='org.fedoraproject.SetroubleshootPrivileged' requested by ':1.1032' (uid=978 pid=45450>
Jan 18 16:32:56 centos8 dbus-daemon[1048]: [system] Successfully activated service 'org.fedoraproject.SetroubleshootPrivileged'
Jan 18 16:32:57 centos8 setroubleshoot[45450]: SELinux is preventing /usr/sbin/haproxy from name_connect access on the tcp_socket port 8082\. For complete SELinux >
Jan 18 16:32:57 centos8 setroubleshoot[45450]: SELinux is preventing /usr/sbin/haproxy from name_connect access on the tcp_socket port 8082.

如果你只想显示内核日志消息,可以使用 -k 选项,如下所示:

[voxsteel@centos8 ~]$ journalctl -k
-- Logs begin at Wed 2023-01-18 14:07:03 GMT, end at Wed 2023-01-18 16:46:01 GMT. --
Jan 18 14:07:03 centos8 kernel: microcode: microcode updated early to revision 0xd6, date = 2019-10-03
Jan 18 14:07:03 centos8 kernel: Linux version 4.18.0-348.7.1.el8_5.x86_64 (mockbuild@kbuilder.bsys.centos.org) (gcc version 8.5.0 20210514 (Red Hat 8.5.0-4) (GCC)>
Jan 18 14:07:03 centos8 kernel: Command line: BOOT_IMAGE=(hd1,gpt2)/vmlinuz-4.18.0-348.7.1.el8_5.x86_64 root=/dev/mapper/cl-root ro crashkernel=auto resume=/dev/m>
Jan 18 14:07:03 centos8 kernel: x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'
Jan 18 14:07:03 centos8 kernel: x86/fpu: Supporting XSAVE feature 0x002: 'SSE registers'
Jan 18 14:07:03 centos8 kernel: x86/fpu: Supporting XSAVE feature 0x004: 'AVX registers'

你还可以使用以下命令根据优先级过滤日志消息:

journalctl -p <number or text priority>

以下是优先级级别:

  • 0emerg

  • 1alert

  • 2crit

  • 3err

  • 4warning

  • 5notice

  • 6info

  • 7debug

你可以使用 man journalctl 命令查找所有可用的 journalctl 参数。

DMESG

dmesg 是 Linux 中的一个命令行工具,允许你查看内核环形缓冲区的消息。内核环形缓冲区是内存中的一个循环缓冲区,存储内核生成的消息,如硬件事件、设备驱动程序信息和系统错误消息。

dmesg 命令显示这个内核环形缓冲区的内容,允许你查看自系统上次启动以来生成的消息。这些消息对于调试系统问题、识别硬件问题和监控系统事件非常有用。

dmesg 的一些常见用例包括以下内容:

  • dmesg 可用于通过显示错误消息、警告和其他相关信息来识别和诊断系统问题。

  • dmesg 可以提供关于硬件设备和驱动程序的信息,例如设备何时被检测到或驱动程序加载失败时的情况。

  • dmesg 可用于监控系统事件,例如当用户插入 USB 设备或系统服务启动或停止时。

下面是一些常用选项,可与 dmesg 命令一起使用:

  • -T:以人类可读格式显示时间戳。

  • -H:以更易读的格式显示输出。

  • -l level:仅显示指定日志级别(debuginfonoticewarningerrcritalertemerg)的消息。

  • -k:仅显示内核消息。

总体而言,dmesg 是一个强大的工具,可以帮助你排查系统问题并监控 Linux 系统中的事件。

dmesg 命令提供了一个观察 Linux 内部工作情况的窗口。这个 故障排查者的朋友 允许你读取和观察从内核的硬件设备和驱动程序发送的消息,这些消息来自内核的内部环形缓冲区。

理解 Linux 中的环形缓冲区

当计算机开机时,会按特定顺序发生多个事件;在 Linux 和类 Unix 系统中,这些活动分别被称为引导和启动。

在系统初始化(BIOS 或 UEFI、MBR 和 GRUB)完成后,内核被加载到内存中,初始内存磁盘(initrd 或 initramfs)与内核连接,并启动 systemd。

操作系统被交给启动程序,这些程序完成设置。当系统首次启动时,可能需要一段时间才能使日志守护进程(如 syslogd 和 rsyslogd)开始工作。内核有一个环形缓冲区,用作消息缓存,以确保在初始化阶段产生的关键错误消息和警告不会丢失。

环形缓冲区是一个特殊的内存区域,用于存储消息。它具有固定的大小和简单的结构。当缓冲区达到容量时,新的消息会替换掉旧的消息。它可以被概念化为环形缓冲区

如设备驱动初始化消息、硬件消息和内核模块消息等信息都会保存在内核环形缓冲区中。环形缓冲区是开始故障排除硬件问题或其他启动问题的一个便捷位置,因为它存储着这些低级别的消息。

使用 dmesg 命令,你可以检查系统环形缓冲区中保存的消息日志:

dmesg -T | less

我添加了 -T 以以可读的格式显示时间戳,并使用 less 使其可滚动。

以下是前述命令的输出:

[Fri Jan 20 08:12:36 2023] wlp2s0: associate with d4:5d:64:e1:e0:2c (try 1/3)
[Fri Jan 20 08:12:36 2023] wlp2s0: RX AssocResp from d4:5d:64:e1:e0:2c (capab=0x1011 status=0 aid=5)
[Fri Jan 20 08:12:36 2023] wlp2s0: associated
[Fri Jan 20 08:12:36 2023] wlp2s0: Limiting TX power to 23 (23 - 0) dBm as advertised by d4:5d:64:e1:e0:2c
[Fri Jan 20 15:08:39 2023] atkbd serio0: Unknown key pressed (translated set 2, code 0x85 on isa0060/serio0).
[Fri Jan 20 15:08:39 2023] atkbd serio0: Use 'setkeycodes e005 <keycode>' to make it known.
[Mon Jan 23 06:27:58 2023] wlp2s0: deauthenticating from d4:5d:64:e1:e0:2c by local choice (Reason: 2=PREV_AUTH_NOT_VALID)
[Mon Jan 23 06:27:59 2023] wlp2s0: authenticate with d4:5d:64:e1:e0:2c
[Mon Jan 23 06:27:59 2023] wlp2s0: send auth to d4:5d:64:e1:e0:2c (try 1/3)
[Mon Jan 23 06:27:59 2023] wlp2s0: authenticated
[Mon Jan 23 06:27:59 2023] wlp2s0: associate with d4:5d:64:e1:e0:2c (try 1/3)
[Mon Jan 23 06:27:59 2023] wlp2s0: RX AssocResp from d4:5d:64:e1:e0:2c (capab=0x1011 status=0 aid=5)
[Mon Jan 23 06:27:59 2023] wlp2s0: associated
[Mon Jan 23 06:27:59 2023] wlp2s0: Limiting TX power to 23 (23 - 0) dBm as advertised by d4:5d:64:e1:e0:2cresume=/dev/mapper/cl-swap rd.lvm.lv=cl/root rd.lvm.lv=cl/swap rhgb quiet
[Wed Jan 18 14:06:39 2023] x86/fpu: Supporting XSAVE feature 0x001: 'x87 floating point registers'

dmesg --follow 命令是 dmesg 命令的一种变体,它会持续显示在内核环形缓冲区中生成的新消息。

当你在终端中运行 dmesg --follow 时,它将显示最近的内核消息,并继续实时显示任何新生成的消息。这对于监控系统事件的发生或诊断实时发生的问题非常有用。

--follow 选项等同于 -w--wait 选项,它告诉 dmesg 等待将新消息添加到内核环形缓冲区,并在消息到达时显示它们。

下面是 dmesg --follow 命令的一些用例:

  • dmesg --follow 用于监控在插拔设备时或与硬件交互时产生的内核消息。

  • dmesg --follow 可以帮助你实时了解发生了什么,并识别可能导致问题的模式或问题。

  • dmesg --follow 用于监视在内核环形缓冲区中可能生成的任何错误消息或警告。

值得注意的是,因为 dmesg --follow 会持续显示新消息,输出可能很快变得压倒性,难以阅读。要停止 dmesg 命令的运行,你可以在终端中按 Ctrl + C

dmesg –follow
[151557.551942] wlp2s0: Limiting TX power to 23 (23 - 0) dBm as advertised by d4:5d:64:e1:e0:2c
[176520.971449] atkbd serio0: Unknown key pressed (translated set 2, code 0x85 on isa0060/serio0).
[176520.971452] atkbd serio0: Use 'setkeycodes e005 <keycode>' to make it known.
[404479.355923] wlp2s0: deauthenticating from d4:5d:64:e1:e0:2c by local choice (Reason: 2=PREV_AUTH_NOT_VALID)
[404480.713565] wlp2s0: authenticate with d4:5d:64:e1:e0:2c
[404480.722235] wlp2s0: send auth to d4:5d:64:e1:e0:2c (try 1/3)
[404480.724148] wlp2s0: authenticated
[404480.724865] wlp2s0: associate with d4:5d:64:e1:e0:2c (try 1/3)
[404480.725868] wlp2s0: RX AssocResp from d4:5d:64:e1:e0:2c (capab=0x1011 status=0 aid=5)
[404480.727602] wlp2s0: associated
[404480.781339] wlp2s0: Limiting TX power to 23 (23 - 0) dBm as advertised by d4:5d:64:e1:e0:2c

请注意,你不会被带回到输入命令的提示符处。每当接收到新消息时,dmesg 会在终端底部显示这些消息。

例如,你可以使用以下命令查看最近的 15 条消息:

dmesg | tail -15

或者,你可以使用 dmesg | grep -I memory 命令搜索特定的术语(例如,memory):

[    0.000000] Memory: 2839388K/12460644K available (12293K kernel code, 2261K rwdata, 7872K rodata, 2492K init, 13944K bss, 795924K reserved, 0K cma-reserved)
[    0.021777] Freeing SMP alternatives memory: 32K
[    0.048199] x86/mm: Memory block size: 128MB
[    4.052723] Freeing initrd memory: 53084K
[    4.215935] Non-volatile memory driver v1.3
[    6.181994] Freeing unused decrypted memory: 2036K
[    6.182301] Freeing unused kernel memory: 2492K
[    6.188636] Freeing unused kernel memory: 2012K
[    6.188833] Freeing unused kernel memory: 320K
[    8.302610] i915 0000:00:02.0: [drm] Reducing the compressed framebuffer size. This may lead to less power savings than a non-reduced-size. Try to increase stolen memory size if available in BIOS.
[   37.829370] PM: Saving platform NVS memory
[   37.837899] PM: Restoring platform NVS memory

使用 man 命令可以查找你可以用一个命令做的所有操作。dmesg 是一个非常强大的日志调查工具。

总结

在本章中,你了解了日志以及如何配置 rsyslog 实现集中式日志记录。通过使用各种 rsyslog 输入和输出插件,你将服务器日志通过网络传输到集中式 rsyslog 服务器。现在,你的 rsyslog 服务器是查找日志的唯一位置。

我们还提供了如何读取 systemd 日志的示例。journalctl 命令是一个强大的工具,用于诊断 Linux 服务的问题并查找操作系统中的故障。

最后,你了解了 dmesg 命令的强大功能及其使用方法。dmesg 是一个强大的工具,能够帮助你排查系统问题并监控 Linux 系统事件。

在下一章中,我们将讨论集中式认证,你可以使用一台服务器让所有客户端进行认证。

第十二章:集中式身份验证

用户访问控制是信息安全中至关重要的一部分。在单一计算机上,跟踪用户并确保只有授权人员能够访问是简单的,但随着网络规模的增大,在所有计算机上保持用户账户同步变得越来越困难,这也是为什么大型网络使用集中式身份验证机制的原因。历史上,类 UNIX 系统通常使用网络信息服务NIS),该协议由 Sun Microsystems 开发,曾广泛使用,但现在基本不再使用。如今,选择更加多样,包括独立的 LDAP 目录、Kerberos 域,或提供目录服务用于存储用户信息并支持单点登录协议的身份验证解决方案,例如 FreeIPA 和 Microsoft Active Directory。

本章我们将学习以下内容:

  • Linux 中的身份验证与用户信息查找框架

  • 名称服务切换NSS)框架、可插拔身份验证模块PAM)和系统安全服务守护进程SSSD)的作用

  • 如何设置与 Microsoft Active Directory 兼容的域控制器并将客户端计算机连接到该域控制器

AAA 框架

访问控制框架通常被称为AAA,因为它包括三个组件:身份验证授权审计

身份验证负责验证用户的身份——通常通过检查用户是否拥有某些知识(如密码)、数据(如加密密钥或基于时间的身份验证算法的正确种子)、物理项(如硬件密钥存储)或属性(如指纹)来完成。

授权是检查尝试执行某个操作的用户是否有权限执行该操作的过程。由于在 UNIX 系统中,许多实体(如硬件设备和套接字)被表示为文件,因此许多时候,文件访问权限被用作授权框架。

最后,审计过程确保用户的操作被记录,以便能够将操作归属给用户、监控用户活动中的异常情况,并调查安全事件。由于对于通用操作系统来说,无法列出所有用户操作的详尽清单,因此无法制定通用的审计框架。syslog 机制是记录日志信息的常用方式,但每个应用程序的日志信息格式各不相同。

在用户访问控制组件中,Linux 的身份验证具有独特性,因为它拥有一个广泛使用且通用的框架,该框架由三个部分组成:NSSPAM,以及在更新的 Linux 发行版中使用的 SSSD。它们之间的关系较为复杂,因为它们的功能广泛且部分重叠,许多任务可以在不同层次上解决。

这个框架的两个较旧部分,NSS 和 PAM,起源于名为 Solaris 的操作系统,该操作系统由 Sun Microsystems(后来被 Oracle 收购)开发,并很快被几乎所有类 UNIX 系统采纳。然而,两个机制都没有成为 POSIX 标准的一部分,而且它们在不同操作系统中的实现略有不同。SSSD 是在 2000 年代末为 Linux 开发的,现在广泛被 Linux 发行版使用,但其他类 UNIX 系统并未使用。

让我们详细探讨一下这些子系统的目的和功能。

Linux 中的认证机制

在我们了解集中式认证机制之前,我们需要先了解 Linux 中身份验证的基本工作原理。在系统检查用户凭证之前,它需要先获取用户信息——让我们来看一下信息查询是如何工作的。

信息查询

用户和组的信息对于身份验证是必要的,但它还有许多其他用途。例如,文件所有权信息通常以人类可读的方式显示,但内部,文件系统存储的是数值化的用户和组标识符,因此即使是与安全无关的程序,如 ls,也可能需要查找用户和组的信息,以将它们的标识符映射到名称。

POSIX API 标准包括多个查询用户、组和主机等各种实体信息的函数。例如,gethostbyname(name) 检索与域名关联的网络地址,getgrpnam(name) 检索用户组的信息。对于应用程序而言,这些函数是黑盒子,它们既不了解也无法控制这个过程——底层系统如何获取这些数据以及在哪里获取这些数据并不是应用程序开发人员关心的问题。通过保持该过程对应用程序不透明,操作系统开发人员可以确保所有应用程序从查询调用中获得一致的数据。

在 GNU/Linux 系统中,这些函数是由 GNU C 库(glibc)实现的。然而,这个库并没有固定的支持查询机制和数据源,而是使用 NSS 机制作为一个抽象层。

名称服务切换

NSS 子系统允许管理员将 数据库 与不同类型的信息映射到 数据源。让我们查看一个 Fedora 安装的配置文件,以了解它的功能:

$ cat /etc/nsswitch.conf
# Generated by authselect on Tue May 31 00:21:30 2022
# Do not modify this file manually, use authselect instead. Any user changes will be overwritten.
# You can stop authselect from managing your configuration by calling 'authselect opt-out'.
# See authselect(8) for more details.
# In order of likelihood of use to accelerate lookup.
passwd:     files sss systemd
shadow:     files
group:      files sss systemd
hosts:      files myhostname mdns4_minimal [NOTFOUND=return] resolve [!UNAVAIL=return] dns
services:   files sss
netgroup:   files sss
automount:  files sss
aliases:    files
ethers:     files
gshadow:    files
networks:   files dns
protocols:  files
publickey:  files
rpc:        files

NSS 不仅限于身份验证数据查询,它的功能要广泛得多。NSS 数据库和数据源的概念在一个非身份验证的例子中更容易展示:主机名查询。负责网络主机名解析的 hosts 数据库被设置为使用多个数据源,包括 filesdns

数据源的顺序决定了查找的顺序。在这种情况下,hosts 条目指示系统首先查找本地文件,然后再转向网络源,例如零配置网络发现机制(多播 DNS),最后才是全局 DNS 系统。在这个特定的案例中,files 关键字指的是 /etc/hosts 文件,该文件在第六章中有讨论,属于 基本 系统设置

数据源通常按照从最快和最可靠的顺序排列。本地源排在前面,因为即使网络中断,它们始终可用,而且读取本地文件是一个非常快速的操作。远程源排在后面,因为访问它们总是至少涉及一些延迟,并且它们可能由于网络或服务器故障而变得无法访问。有时,比如在主机名查找的情况下,顺序还有安全性上的影响:确保系统在向 DNS 服务器发出请求之前首先查找 /etc/hosts 中的名称,确保传统名称如 localhost 始终指向本地主机(IPv4 为 127.0.0.1,IPv6 为 ::1)。如果先查询 DNS,一个恶意或配置错误的服务器可能返回不同的地址,进而将 ping localhost 等命令的流量重定向到任意地址。

passwd 数据库用于查找用户信息。它的 files 数据源就是熟悉的 /etc/passwd —— 这也是它名字的来源。

在不同数据源中的查找是通过动态加载的库(共享对象)实现的,例如 /usr/lib/libnss_files.so.2/usr/lib/libnss_dns.so.2

当然,使用 NSS 独立实现对不同身份验证信息数据库的支持并重新使用最旧的登录处理代码是完全可能的 —— NSS 的目标是让这样的客户端代码与任何数据源兼容,这样代码甚至不会知道它现在是从远程源(例如 RADIUS 或 LDAP 服务器)获取用户信息和密码哈希,而不是从 /etc/passwd/etc/shadow 获取。这在过去曾经实现过,LDAP 的 NSS 模块至今仍可在互联网上找到。

然而,存在多个理由来支持一个更灵活的用户身份验证框架,以及提供身份验证和查找功能的抽象层。

可插拔身份验证模块

NSS 帮助程序检索各种信息,包括用户名、组成员信息和密码哈希值。然而,身份验证的逻辑仍然必须存在于某处。例如,要进行基于密码的身份验证,必须有代码计算用户输入的明文密码的哈希值,并与存储在身份验证数据库中的哈希值进行对比。

然而,认证策略不仅仅是拥有密码并检查它们是否正确。管理员可能希望强制执行密码强度规则,或使用多因素认证来提高安全性。例如,使用远程数据库进行认证也带来了一些挑战,比如凭证缓存,以确保在远程数据库暂时不可用时,用户不会被锁定在他们的机器之外。

为了让开发者和管理员能够创建并设置灵活的认证策略工具,并增加新的认证算法,Linux 发行版使用了一个名为PAM的框架。

PAM 为应用程序提供了一个 API,以便进行用户认证,并供安全工具开发者实现认证机制。PAM 模块可以依赖 NSS 层来查找它们需要的信息进行用户认证,或者提供自己的查找机制和配置文件。

在 Fedora 和 Red Hat 系统中,PAM 模块可以在/usr/lib64/security/找到,而在 Debian(x86 机器)中,它们可以在/usr/lib/x86_64-linux-gnu/security/中找到。模块通常会带有自己的手册页面,因此可以通过运行man pam_unixman pam_empty来获取简要描述。

例如,pam_empty是最简单的模块,它总是返回认证失败——它仅作为一个守卫模块,用于确保当多个回退机制都失败时,用户将被拒绝访问。

pam_unix模块实现了常规的密码认证,通常使用/etc/passwd/etc/shadow文件,除非 NSS 被配置为使用其他方式。

一些模块并不实现任何认证机制,而是执行辅助操作。例如,pam_motd在登录后显示消息(通常位于/etc/motd);pam_mail负责检查本地邮件并显示;pam_pwqualitypam_pwhistory有助于确保用户不使用弱密码,并且不重复使用旧密码。

PAM 配置

尽管手动配置 PAM 通常不是一个好主意,大多数 Linux 发行版也不鼓励这样做,并提供了高层次的配置工具,然而,了解其配置文件如何工作仍然很重要。

首先,PAM 不是一个程序,而是一个框架和一组 API,供其他程序使用。Linux 中没有单一的登录程序,因此也没有单一的全局认证配置文件。相反,有多个具备登录功能的程序,它们共享大部分但不是全部配置。这些程序包括处理本地虚拟控制台登录尝试的/bin/login可执行文件,但 PAM 也被图形登录管理器(如 GDM 或 LightDM)、屏幕保护程序和网络访问服务(如 OpenSSH)独立使用。

配置文件存储在 /etc/pam.d/ 中,但它们本身没有任何特殊意义——它们都由不同的程序读取和使用。例如,名为 /etc/pam.d/login 的文件由 /bin/login 使用,因此仅应用于本地虚拟控制台。

这些应用程序特定文件的名称是硬编码在使用它们的程序中的,但共享配置存储在独立的文件中,这些文件的名称在不同的发行版之间有所不同。

让我们比较 Fedora 和 Debian 中 OpenSSH 守护进程使用 PAM 的默认配置文件。如果你没有安装 OpenSSH,你可以查看其他文件,比如 /etc/pam.d/login

这个文件来自 Fedora:

$ cat /etc/pam.d/sshd
#%PAM-1.0
auth       substack     password-auth
auth       include      postlogin
account    required     pam_sepermit.so
account    required     pam_nologin.so
account    include      password-auth
password   include      password-auth
# pam_selinux.so close should be the first session rule
session    required     pam_selinux.so close
session    required     pam_loginuid.so
# pam_selinux.so open should only be followed by sessions to be executed in the user context
session    required     pam_selinux.so open env_params
session    required     pam_namespace.so
session    optional     pam_keyinit.so force revoke
session    optional     pam_motd.so
session    include      password-auth
session    include      postlogin

这个来自 Debian 系统:

$ cat /etc/pam.d/sshd
# PAM configuration for the Secure Shell service
# Standard Un*x authentication.
@include common-auth
# Disallow non-root logins when /etc/nologin exists.
account    required     pam_nologin.so
# Uncomment and edit /etc/security/access.conf if you need to set complex
# access limits that are hard to express in sshd_config.
# account  required     pam_access.so
# Standard Un*x authorization.
@include common-account
# SELinux needs to be the first session rule.  This ensures that any
# lingering context has been cleared.  Without this it is possible
# that a module could execute code in the wrong domain.
session [success=ok ignore=ignore module_unknown=ignore default=bad]        pam_selinux.so close
# Set the loginuid process attribute.
session    required     pam_loginuid.so
# Create a new session keyring.
session    optional     pam_keyinit.so force revoke
# Standard Un*x session setup and teardown.
@include common-session
# Print the message of the day upon successful login.
# This includes a dynamically generated part from /run/motd.dynamic
# and a static (admin-editable) part from /etc/motd.
session    optional     pam_motd.so  motd=/run/motd.dynamic
session    optional     pam_motd.so noupdate
# Print the status of the user's mailbox upon successful login.
session    optional     pam_mail.so standard noenv # [1]
# Set up user limits from /etc/security/limits.conf.
session    required     pam_limits.so
# Read environment variables from /etc/environment and
# /etc/security/pam_env.conf.
session    required     pam_env.so # [1]
# In Debian 4.0 (etch), locale-related environment variables were 
# moved to
# /etc/default/locale, so read that as well.
session    required     pam_env.so user_readenv=1 envfile=/etc/default/locale
# SELinux needs to intervene at login time to ensure that the process # starts
# in the proper default security context.  Only sessions which are 
# intended
# to run in the user's context should be run after this.
session [success=ok ignore=ignore module_unknown=ignore default=bad]        pam_selinux.so open
# Standard Un*x password updating.
@include common-password

如你所见,它们的功能几乎相同,但组织方式不同。例如,Debian 广泛使用 @include 指令,它加载来自不同文件的所有行。例如,@include common-auth 指示 PAM 加载 /etc/pam.d/common-auth ——所有此类文件引用都是相对于 /etc/pam.d 的。

Fedora 文件的作者选择使用模块接口调用,而不是:auth substack system-auth。在这样的行中,最后一个选项要么是模块名称,要么是配置文件引用。如果以 .so 结尾(这是共享对象文件的常见扩展名——动态加载的库文件),那么它就是一个模块;否则,它是 /etc/pam.d 中的配置文件。因此,当你看到 substack system-auth 时,它意味着加载来自 /etc/pam.d/system-auth 的行(includesubstack 选项的工作方式类似)。

所有此类行的第一部分是模块接口名称。有些模块仅提供一种接口类型(例如,pam_unix 仅处理密码认证),而其他模块可能提供多种类型。auth 接口用于执行身份验证。其他接口类型表明 可插拔认证模块 的名称在某些情况下并不准确。account 接口处理授权并检查用户账户是否允许登录——例如,pam_time 模块可以让特定用户仅在某些时间段或某些日期访问系统。password 接口处理密码更改。最后,session 接口处理与认证无关的辅助任务,例如在用户第一次登录时为其创建主目录或通过网络挂载目录。

PAM 的局限性

PAM 仅关注认证和相关活动;例如,配置一个正确的 LDAP 服务器和选项的 pam_ldap 模块并不会自动使 LDAP 中的用户和组信息对所有需要将数字标识符映射到名称的应用程序可用。在这种情况下,为了提供无缝体验,管理员还需要配置一个 NSS 模块。独立配置两个模块并提供相同的信息会带来显著的维护负担,而且将信息从远程源传递给像 pam_unix 这样的简单模块对于需要发放和验证会话令牌而非简单密码哈希检查的单点登录协议(如 Kerberos)不起作用。历史上,这个问题通过定制守护进程如 winbind 来解决,用于与 Microsoft Active Directory 或 Samba 域交互。如今,大多数 Linux 发行版通过通用抽象层——SSSD 解决了这个问题。

系统安全服务守护进程

NSS 和 PAM 的结合提供了很大的灵活性,但也可能使常见场景的配置和维护变得困难。SSSD 项目努力通过协调 PAM 和 NSS 与远程数据库的交互来简化这一过程。

单点登录方案的配置复杂性之一在于它们通常涉及多个组件和协议,例如用于存储用户信息的 LDAP 和用于发放及检查加密认证票证的 Kerberos,以及发现这些服务的方式,通常通过特殊的 DNS 记录。SSSD 内置支持流行的单点登录方案,如 Microsoft Active Directory 和 FreeIPA,这大大简化了过程。

在本示范中,我们将在 Linux 上使用 Samba 项目设置与 Microsoft Active Directory 兼容的域控制器,并将客户端计算机加入该域。我们将使用 Fedora Linux,但其他发行版在包安装命令上大多有所不同。

使用 Samba 4 进行 Active Directory 认证

Samba 是一个开源实现,支持与 Microsoft Windows 机器互操作所需的多个协议,包括 SMB 文件共享协议(Samba 名字的来源)。除了文件共享外,它还实现了认证和用户管理——最初,它只支持 Windows NT 域,但自 4.0.0 版本以来,它完全支持与 Windows Server 2008 兼容的 Active Directory,并且还包括内建的 LDAP 和 DNS 后端,使得小规模安装变得非常简单。

设置域控制器

首先,您需要安装 Samba 域控制器软件包:

$ sudo dnf install samba-dc

然后,您可能需要删除 Samba 和 Kerberos 守护进程的所有配置文件,以确保清洁状态:

$ sudo rm -f /etc/samba/smb.conf
$ sudo rm -f /etc/krb5.conf

Samba 包含一个用于自动配置域控制器的命令,因此无需手动编写配置文件。该命令支持交互式和非交互式模式,并且还可以通过命令行选项指定一些参数,但其余部分可以交互式输入。例如,如果你计划在 VirtualBox 虚拟机中运行客户端系统,你可以设置控制器只监听虚拟机网络接口,使用--option="interfaces=vboxnet0" --option="bind interfaces only=yes"。你可能还希望在控制器使用的 LDAP 模式中包含 UNIX 用户的字段 —— 它们由 RFC2307 定义,因此该选项是--use-rfc2307

最重要的必需参数是领域名称,必须是用大写字母书写的域名 —— 这是 Kerberos 协议的要求,小写的领域名称将被拒绝。我们将使用 AD.EXAMPLE.COM,其中 example.com 是为示例和文档保留的域之一,保证不会属于任何真实的个人或组织。大多数现代客户端软件主要或专门使用领域名称,但配置工具也会要求输入域名(以 NetBIOS 而非 DNS 的形式) —— 一个最多 15 个字符长的字符串。由于 Active Directory 及其兼容实现依赖于 DNS SRV 记录来查找控制器并与其通信,控制器还将提供这些 DNS 记录,但我们将限制其只监听虚拟机网络接口 —— 在此情况下,使用 192.168.56.1(这是 VirtualBox 中仅主机网络适配器的默认地址)。它还将要求输入域管理员密码 —— 你需要记住它以便从客户端机器加入该域:

$ sudo samba-tool domain provision --option="interfaces=vboxnet0" --option="bind interfaces only=yes" --use-rfc2307 --interactive
Realm:  AD.EXAMPLE.COM
Domain [AD]:  AD
Server Role (dc, member, standalone) [dc]:
DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE) [SAMBA_INTERNAL]:
DNS forwarder IP address (write 'none' to disable forwarding) [127.0.0.53]:  192.168.56.1
Administrator password:
Retype password:
INFO 2022-12-07 16:25:13,958 pid:54185 /usr/lib64/python3.10/site-packages/samba/provision/__init__.py #2108: Looking up IPv4 addresses
INFO 2022-12-07 16:25:17,450 pid:54185 /usr/lib64/python3.10/site-packages/samba/provision/__init__.py #2017: Fixing provision GUIDs
...
INFO 2022-12-07 16:25:17,784 pid:54185 /usr/lib64/python3.10/site-packages/samba/provision/__init__.py #2342: The Kerberos KDC configuration for Samba AD is located at /var/lib/samba/private/kdc.conf
INFO 2022-12-07 16:25:17,785 pid:54185 /usr/lib64/python3.10/site-packages/samba/provision/__init__.py #2348: A Kerberos configuration suitable for Samba AD has been generated at /var/lib/samba/private/krb5.conf
INFO 2022-12-07 16:25:17,785 pid:54185 /usr/lib64/python3.10/site-packages/samba/provision/__init__.py #2350: Merge the contents of this file with your system krb5.conf or replace it with this one. Do not create a symlink!
INFO 2022-12-07 16:25:17,948 pid:54185 /usr/lib64/python3.10/site-packages/samba/provision/__init__.py #2082: Setting up fake yp server settings
INFO 2022-12-07 16:25:18,002 pid:54185 /usr/lib64/python3.10/site-packages/samba/provision/__init__.py #487: Once the above files are installed, your Samba AD server will be ready to use
INFO 2022-12-07 16:25:18,002 pid:54185 /usr/lib64/python3.10/site-packages/samba/provision/__init__.py #492: Server Role:           active directory domain controller
INFO 2022-12-07 16:25:18,002 pid:54185 /usr/lib64/python3.10/site-packages/samba/provision/__init__.py #493: Hostname:              mizar
INFO 2022-12-07 16:25:18,002 pid:54185 /usr/lib64/python3.10/site-packages/samba/provision/__init__.py #494: NetBIOS Domain:        AD
INFO 2022-12-07 16:25:18,002 pid:54185 /usr/lib64/python3.10/site-packages/samba/provision/__init__.py #495: DNS Domain:            ad.example.com
INFO 2022-12-07 16:25:18,002 pid:54185 /usr/lib64/python3.10/site-packages/samba/provision/__init__.py #496: DOMAIN SID:            S-1-5-21-2070738055-845390856-828010526

请注意,配置脚本会生成 Kerberos 配置文件,但不会自动部署 —— 你需要手动将其复制到目标位置:

$ sudo cp /var/lib/samba/private/krb5.conf /etc/krb5.conf

现在,我们只需要启动 Samba 服务:

$ sudo systemctl start samba

我们还将创建一个名为testuser的用户,以验证身份验证是否按预期工作。我们在控制器中创建新用户的命令是sudo samba-tool user create test-user(它会提示你输入密码)。

使用sudo samba-tool user show testuser,你可以查看该用户账户的 LDAP 字段:

$ samba-tool user show testuser
dn: CN=testuser,CN=Users,DC=ad,DC=example,DC=com
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: user
cn: testuser
instanceType: 4
whenCreated: 20221207171435.0Z
uSNCreated: 4103
name: testuser
objectGUID: 773d2799-e08c-41fb-9768-675dd59edbbb
badPwdCount: 0
codePage: 0
countryCode: 0
badPasswordTime: 0
lastLogoff: 0
primaryGroupID: 513
objectSid: S-1-5-21-2070738055-845390856-828010526-1103
accountExpires: 9223372036854775807
sAMAccountName: testuser
sAMAccountType: 805306368
userPrincipalName: testuser@ad.example.com
objectCategory: CN=Person,CN=Schema,CN=Configuration,DC=ad,DC=example,DC=com
pwdLastSet: 133149068751598230
userAccountControl: 512
lastLogonTimestamp: 133149085286105320
lockoutTime: 0
whenChanged: 20221207180044.0Z
uSNChanged: 4111
logonCount: 0
distinguishedName: CN=testuser,CN=Users,DC=ad,DC=example,DC=com

服务器现在已经准备好接收身份验证请求。

设置客户端

既然域控制器已经准备就绪,我们可以设置客户端机器以便验证用户。由于我们同样使用 Fedora 作为客户端,因此它很可能默认安装了 SSSD 和 realmd —— 一个用于配置认证领域的工具。

我们可以使用 realm discover 命令验证控制器是否工作正常,如下所示:

$ realm discover ad.example.com
ad.example.com
  type: kerberos
  realm-name: AD.EXAMPLE.COM
  domain-name: ad.example.com
  configured: no
  server-software: active-directory
  client-software: sssd
  required-package: oddjob
  required-package: oddjob-mkhomedir
  required-package: sssd
  required-package: adcli
  required-package: samba-common-tools

从此输出中,我们可以看到这台机器尚未加入该域(configured: no)。它还显示了需要安装的加入域的包列表——如果某些包尚未安装,你可以使用如sudo dnf install samba-common-tools的 DNF 命令进行安装。

加入域也非常简单。一旦运行sudo realm join ad.example.com,它将要求你输入域管理员密码(即你在配置过程中在控制器上输入的密码),并生成 SSSD 的配置文件:

$ sudo realm join ad.example.com
Password for Administrator:

你需要通过sudo systemctl restart sssd重启 SSSD 以应用更改。

生成的 SSSD 配置将如下所示:

$ sudo cat /etc/sssd/sssd.conf
[sssd]
domains = ad.example.com
config_file_version = 2
services = nss, pam
[domain/ad.example.com]
default_shell = /bin/bash
krb5_store_password_if_offline = True
cache_credentials = True
krb5_realm = AD.EXAMPLE.COM
realmd_tags = manages-system joined-with-adcli
id_provider = ad
fallback_homedir = /home/%u@%d
ad_domain = ad.example.com
use_fully_qualified_names = True
ldap_id_mapping = True
access_provider = ad

由于 SSSD 充当 NSS 提供程序,并且在 Fedora 中passwd的默认nssswitch.conf值为files sss,我们可以使用常规的id命令查询testuser账户的信息,尽管在我们的配置中,我们需要指定其完整的域名:

$ id testuser@ad.example.com
uid=1505601103(testuser@ad.example.com) gid=1505600513(domain users@ad.example.com) groups=1505600513(domain users@ad.example.com)

通过输入testuser@ad.example.com以及我们之前在控制器上设置的密码,也可以立即登录到控制台。

如你所见,使用 Samba 设置与 Microsoft Active Directory 兼容的域控制器是一个简单且大部分自动化的过程。

概述

在本章中,我们学习了基于 Linux 的系统如何通过 PAM、NSS 和 SSSD 实现认证过程和用户信息查找。我们还学习了集中认证的解决方案,并演示了如何使用 Samba 软件设置与 Microsoft Active Directory 兼容的域控制器,并从另一台 Linux 机器加入该域。

在下一章中,我们将学习 Linux 中的高可用性机制。

进一步阅读

要了解更多关于本章所涵盖的主题,请查看以下资源:

第十三章:高可用性

所有计算机硬件在性能和可靠性方面都有一定的限制,因此必须能够在不间断的情况下处理大量用户请求的系统,通常由多个独立的工作机器和专用的负载均衡节点组成,这些节点将负载分配到各个工作机器上。

Linux 在内核中包含负载均衡和冗余的功能,多个用户空间守护进程管理这些内置功能并实现额外的协议和特性。

在本章中,我们将学习以下内容:

  • 不同类型的冗余和负载均衡

  • Linux 中的链路层和网络层冗余机制

  • 使用 Linux Virtual Server (LVS) 进行传输层负载均衡

  • 使用 Keepalived 在多个节点之间共享虚拟 IP 地址并自动化 LVS 配置

  • 应用层负载均衡解决方案,以 HAProxy 为例

冗余和负载均衡的类型

在深入讨论具体的高可用性特性及其配置之前,让我们先讨论可能的冗余和负载均衡类型、它们的优点及其局限性。

首先,我们需要记住,现代的 TCP/IP 网络栈是 分层的。多种分层模型包括不同数量的层,但思路是相同的:上层的协议对任何下层的协议都不了解,反之亦然。最常用的模型是七层 开放系统互联 (OSI) 模型和四层 DoD 模型(由美国国防部开发)。我们已在下表中对它们进行了总结:

OSI 模型 DoD 模型 目的 示例
物理 链路 传输代表比特流的电气/光学信号 Ethernet, Wi-Fi
数据链路
网络 互联网 在分段、路由的网络中传输数据包 IPv4, IPv6
传输 传输 数据段的可靠传输(完整性检查、确认、拥塞控制等) TCP, UDP, SSTP
会话 应用 传输特定应用数据 HTTP, SMTP, SSH
表示层
应用层

表 13.1 — OSI 和 DoD 网络栈模型

由于网络栈是分层的,为了让网络抵御不同类型的故障,冗余可以并且应该在多个层级上实现。例如,通过两根电缆而不是一根将单个服务器连接到网络,可以保护它免受单根电缆断开或网络卡故障的影响,但不会保护用户免受服务器软件故障的影响——在这种情况下,服务器仍然会连接到网络,但无法处理任何请求。

这个问题通常可以通过设置多个服务器并引入专用的负载均衡节点来解决,该节点充当中介:它接收用户的连接并将负载分配到所有这些服务器上。

引入负载均衡器可以在传输层或应用层增加冗余,因为只要至少有一个服务器可用,系统就能继续提供服务。它还增加了超出单个服务器性能极限的总服务能力。

然而,负载均衡器本身成为了单点故障——如果其软件故障或与网络断开连接,整个服务将变得不可用。此外,它相比任何单独的服务器,还将承受最大的网络流量负载。这使得链路层和网络层冗余尤其重要。最后,为了确保用户发送到负载均衡器 IP 地址的请求始终被接受,公共地址通常在多个物理负载均衡服务器之间共享,形成一个集群,并使用首跳冗余协议FHRP):

图 13.1 — 典型的高可用性设置

图 13.1 — 典型的高可用性设置

图 13.1 显示了一个完全冗余的设置,包括三个应用服务器和两个负载均衡器,且通过聚合以太网链接保护免受电缆或网卡故障的影响。

在我们学习 Linux 中不同冗余类型及其实现之前,我们应该回顾一下术语。不同的协议和技术使用不同的节点角色名称,其中一些仍使用不准确且可能冒犯的术语,但了解这些术语对于理解它们的文档至关重要。

术语说明

为了描述冗余设置,我们默认使用活动/备用active/standby)术语:只有一个活动节点在任何时刻执行工作,且一个或多个额外的备用节点在等待接管它的任务(如果它发生故障)。

许多较早的文献,包括协议标准、配置文件以及高可用性解决方案的官方文档,可能仍使用主/从master/slave)术语。由于这些术语与人类奴隶制的关联以及其误导性——因为在大多数协议中,活动节点并没有控制备用节点——许多项目正在逐步淘汰这些术语。我们在讨论仍使用这些术语的协议和软件时,会为了与它们的文档一致而使用这些术语。

链路层冗余

电缆断开和以太网交换机端口故障是非常常见的,尤其是在户外安装和工业网络中。在这些情况下,拥有多个链路层连接非常有用。然而,单纯将一台 Linux 机器的两个不同网络卡连接到同一交换机的不同端口并不能使它们作为单一连接工作。用户需要显式地设置这两个网络卡,使它们协同工作。

幸运的是,Linux 支持多种方法将多个网卡一起使用——包括主动/备用模式和负载均衡配置。其中一些方法不需要以太网交换机的支持,甚至可以在非常基础的非托管交换机上使用。其他模式则要求交换机支持较旧的 EtherChannel 协议(由思科系统设计)或较新的、厂商中立的 IEEE 802.3ad 链路聚合控制协议 (LACP),且端口必须显式配置以启用这些协议。我们可以将所有这些方法总结在以下表格中:

类型 操作 交换机要求
active-backup (1) 一个网络卡保持禁用,而另一个处于工作状态 无;适用于任何交换机(即使是非托管交换机)
802.3ad (4) 帧在所有端口间平衡分配 需要 803.3ad LACP 支持
balance-xor (2) 和 broadcast (3) 需要 EtherChannel 支持
balance-tlb (5) 和 balance-alb (6)

表 13.2 — Linux 中的链路层冗余方法

最简单的模式是 active-backup,它不需要对以太网交换机进行特殊设置,甚至可以与最简单、最便宜的非托管交换机一起使用。与 802.3ad LACP 等模式不同,它仅提供主动-备用冗余,而非负载均衡。使用以下命令,你可以将 eth0eth1 网络接口通过 active-backup 方法连接到一个单一的 bond0 接口,在使用 NetworkManager 配置的系统上:

$ sudo nmcli connection add type bond con-name bond0 ifname bond0 bond.options "mode=active-backup"
$ sudo nmcli connection add type ethernet slave-type bond con-name bond0-port1 ifname eth0 master bond0
$ sudo nmcli connection add type ethernet slave-type bond con-name bond0-port2 ifname eth1 master bond0
$ sudo nmcli connection up bond0

现在,如果 eth0eth1 中的任何一个与交换机物理断开,链路层连接将保持。

这种配置简单且对以太网交换机的要求低,但代价是浪费带宽。尽可能地,高性能服务器应使用当前行业标准的 802.3ad LACP 协议连接到以太网网络,这样它们可以利用多个链路的合并带宽,并自动排除故障链路,以提供冗余。

网络层冗余和负载均衡

如果一个系统有多个独立的互联网或内部网络连接,可以提供备用路由或在多个路由间平衡 IP 数据包。然而,实际上,网络层冗余通常仅被路由器使用,而非主机,其最简单的形式仅适用于拥有公共、全球路由地址的网络。

假设你的 Linux 系统连接了两个不同的路由器,一个具有 IPv4 地址 192.0.2.1,另一个具有 203.0.113.1。如果你希望其中一个连接完全不使用,仅作为备用连接,你可以创建两个具有不同度量值的默认路由,并将备用连接的度量值设置为更高。度量值的大小决定了路由的优先级,如果存在多个不同度量值的路由,内核将始终使用度量值最小的路由。当该路由消失时(例如,由于网络卡故障),内核将切换到使用仍可用的、度量值次小的路由。

例如,如果你希望192.0.2.1成为备用路由器,你可以使用以下命令:

$ sudo ip route add 0.0.0.0/0 via 192.0.2.1 metric 200
$ sudo ip route add 0.0.0.0/0 via 203.0.113.1 metric 100

这种方法的优点是,它与同一系统上设置的网络地址转换(NAT)兼容。如果你想创建负载均衡配置,则涉及更多的问题,因为网络层负载均衡是按数据包进行的,并且无法感知连接的概念。

从表面上看,多路径路由的配置相当简单。你可以指定任意数量的网关地址,并且可以选择性地为它们分配权重,以将更多流量导向更快的链路。例如,如果你希望203.0.113.1的流量是其他链接的两倍,你可以使用以下命令来实现:

$ sudo ip route add 0.0.0.0/0 nexthop via 192.0.2.1 weight 5 nexthop via 203.0.113.1 weight 10

问题在于,这种配置本身与 NAT 不兼容,因为它会将属于同一 TCP 连接或 UDP 流的包发送到不同的网关。如果你有一个公开路由的网络,这是正常且不可避免的。然而,如果你只有来自每个提供商的单个外部地址,并且必须使用 NAT 将私有网络映射到该单一的外发地址,则属于同一连接的包必须始终通过相同的网关流动,才能按预期工作。有一些方法可以使用基于策略的路由来设置每连接负载均衡,但这超出了本书的范围。如果你感兴趣,可以在其他来源中找到更多信息,例如Policy Routing With Linux,作者是Matthew G. Marsh,该书可以在线免费获取。

使用 LVS 进行传输层负载均衡

所有网络层机制的主要缺点是网络层是以单个数据包为单位进行操作的,并没有连接的概念。许多网络服务是面向连接的,因此至少,属于同一连接的所有数据包必须始终发送到同一台服务器。虽然 Linux 中的 NAT 实现足够智能,可以检测同一连接的多个数据包,但简单的负载均衡(基于一对多的 NAT)对于许多使用场景来说仍然过于简化。例如,它没有提供一种简单的方法来跟踪每台服务器接收到多少连接,也无法优先将新连接发送到负载最轻的服务器(即处理现有连接数最少的服务器)。

为了应对这种使用场景,Linux 包含了ipvsadm

LVS 框架的关键概念是虚拟服务器真实服务器。虚拟服务器是提供服务公共地址的 Linux 机器,接受连接并将这些连接分发到多个真实服务器。真实服务器可以运行任何操作系统和软件,并且可以不知道虚拟服务器的存在。

LVS 是一个灵活的框架,提供多种负载调度算法、负载均衡机制和配置选项,具有各自的优缺点。我们将详细探讨它们。

调度算法

有多种方法可以在多个服务器之间分配负载,每种方法都有其优缺点。我们可以通过以下表格进行总结:

算法 描述
轮询(rr 将连接均匀地分配到所有服务器。
加权轮询(wrr 这类似于轮询,但通过为某些服务器分配更高的权重值,允许将更多连接发送到这些服务器。
最少连接(lc 优先将新连接发送到当前连接数最少的服务器。
加权最少连接(wlc 默认调度算法。这类似于最少连接,但允许为服务器分配权重。
基于位置的最少连接(lblc 将具有相同目标 IP 地址的新连接发送到同一服务器,如果第一台服务器不可用或超载,则切换到下一台服务器。
基于位置的最少连接与复制(lblcr 将具有相同目标 IP 地址的新连接发送到同一服务器,如果该服务器未超载。否则,将它们发送到连接数最少的服务器。
目标地址哈希(dh 创建一个哈希表,将目标 IP 地址映射到服务器。
源地址哈希(sh 创建一个哈希表,将源 IP 地址映射到服务器。
最短预期延迟(sed 将新连接发送到具有最短预期延迟的服务器。
永不排队 (nq) 将新连接发送到第一个空闲服务器,并在没有空闲服务器时切换到最短预期延迟。

表 13.3 – LVS 调度算法

选择正确的调度算法取决于服务类型;对于所有用例,没有一个算法本质上比其他算法更好。例如,轮询和加权轮询最适合服务于短连接的服务,例如提供静态页面或文件的 Web 服务器(例如内容分发网络)。

使用非常长寿命、持久连接的服务,如在线游戏服务器,可以从最少连接算法中受益。对于这类服务,使用轮询方法可能是适得其反的,因为新连接相对不频繁,但每个连接的资源消耗较高,可能会导致某些服务器过载或创建非常不平衡的负载分布。跟踪每个服务器活动连接数的最少连接算法旨在解决这个问题。

最后,如果响应延迟是服务质量的重要因素,则最短预期延迟和永不排队算法可以改善它,而轮询和最少连接则完全不考虑响应时间。

LVS 负载平衡方法

首先,我们将审视 LVS 提供的负载平衡方法。它支持三种方法:直接路由、IP 隧道和 NAT。我们将总结它们之间的区别及其优缺点,并通过配置示例详细审查它们:

机制 实施 优势 劣势
直接路由 替换目标 MAC 地址 最佳性能;真实服务器直接向客户端发送回复 所有服务器必须在同一网络上,对 ARP 有困难
IP 隧道 将客户端请求封装在隧道协议中发送 真实服务器直接向客户端发送回复,真实服务器可以在任何网络上 真实服务器必须支持 IPIP 隧道并且必须具有到虚拟服务器的隧道,返回数据包可能会被拒绝作为伪造
NAT 在幕后创建 NAT 规则 真实服务器不需要公共地址或任何特殊配置 相对资源密集,所有流量经过虚拟服务器,在实践中是最佳方法,尽管存在缺点

表 13.4 – LVS 负载平衡方法

让我们详细分析这些负载平衡机制,首先从 NAT 开始。

NAT

由于两个因素,NAT 是 LVS 中最实用的负载平衡方法:真实服务器不需要具有公共可路由的 IP 地址,也不需要知道虚拟服务器或专门配置以与其配合。

在 IPv4 网络中,能够使用非公开的内部地址尤为重要,因为 IPv4 地址有限。真实服务器无需特殊配置,这也使得可以在其上使用任何操作系统,并简化了配置过程。

这种方法的另一个优势是,TCP 或 UDP 端口不需要在虚拟服务器和真实服务器上相同,因为虚拟服务器执行的是地址转换,而不是转发未经修改的 IP 数据包。

我们将设置虚拟服务器,以便在192.168.56.100:80上监听 HTTP 请求,并将这些请求转发到真实服务器的端口8000

root@virtual-server# ipvsadm --add-service --tcp-service 192.168.56.100:80
root@virtual-server# ipvsadm --add-server --tcp-service 192.168.56.100:80 --real-server 10.20.30.2:8000 --masquerading

第一个命令创建一个虚拟服务器实例。第二个命令添加一个真实服务器以转发数据包——在我们的例子中是10.20.30.2:8000。最后,--masquearding (-m)选项告诉它在发送连接到该服务器时使用 NAT 方法。

我们在这里使用了ipvsadm命令行选项的长版本,但该命令也可以用短格式写出(指定轮询调度算法,-s rr):

ipvsadm -A -t 192.168.56.100:80 -s rr.

现在,我们可以通过ipvsadm –listipvsadm -l命令来确保虚拟服务器已正确配置:

root@virtual-server# ipvsadm –list
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  192.168.56.100:http rr
  -> 10.20.30.2:8000              Masq    1      0          0

现在,如果我们在客户端机器上运行wget http://192.168.56.100:80并在真实服务器上运行流量捕获工具,我们将看到以下输出:

root@real-server# tcpdump -n -i eth1 -q tcp port 8000
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes
23:56:16... IP 192.168.56.1.44320 > 10.20.30.2.8000: tcp 0
23:56:16... IP 10.20.30.2.8000 > 192.168.56.1.44320: tcp 0
23:56:16... IP 192.168.56.1.44320 > 10.20.30.2.8000: tcp 0
23:56:16... IP 192.168.56.1.44320 > 10.20.30.2.8000: tcp 129

在虚拟服务器上,我们将看到一个显著不同的输出:

root@virtual-server# tcpdump -n -i eth0 -q tcp port 80
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
00:02:19... IP 192.168.56.1.32890 > 192.168.56.100.80: tcp 0
00:02:19... IP 192.168.56.100.80 > 192.168.56.1.32890: tcp 0
00:02:19... IP 192.168.56.1.32890 > 192.168.56.100.80: tcp 0
00:02:19... IP 192.168.56.1.32890 > 192.168.56.100.80: tcp 129

如您所见,虚拟服务器完全接管了客户端和真实服务器之间的通信。从理论上讲,这是一个缺点,因为它大大增加了通过虚拟服务器传输的流量。但实际上,即使在普通硬件上,Linux 的网络性能也相当不错,所以这不是一个严重的问题。而且,特定应用的负载均衡解决方案也通过服务器代理所有流量,因此这与使用像 HAProxy 这样的服务并无太大区别。由于数据包转发和端口/地址转换发生在内核空间,这种方法的性能优于用户空间的负载均衡应用。

我们将简要介绍其他负载均衡机制,但由于种种原因,它们比 NAT 实现起来要不太实用,通常无需使用。

直接路由

要为直接路由设置 LVS,我们需要在添加真实服务器时使用--gatewaying (-g)选项:

root@virtual-server# ipvsadm --add-service --tcp-service 10.20.30.1:8000
root@virtual-server# ipvsadm --add-server --tcp-service 10.20.30.1:8000 --real-server 10.20.30.2 --gatewaying

使用此配置,当虚拟服务器接收到10.20.30.1:8000的请求时,它将简单地将该数据包中的 MAC 地址更改为10.20.30.2真实服务器的 MAC 地址,并将其重新发送到以太网网络,供真实服务器接收。然后,真实服务器将直接回复客户端,而不会对虚拟服务器造成额外负载。

尽管理论上这种方法是性能最优且概念上最简单的,但在实际操作中,它对真实服务器提出了最严格的要求。最小的要求是所有真实服务器必须在同一广播网络段内。另一个要求是所有真实服务器还必须能够响应来自与服务 IP 相同的虚拟 IP 的数据包,通常是通过将虚拟服务 IP 分配为别名来实现。

然而,将相同的 IP 地址分配给多个主机会导致地址冲突。为了在存在地址冲突的情况下使网络正常工作,必须让所有节点(除了虚拟服务器)忽略虚拟 IP 的 ARP 请求。例如,可以使用arptables工具来实现这一点:

root@real-server# arptables -A IN -d 10.20.30.1 -j DROP
root@real-server# arptables -A OUT -d 10.20.30.1 -j mangle --mangle-ip-s 10.20.30.2

为了真正避免这种冲突并确保没有真实服务器响应虚拟 IP 的 ARP 请求,必须在分配地址之前插入这些规则。这个事实使得使用通常的网络配置方法(如分发特定脚本或 NetworkManager)正确配置真实服务器变得困难,甚至不可能。

这个事实使得尽管从理论上讲具有优势,但在大多数网络中实施这一方案变得不切实际。

隧道

要设置一个用于隧道的虚拟服务器,我们在添加真实服务器时需要使用--ipip (-i)选项:

root@virtual-server# ipvsadm --add-service --tcp-service 192.168.56.100:8000
root@virtual-server# ipvsadm --add-server --tcp-service 192.168.56.100:8000 --real-server 10.20.30.2 --ipip

然后,我们需要在真实服务器上设置一个 IPIP 隧道,以便它能够处理来自虚拟服务器的传入隧道流量,并为其分配虚拟服务器 IP:

root@real-server# ip tunnel add ipip1 mode ipip local 10.20.30.2
root@real-server# ip link set dev ipip1 up
root@real-server# ip address add 192.168.56.100/32 dev ipip1

现在,如果我们向虚拟服务器发起一个 HTTP 请求,并在真实服务器上进行流量捕获,我们将看到带有虚拟 IP 请求的传入 IPIP 数据包:

root@real-server# tcpdump -vvvv -n -i eth1
tcpdump: listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes
01:06:05.545444 IP (tos 0x0, ttl 63, id 0, offset 0, flags [DF], proto IPIP (4), length 80)
    10.20.30.1 > 10.20.30.2: IP (tos 0x0, ttl 63, id 44915, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.56.1.51886 > 192.168.56.106.8000: tcp 0

尽管理论上这种方法使真实服务器可以位于任何网络,但在实践中存在若干困难。首先,真实服务器的操作系统必须支持 IPIP 隧道。即使在 Linux 系统中,如果它们运行在容器内且没有创建隧道的权限,这也可能是一个严重的问题,即使主机系统内核已构建了 IPIP 支持。其次,由于应直接将回复发送给客户端,而不是通过隧道返回,因此在采取源 IP 伪造防护措施的网络中,这种方案会失败——而这些防护措施应该是存在的。

保存和恢复 LVS 配置

可以以一种格式导出当前的 LVS 配置,以便它能够从标准输入加载:

root@virtual-server# ipvsadm --save
-A -t 192.168.56.100:http -s wlc
-a -t 192.168.56.100:http -r 10.20.30.2:8000 -m -w 1

你可以将输出保存到文件中,然后将其输入到ipvsadm –restore

root@virtual-server# ipvsadm –save > lvs.conf
root@virtual-server# cat lvs.conf | ipvsadm --restore

然而,在实践中,最好通过 Keepalived 或其他用户空间守护进程自动化 LVS 配置,正如我们将在本章稍后学习的那样。

额外的 LVS 选项

除了调度算法和真实服务器之间的负载均衡,LVS 还提供了一些额外的功能和选项。

连接持久性

默认情况下,LVS 将客户端的连接均衡到所有服务器,而不会将客户端与特定的服务器匹配。例如,这种方法适用于通过 HTTP 提供网页服务。然而,一些服务使用持久且有状态的连接,如果没有持久性机制,效果会很差。一个极端的例子是远程桌面连接:如果这种连接在多个服务器之间均衡,断开连接后将用户发送到另一台服务器,会创建一个全新的会话,而不是让用户回到已经运行的应用程序。

为了让 LVS 记住客户端与服务器的映射,并将来自同一客户端的新连接发送到同一服务器,你需要指定--persistent,并可选地指定持久性超时:

ipvsadm --add-service --tcp-service 192.168.56.100:80 --persistent 600

这个前述的命令会创建一个服务器,记住客户端到服务器的关联,持续600秒。

连接状态同步

LVS 的一个显著特点是其连接状态同步守护进程。在这种情况下,守护进程这个词部分上是个误用,因为它是在内核中实现的,并不是用户空间的进程。连接同步是单向的,具有专用的主(主控)节点和副本(备份)节点。

没有明确的对等配置。相反,连接状态通过 IP 组播发送到对等方。可以指定用于同步消息的网络接口:

root@first-virtual-server# ipvsadm --start-daemon=master --mcast-interface=eth0
root@second-virtual-server# ipvsadm --start-daemon=backup --mcast-interface=eth0

然而,仅仅通过连接状态同步是没有用的,除非还存在一个故障转移机制,它可以在主负载均衡器节点失败时,将虚拟 IP 转移到备份节点。

在下一部分,我们将学习如何使用 Keepalived 守护进程为 VRRP 配置故障转移。

使用 Keepalived 的主动/备份配置和负载均衡

一台被配置为负载均衡器的 Linux 服务器,为多个工作服务器提供服务,即使其中任何一个工作服务器失败,服务也能保持可用。然而,在这种方案中,负载均衡器本身成为了单点故障,除非管理员还采取措施为多个负载均衡器提供故障转移机制。

实现故障转移的常见方法是使用一个漂浮的虚拟 IP 地址。假设www.example.com被配置为指向192.0.2.100。如果你将该地址直接分配给192.0.2.0/24网络中的一个负载均衡服务器,它就成为了单点故障。然而,如果你设置了两个来自该网络的主地址的服务器(例如192.0.2.10192.0.2.20),你可以使用一种特殊的故障转移协议,让两台或更多服务器决定哪个服务器持有虚拟192.0.2.100地址,并在主服务器失败时自动将其转移到其他服务器。

为此目的,最流行的协议叫做虚拟路由冗余协议VRRP)。尽管它的名字中有“路由器”一词,但使用 VRRP 的机器不一定是路由器——尽管它最初是由路由操作系统实现的,但现在它的应用范围更广泛。

Linux 上最流行的 VRRP 实现是 Keepalived 项目。除了 VRRP 外,它还实现了 LVS 的配置前端,因此可以编写同时处理故障转移和负载均衡的配置文件,而无需手动使用ipvsadm设置 LVS。

安装 Keepalived

大多数 Linux 发行版都在其软件库中提供了 Keepalived,因此安装它是一个简单的过程。在 Fedora、RHEL 及其社区衍生版本如 Rocky Linux 上,可以使用以下命令安装它:

sudo dnf install keepalived

在 Debian、Ubuntu 和其他使用 APT 的发行版上,运行以下命令:

sudo apt-get install keepalived

现在我们已经安装了 Keepalived,让我们来看看 VRRP 协议的基础知识。

VRRP 协议操作的基础知识

VRRP 和类似协议(例如较旧的热备份路由器协议HSRP)和社区开发的通用地址冗余协议CARP))基于选举主节点的思想,并通过监听其保持活动数据包来持续检查其状态。这些协议统称为第一跳冗余协议FHRPs)。

最初,每个节点都假设可能是主节点,并开始传输保持活动的数据包(在 VRRP 术语中称为广告),其中包括 VRRP 实例的唯一标识符和优先级值。同时,它们都开始监听传入的 VRRP 广告数据包。如果一个节点接收到优先级值比自己更高的数据包,则该节点承担备份角色并停止传输保持活动的数据包。优先级最高的节点成为主节点,并分配虚拟地址给自己。

当选主节点继续定期发送 VRRP 广告数据包以表明其功能时,其他节点保持备份状态。如果原始主节点停止传输 VRRP 数据包,则会启动新的选举。

如果原始的主节点在故障后重新出现,有两种可能的情况。默认情况下,在 Keepalived 实现中,优先级最高的节点将始终抢占,并且在其宕机期间承担其角色的节点将返回备份状态。通常这是一个好主意,因为它可以在正常情况下保持主路由器的可预测性。然而,抢占也会导致额外的故障转移事件,可能会导致连接中断和短暂的服务中断。如果不希望发生这样的中断,可以禁用抢占。

配置 VRRP

让我们看一个简单的 VRRP 配置示例,然后详细查看其选项:

vrrp_instance LoadBalancers {
    state BACKUP
    interface eth1
    virtual_router_id 100
    priority 100
    advert_int 1
    nopreempt
    virtual_ipaddress {
        10.20.30.100/24
    }
}

您需要将该配置保存到 Keepalived 配置文件中 - 通常是/etc/keepalived/keepalived.conf

Keepalived 配置文件可能包含一个或多个 VRRP 实例。它们的名称仅供信息参考,可以是任意的,只要它们在配置文件中是唯一的。

state选项定义了路由器的初始状态。将BACKUP指定给所有路由器是安全的,因为它们会自动选举活动路由器,即使它们的配置中没有任何一个具有MASTER状态。

VRRP 实例绑定到网络接口并且只存在于一个广播域中,因此我们需要指定 VRRP 广告包将从哪个网络接口发送。在这个示例中,它是interface eth1

virtual_router_id 100

接下来的两个参数是可选的。默认的 VRRP 路由器优先级是 100,除非另有指定。如果你希望手动指定路由器优先级,可以使用 1 到 254 之间的数字——优先级数字 0255 是保留的,不能使用。较高的优先级值意味着路由器更有可能被选为活动(主)路由器。

广告包传输间隔(advertise_interval)默认为 1 秒,对于大多数安装来说,这是一个合理的设置。VRRP 不会产生太多流量,因此没有强烈的理由将间隔设置得更长。

最后,我们指定了一个虚拟地址,10.20.30.100/24。最多可以指定 20 个虚拟地址,地址之间用空格分隔。需要注意的一点是,所有虚拟地址不必属于同一网络,也不必与 VRRP 实例运行的网络接口的永久非浮动地址位于同一网络。通过将私有 IPv4 地址分配给两台路由器的 WAN 接口,并将互联网服务提供商分配的公共 IPv4 地址设置为虚拟地址,甚至可以创建冗余的互联网连接。

验证 VRRP 的状态

当你将示例配置保存到/etc/keepalived/keepalived.conf并使用sudo systemctl start keepalived.service启动进程(在带有 systemd 的 Linux 发行版中),你的服务器将成为活动(主)节点,并将虚拟地址分配给其网络接口,直到你将一个优先级更高的第二台服务器添加到同一网络。

验证此操作的最简单方法是查看我们配置 VRRP 运行的接口的 IP 地址:

$ ip address show eth1
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
  link/ether 08:00:27:33:48:b8 brd ff:ff:ff:ff:ff:ff
  inet 10.20.30.1/24 brd 10.20.30.255 scope global eth1
  valid_lft forever preferred_lft forever
  inet 10.20.30.100/24 scope global secondary eth1
  valid_lft forever preferred_lft forever
  inet6 fe80::a00:27ff:fe33:48b8/64 scope link
  valid_lft forever preferred_lft forever

你还可以使用流量捕获工具,如tcpdump,验证服务器是否确实正在发送 VRRP 广告包:

$ sudo tcpdump -i eth1
listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes
04:38:54.038630 IP 10.20.30.1 > 224.0.0.18: VRRPv2, Advertisement, vrid 100, prio 100, authtype none, intvl 1s, length 20
04:38:55.038799 IP 10.20.30.1 > 224.0.0.18: VRRPv2, Advertisement, vrid 100, prio 100, authtype none, intvl 1s, length 20
04:38:56.039018 IP 10.20.30.1 > 224.0.0.18: VRRPv2, Advertisement, vrid 100, prio 100, authtype none, intvl 1s, length 20

然而,也有一种方法可以直接从 Keepalived 请求 VRRP 的状态数据。与一些其他服务不同,Keepalived(自 2.2.7 版本起)不包含套接字接口或命令行工具来与其交互,而是使用 POSIX 信号来触发状态文件的创建。这比专用工具要不太方便。

首先,你需要查找 Keepalived 进程的标识符(PID)。获取它的最佳方法是读取其 PID 文件,通常位于/run/keepalived.pid

通过向进程发送 SIGUSR1 信号(kill -USR1 <PID>),会在 /tmp/keepalived.data 生成一个数据文件。此文件包含多个部分,我们立即感兴趣的部分是名为 VRRP 拓扑 的部分,用来查找 VRRP 实例的状态:

$ cat /run/keepalived/keepalived.pid
3241
$ sudo kill -USR1 3241
$ sudo cat /etc/keepalived.data
...
------< VRRP Topology >------
  VRRP Instance = LoadBalancers
   VRRP Version = 2
   State = MASTER
   Flags: none
   Wantstate = MASTER
   Last transition = ...
   Interface = eth1
   Using src_ip = 10.20.30.1
   Multicast address 224.0.0.18
   ...
   Virtual Router ID = 100
   Priority = 100
   ...
   Preempt = enabled
   Promote_secondaries = disabled
   Authentication type = none
   Virtual IP (1):
     10.20.30.100/24 dev eth1 scope global set
   ...

还可以通过向 Keepalived 进程发送 SIGUSR2 信号,要求其生成统计文件(/tmp/keepalived.stats):

$ sudo kill -USR1 $(cat /run/keepalived.pid)
$ sudo cat /etc/keepalived.stats
VRRP Instance: LoadBalancers
  Advertisements:
    Received: 0
    Sent: 112
  Became master: 1
  Released master: 0
  Packet Errors:
    Length: 0
    TTL: 0
    Invalid Type: 0
    Advertisement Interval: 0
    Address List: 0
  Authentication Errors:
    Invalid Type: 0
    Type Mismatch: 0
    Failure: 0
  Priority Zero:
    Received: 0
    Sent: 0

虽然目前信息获取方式稍显笨拙,但您可以从这些数据文件中获取大量关于 VRRP 实例的信息。

配置虚拟服务器

正如我们之前所说,Keepalived 也可以创建和维护 LVS 配置。与手动配置 LVS 相比,它的优势在于,Keepalived 很容易在启动时启动,因为它总是与服务管理集成(通常是 systemd 单元),而 LVS 是内核组件,缺乏配置持久化机制。此外,Keepalived 还可以执行健康检查,并在服务器故障时重新配置 LVS 子系统。

为了演示,我们假设一个最小的负载均衡配置,使用加权轮询(Weighted Round Robin)负载均衡算法,NAT 作为负载均衡方法,两个具有相等权重的真实服务器:

global_defs {
  lvs_id WEB_SERVERS
}
virtual_server 192.168.56.1 80 {
    ! Weighted Round Robin
    lb_algo wrr
    lb_kind NAT
    protocol TCP
    ! Where to send requests if all servers fail
    sorry_server 192.168.56.250 80
    real_server 192.168.56.101 80 {
        weight 1
    }
    real_server 192.168.56.102 80 {
       weight 1
    }
}

我们在 LVS 传输层负载均衡 部分讨论的每个负载均衡算法都可以在 lb_algo 选项中指定,因此它可以是 lb_algo wlc(加权最少连接)等。

如果将该配置保存到 /etc/keepalived/keepalived.conf 并使用 systemctl restart keepalived 重启守护进程,您可以验证它是否创建了 LVS 配置:

$ sudo systemctl restart keepalived
$ sudo ipvsadm
IP Virtual Server version 1.2.1 (size=4096)
Prot LocalAddress:Port Scheduler Flags
  -> RemoteAddress:Port           Forward Weight ActiveConn InActConn
TCP  server:http wrr
  -> 192.168.56.101:http          Masq    1      0      0
  -> 192.168.56.102:http          Masq    1      0       0

现在我们已经知道如何进行基本的虚拟服务器配置,接下来让我们学习如何监控真实服务器的状态,并在服务器失败时将其排除。

服务器健康监控

LVS 本身纯粹是一个负载均衡解决方案,它不包含服务器健康监控组件。然而,在实际安装中,及时排除无法正常工作或正在进行计划维护的服务器是一项至关重要的任务,因为将用户请求指向无法正常工作的服务器会破坏高可用性配置的目标。Keepalived 包含监控功能,可以检测并移除未通过健康检查的服务器。

每个真实服务器都单独配置健康检查,尽管在大多数实际安装中,它们的配置应逻辑上保持一致,通常不建议为不同的服务器使用不同的健康检查设置。

TCP 和 UDP 连接检查

最简单但最不具体的健康检查类型是简单的连接检查。它有两种变体——UDP_CHECKTCP_CHECK,分别适用于 UDP 和 TCP 协议。以下是该检查类型的配置示例:

real_server 192.168.56.101 80 {
    weight 1
    TCP_CHECK {
        connect_timeout 3
        retry 3
        delay_before_retry 2
    }
}

如你所见,不需要显式地指定用于连接检查的 TCP 端口:Keepalived 会使用服务器地址配置中指定的端口(在本例中为端口80)。

当你使用该配置启动 Keepalived 时,它将激活健康检查子系统并开始连接检查。如果192.168.56.101上没有运行 Web 服务器并且端口80没有监听,Keepalived 将在检查失败三次后(根据retry选项定义)将该服务器从 LVS 配置中移除。你将在系统日志中看到如下信息(例如,你可以使用sudo journalctl -u keepalived查看):

Keepalived_healthcheckers: Activating healthchecker for service [192.168.56.101]:tcp:80 for VS [192.168.56.1]:tcp:80
Keepalived: Startup complete
Keepalived_healthcheckers: TCP_CHECK on service [192.168.56.101]:tcp:80 failed.
Keepalived_healthcheckers: Removing service [192.168.56.101]:tcp:80 from VS [192.168.56.1]:tcp:80

这种简单的 TCP 检查的优点是它适用于任何基于 TCP 的服务,无论其应用层协议是什么:你可以用它来检查 Web 应用程序,也可以用于 SMTP 服务器或任何自定义协议。然而,服务器对 TCP 连接的响应本身并不总是意味着它也正常运行。例如,Web 服务器可能会响应 TCP 连接,但每次请求都返回500 内部服务器错误的结果。

如果你希望对检查逻辑进行完美、精细的控制,Keepalived 提供了MISC_CHECK方法来满足这个需求。

任意脚本检查

最通用的检查是MISC_CHECK,它没有任何内建的检查逻辑,而是依赖于外部脚本。例如,你可以让 Keepalived 执行/tmp/my_check.sh脚本,并在该脚本返回非零退出码时认为服务器不可用:

real_server 192.168.56.101 80 {
    MISC_CHECK {
        misc_path "/tmp/my_check.sh"
        misc_timeout 5
        user nobody
    }
}

使用这种类型的健康检查,你可以监控任何类型的服务器,尽管缺点是你必须在脚本中实现所有的检查逻辑。

HTTP 和 HTTPS 检查

虽然MISC_CHECK给你完全的控制权,但在大多数情况下,它也是一种过度设计。

作为具体性和灵活性之间的折衷,你还可以使用特定协议的检查。例如,HTTP_GET检查会向 URL 发起 HTTP 请求,并检查响应的哈希值,或其 HTTPS 等价物SSL_CHECK

例如,假设你想提供一个简单的静态页面。在这种情况下,你可以通过使用md5sum命令手动计算该页面的 MD5 哈希值:

$ cat index.xhtml
<!DOCTYPE html>
<html>
  <body>
    <p>hello world</p>
  </body>
</html>
$ md5sum index.xhtml
fecf605e44acaaab933e7b509dbde185  index.xhtml

要计算动态生成页面的预期哈希值,你可以使用 Keepalived 自带的genhash工具。如果你用--verbose运行它,它将显示有关其执行的 HTTP 请求的详细信息:

$ genhash --verbose --server 192.168.56.101 --port 80 --url /index.xhtml
----------[    HTTP Header Buffer    ]----------
0000  48 54 54 50 2f 31 2e 30 - 20 32 30 30 20 4f 4b 0d   HTTP/1.0 200 OK.
...
----------[ HTTP Header Ascii Buffer ]----------
HTTP/1.0 200 OK
...
----------[        HTML Buffer        ]----------
0000  3c 21 44 4f 43 54 59 50 - 45 20 68 74 6d 6c 3e 0a   <!DOCTYPE html>.
...
---------[    HTML hash resulting    ]---------
0000  fe cf 60 5e 44 ac aa ab - 93 3e 7b 50 9d bd e1 85   ..`^D....>{P....
--------[ HTML hash final resulting ]--------
fecf605e44acaaab933e7b509dbde185
Global response time for [/index.xhtml] = 2468 usecs

然而,它只计算 HTTP 响应体的哈希值,而不是包括头部的完整响应,因此你不必非得使用它——如果你愿意,你可以通过任何其他 HTTP 请求工具来获取响应体。

一旦你有了预期的响应哈希值,你可以配置HTTP_GET检查定期发起请求并将其响应体与给定的 MD5 哈希值进行对比:

real_server 192.168.56.101 80 {
    weight 1
    HTTP_GET {
         url {
             path /index.xhtml
             digest fecf605e44acaaab933e7b509dbde185
        }
        connect_timeout 3
        retry 3
        delay_before_retry 2
    }
}

由于普通的、用户可见的页面可能随时变化,因此如果您希望使用哈希值检查,最好创建一个内容保持不变的特殊页面。否则,页面内容变化时哈希值会发生变化,从而导致检查失败。

邮件通知

还可以配置 Keepalived,当任何状态变化发生时,将邮件通知发送到一个或多个地址——即 VRRP 从主节点切换到备节点或反之,或者当实际服务器变得不可用并且失败检查时,或者通过之前失败的检查并重新添加到内核中的 LVS 配置时。

下面是一个配置示例:

global_defs {
    notification_email {
        admin@example.com
        webmaster@example.com
    }
    notification_email_from keepalived@example.com
    smtp_server 203.0.113.100
    smtp_connect_timeout 30
}

不幸的是,LVS 不支持 SMTP 验证,因此如果选择使用内置的邮件通知机制,您需要将服务器配置为开放转发,并采取适当措施确保只有运行 Keepalived 的服务器能够通过它发送邮件——例如,通过防火墙规则限制仅允许您的私人网络访问它。

应用层负载均衡

LVS 是一个灵活的负载均衡框架,且由于它是在内核中实现的,因此它是一种高性能的解决方案,因为它不需要上下文切换,也不需要在用户空间程序与内核之间进行数据传输。它在 TCP 或 UDP 协议层工作,使其不依赖于应用程序,能够与任何应用服务一起使用。

然而,它缺乏对应用协议的意识也是其最大弱点,因为这意味着它无法执行任何特定于协议的优化。例如,对于那些可能向多个用户返回相同回复的应用程序,一个显而易见的提高性能的方法是缓存回复。LVS 仅在 TCP 连接或 UDP 流上操作,因此它无法知道任何应用层协议中的请求或回复是什么样子的——它根本不检查 TCP 或 UDP 负载。

此外,许多现代应用层协议都进行了加密,因此无法查看服务器未启动或终止的连接的负载内容。

直接将连接从用户转发到实际服务器也有更多潜在的缺点。例如,这使得服务器暴露于基于 TCP 的攻击,如 SYN 洪水攻击,并且要求所有服务器或入口点的专用防火墙设置采取适当的安全措施。

解决这些问题的一种方法是使用一个用户空间的守护进程,它实现您运行的服务协议,终止 TCP 连接,并将应用层协议请求转发到目标服务器。

由于世界上大多数应用程序目前都是 Web 应用程序,因此大多数此类解决方案都针对 HTTP 和 HTTPS。它们提供内存中的响应缓存来加速回复,终止 SSL 连接,管理证书,并可以选择提供安全功能。HAProxy 和 Varnish 是 Web 应用负载均衡服务器的典型例子,尽管也有其他类似的解决方案。

也有针对其他协议的解决方案,提供高可用性和负载均衡。例如,OpenSIPS 和 FreeSWITCH 可以为使用 SIP 协议的 互联网语音协议 (VoIP) 通话提供负载均衡。然而,此类解决方案超出了本书的范围。我们将快速了解 HAProxy 作为 Web 应用的高可用性解决方案之一。

使用 HAProxy 进行 Web 应用负载均衡

HAProxy 配置是一个庞大的主题,因为它包含了许多功能。我们将通过一个简单的配置示例来了解它的能力:

frontend main
    bind *:80
    acl url_static path_beg -i /static /images /javascript /stylesheets
    acl url_static path_end -i .jpg .gif .png .css .js
    use_backend static          if url_static
    default_backend             app
backend static
    balance     roundrobin
    server static 192.168.56.200:80 check
backend app
    balance     roundrobin
    server  srv1 192.168.56.101:5000 check
    server  srv2 192.168.56.102:5000 check

正如你所看到的,其核心功能是将前端(即负载均衡实例)与后端——实际应用服务器集合进行映射。

在这种情况下,一个前端映射到两个后端:一个专门用于提供静态文件的服务器和两个应用服务器。这仅在 HAProxy 中可行,因为它自己处理 HTTP 请求,发送新请求到其后端,并准备回复用户,而不是仅仅平衡连接。

总结

在本章中,我们学习了高可用性的概念:冗余、故障转移和负载均衡。我们还学习了如何通过创建绑定接口来配置链路层冗余,以及如何在网络层设置冗余路由。为了确保传输层冗余,我们学习了如何手动使用 ipvsadm 配置 LVS 子系统,或者使用 Keepalived,并学习了如何使用 VRRP 为负载均衡节点提供故障转移。最后,我们简要了解了 HAProxy 作为 Web 服务器的应用层负载均衡解决方案。

在下一章中,我们将学习如何使用配置自动化工具管理 Linux 系统。

进一步阅读

若想了解更多本章涉及的内容,请查看以下资源:

第十四章:使用 Chef 进行自动化

在当今技术驱动的世界中,管理和维护大规模基础设施变得越来越复杂。系统管理员经常面临部署和配置大量服务器、确保各环境一致性以及有效管理更新和变更的挑战。手动配置过程耗时、容易出错且难以扩展。为了解决这些问题,自动化已成为关键解决方案。在众多可用的自动化工具中,Chef 作为一个强大的配置管理系统脱颖而出,简化了基础设施的供应和管理。

本章的目的是全面介绍在 Linux 环境中使用 Chef 自动化基础设施的概述。旨在探讨 Chef 的各个组件及其角色,分析使用 Chef 进行基础设施自动化的好处,讨论实际应用案例,并分析 Chef 的优势和局限性。通过本章的学习,您将清楚地了解 Chef 如何在基于 Linux 的系统中革新基础设施管理。

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

  • 基础设施自动化概述

  • Chef 简介

  • Chef 服务器

  • Chef 工作站

基础设施自动化概述

自动化基础设施对于克服管理复杂和动态环境所带来的挑战至关重要。通过自动化重复任务,管理员可以减少人为错误,提高效率,并确保所有服务器上的配置一致性。自动化还能实现更快的部署、提高可伸缩性和增强系统安全性。在灵活性和定制性至关重要的 Linux 环境中,基础设施自动化变得更加关键。

Linux 自动化的好处

在 Linux 中自动化基础设施带来了几个好处。首先,它允许快速和一致的服务器供应,减少了手动配置所需的时间。其次,自动化确保遵循标准配置,最小化不一致性,并便于故障排除。此外,自动化通过根据需求快速添加或删除服务器来提高可伸缩性。最后,自动化通过强制实施一致的安全策略和便于及时更新和补丁管理来增强安全性。既然我们已经了解了自动化的好处,让我们继续看看下一节中自动化的工作原理。

Chef 简介

在其核心,Chef 遵循基础设施即代码IaC)的方法,即使用代码定义系统或服务器的期望状态。这段代码使用称为 Chef DSL 的领域特定语言DSL)编写,描述了系统中各种组件、应用程序和服务的配置、安装和管理。

什么是 Chef?

Chef 是一个开源配置管理工具,允许管理员定义并自动化基础设施即代码(IaC)。它遵循声明式方法,管理员指定系统的期望状态,Chef 确保系统符合该状态。Chef 提供平台无关的解决方案,支持在各种操作系统上进行自动化,包括 Linux。它基于客户端-服务器架构,并使用一种名为 Ruby 的领域特定语言(DSL)。

Chef 的主要功能

Chef 提供了一套强大的功能,促进了基础设施的自动化。这些功能包括:

  • 基础设施即代码:Chef 将基础设施配置视为代码,允许管理员轻松地进行版本控制、测试和部署基础设施变更。

  • 幂等操作:Chef 通过仅在必要时运行配置食谱来确保幂等性,从而消除了不必要变更的风险。

  • 资源抽象:Chef 将系统资源(如文件、服务和软件包)抽象为可管理的组件,从而简化了配置管理。

  • 可测试性:Chef 支持测试驱动的基础设施开发,使管理员能够在部署前验证和测试其配置。

使用自动化可以节省大量时间并减少人为错误的风险。

Chef 架构概述

Chef 遵循客户端-服务器架构。Chef 架构的关键组件如下:

  • Chef 服务器:存储和管理配置数据、策略和食谱的中央组件。它充当系统期望状态的权威信息源。

  • Chef 工作站:管理员编写和测试食谱并管理基础设施的机器。它承载开发环境和 Chef 客户端工具。

  • Chef 节点:由 Chef 管理和配置的目标机器。每个节点都运行一个 Chef 客户端,该客户端与 Chef 服务器通信,以获取配置指令并将其应用到节点。

这些组件将在以下章节中进行更详细的介绍。

Chef 服务器

Chef 服务器是 Chef 基础设施的核心。它充当存储配置数据、食谱、策略和节点信息的中央存储库。服务器提供一个 Web 界面和 API 来与 Chef 资源交互。管理员使用 Chef 服务器来管理节点、角色、环境和数据包,并将食谱分发到节点。

Chef 服务器采用推拉机制来管理系统中节点(服务器)的配置。该机制允许管理员定义节点的期望状态并强制执行这些状态:

  • 推送机制:在推送机制中,Chef 服务器主动将配置更新和配方推送到节点。当管理员对配置进行更改或定义新的配方时,他们会将这些更改上传到 Chef 服务器。Chef 服务器随后识别目标节点并将更新的配置推送到它们。这个过程可以手动启动,也可以通过自动化流程启动。

  • 拉取机制:在拉取机制中,节点被配置为定期检查 Chef 服务器以获取更新。节点会定期从 Chef 服务器请求并拉取其配置。当节点检查更新时,它会将当前状态与 Chef 服务器上指定的期望状态进行比较。如果存在差异,节点会拉取必要的配置并相应地更新自身。

Chef 服务器组件

Chef 服务器的关键组件包括以下内容:

  • 数据存储:数据存储是 Chef 服务器存储节点、cookbooks、角色、环境和数据包的元数据和配置信息的地方。它利用数据库(如 PostgreSQL)来存储这些信息。

  • Chef 服务器 API:Chef 服务器 API 提供一个 RESTful 接口,用于以编程方式管理和与 Chef 资源交互。它允许管理员查询、修改和删除资源。

  • 用户和认证管理:Chef 服务器管理用户帐户和认证机制。它提供 基于角色的访问控制 (RBAC) 来控制用户权限并限制对敏感信息的访问。

Chef 服务器在实现高效的基础设施自动化和配置管理中起着至关重要的作用,支持大规模部署中的可扩展性、一致性和安全性。通过提供一个集中管理配置和 cookbooks 的中心,它简化了部署和维护复杂系统的过程,同时促进了系统管理员和开发人员团队之间的协作和最佳实践。

Cookbooks 和配方

Cookbooks 是 Chef 的基本构建模块。它们包含配置和管理基础架构特定组件所需的配方、属性、模板和其他文件。Chef 服务器充当存储和管理 cookbooks 的仓库。管理员可以通过 Chef 服务器上传、版本控制和分发 cookbooks 到节点。Cookbooks 按逻辑组进行组织,并使用目录结构定义。

使用 Chef,系统管理员可以自动化任务,如包安装、配置文件管理、服务管理等。Chef 的声明式特性使得系统易于扩展和可重复性强,非常适合用于管理复杂的基础架构。

Chef 工作站

Chef 工作站作为管理员开发、测试和管理 Chef 基础架构的管理机器。要设置 Chef 工作站,管理员需要在本地机器上安装 Chef 开发工具包ChefDK)。ChefDK 包含开发和管理食谱所需的所有工具、库和依赖项。

开发工作流

Chef 工作站提供了一个开发环境,管理员可以在其中编写和测试食谱,然后再将其部署到节点上。管理员使用文本编辑器或 集成开发环境IDEs)来编写使用基于 Ruby 的 Chef DSL 编写的食谱。工作站还包括诸如 Test Kitchen 等工具,允许在各种虚拟化或容器化环境中测试食谱。

管理环境和角色

在 Chef 中,环境和角色在管理基础架构配置中起着至关重要的作用。管理员定义环境来表示基础架构的不同阶段(如开发、测试和生产)。环境使管理员能够设置特定于环境的属性,并控制部署到每个环境的食谱版本。而角色则定义了根据节点的目的或功能所需的状态。它们将特定角色所需的属性和食谱组合在一起,并可以应用于多个节点。

这是一个 Chef 环境 JSON 文件的示例:

{
  "name": "my_environment",
  "description": "Sample environment for my application",
  "cookbook_versions": {
    "my_cookbook": "= 1.0.0",
    "another_cookbook": "= 2.3.1"
  },
  "default_attributes": {
    "my_app": {
      "port": 8080,
      "debug_mode": false
    },
    "another_app": {
      "enabled": true
    }
  },
  "override_attributes": {
    "my_app": {
      "port": 8888
    }
  },
  "json_class": "Chef::Environment",
  "chef_type": "environment"
}

在此示例中,我们定义了一个名为 my_environment 的环境。它具有一个 description 字段,用于简要描述该环境。

cookbook_versions 部分指定了环境中食谱的所需版本。在这种情况下,my_cookbook 被设置为版本 1.0.0,而 another_cookbook 被设置为版本 2.3.1

default_attributes 部分包含环境的默认属性值。它将 my_appport 属性设置为 8080,将 debug_mode 属性设置为 false,并将 another_appenabled 属性设置为 true

override_attributes 部分允许您覆盖食谱中的特定属性值。在此示例中,它将 my_appport 属性设置为 8888,覆盖了默认值。

json_classchef_type 字段是必需的,分别指定环境的类和类型。

要使用此 JSON 文件创建或更新环境,您可以使用 knife 命令行工具或 Chef API。例如,使用 knife,您可以运行以下命令:

knife environment from file my_environment.json

请确保将 my_environment.json 替换为实际的文件名,并根据您的特定环境配置调整 JSON 文件的内容。

现在,让我们来看一个角色配置的 JSON 模板:

{
  "name": "webserver",
  "description": "Role for web server nodes",
  "json_class": "Chef::Role",
  "chef_type": "role",
  "run_list": [
    "recipe[my_cookbook::default]",
    "recipe[another_cookbook::setup]"
  ],
  "default_attributes": {
    "my_cookbook": {
      "port": 8080,
      "debug_mode": false
    },
    "another_cookbook": {
      "config_file": "/etc/another_cookbook.conf"
    }
  },
  "override_attributes": {
    "my_cookbook": {
      "port": 8888
    }
  },
  "env_run_lists": {
    "production": [
      "recipe[my_cookbook::production]"
    ],
    "development": [
      "recipe[my_cookbook::development]"
    ]
  }
}

在此示例中,我们定义了一个名为 webserver 的角色。它具有一个 description 字段,用于简要描述该角色。

run_list部分指定要包含在角色运行列表中的食谱列表。在此示例中,它包括来自my_cookbook食谱的default食谱和来自another_cookbook食谱的setup食谱。

default_attributes部分包含角色的默认属性值。它将my_cookbookport属性设置为8080,并将debug_mode属性设置为false。它还将another_cookbookconfig_file属性设置为/etc/another_cookbook.conf

override_attributes部分允许你覆盖来自食谱的特定属性值。在这个例子中,它将my_cookbookport属性设置为8888,覆盖默认值。

env_run_lists部分为不同的环境指定不同的运行列表。在这个例子中,它包括production运行列表,其中包含来自my_cookbook食谱的production食谱,以及development运行列表,其中包含来自my_cookbook食谱的development食谱。

json_classchef_type字段是必需的,分别指定角色的类和类型。

要使用此 JSON 文件创建或更新角色,可以使用 knife 命令行工具或 Chef API。例如,使用knife时,可以运行以下命令:

knife role from file webserver.json

确保将webserver.json替换为实际的文件名,并根据你的角色配置调整 JSON 文件的内容。

角色和环境是 Chef 非常有用的功能,因为它们可以简化操作;例如,你只需提到一个角色,而不是列举每一个食谱,这样就可以包括多个食谱。

Chef 节点

Chef 节点是由 Chef 管理和配置的目标机器。每个节点都有唯一的身份,并需要安装 Chef 客户端。管理员在 Chef 服务器中为每个节点定义属性和角色,决定节点如何配置以及应用哪些食谱。

节点注册

要将节点加入 Chef 基础架构,管理员通过安装 Chef 客户端并将其注册到 Chef 服务器来启动节点。在引导过程中,节点生成一个客户端密钥,使其能够安全地与 Chef 服务器进行身份验证。

与 Chef 服务器的通信

一旦注册,节点上的 Chef 客户端将与 Chef 服务器通信以检索其配置指令。客户端定期与服务器进行合并,以确保节点的状态与 Chef 服务器定义的期望状态一致。Chef 客户端对节点的配置进行必要的更改以实现合并。

Chef 节点通过安全且经过认证的过程与 Chef 服务器进行通信。Chef 客户端(运行在节点上)与 Chef 服务器之间的通信基于 HTTPS,并依赖于加密密钥和证书进行身份验证。以下是通信过程的工作原理以及所需的配置:

  • /etc/chef/client.rb(在 Linux 系统中)或等效位置。

  • 客户端身份和验证:Chef 客户端需要一个唯一的身份,这个身份由客户端名称(通常是节点的主机名)和客户端密钥表示。

客户端密钥是为客户端生成的私钥,必须安全地存储在节点上。此密钥用于在客户端与 Chef 服务器通信时进行身份验证。

  • Chef 服务器 URL:Chef 客户端需要知道 Chef 服务器的 URL,以建立连接。此 URL 会在客户端配置文件中指定。

  • 验证密钥:Chef 服务器发放一个验证密钥(也称为验证器密钥),新节点使用该密钥将自己注册到 Chef 服务器。

此验证密钥必须放置在节点上,通常放在名为validation.pem的文件中(同样,在 Linux 系统中位于/etc/chef/目录下)。

  • 节点注册:当一个新节点(已安装 Chef 客户端)上线时,它使用验证密钥将自己注册到 Chef 服务器。

在注册过程中,节点提供其客户端名称和客户端密钥来进行身份验证。

通过设置正确的配置并确保合适的加密密钥和证书到位,Chef 节点可以安全地与 Chef 服务器通信,获取最新的配置数据,并执行 Chef 运行,使其配置达到预期状态。

食谱开发

食谱书是 Chef 中配置管理的核心单元。它们由一组相关的食谱、属性、模板和其他文件组成,这些文件用于配置和管理基础设施的特定方面。食谱书被组织成多个目录,每个目录代表一个特定的组件或服务。

食谱结构与组件

食谱遵循特定的目录结构,包含以下组件:

  • 食谱:食谱是食谱书的主要构建模块。它们定义了配置特定组件或服务所需的步骤和资源。食谱可以包含其他食谱,并利用属性来定义所需的状态。

  • 属性:属性允许管理员定义自定义的变量来调整食谱的行为。它们可以用于指定软件包版本、文件路径、服务配置和其他参数。

  • 模板:模板用于动态生成配置文件。它们可以包含 嵌入式 Ruby (ERB) 代码,以注入属性或动态生成内容。

  • 文件:食谱书可以包含配置所需的其他文件,如脚本、证书或二进制文件。

一系列的食谱、模板和其他资源被组合在一起,用于管理一组特定的相关配置。食谱提供了一种模块化和可重用的方式来管理系统的各个方面。

编写食谱和资源

如前所述,配方定义了配置特定组件或服务所需的步骤和资源。资源代表系统中的个体元素,例如包、文件、服务或用户。管理员使用 Chef 提供的资源类型,例如packagefileservicetemplate等,来定义每个资源的期望状态。通过指定期望状态,管理员可以让 Chef 将节点的配置调整为匹配该状态。

这是一个简单的 Chef 配方示例,用于安装一个包:

# Recipe: install_package
package 'my_package' do
  action :install
end

在这个示例中,我们有一个名为install_package的配方。package资源用于管理名为my_package的包的安装。默认情况下,package资源的action属性设置为:install,这指示 Chef 安装该包。如果该包已安装,它将直接继续。

要使用此配方,您可以创建一个食谱,并将此配方放入适当的配方文件中(例如recipes/default.rb)。确保您要安装的包在系统的包管理器仓库中可用。

配方设置完成后,您可以在节点上运行 Chef 并使用食谱触发安装指定的包。

资源模板的示例如下:

# Resource definition
resource_name :my_resourceproperty :name, String, name_property: true
property :port, Integer, default: 8080
property :enabled, [true, false], default: true
# Actions
default_action :create
action :create do
  template "/etc/myapp/#{new_resource.name}.conf" do
    source 'myapp.conf.erb'
    variables port: new_resource.port, enabled: new_resource.enabled
    owner 'root'
    group 'root'
    mode '0644'
    action :create
  end
end
action :delete do
  file "/etc/myapp/#{new_resource.name}.conf" do
    action :delete
  end
end

在这个示例中,我们定义了一个名为my_resource的资源。它有三个属性:name(字符串)、port(整数)和enabled(布尔值)。name属性被标记为名称属性,使用name_property: trueport属性的默认值为8080enabled属性的默认值为true

该资源的默认操作设置为:create。在:create操作中,我们使用template资源为我们的应用程序生成配置文件。模板源指定为myapp.conf.erb,这意味着它将使用相应的 ERB 模板文件。我们通过variables属性将portenabled变量传递给模板。模板文件将以root为拥有者和组,并将文件权限设置为0644

:delete操作使用file资源删除配置文件。

您可以根据具体需求自定义此模板。记得将myapp.conf.erb替换为您的实际模板文件名,并根据需要调整路径和权限。

使用 Chef 管理基础设施

节点引导是将目标机器准备为由 Chef 管理的过程。它包括安装 Chef 客户端并将节点注册到 Chef 服务器。引导过程可以手动进行,也可以使用 Chef 提供的命令行工具 knife 自动化。

配置管理

Chef 使管理员能够使用食谱和配方来定义和管理基础设施组件的配置。管理员可以指定每个资源的期望状态,而 Chef 确保节点的配置达到该状态。配置管理包括任务,如安装软件包、管理文件和目录、配置服务以及设置网络。

Chef 客户端-服务器交互

每个节点上的 Chef 客户端定期与 Chef 服务器进行聚合,确保节点的配置与 Chef 服务器中定义的期望状态相匹配。在聚合过程中,客户端从服务器获取更新的食谱、属性和配方。它将节点的当前状态与期望状态进行比较,并进行必要的更改以实现聚合。

报告和监控

Chef 提供报告和监控功能,用于跟踪基础设施和 Chef 客户端运行的状态。管理员可以查看关于食谱版本、节点状态和聚合详细信息的报告。这些信息有助于故障排除、审计以及确保符合配置政策:

图 14.1 – Chef 仪表板(来源:)

图 14.1 – Chef 仪表板(来源:docs.chef.io/manage/

如你所见,从一个仪表板监控和管理一切非常简单。

数据同步

Chef 通过使用数据包和加密数据包来实现服务器和节点之间的数据同步。数据包是存储由食谱使用的任意数据的 JSON 数据结构。它们允许在节点之间共享数据并提供特定配置的信息。加密数据包通过在将敏感数据存储到 Chef 服务器之前进行加密,提供额外的安全性。

使用 Chef 在 Linux 中自动化基础设施的好处

在 Linux 中使用 Chef 自动化基础设施显著提高了操作效率。它减少了手动配置的工作量,使管理员能够大规模部署和管理基础设施。Chef 的幂等操作确保仅在必要时应用配置,节省了时间并减少了错误。

一致性和可扩展性

Chef 确保分布式环境中的配置一致性。通过在食谱和配方中定义期望状态,管理员可以轻松地在节点之间复制配置,确保一致性并消除配置漂移。Chef 的可扩展性使得添加或移除节点变得简单,适应动态的基础设施需求。

降低人为错误

使用 Chef 自动化基础设施可以最小化人为错误,避免手动干预。Chef 的声明式方法确保按照预定义规则一致地应用配置。这减少了配置错误的风险,增强了系统的稳定性和可靠性。

增强的安全性

Chef 通过提供集中化的机制来管理配置策略并执行一致的安全实践,从而增强了安全性。它允许管理员定义和分发与安全相关的配置,如防火墙规则、用户权限和访问控制,跨节点进行管理。定期更新和补丁管理也可以自动化,确保及时应用安全修复。

挑战与最佳实践

实施 Chef 自动化可能会面临一些挑战,例如与 Chef DSL 相关的学习曲线、管理复杂的依赖关系以及处理基础设施漂移等。为了克服这些挑战,管理员可以投资培训和文档,采用版本控制实践,并有效利用 Chef 的功能,如环境和角色。

有效实施 Chef 的最佳实践

为了确保成功实施 Chef,遵循最佳实践非常重要。这些实践包括模块化食谱以便复用、使用版本控制进行食谱管理、利用测试框架进行食谱验证、实施分阶段发布策略以及保持清晰的文档。

总结

总结来说,在 Linux 中使用 Chef 自动化基础设施提供了一个强大的解决方案,用于管理和配置复杂的环境。Chef 的客户端-服务器架构、声明式方法和丰富的功能集使其成为配置管理的热门选择。通过采用 Chef,组织可以实现高效的基础设施部署、一致的配置、减少错误并增强安全性。尽管 Chef 也有其挑战和局限性,但它仍在不断发展,并且其充满活力的社区确保了持续的开发和支持。随着对可扩展和自动化基础设施需求的增长,Chef 仍然是 Linux 系统中一个宝贵的工具。

在下一章中,我们将讨论最佳实践和安全指南。

第十五章:安全指南和最佳实践

在现代社会,几乎所有计算机都连接到互联网,在线应用在我们生活的各个方面扮演着越来越重要的角色,信息安全也变得愈发重要。当以数字形式存储的信息变得越来越有价值,而恶意行为者不断设计新的攻击方式时,每个系统管理员都必须有意识地努力保持机器的安全。

幸运的是,遵循安全指南和最佳实践可以防止大多数攻击,并在攻击发生时限制其影响。

在本章中,我们将学习以下内容:

  • 信息安全组件和泄露类型

  • 常见的攻击类型和威胁

  • 攻击途径和安全漏洞

  • 保持系统安全和稳定的方法

常见的威胁和攻击类型

攻击者可能针对系统的原因有很多,攻击目标的方式也各不相同,而受损系统的操作员可能面临多种后果。我们将详细探讨这些内容。

攻击者的动机及其可能带来的后果

电影、文学和视频游戏中呈现的计算机系统攻击画面通常是针对一个精心挑选的目标进行攻击,具有特定的目标——最常见的目标是窃取一些有价值的信息、修改它,或者可能摧毁它。

这样的攻击在现实世界中确实存在,并且对高知名度公司和政府机构来说是一个巨大的隐患。然而,这种流行的描绘常常误导人们认为,安全对他们并不重要,因为他们没有任何有价值的信息,也不是高知名度的目标。

这种看法在互联网初期可能是正确的,但如今,这种假设已经变得非常危险。实际上,大多数攻击不再是针对特定目标并精心策划的,而是自动化的、机会主义的。每一台连接到互联网的机器都会不断被自动化工具扫描,试图利用已知的安全漏洞。

更糟糕的是,自动化攻击通常依赖于被攻陷的第三方机器的可用性——在攻击者自己的机器上运行自动化攻击工具既昂贵又容易被发现并加以遏制。为了避免支付托管费用并逃避检测和封锁,攻击者通常会获得对几台机器的未经授权的访问,并利用它们来促进进一步的攻击。一组被恶意实体控制的机器通常被称为僵尸网络。僵尸网络用于扫描更多机器并控制它们,分发恶意软件,发送未经请求的消息(垃圾邮件),并执行其他类型的攻击。

因此,一位粗心的系统管理员不仅可能成为攻击的受害者,还可能成为攻击者的无意和不知情的同谋。在某些情况下,受攻击系统的所有者可能会受到执法机关的调查,并因攻击源自其机器而被怀疑为攻击的实施者。这类情况虽罕见,但即使所有者不承担法律责任,允许攻击者控制你的机器仍然会带来许多潜在后果:电力成本(对于本地机器或联合托管的机器)、云平台上的 CPU 时间、带宽使用费用,以及系统过载,导致合法用户的资源被抢占。

最后,被确认作为攻击或垃圾邮件来源的机器可能会被加入黑名单。多个黑名单由不同的公司维护,因此,如果你的 IP 地址或域名被列入这些名单,清除它们可能是一个非常耗时的工作。此外,黑名单维护者并没有义务删除你的地址,因为它们是私营公司,黑名单的加入和移除并不受任何法律监管,且它们可能会拒绝删除重复违规者的条目,或者要求提供大量证明,证明当前所有者已经显著改善了安全措施。

信息安全特性及其攻击

信息安全的三个组成部分通常定义如下:

  • 可用性

  • 保密性

  • 完整性(或真实性)

信息可用性意味着授权用户可以在需要时访问信息。保密性意味着用户只能访问他们被授权访问的信息。最后,完整性意味着没有任何无意或故意的修改被合法用户执行或授权。

攻击可能有不同的目标——要么是破坏信息的可用性,要么是获得目标的控制权,从而破坏存储在其中的信息的机密性和真实性。在现代社会,许多攻击者更关心的是机器的资源,而非存储在其中的信息。我们来详细看看这些攻击。

拒绝服务

对信息可用性的攻击称为拒绝服务DoS)攻击。表面上看,这可能是最无害的攻击类型,因为其影响通常是暂时性的。然而,这种攻击仍然可能带来严重后果——例如,一个在线商店的网站无法访问可能导致收入的大幅损失,而对电话系统的攻击可能使用户无法拨打紧急电话,甚至可能导致生命丧失。一些 DoS 攻击仅仅是破坏行为,但许多此类攻击是为了从目标系统运营商勒索资金以换取停止攻击,损害其声誉,使服务变得不可靠,或阻止其向用户提供信息(最后一种目标对于政治动机的攻击尤为常见)。

执行 DoS 攻击有两种可能的方式。经典方式是利用系统软件中的漏洞来使其崩溃,或让其反复执行复杂操作,从而使其变慢。这些攻击可以通过适当的软件开发和配置来防止。

另一种、日益常见的攻击类型是分布式拒绝服务DDoS)攻击。这种攻击利用大量机器来饱和目标系统的网络链接,或用超出其处理能力的请求使其过载。攻击者可以通过大型僵尸网络生成攻击流量,或使用放大攻击——即他们可以向公共服务器发送 DNS 或 NTP 请求,并将攻击目标的地址指定为源地址,以使其向目标发送未经请求的回复数据包。由于回复通常比请求数据包大,放大攻击可以通过让好心的第三方参与攻击,从而为攻击者节省大量带宽和计算资源。

DDoS 攻击最糟糕的部分是,如果攻击者产生足够的流量来使目标的网络链接饱和,目标机器的管理员本身无法采取任何措施来缓解这种情况——如果攻击流量已经到达目标,损害已经发生。这种攻击只能通过托管服务提供商、互联网服务提供商或专门的 DDoS 保护服务来缓解,这些服务通过过滤恶意数据包并将合法请求转发到目标机器。然而,DDoS 攻击总是有明确目标的,而非机会主义性攻击,因为它们需要从多台机器向单一指定目标发送流量,并且大多数系统永远不会成为 DDoS 攻击的目标。

凭证盗窃和暴力破解攻击

完全控制目标机器是任何攻击者最吸引人的目标之一,因为它允许攻击者轻松破坏机器上存储的任何信息的完整性和机密性,并将机器本身用于自己的目的。

获取访问权限的最干净方法是冒充合法用户。如果攻击者以某种方式获取了密码、加密密钥或用于身份验证的 API 密钥,他们使用系统的行为将与正常访问无法区分。

即便是访问小型服务器的凭证,在现代社会也能变得非常有价值——如果攻击者能窃取足够多的凭证,获取访问权限后的常见攻击行为之一就是在受害机器上运行加密货币挖矿软件,从而直接将其 CPU 和 GPU 的计算能力转化为货币。窃取云平台、电子邮件服务或互联网语音协议VoIP)的访问凭证更具盈利性,因为攻击者可以利用这些凭证生成新的虚拟机、发送垃圾邮件或拨打国际电话——有些人甚至会将这些非法获取的资源出售给第三方,第三方对此毫不知情。当然,这些服务的费用最终要由合法的凭证拥有者支付。

许多恶意软件被编程用来窃取终端用户计算机上的密码和密钥。这种方法对攻击者来说是理想的,因为它不会在他们通过窃取的凭证访问的目标机器上留下任何痕迹。

然而,许多其他攻击利用了终端用户通常使用容易猜测的弱密码这一事实。这正是暴力破解攻击的基础,攻击者通过反复尝试使用包含常见单词、常用口令和经常被窃取的其他机器密码的密码字典登录,希望某些用户将它们用于多台机器或服务(这种情况很常见)。

通过使用强密码、加密密钥和设置速率限制,可以显著增加暴力破解攻击的难度,从而减少攻击者登录和猜测密码的机会。

利用配置和软件漏洞进行的攻击

最后,在某些情况下,攻击者可以通过利用系统本身的漏洞,执行那些逻辑上应该被拒绝的操作。

这种漏洞有两类:配置问题和软件漏洞。

以配置缺陷为例,考虑到电子邮件提交的标准协议——简单邮件传输协议SMTP)——不要求强制身份验证。因此,每个 SMTP 服务器的实现都可以配置为允许任何人通过它发送邮件,并充当开放中继。如果一个配置了这种设置的服务器暴露在公共互联网中,攻击者可以利用它发送大量垃圾邮件,仅仅因为它没有检查发件人是否是系统的合法用户。

在其他情况下,缺陷存在于软件本身。例如,假设一个 Web 应用程序实现了用户身份验证,并且在用户登录后正确地将他们重定向到其账户页面——假设用户名为bob的用户从https://example.com/login被重定向到https://example.com/users/bob。然而,由于编程错误,应用程序在用户尝试访问账户页面时从未检查用户账户,因此任何知道系统中有一个名为bob的用户的人,只需在地址栏输入https://example.com/users/bob,就可以访问该账户页面。

这个例子听起来可能很严重,但它与在真实软件中发现的漏洞并不太远,即使它们的利用方法可能比在地址栏中输入 URL 更复杂。

幸运的是,大多数漏洞并没有那么危险。当安全研究人员发布他们的发现,以及软件维护人员发布已发现漏洞的修复程序时,他们会使用一组术语来表示漏洞类型和严重性等级,你应该熟悉这些术语,以估计修复的重要性。

漏洞报告由个人研究人员和软件供应商发布,并且也会汇总到像美国国家标准与技术研究院NIST)维护的国家漏洞数据库中。在这些数据库中,每个已知的漏洞都会分配一个唯一的标识符,例如 CVE-2023-28531(nvd.nist.gov/vuln/detail/CVE-2023-28531),其中CVE代表公共漏洞和暴露

常见的严重性等级如下:

  • 严重:通常,这使得任何能够连接到系统的攻击者都可以完全控制它。易受攻击的系统应该立即修补,或者如果补丁尚不可用,则应隔离,使其无法被攻击者访问。

  • 高和中等:这些可能允许攻击者显著危害系统,但需要特殊的情况(例如,系统中启用了某些特性)或复杂的利用过程。如果补丁不可用,受影响的系统应该尽快修补,且可能需要采取临时缓解措施(例如禁用受影响的特性)。

  • :这不会给攻击者带来显著的优势,因为只有在罕见的情况下才能进行利用。

数据库根据多个因素(例如风险等级、攻击者利用漏洞所需的技能、影响等)来分配这些等级。有关详细信息,你可能想要阅读 NIST 使用的常见漏洞评分系统或 OWASP 风险评级方法。

以下是一些常见的漏洞类型:

  • 任意代码执行

  • 提权

  • 拒绝服务

任意代码执行攻击往往是最危险的,因为它们允许攻击者向目标系统引入新的程序逻辑,而不仅仅是使用或滥用系统上已运行的软件。然而,也有许多缓解因素。一个允许远程未认证攻击者通过向目标系统发送特制请求来执行任意代码的漏洞是所有威胁中最严重的,可能需要将受影响的系统完全下线,直到修补漏洞。然而,如果攻击者需要先认证才能执行攻击,那么它的影响会大大减少——要到达任意代码执行阶段,攻击者首先需要窃取有效的用户凭证。此外,从一个具有高度限制权限且无法访问任何敏感信息的进程中执行任意代码,可能对攻击者的帮助有限。

权限提升攻击允许合法用户执行本不应由他们执行的操作。其影响可能非常严重——例如,如果一个漏洞允许任何已认证用户读取系统上的所有文件,不论文件权限如何,那么任何允许非管理员用户登录的机器都有隐私泄露的风险。然而,在外部攻击者利用这个漏洞之前,他们首先需要找到一种登录的方法。对于仅允许管理员访问的系统而言,除非同时存在远程任意代码执行漏洞或凭证盗窃漏洞,否则这种漏洞根本不需要担心。

最后,拒绝服务漏洞仅仅允许攻击者妥协系统的可用性,就像我们之前讨论的那样。

牢记这一点,接下来我们将讨论如何保护您的系统免受这些类型的攻击。

保持系统安全

在信息安全圈中有一个常见的笑话,那就是唯一完全安全的系统就是关机的系统。当然,这样的系统仅在完整性和机密性的意义上是安全的——以可用性为代价。任何现实场景总是一个妥协,总是存在风险;系统管理员的目标是防止已知攻击,并减少未知攻击的影响,每个管理员必须始终准备好应对新的威胁并加以缓解。

幸运的是,遵循一些简单的指导原则可以大大降低风险——接下来我们将讨论防止特定攻击类型的一般策略和战术。

减少攻击面

系统的攻击面大致可以理解为访问系统的所有方式。例如,一个同时运行着 web 服务器和邮件服务器的机器,其攻击面比只运行其中之一的系统要大。如果我们假设这些服务中的漏洞和配置问题发生的概率相同且可以独立出现,那么运行这两项服务的系统就有两倍的可能性受到攻击。

当然,许多真实的系统需要同时提供多项服务——例如,电子邮件提供商需要邮件服务器和面向客户的网站。减少攻击面并不是减少服务的数量,而是将服务彼此隔离,并且理想情况下,要将它们与攻击者隔离开来。

例如,一个典型的 Web 应用堆栈包括应用服务器和数据库服务器。大多数情况下,数据库服务器不需要对外公开访问——将其访问限制在内部网络中总是一个好主意。此外,应用服务器也不需要直接对外公开——正如我们在第十三章中讨论的内容,高可用性,它们可以被放置在负载均衡器后面。负载均衡器不仅能提高系统的可用性,还能减少攻击者通过公共互联网能够接触到的攻击面。此外,它还可以提供速率限制和威胁检测系统,至少能在一定程度上保护应用服务器免受部分攻击流量的影响。

隔离与权限分离

很久以前,系统中不同组件之间唯一的隔离方法是将它们运行在不同的物理机器上。这种方法成本很高,通常只有在有充分理由的情况下才会采用——例如,设计用来处理高负载的应用程序必须使用独立的数据库和应用服务器,以满足其性能要求,而不仅仅是为了减少系统的攻击面。

然而,在过去二十年里,出现了许多更细粒度的方法来隔离进程,即使是在普通硬件上。虚拟化技术使你可以在单一物理机器上运行多个操作系统实例,现代虚拟机管理器使得启动虚拟机变得简单——更不用说云平台,它们可以通过单一点击或 API 调用来启动你选择的操作系统的虚拟机。

除了完全虚拟化外,还有许多方法可以在单台机器上隔离应用程序。包括chroot环境、容器和强制访问控制系统。

使用 chroot

完全隔离进程的最古老方法是chroot。从技术上讲,chroot(更改根目录)是内核中的一个系统调用,用于更改进程的根目录。根目录已更改的进程将无法访问根目录以外的任何文件——对该进程而言,系统中似乎只有它所在的目录。手动为进程设置chroot环境可能是一个耗时的任务,因此许多发行版提供了特殊的包来简化该过程。

例如,Fedora 提供了一种简单的方法来在chroot环境中运行 ISC BIND(也称为named),这是一款流行的 DNS 服务器:

$ sudo dnf install bind9-next-chroot
Installed:
  bind9-next-32:9.19.11-1.fc37.x86_64     bind9-next-chroot-32:9.19.11-1.fc37.x86_64     bind9-next-dnssec-utils-32:9.19.11-1.fc37.x86_64     bind9-next-libs-32:9.19.11-1.fc37.x86_64     bind9-next-license-32:9.19.11-1.fc37.noarch
Complete!
$ sudo systemctl enable  named-chroot
Created symlink /etc/systemd/system/multi-user.target.wants/named-chroot.service → /usr/lib/systemd/system/named-chroot.service.
$ tree /var/named/chroot/
/var/named/chroot/
├── dev
├── etc
│   ├── crypto-policies
│   │   └── back-ends
│   ├── named
│   └── pki
│     └── dnssec-keys
├── proc
│   └── sys
│       └── net
│           └── ipv4
...

正如你所见,作为 BIND 进程的限制根目录的目录与真实根目录类似,它也有 /dev/proc 层级 —— 这些目录的全局版本将无法被进程访问。

因此,即使攻击者利用远程代码执行漏洞成功将恶意代码注入到 BIND 进程中,他们也无法读取 /var/named/chroot 以外的任何文件。然而,他们仍然可以与系统上的其他进程进行交互。如果需要更深层次的隔离,可以改用容器。

使用容器

Linux 内核提供了一种名为 LXC 的容器技术。它由多个子组件组成,例如进程组、控制组和网络命名空间。手动创建容器环境并启动它们是一个繁琐的过程,因此人们创建了可以自动化该过程的工具,并且有了可供使用的容器镜像注册表。

此时,Linux 上最流行的容器管理工具是 Docker,尽管也有替代工具,例如 Podman。我们将通过使用 Docker 启动 Fedora 镜像来演示进程隔离:

$ sudo dnf install docker
$ sudo docket pull fedora:latest
$ docker run -it fedora:latest bash
[root@df231cc10b87 /]# dnf install procps-ng
[root@df231cc10b87 /]# ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0   4720  3712 pts/0    Ss   15:21   0:00 /bin/bash
root          55  0.0  0.0   5856  2688 pts/0    R+   15:23   0:00 ps aux

如你所见,当我们在容器中启动一个 bash shell 进程时,从容器内部看,它就像是系统中的唯一进程。宿主系统中的进程(例如 systemd 或 Docker 守护进程)对容器来说是不可见的,容器中的进程也无法与它们进行任何交互。

使用强制访问控制

最后,存在授予用户和进程不同能力的方式 —— 理想情况下,只有它们需要的能力才能正常运行。这种机制被称为 强制访问控制 (MAC) 系统 —— 与经典的 自主访问控制(即 Unix 文件权限系统)不同。

当前,Linux 最流行的 MAC 系统是 安全增强 LinuxSELinux),尽管也有一些不太常见的替代方案 —— 最著名的就是 AppArmor 内核模块。

如今,许多 Linux 发行版默认启用 SELinux,并为流行服务包含了相应的能力和对象上下文。

你可以使用 getenforce 命令检查系统是否启用了 SELinux:

$ getenforce
Enforcing

Enforcing 模式下,SELinux 会禁止用户或进程未被授权的操作 —— 例如,若进程没有 can_network_connect 能力,它将无法发起任何网络连接。而在 Permissive 模式下,SELinux 会在系统日志中生成警报,但不会强制执行能力 —— 该模式适合用于测试。最后,如果模式为 Disabled,则 SELinux 完全不执行任何能力检查。

配置 SELinux 策略是一本入门书籍无法涵盖的主题。然而,如果你决定启用 SELinux,你将经常需要知道如何授予进程对文件的访问权限。

例如,在 Apache HTTPd 配置文件中,可以将任何目录指定为网站根目录。如果攻击者控制了 Apache 进程,这也可能带来安全问题。这就是为什么在 Fedora 中,该软件包的维护者为必须让 web 服务器访问的文件引入了一组特殊的 SELinux 上下文。

你可以通过在 ls 命令中添加 -Z 选项来查看文件的 SELinux 上下文。例如,/var/lib/httpd 目录具有 httpd_var_lib_t 上下文,授予读取权限:

$ sudo ls -alZ /var/lib/httpd/
total 8
drwx------.  2 apache apache system_u:object_r:httpd_var_lib_t:s0 4096 Jun 17  2022 .
drwxr-xr-x. 55 root   root   system_u:object_r:var_lib_t:s0       4096 Nov 16 02:39 ..

可能是可写的网页目录使用不同的上下文 —— httpd_sys_content_t

$ ls -alZ /var/www/html/
total 8
drwxr-xr-x. 2 root root system_u:object_r:httpd_sys_content_t:s0 4096 Jun 17  2022 .
drwxr-xr-x. 6 root root system_u:object_r:httpd_sys_content_t:s0 4096 Jun 17  2022 ..

如果你将网站根目录设置为一个新创建的其他目录,Apache 进程将无法访问该目录,因为它缺少所需的上下文,即使该目录根据 Unix 权限应该是可读的。你可以使用chcon命令授予它访问权限:

$ sudo chcon system_u:object_r:httpd_sys_content_t:s0 /home/webmaster/public_html

使用 SELinux 创建灵活且细粒度的安全策略还有很多其他可能性,但初学者应当先从使用发行版默认配置开始。

防止凭证盗窃和暴力破解攻击

凭证盗窃必须在用户工作站上处理。没有绝对的方法可以防止凭证盗窃,因此你应该努力保护你的笔记本或台式系统免受攻击 —— 保持系统更新,保护免受恶意软件侵害,避免陷入如钓鱼邮件等社会工程攻击,其中包含恶意链接。

对于暴力破解攻击,有两种互补的策略 —— 保持密码难以猜测和限制认证尝试次数。

大多数 Linux 发行版默认启用了一个名为 pam_pwquality.so 的 PAM 模块,它防止非特权用户使用不安全的密码。你可以通过尝试设置一个非常短的密码或简单的字典词来验证这一点:

$ passwd
Changing password for user ...
Current password:
New password: qwerty
BAD PASSWORD: The password is shorter than 8 characters
passwd: Authentication token manipulation error
$ passwd
Changing password for user ...
Current password:
New password: swordfish
BAD PASSWORD: The password fails the dictionary check - it is based on a dictionary word
passwd: Authentication token manipulation error

它用于密码强度检查的后端叫做 cracklib。它通常将数据保存在 /usr/share/cracklib 中。其字典是二进制格式,但它提供了用于操作这些字典文件和检查密码强度的工具,无需实际设置密码:

$ echo "swordfish" | cracklib-check
swordfish: it is based on a dictionary word

请注意,root 用户不受密码质量检查的限制,可以为任何用户设置任何密码。

还有一个名为 pwgen 的工具可以生成安全的随机密码,这个工具存在于大多数 Linux 发行版的软件包仓库中。它允许你指定密码是否应完全随机,密码应包含哪些字符,密码的长度,及一次生成多少个密码:

$ pwgen --secure 40 1
DMYRsJNuXJQb98scCUASDzt3GFPa7yzGg9eS1L1U
$ pwgen --secure 8 16
Ob4r24Sp KwGCF63L 3bgKI79L aWK2K7aK MFf1ykum y74VTKqb OxbrNlI0 8Dl4yilz
bmI1RsKr oM4p8dsJ 2EXmmHIJ bt9Go1gg 3znmVqpo vZ9BDIlz QMZ7eME0 izkB15Xe

限制速率是一个广泛的话题,且其设置在不同的服务和应用程序之间有所不同。然而,也有一些通用的集成解决方案支持多个服务,例如fail2ban

降低软件漏洞的风险

确保系统没有脆弱软件的最佳方法是使用来自发行版仓库的软件,并按时安装更新。应该避免手动安装软件包,因为这些软件包不会自动接收发行版维护者的更新。如果确实需要,您应该确保订阅它们的发布公告,并自行检查更新。

很多时候,漏洞是在已经发布的软件版本中发现的,而不是在开发过程中 – 这种情况被称为零日漏洞(通常简称为0day)。有时,这些漏洞在被发现之前已经存在了几个月甚至几年。修复这些漏洞的更新包可能会晚于恶意行为者开始利用它们的时间。在这种情况下,软件维护者通常会建议一种临时的缓解策略,可能包括更改配置选项、禁用特定功能或对系统进行其他更改,以使攻击更难执行。因此,关注您常用的发行版和软件项目的博客、邮件列表也是一个好主意。

总结

在本章中,我们了解了计算机系统受到的各种攻击类型、攻击者执行攻击的动机,以及这些攻击可能给用户带来的后果。

我们还了解了保持系统免受攻击的常见策略。然而,我们仅仅触及了表面——信息安全是一个庞大的领域,保持对它的知识更新并确保系统安全是每个系统管理员终身的追求。

深入阅读

要了解本章中涉及的更多内容,请查阅以下资源:

posted @ 2025-07-04 15:40  绝不原创的飞龙  阅读(10)  评论(0)    收藏  举报