精通 Ubuntu 服务器(二)

原文:annas-archive.org/md5/118307b747c51933266231837979f8d8

译者:飞龙

协议:CC BY-NC-SA 4.0

第六章:提高命令行效率

在本书中,我们一直在大量使用命令行。通过使用 shell,我们安装了软件包、创建了用户、编辑了配置文件等等。在上一章中,我们探讨了文件管理,以进一步提升我们的终端技能。这一章,我们将专门用一整章的内容讲解 shell,目标是提高我们的效率。在这里,我们将利用已经掌握的知识,加入一些有用的节省时间的技巧,介绍循环、变量等内容,甚至还会编写脚本。

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

  • 理解 Linux shell

  • 理解 Bash 历史

  • 学习一些有用的命令行技巧

  • 理解变量

  • 编写简单脚本

  • 将所有内容结合起来:编写 rsync 备份脚本

让我们从进一步讨论 Linux shell 开始,这将帮助我们更好地理解在输入命令时与服务器的交互方式。

理解 Linux shell

当谈到 Linux shell 时,理解这个术语的真正含义非常重要。我们在本书中反复使用命令行,但至今我们还没有正式讨论过实际输入命令的接口。

本质上,我们一直在通过一个命令解释器输入命令,这个命令解释器被称为Bourne Again Shell,简称Bash。Bash 只是你可以用来输入命令的许多不同shell之一。

还有其他选择,包括ZshFishksh,但是 Bash 是大多数 Linux 发行版的默认命令 shell。它甚至可以在 macOS 上使用(尽管在该平台上默认的是 Zsh),通过安装 Windows Subsystem for Linux(WSL)还可以在 Windows 上使用。因此,通过理解 Bash 的基础知识,你的知识将与其他发行版和平台兼容。虽然学习其他 shell(如 Zsh)也很有趣,但如果你刚开始使用,Bash 无疑是最值得集中精力的。

你可能会想知道,那么,在哪儿配置你的用户账户使用的 shell。回想一下第二章《管理用户和权限》,我们查看过 /etc/passwd 文件。相信你还记得,这个文件保存了系统上所有用户账户的列表。你可以通过输入以下命令,查看这个文件来刷新一下记忆:

cat /etc/passwd 

这将产生类似图 6.1中所示的输出:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_06_01.png

图 6.1:示例 /etc/passwd 文件的最后几行

你看到每个条目的最后一个字段了吗?那是我们配置用户登录或启动新终端会话时启动哪个 shell 的地方。除非你已经更改过,否则你账户的条目应该是 /bin/bash

你会在这个文件中看到其他变体,例如/bin/false/usr/sbin/nologin。这些实际上是无效的 shell,当某个用户的默认 shell 设置为这些时,就会阻止该用户登录系统。虽然设置一个阻止登录的 shell 看起来很奇怪,但这种做法其实相当常见——并不是所有的用户账户都需要登录服务器。

系统账户与普通用户账户并存,这些账户是为后台工作创建的。系统用户不需要实际登录系统来执行工作,因此通常会进一步将系统用户的 shell 设置为无效的 shell,这样即使该账户被外部威胁者控制,也无法用来登录服务器(账户能做的事越少,就越安全)。

Shell 程序本身负责读取你输入的命令,并让 Linux 内核执行它们。某些 shell,特别是 Bash,具有额外的功能,如history,这些功能对于管理员非常有用。

理解 Bash 历史

说到 history,让我们深入探讨这个概念。默认情况下,Bash 会记录你在会话期间输入的所有命令,这样你就可以在需要时回忆起之前输入的命令。History 还有另一个用途,就是查看其他用户的活动。不过,由于用户可以编辑自己的历史记录来掩盖痕迹,因此这个功能在这个方面并不总是有用的。

如果你曾经按过 shell 中的上下箭头来回忆之前使用过的命令,你可能已经见过 Bash 的历史功能。如果你还不知道可以这么做,试试看吧。你会发现,按上下箭头可以浏览你之前使用过的命令。

另一个技巧是,你也可以在 shell 中直接输入history,并查看之前输入过的命令列表,如图 6.2所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_06_02.png

图 6.2:history 命令的输出

此时,你可以从这个列表中复制并粘贴一个之前使用过的命令再次执行。实际上,还有一种更简单的方法。你注意到每个命令左侧的数字了吗?我们可以利用这个数字快速回忆起之前使用过的命令。在我的截图中,项566是我运行sudo apt update时的位置。如果我想再次运行相同的命令,只需输入以下命令:

!566 

在这种情况下,我只输入了四个字符,就能够回忆起之前使用过的命令,这个命令执行的操作与输入以下内容相同:

sudo apt update 

这节省了很多输入,真是太好了,因为我们管理员希望尽可能少打字(除非我们在写书)。

让我们来看一些额外的历史命令。首先,如果我们想从历史记录中删除某个条目,我们可以简单地执行以下命令:

history -d 566 

在这个例子中,我们从 Bash 的历史记录中删除了条目563。要删除其他历史记录条目,只需将563替换为我们想要删除的条目的编号。你可能会想,为什么从历史记录中删除某些内容是必要的。这个问题的答案很简单:有时我们会犯错。也许我们打错了什么,而我们不希望某个初级管理员查看历史记录并重新执行一个无效的命令。更糟糕的是,如果我们不小心将密码保存到了历史记录中,那么它将会被所有人看到。我们肯定会想要删除那条记录,这样密码就不会以明文形式保存在历史文件中。一个非常常见的例子是 MySQL 或 MariaDB。当你进入 MySQL 或 MariaDB 的 shell 时,可以使用-p选项并在一行中输入密码。它可能是这样子的:

mariadb -u root -pSuperSecretPassword 

这个命令看起来很有用,因为它可以让你通过一个命令以数据库服务器的root用户身份登录。然而,这也是我非常不喜欢的一点——我真的不喜欢有人在命令中直接写明密码。将root密码保存在 shell 历史记录中是一个巨大的安全隐患。这只是你不希望出现在 Bash 历史记录中的一个例子。我在这里的主要目的是提醒你,在输入命令时要考虑安全。如果你的命令历史中有潜在的敏感信息,你应该将其删除。事实上,你实际上可以输入一个命令,但不将它保存在历史记录中。只需在命令前加一个空格。如果你这样做,它将不会被记录在历史文件中。试试看,亲自体验一下吧。

在 Bash 中,前缀为空格的命令被忽略,实际上是 Ubuntu Server 默认启用的自定义选项。并非所有的发行版都默认启用此功能。如果你使用的发行版没有默认启用这个功能,可以将以下内容添加到你的.bashrc文件中(我们稍后会详细讲解这个文件)。

HISTCONTROL=ignoreboth 

这一配置行还会导致重复的命令不被写入历史记录文件,从而可以压缩历史记录文件。

那么,你可能会问,这些历史记录信息到底存储在哪里呢?查看一下.bash_history文件,它位于你的主目录中(对于root用户来说是/root目录)。当你退出 shell 时,历史记录会被复制到这个文件中。如果你删除这个文件,实际上就是清空了历史记录。但我不建议你养成删除它的习惯。保留命令历史记录非常有用,特别是当你可能不记得上次是如何解决某个问题的时候。Bash 中的历史记录可以帮助你避免重复查找命令。要了解更多关于history命令的功能,查看相关的 man 页面,命令是man history

学习使用命令行的新技巧,使我能更高效地工作,这是一个很棒的感觉,至少对我来说是这样。在接下来的部分,我们将探讨一些在使用 shell 时可以利用的有用技巧。

学习一些有用的命令行技巧

利用 shell 提高生产力的技巧是我最喜欢的事情之一,和音乐、电子游戏、健怡可乐一样。没有什么比你发现一个可以节省时间或提高效率的有用功能时的感觉更好了。我在这条路上发现了许多事情,真希望我早些知道它们。我写这本书的目标之一,就是教你我学得比我愿意承认的还要慢的那些东西。在这一部分,按顺序不分先后,我将介绍一些提高我工作流的技巧。

首先,在终端输入 !!(两个感叹号)将重复你上次使用的命令。单独使用这个功能看起来可能没什么特别的。毕竟,你可以按一次上箭头键,然后按 Enter 来回忆上一个命令并执行它。但是,当与 sudo 一起使用时,!! 变得更有趣了。想象一下,你输入了一个需要 root 权限的命令,但忘记加上 sudo。我们都犯过这个错误。实际上,直到我写这本书的时候,我已经用了 20 年的 Linux 了,但我仍然时不时忘记使用 sudo。当我们忘记加 sudo 时,我们必须重新输入命令。或者,我们可以直接这么做:

sudo !! 

就这样,你用 sudo 前缀加上了之前使用的命令,而不必重新输入它。

说到避免不必要的输入,一个非常简单(但极其有用)的功能是Tab 完成。通常,Bash shell 可以自动完成你输入的部分命令。如果你开始输入命令或路径的几个字符,按下键盘上的Tab键,如果你输入的字符足以缩小结果范围,shell 会为你完成路径。你也可以连续按两次Tab,以查看与已输入字符匹配的所有可能选项。试试看吧。举个例子,你可以输入 ls 和你主目录的路径,故意漏掉一些字符,然后按下Tab,看看命令是否会自动完成。例如,我可以在终端输入以下内容:

ls /home/j 

然后按下 Tab,它就自动完成了命令:

ls /home/jay 

此外,还有一些特殊的键盘快捷键可以帮助你更快速地导航命令行。下面是一个包含一些最有用的键盘快捷键的表格:

键盘快捷键结果
Ctrl + a将光标移动到行首
Ctrl + e将光标移动到行尾
Ctrl + l清屏
Ctrl + k删除光标到行尾的字符
Ctrl + u删除当前行输入的所有内容(在输入密码时也能清除文本)
Ctrl + w删除光标左侧的一个单词

进一步探讨命令历史,我们还可以在 shell 中按 Ctrl + r 来启动搜索。按下这两个键后,我们可以开始输入一个命令,并且会看到一个与输入内容匹配的命令预览,随着输入更多字符,这个预览会进一步缩小范围。这是我很难描述的事情,截图也不太有帮助,所以不妨试试看。例如,按下 Ctrl + r 然后开始输入 sudo apt。你上次使用该命令时的记录应该会出现,你可以再次按 Ctrl + r,这样会看到包含这些字符的历史命令的更多实例。当你熟练掌握这一技巧时,它实际上比 history 命令更高效,但这需要一些时间来适应。

另一个有趣的技巧是,在文本编辑器中编辑你之前输入的命令。我知道这听起来很奇怪,但请耐心听我解释。假设你按了上箭头键,输入了一个非常长的命令,而你只想编辑其中的一部分,而不必执行整个命令,比如像这样:

sudo apt update && sudo apt install apache2 

假设你想安装 nginx 而不是 apache2,但其余的命令是正确的。如果你按住 Ctrl 然后按 x 再按 e,命令会在文本编辑器中打开。在那里,你可以更改命令。修改完毕后,一旦保存文件,命令就会执行。坦白说,这通常只有在你有非常长的命令,需要更改其中的一部分时才有用。虽然这有点奇怪,但计算机本身也挺奇怪的。

你注意到之前命令中的两个 & 符号了吗?这是另一个有用的技巧;你实际上可以将多个命令链接在一起。在之前的命令示例中,我们告诉 shell 执行 sudo apt update。接着,我们告诉 shell 执行 sudo apt install apache2。双重和号被称为逻辑 AND 运算符,因此第二个命令只有在第一个命令成功时才会执行。如果第一个命令成功,第二个命令会紧接着执行。另一种链接命令的方法是:

sudo apt update; sudo apt install apache2 

与分号的区别在于,我们告诉 shell 执行第二个命令,无论第一个命令是否成功。你可能会想,什么才算 shell 上的成功呢?一个显而易见的答案可能是“没有错误信息就是成功”。虽然这通常是对的,但 shell 使用退出码来编程地标识成功或失败。你可以通过在上一个命令完成后立即输入以下命令来查看命令的退出码:

echo $? 

0的退出代码表示成功;其他任何代码则表示某种错误。不同的程序会为不同类型的失败分配不同的代码,但0始终表示成功。通过这个命令,我们实际上是在打印一个变量的内容。$?实际上是一个变量,在这种情况下它仅用于存储退出代码。echo命令本身可以用于向终端打印文本,但它常常被用来打印变量的内容(我们将在理解变量部分详细讨论这个)。

现在,到了我最喜欢的节省时间的小技巧——命令别名的时候了。别名的概念很简单:它允许你创建一个命令,实际上是另一个命令的别名。这让你可以将命令简化为一个单词或几个字母。比如,考虑这个命令:

alias install="sudo apt install" 

当你输入前面的命令时,实际上不会有任何输出。但发生的事情是你现在拥有了一个新命令——install。这个命令通常是不可用的,你刚刚通过这个命令创建了它。

你可以通过运行alias命令来验证别名是否成功创建,这会显示当前在 shell 中存在的别名列表。如果你创建了新的别名,你应该能在输出中看到它。你还会看到输出中出现你没有创建的其他别名,这是因为 Ubuntu 默认设置了一些别名。事实上,连ls命令本身也是一个别名!

创建这个新的别名后,每次在命令行中执行install时,实际上是在执行sudo apt install。现在,安装软件包变得更简单了:

install tmux 

就这样,你安装了tmux。你不需要输入sudo apt install tmux,你只是将命令中的前三个词简化成了install。事实上,你甚至可以将它进一步简化:

alias i='sudo apt install' 

现在,你可以使用以下命令安装软件包:

i tmux 

使用别名,你可以发挥很大的创造力。这是我个人最喜欢的一些别名。

查看消耗 CPU 最多的前 10 个进程:

alias cpu10='ps -L aux | sort -nr -k 3 | head -10' 

查看消耗 RAM 最多的前 10 个进程:

alias mem10='ps -L aux | sort -nr -k 4 | head -10' 

查看所有已挂载的文件系统,并以干净的标签页布局展示信息:

alias lsmount='mount | column -t' 

通过简单地输入c来清除屏幕:

alias c=clear 

你能想出其他的别名吗?想一想你可能经常使用的命令,并简化它。

但是有一个问题,那就是当你退出终端窗口时,你的别名会被清除。如何保留它们呢?这就引出了我的下一个提高效率的小技巧,编辑你的.bashrc文件。这个文件位于你的主目录中,并且每次你启动一个新的终端会话时都会被读取。你可以在其中添加所有的alias命令;只需将它们添加到文件中的某个位置(例如,文件末尾)。你需要包括整个命令,从alias开始,到命令的引号结束。如果你想复制我的示例别名,你可以将以下几行添加到你的.bashrc文件中的某个位置:

alias i='sudo apt install'
alias cpu10='ps -L aux | sort -nr -k 3 | head -10'
alias mem10='ps -L aux | sort -nr -k 4 | head -10'
alias lsmount='mount |column -t' 

当然,我们还可以讨论更多节省时间的技巧,但 Bash 的复杂性足以让我们写一本书(而且很多人确实这么做了)。随着本章的进行,我会给你更多的提示。现在,给你一个最后的技巧,它会将你的工作目录切换回你之前所在的目录:

cd - 

这个简单的命令在第四章导航和基本命令中有提到,但值得再提一遍——不客气!接下来,让我们看看 shell 变量,它们允许我们存储信息,以便在其他命令中轻松访问。

理解变量

Bash 不仅仅是一个 shell。你可以说它非常类似于一个完整的编程语言,这样的说法并不错误。Bash 有一个内建的脚本引擎(稍后我们将讨论脚本编写),关于脚本语言和编程语言的区别存在很多争议,随着新语言的出现,这两者之间的界限也越来越模糊。

与任何脚本语言一样,Bash 也支持变量。Bash 中变量的概念非常简单,但我觉得有必要单独(相对简短地)讲解这一部分,以确保你理解基础知识。你可以通过如下命令设置变量:

myvar='Hello world!' 

当 Bash 遇到等号后面的字符串时,它认为你正在创建一个变量。在这里,我们创建了一个名为myvar的变量,并将其设置为Hello world!然而,每次引用变量时,我们需要明确告诉 Bash 我们正在请求一个变量,方法是用美元符号($)前缀它。考虑一下这个命令:

echo $myvar 

如果你像我一样设置了变量,执行该命令将会将Hello world!打印到stdoutecho命令在打印变量内容时非常有用。这里需要记住的关键是,当你设置一个变量时,不需要加$符号,但在获取变量时需要加上。另外,请记住等号两边不能有空格。

在使用各种 Linux 服务器时,你会看到变量名格式的不同变体。例如,你可能会看到全大写的变量名、驼峰命名法(MyVar)等其他变体。这些变体都是有效的,具体使用哪种形式取决于创建者的背景(开发者、管理员等),因此你可能会看到不同的变量命名方式。内建的变量通常会采用全大写的命名方式,这也非常常见。

变量也可以在 shell 的其他方面使用,而不仅仅是与echo一起。考虑一下这个:

mydir="/etc"
ls $mydir 

在这里,我们将一个目录名称存储在变量中,并使用 ls 命令列出其内容。这看起来可能相对无用,但在编写脚本时,这将为你节省时间。任何你需要多次引用的内容,都应该放在变量中。这样,在脚本中,你只需修改一次该变量的内容,脚本中的所有地方都会引用到它。

还有一些在你的 shell 中自动存在的变量,这些是你没有明确设置的。执行以下命令来试试看:

env 

哇!你应该能看到很多变量,特别是如果你在 Ubuntu 的桌面版本中输入它的话。

这些变量是由系统设置的,但仍然可以像其他变量一样通过 echo 访问。其中一些值得注意的包括 $SHELL(存储当前处理你的 shell 的二进制文件名)、$USER(存储当前用户名)和 $HOST(存储你设备的主机名)。这些变量可以随时访问,甚至在脚本中也能发挥作用。

我们已经在上一章讲解了标准输出stdout)、标准错误stderr)和标准输入stdin)。我们在这里再次使用标准输入,当我们捕获输入并将其存储为变量时。尝试执行以下命令:

read age 

当你运行此命令时,你会被带到一个空白行,没有任何提示说明你应该做什么。继续输入你的年龄,然后按 Enter。接下来,执行这个:

echo $age 

在脚本中,你需要告知用户他们应该输入什么,所以你可能会使用类似于以下的命令:

echo "Please enter your age"
read age
echo "Your age is $age" 

我们在上一章讨论了标准输入,这里我们可以再次看到它的应用,我们从用户获取输入并将其存储在一个变量中。

自动化是我们将在本书剩余部分多次探讨的主题,其中将包括更多高级主题,如配置管理。编写脚本是最简单的自动化形式,它让你能够将命令写入文本文件并逐一执行。这就是我们接下来要探讨的内容。

编写简单脚本

这是我们到目前为止讨论的所有内容开始汇聚的部分。编写脚本既有趣又有回报,因为它们允许你自动化大任务,或简化那些你反复执行的操作。关于脚本的最重要一点是:如果是你需要做多次的事情,确实应该将其写成脚本。这是一个非常好的习惯。

脚本是一个非常简单的概念;它只是一个包含 shell 要逐一执行命令的文本文件。专门为 Bash 执行编写的脚本被称为 Bash 脚本,这就是我们在本节中将要创建的脚本类型。

目前,我假设你已经在 Linux 上使用过文本编辑器。不管你是使用 Vim 还是 Nano。既然我们之前已经编辑过文本文件(我们在 第五章文件和目录管理 中讲过),我假设你已经知道如何创建和编辑文件了。我们将使用文本编辑器来创建一个简单的脚本作为示例,使用以下命令:

nano ~/myscript.sh 

如果你还不清楚,波浪线(~)只是指代用户主目录的快捷方式。因此,在我的系统上,前面的命令就相当于我输入了:

nano /home/jay/myscript.sh 

在文件中,输入以下内容:

#!/bin/bash
echo "My name is $USER"
echo "My home directory is $HOME" 

保存文件并退出编辑器。为了将此文件作为脚本运行,我们需要将它标记为可执行:

chmod +x ~/myscript.sh 

要执行它,我们只需要调用文件的路径和文件名:

~/myscript.sh 

输出应该类似于以下内容:

My name is jay
My home directory is /home/jay 

第一行 #!/bin/bash 可能会让你觉得奇怪,如果你以前没见过它。通常,带有井号(#)符号的行会被解释器忽略。但是第一行是一个例外。我们在第一行看到的 #!/bin/bash 叫做 哈希 bangshebang。基本上,它只是告诉内核应该使用哪个解释器来执行脚本中的命令。如果我们写的是 Python 脚本,可能会使用 #!/usr/bin/python。但是因为我们编写的是 Bash 脚本,所以使用了 #!/bin/bash

后面的几行是简单的打印语句。每一行都使用了系统变量,所以你不需要声明这些变量,因为它们已经存在。在这里,我们打印了当前用户的用户名和主目录。

脚本的概念变得更加有价值,当你开始考虑那些你日常做的事情,你可以用自动化来替代它们时。作为一名有效的 Linux 管理员,采用自动化思维方式非常重要。再说一遍,如果你需要做某项工作超过一次,那就写个脚本吧。这里有另一个示例脚本,帮助你理解这个概念。这一次,这个脚本将会稍微有点用:

#!/bin/bash
sudo apt install -y apache2
sudo apt install -y libapache2-mod-php8.1
sudo a2enmod php8.1
sudo systemctl restart apache2 

我们在这里做的,理论上是脚本化了一个 Web 服务器的设置。我们可以进一步扩展这个脚本,让它将网站内容复制到 /var/www/html,启用配置文件等等。但从上面的脚本来看,你可能已经看出,脚本可以帮助你减少工作量。这个脚本可以是一个高级的 Web 服务器安装脚本,你可以将它直接复制到新服务器上然后运行。

请注意,示例中使用了apt-y选项。如果你之前没有注意到,这会自动回答yes,以应对在过程中可能出现的提示。脚本通常是非交互式的,这意味着可能没有管理员在旁边处理提示问题。另外,使用a2enmod命令启用php8.1实际上并不必要,因为它在安装libapache2-mod-php8.1包时已经会自动启用。但我想你明白我的意思;我们希望在脚本中明确写出,给出我们希望的具体状态。

现在,让我们更深入一点编写脚本。之前的脚本只是安装了一些包,实际上我们可能也可以通过复制和粘贴命令到 shell 中轻松完成。让我们把这个脚本做得更进一步。让我们编写一个条件语句。这是之前脚本的修改版本:

#!/bin/bash
# Install Apache if it's not already present
if [ ! -f /usr/sbin/apache2 ]; then
    sudo apt install -y apache2
    sudo apt install -y libapache2-mod-php8.1
    sudo a2enmod php8.1
    sudo systemctl restart apache2
fi 

现在它变得有趣起来了。哈希标记之后的第一行是一个注释,告诉我们脚本的作用:

# Install Apache if it's not already present 

注释被解释器忽略,但它们有助于让我们知道代码块在做什么。

接下来,我们开始一个if语句:

if [ ! -f /usr/sbin/apache2 ]; then 

Bash,像任何脚本语言一样,支持分支,if语句就是实现分支的一种方式。在这里,它正在检查apache2二进制文件是否存在。这里的-f选项表示我们正在查找一个文件。我们可以将其更改为-d来检查目录是否存在。感叹号表示取反。它基本上意味着我们在检查某个东西是否不存在。如果我们想检查某个东西是否存在,我们将省略感叹号。基本上,我们正在设置脚本,如果 Apache 已经安装,就不执行任何操作。在这种情况下,在方括号中我们只是执行一个 shell 命令,然后检查结果。if语句中的命令只是安装包。

最后,我们用if的反向词(fi)来结束我们的if语句。如果你忘记这么做,脚本将会失败。

关于if语句的概念,我们也可以比较值。请看以下例子:

#!/bin/bash
myvar=1
if [ $myvar -eq 1]; then
    echo "The variable equals 1"
fi 

通过这个脚本,我们只是检查一个变量的内容,并在其等于某个特定数字时采取行动。注意,我们在创建变量时没有使用引号,因为这里只是设置了一个数字(整数)。只有当我们想把变量值设置为字符串时,才会使用引号。如果if语句不匹配,我们也可以采取行动:

#!/bin/bash
myvar=10
if [ $myvar -eq 1]; then 
    echo "The variable equals 1" 
else 
    echo "The variable doesn't equal 1" 
fi 

这是一个愚蠢的例子,我知道,但它有助于说明如何在 Bash 中创建if/else逻辑块。if语句检查变量是否等于1。它不是,所以执行else块。

命令中的-eq部分类似于大多数编程语言中的==。它是在检查某个值是否等于某个值。或者,我们可以使用-ne(不等于)、-gt(大于)、-ge(大于或等于)、-lt(小于)等。

在这一点上,我建议你暂停阅读,进一步练习脚本编写(练习是将概念牢记在心的关键)。尝试以下挑战:

  • 要求用户输入信息,例如他们的年龄,并将其保存到一个变量中。如果用户输入的数字小于 30,告诉他们他们还年轻。如果数字大于或等于 30,使用echo打印出一个告诉他们已经老了的语句。

  • 编写一个脚本,将一个文件从一个地方复制到另一个地方。让脚本先检查文件是否存在,如果文件不存在,使用else语句打印错误信息。

  • 想想我们在本书中已经学习过的任何主题,并尝试将其自动化。

现在,让我们来看看另一个概念,那就是循环。循环的基本思想就是反复执行某个操作,直到满足某个条件。考虑以下示例脚本:

#!/bin/bash
myvar=1
while [ $myvar -le 15 ] 
do 
    echo $myvar 
    ((myvar++)) 
done 

让我们逐行分析脚本,以理解它在做什么。

myvar=1 

通过这个新脚本,我们创建了一个控制变量,名为myvar,并将其设置为1

while [ $myvar -le 15 ] 

接下来,我们设置一个while循环。while循环会持续执行,直到满足某个条件。在这里,我们告诉它重复执行代码块中的语句,直到$myvar等于15。事实上,如果你输入不正确的条件,while循环可能会永远执行下去,这就是所谓的无限循环。无限循环是危险的,可能导致服务器停止响应。如果你用了-ge 0,你就创建了一个这样的循环。

do 

使用do,我们告诉for循环准备开始执行某些操作。

 echo $myvar 

在这里,我们打印出当前$myvar变量的内容——没有什么令人惊讶的地方。

 ((myvar++)) 

通过这个语句,我们使用了所谓的增量器来将变量的值增加1。双重括号告诉 shell 我们正在进行一个算术运算,因此解释器不会误认为我们在处理字符串。

done 

当我们编写完while循环时,必须用done来关闭代码块。如果你正确输入了脚本,它应该从1计数到15

另一种类型的循环是for循环。for循环会对集合中的每个项执行一条语句。例如,你可以让for循环对目录中的每个文件执行一个命令。考虑这个例子:

#!/bin/bash
turtles='Donatello Leonardo Michelangelo Raphael'
for t in $turtles
do
    echo $t
done 

让我们深入探讨一下我们在这里做了什么:

turtles='Donatello Leonardo Michelangelo Raphael' 

在这里,我们创建了一个列表并将其填充上名字。每个名字都是列表中的一个项目。我们将这个列表命名为turtles。我们可以像查看任何其他变量一样,使用echo查看该列表的内容:

echo $turtles 

接下来,让我们看看如何设置for循环:

for t in $turtles 

现在,我们告诉解释器准备对列表中的每一项执行某些操作。这里的t是任意的,我们可以使用任何字母,甚至是更长的字符串。我们只是设置了一个临时变量,用来保存脚本正在处理的当前项。

do 

使用do,我们告诉for循环准备开始执行某些操作。

 echo $t 

现在,我们将当前t的值打印到stdout

done 

就像我们在使用while循环时做的那样,我们输入done来让解释器知道这是for循环的结束。实际上,我们刚刚创建了一个for循环,用来独立地打印列表中的每一项:

Donatello
Leonardo
Michelangelo
Raphael 

我们在列表中包含了四个乌龟的名字,我们能够遍历它们并逐个打印出来。

尽管我喜欢乌龟(尤其是忍者神龟),但是这个脚本对于服务器管理来说并不是特别实用或有用。接下来,我们将编写一个实际上非常有用的脚本。

把所有内容放在一起——编写一个 rsync 备份脚本

让我们用一个 Bash 脚本来结束这一章,它不仅非常实用,还能帮助你提高技能。rsync工具是我最喜欢的工具之一;它在将数据从一个地方复制到另一个地方时非常有用,也有助于设置备份任务。让我们使用以下的rsync命令来练习自动化:

rsync -avb --delete --backup-dir=/backup/incremental/08-17-2022 /src /target 

这个示例rsync命令使用了-a(归档)选项,它保留了文件的元数据(例如时间戳和所有者)在目标中复制的文件。-v选项提供了详细输出,这样我们可以准确看到rsync正在做什么。-b选项启用了备份模式,这意味着如果目标文件将被源文件覆盖,那么目标文件的旧版本将被重命名以防被覆盖。将这三个选项结合在一起,我们简化为-avb,而不是输入-a -v -b--delete选项告诉rsync删除目标中不存在于源中的任何文件(由于我们使用了-b,被删除的文件会被保留)。

--backup-dir选项告诉rsync,每当一个文件会被这样重命名(或删除)时,改为将其复制到另一个目录。在这种情况下,我们将所有可能被覆盖的文件发送到/backup/incremental/08-16-2022目录。

让我们编写这个rsync任务的脚本。我们可以立刻修复的一个问题是目录中日期的存在,这个日期是我们在--backup-dir中使用的目录路径的一部分。日期每天都会变化,所以我们不应该将其写死在代码中。因此,让我们从解决这个问题开始脚本:

#/bin/bash
curdate=$(date +%m-%d-%Y) 

我们正在创建一个名为curdate的变量。我们将其设置为$(date +%m-%d-%Y)命令的输出。你可以在终端窗口中执行date +%m-%d-%Y来查看它具体做了什么。在这种情况下,将命令(例如date)放在括号中,并在前面加上美元符号意味着我们在子 Shell中执行该命令。命令会运行,我们将捕获该命令的结果并存储在curdate变量中。

接下来,让我们确保rsync已经安装,如果没有安装,则进行安装:

if [ ! -f /usr/bin/rsync ]; then 
    sudo apt install -y rsync 
fi 

在这里,我们仅仅是检查是否没有安装rsync。如果没有安装,我们将通过apt进行安装。这与我们在本章前面检查apache2是否存在的方式类似。

现在,我们添加最后一行:

rsync -avb --delete --backup-dir=/backup/incremental/$curdate /src /target 

如果你还没有发现,Bash 中的变量魔力现在肯定能让你看得清楚。我们在命令中包含了$curdate,它被设置为当前实际日期。当我们把这些拼凑在一起时,我们的脚本如下所示:

#/bin/bash
curdate=$(date +%m-%d-%Y)
if [ ! -f /usr/bin/rsync ]; then 
    sudo apt install -y rsync 
fi
rsync -avb --delete --backup-dir=/backup/incremental/$curdate /src /target 

这个脚本在运行时会执行一个rsync任务,将内容从/src复制到/target。(确保更改这些目录,以匹配你想要备份的源目录和目标复制目录。)这样做的好处是,/target可以是一个外部硬盘或网络共享。因此,简而言之,你可以实现自动化的夜间备份。由于我们使用了-b选项并配合--backup-dir,这个备份将允许你从/backup/incremental目录中恢复文件的先前版本。你可以自由地发挥创意,决定在哪里放置先前的文件版本,以及将备份存储在哪里。

当然,不要忘记将脚本标记为可执行文件,前提是它被保存为像backup.sh这样的文件名:

chmod +x backup.sh 

此时,你可以将此脚本放入定时任务中以自动运行。为了做到这一点,最好将脚本放在一个可以找到的中心位置,比如/usr/local/bin

sudo mv backup.sh /usr/local/bin 

你可以考虑为这个脚本创建一个定时任务,使其定期运行。我们将在第七章控制和监控进程中详细介绍这一点,紧接着就是本章内容。通过定时任务,你可以设置不同的任务在不同时间运行,从而让你的服务器基本上代替你完成工作。

总结

在本章中,我们深入探讨了与 Shell 命令相关的一些更高级的概念,如重定向、Bash 历史记录、命令别名、一些命令行技巧等等。与 Shell 的工作无疑是你将继续提升的技能,所以如果你在记忆这些知识时遇到困难,不必担心。在我从事 Linux 工作超过 20 年的过程中,我仍然在不断学习新的东西。本章的主要收获是为你提供一个起点,拓宽你的命令行技巧,并为未来的探索奠定基础。

在下一章,我们将探讨如何管理进程,包括作业管理、驯服不良进程等内容。我们下次见!

相关视频

深入阅读

加入我们在 Discord 上的社区

加入我们社区的 Discord 空间,与作者和其他读者讨论:

packt.link/LWaZ0

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/QR_Code50046724-1955875156.png

第七章:控制和管理进程

在典型的 Linux 服务器上,任何时候都可能有超过一百个进程在运行。这些进程的用途从系统服务(例如 网络时间协议NTP)服务),到为其他进程提供服务的进程(如 Apache web 服务器)不等。作为 Ubuntu 服务器的管理员,你需要能够管理这些进程,以及管理它们所使用的资源。在本章中,我们将探讨进程管理,包括 ps 命令、管理作业控制命令等。

在我们学习这些概念的过程中,我们将涉及以下主题:

  • 管理作业

  • 理解 ps 命令

  • 更改进程优先级

  • 处理异常进程

  • 管理系统进程

  • 使用 cron 调度任务

为了开始探索进程管理,首先让我们来看一下作业管理。这不仅有助于我们更好地理解这些概念,还能帮助我们更好地理解后台和前台操作。

管理作业

直到现在,我们在 shell 中做的每一件事都直接呈现在我们面前,从执行到完成。我们安装了应用程序,运行了程序,走过了各种命令。每次,我们的 shell 控制权都会被夺走,直到上一个任务完成,我们才能开始下一个任务。例如,如果我们用 apt install 命令安装 vim-nox 包,我们只能眼睁睁看着 apt 为我们获取包并安装它。

在此期间,我们的光标消失,shell 会为我们完成任务,而不会让我们排队执行其他命令。我们可以随时打开一个新的 shell 会话到服务器,利用同时打开两个窗口来进行多任务处理,每个窗口执行不同的任务。但在使用命令行时,这可能不是最有效的多任务处理方式。

相反,我们实际上可以将一个进程放到后台,而不必等待它完成,然后可以将该进程带回前台,继续处理它,或者检查它是否成功完成。可以将这看作类似于窗口化桌面环境或 Windows 或 macOS 操作系统上的用户界面。我们可以在使用某个应用程序时,将它最小化以便不妨碍操作,再将其最大化以继续工作。实际上,这与在 Linux shell 中将进程放到后台的概念是一样的。

那么,究竟如何将进程放入后台或前台呢?这个概念有点难以解释。在我看来,学习新概念最简单的方法是亲自尝试,而我能想到的最简单的例子就是(再次)使用文本编辑器。我保证这次以文本编辑器为例不会无聊。事实上,这个例子非常有用,可能会成为你日常工作流的一部分。为了完成这个练习,你可以使用任何你喜欢的命令行文本编辑器,比如 Vim 或 Nano。在 Ubuntu Server 中,nano通常是默认安装的,因此如果你想使用它,你已经有了。如果你更喜欢使用 Vim,可以随时安装vim-nox包(如果你还没有安装的话):

sudo apt install vim-nox 

你实际上可以安装vim而不是vim-nox,但我总是默认安装vim-nox,因为它内置支持脚本语言。

再次说明,随意使用你感到舒服的文本编辑器。在以下的例子中,我将使用nano,但如果你使用vim,只需每次看到nano时都将其替换为vim

无论如何,为了查看后台操作的实际效果,打开你的文本编辑器。你可以选择打开一个文件,或者只启动一个空白会话。(如果不确定,可以输入nano并按Enter。)文本编辑器打开后,我们可以随时通过按下键盘上的Ctrl + z将其放入后台。

如果你使用vim而不是nano,你只能在非插入模式下将vim放到后台,因为它会捕获Ctrl + z,而不是将其传递给 shell。

你看到了发生了什么吗?你会立刻被带离编辑器,返回到 shell,这样你就可以继续执行命令了。你应该会看到类似以下的输出:

[1]+ Stopped nano 

在这里,我们可以看到进程的job编号、其状态以及进程名称。即使文本编辑器的进程显示状态为Stopped,它仍然在运行。你可以通过以下命令确认这一点:

ps au | grep nano 

在我的情况下,我看到nano进程正在运行,PID 为43231

jay        43231  0.0  0.1   5468  3632 pts/0    T    11:27   0:00 nano 

此时,我可以执行其他命令,浏览我的文件系统,并完成其他工作。当我想将文本编辑器恢复到前台时,我可以使用fg命令将进程放到前台,这样它就会恢复运行。如果我有多个后台进程,fg命令会将我最近操作的那个进程恢复到前台。

我给你举了一个ps命令的例子,展示进程仍在后台运行,但实际上有一个专门的命令用于此目的,那就是jobs命令。

如果你执行jobs命令,你会看到输出中列出所有后台运行的进程:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_07_01.png

图 7.1:在将两个nano进程放入后台后运行jobs命令

输出显示我有两个正在使用的nano会话,一个在修改file1.txt,另一个在修改file2.txt。如果我执行fg命令,它将把编辑file2.txtnano会话调回前台,因为那是我最近在使用的会话。不过,这不一定是我想要返回编辑的文件。由于我左侧有作业 ID,我可以通过fg命令使用其 ID 来调回特定的后台进程:

fg 1 

了解如何将进程放入后台可以极大地提高工作效率。例如,假设我正在编辑一个服务器应用程序的配置文件,如 Apache。在编辑配置文件时,我需要查阅 Apache 的文档(手册页面),因为我忘记了某个语法。我可以打开一个新的 Shell 和 SSH 会话,查看另一个窗口中的文档。如果打开太多的 Shell 窗口,可能会变得非常混乱。将当前的nano会话放入后台,查看文档,然后用fg命令将进程调回前台继续工作,所有操作都可以在一个 SSH 会话中完成,这样会更加简便!

要将一个进程放入后台,你不一定需要使用Ctrl + z;实际上,你可以在执行命令时,直接通过在命令末尾加上&符号(&)来将进程放入后台。为了演示这个操作,我将使用htop作为示例。虽然这可能不是最实际的例子,但它可以有效地展示如何启动一个进程并立即将其放入后台。

我们可能还没有安装htop,但现在如果没有安装,请随意安装此软件包(如果尚未安装),然后使用&符号运行它:

sudo apt install htop 
htop & 

如你所知,第一个命令将在我们的服务器上安装htop软件包。第二个命令会打开htop,但会立即将其放入后台。当它被放入后台时,我会看到它的作业 ID 和进程 ID(接下来会详细讲解)。现在,在任何时候,我都可以使用fghtop调回前台。由于我刚刚将其放入后台,fg会将htop带回前台,因为它被视为最近的任务。如你所知,如果它不是最近的任务,我可以通过fg命令引用它的作业 ID 来将其带回,即使它不是我最近使用的任务。试着练习一下,使用命令中的&符号并将其带回前台。以htop为例,这种操作很有用,启动它、将其放入后台,然后在需要检查服务器性能时随时将其带回前台。

不过请记住,当你退出 Shell 时,所有后台进程都会关闭。如果你在文本编辑器中有未保存的工作,你将会丢失正在处理的内容。因此,如果你使用后台进程,在注销之前,最好先执行jobs命令检查是否有待处理的任务仍在运行。

此外,你可能会注意到,一些应用程序能够平稳地置于后台运行,而其他应用程序则不能。以使用文本编辑器和htop为例,这些应用会在后台保持暂停状态,允许我们执行其他任务,然后再返回这些命令。然而,一些应用即使被置于后台,仍可能会定期在主窗口输出诊断文本。为了更好地控制你的 Bash 会话,你可以学习如何使用多路复用器,如tmuxscreen,让这些进程在它们自己的会话中运行,这样就不会打断你的工作。虽然本书不打算详细介绍如何使用tmux这类程序,但如果你感兴趣,它是一个非常有用的工具。

能够将进程置于后台和前台运行,使我们能更有效地管理命令行上的任务,这绝对是有用的。现在,我们可以扩展这一点,看看如何查看服务器上的其他进程,包括那些我们没有手动启动的进程,比如文本编辑器。在接下来的部分中,我们将详细了解ps命令,它可以帮助我们理解服务器上实际运行的进程。

理解ps命令

在管理我们的服务器时,我们需要了解哪些进程正在运行,并且如何管理它们。在本章后续的部分中,我们将学习如何启动、停止和监控进程。但在讲解这些概念之前,我们首先需要能够确定服务器上实际运行的进程。ps命令可以帮助我们完成这项工作。

使用ps查看正在运行的进程

ps命令单独执行时,它会显示由调用该命令的用户运行的进程列表:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_07_02.png

图 7.2:当以普通用户身份运行ps命令且没有选项时的输出

图 7.2中,你可以看到当我作为自己的用户运行ps命令且没有任何选项时,它显示了我以自己身份运行的进程列表。在这个例子中,我有一个vim会话正在运行(在后台),并且在最后一行,我们还看到了ps本身,它也包含在输出中。

在输出的左侧,你会看到每个正在运行的进程的编号。这被称为进程 IDPID),我们在管理作业部分中提到过。为了继续之前,PID 是你应该熟悉的内容,所以我们不妨现在就来讲解一下。

服务器上运行的每个进程都会分配一个 PID,这使它与系统上的其他进程区分开来。你可能会理解一个进程是vimtop或其他某个名称。但是,我们的服务器通过进程的 ID 来识别它们。当你打开一个程序或启动一个进程时,内核会为其分配一个 PID。当你管理服务器时,你会发现知道 PID 非常有用,特别是对于本章将要介绍的命令。例如,如果你想终止一个表现不正常的进程,一个典型的工作流程是先找到该进程的 PID,然后在杀死进程时引用该 PID(稍后我会展示如何做)。实际上,PID 比单纯分配给运行进程的数字要复杂,但本章的主要目的是帮助你记住这个概念。

如果你知道进程的名称,你也可以使用pidof命令来查找进程的 PID。例如,我曾向你展示过一个vim进程的截图,PID 为1385。你也可以通过运行以下命令来做到这一点:

pidof vim 

输出将给出进程的 PID,而无需使用ps命令。

配置ps的参数

继续使用ps命令,你可以通过提供几个有用的参数来改变输出的方式。如果你使用a选项,你会看到比正常情况更多的信息:

ps a 

这将产生如下所示的输出:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_07_03.png

图 7.3:ps a命令的输出

使用ps a,我们看到的输出与之前相同,但附加了更多的信息,并且顶部有了列标题。现在我们看到了PIDTTYSTATTIMECOMMAND的标题。从这个新的输出中,你可以看到我正在运行的vim进程正在编辑一个名为testfile.txt的文件。这很有用,因为如果我有多个vim会话打开,而其中一个会话表现异常,我可能想知道需要停止的是哪一个。

我们已经看到了PIDCOMMAND字段,尽管我们没有看到顶部的正式标题。我们已经讨论过PID列,所以我就不再详细介绍了。COMMAND字段告诉我们实际正在运行的命令,如果我们想确保管理的是正确的进程,或者想查看某个用户正在运行的程序,这非常有用(稍后我会演示如何显示其他用户的进程)。

STAT 字段是新的;我们在单独运行 ps 时并没有看到它。STAT 字段告诉我们进程的状态码,表示该进程当前所处的状态。状态可以是不可中断的睡眠(D)、僵尸进程(Z)、停止(T)、可中断的睡眠(S)和运行队列(R)。还有分页(W),但现在不再使用,因此不需要讨论。不可中断的睡眠是一种进程状态,通常表示该进程在等待输入,无法处理额外的信号(我们稍后会简要介绍信号)。僵尸进程(也称为僵尸进程)在所有实际情况下已经完成了任务,但仍在等待父进程执行清理操作。僵尸进程实际上并没有运行,但会保留在进程列表中,通常会自行关闭。如果这样的进程无限期地保留在列表中并没有关闭,它就可能成为 kill 命令的候选对象,我们稍后会讨论该命令。停止的进程通常是已被发送到后台的进程,这将在下一节中讨论。可中断的睡眠表示程序处于空闲状态:它在等待输入,以便唤醒。

TTY 列告诉我们进程附加到哪个 TTY。TTY 是指打字机,这是一个来自不同历史时期的术语。过去,在大型主机时代,用户使用“终端”来操作计算机——这种终端设备由显示器和键盘组成,通过电缆与主机相连。这些设备只能显示从主机接收到的输出,并接收键盘上输入的数据。打字机就是用来指代这类设备的术语。显然,现在我们不再使用这样的设备,但从虚拟的角度来看,概念是相似的。

在我们的服务器上,我们使用键盘向一个设备发送输入,然后该设备将输出显示到另一个设备上。在我们的例子中,输入设备是我们的键盘,输出设备是我们的屏幕,屏幕要么直接连接到服务器,要么连接到我们的计算机,计算机通过像 SSH 这样的服务与服务器相连。在 Linux 系统中,大多数进程运行在 TTY 上,TTY 是(从实际应用的角度来说)一个终端,它接收输入并管理输出,类似于虚拟意义上的打字机。终端是我们与服务器交互的方式。

图 7.3中,我们看到一个进程正在tty1的 TTY 上运行,其他进程则在pts/0上运行。我们看到的 TTY 是实际的终端设备,而pts指代的是一个虚拟(伪)终端设备。我们的服务器实际上能够运行多个tty会话,通常是 1 到 7 个。每个 TTY 会话都可以运行自己的程序和进程。为了更好地理解这一点,尝试按下Ctrl + Alt + 任意功能键,从F1F7(如果你插入了物理键盘到物理服务器)。每按一次,你应该看到屏幕清空,然后转到另一个终端。每个终端都是独立的。每个功能键代表一个特定的 TTY,所以按下Ctrl + Alt + F6时,你会把显示切换到 TTY 6。

本质上,你是在从 TTY 1 切换到 TTY 7,每个 TTY 都可以容纳自己正在运行的进程。如果你再次运行ps a,你会看到你在这些 TTY 上启动的任何进程会以tty会话的形式显示在输出中,例如tty2tty4。你在终端模拟器中启动的进程会被标记为pts,因为它们并不在一个实际的 TTY 上运行,而是运行在伪 TTY 上。

这是一个看似复杂但实际上很简单的讨论(TTY 或伪 TTY),但通过掌握这些知识,你应该能够区分一个进程是在实际服务器上运行还是通过一个终端外壳运行。

接下来,让我们看看ps命令输出中的TIME字段。这个字段表示 CPU 为该特定进程分配的总时间。然而,在我提供的截图中,每个进程的时间显示为0:00。刚开始可能会有些困惑。就我而言,特别是vim进程自从我截图以来已经运行了大约 15 分钟,但它们现在仍然显示为0:00的利用时间。实际上,这并不是进程运行的时间,而是进程与 CPU 进行交互的时间。对于vim而言,每个进程只是一个打开了文件的缓冲区。为了进行对比,我现在正在写这章的 Linux 机器的进程 ID 为759,时间为92:51。PID 759属于我的 X 服务器,它提供了我的图形用户界面GUI)和窗口功能。然而,当前这台笔记本的正常运行时间为 6 天 22 小时,也就是大约 166 小时,这与 PID 759TIME列中报告的时间并不相同。因此,我们可以推测,尽管我的笔记本已经连续运行了 6 天,但 X 服务器实际利用的 CPU 时间仅为 92 小时 51 分钟。总结一下,TIME列指的是进程需要 CPU 的时间以便计算某些东西,它不一定等于进程已经运行的时间,也不等于图形进程在屏幕上显示的时间。

让我们继续探讨ps命令,并查看一些额外的选项。首先,让我们看看当我们在之前的例子中添加u选项时,会得到什么结果,下面是相应的命令:

ps au 

这将产生类似于以下内容的输出:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_07_04.png

图 7.4:ps au命令的输出

当你运行时,你应该立刻注意到与ps a命令的不同。使用这种变体,你会看到列出的是由你的用户 ID 以及其他用户运行的进程。当我运行时,我看到输出中列出了我的用户(jay)的进程,以及一个root的进程。u选项将是你可能会常用的选项,因为在大多数管理服务器的情况下,你可能更关心的是监控你的用户在做些什么。但是,ps命令最常用的变体可能是以下这种:

ps aux 

加上 x 选项后,我们不再将输出限制于 TTY 内的进程(无论是本地的还是伪终端的)。结果是我们将看到更多的进程,包括那些不与我们自己启动的进程相关的系统级进程。试试看吧。不过在实践中,ps aux 命令最常与 grep 一起使用,以查找特定的进程或字符串。例如,假设你想查看所有 nginx 工作进程的列表。为此,你可以执行如下命令:

ps aux | grep nginx 

在这里,我们像之前一样执行 ps aux 命令,但我们将输出通过管道传输到 grep,并只查找包含字符串 nginx 的输出行。在实践中,这是我常用 ps 的方式,也是我注意到很多其他管理员常用的方式。使用 ps aux 后,我们可以看到更多的输出,然后通过管道传输到 grep 来用搜索条件进一步缩小范围。然而,如果我们只想显示包含特定字符串的进程,我们也可以执行以下命令:

ps u -C nginx 

这将输出匹配 nginx 的进程列表及相关详细信息。ps 命令的另一个有用变体是通过按 CPU 使用率排序来排序进程:

ps aux --sort=-pcpu 

不幸的是,该命令会显示大量的输出,我们需要滚动回到顶部才能看到最上面的进程。根据你的终端配置,你可能无法回滚太多(甚至根本无法回滚),因此以下命令将进一步缩小输出范围:

ps aux --sort=-pcpu | head -n 5 

这可真有用!在这个示例中,我使用 ps aux 命令并加上 --sort 选项,按照 CPU 使用百分比(-pcpu)排序。然后,我将输出传送到 head 命令,指示它只显示前五行(-n 5)。本质上,这给了我自开机以来使用最多 CPU 的前五个进程列表。事实上,我也可以做到同样的事情,只不过是按使用最多内存来排序:

ps aux --sort=-pmem | head -n 5 

如果你想确定哪些进程表现异常并占用大量内存或 CPU,那么这些命令将帮助你缩小范围。ps 命令是管理员工具箱中非常有用的命令。你可以在我提供的示例之外自由实验,想要了解更多技巧,你可以查阅 ps 命令的手册页。实际上,ps 命令的手册页第二部分(在 examples 下)提供了更多有趣的示例,供你尝试。

现在我们已经知道如何检查正在运行的进程,在接下来的部分中,我们将了解如何更改进程的优先级,确保更重要的进程能够获得 CPU 更多的关注。

更改进程的优先级

Linux 系统上的进程可以以更改后的优先级运行,从而让某些进程拥有更高的优先级,其他进程则拥有较低的优先级。这为你作为管理员提供了充分的权限,确保系统中最重要的进程得到足够的优先级。为此,我们有专门的命令:nicerenice。这些命令允许你启动一个具有特定优先级的进程,或者更改已经运行中的进程的优先级。

现在,管理员们手动编辑进程的优先级已经不像以前那么常见了。拥有 32 个核心(甚至更多)的处理器已经不是什么稀奇事,几百 GB 的内存也是如此。如今的服务器无疑比以前更强大,且不像过去的机器那样资源紧张。许多服务器(例如虚拟机)和容器被专门用于执行单一任务,因此进程调优可能已经不再那么重要。然而,数据处理公司和使用深度学习功能的公司可能仍然需要进行某些细节调整。

无论是否优先处理进程对你有直接用处,至少理解这一概念是个好主意,免得有一天你需要增加或减少某个进程的优先级时能够应对。让我们重新查看一下ps命令,这次加上-l参数:

ps -l 

该命令的输出将如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_07_05.png

图 7.5:ps -l命令的输出

ps -l命令的输出中,可以注意到PRINI列。PRI表示优先级,NI表示“niceness”值,我们稍后将在本节中详细讨论。在这个例子中,我运行的每个进程的PRI都是80NI0。我没有改变或调整这些值;这些是我启动没有特别调整的进程时的默认值。PRI的值80是所有进程的初始值,并会随着“niceness”值的增加或减少而变化。

正如我所提到的,我们有专门的命令来允许我们更改优先级,分别是nicerenice。选择使用哪个命令,取决于进程是否已经在运行。关于图 7.5中的进程,我们需要使用renice来更改它们的优先级,因为这些进程都已经在运行。如果我们想从一开始就启动一个具有特定优先级的进程,我们应该使用nice

例如,让我们改变我正在运行的vim会话的进程。确实,这是一个有些无聊的例子,因为vim并不是一个非常重要的进程。在现实中,你会优先处理那些真正重要的进程。以我为例,既然vim进程的 PID 是1789,那么我需要运行的命令来改变优先级将是这个:

renice -n 10 -p 1789 

该命令的输出将如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_07_06.png

图 7.6:使用 renice 更改进程优先级

如果我们再次运行ps -l,可以看到vim的新的 nice 值:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_07_07.png

图 7.7:更改进程优先级后ps -l命令的输出

现在,vim的新的 nice 值10NI列中显示,PRI值也增加到90。现在,这个vim实例将以比其他任务更低的优先级运行,因为 nice 值越高,优先级越低。注意,当我更改优先级时,并没有使用sudo。在这个例子中,这是可以的,因为我只是增加了进程的 nice 值,这是允许的。但是,让我们尝试在没有sudo的情况下减少 nice 值,使用以下命令:

renice -n 5 -p 1789 

正如你在以下输出中看到的,我并没有成功:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_07_08.png

图 7.8:尝试降低进程的优先级

我尝试将 nice 值从 10 降低到 5 被阻止了。如果我能够降低 nice 值,那么我的进程将以更高的优先级运行。相反,我收到了一个Permission denied错误。所以,本质上,用户可以增加他们进程的 nice 值,但不能减少,哪怕是自己启动的进程。如果你希望减少 nice 值,必须使用sudo。所以,本质上,如果你想“更友善”一些,你可以继续进行。如果你想“更严苛”一些,你需要root权限。此外,用户无法更改他们不拥有的进程的优先级。因此,如果你试图使用renice更改另一个用户正在运行的任务的 nice 值,你将收到Operation not permitted错误。

此时,我们知道如何使用renice重新调整正在运行的进程的优先级。现在,让我们看一下如何使用nice启动一个具有特定优先级的新进程。请看以下命令:

nice -n 10 vim 

在这里,我们启动了一个新的vim实例,但一开始就将优先级设置为特定的值。如果我们稍后想再次更改vim的优先级,我们需要使用renice。正如我之前提到的,nice用于启动一个具有特定优先级的新进程,而renice则用于更改已存在进程的优先级。在这个例子中,我们通过一个命令启动了vim并将其 nice 值设置为10

将像vim这样的文本编辑器的优先级改变可能看起来是一个奇怪的测试用例,确实是这样。但vim编辑器是无害的,因为我们改变它的优先级导致系统崩溃的可能性极小。我想不出有什么实际理由需要重新调整像文本编辑器这样的程序的优先级。不过值得注意的是,你可以改变运行在服务器上的进程的优先级。在真实的服务器上,你可能有一个重要的进程,它生成报告并且必须按时交付。或者你可能有一个进程生成一个数据导出,客户需要这些数据来完成按时交付。所以,从更广阔的视角来看,你可以用对你或你的组织真正重要的进程名来替换vim

你可能会好奇,nicerenice命令中的nice是什么意思。nice值本质上指的是一个进程对其他用户的“友好程度”。nice 值越高,优先级越低。所以,值为 20 比值为 10 更“友好”。在这种情况下,nice 值为 20 的进程以较低的优先级运行,因此对系统中的其他进程更加友好。nice 值的范围是从-20 到 19。nice 值为-20 的进程优先级最高,而 19 是最低的优先级。整个系统比这个简单的描述要复杂得多。尽管我将 nice 值称作优先级,但它实际上并不是。nice 值用于计算实际的优先级。不过现在,如果我们将 nice 值简化为代表优先级,并且假设 nice 值越高优先级越低,那就足够了。

到目前为止,我们一直在使用nicerenice命令,并结合-n选项直接设置 nice 值。不过值得注意的是,你可以简化renice命令,省略-n选项:

renice 10 42467 

该命令将进程的 nice 值设置为正数 10,类似于我们之前的其他示例。如果我们想提高优先级,也可以使用负数来设置 nice 值:

sudo renice -10 42467 

虽然省略-n选项并不会大幅减少我们打字的工作量,但现在你知道这是一个可行的选项。与这个例子不同的是,由于我在降低优先级值(稍后会详细介绍),我需要使用sudo

在使用nice命令时,我们也可以省略-n选项,但在这方面命令的行为略有不同。下面的命令将无法正常工作:

nice 15 vim 

nice的语法略有不同,所以直接给它一个正数并不会像在renice中那样有效。为此,我们需要在数字前面加上一个短横线:

nice -15 vim 

当你查看这个命令时,可能会认为我们在应用一个负数。实际上,情况并非如此。由于nice的语法不同,我们使用的-15值实际上会转换为正数 15。我们在值前加上连字符是为了告诉nice我们正在将该值作为选项应用。如果我们确实想在不使用-n选项的情况下使用负值与nice命令配合使用,我们需要使用两个连字符:

nice --10 vim 

我认为这两个命令在使用-n选项时的语法差异有点令人困惑,因此我建议直接在nicerenice中都使用-n选项,因为这样它们会更加一致:

nice -n 10 vim
sudo nice -n -10 vim
renice -n 10 42467
sudo renice -n -10 42467 

这些示例展示了如何使用-n选项分别操作nicerenice命令,并设置正值和负值。由于这两个命令中-n选项的使用方式相同,因此可能更容易将其记住,而不是关注具体的细节。如前所述,我在设置负值时使用了sudo,因为只有root用户才能将进程的优先级设置为低于0。如果你试图这样做,你将收到以下错误:

nice: cannot set niceness: Permission denied 

这种类型的保护是有一定重要性的,因为你可能会遇到一些用户,他们认为自己的进程是最重要的,试图将它们的优先级提升到-19。归根结底,最好由系统管理员来决定哪些进程可以将优先级设置为负值。

作为 Ubuntu 服务器的管理员,决定哪些进程应该运行,且运行时的优先级是多少,是你的责任。然后,你将确定最佳的方式以实现合适的系统状态,调优进程优先级可能是其中的一部分。如果没有别的,学习nicerenice命令将为你的工具集增添另一项实用功能。

处理不正常的进程

关于ps命令,到目前为止你已经知道如何显示服务器上正在运行的进程,并且可以通过字符串或资源使用情况来缩小输出范围。但你实际上能做些什么呢?尽管我们不愿承认,但有时服务器上运行的进程会失败或表现不正常,这时你可能需要重启它们。如果某个进程无法正常关闭,你可能需要终止该进程。在本节中,我们介绍了killkillall命令来执行这一操作。

kill命令接受 PID 作为参数,并尝试优雅地关闭一个进程。在一个典型的工作流中,当你需要终止一个无法自行关闭的进程时,你首先会使用ps命令找到该进程的 PID。然后,知道 PID 后,你可以尝试kill该进程。例如,如果 PID 为31258的进程需要被终止,你可以执行以下命令:

sudo kill 31258 

如果一切顺利,进程将结束。你可以重新启动它,或者通过查看日志来调查它为什么失败。

为了更好地理解kill命令的作用,你首先需要理解Linux 信号的基础知识。信号由管理员和开发者使用,可以通过内核、另一个进程或手动命令发送给一个进程。信号指示进程执行请求或更改,有时甚至是完全终止。例如,SIGHUP信号告诉进程它们的控制终端已经退出。一个可能发生这种情况的情况是你打开了一个终端模拟器,里面运行着几个进程。如果你关闭终端窗口(而没有停止你正在运行的进程),这些进程将会收到SIGHUP信号,基本上告诉它们退出(本质上意味着 shell 已退出或挂起)。

其他示例包括SIGINT(当应用程序在前台运行时,按下Ctrl + c会停止它),以及SIGTERM,当发送到进程时,请求它干净地终止。还有一个例子是SIGKILL,它强制进程以不干净的方式终止。除了名称,每个信号还由一个值表示,例如SIGTERM的值是15SIGKILL的值是9。逐一介绍所有信号超出了本章的范围(信号的高级主题主要对开发者有用),但如果你感兴趣的话,可以通过查阅 man 页查看更多信息:

man 7 signal 

在本节中,我们最关注的两种信号是SIGTERM(15)SIGKILL(9)。当我们想要停止一个进程时,我们向它发送其中一个信号,而kill命令正是允许我们做到这一点。默认情况下,kill命令会发送信号15SIGTERM),告诉进程干净地终止。如果成功,进程将释放其内存并优雅地关闭。通过我们之前的kill命令,我们向进程发送了信号15,因为我们没有明确指定发送哪个信号。

使用SIGKILL(9)终止进程被认为是一种极端的最后手段。当你向一个进程发送信号9时,相当于把地毯从它下面撕掉,或者用一根炸药棍把它炸飞。进程会被强制关闭,完全没有反应的时间,因此,这是一种你应该避免使用的手段,除非你已经尝试了所有能想到的办法。理论上,发送信号9可能会导致文件损坏、内存问题或其他异常情况。至于我,虽然我自己使用过它,但从未遇到过长期的损害,但理论上是有可能发生的,因此你只应该在极端情况下使用它。一个可能需要使用此信号的情况是有关defunct僵尸进程的情况,当它们无法自行关闭时。这些进程基本上已经“死了”,通常是等待其父进程来回收它们。

如果父进程从未尝试这样做,它们将一直保留在进程列表中。单从这个角度看,这可能并不算什么大问题,因为这些进程技术上并没有做任何事情。但如果它们的存在引发了问题,而你又无法终止它们,可以尝试向进程发送SIGKILL信号。消除僵尸进程应该不会带来任何危害,但你还是希望它们能有时间被清除。

要向进程发送9信号,你需要使用kill命令的-9选项。尽管不言而喻,但还是要确保你执行的是正确的进程 ID。

sudo kill -9 31258 

就这样,PID 为31258的进程会毫无痕迹地消失。它正在写入的任何数据将会处于悬空状态,并且会立即从内存中删除。如果出于某种原因,该进程仍然继续运行(这非常罕见),你可能需要重启服务器来彻底清除它,这种情况我只在极为罕见的几个案例中见过。例如,僵尸进程就是这样一种情况,它出现在进程列表中,但无论发送什么信号,它都不会受到影响,因为这种进程根本不会被调度占用 CPU 时间。总的来说,如果kill -9不能清除进程,那么就没有什么能做到的了。

另一种终止进程的方法是使用killall命令,这比kill命令更安全(至少在没有其他原因的情况下,误终止错误进程的可能性较小)。与kill类似,killall允许你向进程发送SIGTERM信号,但与kill不同,你可以通过进程名称来执行。此外,killall不仅会终止一个进程,它会终止所有与给定名称匹配的进程。要使用killall,你只需执行killall并指定一个进程的名称:

sudo killall myprocess 

kill命令一样,你也可以向进程发送9信号:

sudo killall -9 myprocess 

同样,只有在必要时才使用该方法。实际上,你可能不会经常使用killall -9(甚至从未使用过),因为多个同名进程被锁住的情况很少发生。如果确实需要发送9信号,尽量使用kill命令。

killkillall命令在进程卡住的情况下非常有用,但这些命令最好不是经常使用的。进程卡住通常发生在应用程序遇到无法恢复的情况时,因此,如果你经常需要终止进程,可能需要检查负责服务的软件包是否有更新,或者检查服务器是否存在硬件问题。

在下一节中,我们将查看在后台运行并为我们或我们的用户提供服务的系统进程,例如 web 服务器进程或 DHCP 服务器。

管理系统进程

系统进程,也称为守护进程,是在服务器后台运行的程序,通常会在服务器启动时自动启动。我们通常不会直接管理这些服务,因为它们在后台运行并执行其职责,无论是否需要我们的输入。例如,如果我们的服务器是一个 DHCP 服务器并且运行isc-dhcp-server进程,那么该进程将在后台运行,监听 DHCP 请求并为它们分配新的 IP 地址。通常,当我们安装一个作为服务运行的应用程序时,Ubuntu 会配置它在启动时自动启动,因此我们不需要手动启动它。假设该服务没有出现问题,它将继续执行其工作,直到我们告诉它停止。在 Linux 中,服务由 init 系统管理,也称为 PID 1,因为 Linux 系统的 init 系统总是获得该 PID。在近年来,Ubuntu Server 中进程管理的方式发生了很大变化。Ubuntu 已经切换到systemd作为其init系统,之前一直使用 Upstart,直到几年前。Ubuntu 16.04 是首个使用systemd的 LTS 版本,并且这一标准在 Ubuntu 22.04 中继续沿用。由于systemd已经成为标准一段时间,我们将重点介绍与其一起使用的命令来管理服务。旧的init系统正在逐渐淘汰。

systemd中,服务被称为单元,但在实际使用中,“服务”、“守护进程”和“单元”这几个术语本质上是相同的。自从 20 多年前开始使用 Linux 以来,我依然习惯性地将systemd的单元称为服务。为了帮助我们管理这些“单元”,systemd包括了systemctl命令,它允许你启动、停止并查看服务器上单元的状态。为了帮助说明这一点,我将以 OpenSSH 为例。单元的名称并不重要,因为systemctl命令的语法无论我们操作的是哪个单元名称都是相同的。你可以使用systemctl启动、停止或重新启动 Apache 实例、数据库服务器,甚至用它重新启动整个网络堆栈。systemctl命令如果没有任何选项或参数,会默认使用list-units选项,列出单元的列表到你的 shell 中。不过,这可能会有点杂乱,所以如果你已经知道你要查找的单元名称,你可以将输出通过管道传递给grep,并搜索字符串。在你可能不知道单元的确切名称,但知道部分名称的情况下,这非常方便:

systemctl | grep ssh 

如果你想检查某个单元的健康状况,最好的方法是使用status关键字,它会显示有关该单元的一些非常有用的信息。这些信息包括该单元是否正在运行、是否已启用(意味着它已配置为在启动时自动启动),以及该单元的最新日志条目:

systemctl status ssh 

这个命令将产生类似以下的输出:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_07_09.png

图 7.9:使用 systemctl 检查单元的状态

大多数情况下,你实际上可以在不需要 root 权限的情况下检查单元的状态,但你可能看不到所有可用的信息。在截图中,你可以看到几个关于 ssh 服务的日志条目,但某些单元在没有 sudo 权限的情况下不会显示这些条目。特别是对于 ssh 单元,我们在使用或不使用 sudo 时都会看到日志条目。

你可能在截图中注意到的另一件事是,ssh 单元的名称实际上是 ssh.service,但你不需要包含 .service 部分,因为默认情况下它是隐含的。有时,在使用 systemctl 查看进程状态时,输出可能会被压缩以节省屏幕空间。为了避免这种情况并查看完整的日志条目,可以添加 -l 选项:

systemctl status -l ssh 

另一件需要注意的事情是单元的 vendor preset。大多数包含 systemd 服务文件的 Ubuntu 软件包会自动启用它,但其他发行版通常默认不会启动和启用单元(例如 CentOS)。以 ssh 为例,你可以看到 vendor preset 被设置为 enabled。这意味着一旦安装 openssh-server 包,ssh.service 单元将自动启用。你可以通过检查 Active 行来确认这一点(例如输出中显示 active (running)),它告诉我们该单元正在运行。Loaded 行则明确指出该单元是 enabled,所以我们知道下次启动服务器时,ssh 将会自动加载。虽然在 Ubuntu 中安装包时,systemd 单元通常会被自动启用并启动,但这仍然会有所不同。在安装新软件包时,确保检查该单元的状态,以便了解它的设置。

启动和停止单元同样简单;你只需将 systemctl 中使用的关键字更改为 startstop,即可实现所需的效果:

sudo systemctl stop ssh 
sudo systemctl start ssh 

还有一些其他关键字,例如 restart(它一次性处理前两个命令示例的功能),一些单元甚至提供 reload,允许你在不关闭整个应用程序的情况下激活新的配置设置。一个有用的例子是 Apache,它向本地或外部用户提供网页服务。如果你停止 Apache,所有用户将与网站断开连接。如果你添加了一个新的网站,可以使用 reload 而不是 restart,这样可以在不打断现有连接的情况下激活你所做的任何新配置。我们将在第十四章《提供网页内容》中查看 Apache,所以现在不必过多担心 Apache。它只是一个具有附加功能的单元的好例子,并不是所有单元都有 reload 选项,因此你应当查阅提供该单元的应用程序文档,以确保这一点。

由于我在之前的示例中提到了启动和停止 OpenSSH 单元的操作,有一个有趣的补充是,执行这些操作不会中断当前的 SSH 会话,如果你已经打开了 SSH 会话。停止 ssh 服务不会断开你的连接。现有的连接会被保持,停止 SSH 只会阻止新的连接建立。因此,与其他单元(例如 Apache)不同,SSH 在重新启动时不会中断现有连接。

如我之前所提到的,如果你希望一个单元在服务器启动时自动启动,则该单元需要被启用。大多数单元默认是自动启用的,但如果你遇到一个没有启用的单元,你可以通过 enable 关键字来启用它:

sudo systemctl enable ssh 

禁用一个单元同样也很简单:

sudo systemctl disable ssh 

你可以将启用一个单元的过程与启动它的过程结合在一起:

sudo systemctl enable --now ssh 

--now 参数告诉 systemctl 在启用单元后立即启动它,而不是等待下次启动时才执行,或者需要你在单独的命令中运行 start 参数。

虽然 systemd 主要用于管理单元,但它实际上是一个管理 Linux 系统多个方面的平台,包括 DNS 解析、网络等。systemd 甚至还处理日志记录,并且提供了 journalctl 命令,我们可以使用它来查看日志信息(这也是为什么 systemctl status ssh 的输出能够显示日志条目的原因)。

我们在第四章《导航和基本命令》中已经讨论过一些日志记录内容,接下来在第二十二章《故障排除 Ubuntu 服务器》中,我们将更详细地讨论这方面内容(其中还会包括对 journalctl 命令的进一步讨论)。

目前,只需要理解 systemd 在管理系统中的各个方面非常广泛。对于本章而言,如果你了解如何启动、停止、启用、禁用并检查单元的状态,那么你现在就可以继续往下进行。

使用 cron 调度任务

在本章前面,我们已经学习了如何启动进程并使其在后台运行,以及确保它们在服务器启动时立即启动。在某些情况下,您可能需要一个应用程序在特定时间执行任务,而不是始终在后台运行。这就是cron的用武之地。通过cron,您可以设置一个进程、程序或脚本在特定时间运行,甚至到分钟级别。每个用户可以拥有自己的cron配置(称为crontab),可以执行任何用户通常可以执行的功能。root用户也有一个crontab,允许执行系统范围的管理任务。每个crontab包含一个cron任务列表(每行一个),我们马上就会介绍。要查看用户的crontab,我们可以使用crontab命令:

crontab -l 

使用-l选项,crontab命令将显示执行命令的用户的作业列表。如果以root身份执行它,您将看到root帐户的crontab。如果以用户jdoe身份执行它,您将看到jdoecrontab,依此类推。如果您想查看除您自己以外的用户的crontab,您可以使用-u选项并指定一个用户,但需要以rootsudo权限执行才能查看其他用户的crontab

sudo crontab -u jdoe -l 

默认情况下,没有用户具有crontab,直到您创建一个或多个任务。因此,当您检查当前用户时,您可能会看到类似以下的输出:

no crontab for jdoe 

要创建一个cron任务,请首先登录为您希望该任务在其下运行的用户帐户。然后,发出以下命令:

crontab -e 

如果您的系统上有多个文本编辑器,您可能会看到类似以下的输出:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_07_10.png

图 7.10:选择与crontab命令一起使用的编辑器

在这种情况下,当您创建您的cron任务时,您只需按下对应于您想使用的文本编辑器的数字。要设置一个指定编辑器的环境变量,并用单个命令编辑您的crontab,以下命令将准确完成此操作:

EDITOR=vim crontab -e 

在这个例子中,您可以用您喜欢的文本编辑器替换vim。此时,您应该进入一个文本编辑器,并打开您的crontab文件。每个用户的默认crontab文件都包含一些有用的注释,这些注释提供了关于cron如何工作的一些有用信息。要添加一个新的任务,您需要滚动到文件的底部(在所有注释之后)并插入一行。格式在这里非常重要,文件中的示例注释会给出每行布局的一些线索。特别是这部分:

m h dom mon dow command 

每个cron任务有六个字段,每个字段之间至少由一个空格或制表符分隔。如果你使用多个空格或制表符,cron足够智能,能够正确解析文件。在第一个字段中,我们设置任务执行的分钟数。在第二个字段中,我们设置 24 小时制的小时数,范围从 0 到 23。第三个字段表示月份中的天数。在该字段中,你可以填写 5(5 号)、23(23 号)等。第四个字段对应于月份,比如 3 代表 3 月,12 代表 12 月。第五个字段是星期几,数字从 0 到 6,分别表示星期日到星期六。最后一个字段是要执行的命令。以下是几个crontab行的示例:

3 0 * * 4 /usr/local/bin/cleanup.sh 
* 0 * * * /usr/bin/apt update 
0 1 1 * * /usr/local/bin/run_report.sh 

在第一个例子中,位于/usr/local/bincleanup.sh脚本将在每周四凌晨 12:03 运行。我们知道这一点,因为分钟列设置为3,小时列设置为0(午夜),日期列为4(星期四),而命令列显示了完全限定的命令/usr/local/bin/cleanup.sh

命令的完全限定是什么意思?基本上,命令完全限定意味着完全写出负责该命令的二进制文件的完整路径。在第二个例子中,我们本可以简单地输入apt update命令,并且可能能够正常运行。然而,不包括程序的完整路径被认为是糟糕的cron习惯。虽然没有完全限定的命令可能仍然能成功运行,但其成功与否取决于调用该命令的用户路径中是否能找到该应用程序。并非所有服务器都以相同的方式设置,因此根据外壳的设置,可能无法运行。如果包含完整路径,则无论底层外壳如何配置,任务都应能正常运行。

如果你不知道完全限定的命令是什么,只需使用which命令。这个命令,当与你想要运行的命令名称一起使用时,将返回该命令在系统中的完全限定路径。

继续第二个例子,我们正在运行/usr/bin/apt update来每天凌晨 12 点更新服务器的仓库索引。每一行的星号代表任何,因此分钟列为*时,意味着此任务可以在任何分钟执行。基本上,我们唯一明确的是小时字段,我们将其设置为0,表示凌晨 12 点。

在第三个例子中,我们在每个月的第一天凌晨 01:00 运行/usr/local/bin/run_report.sh脚本。如果你注意到,我们将第三列()设置为1,这与 2 月 1 日、3 月 1 日等相同。这个任务将在每月的第一天执行,但只有在当前时间也是凌晨 01:00 时才会执行,因为我们填充了第一列和第二列,分别代表分钟和小时。

一旦你编辑并保存了用户的crontabcron会被更新,从那时起,它将在你选择的时间执行任务。crontab将根据你服务器上的当前时间和日期执行,所以你要确保时间是正确的,否则你的任务会在意外的时间执行。你可以通过简单地运行date命令查看你服务器上的当前日期和时间。

要掌握使用cron创建作业,最好的方法(一如既往)是多加练习。第二个示例cron作业可能是一个不错的实验对象,因为更新你的仓库索引不会带来任何坏处。

总结

在本章中,我们学习了如何管理进程。我们首先查看了ps命令,可以用它来查看当前正在运行的进程列表。我们还了解了如何管理作业,以及如何终止一些因某些原因而不正常的进程。我们还讨论了如何改变进程的优先级,以确保我们可以完全控制哪些进程获得更多的处理时间,并且我们还学会了如何使用cron安排任务在稍后的时间和日期执行。

第八章监控系统资源中,我们将探讨一些方法,通过它们我们可以关注服务器上可用的资源,在这里我们将学习如何检查磁盘使用情况,理解内存使用情况和交换空间,同时也会看一些可以让资源管理变得轻松的工具。

相关视频

进一步阅读

加入我们的 Discord 社区

加入我们的社区 Discord 空间,与作者和其他读者讨论:

packt.link/LWaZ0

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/QR_Code50046724-1955875156.png

第八章:监控系统资源

在上一章中,我们学习了如何管理服务器上运行的任务。我们现在知道如何查看后台运行的任务,如何启用或禁用某个单元在启动时自动启动,以及如何安排任务在未来某个时间运行。但是,为了有效管理服务器执行的任务,我们还需要时刻关注系统资源。如果我们内存不足,磁盘已满,或者 CPU 过载,那么本来高效处理任务的服务器可能会突然停滞。在这一章中,我们将探讨这些资源及如何监控它们。

我们关于资源管理的讨论将包括:

  • 查看磁盘使用情况

  • 监控内存使用情况

  • 理解负载平均值

  • 使用 htop 查看资源使用情况

我们服务器上的一个非常重要的资源是存储,跟踪可用磁盘空间等内容至关重要——即使是你能购买到的最强大服务器,也无法在没有空闲磁盘空间的情况下正常运行。我们将在下一节中探讨一些监控磁盘使用情况的方法。

查看磁盘使用情况

时刻关注你的存储空间非常重要,因为没有人愿意在半夜接到电话,说服务器遇到了问题,尤其是那种本可以轻松避免的问题,例如文件系统空间即将满了。在 Linux 系统上管理存储很简单,一旦你掌握了相关工具,这一过程就会变得容易。接下来我将介绍一些在这一节中最有用的工具,特别是我们将看看如何回答“是什么占用了所有磁盘空间?”这个问题,这是处理磁盘使用情况时最常遇到的问题。

首先,让我们来看一下 df 命令。

使用 df

df 命令通常会是你在不知道哪个卷或挂载点即将满时的起点。执行它后,它会给出一个高层次的概述,因此在你想弄清楚是谁或什么占用了所有空间时,它不一定会非常有用。然而,当你只想列出所有挂载的卷,并查看每个卷剩余的空间时,df 是非常合适的。默认情况下,它会以字节为单位显示信息。不过,我发现使用 df 时加上 -h 选项更加方便,这样可以显示更具可读性的输出,读起来也更加轻松。试试看:

df -h 

这应该会生成类似以下内容的输出:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_08_01.png

图 8.1:df -h 命令的输出

输出的结果会根据系统中与磁盘和挂载点相关联的类型有所不同。在截图中,你会看到根文件系统位于/dev/mapper/ubuntu--vg-ubuntu--lv。我们知道这一点是因为在“挂载点”这一列下,我们看到挂载点被设置为一个单一的正斜杠(/)。正如我们在第四章导航和基本命令》中讨论的那样,这个单一的正斜杠表示文件系统的起始位置(也称为根文件系统)。在我的情况下,这是一个 LVM 卷,因此我们看到一个从/dev/mapper开始的长设备名称。现在不必担心 LVM,我们稍后会讨论这个话题。不过目前,记住单一的正斜杠指的是文件系统的起点,左侧的设备名称则指的是实际挂载在那里设备的名称。

实际设备名称因服务器不同而异,且也取决于你在安装过程中是否选择使用 LVM。除了从/dev/mapper开始的长路径外,你可能会看到设备名称如/dev/sda1/dev/xvda1/dev/nvme0n1p1或其他变种。设备名称是根据底层存储设备的硬件类型生成的,例如用于 NVME 硬盘的/dev/nvme...命名约定,标准 SATA 硬盘则使用/dev/sdaN,依此类推。

实际底层存储硬件的设备类型并不那么重要;真正重要的是你能够识别出哪个设备最有可能变满。在示例截图中,根文件系统使用了35%的可用空间。在这种情况下,我们并不担心空间会被用完。

如果你确实发现某个重要的存储卷已满或接近满,那么你就能确定需要关注哪个卷,接下来我们会探索更多方法,帮助你获取有关哪些内容占用了空间的更多信息。

然而,有时即使存储卷看似有足够的可用空间,它仍然可能被视为已满。这是因为在 Linux 系统中,存储的数据和数据的大小并不是唯一需要考虑的因素。我们还需要考虑 inode。

那么,inode 究竟是什么?为什么这种东西会导致磁盘被报告为已满,实际上它并没有满呢?可以将 inode 看作一种数据库对象,它包含了你存储的实际项目的元数据。存储在 inode 中的信息包括文件的所有者、权限、最后修改日期和类型(是目录还是文件)。尽管元数据有它的好处,但 inode 的问题在于,每个存储设备上可用的 inode 数量是有限的。如果一个存储设备达到 inode 限制,那么该磁盘仍然被视为已满,无法接受更多数据。

实际上,这种情况的症状是,像df这样的命令会显示磁盘有可用空间,但当你尝试将新文件保存到设备时,会看到一个错误,提示你无法保存,因为磁盘已满。如果你不了解 inode 的存在,那么这种情况可能会让人感到困惑。

尽管似乎有 inode 限制会给存储带来不便,实际上,存储卷上的 inode 限制通常非常高,并且很难达到。通常情况下,如果 inode 限制被触及,意味着服务器本身可能存在更大的问题,导致它达到这个限制。例如,可能是服务器存在问题,保存了比应有的更多文件,例如异常数量的日志文件或排队的电子邮件消息文件。

幸运的是,确定特定存储卷是否快用完 inode 非常简单——与其使用df命令的-h选项,不如改用-i选项。-i选项将显示 inode 计数,而不是基于大小的存储度量。为了帮助说明这一点,我会展示一些来自我服务器的输出,帮助你了解这是什么样子的:

df -i 

我系统上该命令的输出如下:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_08_02.png

图 8.2:df -i命令的输出

在这个例子中,示例服务器上的根文件系统总共有999424个 inode,其中84223个已被使用,915201个是空闲的。在我的情况下,我有很多可用的 inode。不过,我建议你把df -hdf -i命令记住。不论你遇到的存储空间问题是与实际空间还是 inode 使用相关,使用这两个命令就能知道具体是哪个问题。

假设你有的存储快满了(或者已经满了),你该如何准确定位是哪个东西占用了所有空间呢?有一些额外的工具可以帮助你缩小范围。接下来我们就来探讨这一点。

深入了解磁盘使用情况

调查占用你磁盘空间的具体文件的下一步是找出哪些文件特别占用了这些空间。在这个阶段,你可以使用多种工具来进行调查。我要提到的第一个工具是du命令,它能够显示一个目录占用了多少空间。使用du命令对目录和子目录进行扫描将帮助你缩小问题范围。像df一样,我们也可以与du命令一起使用-h选项,以使输出更容易阅读。默认情况下,du会扫描当前 shell 附加的工作目录,并列出该目录中的每一项,显示每项所占用的总空间,并在最后给出总结。

du命令只能扫描调用该命令的用户有权限访问的目录。如果你以非 root 用户身份运行该命令,可能无法获取完整的信息。此外,当前工作目录中的文件和子目录越多,执行此命令的时间也会越长。如果你大致知道资源占用的地方在哪里,可以尝试cd进入文件系统树中更深的目录,缩小搜索范围,从而减少命令执行的时间。du -h的输出通常会比实际需要的更多,可能需要多个屏幕才能查看完。为了简化这一过程,我最喜欢的命令变体是:

du -hsc * 

基本上,你需要在尽可能接近问题所在的目录中运行du -hsc *。正如我们所知道的,-h选项会让输出结果更易读(基本上是以兆字节、千兆字节等形式呈现)。-s选项会提供一个总结,而-c则会显示当前工作目录中所使用的总空间。下面的截图显示了我电脑中的输出结果:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_08_03.png

图 8.3:du -hsc *命令的示例输出

为了让这个示例更有趣,我从我的个人桌面上截图,但无论使用哪个设备,最终的命令和语法是一样的。如你所见,du -hsc *提供的信息是一个简洁的总结。从输出中,我们可以清楚地看到当前工作目录中的每个子目录占用了多少空间。例如,我的projects目录当前占用了 2.2GB 的空间,而 ISO 镜像文件占用了 53GB 的空间。

到这一步,我们已经知道当前工作目录顶部的哪些目录占用了最多的空间。但我们仍然需要缩小范围,查找这些目录中究竟是 哪些 项目在占用这些空间。为了深入分析,我们可以 cd 进入这些大目录中的任何一个,再次运行 du 命令。经过几次操作后,我们应该能够缩小到这些目录中最大的文件,并决定如何处理它们。也许我们可以清理不必要的文件,或者添加另一个磁盘。一旦知道了是什么占用了我们的空间,我们就可以决定如何处理。

在阅读到本书的这一部分时,你可能已经认为我有一种奇怪的习惯,总是喜欢把最好的留到最后。你猜对了。我想通过介绍我最喜欢的应用之一来结束这一节,那就是 NCurses 磁盘使用情况 工具(简称 ncdu)。ncdu 命令是那些经常处理磁盘空间问题的管理员学会喜爱和欣赏的工具之一。通过一次执行,这个命令不仅能给你提供占用空间的概览,还能让你在无需反复运行命令和手动浏览目录树的情况下,浏览并深入查看结果。你只需执行一次,然后可以导航结果,深入挖掘直到你需要的程度。

要使用 ncdu,你需要安装它,因为它默认不随 Ubuntu 提供:

sudo apt install ncdu 

安装完成后,只需在你选择的任何起始目录中执行 ncdu。完成后,只需按 q 键退出。像 du 一样,ncdu 只能扫描调用用户有权限访问的目录。你可能需要以 root 身份运行它,以获得准确的磁盘使用情况。

你可能想考虑在使用 ncdu 时添加 -x 选项。此选项会将扫描限制在当前文件系统内,这意味着它不会扫描网络挂载点或其他存储设备;它只会关注你开始扫描的设备。这可以避免你浪费时间扫描与问题无关的区域。

执行时,ncdu 将从其起始位置开始扫描每个目录。完成后,它会提供一个基于菜单的布局,允许你浏览结果:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_08_04.png

图 8.4:ncdu 实时展示

再次说明,我从我的桌面截图中获取了这张图,截图的目录是在我的 home 目录中。ncdu 的作用是展示从当前目录开始的磁盘使用情况,并且它会按使用空间从高到低排列结果。在 ncdu 内部移动时,你可以通过键盘上的上下箭头来移动你的选择(长白色高亮显示)。

如果你在目录上按下Enterncdu将切换到显示该目录的摘要,你可以根据需要继续深入查看。实际上,你还可以按d删除项目和整个文件夹。因此,ncdu不仅允许你找出占用空间的内容,还允许你采取行动!

有时候,磁盘上占用空间的内容很明显,ncdu可能并不总是必要的。一般来说,你会通过df -h开始调查,查看哪个存储卷空间不足。然后,你进入该目录并执行另一个命令,如du -hsc *,以查看哪个目录占用了最多的空间。如果从du的输出中无法立即识别问题所在,那么可以考虑使用像ncdu这样的工具,进一步深入查找。

尽管监控存储至关重要,我们还需要时刻关注空闲内存。接下来,我们将看看如何监控我们服务器的内存。

内存使用情况监控

我经常忘记事情。即便车钥匙几乎总是就在我的口袋里,我也经常忘记它们在哪里。即使已经使用 Linux 超过 20 年,我还是经常忘记在需要时使用sudo命令。幸运的是,计算机的记忆比我强大,但如果我们不有效地管理它,我们服务器上的内存就像我忘记把刚洗完的衣服放进干衣机一样,毫无用处。

了解 Linux 如何管理内存实际上是一个相对复杂的话题,因为对于初学者来说,理解内存到底有多少是空闲的,可能会是一个难以克服的障碍。你会很快发现,一旦解释清楚,Linux 如何管理你服务器的内存其实是相当简单的。

了解服务器内存

为了监控我们服务器的内存使用情况,我们可以使用free命令,这个命令可以让我们查看在任何给定时刻内存的消耗情况。若不带任何选项地执行free命令,输出将以千字节为单位显示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_08_05.png

图 8.5:free命令的输出结果

我最喜欢的这个命令的变种是free -m,它以兆字节为单位显示内存的使用量。你也可以使用free g以吉字节为单位显示输出,但在大多数服务器上,输出的精度不足。依我看,添加-m选项能让free命令的输出更易于阅读:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_08_06.png

图 8.6:free -m命令的输出结果

由于一切都以兆字节为单位显示,对我来说,阅读起来要容易得多。

刚开始看,可能会觉得这个服务器只有277 MB 的空闲内存。你会在第一行和第三列的free下看到这个数值。实际上,你真正需要关注的数字是available列下的数字,这里是2943 MB。那才是实际可用的内存。因为这个服务器总共有3925 MB 的 RAM(你会在第一行的total下看到),这意味着大部分内存是空闲的,这台服务器其实并没有在高负荷运行。

若要真正理解这些数字,需要一些额外的解释。你完全可以在此时停止阅读这一部分,只要记住available列表示的是应用程序可以使用的空闲内存量即可。不过,事情并没有那么简单。

从技术上讲,当你查看我的输出时,服务器实际上只有277 MB 的空闲内存。available列下列出的内存量实际上是被系统用作缓存的,但如果任何应用程序需要它,它将被释放。如果一个应用程序启动并需要较大内存来运行,内核会从这个缓存中提供一些内存。

Linux 与大多数现代系统一样,遵循“未使用的 RAM 就是浪费的 RAM”这一理念。未被任何进程使用的 RAM 会分配给所谓的文件系统缓存,用于提高服务器的运行效率。当数据需要写入存储设备时,并不会直接立即写入。相反,这些数据会先写入文件系统缓存(一个预留的 RAM 部分),然后在后台同步到存储设备。这样做使得服务器更高效的原因在于,存储在 RAM 中的数据相较于从硬盘读取,要写入和读取得更快。应用程序和服务可以在后台同步数据到硬盘,而不需要你等待。此外,这个缓存也适用于读取数据,比如当你第一次打开一个文件时,它的内容会被缓存。如果你再次读取该文件,系统会从 RAM 中获取,而不是每次都从存储设备加载。若你刚保存了一个新文件并立即访问它,很可能它仍然在缓存中,会从缓存中提取,而不是直接从硬盘读取。

为了理解图 8.6中显示的所有列,我将在下表中列出每列的含义:

含义
total服务器上安装的总内存量。
used使用的内存(来自任何来源)。计算方法如下:used = total - free - buffers/cache
free没有被任何东西使用的内存,无论是缓存还是其他。
sharedtmpfs以及其他共享资源使用的内存。
buff/cache用于缓冲区和缓存的内存量。
available应用程序可以使用的空闲内存。

您可能已经注意到在 图 8.6 中,还列出了一个名为 swap 的资源的内存使用情况。我们也来看看这个。我们将专门为它保留下一节,以确保我们理解它是什么,以及它对我们有什么作用。

管理 swap

swap 是我们从不想要使用的东西之一,但总是希望确保它是可用的。这有点像汽车保险,没有人会为购买它感到兴奋,但如果发生不好的事情,我们确实希望有它。甚至在管理员之间,关于 swap 在今天是否仍然相关还存在一些争论。无论别人怎么说,它绝对是相关的,因为它就像一种安全网。(而且现在磁盘空间更便宜了,所以将一些存储空间专门用于这项任务真的不是什么大问题,所以我们也可以这样做。)

那么它是什么? swap 基本上是一个在服务器内存饱和时充当 RAM 的分区或文件。如果我们正确管理服务器,希望永远不需要它,因为 swap 存储在硬盘上,比 RAM 慢几个数量级。但是,如果服务器出现问题,内存使用量飙升,swap 可能会防止服务器崩溃。当内存满时,内存不足杀手(OOM Killer)也可能自动激活,以终止使用大部分内存的行为不端进程,但我们尽可能不希望依赖它,而是确保有足够的 swap 以防内存耗尽。

在 Ubuntu 中,默认实现 swap 的方式是通过一个 swap 文件。在早期版本的 Ubuntu(具体来说是 16.04 及之前的版本),它是通过一个 swap 分区实现的。事实上,如果你有一个已经升级到 Ubuntu 22.04 的现有服务器,并且它在旧版 Ubuntu 上启动过,你可能仍然有一个 swap 分区,即使你正在运行最新的发布版。在 16.04 之后进行的新安装的 Ubuntu 将使用 swap 文件。

使用 swap 文件比使用 swap 分区好吗?我会说是的,它更受欢迎 - 尽管在性能方面你不会注意到任何差异。无论在服务器上 swap 是以 swap 文件还是分区的形式,它都不改变 swap 使用硬盘且比 RAM 慢的事实。与 swap 分区相比,swap 文件的一个好处是,它比 swap 分区更容易扩展或收缩。

总之,考虑到 swap 文件是未来首选的方法(并且是新的默认设置),我不会再覆盖创建 swap 分区的过程,因为没有必要再这样做了。

你的服务器的swap文件在/etc/fstab文件中声明(我们将在第九章管理存储卷中更详细地讨论/etc/fstab文件)。在大多数情况下,你在安装过程中已经为你创建了swap文件。当然,如果出于某种原因你没有swap文件,你可以稍后添加一个。在一些云实例提供商的情况下,默认情况下你可能不会获得swap文件。在这种情况下,你需要自己创建一个swap文件(我们将在本节稍后讨论这个过程),然后使用swapon命令来激活它:

sudo swapon -a 

当运行swapon -a命令时,它会在/etc/fstab中找到你的swap文件(如果那里列出了它),挂载并激活它供使用。这个命令的反向操作是swapoff -a,它会停用你的swap文件。除非你计划删除swap文件并创建一个更大的文件,否则通常不需要禁用swap。如果你发现服务器的swap不足,可能需要采取这种操作。

虽然有swap通常是一个好主意,但实际上有些应用程序更倾向于服务器完全没有swap。没有swap并不是一个常见的要求,但 Kubernetes 就是一个可能需要完全禁用swap的情况的好例子。事实上,如果你启用了swap,Kubernetes 的安装过程可能会报错(或者可能会失败)。对于 Kubernetes 集群来说,集群内的每个单独服务器本身就是一个特殊的情况,每个服务器都专门用于运行容器(这正是 Kubernetes 的功能;更多内容将在第十八章容器编排中介绍)。

当你检查空闲内存时(提示:执行free -m),无论你是否有swap,都会看到swap被列出,但当swap被停用时,你会看到所有大小总计为零。

那么,如果你希望使用swap并且当前没有swap文件,应该如何创建一个呢?首先,你需要创建一个实际的文件来用作swap。它可以存储在任何地方,但通常/swapfile是理想的选择。你可以使用fallocate命令来创建这个实际的文件。fallocate命令会强制文件为特定大小:

sudo fallocate -l 4G /swapfile 

在这里,我正在创建一个 4 GB 的swap文件,但你可以根据需要设置任何大小,以满足你的需求。接下来,我们需要准备这个文件以便用作swap。首先,我们需要修复文件的权限,因为我们需要这个文件比大多数文件更具限制性:

sudo chmod 0600 /swapfile 

然后,我们可以使用mkswap命令将此文件转换为实际的swap文件:

sudo mkswap /swapfile 

现在,我们已经在根文件系统上存储了一个方便的swap文件。接下来,我们需要挂载它。像往常一样,推荐将其添加到/etc/fstab文件中。以下是一个示例条目:

/swapfile   none   swap   sw   0 0 

从这一点开始,我们可以通过之前提到的swapon命令来激活新的swap文件:

sudo swapon -a 

运行该命令后,swap文件应该已激活并正在使用。您可以通过运行free -m命令,查看是否列出了swap文件,并且其大小大于 0 来验证这一点。尽管我真心希望您不需要使用swap,但根据我的经验,这只是时间问题。了解在需要时如何添加并激活swap绝对是一个好习惯,不过大多数情况下,您应该没问题,因为在大多数平台上,首次安装 Ubuntu 时,swap会自动创建。如果您出于某种原因需要手动创建swap,我总是建议在服务器上至少设置 2GB,或者根据您的使用情况适当增加。

您应当密切关注swap的使用情况。当内存开始接近满时,服务器会开始使用swap文件。即使大部分 RAM 空闲,swap也会被部分使用,这很正常。但如果大量swap被使用,就应该调查一下(可能某个进程使用了比平常更多的内存)。

实际上,您可以控制服务器何时开始使用swap。Linux 服务器使用swap的频率被称为其swappiness。默认情况下,Linux 服务器的swappiness值通常设置为60。您可以通过以下命令验证这一点:

cat /proc/sys/vm/swappiness 

swappiness值越高,服务器越有可能使用swap。如果将swappiness值设置为100,服务器会更频繁地使用swap;如果将其设置为0,则会很少使用swap。该值大致与使用的 RAM 百分比相关。

要实时更改此值,您可以执行以下命令:

sudo sysctl vm.swappiness=30 

执行该命令后,swappiness值的更改将立即生效。然而,一旦重启服务器,该值将恢复默认。为了使更改保持生效,请使用您选择的文本编辑器打开以下文件:

/etc/sysctl.conf 

在该文件中,通常不会默认包含与swappiness对应的行,但您可以手动添加。为此,请在文件末尾添加类似以下内容的行并保存:

vm.swappiness = 30 

更改此值是性能调优领域中的多种技术之一。虽然默认值60对大多数情况可能足够,但可能会遇到运行性能密集型应用程序的情况,在这种情况下,您无法承受过度使用swap。在这种情况下,您可以尝试不同的swappiness值,并在性能测试中使用最适合的值。

在下一部分中,我们将关注另一个重要的指标:负载平均值。负载平均值可以帮助我们了解 CPU 的忙碌程度,从而更好地判断何时服务器过载,可能需要采取措施。

理解负载平均值

另一个在监控性能时需要理解的非常重要的话题是负载平均值,它是一系列数字,表示服务器在特定时间段内 CPU 使用率的趋势。你可能已经见过这些数字,因为负载平均值会出现在多个地方。例如,如果你运行htop工具,屏幕上会显示负载平均值。此外,如果你执行uptime命令,你也可以在该命令的输出中看到负载平均值。你还可以通过查看存储负载平均值的文本文件来查看它:

cat /proc/loadavg 

就个人而言,我习惯使用uptime命令来查看负载平均值。uptime命令的主要功能是显示服务器运行的时间,并且每次你关机或重启服务器时,时间会重置。但是,除了显示服务器开机的时长,uptime命令还会显示当前服务器的负载平均值。

理解负载平均值一开始可能有点困惑,但你很快就会意识到它并不像看起来那么复杂。负载平均值是一组三个数字,每个数字对应一个时间段。从左到右,这些数字分别对应 1 分钟、5 分钟和 15 分钟。一个典型的负载平均值可能是以下这样的:

0.36, 0.29, 0.31 

在这个例子中,我们在 1 分钟、5 分钟和 15 分钟的时间段内分别有0.360.290.31的负载平均值。特别地,每个数字表示在给定时间段内,有多少任务在等待 CPU 处理。因此,这些数字非常理想。服务器并不繁忙,因为几乎没有任务在任何时刻等待 CPU(每个数字都小于 1)。这与诸如总体 CPU 使用百分比之类的指标不同,你可能在其他平台的任务管理器中见过,甚至在像htop这样的 Linux 工具中也能看到。虽然查看 CPU 使用百分比可能有用,但问题在于,CPU 使用率会不断从高使用率变到低使用率,你可以通过运行htop一段时间来亲自观察。当任务进行某种处理时,你可能会看到 CPU 核心使用率飙升到 100%,然后迅速下降到较低的数字。然而,这并不能告诉你太多。而负载平均值则展示了在三个给定时间段内的使用趋势,这在判断服务器的 CPU 是否高效运行或是否因无法处理的工作负载而陷入困境时更加准确。

然而,主要的问题是,你应该在什么时候开始担心,这实际上取决于你服务器上安装了什么样的 CPU。你的服务器将有一个或多个 CPU,每个 CPU 都有一个或多个核心。对于 Linux 系统来说,这些核心无论是物理的还是虚拟的,都是一样的(即 CPU)。在我的案例中,我之前输出数据的机器有一个四核的 CPU。

你服务器上的 CPU 越多,它在任何给定时间能够处理的任务就越多,这也意味着它能够处理更高的负载平均值。

当某个时间段的负载平均值等于系统上的 CPU 数量时,这意味着你的服务器达到了 100% 的容量。它正在处理的任务数与它能够处理的任务数相等。例如,如果你有一个 8 核的 CPU,而负载平均值在某个时间段是 8,那么 CPU 在该时间段内就是 100% 满负荷运行的。如果你的负载平均值持续高于可用核心数,那时你可能就需要关注这个情况。偶尔达到服务器满载是可以接受的,但如果一直处于满载状态,那就需要引起警惕了。

我不太愿意使用陈词滥调的例子来充分说明这个概念,但我忍不住,所以还是用了这个。Linux 服务器上的负载平均值就相当于超市的结账区。超市会有多个收银台开放,顾客可以在这里支付并完成购物。在我所在地区典型商店的经验中,你会看到大约 20 个收银台,但每次只有两个收银员在工作,不过在这个例子中,我们假设每个收银台都有一个收银员在操作。

每个收银员一次只能处理一个顾客。如果等待结账的顾客多于收银员,排队就会开始积压,顾客会感到沮丧。在有四个收银员且此时有四个顾客的情况下,收银员将达到最大承载量,但这并不是什么大问题,因为没有其他顾客在等候。可能会加剧这个问题的是某个顾客使用支票支付和/或使用几打优惠券,这样结账过程就会变得更加耗时(类似于资源密集型进程)。如果有四个收银员,而有六个顾客在等待,那就意味着有两个顾客超出了商店同时能够处理的数量。在这种情况下,商店的结账区域将超出容量。这基本上就是负载平均值的工作原理。每个收银员就是一个 CPU,每个顾客就是一个需要 CPU 时间的进程。

就像收银员一样,每个 CPU 一次只能处理一个任务,有些任务会占用 CPU 更长时间。如果任务的数量恰好和 CPU 数量一样,那就无需担心。但如果排队开始积压,我们可能需要调查是什么导致了这种延迟。为了控制局面,我们可以雇佣一个额外的收银员(增加一个 CPU),或者请一个不满的顾客离开(终止一个进程)。

让我们看一个其他的负载平均值示例:

1.87, 1.53, 1.22 

在这种情况下,我们不需要担心,因为我们的假设服务器有 4 个 CPU,而且在 1 分钟、5 分钟和 15 分钟的时间段内,没有任何一个 CPU 处于满负荷状态。即使负载始终高于 1,我们仍然有 CPU 资源剩余,所以这并不是什么大问题。如果我们拥有 AMD 的那些非常强大的 Threadripper CPU(可以包含大量的核心),那么这些数字就代表着极其低的负载。回到我们的超市比喻,前面例子中的负载平均值相当于有四个收银员在每一分钟内平均为几乎两名顾客提供服务。如果这台服务器只有一个 CPU,我们可能需要搞清楚是什么导致了队伍的积压。

虽然你可能理性地认为低负载平均值是件好事,但实际上,根据具体情况,它可能代表着一个非常大的问题。当我们部署服务器时,我们是为了完成某种工作。

无论这“工作”是托管一个应用程序,还是运行任务处理数据,我们的服务器需要在执行某些工作,否则我们就是在浪费钱。如果服务器的负载平均值下降到异常低的值,这可能意味着一个本应始终运行的服务已失败并退出。例如,如果你的数据库服务器的负载通常在 1.x 范围内,突然降到 0.x,这可能意味着你要么流量确实减少了,要么数据库服务器服务已经停止运行。这就是为什么总是建议为服务器制定基准,以便判断什么是正常的,什么不是。基准通常指的是资源使用情况。如果资源使用量显著高于或低于基准,这无论如何都是潜在的关注点。

总的来说,如果你还没有遇到过,作为 Linux 管理员,你会非常熟悉负载均值。它是你服务器利用情况的时间快照,帮助你理解服务器什么时候运行高效,什么时候出现问题。如果服务器无法应对你分配的工作负载,可能是时候考虑增加核心数(如果可以的话)或将工作负载分配到其他服务器上。在排查资源利用问题、规划升级或设计集群时,整个过程通常是从了解服务器的负载均值开始的,这样你可以为其指定的任务有效地规划基础设施。

现在我们已经讲解了需要监控的重要资源,以确保我们的服务器保持健康,让我们来看看一个有用的工具,它能让资源使用情况更加易于理解。

使用 htop 查看资源使用情况

想要查看服务器的整体性能时,没有什么比htop更好的工具了。虽然它通常不是默认安装的,但htop是我推荐每个人尽早安装的工具,因为它在查看服务器资源利用情况时是不可或缺的。事实上,它有如此的实用性,以至于我在本章的前面就已经多次提到过它,即便我们在本节才开始讨论它。它真的是一个很棒的工具。

如果你还没有安装htop,只需使用apt安装即可:

sudo apt install htop 

当你在终端提示符下运行htop时,你会看到htop应用程序的完整显示。在某些情况下,以root身份运行htop可能会更有利,因为这样你可以获得额外的选项,比如能够终止进程,尽管这并不是必需的:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_08_07.png

图 8.7:运行 htop

htop显示的顶部,你会看到每个核心的进度条(我截图时使用的服务器只有一个核心),以及内存和swap的进度条。此外,顶部还会显示你的UptimeLoad average和当前正在运行的Tasks数量。htop显示的下半部分会列出服务器上正在运行的进程,字段中会显示每个进程消耗的内存或 CPU,以及正在运行的命令、运行该命令的用户和其进程 IDPID)。我们在第七章《控制和管理进程》中讨论过 PID。要滚动查看进程列表,你可以按Page UpPage Down,或者使用箭头键。此外,htop还支持鼠标操作,你可以点击顶部的列标题来按该标准对进程列表进行排序。例如,如果你点击MEM%CPU%,进程列表将按内存或 CPU 使用率排序。显示的内容会每 2 秒更新一次。

htop 工具也是可以定制的。例如,如果您更喜欢不同的颜色方案,可以按 F2 进入 Setup mode,然后在左侧导航到 Colors,选择六种提供的颜色方案之一。其他选项包括添加额外的计量表、增加或删除列等。对于多核服务器,我特别觉得有用的一个调整是能够添加一个 CPU 平均负载条。通常,htop 会显示服务器上每个核心的计量表,但如果您有多个核心,您可能也会对平均值感兴趣。为此,请再次进入 Setup mode(按 F2),然后在 Meters 高亮时,右箭头选择 CPU average,接着按 F5 将其添加到左侧列。您还可以添加其他计量表,如 Load averageBattery 等。

根据您的环境,功能键可能在终端程序中无法正常工作,例如 htop,因为这些键可能已被映射到其他功能。例如,F10 用于退出 htop,但如果 F10 被映射到终端仿真器中的某个功能,这可能不起作用,使用虚拟机解决方案(如 VirtualBox)也可能会导致某些键无法正常工作。您还可以通过鼠标导航 htop,即使是在 SSH 连接中。这也意味着,您可以点击右下角的 Quit 来退出应用程序。

这是一个配置了 CPU 平均负载计量表的 htop 示例:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_08_08.png

图 8.8:添加了 CPU 平均负载计量表的 htop

打开 htop 后,您将看到系统中每个用户的进程列表。当您遇到不知道是哪个用户/进程导致极端负载的情况时,这种方式非常理想。不过,一个非常实用的技巧(如果您想查看特定用户)是按下 u 键,这将打开 Show processes of: 菜单。在此菜单中,您可以使用上下箭头键选择一个特定用户,并按 Enter 仅显示该用户的进程。这将大大缩小进程列表。

另一个有用的视图是 树状视图,它允许您查看按父子关系组织的进程列表,而不仅仅是一个平坦的列表。在实际操作中,进程通常是由另一个进程生成的。事实上,Linux 中的所有进程都是从至少一个其他进程派生出来的,而这个视图直接显示了这种关系。在您停止一个进程,但它会立即重新生成的情况下,您需要知道该进程的父进程,以便阻止它自我恢复。按 F5 将切换到树状视图模式,再次按 F5 将禁用树状视图。

启用树状视图后,htop 将显示如下:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_08_09.png

图 8.9:启用树状视图的 htop

正如我提到的,htop 默认每 2 秒更新一次其统计信息。就个人而言,我觉得这个更新频率是可以接受的,但如果你想改变刷新速度,可以在调用 htop 时使用 -d 选项,然后设置刷新间隔(以十分之一秒为单位)。例如,要使 htop 每 7 秒更新一次,可以通过以下命令启动 htop

htop -d 70 

要使用 htop 杀死一个进程,可以用上下箭头键选择你想要结束的进程,然后按 F9。会弹出一个新菜单,列出你可以通过 htop 发送给该进程的信号。如我们之前讨论过的,SIGTERM 将尝试优雅地终止该进程,而 SIGKILL 则会强制结束它。一旦选中你想发送的信号,按 Enter 发送,或者按 Esc 取消操作。

正如你所见,htop 非常有用,并且(在大多数情况下)已经取代了以前在许多管理员中流行的老旧 top 命令。top 命令在 Ubuntu 服务器中默认可用,值得一看,哪怕只是与 htop 做个比较。和 htop 一样,top 命令也会列出你服务器上正在运行的进程及其资源使用情况。虽然没有漂亮的仪表盘,且定制化较少,但 top 命令能完成相同的任务。然而,在大多数情况下,htop 可能是你未来的最佳选择。

总结

在本章中,我们学习了如何监控服务器的资源使用情况。我们首先了解了可以用来查看磁盘使用情况的命令,并学习了如何监控内存使用情况。我们还讨论了 swap,包括它是什么,为什么你需要它,以及如何在需要时手动创建 swap 文件。接着,我们看了负载平均值,并通过查看 htop 来结束本章,这是我最喜欢的一个工具,可以全方位查看服务器的资源使用情况。

第九章管理存储卷 中,我们将深入探讨存储。在本章中,我们学习了如何查看使用情况,而在下一章中,我们将研究有关存储的更多高级概念,如格式化卷、添加额外的卷,甚至是 LVM。到时见!

相关视频

进一步阅读

加入我们的 Discord 社区

加入我们社区的 Discord 空间,与作者和其他读者进行讨论:

packt.link/LWaZ0

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/QR_Code50046724-1955875156.png

第九章:管理存储卷

在我们服务器的存储管理中,似乎永远也不够用。虽然硬盘的容量每年都在增长,而且大容量硬盘比以往更便宜,但我们的服务器很快就会消耗掉所有可用空间。作为服务器管理员,我们总是尽力为服务器选择充足的存储空间,但随着业务需求的变化,无论我们如何规划,一个成功的企业始终会需要更多的存储空间。在管理服务器的过程中,你很可能会遇到需要增加额外存储的情况。但存储管理不仅仅是每次磁盘满了就添加新磁盘。提前规划同样重要,像逻辑卷管理器LVM)这样的技术,只要尽早使用,就能极大简化你的工作。

LVM 本身就是本章将要讨论的一个概念,它能为你提供更多的灵活性来管理服务器。我还将带你了解其他一些概念,这些概念在你管理服务器存储和卷时无疑会派上用场。具体来说,本讨论将包括:

  • 将额外的存储卷添加到文件系统

  • 格式化和分区存储设备

  • 挂载和卸载存储卷

  • 理解/etc/fstab文件

  • 备份和恢复存储卷

  • 使用 LVM

当你的服务器磁盘空间不足时,一个可能的解决方案是添加一个额外的存储卷。因此,本章的首要任务是探讨我们如何实现这一目标。

添加额外的存储卷

在某个时刻,你可能会遇到需要为服务器增加额外存储的情况。在物理服务器上,我们可以添加额外的硬盘;而在虚拟或云服务器上,我们可以添加额外的虚拟磁盘。无论是哪种情况,为了充分利用额外的存储,我们需要确定设备名称、格式化它并挂载。

在 LVM 的案例中(我们将在本章后面讨论),我们有机会扩展现有的卷,通常不需要重新启动服务器。但在添加新设备时,仍然有一个整体流程需要遵循。当你向系统添加额外存储时,应该问自己以下问题:

你需要多少存储空间? 如果你要添加一个虚拟磁盘,通常可以根据需要设定其大小,只要你在虚拟化平台的存储池中有足够的剩余空间。

它连接后,设备名称是什么? 当新磁盘连接到服务器时,系统会检测到并分配一个设备名称。在大多数情况下,会使用 /dev/sda/dev/sdb 等命名方式。在其他情况下(如虚拟磁盘),命名可能不同,如 /dev/vda/dev/xda,甚至其他名称。命名方案通常以字母结尾,每增加一个磁盘,字母就会递增。

你希望如何格式化存储设备? 在撰写本文时,ext4 文件系统是最常见的选择。然而,对于不同的工作负载,你可能需要考虑其他选项(例如 XFS)。如果不确定,使用 ext4 是比较稳妥的选择,但一定要了解其他选项,看看它们是否能带来对你使用案例的好处。ZFS 也是一个可以考虑的选项,尽管与其他选择相比,它相对较新。我们将在下一节中讨论格式化内容,格式化和分区存储设备

对你来说这可能是常识,但文件系统这个词在 Linux 系统中有多重含义,具体取决于其上下文,这可能会让新手感到困惑。像我们这样的 Linux 管理员通常会用文件系统一词来讨论典型 Linux 系统中的文件和目录结构。然而,这个词也被用来描述如何格式化硬盘以供该发行版使用(例如,ext4 文件系统)。在本章中,我们将主要关注后者。

你希望它挂载在哪里? 新硬盘需要对系统和可能的用户可访问,因此你需要将其挂载(附加)到文件系统中的某个目录,以便你的用户或应用程序能够使用它。在本章中我们还将讨论的 LVM(逻辑卷管理),你可能希望将它附加到现有的存储组。你可以为新卷创建自己的目录,但我将在本章稍后讨论一些常见的位置。我们将在 挂载和卸载卷 部分中介绍挂载和卸载的过程。

让我们考虑一下前两个问题的答案。关于你应该增加多少空间,你需要研究你的应用程序或组织的需求,并找到一个合理的数量。对于物理硬盘,你基本上没有选择,除了决定购买哪种硬盘。对于虚拟硬盘,你可以更加节省,因为你可以添加一个小的硬盘来满足你的需求(以后可以随时增加更多)。

LVM 在虚拟硬盘上的主要优势是能够在不重启服务器的情况下扩展文件系统。例如,你可以从一个 30 GB 的卷开始,然后通过增加额外的 10 GB 虚拟硬盘来按 10 GB 的增量进行扩展。这种方法显然比一次性添加一个 200 GB 的卷要好,尤其是当你不确定这些空间是否都会被用到时。

LVM 也可以在物理服务器上使用,但通常还是需要重启,因为你需要打开机箱并物理连接硬盘。一些服务器支持热插拔,可以让你在不关闭服务器的情况下添加或移除物理硬盘,这是一个很大的优势。

接下来,可以使用fdisk -l命令找到设备名称。fdisk命令通常用于创建和删除分区,但它也能帮助我们确定新磁盘获得的设备名称。使用fdisk -l命令将显示相关信息,但你需要以root身份运行,或者使用sudo

sudo fdisk -l 

执行此命令会产生类似于以下的输出:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_09_01.png

图 9.1:fdisk -l命令的输出

我总是建议在连接新设备之前和之后都运行fdisk -l。这样,哪个设备名称代表新设备就会更加明显。

另一个技巧是使用以下命令,该命令会随着你添加磁盘而自动更新输出:

dmesg --follow 

只需启动命令,连接磁盘,观察输出。当完成时,按Ctrl + c键返回命令提示符。

你还可以通过lsblk命令找到新磁盘的设备名称。lsblk的好处是你不需要root权限,且它返回的信息是简化版的:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_09_02.png

图 9.2:lsblk命令的输出

在一台典型的服务器上,第一个磁盘(基本上就是你安装了 Ubuntu Server 的磁盘)将被命名为/dev/sda,而额外的磁盘将被分配下一个可用的名称,例如/dev/sdb/dev/sdc,以此类推(具体取决于你使用的硬盘类型)。如今,非易失性内存快速接口NVMe)硬盘越来越常见,因此你可能会看到类似/dev/nvme0n1的设备名称。你还需要知道分区编号。磁盘的设备名称后面会有数字,表示单独的分区。例如,/dev/sda的第一个分区将被命名为/dev/sda1,而/dev/sdc的第二个分区将被命名为/dev/sdc2。这些数字是递增的,通常很容易预测。正如我之前提到的,你的设备命名规则可能会因服务器而异,尤其是当你使用独立磁盘冗余阵列RAID)控制器或虚拟化主机(如 VMware 或 XenServer)时。如果你尚未在新磁盘上创建分区,你将不会看到名称末尾的任何分区编号。

现在,你已经添加并命名了一个额外的存储卷,我们可以继续进行设置过程。我们需要决定将其挂载到哪里,以及它的用途是什么。但在我们能够挂载存储设备之前,我们需要先在它上面创建至少一个分区,然后再对其进行格式化。我们将在下一节中处理这两个步骤。

格式化和分区存储设备

一旦你安装了物理磁盘或虚拟磁盘,你就能充分利用额外的存储空间。但为了使用磁盘,必须先对其进行格式化。为了确保我们格式化的是正确的磁盘,我们需要找到该设备的名称。正如你从上一节中了解到的,Linux 发行版使用特定的命名规则来命名磁盘。所以你应该已经知道新磁盘的设备名称。正如前面所解释的,你可以使用 sudo fdisk -l 命令来查看服务器上连接的存储设备的详细信息:

sudo fdisk –l 

这将产生类似以下内容的输出:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_09_03.png

图 9.3:使用 fdisk -l 查看服务器上的存储设备列表

在我的情况下,设备 /dev/sdb 是全新的——我刚刚将其添加到服务器中。由于我在本章中使用的是虚拟机示例,新的磁盘显示为 QEMU HARDDISK 型号。目前它没有任何分区;注意我们在它上方看到了一些与不同硬盘和分区相关的行,比如 /dev/sda3。而在 /dev/sdb 的描述下方没有类似的行。如果我们在该设备上有一个或多个分区,它们将会显示在输出中。

此时,我们已经知道哪个存储设备是新的——毫无疑问,在上一个示例中,它是 /dev/sdb。我们始终需要确保不要尝试格式化或重新分区错误的设备,否则可能会丢失数据。在这种情况下,我们可以看到 /dev/sdb 没有分区(而且在我添加它之前,这个卷并不存在),所以很明显我们要操作的磁盘是哪一个。现在我们可以在其上创建一个或多个分区,继续准备它以供使用。

创建分区

要在该设备上创建实际的分区,我们将使用带 sudofdisk 命令,并将设备的名称作为选项。在我的例子中,我将执行以下操作来处理磁盘 /dev/sdb

sudo fdisk /dev/sdb 

请注意,我在这里没有包括分区编号,因为 fdisk 是直接与磁盘交互的(而且我们还没有创建任何分区)。在本节中,我假设你有一个尚未分区的磁盘,或者你愿意清除的磁盘。当正确执行时,fdisk 会显示一条介绍信息并给出一个提示符:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_09_04.png

图 9.4:fdisk 的主提示符

此时,你可以按下键盘上的 m 键来查看可以执行的命令菜单。在这个示例中,我将带你完成第一次设置新磁盘所需的命令。

我相信这不言而喻,但请注意 fdisk 可能带来的破坏性后果。如果你在错误的磁盘上运行 fdisk,可能会导致无法恢复的数据丢失。管理员通常会将像 fdisk 这样的工具熟记到一定程度,以至于它们的使用变成了肌肉记忆。但总是要花时间确保你正在对正确的磁盘运行这些命令。

在继续创建新分区之前,需要对主引导记录MBR)和GUID 分区表GPT)分区表进行一些讨论。在新硬盘上创建分区表时,你可以选择使用 MBR 或 GPT 分区表。GPT 是较新的标准,而 MBR 已经存在很长时间了,如果你长时间从事服务器工作,可能一直在使用 MBR。

你可能会看到 MBR 被称为 DOS,这是指较旧的分区结构。正如你可能已经知道的,DOS磁盘操作系统Disk Operating System)的缩写,但在本章节中我们并不是在指这个操作系统,而是指 IBM 几十年前提出的分区结构。例如,在使用fdisk时,它会将 MBR 分区结构称为 DOS。在本章节中,我们会尽可能使用 MBR 来指代较旧的分区样式,以避免混淆。

使用 MBR 分区表时,你需要考虑一些限制。首先,MBR 只允许你创建最多四个主分区。此外,它还将你限制在大约 2 TB 的磁盘容量内。如果你的磁盘容量是 2 TB 或更小,这不会成为问题。然而,大于 2 TB 的硬盘越来越常见。

另一方面,GPT 没有 2 TB 的限制,所以如果你有一个非常大的硬盘,那么 MBR 和 GPT 之间的选择几乎已经为你决定好了。此外,GPT 没有最多四个主分区的限制,因为使用 GPT 分区表的fdisk可以创建最多 128 个主分区。毫无疑问,GPT 正迅速成为新的标准!GPT 成为默认分区表只是时间问题,所以除非你有充分的理由,否则我推荐如果有选择的话使用 GPT。

当你第一次进入fdisk提示符时,你可以按o来创建 MBR 风格的分区布局,或者按g来创建更新的 GPT 风格的分区布局。正如我之前提到的,这是一个可能破坏性很大的过程,所以请确保你正在对正确的硬盘使用这个工具!确保按下与你选择的分区风格相对应的键,然后按Enter,这样我们就可以继续了。一旦你按下go,你应该会看到一条确认信息,表示你已创建了一个新的分区表。

接下来,在你做出选择并创建了 MBR 或 GPT 分区表之后,我们就可以继续了。然后,在fdisk提示符下,输入n,告诉fdisk你想要创建一个新分区。接着,你会被问到是否想要创建主分区或扩展分区(如果你选择了 MBR)。使用 MBR 时,你会想选择主分区作为第一个分区,然后可以使用扩展分区来创建更多的分区。如果你选择了 GPT,这个提示不会出现,因为它会将你的分区创建为主分区。

接下来出现的提示会要求你输入分区号,默认选择下一个可用的编号。按Enter接受默认值。随后,你将被要求输入分区的第一个扇区(按Enter接受默认值2,048),然后下一个提示会询问你要使用的最后一个扇区。如果你按Enter接受默认的最后一个扇区,分区将包含设备上剩余的所有空闲空间。如果你希望创建多个分区,可以在最后一个扇区提示时不接受默认值。你可以通过输入*+*符号后跟要使用的兆字节或吉比字节的数字,并且在数字后加上M表示兆字节,或者加上G表示吉比字节,来明确新分区的大小。例如,你可以输入+20G来创建一个 20 GiB 的分区。注意,+符号后面没有空格,20G之间也没有空格。

此时,你将返回到fdisk提示符。要保存更改并退出fdisk,按下w然后按Enter。现在,如果你以root身份运行fdisk -l命令,你应该会看到你创建的新分区。以下是我某台服务器上fdisk命令的示例输出,给你一个完整过程的概念:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_09_05.png

图 9.5:fdisk命令的示例运行

如果你犯了错误或者想要重新调整分区布局,你可以再次进入fdisk提示符,然后按g创建一个新的 GPT 布局,或者按o创建一个新的 MBR 布局。然后,重新按照步骤分区你的磁盘。可以多次练习,直到熟练掌握这个过程。

格式化分区

在你为新磁盘创建好分区布局并且满意后,你就可以开始格式化它了。现在我已经在新磁盘上创建了一个分区布局,运行sudo fdisk -l的输出将有所不同:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_09_06.png

图 9.6:创建分区后运行sudo fdisk -l的另一个示例

请注意,现在我们添加了分区/dev/sdb1,它可以在输出中看到。接下来,我们可以继续格式化它。为此,我们使用mkfs命令。此命令需要特定的语法,输入mkfs后跟一个句点(.),然后输入你希望将目标格式化为的文件系统类型。以下示例将/dev/sdb1格式化为 ext4:

sudo mkfs.ext4 /dev/sdb1 

你的输出将与以下截图中的内容类似:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_09_07.png

图 9.7:使用 ext4 文件系统格式化卷

如果你选择了其他文件系统类型而不是 ext4,你可以在使用mkfs时替换为所选文件系统类型。以下示例将创建一个 XFS 文件系统:

sudo mkfs.xfs /dev/sdb1 

一些文件系统,例如 XFS,默认不被支持,可能需要安装额外的软件包才能使用它们。以 XFS 为例,需要安装xfsprogs软件包。

现在,我们已经创建了一个或多个分区并格式化了它们,我们准备在服务器上挂载新创建的分区。在接下来的部分中,我将带你逐步了解如何挂载和卸载存储卷。

挂载与卸载卷

现在你已经为服务器添加了一个新的存储卷并格式化了它,你可以挂载这个新设备,以便开始使用它。为此,我们使用mount命令。这个命令允许你将一个存储设备(甚至是一个网络共享)附加到服务器上的本地目录。在挂载之前,目录必须是空的。mount命令,稍后我们将通过一个示例进行实践,基本上只需要你指定一个位置(目录)来挂载设备。但我们该将卷挂载到哪里呢?

通常,在你的 Ubuntu Server 安装中,默认会创建两个目录,用于挂载卷:/mnt/media。虽然没有硬性规定媒体必须挂载到哪里,但这两个目录是文件系统层次结构标准FHS)的一部分,FHS 在《第四章,导航和基本命令》中已经提到过。/mnt/media 目录的目的在这个规范中已有定义。FHS 定义 /mnt 为暂时挂载文件系统的挂载点,而 /media 则是可移动媒体的挂载点。

简单来说,这意味着 /mnt 的预定用途是存放你通常大部分时间都挂载的存储卷,例如额外的硬盘、虚拟硬盘和网络附加存储。FHS 文档在描述 /mnt 时使用了“暂时”的术语,但实际上,这通常是你期望会存在一段时间的挂载位置。至于 /media,FHS 基本上是在指示可移动媒体(如闪存驱动器、CD-ROM 媒体、外部硬盘等)应当挂载在这里。

然而,重要的是要指出,FHS 所指示的挂载额外卷的位置只是一个建议。(也许是一个强烈的建议,但毕竟只是建议。)没有人会强迫你遵循它,世界的命运也不取决于你的选择。使用mount命令,你可以将额外的存储挂载到任何没有挂载或没有文件的地方。你甚至可以创建一个目录 /kittens 并在那里挂载你的磁盘,除了同事们的几声笑声,你不会遭遇任何后果。

通常,组织会自行制定额外磁盘挂载的位置方案。尽管我个人遵循 FHS 标准,但我曾在过去与一家公司合作时,遇到过一个自定义布局的例子。该公司在其服务器上使用了/store目录来挂载存储,这是他们在每台服务器上自行创建的目录。你使用什么样的方案由你决定;我唯一的建议是尽可能在不同服务器之间保持一致,至少为了保持理智。

mount命令通常需要以root身份执行。虽然有办法绕过这一点(你可以允许普通用户挂载卷,但我们暂时不讨论这个问题),通常情况下,只有root用户才能或应该挂载卷。正如我所提到的,你需要一个地方来挂载这些卷,因此,为了方便操作,我们可以使用以下命令创建一个名为/mnt/vol1的目录:

sudo mkdir /mnt/vol1 

当你创建了一个目录(就像我这样做的)或决定使用一个现有目录后,你可以使用类似下面的命令来挂载一个卷:

sudo mount /dev/sdb1 /mnt/vol1 

在这个例子中,我将设备/dev/sdb1挂载到目录/mnt/vol1

当然,你需要调整命令,以引用你想要挂载的设备和你想要挂载的位置。提醒一下,如果你不记得服务器上有哪些设备,可以使用fdisk –l列出它们。

通常,mount命令要求你指定一个-t选项,后面跟着指定的类型。在我的情况下,如果我使用了-t选项,那么mount命令应该是以下内容,因为我的磁盘格式化为ext4

sudo mount /dev/sdb1 -t ext4 /mnt/vol1 

一个有用的技巧是在挂载设备之前和之后执行df –h命令。

虽然这个命令通常用于检查不同挂载点的可用空间,但它会显示挂载的设备列表,因此你可以在挂载设备后对比结果,确认设备是否已挂载。

在这个例子中,我使用了-t选项,并指定了我格式化该设备时使用的文件系统类型。在第一个例子中,我没有使用这个选项。这是因为,在大多数情况下,mount命令能够自动识别设备使用的文件系统类型并相应调整。因此,大多数情况下,你不需要使用-t选项。过去,你几乎总是需要它,但现在操作起来更简单了。我之所以提到这一点,是因为如果你在尝试挂载文件系统时遇到错误,提示无效的文件系统类型,你可能需要指定这个选项。可以查看mount命令的手册页,了解更多关于不同选项的信息。

当你使用完一个卷后,可以使用umount命令卸载它(单词unmount中缺失的n是故意的):

sudo umount /mnt/vol1 

umount命令也需要以root身份或通过sudo运行,它可以让你将存储设备从文件系统中断开。为了确保此命令成功执行,卷不能正在被使用。如果正在使用,你可能会收到设备或资源忙碌的错误消息。如果你在卸载后执行df -h,你应该会看到文件系统不再出现在输出中,意味着它已经不再挂载。

手动挂载设备的缺点是,它们在下次服务器启动时不会自动重新挂载。为了确保挂载点在每次服务器启动时都可用,你需要编辑/etc/fstab文件,接下来我会引导你完成这个步骤。

理解/etc/fstab 文件

/etc/fstab文件是你 Linux 系统中的一个非常关键的文件。你可以编辑此文件,列出你希望在启动时自动挂载的其他卷。然而,这个文件的主要作用也是挂载你的主文件系统,所以如果在编辑时出错,可能会导致服务器无法启动(完全无法启动)。一定要小心。

分析/etc/fstab 文件的内容

当你的系统启动时,它会查看/etc/fstab文件以确定根文件系统的位置。此外,swap区域的位置也会从这个文件中读取,并在启动时挂载。系统还会读取此文件中列出的其他挂载点,一行一个,并进行挂载。基本上,几乎所有你能想到的存储都可以添加到这个文件中并自动挂载。即使是来自 Windows 服务器的网络共享也可以在这里添加。它不会对你评判(除非你打错字)。

作为示例,以下是我某台机器上/etc/fstab文件的内容:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_09_08.png

图 9.8:查看/etc/fstab 文件的内容

当你安装 Ubuntu Server 时,/etc/fstab文件会为你创建,并为安装过程中创建的每个分区添加一行。在我用于获取示例fstab内容的服务器上,只有一个根文件系统分区,你还可以看到 swap 文件的位置。

每个分区通常使用通用唯一标识符UUID)来标识,而不是你可能更习惯的/dev/sdaX命名约定,尤其是当你以前处理过存储设备时。在我的输出中,你可以看到 UUID dm-uuid-LVM-H8VEs7qDbMgv...,它指的是我的根文件系统,你还可以看到我有一个位于/swap.imgswap文件。

UUID 的概念已经存在了一段时间,但并没有任何限制阻止你用实际的分区名称(例如/dev/sda1或类似的名称)替换 UUID。如果你这么做,服务器依然会启动,你可能不会注意到什么不同(前提是没有打错字)。

现在,由于设备的名称可能会根据设备的物理位置(例如硬盘插入了哪个串行高级技术附件SATA)端口,外部硬盘连接到哪个 USB 端口,等等)或它们的排序(虚拟磁盘的情况)而发生变化,因此 UUID 比常见的设备名称更受青睐。

再加上可移动媒体可以随时插入或移除的事实,你会遇到一种情况:你无法确定每个设备在任何时刻会被分配什么名称。例如,现在你的外部硬盘可能被命名为/dev/sdb1,但下次挂载时,如果你连接的其他设备占用了/dev/sdb1这个名称,它可能就不再是这个名字。这时 UUID 的概念就非常有用了。设备的 UUID 不会因为你重新排列硬盘顺序而改变(但如果重新格式化卷,它会改变)。如图 9.8所示,你可以使用blkid命令轻松列出卷的 UUID:

blkid 

输出将显示附加到你系统上的每个设备的 UUID,你可以在每次向服务器添加新卷时使用此命令列出 UUID。这也是将新卷添加到/etc/fstab文件的第一步。虽然我之前说过使用 UUID 不是必须的,但强烈推荐使用,因为它可以避免你以后遇到麻烦。

每行fstab条目都被分为几列,每列由空格或制表符分隔。没有规定需要多少个空格来分隔每列;在大多数情况下,空格仅用于对齐每列,使其更易读。但至少需要一个空格。

在示例的fstab文件的第一列中,我们有设备标识符,它可以是每个设备的 UUID 或标签,用来区分其他设备。(你可以在使用mkfs命令格式化设备时,使用-L参数为设备添加标签。)在第二列中,我们有设备挂载位置。对于根文件系统,挂载点是/,这是 Linux 文件系统的起点,正如你所知。截图中的第三项(swap)的挂载点是none,这意味着这个设备不需要挂载点。第三列中,我们有文件系统类型,前两项是ext4,第三项是swap类型。

在第四列中,我们列出了每个挂载点的选项,并用逗号分隔。在这个例子中,每一行的选项只有一个。对于根文件系统,我们有一个选项errors=remount-ro,表示如果发生错误,系统会将文件系统重新挂载为只读模式。此类问题虽然罕见,但在出现故障时,能让系统尽量以只读模式继续运行。swap分区只有一个选项sw。这里可以使用许多其他选项,因此可以参考手册页获取完整的选项列表。在这一节中,我们会介绍一些常见的选项。

第五列和第六列分别指的是dumppass,在我的系统中,每一行的值为00dump分区几乎总是0,可以与备份工具一起使用来确定是否需要备份文件系统(0表示不备份,1表示备份)。在大多数情况下,只需将其保持为0,因为现在很少有工具会使用这个选项。pass字段指的是fsck检查文件系统的顺序。fsck工具用于扫描硬盘中的文件系统错误,尤其是在系统故障或计划扫描时。pass的可能值是012。如果是0,则文件系统不会用fsck检查。如果设置为1,则首先检查该分区。pass2的分区优先级较低,最后被检查。一般来说,建议将主文件系统设置为1,其他分区设置为2。云服务器提供商使用0作为两个字段的值并不少见,这可能是因为如果磁盘确实要进行例行检查,启动时间会显著增加。而在云环境中,无法等待很长时间来启动服务器。

现在我们已经理解了典型fstab条目的所有列,可以开始将另一个卷添加到fstab文件中了。

添加到/etc/fstab 文件

要将另一个卷添加到fstab文件中,我们首先需要知道我们要添加的卷的UUID(假设它是硬盘或虚拟磁盘)。同样,我们可以使用blkid命令来完成这项操作:

blkid /dev/sdb1 

注意,我使用了/dev/sdb1设备名称作为参数。这是因为我想专门获取我们添加的新设备的 UUID。该命令的输出将返回该设备的 UUID,然后我们可以将其添加到/etc/fstab文件中。记下这个 UUID,因为我们稍后会用到它。接下来,我们需要确定将卷挂载到哪里。现在可以创建一个目录,或者使用现有的目录。例如,可以创建目录/mnt/extra_storage来用于挂载:

sudo mkdir /mnt/extra_storage 

到这里,我们应该已经拥有了所有信息,可以向fstab文件添加新的条目。为此,我们需要在文本编辑器中打开文件,然后在所有其他条目之后创建新的一行。如果没有偏好的编辑器,可以使用nano编辑器:

sudo nano /etc/fstab 

例如,向 /etc/fstab 文件添加 /dev/sdb 条目后的内容如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_09_09.png

图 9.9:向 /etc/fstab 文件添加新条目后的内容

在我的示例中,我创建了一行注释,简要说明了额外卷的用途(额外存储)。留下注释总是个好主意,这样其他管理员就能了解额外存储的目的。接着,我创建了一行,包含卷的 UUID、卷的挂载点、文件系统类型、defaults 选项,以及 dump/pass 的值 00

defaults 选项是我之前未提及的。通过在 fstab 中使用 defaults 作为挂载选项,你的挂载将会一次性获得几个有用的选项,而无需单独列出它们。defaults 包含的选项有以下几项,值得解释:

  • rw:设备将被挂载为读写模式

  • exec:允许在此卷中的文件作为程序执行

  • auto:在启动时自动挂载设备

  • nouser:只有 root 用户能够挂载文件系统

  • async:输出到设备应为异步

根据你的需求,defaults 包含的选项可能适合,也可能不适合。你可以单独调用这些选项,以逗号分隔,选择你需要的选项。例如,对于 rw,你可能不希望用户能够更改内容。实际上,除非用户有非常强烈的需求来修改文件,否则我强烈建议你使用 ro(只读)选项。我就是在经历了一次整个卷完全被清空的事件后学到的这一点(而且没有人承认清空了内容)。这个卷中包含了非常重要的公司数据。从那时起,我要求所有内容都使用 ro,并创建了一个单独的 rw 挂载点,只有极少数(非常负责任的)人员才能访问。

exec 选项也可能不是最理想的。例如,如果你的磁盘卷是用于存储文件和备份的,你可能不希望在该位置运行脚本。通过使用 exec 的反向选项(noexec),你可以防止脚本运行,从而创建一个用户可以在该卷上存储文件,但无法执行存储在其中的程序的情况。

另一个值得解释的选项是 autoauto 选项基本上告诉系统每次启动时自动挂载该卷,或者当你输入以下命令时自动挂载:

sudo mount -a 

执行 sudo mount -a 时,会挂载 /etc/fstab 文件中所有设置了 auto 选项的条目。如果你为挂载使用了 defaults 选项,那么这些条目也会被挂载,因为 defaults 隐含了 auto。这样,你就可以在不重启服务器的情况下挂载所有应该挂载的文件系统(该命令可以随时安全执行,因为它不会中断任何已经挂载的内容)。

auto选项的反义词是noauto,可以替代使用。如你所猜测,带有noauto选项的fstab条目不会自动挂载,也不会在运行mount -a时被挂载。相反,带有此选项的条目需要手动挂载。

你可能会疑惑,既然使用noauto会使挂载失效,那么在/etc/fstab中包含这样的条目有什么意义呢?为了更好地解释这一点,下面是一个使用了noautofstab条目示例:

UUID=e51bcc9e-45dd-45c7 /mnt/ext_disk ext4 rw,noauto 0 0 

假设我有一个外部磁盘,只有在进行备份时才会挂载。我不希望这个设备在启动时自动挂载(因为我可能并不总是把它连接到服务器),所以我使用了noauto选项。但由于我在/etc/fstab中确实有这个设备的条目,一旦连接了它,我可以随时通过以下命令轻松挂载:

sudo mount /mnt/ext_disk 

请注意,我不需要包括设备名称或选项;只需提供挂载的目标路径即可。由于我在/etc/fstab文件中为一个设备添加了挂载路径/mnt/ext_disk,因此mount命令知道我指的是哪个设备。这避免了每次挂载设备时都需要输入设备名称和选项。因此,除了在启动时挂载设备外,/etc/fstab文件还成为了一个方便的地方,用来声明可能按需使用但并非总是连接的设备。

在我们继续之前,我想讲解最后一个选项users。当在/etc/fstab中与挂载一起使用时,它允许普通用户(非root用户)挂载和卸载文件系统。这样,使用此选项的挂载就不再需要rootsudo权限。请谨慎使用此选项,但如果你的设备存储的是非关键数据,且你不介意用户在挂载和卸载时拥有完全控制权限,它会非常有用。

虽然一开始可能会觉得通过文本文件来控制哪些设备挂载到系统上有些奇怪,但我想你会发现,能够查看一个文件以找出所有需要挂载的内容以及挂载位置,是非常方便的。只要管理员将所有按需设备添加到此文件中,它就可以成为一个方便的地方,用于概览服务器上正在使用的文件系统。作为额外的好处,你还可以使用mount命令(不加选项)来查看系统列出的所有挂载内容。试试看,然后我们在下一部分再见。

备份和恢复卷

由于我们正在处理服务器,存储在存储设备上的数据无疑非常重要。虽然在典型的环境中拥有一些用于测试的服务器是正常的,但我们的服务器通常承担着非常重要的任务。我可以根据亲身经历告诉你,永远不要对存储设备过于信任。事实上,我建议你根本不要信任它们。我认为所有存储设备都是临时的,因为硬盘是会坏的。如果你的重要数据仅仅存储在一个设备上,那是非常不安全的。在这一节中,我将讨论一些与备份相关的非常重要的话题。

首先,考虑一下 RAID 卷。在本章中我们没有讨论它,因为虽然这项技术仍然有其优势,但它已不再像以前那样流行。别误会,我并不是说 RAID 已经过时,它依然有其应用场景,只是它不再像过去那样普及。RAID 允许你将多个磁盘以不同的配置组合在一起,这样可以降低丢失数据的风险。

例如,RAID 1 级别确保两块硬盘始终保持相同的数据。如果其中一块硬盘发生物理故障,那么你实际上并没有丢失任何数据。当你在 RAID 中更换故障磁盘时,系统会使用新磁盘重建阵列,然后你将再次从可扩展性中获益。RAID 5 允许你使用多块硬盘,能够获得更多的存储空间,而 RAID 6 则与 RAID 5 类似,不过它允许两块磁盘在丢失数据之前故障,而不是只有一块。通常,RAID 的不同级别之间的区别就是允许多少块磁盘故障,超过这个数就会导致问题。

然而,RAID 存在一些严重的问题。最糟糕的是,它并不是备份解决方案。虽然它没有宣传自己是备份工具,但许多管理员错误地认为在使用 RAID 时他们的数据是安全的。事实上,RAID 提供的保护级别是非常有限的。如果发生雷暴天气,电源浪涌突破了你的浪涌保护器并烧坏了一块硬盘,那么很可能另一块硬盘也会被烧坏。一般来说,导致一块硬盘故障的环境因素很可能会导致其他硬盘故障。更糟的是,如果一个罪犯闯入你的服务器房,抢走了你的服务器并带走了它,那么小偷不光拿走了服务器,还带走了 RAID 中的所有磁盘,所以在各种情况下,RAID 可能并不能救你。RAID 确实有一定的好处,但更多的是一种便利,而非解决方案。

可靠的备份应该存储在服务器以外的地方。备份距离源服务器越远越好。如果你把备份存放在服务器室外的抽屉里,那肯定比一直连接外部硬盘更好(外部硬盘和内部硬盘一样,也可能会受到电力波动的影响)。但如果一个可怕的风暴摧毁了你的整个建筑,那么把备份硬盘存放在同一物理位置就会适得其反。

这可能看起来有些夸张。但实际上,我并不是这样。这些情况确实会发生。成功的备份方案具有韧性,并且能够让你迅速恢复服务器运行。备份你的数据更为重要,因为有些公司如果丢失了重要文件,甚至可能会倒闭,而这些文件可能包括那些能让公司维持运营的设计图纸。作为系统管理员,你需要制定一个备份方案,以应对尽可能多的情境。

一个有效的备份常规应包括多个层次。拥有外部硬盘作为备份是一个有用的方案,但为什么不准备多个呢,以防其中一个硬盘故障?也许你可以将其中一个备份存放在远离本地的地方,并每周交换本地和远程备份硬盘。此外,你还可以使用类似rsync的命令定期将服务器上的文件复制到远程服务器。你甚至可以考虑云备份方案,这也是一个很好的补充。

在这一节中,我无法为你的组织提供具体的备份方案,因为你的备份系统的布局将取决于组织的需求,而这些需求在不同公司之间是不同的。但我可以给你一些建议:

  • 确保定期测试你的备份。仅仅有备份是不够的——备份必须有效!定期尝试从备份中恢复数据,以测试其有效性。

  • 在你的备份方案中至少应有三层备份,其中至少有一层是离线的。这可以是外部硬盘、网络附加存储、云存储、将数据镜像到另一个位置的服务器,或者任何最符合你组织需求的方式。

  • 考虑加密。尽管这超出了本章的范围,但如果你的备份落入错误的人手中,受到保护的数据可能会泄漏,并且被不希望看到这些信息的人读取。

  • 检查你组织的政策,并确保你的备份方案符合相关要求。并非所有公司都有这样的方案,但如果你的公司有,这非常关键。考虑备份保留期(备份需要保存多长时间)以及备份更新的频率。如果你没有相关政策,可以咨询律师,了解你所在行业是否有法律要求的保留期限。

最重要的是,关键在于确保数据安全。到目前为止,在本书中,我们已经看过如何创建和挂载额外的卷,甚至在前面快速介绍过rsync。你已经学习了一些可以作为备份方案一部分的工具,在本书结束之前,你还将学习更多的方法。目前,在继续阅读本书时,请牢记这些要点,并考虑你学到的每一项新技能,看看是否可以将其作为备份方案的一部分应用,当然,如果适用的话。

LVM是我最喜欢的技术之一,它为我们的存储提供了额外的灵活性。事实上,我们现在就来看看它。

使用 LVM

你的组织需求会随着时间变化。虽然作为服务器管理员,我们总是尽力在配置资源时考虑长期增长,但预算和政策的变化似乎总是会给我们带来障碍。LVM 是我相信你会逐渐感激的技术。事实上,像 LVM 这样的技术正是让 Linux 在可扩展性和云部署方面成为赢家的原因。有了 LVM,你可以在线调整文件系统的大小,而无需重启服务器。

以以下场景为例。假设你在虚拟化的生产服务器上运行一个应用程序——这个服务器如此重要,以至于停机会给你的组织带来严重的经济损失。当服务器最初设置时,也许你为应用程序的存储目录分配了一个 100GB 的分区,认为它永远不会需要更多空间。现在,随着业务的增长,存储空间不仅用得非常多,而且即将用完!你该怎么办?如果服务器最初配置了 LVM,你可以添加一个额外的存储卷,将它添加到 LVM 池中,并扩展你的分区,所有这一切都无需重启服务器!另一方面,如果你没有使用 LVM,你就不得不找到一个服务器的维护窗口,使用传统方式添加更多存储,这意味着服务器会暂时无法访问。

对于物理服务器,你可以安装额外的硬盘并保持备用,尽管服务器不是虚拟化的,但仍然可以通过在线扩展文件系统来获益。此外,如果你的服务器支持热插拔,你仍然可以在不停机的情况下添加额外的存储卷。

正因如此,我必须强调,在虚拟服务器上的存储卷中,尽可能始终使用 LVM。让我再重复一遍:当你设置虚拟服务器时,一定要在存储卷上使用 LVM!如果你不这么做,最终当你可用空间开始耗尽,且不得不在周末工作以添加新磁盘时,你会后悔的。

这个过程可能涉及手动将数据从一个磁盘同步到另一个磁盘,然后将用户迁移到新的磁盘。相信我,这可不是一个愉快的经历。你可能现在觉得不需要使用 LVM,但你永远不知道。

开始使用 LVM

在通过 Ubuntu 安装程序设置新服务器时,你会在安装过程中看到使用 LVM 的选项。但是,更重要的是你的存储卷使用 LVM,指的是用户和应用程序存储数据的卷。如果你希望根文件系统也能受益于 LVM 的功能,LVM 是 Ubuntu Server 根文件系统的一个不错选择。为了开始使用 LVM,我们需要理解一些概念,特别是卷组物理卷逻辑卷

卷组是赋予所有你希望与该 LVM 实现一起使用的物理卷和逻辑卷的命名空间。基本上,卷组是包含你整个 LVM 设置的最高级别名称。可以将其视为一种容器,能够容纳磁盘。例如,一个名为vg-accounting的卷组可能会用于会计部门存储文件的地方。它将包含这些用户使用的物理卷和逻辑卷。需要注意的是,你并不局限于仅使用一个卷组;你可以有多个卷组,每个卷组都有自己的磁盘和卷。

物理卷是一个物理或虚拟硬盘,它是一个卷组的成员。例如,假设的vg-accounting卷组可能包含三个 100 GB 的硬盘,每个硬盘都被视为物理卷。请记住,即使这些硬盘是虚拟的,在 LVM 的上下文中,它们仍然被称为物理卷。基本上,任何由卷组拥有的块设备都是物理卷。

最后,逻辑卷在概念上类似于分区。逻辑卷可以占用磁盘的一部分或全部,但与标准分区不同,逻辑卷也可以跨多个磁盘。例如,一个逻辑卷可以包括三个 100 GB 的磁盘,并配置为总共获得 300 GB 的存储空间。挂载后,用户可以像在标准磁盘的单个分区中一样存储文件。当卷空间满时,你可以添加一个额外的磁盘,然后扩展分区以增加其大小。用户会将其视为一个单一的存储区域,尽管它可能由多个磁盘组成。

卷组可以取任何你喜欢的名字,但我总是给它们取以vg-开头并以描述其用途的名称结尾的名字。如前所述,你可以有多个卷组。因此,你可以在同一台服务器上有vg-accountingvg-salesvg-techsupport(等等)。然后,你将物理卷分配给每个卷组。例如,你可以向服务器添加一个 500 GB 的磁盘,并将其分配给vg-sales。从此之后,vg-sales卷组拥有该磁盘。你可以以任何适合你的方式划分物理卷。然后,你可以使用这些物理卷创建逻辑卷,这些逻辑卷将供用户使用。

我认为在学习新概念时,最好通过实际示例来操作,所以我会带你走一遍这样的场景。在我的例子中,我通过 VirtualBox 在我的机器上创建了一个本地的 Ubuntu Server 虚拟机,然后在安装了发行版之后,我又添加了四个额外的 20 GB 磁盘。

如果你没有带有多个空闲物理磁盘的服务器,虚拟化是一个非常好的方式来尝试学习 LVM。

要在尚未使用 LVM 的服务器上开始使用 LVM,你首先需要至少有一个额外的(未使用的)卷,并安装所需的包,这些包可能已安装,也可能未安装在你的服务器上。要检查你的服务器上是否已安装所需的lvm2包,请执行以下命令:

apt search lvm2 |grep installed 

如果没有安装(前一个命令的输出不包括[installed,automatic]),则可以使用以下命令安装lvm2包及其依赖项:

sudo apt install lvm2 

接下来,我们需要盘点我们可用的磁盘。你可以像我们之前做过的那样使用fdisk -l命令列出它们。在我的例子中,我为服务器添加了几个新磁盘,现在我有/dev/sdb/dev/sdc/dev/sdd/dev/sde可以使用。根据硬件或虚拟化平台的不同,你的磁盘名称会有所不同,因此确保相应地调整以下所有命令。

首先,我们需要配置每个磁盘以供 LVM 使用,将每个磁盘设置为物理卷。注意,我们不需要格式化存储设备,甚至不需要使用fdisk来设置它,在开始配置 LVM 的过程中,格式化实际上是稍后的步骤。pvcreate命令是我们用来配置磁盘以供 LVM 使用的第一个命令。因此,我们需要对所有希望用于此目的的驱动器运行pvcreate命令。例如,如果我有四个磁盘希望用于 LVM,我会运行以下命令来设置它们:

sudo pvcreate /dev/sdb
sudo pvcreate /dev/sdc
sudo pvcreate /dev/sdd
sudo pvcreate /dev/sde 

如此类推,根据你计划使用的磁盘数量。

为了确认你是否正确按照步骤操作,可以使用pvdisplay命令以root身份查看你在服务器上可用的物理卷:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_09_10.png

图 9.10:在示例服务器上运行pvdisplay命令的输出

截图中只显示了一个卷,因为它需要格式化以适应这页内容。如果你向上滚动,pvdisplay命令将显示更多输出。尽管我们有一些物理卷可以使用,但它们并没有被分配到卷组中。事实上,我们甚至还没有创建卷组。现在我们可以使用vgcreate命令来创建卷组,在命令中我们将为卷组指定名称,并将第一个磁盘分配给它:

sudo vgcreate vg-test /dev/sdb 

在这里,我正在创建一个名为vg-test的卷组,并将我之前准备的一个物理卷(/dev/sdb)分配给它。现在我们的卷组已经创建完成,我们可以使用vgdisplay命令并加上sudo来查看它的详细信息,包括已分配磁盘的数量(现在应该是1):

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_09_11.png

图 9.11:在示例服务器上运行 vgdisplay 命令的输出

到这一步,如果你像我一样创建了四个虚拟磁盘,你会发现还有三个磁盘没有被分配到卷组。别担心,我们稍后再处理它们。现在暂时不需要考虑它们,我们有其他概念需要继续操作。

此时我们只需要做的就是创建一个逻辑卷并格式化它。我们的卷组可以包含我们分配给它的整个磁盘或其中的一部分。通过以下命令,我将从我添加到卷组的虚拟磁盘中创建一个 5 GB 的逻辑卷:

sudo lvcreate -n myvol1 -L 5g vg-test 

这个命令看起来可能很复杂,但其实并不复杂。在这个例子中,我通过-n选项为我的逻辑卷命名为myvol1。由于我只想分配 5 GB 的空间,所以我使用了-L选项,然后用5g表示 5 GB。最后,我指定了这个逻辑卷将被分配到的卷组名称。你可以使用sudo运行lvdisplay命令来查看这个卷的相关信息:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_09_12.png

图 9.12:在示例服务器上运行 lvdisplay 命令的输出

到这一步,我们在设置 LVM 方面应该已经完成了所有必要的操作。但在我们能够使用卷之前,仍然需要格式化它,类似于非 LVM 磁盘的处理。

格式化逻辑卷

接下来,我们需要格式化我们的逻辑卷,以便它可以使用。然而,像往常一样,我们需要知道设备的名称,以便知道我们要格式化的是哪个。使用 LVM 这一点很容易。lvdisplay命令已经给我们提供了这个信息;你可以在输出中看到它(在图 9.12中的第三行,位于LV Path下)。让我们用ext4文件系统来格式化它:

sudo mkfs.ext4 /dev/vg-test/myvol1 

现在这个设备可以像其他硬盘一样挂载了。我将它挂载到/mnt/lvm/myvol1,但你也可以使用任何你喜欢的目录名称:

sudo mount /dev/vg-test/myvol1 /mnt/lvm/myvol1 

为了检查我们的工作,执行df -h命令以确保我们的卷已经挂载并显示正确的大小。我们现在只有一个包含单个磁盘的 LVM 配置,因此这并不是特别有用。我给它分配的 5 GB 可能不会持续太久,但我们仍有一些剩余的空间可以使用,这些空间尚未被利用。通过以下lvextend命令,我可以调整我的逻辑卷大小以占用物理卷的剩余空间:

sudo lvextend -n /dev/vg-test/myvol1 -l +100%FREE 

在这种情况下,+100%FREE是指定我们想要将剩余空间的全部用于逻辑卷的参数。如果操作正确,你应该看到类似以下的输出:

Logical volume vg-test/myvol1 successfully resized. 

现在,我的逻辑卷正在使用我分配给它的整个物理卷。不过要小心,因为如果我有多个物理卷分配给它,那个命令会将这些物理卷上的所有空间都占用,从而使逻辑卷的大小是所有磁盘可用空间的总和。你不一定总是想这样做,但由于我只有一个物理卷,所以我不介意。现在,使用df -h命令再次检查你的空闲空间:

df -h 

不幸的是,它没有显示我们为卷分配的额外空间。df命令的输出仍然显示的是卷调整前的大小。这是因为,尽管我们有了更大的逻辑卷,并且它已经分配了所有空间,但我们并没有实际调整驻留在该逻辑卷上的ext4文件系统的大小。为了做到这一点,我们将使用resize2fs命令:

sudo resize2fs /dev/mapper/vg--test-myvol1 

前面命令中的双连字符是故意的,所以确保你正确输入了命令。

如果执行正确,你应该看到类似以下的输出:

The filesystem on /dev/mapper/vg--test-myvol1 is now 5241856 (4k) blocks long. 

现在,当你执行df -h时,应该能看到新增的空间已经可用。最酷的部分是,我们在不重启服务器的情况下调整了整个文件系统的大小。在这种情况下,如果我们的用户已经使用了大部分的空闲空间,我们就能为他们提供更多空间而不会打断他们的工作。

然而,你可能还有其他尚未分配给卷组的物理卷。在我的例子中,我创建了四个物理卷,目前只在 LVM 配置中使用了其中一个。我们可以使用vgextend命令将额外的物理卷添加到我们的卷组中。在我的例子里,我将对剩下的三个磁盘执行此操作。如果你有额外的物理卷,可以按照我使用的命令来添加,但将我的设备名称替换为你的设备名称:

sudo vgextend vg-test /dev/sdc
sudo vgextend vg-test /dev/sdd
sudo vgextend vg-test /dev/sde 

你应该看到类似以下的确认信息:

Volume group "vg-test" successfully extended 

现在当你运行pvdisplay时,你应该能看到附加的物理卷,它们之前并未显示在此处。现在我们的 LVM 配置中有了额外的磁盘,提供了更多的选择。

我们可以立即将所有额外的空间分配给我们的逻辑卷,并像之前一样进行扩展。然而,我认为最好是保留一部分空间给我们的用户。这样,如果我们的用户再次用尽所有可用空间,我们就可以有一个紧急的备用空间,在我们找到长期解决方案的过程中,能够应急使用。此外,LVM 快照(我们很快会讨论)要求你在 LVM 设置中有未分配的空间。

以下示例命令将为逻辑卷添加额外的 10GB 空间:

sudo lvextend -L+10g /dev/vg-test/myvol1 

最后,将空闲空间提供给文件系统:

sudo resize2fs /dev/vg-test/myvol1 

对于非常大的卷,调整大小可能需要一些时间。如果你没有立即看到新增的空间,你可能会看到它逐渐增加,每隔几秒钟,直到所有新的空间完全分配完毕。

使用 LVM 删除卷

最后,你可能会好奇如何删除逻辑卷或卷组。对于这些操作,你可以使用lvremovevgremove命令。不言而喻,这些命令是有破坏性的,但在你想删除一个逻辑卷或卷组时,它们是非常有用的。要删除逻辑卷,以下语法就能完成任务:

sudo lvremove vg-test/myvol1 

基本上,你所做的就是给lvremove命令传递你的卷组名称,一个斜杠,然后是你想删除的该组中的逻辑卷名称。要删除整个卷组,下面的命令和语法应该是相当直观的:

sudo vgremove vg-test 

你只能在逻辑卷未被使用时将其删除,这可能不会是你经常做的事情,但如果你需要废弃 LVM 组件,那么有一些命令可以帮助你做到这一点。

希望到现在为止,你已经被 LVM 的强大功能打动了。它为你的服务器存储提供了其他平台只能梦寐以求的灵活性。LVM 的灵活性是 Linux 在云市场中表现出色的众多原因之一。如果你之前没有使用过 LVM,这些概念可能一开始会让人难以理解。但得益于虚拟化,玩转 LVM 变得很容易。我建议你多练习创建、修改和销毁卷组和逻辑卷,直到你熟悉它。如果这些概念现在还不清楚,随着实践它们会变得更容易理解。

在这一节中,你看到了一些 LVM 如何为你带来好处的方法;它使你能够将服务器的存储提升到一个新的水平,甚至可以按需扩展和增长。然而,LVM 还有更多的技巧,它甚至允许你创建快照。我们接下来会介绍这个有用的功能。

理解 LVM 快照

LVM 快照允许你在某一时刻捕获一个逻辑卷并将其保留。创建快照后,你可以像挂载其他任何逻辑卷一样挂载它,甚至在出现故障时,将你的卷组还原到快照状态。实际上,如果你想测试一些可能对存储在卷中的文件产生风险的更改,但又希望有保障以防万一出错,你可以随时撤销更改并恢复到原始状态,LVM 快照正好能实现这一点。LVM 快照要求你在卷组中有一些未分配的空间。

然而,LVM 快照绝对不是一种可行的备份方式。在大多数情况下,这些快照最适合用作运行测试或在正式系统上部署更改之前,测试实验性软件的临时存储区域。在 Ubuntu 的安装过程中,你有机会创建 LVM 配置。因此,如果你为根文件系统使用了 LVM,你可以使用快照测试安全更新如何影响你的服务器。如果新的更新开始导致问题,你可以随时还原。当你完成测试后,你应该合并或删除快照。

那么,为什么我称 LVM 快照为临时解决方案,而不是备份呢?首先,和我们之前讨论的类似,如果备份存储在与备份目标相同的服务器上,那么备份就不安全。始终重要的是至少将服务器的备份保存到外部位置,最好是异地保存。但更糟糕的是,如果你的快照开始占用卷组中所有可用空间,它可能会损坏并停止工作。因此,这是一个你需要小心使用的功能,作为测试手段,在实验完成后要么还原,要么删除快照。不要让 LVM 快照长期存在。

当你使用 LVM 创建快照时,实际上是创建了一个新的逻辑卷,它是原始逻辑卷的克隆。最初,这个快照不会占用任何空间。但随着你运行服务器并操作卷中的文件,原始的数据块在你更改时会被复制到快照中,以保留原始逻辑卷。如果你不注意使用情况,可能会丢失数据,如果不小心,逻辑卷会填满。

为了通过例子展示,以下命令将创建一个myvol1逻辑卷的快照(称为mysnapshot):

sudo lvcreate -s -n mysnapshot -L 4g vg-test/myvol1 

你应该会看到以下输出:

Logical volume "mysnapshot" created. 

在这个例子中,我们使用了lvcreate命令,使用了-s选项(快照)和-n选项(允许我们为快照命名),我们指定了mysnapshot作为快照名称。我们还使用了-L选项来指定快照的最大大小,在这种情况下设置为 4GB。最后,我们给出卷组和逻辑卷名称,用斜杠(/)分隔。从这里,我们可以使用lvs命令来监控其大小。

由于我们在创建快照时创建了一个新的逻辑卷,因此可以像挂载正常的逻辑卷一样挂载它。如果我们只想提取一个文件而无需恢复整个文件系统,这非常有用。

那么,如何恢复快照呢?快照的一个主要优势是可以“回滚”到快照创建时的状态。本质上,这允许你测试服务器的更改,然后撤销这些更改。要回滚到某个快照,我们可以使用lvconvert命令:

sudo lvconvert --merge vg-test/mysnapshot 

输出将类似于以下内容:

Merging of volume mysnapshot started.
myvol1: Merged: 100.0% 

然而,需要注意的是,与在线调整逻辑卷大小不同,我们不能在快照正在使用时将其合并。如果这样做,变化将在下次挂载时生效。因此,你可以在合并前卸载逻辑卷,或在合并后卸载并重新挂载。之后,你会发现在下次运行lvs命令时,快照会被删除。

由于无法合并(回滚)正在使用的快照,如果快照是根文件系统的快照,必须重启服务器才能完成回滚。

如果你想让快照成为永久性的,即最终确定自快照创建以来所做的所有更改,我们可以使用lvremove命令。对于本节中的示例快照,我们可以使用以下命令使快照永久生效:

sudo lvremove vg-test/mysnapshot 

正如你从命令的名称中可以推断的那样,lvremove命令删除快照。删除快照的操作实际上是使其更改生效,而前面提到的lvconvert命令则是回滚到快照创建时的状态。

LVM 快照无疑是一个有用的功能,尽管它不应被视为备份解决方案。我最喜欢使用这些快照的场景是在安装所有可用更新之前先拍摄根文件系统的快照。重启后,更新生效,我可以选择删除快照(如果一切正常),或者如果更新导致问题,则恢复到快照。即便如此,LVM 快照是一个在需要时可以使用的额外工具。

总结

高效管理服务器存储将确保系统持续平稳运行,因为满载的文件系统肯定会导致服务器崩溃。幸运的是,Linux 服务器提供了一个非常强大的存储管理工具集,其中一些工具是其他平台羡慕的。作为 Linux 服务器管理员,我们从 LVM 等技术和ncdu等工具中受益,还有很多其他工具。在本章中,我们探索了这些工具以及如何管理存储。我们涵盖了如何格式化、分区、挂载和卸载卷,以及管理fstab文件、LVM、监控磁盘使用情况等内容。

在我们 Ubuntu Server 系列的下一集里,我们将介绍如何连接到网络。我们将配置服务器的主机名,演示如何通过 OpenSSH 连接到其他服务器,并且了解 IP 地址配置。

相关视频

深入阅读

加入我们的社区在 Discord

加入我们社区的 Discord 空间,与作者和其他读者进行讨论:

packt.link/LWaZ0

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/QR_Code50046724-1955875156.png

第十章:连接到网络

Linux 网络席卷了 IT 行业。许多组织在其数据中心使用 Linux,包括物理服务器和云服务器。Ubuntu Server 是运行关键任务应用程序最受欢迎的选择之一,但如果没有稳定的网络将基础设施的各个组件连接起来,即使是最强大的服务器硬件也将无效。

到目前为止,在本书中我们一直使用单一的 Ubuntu Server 实例。在这里,我们开始对 Linux 网络进行两部分的探讨。本章将讨论与初始网络连接和远程管理相关的主题。我们将在第十一章设置网络服务中继续学习更多的网络话题,届时我们将构建和配置额外的组件,使您的服务器能够更有效地进行通信,从而建立一个强大的基础网络,满足您未来多年的需求。

在我们 Ubuntu 探险的这一章节中,我们将讨论:

  • 设置主机名

  • 管理网络接口

  • 分配静态 IP 地址

  • 理解 Linux 名称解析

  • 开始使用 OpenSSH

  • 开始使用 SSH 密钥管理

  • 通过配置文件简化 SSH 连接

在我们探索网络的过程中,首先应该为每台 Ubuntu 服务器分配一个唯一的身份;基本上,我们应该给它们一个名字,以便帮助区分它们之间的差异。

设置主机名

在安装过程中,您被要求为您的服务器创建一个主机名。具体来说,在初始设置过程中,字段被标记为 Your server's name。那时,我们的目标只是简单地设置一个 Ubuntu Server 实例,以便在本书中进行示例演示。现在,您可能考虑更改服务器的主机名。

当我们使用 OpenSSH 远程管理服务器(正如本章后面将要做的那样)时,主机名会显示在命令行上。如果所有服务器的名称都相同,这可能会让人感到非常困惑。更重要的是,服务器的主机名赋予了它一个身份。在实际的 Ubuntu Server 生产部署中,每台服务器应该有其特定的用途,并相应地命名。通常,组织会有自己的命名方案。比如,公司的 web 服务器可能被命名为类似于 webserver-01,或者使用完全限定的域名,例如 webserver-01.example.com

在本书中,我不会假设使用任何特定的命名方案,因此,当我们讨论更改主机名时,你可以根据需要调整名称。如果你没有命名方案(但想要创建一个),可以尽情发挥创意。我见过许多不同的命名方式,从以卡通人物命名服务器(谁不想有一台名为daffy-duck的服务器?)到希腊神话中的神祇。一些公司选择更为简单的命名方案,使用由连字符分隔的一系列字符,其中包含服务器所在机架的代码,以及服务器的用途。如果你还没有命名方案,可以自己创造一个。不管你最终选择什么,我都不会评价你。

正如我之前提到的,服务器的主机名就是它的身份标识。它向网络中的其他设备标识你的服务器。虽然像ubuntu这样的简单主机名对于只有一个主机的情况是可以接受的,但如果在你的网络中每台 Ubuntu 服务器都保留默认的主机名,很快就会变得混乱。为每台服务器命名一个描述性的名称有助于你区分它们。但是,服务器的名称不仅仅是主机名,更多内容将在第十一章设置网络服务中讨论,当我们讲解 DNS 时会详细说明。现在,我们将介绍如何查看和配置主机名,等到我们讲到 DNS 时,你可以将主机名正式化并进行 DNS 分配。

那么,如何查看你的主机名呢?一种方法是直接查看你的 shell 提示符;你可能已经注意到,主机名已经包含在提示符中。虽然你可以通过许多方式自定义 shell 提示符,但默认情况下会显示当前的主机名。然而,根据你为服务器命名的方式,它可能会或不会显示完整的名称。通常,默认的提示符(如果你在想的话,它被称为PS1 提示符)只显示主机名,直到第一个句点为止。例如,如果你的主机名是dev.mycompany.org,那么提示符只会显示dev。要查看完整的主机名,只需输入hostname命令:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_10_01.png

图 10.1:hostname 命令的输出

更改主机名是相当简单的。我们可以使用hostnamectl命令作为root用户或通过sudo来执行。例如,如果我想将主机名从dev.mynetwork.org更改为dev2.mynetwork.org,我会执行以下命令:

sudo hostnamectl set-hostname dev2.mynetwork.org 

这很简单,但这个命令究竟做了什么呢?嗯,我很想给你一个复杂的概述,但实际上它只是更改了一个文本文件的内容(具体来说,是/etc/hostname文件)。你可以通过在更改前后使用cat命令查看这个文件的内容来验证这一点:

cat /etc/hostname 

你会看到这个文件只包含你的主机名。

一旦你更改了主机名,执行某些命令后,可能会看到类似以下的错误信息:

unable to resolve host dev.mynetwork.org 

此错误意味着计算机无法再解析您的本地主机名。这是因为/etc/hostname文件并不是唯一存放您主机名的文件;它还被引用于/etc/hosts。不幸的是,hostnamectl命令不会为您更新/etc/hosts,因此您需要自己编辑该文件以消除错误。以下是一个示例服务器上/etc/hosts文件的示例:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_10_02.png

图 10.2:来自/etc/hosts文件的示例内容

前两个条目在此示例中指代本地机器本身。本地主机地址,也称为环回地址,允许机器直接访问自身。如果您对127.0.0.1地址使用ping命令,回复将来自执行该命令的机器,而不是网络中的另一台主机。在第一行上,我们有以下内容:

127.0.0.1 localhost 

如果您要使用任何网络命令尝试与本地服务器通信,例如 ping 127.0.0.1localhost,则该行上的/etc/hosts文件声明此通信是针对底层服务器本身的。

在示例截图的第二行中,我们有以下内容:

127.0.1.1 dev.mynetwork.org dev 

根据您的配置,例如是否使用物理服务器、虚拟化平台或云服务器提供商,该行可能存在,也可能不存在。如果缺少该行,您可以添加它,但我们稍后会更详细地讨论这个问题。

本质上,该特定行标识出本地服务器还可以通过 IP 地址127.0.1.1、完全限定域名dev.mynetwork.org以及简化形式dev访问。完全限定域名包括服务器名称(在此例中为dev)以及组织的域名(例如此示例中的mynetwork.org)。这使您可以通过使用名称dev.mynetwork.org或简化形式dev直接从该服务器 ping 本地服务器。

如果您没有要与服务器一起使用的域名,可以在/etc/hosts文件中省略完全限定的域名。因此,在我们的示例中,如果没有域名,该行将如下所示:

127.0.1.1 dev 

回到我们更改服务器主机名的示例,我提到您可以使用hostnamectl命令来执行此操作,但该命令不会为您更新/etc/hosts文件,它只会更新/etc/hostname文件。最佳做法是同时更新/etc/hosts文件以保持一致。您可以完全避免使用hostnamectl命令,并手动编辑/etc/hosts/etc/hostname文件,这实际上是我更喜欢的方法。如果我必须手动编辑文本文件,无论是否使用hostnamectl命令,我认为最好还是使用文本编辑器来处理。

主要的收获是,给你的服务器赋予一个合理且与其在网络中角色相匹配的身份。在典型的组织中,你会有 Web 服务器、文件服务器、数据库服务器等等。一个一致且合乎逻辑的命名方案会使得所有事情变得更加简单。

现在我们已经学会了如何为服务器赋予身份,接下来可以学习如何管理网络接口。

管理网络接口

网络对于服务器基础设施至关重要。如果没有网络,服务器无法相互通信,用户也无法访问它们。为了让服务器连接到网络,需要安装网络接口。大多数服务器会安装标准的有线以太网适配器,使你能够插入网络电缆并将其连接到交换机。如果我们的服务器硬件已被 Ubuntu 正确识别,那么这一过程基本上是自动完成的。然而,自动配置并不总是理想的。也许我们希望自定义 IP 地址或与连接相关的设置。

首先,我们需要了解如何查看当前网络卡在服务器中生效的连接参数。这是本节的主要目标。我们可以使用两个基本命令来实现:ip(推荐使用)和 ifconfig(这是在 Ubuntu 的早期版本中使用的方法,现在不再推荐使用)。

我们可以通过 ip 命令查看和管理我们的网络接口信息。例如,我们可以使用 ip addr show 来查看当前分配的 IP 地址:

ip addr show 

这将产生类似于以下截图的输出:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_10_03.png

图 10.3:使用 ip addr show 命令查看 IP 信息

输入该命令后,你应该会看到与可用的网络接口及其当前状态相关的输出。另外,你可以将命令简化为 ip a(无论哪种方式,输出都会相同)。从输出中,我们可以看到一些有用的信息,比如每个设备的 IP 地址(如果有的话),以及它的 MAC 地址。

使用 ip 命令,我们还可以管理接口的状态。我们可以将设备关闭(阻止它连接到网络),然后再将其启动:

sudo ip link set enp0s3 down 
sudo ip link set enp0s3 up 

在这个例子中,我只是切换了接口 enp0s3 的状态。首先,我将其关闭,然后再将其重新启动。

启动和关闭接口是好的,但那种命名规则是什么情况呢?对于那些习惯于早期版本使用的命名方案(如 eth0wlan0 等)的用户来说,Ubuntu 22.04 使用的命名规则可能会显得有些奇怪。由于 Ubuntu 基于 Debian,因此它采用了从 Debian 9.0 开始引入的新命名规则。

新的命名约定被引入是为了使接口命名更加可预测。虽然你可能认为像eth0这样的名称比enp0s3更容易记住,但这个变化有助于使名称在启动时保持一致。当你向 Linux 系统添加新的网络接口时,其他接口名称发生变化的可能性始终存在。

例如,如果你在一台服务器上安装了较旧的 Linux 版本,并且该服务器有一个网络卡(eth0),然后你添加了第二个网络卡(命名为eth1),那么如果在下次启动时接口名称顺序发生了变化,可能会导致你的配置出现问题。假设一个接口连接到了互联网,另一个连接到了交换机(基本上,你拥有一个互联网网关)。如果接口顺序错乱,整个办公室的互联网连接将会中断,因为你编写的防火墙规则应用到了错误的接口上。显然,这并不是一种愉快的体验!

过去,Ubuntu 的早期版本(以及 Debian,甚至 CentOS)选择使用udev来使网络接口名称保持不变,以解决这个问题。如今,这已经不再必要,但我还是想在这里提一下,以防你碰到的是一个老版本的服务器。这些老服务器通过存储在以下文件中的配置来实现接口名称的固定:

/etc/udev/rules.d/70-persistent-net-rules 

这个文件在一些流行的 Linux 发行版的早期版本中存在(包括 Ubuntu),作为解决该问题的一个变通方法。这个文件包含了一些信息,用来识别网络接口的特定属性,因此每次启动时,它都会使用相同的名称。因此,你所熟悉的eth0始终会是eth0。如果你使用的是较旧版本的 Ubuntu 服务器,应该能够自己查看到这个文件。下面是这个文件在老旧安装中的一些示例输出:

SUBSYSTEM=="net", ACTION=="add", DRIVERS=="?*", 
TTR{address}=="01:22:4e:a5:f2:ec", ATTR{dev_id}=="0x0", ATTR{type}=="1", KERNEL=="eth*", NAME="eth0" 

如你所见,它是通过卡的 MAC 地址来识别eth0的。但是如果我想将这台机器的镜像拍摄并重新部署到另一台服务器上,这就成了一个小问题。这是一个常见的做法——我们管理员通常不会轻易从头开始,除非没有选择。如果另一台服务器与新服务器的需求足够相似,克隆它将是一个可行的选项。然而,当我们将镜像恢复到另一台服务器时,/etc/udev/rules.d/70-persistent-net-rules文件也会一起迁移。我们很可能会发现,新的服务器的第一个网络接口会被命名为eth1,即使我们只有一个接口。

这是因为文件已经将设备指定为eth0(它引用了系统中不存在的设备),所以我们需要自己修正这个文件以重新占用eth0。我们可以通过编辑rules文件,删除包含系统中不存在的网卡的那一行,然后将剩余行中的设备名称改回eth0来完成这个操作。

新的命名方案从systemd v197及更高版本开始生效(如果你之前没有了解过,本书早些部分提到过,systemd是 Ubuntu 用于管理进程和各种资源的底层框架)。大多数情况下,新的命名规范参考了网络卡在系统总线上的物理位置。

因此,它获得的名称除非你真的将网卡移除并将其放入系统板上的其他插槽,或者在虚拟化管理程序中更改虚拟网络设备的位置,否则无法更改。

简单概述一下网络名称的组成:en表示以太网,wl表示无线网络。因此,我们知道我之前提到的示例接口(enp0s3)代表的是一个有线网卡。p表示使用的是哪条总线,p0表示系统的第一条 PCI 总线(编号从 0 开始)。接下来是s3,它代表的是 PCI 插槽 3。把这些放在一起,enp0s3代表的是系统第一条总线上放置在 PCI 插槽 3 的有线网络接口卡。新的命名规范的详细内容甚至可以单独成章,但希望这能给你一个大概的了解。网上有更多关于这个细节的文档,如果你有兴趣了解更深入的信息,可以查看进一步阅读部分。这里需要强调的重点是,由于新的命名方案基于网卡的物理位置,因此它不太可能会突然改变。实际上,只要你不物理地更换网卡在机箱内的位置,它是不会改变的。

回到我们如何管理网络接口,另一个值得讨论的命令是ifconfig

ifconfig 命令是 net-tools 工具套件的一部分,该套件大部分已被弃用。它的替代工具是 iproute2 套件,其中包括我们已经讨论过的 ip 命令。总结来说,这意味着你应该使用 iproute2 套件中的命令,而不是 ifconfig 之类的命令。问题是,如今大多数管理员仍然使用 ifconfig,而且这种趋势没有减弱的迹象。事实上,net-tools 套件已被推荐弃用多年,许多今天出厂的 Linux 发行版仍然默认安装这个套件。那些没有安装它的发行版,提供它作为额外的软件包供你安装。在 Ubuntu Server 22.04 中,net-tools 包不再默认安装,但如果你想手动安装,它仍然可用。不过,我不推荐安装它,因为它已被弃用,不再应该使用。

诸如 ifconfig 之类的命令之所以在被弃用后仍然存在,通常归结于 改变是困难的 这种心态,但仍有相当多的脚本和程序在使用 ifconfig,因此在这里讨论它是有价值的。即使你立即停止使用 ifconfig,并从今以后改用 ip,你仍然会在旅途中遇到这个命令,所以了解一些例子还是很有帮助的。如果你碰到老旧的服务器,了解旧命令也能帮你解决问题。

首先,当单独执行 ifconfig 且没有任何选项时,它会打印有关你的网络接口的信息,就像我们之前使用 ip addr show 所做的那样。这看起来很简单。

如果你在使用普通用户时无法通过 ifconfig 查看接口信息,可以尝试使用完整路径的命令(包括完整路径):

/usr/sbin/ifconfig 

/usr/sbin 目录可能在你的 $PATH 中,也可能不在(这是一个 shell 查找命令的目录集合),所以如果你的系统无法识别 ifconfig,使用完整路径命令应该能够输出你想要的结果,如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_10_04.png

图 10.4:使用 ifconfig 命令查看接口信息

其次,就像我们之前练习的 ip 命令一样,我们也可以通过 ifconfig 来禁用或启用一个接口:

sudo ifconfig enp0s3 down 
sudo ifconfig enp0s3 up 

当然,ipifconfig 还有其他选项和变化,所以如果你需要更多信息,可以随时查阅它们的手册页。在本节中,主要需要记住的是如何查看当前的 IP 分配,以及如何启用或禁用一个接口。

尽管我们的网络接口非常有用,但如果没有分配 IP 地址,它们就毫无用处。虽然网络通常会使用 DHCP 来处理这项工作,但在下一节中,我们将看看如何分配静态 IP 地址。

分配静态 IP 地址

对于服务器来说,确保 IP 地址固定且不发生变化非常重要。如果 IP 地址发生变化(例如没有保留的动态租约),用户将会遇到中断,服务将会失败,甚至整个网站可能无法访问。当你安装 Ubuntu Server 时,它会从 DHCP 服务器获取一个动态分配的租约,但在你配置好服务器并准备投入生产之前,重要的是要立即设置一个永久的 IP 地址。这个规则的唯一例外是基于 Ubuntu 的 VPS。提供这些服务器的云服务商会有自动系统为你的新 VPS 声明一个 IP 地址,并且已经配置好使其保持不变。但对于你自己管理的虚拟或物理服务器来说,除非你在安装时已经配置了静态 IP 地址,否则它们一开始会是动态地址。

在大多数情况下,你的办公室或组织会有一个 IP 地址方案,其中会列出可用于静态分配的 IP 地址范围。如果你没有这样的方案,重要的是要创建一个,这样当你添加更多服务器时,会减少后续的工作量。我们将在第十一章设置网络服务中讨论如何设置 DHCP 服务器和 IP 地址方案,但现在我会给你一些快速提示。你的 DHCP 服务器通常会有一个 IP 地址范围,自动分配给任何请求分配的主机。在为服务器设置静态 IP 时,你需要确保所选择的 IP 地址不在 DHCP 服务器分配的范围内,这样就不会在网络中产生重复的 IP 地址。例如,如果你的 DHCP 服务器分配的 IP 范围是从10.10.10.10010.10.10.150,你就应该选择一个包含在该范围内的 IP 地址来给服务器使用。

给网络主机(包括你的服务器)分配固定地址有两种方法。我之前提到过第一种方法,即使用静态 IP 分配。使用这种方法,你可以任意选择一个没有被任何设备使用的 IP 地址,然后配置你的 Ubuntu 服务器使用该地址。在这种情况下,服务器不会向网络的 DHCP 服务器请求 IP 地址,而是直接使用你分配给它的地址。这就是我将在本节中讲解的方法。

将服务器分配固定地址的另一种方法是使用静态租约。这也被称为DHCP 保留,但我更倾向于使用前者的术语。通过这种方法,您配置 DHCP 服务器将特定的 IP 地址分配给特定的主机。换句话说,您的服务器将从本地 DHCP 服务器请求 IP 地址,您的 DHCP 服务器被指示每次请求时都给服务器一个特定的地址。这是我喜欢的方法,因为它使得您的 DHCP 服务器成为网络上分配的 IP 地址的唯一真实来源。我会在第十一章设置网络服务中详细介绍这一点。

然而,并非总是有选择的余地。作为 Linux 管理员,您可能并不负责 DHCP 服务器。在许多组织中,管理服务器的管理员通常与管理网络的个人不同。

如果您无权设计网络,您将使用由网络管理员提供的 IP 地址,并根据他们给出的参数配置您的 Ubuntu 服务器来使用它。

在过去几年中,我们用来自定义服务器 IP 地址的方法已经从过去的方式改变了。自 Ubuntu 17.10 发布以来,即 2017 年,配置 IP 地址设置现在通过 Netplan 完成。过去,我们会通过 NetworkManager 配置网络,但这只在 Ubuntu 桌面版中默认安装。通过 Netplan,网络接口的配置文件现在以 YAML 格式存储在/etc/netplan目录中。本书不涵盖 YAML 格式本身的解释,但其语法非常易于理解,因此您在配置网络接口时无需深入理解这种格式。如果列出/etc/netplan目录的内容,您应该至少看到一个文件,通常命名为00-installer-config.yaml50-cloud-init.yaml。文件可能以不同的名称保存,因此请检查/etc/netplan目录的内容以查看文件在您那里的名称。在我的一台服务器上,我在/etc/netplan/00-installer-config.yaml文件中看到以下内容:

# This is the network config written by 'subiquity'
network:
  ethernets:
    enp0s3:
      dhcp4: true
  version: 2 

我们已经可以从这个默认文件中获取一些明显的信息。首先,开头的注释提到了subiquity,这是 Ubuntu 服务器安装程序的官方名称,在您使用从 ISO 文件创建的引导介质安装分发时使用。

更重要的是,我们可以看到这台特定服务器配置为使用 DHCP 来获取 IP 地址,这可以从以下一行中看出:

 dhcp4: true 

我们还可以看出,这个配置文件与接口enp0s3相关。综合来看,这个文件告诉我们接口enp0s3已配置为通过 DHCP 自动获取 IP 地址。如果我们想将此配置更改为静态 IP 地址,首先应备份文件:

sudo cp /etc/netplan/00-installer-config.yaml /etc/netplan/00-installer-config.yaml.bak 

这样,如果我们犯了错误,我们可以通过将备份文件重命名为原文件名来轻松恢复原始文件。这是一个很好的做法,适用于我们编辑的任何文件。能够恢复之前的配置是我们进行任何更改时的最佳实践。我们需要做的第一项更改是删除以下这一行(或者将其改为false):

 dhcp4: true 

实质上,要设置静态 IP,我们将用特定于我们配置的详细信息替换那一行。这是一个为静态 IP 地址配置的文件示例:

# This is the network config written by 'subiquity'
network:
  ethernets:
    enp0s3:
      **addresses:** **[****192.168.100.50****/24****]**
      **gateway4:****192.168.100.1**
      **nameservers:**
        **addresses:** **[****192.168.100.1****,** **192.168.100.2****]**
  version: 2 

在这个示例中,我已经将四行加粗,这些行是替代dhcp4: true行所添加的。首先,我们设置实际的 IP 地址:

 addresses: [192.168.100.50/24] 

在这里,我使用了一个示例 IP 地址192.168.100.50/24。在你的环境中,你需要确保选择的 IP 地址位于你希望服务器加入的网络范围内。正如我之前提到的,你选择的 IP 地址不能在自动分配 IP 地址的 DHCP 范围内。如果你的网络上 DHCP 服务器的示例范围是从192.168.100.100192.168.100.150,那么前面的 IP 地址就没有问题。这里选择的192.168.100.50在这个范围外,因此我们不需要担心会有其他设备被分配到这个地址。

我们还添加了/24来声明 IP 地址是 24 位子网的一部分,这是相当标准的,除非你的网络管理员设置了更大的范围。/24网络等同于 C 类网络,如果这个说法更熟悉的话。这也解决了子网掩码的问题,我们这里不需要设置,因为/24意味着子网掩码为255.255.255.0(如果你更熟悉传统的网络分类样式,它会像 IP 地址一样显示子网掩码)。我们将在第十一章设置网络服务中讨论子网问题;不过,完整的子网划分和 TCP/IP 协议的介绍更适合放在专门讲解网络概念的书中,因此我们不会进一步展开讨论。

接下来,我们还需要设置网关:

 gateway4: 192.168.100.1 

在网络配置中,网关指的是你的出站连接所通过的设备,这通常是路由器或防火墙,具体取决于你的网络设置。此值需要与网络中实际的默认网关地址匹配,默认网关地址通常与 IP 地址的最后一部分为.1。如果不确定,你可以检查连接到相同网络的另一台设备的 IP 地址,它应该是相同的。

最后一部分允许我们配置服务器用来查找外部域名的 DNS 服务器:

 nameservers:
        addresses: [192.168.100.1, 192.168.100.2] 

示例配置仅仅是一个示例——所有值必须与适合你网络的设置相匹配。通常,DNS 服务器的 IP 地址与网关地址相同,但并非总是如此。有时,网络管理员会为 DNS 服务器设置自定义 IP 方案。在示例中我还添加了一个次级 DNS 服务器 192.168.100.2,但如果不需要,可以删除第二个 IP 地址。

一旦确认文件中的值是合适的,我们需要应用并测试这些更改:

  • 如果你正在使用虚拟机,你可能想要从虚拟机控制台进行更改

  • 如果你正在更新一台物理机器,你可能需要连接显示器和键盘。

  • 尽管我们在本章稍后讨论 OpenSSH,如果你已经知道如何通过 OpenSSH 连接到服务器,你可能不希望在 OpenSSH 中更改网络配置,因为一旦激活这些更改,你的连接将会断开。

慢慢来,仔细检查一切,确保没有输入错误,这样就不会遇到无法连接的服务器问题。

要使这些更改生效,你可以运行以下命令:

sudo netplan apply 

当你运行之前的命令时,它会告诉你文件中是否有错误,如果没有错误则会应用更改。新的 IP 地址会立即生效。

如果在配置网络时使用像 OpenSSH 这样的远程连接,你可以通过使用 tmux(一个流行的终端多路复用器)来解决断开连接和网络无法正确重启的问题。关于 tmux 的完整教程超出了本书的范围,但在这个场景中它对我们非常有帮助,因为即使我们与服务器的连接丢失,它也能保持命令在后台运行。要使用它,首先安装该软件包:

sudo apt install tmux 

然后,只需在你的终端提示符下输入 tmux 来激活 tmux

从此时起,tmux 就负责你的会话。如果你在 tmux 中运行一个命令,它会继续运行,无论你是否已连接到它。要看到这一点,首先进入 tmux,然后执行 top 命令。当 top 正在运行时,从 tmux 断开连接。为此,在键盘上按 Ctrl + b,松开后,再按 d。你将退出 tmux,但是如果你输入 tmux a 命令重新连接会话,你会发现即使断开了连接,top 仍然在运行。按照这种逻辑,你可以在执行 sudo netplan apply 命令之前启动 tmux

很可能,你仍然会被从 shell 中断开,因为激活网络更改的过程会使网络接口关闭并重新启动,但使用tmux时,命令会在后台完成。然后,你可以重新连接到服务器,并运行tmux a以重新加入你的tmux会话。

tmux工具非常强大,当正确使用时,它可以显著提升你在使用 Linux shell 时的工作效率。虽然本书无法详细讲解完整教程,但我强烈建议你深入了解如何使用它,你可以在这里找到更多信息:www.packtpub.com/hardware-and-creative/getting-started-tmux。如果你需要一些指导,可以查看 LearnLinuxTV 上的视频教程,链接将在本章末尾提供。

网络重启后,你应该能够立即重新连接到服务器,并通过执行ip a来查看新的 IP 分配是否已生效。如果由于某种原因无法重新连接到服务器,可能是你在编辑 Netplan 配置文件时犯了错误。请仔细检查该文件,确保没有错误。但只要你按照步骤操作,并为你的接口和网络输入了正确的值,就应该能够成功配置静态 IP。

现在,我们已经有了实际的网络——我们已经为服务器命名并配置了网络接口。我们还应了解 Ubuntu 中的名称解析工作原理,这是服务器通过名称查找其他服务器的过程。

理解 Linux 名称解析

第十一章设置网络服务中,我们将讨论为本地名称解析设置 DNS 服务器的过程。但在此之前,理解 Linux 如何解析名称是很重要的。你们中的大多数人可能都知道域名系统DNS)的概念,它将人类可理解的域名映射到 IP 地址。这使得浏览网络(以及互联网)变得更加轻松。然而,DNS 并不是 Linux 服务器在解析名称时首先使用的工具。

欲了解 Ubuntu Server 在解析名称时检查资源的顺序,欢迎查看/etc/nsswitch.conf文件。该文件中有一行以hosts开头。以下是我服务器中该行的输出:

hosts:          files mdns4_minimal [NOTFOUND=return] dns mymachines 

在这种情况下,服务器配置为首先检查本地文件,如果未找到请求的信息,则检查 DNS。这是默认顺序,我认为没有理由在这里进行更改(但你当然可以)。具体来说,服务器将检查的文件是/etc/hosts。如果在那里找不到所需内容,它将转向 DNS(基本上,它会检查我们之前通过 Netplan 配置的 DNS 服务器,或者由 DHCP 提供的默认服务器)。

nsswitch.conf文件中还有许多其他行,但由于超出了本节的讨论范围,我在这里不再讨论。

我们在讨论主机名时简要提到了/etc/hosts文件,它告诉我们的服务器如何解析自身(它有一个主机名映射到本地主机 IP 127.0.0.1),但你也可以在这里创建其他名称到 IP 的映射。

例如,如果我有一台服务器(myserver.mydomain.org)的 IP 地址是10.10.96.124,我可以在/etc/hosts中添加以下行,使得每次都能将我的机器解析到该 IP,而不需要查询 DNS 服务器:

10.10.96.124 myserver.mydomain.org 

然而,实际上,这通常不是一个非常方便的配置名称解析的方法。别误会,我并不是说你不能在这个文件中列出你的服务器和它们的 IP 地址,你的服务器肯定能解析这些名称。问题在于,这种方法很难维护。名称映射仅适用于你在其上修改了/etc/hosts文件的服务器;其他服务器无法受益,因为它们只会检查自己的/etc/hosts文件。你可以在每个服务器的hosts文件中添加服务器列表,但那会很麻烦。这也是为什么拥有一个中央 DNS 服务器对任何网络都有好处,尤其是在解析本地资源名称时。

然而,/etc/hosts文件偶尔仍在企业中作为快速的临时解决方案使用,最终你可能因某种原因需要使用这种方法。使用这种手动名称解析方法的一个非常常见的原因是在测试替换服务器的情况下。此时,你可以将/etc/hosts文件配置为与原始服务器相同的名称,但使用新服务器的 IP 地址。完成测试并确认新服务器正常运行后,你就可以将全网的 DNS 名称替换为指向新 IP 地址。

在旧版 Ubuntu 服务器上,/etc/resolv.conf文件包含了系统用于解析名称的 DNS 服务器的 IP 地址。如果你想覆盖服务器的 DNS 服务器,你需要修改这个文件。尽管这个文件在 Ubuntu 22.04 中仍然存在,但它仅用于将查询重定向到systemd-resolved,这是一个在后台运行并根据系统通过 DHCP 接收到的设置或你在 Netplan 中配置的设置应用 DNS 配置的 systemd 单元。为了完整起见,这里简要概述一下旧版本中该文件的语法,以防你需要在这种服务器上工作。以下是该文件的示例:

nameserver 10.10.96.1 
nameserver 10.10.96.2 

在这个例子中,/etc/resolv.conf文件输出使用的是10.10.96.110.10.96.2这两台服务器。因此,服务器首先会检查/etc/hosts文件,看看是否有与正在查询的资源匹配的记录,如果没有,它将继续检查/etc/resolv.conf,以确定下一个要检查的服务器。在这个例子中,服务器将检查10.10.96.1

传统服务器上的/etc/resolv.conf文件通常不需要进行实际更改,因为它是由 NetworkManager 自动生成的。NetworkManager 是一个帮助你管理网络接口的服务;然而,在 Ubuntu Server 的多个版本中,它已经不再使用了。尽管你通常不需要手动编辑/etc/resolv.conf文件,但在老旧服务器上查看它可能很有用,这样你就可以知道分配了哪些 DNS 服务器,以防你在排查网络问题时需要用到这些信息。

如今,现代的 Ubuntu 服务器使用systemd-resolved来处理名称解析。如果你想查看在较新的 Ubuntu Server 安装中分配了哪些 DNS 服务器,你可以简单地查看之前我们在静态 IP 配置时使用的 Netplan 配置文件,但如果使用的是 DHCP,resolvectl命令会告诉你当前服务器正在指向的 DNS 服务器。它的输出将类似于以下截图所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_10_05.png

图 10.5:查看服务器当前的 DNS 分配

在典型的企业 Linux 网络中,你会设置一个本地 DNS 服务器来解析内部资源,然后将请求转发到公共 DNS 服务器,以防你试图访问某个非内部的资源。我们将在第十一章设置网络服务中讲解这个内容,但你现在应该了解在 Ubuntu Server 上名称解析的工作原理。

作为 Linux 管理员,我们可能需要管理大量服务器,而且我们管理的服务器往往不在与我们相同的物理位置。OpenSSH 是一个强大的远程管理工具,接下来我们将深入探讨它。

开始使用 OpenSSH

OpenSSH可能是管理 Linux 服务器时最有用的工具。在所有可用的无数工具中,这是我建议每个人尽早开始练习的工具。从技术角度来看,我可能更适合在第十一章设置网络服务中加入一节内容来介绍如何设置 OpenSSH,但这个工具非常实用,我们应该尽早开始使用它。

OpenSSH 允许你在其他 Linux 服务器上打开命令行界面,使你能够像在服务器面前一样执行命令。对于我们这样的 Linux 管理员来说,这非常方便。我们可能需要管理数十台、数百台,甚至上千台服务器。

借助 OpenSSH,我们可以在不离开椅子的情况下管理整个服务器架构。在本节中,我将为你提供关于 OpenSSH 的一些信息,并介绍如何安装它,最后通过一些实际使用的例子来结束这一节。

安装 OpenSSH

OpenSSH 由两个组件组成:在后台运行、接受 SSH 连接的服务器守护进程,以及运行在笔记本、工作站或其他服务器上的客户端,它使你能够连接到 SSH 服务器并运行命令。如今,所有操作系统都提供了 OpenSSH 客户端,可以用来连接到服务器,因此这一要求大概率已经满足。对于 Linux,大多数发行版默认提供了 OpenSSH 客户端。你可以通过在命令行提示符下运行which ssh来验证这一点。如果客户端已经安装,你的输出应该是/usr/bin/ssh

如果由于某种原因,你没有安装此软件包,并且在运行前一个命令时没有收到任何输出(这种情况比较罕见),你可以使用以下命令来安装 OpenSSH 客户端:

sudo apt install openssh-client 

根据你在安装过程中做出的选择,Ubuntu 服务器很可能已经安装了 OpenSSH 服务器。如果你不记得在最初安装时是否选择了安装它,你可以在命令行提示符下运行which sshd命令,你应该会看到输出/usr/sbin/sshd。你也可以执行systemctl status ssh,如果服务器组件存在并且正在运行,那么你的服务器已经准备好接受 SSH 连接:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_10_06.png

图 10.6:验证 OpenSSH 服务是否在服务器上运行

如果你的服务器没有安装 OpenSSH 服务器组件,你可以使用以下命令来安装它:

sudo apt install openssh-server 

然而,强大的力量伴随巨大的责任。尽管 OpenSSH 非常强大,但任何监听连接的服务都有可能被滥用。外部入侵者发现弱点或漏洞,进而控制你的服务器,情况将变得非常糟糕。因此,像所有在服务器上运行的服务一样,只有在需要时才应该启动它。由于 OpenSSH 极为有用(而且它是远程管理的标准方法),因此几乎很难不使用它。

你可以利用许多方法来保护此类服务并帮助加固它。下一节将介绍其中一种方法,我们在本书结束之前,还将再次讨论与 OpenSSH 相关的安全问题。具体来说,在第二十一章《保护你的服务器》中,我将带你逐步了解可以采取的各种配置更改,帮助最小化外部不法分子入侵服务器并造成破坏的威胁。

保护 OpenSSH 实际上并不困难,可能只需要几分钟的时间。因此,您可以绕道阅读该章节中关于保护 OpenSSH 的部分,读完后再回来。现在,至少确保服务器上有安全的、随机生成的密码。如果 OpenSSH 可以通过公共互联网访问,而您的用户有弱密码,那么这绝对不是一个好情况。

完成所有这些后,我们就可以开始实际使用 OpenSSH 了。

使用 OpenSSH 执行命令

在您已安装 openssh-server 软件包到目标机器(您希望远程控制的那台机器)之后,如果它尚未启动,您需要启动它。默认情况下,Ubuntu 的 openssh-server 软件包会在安装后自动配置为启动并启用。就像我们之前做的那样,您可以通过以下命令验证所需的服务是否正在运行:

systemctl status ssh 

如果 OpenSSH 作为守护进程在您的服务器上运行,您应该看到输出信息,告知它处于 active (running) 状态。如果不是,您可以使用以下命令启动它:

sudo systemctl start ssh 

如果 systemctl status ssh 命令的输出显示守护进程被禁用(意味着它不会在服务器启动时自动启动),您可以使用以下命令启用它:

sudo systemctl enable ssh 

当 OpenSSH 服务器已启动并正在运行时,您的服务器现在应该在监听连接。为了验证这一点,可以使用以下命令列出监听的端口,将输出限制为 SSH:

sudo ss -tunlp |grep ssh 

ss 命令允许我们查看正在服务器上运行并监听连接的进程列表。它还会显示进程监听的端口。

我们将在 第二十一章保护您的服务器 中更详细地探讨此命令。但现在,这个命令应该会输出类似以下内容:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_10_07.png

图 10.7:检查 SSH 所需端口是否在监听

如果由于某种原因,您的服务器没有显示有 SSH 服务器在监听,仔细检查您是否已经启动了守护进程。默认情况下,SSH 服务器会在端口22上监听连接。可以通过修改/etc/ssh/sshd_config文件中的端口声明来更改这一设置,但这是后续章节的内容。虽然我现在不打算详细讲解如何编辑此文件,但请注意,它是守护进程的默认配置文件。每次 OpenSSH 启动或重启时,它都会读取此文件中的配置值。

要使用 SSH 连接到服务器,只需执行 ssh 命令,后面跟上您要连接的服务器的名称或 IP 地址:

ssh 192.168.1.120 

默认情况下,ssh 命令将使用你当前登录的用户名进行连接。如果你想使用不同的用户名,可以在 ssh 命令中指定它,通过在 IP 地址或主机名之前加入用户名,并跟随 @ 符号:

ssh fmulder@192.168.1.120 

除非你另行指定,ssh 命令假定你的目标主机监听的是端口 22。如果不是,你可以通过 -p 选项后跟端口号来指定不同的端口:

ssh -p 2242 fmulder@192.168.1.120 

一旦你连接到目标机器,你就可以像直接站在它面前一样运行 shell 命令并管理系统。你将拥有与你登录时相同的权限,如果你在该服务器上有权限,你还可以使用 sudo 来执行管理员命令。

基本上,如果你在服务器面前时能够做的任何事情,你都能通过 SSH 来做。当你完成会话时,只需在 shell 提示符下输入 exit,或按下键盘上的 Ctrl + d

当你退出 OpenSSH 连接时,任何在后台运行的进程都会被终止。请确保在退出连接之前,恢复你可能正在运行的后台进程,并完成相关工作。我们在第七章控制和监控进程中讨论过如何在后台运行进程。

如你所见,OpenSSH 是一款神奇的工具,它能够让你从任何允许 SSH 访问的地方远程管理你的服务器。但要确保阅读第十一章设置网络服务中的相关部分,关于如何保证它的安全。在接下来的章节中,我们将讨论 SSH 密钥管理,它带来了便利性,同时也能提升安全性。

开始使用 SSH 密钥管理

当你通过 SSH 连接到主机时,系统会要求你输入密码,验证通过后便会建立连接。不过,你可以选择通过公钥认证而不是输入密码进行验证。这种方法的核心优点是增加了安全性,因为在连接服务器的过程中,系统密码不会被传输。当你创建 SSH 密钥对时,你会生成两个文件,一个公钥和一个私钥。这两个文件在数学上是相互关联的,因此,如果你连接到一台已保存你公钥的服务器,服务器会知道是你在连接,因为只有你(并且只有你)拥有与公钥匹配的私钥。这种方法比密码验证更加安全,我强烈建议你使用它。为了最大化通过密钥认证带来的安全性,你实际上可以在服务器上禁用基于密码的认证,这样你的 SSH 密钥就成为唯一的登录方式。通过禁用基于密码的认证并仅使用密钥,你可以大幅提升服务器的安全性。我们将在第二十一章保护你的服务器中讲解这一部分。

生成公钥和私钥

首先,你需要生成密钥。在你的工作站或笔记本电脑(即你用来连接服务器的设备)上,使用ssh-keygen命令,以普通用户账号进行操作。下面的截图展示了这一过程的大致样子:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_10_08.png

图 10.8:生成 SSH 密钥对

首先,你会被要求输入保存密钥文件的目录,默认路径是/home/<user>/.ssh。接下来,系统会询问是否设置密码短语,这一步是可选的。虽然这会增加通过密钥进行身份验证的步骤,但我建议你设置一个密码短语(与系统密码不同),因为它大大提高了安全性(如果你设置了密码短语,密钥没有密码短语就无法使用)。如果你不想设置密码短语,可以直接按Enter键跳过。

该命令的作用是,在你的home目录下创建一个名为.ssh的目录(如果它还不存在)。在该目录中,它会创建两个文件,id_rsaid_rsa.pubid_rsa文件是你的私钥,它永远不应该离开你的计算机、传给其他用户,或者存储在任何外部介质上。如果你的私钥泄露,你的密钥对就不再可信。默认情况下,私钥的拥有者是创建它的用户,且rw权限只授予文件所有者。

公钥则不同,它可以离开你的计算机,并且不需要像私钥那样严格保护。它的权限更加宽松,所有人都可以读取,只有所有者可以写入。你可以通过执行ls -l /home/<user>/.ssh来查看这一点:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_10_09.png

图 10.9:列出.ssh 目录的内容,显示新创建密钥的权限

公钥是实际复制到其他服务器上的密钥,以便通过这样的密钥对进行登录。当你登录到一个包含你公钥的服务器时,服务器会检查公钥与私钥是否在数学上匹配,如果匹配,它会允许你登录。如果在创建密钥时设置了密码短语,系统也会要求你输入它。但在我们实际使用密钥之前,我们需要将公钥复制到目标服务器上。

将你的公钥复制到远程服务器

要实际将公钥传输到目标服务器,可以使用ssh-copy-id命令,下面是我在例子中使用的命令:

ssh-copy-id -i ~/.ssh/id_rsa.pub 192.168.1.150 

使用该命令时,将 IP 地址替换为目标服务器的实际 IP 地址,或替换为目标服务器的 主机名。你首先会被要求通过密码登录,然后密钥会被复制过去。从此以后,你将通过密钥登录,如果因某种原因密钥关系被破坏,你会被要求输入密码。以下是这个过程的示例,如果我要将我的密钥复制到名为 myserver.mycompany.org 的服务器时的步骤:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_10_10.png

图 10.10:使用 ssh-copy-id 命令将公钥复制到服务器

那么,ssh-copy-id 命令到底做了什么呢?你的公钥到底被复制到了哪里?其实,执行此命令后,如果目标服务器的 home 目录下没有 .ssh 目录,它会自动创建一个。在该目录内,如果没有现成的 authorized_keys 文件,它会创建一个。你机器上 ~/.ssh/id_rsa.pub 文件的内容将被复制到目标服务器的 ~/.ssh/authorized_keys 文件中。每添加一个新的密钥(例如,你从多台机器连接到该服务器时),密钥会被添加到 authorized_keys 文件的末尾,每个密钥占一行。

使用 ssh-copy-id 命令仅仅是为了方便;你完全可以将 id_rsa.pub 文件的内容复制并手动粘贴到目标服务器的 authorized_keys 文件中。那种方法其实也能正常工作。

当你连接到已经设置了密钥关系的服务器时,SSH 会检查该服务器上 ~/.ssh/authorized_keys 文件的内容,寻找一个与本机私钥(~/.ssh/id_rsa)在数学上匹配的密钥。如果这两个密钥匹配,你将被授予访问权限。如果你设置了密码短语,则在打开公钥时,会要求你输入密码短语。

如果你决定在密钥中不设置密码短语,实际上就是在设置无需密码的身份验证,这意味着在身份验证时,你不需要输入任何内容。

使用 SSH 代理

当我们之前创建 SSH 密钥时提到过,设置密码短语是可选的,但它是一个好主意。使用密码短语可以提高 OpenSSH 密钥对的安全性。如果一个 OpenSSH 密钥落入错误的人手中,只要他们不知道密码短语,就无法使用它。然而,我们也会失去一些便利,因为每次使用密钥时都需要输入密码短语。没有密码短语的 OpenSSH 密钥允许我们连接到服务器并直接登录,而不需要输入任何东西。使用SSH 代理,你实际上可以在第一次使用密码短语时将其缓存,这样每次连接时就不需要再输入密码短语了。这实际上让你在享受密码短语增加的安全性的同时,仍然保持了一定的便利性。最棒的是,如果你的笔记本电脑或台式机能够使用 OpenSSH 客户端连接到远程系统,那么你应该已经在系统中安装了 SSH 代理。例如,如果我们在工作站或笔记本电脑上使用 Linux 或 macOS 的某种版本,ssh-agent命令会是可用的。

ssh-agent是通过在终端后台启动来使用的。然后,我们可以使用密码短语“解锁”我们的密钥,解锁后的密钥将被存储在内存中,当我们尝试连接到已经将公钥复制到的服务器时,系统会自动使用这个解锁的密钥。要启动它,在你用来启动连接的机器上(也就是你的工作站)输入以下命令:

eval $(ssh-agent) 

这个命令将启动一个 SSH 代理,它将继续在你的 shell 后台运行。但它现在还没有给我们带来任何好处——所以我们需要将一个 SSH 密钥添加到正在运行的代理中。ssh-add命令允许我们将一个 SSH 密钥添加到正在运行的ssh-agent中。为此,我们可以将ssh-add命令与公钥的路径作为参数一起使用:

ssh-add ~/.ssh/id_rsa 

到此时,你将被要求输入密码短语。只要你正确输入,它将保持解锁状态,未来的连接无需再次输入密码短语,直到你关闭该 shell 或注销。现在,你已经在后台运行了ssh-agent,并且密钥已经解锁,使用带有密码短语的密钥变得更加简单,你将输入更少的内容。

更改 OpenSSH 密钥的密码短语

有时候,你可能想要更改与密钥相关的密码短语。如果你想这样做,可以使用-p参数与ssh-keygen命令配合使用。如果你在最初创建密钥时没有选择添加密码短语,那个参数也可以用来添加。就是这么简单。你输入的命令就像以下这样简单:

ssh-keygen -p 

一旦你输入该命令,按 Enter 键接受默认文件(id_rsa),除非你想更改的密钥有不同的名称,届时你可以键入该密钥的路径和名称。接下来,你将被要求输入当前的密码短语(如果没有密码短语,可以留空),然后再输入两次新密码短语。整个过程如下所示:

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/B18425_10_11.png

图 10.11:更改 SSH 密码短语

如果你从未使用过 SSH,这些概念可能需要一些练习。最好的练习方法是设置多个 Ubuntu Server 安装(可能是几个虚拟机),并练习使用 SSH 连接到它们,同时通过 ssh-copy-id 命令将你的密钥部署到每台机器上。实际上,一旦掌握了,它会变得相当简单。

用配置文件简化 SSH 连接

在我们离开 OpenSSH 话题之前,还有一个便捷的技巧,那就是创建 SSH 的本地配置文件。这个文件必须存储在你的主目录的 .ssh 文件夹中,并命名为 config。在我的情况下,这个文件的完整路径如下所示:

/home/jay/.ssh/config 

这个文件默认是不存在的,但如果它被找到,SSH 每次使用客户端时都会解析它,你将能够从中受益。请打开这个文件并在文本编辑器中编辑,例如 nano

nano /home/your_username/.ssh/config 

这个 config 文件允许你为经常连接的服务器输入配置,从而自动简化 ssh 命令。以下是该文件中的示例内容,帮助我说明它的作用:

host myserver 
    Hostname 192.168.1.23 
    Port 22 
    User jdoe 
Host nagios 
    Hostname nagios.mynetwork.org 
    Port 2222 
    User nagiosuser 

在示例内容中,我列出了两个主机,myservernagios。对于每个主机,我都标明了如何通过名称或 IP 地址(Hostname 行)来访问它,以及连接时使用的 PortUser 账户。如果我通过文件中列出的名称使用 ssh 连接到这两个主机中的任何一个,它会使用我在那里存储的值,例如:

ssh nagios 

这个命令比手动设置所有选项要短得多。考虑到我有一个 SSH 的 config 文件,这个命令实际上和我手动识别连接详细信息后输入的命令是一样的,手动输入的命令如下所示:

ssh -p 2222 nagiosuser@nagios.mynetwork.org 

我相信你能看出,输入第一个命令比第二个命令要简单得多。通过 SSH 的 config 文件,我可以让一些细节自动应用。因为我已经在文件中说明了我的 nagios 服务器位于 nagios.mynetwork.org,它的 SSH 用户是 nagiosuser,并且它监听的是 2222 端口,所以即使我只输入了 ssh nagios,它也会自动使用这些值。此外,你还可以覆盖这个条目。如果你在使用 ssh 命令时提供了不同的用户名,它会使用你提供的用户名,而不是 config 文件中写的用户名。

在第一个示例中(针对 myserver 服务器),我为连接提供了 IP 地址,而不是主机名。在没有目标服务器的 DNS 条目的情况下,这非常有用。通过这个示例,我不必记住 myserver 的 IP 地址是 192.168.1.23。我只需要执行 ssh myserver,系统就会为我处理好这一切。

config 文件中的每个服务器名称都是任意的,不必与目标服务器的主机名匹配。我本可以将第一个服务器命名为 potato,它仍然会将我路由到 192.168.1.23,所以我可以创建任何我想要的命名快捷方式,无论是最方便我记住的名字还是最容易记住的名称。如你所见,在你的主目录中维护一个包含最常用 SSH 连接的 config 文件,肯定能帮助你保持井井有条,并且让你更容易连接。

总结

在本章中,我们通过多个示例讲解了如何连接到其他网络。我们从配置主机名开始,管理网络接口,分配静态 IP 地址,并了解 Linux 中的名称解析如何工作。本章的相当一部分内容专门讲解了 OpenSSH,这是一个非常有用的工具,允许你远程管理服务器。在 第二十一章《保护你的服务器》中,我们将再次探讨 OpenSSH,重点讲解如何增强其安全性。总的来说,我们才刚刚触及到这个工具的表面。关于 SSH 已经写了整本书,但本章中的示例应该足以让你开始高效使用它。关键在于:练习,练习,再练习!

在下一章中,我们将讨论如何管理软件包。我们将通过添加和删除软件包、添加附加软件库等内容进行讲解!

相关视频

进一步阅读

加入我们社区的 Discord

加入我们社区的 Discord 空间,与作者和其他读者进行讨论:

packt.link/LWaZ0

https://github.com/OpenDocCN/freelearn-linux-pt3-zh/raw/master/docs/ms-ubt-svr/img/QR_Code50046724-1955875156.png

【EI复现】基于主从博弈的新型城镇配电系统产消者竞价策略【IEEE33节点】(Matlab代码实现)内容概要:本文介绍了基于主从博弈理论的新型城镇配电系统中产消者竞价策略的研究,结合IEEE33节点系统,利用Matlab进行仿真代码实现。该研究聚焦于电力市场环境下产消者(既生产又消费电能的主体)之间的博弈行为建模,通过构建主从博弈模型优化竞价策略,提升配电系统运行效率与经济性。文中详细阐述了模型构建思路、优化算法设计及Matlab代码实现过程,旨在复现高水平期刊(EI收录)研究成果,适用于电力系统优化、能源互联网及需求响应等领域。; 适合人群:具备电力系统基础知识和一定Matlab编程能力的研究生、科研人员及从事能源系统优化工作的工程技术人员;尤其适合致力于电力市场博弈、分布式能源调度等方向的研究者。; 使用场景及目标:① 掌握主从博弈在电力系统产消者竞价中的建模方法;② 学习Matlab在电力系统优化仿真中的实际应用技巧;③ 复现EI级别论文成果,支撑学术研究或项目开发;④ 深入理解配电系统中分布式能源参与市场交易的决策机制。; 阅读建议:建议读者结合IEEE33节点标准系统数据,逐步调试Matlab代码,理解博弈模型的变量设置、目标函数构建与求解流程;同时可扩展研究不同市场机制或引入不确定性因素以增强模型实用性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值