Linux-实战-全-
Linux 实战(全)
原文:Linux in action
译者:飞龙
第六章. 紧急工具:构建系统恢复设备
本章涵盖
-
恢复损坏的 Linux 系统
-
使用 Linux live-boot 驱动控制资源
-
从损坏的存储介质恢复数据
-
操作无法访问的文件系统
不要试图说服自己否则:除了所有的好东西,你还会在 Linux 中遇到坏日子。有时你会忘记命令的语法(这就是为什么你应该总是把这本书放在手边)。你(或你支持的用户)会误打命令并永久性地破坏文档。或者,当你意识到某些重要的硬件或软件已经失败时,你会感到一种沉沦的感觉。(那是在你为它做了所有那些年后的感激。)正如最后几章所展示的,正确备份意味着你可以离开一个无法工作的操作系统(OS)或计算机,并在其他地方重建它。但那始终将是 B 计划。A 计划是恢复。
本章我将向你介绍 Linux 恢复套件中的关键工具。你将了解如何使用 live-boot 设备加载 Linux 的新副本,挂载给你带来麻烦的驱动器,或者更新损坏的配置文件以便你可以正常启动,或者在你重新使用或销毁损坏的驱动器之前恢复任何可以恢复的数据。你还将看到如何使非功能系统的文件“复活”并在它们自己的虚拟环境中运行,这样你就可以做一些事情,比如更改用户忘记的密码。
当你试图将硬件和软件放入一个盒子中并期望它们和睦相处时,可能会出很多问题。我将专注于这些灾难性事件:
-
你的计算机启动了,硬盘工作正常,但 Linux 没有加载。
-
你的计算机启动了(至少就你所知是这样),但你并不确定硬盘是否完全正常工作。
-
一切都正常工作,但软件问题或丢失的密码阻止你登录到 Linux。
你面临的具体问题将决定你采取的行动计划以恢复业务。展示了某些诊断和恢复选项,其中大部分我将在本章后面讨论。
图 6.1. 本章我们将探讨的常见系统问题以及诊断和解决方案考虑因素

6.1. 在恢复/救援模式下工作
Linux 不让你正常登录?也许启动过程在显示登录屏幕之前意外停止。你可能需要一些基本的系统管理工具。
但等等:如果 Linux 无法加载,你将如何启动这些工具?好吧,即使 Linux 无法完全加载到正常命令提示符,它通常也会带你到 GRUB 菜单。从那里(如图 6.2 所示),你可以使用上箭头键和下箭头键,然后按 Enter 键来选择在恢复模式下运行的 Linux 内核。正如你很快就会看到的,这将打开一整套技巧。
图 6.2. Ubuntu 安装的 GRUB 高级选项菜单,显示指向当前和较旧内核版本的链接,以及启动恢复模式的选项

在你能够充分利用这些工具之前,然而,你首先需要了解 GRUB 是什么以及它是如何工作的。在接下来的几个部分中,我将解释 GRUB 的功能,它是如何被用来访问恢复工具的,以及你如何使用这些工具来摆脱一些棘手的困境。
6.1.1. GRUB 引导加载程序
什么是GRUB?它是 GNU GRand Unified Bootloader。好吧,什么是引导加载程序?它是操作系统在开机时用来唤醒自己的代码。图 6.3 说明了这个过程。
图 6.3. Linux 计算机启动过程中的关键步骤

当计算机开机时,嵌入在基本系统硬件中的固件指令会识别可用的网络、存储和内存资源。这通过较老计算机上的 BIOS 系统完成,以及最近使用 UEFI(两者你都在第四章中简要了解过)。
一旦系统找到一个包含主引导记录(MBR)的硬盘分区,它就会将内容加载到活动内存中。在 Linux 系统中,MBR 分区包含一些文件,当运行时,会提供一到多个可加载的内核映像启动配置。你可以从 GRUB 引导加载程序菜单中选择加载这些配置中的任何一个。
注意
通常,GRUB 会自动加载其默认映像,而不会询问你的意见,除非之前的会话加载失败。如果你想强制 GRUB 菜单出现,请在计算机启动时按下右 Shift 键。
6.1.2. 在 Ubuntu 上使用恢复模式
如图 6.4 所示,一旦 Ubuntu 在恢复模式下加载,你会看到一个工具菜单,这些工具可以解决一些常见的启动时问题。逐一尝试它们是值得的,以防其中之一解决了你的根本问题。例如,清洁选项会在你怀疑问题源于磁盘已满时删除未使用的文件。而 dpkg 选项则尝试修复任何可能使事情变得混乱的基于 APT 的软件包(dpkg 工具可能需要你首先启用网络)。
图 6.4. Ubuntu 恢复菜单,包含指向一些基本诊断和修复工具的链接,以及以 root 用户打开 shell 会话的选项

根选项为你打开一个根命令行 shell 会话,在那里你可以使用 Bash。一般来说,使用简单的 shell 会话进行恢复而不是完整的 GUI 桌面是有很多道理的。这是因为你运行的服务越少,你至少能够启动系统的可能性就越大。
一旦你成功获得一个可用的命令提示符,你就可以开始探索以确定是否可以识别和修复问题。至少,这样做会让你看起来非常酷。
6.1.3. 在 CentOS 上使用救援模式
CentOS 的 GRUB 菜单在启动时提供救援内核而不是恢复模式。这个内核不包含像 Ubuntu 那样的工具菜单,但它将以类似的方式将你放入一个作为 root 的单用户 shell 会话。图 6.5 显示了 CentOS GRUB 中的救援启动选项。
图 6.5. CentOS Linux 提供了一个救援内核,可以直接启动到单用户 shell 会话,用于排除系统损坏的故障。

一旦你在 CentOS 机器上选择了救援选项,接下来是什么?你将通过本章的其余部分遇到一些有用的工具。但首先,为什么不从 Ubuntu 操场手册中取一页,手动应用自动化恢复菜单中的某些工具?“说起来容易做起来难,”我听到你说。“但那些工具是如何工作的?”在接下来的几页中,我将向你展示如何运用你的 Bash 和脚本技能来找出一些答案。
6.1.4. 查找命令行救援工具
拥有正在运行的 Ubuntu 机器(这个脚本在 Debian 上无法工作)?如果代码正在运行菜单,它必须已经存在于 Ubuntu 文件系统的某个地方。自己去看看吧。使用 locate 来查找它:
$ locate recovery-mode
/lib/recovery-mode
/lib/recovery-mode/l10n.sh *1*
/lib/recovery-mode/options
/lib/recovery-mode/recovery-menu *2*
/lib/recovery-mode/options/apt-snapshots
/lib/recovery-mode/options/clean *3*
/lib/recovery-mode/options/dpkg
/lib/recovery-mode/options/failsafeX
/lib/recovery-mode/options/fsck
/lib/recovery-mode/options/grub
/lib/recovery-mode/options/network
/lib/recovery-mode/options/root
/lib/recovery-mode/options/system-summary
-
1 110n.sh 脚本为菜单设置适当的环境变量。
-
2 恢复菜单脚本文件
-
3 减少磁盘使用量的脚本
如果你导航到 /lib/recovery-mode/ 目录,你会看到 recovery-menu 文件是显示你在图 6.3 中看到的菜单界面的脚本。options/ 目录包含执行每个菜单项的文件。例如,fsck 将检查并修复(如果可能的话)任何损坏的文件系统。
因为现在你是一位熟练的 Bash 脚本专家,为什么不查看 options/ 目录中的每个脚本,看看你是否能弄清楚它们是如何工作的?以下是为你提供的 fsck 脚本内容。注意脚本是如何很好地使用 # 字符进行文档化,以帮助你理解正在发生的事情:
$ cat /lib/recovery-mode/options/fsck
#!/bin/sh
. /lib/recovery-mode/l10n.sh *1*
if [ "$1" = "test" ]; then
echo $(eval_gettext "Check all file systems")
exit 0
fi
# Actual code is in recovery-menu itself *2*
exit 0
-
1 调用 110n.sh 脚本以设置环境。
-
2 这条注释告诉你在哪里发生真正的操作。
这里有一些您可以自己尝试的事情。在 Ubuntu 机器上手动运行 clean 脚本。发生了什么?然后尝试仔细编辑/lib/recovery-mode/recovery-menu 脚本(首先创建备份副本)。更改一些简单的内容,比如菜单标题或脚本描述之一。然后重启您的机器,从 GRUB 菜单进入恢复模式,看看恢复环境的样子。通过一些变化和例外,您应该能够在其他地方很好地使用这些脚本,包括在 CentOS 上。
6.2. 构建实时启动恢复驱动器
如您可能已经知道,您在第二章中为您的 VirtualBox 虚拟机使用的那些.ISO 操作系统镜像也可以写入 CD 或 USB 驱动器,并用于启动 OS 的实时会话。这种实时启动设备允许您加载完全功能的 Linux 会话,而无需将任何内容安装到硬盘上。许多人使用这样的驱动器来确认特定的 Linux 发行版是否能在他们的硬件上顺利运行,然后再尝试安装。其他人则会运行实时会话,作为一种在参与敏感活动(如在线银行)时维护隐私的安全方式。
结果表明,这些实时启动驱动器也是系统救援和恢复的绝佳工具。还记得本章前面提到的第二个灾难场景吗?
您的计算机启动了(至少您是这样认为的),但您并不完全确定硬盘是否完全正常工作。
将实时启动驱动器插入故障计算机,并使用所有管理工具启动 Linux,可以帮助您弄清楚真正发生的事情——并为您提供解决问题的工具。我将向您展示如何创建实时启动 USB 驱动器,然后如何使用它。但首先,让我们快速看一下目前可用的最有用的发行版镜像。
6.2.1. 系统救援镜像
如果您碰巧已经有一个包含完整 Linux 系统(如 Ubuntu)的 DVD 或 USB 驱动器,那么这将是您最简单的解决方案,因为您需要的软件中的大部分都将预先安装。假设您有一个网络连接,您可以在实时会话期间安装其他包。否则,以下几节将描述一些特殊镜像。
Boot-Repair
如果您正在尝试救援的是 Ubuntu 系统,那么您将想要尝试 Boot-Repair。一个小巧快速的 Boot-Repair 实时启动会检查您的 GRUB 设置,并在必要时重新构建它们。如图 6.6 所示,它还可以执行其他有用的管理任务。这个工具在我需要的时候救了我很多次。
图 6.6. Boot-Repair 桌面,系统工具菜单可见

Boot-Repair 也可以安装在已经启动的实时会话上。网站(help.ubuntu.com/community/Boot-Repair)提供了很好的说明。
GParted Live
如果你的问题与可能损坏你的数据或阻止你成功启动的损坏分区有关,GParted Live 镜像将全功能的强大 GParted 分区编辑器带到实时 CD 或 USB 上。像 Boot-Repair 一样,GParted 也可以从任何正常的 Linux 会话中安装和使用。如果你知道你的问题是与分区相关的,那么完整的实时启动版本可能是最快和最直接解决问题的方法。与 Boot-Repair 不同,GParted 是为了在几乎所有 Linux 发行版上使用而构建的,而不仅仅是 Ubuntu。
SystemRescueCd
另一个替代方案,SystemRescueCd,是基于 Gentoo Linux 发行版的轻量级镜像。由于它包含大量有用的系统恢复工具,SystemRescueCd 成为随身携带的出色 USB 驱动器。(CD 也很棒,尽管它不太容易放入后口袋,而且安全地坐着可能是一个挑战。)
6.2.2. 将实时启动镜像写入 USB 驱动器
在你可以写入任何.ISO 镜像之前,你需要下载它。我认为我可以安全地假设你能够找到你想要的发行版的网站上的正确页面来完成这项工作。但你可能不知道,大型下载有时在跨越互联网的过程中可能会损坏。更糟糕的是,它们可能会被入侵者或中间人攻击者用恶意软件替换。这发生在一些人在一段时间前下载 Mint Linux 时。1
¹
参考凯利·菲瓦什的文章,“Linux Mint 网站和论坛在遭受黑客攻击后感染恶意软件,” Ars Technica,2016 年 2 月 22 日,
mng.bz/irPi。
除了检查你所在的网站是否已正确加密(你的浏览器应该在 URL 栏中显示某种锁形图标),你的最佳防御措施是生成下载文件的哈希值,并将其与网站提供的哈希值进行比较。(哈希值在第二章中已有讨论。)
有时哈希值会直接显示在镜像下载页面上,但某些发行版使它们变得有点难以找到。Canonical(为我们提供 Ubuntu 的杰出人士)在这方面并没有完全赢得荣誉。有一个单独的页面(help.ubuntu.com/community/UbuntuHashes),其中包含所有哈希值的链接。从下载站点找到该页面并不简单。通常,使用可靠的互联网搜索引擎会更好。图 6.7 显示了 Ubuntu 17.04 的哈希值将看起来是什么样子。
图 6.7. 可下载的 Ubuntu 17.04 各种镜像的 SHA256 哈希值

一旦找到你镜像的已发布哈希值,你将计算下载文件的哈希值,并将两个值进行比较。以下示例从下载到同一目录的 SystemRescueCd 镜像生成 SHA256 哈希值:
$ cd Downloads
$ ls | grep systemrescue
systemrescuecd-x86-5.0.2.iso
$ sha256sum systemrescuecd-x86-5.0.2.iso
a2abdaf5750b09886cedcc5233d91ad3d1083e10380e555c7ca508
49befbf487 systemrescuecd-x86-5.0.2.iso
在安全下载了某种可靠的镜像之后,是时候创建恢复工具的 live-boot 驱动器了。如果您当前计算机和您正在创建的 live-boot USB 都将运行 Debian、Ubuntu 或其衍生版本,最简单的方法是使用 Ubuntu 启动磁盘创建器。
创建者工具可以从常规 GUI 菜单中访问,正如您从图 6.8 中可以看到,它是直接的。从您的硬盘上的某个位置选择一个.ISO 文件,以及您希望写入图像的目标 USB(或 CD)驱动器。创建者将处理其余部分。
图 6.8. Ubuntu 启动磁盘创建器,已选择 Ubuntu Server 16.04 图像和目标 USB 驱动器

另一方面,如果您需要从不同的发行版构建 live-boot 设备(您正在尝试恢复 CentOS 机器,并希望使用 CentOS 工具来完成它),那么您需要重新启用您的好友dd。如果您需要一个基于 CD 或 DVD 的 live 可引导设备,任何 Linux 主机上的dd都可以完成这项工作。但如果图像将被写入 USB 驱动器,并且您正在 Ubuntu 主机上工作,您首先需要通过向.ISO 存档中添加 MBR 来修改图像,这样 BIOS 和 UEFI 固件就会知道如何处理它。图 6.9 展示了这个过程的外观。
图 6.9. 编写可工作的 live-boot USB 镜像所需的步骤

在 Ubuntu 主机上,您使用isohybrid进行图像修改。包含isohybrid的 apt 包被称为 syslinux-utils。一旦安装,转到包含下载的图像的目录,并使用图像文件名作为唯一参数运行isohybrid:
# apt update
# apt install syslinux-utils
$ cd ~/Downloads
$ isohybrid systemrescuecd-x86-5.0.2.iso
下一步应该适用于您使用的任何发行版。仔细识别目标设备的系统标识。如您所记得,df列出了所有当前识别的文件系统及其标识:
$ df -h
Filesystem Size Used Avail Use% Mounted on
udev 3.5G 0 3.5G 0% /dev
tmpfs 724M 1.5M 722M 1% /run
/dev/sda2 910G 183G 681G 22% / *1*
tmpfs 3.6G 214M 3.4G 6% /dev/shm
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 3.6G 0 3.6G 0% /sys/fs/cgroup
/dev/sda1 511M 3.4M 508M 1% /boot/efi
tmpfs 724M 92K 724M 1% /run/user/1000
/dev/sdb1 15G 16K 15G 1% /media/myname/KINGSTON *2*
-
1 这是我的根文件系统。我绝对不希望覆盖它!
-
2 我的可移动 USB 驱动器上的文件系统
在这个例子中,我的金士顿 USB 设备上挂载了一个名为/dev/sdb1 的文件系统。这告诉我该设备本身被称为/dev/sdb。
如果您计划将图像写入 CD 或 DVD 等光驱,那么您可以通过lsblk获取其标识,它代表列出块设备。驱动器本身必须是可写的。在这里,我的 DVD 驱动器被称为 sr0:
$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 931.5G 0 disk
sda1 8:1 0 512M 0 part /boot/efi
sda2 8:2 0 923.8G 0 part /
sda3 8:3 0 7.2G 0 part [SWAP]
sdb 8:16 1 14.4G 0 disk
sdb1 8:17 1 14.4G 0 part /media/clinton/KINGSTON
sr0 11:0 1 1024M 0 rom *1*
- 1 可写的 DVD 驱动器
真正的时刻。不再推迟。是时候将dd释放到您可怜的、毫无防备的文件系统上,并将您的图像写入 USB 驱动器了。
首先,卸载驱动器本身,以便dd可以完全访问。然后写入存档。在这个例子中,我使用了 systemrescuecd-x86-5.0.2.iso 图像,并将其写入到/dev/sdb 的驱动器上。
小心!在特定情况下,将sda而不是sdb(在这种情况下)输入将不可恢复地覆盖您的宿主文件系统,并毁掉您的一天,更不用说您的生命了。当然,您还应该确保 USB 驱动器上没有重要内容,因为那肯定会消失:
# umount /dev/sdb
# dd bs=4M if=systemrescuecd-x86-5.0.2.iso \
of=/dev/sdb && sync *1*
- 1 添加的同步命令确保所有缓存的数据立即写入目标磁盘。
dd将镜像写入 USB 设备可能需要一些时间,但完成时,您应该能够将驱动器插入计算机,打开电源,并进入 live 会话。这是假设您的计算机已配置为从 USB 驱动器启动。如果不是,您可以在启动过程中进入 Boot 菜单,强制将计算机引导到选定的设备这一次。每个 PC 制造商都为其指定了自己的键(通常在启动过程中显示按键选项列表)。但通常在启动序列的早期按下 F1、F9 或 F12 中的一个键会起作用。
您也可以进入设置实用程序(无论是 BIOS 还是 UEFI)以永久设置用于启动设备的顺序。访问设置实用程序也可能通过一系列键来完成:我见过 F2、F10、Del 和 Enter。
6.3. 使用您的 live-boot 驱动器
您可以使用您创建的口袋 Linux 驱动器做很多事情。以下几节描述了一些常见场景及其解决方法。
6.3.1. 测试系统内存
如果您经历过突然和意外的系统崩溃,一个可能的原因是您的物理内存(RAM)。像所有硬件一样,RAM 最终会失效。问题是,当操作系统正在使用时,您无法正确测试 RAM 的错误。相反,您必须在操作系统加载之前捕捉到它。幸运的是,作为 Linux live-boot 驱动器的自豪拥有者,您已经拥有了完成这项工作所需的一切。如图 6.10 所示,在启动到 Ubuntu 驱动器后,将显示的一个菜单项是测试内存。
图 6.10. Ubuntu 安装过程中的主菜单显示测试内存

选择测试内存(使用上箭头和下箭头键并按 Enter 键)将您带到 Memtest86+程序(如图 6.11 所示)。该程序对您的 RAM 进行多次扫描,显示它发现的任何错误。老实说,我不确定这个工具是否会自己停止;我肯定从未达到那个点。但如果它运行了至少几轮完整的扫描后没有返回错误,那么您的内存可能不是麻烦的原因。这可能是好消息也可能是坏消息……毕竟,您仍然不知道是什么导致了您的崩溃。
注意
除了测试内存外,Ubuntu 安装过程中的主菜单中的“修复损坏的系统”选项将为您提供工作的 Bash shell。
图 6.11. Memtest86+工具显示了 RAM 内存中任何错误的位置和类型。这个系统是干净的(到目前为止)。

CentOS 安装盘在故障排除菜单中包含了对 Memtest86+的链接(如图 6.12 所示)。
图 6.12。从 CentOS 安装盘主菜单链接的故障排除菜单

6.3.2。损坏的分区
一个分区实际上是指向物理磁盘上文件系统位置的元数据。这些分区很脆弱,它们似乎很容易被破坏。如果磁盘数据以某种方式损坏,并且分区的起始和结束地址被更改或丢失,那么分区上的文件系统将变得无法访问。如果一个文件系统无法访问,那么该文件系统上的数据就相当于丢失了。
无法访问你的分区?是时候启动你的新 SystemRescue 驱动了。SystemRescue 是一个轻量级的包,所以不要期待一个功能齐全的发行版的所有 GUI 奇迹。像笔记本电脑触摸板和自动 WiFi 连接这样的功能可能不会按你期望的方式工作。但这是快速获取一些强大救援工具的方法。如图 6.13 所示,默认的启动选项将打开一个特殊的命令行 shell。
图 6.13。SystemRescue 启动期间显示的启动选项菜单。注意页面底部的每个选项的详细说明。

当你准备恢复你的受损分区(或其数据)时,SystemRescue 提供了有关命令行(包括网络和文本编辑基础)的一些有用的方向信息(如图 6.14 所示)。不用担心:Vim 和 Nano 都可以从命令行访问。输入startx将加载一个快速轻量的 GUI 桌面。
图 6.14。SystemRescue shell。注意默认可用的文本编辑器(包括 Nano),以及输入startx将启动图形桌面。

如果你需要网络访问来下载或安装更多工具,或许,或者传输数据,你将在命令提示符下输入net-setup,选择正确的接口,并指明你的网络是有线还是无线。如果是无线,你需要输入你的 WiFi 路由器的 SSID 及其加密密钥(这可能会使用 ASCII 字符而不是十六进制字符)。在大多数情况下,你希望让 DHCP 自动检测你的网络。
当你需要担心损坏的分区时,你现在的首要任务是恢复。如果物理磁盘本身可能正在损坏,那么你的首要任务必须是确保其数据的安全。为此,我会在 SystemRescue 命令提示符下使用dd创建分区当前状态的完美副本,并将其保存到容量相等或更大的健康磁盘上。在运行lsblk以确认两个分区的指定后,复制操作可能看起来像这样,其中损坏的磁盘是/dev/sda,而你目标驱动器上的空分区是/dev/sdc1。
# dd if=/dev/sda of=/dev/sdc1
完成这些后,你可以自由地看看是否可以保存原始副本。输入testdisk。你会被询问你希望如何记录会话事件,你想恢复哪个磁盘,以及你期望找到哪种分区类型。通常情况下,TestDisk 会猜对正确的类型并将其突出显示为默认选项。
你会发现你面对的屏幕看起来像图 6.15,在那里你可以要求 TestDisk 分析你的磁盘,寻找现有的分区。当你已经发现并适当地标记了损坏的分区后,你可以将更改写入磁盘,你应该能够再次成功启动。
图 6.15. TestDisk 分区修复页面,在这里可以通过分析发现的分区然后使用其他工具进行编辑和恢复

等等...这就结束了?所有那些细节呢?“发现并适当地标记了损坏的分区”是否以某种有用的方式涵盖了复杂的过程?不,没有。但是,那么,你为什么还要读这一章呢?我会说,要么是因为你正在为未来的灾难做准备,要么是因为未来已经到来,你正在查看你损坏的分区。
如果你对未来感到担忧,那么跟随我处理一个或两个假设解决方案的狭窄细节并不会帮到你太多。此外,我不会建议你故意损坏一个真实的分区以便你可以真正尝试 TestDisk。如果你有自己的实际问题要处理,那么我随机选择的任何例子很可能对你都没有用。实际上,我能做的最好的事情就是让你知道这样的软件存在,并教你如何运行它。哦,还有,cgsecurity.org/testdisk.pdf上有一些相当不错的文档。
6.3.3. 从损坏的文件系统中恢复文件
如果最终发现你无法完全恢复你的磁盘,也许你只是设法复活了分区,足以允许访问文件,但不足以从磁盘可靠地启动。那么你的新优先级就是尽可能多地保存重要的文件。
最简单的方法是使用任何类型的实时启动会话中的常规 Linux 文件系统管理工具。如果你不确定分区的指定,运行lsblk将像往常一样让你了解情况。
可能您的分区还无法作为文件系统访问,因为它尚未挂载。如果实际上它没有出现在 df 命令的结果中,那么您将不得不 挂载 它,这意味着将其分配到文件系统中的一个位置,以便可以访问。您可以通过创建一个新目录来快速解决这个问题,其中您的分区将被挂载,然后使用 mount 命令。我选择在 /run/ 中创建我的临时挂载点目录,但任何您找到的未使用位置(如 /media/ 或 /mnt/)也可以。假设,像之前一样,分区是 /dev/sdc1:
# mkdir /run/temp-directory
# mount /dev/sdc1 /run/temp-directory
从现在开始,任何来自受损害文件系统的健康文件都可以在 temp-directory 内进行复制或编辑。它们也可能自动出现在您可能想要使用的任何 GUI 桌面文件管理工具中。
使用 ddrescue 进行恢复
如果不起作用?是时候拿出重型武器了。数据恢复工具 ddrescue 在文件系统之间复制文件。但它在同时还做了一件能让您笑的事情:它分析您的文件并尝试修复任何损坏的文件。没有工具能保证修复所有变坏的东西,但据传闻,ddrescue 是您能得到的最好的选择。
如果您使用的实时启动中没有安装 ddrescue,那么它默认是 SystemRescue 的一部分。从您的仓库中获取它。然后确定有问题的分区(在这个例子中是 /dev/sdc1),您想要保存镜像的分区以及记录操作事件的日志文件的名字和位置。拥有日志文件允许 ddrescue 继续停止的操作而不是从头开始。我的例子使用了一个外部驱动器(容量大于源驱动器)挂载到一个名为 /run/usb-mount 的目录:
# apt install gddrescue *1*
# ddrescue -d /dev/sdc1 /run/usb-mount/sdc1-backup.img \
/run/usb-mount/sdc1-backup.logfile *2*
-
1 注意:对于 CentOS,命令将是 yum install ddrescue。
-
2 告诉 ddrescue 忽略内核缓存并直接访问磁盘
您可以通过使用 dd 将备份镜像写入新的空驱动器(在我的例子中称为 /dev/sdd/)并从新驱动器启动系统来测试恢复:
# dd if=backup.img of=/dev/sdd
即使您发现您仍然无法启动分区,现在可能可以使用它来可靠地访问重要的单个文件。无论如何,您都比之前的情况要好。
使用 PhotoRec 进行文件恢复
另一个可以帮助您从损坏的驱动器中提取文件的工具是 PhotoRec。一眼看去,您就会知道它必须来自与 TestDisk 相同的地方。事实上,这两个工具都是由 CGSecurity 的 Christophe Grenier 创建和维护的。要加载程序,请输入
# photorec
一旦您确定了需要恢复的文件系统、您想要包含的文件类型以及您想要保存文件的位置,文件将使用 recup_dir.? 前缀保存到编号目录中:
$ ls recup_dir.12
f1092680.elf
f0668624.png
f0853304.xml
f0859464.xml
f0867192.txt
f0977016.gz
f0982184.xml
[...]
关于 PhotoRec 有一点需要注意,文件都被赋予了新的编号文件名,同时保留了它们的原始文件扩展名。这有时会使得寻找单个文件变得困难,但总比完全丢失要好。
6.4. 密码恢复:使用 chroot 挂载文件系统
你知道你支持的人选择的密码可能不足以保护你的基础设施免受严重攻击。即使是规则的少数例外,可能也在多个服务器和账户上重复使用。你恳求和唠叨,但似乎是一场输不起的战斗。但并非一切都已失去。
通过使用像 KeePass2 或 LastPass 这样的良好密码保险库,可以很大程度上解决跟踪足够复杂的密码的问题。而过度使用密码的问题至少可以通过实施像 Kerberos 这样的单一登录解决方案来减轻。然而,不幸的事情总是会发生。
对于那些足够关心并为每个访问的服务器想出好、强密码的用户来说,会发生什么?他们时不时地会忘记密码。如果有另一个拥有 sudo 权限的管理员可以登录到服务器并运行 passwd 为用户创建一个新的密码,那么这不会是问题:
# passwd username
[sudo] password for yourname:
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
但如果你的不幸且健忘的用户是唯一一个在该机器上有账户的管理员,那你就有麻烦了。除了这个之外,你没有。Linux 虚拟化的鼻祖 chroot 将会拯救你的日子。这里有一种可能的工作方式。
使用 live-boot 驱动为被锁定的服务器供电,运行 lsblk 确定服务器硬盘上根分区的指定,并将根分区挂载到一个临时目录:
# mkdir /run/mountdir/
# mount /dev/sdb1 /run/mountdir/
然后低声说出魔法词语,你就进来了:
# chroot /run/mountdir/
root@ubuntu:/#
这就是全部。到了这一点,你可以像在运行物理硬盘的版本上工作一样运行命令。使用 passwd 为你的管理员提供一个新密码来替换丢失的密码。然后,在输入 exit 关闭你的 chroot 会话后,重新启动机器(不要使用 live-boot USB)。现在一切应该都正常了。
加密还是不加密?
使用 ecryptfs 或 dm-crypt 等工具加密存储驱动器上的数据,将大大降低你的数据被泄露的风险。但另一方面,本章中讨论的许多救援和恢复操作在加密卷上都不会工作。
在安全性和易用性之间取得平衡并不是一门精确的科学。许多管理员没有对本地服务器和桌面工作站进行加密,因为至少它们可以通过锁着的办公室门得到保护,但他们坚持要求移动设备必须加密。
摘要
-
Linux 恢复模式提供了访问用于修复无法正常启动的系统管理工具的权限。
-
Live-boot 驱动允许您独立于计算机物理驱动器上的文件系统,独立启动您选择的 Linux 发行版。
-
定制构建的发行版,如 SystemRescueCd,是 Linux 的轻量级版本,预装了完整的救援工具。
-
有时可以使用 TestDisk 等工具恢复损坏的分区。
-
有时可以使用 ddrescue 和 PhotoRec 等工具从损坏的分区中恢复数据。
-
可以使用名为
chroot的虚拟进程挂载和管理文件系统。
关键术语
-
GRUB 是一个引导加载程序,它管理 Linux 引导过程中使用的镜像。
-
一个 哈希(校验和)是一个通过密码学生成的值,可以与主副本进行比较以确认镜像的真实性。
-
CD/DVD 的分区 主引导记录(MBR)将不同于 USB,创建 live-boot USB 时需要特别注意。
-
工具
chroot在挂载的文件系统中打开虚拟根 shell。
安全最佳实践
-
总是确认下载的镜像是否真实,通过检查它们的哈希值。并且避免从未加密的网站(HTTP 而不是 HTTPS)下载。
-
仔细考虑是否在系统驱动器上加密静态数据,在可访问性和安全性之间取得平衡。
-
强制使用密码保险库和单点登录服务来保护你的基础设施用户。
命令行审查
-
sha256sum systemrescuecd-x86-5.0.2.iso计算一个.ISO 文件的 SHA256 校验和。 -
isohybrid systemrescuecd-x86-5.0.2.iso向 live-boot 镜像添加一个 USB 友好的 MBR。 -
dd bs=4M if=systemrescuecd-x86-5.0.2.iso of=/dev/sdb && sync将 live-boot 镜像写入一个空驱动器。 -
mount /dev/sdc1 /run/temp-directory将分区挂载到 live 文件系统上的一个目录。 -
ddrescue -d /dev/sdc1 /run/usb-mount/sdc1-backup.img /run/usb-mount/ sdc1-backup.logfile将损坏分区上的文件保存到名为 sdc1-backup.img 的镜像,并将事件写入日志文件。 -
chroot /run/mountdir/在文件系统上打开一个根 shell。
测试自己
1
以下哪个选项可以让你访问损坏的 Linux 机器的自己的恢复模式?
- SystemRecovery
- GRUB
- 启动时按 CRTL+右 Shift
- chkdsk
2
默认情况下,Linux 恢复模式 shell 会话使用哪个用户账户?
- root
- 安装过程中创建的初始账户
- admin
- guest
3
Ubuntu 恢复模式菜单中的“清洁”选项的目标是什么?
- 查找并删除病毒
- 删除未使用的程序文件
- 删除未使用的用户账户
- 清理会话并关闭
4
引导 Linux live-boot 设备将执行以下哪项操作?
- 启动系统恢复环境
- 启动文件恢复环境
- 使用宿主机的分区启动 Linux 会话
- 仅使用 live-boot 分区启动 Linux 会话
5
你需要什么工具来创建一个可以从 USB 设备引导的 live-boot 镜像?
- ddrescue
- GRUB
- isohybrid
- ecryptfs
6
sha256sum 程序用于什么目的?
- 在挂载的文件系统上打开虚拟 shell 会话
- 管理系统启动时的 Linux 镜像
- 生成散列以验证文件完整性
- 管理从损坏的驱动器中恢复文件
7
以下哪个选项会将 SystemRescue 镜像写入系统上的第二个块设备?
dd if=systemrescuecd-x86-5.0.2.iso of=/dev/sdbdd if=/dev/sdb of=systemrescuecd-x86-5.0.2.isodd if=/dev/sdc of=systemrescuecd-x86-5.0.2.isodd if=systemrescuecd-x86-5.0.2.iso of=/dev/sdb28
chroot命令的作用是什么?
- 创建一个可用于从 USB 设备启动的实时引导镜像
- 在挂载的文件系统上打开虚拟 shell 会话
- 生成散列以验证文件完整性
- 删除未使用的程序文件
答案键
1.
b
2.
a
3.
b
4.
d
5.
c
6.
c
7.
a
8.
b
第一章. 欢迎来到 Linux
本章涵盖
-
Linux 的不同之处
-
基本生存技能
-
获取帮助
这本书将技术培训颠倒过来。尽管其他书籍、课程和在线资源围绕技能组织内容,但我将使用现实世界的项目作为教学工具。每个核心技能和 Linux 系统的功能都将被涵盖,并且覆盖得很好,但只有在项目需要时才会涉及。当你完成时,你将学到从传统来源学到的一切,但你还将知道如何执行十几个关键和复杂的行政任务,并且能够轻松应对更多。
前两到三章将快速引导你了解 Linux 服务器世界的初步介绍。之后,你将进行实际的手动项目,并且只有实际的手动项目。通过这些项目,你将学到不仅仅是命令和技能。准备好深入探索,并最终为你的商业问题创造解决方案。
没有一本书能够预测你整个职业生涯中可能遇到的所有挑战。但是,通过展示如何使用现实世界的工具解决现实世界的问题,这本书将使你通过内联文档和互联网使用大量资源变得更加容易。如果你的 Linux 经验有限,本章介绍了基本的命令行生存技能,并指出当事情不工作时你可以去哪里寻求帮助。
注意
正如你将看到的,命令行是操作系统(OS)提供的一个界面,允许你输入文本命令来控制操作系统或查询它管理的数据。
我应该指出,在本章以及每一章中,我们都强烈鼓励你自己尝试所有内容。没有比实际操作更好的方式来真正掌握 IT 技能,意识到它没有按你预期的方向发展,然后玩弄它直到它永远属于你。祝你好运,玩得开心!
1.1. Linux 与其他操作系统的不同之处
Linux 是免费的,这意味着它比其他操作系统更容易安装到任何需要的位置和时间,用于任何你能想象到的用途。不必担心购买站点许可证和跳过数字版权管理障碍,这使得测试各种硬件组合和服务器配置变得更加直接。
Linux 使得做各种真正有用和有创意的事情成为可能。例如,你可以在 U 盘上加载 Linux live boot 镜像,启动一个自身硬盘已损坏的 PC,然后进行故障排除和修复问题。(你将在第六章中学习如何做这件事。)或者,因为 Linux 是一个真正的多用户操作系统,整个团队可以同时登录本地或远程工作,对系统的隐私和稳定性充满信心。
Linux 是使用与深度成熟的 UNIX 操作系统相同的技术构建的,并配备了大多数相同的工具。这增加了极大的稳定性和安全性。Linux 发行版还提供了复杂的软件包管理系统,可以可靠地安装和维护通过在线精选仓库提供的成千上万免费软件应用程序。
但除了免费之外,Linux 还是 开源 的,这意味着任何人都可以取用代码库并将其重塑成他们想要的任何东西。实际上,这催生了一个庞大的专业 Linux 发行版生态系统。发行版(有时简称为 distro)是一组定制的软件栈,它与 Linux 内核一起打包,并附带安装用户计算机上工作版本 Linux 的工具。表 1.1 提供了一个非常不完整的发行版列表,以说明可用的各种事物。
表 1.1. 许多可用的 Linux 发行版
| 目的 | 发行版 |
|---|---|
| 安全/反黑客 | Kali Linux |
| Parrot | |
| 消费者桌面 | Mint |
| Elementary OS | |
| 轻量级(旧硬件;诊断) | Puppy Linux |
| LXLE | |
| 物联网管理 | Snappy Ubuntu Core |
| 企业服务器室 | CentOS(Red Hat Enterprise Linux 的社区版本) |
| OpenSUSE(SUSE 的社区版本) | |
| 云计算 | Amazon Linux(AWS AMI) |
| Ubuntu Server(AWS AMI) | |
| 多用途(除轻量级外) | Ubuntu |
找不到你想要的东西?自己创建一个。需要帮助?在线上有一个庞大且活跃的社区,如果有人还没有解决你的问题,他们知道去哪里找到解决方案。我认为最重要的是,基于社区的资源让 Linux 变得如此强大。
1.2. 基本生存技能
在开始本书中其余部分的企业级项目之前,确保我们站在同一起跑线上是值得的。这一章涵盖了 Linux 基础:UNIX 文件系统层次标准(包括伪文件系统),导航(ls,pwd 和 cd),文件管理工具(cat,less,touch,mkdir,rmdir,rm,cp 和 mv),一些技巧(如自动补全和文件通配符),sudo,以及寻求帮助的地方(man,info 和 journalctl)。
可能你已经拥有了足够多的经验,以至于你不需要任何那些材料。你可以自由地跳过这一章。不用担心我们其他人。我们会赶上的。
安装 Linux
我不会花时间讨论如何在你的 PC 上安装 Linux。这并不是因为安装过程如此荒谬地简单;有时它可能会相当复杂。相反,这是因为你选择的方法取决于你的具体情况。描述一种可能性,甚至六种可能性,对于其中 75% 的人来说这些场景不起作用,只会让他们感到烦恼。
需要一些帮助开始安装吗?请查看《一个月午餐时间学 Linux》(Manning,2016)。遇到特定的安装问题?花一分钟时间写一个简短但详细的描述,然后使用它在网上搜索帮助。在寻找预装 Linux 的笔记本电脑或台式机?在网上搜索“预装 Linux 的电脑”。有一些未使用的硬件和一个 USB 闪存盘?搜索“从 USB 安装 Linux”。更喜欢将 Linux 作为虚拟机安装?这是一个明智的选择。请留在第二章。
1.2.1. Linux 文件系统
人们常说 Linux 中的所有东西都是通过纯文本文件工作的,因此,从理解 Linux 文件系统开始可能最有意义。但在我们能够进入 Linux 之前,什么是文件系统呢?你可以将其视为一个数据表(或索引),它创建了个别文件和具有可识别磁盘位置的文件组之间的明显联系。图 1.1 可以帮助你可视化数据如何在磁盘分区中分布,并在目录结构中向系统用户展示。
图 1.1. 存储设备上的原始数据可以通过操作系统以组织良好的目录层次结构进行可视化表示。

为什么你需要索引呢?像硬盘或 USB 这样的数字存储设备并没有划分为可以用来组织文件夹(或 Linux 圈中称之为目录)的物理分区。一个特定的文件可以位于实际媒体上的一个位置,这个位置与几分钟或几秒钟前创建的几乎相同的文件相距甚远,而单个文件的所有部分可能并不连续。不仅如此,文件在磁盘上的地理位置也不一定随时间保持静态。
如果你想可靠地检索你的数据,你需要某种索引,它可以始终如一地指向你想要获取的资源。文件系统使用这种索引来提供一个有序的目录和文件集合的表象,这些目录和文件位于称为分区的单个磁盘分区中。
备注
如果你在某个时候需要自己深入研究,了解这些信息将很有用:如今,最常用的 Linux 文件系统是 ext4。但 Linux 也可以与使用 FAT32 和 NTFS 等平台文件系统格式化的存储驱动器一起工作。
磁盘分区中的所有文件都保存在根目录下的目录中,根目录由/(正斜杠)字符表示。这些目录的排列方式在很大程度上受 UNIX 文件系统层次结构标准(FHS)的约束。无论你使用的是 Linux 发行版、UNIX 还是甚至 macOS,你都会看到几乎相同的基本布局。图 1.2 显示了一些最常用的顶级目录。
图 1.2. UNIX FHS 定义的常见顶级目录

顶级目录——位于根目录直接下方的目录——包括 /etc/,其中包含定义个别程序和服务功能的配置文件,以及 /var/,其中包含系统或个别应用程序的 可变 文件,其内容在正常系统活动过程中经常发生变化。你还需要了解 /home 目录,其中为个别用户提供用于其私有文件的目录。
1.2.2. 导航:Linux 导航工具
在这里,你将学习五个最基本、必备的 Linux 导航命令 (ls、pwd、cd、cat 和 less)。由于命令行环境并不是一个特别直观的环境,无论你试图做什么,你都将大量依赖这五个工具来定位自己。
注意
我希望很明显,你应该在自己的计算机上尝试这些工具。这是你学习的唯一方法。
本书其余部分需要某种命令行终端。不幸的是,没有一种打开终端窗口的方法可以在所有 Linux 发行版中工作。例如,在 Ubuntu 菜单系统中终端的位置不一定与 Fedora 或 Mint 相匹配。至于 Ubuntu 本身?那取决于你运行的是哪个版本。
Ctrl-Alt-t 键盘组合至少在大多数环境中应该可以工作,同样,通过在应用程序菜单中搜索带有 终端 的项目也可以实现。默认情况下,一旦你的终端打开,你的主目录 (/home/yourname/) 将处于活动状态。
ls (列出)
如果你看不到终端中的内容,就没有必要在终端中逗留。你可以使用 ls 命令列出当前目录中文件和子目录的名称。带有 l 标志的 ls 命令(l 代表 长)不仅列出对象名称,还包括文件权限、所有者、组、文件大小和时间戳。添加目录指定符如 /var/ 将显示该目录的内容:
$ ls -l /var
total 40
drwxr-xr-x 2 root root 4096 May 3 06:25 backups
drwxr-xr-x 11 root root 4096 Jan 17 21:16 cache
drwxr-xr-x 39 root root 4096 Jan 17 21:16 lib
drwxrwsr-x 2 root staff 4096 Apr 12 2016 local
lrwxrwxrwx 1 root root 9 Aug 12 2016 lock -> /run/lock
drwxrwxr-x 7 root syslog 4096 May 3 06:25 log
drwxrwsr-x 2 root mail 4096 Aug 12 2016 mail
drwxr-xr-x 2 root root 4096 Aug 12 2016 opt
lrwxrwxrwx 1 root root 4 Aug 12 2016 run -> /run
drwxr-xr-x 5 root root 4096 Jan 17 21:16 spool
drwxrwxrwt 2 root root 4096 Nov 7 2016 tmp
drwxr-xr-x 3 root root 4096 Sep 11 2016 www
当将 h 参数添加到 ls -l 时,会以人类可读的格式显示文件大小——千字节、兆字节和吉字节,而不是字节,字节往往涉及大量难以计算的数字:
$ ls -lh /var/log
total 18M *1*
-rw-r--r-- 1 root root 0 May 3 06:25 alternatives.log
drwxr-xr-x 2 root root 4.0K May 3 06:25 apt
-rw-r----- 1 syslog adm 265K Jun 9 00:25 auth.log
-rw-r--r-- 1 root root 312K Aug 12 2016 bootstrap.log
-rw------- 1 root utmp 0 May 3 06:25 btmp
-rw-r----- 1 root adm 31 Aug 12 2016 dmesg
-rw-r--r-- 1 root root 836 May 21 14:15 dpkg.log
-rw-r--r-- 1 root root 32K Nov 7 2016 faillog
drwxr-xr-x 2 root root 4.0K Aug 12 2016 fsck
-rw-r----- 1 syslog adm 128K Jun 8 20:49 kern.log
-rw-rw-r-- 1 root utmp 287K Jun 9 00:25 lastlog
-rw-r----- 1 syslog adm 1.7M Jun 9 00:17 syslog
-rw-rw-r-- 1 root utmp 243K Jun 9 00:25 wtmp
- 1 该目录中文件占用的总磁盘空间(以 MB 计)
注意
通常情况下,你可以在两种方式中添加参数到 Linux 命令:一个连字符后跟一个单个字母(例如修改 ls 的 h),或者两个连字符引入相同参数的更详细版本。在示例中,ls --human-readable 生成的输出与 ls -h 完全相同。几乎所有的 Linux 命令都附带完整的文档,我们将在本章后面进行探讨。
想要知道你的当前目录下面有什么吗?将大写 R 作为参数添加到 ls 命令中会显示子目录以及它们包含的文件和子目录,无论目录嵌套层级有多少。为了了解这可能会变得多么复杂,以及因此如何有助于正确可视化,运行 ls -R 对 /etc/ 目录树进行操作:
$ ls -R /etc
pwd (当前工作目录)
在许多情况下,你的文件系统中的当前位置会显示在命令提示符的左侧。在这个例子中,我位于 /etc/ 下的网络目录中:
ubuntu@base:/etc/network$
由于你可能会在那些没有该提示符的系统上工作,你有时可能需要快速了解你的位置。为此,输入 pwd 将会打印出你的当前工作目录:
$ pwd
/etc/network
cd (更改目录)
一旦你对你的位置以及当前目录中立即可用的内容有了大致的了解,你需要知道如何更改位置。输入 cd 告诉命令行解释器(通常将是 Bash)移动到你指定的目录。当你第一次打开终端会话(通常被称为 shell)时,默认情况下,你会在自己的账户的 home 目录中。如果你运行 pwd,你可能会看到类似以下的内容:
$ pwd
/home/yourname
什么是 Bash?
Bash 可能是使用最广泛的 UNIX Shell。太好了!但什么是 Shell?Shell 是任何解释用户命令的用户界面,无论是通过命令行界面 (CLI) 还是图形用户界面 (GUI)。你可以将 Shell(如图所示)想象为一个软件层,旨在使用底层内核和硬件系统资源执行所有适当格式的命令。换句话说,它是你与计算机交流的方式。

Shell 解释用户输入的命令。
现在,让我们通过输入 cd 和一个正斜杠来返回根目录:
cd /
再次运行 ls 命令来看看有哪些内容。(你会看到之前在图 1.2 中展示的目录。)注意你可以访问的 yourname 目录的 home 目录。要移动到那里列出的任何子目录,请输入 cd 然后你想要访问的目录名。因为这里指定的路径是相对于你当前位置的 相对路径,所以你不需要在目录名前加上正斜杠字符。命令 cd .. 会将你向上移动一个目录层级,例如从 /home/yourname/ 移动到 /home/。
然而,如果你有更雄心勃勃的旅行计划,并且想看到远在你当前目录之外的世界的一部分,你需要使用一个绝对路径。这意味着你将始终使用以根目录(用正斜杠表示)开始的路径。要从系统上的其他位置返回到你的主目录,你将输入正斜杠,然后输入home(记住,它存在于根目录中),然后是你的用户名。试试看:
$ cd /home/yourname
也就是说,不带任何参数输入cd将带你回到当前登录用户的家目录。
cat(将文件内容打印到输出)
在终端中访问文本文件的内容有时可能有点棘手。cat工具会将文件打印到屏幕上,以便阅读,但不能编辑。这对于像/etc/中的fstab文件这样的较短的文档来说效果很好。下一个示例使用绝对路径,这样无论你在文件系统中的哪个位置,都可以找到该文件:
$ cat /etc/fstab
注意
“cat”这个名字实际上是“concatenate”的简称,这反映了该工具在将多个字符串或文件合并成一个文本流中的价值。
假设你想要阅读的文件包含的行数超过了单屏可以显示的行数。尝试查看/etc/group文件:
cat /etc/group
很可能,第一行滚得太快,以至于你无法阅读。如果你无法阅读,纯文本文件有什么用呢?当然,正如你很快就会看到的,Linux 有很多文本编辑器可以用来积极管理内容,但能够一次阅读较长的文件,一次一屏,可能也很不错。
less(显示文件内容)
欢迎使用less——这个名字可能是由于它能够快速读取和显示比完整文件内容更少的文本(或者可能是为了与较老的more命令区分开来)。你可以通过运行它来启动less,针对一个现有的文件名:
less /etc/services
使用less,你可以使用箭头键、PgUp、PgDn 和空格键上下滚动文件。当你完成时,按 q 键退出。
1.2.3. 完成任务:Linux 文件管理工具
如果你拥有文件和目录,你需要知道如何创建、销毁、移动和复制它们。文件通常由某些外部过程自动创建,例如软件安装或自动日志生成,或者,比如,在 LibreOffice 这样的办公生产力软件包中保存你的工作。在这里讨论所有这些内容并不是很有必要。然而,我要指出的是,你可以使用touch命令快速创建一个空文件,然后输入你想要给它的名字:
$ touch myfile
然后,你可以通过ls命令在你的当前目录中看到该文件的列表。当然,使用cat显示其内容将不会显示任何内容,因为你刚刚创建了该文件:
$ ls
myfile
$ cat myfile
使用touch命令“触摸”现有文件会更新其时间戳,而不会进行任何更改。如果出于某种原因,你想改变ls等命令如何列出或显示文件,这可能很有用。(如果你想让你的老板认为你一直在努力工作在一个数据文件上,而这个文件实际上你已经几周没有打开过,这也有帮助。)
当然,仅通过创建满是空文件的目录,你在这个快节奏、弱肉强食的世界中不会走得太远。最终,你需要填充它们,并编辑已经存在的文件。为此,你需要熟悉一个可靠的文本编辑器。
在我一头扎进一个非常危险的地方之前,我应该提到,很多人对他们的文本编辑器有着强烈的感情。你有没有礼貌地暗示过 Vim 用户,他们尊贵的编辑器可能不像以前那样有用和重要?当然没有。如果你做了类似的事情,你就不可能读到这本书。
我绝对不会告诉你必须使用哪个文本编辑器。然而,我会告诉你,功能齐全的文本处理器,如 LibreOffice 和 MS Word,永远不应该用于你的 Linux 管理工作。这些应用程序会在你的文档中添加各种隐藏的格式,这会破坏系统级文件。我可以说的是,粗略地说,有三种编辑器可能适合你:
-
如果你更喜欢在 GUI 环境中处理文档,那么一个简单的纯文本编辑器,如 gedit(Ubuntu 将其称为文本编辑器)就非常不错。还有各种语法高亮工具可供使用,使编码和脚本编写更加高效,你可以放心,这样的工具只会保存你看到的文本。
-
当你需要从终端会话中编辑文件时,一个具有直观界面的命令行编辑器,如 nano(或 Pico)可以完成这项工作。
-
最后,还有Vim(或其原始版本:vi)。啊,Vim。如果你愿意投入几个月的时间来学习这个在很大程度上是非直观的界面,那么你将获得一生大幅提高的生产力。就这么简单。
注意
我所有的书籍、文章和课程相关文档都是用 gedit 编写的。为什么?因为我喜欢它。
为什么不现在花一两分钟时间,使用前面提到的三个文本编辑器之一编辑你刚刚创建的 myfile 文档呢?例如:
$ nano myfile
$ vi myfile
对于 Vim,通过按 i 键进入插入模式,然后输入你的文本。如果你不想余生都困在 Vim 中,你可以通过按 Esc 键,然后输入:w来保存你的工作,然后通过输入:q来退出。
创建和删除目录
Linux 文件系统中的每个对象都由一个唯一的元数据集合表示,称为 inode。我想你可以这样说,之前讨论的文件系统索引是由驱动器上所有许多 inode 的元数据构建的。要显示使用 touch 创建的文件(包括 inode 信息)的更多信息,你可以使用 stat 命令:
$ stat myfile
File: 'myfile'
Size: 0 Blocks: 0 IO Block: 4096 regular empty file
Device: 802h/2050d Inode: 55185258 Links: 1 *1*
Access: (0664/-rw-rw-r--) Uid: ( 1000/ ubuntu) *2*
Gid: ( 1000/ ubuntu)
Access: 2017-06-09 13:21:00.191819194 +0000
Modify: 2017-06-09 13:21:00.191819194 +0000
Change: 2017-06-09 13:21:00.191819194 +0000
Birth: -
-
1 文件的 inode ID
-
2 文件的权限和所有权状态
正如你所见,输出包括描述文件名称、属性和时间戳的数据。但它还告诉你它的 inode ID 号。重要的是要意识到,当你移动、复制或删除文件或目录时,你实际上只是在编辑它的 inode 属性,而不是它的 ID。顺便说一句,inode 是 UNIX 系统用来识别文件系统中文件磁盘位置和属性的对象(如图 1.2 所示)。通常,每个文件或目录将有一个 inode。
假设你处于你的主目录中,为什么不创建一个新的目录,你可以用它来进行实验?为此,你将使用 mkdir:
$ mkdir myplace
现在移动到你的新目录并在那里创建一个文件:
$ cd myplace
$ touch newfile
$ ls
newfile
所以你可以看到删除对象是如何工作的,向上移动到父目录(使用 cd ..)并删除你刚刚创建的目录。奇怪的是,预定义的删除目录命令 rmdir 在这种情况下不起作用。试试看:
$ cd ..
$ rmdir myplace
rmdir: failed to remove 'myplace': Directory not empty
“目录不为空?”那又怎样?这是一个内置的检查,以防止你意外删除包含重要文件和子目录的目录,你可能已经忘记了。为了解决这个问题,你可以做几件事情。
一种方法是在 rmdir 命令中添加 --ignore-fail-on-non-empty 参数,但这需要大量的输入。另一种方法是手动逐个处理每个子目录,并逐个删除你找到的每个对象。但有时这可能会更糟。当你 100%确信目录下绝对没有任何你需要的东西时,最快的方法是向 rm 命令中添加 -r 标志(表示递归):
$ rm -r myplace
现在可能是告诉你关于使用图形用户界面桌面界面和命令行之间一个非常重要区别的好时机:命令行没有回收站。如果你使用 rm(或 rmdir)删除了某个东西,然后又后悔了,总的来说,你将无法恢复它。但嘿;想想看,你将释放出多少磁盘空间。
复制和移动文件
对于下一步,创建一些更多的文件和一个新的目录:
$ touch file1 file2 file3
$ mkdir newdir
你可以使用 cp 命令创建对象的相同副本。以下示例在目录 newdir 中创建了文件 file1 的副本:
$ cp file1 newdir
顺便说一句,cp 命令知道如何处理这个命令行,因为它足够聪明,能够识别 newdir 作为一个目录而不是文件。如果当前位置没有名为 newdir 的目录,cp 将会创建一个名为 newdir 的新文件副本。如果你像我一样,某个时候你可能会不小心拼错命令,最终得到一个奇怪的新文件而不是你想要的目录。无论如何,检查一切以确保一切按预期工作。
与 cp 命令不同,mv 命令会将对象永久地从一处移动到另一处。因此,如果你要将文件从你的主目录移动到 newdir 子目录,原始文件将不再可用:
$ mv file2 newdir
再次,亲自检查结果。你可以使用与文件相同的命令复制、移动或删除目录,在必要时添加 -r 标志。记住,你可能移动的不仅仅是可见的目录:任何现有的未见的嵌套层也将被拖动。
文件通配符
如果我在他们提出 globbing 这个名字的时候在场,我肯定会建议他们重新考虑。也许它是指一条蒸腾的沼泽生物?或者是从高速公路上的化工厂意外排放的?实际上,globbing(源自单词 global)描述的是将通配符字符应用于你的命令所处理的文件名。
如果你需要移动或复制多个文件,并且不想逐个输入文件名,你通常可以通过使用星号 (*) 通配符全局应用操作。要将当前目录的所有内容移动到其他位置,你可能做如下操作:
$ mv * /some/other/directory/
要移动只与特定序列部分匹配的文件名,尝试这个:
$ mv file* /some/other/directory/
这个命令会将所有以字母 file 开头的文件移动,但会保留其他所有文件不变。如果你有名为 file1, file2...file15 的文件,并且只想移动 file1 和 file9 之间的文件,你应该使用问号 (?) 而不是星号 (*):
$ mv file? /some/other/directory/
问号 (?) 只对那些文件名包含字母 file 和另一个字符的文件应用操作。它会在当前目录中留下 file10 到 file15。
删除文件
如你之前所学,可以使用 rm 删除对象。但请记住,这些操作实际上是不可逆的。如果你想从目录中删除 file1,你应该输入:
$ rm file1
文件通配符可以像对 cp 或 mv 一样应用于 rm,并且具有相同的效率。例如,这个命令
$ rm file*
删除当前目录中以字母 file 开头的所有文件。在删除操作中添加 -r 参数会使操作递归,并删除指定路径中任何子目录的内容:
$ rm -r *
实际上,这是一个非常危险的组合,尤其是当你以 root 权限工作,拥有对所有系统文件的控制权时。在投入太多到rm命令之前,请务必非常仔细地思考。
1.2.4. 键盘技巧
我怀疑没有多少人只是为了纯粹的乐趣而打字。而且我猜大多数人都会非常感激得知他们可以用,比如说,40%的更少的按键来完成打字。好吧,我即将为你节省一些相当显著的键盘时间。
剪切和粘贴
首先,尽管你可能看到了相反的情况,你可以在终端中复制和粘贴文本。确实,熟悉的 Ctrl-c(复制)和 Ctrl-v(粘贴)键组合在 Bash 会话中不起作用,但 Shift-Ctrl-c 和 Shift-Ctrl-v 会。你还可以通过右键单击鼠标并从菜单中选择适当的操作来剪切和粘贴。相信我,那可以带来很大的不同。只需想象你从一个可靠的在线来源找到了一个非常长的命令序列,看起来像这样:
$ find ./ -name \*.html -printf '%CD\t%p\n' | grep "09/10/17"
| awk '{print $2}' | xargs -t -i mv {} temp/
你想要输入全部内容吗?我也不想。这就是剪切和粘贴大显身手的地方。
Tab 补全
你真的需要了解这一点。Bash 会跟踪你的位置和环境,并监视你编写新命令的过程。如果你输入的字符,基于你当前环境中的文件和目录,包含任何关于你最终目标的提示,按 Tab 键会告诉 Bash 在命令行上显示其最佳猜测。如果你对建议满意,按 Enter 键,你就可以继续了。
这里有一个例子。假设你下载了一个软件存档文件,细心地命名为类似于 foo-matic-plus_0.9.1-3_amd64.deb 的东西。你希望将其复制到一个工作目录中,以便提取它。通常,你不得不输入
$ sudo cp foo-matic-plus_0.9.1-3_amd64.deb /usr/bin/foo-matic/
但是,如果文件位于你的当前目录中,假设它是该目录中唯一以foo开头的文件,那么你只需要输入cp foo并按 Tab 键。Bash 会为你填写剩余的文件名。当然,由于 Bash 无法读取你的思想,你仍然至少需要输入足够的目的地地址,以便 Tab 补全有东西可以工作。
试试看。使用touch创建一个具有非常长的文件名,然后尝试使用 Tab 补全删除或复制它。这是我想到的:
$ touch my-very-silly-filename_66-b.txt
$ rm my-<tab>
1.2.5. 伪文件系统
一个普通文件是一组数据,可以在系统重启后反复可靠地访问。相比之下,Linux 伪文件(或虚拟文件)的内容,例如可能存在于/sys/和/proc/目录中的内容,在正常意义上并不真正存在。伪文件的内容是由操作系统本身动态生成的,以表示特定的值。
例如,你可能想知道你的硬盘上总共有多少空间。让我向你保证,Linux 会非常乐意告诉你。让我们使用一个名为cat的命令行程序来读取一个包含磁盘字节数的文件,该文件由系统指定为 sda:
$ cat /sys/block/sda/size
1937389568
注意
如果系统上的第一个存储设备被称为/dev/sda,那么,正如你可能猜到的,第二个将被称为/dev/sdb,第三个将被称为/dev/sdc。最初,sda可能代表 SCSI Device A,但我发现将其视为 Storage Device A 更有意义。你也可能会遇到像/dev/hda(硬盘)、/dev/sr0(DVD 驱动器)、/dev/cdrom(没错,是 CD-ROM 驱动器)或甚至/dev/fd0(软盘驱动器)这样的设备标识符。
要获取这类信息,有更简单的方法。例如,你可以在 GUI 文件管理器中右键单击驱动器的图标,但/sys/中的伪文件是所有系统进程依赖的常见来源。
你不知道你的驱动器标识符?没问题。知道 Linux 将附加存储组织为块设备,你可以移动到/sys/block/目录并列出其内容。在其内容中,将有一个名为 sda/的目录。(记住,sda 代表 Storage Drive A。)这是系统启动时使用的第一个驱动器:
$ cd /sys/block
$ ls
loop0 loop1 loop2 sda sr0 *1*
- 1 所有当前可用的块设备。循环设备是一种伪设备,它允许文件像实际物理设备一样被使用。**
切换到 sda/目录并运行ls。在其内容中,你可能会看到名为 sda1、sda2 和 sda5 等的文件。这些中的每一个都代表 Linux 为更好地组织你的驱动器上的数据而创建的一个分区:
$ cd sda
$ ls
alignment_offset discard_alignment holders range sda3 trace
bdi events inflight removable size uevent
capability events_async integrity ro slaves
dev events_poll_msecs power sda1 stat
device ext_range queue sda2 subsystem
1.2.6. 显示谁才是老板:sudo
由于实际原因,使用享有完全管理权限的操作系统账户进行日常计算活动是不必要的风险。另一方面,完全限制自己使用非管理账户几乎无法完成任何事情。
许多 Linux 版本通过为选定的账户提供管理权限来解决此问题,在大多数情况下,这些权限纯粹是理论上的,但在必要时可以通过在命令前加上单词sudo来调用。一旦你通过提供密码来确认你的身份,你的命令将被视为由 root 用户发出的:
$ cat /etc/shadow
cat: /etc/shadow: Permission denied *1*
$ sudo cat /etc/shadow
[sudo] password for ubuntu:
- 1 没有 sudo 权限无法显示/etc/shadow 文件。
注意
默认情况下,在初始 Linux 安装过程中创建的用户将拥有sudo权限。
在本书中展示命令行示例时,我使用 $ 作为不需要管理员权限的命令的命令提示符,而对于需要使用 $ sudo 的命令,我则使用 #。因此,非管理员命令将看起来像这样:
$ ls
一个sudo命令将看起来像这样:
# nano /etc/group
1.3. 获取帮助
不论如何,IT 项目总会给您带来麻烦。这可能是因为您在尝试复杂的问题解决时遇到了困难,或者可能是因为您已经很久没有面对这个任务,以至于忘记了确切的语法。您将需要帮助。以下是一些可靠的查找地点。
1.3.1. Man 文件
根据公认惯例,创建和维护 Linux 命令背后软件的人们也会编写一份高度结构化的文档手册,称为man 文件。当 Linux 程序安装时,其 man 文件几乎总是与之一起安装,并且可以通过在命令行中输入man后跟命令名称来查看。信不信由你,man 系统本身也有一个 man 文件,所以我们将从这里开始:
$ man man
当您在自己的计算机上运行此操作时,您会看到 NAME 部分,即第一个部分,包括简短介绍,SYNOPSIS 提供详细的语法概述,DESCRIPTION 提供对程序的更深入解释,通常包括命令行参数和标志列表。如果您幸运的话,您还会找到一些有用的示例。
Man 文件有时可能相当大,因此浏览文档寻找特定细节并不总是实用的。由于各种历史原因,在像网络浏览器和文字处理程序这样的现代应用程序中启动本地搜索操作的 Ctrl-f 组合键不可用。相反,按/键以在屏幕底部获得一个文本输入字段,您可以在其中输入您的搜索模式。如果第一个高亮显示的结果不是您想要的,按 n 键(根据需要多次)在文档中向前搜索相同的字符串,直到找到您要找的内容。
1.3.2. Info
如果您恰好知道您要找的命令或程序的名称,man 系统就非常棒。但假设命令名称是您缺失的部分。在命令提示符下输入info,您将被带到 Bash 标准下,一个直接交互的环境:
$ info
如您从图 1.3 中可以看到,内容按主题字母顺序排列,标题如基础和压缩。您可以使用上下箭头键在行之间滚动;并且,当您到达感兴趣的某个主题时,您可以按 Enter 键跳转到该主题的页面。
图 1.3. Info 主菜单的第一屏。Info 链接在您的系统上可能因您安装的软件而异。

假设您想了解更多关于文件权限的信息。在基础部分向下滚动,直到到达文件权限,然后按 Enter 键。此页面的菜单部分表明,以下行是链接到下一级更多页面的链接。按 u 键将您带回到上一级,按 q 键将完全退出 Info。
我有一种感觉,Info 在社区中的使用并没有像它应该的那样广泛。事实上,我自己也有一个关于 Info 的秘密要分享——在我甚至注意到它之前,我已经与 Linux 合作了十年多!
默认情况下,Info 系统可能没有安装在一些 Linux 服务器发行版上。如果你在命令提示符下输入 info 没有给你带来你寻求的满足感,你可以在 Ubuntu/Debian 系统上使用 sudo apt install info 来安装它。
1.3.3. 互联网
无论你认为自己有多笨,我都可以向你保证,成千上万的 Linux 管理员,无论经验水平如何,都面临过相同类型的问题并解决了它们。许多解决方案都是通过在线社区论坛(如 link:serverfault.com 或 link:linuxquestions.org/questions)寻求帮助的结果。
当然,你可以在那些网站上发布自己的问题,但为什么要麻烦呢?互联网搜索引擎已经很好地索引了已经提出并回答的问题。一个良好的搜索查询通常可以让你更快地找到你需要的东西,而不是从头开始整个过程。
小技巧在于知道如何智能地搜索。在搜索框中输入我的服务器崩溃了并寄希望于最好的结果可能不会很有用。显然,你需要更多的细节。好吧。这是什么类型的服务器:Apache 网络服务器?你的浏览器中是否出现了任何错误消息?崩溃是否生成了任何日志条目?弄清楚这些可能是个不错的想法。
从系统日志获取错误信息
在几乎所有现代 Linux 发行版(Ubuntu 14.04 是一个值得注意的例外),你都可以通过 journalctl 访问所有系统日志:
# journalctl
很快你就会看到,不使用任何参数运行 journalctl 会使你淹没在数据洪流中。你需要找到一种方法来过滤你想要的信息。让我向你介绍 grep:
# journalctl | grep filename.php *1*
- 1 管道(|)字符使用一个命令(例如 journalctl)的输出作为下一个命令(grep)的输入。
在这个例子中,我使用的是通过美国键盘布局中的 Shift-\ 组合实现的垂直线(|)。这会将 journalctl 的输出传递到 grep 过滤器,只打印包含字符串 filename.php 的那些行。当然,我假设你的网络服务器正在运行 PHP 内容,并且有一个名为 filename.php 的文件。当然,我永远不会这样做。我通常给我的文件起更描述性和有用的名字,比如 stuff.php。
你可以使用 grep 连续使用来进一步缩小结果。假设 filename.php 的日志条目太多,你意识到你只需要包含单词 error 的那些。你可以将第一个操作的输出传递到第二个 grep 命令,过滤 error:
# journalctl | grep filename.php | grep error
如果你只想看到不包含单词 error 的那些行,你可以添加 -v(用于反转结果):
# journalctl | grep filename.php | grep -v error
在互联网上搜索
现在想象一下,您从 journalctl 获取的输出包括以下文本:
[Fri Oct 12 02:19:44 2017] [error] [client 54.208.59.72]
Client sent malformed Host header
这可能很有用。在互联网上搜索日期戳或特定 IP 地址毫无意义,但我敢打赌其他人也遇到过 Client sent malformed Host header。
为了减少误报,您可能希望将单词用引号括起来,以便您的搜索引擎只返回匹配该确切短语的搜索结果。减少误报的另一种方法是告诉搜索引擎忽略包含特定字符串的页面。
在这个相当愚蠢的例子中,您正在互联网上搜索关于编写 Linux 脚本的优秀介绍。您发现,根据您搜索引擎显示的大多数编写脚本的结果,似乎有人认为您更愿意住在好莱坞。您可以通过排除包含单词 movie 的页面来解决这个问题:
writing scripts -movie
概述
-
几乎任何 Linux 命令行操作都会使用五个基本工具中的某些或全部:
ls、pwd、cd、cat和less。 -
Linux 使用伪文件系统将硬件环境上的数据暴露给进程和用户。
-
授权用户可以通过调用
sudo来为单个命令获取管理权限。 -
通过 man 系统、Info 和在线,有大量的文档和其他帮助信息可用。
关键术语
-
文件系统 由按目录组织方式索引的数据文件组成。
-
进程 是运行软件程序的活动实例。
-
磁盘分区 是物理存储设备的逻辑划分,可以使其像独立设备一样工作。分区是所有现代操作系统的常见组织工具。
-
Bash 是一个用于执行系统操作的命令行用户界面。
-
可用于管理的 纯文本 是由有限字符集组成的文本,不包含任何多余的格式化代码。
-
文件通配符 涉及使用通配符来通过单个命令引用多个文件。
-
Tab 补全 使用 Tab 键来建议部分输入命令的可能完成。
-
伪文件系统 是包含具有动态数据(在系统启动时或之后自动生成)的目录。
安全最佳实践
避免以 root 用户身份在 Linux 机器上工作。使用普通用户账户,并在需要执行管理任务时使用 sudo。
命令行审查
-
ls -lh /var/log列出 /var/log/ 目录的内容和完整、人性化的详细信息。 -
cd单独使用时,会返回到您的家目录。 -
cp file1 newdir将名为 file1 的文件复制到名为 newdir 的目录中。 -
mv file? /some/other/directory/将包含字母 file 和一个额外字符的所有文件移动到目标位置。 -
rm -r *删除当前位置下的所有文件和目录。请谨慎使用。 -
man sudo打开使用sudo的 man 文档文件。
测试自己
1
以下哪个 Linux 发行版最适合安全操作?
- OpenSUSE
- CentOS
- Kali Linux
- LXLE
2
以下哪个工具允许你在终端会话中编辑文本?
- nano
- gedit
- touch
- LibreOffice
3
向
ls命令添加-l参数的作用是什么?
- 列出文件详细信息
- 以人类可读的格式列出信息
- 仅显示文件名
- 递归显示子目录
4
以下哪个命令会显示你在文件系统中的当前位置?
touchpwdls -ccd5
命令
cat /etc/group的作用是什么?
- 在可导航界面中显示 /etc/group 文件的内容
- 将 /etc/group 文件复制到新的指定位置
- 更新 /etc/group 文件的最后访问值
- 将 /etc/group 文件的内容打印到输出(在屏幕上滚动内容)
6
以下哪个命令会删除包含文件和子目录的目录?
rmdir myfulldirectorysudo rmdir myfulldirectoryrm -r myfulldirectoryrm myfulldirectory7
假设没有名为 mynewfile 的目录,
mv myfile mynewfile命令会做什么?
- 创建一个名为 mynewfile 的文件名为 myfile 的文件副本
- 创建一个名为 mynewfile 的空目录
- 创建一个名为 mynewfile 的空目录并将 myfile 文件移动到新目录中
- 将 myfile 的名称更改为 mynewfile
8
以下哪个命令会删除所有包含单词 file 以及其名称中任意数量的字符的文件?
rm file*rm file?rm file.rm file??
答案键
1.
c
2.
a
3.
a
4.
b
5.
d
6.
c
7.
d
8.
a
第二章. Linux 虚拟化:构建 Linux 工作环境
本章涵盖
-
寻找合适的虚拟化技术
-
使用 Linux 仓库管理器
-
使用 VirtualBox 构建有效的环境
-
使用 LXC 构建容器
-
如何以及何时密切管理虚拟机
虚拟化是几乎所有最近在服务和服务产品交付方式改进背后的单一最重要的技术。它使从云计算到自动驾驶汽车等整个行业不仅成为可能,而且变得引人入胜。好奇吗?以下是您从一开始就需要了解的两个虚拟化事实:
-
Linux 在虚拟空间中绝对占据主导地位。
-
虚拟化使学习任何技术都变得更容易。
本章介绍了目前广泛使用的占主导地位的企业虚拟化技术。但更重要的是,它还使您能够在虚拟环境中安全地学习 Linux 管理技能。为什么这种相当复杂的技术会在书中出现得这么早?因为它会使您处理剩余章节的内容变得容易得多。
需要一个全新的、干净的操作系统(OS)来尝试新事物?只需几秒钟就可以创建一个。配置错误导致您无法访问您的机器?没问题。终止它并启动一个新的。在这个过程中,您将学习如何使用 Linux 软件包管理器下载、安装和管理所有需要的软件(如 VirtualBox 和 LXC)。
2.1. 什么是虚拟化?
以前,当您想为您的公司或其客户提供新的服务器以提供一些网络服务器或文档共享时,您需要研究、申请预算批准、协商、订购、安全存放、配置和最后启动一台全新的机器。整个过程可能需要数月时间(请相信我——我经历过)。当对该服务的需求增加威胁到服务器容量时,您将重新开始整个过程,希望最终能够平衡容量/需求。
一个常见的场景是,一家公司提供多个但相互依赖的服务,每个服务都运行在自己的硬件上。想象一下,一个前端网络服务器与后端数据库一起部署。然而,当尘埃落定后,您通常会发现自己有一台服务器深度未使用,而另一台(通常就在机架旁边)无法跟上。但想象一下,您能否在多个服务之间安全地共享单个高容量服务器的计算、内存、存储和网络资源。想象一下,您能否通过仅分配它们所需的资源级别,从物理服务器中切割出虚拟服务器实例,然后立即调整容量以满足不断变化的需求。
现在想象一下,能够高效地将运行多个操作系统的数十个虚拟计算机打包到单个裸机服务器上,这样就不会有任何浪费。想象一下,当第一个服务器填满时,那些虚拟机(VM)能够自动溢出到其他物理服务器上。再想象一下,能够迅速杀死一个失败的 VM 或需要更新的 VM,并且替换得如此之快,以至于用户可能永远不会意识到有任何变化。在你的脑海中形成了那个图像(希望它类似于图 2.1)吗?你正在想象虚拟化。
图 2.1. 具有相互连接以及通过外部路由器连接到更大网络的硬件主机 VM 客户端

那张图片如此吸引人,现在它已经主导了企业计算领域。此时,我怀疑还有许多本地或基于云的服务器负载没有运行在某种虚拟化技术上。而运行这些大量虚拟工作负载的操作系统是 Linux。
顺便说一句,亚马逊网络服务(AWS)允许客户租用(Linux)服务器上的容量,这些服务器托管着数百万个虚拟机(VM),这些虚拟机反过来运行着无数的工作负载,包括许多最受欢迎的在线服务。图 2.2 展示了 AWS 弹性计算云(EC2)虚拟机实例如何作为一个全面存储、数据库和网络工具的中心。
图 2.2. 以亚马逊网络服务(AWS)的弹性云计算(EC2)虚拟机实例为中心的典型云计算工作负载

如果你对那些 AWS 细节感到有些晦涩,不要担心——无论如何,它们都不是这本书的主题。但如果你发现自己想了解更多关于亚马逊网络服务的信息,你总是可以阅读我的书《一个月午餐时间学习亚马逊网络服务》(Manning,2017 年)。至于虚拟化?这是我的《自学 Linux 虚拟化和高可用性》(LULU Press,2017 年)。
下一个简短的部分可能会感觉有点沉重,但它将帮助那些对了解内部工作原理感兴趣的人提供一些背景信息。成功的虚拟化使用物理计算机上的某种隔离空间,其中可以安装客户操作系统,然后欺骗它认为它独自在自己的计算机上。客户操作系统可以共享网络连接,以便管理员可以远程登录(我将在第三章中讨论这一点)并像在传统机器上一样完成他们的工作。这些相同的共享网络连接允许您使用虚拟机提供像网站这样的公共服务。广义而言,目前有两种虚拟化方法:
-
虚拟机管理程序—在一定程度上控制宿主系统硬件,为每个客户操作系统提供所需的资源(图 2.3)。客户机作为系统进程运行,但具有虚拟化访问硬件资源的权限。例如,AWS 服务器长期以来一直基于开源的 Xen 虚拟机管理程序技术(尽管他们最近开始将一些服务器切换到同样开源的 KVM 平台)。其他重要的虚拟机管理程序平台包括 VMware ESXi、KVM 和微软的 Hyper-V。
图 2.3. 类型 2 虚拟机管理程序架构图,显示了每个客户机安装了完整的操作系统,并将一些特殊管理任务委托给 Guest1
![]()
-
容器—极其轻量级的虚拟服务器,它们不是作为完整的操作系统运行,而是共享宿主操作系统的底层内核(见图 2.4)。容器可以从纯文本脚本构建,几秒钟内创建和启动,并且可以轻松可靠地在网络上共享。目前最知名的容器技术可能是 Docker。在本章中我们将要使用的 Linux 容器(LXC)项目是 Docker 的原始灵感来源。
图 2.4. LXC 架构图,显示了 LXC 环境与 Linux 内核及其下硬件层之间的访问

没有任何一种技术适合每个项目。但如果你决定继续阅读本章的其余部分,你将学习如何以及为什么使用两种虚拟化技术:VirtualBox(类型 2 虚拟机管理程序)和之前提到的 LXC(容器管理器)。
设计考虑因素
我不希望你在离开这本书时没有至少一些基本指南来选择虚拟化技术,所以这里有一些想法:
-
完整大小的虚拟机管理程序,如 Xen 和 KVM(通过如 Libvirt 这样的管理前端),通常用于涉及大量 Linux 虚拟机的大型企业级部署。
-
VirtualBox(以及 VMware 的 Player)非常适合测试和实验实时操作系统,一次一个或两个,无需在真实 PC 上安装。它们相对较高的开销使它们不适合大多数生产环境。
-
类似于 LXC 和 Docker 的容器技术轻量级,可以在几秒钟内配置和启动。LXC 容器特别适合于尝试新技术和构建操作系统软件栈。Docker 目前是运行无数动态、集成容器编队的核心技术,这些编队是庞大微服务架构的一部分。(我将在第九章中详细谈谈微服务。)
2.2. 使用 VirtualBox
使用 Oracle 的开源 VirtualBox,你可以将它安装在运行在任何桌面或笔记本电脑上的任何操作系统(包括 Windows)上,或者用它来托管几乎所有主要操作系统的虚拟机实例。
在 Windows PC 上安装 VirtualBox
想要从 Windows PC 尝试所有这些?请访问 VirtualBox 网站(www.virtualbox.org/wiki/Downloads)并下载可执行存档。点击你下载的文件,然后完成几个设置步骤(默认值应该都适用)。最后,你会被问及是否可以重置你的网络接口,然后是否要安装 VirtualBox。你当然会安装。
VirtualBox 提供了一个环境,在这个环境中,你可以启动尽可能多的虚拟计算机,数量取决于你的物理系统资源。它尤其适用于安全测试和学习新的管理技能,这是我们现在的首要目标。但在那之前,你需要了解在 Linux 上下载和安装软件的工作原理。
2.2.1. 使用 Linux 包管理器
在 Ubuntu 机器上顺利安装 VirtualBox 很简单。只需要两个命令:
# apt update
# apt install virtualbox
注意
记住,# 提示符表示此命令需要管理员权限,通常通过在命令前加上 sudo 来访问。
但在我们的例子中发生了什么?一切都围绕着名为高级包工具(Advanced Package Tool,简称 APT,更常被称为apt)的软件包管理器。在 Linux 世界中,包管理器将计算机连接到成千上万软件应用的庞大在线仓库,其中大多数是免费和开源的。默认情况下随 Linux 安装的管理器有几个任务:
-
维护本地索引以跟踪仓库及其内容
-
跟踪你本地机器上安装的所有软件的状态
-
确保将所有可用的更新应用到已安装的软件
-
在安装新应用程序之前确保软件依赖项(你安装的包所需的其它软件包或配置参数)得到满足
-
处理软件包的安装和卸载
图 2.5 展示了在线软件仓库与运行在 Linux 计算机上的包管理器之间持续关系的一些要素。
图 2.5. 主软件仓库、镜像下载服务器和终端用户机上运行的 Linux 之间的关系

该系统工作得非常好,并且由于历史和经济原因,在 Linux 世界之外几乎没有与之相当的东西。但是,你要知道,你所使用的管理器将取决于你的特定 Linux 发行版。总的来说,如果你的发行版属于 Debian/Ubuntu 家族,那么你会使用 APT。Red Hat 家族的成员将使用 RPM 管理器和 Yum(或其新的 DNF 替代品)。表 2.1 显示了部分发行版列表。
表 2.1. 包管理器和发行版
| 包管理器 | 发行版 |
|---|---|
| APT | Debian |
| Ubuntu | |
| Mint | |
| Kali Linux | |
| RPM | Red Hat Enterprise Linux |
| CentOS | |
| Fedora | |
| YaST | SUSE Linux |
| OpenSUSE |
除了使用包管理器从远程仓库安装软件外,您可能还需要从网站下载软件。通常,您会发现一些软件包,它们的开发者已经格式化以与 APT 或 Yum 兼容,一旦从命令行使用后端工具安装,您就可以使用它们。比如说,您想使用 Skype。访问其下载页面(图 2.6)可以让您下载 Skype Linux 包的 DEB 或 RPM 文件。如果您使用的是基于 Debian 的发行版和 APT,则选择 DEB;如果您属于喜欢 Yum 的 Red Hat 家族,则选择 RPM。
图 2.6. Skype for Linux 的下载页面。注意 Debian (APT)和 RPM (Yum)包管理器的单独链接。

使用 Debian 包管理器
下载文件后,您可以使用 dpkg 从命令行安装它。使用 -i 标志(表示安装)。您需要确保您从包含 skypeforlinux-64 文件的目录中运行 dpkg 命令。此示例假设您将包保存到用户账户的下载目录中:
$ cd /home/<username>/Downloads
# dpkg -i skypeforlinux-64.deb
dpkg命令应该会为您处理依赖项。但如果它没有这样做,它的输出通常会提供足够的信息,让您知道发生了什么。
“-64”是什么意思?
Linux,像其他基于 x86 的操作系统的版本一样,有 64 位和 32 位版本。过去十年中制造和销售的绝大多数计算机使用的是更快的 64 位架构。因为仍然有较旧的或面向开发的硬件存在,您有时需要运行 32 位,并且您希望安装的软件能够与它兼容。
您可以通过在命令行中运行 arch 来自行检查。除非您知道您正在使用较旧的硬件(顺便说一句,Linux 在这方面做得特别出色),否则您安全地假设您是一个 64 位用户。
为 RPM 包管理器安装 VirtualBox
之前,我介绍了 apt update 和 apt install virtualbox。这些简短的命令做了什么?为了解释,我将在运行 Fedora Linux 发行版的机器上安装相同的 VirtualBox 软件。因为我将使用 Red Hat 的 DNF 包管理器,所以需要额外的几个步骤——这很好,因为运行这些步骤将说明这个过程是如何工作的。这个过程有点复杂,所以表 2.2 列出了步骤。
表 2.2. 在 Fedora 上安装 VirtualBox 的步骤
| 任务 | 命令 |
|---|---|
| 添加仓库 | wget http://download.virtualbox.org/virtualbox/rpm/fedora/ virtualbox.repo |
| 更新索引 | dnf update |
| 安装依赖项 | dnf install patch kernel-devel dkms |
| 安装包 | dnf install VirtualBox-5.1 |
注意
这些步骤是为 Fedora 版本 25 设计并测试的,并且很好地说明了软件包管理过程。尽管如此,在更近期的 Fedora 版本上,这一切可能会运行得更顺畅。
回到 Ubuntu,当我将其添加到 install 命令时,APT 知道我所说的 virtualbox 是什么。这是因为 VirtualBox 软件包是 APT 已经熟悉的在线仓库的一部分。然而,结果是,Red Hat 及其子产品(如 CentOS 和 Fedora)并不那么社交,至少不是默认情况下,所以我需要将 virtualbox 仓库添加到 Yum。
从上一章,你会记得第三方软件配置文件通常保存在 /etc/ 目录结构中,在这方面,yum/DNF 并无不同。仓库信息保存在 /etc/yum.repos.d/ 中,因此你应该切换到该目录。从那里,你将使用 wget 程序(通常默认安装)下载 .repo 文件。以下是完成所有这些步骤的方法:
$ cd /etc/yum.repos.d/
# wget http://download.virtualbox.org/virtualbox/rpm/fedora/
virtualbox.repo
在 Linux 上安装软件
安装 Linux 软件的特定说明,包括我之前使用的精确 URL 等细节,几乎总是可以在网上找到。你可以在软件开发商的网站上找到这些信息,或者通过免费可用的指南。互联网是你的朋友。
确保你在搜索引擎短语中指定 Linux 发行版、版本和架构, wherever necessary。我发现有关此项目所需特定软件包的详细信息是通过我最喜欢的搜索引擎找到的——你也应该这样做。
在正确的目录中拥有 .repo 文件,直到你告诉 RPM 发生了什么变化之前,它并不会做什么。你可以通过运行 update 命令来做到这一点。update 命令还会将本地仓库索引与其在线对应项进行比较,以查看是否有任何新信息你需要了解。无论你使用什么管理器,在安装新软件之前更新仓库信息总是一个好主意:
# dnf update
Importing GPG key 0x98AB5139: *1*
Userid : "Oracle Corporation (VirtualBox archive signing key)
<info@virtualbox.org>"
Fingerprint: 7B0F AB3A 13B9 0743 5925 D9C9 5442 2A4B 98AB 5139
From : https://www.virtualbox.org/download/
oracle_vbox.asc *2*
Is this ok [y/N]: y
Fedora 25 - x86_64 - VirtualBox 120 kB/s | 33 kB 00:00
Dependencies resolved.
Nothing to do.
Complete!
-
1 所有与仓库的交易都使用 GPG 密钥加密。
-
2 VirtualBox 引用指的是我将这个 Fedora 主机作为 VirtualBox 中的 VM 运行的事实。
下一步涉及安装 VirtualBox 正确运行所需的所有软件依赖项。依赖项 是指必须已经安装在你的计算机上,以便新软件包可以正常工作。回到 Ubuntu,APT 无形中处理了这些重要的细节;Yum 也经常处理许多后台细节。但是当它不这样做时,迫使你手动处理,这些细节可以从之前讨论的相同在线来源中轻松获得。以下是这将看起来像的截断版本:
# dnf install patch kernel-devel dkms
Last metadata expiration check: 0:43:23 ago on Tue Jun 13 12:56:16 2017.
[...]
Dependencies resolved.
==========================================================================
Package Arch Version Repository Size
==========================================================================
Installing:
dkms noarch 2.3-5.20170523git8c3065c.fc25 updates 81 k
kernel-devel x86_64 4.11.3-202.fc25 updates 11 M
patch x86_64 2.7.5-3.fc24 fedora 125 k
Transaction Summary
==========================================================================
Install 3 Packages
Total download size: 12 M
Installed size: 43 M
Is this ok [y/N]: y *1*
Downloading Packages:
(1/3): dkms-2.3-5.20170523git8c3065c.fc25.noarc 382 kB/s | 81 kB 00:00
(2/3): patch-2.7.5-3.fc24.x86_64.rpm 341 kB/s | 125 kB 00:00
(3/3): kernel-devel-4.11.3-202.fc25.x86_64.rpm 2.4 MB/s | 11 MB 00:04
---------------------------------------------------------------------------
Total 1.8 MB/s | 12 MB 00:06
[...]
Running transaction
Installing : kernel-devel-4.11.3-202.fc25.x86_64 1/3
Installing : dkms-2.3-5.20170523git8c3065c.fc25.noarch 2/3
Installing : patch-2.7.5-3.fc24.x86_64 3/3
Verifying : patch-2.7.5-3.fc24.x86_64 1/3
Verifying : kernel-devel-4.11.3-202.fc25.x86_64 2/3
Verifying : dkms-2.3-5.20170523git8c3065c.fc25.noarch 3/3
Installed: *2*
dkms.noarch 2.3-5.20170523git8c.fc25 kernel-devel.x86_64 4.11.3-202.fc25
patch.x86_64 2.7.5-3.fc24
Complete!
-
1 在它运行之前,通过输入 y 来批准操作。
-
2 快速回顾成功操作
经过一段时间的旅程,你终于准备好在你的 Red Hat、CentOS 或 Fedora 机器上安装 VirtualBox 本身了。我在这个例子中使用的版本号来自之前使用的在线指南。当然,当你尝试这个时,它可能不再是 5.1。
# dnf install VirtualBox-5.1
Last metadata expiration check: 0:00:31 ago on Tue Jun 13 13:43:31 2017.
Dependencies resolved. *1*
==========================================================================
Package Arch Version Repository Size
==========================================================================
Installing: *2*
SDL x86_64 1.2.15-21.fc24 fedora 213 k
VirtualBox-5.1 x86_64 5.1.22_115126_fedora25-1 virtualbox 68 M
python-libs x86_64 2.7.13-2.fc25 updates 6.2 M
qt5-qtx11extras x86_64 5.7.1-2.fc25 updates 30 k
Transaction Summary
==========================================================================
Install 4 Packages
[...]
Is this ok [y/N]: y
[...]
Creating group 'vboxusers'. VM users must be member
of that group! *3*
[...]
Installed:
SDL.x86_64 1.2.15-21.fc24
VirtualBox-5.1.x86_64 5.1.22_115126_fedora25-1
python-libs.x86_64 2.7.13-2.fc25
qt5-qtx11extras.x86_64 5.7.1-2.fc25
Complete!
-
1 DNF 显然对之前安装的依赖项感到满意。
-
2 返回此操作中将安装的所有包的列表
-
3 注意,该过程创建了一个新的系统组。我将在第九章中讨论组。
VirtualBox 附加组件
你应该知道,Oracle 为 VirtualBox 提供了一个扩展包,它增加了 USB 支持、磁盘加密以及一些现有的引导选项的替代方案。如果你在运行标准包时遇到死胡同,请考虑这些工具。
你还可以通过 VBox Guest Additions CD-ROM 镜像在 VirtualBox 虚拟机(VM)和其宿主机之间添加额外的文件系统和设备集成。这为你提供了共享剪贴板和拖放等特性。如果 Vbox 附加组件尚未通过你的宿主机提供,请使用以下命令在 Ubuntu 上安装扩展包:
sudo apt install virtualbox-guest-additions-iso
然后将它作为虚拟光驱添加到正在运行的虚拟机中。搜索在线文档,了解可能需要的任何额外包,以便在宿主机上运行。
在实际使用虚拟化工具如 VirtualBox 之前,我应该至少给你一两个追踪其他可能需要的仓库包的提示。APT 系统允许你直接使用apt search搜索可用的包。以下示例搜索可能帮助你监控系统健康的包,然后使用apt show显示完整的包信息:
$ apt search sensors
$ apt show lm-sensors
当 aptitude 程序安装后,它是一个半图形化的 shell 环境,你可以在这里探索和管理可用的和已安装的包。如果你不能没有鼠标,Synaptic 是桌面环境的全 GUI 包管理器。Yum 世界也是完全可搜索的:
$ yum search sensors
$ yum info lm_sensors
2.2.2. 定义虚拟机(VM)
我不确定你是否曾经从组件中组装过一台物理计算机,但这可能相当复杂。在 VirtualBox 中定义一个新的虚拟机基本上是以相同的方式进行的。唯一的显著区别是,你不需要弯腰低头,用牙齿咬着手电筒手动添加 RAM 和存储驱动器到你的机器中,VirtualBox 允许你通过点击鼠标来定义虚拟机的“硬件”规格。
在 VirtualBox 界面中点击“新建”后,你需要给你的虚拟机起一个描述性的名字。正如你在图 2.7 中看到的,软件应该能够自动正确填充类型和版本字段。在这里你选择的类型和版本不会安装实际的操作系统,而是用于应用适当的硬件仿真设置。
图 2.7. 创建虚拟机对话框:VirtualBox 将尝试近似你的操作系统和操作系统版本,以便稍后提供智能默认选择。

在下一屏,你将为你的虚拟机分配 RAM。除非你计划做一些特别要求高的任务,比如托管容器集群或运行繁忙的 Web 服务器,否则默认的(768 MB)应该足够了。如果你需要,当然可以给它更多的 RAM,但别忘了为主机机器和可能已经存在于其上的任何其他虚拟机留出足够的空间。如果你的主机只有 4 GB 的物理 RAM,你可能不会想将其中一半分配给虚拟机。
如果你最终决定同时运行多个虚拟机,请记住这些限制,这在书中稍后尝试的一些项目中将很有用。即使每个虚拟机只使用默认的内存量,两三个虚拟机也可能开始消耗正常主机操作所需的 RAM。
VirtualBox 的设置过程现在会询问你是否想要为你的虚拟机创建一个新的虚拟磁盘或使用一个已经存在的磁盘(图 2.8)。没有硬盘的计算机是什么?可能有时你想要在两个虚拟机之间共享单个磁盘,但在这个练习中,我猜你可能会想从头开始。选择现在创建虚拟硬盘。
图 2.8. 硬盘屏幕。注意,在这种情况下,非默认的“使用现有虚拟硬盘文件”单选按钮被选中。

下一屏(图 2.9)允许你为即将创建的磁盘选择硬盘文件格式。除非你计划最终将磁盘导出以用于其他虚拟化环境,否则默认的 VirtualBox 磁盘镜像(VDI)格式应该足够好。
图 2.9. 虚拟硬盘可以使用多种格式创建。VDI 格式适合仅用于 VirtualBox 的虚拟机。

我也从未后悔过选择默认的动态分配选项(图 2.10)来确定虚拟驱动器如何在宿主上消耗空间。这里的“动态”意味着宿主存储磁盘上的空间将根据需要分配给虚拟机。如果虚拟机磁盘使用量保持较低,分配的宿主空间将更少。
图 2.10. 动态分配的虚拟磁盘将只在其宿主设备上占用它们所需的空间。

另一方面,固定大小的磁盘将立即分配其最大空间,无论其实际使用量如何。固定大小磁盘的唯一优势是应用性能。因为我通常只使用 VirtualBox 虚拟机进行测试和实验,所以我可以避免这种权衡。
因为它知道您想要的是 Linux,并且因为 Linux 对存储空间的利用非常高效,VirtualBox 在下一次屏幕上可能会只为您提供 8 GB 的总磁盘大小(图 2.11)。除非您对虚拟机有非同寻常的大计划(比如,您将要进行一些严肃的数据库操作),否则这可能会是合适的。另一方面,如果您选择了 Windows 作为操作系统,默认选择将是 25 GB——这是有充分理由的:Windows 并不羞于要求大量的资源。这是一个很好的例子,说明了 Linux 非常适合虚拟环境的一种方式。
图 2.11. 如果需要,您的虚拟磁盘可以大至 2 TB 或主机设备上的最大可用空间。

注意
如果您愿意,您还可以在“文件位置和大小”屏幕上编辑 VirtualBox 将用于您的磁盘的名称和位置。
完成后,点击创建,新的虚拟机将出现在 VirtualBox 管理器左侧的虚拟机列表中。但您还没有完成:那只是机器。现在您还需要一个操作系统来激活它。
2.2.3. 安装操作系统(OS)
现在您已经定义了新虚拟机的虚拟硬件配置文件,以下是你还需要做的事情:
-
下载一个包含您想要使用的 Linux 发行版镜像的文件(ISO 格式)。
-
使用包含您下载的 ISO 的虚拟 DVD 驱动器启动新的虚拟机。
-
按照标准的操作系统安装流程进行操作。
-
启动虚拟机并运行您之前安装的操作系统。
一旦您确定了一个发行版,您将需要下载一个包含操作系统文件和安装程序的.ISO 文件。找到正确的文件通常只是在网上搜索发行版名称和“下载”一词。对于 Ubuntu,您也可以访问ubuntu.com页面,并点击您在图 2.12 中看到的“下载”标签。注意可用的各种 Ubuntu 版本。如果您打算使用这个虚拟机进行管理任务,那么小巧快速的 Server 版本可能比桌面版本更好选择。
图 2.12. Ubuntu.com 首页上的“下载”下拉菜单。注意 Ubuntu 提供的版本范围。

大文件有时在下载过程中可能会损坏。如果您的.ISO 文件中的任何一个字节被更改,安装可能无法正常工作。由于您不希望投入时间和精力,最后却发现下载存在问题,因此立即计算您下载的.ISO 文件的校验和(或哈希值)以确认一切正常总是一个好主意。为此,您需要获取适当的 SHA 或 MD5 校验和,它是一个看起来像这样的长字符串:
4375b73e3a1aa305a36320ffd7484682922262b3
你应该能够从获取 .ISO 文件的地方获取这个字符串。以 Ubuntu 为例,这意味着你要访问 releases.ubuntu.com/ 上的网页,点击与下载版本匹配的目录,然后点击一个校验和链接(例如,SHA1SUMS)。你应该将页面上的相应字符串与从下载 .ISO 的同一目录运行命令的结果进行比较,这可能看起来像这样:
$ shasum ubuntu-16.04.2-server-amd64.iso
如果它们匹配,你就可以开始了。如果不匹配(并且你已经仔细检查过确保你查看的是正确的版本),那么你可能需要再次下载 .ISO。
注意
你应该知道,存在多种哈希类型。多年来,MD5SUM 算法一直占主导地位,但 SHA256(其具有更长的 256 位哈希)越来越受欢迎。实际上,对于大型操作系统镜像文件,一种方法可能并不比另一种方法差。
一旦你的 .ISO 文件已经就绪,回到 VirtualBox。在左侧面板中高亮显示你刚刚创建的虚拟机,点击应用顶部的绿色启动按钮。系统会提示你从文件系统中选择一个 .ISO 文件作为虚拟 DVD 驱动器使用。当然,你会选择你下载的那个。新的虚拟机会读取这个 DVD 并启动操作系统安装。
大多数情况下,安装过程会顺利进行;然而,包括解决可能出错的各种小问题,可能需要几章完整的章节。如果你遇到麻烦,可以查阅每个 Linux 发行版的文档和指南,或者在你所在的 Manning 网站上的 Linux in Action 论坛上发帖提问,让我们其他人帮助你。
当一切安装得很好时,在你成功引导到虚拟机之前,可能还有一些其他事情需要处理。在你的虚拟机高亮显示时,点击黄色的设置图标。这里你可以调整虚拟机的环境和硬件设置。例如,点击网络,你可以定义网络连接。如果你想通过主机机的网络接口为虚拟机提供完整的互联网访问,那么,如 图 2.13 所示,你可以在“连接到”下拉菜单中选择桥接适配器,然后选择你主机适配器的名称。
图 2.13. 设置对话框的网络选项卡,你可以在这里确定为你的虚拟机使用哪种网络接口(或接口)

注意
使用桥接适配器可能不是你的首选,有时它可能带来安全风险。实际上,选择 NAT 网络是一种更常见的为虚拟机提供互联网访问的方式。因为这本书中的许多练习都需要多个虚拟机之间的网络连接(使用 NAT 是复杂的事情),所以我目前选择使用桥接。
你可能永远不需要这条信息,但如果需要,你会感激知道它。在某些情况下,为了使虚拟机能够正确启动,你可能还需要从驱动器中移除 DVD,就像为“真实”的物理安装一样。你可以通过点击存储来实现。点击列出的第一个磁盘,然后点击底部的移除磁盘图标(图 2.14)。确保你不会意外地移除你的硬盘!
图 2.14. 通过右键单击其链接并选择移除来移除虚拟磁盘。你可能需要这样做以确保虚拟机能够启动到正确的驱动器。

也可能有时你需要挂载 DVD(或.ISO 文件)以使 VirtualBox 能够识别它。点击带有控制器:IDE 行的+图标,你可以选择一个文件作为虚拟光驱。
2.2.4. 克隆和共享 VirtualBox 虚拟机
这一部分有点像额外奖励,但谁不喜欢免费的东西呢?我将告诉你两个相关的技巧:如何组织你的 VirtualBox 虚拟机,以便快速启动新的虚拟机,以及如何使用命令行在网络上共享虚拟机。
快速启动虚拟机的克隆
与虚拟机一起工作的最明显优点之一是能够快速访问一个新鲜、干净的操作系统环境。但如果访问该环境需要通过完整的安装过程,那么在你将克隆加入其中之前,我不认为有多少是“快速”的。为什么不保持你的原始虚拟机在干净的后安装状态下,每次你想做一些实际工作时就创建一个相同的克隆呢?
这很简单。再次查看 VirtualBox 应用程序。选择你想要用作主副本的(停止)虚拟机,点击机器菜单链接,然后点击克隆。你将确认你想要给你的克隆起的名字,然后点击下一步,选择是否创建完整克隆(这意味着将为新的虚拟机创建全新的文件副本)或链接克隆(这意味着新的虚拟机将与主虚拟机共享所有基础文件,同时保持你的新工作独立)。
注意
选择链接选项将更快,并且占用你硬盘上的空间更少。唯一的缺点是,你将无法稍后将其克隆移动到不同的计算机上。这是你的选择。
现在点击克隆,新的虚拟机就会出现在虚拟机面板中。按照你通常的方式启动它,然后使用你在主虚拟机上设置的相同凭据登录。
从命令行管理虚拟机
VirtualBox 自带一个命令行 shell,使用vboxmanage来调用。为什么要使用命令行呢?因为,除了其他好处之外,它允许你在远程服务器上工作,这可以大大增加可能项目的范围。要了解vboxmanage是如何工作的,使用list vms来列出你系统上当前可用的所有虚拟机:
$ vboxmanage list vms
"Ubuntu-16.04-template" {c00d3b2b-6c77–4919–85e2–6f6f28c63d56}
"centos-7-template" {e2613f6d-1d0d-489c-8d9f-21a36b2ed6e7}
"Kali-Linux-template" {b7a3aea2–0cfb-4763–9ca9–096f587b2b20}
"website-project" {2387a5ab-a65e-4a1d-8e2c-25ee81bc7203}
"Ubuntu-16-lxd" {62bb89f8–7b45–4df6-a8ea-3d4265dfcc2f}
vboxmanage clonevm 命令将执行与我之前使用图形界面描述的相同类型的克隆操作。在这里,我正在克隆 Kali Linux 模板虚拟机,并将副本命名为 newkali:
$ vboxmanage clonevm --register Kali-Linux-template --name newkali
你可以通过再次运行 vboxmanage list vms 来验证它是否成功。
这将在我本地计算机上使用新虚拟机时工作得很好。但假设我想让团队成员能够运行该虚拟机的确切副本,也许他们可以测试我一直在工作的东西。为此,我需要将虚拟机转换为某种标准化的文件格式。以下是如何使用 Open Virtualization Format 导出本地虚拟机到文件的示例:
$ vboxmanage export website-project -o website.ova *1*
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Successfully exported 1 machine(s).
- 1 The -o flag specifies the output filename: website.ova, in this case.
接下来,你需要将 .OVA 文件复制到同事的计算机上。请注意,根据任何标准,该文件都不会被认为是小而精致的。如果你没有足够的网络带宽进行多 GB 的传输,那么考虑通过 USB 设备移动它。但如果你选择网络路径,最适合这项工作的工具是 Secure Copy (scp)。以下是它可能的工作方式:
$ scp website.ova username@192.168.0.34:/home/username
如果整个 scp 事情看起来有些出乎意料,不要担心:帮助即将到来。scp 命令将在第三章(作为 OpenSSH 内容的一部分)中全面介绍。同时,那个 scp 命令只有在两台计算机上都安装了 OpenSSH、你已授权远程计算机上的用户账户访问,并且可以从你的本地机器访问时才会工作。
一旦传输完成,剩下的就是在远程计算机上,将虚拟机导入到该机器的 VirtualBox 中。命令很简单:
$ vboxmanage import website.ova
使用 list vms 验证导入操作是否成功,并尝试从桌面启动虚拟机:
$ vboxmanage list vms
"website" {30ec7f7d-912b-40a9–8cc1-f9283f4edc61}
如果你不需要复杂的远程访问,你也可以从图形界面共享虚拟机。在 VirtualBox 中,选择你想要共享的机器,然后点击文件菜单,接着导出虚拟机。
2.3. 使用 Linux 容器 (LXC)
VirtualBox 对于运行需要 Linux 内核访问的操作(就像你使用 SELinux 等安全功能时那样,你将在第九章(kindle_split_017.xhtml#ch09)中看到),需要 GUI 桌面会话,或测试像 Windows 这样的操作系统来说非常出色。但如果你需要快速访问一个干净的 Linux 环境,并且你不需要任何特殊的发布版本,那么你很难打败 LXC。
注意
就像任何复杂的系统一样,LXC 可能不会与所有硬件架构很好地工作。如果你在启动容器时遇到麻烦,考虑一下可能存在兼容性问题。互联网,一如既往地,应该是一个提供更深入信息的有帮助的资源。
LXC 容器的速度有多快?你很快就会自己看到。但因为他们巧妙地与主机和其他容器共享许多系统资源,它们就像全速运行的独立服务器一样工作,只使用最小的存储空间和内存。
2.3.1. 使用 LXC 入门
在你的 Ubuntu 工作站上安装 LXC?小菜一碟:
# apt update
# apt install lxc
现在在 CentOS 上呢?嗯,蛋糕还在那里,但吃蛋糕需要做更多的工作。这主要是因为 Ubuntu 是为 Ubuntu 和 Debian 构建的。无论如何,在 CentOS 上试一试吧,但我不能保证成功。你首先需要添加一个新的仓库,企业 Linux 的额外包(EPEL),然后安装 LXC 以及一些依赖项:
# yum install epel-release
# yum install lxc lxc-templates libcap-devel \ *1*
libcgroup busybox wget bridge-utils lxc-extra libvirt
- 1 反斜杠字符(\)可以方便地将长命令拆分成多个行在命令行上。**
就这样。你已经准备好开始工作了。基本的 LXC 技能集实际上非常简单。我将向你展示你需要的三四个命令来让它全部工作,然后一个内部提示,一旦你理解了 LXC 是如何组织自己的,它将让你大吃一惊。
2.3.2. 创建你的第一个容器
为什么不直接跳进去创建你的第一个容器呢?-n参数设置的值将是你将用于容器的名称,而-t告诉 LXC 使用 Ubuntu 模板来构建容器:
# lxc-create -n myContainer -t ubuntu *1*
- 1 创建过程可能需要几分钟才能完成,但你将看到详细的输出,最终在终端显示成功通知。**
注意
你可能会开始看到与相对较新的 LXD 容器管理器相关联的另一个lxc命令集。LXD 仍然在底层使用 LXC 工具,但通过一个稍微不同的界面。例如,使用 LXD,之前的命令将看起来像这样:lxc launch ubuntu:16.04 myContainer。这两个命令集将继续广泛可用。
实际上有很多模板可供选择,正如你可以从/usr/share/lxc/templates/目录的列表中看到的那样:
$ ls /usr/share/lxc/templates/
lxc-alpine lxc-centos lxc-fedora lxc-oracle lxc-sshd
lxc-altlinux lxc-cirros lxc-gentoo lxc-plamo lxc-ubuntu
lxc-archlinux lxc-debian lxc-openmandriva lxc-slackware lxc-ubuntu-cloud
lxc-busybox lxc-download lxc-opensuse lxc-sparclinux
警告
并非所有这些模板都能保证直接使用时就能正常工作。一些是作为实验或正在进行的工作提供的。在 Ubuntu 主机上坚持使用 Ubuntu 模板可能是一个安全的选择。正如我提到的,从历史上看,LXC 在 Ubuntu 主机上总是表现最好。在其他发行版上,你的体验可能会有所不同。
如果你决定创建,比如说,一个 CentOS 容器,那么你应该注意输出中的最后几行,因为它包含了你用于登录的信息:
# lxc-create -n centos_lxc -t centos *1*
[...]
The temporary root password is stored in:
'/var/lib/lxc/centos_lxc/tmp_root_pass' *2*
-
1 在这个例子中,我称容器为 centos_lxc 并使用了 centos 模板。**
-
2 容器的根密码位于以容器命名的目录中。**
你将使用用户名root和 tmp_root_pass 文件中包含的密码进行登录。另一方面,如果你的容器使用的是 Ubuntu 模板,那么你将使用ubuntu作为用户名和密码。当然,如果你打算将这个容器用于任何严肃的事情,你将立即更改密码:
$ passwd
Changing password for ubuntu.
(current) UNIX password:
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
顺便说一下,那个命令实际上是 passwd 而不是 password。我的猜测是 passwd 程序的创建者不喜欢打字。现在使用 lxc-ls --fancy 来检查你的容器状态:
# lxc-ls --fancy
NAME STATE AUTOSTART GROUPS IPV4 IPV6
myContainer STOPPED 0 - - -
嗯,它确实存在,但显然需要启动。与之前一样,-n 通过名称指定你想要启动的容器。-d 代表分离,意味着你不想在容器启动时自动进入交互式会话。交互式会话没有问题:事实上,我的许多好朋友都是交互式会话。但在这个情况下,不使用 -d 运行 lxc-start 命令意味着唯一的退出方式是关闭容器,这可能不是你想要的:
# lxc-start -d -n myContainer
列出你的容器应该会显示类似以下内容:
# lxc-ls --fancy
NAME STATE AUTOSTART GROUPS IPV4 IPV6
myContainer RUNNING 0 - 10.0.3.142 - *1*
- 1 容器状态现在是正在运行。
这次,容器正在运行,并已被分配了一个 IP 地址(10.0.3.142)。你可以使用这个地址通过安全的 shell 会话登录,但在阅读 第三章 之前不要这样做。现在,你可以使用 lxc-attach 在运行中的容器内启动一个 root shell 会话:
# lxc-attach -n myContainer
root@myContainer:/# *1*
- 1 注意新命令提示符中的信息。
你可能想花几分钟检查一下周围的环境。例如,ip addr 会列出容器的网络接口。在这种情况下,eth0 接口已被分配了一个 IP 地址 10.0.3.142,这与之前通过 lxc-ls --fancy 收到的 IPV4 值相匹配:
root@myContainer:/# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue
state UNKNOWN group default qlen 1
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
10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue
state UP group default qlen 1000 *1*
link/ether 00:16:3e:ab:11:a5 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.0.3.142/24 brd 10.0.3.255 scope global eth0 *2*
valid_lft forever preferred_lft forever
inet6 fe80::216:3eff:feab:11a5/64 scope link
valid_lft forever preferred_lft forever
-
1 在这个例子中,eth0 是容器的主要网络接口的指定名称。
-
2 容器的 IP 地址(10.0.3.142)和 CIDR 子网掩码(/24)
当你完成对新容器的探索后,你可以运行 exit 登出,同时保持容器运行
root@myContainer:/# exit
exit
或者使用 shutdown -h now 关闭容器。但在你这样做之前,让我们了解一下 LXC 容器有多快。我在 shutdown 前添加的 -h 标志代表 停止。如果我用 r 代替,那么容器不会完全关闭,而是会重新启动。让我们运行 reboot 并立即尝试再次登录,看看容器恢复需要多长时间:
root@myContainer:/# shutdown -r now
# lxc-attach -n myContainer
那个过程怎么样?我敢打赌,在你设法重新输入 lxc-attach 命令的时候,myContainer 已经醒来并准备好行动了。你知道在 Bash 中按上箭头键会将上一条命令填充到命令行中吗?使用这个方法会让请求登录更快。在我的情况下,没有明显的延迟。容器关闭并完全重启不到 2 秒!
注意
LXC 容器对系统资源也很友好。与我在 VirtualBox 虚拟机上的经验不同,那里同时运行三个虚拟机就已经开始严重影响我的 8 GB 主机工作站性能,而我可以启动各种 LXC 容器而不会出现任何减速。
你说什么?那个我承诺给你的内部小贴士?太好了。我看得出你在认真听。好吧,回到主机机器上的终端(与容器相对),您需要使用 sudo su 打开管理员 shell。从现在开始,直到您输入 exit,您将一直处于 sudo 状态:
$ sudo su
[sudo] password for username:
#
现在切换到 /var/lib/lxc/ 目录,并列出其内容。您应该会看到一个以您的容器命名的目录。如果您在系统上还有其他容器,它们也会有它们自己的目录:
# cd /var/lib/lxc
# ls
myContainer
移动到您的容器目录,并列出其内容。您会看到一个名为 config 的文件和一个名为 rootfs 的目录(fs 代表文件系统):
# cd myContainer
# ls
config rootfs
随意查看 config:这是设置容器基本环境值的地方。一旦您对 LXC 的工作方式更加熟悉,您可能会想使用这个文件来调整容器的行为。但真正想展示给您的是 rootfs 目录:
# cd rootfs
# ls
bin dev home lib64 mnt proc run srv tmp var
boot etc lib media opt root sbin sys usr
那些填充 rootfs 的所有子目录,您看起来熟悉吗?它们都是 Linux 文件系统层次结构标准(FHS)的一部分。这是容器的根 (/) 目录,但位于主机文件系统中。只要您在主机上有管理员权限,您就可以浏览这些目录并编辑您想要的任何文件——即使容器没有运行。
您将能够使用这个访问权限做各种事情,但这里有一个可能有一天能救您(职业)一命的事情。假设您在容器上锁定了自己。现在没有任何东西阻止您在文件系统中导航,修复您搞砸的配置文件,并回到工作中。说吧,告诉我这不是很酷。但还有更好的。
的确,自从几年前的技术从 LXC 的阴影下走出来后,Docker 生态系统已经获得了许多层级的特性和复杂性。然而,在底层,它仍然建立在 LXC 熟悉者会立即认出的基本结构范式之上。这意味着,如果您倾向于测试本十年增长最快的虚拟化技术,您已经有所准备了。
摘要
-
虚拟机管理程序如 VirtualBox 提供了一个环境,虚拟操作系统可以安全地访问硬件资源,而轻量级容器则共享主机的软件内核。
-
Linux 软件包管理器如 APT 和 RPM(Yum)通过定期更新的索引来监督从精选的在线仓库安装和管理软件,该索引反映了远程仓库的状态。
-
在 VirtualBox 中启动虚拟机需要定义其虚拟硬件环境,下载操作系统镜像,并在虚拟机上安装操作系统。
-
您可以从命令行轻松克隆、共享和管理 VirtualBox 虚拟机。
-
LXC 容器是基于预定义的、基于发行版的模板构建的。
-
LXC 数据存储在宿主文件系统中,这使得管理容器变得容易。
关键术语
-
虚拟化 是在多个进程之间逻辑共享计算、存储和网络资源,使每个进程都能像独立物理计算机一样运行。
-
虚拟化程序 是在宿主机器上运行的软件,它将系统资源暴露给客户层,允许启动和管理全栈客户虚拟机。
-
容器 是一种虚拟机,它不是全栈,而是位于(并共享)宿主机的核心操作系统内核之上。根据短期需求,容器非常容易启动和终止。
-
VirtualBox 中的动态分配虚拟驱动器只占用物理驱动器上与虚拟机实际使用量相当的空间。相比之下,固定大小的磁盘无论数据量如何,都会占用最大空间。
-
软件仓库 是可以存储数字资源的位置。仓库对于软件包的协作和分发特别有用。
安全最佳实践
-
与手动安装和维护相比,允许官方软件包管理器在您的 Linux 系统上安装和维护软件是首选。在线仓库更加安全,下载是正确加密的。
-
总是扫描下载文件的校验和哈希值与正确的哈希字符串进行对比,这不仅因为软件包在下载过程中可能会被损坏,还因为它们有时也可能被中间人攻击者替换。
命令行回顾
-
apt install virtualbox使用 APT 从远程仓库安装软件包。 -
dpkg -i skypeforlinux-64.deb直接在 Ubuntu 机器上安装下载的 Debian 软件包。 -
wget https://example.com/document-to-download使用 wget 命令行程序下载文件。 -
dnf update、yum update或apt update将本地软件索引与在线仓库中可用的内容同步。 -
shasum ubuntu-16.04.2-server-amd64.iso计算下载文件的校验和以确认其与提供的值匹配。这意味着内容在传输过程中没有被损坏。 -
vboxmanage clonevm Kali-Linux-template --name newkali使用 vboxmanage 工具克隆现有的虚拟机。 -
lxc-start -d -n myContainer启动现有的 LXC 容器。 -
ip addr显示系统每个网络接口的信息(包括它们的 IP 地址)。 -
exit命令离开 shell 会话而不会关闭机器。
测试自己
1
以下哪项是容器和虚拟化共同拥有的特性?
- 它们都允许虚拟机独立于宿主操作系统运行。
- 它们都依赖于宿主机的内核进行基本操作。
- 它们都允许极其轻量级的虚拟机。
- 它们都允许极其高效地使用硬件资源。
2
以下哪项不是 Linux 软件包管理器的责任?
- 将本地索引与远程仓库同步。
- 扫描已安装的软件以查找恶意软件。
- 应用已安装软件的更新。
- 确保所有软件包依赖都已安装。
3
你会使用以下哪个命令直接在 Ubuntu 系统上安装下载的软件包?
dpkg -idnf --installapt installyum -i4
在 VirtualBox 上创建虚拟机时,以下哪个步骤是第一步?
- 选择硬盘文件类型。
- 在动态分配和固定大小之间进行选择。
- 从驱动器中移除虚拟 DVD。
- 配置网络接口。
5
以下哪种格式可以用于操作系统镜像?
- VDI
- VMI
- ISO
- VMDK
6
你会使用以下哪个命令将虚拟机保存为 .OVA 格式的文件?
vboxmanage exportvboxmanage clonevmvboxmanage importvboxmanage clone-ova7
以下哪个 LXC 命令行标志可以在不自动打开新 shell 会话的情况下启动容器?
lxc-start -tlxc-start -alxc-start -dlxc-start -n8
默认情况下,你将在以下哪个目录中找到容器的文件系统?
- /usr/share/lxc/
- /etc/share/lxc/
- /usr/lib/lxc/
- /var/lib/lxc/
答案键
1.
d
2.
b
3.
a
4.
a
5.
c
6.
a
7.
c
8.
d
第三章. 远程连接:安全访问网络机器
本章涵盖
-
加密和安全的远程连接
-
使用 systemd 进行 Linux 系统进程管理
-
额外安全和方便的无密码 SSH 访问
-
使用 SCP 在远程位置之间安全地复制文件
-
通过 SSH 连接使用远程图形程序
他们说,一半的乐趣在于到达那里。好吧,当谈到在分布式计算世界中工作时,无法访问你的服务器和远程资源几乎是一个拦路虎。由于当今的大部分工作量都是由你在上一章中看到的虚拟机承担的,而且你不能只是走到虚拟服务器前,按下电源按钮,然后登录,你需要其他访问途径。欢迎来到安全壳(SSH)的世界。
3.1. 加密的重要性
在最初,无论如何,网络登录连接都是通过 Telnet 实现的。Telnet 协议速度快且可靠,在一个由较小、较简单的网络组成的纯真世界中,它完全能够满足需求。当时,Telnet 会话发送数据包不加密的事实并不是什么大问题。
然而,我听说,在过去的几十年里,事情已经有所变化。如今所有酷孩子都在玩的这个互联网事物比以前要大得多,网络管理员也不再都互相认识。显然,安全已经成为一些热烈讨论的主题。换句话说,如果你使用 Telnet 在不受保护的网络中传输包含密码和个人信息的私有数据,那么你应该假设它已经不再私密。事实上,任何使用免费可用的数据包嗅探软件(如 Wireshark)的人都可以轻松读取你发送和接收的所有内容。
由于每个人都在公共网络上定期传输敏感数据,管理员该怎么办呢?解决方案是加密传输的数据。但加密究竟是什么呢?
为了保护数据隐私,即使数据落入错误的手中,安全软件可以使用所谓的加密密钥,这是一个包含随机字符序列的小文件。如图 3.1 所示,该密钥可以作为加密算法的一部分应用,将可读的纯文本数据转换为几乎无法辨认的乱码。至少在应用密钥之前是这样的。在加密文件的版本上使用密钥将乱码转换回原始形式。只要你和你的信任朋友是唯一拥有密钥的人,其他人就不应该能够理解数据,即使它被截获。
图 3.1. 用于加密和解密纯文本消息内容的私有/公开密钥对。此图说明了对称加密设计。

当你登录到远程服务器时,你做的只是让包含会话信息的数据包在两台计算机之间来回发送。安全通信的技巧是在传输之前快速加密每个数据包,然后在另一端快速解密它们。SSH 网络协议做得如此之快、如此之隐蔽,以至于已经习惯通过 Telnet 会话连接的人不会看到任何区别。
SSH 是在 1990 年代设计的一种简单方式,用于 UNIX 类似操作系统中安全地加密远程登录过程中传输的数据。现在,OpenSSH 协议的实现非常流行,以至于微软最近将其原生地提供给 Windows。
3.2. 开始使用 OpenSSH
在本节中,你将检查 OpenSSH 是否已安装并激活在你的机器上。然后,如果需要,你将安装它。因为测试包的激活状态需要了解现代 Linux 发行版如何管理进程,你还将进入 systemd 的世界。所有这些都完成后,你将使用 OpenSSH 在远程服务器上打开登录会话。
如果尚未安装,从 Ubuntu 或 Debian 机器上运行 apt install openssh-server 将会为你提供所需的所有软件。但许多 Linux 发行版的版本默认至少包含最小化的 SSH 功能。为了找出你机器下(至少在基于 Debian/Ubuntu 的机器上)有什么,你可以使用包管理器 dpkg。
dpkg 命令行工具管理并查询属于高级包工具 (APT) 系统的软件包。使用 -s 标志和包名称运行 dpkg 会返回当前已安装和更新状态。如果包已经安装(如这个 gedit 示例所示),输出将类似于以下内容:
$ dpkg -s gedit *1*
Package: gedit
Status: install ok installed *2*
Priority: optional
Section: gnome
Installed-Size: 1732
Maintainer: Ubuntu Desktop Team <ubuntu-desktop@
lists.ubuntu.com>
Architecture: amd64
Version: 3.18.3-0ubuntu4
Replaces: gedit-common (<< 3.18.1-1ubuntu1)
Depends: python3:any (>= 3.3.2-2~), libatk1.0-0
(>= 1.12.4) *3*
[...]
-
1 gedit 包的 dpkg -s 输出示例
-
2 包状态
-
3 许多依赖包中的两个
注意
在 第二章 中,你看到可以使用 apt search packagename 搜索尚未安装的可用包。
如 图 3.2 所示,当你登录到远程计算机时,你的本地 PC 正在充当远程服务器的客户端,因此你会使用 openssh-client 包。然而,你登录到的远程服务器上的操作系统正在充当 shell 会话的主机,因此它必须运行 openssh-server 包。
图 3.2. 通过加密的 SSH 连接登录到远程服务器

你可以运行 dpkg -s openssh-client 或 dpkg -s openssh-server 来确认你的机器上安装了正确的包。因为它们被构建来托管远程 shell 会话,Linux 容器默认总是安装完整的套件。
服务器版本还包括客户端包中包含的所有工具。这意味着在安装了 openssh-server 包的机器上工作的某人也将能够通过 SSH 登录到其他服务器。因此,如果客户端包尚未安装到您的机器上,安装服务器包将覆盖您可能需要的任何内容。
另一方面,安全最佳实践教导我们只将进入我们基础设施的访问路径限制在绝对必要的内容。如果您认为您不需要登录到您的台式机或笔记本电脑,那么只需安装 openssh-client:
# apt install openssh-client
虽然软件包已经正确安装,但这并不意味着您可以立即使用它。有时配置文件默认设置为不活动。您将在阅读本书的过程中看到大量的设置配置示例,您将在本章稍后查看 OpenSSH 配置文件。但还有另一个常见的理由,Linux 程序可能无法为您工作——它没有运行。您可以使用 systemctl status 来找出 SSH 是否在您的机器上运行:
$ systemctl status ssh
? ssh.service - OpenBSD Secure Shell server
Loaded: loaded (/lib/systemd/system/ssh.service;
enabled; vendor preset: enabled)
Active: active (running) since Mon 2017-05-15 12:37:18
UTC; 4h 47min ago *1*
Main PID: 280 (sshd) *2*
Tasks: 8
Memory: 10.1M
CPU: 1.322s
CGroup: /system.slice/ssh.service
280 /usr/sbin/sshd -D
894 sshd: ubuntu [priv]
903 sshd: ubuntu@pts/4
904 -bash
1612 bash
1628 sudo systemctl status ssh
1629 systemctl status ssh
[...]
-
1 SSH 目前处于活动状态。
-
2 分配给 SSH 的进程 ID(PID)(在本例中为 280)
如您从输出中的“Active”行所看到的那样,一切正常。如果您确实需要自己启动它,那么您将再次使用 systemctl,但这次用 start 代替 status。
对您的新玩具感到无聊?使用 systemctl stop 可以整洁地将其收起来:
# systemctl stop ssh
您可以使用 systemctl enable ssh 命令强制一个进程(如 SSH)在系统启动时自动加载,或者使用 systemctl disable ssh 命令使其在启动时不加载。此代码片段启用了 SSH:
# systemctl enable ssh
systemctl 这个家伙看起来很友好,但你几乎还没有机会认识他。现在 OpenSSH 正在等待我们,但我会在本章末尾更深入地解释进程管理。
3.3. 使用 SSH 登录远程服务器
启动远程会话比你想象的要简单得多。确保你有一台运行着 openssh-server 并可以网络访问的第二台计算机。例如,你可以像上一章那样启动一个 LXC 容器。
现在找到该计算机的 IP 地址。如果您使用的是 LXC 容器,它可以通过 lxc-ls --fancy 命令提供您所需的一切。以下是一个示例,显示一个名为 test 的未运行容器和一个名为 base 的运行容器,使用 IP 地址 10.0.3.144:
# lxc-ls --fancy *1*
[sudo] password for ubuntu:
NAME STATE AUTOSTART GROUPS IPV4 IPV6 *2*
test STOPPED 0 - - -
base RUNNING 1 - 10.0.3.144 -
-
1 列出 LXC 容器和其状态详情的命令
-
2 列表头
或者,如果您恰好登录到您的服务器,您可以使用 ip addr 命令获取其公网 IP 地址,这将输出一个相当糟糕的字符杂乱列表,列出所有本地网络接口。它看起来像这样:
$ ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue
state UNKNOWN group default qlen 1
link/loopback 00:00:00:00:00 brd 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
8: eth0@if9: <BROADCAST,MULTICAST,UP,LOWER_UP> *1*
mtu 1500
qdisc noqueue state UP group default qlen 1000
link/ether 00:16:3e:ab:11:a5 brd
ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.0.3.144/24 brd 10.0.3.255 scope *2*
global eth0
valid_lft forever preferred_lft forever
inet6 fe80::216:3eff:feab:11a5/64 scope link
valid_lft forever preferred_lft forever
-
1 公共网络接口(在本例中,为 eth0)
-
2 显示接口公网 IP 地址的 inet 行
在这种情况下,接口中编号为 8 的 inet 行是我们主要关注的。它显示了一个 IP 地址为 10.0.3.144。
拿到这些信息后,为了连接,你需要使用ssh命令,并指定你将用于登录的服务器上的账户名称和 IP 地址。如果你是第一次从你的 PC 访问服务器,那么你将需要通过输入yes来确认服务器 OpenSSH 程序发送回的信息的真实性。(顺便说一句,是yes,而不是仅仅字母y。)最后,你将输入你指定的服务器账户的密码(在我的例子中是ubuntu),然后你就可以登录了:
$ ssh ubuntu@10.0.3.144
The authenticity of host '10.0.3.144 (10.0.3.144)' can't be established.
ECDSA key fingerprint is SHA256:BPwiWLii7e+wPhFeLxJbYDjW53SgiBvZermGT9Hqck.
Are you sure you want to continue
connecting (yes/no)? yes *1*
Warning: Permanently added '10.0.3.144' (ECDSA) to the list of known hosts.
ubuntu@10.0.3.144's password: *2*
-
1. 确认请求
-
2. 输入你在远程服务器上的账户密码。
没有达到你预期的效果?看起来你将有一个极好的学习体验!你很可能会遇到的最常见问题是网络连接问题,所以为什么不偷偷看看第十四章呢?不过,现在,使用ping来测试你的两台计算机是否能够相互看到并通信。假设你从你的本地 PC 运行这个命令,并使用 IP 10.0.3.144 测试与远程服务器的连接性,成功的 ping 将看起来像这样:
$ ping 10.0.3.144
PING 10.0.3.144 (10.0.3.144) 56(84) bytes of data.
64 bytes from 10.0.3.144: icmp_seq=1
ttl=64 time=0.063 ms *1*
64 bytes from 10.0.3.144: icmp_seq=2 ttl=64 time=0.068 ms
64 bytes from 10.0.3.144: icmp_seq=3 ttl=64 time=0.072 ms
64 bytes from 10.0.3.144: icmp_seq=4
ttl=64 time=0.070 ms *2*
-
1. 对 ping 请求成功响应的记录
-
2. 你可以通过按 Ctrl-c 来停止 ping 请求并重新控制命令行。
失败将看起来像以下这样。为了说明,我 ping 了一个未使用的 IP 地址:
$ ping 10.0.3.145
PING 10.0.3.145 (10.0.3.145) 56(84) bytes of data.
From 10.0.3.1 icmp_seq=1
Destination Host Unreachable *1*
From 10.0.3.1 icmp_seq=1 Destination Host Unreachable
- 1. 对 ping 请求未成功响应的记录
3.4. 无密码 SSH 访问
关于密码,有些令人沮丧的事情。它们几乎从未被正确使用。要么太短,要么容易被猜到,或者只是因为多个账户而过度使用。而且人们似乎会以惊人的频率忘记它们。如果保护你的数据唯一的东西是密码,那么它可能并没有得到很好的保护。
这就是为什么在安全性方面最具信誉的行业参与者——如亚马逊网络服务(AWS)——默认情况下会在他们的云实例上完全禁用密码认证。如果你担心未经授权访问你的服务器,你可能想考虑效仿他们的做法。以下是在 EC2 服务上的 Amazon Linux 实例的/etc/ssh/sshd_config文件中该设置的示例:
# EC2 uses keys for remote access
PasswordAuthentication no
OpenSSH 配置文件
就像 Linux 中的任何其他东西一样,OpenSSH 在机器上的行为很大程度上取决于其纯文本配置文件中的设置。而且,就像大多数其他程序一样,这些配置文件可以在/etc目录层次结构中找到。在这种情况下,它们位于/etc/ssh/目录中。
控制远程客户端如何登录到您的机器的配置文件是 /etc/ssh/sshd_config。另一方面,/etc/ssh/ssh_config 文件控制本机上的用户作为客户端登录到远程主机的方式。除了限制人们通过 SSH 登录到您的系统的方式外,这些文件中的设置还可以用来控制各种行为,包括您将在本章稍后看到,是否允许远程 GUI 访问本地程序。
SSH 密码认证的替代方法是创建一个特殊的密钥对,然后将密钥对的一半(公钥)复制到远程主机,即您最终想要登录的计算机。在连接的两端都有加密密钥的情况下,运行在主机上的 OpenSSH 现在将有一种方式知道你是谁,而无需要求密码。但这并不是说密码在基础设施安全中没有积极作用。实际上,您很快就会看到。理想情况下,您应该创建一个所谓的密码短语,并在使用密钥对之前用它来本地验证自己。
注意
密码短语,就像密码一样,是你选择的一个秘密文本字符串。但密码短语通常会包含空格,并由一系列真实单词组成。像 3Kjsi&*cn@PO 这样的密码相当不错,但像“fully tired cares mound”这样的密码短语可能更好,因为它长度更长,而且相对容易记住。
3.4.1. 生成新的密钥对
一定有不止一种方法可以解决这个问题。但既然所有优秀的系统管理员在训练中都是懒惰的,我会选择需要最少按键的方法。这个选择的一个意外但令人高兴的结果是,我将有机会向您介绍管道字符(|)的更复杂用法。
您将首先使用 ssh-keygen 程序在客户端计算机上创建一个新的公钥/私钥对。您将被要求输入密钥对名称,但除非您已经有一个名为 id_rsa 的密钥对,否则我会按 Enter 键并保留默认设置。正如您之前所看到的,在提示时创建密码短语通常更好,尤其是如果您与他人共享计算机。记住,如果您选择添加密码短语,每次使用密钥时都会提示您输入它。下面是整个过程:
ubuntu@base:~$ ssh-keygen *1*
Generating public/private rsa key pair.
Enter file in which to save the key (/home/ubuntu/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in
/home/ubuntu/.ssh/id_rsa.
Your public key has been saved in
/home/ubuntu/.ssh/id_rsa.pub. *2*
The key fingerprint is: *3*
SHA256:1wQzEnybUSOFpYEvmbxVPZjy1uGAV6Tnn5m1w0qD5T8 ubuntu@base
The key's randomart image is: *4*
+---[RSA 2048]----+
| .oo**=*o |
| o.*B*o+ |
| . =.Oo+.o |
| = =oooo |
| S+..... .|
| .. + ..*|
| . + B.|
| . +E.|
| . ..|
+----[SHA256]-----+
-
1 显示我的本地客户端名称,base
-
2 新密钥对的位置和名称
-
3 有助于防止中间人攻击
-
4 密钥的随机艺术(randomart),一种视觉提示,也有助于防止中间人攻击。
好吧,现在你已经是拥有一对闪亮的新 RSA 加密密钥的所有者了。继续使用ls -l来显示.ssh/目录的长列表。注意,有两个文件名为 id_rsa,但只有一个具有.pub 文件扩展名。这个文件是这对密钥的公共部分,是你最终需要复制到将成为会话主机的远程机器上的文件:
ubuntu@base:~$ ls -l .ssh
total 12
-rw------- 1 ubuntu ubuntu 1675 Jun 5 22:47 id_rsa
-rw-r--r-- 1 ubuntu ubuntu 393 Jun 5 22:47 id_rsa.pub
你应该使用哪种算法?
除了 RSA(由首先描述它的三位研究者的姓氏缩写而成:Ron Rivest, Adi Shamir, 和 Leonard Adleman)之外,OpenSSH 还支持 ECDSA 和 ED25519 签名算法。你会在默认的 RSA 和 ECDSA 以及 ED25519 之间发现一些相当晦涩的技术差异,它们的优势在于基于椭圆曲线。但所有这些都被认为是相当安全的。关于 ECDSA 和 ED25519 需要注意的一点是,它们可能还没有被一些较旧的实现完全支持。
你不应再假设所有 OpenSSH 的实现都支持 DSA。由于对其起源的怀疑,DSA 在任何情况下都被广泛避免。
3.4.2. 通过网络复制公钥
无密码 SSH 访问直到你将公钥复制到主机上才会生效。正如你在图 3.3 中看到的,密钥对通常是在客户端计算机上创建的。这是因为私钥应该是这样的:私有的。尽可能避免不必要地移动它,并使其暴露在不友好的目光之下。
图 3.3. 密钥对的公钥必须移动到主机 PC 上,而私钥则保持在客户端。

一旦创建,你可以将公钥移动到主机计算机上的.ssh/authorized_keys 文件中。这样,运行在主机上的 OpenSSH 软件将能够验证由客户端上的私钥创建的加密消息的真实性。一旦消息被验证,SSH 会话将被允许开始。
你首先需要做的是确定你将登录到主机上的哪个用户账户。在我的情况下,它将是名为 ubuntu 的账户。密钥需要复制到名为.ssh/的目录中,该目录位于/home/ubuntu/下。如果它还没有在那里,你应该现在使用mkdir来创建它。
首先,我将向你介绍一个酷的快捷方式:要运行单个命令,你实际上不需要在远程主机上打开完整的 SSH 会话。相反,你可以将你的命令附加到常规的ssh语法中,如下所示:
ubuntu@base:~$ ssh ubuntu@10.0.3.142 mkdir -p .ssh
ubuntu@10.0.3.142's password:
你仍然需要为远程主机提供密码。但一旦完成,你将在主机上的/home/ubuntu/目录下拥有一个.ssh/目录。
为了让你更容易阅读,我使用反斜杠字符(\)将下一个命令拆分为三行,这告诉 Bash 将下一行作为当前行的一部分读取。确保反斜杠后面没有字符(包括空格)。这肯定会给你带来麻烦:
ubuntu@base:~$ cat .ssh/id_rsa.pub \ *1*
| ssh ubuntu@10.0.3.142 \ *2*
"cat >> .ssh/authorized_keys" *3*
ubuntu@10.0.3.142's password:
-
1 cat 命令读取 id_rsa.pub 文件的内容。
-
2 文本被传递到 ssh 命令中。
-
3 文本被追加到名为 authorized_keys 的文件中。
那个单一的多行命令将使用 cat 来读取 id_rsa.pub 文件中的所有文本,并将其存储在内存中。然后,它将通过远程主机计算机上的 SSH 登录将文本传递过去。最后,它再次读取文本,这次是在主机计算机上,并将其追加到名为 authorized_keys 的文件中。如果该文件尚不存在,>>(追加工具)将创建它。如果已存在同名文件,文本将被添加到文件中的任何内容。
就这些。你已经准备好了。这次,当你运行相同的旧 ssh 命令时,不需要输入密码:
ubuntu@base:~$ ssh ubuntu@10.0.3.142 *1*
Welcome to Ubuntu 16.04.1 LTS (GNU/Linux 4.4.0-78-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/advantage
Last login: Tue May 16 15:14:37 2017 from 10.0.3.1
ubuntu@tester:~$ *2*
-
1 登录过程不需要密码请求。
-
2 新的命令提示符表明你在一台不同的计算机上。
3.4.3. 使用多个加密密钥
在某些情况下(比如需要登录到在亚马逊 EC2 服务上运行的虚拟机实例),你需要指定为给定会话使用哪个密钥对。一旦你开始收集用于不同主机的密钥,这肯定会发生。为了告诉 OpenSSH 你想要哪个密钥,你添加 -i 标志,后跟私钥文件的完整名称和位置:
ssh -i .ssh/mykey.pem ubuntu@10.0.3.142
注意那个例子中的 .pem 文件扩展名吗?这意味着密钥是以一种常用于访问各种虚拟机(包括亚马逊 EC2 实例)的格式保存的。
3.5. 使用 SCP 安全地复制文件
我确信你记得 cp 命令是如何在文件系统中从一个地方复制文件和目录到另一个地方的。好吧,至少在理论上,没有理由说这不能同样适用于跨网络复制文件。除非那将是完全疯狂的——文件内容将暴露给那天偶然在网络周围的人,或者任何后来偶然浏览网络日志数据的人。
忘掉那个想法,除非你在那个 cp 命令前加上一个 s 表示 secure。SCP 程序使用 SSH 协议进行文件传输,依靠所有相同的密钥、密码和口令短语,来复制各种类型的文件。假设你知道你之前工作的远程主机上已经有一个 .ssh/ 目录,以下是你可以如何将公钥(id_rsa.pub)传输到远程主机,并将其重命名为 authorized_keys:
ubuntu@base:~$ scp .ssh/id_rsa.pub \
ubuntu@10.0.3.142:/home/ubuntu/.ssh/authorized_keys
警告
如果在那个目录中已经存在一个 authorized_keys 文件,这个操作将会覆盖它,破坏任何现有的内容。此外,你只能复制或保存文件,如果使用的用户账户具有适当的权限。因此,如果你的用户没有 root 权限,不要尝试将文件保存到远程机器上的,例如,/etc/目录。在你问之前,以 root 用户登录 SSH 会话通常是一个很大的安全禁忌。
你顺便可以把远程文件复制到你的本地机器上。以下示例展示了如何从一个 AWS EC2 实例(用虚构的 IP 地址表示)复制文件到指定的本地目录:
$ scp -i mykey.pem mylogin@54.7.61.201:/home/mylogin/backup-file.tar.gz \
./backups/january/ *1*
- 1 将文件保存到相对于当前工作目录的目录位置
你到目前为止使用的命令已经展示了几个重要的工具。但我应该提到,还有第三种(也是官方的)安全地将你的密钥复制到远程主机的方法——专门为此目的编写的程序叫做 ssh-copy-id:
$ ssh-copy-id -i .ssh/id_rsa.pub ubuntu@10.0.3.142 *1*
- 1 自动将公钥复制到远程主机上的适当位置
SSH 会话的好处是,不受 GUI 层级的负担,它们速度快且效率高。但如果需要在远程主机上运行的程序是图形化的,这可能会成为一个问题。下一节将为你解决这个问题。
3.6. 使用 SSH 连接远程图形程序
假设你正在尝试支持一个在远程位置的用户新报告了一个桌面软件(如 LibreOffice)的问题。如果你觉得能够启动并运行程序有助于诊断和解决问题,那么可以使用图形会话(通过 Linux X 窗口管理器)在 SSH 上完成。
说了这么多,不要期望出现奇迹。使用带有-X标志的ssh,使用所谓的X11 转发,将允许你在客户端的桌面上加载基于主机机器的程序。根据许多因素,包括你的网络连接质量,你的结果可能不会达到你的期望。这对于资源密集型程序(如 LibreOffice)尤其如此。尽管如此,它总是值得一试。忍受一点低带宽的痛苦可能仍然比开车两小时去客户办公室要好。
还有一件事:不要在服务器上尝试这个。在大多数情况下,服务器或虚拟机(如 LXC 或 Docker 容器)上安装的操作系统版本带有很少或没有图形功能。如果你绝对必须这样做,你可以安装桌面包来升级它。在 Ubuntu 机器上,它看起来像这样:
# apt update
# apt install ubuntu-desktop
在所有免责声明都讲完之后,我认为是时候看看这到底是如何工作的了。首先,打开主机机器上的 sshd_config 文件(你想要运行程序的那个机器)。你需要确保 X11Forwarding 行具有值yes(尽管,出于安全考虑,可能没有必要让它保持这种方式太长时间):
# nano /etc/ssh/sshd_config
X11Forwarding yes *1*
- 1 将行编辑成这个样子。
在客户端机器上的 ssh_config 文件中也有类似的行,也需要正确设置:
# nano /etc/ssh/ssh_config
ForwardX11 yes *1*
- 1 修改这一行,使其看起来完全像这样。
由于你编辑了配置文件,你需要在两台机器上重新启动 SSH 以确保你的更改生效:
# systemctl restart ssh
你现在可以开始了。要启动一个图形化的会话,将-X标志添加到你的ssh命令中:
$ ssh -X ubuntu@10.0.3.142
你将看到常规的命令提示符,但现在你可以运行一个命令来启动一个图形程序。尝试做一些小事情。这应该在 Ubuntu 系统上工作:
$ gnome-mines
太棒了!你成功地在本地桌面的窗口中运行了一个远程程序。
OpenSSH 比你已经看到的核心理念提供了更多的价值。一旦你建立了 SSH 连接,你就可以施展各种技巧。尝试在远程机器上挂载本地文件系统或目录,允许远程用户无缝访问你的文件。或者,通过 SSH 隧道技术的魔力,使用端口转发来允许远程 HTTP 服务的安全、私有使用。
3.7. Linux 进程管理
正如承诺的那样,现在我将重新审视 Linux 进程管理,这样你就可以正确理解像 OpenSSH 这样的程序是如何处理的。了解这些是如何工作的可以使长期的一般管理和故障排除更加有效。但如果你现在不想深入研究这样一个复杂的话题,你可以安全地跳过本章的其余部分。你应该没有问题跟随本书的其余部分。
systemctl 究竟是什么,它实际上在做什么?要正确回答这些问题,你需要稍微思考一下 Linux 是如何管理系统进程的。而且,因为结识新朋友总是很愉快,你还将了解一些进程跟踪工具,使理解事物的工作方式更加容易。
软件,正如你肯定已经知道的,是包含控制计算机硬件指令的编程代码,代表人类用户。进程是运行软件程序的一个实例。操作系统是组织和管理工作这些实例/进程的工具,以有效地使用计算机的硬件资源。
在一个复杂的、多进程、多用户的操作环境中组织和管理工作并不简单。为了使其工作,你需要某种交通警察来紧密控制许多移动部件(图 3.4)。让我向你介绍 systemctl。
图 3.4. 许多系统服务的可用性和响应性由 systemd 的 systemctl 进程管理器管理。

3.7.1. 使用 ps 命令查看进程
让我们拿出一个电子显微镜,看看我们是否能在其自然栖息地中找到一个进程。在终端中输入以下命令。它将在后台(sleep)运行 10 秒钟然后停止。然而,当它在运行时,输入ps:
$ for i in {1..10}; do sleep 1; done &
[1] 19829 *1*
$ ps
PID TTY TIME CMD
19522 pts/17 00:00:00 bash
19829 pts/17 00:00:00 bash
19832 pts/17 00:00:00 sleep *2*
19833 pts/17 00:00:00 ps *3*
-
1 背景运行命令的 PID*
-
2 由原始命令启动的 sleep 进程*
-
3 列出运行进程的 ps 命令*
你会看到由该命令产生的两个正在运行进程的记录,以及它们的 PID:在我的例子中是 19829 和 19832。如果你等待 10 秒后再次运行 ps 命令,你会看到这两个进程不再运行。你也应该看到 sleep 命令成功完成的报告:
$ ps
PID TTY TIME CMD
19522 pts/17 00:00:00 bash
20549 pts/17 00:00:00 ps
[1]+ Done for i in {1..10};
do
sleep 1;
done
通常,如果你只输入 ps 并运行它,你可能会只得到两个结果。第一个是一个名为 bash 的进程,代表你的当前 shell 会话使用的 Bash 命令解释器,以及最近的命令(当然是 ps)。但通过查看分配给 bash 的 PID(以下示例中的 7447),你知道在你的系统上已经有成千上万的进程正在努力工作。这些进程是由从 init 进程本身回溯的所有父 shell 生成的:
$ ps
PID TTY TIME CMD
7447 pts/3 00:00:00 bash
8041 pts/3 00:00:00 ps
在 Ubuntu 机器上,当 Linux 计算机启动时,第一个唤醒并启动其他所有进程的进程被称为 init。很快你就会发现,这个名字可能会误导,这就是为什么在 CentOS 上第一个进程有不同的名字。你可以通过运行以下 ps 命令(就像这里打印的那样)亲自看到 init 是第一个进程。我将在一分钟内解释细节:
$ ps -ef | grep init
root 1 0 0 12:36 ? 00:00:00 /sbin/init *1*
ubuntu 1406 904 0 16:26 pts/4 00:00:00 grep --color=auto init
- 1 负责进程 1 的文件*
输出的最右边一列(第一行显示 /sbin/init)表示进程背后的文件的位置和名称。在这种情况下,它是一个位于 /sbin/ 目录下的名为 init 的文件。这一行的最左边一列包含单词 root,告诉你这个进程的所有者是 root 用户。目前唯一其他值得关注的信息是数字 1,这是 init 进程的 PID。你获得 PID 1 的唯一方法是在其他人之前到达那里。
在继续之前,花更多的时间与 ps 命令打交道是值得的。正如你所看到的,ps 命令显示有关活动进程的信息。通常,访问与进程相关的信息对于正确规划和排除系统行为故障非常重要。你可以预期会经常且早期使用 ps 命令。
将 -e 参数添加到 ps 命令中,就像你之前做的那样,不仅会返回你当前子 shell 中正在运行的所有进程,还会返回从所有父 shell 回溯到 init 的所有进程。
注意
父 shell 是一个可以从中启动新的(子)shell 并通过它运行程序的环境。你可以把你的 GUI 桌面会话看作是一个 shell,你打开以获取命令行的终端是其子 shell。顶级 shell(外祖父?)是在 Linux 启动时首先运行的 shell。
如果您想可视化父 shell/进程和子 shell/进程,可以使用pstree命令(添加-p参数以显示每个进程的 PID)。注意第一个进程(分配 PID 1)是 systemd。在较旧的 Linux 版本(例如 Ubuntu 14.04 及之前)中,这将被称为 init:
$ pstree -p *1*
systemd(1)agetty(264) *2*
agetty(266)
agetty(267)
agetty(268)
agetty(269)
apache2(320)apache2(351)
apache2(352)
apache2(353)
apache2(354)
apache2(355)
cron(118)
dbus-daemon(109)
dhclient(204)
dockerd(236)docker-containe(390){docker-containe}(392)
{docker-containe}(404)
{dockerd}(306)
{dockerd}(409)
mysqld(280){mysqld}(325)
{mysqld}(326)
{mysqld}(399)
nmbd(294)
rsyslogd(116){in:imklog}(166)
{in:imuxsock}(165)
{rs:main Q:Reg}(167)
smbd(174)smbd(203)
smbd(313)
sshd(239)sshd(840)sshd(849)bash(850)pstree(15328)
systemd-journal(42)
systemd-logind(108)
-
1 CentOS 用户可能需要安装 psmisc 包才能运行 pstree。
-
2 systemd,顶级父进程
在您的机器上尝试所有这些命令。即使在安静的系统上,您也可能看到成百上千的进程;繁忙的桌面 PC 或服务器可以轻松地有数百甚至数千个。
3.7.2. 与 systemd 一起工作
您刚才看到的那个/sbin/init 文件有些有趣:file是一个古老的 UNIX 程序,它提供了关于文件的内幕信息。如果您用/sbin/init作为参数运行file,您会看到 init 文件实际上不是一个程序,而是指向名为 systemd 的程序的一个符号链接。我们将在第十二章中更详细地讨论符号链接,但在这里您将有机会认识 systemd:
$ file /sbin/init
/sbin/init: symbolic link to /lib/systemd/systemd
经过多年的碎片化和一些激烈的政斗,现在几乎所有 Linux 发行版都使用相同的进程管理器:systemd。它是名为init的进程的替代品,这个进程长期以来一直是所有基于 UNIX 操作系统的启动过程中启动的第一个进程。通过drop-in replacement,我的意思是,即使它完成任务的方式可能相当不同,但对普通观察者来说,systemd 的功能始终如 init。这就是为什么/sbin/init 文件现在只是一个指向 systemd 程序的链接。
这一切都是理论性的,因为您可能永远不会直接通过其/sbin/init 前端以名称调用 systemd 程序。这是因为,如您所见,关键管理任务是由 systemd 代表systemctl处理的。
从技术上讲,systemd 的主要任务是控制单个进程的诞生、生活以及死亡的方式。您之前使用的systemctl命令是执行这些任务的首选工具。但是,有些有争议的是,systemd 开发者将功能扩展得远远超出了传统进程管理的角色,以控制各种系统服务。在新 systemd 的范围内包括日志管理器(journald)、网络管理器(networkd)和设备管理器(正如您所猜想的:udevd)。好奇吗?其中的d代表daemon,即后台系统进程。
您在阅读本书的过程中会遇到至少一些 systemd 工具。我们的下一个目的地将是学习如何管理和,最重要的是,备份文件系统和存档。
摘要
-
加密连接是所有网络通信的关键部分,而 SSH 几乎是行业标准。
-
您可以通过共享密钥对中的公钥来启用无密码 SSH 访问。
-
OpenSSH 软件包还允许安全地复制文件和远程图形会话。
-
在大多数现代 Linux 发行版中,进程通过 systemctl 工具由 systemd 管理。
-
你可以使用
|(管道)字符在命令之间传递数据,并使用grep过滤流数据。
关键术语
-
密码 是一串常规字符,而 口令短语 可以包含空格和标点符号。
-
RSA 是一种流行的加密算法。
-
X11 转发 允许在远程连接上运行图形程序。
-
Linux 的 进程 是与单个运行程序相关的所有持续活动。
-
shell 是一个提供命令行解释器(如 Bash)的终端环境,允许用户执行命令。当你从 Linux 桌面 PC 或笔记本电脑工作的时候,你通常会通过打开一个终端程序(如 GNOME 终端)来访问 shell。
-
父 shell 是一个初始环境,从中可以启动新的子 shell,并通过它运行程序。从所有目的来看,shell 也是一个进程。
安全最佳实践
-
总是在公共网络上运行的远程登录会话中加密。
-
不要仅依赖密码;就像人一样,它们是会出错的。
-
基于密钥、无密码的 SSH 会话比简单的密码登录更可取。
-
不要在公共网络上以纯文本形式传输文件。
命令行回顾
-
dpkg -s openssh-client检查基于 APT 的软件包的状态。 -
systemctl status ssh检查系统进程(systemd)的状态。 -
systemctl start ssh启动一个服务。 -
ip addr列出计算机上的所有网络接口。 -
ssh-keygen生成一个新的 SSH 密钥对。 -
$ cat .ssh/id_rsa.pub | ssh ubuntu@10.0.3.142 "cat >> .ssh/authorized_keys"将本地密钥复制并粘贴到远程机器上。 -
ssh-copy-id -i .ssh/id_rsa.pub ubuntu@10.0.3.142安全地复制加密密钥(推荐和标准)。 -
ssh -i .ssh/mykey.pem ubuntu@10.0.3.142指定特定的密钥对。 -
scp myfile ubuntu@10.0.3.142:/home/ubuntu/myfile安全地将本地文件复制到远程计算机。 -
ssh -X ubuntu@10.0.3.142允许你登录到具有图形功能的远程主机。 -
ps -ef | grep init显示所有当前运行的系统进程,并使用字符串init过滤结果。 -
pstree -p以可视树格式显示所有当前运行的系统进程。
测试自己
1
加密密钥的目的是:
- 建立安全网络连接
- 加密和解密数据包
- 在传输过程中隐藏敏感数据
- 确保数据传输的可靠性
2
你可以使用以下哪个命令来检查服务的状态?
dpkg -s <servicename>systemd status <servicename>systemctl status <servicename>systemctl <servicename> status3
在主机服务器可以接受远程 SSH 登录之前,必须安装以下哪些软件包?
- openssh-server
- ssh-server
- openssh-client
- ssh-client
4
在使用 systemd 的 Linux 发行版中,init 的任务由以下哪个程序执行?
- /lib/systemd/systemd
- /bin/systemd
- /sbin/init
- /bin/init
5
以下哪个服务不是 systemd 服务?
- networkd
- journald
- processd
- udevd
6
对于无密码 SSH 连接,密钥必须放在哪里?
- 主机上的公钥和私钥,客户端上的私钥
- 主机上的公钥和私钥,客户端上的公钥
- 主机上的私钥,客户端上的公钥
- 主机上的公钥,客户端上的私钥
7
SSH 会话中密码的作用是什么?
- 为了验证您的身份给远程的 OpenSSH 程序
- 为了验证您的身份给本地的 OpenSSH 程序
- 为了确定您想使用哪个密钥对
- 为了验证密钥对的状态
8
以下哪个操作会将远程文件复制到您的本地机器的当前目录(假设远程目录和文件都存在)?
scp mylogin@10.0.3.142:/home/mylogin/filename .scp mylogin@10.0.3.142/home/mylogin/filename .scp mylogin@10.0.3.142:/home/mylogin/filenamescp mylogin@10.0.3.142:/home/mylogin/filename ./home/myname/Documents
答案键
1.
b
2.
c
3.
a
4.
a
5.
c
6.
d
7.
b
8.
a
第四章. 归档管理:备份或复制整个文件系统
本章涵盖
-
为什么、是什么以及在哪里归档
-
使用
tar归档文件和文件系统 -
搜索系统文件
-
使用对象权限和所有权来保护文件
-
使用
dd归档整个分区 -
使用
rsync同步远程归档
在本书的前几章中,你学习了如何在 Linux 环境中既安全又高效地操作。你还学会了利用虚拟化的奇妙之处来生成基础工作环境。从现在开始,我将专注于构建和维护你完成实际工作所需的基础设施元素。
在没有良好的备份协议的情况下构建 IT 基础设施,就像抵押你的房子来投资你妹夫的、不会失败的超导聚变发明一样。你知道这种做法不太可能有好结果。但在你能够正确备份文件系统和分区之前,你需要确切了解文件系统和分区是如何工作的。之后呢?有哪些工具可用?何时应该使用每个工具,如果发生灾难,你将如何重新组装一切?请保持关注。
4.1. 为什么需要归档?
在我们讨论为什么之前,先来了解一下什么是 归档?它不过是一个包含一组对象的单个文件:文件、目录,或者两者的组合。将对象捆绑在单个文件中(如图 4.1 所示)有时可以更容易地移动、共享或存储那些可能难以管理和组织的大量对象。
图 4.1. 文件和目录可以捆绑成一个归档文件并保存到文件系统中。

想象一下尝试复制散布在十几个目录和子目录中的几千个文件,以便你的网络另一端的同事也能看到它们。当然,使用正确的命令行语法参数,任何事都可以做到。(还记得第一章(kindle_split_009.xhtml#ch01)中的 cp 吗?还有 -r?)但确保只复制你需要的文件,并且没有意外遗漏任何文件,这可能是一个挑战。当然,你至少需要一次计算所有这些文件,以便构建归档。但一旦你将所有内容都封装在一个归档文件中,跟踪起来就简单多了。所以,归档就是归档。
但归档有归档,哪种归档适合你?这取决于你想要组织哪些类型的文件以及你计划如何使用它们。你可能需要创建目录及其内容的副本,以便你可以轻松地共享或备份它们。为此,tar 可能将成为你的首选冠军。然而,如果你需要一个分区的精确副本,甚至是一个整个硬盘的副本,那么你需要了解 dd。如果你正在寻找一个用于常规系统备份的持续解决方案,那么尝试 rsync 吧。
学习如何使用这三个工具,更重要的是,学习这三个工具可以为你解决哪些实际问题,将是本章剩余部分的重点。在这个过程中,我们将稍微偏离一下,看看如何保护存档中文件在存档生命周期中的权限和所有权属性。最后,我们将窥视一下为什么 Linux 首先使用文件权限和文件所有权。
4.1.1. 压缩
在我们开始之前还有一个注意事项。尽管这两个经常一起使用,但不要将存档与压缩混淆。压缩,如图 4.2 所示,是一种软件工具,它将一种巧妙的算法应用于文件或存档以减少其占用的磁盘空间。当然,当文件被压缩后,它们是不可读的,这就是为什么算法也可以逆向应用以解压缩它们。
图 4.2. 通过消除统计冗余和/或删除文件中不太重要的部分来实现对象压缩

你很快就会看到,将压缩应用于 tar 存档非常简单,如果你计划通过网络传输大型存档,这样做尤其是个好主意。压缩可以显著减少传输时间。
4.1.2. 存档:一些重要考虑事项
你想要创建存档的两个主要原因是为了构建可靠的文件系统镜像和创建高效的数据备份。本节将描述这些目标。
镜像
什么是镜像?还记得你在第二章中用来在虚拟机上安装 Linux 的那些 .ISO 文件吗?这些文件是完整操作系统的镜像,特别组织以便于将包含的文件复制到目标计算机上。
你也可以从活动的工作操作系统(OS)的所有或部分创建镜像,这样你就可以将内容复制粘贴到第二台计算机上。这实际上使得第二台(复制)计算机在其当前状态下成为第一系统的精确克隆。我经常这样做,从失败的硬盘上恢复复杂的安装,而不想在新硬盘上从头开始重建整个系统。当你想要快速向多个用户(如教室中的学生工作站)提供相同的系统设置时,这也非常棒。
注意
根本不要考虑在 Windows 上尝试这些操作。从所有目的和用途来看,Windows 注册表架构使得将安装的操作系统与其原始硬件分离变得不可能。
虽然我们将在本章的剩余部分讨论备份而不是镜像,但请放心。我们用于创建和恢复镜像的工具基本上是相同的,所以无论哪种方式你都会没事的。
数据备份
备份应该是你生活中的一部分。实际上,如果你从不担心你数据的安全,那么要么你是一位禅宗大师,要么你只是没有正确地完成你的工作。可能会有很多可怕的事情发生:
-
硬件会失败——而且通常会在你计划进行大备份之前发生。真的。
-
大拇指笨拙(我指的是笨拙的人)和键盘可能会串通起来破坏配置文件,让你完全无法访问加密系统。拥有一个备用副本可以保住你的工作,甚至可能保住你的生命。
-
存储在像亚马逊网络服务(AWS)这样的云基础设施提供商上的数据可能突然且不可预测地丢失。2014 年,这种情况发生在一个名为 Code Spaces 的公司身上。该公司的 AWS 账户控制台配置不当,被攻击者入侵,他们删除了大部分数据。Code Spaces 是如何恢复的?好吧,你最后一次听说 Code Spaces 是什么时候?
-
可能最可怕的是,你可能会成为勒索软件攻击的受害者,除非你支付一大笔赎金,否则你的所有文件都会被加密或禁用。你有一个可靠且最近的备份吗?你可以自由地告诉攻击者你的想法。
在继续之前,我应该提到,未经测试的数据备份可能实际上不起作用。事实上,有证据表明,几乎一半的时间它们都不会起作用。问题是什么?有很多可能出错的地方:你的备份设备可能有缺陷,存档文件可能已损坏,或者初始备份本身可能无法正确处理你所有的文件。
生成和监控日志消息可以帮助你发现问题,但唯一能够对备份有合理信心的方式是将试验性恢复到匹配的硬件上。这将需要能量、时间和金钱。但确实比替代方案好。我所认识的最好的系统管理员似乎都持有相同的观点:“偏执狂只是开始。”
4.2. 要存档的内容
如果你想要备份的文件不多,而且它们不是很大,你不妨直接将它们传输到它们的存储目的地。使用你在第三章中看到的类似 SCP 程序。以下示例使用 SCP 将我的公钥内容复制到远程机器上的名为 authorized_keys 的文件中:
ubuntu@base:~$ scp .ssh/id_rsa.pub \
ubuntu@10.0.3.142:/home/ubuntu/.ssh/authorized_keys *1*
- 1 覆盖远程授权 _keys 文件当前内容
但如果你想要备份分布在多个目录中的许多文件(例如,一个包含源代码的复杂项目)或甚至整个分区(如你现在运行的操作系统),你需要一个更有力的工具。
虽然我们在第一章中讨论了磁盘分区和伪文件,但如果你想制定某种智能备份策略,你将想要了解它们的外观。假设你正在计划备份包含你公司大型会计数据库的分区;如果你不知道该分区占用了多少空间以及如何找到它,你可能不会走得太远。
让我们从 df 命令开始,它显示 Linux 系统上当前挂载的每个分区,以及其磁盘使用量和在文件系统中的位置。添加 -h 标志将分区大小转换为人类可读的格式,如 GB 或 MB,而不是字节:
$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/sda2 910G 178G 686G 21% / *1*
none 492K 0 492K 0% /dev
tmpfs 3.6G 0 3.6G 0% /dev/shm *2*
tmpfs 3.6G 8.4M 3.6G 1% /run *3*
tmpfs 5.0M 0 5.0M 0% /run/lock
tmpfs 3.6G 0 3.6G 0% /sys/fs/cgroup
-
1 根分区:本系统上唯一的正常分区
-
2 注意磁盘使用量为 0 字节。这(通常)表示一个伪文件系统。
-
3 /run 目录包含在启动期间生成的运行时数据文件。
列出的第一个分区被指定为 /dev/sda2,这意味着它是存储设备 A 的第二个分区,并且它通过伪文件系统目录 /dev/ 表示为一个系统资源。在这种情况下,这恰好是主要的操作系统分区。与系统关联的所有设备都将由 /dev/ 目录中的一个文件表示。(你的会计软件所使用的分区可能会出现在这个列表中,可能使用类似 /dev/sdb1 的方式指定。)
注意
在 LXC 容器上运行 df 命令会显示与 LXC 主机关联的分区。
重要的是要区分真实和 伪 文件系统(文件系统中的文件实际上并没有保存到磁盘上,而是存在于易失性内存中,并在机器关闭时消失)。毕竟,备份代表短暂硬件配置的文件没有意义,而且无论如何,操作系统都会在启动时自动替换真实文件系统。
很容易判断哪些分区用于伪文件:如果文件指定为 tmpfs 并且在“已用”列中报告的字节数为 0,那么你很可能在查看一个临时文件系统而不是正常文件系统。
顺便说一下,那个 df 命令是在 LXC 容器上运行的,这就是为什么只有一个真实分区,即 /。让我们看看它在物理计算机上运行时显示什么:
df -h
Filesystem Size Used Avail Use% Mounted on
udev 3.5G 0 3.5G 0% /dev
tmpfs 724M 1.5M 722M 1% /run
/dev/sda2 910G 178G 686G 21% /
tmpfs 3.6G 549M 3.0G 16% /dev/shm
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 3.6G 0 3.6G 0% /sys/fs/cgroup
/dev/sda1 511M 3.4M 508M 1% /boot/efi *1*
tmpfs 724M 92K 724M 1% /run/user/1000
/dev/sdb1 1.5G 1.5G 0 100% /mnt/UB-16 *2*
-
1 这个分区是在安装期间创建的,以启用 UEFI 启动。
-
2 sdb1 是一个包含 Ubuntu live 启动镜像的 USB 指示棒驱动器。
注意到 /dev/sda1 分区挂载在 /boot/efi 上。这个分区是在原始 Linux 安装期间创建的,以便允许由 UEFI 固件控制的系统启动。UEFI 现在已经很大程度上取代了在系统启动期间用于硬件初始化的旧 BIOS 接口。安装在这个分区上的软件允许 UEFI 与 Linux 系统集成。而 /dev/sdb1 是一个恰好插在我机器后面的 USB 指示棒。
当你处理生产服务器时,你经常会看到像/var/和/usr/这样的目录有单独的分区。这通常是为了更容易地维护敏感数据的完整性和安全性,或者为了保护系统免受来自如/var/log/日志文件的文件膨胀的影响。无论原因如何,对于任何特定的磁盘设计,你都会想要就哪些需要备份以及哪些不需要备份做出明智的决定。
你有时会看到/boot/目录被分配了自己的分区。我个人认为这是一个坏主意,我有伤痕可以证明。问题是新的内核镜像被写入/boot/,随着你的系统升级到新的 Linux 内核版本,存储所有这些镜像所需的磁盘空间会增加。如果你像标准做法那样只分配 500 MB 给引导分区,你将有大约六个月的时间来填满它——到那时更新将失败。在手动删除一些较旧的文件并更新 GRUB 菜单之前,你可能无法完全引导到 Linux。如果你觉得这不是一件有趣的事情,那么请确保你的/boot/目录位于最大的分区中。
4.3. 备份的位置
从操作系统的角度来看,你将存档发送到何处并没有区别。你可以自由选择使用传统的磁带驱动器、USB 挂载的 SATA 存储驱动器、网络附加存储(NAS)、存储区域网络(SAN)或云存储解决方案。更多关于这一点,请参阅我的书籍《一个月午餐时间学会亚马逊网络服务》(Manning,2017 年)。
无论你选择哪种方式,都要确保仔细遵循最佳实践。不分先后,你的所有备份都应该:
-
可靠—只使用在预期使用时间内合理可能保持其完整性的存储介质。
-
测试过—在模拟的生产环境中尽可能测试尽可能多的存档运行。
-
轮换—保持至少几个比当前备份更早的历史存档,以防最新的存档以某种方式失败。
-
分布式—确保至少一些存档存储在物理上远程的位置。在发生火灾或其他灾难的情况下,你不想让数据随着办公室一起消失。
-
安全—在任何时候都不要将你的数据暴露在不安全的网络或存储站点上。
-
合规—始终遵守所有相关的法规和行业标准。
-
最新—保留与当前实时版本相差数周或数月的存档有什么意义?
-
脚本化—永远不要依赖人类记住执行持续的任务。自动化它(阅读第五章)。
4.4. 使用 tar 存档文件和文件系统
要成功创建你的存档,必须发生以下三件事:
-
查找并确定你想要包含的文件。
-
确定你想要你的存档使用的存储驱动器上的位置。
-
将你的文件添加到存档中,并将其保存到其存储位置。
想一次性完成这三个步骤?使用tar。叫我一个无可救药的浪漫主义者吧,但我看到精心制作的tar命令中充满了诗意:一行精心平衡的代码就能完成这么多事情,可以是一件美丽的事物。
4.4.1. 简单归档和压缩示例
这个例子会复制当前工作目录及其下所有的文件和目录,并构建一个我巧妙命名的 archivename.tar 的归档文件。在这里,我在tar命令后面使用了三个参数:c告诉 tar 创建一个新的归档,v设置屏幕输出为详细模式,这样我会收到更新,而f指向我想要归档的文件名:
$ tar cvf archivename.tar *
file1 *1*
file2
file3
- 1 详细参数(v)会列出添加到归档中的所有文件名。
注意
tar命令永远不会移动或删除你提供给它的原始目录和文件;它只制作归档副本。你还应该注意,在之前的命令中使用点(.)而不是星号(*)将包括归档中的隐藏文件(文件名以点开头)。
如果你在自己的电脑上跟着做(你绝对应该这样做),那么你会看到一个名为 archivename.tar 的新文件。.tar文件名扩展名不是必需的,但尽可能清楚地传达文件的目的总是一个好主意。
你并不总是想要将目录树中的所有文件包含在归档中。假设你已经制作了一些视频,但原始文件目前保存在包含各种图形、音频和文本文件(包含你的笔记)的目录中。你唯一需要备份的文件是使用.mp4文件扩展名的最终视频剪辑。下面是如何做到这一点:
$ tar cvf archivename.tar *.mp4
那太棒了。但那些视频文件非常大。难道不好吗,通过压缩使那个归档文件变得更小?说吧!只需运行带有z(zip)参数的先前命令。这将告诉 gzip 程序压缩归档。如果你想遵循惯例,你还可以在已有的.tar扩展名之外添加.gz扩展名。记住:清晰。下面是如何实现这一点:
$ tar czvf archivename.tar.gz *.mp4
如果你尝试使用自己的.mp4文件并然后在包含新归档的目录上运行ls -l,你可能注意到.tar.gz文件并没有比.tar文件小很多,可能只有 10%左右。这是怎么回事?嗯,.mp4文件格式本身已经是压缩的,所以 gzip 压缩的空间就很小了。
由于 tar 完全了解其 Linux 环境,你可以使用它来选择存在于当前工作目录之外的文件和目录。这个例子添加了/home/myuser/Videos/目录中的所有.mp4文件:
$ tar czvf archivename.tar.gz /home/myuser/Videos/*.mp4
由于归档文件可能会变得很大,有时将它们分解成多个较小的文件,将它们传输到新家,然后在另一端重新创建原始文件是有意义的。split 工具就是为了这个目的而设计的。
在这个例子中,-b 告诉 Linux 将 archivename.tar.gz 文件分割成 1 GB 大小的部分;archivename 是你希望给文件起的任何名字。然后操作会给每个部分命名——archivename.tar.gz.partaa、archivename.tar.gz.partab、archivename.tar.gz.partac 等等:
$ split -b 1G archivename.tar.gz "archivename.tar.gz.part"
在另一边,你通过按顺序读取每个部分(cat archivename.tar.gz.part*),然后将输出重定向到一个名为 archivename.tar.gz 的新文件来重新创建存档:
$ cat archivename.tar.gz.part* > archivename.tar.gz
4.4.2. 流式传输文件系统存档
这里诗歌开始了。我将向你展示如何创建一个工作 Linux 安装程序的存档镜像并将其流式传输到远程存储位置——所有这些都在一个命令中完成 (图 4.3)。
图 4.3. 存档是一个可以使用正常 Bash 工具复制或移动的文件。

这是命令:
# tar czvf - --one-file-system / /usr /var \
--exclude=/home/andy/ | ssh username@10.0.3.141 \
"cat > /home/username/workstation-backup-Apr-10.tar.gz"
而不是立即解释所有这些,我将使用更小的例子来逐步探索。让我们创建一个名为 importantstuff 的目录内容的存档,这个目录里装满了,嗯,真正重要的东西:
$ tar czvf - importantstuff/ | ssh username@10.0.3.141 \
<linearrow /> "cat > /home/username/myfiles.tar.gz"
importantstuff/filename1
importantstuff/filename2
[...]
username@10.0.3.141's password: *1*
- 1 你需要输入远程主机上你账户的密码。
让我解释一下这个例子。与之前的方式不同,我并没有在命令参数(你一直这样做)之后立即输入存档名称,而是使用了破折号(czvf -)。破折号将数据输出到标准输出。它让你将存档文件名细节推回到命令的末尾,并告诉 tar 期望存档的源内容。然后我将未命名的、压缩的存档通过管道(|)传输到一个远程服务器上的 ssh 登录,我在那里被要求输入密码。然后,引号内的命令对存档数据流执行了 cat 操作,将流内容写入远程主机上我主目录中的 myfiles.tar.gz 文件。
如 图 4.4 所示,以这种方式生成存档的一个优点是避免了中间步骤的开销。甚至不需要在本地机器上临时保存存档的副本。想象一下备份一个占用 110 GB 可用空间的 128 GB 安装程序。存档将去哪里?
图 4.4. 在创建存档的同时进行流式传输,避免了首先将其保存到本地驱动器的需要。

那只是一个文件目录。假设你需要将一个活动的 Linux 操作系统备份到 USB 驱动器,以便你可以将其移动到另一台机器并放入该机器的主驱动器中。假设第二台机器上已经安装了相同的 Linux 版本的新安装,接下来的复制/粘贴操作将生成第一台机器的精确副本。
注意
这在尚未安装 Linux 文件系统的目标驱动器上不会工作。为了处理这种情况,正如你很快就会看到的,你需要使用 dd。
下一个示例在名为 /dev/sdc1 的 USB 驱动器上创建一个压缩存档。--one-file-system 参数排除了除当前文件系统之外的所有文件系统的数据。这意味着像 /sys/ 和 /dev/ 这样的伪分区不会被添加到存档中。如果你还想包含其他分区(如本例中的 /usr/ 和 /var/),则应明确添加它们。最后,你可以使用 --exclude 参数排除当前文件系统的数据:
# tar czvf /dev/sdc1/workstation-backup-Apr-10.tar.gz \
--one-file-system \ *1*
/ /usr /var \ *2*
--exclude=/home/andy/ *3*
-
1 在构建存档时排除其他分区的数据
-
2 明确引用 /usr 和 /var 分区
-
3 在必要时排除所选文件系统中的目录或文件(可怜的老 Andy)
现在让我们回到那个完整的命令示例。使用你已经学到的知识,归档文件系统的所有重要目录,并将存档文件复制到 USB 驱动器上。现在这应该对你来说是有意义的:
# tar czvf - --one-file-system / /usr /var \
--exclude=/home/andy/ | ssh username@10.0.3.141 \
"cat > /home/username/workstation-backup-Apr-10.tar.gz"
如果你需要归档的文件(仅限这些文件)都愉快地呆在单个目录层次结构中,那就没问题。但如果混合了其他你不想包含的文件呢?有没有一种方法可以聚合特定的文件,而无需修改源文件本身?是时候学习 find 命令了。
4.4.3. 使用 find 命令聚合文件
find 命令在文件系统中搜索匹配你提供的规则的对象。搜索输出它发现的文件名称和位置到所谓的 标准输出(stdout),通常打印到屏幕。但这个输出也可以很容易地重定向到另一个命令,如 tar,然后它会将这些文件复制到存档中。
下面是故事。你的服务器托管了一个提供大量 .mp4 视频文件的网站。这些文件散布在 /var/www/html/ 树中的许多目录中,因此逐个识别它们会非常痛苦。这里有一个单独的命令,它将在 /var/www/html/ 层次结构中搜索包含文件扩展名 .mp4 的文件。当找到文件时,tar 将使用参数 -r 来追加(而不是覆盖)视频文件到名为 videos.tar 的文件中:
# find /var/www/html/ -iname <1> "*.mp4" -exec tar \ *1*
-rvf videos.tar {} \; *2*
-
1
-iname标志返回大小写不敏感的结果;另一方面,-name搜索大小写敏感的匹配。 -
2 大括号
{ }字符告诉find命令对其找到的每个文件应用tar命令。
在这种情况下,运行 find 命令作为 sudo 是一个好主意。因为你正在系统目录中查找文件,所以其中一些文件可能有限制性权限,这可能会阻止 find 读取并因此报告它们。
此外,因为我们正在讨论find,我应该也告诉你一个类似的工具locate,当你非常匆忙时,它通常会是你的首选。默认情况下,locate会在整个系统中搜索与您指定的字符串匹配的文件。在这种情况下,locate将查找以字符串video.mp4结尾的文件(即使它们有任何前缀):
$ locate *video.mp4
如果你将locate与find直接比较,locate几乎总是会返回更快的结果。秘诀是什么?locate实际上并不是在搜索文件系统本身,而是在对现有索引中的条目运行你的搜索字符串。问题是,如果索引允许过时,搜索就会变得越来越不准确。通常索引会在系统启动时更新,但你也可以通过运行updatedb手动完成这项工作:
# updatedb
4.4.4. 保留权限和所有权...以及提取归档
我遗漏了什么吗?实际上,如何从 tar 归档中提取文件和目录以便再次使用。但在到达那里之前,还有另一项我承诺要处理的业务——确保你的归档操作不会损坏文件权限和文件所有权属性。
权限
正如你所见,运行ls -l以长格式列出目录内容,从右到左显示文件名、年龄和大小。但它也重复了一个名称(在这个例子中是 root)并提供了一些由字母r、w和x组成的相当晦涩的字符串:
$ ls -l /bin | grep zcat
-rwxr-xr-x 1 root root 1937 Oct 27 2014 zcat
这里是我解读那两个最左侧部分的地方(如图 4.5 所示)。左侧的 10 个字符由四个独立的部分组成。第一个破折号(图中的1)表示所列出的对象是一个文件。如果是目录,则会被替换为d。接下来的三个字符2表示文件权限,这些权限适用于文件的所有者,接下来的三个字符3表示权限适用于文件所属的组,最后的三个字符4表示所有其他用户对此文件的权限。
图 4.5. ls -l命令显示的数据分解

在这个例子中,文件所有者拥有完全权限——包括读取(r)、写入(w)和执行(x)权限。组内成员和其他用户可以读取和执行,但不能写入。
但这一切究竟意味着什么呢?在这里,文件 zcat 是一个命令行程序的脚本,该程序读取压缩文件。权限告诉你每个人都有权读取脚本本身并执行它(通过类似zcat myfile .zip的方式),但只有所有者可以编辑(w)文件。如果某个登录到不同用户账户的人试图编辑文件,他们会收到无写入权限警告。
如果你想更改文件的权限,请使用更改模式(chmod)工具:
# chmod o-r /bin/zcat
# chmod g+w /bin/zcat
此示例移除了其他人(o)读取文件的能力,并为组(g)添加了写权限。文件的所有者将由字母 u(代表用户)表示。
什么是组?
你可以将组想象成与常规用户账户相似的方式:两者都可以和不能做或访问的事情由文件权限定义。不同之处在于,没有人可以以组身份登录 Linux 系统。那么为什么创建组,它们有什么作用呢?下面是详细解释。
组是组织资源的一种强大且超级高效的方式。这里有一个简单的例子。考虑一家有几十名员工的公司,他们需要某种服务器访问权限,但不一定是相同的资源。例如,你可以创建几个组,称为 dev 和 IT。当用户最初获得他们的账户时,所有开发者都会被添加到 dev 组,所有系统管理员都会被添加到 IT 组。现在,假设一个系统配置文件开始使用:而不是逐个为 10 或 15 个管理员添加文件权限,你只需给 IT 组提供访问权限。所有 IT 组成员将自动添加,而所有开发者将保持被排除在外。
每个系统用户以及许多应用程序都将自动分配给他们自己的组。这就解释了为什么你创建的文件通常将由 yourname 拥有,并成为 yourname 组的一部分。如果你决定留下来,你将看到更多关于第九章(chapter 9)中组的实现。
你会发现 Linux 中还有两种描述权限的系统:数字和掩码。在这里讨论掩码可能会有些分散注意力,而且无论如何,掩码并不常用。但你需要了解数字系统,其中每个可能的权限组合都可以用一个介于 0 和 7 之间的数字表示。
如何指南和命令文档通常会告诉你为了使操作成功运行,需要给文件设置 644 权限(或类似设置)。例如,调用加密密钥对的私有部分通常不会工作,除非它有 400 或 600 的权限。你将想知道这是如何工作的。
读取权限始终被赋予数字 4;写权限,数字 2;执行权限,数字 1。拥有所有三种权限的用户用数字 7(4+2+1=7)表示。读取和写权限,但没有执行权限,是 6;读取和执行,但没有写权限,是 5,没有任何权限是 0。
要更改一个对象权限,你需要输入每个用户类别(即所有者、组和其他人)的最终总分。例如,使用 chmod 更新 zcat 文件的原有状态,就像你之前使用 g+w 和 o-r 那样,需要 755(所有者 7 分,然后是组和其他人各 5 分)。从其他人那里移除读取权限将使其变为 751,而向组添加写入权限又将使其变为 771。以下是使用 chmod 应用该值的方法:
# chmod 771 /bin/zcat
这里有一个快速图表,帮助你记住所有这些细节:
| 权限 | 字符 | 数字 |
|---|---|---|
| 读取 | r | 4 |
| 写入 | w | 2 |
| 执行 | x | 1 |
所有权
那些文件所有权值又是怎么回事呢?这个很简单:这些值定义了文件的所有者(u)和组(g)。你自己检查一下。从你的家目录中创建一个新文件,然后以长格式列出目录内容。你会发现所有者和组的值都匹配你的用户名。在这个例子中,是 username:
$ cd
$ touch newfile
$ ls -l
-rw-rw-r-- 1 username username 0 Jun 20 20:14 newfile
我很少连续几天不用担心文件所有权问题。假设我的一个用户请求一个文件。文件可能太大无法通过电子邮件发送,或者可能包含不应该通过电子邮件发送的敏感数据。显然的解决方案是,如果我在同一台服务器上,就复制它。如果我在不同的服务器上,我总是可以使用 scp 来传输文件,然后将文件复制到用户的家目录。但无论如何,我需要使用 sudo 来将文件复制到用户的目录,这意味着它的所有者将是 root。
你不相信我?试着使用 sudo 创建一个文件:
$ sudo touch newerfile
[sudo] password for username:
$ ls -l
-rw-r--r-- 1 root root 0 Jun 20 20:37 newerfile *1*
- 1 注意这个文件的所有者和组是 root。
好吧,如果我的用户需要编辑我慷慨发送的文件,那可真是个大问题。结果证明,我并没有那么有帮助——除非我正确地完成工作,并使用 chown 命令更改文件的所有权,这个命令和您之前看到的 chmod 命令非常相似。这个例子假设那个其他用户账户的名字是 otheruser。现在就使用 sudo useradd otheruser 创建这样一个账户吧:
$ sudo chown otheruser:otheruser newerfile
$ ls -l
-rw-r--r-- 1 otheruser otheruser 0 Jun 20 20:37 newerfile *1*
- 1 注意新的文件所有者和组。
这就是权限和所有权。但是这与提取你的存档有什么关系呢?好吧,如果你听说在灾难性系统崩溃后恢复的所有文件和目录都有错误的权限,你会感到沮丧吗?我想你会的。想想看:你重建了系统,并邀请所有用户再次登录,但他们立即开始抱怨他们无法编辑自己的文件!
我认为让你亲自看到这些内容会有所帮助。所以你可以自己完成这些例子,创建一个新的目录,并用几个空文件填充它,然后,如果你的系统上还没有其他用户账户,就创建一个:
$ mkdir tempdir && cd tempdir *1*
$ touch file1
$ touch file2
$ touch file3
# useradd newuser
- 1 只有当第一个命令成功执行时,&& 字符才会执行第二个命令。
目前,所有三个文件都将归你所有。使用 chown 将其中一个文件的拥有权更改为新用户,然后使用 ls -l 来确认其中一个文件现在属于新用户:
# chown newuser:newuser file3
$ ls -l
-rw-rw-r-- 1 username username 0 Jun 20 11:31 file1
-rw-rw-r-- 1 username username 0 Jun 20 11:31 file2
-rw-rw-r-- 1 newuser newuser 0 Jun 20 11:31 file3
现在创建一个包含当前目录中所有文件的 tar 存档,就像你之前做的那样:
$ tar cvf stuff.tar *
file1
file2
file3
要提取存档,运行 tar 命令针对存档的名称,但这次使用参数 x(表示提取)而不是 c:
$ tar xvf stuff.tar
警告
在当前目录中提取存档会覆盖同名文件,而不会发出警告。在这里,这没问题,但通常不会是这样。
再次运行 ls -l 将会显示一些你不希望看到的内容。现在所有三个文件都归你所有...甚至包括 file3:
$ ls -l
-rw-rw-r-- 1 username username 0 Jun 20 11:31 file1
-rw-rw-r-- 1 username username 0 Jun 20 11:31 file2
-rw-rw-r-- 1 username username 0 Jun 20 11:31 file3
这并不好,我相信我们的朋友 newuser 也不会对此感到高兴。解决方案是什么?首先,让我们尝试找出确切的问题是什么。
通常,只有具有管理员权限的用户才能在其他用户的账户中操作资源。如果我想,比如说,将我其中一个文件的拥有权转让给同事,因为这需要更改他人的账户,我无法做到。慷慨是有极限的。因此,当我尝试从存档中恢复文件,并将它们保存到其他用户的拥有权下时,这是不可能的。以原始权限恢复文件会带来类似(尽管不是完全相同)的问题。解决方案是以管理员身份执行这些操作,使用 sudo。现在你明白了。
4.5. 使用 dd 存档分区
如果你足够努力地研究,你可以用 dd 做很多事情,但它最擅长的是让你玩转分区。之前,你使用 tar 通过从一台计算机复制文件并直接粘贴到另一台计算机的新 Linux 安装上,来复制整个文件系统。但由于那些文件系统存档并不完整,它们需要一个运行的宿主操作系统作为基础。
另一方面,使用 dd 可以完美地创建几乎任何数字内容的字节数据镜像。但在你开始将分区从一个地球端传送到另一个地球端之前,我应该提到,那个古老的 UNIX 管理员笑话确实有些真实性:dd 代表 Disk Destroyer。如果你在 dd 命令中输入哪怕一个错误的字符,你都可以瞬间永久地擦除整个驱动器上宝贵的所有数据。而且,拼写很重要。
注意
就像 dd 一样,在按下 Enter 键之前,请务必暂停并仔细思考!
4.5.1. dd 操作
现在你已经被适当地警告了,我们将从一些简单的事情开始。假设你想要创建一个指定为 /dev/sda 的整个数据磁盘的精确镜像。你已经插入了一个空驱动器(理想情况下,容量与你的 /dev/sdb 系统相同)。语法很简单:if= 定义源驱动器,而 of= 定义你想要保存数据的文件或位置:
# dd if=/dev/sda of=/dev/sdb
下一个示例将创建/dev/sda驱动器的.img存档并将其保存到您的用户账户的主目录中:
# dd if=/dev/sda of=/home/username/sdadisk.img
这些命令创建了整个驱动器的图像。您也可以专注于驱动器中的一个单独分区。下一个示例就做了这件事,并且还使用了bs来设置每次复制时的字节数(在这个例子中是 4,096)。调整bs值可能会影响dd操作的整体速度,尽管理想的设置将取决于硬件和其他考虑因素:
# dd if=/dev/sda2 of=/home/username/partition2.img bs=4096
恢复很简单:实际上,您只需反转if和of的值。在这种情况下,if=接受您想要恢复的镜像,而of=接受您想要写入镜像的目标驱动器:
# dd if=sdadisk.img of=/dev/sdb
您应该始终测试您的存档以确认它们是否正常工作。如果您创建的是启动驱动器,将其插入计算机并查看是否按预期启动。如果是一个普通的数据分区,将其挂载以确保文件存在且可适当访问。
4.5.2. 使用 dd 擦除磁盘
多年前,我有一个朋友负责他政府海外大使馆的安全。他曾经告诉我,在他监管下的每个大使馆都配备了一根官方政府颁发的锤子。为什么?以防设施有被不友好势力占领的风险,锤子是用来摧毁所有硬盘的。
那是什么?为什么不直接删除数据呢?你在开玩笑吧?众所周知,从存储设备中删除包含敏感数据的文件实际上并没有真正删除它们。只要有足够的时间和动机,几乎可以从任何数字媒体中恢复几乎所有内容,可能唯一的例外是那些经过良好且适当破坏的。
然而,您可以使用dd使坏人更难获取您旧数据。这个命令将花费一些时间,在/dev/sda1分区的每个角落和缝隙中写入数百万个零:
# dd if=/dev/zero of=/dev/sda1
但还有更好的。使用/dev/urandom文件作为源,您可以写入随机字符覆盖磁盘:
# dd if=/dev/urandom of=/dev/sda1
4.6. 使用 rsync 同步存档
您已经知道关于正确备份的一件事是,为了有效,它们绝对必须定期进行。其中一个问题是,大量存档的每日传输会给您的网络资源带来很大压力。如果只需传输自上次以来创建或更新的少量文件,而不是整个文件系统,那岂不是很好?做到了。向您介绍rsync。
我将向你展示如何创建一个包含文件的远程副本,并确保即使本地文件发生变化,副本的准确性也能得到保持。(你首先需要确保你将要使用的客户端和主机机器上都安装了 rsync 软件包。)为了说明这个过程发生在你的本地机器和远程服务器之间(可能是一个你正在运行的 LXC 容器),创建一个目录并用一些空文件填充它:
$ mkdir mynewdir && cd mynewdir
$ touch file{1..10} *1*
- 1 创建了 10 个名为 file1 到 file10 的文件
现在用 ssh 在你的远程服务器上创建一个新的目录,复制文件将放入其中,然后使用 -av 参数运行 rsync。v 参数告诉 rsync 显示它所做的一切的详细列表。a 参数稍微复杂一些,但也很重要。指定 -a 超级参数将使 rsync 递归同步(这意味着子目录及其内容也将包括在内)并保留特殊文件、修改时间以及(至关重要)所有权和权限属性。我敢打赌你们都会选择 -a。以下是一个例子:
$ ssh username@10.0.3.141 "mkdir syncdirectory"
$ rsync -av * username@10.0.3.141:syncdirectory *1*
username@10.0.3.141's password:
sending incremental file list
file1 *2*
file10
file2
file3
file4
file5
file6
file7
file8
file9
sent 567 bytes received 206 bytes 1,546.00 bytes/sec
total size is 0 speedup is 0.00
-
1 在冒号(:)后指定远程目标目录。
-
2 verbose 参数会显示已复制的文件。
如果一切如预期进行,前往你的远程服务器并列出 /syncdirectory/ 的内容。应该有 10 个空文件。
为了给 rsync 一个适当的测试运行,你可以在本地的 mynewdir 目录中添加一个新文件,并使用 nano 添加一些文字到现有的某个文件中。然后运行之前完全相同的 rsync 命令。完成后,看看新文件和旧文件的更新版本是否已经传到了远程服务器上:
$ touch newfile
$ nano file3
$ rsync -av * username@10.0.3.141:syncdirectory
username@10.0.3.141's password:
sending incremental file list
file3 *1*
newfile
- 1 只有新/更新的文件会在输出中列出。
有很多关于 rsync 备份的精彩功能等待你去发现。但是,就像我在这本书中讨论的所有其他工具一样,你现在已经掌握了基础知识。接下来你要走的方向取决于你自己。然而,在下一章中,你将学习如何使用系统调度器来自动化备份。现在,我还有一个关于备份的最终想法想要分享。
4.7. 计划考虑因素
仔细考虑将大大有助于确定你在备份上投入多少金钱和精力。你的数据对你越有价值,它应该越可靠。目标是衡量你的数据价值与以下问题:
-
应该多久创建一次新的存档,以及你将保留旧副本多长时间?
-
你将在备份过程中构建多少层验证?
-
你将维护多少个数据副本?
-
维护地理位置上远程存档有多重要?
另一个同样重要的问题:你应该考虑增量备份还是差异备份?尽管你可能无论如何都会想使用 rsync,但你备份的顺序可能会影响它们消耗的资源以及产生的存档的可用性。
使用 差异 系统,您可能每周运行一次完整备份(周一),然后在接下来的六天中运行较小且较快的差异备份。周二的备份将仅包括自周一备份以来更改的文件。周三、周四和周五的备份将各自包括自周一以来更改的所有文件。周五的备份显然将比周二占用更多的时间和空间。优点是,恢复差异存档只需要最后一个完整备份和最近的差异备份。
一个 增量 系统也可能只在周一进行完整备份,也可以在周二运行仅覆盖已更改文件的备份。周三的备份,与差异方法不同,将仅包括自周二以来添加或更改的文件,周四的备份将仅包括自周三以来更改的文件。增量备份将快速高效;但是,由于更新的数据分布在更多文件中,恢复增量存档可能既耗时又复杂。这如图 4.6 所示。
图 4.6. 增量备份和差异备份系统之间的差异

摘要
-
没有良好的备份可能会毁掉你的早晨。
-
tar命令通常用于存档完整或部分文件系统,而dd更适合于分区镜像。 -
在存档中添加压缩不仅可以节省存储驱动器上的空间,还可以在网络传输期间节省带宽。
-
包含伪文件系统的目录通常不需要备份。
-
您可以将存档的传输集成到生成它的命令中,可选地避免需要本地保存存档。
-
保留从存档中恢复的对象的所有权和权限属性是可能的——也是首选的。
-
您可以使用
dd(相当)安全地擦除旧磁盘。 -
您可以使用
rsync逐步同步存档,大大减少持续备份所需的时间和网络资源。
关键术语
-
一个 存档 是一个特殊格式的文件,其中包含文件系统对象。
-
压缩 是通过应用压缩算法来减少文件占用的磁盘空间的过程。
-
一个 图像 是一个包含文件和目录结构,用于在新位置重新创建源文件系统的存档。
-
权限 是分配给对象的属性,它决定了谁可以使用它以及如何使用。
-
所有权 是拥有对象和组的所有者和组。
-
一个 组 是一个用于管理多个用户权限的账户。
安全最佳实践
-
为备份所有重要数据创建一个自动的、可靠的、经过测试的、安全的定期过程。
-
在适当的情况下,通过将它们放置在各自的分区并将它们在启动时挂载到文件系统上来分离包含敏感数据的文件系统。
-
总是确保文件权限准确无误,并仅允许必要的最低访问权限。
-
不要假设旧存储驱动器上的数据真正被删除。
命令行审查
-
df -h显示所有当前活动分区,大小以人类可读的格式显示。 -
tar czvf archivename.tar.gz /home/myuser/Videos/*.mp4从指定目录树中的视频文件创建一个压缩归档。 -
split -b 1G archivename.tar.gz archivename.tar.gz.part将大文件拆分为最大大小为 1G 的较小文件。 -
find /var/www/ -iname "*.mp4" -exec tar -rvf videos.tar {} \;查找满足特定标准的文件并将它们的名称流式传输到tar以包含在归档中。 -
chmod o-r /bin/zcat移除其他人的读权限。 -
dd if=/dev/sda2 of=/home/username/partition2.img创建 sda2 分区的镜像并将其保存到您的家目录中。 -
dd if=/dev/urandom of=/dev/sda1用随机字符覆盖分区以隐藏旧数据。
测试自己
1
以下哪个参数告诉
tar压缩归档?
-a-v-z-c2
以下哪个分区你最不可能想要包含在备份归档中?
- /var
- /run
- /
- /home
3
系统上第一个存储驱动器的第二个分区通常由以下哪个指定?
- /dev/sdb2
- /dev/srb0
- /dev/sda2
- /dev/sdb1
4
以下哪个命令会在目录中创建所有 .mp4 文件的压缩归档?
tar cvf archivename.tar.gz *.mp4tar cvf *.mp4 archivename.tar.gztar czvf archivename.tar.gz *.mp4tar *.mp4 czvf archivename.tar5
以下哪个工具可以帮助你将多个文件部分重新组合在一起?
catsplit|part6
以下哪个命令会在指定的目录中查找所有 .mp4 文件并将它们添加到
tar归档中?
find /var/www/ -iname "*" -exec tar -rvf videos.tar {} \;find /var/www/ -iname "*.mp4" -exec tar -vf videos.tar {} \;find /var/www/ -iname "*.mp4" | tar -rvf videos.tar {} \;find /var/www/ -iname "*.mp4" -exec tar -rvf videos.tar {} \;7
以下哪个命令会给文件的所有者完全权限,组读执行权限,其他人只有执行权限?
chmod 752chmod 751chmod 651chmod 7448
命令
dd if=sdadisk.img of=/dev/sdb会做什么?
- 将 /dev/sdb 驱动的所有内容复制到名为 sdadisk.img 的文件中
- 毁灭网络上的所有数据
- 将名为 sdadisk.img 的镜像复制到 /dev/sdb 驱动器
- 格式化 /dev/sdb 驱动器并将 sdadisk.img 移动到其中
答案键
1.
c
2.
b
3.
c
4.
c
5.
a
6.
d
7.
b
8.
c
第五章. 自动化管理:配置自动远程备份
本章涵盖
-
使用脚本自动化管理任务
-
提高安全性和系统效率
-
备份本地数据
-
安排自动化任务
如果我在上一章中已经足够清晰地说明了某一点,那就是定期且可靠的系统备份绝对是关键。但是,从许多方面来看,困难的部分在于定期。跟上具有直接后果的重要任务已经足够困难;记得运行一些无聊的每日或每周备份几乎是不可能的。
没有什么秘密:解决这个问题的最佳方案是配置一个自动调度器来为你执行任务,然后忘记它。直到最近,Linux 上使用的调度器几乎肯定是一些软件工具 cron 的变体;实际上,这仍然是一个很好的选择。但是,你在第三章(kindle_split_011.xhtml#ch03)中学到的 systemd 进程管理器已经将 systemd 定时器添加到其中。
我将在本章中介绍这两种方法,但也会向你展示如何将备份和其他管理任务打包到脚本中,这些脚本本身也可以被安排在自动化的时间表中。为了展示这一切在实际世界中的工作原理,我将创建一个命令,将一些数据备份到 AWS 简单存储解决方案(S3)桶中,然后使用该命令通过 cron 和 systemd 定时器创建调度器。
5.1. 使用 Bash 脚本
Linux 的 脚本 是一个包含一个或多个符合 Bash(或某些其他 shell 解释器)命令的纯文本文件。能够在单个文件中串联多个命令,使得创建可以与编程语言在复杂性和多功能性上相媲美的可执行例程成为可能。
5.1.1. 系统文件备份的示例脚本
为了说明一个工作脚本可能的样子,让我给你展示一个简短、写得很好的例子,这个例子可能已经在你的机器上运行了。一旦我们逐行分析完脚本,我会告诉你这一切与本章有什么关系。而且别忘了这个练习的另一个重要收获:如果你能读懂脚本,你也能编写它们。这是完全相同的技能集。
此脚本使用一系列强大的工具来完成实际上相当简单的事情:创建四个重要系统文件的加密备份,以确保在原始文件被意外损坏时有一个可用的替代品。图 5.1 说明了脚本的操作流程图。请注意,$FILE 是一个用于表示脚本处理的文件集的变量。
图 5.1. passwd 脚本跟踪的决策流程

转到 /etc/cron.daily/ 目录并列出内容。你可能会看到一个名为 passwd 的文件。使用 less(或 cat、nano 或 vim)显示该文件。由你选择。如果它恰好不在那里,它看起来是这样的:
#!/bin/sh
cd /var/backups || exit 0
for FILE in passwd group shadow gshadow; do
test -f /etc/$FILE || continue
cmp -s $FILE.bak /etc/$FILE && continue
cp -p /etc/$FILE $FILE.bak && chmod 600 $FILE.bak
done
通常 # 字符引入的注释不会被解释器读取。在这个特定的例子中,由于同时使用了 # 和 !,Linux 将读取注释并使用其值(/bin/sh)作为活动 shell。这个字符串通常被称为 shebang 行,尽管我不清楚为什么。虽然 sh 是 bash 的一个替代品,但就我们现在的目的而言,两者之间没有实际的区别。
脚本的下一行将更改目录到 /var/backups/。如果不存在这样的目录,它将退出脚本并发出退出状态码 0,这表示命令成功:
cd /var/backups || exit 0
|| 序列(有时被称为双竖线)可以读作单词 or。所以这一行意味着:要么更改目录到 /var/backups/,要么退出脚本。如果一切按计划进行,后续的脚本操作将在 /var/backups/ 目录中进行。
注意
当 Linux 命令完成时,会传递退出码。0 将被传递以表示成功,而不同的数字可以配置来指定某种错误。
在代码的下一部分,以 for 开头的行引入了一个循环。对于有编程经验的你们来说,理解这里发生的事情不会有任何困难:脚本将依次将以下四个字符串(passwd、group 等等)作为变量 FILE 的值。然后它执行 do 和 done 保留词之间的代码块:
for FILE in passwd group shadow gshadow; do
这里是一些概念的简要定义:
-
循环—由保留词分隔的一系列操作,直到满足指定条件为止
-
字符串—字符的连续序列
-
变量—一个可以更改并且可以动态地纳入脚本操作中的值
-
保留词—由 shell 根据预定义的含义解释的术语
回到脚本,下面第一行将检查 /etc/ 目录中是否存在一个文件,其名称与变量 $FILE 的当前值匹配。如果 /etc/ 中没有以该名称命名的文件,则脚本将继续通过将下一个字符串(下一个文件名)分配给 $FILE 变量并检查其存在性来继续:
test -f /etc/$FILE || continue
如果 /etc/ 中存在这样的文件,则脚本将比较该文件的内容与当前目录(/var/backups/)中具有相同名称加上 .bak 文件扩展名的文件的内容。如果比较操作(&&)成功,shell 将继续 for 循环并尝试下一个字符串。另一方面,如果两个文件的内容不匹配,则它将移动到下一行:
cmp -s $FILE.bak /etc/$FILE && continue
然后,最后,脚本将交付其有效载荷:它将 /etc/ 目录中的当前版本复制到 /var/backups/ 目录,将其名称添加 .bak,并调整文件权限以防止未经授权的用户读取它。此操作将覆盖具有相同名称的任何现有文件。示例中的 -p 标志保留源文件的原始所有权属性和时间戳:
cp -p /etc/$FILE $FILE.bak && chmod 600 $FILE.bak
那个脚本做什么?它旨在创建指定配置文件的副本,这些文件自上次备份以来已更新。以下是工作原理:如果指定的名称在活动 /etc/ 目录中存在,并且其内容与 /var/backups/ 目录中类似名称的文件不同,那么 /etc/ 中的文件将被复制到 /var/backups/,并适当地重命名和加密。
但那四个文件(passwd、group、shadow 和 gshadow)是怎么回事?它们的内容决定了单个用户和组如何能够访问特定资源。例如,如果你查看 /etc/passwd 的内容,你会看到每个账户都有一行。在下面的摘录中,你可以看到常规用户账户被分配了用户和组 ID(例如 ubuntu 的 1000),家目录(/home/ubuntu/),以及默认的 shell(bash)。一些系统用户(如 syslog)也有一个默认的 shell,奇怪的是,设置为 /bin/false。这是一种防止人类用户使用该账户登录系统的方法,这会不安全:
$ cat /etc/passwd
[...]
syslog:x:104:108::/home/syslog:/bin/false
_apt:x:105:65534::/nonexistent:/bin/false
sshd:x:106:65534::/var/run/sshd:/usr/sbin/nologin
ubuntu:x:1000:1000::/home/ubuntu:/bin/bash *1*
mysql:x:107:111:MySQL Server,,,:/nonexistent:/
bin/false *2*
bind:x:108:112::/var/cache/bind:/bin/false
newuser:x:1002:1002:,,,:/home/newuser:/bin/bash
messagebus:x:109:114::/var/run/dbus:/bin/false
-
1 ubuntu 的用户 ID(1000)、家目录(/home/ubuntu)和默认 shell(bash)
-
2 不应使用非用户账户进行登录 (/bin/false)。
当你使用以下方式将新用户添加到你的系统时
# useradd -m alan
将在新添加的 passwd、shadow 和 group 文件中添加新行。实际上,所有相关的用户管理操作都可以从命令行(或通过脚本)执行,而无需直接编辑这些文件。
注意
Ubuntu 倾向于使用 adduser username 而不是 useradd username,尽管两者都可以工作。adduser 的一个优点是会自动创建家目录,而 useradd 需要使用 -m 参数。adduser 命令还会为新用户提示输入密码。如果你使用 useradd,你需要单独运行 sudo passwd new-user-name 来设置密码。
以前,每个用户的密码的加密版本也会包含在这里。出于实际原因,因为 passwd 文件必须由系统上的任何人可读,所以包括加密密码被认为是不明智的。那些密码被移动到了 /etc/shadow。使用 sudo 权限,你应该在自己的系统上查看包含加密密码的该文件。以下是方法:
$ sudo cat /etc/shadow
/etc/group 文件包含有关所有当前存在的系统和用户组的基本信息。您可以手动编辑组文件来管理组成员。例如,您可以通过将新加入团队的用户姓名添加到 sudo 组中来授予他们管理权限。该行将看起来像这样:
sudo:x:27:steve,newuser,neweruser
在名称和逗号之间不要添加任何空格。这样做会导致立即的不愉快。
最后一个文件:/etc/gshadow 文件包含用于在您有时想允许非组成员访问组资源时使用的加密组密码版本。
如您可能已经猜到的,这个脚本是这个章节的一个很好的例子,因为它位于:/etc/cron.daily/ 目录。保存到 /cron.daily/ 目录的脚本将每天执行。我们很快就会回到所有这些。现在,作为另一个简单的例子,这里有一个名为 upgrade.sh 的脚本文件,用于让 apt 自动更新所有已安装的软件:
#!/bin/bash
# Script to automate regular software upgrades
apt update
apt upgrade -y
如您所无疑问地回忆起,apt update 命令将与在线仓库的索引同步,确保 APT 了解所有最新的软件包和版本。apt upgrade 将下载并安装任何相关的更新。-y 将在询问确认操作时自动回答是。
您仍然还没有准备好运行您的脚本。因为您将以程序的方式运行脚本,您需要更改文件属性使其可执行。chmod +x 后跟文件名即可完成此操作:
$ chmod +x upgrade.sh
就这样。现在您可以自由地将文件复制到 /etc/cron.daily/ 目录中,这样它就可以与 passwd 和其他文件一起每天运行:
# cp upgrade.sh /etc/cron.daily/
由于它运行 apt,新的脚本需要管理员权限,但不需要在命令本身中包含 sudo。默认情况下,Cron 总是以 root 身份运行。如果您想直接从命令行运行脚本,则需要添加 sudo 并在文件名前加上点号和正斜杠,以告诉 Linux 您引用的命令位于当前目录中:
$ sudo ./upgrade.sh
5.1.2. 一个用于更改文件名的示例脚本
让我再向您介绍一些脚本工具。您可能已经遇到了这样一个事实,即 Linux shell 有时可能会错误地解释包含空格的文件名。如果您尝试 cat 一个名为 big name 的文件的 内容,它看起来会是这样:
$ cat big name
cat: big: No such file or directory
cat: name: No such file or directory
简单的解决方案是将完整的文件名用单引号或双引号括起来,如下所示:
$ cat 'big name'
Hello world
但这个选项并不总是可用。在这种情况下,您可以通过将文件名中的空格自动转换为,例如,下划线字符来自动化一个过程。然后,在您的旅途中,当您遇到包含大量违规文件名的目录时,您将能够执行一个脚本来快速修复问题。好吧,这就是它:
#!/bin/bash
echo "which directory would you like to check?"
read directory
find $directory -type f | while read file; do
if [[ "$file" = *[[:space:]]* ]]; then
mv "$file" `echo $file | tr ' ' '_'`
fi;
done
echo 行将文本打印到屏幕上,然后等待用户输入。用户将输入一个有效的目录,如 /home/ubuntu/files/,它将被分配为变量 directory 的值。find 命令将被调用以返回指定目录中的所有文件对象 (-type f)。find 的文件名集将逐个在 while 循环中读取,每个文件都会检查是否存在空格。如果找到空格,then 将文件名中的任何空格(‘ ’)更改为下划线(‘_’)。fi; 当目录中没有更多文件名时停止循环。
要尝试这个,创建一个包含带有空格的文件名的几个文件的目录,然后运行脚本。一个看起来像这样的目录
$ ls
file name file - name
应该看起来像这样:
$ ls
file_name file_-_name
注意
仔细思考组成脚本的每个步骤,并确保你确切地了解正在发生什么。
让我们花一点时间提醒自己我们现在在哪里。确保你不是只盯着树木而错过了整个森林总是一个好主意。到目前为止发生的事情如下:
-
本章是关于使用脚本创建自动备份。
-
你探索了从 /etc/ 到 /var/backup/ 的用户管理员文件的备份脚本。
-
你学习了那些用户管理员文件的照顾和喂养。
-
你编写了自己的简单脚本。
接下来还将发生什么:
-
你将把自己的数据备份到 AWS S3 存储桶。
-
你将使用 cron 和 anacron 来安排定期备份。
-
你将学习如何使用 systemd 定时器来完成这项工作。
脚本可以用于比备份和文件名多得多的用途。随着对服务器和网络环境需求的不断增长,有时需要数百甚至数千个动态生成的虚拟微服务,手动管理几乎是不可能的。在你作为系统管理员的职业生涯中,你可能会需要创建脚本来配置和启动单个和成群的虚拟机,以及监控庞大且不断变化的环境。
5.2. 将数据备份到 AWS S3
我选择亚马逊的 AWS S3 作为我将要使用的备份示例的目标,有以下两个原因:
-
总是保持重要数据离线副本至关重要。
-
将存档到 S3 是目前流行的事情,而且非常简单。
就这样。这个事实是,这是一个极好的机会来毫不掩饰地推广我的书《一个月午餐时间学习亚马逊网络服务》(Manning,2017),这本书充满了你可能需要知道的所有关于 AWS 的知识,这绝对没有影响我的选择。没有。好吧,也许只是有一点。
无论如何,如果您还没有自己的 AWS 账户,您仍然可以跟随下一节,并用您自己的备份脚本替换我将向您展示的 AWS 脚本。或者,您也可以访问 aws.amazon.com 并注册一个新账户。开设账户不会花费您任何费用,并且根据免费层,许多服务(包括 5 GB 的 S3 存储)在第一年都是免费的。
顺便说一句,即使您的 AWS 免费层权限已经结束,存储费用仍然大约为每月每 GB 0.025 美元。这可能足够便宜,足以让您改变对异地存档的一般看法。
5.2.1. 安装 AWS 命令行界面 (CLI)
您可以在 AWS 控制台中通过浏览器执行许多 AWS 管理任务,但这并不是真正的 Linux 管理员完成任务的方式。如果您打算将备份到 S3 的操作集成到脚本中,那么它必须在命令行上工作。为此,您无需寻找其他选择,只需使用亚马逊自己的 AWS CLI。因为它运行在 Python 上,所以您至少需要运行 Python 2(版本 2.6.5)或 Python 3(版本 3.3)。此外,您还需要 pip Python 包管理器来处理安装。截至本文撰写时,Ubuntu 正在努力将 Python 3 作为其默认版本,尽管其他发行版可能仍然更倾向于 Python 2。
如果以下安装命令不起作用,那么您可能需要安装 pip。(使用 apt install python-pip 或 apt install python3-pip。)以下是超秘密、内部人士专属、我可以向您透露但之后我必须杀掉您的隐藏代码,它告诉您需要哪种 pip 安装。如果它有效,那就是正确的。如果不起作用,请尝试另一个:
$ pip3 install --upgrade --user awscli
Collecting awscli
Downloading awscli-1.11.112-py2.py3-none-any.whl (1.2MB) *1*
100% |################################| 1.2MB 946kB/s
Collecting PyYAML<=3.12,>=3.10 (from awscli)
Downloading PyYAML-3.12.tar.gz (253kB)
100% |################################| 256kB 2.2MB/s
[...]
Collecting jmespath<1.0.0,>=0.7.1 (from botocore==1.5.75->awscli)
Downloading jmespath-0.9.3-py2.py3-none-any.whl
Collecting six>=1.5 (from python-dateutil<3.0.0,>=2.1->
botocore==1.5.75->awscli)
Downloading six-1.10.0-py2.py3-none-any.whl
Building wheels for collected packages: PyYAML
Running setup.py bdist_wheel for PyYAML ... done
Stored in directory: /home/ubuntu/.cache/pip/wheels
/2c/f7/79/13f3a12cd723892437c0cfbde1230ab4d82947ff7b3839a4fc
Successfully built PyYAML
Installing collected packages: PyYAML, pyasn1, rsa, colorama, six,
python-dateutil, docutils, jmespath, botocore, s3transfer, awscli *2*
Successfully installed PyYAML awscli botocore colorama docutils
jmespath pyasn1 python-dateutil rsa s3transfer six
-
1 pip 可以很好地显示实时进度详情。
-
2 pip 安装的包的摘要
5.2.2. 配置您的 AWS 账户
现在,您已经准备好将本地 AWS CLI 与您的 AWS 账户链接。为此,您需要检索一些访问密钥。在控制台中的任何页面,点击带有您账户名称的下拉菜单标题(页面右上角),然后点击“我的安全凭证”链接(图 5.2)。
图 5.2. AWS 控制台,直接链接到数十个 AWS 服务。这里可以看到“我的安全凭证”链接。

一旦您进入“我的安全凭证”页面,点击展开“访问密钥”(访问密钥 ID 和秘密访问密钥)部分,并注意可能出现的警告:现有根密钥无法检索。然后点击“创建新访问密钥”按钮。您将看到您的新访问密钥 ID 和其相应的秘密访问密钥。前者与登录名具有相同的功能,后者则像密码一样。您可以将访问密钥下载并保存到计算机上的安全位置,或者选择、复制并粘贴到其他地方。
为了设置这些,在你的本地机器上打开一个终端,并从命令行运行 aws configure。你将被要求输入你的访问密钥 ID、你的秘密访问密钥、你想要设置为默认的 AWS 区域,以及你想要使用的输出格式。如果你愿意,可以留出最后两个值。以下 AWS 文档中的示例使用了假凭证(你永远不应该公开显示真实的密钥集):
$ aws configure
AWS Access Key ID [None]: AKIAIOSFODNN7EXAMPLE
AWS Secret Access Key [None]: wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
Default region name [None]: us-east-1 *1*
Default output format [None]: *2*
-
1 确定资源将从中启动的 AWS 地理中心
-
2 选择以文本(默认)、JSON 或表格格式显示输出。
注意
在设置默认区域名称时,请记住,地方监管制度可能会限制将某些数据备份到海外服务器。
现在,你应该已经准备好开始工作了。以下命令行
$ aws s3 ls
将列出你账户中的所有 S3 存储桶。存储桶 是 AWS 用于我们称之为目录的术语。
5.2.3. 创建你的第一个存储桶
假设这是一个全新的账户,将不会显示任何内容。你应该使用创建存储桶命令 mb 创建一个新的存储桶。在选择存储桶名称时要注意的是,它必须在整个 S3 系统中是唯一的。如图所示,像 mybucket 这样的名称可能不会被接受:
$ aws s3 mb s3://mybucket
make_bucket failed: s3://mybucket/ An error occurred (BucketAlreadyExists)
when calling the CreateBucket operation: The requested bucket name is not
available. The bucket namespace is shared by all users of the system.
Please select a different name and try again.
相反,使用不太常见的词汇并添加几个数字可能会效果更好:
$ aws s3 mb s3://linux-bucket3040
再多一步,我们就有了离线备份工作。假设你需要备份的文件位于名为 /dir2backup 的目录中,该目录位于你的主目录中,以下是它的工作方式:
aws s3 sync /home/username/dir2backup s3://linux-bucket3040
s3 sync 工具的工作方式与你在第四章中遇到的 rsync 工具非常相似。第一次运行它时,源目录中的所有内容都将上传到你的 S3 存储桶;随后,只有新文件或更改的文件将被传输。创建一个运行该 sync 命令的脚本将非常直接。下面是如何看起来:
#!/bin/bash
/usr/local/bin/aws s3 sync \
/home/username/dir2backup s3://linux-bucket3040
注意我添加了 aws 命令的完整路径 (/usr/local/bin/aws)。这是为了确保 Bash 知道在系统中的哪个位置可以找到该命令。你可以使用 whereis 命令来确认 aws 的位置:
$ whereis aws
aws: /usr/local/bin/aws
虽然已经部署了优秀的备份工具,但这并不意味着你会使用它。这需要某种类型的任务调度器。你将在下一节中了解几个这样的调度器。
5.3. 使用 cron 定期安排备份
Cron 有多种风味。因为那个数字大于一,你可以预期通常有不止一种方法来完成特定的任务。为了了解有哪些内容,列出 /etc/ 目录中包含字母 cron 的对象:
$ ls /etc | grep cron
anacrontab
cron.d
cron.daily
cron.hourly
cron.monthly
crontab
cron.weekly
在这些中,只有 anacrontab 和 crontab 是文件;其余的是目录。让我们先看看这些目录是如何工作的。
如果你有一个文件系统备份脚本,你希望它在设定的时间间隔内运行,你可以将其复制到相应的目录:cron.hourly/ 以每小时执行一次,cron.daily/ 以每天执行一次,依此类推。cron.d/ 目录略有不同。它是为那些内容精确控制命令执行时间的文件设计的。
假设你想要运行我之前编写的那个软件升级任务,但不需要脚本,每周一运行一次。你可以在 /etc/cron.d 目录中创建一个包含以下内容的文件:
21 5 * * 1 root apt update && apt upgrade
这个例子将在每周一早上 5:21 运行升级。这是怎么工作的?看看前几个字符:21 5。第一个字段(21)代表你希望命令运行的每小时中的分钟数。在这种情况下,它将在 21 分钟后运行。下一个字段是你可以指定一天中的哪个小时(例如,8 或 23)。这个是 5,意味着早上 5:00。接下来的两个星号后面跟着一个 1 表示你想要在每月的每一天、每年的每个月、每周的每个星期一(1 代表星期一)遵循该计划。为了避免混淆,可以使用 0 或 7 来表示星期日。root 参数意味着命令将以 root 用户身份运行。
为什么这么早?因为,假设在每个人都进入办公室之前,对网络带宽的竞争需求会较低。为什么是 5:21 而不是 5:00?因为你不想养成将所有脚本都安排在每小时顶点(或任何特定时间)的习惯,因为这可能会最终导致冲突。最好是错开时间。
你可以直接将那一行添加到 /etc/crontab 文件中,它将以相同的方式运行,但不需要创建一个单独的文件。但我不建议这样做。这样做可能不会在世界范围内引发僵尸末日,但仍然是一个糟糕的想法。你看,在系统升级期间,crontab 文件可能会被覆盖,你的自定义命令将会丢失。
那么,crontab 文件有什么用呢?正如你应该能够从文件内容中看到的那样(图 5.3),这是执行 /cron.? 目录中脚本的调度器。花一两分钟自己查看每个命令。
图 5.3。详细说明的 /etc/crontab 文件,展示了四个任务,每个任务都旨在启动 /etc/cron.? 目录的内容

图 5.3 中最后三个命令开头的 test -x 确认了一个名为 anacron 的二进制程序存在且可执行。如果其中任何一个不成立,那么 /cron.? 目录中的脚本将会运行。test -x 命令在脚本编写中通常很有用,当你需要在启动相关操作之前确认对象的状态时。
你肯定不想把/etc/crontab 文件留给专业人士。但鉴于 Linux 关心你的感受,他们给了你自己的 crontab 来玩。这个 crontab 将以你的用户身份而不是 root 身份运行命令,只有你拥有的权限——并且在升级过程中不会被覆盖。
想看看你已经安排了什么?运行crontab -l。如果你还没有添加任何命令,你可能会看到以下消息:没有为yourname设置 Crontab。
$ crontab -l
no crontab for ubuntu
你可以使用crontab -e编辑你的 crontab。第一次编辑时,你会被要求选择一个文本编辑器。如果你已经熟悉 Nano,就选择它,因为它可能是列表中三个中最容易使用的:
$ crontab -e
no crontab for ubuntu - using an empty one *1*
Select an editor. To change, run 'select-editor'. *2*
1\. /bin/nano *3*
2\. /usr/bin/vim.basic
3\. /usr/bin/vim.tiny
Choose 1-3 [1]:
-
1 检查现有任务
-
2 选择一个文本编辑器。
-
3 Nano 编辑器选择
注意
默认情况下,在某些系统上,如果没有创建包含用户名的文件/etc/cron.allow,用户将无法创建 crontab 任务。然而,Debian/Ubuntu 允许直接使用单个 crontab 任务。
所有基于 cron 的工具对于可能一直运行的计算机(如生产服务器)都工作得很好。但如果你想在经常关闭的笔记本电脑上执行重要任务怎么办?告诉 cron(或 cron.daily 等)在周一早上 5:21 备份你的文件听起来很好,但你有多可能记得及时开机?正好——几乎不可能。是时候谈谈 anacron 了。
5.4. 使用 anacron 安排不规则的备份
我们还没有讨论的一个 cron 文件是anacrontab,这是你安排在每次系统启动后固定时间运行操作的地方。如果你想在笔记本电脑上备份这些文件,但又不能保证在一天中的任何给定时间都能开机,你可以在 anacrontab 中添加一行。
在 anacrontab 文件中,你应该注意到的就是条目只有两列用于控制时间,而不是cron命令中使用的五列。这是因为 anacron 不是基于绝对时间工作,而是相对于最近的系统启动。以下是文件的全部内容:
# /etc/anacrontab: configuration file for anacron
# See anacron(8) and anacrontab(5) for details.
SHELL=/bin/sh
PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/
usr/sbin:/usr/bin *1*
HOME=/root
LOGNAME=root
# These replace cron's entries
1 5 cron.daily run-parts --report /etc/cron.daily *2*
7 10 cron.weekly run-parts --report /etc/cron.weekly
@monthly 15 cron.monthly run-parts --report
/etc/cron.monthly
-
1 anacrontab 的 PATH 中的目录。这些位置中的文件可以在不引用完整目录树的情况下进行引用。
-
2 此任务每天执行一次/etc/cron.daily/目录中的任何脚本。
例如,anacrontab 文件中的cron.daily行在启动后每隔一天运行,正好在启动后 5 分钟,无论何时发生。另一方面,cron.weekly命令在每隔七天(即自上次运行以来至少过去了七天)后运行,启动后 10 分钟。
你可能也对那些cron.?命令感到好奇;它们不是已经通过/etc/crontab文件运行了吗?是的,但前提是系统上 anacron 没有激活。正如你从 crontab 文件中看到的,anacron 被赋予了比 cron 更高的优先级。考虑到所有这些,下面是如何为你的笔记本电脑运行备份脚本的方法:
1 10 myBackupJob /home/myname/backup.sh
这个命令每天在系统启动后 10 分钟内最多运行一次backup.sh脚本。作业标识符是myBackupJob,一个包含作业状态信息的同名日志文件将被保存到/var/spool/anacron/目录中。
5.4.1. 运行 S3 同步任务
现在,最后,你已经学习了关于脚本的所有知识——AWS S3、cron 和 anacron——你终于准备好做出明智的选择:安排备份的最佳方式是什么?而且,像往常一样,正确答案取决于上下文。
如果你想要确保备份即使在机器不总是运行的情况下也能完成,你可以在 anacrontab 文件中添加类似这样的行(假设你已经在/etc/中有一个同名的可执行脚本文件):
1 10 myDailyBackup /etc/s3script.sh
这个例子每天都会运行脚本(1),在系统启动后 10 分钟(10)执行。对于 24/7 的服务器,你可以将特定于语法的指令添加到用户的 crontab 中,或者在一个文件中添加到/etc/cron.d/目录中(尽管你可能需要将用户名如 root 添加到 cron.d 版本中)。
下一个例子每天早上 5:47 运行脚本,每个月每一天,每周每一天(* * *):
47 5 * * * /home/myusername/s3script.sh *1*
- *1. crontab 语法每天早上 5:47 运行脚本
在这个例子中,因为我们谈论的是一个简单的脚本,你也可以通过将aws命令本身插入到 cron 文件(如 anacrontab)中来同样有效地做到这一点:
47 5 * * * username /usr/local/bin/aws s3 sync
/home/username/dir2backup s3://linux-bucket3040
可以直接将像这种备份操作的具体命令插入到 crontab 文件中。注意用户名和aws的绝对位置。这清楚地确立了你要运行的过程的所有权和运行它的二进制文件的文件系统位置。
5.5. 使用 systemd 定时器安排定期备份
如果对新的 systemd 定时器替代 cron 有批评,那可能是因为它明显更复杂,需要更多步骤来设置。你不仅需要创建你的脚本,然后将文件复制到正确的 cron 目录中,你还需要创建并保存两个单独的文件到不为人知的目录位置(这还不是所有 Linux 发行版的行业标准),然后运行两个systemctl命令。
可能复杂,但绝对不是不可逾越的。systemd 定时器带来了一些显著的优势,包括与其他系统服务的更深层次集成(包括日志)以及根据系统状态的变化执行命令的能力(例如,有人连接 USB 设备),而不仅仅是设定时间。
我会留给你时间去探索那些你感兴趣的特定、深入的功能。但我会带你一步步完成一个简单的备份。你的任务?生成一个动态命名的 tar 归档文件,用于备份你服务器上运行的 Apache 网站。为了演示目的,你可以选择任何目录进行备份,而且你不需要配置和启动一个网站来完成这个任务。
首先,看看你可能已经运行的所有定时器。我的定时器通过systemctl list-timers --all命令显示在图 5.4 中。
图 5.4. systemctl list-timers --all命令为所有现有的 systemd 定时器作业提供了丰富的历史数据。

这些定时器都是系统自动创建的。要创建你自己的,从一个备份脚本开始。深入挖掘我丰富多彩、富有创意的内心世界,我会把我的脚本文件命名为 site-backup.sh。下面是这个脚本的样子:
#!/bin/bash
NOW=$(date +"%m_%d_%Y") *1*
tar czvf /var/backups/site-backup-$NOW.tar.gz /var/www
- 1 将系统日期分配给环境变量$NOW
如果我的归档文件名总是包含创建日期,那么识别归档文件会容易得多。为了做到这一点,我将当前系统日期分配给$NOW变量的值,并将其包含在新归档的文件名中。下面是结果文件名可能的样子:
site-backup-11_28_2017.tar.gz
不要忘记使你的脚本文件可执行(chmod +x site-backup-11_28_2017.tar.gz)。实际上,永远不要忘记使你的脚本文件可执行!
$ chmod +x site-backup.sh
现在,你需要创建你的.service 和.timer 文件。正如我之前所写的,没有单一的位置来保存所有的服务文件,但/lib/systemd/system/和/etc/systemd/system/都可以工作。如果可以选择,我更喜欢/etc/systemd/system/,因为对我来说,这是一个容易记住且逻辑上合理的地方。你的体验可能会有所不同。
我将从.service 文件开始,我将称之为 site-backup.service。服务文件在 systemd 操作中是通用的。它们旨在以统一、可预测的方式描述和定义系统服务。Description值应包含你认为可以准确描述服务的任何文本,而ExecStart行指向可执行资源的位置:在这个例子中是脚本。这是 systemd 需要了解你想要做什么的所有信息:
[Unit]
Description=Backup Apache website
[Service]
Type=simple
ExecStart=/home/username/site-backup.sh *1*
[Install]
WantedBy=multi-user.target
- 1 服务要运行的可执行资源
.timer 文件,这是 systemd 定时器特有的,告诉 systemd 何时运行关联的服务。关联是通过[Timer]部分的Unit行设置的,在这个例子中,它指向我的 site-backup.service 文件。请注意,在这个例子中,OnCalendar的值被设置为每天早上 5:51 执行(*-*-*),而Unit的值是 site-backup.service 文件:
[Unit]
Description=Backup Apache website - daily
[Timer]
OnCalendar=*-*-* 5:51:00 *1*
Unit=site-backup.service *2*
[Install]
WantedBy=multi-user.target
-
1 控制服务执行的计划设置
-
2 与此定时器关联的服务单元
有这些文件在位,您可以使用 systemctl start 启动服务。此外,您可以使用 systemctl enable 将其设置为每次系统启动时自动加载:
# systemctl start site-backup.timer
# systemctl enable site-backup.timer
对您服务的状态感到好奇?is-enabled 和 is-active 应该是一个好的开始:
# systemctl is-enabled backup.timer
enabled
# systemctl is-active backup.timer
active
最后,当您编辑您的 .timer 文件时,您需要更新系统。在练习本章所学内容的过程中,您可能需要进行大量的编辑。当然,您会想知道如何做到这一点。好吧,这里就是:
# systemctl daemon-reload
摘要
-
编写良好的 Bash 脚本可以高效且可靠地自动化复杂和简单的管理任务。
-
Linux 在 /etc 目录中用纯文本文件(名为 passwd、group、shadow 和 gshadow)存储用户账户和认证信息。
-
您可以将本地数据备份到 S3 存储桶,并通过命令行直接管理其生命周期。
-
将可执行脚本复制到 /etc/cron.? 中的一个目录会导致它以适当的间隔运行。
-
在 anacrontab 文件中添加指令将执行与系统启动相关的命令,而不是在绝对时间。
-
systemd 计时器可以根据绝对时间和系统事件(如硬件状态的变化)来设置运行。
关键术语
-
所有 Linux 命令在完成时都会输出 退出代码:0 表示成功执行,但所有正整数都可以由程序设置来表示各种失败状态。
-
通过使用 访问密钥(访问密钥 ID 和秘密访问密钥)来保护对 AWS 资源的命令行访问。
-
存储桶 是 AWS 对象,它在操作系统目录中工作方式几乎相同。
安全最佳实践
-
将系统账户(如 syslog 和理想情况下甚至 root)锁定,以防止它们被用于远程登录。
-
在您的安全规划中包含异地备份,这为数据可靠性增加了另一层。
-
总是保护您的(AWS)访问密钥,不用说密码和加密密钥对,免得任何形式的公开暴露。
命令行审查
-
#!/bin/bash(所谓的“shebang 行”)告诉 Linux 您将使用哪个 shell 解释器来执行脚本。 -
||在脚本中插入一个 或 条件。想想看,这是“左边的命令成功”或“执行右边的命令”。 -
&&- 在脚本中插入一个 和 条件。想想看,这是“如果左边的命令成功”然后“执行右边的命令”。 -
test -f /etc/filename检查指定的文件或目录名是否存在。 -
chmod +x upgrade.sh使脚本文件可执行。 -
pip3 install --upgrade --user awscli使用 Python 的 pip 软件包管理器安装 AWS 命令行界面。 -
aws s3 sync /home/username/dir2backup s3://linux-bucket3040将本地目录的内容与指定的 S3 存储桶同步。 -
21 5 * * 1 root apt update && apt upgrade(一个 cron 指令)在每天早晨 5:21 执行两个apt命令。 -
NOW=$(date +"%m_%d_%Y")将当前日期分配给脚本变量。 -
systemctl start site-backup.timer激活一个 systemd 系统定时器。
测试自己
1
Linux 脚本中用于引入注释的字符是什么?
- !
- //
- ^
2
Linux 脚本中
||的作用是什么?
- 或者
- 和
- If
- Comment
3
/etc/shadow 文件中保存的数据类型是什么?
- 账户组及 shell 数据
- 组成员数据
- 加密账户密码
- 加密组密码
4
以下哪个操作将在你的 AWS S3 账户中创建一个新的存储桶?
s3 mb s3://mybucketaws s3 mb s3://mybucketaws s3 cb s3://mybucketaws s3 sync mb s3://mybucket5
哪个命令可以让你输入一个以你自己的用户身份运行的 cron 指令?
nano anacrontabcrontab -lnano /etc/crontabcrontab -e6
以下哪个将在每周一早上运行命令?
21 * 1 * * root apt update && apt upgrade21 5 * * 1 root apt update && apt upgrade21 1 * * 0 root apt update && apt upgrade21 5 * 4 * root apt update && apt upgrade7
以下哪个选项不适用于始终运行的计算器?
- crontab
- anacron
- systemd timer
- anacrontab
8
systemctl enable site-backup.timer命令的作用是什么?
- 手动加载网站备份定时器
- 配置网站备份定时器在系统启动时加载
- 显示网站备份定时器的当前状态
- 强制网站备份定时器在计算机关闭前运行
答案键
1.
c
2.
a
3.
c
4.
b
5.
d
6.
b
7.
a
8.
b
第七章. 网络服务器:构建 MediaWiki 服务器
本章涵盖
-
使用 Apache 构建动态网络服务器
-
使用 SQL 数据库管理后端应用程序数据
-
识别和解决应用程序包依赖关系
-
安装和配置 MediaWiki CMS
你有一个小型公司博客要发布,或者有跨越 30 年、100,000 页的技术和公司数据?你需要某种内容管理系统(CMS)。如果你在好奇,CMS是一种设计为创建和管理数字内容框架的应用程序。你可能遇到的一些流行的 CMS 应用程序包括 WordPress 和 Joomla。
维基可以是一种特别有效的管理大量贡献者社区的方式。它是一种其架构有意地分散化的 CMS,允许用户不仅自由地协作于内容本身,还可以协作于整个数据集的更大结构。维基引擎是构建维基的平台,通常使用某种简单直观的标记语言。MediaWiki 是一个流行的开源维基引擎的例子,但 Atlassian Confluence 是一个成熟的商业替代品。
目前 MediaWiki 对我们来说可能并不那么重要。我之所以选择专注于安装 MediaWiki,是因为它是一个很好的说明在 Linux 上构建网络服务器(通常称为LAMP 服务器)的过程的方法。考虑到今天互联网上超过三分之二的网络服务器都在 Linux 上运行,这意义重大。
请不要误解:MediaWiki 并不缺乏魅力。毕竟,它是最初为支持构成维基百科和其他维基媒体基金会项目的数千万篇文章而创建的 CMS。如果你在寻找一种强大且可靠的方式来管理大量媒体内容,无论是记录你公司流程的私人收藏还是面向公众的帮助页面,MediaWiki 可能是一个不错的选择。但那些特定用例并不常见,不足以证明在像这本书这样的核心技能书中包含它们的合理性。
然而,通过 MediaWiki 的部署过程可以让你了解组成 Linux 网络服务器的软件包,以及它们如何组合起来使那些两到三个网站成为可能。作为一个 Linux 管理员,你很可能会被要求构建支持各种应用程序的网络服务器,因此你对学习如何做这些事情很感兴趣,对吧?
7.1. 构建 LAMP 服务器
如果你或你的业务有信息、应用程序或服务,那么你很可能希望让它们可以通过网络浏览器进行访问。网络服务器是运行在计算机上的软件,它允许本地托管资源被网站访客查看和消费。为了明确,网络服务器这个术语也经常用来描述托管网络服务器软件的计算机。
静态还是动态网站?
本节将指导您构建一个动态网站的过程。这是一个通过服务器端操作生成页面的网站。也可以创建静态网站,这类网站大部分只提供纯 HTML 文件,并将所有工作委托给客户端的网页浏览器。
对于更简单的网站,静态网站可以是一个快速且经济的选择。但鉴于这与 Linux 无关,我在这里就不再多说了。另一方面,我的《一个月午餐时间学习亚马逊网络服务》(Manning,2017)的第六章(Chapter 6)中,有一个很好的使用 AWS 的 S3 来托管静态网站的演示。
如图 7.1 所示,大多数 Linux 网络服务器都是建立在所谓的 LAMP 服务器的基础之上。LAMP代表 Linux、Apache 网络服务器管理软件、MySQL 或 MariaDB 数据库引擎,以及 PHP 服务器端脚本语言(或者,也可以是 Perl 或 Python)。除了 Linux(希望您已经熟悉)之外,这些内容将是本章的重点。
图 7.1. Apache 网络服务器软件向外部客户端暴露基于 HTTP 的资源,并协调内部服务。

LAMP 服务器是一种常见的 Linux 配置,至少 Ubuntu 有它自己的安装元包。这个例子末尾的撇号(^)标识目标为一个特殊包,捆绑在一起以简化常见软件堆栈的安装:
# apt install lamp-server^
那条命令会在您创建数据库密码后,自动在您的系统上创建一个工作的网络服务器,除了创建一些网站内容外,您无需做任何事情。将您的网页浏览器指向服务器的 IP 地址应该会显示 Apache 安装时创建的欢迎页面。
但自动化并不总是最佳解决方案。有时您可能需要通过指定特定的发布版本来自定义软件堆栈,以确保应用程序兼容性,或者通过替换一个包来替代另一个(例如,您很快就会看到,MariaDB 替代 MySQL)。在这种情况下,手动设置将特别有用,因为它会迫使您更好地理解每个部分是如何工作的。这就是我在本章中采取的方法。以下是需要完成的任务列表,以帮助您达到目标:
-
安装 Apache
-
在网页文档根目录中添加一个或两个网页
-
安装一个 SQL 引擎(在本例中为 MariaDB)
-
安装 PHP 服务器端脚本语言
-
安装和配置 MediaWiki
7.2. 手动设置 Apache 网络服务器
网络服务器软件的主要任务是指引网站访客访问服务器主机上的正确目录和文件,因此应提供适当的网站资源。从实际角度来说,在浏览器地址栏中输入统一资源定位符(URL)地址实际上是对运行在远程网站主机上的网络服务器软件的请求,以从主机文件系统中检索网页、视频或其他资源并将其加载到您的浏览器中。网络服务器软件通常会与主机服务器上的其他系统紧密集成,如网络、安全和文件系统工具,以确保对本地资源的良好管理。
尽管这是一个波动很大的市场,但开源的 Apache HTTP 服务器通常在所有平台上主导着网络服务器市场。由于它非常受欢迎,尽管 Apache 有包括 Nginx(也是跨平台)和微软的 IIS(仅在 Windows 服务器上运行)在内的严重竞争对手,我仍然会坚持使用 Apache。(拜托。你真的认为我会用整整一章来介绍 IIS 吗?)
7.2.1。在 Ubuntu 上安装 Apache 网络服务器
安装 Apache 本身很简单。在 Debian/Ubuntu 上,使用apt install apache2。如果您正在 Ubuntu 机器上操作,一旦安装了 Apache,就没有什么阻止您立即打开浏览器并访问您的实时网站了。您将看到图 7.2 中显示的介绍页面。
注意
您将使用的 URL 来访问在您的工作站上运行的 Apache 网站是localhost。如果您选择在 LXC 容器或 VirtualBox 虚拟机(VM)上工作,那么您将使用机器的 IP 地址作为 URL。为了确保您将能够访问在 VirtualBox VM 上运行的网络站点,请确保它配置为使用桥接适配器(就像您在第二章中做的那样)。
图 7.2。当浏览器指向您的服务器 URL 或 IP 地址时,显示的 Apache 默认页面包括一些重要的基本配置和导航信息。

Ubuntu 让事情变得简单。但那样有什么乐趣呢?再次强调,学习如何在 CentOS 上工作将帮助您理解底层发生了什么。即使您现在没有运行 CentOS、Fedora 或 Red Hat 的虚拟机(VM)或容器,我也建议您至少熟悉 CentOS 的工作方式。
本章中您将看到的 CentOS 进程与 Ubuntu 的不同。为了清晰起见,我决定将它们分开,将所有 CentOS 说明放在自己的部分:7.6。如果您打算在 CentOS 上安装 Apache,请前往那里获取详细信息。
7.2.2。填充您的网站文档根目录
太棒了!您已经拥有了一个运行中的网站。考虑到网站上除了 Apache 欢迎页面外没有其他内容,不要期望赢得任何奖项(或产生太多收入)。您将需要添加一些内容。为此,您需要知道内容放在哪里。
内容的位置由 Apache 配置文件中的 DocumentRoot 设置控制。在 CentOS 系统上,配置设置在 /etc/httpd/conf/ 目录下的 httpd.conf 文件中。Ubuntu 用户将在 /etc/apache2/sites-available/ 目录下的一个名为 000-default.conf 的文件中找到它。无论如何,在配置文件中搜索 DocumentRoot 可能会显示类似这样的值:
DocumentRoot "/var/www/html"
这意味着 Apache 将将所有传入的浏览器请求都指向 /var/www/html/ 目录下的文件。你可以更改这个值,使其指向你文件系统上的任何位置。实际上,尽管这不是本书的主题,如果你计划在服务器上托管多个网站,你可以将多个文件系统位置指向这些网站。
如果你以前从未这样做过,为什么不现在花几分钟时间构建一个简单的个人网站呢?在你的文档根目录中创建一个名为 index.html 的文本文件。(此文件将使用相同名称覆盖 Apache 的欢迎页面。)你可以在文件中输入自己的欢迎文本,并添加一个指向第二个 HTML 文件和图形图像的链接。确保创建那个第二个文件以及图像。index.html 文件可能看起来像这样:
<h2>Welcome!</h2>
Take a look at our <a href="info.html">company history</a>.
<br>
And how about a look at our new company logo: <img src="logo.png">
7.3. 安装 SQL 数据库
快速浏览一下美国政府的劳工统计局(BLS)职业展望手册页面,针对网络和计算机系统管理员(mng.bz/kHN3),如图 7.3 所示。[#ch07fig03]。考虑到每个页面的九个标签页上显示的所有内容,文本量相当大。但我怀疑其中非常少的内容是由人类手动添加到这个页面的。
图 7.3. 劳动力统计局的一页。网络和计算机系统管理员做什么的标题可能是由类似这样的内容扩展而来:$selected_occupation 做什么。

更可能的情况是,BLS 服务器上的数据库包含数以兆字节的数据,其中包含与所包含的成千上万种职业相关的结构化信息。这些数据可能随后按信息类别(摘要、工作环境等)进行组织。当我从 BLS 菜单(或通过互联网搜索引擎)请求这个页面时,BLS 网络服务器可能已从数据库中请求相关原始数据,并按你在 图 7.3 中看到的方式动态组织在页面上。
网站可以利用动态访问后端安装的数据库引擎的许多其他方式,但这只是一个很好的示例。在类似 BLS 的项目(或我们的 MediaWiki 网站)中可能使用的数据库引擎类型被称为 关系数据库,这是一种将数据组织成由列和行组成的表格的工具。单个行中的数据称为 记录。记录通过一个称为 键 的 ID 值来识别,它可以用来在表之间引用记录。
结构化查询语言(SQL)是管理关系数据库中数据的标准语法。数据库引擎是用于管理关系数据库数据并将其通过 SQL 语法暴露给管理员和自动化过程的软件。
我将很快向你展示如何创建和显示一个简单的数据库表。但首先,你必须安装自己的数据库引擎,这样你才能亲自跟随操作。因为我们的长期目标是完整的 LAMP 服务器,所以将它安装在你构建 Apache 网络服务器相同的计算机/VM/容器上是有意义的。(CentOS 的方法可以在本章末尾找到。)
# apt update
# apt install mariadb-server *1*
- 1 在你的服务器上安装 MariaDB。
为什么我选择 MariaDB 而不是 MySQL?它们都遵循完全相同的 MySQL 标准。事实上,它们最初都是由同一群人创建的。两者都很好,但至少目前,MariaDB 似乎得到了更活跃的开发和支持。除了这两个之外,还有其他重要的 SQL 数据库引擎在 IT 世界中广泛使用,包括 Oracle、PostgreSQL 和专为 AWS 工作负载构建的 Amazon Aurora。
为什么不检查你刚刚安装的数据库(DB)的状态?你可以使用systemctl来确认数据库正在运行:
# systemctl status mysql *1*
? mysql.service - MySQL Community Server
Loaded: loaded (/lib/systemd/system/mysql.service;
enabled; vendor preset: enabled)
Active: active (running) since Wed 2018-05-02 12:26:47 UTC; 6h ago
Process: 396 ExecStartPost=/usr/share/mysql/mysql-systemd-start post
(code=exited, status=0/SUCCESS)
Process: 318 ExecStartPre=/usr/share/mysql/mysql-systemd-start pre
(code=exited, status=0/SUCCESS)
Main PID: 395 (mysqld)
Tasks: 28
Memory: 126.3M
CPU: 20.413s
CGroup: /system.slice/mysql.service
??395 /usr/sbin/mysqld
May 02 12:26:29 base systemd[1]: Starting MySQL Community Server...
May 02 12:26:47 base systemd[1]: Started MySQL Community Server.
- 1 无论你安装了 MySQL 还是 MariaDB,Linux 都使用 mysql 命令来引用数据库。
7.3.1. 强化 SQL
一旦安装了 MariaDB,强化数据库安全性总是一个好主意,因此你将需要运行 mysql_secure_installation 工具。如果你在安装过程中没有被提示创建 root MariaDB 密码(这种情况相当常见),那么你需要运行 mysql_secure_installation,因为这也是你设置认证的方式。运行此工具将显示以下交互式对话框:
# mysql_secure_installation *1*
NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
SERVERS IN PRODUCTION USE! PLEASE READ EACH STEP CAREFULLY!
In order to log into MariaDB to secure it, we'll need the current
password for the root user. If you've just installed MariaDB, and
you haven't set the root password yet, the password will be blank,
so you should just press enter here.
Enter current password for root (enter for none): *2*
OK, successfully used password, moving on...
Setting the root password ensures that nobody can log into the MariaDB
root user without the proper authorisation.
Set root password? [Y/n]
-
1 注意,此命令可能需要 sudo 权限,这可能会在以后造成麻烦。请保持关注。
-
2 输入数据库 root 用户的密码,而不是 Linux root 用户的密码。
mysql_secure_installation 推荐的价值旨在防止匿名和远程用户访问你的数据。除非你计划仅为此数据库进行测试,并且它不会包含重要或敏感数据,否则你将希望接受默认设置。
注意
如果 mysql_secure_installation 仅在以 sudo 运行时才起作用,那么请以此方式创建你的密码。但请记住,这将导致一个以后需要解决的问题。你将在下一节中看到如何解决这个问题。
7.3.2. SQL 管理
现在,如承诺的那样,我将向你展示一些简单的数据库管理命令。实际上,你可能永远不需要直接运行这些命令,因为大多数数据库都是从应用程序代码中访问的,而不是从命令行。考虑到手动管理 SQL 数据库中常见的数千甚至数百万条数据记录是多么不方便,这很有道理。你将在本章稍后安装和配置 MediaWiki 时看到这个自动化的应用程序/数据库关系的完美示例。
仍然,有时你可能需要手动创建自己的数据库。也许当你组装一个新应用程序时,你需要一些测试数据来工作。或者,也许你的新业务起步缓慢,与其投资新应用程序,不如现在手动管理客户更有意义。至少你应该知道如何操作。
如果你觉得自己现在还不足以深入数据库,可以自由跳转到第 7.4 节。但在你离开之前,还有一件更重要的事情我应该谈谈。
默认情况下,你将使用 root 用户访问和管理你的 MariaDB 或 MySQL 安装中的数据库。这可不是什么好主意。出于安全原因,应该由只有完成特定工作所需权限的常规数据库用户拥有和管理各个数据库。然而,为了演示的简便,我将冒险使用 root 用户。稍后,当我向你展示如何设置 MediaWiki 数据库时,我会正确地创建一个非 root 用户。
访问数据库
无论你安装了 MariaDB 还是 MySQL,你都应该使用mysql登录到你的 shell,后面跟着-u root。这告诉数据库你想要以 root 用户身份进行身份验证。-p表示你将被提示输入你的 MariaDB 密码:
$ mysql -u root -p *1*
Enter password:
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 10
Server version: 5.5.52-MariaDB MariaDB Server
Copyright (c) 2000, 2016, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type \c to clear the current input statement
MariaDB [(none)]> *2*
-
1 通过指定用户登录,并提示输入用户的密码。
-
2 [(none)]的值将更改为活动数据库的名称。
这里就是之前暗示的麻烦可能浮现的地方。MariaDB 可能不会让你登录,除非你以sudo运行mysql命令。如果发生这种情况,使用sudo登录并提供你创建的 MariaDB 密码。然后在这些 MySQL 提示符下运行这三个命令(用your-password替换你的密码):
> SET PASSWORD = PASSWORD('your-password');
> update mysql.user set plugin = 'mysql_native_password' where User='root';
> FLUSH PRIVILEGES;
下次你登录时,你应该不再需要sudo,更重要的是,MediaWiki 应该能够正确地完成其工作。解决了这个小麻烦后,看看你的 SQL 环境。这是创建新数据库的方法:
MariaDB> CREATE DATABASE companydb; *1*
- 1 大多数 MySQL 命令必须以分号 (😉 结尾。忘记这一点将阻止命令的执行。
假设你的公司需要存储客户联系信息。你可以在数据库中创建一个新的联系人表,如下所示:
MariaDB> use companydb
MariaDB> CREATE TABLE Contacts (
ID int,
LastName varchar(255),
FirstName varchar(255),
Address varchar(255),
City varchar(255)
); *1*
- 1 这是单个命令的最后部分,为了清晰起见,被扩展到七行。
刚刚获得第一个客户?恭喜!以下是输入新信息的方法:
MariaDB> INSERT INTO Contacts (ID, LastName, FirstName, Address, City) *1*
VALUES ('001', 'Torvalds', 'Linus', '123 Any St.', 'Newtown');
- 1 这行定义了表格中哪些列将添加新值。
想看看你做了什么?要显示你新创建的 Contacts 表中的所有数据,请输入 select *:
MariaDB> select * from Contacts;
+------+----------+-----------+-------------+---------+
| ID | LastName | FirstName | Address | City |
+------+----------+-----------+-------------+---------+
| 1 | Torvalds | Linus | 123 Any St. | Newtown |
+------+----------+-----------+-------------+---------+
1 row in set (0.00 sec)
注意你表中的 ID 值,它可以作为你记录的关键值。当你完成所有操作后,你可以通过输入 exit 命令关闭 MariaDB shell。
如果,如我之前提到的,你可能永远不需要手动执行任何这些任务,为什么还要在这里阅读它呢?因为,为了将自动化操作与你的数据库集成,你几乎肯定需要在脚本和应用程序代码中包含 MySQL 语法的变体。即使是简单的网络购物门户也依赖于外部数据。你可能不是编写代码的人,但几乎可以肯定的是,你认识或爱的人会,他们可能需要你的帮助来建立数据库连接。这对于不知疲倦的 Linux 系统管理员来说,是日常工作中的一部分。
创建 MediaWiki 数据库用户
还有另一项数据库管理任务。正如你所看到的,MariaDB 默认附带一个活跃的 root 用户。但由于该用户拥有对系统所有表的完全管理权限,因此使用 root 进行日常操作并不是一个好主意。从安全角度考虑,你最好为每个数据库消费者创建唯一的用户,并只授予他们所需的访问权限。
让我们再次登录到 MariaDB,并为 MediaWiki 创建一个名为 wikidb 的新数据库,以便稍后使用。然后,你将创建一个名为 mw-admin 的用户。FLUSH PRIVILEGES 命令启用新设置,并授予 mw-admin 用户对 wikidb 数据库的完全控制权:
mysql> CREATE DATABASE wikidb;
Query OK, 1 row affected (0.01 sec)
mysql> CREATE USER 'mw-admin'@'localhost' IDENTIFIED BY 'mypassword';
Query OK, 0 rows affected (0.00 sec)
mysql> GRANT ALL PRIVILEGES ON wikidb.* TO 'mw-admin'@'localhost'
IDENTIFIED BY 'mypassword';
mysql> FLUSH PRIVILEGES;
Query OK, 0 rows affected (0.00 sec)
mysql> exit
7.4. 安装 PHP
LAMP 的最后一个组成部分是 PHP 脚本语言。PHP 是一个可以用来编写你自己的网络应用程序的工具。预构建的 PHP 应用程序通常被第三方应用程序(如 MediaWiki)用来访问和处理系统资源。因此,可以安全地假设,你将需要在你的 LAMP 服务器中用到 P。
7.4.1. 在 Ubuntu 上安装 PHP
尽管你迄今为止看到的示例可能表明,Ubuntu 安装并不总是比 CentOS 简单。但这里有一个任务,使用 Ubuntu 方式会更快完成。想要最新的 PHP 吗?在 Ubuntu 上只需执行 apt install php 命令,它就能满足你的需求。因为你希望它与 Apache 顺利协作,所以你还需要一个扩展:
# apt install php
# apt install libapache2-mod-php
你应该养成在更改网络服务器系统配置时重启 Apache 的习惯。以下是方法:
# systemctl restart apache2
就这些。PHP 现在应该已经启动了。
7.4.2. 测试 PHP 安装
为了确保您的 PHP 安装是活跃的(并且了解 PHP 的本地环境和资源集成),在 Apache 网络文档根目录下创建一个新的文件,使用 .php 文件扩展名。然后按照以下所示填充文件的剩余行文本:
# nano /var/www/html/testmyphp.php
<?php
phpinfo();
?>
现在转到浏览器,输入运行 PHP 的机器的 IP 地址(或者如果您正在工作的桌面,则为 localhost)以及您创建的文件名:
10.0.3.184/testmyphp.php
您将看到一个长网页(就像您在 图 7.4 中看到的那样),分为多个部分,描述了您的计算机以及 PHP 与其通信的方式。
注意
完成后,请确保删除或限制对 testmyphp.php 文件的访问。将此类有关您系统的信息公之于众是一种严重的安全漏洞。
图 7.4. phpinfo 显示的配置和环境数据的小样本

与之前一样,在 CentOS 上完成所有这些操作将在以后进行。现在,让我们为 MediaWiki 创建一个专用的数据库。
7.5. 安装和配置 MediaWiki
有文档和媒体要共享(就像我们在本章开头讨论的那样)?您来对地方了。以下是该过程的分解:
-
下载并解压 MediaWiki 存档包
-
识别和安装必要的软件扩展
-
将 MediaWiki 连接到您的 MariaDB 数据库
-
运行并测试安装。
转到 MediaWiki 下载页面 (www.mediawiki.org/wiki/Download),点击下载 MediaWiki 以获取最新包。如果您更愿意通过命令行直接将文件拉入您的服务器,可以右键单击下载链接,选择复制链接地址,并将地址粘贴到终端窗口中,包括 wget 程序:
$ wget https://releases.wikimedia.org/mediawiki/1.30/\
mediawiki-1.30.0.tar.gz *1*
- 1 当您阅读此内容时,确切的地址(和版本)可能已经更改。
注意
如果在运行上一条命令时遇到 -bash: wget: Command Not Found 错误,那么您需要安装 wget。
使用 tar 对下载的存档进行操作将创建一个包含所有提取的文件和目录的新目录。您需要将整个目录层次结构复制到文件系统中的工作位置。如果 MediaWiki 将是您服务器上唯一的网络应用程序,那么这很可能意味着您的网络根目录:
$ tar xzvf mediawiki-1.30.0.tar.gz
$ ls
mediawiki-1.30.0 mediawiki-1.30.0.tar.gz
# cp -r mediawiki-1.30.0/* /var/www/html/
如果 MediaWiki 只是一系列应用程序中的一个,那么您可能希望在文档根目录内创建一个子目录,以便以实用和可预测的方式公开服务。例如,将文件放入名为 /var/www/html/mediawiki/ 的目录中,这意味着您的用户将在 www.example.com/mediawiki 找到 MediaWiki,假设您正在使用 example.com 作为您的公共域名。
从这个点开始,MediaWiki 的浏览器界面接管了。将你的浏览器指向服务器 IP 地址上 MediaWiki 目录中的 index.php 文件(如果你在桌面上运行所有这些,则是 localhost)。如果你将文件复制到了/var/www/html/根目录,那么它看起来可能像这样:
10.0.3.184/index.php
如果,相反,你为 MediaWiki 创建了一个子目录,它可能看起来像这样:
10.0.3.184/mediawiki/index.php
通常,当我第一次安装应用程序时,我会遵循一个优秀的在线指南的指示,最好是项目开发者自己创建和维护的。很多时候,这样的指南会给我提供一个长长的包依赖列表,我会快速阅读(通常读得太快)然后复制粘贴到apt install命令中。
然而,这次,我倒转了整个过程,只安装了 Apache、MariaDB 和 PHP 包的基础部分。我知道这不会给 MediaWiki 提供足够的工作环境,这正是我想要的。还有什么更好的方法来了解所有复杂的部分应该如何组合在一起呢?
7.5.1. 解决缺失扩展的问题
设计 MediaWiki 设置过程的人们明智地理解到事情并不总是顺利。如果发现你的配置中缺少某些东西,而不是默默失败,你会得到一个包含有用信息的错误页面。在这个例子中,如图 7.5 所示,我似乎缺少几个 PHP 扩展:mbstring 和 xml。
图 7.5. 一个有用的错误页面告诉我我的系统缺少两个扩展,并提供适当的 PHP 文档页面链接

我将使用apt search来查看哪些包与 mbstring 相关。由于我运行的是 PHP 7,php7.0-mbstring 包似乎是最有可能让 MediaWiki 的华丽面容重现笑容的:
$ apt search mbstring
Sorting... Done
Full Text Search... Done
php-mbstring/xenial 1:7.0+35ubuntu6 all
MBSTRING module for PHP [default]
php-patchwork-utf8/xenial 1.3.0-1build1 all
UTF-8 strings handling for PHP
php7.0-mbstring/xenial-updates 7.0.18-0ubuntu0.16.04.1 amd64
MBSTRING module for PHP *1*
- 1 为 PHP 7 提供多字节字符串编码
对 xml 和 php(apt search xml | grep php)的类似搜索告诉我有一个名为 php7.0-xml 的包似乎可以满足 MediaWiki 的 XML 需求。我将安装这两个包,然后使用systemctl重新启动 Apache:
# apt install php7.0-mbstring php7.0-xml
# systemctl restart apache2
所有这一切工作方式的美好之处在于,你(和我)不需要理解多字节字符串编码或甚至 XML 是什么,以及为什么 MediaWiki 没有它们会感到如此完全迷失。只要我们能信任应用程序的开发者,遵循他们的指示就没有什么问题。这假设我们能信任应用程序的开发者,而这往往很难评估。整个 Linux 软件包管理系统背后的哲学建立在这样一个前提上:官方仓库的管理者已经为我们完成了审查工作。这取决于我们希望他们是正确的。
回到我们的例子,如果一切按计划进行,刷新浏览器页面应该会带你回到最初的 MediaWiki 简介页面。当页面加载时,你会看到一个关于缺少 LocalSettings.php 文件的警告和一个设置维基的链接。当你点击链接时,你可以选择语言偏好,然后是 MediaWiki 环境检查页面和...更多麻烦!
最大的问题是缺少一个数据库驱动程序。这将是用于在 PHP 和我们的案例中 MariaDB 之间协商的软件。没有安装它确实是一个杀手。尽管图 7.6 中显示的建议包是 php5-mysql,但apt search告诉我们,我们更有可能使用 php-mysql 包成功。
图 7.6。明亮的x表示我们配置中的一个阻止性的漏洞;其他注释提供了不那么严重的警告。

还应包括为 APCu(缓存和优化 PHP 中间代码的框架的一部分)和 ImageMagick(一个图像处理工具)建议的 PHP 扩展。重新启动 Apache 并刷新浏览器窗口,你应该就绪了:
# apt install php-mysql php-apcu php-imagick
# systemctl restart apache2
你会在页面底部看到一个继续按钮。使用它。
7.5.2. 将 MediaWiki 连接到数据库
你通过“连接到数据库”页面提供的信息告诉 MediaWiki:
-
你在系统上安装了什么类型的数据库(在这个例子中是 MySQL 或兼容的)
-
数据库所在的位置(它可以是远程的,甚至是基于云的,但这是在本地服务器上,所以
localhost是正确的值) -
你希望给 MediaWiki 将使用的数据库取的名字(我将使用我之前创建的 wikidb 数据库)
-
现有的数据库用户账户的名称(在这个例子中是 mw-admin)
-
当前数据库账户的密码(这允许 MediaWiki 访问 MariaDB 并创建和管理其数据库;参见图 7.7)
图 7.7。MySQL 设置页面的一部分,其中你告诉 MediaWiki 如何连接到数据库
![图片]()
注意
如果不起作用?如果 MediaWiki 无法连接到你的数据库,请确认你使用的是正确的密码,但也请确保你能够从命令行登录到 MariaDB shell。
如果一切顺利,你将进入一系列屏幕,在那里你可以输入配置细节,如数据库设置、你的维基的名称(例如,在这个例子中是公司文档)以及一个用户名、密码和维基管理员账户的联系方式。此账户与你在 Linux 主机或 MariaDB 上已有的账户无关。
一些可选的设置问题让你可以设置用户权限的偏好、维基内容的版权、发送通知邮件的邮件服务器以及添加诸如浏览器内 WikiEditor 或反垃圾邮件软件等附加软件扩展。除了可能的返回电子邮件地址外,默认值应该可以工作。
当你完成时,MediaWiki 将开始其安装过程。当安装完成时,它会提示你下载一个名为 LocalSettings.php 的文件,并将其保存到 MediaWiki 根目录(在这个例子中是 /var/www/html/)。你可以使用 scp 将你保存的文件复制到你的用户主目录:
$ scp LocalSettings.php ubuntu@10.0.3.184:/home/ubuntu/
然后,从服务器上的命令行中,将其移动到文档根目录:
# cp /home/ubuntu/LocalSettings.php /var/www/html/
一切准备就绪后,回到之前使用的浏览器页面(例如 10.0.3.184/index.php)。这次,正如你在 图 7.8 中可以看到的,你将发现自己在新创建的维基百科的主页上。从现在开始,你的任务就是开始用你智慧和经验的丰富果实来充实你的维基百科。
图 7.8. 你可以通过点击顶部的“编辑”标签来添加和编辑页面内容以及链接。

随意花些时间阅读 MediaWiki 用户指南 (www.mediawiki.org/wiki/Help:Contents)。在这里,你将学习如何处理文件以及如何使用简单的标记语言进行编写。
7.6. 在 CentOS 上安装 Apache 网络服务器
如果你想要避免我们在 Ubuntu 安装过程中遇到的一些麻烦,考虑做一些预防性的研究,以找出 LAMP 拼图每个部分所需的精确版本。快速访问 MediaWiki 安装要求页面 (mng.bz/4Bp1) 应该会给你安装 Apache 网络服务器(或 CentOS 上的 httpd)所需的所有信息。一旦完成,获取软件就很简单了:
# yum install httpd
默认情况下,Apache 不会运行。你还记得如何在 systemd 中修复这个问题吗?不要偷看!我希望你已经猜到了:
# systemctl start httpd
# systemctl enable httpd
命令 systemctl start 启动服务,而 enable 则会在计算机启动时启动它。要确认 Apache 是否正在运行,你可以使用 curl 将网页打印到终端,指定 localhost 来告诉 curl 你想要的是本地正在提供的服务器默认页面。你可能需要安装 curl...我相信你现在应该没问题了。如果 curl 输出以这种方式开始的内容,那么 Apache 显然正在正常工作:
$ curl localhost
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd"><html><head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Apache HTTP Server Test Page
powered by CentOS</title> *1*
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
[...]
- 1 这个 Apache 测试 index.html 页面的标题属性
但这里有一个奇怪的现象:那个成功的 curl 命令意味着 Apache 正在运行,并且,希望你能知道,通过桥接适配器,你的计算机和 CentOS 虚拟机之间有网络连接。(第十四章 如果你在处理这个问题上有困难,将会很有帮助。)但你可能仍然无法从 GUI 浏览器中加载页面。你可能会遇到“无法访问此网站”的错误,而不是其他问题。问题出在哪里?
这个错误说明了 Ubuntu 哲学与 CentOS 的不同之处还有另一种有趣的方式。默认情况下,Ubuntu 没有安装任何类型的防火墙。(因为除了基本基础设施之外没有开放的网络服务,所以这并不像听起来那么疯狂。)Apache 一旦安装完毕,就准备好接受外部传入的流量。当然,有很多方法可以关闭网络访问以满足您的需求,但出厂时,您对全世界都是开放的。另一方面,CentOS 默认将所有端口都安全关闭。如果您想使您的 Web 服务器接收传入的 HTTP 请求,您首先需要打开 HTTP 端口(按照惯例,是端口 80)。
7.6.1. 理解网络端口
顺便问一下,什么是网络端口?它不过是一种识别特定服务器资源给网络用户的方式。想象一下,您的服务器正在托管两个不同的应用程序。访客可以使用其公共 IP 地址或相应的 DNS 域名(如 google.com 的 172.217.1.174)来访问您的服务器。但浏览器如何知道您想加载哪个应用程序呢?
当指定一个预定的端口时,可以指示应用程序监听传入服务器的流量。因此,一个应用程序可以使用端口 50501,另一个应用程序使用端口 50502。如图 7.9 所示,第一个应用程序将使用 192.168.2.10:50501 响应传入请求(假设 192.168.2.10 是您的服务器 IP 地址),第二个应用程序将期望使用 192.168.2.10:50502 的流量。
图 7.9.配置在单独网络端口上监听的应用程序(80 = 不安全的 HTTP;443 = 安全的 HTTPS;30303 = 自定义应用程序)

我们将在本书的后续章节中回到端口的问题,例如在第九章(kindle_split_017.xhtml#ch09)和第十四章(kindle_split_022.xhtml#ch14)。但您的 Web 服务器软件(如 Apache)将为您托管多个网站的服务器做大部分繁重的工作,自动将端口转换为本地文件系统位置。如果您考虑将您的 Web 服务器向多个网站开放,软件配置指南(如mng.bz/w6h4)是您开始的地方。
7.6.2. 控制网络流量
您如何控制对您网络的访问?一种方法是通过防火墙规则。在 CentOS 上,这通过 firewalld 服务和 firewall-cmd 工具来处理。在这种情况下,您需要添加http服务,并通过--permanent标志确保每次您重新启动服务或启动计算机时,规则都将生效。(我将在第九章(kindle_split_017.xhtml#ch09)中详细介绍防火墙。)要应用更改,请重新启动服务:
# firewall-cmd --add-service=http --permanent
success
# systemctl restart firewalld
完成这些后,您应该能够成功加载 CentOS 版本的 Apache 测试页面(图 7.10)。
图 7.10. CentOS Apache Web 服务器的默认网页

7.6.3. 在 CentOS 上安装 MariaDB
再次强调,安装本身很简单。但是,您需要手动启动 MariaDB,然后使用 enable 命令来配置它在系统启动时加载:
# yum install mariadb-server
# systemctl start mariadb
# systemctl enable mariadb
从这里,您可以遵循之前作为 Ubuntu 安装过程一部分概述的 MariaDB 设置步骤。
7.6.4. 在 CentOS 上安装 PHP
在撰写本文时,通过 CentOS 仓库提供的 PHP 默认版本仍然是 5.4,这远远落后于 Ubuntu 仓库中可用的最新版本。问题是当前的 MediaWiki 版本(1.30)无法与低于 PHP 5.5.9 的任何版本兼容。到您阅读本文时,问题可能已经自行解决。如果没有,您将需要使用网络搜索来查找包含更新版本 PHP 的仓库以进行安装。
我不是提到这一点是因为 CentOS 目前恰好落后一个或两个版本,而是因为类似的事情经常发生;有许多重要的教训要学习。第一个教训是在安装之前始终检查软件包版本。所有 Linux 软件包管理器在您跳入之前都会提供各种信息。养成阅读的习惯。以下是 PHP 安装软件包的软件摘要信息:
# dnf install php
Using metadata from Sun Jul 23 10:37:40 2017
Dependencies resolved.
======================================================================
Package Arch Version Repository Size
======================================================================
Installing: *1*
libzip x86_64 0.10.1-8.el7 base 48 k
php x86_64 5.4.16-42.el7 base 1.4 M *2*
php-cli x86_64 5.4.16-42.el7 base 2.7 M
php-common x86_64 5.4.16-42.el7 base 564 k
Transaction Summary
======================================================================
Install 4 Packages
Total download size: 4.7 M
Installed size: 17 M
Is this ok [y/N]: *3*
-
1 dnf 在启动操作之前会显示安装摘要。
-
2 注意 dnf 默认将安装的 PHP 版本。
-
3 安装将在您明确授权之前不会开始。
还需要记住的是,软件包的新版本和旧版本可以在仓库系统中同时存在。记住,您有选择权。同样,也要记住,最新的版本并不总是您想要的版本。这可能是因为您正在使用的另一个应用程序可能与该软件包的最新版本不兼容,或者因为新版本可能对您的项目来说不够稳定或安全。
注意
有很大可能性您将来可能需要安装不属于官方 Linux 仓库的软件包。这既不是犯罪也不是不道德的行为,因此您没有必要感到内疚。但您应该意识到您将完全负责确保与其他服务和应用程序的兼容性得到维护,以及您的非仓库软件包和配置在系统更新中能够生存。当然,您还必须格外小心,确保该软件包本身不包含恶意软件。
无论您是否使用官方的 CentOS PHP 仓库版本,您可能都需要手动添加单个模块以支持您预期的操作。遵循在线指南来设置这些设置将非常有用。但您可以使用 yum search php- 来检查可用的模块,然后使用 yum info 和一个有趣的选项名称来了解更多关于特定软件包的信息:
# yum search php- | grep mysql *1*
php-mysql.x86_64 : A module for PHP applications that use
MySQL databases *2*
php-mysqlnd.x86_64 : A module for PHP applications that use
MySQL databases
php-pear-MDB2-Driver-mysql.noarch : MySQL MDB2 driver
php-pear-MDB2-Driver-mysqli.noarch : MySQL Improved MDB2 driver
[...]
# yum info php-mysql.x86_64 *3*
Available Packages
Name : php-mysql *4*
Arch : x86_64
Version : 5.4.16
Release : 42.el7
Size : 101 k
Repo : base/7/x86_64
Summary : A module for PHP applications that use MySQL databases
URL : http://www.php.net/
License : PHP
Description : The php-mysql package contains a dynamic shared object
: that will add MySQL database support to PHP. MySQL is
: an object-relational databasemanagement system. PHP is
: an HTML-embeddable scripting language. Ifyou need MySQL
: support for PHP applications, you will need to install
: this package and the php package.
-
1 因为已经安装了类似 MySQL 的数据库,所以查找相关模块是有意义的。
-
2 64 位架构的 php-mysql 软件包
-
3 使用 yum info 了解有关有趣软件包的更多信息。
-
4 这是您将使用 yum 安装模块的软件包名称。
安装 PHP 和任何额外的 PHP 模块后,重新启动 Apache 以确保 Apache 能够将新模块纳入其服务:
# systemctl restart httpd.service
如本章前面所述的 Ubuntu 设置过程中所描述的,使用 phpinfo 确认 PHP 是否正确安装。
摘要
-
Web 服务器软件包,如 Apache,协调系统资源(如数据库和文件)之间的连接,并将网站资源暴露给客户端。
-
可用的软件包版本在不同 Linux 发行版之间可能会有所不同,您需要的发布版本可能取决于您的特定项目。
-
软件包依赖关系可以通过包管理系统(如 APT 和 Yum)提供的搜索工具和元数据来满足。
-
应用程序通常需要访问数据库引擎,因此需要提供有效的身份验证信息。
关键术语
-
Wiki 是一种用于创建和管理分布式、协作项目的工具。
-
内容管理系统(CMS)是一种应用程序,旨在使创建、共享和编辑数字内容变得容易。
-
Web 服务器 是一种软件,旨在安全可靠地将服务器资源暴露给远程客户端。
-
DocumentRoot 是 Apache 设置,它确定 Web 服务器在文件系统中查找网站文件的位置。
-
结构化查询语言(SQL)是用于管理关系数据库中数据的语法。
-
软件包依赖关系 是安装的应用程序正常功能所需的程序或扩展。
安全最佳实践
-
确保您的系统防火墙设置允许适当的客户端访问系统资源,但阻止所有其他请求。
-
在大多数情况下,允许远程 root 访问数据库不是一个好主意。
-
永远不要在面向公众的网站上暴露运行
phpinfo的文件。
命令行审查
-
apt install lamp-server^(单个 Ubuntu 命令)安装 LAMP 服务器的所有元素。 -
systemctl enable httpd在 CentOS 机器上每次系统启动时启动 Apache。 -
firewall-cmd --add-service=http --permanent允许 HTTP 浏览器流量进入 CentOS 系统。 -
mysql_secure_installation重置您的 root 密码并加强数据库安全性。 -
mysql -u root -p以 root 用户登录 MySQL(或 MariaDB)。 -
CREATE DATABASE newdbname;在 MySQL(或 MariaDB)中创建一个新的数据库。 -
yum search php- | grep mysql在 CentOS 机器上搜索与 PHP 相关的可用软件包。 -
apt search mbstring在系统中搜索与多字节字符串编码相关的可用软件包。
测试自己
1
您需要安装以下哪个软件包来为 LAMP Web 服务器提供数据库?
- mariadb
- httpd
- mariadb-client
- mariadb-server
2
哪个平台允许在浏览器客户端和服务器上的数据资源之间建立网络连接?
- Apache
- Perl
- PHP
- systemctl
3
localhost是一个用于调用的标识
- 远程客户端的 DocumentRoot
- 运行命令的服务器上的 DocumentRoot
- 网络服务器上的 .conf 文件
- 主服务器上的默认 HTTP 端口
4
默认情况下,Apache 网络服务器的 DocumentRoot 在哪里?
- /var/www/html/
- /var/html/www/
- /etc/apache2/
- /home/username/www/html/
5
以下哪个命令允许通过端口 80 将网络浏览器流量引入 CentOS 网络服务器?
firewalld --add-service=httpfirewall-cmd --add-service=httpsfirewall --add-service=httpsfirewall-cmd --add-service=http6
以下哪个命令可以让你成功登录到你的 MariaDB 命令行界面?
mariadb -u rootmysql root -pmariadb -r -pmysql -u root -p7
以下哪个
yum命令可以显示指定软件包的上下文数据?
yum search php-mysql.x86_64yum describe php-mysql.x86_64yum info php-mysql.x86_64yum data php-mysql.x86_648
以下哪个工具是运行 MediaWiki 在 LAMP 服务器上的可选工具?
- php-mysql
- mbstring
- php-imagick
- php7.0-xml
答案键
1.
d
2.
a
3.
b
4.
a
5.
d
6.
d
7.
c
8.
c
第八章. 网络文件共享:构建 Nextcloud 文件共享服务器
本章涵盖
-
使用 snaps 安装和管理软件包
-
配置 Apache 以在单个服务器上管理多个网站
-
配置和管理企业级文件共享站点
-
配置 Nextcloud 以使用云存储(AWS S3)
在上一章介绍了 LAMP 网络服务器和基于文本的内容管理协作之后,我相信您已经开始思考其他可以使用的技术堆栈。也许您的思绪并没有立即转向企业级文件共享的问题,但那并不是一个学习 Linux 提供网络服务方式的坏地方。
我所谈论的文件共享与人们非法交换受版权保护的电影和书籍无关。我也没有提到像 BitTorrent 这样的对等共享协议,尽管它们可以用于完全合法的目的。相反,我是在谈论那些拥有大量文档和其他媒体文件的公司,这些文件必须可访问,但同时也必须安全且安全地维护。
想象一个涉及数十名开发者的项目,他们被分成几个团队,分布在三个或四个物理位置。除了可能存放在私有仓库中的代码外,每个团队还会产生大量的设计文档、视频演示、项目资金提案和视频聊天的录音,更不用说 PDF 格式的合同和工作说明书了。对于这种系统要正常工作,您需要确保所有内容都能在不受保护的网络中安全可用,同时还要仔细控制访问权限,以确保只有正确的人能看到正确的资源。
我们将设置一个 Nextcloud 服务器来展示如何使用一些重要的 Linux 管理技能来完成这项工作。您将看到 Ubuntu 的新 snapd 包管理器,了解如何扩展 Apache 配置的限制,理解应用程序如何管理它们的配置,以及看到数据存储如何跨越多个设备。
8.1. 企业级文件共享与 Nextcloud
Nextcloud是一个开源软件套件,可以使用存储容量来保存、编辑和消费各种文档类型,包括音频/视频通话托管等服务。Nextcloud 还提供了客户端应用程序,允许 Linux、Windows、MacOS 和智能手机平台上的用户参与媒体资源。
使用 Nextcloud,您可以创建自己的 Dropbox 或 Google Drive 的私有版本,但这是根据您的条款,而且无需担心可用性或服务/隐私协议的意外变化。
注意
Nextcloud 是较老 ownCloud 项目的分支。虽然两者都还在运行,但 Nextcloud 似乎拥有一个更强大、活跃的开发者团队和更丰富的功能集。
太好了。Nextcloud 有一些真正的优势。但是,如果你可以从 Dropbox 和 Google Drive 等服务以极低或零成本获得大量存储空间,那么 Nextcloud 值得所有这些麻烦和开销吗?
好消息是——你可以两者兼得。对于特别敏感的数据,你可以将其全部保留在内部。但是,正如你很快就会看到的,你还可以构建一个 Nextcloud 服务器作为你的前端,精细控制用户如何与你的媒体交互,并将数据本身自动且安全地保存到更便宜、更可靠的第三方服务中,包括 Dropbox、Google Drive 和亚马逊的 S3。如果你最终发现你需要将数据迁移出 Dropbox 等服务,你可以在用户完全没有察觉到变化的情况下完成迁移。
但是,这一切 Linux 的特性是否足以证明在本书中用整整一章来介绍它是合理的?我认为是的。当然,Nextcloud 是一种第三方技术,它只位于 Linux 栈的顶部,但它仍然与 Linux 操作系统紧密相连。更重要的是:通过部署过程,你会接触到之前列出的那些 Linux 管理技能。
8.2. 使用 snaps 安装 Nextcloud
到这本书的这一部分,我敢打赌你对 Linux 发行版使用的包管理器之间的冲突已经有些沮丧了。正如我提到的,这不仅仅是 APT 与 Yum 的对决;还有像 Arch Linux 和 SUSE 这样的个别发行版使用的管理器。
如果能够使用单个工具在所有发行版上统一安装和管理一组 Linux 应用程序,那岂不是很好?当我们谈论这个问题时,为什么人们不能和平相处,而不必参与这样昂贵的战争?还有我们一直在经历的那些雨天:你把那叫做夏天吗?
我至少有一个问题的解决方案:Ubuntu 的 snaps。我应该提到的是,这本书中的所有项目都不依赖于 snaps,所以如果你对这个话题不感兴趣,你可以跳过这一节,在几页之后再次加入我们,在第 8.3 节中。没有坏心情。
现在我又在哪里?哦,是的。Snaps 是一种设计上完全自包含的软件包。你所需要的就是一个符合 snap 标准的 Linux 发行版和包的名称。(截至本书写作时,包括 CentOS 和 OpenSUSE 在内的大多数发行版要么已经实现,要么正在接近实现。)如果你从 Linux 终端运行此命令
# snap install blender
它会自动安装完整的 Blender 3D 创作套件,以及它需要的所有环境依赖项和扩展。更新和补丁也将由你背后处理。
Blender 只是一个例子。作为一个高度图形化的程序,它只适合安装在 Linux 桌面版本上;在服务器上你不会得到很好的效果。
Snap 仍在开发中。由于许多应用程序还没有 Snap 版本,APT 及其相关工具在短期内不会消失。但这项技术具有显著优势,越来越多的主要行业参与者正在加入。自我介绍会让你受益。
注意
Ubuntu Core 是一个特殊的轻量级发行版,主要用于与物联网(IoT)设备如智能家电和联网汽车以及 Docker 容器集群一起工作。按照设计,Core 仅使用 Snap 进行软件包管理。
Snap 系统不仅仅是一个软件包管理器。如您在图 8.1 中看到的那样,Snap 本身是隔离的沙箱,对其他系统资源的访问有限。
图 8.1. Snap 架构:注意 Snap 与系统资源之间交换数据是如何仔细控制以保持安全隔离的。

如此图所示,与其他 Snap 的通信是通过一套接口系统实现的,这些接口仅在适当的位置连接。Snap 被赋予了访问文件系统中的特殊位置(在/var/snap/
# apt install snapd *1*
- 1 snapd 软件包可能尚未适用于 CentOS 7,但
dnf install snapd命令将在 Fedora 25 上工作。
应用程序开发者已经构建了数百个 Snap,这些 Snap 通过包括 Ubuntu Store (snapcraft.io)在内的多个来源进行管理。您可以使用snap find命令和描述您要查找内容的关键词在命令行中搜索可用的 Snap。以下是从包含关键词server的 Snap 搜索结果中的一些示例:
$ snap find server
Name Version Developer Summary
minecraft-server-jdstrand 16.04.10 jdstrand Minecraft server packaging
for Ubuntu Core
thinger-maker-server 1.3.0 thinger Thinger.io Internet Of
Things Server
rocketchat-server 0.57.2 rocketchat Group chat server for 100s,
installed in seconds.
tika-server 1.16 magicaltrout Tika Server for metadata
discovery and extraction
kurento-media-server 6.6.1 surjohn kurento-media-server on
snappy
nats-server 0.9.4 nats-io High-Performance server
for NATS
kms-serverproxy-demo 6.6.0 surjohn kurento service server
side proxy demo
lxd-demo-server git stgraber Online software demo
sessions using LXD
[...]
结果表明,Nextcloud 是首批将软件包作为 Snap 添加的主要应用程序项目之一。在全新的、干净的 Ubuntu 17.04 VirtualBox 虚拟机上运行snap install nextcloud将为您提供一个完全功能的 Nextcloud 服务器,而无需先手动安装所有 LAMP 组件:
# snap install nextcloud
2017-07-25T21:07:41-04:00 INFO cannot auto connect core:core-support-plug
to core:core-support: (slot auto-connection), existing connection state
"core:core-support-plug core:core-support" in the
way *1*
nextcloud 11.0.3snap7 from 'nextcloud' installed
- 1 此错误信息不会影响安装;在安装前注销并重新登录可以完全避免它。
当 Snap 安装完成后,使用浏览器导航到您的虚拟机(VM)的 Web 根目录(使用虚拟机的 IP 地址,如您所记得,可以使用ip addr检索)。页面加载后,您将被要求创建一个管理员账户,然后就可以开始使用了。
为什么是 Ubuntu 17.04,为什么是 VirtualBox?
看起来将 Snap 集成到 LXC 环境中比预期的要慢一些,并且在 Ubuntu 16.10 之前,它还没有适用于 LXC 容器。由于我目前在我的工作站上运行 Ubuntu 16.04,因此使用基于 Ubuntu 17.04 ISO 镜像构建的 VirtualBox 虚拟机是最快、最简单的解决方案。
这又是学习使用多种虚拟化技术的价值的另一个例证:当一种技术失败时,另一种技术可能会工作。
8.3. 手动安装 Nextcloud
如果安装 Nextcloud snap 如此简单,为什么还要选择老式的、手动的方式呢?这里有几个原因。一个是 snaps 可能过于简单,不允许在设计和配置上有太多灵活性。另一个原因是,至少在我写这篇文章的时候,您将无法使用 snap 版本添加和完全管理应用程序。您很快就会看到这有多么有价值。而且手工制作应用程序安装意味着更多地了解您 Linux 系统的内部工作原理,以及识别和克服问题。这有什么不好呢?
8.3.1. 硬件要求
总是检查应用程序的文档是个好主意,以确保您有足够的硬件和软件资源来处理负载。图 8.2 显示了 Nextcloud 的系统要求网页。如果您计划托管一个简单、轻度使用的服务器,为几十个用户提供服务,那么您会发现 Nextcloud 相当容易使用,不会对现成的容器提出任何无法处理的要求。
图 8.2. 推荐和最小 Nextcloud 安装的硬件和软件要求

对于我们的技术测试,任何旧的最低硬件配置都可以正常工作,但我不想依赖一个运行旧 PC 的单个 LXC 容器来服务成千上万的用户和 TB 级的数据。
计划企业级部署?Nextcloud 提供了一个有用的、多层次的部署建议指南,用于配置全功能平台(mng.bz/8ZAO)。例如,Nextcloud 为拥有最多 150 个用户且数据量高达 10 TB 的小型工作组推荐以下配置:
-
一台拥有两个 CPU 核心的服务器
-
16 GB 的 RAM
-
通过轻量级目录访问协议(LDAP,一种广泛使用的分布式信息协议)进行身份验证
-
Red Hat Enterprise Linux 或 Ubuntu 16.04 带有厂商支持
-
配备 TLS/SSL 加密证书的 Apache
-
MySQL 或 MariaDB 数据库
-
使用
nodatacow挂载的 Btrfs 文件系统为 Nextcloud 数据分区提供零停机备份 -
使用 memcache 进行缓存以加快访问性能
注意
Btrfs 是一种文件系统类型,尽管不如 ext4(在第一章中简要提及)普及,但旨在在大规模上提供卓越的性能和数据可靠性。但是,截至发布 7.4(2017 年 8 月),Red Hat Enterprise Linux 已经废弃了对 Btrfs 的支持。
8.3.2. 构建 LAMP 服务器
到目前为止,下一步应该足够简单。我建议启动一个全新的容器或虚拟机,而不是使用你 MediaWiki 项目留下的那个,至少这样做是良好的实践。以下是在单个命令中你需要为你的服务器安装的所有包。我添加了 wget 和 nano,以防它们尚未安装:
# apt install apache2 mariadb-server libapache2-mod-php7.0 \
php7.0-gd php7.0-json php7.0-mysql php7.0-curl php7.0-mbstring \
php7.0-intl php7.0-mcrypt php-imagick php7.0-xml php7.0-zip \
wget nano *1*
- 1 为了尽可能保持基本镜像的大小,像 nano 这样的包通常在新 LXC 容器中默认不安装。
如果你不是特别挑剔,想要使用 MySQL 而不是 MariaDB,并且你在一个 Ubuntu 服务器上,那么你可以轻松地节省很多输入,并使用我在上一章中提到的 LAMP 服务器元包。再次提醒,不要忘记在包名末尾的撇号(^):
# apt install lamp-server^
安装完成后,请记住运行 MySQL 安全安装工具:
# mysql_secure_installation
如果你选择了 MariaDB 路径并发现自己必须使用sudo执行该命令,这里就是我在第七章中向你展示的快速修复方法:第七章:
MariaDB [(none)]> SET PASSWORD = PASSWORD('your-password');
MariaDB [(none)]> update mysql.user set plugin = 'mysql_native_password'
where User='root';
MariaDB [(none)]> FLUSH PRIVILEGES;
在安装了 LAMP 软件并启动数据库后,你现在可以告诉 Apache 如何与你的应用程序一起工作了。
8.3.3. 配置 Apache
为了确保 Apache 能够与 Nextcloud 通信,你需要做一些相对简单的调整。首先,你应该通过 a2enmod 工具启用几个 Apache 模块。rewrite模块用于在客户端和服务器之间实时重写 URL。headers模块对 HTTP 头执行类似的功能。
# a2enmod rewrite
# a2enmod headers
如果你没有计划将此服务器用于其他任何目的,将 Nextcloud 应用程序文件放在 Apache 文档根目录中是可以的。因为你的/etc/apache2/sites-available/目录中 000-default.conf 文件中的DocumentRoot条目的值已经指向/var/www/html/,所以没有其他事情要做。但是,将 Nextcloud 的数据文件放在默认文档根目录中可能会带来潜在的安全风险。你可能希望 Nextcloud 位于你的文件系统的其他部分。
有两种方法可以告诉 Apache 如何找到不在文档根目录中的站点文件。Ubuntu 方法涉及在你的现有 000-default.conf 文件中添加一个新部分,其中包含所有必要的信息。大多数人似乎更喜欢在/etc/apache2/sites-available/目录中为每个新服务创建一个新的.conf 文件。两者都可以正常工作,但这里是一个单独文件应该看起来像什么,假设你将应用程序放置在/var/www/而不是文档根目录中。
列表 8.1. /etc/apache2/sites-available/nextcloud.conf 文件的内容
Alias /nextcloud "/var/www/nextcloud/" *1*
<Directory /var/www/nextcloud/>
Options +FollowSymlinks
AllowOverride All
<IfModule mod_dav.c>
Dav off
</IfModule>
SetEnv HOME /var/www/nextcloud *2*
SetEnv HTTP_HOME /var/www/nextcloud
</Directory>
-
1 将/var/www/nextcloud/目录的内容与 Nextcloud 主机(或站点)关联
-
2 分配环境变量,定义 Nextcloud 应用程序的工作方式
使用 Ubuntu 方法的一个类似指令将涉及在你的 000-default.conf 文件中添加一个部分。它可能看起来像这样。
列表 8.2。在 000-default.conf Apache 配置文件中的 Nextcloud 示例指令
<VirtualHost *:443> *1*
ServerName bootstrap-it.com
DocumentRoot /var/www/nextcloud
ServerAlias bootstrap-it.com/nextcloud *2*
</VirtualHost>
-
1 注意这个例子使用了 443 HTTP 安全端口。
-
2 将针对 bootstrap-it.com/nextcloud 地址的流量重定向到自定义文档根
正如你在图 8.3 中可以看到的,当 Apache 读取这个配置文件时,它会将所有指向 example.com/nextcloud 的传入流量重定向到/var/www/中的应用程序文件,假设你的域名是 example.com。和之前一样,IP 地址也可以工作。
图 8.3。Apache 读取/etc/apache2/sites-enabled/中的配置文件,并使用它们的设置来重定向请求。

最后,你需要在/etc/apache2/sites-enabled/目录中创建一个指向你在/etc/apache2/sites-available/中创建的 nextcloud.conf 文件的符号链接:
# ln -s /etc/apache2/sites-available/nextcloud.conf \
/etc/apache2/sites-enabled/nextcloud.conf
但为什么?什么是符号链接?当 Apache 启动时,它会读取/etc/apache2/sites-enabled/目录的内容,寻找要加载的站点配置。这些配置实际上不会存在于/etc/apache2/sites-enabled/中,但会有指向/etc/apache2/sites-available/中实际文件的符号链接。
为什么不一开始就让 Apache 读取/etc/apache2/sites-available/并去掉中间人?因为所有这些都依赖于符号链接,这使得快速禁用站点并在编辑一轮后再次启用它变得容易和方便。你不需要删除和重写实际文件,你只需要玩一个容易管理的链接。
符号链接?它们是代表文件或目录在文件系统其他位置的对象。它们允许用户在一个地方执行或查看资源,即使资源本身在其他地方。当你到达第十二章时,你会了解更多关于符号链接的内容。链接到第十二章。
8.3.4。下载和解压 Nextcloud
你可以从 Nextcloud 安装页面(nextcloud.com/install)下载最新的 Nextcloud 包。如果你是在容器或 VM 中安装,或者从没有安装桌面 GUI 的服务器安装,那么最方便的方法是获取包的下载 URL,并在命令行中获取包。
从 Nextcloud 网站获取 URL 的一个快速方法是在你的 PC 上使用常规会话,点击“获取 Nextcloud 服务器”下的“下载”标签,然后,如图 8.4 所示,点击“详细信息”和“下载”选项按钮。右键单击.tar.bz2 链接,并从菜单中选择“复制链接地址”。
图 8.4。指向 Nextcloud 下载存档的链接:无论是.tar.bz2 还是.zip 格式都可以工作。

你可以通过在终端中右键单击并选择粘贴,或者按 Shift-Ctrl-v 来将.tar.bz2 URL 复制到wget命令中。命令看起来是这样的:
$ wget https://download.nextcloud.com/server/releases/nextcloud-12.0.0.tar.bz2
不要忘记点击在点击“详细信息”和“下载选项”按钮时显示的 MD5 或 SHA256 哈希链接。然后确认这些值与从下载的存档中生成的哈希值相同。
你会注意到你下载的文件具有 .tar.bz2 扩展名,而不是在 第四章 中看到的 .tar.gz。.gz 存档使用与 .bz2 存档不同的压缩算法创建。哪一个更好?似乎 gzip 存档压缩所需时间(和资源)更少,但产生的存档比 BZ2 大。因此,你的选择将取决于你的操作更重视速度还是存储空间。
解包 .tar.bz2 存档需要 xjf 参数,而不是用于 .gz 的 xzf:
$ tar xjf nextcloud-12.0.0.tar.bz2 *1*
- 1 j 参数用于解压缩 BZ2 压缩的存档。
下一步涉及将解包的文件和目录复制到它们的新家,根据我之前提到的最佳实践,这将位于 /var/www/。这是一个位于文档根目录之外的位置。
在复制命令中添加 -r 将递归地复制文件到包括子目录及其内容的目录中:
# cp -r nextcloud /var/www/
再进行两个小步骤,你就可以开始了。Apache 需要完全访问 Nextcloud 目录中的所有文件才能完成其工作。你可以让 root 用户拥有这些文件,但这意味着你必须给访问用户赋予 root 权限来访问这些文件。正如你可能想象的那样,给互联网上每个人提供这种文件访问权限会带来一些问题。
许多网络服务器使用一个名为 www-data 的特殊系统用户。下一个命令使用 chown(你已经在 第四章 中见过)将所有这些文件的用户和组所有权转移到网络服务器用户 www-data。使用大写 -R 将(就像你使用小写 -r 与 cp 一起使用时那样)递归地应用于目录层次结构中的所有文件和目录:
# chown -R www-data:www-data /var/www/nextcloud/
当 Apache 没有注意到我们在忙些什么时,它对所发生的事情一无所知。你最好通过重新启动服务让它加入乐趣:
# systemctl restart apache2
如果重新启动不成功,请记下任何错误消息,并查看是否有可以修复的内容。您还可以通过使用 tail 命令显示日志的最后 10 条条目来深入了解日志。例如,可能有一个对 nextcloud.conf 文件中特定行的引用:
# journalctl | tail
但如果一切顺利,请将您的浏览器指向您的容器 IP 地址,然后输入 nextcloud。您将被带到要求创建新管理员账户并提供 MariaDB 数据库有效登录凭证的页面。除非您为此目的创建了不同的数据库用户账户,否则您将使用 root 和您之前提供的密码:
10.0.3.36/nextcloud
注意
将数据库资源与非 root 数据库用户关联具有安全和物流方面的优势。当你有多个应用程序使用单个数据库的资源时,建议通过创建多个隔离的账户来保持应用程序的分离。
一旦你的信息被处理,你将看到指向 Nextcloud 客户端应用程序的链接,然后被带到你在图 8.5 中看到的行政控制台。那里你可以上传、查看和共享文档和媒体文件。
图 8.5. 主要的 Nextcloud 控制台,包括示例文件夹和文件。你可以在这里像使用操作系统文件管理器一样处理对象。

作为网站管理员,你还可以创建组用户,分配权限和配额,并管理网站的功能。现在,让我们看看如何管理 Nextcloud。
8.4. Nextcloud 管理
随着 Nextcloud 网站复杂性的增加和你的需求的变化(例如,可能使用脚本管理不断增长的资源会更有效率),你有时需要以命令行级别与行政资源进行交互。了解事物的位置和它们的外观将会很有帮助。
与许多 Linux 应用程序使用 /etc/ 的方式不同,Nextcloud 将其主要配置文件(命名为 config.php)保存在 /var/www/nextcloud/config/ 目录中。该文件包含认证和环境信息,以及关键的数据库连接细节。以下是该文件的快速预览:
<?php
$CONFIG = array (
'instanceid' => 'ociu535bqczx',
'passwordsalt' => '',
'secret' => '', *1*
'trusted_domains' =>
array (
0 => '10.0.3.36', *2*
),
'datadirectory' => '/var/www/nextcloud/data', *3*
'overwrite.cli.url' => 'http://10.0.3.36/nextcloud',
'dbtype' => 'mysql',
'version' => '12.0.0.29',
'dbname' => 'nextcloud',
'dbhost' => 'localhost',
'dbport' => '',
'dbtableprefix' => 'oc_',
'dbuser' => 'oc_admin',
'dbpassword' => '',
'installed' => true,
);
-
1 为了安全原因,所有加密的认证码都已删除。
-
2 服务器的公共域名或,在这种情况下,IP 地址
-
3 文件系统中账户和文档数据的位置
就像任何优秀的 Linux 应用程序一样,Nextcloud 有一个功能齐全的命令行 shell,称为occ 命令。要使用它,你需要使用 PHP 以 www-data 用户身份运行命令时,在命令前加上一个令人惊讶的详细字符串。输入sudo -u www-data php occ -h会显示基本的语法帮助,而list会打印出所有可用命令的完整列表:
$ cd /var/www/nextcloud
$ sudo -u www-data php occ -h
$ sudo -u www-data php occ list
目前,让我们忽略那些命令行工具,回到浏览器控制台。正如你在图 8.6 中可以看到的,点击右上角的齿轮图标会显示到用户页面(在这里你可以管理用户和组)、应用程序页面(包含可以启用的应用程序菜单)和管理页面(在这里你可以管理安全设置、服务和应用程序)的链接。
图 8.6. 点击齿轮图标会显示一个下拉菜单,包含适合你的账户权限的资源链接。

如果您点击到管理页面,您可能会看到一些安全和设置警告。如图 8.7 所示,我的网站没有配置为使用加密的 HTTPS 协议进行数据传输是一个值得关注的问题。Nextcloud 在这一点上确实有理,我计划在第九章中解决这个问题。
图 8.7. 管理页面,左侧面板中有各种管理应用程序的链接,中间有警告和状态信息

HTTPS 的核心是加密数据,在服务器(例如现在为您运行 Nextcloud 的服务器)和您访客的电脑上的浏览器之间传输数据。这一点至关重要,您不希望将数据在传输过程中暴露给任何位于您和用户之间的网络上的任何人。但您也应该关注数据在静止状态时的安全。
加密您服务器上的文件将防止任何以某种方式获得对您的存储驱动器访问权限的人挂载它们并读取其内容。那会是谁呢?想象一下有人黑入您的网络。或者有人带着您的物理服务器离开。如果文件被加密,它们对窃贼几乎没有或没有价值。
要启用加密,请点击左侧面板上的加密链接,然后选择启用服务器端加密复选框。如图 8.8 所示,您将看到一些您应该考虑的后果列表。阅读完毕后,点击启用加密按钮。
图 8.8. 在 Nextcloud 中启用服务器端加密是一个简单的一键过程,但您应该意识到其后果。

现在无法回头了。只有向前...
8.5. 使用 AWS S3 作为主要 Nextcloud 存储
关于存储,您必须找到空间来存放所有这些数据。而且,由于所有存储设备最终都会无预警地失败,您将需要每个设备的多个副本。弄清楚如何配置、连接和维护这样的存储阵列是耗时的,而且保持其运行相对昂贵。但还有希望。
云存储相对便宜,并且正如您可以在我的《一个月午餐时间学习亚马逊网络服务》一书中所读到的(Manning,2017),设置简单。由于大型云服务提供商在数据安全和弹性方面投入了大量资金,他们的服务几乎可以保证比您能组合起来的任何东西都更可靠。因此,使用基于云的数据作为您本地托管 Nextcloud 网站的后端是一个值得探索的严肃选项。以下是它是如何工作的。
您首先需要启用外部存储支持应用程序包。从右上角的齿轮图标开始,点击应用程序项,然后点击左侧面板中的禁用应用程序链接。如图 8.9 所示,外部存储支持选项出现在列表中。点击启用。
图 8.9. 当前可用的应用程序列表,包括外部存储支持

在安装并配置了 AWS CLI 的任何计算机的命令行上(就像您在第五章中做的那样),创建一个具有全局唯一名称的新存储桶:
$ aws s3 mb s3://nextcloud32327
再次,就像您在第五章中做的那样,从 AWS 控制台的“您的安全凭证”页面检索一组账户访问密钥。如果您已有可用的密钥集,您也可以使用它。
现在,返回您的 Nextcloud 控制台,点击齿轮下拉菜单中的“管理”,然后点击左侧面板中可见的“外部存储”链接。这会打开外部存储页面,您可以在其中点击“添加存储”下拉菜单并从列表中选择 Amazon S3。(列表还包括 Dropbox 和 Google Drive。)
您将被提示输入您想要使用的 S3 存储桶以及您的访问密钥和秘密密钥。所有其他字段,允许您使用非标准端口或 SSL 加密等来定制配置,都是可选的。完成设置后,点击右侧的勾号保存您的设置,并使 Nextcloud 尝试与 AWS 进行身份验证。
如果您成功,您将看到左侧的愉快绿色圆圈,如图 8.10 所示。图 8.10。如果它不起作用,最可能的原因是您以某种方式使用了无效的认证密钥。确认网络连接到互联网,特别是 AWS,不会有任何坏处。
图 8.10。Amazon S3 的外部存储设置页面显示了我 S3 存储桶的成功连接。

您可以通过将文件从您的计算机复制粘贴到 Nextcloud 控制台中的文件夹来测试您的新存储配置。然后,从您的 AWS CLI 中列出您的存储桶内容:
$ aws s3 ls s3://nextcloud32327
testfile.pdf
当然,您还需要以相反的方式测试它。从您的命令行将本地文件复制到存储桶中:
$ aws s3 cp test.txt s3://nextcloud32327
test.txt 文件应出现在您的控制台中。令人赞叹的多平台存储集成。
摘要
-
制定数据存储策略需要在一侧平衡成本和易用性,在另一侧平衡安全性和控制。
-
Ubuntu 的 snaps 是一个与发行版无关的包管理系统,同时也提供了安全、只读的隔离运行环境。
-
为了知道如何路由传入的 HTTP 流量,Apache 读取单个.conf 文件中的多个主机定义或多个主机定义文件。
-
网络服务器通常使用特殊的网络服务器用户账户为访客提供资源,以平衡访问和系统安全。
-
Nextcloud 将其配置文件存储在/
/nextcloud/config 目录中,特别是 config.php 文件中。 -
将 Nextcloud 验证到 AWS S3 存储桶的问题在于提供存储桶名称和访问密钥。
关键术语
-
snap是一个打包的应用程序,可以使用
snapd在大多数现代 Linux 发行版上自主安装。 -
符号链接是表示不同位置中的文件或目录的文件系统对象。/etc/apache2/sites-enabled/目录中的符号链接指向在 Apache 启动时由 Apache 读取的/etc/apache2/sites-available/目录中的文件。
-
归档可以使用 BZ2 算法进行压缩,作为 gzip 或 zip 的替代方案。
安全最佳实践
-
避免将 Nextcloud 数据文件保存在文档根目录中。
-
确保您网站的所有面向公众的文件的所有权属于 www-data 用户,而不是 root 用户。
-
通过创建单独的用户账户,将不同应用程序使用的数据库资源隔离开来。
命令行审查
-
a2enmod rewrite启用重写模块,以便 Apache 可以在客户端和服务器之间移动 URL 时编辑它们。 -
nano /etc/apache2/sites-available/nextcloud.conf创建或编辑 Nextcloud 的 Apache 主机配置文件。 -
chown -R www-data:www-data /var/www/nextcloud/将所有网站文件的用户和组所有权更改为 www-data 用户。 -
sudo -u www-data php occ list使用 Nextcloud CLI 列出可用的命令。 -
aws s3 ls s3://nextcloud32327列出 S3 存储桶的内容。
自我测试
1
Snap 是以下哪个?
- 用于管理设备的物联网驱动程序
- 用于安全运行应用程序的隔离环境
- 以 Ubuntu 为中心的包管理器
- 用于作为独立资源使用的应用程序包
2
以下哪个命令将列出系统上当前安装的所有包?
snap listsnap findsnap lssnapd ls3
以下哪个不是将 Nextcloud 文件安装到文档根目录的理由?
- 默认情况下,文档根目录中的文件对公众是开放的。
- 如果 Apache 正在为其他主机提供服务,则会导致冲突。
- Nextcloud 配置将只识别/var/www/nextcloud/目录中的文件。
- 文档根目录中的文件不能赋予与 Nextcloud 兼容的所有权和组属性。
4
以下哪个命令将创建一个指向 Apache 读取的 nextcloud.conf 文件的符号链接?
ln -s /etc/apache2/sites-enabled/nextcloud.conf /etc/apache2/sites-available/nextcloud.confln -s /etc/apache2/sites-available/nextcloud.conf /etc/apache2/sites-enabled/nextcloud.confln /etc/apache2/sites-enabled/nextcloud.conf /etc/apache2/sites-available/nextcloud.confln -h /etc/apache2/sites-enabled/nextcloud.conf /etc/apache2/sites-available/nextcloud.conf5
以下哪个命令将解压缩 nextcloud-12.0.0.tar.bz2 存档?
tar xcf nextcloud-12.0.0.tar.bz2tar xzf nextcloud-12.0.0.tar.bz2tar jf nextcloud-12.0.0.tar.bz2tar xjf nextcloud-12.0.0.tar.bz2
答案键
1.
d
2.
a
3.
a
4.
b
5.
d
第九章. 保护你的 Web 服务器
本章涵盖
-
保护你的基础设施
-
使用防火墙控制对服务器的访问
-
使用加密来保护你的数据
-
紧缩认证过程
-
控制软件和进程
Web 服务器中的Web部分有点误导。毕竟,我在本章中将要讨论的大多数安全工具无论你运行什么类型的服务器都是重要的。实际上,服务器这个词也有点多余,因为所有计算机都需要保护。然而,由于它们按定义暴露于大量的外部流量,因此你的 Web 服务器的安全性应该是一个特别高的优先级。所以,测试本章中将要学习的内容的最佳方式是运行一个 Apache Web 服务器。考虑现在就把它组装起来:apt install apache2。
在 IT 环境中,安全是指保护硬件、软件、数据和数字服务免受未经授权的访问和破坏。鉴于网络计算机资源被设计成要向各种客户端用户暴露,确保只有正确的客户端能够执行正确的操作是一个挑战。
你可以将安全性视为权衡价值与风险的精细艺术。当你考虑到已经存在多少种安全威胁,以及新的威胁出现得有多频繁时,你可能会理解到这种平衡永远不会完美。它肯定需要经常重新评估。
没有任何单一的工具或实践可以涵盖安全性的各个方面。虽然为自己制定一个关键安全待办事项清单并不是一个坏主意,但这还远远不够。我所知道的最为成功的管理员都是技艺精湛且知识渊博的,而且他们似乎还共享一种特定的态度:没有任何软件、供应商、政府机构、同事,甚至亲密的朋友可以完全信赖。他们可能对你并无恶意,但犯错误并留下一个容易被攻击的重要漏洞实在太容易了。每一件事和每一个人都可以使用一双额外的眼睛和一些双重检查。
你能做些什么来保护你的服务器?这实际上关乎许多小事。事实上,有很多小事,其中一些可能会溢出到下一章。然而,在这一章中,我们将在深入探讨如何使用防火墙来控制网络访问、使用 SSL/TLS 加密保护网站数据传输以及通过战略性地使用像安全增强 Linux (SELinux) 和系统组这样的工具来限制对服务器资源的使用之前,先从一些基础知识开始。
9.1. 显而易见的事情
让我们从选择一些容易解决的问题开始。考虑到你在本书中已经看到了许多安全最佳实践,许多安全都是常识。但是,尽管它可能很简单,你也不能忽视这些基础知识:
-
今天就备份你的数据。无论坏人对你服务器做了什么,如果你能从可靠的备份中重建它,那么你仍然在游戏中。再次查看第四章和第五章,然后为自己编写一个定期、自动化、全面且可验证的备份计划,涵盖你拥有的任何有价值的东西。确保始终有多个存档版本可用,并且至少有一个存档存储在异地。
-
将所有软件更新应用到你的系统上。没有借口。哦,总是有借口:你害怕更新可能会破坏你的应用程序所依赖的东西,或者它可能需要重启,这可能会造成中断。但无论如何都要做。不要误会我。我理解那些是真实的问题。问题是替代方案更糟糕。这是你友好的系统更新提醒:
# yum update或者(在 Ubuntu 上):
# apt update # apt upgrade
注意
不要忘记,包管理器只更新通过管理仓库安装的软件包。任何你手动添加的应用程序将保持未修补(并且可能不安全),直到你手动应用补丁或禁用它们。
通过构建测试(或预发布)环境(图 9.1),其中运行着你的应用程序的镜像,并且安全地保护免受公共网络的影响,你可以避免大多数中断的风险。对你的预发布基础设施应用更新和补丁应该能给你一个很好的想法,了解它在现实世界中的工作情况。
图 9.1。你可以在受保护的预发布环境中复制服务器基础设施,以安全地进行维护。

更好的是,你可以使用第十六章中提到的代码配置管理软件作为基础设施来自动化你的整个部署流程。这样,一旦你确认你修补的预发布环境运行正常,它就可以成为你的生产基础设施。但这是一个我们稍后再讨论的话题。
9.2. 控制网络访问
将你的服务器连接到网络以及外面的广阔、危险的互联网视为你的第一道防线。网络协议被设计成灵活的,以帮助你严格控制哪些流量能够通过。关键是理解这些协议是如何工作的,然后正确地使用这些知识来设置正确的事情。
9.2.1. 配置防火墙
防火墙是一组规则。当数据包进入或离开受保护的网络空间时,其内容(特别是关于其来源、目标以及它计划使用的协议的信息)将根据防火墙规则进行测试,以确定是否允许通过。以下是一个简单示例,如图 9.2 所示。
图 9.2。防火墙可以根据协议或基于目标的规则过滤请求。

假设你的公司的网络服务器需要对外开放,接受来自地球上任何地方的 HTTP 或 HTTPS 协议的 Web 流量。因为你的开发人员和管理员有时需要进入后端进行工作,你也会希望允许 SSH 流量,但仅限于那些需要它的人。对任何其他服务的请求应自动拒绝。让我们看看如何实现这一点。
可以通过名为iptables的程序在内核级别配置 Linux 机器以应用防火墙规则。创建 iptables 规则并不困难;语法可以在不太麻烦的情况下学习。但是,为了简化你的生活,许多 Linux 发行版已经添加了自己的高级工具来抽象化这项工作。在本节中,你将看到 CentOS 的 firewalld 和 Ubuntu 的 UncomplicatedFirewall(ufw)。
防火墙功能也通过 Juniper 和 Cisco 等公司制造的硬件设备提供。这些专有设备运行在自己的操作系统上,具有独特的语法和设计。对于涉及数百台服务器的大型企业部署,这些工具通常非常有意义,但你可以用任何旧的 Linux 机器以极小的成本完成大量工作。话虽如此,本节将仅介绍 Linux 防火墙功能的一小部分。当然,如果你想了解更多,请稍作停留,以便在第十章中了解更多深度内容,并咨询 Linux 智慧的常规来源,如 man 文件和在线指南。
firewalld
如你所猜测的,firewalld 是 systemd 家族的一部分。firewalld 可以安装在 Debian/Ubuntu 机器上,但在 Red Hat 和 CentOS 上默认存在。如果你对 firewalld 的热情如此之高,以至于甚至不考虑尝试其他任何东西,这里是如何在 Ubuntu 上安装它并使其运行的方法:
# apt update
# apt install firewalld
要确认防火墙是否正常工作,尝试浏览到服务器的 Web 根目录。如果网站无法访问,那么 firewalld 正在执行其工作。
你将使用firewall-cmd工具从命令行管理 firewalld 设置。添加--state参数会返回当前的防火墙状态:
# firewall-cmd --state
running
一些重要术语
为了确保没有人被遗漏,让我们定义一些重要的术语。超文本传输协议(HTTP)在网络中协调客户端和服务器之间资源的交换。例如,浏览器可能会请求一个用超文本标记语言(HTML)编写的网页,服务器可以通过传输页面内容来响应。每个数据传输事件都会生成包含有关会话状态信息的元数据(附加到数据包上的上下文信息),随后由管理员在试图找出问题所在时使用。HTTPS 协议的变体确保使用传输层安全性(TLS)协议安全地加密数据传输。
数据包是可能从较大的数据文件或存档中分离出来的一小部分数据。传输后,数据包可以被重新组装成其原始形式。当使用传输控制协议(TCP)进行网络数据传输时,接收到的数据包会在接收时进行检查,并在必要时重新发送。使用用户数据报协议(UDP)的传输将比 TCP 更快完成,但由于它们不包含错误纠正,因此仅适用于对错误高度容忍的操作。
默认情况下,firewalld 将处于活动状态,并将拒绝除 SSH 之外的所有传入流量。这意味着你的网站不会吸引太多访客,这无疑会为你节省大量的数据传输成本。既然这很可能不是你为你的 Web 服务器所设想的情况,你将想要打开 HTTP 和 HTTPS 端口,按照惯例,分别被指定为 80 和 443。firewalld 提供了两种方法来实现这一点。一种是通过--add-port参数直接引用端口号以及它将使用的网络协议(在这种情况下是 TCP)。--permanent参数告诉 firewalld 在服务器启动时加载此规则:
# firewall-cmd --permanent --add-port=80/tcp
# firewall-cmd --permanent --add-port=443/tcp
--reload 参数将应用这些规则到当前会话:
# firewall-cmd --reload
这种方法适用于你所能想到的任何复杂或定制配置。但如果你有更简单的需求,你可以使用 firewalld 为许多常用服务预定义的值。这些值是从/etc/services文件中保留的数据中提取的。
--add-service参数,当它指的是你的 HTTP 和 HTTPS 服务时,将打开 80 和 443 端口。在这种情况下,这看起来可能不是什么大问题,但当紧急情况发生且时间紧迫时,你确定你会记得默认 MySQL 端口号恰好是 3306 吗?直接输入mysql不是更容易吗?
# firewall-cmd --permanent --add-service=http
# firewall-cmd --permanent --add-service=https
对你防火墙上的当前设置感到好奇吗?运行 --list-services:
# firewall-cmd --list-services
dhcpv6-client http https ssh
假设你已经按照前面描述的方式添加了浏览器访问,HTTP、HTTPS 和 SSH 端口都是开放的,还有dhcpv6-client,它允许 Linux 从本地 DHCP 服务器请求 IPv6 IP 地址。你将在第十四章中了解更多关于这个内容。
你当然不希望任何人都能访问你的服务器 SSH,所以让我们让 firewalld 来保护它。你将限制 SSH 访问,只允许来自特定 IP 地址的会话。为此,我将向你展示如何切断所有 SSH 访问,然后只为单个 IP 地址开放。
注意
我应该警告你,在 SSH 会话中玩弄防火墙是有点危险的。你可能会被锁在自己的服务器外面。如果发生这种情况,有一些技巧(在本章后面介绍)可以帮助你重新进入。无论如何,如果你使用的是可丢弃的 LXC 容器或 VM,你不必太担心:如果出了问题,销毁它并启动一个干净的。
要关闭现有的 SSH 访问,使用--remove-service然后重新加载 firewalld(如果你指的是端口号,--remove-port将起到相同的作用):
# firewall-cmd --permanent --remove-service=ssh
success
# firewall-cmd --reload
测试你的新配置以确保它已经生效。在任何其他有网络访问的机器上打开一个新的终端,并尝试使用 SSH 登录到你的服务器。你的尝试应该失败:
$ ssh root@192.168.1.22 *1*
ssh: connect to host 192.168.1.22 port 22: No route to host
- 1 通过 SSH 允许 root 登录不是一个好主意,可以在
/etc/ssh/sshd.conf文件中通过 PermitRootLogin 设置禁止,设置为 no。
现在,回到你的 firewalld 机器上,添加一个新规则,该规则将接受来自使用 IP 地址 192.168.1.5(或你的客户端机器的 IP 地址)的客户端的 22 端口(默认 SSH 端口)的 TCP 流量。--add-rich-rule参数告诉firewall-cmd这个命令使用的是Rich Language集,这是一种高级语法,旨在简化复杂防火墙规则的创建(有关更多详细信息,请参阅mng.bz/872B):
# firewall-cmd --add-rich-rule='rule family="ipv4" \
source address="192.168.1.5" port protocol="tcp" port="22" accept'
success
现在尝试从指定 IP 地址的终端再次登录。应该可以登录。因为你没有使这个规则永久化,下次启动时一切应该恢复正常。
UncomplicatedFirewall (ufw)
让我们看看如何使用 ufw 在 Ubuntu 机器上以类似的方式控制 SSH 访问。ufw 程序可能不会在新安装的 Ubuntu 上安装,并且在任何情况下,默认情况下都会被禁用,所以你需要让它运行:
# apt install ufw
因为 ufw 默认所有端口都是关闭的,启用它会阻止你打开新的 SSH 会话。现有的会话不应该受到影响,但仍然,添加一个允许 SSH 的规则在启用 ufw 之前可能是个好主意:
# ufw allow ssh *1*
Rules updated
# ufw enable *2*
Command may disrupt existing ssh connections. *3*
Proceed with operation (y|n)?
-
1 使用 ufw deny ssh 命令来禁用 SSH。
-
2 启动防火墙。当需要时,使用 ufw disable 命令来关闭 ufw。
-
3 警告:现有或新的远程连接可能会受到影响
如果您在 LXC 容器上运行 ufw,那些命令可能没有起作用。相反,您可能看到了以下相当令人恐惧的错误信息:
ERROR: initcaps
[Errno 2] modprobe: ERROR: ../libkmod/libkmod.c:586 kmod_search_moddep()
could not open moddep file '/lib/modules/4.4.0-87-generic/modules.dep.bin'
modprobe: FATAL: Module ip6_tables not found in directory
/lib/modules/4.4.0-87-generic
ip6tables v1.6.0: can't initialize ip6tables table `filter':
Table does not exist (do you need to insmod?)
Perhaps ip6tables or your kernel needs to be upgraded. *1*
- 1 在主机系统上禁用了 IPv6 支持,您可能会遇到此错误信息。
这与 LXC 容器可能默认未启用 IPv6 支持的事实有关。考虑到容器无法完全访问其宿主机的内核,解决这个问题可能很复杂。如果您不打算将 IPv6 包含在网络配置中(无论如何,这符合绝大多数用例),那么最简单的方法是在您的/etc/default/ufw 配置文件中将IPV6=yes行编辑为IPV6=no。
列表 9.1. /etc/default/ufw 配置文件的一部分
# /etc/default/ufw
#
# Set to yes to apply rules to support IPv6 (no means only IPv6 on loopback
# accepted). You will need to 'disable' and then 'enable' the firewall for
# the changes to take affect.
IPV6=no *1*
# Set the default input policy to ACCEPT, DROP, or REJECT. Please note that
# if you change this you'll most likely want to adjust your rules.
DEFAULT_INPUT_POLICY="DROP"
# Set the default output policy to ACCEPT, DROP, or REJECT. Please note that
# if you change this you'll most likely want to adjust your rules.
DEFAULT_OUTPUT_POLICY="ACCEPT"
[...]
- 1 将 IPV6 的值从 yes 更改为 no 以禁用 IPv6 支持并避免 ufw 错误。
启用 ufw、添加 SSH 规则并运行ufw enable现在应该可以正常工作:
# ufw enable
Command may disrupt existing ssh connections.
Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
# ufw allow ssh
Rules updated
与 firewalld 类似,ufw 允许您使用端口号或服务名称(如您刚刚使用的ufw allow ssh)来创建规则。以下两个命令将为您的 Web 服务器打开 HTTP 和 HTTPS 访问:
# ufw allow 80
# ufw allow 443
ufw status命令显示服务正在运行,并且您需要的三个规则现在都是活动的。请继续测试您的 Web 服务器:
# ufw status
Status: active
To Action From
-- ------ ----
80 ALLOW Anywhere
22 ALLOW Anywhere
443 ALLOW Anywhere
注意
要正确测试通过防火墙访问 Web 服务器,别忘了您的浏览器缓存页面数据。这意味着即使现在有防火墙规则应该阻止这种情况,浏览器仍然可能加载它之前访问过的页面。为了确保您正在测试网站当前的状态,请清除浏览器缓存或刷新浏览器页面。
再进行一项微调将限制 SSH 访问仅限于坐在特定 IP 地址后的团队成员。如果现在安全(意味着您的 Web 服务器目前没有暴露在互联网流量中),在做出这些更改之前禁用 ufw 是一个好主意。然后使用delete 2(这指的是 ufw 列表中的第二个规则)删除您的允许 SSH 规则,并且只为来自 10.0.3.1 的流量重新打开它。(在我这个例子中,因为我从我的 LXC 主机登录到 LXC 容器,所以这个 IP 地址是我将使用的;您的结果可能会有所不同。)最后,重新启动 ufw 并检查其新状态:
# ufw disable
Firewall stopped and disabled on system startup
#
# ufw delete 2 *1*
Rules updated
#
# ufw allow from 10.0.3.1 to any port 22 *2*
Rules updated
#
# ufw enable
Command may disrupt existing ssh connections.
Proceed with operation (y|n)? y
Firewall is active and enabled on system startup
#
# ufw status
Status: active
To Action From
-- ------ ----
80 ALLOW Anywhere
443 ALLOW Anywhere
22 ALLOW 10.0.3.1 *3*
-
1 删除 ufw 状态显示的第二个防火墙规则
-
2 仅允许从指定的 IP 地址进行 SSH 流量,而不允许其他任何地方
-
3 一个新的规则,仅允许从指定的 IP 进行 SSH 流量
您可以通过从允许的 IP 地址的机器和任何其他机器登录来测试您的配置。第一个应该可以工作,但第二个最好不要工作!
通过这样,你现在已经看到了如何使用 firewalld 和 ufw 来安全地配置对简单 Web 服务器的访问。尽管防火墙可以使用任何协议或端口来控制流量,但我们只介绍了 HTTP、HTTPS 和 SSH。也值得提到的是,正如你将在本章稍后看到的那样,你可以为你的应用程序使用非标准网络端口。
恢复被锁定的虚拟机
如果你真的把自己锁在了 LXC 容器外面,你可以使用chroot(就像你在第六章中做的那样)来禁用或重新配置你的防火墙。首先,停止容器,然后对 LXC 容器使用的目录层次结构中的 rootfs 目录(/var/lib/lxc/your-container-name/)运行chroot。你将得到的命令提示符让你可以像容器实际运行时一样执行命令。现在禁用 ufw,或者如果你更喜欢,运行必要的命令来解决问题,然后退出chroot外壳。当你再次启动容器时,你应该有 SSH 访问:
# lxc-stop -n your-container-name *1*
# chroot /var/lib/lxc/your-container-name/rootfs/ *2*
# ufw disable
# exit *3*
# lxc-start -d -n your-container-name
-
1 停止运行的 LXC 容器
-
2 将你的容器文件系统挂载为 chroot
-
3 关闭 chroot 外壳会话
如果是一个 VirtualBox 虚拟机锁住了你呢?这很简单:通过最初启动 VM 时打开的原始终端登录。这相当于坐在连接到物理服务器的键盘上,不需要任何网络连接即可访问。
9.2.2. 使用非标准端口
能够通过数字设置网络端口的一个优点是,它让你可以配置应用程序使用非标准端口。例如,你可以将 SSH 设置为端口 53987 而不是 22。非标准端口的优势在于,它们让你能够实现通过隐蔽性来提高安全性。
让我来解释一下。本身来说,端口 53987 并不比端口 22 更安全:利用它只是更新 SSH 客户端的新设置的问题。但无论如何,它还是可以增加一层保护。
想象一下,有一个黑客正在对你的基础设施进行攻击,试图找到进入的方法。也许那个人发现你的管理员有一个坏习惯,就是为多个账户重复使用相同的密码——其中一个账户已经被攻破。黑客从那次入侵中获得了大量有价值的信息:你的服务器的 IP 地址(通常与你的网站使用的相同)以及你的管理员的用户名和密码。假设你允许通过密码登录你的 SSH 账户(正如你在第三章中知道的,这并不是一个好主意),那么黑客登录并给你的生活带来混乱就没有什么阻碍。除非没有人告诉黑客端口 22 已经关闭,SSH 访问只能通过一些神秘的较高端口号(如 53987)进行。因为你重置了默认端口,这使得突破你的防御变得更加困难,而这小小的困难可能会在未来的某一天产生重大影响。
它是如何工作的?首先,你需要在服务器上编辑/etc/ssh/sshd_conf配置文件(将托管你的 SSH 会话的计算机)。该文件将包含一个默认情况下读取为Port 22的行。你需要将其编辑为使用你计划使用的任何端口。
列表 9.2. 来自 SSH 主机 ssh_d.conf 文件的端口号设置行
# What ports, IPs, and protocols we listen for
Port 22 *1*
- 1 将此值更改为你想要使用的端口号。
完成后,并且你确信即使当前的 SSH 会话中断,你也能重新进入你的服务器,请重新启动 SSH 服务。如果你有一个防火墙,你需要告诉它允许在新端口上进行访问……这即将到来:
# systemctl restart ssh
现在,当你想从远程机器登录时,请添加-p后跟新的端口号。然后,你的 SSH 客户端将能够通过新端口请求会话:
$ ssh -p53987 username@remote_IP_or_domain
如果你正在使用不同的 SSH 客户端(如 PuTTY),你也需要同样告诉客户端关于非标准端口号的信息。让我们接下来看看这一点。
配置 ufw 防火墙以允许通过非标准端口传输流量
通过数字打开端口相对简单,但你需要明确指定你将使用的协议(TCP 或 UDP)。此示例使用 TCP 协议:
# ufw allow 53987/tcp
你也可以使用单个命令通过冒号(:)字符打开一系列端口。这在基础设施规划中可能很有用,比如当你知道你的开发人员将推出新的应用程序并需要访问多个端口时。现在给他们一个可以玩耍的范围可以节省时间和挫折。这个特定的示例打开了 52900 和 53000 之间的所有端口:
# ufw allow 52900:53000/tcp
网络端口
65,535 个可用的网络端口分为三类:
-
介于 1 和 1023 之间的端口被指定为知名端口,并已留出供已识别服务使用,如 SSH(22)和 HTTP(80)。你不应该为你的应用程序使用知名端口号,因为你可能会引起冲突。
-
介于 1024 和 49151 之间的端口是已注册的,这意味着公司和组织已经请求在这个范围内的特定端口被留出供他们的应用程序使用,即使它们还没有被普遍采用。例如,1812 端口用于 RADIUS 身份验证协议,3306 端口是 MySQL 的专用端口。
-
介于 49152 和 65535 之间的端口是未注册的,被认为是动态的(或私有的)。这些端口可用于任何临时或即兴使用,尤其是在私有网络上。你可以确信它们不会与已知的应用程序或服务冲突。
选择非标准端口号
你应该选择哪个端口号呢?首先,让我们明确一点:你绝对不应该让外人(像我这样的人)影响这样的决定!但是,为了避免与正在运行的网络安全应用发生冲突,你将希望坚持使用 49152 和 65535 之间的未注册范围内的值。这应该会给你足够的工作空间。
当然,使用非标准端口不仅限于 SSH。你应该考虑为任何你自己编写的或可以通过配置文件控制的程序使用这个技巧。记住:就像本章中的大多数工具一样,仅靠它本身可能不会非常有效,但作为更大安全协议集的一部分,它是一个强大的元素。
9.3. 传输中的数据加密
由于两个原因,网站加密非常重要:
-
未加密的网站危险地暴露了它们的数据,并将用户置于重大风险之中。
-
未加密的网站产生的商业价值显著较低。
第一个问题源于未加密网站以纯文本形式显示和处理所有内容的事实。这意味着涉及密码、个人和财务信息(如信用卡)的所有传输都对任何有权访问网络的好奇观察者可见。这显然是一个糟糕的想法。
第二个问题源于谷歌在 2017 年 1 月做出的一个决定。谷歌决定通过在互联网搜索结果中将未加密的网站排名降低来惩罚它们。这使得用户很难找到不安全的内容。
为什么谷歌(以及其他强大的互联网公司)会关心?你为什么应该关心?因为如果我们不能信任互联网的内容以及网站处理我们私人信息的方式,互联网的稳定性和我们所有人用它所做的一切都无法生存。即使你的网站不处理信用卡购买,它未加密的事实意味着它更有可能被入侵,其资源被劫持用于对其他网站的僵尸攻击。任何一个薄弱的网站都会使整个互联网变得更弱。
如果你想要保护你的网站(毕竟,这是本章的主题),那么加密是这个过程的重要组成部分。请注意,不要认为加密保证你的数据是安全的。它只是让错误的人更难接触到它。为了使这起作用,你需要一个证书,这是一个包含有关域名、所有者、密钥和可靠数字签名的信息的文件。
一旦你有了证书,浏览器就可以验证你站点的安全性,并在整个会话中仅交换加密数据。所有广泛使用的现代浏览器都预装了公共根证书,因此它们可以使用私有证书颁发机构(CA)证书验证与任何站点的连接。以下是工作原理:
-
客户端浏览器请求服务器身份,以便两者可以执行握手。
-
服务器通过发送从 CA 接收到的证书副本进行响应。
-
浏览器将证书与其根证书列表进行比较,并确认你的证书尚未过期或被撤销。
-
如果浏览器满意,它将使用服务器发送的公钥加密对称会话密钥,并将密钥传输到服务器。
-
所有传输都将使用会话密钥进行加密。
该过程在图 9.3 中进行了说明。
图 9.3. TLS 加密浏览器会话中标识数据、证书和会话密钥的交换

直到 2016 年,使用 SSL/TLS 标准从受信任的 CA 生成和安装加密证书需要时间和金钱。在 Linux 上,你会使用 OpenSSL 命令行界面工具生成密钥对,然后组装一个包含对的一半以及站点配置信息的特殊格式证书签名请求(CSR)包。
然后将 CSR 发送给 CA。如果请求被批准,CA 会发送一个证书供你在文件系统中安装。你还需要更新 Web 服务器配置文件(例如,在 Ubuntu 上的 Apache,为/etc/apache2/sites-available/default-ssl.conf),以便软件知道证书存储在文件系统的哪个位置。那已经是过去了。
自 2016 年以来,Let’s Encrypt 作为一个免费 CA 一直在发放证书。Let’s Encrypt (letsencrypt.org)由电子前沿基金会赞助,同时还有包括思科、Chrome、Shopify 和 Digital Ocean 在内的众多企业合作伙伴。其使命是通过降低成本和,同样重要的是,简化流程来推广网站加密。
忘记配置文件和使用 OpenSSL 生成 CSRs 吧:Let’s Encrypt 的 Certbot ACME 客户端会为你完成大部分工作。Let’s Encrypt 证书有效期为 90 天,并可以设置为自动续订。
9.3.1. 准备你的网站域名
在你能够安装证书以加密你的网站域名之前,你需要拥有一个域名。这涉及到从像 GoDaddy 或 Amazon 的 Route 53 这样的域名注册商那里购买一个名称。关于如何操作的更多信息,你可以阅读我书中关于“在一个月的午餐时间学习 Amazon Web Services”的第五章(Manning,2017 年)。
由于你希望 Apache 处理来自外部客户端的特定域名请求,你还需要在/etc/apache2/sites-available/000-default.conf文件中添加一个部分(在 CentOS 机器上,你将编辑/etc/httpd/conf/httpd.conf文件中的设置)。以下是我 bootstrap-it.com 服务器上的示例。注意,在这个阶段,它仅配置为接受不安全的 HTTP 端口 80 的流量。
列表 9.3. 来自 Apache 配置文件的可能的域名部分
<VirtualHost *:80> *1*
ServerName bootstrap-it.com *2*
DocumentRoot /var/www/html
ServerAlias www.bootstrap-it.com *3*
</VirtualHost>
-
1 此配置只监听 80 端口的流量。
-
2 你的域名用作 ServerName 的值。
-
3 这条 ServerAlias 行将 www 添加为有效的域名前缀。
9.3.2. 使用 Let’s Encrypt 生成证书
从这个点开始,操作非常简单。浏览到电子前沿基金会 Certbot 网站的“入门”页面(certbot.eff.org),正如你在图 9.4 中看到的,指定你正在使用的 Web 服务器软件和操作系统。
图 9.4. 一旦你在 Certbot 主页上选择了你的 Web 服务器软件和操作系统,你将看到安装说明。

从 Certbot 主页,你将被重定向到一个包含一些简要说明的页面。对于 Ubuntu 16.04 上的 Apache,它包括安装软件属性管理工具的命令,将 Certbot 存储库添加到你的 APT 列表中,然后安装 Apache 的基于 Python 的 Certbot 软件:
# apt update
# apt install software-properties-common
# add-apt-repository ppa:certbot/certbot
# apt update
# apt install python-certbot-apache
最后,你将以管理员身份启动 Certbot 程序(在我的情况下,使用--apache作为参数)。Certbot 将读取你的 Web 服务器配置文件,以了解你可能会想要注册的域名:
# certbot --apache
在回答有关联系信息和 Let’s Encrypt 服务条款的一些问题后,你将看到一个可能看起来像这样的可能域名列表:
Which names would you like to activate HTTPS for?
--------------------------
1: bootstrap-it.com
2: www.bootstrap-it.com
--------------------------
Select the appropriate numbers separated by commas and/or spaces,
or leave input blank to select all options shown (Enter 'c' to cancel):
一旦你做出回应,机器人将尝试确认你选择的域名存在并且已注册在公开可访问的 DNS 服务器上。证书服务器最终将尝试连接到你的网站。如果成功,Let’s Encrypt 证书将被自动安装,并在配置文件中添加任何必要的附加部分。
如果在过程中出现问题,Certbot 将显示有用的错误信息,你可以使用这些信息来寻找解决方案。此外,Let’s Encrypt 还提供了一个活跃的社区帮助论坛,所有技能水平的用户都可以在这里安全地寻求帮助:community.letsencrypt.org。
到目前为止,在这章繁忙的内容中,你已经学会了如何通过保持应用程序修补和更新来增强网站安全性,使用防火墙规则来控制对网络的访问,将隐蔽性添加到你的组合中,以及加密在网站和访客之间传输的数据。我们的安全工作还没有结束。
接下来:加强登录协议,使用 SELinux 内核模块和组来更紧密地控制用户可能陷入的麻烦,以及跟踪你的运行进程以确保在没有人注意时后台没有发生不适当的事情。
9.4. 强化认证过程
使用安全的连接解决方案,特别是 SSH,是很好的。但也要注意你的团队成员如何使用 SSH。这里有一些建议来提高你的远程访问安全性。在每种环境中强制执行它们可能并不实用(尤其是在你设置事情的过程中),但至少你应该熟悉它们。
避免以 root 用户登录服务器。在需要管理员权限时,始终使用sudo更好。实际上,你可以通过编辑/etc/ssh/sshd_conf文件中的PermitRootLogin行来完全防止 root 登录:
PermitRootLogin no *1*
- 1 /etc/ssh/sshd_conf 中的 root 登录控制行
你还可以鼓励你的管理员只通过密钥对(就像你在第三章中看到的那样)使用无密码 SSH 访问。这也可以从sshd_conf文件中强制执行,这次是在PasswordAuthentication行上。没有密码认证,用户将被迫使用密钥对:
PasswordAuthentication no *1*
- 1 /etc/ssh/sshd_conf 中的密码认证控制行
在每次编辑后,确保重新加载 SSH;否则,新设置将不会在下次启动前生效:
# systemctl restart sshd
这些是在任何环境中都非常重要的步骤。但如果你的部署需要一些工业级的隔离,考虑启用 SELinux。
9.4.1. 使用 SELinux 控制文件系统对象
记得我们之前在第四章讨论对象权限吗?当时的背景是需要确保用户可以访问和编辑自己的文件。但硬币的另一面是确保错误用户无法染指他人的文件。
你会记得,一个对象常见的权限配置可能给所有者提供完全的读写执行权限,但只给对象组和其他人读取权限。在我们的数字表示法中,这将是 744,或者说是rwx r-- r--。
给你的用户完全控制他们自己的资源的做法有时被称为自主访问控制(DAC)系统。如果你希望用户能够高效工作,DAC 将非常有意义,但它也有代价:完全控制的风险是他们可能会在不完全了解后果的情况下使用它。
这里有一个我所说的实际例子。假设有几位开发者辛勤地为你的公司工作,遇到了一个问题:在本地测试软件时,尝试写入数据文件总是失败。调试发现,这是由于应用程序由一个用户运行,但数据文件属于另一个用户导致的权限问题。
因为这种情况已经发生不止一次,而且涉及多个数据文件(或者说是 SSH 密钥文件),开发者选择了快捷而懒惰的方法:他们打开了数据文件以及那些目录下所有文件的权限,设置为 777——对整个世界完全开放。这确实是一个重大的安全问题。还有很大的可能性,他们正在开发的应用程序最终会以相同的系统设置移至生产环境。这种错误正是导致你时不时听到的一些重大数据泄露事件的原因。
SELinux 是那些复杂主题之一,虽然对于许多 Linux 工作负载至关重要,但在这个书的工程项目中不必扮演主要角色。再次提醒,如果你喜欢,可以直接跳过它,进入系统组的讨论。
当安装并激活时,SELinux 内核模块会对文件系统对象应用强制访问控制(MAC),无论特定对象的拥有者是谁。实际上,如图 9.5 所示,它对用户可以做什么设定了仔细定义的、系统范围内的限制,使得设置固有的危险配置变得不可能。
图 9.5。通过 SELinux 策略过滤器请求资源访问后的流程

如果 SELinux 是激活的,那两位开发者可能会不断地将 777 权限应用于他们的数据文件,直到他们的手指累得酸痛,但这并不能解决问题。相反,他们被迫寻找更合适和有效的解决方案。例如,他们可能会考虑创建一个拥有数据权限的系统组,然后将适当的用户账户添加到该组中。你将在本章稍后了解更多关于这方面的内容。听起来很棒。关于安全性,有什么不喜欢的呢?
嗯,这里有个问题。SELinux 与应用兼容性有着黑暗而可怕的关系。如此黑暗和可怕,以至于许多管理员宁愿禁用它,也不愿尝试让它工作。问题是许多应用程序,无论是现成的还是本地构建的定制应用程序,都需要访问和编辑系统资源。因此,在未经修改的 SELinux 环境中运行此类应用程序通常会失败。
我有可靠的消息说,所有这些冲突都有解决方案,并且它们并非不可能实施。但同样经常的是,通过更好地理解文件系统设计和安全原则,可以完全避免冲突。特别是,您应该记住最小权限原则,该原则旨在仅允许所有用户和进程访问他们需要的权限,而不需要更多。在任何情况下,您都需要了解 SELinux,因此以下章节介绍了基础知识。
9.4.2. 安装和激活 SELinux
也许是因为 SELinux 是由 Red Hat Linux(和 CentOS)开发和为它们设计的,所以默认情况下,这些系统上已经安装并激活了 SELinux。在其他发行版上运行它,包括 Ubuntu,绝对可能(尽管 AppArmor 在 Ubuntu 上是一个更常见的选择),但我不能保证它总是能顺利运行。(甚至不要考虑在 LXC 容器上尝试;相反,使用 VirtualBox 进行测试。)在 Ubuntu 上,您需要三个软件包:selinux、setools 和 policycoreutils。以下是它的样子:
# apt install setools policycoreutils selinux
一旦妥善设置,重启 Ubuntu,并运行sestatus以获取当前 SELinux 状态的快照,包括重要的文件系统位置和政策。如果有幸,您应该看到类似以下内容:
# sestatus
SELinux status: enabled *1*
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted *2*
Current mode: permissive
Mode from config file: permissive
Policy MLS status: enabled
Policy deny_unknown status: allowed
Max kernel policy version: 30
-
1 当前 SELinux 状态是启用的。
-
2 正在使用的默认策略是目标型。
您有时可能需要运行selinux-activate命令,以便将 SELinux 设置纳入启动过程:
# selinux-activate
Activating SE Linux *1*
Generating grub configuration file ...
Warning: Setting GRUB_TIMEOUT to a non-zero value when GRUB_HIDDEN_TIMEOUT
is set is no longer supported.
Found linux image: /boot/vmlinuz-4.4.0-89-generic *2*
Found initrd image: /boot/initrd.img-4.4.0-89-generic
Found linux image: /boot/vmlinuz-4.4.0-87-generic
Found initrd image: /boot/initrd.img-4.4.0-87-generic
Found linux image: /boot/vmlinuz-4.4.0-83-generic
Found initrd image: /boot/initrd.img-4.4.0-83-generic
Found memtest86+ image: /boot/memtest86+.elf
Found memtest86+ image: /boot/memtest86+.bin
done
SE Linux is activated. You may need to reboot now. *3*
-
1 如其名所示,selinux-activate 在下次启动后会将 SELinux 设置为活动状态。
-
2 为 GRUB 控制的每个映像的启动命令添加了 SELinux 友好标志。
-
3 因为 SELinux 依赖于内核级设置,更改通常需要重启。
就像那个人说的,你可能需要重启才能使更改生效。
您可以通过位于/etc/selinux/的配置文件来控制 SELinux 的行为。该文件包含两个设置,SELinux 状态和 SELinux 类型。表 9.1 提供了可能值的简要概述。
表 9.1. /etc/selinux/config 中 SELinux 的配置设置
| 类别 | 值 | 描述 | 使用 |
|---|---|---|---|
| 状态 | disabled | SELinux 已关闭。 | |
| enforcing | 安全策略正在执行。 | ||
| permissive | 策略违规只会触发记录警告。 | 适用于测试配置 | |
| 策略类型 | targeted | 启用了一个由 SELinux 限制“不受限制”的域的进程。 | 适用于不是所有进程都需要限制的混合使用系统 |
| minimum | 只有最小进程受到 SELinux 的限制。 | 可以允许对实验系统进行更精细的调整 | |
| mls | 根据敏感级别和能力应用策略。 |
除了配置文件外,您还可以使用 setenforce 命令从命令行设置 SELinux 状态,其中 setenforce 1 启用强制状态,而 setenforce 0 将 SELinux 设置为宽容状态。在宽容状态下,规则违规是被允许的,但会被记录下来。这是一种在不完全打乱过程中调试或测试配置的好方法:
# setenforce 1
以下是一个 SELinux 示例,说明您如何控制对单个文件的访问?任务完成。您绝对应该尝试下一节中的示例(或类似的内容)。
9.4.3. 应用 SELinux 策略
假设你是负责那些之前遇到的两个懒惰开发者的系统管理员。根据以往的经验,你怀疑他们可能会被诱惑过于广泛地开放对数据文件的访问。以下是无论开发者尝试什么,你都可以如何保护你的数据的方法。
您可以使用 SELinux 来控制任何文件或进程的访问方式,但为了使这个例子简单,让我们以安装了 Apache(或 httpd)并包含在文档根目录 /var/www/html/ 中的 index.html 文件的服务器为例。默认情况下,该文件至少对本地请求(通过从服务器命令行运行 wget localhost)是可访问的。以下是它通常看起来是怎样的:
$ wget locahost
--2017-08-02 10:24:25-- http://localhost/
Resolving localhost (localhost)... ::1, 127.0.0.1
Connecting to localhost (localhost)|::1|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 11 [text/html]
Saving to: 'index.html' *1*
100%[======================================>] 11 --.-K/s in 0s
- 1 wget 成功将 index.html 文件保存到本地目录。
现在,使用 ls -Z(-Z 将显示文件的安全上下文)检查 index.html 文件的权限状态:
# ls -Z /var/www/html/
-rw-r--r--. root root unconfined_u:object_r:httpd_sys_content_t:s0 index.html
首先,注意使文件可读(r)的常规权限(-rw-r--r--)。这对于网站资源来说是标准的。文件的 SELinux 状态显示为 unconfined_u:object_r:httpd_sys_content_t:s0。您可以使用 chcon -t 来更改文件上下文类型。此命令将 Apache 的 httpd_sys_content_t 类型替换为与 Samba 相关的 samba_share_t 类型。我不确定你真的想在现实生活中这样做,但这应该很好地演示了您可以如何平衡您授予用户的权限与他们的潜在破坏能力:
# chcon -t samba_share_t /var/www/html/index.html
第二次运行 ls -Z 显示文件现在与 samba_share_t 类型相关联:
# ls -Z /var/www/html/
-rw-r--r--. root root unconfined_u:object_r:samba_share_t:s0
/var/www/html/index.html
下一次运行 wget localhost 将如何处理新的 SELinux 上下文?
$ wget localhost
--2017-08-02 10:27:30-- http://localhost/
Resolving localhost (localhost)... ::1, 127.0.0.1
Connecting to localhost (localhost)|::1|:80... connected.
HTTP request sent, awaiting response... 403 Forbidden
2017-08-02 10:27:30 ERROR 403: Forbidden. *1*
- 1 Apache 以 403:禁止访问的失败消息响应请求。
这是不行的。Apache 被迫让你(或者更确切地说,开发者)失望,因为在这个上下文中,Apache 本身对文件没有控制权。尽管文件属性包括所有用户的读取权限,但这仍然是正确的。无论你的开发者多么渴望打开对受保护文件的访问,他们都会徒劳无功。
9.4.4. 系统组和最小权限原则
那两个开发者终于明白了。他们明白自己被阻止了,不能过于广泛地开放访问权限。但现在他们正在请求你的帮助来解决原始问题:如何在不对所有人开放的情况下,使包含敏感数据的文件可供多个账户访问。
简短的回答是组。(而长一点的回答是 g—r—o—u—p—s。)一个 组 是一个系统对象,与用户非常相似,只是没有人会以组的形式登录到系统中。组的强大之处在于,它们像用户一样可以被分配给文件或目录,允许任何组成员共享组权限。这可以在图 9.6 中看到。
图 9.6. 开发者组成员可以访问特定的目录,而那些不是组成员的个人则不行。

尝试自己操作:使用 nano 创建一个新文件。添加一些 Hello World 文本,这样你就可以轻松地判断你是否能够成功访问它。现在使用chmod 770编辑其权限,这样文件的所有者和组对文件拥有完全权限,但其他人不能读取它:
$ nano datafile.txt
$ chmod 770 datafile.txt
如果你的系统除了你的账户外还没有其他额外用户,你可以使用adduser(Debian/Ubuntu 的方式)或useradd(如果你在 CentOS 上)来创建一个。请注意,useradd在 Ubuntu 上也可以使用:
# useradd otheruser *1*
# passwd otheruser
Enter new UNIX password:
Retype new UNIX password:
passwd: password updated successfully
- 1 用户添加命令(与 Debian 的 adduser 命令相反)要求你单独生成用户密码。
使用su切换到你的新用户。一旦你输入了用户的密码,你执行的所有命令都将以该用户身份运行。你将只使用该用户的权限:不多也不少。如果你尝试读取 datafile.txt 文件(使用cat),你将不会成功,因为你记得,其他人被拒绝读取权限。完成操作后,输入exit离开新用户 shell 并返回到原始 shell:
$ su otheruser
Password:
$ cat /home/ubuntu/datafile.txt
cat: /home/ubuntu/datafile.txt: Permission denied
$ exit
所有这些都是预期之中且容易理解的。而且,正如你所看到的,无法读取属于不同读者的文件有时可能是一个问题。让我们看看通过将文件与组关联并正确配置文件权限,你能做些什么。
创建一个你可以用来管理你的应用程序数据的新组,然后使用chown编辑你的数据文件属性。ubuntu:app-data-group参数将文件所有权留给了 ubuntu 用户,但将其组更改为你的新 app-data-group:
# groupadd app-data-group
# chown ubuntu:app-data-group datafile.txt
使用带有长输出(-l)的ls命令针对文件查看其新的权限和状态。请注意,正如预期的那样,ubuntu 是文件的所有者,app-data-group 是其所属组:
$ ls -l | grep datafile.txt
-rwxrwx--- 1 ubuntu app-data-group 6 Aug 9 22:43 datafile.txt
你可以使用usermod将你的用户添加到 app-data-group,然后再次使用su切换到另一个用户的 shell。这次,尽管文件的权限阻止了其他人,而且你现在确实是以“其他”的身份行事,但你应该能够读取它...多亏了你的组成员资格:
# usermod -aG app-data-group otheruser
$ su otheruser *1*
$ cat datafile.txt
Hello World *2*
-
1 使用 su 命令在用户账户之间切换。
-
2 这正是我的 datafile.txt 文件的内容。
这种组织方式是正确且有效处理多用户系统上出现的许多复杂权限问题的方法。实际上,它不仅用于给个别用户提供他们需要的访问权限,而且许多系统进程没有特殊的组成员资格就无法完成工作。快速浏览一下/etc/group文件,注意有多少系统进程有自己的组:
列表 9.4. /etc/group文件内容的部分列表
$ cat /etc/group
root:x:0:
daemon:x:1:
bin:x:2:
sys:x:3:
adm:x:4:syslog
tty:x:5:
disk:x:6:
lp:x:7:
mail:x:8:
news:x:9:
uucp:x:10:
man:x:12:
proxy:x:13:
[...]
我将以几个快速但至关重要的协议结束本章,您可以将这些协议融入您的安全实践中。
9.4.5. 在容器内隔离进程
担心您在单个服务器上运行的多个服务,如果其中一个服务被入侵,所有服务都会处于风险之中?限制粗心或恶意用户可能造成的损害的一种方法是通过隔离系统资源和进程。这样,即使有人可能想要超出设定的限制范围,他们也不会有物理访问权限。
以前处理这个问题的方法是为每个服务分配一个单独的物理机器。但虚拟化可以使构建隔离的基础设施变得更加容易和更经济。这种架构通常被称为微服务,您将启动多个容器,其中可能只有一个运行数据库,另一个运行 Apache,第三个包含可能嵌入到您的网页中的媒体文件。除了与微服务架构相关的许多性能和效率优势外,这还可以大大降低每个单独组件的风险暴露。
注意
当我提到容器时,并不一定是指 LXC 类型的容器。如今,对于这种部署,Docker 容器要更为流行。如果您想了解更多信息,可以查看 Manning 出版的《Microservices in Action》(Morgan Bruce 和 Paulo A. Pereira,2018 年),《Microservice Patterns》(Chris Richardson,2018 年),或者《Docker in Practice, 2nd ed.》(Ian Miell 和 Aidan Hobson Sayers,2018 年)。
9.4.6. 检查危险的用户 ID 值
虽然任何管理员用户都可以使用sudo临时假定 root 权限,但只有root才是真正的 root。如您所见,以 root 身份执行常规操作并不安全。但这种情况可能发生,无论是由于无辜的意外还是恶意的篡改,普通用户可以有效地获得全职的管理权限。
好消息是,很容易发现冒名顶替者:它们的用户和/或组 ID 号,就像 root 一样,将是零(0)。看看/etc/中的 passwd 文件。此文件包含每个当前存在的普通和系统用户账户的记录。第一个字段包含账户名称(在这个例子中是 root 和 ubuntu),第二个字段可能包含一个x代替密码(如果存在,它将在/etc/shadow文件中加密显示)。但接下来的两个字段包含用户和组 ID。在这个例子中,ubuntu 的这两个 ID 都是 1000。而且,正如你所看到的,root 是零:
$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
[...]
ubuntu:x:1000:1000::/home/ubuntu:/bin/bash
如果你看到任何普通用户具有用户或组 ID 为 0,那么你就知道有些事情很糟糕,你应该开始修复它。快速简单地发现问题的方式是运行这个awk命令针对 passwd 文件,它打印出任何第三个字段只包含 0 的行。在这种情况下,让我非常欣慰的是,唯一的结果是 root。你可以再次运行它,用$4替换$3以获取组 ID 字段:
$ awk -F: '($3 == "0") {print}' /etc/passwd *1*
root:x:0:0:root:/root:/bin/bash
- 1 awk 命令将在第十一章中更详细地讨论。
9.5. 审计系统资源
你运行的东西越多,出现问题的可能性就越大,所以你想要跟踪正在运行的内容是有道理的。这适用于网络端口(如果它们是开放的,那么根据定义,必须有进入的方法),服务(如果它们是活跃的,那么人们可以运行它们),以及安装的软件(如果它已安装,则可以执行)。
为了使审计有用,你必须记得偶尔运行它们。因为你知道你会忘记,所以将你的审计工具集成到一个脚本中会更好,这个脚本不仅定期执行,而且理想情况下还会解析结果以使其更易于阅读。在本节中,我将重点介绍三个关键的审计工具,以帮助您扫描开放端口、活跃服务和不必要的软件包。实现这一切将是你的工作。
9.5.1. 扫描开放端口
如果主机上有一个进程正在监听该端口的请求,则该端口被认为是开放的。关注你的开放端口可以帮助你了解服务器上正在发生的事情。
你已经知道,一个普通的 Web 服务器可能会有 HTTP(80)和 SSH(22)端口开放,所以发现这些端口并不令人惊讶。但你需要关注其他意外结果。netstat命令显示了开放端口以及有关这些端口如何使用的丰富信息。
在这个例子中,针对一个相当典型的多功能服务器,-n告诉netstat包括数字端口和地址,-l仅包括监听套接字,而-p添加监听程序的进程 ID。自然地,如果你看到什么,就要采取行动:
# netstat -npl
Active Internet connections (only servers)
Proto Local Address Foreign Address State PID/Program name
tcp 127.0.0.1:3306 0.0.0.0:* LISTEN 403/mysqld *1*
tcp 0.0.0.0:139 0.0.0.0:* LISTEN 270/smbd
tcp 0.0.0.0:22 0.0.0.0:* LISTEN 333/sshd *2*
tcp 0.0.0.0:445 0.0.0.0:* LISTEN 270/smbd
tcp6 :::80 :::* LISTEN 417/apache2
[...]
-
1 MySQL 进程正在 3306 端口上运行。
-
2 SSH 进程的进程 ID 为 333。
近年来,ss已经开始取代netstat用于许多用途。如果您有一天在聚会上有人问您关于ss的问题,这个例子(列出所有建立的 SSH 连接)应该会为您提供足够的信息,让您免受真正的尴尬:
$ ss -o state established
'( dport = :ssh or sport = :ssh )' *1*
Netid Recv-Q Send-Q Local Address:Port Peer Address:Port
tcp 0 0 10.0.3.1:39874 10.0.3.96:ssh
timer:(keepalive,18min,0)
- 1 显示所有 TCP 套接字
9.5.2. 扫描活动服务
快速查看当前系统上启用的 systemd 管理的服务也可以帮助您发现不属于那里的活动。systemctl可以列出所有现有服务,然后可以缩小到仅包含enabled描述的结果。以下代码仅返回活动服务:
# systemctl list-unit-files --type=service --state=enabled
autovt@.service enabled
bind9.service enabled
cron.service enabled
dbus-org.freedesktop.thermald.service enabled
docker.service enabled
getty@.service enabled
haveged.service enabled
mysql.service enabled
networking.service enabled
resolvconf.service enabled
rsyslog.service enabled
ssh.service enabled
sshd.service enabled *1*
syslog.service enabled
systemd-timesyncd.service enabled
thermald.service enabled
unattended-upgrades.service enabled
ureadahead.service enabled
- 1 sshd 是 SSH 服务器;ssh 是客户端软件。
如果您确实发现了一些不应该存在的东西,您可以使用systemctl来停止服务并确保它不会在下次启动时启动:
# systemctl stop haveged
# systemctl disable haveged
实际上,我在这个例子中停止的 haveged 服务并没有什么黑暗和邪恶。这是一个我经常安装的小工具,用于在创建加密密钥时生成随机的后台系统活动。
9.5.3. 搜索已安装的软件
是否有人或某物在您不知情的情况下安装了软件?好吧,如果您不查看,您怎么知道?要获取完整简报,请使用yum list installed或 Debian/Ubuntu 上的dpkg --list。要删除任何不属于的包,请使用remove <packagename>:
# yum list installed
# yum remove packageName
下面是如何在 Ubuntu 上操作的:
# dpkg --list *1*
# apt-get remove packageName
- 1 输出长长的包列表,您需要尽可能快地视觉扫描。我不了解任何快捷方式。
也要注意您的系统配置文件的变化。这是您将在第十一章中学到的东西。
摘要
-
使用防火墙,您可以通过协议、端口、源或目的地来控制网络流量。
-
配置应用程序监听非标准网络端口,以向您的基础设施添加隐蔽性安全。
-
使用从 CA 接收到的证书,客户端主机浏览器会话被加密,大大减少了传输数据被泄露的风险。
-
在多用户文件系统中使用 SELinux 强制执行全局控制。
-
使用组来严格控制对资源的访问,以允许用户和进程获得他们需要的精确访问权限。
-
定期(脚本)审计运行进程、已安装软件和开放端口对于持续的服务器安全至关重要。
关键术语
-
您可以使用iptables或更简单的高级工具在 Linux 上管理防火墙规则。
-
超文本传输协议(HTTP)管理通过网络进行的基于浏览器的数据传输。
-
传输层安全性(TLS)协议强制执行主机-客户端网络数据传输的数据加密。
-
自主访问控制系统(DACs)允许用户控制文件系统资源。
-
在强制访问控制系统(MACs)上的资源控制最终由系统级策略管理。
-
微服务 是作为跨越多个容器的单个大型应用程序基础设施的一部分,从单个容器中运行的独立计算机服务。
命令行审查
-
firewall-cmd --permanent --add-port=80/tcp打开 80 端口以允许传入 HTTP 流量,并配置它在启动时重新加载。 -
firewall-cmd --list-services列出 firewalld 系统上的当前活动规则。 -
ufw allow ssh使用 Ubuntu 上的 UncomplicatedFirewall (ufw) 打开 22 端口以允许 SSH 流量。 -
ufw delete 2根据由ufw status命令列出的第二个 ufw 规则来删除。 -
ssh -p53987 username@remote_IP_or_domain使用非默认端口登录 SSH 会话。 -
certbot --apache配置 Apache Web 服务器使用 Let’s Encrypt 加密证书。 -
selinux-activate在 Ubuntu 机器上激活 SELinux。 -
setenforce 1在 SELinux 配置中切换强制模式。 -
ls -Z /var/www/html/显示指定目录中文件的网络安全上下文。 -
usermod -aG app-data-group otheruser将 otheruser 用户添加到 app-data-group 系统组。 -
netstat -npl在服务器上扫描打开(监听)的网络端口。
测试自己
1
你担心黑客可能已经访问了你的服务器,并想确保他们无法将权限提升到 root 权限。以下哪个命令可能有所帮助?
firewall-cmd --list-servicesnetstat -nplcertbot --apacheawk -F: '($3 == "0") {print}' /etc/passwd2
你注意到你的服务器上有一些你无法解释的网络端口是开放的。以下哪个工具可以用来关闭它们?
- firewalld
- netstat
- certbot --apache
- awk
3
在多个容器中分割单个应用程序的服务有什么安全优势?
- 一个失败不会必然影响其他人的性能。
- 一个漏洞不会必然传播到其他。
- 这种设计将身份验证进一步推离服务器。
- 这种设计增加了进程可见性。
4
以下哪个命令将允许从单个 IP 地址访问服务器的 SSH?
firewall-cmd allow from 10.0.3.1 to any port 22ufw allow from 10.0.3.1 to port 22ufw allow from 10.0.3.1 to any port 22firewall-cmd --allow from 10.0.3.1 to any port 225
从 CA 请求 TLS 证书允许你
- 防止未经授权的用户访问你的 Web 服务器后端
- 在 Web 服务器上确保静态数据的安全
- 在 Web 服务器和客户端之间传输数据时确保数据安全
- 允许无密码 SSH 访问你的 Web 服务器后端
6
以下哪个/etc/ssh/sshd_conf 文件中的设置将强制 SSH 客户端使用密钥对?
- PermitRootLogin no
- PermitRootLogin yes
PasswordAuthentication no
- PasswordAuthentication no
7
以下哪个命令将设置 SELinux 为宽容模式?
setenforce 0chcon -t samba_share_t /var/www/html/index.htmlsetenforce 1selinux-activate8
以下哪个命令将使 app-data-group 成为 datafile.txt 文件的组?
chown app-data-group,ubuntu datafile.txtchown app-data-group datafile.txtchown app-data-group:ubuntu datafile.txtchown ubuntu:app-data-group datafile.txt
答案键
1.
d
2.
a
3.
b
4.
c
5.
c
6.
d
7.
a
8.
d
第十章. 保护网络连接:创建 VPN 或 DMZ
本章涵盖
-
实施服务器安全配置
-
部署 OpenVPN 隧道以保护远程连接
-
使用防火墙控制段之间的访问
-
使用 iptables 和 Shorewall 创建基于 DMZ 的网络
-
使用虚拟环境测试网络连接解决方案
他们告诉我们我们生活在一个高度移动的世界。不过,我不知道:我很少离开我的家庭办公室。我能享受到家庭办公室的舒适,因为所有我可能需要的服务器资源都可以远程访问。显然,我并不孤单。
几乎所有与 IT 工作相关的人都会时不时地从远程位置访问他们的专业工具。鉴于你通过这些远程位置访问的公共网络本质上是不可靠的,你将想要仔细控制这些连接。
上一章主要关注确保远程客户端使用的数据能够可靠地传输,并且对可能潜伏在连接网络上的任何人都不可见。而本章,与上一章形成鲜明对比,将专注于确保远程客户端使用的数据能够可靠地传输,并且对可能潜伏在连接网络上的任何人都不可见。你看到了区别吗?我也没看到。
事实上,有各种各样的技术致力于保护网络通信,深度防御的原则教导我们永远不要依赖单一技术。这就是你将学习为你的远程活动添加新层保护的地方。
在本章中,你将重温几位老朋友。你将使用 VirtualBox 虚拟机加密来构建一个虚拟专用网络(VPN)隧道,以允许安全的、不可见的远程连接。在另一个项目中,你将设计更复杂的防火墙架构,以战略性地将你的网络划分为隔离的段。最后,你将在 VirtualBox 中构建一个虚拟网络环境,以便你可以测试你的配置。
10.1. 构建 OpenVPN 隧道
在这本书中,我已经详细讨论了加密。SSH 和 SCP 可以保护通过远程连接传输的数据(第三章),文件加密可以保护静态数据(第八章),而 TLS/SSL 证书可以保护网站和客户端浏览器之间传输的数据(第九章)。但有时你的需求需要保护更广泛的连接,因为偶尔你可能需要做不同类型的工作。例如,也许你的团队中的一些成员需要使用公共 WiFi 热点在路上工作。假设随机的 WiFi 接入点是安全的肯定是不明智的,但你的员工确实需要一种方式来连接公司资源——VPN 来拯救。
一个设计得当的 VPN 隧道以隐藏数据在跨越不安全网络传输的方式,为远程客户端和服务器之间提供直接连接。但这又如何呢?您已经看到了很多使用加密来完成这一点的工具。VPN 的真实价值在于一旦您打开了一个隧道,就可以将远程网络连接起来,就像它们都在本地一样。从某种意义上说,您是在绕过那些可疑的咖啡店热点。
使用这样的扩展网络,管理员无论身在何处都可以在其服务器上完成工作。但更重要的是,正如您在图 10.1 中可以看到的,一个资源分散在多个分支办公室的公司可以使所有这些资源对所有需要它们的团队都可见且可访问,无论它们身在何处。
图 10.1. 通过公共网络连接远程私有连接的隧道

仅凭隧道的存在并不能保证安全。但可以将多种加密标准之一纳入设计之中,从而使事情变得好得多。使用开源 OpenVPN 软件包构建的隧道使用了您在其他地方已经见过的相同 TLS/SSL 加密。OpenVPN 并非隧道连接的唯一选择,但它是最为人所知的之一。而且普遍认为它比使用 IPsec 加密的替代性第二层隧道协议要快一些,也更安全。
您希望您的团队能够在公路上或多个校园之间安全地相互连接。为此,您需要构建一个 OpenVPN 服务器,以允许共享应用程序并访问服务器的本地网络环境。为了使其工作,启动两个虚拟机或容器应该足够了:一个扮演服务器/主机的角色,另一个扮演客户端的角色。构建 VPN 涉及许多步骤,因此花几分钟时间思考这个系统将如何运作可能是值得的。
10.1.1. 配置 OpenVPN 服务器
在开始之前,这里有一个有用的提示。如果您打算自己跟随这个过程,我强烈推荐这样做,您可能会发现自己需要在桌面上打开多个终端窗口,每个窗口登录到不同的机器。相信我,您会在某个时候将命令输入到错误的窗口中,完全破坏您的环境。为了避免这种情况,您可以使用hostname命令将命令行上显示的机器名称更改为一个能视觉上提醒您所在位置的名字。一旦完成,您需要退出服务器并重新登录,以便新设置生效。以下是它的样子:
ubuntu@ubuntu:~# hostname OpenVPN-Server
ubuntu@ubuntu:~$ exit *1*
<Host Workstation>$ ssh ubuntu@10.0.3.134
ubuntu@OpenVPN-Server:~#
- 1 通过退出 shell 并重新登录来激活您的新主机名。
按照这种方法为每个您正在使用的机器分配适当的名称可以帮助您跟踪您的位置。
注意
在使用 hostname 之后,当你运行后续命令时可能会遇到令人烦恼的无法解析主机 OpenVPN-Server 消息。更新 /etc/hosts 文件以匹配新的主机名应该可以解决这个问题。
为 OpenVPN 准备你的服务器
在你的服务器上安装 OpenVPN 需要两个软件包:openvpn 和用于管理加密密钥生成过程的 easy-rsa。CentOS 用户如果需要,应首先以你在第二章中做的方式安装 epel-release 仓库。为了提供一个简单的方法来测试对服务器应用程序的访问,你也可以安装 Apache 网络服务器(Ubuntu 上的 apache2 和 CentOS 上的 httpd)。
当你在设置服务器时,不妨做得正确并激活一个防火墙,该防火墙除了 22(SSH)和 1194(默认 OpenVPN 端口)之外的所有端口都被阻止。以下示例说明了在 Ubuntu 的 ufw 上如何工作,但我相信你仍然记得第九章中的 CentOS 的 firewalld:
# ufw enable
# ufw allow 22
# ufw allow 1194
要允许服务器上网络接口之间的内部路由,你需要在 /etc/sysctl.conf 文件中取消注释一行(net.ipv4.ip_forward=1)。这允许远程客户端在连接后按需进行重定向。要加载新设置,运行 sysctl -p:
# nano /etc/sysctl.conf
# sysctl -p
服务器环境现在已经全部设置好了,但在你准备好切换之前还有一段路要走。接下来几页我们将介绍以下步骤:
-
使用 easy-rsa 软件包提供的脚本在服务器上生成一套公共密钥基础设施(PKI)加密密钥。实际上,OpenVPN 服务器也充当自己的证书颁发机构(CA)。
-
准备客户端的匹配密钥。
-
为服务器配置 server.conf 文件。
-
配置你的 OpenVPN 客户端。
-
测试你的 VPN。
生成加密密钥
为了简单起见,你将在运行 OpenVPN 服务器的同一台机器上设置密钥基础设施。然而,安全最佳实践通常建议在生产部署中使用单独的 CA 服务器。在任何情况下,图 10.2 说明了为 OpenVPN 使用生成和分发加密密钥资源的过程。
图 10.2. 你的 PKI 服务器将创建的文件及其分发目标

当你安装了 OpenVPN 后,一个 /etc/openvpn/ 目录会自动创建,但里面目前还没有太多内容。openvpn 和 easy-rsa 软件包都包含了一些样本模板文件,你可以将这些文件作为配置的基础。为了快速启动认证过程,将 easy-rsa 模板目录从 /usr/share/ 复制到 /etc/openvpn/,然后切换到 easy-rsa/目录:
# cp -r /usr/share/easy-rsa/ /etc/openvpn
$ cd /etc/openvpn/easy-rsa
因为 easy-rsa 目录现在将包含相当多的脚本,表 10.1 给出了你将使用以创建密钥的工具的快速预览。
表 10.1. 关键的 easy-rsa 脚本及其功能
| 脚本名称 | 功能 |
|---|---|
| clean-all | 清除旧密钥文件,为生成新密钥做准备 |
| pkitool | OpenSSL 的前端(执行大部分密钥生成繁重工作) |
| build-ca | 使用 pkitool 脚本生成根证书 |
| build-key-server server | 使用 pkitool 脚本生成密钥对和证书 |
| build-dh | 设置 Diffie-Hellman 认证参数 |
注意事项
这些操作需要 root 权限,因此通过sudo su,你需要成为 root 用户。
你将要处理的第一份文件名为 vars,其中包含了 easy-rsa 生成密钥时使用的环境变量。你需要编辑该文件,用你自己的值替换掉已经存在的示例默认值。以下是我的文件看起来会是什么样子。
列表 10.1. 来自 /etc/openvpn/easy-rsa/vars 文件的要点摘录
export KEY_COUNTRY="CA"
export KEY_PROVINCE="ON"
export KEY_CITY="Toronto"
export KEY_ORG="Bootstrap IT"
export KEY_EMAIL="info@bootstrap-it.com"
export KEY_OU="IT"
运行 vars 文件会将它的值传递到 shell 环境中,这些值将随后被整合到您新键的内容中。为什么仅使用 sudo 就不起作用呢?因为第一步是编辑一个名为 vars 的脚本,然后执行它。执行 意味着 vars 文件将它的值传递到 shell 环境中,这些值将随后被整合到您新键的内容中。
确保使用新的 shell 再次运行文件,如果你需要完成未完成的进程。当这完成后,脚本将鼓励你运行 clean-all 脚本以删除/etc/openvpn/easy-rsa/keys/目录中存在的任何内容:
$ cd /etc/openvpn/easy-rsa/
# . ./vars *1*
NOTE: If you run ./clean-all,
I will be doing a rm -rf on /etc/openvpn/easy-rsa/keys
- 1 此命令需要 sudo 权限。
自然地,你的下一步将是运行那个清理所有内容的脚本,然后是 build-ca 脚本,该脚本使用 pkitool 脚本来创建你的根证书。你将需要确认由 vars 提供的识别设置:
# ./clean-all
# ./build-ca
Generating a 2048 bit RSA private key
接下来是构建密钥服务器脚本。因为它使用了与新的根证书相同的 pkitool 脚本,所以你将需要回答相同的问题来生成密钥对。密钥的名称将基于你传递的参数,除非你在这台机器上运行多个 VPN,否则通常会是server,就像这个例子中一样:
# ./build-key-server server
[...]
Certificate is to be certified until Aug 15 23:52:34 2027 GMT (3650 days)
Sign the certificate? [y/n]:y
1 out of 1 certificate requests certified, commit? [y/n]y
Write out database with 1 new entries
Data Base Updated
OpenVPN 使用通过运行 build-dh 生成的 Diffie-Hellman 算法参数来协商新连接的认证。在此处创建的文件不需要保持机密,但它必须使用当前有效的 RSA 密钥通过 build-dh 脚本生成。如果在未来的某个时间点创建了新的 RSA 密钥,也需要更新 Diffie-Hellman 文件:
# ./build-dh
您的服务器端密钥现在已经被写入到 /etc/openvpn/easy-rsa/keys/ 目录中,但 OpenVPN 并不知道这一点。默认情况下,OpenVPN 将在 /etc/openvpn/ 中查找它们,因此请将它们复制过去:
# cp /etc/openvpn/easy-rsa/keys/server* /etc/openvpn
# cp /etc/openvpn/easy-rsa/keys/dh2048.pem /etc/openvpn
# cp /etc/openvpn/easy-rsa/keys/ca.crt /etc/openvpn
准备客户端加密密钥
正如您已经看到的,TLS 加密使用匹配的密钥对:一个安装在服务器上,另一个安装在远程客户端上。这意味着您将需要客户端密钥。我们那位老朋友 pkitool 正是制作这些密钥的好工具。此示例在仍位于/etc/openvpn/easy-rsa/目录下运行,将client作为参数传递以生成名为 client.crt 和 client.key 的文件:
# ./pkitool client
两个客户端文件,以及仍然位于 keys/目录中的原始 ca.crt 文件,现在必须安全地传输到您的客户端。由于它们的所有权和权限,这可能有点复杂。最简单的方法是在您的 PC 桌面上运行的终端中手动复制源文件的内容(仅限于这些内容),通过突出显示文本,右键单击并从菜单中选择复制,然后将其粘贴到在客户端登录的第二个终端中创建的同名新文件中。
但任何人都可以剪切和粘贴。相反,要像管理员一样思考,因为您并不总是能够访问到可以进行剪切和粘贴的 GUI。相反,将文件复制到您的用户主目录中(以便远程scp操作可以访问它们),然后使用chown将文件的所有权从 root 更改为您的常规非 root 用户,以便远程 scp 操作可以工作。确保您的文件现在都已就绪并舒适。您稍后会将它们移动到客户端:
# cp /etc/openvpn/easy-rsa/keys/client.key /home/ubuntu/
# cp /etc/openvpn/easy-rsa/keys/ca.crt /home/ubuntu/
# cp /etc/openvpn/easy-rsa/keys/client.crt /home/ubuntu/
# chown ubuntu:ubuntu /home/ubuntu/client.key
# chown ubuntu:ubuntu /home/ubuntu/client.crt
# chown ubuntu:ubuntu /home/ubuntu/ca.crt
准备好一套完整的加密密钥后,您需要告诉服务器您想要如何构建 VPN。这是通过使用 server.conf 文件来完成的。
节省按键
对于您那双可怜、疲惫的手指来说,输入太多?展开式可以帮您将这六个命令减少到两个。我相信您能够研究这两个示例并弄清楚发生了什么。更重要的是,您将能够弄清楚如何将这些原则应用到涉及数十个甚至数百个元素的操作中:
# cp /etc/openvpn/easy-rsa/keys/{ca.crt,client.{key,crt}} /home/ubuntu/
# chown ubuntu:ubuntu /home/ubuntu/{ca.crt,client.{key,crt}}
配置 server.conf 文件
您应该如何知道 server.conf 文件应该是什么样子呢?好吧,记得您从/usr/share/中复制的 easy-rsa 目录模板吗?那里还有更多好东西。OpenVPN 安装留下了一个压缩的模板配置文件,您可以将其复制到/etc/openvpn/。我会利用模板是压缩的这个事实来向您介绍一个有用的工具:zcat。
您已经知道如何使用cat将文件的文本内容打印到屏幕上,但如果文件是用 gzip 压缩的呢?您始终可以解压缩文件,然后cat会乐意将其打印出来,但这多了一步或两步。相反,正如您可能已经猜到的,您可以使用zcat在一步中将解压缩的文本加载到内存中。在这种情况下,您不会将其打印到屏幕上,而是将文本重定向到名为 server.conf 的新文件中:
# zcat \
/usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz \
> /etc/openvpn/server.conf
$ cd /etc/openvpn
忽略文件附带的大量有用文档,编辑完成后可能看起来是这样的。注意,分号(;)告诉 OpenVPN 不要读取和执行其后的行。
列表 10.2. 来自/etc/openvpn/server.conf文件的活跃设置
port 1194
# TCP or UDP server?
proto tcp
;proto udp
;dev tap
dev tun
ca ca.crt
cert server.crt
key server.key # This file should be kept secret
dh dh2048.pem
server 10.8.0.0 255.255.255.0
ifconfig-pool-persist ipp.txt
push "route 10.0.3.0 255.255.255.0"
keepalive 10 120
comp-lzo
port-share localhost 80
user nobody *1*
group nogroup
persist-key
persist-tun
status openvpn-status.log
log openvpn.log *2*
;log-append openvpn.log
verb 3 *3*
-
1 最小化系统权限暴露
-
2 将会话日志写入到
/etc/openvpn/openvpn.log -
3 输出详细程度,可以高达 9
让我们逐个分析这些设置:
-
默认情况下,OpenVPN 通过端口号 1194 工作。你可以更改它,可能是为了进一步隐藏你的活动或避免与其他活动隧道冲突。因为它需要客户端之间最少的协调,所以 1194 通常是你的最佳选择。
-
OpenVPN 使用传输控制协议(TCP)或用户数据报协议(UDP)进行数据传输。TCP 可能稍微慢一点,但更可靠,并且更有可能与隧道两端运行的应用程序兼容。
-
当你想创建一个更简单、更有效的 IP 隧道,仅传输数据内容而不传输其他内容时,可以指定
dev tun。另一方面,如果你需要通过创建以太网桥接连接多个网络接口(以及它们所代表的网络),那么你必须选择dev tap。如果你对这一切都不懂,就选择tun。 -
接下来的四行将 OpenVPN 传递之前创建的三个服务器认证文件和 dh2048 参数文件的名称。
-
server行设置用于在客户端登录时分配 IP 地址的子网范围和子网掩码。 -
可选的
push "route 10.0.3.0 255.255.255.0"设置允许远程客户端访问服务器后面的私有子网。使此设置生效还需要在服务器本身上进行网络配置,以确保私有子网知道 OpenVPN 子网(10.8.0.0)。 -
port-share localhost 80这一行允许进入端口号 1194 的客户端流量被重定向到监听端口号 80 的本地 Web 服务器。(这在当前情况下将很有用,因为你将使用 Web 服务器来测试你的 VPN。)这仅在proto设置为tcp时才有效。 -
应该通过删除分号(;)来启用
user nobody和group nogroup行。强制远程客户端以 nobody 和 nogroup 身份工作确保了它们在服务器上的会话将不具备特权。 -
log设置每次 OpenVPN 启动时覆盖旧日志条目,而log-append则将新条目追加到现有日志文件中。openvpn.log 本身将被写入到/etc/openvpn/目录。
此外,通常还会在配置文件中添加client-to-client,这样多个客户端除了能看到 OpenVPN 服务器外,还能相互看到。一旦你对配置满意,就可以启动 OpenVPN 服务器了:
# systemctl start openvpn
注意
由于 OpenVPN 和 systemd 之间关系的发展性,启动服务有时可能需要这样的语法:systemctl start openvpn@server。
运行 ip addr 列出您服务器的网络接口时,现在应该包括一个名为 tun0 的新接口的引用。这将是由 OpenVPN 为 incoming clients 创建的:
$ ip addr
[...]
4: tun0: <POINTOPOINT,MULTICAST,NOARP,UP,LOWER_UP> mtu 1500 qdisc [...]
link/none
inet 10.8.0.1 peer 10.8.0.2/32 scope global tun0
valid_lft forever preferred_lft forever
可能您需要在一切完全正常工作之前重新启动服务器。下一个步骤:客户端计算机。
10.1.2. 配置 OpenVPN 客户端
传统上,隧道至少有两个端点(否则我们将它们称为洞穴)。在服务器端正确配置 OpenVPN 将引导流量进入和离开该端的隧道。但您还需要在客户端侧运行某种软件。
在本节中,我将专注于手动配置某种 Linux 计算机作为 OpenVPN 客户端。但这不是您可能想要使用该服务的唯一方式。OpenVPN 本身维护着可以在 Windows 或 Mac 桌面和笔记本电脑、Android 和 iOS 智能手机和平板电脑上安装和使用的客户端应用程序。有关详细信息,请参阅 openvpn.net 网站。
OpenVPN 软件包需要在客户端计算机上安装,就像在服务器上一样,尽管这里不需要 easy-rsa,因为您将使用的密钥已经存在。您需要将客户端.conf 模板文件复制到刚刚创建的 /etc/openvpn/ 目录。这次,由于某种原因,文件不会被压缩,所以普通的 cp 就可以完成工作:
# apt install openvpn
# cp /usr/share/doc/openvpn/examples/sample-config-files/client.conf \
/etc/openvpn/
您客户端.conf 文件中的大多数设置都将相当明显:它们需要与服务器使用的值匹配。正如您可以从下一个示例文件中看到的那样,一个独特的设置是 remote 192.168.1.23 1194,它将客户端指向服务器的 IP 地址。再次强调,请确保您使用您服务器的地址。您还应该强制客户端验证服务器证书的真实性,以防止可能的中间人攻击。这样做的一种方法是通过添加 remote-cert-tls server 行。
列表 10.3. VPN 客户端的 /etc/openvpn/client.conf 文件中的活动设置
client *1*
;dev tap
dev tun
proto tcp
remote 192.168.1.23 1194 *2*
resolv-retry infinite
nobind
user nobody
group nogroup
persist-key
persist-tun
ca ca.crt
cert client.crt
key client.key
comp-lzo
verb 3
remote-cert-tls server *3*
-
1 识别计算机为 VPN 客户端,其配置将基于远程服务器
-
2 用于访问 VPN 服务器的地址和网络端口
-
3 强制服务器证书验证
现在您可以移动到 /etc/openvpn/ 目录并从服务器拉取那些证书密钥。用您服务器的 IP 地址或域名替换示例中的地址:
$ cd /etc/openvpn
# scp ubuntu@192.168.1.23:/home/ubuntu/ca.crt . *1*
# scp ubuntu@192.168.1.23:/home/ubuntu/client.crt .
# scp ubuntu@192.168.1.23:/home/ubuntu/client.key .
- 1 句点 (.) 在末尾告诉 scp 将文件保存到当前目录。
直到你在客户端启动 OpenVPN 之前,不太可能发生任何激动人心的事情。因为你需要传递几个参数,所以你会从命令行触发。参数--tls-client告诉 OpenVPN 你将作为客户端并通过 TLS 加密连接,而--config指向你的配置文件:
# openvpn --tls-client --config /etc/openvpn/client.conf
仔细阅读命令输出以确保你已正确连接。如果第一次出现错误,可能是由于服务器和客户端配置文件之间的设置不匹配,或者可能是网络连接/防火墙问题。以下是一些故障排除步骤:
-
仔细阅读客户端上 OpenVPN 操作的输出。它通常会包含关于它无法做什么以及为什么的宝贵提示。
-
在服务器上/etc/openvpn/目录中的 openvpn.log 和 openvpn-status.log 文件中检查错误相关的消息。
-
检查服务器和客户端系统日志中的 OpenVPN 相关和及时消息。(
journalctl -ce将打印出满屏的最新条目。) -
确认服务器和客户端之间有一个活跃的网络连接(详情请见第十四章)。
10.1.3. 测试你的 VPN
如果客户端加载时 OpenVPN 输出的所有内容看起来都正常,那么你应该继续测试你的隧道以确认它正在工作并保护你的连接。从客户端使用 OpenVPN 端口针对服务器地址运行curl应该返回你的 Web 根目录中的 index.html 文件(/var/www/html/):
curl 192.168.1.23:1194
通过移除端口号及其前面的冒号来测试你的设置。假设你的防火墙以之前的设置运行,curl不应该工作。
事实上,这本身并不是一个有用的测试。毕竟,VPN 的目标是防止你的会话活动对网络上其他人可见,所以能够加载页面并不能证明什么。不深入细节,以下几节建议了一些方法(不分先后顺序)来确认你的 VPN 是否正常工作。
网络嗅探
将一台常规的 GUI 桌面 PC 设置为客户端,并使用浏览器打开需要某种数据输入的服务器端应用程序。对于应用程序,你可以使用以下简单的 HTML 表单。
列表 10.4. 提供浏览器数据输入表单的简单 HTML 页面
<!DOCTYPE html>
<html>
<body>
<form action="/action_page.php">
First name:<br>
<input type="text" name="firstname" value="Your">
<br>
Last name:<br>
<input type="text" name="lastname" value="Name">
<br><br>
<input type="submit" value="Submit">
</form>
</body>
</html>
你可以将文件保存到服务器的 Web 文档根目录,例如 form.html。然后,你将使用 192.168.1.23/form.html URL(替换你的服务器 IP 地址)访问该页面。
为了找出你输入到表单中的信息是否会被未经授权的眼睛看到,你可以使用 Wireshark 等网络数据包嗅探软件来捕获和分析数据。如果隧道成功加密了会话,那么包含你输入的名称的数据包将无法理解。
不幸的是,一本关于如何设置和使用 Wireshark 进行此目的的有用指南将需要自己的完整章节。由于这将远离我们钟爱的 Linux,那个章节将不得不在另一本书中找到位置。
网络基础设施
测试 VPN 的另一种方法是在服务器的本地网络内设置资源(服务器、路由器等),这是从你的 server.conf 文件中将客户端推送到的地方。构建一种基础设施以使其工作可能相当复杂。但如果你能成功做到这一点,并且你的客户端可以访问这些资源,那么你就知道一切正常。我在本章末尾添加了一个使用 VirtualBox 构建此类基础设施的指南。
10.2. 构建防入侵网络
通过加密的魔力,VPN 非常适合保护会话数据。防火墙可以通过端口、协议或路由信息很好地控制进出流量。从理论上讲,这应该足以保护你的服务器网络。毕竟,如果你已经将除了几个方法之外的所有方法都锁定在你的网络中,并加强了剩余几个门的锁(例如,使用无密码密钥对认证 SSH),那么你应该很安全,对吧?如果事情真的这么简单就好了。
无论你多么优秀,在 IT 安全方面,100%的保证是不存在的。你尽你所能了解新的威胁类型,修补你的软件,审计你的流程,并微调你的防火墙,但总有人会找到一种方法穿过墙壁。除了人为错误和有缺陷或故意被破坏的软件的风险之外,量子计算机器能够破解加密算法并变得广泛可用只是时间问题。
实际上,解决方案是通过尽可能多地添加安全配置文件中的层级来过度配置,这样即使坏人通过了第一个关卡,仍然还有两三个关卡在你和深渊之间。 (之前我们称之为深度防御。) 经过深思熟虑,你可能会决定其中一层涉及将资源分成多个隔离的网络,希望如果其中一个被攻破,其他网络可能仍然得到保护。
当所有事情都讨论完毕后,主要目标是安全地暴露所有应该暴露的服务,并严格保护其他一切。假设你正在运行一个托管公开应用的 Web 服务器。用户的浏览器将从 Web 服务器加载页面,而用户信息和应用程序数据则由运行数据库的第二个服务器处理。你希望地球上每个人(以及可能在大气层低轨道欣赏风景的任何人)都能访问你的 Web 服务器,但关闭对数据库服务器的所有公开访问。让我们讨论一下如何完成这个特别的魔术表演。
10.2.1. 非军事化区域(DMZ)
一种流行的隔离架构被称为DMZ(这是描述两个不信任国家之间地理缓冲区的短语的缩写:非军事区)。其想法是将你的服务器、工作站、WiFi 路由器和其他资源划分为单独的网络。为了使其工作,你的每个设备都需要物理连接到单独的网络接口。
DMZ 的一个简单实现,如图 10.3[figure 10.3]所示,是使用单个服务器作为路由器,在互联网和两个内部网络之间重定向流量。其中一个网络可能包含后端数据库或你在办公室中使用的工作站和笔记本电脑。这个网络将通过严格的访问规则得到高度保护。另一个网络将享有相当直接和容易的外部世界访问,可能包括像 Web 服务器这样的公共资源。
图 10.3. 一个简单的三接口 DMZ 架构

在继续构建自己的 DMZ 环境之前,你应该至少了解一些 DMZ 模型的替代方案。我将在接下来的章节中提到一些。
抵抗性网络:设计考虑因素
除了 DMZ 之外,它们足够可靠和灵活,可以成为各种用例的候选者,还有其他你可能考虑的网络安全配置模型。
一个跳转服务器(有时被称为跳转盒或,也许,堡垒主机)是一个对互联网开放的轻量级服务器,它只赋予一个功能:允许远程 SSH 客户端登录,然后“跳转”到跳转服务器私有网络上运行的其他服务器。如图 10.4figure 10.4 所示,网络上的私有服务器将配置某种访问控制,仅允许来自跳转服务器的远程登录访问。
图 10.4. 一个典型的跳转服务器架构

跳转服务器(按照定义,代表一个可能的单点故障和额外的复杂性层)不像以前那样受欢迎。如第九章 chapter 9 中提到的,像 Juniper 和 Cisco 这样的公司提供的专有硬件解决方案可以用来管理大型企业部署的连接性和安全性。但这类硬件的成本以及你需要克服的显著的学习曲线有时可能会超过其带来的好处。
在混合云解决方案中,公司基础设施既托管在本地,又托管在远程云平台上,这些系统中的任何一个都不一定对混合云解决方案有很大意义。但底层工具和设计概念始终会很有用。
为什么使用 DMZ?
接下来,我将讨论使用两个广泛使用的软件防火墙包创建 DMZ:iptables 和 Shorewall。在我这样做之前,我应该告诉你,从所有目的和意义上讲,它们基本上是同一件事。Shorewall 是一个更用户友好的工具,它所做的只是在不让你看到的情况下操作 iptables 规则。这里有两条原因,为什么我想让你看到这两种方法:
-
通常情况下,会有一些狭窄、实用的考虑因素来指导你选择这些工具,因此拥有多个选项可以让你有一个良好的起点。
-
即使你最终选择使用像 Shorewall 这样更简单的解决方案,了解 iptables 在其自然环境中也是有益的。这将有助于加深你对 Linux 防火墙的一般理解。
好了,让我们开始吧。警告:接下来的内容是复杂且深入的。如果你觉得不感兴趣,可以直接跳到本章的结尾。记住,下次有人要求你构建这类基础设施时,一定要回到这里。
10.2.2. 使用 iptables
iptables 是用于管理 Linux 机器上防火墙规则的工具。iptables?那么你之前章节中学到的 ufw 和 firewalld 命令集怎么办?所以,你不喜欢选择吗?如果告诉你还有一个名为 nftables 的第四个工具,会不会破坏你的心情?
好吧,我承认整个事情确实有点奇怪,所以让我来解释一下。一切始于 netfilter,它在 Linux 内核模块级别控制对网络堆栈的访问。几十年来,管理 netfilter 钩子的主要命令行工具是 iptables 规则集。
因为调用这些规则所需的语法可能显得有些晦涩,因此引入了各种用户友好的实现,如 ufw 和 firewalld,作为更高级的 netfilter 解释器。然而,ufw 和 firewalld 主要设计用来解决独立计算机面临的问题。构建完整的网络解决方案通常需要 iptables 的额外力量,或者从 2014 年起,它的替代品 nftables(通过 nft 命令行工具)。
iptables 并没有消失,仍然被广泛使用。实际上,你应该预期在接下来的许多年里,作为管理员,你可能会遇到被 iptables 保护的网络。但是,通过扩展经典的 netfilter 工具集,nftables 增加了一些重要的新功能。然而,为了简单起见,我将使用 iptables 来快速展示如何从命令行手工构建一个 DMZ。完成这个任务后,我们将转向稍微更直观的 Shorewall 实现。
10.2.3. 使用 iptables 创建 DMZ
iptables 应该默认安装。要确认这一点,你可以运行iptables -L来列出所有当前规则。如果你还没有玩过它,你应该看到一个完全开放的(ACCEPT)防火墙。请注意,INPUT、FORWARD和OUTPUT链都是默认Filter表的一部分。默认存在的 NAT 表可以用来更改(或转换)数据包的路由信息,以便它可以在网络之间成功移动。我们稍后会再次讨论 NAT:
# iptables -L
Chain INPUT (policy ACCEPT) *1*
target prot opt source destination
ACCEPT tcp -- anywhere anywhere tcp dpt:domain
ACCEPT udp -- anywhere anywhere udp dpt:domain
ACCEPT tcp -- anywhere anywhere tcp dpt:bootps
ACCEPT udp -- anywhere anywhere udp dpt:bootps
Chain FORWARD (policy ACCEPT)
target prot opt source destination
ACCEPT all -- anywhere anywhere
ACCEPT all -- anywhere anywhere
Chain OUTPUT (policy ACCEPT)
target prot opt source destination
- 1 输出(在这种情况下是过滤表的一部分)通过链来标识规则:在这个第一个例子中是 INPUT。
一旦你有了整体计划,在这个例子中是通过 DMZ 设置来区分你运行的服务器类型,你应该设置一个严格的默认策略。
小贴士
防火墙管理员规则#1:在开始之前,始终知道它应该如何结束。
这些规则构成了防火墙的基础,通过阻止(DROP)所有接口的所有传入和转发流量,但允许传出:
# iptables -P INPUT DROP
# iptables -P FORWARD DROP
# iptables -P OUTPUT ACCEPT
因为他们的互联网连接是通过路由服务器(可能通过网络交换机)进行的,所以你网络上的所有设备都会自动绑定到这些规则。亲自测试一下。再次提醒,你可以自由使用以下描述的虚拟网络基础设施。我们将假设连接到你的防火墙服务器的三个网络接口的名称如表 10.2 中所述。
表 10.2. 用于后续示例的网络接口标识
| 标识 | 目的 |
|---|---|
| eth0 | 连接到互联网 |
| eth1 | 连接到 DMZ |
| eth2 | 连接到本地私有网络 |
Linux 网络接口命名约定
从前,你可以安全地假设你的 Linux 发行版会为连接到你的计算机的物理网络接口分配简单直接的名字。第一个识别的以太网接口将是 eth0,第二个是 eth1,以此类推;无线接口将是 wlan0 和 wlan1 等等。(如你在第九章中看到的,虚拟设备如隧道和桥接可以使用不同的约定来命名。)关键是,即使你不知道接口的确切名称,你通常也能很快猜出来。但在你的现代 systemd 机器上运行ip addr,你可能会遇到类似 enp1s0 这样的东西。或者,这个令人厌恶的东西:wlx9cefd5fe4a18。当你重复它时,听起来怎么样?
这里发生的事情是这样的。出于许多合理的理由,使用可预测接口名称的系统更容易管理,即使有一些便利性的权衡。一个以太网接口可能由en表示以太网,p1表示总线 1,s0表示第一个物理插槽。而不同的设备可能得到wl(无线局域网)和x9cefd5fe4a18,其中x表示随后的十六进制数是设备的 MAC 地址。
只要您知道设备连接在哪里以及/或它的 MAC 地址是什么,您就可以安全地预测其接口名称。然而,为了简单起见,我将在本章的示例中使用较旧的约定。
您希望本地网络内的设备能够与 DMZ 中的公共服务器通信,包括路由器本身。这两条规则被添加到 Filter 表中的 FORWARD 链,将允许数据包在 eth1 和 eth2 接口背后的网络之间移动(FORWARD)。-A 表示以下内容应作为新规则添加。这允许 DMZ 中的您的 Web 服务器与私有网络中的数据库服务器交换数据:
iptables -A FORWARD -i eth1 -o eth2 -m state \
--state NEW,ESTABLISHED,RELATED -j ACCEPT
iptables -A FORWARD -i eth2 -o eth1 -m state \
--state ESTABLISHED,RELATED -j ACCEPT
下一条规则将被添加到 NAT 表(-t nat)。它使用 TCP 协议处理通过 eth0 接口到达您应用程序(虚构的)公共 IP 地址(54.4.32.10)的所有流量,并指定它想要使用 HTTP 端口 80。任何满足所有这些条件的流量都将被重定向到 DMZ 中您的 Web 服务器的互联网 IP 地址(192.168.1.20):
iptables -t nat -A PREROUTING -p tcp -i eth0 -d 54.4.32.10 \
--dport 80 -j DNAT --to-destination 192.168.1.20
因为您还没有通过 eth0 接口从互联网打开任何访问权限到 DMZ 或私有网络,所以外部来的任何东西都无法到达任何本地服务器。
您可能认为您已经完成了,但实际上您还没有。等上半个小时左右,您应该开始收到来自您团队开发者的愤怒邮件,他们想知道为什么他们无法下载关键的软件更新或可爱的猫咪视频。准备好自定义您的防火墙设置以适应意外的特殊需求(例如访问远程软件仓库或云资源)。理想情况下,您应该在构建防火墙之前预测您将需要的东西,但老式的“扣动扳机看看什么会坏”的方法也有效。
如我之前提到的,iptables 已经不再是防火墙技术的先锋了,这个例子非常基础,但我认为它确实有助于说明这类事物是如何工作的。如果您对大人物和女士如何在新的部署中完成这类事情感兴趣,可以使用 nftables 组装类似内容的官方指南可在 mng.bz/b0DM 获取。
10.2.4. 使用 Shorewall 创建 DMZ
我确信您已经知道了流程:第一步是获取软件。Ubuntu 用户可以使用 APT 安装 shorewall 软件包,而 CentOS 用户则需要通过 epel-release 仓库来获取:
# yum install epel-release
# yum install shorewall
与基于命令行的 iptables 不同,Shorewall 通过配置文件进行管理。我个人发现,配置文件提供的复杂设置的稳固视觉表示令人安心。一旦您理解了 Shorewall 设置是如何分布在五六份文件中的,您可能会发现使用 Shorewall 语法比使用 iptables 更不令人畏惧。
Shorewall 启动选项由 /etc/default/shorewall 文件控制,但防火墙配置通常由 /etc/shorewall/ 目录中的多个文件处理。/etc/shorewall/shorewall.conf 设置 Shorewall 将在其中运行的 shell 环境。对于简单的项目,您甚至可能不需要修改该文件或您也会在那里找到的 params 和 conntrack 文件。但是,您将需要创建一些其他文件,这些文件在 /usr/share/doc/shorewall/examples/(或在 CentOS 上的 /usr/share/doc/shorewall-5.0.14.1/Samples,其中 5.0.x 是版本号)中存在模板。如果您列出 examples/ 目录的内容,您将看到四个子目录,这些子目录反过来又包含覆盖许多常见场景的示例配置文件:
# ls /usr/share/doc/shorewall/examples/
LICENSE.gz README.txt two-interfaces
one-interface three-interfaces Universal
尽管三个接口选项看起来非常适合我们在这里的计划,但我们将从零开始构建所需的内容,以便您可以清楚地看到整个过程是如何工作的。表 10.3 展示了您可能会使用到的 /etc/shorewall/ 文件的一个快速概述。
表 10.3. 保存于 /etc/shorewall/ 的 Shorewall 配置文件
| Filename | 目的 | 必需 |
|---|---|---|
| zones | 声明您想要创建的网络区域 | 是 |
| interfaces | 定义用于指定区域的网络接口 | 是 |
| policy | 定义控制区域间流量的高级规则 | 是 |
| rules | 定义策略文件中的规则例外 | 否 |
| masq | 定义动态 NAT 设置 | 否 |
| stoppedrules | 定义 Shorewall 停止时的流量流向 | 否 |
| params | 为 Shorewall 设置 shell 变量 | 否 |
| conntrack | 允许指定的流量从 Netfilter 连接跟踪中豁免 | 否 |
通过调用 man shorewall- 并提供你正在寻找的特定文件名称,可以通过 man 命令找到大量良好的文档:
$ man shorewall-rules
区域文件
让我们开始使用文本编辑器创建一个区域文件。第一行定义了 Shorewall 服务器为类型 firewall。您将创建的每个三个活动区域都将使用 ipv4 地址(与 IPv6 相反)。net 区域代表公共网络(所有酷孩子都称之为互联网),dmz 将是您基础设施中的公共面向区域,而 loc 将是您后端服务器的私有、本地网络。
列表 10.5. /etc/shorewall/zones
fw firewall
net ipv4
dmz ipv4
loc ipv4
接口文件
现在,您需要创建一个名为 interfaces 的文件,在该文件中,您将把每个新区域与连接到 Shorewall 服务器的三个网络接口之一关联起来。detect 告诉 Shorewall 您希望网络设置自动检测;dhcp 表示您希望 DHCP 服务器自动分配 IP 地址给您的接口。并且 nosmurfs,routefilter,logmartians 在面向互联网的接口上会过滤可疑的包和源域,并记录相关事件。
列表 10.6. /etc/shorewall/interfaces
net eth0 detect dhcp,nosmurfs,routefilter,logmartians *1*
dmz eth1 detect dhcp
loc eth2 detect dhcp
- 1 将 eth0 接口映射到互联网(net)区域并建立行为协议
策略文件
策略文件建立了你想要的默认、基准行为。在这个示例中,第一行将静默删除来自互联网(net)的所有流量,指向任何目的地。从私有区域(loc)到互联网的出站流量是允许的,使本地计算机能够接收软件更新。第三行说明来自防火墙的流量应在任何地方被接受。最后一行拒绝其他规则未涵盖的所有数据包。
列表 10.7. /etc/shorewall/policy
net all DROP *1*
loc net ACCEPT
fw all ACCEPT
all all REJECT *2*
-
1 DROP 将删除过滤后的数据包,但 REJECT 将删除并还会向发送者发送通知。
-
2 此策略必须放在最后,因为它涵盖了所有前一条规则未涵盖的流量。
规则文件
除了这个简单配置外,你还需要创建的另一个文件是规则文件。随着你的需求变化,规则文件可能会变得相当长且复杂。然而,对于这个练习,你只需要几行。规则文件的主要目标是微调策略文件的粗略排除。
例如,因为你在 DMZ 区域运行 Web 服务器,你希望允许所有使用 TCP 协议的流量通过端口 80(不安全的 HTTP)或端口 443(安全的 HTTP)访问。你还希望从你的本地服务器到 DMZ 中的机器(包括 Shorewall 防火墙服务器本身)开放 SSH 访问。没有 SSH,本地网络上的管理员工作站如何能够管理防火墙?
如果需要远程 SSH 访问,你可能还需要从 net 网络到防火墙开放端口 22。Web(DNAT) 规则允许从互联网到 DMZ 中你的 Web 服务器的端口转发访问。尽管这不是本章示例的一部分,如果你最终在你的防火墙机器上运行 DNS 服务器,你也将从你的本地网络到防火墙机器开放端口 53 的访问。
列表 10.8. /etc/shorewall/rules
ACCEPT all dmz tcp 80,443
ACCEPT net dmz tcp 22
ACCEPT loc dmz tcp 22
ACCEPT loc fw udp 53
Web(DNAT) net dmz:10.0.1.4
剩下的就是启动防火墙机器上的 Shorewall:
# systemctl start shorewall
注意
不要忘记在修改配置文件后重新启动 Shorewall。而且不要以为你不会修改配置文件,因为你努力让一切按预期工作。
10.3. 为基础设施测试构建虚拟网络
你和我都知道,在理解一个工具的工作原理之前,你必须亲自尝试它。但尝试本章讨论的部署将需要不止一个或两个虚拟服务器进行测试。要做好这项工作,你需要设置多个网络,其 I/O 访问可以完全控制。
如果你有一些闲置的机器、一些电缆和几个交换机,那么,太好了,卷起袖子开始吧。我本人非常怀念玩服务器和网络硬件。但我能做什么呢?我的世界几乎完全是虚拟的:我甚至记不起上一次打开自己的工作站是什么时候了。
如果像我一样,你只能通过命令行构建自己的网络,以下是它的工作方式。你将使用 VirtualBox 创建几个虚拟 NAT 网络,为你要启动的每个虚拟机提供网络接口,并手动将这些接口与适当的网络关联起来。
请记住,你能在 VirtualBox 主机机器上启动的虚拟机数量受限于你主机机器上的空闲物理内存和 CPU 能力。在一个运行在 8GB RAM 上的 Linux 主机上,你可能能够挤出三个并发虚拟机。除非你至少有 16GB 的 RAM,否则不要在 Windows 主机上尝试这样做。考虑将一个或两个虚拟机卸载到网络内的另一台单独的 PC 上。
你将使用 vboxmanage 命令行工具进行第一步(为 DMZ 配置创建网络)。据我所知,从 VirtualBox GUI 内部无法完成这项操作。命令natnetwork add告诉 VirtualBox 你想要添加一个新的 NAT 网络。在第一个例子中,netname 将是 dmz,子网将是 10.0.1.0/24。第二个网络将被称为 loc(代表本地),其子网将是 10.0.2.0/24。当网络创建完成后,你将使用natnetwork start启动它们:
$ vboxmanage natnetwork add --netname dmz \
--network "10.0.1.0/24" --enable --dhcp on *1*
$ vboxmanage natnetwork add --netname loc \
--network "10.0.2.0/24" --enable --dhcp on
$ vboxmanage natnetwork start --netname dmz
$ vboxmanage natnetwork start --netname loc
- 1 因为这些只是虚拟网络,创建它们不需要管理员权限。
注意
由于一个已知的错误,可能无法在 5.1 版本之前的 VirtualBox 版本上创建和运行 NAT 网络。
下一步是创建(或者更好,克隆)你需要的虚拟机。(确保你的主机机器有足够的系统内存和 CPU 处理你计划启动的所有虚拟机。)在启动虚拟机之前,点击 VirtualBox 列表中第一个虚拟机的名称一次,然后点击设置按钮,然后点击左侧的“网络”项。如果这个虚拟机将是一个 Shorewall 服务器,那么将其第一个网络适配器连接到通常用于给你的虚拟机提供完全互联网访问的接口。
现在点击适配器 2 标签,然后点击“附加到”下拉菜单,选择 NAT 网络设置。因为你已经创建了一些 NAT 网络,两者都应作为选项可用(图 10.5)。为适配器 2 选择其中一个,然后为适配器 3 选择另一个。
图 10.5. 将连接到 NAT 网络的网络适配器与 VirtualBox 虚拟机关联

要完成 Shorewall 测试环境,请处理其他两个虚拟机的设置 | 网络部分,并将一个与 dmz NAT 网络关联,另一个与 loc 关联。这两个虚拟机将各自只有一个接口。
完成所有这些后,您可以启动您的三个虚拟机并登录。在每个虚拟机上运行ip addr以确认接口已被识别并连接到其网络。如果您在 inet 行上看到正确的 IP 地址(如 10.0.1.5/24),那么一切正常:
$ ip addr
2: enp0s3:: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc [...]
inet 10.0.1.5/24 brd 10.0.1.255/ scope global enp0s3 *1*
valid_lft forever preferred_lft forever
- 1 此接口已正确连接到您的 dmz NAT 网络。
但如果没有 inet 地址,那么您需要手动启动它。以示例中的接口名称(enp0s3)为例,将其用作ifconfig up的参数。这告诉接口最好醒来并开始工作。然后运行dhclient将从 DHCP 服务器请求 dmz 网络上的 IP 地址。(还记得您在创建 NAT 网络时设置--dhcp on的原因吗?那是有原因的。)
# ifconfig enp0s3 up
# dhclient enp0s3 *1*
- 1 如果此操作失败,可能是您的网络硬件或配置有问题。
最后,再次运行ip addr以确保一切正常。您已经拥有了一个测试环境。如果您对子网划分和 NAT 网络不是很清楚,一切都没有失去:耐心等待第十四章。
摘要
-
VPN 隧道可以用来保护远程连接,也可以在远程位置之间安全地暴露网络基础设施。
-
Easy RSA 软件包包含可以用于为 TLS 加密应用程序生成完整公共密钥基础设施(PKI)的脚本。
-
防火墙和网络架构可以一起使用,为不应暴露给公共网络的资源创建受保护的子网环境。
-
Shorewall 防火墙工具通过纯文本文件配置,而 iptables 防火墙实现则通过命令行进行控制。
-
DMZ 网络结构使用防火墙配置将易受攻击的资源放置在受保护的私有子网中,并将面向公众的服务器放置在子网中,从而允许更方便的远程访问。
-
iptables 和 nftables 是 netfilters Linux 内核防火墙工具的深度和灵活的命令行实现。
关键术语
-
虚拟专用网络(VPN)是一种连接远程网络并在它们之间安全暴露资源的方法。
-
VPN 隧道描述了网络协议如何被用来在跨越不安全网络时隐藏 VPN 流量。
-
VPN 客户端是登录到或通过 VPN 服务器的设备。在 OpenVPN 的情况下,客户端可以是运行 OpenVPN GUI 客户端应用程序的手机或 PC,或者配置了 OpenVPN 的 Linux 机器。
-
类似于 WireShark 的数据包嗅探器可以捕获并分析在网络中移动的数据包,揭示它们的内容、来源和目的地细节。
-
一个 NAT 网络 向公共网络呈现单个外部 IP 地址,同时管理其后私有设备之间的流量。这将在第十四章中详细讨论。
命令行审查
-
hostname OpenVPN-Server设置命令提示符描述,以便更容易跟踪您登录的服务器。 -
cp -r /usr/share/easy-rsa/ /etc/openvpn将 Easy RSA 脚本和环境配置文件复制到工作 OpenVPN 目录。 -
./build-key-server server生成一个名为 server 的 RSA 密钥对。 -
./pkitool client从现有的 RSA 密钥基础设施生成客户端密钥集。 -
openvpn --tls-client --config /etc/openvpn/client.conf使用 client.conf 文件中的设置在 Linux 客户端启动 OpenVPN。 -
iptables -A FORWARD -i eth1 -o eth2 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT允许 eth1 和 eth2 网络接口之间的数据传输。 -
man shorewall-rules显示 Shorewall 所使用的规则文件的文档。 -
systemctl start shorewall启动 Shorewall 防火墙工具。 -
vboxmanage natnetwork add --netname dmz --network "10.0.1.0/24" --enable --dhcp on使用 VirtualBox CLI 创建和配置一个带有 DHCP 的虚拟 NAT 网络,用于 VirtualBox 虚拟机。 -
vboxmanage natnetwork start --netname dmz启动一个虚拟 NAT 网络。 -
dhclient enp0s3从 DHCP 服务器请求 enp0s3 接口的 IP 地址。
自我测试
1
正确配置的 OpenVPN 隧道可以通过以下方式提高安全性:
- 将防火墙规则应用于控制网络之间的访问
- 通过子网划分隔离网络连接设备
- 向网络连接添加加密
- 隐藏通过公共网络移动的流量
2
要在服务器上启用内部路由,您需要在 /etc/sysctl.conf 文件中取消注释一行。哪一行?
net.ipv4.ip_forward=1net.ipv4.tcp_syncookies=1net.ipv6.conf.all.accept_redirects = 0net.ipv4.conf.all.accept_source_route = 03
安装 easy-rsa 后,您将在哪里找到用于生成密钥的脚本?
- /usr/share/easy-rsa/
- /usr/share/easy-rsa/scripts/
- /usr/share/easy-rsa/examples/
- /usr/share/docs/easy-rsa/
4
以下哪个脚本将在生成 RSA 脚本方面做大部分工作?
- 变量
- build-key-server
- build.ca
- pkitool
5
安装 OpenVPN 后,您将在哪里找到配置文件模板?
- /usr/share/doc/openvpn/examples/sample-config-files/server.conf/
- /usr/share/doc/openvpn/sample-config-files/server.conf.gz
- /usr/share/doc/openvpn/examples/sample-config-files/server.conf.gz
- /usr/share/openvpn/examples/sample-config-files/server/
6
以下哪个值可以添加到 /etc/openvpn/server.conf 文件中,以将客户端端口转发到 Web 服务器?
port-share localhost 80proto tcpclient-to-clientpush "route 10.0.3.0 255.255.255.0"7
以下哪个 iptables 命令会静默地阻止发送到接口的所有流量?
iptables -P OUTPUT DROPiptables -P INPUT DROPiptables -P INPUT REJECTiptables -P FORWARD DROP8
以下哪个 Shorewall 文件用于为您的防火墙设置默认访问配置文件?
- params
- 接口
- 规则
- 策略
答案键
1.
c
2.
a
3.
a
4.
d
5.
c
6.
a
7.
b
8.
d
第十一章. 系统监控:与日志文件一起工作
本章涵盖
-
过滤日志条目以维护系统健康
-
关心并培养你的 Linux 日志系统
-
使用 grep、awk 和 sed 过滤文本流
-
部署入侵检测系统
如果你只能使用这本书中到目前为止学到的东西,我会说你已经准备好构建一个相当令人尊重的服务器。它将连接、自动化、备份,对远程客户端请求数据和其它服务开放,并且至少是合理安全的。所有家的舒适。
是时候放下脚休息并享受风景了吗?还不行。你的服务器可能已经正确配置,但你还需要关注它在进入生产环境后如何处理道路。这是怎么工作的?正如你很快就会看到的,大多数 Linux 系统监控都包括读取日志文件。
日志条目是某些系统事件的基于文本的记录。当用户输入身份验证凭据、远程客户端从 Web 服务器请求数据、应用程序崩溃或连接了新的硬件设备时,描述性注释会被附加到一个或多个日志文件中。
即使是轻度使用的系统,在启动和关闭之间也可能生成数千行日志文件,繁忙的应用程序每天可以轻松生成数百万行。由于日志文件通常很长且无聊,你可能希望将它们的阅读外包给能够智能地过滤仅包含紧急条目(如即将发生的故障警告)的软件,并且仅在绝对必要时才通知你。你越擅长配置你系统的日志行为和管理不断增长的日志文件,你就越能了解你系统的优势和劣势——你的系统也将变得更加可靠。
对于能够阅读它们的人来说,日志文件是一个充满宝贵见解的宝库。它们可以告诉你关于你安全防御中的弱点以及过去的未授权入侵。日志条目可以帮助你预测系统安全和性能问题,并在一切都已经失控后诊断它们。
你可以说,本章是关于为你提供可用的日志资源的清单,并描述配置、消费和管理日志的最佳实践方法。你还将了解可以设置为定期扫描你的服务器和网络环境以寻找可疑活动迹象的入侵检测工具。
但本章实际上专注于使你的系统免受安全漏洞和性能中断的影响。如果你是一个负责关键公开服务器的管理员,那么这种加固措施就是为你准备的。
11.1. 与系统日志一起工作
几十年来,Linux 日志记录一直由syslogd守护进程管理。Syslogd 会收集系统进程和应用程序发送到/dev/log 虚拟设备上的日志消息。然后它会将这些消息定向到/var/log 目录中适当的纯文本日志文件。Syslogd(图 11.1)会知道将消息发送到何处,因为每个消息都包含包含元数据字段的标题(包括时间戳、消息来源和优先级)。
图 11.1. 来自样本源通过 syslogd 守护进程的日志数据流

在 systemd 不屈不挠、征服世界的巨轮之后,Linux 日志记录现在也由 journald 处理。我说也是因为 syslogd 并没有消失,你仍然可以在/var/log/中找到大部分传统的日志文件。但你需要意识到,镇上来了一个新的警长,他的(命令行)名字是journalctl。
与 systemd 相关的所有事物一样,转向 journald 的举措引起了争议。没有人会否认 journald 新功能的价值(如图 11.2 所示)。你很快就会亲自看到它如何引入一些强大的过滤和搜索功能。但 journald 将日志数据存储在二进制文件而不是纯文本文件中,这确实是一个问题。
图 11.2. 使用 journalctl 命令行工具消费 journald 日志(包括由 syslogd 生成的日志)。

实际上,由于它们是二进制文件,有时可能很难甚至无法访问你的日志。想想看:当你最需要它的时候(可能在努力从系统崩溃中恢复时),journalctl 可能无法提供服务。另一方面,只要你能挂载它们的驱动器,syslogd 日志总是会对你可用。当你试图找出是什么导致了系统崩溃以及需要做什么来恢复系统时,这可以非常有帮助。好消息是,在可预见的未来,这两个系统将继续共存。
11.1.1. 使用 journald 进行日志记录
这里有一个上周发生在我身上的例子。一位担忧的开发者带来了他的运行 Ubuntu 的笔记本电脑,抱怨它要死了。实际上,引导已经停滞,留下一个黑色屏幕,显示一条类似以下的消息:
/dev/sda1: clean, 127663/900212 files, 709879/3619856 blocks
我得到一个指向/dev/sda1 的引用,这告诉我硬盘是活着的,引导过程已经通过了 GRUB,Linux 至少部分加载了。实际上,那个特定的屏幕信息最终证明是误导性的,因为它代表了引导过程在停滞之前清除的最后成功阶段,这与问题本身无关。
经过一番尝试和错误,包括将笔记本电脑引导到可启动 USB 驱动器并挂载笔记本电脑的驱动器(就像你在第六章中看到的那样),结果发现 Linux 已经完全加载,但未能启动 GUI 桌面。我是如何发现这个宝贝的?在失败的引导过程中(屏幕上显示/dev/sda1:...),我按了 Alt-F1,被带到了虚拟控制台的登录界面。从那里,我有了完整的命令行 shell 访问权限。
小贴士
结果表明,你可以使用 Alt-F2、Alt-F3 等组合键打开多个虚拟控制台。你还可以通过 Alt-<相应的 F 键>在它们之间切换。请记住,你可以使用 Alt-F7 返回到主 shell(通常是你的 GUI 会话)。
为什么桌面 GUI 没有加载?我为什么会首先告诉你这整个事情?这两个问题的答案,你猜对了,可以在笔记本电脑的日志中找到。
为了弄清楚这一切,让我们稍微偏离一下,先了解一下 journalctl,然后再回到那台可怜的笔记本电脑。单独输入journalctl会返回系统上当前最旧的日志条目的一整屏。然而,第一行显示了可用条目的开始和结束日期。接下来,有近九个月的日志条目,需要很多屏幕才能显示:
# journalctl
-- Logs begin at Thu 2016-12-15 08:46:17 EST,
end at Mon 2017-09-04 21:14:54 EDT. --
由于你很可能对最近的活动更感兴趣,-n 20参数将只显示最后 20 条条目:
# journalctl -n 20
时不时地检查你的日志总是一个好主意。我在自己的工作站上运行了那个命令来确认它按预期工作,并发现了一个孤立的 OpenVPN 客户端。愿它的心得到祝福。每 5 秒钟,客户端都会忠诚地尝试连接到一个 VPN 服务器,不幸的是,这个服务器已经不存在了,然后发送新的条目告诉我这一点。为了解决这个问题,我使用 systemctl 首先停止然后禁用了 OpenVPN 服务。
如我之前所写,你越能准确地缩小结果范围,你就能越快地得到你想要的信息。过滤日志的一个有用方法是按优先级显示结果。例如,添加-p emerg会显示仅被归类为紧急情况的日志条目。如果你知道你的系统上出了问题,但无法隔离问题,emerg会是一个好的起点:
# journalctl -p emerg
除了emerg之外,你还可以过滤调试、信息、通知、警告、错误、严重和警报消息(参见下一节中 syslogd 的优先级)。添加-f标志(表示跟随)会显示最近的 10 条条目以及随后创建的任何条目。这允许你实时观察事件的发生:
# journalctl -f
你还可以通过日期和时间过滤日志。而且,我们回到了没有 GUI 的笔记本电脑。如果你对引导过程何时停止有相当的了解,你可以缩小搜索范围,只返回该时间框架的事件。幸运的是,--since和--until参数接受日期和时间。如果你没有指定日期,你将获得满足这些条件的最近期时间。
在我的情况下,我可以在 2 分钟的时间段内指定失败发生。在系统引导期间,2 分钟仍然可以产生大量的日志条目——但与 20 分钟的日志相比,浏览起来会容易得多:
# journalctl --since 15:50:00 --until 15:52:00
如果我在笔记本电脑上运行那个命令,我可能已经节省了一些时间。然而,实际上,我选择了较旧的 syslog 日志文件。让我们看看这些是如何工作的。
11.1.2. 使用 syslogd 进行日志记录
由 syslogd 系统上事件生成的一切日志都被添加到/var/log/syslog 文件中。但是,根据它们的标识特征,它们也可能被发送到同一目录中的一个或多个其他文件中。
使用 syslogd,消息的分配方式由位于/etc/rsyslog.d/目录中的 50-default.conf 文件的内容决定。以下是从 50-default.conf 文件中摘取的示例,展示了如何将标记为 cron 相关的日志消息写入 cron.log 文件。在这种情况下,星号(*)告诉 syslogd 发送任何优先级级别的条目(与单个级别如emerg或err相反):
cron.* /var/log/cron.log
与 syslogd 日志文件一起工作不需要任何特殊工具,如 journalctl。但如果你想精通这项技能,你需要知道每个标准日志文件中保存了什么类型的信息。表 11.1 列出了最常见的 syslogd 日志文件及其用途。(要了解其他信息,请查看它们的 man 页面。man lastlog是一个例子。)
表 11.1. 常用 syslogd 设施
| Filename | 目的 |
|---|---|
| auth.log | 系统身份验证和安全事件 |
| boot.log | 与引导相关事件的记录 |
| dmesg | 与设备驱动程序相关的内核环形缓冲区事件 |
| dpkg.log | 软件包管理事件 |
| kern.log | Linux 内核事件 |
| syslog | 所有日志的集合 |
| wtmp | 跟踪用户会话(通过 who 和 last 命令访问) |
此外,某些应用程序有时会写入它们自己的日志文件。你通常也会看到像/var/log/apache2/或/var/log/mysql/这样的整个目录被创建来接收应用程序数据。日志重定向也可以通过之前看到的任何八个优先级级别来控制。 表 11.2 列出了 syslogd 的优先级级别。
表 11.2. Syslogd 优先级级别
| Level | 描述 |
|---|---|
| debug | 有助于调试 |
| info | 信息 |
| notice | 正常条件 |
| warn | 需要警告的条件 |
| err | 错误条件 |
| crit | 临界条件 |
| 警报 | 立即采取行动 |
| 紧急 | 系统不可用 |
现在,好奇我在那台笔记本电脑上发现了什么?好吧,这章内容并不真正相关,但因为你是如此好的读者,我还是会告诉你。这个信息出现在 syslog 日志文件本身:
xinit: unable to connect to X server: connection refused
X 服务器是处理桌面图形界面的 Linux 系统。如果用户无法连接,可能是因为以下两种情况之一:
-
X 服务器系统已经损坏。
-
存在某种身份验证问题。
这不是前者,因为到那时,我已经成功使用不同的用户账户登录到桌面会话。但是,很难想象笔记本电脑的所有者如何在命令行 shell 中进行身份验证,却仍然被拒绝访问桌面。尽管如此,这似乎值得进一步探索,所以我打开了 auth.log 文件,下面是我看到的内容(这里用用户名代替开发者的名字):
lightdm: pam_succeed_if(lightdm:auth): requirement
"user ingroup nopasswdlogin" not met by user "username"
我知道 lightdm 是 Ubuntu 计算机使用的桌面管理器,pam 是一个处理 Linux 用户身份验证的模块。但是,我必须承认,直到我将它输入我最喜欢的搜索引擎,我才没有完全理解这条消息的重要性。
在那里,我了解到其他遇到这条消息的人将其追溯到用户主目录中保存的.Xauthority 文件的错误所有权。显然,X 服务器只能加载 GUI 会话,如果.Xauthority 文件属于用户。我检查了一下,这个用户的.Xauthority 文件实际上属于 root。修复这个问题只需要运行chown将所有权改回username:
$ ls -al | grep Xauthority
-rw------- 1 root root 56 Sep 4 08:44 .Xauthority
# chown username:username .Xauthority
最初所有权是如何混乱的?谁知道。但事情确实发生了。而智能地使用日志文件帮助我解决了问题。
11.2. 管理日志文件
在数百个系统进程每小时产生数千条日志消息的情况下,未管理的日志系统很快就会填满它可用的所有存储空间。到那时,日志记录将崩溃,以及任何依赖该空间的系统进程。那该怎么办?继续阅读。
11.2.1. journald 方式
Journald 通过自动限制 journald 系统允许使用的最大磁盘空间来处理这个问题。一旦达到限制,较旧的日志将被删除。这个设置由/etc/systemd/journal.conf 文件中的SystemMaxUse=和RuntimeMaxUse=设置控制。
这些设置之间的区别是什么?默认情况下,journald 在/run/log/journal 文件中构建和维护其日志,这是一个在系统关闭时被销毁的易失性文件。然而,你可以指导 journald 在/var/log/journal 目录中维护一个持久的日志文件。你会根据你的系统设置使用这两个 journal.conf 设置之一。将日志转换为持久日志文件只需要创建一个/var/log/journal/目录,并使用systemd-tmpfiles来适当地引导日志流量:
# mkdir -p /var/log/journal
# systemd-tmpfiles --create --prefix /var/log/journal
11.2.2. syslogd 方法
默认情况下,syslogd 在幕后处理日志轮换、压缩和删除,无需您的任何帮助。但您应该知道它是如何操作的,以防您有需要特殊处理的日志。
简单的日志可能需要什么样的特殊处理呢?好吧,假设贵公司必须遵守与萨班斯-奥克斯利法案或 PCI-DSS 等监管或行业标准相关的交易报告规则。如果您的 IT 基础设施记录必须保留更长的时间,那么您肯定想知道如何找到关键文件的方法。
要查看 logrotate 系统的实际操作,列出 /var/log/ 目录的一些内容。例如,auth.log 文件以三种不同的格式出现:
-
auth.log—当前活跃的版本,新的认证消息将被写入其中。
-
auth.log.1—最近被轮换出服务的文件。它以未压缩格式维护,以便在必要时可以快速将其重新投入使用。
-
auth.log.2.gz—一个较旧的集合(如以下列表中所示 .gz 文件扩展名所示),因为它不太可能被需要,所以被压缩以节省空间。
列表 11.1. /var/log/ 目录的内容
$ ls /var/log | grep auth
auth.log *1*
auth.log.1 *2*
auth.log.2.gz *3*
auth.log.3.gz
auth.log.4.gz
-
1 当前活跃的 auth.log 版本
-
2 最近退役的版本
-
3 后续版本将使用 gzip 进行压缩。
当经过七天,下一个轮换日期到来时,auth.log.2.gz 将被重命名为 auth.log.3.gz,auth.log.1 将被压缩并重命名为 auth.log.2.gz,auth.log 将成为 auth.log.1,并将创建一个新的文件并命名为 auth.log。默认的日志轮换周期由 /etc/logrotate.conf 文件控制。本列表中所示值在单周活跃后轮换文件,并在四周后删除旧文件。
列表 11.2. 来自 /etc/logrotate.conf 文件的常见设置
# rotate log files weekly
weekly
# keep 4 weeks worth of backlogs
rotate 4
# create new (empty) log files after rotating old ones
create
# packages drop log rotation information into this directory
include /etc/logrotate.d
/etc/logrotate.d/ 目录还包含用于管理单个服务或应用程序日志轮换的自定义配置文件。列出该目录的内容,你会看到这些配置文件:
$ ls /etc/logrotate.d/
apache2 apt dpkg mysql-server rsyslog samba unattended-upgrade
这是我系统上的 apt 配置文件的样子。
列表 11.3. /etc/logrotate.d/apt 日志轮换配置文件的内容
/var/log/apt/term.log {
rotate 12 *1*
monthly *2*
compress *3*
missingok
notifempty
}
/var/log/apt/history.log {
rotate 12
monthly
compress
missingok
notifempty
}
-
1 文件在被删除之前将被轮换 12 次。
-
2 轮换将每月进行一次。
-
3 轮换的文件将被立即压缩。
小贴士
许多管理员选择将日志条目重定向到专门构建的远程日志服务器,这样数据就能得到应有的专业关注。这可以释放应用服务器执行其即时任务,并将日志数据集中在一个易于访问的中心位置。
11.3. 消耗大文件
你知道你有比阅读数百万行日志条目更好的事情要做。以下几节描述了三种可以为你完成这项工作的文本处理工具——更快、更好。
11.3.1. 使用 grep
根据我的经验,至少,最灵活且直接的工具是 grep 这个老朋友。以下是一个明显的例子,它将在 auth.log 文件中搜索失败的登录尝试的证据。搜索单词 failure 将返回任何包含短语 authentication failure 的行。偶尔检查一下可以帮助你发现通过猜测正确密码来尝试破坏账户的行为:
$ cat /var/log/auth.log | grep 'Authentication failure'
Sep 6 09:22:21 workstation su[21153]: pam_authenticate: Authentication failure
如果你是一位从不犯错的管理员,那么这次搜索可能一无所获。你可以通过使用名为 logger 的程序手动生成日志条目来确保至少有一个结果。试着这样做:
logger "Authentication failure"
您也可以通过登录用户账户并输入错误的密码来预先设置一个真实的错误。
如你所见,grep 为你完成了这项工作,但你从结果中只能看到发生了认证失败。知道涉及的是哪个账户不是很有用吗?你可以通过告诉 grep 包含匹配前后立即的行来扩展 grep 返回的结果。这个例子打印了匹配项及其周围的行。它告诉你,有人使用账户 david(我想那应该是我)尝试使用 su(切换用户)登录到工作室账户,但未能成功:
$ cat /var/log/auth.log | grep -B 1 -A 1 failure
Sep 6 09:22:19 workstation su[21153]: pam_unix(su:auth): authentication
failure; logname= uid=1000 euid=0 tty=/dev/pts/4 ruser=david rhost=
user=studio
Sep 6 09:22:21 workstation su[21153]: pam_authenticate:
Authentication failure
Sep 6 09:22:21 workstation su[21153]: FAILED su for studio by david
顺便说一句,你还可以使用 grep 在多个文件中进行搜索,我在写这本书的时候发现这非常有用。我的问题是每个章节都保存在它自己的目录中作为一个单独的文件,所以搜索每个对单词 daemon 的引用可能会变得很繁琐。因为纯文本章节文件的副本也保存在一个单独的手稿目录中,我可以移动到那个目录并使用 grep -nr 进行搜索。就在 第三章:
$ grep -nr daemon *1*
linux-admin-chapter3.adoc:505: *2*
[...]
-
1 n 参数告诉 grep 包含行信息;r 参数返回递归结果。
-
2 结果中的数字 505 告诉我匹配出现在指定文件的第 505 行。
11.3.2. 使用 awk
grep 可以做很多事情,但它并非万能。让我们以 /var/log/mysql/error.log 文件中的这些条目为例。
列表 11.4. 来自 /var/log/mysql/error.log 文件的一些条目
2017-09-05T04:42:52.293846Z 0 [Note] Shutting down plugin 'sha256_password'
2017-09-05T04:42:52.293852Z 0 [Note]
Shutting down plugin 'mysql_native_password'
2017-09-05T04:42:52.294032Z 0 [Note] Shutting down plugin 'binlog'
2017-09-05T04:42:52.294647Z 0 [Note] /usr/sbin/mysqld: Shutdown complete
2017-09-05T12:24:23.819058Z 0 [Warning] Changed limits:
max_open_files: 1024 (requested 5000)
2017-09-05T12:24:23.819193Z 0 [Warning] Changed limits:
table_open_cache: 431 (requested 2000)
如你所见,条目是根据括号中包含的优先级进行分类的(例如 [Warning])。假设你想知道自日志上次轮换以来有多少条警告信息。也许你经常将数字与一个接受的基线频率进行比较,以知道何时可能有一个需要调查的问题。你可以使用 grep 搜索 [Warning] 出现的每一次,然后将结果通过管道传递给 wc,它将计算输出中的行数、单词数和字符数:
# cat error.log | grep [Warning] | wc
4219 37292 360409
看起来 [Warning] 出现了 4,219 次。有人在这里试图引起你的注意。但尝试使用 awk 工具做同样的事情。wc 将只返回 204 行!这是怎么回事?
# cat error.log | awk '$3 ~/[Warning]/' | wc
204 3213 27846
看起来 grep 使用括号来包含一个字符列表,并在文本流中搜索这些字符中的任何一个。因为 Warning 中有六个独特的字母,所以几乎日志文件中的每一行都是匹配项。你总是可以移除括号并搜索 Warning,但可能会有一些情况,这会导致太多的误报。
只要搜索字符串被包含在向前斜杠中(/[Warning]/),awk 就不会在括号上遇到麻烦。awk 的复杂语法也带来了它自己的独特优势。以之前的例子中的 $3 为例。默认情况下,awk 将一行文本分割成单独的字段,每个字段由一个或多个空格分隔。例如,这个日志条目有七个字段:
2017-09-05T04:42:52.294032Z 0 [Note] Shutting down plugin binlog
grep 将最后的四个单词(Shutting down plugin binlog)解释为四个不同的数据字段,因此很难找到一种方法来有效地搜索这些字段。但第三个字段代表了条目的优先级,因此你可以运行 awk 并使用 $3 来只返回该字段的匹配结果。
11.3.3. 使用 sed
如果你不喜欢使用 wc 来返回流中的行数,你总是可以用 sed 做同样的事情,在 sed 的世界里 = 会打印当前行号(-n 告诉 sed 不要打印文本本身):
# cat error.log | awk '$3 ~/[Warning]/' | sed -n '$='
204
好吧,但你可能永远不会需要拖出一个工业级的流编辑器 sed 来做像计数行这样简单的事情。我之所以提到它,只是为了向你介绍 sed,特别是它在文本流上执行复杂替换的能力。
这里有一个简单的例子,sed 被给定了文本 hello world 并被指示用单词 fishtank 替换(s)第一个出现的单词 world:
$ echo "hello world" | sed "s/world/fishtank/"
hello fishtank
在最后的引号前加上 g 会告诉 sed 替换所有出现的 world,假设有多个的话。
这很好,但不是很实用。sed 在清理文本作为更大过程中的一个部分时表现得很好。也许你正在工作的 Bash 脚本的下一步需要只操作它接收到的输入的一部分。或者,也许你正在准备文本,以便它对最终用户来说更容易阅读。
假设你有一个名为 numbers.txt 的文件,其中包含带有行号的代码。你需要删除行号,同时不修改代码本身,包括代码中的行。以下是一些你可以使用的文本(将它们粘贴到你的电脑上的文件中以便跟随):
列表 11.5. 包含不需要行号的示例代码片段
4 <menuitem action='Item 1'/>
5 <menuitem action='Item 2'/>
6 <menuitem action='Item 3'/>
7 <menuitem action='Exit'/>
8 <separator/><
现在将 numbers.txt 文件传递给 sed:使用插入符字符(^)指向 sed 到每一行的开始。指定任何数字,并删除该位置上任何数字的实例。注意行号已经消失,但Item数字仍然存在:
$ sed "s/^ *[0-9]* //g" numbers.txt
<menuitem action='Item 1'/>
<menuitem action='Item 2'/>
<menuitem action='Item 3'/>
<menuitem action='Exit'/>
<separator/><
或者,你也可以将输出重定向到新文件:
$ sed "s/^ *[0-9]* //" numbers.txt > new-numbers.txt
最后,sed 可以选择性地仅打印目录列表中的子目录(而不是单个文件)。以下是一个未过滤的列表看起来会是什么样子:
$ ls -l
total 28
drwxrwxr-x 2 dbclinton dbclinton 4096 Sep 7 14:15 code
-rw-rw-r-- 1 dbclinton dbclinton 55 Sep 6 11:53 file
-rw-rw-r-- 1 dbclinton dbclinton 76 Sep 6 11:54 file2
-rw-rw-r-- 1 dbclinton dbclinton 4163 Sep 6 00:02 mysql.log
-rw-rw-r-- 1 dbclinton dbclinton 137 Sep 7 13:58 numbers
drwxrwxr-x 2 dbclinton dbclinton 4096 Sep 7 14:15 old-files
使用 sed 和^d来打印(p)仅以d(正如你所知,表示目录)开头的那些行,以下是这样做的方法:
$ ls -l | sed -n '/^d/ p'
drwxrwxr-x 2 dbclinton dbclinton 4096 Sep 7 14:15 code
drwxrwxr-x 2 dbclinton dbclinton 4096 Sep 7 14:15 old-files
11.4. 使用入侵检测进行监控
除了日志之外,还有另一种方法来监控你系统的健康和福利:入侵检测。想法是创建一个系统状态的基线配置文件,即它应该看起来是什么样子,然后定期扫描系统,寻找表明有恶意行为的变化。
处理这个问题的一种方法是通过实现基于网络的入侵检测系统(NIDS),它依赖于软件(如 Snort、Nmap 和 Wireshark)定期“嗅探”网络邻居,寻找不应该存在的设备、开放端口和主机。我们在这里不会花费时间在 NIDS 上(尽管我的“Linux 网络安全”课程在 Pluralsight 上有所涉及)。
更接近家庭的是,你也可以构建一个基于主机的入侵检测系统(HIDS),以持续关注你的服务器。这正是我们将学习如何使用一个名为 Tripwire 的开源软件包来做的。
Tripwire 会扫描你的服务器,并将重要系统文件(如文件大小)的关键属性添加到其自己的数据库中。如果这些文件中的任何一个被编辑或删除,或者如果被监控的目录中添加了新文件,这些属性将会改变。当你后来告诉 Tripwire 检查系统时,它会将当前值与数据库中存储的值进行比较,并报告任何差异。
注意
Tripwire 公司还提供了一种商业版本,为多个安装提供集中式管理、策略合规性、支持和与 Windows 的兼容性(尽管我们可能不需要那个)。它并不便宜,但对于企业规模的部署,它可能是值得的。
要启动 Tripwire,你首先需要安装一个普通的邮件服务器,以便将电子邮件报告发送到任何指定的管理员。然后,你将安装和配置 Tripwire 本身,并编辑和加密其策略和配置文件(分别是 tw.cfg 和 tw.pol)。最后,你将模拟系统更改,以查看电子邮件报告中的外观。
11.4.1. 设置邮件服务器
创建基本的 Linux 邮件服务器比你想象的要容易得多。安装 postfix(对于 Ubuntu,还需要 mailutils)软件包。在安装过程中(至少对于 Ubuntu),当被提示选择邮件配置类型时,选择 Internet Site,并为你的系统邮件名称选择 localhost.localdomain。你还需要确保 /etc/postfix/main.cf 文件中的 inet_interfaces 设置为 localhost。剩下的只是确保在运行中的任何防火墙上都打开 SMTP 的 25 端口,并重新启动 postfix:
# systemctl restart postfix
这并不难,对吧?你现在应该有一个可以发送电子邮件到远程收件人的活动邮件服务器。它还没有准备好处理传入的邮件,但在这个项目中你不需要这个功能。
如何知道你的邮件服务器是否运行正常?
使用 sendmail steve 向本地地址(例如,命名为 Steve)发送邮件。你将面对一个空白行,你需要在上面输入你的消息,然后是硬回车,一个点,再是硬回车。Steve 可以通过在命令行中输入 mail 来收集他的电子邮件。
11.4.2. 安装 Tripwire
安装过程很简单,尽管 Ubuntu 和 CentOS 之间有一些细微的差异。我将在各自的章节中介绍每个差异。
Ubuntu
当 apt 安装 Tripwire 软件包时,你将被提示为两套签名密钥创建新的密码短语。这个过程将暂时使你的密码短语未加密且暴露给任何可能登录到你的系统的人 (图 11.3)。
图 11.3. Debian/Ubuntu 安装过程中 Tripwire 的警告屏幕

在从两套密钥中创建密码短语后,你将被询问是否要重建加密的 Tripwire 配置文件 (图 11.4)。在编辑其源文本文件后,需要重建配置和政策文件。你将在稍后了解如何手动完成,但这次 Tripwire 安装会为你完成。
图 11.4. 在 Tripwire 重建加密配置文件之前的最终屏幕

安装完成后,你将看到一个包含关键 Tripwire 文件位置的屏幕 (图 11.5)。记下这些信息,特别是注意 /usr/share/ 中的文档位置。正如你所期望的,/etc/tripwire/ 目录中也有配置文件。
图 11.5. 安装完成后,Tripwire 显示重要文件的位置。

CentOS
CentOS 的安装缺少 Ubuntu 向导所伴随的复杂、前沿、复古 1980 年代的图形界面。实际上,假设你已经安装了 epel-release 仓库(install epel-release),那么只需安装 Tripwire 软件包并观察文本行飞快地滚动即可。
在 CentOS 中,在主要安装完成后,通过运行名为 tripwire-setup-keyfiles 的程序来创建签名密钥。在那里,你会被提示创建两个密码短语。一旦安装完成(对于 Ubuntu 和 CentOS 都适用),你需要运行tripwire --init来初始化数据库:
[...]
Wrote database file: /var/lib/tripwire/tripwire.twd
The database was successfully generated.
如果你在一个 LXC 容器上设置 Tripwire,请注意你可能会看到很多这样的错误:
The object: "/proc/cpuinfo" is on a different file system...ignoring.
The object: "/proc/diskstats" is on a different file system...ignoring.
The object: "/proc/meminfo" is on a different file system...ignoring.
这是因为一些通常由 Tripwire 监控的系统文件正在与容器的宿主机共享。这意味着在容器中运行的过程可能不会始终拥有完全访问权限。不过,不用担心。除了产生大量难看的错误消息外,这不会对 Tripwire 的工作方式产生影响。
11.4.3. 配置 Tripwire
你通过在/etc/tripwire/目录中保留的两个文件来控制 Tripwire 的行为:tw.cfg 和 tw.pol。问题是,这些文件是加密的,不仅无法编辑,而且除非你有特殊超级英雄技能,否则也无法阅读。这些文件是从两个纯文本文件中的信息构建的:twcfg.txt 和 twpol.txt。
twcfg.txt 文件包含一些基本的环境变量,你都可以根据需要更改。大多数变量将直接正常工作。但更改一些文件位置可以通过隐蔽性增加一层安全性,特别是如果你担心坏人可能会在开始工作之前识别并禁用警报系统。如果你决定移动某些内容,twcfg.txt 是你要编辑的文件。
列表 11.6. twcfg.txt 文件的默认内容
ROOT =/usr/sbin
POLFILE =/etc/tripwire/tw.pol
DBFILE =/var/lib/tripwire/$(HOSTNAME).twd
REPORTFILE =/var/lib/tripwire/report/$(HOSTNAME)-$(DATE).twr *1*
SITEKEYFILE =/etc/tripwire/site.key
LOCALKEYFILE =/etc/tripwire/$(HOSTNAME)-local.key
EDITOR =/bin/vi *2*
LATEPROMPTING =false
LOOSEDIRECTORYCHECKING =false
MAILNOVIOLATIONS =true
EMAILREPORTLEVEL =3 *3*
REPORTLEVEL =3
MAILMETHOD =SENDMAIL
SYSLOGREPORTING =false
MAILPROGRAM =/usr/sbin/sendmail -oi -t
-
1 注意报告文件名如何反映了服务器的主机名,以便于识别。
-
2 如果你更习惯使用 Nano 文本编辑器,你可以将其设置为默认文本编辑器。
-
3 你可能会发现将详细程度设置改为 1 会使报告更容易阅读。
你需要添加到这个文件中的一个是你希望报告发送到的电子邮件地址(或地址)。你可以通过添加一个指向你地址的GLOBALEMAIL行来实现:
GLOBALEMAIL =info@bootstrap-it.com
twpol.txt 文件设置了 Tripwire 将用于分类和扫描文件系统的策略。该文件提供了一个工作通用的策略,但你几乎肯定需要至少进行一些定制以适应你特定的服务器配置。你可以随着时间的推移逐渐添加这些定制,因为你会了解你在报告中看到的假阳性类型,以及 Tripwire 可能遗漏的事情。
你可能会很快对系统产生的假阳性有感觉。可能的原因包括正在积极工作的应用程序的进程 ID (PID) 文件和配置文件。一旦你运行了几次 Tripwire 并开始识别假阳性,就查看 twpol.txt 文件中引用受影响文件的行(例如,bashrc 文件的行可能看起来像 /etc/bashrc -> $(SEC_CONFIG) ;)并使用 # 字符将其注释掉(意味着,#/etc/bashrc -> $(SEC_CONFIG) ;)。
浏览你安装在自己服务器上的 twpol.txt 文件,并注意策略是通过具有“不变目录”等名称的规则定义的。每个规则都被分配了一个严重级别(如列表中所示的 SIG_MED,表示中等重要性)。不变 标识意味着你不期望此类对象更改权限或所有权,因此如果发生此类事件,你应该收到警告。
列表 11.7. 来自 twpol.txt 文件的示例 Tripwire 策略规则
# Commonly accessed directories that should remain static with regards
# to owner and group.
(
rulename = "Invariant Directories",
severity = $(SIG_MED)
)
{
/ -> $(SEC_INVARIANT) (recurse = 0) ;
/home -> $(SEC_INVARIANT) (recurse = 0) ;
/etc -> $(SEC_INVARIANT) (recurse = 0) ;
}
显然,你可以自由编辑构成 Tripwire 策略的十几个规则的内容和值以适应你的需求。该文件有很好的文档说明,所以花上 10 或 15 分钟阅读它应该会使你可以用它做什么变得很清楚。
在编辑纯文本文件后,假设你仍在同一目录下,使用 twadmin --create-cfgfile 和 twadmin --create-polfile 更新加密版本:
# twadmin --create-cfgfile --site-keyfile site.key twcfg.txt
Please enter your site passphrase:
Wrote configuration file: /etc/tripwire/tw.cfg
#
# twadmin --create-polfile twpol.txt
Please enter your site passphrase:
Wrote policy file: /etc/tripwire/tw.pol
由于源文件是纯文本格式,你应该在它们的设置成功集成到加密版本后立即删除它们:
$ cd /etc/tripwire
# rm twcfg.txt
# rm twpol.txt
如果在未来某个时候你需要更新你的配置,你可以通过运行 twadmin --print-cfgfile 或 twadmin --print-polfile 来恢复原始值。这些命令会给你一个文件,你可以在其中进行任何必要的编辑:
# twadmin --print-cfgfile > twcfg.txt
# twadmin --print-polfile > twpol.txt
是时候对 Tripwire 进行测试运行了。在命令行中,tripwire 接受许多带有 -m 前缀的参数,其中 m 代表模块,所以 -m c 就会加载检查模块。运行检查会在屏幕上打印报告,该报告(可能)主要包含关于文件和目录不存在的数十个文件系统错误信息:
# tripwire -m c
[...]
176\. File system error.
Filename: /proc/pci
No such file or directory
------------------------------
*** End of report ***
Integrity check complete.
现在你可以根据之前扫描的结果更新 Tripwire 数据库:
# tripwire -m u
Please enter your local passphrase:
Wrote database file: /var/lib/tripwire/localhost.localdomain.twd
有可能 Tripwire 会抱怨它无法打开报告文件。如果发生这种情况,请在 /var/lib/tripwire/report/ 目录中最新的文件上运行 --update -r。你将在文本编辑器中看到报告。退出,更新将继续(如果你在 vi 编辑器中,:q! 应该可以解决问题):
tripwire -m u -r \ /var/lib/tripwire/report/\
localhost.localdomain-20170907-102302.twr *1*
- 1 你不需要包含包括日期/时间戳在内的完整文件名,只要包含足够的信息以保持唯一性即可。
11.4.4. 生成测试 Tripwire 报告
让我们制造一些麻烦,看看 Tripwire 是否会注意到。向系统中添加新用户会在各个地方留下指纹。至少,/etc/ 目录中的 passwd、shadow 和 group 文件将被更新。给你的朋友 Max 创建一个账户和密码:
# useradd max
# passwd max
Changing password for user max.
New password:
Retype new password:
passwd: all authentication tokens updated successfully.
现在运行 tripwire,指定使用 1 级详细程度发送电子邮件报告:
tripwire --check --email-report --email-report-level 1
如果一切顺利,你应该会收到一封包含如下文本的电子邮件:
Modified: "/etc"
Modified: "/etc/group"
Modified: "/etc/group-"
Modified: "/etc/gshadow"
Modified: "/etc/gshadow-"
Modified: "/etc/passwd-"
Modified: "/etc/shadow-"
Modified: "/etc/subgid"
Modified: "/etc/subgid-"
Modified: "/etc/subuid"
Modified: "/etc/subuid-"
Modified: "/etc/tripwire"
Modified: "/etc/tripwire/tw.pol"
Modified: "/etc/tripwire/tw.cfg"
Modified: "/etc/passwd"
Modified: "/etc/shadow"
还剩下什么?一旦你完成了策略的微调,你可以添加 tripwire --check 和 tripwire -m u 命令到 cron 中以自动化此过程。祝您狩猎愉快!
摘要
-
Journald 日志是基于二进制日志文件构建的,可以使用 journalctl 进行精细解析,以针对特定的匹配项。
-
日志文件增长迅速,必须通过考虑业务和监管需求的轮换计划来控制。
-
grep、awk 和 sed 文本过滤和格式化工具可以用于管理大量数据:grep 通过字符串匹配;awk 通过将字符串分解成字段;sed 通过字符串替换。
-
Tripwire 可以用于持续的服务器监控,以便在检测到可疑行为时提醒管理员。
关键术语
-
Journald 将其日志数据存储在二进制文件中,这允许更灵活的检索,但需要运行中的主机系统。
-
在一个 syslogd 系统上,所有日志事件都写入 /var/log/syslog 文件。
-
日志轮转 对于 syslogd 日志文件是通过 /etc/logrotate.conf 文件以及 /etc/logrotate.d/ 目录中的单个文件来控制的。
-
一个 文本流过滤器(如 grep)在文本中搜索匹配的字符串,并且通常返回包含匹配的整个行。
-
一个 基于网络的入侵检测系统(NIDS)监控私有网络,以查找入侵的证据。
-
一个 基于主机的入侵检测系统(HIDS)监控服务器,以查找系统文件被恶意篡改的证据。
-
一个 邮件服务器 允许应用程序或命令行级别发送和接收电子邮件。
安全最佳实践
-
定期扫描 auth.log 文件,查找重复尝试访问用户账户的证据。
-
配置扫描软件,如入侵检测系统,在发生可能损害的事件时向管理员推送警报。
-
创建加密 Tripwire 配置和政策文件后,应立即删除原始的纯文本文件。
命令行审查
-
Alt-F
从非 GUI 终端打开一个虚拟控制台。 -
journalctl -n 20显示日志中的 20 个最新条目。 -
journalctl --since 15:50:00 --until 15:52:00仅显示 since 和 until 时间之间的事件。 -
systemd-tmpfiles --create --prefix /var/log/journal指示 systemd 创建并维护一个持久的日志,而不是每次启动时被销毁的文件。 -
cat /var/log/auth.log | grep -B 1 -A 1 failure显示匹配的行以及其前后立即的行。 -
cat /var/log/mysql/error.log | awk '$3 ~/[Warning]/' | wc在 MySQL 错误日志中搜索被分类为警告的事件。 -
sed "s/^[0-9]//g" numbers.txt从文件的每一行开头删除数字。 -
tripwire --init初始化 Tripwire 安装的数据库。 -
twadmin --create-cfgfile --site-keyfile site.key twcfg.txt为 Tripwire 生成一个新的加密 tw.cfg 文件。
测试自己
1
/dev/log 伪设备用于
- 存储 journald 的临时日志文件
- 存储 journald 的持久日志文件
- 收集 syslogd 的日志事件数据
- 存储由 syslogd 收集的日志事件数据
2
以下哪个命令将显示日志文件中的五条最新日志条目?
journalctl -l 5journalctl -n 5journalctl -f 5journalctl --since 53
以下哪个指令只会将内核相关的紧急消息发送到现有的 kern.log 文件?
/etc/rsyslog.d/50-default.conf文件中的kern.emerg -/var/log/kern/etc/rsyslog.d/50-default.conf文件中的kern.* -/var/lib/kern.log/etc/rsyslog.d/30-default.conf文件中的*.emerg -/var/log/kern.log/etc/rsyslog.d/50-default.conf文件中的kern.emerg -/var/log/kern.log4
用于控制/var/log/中文件日志轮转策略的配置文件是哪个?
- /etc/logrotate.conf
- /etc/systemd/journal.conf
- /etc/logrotate.d
- /etc/rsyslog.conf
5
哪些参数会告诉 grep 在显示匹配行的同时包括周围的文本行?
cat /var/log/auth.log | grep --B 1 --A 1 failurecat /var/log/auth.log | grep --since 1 --until 1 failurecat /var/log/auth.log | grep -B 1 -A 1 failurecat /var/log/auth.log | grep -b 1 -a 1 failure6
以下哪个 sed 命令将删除文本行的行号?
sed "s/^ [0-9] //g"sed -n '/^d/ p'sed -n '/^d/ [0-9] p'sed "s/^ [0-9] //"7
以下哪个命令将为 Tripwire 数据库准备操作?
tripwire-setup-keyfilestripwire --inittwadmin --create-polfile twpol.txttripwire -m c8
哪个命令包含适当的语法来加密并更新 Tripwire 配置文件?
wadmin --create-polfile --site-keyfile site.key twcfg.txtwadmin --create-cfgfile --site-keyfile site.key twcfg.txtwadmin --create-cfgfile --local-keyfile local.key twcfg.txtwadmin --create-cfgfile --site-keyfile site.key twpol.txt
答案键
1.
c
2.
b
3.
d
4.
a
5.
c
6.
a
7.
b
8.
b
第十二章. 在私有网络上共享数据
本章涵盖
-
使用网络文件系统(NFS)共享文档
-
微调对 NFS 共享的受限访问
-
使用 /etc/fstab 自动化远程文件共享
-
保护并加密 NFS 共享
-
为与 Windows 客户端共享文件配置 Samba
-
使用符号链接和硬链接组织系统资源
如果你已经阅读了第八章,那么你已经熟悉了如何使用 Nextcloud 在互联网等不安全网络上共享文件。但对于受信任的本地网络,有更简单的方法进行协作,这些方法与 Linux 文件系统本身更加紧密地集成。
我并不是建议你只能在本地做这件事。完全加密和确保基于文件系统的共享工具的安全性,以便在互联网上安全使用是可能的,但正确地做到这一点并不容易。而且,这些工具的特定优势最好在更接近家的环境中发挥。
在本章中,你将学习如何将服务器文件系统中精心定义的部分暴露出来,以便远程受信任的客户端可以协作处理文件和文档。你不需要像 Nextcloud 那样创建一个单独的文档存储,因为你的客户端将能够访问它们在本地位置的文档。这在许多常见场景中可能很有用:
-
你希望工作人员能够登录到建筑内任何物理工作站,并立即访问他们自己的家目录中的文件。
-
你希望将特定的数据集合提供给适当团队的成员,无论他们可能在何处。
-
你希望为一些远程工作人员提供完整的文档读写访问权限,但为其他人提供只读访问权限。
稍后我们将探讨设置 Samba 的方法,这是与 Windows 客户端共享基于 Linux 文档的首选工具。但我们的大部分注意力将集中在使用 NFS 允许 Linux 系统之间集成协作。
12.1. 通过网络文件系统(NFS)共享文件
完成事情的时间。个人如何通过网络共享对文档的完全访问权限?如图 12.1 所示,NFS 通过允许客户端将远程服务器上托管的特定目录挂载为本地分区来实现。一旦挂载,这些目录的内容将在客户端系统上可见,无论是在命令行级别还是在桌面 GUI 中。
图 12.1. 挂载的文件共享将像远程客户端上的本地资源一样出现。

在第六章中,你学习了如何挂载外围媒体驱动器以允许从文件系统访问。这里是一个提醒:
# mkdir /media/mountdir/
# mount /dev/sdb1 /media/mountdir/
你将在这里使用mount命令,但这次是将远程 NFS 服务器上的目录挂载为本地分区。不过,在我们到达那里之前,还有一些事情要做。以下是如何一步一步进行的:
-
在服务器上安装 NFS。
-
通过 /etc/exports 文件定义客户端对服务器资源的访问。
-
更新服务器上的 NFS。
-
在客户端安装 NFS。
-
挂载 NFS 共享。
-
配置 NFS 共享在启动时挂载。
-
打开你正在运行的任何防火墙(如果需要)。
12.1.1. 设置 NFS 服务器
让我向你展示如何在你的服务器上共享家目录,以便多个用户可以远程访问内容。对于 CentOS 服务器,你想要安装的包是 nfs-utils。在 Ubuntu 上,那将是 nfs-kernel-server。
注意
在 LXC 容器上安装 NFS 服务器软件并欺骗它认为它有足够的内核访问权限来正常运行可能是可能的,但我怀疑这不会值得你花费时间。如果你想虚拟化这个练习,请坚持使用 VirtualBox。
无论哪种方式,你将要工作的配置文件叫做 exports,它位于 /etc/ 目录下。至少在 Ubuntu 上,该文件包含了一些有用的示例指令,每个指令都通过 # 注释字符禁用。对于我们的简单示例(并且假设你的客户端计算机使用的 IP 地址是 192.168.1.11),这是文件中唯一的活动行:
/home 192.168.1.11(rw,sync)
让我们分解一下:
-
/home告诉 NFS 你想要在服务器上公开 /home 目录及其所有子目录。只要你不无谓地公开敏感的系统或个人数据,你可以自由地公开你喜欢的任何目录。 -
192.168.1.11是你想要允许进入的 NFS 客户端的 IP 地址。 -
rw为该客户端分配了在暴露目录中的文件上的读写权限。 -
sync通过在回复远程请求之前将更改写入磁盘来维护一个稳定的环境。
默认的 NFS 值包括 ro(只读,意味着阻止写操作)和 root_squash(远程客户端用户不允许以 root 身份在服务器上执行操作,无论他们在自己的系统上有什么状态)。这两个设置都为服务器及其文件提供了保护。如果你试图开放一些资源作为某种知识库,那么默认设置将是最合适的。
如果你想要覆盖 root_squash 默认设置并允许远程用户执行 root 操作,你可以添加 no_root_squash 值。尽管可能是一个严重的安全漏洞,但在你需要客户端用户对系统文件执行管理任务时,no_root_squash 有时可能是必要的。下面是这样的样子:
/home 192.168.1.11(rw,sync,no_root_squash)
根据为贵公司所有用户提供单一基于网络的 home 目录层次结构的用例场景,你可能希望为不仅仅是单个客户端开放访问权限。接下来的示例将允许来自本地网络任何地方的人挂载并使用服务器的/home/目录。它假设你信任所有访问 192.168.1.0 网络的人。然而,如果这个网络可以通过你为商务访客提供的 WiFi 服务访问,那么这可能不是一个好主意:
/home 192.168.1.0/255.255.255.0(rw,sync)
如果你对于255.255.255.0可能的作用以及所有这些网络结构是如何工作的感到困惑,第 14.1 节应该能让你明白。
注意
再次强调,在 Linux 中拼写很重要——标点符号也同样重要。请注意,192.168.1.11 (rw,sync)表示来自 192.168.1.11 的客户端将获得读写权限。但是,如果在这个指令中添加一个空格,使其变为192.168.1.11 (rw,sync),那么世界上任何地方的人都将获得rw权限,而来自 192.168.1.11 IP 地址的客户端将默认只有只读权限!
当你完成编辑 exports 文件后,你需要运行exportfs来强制 NFS 采用你的新设置。你可能会看到一个通知告诉你默认禁用了子树检查。这是对默认行为的更改,因为对于大多数现代用例,这通常不值得麻烦:
# exportfs -ra *1*
exportfs: /etc/exports [2]: Neither 'subtree_check' or 'no_subtree_check'
specified for export "192.168.1.0/255.255.255.0:/home".
Assuming default behaviour ('no_subtree_check').
NOTE: this default has changed since nfs-utils version 1.0.x
- 1 r 标志告诉 exportfs 同步文件系统,而 a 标志将操作应用于所有目录。
如果你感兴趣,当启用时,子树检查用于确保文件使用与基于服务器的文件系统和导出的树所遵循的政策保持一致。
你可以使用exportfs查看当前向客户端公开的所有 NFS 文件系统:
# exportfs
/home 192.168.1.0/255.255.255.0
为了便于从客户端测试共享,在服务器上的家目录中创建一个新文件,并添加一些文本。如果你在 CentOS 上运行 NFS 服务器,别忘了打开防火墙(默认完全启用)并启动 NFS(默认停止)。以下是这样做的外观:
# firewall-cmd --add-service=nfs
# firewall-cmd --reload
# systemctl start nfs-server
你现在可以开始在一台客户端计算机上设置 NFS 了。关于这一点,你将在下一节中详细了解。
12.1.2. 设置客户端
从客户端来看,事情变得快速且简单。在 CentOS 上安装与服务器相同的 nfs-utils 包,在 Ubuntu 上安装 nfs-common。从这里开始只需再走两步。首先,创建一个新目录,你将在其中挂载远程文件系统。然后使用 NFS 服务器的 IP 地址和服务器/etc/export 配置文件中公开的文件系统地址挂载它:
# mkdir -p /nfs/home/ *1*
# mount 192.168.1.23:/home /nfs/home/
- 1 -p 标志告诉 Linux 创建路径中尚不存在任何目录(如/nfs/)。
到目前为止,你应该能够打开和编辑共享文件。导航到您创建的挂载点,你应该会找到一个子目录,即属于您服务器主用户的目录(以我的情况为例是 ubuntu)。进入该目录,尝试打开、编辑和保存文件,然后返回服务器查看您是否可以看到新的、更新的版本:
$ cd /nfs/home/
$ ls
ubuntu
成功?恭喜!虽然和你自己对话有点诡异。我保证不会告诉任何人。
没有成功?以下是一些可以考虑的故障排除技巧:
-
确保您的客户端和服务器之间有基本的网络连接,并且实际上可以通信。从服务器 ping 客户端的 IP 地址,从客户端 ping 服务器的 IP 地址(例如,
ping 192.168.1.23),并确认您得到了适当的响应。记得你在 第三章 中是如何做的吗? -
确保没有防火墙阻止流量。默认情况下,NFS 需要打开 TCP 端口 2049 才能工作。如果你使用 ufw,你不需要记住端口号:
ufw allow nfs就可以完成这项工作(有关可用服务别名的完整列表,请参阅 /etc/services 文件)。 -
确保 NFS 在服务器上运行正常。使用
exportfs检查您的配置,以验证 NFS 是否使用您最新的版本exportfs -a。 -
确保服务器的 IP 地址没有更改。如果服务器从 DHCP 服务器动态获取 IP 地址,这很容易发生。理想情况下,您的 NFS 服务器应使用静态地址(有关详细信息,请参阅 第十四章)。在任何情况下,您都可以通过更新 /etc/fstab 中的指令(见下一节)临时解决这个问题。
如果您想从客户端删除 NFS 挂载,请使用 umount:
# umount /nfs/home/
12.1.3. 在启动时挂载 NFS 共享
虽然你现在可以访问那些远程文件,但我恐怕你的快乐不会持续到下一次系统关闭。你可以每次启动时都运行mount命令,但这可能不会是一个持久的习惯。相反,你可以通过编辑 /etc/fstab 文件来告诉 Linux 每次启动时自动挂载共享:
列表 12.1. 典型 /etc/fstab 文件的内容
# /etc/fstab: static file system information.
#
# Use 'blkid' to print a universally unique identifier *1*
# for a device; this may be used with UUID= as a more robust way to name
# devices that works even if disks are added and removed. See fstab(5).
#
# <file system> <mount point> <type> <options> <dump> <pass>
# / was on /dev/sda2 during installation
UUID=130fe070-9c44-4236-8227-6b1515baf270 /
ext4 errors=remount-ro 0 1
# /boot was on /dev/sda1 during installation
UUID=e06b8003-255f-44b4-ab0f-291367d2928b /boot
ext4 defaults 0 2
# /utility was on /dev/sda5 during installation
UUID=9cae6b93-963d-4995-8d33-5826da106426 /utility
ext4 defaults 0 2
- 1 寻找尚未列出设备的 UUID 标识符的说明
如你所见,一个活跃的 fstab 行将包括包含有关每个列出设备信息的六个字段(有关表格 12.1 的详细信息,请参阅 table 12.1)。
注意
一些 fstab 选项包括 exec(或 noexec)来控制是否可以从文件系统执行二进制文件,ro 来限制只读访问,rw 来允许读写访问,以及 defaults 来调用默认设置(rw、suid、dev、exec、auto、nouser 和 async)。
表 12.1. fstab 文件中的字段
| 字段 | 目的 |
|---|---|
| File system | 通过启动时的指定(例如/dev/sda1,有时会发生变化)或更可靠的 UUID 来识别设备。 |
| Mount point | 识别文件系统中设备当前挂载的位置。 |
| Type | 文件系统类型。 |
| Options | 分配给设备的挂载选项。 |
| Dump | 告诉(过时的)Dump 程序是否(1)或不是(0)备份设备。 |
| Pass | 在启动时告诉 fsck 程序首先检查哪个文件系统。根分区应该是第一个(1)。 |
fstab 文件最初在 OS 安装期间填充了附加硬件设备的引用。作为管理员,您有权添加自己的设备,以便它们在启动时也能挂载。请记住设备的引用方式。为您的 NFS 共享在客户端的 fstab 文件中添加新行可能看起来像这样:
192.168.1.23:/home /nfs/home nfs
重新启动您的客户端。这次,不要手动挂载 NFS 共享,而是立即前往它应该挂载的目录(/nfs/home/),以确保您可以看到基于服务器的文件。如果它在那里,那么您就知道 fstab 已经完成了它的任务。单独运行mount命令;除了其他文件系统外,您应该看到分配给它的新 NFS 共享及其选项。
列表 12.2. 客户端挂载的文件系统部分输出
# mount
sysfs on /sys type sysfs (rw,nosuid,nodev,noexec,relatime)
/dev/sda2 on / type
ext4 (rw,relatime,errors=remount-ro,data=ordered) *1*
/dev/sda1 on /boot type ext4 (rw,relatime,data=ordered)
192.168.1.23:/home on /nfs/home type nfs4 *2*
(rw,relatime,vers=4.0,rsize=262144,wsize=262144,namlen=255,hard,
proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=192.168.1.11,
local_lock=none,addr=192.168.1.23)
tmpfs on /run/user/1000 type tmpfs (rw,nosuid,nodev,relatime,
size=204828k,mode=700,uid=1000,gid=1000)
-
1 根分区
-
2 随后是 NFS 共享及其启动选项
12.1.4. NFS 安全
再快速看一下本章标题(“通过私有网络共享数据”)并注意单词private。正如我之前提到的,如果 NFS 任其自然,它不会加密在主机之间来回传输的数据。您应该只在私有空间中使用它。
但是私有空间有多安全呢?当我们说一个网络私有时,意味着它不能通过正常的访问规则从外部访问。但是如果您还不知道,让我尽可能温和地告诉您:黑客并不害羞地使用异常访问规则。或者,更准确地说,他们很可能会生成和部署他们自己的自制规则。
如果您对 NFS 服务器共享的安全性取决于您对办公室里的人的信任程度,那么它可能需要一些改进。这并不是说您永远不应该信任您认识并与之共事的人,而是您不能总是确定他们的工作站和账户是否已被外部人员破坏。您也不能确定是否有外部人员正在监听您的网络流量。
您的一般方法应该是遵循最小权限原则:永远不要打开比绝对必要更宽的资源,永远不要给用户(或客户端)提供他们完成工作所绝对需要的更多访问权限。例如,如果您的客户端不需要编辑文件,那么在/etc/exports 文件中绝对可以通过ro(只读)选项。或者,如果您的客户端只需要访问目录层次结构中的某些文件,那么阻止其他所有文件。
防火墙规则
一条重要的防御线将是您的防火墙。如果您的受信任私有网络中有任何不需要访问 NFS 服务器的个人工作站,请将其锁定:
# ufw deny to 192.168.1.10
在 CentOS 上,那将是
# firewall-cmd --add-rich-rule="rule family='ipv4'
source address='192.168.1.10' reject"
# firewall-cmd --reload
谈到将它们锁定在外,在 NFS 服务器上更改防火墙规则之后,在 NFS 共享完全被阻止之前,您可能需要重新启动客户端机器。
有时候你可能希望你的服务器不仅仅托管 NFS 文件。可能还需要存储与开发团队或管理员团队无关的数据和配置文件,这些团队在自己的机器上工作。以下是如何阻止一个 IP 地址为 192.168.1.10 的开发者访问服务器上的 NFS,同时保留对其他服务器资源的完全访问权限的方法:
# ufw deny from 192.168.1.10 to any port 2049
为了测试这一点,考虑在你的 NFS 服务器上除了 NFS 之外还安装 Apache 网络服务器。如果你的 NFS 共享在客户端无法加载,但你仍然可以访问服务器上的网络应用程序,那么你就知道你已经找到了你的最佳平衡点:
# ls /nfs/home/
# curl 192.168.1.3 *1*
Welcome to my web server on my NFS machine
- 1 curl 获取可访问网页的内容。*
加密
和往常一样,限制对您资源的访问并不意味着您在服务器和客户端之间传输的数据是安全的——至少当这些数据未加密时是这样的。如果您只是在您的本地私有网络内传输数据,并且您确信这些数据确实是本地且私有的,那么您依赖现有的系统是可以被原谅的。但是,任何将在不安全网络上传输的数据都需要额外的保护。
这里有一份简短的列表,列出了你可以用来处理问题的方法(除了闭上眼睛并希望只有好事发生给你,这在严格意义上来说,更像是一种应对机制):
-
你可以在 VPN 之上运行一个 NFS 共享,这与你在第十章中使用 VPN 加密数据传输的方式非常相似。
-
IPSec 协议通过防止 IP 欺骗和包篡改,在 IP 网络级别确保数据安全。它通过使用会话密钥加密 IP 数据包中包含的所有数据。您可以将 IPSec 配置为隧道,从广义上讲,其工作方式与 OpenVPN 隧道相似。
-
NFS 可以配置为在底层的 SSH 会话上运行,这又是一种隧道形式。在遇到不寻常的使用场景时,拥有多个选项总是好的。请注意,这是一个非标准选项。
-
假设您已经在您的基础设施中运行了一个 Kerberos 身份验证服务器,并且您至少使用了 NFS v.4,将
sec=krb5p行添加到您的 NFS 配置中,将把管理问题的任务交给 Kerberos 的强大之手。 -
最后,NFS 的一个替代方案是 SSH 文件系统(SSHFS)。SSHFS 类似地将远程文件系统挂载为本地卷,但通过使用用户空间文件系统(FUSE)软件接口通过 SFTP 协议工作。
12.2. 使用 Samba 与 Windows 用户共享文件
如果您的办公网络包括需要从他们的 Windows PC 访问基于 Linux 文件的用户,那么 Samba 是一个简单而复杂得令人难以置信的解决方案。我的意思是什么?设置一个基本连接,就像我将要演示的那样,不会花费太多精力。但是,将身份验证与 Windows Active Directory 域集成或与 Linux 服务器上的 SELinux 作斗争可能会给您带来相当多的麻烦。多年来出版了一整套 Samba 配置书籍,这并非偶然。
对于我们的目的,您可以在 Ubuntu 上仅安装 samba 和 smbclient 软件包。对于 CentOS,运行 yum 对 samba* 进行操作以获取整个 Samba 相关工具集合要简单得多(记住:* 字符被解释为返回包含前面文本的所有结果)。以下是其余过程将如何展开:
-
在 Linux 服务器上创建一个 Samba 用户账户。
-
指定共享目录。
-
通过编辑 smb.conf 文件来定义共享。
-
测试配置。
-
从 Windows 客户端连接。
您需要使用一个名为 smbpasswd 的程序来设置一个 Samba 用户,作为客户端将用于登录的账户。但由于 Samba 权限将添加到现有账户中,您应该首先创建一个新的 Linux 账户。我将称它为 sambauser,但您可以选择任何您喜欢的名称:
# adduser sambauser
# smbpasswd -a sambauser
接下来,您可以创建一个共享将基于的目录。我将遵循我之前为我的 NFS 共享使用的相同模式。为了便于稍后测试,我将在新目录中创建一个文件。因为多个客户端最终可能会在这个目录中处理文件,您可以通过使用 chmod 将目录权限设置为 777(所有用户的读取、写入和执行权限)来避免潜在权限问题:
# mkdir -p /samba/sharehome
# touch /samba/sharehome/myfile
# chmod 777 /samba/sharehome
这样,Samba 环境就全部搭建好了。现在您可以将配置添加到 /etc/samba/ 目录下的 smb.conf 文件中。您应该浏览配置文件,以了解可以进行的多少定制以及事情可能会变得多么复杂:
# nano /etc/samba/smb.conf
然而,就您目前谦逊的愿望而言,您只需要添加一个定义您的共享的单个部分。我将冒险将其称为 sharehome。您绝对必须包含的两个条目是 path,它指向您计划用于共享文档的目录,以及,假设您希望客户端能够创建和编辑文件,writable 的值为 yes。完成这些操作后,保存文件并关闭文本编辑器。
列表 12.3. 来自 /etc/samba/smb.conf 文件的文件共享配置部分
[sharehome]
path = /samba/sharehome
writable = yes
现在请使用 systemctl 启动并启用 Samba 守护进程。Ubuntu 将 Samba 识别为 smbd,但在 CentOS 上,它将是 smb(不带 d):
# systemctl start smbd *1*
# systemctl enable smbd
- 1 注意,smbd 是 Ubuntu 上 systemctl 识别的守护进程名称;在 CentOS 上将是 smb。
12.2.1. 测试您的 Samba 配置
在深入挖掘之前测试你的配置总是一个好主意,所以运行 testparm 将会显示你添加的章节是否可以被 Samba 服务正确读取。这个输出表明一切正常:
# testparm *1*
Load smb config files from /etc/samba/smb.conf
rlimit_max: increasing rlimit_max (1024) to minimum Windows limit (16384)
WARNING: The "syslog" option is deprecated
Processing section "[printers]"
Processing section "[print$]"
Processing section "[sharehome]" *2*
Loaded services file OK.
Server role: ROLE_STANDALONE
Press Enter to see a dump of your service definitions
-
1 testparm 测试并显示你的 Samba 配置。
-
2 确认你的 sharehome 部分被识别
在邀请你的 Windows 朋友加入之前,还有一个测试:你可以使用 smbclient 程序从本地机器登录到你的 Samba 共享。首先,你需要切换用户(su)到之前与 Samba 关联的 sambauser 账户。然后,你可以运行 smbclient 对共享的主机地址和名称(//localhost/sharehome)进行操作:
$ su sambauser
Password:
# smbclient //localhost/sharehome
Enter sambauser's password:
Domain=[WORKGROUP] OS=[Windows 6.1] Server=[Samba 4.3.11-Ubuntu]
smb: \> *1*
- 1 注意特殊的 Samba 命令提示符告诉你你现在处于 Samba 壳层会话中。
在这种情况下,正如你从命令行提示符中可以看到的,你已经进入了 Samba 壳层。运行 ls 来显示内容,你应该能看到你创建的 myfile 文件:
smb: \> ls
. D 0 Thu Sep 14 20:54:32 2017
.. D 0 Thu Sep 14 20:36:52 2017
myfile N 0 Thu Sep 14 20:54:32 2017
953363332 blocks of size 1024\. 732660884 blocks available
顺便说一句,正如你从第九章章节 9 中已经知道的,训练不足和/或缺乏耐心的管理员有时会一遇到冲突就倾向于关闭 SELinux。这是件坏事,而且绝对不是像我们这样受过高度训练且极具耐心的 Linux 用户会考虑的事情。大多数时候。
如果你在这个演示中运行 CentOS 并且发现自己无法访问共享中的文件,那可能是因为 SELinux 在作祟:
smb: \> ls
NT_STATUS_ACCESS_DENIED listing \*
千万不要做我即将告诉你的事情。即使是在非生产服务器上的简单演示也不要做。永远不要。但如果你想禁用 SELinux 一分钟或两分钟以避免不得不阅读 smb.conf 文件文档来找出如何正确修复问题,我确信你会记得运行 setenforce 0 将会禁用 SELinux。但我从未告诉你这一点。事实上,这个段落从未发生过。
如果你服务器上有防火墙保护,你需要打开一些端口以便 Samba 可以进出。但你需要的具体端口将至少部分取决于你在 Samba 配置中包含的内容。Kerberos、LDAP、DNS 和 NetBIOS 是与 Samba 一起运行的常见服务。
在服务器上使用 netstat 扫描 Samba(使用 smbd 搜索词)使用的端口将给出基本系统上的关键端口。这个例子返回了 139 和 445,但可能有更多:
# netstat -tulpn | egrep smbd *1*
tcp 0 0 0.0.0.0:139 0.0.0.0:* LISTEN 2423/smbd
tcp 0 0 0.0.0.0:445 0.0.0.0:* LISTEN 2423/smbd
tcp6 0 0 :::139 :::* LISTEN 2423/smbd
tcp6 0 0 :::445 :::* LISTEN 2423/smbd
- 1 如果你机器上没有 netstat,请安装 net-tools 软件包。
12.2.2. 从 Windows 访问 Samba 服务器
从 Windows 客户端机器创建一个桌面快捷方式(Windows 也有这些功能!),并将其位置指向你的 Linux 服务器上的 Samba 共享。假设 Linux 服务器的 IP 地址是 192.168.1.23,你给 Samba 共享起的名字是 sharehome,你将输入的地址将看起来像这样:
\\192.168.1.23\sharehome
为了连接,Windows 用户必须使用你之前创建的 Linux Samba 用户名和密码进行身份验证,除非你选择将 Samba 身份验证与 Windows Active Directory 域集成。这可能是长期来看更高效且更安全的方法,而不是依赖于简单的密码。我会向你展示如何做到这一点,但首先让我快速看一下书封面以确认我的怀疑。是的,这是一本 Linux 书。抱歉,没有 Windows 配置指南。
12.3. 使用符号链接与自身共享文件
承认,这个章节与本章的联系可能有点牵强,但用户有时会想要与自己共享文件。创建符号链接(也称为symlinks)可以实现这一点。为了明确,我并不是在谈论让你的文件同时对你和你邪恶的李生兄弟可用,而是在你可能在的任何地方都能更方便地访问它们。
让我来举例说明。在本章的早期部分,你使用了一个 NFS 共享来让你的用户能够访问中央/home/目录。目标是让任何用户坐在办公室的任何工作站前,通过你创建的/nfs/home/目录,立即开始使用自己的文档。
但如果并非所有用户都对 Linux 文件系统层次结构如此熟悉,更不用说命令行了怎么办?也许有些用户不知道他们的文件在文件系统中的具体位置。你可以自由地创建一个指向挂载的共享目录的符号链接,该目录将在用户的 GUI 桌面上显示。
注意
GUI Linux 系统上的用户账户在他们的/home/username/目录中有一组子目录(如文档和图片),他们可以在其中方便地组织他们的文档。其中一个子目录被称为桌面,任何保存到其中的文件都将作为图标出现在用户登录时首先看到的 GUI 屏幕上。请记住,这些子目录直到用户首次登录新账户时才会创建。
你可以通过使用带有 -s 参数的 ln 命令,想要链接的文件系统对象,以及你希望放置链接的位置来创建一个符号链接:
# ln -s /nfs/home/ /home/username/Desktop/
这就是全部内容。事实上,它如此简单,你可能想知道为什么它值得单独成节。这与 -s 参数有关。这里的 s 代表符号,这意味着也必须有一种不同类型的链接。确实,默认情况下,ln 命令会创建一个硬链接。
但区别在哪里呢?一个符号链接指向一个独立的文件系统对象。读取、执行或编辑符号链接将会读取、执行或编辑链接的对象。但如果移动或删除原始的符号链接,由于它仅仅是一个指向独立对象的指针,它将会断开。
与之相反,硬链接是其目标的精确副本,以至于两个文件将共享一个单一的 inode。正如你从第一章中回忆的那样,inode 是描述对象属性的元数据:特别是它在文件系统中的位置。
让我们看看这一切是如何付诸实践的。创建两个文件,并为每个文件添加不同的文本。现在为其中一个文件创建一个硬链接,为另一个文件创建一个符号链接:
$ nano file1
$ nano file2
$ ln file1 file1-hard
$ ln -s file2 file2-sym
使用i参数运行ls来显示 inode ID。注意,硬链接文件共享相同的 inode ID 号码。注意文件 2-sym 文件指向文件 2:
$ ls -il
9569544 -rw-rw-r-- 2 ubuntu ubuntu 4 Sep 14 15:40 file1
9569544 -rw-rw-r-- 2 ubuntu ubuntu 4 Sep 14 15:40 file1-hard
9569545 -rw-rw-r-- 1 ubuntu ubuntu 5 Sep 14 15:40 file2
9569543 lrwxrwxrwx 1 ubuntu ubuntu 5 Sep 14 15:40 file2-sym -> file2
如果你删除、重命名或移动文件 2,它的符号链接将会断裂,并且根据你的 shell 颜色方案,将以愤怒的红色背景显示。另一方面,硬链接将能够承受对原始文件的所有更改。试着亲自试试:重命名文件 1 和文件 2,看看它们的链接会发生什么变化:
$ mv file1 newname1
$ mv file2 newname2
当你再次运行ls -il时,你可能已经注意到,即使在用mv重命名文件 1 之后,file1-hard 仍然存在,更重要的是,它仍然与 newname1 共享相同的 inode。对其中一个文件的任何编辑都会反映在另一个文件上。遗憾的是,file2-sym 已经被遗弃。
为什么要费这么大的劲?为什么不直接复制呢?想想你之前看到的情况,你创建了一个指向用户桌面文件系统位置的链接。或者,关于你在第八章中看到的/sbin/init 和/lib/systemd/systemd 文件之间的连接,或者第三章中/etc/apache2/sites-available/和/etc/apache2/sites-enabled/的内容?这些都是使用链接来增强系统功能的事例。
硬链接在 Linux 中应用得更为广泛,但它们的目的并不总是那么明显。在一种常见的使用场景中,尽管文件存储在文件系统的不同位置,但它们会被链接在一起。这可以使得备份重要的但分布广泛的配置文件变得更加容易,而无需定期创建新的副本。并且由于硬链接不占用任何空间,你不必担心额外的磁盘使用。
摘要
-
NFS 共享在远程计算机上公开定义的文件系统资源,允许方便的文档访问和团队之间的协作。
-
文件系统和设备可以通过命令行手动挂载(并使用),或者通过在/etc/fstab 文件中的条目在启动时挂载。
-
你可以通过智能配置策略、防火墙规则以及通过第三方解决方案(如 Kerberos 和 IPSec)提供的加密来确保 NFS 共享的安全。
-
Windows 客户端可以使用 Samba 以及 Samba 生成的账户认证来消费基于 Linux 的文件。
-
符号链接指向文件系统对象,而硬链接创建的对象是原始文件的精确副本,不占用存储空间。
关键术语
-
共享是一组文档或文件系统,与远程客户端共享。
-
可以使用
testparm测试一个Samba 服务器配置。 -
符号链接和硬链接允许 Linux 文件系统对象(文件和目录)在多个位置表示。
安全最佳实践
-
NFS 共享应尽可能具体,包括需要访问的客户端以及每个客户端需要的权限。这符合最小权限原则。
-
在可能的情况下,NFS 共享策略应始终包括
squash_root,以确保远程客户端不会获得对服务器的无限制 root 访问。 -
NFS(和 Samba)共享默认不加密。如果您的客户端将通过不受信任的网络访问它们,那么您应该应用加密。
命令行审查
-
/home 192.168.1.11(rw,sync)(NFS 服务器/etc/exports 文件中的条目)定义了一个远程客户端共享。 -
firewall-cmd --add-service=nfs为 CentOS 防火墙打开客户端访问您的 NFS 共享。 -
192.168.1.23:/home /nfs/home nfs(NFS 客户端/etc/fstab 文件中的典型条目)加载了一个 NFS 共享。 -
smbpasswd -a sambauser为现有 Linux 用户账户添加 Samba 功能(以及一个唯一的密码)。 -
nano /etc/samba/smb.conf控制服务器上的 Samba。 -
smbclient //localhost/sharehome使用 Samba 用户账户登录到本地 Samba 共享。 -
ln -s /nfs/home/ /home/username/Desktop/创建了一个符号链接,允许用户通过点击桌面图标轻松访问 NFS 共享。
测试自己
1
以下哪个指令可以为使用 192.168.1.11 IP 地址的 NFS 客户端提供只读访问?
/home 192.168.1.11(ro,sync)192.168.1.11 /home(ro,sync)/home 192.168.1.11(rw,sync)192.168.1.11 /home(r,sync)2
应编辑哪个客户端配置文件以确保在启动时加载 NFS 共享?
- /etc/nfs
- /etc/exports
- /etc/nfs/exports
- /etc/fstab
3
exportfs -ra命令将完成什么?
- 强制 NFS 重新加载配置文件中的策略
- 列出当前 NFS 文件系统
- 测试 Samba 配置
- 重新加载所有附加设备
4
以下哪个将完全阻止单个远程客户端机器从服务器访问?
ufw deny ALL 192.168.1.10/UDPufw deny ALL 192.168.1.10ufw deny to 192.168.1.10ufw deny to 192.168.1.10/255.255.255.05
以下哪个安全工具在服务器和客户端机器之间传输数据时不会帮助保护数据?
- Kerberos
- firewalld
- SSHFS
- IPSec
6
以下哪个是 Samba 服务器配置文件的正确名称和位置?
- /etc/samba/smb.conf
- /etc/smb.cfg
- /etc/samba/smb.conf
- /etc/samba.cfg
7
以下哪个命令将在 Ubuntu 服务器上启动 Samba?
systemctl start sambadsystemctl start smbdsystemctl start smbsystemctl start samba8
以下哪个命令在创建指向配置文件的链接以使常规备份更简单、更可靠方面最有意义?
ln -s /var/backups/important_config_file_backup.cfg /etc/important_config_file.cfgln /var/backups/important_config_file_backup.cfg /etc/important_config_file.cfgln -s /etc/important_config_file.cfg /var/backups/important_config_file_backup.cfgln /etc/important_config_file.cfg /var/backups/important_config_file_backup.cfg
答案键
1.
a
2.
d
3.
a
4.
c
5.
b
6.
c
7.
b
8.
d
第十三章:系统性能问题故障排除
本章涵盖
-
理解和衡量你的系统行为
-
控制应用和客户端对系统资源的需求
-
应对资源短缺的多层次策略
-
有效的持续监控协议策略
“级联混沌”和“即将到来的灾难”是否正是你现在 IT 运营的写照?你的服务器是否运行缓慢且无响应?你的客户是否抱怨应用性能不佳?或者,你是否已经停止吹嘘你那新购置的工作站带来的不可思议体验?
即使情况没有这么糟糕,生活也不会总是顺利。我们总是试图从 IT 投资中获得最大价值,这是定义上的,这意味着有时我们会做得太过分:压力过大的系统有时会崩溃,复杂的软件堆栈元素有时会停止协同工作。
长寿和幸福生活的秘诀是预见麻烦,快速识别症状和原因,并在正确的时间采取正确的修复措施。这对于你作为 IT 管理员的工作同样适用。
我所说的“系统”是指你用来提供服务的硬件和软件环境,无论是应用、数据库、Web 服务器,还是简单独立工作站的可靠使用。在本章中,你将关注系统四个核心元素的健康和福祉:中央处理器(CPU)、内存(RAM,包括物理和虚拟)、存储设备和网络负载管理。你将学习如何发现问题、确定原因,然后修复根本问题,或者在必要时投入更多硬件。
当然,你总是希望避免问题。一种方法是让你的健康系统接受压力测试,看看它如何应对。在本章稍后,我还会简要介绍yes,这是一个将你的基础设施暴露于残酷和非常规折磨的出色工具。
在任何情况下,我将假设你负责的其中一台服务器或工作站出现了异常行为:在最糟糕的时刻崩溃或变慢。你用你最凶狠、最恶毒的表情瞪视它一两分钟,但机器似乎并不在意,也没有响应。让我们一步一步解决这个问题。
13.1. CPU 负载问题
CPU 是电脑的大脑。构成 CPU 的电子电路,无论有多少核心和它们使用的总线有多宽,都只预期做一件事:等待软件程序传入的指令,执行计算,并返回答案。
总体来说,你的 CPU 要么工作,要么不工作。大多数与 CPU 相关的性能问题(如响应时间缓慢或意外关机)都可以追溯到超出它们的物理容量。当你怀疑某个性能问题可能与 CPU 有关时,你需要做的第一件事就是找出你是否对它太过苛刻。
13.1.1. 测量 CPU 负载
CPU 状态的两个指标包括 CPU 负载和 CPU 利用率:
-
CPU 负载是衡量 CPU 作为总容量百分比所执行的工作量(意味着当前活跃和排队进程的数量)的度量。表示随时间推移的系统活动的负载平均数,因为它们提供了一个更准确的系统状态图景,是表示此指标更好的方式。
-
CPU 利用率(或使用率)是衡量 CPU 不空闲时间的度量(描述为总 CPU 容量的比例)。
在单核机器上,负载得分为 1 表示满负荷。如果你的系统有多个 CPU 核心,比如四个,那么满负荷将表示为数字 4。一旦 CPU 利用率得分超过 75%,用户体验可能会开始受到影响(至少有时如此),对于单核来说,这将表示为 0.75,而对于四核系统来说,将表示为 3.0。
获取你的 CPU 负载平均数很容易。运行uptime会返回当前时间、自最近一次系统启动以来经过的时间、当前登录的用户数,以及对我们来说现在最重要的是,过去一分钟、五分钟和十五分钟的负载平均数:
$ uptime
10:08:02 up 82 days, 17:13, 1 user, load average: 0.12, 0.18, 0.27
在一个拥有一个 CPU 的系统上,平均负载为 1.27 意味着 CPU 平均处于满负荷工作状态,另外 27%的进程正在等待轮到它们使用 CPU。相比之下,在一个拥有一个 CPU 的系统上,平均负载为 0.27 意味着 CPU 平均有 73%的时间未被使用。在一个四核系统上,你可能会看到负载平均在 2.1 左右,这将是 50%以上的容量(或大约 52%的时间未被使用)。
要正确理解这些数字,你需要知道你的系统有多少核心。如果这些信息没有打印在机箱标签上,而你又不感兴趣打开机箱亲自查看,你可以查询伪文件 cpuinfo:
$ cat /proc/cpuinfo | grep processor
processor : 0
processor : 1
processor : 2
processor : 3
看起来那个系统有四个核心。因为你在那里,不妨浏览一下那个文件的其余部分,以了解 Linux 是如何描述你的 CPU 的。特别是,尝试理解标志部分,它列出了你的硬件支持的功能。
13.1.2. 管理 CPU 负载
持续低于满负荷(基于你从uptime得到的结果)?你可以只是享受额外的空间。或者,你可能会考虑通过使用未充分利用的计算机提供额外服务(而不是购买额外的服务器)来整合资源,以最大化投资回报率。
持续超负荷?你可能需要切换到具有更多 CPU 核心的更健壮的硬件架构,或者在虚拟环境中,为更多的 VM 或容器提供额外的负载。偶尔,你也可以仔细查看系统上运行的进程,看看是否有可以关闭的,甚至是否有未经你知晓而运行异常的进程。运行top提供了丰富、自动更新的进程信息显示。
图 13.1 是top的典型全屏数据。注意第一行提供了与运行uptime相同的见解。因为你试图解决性能问题,你应该最感兴趣的列是%CPU(当前由给定进程使用的 CPU 容量百分比)以及特别是出现在列表顶部的进程。
图 13.1. top显示的进程数据快照

在这种情况下,你可以看到 MySQL 守护进程正在使用服务器 CPU 的 4.3%,从下一列来看,占其内存的 13%。如果你将那一行向左移动,你会看到进程 ID 是 1367,进程属于 mysql 用户。你可能会得出结论,这个进程占用的资源比可以证明的要多,将不得不牺牲(为了更大的利益,你知道的)。你可以通过按 q 键退出top。
那个top显示给了你结束进程所需的一切。因为 MySQL 是由 systemd 管理的服务,你的首选应该是使用systemctl来温和地结束进程,以免任何应用程序数据处于风险之中:
# systemctl stop mysqld
如果它不是由 systemd 管理的,或者如果出了问题,systemctl未能停止它,那么你可以使用kill或killall来消除你的进程(某些系统要求你作为 psmisc 包的一部分安装killall)。你可以这样将 PID 传递给kill:
# kill 1367
另一方面,killall使用进程名而不是其 ID:
# killall mysqld
要kill还是要killall,这是一个问题。实际上,答案相当明显。kill将基于 PID 关闭单个进程,而killall将杀死尽可能多的特定程序实例。如果有两个或三个独立的 MySQL 实例,可能属于不同的用户,所有这些都将被停止。
注意
在启动killall之前,确保没有运行着类似名称的进程,否则可能会成为附带损害。
你还必须再次使用 systemctl 来确保进程在下次启动时不会重新启动:
# systemctl disable mysqld
使用“良好”来设置优先级
有时候您可能无法终止一个进程,因为它是一个关键任务服务的必要部分。但您可以使用 nice 来限制它获得的 CPU 资源。默认情况下,新进程被赋予 nice 值为 0,但您可以将其更改为介于 -20 和 19 之间的任何数字。数字越高,进程在放弃资源以供其他进程使用时越“友好”。相反,数字越低,进程在尽可能多地获取资源时越“不友好”,无论这可能会给其他人带来多少痛苦和困扰。
假设您想运行一个名为 mybackup.sh 的脚本,该脚本启动一个远程备份操作。问题是这是一个活跃的服务器,它不时需要大量电力来响应重要的客户端请求。如果在备份全速运行时收到请求,性能将无法接受。另一方面,如果从开始到结束备份都进行节流,它可能永远无法完成。
这就是 nice 如何帮助您以让每个人都轻松微笑的方式处理这种情况。在脚本(或任何其他命令名称)前加上 nice 和您为其选择的数值。在这种情况下,跟在破折号(-)后面的 15 告诉 Linux 该脚本将以非常友好的态度运行。这意味着当资源访问发生冲突时,您的脚本会退让,但除此之外,它会接受可用的任何资源:
# nice -15 /var/scripts/mybackup.sh
如果运行您的脚本是一个紧急优先事项,必须尽快完成,您可以在第二个破折号中添加一个负值(-15),如下例所示:
# nice --15 /var/scripts/mybackup.sh
无论哪种方式,如果您想看到这种情况,创建一个脚本,并在它执行时,在第二个终端中运行 top。您应该看到您的进程正在运行,并且其适当的 nice 值应出现在 NI 列中。
对于许多 Linux 程序,您也可以设置默认的 nice 值。至少在 Ubuntu 上,rsync 允许您通过 /etc/default/rsync 中的 RSYNC_NICE 设置显式定义其友好程度。
列表 13.1. 来自 /etc/default/rsync 配置文件的可能的 nice 设置
RSYNC_NICE='10'
您还可以使用 renice 来更改进程的行为,即使它在启动后也是如此。此示例将在必要时限制分配给 PID 2145 的进程可用的资源:
renice 15 -p 2145
top 的技巧
如果您需要,您之前看到的 top 输出的第三行会为您提供许多其他 CPU 指标的时间值(作为百分比)。表 13.1 提供了您在那里看到的混乱缩写的快速概述。
表 13.1. top 显示的与 CPU 相关的指标符号
| 指标 | 含义 |
|---|---|
| us | 运行高优先级(非友好)进程花费的时间 |
| sy | 运行内核进程花费的时间 |
| ni | 运行低优先级(友好)进程花费的时间 |
| id | 空闲时间 |
| wa | 等待 I/O 事件完成花费的时间 |
| hi | 管理硬件中断花费的时间 |
| si | 软件中断管理所花费的时间 |
| st | 虚拟机(主机)从该 VM 中窃取的时间 |
注意,top显示可以通过键盘输入实时自定义。输入h以获取更多信息。
13.1.3. 制造麻烦(模拟 CPU 负载)
想要尝试这些技巧,但你知道吗,一切似乎都在正常运行?为什么不自己模拟危机级别的 CPU 过载呢?
与孩子一样,yes会持续输出(数字)噪音,直到被告知停止。再想想,这根本不像孩子。这个命令会将噪音重定向到可丢弃的/dev/null文件,并且&字符会将进程推入后台,将命令行的控制权交还给你。为了增加压力,可以多次启动该命令:
$ yes > /dev/null &
这样应该能让它们忙起来。当所有这些都在运行时,观察top以了解发生了什么。你也可以尝试运行其他应用程序,看看需要多少才能使它们变慢。完成后,运行killall一次性结束所有的yes会话:
$ killall yes
13.2. 内存问题
尽管信息技术在过去的几十年中取得了巨大的进步,但随机存取存储器(RAM)本身的使用方式仍然和以前一样。计算机通过将操作系统内核和其他软件代码加载到易失性 RAM 模块中来加速核心计算操作。这允许快速访问频繁请求的软件指令。
内存的最大挑战通常是它的限制。我的第一台电脑有 640 KB 的 RAM(那不到 1 MB),这还不够。我不得不让电脑整夜运行,只是为了渲染一个 640 × 480 的 GIF 图像。现在放在我桌子下的工作站有 8 GB,一旦我向我的常规工作量添加三个 VirtualBox 虚拟机,它也开始感到压力。
13.2.1. 评估内存状态
通常,内存不足的系统将无法完成请求的任务或只是变慢。当然,这些问题可以描述各种各样的事情,所以在得出结论之前,你需要得到确认。除了将电子显微镜聚焦在你主板上的内存模块上,运行free是最直接的方法。
free解析/proc/meminfo文件并显示可用的总物理内存(在本例中描述为 7.1 GB)以及它的当前使用方式。shared是 tmpfs 使用的内存,用于维护我们熟悉和喜爱的各种伪文件系统,如/dev/和/sys/。缓冲区和缓存与内核用于块级 I/O 操作的内存相关联(如果你不理解这一切,不必过于担心)。
任何由系统进程使用的内存都被标记为used。available值是对当前可用于启动新应用程序的内存的估计,即使它目前正被磁盘缓存使用,也不需要涉及到交换内存(你将在稍后遇到它)。以下是一个例子:
$ free -h
total used free shared buff/cache available
Mem: 7.1G 2.8G 1.3G 372M 3.0G 3.5G
Swap: 7.2G 540K 7.2G
您会注意到我为free添加了-h参数,它以人类可读的格式显示输出,使用更易于阅读的较大数值单位(GB、MB 和 KB)而不是字节。
这个例子看起来像是一个健康系统,有足够的空间进行扩展。但如果free的值持续接近 0,并且将负载转移到交换内存不能缓解问题,那么您可能需要增加内存。现在,关于那个交换空间...
13.2.2. 评估交换状态
因为,从字节到字节,RAM 模块通常比磁盘存储更昂贵,许多操作系统安装将存储驱动器上的一个文件或分区指定为用作虚拟 RAM 的紧急来源。这样,即使严格来说,您可能没有足够的 RAM 来运行所有进程,过载的系统也不会失败,尽管它会明显变慢。
您可以通过vmstat命令了解系统上交换空间的使用情况。在此命令中添加的30和4参数告诉程序返回四个读数,每个读数之间间隔 30 秒。在实际情况下,您可能希望将测试扩展到更长时间,例如几个小时,以提高结果的准确性。您应该密切关注的两列是si,它衡量从交换空间到系统内存的数据传输,以及so,它报告从系统内存到交换空间的传输。正如承诺的那样,以下是该命令:
$ vmstat 30 4
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu--
r b swpd free buff cache si so bi bo in cs us sy id wa st
0 0 540 1311400 373100 2779572 0 0 35 41 418 186 4 1 94 1 0
0 0 540 1311168 373104 2779540 0 0 0 9 671 881 0 0 99 0 0
0 0 540 1311216 373116 2779508 0 0 0 33 779 1052 1 1 98 0 0
0 0 540 1310564 373116 2779476 0 0 0 2 592 815 0 0 99 0 0
如果您发现交换空间中的数据流动持续不断,您应该考虑增加物理 RAM,除非较慢的性能对您的负载不是问题。
13.3. 存储可用性问题
如果您的应用程序软件堆栈经常将新的文档、数据或日志文件写入存储驱动器,那么您不能忽视这样一个事实:您可用的空间总是有限的。有些事情不可能永远持续下去,最终会停止。当您的驱动器上没有剩余空间时,这些数据写入将停止,您的系统功能也将随之停止。
您如何知道您有多接近极限?很简单。您已经遇到了df。因为设备不使用任何实际的磁盘空间,您可以忽略Use%列中列出的使用 0%最大空间的条目。您已经知道它们是伪文件系统。但您应该关注其他条目,尤其是根分区(/)。在这种情况下,根仍有 686 GB(近 80%)的空闲空间,所以目前没有必要担心。但显然这是您需要定期检查的事情:
$ df -h
Filesystem Size Used Avail Use% Mounted on
udev 3.5G 0 3.5G 0% /dev
tmpfs 726M 1.5M 724M 1% /run
/dev/sda2 910G 178G 686G 21% / *1*
tmpfs 3.6G 71M 3.5G 2% /dev/shm
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 3.6G 0 3.6G 0% /sys/fs/cgroup *2*
/dev/sda1 511M 3.4M 508M 1% /boot/efi
tmpfs 726M 72K 726M 1% /run/user/1000
-
1 根分区条目
-
2 一个伪文件系统;注意使用了 0 字节。
这是跟踪使用情况的一种简单方法。困难的方式发生在您发现无法将文件保存到磁盘上,或者您登录到系统并收到一条消息,表明您的会话是只读的。
满载或失败?
当然,并非所有的只读失败都是由于存储驱动器已满。这也可能意味着物理设备正在故障。在这种情况下,你将需要立即开始将任何重要数据保存到外围驱动器或在线存储账户中,以免驱动器完全故障。你如何判断区别?如果你的系统足够健康可以运行df,那么它应该会帮助,但如果不确定,安全总是比后悔好。
13.3.1. Inode 限制
物理空间并不是 Linux 数据存储的唯一限制。你还记得我们在第十二章中的讨论,我们提到所有 Linux 文件系统对象都是通过包含在唯一 inode 中的元数据来识别和管理的。结果证明,系统上允许的 inode 数量有一个硬限制,即使还有足够的物理空间,也可能耗尽 inode。
注意
当文件系统创建时,可用的 inode 数量是永久设置的。考虑到 inode 本身会占用空间,在创建文件系统(例如使用 mkfs.ext4 这样的工具)时,目标是找到一个平衡点,既能允许最多的潜在文件,又能浪费最少的磁盘空间。
这里是我在之前相同的系统上运行 df 命令时的样子,但这次使用了 -i 参数来显示 inode 数据:
$ df -i
Filesystem Inodes IUsed IFree IUse% Mounted on
udev 914806 546 914260 1% /dev
tmpfs 928143 797 927346 1% /run
/dev/sda2 60547072 701615 59845457 2% / *1*
tmpfs 928143 155 927988 1% /dev/shm
tmpfs 928143 5 928138 1% /run/lock
tmpfs 928143 16 928127 1% /sys/fs/cgroup
/dev/sda1 0 0 0 - /boot/efi
tmpfs 928143 31 928112 1% /run/user/1000
- 1 这是根分区,其 inode 状态最为重要。
虽然这个系统上有许多空闲 inode,但并没有立即的警报原因,但如果你发现自己接近了 10%或 20%的容量上限,你肯定会想采取行动。我的一个服务器曾经遇到过 inode 用尽的情况,我花了几分钟才明白发生了什么。让我告诉你所有关于这件事的细节。
当我尝试使用apt安装一些新软件时,麻烦的第一迹象出现了。安装失败,显示的错误信息中包含了“设备空间不足”的字样。这显然是胡说八道,因为我知道还有几 GB 的空闲空间。经过一些网络搜索,我意识到需要检查 inode 级别:果然,我恰好在那个特定的分区上耗尽了 inode。
从逻辑上讲,下一步是搜索包含最多文件的目录。毕竟,inode 的集中可能会出现在文件频繁访问的地方。以下是如何调整find以获取这些信息的方法:
$ cd /
# find . -xdev -type f | cut -d "/" -f 2 | sort | uniq -c | sort -n
表 13.2 解释了这一切的含义。
表 13.2. find命令语法
| 语法 | 功能 |
|---|---|
| . | 在当前目录及其以下开始搜索。 |
| -xdev | 保持在一个文件系统内。 |
| -type f | 搜索文件类型的对象。 |
| cut -d "/" | 删除由分隔符(/字符,在这种情况下)标识的文本。 |
| -f 2 | 选择找到的第二个字段。 |
| sort | 对输出行进行排序,并发送到标准输出(stout)。 |
| uniq -c | 计算 sort 发送的行数。 |
| sort -n | 以数字顺序显示输出。 |
这是一个很棒的命令。问题是它失败了,因为find临时将原始数据保存到磁盘上。但是因为我 inode 用完了,所以目前保存任何东西都是不可能的。太棒了。现在怎么办?通过找到一些不必要的文件并将它们删除来腾出一些空间。完成这些后,这是find显示给我的:
# find . -xdev -type f | cut -d "/" -f 2 | sort | uniq -c | sort -n
5 root
48 tmp
127 sbin
128 bin
377 boot
989 etc
2888 home
6578 var
15285 lib
372893 usr *1*
- 1 /usr/目录显然是“单个目录树中文件最多”的奖项获得者。
警告
因为它可能需要搜索成千上万的文件和目录,find可能需要一些时间来运行。
最大的文件数量都在/usr/目录的某个地方。但具体在哪里?没问题,降级一层并再次运行find:
$ cd usr
# find . -xdev -type f | cut -d "/" -f 2 | sort | uniq -c | sort -n
6 include
160 sbin
617 bin
7211 lib
16518 share
348381 src *1*
- 1 包含最多文件的目录
这次,/usr/src/显然是罪魁祸首。/usr/src/里到底发生了什么?结果是那里保存了内核头文件,包括之前安装在你机器上的旧内核版本留下的那些。如果你深入目录树,你会发现确实有很多文件。
13.3.2. 解决方案
为了腾出空间,你可能需要手动删除一些旧的目录。然后,假设你使用的是 Ubuntu,让dpkg通过--configure安全地移除不需要的其他内容:
# dpkg --configure -a
要安全地移除所有旧的内核头文件,运行autoremove,一切应该都会恢复到最佳工作状态:
# apt-get autoremove
在 CentOS 上,安装 yum-utils 包,然后运行package-cleanup。添加--count=2将删除除了最新的两个内核之外的所有内核:
# package-cleanup --oldkernels --count=2
小贴士
总是保留至少一个较旧的内核以备不时之需是个好主意,以防最新的内核出现问题。
为了解决存储限制,我们可以做些什么?最明显的事情是添加更多的存储。但你也可以定期审计你的系统,看看什么可以被删除或转移到替代的、通常更便宜的存储解决方案。亚马逊的 Glacier 是一个存放不常访问的大型数据存储的绝佳地点。
你还应该努力减少你产生的数据量。正如你在第十一章中看到的,做到这一点的一种方法就是确保你的日志定期轮换和退役。
13.4. 网络负载问题
当单词“网络”和“问题”在同一个句子中出现时,大多数人可能首先想到的是失败的连接。但这就是我们将在下一章讨论的内容。然而,在这里我们将讨论如何处理一个活跃且健康的过载连接。
您应该在什么时候怀疑负载超出了您的处理能力?在常规桌面工作站上,您可能会看到下载时间比预期长或完全失败;在面向公众的服务器上,您的客户可能会抱怨服务缓慢。这些症状可能是多个原因的结果,因此您将需要进行一些进一步的调查,然后尝试可能的解决方案。这就是您接下来将要学习的。
13.4.1. 测量带宽
实际上存在数十种 Linux 工具可以提供对网络使用的洞察。我将向您展示的两个工具特别有用,可以快速识别使用最多带宽的资源,从而让您能够智能地解决根本问题。
与 top 类似,iftop(通过通过安装 iftop 软件包通过常规渠道获得)显示通过网络接口传输的最贪婪的网络活动的自更新记录:指定 iftop -i eth0。
如您从 图 13.2 中所见,iftop 列出了我的计算机(工作站)和远程主机之间的网络连接,以及以字节或千字节为单位的带宽消耗。连接按入站/出站对列出。显然,高消耗的连接需要检查,并在必要时删除。显示的底部行跟踪累积和峰值使用(入站和出站),以及平均使用率。
图 13.2. 网络连接及其带宽使用的典型 iftop 显示

您应该仔细扫描和分析 iftop 识别的远程主机。定期监控可能会揭示您网络资源上的意外消耗,甚至 iftop 捕获的以前未识别的恶意软件“回家”的调用。对亚马逊或谷歌的调用可能并不引人注目,但奇怪和隐秘的 URL 应该激发您再次审视。
iftop 对于从远程主机角度缩小网络使用范围非常有用(例如,对于故障排除网络浏览器流量很有用)。但有时您需要通过它们的 PID 管理本地进程;iftop 对此无能为力。另一方面,NetHogs(通过 nethogs 存储库软件包安装),将会有所帮助。通过指定网络接口从命令行启动 NetHogs。请注意,这次您不需要包含 -i 标志:
# nethogs eth0
图 13.3 展示了我工作站的典型 NetHogs 显示,包括我的 Linux Slack 客户端(嘿,我还需要跟上我的同事)和 Chrome 浏览器的 PID。如果有什么行为不当,我可以通过其 PID 跟踪并控制它。
图 13.3. 通过 NetHogs 看到的我的工作站上相对安静的一天

13.4.2. 解决方案
一旦你缩小了导致你麻烦的系统进程,你就必须找出如何处理它。如果不是必需的,或者它是一个恶意软件进程,你可以像之前使用 systemctl、kill 或 killall 那样永久关闭它。但,更常见的情况是,这不可能:你服务器上运行的大多数进程可能都有一定的原因。
你还可以考虑升级你的网络连接。这可能涉及联系你的 ISP 讨论提高提供的服务级别。对于本地现场网络,你也可能考虑改善你的网络硬件。如果你的布线容量目前限制在 100 MB/s(CAT 5),你可以升级到 1000 MB/s(CAT 6)。记住,仅仅更换电缆是不够的。你所有的路由器、交换机以及连接到你的设备的接口也必须具备这种容量,才能充分感受到好处。除了以太网布线外,转向光纤可以提供更高的性能,但价格要高得多。
13.4.3. 使用 tc 整形网络流量
对于负载问题,有一种更微妙和复杂的解决方案,即 流量整形。而不是完全关闭特定的服务,例如,你可以限制进程所获得的带宽上限。从某种意义上说,你希望以与之前看到 nice 管理进程相同的方式管理带宽。这可以使有限的资源在你的系统中均匀或战略性地共享。
例如,你可以限制允许网络客户端的带宽,以确保其他进程(如 DNS 更新或备份)不会因带宽不足而受影响。作为一个快速而简单的说明,我将向你展示如何使用流量控制 (tc) 工具对面向互联网的网络接口施加基本节流。tc 通常在 Linux 上默认安装。
首先,ping 一个远程站点,并注意你得到的响应时间。这个例子平均大约是 37 毫秒:
$ ping duckduckgo.com
PING duckduckgo.com (107.21.1.61) 56(84) bytes of data.
64 bytes from duckduckgo.com (107.21.1.61):
icmp_seq=1 ttl=43 time=35.6 ms *1*
64 bytes from duckduckgo.com (107.21.1.61): icmp_seq=2 ttl=43 time=37.3 ms
64 bytes from duckduckgo.com (107.21.1.61): icmp_seq=3 ttl=43 time=37.7 ms
- 1 时间值表示单程操作所需的时间。
为了确保没有与你的网络接口(例如,eth0)关联的现有规则,列出所有当前规则:
$ tc -s qdisc ls dev eth0 *1*
qdisc noqueue 0: root refcnt 2
Sent 0 bytes 0 pkt (dropped 0, overlimits 0 requeues 0)
backlog 0b 0p requeues 0
- 1 qdisc 代表排队规则,网络数据包必须通过的队列。
现在添加一个规则,延迟所有流量 100 毫秒。这将给每个网络传输增加 100 毫秒的额外时间,允许其他进程获得更多的资源份额。它也会减慢你的网络活动,所以你必须对结果感到满意。别担心,我很快就会向你展示如何撤销所有这些操作:
# tc qdisc add dev eth0 root netem delay 100ms
再次列出 tc 规则,并注意新的规则将延迟流量 100 毫秒:
$ tc -s qdisc ls dev eth0
qdisc netem 8001:
root refcnt 2 limit 1000 delay 100.0ms *1*
Sent 514 bytes 3 pkt (dropped 0, overlimits 0 requeues 0)
backlog 102b 1p requeues 0
- 1 qdisc 现在有一个单独的规则,延迟流量 100 毫秒。
测试你的规则。再次运行 ping,并观察 time 的值。它们现在应该比之前高约 100 毫秒:
$ ping duckduckgo.com
PING duckduckgo.com (107.21.1.61) 56(84) bytes of data.
64 bytes from duckduckgo.com (107.21.1.61): icmp_seq=1 ttl=43 time=153 ms
64 bytes from duckduckgo.com (107.21.1.61): icmp_seq=2 ttl=43 time=141 ms
64 bytes from duckduckgo.com (107.21.1.61): icmp_seq=3 ttl=43 time=137 ms
如您预期的那样完成了吗?太好了。您可能想要将系统恢复到原始状态,因此运行此命令以删除规则,然后再次测试一切以确认您已恢复正常:
# tc qdisc del dev eth0 root
tc是一个复杂的软件,一本完整的指南可能需要填满一本书。但我认为您已经看到了足够的功能。与其他所有内容一样,目标是将工具交到您的手中,以便您可以深入研究并将它们应用于您特定的难题。
13.5. 监控工具
就像大多数与管理工作相关的任务一样,记住运行本章中讨论的各个工具,嗯,这并不经常发生。一些任务可以编写脚本,由 cron 管理,并在预设阈值达到时发出警报。这些都很不错。但有时,至少偶尔查看实际进行中的数据也是不可或缺的。以下是一些帮助您设置有效监控系统的一些建议。
13.5.1. 聚合监控数据
nmon 是一个多目标系统监控和基准测试工具。它为您提供了一个可定制的单视图,可以查看所有系统组件的状态的有限细节。您可以通过安装 nmon 软件包并在命令行中运行nmon来启动它。您将看到的第一个屏幕将类似于图 13.4,并显示用于切换各种视图的按键。
图 13.4。nmon 简介屏幕,包括基本使用说明

关键在于切换,因为例如,按一次 c 键会显示 CPU 信息视图,而再次按它会使 CPU 消失。这种设置使得在屏幕上添加多个视图成为可能。按 c、m 和 n 键可以同时启用 CPU、内存和网络视图,正如您在图 13.5 中看到的那样。这是一种构建单个窗口视图的绝佳方式,通过它可以捕捉到系统频繁更新的概述。顺便说一句,按 q 键将退出屏幕。
图 13.5。在单个 nmon 屏幕中监控多个服务

nmon 只能覆盖单个服务器。如果您负责整个服务器群,您将需要一些更健壮的工具,如 Nagios 或 collectd 来跟踪多个服务器的健康和活动,以及 Munin 来跟踪趋势并提供有用的分析。
13.5.2. 可视化您的数据
太好了。但这仍然只能在您实际查看时起作用。如果您出去喝个长时间的咖啡,可能会完全错过全面崩溃。幸运的是,nmon 允许您将随时间收集的数据记录到文件中。此示例将收集到的数据每 30 秒保存一次,持续一小时(120 * 30 秒),保存到当前工作目录的文件中:
# nmon -f -s 30 -c 120
如果您的系统主机名为 ubuntu,则默认文件名将包含 ubuntu、日期和时间戳,以及.nmon 扩展名:
ubuntu_170918_1620.nmon
只要您的系统安装了 Web 服务器,您就可以使用一个名为 nmonchart 的工具将数据文件转换为更用户友好的.html 格式。您需要从 nmon SourceForge 网站(sourceforge.net/projects/nmon/files)获取该工具。最简单的方法是右键单击 nmonchartx.tar 文件(x 将是版本号)并复制 URL。从您的服务器的命令行使用 wget 下载 tar 文件,然后以通常的方式解压缩存档:
$ wget http://sourceforge.net/projects/nmon/files/nmonchart31.tar
$ tar xvf nmonchart31.tar
这是您将如何调用 nmonchart 以转换.nmon 文件并将其保存到您的 Web 根目录的方法:
# ./nmonchart ubuntu_170918_1620.nmon /var/www/html/datafile.html
如果您遇到一个关于缺少 ksh 解释器的错误,请随意安装 ksh 包。ksh 是一个命令行解释器,是 Bash 的替代品:
-bash: ./nmonchart: /usr/bin/ksh: bad interpreter: No such file or directory
当所有这些完成后,您可以将浏览器指向您的服务器的 IP 地址,后面跟您为 nmonchart 指定的文件名:
10.0.3.57/datafile.html
这应该会给你一个看起来像图 13.6 的页面。
图 13.6. 点击网络按钮的 nmap 生成的网页

您可以将所有这些脚本化,包括生成顺序.html 文件名的代码。这使得跟踪新事件和归档事件都变得方便。
摘要
-
uptime工具可以提供关于平均 CPU 负载随时间变化的洞察,异常结果可能会提示您查找和管理高使用进程。 -
nice命令可以密切控制进程之间为有限系统资源竞争的方式。 -
实际内存和交换内存可以使用
free和vmstat等工具进行监控。 -
存储限制由可用的物理磁盘空间和可用的 inode 定义。
-
iftop 和 NetHogs 是众多用于访问网络负载数据的 Linux 工具中的两个,而 tc 可以用来控制使用。
-
定期监控是可能的,但使用命令行工具进行脚本化或推送监控数据到基于浏览器的可视化表示可能更有效。
关键术语
-
CPU 负载 是 CPU 正在执行的工作量。
-
CPU 利用率 是当前正在使用的 CPU 容量的比例。
-
nice允许您在有限资源冲突的情况下控制进程的优先级。 -
RAM 内存 存储操作系统内核和其他重要软件,以提供对关键数据的快速访问。
-
交换内存 是在硬盘上指定为虚拟 RAM 的空间,以防您用完真实的东西。
-
inode 是包含位置和其他信息的元数据,并与所有 Linux 文件系统对象相关联。
-
网络流量限制 限制了允许一个进程使用的带宽量,以优先考虑一个或多个优先级更高的进程。
安全最佳实践
-
定期扫描正在运行的过程,寻找未经认证用户或您的基系统启动的恶意软件。
-
运行 iftop 可以显示与你的系统有实时连接的远程网络主机。如果你不认识一个连接,它可能是未经授权且危险的。
命令行审查
-
uptime返回过去 1 分钟、5 分钟和 15 分钟的 CPU 负载平均值。 -
cat /proc/cpuinfo | grep processor返回系统 CPU 处理器的数量。 -
top显示正在运行的 Linux 进程的实时统计信息。 -
killall yes关闭所有正在运行的yes命令的实例。 -
nice --15 /var/scripts/mybackup.sh提高了 mybackup.sh 脚本的系统资源优先级。 -
free -h显示系统总 RAM 和可用 RAM。 -
df -i显示每个文件系统的可用和总 inode。 -
find . -xdev -type f | cut -d "/" -f 2 | sort | uniq -c | sort -n按父目录计数并显示文件数量。 -
apt-get autoremove删除旧的未使用的内核头文件。 -
nethogs eth0使用 eth0 接口显示与网络连接相关的进程和数据传输。 -
tc qdisc add dev eth0 root netem delay 100ms通过 eth0 接口将所有网络传输速度减慢 100 毫秒。 -
nmon -f -s 30 -c 120将一系列nmon扫描的数据记录到一个文件中。
测试自己
1
以下哪个 CPU 负载分数可能会在双核系统上导致系统减慢?
- 1.7
- 2.1
- .17
- 3.0
2
uptime显示的负载分数代表过去 1 分钟、5 分钟和 15 分钟的平均值。
- 1 分钟、10 分钟和 25 分钟
- 1 小时、5 小时和 24 小时
- 1 分钟、5 分钟和 15 分钟
- 10 秒、60 秒和 300 秒
3
以下哪个文件将包含有关系统处理器数量的信息?
- /proc/uptime
- /sys/cpuconf
- /proc/cpuinfo
- /etc/proc/envinfo
4
以下哪个命令将关闭 PID 为 4398 的 mysqld 进程?
kill mysqldkillall mysqldkillall 4398kill mysqld:43985
你想要降低 mybackup.sh 脚本所赋予的优先级。以下哪个选项会将其优先级降低最多?
nice -10 /var/scripts/mybackup.shnice -0 /var/scripts/mybackup.shnice --15 /var/scripts/mybackup.shnice -15 /var/scripts/mybackup.sh6
以下哪组症状最可能表明一个严重且持续的记忆问题?
- 慢速应用程序性能和交换进出的数据传输高等级
- 慢速应用程序性能和交换进出的数据传输低水平
- 慢速应用程序性能和高的 CPU 平均负载分数
- 慢速应用程序性能和高 inode 可用性
7
以下哪个选项将提供与网络带宽数据一起的进程 ID?
nmon -i eth0nethogs eth0nethogs -i eth0iftop eth08
你应该如何通过 eth0 接口减慢网络流量 100 毫秒?
tc qdisc add dev eth0 root netem delay 1000mstc qdisc add eth0 root netem delay 100mstc qdisc add dev eth0 root netem delay 100mstc qdisc add dev eth0 root netem -h delay 100ms
答案键
1.
a
2.
c
3.
c
4.
b
5.
d
6.
a
7.
b
8.
c
第十四章. 故障排除网络问题
本章涵盖
-
使用 TCP/IP 网络管理网络问题
-
网络和网络接口的故障排除
-
管理 DHCP 连接
-
配置 DNS 进行地址转换
-
故障排除入站网络连接
当我还是个孩子的时候,为 PC 获取新软件意味着要么自己编写,要么开车去商店购买一个包含一个或多个 5.25 英寸软盘的程序盒。通常情况下,远程协作需要点阵打印机和邮局。流媒体视频?别逗我了。我不记得我的第一台 PC 是否有调制解调器。如果有,我肯定从未使用过它。
现在,网络连接对计算的重要性就像键盘和浓咖啡一样不可或缺。随着语音界面的发展,比如亚马逊的 Alexa,可能不太明智地大量投资于键盘制造。(不过咖啡的前景仍然看好。)总之,没有快速可靠的网络访问,你和你要支持的用户将会非常无助。
为了提供快速可靠的网络访问,你需要知道如何使用网络工具和协议在你的网络接口和外部世界之间建立连接。你还需要知道如何识别和连接网络适配器到你的计算机上,这样工具和协议才能有所作为。我们很快就会谈到这一点。
但如果你要面对可能困扰你的网络通信的令人烦恼和不可预测的中断,你首先需要扎实的网络协议基础知识,通常被称为传输控制协议(TCP)和互联网协议(IP),简称 TCP/IP。从技术上讲,TCP/IP 根本不是 Linux 主题,因为无论运行什么操作系统,所有联网设备都普遍使用这些协议。由于本章的工作不涉及 TCP/IP,我们将从这里开始。如果你已经熟悉这些材料,可以自由跳过这一部分。
14.1. 理解 TCP/IP 寻址
网络最基本单元是谦逊的互联网协议(IP)地址,至少必须分配给每个连接的设备。每个地址在整个网络中必须是唯一的;否则,消息路由将陷入混乱。
几十年来,标准的地址格式遵循 IPv4 协议:每个地址由四个 8 位八位字节组成,总共 32 位。(如果你不懂如何用二进制计数,不用担心。)每个八位字节必须是一个介于 0 到 255 之间的数字。这里有一个典型的(虚构的)例子:
154.39.230.205
从 IPv4 池中可以提取的最大理论地址数量超过 40 亿(256⁴)。曾经,这看起来很多。但随着互联网远远超出任何人的预期,IPv4 池中显然不会有足够的唯一地址来满足无数寻求连接的设备。
四十亿个可能的地址听起来是一个很大的数字,但考虑到目前有超过 10 亿部正在使用的 Android 智能手机;这还加上数以百万计的服务器、路由器、PC 和笔记本电脑,更不用说苹果手机了。有很大可能性,您的汽车、冰箱和家用安全摄像头也有它们自己的网络可访问地址,所以显然必须做出一些妥协。
提出了两种解决互联网地址系统即将崩溃(以及我们所知生活的终结)的方案:IPv6(一个全新的地址协议)和网络地址转换(NAT)。IPv6 提供了更大的地址池,但由于它还没有得到广泛部署,我将重点介绍 NAT。
14.1.1. 什么是 NAT 地址?
NAT 背后的组织原则非常出色:为什么不给每个设备分配一个唯一的、网络可读的地址,而是让它们都共享路由器使用的单个公共地址呢?但是,如何使流量流向和离开本地设备呢?通过使用 私有 地址。如果你想要将网络资源分成多个子组,如何才能有效地管理一切呢?通过网络分段。听起来很模糊?让我们看看 NAT 地址是如何工作的,以获得一些视角。
14.1.2. 使用 NAT 地址
当连接到您家庭 WiFi 的笔记本电脑上的浏览器访问网站时,它使用的是您互联网服务提供商(ISP)提供的 DSL 调制器/路由器分配的公共 IP 地址。通过同一 WiFi 网络连接的其他设备将使用相同的地址进行所有浏览活动(参见 图 14.1)。
图 14.1. 一个典型的 NAT 配置,展示了多个具有各自私有地址的本地设备如何都可以用一个单一的公共 IP 地址来表示

在大多数情况下,路由器使用动态主机配置协议(DHCP)为每个本地设备分配唯一的私有(NAT)地址,但这些地址在本地环境中是唯一的。这样,所有本地设备都可以与它们的本地对等方进行全面、可靠的通信。这对大型企业来说同样适用,其中许多企业使用成千上万的 NAT IP 地址,所有这些地址都位于单个公共 IP 后面。
NAT 协议预留了三个只能用于私有地址的 IPv4 地址范围:
-
10.0.0.0 至 10.255.255.255
-
172.16.0.0 至 172.31.255.255
-
192.168.0.0 至 192.168.255.255
本地网络管理员可以自由地以任何方式使用这些地址(有超过 1700 万个地址)。但是,地址通常被组织成更小的网络(或子网)块,其中主机网络由地址左侧的八位字节标识。这为地址右侧的八位字节留出了空间,可以分配给单个设备。
例如,你可能会选择在 192.168.1 上创建一个子网,这意味着这个子网中的所有地址都将以 192.168.1(地址的网络部分)开头,并以 2 到 254 之间的唯一单字节设备地址结尾。因此,该子网上的一个 PC 或笔记本电脑可能会获得地址 192.168.1.4,另一个可能会获得 192.168.1.48。
注意
根据网络惯例,DHCP 服务器通常不会将数字 0、1 和 255 分配给网络设备。
继续使用这个例子,你可能随后想要使用 192.168.2 添加一个并行但独立的网络子网。在这种情况下,192.168.1.4 和 192.168.2.4 不仅是两个不同的地址,可以分配给两个不同的设备,而且由于它们位于不同的网络上,这两个设备甚至可能无法互相访问(参见图 14.2)。
图 14.2. 192.168.x网络范围内连接到两个单独 NAT 子网的设备

子网表示法
由于确保系统知道网络地址属于哪种子网至关重要,我们需要一种标准表示法,可以准确地传达哪些八位字节是网络的一部分,哪些是分配给设备的。有两种常用的标准:无类域间路由(CIDR)表示法和子网掩码。使用 CIDR,上一个例子中的第一个网络将被表示为 192.168.1.0/24。/24 告诉你前三个八位字节(8×3=24)构成了网络部分,只留下第四个八位字节用于设备地址。第二个子网在 CIDR 中表示为 192.168.2.0/24。
这两个相同的网络也可以通过子网掩码 255.255.255.0 来描述。这意味着前三个八位字节中的所有 8 位都被网络使用,但第四个八位字节没有被使用。
你不必完全按照这种方式拆分地址块。如果你知道你不太可能在域中需要很多网络子网,但你预计需要连接超过 255 个设备,你可以选择只将前两个八位字节(192.168)指定为网络地址,将 192.168.0.0 到 192.168.255.255 之间的所有内容留给设备。在 CIDR 表示法中,这将被表示为 192.168.0.0/16,并具有子网掩码 255.255.0.0。
你的网络部分也不需要使用完整的(8 位)八位字节。特定八位字节范围内的部分可以专门用于整个网络的地址(例如 192.168.14.x),其余的留给设备(或主机,更常见的是这样称呼)。这样,你可以预留子网前两个八位字节的所有地址(192 和 168),以及第三八位字节的一些地址(0),作为网络地址。这可以表示为 192.168.0.0/20 或使用子网掩码 255.255.240.0。
我从哪里得到这些符号编号?大多数经验丰富的管理员使用他们的二进制计数技能自己解决。但对于一个关于一般网络故障排除的章节,这有点超出范围,对于你可能会遇到的正常工作来说也是不必要的。尽管如此,仍然有许多在线子网计算器可以为你完成计算。
你为什么要将你的网络划分为子网?一个常见的场景涉及需要某些团队(可能是开发者)访问但不需要其他团队访问的公司资产。将它们逻辑上分离到它们自己的子网中可以是一个有效的方法。
14.2. 建立网络连接
每个人都在周一早上早早地来到工作场所。他们互相交换简短但愉快的问候,坐在他们的笔记本电脑和工作站上,准备开始一周富有成效的工作,然后发现无法访问互联网。除了可能愉快和富有成效的部分,你应该预期这种情况很快就会发生在你身上(如果还没有发生的话)。网络中断的原因可能是以下任何一种:
-
本地机器上的硬件或操作系统故障
-
对你的物理布线、路由或无线连接的干扰
-
本地路由软件配置问题
-
在 ISP 层面的故障
-
整个互联网本身出现故障
你的第一项工作是通过排除不相关的内容来缩小搜索范围。你可以通过遵循一个从最接近的地方开始的协议来完成这项工作,确认故障不在你自己的本地系统中,然后逐渐向外扩展。图 14.3 展示了这个过程流程。
图 14.3. 展示在故障排除出站连接问题时可能遵循的顺序的流程图

让我们看看这一切可能如何运作。你将从解决本地计算机可能访问外部资源的问题开始,然后解决外部客户端或用户可能访问你的服务器上的资源的问题。
14.3. 故障排除出站连接
有可能你的计算机从未被分配自己的 IP 地址,没有它,作为网络良好成员的存在是不可能的。运行ip来显示你的网络接口设备,并确认你有一个活跃的外部设备,并且与之关联了一个有效的 IP 地址。在以下示例中,eth0 接口正在使用 10.0.3.57:
$ ip addr *1*
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue
state UNKNOWN group default qlen 1 *2*
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo *3*
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
7: eth0@if8: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue
state UP group default qlen 1000 *4*
link/ether 00:16:3e:29:8e:87 brd ff:ff:ff:ff:ff:ff link-netnsid 0
inet 10.0.3.57/24 brd 10.0.3.255 scope global eth0 *5*
valid_lft forever preferred_lft forever
inet6 fe80::216:3eff:fe29:8e87/64 scope link
valid_lft forever preferred_lft forever
-
1 ip 的 addr 参数也可以缩短为 a。
-
2 通过回环(lo)接口访问本地(localhost)资源
-
3 注意回环设备使用的 IP 地址是 127.0.0.1。这遵循了标准的网络约定。
-
4 接口状态显示为 UP。
-
5 计算机的当前公共 IP 地址显示为 inet 的值。
如果inet行上没有列出 IP 地址,或者根本没有列出网络接口,那么这就是你需要集中注意力的地方。
14.3.1. 跟踪网络状态
首先,确认你的计算机上安装了物理网络适配器(也称为网络接口卡,或 NIC),并且 Linux 能够识别它。你可以使用lspci列出当前安装的所有基于 PCI 的硬件。在以下输出中,lspci发现了一个 PCI Express 千兆以太网控制器:
$ lspci
00:00.0 Host bridge: Advanced Micro Devices, Inc. [AMD]
Family 15h (Models 10h-1fh) Processor Root Complex
[...]
01:00.0 Ethernet controller:
Realtek Semiconductor Co., Ltd. RTL8111/8168/8411
PCI Express Gigabit Ethernet Controller (rev 06) *1*
- 1 术语“以太网控制器”指的是一种硬件网络接口设备。
如果lspci没有返回任何网络接口卡(NIC),你应该考虑你可能有某种硬件故障的可能性。
注意
外围组件互连(PCI)是一种硬件标准,用于允许外围设备通过 PCI 总线连接到计算机主板上的微处理器。还存在着各种更新的标准,如 PCI Express(PCIe),每个标准都使用其独特的形式因子来物理连接到主板。
除了lspci之外,你还可以使用lshw工具来显示系统所知的网络硬件。lshw本身返回一个完整的硬件配置文件,但lshw-class network将只显示与网络相关的配置文件子集。试试看。
lspci的积极结果本身不会让你走得太远,因为它没有告诉你如何从命令行访问该设备。但它确实提供了一些重要信息。例如,从lspci输出中提取以太网这个词,并使用 grep 来搜索dmesg的输出。你可能还记得第十一章,dmesg是涉及设备的内核相关事件的记录。经过一些尝试和错误,我发现通过包括包含搜索字符串的行之后的两个dmesg行(使用-A 2)进行搜索将是最有效的:
$ dmesg | grep -A 2 Ethernet
[ 1.095265] r8169 Gigabit Ethernet driver 2.3LK-NAPI loaded
[ 1.095840] r8169 0000:01:00.0 eth0<1>: RTL8168evl/8111evl
at 0xffffc90000cfa000, 74:d4:35:5d:4c:a5, XID 0c900800 IRQ 36
[ 1.095842] r8169 0000:01:00.0
eth0: jumbo features [frames: 9200 bytes, tx checksumming: ko] *1*
- 1 设备标识 eth0 显示与千兆以太网设备相关联。
成功!您可以看到设备被分配了 eth0 的指定。等等。别急。尽管 eth0 最初被分配给设备是因为 Linux 现在使用可预测的接口名称(请参阅第十章),但这可能不是接口实际使用的指定。为了安全起见,您想再次搜索dmesg以查看 eth0 是否出现在其他地方:
$ dmesg | grep eth0
[ 1.095840] r8169 0000:01:00.0
eth0: RTL8168evl/8111evl at 0xffffc90000cfa000, 74:d4:35:5d:4c:a5,
XID 0c900800 IRQ 36
[ 1.095842] r8169 0000:01:00.0
eth0: jumbo features [frames: 9200 bytes, tx checksumming: ko]
[ 1.129735] r8169 0000:01:00.0 enp1s0:
renamed from eth0 *1*
- 1 eth0 的指定已被删除,并替换为 enp1s0。
哎。看起来在启动过程中某个时刻设备被重命名为 enp1s0。好的。您已经配置了正确的网络接口,但仍然没有 IP 地址,也没有网络连接,接下来是什么?dhclient,但首先有一些背景知识。
14.3.2. 分配 IP 地址
网络设备可以通过以下方式获取 IP 地址:
-
有些人手动设置了一个静态地址,希望它位于本地网络的地址范围内。
-
DHCP 服务器会自动为设备提供一个未使用的地址。
如通常情况,每种方法都有其权衡。DHCP 服务器自动且无形地完成工作,并保证两个管理设备永远不会尝试使用相同的地址。但另一方面,这些地址是动态的,这意味着它们今天使用的地址可能不是他们明天得到的。考虑到这一点,如果您一直在成功使用,比如说,192.168.1.34 通过 SSH 远程连接到服务器,请准备好应对意外变化。
相反,手动设置 IP 地址确保这些地址永久关联到它们的设备。但总有可能您可能会引起地址冲突——结果不可预测。一般来说,除非您有特定的静态地址需求——比如您可能需要通过地址可靠地远程访问资源——否则我会选择 DHCP。
定义网络路由
在寻找地址之前,您需要确保 Linux 首先知道如何找到网络。如果 Linux 已经可以看到通往工作网络的路,那么ip route将显示您的计算机的路由表,包括本地网络和您用作网关路由器的设备的 IP 地址:
$ ip route
default via 192.168.1.1 *1*
dev enp0s3 proto static metric 100
192.168.1.0/24 dev enp0s3 proto kernel scope
link src 192.168.1.22 metric 100 *2*
-
1 本地计算机将通过该网关路由器访问更广泛的网络
-
2 本地 NAT 网络的 NAT 网络(192.168.1.x)和子网掩码(/24)
如果没有列出工作路由,那么您需要创建一个,但首先您需要确定您本地网络的子网范围。如果有其他计算机使用相同的网络,请检查它们的 IP 地址。比如说,如果其中一台计算机正在使用 192.168.1.34,那么路由器的地址很可能是 192.168.1.1。同样,如果连接的计算机的 IP 地址是 10.0.0.45,那么路由器的地址将是 10.0.0.1。您应该明白了。基于此,以下是创建到网关的新默认路由的ip命令:
# ip route add default via 192.168.1.1 dev eth0
注意
本章讨论的 ip 命令相对较新,旨在取代现在已弃用的命令集,如 ifconfig、route 和 ifupdown。您仍然会看到大量专注于这些旧命令的指南,至少目前它们仍然有效,但您应该习惯使用 ip。
请求动态地址
请求 DHCP 地址的最佳方式是使用 dhclient 在您的网络上搜索 DHCP 服务器,然后请求一个动态地址。以下是一个示例,假设您的外部网络接口名为 enp0s3:
# dhclient enp0s3
Listening on LPF/enp0s3/08:00:27:9c:1d:67
Sending on LPF/enp0s3/08:00:27:9c:1d:67
Sending on Socket/fallback
DHCPDISCOVER on enp0s3 to 255.255.255.255
port 67 interval 3 (xid=0xf8aa3055)
DHCPREQUEST of 192.168.1.23 on enp0s3 to 255.255.255.255
port 67 (xid=0x5530aaf8)
DHCPOFFER of 192.168.1.23 from 192.168.1.1 *1*
DHCPACK of 192.168.1.23 from 192.168.1.1
RTNETLINK answers: File exists
bound to 192.168.1.23 -- renewal in 34443 seconds. *2*
-
1 在这种情况下 DHCP 服务器的地址是 192.168.1.1。
-
2 新地址已成功租用一定时间;续订将是自动的。
配置静态地址
您可以使用 ip 从命令行临时为接口分配一个静态 IP 地址,但这只会持续到下一次系统启动。考虑到这一点,以下是操作方法:
# ip addr add 192.168.1.10/24 dev eth0
这对于快速且简单的单次配置很有用,例如在故障排除时尝试在受影响的系统上获得连接。但通常您可能更喜欢使您的编辑永久化。在 Ubuntu 机器上,这需要编辑 /etc/network/interfaces 文件。该文件可能已经包含一个部分,将您的接口定义为 DHCP 而不是静态。
列表 14.1. /etc/network/interfaces 文件中的一个部分
auto enp0s3
iface enp0s3 inet dhcp
您将编辑该部分,将 dhcp 改为 static,输入您希望它拥有的 IP 地址,子网掩码(以 x.x.x.x 格式),以及计算机将使用的网络网关(路由器)的 IP 地址。以下是一个示例:
auto enp0s3
iface enp0s3 inet static
address 192.168.1.10
netmask 255.255.255.0
gateway 192.168.1.1
在 CentOS 上,每个接口都会在 /etc/sysconfig/network-scripts/ 目录中有一个自己的配置文件。一个典型的用于 DHCP 地址设置的接口配置将如下所示。
列表 14.2. /etc/sysconfig/network-scripts/ifcfg-enp0s3 中的配置
TYPE="Ethernet"
BOOTPROTO="dhcp" *1*
DEFROUTE="yes"
PEERDNS="yes"
PEERROUTES="yes"
IPV4_FAILURE_FATAL="no"
IPV6INIT="yes"
IPV6_AUTOCONF="yes"
IPV6_DEFROUTE="yes"
IPV6_PEERDNS="yes"
IPV6_PEERROUTES="yes"
IPV6_FAILURE_FATAL="no"
NAME="enp0s3"
UUID="007dbb43-7335-4571-b193-b057c980f8d0"
DEVICE="enp0s3"
ONBOOT="yes"
- 1 告诉 Linux 为该接口请求一个动态 IP 地址
下一个列表显示了在编辑以允许静态地址后该文件可能的样子。
列表 14.3. CentOS 接口配置文件的静态版本
BOOTPROTO=none *1*
NETMASK=255.255.255.0
IPADDR=10.0.2.10 *2*
USERCTL=no
DEFROUTE="yes"
PEERDNS="yes"
PEERROUTES="yes"
IPV4_FAILURE_FATAL="no"
IPV6INIT="yes"
IPV6_AUTOCONF="yes"
IPV6_DEFROUTE="yes"
IPV6_PEERDNS="yes"
IPV6_PEERROUTES="yes"
IPV6_FAILURE_FATAL="no"
NAME="enp0s3"
UUID="007dbb43-7335-4571-b193-b057c980f8d0"
DEVICE="enp0s3"
ONBOOT="yes"
-
1 不会使用 DHCP 地址
-
2 设置您想要使用的静态 IP 地址
如果您希望您的设置立即生效,您需要重新启动网络。大多数情况下,现代系统中的网络由 systemd 服务 NetworkManager 管理。但在 Ubuntu 上至少,启动或停止 /etc/network/interfaces 文件中定义的接口是由网络服务处理的。因此,如果您想应用接口文件中的新编辑设置,您将运行 systemctl restart networking 而不是 systemctl restart NetworkManager。或者,您可以使用 ip 只启动(或关闭)一个接口:
# ip link set dev enp0s3 up
了解你的系统中 NetworkManager 隐藏工作文件的某些位置不会有害。在/etc/NetworkManager/目录中有一个名为NetworkManager.conf的配置文件,每个你的电脑历史上建立的网络的配置文件都在/etc/NetworkManager/system-connections/中,以及详细说明你的电脑历史 DHCP 连接的数据在/var/lib/NetworkManager/中。为什么不快速浏览一下这些资源呢?
14.3.3. 配置 DNS 服务
如果你有一个有效的网络路由和 IP 地址,但连接问题还没有解决,那么你可能需要扩大你的搜索范围。想想看,你到底不能做什么。
你的网页浏览器无法加载页面吗?(我不知道,也许像 bootstrap-it.com 这样的例子。)这可能意味着你没有连接性。也可能意味着没有发生 DNS 转换。
什么是 DNS?
虽然看起来不是这样,但万维网实际上完全是关于数字的。没有名为 manning.com 或 wikipedia.org 的地方。相反,它们分别是 35.166.24.88 和 208.80.154.224。连接我们到我们所知和喜爱的网站的软件只识别数字 IP 地址。
在文本爱好者的人类和我们的更数字化机器之间进行翻译的工具被称为域名系统(DNS)。域名是一个常用来描述一个独特的网络资源组的词,特别是那些由唯一可读的名称标识的资源。如图 14.4 所示,当你你在浏览器中输入一个文本地址时,会寻求 DNS 服务器提供的服务。
图 14.4. 对 stuff.com 的 DNS 地址查询及其包含(虚构)IP 地址的回复

DNS 是如何工作的?
第一个地方通常是本地索引,其中包含名称及其关联的 IP 地址,这些信息存储在一个由电脑上的操作系统自动创建的文件中。如果这个本地索引没有回答这个特定的转换问题,它将请求转发到一个指定的公共 DNS 服务器,该服务器维护一个更完整的索引,并且可以连接到你想要的网站。知名的公共 DNS 服务器包括由 Google 提供的,它们使用美味的简单地址 8.8.8.8 和 8.8.4.4,以及 OpenDNS。
修复 DNS
除非出了问题,否则你通常不会花很多时间去思考 DNS 服务器。但恐怕真的出了点问题。你可以使用 ping 工具来确认问题。如果你 ping 一个正常的网站 URL(如 manning.com)不起作用,但使用 IP 地址可以,那么你就找到了你的问题所在。这可能看起来是这样的:
$ ping 8.8.8.8
PING 8.8.8.8 (8.8.8.8) 56(84) bytes of data.
64 bytes from 8.8.8.8: icmp_seq=1 ttl=60 time=10.3 ms
64 bytes from 8.8.8.8: icmp_seq=2 ttl=60 time=10.2 ms
64 bytes from 8.8.8.8: icmp_seq=3 ttl=60 time=9.33 ms
^C *1*
--- 8.8.8.8 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2002ms
rtt min/avg/max/mdev = 9.339/10.002/10.378/0.470 ms
- 1 这个符号告诉你 Ctrl-c 键组合被用来中断 ping 操作。
解决方案?这取决于。很多时候,单个计算机将通过它们连接到更广网络的路由器继承 DNS 设置。我想这意味着你接下来几分钟将花费在抽屉里寻找路由器登录密码(提示:默认密码通常打印在路由器外壳上)。
一旦你成功登录,通常是从一个连接的 PC 登录,该 PC 运行一个指向路由器 IP 地址的浏览器,通过路由器操作系统的 GUI 菜单进行操作,确保 DNS 设置有效。你还可以在本地计算机上配置 DNS 设置,这将覆盖路由器上的设置。在 CentOS 机器上,将几个公共 DNS 服务器的引用添加到接口的/etc/sysconfig/network-scripts/ifcfg-enp0s3文件中。此示例使用 Google DNS 服务器使用的两个 IP 地址:
DNS1=8.8.8.8
DNS2=8.8.4.4
在 Ubuntu 上,将dns-nameserver值添加到/etc/network/interfaces文件中相应的接口:
dns-nameserver 8.8.8.8
dns-nameserver 8.8.4.4
14.3.4. 水暖工程
是的。现在是卷起袖子,拿出排水蛇的时间了。如果你已经有了工作界面、路由、IP 地址和 DNS 服务,但仍然没有完整的连接性,那么肯定有什么东西在外面阻挡了流量。
Linux 中排水蛇的等效物,特别是带有视频摄像头末端的复杂类型,是 Traceroute——正如其名,它追踪数据包在网络中到达目标的路由。如果任何地方有阻塞流量,Traceroute 至少会显示阻塞的位置。即使你无法进一步调查,这些信息也可能特别有价值,因为你的 ISP 试图让一切恢复正常。
此示例显示了从我的家庭工作站到 google.com(表示为 172.217.0.238)的成功端到端行程。如果出了任何问题,显示的跳转会在达到目标之前停止。只包含星号(*)的输出行有时可能表示数据包未能返回。通常,完全失败会伴随着错误信息:
$ traceroute google.com
traceroute to google.com (172.217.0.238), 30 hops max, 60 byte packets
1 ControlPanel.Home (192.168.1.1)
21.173 ms 21.733 ms 23.081 ms *1*
2 dsl-173-206-64-1.tor.primus.ca (173.206.64.1)
25.550 ms 27.360 ms 27.865 ms *2*
3 10.201.117.22 (10.201.117.22) 31.185 ms 32.027 ms 32.749 ms
4 74.125.48.46 (74.125.48.46) 26.546 ms 28.613 ms 28.947 ms
5 108.170.250.241 (108.170.250.241) 29.820 ms 30.235 ms 33.190 ms
6 108.170.226.217 (108.170.226.217)
33.905 ms 108.170.226.219 (108.170.226.219) 10.716 ms 11.156 ms
7 yyz10s03-in-f14.1e100.net (172.217.0.238) 12.364 ms * 6.315 ms
-
1 第一跳是我的本地路由器;显示的~20 毫秒跳转时间有点慢,但可以接受。
-
2 我的互联网服务提供商
仍然没有解决问题?听起来是时候给你的 ISP 打电话了。
接下来要讨论的是:当你的本地网络内的人可以访问互联网上的一切,但你的远程工作人员、客户和访客无法进入以消费你提供的服务时会发生什么。
14.4. 故障排除入站连接
不论是你们公司的网站、支持你们应用的 API 还是内部文档维基,你们的基础设施中都有一些部分你们希望 24/7 可用。这类入站连接对于你们的企业或组织来说可能和刚刚讨论的出站连接一样重要。
如果您的远程客户端无法连接到您的服务,或者它们的连接速度太慢,您的业务将受到影响。因此,您需要定期确认您的应用程序运行正常,正在监听传入的请求,这些请求拥有所需的访问权限,并且有足够的带宽来处理所有流量。netstat 和 netcat 可以帮助您做到这一点。
14.4.1. 内部连接扫描:netstat
在服务器上运行 netstat 会显示广泛的网络和接口统计信息。然而,当面对一群愤怒的 Web 客户端时,您最感兴趣的可能是一份正在监听网络请求的服务列表。
netstat -l 将显示当前所有打开的套接字。如果您正在运行一个网站,则可以通过过滤 http 来缩小结果。在这种情况下,端口 80(http)和 443(https)似乎都是活跃的:
$ netstat -l | grep http
tcp6 0 0 [::]:http [::]:* LISTEN *1*
tcp6 0 0 [::]:https [::]:* LISTEN
- 1 协议显示为 tcp6,这表明这是一个专用的 IPv6 服务。实际上,它涵盖了 IPv6 和 IPv4。
网络套接字究竟是什么?
坦白说,我不太确定如何描述它。对于一个 C 语言程序员来说,这可能感觉有些奇怪,但对于一个简单的系统管理员,比如我这样的谦卑仆人来说,可能感觉更奇怪。尽管如此,我还是冒险简化一下,说一个 服务端点 是由服务器的 IP 地址和端口号(例如 192.168.1.23:80)定义的。这种组合确定了 网络套接字。在涉及两个端点/套接字(客户端和服务器)的会话期间创建连接。
netstat -i 将列出您的网络接口。表面上,这看起来可能不是什么大问题;毕竟,ip addr 也能做到这一点,对吧?啊,是的。但是 netstat 还会显示接收到的数据包数量(RX)和发送的数据包数量(TX)。OK 表示无错误传输;ERR 表示损坏的数据包;DRP 表示丢弃的数据包。当您不确定服务是否活跃时,这些统计数据可能会有所帮助:
$ netstat -i
Kernel Interface table
Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR Flg
enp1s0 1500 0 0 0 0 0 0 0 BMU
lo 65536 0 16062 0 0 0 16062 0 LRU
wlx9cefd5fe6a19 1500 0 1001876 0 0 0 623247 0 BMRU
该示例似乎显示了一个健康且繁忙的无线接口(不幸的名称为 wlx9cefd5fe6a19),以及一个名为 enp1s0 的非活动接口。发生了什么事?这是一台带有未使用的以太网端口且通过 WiFi 获取互联网的 PC。在代码中,lo 是 localhost 接口,也称为 127.0.0.1。这是一种从服务器内部评估事物的好方法,但外部看起来如何呢?
14.4.2. 外部连接扫描:netcat
您已经使用 cat 来流式传输文本文件,使用 zcat 来流式传输压缩归档的内容。现在是时候认识(猫科)家族的另一位成员了:netcat(通常称为 nc)。正如您从其名称中猜测的那样,netcat 可以用于在网络上流式传输文件,甚至可以作为简单的双向聊天应用程序。
但现在您更感兴趣的是服务器状态,特别是客户端将如何看到它。nc在针对远程地址运行时,会告诉您是否能够建立连接。-z将netcat的输出限制为扫描监听守护进程的结果(而不是尝试建立连接),而-v增加了输出的详细程度。您需要指定要扫描的端口或端口。以下是一个示例:
$ nc -z -v bootstrap-it.com 443 80
Connection to bootstrap-it.com 443 port [tcp/https] succeeded!
Connection to bootstrap-it.com 80 port [tcp/http] succeeded!
如果其中任何一个或两个服务(HTTP 和 HTTPS)不可用,扫描将失败。这可能是因为服务没有在服务器上运行(例如,您的 Apache 网络服务器已停止)或者存在过于严格的防火墙规则阻止访问。失败的扫描将如下所示:
$ nc -z -v bootstrap-it.com 80
nc: connect to bootstrap-it.com port 80 (tcp) failed: Connection timed out
然而,这是 Linux,所以您可以确信完成这项工作有不止一种好方法。因此,请注意,nmap可以用来执行类似的扫描:
$nmap -sT -p80 bootstrap-it.com
Nmap scan report for bootstrap-it.com (52.3.203.146)
Host is up (0.036s latency).
PORT STATE SERVICE
80/tcp open http
Nmap done: 1 IP address (1 host up) scanned in 0.37 seconds
这个nmap命令将扫描 1 到 1023 端口之间的任何开放端口,这是一种快速审计系统以确保没有不应该开放的端口的好方法:
$ nmap -sT -p1-1023 bootstrap-it.com
Nmap scan report for bootstrap-it.com (52.3.203.146)
Host is up (0.038s latency).
Not shown: 1020 filtered ports
PORT STATE SERVICE
80/tcp open http
443/tcp open https
Nmap done: 1 IP address (1 host up) scanned in 4.69 seconds
哪些端口“应该”开放?这取决于您在服务器上运行的软件。作为一个经验法则,如果nmap报告任何不熟悉的开放端口,请在网上搜索以找出使用这些端口的软件,然后问问自己,该软件在您的服务器上运行是否合理。
摘要
-
至少在实践上,Linux 在 NAT 网络协议的上下文中定义和管理网络接口和路由。
-
Linux 需要识别连接的硬件外围设备,如网络接口,但在它们可用之前还需要指定设备标签(如 eth0)。
-
可以通过编辑配置文件和从命令行(使用
ip)为设备分配自定义静态 IP 地址。动态地址可以从 DHCP 服务器自动请求,但您无法控制获得的地址。 -
确认远程客户端可以访问适当的本地服务涉及扫描开放的套接字和端口。
关键术语
-
TCP/IP是定义网络行为管理的传输控制协议和互联网协议约定。
-
公网 IP 地址必须是全局唯一的,而NAT 地址只需要在本地网络内是唯一的。动态主机配置协议(DHCP)通常用于管理动态(非永久性)地址分配。
-
网络路由是通过计算机获得网络访问的网关路由器的地址。
-
域名系统(DNS)提供数字 IP 地址和可读 URL 之间的转换,使得方便地导航互联网资源。
-
网络套接字是 IP 地址和端口的表示,通过网络连接可以通过它被激活。
安全最佳实践
定期使用像nmap这样的工具来审计您的系统,检查是否有不适当地开放的端口,这是一个好习惯。
命令行审查
-
ip addrlists the active interfaces on a Linux system. You can shorten it toip aor lengthened it toip address. It’s your choice. -
lspcilists the PCI devices currently connected to your computer. -
dmesg | grep -A 2 Ethernetsearches thedmesglogs for references to the string Ethernet and displays references along with the subsequent two lines of output. -
ip route add default via 192.168.1.1 dev eth0manually sets a new network route for a computer. -
dhclient enp0s3requests a dynamic (DHCP) IP address for the enp0s3 interface. -
ip addr add 192.168.1.10/24 dev eth0assigns a static IP address to the eth0 interface, which won’t persist past the next system restart. -
ip link set dev enp0s3 upstarts the enp0s3 interface (useful after editing the configuration). -
netstat -l | grep httpscans a local machine for a web service listening on port 80. -
nc -z -v bootstrap-it.com 443 80scans a remote web site for services listening on the ports 443 or 80.
测试自己
1
以下哪个是有效的 NAT IP 地址?
- 11.0.0.23
- 72.10.4.9
- 192.168.240.98
- 198.162.240.98
2
你将如何描述使用两个八位字节作为网络地址的 IPv4 网络子网,同时使用 CIDR 和 netmask 表示法?
- x.x.x.x/16 或 255.255.0.0
- x.x.x.x/24 或 255.255.255.0
- x.x.x.x/16 或 255.0.0.0
- x.x.x.x/16 或 255.255.240.0
3
以下哪个命令可以帮助你发现 Linux 为网络接口指定的名称?
dmesglspcilshw -class networkdhclient4
你正在设置办公室的 PC,并希望它具有可靠的网络连接。以下哪个配置文件将最适合?
- 动态 IP 地址,直接连接到互联网
- 静态 IP 地址,属于 NAT 网络的一部分
- 静态 IP 地址,直接连接到互联网
- 动态 IP 地址,属于 NAT 网络的一部分
5
以下哪个命令用于请求动态 IP 地址?
ip routedhclient enp0s3ip client enp0s3ip client localhost6
你将编辑哪个文件来配置 CentOS 机器上名为 enp0s3 的网络接口?
- /etc/sysconfig/networking/ipcfg-enp0s3
- /etc/sysconfig/network-scripts/ipcfg-enp0s3
- /etc/sysconfig/network-scripts/enp0s3
- /etc/sysconfig/network-scripts/ifcfg-enp0s3
7
你会在 Ubuntu 机器的
/etc/network/interfaces文件的哪个网络接口配置部分添加哪一行,以强制接口使用 Google DNS 名称服务器?
- DNS1=8.8.8.8
- dns-nameserver 8.8.8.8
- nameserver 8.8.8.8
- dns-nameserver1 8.8.8.8
8
以下哪个命令将扫描远程服务器上知名 TCP 端口,以查找可访问的监听服务?
nmap -s -p1-1023 bootstrap-it.comnmap -sU -p80 bootstrap-it.comnmap -sT -p1-1023 bootstrap-it.comnc -z -v bootstrap-it.com
答案键
1.
c
2.
a
3.
a
4.
d
5.
b
6.
d
7.
d
8.
c
第十五章. 解决外围设备故障
本章涵盖
-
分析系统硬件配置文件
-
管理内核模块以管理硬件设备
-
管理内核设置以解决硬件启动冲突
-
使用 CUPS 管理和解决打印机问题
点击鼠标按钮和你在屏幕上看到发生的事情之间的连接很复杂。简单来说,你需要某种软件过程,它将在鼠标和电脑之间、电脑和运行在其上的软件之间、以及软件和屏幕之间来回穿梭数据。
不仅仅是数据传输,你还需要一种方法来在只知道它所坐的桌面的鼠标和只知道零和一的软件之间转换数据。将这一点乘以数千种设备型号,再加上许多连接类型(PCI、SATA、USB、串行),你就在你的 PC 中煮了一锅相当复杂的汤。
考虑到整个系统的复杂性,它竟然如此可靠地工作,真是令人惊讶。在本章中,你将学习如何处理那些它不工作的时候……比如当市场营销团队在等待你激活摄像头,以便他们的虚拟会议开始。或者当他们的 WiFi 不允许他们首先连接。要完成所有这些魔法,你需要了解 Linux 如何看待你的外围设备,以及你如何引导 Linux 内核将一个害羞的设备纳入其羽翼下并照顾它。因为我们关心我们所有的设备。
15.1. 识别连接的设备
插入的摄像头没有在互联网上展示你的笑脸?打印机没有打印?WiFi 适配器没有适配(或者它们所做的任何事情)?
在你投入太多时间和精力去激活硬件设备之前,你必须首先接受这个令人悲伤的事实:操作系统(OS)有时甚至可能无法识别其连接的一些硬件。如果你新插入的设备似乎不起作用,你首先应该确认 Linux 知道它的存在。这将让你忙上接下来的几页。如果你运行我即将展示的诊断程序,仍然没有生命迹象,那么考虑一下这种可能性:
-
设备与你的硬件或 Linux 不兼容。
-
设备损坏或故障。
-
硬件接口或电缆损坏或故障。
-
系统需要重启。
-
你今天过得不太好。
当它们开始互相通信后,我会向你展示如何使用内核模块,让 Linux 和你的设备能够联手为你完成一些工作。我们将从通过 Linux 的视角查看你的硬件开始。实际上,这个“找出 Linux 是否识别了你刚刚插入的设备”的业务并不是全新的。你还记得在第六章中使用了 lsblk 来发现附加的块设备。嗯,lsblk 有一些表亲:lsusb 列出 Linux 所知的任何 USB 设备,正如你在上一章中看到的,lspci 会为 PCI 设备做同样的事情。以下是一个例子:
$ lsusb
Bus 005 Device 001: ID 1d6b:0001 Linux Foundation 1.1 root hub
Bus 002 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root hub
Bus 001 Device 008: ID 04f9:0249
Brother Industries, Ltd *1*
Bus 001 Device 007: ID 413c:2005 Dell Computer Corp. RT7D50 Keyboard
Bus 001 Device 006: ID 046d:081a Logitech, Inc. *2*
Bus 001 Device 005: ID b58e:9e84 Blue Microphones Yeti Stereo Microphone
Bus 001 Device 004: ID 1a40:0101
Terminus Technology Inc. Hub *3*
Bus 001 Device 002: ID 148f:5372
Ralink Technology, Corp. RT5372 Wireless Adapter *4*
Bus 003 Device 002: ID 093a:2510 Pixart Imaging, Inc. Optical Mouse
-
1 一台兄弟激光打印机
-
2 一款网络摄像头
-
3 一款 USB 多端口集线器
-
4 一款 USB 无线网卡
第十四章也是你看到 ls 家族的老大 lshw 的地方。当以 root 权限运行时,lshw 会打印出系统的完整硬件配置文件。你首先会注意到的是,lshw 对你硬件的每一部分都有很多话要说。驯服这个巨兽的一种方法是将输出转换为易于阅读的 .html 文件,你可以在网页浏览器中查看它。-html 参数就是用来做这个的。在 Nautilus 这样的 GUI 文件管理器中点击文件名应该会将其加载到你的默认浏览器中。以下是操作方法:
# lshw -html > lshw-output.html
记得我们在上一章中使用 lshw -class network 来限制输出只显示与网络相关的内容吗?这个技巧同样适用于其他数据子集。例如,lshw -c memory 会显示系统使用所有类型的内存的详细信息(包括 RAM、BIOS 固件和缓存);你可能已经猜到了,-c 作为 -class 的快捷方式,可以更快地使用。除此之外,lshw -c storage 会显示 SATA 和 SCSI 接口的信息,-c multimedia 包括音频和视频设备,而 -c cpu 会告诉你关于连接到主板上的 CPU 的所有信息。这就是如何优雅地消费 lshw 信息。但你应该如何使用它呢?
这是一个常见的场景。假设你正在考虑向系统添加额外的 RAM——也许你在第十三章中收集的指标表明你的 RAM 可能不足。你需要知道你已经有多少 RAM 以及它的类型,更不用说你需要了解你正在使用的主板,以便你可以研究有多少 RAM 插槽可用以及它们的最大容量是多少。
好的。所以 RAM 并不完全是一个外围设备,但它很好地作为一个例子,说明了可能的硬件发现类型。而且,在解决硬件问题时,硬件发现始终应该是你的第一步。
为了说明这一点,lshw告诉我我的主板有四个 RAM 插槽,其中两个目前被 4GB A-Data DDR3 1600 内存模块占用。由于你应该避免在单个系统上安装不匹配的内存模块,这告诉我我应该购买什么类型的 RAM 来填充这两个空槽,并将我的容量加倍。
我应该指出,我目前没有升级工作站的计划。我为什么要升级呢?我已经拥有的硬件配置相当适中,它允许我在编辑和/或编码少量视频(使用 Kdenlive)的同时运行多个虚拟机,并且还能让至少一个网络浏览器保持忙碌,打开十几个标签页。而我用不到 300 美元从头搭建的电脑,其性能比许多同事使用的 1000 多美元的设备要好得多。区别在哪里?那些可怜的家伙在喂养着资源消耗巨大的 Windows 和 macOS 操作系统,而我却在使用快速高效的 Linux。看吧。
如果你的设备被 Linux 识别但仍然不活跃,可能有一个合适的内核模块正在等待被加载。
15.2. 使用 Linux 内核模块管理外围设备
Linux 通过内核模块来管理硬件外围设备。以下是它是如何工作的。
运行中的 Linux 内核是你不希望打扰的东西之一。毕竟,内核是驱动你电脑所有操作的软件。考虑到在实时系统中需要同时管理的许多细节,最好是让内核在没有太多干扰的情况下完成其工作。但如果连对计算环境进行微小更改都需要重启整个系统,那么插入新的摄像头或打印机可能会对你的工作流程造成痛苦的干扰。每次添加设备都需要重启以使系统识别它,这几乎是不高效的。
为了在稳定性和可用性这两种对立的优点之间创造一个有效的平衡,Linux 将内核本身隔离出来,但允许你通过可加载内核模块(LKMs)即时添加特定功能。查看图 15.1,你可以将模块想象成一段软件,它告诉内核设备的位置以及如何处理它。反过来,内核将设备提供给用户和进程,并监督其操作。
图 15.1. 内核模块作为设备和 Linux 内核之间的翻译器。

没有什么阻止你编写自己的模块来支持你想要的设备,但你为什么要费这个劲呢?Linux 模块库已经非常强大,通常没有必要自己开发。而且,绝大多数情况下,Linux 会自动加载新设备的模块,而你甚至可能都不知道。
然而,有时出于某种原因,它不会自动发生。(你不想让那个招聘经理不耐烦地等待太久,直到你的笑脸加入视频会议面试。)为了帮助事情顺利进行,你需要了解更多关于内核模块的信息,特别是如何找到将运行你的外围设备的实际模块,然后如何手动激活它。
15.2.1. 查找内核模块
根据公认的习惯,模块是以.ko(内核对象)扩展名的文件,位于/lib/modules/目录之下。然而,在你导航到那些文件之前,你可能需要做出一个选择。因为你在启动时可以从一系列版本中选择加载一个,所以支持你的选择(包括内核模块)的特定软件必须存在于某个地方。好吧,/lib/modules/就是那些某个地方之一。你会在那里找到每个可用的 Linux 内核版本的模块目录,如下所示:
$ ls /lib/modules
4.4.0-101-generic
4.4.0-103-generic
4.4.0-104-generic
在我的情况下,活动的内核是具有最高发布号的版本(4.4.0-104-generic),但并不能保证这对你来说也是一样的(内核经常更新)。如果你打算做一些你希望在实时系统上使用模块的工作,你需要确保你有了正确的目录树。
好消息:有一个可靠的技巧。与其通过名称识别目录并希望得到正确的目录,不如使用始终指向活动内核名称的系统变量。你可以使用uname -r(-r指定系统信息中通常显示的内核发布号)来调用该变量:
$ uname -r
4.4.0-104-generic
通过这些信息,你可以使用称为命令替换的过程将uname整合到你的文件系统引用中。例如,为了导航到正确的目录,你可以在/lib/modules/中添加它。为了告诉 Linuxuname本身不是一个文件系统位置,将uname部分用反引号括起来,如下所示:
$ ls /lib/modules/`uname -r`
build modules.alias modules.dep modules.softdep
initrd modules.alias.bin modules.dep.bin modules.symbols
kernel modules.builtin modules.devname modules.symbols.bin
misc modules.builtin.bin modules.order vdso
你会发现大多数模块都组织在 kernel/目录下的自己的子目录中。现在花几分钟浏览这些目录,以了解事物的组织方式和可用性。文件名通常会给你一个很好的提示,你正在看什么:
$ ls /lib/modules/`uname -r`/kernel
arch crypto drivers fs kernel lib mm
net sound ubuntu virt zfs *1*
- 1 这里列出的子目录中最繁忙的是 kernel,在其下你可以找到数百个设备的模块。
这实际上是一种定位内核模块的方法,也是快速而简单的方法。但并非只有这一种方法。如果你想获取完整的集合,你可以使用lsmod列出所有当前加载的模块和一些基本信息。第一列是模块名称,后面是文件大小和数量,然后是每个模块所依赖的其他模块的名称:
$ lsmod
[...]
vboxdrv 454656 3 vboxnetadp,vboxnetflt,vboxpci *1*
rt2x00usb 24576 1 rt2800usb *1*
rt2800lib 94208 1 rt2800usb *1*
[...]
- 1 这一小部分结果展示了与 VirtualBox 和我的 USB WiFi 适配器相关的模块。
多少才算太多?好吧,让我们再次运行lsmod,但这次将输出通过管道传递给wc -l以获取行数:
$ lsmod | wc -l
113
这些是已加载的模块。总共有多少个可用?运行modprobe -c并计算行数将给出这个数字:
$ modprobe -c | wc -l
33350
33,350 个可用的模块?看起来有人在多年来努力为我们提供运行物理设备的软件。
注意
在某些系统上,你可能会遇到自定义模块,这些模块要么在/etc/modules文件中有它们自己的唯一条目,要么作为配置文件保存在/etc/modules-load.d/中。这些模块很可能是本地开发项目的产物,可能涉及前沿实验。无论如何,了解你所看到的内容总是一个好主意。
这就是如何查找模块。你的下一个任务是弄清楚如何手动加载一个非活跃模块,如果由于某种原因它没有自动发生。
15.2.2. 手动加载内核模块
在加载内核模块之前,逻辑上你必须确认它存在。而在你能够做到这一点之前,你需要知道它的名字。有时候,获取这部分信息可能需要魔法和运气的同等部分,以及来自在线文档作者的某些帮助。
我将通过描述我之前遇到的一个问题来阐述这个过程。有一天,出于我至今仍无法理解的原因,笔记本电脑上的 WiFi 接口突然停止工作。就是这样。可能是一个软件更新让它失效了。谁知道呢?我运行了lshw -c network并得到了以下非常奇怪的信息:
network UNCLAIMED
AR9485 Wireless Network Adapter
Linux 识别了接口(Atheros AR9485),但将其列为未声明。嗯,正如他们所说,“当困难来临时,坚强的人会去搜索互联网。”我搜索了“atheros ar9 linux module”,在翻阅了成页甚至 10 年前的结果后,建议我要么编写自己的模块,要么放弃,我终于发现,至少在 Ubuntu 16.04 上,存在一个可用的模块。它的名字是 ath9k。
是的!战斗几乎已经胜利!向内核添加模块比听起来要容易得多。为了确认它可用,你可以对模块的目录树运行find命令,指定-type f告诉 Linux 你正在寻找一个文件,然后添加字符串ath9k以及通配符星号来包括所有以你的字符串开头的文件名:
$ find /lib/modules/$(uname -r) -type f -name ath9k*
/lib/modules/4.4.0-97-generic/kernel/drivers/net/wireless/ath/
ath9k/ath9k_common.ko
/lib/modules/4.4.0-97-generic/kernel/drivers/net/wireless/ath/ath9k/ath9k.ko
/lib/modules/4.4.0-97-generic/kernel/drivers/net/wireless/ath/ath9k/ath9k_htc.ko
/lib/modules/4.4.0-97-generic/kernel/drivers/net/wireless/ath/ath9k/ath9k_hw.ko
只需再走一步,加载模块:
# modprobe ath9k
就这样。无需重启。无需麻烦。
再举一个例子,展示如何处理已损坏的活跃模块。曾经有一段时间,使用我的罗技摄像头与某款特定软件配合时,会使摄像头在下次系统启动之前对任何其他程序都不可用。有时我需要在不同应用程序中打开摄像头,但没有时间关闭和重新启动。(我运行了很多应用程序,启动后需要一些时间才能将它们全部设置好。)
因为这个模块可能处于活动状态,使用lsmod搜索单词video给了我关于相关模块名称的提示。实际上,这比提示更好——唯一描述了单词video的模块是 uvcvideo,如下所示:
$ lsmod | grep video
uvcvideo 90112 0
videobuf2_vmalloc 16384 1 uvcvideo
videobuf2_v4l2 28672 1 uvcvideo
videobuf2_core 36864 2 uvcvideo,videobuf2_v4l2
videodev 176128 4 uvcvideo,v4l2_common,videobuf2_core,
videobuf2_v4l2
media 24576 2 uvcvideo,videodev
对于导致崩溃的原因,可能有一些我可以控制的事情;而且,我想,我可以挖得更深,看看是否可以正确地解决问题。但你知道,有时候你不在乎理论,只想让你的设备工作。所以我使用rmmod来终止 uvcvideo 模块,并使用modprobe来重新启动它,让它焕然一新:
# rmmod uvcvideo
# modprobe uvcvideo
再次强调:无需重启。无需顽固的血迹。
15.3. 手动管理启动时的内核参数
因为无论如何我们都在谈论内核,现在正是讨论内核参数的好时机。你知道,我们一直推迟的对话,因为内核参数听起来很吓人。好吧,它们确实很吓人:选错它们可能会让你的电脑至少暂时无法启动。而且拼写也很重要。
最初为什么要制造麻烦?因为有时你的内核默认启动配置可能不适合你的需求,而唯一解决问题的方法就是改变内核的启动方式。
在启动时向内核传递自定义参数有两种方式。一种是在启动过程中编辑 GRUB 菜单项,另一种是在运行的系统上编辑/etc/default/grub配置文件,以便在下次启动时生效。为了说明这些方法,我将使用两个实际用例场景。你必须继续阅读才能了解这些是什么。
15.3.1. 在启动时传递参数
我不确定这个问题有多普遍,但它作为一个教学示例会很合适。一些不幸的人发现他们无法正确关闭或重启 Linux,每次都会遇到不想要的系统冻结。添加一个简单的内核参数有时可以解决这个问题。下面是如何操作的。
在 GRUB 菜单中选择你想要启动的 Linux 版本(见图 15.2),按 e 键,你将被带到编辑屏幕。在那里,你可以使用常规的光标和文本键进行导航,然后编辑内容。
图 15.2. 主要 GRUB 菜单显示可加载的多个 Linux 内核

滚动直到你到达图 15.3 中突出显示的 Linux 条目。在这个例子中,在换行后,该条目以ro结束。(如果你的不同,不用担心。)然后在行尾添加reboot=bios,并按 Ctrl-x 接受更改并启动。如果这不能解决关闭问题,你可以尝试再次使用reboot=pci而不是reboot=bios。
图 15.3. 显示启动参数的 Linux 行,指向 Linux 镜像的位置

请记住,这次编辑不会是永久的。下次启动后,GRUB 设置将再次由文件系统中的配置文件控制。要了解如何进行持久更改,请继续阅读。
15.3.2. 通过文件系统传递参数
好吧,这个怎么样?可能会有时候你想在没有 GUI 的情况下启动桌面机器。也许 GUI 本身的一些元素没有正确加载,你需要一个干净、可靠的 shell 会话来排除故障。你知道吗,你可以通过 GRUB 将默认运行级别设置为 3(多用户,非图形模式)。
备注
运行级别 是一个设置,用于定义特定会话的 Linux 系统状态。在运行级别 0–6 之间进行选择,确定哪些服务应该可用,从完整的图形多用户系统到没有任何服务(即关机)。
打开 /etc/default/grub 文件,找到 GRUB_CMDLINE_LINUX_DEFAULT 行。它通常带有几个参数,看起来像这样:
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash"
将 systemd.unit=runlevel3.target 添加到行尾,使其看起来如下所示。(quiet splash 对我们来说无关紧要;它控制你在启动时在屏幕上看到的内容。)
GRUB_CMDLINE_LINUX_DEFAULT="quiet splash systemd.unit=runlevel3.target"
在 Ubuntu 上运行 update-grub 或在 CentOS 上运行 grub2-mkconfig 以使更改生效。下次启动计算机时,你将进入命令行 shell。一旦你完成了故障排除,你可以从 /etc/default/grub 中移除 systemd.unit=runlevel3.target,再次更新 GRUB,然后重新启动。
15.4. 管理打印机
准备好受到惊吓了吗?确保你已经坐好。我将建议你跳过一个完美的命令行工具,转而使用其 GUI 等效工具。命令行工具?lp。在引擎盖下配备了更新的引擎,lp 仍然存在,并且它确实可以做一些有趣的事情。但相信我,如果你支持的办公室打印机从网络上消失,你不会打开命令行来排除故障。如今,一切都是 Common UNIX Printing System (CUPS),一直如此。但在我们深入之前,我会给你一些可能用得上的 lp 命令。
15.4.1. lp 的基础知识
假设有一台远程计算机上的文件需要打印。你知道通过远程 X 会话启动 LibreOffice 并不是很有趣,对吧?通过简单、快速、可靠的 SSH shell 来做不是更好吗?说吧,不再多说了。使用 lpq 列出可用的打印机(包括当前的作业队列):
$ lpq
Brother-DCP-7060D is ready
no entries
然后使用 lp 打印文件。如果系统上有多个打印机,你还需要指定你想要使用的打印机。以下是一个示例:
$ lp -d Brother-DCP-7060D /home/user/myfile.pdf
不想立即打印?稍后安排。-H 调度设置始终使用 UTC 时间而不是本地时间:
$ lp -H 11:30 -d Brother-DCP-7060D /home/user/myfile.pdf
15.4.2. 使用 CUPS 管理打印机
从前,在购买打印机用于 Linux 系统之前,你需要进行仔细且耗时的研究以确保其兼容性。如果兼容,你通常需要下载并安装适当的驱动程序,然后通过操作系统手动安装打印机。当它工作正常时,这会是一个值得庆祝的时刻。在过去几年中,有三件事情的发生使得 Linux 打印变得更好:
-
CUPS 模块化打印系统已被许多,如果不是所有,的 Linux 发行版采用来管理打印机和打印。信不信由你,CUPS 是由苹果公司代表社区进行管理的。正如你很快就会看到的,CUPS 界面极大地简化了管理和故障排除,并且非常可靠。
-
主要打印机制造商现在通常提供 Linux 驱动程序。它们并不总是完美的,但它们是可行的。这意味着,如今,几乎任何现代打印机都可以与 Linux 计算机一起部署。尽管如此,快速查看像
help.ubuntu.com/community/Printers这样的在线资源也无妨。 -
从 17.04 版本开始,Ubuntu 现在提供无驱动打印。这意味着任何可访问的本地或网络打印机都将自动添加到 CUPS,无需任何设置。
无论如何,你通过浏览器访问 CUPS 界面,将其指向你自己的机器上的端口 631(localhost:631)。管理标签页(在图 15.4 中可见)包含管理查找、保护、安排和跟踪所有可用打印机的直接链接。
图 15.4. 默认情况下通过任何运行 CUPS 的 Linux 计算机的端口 631 可用的 CUPS 浏览器界面

你甚至可以管理一组打印机,以允许有效使用多个设备。这可以是一种组织资源的好方法,例如,确保昂贵的彩色打印仅用于高优先级的工作,草稿文档被迫打印在更便宜、质量较低的打印机上。
CUPS 无痕处理所有脏管理细节。如果你需要将特定的打印机从一台计算机(或网络子网,如果是网络打印机)断开连接,并将其连接到另一台,你只需在主机计算机的 CUPS 界面上进行相关更改即可。适当的路由信息将在一分钟或两分钟内自动更新到网络中。
CUPS 知道哪些打印机可用,因为默认情况下,连接到任何运行 CUPS 的计算机的打印机都会向网络上的所有其他计算机广播其存在。这,连同许多其他配置设置,可以通过/etc/cups/cupsd.conf 文件进行控制。
列表 15.1. /etc/cups/cupsd.conf 配置文件的一部分
LogLevel warn
PageLogFormat
MaxLogSize 0
# Allow remote access
Port 631 *1*
Listen /var/run/cups/cups.sock
# Share local printers on the local network.
Browsing On
BrowseLocalProtocols dnssd
DefaultAuthType Basic
WebInterface Yes *2*
[...]
-
1 你可以更改 CUPS 使用的网络端口。
-
2 如果出于安全考虑需要,你可以完全阻止对 Web 界面的访问。
如果 CUPS 未能识别您的打印机,在搜索互联网(使用打印机的名称和单词 linux)之前,有一些事情可以尝试:
-
检查错误信息。运行
systemctl status cups是查看最新警报的好方法。 -
从命令行运行
lsusb(假设是 USB 打印机)和/或lpinfo -v以确认系统可以看到您的打印机。 -
确保在
/etc/cups/printers .conf文件中没有重复或过时的条目。如果有,关闭 CUPS(systemctl stop cups),保存原始文件的副本,然后删除任何旧条目。再次启动 CUPS,并尝试从浏览器界面添加打印机。 -
确保在
/etc/cups/cupsd.conf文件的<Policy default>部分没有过于严格的设置,这些设置可能会阻止合法请求。
摘要
-
应该使用 Linux 工具(如
lshw)提供的硬件配置的可见性和洞察力来辅助所有硬件升级和维修决策。 -
Linux 内核通过隔离系统活动来保护自己免受不稳定变化的影响,但内核模块可以安全地为硬件设备提供对内核资源的动态访问。
-
Linux 内核可以通过在启动时添加参数来修改,无论是通过编辑配置文件还是通过 GRUB 菜单。
-
CUPS 提供了一个网络管理打印机的接口。
关键术语
-
内核模块 是一种软件,它为 Linux 内核定义了硬件设备的属性,以供其使用。
-
内核参数 是在运行时添加到内核的参数,用于控制系统行为。
安全最佳实践
使用 /etc/cups/cupsd.conf 文件来控制对打印机的网络访问。
命令行审查
-
lshw -c memory(或lshw -class memory)显示系统硬件配置的内存部分。 -
ls /lib/modules/`uname -r`列出/lib/modules/目录下包含当前活动内核模块的内容。 -
lsmod列出所有活动模块。 -
modprobe -c列出所有可用的模块。 -
find /lib/modules/$(uname -r) -type f -name ath9k*在可用的内核模块中搜索以 ath9k 开头的文件。 -
modprobe ath9k将指定的模块加载到内核中。 -
GRUB_CMDLINE_LINUX_DEFAULT="systemd.unit=runlevel3.target"(在/etc/default/grub文件中)以多用户、非图形会话方式加载 Linux。 -
lp -H 11:30 -d Brother-DCP-7060D /home/user/myfile.pdf在 UTC 时间 11:30 调度打印任务到 Brother 打印机。
测试自己
1
什么是最简单的方法来直观地查看计算机的完整硬件配置?
lsmodlshw -class memorylshw -html > lshw-output.htmlmodprobe -C2
最好的方法来引用包含活动内核模块的文件系统位置是什么?
/lib/kernel/uname -a/lib/modules/name -r/usr/modules/uname -r/lib/modules/uname -r3
以下哪个命令将禁用内核模块?
delmod uvcvideormmod uvcvideomodprobe -d uvcvideormmod -r uvcvideo4
您需要向 Linux 内核传递一个参数,使其立即且永久生效。您应该采取哪些步骤?
- 在引导时,编辑 GRUB 菜单的“编辑”菜单中的 linux 行,使用 Ctrl-x 保存文件,然后引导系统。
- 将参数添加到 /etc/default/grub 文件中,更新 GRUB,然后重启系统。
- 从命令行升级 GRUB,重启系统,在引导时编辑 GRUB 菜单的“编辑”菜单中的 linux 行,使用 Ctrl-x 保存文件,然后引导系统。
- 重启系统,从命令行升级 GRUB,并将参数添加到 /etc/default/grub 文件中。
5
以下哪个命令将在十点半安排打印作业?
lpd 10:30 -d Brother-DCP-7060D /home/user/myfile.pdflpq -h 10:30 -d Brother-DCP-7060D /home/user/myfile.pdflp -T 10:30 -d Brother-DCP-7060D /home/user/myfile.pdflp -H 10:30 -d Brother-DCP-7060D /home/user/myfile.pdf
答案键
1.
c
2.
d
3.
b
4.
b
5.
d
第十六章. DevOps 工具:使用 Ansible 部署脚本化服务器环境
本章涵盖
-
使用编排工具自动化分层 Linux 部署
-
使用 Ansible 剧本管理 Linux 服务器
-
在模块化架构中组织部署相关数据
你已经看到了脚本如何自动化复杂且无聊的过程,以确保它们定期且正确地完成。你曾在第五章中编写脚本以帮助备份工作。你也在第二章中看到了虚拟 Linux 服务器如何在几秒钟内配置和启动。有什么理由你不能将这些工具组合起来,自动化整个虚拟基础设施环境的创建呢?没有。完全没有理由。
你是否想要这样做?好吧,如果你和你的团队参与了一个涉及多个开发者定期将软件版本推送到多个服务器的 IT 项目,那么你可能需要认真考虑一下,尤其是如果你有计划采用图 16.1 中展示的 DevOps 项目管理方法之一。
图 16.1. 典型的 DevOps 周期将反馈、测试和监控纳入开发过程。

什么是 DevOps?它是一种通过项目开发、质量保证(QA)和系统管理团队之间的紧密协作来组织科技公司和组织工作流程的方法。目标是使用模板(基础设施即代码)来加快部署和软件更新周期,并允许更高水平的流程自动化和监控。
许多自动化收益将通过智能实施像 Ansible 这样的编排工具来实现。能够将新的或更新的代码插入到一个虚拟装配线上,所有底层基础设施和兼容性细节都无形中得到了处理,这无疑可以加快速度。但它也可以大大提高质量并减少错误。
因为大多数 DevOps 操作都是建立在 Linux 基础设施之上的,而且系统管理员在流程中的重要性不亚于开发者,所以你的 Linux 职业生涯迟早会接触到 DevOps。在结束这本书之前,了解一点 DevOps 和编排的世界会是个不错的选择。
想象一下,您负责一个像图 16.2 中展示的复杂平台。这包括单独的应用程序、数据库和认证服务器,所有这些都在开发、生产和备份环境中进行了复制。开发服务器为您提供了一个安全的地方来测试您的代码,在将其推送到生产环境之前,备份服务器可以在生产服务器崩溃时投入使用。您的开发人员不断努力添加功能和修复错误。他们定期将新代码推送到生产周期中。此外,您运行的服务器数量不断变化,以满足不断上升和下降的用户需求。
图 16.2. 一个典型的应用程序开发环境,由开发团队定期提供的常规软件更新所驱动

在如此多的代码在多个方向上飞来飞去的情况下,您将需要一些帮助来保持一切井然有序。
16.1. 部署编排器能为您做什么
部署编排器在旧式的裸金属服务器上施展它们的魔力时会非常满意,但只有当您将它们融入虚拟化部署中时,您才能享受到它们的全部力量。鉴于在您自己的硬件上或使用像 AWS 这样的云服务提供商的资源上创建虚拟服务器的脚本化过程是多么容易,能够自动化为您的虚拟机创建软件堆栈只会增加速度和效率。
理念是您编写一个或多个文本文件,其内容声明了您希望指定机器(通常称为主机)上所有系统和应用程序软件的精确状态。当运行时,编排器将读取这些文件,登录到适当的宿主或宿主机,并执行所有必要的命令以实现所需的状态。您不必在您启动的每个宿主机上手动进行繁琐且容易出错的流程,而是告诉编排器为您完成所有这些工作。一旦您的基础设施扩展到数十个甚至数千个宿主机,这种自动化不仅方便,而且是必不可少的。
但如果这一切都是关于自动化文件系统操作,为什么不用您已经拥有的 Bash 脚本技能呢?好吧,您可能可以,但一旦您开始尝试将这些脚本与远程认证和冲突的软件堆栈等元素结合起来,您的生活很快就会变得疯狂复杂。
编排器将安全可靠地为你管理变量和密码,并在适当的情况下以必要的方式和次数应用它们。你不需要自己跟踪所有细节。由于有各种各样的编排工具,你选择的工具将主要取决于你项目的具体细节、组织背景。你需要问自己一些基本问题:“大多数参与人员会是开发者还是 IT 专业人士?” “你将使用持续集成方法吗?” 表 16.1 提供了四个主要参与者的快速简略介绍。
表 16.1. 流行的部署编排器
| 工具 | 功能 |
|---|---|
| --- | --- |
| Puppet | 广泛的社区支持 |
| 推荐一些编码技能 | |
| 可使用 Ruby 扩展 | |
| 需要在所有客户端上安装代理 | |
| Chef | 与 Git 集成 |
| 推荐一些编码技能 | |
| 可使用 Ruby 扩展 | |
| 学习曲线高 | |
| 广泛的社区支持 | |
| 需要在所有客户端上安装 chef-client | |
| Ansible | 系统管理员友好 |
| 基于 Python | |
| 无需代码,无需基于主机的代理 | |
| 简单、快速通过 SSH 连接 | |
| 通过基于文本的文件(称为 playbooks)运行 | |
| 学习曲线最小 | |
| Salt | 通过代理(称为 minions)工作 |
| 高度可扩展 | |
| 系统管理员友好 |
作为系统管理员,Ansible 对我来说听起来是一个赢家,所以我们将在本章的剩余部分集中关注它。但你的具体项目和期望可能不同。
16.2. Ansible:安装和设置
在开始之前,你需要在 Ansible 服务器以及你计划用作主机的所有机器上安装最新的 Python 版本。无论是使用apt install python还是yum install python都可以完成这项工作。无论你使用哪个版本的 Python(意味着 Python 2 或 3),确保python --version可以从命令行工作。
注意
到目前为止,Ansible 通常使用较旧的 2.7 版本的 Python 表现更好。然而,这可能不会是一个长期的状态。
为了在服务器(或控制机器)上安装 Ansible,你需要启用 EPEL 仓库(针对 CentOS 6)、 Extras 仓库(针对 CentOS 7),或者 Ubuntu 的 ppa:ansible 仓库。然而,在 Ubuntu 上使用add-apt-repository命令启用该仓库之前,你可能需要安装软件包software-properties-common。操作步骤如下:
# apt install software-properties-common
# add-apt-repository ppa:ansible/ansible
# apt update
# apt install ansible
最后,启动两个或三个 Python 就绪的 LXC 容器作为主机(或节点),这些是执行所有工作的生物。不需要在任何主机上安装 Ansible,只需在控制机器上安装即可。
16.2.1. 设置无密码访问主机
让我们看看如何设置对主机的无密码访问。Ansible 倾向于通过 SSH 连接来完成其工作。尽管从命令行处理身份验证是可能的,但将 SSH 密钥发送到主机以实现无密码访问要远远好于从命令行处理身份验证。你记得这是从 第三章 中学到的,但这里再重复一遍:
$ ssh-keygen *1*
$ ssh-copy-id -i .ssh/id_rsa.pub ubuntu@10.0.3.142 *2*
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s),
to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed --
if you are prompted now it is to install the new keys
ubuntu@10.0.3.142's password: *3*
-
1 如果你本地系统上还没有密钥对,请运行此命令。
-
2 你要复制服务器 SSH 密钥的主机机器的登录名和 IP 地址
-
3 你需要为主机机器用户账户输入密码以授权密钥传输。
现在,Ansible 已经正确安装并连接到你的主机,是时候配置你的环境了。
16.2.2. 组织 Ansible 主机
Ansible 从位于 /etc/ansible 目录下的名为 hosts 的清单文件中获取有关要管理的哪些主机的信息。该文件可以是 IP 地址或域名的一个简单列表,或者两者的组合。
列表 16.1. 一个简单的 /etc/ansible/hosts 文件示例
10.0.3.45
192.168.2.78
database.mydomain.com
但是,随着你期望 Ansible 管理的主机数量以及你整体环境的复杂性增长,你可能想要更好地组织这些事情。实现这一目标的一种方法是将你的主机划分为主机组,然后可以对它们执行精确的 Ansible 操作。
列表 16.2. 一个组织成主机组的 /etc/ansible/hosts 文件示例
[webservers]
10.0.3.45
192.168.2.78
[databases]
database1.mydomain.com
使用主机组,Ansible 任务可以配置为仅针对定义良好的主机子集运行,例如,仅向 Web 服务器发送更新的公共网页,向数据库发送新的配置文件(如图 16.3 所示)。
图 16.3. 推送到主机组中特定任务的更新

可以应用于 hosts 文件的控制还有很多。你会发现,在安装过程中在 /etc/ansible/ 中创建的默认 hosts 文件已经包含了一些很好的语法建议,例如,如何在单行中引用多个主机名:www[001:006].example.com。
注意
因此,你将能够跟随本章中的演示,将你的 Python 准备好的 LXC(或其他)主机的 IP 地址添加到 hosts 文件中。
16.2.3. 测试连接性
为了测试设置是否正确,Ansible 可以尝试联系 hosts 文件中列出的主机。这个命令以所谓的 ad hoc 模式从命令行运行 Ansible。-m 参数告诉 Ansible 加载并运行 ping 模块,向 hosts 文件中列出的所有主机发送简单的“你在吗?”请求:
$ ansible all -m ping
10.0.3.103 | SUCCESS => {
"changed": false,
"ping": "pong"
}
该命令中的 all 条件表示你希望此操作在 hosts 文件中列出的所有主机上执行。如果你只想 ping 一个特定的主机组,你将使用组名而不是 all:
$ ansible webservers -m ping
现在你已经连接上了,你可以远程运行简单的命令。这个例子将/etc/group 文件复制到每个主机的家目录中。记住,你之所以能够不提供认证就完成这项操作,是因为你之前使用了ssh-keygen将你的 SSH 密钥保存到了远程主机上:
$ ansible all -a "cp /etc/group /home/ubuntu"
你可以通过在 SSH 上运行ls来确认操作是否成功:
$ ssh ubuntu@10.0.3.103 "ls /home/ubuntu"
如果你登录到 Ansible 服务器上的账户的用户名与你的主机上的用户名不同,你需要告诉 Ansible。你可以通过命令行使用--user参数来做到这一点,假设主机用户名是 ubuntu,它看起来像这样:ansible --user ubuntu all -m ping。
16.3. 认证
现在假设你需要在你远程主机上执行需要 sudo 权限的命令。想象一下,你想要将更新的.html 文件推送到你负载均衡器后面辛勤工作的数十个 Web 服务器上。一次性完成这个操作,而不是为每个主机单独重复操作,确实很有意义。
什么是负载均衡器?
如果你对这个好奇,负载均衡器是一个服务器或网络路由器,它接收访问服务的请求并将这些请求重定向到多个应用服务器。负载均衡器擅长在服务器之间分配需求,以确保没有一台服务器过载,并且能够将请求从不健康或不可用的服务器上移开。两个广泛使用的开源 Linux 负载均衡软件包是 HAProxy 和 nginx,除了其 Web 服务器功能外,nginx 也用于负载均衡。
为什么不自己试试?看看当你尝试使用 copy 模块将本地家目录中的文件(可能是你之前复制到那里的 group 文件)复制到远程主机的/var/www/html 目录时会发生什么。如果你的主机没有预先设置/var/www/html 目录,你可以通过替换任何不属于你的用户的系统目录(如/etc/)来产生相同的效果:
$ ansible webservers -m copy -a "src=/home/ubuntu/group \
dest=/var/www/html/" *1*
10.0.3.103 | FAILED! => {
"changed": false,
"checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
"failed": true,
"msg": "Destination /var/www/html not writable" *2*
}
-
1 src=指向本地机器上源文件的位置;dest=指向主机上的目标位置。
-
2 一个描述性的错误消息,解释出了什么问题
哎呀。 “目标/var/www/html 不可写”听起来像是一个权限问题。看起来你需要找到一种方法来提升你的权限。最好的方法是通过/etc/ansible/ansible.cfg 文件中的设置。正如以下示例所示,我通过取消注释 ansible.cfg 文件中的四行来编辑了[privilege_escalation]部分。
列表 16.3. /etc/ansible/ansible.cfg 中的更改设置
[privilege_escalation]
become=True
become_method=sudo
become_user=root
become_ask_pass=True
当你再次运行 copy 操作时,这次添加了--ask-become-pass参数,Ansible 会读取更新的配置文件并提示输入远程 ubuntu 用户的 sudo 密码。这次你会成功:
$ ansible --ask-become-pass webservers -m copy -a "src=/home/ubuntu/group \
dest=/var/www/html/"
SUDO password:
10.0.3.103 | SUCCESS => {
"changed": true,
"checksum": "da39a3ee5e6b4b0d3255bfef95601890afd80709",
"dest": "/var/www/html/stuff.html",
"gid": 0,
"group": "root",
"md5sum": "d41d8cd98f00b204e9800998ecf8427e",
"mode": "0644",
"owner": "root",
"size": 0,
"src": "/home/ubuntu/.ansible/tmp/
ansible-tmp-1509549729.02-40979945256057/source",
"state": "file",
"uid": 0
}
登录到你的远程服务器以确认文件已被复制。顺便说一句,从安全的角度来看,将你的组文件留在 web 根目录里是一个糟糕的主意。这只是一个例子。请不要将其留在那里。
16.4. Ansible playbooks
正如你所看到的,你可以在一两分钟内启动一些基本的 Ansible 活动。但那些基础知识不会让你走得太远。如果你想利用工具的真正力量,使其能够编排我在章节引言中描述的那种自动化多级基础设施,你需要学习如何使用 playbooks。Playbooks是你紧密定义 Ansible 想要触发的策略和动作的方式。它们也是共享工作配置配置文件的一种简单方法。这里有你可以使用 playbook 的两种方式:
-
作为简单的独立脚本
-
作为指向一个特别结构的目录树中分布的资源(用于更复杂的环境)的参考
16.4.1. 编写简单的 playbook
让我们学习如何创建一个简单的 playbook,它可以一次性部署一个相对简单的 web 服务器。为此,你需要使用模块(如之前看到的 copy 模块),运行 Linux 系统操作的 tasks,以及用于动态响应系统事件的 handlers。首先,确保你的/etc/ansible/目录下的 hosts 文件是最新的。
列表 16.4. 一个简单的/etc/ansible/hosts 文件
10.0.3.103
10.0.3.96
接下来,你需要创建一个名为 site.yml 的 YAML 格式文件。YAML是一种与更广泛使用的 JavaScript Object Notation (JSON)相关的文本格式化语言。虽然你需要小心地正确缩进,但 YAML 格式确实可以生成易于阅读、理解和编辑的配置配置文件。
在文件中以包含三个短横线(---)的行开始后,你的文件将包括三个部分:hosts、tasks和handlers。在这种情况下,hosts部分告诉 Ansible 将 playbook 的操作应用于 hosts 文件中 webservers 组的所有地址。tasks部分(缩进与 hosts 相同数量的空格)介绍了三个 tasks(或modules):apt用于安装 Apache web 服务器,copy用于将本地文件复制到 web 文档根目录,以及service,类似于 systemd 环境中的systemctl,以确保 Apache 正在运行。
列表 16.5. 一个简单的名为 site.yml 的 Ansible playbook
- hosts: webservers *1*
tasks:
- name: install the latest version of apache
apt:
name: apache2 *2*
state: latest *3*
update_cache: yes
- name: copy an index.html file to the web root and rename it index2.html
copy: src=/home/ubuntu/project/index.html *4*
dest=/var/www/html/index2.html
notify:
- restart apache
- name: ensure apache is running
service: name=apache2 state=started
handlers:
- name: restart apache *5*
service: name=apache2 state=restarted
-
1 只在 webservers 主机组中列出的主机上运行任务
-
2 apt 模块在 Ubuntu 机器上安装 apache2 包
-
3 确保 state 属性的最新值确保安装了软件的最新版本。
-
4 需要一个名为 project/的本地目录中存在 index.html 文件**
-
5 处理器名称;尽管它看起来更像是一个描述,但它可以在任务内部用作参考。
要自己测试,你可以创建一个名为 index.html 的简单文件,并将其保存到你的 Ansible 服务器上的一个目录中。(我在我的 Ansible 实验室中使用了 LXC 容器。)确保在剧本中正确引用文件位置(就像上一个剧本示例中的 copy: src= 行那样)。如果你愿意,该文件可以不包含比 Hello World 更复杂的单词。它仅仅是为了确认剧本是否工作。一旦剧本运行,该文件的副本应该存在于网页文档根目录中。
此外,请注意上一个示例中复制任务内的 notify: 行。一旦复制任务完成,notify 将触发名为 restart apache 的处理程序,这将确保 Apache 被重新启动并正常运行。
当你构建自己的剧本时,你肯定需要更多的语法和功能信息。运行 ansible-doc 和特定模块的名称将帮助你入门:
$ ansible-doc apt
运行你的剧本
假设你的 /etc/ansible/ansible.cfg 文件仍然正确配置以处理主机身份验证,你现在可以使用 ansible-playbook 命令来运行你的剧本。默认情况下,该命令将使用 /etc/ansible/hosts 中列出的主机,但你可以使用 -i 来指向不同的文件。以下是一个示例:
$ ansible-playbook site.yml
SUDO password:
PLAY ****************************************************
TASK [setup] ********************************************
ok: [10.0.3.96]
TASK [ensure apache is at the latest version] *** *1*
changed: [10.0.3.96]
TASK [copy an index.html file to the root directory] ****
changed: [10.0.3.96]
TASK [ensure apache is running] *************************
ok: [10.0.3.96] *2*
RUNNING HANDLER [restart apache] ************************
changed: [10.0.3.96]
PLAY RECAP **********************************************
10.0.3.96 : ok=5 changed=3 unreachable=0 failed=0 *3*
-
1 对任务目的的简要总结
-
2 成功消息告诉你任务已成功完成。
-
3 运行剧本的结果摘要
成功!使用这个单一命令,你已经在你的 hosts 文件中列出的所有主机上建立了一个工作的 Web 服务器。你不相信吗?将你的浏览器指向应该由你复制的 index2.html 文件使用的 URL(在我的例子中是 10.0.3.96/index2.html)。你应该能看到你的 index2.html 文件被显示出来。
16.4.2. 创建多级、基于角色的剧本
一旦你的 Ansible 管理的基础设施被层层元素压垮,每个元素都有其详细的参数,将它们全部放在一个剧本脚本中是不切实际的。试着想象一下,管理前面在 图 16.2 中展示的那种平台会是什么样子。
将任务、处理程序和其他数据类型拆分到单独的目录和文件中会使内容更加易于阅读。这种模块化组织也使得构建新的剧本成为可能,而无需重新发明轮子:你将始终能够轻松访问你创建的一切。
Ansible 将其模块化元素组织到角色中,甚至提供自己的命令行工具 ansible-galaxy 来管理现有角色并生成启动新角色所需必要的文件系统框架。图 16.4 展示了基本的 Ansible 拓扑。
图 16.4. 以资源自包含分组形式展示的 Ansible 角色,包括对系统依赖的访问

生成 Ansible 角色
选择一个目录作为你的 Ansible 根目录。如果你在一个容器或 VM 上工作,其整个目的就是作为 Ansible 服务器,那么这也可以是你的主要用户的文档根目录(/home/username/)。从 Ansible 根目录开始,你将创建一个名为 roles 的目录,然后移动到新目录。
一旦到达那里,使用 ansible-galaxy init 初始化目录,然后跟随着你想要用于你的角色的名称:
$ mkdir roles
$ cd roles
$ ansible-galaxy init web-app
- web-app was created successfully
创建了一个名为 web-app 的新目录。运行 ls -R 以递归列出 ansible-galaxy 为你创建的新子目录及其内容:
$ cd web-app
$ ls -R
.:
defaults files handlers meta README.md tasks templates tests vars
./defaults: *1*
main.yml *2*
./files:
./handlers:
main.yml
./meta:
main.yml
./tasks:
main.yml
./templates:
./tests:
inventory test.yml
./vars:
main.yml
-
1 web-app 中的每个子目录都以 ./ 开头。
-
2 一些目录已经包含了自己大量为空的 playbook 文件。
Ansible 如何消费这些目录中的数据?这里有件事值得思考:那些文件中设置的变量值和参数通常会控制 Ansible 使用该特定角色管理资源的方式。
读一遍或两遍。完成了吗?好的,有两件事应该会突出:经常控制(但并不总是?)和那个特定的角色(你是说我可以有其他角色?)。
对,对。添加到你的 web-app 角色目录下文件中的设置可以从顶层 playbook 或通过 ad hoc 命令行操作来调用。例如,你可能在 roles/web-app/defaults/main.yml 文件中定义了一个网络文档根位置,为 webroot_location: /var/www/myroot/。调用 webroot_location 变量将始终返回 /var/www/myroot/ 的值。
除了某些情况外。你看,Ansible 是为包含多个项目的环境设计的。你可能为几个独立的应用程序各自有一个单独的 playbook,还有其他用于公司内部服务的 playbook。没有任何阻止你从单个 Ansible 服务器管理多个应用程序的。这可能意味着你希望特定的变量对于应用程序 x 来说意味着一件事,而对于应用程序 y 来说意味着另一件事。
这就引出了第二个值得注意的点:每个应用程序或服务都可以通过其自己的 Ansible 角色来定义。但是,为了让多个角色能够在单个系统上愉快地共存,你需要一种方法来优先处理它们的重叠变量。Ansible 这样做的方式相当复杂,但我可以总结说,角色 vars/ 目录中的值会覆盖 /defaults 中的值,而使用 -e(或 --extra-vars=)显式设置的值会覆盖其他所有值。
你的 roles/web-app/ 目录中可能包含什么内容?这里有一个简短的列表:
-
vars/ 目录可能包含有关加密密钥文件系统位置的信息。
-
templates/ 目录将包含模板,这些模板旨在以,例如,Apache 配置文件的形式安装,格式为 Python 的 .j2。
-
files/ 目录可能包含其他文件(比如你在上一个例子中复制的那个 .html 文件),这些文件用于基于主机的数据。
16.4.3. 在 Ansible 中管理密码
尽管你可能需要在 Ansible 基础设施中包含主机密码,但你绝不应该以纯文本形式存储它们。永远不要。相反,Ansible 提供了一个名为 Vault 的工具,该工具将敏感数据存储在加密文件中,当需要时,可以通过 playbook 安全地调用。此片段打开一个编辑器,你可以在此处输入新的 Vault 密码:
$ export EDITOR=nano *1*
$ ansible-vault create mypasswordfile
New Vault password: *2*
Confirm New Vault password:
-
1 如果你不希望 Vault 在 Vim 中打开密码文件,可以将编辑器变量导出为 Nano。
-
2 在添加你想要用于主机访问的密码之前,你将被提示输入一个新的 Vault 密码。
假设你的主机都只使用单个密码,这是通过在ansible-playbook命令中添加--ask-vault-pass参数来实现的:
$ ansible-playbook site.yml --ask-vault-pass
Vault password:
供你参考,自从 Ansible 版本 2.3 以来,也可以利用 Ansible 所说的加密变量,这实际上是一个存储在纯文本 YAML 文件中的加密密码。这使得管理多个密码成为可能。
摘要
-
编排工具让你能够大规模自动化服务器基础设施,无论是单个主机还是数千个。你选择的工具将取决于你团队的技能、项目需求和公司文化。
-
Ansible 不需要编写代码,在 SSH 上运行,并且占用资源较少。
-
Ansible playbooks,尤其是通过角色运行资源的 playbooks,是管理和有效利用安全资源的高效方式。
关键术语
-
DevOps是一种项目组织结构,旨在帮助开发和运维团队加快并自动化产品开发周期。
-
编排部署工具让你能够精确地通过自动化脚本基础设施行为来实现所需的状态。
-
主机组是一种组织主机的方式,以便 Ansible 可以指向管理主机群集中的明确子集。
-
在 Ansible playbooks 中,模块是在主机系统上运行的预定义命令序列,处理器是由事件触发的动作,而角色是组织成单一项目服务的捆绑资源。
安全最佳实践
-
将 Ansible 指向使用无密码、基于密钥对的 SSH 访问你的主机,比在每次操作中输入密码更受欢迎。
-
永远不要在 Ansible playbooks 或其他纯文本脚本中包含密码。请使用 Ansible Vault。
命令行审查
-
add-apt-repository ppa:ansible/ansible将 Debian Ansible 软件仓库添加到 apt 中,以便在 Ubuntu/Debian 机器上安装 Ansible。 -
ansible webservers -m ping测试 webservers 主机组中的所有主机以检查网络连接性。 -
ansible webservers -m copy -a "src=/home/ubuntu/stuff.html dest=/var/www/html/"将本地文件复制到 webservers 组中所有主机的指定文件位置。 -
ansible-doc apt显示 apt 模块的语法和用法信息。 -
ansible-playbook site.yml根据 site.yml playbooks 启动操作。 -
ansible-playbook site.yml --ask-vault-pass使用 Vault 密码进行身份验证并执行 playbook 操作。
测试自己
1
以下哪个编排工具最适合对 DevOps 经验较少的开发者团队构建大型且复杂的平台?
- Ansible
- Chef
- Puppet
- Salt
2
以下哪个软件包必须在每个主机上安装,以便 Ansible 能够工作?
- Ansible
- Python
- software-properties-common
- Ansible 和 Python
3
以下哪个设计考虑因素主要是安全关注点?
- 将您的宿主机组织成宿主机组
- 为所有宿主机安排定期的连通性测试
- 分离环境变量
- 在 Ansible Vault 中存储数据
4
哪个命令告诉 Ansible 仅在运行 Apache 的宿主机上自动用本地文件填充默认的 Web 文档根目录?
ansible all -i copy -a "src=/var/www/html/ dest=/home/ubuntu/stuff.html"ansible all webservers -m copy -a "/home/ubuntu/stuff.html /var/www/html/"ansible webservers -m copy -a "src=/home/ubuntu/stuff.html dest=/var/www/html/"ansible webservers -m copy -a src=/home/ubuntu/stuff.html dest=/var/www/html/5
以下哪个命令将创建您为新的 Ansible 角色所需的所有目录和文件?
ansible-root-directory/roles/ansible-galaxy init rolenameansible-root-directory/ansible-galaxy rolenameansible-root-directory/roles/ansible-init rolenameansible-root-directory/roles/ansible init rolename
答案键
1.
b
2.
b
3.
d
4.
c
5.
a
结论
如此一来,我们的旅程即将结束。如果我已经尽了我的责任,而您也尽到了您的责任,那么现在您应该已经掌握了一些严肃的 Linux 技能。实际上,如果您已经练习得足够多,您应该会感到舒适地承担许多常见的服务器管理任务,雇主也应该会感到舒适地雇佣您在他们的服务器上执行这些任务。但在我们分别之前,让我们花几分钟时间回顾一下您所学的知识以及它可能带您走向何方。
您所学的知识
组织和吸收通过《Linux 实战》所采取的众多步骤将是一项挑战。为了使这次复习更有用,我将把您阅读的大部分内容重新整理成六个高级、需求迫切的主题:
-
虚拟化
-
连接性
-
加密
-
网络连接
-
图片管理
-
系统监控
虚拟化
在第二章中与虚拟机和容器一起工作时,您使用了虚拟化来构建沙盒计算机环境,在那里您可以安全地实验新的工具和技术。在第六章和第九章中,您启动了chroot会话来恢复损坏的配置和文件系统,或者重置认证密码。凭借对虚拟化技术的理解,以及第十六章中基础设施编排的介绍,您只需再迈一步就可以深入探索企业云和容器计算的世界。
连接性
远程连接在本书的几乎所有项目中都扮演了重要角色:从远程服务器管理(第三章)到脚本化的归档备份(第四章),从提供 Web 服务器(第七章)和文件共享(第八章)到系统监控(第十一章)。没有许多关键的管理任务可以在没有安全可靠连接的情况下完成。您现在拥有了创建、管理和故障排除这些连接所需的核心 Linux 工具。
加密
严重依赖开放网络如互联网的商业流程将需要方法来确保其数据在传输和静止状态下的安全。在此阶段,您可以通过使用加密工具来满足这一需求。您在第三章中学习了 SSH 会话加密,在第八章中学习了加密网络文件共享,在第九章中学习了网站加密和 TLS 证书,以及在第十章中学习了 VPN。
网络连接
在一个复杂的世界里,你的组织将需要复杂的网络解决方案来让事情发生。(当然,我是指好的事情。)Linux 在网络方面确实承担了它的重量,在这本书中,你学习了如何使用 Linux 作为平台来构建复杂的连接解决方案,如 VPN 和 DMZ(第十章)。你还在第十二章中看到了如何使用 NFS 在私有网络上共享数据,并在第十四章中发现了一整套网络性能优化工具。
图像管理
你看到了完整的文件系统镜像可以用于数据备份和恢复。正如你在第二章中学习到的,镜像对于服务器编排也很重要。你会发现这在基于云的基础设施管理世界中尤其真实和有用。
系统监控
在第十三章中,你探索了持续系统监控的工作原理。你使用监控工具来预见与安全和系统性能相关的问题,这些问题与四个核心计算机元素:CPU、内存、存储和网络有关。现在这一切都回到你脑海中了吗?
接下来是什么
如果你带着自己的具体目标来到这本书,那么请,不要让我阻止你。去做吧。但如果你正在寻找一些更高层次的冒险想法,请考虑以下这些:
-
第一位(第二位和第三位):获取实际动手经验。敲击命令行,不要停下来。用它来管理从简单的文件传输到在连接到桌面上 DVD 上的视频在你的 WiFi 连接的笔记本电脑上查看的一切。作为额外的奖励,当你从命令行运行 VLC 视频应用程序时,你会看到各种进程输出。查看输出可以帮助你弄清楚为什么一部喜欢的电影在观看中途突然失败。
-
拿出一个空的 USB 驱动器,为自己构建一个应急工具包。按照你在第六章中看到的方式加载 Linux live-boot 镜像,并添加一些你最喜欢的故障排除和恢复工具。在你把它收进抽屉或汽车储物箱之前,至少测试一次。
-
深入学习 Bash 脚本。追踪使用事件处理、变量、循环和输入的在线脚本样本。考虑构建脚本来自动化你经常执行的管理任务。Vivek Gite 的 Bash 脚本全面指南是一个良好的起点:
bash.cyberciti.biz/guide/Main_Page。 -
使用 eCryptfs 和 cryptsetup 等工具探索加密存储驱动器的世界。如果你在笔记本电脑或 USB 驱动器上携带敏感数据,那么你将认真考虑如果设备落入错误之手可能会发生什么。
-
如果你拥有编程技能,你可能想打开 Linux 内核并添加一些自定义。当然,在您的更改被接受到官方内核之前,您需要得到 Linus Torvalds 的支持,但您当然可以部署您的分支以供自己使用。这个过程将教会你很多关于 Linux 内部工作原理的知识。
-
将你的 Linux 服务器技能应用到云端世界。现在,既然你了解了推动云基础设施的许多因素,你在 AWS 或甚至 Azure 上将更加高效。不确定从哪里开始?曼宁肯定能为你提供帮助。我的书《一个月午餐时间学习 Amazon Web Services》(2017 年),Wittig 兄弟的《Amazon Web Services in Action,第 2 版》(2018 年),《Azure in Action》(Chris Hay 和 Brian H. Prince,2010 年),以及《一个月午餐时间学习 Azure》(Iain Foulds,2018 年)都是不错的选择。
-
通过 Docker 和 Kubernetes 等技术拥抱容器。考虑到容器带来的速度和可扩展性,它们是企业计算的未来。曼宁的《Kubernetes in Action》(Marko Lukša,2017 年)、《Docker in Action》(Jeff Nickoloff,2016 年)和《Docker in Practice》(Ian Miell 和 Aidan Hobson Sayers,2016 年)都是很好的资源。
资源
什么?你想要更多?好吧。但就这一次。
你当然可以自由地访问我的个人网站 bootstrap-it.com,在那里你可以找到原创内容和我的其他书籍和课程的详细信息,所有这些都经过认证,是 Linux 和云友好的。请随意关注我的 Twitter (@davidbclinton),并成为第一个了解我下一个征服世界的黑暗计划的人。
查看曼宁的《Linux in Action》论坛(forums.manning.com/forums/linux-in-action)。如果你在书的项目中遇到任何问题,或者只是想描述你最新的成功,与社区分享它们。我会密切关注那个论坛,当我需要的时候我会在那里。
上网。几十年来,有很多人一直在为使用 Linux 提供优秀的文档和指南。论坛、博客、IRC 频道、Slack 群组和文档网站太多了,无法在这里一一列举。但 www.tldp.org/FAQ/Linux-FAQ/online-resources.html 是一个很好的起点。
当你寻找帮助时,你的首选应该是你喜欢的互联网搜索引擎。别忘了(令人惊讶的是)man 在互联网上几乎和命令行上一样工作。你在浏览器中搜索 man selinux 的第一个结果应该和你从 shell 中得到的相同 man 文档。
我祝愿你在 Linux 学习中取得巨大成功。保持联系!
DAVID CLINTON
附录。逐章的命令行回顾
1. 欢迎使用 Linux
-
ls -lh /var/log—列出/var/log/目录的内容和完整、人性化的详细信息。 -
cd—返回您的家目录。 -
cp file1 newdir—将名为 file1 的文件复制到名为 newdir 的目录中。 -
mv file? /some/other/directory/—将包含字母file和一个更多字符的所有文件移动到目标位置。 -
rm -r *—删除当前位置下的所有文件和目录—请谨慎使用。 -
man sudo—打开使用 sudo 与命令的 man 文档文件。
2. Linux 虚拟化:构建 Linux 工作环境
-
apt install virtualbox—使用apt从远程仓库安装软件包。 -
dpkg -i skypeforlinux-64.deb—直接在 Ubuntu 机器上安装下载的 Debian 包。 -
wget https://example.com/document-to-download—一个用于下载文件的命令行程序。 -
dnf update—或yum update或apt update—同步本地软件索引与在线仓库中可用的内容。 -
shasum ubuntu-16.04.2-server-amd64.iso—计算下载文件的校验和,以确认其与提供的值匹配,这意味着内容在传输过程中未被损坏。 -
vboxmanage clonevm Kali-Linux-template --name newkali—使用vboxmanage工具克隆现有的虚拟机。 -
lxc-start -d -n mycont—启动现有的 LXC 容器。 -
ip addr—显示系统每个网络接口的信息(包括它们的 IP 地址)。 -
exit—在不关闭机器的情况下离开 shell 会话。
3. 远程连接:安全访问网络机器
-
dpkg -s ssh—检查基于 Apt 的软件包的状态。 -
systemctl status ssh—检查系统进程(systemd)的状态。 -
systemctl start ssh—启动系统进程。 -
ip addr—列出计算机上的所有网络接口。 -
ssh-keygen—生成新的 SSH 密钥对。 -
$ cat .ssh/id_rsa.pub | ssh ubuntu@10.0.3.142 "cat >> .ssh/authorized_keys"—复制本地密钥,并将其粘贴到远程机器上。 -
ssh -i .ssh/mykey.pem ubuntu@10.0.3.142—指定特定的密钥对。 -
scp myfile ubuntu@10.0.3.142:/home/ubuntu/myfile—安全地将本地文件复制到远程计算机。 -
ssh -X ubuntu@10.0.3.142—登录到远程主机以进行图形化会话。 -
ps -ef | grep init—显示所有当前运行的系统进程,并通过字符串init过滤结果。 -
pstree -p—以可视树格式显示所有当前运行的系统进程。
4. 归档管理:备份或复制整个文件系统
-
df -h—以人类可读的格式显示所有当前活动的分区及其大小。 -
tar czvf archivename.tar.gz /home/myuser/Videos/*.mp4—从指定目录树中的视频文件创建压缩归档。 -
split -b 1G archivename.tar.gz archivename.tar.gz.part—将大文件分割成指定最大大小的较小文件。 -
find /var/www/ -iname "*.mp4" -exec tar -rvf videos.tar {} \;—查找满足特定标准的文件,并将它们的名称流式传输到 tar 以包含在存档中。 -
chmod o-r /bin/zcat—移除用户 others 的读取权限。 -
dd if=/dev/sda2 of=/home/username/partition2.img—创建 sda2 分区的镜像,并将其保存到您的家目录中。 -
dd if=/dev/urandom of=/dev/sda1—用随机字符覆盖分区以隐藏旧数据。
5. 自动化管理:配置自动离站备份
-
#!/bin/bash—所谓的“shebang 行”,告诉 Linux 您将使用哪个 shell 解释器来执行脚本。 -
||—在脚本中插入一个“或”,意味着左侧的命令成功或执行右侧的命令。 -
&&—在脚本中插入一个“和”,意味着如果左侧的命令成功,则执行右侧的命令。 -
test -f /etc/filename—检查指定的文件或目录名是否存在。 -
chmod +x upgrade.sh—使脚本文件可执行。 -
pip3 install --upgrade --user awscli—使用 Python 的 pip 软件包管理器安装 AWS 命令行界面。 -
aws s3 sync /home/username/dir2backup s3://linux-bucket3040—同步本地目录的内容与指定的 S3 存储桶。 -
21 5 * * 1 root apt update && apt upgrade—cron 指令,每天早上 5:21 执行两个apt命令。 -
NOW=$(date +"%m_%d_%Y")—将当前日期分配给脚本变量。 -
systemctl start site-backup.timer—激活 systemd 系统定时器。
6. 紧急工具:构建系统恢复设备
-
sha256sum systemrescuecd-x86-5.0.2.iso—计算 .ISO 文件的 SHA256 校验和。 -
isohybrid systemrescuecd-x86-5.0.2.iso—为实时启动镜像添加一个 USB 友好的 MBR。 -
dd bs=4M if=systemrescuecd-x86-5.0.2.iso of=/dev/sdb && sync—将实时启动镜像写入空驱动器。 -
mount /dev/sdc1 /run/temp-directory—将分区挂载到实时文件系统上的一个目录。 -
ddrescue -d /dev/sdc1 /run/usb-mount/sdc1-backup.img /run/usb-mount/sdc1-backup.logfile—将损坏分区上的文件保存到名为 sdc1-backup.img 的镜像中,并将事件写入日志文件。 -
chroot /run/mountdir/—在文件系统上打开 root shell。
7. 网络服务器:构建 MediaWiki 服务器
-
apt install lamp-server^—Ubuntu 命令,安装 LAMP 服务器的所有元素。 -
systemctl enable httpd—在 CentOS 机器的每次系统启动时启动 Apache。 -
firewall-cmd --add-service=http --permanent—允许 HTTP 浏览器流量进入 CentOS 系统。 -
mysql_secure_installation—重置您的 root 密码并加强数据库安全性。 -
mysql -u root -p—以 root 用户登录 MySQL(或 MariaDB)。 -
CREATE DATABASE newdbname;—在 MySQL(或 MariaDB)中创建一个新的数据库。 -
yum search php- | grep mysql—在 CentOS 机器上搜索与 PHP 相关的可用软件包。 -
apt search mbstring—搜索与多字节字符串编码相关的可用软件包。
8. 网络文件共享:构建 Nextcloud 文件共享服务器
-
a2enmod rewrite—启用重写模块,以便 Apache 可以在客户端和服务器之间移动 URL 时编辑它们。 -
nano /etc/apache2/sites-available/nextcloud.conf—为 Nextcloud 创建或编辑 Apache 主机配置文件。 -
chown -R www-data:www-data /var/www/nextcloud/—将所有网站文件的用户和组所有权更改为 www-data 用户。 -
sudo -u www-data php occ list—使用 Nextcloud CLI 列出可用命令。 -
aws s3 ls s3://nextcloud32327—列出 S3 存储桶的内容。
9. 保护你的 Web 服务器
-
firewall-cmd --permanent --add-port=80/tcp—打开端口 80 以允许传入 HTTP 流量,并在启动时配置它重新加载。 -
firewall-cmd --list-services—列出 firewalld 系统上当前活动的规则。 -
ufw allow ssh—使用 Ubuntu 上的 UncomplicatedFirewall 打开端口 22 以允许 SSH 流量。 -
ufw delete 2—删除ufw status命令列出的第二个ufw规则。 -
ssh -p53987 username@remote_IP_or_domain—使用非默认端口登录 SSH 会话。 -
certbot --apache—配置 Apache Web 服务器以使用 Let’s Encrypt 加密证书。 -
selinux-activate—在 Ubuntu 机器上激活 SELinux。 -
setenforce 1—在 SELinux 配置中切换强制模式。 -
ls -Z /var/www/html/—显示指定目录中文件的保密上下文。 -
usermod -aG app-data-group otheruser—将 otheruser 用户添加到 app-data-group 系统组。 -
netstat -npl—在服务器上扫描打开(监听)的网络端口。
10. 确保网络连接安全:创建 VPN 或 DMZ
-
hostname OpenVPN-Server—设置命令提示符描述,以便更容易跟踪你登录的是哪个服务器。 -
cp -r /usr/share/easy-rsa/ /etc/openvpn—将 Easy RSA 脚本和环境配置文件复制到工作 OpenVPN 目录。 -
./build-key-server server—生成名为server的 RSA 密钥对集。 -
./pkitool client—从现有的 RSA 密钥基础设施生成客户端密钥集。 -
openvpn --tls-client --config /etc/openvpn/client.conf—使用 client.conf 文件中的设置在 Linux 客户端上启动 OpenVPN。 -
iptables -A FORWARD -i eth1 -o eth2 -m state --state NEW,ESTABLISHED,RELATED -j ACCEPT—允许 eth1 和 eth2 网络接口之间的数据传输。 -
man shorewall-rules—显示 Shorewall 使用的规则文件的文档。 -
systemctl start shorewall—启动 Shorewall 防火墙工具。 -
vboxmanage natnetwork add --netname dmz --network "10.0.1.0/24" --enable --dhcp on—使用 VirtualBox CLI 创建和配置一个带有 DHCP 的虚拟 NAT 网络,用于 VirtualBox 虚拟机。 -
vboxmanage natnetwork start --netname dmz—启动虚拟 NAT 网络。 -
dhclient enp0s3—从 DHCP 服务器请求 enp0s3 接口的 IP 地址。
11. 系统监控:处理日志文件
-
Alt-F<n>—从非 GUI shell 打开虚拟控制台。
-
journalctl -n 20—显示最近的 20 条日志条目。 -
journalctl --since 15:50:00 --until 15:52:00—仅显示在since和until时间之间的事件。 -
systemd-tmpfiles --create --prefix /var/log/journal—指示 systemd 创建并维护一个持久的日志文件,而不是每次启动时都会被销毁的文件。 -
cat /var/log/auth.log | grep -B 1 -A 1 failure—显示匹配的行以及其前后的一行。 -
cat /var/log/mysql/error.log | awk '$3 ~/[Warning]/' | wc—在 MySQL 错误日志中搜索被分类为警告的事件。 -
sed "s/^[0-9]//g" numbers.txt—删除文件每行的开头数字。 -
tripwire --init—初始化 Tripwire 安装的数据库。 -
twadmin --create-cfgfile --site-keyfile site.key twcfg.txt—为 Tripwire 生成一个新的加密 tw.cfg 文件。
12. 在私有网络上共享数据
-
/home 192.168.1.11(rw,sync)—NFS 服务器 /etc/exports 文件中的一个条目,定义了远程客户端共享。 -
firewall-cmd --add-service=nfs—打开 CentOS 防火墙以允许客户端访问您的 NFS 共享。 -
192.168.1.23:/home /nfs/home nfs—NFS 客户端 /etc/fstab 文件中的一个典型条目,用于加载 NFS 共享。 -
smbpasswd -a sambauser—向现有的 Linux 用户账户添加 Samba 功能(以及一个唯一的密码)。 -
nano /etc/samba/smb.conf—Samba 由服务器上的 smb.conf 文件控制。 -
smbclient //localhost/sharehome—使用 Samba 用户账户登录本地 Samba 共享。 -
ln -s /nfs/home/ /home/username/Desktop/—创建一个符号链接,允许用户通过点击桌面图标轻松访问 NFS 共享。
13. 解决系统性能问题
-
uptime—返回过去 1、5 和 15 分钟的 CPU 负载平均值。 -
cat /proc/cpuinfo | grep processor—返回系统 CPU 处理器的数量。 -
top—显示正在运行的 Linux 进程的实时统计信息。 -
killall yes—关闭所有正在运行的yes命令实例。 -
nice --15 /var/scripts/mybackup.sh—提高 mybackup.sh 脚本的系统资源优先级。 -
free -h—显示系统和可用系统 RAM。 -
df -i—显示每个文件系统的可用和总 inodes。 -
find . -xdev -type f | cut -d "/" -f 2 | sort | uniq -c | sort -n—按父目录计数并显示文件数量。 -
apt-get autoremove—删除旧的未使用的内核头文件。 -
nethogs eth0—使用 eth0 接口显示与网络连接相关的进程和数据传输。 -
tc qdisc add dev eth0 root netem delay 100ms—通过 eth0 接口将所有网络传输速度减慢 100 毫秒。 -
nmon -f -s 30 -c 120—将一系列nmon扫描的数据记录到文件中。
14. 解决网络问题
-
ip addr—列出 Linux 系统上的活动接口。可以是ip a的缩写,也可以是ip address的扩展,由您选择。 -
lspci—列出当前连接到您的计算机的 PCI 设备。 -
dmesg | grep -A 2 Ethernet—在dmesg日志中搜索字符串 Ethernet 的引用,并显示引用及其后续两行输出。 -
ip route add default via 192.168.1.1 dev eth0—手动为计算机设置新的网络路由。 -
dhclient enp0s3—为 enp0s3 接口请求动态(DHCP)IP 地址。 -
ip addr add 192.168.1.10/24 dev eth0—将静态 IP 地址分配给 eth0 接口(在下次系统重启后不会持续)。 -
ip link set dev enp0s3 up—启动 enp0s3 接口(在编辑配置后很有用)。 -
netstat -l | grep http—扫描本地机器,查找在端口 80 上监听的 Web 服务。 -
nc -z -v bootstrap-it.com 443 80—扫描远程网站,查找在端口 443 或 80 上监听的服务。
15. 外设故障排除
-
lshw -c memory(或lshw -class memory)—显示系统硬件配置的内存部分。 -
ls /lib/modules/`uname -r`—列出位于 /lib/modules/ 目录下的内容,该目录包含当前活动内核的模块。 -
lsmod—列出所有活动模块。 -
modprobe -c—列出所有可用的模块。 -
find /lib/modules/$(uname -r) -type f -name ath9k*—在可用的内核模块中搜索以 ath9k 开头的文件。 -
modprobe ath9k—将指定的模块加载到内核中。 -
GRUB_CMDLINE_LINUX_DEFAULT="systemd.unit=runlevel3.target"—/etc/default/grub 文件加载 Linux 为多用户、非图形会话。 -
lp -H 11:30 -d Brother-DCP-7060D /home/user/myfile.pdf—计划在 11:30 UTC 的时间将打印任务发送到 Brother 打印机。
16. DevOps 工具:使用 Ansible 部署脚本化服务器环境
-
add-apt-repository ppa:ansible/ansible—将 Debian Ansible 软件仓库添加到系统中,以便apt在 Ubuntu/Debian 机器上安装 Ansible。 -
ansible webservers -m ping—测试 webservers 主机组中的所有主机以检查网络连通性。 -
ansible webservers -m copy -a "src=/home/ubuntu/stuff.html dest=/var/www/html/"—将本地文件复制到 webservers 组中所有主机的指定文件位置。 -
ansible-doc apt—显示apt模块的语法和用法信息。 -
ansible-playbook site.yml—根据 site.yml playbook 启动操作。 -
ansible-playbook site.yml --ask-vault-pass—使用 Vault 密码进行身份验证并执行 playbook 操作。
Linux in Action 主题
| Chapter | 技能领域 | 工具 |
|---|---|---|
| 1 | 欢迎使用 Linux | Shell、分区和文件系统 |
| 2 | Linux 虚拟化:构建安全简单的 Linux 工作环境 | 虚拟化、文件系统 |
| 3 | 远程连接:安全访问网络机器 | 安全性、远程连接 |
| 4 | 归档管理:备份或复制整个文件系统 | 分区和文件系统、文本流 |
| 5 | 自动化管理:配置自动远程备份 | 脚本、系统进程管理、安全 |
| 6 | 紧急工具:构建系统恢复设备 | 分区和文件系统、设备管理 |
| 7 | 网络服务器:构建 MediaWiki 服务器 | 数据库、网络、软件包管理 |
| 8 | 网络文件共享:构建 Nextcloud 文件共享服务器 | 软件包管理、网络、安全 |
| 9 | 保护您的网络服务器 | 网络、安全、系统监控 |
| 10 | 保护您的网络连接:创建 VPN 或 DMZ | 网络、安全 |
| 11 | 系统监控:处理日志文件 | 系统监控、文本流、安全 |
| 12 | 在私有网络上共享数据 | 网络、分区、文件系统 |
| 13 | 故障排除系统性能问题 | 系统监控、系统进程管理、网络 |
| 14 | 网络问题故障排除 | 网络 |
| 15 | 故障排除外围设备 | 设备管理 |
| 16 | DevOps 工具:使用 Ansible 部署脚本化服务器环境 | 脚本、虚拟化 |
网络故障排除工作流程




virtualbox.repo
浙公网安备 33010602011771号