Linux-管理秘籍-全-

Linux 管理秘籍(全)

原文:zh.annas-archive.org/md5/d1276a108c48d7de17a374836db89ea5

译者:飞龙

协议:CC BY-NC-SA 4.0

前言

在服务器方面,没有比 Linux 及其发行版系列更受欢迎的操作系统了。无论你现在为哪家公司工作,他们的基础设施中至少有一部分很可能是在运行某种 Linux 系统。

正因为如此,现在是进入 Linux 系统管理和工程(以及相关学科)的最佳时机,尽管我当然会这么说……作为一名 Linux 系统管理员。

这本书旨在成为你在 Linux 世界中某些常见任务的参考和指南,从平凡和基础的任务到有趣和复杂的任务,尽管任何任务都可能变得复杂,只要你努力。我希望在阅读时,你能发现一些新的东西(最好是很多新的东西),也许会遇到一些你以前没有遇到过的建议。

我们还将在工作中变得更加实用(因为仅仅阅读东西是无聊的),在整个过程中使用虚拟机来实现我们的目标。

这本书是为谁准备的

这本书适合各种人,从新手到老手(就像我一样)。

它旨在教会你入门所需的基础知识,同时介绍一些真实世界的例子,并提出一些你可能不知道的各种技巧和窍门。

即使你已经使用 Linux 几十年了,我希望你在这本书中能找到一些你不知道的东西,或者有所启发。

这本书涵盖了什么

第一章《介绍和环境设置》解释了如何设置一个原始环境,以便你了解 Vagrant 在幕后的操作,以及我们在安装方面的做法。

第二章《使用 SSH 进行远程管理》帮助你理解 SSH 的奇迹,以及它如何不仅可以让你的生活更加轻松,而且显著改善。

第三章《网络和防火墙》涵盖了我认为比其他任何主题都更痛苦的一个主题,即网络和防火墙。我们将探讨它们的重要性。

第四章《服务和守护进程》检查了守护进程的本质,追踪它们,并在它们变得过于贪婪时将其终结。本章还涵盖了服务。

第五章《硬件和磁盘》涵盖了任何系统中最险恶的部分,即硬件。在这里,我们将讨论磁盘的问题以及如何解决物理系统的故障。

第六章《安全、更新和软件包管理》涵盖了使服务器有用的内容。软件包必须以某种方式进入系统,我们将调查它们是如何做到的!

第七章《监控和日志记录》探讨了大多数系统管理员都觉得重要但又让人叹息的两个主题。我们将探讨为什么你需要合理的监控和健壮的日志记录。

第八章《权限、SELinux 和 AppArmor》涵盖了许多服务器上已经存在的内在安全系统,无论它们使用和配置起来有多么痛苦。在这里,我们将讨论它们的重要性。

第九章《容器和虚拟化》探讨了我喜欢的一个主题,即操作系统的分割以及你可能如何完成这样一项神秘的任务。

第十章《Git、配置管理和基础设施即代码》讨论了当你的计算机突然死机时不丢失配置的重要性,以及解决方案可以轻松启动和关闭的便利性。

第十一章,Web 服务器、数据库和邮件服务器,探讨了服务器可以提供的一些核心功能,支撑了互联网的大部分发明目标:通信。

第十二章,故障排除和职场外交,阐述了一些基本的故障排除技术,并包含了关于在压力情况下保持冷静的哲学讨论。作者在这方面有一定的权威。

第十三章,BSDs、Solaris、Windows、IaaS 和 PaaS 以及 DevOps,是关于 Linux 世界中一些半相关系统的有趣的最后一章,其中一些你肯定会遇到,一些本应该比它们现在的情况更好。

为了充分利用本书

你需要一台电脑。如果你打算跟着示例一起工作,最简单的方法是使用 Vagrant,这是一种用于通过编程构建便携式开发环境的软件。

在每个具有可运行代码的章节开头,你会找到一个 Vagrantfile 条目。这可以从前言后面提供的链接中下载,但如果你愿意的话也可以手动输入。

为了获得最佳体验,我建议使用至少四个内核和最好是 8GB RAM 的电脑,尽管你可能需要根据自己的目的进行调整。

本书假设您具有使用命令行在 Linux 文件系统中移动的基本知识。

下载示例代码文件

你可以从www.packt.com的账户中下载本书的示例代码文件。如果你在其他地方购买了本书,你可以访问www.packt.com/support并注册,文件将直接发送到你的邮箱。

你可以按照以下步骤下载代码文件:

  1. 登录或注册www.packt.com

  2. 选择“支持”选项卡。

  3. 点击“代码下载和勘误”。

  4. 在搜索框中输入书名,然后按照屏幕上的说明操作。

文件下载后,请确保使用最新版本的解压缩软件解压缩文件夹:

  • WinRAR/7-Zip for Windows

  • Zipeg/iZip/UnRarX for Mac

  • 7-Zip/PeaZip for Linux

该书的代码包也托管在 GitHub 上,网址是github.com/PacktPublishing/Linux-Administration-Cookbook。如果代码有更新,将在现有的 GitHub 存储库中更新。

我们还有其他代码包,来自我们丰富的图书和视频目录,可在github.com/PacktPublishing/上找到。去看看吧!

下载彩色图片

我们还提供了一个 PDF 文件,其中包含本书中使用的屏幕截图/图表的彩色图片。你可以在这里下载:www.packtpub.com/sites/default/files/downloads/9781789342529_ColorImages.pdf

使用的约定

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

CodeInText:指示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟 URL、用户输入和 Twitter 用户名。这是一个例子:“我已经为本章准备了以下Vagrantfile。”

代码块设置如下:

# -*- mode: ruby -*-
# vi: set ft=ruby :

$provisionScript = <<-SCRIPT
sed -i 's#PasswordAuthentication no#PasswordAuthentication yes#g' /etc/ssh/sshd_config
systemctl restart sshd
SCRIPT

当我们希望引起你对代码块的特定部分的注意时,相关行或项目会以粗体显示:

[vagrant@centos2 ~]$ ip a
<SNIP>
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:56:c5:a7 brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.11/24 brd 192.168.33.255 scope global noprefixroute eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe56:c5a7/64 scope link 
       valid_lft forever preferred_lft forever

任何命令行输入或输出都以以下形式书写:

[vagrant@centos1 ~]$ ssh centos2 -X

粗体:表示一个新术语、一个重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词会以这种形式出现在文本中。这是一个例子:“从主屏幕上做的最后一件事是设置我们的安装目的地。”

警告或重要说明会以这种形式出现。

技巧和窍门会以这种形式出现。

章节

在本书中,您会发现一些经常出现的标题(准备工作如何操作它是如何工作的还有更多另请参阅)。

为了清晰地说明如何完成一道菜谱,使用这些部分如下:

准备工作

本节告诉您可以在配方中期望什么,并描述了如何设置任何软件或配方所需的任何初步设置。

如何操作

本节包含了遵循配方所需的步骤。

它是如何工作的

本节通常包括对前一节发生的事情的详细解释。

还有更多

本节包含了有关配方的额外信息,以使您对配方更加了解。

另请参阅

本节提供了有用的链接,指向其他有关该配方的信息。

联系我们

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

一般反馈:如果您对本书的任何方面有疑问,请在邮件主题中提及书名,并发送电子邮件至customercare@packtpub.com

勘误:尽管我们已经尽最大努力确保内容的准确性,但错误是难免的。如果您在本书中发现错误,我们将不胜感激地接受您的报告。请访问www.packt.com/submit-errata,选择您的书籍,点击勘误提交表格链接,并输入详细信息。

盗版:如果您在互联网上发现我们作品的任何非法副本,请提供给我们地址或网站名称。请通过copyright@packt.com与我们联系,并附上材料链接。

如果您有兴趣成为作者:如果您在某个专题上有专业知识,并且有兴趣撰写或为一本书做出贡献,请访问authors.packtpub.com

评论

请留下评论。在阅读并使用本书后,为什么不在购买网站上留下评论呢?潜在读者可以看到并使用您的客观意见来做出购买决策,我们在 Packt 可以了解您对我们产品的看法,我们的作者也可以看到您对他们书籍的反馈。谢谢!

有关 Packt 的更多信息,请访问packt.com

第一章:介绍和环境设置

在本章中,我们将涵盖以下内容:

  • 理解和选择发行版

  • 安装 VirtualBox

  • 手动安装我们选择的发行版

  • 连接到我们的虚拟机VM

  • 访问和更新我们的虚拟机

  • 了解 VM 的不同之处

  • 快速的sudo解释

  • 使用 Vagrant 自动配置 VM

  • 轶事(尝试,尝试,再尝试)

介绍

在我们深入讨论我们将使用哪种发行版(有时缩写为“distro”)之前,我们必须首先迈出一大步,以一种略带哲学色彩的方式考虑Linux的概念。

对“Linux 是什么”进行一个良好的描述可能很难确定,这在很大程度上是由 IT 专业人员故意传播的混乱造成的,因为当他们来解释时,这让他们听起来比实际上更聪明。

因为你正在阅读这本书,我会假设你对 Linux 有一个较高层次的了解;你知道它是一个操作系统OS),就像 Windows 或 macOS 一样,它并没有受到太多关注,并且通常不在桌面上使用。

这个评估是对也是错的,这取决于你和谁在说话。

悠闲的系统管理员sysadmins)会更加放松,点点他们 80 年代的摩西头,同意 Linux 是一个操作系统,而且是一个不错的操作系统。然后他们会回去玩他们这周正在学习的时髦软件,以便下周可以尝试将其塞入基础设施中。

自称为灰发老者的人会停下手头的工作,大声叹息,拿起他们的第四杯咖啡,然后转身给你讲解 GNU/Linux(或 GNU+Linux)和 Linux 内核之间的区别。

内核是任何完整操作系统的重要组成部分。它是软件的一部分,位于硬件和软件之间,执行在两者之间进行翻译的繁重工作。所有操作系统都会有某种类型的内核,例如,macOS 的内核被称为XNU

你将收到的讲座将会很乏味,会涉及到 Richard Stallman、Linus Torvalds,甚至可能还有 Andrew Tanenbaum 等人的名字,甚至可能会持续一个多小时,但主要的收获是 Linux 是你正在学习的操作系统的公认名称,同时也在技术上是不正确的。他们会说 Linux 实际上只是内核,所有其他东西都是包裹在 GNU 工具套件之上的发行版。

被认为是明智的避免这场辩论

在本书中,当我提到 Linux 时,我指的是整个操作系统,当我提到内核时,我实际上是在谈论 Linux 内核,其开发由 Linus Torvalds 领导。

理解和选择发行版

如前一节所示,Linux 是分散的。由于可以从众多不同供应商那里下载不同的发行版,这是描述这一点的最好方式。其中一些供应商是营利性的,提供支持合同和购买他们的操作系统时的服务级别协议(SLA),而另一些则完全是自愿的,由一个人在他们的车库里管理。

有成百上千种发行版可供选择,每种发行版都有自己的拥护者军团,告诉你为什么他们的发行版是“唯一真正的发行版”,“真的没有理由去寻找其他的”。

也有为特定目的创建的 Linux 发行版,例如据称是朝鲜 Linux 发行版的 Red Star OS。

事实上,大多数企业使用他们使用的 Linux 发行版是因为它:

  • 当所有者谷歌搜索免费操作系统时,第一个弹出来的

  • 第一个 IT 管理员喜欢的

  • 提供可以在出现故障时调用的合同

逐个介绍当前存在的每个发行版是徒劳的,因为它们几乎每周都在被创建或被放弃。相反,我将介绍一些受欢迎的选择(在服务器空间而不是桌面上),解释一些关键区别,然后谈论我将在本书的其余部分中使用的发行版。

如果你的企业使用的发行版不是我们在这里讨论的,不要灰心 - 大多数工具在各种发行版上都是一致的,而在有差异的地方,都有文档可以帮助你。

如果你想了解更多关于可用发行版的信息,一个名为 DistroWatch 的网站(distrowatch.com/)已经存在多年,提供一个定期更新的 Linux 发行版列表,按页面点击排名组织。

Ubuntu

Ubuntu 是我安装的第一个 Linux 发行版,我敢打赌,对于很多在 2000 年代中期开始使用 Linux 的人来说也是如此。这也是我用来写这本书的发行版。

由于其良好的营销尝试(包括在搜索Linux时在谷歌排名中的位置)、被视为“人类的 Linux”以及用户友好性,它在桌面上一直享有一致的关注。

在 Debian 的下游,Ubuntu 的开发由 Canonical 负责,虽然他们最初强调制作坚固的桌面操作系统,但他们后来进入了试图主导服务器空间的高尚领域,并且也进入了物联网设备市场。

当我们在这方面说“下游”时,我们的意思是 Ubuntu 与 Debian 共享许多基础,只是它添加了一些额外的部分并去掉了一些部分。在 Linux 世界中,很少有从头开始的发行版,大多数使用其他发行版作为基础。

Ubuntu 以其可爱的命名惯例(18.04 被称为 Bionic Beaver)而闻名,事实上 Ubuntu 在桌面上如此受欢迎,这意味着它是系统管理员在服务器上安装的明显选择,因为他们已经熟悉它。

最近,处理继承系统时越来越常见的是找到 Ubuntu 安装,通常是一个长期支持(LTS)版本(这样可以避免在合理的时间内升级操作系统时的混乱和头痛)。

Ubuntu 每六个月发布一次版本,每两年发布一个 LTS 版本(最近的是 14.04、16.04 和 18.04)。它们的编号惯例是发布年份,后跟月份(所以 2018 年 4 月是 18.04)。可以从一个版本升级到另一个版本的 Ubuntu。

Canonical 在 Ubuntu 中引入新技术和软件时也毫不犹豫,即使它与他们的 Debian 基础有所偏离。最近的例子包括以下内容:

  • Snaps:一种分发与发行无关的软件的方式

  • Upstart:一种替代初始化系统,后来也被systemd替换

  • Mir:一种显示服务器,最初构想为取代老化的 X Window 系统

Ubuntu 可以从ubuntu.com下载。

Debian

如前所述,Debian(通用操作系统)是许多后来的其他发行版的基础,但它一直是最受欢迎的发行版之一,无论是在桌面上还是在服务器上。很可能你会选择自己安装 Debian,或者继承一个运行这个发行版的系统,因为它以稳定性而闻名。

传统上,服务器空间的争夺是在两个阵营之间进行的,即 Debian Druids 和 CentOS Cardinals。近年来,新手已经加入了这场争夺(比如 Ubuntu),但这两个仍然控制着相当多的硬件。

每两三年发布一次,Debian 版本以《玩具总动员》角色命名(7—Wheezy,8—Jessie,9—Stretch)。它以稳定性而闻名,具有经过试验和测试的软件版本,以及合理的回溯修复。

回溯是指从最近的软件版本(例如内核本身)中获取修复,并将这些修复合并到您正在运行的版本中,将其重新编译为新的软件。由于功能可能会引入更多破坏性变化到长期支持的发行版中,功能很少会被回溯。

有时会对 Debian 提出一些批评,因为它通常在发布版本中提供较旧版本的软件包,这可能不包括系统管理员想要的所有时髦和酷功能,或者开发人员想要的。鉴于人们通常在服务器世界中寻求稳定性和安全性,而不是最新和最伟大的 Node.js 版本,这是不公平的。

Debian 有坚定的捍卫者,并且在许多人心中占有特殊地位,尽管在一些企业环境中看到它是不寻常的,因为它是由 Debian 项目开发的,而不是传统公司可以提供支持合同。根据我的个人经验,我更经常在需要快速解决方案的小公司和仍在运行一些传统系统的稍大公司中看到 Debian。

Debian 可以从www.debian.org下载。

CentOS - 我们将主要使用的一个

在传统的服务器领域战争的另一部分,CentOS 拥有自己的士兵和烈士。它仍然被广泛使用,并以稳定和无聊的声誉与 Debian 相媲美。

社区企业操作系统CentOS)是红帽企业 Linux 发行版的免费可用和编译版本,旨在提供功能兼容性,通常用 CentOS 标志取代红帽标志,以避免侵犯商标。 (2014 年 1 月宣布红帽将与 CentOS 合作,以帮助推动和投资于 CentOS 的发展。)

因为它的性质,许多系统管理员安装了 CentOS 来更好地了解红帽世界,因为(如前所述)红帽在企业公司中有很好的声誉,所以安装一些如此相似的东西是有道理的。

这种安装趋势是双向的。我见过一些公司最初安装 CentOS,因为它很容易获得,并允许他们轻松设计基础架构,利用公开可用的免费仓库,然后转移到 RHEL 部署成品。

仓库是指软件安装在 Linux 系统上的常见位置的简称。Windows 通常从网站下载,macOS 有应用商店,Linux 在大部分时间里使用软件仓库,并且在命令行上很容易搜索。

我还见过一些公司在所有地方部署了 RHEL,只是意识到他们花了很多钱,却从未使用他们购买的支持,因为他们的运营团队实在太优秀了!然后他们逐渐淘汰了他们的红帽部署,并转移到了 CentOS,在这个过程中几乎没有改变。

每隔几年发布一次版本,版本 7 于 2014 年发布,并自那时起持续更新。然而,应该注意的是,2011 年发布的版本 6 将在 2020 年之前获得维护更新。

CentOS 可以从centos.org下载。我们将在安装部分进行介绍。

红帽企业 Linux

红帽企业 Linux,或者更常见的 RHEL(因为它的名字很长),在企业中有着非常牢固的基础。它非常适合商业领域,因此很常见的情况是你发现自己在一个 RHEL 的系统上,而你最初以为它是一个 CentOS 的安装。

RHEL 的不同之处在于红帽公司提供的支持,以及如果你购买了官方软件包,你可以利用的各种服务。

尽管红帽仍然毫不犹豫地提供他们发行版的源代码(因此有了 CentOS),但他们为从桌面到数据中心安装的各种版本和软件包销售。

有一句谚语说“没有人因为购买 IBM 而被解雇”,这在今天有点过时,但我听说人们在多次场合上引用这个哲学来描述红帽。没有人会因为购买红帽而被解雇(但你可能会被问到为什么要为另一个名字下免费提供的东西付费的好处是什么)。

美妙的是,在我编辑这本书的过程中,IBM 宣布收购了红帽,这使我上面的评论变得完整。宇宙有时候真是伟大。

除了支持外,其他企业喜欢的商业态度,以及对整个社区的贡献,红帽还提供了一些被描述为“浪费时间”和“对这个角色至关重要”的东西。

考试在 Linux 社区中备受喜爱和嘲笑,这取决于你和 Linux 社区中的谁说话(就像许多事情一样,关于它们有一些圣战)。红帽提供了两个最受欢迎的考试,还有更多。你可以学习并成为红帽认证系统管理员,然后是红帽认证工程师,这被广泛认为是非常可接受的资格。

作为一名大学辍学生,我很高兴拥有 RHCE 资格。

有些人认为这些考试是为了通过那些招聘者的第一道关卡(就像那些扫描你的简历并寻找他们认识的徽章的人)。其他人认为这些考试证明了你对 Linux 系统的了解,因为这些考试是实际的(意味着他们让你坐在电脑前,给你一组完成的步骤)。有些人完全不理会考试,尽管他们通常是那些从未尝试过考试的人。

查看www.redhat.com,特别注意提供的各种软件包。他们也有开发者账户,可以让你访问你本来需要付费的服务(只要你不试图将它们悄悄地引入生产环境!)。

安装 VirtualBox

正如我在前一节中所说,我选择在这本书中大多数情况下使用 CentOS。希望这为你提供了一个很好的基础来学习 Linux 管理,同时也为你提供了一些优势,如果你打算参加任何红帽的考试。

我不会要求你有一台备用的笔记本电脑,或者在某个地方租用服务器,我会主张使用虚拟机来测试和运行给出的示例。

虚拟机正如它们的名字所示 - 是一种在一个或一组物理机上虚拟化计算机硬件的方式,因此允许你进行测试、破坏和尽情玩耍,而不会冒着使自己的计算机无法启动的风险。

创建虚拟机有很多种方式:macOS 有 xhyve,Windows 有 Hyper-V,Linux 有一个称为 Kernel Virtual Machine(KVM)的本地实现。

KVM(连同 libvirt)是你在 Linux 虚拟化领域经常遇到的技术。它构成了流行技术的基础,比如 Proxmox 和 OpenStack,同时提供接近本机速度。

另一种创建和管理虚拟机的方法是一个名为 VirtualBox 的程序,现在由 Oracle 开发。这个软件的好处,也是我在这里使用它的原因,是它是跨平台的,适用于 macOS、Windows 和 Linux。

在 Ubuntu 上安装 VirtualBox

我正在使用 Ubuntu 来撰写这本书,所以我将介绍在 Ubuntu 桌面上安装 VirtualBox 的基本方法。

这与在其他发行版上安装有些不同,但其中有很多都为其提供了安装包,并应该提供安装指南。

命令行安装

打开您的终端并运行以下命令:

$ sudo apt install virtualbox 

使用 sudo 通常会提示您输入密码,当您输入时,屏幕上不会显示任何内容。

您可能会被提示确认安装 VirtualBox 及其依赖项(可能有很多-这是一个复杂的程序,如果您有一段时间没有更新,您可能也会得到一些依赖项更新)。

YEnter继续。以下屏幕截图显示了从命令行启动安装的示例:

完成后,您应该已经安装了一个可用的 VirtualBox。

图形安装

如果愿意,您也可以通过 Ubuntu 软件安装 VirtualBox。

只需搜索您想要的软件,在本例中是 VirtualBox,并转到其商店页面。

然后,点击安装,软件包将被安装,无需终端!

安装后,您的屏幕将更改以显示启动和删除选项。

在 macOS 上安装 VirtualBox

虽然我正在使用 Ubuntu,但如果您不是,也不是世界末日。macOS 也是一个很好的操作系统,并且方便的是它支持 VirtualBox。

在本教程中,我们将介绍在 macOS 中安装 VirtualBox 的几种方法。您会发现,无论您使用的操作系统是什么,布局都非常相似。

命令行安装

如果您已经安装了命令行程序brew,那么获取 VirtualBox 就像运行以下命令一样简单:

$ brew cask install virtualbox

您可能会被提示输入超级用户密码以完成安装。

Homebrew 可以从brew.sh/获得,并且实际上是 macOS 需要但默认没有的软件包管理器。我不建议盲目地从神秘的网站运行脚本,所以在您决定冒险安装 brew 之前,请确保您了解正在执行的操作(阅读代码)。

图形安装

Oracle 还为 macOS 提供了安装镜像,如果您愿意以更传统的方式安装。

只需转到www.virtualbox.org/wiki/Downloads并选择 OS X hosts 选项。

这将提示您将安装程序下载到本地系统,然后您可以解压并安装。

在安装过程中,您可能会被提示输入超级用户密码。

在 Windows 上安装 VirtualBox

如果您的计算机上没有使用 Linux 版本,也没有使用 macOS,那么您很可能在运行 Windows(除非您已经在桌面上深入研究了 FreeBSD 或类似的操作系统,在这种情况下我无法帮助您-我们需要整整一个下午)。

如果使用 Windows,我可以再次建议 VirtualBox,因为它具有跨操作系统的特性,并且可以再次从 Oracle 的网站上安装。

图形安装

像 macOS 安装一样,转到www.virtualbox.org/wiki/Downloads并选择 Windows hosts 选项:

这将下载一个可执行文件。

值得注意的是,如果您尝试同时运行多个虚拟化解决方案,Windows 可能会抱怨。如果您之前运行过 Hyper-V 或 Docker,并且在尝试启动 VirtualBox 虚拟机时遇到问题,请先尝试禁用其他解决方案。

手动安装我们选择的发行版

哦,这是一次旅程,我们甚至还没有正式开始!

接下来,我们将手动设置一个虚拟机。但不要担心!我们还将研究使用 Vagrant 自动化此过程,以避免在本书的其余部分中执行重复的步骤。

如果您已经熟悉安装 CentOS,可以完全跳过本节。我已经在本书的其余部分提供了 Vagrantfiles,用于自动化我们将要使用的虚拟机。

获取我们的 CentOS 安装媒体

Linux 发行版的主要分发方式是 ISO 镜像。然后可以将这些镜像刻录到 DVD 上,或者挂载到虚拟机以引导。

前往centos.org/download/,看看提供的选项。

我将下载最小 ISO,原因很快就会变得清楚。

点击后应该会带您到一个镜像页面:

这是 CentOS 项目为节省带宽而采取的措施,通过提示最终用户从任意数量的不同主机下载。他们可以将带宽成本分摊给志愿者。

你会发现这些提供者通常分为两类,但也有例外。通常,这些镜像是由大学或托管提供商提供的。我内心的怀疑者认为,托管提供商提供镜像服务是一种易于营销的方式,而不是一种慈善举动。

选择一个靠近您的下载位置,并等待下载完成。

您可能会注意到其中一个下载选项是通过 Torrent。通过 Torrent 下载是一种将带宽成本分摊给多个人的好方法,并且允许从多个位置下载软件的小部分大大减少了任何一个来源的负载。但应该注意,一些工作场所会注意他们的网络上是否有这种类型的流量,因为 Torrent 下载的声誉。

检查校验和

一旦下载完成(可能需要一段时间,因为即使是最小的也很大),您将面对一个 ISO 镜像。

在我的 Ubuntu 安装中,我可以在我的Downloads文件夹中看到它:

$ ls ~/Downloads/
CentOS-7-x86_64-Minimal-1804.iso

确认我们的安装媒体并确保我们已经下载了我们期望的内容的一种方法是比较已下载文件的Sha256总和与已知良好值。这既证明了它是我们期望的下载,也检查了文件下载过程中是否发生了损坏。

CentOS 提供了一个发布说明页面,我们可以访问以找到我们要比较的Sha256总和:wiki.centos.org/Manuals/ReleaseNotes

点击进入 CentOS 7 的发布说明,这应该会带您到最新版本的发布说明。

在这个页面上,我们可以滚动到验证已下载安装镜像,其中将列出下载镜像的当前Sha256总和。

始终确保您获取已知良好的Sha256值的网站本身是合法的。

在我的情况下,我可以看到我刚下载的文件的Sha256值如下:

714acc0aefb32b7d51b515e25546835e55a90da9fb00417fbee2d03a62801efd  CentOS-7-x86_64-Minimal-1804.iso

有了这个,我可以回到终端中列出文件的地方,并运行一个基本命令来检查已下载镜像的Sha256值:

$ sha256sum CentOS-7-x86_64-Minimal-1804.iso 
714acc0aefb32b7d51b515e25546835e55a90da9fb00417fbee2d03a62801efd  CentOS-7-x86_64-Minimal-1804.iso

将 CentOS 网站上的值与我下载的镜像的值进行比较,确认它们是相同的。

媒体与我们预期的一样!

Sha256检查也可以在 Windows 和 macOS 上进行。在 macOS 上,可以使用内置工具来完成,但 Windows 可能需要其他软件。

设置我们的虚拟机

现在我们有了媒体并且 VirtualBox 已安装,是时候手动进行机器配置(技术术语)并安装 CentOS 了。

在这一部分,我们将配置一个小型虚拟机,但即使这样也会消耗处理能力、内存和磁盘空间。始终确保您有适当的资源可用于您要创建的机器。在这种情况下,至少需要 50GB 的可用磁盘空间和至少 8GB 的内存。

VirtualBox 主窗口

启动后,您将看到 VirtualBox 主窗口。目前,我们只对左上角的“新建”按钮感兴趣。您需要点击“新建”按钮。

接下来,您将被提示为您的虚拟机命名。

将您的第一台机器命名为CentOS-1

注意当您为机器命名时,类型和版本会自动检测您输入的内容,并根据需要重新配置选择。

在这种情况下,它给我们提供了 Linux 类型和 Red Hat 版本(64 位)。这没问题,因为我们之前说过 CentOS 和 Red Hat Enterprise Linux 非常接近。

点击下一步。

64 位是操作系统的架构,尽管您安装的操作系统必须受到您的 CPU 支持(大多数 CPU 现在都是 x86_64)。常见的架构一直是 x86(32 位)和 x86_64(64 位),但最近 x86 变种已经逐渐消失。如今最常见的安装是 x86_64,尽管 ARM 和 aarch64 机器变得越来越普遍。在本书中,我们只会使用 x86_64 机器。

现在,我们必须配置要为我们的机器提供的内存量。如果受到限制,您可以将其设置为低于默认值1024MB(1GB),但 1,024MB 是一个合理的起点,如果需要,我们随时可以调整它。

现在,我们将被提示为我们的虚拟系统配置硬盘。

保留默认选项“现在创建虚拟硬盘”,并点击创建。

您将被提示选择类型。保留默认选择,即 VDI(VirtualBox 磁盘映像)。

您将有选择随时间分配磁盘空间(动态分配)或一次性分配所有空间(固定大小)的选项。我倾向于将其保留为动态分配。

接下来,您将被提示选择磁盘的位置和大小。我建议将磁盘保留在默认位置,并且暂时默认大小的8GB 应该足够开始使用。

点击创建。

如果一切顺利,您将返回主窗口,并且新的虚拟机应该会显示在左侧处于“已关闭”状态。

CentOS 安装

现在我们有了虚拟机,是时候在上面安装我们的操作系统了。

在主 VirtualBox 窗口顶部点击“启动”,选择您的虚拟机,应该会提示您首先选择启动盘。

我已经导航到我的“下载”文件夹,并选择了之前下载的镜像。

按下“启动”将从我们的媒体引导机器。

您将在虚拟机中看到选项屏幕,默认选择了“测试此媒体并安装 CentOS 7”。

我通常按下箭头键(在虚拟机窗口内)只选择安装 CentOS 7 并跳过媒体检查,尽管您可能希望进行测试。

如果您使用物理媒体来安装机器(DVD 或 CD),在安装之前进行媒体测试可能是个好主意。

按下Enter将继续安装。

您将被提示选择您的语言。我选择英语,因为我只会一种语言。

完成后,您将会发现自己在最新的 CentOS 安装程序的起始页面上:

注意底部的消息,建议需要完成标有黄色图标的项目。

因为我们的日期/时间、键盘和语言都是正确的,我们将继续进行下一步,但如果对您来说有错,可以随时更正。

注意,在“安装源”下我们选择了“本地媒体”,在“软件选择”下我们选择了“最小安装”。这是我们之前选择最小镜像的结果,并给了我们一个很好的机会来谈论通过互联网进行安装。

首先,我们需要配置我们的网络。点击“网络和主机名”来做这个。

你应该有一个以太网设备,作为制作 VM 时默认配置的一部分。

切换设备名称右侧的开/关切换,并检查网络值是否与我的类似:

VirtualBox 默认创建一个 NAT 网络,这意味着你的 VM 不在与主机计算机完全相同的网络上。相反,VM 存在于一个独立的网络中,但通过主机机器有通往外部世界的路径。

在左上角按“完成”来完成我们的网络设置(暂时)!

回到主屏幕,点击“安装源”:

在这个屏幕上,你可以看到自动检测到的媒体实际上是我们的磁盘映像(sr0是 Linux 对光驱的表示)。

将选中的单选按钮更改为“在网络上”。

在 URL 栏中填入以下内容:

mirror.centos.org/centos/7/os/x86_64/

你最终会看到以下截图:

在左上角按“完成”。

一旦你回到主屏幕,会显示你的软件源已经改变,你需要通过进入“软件选择”窗口来验证这一点。继续进行。

浏览不同的选项,但现在保留“最小安装”选中并点击“完成”:

从主屏幕上做的最后一件事是设置我们的“安装目的地”。点击进入这个屏幕。

看看选项,但现在我们不打算改变默认的分区布局,或者加密我们的磁盘。你还应该看到默认选择的磁盘是我们的 8GB VirtualBox 磁盘。

点击“完成”(你不需要做任何更改,但安装程序至少让你进入这个屏幕):

我们终于完成了我们(相当基本的)配置。在主屏幕底部点击“开始安装”按钮。

你会看到安装开始,并在等待时会看到以下屏幕:

依次点击顶部的选项,设置root密码并创建一个用户。

root用户类似于 Windows 系统上的管理员;它是全能的,在错误的手中可能会很危险。有些发行版甚至在安装时都不提示你设置 root 密码,而是让你使用自己的用户和susudo

在创建用户时,也将其标记为管理员:

点击“完成”将带你回到安装进度屏幕,你可能会被提示完成安装的其余部分,并最终被要求重新启动到你新安装的系统中。

没有理智的人应该产生那么多的截图。

访问和更新我们的 VM

现在我们已经安装了 VM,我们将登录并快速查看一下。

从 VirtualBox 窗口登录

点击进入我们的 VM,就像我们在安装时所做的那样,允许我们在登录提示符处输入:

我们将使用安装时创建的用户,而不是 root。

注意,你还会得到有关自上次登录以来的登录尝试的一些信息。在这种情况下,我第一次尝试登录失败了,它告诉我这一点:

恭喜-你已经安装了 CentOS!

很少见到安装了图形用户界面GUI)的 Linux 服务器,尽管确实会发生。在我使用过的成千上万台服务器中,我只能用一只手数出安装了 GUI 的次数。通常会导致短暂的困惑和苦恼,然后得出结论,即肯定是有人不小心安装了 GUI - 没有其他解释。

在我们继续之前,我们将运行一个快速命令来查找我们机器的 IP 地址:

$ ip a

ip a是输入ip address的一种简写方式,我们稍后会更详细地介绍。

这给了我们很多网络信息,但至关重要的是它给了我们网络接口的inet地址,即10.0.2.15

从主机终端登录

因为使用 VirtualBox 界面有点麻烦(使得复制和粘贴之类的事情变得棘手),所以有一个更优雅的连接和与我们的机器交互的方法是有意义的。

安全外壳SSH)是我们将要使用的工具,因为它提供了一种快速和安全的连接远程机器的方式。

原生 SSH 客户端适用于 macOS 和所有 Linux 发行版。Windows 在这方面也取得了一些进展,尽管我知道在 Windows 上使用 SSH 的最简单方法仍然是下载一个名为 PuTTY 的程序。

把 SSH 想象成 Windows 远程桌面协议。如果你是这个领域的新手,它通常更快,因为它不需要向你传输图形连接。SSH 完全是基于文本的。

使用我们刚才的 IP 地址,我们将尝试从主机(你在 VirtualBox 上运行的机器)SSH 到我们的虚拟机:

$ ssh adam@10.0.2.15
ssh: connect to host 10.0.2.15 port 22: Connection refused

哦,不!有些不对劲!

我们还没有连接,连接显然被拒绝了!

确保 sshd 正在运行

首先,我们要确保sshd的服务器组件正在运行,方法是登录到 VirtualBox 中的虚拟机并运行以下命令:

$ sudo systemctl enable sshd
$ sudo systemctl start sshd

你应该被提示(至少一次)输入之前设置的用户密码。

我们正在启用sshd服务,使其在服务器启动时启动第一个命令,并立即启动它(这样我们就不必重新启动虚拟机)。

确保 VirtualBox 让我们通过

仅仅启动sshd还不足以让我们从主机连接到虚拟机,我们还必须为 VirtualBox NAT 网络设置一些端口转发。

端口转发是手动指定流量如何穿越 NAT 网络的方法。如果你在 2000 年代中期玩 Diablo 2 或魔兽争霸 III,你可能会很有趣地尝试让端口转发与你家里的路由器一起工作。

从主 VirtualBox 窗口,突出显示你的虚拟机并点击顶部的设置。转到网络部分并点击高级旁边的箭头以展开更大的部分。点击端口转发:

在弹出的新窗口中,点击右侧添加新规则,并使用以下截图中的设置填充它,如果你的 Guest IP 不同,就替换它:

请注意,我们实际上是将我们主机上的127.0.0.1:2222映射到我们虚拟机上的10.0.2.15:22。我们设置了这样的连接尝试,使得任何连接尝试都会被转发到端口22的虚拟机,从而连接到我们主机机器的localhost地址上的端口2222

在给出的示例中,2222是完全随机的 - 它可以是822251232020等等。我选择2222是为了方便。你不应该尝试使用低于1024的端口进行这种操作,因为这些端口只允许 root 访问。

我们现在可以尝试我们刚刚设置的 SSH 命令:

$ ssh adam@127.0.0.1 -p2222
The authenticity of host '[127.0.0.1]:2222 ([127.0.0.1]:2222)' can't be established.
ECDSA key fingerprint is SHA256:M2mQKN54oJg3B1lsjJGmbfF/G69MN/Jz/koKHSaWAuU.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '[127.0.0.1]:2222' (ECDSA) to the list of known hosts.
adam@127.0.0.1's password: 

关于这个命令有一些要解释的事情。

我通过使用adam@指定了用户名,并告诉 SSH 尝试连接到本地地址127.0.0.1,以及我们选择的端口,即2222

我们会看到主机的指纹,稍后我们会更详细地讨论,并且我们会接受它。

然后,我们将使用在 VM 中设置的密码登录我们的用户:

Last login: Mon Aug  6 15:04:26 2018
[adam@localhost ~]$ 

成功!

现在我们可以像处理真实服务器一样处理我们的 VM - 只需确保在运行任何命令时您在 VM 上。

更新我们的 VM

现在我们已经可以访问我们的机器,我们将运行一个命令,以确保我们拥有所有安装软件的最新版本:

$ sudo yum update

运行时,您可能会看到一个需要更新的软件的长列表。输入Y进行确认并按Enter将处理此软件的升级,以及任何需要的依赖软件。您可能还会被提示接受新的或更新的 GPG 密钥。

GPG 是一本书的内容 - 不是一本令人兴奋的书,但肯定是一本书。

如果您升级了不断运行的软件,比如 Apache web 服务器,最好安排重新启动该服务,以确保使用新版本。

一般来说,只有内核和 init(初始化)系统在更新后才需要完全系统重启。这与 Windows 有着明显的不同,Windows 似乎设计用于重启,而实际工作只是副产品。

在我的情况下,我的内核得到了更新。我可以通过以下方式确认这一点。

首先,我们列出已安装的kernel包的版本:

$ yum info kernel
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: repo.uk.bigstepcloud.com
 * extras: mirror.sov.uk.goscomb.net
 * updates: mirrors.melbourne.co.uk
Installed Packages
Name        : kernel
Arch        : x86_64
Version     : 3.10.0
Release     : 862.el7
Size        : 62 M
Repo        : installed
From repo   : anaconda
Summary     : The Linux kernel
URL         : http://www.kernel.org/
Licence     : GPLv2
Description : The kernel package contains the Linux kernel (vmlinuz), the core of any
 : Linux operating system.  The kernel handles the basic functions
 : of the operating system: memory allocation, process allocation, device
 : input and output, etc.

Name        : kernel
Arch        : x86_64
Version     : 3.10.0
Release     : 862.9.1.el7
Size        : 62 M
Repo        : installed
From repo   : updates
Summary     : The Linux kernel
URL         : http://www.kernel.org/
Licence     : GPLv2
Description : The kernel package contains the Linux kernel (vmlinuz), the core of any
 : Linux operating system.  The kernel handles the basic functions
 : of the operating system: memory allocation, process allocation, device
 : input and output, etc.

然后,我们使用uname检查当前正在使用的内核的版本:

$ uname -a
Linux localhost.localdomain 3.10.0-862.el7.x86_64 #1 SMP Fri Apr 20 16:44:24 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

从中我们可以看到我们正在运行版本3.10.0-862.el7,但我们也有3.10.0-862.9.1.el7

重新启动系统会导致在启动时选择新的内核,再次运行uname会显示不同的结果:

$ uname -a
Linux localhost.localdomain 3.10.0-862.9.1.el7.x86_64 #1 SMP Mon Jul 16 16:29:36 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

万岁 - 我们正在运行更新的内核!

了解 VM 的不同之处

早些时候,我们开始谈论 VM 和它们是什么。现在我们将从机器内部看一下几种确定我们是否在 VM 中的方法。

如果我从托管提供商那里获得了一个新的虚拟专用服务器VPS),并想知道用于虚拟化我的新机器的软件是什么,我通常会这样做。

dmidecode

我最喜欢的工具之一,dmidecode,可以用来转储计算机的桌面管理接口DMI)表。在实践中,这意味着它可以用来查找在机器中运行的硬件类型。

此命令需要 root 访问权限,因此在这些示例中我们将一直使用sudo

首先,我们将列出可以传递给 dmidecode 的有效types

$ dmidecode --type
dmidecode: option '--type' requires an argument
Type number or keyword expected
Valid type keywords are:
 bios
 system
 baseboard
 chassis
 processor
 memory
 cache
 connector
 slot

从顶部开始,我们将使用bios,看看它是否给我们一些有用的东西:

$ sudo dmidecode --type bios
# dmidecode 3.0
Getting SMBIOS data from sysfs.
SMBIOS 2.5 present.

Handle 0x0000, DMI type 0, 20 bytes
BIOS Information
 Vendor: innotek `GmbH`
 Version: VirtualBox
 Release Date: 12/01/2006
 Address: 0xE0000
 Runtime Size: 128 kB
 ROM Size: 128 kB
 Characteristics:
 ISA is supported
 PCI is supported
 Boot from CD is supported
 Selectable boot is supported
 8042 keyboard services are supported (int 9h)
 CGA/mono video services are supported (int 10h)
 ACPI is supported

立即,我们可以在Version旁边看到VirtualBox,这是一个相当明显的暗示,表明我们正在处理一个 VM。

接下来,我们将选择其他内容,system

$ sudo dmidecode --type system
# dmidecode 3.0
Getting SMBIOS data from sysfs.
SMBIOS 2.5 present.

Handle 0x0001, DMI type 1, 27 bytes
System Information
 Manufacturer: innotek GmbH
 Product Name: VirtualBox
 Version: 1.2
 Serial Number: 0
 UUID: BDC643B8-8D4D-4288-BDA4-A72F606CD0EA
 Wake-up Type: Power Switch
 SKU Number: Not Specified
 Family: Virtual Machine

再次看到这里的Product NameVirtualBoxFamilyVirtual Machine,这两者都是相当有力的证据。

最后,我们将查看Chassis Information

$ sudo dmidecode --type chassis
# dmidecode 3.0
Getting SMBIOS data from sysfs.
SMBIOS 2.5 present.

Handle 0x0003, DMI type 3, 13 bytes
Chassis Information
 Manufacturer: Oracle Corporation
 Type: Other
 Lock: Not Present
 Version: Not Specified
 Serial Number: Not Specified
 Asset Tag: Not Specified
 Boot-up State: Safe
 Power Supply State: Safe
 Thermal State: Safe
 Security Status: None

甲骨文公司再次是一个重要的信息,让我们相信我们处于一个虚拟化的环境中。

如果我们不想要很多其他信息,我们可以使用 dmidecode 的-s选项来微调我们的搜索。

运行此选项而不带参数会输出一个我们可以使用的潜在参数列表:

$ sudo dmidecode -s
dmidecode: option requires an argument -- 's'
String keyword expected
Valid string keywords are:
 bios-vendor
 bios-version
 bios-release-date
 system-manufacturer
 system-product-name
 system-version
 system-serial-number
 system-uuid
 baseboard-manufacturer
 baseboard-product-name
 baseboard-version
 baseboard-serial-number
 baseboard-asset-tag
 chassis-manufacturer
 chassis-type
 chassis-version
 chassis-serial-number
 chassis-asset-tag
 processor-family
 processor-manufacturer
 processor-version
 processor-frequency

在这里,我们可以立即看到bios-version,正如我们之前所知道的,它应该是VirtualBox

$ sudo dmidecode -s bios-version
VirtualBox

这些类型的短输出命令对于脚本编写非常有用,有时简洁是可取的。

dmidecode 通常默认安装,至少在 Ubuntu 和 CentOS 安装中是这样。

lshw

如果 dmidecode 不可用,您也可以使用lshw,这是一个列出硬件的命令。同样,它利用设备上的 DMI 表。

我们可以很快地使用lshw的格式选项来显示系统的总线信息:

$ sudo lshw -businfo
Bus info Device Class Description
=====================================================
 system VirtualBox
 bus VirtualBox
 memory 128KiB BIOS
 memory 1GiB System memory
cpu@0 processor Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz
pci@0000:00:00.0 bridge 440FX - 82441FX PMC [Natoma]
pci@0000:00:01.0 bridge 82371SB PIIX3 ISA [Natoma/Triton II]
pci@0000:00:01.1 scsi1 storage 82371AB/EB/MB PIIX4 IDE
scsi@1:0.0.0 /dev/cdrom disk CD-ROM
pci@0000:00:02.0 display VirtualBox Graphics Adapter
pci@0000:00:03.0 enp0s3 network 82540EM Gigabit Ethernet Controller
pci@0000:00:04.0 generic VirtualBox Guest Service
pci@0000:00:05.0 multimedia 82801AA AC'97 Audio Controller
pci@0000:00:06.0 bus KeyLargo/Intrepid USB
usb@1 usb1 bus OHCI PCI host controller
pci@0000:00:07.0 bridge 82371AB/EB/MB PIIX4 ACPI
pci@0000:00:0d.0 scsi2 storage 82801HM/HEM (ICH8M/ICH8M-E) SATA Controller [AHCI mode]
scsi@2:0.0.0 /dev/sda disk 8589MB VBOX HARDDISK
scsi@2:0.0.0,1 /dev/sda1 volume 1GiB Linux filesystem partition
scsi@2:0.0.0,2 /dev/sda2 volume 7167MiB Linux LVM Physical Volume partition
 input PnP device PNP0303
 input PnP device PNP0f03

这给了我们一些关于虚拟机的信息,比如系统、总线和显示条目。

我们还有一个易于阅读的类别可用的详细信息,这意味着我们可以直接查询这些信息,从这个例子开始,首先是disk

$ sudo lshw -c disk
 *-cdrom 
 description: DVD reader
 product: CD-ROM
 vendor: VBOX
 physical id: 0.0.0
 bus info: scsi@1:0.0.0
 logical name: /dev/cdrom
 logical name: /dev/sr0
 version: 1.0
 capabilities: removable audio dvd
 configuration: ansiversion=5 status=nodisc
 *-disk
 description: ATA Disk
 product: VBOX HARDDISK
 vendor: VirtualBox
 physical id: 0.0.0
 bus info: scsi@2:0.0.0
 logical name: /dev/sda
 version: 1.0
 serial: VB5cbf266c-3015878d
 size: 8GiB (8589MB)
 capabilities: partitioned partitioned:dos
 configuration: ansiversion=5 logicalsectorsize=512 sectorsize=512 signature=000b6a88

或者,如果我们认为这是太多的信息,我们可以查询系统类:

$ sudo lshw -c system
localhost.localdomain 
 description: Computer
 product: VirtualBox
 vendor: innotek GmbH
 version: 1.2
 serial: 0
 width: 64 bits
 capabilities: smbios-2.5 dmi-2.5 vsyscall32
 configuration: family=Virtual Machine uuid=BDC643B8-8D4D-4288-BDA4-A72F606CD0EA

快速 sudo 解释

在前面的配方中给出的各种命令中,我们反复使用了sudo。这是为了我们不必以root用户登录来执行各种受限制的操作。

sudo是'superuser do'的缩写,因为sudo过去只用于以"超级用户"身份运行命令,现在你可以用它以各种用户的身份运行命令。

通常,如果你尝试运行一个你没有权限成功完成的命令,你会收到一个错误提示:

$ less /etc/sudoers 
/etc/sudoers: Permission denied

在这里,我试图查看/etc/sudoers文件,这也恰好是决定用户sudo权限的文件。

sudo运行这个命令就是另一回事了。它会为我打开文件,将我放入less分页器。

在这个文件的底部,我们找到以下的代码块:

## Allows people in group wheel to run all commands
%wheel  ALL=(ALL)       ALL

这个代码块中的wheel部分是没有被注释的,上面的文本告诉我们这意味着什么。

因此,显而易见的下一个问题是,我是否在wheel组中?

术语wheel在古老的 UNIX 安装中有着悠久的历史。这些天,它可能被称为admin或其他。CentOS 通过使用wheel保持了经典。

幸运的是,这很容易检查 - 问题文件总是在同一个地方:/etc/group

在这里,我们将group文件的内容打印到屏幕上,并特别查找wheel

我们看到以下的布局:

group_name:password:GID:user_list

我们可以看到group_namewheelpassword是一个小写的x,这意味着正在使用影子密码,组 ID 是10,而这个组中唯一的用户就是我自己:

$ sudo cat /etc/group | grep wheel
wheel:x:10:adam

我们甚至可以用一个单词来做到这一点,那就是groups命令,它会打印出你当前用户所属的组:

$ groups
adam wheel

被授予使用sudo运行超级用户命令的能力并不是每个系统上的每个人的直接权利,这取决于个别公司和管理团队决定如何分配这种权力。

有些地方的运维人员都有sudo的权限,而有些地方只有一个人有这个权限。

使用 Vagrant 自动配置虚拟机

每次想要测试新东西或创建一个沙盒来工作时,都要重复安装新的虚拟机会很快变得乏味。

因此,各种管理员和开发人员想出了解决方案,使得配置虚拟机(或多个虚拟机)变得轻而易举。

如果我们花点时间考虑一下这种方法的优势,很容易就能突出自动化虚拟机配置的一些好处:

  • 它消除了手动输入答案到虚拟机窗口中所需的时间。

  • 它允许在开发环境中自动运行软件测试。

  • 它允许共享文本文件,作为构建虚拟机的配方,而不是在各个站点之间传输大型虚拟机镜像。这是一种基础设施即代码IaC)的形式。

Kickstart

自动部署盒子的一种方法是 kickstart 文件,它经常用于大型部署中自动回答安装程序向用户提出的问题。

如果你在 CentOS 虚拟机的/root/文件夹中查看,很有可能会找到一个名为anaconda-ks.cfg的文件,这实际上是你在安装机器时执行手动步骤的 kickstart 文件(anaconda 是安装程序的名称)。

这些文件被调整或从头开始编写,然后托管在 Web 服务器上,放在一个未配置的机器上,准备好被拾取。

Vagrant

在本地,kickstart 文件并不是很实用,而且使用起来也不太快。我们需要一些可以快速轻松设置的东西,但又非常强大。

输入Vagrant

Vagrant 由 Hashicorp 开发为开源软件,可用于自动配置 VM 甚至整个开发环境。

通常,您可能会在某个内部应用程序的存储库中找到一个Vagrantfile(核心 Vagrant...文件...)的名称。

开发人员将应用程序的存储库下载到他们的本地计算机,并使用 Vagrant 配置文件启动本地开发环境,然后他们可以使用该环境测试代码更改或功能添加,而不需要使用昂贵的开发环境。

Vagrant 适用于 macOS、Linux 和 Windows。

在我的 Ubuntu 主机上,我安装 Vagrant 如下:

$ sudo apt install vagrant

之后会有相当多的依赖项,总共使用了大约 200MB 的磁盘空间。

Ubuntu 的软件包相当新,所以我们得到了一个最新版本:

$ vagrant --version
Vagrant 2.0.2

我对文件存放的位置非常挑剔,所以我将在我的主目录中创建一个名为Vagrant的专用文件夹,用于处理我的 Vagrant VM:

$ ls
 Desktop     Downloads   Pictures   snap        Videos
 Documents   Music       Public     Templates  'VirtualBox VMs'
$ mkdir Vagrant
$ cd Vagrant/

接下来,我们将初始化一个新的Vagrantfile。以下命令将自动执行此操作:

$ vagrant init
$ ls
Vagrantfile

查看Vagrantfile,但暂时不要进行任何更改。您会看到许多选项都已列出,但默认情况下都已注释掉。这是一个很好的介绍,让您了解 Vagrant 的功能。

请注意,默认情况下,Vagrant 将尝试使用一个名为base的盒子,但也会提示您查看vagrantcloud.com/search以获取其他盒子:

 # Every Vagrant development environment requires a box. You can search for
 # boxes at https://vagrantcloud.com/search.
 config.vm.box = "base"

vagrantcloud上搜索 CentOS 会发现一个很好的默认盒子可供使用:app.vagrantup.com/centos/boxes/7

它还列出了可以在其下进行配置的提供者。VirtualBox 是其中之一,这意味着它将在我们的安装中工作。

我们需要修改我们的Vagrantfile指向这个盒子。从包含您的Vagrantfile的文件夹中运行以下命令:

$ sed -i 's#config.vm.box = "base"#config.vm.box = "centos/7"#g' Vagrantfile

我们刚刚使用了sed(一个常用的命令行文本编辑工具,可以在文件中或标准输出中编辑文本)和-i选项,以原地修改我们的Vagrantfile。现在打开文件将会显示base行已更改为指向centos/7

现在,我们可以使用另一个简单的命令配置我们的 VM:

$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
==> default: Box 'centos/7' could not be found. Attempting to find and install...
 default: Box Provider: virtualbox
 default: Box Version: >= 0
==> default: Loading metadata for box 'centos/7'
 default: URL: https://vagrantcloud.com/centos/7
==> default: Adding box 'centos/7' (v1804.02) for provider: virtualbox
 default: Downloading: https://vagrantcloud.com/centos/boxes/7/versions/1804.02/providers/virtualbox.box
==> default: Successfully added box 'centos/7' (v1804.02) for 'virtualbox'!
<SNIP>
 default: No guest additions were detected on the base box for this VM! Guest
 default: additions are required for forwarded ports, shared folders, host only
 default: networking, and more. If SSH fails on this machine, please install
 default: the guest additions and repackage the box to continue.
 default: 
 default: This is not an error message; everything may continue to work properly,
 default: in which case you may ignore this message.
==> default: Rsyncing folder: /home/adam/Vagrant/ => /vagrant

一切顺利的话,您的 VM 镜像将开始从vagrantcloud下载,并且您的虚拟机将在 VirtualBox 中启动。

我们甚至可以在 VirtualBox 的主窗口中看到我们的 VM:

在设置|网络和端口转发下查看,Vagrant 还自动设置了对 NAT 网络的访问,这与我们手动设置的方式非常相似。

我们还可以使用内置的 Vagrant 快捷方式连接到我们的新 VM:

$ vagrant ssh
Last login: Tue Aug  7 09:16:42 2018 from 10.0.2.2
[vagrant@localhost ~]$

这意味着我们已经通过四个命令配置和连接到了一个 VM,总结如下:

$ vagrant init
$ sed -i 's#config.vm.box = "base"#config.vm.box = "centos/7"#g' Vagrantfile
$ vagrant up
$ vagrant ssh
[vagrant@localhost ~]$

我们还可以销毁我们在同一个文件夹中创建的任何 VM,只需使用一个命令运行到我们的Vagrantfile

$ vagrant destroy

我首先写了关于使用 VirtualBox 手动设置 VM(并且拍摄了所有这些截图),因为习惯于在自动化繁琐的部分之前学习如何手动操作是很好的。这个规则也适用于大多数软件,因为即使花费更长的时间,了解底层工作原理会使以后的故障排除更容易。

轶事 - 尝试,尝试,再尝试

在你的职业生涯中,你会发现圣战的概念占主导地位,每一代新技术都有其辩护者和反对者。这在发行版之争中尤为明显,部落派系坚定地捍卫他们选择的操作系统。如果你发现自己需要为公司或项目选择要安装的发行版,考虑一下你在这里读到的一切,并在盲目接受一个人的意见之前自己做些调查。

这并不是说你应该成为一个部落人——我曾经安装过所有前面提到的发行版,其中第一个是 Ubuntu。

回到 2005 年,我了解到了一个叫做 Linux 的东西。

在那之前,我一直都在用 Mac,因为那是我爸爸决定的品牌。我还拼凑了一台 Windows 机器,专门用来玩暗黑破坏神,尽管我不能说我喜欢使用这个操作系统本身。

一切都改变了,当我在度假时看到一本计算机杂志,翻阅各种页面,直到我看到一篇关于 Linux 的文章,这立刻吸引了我的想象力。不同和古怪的东西吸引了我叛逆的态度,结果我最终刻录了一个叫做 Ubuntu 的东西到一张 CD 上(或者说是几张)。

当时,Canonical 会免费向你发送 Ubuntu 的 CD,但我很不耐烦,刻录光盘更快。

我在电脑上备份了我关心的一切,然后开始逐步完成我的第一次安装,一旦我弄清楚了如何从 CD 启动。据说一切都进行得很顺利,尽管我不得不偶尔跑到另一台电脑上(记住没有智能手机)查找某些选项的含义,但最终我安装了一个全新的桌面操作系统。

麻烦是从那时开始的。

我的无线网卡不工作,图形看起来很卡,我只运行了一个更新,然后重启,结果我不是进入了桌面,而是进入了一个命令行界面。

我以前从未见过命令行界面。

直到今天,我仍然不知道我是如何设法在那台电脑上安装了一个功能正常的操作系统的,我一直在与一个叫做NdisWrapper的程序作斗争,以使我的无线网卡工作,或者安装专有(尽管当时我不知道这个词)的图形驱动程序,但只要你升级了内核,它们就会崩溃(尽管当时我不知道发生了什么)。

我以某种方式艰难前行,很快对 Ubuntu 感到厌倦,当我发现了不同的发行版,并在接下来的几个月里每周尝试不同的桌面。我清楚地记得在 Ubuntu、Debian、Fedora、OpenSUSE 以及一个非常早期的尝试安装 Gentoo 之间切换,大约五分钟后我放弃了。

我经常上论坛,费力地将错误复制到谷歌上,试图找到其他遇到我所经历问题的人,经常发现一个帖子,帖子的作者不情愿地宣布他们已经解决了!,却没有提供他们使用的解决方案。

尽管当时对我来说很烦人,但这一切都是一次学习经历,我认为我对 Linux 和计算机的热爱可以追溯到我第一次安装 Ubuntu 的时候。在那之前,计算机只是游戏机而已。

很快,我开始使用 Linux Mint 绕过学校的防火墙,启动一个 Live USB 驱动器,并无视学校 IT 部门启用的所有微弱尝试阻止(出于某种原因,他们认为 Windows 是唯一存在的操作系统)。我仍然不太明白这是如何运作的,但重点是它确实运作了。

在玩魔兽世界的空隙中,Linux 是我多年来摆弄的东西,我一直关注最新的发行版并安装其他发行版进行尝试(经常“跳槽”)。我破坏了东西,修复了它们,对 Linux 感到愤怒,对计算机感到愤怒,但总的来说,我慢慢地进步了。

再往前走一小段时间,由于学校成绩普遍不好,我没有完成大学,也没有上大学。我几乎没有什么资格,但在计算机方面还是有一些天赋。我找到了一个持续几个月的课程,获得了一些微软的认证,但最终意味着我有了一个简陋的简历,可以开始向公司提交申请。

我接到了曼彻斯特一家托管提供商的电话,去参加了一次面试,见到了现在的首席技术官。面试很奇怪,我们讨论了拆解计算机、一点 Linux,还有很多《反恐精英》,结果他过去玩过很多。我离开时感到紧张,但对面试的进展感到相当好笑。

回来后,被召回参加面试,我非常惊讶地被提供了数据中心工程师的工作,虽然不是一个以 Linux 为重点的职位,但考虑到我的教育水平,这已经超出了我的期望。能够就业让我感到非常幸福,我永远感激那家公司和面试官给了我一个机会。

我想要传达的观点是,Linux 相当不错 - 即使我们中最没有学术素养的人也可以在这个领域有一个体面的职业,而且它是如此充满活力和不断发展,总是有新东西可以学习。在我的旅程中,我遇到了一些很棒的人,学到了一些很有趣的东西,其中很多我希望在这些页面中传递下去。

希望你会发现这本书的其余部分有益,无论你是 Linux 管理的新手,还是有经验的人只是在寻找你可能不知道的技巧和窍门。

第二章:使用 SSH 进行远程管理

本章将涵盖以下配方:

  • 使用 ssh-keygen 生成和使用密钥对

  • SSH 客户端参数和选项

  • 使用客户端端 SSH 配置文件

  • 修改服务器端 SSH 配置文件

  • 旋转主机密钥和更新known_hosts

  • 使用本地转发

  • 使用远程转发

  • ProxyJump 和堡垒主机

  • 使用 SSH 创建 SOCKS 代理

  • 理解和使用 SSH 代理

  • 在一台主机上运行多个 SSH 服务器

介绍

在第一章中,我们使用了一个命令连接到我们的虚拟机:

$ ssh adam@127.0.0.1 -p2222
adam@127.0.0.1's password: 
Last login: Mon Aug 6 17:04:31 2018 from gateway
[adam@localhost ~]$

在本章中,我们将扩展这一点,探讨使用 SSH 密钥对使连接更容易;介绍 SSH 的安全优势;对客户端和服务器端配置进行更改;设置端口转发和反向端口转发连接;学习 ProxyJump 和堡垒主机的设置,以及使用 SSH 设置临时代理;最后,我们将研究 SSH 代理并在我们的虚拟机上设置额外的 SSH 服务器。

本章假定您对 SSH 有基本的了解。

技术要求

正如在第一章中介绍的,我们将在本章和以后的所有工作中使用 Vagrant 和 VirtualBox。这使我们能够快速配置基础设施进行测试,并节省了您每次创建多个虚拟机的手动工作。

如果您真的不想使用 VirtualBox 或 Vagrant,那么您不必使用,我已经尽量使示例尽可能通用,但如果您使用它会更容易一些。

我已经准备了以下Vagrantfile供本章使用:

# -*- mode: ruby -*-
# vi: set ft=ruby :

$provisionScript = <<-SCRIPT
sed -i 's#PasswordAuthentication no#PasswordAuthentication yes#g' /etc/ssh/sshd_config
systemctl restart sshd
SCRIPT

Vagrant.configure("2") do |config|
 config.vm.provision "shell",
 inline: $provisionScript

 config.vm.define "centos1" do |centos1|
   centos1.vm.box = "centos/7"
   centos1.vm.network "private_network", ip: "192.168.33.10"
   centos1.vm.hostname = "centos1"
   centos1.vm.box_version = "1804.02"
 end

 config.vm.define "centos2" do |centos2|
   centos2.vm.box = "centos/7"
   centos2.vm.network "private_network", ip: "192.168.33.11"
   centos2.vm.hostname = "centos2"
   centos2.vm.box_version = "1804.02"
 end

 config.vm.define "centos3" do |centos3|
   centos3.vm.box = "centos/7"
   centos3.vm.network "private_network", ip: "192.168.33.12"
   centos3.vm.hostname = "centos3"
   centos3.vm.box_version = "1804.02"
 end
end

关于这个Vagrantfile有一些新的东西。我们在文件顶部包含了一个 provision 步骤,该步骤运行分配给变量的代码。在这种情况下,我们对默认的 CentOS 镜像的 SSH 配置进行了一些更改,以便我们的示例按照我们的期望工作。我们将所有三个虚拟机放在它们自己的私有网络上。

建议创建一个名为第二章的文件夹,并将此代码复制到名为Vagrantfile的文件中,或者如果您正在使用 GitHub 上的代码,则导航到正确的文件夹。

从包含您的Vagrantfile的文件夹中运行vagrant up应该配置两个虚拟机进行测试。

一旦配置完成,请确保您可以通过运行以下命令连接到第一个:

$ vagrant ssh centos1

Vagrant 非常适合测试,但不应在生产环境中用于部署机器。一些决定是为了方便使用(比如我们镜像中的默认vagrant用户),因此并不是安全部署的最佳实践。

使用 ssh-keygen 生成和使用密钥对

密码很棒,但也很糟糕。

大多数人使用弱密码,虽然我希望您不是其中之一,但是您团队中总有可能有人没有您的纪律,而是使用类似football99的密码连接到您共享的远程主机。

启用密码访问后,任何人都可能通过暴力破解的方式从任何国家连接到您的服务器,只要有足够的时间和足够的处理能力。

我说“可能”是因为只要您使用安全密码并且长度足够长,即使在太阳的力量下,密码也很难被猜到。在决定这些事情时,请参考您公司的安全政策,或者在编写自己的政策时了解最佳实践。

这就是密钥的用武之地。

SSH 密钥基于公钥加密的概念。它们由两部分组成:公共部分和私有部分,其中的公共部分可以放在服务器上,而私有部分则由您保管,可以放在您的笔记本电脑上,或者可能是一个安全的 USB 存储设备(本身已加密并受密码保护)。

尽管公共和私有一半的概念很明显,但我经常看到人们误解这个概念,分享他们的私有一半而不是公共一半。这通常会导致密钥被标记为受损,并要求相关人员生成一个新的密钥对,并在此期间简要讨论“私有”和“公共”的定义。

一旦您的密钥的公共一半在服务器上,您就可以使用密钥的本地私有一半进行身份验证,从而 SSH 到远程主机。

SSH 密钥甚至可以提供一定程度的便利,因为大多数操作系统都配备了某种类型的钥匙链,可以在用户登录时自动解锁,并且安全地存储了密钥的私有部分。然后,通过 SSH 连接到一台机器变得非常简单,您可以在不被提示的情况下安全连接!

我们将生成一个 SSH 密钥对,并使用该对在我们的机器之间进行 SSH。

准备工作

首先确保您已经配置了两个 VM,并在它们之间建立了一个私有网络。

您可以使用技术要求部分的Vagrantfile来完成此操作。

连接到您的第一台机器:

$ vagrant ssh centos1

使用ip a命令从第一章 介绍和环境设置检查centos1的 IP 地址是否正确配置。

我们期望它是192.168.33.10,在eth1设备下:

[vagrant@centos1 ~]$ ip a
<SNIP>
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
 link/ether 08:00:27:ac:f2:12 brd ff:ff:ff:ff:ff:ff
 inet 192.168.33.10/24 brd 192.168.33.255 scope global noprefixroute eth1
 valid_lft forever preferred_lft forever
 inet6 fe80::a00:27ff:feac:f212/64 scope link 
 valid_lft forever preferred_lft forever

您还可以使用hostname -I来获取框的 IP 地址,如下所示,但是您应该注意到您不会得到一个明显的接口标识:

$ hostname -I
10.0.2.15 192.168.33.10

检查您是否可以从centos1内部 ping 通centos2的 IP 地址。

我们将第二个 IP 设置为192.168.33.11

$ ping 192.168.33.11
PING 192.168.33.11 (192.168.33.11) 56(84) bytes of data.
64 bytes from 192.168.33.11: icmp_seq=1 ttl=64 time=1.17 ms
64 bytes from 192.168.33.11: icmp_seq=2 ttl=64 time=0.997 ms
64 bytes from 192.168.33.11: icmp_seq=3 ttl=64 time=1.18 ms

我们的 VM 之间有网络连接!

如果您无法在机器之间进行 ping,请首先检查 VirtualBox 中的网络设置,并使用vagrant ssh命令连接到每台机器,以检查分配的 IP 地址。

如何做...

我们将逐步介绍如何生成并复制密钥到远程主机,使用两种类型的密钥。

首先,我们将生成一个更传统的Rivest-Shamir-AdlemanRSA)密钥,然后我们将生成一个更新类型的密钥,即Ed25519密钥。

RSA 示例

首先,我们将生成我们的密钥,确认保存密钥的默认位置,并在提示时提供一个密码:

$ ssh-keygen -b 4096 -C "Example RSA Key"
Generating public/private rsa key pair.
Enter file in which to save the key (/home/vagrant/.ssh/id_rsa): 
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/vagrant/.ssh/id_rsa.
Your public key has been saved in /home/vagrant/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:hAUNhTqXtfnBOkXMuIpxkvtTkM6NYRYxRbT5QWSVbOk Example RSA Key
The key's randomart image is:
+---[RSA 4096]----+
|      =@*=+o.o   |
|      o++=+ =    |
|     o.=+*.o     |
|    * X.+.+.E    |
|     & *S+..     |
|    o = = .      |
|     . . .       |
|      o          |
|       .         |
+----[SHA256]-----+

前面代码中的随机艺术图主要是为了让人们可以通过视觉验证密钥。就我个人而言,我从来没有使用过它(除了在本章稍后的一点,)但您可能会用到。

接下来,我们将把新生成的 RSA 密钥复制到centos2,在提示时提供centos2的密码:

这些框上vagrant用户的默认密码是vagrant

$ ssh-copy-id 192.168.33.11
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/vagrant/.ssh/id_rsa.pub"
The authenticity of host '192.168.33.11 (192.168.33.11)' can't be established.
ECDSA key fingerprint is SHA256:LKhW+WOnW2nxKO/PY5UO/ny3GP6hIs3m/ui6uy+Sj2E.
ECDSA key fingerprint is MD5:d5:77:4f:38:88:13:e7:f0:27:01:e2:dc:17:66:ed:46.
Are you sure you want to continue connecting (yes/no)? yes
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
vagrant@192.168.33.11's password: 

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh '192.168.33.11'"
and check to make sure that only the key(s) you wanted were added.

最后,我们将检查我们是否可以通过刚刚生成的密钥访问centos2

在需要时,我们将输入生成密钥时设置的密码。

[vagrant@centos1 ~]$ ssh 192.168.33.11
Enter passphrase for key '/home/vagrant/.ssh/id_rsa': 
[vagrant@centos2 ~]$

Ed25519 示例

与 RSA 示例一样,我们将首先生成一个新的密钥,这次指定类型为'ed25519'。

Ed25519 密钥是基于椭圆曲线的,许多非常聪明的人认为它们比 RSA 提供更高级的安全性。这些密钥本身也要短得多(我们稍后会提到),这意味着如果您必须输入一个密钥,那么工作量要少得多。令人讨厌的是,您不能像 RSA 公钥那样使用 Ed25519 公钥的一半来加密文件,因此存在一些权衡,但这取决于您的需求。

我们将再次接受保存密钥的默认位置,并提供一个密码:

[vagrant@centos1 ~]$ ssh-keygen -t ed25519 -C "Example Ed25519 key"
Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/vagrant/.ssh/id_ed25519): 
/home/vagrant/.ssh/id_ed25519 already exists.
Overwrite (y/n)? y
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in /home/vagrant/.ssh/id_ed25519.
Your public key has been saved in /home/vagrant/.ssh/id_ed25519.pub.
The key fingerprint is:
SHA256:nQVR7ZVJMjph093KHB6qLg9Ve87PF4fNnFw8Y5X0kN4 Example Ed25519 key
The key's randomart image is:
+--[ED25519 256]--+
|          o*o+=+=|
|          ..+.B*=|
|           ooB Bo|
|         . +o.B+E|
|        S +.. +==|
|         ..  +.+=|
|        ..    o o|
|        ...    o.|
|         o.     +|
+----[SHA256]-----+

我们将复制我们的新密钥到centos2。请注意,我们还指定了要复制的文件为id_ed25519.pub

同样,这些框上的默认密码是vagrant

[vagrant@centos1 ~]$ ssh-copy-id -i .ssh/id_ed25519.pub 192.168.33.11
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: ".ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
vagrant@192.168.33.11's password: 

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh '192.168.33.11'"
and check to make sure that only the key(s) you wanted were added

如果您在之前的示例之后立即运行此示例,则可能会要求输入 RSA 密钥的密码短语,而不是密码本身。这没问题,这突显了首先尝试基于密钥的身份验证。如果是这种情况,请简单地提供 RSA 密钥的密码短语。

安装完成后,尝试使用私有部分的Ed25519密钥连接到centos2

[vagrant@centos1 ~]$ ssh 192.168.33.11 -i .ssh/id_ed25519
Enter passphrase for key '.ssh/id_ed25519': 
Last login: Wed Aug  8 10:06:33 2018 from 192.168.33.10
[vagrant@centos2 ~]$

它是如何工作的...

异步密钥和公钥加密的原则可能让人们难以理解。在大多数情况下,您不需要担心密钥生成的数学问题——您只需要知道您最终会得到两个密钥,一个公钥和一个私钥。

Dimble 是一个完全虚构的工程师,他认为在名为my stuff的存储库中将他的私有SSH 密钥存储在公共GitLab服务器上是一个安全风险,因为他从未拥有过字典,并且认为私有一词意味着“与世界分享”,实际上并非如此。他还禁用了私有密钥上的密码短语,因为他不喜欢在他和服务器之间多出一步。不要像 Dimble 一样——保护好您的私有密钥。

公钥和私钥文件

正如之前暗示的,我们在这里创建了两个文件,其中一个可以自由传递(公共部分),另一个我们在其他地方安全保管(私有部分)。

默认情况下,这些文件位于用户的主目录中,即隐藏的.ssh文件夹中:

[vagrant@centos1 ~]$ pwd
/home/vagrant
[vagrant@centos1 ~]$ ls -a
.  ..  .bash_history  .bash_logout  .bash_profile  .bashrc  .ssh
[vagrant@centos1 ~]$ ls .ssh
authorized_keys  id_ed25519  id_ed25519.pub  id_rsa  id_rsa.pub  known_host

我们的密钥的公共部分以.pub结尾,而私有部分没有文件扩展名。

让我们来看看这四个文件:

[vagrant@centos1 ~]$ cat .ssh/id_rsa
-----BEGIN RSA PRIVATE KEY-----
Proc-Type: 4,ENCRYPTED
DEK-Info: AES-128-CBC,9AFF0BD949B955DA3595262BB18E5BF7

n1K6zUfhIynq9dwRMAGlMuTU/7Ht3KgBuelsWy3mxJM+NxprFkhAV2cyEVhnJI+5
xgDkx7+6PcGVv/oQAH3pSICefZSJvHvnFLO+M7HKkcmdz9IYXlQC1gkeZwhS6708
<SNIP>
wTXVajpn0anc3TWDw78sZkLmoP5MEs14gJvyegmyLd8qAGvSmfXYNFgYh49hnX9E
vdAmtTJPqglcw0F1JVCOEevIWA/WoIkkTAgLuKvka5ZepKKnScwnRiAhKTVXCN3W
-----END RSA PRIVATE KEY-----

我们的 RSA 密钥的私有部分是一个敏感的文件,当我们生成它时,我们将其设为4096位,因此这个文件会非常长(因此我剪辑了一部分)。

我们的 RSA 密钥的公共部分是放在远程机器上的文件。尽管受到我们指定的位长度的影响,但它远没有私有部分那么长。

[vagrant@centos1 ~]$ cat .ssh/id_rsa.pub 
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCwR6+SAohzU9f1SAha634StlJaBGIZ+k5Rb6T6L4VqxHIfRwCV+uciXbkTg8+lxiP8whGYEiDxfPveqW1xf87JYlWTT3ZT3gd3pfxY1+IgRB7j5Ttd2RBCMeMYB9VJWLqib6K9oeHJyGzM39aJqE2AzxKxc+rXeXT16RlFxs7nDZwS9xV7Dai9LB/Jez0pT8pLFVD/QRsGw0uMjMMSjmKqxPrDpHzZ3OUymB5AdyVfts4JTZINSrWdejPR8G93pzH4S8ZYijhgpOnSuoyGhMnwAjwOJyNkkFOT1rKCuzpW33hr2c1pJSBPZTAx2/ZvB1He2/UweBF2VeQpSruQB7tXkQMeXSQBpe+/zMqOLD82vake3M8mqNpFJoVG3afr9RcCXtqn7cF3fDEqj7nNk0Em6/9akO2/tK5KInWhyOjKdV41ntB6IVPGJWOUBmnvf9HVpOMa8rxeb3KpBqnn6z70rjMTKqHmAQ5BeCuVSezTl4xAUP940PbkHSm0mDeWYMi2AgbofKDGBmH/GGUn3QeahhiLTXGzbIHszbXJdJ5dn30oWAPovW/gc0CeeHgUV7IwJ6wxVIz8jYKpjtDtIPYDs+RJMrWo8qPnhHWxA6HVp42eUylh7eazPUzitfZ2SBQHe3ShbBHTh2wHcLcRoVgSMrMJmfQ7Ibad5ZiWepobJw== Example RSA Key

请注意,我们在末尾还有一个注释,示例 RSA 密钥,这是我们在生成时指定的。当没有显式传递注释时,这通常是生成它的用户和机器主机名。

[vagrant@centos1 ~]$ cat .ssh/id_ed25519
-----BEGIN OPENSSH PRIVATE KEY-----
b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jYmMAAAAGYmNyeXB0AAAAGAAAABCV2EFqnw
9/2J52LIVBzp50AAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIEGnqP8zTx50SwjP
+Fe26RdDx2W3/TQ+0ET8ylxfFB+aAAAAoJUzLk7IAaszO2npeAJIgfYmsqWCcgTM+EfF15
3A1iw4PruO+q8b3BxAjFZGK0tjFTSm3rkKtM9+JYTxOI+CSnEyqPnjnCjPODa7aF/X8GBt
RNkSKBlM7aROwpon0Z8UXH+Js8uyNOsKto+DS+BfVSKvshkQ6bNF/5DlU0fQcnRaYnVdyl
mIJUaPLdl/vKLwF+S4OyU87n8rac0ezjfAOhk=
-----END OPENSSH PRIVATE KEY-----

然后,我们有我们的私有(敏感的)Ed25519密钥。请注意,这个文件比其 RSA 对应文件要短得多,这是因为Ed25519密钥具有固定长度(这也意味着在生成时忽略了-b标志)。

[vagrant@centos1 ~]$ cat .ssh/id_ed25519.pub 
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEGnqP8zTx50SwjP+Fe26RdDx2W3/TQ+0ET8ylxfFB+a Example Ed25519 key

在这里,我们有我们的公共Ed25519文件,它非常短,您可以想象将其写在一张纸上,然后递给您的同事复制到服务器上(尽管他们真的不会感谢您,并且很可能不会很快为您泡一杯茶)。

我们还有我们的注释,示例 Ed25519 密钥

显然,打印我刚刚生成的密钥的私有部分与我关于传递私有密钥的说法相矛盾,尽管这是为了文档目的,我将在完成后销毁这些虚拟机,所以我觉得在这里添加它们是很重要的。请不要使用这些密钥。

authorized_keys 文件

当您连接到远程主机时,SSH 会验证您提供的密钥 ID 是否在authorized_keys列表中。

在我们的示例中,我们使用ssh-copy-id命令将我们的密钥放在远程服务器上。实际上,这是将其放在您要连接的主目录用户的特定文件中。

在我们的centos2主机上,我们可以在用户的主目录下的.ssh文件夹中找到这个文件:

[vagrant@centos2 ~]$ pwd
/home/vagrant
[vagrant@centos2 ~]$ ls .ssh/
authorized_keys

查看此文件的内容会显示如下内容:

[vagrant@centos2 ~]$ cat .ssh/authorized_keys 
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNkm9JCaRa/5gunzDZ8xO2/xwRvUx03pITH6f4aYziY/j+7o39XnmNyLRVpvh16u9W75ANJeFpBD7lkevluvaFVRQnZGAhuIdGqLHBlGDnVzkzcQGUFc/fcAc9rDAFGa0h7+BF18P0jpOMXfHQu8+7+cBjJ6cW+ztKerG2ali/JLtSHFirXaVTkOKYkwYVfK7z7nmdMsSzgEOsfg5XrylI+ufhGdgWCKtweHsBeAVWjBBbvNaIwgdRVpB1YmLkLgLN7NxRs53OuejwArLS6tvNS+ZBDiSX+was9gErrhGhZ1mdiOMbd3/oTfFEcOiRNOv/+7Tk4P8fJbnO1dzM8Gid vagrant
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCwR6+SAohzU9f1SAha634StlJaBGIZ+k5Rb6T6L4VqxHIfRwCV+uciXbkTg8+lxiP8whGYEiDxfPveqW1xf87JYlWTT3ZT3gd3pfxY1+IgRB7j5Ttd2RBCMeMYB9VJWLqib6K9oeHJyGzM39aJqE2AzxKxc+rXeXT16RlFxs7nDZwS9xV7Dai9LB/Jez0pT8pLFVD/QRsGw0uMjMMSjmKqxPrDpHzZ3OUymB5AdyVfts4JTZINSrWdejPR8G93pzH4S8ZYijhgpOnSuoyGhMnwAjwOJyNkkFOT1rKCuzpW33hr2c1pJSBPZTAx2/ZvB1He2/UweBF2VeQpSruQB7tXkQMeXSQBpe+/zMqOLD82vake3M8mqNpFJoVG3afr9RcCXtqn7cF3fDEqj7nNk0Em6/9akO2/tK5KInWhyOjKdV41ntB6IVPGJWOUBmnvf9HVpOMa8rxeb3KpBqnn6z70rjMTKqHmAQ5BeCuVSezTl4xAUP940PbkHSm0mDeWYMi2AgbofKDGBmH/GGUn3QeahhiLTXGzbIHszbXJdJ5dn30oWAPovW/gc0CeeHgUV7IwJ6wxVIz8jYKpjtDtIPYDs+RJMrWo8qPnhHWxA6HVp42eUylh7eazPUzitfZ2SBQHe3ShbBHTh2wHcLcRoVgSMrMJmfQ7Ibad5ZiWepobJw== Example RSA Key
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEGnqP8zTx50SwjP+Fe26RdDx2W3/TQ+0ET8ylxfFB+a Example Ed25519 key

在这里,我们可以看到三个密钥,分布在三行上。

第一个密钥如下:

[vagrant@centos2 ~]$ cat .ssh/authorized_keys | head -n1
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDNkm9JCaRa/5gunzDZ8xO2/xwRvUx03pITH6f4aYziY/j+7o39XnmNyLRVpvh16u9W75ANJeFpBD7lkevluvaFVRQnZGAhuIdGqLHBlGDnVzkzcQGUFc/fcAc9rDAFGa0h7+BF18P0jpOMXfHQu8+7+cBjJ6cW+ztKerG2ali/JLtSHFirXaVTkOKYkwYVfK7z7nmdMsSzgEOsfg5XrylI+ufhGdgWCKtweHsBeAVWjBBbvNaIwgdRVpB1YmLkLgLN7NxRs53OuejwArLS6tvNS+ZBDiSX+was9gErrhGhZ1mdiOMbd3/oTfFEcOiRNOv/+7Tk4P8fJbnO1dzM8Gid vagrant

这是 Vagrant 用来连接虚拟机的密钥。这不是我们创建的密钥。

第二个密钥如下:

[vagrant@centos2 ~]$ cat .ssh/authorized_keys | head -n2 | tail -n1
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCwR6+SAohzU9f1SAha634StlJaBGIZ+k5Rb6T6L4VqxHIfRwCV+uciXbkTg8+lxiP8whGYEiDxfPveqW1xf87JYlWTT3ZT3gd3pfxY1+IgRB7j5Ttd2RBCMeMYB9VJWLqib6K9oeHJyGzM39aJqE2AzxKxc+rXeXT16RlFxs7nDZwS9xV7Dai9LB/Jez0pT8pLFVD/QRsGw0uMjMMSjmKqxPrDpHzZ3OUymB5AdyVfts4JTZINSrWdejPR8G93pzH4S8ZYijhgpOnSuoyGhMnwAjwOJyNkkFOT1rKCuzpW33hr2c1pJSBPZTAx2/ZvB1He2/UweBF2VeQpSruQB7tXkQMeXSQBpe+/zMqOLD82vake3M8mqNpFJoVG3afr9RcCXtqn7cF3fDEqj7nNk0Em6/9akO2/tK5KInWhyOjKdV41ntB6IVPGJWOUBmnvf9HVpOMa8rxeb3KpBqnn6z70rjMTKqHmAQ5BeCuVSezTl4xAUP940PbkHSm0mDeWYMi2AgbofKDGBmH/GGUn3QeahhiLTXGzbIHszbXJdJ5dn30oWAPovW/gc0CeeHgUV7IwJ6wxVIz8jYKpjtDtIPYDs+RJMrWo8qPnhHWxA6HVp42eUylh7eazPUzitfZ2SBQHe3ShbBHTh2wHcLcRoVgSMrMJmfQ7Ibad5ZiWepobJw== Example RSA Key

这是我们生成的 RSA 密钥。请注意,由于我们指定了自定义的4096位长度,它比 Vagrant 默认值要长。

我们的第三个密钥如下:

[vagrant@centos2 ~]$ cat .ssh/authorized_keys | tail -n1
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEGnqP8zTx50SwjP+Fe26RdDx2W3/TQ+0ET8ylxfFB+a Example Ed25519 key

这是我们的Ed25519密钥。

如果你愿意的话,你可以手动将公钥复制到你要连接的主机上的authorized_keys文件中。我们使用的ssh-copy-id命令只是一种方便的方式,可以省略一些额外的步骤。

还有更多...

SSH 对其文件的权限非常敏感。

你不希望你的私钥可以被任何可能在你的系统上的随机用户读取,因此,如果它认为你设置了错误的权限,普通的 SSH 就不会起作用。

一般来说,如果你刚刚生成了你的密钥,这不会是一个问题,但是如果你以后将它们移动到其他计算机上,你可能会发现你稍微破坏了权限。

一个很好的经验法则是假设设置是锁定的:

[vagrant@centos1 ~]$ ls -lha .ssh/
total 28K
drwx------. 2 vagrant vagrant  134 Aug  8 14:05 .
drwx------. 3 vagrant vagrant   95 Aug  8 10:29 ..
-rw-------. 1 vagrant vagrant  389 Aug  7 16:40 authorized_keys
-rw-------. 1 vagrant vagrant  464 Aug  8 10:04 id_ed25519
-rw-r--r--. 1 vagrant vagrant  101 Aug  8 10:04 id_ed25519.pub
-rw-------. 1 vagrant vagrant 3.3K Aug  8 11:15 id_rsa
-rw-r--r--. 1 vagrant vagrant  741 Aug  7 16:43 id_rsa.pub
-rw-r--r--. 1 vagrant vagrant  535 Aug  8 11:39 known_hosts

在上面的命令中,我们可以看到密钥的公共部分和私有部分(id_rsa 密钥和 id_ed25519 密钥)具有不同的值。

密钥的公共部分(*.pub)的值为644(读/写,读,读):

-rw-r--r--.

密钥的私有部分的值为 600(读/写,无,无):

-rw-------.

是否需要密码

虽然你可以生成一个没有密码的密钥,并且有一些有效的用例(例如,在自动部署的情况下),但是最好的做法是生成一个带有密码的密钥。

这意味着如果你的密钥没有在你的钥匙链中解锁(当你登录到你的机器时,它本身可能已经解锁),你将被提示输入密码来解锁密钥。你可能认为这很麻烦,但从安全的角度来看(多层安全...这不是一个很好的类比,除非安全让你哭泣。)如果你丢失了你的私钥,那么捡起它的恶意人士将无法使用它来访问你的东西。

如果你丢失了私钥,或者把它留在了公共汽车上的 U 盘上,你应该立即通过撤销安装了公钥的任何位置来旋转你的密钥,并生成一个新的密钥对来使用。

附加标志

当我们生成我们的密钥时,我们还添加了一些标志。

与任何软件一样,查看你运行的命令的手册页面可能会提供一些额外的细节,有时可能会有点压倒性:

$ man ssh-keygen

为了节省一点麻烦,我将重点介绍一些可能对你有兴趣的选项,首先是-b

-b bits

我们使用-b标志在生成 RSA 密钥时指定了大量的位数。最小值是1024,默认值是2048。你的公司可能对 RSA 密钥的长度有要求。

接下来,我们有评论标志:

-C comment

我们用这个来为我们的密钥添加一些描述。如果你为不同的事情使用不同的密钥(这是我的GitLab密钥,这是我的个人服务器密钥,这是我的公司服务器密钥,等等),这可能会很有用。

如果你需要多个密钥,你可能希望在生成命令中传递你的新密钥的名称(而不是在提示时输入):

-f filename

我们还有-l来打印密钥的指纹,或者如果你愿意的话,打印 ASCII 艺术。这对于验证密钥对非常有用:

-l (or -lv for a pretty picture)

如果你想要更改私钥的密码,但又不想生成新的密钥,你可以使用-p选项:

-p

要指定要生成的密钥类型,可以使用-t选项:

-t dsa | ecdsa | ed25519 | rsa

在选择要生成的密钥类型时,请考虑你的要求。RSA 通常是最兼容的,但你的公司可能有其他政策,或者你可能有个人偏好。

我遇到过两种情况,Ed25519密钥无法使用——一种是需要 RSA 加密文件的内部脚本,另一种是当时的 OpenStack。

最后,还有老式的-v,自从早期就提供详细的输出:

-v

这可以多次传递,也就是说,-vvv也是有效的,每个v都会增加调试级别。

另请参阅

本节故意不涉及 SSH 密钥交换的细节或密钥的不同类型(例外是我们示例中使用的两种类型)。有关 SSH 的优秀书籍可以提供丰富的信息,OpenSSH 开发人员自己也在不断改进软件。OpenSSH 只是 SSH 的一个实现,但它是迄今为止最受欢迎的。它是我使用的每个 Linux 发行版的默认值,在 macOS 上使用,并且是 BSD(特别是 OpenBSD,在那里开发它)的标准。

SSH 客户端参数和选项

正如我们已经讨论过的那样,SSH 是一款功能强大的软件,虽然它可以以非常简单的方式用于启用对服务器的访问,但它也非常灵活。

在本节中,我们将看一下在可能具有不同要求的环境中与 SSH 一起使用的常见标志。

我们将使用与之前相同的 Vagrant 框。

准备就绪

与前一节一样,确认您的两个 Vagrant 框都已启用,并使用vagrant命令连接到第一个:

$ vagrant ssh centos1

如何做到这一点...

我们首先要了解 SSH 的基础知识。

使用主机名而不是 IP 的 SSH

到目前为止,我们一直在使用 IP 地址连接到远程主机。

SSH 还能够连接到主机名。

首先,我们必须创建一个快速的主机条目,以便我们可以将我们的名称解析为 IP 地址:

[vagrant@centos1 ~]$ echo "192.168.33.11 centos2" | sudo tee -a /etc/hosts

上面的代码是使远程主机可解析为名称的快速方法。不能保证它会在某些系统上保持,特别是在第三方控制hosts文件的系统上。在实际情况下,您很可能会有某种 DNS 设置,使连接到主机名更容易。

现在我们应该能够使用主机名进行 SSH:

[vagrant@centos1 ~]$ ssh centos2
The authenticity of host 'centos2 (192.168.33.11)' can't be established.
ECDSA key fingerprint is SHA256:LKhW+WOnW2nxKO/PY5UO/ny3GP6hIs3m/ui6uy+Sj2E.
ECDSA key fingerprint is MD5:d5:77:4f:38:88:13:e7:f0:27:01:e2:dc:17:66:ed:46.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added 'centos2' (ECDSA) to the list of known hosts.
Enter passphrase for key '/home/vagrant/.ssh/id_rsa': 
Last login: Wed Aug 8 11:28:59 2018 from fe80::a00:27ff:fe2a:1652%eth1
[vagrant@centos2 ~]$

请注意,我们再次不得不接受我们要连接的主机的指纹。

SSH 到不同的用户

如果您要连接的用户与您在本地使用的用户不同(在我们的示例中,它总是vagrantvagrant),那么您可以在命令行上手动指定用户名。

这样做的第一种方法是使用以下语法:

[vagrant@centos1 ~]$ ssh vagrant@centos2

第二种方法是使用一个标志:

[vagrant@centos1 ~]$ ssh centos2 -l vagrant

SSH 到不同的端口

如果您要连接的 SSH 服务器正在侦听不同的端口(这是相当常见的),那么您可能需要指定相关端口。

默认值是22,但是如果出于某种原因更改了这个值,您也可以指定新端口,例如2020

[vagrant@centos1 ~]$ ssh centos2 -p2020

请注意,此示例现在无法正常工作,因为我们尚未更改服务器正在侦听的端口。

SSH 到 IPv6 地址

IPv6 地址看起来比它们实际上更为严峻,建议您尽早熟悉它们(即使人们已经预测了 IPv6 的主导地位已经超过十年了)。

对于此示例,我们将查找centos2的 IPv6 地址并连接到该地址。

首先,连接到centos2并运行ip a命令:

[vagrant@centos2 ~]$ ip a
<SNIP>
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:56:c5:a7 brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.11/24 brd 192.168.33.255 scope global noprefixroute eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe56:c5a7/64 scope link 
       valid_lft forever preferred_lft forever

我在上面的代码中突出显示了 IPv6 地址。

回到centos1,让我们使用 IPv6 连接:

[vagrant@centos1 ~]$ ssh fe80::a00:27ff:fe56:c5a7%eth1
Enter passphrase for key '/home/vagrant/.ssh/id_rsa': 
Last login: Wed Aug  8 11:44:34 2018 from 192.168.33.10
[vagrant@centos2 ~]$ 

请注意,我们不得不在命令的末尾指定网络接口。这只在链路本地地址的情况下才是必要的,对于全局 IPv6 地址则不必要。

在 IPv6 世界中与链路本地地址进行比较的是 IPv4 世界中的子网,也就是说,链路本地设备是可以通过它们的链路本地地址在本地网络上看到彼此的设备(这些地址本身是基于诸如地址所在接口的 MAC 地址等因素生成的)。它们应该始终具有链路本地前缀(FE80::/10)。

在运行命令之前进行 SSH

虽然您大多数时候会使用 SSH 连接到远程框,但也可以在远程主机上运行命令,而无需在那里逗留。

在这里,我们正在运行一个命令来打印远程主机上的主机名文件,同时保持在centos1上:

[vagrant@centos1 ~]$ ssh 192.168.33.11 "cat /etc/hostname"
Enter passphrase for key '/home/vagrant/.ssh/id_rsa': 
centos2
[vagrant@centos1 ~]$ 

这对于自动化软件或您想在本地运行但与远程机器交互的脚本特别有用。

SSH 和 X11 转发

现在一般不再使用,但在某些特定情况下仍然有用,X11转发是在远程主机上运行程序,并在本地机器上显示该程序的行为。

您可以使用以下命令设置您的会话:

[vagrant@centos1 ~]$ ssh centos2 -X

使用X11转发存在安全隐患。请查阅您发行版的手册页面,了解相关信息,因为默认行为可能因发行版而异。

目前,这只有在X窗口管理器设置中才可能,并且更现代的 Wayland 显示服务器协议没有类似的功能,部分原因是希望保持简单。

工作原理...

SSH 是一个庞大且功能丰富的程序。当您使用标志来操纵它的行为时,您正在修改默认行为以符合您自己的目的。

与任何命令一样,它们可能很简单:

$ ssh 192.168.33.11

但它们也可能很复杂:

$ ssh -Y -D9999 -J buser@BASTION:22 -L 8888:127.0.0.1:80 myself@centos2 -p4433

作为练习,使用 SSH 手册页面,如果需要,看看您能否弄清楚这个命令将实现什么目的。

更多信息...

SSH 转义字符是一个重要的额外元素需要注意。

偶尔,您可能连接到一个系统,然后连接超时,导致会话被锁定。

这通常表现为一个不眨眼且无响应的终端。通常无法按下Ctrl + D退出登录,也无法输入。

您应该按下以下按键:

~. 

虽然这个键组合官方上标为~.,但实际上需要先按下Enter键(即换行),所以它经常被写成\n~

这个提示是由一个敏锐的技术编辑提供的!

这是一个波浪符号(在键盘上找到它,通常使用 Shift键),后面跟着一个点。

您的会话应该立即断开。

查看 SSH 手册页面以获取更多转义字符。

另请参阅

与前一节一样,SSH 选项比我在本章节中列出的要多得多,而且我们还没有涵盖一些在本章节其余部分有自己章节的选项,但在这里我们不会使用很多。

在一个无聊的星期二看一下 SSH 的手册页面。我看过了。

使用客户端 SSH 配置文件

虽然能够使用命令行参数来操作 SSH 很好,但也很好不必费心。

如果您每天都在一个系统上工作,将您的典型参数配置到永久基础上可能会有益。这就是客户端 SSH 配置文件的用武之地。

在我们的示例框中,默认的ssh_config文件位于/etc/ssh/目录中。如果您愿意,可以打开此文件查看,但暂时不要进行任何更改。

准备工作

与前一节一样,请确认您的两个 Vagrant 框已启用,并使用vagrant命令连接到第一个框:

$ vagrant ssh centos1

为了为我们的用户配置不同的选项,我们将在我们的主目录中创建一个 SSH 配置文件。

这与我们大多数 SSH 文件放置在同一个位置,~/.ssh/

每当您看到一个~字符时,把它想象成我的主目录。扩展开来,这个位置是/home/vagrant/.ssh/

创建文件,锁定权限,并在你选择的编辑器中打开它——我将使用vi

一定要称它为config

[vagrant@centos1 ~]$ touch ~/.ssh/config
[vagrant@centos1 ~]$ chmod 600 ~/.ssh/config
[vagrant@centos1 ~]$ vi ~/.ssh/config

操作方法...

在你的config文件中,创建四个块的开头。

一个应该是通配符块(使用*),另一个应该是CentOS2名称的变体(注意大写):

Host * !CentOS2-V6
 IdentityFile ~/.ssh/id_ed25519
 Port 22

Host CentOS2-V4
 Hostname 192.168.33.11
 User vagrant

Host CentOS2-V6
 Hostname fe80::a00:27ff:fe56:c5a7%%eth1
 IdentityFile ~/.ssh/id_rsa
 Port 22
 User vagrant

Host CentOS2-Hostname
 Hostname centos2
 User vagrant

请注意,在 V6 条目中,我们实际上使用了两个百分号,而不是我们在命令行上使用的单个百分号。这是为了避免 SSH 误解我们的意思,并尝试用%e值读取该条目。

在这些块中,我们根据之前在命令行上所做的事情设置了一些基本选项。

有了这些设置,我们可以保存并退出我们的配置文件,并尝试连接到我们指定的主机。

首先,我们将连接到我们的另一个 VM 的 IPv4 地址:

[vagrant@centos1 ~]$ ssh CentOS2-V4 
Enter passphrase for key '/home/vagrant/.ssh/id_ed25519': 
Last login: Wed Aug  8 13:31:41 2018 from fe80::a00:27ff:fe2a:1652%eth1
[vagrant@centos2 ~]$ 

接下来,我们将使用我们的 IPv6 地址:

[vagrant@centos1 ~]$ ssh CentOS2-V6
Enter passphrase for key '/home/vagrant/.ssh/id_rsa': 
Last login: Wed Aug  8 13:34:26 2018 from 192.168.33.10
[vagrant@centos2 ~]$ 

最后,我们将解析主机的主机名:

[vagrant@centos1 ~]$ ssh CentOS2-Hostname 
Enter passphrase for key '/home/vagrant/.ssh/id_ed25519': 
Last login: Wed Aug  8 13:34:04 2018 from fe80::a00:27ff:fe2a:1652%eth1
[vagrant@centos2 ~]$ 

大多数系统还将自动完成 SSH 配置文件中的条目。通过键入ssh C并连续按Tab三次来自行尝试。

它是如何工作的...

从通配符主机条目(Host *)开始,这是一个全局条目。此块中的设置将适用于所有主机(除了CentOS2-V6,我们很快就会谈到):

Host * !CentOS2-V6
 IdentityFile ~/.ssh/id_ed25519
 Port 22

在这里,我们说这个文件中的每个主机都将使用我们的Ed25519密钥进行连接,我们将始终在端口22上进行连接。这个块应该用于一般的全局设置。如果您愿意,也可以完全省略它:

Host CentOS2-V4
 Hostname 192.168.33.11
 User vagrant

在我们的第一个特定主机块中,我们称之为CentOS2-V4,我们指定了主机的 IPv4 地址和要使用的用户。

以详细模式连接到这个条目看起来像这样:

[vagrant@centos1 ~]$ ssh -v CentOS2-V4 
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017
debug1: Reading configuration data /home/vagrant/.ssh/config
debug1: /home/vagrant/.ssh/config line 1: Applying options for *
debug1: /home/vagrant/.ssh/config line 5: Applying options for CentOS2-V4
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 58: Applying options for *
debug1: Connecting to 192.168.33.11 [192.168.33.11] port 22.
debug1: Connection established.
debug1: identity file /home/vagrant/.ssh/id_ed25519 type 4
<SNIP>
debug1: rekey after 134217728 blocks
debug1: SSH2_MSG_NEWKEYS sent
debug1: expecting SSH2_MSG_NEWKEYS
debug1: SSH2_MSG_NEWKEYS received
debug1: rekey after 134217728 blocks
debug1: SSH2_MSG_EXT_INFO received
debug1: kex_input_ext_info: server-sig-algs=<rsa-sha2-256,rsa-sha2-512>
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey,gssapi-keyex,gssapi-with-mic,password
debug1: Next authentication method: gssapi-keyex
debug1: No valid Key exchange context
debug1: Next authentication method: gssapi-with-mic
debug1: Unspecified GSS failure.  Minor code may provide more information
No Kerberos credentials available (default cache: KEYRING:persistent:1000)

debug1: Unspecified GSS failure.  Minor code may provide more information
No Kerberos credentials available (default cache: KEYRING:persistent:1000)

debug1: Next authentication method: publickey
debug1: Offering ED25519 public key: /home/vagrant/.ssh/id_ed25519
debug1: Server accepts key: pkalg ssh-ed25519 blen 51
Enter passphrase for key '/home/vagrant/.ssh/id_ed25519': 
debug1: Authentication succeeded (publickey).
Authenticated to 192.168.33.11 ([192.168.33.11]:22).
debug1: channel 0: new [client-session]
debug1: Requesting no-more-sessions@openssh.com
debug1: Entering interactive session.
debug1: pledge: network
debug1: client_input_global_request: rtype hostkeys-00@openssh.com want_reply 0
debug1: Sending environment.
debug1: Sending env LANG = en_GB.UTF-8
Last login: Wed Aug  8 13:46:27 2018 from 192.168.33.10

在这一堆噪音中,我们可以看到一些关键的东西,为了您的方便而加粗。

首先,我们可以看到 SSH 从我们的配置文件中开始读取配置数据的地方。它应用通配符条目的设置,然后是特定主机的设置。

稍后,我们可以看到在主机通配符块中指定的Ed25519密钥的提示。

最后,我们可以看到我们的会话已经经过身份验证,连接到了192.168.33.11(或 IPv4 地址)。

如果我们现在看一下CentOS-V6块,我们开始看到不同之处:

Host CentOS2-V6
 Hostname fe80::a00:27ff:fe56:c5a7%%eth1
 IdentityFile ~/.ssh/id_rsa
 Port 22
 User vagrant

再次注意双百分号。

首先,您会注意到我们已经指定了端口和不同的IdentityFile条目。这是因为Host *块不适用于CentOS2-V6,如下所示:

Host * !CentOS2-V6

这意味着通配符块中的任何设置都不会应用于CentOS2-V6

如果我们以详细模式连接到我们的主机,我们会看到以下内容:

[vagrant@centos1 ~]$ ssh -v CentOS2-V6
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017
debug1: Reading configuration data /home/vagrant/.ssh/config
debug1: /home/vagrant/.ssh/config line 1: Skipping Host block because of negated match for CentOS2-V6
debug1: /home/vagrant/.ssh/config line 9: Applying options for CentOS2-V6
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 58: Applying options for *
debug1: Connecting to fe80::a00:27ff:fe56:c5a7%eth1 [fe80::a00:27ff:fe56:c5a7%eth1] port 22.
debug1: Connection established.
debug1: identity file /home/vagrant/.ssh/id_rsa type 1
debug1: key_load_public: No such file or directory
<SNIP>
debug1: Next authentication method: publickey
debug1: Offering RSA public key: /home/vagrant/.ssh/id_rsa
debug1: Server accepts key: pkalg rsa-sha2-512 blen 535
Enter passphrase for key '/home/vagrant/.ssh/id_rsa': 
debug1: Authentication succeeded (publickey).
Authenticated to fe80::a00:27ff:fe56:c5a7%eth1 ([fe80::a00:27ff:fe56:c5a7%eth1]:22).
debug1: channel 0: new [client-session]
debug1: Requesting no-more-sessions@openssh.com
debug1: Entering interactive session.
debug1: pledge: network
debug1: client_input_global_request: rtype hostkeys-00@openssh.com want_reply 0
debug1: Sending environment.
debug1: Sending env LANG = en_GB.UTF-8
Last login: Wed Aug  8 13:50:39 2018 from fe80::a00:27ff:fe2a:1652%eth1

具体不同的是关于匹配配置的行,这次通知我们通配符块不会应用,因为对CentOS2-V6的否定匹配。

我们还可以看到这次使用的是id_rsa,而且我们已经明确连接到了主机的 IPv6 地址。

最后,让我们看看CentOS2-Hostname

[vagrant@centos1 ~]$ ssh -v CentOS2-Hostname 
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017
debug1: Reading configuration data /home/vagrant/.ssh/config
debug1: /home/vagrant/.ssh/config line 1: Applying options for *
debug1: /home/vagrant/.ssh/config line 15: Applying options for CentOS2-Hostname
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 58: Applying options for *
debug1: Connecting to centos2 [192.168.33.11] port 22.
debug1: Connection established.
debug1: identity file /home/vagrant/.ssh/id_ed25519 type 4
debug1: key_load_public: No such file or directory
debug1: identity file /home/vagrant/.ssh/id_ed25519-cert type -1
debug1: Enabling compatibility mode for protocol 2.0
debug1: Local version string SSH-2.0-OpenSSH_7.4
debug1: Remote protocol version 2.0, remote software version OpenSSH_7.4
debug1: match: OpenSSH_7.4 pat OpenSSH* compat 0x04000000
debug1: Authenticating to centos2:22 as 'vagrant'
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: algorithm: curve25519-sha256
<SNIP>
debug1: Next authentication method: publickey
debug1: Offering ED25519 public key: /home/vagrant/.ssh/id_ed25519
debug1: Server accepts key: pkalg ssh-ed25519 blen 51
Enter passphrase for key '/home/vagrant/.ssh/id_ed25519': 
debug1: Authentication succeeded (publickey).
Authenticated to centos2 ([192.168.33.11]:22).
debug1: channel 0: new [client-session]
debug1: Requesting no-more-sessions@openssh.com
debug1: Entering interactive session.
debug1: pledge: network
debug1: client_input_global_request: rtype hostkeys-00@openssh.com want_reply 0
debug1: Sending environment.
debug1: Sending env LANG = en_GB.UTF-8
Last login: Wed Aug  8 13:55:20 2018 from fe80::a00:27ff:fe2a:1652%eth1

再次注意配置的匹配,以及我们连接到 IPv4 地址的事实。

我们可以用这个做更多的事情,即增加我们连接的详细程度,达到下一个调试级别,并希望看到其他值得注意的东西:

[vagrant@centos1 ~]$ ssh -vv CentOS2-Hostname 
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017
debug1: Reading configuration data /home/vagrant/.ssh/config
debug1: /home/vagrant/.ssh/config line 1: Applying options for *
debug1: /home/vagrant/.ssh/config line 15: Applying options for CentOS2-Hostname
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 58: Applying options for *
debug2: resolving "centos2" port 22
debug2: ssh_connect_direct: needpriv 0
debug1: Connecting to centos2 [192.168.33.11] port 22.
debug1: Connection established.

在这里,我们可以看到第二个调试级别(debug2),并且特别地,我们可以看到centos2在块中被给出并解析为一个地址的时刻。

还有更多...

您可能已经注意到,在我的示例中,我对我的名称使用了大写和小写字符的混合(例如CentOS2-V4)。我这样做是因为这意味着我知道我何时在使用我的 SSH 配置文件,并且可以一眼就确定我正在使用我配置的设置。

没有什么能阻止您创建这样的块:

Host centos2
 Hostname 192.168.33.11
 User vagrant

这是完全有效的,设置将被正常读取。

您还可以做一些聪明的事情,比如特定的域匹配。如果您必须管理通过其域区分的两组不同的服务器,您可以这样做:

Host *.examplecake.com
  Port 2222
  User Alie

Host *.examplebiscuit.co.uk
  Port 5252
  User Gingerbread

尝试连接到这两个域中的主机将导致使用特定的配置选项:

[vagrant@centos1 ~]$ ssh -v potato.examplecake.com
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips 26 Jan 2017
debug1: Reading configuration data /home/vagrant/.ssh/config
debug1: /home/vagrant/.ssh/config line 1: Applying options for *
debug1: /home/vagrant/.ssh/config line 19: Applying options for *.examplecake.com
debug1: Reading configuration data /etc/ssh/ssh_config

另请参阅

ssh_config手册页值得一看,即使您只是用它来入睡。

修改服务器端的 SSH 配置文件

在过去的几个部分中,我们一直在关注客户端配置。我们已经在命令行上调整了我们的连接字符串,并且我们已经编写了一个配置文件,当连接到我们的第二个主机时,SSH 会自动读取它。

在这一部分,我们将看一下sshd_config文件,或者说是配置探戈的服务器端,我们的第二个主机。

我们将进行一些示例和例行更改,以使您熟悉这个概念。

做好准备

连接到centos1centos2。最好在外部执行此操作(在单独的窗口中,并使用vagrant ssh):

$ vagrant ssh centos1
$ vagrant ssh centos2

将您的终端窗口并排放置以便查看。

在本节中,有可能会破坏对服务器的 SSH 访问,这就是为什么我建议您使用 Vagrant 进行测试。如果您犯了一个错误,不要担心-只需销毁您的虚拟机并重新开始。

如何做...

在您的centos2机器上,用您喜欢的编辑器打开/etc/ssh/sshd_config

这个文件很大,第一次打开它可能会有点令人生畏。

列出的选项是 SSH 服务器(sshd)在启动时将读取的大部分设置,并适用于您正在运行的守护程序。

更改默认端口

我们将从一个简单的开始,也就是更改 SSH 守护程序运行的默认端口:

# If you want to change the port on a SELinux system, you have to tell
# SELinux about this change.
# semanage port -a -t ssh_port_t -p tcp #PORTNUMBER
#
#Port 22
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::

更改前面的代码,使Port行取消注释,并且现在读取2222

#
Port 2222
#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::

正如这个块之前的便利说明所告诉我们的那样,我们还必须修改 SELinux,以便它知道 SSH 守护程序将尝试使用不同的端口。

这个文件建议我们使用semanage,所以让我们这样做。

首先,我们将找到提供 semanage 的软件包:

[vagrant@centos2 ~]$ sudo yum whatprovides semanage
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirror.vorboss.net
 * extras: mozart.ee.ic.ac.uk
 * updates: mozart.ee.ic.ac.uk
base/7/x86_64/filelists_db                                                    | 6.9 MB  00:00:01 
extras/7/x86_64/filelists_db                                                  | 588 kB  00:00:00 
updates/7/x86_64/filelists_db                                                 | 2.4 MB  00:00:00 
policycoreutils-python-2.5-22.el7.x86_64 : SELinux policy core python utilities
Repo        : base
Matched from:
Filename    : /usr/sbin/semanage

然后,我们将安装它:

[vagrant@centos2 ~]$ sudo yum install -y policycoreutils-python

最后,我们将使用新端口运行推荐的命令:

[vagrant@centos2 ~]$ sudo semanage port -a -t ssh_port_t -p tcp 2222

完成后,我们可以安全地重新启动 SSH 守护程序:

[vagrant@centos2 ~]$ sudo systemctl restart sshd

这不应该将您从虚拟机中踢出来,因为sshd是设计成这样的,即使这些更改会阻止您再次登录(一旦您自愿断开连接)。

现在尝试注销,然后再次登录。

一个预警:这应该失败!

不要害怕!相反,连接到您的第二个终端上的centos1(此时您应该已经打开了两个连接到centos1),然后像这样重新登录到centos2

[vagrant@centos1 ~]$ ssh 192.168.33.11 -p2222

恭喜!SSH 现在在不同的端口上运行!

您可以使用以下命令从操作系统内部确认这一点(我们稍后将更详细地介绍):

[vagrant@centos2 ~]$ ss -nl sport = :2222
Netid State      Recv-Q Send-Q   Local Address:Port                  Peer Address:Port 
tcp   LISTEN     0      128                  *:2222                             *:* 
tcp   LISTEN     0      128                 :::2222                            :::*  

请注意,在前面的代码中,我们打印了 IPv4 和 IPv6 的值。

更改监听地址

默认情况下,SSH 将监听所有地址和接口:

#AddressFamily any
#ListenAddress 0.0.0.0
#ListenAddress ::

我们将更改这样,它只监听 IPv4 和我们的eth1地址。

将前面的选项更改为以下内容:

AddressFamily inet
ListenAddress 192.168.33.11
#ListenAddress ::

我们已经取消注释了两个选项并更改了它们的值。

在前面的块中,您可能已经注意到ListenAddress ::也被列出。在这里,::是 IPv6 中0.0.0.0的等价物。

重新启动 SSH 守护程序:

[vagrant@centos2 ~]$ sudo systemctl restart sshd

运行我们之前的ss命令,您可能会注意到 IPv6 选项已经消失:

[vagrant@centos2 ~]$ ss -nl sport = :2222
Netid State      Recv-Q Send-Q   Local Address:Port                  Peer Address:Port 
tcp   LISTEN     0      128      192.168.33.11:2222                             *:*      

如果我们现在退出到centos2的会话(使用Ctrl + D),然后尝试 SSH 到 IPv6 链路本地地址,它将失败:

[vagrant@centos1 ~]$ ssh fe80::a00:27ff:fe56:c5a7%eth1 -p2222
ssh: connect to host fe80::a00:27ff:fe56:c5a7%eth1 port 2222: Connection refused

一个巨大的成功-我们已经消除了任何趋势设置者登录到我们的 IPv6 服务器的可能性!

现在,让我们认真地谈一分钟,我听说 IPv4 的消亡和 IPv6 的崛起已经有好几年了,基本上是自从我开始从事计算机工作以来。在这段时间里,几乎没有什么改变,运营商和服务提供商都继续从 IPv4 中获取尽可能多的东西,甚至引入了可怕的事情,比如 Carrier-grade NAT。我真心希望 IPv6 能够蓬勃发展,至少因为我们已经基本用完了 IPv4 地址。

更改守护程序日志级别

SSH 可以记录多个级别,由LogLevel设置决定:

# Logging
#SyslogFacility AUTH
SyslogFacility AUTHPRIV
#LogLevel INFO

可能性有QUIETFATALERRORINFOVERBOSEDEBUGDEBUG1DEBUG2DEBUG3

SSH 守护程序手册将DEBUG选项列为违反用户隐私的所有选项,因此不建议您使用它们。

我们将把它提升到VERBOSE

# Logging
#SyslogFacility AUTH
SyslogFacility AUTHPRIV
LogLevel VERBOSE

重新启动 SSH 守护程序:

[vagrant@centos2 ~]$ sudo systemctl restart sshd

现在,让我们看看这个改变带来了什么不同。

这是我们在INFO级别的secure日志:

[vagrant@centos2 ~]$ sudo grep "1137" /var/log/secure
Aug  7 16:40:44 localhost sshd[1137]: Accepted publickey for vagrant from 10.0.2.2 port 53114 ssh2: RSA SHA256:1M4RzhMyWuFS/86uPY/ce2prh/dVTHW7iD2RhpquOZA
Aug  7 16:40:45 localhost sshd[1137]: pam_unix(sshd:session): session opened for user vagrant by (uid=0)

这是我们在VERBOSE级别的secure日志:

[vagrant@centos2 ~]$ sudo grep "5796" /var/log/secure
Aug  8 15:00:00 localhost sshd[5796]: Connection from 192.168.33.10 port 39258 on 192.168.33.11 port 2222
Aug  8 15:00:00 localhost sshd[5796]: Postponed publickey for vagrant from 192.168.33.10 port 39258 ssh2 [preauth]
Aug  8 15:00:02 localhost sshd[5796]: Accepted publickey for vagrant from 192.168.33.10 port 39258 ssh2: ED25519 SHA256:nQVR7ZVJMjph093KHB6qLg9Ve87PF4fNnFw8Y5X0kN4
Aug  8 15:00:03 localhost sshd[5796]: pam_unix(sshd:session): session opened for user vagrant by (uid=0)
Aug  8 15:00:03 localhost sshd[5796]: User child is on pid 5799

禁止 root 登录

一些发行版默认禁止 root 登录,这被广泛认为是一个好主意。在这里,我们有一个用户(vagrant),我们可以用来绕过,这样我们就不需要以 root 身份登录。

找到带有PermitRootLogin的行:

#LoginGraceTime 2m
#PermitRootLogin yes
#StrictModes yes

将其更改为no

#LoginGraceTime 2m
PermitRootLogin no
#StrictModes yes

重新启动 SSH 守护程序:

[vagrant@centos2 ~]$ sudo systemctl restart sshd

这并不禁止local root 登录,所以在紧急情况下,您仍然可以连接到控制台(或将键盘和鼠标插入物理机器)并在本地使用 root 用户登录。

禁用密码(强制使用密钥)

因为我们在这台主机上有公钥,所以不再需要允许基于密码的访问。

找到PasswordAuthentication行:

#PermitEmptyPasswords no
PasswordAuthentication yes

将此行更改为no

#PermitEmptyPasswords no
PasswordAuthentication no

重新启动 SSH 守护程序:

[vagrant@centos2 ~]$ sudo systemctl restart sshd

你们中的敏锐者可能已经注意到,我在本章开头的Vagrantfile中已经翻转了这个设置一次。这是为了让我们能够将 Vagrant 用作学习经验,我们现在正在有效地扭转这种逆转。

设置每日消息(motd)

只要您的PrintMotd设置为yes,您就可以让用户在登录时看到/etc/motd的内容。

首先,确保在 SSH 守护程序配置中将其设置为yes

#PermitTTY yes
PrintMotd yes
#PrintLastLog yes

接下来,重新启动 SSH 守护程序,然后修改/etc/motd文件为合理的内容。或者,您可以使用以下命令:

sudo sh -c 'echo "This is a testing system, how did you get here?" > /etc/motd'

现在每次您登录时都会打印此消息。

这个功能通常被公司用来警告试图访问他们系统的不良分子。偶尔,无聊的系统管理员也会用它来引用《飞出个未来》。

UseDNS 设置

我要讲解的最后一个选项是UseDNS条目,因为它是一些人的痛点:

#UseDNS yes
UseDNS no

在这里,我们可以看到UseDNS在我们的配置文件中已被明确设置为no。这是默认设置。

当设置为no时,SSH 守护程序将不会查找远程主机名,并检查远程 IP 是否映射回预期的 IP,基于该主机名。

为了让您更加困惑,这是UseDNS的手动输入:

“指定 sshd(8)是否应查找远程主机名,并检查解析的远程主机名是否映射回到相同的 IP 地址。

如果此选项设置为 no(默认值),则只能在~/.ssh/authorized_keys 和 sshd_config Match Host 指令中使用地址而不是主机名。

这意味着当UseDNS设置为yes时,您正在连接的机器没有设置反向 DNS 条目,SSH 将尝试将其期望的 IP 与其所看到的 IP 进行匹配,并可能无法这样做。

实际上,这意味着如果您要连接的计算机上的 DNS 出现问题,您必须像柠檬一样等待一段时间,直到 DNS 请求超时,并最终让您进入。更让事情变得更加令人沮丧的是,这个功能在开箱即用时几乎没有用处,正如这封邮件列表邮件中所强调的那样:lists.centos.org/pipermail/centos-devel/2016-July/014981.html

AllowUsers

我们已经拒绝了 root 用户访问我们的系统,但是如果我们想进一步指定要授予访问权限的用户呢?

为此,我们需要AllowUsers设置。

这通常不是默认设置,甚至在sshd_config文件中也没有被注释掉,所以我们要将其添加到底部:

#       PermitTTY no
#       ForceCommand cvs server
AllowUsers vagrant

重新启动 SSH 守护程序:

[vagrant@centos2 ~]$ sudo systemctl restart sshd

现在,您有一个只有vagrant用户才能 SSH 到的系统。您还可以将多个名称添加到此列表,甚至用DenyUsers替换此白名单为黑名单。

如果我们愿意,我们可以使用AllowGroupsDenyGroups以组为基础(而不是个人用户名)进行工作。

工作原理...

现在,我们已经浏览并更改了一些常见设置,我们将快速查看重新启动 SSH 守护程序时会发生什么。

SSH 的systemd单元文件看起来类似于这样,尽管您的系统可能有所不同:

[vagrant@centos2 ~]$ cat /etc/systemd/system/multi-user.target.wants/sshd.service 
[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.service
Wants=sshd-keygen.service

[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd -D $OPTIONS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target

默认情况下,我们可以看到使用的二进制文件是/usr/sbin/sshd,并且从其他地方传递了$OPTIONS(在这种情况下是EnvironmentFile值,如前面所列)。

阅读sshd的手册,我们找到了以下部分:

-f config_file选项(man.openbsd.org/sshd)的描述如下:

“指定配置文件的名称。默认值为/etc/ssh/sshd_config。如果没有配置文件,sshd 将拒绝启动。”

在这里,我们得到了为什么sshd_config默认被读取的答案——它是内置的。

还有更多...

我们只涵盖了一些人们在配置 SSH 守护程序时倾向于更改的基本选项,但大多数管理员根本不费心进行任何更改,而是保留配置的默认值。

另请参阅

要更好地了解可用于您的所有守护程序选项,请阅读sshd_config手册页面,并查看sshd可执行文件的页面。

旋转主机密钥和更新 known_hosts

我们还没有提到的一件事是主机密钥和known_hosts文件。

这是一个经常被忽视的事情,所以我想花几分钟时间来讨论这些被忽视的宝藏。

在本节中,我们将检查第一次 SSH 到新机器时会发生什么,然后我们将更改该机器的密钥,看看这会给我们带来什么问题。

做好准备

在不同的会话中连接到centos1centos2

$ vagrant ssh centos1 $ vagrant ssh centos2

如果您正在进行新安装,从centos1 SSH 到centos2并在出现时接受主机密钥。

centos2注销:

[vagrant@centos1 ~]$ ssh 192.168.33.11
The authenticity of host '192.168.33.11 (192.168.33.11)' can't be established.
ECDSA key fingerprint is SHA256:D4Tu/OykM/iPayCZ2okG0D2F6J9H5PzTNUuFzhzl/xw.
ECDSA key fingerprint is MD5:4b:2a:42:77:0e:24:b4:9c:6e:65:69:63:1a:57:e9:4e.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.33.11' (ECDSA) to the list of known hosts.
vagrant@192.168.33.11's password: 
[vagrant@centos2 ~]$ logout
Connection to 192.168.33.11 closed.
[vagrant@centos1 ~]$ 

我们现在在我们的known_hosts文件中有一个条目,如下所示:

[vagrant@centos1 ~]$ cat .ssh/known_hosts 
192.168.33.11 ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOK52r7ZJ8hwU34RzaY3AD7HitT6UP2qBv3WK8lWEELSoeTsmJ4+zO8QiuULp3cCQBKYqi55Z60Vf/hsEMBoULg=

请注意,在centos2上找到了此 IP 和密钥:

[vagrant@centos2 ~]$ cat /etc/ssh/ssh_host_ecdsa_key.pub 
ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOK52r7ZJ8hwU34RzaY3AD7HitT6UP2qBv3WK8lWEELSoeTsmJ4+zO8QiuULp3cCQBKYqi55Z60Vf/hsEMBoULg=

我们可以通过在两台机器上查看密钥的指纹并比较 ASCII 图来轻松证明这一点。

centos2上,操作如下:

[vagrant@centos2 ~]$ ssh-keygen -lv -f /etc/ssh/ssh_host_ecdsa_key.pub 
256 SHA256:D4Tu/OykM/iPayCZ2okG0D2F6J9H5PzTNUuFzhzl/xw no comment (ECDSA)
+---[ECDSA 256]---+
|   . .       o.  |
|  . . o.    o..  |
| o . =. .  + o.  |
|. o o.+.    B  . |
|.  + +..S. o o E.|
|. + +o. oo. .  .o|
|.+ o +o ...     o|
|o.o . +*         |
|.    o=*=        |
+----[SHA256]-----+

centos1known_hosts文件中如下:

[vagrant@centos1 ~]$ ssh-keygen -lv -f .ssh/known_hosts 
256 SHA256:D4Tu/OykM/iPayCZ2okG0D2F6J9H5PzTNUuFzhzl/xw 192.168.33.11 (ECDSA)
+---[ECDSA 256]---+
|   . .       o.  |
|  . . o.    o..  |
| o . =. .  + o.  |
|. o o.+.    B  . |
|.  + +..S. o o E.|
|. + +o. oo. .  .o|
|.+ o +o ...     o|
|o.o . +*         |
|.    o=*=        |
+----[SHA256]-----+

这真的是我第一次使用-v选项来获取密钥的 ASCII 图进行比较。

如何操作...

现在我们已经确认了我们的设置,我们将在centos2上更改主机密钥,看看会发生什么。

centos2上,运行以下命令:

[vagrant@centos2 ~]$ sudo mv /etc/ssh/ssh_host_ecdsa_key* /home/vagrant/
[vagrant@centos2 ~]$ ls
ssh_host_ecdsa_key  ssh_host_ecdsa_key.pub

我们刚刚将我们在centos1上接受为真理的密钥移动了。

我们的会话保持连接,因为我们已经经过身份验证并连接。如果此时断开连接,我们将不得不接受不同的密钥(我们移动了 ECDSA 密钥,但仍然有Ed25519主机密钥可用,SSH 将选择使用它们)。

现在,我们将使用通用的-A标志生成一组新的密钥:

[vagrant@centos2 ~]$ sudo ssh-keygen -A
ssh-keygen: generating new host keys: RSA1 DSA ECDSA

我们可以通过检查目录来确认这些存在:

[vagrant@centos2 ~]$ ls -l /etc/ssh/ssh_host_ecdsa_key*
-rw-------. 1 root root 227 Aug  8 16:30 /etc/ssh/ssh_host_ecdsa_key
-rw-r--r--. 1 root root 174 Aug  8 16:30 /etc/ssh/ssh_host_ecdsa_key.pub

centos2注销并尝试重新登录:

[vagrant@centos1 ~]$ ssh 192.168.33.11
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
Someone could be eavesdropping on you right now (man-in-the-middle attack)!
It is also possible that a host key has just been changed.
The fingerprint for the ECDSA key sent by the remote host is
SHA256:vdJTJW4ewGtOAdQXCXJ+cbjvrNm9787/CQQnCeM9fjc.
Please contact your system administrator.
Add correct host key in /home/vagrant/.ssh/known_hosts to get rid of this message.
Offending ECDSA key in /home/vagrant/.ssh/known_hosts:1
ECDSA host key for 192.168.33.11 has changed and you have requested strict checking.
Host key verification failed.
[vagrant@centos1 ~]$

SSH 试图阻止您做一些坏事。因为它已经知道您要连接的 IP,并且有一个known_hosts条目,它将文件中的已知密钥与盒子上的密钥进行比较。

由于我们刚刚在盒子上重新生成了密钥,我们被呈现了一个看起来很糟糕的错误。

值得克服心理障碍,不要只是嘲笑并绕过这个错误。试着花五秒钟的时间来确认错误是否符合预期。我经常看到人们一遇到这个消息就立刻抱怨并立即绕过它。如果您已经在某个盒子上接受了密钥,那么您不应该再看到有关它的警告,这可能意味着该盒子已被篡改,或者您的连接被“中间人”攻击。要保持警惕!

从我们的known_hosts文件中清除旧密钥(在前面的代码中加粗显示了行位置):

[vagrant@centos1 ~]$ ssh-keygen -R 192.168.33.11
# Host 192.168.33.11 found: line 1
/home/vagrant/.ssh/known_hosts updated.
Original contents retained as /home/vagrant/.ssh/known_hosts.olds 

现在您应该能够再次 SSH 到centos2并接受新密钥:

[vagrant@centos1 ~]$ ssh 192.168.33.11
The authenticity of host '192.168.33.11 (192.168.33.11)' can't be established.
ECDSA key fingerprint is SHA256:vdJTJW4ewGtOAdQXCXJ+cbjvrNm9787/CQQnCeM9fjc.
ECDSA key fingerprint is MD5:c3:be:16:5b:62:7f:4d:9c:0b:15:c0:cd:d6:87:d6:d6.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.33.11' (ECDSA) to the list of known hosts.
vagrant@192.168.33.11's password: 
Last login: Wed Aug  8 16:26:50 2018 from 192.168.33.10
[vagrant@centos2 ~]$ 

工作原理...

我们使用的ssh-keygen命令是在默认位置放置预期主机密钥的快速方法。因为我们删除了我们期望在那里的密钥,所以我们将无法连接到我们的主机,并且会被提示我们之前看到的可怕错误:

<SNIP>
debug1: Server host key: ecdsa-sha2-nistp256 SHA256:zW4PXt4o3VRA/OiePUc4VoxBY50us9vl2vemgcrLduA
debug3: hostkeys_foreach: reading file "/home/vagrant/.ssh/known_hosts"
debug3: record_hostkey: found key type ECDSA in file /home/vagrant/.ssh/known_hosts:1
debug3: load_hostkeys: loaded 1 keys from 192.168.33.11
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@    WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED!     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
<SNIP>

在前面的片段中,我们可以看到 SSH 检查我们的known_hosts文件,然后从远程主机获取密钥,最后引发了一场骚动。

要重新连接到主机,我们只需从客户端的known_hosts文件中删除有问题的条目,然后再次尝试我们的连接。

我们使用了-R来删除有问题的密钥,但您可以使用任何方法来这样做,因为它只是一个文本文件。如果您愿意,甚至可以清空整个known_hosts文件,但这也意味着您将不得不再次接受您曾经连接过的每个盒子的密钥。

还有更多...

那么,如果您从服务器中删除所有主机密钥会发生什么?

这就是你会得到的:

[vagrant@centos2 ~]$ sudo rm /etc/ssh/ssh_host_*
[vagrant@centos2 ~]$ logout
Connection to 192.168.33.11 closed.
[vagrant@centos1 ~]$ ssh  192.168.33.11 
ssh_exchange_identification: read: Connection reset by peer

此时,您可以重新配置您的 VM,或者通过控制台登录并生成新的密钥。

技术要求

确认您的两个 Vagrant 盒子都已启用,并使用vagrant命令连接到两个盒子。

如果您之前更改了 SSH 配置文件,最好先销毁您的盒子并重新配置它们:

$ vagrant ssh centos1
$ vagrant ssh centos2

使用本地转发

本地转发是将本地 TCP 端口或 Unix 套接字映射到远程端口或套接字的行为。当要么安全地访问系统(要求用户首先 SSH 到盒子,从而加密他们的连接),要么用于解决问题时,它通常被使用。

在本节中,我们将在centos2上启动一个小的webserver,我们将首先通过直接连接到 IP 和端口,然后通过连接到映射的本地端口来从centos1连接到它,利用端口转发。

做好准备

centos2上运行以下命令:

[vagrant@centos2 ~]$ python -m SimpleHTTPServer 8888
Serving HTTP on 0.0.0.0 port 8888 ...

您刚刚创建了一个小型的基于 Python 的 Web 服务器,监听端口8888上的每个地址。

您可以通过从centos1运行curl命令来确认这一点:

[vagrant@centos1 ~]$ curl 192.168.33.11:8888
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html>
<title>Directory listing for /</title>
<body>
<h2>Directory listing for /</h2>
<hr>
<ul>
<li><a href=".bash_logout">.bash_logout</a>
<li><a href=".bash_profile">.bash_profile</a>
<li><a href=".bashrc">.bashrc</a>
<li><a href=".ssh/">.ssh/</a>
</ul>
<hr>
</body>
</html>

注意从centos2列出的主目录内容。

centos2上,您应该看到您的连接(200响应):

[vagrant@centos2 ~]$ python -m SimpleHTTPServer 8888
Serving HTTP on 0.0.0.0 port 8888 ...
192.168.33.10 - - [09/Aug/2018 10:47:13] "GET / HTTP/1.1" 200 -

Python 的内置 Web 服务器模块非常适用于测试。我在这里使用它是因为它在我们的安装中是开箱即用的,但我不会在生产环境中使用它,因为有更好(更快)的替代品。

要确认我们尚未在端口9999上本地监听任何内容,请从centos1执行另一个curl命令:

[vagrant@centos1 ~]$ curl 127.0.0.1:9999
curl: (7) Failed connect to 127.0.0.1:9999; Connection refused

如何做...

我们将本地转发连接到本地端口9999到远程端口8888

在命令行上

centos1运行以下命令:

[vagrant@centos1 ~]$ ssh -f -L 9999:127.0.0.1:8888 192.168.33.11 sleep 120

您可能会被提示输入密码(取决于您在密钥设置方面做了什么),然后被放回到centos1提示符。

我们的 SSH 连接将保持两分钟。

现在,我们运行一个curl,检查我们的转发是否正常工作:

[vagrant@centos1 ~]$ curl 127.0.0.1:9999
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html>
<title>Directory listing for /</title>
<body>
<h2>Directory listing for /</h2>
<hr>
<ul>
<li><a href=".bash_history">.bash_history</a>
<li><a href=".bash_logout">.bash_logout</a>
<li><a href=".bash_profile">.bash_profile</a>
<li><a href=".bashrc">.bashrc</a>
<li><a href=".ssh/">.ssh/</a>
</ul>
<hr>
</body>
</html>

成功!在这里,我们正在从我们转发的端口上的centos1的本地主机 IP 地址进行 curl,并且我们正在从centos2获取目录列表!

使用 SSH 配置文件

如果我们想要每次连接到centos2时创建这种转发设置,我们可以将选项添加到我们的 SSH 配置文件中。

在以下代码中添加加粗的行:

Host * !CentOS2-V6
 IdentityFile ~/.ssh/id_ed25519
 Port 22

Host CentOS2-V4
 Hostname 192.168.33.11
 LocalForward 9999 127.0.0.1:8888
 User vagrant

Host CentOS2-V6
 Hostname fe80::a00:27ff:fe56:c5a7%%eth1
 IdentityFile ~/.ssh/id_rsa
 Port 22
 User vagrant

Host CentOS2-Hostname
 Hostname centos2
 User vagrant

现在,如果您 SSH 到指定的主机,您将创建一个转发连接,而无需指定它:

[vagrant@centos1 ~]$ ssh -f CentOS2-V4 sleep 120
[vagrant@centos1 ~]$ curl 127.0.0.1:9999
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html>
<title>Directory listing for /</title>
<body>
<h2>Directory listing for /</h2>
<hr>
<ul>
<li><a href=".bash_history">.bash_history</a>
<li><a href=".bash_logout">.bash_logout</a>
<li><a href=".bash_profile">.bash_profile</a>
<li><a href=".bashrc">.bashrc</a>
<li><a href=".ssh/">.ssh/</a>
</ul>
<hr>
</body>
</html>

您不限于每个主机一条LocalForward条目 - 您可以有多个。

它是如何工作的...

当您使用 SSH 的-L标志时,您正在指定任何连接尝试都将转发到远程主机和端口的本地机器上列出的第一个端口。

让我们分解一下这个命令:

[vagrant@centos1 ~]$ ssh -f -L 9999:127.0.0.1:8888 192.168.33.11 sleep 120

首先,命令末尾的-fsleep 120是一种快速创建会话并在我们进行测试时将其后台化的方法:

-f ... sleep 120

在现实世界中,您不仅限于一个终端窗口,通常情况下,您会发现自己在一个窗口中连接到远程主机,而在另一个窗口中进行工作。

第二部分是有趣的部分:

-L 9999:127.0.0.1:8888

在这里,我们说本地端口9999应将任何连接请求转发到远程主机的127.0.0.1:8888

由于我们创建 Web 服务器的方式,以下语法也是有效的:

-L 9999:192.168.33.11:8888

这是因为我们的远程 Web 服务器正在监听所有地址,因此我们不是将请求发送到远程本地主机地址,而是使用eth1地址。

我经常看到设置,其中不太安全的程序仅在本地主机地址上运行,这意味着如果要访问该程序,必须先 SSH 到远程主机。

您不仅限于 cURL 和命令行 - 您可以在 Web 浏览器中导航到http://127.0.0.1:9999,它仍然可以工作。

还有更多...

SSH 的技巧和窍门有点无穷无尽,但以下内容可能是很好的练习。

观察我们的 SSH 会话

如果要查看 SSH 隧道何时关闭,请运行以下命令:

[vagrant@centos1 ~]$ ps aux | grep "ssh -f" | grep -v grep
vagrant   3525  0.0  0.2  82796  1196 ?        Ss   11:03   0:00 ssh -f -L 9999:127.0.0.1:8888 192.168.33.11 sleep 120

在断开连接时,此过程将结束:

[vagrant@centos1 ~]$ ps aux | grep "ssh -f" | grep -v grep
[vagrant@centos1 ~]$ 

连接到远程主机之外的系统

LocalForwarding甚至可以用于访问远程机器可以看到但本地机器看不到的主机。

考虑以下配置条目:

Host *
 IdentityFile ~/.ssh/id_ed25519
 Port 22

Host CentOS2-V4
 Hostname 192.168.33.11
 LocalForward 7777 192.168.33.12:6666
 User vagrant

在此示例中,centos2可以看到具有 IP192.168.33.12的主机,以及在端口6666上监听的服务器。

当我们连接到centos2并创建我们的隧道时,我们可以在本地连接到127.0.0.1:7777,查看192.168.33.12:6666上的 Web 服务器。

这通常与堡垒主机一起使用,我们很快将会看到。

另请参阅

将本地连接尝试转发到远程主机可能是一种非常有用的故障排除和访问控制方法。

查看 SSH 手册页面,以获取有关此处列出的选项的更多详细信息和扩展。

可以使用以下命令在大多数 Linux 系统上打开 SSH 手册页面:

$ man ssh

使用远程转发

在上一节中,我们看到了将本地连接尝试转发到远程机器的能力。

在本节中,我们将看到非常相似的内容:远程转发。

通过远程转发,对远程机器上指定地址和端口的连接尝试将通过您设置的 SSH 隧道传回,并在本地机器(客户端)上进行处理。

centos1开始。

在开始之前值得注意的是,远程转发是打开网络的一种很好的方法,这意味着它也可能是网络维护的安全专业人员的噩梦。伴随着巨大的力量而来的是巨大的责任等等。

准备工作

确认您的 Vagrant 框都已启用,并连接到两者:

$ vagrant ssh centos1
$ vagrant ssh centos2

如何做...

首先,我们将从我们的提示符处使用我们的单个命令开始,然后我们将看看如何使用 SSH 配置文件每次 SSH 到一台机器时设置连接。

在命令行上

centos1上运行以下命令:

[vagrant@centos1 ~]$ ssh -R 5353:127.0.0.1:22 192.168.33.11

连接到centos2后,运行以下命令:

[vagrant@centos2 ~]$ ssh 127.0.0.1 -p5353

您可能会提示添加主机密钥,然后提示输入密码。我们正在连接回centos1,因此提供默认的 Vagrant 密码。

您应该留在centos1命令行提示符处:

vagrant@127.0.0.1's password: 
Last login: Thu Aug  9 12:29:56 2018 from 127.0.0.1
[vagrant@centos1 ~]$ 

使用 SSH 配置文件

LocalForward一样,我们也可以使用 SSH 配置文件进行RemoteForward连接:

Host *
 IdentityFile ~/.ssh/id_ed25519
 Port 22

Host CentOS2-V4
 Hostname 192.168.33.11
 LocalForward 9999 127.0.0.1:8888
 RemoteForward 5353 127.0.0.1:22
 User vagrant

在这里,您可以看到我们在命令行部分中使用的确切设置,只是在配置文件中指定,因此它始终可用,而无需每次键入标志:

[vagrant@centos1 ~]$ ssh CentOS2-V4
[vagrant@centos2 ~]$ ssh 127.0.0.1 -p5353
[vagrant@centos1 ~]$ 

工作原理...

我们实际上在这里做的是...奇怪的:

  1. 我们 SSH 到centos2,同时指定在远程机器(centos2)上对端口5353进行的任何连接尝试都将通过 SSH 会话传回到我们的客户端(centos1)。

  2. 然后我们在我们的远程机器(centos2)上运行 SSH,指定本地地址和我们传递回centos1的端口,127.0.0.1:5353

  3. 连接尝试被传递回我们已建立的 SSH 会话到centos1,SSH 服务器接受连接请求。

  4. 结果是,我们通过在centos2上指定本地地址和远程转发端口来本地 SSH 到centos1

困惑吗?有人第一次向我解释这个时我也感到困惑。

为了更好地理解这一点,我们可以使用w命令。

centos1上,这给我们以下结果:

[vagrant@centos1 ~]$ w
 12:47:50 up  2:10,  2 users,  load average: 0.00, 0.02, 0.05
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
vagrant  pts/0    10.0.2.2         10:38    6.00s  1.07s  0.08s ssh -R 5353:127.0.0.1:22 192.168.33.
vagrant  pts/1    127.0.0.1        12:44    6.00s  0.07s  0.05s w

在这里,我们可以看到我们的默认 Vagrant 连接(来自10.0.2.2),但我们也可以看到一个本地连接。

显然,我们已经从本地地址(127.0.0.1)SSH 到我们的机器。这实际上是我们在centos2上使用以下命令建立的 SSH 会话:

[vagrant@centos2 ~]$ ssh 127.0.0.1 -p5353

centos2上,w命令给出以下结果:

[vagrant@centos2 ~]$ w
 12:48:08 up  2:09,  2 users,  load average: 0.00, 0.01, 0.05
USER     TTY      FROM             LOGIN@   IDLE   JCPU   PCPU WHAT
vagrant  pts/0    10.0.2.2         10:43    0.00s  0.92s  0.04s w
vagrant  pts/1    192.168.33.10    12:44   24.00s  0.07s  0.04s ssh 127.0.0.1 -p5353

在这里,我们可以看到我们的默认 Vagrant 连接(来自10.0.2.2),但我们也可以看到从centos1192.168.33.10)的远程连接。

还有更多...

不仅仅是 SSH 可以使用这个。同样,我们可以将远程会话的端口转发到我们的本地机器上 - 我们有许多可用的选项。

让我们在centos1上启动并在后台运行一个简单的 Web 服务器:

[vagrant@centos1 ~]$ python -m SimpleHTTPServer 8888 &
[1] 6010

现在,让我们 SSH 到centos2,同时声明在远程机器上对127.0.0.1:7777的任何请求都会沿着已建立的 SSH 会话传递回centos1

[vagrant@centos1 ~]$ ssh -R 7777:127.0.0.1:8888 192.168.33.11

centos2上,我们现在应该能够curl 127.0.0.1:7777并查看centos1上 Vagrant 主目录的内容:

[vagrant@centos2 ~]$ curl 127.0.0.1:7777
127.0.0.1 - - [09/Aug/2018 12:56:43] "GET / HTTP/1.1" 200 -
 <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html>
<title>Directory listing for /</title>
<body>
<h2>Directory listing for /</h2>
<hr>
<ul>
<li><a href=".bash_history">.bash_history</a>
<li><a href=".bash_logout">.bash_logout</a>
<li><a href=".bash_profile">.bash_profile</a>
<li><a href=".bashrc">.bashrc</a>
<li><a href=".ssh/">.ssh/</a>
</ul>
<hr>
</body>
</html>

成功!

另请参阅

虽然看起来似乎用途有限,但就巧妙的技巧而言,您可能会在职业生涯中找到一些奇特的用例。

我曾在一两个场合使用过这个方法,当远程机器的 DNS 出现故障时,我反而通过已建立的 SSH 连接转发 DNS 请求。

代理跳转和堡垒主机

在这个教程中,我们将看一看一个非常新的 SSH 选项,一个稍老的 SSH 选项,以及堡垒主机(或跳板机)的概念。

我们需要三台机器,因为我们将使用一台机器作为到另一台机器的“网关”。

准备好了

设置好您的三个 VM,最好使用本章开头的Vagrantfile

连接到每个框,然后检查从centos1是否可以 ping 通centos2centos3

[vagrant@centos1 ~]$ ping 192.168.33.11
PING 192.168.33.11 (192.168.33.11) 56(84) bytes of data.
64 bytes from 192.168.33.11: icmp_seq=1 ttl=64 time=2.54 ms
64 bytes from 192.168.33.11: icmp_seq=2 ttl=64 time=1.09 ms
64 bytes from 192.168.33.11: icmp_seq=3 ttl=64 time=0.929 ms
^C
--- 192.168.33.11 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2009ms
rtt min/avg/max/mdev = 0.929/1.524/2.548/0.728 ms
[vagrant@centos1 ~]$ ping 192.168.33.12
PING 192.168.33.12 (192.168.33.12) 56(84) bytes of data.
64 bytes from 192.168.33.12: icmp_seq=1 ttl=64 time=0.743 ms
64 bytes from 192.168.33.12: icmp_seq=2 ttl=64 time=1.15 ms
64 bytes from 192.168.33.12: icmp_seq=3 ttl=64 time=1.12 ms
^C
--- 192.168.33.12 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2015ms
rtt min/avg/max/mdev = 0.743/1.008/1.157/0.187 ms

如果您使用提供的Vagrantfile,这些地址是192.168.33.11192.168.33.12

如何做...

centos1,运行以下命令:

[vagrant@centos1 ~]$ ssh -J vagrant@192.168.33.11:22 192.168.33.12

您可能会被提示接受密钥,并被要求输入密码。

您将发现自己在centos3上,已经跳转到centos2

[vagrant@centos3 ~]$ 

使用 SSH 配置文件

通过在 SSH 配置文件中指定ProxyJump选项,可以使用相同的技巧:

Host *
 IdentityFile ~/.ssh/id_ed25519
 Port 22

Host CentOS2-V4
 Hostname 192.168.33.11
 User vagrant

Host CentOS3-V4
 Hostname 192.168.33.12
 User vagrant
 ProxyJump CentOS2-V4

现在您可以通过centos2 SSH 到centos3

[vagrant@centos1 ~]$ ssh CentOS3-V4
vagrant@192.168.33.11's password: 
vagrant@192.168.33.12's password: 
Last login: Thu Aug  9 14:15:03 2018 from 192.168.33.11
[vagrant@centos3 ~]$ 

工作原理...

-JProxyJump选项是通过指定的主机连接到更远主机的一种方法。

官方手册页面(man.openbsd.org/ssh)中-J [user@]host[:port]的手动输入如下:

首先通过 SSH 连接到由目标描述的跳转主机,然后从那里建立到最终目的地的 TCP 转发,连接到目标主机。可以指定多个跳转跳数,用逗号字符分隔。这是指定 ProxyJump 配置指令的快捷方式。

ProxyJump的手动输入来自man.openbsd.org/ssh_config,如下所示:

指定一个或多个跳转代理,格式为[user@]host[:port]或 ssh URI。多个代理可以用逗号字符分隔,并将按顺序访问。设置此选项将导致 ssh(1)首先通过与指定的 ProxyJump 主机建立 ssh(1)连接,然后从那里建立到最终目标的 TCP 转发。

如果我们使用 SSH 的-v标志,我们可以更详细地看到发生了什么:

[vagrant@centos1 ~]$ ssh -v CentOS3-V4
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017
debug1: Reading configuration data /home/vagrant/.ssh/config
debug1: /home/vagrant/.ssh/config line 1: Applying options for *
debug1: /home/vagrant/.ssh/config line 8: Applying options for CentOS3-V4
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 58: Applying options for *
debug1: Setting implicit ProxyCommand from ProxyJump: ssh -v -W %h:%p CentOS2-V4
debug1: Executing proxy command: exec ssh -v -W 192.168.33.12:22 CentOS2-V4
<SNIP>
debug1: permanently_drop_suid: 1000
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017
debug1: Reading configuration data /home/vagrant/.ssh/config
debug1: /home/vagrant/.ssh/config line 1: Applying options for *
debug1: /home/vagrant/.ssh/config line 4: Applying options for CentOS2-V4
debug1: Reading configuration data /etc/ssh/ssh_config
debug1: /etc/ssh/ssh_config line 58: Applying options for *
debug1: Connecting to 192.168.33.11 [192.168.33.11] port 22.
debug1: Connection established.
debug1: key_load_public: No such file or directory
<SNIP>
debug1: kex_input_ext_info: server-sig-algs=<rsa-sha2-256,rsa-sha2-512>
debug1: SSH2_MSG_SERVICE_ACCEPT received
debug1: Authentications that can continue: publickey,gssapi-keyex,gssapi-with-mic,password
debug1: Next authentication method: gssapi-keyex
debug1: No valid Key exchange context
debug1: Next authentication method: gssapi-with-mic
debug1: Unspecified GSS failure.  Minor code may provide more information
No Kerberos credentials available (default cache: KEYRING:persistent:1000)

debug1: Unspecified GSS failure.  Minor code may provide more information
No Kerberos credentials available (default cache: KEYRING:persistent:1000)

debug1: Next authentication method: publickey
debug1: Trying private key: /home/vagrant/.ssh/id_rsa
debug1: Trying private key: /home/vagrant/.ssh/id_dsa
debug1: Trying private key: /home/vagrant/.ssh/id_ecdsa
debug1: Trying private key: /home/vagrant/.ssh/id_ed25519
debug1: Next authentication method: password
vagrant@192.168.33.11's password: 
debug1: Authentication succeeded (password).
Authenticated to 192.168.33.11 ([192.168.33.11]:22).
debug1: channel_connect_stdio_fwd 192.168.33.12:22
debug1: channel 0: new [stdio-forward]
debug1: getpeername failed: Bad file descriptor
debug1: Requesting no-more-sessions@openssh.com
debug1: Entering interactive session.
debug1: pledge: network
debug1: client_input_global_request: rtype hostkeys-00@openssh.com want_reply 0
debug1: Remote protocol version 2.0, remote software version OpenSSH_7.4
debug1: match: OpenSSH_7.4 pat OpenSSH* compat 0x04000000
debug1: Authenticating to 192.168.33.12:22 as 'vagrant'
debug1: SSH2_MSG_KEXINIT sent
debug1: SSH2_MSG_KEXINIT received
debug1: kex: algorithm: curve25519-sha256
debug1: kex: host key algorithm: ecdsa-sha2-nistp256
debug1: kex: server->client cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug1: kex: client->server cipher: chacha20-poly1305@openssh.com MAC: <implicit> compression: none
debug1: kex: curve25519-sha256 need=64 dh_need=64
debug1: kex: curve25519-sha256 need=64 dh_need=64
debug1: expecting SSH2_MSG_KEX_ECDH_REPLY
<SNIP>
vagrant@192.168.33.12's password: 
debug1: Authentication succeeded (password).
Authenticated to 192.168.33.12 (via proxy).
debug1: channel 0: new [client-session]
debug1: Requesting no-more-sessions@openssh.com
debug1: Entering interactive session.
debug1: pledge: proc
debug1: client_input_global_request: rtype hostkeys-00@openssh.com want_reply 0
debug1: Sending environment.
debug1: Sending env LANG = en_GB.UTF-8
Last login: Thu Aug  9 14:22:08 2018 from 192.168.33.11
[vagrant@centos3 ~]$

在前面的输出中加粗显示,我们可以看到连接序列中发生的关键步骤:

  1. SSH 读取我们要连接的主机的配置。

  2. SSH 意识到它必须使用ProxyJump主机来访问指定的主机。

  3. SSH 将ProxyJump选项转换为等效的ProxyCommand条目。

  4. SSH 读取ProxyJump主机的配置。

  5. SSH 连接并对ProxyJump主机进行身份验证。

  6. SSH 使用其已建立的连接到ProxyJump来连接目标主机。

  7. SSH 注意到它已经通过代理对目标主机进行了身份验证。

还有更多...

现在你已经了解了ProxyJump的基础知识,让我们看看一些你可能会发现有用的场景。

不止一次,以以下列出的方式使用ProxyJump为我节省了几毫秒的时间!

多个主机

虽然先前给出的示例相对简单,但值得注意的是,你可以用ProxyJump做一些相当复杂的事情。

你可以像手册页建议的那样列出主机,也可以像下面这样链接主机:

Host *
 Port 22

Host CentOS2-V4
 Hostname 192.168.33.11
 User vagrant

Host CentOS3-V4
 Hostname 192.168.33.12
 User vagrant
 ProxyJump CentOS2-V4

Host CentOS4-V4
 Hostname 192.168.33.14
 User vagrant
 ProxyJump CentOS3-V4

ProxyJump本身的优势应该是显而易见的:使用这种技术,你可以创建一个只需要从本地机器输入一个命令就能访问远程且其他情况下无法访问的主机的设置。

通常,你可能会在只有一个入口服务器的环境中使用ProxyJump

ProxyJump还使转发端口变得更容易。如果你在前面的代码中的CentOS4-V4中添加了一个LocalForward行,SSH 也会通过ProxyJump主机处理流量!这可能特别方便,因为它可以阻止你手动转发端口,可能需要通过几个主机。

ProxyCommand

在我们的调试消息中看到的是 SSH 将相当简单的ProxyJump条目转换为ProxyCommand行。

ProxyCommand是设置这种转发的更传统的方式,但它不仅在语法上更加恼人,而且也很混乱。

考虑以下示例:

Host *
 Port 22

Host CentOS2-V4
 Hostname 192.168.33.11
 User vagrant

Host CentOS3-V4
 Hostname 192.168.33.12
 User vagrant
 ProxyCommand ssh -v -W %h:%p CentOS2-V4

看起来更笨拙,不是吗?但它的工作方式是一样的。

这在较旧的发行版上可能很有用,这些发行版可能尚未收到ProxyJump功能。

如果你忘记了ProxyCommand的语法,并且你有一个支持ProxyJump的主机,记住我们之前创建的SSH -v调试中已经为你打印了ProxyCommand的语法。

堡垒主机

所有这些都很棒,但是如果你正在管理服务器,为什么你需要这个呢?尤其是你控制的服务器...

考虑你的环境。

在办公室,你可能可以访问公司在其支配下的每台机器,因为你坐在一个可以无限制访问每个其他网络段的 LAN 段上。

远程,你可能有一个 VPN 机器位于你的网络边界上,你需要最初建立连接,然后才能 SSH 到其他机器。

堡垒主机是你可能考虑的东西,它们可以与 VPN 一起使用。

作为系统管理员,你可以决定你想要一个单一的入口点,让人们轻松地通过 SSH 连接到机器来记录流量,也许管理密钥——也许是因为你只是恶毒的,想让每个人的配置文件变得更长一些?

与你的网络团队合作,咨询你公司的政策,并设计一个你可以轻松维护的网络,而其他人也不会介意使用的网络。

你的公司可能有特定的安全政策限制你可以做什么。记住,问题不在于你做什么,而在于你应该做什么。当你因为绕过安全措施而被赶出办公室时,没有人会因为你聪明而向你祝贺。尽管在发现安全问题时可以指出,但不要利用它们。

使用 SSH 创建 SOCKS 代理

SSH 很棒。

我永远不会厌倦谈论它有多么棒,而且我不提到它最好的功能之一会是我的疏忽:快速轻松地设置一个 SOCKS 代理的能力。

在以前的部分中,我们转发了单个端口,但是如果我们使用跳板主机连接网络中的大量不同网站怎么办?您想要在 SSH 配置文件中添加数十行吗?还是每次手动输入每个端口和映射?

我不这么认为。

这就是-D标志的作用。

请参阅 SSH 手册页中的-D [bind_address:]portman.openbsd.org/ssh):

指定本地“动态”应用级端口转发。这通过分配一个套接字来监听本地端口,可选地绑定到指定的 bind_address。每当有连接到该端口时,连接将通过安全通道转发,并且然后使用应用程序协议确定从远程计算机连接到何处。当前支持 SOCKS4 和 SOCKS5 协议,并且 ssh 将充当 SOCKS 服务器。只有 root 可以转发特权端口。动态端口转发也可以在配置文件中指定。

IPv6 地址可以通过用方括号括起来指定。只有超级用户才能转发特权端口。默认情况下,本地端口将根据 GatewayPorts 设置进行绑定。但是,可以使用显式的 bind_address 将连接绑定到特定地址。 "localhost"的 bind_address 表示监听端口仅绑定供本地使用,而空地址或'*'表示该端口应该从所有接口可用。

这意味着通过一个命令,您可以建立一个连接,然后可以通过该连接转发流量(从 Web 浏览器或其他支持SOCKS代理的应用程序)。您不必穿透防火墙,也不必手动映射端口。

SOCKS本身是一种互联网协议,而且是相当古老的协议,尽管我们仍然积极使用SOCKS5,这是由互联网工程任务组在 1996 年批准的!它就像任何其他代理服务器一样,允许您通过连接交换数据包;在这种情况下,是我们的 SSH 隧道。应用程序可以选择本机支持 SOCKS 代理或不支持,但很多常见的应用程序会支持(例如 Firefox)。

让我们开始吧。

准备工作

在本节中,我们将使用centos1centos2

确保您同时连接到两台机器:

$ vagrant ssh centos1
$ vagrant ssh centos2

centos2上,让我们再次设置我们的小型 Web 服务器:

[vagrant@centos2 ~]$ python -m SimpleHTTPServer 8888 &
[1] 7687

如何做...

首先连接到centos1,我们将首先使用一个命令设置我们的 SOCKS 代理,然后看看如何在每次 SSH 到该服务器时启动代理。

在命令行上

让我们建立我们的 SSH 会话并同时断开已建立的会话:

[vagrant@centos1 ~]$ ssh -f -D9999 192.168.33.11 sleep 120
vagrant@192.168.33.11's password: 
[vagrant@centos1 ~]$ 

一旦建立(直到休眠结束),我们可以使用我们的代理通过 SSH 会话查询centos2可以看到的任何东西。

让我们从centos1上检查我们的 Web 服务器,到centos2上:

[vagrant@centos1 ~]$ all_proxy="socks5://127.0.0.1:9999" curl 127.0.0.1:8888
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html>
<title>Directory listing for /</title>
<body>
<h2>Directory listing for /</h2>
<hr>
<ul>
<li><a href=".bash_history">.bash_history</a>
<li><a href=".bash_logout">.bash_logout</a>
<li><a href=".bash_profile">.bash_profile</a>
<li><a href=".bashrc">.bashrc</a>
<li><a href=".lesshst">.lesshst</a>
<li><a href=".mysql_history">.mysql_history</a>
<li><a href=".ssh/">.ssh/</a>
</ul>
<hr>
</body>
</html>
[vagrant@centos1 ~]$

太棒了!我们对本地主机地址运行了一个 cURL,但是通过代理传递,我们的请求已经在centos2上运行了!

使用 SSH 配置文件

如以前所示,可以使用 SSH 配置文件来完成相同的操作:

Host *
 Port 22

Host CentOS2-V4
 Hostname 192.168.33.11
 User vagrant
 DynamicForward 9999

现在我们可以确信我们的代理每次连接时都可用:

[vagrant@centos1 ~]$ ssh -f CentOS2-V4 sleep 120

再次,查看 Web 服务器的内容:

[vagrant@centos1 ~]$ all_proxy="socks5://127.0.0.1:9999" curl 127.0.0.1:8888
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html>
<title>Directory listing for /</title>
<body>
<h2>Directory listing for /</h2>
<hr>
<ul>
<li><a href=".bash_history">.bash_history</a>
<li><a href=".bash_logout">.bash_logout</a>
<li><a href=".bash_profile">.bash_profile</a>
<li><a href=".bashrc">.bashrc</a>
<li><a href=".lesshst">.lesshst</a>
<li><a href=".mysql_history">.mysql_history</a>
<li><a href=".ssh/">.ssh/</a>
</ul>
<hr>
</body>
</html>

为了证明我们真的在使用我们的代理,让我们尝试curl命令而不建立会话(您将不得不等待 SSH 超时,或者如果进程尚未死亡,则终止该进程):

[vagrant@centos1 ~]$ all_proxy="socks5://127.0.0.1:9999" curl 127.0.0.1:8888
curl: (7) Failed connect to 127.0.0.1:9999; Connection refused

它是如何工作的...

当您在 SSH 中添加-D选项,或者在 SSH 配置文件中添加DynamicForward选项时,您告诉 SSH 您要在本地端口上指定一个端口,该端口将通过 SSH 连接转发收到的任何请求。

让我们分解我们的命令:

[vagrant@centos1 ~]$ ssh -f -D9999 192.168.33.11 sleep 120

首先,就像以前一样,我们使用了-f和 sleep 来保持连接打开,一旦建立连接,我们就会回到centos1提示符:

-f ... sleep 120

我们还指定了我们的-D选项,并选择了一个随机选择的端口:

-D9999

我习惯使用9999,但偶尔也会使用7777,甚至在感觉非常疯狂时使用6666。您可以使用任何您希望的端口(在1024以上,因为低于此值的端口只能由 root 使用)。

一旦我们建立,我们使用以下命令来检查我们的代理是否可用:

[vagrant@centos1 ~]$ all_proxy="socks5://127.0.0.1:9999" curl 127.0.0.1:8888

将此分解为两部分,我们从设置为此运行的变量开始:

all_proxy="socks5://127.0.0.1:9999"

cURL 使用all_proxy来设置其运行的 SOCKS 代理。

在浏览器中,您可能会在设置下找到设置SOCKS服务器的选项,并且在某些其他应用程序中,可以在需要时配置SOCKS代理。Gnome 的网络管理器如下所示:

我们命令的另一部分是curl

curl 127.0.0.1:8888

通过我们的all_proxy设置,cURL 知道要在其连接中使用端口9999上的 SOCKS 代理,这意味着当我们查询127.0.0.1:8888时,我们将通过我们的 SSH 会话发送该请求以在centos2上解析。

整洁!

还有更多...

如果您想进一步进行,可以在远程端使用tcpdump来检查穿过您的网络的流量:

[vagrant@centos2 ~]$ sudo tcpdump port 8888 -ilo -n

您应该看到流量通过:

<SNIP>
15:18:48.991652 IP 127.0.0.1.54454 > 127.0.0.1.ddi-tcp-1: Flags [F.], seq 79, ack 618, win 700, options [nop,nop,TS val 16534669 ecr 16534658], length 0
15:18:48.991677 IP 127.0.0.1.ddi-tcp-1 > 127.0.0.1.54454: Flags [.], ack 80, win 683, options [nop,nop,TS val 16534669 ecr 16534669], length 0
<SNIP>

理解和使用 SSH 代理

我们简要提到的一件事是 SSH 代理的概念。

当您 SSH 到服务器(设置密钥后)并提示输入密码时,您实际上正在解密公私钥对的私钥部分(默认情况下为id_rsa文件),以便用于验证您对远程主机的身份。如果您管理数百或数千个不断变化的服务器,每次 SSH 到服务器时都这样做可能会变得乏味。

这就是 SSH 代理的作用。一旦您给出了密码,它就是您现在解密的私钥的所在地,直到您的会话结束。

一旦您将私钥加载到代理中,代理就负责向您连接的任何服务器呈现密钥,而无需再次输入密码,节省宝贵的时间和手指压力。

大多数桌面 Linux 发行版都会在用户会话中启动某种 SSH 代理,有时在您登录到用户帐户时解锁您的私钥。

macOS 有一个特定的 SSH 配置文件选项UseKeychaindeveloper.apple.com/library/archive/technotes/tn2449/_index.html):

“在 macOS 上,指定系统在尝试使用特定密钥时是否应在用户的钥匙串中搜索密码。当用户提供密码时,此选项还指定密码一经验证正确后是否应存储到钥匙串中。参数必须是“yes”或“no”。默认值为“no”。”

如果您在桌面上运行 macOS,您可能会考虑此选项。

在我的 Ubuntu 笔记本安装中,查找正在运行的代理会显示如下内容:

$ env | grep SSH
SSH_AUTH_SOCK=/run/user/1000/keyring/ssh
SSH_AGENT_PID=1542

查找此进程 ID 会显示我正在运行的ssh-agent

adam 1542 0.0 0.0 11304 320 ? Ss Aug04 0:02 /usr/bin/ssh-agent /usr/bin/im-launch env GNOME_SHELL_SESSION_MODE=ubuntu gnome-session --session=ubuntu

在本节中,我们将在centos1上启动一个 SSH 代理并将密钥加载到其中。

做好准备

与上一节一样,请确认您的两个 Vagrant 框都已启用,并使用vagrant命令连接到第一个:

$ vagrant ssh centos1

确保在centos1上有一个可用的 SSH 密钥。如果需要,请重新阅读上一节有关生成 SSH 密钥的内容:

[vagrant@centos1 ~]$ ls .ssh/
authorized_keys  config  id_ed25519  id_ed25519.pub  known_hosts

如果您尚未将密钥复制到centos2,则需要接受主机密钥:

[vagrant@centos1 ~]$ ssh-copy-id 192.168.33.11
/usr/bin/ssh-copy-id: INFO: Source of key(s) to be installed: "/home/vagrant/.ssh/id_ed25519.pub"
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 1 key(s) remain to be installed -- if you are prompted now it is to install the new keys
vagrant@192.168.33.11's password: 

Number of key(s) added: 1

Now try logging into the machine, with:   "ssh '192.168.33.11'"
and check to make sure that only the key(s) you wanted were added.

[vagrant@centos1 ~]$ 

检查尝试登录到centos2是否提示您输入密钥密码:

[vagrant@centos1 ~]$ ssh 192.168.33.11
Enter passphrase for key '/home/vagrant/.ssh/id_ed25519': 

确保您在centos1上开始。

如何做...

首先运行ssh-agent命令:

[vagrant@centos1 ~]$ ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-9On2mDhHTL8T/agent.6693; export SSH_AUTH_SOCK;
SSH_AGENT_PID=6694; export SSH_AGENT_PID;
echo Agent pid 6694;

您可以看到它打印了几个环境变量和它正在运行的进程 ID。

我们可以确认这是事实:

[vagrant@centos1 ~]$ pidof ssh-agent
6694

复制为你提供的各种变量,并将它们粘贴到同一个窗口中:

[vagrant@centos1 ~]$ SSH_AUTH_SOCK=/tmp/ssh-9On2mDhHTL8T/agent.6693; export SSH_AUTH_SOCK;
[vagrant@centos1 ~]$ SSH_AGENT_PID=6694; export SSH_AGENT_PID;
[vagrant@centos1 ~]$

现在,运行ssh-add命令,并在提示时填写你的密钥密码:

[vagrant@centos1 ~]$ ssh-add
Enter passphrase for /home/vagrant/.ssh/id_ed25519: 
Identity added: /home/vagrant/.ssh/id_ed25519 (vagrant@centos1)
[vagrant@centos1 ~]$ 

你可以看到它通知你已经添加了你的身份。

SSH 到centos2,准备好惊讶,当你不被提示输入你的密码时:

[vagrant@centos1 ~]$ ssh 192.168.33.11 
Last login: Thu Aug 9 15:36:02 2018 from 192.168.33.10
[vagrant@centos2 ~]$ 

你可能认为每天输入一两次密码并不介意,你可能会认为没问题,但如果你很少登录到一台机器,你可能是一个非常幸运的系统管理员。 SSH 代理的优势在于当你想要登录到数十甚至数百台机器时,甚至如果你使用ProxyJump框,并且不想比必要的次数输入你的密码。

要终止运行的代理,使用-k

[vagrant@centos1 ~]$ ssh-agent -k
unset SSH_AUTH_SOCK;
unset SSH_AGENT_PID;
echo Agent pid 6694 killed;

我曾见过一些公司不喜欢使用 SSH 代理,并且要求每次输入密码或密码。检查一下,确保你没有违反某些晦涩的安全策略以便让你的生活更轻松。

然后,运行建议的取消设置命令来删除我们之前设置的变量:

[vagrant@centos1 ~]$ unset SSH_AUTH_SOCK;
[vagrant@centos1 ~]$ unset SSH_AGENT_PID;

简单地注销你的会话不会停止ssh-agent程序的运行。如果你选择使用它,要注意这一点。同样,你不应该在多人共享的远程主机上运行代理-最好保留在你的个人机器上。如果你打算使用 SSH 代理,请阅读当前的安全实践。

它是如何工作的...

当我们最初运行ssh-agent时,代理本身在后台启动,并且我们得到了 SSH 所需的环境变量。设置后,运行 SSH 将导致它读取这些变量。

如果我们在 SSH 中添加了一些-vv标志,我们可以看到它在代理中找到我们的密钥:

debug2: key: /home/vagrant/.ssh/id_ed25519 (0x55b11351c410), agent

没有加载代理,但有密钥存在时,看起来是这样的:

debug2: key: /home/vagrant/.ssh/id_ed25519 (0x55dea5015410)

ssh-add也会读取 SSH 环境变量,我们用它将我们的密钥添加到代理中。引用手册页:

"认证代理必须正在运行,并且 SSH_AUTH_SOCK 环境变量必须包含其套接字的名称,以便 ssh-add 正常工作。"

当你的代理中有一个或多个密钥时,SSH 将尝试使用这些密钥对远程主机进行身份验证,从而无需每次输入密码。

还有更多...

如果你想将代理启动命令添加到脚本(比如.bashrc),你可能希望自动评估给你的环境变量。 ssh-agent假设你是以这种方式启动它的。

ssh-agent的手册页中,你甚至会得到这个提示。

"有两种主要的设置代理的方法:第一种是代理启动一个新的子命令,其中一些环境变量被导出,例如 ssh-agent xterm &。第二种是代理打印所需的 shell 命令(可以生成 sh(1)或 csh(1)语法),可以在调用 shell 中进行评估,例如对于 Bourne 类型的 shell,如 sh(1)或 ksh(1),可以使用 eval 'ssh-agent -s',对于 csh(1)和衍生版本,可以使用 eval 'ssh-agent -c'。"

实际上,这意味着最容易这样启动代理:

[vagrant@centos1 ~]$ eval $(ssh-agent)
Agent pid 6896

在这里,我们使用 Bash 子 shell 来启动和读取代理的输出。

ssh-add

ssh-add有一些不错的选项可用,其中一些是很方便知道的。

-l将允许你查看已加载的身份,以及它们的指纹:

[vagrant@centos1 ~]$ ssh-add -l
256 SHA256:P7FdkmbQQFoy37avbKBfzMpEhVUaBY0TljwYJyNxzUI vagrant@centos1 (ED25519)

-D将允许你删除所有身份(-d可用于删除特定身份):

[vagrant@centos1 ~]$ ssh-add -D
All identities removed.

-x将锁定代理,而-X将解锁代理:

[vagrant@centos1 ~]$ ssh-add -l
256 SHA256:P7FdkmbQQFoy37avbKBfzMpEhVUaBY0TljwYJyNxzUI vagrant@centos1 (ED25519)
[vagrant@centos1 ~]$ ssh-add -x
Enter lock password: 
Again: 
Agent locked.
[vagrant@centos1 ~]$ ssh-add -l
The agent has no identities.
[vagrant@centos1 ~]$ ssh-add -X
Enter lock password: 
Agent unlocked.
[vagrant@centos1 ~]$ ssh-add -l
256 SHA256:P7FdkmbQQFoy37avbKBfzMpEhVUaBY0TljwYJyNxzUI vagrant@centos1 (ED25519)

AddKeysToAgent

当使用代理时,你可能会喜欢 SSH 配置文件选项AddKeysToAgent,它将自动将使用的密钥添加到你的ssh-agent中以供将来使用。

考虑以下;我们从没有密钥的代理开始:

[vagrant@centos1 ~]$ ssh CentOS2-V4
Enter passphrase for key '/home/vagrant/.ssh/id_ed25519': 
Last login: Thu Aug  9 15:58:01 2018 from 192.168.33.10
[vagrant@centos2 ~]$ logout
Connection to 192.168.33.11 closed.
[vagrant@centos1 ~]$ ssh CentOS2-V4
Last login: Thu Aug  9 16:12:04 2018 from 192.168.33.10
[vagrant@centos2 ~]$ 

请注意,第一次,我们被提示输入我们的密钥密码。第二次,我们没有被提示。

现在它已经加载到我们的代理中:

[vagrant@centos1 ~]$ ssh-add -l
256 SHA256:P7FdkmbQQFoy37avbKBfzMpEhVUaBY0TljwYJyNxzUI vagrant@centos1 (ED25519)

这一切都由一个配置选项处理:

[vagrant@centos1 ~]$ cat .ssh/config 
Host *
 Port 22
 AddKeysToAgent yes

另请参阅

除了 OpenSSH 提供的默认 SSH 代理之外,还有其他 SSH 代理(我们在这里使用了)。还有一些系统使用更多的组件(例如大多数桌面发行版上的 PAM)。四处阅读,看看你是否能弄清楚你选择的发行版是如何做事的。

在一台主机上运行多个 SSH 服务器

有时,在一台主机上运行多个 SSH 服务器是一个要求。您可能希望使用一个进行常规的日常活动,另一个用于备份或自动化。

在这种情况下,同时运行两个不同版本的 SSH 服务器是完全可能的。

我们将使用centos2,在端口2020上设置一个辅助 SSH 服务器。

准备工作

如果您还没有这样做,我建议销毁之前的 Vagrant 盒子,并为此部署新的盒子。

创建新的盒子后,连接到两个:

$ vagrant ssh centos1
$ vagrant ssh centos2

centos2上安装policycoreutils-python,以备稍后使用semanage

[vagrant@centos2 ~]$ sudo yum -y install policycoreutils-python

如何做...

首先,我们要复制我们的初始配置文件:

[vagrant@centos2 ~]$ sudo cp /etc/ssh/sshd_config /etc/ssh/sshd_config_2020

然后,我们要做一些改变:

[vagrant@centos2 ~]$ sudo sed -i 's#\#Port 22#Port 2020#g' /etc/ssh/sshd_config_2020
[vagrant@centos2 ~]$ sudo sed -i 's#\#PidFile /var/run/sshd.pid#PidFile /var/run/sshd_2020.pid#g' /etc/ssh/sshd_config_2020

现在,我们要复制我们的systemd单元文件:

[vagrant@centos2 ~]$ sudo cp /usr/lib/systemd/system/sshd.service  /etc/systemd/system/sshd_2020.service

然后,我们要在这里做一些更改:

[vagrant@centos2 ~]$ sudo sed -i 's#OpenSSH server daemon#OpenSSH server daemon on port 2020#g' /etc/systemd/system/sshd_2020.service
[vagrant@centos2 ~]$ sudo sed -i 's#EnvironmentFile=/etc/sysconfig/sshd#EnvironmentFile=/etc/sysconfig/sshd_2020#g' /etc/systemd/system/sshd_2020.service

将旧的环境文件复制到新文件:

[vagrant@centos2 ~]$ sudo cp /etc/sysconfig/sshd /etc/sysconfig/sshd_2020

然后,将这个环境文件指向我们的新配置文件:

[vagrant@centos2 ~]$ sudo sed -i 's#OPTIONS="-u0"#OPTIONS="-u0 -f /etc/ssh/sshd_config_2020"#g' /etc/sysconfig/sshd_2020

告诉 SELinux 我们将在2020上运行 SSH 守护程序:

[vagrant@centos2 ~]$ sudo semanage port -a -t ssh_port_t -p tcp 2020

告诉systemd我们已经做出了更改:

[vagrant@centos2 ~]$ sudo systemctl daemon-reload 

启动并启用我们的第二个服务器:

[vagrant@centos2 ~]$ sudo systemctl enable sshd_2020
Created symlink from /etc/systemd/system/multi-user.target.wants/sshd_2020.service to /etc/systemd/system/sshd_2020.service.
[vagrant@centos2 ~]$ sudo systemctl start sshd_2020

通过从centos1进行 SSH 连接来检查它是否正在运行:

[vagrant@centos1 ~]$ ssh 192.168.33.11
The authenticity of host '192.168.33.11 (192.168.33.11)' can't be established.
ECDSA key fingerprint is SHA256:I67oI3+08lhdO2ibnoC+z2hzYtvfi9NQAmGxyzxjsI8.
ECDSA key fingerprint is MD5:03:68:ed:a2:b5:5d:57:88:61:4e:86:28:c3:75:28:fa.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.33.11' (ECDSA) to the list of known hosts.
vagrant@192.168.33.11's password: 
Last login: Thu Aug  9 16:24:50 2018 from 10.0.2.2
[vagrant@centos2 ~]$ logout
Connection to 192.168.33.11 closed.
[vagrant@centos1 ~]$ ssh 192.168.33.11 -p2020
vagrant@192.168.33.11's password: 
Last login: Thu Aug  9 16:40:55 2018 from 192.168.33.10
[vagrant@centos2 ~]$ 

还记得我们之前看主机密钥时吗?在前面的代码中,我们可以看到端口22服务器和2020服务器共享主机密钥,因为我们只被要求接受一次。

它是如何工作的...

我们所做的一切只是复制了一些文件,并做了一些明智的更改,以确保这两个进程之间的交互不会太多。

首先,我们创建了以下文件:

/etc/ssh/sshd_config_2020

然后,我们运行了一些sed命令来更改一些值。具体来说,我们修改了服务器监听的端口,以及它将使用的进程 ID 文件(PID 修改经常被忽视)。

接下来,我们复制了 CentOS OpenSSH 服务器软件包中提供的systemd单元文件,并稍微调整了它,改变了描述并将其指向不同的环境文件。

我们将生成的单元文件(sshd_2020.service)放在与原始文件不同的位置,以使其与提供的默认文件区分开来。

我们复制了环境文件,并对其进行了修改,以便在启动 SSH 守护程序时传递一个新选项。这个新选项是一个不同的配置文件(我们开始制作的那个):

OPTIONS="-u0 -f /etc/ssh/sshd_config_2020"

然后,我们更新了 SELinux 策略,使其了解新服务器的意图,重新加载了 systemd 的运行配置,并启用并启动了我们的服务器。

在配置和环境文件的标准位置方面,可能会有所不同。这可能会在主要的发行版发布之间发生变化,并且一些设置在不同的发行版之间经常不同。

还有更多...

如果您有兴趣看到两个服务器在运行,有几种方法可以做到这一点。

centos2上,从ss开始:

[vagrant@centos2 ~]$ sudo ss -tna -4
State       Recv-Q Send-Q     Local Address:Port                    Peer Address:Port 
LISTEN      0      128                    *:2020                               *:* 
LISTEN      0      128                    *:111                                *:* 
LISTEN      0      128                    *:22                                 *:* 
LISTEN      0      100            127.0.0.1:25                                 *:* 
ESTAB       0      0              10.0.2.15:22                          10.0.2.2:59594 

我们还可以使用 systemd 的内置命令:

[vagrant@centos2 ~]$ PAGER= systemctl | grep sshd
 sshd.service                                                                             loaded active running   OpenSSH server daemon
 sshd_2020.service                                                                        loaded active running   OpenSSH server daemon on port 2020

最后,我们可以使用老式的ps

[vagrant@centos2 ~]$ ps aux | grep sshd
root       856  0.0  0.8 112796  4288 ?        Ss   16:52   0:00 /usr/sbin/sshd -D -u0 -f /etc/ssh/sshd_config_2020
root       858  0.0  0.8 112796  4292 ?        Ss   16:52   0:00 /usr/sbin/sshd -D -u0

总结

虽然我在本章中描述了 SSH 的一些出色功能,并一直在赞扬它,但值得注意的是,它仍然是软件,并且不断在发展。因为它是软件,它可能会有错误和意外行为,尽管背后的开发人员是最好的,因为它是 OpenBSD 软件套件的一部分。

如果你从本章中学到了什么,那就是:

  • 使用基于密钥的身份验证

  • 禁用 SSH 上的 root 登录

  • 使用本地 SSH 配置文件连接到远程机器

如果你像我一样有点悲伤,我强烈建议你注册各种 SSH 邮件列表,并密切关注可能吸引你注意的新功能。ProxyJump还没有出现很久,但非常方便。

我确实记得有时 SSH 以某种形式或其他让我困扰,比如有一次我花了很长时间在桌子上砸头,试图弄清楚为什么 SSH 就是无法读取私有 RSA 文件,最后才发现它需要公钥也在本地机器的同一个文件夹中。这是我不会再浪费的相当长的一段时间,但这是一个我不会再犯的错误。

也就是说,我还可以分享更多 SSH 让我印象深刻,让我的生活更轻松的例子。它基本上是系统管理的瑞士军刀,不仅仅是因为它通常是连接到设备的方式。

人们使用 SSH 进行管理,传输备份,在不同设备之间移动文件,使用诸如 Ansible 之类的工具进行自动化,将其他连接包装在其中,以及更多其他用途。

我曾经看到过在 Windows 上实现 OpenSSH,因为运行 Windows 服务器的人是 Unix 人,不信任远程桌面协议(RDP)。他们习惯于通过 SSH 连接到设备,本地转发 RDP 会话到127.0.0.1:3389,然后通过 SSH 会话连接到 RDP……速度很慢……

它稳固、安全,使用起来很愉快。它适用于 Linux、macOS、BSD、Solaris,甚至 Windows!

在此向 SSH 和特别是 OpenSSH 表示衷心的感谢。

本章中我们没有讨论的一些事情包括密码、消息完整性代码、密钥交换算法等。主要是因为这些主题本身几乎就是一本书,而且绝对超出了我们在这里所做的范围。我通常相信各种软件包的维护者会选择明智的默认设置,但如果你感到有必要,独立阅读有关安全性的内容也无妨。

第三章:网络和防火墙

本章将涵盖以下配方:

  • 确定我们的网络配置

  • 使用 IP 套件的更多示例

  • 添加和配置网络接口

  • 在 Linux 上进行现代域名解析

  • 配置 NTP 以及我们面临的问题

  • 在命令行上列出防火墙规则

  • 在命令行上添加防火墙规则

  • 确定正在运行的服务和使用的端口

  • 使用iftop进行调试

  • 总结-防火墙和网络

介绍

现在我们已经了解了 SSH 和连接到我们的机器的方方面面,无论是本地虚拟机还是远程服务器,我们将着眼于安全洋葱(防火墙)和 Linux 网络堆栈的最显而易见的部分。

传统上,防火墙管理是通过iptables和相关命令处理的,事实上,您很难找到不立即在每台服务器上安装iptables工具套件的系统管理员(如果尚未安装)。

在现代的安装中,firewall-cmd(CentOS)和ufw(Ubuntu)可以替代或补充传统工具。这些都是为了使防火墙过程更加轻松,尽管纯粹主义者会誓死捍卫iptables

无论使用的用户空间工具(在内核之外,在用户级别运行的程序的名称)是什么,我们所做的所有更改都是使用 Netfilter 内核框架进行的。事实上,内核中已经有一段时间可用的nftiptables和 Netfilter 的替代品,尽管目前并不广泛使用。

iptables在某种程度上是一个笼统的名称,至少在非正式场合是如此。从技术上讲,iptables用于 IPv4,ip6tables 用于 IPv6,ebtables 用于以太网帧,arptables 用于 ARP。

在我们踏上令人困惑的防火墙世界的旅程时,我们将创建一个网络接口,并使用静态和动态 IP 分配进行配置。

技术要求

我们将再次使用 Vagrant 和 VirtualBox 进行工作。我们将配置三台虚拟机。

我已经为本章准备了以下Vagrantfile

# -*- mode: ruby -*-
# vi: set ft=ruby :

$provisionScript = <<-SCRIPT
sed -i 's#PasswordAuthentication no#PasswordAuthentication yes#g' /etc/ssh/sshd_config
systemctl restart sshd
SCRIPT

Vagrant.configure("2") do |config|

 config.vm.provision "shell",
 inline: $provisionScript

 config.vm.define "centos1" do |centos1|
   centos1.vm.box = "centos/7"
   centos1.vm.network "private_network", ip: "192.168.33.10"
   centos1.vm.network "private_network", ip: "192.168.44.10", auto_config: false
   centos1.vm.hostname = "centos1"
   centos1.vm.box_version = "1804.02"
 end

 config.vm.define "centos2" do |centos2|
   centos2.vm.box = "centos/7"
   centos2.vm.network "private_network", ip: "192.168.33.11"
   centos2.vm.network "private_network", ip: "192.168.44.11", auto_config: false
   centos2.vm.hostname = "centos2"
   centos2.vm.box_version = "1804.02"
 end

 config.vm.define "ubuntu1" do |ubuntu1|
   ubuntu1.vm.box = "ubuntu/bionic64"
   ubuntu1.vm.hostname = "ubuntu1"
   ubuntu1.vm.box_version = "20180927.0.0"
 end

end

建议创建一个名为“第三章”的文件夹,并将此代码复制到名为Vagrantfile的文件中。从包含您的Vagrantfile的文件夹内运行vagrant up应该可以配置两个用于测试的虚拟机。一旦配置完成,请确保可以通过运行以下命令连接到第一个虚拟机:

$ vagrant ssh centos1

对于本节,请确保您的centos1虚拟机正在运行,并连接到它。本节假定您在基本水平上了解网络,即您了解静态和动态 IP 地址之间的区别,并且大致了解公共和私有 IP 地址之间的区别。

确定我们的网络配置

如果您发现自己在数据中心使用物理设备,可能会在某个时候坐在一个箱子前,只有用户名和密码。您将不得不使用这些凭据来发现箱子的 IP 信息,以便遥远的象牙塔中的另一个人可以远程连接到它。

象牙塔中的人有时会因为整天坐在椅子上按键盘而得到比您更好的报酬。我的建议是不要纠结于此,否则您会变得愤世嫉俗。

准备工作

如果您尚未连接到centos1,请连接。

$ vagrant ssh centos1

为了演示目的,确保也安装了ifconfig

$ sudo yum install net-tools

如何做到...

在这个简短的部分中,我们将发现我们的箱子有什么 IP 地址,它所在的网络是什么,以及我们将学习它的配置方式。

辨别 IP

在现代的系统上,这是通过ip完成的,我们稍后会更深入地讨论:

$ ip address show

您可以缩短ip命令以使其更快地输入(就像我们在前一章中所做的那样),但在前面的代码中,我使用了完整的表达,因为它更好地表达了我们正在做的事情。

这个命令将为您提供有关系统上所有接口的信息。在我们的虚拟机的情况下,它看起来像这样:

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
 link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
 inet 127.0.0.1/8 scope host lo
 valid_lft forever preferred_lft forever
 inet6 ::1/128 scope host 
 valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
 link/ether 52:54:00:c9:c7:04 brd ff:ff:ff:ff:ff:ff
 inet 10.0.2.15/24 brd 10.0.2.255 scope global noprefixroute dynamic eth0
 valid_lft 85733sec preferred_lft 85733sec
 inet6 fe80::5054:ff:fec9:c704/64 scope link 
 valid_lft forever preferred_lft forever
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
 link/ether 08:00:27:4b:03:de brd ff:ff:ff:ff:ff:ff
 inet 192.168.33.10/24 brd 192.168.33.255 scope global noprefixroute eth1
 valid_lft forever preferred_lft forever
 inet6 fe80::a00:27ff:fe4b:3de/64 scope link 
 valid_lft forever preferred_lft forever
4: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
 link/ether 08:00:27:50:a5:cb brd ff:ff:ff:ff:ff:ff

我们知道lo是环回地址,它将始终具有127.0.0.1/8设置,或者在该范围内的其他地址。

如果您明确知道要使用的设备,也可以指定它。在下面的代码中,我们已经使用了eth1

$ ip address show dev eth1

在打印的块中,我们正在寻找 IPv4 地址,该地址被列为inet系列:

3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:0d:d9:0c brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.10/24 brd 192.168.33.255 scope global noprefixroute eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe0d:d90c/64 scope link 
       valid_lft forever preferred_lft forever

我们有 IP192.168.33.10,并且我们知道它所在的子网是/24(255.255.255.0)

如果您有点懒惰并且想节省眼睛移动,您可以在脚本中使用类似以下内容来获取这些信息:

$ ip address show dev eth1 | grep "inet " | awk '{ print $2 }'
192.168.33.10/24

前面的代码是许多许多种编写一行代码以生成所需输出的方式之一。Linux 是灵活的,您可能选择以完全不同的方式达到相同的输出。您如何做并不是很重要,但如果您打算分享,以简洁和可读的风格进行通常是最好的。

辨别 IP(已弃用的方法)

在旧系统或那些由守旧管理员管理的系统中,您可能还会使用ifconfig来查找 IP 和子网。

简单运行ifconfig将打印所有相关信息:

$ ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
 inet 10.0.2.15 netmask 255.255.255.0 broadcast 10.0.2.255
 inet6 fe80::5054:ff:fec9:c704 prefixlen 64 scopeid 0x20<link>
 ether 52:54:00:c9:c7:04 txqueuelen 1000 (Ethernet)
 RX packets 14404 bytes 12885029 (12.2 MiB)
 RX errors 0 dropped 0 overruns 0 frame 0
 TX packets 5672 bytes 409079 (399.4 KiB)
 TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
 inet 192.168.33.10 netmask 255.255.255.0 broadcast 192.168.33.255
 inet6 fe80::a00:27ff:fe4b:3de prefixlen 64 scopeid 0x20<link>
 ether 08:00:27:4b:03:de txqueuelen 1000 (Ethernet)
 RX packets 8 bytes 3164 (3.0 KiB)
 RX errors 0 dropped 0 overruns 0 frame 0
 TX packets 17 bytes 1906 (1.8 KiB)
 TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

eth2: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
 ether 08:00:27:50:a5:cb txqueuelen 1000 (Ethernet)
 RX packets 43 bytes 14706 (14.3 KiB)
 RX errors 0 dropped 0 overruns 0 frame 0
 TX packets 112 bytes 19336 (18.8 KiB)
 TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
 inet 127.0.0.1 netmask 255.0.0.0
 inet6 ::1 prefixlen 128 scopeid 0x10<host>
 loop txqueuelen 1000 (Local Loopback)
 RX packets 0 bytes 0 (0.0 B)
 RX errors 0 dropped 0 overruns 0 frame 0
 TX packets 0 bytes 0 (0.0 B)
 TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

与 IP 一样,轻松地转储有关特定接口的信息。

正如我们在下面的代码中所看到的,我们再次查看了eth1的具体情况:

$ ifconfig eth1
eth1: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
 inet 192.168.33.10  netmask 255.255.255.0  broadcast 192.168.33.255
 inet6 fe80::a00:27ff:fe0d:d90c  prefixlen 64  scopeid 0x20<link>
 ether 08:00:27:0d:d9:0c  txqueuelen 1000  (Ethernet)
 RX packets 24  bytes 4268 (4.1 KiB)
 RX errors 0  dropped 0  overruns 0  frame 0
 TX packets 20  bytes 2116 (2.0 KiB)
 TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

我用“deprecated”这个词,但这通常只适用于 Linux 世界。在 BSD 和 macOS 中,ifconfig仍然是唯一的选择,并且仍在积极改进和维护。

辨别网关地址

一旦我们有了设备的 IP 地址和子网,我们可能想知道我们的设备是使用哪个设备与外部世界进行通信的。

这是流量离开您的设备的“默认”路由。

使用ip命令,这很容易发现:

$ ip route 
default via 10.0.2.2 dev eth0 proto dhcp metric 102 
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 102 
192.168.33.0/24 dev eth1 proto kernel scope link src 192.168.33.10 metric 100 

注意default via部分。

辨别网关地址(已弃用的方法)

安装了 net-tools 后,我们还可以使用route命令来找到我们的默认路由。

$ route
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
default         gateway         0.0.0.0         UG    102    0        0 eth0
10.0.2.0        0.0.0.0         255.255.255.0   U     102    0        0 eth0
192.168.33.0    0.0.0.0         255.255.255.0   U     100    0        0 eth1

工作原理...

当您使用ipifconfig时,您正在查询内核所知道的网络设备,以及它当前正在管理的流量。

如果您在任何常见命令的输出列表中都看不到您的网络设备,则可能是您的内核对该设备没有驱动程序。这在今天很少见,但对于定制的网络设备可能会发生,此时供应商应提供用于其的内核模块。

ip本身不仅仅是一个命令:它是一个套件,这意味着当我们在这里使用ip address命令时,我们只是使用了我们可以使用的工具的一个子集。

当您使用ip routeroute命令时,实际上是在查询分发的路由表。

还有更多...

了解网络配置的基础知识是很好的,但通常只是第一步。

在这里,我将介绍一些基本的网络故障排除。

检查连接

知道我们的设备正在使用的网关是一回事,但实际上能够到达它是另一回事。

如果您站在一个设备前面,很可能您已被召唤来尝试弄清楚哪里出了问题。尝试 ping 网关是一个很好的第一步:

$ ping 10.0.2.2
PING 10.0.2.2 (10.0.2.2) 56(84) bytes of data.
64 bytes from 10.0.2.2: icmp_seq=1 ttl=64 time=0.768 ms
64 bytes from 10.0.2.2: icmp_seq=2 ttl=64 time=0.333 ms
64 bytes from 10.0.2.2: icmp_seq=3 ttl=64 time=0.637 ms

在这里,我们的网关设备做出了响应,但如果您的设备没有响应,或者出现“目标主机不可达”的情况,那么很可能出现了问题(或者您的同事在象牙塔中给出了错误的信息-始终首先检查这一点)。

在下面的代码中,我们再次使用ip命令来检查接口本身的状态:

$ ip address show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000

您可能会对我们这里有三个UP值感兴趣。

尖括号内的初始UP值表示接口是否物理上启动。尖括号内的LOWER_UP值也表示如果电缆物理连接并启动。尖括号外的state UP值表示接口是否在管理上启动,可以通过软件更改为DOWN

检查我们的设备将采取哪条路线

也有可能连接失败是因为您的设备尝试通过错误的接口进行通信。我们可以再次使用ip来检查这一点。

首先,让我们看看当与更广泛的世界通信时,我们将采取哪条路线。

在下面的代码中,我们正在检查我们的设备如何尝试与 IP1.1.1.1进行通信。它向我们显示,我们将通过网关地址10.0.2.2和我们的eth0接口去到这个 IP:

$ ip route get 1.1.1.1
1.1.1.1 via 10.0.2.2 dev eth0 src 10.0.2.15 
 cache 

现在,让我们看看如果我们与网关通信会走哪条路线。在这里,我们可以看到一个直接连接(没有via)也从eth0出去:

$ ip route get 10.0.2.2
10.0.2.2 dev eth0 src 10.0.2.15 
 cache 

到目前为止,一切都很顺利,但是如果我们想与我们私有网络中的其他 VM 进行通信怎么办?

$ ip route get 192.168.33.11
192.168.33.11 dev eth1 src 192.168.33.10 
 cache 

eth1将被使用,这是非常合理的,因为它在同一个网络上。

等一下——我们自己的 IP 也在这个子网中,那么如果我们尝试与自己通信会发生什么?

$ ip route get 192.168.33.10
local 192.168.33.10 dev lo src 192.168.33.10 
 cache <local> 

啊!聪明!我们仍然使用回环地址(dev lo)与我们的本地 IP 地址进行通信,因为 Linux 知道这个 IP 是我们自己的。

与前面的例子一样,Linux 有时会做一些让您感到惊讶的事情,特别是如果您来自 Windows 或 BSD 管理世界。以lo接口为例——它不仅仅是127.0.0.1,而是整个/8范围。在典型的 Linux 系统上,您可以从127.0.0.1 ping 到127.255.255.254,并从本地机器得到响应。

您甚至可以 ping127.1并获得合法的响应,但我会把它留给读者来确定为什么可能会这样。

另请参阅

查看ipip-address手册页面。

就个人而言,我会专注于学习ip套件,而不是旧的 net-tools,因为如果您尝试解决网络问题,很可能无法在紧要关头安装 net-tools。

更多使用ip 套件的示例

我们将从现在开始使用ip,因为它更现代化。

本节假定您在基本水平上了解网络,即您了解静态和动态 IP 地址之间的区别,并且大致了解公共和私有 IP 地址之间的区别。

准备工作

在本节中,我们将对我们的网络进行一些更改。如果您在任何时候发现自己被踢出 VM,请记住《银河系漫游指南》,不要惊慌——只需销毁您的 VM 并重新开始。

centos1命令行开始。

$ vagrant ssh centos1

如何做...

在本节中,我们将使用ip 套件来更改我们网络堆栈的一些基本元素,例如添加另一个 IP 地址,或者完全关闭一个接口。

添加和删除 IP

这可以通过ip address命令来实现,但需要 root 权限。

在这个例子中,我选择了192.168.33.0/24子网中的另一个 IP,我知道这个 IP 没有被使用:

在现实世界中,最好先 ping 一下 IP,即使您认为它是空闲的。

$ sudo ip address add 192.168.33.22/24 dev eth1

检查我们的eth1接口,我们现在可以看到第二个 IP:

3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
    link/ether 08:00:27:0d:d9:0c brd ff:ff:ff:ff:ff:ff
    inet 192.168.33.10/24 brd 192.168.33.255 scope global noprefixroute eth1
       valid_lft forever preferred_lft forever
 inet 192.168.33.22/24 scope global secondary eth1
       valid_lft forever preferred_lft forever
    inet6 fe80::a00:27ff:fe0d:d90c/64 scope link 
       valid_lft forever preferred_lft forever

重新启动后,这些更改将丢失。我们将在标题为“添加和配置网络接口”的部分中讨论如何永久添加 IP 信息。

如果我们想删除一个 IP,我们再次使用ip,这次用del指令替换add指令:

$ sudo ip address del 192.168.33.22/24 dev eth1

关闭和启动接口

要处理接口本身,我们需要使用ip套件的另一个元素,即ip link。再次注意使用sudo

$ sudo ip link set eth1 down

检查我们的接口,我们可以看到接口处于DOWN状态:

3: eth1: <BROADCAST,MULTICAST> mtu 1500 qdisc pfifo_fast state DOWN group default qlen 1000
 link/ether 08:00:27:0d:d9:0c brd ff:ff:ff:ff:ff:ff
 inet 192.168.33.10/24 brd 192.168.33.255 scope global noprefixroute eth1
 valid_lft forever preferred_lft forever

您会发现自己无法从centos2 ping 通这个主机。

要重新启动接口,只需运行以下命令:

$ sudo ip link set eth1 up

请注意,如果在关闭和打开接口之前临时向接口添加另一个 IP,您的临时附加 IP 将不会被记住。

向我们的路由表添加新路由

如果我们在一个目的地不明显的网络中工作,我们可能需要向我们的设置中添加一个特定的路由。

这通常出现在集群场景和设置中,其中一个路由可以通过另一个通道访问,但可能不会由同一通道广告。

在接下来的代码中,我们将告诉我们的虚拟机,如果它想要与172.16.0.0/12网络上的任何地址通信,必须通过eth1和网关192.168.33.11(这是我们的centos2虚拟机)发送流量:

$ sudo ip route add 172.16.0.0/12 via 192.168.33.11 dev eth1

检查我们的路由表,我们可以看到我们的路由是否已经设置好:

$ ip route
default via 10.0.2.2 dev eth0 proto dhcp metric 100 
10.0.2.0/24 dev eth0 proto kernel scope link src 10.0.2.15 metric 100 
172.16.0.0/12 via 192.168.33.11 dev eth1 
192.168.33.0/24 dev eth1 proto kernel scope link src 192.168.33.10 metric 101

工作原理...

同样,我们使用ip suite与我们机器的各种网络元素进行交互。通常,这些命令可用于故障排除和测试环境更改,以便在您永久进行这些更改之前进行测试。

在添加或删除接口的 IP 地址时,您可以创建一个接口具有多个 IP 地址的情况,用于不同的应用程序。

在 SNI 出现之前,看到在 Web 服务器上使用多个 IP 地址以便每个 HTTPS 站点都可以有自己的 IP 分配是很常见的。

在关闭或启动网络接口时,我们首先告诉系统在管理上关闭一个接口,而不是物理上拔掉以太网电缆。如果你关闭一个接口,然后检查你的/var/log/messages文件,你应该会看到类似以下的内容:

Aug 12 12:38:09 centos1 NetworkManager[566]: <info>  [1534077489.1507] device (eth1): state change: activated -> unavailable (reason 'carrier-changed', sys-iface-state: 'managed')
Aug 12 12:38:09 centos1 dbus[545]: [system] Activating via systemd: service name='org.freedesktop.nm_dispatcher' unit='dbus-org.freedesktop.nm-dispatcher.service'
Aug 12 12:38:09 centos1 systemd: Starting Network Manager Script Dispatcher Service...
Aug 12 12:38:09 centos1 dbus[545]: [system] Successfully activated service 'org.freedesktop.nm_dispatcher'
Aug 12 12:38:09 centos1 systemd: Started Network Manager Script Dispatcher Service.
Aug 12 12:38:09 centos1 nm-dispatcher: req:1 'down' [eth1]: new request (3 scripts)
Aug 12 12:38:09 centos1 nm-dispatcher: req:1 'down' [eth1]: start running ordered scripts...

在这里,我们可以看到网络管理器通过一系列步骤运行,意识到设备的状态已经改变。

网络管理器分发器是控制接口关闭或启动时发生的事情的服务。如果你有兴趣了解更多,请查看/etc/NetworkManager/dispatcher.d/

最后,当我们向我们的路由表添加路由时,我们告诉内核的底层路由子系统通过我们的eth1接口,使用网关地址192.168.33.11发送网络172.16.0.0/12的数据包。

我经常发现自己修改设备的路由表,尽管大部分是为电话 PBX 环境中的集群设置。这可能不是你每天都做的事情,但值得知道!

还有更多...

如果你想查看到达centos2的路由流量,请登录到你的第二个虚拟机,确保安装了tcpdump

$ sudo yum install -y tcpdump

接下来,确保你之前的额外路由在centos1上已经设置好,并开始对范围内的任何地址进行ping

$ ping 172.16.0.3
PING 172.16.0.3 (172.16.0.3) 56(84) bytes of data.

回到centos2,对我们的eth1接口启动tcpdump

$ sudo tcpdump -i eth1

你应该开始看到来自centos1ICMP echo requests,但没有响应:

12:48:26.735055 IP 192.168.33.10 > 172.16.0.3: ICMP echo request, id 1696, seq 1, length 64
12:48:27.736195 IP 192.168.33.10 > 172.16.0.3: ICMP echo request, id 1696, seq 2, length 64
12:48:28.738030 IP 192.168.33.10 > 172.16.0.3: ICMP echo request, id 1696, seq 3, length 64
12:48:29.743270 IP 192.168.33.10 > 172.16.0.3: ICMP echo request, id 1696, seq 4, length 64
12:48:30.747098 IP 192.168.33.10 > 172.16.0.3: ICMP echo request, id 1696, seq 5, length 64
12:48:31.750916 IP 192.168.33.10 > 172.16.0.3: ICMP echo request, id 1696, seq 6, length 64
12:48:32.752634 IP 192.168.33.10 > 172.16.0.3: ICMP echo request, id 1696, seq 7, length 64

回到centos1,停止你的ping(*Ctrl *+ C),注意传输的数据包和丢失的数据包:

--- 172.16.0.3 ping statistics ---
8 packets transmitted, 0 received, 100% packet loss, time 7019ms

因为centos2没有路由我们的数据包的地方,而且centos2实际上并没有设置为路由器,数据包只是无人回应,centos1仍然是孤独的-可怜的centos1

另请参阅

如果你有兴趣将 Linux 用作路由器,这是完全可能的,只需通过 sysctl 更改和可能在防火墙上进行一些伪装。虽然超出了本书的范围,但这是你可能在未来需要的东西。

添加和配置网络接口

在本节中,我们将看看如何在系统中配置多个网络接口,并讨论这在现实世界中可能如何被利用(比如用于备份流量)。

准备工作

我们将使用我们本章新引入的第二个网络。如果你使用的是之前的Vagrantfile,你已经设置好了这个网络。如果你在自己的系统上运行,请在每个虚拟机上添加另一个 NIC 到同一网络。

使用 Vagrant 连接到centos1

$ vagrant ssh centos1

检查eth2接口是否对您可用。 它应该看起来类似于以下内容:

$ ip link show eth2
4: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT group default qlen 1000
 link/ether 08:00:27:7d:f3:6b brd ff:ff:ff:ff:ff:ff

如何做...

我们将看看如何使用配置文件永久添加网络信息,以便我们的配置在重新启动后不会丢失(如果我们使用ip suite和临时分配,情况将是这样)。

配置新接口

首先,为eth2创建一个配置文件:

$ sudo touch /etc/sysconfig/network-scripts/ifcfg-eth2

接下来,使用以下内容填充它:

$ sudo tee /etc/sysconfig/network-scripts/ifcfg-eth2 << HERE 
BOOTPROTO=none
ONBOOT=yes
IPADDR=192.168.44.10
NETMASK=255.255.255.0
DEVICE=eth2
PEERDNS=no
HERE

这里使用了tee命令。 它使从标准输入读取并输出到我们选择的目的地,即文件。

现在,将您的接口作为个体重新启动:

$ sudo ifdown eth2
$ sudo ifup eth2

或者,重新启动网络:

$ sudo systemctl restart network

通常,一个很好的经验法则是在处理组成更大整体的组件时尽量减少干扰。 即使在dev环境中,养成这样的习惯也是一个好习惯。

它的工作原理...

我们在这里所做的一切就是创建一个接口脚本,您的系统可以使用它在启动时正确配置接口。

让我们看看我们添加到文件中的选项。

BOOTPROTO=none

此设置代表启动时协议,我们将其设置为none,而不是dhcpbootp

在 DHCP 环境中,您希望地址由 DHCP 服务器自动分配。 在这里,我们正在设置一个静态地址,因此我们陈述如下。

ONBOOT=yes

这个可能看起来很明显,但接口的默认行为是在启动时不初始化。 通过此设置,我们确保网络接口随系统一起启动。

IPADDR=192.168.44.10
NETMASK=255.255.255.0

有点不言自明,但这些选项是我们正在配置的网络的 IP 地址和子网掩码。 在这里,我选择了另一个/24网络,比我们的eth1配置高了几个八位。

DEVICE=eth2

此选项用于指定我们的配置将应用于哪个硬件接口。 值得注意的是,这些接口名称既可以是通用的(eth0eth1等),也可以是网络卡名称特定的(它们可能不总是eth<something>)。

PEERDNS=no

在 DHCP 环境中默认为yes,此选项确保我们的系统在接口启动时不会尝试修改/etc/resolv.conf

还有更多...

以太网接口配置文件(ifcfg-eth2)还有一些其他选项,可以在设置网络时考虑。 在将网络包装在任何脚本之前,检查是否可能对所需的结果进行配置更改。

接口绑定也可以在接口配置文件中创建和配置,用于当一台机器有两个物理连接到同一目的地时,以保护接口或电缆的任一故障。

在我整个职业生涯中,我只见过一根坏的Cat-5e电缆——电缆是物理的,不会自发断裂。

我们没有做的一件事是指定所讨论的接口不是由网络管理器(网络管理守护程序)管理的。

您可以使用简单的nmcli device命令查看由网络管理器管理和不管理的接口:

$ nmcli device 
DEVICE  TYPE      STATE      CONNECTION 
eth0    ethernet  connected  System eth0 
eth1    ethernet  connected  System eth1 
eth2    ethernet  connected  System eth2 
lo      loopback  unmanaged  --  

我们可以更改这一点,如果我们宁愿网络管理器不干涉,可以通过向我们的ifcfg-eth2文件添加配置选项来实现:

BOOTPROTO=none
ONBOOT=yes
IPADDR=192.168.44.10
NETMASK=255.255.255.0
DEVICE=eth2
PEERDNS=no
NM_CONTROLLED=no

现在,再次启动和关闭您的接口:

$ sudo ifdown eth2
$ sudo ifup eth2

然后,再次检查nmcli,这次注意unmanaged状态:

$ nmcli device 
DEVICE  TYPE      STATE      CONNECTION 
eth0    ethernet  connected  System eth0 
eth1    ethernet  connected  System eth1 
eth2    ethernet  unmanaged  -- 
lo      loopback  unmanaged  --

如果您感兴趣,可以查看nmtui命令,以交互方式配置网络接口,网络管理器知道这些接口。

另请参阅

考虑一下在哪里可能需要额外的网络。 一个很好且常见的例子是备份网络,公司将设置一个专门用于备份流量的专用网络。

备份的大小不等,但有时可能会达到几千兆字节甚至几千兆字节,因此最好为它们提供一个完全独立的网络。

您还可以使用诸如服务质量QoS)设置之类的东西来管理流量优先级,这在更大型和企业网络上可能是一个有效的解决方案。如果您宁愿处理这些情况的软件管理开销,这可能是一个有效的解决方案。一般来说,我更喜欢简单,尽管拥有物理设备的成本肯定更高,但从长远来看,管理起来更少。

Linux 上的现代域名解析

在本节中,我们将查看域名解析,特别是在典型的 CentOS 盒子上安装的软件,该软件在提供域时启用地址查找。

我们将在当前一代 Linux 系统(如网络管理器)上查看/etc/resolv.conf和域名解析方法。我们将测试与我们的 DNS 服务器的连接,并运行 DNS 查询以查看它是否工作。

本节假定对域名和 IP 地址有基本的理解。

做好准备

连接到centos1,并通过 ping 地址检查您的域解析是否已经工作。

我在英国,所以我默认使用 BBC 进行测试:

$ ping bbc.co.uk
PING bbc.co.uk (151.101.0.81) 56(84) bytes of data.
64 bytes from 151.101.0.81 (151.101.0.81): icmp_seq=1 ttl=63 time=30.4 ms

如果由于任何原因您的 DNS 服务器不起作用,请销毁并重新创建您的虚拟机。

一旦您确定可以 ping 通一个域,安装 dig(以及与此软件包一起安装的其他工具):

$ sudo yum install -y bind-utils

如何做...

在这里,我们将运行一些基本的故障排除步骤,旨在帮助您确定问题可能出在哪里。

查询域

在 Linux 上查询域名,可以使用dig

$ dig bbc.co.uk

; <<>> DiG 9.9.4-RedHat-9.9.4-61.el7 <<>> bbc.co.uk
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 6288
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 65494
;; QUESTION SECTION:
;bbc.co.uk.            IN    A

;; ANSWER SECTION:
bbc.co.uk.        227    IN    A    151.101.0.81
bbc.co.uk.        227    IN    A    151.101.64.81
bbc.co.uk.        227    IN    A    151.101.128.81
bbc.co.uk.        227    IN    A    151.101.192.81

;; Query time: 24 msec
;; SERVER: 10.0.2.3#53(10.0.2.3)
;; WHEN: Sun Aug 12 14:20:28 UTC 2018
;; MSG SIZE  rcvd: 102

在这里,我们可以看到用于域解析的服务器(加粗)是10.0.2.3,这恰好是 VirtualBox 提供的解析服务。

我们还看到了ANSWER SECTION,正如名称可能暗示的那样,这是我们查询的答案。我们可以在这些地址中的任何一个(目前)到达bbc.co.uk

检查域解析设置

在系统上,关于 DNS 配置的真相通常是解析器文件/etc/resolv.conf,尽管这个文件越来越不直接使用,而是由外部程序管理。

在 CentOS 的情况下,resolv.conf文件由我们的朋友网络管理器管理,在其他系统上,可能是systemd-resolved

查看您的resolv.conf文件:

$ cat /etc/resolv.conf 
# Generated by NetworkManager
search discworld
nameserver 10.0.2.3

上面的代码中的search是主机名查找的搜索列表。它是通过查看盒子所在的域来生成的。

nameserver是我们的 DNS 解析器的值,在这种情况下是 VirtualBox 默认值。

这告诉我们,网络管理器是填充我们文件的程序。

然后我们可以使用nmcli列出它知道的 DNS 服务器:

$ nmcli -f ipv4.dns,ipv4.ignore-auto-dns connection show System\ eth0
ipv4.dns:                               --
ipv4.ignore-auto-dns:                   no

通过上述代码,我们使用了nmcli和两个过滤器ipv4.dnsipv4.ignore-auto-dns来检查我们的默认接口(在这种情况下是eth0系统)。

ipv4.dns的值未设置,这意味着它不太可能从接口上的配置指令中读取值。

ipv4.ignore-auto-dns的值已设置,我们可以从no值看出,我们没有忽略来自 DHCP 服务器的自动 DNS 分配。

更改域解析设置

要使用我们选择的 DNS 服务器,我们应该使用nmcli

在下面的代码中,我们设置了我们自己的 DNS 服务器值(1.1.1.1),并且我们将ipv4.ignore-auto-dns的真值从no更改为yes

$ sudo nmcli connection modify System\ eth0 ipv4.dns "1.1.1.1" ipv4.ignore-auto-dns "yes"

请注意,这两个字段都可以在一行上进行修改。

使用以下命令重新加载接口配置:

$ sudo nmcli connection up System\ eth0

为了保险起见,再看看/etc/resolv.conf

$ cat /etc/resolv.conf 
# Generated by NetworkManager
nameserver 1.1.1.1

然后运行dig

$ dig bbc.co.uk

; <<>> DiG 9.9.4-RedHat-9.9.4-61.el7 <<>> bbc.co.uk
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 10132
;; flags: qr rd ra; QUERY: 1, ANSWER: 4, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 1452
;; QUESTION SECTION:
;bbc.co.uk.            IN    A

;; ANSWER SECTION:
bbc.co.uk.        210    IN    A    151.101.0.81
bbc.co.uk.        210    IN    A    151.101.64.81
bbc.co.uk.        210    IN    A    151.101.128.81
bbc.co.uk.        210    IN    A    151.101.192.81

;; Query time: 23 msec
;; SERVER: 1.1.1.1#53(1.1.1.1)
;; WHEN: Sun Aug 12 14:46:05 UTC 2018
;; MSG SIZE  rcvd: 102

1.1.1.1是 Cloudflare 和 APNIC 提供的新潮的 DNS 服务。这并不是一种认可,只是非常容易记住。

它是如何工作的...

当您在系统上请求域的目的地时,实际上您正在向配置的解析器发送请求,该解析器返回它(或更上游的系统)知道的地址。

在我们的情况下,我们可以通过连接两个终端到centos1来看到这个请求发生。

在我们的第一个终端上,我们将在eth1上安装并启动tcpdump

$ sudo yum install tcpdump -y
$ sudo tcpdump -i eth0 -n port 53

设置好后,切换到第二个连接,并对 BBC(或你选择的任何英国广播公司)运行你的dig命令:

$ dig bbc.co.uk

切换回你的第一个窗口,你应该看到你请求的结果:

14:58:50.303421 IP 10.0.2.15.51686 > 1.1.1.1.domain: 19866+ [1au] A? bbc.co.uk. (38)
14:58:50.331999 IP 1.1.1.1.domain > 10.0.2.15.51686: 19866 4/0/1 A 151.101.0.81, A 151.101.64.81, A 151.101.128.81, A 151.101.192.81 (102)

我们询问了我们的域名服务器bbc.co.uk的地址,并在响应中得到了一些 IP 地址以连接。很整洁,对吧?

还有更多...

这一节显然是使用网络管理器进行了更改,但这并不意味着这是唯一的方法。可以阻止网络管理器劫持并尝试控制你的 DNS。

可以在NetworkManager.conf文件的main部分中设置dns=none,详细信息请参阅其手册页:

"none: NetworkManager将不会修改resolv.conf。这意味着

rc-manager 未受管理”

/etc/NetworkManager/NetworkManager.conf配置文件中,它看起来像这样:

[main]
#plugins=ifcfg-rh,ibft
dns=none

此时重新启动NetworkManager将阻止它在将来尝试修改/etc/resolv.conf

如果你希望有其他东西来管理你系统的 DNS,即使只是你编写的一个脚本来将你的 DNS 服务器放在/etc/resolv.conf中,这可能会很有用。

完全不使用NetworkManager也是可能的,将其禁用为守护进程,并以旧的脚本方式完成所有操作。事实上,这真的很容易,但在我看来,值得掌握如今默认的操作方式。

DNS 通常是成对进行的,最好有一个备用 DNS 服务器,以防第一个出问题。当你的 DNS 失败时,奇怪的事情可能开始发生。

另请参阅

我们实际上在这里使用了nmcli来对配置文件进行更改。我们可以通过查看在我们的 DNS 更改命令之前和之后的eth0配置文件来实际看到这些更改。

之前看起来是这样的:

$ cat /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE="eth0"
BOOTPROTO="dhcp"
ONBOOT="yes"
TYPE="Ethernet"
PERSISTENT_DHCLIENT="yes"

而在之后会更加冗长:

$ cat /etc/sysconfig/network-scripts/ifcfg-eth0
DEVICE=eth0
BOOTPROTO=dhcp
ONBOOT=yes
TYPE=Ethernet
PERSISTENT_DHCLIENT="yes"
PROXY_METHOD=none
BROWSER_ONLY=no
DNS1=1.1.1.1
DEFROUTE=yes
PEERDNS=no
IPV4_FAILURE_FATAL=no
IPV6INIT=no
NAME="System eth0"
UUID=5fb06bd0-0bb0-7ffb-45f1-d6edd65f3e03

我们没有涉及的一件事是 IPv6,但原则与 IPv4 相同。

看一下 IPv6 服务器是如何配置的,以及它们最终出现在/etc/resolv.conf中。

配置 NTP 和我们面临的问题

在本节中,我们将看一下网络时间协议NTP),以及典型安装的默认设置。

本节假设你对时间有基本的了解。

根据你读了多少普拉切特,你对时间的理解可能是线性的,也可能不是。

我们将看一些东西。首先,我们将看一下 NTP 是什么,我们使用什么软件来设置它,以及如何测试你的系统是否在使用它。

从头开始,NTP 在端口123上运行,它是用于使计算机系统中的时间保持同步的协议。这通常很重要,因为我们希望诸如日志时间戳在机器之间保持一致,盒子之间的交易在双方都有正确的时间,以及诸如身份验证之类的东西实际上能够工作。

但是,系统上配置错误或不正确的时间可能会带来一系列问题,其中并非所有问题都是显而易见的。如果你发现自己在使用短语“这毫无意义”时,请检查一下你的盒子上的日期信息,因为大部分时间它都是错误的。有趣的是,很多系统都依赖时间的准确性来工作。

准备就绪

我们将使用centos1centos2centos1将充当我们的客户端,centos2将充当我们的服务器。在本节中,请确保你已经连接到这两台机器。

同样,如果尚未安装,请在两个盒子上安装tcpdump

$ sudo yum install -y tcpdump
$ sudo yum install -y tcpdump

如何做...

如今,CentOS 和其他系统上的NTP 客户端/服务器chrony,取代了更传统的 NTP 实现。

有两个主要组件,chronyc(命令行工具)和chronyd(守护进程,它本身可以充当客户端或服务器)。

检查 NTP 是否正在运行

首先,一个基本的检查是确认日期是否符合你的预期,使用date命令:

$ date
Mon 13 Aug 10:05:31 UTC 2018

请注意,前面的日期信息设置为协调世界时UTC)。随着分布式计算的普及,这种方式越来越受到重视。服务器不必锁定在一个地理区域,如果您在多个地理位置有一千台服务器,您可能选择统一它们的时间,并在其他软件中管理特定的差异(例如,Web 应用程序负责为其用户所在地区编写时间戳)。

要检查chronyd本身是否正在运行,请使用systemctl

$ systemctl status chronyd
● chronyd.service - NTP client/server
   Loaded: loaded (/usr/lib/systemd/system/chronyd.service; enabled; vendor preset: enabled)
 Active: active (running) since Mon 2018-08-13 07:20:48 UTC; 2h 43min ago
     Docs: man:chronyd(8)
           man:chrony.conf(5)
  Process: 576 ExecStartPost=/usr/libexec/chrony-helper update-daemon (code=exited, status=0/SUCCESS)
  Process: 556 ExecStart=/usr/sbin/chronyd $OPTIONS (code=exited, status=0/SUCCESS)
 Main PID: 570 (chronyd)
   CGroup: /system.slice/chronyd.service
           └─570 /usr/sbin/chronyd

检查 NTP 流量是否正在流动

要确认 NTP 流量是否实际正在流动,通用方法是使用tcpdump检查端口。NTP 使用端口 123,这非常容易记住(可能会有一个关于此的测验)。

我们知道默认流量应该使用eth0进行通信,因此我们将指定它:

$ sudo tcpdump port 123 -i eth0

很快,您应该会看到您的客户端尝试与上游服务器通信,请求时间信息:

10:07:33.229507 IP centos1.37284 > ntp3.wirehive.net.ntp: NTPv4, Client, length 48
10:07:33.266188 IP ntp3.wirehive.net.ntp > centos1.37284: NTPv4, Server, length 48
10:07:39.411433 IP centos1.49376 > 5751b502.skybroadband.com.ntp: NTPv4, Client, length 48
10:07:39.453834 IP 5751b502.skybroadband.com.ntp > centos1.49376: NTPv4, Server, length 48

如果您没有安装tcpdump,也可以使用chronyc

通过使用chronyc sources命令,让我们看看我们正在与哪些服务器通信:

$ chronyc sources
210 Number of sources = 4
MS Name/IP address         Stratum Poll Reach LastRx Last sample 
===============================================================================
^* 85.199.214.100                1   6   377    18   +266us[ +309us] +/- 7548us
^- clocka.ntpjs.org              2   6   377    17   -126us[ -126us] +/-   37ms
^- linnaeus.inf.ed.ac.uk         3   6   377    17    -80us[  -80us] +/-   74ms
^+ 85.199.214.101                1   6   377    17   +166us[ +166us] +/- 7583us

我们还可以通过查看上一次同步时了解到的详细信息,来获取有关客户端状态的更详细信息。在下面的示例中,我们可以看到我们的时间与预期时间相差很远:

$ chronyc tracking
Reference ID    : 5751B502 (5751b502.skybroadband.com)
Stratum         : 3
Ref time (UTC)  : Mon Aug 13 17:39:17 2018
System time     : 26450.427734375 seconds slow of NTP time
Last offset     : -0.000067056 seconds
RMS offset      : 777.442565918 seconds
Frequency       : 1.700 ppm slow
Residual freq   : -0.120 ppm
Skew            : 3.203 ppm
Root delay      : 0.052811030 seconds
Root dispersion : 0.006966238 seconds
Update interval : 60.0 seconds
Leap status     : Normal

假设您的时间差异不是非常大,输出可能如下所示。在这里,我们与上游之间的时间差异微不足道:

$ chronyc tracking
Reference ID    : 55C7D666 (85.199.214.102)
Stratum         : 2
Ref time (UTC)  : Sun Aug 19 10:55:55 2018
System time     : 0.000031875 seconds slow of NTP time
Last offset     : -0.000032510 seconds
RMS offset      : 0.003755528 seconds
Frequency       : 6.102 ppm slow
Residual freq   : -0.009 ppm
Skew            : 3.659 ppm
Root delay      : 0.014116751 seconds
Root dispersion : 0.000280226 seconds
Update interval : 64.4 seconds
Leap status     : Normal

启用 NTP 客户端

假设您的系统没有使用chronyd,并且其日期不断滑动,您可以手动启用它。

chronyd的配置文件位于/etc/chrony.conf。以下是默认配置文件,已删除注释部分:

server 0.centos.pool.ntp.org iburst
server 1.centos.pool.ntp.org iburst
server 2.centos.pool.ntp.org iburst
server 3.centos.pool.ntp.org iburst

driftfile /var/lib/chrony/drift

makestep 1.0 3

rtcsync

logdir /var/log/chrony

一旦安装好,如果尚未安装并运行,systemd将用于维护服务:

$ sudo systemctl enable chronyd
$ sudo systemctl start chronyd

确保没有流氓管理员自行安装了openntpd或其他不同的守护程序来尝试管理 NTP。您可以通过使用前面的tcpdump命令来检查这一点,或者查看端口 123上是否有任何运行的内容。

启用 NTP 服务器

也许您管理的网络非常受限,需要很长时间才能完成网络更改。在这种情况下,您可能有一组服务器,被指定为其余资产的 NTP 提供者。

在这种情况下,您需要配置chronyd以允许来自其他客户端的连接。我们将使用centos2作为服务器。

centos2上,在我们的chrony.conf文件底部添加一行,以允许从我们的eth1网络(192.168.33.0)访问:

$ sudo tee --append /etc/chrony.conf << HERE
allow 192.168.33.0/24
HERE

使用新更改重新启动chronyd

$ sudo systemctl restart chronyd

现在,在要成为客户端的系统centos1上,执行以下步骤。

首先,通过注释掉现有的服务器行修改我们的chrony.conf文件:

$ sudo sed -i 's/server/#server/g' /etc/chrony.conf 

接下来,添加指向centos2的所需配置指令:

$ sudo tee --append /etc/chrony.conf << HERE
server 192.168.33.11 iburst
allow 192.168.33.11
HERE

centos1上重新启动chronyd

$ sudo systemctl restart chronyd

现在您已经配置了一个服务器,并且连接到它的客户端。

工作原理...

NTP 以客户端-服务器方式工作,其中客户端设备(在我们的情况下是centos1)向服务器设备(centos2)请求准确的时间读数,然后将其应用于本地机器。

当我们想要创建自己的服务器时,这相对较为琐碎。在服务器端,我们只需要一行指定哪些客户端可以与centos2同步:

allow 192.168.33.0/24

在客户端端,我们需要首先删除它正在通信的服务器(使用sed注释掉默认池行),然后通过以下配置行将我们的新时间源添加为我们配置的服务器:

server 192.168.33.11 iburst
allow 192.168.33.11

服务器选项是我们真相源的地址(或者如果配置了 DNS 名称)。iburst选项只是使前几个请求更快,以便在启动后更快地进行同步。

如果我们在centos2上配置为服务器后启动tcpdump,然后在centos1上重新启动chronyd,我们应该会看到流量正在传输:

$ sudo tcpdump port 123 -i eth1
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth1, link-type EN10MB (Ethernet), capture size 262144 bytes
11:35:51.370634 IP 192.168.33.10.44912 > centos2.ntp: NTPv4, Client, length 48
11:35:51.370965 IP centos2.ntp > 192.168.33.10.44912: NTPv4, Server, length 48
11:35:53.394843 IP 192.168.33.10.52976 > centos2.ntp: NTPv4, Client, length 48
11:35:53.395162 IP centos2.ntp > 192.168.33.10.52976: NTPv4, Server, length 48
11:35:55.414496 IP 192.168.33.10.42977 > centos2.ntp: NTPv4, Client, length 48
11:35:55.414659 IP centos2.ntp > 192.168.33.10.42977: NTPv4, Server, length 48
11:35:57.437187 IP 192.168.33.10.45651 > centos2.ntp: NTPv4, Client, length 48
11:35:57.437539 IP centos2.ntp > 192.168.33.10.45651: NTPv4, Server, length 48

我们可以看到我们的客户机(.10)正在从centos2请求时间,然后centos2在下一行回复。

这也可以用来突出iburst选项的作用。注意数据包通信之间的两秒差异。

如果我们现在在客户端上再次查看chronyc tracking,我们应该看到正常的细节:

$ chronyc tracking
Reference ID    : C0A8210B (192.168.33.11)
Stratum         : 3
Ref time (UTC)  : Sun Aug 19 11:37:01 2018
System time     : 0.000000264 seconds fast of NTP time
Last offset     : -0.000468330 seconds
RMS offset      : 0.000468330 seconds
Frequency       : 6.604 ppm slow
Residual freq   : -5.715 ppm
Skew            : 7.044 ppm
Root delay      : 0.016203152 seconds
Root dispersion : 0.000595987 seconds
Update interval : 64.2 seconds
Leap status     : Normal

上述代码可能需要一秒钟才能填充。如果你特别快,几秒钟后再试一次。

您还可以使用chronyc sources命令检查客户端是否与正确的服务器通信:

$ sudo chronyc sources
210 Number of sources = 1
MS Name/IP address         Stratum Poll Reach LastRx Last sample 
===============================================================================
^* 192.168.33.11                 2   6   377    48    +53us[ +106us] +/- 7783us

还有更多...

有时,您的时间不同步,并且无法自动修复。如果您的时间与应该的时间相差太远,大多数系统会拒绝突然和突然的动作。

这可以通过使用另一个chrony命令来解决:

$ sudo chronyc makestep
200 OK

但要小心,这个命令可能会产生意想不到的副作用。有时,程序会检测到突然的动作,并会强制自行关闭以避免问题。

如果您想知道服务器有多忙,也可以在命令行上使用serverstats

$ sudo chronyc serverstats
NTP packets received       : 8
NTP packets dropped        : 0
Command packets received   : 1
Command packets dropped    : 0
Client log records dropped : 0

另请参阅

Chrony 实际上比我们在这里讨论的要深入得多,我们几乎没有提到它的命令行实用程序(chronyc),它可以用于在运行时进行各种更改。

查看 Chrony 的文档页面:

chrony.tuxfamily.org/documentation.html

如果您发现自己处于需要 NTP 池的环境中,最好考虑如何设置 NTP 池。通常,您应该配置多个潜在服务器,而不是只有一个(可能会偏离或中断)。三个比一个或两个更好(两个是相当糟糕的,因为您无法达成关于正确时间的协议)。

在命令行上列出防火墙规则

在这里,我们将同时使用较新的方法firewall-cmdufw来列出防火墙规则,以及较旧(但仍然流行)的方法iptables。我们将介绍防火墙中其他表的基础知识,以及规则的存储位置。

准备工作

连接到您的centos1 VM(如果需要,还有ubuntu1)。

启用并启动firewalld以供后续示例使用:

$ sudo systemctl enable --now firewalld

如何做...

尽管本书的大部分内容都可以概括到多个发行版,但防火墙是一个可能有所不同的领域。

在 CentOS 系列发行版中,firewall-cmd是与防火墙交互的常用方式。

在 Ubuntu 系列中,使用的是ufw

通常,iptables至少在可预见的未来应该适用于各种发行版。

iptables

通常,您希望检查盒子上防火墙的运行配置。为此,您可以使用iptables -L

$ sudo iptables -L
Chain INPUT (policy ACCEPT)
target prot opt source destination 
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
ACCEPT all -- anywhere anywhere 
INPUT_direct all -- anywhere anywhere 
INPUT_ZONES_SOURCE all -- anywhere anywhere 
INPUT_ZONES all -- anywhere anywhere 
DROP all -- anywhere anywhere ctstate INVALID
REJECT all -- anywhere anywhere reject-with icmp-host-prohibited

Chain FORWARD (policy ACCEPT)
target prot opt source destination 
ACCEPT all -- anywhere anywhere ctstate RELATED,ESTABLISHED
ACCEPT all -- anywhere anywhere 
FORWARD_direct all -- anywhere anywhere 
FORWARD_IN_ZONES_SOURCE all -- anywhere anywhere 
FORWARD_IN_ZONES all -- anywhere anywhere 
FORWARD_OUT_ZONES_SOURCE all -- anywhere anywhere 
FORWARD_OUT_ZONES all -- anywhere anywhere 
DROP all -- anywhere anywhere ctstate INVALID
REJECT all -- anywhere anywhere reject-with icmp-host-prohibited
<SNIP>
Chain FWDO_public (3 references)
target prot opt source destination 
FWDO_public_log all -- anywhere anywhere 
FWDO_public_deny all -- anywhere anywhere 
FWDO_public_allow all -- anywhere anywhere 

Chain FWDO_public_allow (1 references)
target prot opt source destination <SNIP>

默认情况下,-L选项列出默认表(filter为默认表)中的所有链。默认情况下有五个表:

  • raw

  • filter

  • 搞乱

  • security

  • nat

我们可能希望列出nat表中的规则,可以使用-t选项指定:

$ sudo iptables -t nat -L
Chain PREROUTING (policy ACCEPT)
target prot opt source destination 
PREROUTING_direct all -- anywhere anywhere 
PREROUTING_ZONES_SOURCE all -- anywhere anywhere 
PREROUTING_ZONES all -- anywhere anywhere 

Chain INPUT (policy ACCEPT)
target prot opt source destination 

Chain OUTPUT (policy ACCEPT)
target prot opt source destination 
OUTPUT_direct all -- anywhere anywhere 

Chain POSTROUTING (policy ACCEPT)
target prot opt source destination 
POSTROUTING_direct all -- anywhere anywhere 
POSTROUTING_ZONES_SOURCE all -- anywhere anywhere 
POSTROUTING_ZONES all -- anywhere anywhere 

Chain OUTPUT_direct (1 references)
target prot opt source destination 

Chain POSTROUTING_ZONES (1 references)
target prot opt source destination 
POST_public all -- anywhere anywhere [goto] 
POST_public all -- anywhere anywhere [goto] 
POST_public all -- anywhere anywhere [goto] 
POST_public all -- anywhere anywhere [goto] 

Chain POSTROUTING_ZONES_SOURCE (1 references)
target prot opt source destination 

Chain POSTROUTING_direct (1 references)
target prot opt source destination 
<SNIP>
Chain PRE_public_log (1 references)
target prot opt source destination 

另一种方法,我倾向于首先使用的是在命令行上打印规则,而不是列出它们(这是一个细微的区别)。这可以通过-S选项完成:

$ sudo iptables -S
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-N FORWARD_IN_ZONES
-N FORWARD_IN_ZONES_SOURCE
-N FORWARD_OUT_ZONES
-N FORWARD_OUT_ZONES_SOURCE
-N FORWARD_direct
-N FWDI_public
-N FWDI_public_allow
-N FWDI_public_deny
-N FWDI_public_log
-N FWDO_public
-N FWDO_public_allow
-N FWDO_public_deny
-N FWDO_public_log
-N INPUT_ZONES
-N INPUT_ZONES_SOURCE
-N INPUT_direct
-N IN_public
-N IN_public_allow
-N IN_public_deny
-N IN_public_log
-N OUTPUT_direct
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -i lo -j ACCEPT
-A INPUT -j INPUT_direct
-A INPUT -j INPUT_ZONES_SOURCE
-A INPUT -j INPUT_ZONES
-A INPUT -m conntrack --ctstate INVALID -j DROP
-A INPUT -j REJECT --reject-with icmp-host-prohibited
-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A FORWARD -i lo -j ACCEPT
-A FORWARD -j FORWARD_direct
-A FORWARD -j FORWARD_IN_ZONES_SOURCE
-A FORWARD -j FORWARD_IN_ZONES
-A FORWARD -j FORWARD_OUT_ZONES_SOURCE
-A FORWARD -j FORWARD_OUT_ZONES
-A FORWARD -m conntrack --ctstate INVALID -j DROP
-A FORWARD -j REJECT --reject-with icmp-host-prohibited
-A OUTPUT -j OUTPUT_direct
-A FORWARD_IN_ZONES -i eth2 -g FWDI_public
-A FORWARD_IN_ZONES -i eth1 -g FWDI_public
-A FORWARD_IN_ZONES -i eth0 -g FWDI_public
-A FORWARD_IN_ZONES -g FWDI_public
-A FORWARD_OUT_ZONES -o eth2 -g FWDO_public
-A FORWARD_OUT_ZONES -o eth1 -g FWDO_public
-A FORWARD_OUT_ZONES -o eth0 -g FWDO_public
-A FORWARD_OUT_ZONES -g FWDO_public
-A FWDI_public -j FWDI_public_log
-A FWDI_public -j FWDI_public_deny
-A FWDI_public -j FWDI_public_allow
-A FWDI_public -p icmp -j ACCEPT
-A FWDO_public -j FWDO_public_log
-A FWDO_public -j FWDO_public_deny
-A FWDO_public -j FWDO_public_allow
-A INPUT_ZONES -i eth2 -g IN_public
-A INPUT_ZONES -i eth1 -g IN_public
-A INPUT_ZONES -i eth0 -g IN_public
-A INPUT_ZONES -g IN_public
-A IN_public -j IN_public_log
-A IN_public -j IN_public_deny
-A IN_public -j IN_public_allow
-A IN_public -p icmp -j ACCEPT
-A IN_public_allow -p tcp -m tcp --dport 22 -m conntrack --ctstate NEW -j ACCEPT

这种方法更好的原因在于它显示了用于生成相关规则的语法。在紧张的情况下,这种知识可以节省时间和压力。

firewall-cmd

Firewalld(由firewall-cmd控制的守护程序)引入了 Linux 防火墙的区域概念。

区域分配给特定接口,并为每个区域配置特定规则。

您也可以使用firewall-cmd列出当前配置的区域:

$ sudo firewall-cmd --get-zones
block dmz drop external home internal public trusted work

要检查我们当前使用的区域以及使用的接口,我们使用--get-active-zones

在下面的代码中,我们可以看到eth0eth1正在使用public区域:

$ firewall-cmd --get-active-zones
public
 interfaces: eth0 eth1

eth2也在public区域下,但是接口未配置,因此处于非活动状态。

因为eth0public下是活动的,让我们列出public区域的详细信息:

$ sudo firewall-cmd --list-all --zone public
public (active)
 target: default
 icmp-block-inversion: no
 interfaces: eth0 eth1 eth2
 sources: 
 services: ssh dhcpv6-client
 ports: 
 protocols: 
 masquerade: no
 forward-ports: 
 source-ports: 
 icmp-blocks: 
 rich rules: 

在这里,我们可以看到这个区域知道的服务是sshdhcpv6-client

我们可以使用--list-services来具体列出区域的服务:

$ sudo firewall-cmd --zone public --list-services 
ssh dhcpv6-client

如果我们想要关于该服务允许的确切信息,我们可以使用--info-service

在下面的代码中,我们正在查看ssh,发现它允许端口22/tcp通过:

$ sudo firewall-cmd --info-service ssh
ssh
 ports: 22/tcp
 protocols: 
 source-ports: 
 modules: 
 destination:

ufw

在你的 Ubuntu VM 中,ufw可能不会默认启用。我们将启用它,但首先我们将添加一个规则,以确保一旦防火墙启动我们可以重新进入:

vagrant@ubuntu1:~$ sudo ufw allow ssh/tcp
Rule added
Rule added (v6)

你可以使用以下命令启用它:

vagrant@ubuntu1:~$ sudo ufw enable
Command may disrupt existing ssh connections. Proceed with operation (y|n)? y
Firewall is active and enabled on system startup

现在我们可以使用status verbose选项来查看我们防火墙的状态:

vagrant@ubuntu1:~$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere                  
22/tcp (v6)                ALLOW IN    Anywhere (v6) 

防火墙是激活的,并且有一个22/tcp允许规则让我们进入。

它是如何工作的...

所有前面的示例都与内核的 Netfilter 框架交互——它们只是使用不同的工具做同样的事情。

当你在命令行上列出规则时,你正在查询内核知道的关于盒子安全规则。

你可以使用iptablesfirewall-cmd命令列出规则(在 CentOS 的情况下),因为它们只是作为用户空间前端。

UFW 是一样的,只是在 Ubuntu 世界中。

这就是为什么你会发现很多设置脚本会删除额外的防火墙工具,比如firewalld,而只是使用iptables套件来管理防火墙。

还有更多...

如果你发现自己在一个安装了firewall-cmd但没有iptables用户空间工具的盒子上,你可以用yum找到它所在的包:

$ yum whatprovides iptables

在这种情况下,如果你是一位纯粹主义者,你应该安装iptables包。

另请参阅

虽然还没有默认安装,但你可能想了解nftnftables系统。

nftables是现有内核框架的替代品,nft是相应的命令行工具。

在命令行上添加和删除防火墙规则

在本节中,我们将看看如何添加和删除防火墙设置的示例规则。

准备工作

确保firewalld已安装并在centos1上启动:

$ sudo systemctl enable --now firewalld

完成后,在后台启动一个 Python Web 服务器:

$ python -m SimpleHTTPServer &> /dev/null &
[1] 2732

这应该在端口8000上启动一个 Web 服务器。

连接到centos2,尝试在端口8000上的curl你的centos1盒子。它应该失败:

$ curl 192.168.33.10:8000
curl: (7) Failed connect to 192.168.33.10:8000; No route to host

如果你想同时在 Ubuntu 示例中工作,请登录到你的 Ubuntu 框。

如何做...

我们将使用firewall-cmdiptablesufw来调整我们的防火墙。

firewall-cmd

我们的 Web 服务器正在监听所有接口(默认),我们将允许通过eth1连接到它。

由于前一节的原因,我们知道eth1在默认(public)区域中:

$ sudo firewall-cmd --zone public --list-all
public (active)
 target: default
 icmp-block-inversion: no
 interfaces: eth0 eth1
 sources: 
 services: ssh dhcpv6-client
 ports: 
 protocols: 
 masquerade: no
 forward-ports: 
 source-ports: 
 icmp-blocks: 
 rich rules: 

这意味着我们必须向我们的区域添加另一个端口允许,使连接到8000/tcp

在下面的代码中,我们正在添加到我们的防火墙配置,但我们没有修改运行配置——我们添加了permanent选项,以便在重新加载防火墙时加载规则:

$ sudo firewall-cmd --permanent --zone=public --add-port 8000/tcp

现在,我们需要再次运行命令,不带permanent选项。这样我们的运行配置就被修改了:

$ sudo firewall-cmd --zone=public --add-port 8000/tcp

现在运行--list-all选项将显示你添加的端口:

$ sudo firewall-cmd --zone public --list-all 
public (active)
 target: default
 icmp-block-inversion: no
 interfaces: eth0 eth1 eth2
 sources: 
 services: ssh dhcpv6-client
 ports: 8000/tcp
 protocols: 
 masquerade: no
 forward-ports: 
 source-ports: 
 icmp-blocks: 
 rich rules:

你应该能够从centos2上的8000/tcp上的curl centos1

$ curl 192.168.33.10:8000
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html>
<title>Directory listing for /</title>
<body>
<h2>Directory listing for /</h2>
<hr>
<ul>
<li><a href=".bash_history">.bash_history</a>
<li><a href=".bash_logout">.bash_logout</a>
<li><a href=".bash_profile">.bash_profile</a>
<li><a href=".bashrc">.bashrc</a>
<li><a href=".ssh/">.ssh/</a>
</ul>
<hr>
</body>
</html>

如果你觉得更容易,你也可以主要修改运行配置,然后当你满意时,使用--runtime-to-permanent选项应用规则。选择权在你手中。

要撤消这个添加,你可以将add-port换成remove-port,就像这样:

$ sudo firewall-cmd --zone=public --remove-port 8000/tcp
success

iptables

要在iptables中执行相同的操作,我们必须首先确保firewalld不会干扰。

首先禁用和停止firewalld

$ sudo systemctl disable --now firewalld

现在你应该有一个空的iptables配置,可以用iptables -S来查看:

$ sudo iptables -S
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT

因为我们有一个空的规则列表,我们将从添加一些基本规则开始。

首先,我们将阻止centos2和我们eth1网络上的任何其他东西从 SSH 到centos1

$ sudo iptables -A INPUT -i eth1 -p tcp -m tcp --dport 22 -j DROP

接下来,我们将只允许来自10.0.2.0/24源的传入SSH连接:

$ sudo iptables -A INPUT -s 10.0.2.0/24 -p tcp -m tcp --dport 22 -j ACCEPT

最后,我们将默认的传入策略从ACCEPT更改为DROP

$ sudo iptables -P INPUT DROP

因为我们已更改了默认策略,我们还需要确保允许RELATEDESTABLISHED连接(我们从我们的框发起的连接)。这使我们的防火墙成为“有状态”的或者知道状态的:

$ sudo iptables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

运行iptables -S将显示您的规则:

$ sudo iptables -S
-P INPUT DROP
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-A INPUT -i eth1 -p tcp -m tcp --dport 22 -j DROP
-A INPUT -s 10.0.2.0/24 -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

我们这里的配置有些多余,因为虽然它展示了iptables规则的灵活性,但默认的流量规则是-P INPUT DROP,这意味着如果流量不被我们的其他规则接受,它将不会被允许进入。因此,我们的eth1 DROP行是没有意义的。

ufw

在上一节使用ufw之前,我们必须添加此允许规则以允许 SSH 流量进入,一旦启用防火墙:

vagrant@ubuntu1:~$ sudo ufw allow ssh/tcp
Rule added
Rule added (v6)

默认的ufw配置文件拒绝了传入连接,如下面的代码所示:

vagrant@ubuntu1:~$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
New profiles: skip

因此,如果我们想要访问托管在我们的 Ubuntu 框上的 Web 服务器,我们需要一个新的规则。

以前,我们使用了服务名称(ssh),所以这次我们要特别允许从我们的 VirtualBox 网络中的端口(80,默认的 HTTP 端口):

vagrant@ubuntu1:~$ sudo ufw allow from 10.0.2.0/24 to any port 80 proto tcp
Rule added

我们可以使用status选项来查看此规则的运行情况:

vagrant@ubuntu1:~$ sudo ufw status
Status: active

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW       Anywhere 
80/tcp                     ALLOW       10.0.2.0/24 
22/tcp (v6)                ALLOW       Anywhere (v6)  

使用ufw删除很简单-只需在您的原始规则(无论是allow还是deny)前面加上delete一词:

vagrant@ubuntu1:~$ sudo ufw delete allow from 10.0.2.0/24 to any port 80 proto tcp
Rule deleted

工作原理...

这些示例中的每一个都用于操纵您的框上运行的防火墙配置。当您使用用户空间工具时,就像在上一节中的查询示例中一样,您实际上是在修改内核的 Netfilter 框架。

就个人而言,我发现使用iptables命令最容易理解正在发生的事情,尽管您可能会发现自己更喜欢使用其他方法。

正如我们在上一节中看到的,如果您启用了firewalldufw,您还会发现您的默认iptables规则发生了变化。如果没有启用firewalldufw,那么开箱即用的iptables配置将会更简单,如下面的行所示:

$ sudo iptables -S
-P INPUT ACCEPT
-P FORWARD ACCEPT
-P OUTPUT ACCEPT

每当我们修改防火墙时,我们的更改都会立即生效。

分解一下,如果我们回到我们的iptables示例,我们可以逐步了解我们做了什么:

$ sudo iptables -A INPUT -s 10.0.2.0/24 -p tcp -m tcp --dport 22 -j ACCEPT

在这里,我们使用iptables用户空间工具来修改iptables

$ sudo iptables

然后,我们向我们的INPUT链(一个链位于一个表中)添加规则:

-A INPUT

我们将流量源设置为我们的 VirtualBox 子网:

-s 10.0.2.0/24

我们指定协议并使用扩展匹配功能:

-p tcp -m tcp

我们说目标端口应该是22(SSH 端口):

--dport 22

最后,我们说我们应该ACCEPT这个流量:

-j ACCEPT

还有更多...

如果您使用firewall-cmdufw进行更改,通常可以同时将运行配置保存到持久配置中。

使用iptables,我们希望使用iptables-save来修改我们保存的配置,并确保它在启动时启动:

$ sudo iptables-save
# Generated by iptables-save v1.4.21 on Sun Aug 19 15:04:14 2018
*filter
:INPUT DROP [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [3:236]
-A INPUT -i eth1 -p tcp -m tcp --dport 22 -j DROP
-A INPUT -s 10.0.2.0/24 -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
COMMIT
# Completed on Sun Aug 19 15:04:14 2018

这一切都很好,只是它将配置打印到标准输出而不是保存在某个地方。让我们通过将其重定向到默认的iptables配置位置来解决这个问题:

$ sudo iptables-save | sudo tee /etc/sysconfig/iptables

现在,为了正确地在启动时启动它,我们需要iptables-services包,其中包括systemd单元文件等内容:

$ sudo yum install -y iptables-services

现在我们可以启用iptables以在启动时启动:

$ sudo systemctl enable iptables

运行systemctl restart命令以启动iptables,并确保您的配置是正确的:

$ sudo service iptables restart
Redirecting to /bin/systemctl restart iptables.service
$ sudo iptables -S
-P INPUT DROP
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-A INPUT -i eth1 -p tcp -m tcp --dport 22 -j DROP
-A INPUT -s 10.0.2.0/24 -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

防火墙可以像您需要的那样复杂或简单。最好从简单开始,随着您的学习逐渐增加复杂性。

还有iptables -F选项,我们在本节中没有涵盖。

-F表示刷新,在某些情况下,将防火墙刷新回其默认配置可能非常方便。

但是,值得注意的是,如果您将默认的INPUT策略设置为DROP传入流量,那么刷新任何允许您访问的规则将使您的会话无法使用。

我的默认策略是DROP

$ sudo iptables -S
-P INPUT DROP
-P FORWARD ACCEPT
-P OUTPUT ACCEPT
-A INPUT -i eth1 -p tcp -m tcp --dport 22 -j DROP
-A INPUT -s 10.0.2.0/24 -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

如果我现在刷新我的规则,我的会话就会锁定:

$ sudo iptables -F
$

现在,我们需要进入框的控制台并恢复允许我们访问的规则。大多数情况下,这只是运行防火墙的start命令。

确定正在运行的服务和正在使用的端口

在本节中,我们将仅使用我们的centos1 VM,因为我们确定我们的框上正在使用的端口和背后的服务。

准备就绪

连接到您的centos1 VM。

您还应该安装lsof软件包,以便查看我们将要查看的一些示例:

$ sudo yum install -y lsof

如何做...

在确定服务器上运行的内容时,通常要知道是否有任何内容正在监听连接以及使用哪些端口。

开箱即用,套接字统计ss)通常是可用的。旧的程序netstat有时也会安装,尽管这里不会涉及。

一个很好的第一步是运行ss -tua,它将列出所有 TCP 和 UDP 套接字:

$ ss -tua
Netid  State      Recv-Q Send-Q                                      Local Address:Port                                                       Peer Address:Port 
udp    UNCONN     0      0                                               127.0.0.1:323                                                                   *:* 
udp    UNCONN     0      0                                                       *:bootpc                                                                *:* 
udp    UNCONN     0      0                                                       *:bootpc                                                                *:* 
udp    UNCONN     0      0                                                       *:sunrpc                                                                *:* 
udp    UNCONN     0      0                                                       *:ntp                                                                   *:* 
udp    UNCONN     0      0                                                       *:728                                                                   *:* 
udp    UNCONN     0      0                                                     ::1:323                                                                  :::* 
udp    UNCONN     0      0                                                      :::sunrpc                                                               :::* 
udp    UNCONN     0      0                                                      :::728                                                                  :::* 
tcp    LISTEN     0      5                                                       *:irdmi                                                                 *:* 
tcp    LISTEN     0      128                                                     *:sunrpc                                                                *:* 
tcp    LISTEN     0      128                                                     *:ssh                                                                   *:* 
tcp    LISTEN     0      100                                             127.0.0.1:smtp                                                                  *:* 
tcp    ESTAB      0      0                                               10.0.2.15:ssh                                                            10.0.2.2:36116 
tcp    LISTEN     0      128                                                    :::sunrpc                                                               :::* 
tcp    LISTEN     0      128                                                    :::ssh                                                                  :::* 
tcp    LISTEN     0      100                                                   ::1:smtp                                                                 :::* 

如果我们只想列出ESTABestablished)连接,我们可以使用state指令进行过滤:

$ ss -tua state established
Netid  Recv-Q Send-Q                                           Local Address:Port                                                            Peer Address:Port 
tcp    0      0                                                    10.0.2.15:ssh                                                                 10.0.2.2:36116       

在这里,我们可以看到我的主机机器上的 SSH 会话。

假设我们现在想要列出所有正在监听 TCP 连接的套接字:

$ ss -tl 
State      Recv-Q Send-Q                                         Local Address:Port                                                          Peer Address:Port 
LISTEN     0      5                                                          *:irdmi                                                                    *:* 
LISTEN     0      128                                                        *:sunrpc                                                                   *:* 
LISTEN     0      128                                                        *:ssh                                                                      *:* 
LISTEN     0      100                                                127.0.0.1:smtp                                                                     *:* 
LISTEN     0      128                                                       :::sunrpc                                                                  :::* 
LISTEN     0      128                                                       :::ssh                                                                     :::* 
LISTEN     0      100                                                      ::1:smtp                                                                    :::*             

或者,我们可以为 UDP 执行此操作:

$ ss -ul 
State      Recv-Q Send-Q                                         Local Address:Port                                                          Peer Address:Port 
UNCONN     0      0                                                  127.0.0.1:323                                                                      *:* 
UNCONN     0      0                                                          *:bootpc                                                                   *:* 
UNCONN     0      0                                                          *:sunrpc                                                                   *:* 
UNCONN     0      0                                                          *:ntp                                                                      *:* 
UNCONN     0      0                                                          *:728                                                                      *:* 
UNCONN     0      0                                                        ::1:323                                                                     :::* 
UNCONN     0      0                                                         :::sunrpc                                                                  :::* 
UNCONN     0      0                                                         :::728                                                                     :::*          

这足以让我们对正在运行的服务有一个很好的概述,但它并不让我们知道端口。

ss将根据已知的服务列表检查以确定要显示的名称。在这个例子中,我们故意选择列出监听端口,过滤除了端口22之外的所有内容,我们可以看到ssh已被选择:

$ ss -l sport = :22
Netid  State      Recv-Q Send-Q                                      Local Address:Port                                                       Peer Address:Port 
tcp    LISTEN     0      128                                                     *:ssh                                                                   *:* 
tcp    LISTEN     0      128                                                    :::ssh                                                                  :::*       

::是 IPv6 环回表示,这就是为什么它在 SSH 条目旁边显示在这里。

我们可以检查系统的services列表,看看它认为ssh应该使用什么:

$ grep "^ssh " /etc/services
ssh             22/tcp                          # The Secure Shell (SSH) Protocol
ssh             22/udp                          # The Secure Shell (SSH) Protocol
ssh             22/sctp                 # SSH

它是如何工作的...

ss是确定系统当前正在使用的套接字的快速方法,它不仅限于 TCP 或 UDP,因为它还能够显示 Unix 域套接字(-x)。

在这个例子中,我们已经查询了系统,找出了哪些端口正在使用,ss已经执行了一些解析工作,以确定这些端口可能被哪些服务使用。

然而,这并不是保证。例如,如果您在2222上启动 Python Web 服务器,它将列出以下内容:

$ ss -l sport = :2222
Netid  State      Recv-Q Send-Q                                      Local Address:Port                                                       Peer Address:Port 
tcp    LISTEN     0      5                                                       *:EtherNet/IP-1                                                         *:*     

这仅仅是因为就/etc/services而言,此端口被EtherNet/IP-1使用:

$ grep "^EtherNet" /etc/services
EtherNet/IP-1   2222/tcp  EtherNet-IP-1 # EtherNet/IP I/O
EtherNet/IP-1   2222/udp  EtherNet-IP-1 # EtherNet/IP I/O
EtherNet/IP-2   44818/tcp EtherNet-IP-2  # EtherNet/IP messaging
EtherNet/IP-2   44818/udp EtherNet-IP-2  # EtherNet/IP messaging

还有更多...

确定哪个进程使用哪个端口的一个可以说是更好的方法是使用lsof命令。我说可以说是因为它通常不是默认安装的,尽管它非常方便和强大。

如果我们使用lsof并检查使用端口22的命令,我们会得到以下列表:

$ sudo lsof -i :22
COMMAND  PID    USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
sshd     877    root    3u  IPv4  17409      0t0  TCP *:ssh (LISTEN)
sshd     877    root    4u  IPv6  17479      0t0  TCP *:ssh (LISTEN)
sshd    4262    root    3u  IPv4  43232      0t0  TCP centos1:ssh->gateway:36116 (ESTABLISHED)
sshd    4265 vagrant    3u  IPv4  43232      0t0  TCP centos1:ssh->gateway:36116 (ESTABLISHED) 

如果您不想打印主机名(上面的示例中的 centos1)和端口名(上面的 ssh),您可以使用以下额外的标志(Pn:)

$ sudo lsof -Pni :22
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
sshd 3454 root 3u IPv4 26892 0t0 TCP *:22 (LISTEN)
sshd 3454 root 4u IPv6 26894 0t0 TCP *:22 (LISTEN)
sshd 3457 root 3u IPv4 26951 0t0 TCP 10.0.2.15:22->10.0.2.2:33066 (ESTABLISHED)
sshd 3460 vagrant 3u IPv4 26951 0t0 TCP 10.0.2.15:22->10.0.2.2:33066 (ESTABLISHED)

如果我们在2222上启用我们的 Python Web 服务器,我们会得到这个:

$ sudo lsof -i :2222
COMMAND  PID    USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
python  4542 vagrant    3u  IPv4  45493      0t0  TCP *:EtherNet/IP-1 (LISTEN)

请注意,虽然NAME仍然列为EtherNet,但我们知道它是 Python,因为COMMAND也是如此。

因为我们还有 PID(4542),我们可以轻松地获得完整的命令:

$ ps aux | grep 4542
vagrant   4542  0.0  2.0  97820 10136 pts/2    S    15:39   0:00 python -m SimpleHTTPServer 2222

使用 iftop 进行调试

在本节中,我们将查看top系列的一个成员(它非常广泛,包括atopiotophtop等),它专门用于网络流量统计和调试。

iftop既方便又易读。

准备就绪

在本节中,我们将使用centos1centos2,在不同的窗口中连接到两者。

确保在开始之前在centos2上安装iftop。这来自企业 Linux 的额外软件包EPEL)存储库,因此我们必须首先安装它:

$ sudo yum install -y epel-release
$ sudo yum install -y iftop

centos2上启动iftop

$ sudo iftop -i eth1

如何做...

一旦您在centos2上启动了iftop,切换到centos1并运行一个ssh命令,以生成一些流向centos2的网络流量:

$ ssh 192.168.33.11
The authenticity of host '192.168.33.11 (192.168.33.11)' can't be established.
ECDSA key fingerprint is SHA256:GwCeJ/ObTsyKxMxzazTaYvvyY3SFgxPl6ucjPDGwmao.
ECDSA key fingerprint is MD5:0d:41:ad:71:67:07:35:d4:59:07:de:41:bf:a4:b4:93.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.33.11' (ECDSA) to the list of known hosts.
vagrant@192.168.33.11's password: 
Last login: Sun Aug 19 15:04:49 2018 from 10.0.2.2
$ 

回到centos2,您应该在网络屏幕中看到一个简短的条目显示活动:

在我连接到centos2ssh会话中(现在连接到centos2),我已经启动了top来生成一系列流量。

在您的窗口底部,您将看到以下值:

  • TX

  • RX

  • 总计

这些值是总流量传输,接收和总计。

在右侧,我们有在21040秒内平均的速率。

点击回到centos1,停止,然后启动top,将导致RX值上升,因为您已经通过连接发送了按键。

在窗口顶部,您可以看到流量的可视表示:

这显示了发送到192.168.33.10centos1)的流量和以相对可读的格式接收到的流量。

传输速率的刻度在顶部,并根据数量而变化。

在现实世界的情况下,这种类型的信息可能是非常宝贵的,因为它提供了一种一目了然的方式来看到谁可能在攻击您的服务器。

如果我们切换到观察eth0流量,然后重新启动chronyd,我们可以看到更多的服务器连接到:

它是如何工作的...

tcpdump一样,iftop会监听您指定的接口上的流量,或者如果您没有指定接口,则使用默认接口。

然后它打印主机的带宽使用情况,为您的设备提供了网络的良好视觉表示。

还有更多...

iftop提供了很多选项,值得考虑查看手册页面。像使用-n来禁用主机名查找之类的简单事情可能会很方便,或者更改带宽速率的显示方式。

您甚至可以使用-t标志将纯文本版本打印到stdout

$ sudo iftop -i eth1 -t
interface: eth1
IP address is: 192.168.33.11
MAC address is: 08:00:27:f1:e9:56
Listening on eth1
 # Host name (port/service if enabled)            last 2s   last 10s   last 40s cumulative
--------------------------------------------------------------------------------------------
 1 centos2                                  =>     35.7Kb     35.7Kb     35.7Kb     8.91KB
 192.168.33.10                            <=       416b       416b       416b       104B
--------------------------------------------------------------------------------------------
Total send rate:                                     35.7Kb     35.7Kb     35.7Kb
Total receive rate:                                    416b       416b       416b
Total send and receive rate:                         36.1Kb     36.1Kb     36.1Kb
--------------------------------------------------------------------------------------------
Peak rate (sent/received/total):                     35.7Kb       416b     36.1Kb
Cumulative (sent/received/total):                    8.91KB       104B     9.02KB
=============================================================================================

总结

在本章中,我们看了一下 Linux 世界中的网络和防火墙。我希望这不会让您头痛太多,因为它确实让我感到一些痛苦。

正如我之前提到的,网络和防火墙配置可以像你想要的那样复杂或简单,并且在不断增长的单一服务器世界中,我们看到了越来越简单的配置。

您可能会在多个网络和多宿主服务器的概念周围找到问题,因为扁平网络结构对于普通人来说更容易理解(包括我自己)。

您也不必完全使用 Linux 来完成所有操作。

是的,Linux 可以作为边界防火墙用于企业,但您也可以使用F5设备或 Check Point 盒。

是的,Linux 可以充当路由器,但您更有可能在网络机柜中看到思科或 Juniper 设备。

这些解决方案既有积极的一面,也有消极的一面。

一个简单的积极因素是,专门设计的设备通常非常擅长其专门设计的功能,并且管理这些设备的工具在其方法上几乎是统一的(而不是我们在 Linux 世界中有时会遇到的混乱)。

一个明显的负面因素是,这意味着您要么必须学习您要纳入网络的设备的技术堆栈,要么必须雇佣一个专门的人来为您管理解决方案,这将花费时间和金钱。

网络似乎是您喜欢或不喜欢的东西,而我坚定地属于后者。理论上,一旦设置好,网络和防火墙就可以“自动运行”,但实际上,这意味着边缘情况问题在发生时更难以追踪和纠正。

最后要提到的一件事是,因为我保证您在职业生涯中的某个时候会遇到这个问题,那就是锁定自己的问题。

这种情况会发生。

当这种事情发生时,不要自责。我曾经和每一个工程师一起工作过,他们都曾经把自己锁在盒子外面,要么是通过错误配置的防火墙规则,要么是像改变 SSH 端口而没有先更新 SELinux 配置这样的愚蠢错误。

如果你把自己锁在外面,而你可以访问控制台,无论是远程键盘视频鼠标系统还是类似云服务提供商的基于网络的终端,通常都没问题——这意味着只需要登录并纠正你的错误。

如果你把自己锁在外面,而系统在城市或国家的另一边,你有两个选择:

  1. 跳进你的车里,准备好长途驾驶去那里和回来。

  2. 联系远程工程师,向他们下跪,承认你的错误,并请求他们找一个救援车来解决你的错误。

如果你选择第二个选项,下次见面时给他们买一杯他们喜欢的饮料是完全可以接受的。

我既是远程工程师,负责修复别人的错误,也是犯错误的人。就像我说的,这种事情每个人都会遇到。

第四章:服务和守护进程

本章将涵盖以下主题:

  • 确定运行的服务

  • 列出已安装的服务

  • 启动和停止服务

  • 更改启动和停止的服务

  • 您可能期望看到的常见服务

  • 理解服务单元文件

  • 自定义 systemd 单元文件

  • 测试运行服务

  • 编写基本单元文件

  • 使用 systemd 定时器(和 cron)

  • 其他init系统

介绍

systemd(小写)是一个九头蛇。

在旧世界中,我们为系统上想要做的每件小事都有一个软件。时间由 NTP 处理,设备由udev处理,而 init 通常由SysV Init处理。

在新世界中,我们有 systemd:

  • 系统时钟管理可以由systemd-timesyncd处理。

  • udev已合并到 systemd 代码库中,形成systemd-udevd

  • 进程初始化由 systemd 核心本身处理。

列表还在继续。

一般来说,systemd 一直在采用其他项目,或者将相同的功能写入自己的实现中(例如 systemd-timesyncd,它是一个 NTP 替代品)。然而,systemd 套件也是模块化的,这意味着发行版可以广泛选择要采用和使用的部分。

对我们来说,systemd 的重要工作是替换发行版上的传统 init 系统(CentOS,Debian 和 Ubuntu 现在都使用 systemd)。这意味着首先,systemd 管理着您的计算机上的服务和守护进程。

systemd 是第一个进程,这意味着它经常被分配为第一个进程 IDPID)。所有其他进程都将 systemd 作为父进程。

在本章中,我们将使用 systemd 的 init 组件,了解单元文件,并确定系统的运行状态。

在 systemd 主导每个主流发行版之前,这一章可能会更长。Canonical 为 Ubuntu 编写了一个称为 Upstart 的东西(它曾暂时被 Red Hat 采用),而更传统的发行版则很难放弃SysV初始化风格的守护程序管理。即使在今天,您仍会发现在野外使用旧实现的系统:

  • CentOS 在 7 版本中采用了 systemd

  • Ubuntu 在 15.04 中采用了 systemd

  • Debian 在 Jessie(8)中采用了 systemd

如果 Canonical 编写了 Upstart,您可能会想知道他们最终为什么选择了 systemd。这主要是因为 Debian 采用了 systemd,经过内部的一场内战,Ubuntu 承认如果他们继续与上游发行版保持一致,他们将更容易些。这场战争并不是没有双方激烈的战斗,一些涟漪仍然可以感受到。

技术要求

对于本章,需要一个不同的Vagrantfile

如有需要,可以使用以下示例-确保destroy之前的任何箱子以释放资源:

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

 config.vm.define "centos1" do |centos1|
   centos1.vm.box = "centos/7"
   centos1.vm.network "private_network", ip: "192.168.33.10"
   centos1.vm.hostname = "centos1"
   centos1.vm.box_version = "1804.02"
 end

 config.vm.define "centos6" do |centos6|
   centos6.vm.box = "centos/6"
 end

 config.vm.define "debian7" do |debian7|
   debian7.vm.box = "debian/wheezy64"
 end

end

确定运行的服务

每当您到达一个箱子,特别是您不确定的箱子(被放在柜子后面自言自语的箱子),弄清楚上面运行的软件是个好主意。

在现代系统(2013 年以后),可以使用systemctl命令来实现这一点。

systemctl是任何 systemd 系统的主要控制机制-字面上是“系统控制”。将其视为初始化软件的人类前端(在您的计算机上运行的第一个软件,管理所有其他软件),允许您修改和调查计算机的运行状态。

不仅如此,在 Unix/Linux 世界中,一切都是文件;您的网络连接是一个文件,您的程序是一个文件,您的设备是一个文件,因此您可以通过修改文件来控制一切。

但这很快就变得乏味了。

systemctl通过简单的命令包装功能并以人类可读的方式打印结果,为您节省了手动在系统中移动文件的麻烦。

如何做...

在命令行上,键入以下内容:

$ systemctl

您应该会看到一个 systemd 单元的列表;这是 systemd 知道的每个单元的一个很好的初始概述,但它非常嘈杂(如下面的屏幕截图所示),我们特别想要看到的是守护进程(因为我们有一种哥特风格):

在 Linux 领域,服务和守护进程有些可以互换;例如,systemd 将守护进程称为service文件。这并不能阻止一些人对“正确使用”感到愤怒。

尝试以下命令:

$ systemctl list-units --type service

您应该会得到一个服务及其状态的列表。

这个视图更好,但它也显示了失败和退出的服务,例如,在我们的 Vagrant 实例上,我们应该看到以下内容:

如果我想排除这些,我可以使用以下命令:

$ systemctl list-units --type service --state running

现在我们得到了一个更加简洁的列表,总共有 17 项:

UNIT                     LOAD   ACTIVE SUB     DESCRIPTION
auditd.service           loaded active running Security Auditing Service
chronyd.service          loaded active running NTP client/server
crond.service            loaded active running Command Scheduler
dbus.service             loaded active running D-Bus System Message Bus
getty@tty1.service       loaded active running Getty on tty1
gssproxy.service         loaded active running GSSAPI Proxy Daemon
lvm2-lvmetad.service     loaded active running LVM2 metadata daemon
NetworkManager.service   loaded active running Network Manager
polkit.service           loaded active running Authorization Manager
postfix.service          loaded active running Postfix Mail Transport Agent
rpcbind.service          loaded active running RPC bind service
rsyslog.service          loaded active running System Logging Service
sshd.service             loaded active running OpenSSH server daemon
systemd-journald.service loaded active running Journal Service
systemd-logind.service   loaded active running Login Service
systemd-udevd.service    loaded active running udev Kernel Device Manager
tuned.service            loaded active running Dynamic System Tuning Daemon

LOAD   = Reflects whether the unit definition was properly loaded.
ACTIVE = The high-level unit activation state, i.e. generalization of SUB.
SUB    = The low-level unit activation state, values depend on unit type.

17 loaded units listed. Pass --all to see loaded but inactive units, too.
To show all installed unit files use 'systemctl list-unit-files'.

您可以使用--no-legend选项来消除systemctl的有用但嘈杂的页眉和页脚注释。

工作原理...

在本节中,我们使用systemctl命令来查询 systemd。

我们将逐渐更加细化我们的请求,直到最终构建出一个查询,只显示我们想要看到的内容,即正在运行的服务。

有人认为使用pipesgrep来实现你想要的结果是有道理的,根据你的偏好,你可能会发现这两个命令中的一个比另一个更整洁,尽管它们实现的功能大致相同:

$ systemctl --no-pager | grep service | grep running | column -t

在前面,我们首先从systemctl打印默认的“all”列表,然后我们通过grep几次来过滤我们想要的内容,最后我们以列的形式显示出来,这样基本上是可读的。

$ systemctl list-units --type service --state running --no-legend

在这里,我们使用一个命令来获得比以前更漂亮的输出,并且我们只使用一个命令来完成它。

还有更多...

与已加载和运行的服务一样,您可能对已加载、完成其预期工作然后退出的服务感兴趣:

$ systemctl list-units --type service --state exited

或者你可能对失败的服务感兴趣?尝试以下命令:

$ systemctl list-units --type service --state failed

最后,systemd 默认会使用分页器显示结果,虽然对人类友好,但对脚本来说并不理想。要简单地将命令的输出打印到stdout,请在您的命令中添加--no-pager

另请参阅

通常,查询 init 系统是确定系统上运行哪些服务的最佳方法,尽管如果你在 init 之外运行某些东西,比如由cron启动的后台任务,你可能会用pstop更好,我们稍后会介绍。

列出已安装的服务

在本节中,我们将看看如何列出已安装但永远不会在主机上运行的服务,通常是因为它们没有被启用。

准备工作

连接到您的centos1 VM。

$ vagrant ssh centos1

如何做...

以您的用户身份运行以下命令:

$ systemctl list-unit-files --type service

默认情况下,您将会看到一个通常很长的服务列表。其中很多将被列为static,一些将被列为enabled,其他将被列为disabled

就 systemd 而言,这是系统上所有它知道的服务的列表。

工作原理...

我们再次使用systemctl查询 systemd,只是这一次不是打印运行中的服务,而是获取 init 系统知道的所有内容。

服务文件,实际上所有其他类型的单元文件,通常存在于/usr/lib/systemd/system。从这个位置,文件被符号链接到/etc/systemd/system中的runlevel目录中。

如下所示,我们将ls这个目录:

$ ls -la /etc/systemd/system
total 8
drwxr-xr-x. 13 root root 4096 May 12 2018 .
drwxr-xr-x. 4 root root 151 May 12 2018 ..
drwxr-xr-x. 2 root root 32 May 12 2018 basic.target.wants
lrwxrwxrwx. 1 root root 46 May 12 2018 dbus-org.freedesktop.NetworkManager.service -> /usr/lib/systemd/system/NetworkManager.service
lrwxrwxrwx. 1 root root 57 May 12 2018 dbus-org.freedesktop.nm-dispatcher.service -> /usr/lib/systemd/system/NetworkManager-dispatcher.service
lrwxrwxrwx. 1 root root 37 May 12 2018 default.target -> /lib/systemd/system/multi-user.target
drwxr-xr-x. 2 root root 87 May 12 2018 default.target.wants
drwxr-xr-x. 2 root root 38 May 12 2018 dev-virtio\x2dports-org.qemu.guest_agent.0.device.wants
drwxr-xr-x. 2 root root 32 May 12 2018 getty.target.wants
drwxr-xr-x. 2 root root 35 May 12 2018 local-fs.target.wants
drwxr-xr-x. 2 root root 4096 May 12 2018 multi-user.target.wants
drwxr-xr-x. 2 root root 48 May 12 2018 network-online.target.wants
drwxr-xr-x. 2 root root 31 May 12 2018 remote-fs.target.wants
drwxr-xr-x. 2 root root 51 May 12 2018 sockets.target.wants
drwxr-xr-x. 2 root root 217 May 12 2018 sysinit.target.wants
drwxr-xr-x. 2 root root 44 May 12 2018 system-update.target.wants

请注意,我们有一些targets,它们基本上是系统的不同运行级别;您将与之交互的大多数日常服务都位于multi-user.target.wants中,这基本上是多用户会话所需的服务(典型的操作模式)。

在这个子目录中再次运行ls,可以看到之前提到的符号链接及其在磁盘上的位置:

$ ls -la /etc/systemd/system/multi-user.target.wants/
total 8
drwxr-xr-x. 2 root root 4096 May 12 2018 .
drwxr-xr-x. 13 root root 4096 May 12 2018 ..
lrwxrwxrwx. 1 root root 38 May 12 2018 auditd.service -> /usr/lib/systemd/system/auditd.service
lrwxrwxrwx. 1 root root 39 May 12 2018 chronyd.service -> /usr/lib/systemd/system/chronyd.service
lrwxrwxrwx. 1 root root 37 May 12 2018 crond.service -> /usr/lib/systemd/system/crond.service
lrwxrwxrwx. 1 root root 42 May 12 2018 irqbalance.service -> /usr/lib/systemd/system/irqbalance.service
lrwxrwxrwx. 1 root root 46 May 12 2018 NetworkManager.service -> /usr/lib/systemd/system/NetworkManager.service
lrwxrwxrwx. 1 root root 41 May 12 2018 nfs-client.target -> /usr/lib/systemd/system/nfs-client.target
lrwxrwxrwx. 1 root root 39 May 12 2018 postfix.service -> /usr/lib/systemd/system/postfix.service
lrwxrwxrwx. 1 root root 40 May 12 2018 remote-fs.target -> /usr/lib/systemd/system/remote-fs.target
lrwxrwxrwx. 1 root root 46 May 12 2018 rhel-configure.service ->
... 

这些服务都是在多用户会话中启用启动的。

还有更多...

“启用”和“禁用”的概念相对容易理解,这些状态是服务要么尝试运行要么不尝试运行。

静态是另一回事;当一个单元文件存在但未启用时,这是一个使用术语,由于其单元文件的缺少[Install]部分,它没有能力变得启用。

我们可以使用以下行列出这些服务:

$ systemctl --no-pager list-unit-files --type service --state static

随机选择一个服务(sshd-keygen),我们可以查看其服务文件如下:

$ systemctl cat sshd-keygen.service

使用 systemctl 的cat选项很好,因为它还会显示您所询问的单元文件的位置。

我们得到以下内容:

# /usr/lib/systemd/system/sshd-keygen.service
[Unit]
Description=OpenSSH Server Key Generation
ConditionFileNotEmpty=|!/etc/ssh/ssh_host_rsa_key
ConditionFileNotEmpty=|!/etc/ssh/ssh_host_ecdsa_key
ConditionFileNotEmpty=|!/etc/ssh/ssh_host_ed25519_key
PartOf=sshd.service sshd.socket

[Service]
ExecStart=/usr/sbin/sshd-keygen
Type=oneshot
RemainAfterExit=yes

从这个文件中,我们可以看到它有一个PartOf定义,表明它作为sshd服务的一部分运行。

再次查看该服务(再次使用 systemctl cat)会发现以下内容:

# /usr/lib/systemd/system/sshd.service
[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.service
Wants=sshd-keygen.service

[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd -D $OPTIONS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target

在这里我们可以看到Wants部分,表明sshd-keygensshd启动时运行。

这就解释了为什么它不必自行启用。

另请参阅

与 Linux 系统上的大多数组件一样,systemctl命令有一个 man 页面。

在这个 man 页面中,您会找到一个名为is-enabled output的表,您可以在其中了解有关您的状态命令打印的不同术语的更多信息。

我们有一个处于间接状态的服务,表中列出了以下含义:

“单元文件本身没有启用,但在[Install]单元文件部分中有一个非空的 Also=设置,列出了可能已启用的其他单元文件,或者它在不在 Also=中指定的符号链接下有一个不同名称的别名。对于模板单元文件,除了在 DefaultInstance=中指定的实例之外的实例是启用的。”

启动和停止服务

在本节中,我们将看一下启动和停止服务的微不足道但重要的方面。

想象一下一个没有自动启动守护进程的能力的世界;您将不得不手动进入并在每次重新启动时启动您的服务,确保每次以适当的方式启动您的服务。

像受星际之门复制体主导的世界一样,那不是我想要生活的世界。

如何做...

在这个例子中,我们将使用postfix,因为它是一个在我们的 VM 上不会做太多事情的服务。

postfix是一个通常安装在 CentOS 盒子上的邮件传输代理MTA)。即使您的盒子不处理电子邮件,进程也可能使用它向您发送有关故障的警告等信息。

停止我们的服务

运行以下命令(使用sudo):

$ sudo systemctl stop postfix
$ 

请注意,没有输出来确认或否认您输入的内容是否有效。

启动我们的服务

与停止我们的服务一样,再次启动它是微不足道的:

$ sudo systemctl start postfix 
$

再次注意到令人困惑的沉默。

命令完成后的沉默并不是 systemd 独有的,实际上这是 Unix 和类 Unix 世界的一种哲学。如果一个命令已经完成了它应该做的事情,用户就不需要被告知。

它是如何工作的...

当您指示 systemd 启动或停止一个单元时,实际上是运行其单元文件的ExecStartExecStop部分。

postfix为例,其单元文件如下:

# /usr/lib/systemd/system/postfix.service
[Unit]
Description=Postfix Mail Transport Agent
After=syslog.target network.target
Conflicts=sendmail.service exim.service

[Service]
Type=forking
PIDFile=/var/spool/postfix/pid/master.pid
EnvironmentFile=-/etc/sysconfig/network
ExecStartPre=-/usr/libexec/postfix/aliasesdb
ExecStartPre=-/usr/libexec/postfix/chroot-update
ExecStart=/usr/sbin/postfix start
ExecReload=/usr/sbin/postfix reload
ExecStop=/usr/sbin/postfix stop

[Install]
WantedBy=multi-user.target

在这里,我们可以看到,当我们发出systemctl start postfix命令时,它实际上是使用start选项运行postfix二进制文件。相反的是stop

我们还有一些ExecStartPre行,这些是在主ExecStart命令运行之前执行的命令。

还有更多...

没有sudo,您可能会被提示以root身份进行身份验证;为了以root身份运行命令,当我忘记先使用sudo时,我通常会看到这种对话框:

==== AUTHENTICATING FOR org.freedesktop.systemd1.manage-units ===
Authentication is required to manage system services or units.
Authenticating as: root
Password:

您还可以使用status参数快速确认服务是否已启动或停止:

$ systemctl status postfix
● postfix.service - Postfix Mail Transport Agent
 Loaded: loaded (/usr/lib/systemd/system/postfix.service; enabled; vendor preset: disabled)
 Active: inactive (dead) since Sun 2018-08-26 13:43:46 UTC; 2min 56s ago

另请参阅

有关您正在阅读的单元文件的ExecStartExecStop选项的更多信息,请查看有关此主题的特定systemd.service man 页面。

还有其他一些选项不是startstop,包括reload-or-try-restart,这可能会让事情变得更加混乱。请参阅systemctl命令以获取这些选项。

更改启动和停止的服务

在 CentOS 世界中,安装新服务并不会立即启动和启用它,尽管在 Debian 世界中会(这是一个默认设置,再次引起了一些人的支持和反对)。

在这个例子中,我们将启用一个新的服务并重新启动我们的虚拟机,看看它启动时会发生什么。

做好准备

连接到您的centos1虚拟机。为这些示例安装httpd(Apache):

$ sudo yum install -y httpd

如何做...

首先,让我们看看我们是否实际上有一个httpd的单元文件:

$ systemctl cat httpd
# /usr/lib/systemd/system/httpd.service
[Unit]
Description=The Apache HTTP Server
After=network.target remote-fs.target nss-lookup.target
Documentation=man:httpd(8)
Documentation=man:apachectl(8)

[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/httpd
ExecStart=/usr/sbin/httpd $OPTIONS -DFOREGROUND
ExecReload=/usr/sbin/httpd $OPTIONS -k graceful
ExecStop=/bin/kill -WINCH ${MAINPID}
# We want systemd to give httpd some time to finish gracefully, but still want
# it to kill httpd after TimeoutStopSec if something went wrong during the
# graceful stop. Normally, Systemd sends SIGTERM signal right after the
# ExecStop, which would kill httpd. We are sending useless SIGCONT here to give
# httpd time to finish.
KillSignal=SIGCONT
PrivateTmp=true

[Install]
WantedBy=multi-user.target

我们有一个,现在我们需要看看它当前处于什么状态:

$ systemctl status httpd

我们的输出将服务列为inactivedisabled

启用我们的服务

如果我们只想启动我们的服务,我们可以运行前一节中列出的命令,但这不会使服务在启动时启用。

要启用我们的服务,我们运行enable,令人惊讶:

$ sudo systemctl enable httpd
Created symlink from /etc/systemd/system/multi-user.target.wants/httpd.service to /usr/lib/systemd/system/httpd.service.

请注意,由于某种原因,我们在启用服务时确实会得到输出。

重新启动您的服务器,使用systemctl status命令查看httpd是否在启动时启动。

禁用我们的服务

现在我们已经启用了httpd,我们将再次禁用它,因为这就是我们的风格:

$ sudo systemctl disable httpd
Removed symlink /etc/systemd/system/multi-user.target.wants/httpd.service.

它是如何工作的...

当我们禁用和启用服务时,在输出中看到的实际上是在我们的httpd单元文件的默认位置和multi-user.target.wants目录之间创建一个symlink,如前所述。

在启动时,systemd 将在适当的时间检查此目录并启动它发现的服务。

还有更多...

同时启动和启用服务是完全可能的,绝对比重新启动服务器更可取来改变服务的状态。

通过在我们的enable命令中插入--now可以轻松实现这一点:

$ systemctl enable --now httpd

实际上,我们在一行上运行了以下操作:

$ systemctl enable httpd
$ systemctl start httpd

另请参阅

systemd 对targets的概念与您可能熟悉或不熟悉的老式runlevel类似。这不是直接的比较,因为 systemd 可以同时激活多个 targets,而 runlevels 是单数的。multi-user.target大致相当于 runlevels 2、3 和 4。

请参阅systemd.target手册以获取更多信息。

您可能期望看到的常见服务

在本节中,我们将列出您可能期望在给定服务器上看到的常见服务。这个列表不会是全面的,因为默认情况下会有哪些服务可能会发生变化,甚至在发行版之间也会有所不同。

如何做...

列出系统上的服务,甚至包括那些静态和禁用的服务:

$ systemctl list-unit-files --type service

通过使用默认分页器(less)并使用/进行搜索,浏览列表。

auditd.service

从头开始,我们有auditd.service,"Linux 审计守护程序"。用于编写系统的审计记录,您将在/var/log/audit/目录中找到其工作的成果。

chronyd.service

如我们在讨论系统时间时所讨论的,chronyd负责保持系统时钟的准确性。我期望在大多数服务器上都能看到这个或ntpd在运行。

crond.service

不要与chronyd混淆,crond是负责确保按计划运行命令的守护程序。

lvm2-*.service

lvm2服务文件的集合用于维护和监视逻辑卷管理器LVM)设置。您可能会发现大多数服务器都在使用 LVM 来管理文件系统和卷。

NetworkManager.service

我们已经详细介绍了网络,但了解NetworkManager.service文件是其中几个带有大写字母的让人讨厌的服务之一是很有用的。

这个事实已经在不止一个场合让我措手不及。

nfs.service

通常安装的nfs服务套件用于管理网络文件系统NFS)设备。

NFS 仍然被广泛使用,并且由发行版生产商进行了很好的维护,这意味着即使你不使用它,它可能仍然存在。

postfix.service

作为典型的 MTA,postfix是您在 Red Hat、CentOS 和 Fedora 系统上看到的默认选项。在其他系统上,可能是 Exim 邮件服务器。稍后我们将简要介绍这些内容。

rsyslog.service

在您的服务器上可能已经安装了rsyslogsyslog-ng,至少在可预见的未来是这样。这些是系统日志守护程序,负责将日志写入/var/log(通常)。它们在喜欢二进制日志的 journald中有竞争对手,我们稍后会谈到。

sshd.service

我希望在我连接的任何系统上都能看到sshd.service运行,否则我就不知道我用了什么令人困惑的方法来连接。

systemd-journald.service

syslog的竞争对手journald是由 systemd 管理的日志守护程序,可以使用命令行上的一系列句法汤进行查询。

systemd-logind.service

systemd的另一个来自 Borg 的服务,logind管理您是管理员的系统的用户登录。

它是如何工作的...

这些服务以及您可能在计算机上运行的其他服务,实际上构成了您正在运行的操作系统。

如果你像我一样,可能会在业余时间玩弄这个列表,试图弄清楚哪些部分可以禁用,而最终仍然有一个正常运行的系统。

通常建议保持默认设置不变,因为你永远不知道哪个随机的作业正在清理临时目录,而你甚至没有意识到。

默认服务经常被禁用作为加固任务的一部分;如果你发现自己不得不加固一个系统,通常的规则仍然适用。了解当前的最佳实践是什么,并查看您的供应商是否有现成的指南。

还有更多...

我们可以使用systemd-analyze等方便的工具来查看系统启动所需的时间:

$ systemd-analyze 
Startup finished in 253ms (kernel) + 933ms (initrd) + 6.873s (userspace) = 8.060s

8 秒并不算糟糕,不包括 VirtualBox 初始化我们的虚拟机所需的时间和内核启动所需的时间(在 Grub 引导菜单上为 5 秒)。

你甚至可以在命令的末尾加上blame来运行这个命令,以查看哪个进程占用了你宝贵的时间:

$ systemd-analyze blame
 3.811s NetworkManager-wait-online.service
 806ms tuned.service
 680ms postfix.service
 490ms lvm2-monitor.service
 ...

另请参阅

查看其他系统默认启动的服务。例如,Ubuntu 桌面版可能不会启动auditd,但在启动计算机时可能会有gdm来确保 Gnome 登录窗口。

理解服务单元文件

现在我们已经很清楚在哪里找到服务单元文件,以及如何启用和禁用服务。

之前,我们提到了单元文件中的一些条目,尽管我们并没有深入探讨这些选项或者在哪里找到更多信息和您可能想要使用的替代条目。

如何做...

我们将以sshd.service为例,不仅因为它是您可能经常看到的服务文件,而且因为它具有很好的默认条目混合。

cat我们选择的服务:

$ systemctl cat sshd.service
# /usr/lib/systemd/system/sshd.service
[Unit]
Description=OpenSSH server daemon
Documentation=man:sshd(8) man:sshd_config(5)
After=network.target sshd-keygen.service
Wants=sshd-keygen.service

[Service]
Type=notify
EnvironmentFile=/etc/sysconfig/sshd
ExecStart=/usr/sbin/sshd -D $OPTIONS
ExecReload=/bin/kill -HUP $MAINPID
KillMode=process
Restart=on-failure
RestartSec=42s

[Install]
WantedBy=multi-user.target

要理解这个文件,我们必须将其分解为其组成部分。

Unit文件的Main部分,[Unit]是通用条目的区域,不是Service类型的特定条目:

[Unit]

对于描述,我们有一个简短而简洁的条目,一目了然:

Description=OpenSSH server daemon

我们还有一行方便的信息,详细说明了如果遇到困难应该查看的适当 URI 位置。这里我们有man:作为 URI,但它也可以是https://甚至是info::

Documentation=man:sshd(8) man:sshd_config(5)

info:指的是信息页面,类似于 man 页面,但更广泛地受到嘲笑。After=(和Before=)是一个以空格分隔的单元名称列表,指定需要在此之后(或之前)启动的服务。这里需要启动网络,并且在sshd启动之前需要运行ssh-keygen

After=network.target sshd-keygen.service

我们之前解析了Wants=,但为了更详细地解释一下,你通常会在单元文件中看到Wants=,列出了在启动此服务之前可能触发的服务:

Wants=sshd-keygen.service

Wants=Requires=的一个不那么强制的版本。如果Wants=服务启动失败,父服务仍然会尝试,如果Requires=服务失败,父服务也不会启动。

特定于service类型的单元文件,[Service]块是特定于服务选项的,可能很明显:

[Service]

进程启动类型,notify在这里表示 systemd 期望守护进程在启动完成后发送通知消息。只有在收到此通知后,systemd 才会继续启动依赖于此服务的服务:

Type=notify

用于指向包含其环境变量的文件的服务,对于sshd,这在我们的盒子上包含一个选项,OPTIONS="-u0"

EnvironmentFile=/etc/sysconfig/sshd

当服务启动时运行的关键是ExecStart,它给出了要运行的命令。还要注意从EnvironmentFile行指定的文件中读取的$OPTIONS值:

ExecStart=/usr/sbin/sshd -D $OPTIONS

这一部分告诉 systemd 如果运行systemctl reload sshd命令时应该运行什么。具体来说,我们向sshdPID 发送HUP(挂起)信号:

ExecReload=/bin/kill -HUP $MAINPID

这个指令指定了如何杀死该单元自己创建的进程。这里的process意味着只有主进程本身被 systemd 杀死:

KillMode=process

这里使用了KillMode,但没有使用ExecStop。这是因为ExecStop是可选的,通常只有在 init 守护程序有特定的清理工作要做时才会使用。

我们的Restart选项告诉 systemd 如何重新启动进程。这里使用的on-failure表示sshd将在退出代码不干净、不干净的信号、超时或服务的看门狗超时被触发时重新启动:

Restart=on-failure

RestartSec是在满足Restart=标准后重新启动sshd服务之前需要的时间。我想这里是 42 秒,因为单元文件的作者是道格拉斯·亚当斯的粉丝:

RestartSec=42s

安装是另一个通用部分,例如[Unit]。该部分保存了单元文件的安装信息,这意味着在运行时被 enable 和 disable 指令读取:

[Install]

这里唯一的指令是这个服务是WantedBy多用户目标,这意味着在多用户模式下,sshd将被启动:

WantedBy=multi-user.target

工作原理...

当 systemd 与服务文件交互时,它读取的文件部分决定了它的操作。

如果启动服务,将读取ExecStart;如果停止,将读取ExecStop

还有更多...

单元文件的作者可以使用多种选项,并且每个 systemd 版本都会添加更多选项。你有很大的机会可以通过单元文件条目实现你想要的操作。

尽管单元文件作者可以使用多种选项,但仍然有人坚持编写一个 bash 脚本来完成所有操作,并在四行单元文件中简单地引用它。这是可能的,但不够整洁。

另请参阅

如果你有一个空闲的下午,可以阅读systemd.servicesystemd.unit手册页;它们都很长,是睡眠药片的良好替代品。

自定义 systemd 单元文件

在本节中,我们将看看如何修改 systemd 单元文件的方法。

这里有一个重要的说明,即虽然你绝对可以修改配置文件,但不能保证你的更改会在系统更新后持久存在。软件包管理器可能会对你篡改他们的服务文件感到不满,并在没有警告的情况下替换它们。

修改 systemd 单元文件操作的正确方法是编写另一个包含你的更改的文件。

systemd 执行此操作的方法称为片段。

操作步骤...

systemd 有一种内置的方法来生成我们需要的覆盖文件。

使用以下命令创建sshd.service单元的目录和文件:

$ sudo systemctl edit sshd.service

您将被放置在一个空文件中,但这个文件存在于一个新的目录中,即/etc/systemd/system/sshd.service.d/override.conf

将以下内容复制到我们的空文件中:

[Unit]
Description=OpenSSH server daemon slightly modified
Documentation=man:ssh-additional
Requires=sshd-keygen.service

[Service]
Environment=OPTIONS="-u0"
ExecStart=
ExecStart=/usr/sbin/sshd -4 -D $OPTIONS
RestartSec=10s

当我们保存并退出文件时,会隐式运行systemctl daemon-reload,这意味着当我们运行systemctl restart sshd时,我们的新设置将生效。

工作原理...

我们说我们的edit命令创建了一个新的目录和文件,可以在其中放置覆盖命令;您现在可以通过更改到它来访问这个目录:

$ cd /etc/systemd/system/sshd.service.d/
$ ls 
override.conf

在我们的覆盖文件中,我们存储了新的条目。这些条目通常是主配置文件中的补充。

分解我们的新配置,我们有以下内容:

[Unit]
Description=OpenSSH server daemon slightly modified
Documentation=man:ssh-additional
Requires=sshd-keygen.service

在这里,我们添加了文件的简短描述,显示它稍微修改了。有一个虚假的手册页面条目,我们建议在编写文档时可能是一个不错的地方。我们还更改了服务,使其现在需要sshd-keygen,而不仅仅是想要它。

现在,我们更改服务部分:

[Service]
EnvironmentFile=
Environment=OPTIONS="-u0"
ExecStart=
ExecStart=/usr/sbin/sshd -4 -D $OPTIONS
RestartSec=10s

在这里,我们添加了我们的Environment指令,而不是使用EnvironmentFile(我们已经清空了)。

我们也清空了ExecStart,并传入我们自己的(我们已经添加了-4)。

因为我们觉得无聊,所以我们决定希望sshd在 10 秒内重新启动,而不是42

还有更多...

并不总是明显哪些值可以堆叠在一起,哪些必须首先清空。

要测试您的配置是否被正确加载,请使用systemctl show sshd来读取服务的运行配置。

在输出中,我找到了以下行(Documentation可以堆叠,所以我们的新条目只是添加到末尾):

Documentation=man:sshd(8) man:sshd_config(5) man:ssh-additional

Description被覆盖了,因为您只能有一个条目,所以我们的优先级较高:

Description=OpenSSH server daemon slightly modified

没有EnvironmentFile选项,唯一的条目是我们的Environment行:

Environment=OPTIONS="-u0"

只有一行ExecStart,而且是我们的,因为我们清空了原始的:

ExecStart={ path=/usr/sbin/sshd ; argv[]=/usr/sbin/sshd -4 -D $OPTIONS ...

另请参阅

还有另一种方法可以对自己的单元文件进行更改,但这种方法比较混乱。实际上,您需要编写一个完整的单元文件,更改您想要的选项,然后将新文件放在/etc/systemd/system/中,然后重新启用服务。这种方法有效的原因是因为/etc/systemd/system/的优先级高于/usr/lib/systemd/system/,尽管现在您已经自己承担了整个服务定义的管理,而不仅仅是您想要更改的部分。

测试运行的服务

在本节中,我们将看看三种方法,一旦我们发出start命令,就可以看到服务是否真的在运行。

我们将从内置的 systemd 方式(systemctl)开始,然后转向通用方式(ps),最后是简单方式(telnet)。

准备工作

连接到您的centos1虚拟机。如果尚未启动,请安装并启动httpd

我们还将安装telnet进行一些基本的端口检查:

$ sudo yum install -y httpd telnet
$ sudo systemctl enable --now httpd

如何做...

使用 systemd 的内置工具,我们可以使用status选项来检查状态:

[vagrant@centos1 ~]$ systemctl status httpd
● httpd.service - The Apache HTTP Server
 Loaded: loaded (/usr/lib/systemd/system/httpd.service; enabled; vendor preset: disabled)
 Active: active (running) since Sun 2018-08-26 16:15:50 UTC; 5min ago
 Docs: man:httpd(8)
 man:apachectl(8)
 Main PID: 3578 (httpd)
 Status: "Total requests: 0; Current requests/sec: 0; Current traffic:   0 B/sec"
 CGroup: /system.slice/httpd.service
 ├─3578 /usr/sbin/httpd -DFOREGROUND
 ├─3579 /usr/sbin/httpd -DFOREGROUND
 ├─3580 /usr/sbin/httpd -DFOREGROUND
 ├─3581 /usr/sbin/httpd -DFOREGROUND
 ├─3582 /usr/sbin/httpd -DFOREGROUND
 └─3583 /usr/sbin/httpd -DFOREGROUND

这是一个很好的迹象,因为 systemd 认为它们是正常的。我们还可以使用ps工具来尝试确定我们的进程是否已启动:

[vagrant@centos1 ~]$ ps aux | grep httpd
root      3578  0.0  1.0 224020  4996 ?        Ss   16:15   0:00 /usr/sbin/httpd -DFOREGROUND
apache    3579  0.0  0.5 224020  2948 ?        S    16:15   0:00 /usr/sbin/httpd -DFOREGROUND
apache    3580  0.0  0.5 224020  2948 ?        S    16:15   0:00 /usr/sbin/httpd -DFOREGROUND
apache    3581  0.0  0.5 224020  2948 ?        S    16:15   0:00 /usr/sbin/httpd -DFOREGROUND
apache    3582  0.0  0.5 224020  2948 ?        S    16:15   0:00 /usr/sbin/httpd -DFOREGROUND
apache    3583  0.0  0.5 224020  2948 ?        S    16:15   0:00 /usr/sbin/httpd -DFOREGROUND

在这里,我使用aux选项,部分原因是因为我可以在我的 BSD 系统上可靠地执行相同的操作,另一部分原因是因为我最初观看ps使用这些标志的人,所以这些标志一直留在我心中。

我们可以看到httpd正在运行几个进程。

或者,我们可以尝试使用telnet在本地连接端口:

[vagrant@centos1 ~]$ telnet 127.0.0.1 80
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.

尽管如果您的 Web 服务器不在 localhost 上运行,或者不在80端口上运行,这个测试就有点无意义,并且会失败。

工作原理...

我们已经介绍了三种检查服务是否正在运行的方法。第一种,也可以说是最可靠的,是查看 init 系统是否认为服务正在运行。

我们的systemctl命令报告了一个活动的、运行中的状态,并给出了服务启动的时间。

接下来,我们查询系统的进程列表,看看是否能找到我们的服务器;这很有效,因为可能出现这样的情况,即配置错误的服务文件启动了您的服务,但随后不知道它的状态,因此认为它已经停止。

最后,我们使用telnet尝试连接到我们认为服务可能正在运行的端口;这是检查服务是否在本地运行的最不智能的方法,因为它需要您知道具体细节,而且telnet通常不会默认安装。

还有更多...

要获取有关系统上打开套接字的信息,您将使用ss;有关可能用于确定已使用端口的命令的详细信息,请参阅ss的早期部分。

编写一个基本的 unit 文件

在本节中,我们将组合一个我们自己的 unit 文件。我们还将选择一个位置保存它,并重新启动我们的系统以检查它是否有效。

如何做...

我们将再次使用 Python 的内置 Web 服务器,使用一个小的 unit 文件启动一个实例。

首先,创建我们将存储 unit 文件的目录:

$ sudo mkdir -p /usr/local/lib/systemd/system

接下来,我们将把以下内容回显到一个名为pythonWebServer.service的文件中:

$ sudo tee /usr/local/lib/systemd/system/pythonWebServer.service << HERE
[Unit]
Description=Python Web Server Example

[Service]
ExecStart=/usr/bin/python2.7 -m SimpleHTTPServer

[Install]
WantedBy=multi-user.target
HERE

此 unit 文件仅用于演示目的,绝对不应在生产中使用。

现在我们可以启用并启动它:

$ sudo systemctl enable --now pythonWebServer
Created symlink from /etc/systemd/system/multi-user.target.wants/pythonWebServer.service to /usr/local/lib/systemd/system/pythonWebServer.service.

使用status命令检查它是否正在运行:

$ systemctl status pythonWebServer.service 
● pythonWebServer.service - Python Web Server Example
 Loaded: loaded (/usr/local/lib/systemd/system/pythonWebServer.service; enabled; vendor preset: disabled)
 Active: active (running) since Sun 2018-08-26 16:43:55 UTC; 1s ago
 Main PID: 3746 (python2.7)
 CGroup: /system.slice/pythonWebServer.service
 └─3746 /usr/bin/python2.7 -m SimpleHTTPServer

它是如何工作的...

我们在这里所做的一切只是创建了一个本地 unit 文件,以便 systemd 读取和加载。

为了理解我们的路径选择,请尝试在您的系统上运行以下命令,并查看返回的内容:

$ systemctl --no-pager show --property=UnitPath

其中一个选项应该是/usr/local/lib/systemd/system,一开始不存在,但在我们创建它后就存在了。

使用这样的路径是个好主意,因为不仅有很大的可能性它是空的,这意味着您可以逻辑上将您的 unit 文件与其他文件分开,而且它也是整洁的(不像/etc/systemd/system/目录)。

然后,我们将一个小的示例 unit 文件放在这个目录中,其中包含使服务文件正常运行所需的最低限度。

一旦编写完成,只需启用并启动我们的服务。

还有更多...

我们在这里所做的有一些问题。

第一点是,我们创建了一个非常静态的服务文件,只能通过直接操作来更改。第二个问题是,我们创建了一个默认情况下将列出服务器根目录内容的 Web 服务器,我非常怀疑你想要这样:

$ curl localhost:8000
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 3.2 Final//EN"><html>
<title>Directory listing for /</title>
<body>
<h2>Directory listing for /</h2>
<hr>
<ul>
<li><a href="bin/">bin@</a>
<li><a href="boot/">boot/</a>
<li><a href="dev/">dev/</a>
<li><a href="etc/">etc/</a>
<li><a href="home/">home/</a>
<li><a href="lib/">lib@</a>
<li><a href="lib64/">lib64@</a>
<li><a href="media/">media/</a>
<li><a href="mnt/">mnt/</a>
<li><a href="opt/">opt/</a>
<li><a href="proc/">proc/</a>
<li><a href="root/">root/</a>
<li><a href="run/">run/</a>
<li><a href="sbin/">sbin@</a>
<li><a href="srv/">srv/</a>
<li><a href="sys/">sys/</a>
<li><a href="tmp/">tmp/</a>
<li><a href="usr/">usr/</a>
<li><a href="vagrant/">vagrant/</a>
<li><a href="var/">var/</a>
</ul>
<hr>
</body>
</html>

让我们使用到目前为止学到的知识来解决这两个问题。

打开您的新 systemd unit 文件进行编辑,使用您选择的编辑器,并填充它的一些其他选项:

[Unit]
Description=Python Web Server Example
Documentation=man:python(1)
ConditionFileNotEmpty=/var/www/html/index.html
After=network.target

[Service]
Type=simple
EnvironmentFile=/etc/sysconfig/pythonWebServer
ExecStart=/usr/bin/python2.7 -m SimpleHTTPServer $PORT
Restart=always
WorkingDirectory=/var/www/html/

[Install]
WantedBy=multi-user.target

注意我们之前使用过的一些内容,以及一些新的内容,比如WorkingDIrectory=

接下来,填充/var/www/html/index.html

$ sudo tee /var/www/html/index.html << HERE
This is a python web server.
Running at the behest of systemd.
Isn't that neat?
HERE

并向我们的环境文件添加一个条目:

$ sudo tee /etc/sysconfig/pythonWebServer <<HERE
PORT="8000"
HERE

重新加载 systemd 的配置:

$ sudo systemctl daemon-reload

并使用curl进行测试:

$ curl localhost:8000
This is a python web server.
Running at the behest of systemd.
Isn't that neat?

另请参阅

systemd unit 文件有很多不同的配置选项;我们在这里并没有真正深入探讨,这绝对可以构成一本书。

阅读相关的 man 页面,尝试编写自己的 unit 文件,并用您的发现回报,你勇敢的探险者。

使用 systemd 定时器(和 cron)

新来的孩子,以及 systemd 引入其庞大自身的另一个组件,是 systemd 定时器。定时器是另一种类型的 unit,只是作为另一个 unit 触发的指令。

在旧世界中,您可以使用cron来控制系统上的周期性事件,这仍然被广泛使用,但是越来越多的 systemd 定时器正在夺走这个位置。

我说,但新是相对的。基本上,某些东西可能在 Debian 或 CentOS 中滞留多年,然后最终才进入 systemd。如果您想要最新和最好的东西,可以在笔记本电脑上运行 Fedora 之类的系统。

在本节中,我们将查看系统上现有的cron和定时器条目,然后将我们的 Python 服务器转换为定时器触发的服务。

如何做...

首先,我们将列出系统上现有的周期性作业。

systemd 定时器

从 systemd 定时器开始,我们将列出默认情况下我们的盒子上存在的内容:

$ systemctl --no-pager list-timers --all
NEXT                         LEFT     LAST                         PASSED    UNIT                         ACTIVATES
Mon 2018-08-27 17:39:37 UTC  23h left Sun 2018-08-26 17:39:37 UTC  52min ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service
n/a                          n/a      n/a                          n/a       systemd-readahead-done.timer systemd-readahead-done.service

2 timers listed.

在这个输出中,我们可以看到两个定时器。第一个是systemd-tmpfiles-clean.timer,它的动作是触发systemd-tmpfiles-clean.service

对这个文件运行systemctl cat会显示它的定时器配置:

# /usr/lib/systemd/system/systemd-tmpfiles-clean.timer
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Daily Cleanup of Temporary Directories
Documentation=man:tmpfiles.d(5) man:systemd-tmpfiles(8)

[Timer]
OnBootSec=15min
OnUnitActiveSec=1d

查看服务文件会揭示实际运行的内容:

# /usr/lib/systemd/system/systemd-tmpfiles-clean.service
#  This file is part of systemd.
#
#  systemd is free software; you can redistribute it and/or modify it
#  under the terms of the GNU Lesser General Public License as published by
#  the Free Software Foundation; either version 2.1 of the License, or
#  (at your option) any later version.

[Unit]
Description=Cleanup of Temporary Directories
Documentation=man:tmpfiles.d(5) man:systemd-tmpfiles(8)
DefaultDependencies=no
Conflicts=shutdown.target
After=systemd-readahead-collect.service systemd-readahead-replay.service local-fs.target time-sync.target
Before=shutdown.target

[Service]
Type=oneshot
ExecStart=/usr/bin/systemd-tmpfiles --clean
IOSchedulingClass=idle

请注意,这是一个oneshot服务,意味着它在运行后预期会退出。

根据之前的信息,我们可以知道我们的定时器上次运行的时间和下次运行的时间。

我们可以看到的第二个定时器,systemd-readahead-done.timer及其伴随的服务文件,没有激活。这是通过各种时间字段中的n/a细节表示的。这个服务用于非虚拟化系统记录磁盘引导模式,试图加快随后的引导速度。

这使得定时器比cron更容易阅读和计算上次运行的时间。

cron

我不知道有没有一种简单的方法来列出cron的信息;如果你知道,请写信给我,让我大吃一惊。

正如我们之前所说,cron是一个按计划执行命令的守护进程。它仍然被广泛使用,有许多系统在其中使用了混合的 systemd 定时器/cron设置。

默认情况下,cron/etc/中包含以下目录:

cron.d/       cron.daily/   cron.hourly/  cron.monthly/ cron.weekly/

cron.d中,我们可以看到一个名为0hourly的文件,其内容如下所示:

cat 0hourly 
# Run the hourly jobs
SHELL=/bin/bash
PATH=/sbin:/bin:/usr/sbin:/usr/bin
MAILTO=root
01 * * * * root run-parts /etc/cron.hourly

而在/etc/cron.hourly目录中,我们只有0anacron

#!/bin/sh
# Check whether 0anacron was run today already
if test -r /var/spool/anacron/cron.daily; then
 day=`cat /var/spool/anacron/cron.daily`
fi
if [ `date +%Y%m%d` = "$day" ]; then
 exit 0;
fi

# Do not run jobs when on battery power
if test -x /usr/bin/on_ac_power; then
 /usr/bin/on_ac_power >/dev/null 2>&1
 if test $? -eq 1; then
 exit 0
 fi
fi
/usr/sbin/anacron -s

可读性不强。

乍一看,要弄清楚通过cron定期运行的作业是很尴尬和麻烦的,你总会遇到那种没有任何日志记录的临时作业,而且他们自己都忘了。

很有可能在你的生活中某个时候cron会让你失望,至少直到它永远消失,所以如果你的系统出现意外行为,停下来快速浏览一下cron目录,看看有没有什么不对劲。

你可以很容易地从journalctl中获取具体的cron日志:

$ sudo journalctl -u crond
-- Logs begin at Sun 2018-08-26 17:24:36 UTC, end at Sun 2018-08-26 19:18:33 UTC
Aug 26 17:24:39 centos1 systemd[1]: Started Command Scheduler.
Aug 26 17:24:39 centos1 systemd[1]: Starting Command Scheduler...
Aug 26 17:24:39 centos1 crond[579]: (CRON) INFO (RANDOM_DELAY will be scaled wit
Aug 26 17:24:40 centos1 crond[579]: (CRON) INFO (running with inotify support)

它是如何工作的...

systemd 定时器很棒;它们也工作得很好,主要是因为它们与它们管理的时间触发的 init 和 unit 系统紧密相连。

在一个完美的世界中,我希望看到在它们的下一个版本中完全删除 CentOS 和 Debian 中的所有cron条目。事实是cron可能会在很长一段时间内存在,而像 FreeBSD 这样的系统将在宇宙热死之后很长时间内使用它。

当我们列出定时器时,我们正在检查 systemd 在单位触发方面的意识。

当我们在cron日志和子目录中查找时,我们正在试图找到那个不断触及我们试图删除的文件的模糊一行的隐喻性努力。

还有更多...

我们将编写我们自己的定时器单元,使用上一个示例中的 Python 单元文件。

如果你仍在运行它,我建议首先禁用服务,或者销毁虚拟机并重新编写,必要时参考最后一节。

让我们首先调整我们的pythonWebServer.service文件,使其看起来像下面这样:

[Unit]
Description=Python Web Server Example
Documentation=man:python(1)
ConditionFileNotEmpty=/var/www/html/index.html

[Service]
Type=simple
EnvironmentFile=/etc/sysconfig/pythonWebServer
ExecStart=/usr/bin/python2.7 -m SimpleHTTPServer $PORT
WorkingDirectory=/var/www/html/

具体来说,我们删除了一些行。请注意,完全删除了[Install]部分,因为当定时器控制启动时,它是不需要的。

在现实世界中,这不会是像网页服务器这样的永久性东西,通常会是一些小东西,比如一个在某个墙板上打开网页的脚本,或者发送请求给咖啡机给你做拿铁。

接下来,我们将编写一个timer文件,将其放在与我们的service文件完全相同的位置:

$ sudo tee /usr/local/lib/systemd/system/pythonWebServer.timer << HERE
[Unit]
Description=Start python web server after a pause

[Timer]
OnBootSec=20s

[Install]
WantedBy=timers.target
HERE

之后,我们将重新加载 systemd 并启用定时器,但不启用服务:

$ sudo systemctl daemon-reload 
$ sudo systemctl enable pythonWebServer.timer
Created symlink from /etc/systemd/system/timers.target.wants/pythonWebServer.timer to /usr/local/lib/systemd/system/pythonWebServer.timer.

注意symlink的创建。

让我们重新启动我们的服务器,看看会发生什么。如果你很快,而且你设法在十秒钟内运行systemctl list-timers,你可能会看到类似以下的东西:

$ systemctl --no-pager list-timers
NEXT                         LEFT       LAST PASSED UNIT                         ACTIVATES
Sun 2018-08-26 19:43:48 UTC  9s left    n/a  n/a    pythonWebServer.timer        pythonWebServer.service
Sun 2018-08-26 19:58:28 UTC  14min left n/a  n/a    systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service

2 timers listed.
Pass --all to see loaded but inactive timers, too.

再次运行将显示作业已经通过了:

$ systemctl --no-pager list-timers
NEXT                         LEFT       LAST                         PASSED UNIT                         ACTIVATES
Sun 2018-08-26 19:43:48 UTC  10s ago    Sun 2018-08-26 19:43:56 UTC  3s ago pythonWebServer.timer        pythonWebServer.service
Sun 2018-08-26 19:58:28 UTC  14min left n/a                          n/a    systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service

2 timers listed.
Pass --all to see loaded but inactive timers, too.

一个快速的curl将确认我们的服务已经启动:

$ curl localhost:8000
This is a python web server.
Running at the behest of systemd.
Isn't that neat?

另请参阅

你可以以与任何其他单元相同的方式检查 systemd 定时器的状态。

以下是用于检查我们的pythonWebServer.timer状态的输出和命令:

$ systemctl status pythonWebServer.timer
● pythonWebServer.timer - Start python web server after a pause
 Loaded: loaded (/usr/local/lib/systemd/system/pythonWebServer.timer; enabled; vendor preset: disabled)
 Active: active (running) since Sun 2018-08-26 19:43:30 UTC; 56s ago

其他 init 系统

systemd 可能是 Linux 世界中占主导地位的 init 系统,但你仍然会遇到其他系统。在撰写本文时,CentOS 6 仍然非常活跃,具有其 upstart 核心。

这还没有涉及到 BSD 和 macOS 启动时使用的细节(我实际上相当喜欢)。

我曾经见过一些非常古老的 Debian 安装,其中一些在半个多世纪前就已经停止支持了。我曾经计算过,我通过 SSH 连接的一台服务器,现在已经可以合法驾驶了。

准备工作

连接到你的其他两个虚拟机:

$ vagrant ssh centos6
$ vagrant ssh debian7

如何做到...

我们有两个系统在运行;CentOS 6 使用 Upstart,而 Debian 7 使用 SysV init。

CentOS 6 和 Upstart

在旧世界中,systemctl不起作用:

$ systemctl
-bash: systemctl: command not found

还有一点令人略感恼火的是,没有upstart命令。

相反,Upstart被设计成看起来像一个典型的init系统。主页甚至将其列为这样:

“init:Upstart 进程管理守护程序”

我们使用service --status-all而不是systemctl

$ service --status-all

这将给你类似于以下的输出:

smartd is stopped
openssh-daemon (pid  1526) is running...
tuned (pid  1705) is running...

令人困惑的是,服务名称可能与你看到的不同。以sshd为例。前面的命令可能会让你相信这个系统上的守护进程被称为openssh-daemon,但这是不正确的;它实际上还是sshd

$ sudo service openssh-daemon
openssh-daemon: unrecognized service
$ sudo service sshd status
openssh-daemon (pid  1526) is running...

感到困惑吗?我第一次看到这个时也是。

与服务交互的另一种方式是直接调用它们(因为它们实际上只是一个脚本):

$ sudo /etc/init.d/sshd status
openssh-daemon (pid  4725) is running...

启动和停止非常相似,但会让你困惑的一件事是,传统的init系统将控制命令放在行的末尾(与 systemd 不同):

$ sudo service sshd stop
Stopping sshd:                                             [  OK  ]
$ sudo service sshd start
Starting sshd:                                             [  OK  ]

注意在末尾添加了stopstart

禁用和启用服务也是不同的;在旧的 CentOS 系统上,chkconfig是你最好的朋友:

$ chkconfig --list sshd
sshd               0:off    1:off    2:on    3:on    4:on    5:on    6:off

注意运行级别的概念仍然存在,并且sshd234中启动。

我们可以使用另一个chkconfig命令禁用此服务:

$ sudo chkconfig sshd off
$  chkconfig --list sshd
sshd               0:off    1:off    2:off    3:off    4:off    5:off    6:off

然后我们使用on来启用它:

$ sudo chkconfig sshd on
$ chkconfig --list sshd
sshd               0:off    1:off    2:on    3:on    4:on    5:on    6:off

因为我们正在处理运行级别,你可以前往rc*.d目录。具体来说,我们将进入rc3.d来查看运行级别3

$ pwd
/etc/rc3.d
$ runlevel
N 3

在这个目录中,我们再次处理符号链接。你会发现在进入运行级别3时运行的所有作业的列表。

其中一个将是sshd(假设你在上一步没有禁用它):

$ ls -l | grep ssh
lrwxrwxrwx. 1 root root 14 Aug 26 20:13 S55sshd -> ../init.d/sshd

Debian 7 和 SysV init

与 CentOS 6 不同,Debian 7 使用了一个更旧的init系统,其手动页将其列为如下:

“init,telinit:进程控制初始化”

再次,我们正在处理系统上的 PID 1——这一点没有改变,只是现在进程主要由/etc/inittab文件控制。

运行级别再次是一个主要因素,尽管我们使用不同的命令来发现我们所处的位置(作为普通用户,root仍然可以访问runlevel命令,如果你想使用它的话):

$ who -r
 run-level 2  2018-08-26 19:54                   last=S

upstart一样,我们服务文件的实际目的地是/etc/init.d/目录:

$ ls | grep ssh
ssh

这意味着,与Upstart一样,我们可以手动与我们的服务脚本交互:

$ /etc/init.d/ssh status
[ ok ] sshd is running.

我们之前使用的工具(chkconfig)可以安装在 Debian 7 上,但默认情况下我们使用一个叫做update-rc.d的工具来控制启动和停止服务:

$ sudo update-rc.d ssh disable 
update-rc.d: using dependency based boot sequencing

如果你真的想使用chkconfig,我不会责怪你:

vagrant@wheezy:~$ sudo chkconfig ssh off
vagrant@wheezy:~$ sudo chkconfig ssh 
ssh  off
vagrant@wheezy:~$ sudo chkconfig ssh on
vagrant@wheezy:~$ sudo chkconfig ssh 
ssh  on

工作原理...

Upstart和传统的init系统都依赖于/etc/init.d/目录,然后是各种rc目录,以指示它们在哪个运行级别启动:

rc0.d/ rc1.d/ rc2.d/ rc3.d/ rc4.d/ rc5.d/ rc6.d/ rcS.d/ 

查看 Debian 的inittab,我们可以看到默认的runlevel配置为:

# The default runlevel.
id:2:initdefault:

因此,我们知道我们很可能最终会到达运行级别 2,这意味着我们可以检查在该运行级别启动的服务。

对于Upstart也是如此,在我们的 CentOS 系统上,我们可以看到默认设置为 3:

id:3:initdefault:

尽管必须说,这实际上是inittab在 Upstart 系统上的唯一功能。

还有更多...

老实说,Upstart还有很多内容,传统的init系统也是如此,但我建议如果你要在 Linux 和现代系统上工作,你应该把学习 systemd 作为首要任务。

如果你在 BSD 系统上工作,了解一下它们自己的init系统以及它们的区别。

如果你正在使用老服务器,而且不太可能被允许随时关闭,我很抱歉。

另请参阅

upstart.ubuntu.com/cookbook/上有关于Upstart的很好的文档。

如果你能从互联网的角落找到它们,还有关于 Debian 技术委员会对他们的新init系统投票事件的很好的记录。

总结 - 服务和守护进程

这一部分的内容比我预期的要长得多,但与此同时,我很高兴 Ubuntu、CentOS、Red Hat 和 Debian 现在共享一个init系统。

如果我四年前写这本书,我可能早就放弃了这一部分的开头,去爬山了。

话虽如此,这已经结束了,我希望你在最后几页学到了一些关于 systemd 如何工作的东西。

最后要注意的是,尽管 systemd 现在已经在所有主要系统中,但它仍然有批评者,你肯定会遇到一些无法放弃他的基于 bash 脚本的系统的抱怨的系统管理员。在这种情况下,我的建议是微笑点头;不值得太过介入——或者建议他们是否愿意尝试一下 FreeBSD?

我对 SysV 和 Upstart 系统有着混合的回忆,其中大部分围绕着修改默认的 bash 脚本以使依赖项正确地与彼此配合。当我登录到一个系统,发现它正在运行 Fedora Core 3 时,我确实有些怀旧,被迫记起我以为已经忘记了的 SysV init 的一切。

systemd 已经到来,我个人迫不及待地想看看它接下来会吸收什么。

人们可能会对systemd-kerneld画上界限。

第五章:硬件和磁盘

本章将涵盖以下主题:

  • 确定硬件

  • 测试硬件

  • 内核的作用

  • Linux 上的磁盘配置

  • 文件系统层次结构

  • 配置空白磁盘并挂载它

  • 使用 LVM 重新配置磁盘

  • 使用systemd-mountfstab

  • 磁盘加密和处理静态加密

  • 当前的文件系统格式

  • 即将推出的文件系统格式

介绍

您的硬件并不像您可能对它的关心那样对您关心。

硬件是善变的、喜怒无常的、不可预测的,磁盘,硬件家族中叛逆的青少年,将这一切推向了一个新的境界。

在您的职业生涯中,您会发现自己在某个时刻感到困惑,对看似无关的错误在系统不同部分的发生感到困惑。您的 SSH 守护程序可能会在传输的奇怪时刻随机死机,NTP 可能会漂移,您的数据库可能会锁死,而与此同时,您正在拼命寻找原因。

通常,硬件是这些随机问题的答案(当不是时间时,正如我们之前讨论的)。一根坏内存条可能会以奇怪而奇妙的方式失败,而偶尔只读的磁盘可能意味着零星和夜间干扰的事件,这些事件可能会诱使您用特别沉重的锤子来解决。

如果您不想在老板面前使用短语“用锤子敲一下”,则被接受的命名法是“冲击性维护”。

当硬件出现问题时,除了更换它之外别无选择。过去我们会自己焊接和修理组件的日子已经一去不复返,因为这不是一个可行的解决方案,也不具有成本效益。

在某个时候,您会发现自己蹲在数据中心的一台开放服务器前,对着一排DIMM和一个RAID10磁盘阵列,摸不着头脑,试图确定哪一个是有故障的,以便您可以将其更换并将旧的放入研磨机,以安心。

我们用 k 来拼写硬盘等存储磁盘,用 c 来拼写光盘类型的光盘。

在本章中,我们将探讨确定特定硬件的方法,以及一些用于查找坏内存的简单故障排除步骤。除此之外,我们还将介绍如何向系统添加新磁盘以及安装后如何配置它们。

技术要求

对于本章,我们将使用以下Vagrantfile,其中包含两个额外的磁盘:

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

  config.vm.define "centos1" do |centos1|
    centos1.vm.box = "centos/7"
    centos1.vm.box_version = "1804.02"
    centos1.vm.provider "virtualbox" do |vbcustom|

      unless File.exist?('centos1.additional.vdi')
        vbcustom.customize ['createhd', '--filename', 'centos1.additional.vdi', '--size', 2 * 1024]
      end
      vbcustom.customize ['storageattach', :id, '--storagectl', 'IDE', '--port', 0, '--device', 1, '--type', 'hdd', '--medium', 'centos1.additional.vdi']

      unless File.exist?('centos1.additional2.vdi')
        vbcustom.customize ['createhd', '--filename', 'centos1.additional2.vdi', '--size', 2 * 1024]
      end
      vbcustom.customize ['storageattach', :id, '--storagectl', 'IDE', '--port', 1, '--device', 1, '--type', 'hdd', '--medium', 'centos1.additional2.vdi']

    end
  end

end

这里定义的额外磁盘将在您从 Vagrant 运行的本地目录中创建,确保您有足够的空间。

确定硬件

在第一章中,我们使用dmidecode和其他工具来确定我们是否在虚拟机中;在这里,我们将进一步尝试确定系统中可能运行的硬件,从磁盘 ID 到正在使用的图形卡类型。

准备工作

连接到您的 Vagrant VM 并安装一些我们将要使用的额外工具:

$ vagrant ssh
$ sudo yum install -y pciutils usbutils

如何做到...

我们将通过几种不同的方法来确定系统正在运行的硬件;即使您无法访问互联网,您也应该能够使用默认工具确定一些基本信息。

lspci

我们从pciutils套件安装的工具lspci是列出您的集体 PCI 设备的好方法,而不会有太多额外的噪音。

如果我们只运行lspci,我们会得到一个设备列表及其 ID:

00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)
00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
00:01.1 IDE interface: Intel Corporation 82371AB/EB/MB PIIX4 IDE (rev 01)
00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter
00:03.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller (rev 02)
00:04.0 System peripheral: InnoTek Systemberatung GmbH VirtualBox Guest Service
00:05.0 Multimedia audio controller: Intel Corporation 82801AA AC'97 Audio Controller (rev 01)
00:07.0 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 08)

在上面的列表中,我们可以看到系统中的设备。它们的数字 ID 实际上已经被翻译成了人类可读的格式。

如果您只想列出 ID,可以使用-n标志:

$ lspci -n
00:00.0 0600: 8086:1237 (rev 02)
00:01.0 0601: 8086:7000
00:01.1 0101: 8086:7111 (rev 01)
00:02.0 0300: 80ee:beef
00:03.0 0200: 8086:100e (rev 02)
00:04.0 0880: 80ee:cafe
00:05.0 0401: 8086:2415 (rev 01)
00:07.0 0680: 8086:7113 (rev 08)

或者如果您两者都想要,可以使用-nn

$ lspci -nn
00:00.0 Host bridge [0600]: Intel Corporation 440FX - 82441FX PMC [Natoma] [8086:1237] (rev 02)
00:01.0 ISA bridge [0601]: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II] [8086:7000]
00:01.1 IDE interface [0101]: Intel Corporation 82371AB/EB/MB PIIX4 IDE [8086:7111] (rev 01)
00:02.0 VGA compatible controller [0300]: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter [80ee:beef]
00:03.0 Ethernet controller [0200]: Intel Corporation 82540EM Gigabit Ethernet Controller [8086:100e] (rev 02)
00:04.0 System peripheral [0880]: InnoTek Systemberatung GmbH VirtualBox Guest Service [80ee:cafe]
00:05.0 Multimedia audio controller [0401]: Intel Corporation 82801AA AC'97 Audio Controller [8086:2415] (rev 01)
00:07.0 Bridge [0680]: Intel Corporation 82371AB/EB/MB PIIX4 ACPI [8086:7113] (rev 08)

在这个列表中,我们可以看到一些友好的描述,以帮助我们——例如以太网控制器VGA 兼容控制器IDE 接口等。

乍一看,由于那些不断更新PCI ID存储库的人们的辛勤工作,您应该对每个设备的功能有一个很好的理解:pci-ids.ucw.cz/

比起列出系统中的设备,我们还可以使用-k 列出处理设备的内核驱动程序。

在下面的片段中,我们可以看到以太网控制器由内核驱动程序e1000管理:

$ lspci -k
00:00.0 Host bridge: Intel Corporation 440FX - 82441FX PMC [Natoma] (rev 02)
00:01.0 ISA bridge: Intel Corporation 82371SB PIIX3 ISA [Natoma/Triton II]
00:01.1 IDE interface: Intel Corporation 82371AB/EB/MB PIIX4 IDE (rev 01)
 Kernel driver in use: ata_piix
 Kernel modules: ata_piix, pata_acpi, ata_generic
00:02.0 VGA compatible controller: InnoTek Systemberatung GmbH VirtualBox Graphics Adapter
00:03.0 Ethernet controller: Intel Corporation 82540EM Gigabit Ethernet Controller (rev 02)
 Subsystem: Intel Corporation PRO/1000 MT Desktop Adapter
 Kernel driver in use: e1000
 Kernel modules: e1000
00:04.0 System peripheral: InnoTek Systemberatung GmbH VirtualBox Guest Service
00:05.0 Multimedia audio controller: Intel Corporation 82801AA AC'97 Audio Controller (rev 01)
 Subsystem: Intel Corporation Device 0000
 Kernel driver in use: snd_intel8x0
 Kernel modules: snd_intel8x0
00:07.0 Bridge: Intel Corporation 82371AB/EB/MB PIIX4 ACPI (rev 08)
 Kernel driver in use: piix4_smbus
 Kernel modules: i2c_piix4

内核驱动程序的名称和实际硬件并不总是显而易见,这就是像 lspci 这样的工具如此方便的原因。

在现代机器上,您可能会看到多个 PCI 总线,设备连接到它;碰巧我们的虚拟机只利用一个总线来连接所有设备。

这意味着树视图非常扁平:

$ lspci -t
-[0000:00]-+-00.0
 +-01.0
 +-01.1
 +-02.0
 +-03.0
 +-04.0
 +-05.0
 \-07.0

然而,当我们对物理机器运行 lspci(在这种情况下是我的笔记本电脑)时,树视图可能会有更多的分支:

$ lspci -t
-[0000:00]-+-00.0
 +-02.0
 +-04.0
 +-14.0
 +-14.2
 +-15.0
 +-15.1
 +-16.0
 +-1c.0-[01-39]----00.0-[02-39]--+-00.0-[03]--
 |                               +-01.0-[04-38]--
 |                               \-02.0-[39]----00.0
 +-1c.4-[3a]----00.0
 +-1c.5-[3b]----00.0
 +-1d.0-[3c]----00.0
 +-1f.0
 +-1f.2
 +-1f.3
 \-1f.4

如果您看不到您知道存在的设备(比如显卡),可能有几种情况:也许设备在 BIOS 中被禁用,或者卡本身已经损坏。尝试一些基本的故障排除,比如检查 BIOS/UEFI 配置或将卡插入不同的插槽。

还有 lsusb 用于 USB 设备。如果您使用类似 USB 以太网设备的东西,这可能很方便。在下面的示例中,您可以看到我连接的盒子(树莓派)的网络端口位于 USB 总线上:

$ lsusb
Bus 001 Device 003: ID 0424:ec00 Standard Microsystems Corp. SMSC9512/9514 Fast Ethernet Adapter

lshw

一个特别有用的程序 lshw 具有内置的能力,可以将您的硬件树输出为 JSON、XML、HTML,可能还有更多,因为它们正在开发中。

默认情况下,lshw 的输出非常冗长,但应该看起来像下面这样:

$ sudo lshw 
localhost.localdomain 
 description: Computer
 product: VirtualBox
 vendor: innotek GmbH
 version: 1.2
 serial: 0
 width: 64 bits
 capabilities: smbios-2.5 dmi-2.5 vsyscall32
<SNIP>
 *-pnp00:00
 product: PnP device PNP0303
 physical id: 3
 capabilities: pnp
 configuration: driver=i8042 kbd
 *-pnp00:01
 product: PnP device PNP0f03
 physical id: 4
 capabilities: pnp
 configuration: driver=i8042 aux

我倾向于发现一目了然的解决方案在很多时候更有效。因此,考虑到这一点,让我们来看看-short 选项的输出:

$ sudo lshw -short
H/W path            Device     Class       Description
======================================================
 system      VirtualBox
/0                             bus         VirtualBox
/0/0                           memory      128KiB BIOS
/0/1                           memory      512MiB System memory
/0/2                           processor   Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz
/0/100                         bridge      440FX - 82441FX PMC [Natoma]
/0/100/1                       bridge      82371SB PIIX3 ISA [Natoma/Triton II]
/0/100/1.1          scsi0      storage     82371AB/EB/MB PIIX4 IDE
/0/100/1.1/0.0.0    /dev/sda   disk        42GB VBOX HARDDISK
/0/100/1.1/0.0.0/1  /dev/sda1  volume      1MiB Linux filesystem partition
/0/100/1.1/0.0.0/2             volume      1GiB Linux filesystem partition
/0/100/1.1/0.0.0/3  /dev/sda3  volume      38GiB Linux LVM Physical Volume partition
/0/100/1.1/0        /dev/sdb   disk        2147MB VBOX HARDDISK
/0/100/1.1/1        /dev/sdc   disk        2147MB VBOX HARDDISK
/0/100/2                       display     VirtualBox Graphics Adapter
/0/100/3            eth0       network     82540EM Gigabit Ethernet Controller
/0/100/4                       generic     VirtualBox Guest Service
/0/100/5                       multimedia  82801AA AC'97 Audio Controller
/0/100/7                       bridge      82371AB/EB/MB PIIX4 ACPI
/0/3                           input       PnP device PNP0303
/0/4                           input       PnP device PNP0f03

这样更容易阅读,一目了然,您可以看到系统有三个磁盘、一个网络设备和 512MiB 的系统内存。

正如我们在第一章中看到的,介绍和环境设置,您还可以使用-c 选择class输出,再次显示我们的网络设备:

$ sudo lshw -c network
 *-network 
 description: Ethernet interface
 product: 82540EM Gigabit Ethernet Controller
 vendor: Intel Corporation
 physical id: 3
 bus info: pci@0000:00:03.0
 logical name: eth0
 version: 02
 serial: 52:54:00:c9:c7:04
 size: 1Gbit/s
 capacity: 1Gbit/s
 width: 32 bits
 clock: 66MHz
 capabilities: pm pcix bus_master cap_list ethernet physical tp 10bt 10bt-fd 100bt 100bt-fd 1000bt-fd autonegotiation
 configuration: autonegotiation=on broadcast=yes driver=e1000 driverversion=7.3.21-k8-NAPI duplex=full ip=10.0.2.15 latency=64 link=yes mingnt=255 multicast=yes port=twisted pair speed=1Gbit/s
 resources: irq:19 memory:f0000000-f001ffff ioport:d010(size=8)

从这个输出中,我们可以看到很多相关信息,比如网络设备的容量(1Gbit/s),以及设备的功能。

我们甚至可以看到它的具体配置,这对您可能想要进行的潜在更改很有用。

如果要查看实际的数字 ID,可以在命令中添加-numeric:

$ sudo lshw -c network -numeric
  *-network                 
       description: Ethernet interface
       product: 82540EM Gigabit Ethernet Controller [8086:100E]
       vendor: Intel Corporation [8086]
       physical id: 3
       bus info: pci@0000:00:03.0
       logical name: eth0
       version: 02
       serial: 52:54:00:c9:c7:04
       size: 1Gbit/s
       capacity: 1Gbit/s
       width: 32 bits
       clock: 66MHz
       capabilities: pm pcix bus_master cap_list ethernet physical tp 10bt 10bt-fd 100bt 100bt-fd 1000bt-fd autonegotiation
       configuration: autonegotiation=on broadcast=yes driver=e1000 driverversion=7.3.21-k8-NAPI duplex=full ip=10.0.2.15 latency=64 link=yes mingnt=255 multicast=yes port=twisted pair speed=1Gbit/s
       resources: irq:19 memory:f0000000-f001ffff ioport:d010(size=8)

/proc

/proc 是大多数 Linux 系统上找到的进程信息伪文件系统(但不适用于 BSD)。

它是可读的接口到内核数据结构,其中一些文件是可写的,允许对正在运行的内核进行即时更改。

此目录中的一些有用文件包括/proc/cpuinfo 等,当查询时,它会提供内核了解的有关 CPU 的所有信息:

$ cat /proc/cpuinfo 
processor    : 0
vendor_id    : GenuineIntel
cpu family    : 6
model        : 142
model name    : Intel(R) Core(TM) i7-7500U CPU @ 2.70GHz
stepping    : 9
cpu MHz        : 2904.000
cache size    : 4096 KB
physical id    : 0
siblings    : 1
core id        : 0
cpu cores    : 1
apicid        : 0
initial apicid    : 0
fpu        : yes
fpu_exception    : yes
cpuid level    : 22
wp        : yes
flags        : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 syscall nx rdtscp lm constant_tsc rep_good nopl xtopology nonstop_tsc pni pclmulqdq monitor ssse3 cx16 pcid sse4_1 sse4_2 x2apic movbe popcnt aes xsave avx rdrand hypervisor lahf_lm abm 3dnowprefetch fsgsbase avx2 invpcid rdseed clflushopt
bogomips    : 5808.00
clflush size    : 64
cache_alignment    : 64
address sizes    : 39 bits physical, 48 bits virtual
power management:

它还会给出处理器编号,这意味着如果您想快速计算系统中(正在使用的)处理器的数量,可以用简短的命令和一些管道列出它们。

在这里,我们正在转储文件,寻找单词processor,然后计算行数。这不是最可靠的系统,但在紧要关头很方便:

$ grep -c "processor" /proc/cpuinfo 
1

另一个要注意的有用文件是/proc/meminfo,它可以完全转储系统了解的有关内存的所有信息:

$ cat /proc/meminfo
MemTotal:         499428 kB
MemFree:           66164 kB
MemAvailable:     397320 kB
Buffers:            2140 kB
Cached:           324952 kB
SwapCached:            0 kB
Active:           222104 kB
Inactive:         142044 kB
Active(anon):      12764 kB
Inactive(anon):    28916 kB
Active(file):     209340 kB
Inactive(file):   113128 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:       1572860 kB
SwapFree:        1572860 kB
Dirty:                 0 kB
Writeback:             0 kB
AnonPages:         37100 kB
Mapped:            22844 kB
Shmem:              4624 kB
Slab:              44216 kB
SReclaimable:      21800 kB
SUnreclaim:        22416 kB
KernelStack:        1728 kB
PageTables:         4200 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:     1822572 kB
Committed_AS:     302040 kB
VmallocTotal:   34359738367 kB
VmallocUsed:        4744 kB
VmallocChunk:   34359730812 kB
HardwareCorrupted:     0 kB
AnonHugePages:         0 kB
CmaTotal:              0 kB
CmaFree:               0 kB
HugePages_Total:       0
HugePages_Free:        0
HugePages_Rsvd:        0
HugePages_Surp:        0
Hugepagesize:       2048 kB
DirectMap4k:       49088 kB
DirectMap2M:      475136 kB

/proc 文件系统非常庞大和广泛;如果您有空闲时间,请查看 proc 的手册页面——您不会立刻后悔(但以后可能会后悔)。

/sys

/sys 或 sysfs 是用于导出内核对象的文件系统(根据其手册页面),这意味着它是用于访问内核信息的另一个文件系统(如/proc)。

在脚本中非常有用,可以列出发现的块设备等:

$ ls -l /sys/block
total 0
lrwxrwxrwx. 1 root root 0 Sep  2 12:29 dm-0 -> ../devices/virtual/block/dm-0
lrwxrwxrwx. 1 root root 0 Sep  2 12:29 dm-1 -> ../devices/virtual/block/dm-1
lrwxrwxrwx. 1 root root 0 Sep  2 12:29 sda -> ../devices/pci0000:00/0000:00:01.1/ata1/host0/target0:0:0/0:0:0:0/block/sda
lrwxrwxrwx. 1 root root 0 Sep  2 12:29 sdb -> ../devices/pci0000:00/0000:00:01.1/ata1/host0/target0:0:1/0:0:1:0/block/sdb
lrwxrwxrwx. 1 root root 0 Sep  2 12:29 sdc -> ../devices/pci0000:00/0000:00:01.1/ata2/host1/target1:0:1/1:0:1:0/block/sdc

如果您想要查找单个组件的设备 ID(例如,如果您没有 lshw 或 lspci 方便的话),这也可能很有用。

在下面的示例中,我列出了 eth0 设备的供应商和设备 ID:

$ cat /sys/class/net/eth0/device/vendor 
0x8086
$ cat /sys/class/net/eth0/device/device
0x100e

有了这些信息,我可以将详细信息与设备列表进行对比。我选择查看PCI ID存储库,在那里我了解到供应商 ID 属于英特尔公司,设备 ID 转换为 82540EM 千兆以太网控制器。

/sys在硬件领域之外还有很多内容,深入研究文件系统可能是个好主意。sysfs的手册页(5)是一个有争议的必备品。

dmesg(和内核日志)

dmesg是一种根据其手册打印或控制内核环形缓冲区的方法,但对你和我来说,它是一个快速查看内核是否检测到硬件的好方法,因为它在初始化时被检测到。

运行dmesg将打印到stdout,所以将其导入less很方便:

$ dmesg | less

完成后,你应该能够搜索特定的字符串。继续我们的主题,我们将搜索Intel并查看加载了什么:

[    2.221242] e1000: Intel(R) PRO/1000 Network Driver - version 7.3.21-k8-NAPI

如果我们搜索ATA,我们还可以看到我们的磁盘被检测到:

[ 0.940242] ata2.01: ATA-6: VBOX HARDDISK, 1.0, max UDMA/133
[ 0.940248] ata2.01: 4194304 sectors, multi 128: LBA 
[ 0.940807] ata1.00: ATA-6: VBOX HARDDISK, 1.0, max UDMA/133
[ 0.940810] ata1.00: 83886080 sectors, multi 128: LBA 
[ 0.940815] ata1.01: ATA-6: VBOX HARDDISK, 1.0, max UDMA/133
[ 0.940817] ata1.01: 4194304 sectors, multi 128: LBA 

甚至还有一个-T选项,可以给你人类可读的时间戳,这可能特别有用,如下所示,与我们的 IDE(PATA)控制器相比:

[Sun Sep 2 12:29:08 2018] ata1: PATA max UDMA/33 cmd 0x1f0 ctl 0x3f6 bmdma 0xd000 irq 14
[Sun Sep 2 12:29:08 2018] ata2: PATA max UDMA/33 cmd 0x170 ctl 0x376 bmdma 0xd008 irq 15

dmidecode

另一个喧闹但最喜欢的工具来自第一章,介绍和环境设置dmidecode解码 DMI 表。

我们最初用它来检查虚拟硬件,但在非虚拟机上,它可能更有用。

比较以下-t processor转储:

$ sudo dmidecode -t processor
# dmidecode 3.0
Getting SMBIOS data from sysfs.
SMBIOS 2.5 present.

注意我们的虚拟机上的明显空旷,与我笔记本电脑上的示例相比:

$ sudo dmidecode -t processor
# dmidecode 3.1
Getting SMBIOS data from sysfs.
SMBIOS 3.0.0 present.

Handle 0x003F, DMI type 4, 48 bytes
Processor Information
    Socket Designation: U3E1
    Type: Central Processor
    Family: Core i7
    Manufacturer: Intel(R) Corporation
    ID: E9 06 08 00 FF FB EB BF
    Signature: Type 0, Family 6, Model 142, Stepping 9
    Flags:
<SNIP>
    Core Count: 2
    Core Enabled: 2
    Thread Count: 4
    Characteristics:
        64-bit capable
        Multi-Core
        Hardware Thread
        Execute Protection
        Enhanced Virtualization
        Power/Performance Control

这表明,在某些情况下,从物理机器中可能获得的信息比从虚拟机器中获得的信息更多。

反过来也可以这样说:如果有人竭尽全力地掩盖你的硬件信息,首先你应该怀疑自己是否在虚拟机上运行,然后你应该想知道他们为什么要如此努力地隐藏这个事实。

/dev

如果非要我选择一个最喜欢的伪文件系统,我可能会成为一个奇怪的人,但如果你逼我选择,那可能会是/dev

这不是因为对dev这个词的喜爱或者对它的过度使用的偏爱,而是因为我经常发现自己在其中。

与所有的伪文件系统一样,它们都是瞬态和临时的(tmpfs)。不要像我曾经看到的一位同事那样,在其中存储东西,因为一旦重新启动你的机器:噗,你的文件就没了。

在表面上,/dev看起来很混乱:

$ ls /dev/
autofs           hugepages           port    shm       tty18  tty33  tty49  tty7     vcs6
block            hwrng               ppp     snapshot  tty19  tty34  tty5   tty8     vcsa
bsg              initctl             ptmx    snd       tty2   tty35  tty50  tty9     vcsa1
btrfs-control    input               pts     stderr    tty20  tty36  tty51  ttyS0    vcsa2
char             kmsg                random  stdin     tty21  tty37  tty52  ttyS1    vcsa3
console          log                 raw     stdout    tty22  tty38  tty53  ttyS2    vcsa4
core             loop-control        rtc     tty       tty23  tty39  tty54  ttyS3    vcsa5
cpu              mapper              rtc0    tty0      tty24  tty4   tty55  uhid     vcsa6
cpu_dma_latency  mcelog              sda     tty1      tty25  tty40  tty56  uinput   vfio
crash            mem                 sda1    tty10     tty26  tty41  tty57  urandom  vga_arbiter
disk             mqueue              sda2    tty11     tty27  tty42  tty58  usbmon0  vhci
dm-0             net                 sda3    tty12     tty28  tty43  tty59  vcs      vhost-net
dm-1             network_latency     sdb     tty13     tty29  tty44  tty6   vcs1     VolGroup00
fd               network_throughput  sdc     tty14     tty3   tty45  tty60  vcs2     zero
full             null                sg0     tty15     tty30  tty46  tty61  vcs3
fuse             nvram               sg1     tty16     tty31  tty47  tty62  vcs4
hpet             oldmem              sg2     tty17     tty32  tty48  tty63  vcs5

然而,当你知道这些知识时,你会发现它是无价的。

让我们ls一下/dev/disk/目录:

$ ls /dev/disk/
by-id  by-path  by-uuid

有趣的选项——我喜欢那些!

选择by-id选项会显示所有我们的磁盘设备,by-id

$ ls /dev/disk/by-id/
ata-VBOX_HARDDISK_VB4eceb5be-efcdfb56
ata-VBOX_HARDDISK_VB804908ad-25f1585c
ata-VBOX_HARDDISK_VBcf466104-c1479f0d
ata-VBOX_HARDDISK_VBcf466104-c1479f0d-part1
ata-VBOX_HARDDISK_VBcf466104-c1479f0d-part2
ata-VBOX_HARDDISK_VBcf466104-c1479f0d-part3
dm-name-VolGroup00-LogVol00
dm-name-VolGroup00-LogVol01
dm-uuid-LVM-SA8LTUF2yzFEV1RdgThw0ZiRxhyHFKuUIAjIC6ScnMtvH67BTyTN31hd82bgDSzd
dm-uuid-LVM-SA8LTUF2yzFEV1RdgThw0ZiRxhyHFKuUj6b8IVKEw37bTwOqy81Ud3juFCSJBg12
lvm-pv-uuid-vrrtbx-g480-HcJI-5wLn-4aOf-Olld-rC03AY
$ ls /dev/disk/by-path/
pci-0000:00:01.1-ata-1.0        pci-0000:00:01.1-ata-1.0-part2  pci-0000:00:01.1-ata-1.1
pci-0000:00:01.1-ata-1.0-part1  pci-0000:00:01.1-ata-1.0-part3  pci-0000:00:01.1-ata-2.1

以下是我绝对最喜欢的by-uuid

$ ls /dev/disk/by-uuid/
570897ca-e759-4c81-90cf-389da6eee4cc  c39c5bed-f37c-4263-bee8-aeb6a6659d7b
b60e9498-0baa-4d9f-90aa-069048217fee

我喜欢这些的主要原因是因为这些条目实际上是指向以它们命名的设备的符号链接:

$ ls -l /dev/disk/by-uuid/
total 0
lrwxrwxrwx. 1 root root 10 Sep  2 12:29 570897ca-e759-4c81-90cf-389da6eee4cc -> ../../sda2
lrwxrwxrwx. 1 root root 10 Sep  2 12:29 b60e9498-0baa-4d9f-90aa-069048217fee -> ../../dm-0
lrwxrwxrwx. 1 root root 10 Sep  2 12:29 c39c5bed-f37c-4263-bee8-aeb6a6659d7b -> ../../dm-1

因此,我现在知道我的sda2分区具有570897ca-e759-4c81-90cf-389da6eee4cc的 UUID,可以用于各种任务。

UUID 最明显的用途是在大多数系统的fstab文件中:

$ cat /etc/fstab 

#
# /etc/fstab
# Created by anaconda on Sat May 12 18:50:26 2018
#
# Accessible filesystems, by reference, are maintained under '/dev/disk'
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
/dev/mapper/VolGroup00-LogVol00 /                       xfs     defaults        0 0
UUID=570897ca-e759-4c81-90cf-389da6eee4cc /boot                   xfs     defaults        0 0
/dev/mapper/VolGroup00-LogVol01 swap                    swap    defaults        0 0

因此,将这两个信息配对起来,我们现在有了我们fstab UUID 条目的实际设备标识(sda2)!

使用 UUID 的原因是因为设备标识可能会发生变化,历史上更是如此。在一次启动中,你的/boot文件系统可能被标识为sda2,然后在另一次启动中可能首先找到不同的设备,突然之间/boot就变成了sdb2,破坏了fstab

它是如何工作的...

我们在这里主要做的是检查内核对连接到系统的设备的了解。

PCI 设备以及 USB 设备在所有操作系统中都具有相同的标识(在 Windows、Mac 和 BSD 上都会看到相同的十六进制值)。这使得内核可以选择并加载适当的代码,以与相同的设备进行交互。

很少见,但可能会发生一个模块取代旧模块或两个驱动程序都可以与相同的硬件一起使用;在这种情况下,了解您的硬件设备 ID 以及针对它们运行的内核代码的位是很有用的。

如果您在桌面上使用 Linux,并且使用 Nvidia 或 AMD GPU,那么您与驱动程序和内核加载进行交互的可能性很高,因为有闭源和开源版本可供选择。

测试硬件

在本节中,我们将讨论通过查看 SMART 和磁盘测试软件以及物理故障排除 RAM 问题的方法来测试潜在故障的硬件。

处理磁盘可能会非常危险,您应该始终确保在开始可能对数据有危险的任何操作之前备份数据。

准备工作

在这里,我们主要将使用我们创建的 Vagrant box,但您可能还想查看来自www.memtest.org/的 Memtest86+,我提到这个是用于内存测试的。

连接到您的 VM 并安装smartmontools

$ vagrant ssh
$ sudo yum install -y smartmontools hdparm

您可能还想下载最新的 Memtest86+ ISO。

如何做…

我们将从磁盘健康开始。

自我监控、分析和报告技术(SMART)

首先,确保smartd正在运行在您想要查询的系统上是个好主意:

$ sudo systemctl enable smartd

smartd是 SMART 的监控守护程序;守护程序在启动时尝试在兼容的ATA设备上启用监控,并默认每 30 分钟轮询ATA设备。

默认情况下,smartd作为其定期工作的一部分检测到的错误使用SYSLOG接口进行记录。如果配置为这样做,它还可以通过电子邮件发送给管理员。

启用后,可以使用smartctl工具查询磁盘:

$ sudo smartctl -a /dev/sda

注意使用sudo和标记磁盘设备。

很遗憾,因为我们在 VirtualBox VM 中,这不会给我们带来任何有用的东西:

smartctl 6.5 2016-05-07 r4318 [x86_64-linux-3.10.0-862.2.3.el7.x86_64] (local build)
Copyright (C) 2002-16, Bruce Allen, Christian Franke, www.smartmontools.org

=== START OF INFORMATION SECTION ===
Device Model: VBOX HARDDISK
Serial Number: VBcf466104-c1479f0d
Firmware Version: 1.0
User Capacity: 42,949,672,960 bytes [42.9 GB]
Sector Size: 512 bytes logical/physical
Device is: Not in smartctl database [for details use: -P showall]
ATA Version is: ATA/ATAPI-6 published, ANSI INCITS 361-2002
Local Time is: Sun Sep 2 14:18:30 2018 UTC
SMART support is: Unavailable - device lacks SMART capability.

A mandatory SMART command failed: exiting. To continue, add one or more '-T permissive' options.

但是,如果您在物理机上尝试这样做,结果会有所不同,并且可以获得更多信息。

hdparm

可能您的磁盘向您的 SMART 命令报告良好,但您仍然看到某种形式的减速或其他问题。

您可以使用hdparm工具(默认存储库中提供)对磁盘的读取速度进行基准测试。

我们可以使用以下方法测试磁盘的速度:

$ sudo hdparm -tT /dev/sda

hdparm的手册页面建议进行这些测试两到三次以获得平均结果,并在其他情况下的非活动系统上运行它们。

您的情况可能有所不同,但我的系统的结果如下:

/dev/sda:
 Timing cached reads:   13158 MB in  1.99 seconds = 6597.20 MB/sec
 Timing buffered disk reads: 2714 MB in  3.00 seconds = 903.83 MB/sec

部分原因是我们在做的实际上是从内核的页面缓存中读取。我们可以使用--direct选项来绕过这一点,该选项直接从驱动器中读取到hdparm的缓冲区中:

$ sudo hdparm -tT --direct /dev/sda

这些结果更多地是原始磁盘读取性能:

/dev/sda:
 Timing O_DIRECT cached reads:   2416 MB in  2.00 seconds = 1208.08 MB/sec
 Timing O_DIRECT disk reads: 3648 MB in  3.00 seconds = 1215.78 MB/sec

内存测试

内存测试起来要容易一些,尽管检查每个 DIMM 的最彻底方法是将箱子脱机几个小时,同时运行 Memtest86+。

也存在诸如memtester之类的程序,可以在运行中的系统上执行。这些类型的测试的问题在于它们不会测试系统已使用的内存,并且可能会与内存不足OOM)杀手等进程发生冲突。

如果您从 Memtest86+网站获得 ISO 映像,可以将其附加到 VM 并启动该程序(完全独立于 CentOS)。

结果看起来会像这样:

任何错误都将显示在屏幕的下半部分,您将知道您的内存有问题。

在我每晚在数据中心使用 Memtest86+时,我会让 Memtest86+对我正在测试的内存进行五次测试。

如果您发现您的系统根本无法启动,但怀疑是内存问题,那么以二进制方式进行测试可能是个好主意。我的意思是,如果您的服务器有 128 根内存条(这并不罕见),您应该移除 64 根并测试剩下的一批。如果您的服务器启动了,那么您就知道有问题的内存条在您移除的 64 根中的某一根。如果您的服务器无法启动,那么有问题的内存条就在您留在内部的一批内存条中。

重复这种技术,每次检查的内存减半,直到您只剩下两个 DIMM,其中一个您知道是有问题的,然后依次测试每个内存条。

以前的测试可能听起来很明显,但在凌晨两点,当您无法清晰地思考时,阅读这些文字可能会挽救您的理智。

它是如何工作的...

SMART 通过查询您选择的/dev/设备来读取信息,并显示它对兼容 SMART 设备所学到的内容。

当我们使用hdparm时,实际上是在运行测试,而不管我们的文件系统,因为该程序直接从磁盘读取数据——因此,实际速度可能会有所不同。

还有更多...

我没有在本节中包括测试显卡等内容,因为通常很容易判断显卡是否出问题(图形故障、随机出现的线条和偶尔的蜂鸣声)。

我也没有提到物理 RAID 卡,因为种类繁多,不可能为所有 RAID 卡列出一个连贯的测试方法。对于物理 RAID 卡,我能给出的最好建议就是查看制造商关于测试的详细信息。

我们没有涵盖磁盘的写入测试,部分原因是通常很容易通过读取测试来判断磁盘问题,另一部分原因是如果错误进行写入测试,很多测试方法可能会具有破坏性。

内核的作用

我们将观察内核在启动过程中的运行情况,以及在我们到达操作系统时加载了哪些模块。

做好准备

在本节中,请确保您的 VM 正在运行,因为我们将讨论硬件初始化:

$ vagrant ssh

连接到您的 VM。如果您刚刚完成上一节,您可能希望销毁并重新创建您的 VM,以确保获得原始体验。

如何做到的...

首先,我们要观察我们的系统启动。

首先,在我们的引导配置中禁用quiet选项,这样我们就可以在 VM 显示器上实际看到信息:

$ sudo sed -i 's/ quiet//g' /etc/sysconfig/grub

现在,我们需要生成一个新的grub配置文件,因为我们已经做出了更改:

$ sudo grub2-mkconfig | sudo tee /boot/grub2/grub.cfg

上面的代码是一个很好的例子,如果有人要求您进行grub配置更改,这是一个令人惊讶地常见的操作。

现在,打开 VirtualBox 主窗口,双击您的 VM,这样您就可以看到黑色控制台:

打开您的终端连接,这样您就可以同时看到并使用reboot命令重新启动您的 VM:

$ sudo reboot

保持眼睛盯着 VirtualBox 窗口;您应该会看到类似以下截图的内容:

您有注意到滚动信息吗?它可能飞得太快了,以至于您无法阅读,但这是您的系统在初始化自身。

您现在可以关闭 VirtualBox 窗口,并在终端中继续操作。

如果您有兴趣回顾刚才看到的内容,您可能会记得我们之前使用过的dmesg命令;您刚刚看到的所有内容都可以查看。

现在,我们需要在运行的系统中查看内核加载了哪些模块来处理我们的硬件:

$ lsmod
Module                  Size  Used by
sunrpc                353310  1 
intel_powerclamp       14419  0 
iosf_mbi               14990  0 
crc32_pclmul           13133  0 
ghash_clmulni_intel    13273  0 
snd_intel8x0           38199  0 
ppdev                  17671  0 
snd_ac97_codec        130556  1 snd_intel8x0
ac97_bus               12730  1 snd_ac97_codec
aesni_intel           189415  0 
snd_pcm               101643  2 snd_ac97_codec,snd_intel8x0
snd_timer              29810  1 snd_pcm
lrw                    13286  1 aesni_intel
pcspkr                 12718  0 
sg                     40721  0 
gf128mul               15139  1 lrw
e1000                 137574  0 
glue_helper            13990  1 aesni_intel
ablk_helper            13597  1 aesni_intel
snd                    79215  4 snd_ac97_codec,snd_intel8x0,snd_timer,snd_pcm
cryptd                 20511  3 ghash_clmulni_intel,aesni_intel,ablk_helper
i2c_piix4              22401  0 
soundcore              15047  1 snd
i2c_core               63151  1 i2c_piix4
parport_pc             28205  0 
parport                46395  2 ppdev,parport_pc
video                  24538  0 
ip_tables              27126  0 
xfs                  1003971  2 
libcrc32c              12644  1 xfs
sd_mod                 46322  3 
crc_t10dif             12912  1 sd_mod
crct10dif_generic      12647  0 
ata_generic            12923  0 
pata_acpi              13053  0 
ata_piix               35052  2 
libata                242992  3 pata_acpi,ata_generic,ata_piix
crct10dif_pclmul       14307  1 
crct10dif_common       12595  3 crct10dif_pclmul,crct10dif_generic,crc_t10dif
crc32c_intel           22094  1 
serio_raw              13434  0 
dm_mirror              22289  0 
dm_region_hash         20813  1 dm_mirror
dm_log                 18411  2 dm_region_hash,dm_mirror
dm_mod                123941  8 dm_log,dm_mirror

这是很多模块!

正如我之前提到的,其中一些模块是显而易见的,还有更多的则不是。

在列表中,一个很明显的是e1000,因为我们已经知道这是我们在前面一节中讨论过的网络模块。

我们可以使用modinfo获取有关模块的具体信息:

$ modinfo e1000
filename:       /lib/modules/3.10.0-862.2.3.el7.x86_64/kernel/drivers/net/ethernet/intel/e1000/e1000.ko.xz
version:        7.3.21-k8-NAPI
license:        GPL
description:    Intel(R) PRO/1000 Network Driver
author:         Intel Corporation, <linux.nics@intel.com>
retpoline:      Y
rhelversion:    7.5
srcversion:     04454A212DD89712602561D
alias:          pci:v00008086d00002E6Esv*sd*bc*sc*i*
alias:          pci:v00008086d000010B5sv*sd*bc*sc*i*
alias:          pci:v00008086d00001099sv*sd*bc*sc*i*
<SNIP>
alias:          pci:v00008086d00001000sv*sd*bc*sc*i*
depends: 
intree:         Y
vermagic:       3.10.0-862.2.3.el7.x86_64 SMP mod_unload modversions 
signer:         CentOS Linux kernel signing key
sig_key:        66:6E:F0:31:93:3F:51:27:06:23:72:83:2C:E9:BA:8A:49:00:5C:8F
sig_hashalgo:   sha256
parm:           TxDescriptors:Number of transmit descriptors (array of int)
parm:           RxDescriptors:Number of receive descriptors (array of int)
parm:           Speed:Speed setting (array of int)
parm:           Duplex:Duplex setting (array of int)
parm:           AutoNeg:Advertised auto-negotiation setting (array of int)
parm:           FlowControl:Flow Control setting (array of int)
parm:           XsumRX:Disable or enable Receive Checksum offload (array of int)
parm:           TxIntDelay:Transmit Interrupt Delay (array of int)
parm:           TxAbsIntDelay:Transmit Absolute Interrupt Delay (array of int)
parm:           RxIntDelay:Receive Interrupt Delay (array of int)
parm:           RxAbsIntDelay:Receive Absolute Interrupt Delay (array of int)
parm:           InterruptThrottleRate:Interrupt Throttling Rate (array of int)
parm:           SmartPowerDownEnable:Enable PHY smart power down (array of int)
parm:           copybreak:Maximum size of packet that is copied to a new buffer on receive (uint)
parm:           debug:Debug level (0=none,...,16=all) (int)

上面的代码不仅给出了模块的版本和许可证,还给出了作者和他们的联系信息(通常是 bug 报告)。

如果你试图移除一个正在使用的模块,你将不被允许,就像下面的modprobe示例中所示的那样:

$ sudo modprobe -r libata
modprobe: FATAL: Module libata is in use.

同样,如果你想加载一个新的模块(也许是因为你想测试它),你可以再次使用modprobe

$ sudo modprobe nf_tables

然后我们可以看到我们加载的模块:

$ lsmod | grep nf_
nf_tables              74179  0 
nfnetlink              14490  1 nf_tables

在启动时加载模块是另一回事,因为如果没有内置到内核中(并且内核选项通常是通用的,以便供应商可以尽可能涵盖多种情况而不会引起问题),它需要一个配置文件。

为了确保nf_tables与我们的内核一起启动,请运行以下命令:

$ echo "nf_tables" | sudo tee /etc/modules-load.d/nftables.conf

重新启动并使用lsmod来查看你的模块是否已加载。

它是如何工作的...

当系统启动时,会按顺序发生几件事情,这些事情会略有不同(比如你使用的引导加载程序是哪个,尽管现在大多数情况下都是 Grub)。

其中一件事是内核会自行提取并加载,然后将控制权交给init系统(systemd)。

当内核加载时,它还会检测系统中的硬件,并将适当的模块添加到其运行状态,以便可以正确地与硬件进行交互和管理。

当我们使用lsmod列出模块时,实际上只是以更易读的格式打印/proc/modules

还有更多...

你可以动态加载和卸载模块,也可以手动将某些模块列入黑名单,以防止加载。

如果你有一个特定的硬件故障,和/或导致内核崩溃(内核完全停止运行并崩溃),这可能会有所帮助。

要列入黑名单一个模块,只需将其添加到/etc/modprobe.d/中的黑名单中:

$ echo "blacklist e1000" | sudo tee -a /etc/modprobe.d/blacklist.conf

在上一个示例中,我将e1000列入黑名单。显然,这会给我带来问题,因为这意味着我的网络卡在启动时将没有适当的驱动程序,但这样可以使系统更安全!

Linux 上的磁盘配置

在本节中,我们将查看我们的虚拟机中磁盘的开箱即用配置,并讨论vdasdahdanvme之间的区别。我们还将调查磁盘、虚拟磁盘、分区和文件系统之间的区别。

准备工作

在这里,我们将使用我们在本章开头创建的 Vagrant box。

确保你的centos1虚拟机已经启动并连接到它:

$ vagrant ssh centos1

检查你的虚拟机是否安装了本节所需的适当软件包。

$ sudo yum install lvm2 -y

如何做...

我们将首先查看系统中的物理磁盘,并弄清它们与我们可以通过df命令看到的内容之间的关系。

使用 lsblk 列出磁盘

作为基本系统的一部分,应该安装一个名为lsblk的程序。

运行此程序会为您提供一个人类可读的树状视图,显示我们系统的块设备、它们的逻辑分离和它们的挂载点:

$ lsblk
NAME              MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda                 8:0    0   40G  0 disk 
├─sda1              8:1    0    1M  0 part 
├─sda2              8:2    0    1G  0 part /boot
└─sda3              8:3    0   39G  0 part 
 ├─VolGroup00-LogVol00
 253:0    0 37.5G  0 lvm  /
 └─VolGroup00-LogVol01
 253:1    0  1.5G  0 lvm  [SWAP]
sdb                 8:16   0    2G  0 disk 
sdc                 8:32   0    2G  0 disk 

块设备基本上是在存储介质之上的一层抽象;字符(原始)设备允许直接访问存储介质,但可能会施加一些抽象掉的限制,而使用块设备可以解除这些限制。

在上一个示例中,我们有我们的物理磁盘:

  • sda

  • sdb

  • sdc

然后我们有我们的分区:

  • sda1

  • sda2

  • sda3

我们有我们的卷组:

  • VolGroup00

我们在我们的单一卷组上有逻辑卷:

  • LogVol00

  • LogVol01

最后,我们有我们的挂载点:

  • /boot

  • /

  • [SWAP]

使用 df 列出挂载点

现在我们知道了系统的大致磁盘布局,我们可能也想知道所有其他的挂载点。这可以很容易地通过一个名为df的程序来实现:

$ df 
Filesystem 1K-blocks Used Available Use% Mounted on
/dev/mapper/VolGroup00-LogVol00 39269648 849960 38419688 3% /
devtmpfs 239968 0 239968 0% /dev
tmpfs 249712 0 249712 0% /dev/shm
tmpfs 249712 4572 245140 2% /run
tmpfs 249712 0 249712 0% /sys/fs/cgroup
/dev/sda2 1038336 64076 974260 7% /boot
tmpfs 49944 0 49944 0% /run/user/1000

为了更好地阅读输出,我们可以使用-h

$ df -h
Filesystem                       Size  Used Avail Use% Mounted on
/dev/mapper/VolGroup00-LogVol00   38G  831M   37G   3% /
devtmpfs                         235M     0  235M   0% /dev
tmpfs                            244M     0  244M   0% /dev/shm
tmpfs                            244M  4.5M  240M   2% /run
tmpfs                            244M     0  244M   0% /sys/fs/cgroup
/dev/sda2                       1014M   63M  952M   7% /boot
tmpfs                             49M     0   49M   0% /run/user/1000

在这里,我们可以看到我们已经从上一节知道的挂载点,即//boot

我们还可以看到其他挂载点,特别是那些标记有devtmpfstmpfs文件系统的挂载点。

这些挂载点被挂载在 RAM 磁盘上——这个概念已经存在多年了,但我们仍在使用,因为 RAM 速度非常快(目前比 SSD 快得多)。

临时目录是那些包含我们不在乎在重启后保留的文件的目录(大部分情况下)。

大多数情况下,您每天关心的挂载点是那些包含非瞬态文件的挂载点。

使用 df 列出文件系统

除了知道哪个磁盘挂载在哪里,您可能还想知道在空间块上使用的是哪个文件系统;这可以通过-T标志来完成:

$ df -T
Filesystem                      Type     1K-blocks   Used Available Use% Mounted on
/dev/mapper/VolGroup00-LogVol00 xfs       39269648 849924  38419724   3% /
devtmpfs                        devtmpfs    239968      0    239968   0% /dev
tmpfs                           tmpfs       249712      0    249712   0% /dev/shm
tmpfs                           tmpfs       249712   4572    245140   2% /run
tmpfs                           tmpfs       249712      0    249712   0% /sys/fs/cgroup
/dev/sda2                       xfs        1038336  64076    974260   7% /boot
tmpfs                           tmpfs        49944      0     49944   0% /run/user/1000

在这里,我们可以轻松地看到我们的根目录(/)和引导目录被格式化为 XFS。

目前,CentOS 和 Red Hat 更喜欢使用 XFS,但不少系统使用ext4ext3ext2btrfszfs也并不少见。在功能上有区别,但在日常活动中,它们都能处理写入和读取文件,这是重要的部分。

列出逻辑卷管理器磁盘、卷组和逻辑卷

如果您正在使用 LVM(默认情况下我们正在使用,许多系统也是如此),您可能想知道由 LVM 处理的磁盘的布局。

物理磁盘

首先,我们需要知道 LVM 知道的物理卷是哪些;这可以通过pvspvdisplay来完成:

$ sudo pvs
 PV VG Fmt Attr PSize PFree
 /dev/sda3 VolGroup00 lvm2 a-- <38.97g 0 $ sudo pvdisplay
 --- Physical volume ---
 PV Name /dev/sda3
 VG Name VolGroup00
 PV Size <39.00 GiB / not usable 30.00 MiB
 Allocatable yes (but full)
 PE Size 32.00 MiB
 Total PE 1247
 Free PE 0
 Allocated PE 1247
 PV UUID vrrtbx-g480-HcJI-5wLn-4aOf-Olld-rC03AY

请注意sudo pvs是更传统的unix-y输出,而第二个更适合人类解析。

在这里,我们可以看到 LVM 知道的唯一物理设备是sda设备上的sda3分区。

在 LVM 领域,物理卷可以是整个设备(sda)或该设备上的分区(sda3)。通常,使用哪种取决于系统管理员的个人偏好,因为这两种方法都有利弊。就我个人而言,我更喜欢将整个设备交给 LVM,并让它完成所有工作,消除了一层抽象,但我知道有人发誓在 LVM 甚至发言之前将磁盘划分为分区。

卷组

您可以将多个物理卷组合在一起形成一个卷组;稍后,这将为逻辑卷的灵活性提供便利。

使用pvspvdisplay时,您将得到一个打印输出,告诉您磁盘所属的卷组,但如果您只想列出卷组信息,可以使用vgsvgdisplay

$ sudo vgs
 VG         #PV #LV #SN Attr   VSize   VFree
 VolGroup00   1   2   0 wz--n- <38.97g    0 $ sudo vgdisplay
 --- Volume group ---
 VG Name               VolGroup00
 System ID 
 Format                lvm2
 Metadata Areas        1
 Metadata Sequence No  3
 VG Access             read/write
 VG Status             resizable
 MAX LV                0
 Cur LV                2
 Open LV               2
 Max PV                0
 Cur PV                1
 Act PV                1
 VG Size               <38.97 GiB
 PE Size               32.00 MiB
 Total PE              1247
 Alloc PE / Size       1247 / <38.97 GiB
 Free  PE / Size       0 / 0 
 VG UUID               SA8LTU-F2yz-FEV1-RdgT-hw0Z-iRxh-yHFKuU

在这里,我们可以看到有两个逻辑卷位于这个卷组之上。

逻辑卷

最后,在 LVM 堆栈中,我们有逻辑卷。这些是文件系统应用的逻辑设备,然后可以挂载到系统上的某个点。

您是否已经弄清楚了命令命名背后的逻辑?

对于这一部分,我们将使用lvslvdisplay

$ sudo lvs
 LV       VG         Attr       LSize   Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
 LogVol00 VolGroup00 -wi-ao---- <37.47g 
 LogVol01 VolGroup00 -wi-ao----   1.50g 
$ sudo lvdisplay
 --- Logical volume ---
 LV Path                /dev/VolGroup00/LogVol00
 LV Name                LogVol00
 VG Name                VolGroup00
 LV UUID                j6b8IV-KEw3-7bTw-Oqy8-1Ud3-juFC-SJBg12
 LV Write Access        read/write
 LV Creation host, time localhost.localdomain, 2018-05-12 18:50:24 +0000
 LV Status              available
 # open                 1
 LV Size                <37.47 GiB
 Current LE             1199
 Segments               1
 Allocation             inherit
 Read ahead sectors     auto
 - currently set to     8192
 Block device           253:0

 --- Logical volume ---
 LV Path                /dev/VolGroup00/LogVol01
 LV Name                LogVol01
 VG Name                VolGroup00
 LV UUID                IAjIC6-ScnM-tvH6-7BTy-TN31-hd82-bgDSzd
 LV Write Access        read/write
 LV Creation host, time localhost.localdomain, 2018-05-12 18:50:25 +0000
 LV Status              available
 # open                 2
 LV Size                1.50 GiB
 Current LE             48
 Segments               1
 Allocation             inherit
 Read ahead sectors     auto
 - currently set to     8192
 Block device           253:1

有两个逻辑卷!

我们知道其中一个位于我们的根目录下,而且由于之前的lsblk,我们知道第二个提供了我们的交换空间。

列出交换

交换是特殊的,更像是扩展的、缓慢的、有些烦人的内存,而不是磁盘空间。

当您的系统 RAM 已满,并且内核开始将不经常访问的内存转移到磁盘上时,交换被使用,可以以更慢的速度读取回来。

是时候再次进行一场圣战了吗?我认为是的!一些系统管理员信奉交换,并始终确保他们的系统至少有几兆字节的交换空间,即使他们有 256GB 的 RAM;其他系统管理员说,如果您使用了那么多 RAM 并且仍在交换,您需要更多的 RAM。如果您不是做出是否甚至有交换的决定的人,请微笑并点头,这不值得。

我们可以使用swapon列出系统正在使用的交换,如下所示:

$ swapon --show
NAME      TYPE      SIZE USED PRIO
/dev/dm-1 partition 1.5G   0B   -1

在这里,我们可以看到我们的一个交换设备是/dev/dm-1,但我们认为它是一个 LVM 设备?这肯定不对。

但是它可以!

逻辑卷实际上是映射的;dm-1是我们逻辑卷的低级表示。在我们的逻辑卷设备上运行ls -l可以证明以下事实:

$ ls -l /dev/mapper/VolGroup00-LogVol01 
lrwxrwxrwx. 1 root root 7 Sep  9 09:13 /dev/mapper/VolGroup00-LogVol01 -> ../dm-1

我们的设备实际上是链接和映射到dm-1,这就是为什么它在我们的swapon命令中列出的方式。

它是如何工作的...

物理上,你有一个磁盘。

这个磁盘可以是硬盘驱动器(老式的旋转式碟片类型)或某种固态硬盘,比如 M.2 连接器上的NVMe或通用的 SATA。

无论磁盘的类型如何,你都想要用它来存储。

要在磁盘上存储数据,它需要一些东西。首先,它需要被操作系统读取;这一部分由内核处理。如果内核确定磁盘是 IDE 驱动器(不常见),它可能会显示为hda设备。

如果磁盘是 SATA 或 SCSI,它可能显示为一个sda设备。如果它是一个 virtio 虚拟磁盘,并且在虚拟机中显示为这样,它将被列为vda

磁盘的标识是顺序的,这就是为什么我们的三个磁盘显示为sdasdbsdc

这种标识不一定要一致;磁盘在启动时被分配它们的标识,这意味着你的计算机可能一天以sdb的方式启动,另一天以sda的方式启动,这是由于各种因素。解决这个问题的方法是使用磁盘 UUID(在之前的fstab中看到)或标签。

其次,在操作系统识别到磁盘存在后,它必须检查分区和文件系统。分区是磁盘的一个部分,文件系统是文件如何读取和写入到驱动器的配方。

在这一部分,我们首先使用了lsblk,用它来查询sysfs文件系统和udev数据库,然后以人类可读的方式显示出来。通常,这是我在尝试确定系统外观时的第一站。

之后,我们看了挂载点和文件系统。

挂载点是 Linux 层次结构中分配磁盘的区域。与 Windows 不同,Windows 的结构从磁盘开始,而 Linux 的结构是固定的,磁盘适应其中。

Linux 层次结构的挂载点和文件系统有点难以想象,但重要的是要记住的是一切都从根目录开始(即斜杠根目录或/),然后从那里构建。你可以有一个磁盘,有一个分区,并将斜杠根目录放在该分区上,从而使得最简单的系统成为可能。或者,你可以将你的主目录(/home)放在一个独立的物理磁盘上,但它仍然存在于斜杠根目录的上一级。

想象一下 Linux 层次结构的逻辑布局是绝对的,而磁盘几乎是谜题中不重要的一部分。如果你真的想这样做,你可以将文件系统的一部分挂载到/home/me/favourite_things/pokemon/absol,完全在一个磁盘上。

文件系统有点更明显,通常是静态的(除非你是一个真的想要冒险的系统管理员)。一旦你划分出你想要使用的磁盘部分(比如挂载/home),你就要决定文件系统。

最好选择在工作环境中使用的典型文件系统,比如 XFS 或ext4,而不是像btrfs这样的实验性文件系统。

在你的存储创建冒险结束时,你有一个磁盘,上面有一个分区,上面有一个ext4文件系统,你已经挂载到/home

还有更多...

...还有更多!

文件系统的世界是一个不断发展和变化的世界。你可能会认为,到现在为止,我们已经解决了数据存储的问题,但你会错。

有一些文件系统更适合于成千上万的小文件(例如数据库),有一些更适合于大块的文件(比如虚拟机磁盘)。你选择使用哪种取决于你。

有去重、快照,甚至自愈(显然)的文件系统。

FreeBSD 信奉 ZFS,在 Linux 世界的 Ubuntu 中也有。OpenSUSE 偏爱btrfs用于许多新安装,并且一些发行版保持经典,出于熟悉原因使用ext系列。

无论您决定使用哪一个,一定要保持备份-备份很重要。

文件系统层次结构

在本节中,我们将讨论hierman hier,作为确定您的文件系统不同名称含义的一种方式。

当您查看系统时,您可能会质疑为什么某些文件夹以这种方式命名:

$ ls
bin   dev  home  lib64  mnt  proc  run   srv  tmp  vagrant
boot  etc  lib   media  opt  root  sbin  sys  usr  var

什么是sbinopt

您可能还想知道为什么有一个名为root的文件夹,当我们应该在系统的根目录/中时:

[vagrant@localhost /]$ pwd
/

文件系统层次结构有您想要的答案!

准备工作

本节将使用我们的孤立 VM。

如果尚未连接,请连接到您的 VM。

我们还将直接引用 man 页面,因此请确保已安装以下 man 页面:

$ sudo yum reinstall -y man-db man-pages

如何做...

要了解发行版维护者如何看待发行版文件系统应该是什么样子,请运行man hier

$ man hier

手册页应该在您的默认分页器(通常是less)中打开,并且可以进行导航。

您应该看到类似于以下内容的东西-路径列表,并在每个路径旁边有描述:

Linux 手册层次页面

它是如何工作的...

这个手册页很重要,不能保证在您管理的系统上一致(即 Debian 和 CentOS 可能看起来完全陌生)。

它应该是发行版维护者对该发行版上特定文件放置位置的理解。因此,根据本手册页,如下所示:

"/bin 这个目录包含在单用户模式下需要的可执行程序,以及用于启动系统或修复系统的程序。"

这个很明显,但是如果我们想要一个附加包的目录呢?CentOS 的hier可以满足您的需求:

"/opt 这个目录应该包含包含静态文件的附加包。"

如果您看到一个路径,不确定它是用来做什么的,比如/usr/games?请参阅以下内容:

"/usr/games 游戏和教育程序的二进制文件(可选)。"

有趣的是,CentOS 提供的hier手册中省略了一个目录,即/srv,而我经常使用它。

从 Ubuntu 的hier手册中,我们可以看到它的定义:

"/srv 这个目录包含由该系统提供的特定站点数据。"

这是不同系统可能会将文件放在不同位置的很好的例子,也是消除困惑的好地方:

“/ 这是根目录。整个树的起点。

/root 这个目录通常是 root 用户的主目录(可选)。

还有更多...

在您的系统的hier手册的底部,您可能会看到对文件系统层次结构标准的引用,网址为www.pathname.com/fhs/。根据手册,这个标准如下:

“文件系统标准已经被设计用于 Unix 发行版开发人员、软件包开发人员和系统实施者使用。但是,它主要是作为参考,而不是 Unix 文件系统或目录层次结构管理的教程。”

这本身并不是很有帮助,因为它实际上说的是“这些更多是指南而不是规则”,就像《加勒比海盗》中的一样。

基本上,使用man hier作为了解系统布局的良好准则,但不要假设某些自恋的系统管理员会来到/usr/local/sbin中放置 Terraform。

配置空白磁盘并挂载它

在本节中,我们将使用 CLI 工具对我们的一个磁盘进行分区和格式化(不使用 LVM),并在此过程中讨论 GPT 和 MBR。然后我们将在系统上将我们的磁盘挂载到/home

准备工作

如果您在本章使用提供的Vagrantfile,您将拥有连接的两个空白磁盘的系统。如果您使用自己的解决方案,现在是添加几个空白磁盘的时候了。

连接到您的 VM 并确保您可以看到/dev/sdb;如果看不到,请仔细检查您的 Vagrant 设置:

$ ls -l /dev/sd*
brw-rw----. 1 root disk 8,  0 Sep  9 15:27 /dev/sda
brw-rw----. 1 root disk 8,  1 Sep  9 15:27 /dev/sda1
brw-rw----. 1 root disk 8,  2 Sep  9 15:27 /dev/sda2
brw-rw----. 1 root disk 8,  3 Sep  9 15:27 /dev/sda3
brw-rw----. 1 root disk 8, 16 Sep  9 15:27 /dev/sdb
brw-rw----. 1 root disk 8, 32 Sep  9 15:27 /dev/sdc

如何做...

首先,我们需要对磁盘进行分区。在这里,我们将创建两个各占一半磁盘的分区。

首先使用fdisk/dev/sdb进行操作:

$ fdisk /dev/sdb 

您将进入不同的 shell,即fdisk的 shell。

首先,我们将通过输入g创建GPT disklabel

Command (m for help): g
Building a new GPT disklabel (GUID: DE706D04-7069-456C-B8C4-C3E405D18A06)

接下来,我们将创建一个新分区,分区号为1,第一个扇区为2048,大小为 1 GB。

我们通过按下nEnter,接受前两个提示的默认值(通过在没有输入的情况下按Enter)并在提示Last sector时输入+1G来完成这一步:

Command (m for help): n
Partition number (1-128, default 1): 
First sector (2048-4194270, default 2048):
Last sector, +sectors or +size{K,M,G,T,P} (2048-4194270, default 4194270): +1G
Created partition 1

接下来,我们再次使用n创建第二个分区,尽管这次我们将每次接受默认值(按Enter三次),因为我们想使用磁盘的剩余空间:

Command (m for help): n
Partition number (2-128, default 2): 
First sector (2099200-4194270, default 2099200): 
Last sector, +sectors or +size{K,M,G,T,P} (2099200-4194270, default 4194270): 
Created partition 2

现在,我们已经按照我们的意愿布置了分区,我们需要将表写入磁盘并退出fdisk。使用w来完成这一步:

Command (m for help): w
The partition table has been altered!

Calling ioctl() to re-read partition table.
Syncing disks.

如今,系统在设备的分区表更改方面自动重新读取得很好,尽管偶尔您可能仍然需要运行partprobe手动通知内核有任何更改。

现在运行lsblk应该显示我们的新分区:

$ lsblk
<SNIP>
sdb                       8:16   0    2G  0 disk 
├─sdb1                    8:17   0    1G  0 part 
└─sdb2                    8:18   0 1023M  0 part 
<SNIP>

现在我们有了两个分区,我们将使用文件系统对它们进行格式化。

出于本教程的目的,我们将一个格式化为ext4,另一个格式化为 XFS:

$ sudo mkfs.ext4 /dev/sdb1 
mke2fs 1.42.9 (28-Dec-2013)
Filesystem label=
OS type: Linux
<SNIP>

您将看到各种信息,但希望格式化应该很快完成。

对于第二个分区,我们将只使用mkfs命令,它缺少使用mkfs.ext4的隐含类型:

$ sudo mkfs -t xfs /dev/sdb2
meta-data=/dev/sdb2              isize=512    agcount=4, agsize=65471 blks
 =                       sectsz=512   attr=2, projid32bit=1
<SNIP>

我们可以在这里使用一个新工具(blkid)来打印这些分区的 UUID 和TYPE

$ sudo blkid /dev/sdb1
/dev/sdb1: UUID="4fba66a8-4be9-4835-b393-72db4bb74c0a" TYPE="ext4" PARTUUID="c517d14f-0c9d-42cc-863c-8a6985a272c1" 
$ sudo blkid /dev/sdb2
/dev/sdb2: UUID="44a4b4e1-bf8b-4ec0-8485-d544a0333b00" TYPE="xfs" PARTUUID="671f397a-3e33-46b8-831d-2d87ca3d170d" 

看起来不错!

最后,最好在用新文件系统替换之前,从您希望挂载的位置复制文件。

如果我们此刻查看/home,它看起来像这样:

$ ls /home
vagrant

如果我们将我们的文件系统之一挂载到/home并再次ls,它看起来像这样:

$ ls /home
lost+found

我们的 Vagrant 用户的主文件夹消失了!

lost+found文件夹是fsck(文件系统修复实用程序)的一个功能,是它无法理解的文件片段的倾倒地。

这是因为我们在旧位置的顶部挂载了一个系统;如果我们卸载这个新文件系统并再次ls目录,它看起来像这样:

$ ls /home
vagrant

所以,我们真正需要做的是在写入数据之前复制所有现有数据(保留所有权和权利)。

首先,在/mnt中创建一个文件夹(这是这样做的标准位置),挂载我们的新文件系统,并复制数据:

$ sudo mkdir /mnt/home2
$ sudo mount /dev/sdb1 /mnt/home2
$ sudo cp -rp --preserve=all /home/* /mnt/home2/ 

在以前,我们使用了-r进行递归复制和--preserve=all来保留诸如文件的 SELinux 上下文以及所有权和时间戳等内容。

通过确认 Vagrant 用户的 SSHauthorized_keys文件仍然具有权限-rw-------来检查您的结果:

$ ls -lha /mnt/home2/vagrant/.ssh/authorized_keys 
-rw-------. 1 vagrant vagrant 389 Sep  9 15:28 /mnt/home2/vagrant/.ssh/authorized_keys

现在,从我们的临时位置umount文件系统,并将其挂载到以前的/home上,确保我们首先不在/home中(通过移动到不同的目录):

$ cd /
$ sudo umount /mnt/home2/
$ sudo mount /dev/sdb1 /home

我们故意移动到文件系统的根目录(/),以避免设备忙碌并引起复杂性,尽管当尝试卸载您仍在其中的文件系统时,这更多是一个问题。

现在运行df应该显示您新挂载的分区:

$ df
<SNIP>
/dev/sdb1                          999320   2576    927932   1% /home

重新启动后,此更改将不会被保留。稍后,我们将研究如何使用fstabsystemd-mountd使此更改永久。

工作原理...

当我们将物理设备(sdb)划分为两个分区时,我们使用fdisk创建了它们。

不过,首先,我们需要给磁盘分区表,以便它可以存储我们正在创建的分区的信息。

经典的分区表称为主引导记录MBR),新学校的分区表称为GUID 分区表GPT)。

您可能仍然会看到 MBR 系统在周围漂浮,但 GPT 在这些天明显更好,允许诸如多于四个主分区之类的功能(MBR 受限于此)。

您可以通过再次加载fdisk并在命令行上传递p来查看磁盘上的分区:

Command (m for help): p

Disk /dev/sdb: 2147 MB, 2147483648 bytes, 4194304 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: gpt
Disk identifier: DE706D04-7069-456C-B8C4-C3E405D18A06

#         Start          End    Size  Type            Name
 1         2048      2099199      1G  Linux filesyste 
 2      2099200      4194270   1023M  Linux filesyste

然后,这些逻辑空间可以在其上应用文件系统,这样当您的操作系统尝试将文件写入磁盘时,磁盘知道如何存储数据。

完成后,磁盘可以在 Linux 文件系统层次结构中的任何位置挂载,替换您关心的任何路径。

这是因为 Linux 不在乎您的系统连接了多少个磁盘,或者它们是什么类型的磁盘;它只关心挂载点。

还有更多...

需要注意的一件事是,不同的分区类型有不同的分区系统 ID 可用。

在 CentOS 上可用的 Linux 列表如下:

 19 Linux swap                     0657FD6D-A4AB-43C4-84E5-0933C84B4F4F
 20 Linux filesystem               0FC63DAF-8483-4772-8E79-3D69D8477DE4
 21 Linux server data              3B8F8425-20E0-4F3B-907F-1A25A76F98E8
 22 Linux root (x86)               44479540-F297-41B2-9AF7-D131D5F0458A
 23 Linux root (ARM)               69DAD710-2CE4-4E3C-B16C-21A1D49ABED3
 24 Linux root (x86-64)            4F68BCE3-E8CD-4DB1-96E7-FBCAF984B709
 25 Linux root (ARM-64)            B921B045-1DF0-41C3-AF44-4C6F280D3FAE
 26 Linux root    (IA-64)             993D8D3D-F80E-4225-855A-9DAF8ED7EA97
 27 Linux reserved                 8DA63339-0007-60C0-C436-083AC8230908
 28 Linux home                     933AC7E1-2EB4-4F13-B844-0E14E2AEF915
 29 Linux RAID                     A19D880F-05FC-4D3B-A006-743F0F84911E
 30 Linux extended boot            BC13C2FF-59E6-4262-A352-B275FD6F7172
 31 Linux LVM                      E6D6D379-F507-44C2-A23C-238F2A3DF928

这些 ID 如今比任何其他东西都更具信息量,尽管从历史上看,它们可能被用于通知系统所需的特定方法来读写数据。

例如,如果在 OpenBSD 系统上正确标记了分区,然后将其所在的驱动器插入 Linux 系统,Linux 系统应该读取 ID 并意识到它是什么,最好不要触及其中的数据。

重新配置使用 LVM 的磁盘

我们将在系统中格式化第二个磁盘,这次我们将使用 LVM 来完成。在为新创建的逻辑卷创建文件系统并将其挂载到系统的某个位置之前,我们将使用各种 LVM 工具(lvspvsvgs)来完成这一点。

做好准备

在本节中,我们将使用系统中的第二个磁盘(在您的系统上可能是sdc)。

连接到您的centos1虚拟机,并检查是否有另一个可用于工作的磁盘。

如果您直接从上一节过来,您的lsblk可能看起来像下面这样:

$ lsblk
NAME                    MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda                       8:0    0   40G  0 disk 
├─sda1                    8:1    0    1M  0 part 
├─sda2                    8:2    0    1G  0 part /boot
└─sda3                    8:3    0   39G  0 part 
 ├─VolGroup00-LogVol00 253:0    0 37.5G  0 lvm  /
 └─VolGroup00-LogVol01 253:1    0  1.5G  0 lvm  [SWAP]
sdb                       8:16   0    2G  0 disk 
├─sdb1                    8:17   0    1G  0 part /mnt/home2
└─sdb2                    8:18   0 1023M  0 part 
sdc                       8:32   0    2G  0 disk 

我们将在这里使用sdc

如何做到这一点...

正如我之前提到的,有些人喜欢在引入 LVM 生活方式之前在他们的驱动器上创建一个分区。

我们将在这里这样做,但只是因为如果我不这样做,我最终会与我的一位技术作者发生争执。

为了增加新奇感,我们将使用fdisk而不是进入命令的 shell,以强调 Linux 中有多种执行相同操作的方法:

$ printf "g\nn\n\n\n\nt\n31\nw\n" | sudo fdisk /dev/sdc

我们还将设置我们的分区系统 ID 为 31,即 Linux LVM。

为了更进一步,我们将为我们的分区应用一个分区标签,为其提供一个友好的名称:

$ sudo parted /dev/sdc name 1 "MostlyHarmless"

PartLabels 非常有用,尽管几乎没有人使用它们!它们也仅适用于 GPT 磁盘。它们基本上意味着您可以按名称引用分区,而不是按编号或分区 UUID。如果您发现自己在 USB 硬盘上使用 ZFS,我可能刚刚帮您避免了一次动脉瘤。

现在我们有了一个分区,让我们将其呈现给 LVM。首先,我们必须让 LVM 意识到它,使用pvcreate

$ sudo pvcreate /dev/disk/by-partlabel/MostlyHarmless
 Physical volume "/dev/disk/by-partlabel/MostlyHarmless" successfully created.

完成后,pvs命令将列出我们的新物理设备:

$ sudo pvs
 PV         VG         Fmt  Attr PSize   PFree 
 /dev/sda3  VolGroup00 lvm2 a--  <38.97g     0 
 /dev/sdc1             lvm2 ---   <2.00g <2.00g

将其添加到卷组是第二步,这涉及创建卷组(或者我们可以将其添加到VolGroup00,但现在我们将创建一个新的):

$ sudo vgcreate VolGroup01 /dev/disk/by-partlabel/MostlyHarmless
 Volume group "VolGroup01" successfully created

最后,我们将在此组中创建一个逻辑卷,尽管出于新奇,我们不会使用卷组中的所有可用空间:

$ sudo lvcreate -l 50%FREE -n Home3 VolGroup01
 Logical volume "Home3" created.

请注意,现在使用lvs列出我们的逻辑卷将显示我们的新逻辑卷,它使用VolGroup01空间的 50%:

$ sudo lvs
 LV       VG         Attr       LSize    Pool Origin Data%  Meta%  Move Log Cpy%Sync Convert
 LogVol00 VolGroup00 -wi-ao----  <37.47g 
 LogVol01 VolGroup00 -wi-ao----    1.50g 
 Home3    VolGroup01 -wi-a----- 1020.00m 

逻辑卷可以是许多东西,并且具有各种用途。我们在这里创建的是一个简单的线性卷,适用于日常任务,但缺少冗余等功能。

现在我们有一个可以在其上放置文件系统的磁盘,然后将其挂载到我们的 VM 上的某个位置。

为了创建文件系统,我们再次使用mkfs

$ sudo mkfs.btrfs /dev/mapper/VolGroup01-Home3 
btrfs-progs v4.9.1
See http://btrfs.wiki.kernel.org for more information.

Label:              (null)
UUID:               7bf4939e-196a-47cf-9326-1408cdf920ac
Node size:          16384
Sector size:        4096
Filesystem size:    1020.00MiB
Block group profiles:
 Data:             single            8.00MiB
 Metadata:         DUP              51.00MiB
 System:           DUP               8.00MiB
SSD detected:       no
Incompat features:  extref, skinny-metadata
Number of devices:  1
Devices:
 ID        SIZE  PATH
 1  1020.00MiB  /dev/mapper/VolGroup01-Home3

而且,我们可以挂载它(首先创建一个挂载点):

$ sudo mkdir /mnt/home3
$ sudo mount /dev/mapper/VolGroup01-Home3 /mnt/home3

lsblk可以确认我们的新设置:

sdc                       8:32   0    2G  0 disk 
└─sdc1                    8:33   0    2G  0 part 
 └─VolGroup01-Home3    253:2    0 1020M  0 lvm  /mnt/home3

它是如何工作的...

我们创建的是层:

  • 我们有我们的物理磁盘(sdc

  • 我们在我们的物理磁盘(sdc1)之上有一个分区

  • 我们有我们的卷组,里面有我们的物理卷(VolGroup01

  • 我们有我们的逻辑卷,位于我们的卷组之上(Home3

  • 我们有我们的文件系统,位于我们的逻辑卷之上,然后我们将其挂载到/mnt/home3

这意味着我们有复杂性,但我们也有灵活性。

我们所做的是创建一个虚拟块设备,以我们的逻辑卷的形式。这个逻辑卷将有数据写入它,然后根据内核的决定将这些数据应用到卷组中的物理卷上。

还有更多...

当我们创建逻辑卷时,我们只需指定新设备应使用可用空间的 50%,但我们也可以建议使用绝对值的特定大小(例如 1G)。

您可能会问为什么要使用 LVM,如果我们实际上达到了我们在简单地在磁盘分区之上放置文件系统时所处的相同位置。答案是:灵活性。

在 LVM 领域,您可以通过向卷组添加更多物理磁盘来扩展卷组,您可以在运行中的系统中将数据从一个物理磁盘移动到另一个物理磁盘,并且甚至可以在热插拔(或热拔插)模式下将所有数据从驱动器移出,然后移除该驱动器。这取决于您的文件系统是否支持这样的更改,但现代的文件系统会支持(允许您在运行时扩展和收缩它们)。

作为先前的示例,让我们将我们的逻辑卷扩展到使用卷组的所有可用空间:

$ sudo lvextend -l +100%FREE VolGroup01/Home3
 Size of logical volume VolGroup01/Home3 changed from 1.00 GiB (256 extents) to <2.00 GiB (511 extents).
 Logical volume VolGroup01/Home3 successfully resized.

请注意100%部分前面的+符号。这表明lvextend要求您将新大小添加到旧大小上;必须使用整个 2G 磁盘来这样做。

扩展后,我们仍然必须扩展我们的文件系统以适应可用空间:

$ df -h /dev/mapper/VolGroup01-Home3 
Filesystem                    Size  Used Avail Use% Mounted on
/dev/mapper/VolGroup01-Home3 1020M   17M  901M   2% /mnt/home3

为了做到这一点,我们需要使用一个btrfs命令:

$ sudo btrfs filesystem resize max /mnt/home3
Resize '/mnt/home3' of 'max'

现在,我们应该有了我们的空间:

$ df -h /dev/mapper/VolGroup01-Home3 
Filesystem                    Size  Used Avail Use% Mounted on
/dev/mapper/VolGroup01-Home3  2.0G   17M  1.9G   1% /mnt/home3

这只是 LVM 灵活性的一个例子,它还提供了更多功能。它甚至可以更轻松地迁移数据,因为您可以轻松地将池导入其他系统。

但它确实有其权衡之处。例如,我最近试图尽快让虚拟机启动(用于测试目的),最终忽略了 LVM,因为直接在启动时访问磁盘更快(在操作系统中没有区别,但对于我的环境来说,启动速度很重要)。

使用 systemd-mount 和 fstab

在本节中,我们将学习如何确保我们新配置的磁盘在启动时出现,并如何运行测试以查看它是否会在启动时出现。

为此,我们将使用向fstab文件添加磁盘的传统方法,我们还将使用systemd-mount

您可以使用以下内容直接将/dev/sdb重新配置为单个分区,格式为ext4

$ printf "g\nn\n\n\n\nw\n" | sudo fdisk /dev/sdb && sudo mkfs.ext4 /dev/sdb1

准备好了

在本节中,我们将同时使用我们的sdbsdc驱动器。

如果您重建了您的虚拟机,请尝试前面的部分,最终得到一个在分区上有一个简单的文件系统和一个在 LVM 逻辑卷上的驱动器。

重新启动虚拟机,这样你就可以分区驱动器,但它们没有挂载。

它应该看起来像下面这样:

$ lsblk
NAME                    MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
sda                       8:0    0   40G  0 disk 
├─sda1                    8:1    0    1M  0 part 
├─sda2                    8:2    0    1G  0 part /boot
└─sda3                    8:3    0   39G  0 part 
 ├─VolGroup00-LogVol00 253:0    0 37.5G  0 lvm  /
 └─VolGroup00-LogVol01 253:1    0  1.5G  0 lvm  [SWAP]
sdb                       8:16   0    2G  0 disk 
└─sdb1                    8:17   0    2G  0 part 
sdc                       8:32   0    2G  0 disk 
└─sdc1                    8:33   0    2G  0 part 
 └─VolGroup01-Home3    253:2    0    2G  0 lvm 

(请注意,sdb1VolGroup01-Home3没有挂载点)。

如何做到这一点...

我们将从传统的fstab方法开始。

fstab

我们的fstab目前看起来是这样的:

$ cat /etc/fstab 

#
# /etc/fstab
# Created by anaconda on Sat May 12 18:50:26 2018
#
# Accessible filesystems, by reference, are maintained under '/dev/disk'
# See man pages fstab(5), findfs(8), mount(8) and/or blkid(8) for more info
#
/dev/mapper/VolGroup00-LogVol00 /                       xfs     defaults        0 0
UUID=570897ca-e759-4c81-90cf-389da6eee4cc /boot                   xfs     defaults        0 0
/dev/mapper/VolGroup00-LogVol01 swap                    swap    defaults        0 0

我们将在底部添加另一行,提示我们的/dev/sdb1分区挂载为/home。首先,我们将获取分区的 UUID,因为我们不希望我们的sdb设备突然变成sdc并破坏我们的引导:

$ ls -l /dev/disk/by-uuid/ | grep sdb
lrwxrwxrwx. 1 root root 10 Sep 15 06:45 10572fe4-5f65-4df0-9e69-dcd885e9f01e -> ../../sdb1

很好 - 现在我们有了(10572fe4-5f65-4df0-9e69-dcd885e9f01e),我们可以添加它:

$ echo "UUID=10572fe4-5f65-4df0-9e69-dcd885e9f01e /opt ext4 defaults 0 0" | sudo tee -a /etc/fstab

我们在这里做的是告诉fstab我们的分区在哪里(UUID),告诉它在哪里挂载分区(/opt),并且给它文件系统格式,以便它知道如何挂载它(ext4)。然后,我们告诉它使用默认的挂载选项(defaults),这对大多数用例来说已经足够好了;我们指定文件系统不需要被转储(0),并且我们不希望在启动时对其进行任何检查(0),尽管在现实世界中,你可能想要启用这个选项。

请注意,我们可以立即用mount挂载fstab

$ sudo mount -a
$ df -h /opt
Filesystem      Size  Used Avail Use% Mounted on
/dev/sdb1       2.0G  6.0M  1.9G   1% /opt

任何错误都会立即显而易见,因为mount会拒绝工作。

在重新启动之前运行mount -a比让系统停滞并且无法启动更可取——从经验中得出。

systemd-mount

如果你想要新潮一点,你可能想使用systemd-mount而不是老旧的(重点是我的)fstab

首先,如果我们之前的章节中还没有创建我们的local单元目录,我们需要创建它:

$ sudo mkdir -p /usr/local/lib/systemd/system

然后,用以下内容填充一个新文件:

$ sudo tee /usr/local/lib/systemd/system/srv.mount << HERE
[Unit]
Description=Mounting our logical volume as srv

[Mount]
What=/dev/mapper/VolGroup01-Home3
Where=/srv
Type=btrfs
Options=defaults

[Install]
WantedBy=multi-user.target
HERE

现在,我们可以启动和enable我们的mount

$ sudo systemctl enable --now srv.mount
Created symlink from /etc/systemd/system/multi-user.target.wants/srv.mount to /usr/local/lib/systemd/system/srv.mount.

在这一部分结束后,重新启动后,你的lsblk输出应该如下所示:

sdb                       8:16   0    2G  0 disk 
└─sdb1                    8:17   0    2G  0 part /opt
sdc                       8:32   0    2G  0 disk 
└─sdc1                    8:33   0    2G  0 part 
 └─VolGroup01-Home3    253:2    0    2G  0 lvm  /srv

工作原理...

systemd系统上,通常情况下fstab都是由systemd管理的。

当系统启动时,实际上发生的是systemd-fstab-generator读取/etc/fstab文件,并将其找到的内容转换为systemd单元。这就是你可以用systemctl列出挂载点的原因:

$ systemctl list-units *.mount --no-legend
-.mount                      loaded active mounted /
boot.mount                   loaded active mounted /boot
dev-hugepages.mount          loaded active mounted Huge Pages File System
dev-mqueue.mount             loaded active mounted POSIX Message Queue File System
opt.mount                    loaded active mounted /opt
run-user-1000.mount          loaded active mounted /run/user/1000
srv.mount                    loaded active mounted Mounting our logical volume asrv
sys-kernel-config.mount      loaded active mounted Configuration File System
sys-kernel-debug.mount       loaded active mounted Debug File System
var-lib-nfs-rpc_pipefs.mount loaded active mounted RPC Pipe File System

这就是为什么我们可以用systemctl cat看到分区的详细信息:

$ systemctl cat boot.mount
# /run/systemd/generator/boot.mount
# Automatically generated by systemd-fstab-generator

[Unit]
SourcePath=/etc/fstab
Documentation=man:fstab(5) man:systemd-fstab-generator(8)
Before=local-fs.target

[Mount]
What=/dev/disk/by-uuid/570897ca-e759-4c81-90cf-389da6eee4cc
Where=/boot
Type=xfs

这确实引出了一个问题,如果你有使用systemd的选项,为什么你还要使用fstab,简单的答案是:传统。目前,人们期望在fstab中找到mount信息,但在未来,这可能会改变。

还有更多...

如果你的系统在对fstab进行更改或对systemd-mount文件进行更改后无法启动,那么下一步(除了恐慌)就是登录到服务器的控制台。在这种情况下,我们通过连接到 VirtualBox 窗口并连接到我们控制台会话的图形表示,然后进入单用户模式来实现。

然后,你需要从配置中删除有问题的行,并再次重新启动系统。

我导致系统在启动时卡住的次数比我能数清的还要多,历史上这并不是一个问题,因为我之前提到的原因。然而,在现代云环境中,你可能并不总是能够获得控制台(在撰写本文时,Azure 刚刚实现了这个功能),因此在重新启动之前确保你的fstab条目是正确的是一个好主意!

systemd.mount的手册是查找systemd理解的挂载选项的好地方。

另请参阅

fstab自 1980 年发布的 4.0 BSD 以来一直存在,或多或少地以某种形式存在。显然,当时它并没有使用systemd,但是,4.0 BSD 并没有做太多事情。

如果你对fstab文件的历史感兴趣,我会看一下,如果电视上没有好节目的话。

_netdev是另一个需要注意的东西,我在这里提一下,因为它经常帮了我大忙。这是一个可以添加到挂载点(就像默认值)的选项,它告诉systemd文件系统依赖于你的网络是否正常。对于 NFS 和 iSCSI 环境,这可能是必须的。

磁盘加密和处理静态加密

在这里,我们将看看如何使用dm-crypt来加密我们的磁盘,以便在从机器上移除时设备上的数据是安全的。我们也会简要介绍本地文件加密。

准备工作

在这一部分,我们将使用我们的sdb驱动器。

如果你已经重建了你的虚拟机,尝试一下前面的部分,最终得到一个在分区上有一个简单文件系统的驱动器。

在您的虚拟机上,首先确保您已经删除了任何添加的fstab条目;对我来说,这是运行以下sed命令的情况:

$ sudo sed -i 's#UUID=10572fe4-5f65-4df0-9e69-dcd885e9f01e /opt ext4 defaults 0 0##g' /etc/fstab

然后,我重新启动了系统,以确保/opt在启动时没有被挂载,最后我重新生成了我的磁盘上的第一个分区:

$ printf "g\nn\n\n\n\nw\n" | sudo fdisk /dev/sdb

再次说明,您的情况可能有所不同,但您最终想要的是一个只有一个分区的磁盘,现在还没有格式化。

我们还需要安装适当的工具来完成这一部分:

$ sudo yum install -y cryptsetup

如何做...

首先,建议您在创建加密分区之前用随机数据填充驱动器;这样以后无法恢复未加密的数据。

我将使用shred来完成这个任务:

$ sudo shred -v -n1 /dev/sdb1 
shred: /dev/sdb1: pass 1/1 (random)...
shred: /dev/sdb1: pass 1/1 (random)...1.6GiB/2.0GiB 82%
shred: /dev/sdb1: pass 1/1 (random)...2.0GiB/2.0GiB 100%

现在,我们已经用随机数据填满了驱动器,我们必须将我们的分区格式化为Linux 统一密钥设置LUKS),这是一种加密规范:

$ sudo cryptsetup luksFormat /dev/sdb1 

WARNING!
========
This will overwrite data on /dev/sdb1 irrevocably.

Are you sure? (Type uppercase yes): YES
Enter passphrase: 
Verify passphrase: 

如您所见,有一些检查要素,例如在提示输入密码之前,要求您输入所有大写的YES。密码有一些要求,例如至少为 12 个字符,不能基于字典单词。

我们已经创建了一个encrypted分区,现在我们需要在可以使用它之前打开它:

$ sudo cryptsetup luksOpen /dev/sdb1 encrypted-opt
Enter passphrase for /dev/sdb1:

使用lsblk,您现在应该看到加密设备:

sdb                       8:16   0    2G  0 disk 
└─sdb1                    8:17   0    2G  0 part 
 └─encrypted-opt       253:3    0    2G  0 crypt 

那么,我们该怎么做呢?我们用以下方式格式化它:

$ sudo mkfs.btrfs /dev/mapper/encrypted-opt

你可能已经注意到我在这里经常使用btrfs;这不是因为对一个甚至无法正确执行 RAID 的文件系统格式的错误忠诚,而是因为当我打字时,我会在脑海中阅读我所打的字,而且用“butter fs”比“ext”更容易想到。现在你知道了;不用谢。

是时候挂载它了:

$ sudo mount /dev/mapper/encrypted-opt /opt

恭喜!您的加密磁盘现在将像其他磁盘一样运行,并且您可以放心,如果有人偷走了您的硬盘,其内容将被加密和密码锁定。我们可以用lsblk看到我们的设置:

sdb                       8:16   0    2G  0 disk 
└─sdb1                    8:17   0    2G  0 part 
 └─encrypted-opt       253:3    0    2G  0 crypt /opt

要关闭加密卷,首先必须卸载文件系统:

$ sudo umount /opt

现在,您再次运行cryptsetup,只是这次使用luksClose

$ sudo cryptsetup luksClose encrypted-opt

但是,如果我们想在启动时挂载我们的磁盘呢?

好吧,首先,您应该质疑这样做并考虑后果。加密的目的是保护数据,如果您设置一个磁盘自动启动,您将使您的安全性无效(假设整个盒子被拿走,而不仅仅是唯一的硬盘)。

然而,有人可能会认为加密驱动器但自动解锁和挂载它的“解锁密钥”仍然有用,如果您的驱动器损坏并且您想将其送回给驱动器制造商进行更换。没有密钥,即使他们可以读取盘片上的数据,他们得到的只是杂乱的噪音。

考虑到这一点,让我们在启动时挂载/dev/sdb1

我们需要加密磁盘的 UUID;为此,我们使用luksUUID来简化生活:

$ sudo cryptsetup luksUUID /dev/sdb1
8d58f0ec-98f1-4cf8-a78d-3cb6a4643350

首先,在/etc/crypttab中添加一个条目:

$ echo "encrypted-opt UUID=8d58f0ec-98f1-4cf8-a78d-3cb6a4643350 /cryptfile" | sudo tee -a /etc/crypttab

其次,我们需要为我们的磁盘添加fstab条目:

$ echo "/dev/mapper/encrypted-opt /opt btrfs defaults 0 0" | sudo tee -a /etc/fstab

正如我们之前所看到的,如果你愿意的话,这也可以是自己的systemd单元文件。

第三,创建一个适当复杂的密钥来使用:

$ echo "SuperSecretKeyPhrase" | sudo tee -a /cryptfile

现在,将此密钥添加到我们的加密分区。在提示输入任何现有密码时,输入您第一次创建驱动器时给出的密码:

$ sudo cryptsetup luksAddKey /dev/sdb1 /cryptfile 
Enter any existing passphrase: 

在这一点上,您可以重新启动您的虚拟机,希望(在 VirtualBox 的控制台上保持关注)它会顺利启动。

值得注意的是,现在可以使用您的密码或密钥文件(如下所示)打开加密卷:

$ sudo cryptsetup luksOpen /dev/sdb1 encrypted-opt -d /cryptfile 

它是如何工作的...

我们在这里所做的只是简单地创建了一个加密卷;它是另一个逻辑磁盘,一旦解锁,系统就可以像对待其他存储介质一样对待它。

cryptsetup是在这里使用的关键组件,最好的描述方式莫过于它自己的手册页面。

cryptsetup 用于方便地设置 dm-crypt 管理的设备映射。这些包括普通的 dm-crypt 卷和 LUKS 卷。区别在于 LUKS 使用元数据头,因此可以提供比普通 dm-crypt 更多的功能。另一方面,头部是可见的并且容易受到损坏。

使用cryptsetup,我们首先格式化了我们设置的分区并设置了初始密码。这是luksFormat元素。完成后,我们的 LUKS 分区就可以打开了,这是将我们分配的密码传递给设备的过程,然后设备会自动设置设备映射。然后,我们可以使用有用的文件系统(再次是btrfs)格式化我们的映射设备并挂载它。

您在 LUKS 设备上执行的大部分工作将使用cryptsetup完成(至少在服务器上)。

还有更多...

默认情况下,如果您尝试在加密驱动器上挂载文件系统,而不知道或不关心它是 LUKS 加密的,您将收到一个描述性消息,这应该给您一个提示:

$ sudo mount /dev/sdb1 /opt
mount: unknown filesystem type 'crypto_LUKS'

此时,您知道您需要首先在驱动器上运行luksOpen,您可能会发现您早已忘记了密码,磁盘上的数据实际上已经消失了(或者消失了)。

在本节中,我们使用根分区上的密钥文件挂载了我们的驱动器;如果您在crypttab中添加一行带有none,然后将条目添加到fstab,您将在启动时被提示输入密码:

$ echo "encrypted-opt /dev/sdb1 none" | sudo tee -a /etc/crypttab 
$ echo "/dev/mapper/encrypted-opt /opt btrfs defaults 0 0" | sudo tee -a /etc/fstab
$ sudo reboot

现在,您需要转到控制台并输入您的 LUKS 密码,如下面的屏幕截图所示:

这必须从控制台完成,因为 SSH 尚未启动。显然,在云环境或物理服务器构建中,这可能会有些棘手。

另请参阅

如果您一直在跟进,您可能会问systemd如何处理crypttab,如果它将fstab转换为systemd单元。

答案是它实际上做了非常相似的事情,事实上,在启动时使用了一个类似命名的程序:systemd-cryptsetup-generator

我们实际上可以通过查看自动生成的文件来看看我们的加密设备在启动时发生了什么:

$ systemctl cat systemd-cryptsetup@encrypted\\x2dopt.service 
# /run/systemd/generator/systemd-cryptsetup@encrypted\x2dopt.service
# Automatically generated by systemd-cryptsetup-generator

[Unit]
Description=Cryptography Setup for %I
Documentation=man:crypttab(5) man:systemd-cryptsetup-generator(8) man:systemd-cryptsetup@.service(8)
SourcePath=/etc/crypttab
DefaultDependencies=no
Conflicts=umount.target
IgnoreOnIsolate=true
After=systemd-readahead-collect.service systemd-readahead-replay.service
After=cryptsetup-pre.target
Before=cryptsetup.target
RequiresMountsFor=/cryptfile
BindsTo=dev-disk-by\x2duuid-8d58f0ec\x2d98f1\x2d4cf8\x2da78d\x2d3cb6a4643350.device
After=dev-disk-by\x2duuid-8d58f0ec\x2d98f1\x2d4cf8\x2da78d\x2d3cb6a4643350.device
Before=umount.target

[Service]
Type=oneshot
RemainAfterExit=yes
TimeoutSec=0
ExecStart=/usr/lib/systemd/systemd-cryptsetup attach 'encrypted-opt' '/dev/disk/by-uuid/8d58f0ec-98f1-4cf8-a78d-3cb6a4643350' '/cryptfile' ''
ExecStop=/usr/lib/systemd/systemd-cryptsetup detach 'encrypted-opt'

您可以看到底部运行了attach命令。

当前文件系统格式

有很多文件系统格式——有些比其他更受欢迎;有些用于非常特定的任务;有些是某些操作系统的宠儿;而其他一些应该在多年前就消失了。

在 Windows 世界中,我们通常看到 NTFS,但 FAT32,exFAT,甚至在某些情况下还有 FAT16 是可选项。

最近,苹果已经放弃了老化的 HFS+,全力向 APFS 作为未来的文件系统迈进。

FreeBSD 默认为 ZFS(如果您有足够的 RAM)或 UFS(如果您没有)。

OpenBSD——嗯,OpenBSD 使用 FFS,听起来就像它的名字一样好。

快速文件系统FFS)基本上就是 UFS。

Linux 是另一个完全不同的东西,因为它不仅可以处理以前列出的所有文件系统,而且还可以从数百个其他文件系统中进行选择。

现在,我们将看看在我们的 VM 上有什么可用的。

准备工作

对于本节,您只需要访问您的 VM,可能还需要整个互联网。

连接到您的 Vagrant box;本节纯粹是信息性的,所以现在不用担心它的状态。

如何做...

列出您可以创建的系统相对容易,因为您可以多次使用mkfs来获取已建立的别名列表:

$ mkfs
mkfs         mkfs.cramfs  mkfs.ext3    mkfs.minix 
mkfs.btrfs   mkfs.ext2    mkfs.ext4    mkfs.xfs

您的发行版可能还有其他可用的文件系统格式,尽管默认情况下未安装。

例如,如果我想管理 DOS 文件系统,我可以安装dosfstools

$ sudo yum install dosfstools -y

我突然有了msdosvfatfat选项:

$ mkfs
mkfs         mkfs.cramfs  mkfs.ext3    mkfs.fat     mkfs.msdos   mkfs.xfs
mkfs.btrfs   mkfs.ext2    mkfs.ext4    mkfs.minix   mkfs.vfat  

您还可以通过使用当前内核的模块目录列出内核能够与之交互的文件系统:

$ ls /lib/modules/3.10.0-862.2.3.el7.x86_64/kernel/fs
binfmt_misc.ko.xz  cifs    ext4     gfs2   mbcache.ko.xz  nls        udf
btrfs              cramfs  fat      isofs  nfs            overlayfs  xfs
cachefiles         dlm     fscache  jbd2   nfs_common     pstore
ceph               exofs   fuse     lockd  nfsd           squashfs

请注意,像ext3可能由你的ext4模块管理,所以仅因为ext3没有列出来并不意味着你不能挂载、读取和写入ext3驱动器。

它的工作原理...

实际上,它不起作用。

在完美的世界里,我们会有一个文件系统可以跨越每个操作系统,并适用于每项任务,但事实是总会有一些文件系统在某些任务上比其他文件系统更擅长。

在撰写本文时,Red Hat 默认使用 XFS 作为其首选文件系统,这是一个于 1993 年创建的日志文件系统。在那之前,默认使用的是ext系列的文件系统,一些发行版(如 Debian)仍在使用。

目前 OpenSUSE 喜欢btrfs,尽管这可能会改变,尤其是像 Red Hat 这样的公司刚刚决定不包括它在未来的版本中。

这还没有涉及到用户空间文件系统FUSE),它可以将一系列其他(用户空间)文件系统引入其中。

目前,只要你的需求不是太特别,XFS 和ext4可能会持续一段时间,并且它们对于任何系统来说都是一个可靠的选择。如果你打算进行一些调查,找出哪种文件系统最适合你的新的、特别的数据库,你可能需要找一个存储工程师。

我的建议是使用默认的文件系统。

即将推出的文件系统格式

我们已经讨论了像 XFS 和ext4这样的老牌文件系统,但我们只是涉及了像 ZFS 和btrfs这样的新颖文件系统。

除了常规的“我需要一个文件系统来管理我的磁盘”文件系统之外,还有其他一些值得一提的文件系统,比如 LizardFS 和 SSHFS。

准备工作

在这一部分,你需要再次访问你的虚拟机和整个互联网。

连接到你的虚拟机,同时准备一个网络浏览器。

如何做到...

从简单的开始,让我们谈谈 ZFS。

ZFS 不像其他传统的文件系统,最近被捆绑到 Ubuntu 中,这引起了很多争论,甚至有人声称 Canonical 故意违反了 GPL 合规性,这引起了一些骚动。

多年来,ZFS 是人们安装 FreeBSD 的主要原因(不要给我发邮件),它是基于 Solaris 或 OpenSolaris 的系统的骨干文件系统。它的工作方式与 XFS 或 ext4 不同,可以说更接近 LVM 世界。

磁盘被放置到 VDEVs 中,这是处理驱动器镜像或 RAID 的部分。然后这些 VDEVs 形成了 zpools 的基础,这些是数据集的存储池(数据集更像是传统的分区)。

ZFS on Linux 是将 ZFS 放在 Linux 上的项目,我建议你去了解一下。

在 ZFS 之后,你还有诸如分布式文件系统之类的东西,你可以使用 FUSE 在本地系统上挂载它们。LizardFS 就是这样的文件系统。

如果你想要分布式和冗余存储,可以使用 LizardFS,它可以通过网络访问并在本地挂载。你可以做一些聪明的事情,比如存储多个数据副本,甚至在控制节点上设置多主机设置,以便在硬件故障时具有一定的冗余性(因为硬件确实会出现故障)。

如果这一切让你感到困惑,不要惊慌:我也很长时间以来都感到困惑,但这是非常棒的软件,值得花一个周末来掌握。

SSHFS 是另一个 FUSE 文件系统,只是这次它是一个软件,用于将远程系统目录挂载到本地,这样你就可以随心所欲地操纵和处理它们,而不必在远程主机的命令行上操作。

它的工作原理...

再次强调,它通常不起作用。

文件系统是一个深奥而有趣的话题,适合非常专注和特定类型的人。我曾经遇到过存储工程师,他们可以讨论各种不同的文件系统以及为什么你现在不应该再使用ext2作为/boot分区,因为没有相应的权衡,或者他们会滔滔不绝地谈论使用JBOD部署与传统 SAN 之间的选择优势。

我再次指出,每种文件系统都有其用例,甚至包括 WikipediaFS,这都没问题!只要确保你知道有这些选择,如果你需要的话。

总结 - 硬件和磁盘

这一章大部分是无意中变成了对磁盘和文件系统的分析。这是因为传统上,磁盘是你系统中最容易出问题的东西。最近,磁盘不再像以前那样频繁损坏,因为廉价和商业可用的固态硬盘的出现已经从许多系统中移除了“旋转锈”。

也就是说,数据可能会随机地从你的生活中消失。

备份!备份!备份!

不管我说了多少次,你们中的一些人仍然会读到这些话,然后想:“是的,我应该这样做”,却没有打算去设置任何东西。对于你自己的系统,这是你的选择,但至少你可以考虑一下你管理的那些盒子,因为当你在灾难性故障之后拿出备份时,这将使你的生活更轻松(并让你成为英雄),这可能会给你的雇主造成数百万的损失。

不用说,但你应该经常测试你的备份,否则你将进入基于信仰的备份情况,那不是你想要的地方。

文件系统也变得越来越复杂和功能丰富。如果你比较ext2btrfs,你会发现许多设计旨在长期使你的生活更轻松的技巧和聪明的东西,但在短期内可能会让你面对一系列令人困惑的选择。

在这个世界上,人们也会做一些愚蠢的事情,比如在实时环境中部署 RAID5 btrfs解决方案,而没有进行基本的写孔检查,以确保如果一个磁盘坏掉,他们不会丢失任何数据(要了解这背后的完整故事,请在互联网上搜索“btrfs raid lol”,你可能会得到一些有意义的结果)。

总的来说,记住硬件会对你造成影响,当你在凌晨四点坐在数据中心时,拿着一个硬盘听点击声,因为机械头试图并且未能正确读取那个重要的工资数据库时,你可能会对这种知识感到一些黑暗的幽默。

第六章:安全、更新和软件包管理

本章将涵盖以下主题:

  • 检查软件包版本

  • 检查操作系统版本

  • 检查更新

  • 自动更新

  • 检查邮件列表和勘误页面

  • 使用 Snaps

  • 使用 Flatpak

  • 使用 Pip、RubyGems 和其他软件包管理器

  • 依赖地狱(简短说明)

  • 从源代码编译

  • 添加额外的软件源

介绍

您的系统在其生命周期中会有一次(也许两次)处于完美状态。

第一次完美、纯洁和未被玷污的时候是在安装时(假设您已经在安装过程中勾选了更新软件包的选项)。您的系统再也不会处于如此原始的状态,因为它没有被肮脏的人类手干预过。

第二次完美的时候是当它最后一次关闭时,工作完成得很好,并且值得去报废厂参观(或者在云计算的情况下,快速地去硅天堂)。

在本节中,您将了解软件包的不同来源,如何查找和安装新软件,以及保持系统安全和最新的重要性(以免最终成为 The Register 的头条新闻)。

这不是工作中最有趣的部分,你可能会发现自己多次撞头在最近的墙上,但如果你做对了,你会发现你需要处理的基础架构中由软件不匹配引起的问题会大大减少。

我遇到的最好的安装会定期重新构建它们的镜像,然后以一致且可测试的方式在基础架构中推出它们。这需要时间来完成,在这里,我们将看看开始的基本组件。

技术要求

本章将涉及不同的软件包管理器和执行相同操作的多种方法(这基本上概括了 Linux)。

因此,我们将在我们的Vagrantfile中使用三个不同的盒子,如下所示:

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

 config.vm.define "centos7" do |centos7|
   centos7.vm.box = "centos/7"
   centos7.vm.box_version = "1804.02"
 end

 config.vm.define "debian9" do |debian9|
   debian9.vm.box = "debian/stretch64"
   debian9.vm.box_version = "9.5.0"
 end

 config.vm.define "ubuntu1804" do |ubuntu1804|
   ubuntu1804.vm.box = "ubuntu/bionic64"
   ubuntu1804.vm.box_version = "20180927.0.0"
 end

end

启动这些盒子(使用vagrant up)将为您提供一个 CentOS 安装、一个 Debian 安装和一个 Ubuntu 安装:

我们将在某个时候使用每一个。

检查软件包版本

在本节中,我们将查看已安装在我们系统上的软件包,并获取这些软件包的版本号。

这通常是有用的,如果你听说了最新的漏洞,它预示着世界末日,你的老板大喊让你去修复它,修复它,修复它。

存在大量的漏洞;偶尔会有一些更大的漏洞渗透到主流媒体。这些可以说是最危险的,因为它们会让人们恐慌,如果有比处于糟糕境地更糟糕的事情,那就是当你周围的人都在失去理智时处于糟糕境地。

通常也应该保持系统最新,因为跨越多个版本的升级(当您最终需要升级时)比逐步改变事物更加麻烦。

准备工作

确保所有盒子都已启动,并尝试连接前两个(CentOS 和 Debian):

$ vagrant ssh centos7
$ vagrant ssh debian9

在您的 Debian 盒子中,确保安装aptitude,因为这是我们稍后在本节中将要使用的前端之一;虽然它在某些发行版上是默认安装的,但这个 Debian 安装不是其中之一:

$ sudo apt install aptitude

如何做...

每个操作系统的方法都类似,但我们将依次进行。

CentOS

CentOS(和 Red Hat)有两个软件包管理器,另一个即将加入到等式中。

从头开始,我们有RPM 软件包管理器RPM是一个递归缩写),它是 Red Hat 系统中软件包管理的基础。从某种意义上说,它是原始的软件包管理器,因为它是原始的,你可能不会每天直接使用它。

RPM 执行以下四个操作:

  • 选择

  • 查询

  • 验证

  • 安装

这些选项有参数,我经常使用的是查询。

要列出系统上所有已安装的软件包,请使用-qa,如下所示:

$ rpm -qa
kernel-tools-libs-3.10.0-862.2.3.el7.x86_64
grub2-common-2.02-0.65.el7.centos.2.noarch
dmidecode-3.0-5.el7.x86_64
grub2-pc-modules-2.02-0.65.el7.centos.2.noarch
firewalld-filesystem-0.4.4.4-14.el7.noarch
<SNIP>
gssproxy-0.7.0-17.el7.x86_64
dbus-glib-0.100-7.el7.x86_64
python-slip-dbus-0.4.0-4.el7.noarch
python-pyudev-0.15-9.el7.noarch
plymouth-scripts-0.8.9-0.31.20140113.el7.centos.x86_64

要列出特定软件包,可以按名称(不包括完整版本信息),如下所示:

$ rpm -q dmidecode
dmidecode-3.0-5.el7.x86_64

要获取有关软件包的信息,可以使用-i

$ rpm -qi dmidecode
Name        : dmidecode
Epoch       : 1
Version     : 3.0
Release     : 5.el7
Architecture: x86_64
Install Date: Sat 12 May 2018 18:52:07 UTC
Group       : System Environment/Base
Size        : 247119
License     : GPLv2+
Signature   : RSA/SHA256, Thu 10 Aug 2017 15:38:00 UTC, Key ID 24c6a8a7f4a80eb5
Source RPM  : dmidecode-3.0-5.el7.src.rpm
Build Date  : Thu 03 Aug 2017 23:53:58 UTC
Build Host  : c1bm.rdu2.centos.org
Relocations : (not relocatable)
Packager    : CentOS BuildSystem <http://bugs.centos.org>
Vendor      : CentOS
URL         : http://www.nongnu.org/dmidecode/
Summary     : Tool to analyse BIOS DMI data
Description :
dmidecode reports information about x86 & ia64 hardware as described in the
system BIOS according to the SMBIOS/DMI standard. This information
typically includes system manufacturer, model name, serial number,
BIOS version, asset tag as well as a lot of other details of varying
level of interest and reliability depending on the manufacturer.

This will often include usage status for the CPU sockets, expansion
slots (e.g. AGP, PCI, ISA) and memory module slots, and the list of
I/O ports (e.g. serial, parallel, USB).

我发现有用的一个技巧是以伪 YAML 格式输出特定信息。这对于记录软件包的版本非常方便,可以通过--queryformat选项实现:

$ rpm -q --queryformat "---\nName: %{NAME}\n  Version: %{VERSION}\n  Release: %{RELEASE}\n" dmidecode
---
Name: dmidecode
 Version: 3.0
 Release: 5.el7

我开玩笑说 RPM 已经过时了,但它在许多方面都表现出色,而且在许多情况下,使用rpm命令运行软件包查询要比任何可用的前端快得多,这意味着它非常适合脚本。只是要注意,同时使用 RPM 和 YUM(安装东西)可能会导致问题。

如果你想使用一些更近期的东西,那么对 RPM 的良好前端的当前版本称为Yellowdog Updater ModifiedYUM),最初是为 Yellow Dog Linux 开发的。

YUM 通常被使用,因为它处理依赖关系解析(自动下载和安装依赖软件包)以及从配置的远程存储库安装。

那些在 2000 年代中期拥有 Playstation 3 的人可能会感兴趣知道,Yellow Dog 是针对这些游戏机开发的,当时索尼允许在自己的 Orbis OS(基于 FreeBSD)旁边安装第三方操作系统的时间很短。

要列出所有已安装的软件包,请使用list installed

$ yum list installed
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirror.vorboss.net
 * extras: mirror.econdc.com
 * updates: mirror.cwcs.co.uk
Installed Packages
GeoIP.x86_64                                                                      1.5.0-11.el7                                                                  @anaconda 
NetworkManager.x86_64                                                             1:1.10.2-13.el7                                                               @anaconda 
NetworkManager-libnm.x86_64                                                       1:1.10.2-13.el7                                                               @anaconda 
NetworkManager-team.x86_64                                                        1:1.10.2-13.el7                                                               @anaconda 
NetworkManager-tui.x86_64                                                         1:1.10.2-13.el7                                                               @anaconda 
<SNIP> 
yum-plugin-fastestmirror.noarch                                                   1.1.31-45.el7                                                                 @anaconda 
yum-utils.noarch                                                                  1.1.31-45.el7                                                                 @anaconda 
zlib.x86_64                                                                       1.2.7-17.el7                                                                  @anaconda 

您还可以像我们使用 RPM 一样使用yum来查询单个信息,如下所示:

$ yum info zlib
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirror.vorboss.net
 * extras: mirror.econdc.com
 * updates: mirror.cwcs.co.uk
Installed Packages
Name        : zlib
Arch        : x86_64
Version     : 1.2.7
Release     : 17.el7
Size        : 181 k
Repo        : installed
From repo   : anaconda
Summary     : The compression and decompression library
URL         : http://www.zlib.net/
Licence     : zlib and Boost
Description : Zlib is a general-purpose, patent-free, lossless data compression
 : library which is used by many different programs.

Available Packages
Name        : zlib
Arch        : i686
Version     : 1.2.7
Release     : 17.el7
Size        : 91 k
Repo        : base/7/x86_64
Summary     : The compression and decompression library
URL         : http://www.zlib.net/
Licence     : zlib and Boost
Description : Zlib is a general-purpose, patent-free, lossless data compression
 : library which is used by many different programs.

请注意,默认情况下我们还会得到可用的软件包,你们中眼尖的人会注意到这两个软件包之间唯一的区别是可用的版本是 32 位的。

DNF(这并不代表未完成)是最新的前端软件包管理器,负责统一 Red Hat 安装。它已经成为 Fedora(一个很好的发行版,也是 Red Hat 的测试场)的默认选项一段时间了,这意味着它很可能会进入下一个版本的 CentOS 和 Red Hat 本身。在大多数情况下,它是一个可替换的工具,还有一些新功能来证明它的存在。

Debian

在底层,Debian 使用dpkg软件包管理器来安装和管理软件包。还有各种可用的前端,比如aptaptitude,使管理更加用户友好。

从基础开始,您可以使用dpkg-query来查询系统上安装的软件包:

$ dpkg-query -W
adduser    3.115
apt    1.4.8
apt-listchanges    3.10
apt-utils    1.4.8
base-files    9.9+deb9u5
base-passwd    3.5.43
bash    4.4-5
<SNIP>
xauth    1:1.0.9-1+b2
xdg-user-dirs    0.15-2+b1
xkb-data    2.19-1+deb9u1
xml-core    0.17
xxd    2:8.0.0197-4+deb9u1
xz-utils    5.2.2-1.2+b1
zlib1g:amd64    1:1.2.8.dfsg-5

你肯定会注意到,默认情况下,软件包和版本之间用制表符分隔。我个人认为这很丑陋(因为两个空格是更好的选择),但幸运的是,我们可以使用showformat来自定义输出:

$ dpkg-query -W --showformat='${Package} - ${Version}\n'
adduser - 3.115
apt - 1.4.8
apt-listchanges - 3.10
apt-utils - 1.4.8
base-files - 9.9+deb9u5
base-passwd - 3.5.43
<SNIP>
xml-core - 0.17
xxd - 2:8.0.0197-4+deb9u1
xz-utils - 5.2.2-1.2+b1
zlib1g - 1:1.2.8.dfsg-5

这对于脚本来说特别方便!

除了dpkg-query,我们还有apt

$ apt list --installed
Listing... Done
adduser/stable,now 3.115 all [installed]
apt/stable,now 1.4.8 amd64 [installed]
apt-listchanges/stable,now 3.10 all [installed]
apt-utils/stable,now 1.4.8 amd64 [installed]
<SNIP>
xdg-user-dirs/stable,now 0.15-2+b1 amd64 [installed,automatic]
xkb-data/stable,now 2.19-1+deb9u1 all [installed,automatic]
xml-core/stable,now 0.17 all [installed,automatic]
xxd/stable,now 2:8.0.0197-4+deb9u1 amd64 [installed]
xz-utils/stable,now 5.2.2-1.2+b1 amd64 [installed]
zlib1g/stable,now 1:1.2.8.dfsg-5 amd64 [installed]

这个默认输出可能更适合你。

apt是与系统上的软件包交互的新方法,尽管你们中的传统主义者(或者从传统主义者那里学到的人)可能更熟悉apt-getapt-cache工具套件。

最后,在本节中,还有aptitude

Aptitude 是我记得使用的第一个软件包管理器,我还记得它很难用,因为有时它会让我进入 TUI(文本或基于文本的用户界面),我不知道发生了什么。

可以在命令行上使用aptitude,如下所示:

$ aptitude search  ~i --display-format '%p%v'
adduser                                                                  3.115 
apt                                                                      1.4.8 
apt-listchanges                                                          3.10 
apt-utils                                                                1.4.8 
aptitude                                                                 0.8.7-1 
aptitude-common                                                          0.8.7-1 
base-files                                                               9.9+deb9u5 
base-passwd                                                              3.5.43 
<SNIP>
xdg-user-dirs                                                            0.15-2+b1 
xkb-data                                                                 2.19-1+deb9u1 
xml-core                                                                 0.17 
xxd                                                                      2:8.0.0197-4+d
xz-utils                                                                 5.2.2-1.2+b1 
zlib1g                                                                   1:1.2.8.dfsg-5

也可以单独输入aptitude,然后进入 TUI:

可以使用键盘上的箭头键或鼠标来导航此界面。

然而,我们可以立即看到列出的安全更新已安装的软件包,这构成了我们在命令行上得到的369个软件包。

$ aptitude search  ~i --display-format '%p%v'  | wc -l
369

我们可以双击并深入到aptitude界面。

在下面的屏幕截图中,我展示了我们的 VM 中安装的两个内核(4.9.0-64.9.0-7):

您可能还注意到linux-image-amd64,这是一个元包,而不是一个独立的包。

我们也可以在命令行上查找这些内核:

$ aptitude search  '~i linux-image' --display-format '%p%v' 
linux-image-4.9.0-6-amd64                                                                                                                                         4.9.88-1+deb9u
linux-image-4.9.0-7-amd64                                                                                                                                         4.9.110-1 
linux-image-amd64                                                                                                                                                 4.9+80+deb9u5 

它是如何工作的...

您实际上正在做的事情(在这两种情况下)是查询系统上的软件包数据库。

在您的 CentOS 系统上,RPM 和 YUM 都在/var/lib/rpm中查看,以确定系统的状态。

同样,在您的 Debian 系统上,您的软件包状态保存在/var/lib/dpkg中。

最好不要在用于管理它们的应用程序之外搞乱这些文件夹,因为修改系统上安装的软件包的性质(在软件包管理器之外)可能会导致奇怪的,有时是破坏性的行为。

还有更多...

请记住,您不必使用系统的软件包管理器来列出版本;如果您更愿意相信应用程序本身的输出,大多数应用程序都有某种形式的-v--version标准。

例如,bash如下所示:

$ bash --version
GNU bash, version 4.2.46(2)-release (x86_64-redhat-linux-gnu)
Copyright (C) 2011 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>

This is free software; you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.

以下显示了ssh的代码,其中使用了-V(大写):

$ ssh -V
OpenSSH_7.4p1, OpenSSL 1.0.2k-fips  26 Jan 2017

而且,为了使人感到困扰,Vagrant 使用了-v(小写):

$ vagrant -v
Vagrant 2.0.2

您可能已经注意到在前面的示例中缺少 Ubuntu;这是因为在 Debian 系统上运行的任何东西极有可能在 Ubuntu 系统上运行。

检查操作系统版本

我们将使用在上一节中使用的相同的Vagrantfile

在本节中,我们将列出我们操作系统的规范版本,以及内核版本。

我们还将研究 LSB 兼容性的概念。

如何做...

我们将把这一部分分成不同的操作系统。

CentOS

我们可以通过打印centos-release文件的内容来确定我们的CentOS安装版本,如下所示:

$ cat /etc/centos-release
CentOS Linux release 7.5.1804 (Core)

有趣的是(在某种类型的人中间):如果您在您的盒子上cat redhat-release的内容,您将获得相同的信息,因为CentOS和 Red Hat 系统是如此紧密地对齐:

$ cat /etc/redhat-release 
CentOS Linux release 7.5.1804 (Core)

cat(源自 concatenate)是一个历史上用于将多个文件的内容打印到标准输出的程序。

同样,system-release是指向centos-release的符号链接:

$ cat /etc/system-release
CentOS Linux release 7.5.1804 (Core)

如果您想要更详细的信息,甚至可以打印os-release文件的内容:

$ cat /etc/os-release 
NAME="CentOS Linux"
VERSION="7 (Core)"
ID="centos"
ID_LIKE="rhel fedora"
VERSION_ID="7"
PRETTY_NAME="CentOS Linux 7 (Core)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:centos:centos:7"
HOME_URL="https://www.centos.org/"
BUG_REPORT_URL="https://bugs.centos.org/"

CENTOS_MANTISBT_PROJECT="CentOS-7"
CENTOS_MANTISBT_PROJECT_VERSION="7"
REDHAT_SUPPORT_PRODUCT="centos"
REDHAT_SUPPORT_PRODUCT_VERSION="7"

这些命令告诉您操作系统的版本;它们没有提供给您的是内核版本,这是分开的(回想一下第一章,介绍和环境设置)。

要确定内核版本,可以查询dmesg,如下所示:

$ dmesg | grep "Linux version"
[    0.000000] Linux version 3.10.0-862.2.3.el7.x86_64 (builder@kbuilder.dev.centos.org) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-28) (GCC) ) #1 SMP Wed May 9 18:05:47 UTC 2018

或者,对于不依赖日志文件的命令,您可以运行带有-auname,以打印有关系统的所有信息:

$ uname -a
Linux localhost.localdomain 3.10.0-862.2.3.el7.x86_64 #1 SMP Wed May 9 18:05:47 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

仅获取内核版本信息,请使用-r,如下所示:

$ uname -r
3.10.0-862.2.3.el7.x86_64

uname绝对不是一个特定于 Linux 的命令;它将在大多数 Unix 和类 Unix 的衍生产品上运行。看看它在您的 FreeBSD 或 OpenBSD 系统上(或者如果您不那么悲伤的话,在您的 macOS 盒子上)打印出什么。

您还可以使用 YUM,如前所述:

$ yum -q info installed kernel 
Installed Packages
Name        : kernel
Arch        : x86_64
Version     : 3.10.0
Release     : 862.2.3.el7
Size        : 62 M
Repo        : installed
From repo   : koji-override-1
Summary     : The Linux kernel
URL         : http://www.kernel.org/
Licence     : GPLv2
Description : The kernel package contains the Linux kernel (vmlinuz), the core of any
 : Linux operating system.  The kernel handles the basic functions
 : of the operating system: memory allocation, process allocation, device
 : input and output, etc.

如果您是一个真正的叛逆者,您甚至可以查看您在/boot中安装了哪些内核:

$ ls -l /boot
total 25980
-rw-r--r--. 1 root root   147823 May  9 18:19 config-3.10.0-862.2.3.el7.x86_64
drwxr-xr-x. 3 root root       17 May 12 18:50 efi
drwxr-xr-x. 2 root root       27 May 12 18:51 grub
drwx------. 5 root root       97 May 12 18:54 grub2
-rw-------. 1 root root 16506787 May 12 18:55 initramfs-3.10.0-862.2.3.el7.x86_64.img
-rw-r--r--. 1 root root   304926 May  9 18:21 symvers-3.10.0-862.2.3.el7.x86_64.gz
-rw-------. 1 root root  3409102 May  9 18:19 System.map-3.10.0-862.2.3.el7.x86_64
-rwxr-xr-x. 1 root root 6225056 May 9 18:19 vmlinuz-3.10.0-862.2.3.el7.x86_64

最新版本(在前面的代码中加粗)很有可能是您正在运行的版本,尽管这并不总是正确的。

Debian

在 Debian 世界中情况大致相同,尽管要担心的 OS 版本文件较少。

在 Debian 中,我们可以查看/etc/debian_version的内容,以获取我们正在运行的版本:

$ cat /etc/debian_version 
9.5

或者,我们可以像在CentOS中一样查看/etc/os-release

$ cat /etc/os-release 
PRETTY_NAME="Debian GNU/Linux 9 (stretch)"
NAME="Debian GNU/Linux"
VERSION_ID="9"
VERSION="9 (stretch)"
ID=debian
HOME_URL="https://www.debian.org/"
SUPPORT_URL="https://www.debian.org/support"
BUG_REPORT_URL="https://bugs.debian.org/"

就像在CentOS中一样,我们可以通过grep dmesg日志来获取我们内核的版本:

$ sudo dmesg | grep "Linux version"
[    0.000000] Linux version 4.9.0-7-amd64 (debian-kernel@lists.debian.org) (gcc version 6.3.0 20170516 (Debian 6.3.0-18+deb9u1) ) #1 SMP Debian 4.9.110-1 (2018-07-05)

或者,我们可以使用uname,如下所示:

$ uname -r
4.9.0-7-amd64

是的,Debian 在撰写本书时有一个更加最新的内核版本;这是CentOS将修复和功能后移植到他们的旧内核中(从上游获取改进并将其应用到旧版本中),以及 Debian 发行版的发布周期要短得多的混合体。

您可以使用之前列出的任何方法列出已安装的版本;以下是dpkg-query的示例:

$ dpkg-query -W  linux-image*
linux-image-4.9.0-6-amd64    4.9.88-1+deb9u1
linux-image-4.9.0-7-amd64    4.9.110-1
linux-image-amd64    4.9+80+deb9u5

还有老式的/boot,如下所示:

$ ls -l /boot
total 50264
-rw-r--r-- 1 root root   186567 May  7 22:38 config-4.9.0-6-amd64
-rw-r--r-- 1 root root   186568 Jul  5 01:29 config-4.9.0-7-amd64
drwxr-xr-x 5 root root     4096 Jul 17 01:50 grub
-rw-r--r-- 1 root root 18117609 Jul 17 01:48 initrd.img-4.9.0-6-amd64
-rw-r--r-- 1 root root 18125878 Jul 17 01:50 initrd.img-4.9.0-7-amd64
-rw-r--r-- 1 root root  3190138 May  7 22:38 System.map-4.9.0-6-amd64
-rw-r--r-- 1 root root  3192069 Jul  5 01:29 System.map-4.9.0-7-amd64
-rw-r--r-- 1 root root  4224800 May  7 22:38 vmlinuz-4.9.0-6-amd64
-rw-r--r-- 1 root root  4224800 Jul  5 01:29 vmlinuz-4.9.0-7-amd64

Ubuntu

像所有好的发行版一样,Ubuntu 也允许您cat一个文件以获取信息;但是,与其他一些发行版不同的是,它还会在您登录时告诉您(默认情况下)。

通过 SSH 连接到我们的 Ubuntu 主机应该打印出类似以下的内容:

$ vagrant ssh ubuntu1804
Welcome to Ubuntu 18.04.1 LTS (GNU/Linux 4.15.0-34-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Sun Sep 30 14:55:26 UTC 2018

  System load:  0.0              Processes:             95
  Usage of /:   9.8% of 9.63GB   Users logged in:       0
  Memory usage: 12%              IP address for enp0s3: 10.0.2.15
  Swap usage:   0%

 * Read about Ubuntu updates for L1 Terminal Fault Vulnerabilities (L1TF).
   - https://ubu.one/L1TF

 * Having fun with some surprising Linux desktop apps... Alan keeps
   the family entertained over the summer/winter holidays.
   - https://bit.ly/top_10_entertainment_apps

 * Want to make a highly secure kiosk, smart display or touchscreen?
   Here's a step-by-step tutorial for a rainy weekend, or a startup.
   - https://bit.ly/secure-kiosk

  Get cloud support with Ubuntu Advantage Cloud Guest:
    http://www.ubuntu.com/business/services/cloud

0 packages can be updated.
0 updates are security updates.

Last login: Sun Sep 30 14:15:35 2018 from 10.0.2.2

注意加粗的行,它告诉您在登录时正在运行的 Ubuntu 版本。

这个每日消息MOTD)实际上是由几个文件构建的;头部是00-header

$ cat /etc/update-motd.d/00-header

在这个文件中有一些行,如下所示:

[ -r /etc/lsb-release ] && . /etc/lsb-release

if [ -z "$DISTRIB_DESCRIPTION" ] && [ -x /usr/bin/lsb_release ]; then
    # Fall back to using the very slow lsb_release utility
    DISTRIB_DESCRIPTION=$(lsb_release -s -d)
fi

printf "Welcome to %s (%s %s %s)\n" "$DISTRIB_DESCRIPTION" "$(uname -o)" "$(uname -r)" "$(uname -m)"

在这里,我们可以检查lsb-release文件是否存在(并且可读),然后再使用该文件(. /etc/lsb-release)来确定版本。

然后,我们有一个if语句,它说如果DISTRIB_DESCRIPTION变量为空,并且lsb_release二进制文件可执行,我们将回退到使用该实用程序来确定发行版本(lsb_release -s -d)。

然后我们打印输出,这就是我们在登录消息顶部看到的内容。

如果 MOTD 失败,我们可以自己cat /etc/lsb-release,使用以下命令:

$ cat /etc/lsb-release 
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=18.04
DISTRIB_CODENAME=bionic
DISTRIB_DESCRIPTION="Ubuntu 18.04.1 LTS"

或者,我们可以再次使用os-release,如下所示:

$ cat /etc/os-release 
NAME="Ubuntu"
VERSION="18.04.1 LTS (Bionic Beaver)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 18.04.1 LTS"
VERSION_ID="18.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=bionic
UBUNTU_CODENAME=bionic

对于内核,与之前的操作基本相同;检查uname,如下所示:

$ uname -r
4.15.0-34-generic

检查已安装的版本,如下所示:

$ dpkg-query -W  linux-image*
linux-image 
linux-image-4.15.0-34-generic    4.15.0-34.37
linux-image-unsigned-4.15.0-34-generic 
linux-image-virtual    4.15.0.34.36

或者,看看/boot,如下所示:

$ ls -l /boot
total 32720
-rw------- 1 root root  4044038 Aug 27 14:45 System.map-4.15.0-34-generic
-rw-r--r-- 1 root root  1537610 Aug 27 14:45 abi-4.15.0-34-generic
-rw-r--r-- 1 root root   216905 Aug 27 14:45 config-4.15.0-34-generic
drwxr-xr-x 5 root root     4096 Sep 21 12:13 grub
-rw-r--r-- 1 root root 19423451 Sep 21 12:00 initrd.img-4.15.0-34-generic
-rw-r--r-- 1 root root        0 Aug 27 14:45 retpoline-4.15.0-34-generic
-rw------- 1 root root  8269560 Aug 27 15:06 vmlinuz-4.15.0-34-generic

vmlinuz对象,如前所述,是 Linux 内核的压缩可执行文件。

它是如何工作的...

当你查询这些文件时,你在询问操作系统它认为自己是什么版本。

这对于从安全到编写脚本都很有用。您不仅想知道您正在运行的操作系统版本是否不安全,还可能想在任何脚本的顶部添加一个健全性检查,以确保它们只在为其设计的系统上运行,也就是说,您可以为 CentOS 系统编写一个脚本,第一步可以是“检查我们实际上是在 CentOS 系统上执行”。

uname(Unix 名称)更有趣,因为我们实际上不是在查询操作系统版本的文件,而是在查询正在运行的内核的信息。

uname使用uname系统调用(搞糊涂了吗?),它不仅符合 POSIX 标准,而且根源可以追溯到上世纪 70 年代和 PWB/Unix。

还有更多...

您可能已经注意到 Ubuntu 使用lsb_release来获取其操作系统版本;在CentOS上也可以这样做,但首先需要安装lsb_release

$ sudo yum install redhat-lsb-core

现在,我们可以运行 Ubuntu 使用的相同命令以获取操作系统信息:

$ lsb_release -s -d
"CentOS Linux release 7.5.1804 (Core) "

Debian上也可以这样做,而无需默认安装任何内容:

$ lsb_release -s -d
Debian GNU/Linux 9.5 (stretch)

Linux 标准基础LSB)基本上是多个发行版签署的标准。它指定了文件系统层次结构标准FHS),以及 Linux 系统的各种其他组件。

LSB 还建议使用 RPM 的软件包格式,尽管 Debian 和 Ubuntu 显然不会默认使用这个格式,而是选择.deb。为了解决这个问题,Debian 提供了alien软件包,用于在安装之前将.rpm文件转换为.deb文件。这有点像一个肮脏的黑客,它并不能保证合规性;它更像是一种礼貌的表示。

另请参阅...

看看旧的 Unix 程序和约定,您会惊讶地发现其中有多少已经存活到现代。

GNU 不是 Unix,那么为什么 Linux 系统也有uname?答案是,因为它类似于 Unix,并且 Unix 开创的许多命令和约定被 GNU 操作系统和自由软件运动重新编写,以方便熟悉。

检查更新

在本节中,我们将使用第一节的一部分中使用的Vagrantfile。现在我们知道与我们的系统相关的软件版本(软件包、操作系统和内核),我们将查看我们可以使用的更新以及如何安装它们。

我们将检查特定软件包的更新以及所有软件包的更新。

如何做到...

在本节中,我们将进入我们的CentOSDebian框,跳过 Ubuntu,因为 Debian 的相同规则也适用。

我们将在这些示例中使用内核,尽管您的系统上的任何软件包都可以替换。

CentOS

CentOS中,检查软件包更新的最简单方法是使用 YUM,如下所示:

$ yum -q info kernel
Installed Packages
Name : kernel
Arch : x86_64
Version : 3.10.0
Release : 862.2.3.el7
Size : 62 M
Repo : installed
From repo : koji-override-1
Summary : The Linux kernel
URL : http://www.kernel.org/
Licence : GPLv2
Description : The kernel package contains the Linux kernel (vmlinuz), the core of any
 : Linux operating system. The kernel handles the basic functions
 : of the operating system: memory allocation, process allocation, device
 : input and output, etc.

Available Packages
Name : kernel
Arch : x86_64
Version : 3.10.0
Release : 862.14.4.el7
Size : 46 M
Repo : updates/7/x86_64
Summary : The Linux kernel
URL : http://www.kernel.org/
Licence : GPLv2
Description : The kernel package contains the Linux kernel (vmlinuz), the core of any
 : Linux operating system. The kernel handles the basic functions
 : of the operating system: memory allocation, process allocation, device
 : input and output, etc.

请注意,我们不限制输出到已安装的软件包;相反,我们正在检查我们安装了什么以及可用的软件包。

输出告诉我们,虽然版本号没有更改,但内核的发布已经更新,并且可以从updates/7/x86_64仓库中获取。

要更新我们的内核,我们只需运行yum upgrade命令,如下所示:

$ sudo yum upgrade -y kernel

我们之前提到运行 YUM 命令时会列出将更改的软件包列表。使用“-y”我们会自动接受这些更改,但如果您不确定,最好省略“-y”标志,并通过阅读呈现的列表手动进行健全性检查。

因此,特定软件包非常简单,但是如何检查系统上安装的所有软件包呢?

当然是用 YUM!

我们可以使用updateupgrade,在现代安装中基本上是相同的:

$ sudo yum upgrade 

使用upgrade(而不是update)在技术上应该是不同的,因为它还使用逻辑来使过时的程序过时并替换,但因为这是大多数人所期望的行为,obsoletes=1也设置在yum.conf中,使updateupgrade在默认情况下具有相同的功能。

我们之前的命令应该会显示如下屏幕:

请注意,如果没有在命令中添加标志,更新将在此处停止,并提示您选择y/d/N(其中N是默认值)。

如果您准备好升级,通过此命令传递y将更新并安装前面的软件包。

如果您还没有准备好升级,传递d将只下载软件包。

正如我们之前所说,通常,需要重新启动以更新的唯一程序是内核和systemdinit系统),因为它们是您安装的灵魂,您基本上是在杀死旧程序以为新程序腾出空间(在大多数系统上,升级后会默认选择新程序)。

运行我们的yum info命令现在将显示两个已安装的内核,没有可用的内核,如下所示:

$ yum -q info kernel
Installed Packages
Name        : kernel
Arch        : x86_64
Version     : 3.10.0
Release     : 862.2.3.el7
Size        : 62 M
Repo        : installed
From repo   : koji-override-1
Summary     : The Linux kernel
URL         : http://www.kernel.org/
Licence     : GPLv2
Description : The kernel package contains the Linux kernel (vmlinuz), the core of any
 : Linux operating system.  The kernel handles the basic functions
 : of the operating system: memory allocation, process allocation, device
 : input and output, etc.

Name        : kernel
Arch        : x86_64
Version     : 3.10.0
Release     : 862.14.4.el7
Size        : 62 M
Repo        : installed
From repo   : updates
Summary     : The Linux kernel
URL         : http://www.kernel.org/
Licence     : GPLv2
Description : The kernel package contains the Linux kernel (vmlinuz), the core of any
 : Linux operating system.  The kernel handles the basic functions
 : of the operating system: memory allocation, process allocation, device
 : input and output, etc.

Debian

在 Debian 上,我们将使用apt,这是最新的,也是我认为最友好的工具。

与 YUM 不同,我们可以轻松独立地更新可用软件包的列表:

$ sudo apt update
Ign:1 http://deb.debian.org/debian stretch InRelease
Hit:2 http://deb.debian.org/debian stretch Release
Hit:4 http://security.debian.org/debian-security stretch/updates InRelease
Reading package lists... Done
Building dependency tree 
Reading state information... Done
15 packages can be upgraded. Run 'apt list --upgradable' to see them.

请注意,它只更新其列表,而不是程序本身。

现在,我们可以使用建议的命令查找特定信息:

$ apt list --upgradable linux-image*
Listing... Done
linux-image-4.9.0-7-amd64/stable 4.9.110-3+deb9u2 amd64 [upgradable from: 4.9.110-1]
linux-image-amd64/stable 4.9+80+deb9u6 amd64 [upgradable from: 4.9+80+deb9u5]

在末尾不添加regex-matched软件包的情况下,此命令将列出所有可升级的软件包:

$ apt list --upgradable 
Listing... Done
libcurl3-gnutls/stable 7.52.1-5+deb9u7 amd64 [upgradable from: 7.52.1-5+deb9u6]
libfuse2/stable 2.9.7-1+deb9u1 amd64 [upgradable from: 2.9.7-1]
libpython2.7-minimal/stable 2.7.13-2+deb9u3 amd64 [upgradable from: 2.7.13-2+deb9u2]
libpython2.7-stdlib/stable 2.7.13-2+deb9u3 amd64 [upgradable from: 2.7.13-2+deb9u2]
libpython3.5-minimal/stable 3.5.3-1+deb9u1 amd64 [upgradable from: 3.5.3-1]
libpython3.5-stdlib/stable 3.5.3-1+deb9u1 amd64 [upgradable from: 3.5.3-1]
linux-image-4.9.0-7-amd64/stable 4.9.110-3+deb9u2 amd64 [upgradable from: 4.9.110-1]
linux-image-amd64/stable 4.9+80+deb9u6 amd64 [upgradable from: 4.9+80+deb9u5]
openssh-client/stable 1:7.4p1-10+deb9u4 amd64 [upgradable from: 1:7.4p1-10+deb9u3]
openssh-server/stable 1:7.4p1-10+deb9u4 amd64 [upgradable from: 1:7.4p1-10+deb9u3]
openssh-sftp-server/stable 1:7.4p1-10+deb9u4 amd64 [upgradable from: 1:7.4p1-10+deb9u3]
python2.7/stable 2.7.13-2+deb9u3 amd64 [upgradable from: 2.7.13-2+deb9u2]
python2.7-minimal/stable 2.7.13-2+deb9u3 amd64 [upgradable from: 2.7.13-2+deb9u2]
python3.5/stable 3.5.3-1+deb9u1 amd64 [upgradable from: 3.5.3-1]
python3.5-minimal/stable 3.5.3-1+deb9u1 amd64 [upgradable from: 3.5.3-1]

与 YUM 一样,我们可以使用apt来升级单个软件包:

$ sudo apt install linux-image-amd64
Reading package lists... Done
Building dependency tree 
Reading state information... Done
The following additional packages will be installed:
 linux-image-4.9.0-8-amd64
Suggested packages:
 linux-doc-4.9 debian-kernel-handbook
The following NEW packages will be installed:
 linux-image-4.9.0-8-amd64
The following packages will be upgraded:
 linux-image-amd64
1 upgraded, 1 newly installed, 0 to remove and 14 not upgraded.
Need to get 39.1 MB of archives.
After this operation, 193 MB of additional disk space will be used.
Do you want to continue? [Y/n] 

请注意,我们特别使用install选项而不是upgrade,因为upgrade会尝试执行所有软件包,而不仅仅是linux-image-amd64

如果我们想要升级所有内容,我们将使用upgradefull-upgrade

$ sudo apt full-upgrade
Reading package lists... Done
Building dependency tree 
Reading state information... Done
Calculating upgrade... Done
The following NEW packages will be installed:
 linux-image-4.9.0-8-amd64
The following packages will be upgraded:
 libcurl3-gnutls libfuse2 libpython2.7-minimal libpython2.7-stdlib libpython3.5-minimal libpython3.5-stdlib linux-image-4.9.0-7-amd64 linux-image-amd64 openssh-client
 openssh-server openssh-sftp-server python2.7 python2.7-minimal python3.5 python3.5-minimal
15 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 88.3 MB of archives.
After this operation, 193 MB of additional disk space will be used.
Do you want to continue? [Y/n] 

我使用full-upgrade的原因是,仅使用upgrade可能导致软件包不会被升级(如果该升级需要删除另一个软件包)。

可能会有时候upgradefull-upgrade更可取,因此我建议在确认是否要执行upgrade命令之前检查其输出。

工作原理...

当您运行前面的软件包管理器命令时,您所做的是查询它们配置为连接的上游服务器,并询问是否有安装软件的更新版本可用。

配置的存储库位于CentOS/etc/yum.repos.d//etc/apt/sources.list.d(或sources.list.conf)中。

如果您的软件有更新版本可用,您可以选择安装或下载以备后用。通常,确保所有软件保持最新是个好主意,但对于公共服务(如 Web 服务器和 SSH 守护程序)来说尤其如此。

还有更多...

一些受欢迎且棘手的软件存在于感知范围之外(就这位有主见的作者而言)。

特别值得注意的是 Hashicorp 工具套件,当调用时会检查自身是否有新版本可用。这意味着当您运行Terraform时,有可能会通知您它已过时,并建议您下载新版本:

$ terraform --version
Terraform v0.11.7

Your version of Terraform is out of date! The latest version
is 0.11.8\. You can update by downloading from www.terraform.io/downloads.html

发行版的软件包维护者通常不会及时跟进,这并非他们的错,而且很多人甚至都不会费心打包此类软件。这意味着经常人们会添加 Terraform、Packer 和其他酷炫软件到他们的软件包管理器之外,使您需要跟踪的软件包管理系统数量翻倍(一个是您系统的,另一个是您自己的)。

自动更新

在本节中,我们将使用第一节的一部分中使用的Vagrantfile

“自动更新”是一个棘手的话题,对于许多系统管理员来说,因为在历史上,更新通常会使系统变砖。

这在如今越来越不太可能发生,而且您可以在不担心的情况下自动更新您的框(尽管我个人在生产环境中不会这样做)。

我们还将讨论以编程方式重建系统。

如何做...

在本节中依次进入每个框中。

重要的是要注意,您可能根本不想自动安装更新,特别是如果您处于定期销毁和重建机器的环境中。

可能还有内部程序会限制您的操作,这意味着无论您在技术上能够做什么,官僚主义总是会成为阻碍。

CentOS

CentOS系统上,我们有一个方便的工具叫做yum-cron

$ sudo yum install yum-cron -y

它带有两个配置文件,位于/etc/yum/中。

默认情况下,将使用/etc/yum/yum-cron.conf文件,并且其中有一个我们将要禁用的随机休眠:

$ sudo sed -i "s/random_sleep = 360/random_sleep = 0/g" /etc/yum/yum-cron.conf

现在,这意味着当调用yum-cron时,它将自动运行,应用yum-cron.conf的默认设置:

$ sudo yum-cron 
$ 

如果没有更新,yum-cron将不显示任何输出(如之前所见)。

如果有更新,默认情况下,您将收到已成功下载的通知:

如果要自动应用更新,那将涉及另一个配置文件更改,如下所示:

$ sudo sed -i "s/apply_updates = no/apply_updates = yes/g" /etc/yum/yum-cron.conf

再次运行yum-cron将应用已下载的更新:

我们之前已经提到过(但值得再次提到)这并不一定意味着服务将立即修复或将具有新功能。

这就是needs-restarting命令发挥作用的地方。

您还可以使用定时器(或cron,如果必须)运行此命令,以列出在更新后需要重新启动的进程,或者它们使用的组件:

$ sudo needs-restarting 
2617 : /usr/lib/systemd/systemd-udevd 
1082 : qmgr -l -t unix -u 
603 : /usr/sbin/crond -n 
609 : /sbin/agetty --noclear tty1 linux 
574 : /usr/sbin/chronyd 
397 : /usr/lib/systemd/systemd-journald 
851 : /usr/sbin/sshd -D -u0 
1070 : /usr/libexec/postfix/master -w 
1 : /usr/lib/systemd/systemd --system --deserialize 21 
2155 : sshd: vagrant [priv] 
<SNIP>

如果您希望获得更好的输出,可以指定服务,如下所示:

$ sudo needs-restarting -s
rpcbind.service
chronyd.service
systemd-logind.service
NetworkManager.service
postfix.service
dbus.service
getty@tty1.service
crond.service
lvm2-lvmetad.service
sshd.service
gssproxy.service
systemd-udevd.service
systemd-journald.service
polkit.service

或者只有在需要重新启动时,使用以下命令:

$ sudo needs-restarting -r
Core libraries or services have been updated:
 kernel -> 3.10.0-862.14.4.el7
 systemd -> 219-57.el7_5.3
 linux-firmware -> 20180220-62.2.git6d51311.el7_5

Reboot is required to ensure that your system benefits from these updates.

More information:
https://access.redhat.com/solutions/27943

启动yum-cron的一个非常简单的方法如下:

$ sudo systemctl enable --now yum-cron

Debian

在 Debian(和 Ubuntu)世界中,我们使用一个名为unattended-upgrades的软件包。它已经存在了相当长的时间,通常是人们自动更新他们基于 Debian 的发行版的选择。

跳转到您的 stretch 框并运行以下包的快速install

$ sudo apt install unattended-upgrades

如果现在ls /etc/apt/apt.conf.d/目录,您会看到几个新文件:

$ ls -l /etc/apt/apt.conf.d/
total 44
-rw-r--r-- 1 root root   49 Jul 17 01:48 00aptitude
-rw-r--r-- 1 root root   82 Jul 17 01:46 00CDMountPoint
-rw-r--r-- 1 root root   40 Jul 17 01:46 00trustcdrom
-rw-r--r-- 1 root root  769 Sep 13  2017 01autoremove
-r--r--r-- 1 root root 1768 Jul 17 01:49 01autoremove-kernels
-rw-r--r-- 1 root root   80 Dec 11  2016 20auto-upgrades
-rw-r--r-- 1 root root  202 Apr 10  2017 20listchanges
-rw-r--r-- 1 root root 4259 Oct  1 16:49 50unattended-upgrades
-rw-r--r-- 1 root root  182 May 21  2017 70debconf
-rw-r--r-- 1 root root   27 Jul 17 01:49 99translations

这些是unattended-upgrades软件包的关键。

如果我们看一下50unattended-upgrades配置文件中处理要拉取哪些更新的块,我们会看到以下内容:

Unattended-Upgrade::Origins-Pattern {
 // Codename based matching:
 // This will follow the migration of a release through different
 // archives (e.g. from testing to stable and later oldstable).
// "o=Debian,n=jessie";
// "o=Debian,n=jessie-updates";
// "o=Debian,n=jessie-proposed-updates";
// "o=Debian,n=jessie,l=Debian-Security";

 // Archive or Suite based matching:
 // Note that this will silently match a different release after
 // migration to the specified archive (e.g. testing becomes the
 // new stable).
// "o=Debian,a=stable";
// "o=Debian,a=stable-updates";
// "o=Debian,a=proposed-updates";
 "origin=Debian,codename=${distro_codename},label=Debian-Security";
};

请注意,只有最后一行是未注释的行(在右括号之前)。

我们将取消注释它之前的行,如下所示:

$ sudo sed -i 's/\/\/      "o=Debian,a=stable";/      "o=Debian,a=stable";/g' /etc/apt/apt.conf.d/50unattended-upgrades
$ sudo sed -i 's/\/\/      "o=Debian,a=stable-updates";/      "o=Debian,a=stable-updates";/g' /etc/apt/apt.conf.d/50unattended-upgrades
$ sudo sed -i 's/\/\/      "o=Debian,a=proposed-updates";/      "o=Debian,a=proposed-updates";/g' /etc/apt/apt.conf.d/50unattended-upgrades

您可以通过在调试模式下启动命令来运行和测试您的配置:

$ sudo unattended-upgrade -d

它可能看起来像以下内容:

注意到升级实际上已经安装,并且创建了一个日志文件。

工作原理...

yum-cron实际上只是在 cron 作业中使用 YUM 的一种简单方式(我们之前在讨论systemd定时器时曾轻蔑地提到)。因此,您会发现很容易将其纳入自定义定时器(请参阅前几章)或可能每晚运行的 cron 作业中。

一般来说,您可以每晚将所有更新应用到开发环境,然后可能在一周内将更新分散到其他(更高级)环境,将生产环境升级到下一个星期二。这完全取决于您作为全能的系统管理员。

如果您已经采纳了将yum-cron作为服务启用的建议,现在应该会发现以下文件存在:

$ ls /var/lock/subsys/yum-cron 
/var/lock/subsys/yum-cron

这将启用两个cron作业,如下所示:

$ ls /etc/cron.daily/0yum-daily.cron 
/etc/cron.daily/0yum-daily.cron
$ ls /etc/cron.hourly/0yum-hourly.cron 
/etc/cron.hourly/0yum-hourly.cron

这将使用我们提到的配置文件。

在 Debian 的unattended-upgrades的情况下,与大多数现代系统一样,systemd用于每天运行此作业。

列出您的systemd定时器,如下所示:

$ sudo systemctl list-timers
NEXT                         LEFT          LAST                         PASSED    UNIT                         ACTIVATES
Mon 2018-10-01 20:34:32 GMT  3h 16min left Mon 2018-10-01 16:48:00 GMT  30min ago apt-daily.timer              apt-daily.service
Tue 2018-10-02 06:56:19 GMT  13h left      Mon 2018-10-01 16:48:00 GMT  30min ago apt-daily-upgrade.timer      apt-daily-upgrade.service
Tue 2018-10-02 17:03:03 GMT  23h left      Mon 2018-10-01 17:03:03 GMT  15min ago systemd-tmpfiles-clean.timer systemd-tmpfiles-clean.service

3 timers listed.
Pass --all to see loaded but inactive timers, too.

请注意两个apt作业,第一个运行以下内容:

[Service]
Type=oneshot
ExecStart=/usr/lib/apt/apt.systemd.daily update

第二个运行以下内容:

[Service]
Type=oneshot
ExecStart=/usr/lib/apt/apt.systemd.daily install
KillMode=process
TimeoutStopSec=900

还有更多...

与 Debian 上的无人值守升级一样,yum-cron有能力仅处理特定类型的升级。默认情况下,这设置为default,如以下片段所示,这就是为什么我们之前没有修改它的原因:

$ cat /etc/yum/yum-cron.conf 
[commands]
#  What kind of update to use:
# default                            = yum upgrade
# security                           = yum --security upgrade
# security-severity:Critical         = yum --sec-severity=Critical upgrade
# minimal                            = yum --bugfix update-minimal
# minimal-security                   = yum --security update-minimal
# minimal-security-severity:Critical =  --sec-severity=Critical update-minimal
update_cmd = default

没有什么能阻止你改变这一点,也许可以指定只有安全升级应该自动应用?

自动配置

在序言中,我建议我们会涉及到这一点,这绝对值得讨论。

曾经,物理机器在地球上漫游,捕食那些敢于进入它们的沼泽和服务器笼子的无辜系统管理员。

如今,服务器仍然存在,但它们已经被赋予了一个更加不友好和媒体熟悉的名称,变得通俗地被称为,并且变得足够透明,以至于系统管理员不再知道他们最喜欢的时髦发行版是在戴尔、HPE 还是 IBM 机器上运行。

这导致了瞬态服务器的出现,或者服务器在晚上定期停止存在,只在第二天早上重新诞生。

除了让您对于晚上睡觉时是否存在产生存在危机之外,这可能开始让您想到从不更新您的机器,而只是确保它们在所有更新已经应用的情况下重新启动。

按计划自动配置您的基础设施的概念正在获得认可,其核心是通过程序(如 Packer)以编程方式创建一个镜像,然后将其上传和/或移动到虚拟基础设施的不同部分,另一个程序(Terraform)可以使用新镜像来启动许多闪亮的新盒子。

显然,这在生产网络上完全没有问题,因为在您的dev实例上没有客户在周围(我希望-我真的,真的希望)。但在生产中会出现问题,然后您开始考虑一些疯狂的事情,比如蓝/绿部署。

检查邮件列表和勘误页面

在我们的系统之外,我们将看看您在哪里获取有关操作系统整体表现的新闻。它们健康吗?它们需要一些空间吗?它们会很快崩溃吗?

养成这种习惯是很好的做法,因为偶尔,系统和行为变化可能需要系统管理员进行手动干预,即使您已经自动解决了所有其他问题。

服务器-谁需要它们?

准备工作

在本节中,我们将稍微使用我们的虚拟机和强大的互联网。

如何做...

我们将稍微查看一下我们的虚拟机,但我们主要将专注于在线新闻来源。

有各种各样的方法和地方可以获取信息,所以让我们逐步了解一些更受欢迎的方法。

软件包更改日志

如果您想获取有关软件包的信息,您可能会喜欢的一件事是changelog,可以通过简单的 RPM 命令从系统中访问。

首先,找到您想要检查的软件包;我们将获取最近安装的kernel

$ rpm -q kernel --last
kernel-3.10.0-862.14.4.el7.x86_64             Sun 30 Sep 2018 16:40:08 UTC
kernel-3.10.0-862.2.3.el7.x86_64              Sat 12 May 2018 18:51:56 UTC

现在,打开那个kernelchangelog(很长):

$ rpm -q --changelog kernel-3.10.0-862.14.4.el7.x86_64 | less

您将得到类似以下的内容:

这可以是检查特定更改的好方法,但有时可能有点棘手(取决于日志的性质)。

为了证明它也适用于其他软件包,这里是lsof,它要少得多:

在 Debian 和 Ubuntu 下,我们可以使用apt来完成相同的事情,如下所示:

$ apt changelog linux-image-amd64

诚然,输出并不像下面的截图那样详细:

官方来源和邮件列表

Red Hat 通过他们的集体心灵之善,提供了一个用于参考勘误和更新新闻的页面:access.redhat.com/security/updates/advisory

此页面上有一些重要且非常有帮助的链接,例如指向 Red Hat 通用漏洞和暴露 (CVE) 数据库的链接:access.redhat.com/security/security-updates/#/cve

如果您有 Red Hat 登录,还可以在客户门户上找到他们自己的勘误页面链接。

邮件列表是这个世界的重要组成部分,有些邮件列表可以追溯到几十年前,您也可能会收到太多的电子邮件(其中大部分您永远不会阅读)!

大多数大型项目都有邮件列表(有时有几个),订阅所有这些列表几乎是毫无意义的(例如,为什么要订阅内核的 PowerPC 邮件列表,当你在 2000 年代中期就摆脱了你的 New World Macintosh 呢?)

选择您感兴趣的内容,可能对管理有用。安全列表通常是一个很好的起点:

注册的好列表包括公告列表;例如,CentOS-announce列表涵盖了一般和安全信息。

在官方源列表中应该包括各种公开可见的项目源树,以及它们相关的Issues部分(比如 GitHub)。一定要留意你喜欢的任何个人项目,或者那些可能支撑你基础设施的项目(比如 Terraform 等)。

其他来源

BBC,HackerNews,The Register 和 Reddit 以前都曾告诉我一些问题,我在阅读热门新闻网站的头条新闻之前应该意识到这些问题;当涉及到想要引起恐慌时,不要低估主流媒体。

它是如何工作的...

这些项目是公开的,参与其中的人都非常清楚存在问题时的风险。只需看一下当大漏洞被揭示时引起的恐慌,就能够理解为什么通知渠道如此广泛地被使用和赞赏。

如果一个项目没有与用户沟通关键问题的手段,它很快就会发现自己被关心的个人淹没,这些个人只是想知道他们使用的软件是否被及时更新。

我们在对抗安全问题和重大变化方面所能做的就是保持信息灵通,并在需要时迅速采取行动。

还有更多...

实际上还有很多;查看博客,比如 CentOS(blog.centos.org/),以及其他软件包和项目的个人邮件列表。

例如,OpenSSL 是一个值得关注的好例子(www.openssl.org/community/mailinglists.html),我并不是因为任何特定的心脏健康原因而这么说。

一个重要的是内核邮件列表选择,可以通过lkml.org/查看;在这里,内核新闻通常是最先被报道的。

使用 snaps

在这一部分,我们将使用我们的 Ubuntu VM。

Snaps(由 Canonical 提供)是新邻居中的两个新成员之一。它们是一种以通用方式打包软件的方法,因此一个软件包可以部署到支持 snaps 的任何操作系统上。

在写这本书的时候,Ubuntu 可能是对 snaps 支持最好的,但 Canonical 在他们的网站上自豪地列出了相当多发行版的安装说明(尽管其中三个只是 Ubuntu 的下游发行版)docs.snapcraft.io/core/install

我通常对 Canonical 很苛刻,所以让我说一声,我对这一努力表示赞赏。有段时间以来,Linux 上不同的打包方法是一些开发人员远离的原因之一,任何旨在弥合这一差距的努力都是社区的受欢迎的补充。

如何做...

跳转到我们之前创建的 Ubuntu 机器,如下所示:

$ vagrant ssh ubuntu1804

在我们的 VM 上,snapd服务将已经启动并运行(或者应该是的;使用systemctl进行检查)。

搜索 snaps

要搜索 snaps,我们使用snap命令行实用程序。在这个例子中,我将寻找另一个 Canonical 产品(lxd):

$ snap search lxd
Name             Version        Publisher       Notes  Summary
lxd-demo-server  0+git.f3532e3  stgraber        -      Online software demo sessions using LXD
lxd              3.5            canonical       -      System container manager and API
nova             ocata          james-page      -      OpenStack Compute Service (nova)
satellite        0.1.2          alanzanattadev  -      Advanced scalable Open source intelligence platform
nova-hypervisor  ocata          james-page      -      OpenStack Compute Service - KVM Hypervisor (nova)

我们得到了一些结果,分别由 Canonical 和其他一些名称发布。

它不仅限于守护程序;在下面的代码中,我正在搜索aws-cli工具:

$ snap search aws-cli
Name     Version  Publisher  Notes    Summary
aws-cli  1.15.71  aws√       classic  Universal Command Line Interface for Amazon Web Services

请注意发布者名称旁边的勾号;这意味着该软件包来自已验证的帐户(在这种情况下是亚马逊网络服务)。

安装 snaps

我们想要的 snap 的名称是lxd,这样我们的安装就很容易了:

$ sudo snap install lxd

您将看到类似以下的进度条:

完成后,您将从一个 snap 安装了lxd容器管理器。

列出已安装的 snaps

我们可以使用snap list列出我们安装的 snaps,如下所示:

$ snap list
Name  Version    Rev   Tracking  Publisher   Notes
core  16-2.35.2  5548  stable    canonical√  core
lxd   3.5        8959  stable    canonical√  -

与守护程序 snaps 互动

因为 LXD 是一个守护程序,我们可以再次使用snap命令行工具来启用它;首先,我们应该检查我们服务的活动状态,如下所示:

$ sudo snap services
Service Startup Current
lxd.activate enabled inactive
lxd.daemon enabled inactive

它是非活动的,但我们可以激活它(我们有这项技术),如下所示:

$ sudo snap start lxd
Started.

再次检查服务,我们可以看到它已经启动了:

$ sudo snap services
Service       Startup  Current
lxd.activate  enabled  inactive
lxd.daemon    enabled  active

systemd下,服务被称为snap.lxd.daemon.service,如果您想使用传统工具来检查它的状态。

为了证明它已经启动并且我们可以与守护进程交互,我们可以使用捆绑的lxc包,如下所示:

$ lxd.lxc list
Error: Get http://unix.socket/1.0: dial unix /var/snap/lxd/common/lxd/unix.socket: connect: permission denied

您可以看到它试图与套接字通信;虽然在前面的片段中它给了我们一个权限被拒绝的错误,但这确实突出了套接字存在于/var/snap/目录中。

让我们再试一次,使用sudo

$ sudo lxd.lxc list
+------+-------+------+------+------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+------+-------+------+------+------+-----------+

太棒了!

移除 snaps

最后,我们可以使用我们的工具无偏见地移除lxd

$ sudo snap remove lxd
lxd removed

它是如何工作的...

Snaps 像任何其他软件包管理器一样工作,安装和管理从存储库中引入的软件包。

您还会注意到我们安装的 snaps 列表中的核心安装;这实际上是 snaps 工作的基本平台。

snapd是支持 snaps 的守护进程;它是管理已安装的 snaps 的环境,处理安装、更新和删除旧版本。

当你安装一个 snap 时,你实际上下载的是一个位于/var/lib/snapd/snaps/中的只读squashfs文件:

$ ls
core_5548.snap  lxd_8959.snap  partial

这些数字是 snap 修订号。

当这些squashfs镜像被snapd挂载时,您可以用df看到它们被人格化为循环设备:

$ df -h | grep loop
/dev/loop0 67M 67M 0 100% /snap/lxd/8959
/dev/loop1 88M 88M 0 100% /snap/core/5548

你也可以使用mount命令查看特定的mount信息:

$ mount | grep snap
/var/lib/snapd/snaps/lxd_8959.snap on /snap/lxd/8959 type squashfs (ro,nodev,relatime,x-gdu.hide)
/var/lib/snapd/snaps/core_5548.snap on /snap/core/5548 type squashfs (ro,nodev,relatime,x-gdu.hide)
tmpfs on /run/snapd/ns type tmpfs (rw,nosuid,noexec,relatime,size=100896k,mode=755)
nsfs on /run/snapd/ns/lxd.mnt type nsfs (rw)

请注意,我们可以进入这些 snaps 被挂载的位置,如下所示:

$ cd /snap/lxd/8959/bin/

然而,由于文件系统是只读的,我们无法在其中写入任何内容:

$ touch test
touch: cannot touch 'test': Read-only file system

很整洁,对吧?

由于我们的$PATH中有各种snap条目,我们可以在不直接调用二进制文件的情况下使用我们的 snaps:

$ echo $PATH
/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin:/snap/bin:/var/lib/snapd/snap/bin:/snap/bin:/var/lib/snapd/snap/bin

PATH是您的 shell 将查找二进制文件的定义位置列表;当您运行ls时,您正在在PATH中的某个位置定位二进制文件。

Snaps 也是自包含的,这意味着库和运行时被捆绑到软件包中(这使得在不同发行版之间的可移植性变得容易)。

还有更多...

如果您想要关于 snap 的详细信息,还有snap info命令。

当命令针对lxd包运行时,以下是输出:

$ snap info lxd
name:      lxd
summary:   System container manager and API
publisher: Canonical√
contact:   https://github.com/lxc/lxd/issues
license:   unset
description: |
 LXD is a container manager for system containers.

 It offers a REST API to remotely manage containers over the network, using an image based workflow
 and with support for live migration.

 Images are available for all Ubuntu releases and architectures as well as for a wide number of
 other Linux distributions.

 LXD containers are lightweight, secure by default and a great alternative to virtual machines.
commands:
 - lxd.benchmark
 - lxd.buginfo
 - lxd.check-kernel
 - lxd.lxc
 - lxd
 - lxd.migrate
services:
 lxd.activate: oneshot, enabled, inactive
 lxd.daemon:   simple, enabled, inactive
snap-id:      J60k4JY0HppjwOjW8dZdYc8obXKxujRu
tracking:     stable
refresh-date: today at 16:44 UTC
channels: 
 stable:        3.5         (8959) 69MB -
 candidate:     3.5         (8959) 69MB -
 beta:          ↑ 
 edge:          git-47f0414 (8984) 69MB -
 3.0/stable:    3.0.2       (8715) 65MB -
 3.0/candidate: 3.0.2       (8715) 65MB -
 3.0/beta:      ↑ 
 3.0/edge:      git-d1a5b4d (8957) 65MB -
 2.0/stable:    2.0.11      (8023) 28MB -
 2.0/candidate: 2.0.11      (8023) 28MB -
 2.0/beta:      ↑ 
 2.0/edge:      git-92a4fdc (8000) 26MB -
installed:       3.5         (8959) 69MB -

这应该告诉您大部分关于任何特定 snap 的信息。

另请参阅...

如果你生活在 21 世纪,你就不必在命令行上搜索 snaps。

您还可以使用snapcraft.io网站:snapcraft.io/

在商店部分,您将找到一个视觉搜索,它可以帮助您以友好的、点击按钮的方式找到您想要的东西。在下面的截图中,我搜索了aws

使用 Flatpak

Flatpak(由 Alex Larsson 和 Flatpak 团队)是完整解决方案软件包管理器时髦团体中的第二个。这也是一种打包软件的好方法,这样一个软件包可以部署到支持 Flatpak 安装的任何操作系统。听起来很熟悉吗?

实际上,我们又再次涉及到了冲突的技术发展和圣战。

首先,我应该指出,Flatpak 确实强调桌面应用程序而不是服务器应用程序,从它们复杂的运行命令到它们大多是图形工具的事实。Snaps 绝对更多地是两个世界的混合体。

显然,如果您想在服务器上安装 GUI,没有什么能阻止您,您甚至可以使用 VNC 进行管理!然而,这并不是真的,就像鱼柳和卡斯塔德一样。

准备就绪

在本节中,我们将继续使用我们的 Ubuntu 虚拟机(主要是因为它是我写完上一节后仍然打开的)。

我们也可以使用我们的 Debian 或 CentOS 盒子,其他许多发行版也受支持,包括(但不限于)以下:

  • Arch

  • 联邦

  • Gentoo

  • Solus

  • 阿尔卑斯

  • openSUSE

为了设置我们的 VM 以使用 Flatpak,我们必须安装它,尽管它在默认仓库中可用(取决于您阅读本书的时间,可能需要升级;如果您在 2017 年之前阅读本书,我对您的时间位移能力印象深刻,但您应该知道未来是黑暗且充满柠檬):

$ sudo apt update && sudo apt upgrade -y
$ sudo apt install flatpak -y

接下来,我们需要从flathub.org启用远程flathub仓库:

$ sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo

现在,我们可以安装东西了!

如何做...

为了本节的目的,我选择了相对轻量级的vim软件包从 Flathub 安装。

搜索软件包

首先,让我们查找软件包:

$ sudo flatpak search vim
Application ID            Version   Branch Remotes Description 
org.vim.Vim               v8.1.0443 stable flathub Edit text files 
net.mediaarea.AVIMetaEdit 1.0.2     stable flathub Embed, validate, and export AVI files metadata 
org.gnome.Devhelp                   stable flathub A developer tool for browsing and searching API documentation 
org.openshot.OpenShot     2.4.3     stable flathub An easy to use, quick to learn, and surprisingly powerful video editor
org.gnome.Builder         3.30.1    stable flathub An IDE for GNOME   

再次,我们有一些结果,但最重要的是我们要找的。

安装我们的软件包

我们可以使用一个小命令安装软件包,如下所示:

$ flatpak install flathub org.vim.Vim -y

这可能需要相当长的时间来下载,并且占用的空间可能比您预期的要大(尽管它是一个相对轻量级的软件包)。

运行我们的软件包

安装完成后,您可以运行您的新版本Vim

$ flatpak run org.vim.Vim

软件包标识符由三部分组成:通常是org/com.<公司或团队>.<应用程序名称>

这不是最漂亮的命令,但它会让您进入经过验证的文本编辑器,如下所示:

如果我们查看 Flatpak 安装和本地Vim安装的版本,我们可以看到区别:

$ flatpak run org.vim.Vim --version | head -n3
VIM - Vi IMproved 8.1 (2018 May 18, compiled Oct  1 2018 10:15:08)
Included patches: 1-443
Compiled by buildbot
$ vim --version | head -n3
VIM - Vi IMproved 8.0 (2016 Sep 12, compiled Apr 10 2018 21:31:58)
Included patches: 1-1453
Modified by pkg-vim-maintainers@lists.alioth.debian.org

列出已安装的软件包

现在我们有东西可以展示了,我们可以列出我们安装的flatpak软件包:

$ flatpak list
Ref                                        Options 
org.vim.Vim/x86_64/stable                  system,current
org.freedesktop.Platform.ffmpeg/x86_64/1.6 system,runtime
org.freedesktop.Platform/x86_64/1.6        system,runtime

请注意,它还告诉我们该软件包是一个系统软件包,而不是一个每个用户的软件包。

用户安装

Flatpak 还有本地用户安装的概念,这意味着我们也可以以我们的用户身份安装我们的软件包:

$ flatpak --user remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
$ flatpak --user install flathub org.vim.Vim -y

移除软件包

当您最终对Vim感到厌倦,并回到使用ed进行日常编辑需求时(因为ed是标准文本编辑器),您可以轻松地移除您的软件包,如下所示:

$ sudo flatpak uninstall org.vim.Vim --user -y

在这里,我们特别移除了用户安装的版本;系统版本将保留。

工作原理...

当您使用 Flatpak 安装软件包时,它会出现在两个地方之一:

  • 系统软件包最终会出现在/var/lib/flatpak中。

  • 用户软件包最终会出现在~/.local/share/flatpak/中。

查看这些位置,我们可以找到一个app目录,在其中有我们的软件包:

$ pwd
/home/vagrant/.local/share/flatpak/app
$ ls
org.vim.Vim

在这个目录中,还有更多的层,其中包含您的软件包的当前版本和各种二进制文件。

软件包是建立在运行时之上的,就像您之前列出已安装软件包时看到的那样。这些运行时是分发无关的,这意味着它们可以安装在世界上所有的 Ubuntu、CentOS 和 Fedora 系统上。

如果应用程序需要额外的东西来运行,比如特定的库,您也可以将其打包到您的软件包中。

还有更多...

在撰写本书时,有585个软件包可供从flathub安装,这个数字每天都在增长:

$ flatpak remote-ls flathub | wc -l
585

您还可以使用一个命令更新您的应用程序,如下所示:

$ flatpak update
Looking for updates...

参见...

对于那些对Vim深恶痛绝的人,尽管它显然更优秀,Flathub 也为您提供了解决方案:

$ flatpak search emacs
Application ID    Version Branch Remotes Description 
org.gnu.emacs     26.1    stable flathub An extensible text editor 
org.gnome.Devhelp         stable flathub A developer tool for browsing and searching API documentation

还有许多软件包可用,但正如我之前所说,您实际上不太可能在服务器上使用 Flatpak,因为它是一个面向桌面的努力。

然而,在您自己的计算机上,Snaps 和 Flatpak 软件包可以并存安装。

我曾试图将Solus作为我的日常驱动程序,但希望确保我的安装没有任何异常。当时,Solus有自己的软件包、snap 支持和 Flatpak 支持。这实际上导致我使用特定的 snaps 来管理 Kubernetes 设置,使用 Flatpak 安装Slack,并使用系统自带的软件包管理器来处理其他所有事情;最后有点混乱,但是一致的混乱!

使用 Pip、RubyGems 和其他软件包管理器

除了 YUM、Apt、snaps 和 Flatpak 之外,还有许多其他软件包管理系统。Pip 和 RubyGems 是分发软件包到系统的编程语言相关方法;除了这两种方法,还有更多,但它们目前是最受欢迎的。

Pip 安装软件包Pip)在最近的 Python 安装中默认包含。Gem 只是玩弄它是用于打包 Ruby 元素的事实;它也包含在最近的 Ruby 安装中。

我们将涉及使用这些软件包管理器安装软件。

准备工作

在本节中,我们将继续使用我们的 Ubuntu VM。

在您的 Ubuntu 机器上安装 Pip 和 RubyGems(Python 将已经安装,但在这种情况下,Pip 是一个单独的软件包),如下所示:

$ sudo apt update && sudo apt upgrade -y
$ sudo apt install libgmp3-dev make gcc ruby ruby-dev python3-setuptools -y 

Python2 和 Python3 都被广泛使用,尽管如今,您真的不应该在 Python2 中编写任何新内容(Python2 将在 2020 年停止支持)。

现在我们想要安装pip,使用easy_install3脚本:

$ sudo python3 /usr/lib/python3/dist-packages/easy_install.py pip

有一种方法可以使用apt安装python3-pip,但是这个版本经常过时,而使用 Pip 的整个目的是获得所有内容的最新版本;因此我们使用easy_install。除此之外,如果您尝试升级系统安装的 Pip 版本,可能会很好用,但是您将会在系统控制之外更改系统控制的软件包... 哎呀。

如何做...

我们将运行一些基本操作,您可能会使用这些软件包管理器。

Pip

从 Pip 开始,您可以使用--version参数检查您正在运行的版本:

$ pip3 --version
pip 18.1 from /usr/local/lib/python3.6/dist-packages/pip-18.1-py3.6.egg/pip (python 3.6)

您可以使用list列出您安装的软件包(及其版本),如下所示:

$ pip3 list
Package             Version 
------------------- ---------
asn1crypto          0.24.0 
attrs               17.4.0 
Automat             0.6.0 
blinker             1.4 
certifi             2018.1.18
chardet             3.0.4 
click               6.7 
cloud-init          18.3 
colorama            0.3.7 

您可以搜索您想要的软件包;在这里,我正在检查 Ansible:

$ pip3 search ansible
ovirt-ansible (0.3.2) - oVirt Ansible utility
polemarch-ansible (1.0.5) - Wrapper for Ansible cli.
kapellmeister-ansible (0.1.0) - Ansible Playbook manager.
ansible-alicloud (1.5.0) - Ansible provider for Alicloud.
ansible-kernel (0.8.0) - An Ansible kernel for Jupyter
ansible-roles (0.1.4) - Manage ansible roles.
ansible-shell (0.0.5) - Interactive shell for ansible
ansible-toolkit (1.3.2) - The missing Ansible tools
ansible-toolset (0.7.0) - Useful Ansible toolset
...

Python 软件包索引PyPI)中有很多软件包,因此您可能会从搜索中获得很多结果;这就是学习一些regex和调用grep可能会有用的地方。

一旦找到它,我们也可以安装我们的软件包,如下所示:

$ pip3 install ansible --user

请注意缺少sudo;这是因为我们希望将其安装为我们的用户,这意味着软件包最终会出现在我们主目录(~)中的.local目录中:

$ ls .local/bin/
ansible         ansible-connection  ansible-doc     ansible-inventory  ansible-pull   easy_install
ansible-config  ansible-console     ansible-galaxy  ansible-playbook   ansible-vault  easy_install-3.6

默认情况下,.local/bin目录在我们的PATH中(如果我们退出并重新登录):

$ echo $PATH
/home/vagrant/.local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/snap/bin

这意味着我们可以从我们的 shell 中运行ansible

$ ansible --version
ansible 2.7.0
 config file = None
 configured module search path = ['/home/vagrant/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
 ansible python module location = /home/vagrant/.local/lib/python3.6/site-packages/ansible
 executable location = /home/vagrant/.local/bin/ansible
 python version = 3.6.6 (default, Sep 12 2018, 18:26:19) [GCC 8.0.1 20180414 (experimental) [trunk revision 259383]]

在我们的软件包安装完成后,我们可能会发现我们实际上需要一个旧版本;幸运的是,Pip 让您可以轻松指定这一点,如下所示:

$ pip3 install ansible==2.5.0 --user

并且,在以后的某个日期,如果我们决定需要最新版本(因为旧的 playbook 最终已更新以在没有弃用功能的情况下工作),我们可以升级,如下所示:

$ pip3 install ansible --upgrade --user

RubyGems

与 Pip 一样,我们可以使用简单的gem命令检查我们安装了哪个版本的 RubyGems:

$ gem --version
2.7.6

要列出已安装的 gems,我们可以使用list,非常有趣:

$ gem list

*** LOCAL GEMS ***

bigdecimal (default: 1.3.4)
cmath (default: 1.0.0)
csv (default: 1.0.0)
date (default: 1.0.0)
dbm (default: 1.0.0)
did_you_mean (1.2.0)
...

如果我们想搜索软件包,我们使用gem search(RubyGems 中还有--exact选项,Pip 中缺少):

$ gem search -e chef

*** REMOTE GEMS ***

chef (14.5.33 ruby universal-mingw32, 12.3.0 x86-mingw32)

我们也可以使用gem install(作为用户)进行安装:

$ gem install chef --user-install

请注意,默认情况下,.local gem 安装位置不会出现在您的PATH中,但是我们可以在以后的某个日期从我们的主目录中使用其完整路径调用它(以添加到我们的PATH中):

$ ~/.gem/ruby/2.5.0/bin/chef-client --version
Chef: 14.5.33

与 Pip 一样,我们可以安装其他版本的软件包:

$ gem install chef -v14.2.0 --user-install
$ ~/.gem/ruby/2.5.0/gems/chef-14.2.0/bin/chef-client --version
Chef: 14.2.0

请注意,我们在这里使用了不同的路径,进入安装目录的/gems/部分,通过其版本调用软件包。

如果您要卸载软件包,现在您有一个选择,如下所示:

$ gem uninstall chef

Select gem to uninstall:
 1\. chef-14.2.0
 2\. chef-14.5.33
 3\. All versions
> 

选择卸载14.5.33(选项2)。

我们现在已经安装了一个版本的chef,如下所示:

$ gem list -e chef

*** LOCAL GEMS ***

chef (14.2.0)

与 Pip 一样,我们也可以升级它,如下所示:

$ gem update chef --user-install $ gem list -e chef

*** LOCAL GEMS ***

chef (14.5.33, 14.2.0)

请注意,默认情况下它也会保留旧版本的安装。

它是如何工作的...

Pip 和 RubyGems 试图相对独立,但它们仍然是软件包管理器,这意味着它们实际上只是在上游存储库中查询软件包,然后将其下载到您的系统上。

当您更新您的PATH以更新新可执行文件所在的位置时,您就能够运行您刚刚安装的软件包。

还有更多...

Pip 和 RubyGems 是一个庞大的话题,每个话题都有大量的博客潜力,因此有很多我们没有涵盖的内容。接下来的几节将涵盖一些更明显的内容。

何时使用编程语言软件包管理器

所以,问题来了。

Ansible 和 Chef 都可以在 Ubuntu 存储库中找到,经过精心定制和打包,适用于世界各地的 Ubuntu 系统。

那么,为什么我要使用 Pip 来安装呢?

很简单;在撰写本文时,Ubuntu 存储库中的 Ansible 版本是 2.5.1,而 PyPI 存储库中的版本是 2.7.0,这是一个相当大的升级。

如果您想要程序的最新和最好的功能,或者比您的发行版提供的更新的库,您很可能会被诱惑在 Apt 之外安装,这并不一定是一个问题。问题在于记住所有这些软件包是如何安装的,并确保您知道如何保持每个软件包的最新状态。

--user/ --system (pip) and --user-install (gem)

与 Flatpak 一样,我们可以选择在用户级别或系统范围内安装软件包。在使用的示例中,我选择了本地安装,这意味着这些软件包通常只能默认提供给我的用户。

$ pip3 install ansible==2.5.0 --user

Python 虚拟环境

Python 存在一个固有的问题-冲突的软件包版本-因此有了虚拟环境。实际上,虚拟环境是一种隔离安装的方式,使它们不会冲突,并且您可以(可能)轻松地安装同一软件包的多个版本。

这种情况的一个用例可能是 Molecule,这是一个为 Ansible 角色设计的测试框架。Molecule 的 1 和 2 版本彼此不兼容,但您绝对可以在您的基础架构中为第 1 版编写一些 Ansible 角色(因为没有人会立即更新,因为有更紧迫的问题...总是有更紧迫的问题)。不过我们有虚拟环境,所以我们可以安装 Molecule 1 和 Molecule 2 而不必担心它们冲突。

另请参阅

与任何其他软件包管理器一样,Pip 和 RubyGems 管理软件包。

你们中的一些人可能已经发现了这个问题,这是人们很少意识到的问题。如果系统上有多个软件包管理器,每个管理器维护其自己的软件包并调整您的PATH,您可能会遇到从系统软件包管理器安装的软件包和从第三方软件包管理器安装的软件包之间的冲突。

在某些情况下,您会遇到名称冲突。

我曾经看到 Puppet 二进制文件factor与另一个完全相同名称的二进制文件发生冲突,在一台机器上引起了奇怪而奇妙的问题-那真是有趣。

依赖地狱(简短说明)

现在我们将回顾一下记忆;具体来说,作者将在几个小时内蜷缩成一团,回忆着对服务器的怒吼。

依赖地狱是指一个软件包可能对您安装的依赖项有冲突,或者可能尝试使用不兼容的版本,出于任何原因。

在 Python 和 Pip 的情况下,我们已经讨论了虚拟环境的概念,但从历史上看,这在其他软件包管理器中也是一个问题。基于 RPM 的发行版以这些问题臭名昭著,特别是开发了术语RPM 地狱来专门指代它们的问题。

在安装软件时,有时会出现几个依赖项的选择;像 Apt 这样的程序试图通过向用户呈现几个选项并要求他们选择要使用的选项来缓解这种情况。

准备就绪

在这一部分,我们只会在我们的虚拟机上运行几个命令,以便查看输出。

进入您的 Debian 9 系统并确保 Pip 已安装并且是最新的:

$ vagrant ssh debian9
$ sudo apt install gcc python3-dev python3-setuptools -y
$ sudo easy_install3 pip
$ pip --version
pip 18.1 from /usr/local/lib/python3.5/dist-packages/pip-18.1-py3.5.egg/pip (python 3.5)

现在,进入我们的 Ubuntu 系统并使用apt安装pip

$ sudo apt update
$ sudo apt install python3-pip -y

检查version,如下所示:

$ pip3 --version
pip 9.0.1 from /usr/lib/python3/dist-packages (python 3.6)

然后,在 Ubuntu 上进行upgrade(仅限 Ubuntu),如下所示:

$ pip3 install pip --upgrade

注销(一个重要的步骤)并再次检查version

$ pip3 --version
pip 18.1 from /home/vagrant/.local/lib/python3.6/site-packages/pip (python 3.6)

如何做...

要了解依赖问题可能是什么样子,请看以下内容。

Pip 的系统安装和第三方安装版本

在我们的 Ubuntu 系统中,我们使用系统软件包管理器(apt)安装了pip,然后使用 Pip 升级了它。

这意味着apt认为该软件包看起来像这样:

$ apt list python3-pip -a
Listing... Done
python3-pip/bionic-updates,now 9.0.1-2.3~ubuntu1 all [installed]

我们的本地会话认为pip看起来像这样:

$ pip3 --version
pip 18.1 from /home/vagrant/.local/lib/python3.6/site-packages/pip (python 3.6)

这是一个问题,因为将来的软件包可能依赖于 Pip 9,并期望它在系统上正确安装,尽管版本不同。

在这种情况下,我们实际上使用了系统安装的 Pip 版本来安装并升级了本地版本;这就是为什么版本字符串来自我们的.local目录的原因,但这仍然不是一个理想的情况。

Pip 软件包中的依赖问题

为了更好地理解为什么 virtualenv 是一个东西,我们可以看一下 Molecule 的安装。

在您的 Debian 实例中,安装 Molecule 测试框架(具体来说是2.15.0):

$ pip install molecule==2.15.0 --user

一切顺利的话,安装应该会很顺利,你可以检查你的 Molecule 版本:

$ .local/bin/molecule --version
molecule, version 2.15.0

然而,我们现在要使用install ansible-lint(在撰写本书时的最新版本):

$ pip install ansible-lint==3.4.23 --user

我们的安装工作正常,但在安装过程中,我们收到了一个令人讨厌的警告:

molecule 2.15.0 has requirement ansible-lint==3.4.21, but you'll have ansible-lint 3.4.23 which is incompatible.

如果我们检查已安装的versionansible-lint看起来不错:

$ .local/bin/ansible-lint --version
ansible-lint 3.4.23

然而,如果我们再次运行我们的 Molecule 安装,我们会被告知它已经帮助我们将ansible-lint降级了:

Installing collected packages: ansible-lint
 Found existing installation: ansible-lint 3.4.23
 Uninstalling ansible-lint-3.4.23:
 Successfully uninstalled ansible-lint-3.4.23
 Running setup.py install for ansible-lint ... done
Successfully installed ansible-lint-3.4.21

这显然是一个简单的例子,因为只涉及两个软件包;想象一下如果由 Pip 管理的五个、十个甚至二十个软件包会变得多么忙碌和紧张。

Apt 的冲突解决方案

以下是 Apt 检测到依赖问题并拒绝继续安装的示例:

$ sudo apt install postfix exim4-base
Reading package lists... Done
Building dependency tree 
Reading state information... Done
Some packages could not be installed. This may mean that you have
requested an impossible situation or if you are using the unstable
distribution that some required packages have not yet been created
or been moved out of Incoming.
The following information may help to resolve the situation:

The following packages have unmet dependencies:
 exim4-base : Depends: exim4-config (>= 4.82) but it is not going to be installed or
 exim4-config-2
E: Unable to correct problems, you have held broken packages.

请注意,如果你单独安装postfix然后尝试安装exim,你将得到以下结果:

$ sudo apt install exim4-base
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following packages were automatically installed and are no longer required:
  postfix-sqlite ssl-cert
Use 'sudo apt autoremove' to remove them.
The following additional packages will be installed:
  exim4-config exim4-daemon-light guile-2.0-libs libgsasl7 libkyotocabinet16v5 libltdl7 liblzo2-2
  libmailutils5 libmariadbclient18 libntlm0 libpython2.7 libpython2.7-minimal libpython2.7-stdlib
  mailutils mailutils-common mysql-common psmisc python2.7 python2.7-minimal
Suggested packages:
  eximon4 exim4-doc-html | exim4-doc-info spf-tools-perl swaks mailutils-mh mailutils-doc
  python2.7-doc binfmt-support
The following packages will be REMOVED:
 postfix
The following NEW packages will be installed:
  exim4-base exim4-config exim4-daemon-light guile-2.0-libs libgsasl7 libkyotocabinet16v5 libltdl7
  liblzo2-2 libmailutils5 libmariadbclient18 libntlm0 libpython2.7 mailutils mailutils-common
  mysql-common psmisc
The following packages will be upgraded:
  libpython2.7-minimal libpython2.7-stdlib python2.7 python2.7-minimal
4 upgraded, 16 newly installed, 1 to remove and 7 not upgraded.
Need to get 13.2 MB of archives.
After this operation, 27.5 MB of additional disk space will be used.
Do you want to continue? [Y/n]  

告诉您,如果继续,postfix将从您的系统中删除的那一行是用粗体标出的。

潜在的解决方案

尽管存在这些令人讨厌(有时候令人厌烦)的问题,但这些问题确实有一些解决方案。

我们已经提到了 virtualenv,现在,我们要再次提到它,只是为了强调这一点。去寻找关于它的知识,因为它可能会在未来为你节省严重的头痛。

Docker 是另一个潜在的解决方案,尽管将应用程序限制在它们自己的小环境中的想法并不新鲜(参见 Solaris Zones 和 FreeBSD jails),但 Docker 提供了一个快速简单的接口,利用 Linux 内核功能来隔离应用程序和依赖项。

多个虚拟机也可能是您前进的方式。过去我们需要购买一个、两个或者三个服务器,并在每台服务器上使用多个软件包;如今情况已经不同了,虽然您可能仍然有一些物理服务器,但更有可能的是在每台服务器上使用虚拟机,这提供了一个完全隔离整个操作系统的好方法。

它是如何工作的...

软件包管理之所以有效是因为勤奋的人让它有效。依赖问题仍然是一个问题,尽管这些问题对用户来说大多是透明的。随着您支持的软件包越来越多,这个问题就会变得更加严重,这意味着 Debian 作为一个拥有成千上万软件包的系统,需要确保每个软件包始终正常工作,或者在引起问题之前检测到冲突。

让我们向每个发行版中辛勤工作的软件包维护者大喊一声,并感谢他们不知疲倦地努力确保我们的系统不会尝试安装彼此不兼容的软件包和库。

如果您曾经不得不制作自己的软件包,祝您好运!

从源代码编译

“哦,这是 Linux;当你重新编译内核完成时给我打电话!”这是每个不明真相的技术人员的声明。

软件包不是在系统上安装软件的唯一方法;如果您有源代码(要安装的软件的配方),您可以按照自己的方式编译程序!

这在现今很少发生,除了内部软件之外,因为编译软件可能是一项耗时和资源密集的任务。世界上像 Gentoo 用户可能会喜欢它,并且有人认为它可以加快和精简安装,但这些在现代硬件上通常是微不足道的好处。

准备工作

在这里,我们将获取htop的源代码,这是一个流行的交互式进程监视器。

这并不是在推销,但我碰巧喜欢htop,并且我会在有机会的时候在我的个人系统以及我管理的系统上安装它。

您需要访问源代码的 GitHub 页面,网址为github.com/hishamhm/htop

您还将使用您的 CentOS 虚拟机。

登录到您的 CentOS 虚拟机并安装unzipwget,如下:

$ vagrant ssh centos7
$ sudo yum install unzip wget -y

按照惯例,导航到/usr/local/src,这是我们将放置本地安装软件的源代码的地方:

$ cd /usr/local/src

下载htop存储库的最新版本(这里,我使用https):

$ sudo wget https://github.com/hishamhm/htop/archive/master.zip

如何做到...

现在您的目录中应该有一个master.zip文件:

$ ls -lha
total 248K
drwxr-xr-x.  2 root root   24 Oct  7 03:34 .
drwxr-xr-x. 12 root root  131 May 12 18:50 ..
-rw-r--r--.  1 root root 248K Oct  7 03:34 master.zip

我们需要unzip它,为了方便起见更改所有权,并跳转到内部:

$ sudo unzip master.zip
$ sudo chown -R vagrant:vagrant htop-master/
$ cd htop-master

在这个目录中,您会找到一大堆文件,大多数是C类型的,但偶尔也会有其他类型的文件。在源目录中,您几乎总是会找到一个README,这是一个很好的起点:

$ less README

README 总是不同的,但我还没有找到一个严肃的项目,它们不是好的。请参阅以下示例:

这个文件会告诉您需要安装的任何依赖项,并且安装软件包本身的适当方法。

由于我们下载了源代码,我们需要从前面的屏幕截图中获取autogen.sh行。如果我们尝试运行脚本,将会收到一个错误:

$ ./autogen.sh 
./autogen.sh: line 3: autoreconf: command not found

这是因为autoconf软件包没有安装;继续安装,然后再次尝试脚本:

$ sudo yum install autoconf -y
$ ./autogen.sh
Can't exec "aclocal": No such file or directory at /usr/share/autoconf/Autom4te/FileUtils.pm line 326.
autoreconf: failed to run aclocal: No such file or directory

另一个yum告诉我们automake软件包没有安装,所以让我们安装它!

$ sudo yum install automake -y
$ ./autogen.sh 
configure.ac:23: installing './compile'
configure.ac:16: installing './config.guess'
configure.ac:16: installing './config.sub'
configure.ac:18: installing './install-sh'
configure.ac:18: installing './missing'
Makefile.am: installing './INSTALL'
Makefile.am: installing './depcomp

好!这次,它起作用了。

README建议查看INSTALL文件;所以,让我们接下来看一下:

用相当多的文字,这为我们提供了大多数以这种方式打包的软件包的安装过程。

回到README文件,我们将尝试安装步骤的下一部分,如下所示:

$ ./configure 
checking build system type... x86_64-unknown-linux-gnu
checking host system type... x86_64-unknown-linux-gnu
checking target system type... x86_64-unknown-linux-gnu
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for a thread-safe mkdir -p... /usr/bin/mkdir -p
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking whether make supports nested variables... yes
checking for gcc... no
checking for cc... no
checking for cl.exe... no
configure: error: in `/usr/local/src/htop-master':
configure: error: no acceptable C compiler found in $PATH
See `config.log' for more details

我们还缺少其他东西;在这种情况下,错误告诉我们找不到 C 编译器。

大多数系统中默认的 C 编译器是 GCC,但也有其他可能有效或无效的编译器(如musl):

$ sudo yum install gcc -y
$ ./configure
<SNIP>
checking for strdup... yes
checking whether gcc -std=c99 option works... yes
checking if compiler supports -Wextra... yes
checking for addnwstr in -lncursesw6... no
checking for addnwstr in -lncursesw... no
checking for addnwstr in -lncurses... no
configure: error: You may want to use --disable-unicode or install libncursesw.

我们现在可以更进一步,但是由于脚本检查要求,我们发现找不到libncursesw的安装。

它给了我们两个选项:禁用 unicode,或安装libncursesw。为了完整起见,让我们安装ncurses-devel软件包:

$ sudo yum install ncurses-devel -y
$ ./configure
<SNIP>
checking that generated files are newer than configure... done
configure: creating ./config.status
config.status: creating Makefile
config.status: creating htop.1
config.status: creating config.h
config.status: executing depfiles commands

现在,我们到了配置脚本的末尾,没有更多的错误了。万岁!

最后,我们make软件包,这是将源代码编译成可用程序的实际步骤:

$ make

它可能会很吵,如下面的屏幕截图所示:

在我们的目录中,现在有一个htop二进制文件,如下:

$ ls -lah htop
-rwxrwxr-x. 1 vagrant vagrant 756K Oct  7 03:51 htop

试一试:

$ ./htop --version
htop 2.2.0 - (C) 2004-2018 Hisham Muhammad
Released under the GNU GPL.

最后,我们需要将程序安装到适当的位置;这是通过make install命令完成的。这确实需要sudo,因为我们正在将东西从我们的本地文件夹移动到文件系统的其他位置:

$ sudo make install
make  install-am
make[1]: Entering directory `/usr/local/src/htop-master'
make[2]: Entering directory `/usr/local/src/htop-master'
 /usr/bin/mkdir -p '/usr/local/bin'
 /usr/bin/install -c htop '/usr/local/bin'
 /usr/bin/mkdir -p '/usr/local/share/applications'
 /usr/bin/install -c -m 644 htop.desktop '/usr/local/share/applications'
 /usr/bin/mkdir -p '/usr/local/share/man/man1'
 /usr/bin/install -c -m 644 htop.1 '/usr/local/share/man/man1'
 /usr/bin/mkdir -p '/usr/local/share/pixmaps'
 /usr/bin/install -c -m 644 htop.png '/usr/local/share/pixmaps'
make[2]: Leaving directory `/usr/local/src/htop-master'
make[1]: Leaving directory `/usr/local/src/htop-master'

现在,我们可以运行whereis并找出它的安装位置(尽管我们也可以在前面的代码片段中看到):

$ whereis htop
htop: /usr/local/bin/htop

它是如何工作的...

对于大多数 Linux 程序(特别是 C 类型),这种模式是相当标准的。复制源代码,使用默认配置进行配置(或进行任何您想要的更改),编译软件,并在文件系统的各个位置安装它。

INSTALL文件提供了对不同步骤的良好概述,但简而言之,它们如下:

  • configure:根据您的环境创建包含特定系统选项的Makefile。这可能会相当长;htop文件有 1,422 行。

  • make:这是为了正确编译任何需要它的源代码,创建二进制文件和补充可能需要的文件。

  • make install:这将把文件放到适当的位置。

很简单,对吧?

还有更多...

像内核这样的东西也可以编译,但是由于需要考虑的部件和子系统的数量庞大,所以需要更长的编译时间。长时间的编译时间是人们和项目这些天默认使用预编译的二进制文件块的主要原因(因为很少有人愿意等待他们的代码编译完成,除非他们故意想要避免工作)。

即使是以允许可定制性而闻名的 Gentoo(以安装时间为代价),也有预编译的二进制文件,可以安装更大的程序,如果你不想坐在那里等一个星期来编译你的代码。

还有交叉编译,这是为不同架构编译软件的行为。例如,我可能想要在我的x86_64虚拟机上为aarch64硬件编译htop,因为它有 32 个核心,而我的aarch64板是树莓派 3。

另请参阅...

根据使用的语言,还有其他编译软件的方法。例如,Go 将让您go get您想要编译的软件包源代码,但它使用make命令来执行实际的构建,而 Rust 编程语言使用一个叫做cargo的工具。

技术要求

在本节中,我们将使用我们的三台虚拟机,为每台虚拟机添加额外的存储库。这是为了展示不同的软件包管理系统如何以不同的方式处理事情。

添加额外的存储库

在创建系统时会安装默认存储库;还有更疯狂和更偏僻的存储库,可能包含您真正需要的软件(或者您自己不想编译的软件)。

一些常见的存储库如下:

  • EPEL

  • RPMfusion

  • Remi

  • ZFS,关于 Linux

在这里,我们将看看如何添加额外的存储库,以及这样做的后果。

准备工作

您可以按任何顺序查看本节,但从头开始,最后结束可能是明智的选择。

如何做...

登录到您的虚拟机。我们将从 CentOS 开始。首先,让我们使用yum repolist命令查看我们可以使用的默认存储库:

$ yum repolist
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirrors.melbourne.co.uk
 * extras: repo.uk.bigstepcloud.com
 * updates: centos.mirroring.pulsant.co.uk
repo id                                       repo name                                        status
base/7/x86_64                                 CentOS-7 - Base                                  9,911
extras/7/x86_64                               CentOS-7 - Extras                                  432
updates/7/x86_64                              CentOS-7 - Updates                               1,540
repolist: 11,883

我们看到默认启用了三个存储库,BaseExtrasUpdates

CentOS - 使用 epel-release 添加 EPEL 存储库

企业 Linux 的额外软件包EPEL)是 CentOS/Red Hat 空间中更受欢迎的额外存储库之一。

因此,它实际上有一个非常简单的安装方法,从给定的存储库:

$ sudo yum install epel-release -y

在我们的repo目录中查看,现在会看到两个新文件:

$ ls -lha /etc/yum.repos.d/epel*
-rw-r--r--. 1 root root  951 Oct  2  2017 /etc/yum.repos.d/epel.repo
-rw-r--r--. 1 root root 1.1K Oct  2  2017 /etc/yum.repos.d/epel-testing.repo

yum repolist也会显示它:

$ yum repolist
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
epel/x86_64/metalink                                                          |  31 kB  00:00:00 
 * base: mirrors.melbourne.co.uk
 * epel: anorien.csc.warwick.ac.uk
 * extras: mirrors.melbourne.co.uk
 * updates: anorien.csc.warwick.ac.uk
epel                                                                          | 3.2 kB  00:00:00 
(1/3): epel/x86_64/group_gz                                                   |  88 kB  00:00:00 
(2/3): epel/x86_64/updateinfo                                                 | 948 kB  00:00:00 
(3/3): epel/x86_64/primary                                                    | 3.6 MB  00:00:01 
epel                                                                                     12721/12721
repo id                         repo name                                                      status
base/7/x86_64                   CentOS-7 - Base                                                 9,911
epel/x86_64                     Extra Packages for Enterprise Linux 7 - x86_64                 12,721
extras/7/x86_64                 CentOS-7 - Extras                                                 432
updates/7/x86_64                CentOS-7 - Updates                                              1,540
repolist: 24,604

请注意,epel-testing没有列出;这是因为默认情况下它是禁用的。

我们可以使用这个存储库来搜索可能不在默认存储库中的软件包(例如htop):

$ yum whatprovides htop
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * base: mirrors.melbourne.co.uk
 * epel: mirror.bytemark.co.uk
 * extras: mirrors.melbourne.co.uk
 * updates: centos.mirroring.pulsant.co.uk
htop-2.2.0-1.el7.x86_64 : Interactive process viewer
Repo        : epel

CentOS - 通过文件添加 ELRepo 存储库

如前一节所建议的,软件包安装所做的只是添加适当的 GPG 密钥和用于额外存储库的 YUM 配置文件;没有什么能阻止你手动执行相同的操作。

ELRepo 是一个受欢迎的存储库,主要是因为它提供了更更新的 Linux 内核版本,适用于那些喜欢 CentOS 布局和风格但真正想要内核提供的最新驱动程序和功能的人。

首先,我们需要导入存储库的公钥,就像这样:

$ sudo rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org

确保您安装的密钥是合法的是个好主意;有各种方法可以做到这一点,包括检查 TLS 证书是否合法,与其他系统进行比较,或者打电话给密钥的所有者,直到他们把整个内容读给你为止。

此时,我们可以从elrepo站点下载并yum install rpm文件,或者我们自己下载并提取内容,以便查看它的内容:

$ wget https://www.elrepo.org/elrepo-release-7.0-3.el7.elrepo.noarch.rpm
$ rpm2cpio elrepo-release-7.0-3.el7.elrepo.noarch.rpm | cpio -id

rpm2cpio命令就像它的名字一样,允许我们使用cpio来提取存档。

如果我们现在cat刚刚解压缩的目录,我们可以看到它将放入我们系统的文件:

$ cat etc/yum.repos.d/elrepo.repo 
### Name: ELRepo.org Community Enterprise Linux Repository for el7
### URL: http://elrepo.org/

[elrepo]
name=ELRepo.org Community Enterprise Linux Repository - el7
baseurl=http://elrepo.org/linux/elrepo/el7/$basearch/
 http://mirrors.coreix.net/elrepo/elrepo/el7/$basearch/
 http://mirror.rackspace.com/elrepo/elrepo/el7/$basearch/
 http://repos.lax-noc.com/elrepo/elrepo/el7/$basearch/
 http://mirror.ventraip.net.au/elrepo/elrepo/el7/$basearch/
mirrorlist=http://mirrors.elrepo.org/mirrors-elrepo.el7
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-elrepo.org
protect=0
...

还有更多,因为此文件中有多个存储库指定。让我们只复制elrepo块并将其输出到我们自己制作的文件中,就像这样:

$ cat <<HERE | sudo tee /etc/yum.repos.d/elrepocustom.repo
[elrepo]
name=ELRepo.org Community Enterprise Linux Repository - el7
baseurl=http://elrepo.org/linux/elrepo/el7/$basearch/
 http://mirrors.coreix.net/elrepo/elrepo/el7/$basearch/
 http://mirror.rackspace.com/elrepo/elrepo/el7/$basearch/
 http://repos.lax-noc.com/elrepo/elrepo/el7/$basearch/
 http://mirror.ventraip.net.au/elrepo/elrepo/el7/$basearch/
mirrorlist=http://mirrors.elrepo.org/mirrors-elrepo.el7
enabled=1
gpgcheck=1
gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-elrepo.org
protect=0
HERE

请注意,该存储库已设置enabled=1,这意味着我们现在只需运行yum update来确保我们的系统与上游存储库同步并意识到它(尽管如果我们想永久禁用此存储库,我们可以将其更改为0yum将忽略它):

$ sudo yum update

现在,如果我们愿意,我们还可以列出我们刚刚添加的存储库中的所有软件包:

$ yum list available --disablerepo=* --enablerepo=elrepo 
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
 * elrepo: www.jules.fm
Available Packages
CAENVMELib.x86_64                              2.50-2.el7.elrepo                               elrepo
VirtualGL.x86_64                               2.3.3-4.el7.elrepo                              elrepo
VirtualGL-devel.x86_64                         2.3.3-4.el7.elrepo                              elrepo
VirtualGL-libs.i686                            2.3.3-4.el7.elrepo                              elrepo
bumblebee.x86_64                               3.2.1-10.el7.elrepo                             elrepo
bumblebee-selinux.x86_64                       1.0-1.el7.elrepo                                elrepo
drbd84-utils.x86_64                            9.3.1-1.el7.elrepo                              elrepo
...

Debian-添加额外的存储库

Debian 以其大量可供最终用户使用的软件包而闻名。如果你能想到一个软件包,很有可能它可以直接安装,或者某个地方的人正在努力维护这个软件包。

FreeBSD 可能是唯一一个在其基本安装中可能有更多软件包可用的操作系统。

由于这个著名的事实,你可能永远不需要安装额外的存储库,但是永远不要说永远(尽管我刚刚这样做了)。

一个寻找一些非官方存储库的好地方是维护的非官方页面wiki.debian.org/DebianRepository/Unofficial

在这里,我们可以找到各种存储库,包括一个用于谷歌浏览器的存储库,我们将添加它。

首先,我们将查看随图像一起提供的默认sources.list文件:

$ cat /etc/apt/sources.list
# 

# deb cdrom:[Debian GNU/Linux 9.4.0 _Stretch_ - Official amd64 NETINST 20180310-11:21]/ stretch main

#deb cdrom:[Debian GNU/Linux 9.4.0 _Stretch_ - Official amd64 NETINST 20180310-11:21]/ stretch main

deb http://deb.debian.org/debian stretch main
deb-src http://deb.debian.org/debian stretch main

deb http://security.debian.org/debian-security stretch/updates main
deb-src http://security.debian.org/debian-security stretch/updates main

看起来相当稀疏,只启用了stretch mainstretch/updates main存储库。

与 YUM 一样,我们需要确保我们有一个合法的 GPG 密钥;谷歌的安装方式如下:

$ wget https://dl.google.com/linux/linux_signing_key.pub
$ sudo apt-key add linux_signing_key.pub 
OK

现在,我们需要添加存储库-在这种情况下是谷歌浏览器:

$ cat <<HERE | sudo tee -a /etc/apt/sources.list
deb http://dl.google.com/linux/chrome/deb/ stable main
HERE

运行sudo apt update以确保您的可用软件包列表是最新的:

$ sudo apt update
Ign:1 http://deb.debian.org/debian stretch InRelease
Hit:2 http://deb.debian.org/debian stretch Release
Hit:4 http://security.debian.org/debian-security stretch/updates InRelease                          
Ign:5 http://dl.google.com/linux/chrome/deb stable InRelease
Get:6 http://dl.google.com/linux/chrome/deb stable Release [1,189 B]
Get:7 http://dl.google.com/linux/chrome/deb stable Release.gpg [819 B]
Get:8 http://dl.google.com/linux/chrome/deb stable/main amd64 Packages [1,381 B]
Fetched 3,389 B in 1s (2,285 B/s) 
Reading package lists... Done
Building dependency tree       
Reading state information... Done
11 packages can be upgraded. Run 'apt list --upgradable' to see them.

然后,搜索 Chrome:

$ sudo apt search google-chrome
Sorting... Done
Full Text Search... Done
google-chrome-beta/stable 70.0.3538.45-1 amd64
 The web browser from Google

google-chrome-stable/stable 69.0.3497.100-1 amd64
 The web browser from Google

google-chrome-unstable/stable 71.0.3569.0-1 amd64
 The web browser from Google

就这样!

这不是 Chrome 的广告,实际上,Chrome 的开源版本(Chromium)已经在默认存储库中可用。我可能会建议安装它。

大多数情况下,您可能会添加contrib存储库,其中包含非自由软件:

deb http://ftp.debian.org/debian stable main contrib non-free

Ubuntu-添加 PPA

有趣的是,这是 Ubuntu 和 Debian 世界之间的一个重要区别。在 Ubuntu 领域,有个人软件包存档PPA)的概念,可以用来安装第三方软件。

您也可以安装常规存储库,但是 PPA 可能更有针对性。请记住,几乎没有什么能阻止任何人创建 PPA,因此在添加任何内容之前,请务必进行尽职调查。

PPAs 可以在 Canonical 网站上搜索到,网址为launchpad.net/ubuntu/+ppas

我们将以 LibreOffice Fresh PPA 为例添加:

$ sudo add-apt-repository ppa:libreoffice/ppa

你可能会被提示接受额外的存储库,只需按下Enter键即可。

你刚刚添加的存储库配置位于apt sources.list.d目录中:

$ cat /etc/apt/sources.list.d/libreoffice-ubuntu-ppa-bionic.list 
deb http://ppa.launchpad.net/libreoffice/ppa/ubuntu bionic main
# deb-src http://ppa.launchpad.net/libreoffice/ppa/ubuntu bionic main

这意味着你现在可以安装最新版本的 LibreOffice!你终于成为了文字处理世界的酷孩子。

它是如何工作的...

存储库通常只是存放你可能想要安装的软件包的地方。它们并没有什么特别之处,因为它们通常只是像任何其他网页服务器一样,当你请求时为你提供内容(软件包)。

添加额外的存储库是一种相当常见的系统管理员活动,通常是因为你正在添加你的内部代理(目前通常是 Artifactory),或者你的开发人员确实需要最新版本的 NodeJS。

无论添加存储库的原因是什么,只要记住基本的安全措施很重要(毕竟,你是在信任上游没有任何恶意内容),并且要意识到如果存储库消失,你可能会给自己制造问题(这已经发生过,也将继续发生)。

总结 - 安全性、更新和软件包管理

很容易忘记更新。让系统达到稳定状态是令人欣慰的,无论它受到多大的冲击,它都会继续进行下去,做你告诉它要做的事情,而不会做其他事情。令人不安的是打破这种完美和平的想法,这就是更新的作用。

软件不会停滞不前;正在开发功能,正在修补安全漏洞,正在实施更严格的加密方法,所有这些都需要你这个系统管理员来考虑。

软件包维护者可以做很多事情,他们也确实做了,但你需要确保你正在更新的内容经过了测试,不会破坏你环境中的其他任何东西,并且那些开发人员如果曾经利用漏洞让他们的代码在你的平台上运行,他们已经受到了严厉的惩罚。

一天结束时,事情很可能会出错,但这就是为什么开发和测试环境应该存在的原因。

过去进行更新是令人紧张的,这就是为什么我们在周末和深夜进行更新,如果出了问题,没有人会注意到。这种故障仍然可能发生,但现在我们已经从中吸取了教训;我们有警告和勘误,公众会理解如果你的网站必须暂时下线几个小时,以确保他们的信用卡信息不会因为恶意利用而泄露。

一天结束时,软件是愚蠢的,它是由人类组成的,人类是会犯错的。保持系统更新,确保检查你的来源(换句话说,不要从互联网的偏僻角落安装随机可执行文件),并确保让你的上级知道,是的,你可能不得不让网站暂时下线一会儿,或者关闭他们的电话系统,但这总比第二天出现在 BBC 网站的头条新闻上要好。

当然,如果你真的对发行版是如何构建的、软件包是如何组合在一起的、以及它们为什么以这种方式构建或安装感兴趣,那么有一些工具可以帮助你学习。

Linux from Scratch就是这样一种工具,实际上是一本关于构建你自己版本的 Linux 的书。它不适合初学者,有时可能会令人沮丧(或者稍微过时,因为软件在不断发展),但这是一个了解事物为什么是这样的好方法,我鼓励每个人在他们的职业生涯中至少进行一次 Linux from scratch 安装。

第七章:监控和日志记录

本章将涵盖以下主题:

  • 阅读本地日志

  • systemd系统上使用journalctl

  • 集中日志记录

  • 本地资源测量工具

  • 本地监控工具

  • 远程监控工具

  • 使用 Elastic Stack 集中日志记录

介绍

服务器在正常工作时都很好,但我们生活在一个不完美的世界,问题是完全可能发生的(无论是通过人类创造的糟糕代码,还是通过人类引入的管理不善)。

理论上,简单安装您想要的程序,让其运行,并忘记它会很好,但这是现实世界,不是一切都百分之百正确的幻想世界。这就是日志记录和监控发挥作用的地方。

日志记录是为了当某些事情不可避免地出错时,您不必在尝试弄清楚出了什么问题时保持程序处于破碎状态(尽管在偶尔的情况下,这可能正是您必须做的事情;稍后再说)。您可以将系统恢复在线,并开始解析日志文件,以准确找出为什么您的 Web 服务器突然开始用小狗的图片替换网站上的所有图片。

监控是保持生活简单的次要组成部分。它可以确保在使用软件时您拥有流畅的体验,并且可以在系统资源分配上保持监视,监控可以在人类早上醒来之前就发现问题(实际上这经常发生;如果你值班,不要指望有正常的睡眠模式)。

这两个系统的结合可以让你在观察你的一举一动的人面前显得像神一样;就像 Morpheus 一样,你可以感觉到你的手表在会议中震动,通知你公司网站负载过重即将崩溃,并且可以镇定地告诉那些在你身边胡说八道的人你感到了一股力量的波动,然后离开去解决问题,让客户在注意到之前解决问题。

想想看,良好的日志记录和监控可能会导致你的工作看起来毫无意义——那句 Futurama 的名言是什么来着?

“当你做对了事情,人们就不会确定你做了什么。”-有意识的气体云

技术要求

在本章中,我们将使用两个 CentOS 框和两个 Debian 框,但是我们讨论的原则基本上是普遍适用的。

以下的Vagrantfile应该足以让你开始:

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

 config.vm.define "centos1" do |centos1|
 centos1.vm.box = "centos/7"
 centos1.vm.box_version = "1804.02"
 centos1.vm.network "private_network", ip: "192.168.33.10"
 centos1.vm.hostname = "centos1"
 centos1.vm.provider "virtualbox" do |centos1p|
 centos1p.memory = 2048
 centos1p.cpus = 2
 end
 end

 config.vm.define "centos2" do |centos2|
 centos2.vm.box = "centos/7"
 centos2.vm.box_version = "1804.02"
 centos2.vm.network "private_network", ip: "192.168.33.11"
 centos2.vm.hostname = "centos2"
 end

 config.vm.define "debian1" do |debian1|
 debian1.vm.box = "debian/stretch64"
 debian1.vm.box_version = "9.5.0"
 debian1.vm.network "private_network", ip: "192.168.33.12"
 debian1.vm.hostname = "debian1"
 debian1.vm.provider "virtualbox" do |debian1p|
 debian1p.cpus = 1
 end
 end

 config.vm.define "debian2" do |debian2|
 debian2.vm.box = "debian/stretch64"
 debian2.vm.box_version = "9.5.0"
 debian2.vm.network "private_network", ip: "192.168.33.13"
 debian2.vm.hostname = "debian2"
 debian2.vm.provider "virtualbox" do |debian2p|
 debian2p.cpus = 1
 end
 end

end

阅读本地日志

在本节中,我们将看一下我们机器上的默认日志位置。

日志记录很棒——它可以告诉您系统的健康状况如何,它有多忙,谁试图攻击它,以及在过去几分钟内谁成功获得了访问权限。

这些天它相当标准化了,除非您正在使用 Java 应用程序,如果您有耐心阅读日志文件,您可能会想在之后试着读一下《战争与和平》。

做好准备

登录到你的centos1虚拟机:

$ vagrant ssh centos1

如何做到…

层次结构手册告诉我们,如果我们想要找到杂项日志文件,我们应该从/var/log/开始查找:

/var/log

杂项日志文件。

导航到/var/log并列出其内容会显示我们的情况:

$ cd /var/log
$ ls -l
total 156
drwxr-xr-x. 2 root   root      219 May 12 18:55 anaconda
drwx------. 2 root   root       23 Oct  9 16:55 audit
-rw-------. 1 root   root        6 Oct  9 16:55 boot.log
-rw-------. 1 root   utmp        0 May 12 18:51 btmp
drwxr-xr-x. 2 chrony chrony      6 Apr 12 17:37 chrony
-rw-------. 1 root   root      807 Oct  9 17:01 cron
-rw-r--r--. 1 root   root    28601 Oct  9 16:55 dmesg
-rw-r--r--. 1 root   root      193 May 12 18:51 grubby_prune_debug
-rw-r--r--. 1 root   root   292292 Oct  9 17:21 lastlog
-rw-------. 1 root   root      198 Oct  9 16:55 maillog
-rw-------. 1 root   root    91769 Oct  9 17:23 messages
drwxr-xr-x. 2 root   root        6 Aug  4  2017 qemu-ga
drwxr-xr-x. 2 root   root        6 May 12 18:55 rhsm
-rw-------. 1 root   root     2925 Oct  9 17:21 secure
-rw-------. 1 root   root        0 May 12 18:52 spooler
-rw-------. 1 root   root        0 May 12 18:50 tallylog
drwxr-xr-x. 2 root   root       23 Oct  9 16:55 tuned
-rw-rw-r--. 1 root   utmp     1920 Oct  9 17:21 wtmp

在 CentOS 系统上,主要的日志文件是messages日志;在 Debian 和 Ubuntu 下,这被称为syslog,但实质上是一样的。

查看这个日志文件的最后几行应该会显示你系统上运行的一些程序的各种输出:

$ sudo tail -10 messages
Dec 28 18:37:18 localhost nm-dispatcher: req:11 'connectivity-change': start running ordered scripts...
Dec 28 18:37:22 localhost systemd-logind: New session 3 of user vagrant.
Dec 28 18:37:22 localhost systemd: Started Session 3 of user vagrant.
Dec 28 18:37:22 localhost systemd: Starting Session 3 of user vagrant.
Dec 28 18:38:13 localhost chronyd[567]: Selected source 95.215.175.2
Dec 28 18:39:18 localhost chronyd[567]: Selected source 178.79.155.116
Dec 28 18:39:35 localhost systemd-logind: Removed session 2.
Dec 28 18:39:46 localhost systemd: Started Session 4 of user vagrant.
Dec 28 18:39:46 localhost systemd-logind: New session 4 of user vagrant.
Dec 28 18:39:46 localhost systemd: Starting Session 4 of user vagrant.

在这里,我们可以看到chronyd有点抱怨,我们还可以看到我登录的时刻,systemd很好地为我创建了一个会话。

您还可以查看安全日志,例如sshdsudoPAM

$ sudo tail -5 secure
Dec 28 18:39:46 localhost sshd[3379]: Accepted publickey for vagrant from 10.0.2.2 port 44394 ssh2: RSA SHA256:7EOuFLwMurYJNPkZ3e+rZvez1FxmGD9ZNpEq6H+wmSA
Dec 28 18:39:46 localhost sshd[3379]: pam_unix(sshd:session): session opened for user vagrant by (uid=0)
Dec 28 18:39:55 localhost sudo: vagrant : TTY=pts/0 ; PWD=/var/log ; USER=root ; COMMAND=/bin/tail -10 messages
Dec 28 18:40:19 localhost sudo: vagrant : TTY=pts/0 ; PWD=/var/log ; USER=root ; COMMAND=/bin/tail -10 secure
Dec 28 18:40:37 localhost sudo: vagrant : TTY=pts/0 ; PWD=/var/log ; USER=root ; COMMAND=/bin/tail -5 secure

以及像croncron日志这样的文件:

$ sudo cat cron
Dec 28 18:36:57 localhost crond[612]: (CRON) INFO (RANDOM_DELAY will be scaled with factor 89% if used.)
Dec 28 18:36:58 localhost crond[612]: (CRON) INFO (running with inotify support)

由于这些文件只是文本,您可以使用您手头的任何标准工具来操作它们。

我可能想要从消息日志中grepvagrant的提及,然后只打印月份、时间戳和进行日志记录的程序:

$ sudo grep "vagrant." messages | cut -d" " -f 1,3,5
Dec 18:37:07 systemd:
Dec 18:37:07 systemd:
Dec 18:37:07 systemd:
Dec 18:37:07 systemd-logind:
Dec 18:37:07 systemd:

我不知道为什么你要这样做,但人们有奇怪的爱好。

为了避免日志文件变得太大并成为真正的痛苦,甚至打开(实际上,一百万行太多了),我们还有logrotate,它定期运行以交换旧文件以便写入新文件。

在这里,我强制logrotate运行,这样我们就可以看到输出:

$ sudo logrotate -f /etc/logrotate.conf
$ ls -lh
total 168K
drwxr-xr-x. 2 root root 219 May 12 18:55 anaconda
drwx------. 2 root root 23 Oct 9 16:55 audit
-rw-------. 1 root root 0 Oct 9 17:35 boot.log
-rw-------. 1 root root 6 Oct 9 17:35 boot.log-20181009
-rw-------. 1 root utmp 0 Oct 9 17:35 btmp
-rw-------. 1 root utmp 0 May 12 18:51 btmp-20181009
drwxr-xr-x. 2 chrony chrony 6 Apr 12 17:37 chrony
-rw-------. 1 root root 0 Oct 9 17:35 cron
-rw-------. 1 root root 807 Oct 9 17:01 cron-20181009
-rw-r--r--. 1 root root 28K Oct 9 16:55 dmesg
-rw-r--r--. 1 root root 193 May 12 18:51 grubby_prune_debug
-rw-r--r--. 1 root root 286K Oct 9 17:28 lastlog
-rw-------. 1 root root 0 Oct 9 17:35 maillog
-rw-------. 1 root root 198 Oct 9 16:55 maillog-20181009
-rw-------. 1 root root 145 Oct 9 17:35 messages
-rw-------. 1 root root 90K Oct 9 17:28 messages-20181009
drwxr-xr-x. 2 root root 6 Aug 4 2017 qemu-ga
drwxr-xr-x. 2 root root 6 May 12 18:55 rhsm
-rw-------. 1 root root 0 Oct 9 17:35 secure
-rw-------. 1 root root 5.3K Oct 9 17:35 secure-20181009
-rw-------. 1 root root 0 Oct 9 17:35 spooler
-rw-------. 1 root root 0 May 12 18:52 spooler-20181009
-rw-------. 1 root root 0 May 12 18:50 tallylog
drwxr-xr-x. 2 root root 23 Oct 9 16:55 tuned
-rw-rw-r--. 1 root utmp 0 Oct 9 17:35 wtmp
-rw-rw-r--. 1 root utmp 1.9K Oct 9 17:21 wtmp-20181009

请注意旧文件已经被移动并加上了日期戳,新文件已经被赋予了相同的名称。

现在在消息文件上使用cat将显示一行,告诉我们rsyslogd守护进程被HUPed

$ sudo cat messages
Dec 28 18:41:38 centos1 rsyslogd: [origin software="rsyslogd" swVersion="8.24.0" x-pid="898" x-info="http://www.rsyslog.com"] rsyslogd was HUPed

就我个人而言,我认为它应该被 HUP'd,但我可以理解HUPed的论点。

它是如何工作的...

记录到文本文件的守护进程是rsyslogd(在一些较旧的系统上,可能是syslog-ng)。

这个可靠且扩展的 syslogd 程序会将它从两个位置之一读取的消息写入,即imuxsock(旧的)和imjournal(新的);这直接来自于syslog(3)系统调用手册页:

syslog()和 vsyslog()

syslog()生成一个日志消息,该消息将由 syslogd(8)分发。

请注意,syslogd(在这里引用)是一个旧程序,已被rsyslogd取代。

如果有多个相同名称的条目,在 man 手册中,您可以使用数字指定部分。在这种情况下,它将是命令行上的man 3 syslog

rsyslogd配置位于/etc/rsyslog.conf,并为我们提供了有关特定日志写入位置的第一部分信息。这是RULES部分:

#### RULES ####

# Log all kernel messages to the console.
# Logging much else clutters up the screen.
#kern.*                                                 /dev/console

# Log anything (except mail) of level info or higher.
# Don't log private authentication messages!
*.info;mail.none;authpriv.none;cron.none                /var/log/messages

# The authpriv file has restricted access.
authpriv.*                                              /var/log/secure

# Log all the mail messages in one place.
mail.*                                                  -/var/log/maillog

# Log cron stuff
cron.*                                                  /var/log/cron

# Everybody gets emergency messages
*.emerg                                                 :omusrmsg:*

# Save news errors of level crit and higher in a special file.
uucp,news.crit                                          /var/log/spooler

# Save boot messages also to boot.log
local7.*                                                /var/log/boot.log

这向我们展示了在特定消息到达日志位置时应用的各种规则:如果它们是邮件消息,它们将进入/var/log/maillog;如果它们不是邮件(高于info级别)的任何消息、authprivcron,它们将进入/var/log/messages

logger命令可用于直接写入日志,并且对于测试目的和 shell 脚本非常有用,以展示它是如何工作的:

$ logger "So long, and thanks for all the fish." 
$ sudo tail -1 /var/log/messages
Oct 9 18:03:03 centos1 vagrant: So long, and thanks for all the fish.

logger命令还允许您指定设施和日志级别:

$ logger -p cron.err "I'M A PARADOXICAL LOG MESSAGE."
$ sudo tail -3 /var/log/cron
Oct  9 18:07:01 centos1 anacron[3373]: Job `cron.weekly' started
Oct  9 18:07:01 centos1 anacron[3373]: Job `cron.weekly' terminated
Oct  9 18:07:23 centos1 vagrant: I'M A PARADOXICAL LOG MESSAGE.

这似乎很吵,所以让我们为cron错误消息创建一个专用的日志文件。

我们需要一个规则,放在rsyslog.d目录中处理这样的事情:

$ cat <<HERE | sudo tee /etc/rsyslog.d/cronerr.conf
cron.err                                                  /var/log/cron.err
HERE

现在,我们重新启动rsyslog,并再次发送我们的logger消息:

$ sudo systemctl restart rsyslog
$ logger -p cron.err "I'M A PARADOXICAL LOG MESSAGE."
$ sudo cat /var/log/cron.err 
Oct  9 18:17:32 centos1 vagrant: I'M A PARADOXICAL LOG MESSAGE.

这样更清晰;我们的自定义规则看起来不错!

日志级别是以下之一,对于何时使用不同级别只有宽泛的定义指南,尽管通常认为不应将琐碎事件记录为关键问题是礼貌的:

0              emerg

1              alert

2              crit

3              err

4              warning

5              notice

6              info

7              debug

这些数字是各个级别的数字指定。

还有更多...

当我们旋转日志并HUPed``syslog守护程序时,实际上在logrotate中运行了这个脚本:

$ cat /etc/logrotate.d/syslog 
/var/log/cron
/var/log/maillog
/var/log/messages
/var/log/secure
/var/log/spooler
{
 missingok
 sharedscripts
 postrotate
 /bin/kill -HUP `cat /var/run/syslogd.pid 2> /dev/null` 2> /dev/null || true
 endscript
}

当然,一个应用程序没有理由使用syslog(3)调用来记录消息,它也可以轻松地将一系列文本写入/tmp,但这完全取决于相关的应用程序开发人员。

作为管理员,你需要知道的是,大多数日志可能最终以文本格式出现在/var/log中,你通常可以根据程序的具体情况配置日志文件位置。

愉快的记录!

在 systemd 系统上使用 journalctl

现代 Linux 发行版不仅仅依赖于syslog文件;事实上,它们根本不需要依赖于syslog。Debian、Ubuntu 和 CentOS 都将systemd作为初始化系统,并且与systemd捆绑在一起的是一个名为journald的服务(systemd-journald.service)。

该服务作为系统的日志记录解决方案,并利用二进制日志而不是基于文本的日志。

虽然完全可以忽略syslog,只使用journald,但现在很多系统都同时使用两者,以便更容易地从一种格式过渡到另一种格式。如果你使用的是像 Arch 或 Gentoo 这样的系统,你可能决定完全放弃syslog解决方案,而只使用journald

准备工作

对于这一部分,我们可以使用第一部分的Vagrantfile

我们只会使用centos1

SSH 到centos1

$ vagrant ssh centos1

如何做...

如前所述,journald 使用二进制日志格式,这意味着它不能用传统的文本解析器和编辑器打开。相反,我们使用journalctl命令来读取日志。

只需运行以下命令即可打开你的日志:

$ sudo journalctl

上述命令的输出如下所示:

这对于任何看过普通旧syslog文件的人来说都是熟悉的;请注意,默认情况下格式是相同的。

尽管如此,它还是相当吵闹的,在一个繁忙的系统上,我们可能不想看到所有的历史记录。

也许我们只想在写入时观看日志?如果是这样,我们可以使用-f跟踪它:

$ sudo journalctl -f
-- Logs begin at Tue 2018-10-09 18:43:07 UTC. --
Oct 10 17:07:03 centos1 chronyd[554]: System clock was stepped by 80625.148375 seconds
Oct 10 17:07:03 centos1 systemd[1]: Time has been changed
Oct 10 17:07:25 centos1 sshd[1106]: Accepted publickey for vagrant from 10.0.2.2 port 55300 ssh2: RSA SHA256:TTGYuhFa756sxR2rbliMhNqgbggAjFNERKg9htsdvSw
Oct 10 17:07:26 centos1 systemd[1]: Created slice User Slice of vagrant.
Oct 10 17:07:26 centos1 systemd[1]: Starting User Slice of vagrant.
Oct 10 17:07:26 centos1 systemd[1]: Started Session 1 of user vagrant.
Oct 10 17:07:26 centos1 systemd-logind[545]: New session 1 of user vagrant.
Oct 10 17:07:26 centos1 sshd[1106]: pam_unix(sshd:session): session opened for user vagrant by (uid=0)
Oct 10 17:07:26 centos1 systemd[1]: Starting Session 1 of user vagrant.
Oct 10 17:07:28 centos1 sudo[1131]:  vagrant : TTY=pts/0 ; PWD=/home/vagrant ; USER=root ; COMMAND=/bin/journalctl -f

每当写入日志消息时,它都会出现在你面前,流式传输(使用Ctrl + C退出)。

我们可以使用-k标志来具体查看kernel日志(就像我们在运行dmesg一样):

$ sudo journalctl -k --no-pager | head -n8
-- Logs begin at Tue 2018-10-09 18:43:07 UTC, end at Wed 2018-10-10 17:09:08 UTC. --
Oct 09 18:43:07 localhost.localdomain kernel: Initializing cgroup subsys cpuset
Oct 09 18:43:07 localhost.localdomain kernel: Initializing cgroup subsys cpu
Oct 09 18:43:07 localhost.localdomain kernel: Initializing cgroup subsys cpuacct
Oct 09 18:43:07 localhost.localdomain kernel: Linux version 3.10.0-862.2.3.el7.x86_64 (builder@kbuilder.dev.centos.org) (gcc version 4.8.5 20150623 (Red Hat 4.8.5-28) (GCC) ) #1 SMP Wed May 9 18:05:47 UTC 2018
Oct 09 18:43:07 localhost.localdomain kernel: Command line: BOOT_IMAGE=/vmlinuz-3.10.0-862.2.3.el7.x86_64 root=/dev/mapper/VolGroup00-LogVol00 ro no_timer_check console=tty0 console=ttyS0,115200n8 net.ifnames=0 biosdevname=0 elevator=noop crashkernel=auto rd.lvm.lv=VolGroup00/LogVol00 rd.lvm.lv=VolGroup00/LogVol01 rhgb quiet
Oct 09 18:43:07 localhost.localdomain kernel: e820: BIOS-provided physical RAM map:
Oct 09 18:43:07 localhost.localdomain kernel: BIOS-e820: [mem 0x0000000000000000-0x000000000009fbff] usable

请注意,这里我已经禁用了分页器(以与systemctl-land中相同的方式),并且我只转储了日志的前八行,因为kernel非常吵闹,特别是在启动时。

这也表明日志仍然可以在命令行上操作;你只需要先查询它们(增加一些开销)。

这并不是说你必须使用journalctl和其他命令的组合;你很有可能只用journalctl就能得到你需要的东西。在这里,我选择了一个非常具体的时间范围来查询日志:

$ sudo journalctl --since=17:07 --until=17:09
-- Logs begin at Tue 2018-10-09 18:43:07 UTC, end at Wed 2018-10-10 17:12:51 UTC. --
Oct 09 18:43:18 centos1 chronyd[554]: Selected source 188.114.116.1
Oct 09 18:43:18 centos1 chronyd[554]: System clock wrong by 80625.148375 seconds, adjustment started
Oct 10 17:07:03 centos1 chronyd[554]: System clock was stepped by 80625.148375 seconds
Oct 10 17:07:03 centos1 systemd[1]: Time has been changed
Oct 10 17:07:25 centos1 sshd[1106]: Accepted publickey for vagrant from 10.0.2.2 port 55300 ssh2: RSA SHA256:TTGYuhFa756sxR2rbliMhNqgbggAjFNERKg9htsdvSw
Oct 10 17:07:26 centos1 systemd[1]: Created slice User Slice of vagrant.
Oct 10 17:07:26 centos1 systemd[1]: Starting User Slice of vagrant.
Oct 10 17:07:26 centos1 systemd[1]: Started Session 1 of user vagrant.
Oct 10 17:07:26 centos1 systemd-logind[545]: New session 1 of user vagrant.
Oct 10 17:07:26 centos1 sshd[1106]: pam_unix(sshd:session): session opened for user vagrant by (uid=0)
Oct 10 17:07:26 centos1 systemd[1]: Starting Session 1 of user vagrant.
Oct 10 17:07:28 centos1 sudo[1131]:  vagrant : TTY=pts/0 ; PWD=/home/vagrant ; USER=root ; COMMAND=/bin/journalctl -f
Oct 10 17:08:08 centos1 chronyd[554]: Selected source 194.80.204.184
Oct 10 17:08:41 centos1 sudo[1145]:  vagrant : TTY=pts/0 ; PWD=/home/vagrant ; USER=root ; COMMAND=/bin/journalctl -k
Oct 10 17:08:52 centos1 sudo[1148]:  vagrant : TTY=pts/0 ; PWD=/home/vagrant ; USER=root ; COMMAND=/bin/journalctl -k

在 2 分钟内,我们得到了 15 行日志,但这样更容易筛选和消化(当然,假设你的盒子上的时间是正确的!)

这些时间戳只是示例;你可以使用完整的日期(--since="2018-10-10 17:07:00")或者甚至是相对语句(--since=yesterday --until=now)。

如果你不是在寻找一个时间范围,而是特定的systemd unit的日志,journalctl也可以满足你:

$ sudo journalctl -u chronyd
-- Logs begin at Tue 2018-10-09 18:43:07 UTC, end at Wed 2018-10-10 17:18:58 UTC. --
Oct 09 18:43:09 centos1 systemd[1]: Starting NTP client/server...
Oct 09 18:43:09 centos1 chronyd[554]: chronyd version 3.2 starting (+CMDMON +NTP +REFCLOCK +RTC +PRIVDROP +SCFILTER +SECHASH +SIGND +ASYNCDNS +IPV6 +DEBUG)
Oct 09 18:43:09 centos1 chronyd[554]: Frequency -3.308 +/- 6.027 ppm read from /var/lib/chrony/drift
Oct 09 18:43:09 centos1 systemd[1]: Started NTP client/server.
Oct 09 18:43:18 centos1 chronyd[554]: Selected source 188.114.116.1
Oct 09 18:43:18 centos1 chronyd[554]: System clock wrong by 80625.148375 seconds, adjustment started
Oct 10 17:07:03 centos1 chronyd[554]: System clock was stepped by 80625.148375 seconds
Oct 10 17:08:08 centos1 chronyd[554]: Selected source 194.80.204.184

在这里,我使用了-u(unit)标志,只查看了来自chronyd的日志,最大程度地减少了我需要处理的输出量。

在上面的示例中,我们还得到了与chronyd单元交互的systemd日志。但如果我们只想要来自chronyd二进制的日志,我们也可以做到。

$ sudo journalctl /usr/sbin/chronyd
-- Logs begin at Tue 2018-10-09 18:43:07 UTC, end at Wed 2018-10-10 17:21:08 UTC. --
Oct 09 18:43:09 centos1 chronyd[554]: chronyd version 3.2 starting (+CMDMON +NTP +REFCLOCK +RTC +PRIVDROP +SCFILTER +SECHASH +SIGND +ASYNCDNS +IPV6 +DEBUG)
Oct 09 18:43:09 centos1 chronyd[554]: Frequency -3.308 +/- 6.027 ppm read from /var/lib/chrony/drift
Oct 09 18:43:18 centos1 chronyd[554]: Selected source 188.114.116.1
Oct 09 18:43:18 centos1 chronyd[554]: System clock wrong by 80625.148375 seconds, adjustment started
Oct 10 17:07:03 centos1 chronyd[554]: System clock was stepped by 80625.148375 seconds
Oct 10 17:08:08 centos1 chronyd[554]: Selected source 194.80.204.184

说真的,这太酷了吧?

但等等,还有更多!

journald命令可能更加强大,因为它有消息解释的概念,或者如果你愿意的话,消息上下文。一些日志行可以以更详细的方式输出(使用-x)以更好地理解发生了什么。

使用sshd单元的以下两个示例,一个带有-x标志,一个不带:

$ sudo journalctl -u sshd
-- Logs begin at Tue 2018-10-09 18:43:07 UTC, end at Wed 2018-10-10 17:25:46 UTC. --
Oct 09 18:43:14 centos1 systemd[1]: Starting OpenSSH server daemon...
Oct 09 18:43:14 centos1 sshd[853]: Server listening on 0.0.0.0 port 22.
Oct 09 18:43:14 centos1 sshd[853]: Server listening on :: port 22.
Oct 09 18:43:14 centos1 systemd[1]: Started OpenSSH server daemon.
Oct 10 17:07:25 centos1 sshd[1106]: Accepted publickey for vagrant from 10.0.2.2 port 55300 ssh2: RSA SHA256:TTGYuhFa756sxR2rbliMhNqgbggAjFNERKg9htsdvSw
$ sudo journalctl -u sshd -x
-- Logs begin at Tue 2018-10-09 18:43:07 UTC, end at Wed 2018-10-10 17:26:04 UTC. --
Oct 09 18:43:14 centos1 systemd[1]: Starting OpenSSH server daemon...
-- Subject: Unit sshd.service has begun start-up
-- Defined-By: systemd
-- Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
-- 
-- Unit sshd.service has begun starting up.
Oct 09 18:43:14 centos1 sshd[853]: Server listening on 0.0.0.0 port 22.
Oct 09 18:43:14 centos1 sshd[853]: Server listening on :: port 22.
Oct 09 18:43:14 centos1 systemd[1]: Started OpenSSH server daemon.
-- Subject: Unit sshd.service has finished start-up
-- Defined-By: systemd
-- Support: http://lists.freedesktop.org/mailman/listinfo/systemd-devel
-- 
-- Unit sshd.service has finished starting up.
-- 
-- The start-up result is done.
Oct 10 17:07:25 centos1 sshd[1106]: Accepted publickey for vagrant from 10.0.2.2 port 55300 ssh2: RSA SHA256:TTGYuhFa756sxR2rbliMhNqgbggAjFNERKg9htsdvSw

请注意,systemd特定的行突然有了更多的上下文。

我们已经涵盖了一些基础知识,但journalctl仍然可以更加复杂。在将选项传递给输出后,我们可以在我们的语句中添加特定的匹配(格式为FIELD=VALUE)。

看看 SSH,我们可以看到它在起作用:

$ sudo journalctl --since=yesterday _SYSTEMD_UNIT=sshd.service _PID=853 
-- Logs begin at Tue 2018-10-09 18:43:07 UTC, end at Wed 2018-10-10 17:31:48 UTC. --
Oct 09 18:43:14 centos1 sshd[853]: Server listening on 0.0.0.0 port 22.
Oct 09 18:43:14 centos1 sshd[853]: Server listening on :: port 22.

在这里,我们说我们只想要昨天生成的systemd sshd单元的所有消息,但只有来自 PID 853的消息(这恰好是这个盒子上的服务器守护程序 PID)。

有关匹配的更多信息,请参阅systemd.journal-fields手册页。

最后,与syslog一样,我们可以指定我们想要看到的消息的优先级。在这里,我们正在查看整个日志,但我们只对err级别的消息感兴趣:

$ sudo journalctl -p err
-- Logs begin at Tue 2018-10-09 18:43:07 UTC, end at Wed 2018-10-10 17:55:48 UTC. --
Oct 09 18:43:09 centos1 systemd[504]: Failed at step STDIN spawning /usr/libexec/selinux/selinux-policy-migrate-local-changes.sh: Inappropriate ioctl for device
Oct 09 18:43:09 centos1 systemd[1]: Failed to start Migrate local SELinux policy changes from the old store structure to the new structure.
Oct 09 18:43:14 centos1 rsyslogd[857]: imjournal: loaded invalid cursor, seeking to the head of journal  [v8.24.0 try http://www.rsyslog.com/e/2027 ]

它是如何工作的...

/etc/systemd/journald.conf中配置,journald 是一个很棒的软件,但至少在 CentOS 7 上,它在某种程度上是一个二等公民,因为对于很多人来说,syslog 仍然是保持日志监视的主要方法。

日志不会在重新启动后保留(稍后会详细介绍),因此它只适用于查询系统自启动后的状态(这通常足够了,十次中有九次)。

正如我们所说,该文件是以二进制格式存在的,journald 会引入各种来源来创建其日志记录:

  • 内核日志消息(/dev/kmsg

  • 简单的日志消息(先前提到的 syslog libc 调用)

  • 从 Journal API 导入的结构化日志消息(使用 imjournal 模块导入到 rsyslog 中)

  • 服务单元文件的 stdoutstderr

  • 来自内核审计子系统的审计记录

syslog 一样,这意味着 logger 可以用来显示我们仍然可以手动填充日志,这里只显示从 syslog 传输机制接收到的消息:

$ logger -p cron.err "I'M ANOTHER PARADOXICAL LOG MESSAGE."
$ sudo journalctl -p err _TRANSPORT=syslog --since 18:00
-- Logs begin at Tue 2018-10-09 18:43:07 UTC, end at Wed 2018-10-10 18:11:15 UTC. --
Oct 10 18:08:18 centos1 vagrant[1736]: I'M ANOTHER PARADOXICAL LOG MESSAGE.

在未来,syslog 很有可能被淘汰,journald 将成为新的标准,但考虑到 syslog 作为一个概念存在了很长时间,这种情况在成为现实之前还需要很长时间。

journald 以二进制方式记录日志是许多传统主义者争论的焦点,但就像哥伦布一样,它并不是第一个出现在舞台上的,只是得到了所有的关注。那些曾经使用过 OpenBSD 及其防火墙 pf 的人,也许会觉得二进制日志记录是一种安慰。

还有更多...

需要注意的一件事是日志记录将使用的空间。大小限制由 journald.conf 中的选项管理。

选项 SystemMaxUseRuntimeMaxUse 管理日志记录可以使用的最大磁盘空间;这些默认为文件系统大小的 10%,上限为 4GB。

SystemKeepFreeRuntimeKeepFree 选项管理 journald 为其他用途保留多少磁盘空间;这些默认为文件系统大小的 15%,上限也为 4GB。

有各种情况来管理大小,但基本上,journald 将尽最大努力不成为文件系统填满的原因,正是这种对细节的关注使我喜欢它。

另请参阅

在我们的 CentOS 系统上,日志文件是瞬时的,在重新启动时会丢失。在它存在的时候,它存在于 /run/log/journal/ 中。

$ ls /run/log/journal/
4eabd6271dbf4ed0bc608378f4311df8
$ sudo ls -lh /run/log/journal/4eabd6271dbf4ed0bc608378f4311df8/
total 4.0M
-rwxr-x---+ 1 root systemd-journal 4.0M Oct  9 18:43 system.journal

我们实际上可以通过为日志创建一个专门的 /var/log/ 目录,并使用一行命令更改权限来相当容易地改变这种行为:sudo systemd-tmpfiles --create --prefix /var/log/journal

您还可以从命令行列出 journalctl 知道的引导:

$ sudo journalctl --list-boots
 0 b4c3669c7a9841ba841c330a75125e35 Tue 2018-10-09 18:43:07 UTC—Wed 2018-10-10 18:13:27 UTC

日志集中

您不想登录到您的每个盒子来检查日志,您就是不想。在云和自动配置基础设施的时代,这比它值得的要麻烦得多,这是将日志集中在一个(冗余)位置的一个很好的理由。

作为数据,我们的日志可以相对容易地被操纵和移动。 rsyslogjournald 都有这样的能力,在这一部分,我们将把我们的日志流到各个地方,展示这样做有多么有用。

我们在这里涵盖的所有内容在各自的程序中都是可以实现的;这与一些由软件提供的集中式日志记录解决方案不同,例如 Elastic Stack。

为了这些示例的目的,我们没有使用 TLS,这意味着日志将以纯文本格式流式传输。我建议在生产中不要这样做,除非投资于 HTTPS 设置或隧道解决方案。

准备工作

对于本节,我们可以使用第一节的 Vagrantfile

我们将使用 centos1centos2 进行第一部分,然后使用 debian1debian2 进行第二部分,从一个盒子发送日志到另一个盒子。

打开两个终端并连接到 centos1centos2;在两个盒子上安装 tcpdump

$ vagrant ssh centos1
$ sudo yum install tcpdump -y

$ vagrant ssh centos2
$ sudo yum install tcpdump -y

对于第二部分(journald),连接到 debian1debian2,并在两个盒子上安装 tcpdumpsystemd-journal-remote

$ vagrant ssh debian1
$ sudo apt install tcpdump systemd-journal-remote -y

$ vagrant ssh debian2
$ sudo apt install tcpdump systemd-journal-remote -y

如何做...

我们将依次介绍两个日志守护程序,首先是rsyslog,然后再用journald做同样的基本操作。

使用 rsyslog 进行远程日志记录-UDP 示例

为了使rsyslog能够远程记录到一台机器,你需要在客户端上启用流到远程位置的接收,并在服务器上启用接收。

对于这个,centos1将是我们的客户端,centos2将是我们的服务器。

首先在centos1上:

$ sudo sed -i 's/#*.* @@remote-host:514/*.* @192.168.33.11/g' /etc/rsyslog.conf
$ sudo systemctl restart rsyslog

现在在centos2上:

$ sudo sed -i 's/#$ModLoad imudp/$ModLoad imudp/g' /etc/rsyslog.conf
$ sudo sed -i 's/#$UDPServerRun 514/$UDPServerRun 514/g' /etc/rsyslog.conf
$ sudo systemctl restart rsyslog

我们可以立即通过在我们的centos2 VM 上使用tcpdump来检查这是否有效;使用以下命令启动它:

$ sudo tcpdump port 514 -i eth1

现在,在centos1上生成一个要发送的消息;在这里,我们伪造了一个syslog.info消息:

$ logger -p syslog.info "I'm a regular info message."

centos2上,你应该看到类似以下的东西:

当然,在我们的日志行最终到达的/var/log/messages文件中,你会看到以下内容:

$ sudo tail -3 /var/log/messages
Oct 11 18:35:45 centos2 kernel: device eth1 entered promiscuous mode
Oct 11 18:35:48 centos1 vagrant: I'm a regular info message.
Oct 11 18:36:23 centos2 kernel: device eth1 left promiscuous mode

在这里,我们还可以看到tcpdump通过tcpdumpeth1置于混杂模式中,我们传递syslog消息之前和之后。

使用 rsyslog 进行远程日志记录-TCP 示例

前面的例子涵盖了 UDP,这只是一种没有确认服务器是否接收到噪音的信息流。通过 TCP 连接,syslog服务器相互通信首先建立连接。

在你的centos1机器上,用你的目标地址中的单个@符号替换为两个@@符号:

$ sudo sed -i 's/*.* @192.168.33.11/*.* @@192.168.33.11/g' /etc/rsyslog.conf
$ sudo systemctl restart rsyslog

我们的客户端现在已经设置好,但在建立连接之前无法发送日志。

centos2上,让我们将rsyslog服务器设置为接收 TCP 连接和数据:

$ sudo sed -i 's/#$ModLoad imtcp/$ModLoad imtcp/g' /etc/rsyslog.conf
$ sudo sed -i 's/#$InputTCPServerRun 514/$InputTCPServerRun 514/g' /etc/rsyslog.conf
$ sudo systemctl restart rsyslog

一个rsyslog服务器可以同时监听 UDP 和 TCP。

让我们来测试一下!在centos2上,再次设置你的tcpdump

$ sudo tcpdump port 514 -i eth1

并从centos1发送一个日志消息:

$ logger -p syslog.err "I'm a confusing error message."

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

而且,你的消息文件应该有新的警报:

$ sudo tail -3 /var/log/messages
Oct 11 18:39:09 centos1 vagrant: I'm a confusing error message.
Oct 11 18:39:15 centos2 kernel: device eth1 left promiscuous mode
Oct 11 18:39:27 centos1 systemd-logind: Removed session 3.

使用 journald 进行远程日志记录

systemd-journal-remote命令允许你通过网络接收日志消息。不幸的是,这是systemd套件的一个相当新的添加,目前还不在 CentOS 系统上可用。

在你的第一个 Debian 系统(debian1)上,设置你的远程上传位置:

$ sudo sed -i 's/# URL=/URL=http:\/\/192.168.33.13/g' /etc/systemd/journal-upload.conf

在你的第二个盒子(debian2)上,首先通过使用systemctl edit编辑监听服务:

$ sudo systemctl edit systemd-journal-remote

当出现空编辑器时,添加以下三行:

[Service]
ExecStart=
ExecStart=/lib/systemd/systemd-journal-remote --listen-http=-3 --output=/var/log/journal/remote

它应该看起来像以下内容:

保存并退出(Ctrl + OEnterCtrl + X)。

现在你需要创建远程文件夹位置,并确保它具有适当的权限,最后重新启动服务:

$ sudo mkdir -p /var/log/journal/remote
$ sudo chown systemd-journal-remote /var/log/journal/remote
$ sudo systemctl restart systemd-journal-remote

并不要忘记在debian1上启动服务:

$ sudo systemctl restart systemd-journal-upload

通过在debian2上跟踪你的日志来测试一下:

$ sudo journalctl -D /var/log/journal/remote/ -f

也可以通过在debian1上使用我们信任的logger命令来测试:

$ logger -p syslog.err "Debian1 logs, on Debian2!"

幸运的话,你会看到以下内容:

工作原理...

我们实际上正在为syslogjournald解决方案中的日志打开一个监听器。我们的盒子上打开了一个网络端口,并且守护进程知道可能被迫读取数据的来源。在syslog的情况下,我们必须启用rsyslog守护进程中的特定模块才能实现这一点;systemdjournald需要特定的软件包。

显然,journald 的实现看起来有点笨重,但这主要是因为它是较新的。

基本上,我们只是在处理流式日志数据,无论syslog还是journald都不在乎数据来自哪里,只要它是它们可以理解的格式。

还有更多...

在集中记录日志时,时间非常重要。想想看在一个包含多个主机的日志文件中查找并发时间跳跃有多令人困惑。

它也可能使日志解析变得困难,因为我们可以使用特定的时间戳来正确排列数据,如果我们的远程盒子时间错误,我们可能会错过一些关键内容。

TLS 和安全传输也是需要考虑的内容,正如本节介绍中提到的那样。只要你正确配置证书,就可以将systemd-journal-remote配置为监听 HTTPS,而不是 HTTP。

对于 syslog,TLS 和加密可能会有些棘手,但有更多的解决方案需要考虑,比如通过 SSH 隧道流式传输日志数据,或者使用spipe这样的程序来卸载 TLS 的繁重工作。

本地资源测量工具

有时,了解一个盒子此刻正在做什么是非常方便的。通常情况下,这将在调试会话期间发生,当你试图弄清楚为什么网站的响应速度慢了一个数量级,或者为什么在远程会话中输入 SSH 命令要花费 5 分钟的时候。

在这里,本地资源监视器非常有用。我们已经简要提到过这些,但本节将稍微详细地介绍它们,并将涵盖一些在远程连接到服务器时可能会有用的更加隐秘的工具。

我们将先看看freetop这两个经典的程序,然后再转向更近期的添加,比如netdatahtop

准备工作

对于本节,我们将使用我们的centos1debian1虚拟机。

我们所看到的所有程序都将以某种形式普遍存在。

SSH 到你的centos1虚拟机,并确保启用了 EPEL 存储库:

$ vagrant ssh centos1 $ sudo yum install epel-release -y
$ sudo yum install htop -y

SSH 到你的debian1虚拟机,非常具体地,将端口19999转发到你的本地机器(稍后会详细介绍):

$ vagrant ssh debian1 -- -L 127.0.0.1:19999:127.0.0.1:19999

如何做到…

和我们所看到的大多数内容一样,有一些经典的软件例子,它们的形式或多或少自上世纪 70 年代以来一直存在,那时大多数程序的名称只有两三个字符(ls,cp,mv)。其中之一是top,另一个是free,它们仍然有它们的用武之地。

然后还有更现代、时髦、漂亮的程序。单色 CRT 设计的应用程序已经过去了,取而代之的是甚至支持 256 种颜色的终端应用程序!

最后,还有 NetData,我在这里提一下,因为它在本地管理领域引起了轰动。

top

一个老朋友,也保证会安装在任何 Unix 或类 Unix 系统上,top 是你对系统当前活动的直接窗口:

$ top

上述命令的输出如下所示:

从一开始,我们就可以看到几件事情:

  • 左上角的时间,以及盒子已经运行的时间- 17:09:46,已运行 8 分钟

  • 登录用户的数量- 1 个用户

  • 负载平均值- 0.00,0.03,0.03

  • 正在运行、睡眠等任务的数量

  • CPU 使用信息

  • 易失性内存信息(RAM)

  • SWAP 信息(磁盘内存)

将这些分解一下,让我们更详细地看一些:

  • 负载平均值:详细来说,负载平均值是系统在过去 1、5 和 15 分钟内的负载。这个负载平均值显示了正在运行的进程,或者那些正在等待 CPU 时间或磁盘 I/O 等资源的进程。

磁盘 I/O 元素很重要,因为这是相当特定于 Linux 的东西,很多人会忽略。你可以有一个完全没有 CPU 负载的系统,但负载平均值却很高;这可能表明你需要将那个古老而脆弱的 HDD 升级为全新的 NVMe 驱动器。

  • 任务:基本上,你可以将任务看作是当前正在运行的进程数量,或者是在你的系统上处于睡眠/僵尸/停止状态的进程数量。这与ps aux得到的数字是一样的。

  • %CPU:最好参考手册页:

us,user:运行非 niced 用户进程的时间

sy,system:运行内核进程的时间

ni,nice:运行 niced 用户进程的时间

id,idle:在内核空闲处理程序中花费的时间

wa,IO 等待:等待 I/O 完成的时间

hi:用于服务硬件中断的时间

si:用于服务软件中断的时间

st:被超级监视器从此虚拟机中窃取的时间

  • KiB Mem:以数字形式表示,这是可用于系统的 RAM 量,分为总计、空闲、已使用和缓冲区/高速缓存。

缓冲区/高速缓存是正在使用的内存,但任何希望立即使用它的程序都可以吞并它。Linux 喜欢 RAM,未使用的 RAM 是浪费的 RAM,因此它会尽其所能来使用它。

  • KiB Swap:同样以数字形式表示,这是可用的交换量,再次分解。

如果您想要一个更好的视图,通过多次按M循环浏览选项,将为您提供可视化表示:

最后,我们有不断变化的进程列表和有关系统上运行作业的信息:

默认情况下,这是按 CPU 使用率(%CPU)组织的,但如果您愿意,可以进行调整。

顶部有以下内容:

  • 问题任务的 PID

  • 运行进程的用户

  • PR,任务的优先级(优先级越高,越有可能被优先处理)

  • NI,任务的优先级值;负值具有更高的优先级

  • 虚拟内存被任务使用(所有内存,包括共享库等)

  • RES,任务正在使用的未交换物理内存

  • SHR,主要是 RES,没有一些对这本书来说太技术性的位

  • S,进程的状态(运行、睡眠、僵尸等)

  • %CPU,任务自上次刷新以来使用的 CPU 时间,占总 CPU 时间的百分比

  • %MEM,任务当前正在使用的可用物理内存的百分比

  • TIME+,TIME 的更新,它是任务自启动以来使用的总 CPU 时间;加号增加了粒度

  • COMMAND,任务的名称

呼~

这就是top,但它远不止于此,而且在不同的系统上可能看起来略有不同。它也可以显示颜色,但我只知道有一个发行版会默认打开它。

加载top并按H?,以获得top能够做什么的有用指示,是个好主意。

free

free是一种快速了解系统繁忙程度的好方法;更具体地说,它是快速了解系统上正在使用多少内存的最快方法。

谢天谢地,free的选项比top少。大多数情况下,标志只是用来改变命令的输出,使其更易读,或者更少,如果你喜欢的话。

就我个人而言,我使用-m,它以美比字节输出值,但如果您的系统有几 GB 的内存,您可能会发现-g更有用。

在接下来的内容中,您将看到我们的centos1 VM 上的free -ht-h用于人类可读的输出,提供了美比字节和吉比字节值的良好混合;-t是一个标志,用于添加Total行,给出MemSwap值的总和:

重要的字段是available,因为它有效地告诉您在系统开始交换之前有多少物理内存可用;如果这是0,您将不断地读写磁盘,这会严重减慢系统速度。

htop

htop就像top,但更漂亮。如果一个系统在我的控制之下(并且已经得到适当的批准),您很可能会发现htop已安装。

在紧要关头,top是可以的,而且保证会在系统上预装,但如果您想要一个不像是上世纪 70 年代的东西,htop非常有帮助。

这是我们centos1 VM 上的htop

请注意,我们仍然拥有top给我们的所有相同信息,只是现在我们已经利用了现代终端仿真器的功能,使我们默认拥有颜色,并且将输出整齐地排列到一个窗口中,该窗口还支持鼠标输入(试试看!)。

除了top之外,我们还可以通过更改窗口的外观来快速轻松地格式化输出;屏幕底部的选项(F5)如Tree在按下时可以提供非常有用的树视图(请注意,它会更改为Sorted):

与 top 一样,还有更改所显示的列和信息的选项,尽管与top不同,这些选项位于设置菜单(F3)下,并且更改将以配置文件的形式持久保存到磁盘,位于~/.config/htop/htoprc

NetData

由于能够有效地进行市场营销,NetData 主要因此而受欢迎,它是所有系统信息的聚合器。

这不是广告,也不是认可,只是第三方软件可以做的一个例子。NetData 确实使用集中式服务器来记录一些数据,例如系统主机名,这意味着如果您打算使用此工具,应检查您的内部安全策略。有关更多信息,请参阅 NetData 安全页面:

docs.netdata.cloud/docs/netdata-security/

与一切一样,在盲目点击接受之前,了解您要安装的软件的功能。

转到我们的 Debian VM,我们将在这里安装 NetData,因为它在后备存储库中可用(在事后添加到先前发布的 Debian 版本中的软件)。

首先,我们需要启用backports存储库,然后我们可以安装我们的软件包:

$ echo "deb http://ftp.debian.org/debian stretch-backports main" | sudo tee -a /etc/apt/sources.list
$ sudo apt update
$ sudo apt install netdata -y

由于 Debian 通常默认启动服务,因此安装后,NetData 现在已启用并启动。

但是,默认情况下,它只会在本地主机上侦听,这就是为什么我们需要在准备就绪部分中转发该 IP 和端口的原因。如果您还没有这样做,请从debian1 VM 注销,并使用该部分中的命令。

现在,在本地机器的 Web 浏览器中导航到http://127.0.0.1:19999将会将该连接转发到您的 VM,您应该会看到 NetData GUI:

NetData 主页

即使我不得不承认,界面非常漂亮。

请注意,右侧甚至会给出有关 NetData 正在做什么以及它从哪里获取信息的信息片段:debian1上的 netdata 每秒收集 686 个指标,呈现为 142 个图表,并由 41 个警报监视,使用 11 MB 的内存,实时历史记录为 1 小时 6 分钟 36 秒。

它是如何工作的...

top查询内核以收集有关正在运行的系统的信息,使其非常快速地反映了其运行的计算机的性质。它也非常轻量级,这意味着除非计算机负载极重,否则top仍然有很大机会运行(如果负载过重,您将面临更大的问题)。它自上世纪 80 年代以来一直存在;它经过了试验。

free查看/proc/meminfo中可用的值,这意味着虽然您可以自己查询这些文件(有些人确实这样做),但free提供了一种更好的查看值的方式(并且可以选择定期刷新,如果您需要的话)。

htop以与top基本相同的方式查询系统(尽管这在 macOS 或 BSD 系列等操作系统中并非必然相同)。htop的区别在于它使用ncurses库来显示自己,虽然它不像top那样古老,但在撰写本文时已经存在大约 14 年。

NetData 使用各种来源(并可以使用自定义插件)每秒收集数据;然后将此数据显示给用户。

还有更多...

NetData 可能看起来很酷,并且乍一看,它可以是了解服务器正在做什么的一种巧妙方式(特别是如果您将其放在办公室的墙上之类的地方),但这并不是广告,我建议在使用此类工具时要谨慎。不是因为它们危险(尽管始终检查您的来源),而是因为它们可能有点轻浮,只是作为管理人员偶尔想在您的 PC 监视器上看到的仪表板。

哦!我又想到了 NetData 的另一个很好的用途,也许可以作为一些俗气的 DC 或 SciFi 电视节目中的某种背景装饰;那也很好。

我们在这里看到的只是提供的工具的一个样本。默认情况下,您始终可以使用(世界上的顶部和空闲),但还有数百种其他选择,其中一些可能符合您的需求,另一些可能符合办公室角落的墙板,其他人从不使用。

四处看看,搜索一下网络,然后尝试一些东西。

这是 Linux,有一百种方法可以完成同样的事情。

本地监控工具

就像有一些工具可以监视您的系统资源,还有一些工具可以查看系统的历史数据。根据您的使用方式,NetData 可以被认为是其中之一,但除此之外还有更多,我们将看一些其他可以帮助您调试过去问题的工具。

我们将看一下以下内容:

  • atop

  • sar

  • vmstat

准备工作

在本节中,我们将继续使用本章第一节中的Vagrantfile

登录到centos1,这是我们将在本节中使用的虚拟机:

$ vagrant ssh centos1

安装我们将要使用的工具:

$ sudo yum install epel-release -y
$ sudo yum install atop sysstat -y

如何做...

安装好所有工具后,逐个完成以下各节。

atop

atop(高级系统和进程监控)

首先,正常运行atop

$ atop

您应该看到类似于这样的东西:

这为我们提供了一些很好的信息,特别是自启动以来的系统和进程活动,然后它会在之前的 10 秒内显示活动的滚动显示。换句话说,10 秒后,它看起来像这样:

此外,atop不仅可以用于存储当前启动的数据,还可以定期使用。

启用atop服务如下:

$ sudo systemctl enable atop --now

现在您会发现历史记录的日期被记录到/var/log/atop中以二进制格式,并且这些文件可以在将来的某个日期进行回放,以便了解系统在半夜发生了什么导致所有这些红色警报:

$ sudo ls /var/log/atop/
atop_20181013  daily.log

要再次读取文件,可以指定完整的文件名,也可以指定您要查找的日期:

$ atop -r 20181013

因为我们在18:56:14启动了服务,所以当我们加载这个文件时就会看到这个时间:

然后我们可以通过使用tT来调整样本以前进和后退。

atopcron作业在午夜重新启动:

$ sudo cat /etc/cron.d/atop
# daily restart of atop at midnight
0 0 * * * root systemctl try-restart atop

sar

sar是一种读取系统信息的方式,但它也允许您读取历史信息。

它是通过systemctl命令启用的,实际上触发了一个名为sa1的二进制文件在启动时启动:

$ sudo systemctl enable --now sysstat

通过cron作业运行,sar每 10 分钟执行一次以获取系统信息。然后在23:53创建每日摘要:

$ sudo cat /etc/cron.d/sysstat 
# Run system activity accounting tool every 10 minutes
*/10 * * * * root /usr/lib64/sa/sa1 1 1
# 0 * * * * root /usr/lib64/sa/sa1 600 6 &
# Generate a daily summary of process accounting at 23:53
53 23 * * * root /usr/lib64/sa/sa2 -A

使用-f标志指定要打开和读取的sar文件:

$ sar -f /var/log/sa/sa13 
Linux 3.10.0-862.2.3.el7.x86_64 (centos1)     13/10/18     _x86_64_    (1 CPU)

18:50:01        CPU     %user     %nice   %system   %iowait    %steal     %idle
19:00:01        all      0.03      0.00      0.21      0.00      0.00     99.76
Average:        all      0.03      0.00      0.21      0.00      0.00     99.76

19:07:51          LINUX RESTART

19:10:01        CPU     %user     %nice   %system   %iowait    %steal     %idle
19:14:43        all      0.04      0.00      0.22      0.00      0.00     99.75
19:15:07        all      0.17      0.00      0.48      0.00      0.00     99.35
19:20:01        all      0.01      0.00      0.12      0.00      0.00     99.87
19:30:01        all      0.00      0.00      0.09      0.00      0.00     99.91
19:40:01        all      0.00      0.00      0.10      0.00      0.00     99.90
Average:        all      0.01      0.00      0.12      0.00      0.00     99.87

或者,如果您想更加细致,可以指定开始和结束时间:

$ sar -f /var/log/sa/sa13 -s 19:10:00 -e 19:15:08
Linux 3.10.0-862.2.3.el7.x86_64 (centos1) 13/10/18 _x86_64_ (1 CPU)

19:10:01 CPU %user %nice %system %iowait %steal %idle
19:14:43 all 0.04 0.00 0.22 0.00 0.00 99.75
19:15:07 all 0.17 0.00 0.48 0.
00 0.00 99.35
Average: all 0.05 0.00 0.24 0.00 0.00 99.72

vmstat

vmstat是报告内存统计信息的好方法;默认情况下,其输出如下:

$ vmstat
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  0   1544  65260      0 367336    0    0    57    78   45   47  0  0 100  0  0

不过,vmstat的优点在于它的初始报告(前面的输出)是自启动以来的信息,并且您可以在命令的末尾添加一个数字,以获得滚动摘要:

$ vmstat 5
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
 r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
 2  0   1544  65140      0 367336    0    0    57    77   45   47  0  0 100  0  0
 0  0   1544  65140      0 367336    0    0     0     0   49   48  0  0 100  0  0
 0  0   1544  64768      0 367336    0    0     0     0   51   54  0  0 100  0  0
 0  0   1544  64768      0 367336    0    0     0     0   48   48  0  0 100  0  0
 0  0   1544  64768      0 367336    0    0     0     1   46   46  0  0 100  0  0
 0  0   1544  64768      0 367336    0    0     0     0   47   46  0  0 100  0  0
 0  0   1544  64588      0 367336    0    0     0     0   49   53  0  0 100  0  0
 0  0   1544  64784      0 367336    0    0     0     0   49   51  0  0 100  0  0
 0  0   1544  64784      0 367336    0    0     0     0   48   48  0  0 100  0  0
 1  0   1544  64784      0 367336    0    0     0     0   46   48  0  0 100  0  0

与 NetData 一样,vmstat可以适用于这一部分或前一部分,因此用户需要决定如何做。例如,您可以编写一个systemd-timer,每小时运行vmstat 10 次,并将结果输出到文件中,以便以后查看。这比开箱即用的解决方案(如saratop)要手动一些,但对于更大的项目来说是一个很好的练习。

它是如何工作的...

与我们之前的部分一样,开箱即用,atopsar的大部分设置已经为您完成,但是可以在相关进程的配置文件中进行进一步的配置更改。

在 CentOS 下,这些都存储在/etc/sysconfig中,这是传统的做法:

$ cat /etc/sysconfig/atop 
# sysconfig atop
#

# Current Day format
CURDAY=`date +%Y%m%d`
# Log files path
LOGPATH=/var/log/atop
# Binaries path
BINPATH=/usr/bin
# PID File
PIDFILE=/var/run/atop.pid
# interval (default 10 minutes)
INTERVAL=600
$ cat /etc/sysconfig/sysstat
# sysstat-10.1.5 configuration file.
# How long to keep log files (in days).
# If value is greater than 28, then log files are kept in
# multiple directories, one for each month.
HISTORY=28
# Compress (using gzip or bzip2) sa and sar files older than (in days):
COMPRESSAFTER=31
# Parameters for the system activity data collector (see sadc manual page)
# which are used for the generation of log files.
SADC_OPTIONS="-S DISK"
# Compression program to use.
ZIP="bzip2"

当使用systemd启动atop时,会触发/usr/share/atop/atop.daily脚本,使用sysconfig中的选项。

sysstatsystemd一起启用时,它会明确告诉sar从一个虚拟记录开始,表示新的启动。这是在我们之前看到的cron条目之外的,由/etc/sysconfig中的配置文件决定的。

使用这些工具有点复杂,但是如果您擅长解释和使用它们提供的信息,您很快就会发现这些数据对您非常有价值。

远程监控工具

能够在本地查询服务器并了解它在做什么是很好的,但这在现实世界中很少是做事情的方式(在您可能为个人项目维护的单个框之外)。在公司场景中,更有可能的是您会有某种监控解决方案,也许在您的框上有代理,它会监视您负责的机器的健康状况。

Nagios 是全球监控安装的无可争议的王者,不是因为它是最好的,或者最引人注目的,而仅仅是因为它是最古老的之一,一旦监控解决方案就位,您会发现团队非常不愿意切换到新的解决方案。

它已经导致创建了几个克隆版本,并且有各种分支(有些使用原始源代码,有些不使用),但它们都会以类似的方式运行。

在这一部分,我们将在centos1上安装Nagios,并让它监视自己和debian1,同时我们将在centos2上安装Icinga2,并让它监视debian2

准备工作

对于本节,第一节中的Vagrantfile就足够了。我们将使用我们的四个 VM。

我们将首先运行 Nagios 设置,然后再转到 Icinga2。

连接到您的每个框,或者从centos1debian1开始,然后再移动到centos2debian2

连接到centos1进行Nagios安装时,您将希望使用以下端口转发:

$ vagrant ssh centos1 -- -L 127.0.0.1:8080:127.0.0.1:80

如何做...

如前所述,我们将首先运行 Nagios,然后运行 Icinga2。

Nagios

centos1上,让我们从 EPEL 安装 Nagios:

$ sudo yum install -y epel-release
$ sudo yum install -y httpd nagios nagios-plugins-all nagios-plugins-nrpe

现在已经完成了(这可能需要一些时间,因为有很多插件),我们应该启动和启用我们的服务,以及httpd,它应该是默认安装的:

$ sudo systemctl enable --now nagios
$ sudo systemctl enable --now httpd

开箱即用,您将获得一个不安全的 nagios-web 设置。如果您按照先前建议连接到 Vagrant VM,现在应该能够在转发端口(http://127.0.0.1:8080/nagios)上访问 Web 界面:

实际上,我们还没有设置我们的nagiosadmin密码(用于基本的http auth提示),所以现在让我们来做这个:

$ sudo htpasswd /etc/nagios/passwd nagiosadmin
New password: 
Re-type new password: 
Updating password for user nagiosadmin

设置密码后,尝试在提示框中输入密码:

您应该看到 Nagios 的登录页面:

正如我在其他地方提到的,我不建议以这种方式使用基本的 HTTP 身份验证,因为它是不安全的。如果您无法使用 HTTPS/TLS 来保护网页,您应该将其阻止,以便只能在本地访问该框,并使用类似 SSH 转发的方式来加密到门户的连接。不过,理想情况下,从 LetsEncrypt 获取证书并简化您的生活。

现在点击左侧的 Services;这是您大部分时间都想要的地方,因为它显示了 Nagios 当前正在监视的主机和该主机上的服务:

默认情况下,您只能看到我们只监视我们的 localhost,这对现在来说是可以的,但是我们想要将debian1加入其中。回到命令行,让我们首先指向 Nagios 配置文件中的debian1文件:

$ echo "cfg_file=/etc/nagios/objects/debian1.cfg" | sudo tee -a /etc/nagios/nagios.cfg

现在我们需要创建debian1.cfg

$ sudo cp /etc/nagios/objects/localhost.cfg /etc/nagios/objects/debian1.cfg

我们目前与localhost机器具有相同的配置,因此我们将使用debian1特定值替换这些值。我们还将创建一个专门用于远程虚拟机的新主机组,并将本地检查更改为首先使用check_nrpe

$ sudo sed -i 's/localhost/debian1/g' /etc/nagios/objects/debian1.cfg
$ sudo sed -i 's/127.0.0.1/192.168.33.12/g' /etc/nagios/objects/debian1.cfg
$ sudo sed -i 's/linux-servers/remote-vms/g' /etc/nagios/objects/debian1.cfg
$ sudo sed -i 's/check_local/check_nrpe!check_client/g' /etc/nagios/objects/debian1.cfg

有了这些,我们必须定义check_nrpe命令:

$ cat <<HERE | sudo tee -a /etc/nagios/objects/commands.cfg
define command{
 command_name    check_nrpe
 command_line    \$USER1\$/check_nrpe -H \$HOSTADDRESS\$ -c \$ARG1\$ 
 }
HERE

完成后,我们可以重新启动我们的 Nagios 安装:

$ sudo systemctl restart nagios

再次查看您的服务页面,您现在将看到debian1,很可能有很多检查失败。

这是因为debian1上没有设置 NRPE,所以让我们现在 SSH 到debian1并进行设置!

首先,我们需要安装各种部件:

 $ sudo apt install monitoring-plugins nagios-nrpe-server -y

现在,我们需要允许我们的centos1框与debian1通信(通过端口5666):

$ sudo sed -i 's/allowed_hosts=127.0.0.1/allowed_hosts=127.0.0.1,192.168.33.10/g' /etc/nagios/nrpe.cfg

我们还需要定义服务器将要求在客户端上运行的客户端命令:

$ cat <<HERE | sudo tee /etc/nagios/nrpe_local.cfg 
command[check_client_load]=/usr/lib/nagios/plugins/check_load -w 5.0,4.0,3.0 -c 10.0,6.0,4.0
command[check_client_users]=/usr/lib/nagios/plugins/check_users -w 20 -c 50
command[check_client_disk]=/usr/lib/nagios/plugins/check_disk -w 20% -c 10% -p /
command[check_client_swap]=/usr/lib/nagios/plugins/check_swap -w 20 -c 10
command[check_client_procs]=/usr/lib/nagios/plugins/check_procs -w 250 -c 400 -s RSZDT
HERE

最后,我们到了可以重新启动debian1上的nrpe服务的时候:

$ sudo systemctl restart nagios-nrpe-server

现在,回顾一下 Nagios Web 界面(如果您断开连接,请不要忘记再次 SSH 到centos1虚拟机),我们应该看到我们的服务被正确检查:

带有“debian1”的 Nagios 服务页面

请注意,我们有一个失败的检查(HTTP),因为debian1没有安装和运行 Web 服务器。

如果您的检查尚未循环完成,您可以通过单击主机名称,然后选择“计划检查此主机上的所有服务”命令来强制对主机上的所有服务进行检查。

Icinga2

与 Nagios(最初源自 Nagios)一样,Icinga2 具有中央服务器的概念,以及其他主机上的代理,它可以监视。

我们将在我们的centos2虚拟机上安装Icinga2,然后从我们的第一个主机监视我们的debian2虚拟机。

首先,跳转到centos2并安装Icinga2

$ vagrant ssh centos2 -- -L 127.0.0.1:8181:127.0.0.1:80

注意转发部分;这将用于稍后的 GUI 设置(端口8181):

$ sudo yum install epel-release -y
$ sudo yum install centos-release-scl -y
$ sudo yum install https://packages.icinga.com/epel/icinga-rpm-release-7-latest.noarch.rpm -y
$ sudo yum install httpd icinga2 icinga2-ido-mysql nagios-plugins-all icinga2-selinux mariadb-server mariadb icingaweb2 icingacli icingaweb2-selinux rh-php71-php-mysqlnd -y
$ sudo systemctl enable --now icinga2
$ sudo systemctl enable --now mariadb

运行mariadb安装脚本(默认情况下,root 密码为空;将其设置为您记得的内容):

$ mysql_secure_installation

NOTE: RUNNING ALL PARTS OF THIS SCRIPT IS RECOMMENDED FOR ALL MariaDB
      SERVERS IN PRODUCTION USE!  PLEASE READ EACH STEP CAREFULLY!

In order to log into MariaDB to secure it, we'll need the current
password for the root user.  If you've just installed MariaDB, and
you haven't set the root password yet, the password will be blank,
so you should just press enter here.

Enter current password for root (enter for none): 
OK, successfully used password, moving on...

Setting the root password ensures that nobody can log into the MariaDB
root user without the proper authorisation.

Set root password? [Y/n] Y
New password: 
Re-enter new password: 
Password updated successfully!
Reloading privilege tables..
 ... Success!

By default, a MariaDB installation has an anonymous user, allowing anyone
to log into MariaDB without having to have a user account created for
them.  This is intended only for testing, and to make the installation
go a bit smoother.  You should remove them before moving into a
production environment.

Remove anonymous users? [Y/n] Y
 ... Success!

Normally, root should only be allowed to connect from 'localhost'.  This
ensures that someone cannot guess at the root password from the network.

Disallow root login remotely? [Y/n] Y
 ... Success!

By default, MariaDB comes with a database named 'test' that anyone can
access.  This is also intended only for testing, and should be removed
before moving into a production environment.

Remove test database and access to it? [Y/n] Y
 - Dropping test database...
 ... Success!
 - Removing privileges on test database...
 ... Success!

Reloading the privilege tables will ensure that all changes made so far
will take effect immediately.

Reload privilege tables now? [Y/n] Y
 ... Success!

Cleaning up...

All done!  If you've completed all of the above steps, your MariaDB
installation should now be secure.

Thanks for using MariaDB!

现在为icinga设置mariadb。此信息可以在 Icinga2 入门指南中找到,icinga.com/docs/icinga2/latest/doc/02-getting-started/#setting-up-icinga-web-2

$ mysql -u root -p<The Password You Set Above>

MariaDB [(none)]> CREATE DATABASE icinga;
Query OK, 1 row affected (0.00 sec)

MariaDB [(none)]> GRANT SELECT, INSERT, UPDATE, DELETE, DROP, CREATE VIEW, INDEX, EXECUTE ON icinga.* TO 'icinga'@'localhost' IDENTIFIED BY 'icinga';
Query OK, 0 rows affected (0.06 sec)

MariaDB [(none)]> quit
Bye

最后,导入提供的schema数据库:

$ mysql -u root -p icinga < /usr/share/icinga2-ido-mysql/schema/mysql.sql

icinga2中启用实际插件,并重新启动服务:

$ sudo icinga2 feature enable ido-mysql
$ sudo systemctl restart icinga2

数据库设置完成后,我们可以继续进行实际的 Web 安装。

我们在本节的安装步骤中包括httpd(apache),因为 Icinga2 建议使用它,尽管也可以使用 NGINX(实际上是 FreeBSD 上的默认值)。

首先启动并启用它:

$ sudo systemctl enable --now httpd

接下来,启用icinga2api功能并重新启动它:

$ sudo icinga2 api setup
$ sudo systemctl restart icinga2

将在api-users.conf中添加一个 root 用户和随机密码:

$ sudo cat /etc/icinga2/conf.d/api-users.conf
/**
 * The ApiUser objects are used for authentication against the API.
 */
object ApiUser "root" {
 password = "40ebca8aaaf1eba0"
 // client_cn = ""

 permissions = [ "*" ]
}

Icinga2 Web 还需要启用FastCGI Process ManagerFPM),因此请执行此操作:

$ sudo sed -i 's/;date.timezone =/date.timezone = UTC/g' /etc/opt/rh/rh-php71/php.ini
$ sudo systemctl enable --now rh-php71-php-fpm.service

完成后,您应该能够在浏览器中访问已安装的 Icinga2 Web 设置(使用我们的转发连接),http://127.0.0.1:8181/icingaweb2/setup

要找到您的设置代码,请返回到您的centos2命令行并运行以下命令:

$ sudo icingacli setup token create
The newly generated setup token is: 052f63696e4dc84c

输入您的代码并通过安装点击下一步(确保在要求页面上没有红色;黄色对于 Imagick PHP 模块来说是可以的):

在提示要求身份验证类型时,选择数据库:

在下一页上,您将被提示提供数据库名称、数据库用户名和数据库密码。选择合适的值(如果尚未创建,不要担心,我们将在下一步中进行):

在这里,您可以看到我选择了icinga2web作为数据库名称和用户名。点击下一步。

在紧随其后的屏幕上,您将被要求传入一个可以访问 MariaDB 以创建新数据库的用户的凭据;我选择使用我们之前设置的 MariaDB root 用户:

您将被提示选择后端名称,这是一个美学决定,以便您以后可以识别后端:

并且您将被要求创建一个 web 用户;我选择了icingaweb

我将应用配置保留为默认值:

最后,您将被提示确认您输入的设置;在继续之前快速检查一遍。

点击下一步几次,直到到达监控后端设置(在那里 IcingaWeb2 查找监控数据库):

您将被提示添加可以用于查询 icinga 数据库的连接详细信息(我们在本章前面使用 MariaDB CLI 设置了)。我们设置了以下默认值:用户名icinga,密码icinga

使用“验证配置”按钮验证您的配置。

在命令传输屏幕上,您将被提示输入我们创建的 API 用户的详细信息。我们只添加了 root,所以现在让我们使用它(从之前的 api-users.conf 文件):

点击下一步,直到最后,您应该看到一个快乐的绿色横幅:

继续到提示的登录页面,并使用我们创建的 web 用户登录:

Icinga2 登录页面

设计可能在您阅读本书时已经有所改变,但希望您有一个接近以下 Icinga2 仪表板的东西:

在进入下一部分之前,先四处看看,我们将添加另一个主机。

首先,我们必须在安装客户端之前将我们的第一个服务器建立为master

您可以使用设置脚本来完成此操作;我已经将唯一需要的更改用粗体标出:

$ sudo icinga2 node wizard
Welcome to the Icinga 2 Setup Wizard!

We will guide you through all required configuration details.

Please specify if this is a satellite/client setup ('n' installs a master setup) [Y/n]: n

Starting the Master setup routine...

Please specify the common name (CN) [centos2]: 
Reconfiguring Icinga...
Checking for existing certificates for common name 'centos2'...
Certificate '/var/lib/icinga2/certs//centos2.crt' for CN 'centos2' already existing. Skipping certificate generation.
Generating master configuration for Icinga 2.
'api' feature already enabled.

Master zone name [master]: 

Default global zones: global-templates director-global
Do you want to specify additional global zones? [y/N]: 
Please specify the API bind host/port (optional):
Bind Host []: 
Bind Port []: 

Do you want to disable the inclusion of the conf.d directory [Y/n]: 
Disabling the inclusion of the conf.d directory...
Checking if the api-users.conf file exists...

Done.

Now restart your Icinga 2 daemon to finish the installation!

按照建议做,并重新启动icinga2

$ sudo systemctl restart icinga2

为我们的客户端生成一个连接时使用的令牌:

$ sudo icinga2 pki ticket --cn debian2
8c7ecd2c04e6ca73bb0d1a6cc62ae4041bf2d5d2

现在 SSH 到您的debian2盒子并安装icinga2

$ sudo apt install icinga2 -y

通过节点安装,这次指定我们是代理,并在提示时传入之前的令牌:

$ sudo icinga2 node wizard
Welcome to the Icinga 2 Setup Wizard!

We'll guide you through all required configuration details.

Please specify if this is a satellite setup ('n' installs a master setup) [Y/n]: 
Starting the Node setup routine...
Please specify the common name (CN) [debian2]: 
Please specify the master endpoint(s) this node should connect to:
Master Common Name (CN from your master setup): centos2
Do you want to establish a connection to the master from this node? [Y/n]: 
Please fill out the master connection information:
Master endpoint host (Your master's IP address or FQDN): 192.168.33.11
Master endpoint port [5665]: 
Add more master endpoints? [y/N]: 
Please specify the master connection for CSR auto-signing (defaults to master endpoint host):
Host [192.168.33.11]: 
Port [5665]: 
information/base: Writing private key to '/etc/icinga2/pki/debian2.key'.
information/base: Writing X509 certificate to '/etc/icinga2/pki/debian2.crt'.
information/cli: Fetching public certificate from master (192.168.33.11, 5665):

Certificate information:

 Subject:     CN = centos2
 Issuer:      CN = Icinga CA
 Valid From:  Oct 13 22:34:30 2018 GMT
 Valid Until: Oct  9 22:34:30 2033 GMT
 Fingerprint: B5 0B 00 5D 5F 34 14 08 D7 48 8E DA E1 83 96 35 D9 0F 54 1F 

Is this information correct? [y/N]: y
information/cli: Received trusted master certificate.

Please specify the request ticket generated on your Icinga 2 master.
 (Hint: # icinga2 pki ticket --cn 'debian2'): 8c7ecd2c04e6ca73bb0d1a6cc62ae4041bf2d5d2
information/cli: Requesting certificate with ticket '8c7ecd2c04e6ca73bb0d1a6cc62ae4041bf2d5d2'.

information/cli: Created backup file '/etc/icinga2/pki/debian2.crt.orig'.
information/cli: Writing signed certificate to file '/etc/icinga2/pki/debian2.crt'.
information/cli: Writing CA certificate to file '/etc/icinga2/pki/ca.crt'.
Please specify the API bind host/port (optional):
Bind Host []: 
Bind Port []: 
Accept config from master? [y/N]: y
Accept commands from master? [y/N]: y
information/cli: Disabling the Notification feature.
Disabling feature notification. Make sure to restart Icinga 2 for these changes to take effect.
information/cli: Enabling the Apilistener feature.
Enabling feature api. Make sure to restart Icinga 2 for these changes to take effect.
information/cli: Created backup file '/etc/icinga2/features-available/api.conf.orig'.
information/cli: Generating local zones.conf.
information/cli: Dumping config items to file '/etc/icinga2/zones.conf'.
information/cli: Created backup file '/etc/icinga2/zones.conf.orig'.
information/cli: Updating constants.conf.
information/cli: Created backup file '/etc/icinga2/constants.conf.orig'.
information/cli: Updating constants file '/etc/icinga2/constants.conf'.
information/cli: Updating constants file '/etc/icinga2/constants.conf'.
Done.

Now restart your Icinga 2 daemon to finish the installation!

debian2上重新启动icinga2

$ sudo systemctl restart icinga2

现在,我们需要配置主服务器来实际检查客户端;我们已经建立了一个连接,可以用ss查看:

$ ss -t
State      Recv-Q Send-Q                                     Local Address:Port                                                      Peer Address:Port                
ESTAB      0      0                                              10.0.2.15:ssh                                                           10.0.2.2:44828                
ESTAB 0 0 192.168.33.13:5665 192.168.33.11:49398 

现在,回到centos2,添加以下配置:

$ cat <<HERE | sudo tee -a /etc/icinga2/zones.conf
object Endpoint "debian2" {
 host = "192.168.33.13"
}
object Zone "debian2" {
 endpoints = [ "debian2" ]
 parent = "master"
}
HERE

为相关区域创建一个hosts目录:

$ sudo mkdir -p /etc/icinga2/zones.d/master

并添加适当的hosts配置:

$ cat <<HERE | sudo tee /etc/icinga2/zones.d/master/hosts.conf
object Host "debian2" {
 check_command = "hostalive"
 address = "192.168.33.13"
 vars.client_endpoint = name
 vars.os = "Linux"
}
HERE

重新启动icinga2

$ sudo systemctl restart icinga2

此时,您应该在 Icinga2 web GUI 中看到您的客户端:

Icinga2 主机页面上有待检查的检查

仅仅让 ping 检查主机有点无用(嗯,大多数情况下;ping 警报在以前曾经救过我),所以让我们也添加一些推荐的服务检查:

$ cat <<HERE | sudo tee /etc/icinga2/zones.d/master/services.conf
apply Service "ping4" {
 check_command = "ping4"
 assign where host.address
}
apply Service "ssh" {
 check_command = "ssh" 
 assign where host.vars.os == "Linux"
}
apply Service "disk" {
 check_command = "disk"
 command_endpoint = host.vars.client_endpoint
 assign where host.vars.client_endpoint
}
apply Service "icinga" {
 check_command = "icinga"
 command_endpoint = host.vars.client_endpoint
 assign where host.vars.client_endpoint
}
apply Service "ntp_time" {
 check_command = "ntp_time"
 command_endpoint = host.vars.client_endpoint
 assign where host.vars.client_endpoint
}
HERE

再次重启服务。

$ sudo systemctl restart icinga2

回顾我们的 GUI,现在将显示我们的debian2盒子以及一些服务检查(其中有一个失败):

Icinga2 主机页面上的“debian2”

它是如何工作的...

通过拥有一个能够看到其他服务器的主服务器,您可以获得所需的可见性,以便在出现需要立即解决的问题时了解情况。

诸如 Nagios 和 Icinga2 之类的监控工具通常通过与远程计算机交互,使用盒子上的脚本或远程自定义命令查询其状态,并报告这些命令的输出。这可以通过多种方式完成,包括但不限于 NRPE 代理,远程 SSH 命令,或从 SNMP 守护程序查询的 SNMP 结果。

通过在基础设施的状态上创建一个单一的真相来源,您可以立即了解到您的基础设施出现问题,甚至可以根据多个症状的数据进行相关性分析。

Icinga2、Nagios、Zabbix 和 Sensu 的行为方式都相对类似,最终都是很好的工具,但通常取决于实施团队(或个人)的个人偏好来决定采用哪种工具。

安装 Nagios 并进行尝试不会有错,因为它是我在野外遇到最多的,而且它的子代/表亲目前统治着这个领域。

还有更多...

在这里,我们迅速搭建了一个 Nagios 和 Icinga2 的安装,以展示它们在几个简单命令下的功能。这些配置并不是生产就绪的,需要考虑诸如可重用的监控检查模式,以及安全性(例如 GUI 上的 TLS,以及主服务器和客户端之间使用安全通信方法)。

就像本书中的许多软件一样,您现在应该对如何入门有了很好的理解,但在为自己的系统实施解决方案时应考虑所有选项。如果您要监视的池相对较小,并且不太可能增长,您可能会认为 Nagios 基于文件的监控设置是合适的;如果您有一个更大的、跨越多个地区的基础设施,您可能会发现 Icinga2 及其区域更符合您的口味。

我们还没有涉及电子邮件和警报,只是提到了 Nagios 和 Icinga2 产生的视觉警报。可以通过多种方式将警报插入这两种解决方案(例如短信警报,或在房间角落闪烁的灯泡),但是,从出厂时开始,它们都相对良好地处理电子邮件(假设您有一个功能齐全的电子邮件服务器来传递警报)。

最后,这只是一个入门指南,设置 Icinga2 和 Nagios 还有许多其他方法。在很多方面,它们可以被视为框架而不是软件,因为它们在出厂时是一个相当空白的画布,仍然可以让您按照您希望的复杂或简单的方式构建生产系统。

我曾遇到过 Icinga2 的安装,我对自己的能力非常自信(比平时更多),只是在 5 分钟后开始摸不着头脑,因为我试图解开留给我的手工配置的混乱。

另请参阅

我们在这里使用的监控插件很有趣,因为几年前发生了一场激烈的争论,旧的nagios-plugins.org域名从一个独立维护的服务器重定向到了一个由 Nagios Enterprises 控制的服务器。

在这次重定向之后发生了一场争论和分裂,导致monitoring-pluginsnagios-plugins成为独立的实体。值得一提的是,在撰写本文时,nagios-plugins在 Debian 系统上是monitoring-plugins的别名。

更多信息可以在这篇博客文章中找到:www.monitoring-plugins.org/news/new-project-name.html

Debian 的错误报告可以在这里找到:bugs.debian.org/cgi-bin/bugreport.cgi?bug=736331

这是 Red Hat 的错误报告,增加了戏剧性(不要参与其中):bugzilla.redhat.com/show_bug.cgi?id=1054340

使用 Elastic Stack 集中日志记录

之前,我们提到了远程日志记录的解决方案,涉及将我们的日志记录解决方案(syslogjournald)转发到运行相同或类似软件的其他主机,以便将日志聚合在一个地方。

这是一个不错的解决方案,在小型环境中运行良好,但它没有太多花里胡哨的功能,如果有什么东西我们在 IT 中喜欢的,那就是我们可以向管理层展示然后永远不会使用的闪亮东西。

Elastic Stack 就是这样一个产品;用他们自己的话说:

基于开源基础,Elastic Stack 可以可靠且安全地从任何来源以任何格式获取数据,并实时搜索、分析和可视化数据。

大胆的说法,但肯定有支持。Elastic Stack 现在是大多数中型以上企业的事实集中日志记录解决方案,也许在企业级别有一些竞争对手。

我们将在centos1上设置一个小型解决方案,并将我们的日志从centos2debian1debian2转发到它。

我花了一天的时间与 X-Pack 和 Elastic Stack 搏斗,所以如果我写的任何东西听起来讽刺或心怀恶意,那可能是有意的。

准备工作

在本节中,我们将使用所有的 VMs,最好先运行vagrant destroyvagrant up

请注意,对于本节,我们将安装某些 Elastic Stack 组件的版本 6 发布。配置在历史上已经发生了变化,可能在您阅读本书时再次发生变化;如果有任何不符合您期望的地方,请参考您版本的 Elastic 文档来填补空白。

在本节中,我们将在centos1上运行初始设置,然后转到其他 VM 并配置它们的日志目的地。

大多数情况下,我不建议这样做,但对于这一部分,将 VMs 重置为一个新的起点可能是个好主意:

$ vagrant destroy -f
$ vagrant up
$ vagrant ssh centos1 -- -L127.0.0.1:5601:127.0.0.1:5601

为了方便起见,也因为这不是一本关于安装和配置 Elastic Stack 的书,我们将以相当快的速度运行 Elasticsearch、Kibana 和 Logstash 的安装。

首先,我们需要 Java:

$ sudo yum install java-1.8.0-openjdk -y

如何做...

centos1上,让我们获取 Elastic 存储库:

$ cat <<HERE | sudo tee /etc/yum.repos.d/elasticsearch.repo
[elasticsearch-6.x]
name=Elasticsearch repository for 6.x packages
baseurl=https://artifacts.elastic.co/packages/6.x/yum
gpgcheck=1
gpgkey=https://artifacts.elastic.co/GPG-KEY-elasticsearch
enabled=1
autorefresh=1
type=rpm-md
HERE

现在我们需要安装各种组件:

$ sudo yum install elasticsearch kibana logstash -y

并且我们需要启动它们,并进行一些配置调整:

$ sudo systemctl daemon-reload
$ sudo systemctl enable --now elasticsearch
$ sudo systemctl enable --now kibana

我们将使用 Elastic syslog示例(来自www.elastic.co/guide/en/logstash/6.4/config-examples.html#_processing_syslog_messages)来配置我们的Logstash设置:

$ cat <<HERE | sudo tee /etc/logstash/conf.d/logstash-syslog.conf
input {
 tcp {
 port => 5000
 type => syslog
 }
 udp {
 port => 5000
 type => syslog
 }
}

filter {
 if [type] == "syslog" {
 grok {
 match => { "message" => "%{SYSLOGTIMESTAMP:syslog_timestamp} %{SYSLOGHOST:syslog_hostname} %{DATA:syslog_program}(?:\[%{POSINT:syslog_pid}\])?: %{GREEDYDATA:syslog_message}" }
 add_field => [ "received_at", "%{@timestamp}" ]
 add_field => [ "received_from", "%{host}" ]
 }
 date {
 match => [ "syslog_timestamp", "MMM  d HH:mm:ss", "MMM dd HH:mm:ss" ]
 }
 }
}

output {
 elasticsearch { hosts => ["localhost:9200"] }
 stdout { codec => rubydebug }
}
HERE

此配置设置了数据的输入方法,本例中为tcpudp端口5000。然后为syslog内容设置了一个过滤器,最后设置了一个输出到 Elasticsearch(后端)。

现在我们可以启动Logstash

$ sudo systemctl enable --now logstash

您应该看到您的计算机上有各种端口在监听(这可能需要一些时间,因为各种组件正在启动):

$ ss -tunal '( src :5601 or src :9200 or src :5000 )'
Netid  State      Recv-Q Send-Q                                        Local Address:Port                                                       Peer Address:Port 
udp    UNCONN     0      0                                                         *:5000                                                                  *:* 
tcp    LISTEN     0      128                                               127.0.0.1:5601                                                                  *:* 
tcp    LISTEN     0      128                                                      :::5000                                                                 :::* 
tcp    LISTEN     0      128                                        ::ffff:127.0.0.1:9200                                                                 :::* 
tcp    LISTEN     0      128                                                     ::1:9200                                                                 :::*       

现在,我们可以配置我们的其他机器指向centos1上的Logstash

centos2

使用以下rsyslog更改开始将centos2日志转发到centos1实例的Logstash

$ sudo sed -i 's/#*.* @@remote-host:514/*.* @192.168.33.10:5000/g' /etc/rsyslog.conf
$ sudo systemctl restart rsyslog

我之前建议重建 VMs,但如果你还没有,如果你遵循了关于远程syslog配置的早期部分,前面的命令可能需要调整。打开文件并确认行看起来是否符合你的期望。

您可以通过在centos2上使用以下内容来测试您的配置:

$ logger -p syslog.err "Digimon was better than Pokemon."

debian1 和 debian2

对于 Debian 配置,请使用以下行:

$ echo "*.* @192.168.33.10:5000" | sudo tee -a /etc/rsyslog.conf
$ sudo systemctl restart rsyslog
$ logger -p syslog.err "Digimon was better than Pokemon."

Kibana

我们在Logstash中有数据,但除非您有点急躁,否则您还看不到它。

如果您已将连接转发到centos1,如本节开头所示,您应该能够在您选择的浏览器中导航到http://localhost:5601,并受到 Kibana 启动页面的欢迎(假设您的安装已经顺利进行):

如果您以前使用过 Kibana,您可能期望点击 Discover 并查看您的日志,但实际上您会被踢到 Management:

在索引模式的通配符条目中输入*,然后点击右侧的下一步:

在下一部分,从提供的下拉列表中选择@timestamp,然后点击右侧的 Create index pattern:

创建索引后,再次点击左侧的 Discover;这次,您应该会看到一个正确的视图:

显然,我们想要筛选一下,只是为了确保我们已经得到了一切。

在顶部的搜索框中输入以下过滤查询:

(host:192.168.33.13 OR host:192.168.33.12 OR host:192.168.33.11) AND message:"Pokemon*"

你应该看到类似这样的东西:

Kibana Discover 页面

如果在 Kibana 中看不到数据,可能是因为 Logstash 在您从其他主机发送日志之前没有完全启动。尝试使用上面的logger命令再次发送您的日志。

工作原理...

我们实际上正在设置三件事:

  • Elasticsearch:作为我们要传输到我们的盒子中的所有数据的存储点

  • Kibana:作为我们数据的前端和仪表板,这意味着我们可以在闲暇时查询和查看它

  • Logstash:创建一个监听器,就像我们在本章前面设置的syslog接收器一样

当这三个组件都启用时,它们可以创建一种集中任何日志(或其他数据)的方式,我们可能希望将其投入我们的解决方案。

基本上它是一个漂亮的syslog接收器,而且还能做更多的事情。

我们在这里没有做的一件事是将我们的日志从centos1转发到自己的Logstash监听器;这是可以做到的,但需要进行一些调整,以确保您不会意外地创建一个日志风暴,随着其自身的日志消息被反馈到自身而呈指数级增长。我可能做过,也可能没有做过。

还有更多...

Elastic Stack 不仅仅是 Elasticsearch、Kibana 和 Logstash,还有更多的工具和功能,它们完全是模块化的,可以根据您的需要集成到您的安装中。

它们包括但不限于以下内容:

  • Heartbeat - 用于运行时间监控

  • Filebeat - 用于从远程机器发送日志

  • 机器学习能力

我们在这里也设置了一个非常简单的解决方案,我肯定不会在生产中使用。它在传输数据上缺乏安全性,将普通日志流式传输到我们的接收器,并且在 Kibana 仪表板上也没有登录提示或用于浏览器安全性的 TLS。

X-Pack 是解决这些问题的一种方式,在默认安装中作为试用版可用,或者通过许可证,这将花费您一些费用。它允许您在您的安装中设置安全性,包括节点通信和登录安全性(例如 Kibana 用户登录)。

Elastic Stack 也是一套资源消耗大的软件套件,您可能希望在直接安装在壁橱中的中等大小的盒子上之前,先正确地设计您的解决方案。

总结-监控和日志记录

虽然这不是任何人的最爱话题(除了我认识的一些非常奇怪的人之外,其中一位是本书的技术编辑之一),但日志记录和监控是任何安装的重要组成部分,无论大小。

您想知道您的盒子何时死掉,或者更好的是,当它们即将死掉时,您还想能够事后找出它们为什么会挂掉。

监控和日志记录可以像你想要的那样复杂或简单。一些公司会雇佣特定的人来处理这些组件,但在较小的组织中,很可能是你最终会负责管理和配置所有这些。如果是这种情况,我目前建议设置 Icinga2 和某种 Elastic Stack 实现,但你的需求和预算可能会有所不同。

我们需要谈谈一个悬而未决的问题,那就是值班,以及你很可能在职业生涯中的某个时刻要做这件事(除非你已经到了可以说“我已经尽过我的职责”并把它留给次要的凡人去承担的幸运时刻)。

总的来说,当进行值班时,监控是你的朋友。在理想的情况下,你不会因为问题而被叫出去,但至少你可以设置自动电话呼叫来唤醒你,以防某些问题在公司之外被其他人注意到之前就变成更大的问题。你不希望出现这样的情况,即公司网站整个周末都挂掉,导致你损失成千上万的销售额。

随着时间的推移,日志和长期监控数据也可以帮助发现你没有意识到的反复出现的问题,因为事件之间的时间间隔是几周甚至几个月;这是在 Kibana 的仪表板上设置历史警报和模式匹配的一个很好的理由。

如果有人每 5 周就不得不清理一次盒子上的日志,而且每次都是团队中的不同人,你可能没有意识到有一个更大的潜在问题需要解决,或者你可能会发现你因为一个可以用简单的systemd定时器解决的问题而浪费了数百个小时。

总的来说,我讨厌监控,我不愿意翻阅日志,但在我们的工作中是必要的,而且有很多聪明人在制作非常好用的工具,可以让你的生活变得更轻松。

当你不得不向 CEO 展示仪表板时,拥有漂亮的小部件和功能也是有好处的。

第八章:权限、SELinux 和 AppArmor

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

  • Linux 文件权限

  • 修改文件权限

  • 用户和组

  • AppArmor 和修改

  • SELinux 和修改

  • 检查 SELinux 是否正在运行,以及保持其运行的重要性

  • 重置 SELinux 权限

介绍

在早期,早在 90 年代的迷雾中,Linux 在访问控制方面并不多……然后是权限和属性。权限和属性是文件的元素,它们决定了系统和用户对该文件(或文件夹)的访问权限,以及在交互方面对文件的操作能力。在基本水平上,您可以使用ls查看权限信息(稍后会详细介绍),但现在先看以下示例:

$ ls -l .
total 0
-rw-rw-r--. 1 vagrant vagrant 0 Oct 28 10:42 examplefile

在本章中,我们将学习从基本的 Linux 权限到 SELinux 和 AppArmor。我们还将探讨可能由 SELinux 或 AppArmor 引起的故障排除问题。我们还将学习不要禁用扩展权限控制的重要性。

在安全方面,锁定系统显然很重要,而在极端情况下,您可以创建一个系统,其中每个程序都对其他程序一无所知(实际上使每个程序都被隔离)。

虽然安全性从来都不是坏事,但平衡至关重要。您不希望开始为 Ubuntu 安装中的每个文件的权限而感到紧张,那里有成千上万个文件,除非您在完成之前就疯了……除非这确实是您唯一的工作,或者您想要一个特别乏味的爱好,否则就放手去做吧!

技术要求

在本章中,我们将使用以下Vagrantfile;请注意,我们只使用两台机器:CentOS 突出显示 SELinux 的功能和能力,以及 Ubuntu 安装用于 AppArmor:

# -*- mode: ruby -*-
# vi: set ft=ruby :

$provisionScript = <<-SCRIPT
sed -i 's/console=tty0 console=ttyS0,115200n8//g' /boot/grub2/grub.cfg
systemctl restart sshd
SCRIPT

Vagrant.configure("2") do |config|

 config.vm.define "centos7" do |centos7|
 centos7.vm.box = "centos/7"
 centos7.vm.box_version = "1804.02"
 centos7.vm.provision "shell",
 inline: $provisionScript
 end

 config.vm.define "ubuntu1804" do |ubuntu1804|
 ubuntu1804.vm.box = "ubuntu/bionic64"
 ubuntu1804.vm.box_version = "20180927.0.0"
 end

end

在撰写本文时,此处使用的provisionScript是为了修复本章中一个部分的轻微问题。如果您在使用此脚本时遇到问题,请随时从配置中删除它(在相关部分中稍后会有一条注释,我们会在那里讨论.autorelabel)。

Linux 文件权限

首先,我们将回到基础知识,看一下默认的 Linux 文件权限。

在本节中,我们将使用 CentOS 框上的一个文件和一个目录,以突出一些重要的基本知识,这些知识可以帮助我们继续前进。

Unix 和类 Unix 系统上的文件权限与 Windows 和其他操作系统安装中的文件权限不同。如果您将使用 Unix 文件系统(如 XFS)格式化的硬盘连接到 Windows 框,它可能无法准确读取文件的权限(除非您有软件可以为您执行此操作)。近年来,由于 Windows 10 中包含的 Windows 子系统等因素,这些界限已经有所模糊,但基本原则基本上是正确的。

准备工作

跳到您的 CentOS 框。在本节中,我们讨论的所有内容都适用于 Linux 发行版:

$ vagrant ssh centos7

按照以下方式创建一个文件、一个目录和该目录中的一个文件:

$ touch examplefile
$ mkdir exampledir
$ touch exampledir/examplefile-in-exampledir

操作步骤

准备工作部分的文件就绪后,运行ls -l查看我们创建的内容:

$ ls -l
total 0
drwxrwxr-x. 2 vagrant vagrant 39 Oct 28 11:01 exampledir
-rw-rw-r--. 1 vagrant vagrant 0 Oct 28 11:00 examplefile

此处使用的-l表示使用长列表格式,并且不仅用于打印找到的文件和文件夹,还用于为我们提供更完整的图片。

工作原理

我们需要对此进行详细说明,因为乍一看,它可能会显得相当令人困惑:

exampledir

exampledir开始,让我们看看这个目录的权限和所有权。

drwxrwxr-x. 2 vagrant vagrant

我们有一系列字母、一个数字2,然后是两个名字,vagrantvagrant

drwxrwxr-x.

开头的d很容易理解;它表示列出的项目实际上是一个目录。

drwxrwxr-x.

然后,我们有三个看起来相似的元素,其中第一个是用户权限。在这里,权限是读、写和执行。

这意味着用户将能够在目录中 touch(创建)文件,mv(重命名)它们,ls(列出)它们,cat/less(读取)它们,甚至 rm(删除)它们,如果他们愿意的话。

drwxrwxr-x.

接下来,我们有组权限,这里再次是读、写和执行。

drwxrwxr-x.

第三,我们有每个人的权限,这种情况下任何人都可以读取或进入目录。

他们将无法创建、重命名或删除现有文件,因为他们没有写 (w) 权限。

即使是有经验的系统管理员也会忘记这一点。如果你在一个可以访问目录中文件内容的组中,但目录本身的权限不允许这样做,你将无法完成操作。我听到一些与这个小提示相关的相当显著的叹息声。

我们还有块末尾的 .。现在我们不用太担心这个,但它表示目录已经应用了安全上下文:

drwxrwxr-x. 2

在这种情况下,数字 2 指的是指向索引节点的位置的数量(实际存储数据的磁盘上的位置)。在这种情况下为什么是 2 是因为每次创建一个目录时都会创建两个条目,可以用 ls -la 查看:

$ ls -la exampledir/
total 0
drwxrwxr-x. 2 vagrant vagrant 39 Oct 28 11:18 .
drwx------. 4 vagrant vagrant 132 Oct 28 11:01 ..
-rw-rw-r--. 1 vagrant vagrant 0 Oct 28 11:18 examplefile-in-exampledir

在这里,我们可以看到两个特殊条目,...,分别指代这个目录和父目录。

因此,有两个链接指向这个目录;第一个是来自父目录 (/home/vagrant/exampledir),第二个是来自目录本身 (/home/vagrant/exampledir/.)。搞糊涂了吗?

现在是一个更容易的部分,vagrant vagrant 条目:

vagrant vagrant

这些只是用户,然后是组,他们的权限反映在 drwxrwxr-x. 块中。没有每个人的条目,因为那样就没有意义了。

示例文件

继续讨论 examplefile,我们有以下内容:

-rw-rw-r--. 1 vagrant vagrant  0 Oct 28 11:00 examplefile

在这里,我们可以看到与 exampledir 几乎相同,有一些变化。

da 替换了,意味着我们正在处理一个实际文件。

-rw-rw-r--.

用户和组的权限只有读和写,这意味着文件可以被读取和修改,但用户和组还不能执行。

-rw-rw-r--.

其他所有人的权限只有读,这意味着文件可以使用 cat/less,但不能被修改或执行。

-rw-rw-r--. 1

最后,我们可以看到链接数为 1,这是有道理的,因为底层索引节点没有从其他地方引用。

还有更多...

还有一些有用的东西要提一下,即使我们在这里没有涉及。

对目录和文件的根访问

god/super/almighty 用户 (root) 几乎对系统上的所有东西都有完全的访问权限,这意味着你可能会看到人们采取的一个常见快捷方式是以下内容,如果他们对无法读取文件感到沮丧:

$ sudo cat examplefile

这将起作用,因为 root 有这个权限,但是养成使用 sudo 处理所有事情的坏习惯是不好的。要有选择地使用它,并在任意在命令前加上 sudo 之前考虑一下你在做什么。 (大多数情况下,这是对我自己的一条信息,因为我和其他人一样,也经常犯这个错误。)

其他执行字符

在执行列中,除了普通的 x 外,还可能看到其他字符,其中最常见的是 st

看看 wall 程序的这些权限:

$ ls -l /usr/bin/wall
-r-xr-sr-x. 1 root tty 15344 Jun 9 2014 /usr/bin/wall

请注意组中的 s 替代了 x

这被称为 setuidsetgid 位,取决于它是在用户还是组三元组中,它有效地将执行用户的权限更改为所有者或组的权限,再次取决于三元组。在这种情况下,执行 wall 命令的用户获得 tty 组的权限(允许 wall 输出到所有 tty)。

在这里,我正在使用 wall 作为 vagrant 用户:

$ wall There is no Hitchhikers Movie! 
$ 
Broadcast message from vagrant@localhost.localdomain (pts/0) (Sun Oct 28 11:52:12 2018):

There is no Hitchhikers Movie!

t 条目,或者叫做粘性位,再次非常罕见,但它最常设置在 /tmp 目录上:

$ ls -la /tmp
total 0
drwxrwxrwt. 8 root root 172 Oct 28 11:54 .
<SNIP>

记住.字符指的是这个目录。

它设置了只有/tmp中文件的所有者才能重命名或删除该文件,这意味着如果我以vagrant用户的身份在/tmp中创建文件,其他人就不能来删除我的文件(除了root)。在视觉上,它看起来像下面这样:

$ rm /tmp/test2 
rm: remove write-protected regular empty file '/tmp/test2'? y
rm: cannot remove '/tmp/test2': Operation not permitted

还有其他两个执行字符,但这些是最常见的。

修改文件权限

创建文件是很好的,但最终我们会遇到默认权限不可接受的情况。

一个很好的例子是 SSH,除非在你的公钥和私钥上有一些特别严格的文件权限,否则它根本不会工作。

所以,"三剑客"来了,以chownchmodchattr的形式。

如果你想要真的很烦人,并且容易失去朋友,坚持称呼这些为它们的全称:改变所有权,改变模式和改变属性。

准备工作

在本节中,我们将再次使用我们的Vagrantfile中的 CentOS VM,因为我们所做的一切都是普遍适用的。

SSH 到你的 CentOS VM:

$ vagrant ssh centos7

进入/home目录(上一级)并创建一个文件,一个目录,以及该目录中的一个文件:

$ cd /home
$ sudo touch permissionfile
$ sudo mkdir permissiondir
$ sudo touch permissiondir/permissionfile-in-permissiondir

我们还将创建另一个虚拟用户,我们可以用来解释本节中正在做的事情:

$ sudo adduser packt -s /bin/bash -p '$1$2QzaOp2Q$Ke2yWZ1N2h4rk8r8P95Sv/'

请注意,我们设置的密码是'correcthorsebatterystaple'。

如何做...

我们将按顺序运行三个命令(chownchmodchattr)。

chown

从最简单的部分开始,我们将查看所讨论文件的所有权。

首先列出我们已经拥有的内容:

$ ls -lha
total 0
drwxr-xr-x. 4 root root 64 Oct 28 12:37 .
dr-xr-xr-x. 18 root root 239 Oct 28 12:35 ..
drwxr-xr-x. 2 root root 45 Oct 28 12:37 permissiondir
-rw-r--r--. 1 root root 0 Oct 28 12:37 permissionfile
drwx------. 3 vagrant vagrant 74 May 12 18:54 vagrant

假设我们想让我们的 vagrant 用户可以写入permissionfile,而不是当前只能读取它的能力。请注意以下内容:

$ echo "RFCs are great if boring." > permissionfile
-bash: permissionfile: Permission denied

我们将使用chown进行更改,通过传递我们想要将文件更改为的用户和组:

$ sudo chown vagrant:root permissionfile

现在,检查权限:

$ ls -l permissionfile
-rw-r--r--. 1 vagrant root 0 Oct 28 12:37 permissionfile

这意味着我们作为 vagrant 用户现在可以写入文件:

$ echo "RFCs are great if boring." > permissionfile
$ cat permissionfile
RFCs are great if boring.

但是,其他用户(不是root)无法写入文件:

$ su - packt -c "echo IMPOSSIBLE > /home/permissionfile"
Password: 
-bash: /home/permissionfile: Permission denied

在这里,我们使用su以 Packt 用户的身份执行命令,并且我们展示了尽管我们尝试向文件echo IMPOSSIBLE,但失败了。我们使用了permissionfile的完整路径,以确保我们没有在 Packt 用户的home目录中创建文件。

chmod

我们在这里对旧的 Packt 用户有点不公平,所以让我们给每个人都有写入文件的能力,而不仅仅是vagrant

$ sudo chmod 646 permissionfile $ ls -l permissionfile
-rw-r--rw-. 1 vagrant root 26 Oct 28 12:48 permissionfile

现在,我们应该能够像任何用户一样写入文件,而不仅仅是 vagrant:

$ su - packt -c "echo POSSIBLE > /home/permissionfile"
Password: 
$ cat permissionfile 
POSSIBLE

chattr

我开始觉得我们在这里太宽容了,所以让我们完全锁定文件,这样没有人(甚至是全能的root)都不能乱动它:

$ sudo chattr +i permissionfile

我们已经使文件不可变!

$ echo "RFCs are great if boring." > permissionfile
-bash: permissionfile: Permission denied

我们可以使用lsattr命令来查看这一点:

$ lsattr permissionfile
----i----------- permissionfile

甚至root也无法修改文件:

$ sudo echo "RFCs are great if boring." > permissionfile
-bash: permissionfile: Permission denied

chattr可以应用各种属性到文件上,但我敢打赌不可变选项是最常用的。

要删除属性,再次使用chattr

$ sudo chattr -i permissionfile

它是如何工作的...

再次运行每个命令,让我们简要看一下我们做了什么。

chown

首先,我们改变了文件的所有权:

$ sudo chown vagrant:root permissionfile

在这里,我们以最基本的方式使用chown,指定文件应属于哪个用户和组。这些值是用冒号分隔的,尽管如果你像我一样保守,偶尔会使用已弃用和不正确的句号(.)。

如果你只想保留组,你可以只指定一个用户:

$ sudo chown vagrant permissionfile

chmod

接下来,我们更改了我们的文件,以便任何人都可以写入它:

$ sudo chmod 646 permissionfile

在这里,我们传递了一些八进制值给permissionfile,以便依次更改用户、组和其他人的权限。

我不会详细介绍这一点,但实际上,第一个数字表示用户三元组应该是什么值,然后是组的三元组,然后是其他人。

我们的用户得到了6的值,这意味着读/写;我们的组只能读取4,其他人可以读/写6

这是因为每个值都有一个数字等价物,如下所示:

  • x = 1

  • w = 2

  • r = 4

所以,6值是4+2,或者r/w,而4值只是r

你可以设置777,这意味着对所有事物和所有人都有r/w/x权限,这经常是由不理解文件权限的人所做的。这不是一个好的做法,应该在故障排除之外加以阻止。如果我发现有人在生产环境中对文件运行了chmod 777,那么这个人将被取消访问权限,并且会在他们的日历中快速介绍权限。

chattr

最后,我们改变了文件的一个属性,具体是使文件对root甚至是不可变的,然后我们再次移除了标志。

除了不可变之外,chattr主页中列出了许多其他标志,其中一些在特定情况下可能会有用:

  • a:文件只能被追加(对日志有用)

  • c:透明压缩和解压

  • s:导致文件的块在文件删除时被清零并写回磁盘

并非所有属性都受到所有文件系统的尊重;检查你的文件系统是否也支持它们(提示:ext4不支持很多)。

还有更多...

在我们结束本节之前,还有一两件事情需要注意。

在 chmod 中避免八进制表示法(如果你讨厌它)

chmod世界中,你并不一定非要使用八进制格式;它确实给了你其他更容易阅读的选项:

$ sudo chmod uo=rw,g=r permissionfile

前面的命令会给用户和其他人读/写权限,给组读权限。

或者,你可以向权限添加一个值:

$ sudo chmod g+x permissionfile 

这将授予组额外的执行文件的能力:

$ ls -l permissionfile
-rw-r-xrw-. 1 vagrant root 26 Oct 28 13:03 permissionfile

分层权限

我们创建了一个目录,并在该目录中创建了一个文件,所以让我们快速了解一下理解目录权限。

首先,我们的permissiondir看起来是这样的:

$ ls -la permissiondir
total 0
drwxr-xr-x. 2 root root 45 Oct 28 12:37 .
drwxr-xr-x. 5 root root 77 Oct 28 12:37 ..
-rw-r--r--. 1 root root 0 Oct 28 12:37 permissionfile-in-permissiondir

尽管我们想要,但我们目前无法重命名这个文件,因为它太长了:

$ mv permissiondir/permissionfile-in-permissiondir permissiondir/permissionfile2
mv: cannot move 'permissiondir/permissionfile-in-permissiondir' to 'permissiondir/permissionfile2': Permission denied

所以,让我们为这个文件设置所有人的写权限:

$ sudo chmod 646 permissiondir/permissionfile-in-permissiondir

现在,让我们再试一次:

$ mv permissiondir/permissionfile-in-permissiondir permissiondir/permissionfile2
mv: cannot move 'permissiondir/permissionfile-in-permissiondir' to 'permissiondir/permissionfile2': Permission denied

嗯。

好的,这是因为实际上是目录权限阻止我们移动文件,而不是文件权限。我们必须修改包含文件的目录,因为权限不允许我们重命名(mv)文件:

$ sudo chmod 667 permissiondir/

现在我们应该能够移动文件了,因为我们的权限现在非常宽松:

$ mv permissiondir/permissionfile-in-permissiondir permissiondir/permissionfile2

成功!

另请参阅

在本节中我们没有涵盖的一件事是访问控制列表ACLs),它可以用来进一步扩展文件的权限。

首先在我们的permissionfile中放入一个小命令来执行某些操作:

$ echo "printf 'Fire indeed hot'" > permissionfile

假设我们想要查看文件的整个访问控制列表;我们将使用getfacl

$ getfacl permissionfile 
# file: permissionfile
# owner: vagrant
# group: root
user::rw-
group::r-x
other::rw-

在这里,我们可以看到所有者是vagrant,用户有rw

但是,如果我们希望 Packt 能够执行该文件,而不影响其他权限呢?目前,Packt 不能,因为它不在root组中。

一个潜在的解决方案是setfacl

$ setfacl -m u:packt:rwx permissionfile

现在我们可以看到ls中有一个小+号,显示我们的文件有扩展的访问控制:

$ ls -l permissionfile
-rw-r-xrw-+ 1 vagrant root 26 Oct 28 13:03 permissionfile

而且,我们可以再次使用getfacl来查看这些:

$ getfacl permissionfile 
# file: permissionfile
# owner: vagrant
# group: root
user::rw-
user:packt:rwx
group::r-x
mask::rwx
other::rw-

这意味着我们的vagrant用户无法执行该文件:

$ ./permissionfile
-bash: ./permissionfile: Permission denied

但是,我们的 Packt 用户可以:

$ su - packt -c "/home/permissionfile" 
Password: 
Fire indeed hot

技术要求

在本节中,我们将跳转到我们的 CentOS 和 Ubuntu 虚拟机上,以突出用户和组处理方法上的一些重要差异。

用户和组

我们已经涵盖了文件权限方面的用户和组,但是简要地回顾一下我们对用户和组的了解是个好主意。

在本节中,我们将深入探讨用户和组的简要介绍,确定进程正在以哪个用户运行,它如何更改为该用户,并通过使用/etc/passwd和类似命令来查找系统中存在哪些用户。

准备工作

使用 Vagrant 连接到你的 Ubuntu 和 CentOS 虚拟机,在不同的窗口中或者依次进行:

$ vagrant ssh centos7 $ vagrant ssh ubuntu1804

如何做...

在几个简短的部分中,我们将看一下用户和组的不同元素。

whoami

如果你需要知道你是谁,通过深层反思和内心沉思来问问自己。

如果你需要知道有哪些用户登录到服务器上(或者以某个用户身份运行命令),这将会更容易:

$ whoami
vagrant $ sudo whoami
root

系统上的用户

要显示系统上有哪些用户,请查看/etc/passwd

在 CentOS 上,它看起来会像这样:

$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/sbin/nologin
daemon:x:2:2:daemon:/sbin:/sbin/nologin
adm:x:3:4:adm:/var/adm:/sbin/nologin
lp:x:4:7:lp:/var/spool/lpd:/sbin/nologin
sync:x:5:0:sync:/sbin:/bin/sync
shutdown:x:6:0:shutdown:/sbin:/sbin/shutdown
halt:x:7:0:halt:/sbin:/sbin/halt
mail:x:8:12:mail:/var/spool/mail:/sbin/nologin
operator:x:11:0:operator:/root:/sbin/nologin
games:x:12:100:games:/usr/games:/sbin/nologin
ftp:x:14:50:FTP User:/var/ftp:/sbin/nologin
nobody:x:99:99:Nobody:/:/sbin/nologin
systemd-network:x:192:192:systemd Network Management:/:/sbin/nologin
dbus:x:81:81:System message bus:/:/sbin/nologin
polkitd:x:999:998:User for polkitd:/:/sbin/nologin
rpc:x:32:32:Rpcbind Daemon:/var/lib/rpcbind:/sbin/nologin
rpcuser:x:29:29:RPC Service User:/var/lib/nfs:/sbin/nologin
nfsnobody:x:65534:65534:Anonymous NFS User:/var/lib/nfs:/sbin/nologin
sshd:x:74:74:Privilege-separated SSH:/var/empty/sshd:/sbin/nologin
postfix:x:89:89::/var/spool/postfix:/sbin/nologin
chrony:x:998:996::/var/lib/chrony:/sbin/nologin
vagrant:x:1000:1000:vagrant:/home/vagrant:/bin/bash
packt:x:1001:1001::/home/packt:/bin/bash

而在 Ubuntu 上,它看起来会像这样:

$ cat /etc/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
syslog:x:102:106::/home/syslog:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin
lxd:x:105:65534::/var/lib/lxd/:/bin/false
uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin
dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin
sshd:x:109:65534::/run/sshd:/usr/sbin/nologin
pollinate:x:110:1::/var/cache/pollinate:/bin/false
vagrant:x:1000:1000:,,,:/home/vagrant:/bin/bash
ubuntu:x:1001:1001:Ubuntu:/home/ubuntu:/bin/bash

大多数这些用户你不会自己创建;它们大部分是系统用户,或者与你安装的软件捆绑在一起。

系统上的组

组的发现方式与用户类似,同样,你不会创建大部分组。

对于 CentOS,请注意以下内容:

$ cat /etc/group
root:x:0:
bin:x:1:
daemon:x:2:
sys:x:3:
adm:x:4:
tty:x:5:
disk:x:6:
lp:x:7:
mem:x:8:
kmem:x:9:
wheel:x:10:
<SNIP>
postfix:x:89:
chrony:x:996:
screen:x:84:
vagrant:x:1000:vagrant
packt:x:1001:

对于 Ubuntu,请注意以下内容:

$ cat /etc/group
root:x:0:
daemon:x:1:
bin:x:2:
sys:x:3:
adm:x:4:syslog,ubuntu
tty:x:5:
<SNIP>
landscape:x:112:
admin:x:113:
netdev:x:114:ubuntu
vboxsf:x:115:
vagrant:x:1000:
ubuntu:x:1001:

我已经加粗了这个 Ubuntu 和 CentOS 系统之间的第一个重大区别,即wheeladmin组。wheel在我们的 Ubuntu 系统上不存在,因为它已被admin组取代;这意味着 Ubuntu 上的visudo文件引用了admin组的成员,而不是wheel。记住这一点。

使用用户的守护进程

在我们的 Ubuntu 系统上,syslogd守护进程是使用syslog用户运行的。

我们可以通过定位我们的rsyslogd进程并检查最左边列中的用户来确认这一点:

$ pidof rsyslogd
917
$ ps -up 917
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
syslog 917 0.0 0.4 263036 4416 ? Ssl 10:41 0:00 /usr/sbin/rsyslogd -n

我们可以通过查看/etc/rsyslog.conf配置文件来找到这个用户是如何被发现的:

$ grep PrivDrop /etc/rsyslog.conf
$PrivDropToUser syslog
$PrivDropToGroup syslog

如果你想快速排除以root身份运行的进程,你可以使用一个快速的一行命令,比如下面的(尽管这并不完美)。

这是我们的 CentOS 虚拟机上的情况:

$ ps aux | grep -v root
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
dbus 558 0.0 0.5 66428 2568 ? Ssl 12:34 0:00 /usr/bin/dbus-daemon --system --address=systemd: --nofork --nopidfile --systemd-activation
rpc 559 0.0 0.2 69220 1060 ? Ss 12:34 0:00 /sbin/rpcbind -w
polkitd 568 0.0 1.6 538436 8020 ? Ssl 12:34 0:00 /usr/lib/polkit-1/polkitd --no-debug
chrony 581 0.0 0.3 117752 1828 ? S 12:34 0:00 /usr/sbin/chronyd
postfix 1088 0.0 0.8 89792 4080 ? S 12:35 0:00 qmgr -l -t unix -u
vagrant 3369 0.0 0.5 154904 2688 ? S 14:11 0:00 sshd: vagrant@pts/0
vagrant 3370 0.0 0.5 15776 2660 pts/0 Ss 14:11 0:00 -bash
postfix 3399 0.0 0.8 89724 4052 ? S 14:15 0:00 pickup -l -t unix -u
vagrant 3404 0.0 0.3 55140 1872 pts/0 R+ 14:32 0:00 ps aux

它是如何工作的...

通常,不同的用户和组将具有特定的用途,有意地分隔开来,以便它们在自己的权利范围内不会太强大。如果你有一个多租户系统(这在今天非常罕见),有多个人登录进行日常工作,你希望确保这个人不能通过做一些愚蠢的事情,比如覆盖盒子上的日志,让其他人的生活变得更加困难。

你可以通过将所有人类用户放在一个组中来解决这个问题,然后允许他们拥有自己的有限访问权限的用户,然后给予该组访问共享目录和他们可能需要使用的应用程序的权限。

进程有选择放弃它们的特权的选项,尽管并非所有进程都会默认这样做,如果你想再走这一步,通常需要大量工作来设置。在这里,我们看到syslog启动(作为root),然后立即降低自己的特权级别到syslog用户和组的级别。

rsyslogd必须以root身份启动的原因是因为它绑定到低于1024的端口,这些端口是只有root程序可以访问的受限端口。

一些发行版和操作系统对此的处理方式比其他的更加严格,但就像所有与安全相关的事情一样,这就像是安全的洋葱的另一层。

还有更多...

看看你的 Ubuntu 虚拟机上的这个用户:

$ grep apt /etc/passwd
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin

它有一个下划线,在整个/etc/passwd文件中是唯一一个有下划线的;这可能是为什么呢?

一个潜在的原因是它是一个系统账户,应用程序维护者或开发者决定用下划线字符表示这一点,就像其他操作系统一样。

AppArmor 和修改

在本节中,我们将在 Ubuntu 上使用 AppArmor,并确定它对我们的系统有什么影响。

AppArmor 默认安装在 Ubuntu 上。它最初是由 SUSE 开发的,但 Canonical 似乎已经坚定地将他们的旗帜插在了 AppArmor 星球上,在 Ubuntu 7.04 中引入了它,并在 7.10(2007 年)中默认启用了它。

像 SELinux 一样,AppArmor 是将强制访问控制(MAC)引入 Linux 的一种方式;它自 2.6.36 内核以来就已经包含在内。

准备工作

在本节中,我们将使用我们的 Ubuntu 虚拟机。

SSH 到你的 Ubuntu 虚拟机:

$ vagrant ssh ubuntu1804

如何做...

首先,让我们确保apparmor正在运行,使用我们的老朋友systemctl

$ systemctl status apparmor
● apparmor.service - AppArmor initialization
 Loaded: loaded (/lib/systemd/system/apparmor.service; enabled; vendor preset: enabled)
 Active: active (exited) since Sun 2018-10-28 10:41:23 UTC; 4h 21min ago
 Docs: man:apparmor(7)
 http://wiki.apparmor.net/
 Process: 426 ExecStart=/etc/init.d/apparmor start (code=exited, status=0/SUCCESS)
 Main PID: 426 (code=exited, status=0/SUCCESS)

Warning: Journal has been rotated since unit was started. Log output is incomplete or unavailable.

要查看加载了哪些配置文件以及它们运行在什么模式下,使用apparmor_status

$ sudo apparmor_status 
apparmor module is loaded.
15 profiles are loaded.
15 profiles are in enforce mode.
 /sbin/dhclient
 /usr/bin/lxc-start
 /usr/bin/man
 /usr/lib/NetworkManager/nm-dhcp-client.action
 /usr/lib/NetworkManager/nm-dhcp-helper
 /usr/lib/connman/scripts/dhclient-script
 /usr/lib/snapd/snap-confine
 /usr/lib/snapd/snap-confine//mount-namespace-capture-helper
 /usr/sbin/tcpdump
 lxc-container-default
 lxc-container-default-cgns
 lxc-container-default-with-mounting
 lxc-container-default-with-nesting
 man_filter
 man_groff
0 profiles are in complain mode.
0 processes have profiles defined.
0 processes are in enforce mode.
0 processes are in complain mode.
0 processes are unconfined but have a profile defined.

要了解 AppArmor 如何限制应用程序,让我们对tcpdump配置文件进行修改并重新启动 AppArmor:

$ sudo sed -i 's/capability net_raw,/#capability net_raw,/g' /etc/apparmor.d/usr.sbin.tcpdump
$ sudo systemctl restart apparmor

在这里我们做的是删除tcpdump捕获的能力,使其变得相当无用:

$ sudo tcpdump -i enp0s3
tcpdump: enp0s3: You don't have permission to capture on that device
(socket: Operation not permitted)

如果我们查看内核日志,我们可以看到我们试图运行tcpdump时的拒绝:

$ sudo journalctl -k --since 15:34 --no-pager
-- Logs begin at Sun 2018-10-28 10:41:21 UTC, end at Sun 2018-10-28 15:39:29 UTC. --
Oct 28 15:34:34 ubuntu-bionic kernel: kauditd_printk_skb: 6 callbacks suppressed
Oct 28 15:34:34 ubuntu-bionic kernel: audit: type=1400 audit(1540740874.554:97): apparmor="DENIED" operation="capable" profile="/usr/sbin/tcpdump" pid=3365 comm="tcpdump" capability=13 capname="net_raw"

请注意我们之前用sed删除的net_raw能力名称。

它是如何工作的...

AppArmor 的配置文件是使用apparmor_parser程序编写并加载到内核中的。大多数情况下,这些配置文件将位于/etc/apparmor.d/中;尽管如果一个程序没有配置文件,AppArmor 也不会阻止它运行。

当实际的 systemd 单元启动时,会运行一个init.d脚本(位于/etc/init.d/apparmor),该脚本会实际调用apparmor_parser

当配置文件以强制执行模式运行时,就像前面的十五个配置文件一样,它们必须遵守策略定义,否则它们将无法在策略要求之外行事,并且违规行为将被记录。如果配置文件处于投诉模式,则策略不会被执行,但违规行为将被记录以供以后审查。

配置文件通常以用点替换可执行文件的斜杠位置来命名:

/sbin/dhclient -> sbin.dhclient
/usr/sbin/tcpdump -> usr.sbin.tcpdump

如果我们看一下tcpdump配置文件的前几行,我们就可以开始看到配置文件是如何构建的:

$ cat /etc/apparmor.d/usr.sbin.tcpdump 
# vim:syntax=apparmor
#include <tunables/global>

/usr/sbin/tcpdump {
 #include <abstractions/base>
 #include <abstractions/nameservice>
 #include <abstractions/user-tmp>

 #capability net_raw,
 capability setuid,
 capability setgid,
 capability dac_override,
 network raw,
 network packet,

 # for -D
 @{PROC}/bus/usb/ r,
 @{PROC}/bus/usb/** r,
<SNIP>

我们可以首先看到指定了二进制文件的名称,然后是一些包括的内容(这些规则也可以在其他程序中使用)。

接下来,我们有capability,包括我们注释掉的那个。有一系列的 capabilities,可以在man (7) capabilities页面上查看,其中列出了像CAP_NET_RAWCAP_SETGID这样的名称,但这里它们都是小写。

当我们删除了这个capability时,tcpdump失去了使用 RAW 和 PACKET sockets 的能力,以及绑定到任何地址进行透明代理的能力。

在更下面,我们可以看到文件的作者如何使用注释和tcpdump的标志来描述他们允许的权限。在下面的例子中,他们特别允许使用gzipbzip2,以便-z选项起作用:

 # for -z
 /{usr/,}bin/gzip ixr,
 /{usr/,}bin/bzip2 ixr,

可以使用令人惊讶的详细的apparmor.d手册页来比较和理解语法。

还有更多...

虽然 AppArmor 很好,它确实做到了它所宣传的,但也有一些注意事项:

  • 它依赖于开发人员编写和提供配置文件(或其他人贡献时间)

  • 在默认安装中包含配置文件之前,配置文件必须是无懈可击的,这可能是十年后仍然如此之少的原因

  • 它相当不为人知,大多数人甚至在默认情况下都不会去理会它

它也会偏离路径,而不是 inode,这意味着你可以做一些事情,比如创建一个硬链接来绕过限制:

$ sudo ln /usr/sbin/tcpdump /usr/sbin/tcpdump-clone

诚然,如果你在一个盒子上并且有sudo,那么在那一点上游戏基本上就结束了:

$ sudo tcpdump -i enp0s3
tcpdump: enp0s3: You don't have permission to capture on that device
(socket: Operation not permitted)
$ sudo tcpdump-clone -i enp0s3
tcpdump-clone: verbose output suppressed, use -v or -vv for full protocol decode
listening on enp0s3, link-type EN10MB (Ethernet), capture size 262144 bytes
15:52:52.803301 IP ubuntu-bionic.ssh > _gateway.37936: Flags [P.], seq 410213354:410213518, ack 1991801602, win 36720, length 164
<SNIP>

你可能会问为什么你的系统需要这样的东西,如果它很容易调整和绕过,但答案相对简单。

如果你在公共互联网上有一个 web 服务器,很有可能它会在某个时候受到攻击,当这种情况发生时,你可能已经完全更新,并受到了零日漏洞的攻击(尽管可能性很小)。然后你的 web 服务器可能会被攻破,攻击你的个人可能会利用它来尝试建立一个运行在不同端口上的不同进程,甚至利用它开始读取它不应该读取的文件。

强制访问控制在很大程度上确保了这种情况不会发生,对于攻击的另一方来说,生活变得更加沮丧。他们可能攻击了你的 web 服务器,但那就是他们所能做的。

SELinux 和修改

像 AppArmor 一样,安全增强型 LinuxSELinux)是一种在 Linux 中引入强制访问控制的方式,只是它有一些关键的不同:

  • 它比 AppArmor 更广泛使用和令人讨厌

  • 它主要用于基于 Red Hat 的发行版

如果你在企业世界中,或者正在考虑进入那里,SELinux 是一个很好的工具,可以添加到你的工具箱中。

你可能还记得我们之前已经提到过 SELinux,做了一些小的更改,允许诸如 SSH 在不同端口上运行;在这里,我们进一步探讨了它。

准备工作

在本节中,我们将使用我们的 CentOS 虚拟机。

通过 SSH 连接到你的 CentOS 虚拟机,转发8080

$ vagrant ssh centos7 -- -L 127.0.0.1:5858:127.0.0.1:5858

确保为 NGINX 和一些实用程序安装了,并且为这个示例启动了 NGINX:

$ sudo yum install epel-release -y
$ sudo yum install policycoreutils-python setroubleshoot -y
$ sudo yum install nginx -y
$ sudo systemctl enable --now nginx

如何做...

我们要改变 NGINX 默认监听的端口,以展示 SELinux 有多么让人头疼。

首先,通过使用curl并打印返回码来检查 NGINX 是否在端口80(默认端口)上运行:

$ curl -I localhost:80 
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Mon, 29 Oct 2018 17:36:35 GMT
Content-Type: text/html
Content-Length: 3700
Last-Modified: Tue, 06 Mar 2018 09:26:21 GMT
Connection: keep-alive
ETag: "5a9e5ebd-e74"
Accept-Ranges: bytes

在这里使用-I意味着我们不会拉入一屏幕的代码,而是只获取相关信息,比如返回码(200表示 OK)。

很好,所以一切都正常工作,SELinux 没有阻碍。

如果我们想让 NGINX 监听不同的端口呢?比如我们转发的那个?让我们试试:

$ sudo sed -i 's/80 default_server;/5858 default_server;/g' /etc/nginx/nginx.conf
$ sudo systemctl restart nginx
Job for nginx.service failed because the control process exited with error code. See "systemctl status nginx.service" and "journalctl -xe" for details.

再次运行我们的curl命令,使用新端口应该会报错(显然,因为服务启动失败):

$ curl -I localhost:5858
curl: (7) Failed connect to localhost:5858; Connection refused

奇怪...但也不是真的。

这是因为 NGINX 只允许在某些端口上运行,80是其中一个,8080是另一个,等等。5858是奇怪和怪异的;为什么一个 Web 服务器要在上面运行?

因此,我们必须更新 SELinux 以允许 NGINX 在新端口上运行:

$ sudo semanage port --add --type http_port_t --proto tcp 5858
ValueError: Port tcp/5858 already defined

哦该死,看起来5858已经为其他东西定义了(在这种情况下是 Node.js - 诅咒你 Node.js!)。

幸运的是,这并不是世界末日,我们只需要修改端口而不是添加一个:

$ sudo semanage port --modify --type http_port_t --proto tcp 5858

现在,我们可以重新启动 NGINX,应该可以正常工作:

$ sudo systemctl restart nginx
$ curl -I localhost:5858
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Mon, 29 Oct 2018 18:17:37 GMT
Content-Type: text/html
Content-Length: 3700
Last-Modified: Tue, 06 Mar 2018 09:26:21 GMT
Connection: keep-alive
ETag: "5a9e5ebd-e74"
Accept-Ranges: bytes

你也可以在浏览器中访问它:

是的,它说的是 Fedora,而且是错误的。

所以,这是第一步,但现在我们决定,不使用默认的 NGINX 欢迎页面,而是要在/srv/webserver/arbitrary-location/中显示我们的文件。

首先,让我们创建这个目录结构,并在其中放一个简单的文件来提供服务:

$ sudo mkdir -p /srv/webserver/arbitrary-location/
$ echo "HELLO WORLD" | sudo tee /srv/webserver/arbitrary-location/index.html
HELLO WORLD

接下来,让我们检查一下我们在现有页面位置上的权限,并确保它们是一样的:

$ ls -lha /usr/share/nginx/html/
total 20K
drwxr-xr-x. 2 root root 99 Oct 29 17:36 .
drwxr-xr-x. 4 root root 33 Oct 29 17:36 ..
-rw-r--r--. 1 root root 3.6K Mar 6 2018 404.html
-rw-r--r--. 1 root root 3.7K Mar 6 2018 50x.html
-rw-r--r--. 1 root root 3.7K Mar 6 2018 index.html
-rw-r--r--. 1 root root 368 Mar 6 2018 nginx-logo.png
-rw-r--r--. 1 root root 2.8K Mar 6 2018 poweredby.png

我们将确保我们的权限是一样的:

$ ls -lha /srv/webserver/arbitrary-location/
total 4.0K
drwxr-xr-x. 2 root root 24 Oct 29 18:43 .
drwxr-xr-x. 4 root root 62 Oct 29 18:40 ..
-rw-r--r--. 1 root root 12 Oct 29 18:43 index.html

接下来,我们将更新我们的 NGINX 配置,将日志记录到这个新位置:

$ sudo sed -i 's/\/usr\/share\/nginx\/html/\/srv\/webserver\/arbitrary-location/g' /etc/nginx/nginx.conf

现在,我们重新启动我们的服务:

$ sudo systemctl restart nginx

让我们再试一下我们的curl,这次省略-I,这样我们就可以得到我们的页面:

$ curl localhost:5858
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.12.2</center>
</body>
</html>

哎呀...看起来不对。

毫不奇怪,SELinux 是罪魁祸首,但修复它是一组相当简单的命令,我们可以用来纠正文件的fcontext

$ sudo semanage fcontext --add --type httpd_sys_content_t /srv/webserver/arbitrary-location/index.html
$ sudo restorecon /srv/webserver/arbitrary-location/index.html

现在再试一下我们的curl应该会给我们返回消息:

$ curl localhost:5858
HELLO WORLD

我们也可以在浏览器中查看它:

如果这不值得泰特现代美术馆,我不知道还有什么值得。

它是如何工作的...

当我们更改端口并重新启动服务时,我们遇到了一些错误:

$ sudo journalctl -e -u nginx --no-pager | tail -n 8
Oct 29 17:43:17 localhost.localdomain systemd[1]: Starting The nginx HTTP and reverse proxy server...
Oct 29 17:43:17 localhost.localdomain nginx[4334]: nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
Oct 29 17:43:17 localhost.localdomain nginx[4334]: nginx: [emerg] bind() to 0.0.0.0:5858 failed (13: Permission denied)
Oct 29 17:43:17 localhost.localdomain nginx[4334]: nginx: configuration file /etc/nginx/nginx.conf test failed
Oct 29 17:43:17 localhost.localdomain systemd[1]: nginx.service: control process exited, code=exited status=1
Oct 29 17:43:17 localhost.localdomain systemd[1]: Failed to start The nginx HTTP and reverse proxy server.
Oct 29 17:43:17 localhost.localdomain systemd[1]: Unit nginx.service entered failed state.
Oct 29 17:43:17 localhost.localdomain systemd[1]: nginx.service failed.

注意5858端口上的具体Permission denied条目。

你可以使用我们之前作为实用程序安装的一部分安装的semanage命令来查询 SELinux 端口类型及其编号:

$ sudo semanage port -l | grep http
http_cache_port_t tcp 8080, 8118, 8123, 10001-10010
http_cache_port_t udp 3130
http_port_t tcp 80, 81, 443, 488, 8008, 8009, 8443, 9000
pegasus_http_port_t tcp 5988
pegasus_https_port_t tcp 5989

在这里,我们可以看到,虽然80和其他端口被允许作为 HTTP 端口,但5858最初并不在其中。

在我们添加了刚刚显示的额外端口之后,这个命令看起来有些不同:

$ sudo semanage port -l | grep http
http_cache_port_t tcp 8080, 8118, 8123, 10001-10010
http_cache_port_t udp 3130
http_port_t tcp 5858, 80, 81, 443, 488, 8008, 8009, 8443, 9000
pegasus_http_port_t tcp 5988
pegasus_https_port_t tcp 5989

因此,SELinux 现在允许使用这个端口。

就文件而言,我们可以使用ls -Z选项来检查 NGINX 需要文件具有的fcontext

如图所示,我们对默认文件运行了它:

$ ls -lhaZ /usr/share/nginx/html/
drwxr-xr-x. root root system_u:object_r:httpd_sys_content_t:s0 .
drwxr-xr-x. root root system_u:object_r:usr_t:s0 ..
-rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 404.html
-rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 50x.html
-rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 index.html
-rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 nginx-logo.png
-rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 poweredby.png

这是确定你需要给新文件的上下文的好方法。

当我们应用我们的新策略规则并将策略值恢复到系统时,我们的文件突然可以被 NGINX 使用了。

还有更多...

SELinux 实际上并不像每个人想象的那样糟糕,它已经走过了很长的路,不再像以前那样默默地失败。一般来说,当您需要为系统和程序找到正确的配置时,现在有大量的工具和调试程序可供选择,尽管它们可能会填满整整一本书。

如果您从本节中获得了任何信息,请了解禁用 SELinux 不是答案(即将其设置为宽松模式),并且在开发环境之外,您所做的一切只会让您的未来变得不太安全。

semanage并不是管理 SELinux 策略的唯一方法,但它非常易于使用,是一个很好的介绍自己进入策略文件的精彩世界的方式。

另请参阅

一般来说,桌面系统不使用 SELinux,除了 Fedora 之外,因此,如果您真的想开始尝试它,可以启动安装了 Fedora 的虚拟机,并查看诸如audit2allowchcon之类的工具。

检查 SELinux 是否正在运行,以及保持其运行的重要性

在本节中,我们将看看如何检查 SELinux 在我们的系统上是否启用并运行,并且我们将使用 SELinux 在运行过程中写入的日志。同时,我们将使用setroubleshoot来帮助我们确定我们尝试做的事情可能出现的问题。

再次强调,有一段时间,当 SELinux 开始成为一个事物时,人们立即将其摒弃。大多数在线指南都会以不朽的话语“务必检查 SELinux 是否已禁用”开始。幸运的是,这种心态现在大多已经消失了,人们已经接受 SELinux 作为他们唯一真正的上帝。

当您遇到由 SELinux 引起的问题时,很容易就会有冲动直接禁用它。如果问题出现在生产服务器上,并且您面临着修复的压力,这种冲动就会变得更加强烈。不要采用禁用 SELinux 的简单解决方案,因为这样做只会在将来给您带来麻烦。

也就是说,我现在将讨论如何禁用 SELinux(以帮助故障排除!)。

准备工作

在本节中,我们将使用我们的 CentOS 虚拟机。

SSH 到您的 CentOS 虚拟机:

$ vagrant ssh centos7

如果在上一节中未安装,请确保已安装 NGINX 和各种工具:

$ sudo yum install epel-release -y
$ sudo yum install policycoreutils-python setroubleshoot -y
$ sudo yum install nginx -y
$ sudo systemctl enable --now nginx

如何做…

首先,您可以使用sestatus轻松检查 SELinux 的当前状态:

$ sestatus
SELinux status: enabled
SELinuxfs mount: /sys/fs/selinux
SELinux root directory: /etc/selinux
Loaded policy name: targeted
Current mode: enforcing
Mode from config file: enforcing
Policy MLS status: enabled
Policy deny_unknown status: allowed
Max kernel policy version: 31

在这里,我们看到它是“启用”的,并且它正在运行的模式是“强制”,这意味着策略的违规行为将被拒绝。

要临时禁用 SElinux(即在运行时),有一个相对简单的命令:

$ sudo setenforce Permissive

但这将在启动时再次更改。

现在,让我们将其保持启用状态:

$ sudo setenforce Enforcing

接下来,我们将再次更改我们希望 NGINX 使用的端口,重新启动 NGINX,观察它失败,并看看我们如何确定问题所在。

更改端口可以这样完成:

$ sudo sed -i 's/5858 default_server;/5757 default_server;/g' /etc/nginx/nginx.conf 

如果您没有在上一节更改端口(您是从头开始的),那么您将想要将此处显示的5858替换为80

使用systemctl最容易重新启动 NGINX:

$ sudo systemctl restart nginx
Job for nginx.service failed because the control process exited with error code. See "systemctl status nginx.service" and "journalctl -xe" for details.

现在我们可以确定为什么它失败了:

$ sudo sealert -a /var/log/audit/audit.log

这可能会给您很多结果,特别是如果您已经运行该系统一段时间,但在最后附近应该会有一个类似以下内容的报告:

--------------------------------------------------------------------------------

SELinux is preventing /usr/sbin/nginx from name_bind access on the tcp_socket port 5757.

***** Plugin bind_ports (92.2 confidence) suggests ************************

If you want to allow /usr/sbin/nginx to bind to network port 5757
Then you need to modify the port type.
Do
# semanage port -a -t PORT_TYPE -p tcp 5757
 where PORT_TYPE is one of the following: http_cache_port_t, http_port_t, jboss_management_port_t, jboss_messaging_port_t, ntop_port_t, puppet_port_t.

***** Plugin catchall_boolean (7.83 confidence) suggests ******************

If you want to allow nis to enabled
Then you must tell SELinux about this by enabling the 'nis_enabled' boolean.

Do
setsebool -P nis_enabled 1

***** Plugin catchall (1.41 confidence) suggests **************************

If you believe that nginx should be allowed name_bind access on the port 5757 tcp_socket by default.
Then you should report this as a bug.
You can generate a local policy module to allow this access.
Do
allow this access for now by executing:
# ausearch -c 'nginx' --raw | audit2allow -M my-nginx
# semodule -i my-nginx.pp

Additional Information:
Source Context system_u:system_r:httpd_t:s0
Target Context system_u:object_r:unreserved_port_t:s0
Target Objects port 5757 [ tcp_socket ]
Source nginx
Source Path /usr/sbin/nginx
Port 5757
Host <Unknown>
Source RPM Packages nginx-1.12.2-2.el7.x86_64
Target RPM Packages 
Policy RPM selinux-policy-3.13.1-192.el7_5.3.noarch
Selinux Enabled True
Policy Type targeted
Enforcing Mode Enforcing
Host Name localhost.localdomain
Platform Linux localhost.localdomain
                              3.10.0-862.2.3.el7.x86_64 #1 SMP Wed May 9
                              18:05:47 UTC 2018 x86_64 x86_64
Alert Count 1
First Seen 2018-10-30 17:27:06 UTC
Last Seen 2018-10-30 17:27:06 UTC
Local ID 65a65b11-892c-4795-8a1f-163822aa3a0f

Raw Audit Messages
type=AVC msg=audit(1540920426.452:335): avc: denied { name_bind } for pid=4551 comm="nginx" src=5757 scontext=system_u:system_r:httpd_t:s0 tcontext=system_u:object_r:unreserved_port_t:s0 tclass=tcp_socket

type=SYSCALL msg=audit(1540920426.452:335): arch=x86_64 syscall=bind success=no exit=EACCES a0=6 a1=5580c9397668 a2=10 a3=7fff97b00870 items=0 ppid=1 pid=4551 auid=4294967295 uid=0 gid=0 euid=0 suid=0 fsuid=0 egid=0 sgid=0 fsgid=0 tty=(none) ses=4294967295 comm=nginx exe=/usr/sbin/nginx subj=system_u:system_r:httpd_t:s0 key=(null)

Hash: nginx,httpd_t,unreserved_port_t,tcp_socket,name_bind

在顶部加粗显示的是sealert认为问题的一行摘要;在这种情况下,它是正确的。

然后,它会给出一个semanage命令,类似于我们之前使用的命令,用于修改策略。

它还为您提供了两个命令ausearchsemodule,您可以使用这两个命令生成一个本地策略,该策略有效地与基本策略一起使用,但可以与诸如 Ansible 安装脚本之类的东西一起使用。

例如,您有一个 Ansible 角色,该角色在自定义端口上安装 NGINX,但这没关系,因为您可以将基于文本的策略与配置捆绑在一起,并在 Ansible 配置运行中加载它。

让我们运行这些命令:

$ sudo ausearch -c 'nginx' --raw | audit2allow -M my-nginx
******************** IMPORTANT ***********************
To make this policy package active, execute:

semodule -i my-nginx.pp$ sudo semodule -i my-nginx.pp

现在,尝试重新启动 NGINX,并curl我们的新端口:

$ sudo systemctl restart nginx
$ curl -I localhost:5757
HTTP/1.1 200 OK
Server: nginx/1.12.2
Date: Tue, 30 Oct 2018 17:41:42 GMT
Content-Type: text/html
Content-Length: 12
Last-Modified: Mon, 29 Oct 2018 18:43:12 GMT
Connection: keep-alive
ETag: "5bd754c0-c"
Accept-Ranges: bytes

哇!

它是如何工作的...

SELinux 的配置(就其是否正在运行以及处于什么模式)设置在/etc/selinux/config文件中:

$ cat /etc/selinux/config 

# This file controls the state of SELinux on the system.
# SELINUX= can take one of these three values:
# enforcing - SELinux security policy is enforced.
# permissive - SELinux prints warnings instead of enforcing.
# disabled - No SELinux policy is loaded.
SELINUX=enforcing
# SELINUXTYPE= can take one of three two values:
# targeted - Targeted processes are protected,
# minimum - Modification of targeted policy. Only selected processes are protected. 
# mls - Multi Level Security protection.
SELINUXTYPE=targeted

如果您想永久禁用 SELinux,这是您需要更改的文件,将enforcing翻转为permissive甚至disabled

在加载的自定义策略方面,我们正在研究一些更复杂的东西。

这个命令生成了两个文件:

$ sudo ausearch -c 'nginx' --raw | audit2allow -M my-nginx
$ ls
my-nginx.pp my-nginx.te

.pp文件是一个已编译的策略,准备加载,而.te文件是一个可供您确认的人类可读文件。

当我们使用semodule -i命令加载策略时,我们激活了它。

您可以再次使用semodule查看您的活动策略:

$ sudo semodule -l | grep my-nginx
my-nginx 1.0

还有更多...

audit2allow尽力而为,但它并不总是完全正确地获取策略文件(或者它们具有太大的权限,从而使 SELinux 无效)。除非您真的非常有信心,否则请让某人在加载之前对您的配置进行理智检查。

另请参阅

我在一开始就说过,确保 SELinux 正在运行并确保您保持其运行是非常重要的。

禁用它并保持禁用所产生的问题应该是显而易见的,但为了给您一个形象的描述,请注意以下内容。

这是星期五的最后一天,就在圣诞节假期之前,大部分员工已经离开,之前他们做了一些最后的检查,以确保您的电子商务网站在圣诞节和节礼日期间保持运行。

您正要下班时,注意到网站出现了问题,导致客户认为他们可以在最新的任天堂游戏机上获得三百点,而您不能容忍任何这种胡闹。

您进去手动更改,添加额外的配置文件以正确加载价格,并重新启动服务。

服务没有重新启动。

恐慌开始蔓延。

你的胃一下子就空了。

远处有人发出了一声吼叫。

以速度和灵巧,您禁用了 SELinux,重新启动了服务,并使一切恢复在线。网站已经上线,控制台现在显示正确的价格。

呼——您回家吃了几个肉馅饼来庆祝。

然后,一整年都没有人注意到 SELinux 被禁用,直到下一次圣诞节推送软件的时候,使用您的 CI/CD 基础设施,该基础设施还确保 SELinux 已启用。当这种情况发生时,网站就会崩溃。

每个人都陷入恐慌,没有人确定发生了什么,但您并不在乎,因为您早就因为公司让您工作愚蠢的时间而辞职,并且决定搬到日本开始一个水果种植业务。

一切都着火了。

看到你做了什么吗?

保持 SELinux 启用!

重置 SELinux 权限

在本节中,我们将讨论重置 SELinux 权限,并简要介绍如何在您忘记密码的情况下重置root密码,同时考虑到会阻碍您的 SELinux。

准备好了

连接到您的 CentOS VM:

$ vagrant ssh centos7

如何做...

首先,重要的是要理解,对于 SELinux,我们实际上有一个运行中的配置和一个保存的配置。当您运行系统时,重要的是您对 SELinux 所做的任何更改都要保存下来,以便在 SELinux 重新标记的情况下加载。

要看到这一点,让我们复制一些上下文。

首先,看一下我们的.bashrc文件的上下文(因为它立即可用):

$ ls -lhaZ .bashrc 
-rw-r--r--. vagrant vagrant unconfined_u:object_r:user_home_t:s0 .bashrc

这有四个部分:我们有一个用户(unconfined_u),一个角色(object_r),一个类型(user_home_t),以及资源的敏感性(s0.)类型对我们来说很重要。

假设我们想要更改类型;我们可以通过从另一个文件中复制类型来实时更改(在这种情况下,是authorized_keys文件,看起来像这样):

$ ls -lhaZ .ssh/authorized_keys 
-rw-------. vagrant vagrant unconfined_u:object_r:ssh_home_t:s0 .ssh/authorized_keys $ chcon --reference=.ssh/authorized_keys .bashrc

现在请注意,当我们查看我们的.bashrc文件时,SELinux 上下文已经改变了:

$ ls -lhaZ .bashrc 
-rw-r--r--. vagrant vagrant unconfined_u:object_r:ssh_home_t:s0 .bashrc

chcon不是永久的,我们实际上改变了 SELinux 的运行配置,这意味着我们可以用一个简单的命令来重置它:

$ restorecon .bashrc 
$ ls -lhaZ .bashrc 
-rw-r--r--. vagrant vagrant unconfined_u:object_r:user_home_t:s0 .bashrc

你可能还记得之前,我们是用semanage来向文件添加新的上下文,然后用restorecon来应用该上下文。

解决临时上下文更改的另一种方法是重新标记你的文件系统。

让我们再次进行更改,再次复制authorized_keys上下文:

$ chcon --reference=.ssh/authorized_keys .bashrc

现在,让我们把一个非常具体的文件放在一个非常具体的位置,然后重新启动:

$ sudo touch /.autorelabel
$ sudo reboot

一旦你的机器重新启动,再次查看文件的上下文:

$ ls -lhaZ .bashrc 
-rw-r--r--. vagrant vagrant unconfined_u:object_r:user_home_t:s0 .bashrc

而且,你还会发现我们添加的.autorelabel文件已经被自动删除了。

本章的Vagrantfile非常明确地在 CentOS VM 的引导过程中删除了一些控制台选项。这是因为如果你不这样做,.autorelabel函数就不会起作用。如果你在这个修复过程中遇到问题,请尝试在物理机或原始 VM 上进行(在开发环境中)。

它是如何工作的……

restorecon的作用是检查文件的上下文是否符合它所期望的真相,如果发现任何问题,它将使用它所知道的静态配置进行纠正。

当我们运行.autorelabel函数时,实际上是在我们的系统在启动时运行了fixfiles relabel命令,之后我们触摸的文件被删除。你会注意到这次启动可能会花更长的时间,因为它在启动时要做更多的工作。

还有更多……

默认情况下,restorecon只会恢复type上下文,并将其他上下文保留为它发现的样子。这可以通过-F标志来覆盖。

我们还提到了重置root用户密码,这在 SELinux 的情况下变得非常烦人。

假设你忘记了你的盒子的root密码;解决这个问题的方法是进入单用户模式,更改密码,然后重新启动……或者至少,过去是这样的。

现在,所涉及的步骤看起来是这样的:

  1. 重新启动系统。

  2. 在超时之前编辑你的安装的 GRUB 条目。

  3. 确保linux16行是rw而不是ro,并将init更改为类似/bin/bash的东西。

  4. 继续引导过程。

  5. 确保你的/目录被挂载为rw,你可以编辑文件。

  6. 运行passwd来更新root密码。

  7. /目录中运行touch .autorelabel,然后重新启动。

  8. 检查你是否可以登录。

如果你跳过了touch .autorelabel这一步,它就不会起作用,你就得重新开始。

从长远来看,这并不算什么,但在当时可能会令人恼火。

总结-权限、SELinux 和 AppArmor

什么时候为时已晚?

当你已经尽一切可能来解决你的问题时呢?

你有检查并确认工作的良好备份吗?

你是不是快要抓狂了?

已经三天了,你自周二以来就没有见过阳光了吗?

权限可能会很棘手和尴尬,有时最好的办法就是说:“算了,这个系统已经太糟糕了,我要重新开始。”我对这种事情的一般准则是我跳过了多少顿饭来修复问题,如果超过一顿,那就是跳过了太多的饭。

在此之前,我做过很愚蠢的事情,我认为在这本书中我已经非常清楚地表明了。我曾经递归地将整个系统的权限改为777(这会造成很多问题),我曾经删除目录以释放空间,结果发现那个目录实际上对系统的健康非常重要(我不会分享是哪一个,但它里面有文件和非文件)。我甚至阻止了一个意外的rm,比我打算的rm多得多,然后努力工作,试图弄清楚我实际上已经损坏了多少文件系统。

简而言之,我已经把系统搞得乱七八糟,以至于它们在技术上是可以修复的,但花费的时间超过了恢复的痛苦。

SELinux、AppArmor 和简单的 Linux 权限可能会让你在互联网上搜寻晦涩的错误信息,希望有人遇到过和你完全相同的问题,并且他们决定分享他们的解决方案(只要不是“没事了,我解决了,关闭这个帖子”)。

但是,所有这些说法,macOS 系统,甚至 POSIX 标准文件权限,都很重要。这可能会耗费时间和令人讨厌,但使用诸如audit2allow之类的工具可以大大降低你的血压,同时增加你的厉害程度,学习正确的chmod咒语可以将你的故障排除速度提高十倍。

在大多数情况下,你从官方仓库安装的软件会被合理地设置好,只要第三方值得信赖,你甚至可能会发现后来添加的其他仓库也包含他们软件的适当 SELinux 权限。情况比 SELinux 刚开始出现时好多了。

我记得以前人们在他们的指南中建议将 SELinux 禁用作为第一步,我很高兴我们已经走出了那些日子,但有时候还是很诱人。

当你快要绝望,只想让你的应用程序工作时,禁用 SELinux 可能是最诱人的时刻。坚定不移,坚定不移,告诉自己你不会被电脑打败。

这不像是你要对抗 HAL 9000 一样。

第九章:容器和虚拟化

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

  • 什么是容器?

  • 安装 Docker

  • 运行你的第一个 Docker 容器

  • 调试一个容器

  • 搜索容器(和安全性)

  • 什么是虚拟化?

  • 启动我们的 VM 的 QEMU 机器

  • 使用 virsh 和 virt-install

  • 比较本地安装、容器和虚拟机的优势

  • 虚拟化选项的简要比较(VMware、proxmox 等)

介绍

坦率地说:容器和虚拟化是我在与计算机和服务器相关的事情中最喜欢谈论的之一。能够在你的计算机内安装一个完全不同的计算机的概念,对我来说就是一个充满智慧的概念。

这不是一个新概念;这个原则已经存在了相当长的时间,即使我的第一台 OS9 计算机也能在一定程度上进行虚拟化。更早的时候,这个术语的根源可以追溯到 20 世纪 60 年代,尽管它的含义与现代用语中的含义略有不同。

你可能已经使用过虚拟机VM),尽管你可能甚至不知道你已经使用过。如今,虚拟机速度很快,与在底层硬件上运行相比,性能损失可以忽略不计,这要归功于虚拟化的优势,这意味着你不再需要模拟与虚拟机相关的一切,而是直接将虚拟机指令传递给主机计算机的 CPU。

虚拟机无疑是托管和开发的强大工具,能够快速启动和关闭机器,在你不断破坏东西或寻找一种安全且廉价的方式来分割一个庞大的服务器时,它就是一个救世主。

如今,容器在某种程度上已经取代了虚拟机的地位,尽管它们各自都有优势,它们存在于和谐中,而不是不断争斗。

与虚拟机不同,容器更像是系统的一部分,有一个共享的核心。

当你在 Linux 系统上使用容器时,你共享主机机器的内核,而不是安装你自己的内核,并且通常不需要模拟额外的硬件,比如磁盘控制器。

再次强调,容器和容器化并不是新概念,这个想法自从 FreeBSD 上的 jails 以及后来的 Solaris 上的 Zones(它们以一种形式或另一种形式仍然存在,我们稍后会看到)以来就一直存在。然而,最近几年,随着Docker的推出,它们已经迅速发展,使得容器的整个概念对人们来说更容易接受(而且他们的营销手段也很出色)。

在本章中,我们将研究容器和虚拟机,讨论各自的利弊,并谈论虚拟环境的管理。

技术要求

在本节和本章中,我们将主要使用我们的 Ubuntu 机器。

主要是因为 Ubuntu 默认包含了我们需要的更多最新元素,而 CentOS 由于其长期的使用寿命,许多东西都是向后修补的。

请随意使用以下Vagrantfile

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

 config.vm.define "ubuntu1804" do |ubuntu1804|
   ubuntu1804.vm.box = "ubuntu/bionic64"
   ubuntu1804.vm.box_version = "20180927.0.0"
 end

 config.vm.define "centos7" do |centos7|
   centos7.vm.box = "centos/7"
   centos7.vm.box_version = "1804.02"
 end

end

什么是容器?

在本节中,我们将深入探讨容器的实际含义,比我们在介绍中涵盖的更深入一些。

我们不会深入探讨(因为我们会迷失并不得不打电话给詹姆斯·卡梅隆来帮助我们),但我们会触及容器的核心是什么,以及它与运行一个完整的虚拟机有何不同。

准备工作

SSH 到你的 Ubuntu 虚拟机:

$ vagrant ssh ubuntu1804

如何做…

我们将创建一个容器,而不使用市场上最流行的工具。

容器利用某些内核特性(命名空间和 cgroups),这意味着它们不严格可移植到 Windows 和 Mac 等系统。

首先,我们将为我们的容器创建一个存储池:

$ sudo lxc storage create example-pool dir

不鼓励在生产中使用目录存储池。最好使用使用 LVM 或 ZFS 的定制解决方案,但是对于测试和示例,这是可以的。

接下来,我们将使用此存储池启动一个容器:

$ sudo lxc launch ubuntu:18.04 example-container -s example-pool
Creating example
Retrieving image: rootfs: 31% (1.63MB/s)

前面的检索可能需要一些时间,这将取决于您的网络连接速度。

在这个过程结束时,我们的容器应该已经创建。我们可以使用以下命令列出它:

$ sudo lxc list
+-------------------+---------+------+------+------------+-----------+
| NAME | STATE | IPV4 | IPV6 | TYPE | SNAPSHOTS |
+-------------------+---------+------+------+------------+-----------+
| example-container | RUNNING | | | PERSISTENT | 0 |
+-------------------+---------+------+------+------------+-----------+

然后,我们可以在其中执行命令:

$ sudo lxc exec example-container hostname
example-container

在这里,我们运行的命令在我们的主机 VM 上运行时,会告诉我们ubuntu-bionic。因此,通过与我们的lxc命令一起检查它,我们可以证明它正在容器中运行。

如果我们想要进入容器,我们可以简单地启动一个 shell:

$ sudo lxc exec example-container bash
root@example-container:~# hostname
example-container

就是这样 - 一个非常快速的操作系统切片,位于您的操作系统内部!

完成后,只需键入exit或按Ctrl + D退出容器:

root@example-container:~# exit

然后,我们可以使用以下命令销毁它:

$ sudo lxc stop example-container
$ sudo lxc delete example-container

人们在 LXC 世界和 Docker 世界经常忘记的一件事是,你不仅仅需要处理容器。我们已经删除了容器,但是如果您真的想要清理干净,您还必须删除下载的镜像和存储池。

它是如何工作的...

稍微详细解释一下 cgroups 和命名空间的评论,实际上容器是内核和用户空间工具的功能,使事情看起来很好。 LXC 是一个工具,它抽象了复杂性,简化了我们的半隔离机器的设置为几个易于使用的命令。

cgroups(Linux 控制组)

以下是Linux 程序员手册的摘录:

“控制组,通常称为 cgroups,是 Linux 内核的一个功能,允许将进程组织成分层组,然后可以限制和监视各种类型资源的使用。内核的 cgroup 接口通过一个名为 cgroupfs 的伪文件系统提供。分组是在核心 cgroup 内核代码中实现的,而资源跟踪和限制是在一组每种资源类型子系统(内存、CPU 等)中实现的。”

实际上,这意味着内核有能力将进程组合成堆栈,然后可以控制和监视其资源使用情况。

命名空间

不要引起趋势,这里再次是Linux 程序员手册

“命名空间将全局系统资源封装在一个抽象中,使得在命名空间内的进程看起来好像它们有自己的隔离实例的全局资源。对全局资源的更改对于属于命名空间的其他进程是可见的,但对其他进程是不可见的。命名空间的一个用途是实现容器。”

实际上,这意味着您的单个网络接口可以连接多个命名空间,使用这些命名空间的进程将认为这是该设备的唯一实例。

网络接口并不是唯一的例子,但它们是更明显的候选者,因为每个 VM 都需要一个 NIC。

我们创建的细节

当我们在本节开始时创建存储池时,我们实际上是在通知我们的系统(lxd守护程序)需要使用特定目录来存储容器,即下面的/var/lib/lxd/storage-pools/

$ sudo ls /var/lib/lxd/storage-pools/example-pool
containers

当我们启动容器时,我们首先从默认的互联网位置下载了一个预打包的镜像,作为我们创建的容器的基础。

在这里,它被视为一个字母数字字符串,但实际上是 Ubuntu 18.04 的精简容器形式:

$ sudo ls -lhA /var/lib/lxd/images/
total 175M
-rw-r--r-- 1 root root 788 Nov 4 15:44 30b9f587eb6fb50566f4183240933496d7b787f719aafb4b58e6a341495a38ad
-rw-r--r-- 1 root root 175M Nov 4 15:47 30b9f587eb6fb50566f4183240933496d7b787f719aafb4b58e6a341495a38ad.rootfs

注意这个容器的大小,175 M,这是人们强调容器的主要优势之一(它们很小,这实际上是更大的例子之一)。

当我们的容器正在运行时,我们可以从主机上看到它作为一组进程:

$ ps uf -p 3908 --ppid 3908 --ppid 3919 

输出应该看起来像以下截图:

因此,这个容器在里面有大部分的操作系统,它是从我们下载的镜像继承而来的,尽管它显然不包含与主机 VM 共享的内核。

想象一个容器就像一个橙子(我也很喜欢橙子),其中每个片段都可以存在为自己的一小部分多汁的好处,但没有橙子的外皮给它结构和传递养分,它就毫无用处。这与虚拟机形成对比,后者更像是一个永远年轻的小蜘蛛(听我说完),每个都独立存在为活生生的生物,但它们仍然骑在它们母亲的背上,准备向任何接触到它们的人提供一剂神秘的恐怖。

还有更多...

目前,您应该在由 LXC 创建的容器中,位于由 Vagrant 管理的虚拟机(并利用 VirtualBox)之上,位于您自己的笔记本电脑、台式机或服务器上。

这可能有点难以想象,但很多聪明的人花了很多时间来确保这种设置可以无问题地工作。

LXD 守护程序

像往常一样,我们可以使用systemctl来可视化我们的服务:

$ systemctl status lxd
● lxd.service - LXD - main daemon
 Loaded: loaded (/lib/systemd/system/lxd.service; indirect; vendor preset: enabled)
 Active: active (running) since Sun 2018-11-04 15:41:14 UTC; 33min ago
 Docs: man:lxd(1)
 Process: 2058 ExecStartPost=/usr/bin/lxd waitready --timeout=600 (code=exited, status=0/SUCCESS)
 Process: 2036 ExecStartPre=/usr/lib/x86_64-linux-gnu/lxc/lxc-apparmor-load (code=exited, status=0/SUCCESS)
 Main PID: 2057 (lxd)
 Tasks: 16
 CGroup: /system.slice/lxd.service
 └─2057 /usr/lib/lxd/lxd --group lxd --logfile=/var/log/lxd/lxd.log

另请参阅

在本节的开头,我们在容器内运行了hostname,但这并不能让您知道容器在做什么。我发现特别方便的一件事是能够检查运行在我的容器中的进程,而不必先挖出我的ps命令的进程 ID。

在这里,我使用以下命令:

$ sudo lxc exec example-container top

这给了我以下输出:

请注意,它比主机机器安静得多,实际上只有很少的守护程序在容器中运行。

安装 Docker

迄今为止,在 Linux 上运行容器的最流行的解决方案(至少在撰写本文时)是 Docker。

最初是 Docker Inc.(当时是 dotCloud)为了更好地利用其PaaS平台即服务)公司中的容器而开始的,Docker 很快在开源世界中获得了广泛的认可,并很快被视为计算的未来在许多领域(愤世嫉俗的系统管理员通常是在开发人员得知之后才出现的)。

因为它实际上是一种使用已经存在的内核特性的简单方式,并且包括 Docker Hub,供人们上传和下载预构建的镜像,这使得容器变得简单。

很快,人们开始将一切都容器化,从 Firefox 到 Nginx,再到整个发行版,只是因为。

我坚信 Docker 使得上传和下载他们的镜像变得容易,这有助于其成功。正如我已经提到的,容器的概念可以追溯到九十年代,但当时没有“监狱”或“区域”供人们下载预构建的软件集合。Docker Hub 在一个已经流行的平台上提供了这一点。

准备工作

大多数发行版都以某种形式在传统存储库中提供 Docker。然而,这经常与上游不一致,或者只是老旧的,因此在您的环境中利用上游 Docker 存储库是一个好主意。

SSH 到您的 Ubuntu 虚拟机:

$ vagrant ssh ubuntu1804

如何做...

Docker 保持了一个页面,介绍了如何在您选择的发行版上安装 Docker(参见docs.docker.com/install)。以下是 Ubuntu 的简化指令。

运行更新以确保您已准备好安装 Docker:

$ sudo apt update

安装 GPG 密钥,然后添加存储库本身:

$ wget https://download.docker.com/linux/ubuntu/gpg
$ sudo apt-key add gpg $ sudo apt-add-repository 'deb [arch=amd64] https://download.docker.com/linux/ubuntu bionic stable'

像往常一样,检查 GPG 指纹是否与官方来源一致。

现在,我们终于可以安装 Docker 本身了(这可能需要一些时间):

$ sudo apt install docker-ce -y

我们还可以使用systemctl来检查我们的 Docker 守护程序的状态:

$ systemctl status docker
● docker.service - Docker Application Container Engine
 Loaded: loaded (/lib/systemd/system/docker.service; enabled; vendor preset: enabled)
 Active: active (running) since Sun 2018-11-04 16:56:26 UTC; 52s ago
 Docs: https://docs.docker.com
 Main PID: 11257 (dockerd)
 Tasks: 23
 CGroup: /system.slice/docker.service
 ├─11257 /usr/bin/dockerd -H fd://
 └─11275 docker-containerd --config /var/run/docker/containerd/containerd.toml

您可能已经注意到我们还没有启动和启用此服务。这主要是因为派生自 Debian 的系统喜欢为您启动服务...我个人不喜欢这种方法的原因有很多,但它就是这样。

它是如何工作的...

在开始之前,你可能已经注意到我们不断使用一个名为docker-ce的软件包,这是有很好的原因的。

Docker 有两个基本版本,社区版(CE)企业版(EE)。大多数情况下,你只会在野外看到 CE,它完全可以满足你的所有需求。

我们在这里所做的只是直接去软件的作者那里添加他们自己的 GPG 密钥和存储库信息,以及我们的 Ubuntu 默认设置。Docker 是一个非常动态的程序,意味着它经常发布并且发布量很大。在撰写本文时,我们安装了18.06.1-ce,但在你知道之前可能会发生变化。Docker 采用年-月发布格式:

$ docker --version
Docker version 18.06.1-ce, build e68fc7a

我们还安装了两个主要组件(以及许多工具和附加组件),即 Docker 命令行工具和 Docker 守护程序。

Docker 的工作方式与其他用户空间工具相同,利用内核功能。它的独特之处在于它可以是多么用户友好。

你主要通过命令行工具docker来使用 Docker,而这个工具又与 Docker 守护程序进行通信。这个守护程序负责管理它被指示创建的容器,并维护它从 Docker Hub 或其他注册表中拉取的图像。

Docker 注册表是图像的存储库。最受欢迎的是 Docker Hub,但没有什么能阻止你创建自己的注册表,或者使用现成的解决方案来管理一个,比如 Artifactory。

现在要注意的最后一个组件是 Docker 正在使用的运行时,即runC(通用容器运行时)。

运行时实际上只是 Docker 将用于运行容器的统一系统集合的名称(想象一下 cgroups 和命名空间捆绑成一个词,尽管还有其他功能)。这意味着,虽然runC是特定于 Linux 的,但如果 Windows 有一个容器运行时(Host Compute Service),那么 Docker 可以使用它。

这并不使容器在操作系统之间通用 - 你不能在 Linux 上创建一个容器,然后在特定于 Windows 的运行时中运行它,但这确实使得 Docker 工具通用。

还有更多...

获取有关 Docker 安装的所有信息的最简单方法是使用docker info命令:

$ sudo docker info
Containers: 0
 Running: 0
 Paused: 0
 Stopped: 0
Images: 1
Server Version: 18.06.1-ce
Storage Driver: overlay2
 Backing Filesystem: extfs
 Supports d_type: true
 Native Overlay Diff: true
Logging Driver: json-file
Cgroup Driver: cgroupfs
Plugins:
 Volume: local
 Network: bridge host macvlan null overlay
 Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog
Swarm: inactive
Runtimes: runc
Default Runtime: runc
Init Binary: docker-init
containerd version: 468a545b9edcd5932818eb9de8e72413e616e86e
runc version: 69663f0bd4b60df09991c08812a60108003fa340
init version: fec3683
Security Options:
 apparmor
 seccomp
 Profile: default
Kernel Version: 4.15.0-34-generic
Operating System: Ubuntu 18.04.1 LTS
OSType: linux
Architecture: x86_64
CPUs: 2
Total Memory: 985.3MiB
Name: ubuntu-bionic
ID: T35X:R7ZX:MYMH:3PLU:DGXP:PSBE:KQ7O:YN4O:NBTN:4BHM:XFEN:YE5W
Docker Root Dir: /var/lib/docker
Debug Mode (client): false
Debug Mode (server): false
Registry: https://index.docker.io/v1/
Labels:
Experimental: false
Insecure Registries:
 127.0.0.0/8
Live Restore Enabled: false

WARNING: No swap limit support

稍微更多

我没有涉及的一件事是containerdCRI-O之类的东西。如果你已经了解这些术语,那么我之所以没有提到它们,是因为它们远远超出了本书试图实现的范围。

我鼓励任何对 Docker 及其各个组件感兴趣的人,去阅读专门的文献,因为如果你全面了解当今最流行的容器化工具,未来几年你将不会失业。

另请参阅

你有没有在使用 Docker 时注意到pigz?这是一个特别有趣的软件,因为它基本上是gzip的并行版本。当你解压文件并且有 18 个核心时,最好尽可能多地使用它们,而不是过载一个核心。

运行你的第一个 Docker 容器

我们已经在 LXC 部分使用了一个容器,但现在我们将使用更流行的容器运行系统。

本节将涵盖一些基本命令,而不会深入讨论。

准备工作

在本节中,我们将使用我们的 Ubuntu 虚拟机,但请确保先设置好上一节的 Docker。

SSH 到你的虚拟机,并确保在安装 Docker 之前使用上一节设置上游 Docker 存储库:

$ vagrant ssh ubuntu1804

如何做...

与 LXC 部分一样,我们将启动一个 Ubuntu 容器,然后与其进行交互。

从以下命令开始:

$ sudo docker run -itd --rm alpine /bin/ash
Unable to find image 'alpine:latest' locally
latest: Pulling from library/alpine
4fe2ade4980c: Pull complete 
Digest: sha256:621c2f39f8133acb8e64023a94dbdf0d5ca81896102b9e57c0dc184cadaf5528
Status: Downloaded newer image for alpine:latest
5396b707087a161338b6f74862ef949d3081b83bbdcbc3693a35504e5cfbccd4

现在容器已经启动运行,你可以用docker ps查看它:

$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
5396b707087a alpine "/bin/ash" 45 seconds ago Up 44 seconds ecstatic_lalande

如果你愿意,你也可以使用docker exec进入它:

$ sudo docker exec -it ecstatic_lalande /bin/ash
/ # 

你也可以使用docker attach,它在表面上完成了相同的事情(让你访问容器中的 shell)。这种方法的唯一问题是,你将附加到活动进程,这意味着当你关闭会话时,容器也会停止。

再次离开容器(exit)将带你回到你的 VM 提示符。

从这里,你可以停止你的容器:

$ sudo docker stop ecstatic_lalande
ecstatic_lalande

这可能需要几秒钟。

容器现在已被删除,我们可以通过另一个docker ps来确认:

$ sudo docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

它是如何工作的...

让我们分解我们的命令。

创建一个容器

从创建新容器开始,这是我们使用的命令:

$ sudo docker run -itd --rm alpine /bin/ash

在这里,我们告诉 Docker 我们想要在一个新的容器中run一个命令:

docker run

然后,我们通知它我们希望它是交互式的,有一个伪 TTY,并且启动分离的(将我们带回 VM shell):

-itd

接下来,我们告诉 Docker,当容器停止时,我们希望它自动删除自己:

--rm

这是一个相对较新的功能,只是因为人们没有意识到容器在停止后仍然存在,人们最终会得到数百个已停止的容器列表。

最后,我们说我们想使用什么镜像(来自 Docker Hub),以及要运行什么命令(这里是 Alpine Linux 的默认 shell,ash):

alpine /bin/ash

列出我们的容器

其次,我们使用以下命令列出我们的新容器:

$ sudo docker ps

这显示了所有我们的容器(或者在这种情况下,只有一个)的CONTAINER IDIMAGECOMMANDCREATEDSTATUSPORTSNAMES

CONTAINER ID部分是一个随机字符串分配,NAMES部分显示了你的容器的随机生成的友好名称(尽管这也可以在创建时定义)。

当我们后来在我们的列表命令中添加了-a时,是为了显示容器并没有因为停止而被从初始列表中省略,因为-a标志将显示所有容器,而不仅仅是正在运行的容器。

在我们的容器中执行命令

接下来,我们跳进容器内,启动另一个(在创建时已经启动的)shell 会话:

$ sudo docker exec -it ecstatic_lalande /bin/ash

在这里,我们通过在容器内使用交互式会话和另一个伪 TTY 来执行命令(在这里用docker ps中的友好名称表示)。

这将把我们放在容器内。如果我们运行top,我们将看到我们已经启动的/bin/ash命令的两个实例:

你有没有注意到/bin/ash实例中的一个是PID 1

停止我们的容器

一旦我们再次跳出来,然后停止正在运行的容器:

$ sudo docker stop ecstatic_lalande

这需要几秒钟,但是一旦完成,容器将消失(正如我们所看到的),尽管它使用的镜像(alpine)将保留下来。

因为我们的镜像仍然存在,所以下次你想用它做点什么时,你就不必再下载它了!

调试一个容器

在这一部分,我们将再次启动我们的容器,进行一些更改,并检查我们的更改是否产生了影响。

这有助于突出容器的瞬时性质,以及你可以在运行的实例中做些什么。

准备工作

在这一部分,我们将继续使用我们的 Ubuntu VM。

如果还没有连接,通过 SSH 连接到你的 VM,并启动一个容器:

$ vagrant ssh ubuntu1804 $ sudo docker run -itd --rm -p8080:8080 alpine /bin/ash

如何做...

现在你应该有一个正在运行的 docker 容器,在这里列出为docker ps

$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0f649283dcaf alpine "/bin/ash" 41 seconds ago Up 39 seconds 0.0.0.0:8080->8080/tcp compassionate_boyd

请注意,我们在这个例子中还有一个端口转发,即端口8080

在这种情况下,端口转发与其他情况相同-我们正在将主机的一个端口转发到容器中的一个端口。

尝试使用curl命令访问端口:

$ curl localhost:8080
curl: (56) Recv failure: Connection reset by peer

现在,跳到 VM,让我们在指定的端口上启动一个 Web 服务器:

$ sudo docker exec -it compassionate_boyd /bin/ash

首先,我们需要安装一些额外的 busybox 东西:

# apk add busybox-extras

现在,我们可以在端口8080上启动一个小型 Web 服务器,然后退出容器:

# touch index.html
# echo "I hated reading Shakespeare in school." > index.html
# httpd -p8080
# exit

现在,从你的 VM,你将能够curl你的容器的新 Web 服务器:

$ curl localhost:8080
I hated reading Shakespeare in school.

停止容器,然后启动一个新的:

$ sudo docker stop compassionate_boyd
compassionate_boyd
$ sudo docker run -itd --rm -p8080:8080 alpine /bin/ash
592eceb397e7ea059c27a46e4559c3ce7ee0976ed90297f52bcbdb369e214921

请注意,当你再次curl你的端口时,它将不起作用,因为你之前对运行的容器所做的所有更改都已丢失,并且一个新的容器已经取而代之:

$ curl localhost:8080
curl: (56) Recv failure: Connection reset by peer

它是如何工作的...

我们在这里所做的只是强调容器本质上是短暂的,虽然你可以停止和启动相同的容器(在docker run命令中去掉--rm),但在将容器标记并上传到某个注册表之前,你都处于一个瞬态状态。

通常不建议通过启动一个容器然后在其中安装大量软件来构建容器,然后离开并保存它以供以后使用。更好的方法是使用Dockerfile或其他自动化和可重复构建容器的方法。

我们还指出,虽然 Docker 容器应该是一个独立的小实体,但这并不意味着你不能进入其中查看发生了什么,甚至安装额外的软件来帮助你进行调试,如果你愿意的话。

还有更多...

如果你有兴趣使用Dockerfile来做我们在这里做的事情,这是一个相当简单的方法,尽管它在技术上超出了本书的范围。

以下内容足以让你开始:

FROM alpine

MAINTAINER Your Deity of Choice

RUN apk add busybox-extras
RUN touch index.html
RUN echo "I really hated reading Shakespeare in school." > index.html

EXPOSE 8080/tcp

CMD ["/usr/sbin/httpd", "-p8080", "-f"]

然后,你可以使用以下类似的方法构建:

$ sudo docker build .
<SNIP>
Successfully built d097226c4e7c

然后,你可以启动你的结果容器(分离,并转发端口):

$ sudo docker run -itd -p8080:8080 d097226c4e7c

我们在Dockerfile中添加了-f,以确保进程保持在前台(容器不会立即停止):

$ curl localhost:8080
I really hated reading Shakespeare in school.

搜索容器(和安全性)

在这一部分,你大部分时间都需要访问某种类型的浏览器,尽管在紧急情况下,你可能可以打电话给朋友让他们帮你做互联网搜索(如果你是一个真正好的朋友,而且他们确实没有更好的事情要做的话)。

我们还将使用我们的虚拟机来练习我们发现的东西。

我们将在 Docker Hub 上搜索容器,并提及下载和使用公共镜像的安全性问题。

这一部分并不是为了吓唬你,就像你不应该害怕运行任何你找到的自由软件一样——这是在做尽职调查。

准备工作

跳到你的 Ubuntu 虚拟机上(如果你还没有安装 docker,请从上一节安装):

$ vagrant ssh ubuntu1804

如何做...

从你选择的浏览器(对我来说是 Firefox),前往hub.docker.com

你将会看到一个类似以下的页面:

这里有一些暗示,即标题为“新手入门 Docker?”的部分。尽管第一句话可能会让人觉得需要创建 Docker ID 才能开始使用,但实际上并不需要。你可能会发现这样做很方便,甚至可能有充分的理由创建一个 ID,但最初(至少在撰写本文时)绝对没有必要这样做。

相反,使用屏幕顶部的搜索栏,输入redis

哇!那是很多存储库!

这就是关于 Docker 的第一件好事。因为创建镜像并上传到 Docker Hub(我自己有几个)是如此容易,所以你想要的东西可能会有多个版本。

在这里,我们可以看到顶部的结果只是简单地命名为 redis,而不是像其他的<username>/redis-foo

当一个镜像是官方的时候,它会得到特权的荣誉,只有它的软件的明确名称,就像在这种情况下的 redis 一样。

点击它:

这里有几件事情需要注意。

  • 幸运的是,我们得到了一个开始的命令,即右边的docker pull redis

  • 我们得到了存储库信息,这是默认视图,为我们提供了简短和完整的描述。在实践中,这可以是维护者想要的任何长度。

  • 最后,此刻,我们在顶部得到了一个标签部分。现在点击这个:

标签,就像 Git 一样,是表示您要下载的容器的特定版本的一种方式。

默认标签是最新的,如果您要运行以下命令,它就是您要下载的镜像(正如您可以在我们的命令后面立即看到的那样):

$ sudo docker run -d redis
Unable to find image 'redis:latest' locally
latest: Pulling from library/redis
f17d81b4b692: Pull complete 
b32474098757: Pull complete 
<SNIP>

如果我们想要专门拉取 Redis 的 Alpine 版本(即在 Alpine Linux 上安装的 Redis),我们将运行以下命令:

$ sudo docker run -d redis:alpine
Unable to find image 'redis:alpine' locally
alpine: Pulling from library/redis
4fe2ade4980c: Already exists 
fb758dc2e038: Pull complete 
989f7b0c858b: Pull complete 
<SNIP>

请注意,我们拉取了除基本版本之外的每个版本,而基本版本已经存在于我们的设置中。

看这里!您使用 Docker Hub 寻找了每个人最喜欢的内存数据库的一个版本!

它是如何工作的...

我们在这里做的就是从全球 Docker Registry 中拉取一个功能性镜像;这是默认的,最重要的,最大的,最原始的,也是最好的(根据一些人的说法)。

Docker Hub 是一个更小的存储库,每个人都可以在他们构建(或分叉)的容器上加上自己的标记,从而增加了世界软件的种类。

显然这有缺点,正如我在前面讽刺的一行中所暗示的那样。这意味着因为将您的镜像轻松地推送到 Docker Hub,发现您想要的一个镜像可能变得越来越令人沮丧。

人们也可能是恶意的,上传的容器可能确实做了他们所说的事情,同时又利用您计算机的整个核心来挖掘比特币(尽管当这种事情发生时,通常会很快被发现)。作为系统管理员、DevOps 人员、公司的万金油,您需要弄清楚容器在做什么,以及它是否符合您的需求。

我遵循一些基本原则:

  • 检查Dockerfile和源是否免费提供:

  • 通常,Docker Hub 上的存储库是从 GitLab 或其他源代码托管站点触发的构建,这意味着您可以检查容器背后的代码

  • 检查容器的下载次数:

  • 虽然这并不是质量的指标,因为经常软件的第一个镜像是最受欢迎的,但它通常是千里眼原则的一个很好的例子。如果成千上万的人在使用它,那么它隐藏在容器中的恶意内容的可能性更高(尽管仍有可能)。

  • 检查是否为官方项目的 Docker 容器:

  • 像 Redis、Kibana 和 Swift 这样的项目都有官方的 Docker 容器,所以通常我会选择它们的产品而不是其他的。

  • 该项目可能还有未标记为官方的容器,仍然带有创建者的名字。在我看来,这些容器明显优于 Jane Bloggs 的容器。

  • 这并不是说非官方的容器不好,或者它们不满足稍微不同的需求,但是,十有八九,我发现情况并非如此。

  • 您能自己构建吗?

  • 假设Dockerfile是免费许可的,您可以从 GitLab 上将其复制到您的构建服务器上,以创建自己的镜像。至少这样,您知道在过程结束时您看到的就是您得到的(假设您没有从您从未听说过的一些可疑第三方存储库中下载软件作为构建的一部分)。

尽管如此 - 听起来我对自制容器非常不满 - Docker 已经赢得了容器至高无上的战争,因为它在市场上占据了主导地位,并且易于使用(无论是构建容器还是找到它们的简单性)。

Docker Hub 意味着即使我没有本地存储库配置,但我安装了 Docker,我很快就可以在 Alpine 容器上运行一个 Web 服务器,它连接到一个 MariaDB 容器,位于 Gentoo 之上。

然后,该容器可以将日志馈送到一个容器化的 Elasticsearch 实例,运行在 Slackware 上,就在同一主机上,大约十分钟内完成。

还有更多...

如果你愿意的话,你也可以从命令行搜索:

说实话,我从来不这样做,主要是因为现在每个人都随身携带一个浏览器。然而,我知道有些人是纯粹主义者。

什么是虚拟化?

如果你随机翻开这本书的这一页,那么你现在可能知道虚拟化实际上是什么。如果你按照正常的方式,从头开始阅读,那么你很可能已经明白你几乎在整本书中都在使用虚拟化。

虚拟化是在一个机器内部虚拟化(我知道对吧?)另一台机器的行为。不像容器,我们从 USB 控制器到软盘驱动器(说真的)都进行了可视化。

这个概念并不新鲜,但技术在不断发展。

对于我们的例子,你可能和我一样,转而使用了带有 VirtualBox 的 Vagrant。我选择这种方式是因为 VirtualBox 随处可见,适用于 macOS、Linux 和 Windows(以及其他操作系统!)。这有很大的优势,但也有劣势。

虚拟化本质上与其运行的主机的软件和硬件密切相关。考虑到这一点,你可能会理解为什么企业通常选择不在所有地方使用 VirtualBox(尽管有 Windows 和 Linux 机器),而是分别使用 HyperV 和 KVM……它们更本地化。

在 Linux 领域,虚拟化软件的选择是KVMKernel Virtual Machine)。

旁白:KVM 是一个糟糕的产品或软件名称。在决定使用 Kernel Virtual Machine 之前,它已经有了一个含义,全世界的数据中心工程师自从它诞生以来一直在诅咒这个特定的三个字母缩写。键盘视频鼠标是一个标准,在我脑海中,当我听到这些字母时,我仍然想象着数据中心的崩溃车。

准备工作

在本节中,我们将研究容器和虚拟化之间的一些基本区别。

我们将首次使用我们的 Ubuntu 虚拟机和 CentOS 虚拟机。

登录到你的 CentOS 和 Ubuntu 虚拟机:

$ vagrant ssh ubuntu1804
$ vagrant ssh centos7

如何做到…

在我们的容器步骤中,我们简要地看到了在主机虚拟机上运行的内核与容器内部运行的内核是相同的。

在这一步中,我们将在我们的两个虚拟机上运行相同的命令并比较输出:

$ uname -a
Linux ubuntu-bionic 4.15.0-36-generic #39-Ubuntu SMP Mon Sep 24 16:19:09 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux $ uname -a
Linux localhost.localdomain 3.10.0-862.2.3.el7.x86_64 #1 SMP Wed May 9 18:05:47 UTC 2018 x86_64 x86_64 x86_64 GNU/Linux

我们的 Ubuntu 系统正在运行内核 4.15.0,而我们的 CentOS 系统正在运行版本 3.10.0。

这就是容器的第一个优势,它们能够运行完全不同版本的 Linux 内核。

在这方面的第二个优势是虚拟机不必与其主机相同的操作系统:你可以在 Linux 主机上模拟 Windows、FreeBSD,甚至 macOS 机器,以及几乎任何相同的组合。

macOS 有点特殊(它总是这样吗?)因为存在许可问题,你必须以非常特定的方式进行操作,但是可以做到。

让我们看看另一件有点酷的事情。

在我们的 CentOS 虚拟机上,我列出了磁盘:

$ lsblk
NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINT
sda 8:0 0 40G 0 disk 
├─sda1 8:1 0 1M 0 part 
├─sda2 8:2 0 1G 0 part /boot
└─sda3 8:3 0 39G 0 part 
 ├─VolGroup00-LogVol00 253:0 0 37.5G 0 lvm /
 └─VolGroup00-LogVol01 253:1 0 1.5G 0 lvm [SWAP]

这些不是物理驱动器,它们是虚拟的,因此你可以无限次地搞乱它们的配置,而不会损坏主机的引导潜力。

这是我一直在抱怨的一件事情,主要是因为我曾经在容器中运行了一堆 Ansible,完全搞砸了我在笔记本电脑上的安装。这个 Ansible,尽管当时我并不知道,强制性地改变了磁盘分区和布局,并且在容器的情况下,列在/dev/中的设备是你机器上的设备,这意味着我已经彻底毁掉了我的本地安装。幸运的是,我在重启之前弄清楚了发生了什么,并且能够在重新安装之前保存需要的工作,但我再也不这样做了。我还改变了测试,使用 Vagrant 和虚拟机代替……

现在,显然也有缺点——你基本上是在运行整个机器,这意味着它们必须启动(尽管你可以将它缩短到几秒钟),并且启动速度比大多数容器慢。

你可能也只需要安装一个程序(比如在 Windows 虚拟机上安装 Steam),但你会得到其他东西,意味着无论你想要还是不想要,你都会得到 Edge 浏览器、画图和那些烦人的文件夹,比如文档部分中的音乐视频图片,甚至在服务器安装中也是如此。

工作原理...

它在现代计算机上运行,主要是利用 CPU 的特性。

当你模拟你的硬件时,无论是使用 VirtualBox 还是 KVM,你真正做的是为 CPU 创建一整套独立的指令。如果我们在不原生支持 VM 的 CPU 上模拟 VM,并且无法以接近原生速度处理它们的指令,你必须甚至模拟 CPU,这可能是昂贵和缓慢的(稍后详细介绍)。

一般来说,过去十年的 CPU 将具有 AMD-V(在 AMD 的情况下)或 VT-x(在 Intel 的情况下),这意味着你的虚拟机在原始处理速度方面几乎无法与主机机器区分开来。

还有完全虚拟化半虚拟化,前者意味着模拟一切(比如,在 x86_64 处理器上模拟 aarch64 处理器),后者意味着,虽然进程的执行是分离的,但实际使用的处理器与主机是相同的(我们之前讨论过的 CPU 虚拟化感知)。

还有更多...

使用虚拟机还有更多酷炫的功能,这些功能在容器中是不可能的。

假设你是一个玩家,你真的不喜欢使用 Windows,但勉强承认你真的想和你的朋友一起玩文明,他们都是狂热的 Windows 迷。你可以(在某种程度上)在 Linux 内部做到这一点。

好吧,好吧,这样说有点不诚实,暗示你是在 Linux 内部进行操作,但这里有一个方法。

你启动一个虚拟机,安装 Windows(合法),然后将你的显卡连接到你的虚拟机...

什么?

是的!

通过 PCI-passthrough,完全可以将显卡分配给虚拟机,将显示器插入背面,然后在单独的屏幕上进行所有游戏(使用相同的鼠标和键盘)。

进展!

启动一个带有我们的虚拟机的 QEMU 机器

在这一部分,我们将在我们的虚拟机内启动一个虚拟机,并尝试连接到它。

请注意。你可能会认为本节的元素很慢。这不是你的机器或你自己的配置的错,这是物理的错,也是我们尚未拥有消费级量子计算的事实。

准备好了

SSH 到你的 Ubuntu 虚拟机:

$ vagrant ssh ubuntu1804

在 Ubuntu 上安装运行虚拟机的适当组件:

$ sudo apt install qemu -y

如何做...

我们将下载一个 Alpine ISO 并尝试在虚拟机内进行安装(在我们的虚拟机内):

$ wget http://dl-cdn.alpinelinux.org/alpine/v3.8/releases/x86_64/alpine-virt-3.8.1-x86_64.iso

我选择 Alpine 是因为它很小,只有 32MB。

接下来,我们需要创建一个虚拟磁盘来安装我们的操作系统:

$ qemu-img create example-disk 4G
Formatting 'example-disk', fmt=raw size=4294967296

现在,我们可以使用 QEMU 在我们的虚拟驱动器上启动我们的 ISO:

$ qemu-system-x86_64 -drive file=example-disk,format=raw -cdrom alpine-virt-3.8.1-x86_64.iso -boot d -nographic

幸运的话,你应该能看到以下内容:

在命令行提示符下,你应该能够以 root 用户登录(默认情况下没有密码):

localhost login: root
Welcome to Alpine!

The Alpine Wiki contains a large amount of how-to guides and general
information about administrating Alpine systems.
See <http://wiki.alpinelinux.org>.

You can setup the system with the command: setup-alpine

You may change this message by editing /etc/motd.

localhost:~# 

Alpine 的功能类似于一个接近实时 CD 的东西,所以我们现在可以继续进行快速安装到本地驱动器:

# setup-alpine

你会被问到一些标准问题。大多数情况下,你可以用默认答案,但为了完整起见,这是我做的:

  • 键盘:gb

  • 键盘变体:gb

  • 主机名:[默认(localhost)]

  • 接口:[默认(eth0)]

  • IP 地址:[默认(dhcp)]

  • 手动网络配置:[默认(否)]

  • 密码:随机

  • 时区:[默认(UTC)]

  • 代理:[默认(无)]

  • 镜像:3(英国,你可能会找到更接近你的)

  • SSH 服务器:[默认(openssh)]

  • 要使用的磁盘:sda

  • 使用方法:sys

  • 擦除并继续:y

完成后,你将在你的 Ubuntu 虚拟机内安装了 Alpine Linux 虚拟机。

关闭 Alpine 安装:

# poweroff

你会发现自己又回到了你的 Ubuntu 虚拟机。现在,我们将再次启动 Alpine,但这次我们将省略 ISO 文件和-boot参数:

$ qemu-system-x86_64 -drive file=example-disk,format=raw -nographic

正如我在开头所说的,所有这些步骤都可能需要很长时间才能完成,这取决于你的计算机的年龄。

启动后,你会发现自己又回到了 Alpine 安装界面,这次是从我们的虚拟驱动器启动的:

Welcome to Alpine Linux 3.8
Kernel 4.14.69-0-virt on an x86_64 (/dev/ttyS0)

localhost login: root
Password: 
Welcome to Alpine!

The Alpine Wiki contains a large amount of how-to guides and general
information about administrating Alpine systems.
See <http://wiki.alpinelinux.org>.

You can setup the system with the command: setup-alpine

You may change this message by editing /etc/motd.

localhost:~# 

要终止会话,要么再次关闭虚拟机,要么按下Ctrl + A,然后按下X

工作原理...

逐步分解我们在这里所做的事情,我们首先从 Alpine 网站下载了一个 ISO 映像。这是最容易解释的事情,因为我们实际上是使用 ISO 作为我们安装的真相来源。你也可以做一些其他的事情,比如将/dev/cdrom传递给你的虚拟机,如果你希望使用你机器上的物理驱动器(并且你生活在 2009 年)。

一旦我们有了 ISO 映像,我们就创建了一个基于文件的块设备来安装。这样我们可以将一个安装与另一个安装分开,甚至可以将安装从一台机器移动到另一台机器。还有其他不涉及使用文件的解决方案-你可以对 LVM 设置进行分区,将一些空间分配给你的虚拟机,或者你可以连接一个物理磁盘,并将整个磁盘分配给安装。

我们使用qemu-img创建文件,但你也可以使用其他工具,比如fallocate来完成同样的工作。

接下来,我们使用以下命令启动了我们的虚拟机:

$ qemu-system-x86_64 -drive file=example-disk,format=raw -cdrom alpine-virt-3.8.1-x86_64.iso -boot d -nographic

具体来说,我们有以下内容:

qemu-system-x86_64

这是我们想要模拟的 QEMU 架构。我选择了 x86_64,因为它是最常见的架构,也是我们下载的 ISO 期望找到的架构。如果我们愿意的话,我们也可以使用qemu-system-aarch64,并且提供适当的磁盘映像:

-drive file=example-disk,format=raw

在这里,我们向 QEMU 传递了一个要使用的驱动器,具体是我们刚刚创建的example-disk文件,以及它创建的格式:

-cdrom alpine-virt-3.8.1-x86_64.iso

我们明确告诉 QEMU 我们要使用我们下载的 ISO:

-boot d

我们想要从 CD-ROM 而不是虚拟驱动器启动:

-nographic

我们在这里是一个通过 SSH 连接的服务器,所以我们不能为我们的虚拟机使用图形输出。这个选项将串行输入和输出重定向到控制台。

还有更多...

除了速度之外,没有什么能阻止你将 QEMU 驱动的虚拟机用作完整的机器。

你可以安装软件包,甚至运行htop之类的东西:

# apk add htop

另请参阅

你可能注意到了很多我们没有使用的选项,QEMU 的系统工具功能非常强大。通常,人们不直接使用 QEMU 构建虚拟机,他们依赖更亮眼和更用户友好的工具来完成工作。

在服务器上,Virsh 是一个不错的选择(本章后面会介绍),在桌面机器上,虚拟机管理器(virt-manager)是一个非常常见的安装包,它还可以让你连接到远程(无头)服务器,使用点击按钮设置虚拟机。

使用 virsh 和 virt-install

virshvirt-install对于刚开始在 Linux 上使用虚拟机的人来说是很好的工具。现在听起来有点老土,但如果你能在命令行上做得很好,你会想知道为什么你以前需要一个点击按钮的 GUI 来帮你完成工作。

当我们这样谈论客户端时,我们指的是libvirt库的前端,它是一个设计用来使与内核的虚拟化功能交互更容易的 C 工具包。

virshvirt-installlibvirt通信,而libvirt又与内核通信。

准备工作

SSH 到您的 Ubuntu VM,然后安装virtinstlibvirt-clientslibvirt-binlibvirt-daemon软件包:

$ vagrant ssh ubuntu1804 $ sudo apt update
$ sudo apt install virtinst libvirt-clients libvirt-bin libvirt-daemon -y

如何做...

首先,我们将使用我们安装的virt-install工具创建 VM,然后我们将使用virsh对其进行探测。

创建 VM 是简单的步骤;真正麻烦的是维护机器时带来的痛苦。

virt-install

首先,让我们使用之前下载的 Alpine ISO 来启动和安装虚拟机。

如果您没有从上一节获得 ISO,这是重新下载它的命令:

$ wget http://dl-cdn.alpinelinux.org/alpine/v3.8/releases/x86_64/alpine-virt-3.8.1-x86_64.iso

这次让我们使用fallocate创建一个块设备:

$ fallocate -l 2G ex-alpine-2-disk

现在,让我们使用一行命令来配置我们的域(域是这里用于机器和其他部分的集体术语):

$ sudo virt-install --name ex-alpine-2 --memory 512 --disk ex-alpine-2-disk --cdrom alpine-virt-3.8.1-x86_64.iso --graphics none --os-variant virtio26

我们在这里使用virtio26作为 OS 变体,因为没有明确的alpine选项。相反,这告诉virt-install我们正在安装的操作系统使用的是 2.6 之后的内核,并且支持 VirtIO 设备(用于磁盘、网络等)。这使我们拥有一个正常运行的 VM,这很好。

假设一切顺利,您应该再次看到 Alpine 的引导顺序。

使用root用户和空密码登录,然后按照上一节的步骤进行安装(安装到 vda 设备)。

安装完成后,使用Ctrl + ]从控制台断开连接。

virsh

完全可以像我们之前看到的传统 Unix 风格的命令行上的一系列命令一样使用 Virsh。

但是,使用 Virsh 进行交互也是完全可以接受的,它有自己的模式。

使用以下命令启动 Virsh 终端:

$ sudo virsh
Welcome to virsh, the virtualization interactive terminal.

Type: 'help' for help with commands
 'quit' to quit

virsh #

现在,我们将与我们刚刚创建的机器进行交互。首先在命令行上列出它:

virsh # list
 Id Name State
----------------------------------------------------
 3 ex-alpine-2 running

默认情况下,此命令将显示正在运行的域。

如果我们连接到我们的 VM 并连续按Enter几次,我们可以与我们的安装进行交互:

virsh # console ex-alpine-2 
Connected to domain ex-alpine-2
Escape character is ^]

localhost:~# 
localhost:~# 
localhost:~# 

再次使用Ctrl + ]退出 VM。

让我们在我们拥有的基本域上进行构建,首先看看virt-install通过dominfo给我们的东西:

virsh # dominfo ex-alpine-2
Id: 5
Name: ex-alpine-2
UUID: 80361635-25a3-403b-9d15-e292df27908b
OS Type: hvm
State: running
CPU(s): 1
CPU time: 81.7s
Max memory: 524288 KiB
Used memory: 524288 KiB
Persistent: yes
Autostart: disable
Managed save: no
Security model: apparmor
Security DOI: 0
Security label: libvirt-80361635-25a3-403b-9d15-e292df27908b (enforcing)

现在这是有趣的部分-我们实际上还没有在安装后重新启动我们的 VM,所以让我们使用virsh来发出命令:

virsh # destroy ex-alpine-2 
Domain ex-alpine-2 destroyed
virsh # start ex-alpine-2 
Domain ex-alpine-2 started

是的,销毁在这里是一个令人困惑的词,但这是因为 VM 的实际状态是短暂的。数据在驱动器上是安全的。实际配置是域的一部分,所以当我们发出destroystart命令时,我们实际上并没有删除任何东西。我不喜欢这个术语,但这只是你学会接受的东西。

现在,我们可以再次从virsh连接到我们的 VM 控制台(这一部分可能需要一些时间):

virsh # console ex-alpine-2 
Connected to domain ex-alpine-2
Escape character is ^]

Welcome to Alpine Linux 3.8
Kernel 4.14.69-0-virt on an x86_64 (/dev/ttyS0)

localhost login: 

而且,随时可以使用Ctrl + ]断开连接。

Virsh 充满了技巧,我最喜欢的是轻松编辑域的配置 XML 的方法。

发出以下edit命令:

virsh # edit ex-alpine-2 

Select an editor. To change later, run 'select-editor'.
 1\. /bin/nano <---- easiest
 2\. /usr/bin/vim.basic
 3\. /usr/bin/vim.tiny
 4\. /bin/ed

Choose 1-4 [1]: 2

您应该进入您选择的编辑器,并看到您的 VM 的配置文件:

这在某种程度上是另一种做事情的方式。如果您习惯直接编辑文件,这可能比使用命令行更适合您(根据我的经验,有一些选项是不可能不深入研究这个文件就无法完成的)。

在离开virsh世界之前,还有一些事情,首先是version命令:

virsh # version
Compiled against library: libvirt 4.0.0
Using library: libvirt 4.0.0
Using API: QEMU 4.0.0
Running hypervisor: QEMU 2.11.1

这是一个很好的方法来确定您连接到的 hypervisor 版本,libvirt库版本和 API。

您还可以检查 vCPU 计数:

virsh # vcpucount ex-alpine-2 
maximum config 1
maximum live 1
current config 1
current live 1

然后,您可以调整数字:

virsh # setvcpus ex-alpine-2 2 --maximum --config --hotpluggable

我们还从dominfo中知道我们给了我们的 VM 512 MiB 的内存,所以让我们降低它以腾出其他 VM 的空间:

virsh # setmem ex-alpine-2 --size 400MiB

我们也可以提高它,但不能超过 VM 已经设置的最大内存(至少在这种状态下)。

它是如何工作的...

正如之前所暗示的,当你使用virt-install创建一个虚拟机时,实际上你正在编写一个包含虚拟机外观和行为的初始 XML 文件。

这个文件实际上存在于/etc/libvirt/qemu/ex-alpine-2.xml,可以像系统上的任何其他文件一样读取(virsh只是让它更容易,就像systemctl cat一样)。

当我们使用诸如virt-installvirt-viewer或任何virt-*套件时,我们可以省去很多打字和文件复制。你可以编写一个运行簿,只需几条命令就可以重新创建一个环境。然后,Virsh 存在于查询你的设置并获取有关你已经启动的解决方案的一些基本信息。

我们可以使用virsh autostart之类的东西在启动时启动一个虚拟机,如下所示:

virsh # autostart ex-alpine-2 
Domain ex-alpine-2 marked as autostarted

通过这样做,我们使位于/usr/lib/libvirt/libvirt-guests.sh的脚本能够在启动时启动虚拟机。

这个脚本又被一个systemd单元触发:

$ systemctl cat libvirt-guests
# /lib/systemd/system/libvirt-guests.service
[Unit]
Description=Suspend/Resume Running libvirt Guests
Wants=libvirtd.service
Requires=virt-guest-shutdown.target
After=network.target
After=time-sync.target
After=libvirtd.service
After=virt-guest-shutdown.target
Documentation=man:libvirtd(8)
Documentation=https://libvirt.org

[Service]
EnvironmentFile=-/etc/default/libvirt-guests
# Hack just call traditional service until we factor
# out the code
ExecStart=/usr/lib/libvirt/libvirt-guests.sh start
ExecStop=/usr/lib/libvirt/libvirt-guests.sh stop
Type=oneshot
RemainAfterExit=yes
StandardOutput=journal+console
TimeoutStopSec=0

[Install]
WantedBy=multi-user.target

还有更多...

看看virt套件的其他部分:

$ virt-
virt-admin virt-convert virt-install virt-pki-validate virt-viewer virt-xml-validate 
virt-clone virt-host-validate virt-login-shell virt-sanlock-cleanup virt-xml

每件事都有一个工具,而每个工具都有一个工具。

当你有几分钟的时候,看一下virt-clonevirt-viewer - 它们是我最喜欢的。

比较本地安装、容器和虚拟机的优势

我们将看一下本地安装、容器和虚拟机的一些明显的优缺点,以及在何时使用其中一种可能是理想的。

准备工作

如果你想在本节中跟着做,确保你已经安装并设置好了 Docker,并且启用了 QEMU 工具(都是从前面的部分)。

SSH 到你的 Ubuntu 虚拟机:

$ vagrant ssh ubuntu1804

现在,你可能想在我们的 Vagrant VM 中安装 Vagrant(用于接下来的 VM 示例):

$ sudo apt install vagrant -y

一旦你把自己添加到适当的组中,就退出你的 VirtualBox 虚拟机,然后再进入这一部分。

如何做...

从命令行开始,让我们启动一个 Nginx 实例。

你可以用三种方式之一来解决这个问题。

  1. 使用apt从默认存储库安装 Nginx

  2. 使用 Docker 从 Docker Hub 拉取官方 Nginx 镜像

  3. 设置一个虚拟机并在其中安装 Nginx,使用主机的端口转发

这些可以以以下方式完成:

$ sudo apt install nginx -y $ sudo docker run -p80 -d --rm nginx $ cat << HERE > Vagrantfile
# -*- mode: ruby -*-
# vi: set ft=ruby :

\$provisionScript = <<-SCRIPT
apt install nginx -y
SCRIPT

Vagrant.configure("2") do |config|

 config.vm.define "debian8" do |debian8|
 debian8.vm.box = "debian/jessie64"
 debian8.vm.network "forwarded_port", guest: 80, host: 8080
 debian8.vm.provision "shell",
 inline: \$provisionScript

 debian8.vm.provider "libvirt" do |lv|
 lv.driver = "qemu"
 lv.memory = 256
 lv.cpus = 1

 end

 end

end
HERE
$ sudo vagrant up

我在这里使用了一个Vagrantfile,因为这是我们在本书中一直使用的,但我们还可以以其他方式启动一个虚拟机。如果在你的虚拟机中已经运行了其他虚拟机(来自上一节),这也可能行不通,而且可能太慢而根本无法工作。

这些不同方法的优缺点是什么?

本地 Nginx 安装

首先是本地安装。这是最简单的方法,因为我们只是安装了默认 Ubuntu 存储库中 readily 可用的软件。

优点:

  • 它以 Ubuntu 的方式进行配置(即一些 Ubuntu 默认设置,比如启动脚本),并且几乎可以保证与你的设置兼容

  • 它安装非常快

  • 只要存储库保持最新,它也会保持最新,从同一位置安装的其他软件应该以本地方式与它交互,避免手动指定依赖关系之类的事情

  • 这显然会很快,并且能够利用你的主机提供的任何东西

  • 你通常可以期待在官方论坛上或者如果你与 Ubuntu 有特定的支持合同(他们可能会假设你已经从他们的默认存储库安装了东西),在问题上得到合理的帮助。

缺点:

  • 你不能轻松地安装多个版本的 Nginx;虽然这是可能的,但需要更多的工作

  • 你不能轻易删除所有的配置和文件,否则可能会留下一些东西(导致重新安装很麻烦)

  • Nginx 与系统的其他部分没有那么分离

Docker Nginx 安装

接下来,我们在一个 Nginx Docker 容器中设置一个端口转发。

这里的优点如下:

  • 启动你的实例很快

  • 可以启动多个实例,而不必担心交叉污染

  • 这些进程与主机机器相对分离(尽管可能会发生漏洞)

  • 容器可以在瞬间被拆除和重新部署,而不必担心残留的文件可能会给您带来问题

一些消极的方面如下:

  • 您必须先下载容器

  • 映射端口(未明确定义时)会导致随机 NAT'd 端口,而不是默认的端口80

  • 您可能最终得到的容器中的操作系统与主机操作系统不同(这可能会导致内部安全合规性问题)

  • 现在,您在系统上运行的软件实际上有了两个真实来源

  • 容器内的配置不一致-如果您修改了容器,必须明确保存容器的状态

  • 调试变得稍微更加麻烦

  • 如果您需要进行诸如服务文件测试之类的操作,通常没有 init 系统

虚拟机 Nginx 安装

这里有一个小考虑因素,那就是我们在虚拟机内运行了一个虚拟机,但这突显了一些问题。

一些积极的方面是:

  • 它几乎完全隔离了操作系统(除了一些像熔断这样的漏洞)

  • 对于虚拟机的资源分配有很好的控制

  • 随心所欲地拆除和启动

  • 如果您需要根据软件要求进行硬件更改,虚拟机是唯一容易实现这一点的方法

一些消极的方面是:

  • 虚拟机可能比容器慢,而且您必须考虑很多因素(例如,如果您的服务器已经是虚拟机)

  • 为了一个程序(在这个例子中),您正在运行一个完全独立的操作系统和内核

  • 由于需要用于其他操作系统的磁盘空间,虚拟机通常占用更多空间

  • 除了主机之外,您还必须管理另一台机器的更新

  • 您需要密切关注资源隔离,这可能意味着额外的监控(特别是如果您做一些诸如特定 CPU 固定的事情)

它的工作原理...

这并不是要劝阻您选择任何特定的软件安装方法,选择一种方法而不是另一种方法有很多原因。

我曾在主要使用虚拟机的环境中工作,不想使用虚拟机内的虚拟机,我通过使用容器而不是虚拟机来测试软件。

同样,正如之前提到的,我曾通过在 Docker 容器内进行硬件配置更改而搞砸了主机安装,导致主机系统永远无法再次启动。

根据经验,您很快就会厌倦管理不同的安装方法,并且在某些系统中,有些东西是从默认存储库安装的,有些是从 Snaps 安装的,有些是从 Flatpak 安装的,有些是利用 Docker 容器,这些都变得非常陈旧,非常快。

在这个例子中,我很难不选择在 Web 服务器上使用 Docker,特别是因为它提供的管理功能。我可以轻松安装多个 Nginx 实例,并且相对有信心它们永远不会知道另一个实例的存在,而无需以奇特而奇妙的方式隔离配置文件。

这从来都不是简单的。

另外,值得记住的是,因为我们在虚拟机中使用了 Vagrant 和libvirt,我们可以用 Virsh 看到我们的虚拟机:

virsh # list
 Id    Name                           State
----------------------------------------------------
 22    vagrant_debian8                running

virsh # 

我们也可以用 docker 看到我们的容器:

$ sudo docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                   NAMES
4f610d2a6bef        nginx               "nginx -g 'daemon of..."   3 hours ago         Up 3 hours          0.0.0.0:32768->80/tcp   gallant_curie

虚拟化选项的简要比较(VMware、proxmox 等)

在虚拟化方面,每个人都有自己喜欢的解决方案。

到目前为止,您现在应该知道两个选项,即 VirtualBox(我们在本书中一直在使用)和 QEMU/KVM。但是,这并不是您可用的唯一选项,如果您想在服务器上运行虚拟机,就像容器不仅限于 Docker 一样。

在这里,我们将介绍一些其他选项,其中大部分您可能在职业生涯中的某个时候都会遇到:

  • VMware ESXi

  • Proxmox

  • OpenStack

准备就绪

打开您选择的网络浏览器。

如何做到…

我们将看一些可供我们选择的选项,每个选项都有一个专门的部分。

VMware ESXi

VMware 的各种产品之一(现在是戴尔的子公司)ESXi 不是 Linux;它是一个专用的“操作系统”,位于硬件之上,虚拟机可以配置在 ESXi 之上。

这是一种许可产品,不是开源的,但它与 VMware 管理产品很好地配合(例如,您可以轻松地在一个集群中拥有多个由集中式服务器管理的虚拟机)。

就优点而言,VMware ESXi 为您提供以下内容:

  • 专用的虚拟化程序,专门设计用于执行一项任务,并且执行得很好

  • 易于设置-点击几下,您就可以安装好一个盒子

  • 包括一系列服务器在内的广泛硬件支持

  • 易于使用的软件和易于理解的菜单(在本作者看来)

就缺点而言,您可能会考虑以下几点:

  • VMware ESXi 不是开源的;这可能会影响您的决定

  • 作为专用的虚拟机服务器,ESXi 不能做任何其他值得注意的事情。

  • 作为一种产品,它可能会变得昂贵,虽然可以购买支持并签署协议,但您可能会选择完全基于预算的免费产品

VMware 可以从www.vmware.com/products/esxi-and-esx.html获得:

就个人而言,我承认曾多次使用 VMware 产品,用于各种工作,它确实如广告中所说的那样,没有太多华丽的东西。在适当的情况下,它可以优雅地处理诸如虚拟机故障转移之类的事情,而且它非常简单,任何人都可以放在控制台前轻松地进行导航(尽管我不是他们首次尝试基于 Web 的 GUI 的铁杆粉丝)。

Proxmox 虚拟环境

另一个专用的虚拟化程序安装,Proxmox(VE),是一个基于 Linux(具体来说是 Debian)的操作系统,同样具有广泛的硬件支持和友好的 GUI,让您轻松上手。

这个开源解决方案非常适合家庭实验室环境,并且可以很好地扩展到大型安装,这意味着您可以为开发人员和生产部署部署相同的解决方案。

就优点而言,您可能会考虑以下几点:

  • 它是开源的,这可能再次影响您的决定

  • 它是免费的(就像啤酒一样),并提供付费支持和培训的选项

  • 它基于已知和得到良好支持的技术,如 KVM 和 QEMU

  • 它支持容器以及虚拟机

就负面方面而言,您可能会考虑以下几点:

  • 安装基础和事实上它并不像 VMware ESXi 和其他产品那样出名(尽管这也可能对您产生积极的影响)

  • 作为专用的虚拟化安装,您的 Proxmox 服务器不会做任何其他重要的事情(如 ESXi)

Proxmox Virtual Environment 可以在www.proxmox.com/en/downloads获得:

Proxmox 虚拟化主页

同样,根据个人经验,我相对轻松地设置了三个节点的 Proxmox 集群,并实现了自动故障转移,我与使用 Proxmox 的每个人交谈时似乎都很欣赏它在紧要关头是一个多么好的解决方案,同时又知道在需要时它可以进一步扩展。

OpenStack

OpenStack 是新生力量,是一系列技术的集合,当它们组合在一起时,可以与任何更大的虚拟化环境提供者相媲美。

它可以成为虚拟机主机、容器主机、文件存储提供者、块存储提供者,并且它具有快速的开发周期,不断推出新功能。

与此列表上的其他两种解决方案不同,OpenStack 是赋予几种不同软件组件的名称。

对于优点,考虑以下几点:

  • OpenStack 有一个热情洋溢且专注的社区支持

  • 这些组件是开源的,由全球各地的人共同开发

  • 许多公司提供 OpenStack 解决方案并提供不同级别的支持

  • 如果你对 OpenStack 很了解,你未来五十年不会失业(推测)

在缺点方面,我可能会因此收到一些恶意邮件:

  • OpenStack 有一个快速的开发周期,这意味着如果你不及时更新就会被落下

  • OpenStack 可以安装在你想要的任何 Linux 发行版上,这意味着在许多情况下你也必须管理底层操作系统

  • 在我看到 OpenStack 被使用的地方,几乎需要一个专门的 OpenStack 团队来保持管理的最新状态

  • 要以可用的方式设置它并不容易,尽管开发环境确实存在

  • 有多种关于什么是一个好的 OpenStack 部署的观点

  • 当你遇到一个被忽视的 OpenStack 解决方案时,这真是一件让人头疼的事

如果你想尝试 OpenStack(我鼓励你这样做),可以在这里找到入门指南:wiki.openstack.org/wiki/Getting_Started

还有一个起始页面,包括指向 devstack 开发环境的链接:

个人想法-我认识一些非常聪明的人,他们热爱 OpenStack 并对其赞不绝口,但这是一个需要大量关注和专注的领域。

它是如何工作的...

有多种做同样事情的方式。这对大多数经验来说都是真的,尤其是在 Unix 和类 Unix(Linux)世界中更是如此。

在这里,我们有三个很好的软件和解决方案的例子,它们允许你以大多数用户友好的方式控制虚拟机部署,尽管你可能认为所有这些解决方案都比你需要的复杂得多。

我在这里提到它们是因为知道这些选择是存在的很好,并且即使你开始时通过在 Ubuntu 安装上本地安装虚拟机(使用 VirtualBox 或 KVM 和 Libvirt),你可能希望将来扩展到更宏伟的东西。

另一个要考虑的选择是公共云服务,虽然我稍后会详细讨论这些,但值得注意的是有几家提供商可以帮你摆脱管理底层软件的麻烦,让你只需安装和创建虚拟机。

如果你没有硬件或资源,甚至没有预算,你可以按小时使用公共云服务。

看看 Scaleway,Digital Ocean 和 AWS(特别是他们的 Lightsail 产品)。

总结-容器和虚拟化

短短几年前,Linux 社区出现了一个运动。容器突然无处不在,并对在一个瞬息万变的世界中可能发生的事情做出了奇妙的承诺。容器将解决你在软件方面所面临的每一个问题,它们将解决你曾经遇到的每一个安全问题,并且它们将在夜晚哄你入睡并喂养你的宠物。

我们现在知道,虽然容器很棒,它们确实是许多情况下的一个很好的解决方案,但它们并不是万能的。仍然会有一些情况,软件在裸机上运行会更好,或者虚拟机比容器更合理,你知道吗?那没关系。

如果你想的话,不要让我劝阻你尝试在容器中运行你自己的项目-这绝对是一个很好的学习经验,你可能会发现这实际上是提升和转移你的安装的最佳方式,但不要得意忘形。

虚拟机始终会有它们的位置,虽然很多测试、部署和开发环境已经转向无服务器容器部署的方式,但一个良好的本地虚拟机仍然可以提供一个不错的工作方式,特别是如果你想了解某些软件如何与整个操作系统交互(无论是一个单片应用程序,还是许多组成一个程序的小应用程序)。

归根结底,这就像我们世界上的大多数事情一样。仅仅因为你可以用一种方式做某事,并不一定意味着这是最好的方式;同样,这并不意味着你提出的解决方案不好,它可能完全适合你的需求——只是了解所有选项会很方便。

我真诚地希望我能在这本书中进行更多的探索,并深入了解管理和维护虚拟机和容器的不同方式和方法,但这不是一本关于这些东西的书——它应该是对 Linux 管理世界的一个概览。

还记得圣战吗?我也遇到过一些人反对容器的概念,认为它们是各种各样的“困难”和“毫无意义”的解决方案。如果你站在这一边并为之奋斗,要做好失败的准备,因为目前容器支持者的军队比反对者大得多。

第十章:Git、配置管理和基础设施即代码

在这一章中,我们将研究以下主题:

  • 什么是 Git?

  • 设置 Git 服务器

  • 提交到我们的 Git 仓库

  • 分支我们的 Git 仓库并提交更改

  • 安装 Ansible

  • 使用 Ansible 从一个角色安装 Java

  • 将我们的 Ansible 配置存储在 Git 中

  • 探索 IaC 的选项

介绍

曾经有一段时间,系统管理员对他们的服务器了如指掌。他们可以告诉你每个风扇的噪音,每个蜂鸣的含义,以及服务器变热时金属膨胀的声音。

与硬件一样,软件几乎是以超自然的方式索引在系统管理员的头脑中,他们可以在一瞬间告诉你他们正在运行的 OpenSSL、Apache 或 Midnight Commander 的版本。

这显然存在一个问题:被公交车撞倒的效应。

如果一名系统管理员不幸在一个早晨被撞倒,他的随身听被扔到了路边,与他的 Game Boy Color 的残骸一起,那么服务器的所有知识将在瞬间丧失。服务器设置的奇怪方式,意味着必须在恰到好处的时刻拔掉键盘,这样的知识将永远消失。

你不希望一个人对服务器的所有事情了如指掌——你希望有多个人,并且更进一步,你希望它以一种不仅易于复制和修改的格式书写,而且最好是如此简单,以至于连计算机都能理解。

在这一章中,我们将看一下软件世界中相对接近的三件事——一个版本控制系统,叫做 Git;配置管理的概念,主要是 Ansible;以及基础设施即代码,以及每个人都喜欢的 Terraform 和 Packer。

实际上,我们已经通过使用 Vagrant 来完成了很多工作。Vagrant 实际上是一种基础设施即代码的方法,尽管规模很小。我们在一些 Vagrantfiles 中放入的小脚本可以很容易地被视为配置管理,尽管在基本层面上。

这些工具是你在旅途中遇到的最好的工具之一,特别是 Git 几乎被普遍使用,所以了解它是很好的。

技术要求

在这一章中,我们将需要一些虚拟机(VMs)。

随意使用以下的Vagrantfile。我们将主要在虚拟机之间的私有网络上工作:

# -*- mode: ruby -*-
# vi: set ft=ruby :

$provisionScript = <<-SCRIPT
sed -i 's#PasswordAuthentication no#PasswordAuthentication yes#g' /etc/ssh/sshd_config
systemctl restart sshd
SCRIPT

Vagrant.configure("2") do |config|

  config.vm.provision "shell",
    inline: $provisionScript

  config.vm.define "centos1" do |centos1|
    centos1.vm.box = "centos/7"
    centos1.vm.network "private_network", ip: "192.168.33.10"
    centos1.vm.hostname = "centos1"
    centos1.vm.box_version = "1804.02"
  end

  config.vm.define "centos2" do |centos2|
    centos2.vm.box = "centos/7"
    centos2.vm.network "private_network", ip: "192.168.33.11"
    centos2.vm.hostname = "centos2"
    centos2.vm.box_version = "1804.02"
  end

end

什么是 Git?

在这一部分,我们将看一下版本控制系统(VCSs)的至高无上者。还有其他的,将来还会有更多,但现在有 Git,它是目前使用最广泛、最受欢迎的(尽管不是没有批评)。

Git 最初是由 Linus Torvalds 开发的——是的,就是那个开启 Linux 内核开发的 Linus Torvalds,尽管如今它主要由 Junio C Hamano 和许多其他才华横溢的黑客开发。它主要用于软件开发,但越来越多地用于存储诸如 Ansible、Terraform 和任何其他基础设施即代码工具的配置,从而实现对基础设施的历史和版本化的描述。

准备工作

为了理解 Git,我们将安装它,启动你的虚拟机,并跳到你的第一个 CentOS 盒子上:

$ vagrant up
$ vagrant ssh centos1

如何做...

安装 Git 很简单,因为它在大多数默认仓库中(事实上,我还没有遇到过一个 Linux 发行版,它不在默认仓库中):

$ sudo yum install git -y

警告:你很可能会得到 Perl,虽然这本质上并不是一件坏事,但提到 perl 这个词会让开发人员和系统管理员感到不适。

我们现在应该有 Git 了,所以让我们来了解一些基础知识。

克隆

从根本上说,Git 是一个主要用于源代码版本控制的版本控制系统(尽管它还有其他用途)。

为了演示它是如何工作的,让我们克隆一个较小的存储库(在这种情况下是 Ansible):

$ git clone https://github.com/ansible/ansible.git
Cloning into 'ansible'...

这可能需要几分钟,根据您的连接,但完成后,您将得到一个ansible文件夹:

$ ls -l
total 4
drwxrwxr-x. 14 vagrant vagrant 4096 Nov 19 18:10 ansible

探索和进行更改

我们现在有了一个与develAnsible 代码分支相似的副本。它是devel,因为这是存储库所有者希望默认分支的名称,尽管通常它将是masterdevelop

打开文件夹,我们会看到很多文件,里面有很多代码:

$ cd ansible/
$ ls -l
total 100
drwxrwxr-x. 2 vagrant vagrant 243 Nov 19 18:10 bin
drwxrwxr-x. 3 vagrant vagrant 141 Nov 19 18:10 changelogs
-rw-rw-r--. 1 vagrant vagrant 645 Nov 19 18:10 CODING_GUIDELINES.md
drwxrwxr-x. 4 vagrant vagrant 53 Nov 19 18:10 contrib
-rw-rw-r--. 1 vagrant vagrant 35148 Nov 19 18:10 COPYING
drwxrwxr-x. 6 vagrant vagrant 60 Nov 19 18:10 docs
drwxrwxr-x. 4 vagrant vagrant 192 Nov 19 18:10 examples
drwxrwxr-x. 5 vagrant vagrant 4096 Nov 19 18:10 hacking
drwxrwxr-x. 3 vagrant vagrant 21 Nov 19 18:10 lib
drwxrwxr-x. 2 vagrant vagrant 78 Nov 19 18:10 licenses
-rw-rw-r--. 1 vagrant vagrant 13512 Nov 19 18:10 Makefile
-rw-rw-r--. 1 vagrant vagrant 852 Nov 19 18:10 MANIFEST.in
-rw-rw-r--. 1 vagrant vagrant 286 Nov 19 18:10 MODULE_GUIDELINES.md
drwxrwxr-x. 11 vagrant vagrant 133 Nov 19 18:10 packaging
-rw-rw-r--. 1 vagrant vagrant 5370 Nov 19 18:10 README.rst
-rw-rw-r--. 1 vagrant vagrant 360 Nov 19 18:10 requirements.txt
-rw-rw-r--. 1 vagrant vagrant 11028 Nov 19 18:10 setup.py
-rw-rw-r--. 1 vagrant vagrant 3389 Nov 19 18:10 shippable.yml
drwxrwxr-x. 10 vagrant vagrant 121 Nov 19 18:10 test
-rw-rw-r--. 1 vagrant vagrant 1129 Nov 19 18:10 tox.ini

与任何良好的源代码一样,应该有一个README文件,这通常是一个很好的起点,但我们现在不是为了 Ansible 而在这里,我们是为了 Git。

运行git status

$ git status
# On branch devel
nothing to commit, working directory clean

这告诉我们,我们没有进行任何更改,因此我们的工作目录是干净的,因为我们还没有做任何事情。

README文件进行以下更改:

$ sed -i 's/^/The big brain am winning again. /g' README.rst 

现在,如果我们再次应用git status,我们将看到我们的更改:

$ git status
# On branch devel
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: README.rst
#
no changes added to commit (use "git add" and/or "git commit -a")

正如消息所暗示的,我们可以添加和提交我们的更改,然后尝试将它们合并到上游(不要这样做),但现在,我们只是简单地恢复我们刚刚对README文件所做的更改。

首先,让我们看看我们做了什么:

$ cat README.rst 
The big brain am winning again. |PyPI version| |Docs badge| |Chat badge| |Build Status| |Code Of Conduct| |Mailing Lists| |License|
The big brain am winning again. 
The big brain am winning again. *******
The big brain am winning again. Ansible
The big brain am winning again. *******
The big brain am winning again. 
The big brain am winning again. Ansible is a radically simple IT automation system. It handles
<SNIP>

哦,亲爱的,让我们修复一下:

$ git checkout README.rst 
$ git status
# On branch devel
nothing to commit, working directory clean

请注意,当我们检出README文件时,我们基本上将文件重置为其默认状态,这意味着 git 认为我们没有任何要提交的内容,文件恢复正常。

检出是用 Git 存储库的版本覆盖我们未提交的本地更改的行为。

我们可以看到这些变化已经被再次猫的文件所撤销:

$ head -5 README.rst
|PyPI version| |Docs badge| |Chat badge| |Build Status| |Code Of Conduct| |Mailing Lists| |License|

*******
Ansible
*******

不错,是吧?

值得记住的是,覆盖文件之前所做的任何更改都将丢失(因为它们没有被暂存,存储库也没有意识到它们)。

它是如何工作的...

从根本上说,Git 是一个版本控制系统,这意味着它控制着其管理的文件的版本。

大多数文件夹理论上都可以成为 Git 存储库,尽管通常是代码或配置才能获得这种特权。

当您检出存储库时,您会获得该分支上代码的快照。然后,如果您进行更改,这些更改需要被暂存,并且要么保留在一个单独的分支中,要么合并到您更改的分支中。

现在不要太担心这个——第一次有人告诉我 Git 的时候,我也感到非常困惑。

在现实世界中,人们倾向于使用 Git 的几种不同方式,以及一些相对流行的工具和实践。例如,使用 Git 的内核方法与 GitFlow 方法极为不同。

我们将在本章的其余部分介绍更改的示例,但现在,您只需要了解,如果您将某些内容提交并推送到 Git 存储库中,它将记录该更改,这意味着您可以在存储库的生命周期的任何时候返回到代码的早期版本,并将旧的修复或更改回移植到稳定分支中。

遗憾的是,Git 无法从一些奇怪的未来文件中导入您尚未编写的代码,但我仍然对此功能抱有希望。

每个存储库的配置都存储在创建存储库时创建的.git文件夹中:

$ pwd
/home/vagrant/ansible
$ ls -hla .git
total 1.5M
drwxrwxr-x. 8 vagrant vagrant 163 Nov 19 18:17 .
drwxrwxr-x. 14 vagrant vagrant 4.0K Nov 19 18:17 ..
drwxrwxr-x. 2 vagrant vagrant 6 Nov 19 18:09 branches
-rw-rw-r--. 1 vagrant vagrant 261 Nov 19 18:10 config
-rw-rw-r--. 1 vagrant vagrant 73 Nov 19 18:09 description
-rw-rw-r--. 1 vagrant vagrant 22 Nov 19 18:10 HEAD
drwxrwxr-x. 2 vagrant vagrant 242 Nov 19 18:09 hooks
-rw-rw-r--. 1 vagrant vagrant 1.4M Nov 19 18:17 index
drwxrwxr-x. 2 vagrant vagrant 21 Nov 19 18:09 info
drwxrwxr-x. 3 vagrant vagrant 30 Nov 19 18:10 logs
drwxrwxr-x. 4 vagrant vagrant 30 Nov 19 18:09 objects
-rw-rw-r--. 1 vagrant vagrant 28K Nov 19 18:10 packed-refs
drwxrwxr-x. 5 vagrant vagrant 46 Nov 19 18:10 refs

该文件夹还包含您存储库的历史记录(以二进制格式)。当您尝试查找不在您检出的分支中的文件的更改和差异时,它是搜索的地方。因此,它也可能相当大。

查看配置文件,我们可以看到克隆存储库时绘制的默认值:

$ cat .git/config 
[core]
 repositoryformatversion = 0
 filemode = true
 bare = false
 logallrefupdates = true
[remote "origin"]
 url = https://github.com/ansible/ansible.git
 fetch = +refs/heads/*:refs/remotes/origin/*
[branch "devel"]
 remote = origin
 merge = refs/heads/devel

注意远程部分,详细说明我们的origin所在的位置。

还有一个“全局”配置文件和一个“系统”文件,可以用来进行更改,影响用户与系统上任何用户交互的每个存储库。

这应该让您对 Git 为何如此出色有一个很好的了解——它允许轻松协作,因为人们可以在不同的分支上甚至相同的分支上工作,并且有一些功能可以阻止您覆盖彼此的工作。您还可以放心地进行更改,因为文件的历史记录在那里,这意味着当您不可避免地出现问题时,您可以用几个按键来解决它。

还有更多...

Git 非常强大,但它也具有一些非常好的功能。

例如,log对于阅读提交消息很有用:

$ git log --pretty=short 

输出应该如下截图所示:

例如,tag对于列出人们已添加到代码不同时间点的标签很有用:

$ git tag | head -n5
0.0.1
0.0.2
0.01
0.3
0.3.1

branch可用于查看 Git 知道的所有不同分支:

$ git branch -a
* devel
 remotes/origin/HEAD -> origin/devel
 remotes/origin/acozine-patch-1
 remotes/origin/devel
 remotes/origin/mazer_role_loader
 remotes/origin/release1.5.0
 remotes/origin/release1.5.1
 remotes/origin/release1.5.2
<SNIP>

在这里,我们使用了-a,因此远程分支和您的单个本地分支(devel)都包括在内。

设置 Git 服务器

在本节中,我们将看看如何设置一个小型的 Git 服务器,实际上就是使用我们从 CentOS 存储库安装git时得到的内容。

我们将在 centos2 上创建一个基本存储库,并从 centos1 上使用它。

准备工作

在本节中,我们将使用我们的两台机器,使用 centos2 作为服务器,centos1 作为客户端。

在这种情况下,请确保您的两台机器上都安装了git

$ sudo yum install git -y

如何做...

centos2上,让我们创建我们的空存储库:

$ mkdir example.git
$ cd example.git/
$ git init --bare
Initialized empty Git repository in /home/vagrant/example.git/

现在,在我们的centos1机器上,我们可以克隆存储库:

$ git clone 192.168.33.11:example.git
Cloning into 'example'...
The authenticity of host '192.168.33.11 (192.168.33.11)' can't be established.
ECDSA key fingerprint is SHA256:s5NfsrM/XRuH5rXaZSaNmaUxXe3MlN2wRoJ3Q43oviU.
ECDSA key fingerprint is MD5:ea:24:ef:b3:cf:d9:03:3d:06:da:1f:2f:d2:6b:1d:67.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.33.11' (ECDSA) to the list of known hosts.
vagrant@192.168.33.11's password: 
warning: You appear to have cloned an empty repository.

您将被提示输入vagrant用户密码(默认为vagrant),然后您将看到一条消息,通知您已经克隆了一个空存储库。

这很好,因为现在您应该看到一个新的example目录:

$ ls
example
$ cd example/
$ git status
# On branch master
#
# Initial commit
#
nothing to commit (create/copy files and use "git add" to track)

工作原理...

当您在 centos2 上初始化存储库时,您正在创建一个可以存在于任意数量的设备上的存储库,不一定需要从您的中央位置(centos2)克隆。

如果由于某种原因,您无法从初始服务器(centos2)克隆,您还可以从已经检出存储库的另一台机器克隆存储库(尽管您可能面临存储库与第一个节点不同步的风险)。

当您克隆存储库时,实际上是通过 SSH 与centos2进行通信,并且 Git 发送特定命令,这些命令被另一侧的存储库理解并回答。

同样,如果我们在centos1上检出.git存储库,在克隆的存储库中,我们可以看到以下内容:

$ cat .git/config 
[core]
 repositoryformatversion = 0
 filemode = true
 bare = false
 logallrefupdates = true
[remote "origin"]
 url = 192.168.33.11:example.git
 fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
 remote = origin
 merge = refs/heads/master

请注意打印为远程origin的 URL。还要注意这不是一个bare存储库。

让我们看看服务器(centos2)上的config文件:

$ cat config 
[core]
 repositoryformatversion = 0
 filemode = true
 bare = true

这里设置为truebare要小得多。

还有更多...

这是最简单的 Git 存储库,如果您想使其更有趣,还有更多事情可以做。

首先,您可能考虑在系统上创建一个专用的git用户,并使用此用户来管理和拥有 Git 存储库。这通常是一个非常标准的方法,也是 GitLab 和 GitHub 等现成的 Git 解决方案所共享的方法。

其次,您可以考虑设置一个cgit服务器,这是一个小型服务器,可用于可视化 Git 存储库。

最著名的cgit实例可能是git.kernel.org

提交到我们的 Git 存储库

在本节中,我们将编写一个小文件,将其添加到 Git 正在跟踪的文件中,提交我们的更改,并将其推送到原始服务器。

准备工作

如果尚未这样做,请根据上一节设置基于 Git 的服务器。

确保您已在centos1上检出您的存储库。

如何做...

让我们为我们的新存储库添加一些信息并将其推送到服务器(证明它有效):

$ cd example/

让我们首先运行git status来查看我们的进度:

$ git status
# On branch master
nothing to commit, working directory clean

好的-它是空的。

现在,让我们回显一些文本到一个文件中,将文件放入暂存区,并提交带有消息的文件:

$ echo "Warcraft III was the best Warcraft game." > README
$ git add README
$ git commit -m "Adding README to repository." 

*** Please tell me who you are.

Run

 git config --global user.email "you@example.com"
 git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.

fatal: unable to auto-detect email address (got 'vagrant@centos1.(none)')

啊!现在,因为这是一个新的安装,Git 提示我们设置一些默认值:

$ git config --global user.email "you@example.com"
$ git config --global user.name "Your Name... good film."

让我们再试一次提交:

$ git commit -m "Adding README to repository." 
[master (root-commit) 65bf49a] Adding README to repository.
 1 file changed, 1 insertion(+)
 create mode 100644 README

好了,我们已经提交了我们的文件,这意味着本地版本的master是最新的。

然而,这意味着我们现在与我们的源存储库不同步,所以我们也需要将我们的更改push到那里:

$ git push
warning: push.default is unset; its implicit value is changing in
Git 2.0 from 'matching' to 'simple'. To squelch this message
and maintain the current behavior after the default changes, use:

 git config --global push.default matching

To squelch this message and adopt the new behavior now, use:

 git config --global push.default simple

See 'git help config' and search for 'push.default' for further information.
(the 'simple' mode was introduced in Git 1.7.11\. Use the similar mode
'current' instead of 'simple' if you sometimes use older versions of Git)

哇!又一个提示-我们应该设置它。就我个人而言,我选择了新行为,因为虽然 CentOS 7 附带了一个旧版本的 Git,但进步的步伐仍在继续:

$ git config --global push.default simple

呼!好吧,让我们再试一次推送:

$ git push
vagrant@192.168.33.11's password: 
Counting objects: 3, done.
Writing objects: 100% (3/3), 268 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To 192.168.33.11:example.git
 * [new branch] master -> master

在这里,您可以看到我们被提示输入密码,但一旦输入密码,更改就会被写入原始服务器。

这意味着当我们再次从原始克隆存储库克隆存储库时,我们的更改将随之而来。

尝试从您的主目录运行以下命令以证明这一点:

$ cd ~
$ git clone 192.168.33.11:example.git example2
Cloning into 'example2'...
vagrant@192.168.33.11's password: 
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0)
Receiving objects: 100% (3/3), done.
$ ls example2
README

卓越!

它是如何工作的...

在这里,我们正在做一些事情。

首先,在我们创建了带有一些文本的 foo 文件后,我们add了该文件:

$ git add README

在这个小命令中,我们实际上是将README文件添加到 Git 的索引中。

我们以这种方式暂存的任何东西都将在下一次提交中添加:

$ git commit -m "Adding README to repository." 

在这里,我们正在提交(正如名称所示)我们对存储库的添加更改。我们还在命令中包括消息(使用-m)作为命令的一部分,而不是让git commit将我们放入默认编辑器中(因为每次提交都应该有一个消息,因为它提示您写一个)。

最后,我们推送了我们的更改:

$ git push

在技术术语上,我们正在通知远程存储库(在192.168.33.11上)一些引用已经改变,并告诉它哪些引用已经改变。我们还推送了与这些引用更改相关的任何对象。

匹配与简单

关于这一点,我们在本节中设置了我们的默认值。

如果我们将选项设置为匹配,那么在运行git push命令时,我们将一次推送所有分支。这意味着不仅您当前的分支,而且masterdevelop和您可能拥有的任何其他分支也将被推送。

另一方面,简单只会推送你当前正在工作的分支。从逻辑上讲,简单会成为默认行为是可以理解的,因为它假设大多数人一次只会在一个分支上工作。

分支我们的 Git 存储库并提交更改

在本节中,我们将看看如何分支我们的克隆存储库并将这些更改推送到我们的服务器。

做好准备

确保您至少已经完成了设置部分,但最好是您也提交了第一个文件的部分。

所有工作将在centos1example存储库中完成。

如何做到这一点...

切换到example存储库:

$ cd ~/example

现在,检查一下你是否在master上开始:

$ git branch
* master

您当前的分支用星号表示。

好了!现在,我们要从masterbranch出去,并做一些更改:

$ git branch develop

再次运行git branch

$ git branch
 develop
* master

请注意,现在我们有两个分支,但我们仍然在master分支上。我们需要运行不同的命令来切换到我们的新分支:

$ git checkout develop
Switched to branch 'develop'

现在,列出这个分支中有什么:

$ ls
README

我们实际上已经复制了master分支,这意味着提交到该分支的所有文件都可以在我们的新分支中使用。

让我们创建一个新文件并给它一些内容:

$ echo "Keep your keys happy." > RULESFORRULERS

再次运行git status会告诉我们我们的未跟踪更改:

$ git status
# On branch develop
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# RULESFORRULERS
nothing added to commit but untracked files present (use "git add" to track)

正如它所说,让我们addcommit这个文件:

$ git add *
$ git commit -m "Adding an extremely important file\!"
[develop 7f91f4d] Adding an extremely important file\!
 1 file changed, 1 insertion(+)
 create mode 100644 RULESFORRULERS

现在,我们在本地有一个分支,有一个提交的文件,在原始服务器上根本不存在。

让我们尝试将我们的分支push192.168.33.11

$ git push
fatal: The current branch develop has no upstream branch.
To push the current branch and set the remote as upstream, use

 git push --set-upstream origin develop

哦不!

因为我们的本地develop分支没有配置上游对应项,所以push失败了。

然而,您已经知道这一点,因为您是一个勤奋的系统管理员,实际上阅读了错误消息,以及它给我们提供的有用命令来纠正这个问题:

$ git push --set-upstream origin develop
vagrant@192.168.33.11's password: 
Counting objects: 4, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 326 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To 192.168.33.11:example.git
 * [new branch] develop -> develop
Branch develop set up to track remote branch develop from origin. 

好吧,现在我们在本地有一个developmaster分支,以及一个在上游服务器上的副本。

让我们检出master看看现在是什么样子:

$ git checkout master
Switched to branch 'master'
$ ls
README

什么?我们的文件在哪里?它就在这个目录里!

啊,但不,这是同一个目录的不同分支,在这个黑暗的时间线中,我们的工作并不存在。

让我们只检查一下我们两个分支之间的差异:

$ git diff develop
diff --git a/RULESFORRULERS b/RULESFORRULERS
deleted file mode 100644
index c895005..0000000
--- a/RULESFORRULERS
+++ /dev/null
@@ -1 +0,0 @@
-Keep your keys happy.

我们的工作在这里!

好吧,那么如果我们想要从develop合并我们的更改怎么办?

Git 就是这样的:

$ git merge develop
Updating 65bf49a..7f91f4d
Fast-forward
 RULESFORRULERS | 1 +
 1 file changed, 1 insertion(+)
 create mode 100644 RULESFORRULERS
$ ls
README RULESFORRULERS

砰!我们仍然在我们的master分支上,但文件又回来了。

令人困惑的是,如果我们现在运行git status,我们会看到一个新消息:

$ git status
# On branch master
# Your branch is ahead of 'origin/master' by 1 commit.
# (use "git push" to publish your local commits)
#
nothing to commit, working directory clean

幸运的是,再次,这条消息很好地解释了。我们的本地master分支很好,但upstream还不知道我们的更改。

我们可以用另一个diff来证明这一点:

$ git diff origin/master
diff --git a/RULESFORRULERS b/RULESFORRULERS
new file mode 100644
index 0000000..c895005
--- /dev/null
+++ b/RULESFORRULERS
@@ -0,0 +1 @@
+Keep your keys happy.

让我们把我们的更改上传:

$ git push
vagrant@192.168.33.11's password: 
Total 0 (delta 0), reused 0 (delta 0)
To 192.168.33.11:example.git
 65bf49a..7f91f4d master -> master

魔术。

它是如何工作的...

好吧,这并不是真正的魔术,只是一些极其出色的编码的产物。

当我们从master(或者任何分支)分支出来时,我们在新分支中创建了一个时间点的克隆。然后我们对分支进行了更改,做了一些像测试配置更改或者故意破坏东西的事情,而不会影响master分支(和我们已知的良好配置)。

这一点很容易理解(我认为),但当我们想要确保我们的更改被推送到服务器时,问题就开始了。

我们运行了git push,但它抱怨了。这是因为我们的.git/config 文件中没有一个告诉 git 在哪里推送我们的本地分支的部分。

在我们运行建议的命令之后,我们的.git/config现在看起来是这样的:

$ cat .git/config 
[core]
 repositoryformatversion = 0
 filemode = true
 bare = false
 logallrefupdates = true
[remote "origin"]
 url = 192.168.33.11:example.git
 fetch = +refs/heads/*:refs/remotes/origin/*
[branch "master"]
 remote = origin
 merge = refs/heads/master
[branch "develop"]
 remote = origin
 merge = refs/heads/develop

注意包含一个新分支定义。

所以,推送完成了,远程看起来很好。

然后我们切换回master并执行我们的git diff命令:

$ git diff develop
diff --git a/RULESFORRULERS b/RULESFORRULERS
deleted file mode 100644
index c895005..0000000
--- a/RULESFORRULERS
+++ /dev/null
@@ -1 +0,0 @@
-Keep your keys happy.

这应该是相当容易理解的,但我们正在执行一个diff,对比我们现在所在的分支以及指定的分支。

你可以使用以下更明确的语法来实现相同的事情:

$ git diff master..develop

或者,你甚至可以检查你不在的两个分支:

$ git diff origin/master..origin/develop

通过这种方式,我们开始看到 Git 作为开发工具的强大之处。

它也很好地引出了我谈论origin分支。

在这一部分,我们将我们的本地主分支与origin/master进行了比较,如下所示:

$ git diff origin/master
diff --git a/RULESFORRULERS b/RULESFORRULERS
new file mode 100644
index 0000000..c895005
--- /dev/null
+++ b/RULESFORRULERS
@@ -0,0 +1 @@
+Keep your keys happy.

从我们的.git/config中,我们了解到origin是远程仓库的名称,就 Git 而言:

[remote "origin"]
  url = 192.168.33.11:example.git
  fetch = +refs/heads/*:refs/remotes/origin/*

因此,origin/masterremote服务器上的master分支。

我们推送到origin/master(以git push的形式)保持我们的代码是最新的。

还有更多...

你可能会想知道,如果你只是要在本地将你的分支合并到master,为什么还要推送到一个remote分支。

答案很简单:在大多数环境中,你最终会与许多其他人一起工作,能够展示你的改动和你正在处理的内容是一个很好的开发工具。它还允许诸如合并之前的分支协作之类的事情,因为任何其他人都可以检出你的分支进行查看。

大多数人并不只是在他们的基础设施中使用 Git 服务器,而是寻找诸如 Gogs 和 GitLab 之类的解决方案,这些是具有附加功能的 Git 服务器(如用户管理)。

你可能也在考虑服务器,并且欣赏到在这一部分,它有点拖沓,实际上并不是真正需要的。

让我们快速查看一下服务器的更改,首先创建并切换到一个新的分支:

$ git checkout -b super-fixes
Switched to a new branch 'super-fixes'

我在这里使用了创建和检出分支的简写,将-b添加到git checkout命令中。Git 有数百种小技巧,人们倾向于有自己的喜好。

在这个分支中,让我们创建一个新文件并将其pushupstream

$ echo "Never look tired." > RULESFORRULERS
$ git commit -am "Another git trick, adding and committing in one line." 
[super-fixes a9b53e8] Another git trick, adding and committing in one line.
 1 file changed, 1 insertion(+), 1 deletion(-)
$ git push --set-upstream origin super-fixes
vagrant@192.168.33.11's password: 
Counting objects: 5, done.
Compressing objects: 100% (2/2), done.
Writing objects: 100% (3/3), 329 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To 192.168.33.11:example.git
 * [new branch] super-fixes -> super-fixes
Branch super-fixes set up to track remote branch super-fixes from origin.

接下来,切换到centos2并进入仓库:

$ cd ~/example.git/

通过运行ls,你看不到你的实际文件:

$ ls
branches config description gitweb HEAD hooks info objects pid refs

相反,你的文件在objects目录中,以它们当前的格式是无法阅读的:

$ ls objects/
1c 29 65 7f 84 a5 a9 b0 c8 info pack

我们可以通过它们的ref看到我们的分支:

$ ls refs/heads/
develop master super-fixes

您不会在服务器上的裸仓库中工作-您会在另一台机器上克隆并调整您的仓库,但偶尔查看内部工作情况也是很好的。

另请参阅

您可能想要安装 GitLab、Gogs 或其他 GUI-Git 实现,以便您可以尝试它们。就我个人而言,我发现它们相当直观,当团队有一个共同厌恶的 GUI 而不是一系列控制台命令时,他们往往能更好地工作。

安装 Ansible

在本节中,我们将介绍一种最受欢迎的配置管理工具,即 Ansible。与其他工具(如 Chef、Puppet 和 Salt)一样,Ansible 是一种将服务器配置编码的方式,也就是说,您想要进行的任何更改都可以写入文件并以程序方式应用于服务器或一组服务器。

您组合在一起的配置文件可以保存到一个中央存储库,最常见的是 Git,您可以构建诸如自动管道之类的东西,围绕您的配置,以便对该配置的更改会自动应用。

在默认情况下,您的 Ansible 是从基础架构中的指定框中运行的(通常是 GitLab、Jenkins 或 Ansible Tower 安装),或者在较小的环境中。工程师们经常在自己的机器上进行更改并不是不寻常的。

Ansible 不仅适用于具有涵盖各种场景的模块的服务器,例如云提供商配置、交换机配置,甚至证书管理。

我们将在 centos1 上运行一些小的 Ansible 命令,对 centos2 进行更改。

准备工作

打开到您的centos1机器的连接,同时安装 Ansible:

$ vagrant ssh centos1 $ sudo yum install ansible ansible-doc -y

如何做...

首先,我们将检查我们安装的 Ansible 的版本:

$ ansible --version
ansible 2.4.2.0
 config file = /etc/ansible/ansible.cfg
 configured module search path = [u'/home/vagrant/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
 ansible python module location = /usr/lib/python2.7/site-packages/ansible
 executable location = /usr/bin/ansible
 python version = 2.7.5 (default, Apr 11 2018, 07:36:10) [GCC 4.8.5 20150623 (Red Hat 4.8.5-28)]

这是版本2.4.2.0

与标准存储库中的许多软件包一样,它们往往是稳定的(较旧的)版本。如果您愿意,也可以安装pip(在第十一章中介绍了 Web 服务器、数据库和邮件服务器),并使用它安装更近期的 Ansible 版本。现在,我们将坚持使用2.4.2.0

为了测试我们的连接是否存在,我们可以使用一个名为ping的默认 Ansible 模块(尽管这不是 ICMP)。您将被提示输入SSH 密码(vagrant):

$ ANSIBLE_HOST_KEY_CHECKING=false ansible -k -m ping -i 192.168.33.11, 192.168.33.11
SSH password: 
192.168.33.11 | SUCCESS => {
 "changed": false, 
 "ping": "pong"
}

我们添加了'ANSIBLE_HOST_KEY_CHECKING=false'变量,以确保在运行命令时远程机器的 SSH 主机密钥被接受。后续命令不应该需要这个。在生产场景中,您应该始终确认您信任远程机器的密钥。

太棒了!我们的centos2框做出了响应,更好的是,我们的命令告诉我们这并没有导致对远程机器的更改(稍后会详细介绍)。

接下来,让我们尝试使用不同的模块安装一些东西。我们想要安装一个软件包,但我们不知道如何操作。

为此,您可以访问 Ansible 网站(docs.ansible.com/ansible/latest/modules/list_of_all_modules.html)并搜索package,或者您可以使用ansible-doc

$ ansible-doc -l

运行时,您将进入一个分页器,以及所有可用 Ansible 模块的列表。运行搜索(输入/字符,然后输入搜索字符串)以搜索package,并继续直到找到该名称的模块:

不能保证有一个模块存在于您想要的功能中,但也有很大的可能性。搜索与您尝试执行的操作相关的关键字,十有八九您可能会感到惊喜。

假设您已经发现了称为package的软件包模块,您可以退出此分页器。

现在,让我们看看package的语法是什么:

$ ansible-doc -s package
- name: Generic OS package manager
 package:
 name: # (required) Package name, or package specifier with version, like `name-1.0'. Be aware that packages are not always named the same and this module will not 'translate' them
 per distro.
 state: # (required) Whether to install (`present', or remove (`absent') a package. Other states depend on the underlying package module, i.e `latest'.
 use: # The required package manager module to use (yum, apt, etc). The default 'auto' will use existing facts or try to autodetect it. You should only use this field if the
 automatic selection is not working for some reason.

看起来不错,它告诉我们的是要name软件包的要求正是我们需要的,以及它应该处于什么状态。

太棒了!我们可以利用这个知识:

$ ansible -b -k -m package -a 'name=zip state=present' -i 192.168.33.11, 192.168.33.11
SSH password: 
192.168.33.11 | SUCCESS => {
    "changed": true, 
    "msg": "warning: /var/cache/yum/x86_64/7/base/packages/zip-3.0-11.el7.x86_64.rpm: Header V3 RSA/SHA256 Signature, key ID f4a80eb5: NOKEY\nImporting GPG key 0xF4A80EB5:\n Userid : \"CentOS-7 Key (CentOS 7 Official Signing Key) <security@centos.org>\"\n Fingerprint: 6341 ab27 53d7 8a78 a7c2 7bb1 24c6 a8a7 f4a8 0eb5\n Package : centos-release-7-5.1804.el7.centos.x86_64 (@anaconda)\n From : /etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7\n", 
    "rc": 0, 
    "results": [
        "Loaded plugins: fastestmirror\nLoading mirror speeds from cached hostfile\n * base: mirrors.vooservers.com\n * extras: mirror.sov.uk.goscomb.net\n * updates: mirror.sov.uk.goscomb.net\nResolving Dependencies\n--> Running transaction check\n---> Package zip.x86_64 0:3.0-11.el7 will be installed\n--> Finished Dependency Resolution\n\nDependencies Resolved\n\n================================================================================\n Package Arch Version Repository Size\n================================================================================\nInstalling:\n zip x86_64 3.0-11.el7 base 260 k\n\nTransaction Summary\n================================================================================\nInstall 1 Package\n\nTotal download size: 260 k\nInstalled size: 796 k\nDownloading packages:\nPublic key for zip-3.0-11.el7.x86_64.rpm is not installed\nRetrieving key from file:///etc/pki/rpm-gpg/RPM-GPG-KEY-CentOS-7\nRunning transaction check\nRunning transaction test\nTransaction test succeeded\nRunning transaction\n Installing : zip-3.0-11.el7.x86_64 1/1 \n Verifying : zip-3.0-11.el7.x86_64 1/1 \n\nInstalled:\n zip.x86_64 0:3.0-11.el7 \n\nComplete!\n"
    ]
}

如果远程机器上的 sudo 操作需要密码,我们也可以包括-K,我们的没有。

太棒了!所以我们成功使用 Ansible 在我们的远程机器上安装了zip软件包(我会让读者猜测这个软件包的作用)。让我们再次运行这个命令——会发生什么?它会重新安装软件包吗?

$ ansible -b -k -m package -a 'name=zip state=present' -i 192.168.33.11, 192.168.33.11
SSH password: 
192.168.33.11 | SUCCESS => {
    "changed": false, 
    "msg": "", 
    "rc": 0, 
    "results": [
        "zip-3.0-11.el7.x86_64 providing zip is already installed"
    ]
} 

事实证明,不,Ansible 不会重新安装软件包。这是 Ansible 的一个基本理念,远程主机上运行的作业应该是幂等的,也就是说,当不需要时它们不应该做任何更改。

在编写 Ansible 时,确保你编写的代码是幂等的,而不是每次运行都进行更改,这是一个很好的实践。如果不断进行更改,代码就不会是幂等的,你无法保证系统的状态。

有时,特别是在远程系统上执行直接命令时,幂等性必须仔细考虑,有时必须手动构建到你的 Ansible 运行逻辑中,尽管这是一门更高级的学科。即使我偶尔也会在这个问题上求助于更聪明的朋友。

为了完整起见,让我们做最后一件事:

$ ansible -b -k -m yum -a 'name=zip state=present' -i 192.168.33.11, 192.168.33.11
SSH password: 
192.168.33.11 | SUCCESS => {
    "changed": false, 
    "msg": "", 
    "rc": 0, 
    "results": [
        "zip-3.0-11.el7.x86_64 providing zip is already installed"
    ]
} 

你能发现这个命令和我们上一个命令之间的区别吗?

是 yum!

具体来说,我们现在所做的是使用 Ansible 的yum模块代替package,这意味着我们已经使得我们的命令只能在基于 YUM 的系统上工作。这突显了实现相同目标的方法不止一种,我们很快会讨论为什么你可能想使用其中一个模块。

它是如何工作的...

现在我们已经快速看了一下 Ansible 如何用于在远程机器上运行作业,让我们来分解一下我们做了什么,从ping模块开始:

$ ANSIBLE_HOST_KEY_CHECKING=false ansible -k -m ping -i 192.168.33.11, 192.168.33.11

让我们按顺序讨论这些参数:

-k

在这里,-k表示 Ansible 将知道提示我们输入密码,我们将用它连接到远程机器(我们 Vagrant 用户的 SSH 密码):

-m ping

在这里,我们告诉 Ansible 我们要使用ping模块:

-i 192.168.33.11,

在这里,我们正在构建一个清单,尽管可以承认只有一个机器的清单。

Ansible 需要知道它可以使用的主机列表——它的清单——但在这里,我们只有一个,所以我们使用它(确保以逗号结尾):

192.168.33.11

最后,我们以我们要访问的主机的 IP 结束,有点反直觉的是,我们必须重复。

这里我们缺少的一件事是-u标志,如果我们想要作为一个不是vagrant的用户SSH到远程主机,我们会使用它。

既然我们在这里,我们也要看一下我们的第二个ansible-doc命令:

$ ansible-doc -s package

在这里,我们想要看到我们可以为package模块编写的代码片段。这在服务器上很有用,或者在考试情况下非常有用,你可能无法访问更广泛的互联网。

最后,让我们分解一下我们的最后一个命令(尽管相同的原则可以应用到package示例中):

$ ansible -b -k -m yum -a 'name=zip state=present' -i 192.168.33.11, 192.168.33.11

在这里,有一些区别:

-b

我们在这里使用-b,因为我们正在进行需要我们在远程机器上“变成”(become)的更改。这意味着以特权升级(变成 root)运行命令。

-m yum -a 'name=zip state=present'

在这里,我们在我们的参数列表中添加了-a,具体是因为yum模块需要自己的参数,然后我们将这些参数放在引号内。

基于你使用的模块,这些参数可能会变得很长,但在这里它的数量相对较少。

第二次我们使用了yum,特别是为了突出虽然实现了相同的目标(安装zip软件包),我们有选择将我们的命令与特定的发行版绑定。

这样做的原因很简单,当你看一下package模块可用的参数列表时就明白了:

  • name

  • state

  • use

您还可以查看yum模块可用的内容,以帮助解决这个问题:

  • allow_downgrade

  • conf_file

  • disable_gpg_check

  • disablerepo

  • enablerepo

  • exclude

  • installroot

  • list

  • name

  • security

  • skip_broken

  • state

  • update_cache

  • validate_certs

希望您能看到yum模块如何被用来为 Ansible 制定更具体和详细的指令。

在现实世界中,我看到工程师编写代码,试图用一个角色满足所有用例,也就是说,一个角色可以在 Debian、CentOS 或 Alpine 系统上应用并完成相同的事情。这也许是一个崇高的目标,但通常会导致工程师或接下来的工程师因为不灵活而感到极度沮丧。通常情况下,我发现更容易的方法是简单地使用特定于发行版的模块,并让 Ansible 在选择要执行的命令之前检查它正在运行的发行版的类型,或者更好的是,只在您的基础设施中使用一个发行版,而不是混合使用。

还有更多...

还有一些小事情需要注意。

原始模块

在这种情况下,centos2 已经安装了 Python(Ansible 需要在远程机器上运行模块)。但是,如果没有安装,您将需要首先在机器上安装它,或者在任何其他模块之前使用 Ansible 的内置raw模块。

当您使用raw时,实际上是直接在远程机器上运行命令。这通常是通过类似以下的行来看到的:

$ ansible -b -k -m raw -a 'whoami' -i 192.168.33.11, 192.168.33.11
SSH password: 
192.168.33.11 | SUCCESS | rc=0 >>
root
Shared connection to 192.168.33.11 closed.

请注意,远程机器只是简单地回应了SUCCESS和结果命令。

我们将使用这个来安装python

$ ansible -b -k -m raw -a 'yum install python -y' -i 192.168.33.11, 192.168.33.11
SSH password: 
192.168.33.11 | SUCCESS | rc=0 >>
Loaded plugins: fastestmirror
Loading mirror speeds from cached hostfile
<SNIP>
Updated:
 python.x86_64 0:2.7.5-69.el7_5 

Dependency Updated:
 python-libs.x86_64 0:2.7.5-69.el7_5 

Complete!
Shared connection to 192.168.33.11 closed.

它的格式不太好,但是现在我们在remote机器上有了python,我们将能够使用适当的 Ansible 模块,而不是依赖简单的SSH执行。

shell 和 command 模块

raw一样,您可以使用 Ansible 将本机命令传递给远程机器(实际上,您可以非常容易地将整个 bash 脚本转换为 Ansible 角色),尽管通常这种功能是为您绝对无法使用专用 Ansible 模块来实现的情况保留的。

但是这两个确实需要 Python 在远程机器上。

首先,让我们看看shell模块:

$ ansible -b -k -m shell -a 'whoami > BUTTER; cat BUTTER | rev' -i 192.168.33.11, 192.168.33.11
SSH password: 
192.168.33.11 | SUCCESS | rc=0 >>
toor

请注意,我基本上是在我的 Ansible 命令中运行一个小的一行命令。

接下来,让我们使用command模块:

$ ansible -b -k -m command -a 'whoami > BUTTER; cat BUTTER | rev' -i 192.168.33.11, 192.168.33.11
SSH password: 
192.168.33.11 | FAILED | rc=1 >>
whoami: extra operand '>'
Try 'whoami --help' for more information.non-zero return code

啊...这失败了。

这是因为command并没有显式地运行一个 shell(比如 Bash),所以当我们尝试使用 bashism 形式的>时,它失败了。command模块只对短命令真正有效:

$ ansible -b -k -m command -a 'whoami' -i 192.168.33.11, 192.168.33.11
SSH password: 
192.168.33.11 | SUCCESS | rc=0 >>
root

参见

在这里,我们使用了SSH密码来在我们的盒子上运行远程命令,但这通常不会这样做(特别是在自动化环境中)。通常情况下,使用 SSH 密钥,并且权限要得到仔细控制。

使用 Ansible 从角色安装 Java

Ansible 非常适合做我们刚刚做的事情,通过在远程机器上使用模块运行易于理解的命令。

但这不是它的核心。相反,当需要对远程机器运行一系列命令并在几秒钟内进行大规模配置更改时,Ansible 真正发挥作用。

在本节中,我们将编写一个小的 Ansible Playbook 来从公共存储库导入一个 Ansible 角色,并将该角色应用到我们的 centos2 机器上。

抓住你的帽子!

准备工作

对于本节,我们再次使用我们的Vagrantfile和两个 CentOS 虚拟机。

在处理这一部分之前,最好先完成上一部分。

SSH到您的centos1机器:

$ vagrant ssh centos1

如何做...

让我们首先创建一个目录,在这个目录中我们将要工作并进入其中:

$ mkdir ansible-example
$ cd ansible-example/

现在,我们说我们要使用一个公共的 Ansible 角色来完成这项工作,所以让我们搜索一个:

$ ansible-galaxy search "java for linux" --author geerlingguy

Found 7 roles matching your search:

 Name Description
 ---- -----------
 geerlingguy.ac-solr Apache Solr container for Docker.
 geerlingguy.elasticsearch Elasticsearch for Linux.
 geerlingguy.java Java for Linux
 geerlingguy.puppet Puppet for Linux.
 geerlingguy.solr Apache Solr for Linux.
 geerlingguy.sonar SonarQube for Linux
 geerlingguy.tomcat6 Tomcat 6 for RHEL/CentOS and Debian/Ubuntu.

您还可以使用galaxy.ansible.com/网站:

在 Ansible Galaxy 上搜索 java

我实际上更倾向于使用网站而不是命令行,因为它可以让您一目了然地了解角色的流行程度。我已经知道geerlingguy编写的角色非常受欢迎,所以我选择在搜索中明确包含他的名字。

现在我们知道我们想要使用哪个角色,让我们下载它:

$ sudo ansible-galaxy install geerlingguy.java
- downloading role 'java', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-java/archive/1.9.4.tar.gz
- extracting geerlingguy.java to /etc/ansible/roles/geerlingguy.java
- geerlingguy.java (1.9.4) was installed successfully

太棒了!我们有了我们的角色。

现在我们如何应用它?

当然是用 Playbook!

在您的系统上运行以下命令:

$ cat <<HERE > playbook.yml
---
- hosts: all
 roles:
 - geerlingguy.java
HERE

现在,对 centos2 运行我们的playbook

$ ansible-playbook -k -b -i 192.168.33.11, playbook.yml

当您的屏幕上出现一系列信息时,我们之前下载的角色将被应用到远程机器上:

请注意运行底部的ok=6changed=1。如果您现在再次运行 playbook,您应该会得到changed=0,因为该角色是幂等的。

就是这样!只用三个命令(实际上),您就可以在远程机器上安装 Java。

它是如何工作的...

通过逐条命令运行我们在本节中所做的事情,我们将分解发生的事情。

$ ansible-galaxy search "java for linux" --author geerlingguy

最初,我们需要知道是否有人已经编写了一个在服务器上安装 Java 的 Ansible 角色。事实上,我们能够搜索到 Linux 的 Java,同时传入作者的名字,结果找到了七个角色。

我有一个朋友喜欢为工作编写自己的 Ansible,但我一直认为,如果解决方案已经存在(而且合适),采用其他人编写的工作是没有害处的,只要其许可证允许您这样做,并且您的公司也没有意见。开源社区很好,如果有人已经发明了轮子,那么尝试放下锤子和凿子。

$ sudo ansible-galaxy install geerlingguy.java

一旦我们知道我们想要的角色,我们就运行了install命令。具体来说,我们必须使用sudo,因为所涉及的角色被拉到您的服务器上,并放在共享的/etc/ansible/roles/目录中,我们的用户没有写入权限。

您可以在目录中看到这个角色(并复制或调整它)。

$ ls /etc/ansible/roles/geerlingguy.java/
defaults LICENSE meta molecule README.md tasks templates vars

实际的角色从tasks/main.yml开始,所以最好也看一下那里。

$ cat /etc/ansible/roles/geerlingguy.java/tasks/main.yml 
---
- name: Include OS-specific variables for Fedora or FreeBSD.
 include_vars: "{{ ansible_distribution }}.yml"
 when: ansible_distribution == 'FreeBSD' or ansible_distribution == 'Fedora'

- name: Include version-specific variables for CentOS/RHEL.
 include_vars: "RedHat-{{ ansible_distribution_version.split('.')[0] }}.yml"
<SNIP>

接下来,我们创建了一个小的playbook.yml文件,来指定我们应该在哪里安装哪个角色。

$ cat <<HERE > playbook.yml
---
- hosts: all
 roles:
 - geerlingguy.java
HERE

这里值得注意的是,我们将hosts列为all而不是特定的主机名或 IP,角色以 YAML 列表的形式存在。

通过添加不同的变量,不同的规则以及各种其他选项,您可以将 playbook 变成一个相当复杂的东西。

$ ansible-playbook -k -b -i 192.168.33.11, playbook.yml

最后,我们使用ansible-playbookplaybook.yml的内容实际应用到centos2。我们使用了常见的选项-k-b-i来构建我们的命令。

现在,它转到centos2,并执行geerlingguy.java角色的每一行。

还有更多...

也许为了一个角色而组合一个 playbook 似乎有点多余,但 playbook 的美妙之处在于它们不局限于一个角色。您可以在playbook.yml文件中包含尽可能多的角色,只要它们不相互冲突,您应该会有一个非常顺利的 Ansible 体验。

而不是:

$ cat <<HERE > playbook.yml
---
- hosts: all
 roles:
 - geerlingguy.java
 - geerlingguy.docker
 - geerlingguy.apache
HERE

我们还以非常特定的方式使用了galaxy,使用ansible-galaxy首先从互联网安装角色。您还可以在您的代码中包含一个requirements.yml文件,ansible-galaxy能够读取它,以便在尝试将它们应用到远程服务器之前,它可能会下载找到的任何列出的角色:

$ cat <<HERE > playbook-requirements.yml
---
- name: geerlingguy.java
- name: geerlingguy.docker
- name: geerlingguy.apache
HERE

然后,在运行playbook之前,您首先会运行galaxy

$ sudo ansible-galaxy install -r playbook-requirements.yml 
 [WARNING]: - geerlingguy.java (1.9.4) is already installed - use --force to change version to unspecified

- downloading role 'docker', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-docker/archive/2.5.2.tar.gz
- extracting geerlingguy.docker to /etc/ansible/roles/geerlingguy.docker
- geerlingguy.docker (2.5.2) was installed successfully
- downloading role 'apache', owned by geerlingguy
- downloading role from https://github.com/geerlingguy/ansible-role-apache/archive/3.0.3.tar.gz
- extracting geerlingguy.apache to /etc/ansible/roles/geerlingguy.apache
- geerlingguy.apache (3.0.3) was installed successfully

这意味着当您运行ansible-playbook时,您的角色已经准备好了:

$ ansible-playbook -k -b -i 192.168.33.11, playbook.yml
SSH password: 

PLAY [all] *********************************************************************************************************************************************************************

TASK [Gathering Facts] *********************************************************************************************************************************************************
ok: [192.168.33.11]

<SNIP>

RUNNING HANDLER [geerlingguy.apache : restart apache] **************************************************************************************************************************
changed: [192.168.33.11]

PLAY RECAP *********************************************************************************************************************************************************************
192.168.33.11 : ok=33 changed=14 unreachable=0 failed=0 

另请参阅

正如我在如何做...部分中提到的,我已经知道geerlingguy.java,因为作者(geerlingguy)在他写的 Ansible 中非常活跃。无论您最终为哪个机构工作,您都很有可能再次看到这个名字。

将我们的 Ansible 配置存储在 Git 中

让我们结合到目前为止学到的知识,并将我们在上一节中编写的 Ansible 配置存储在我们的 Git 服务器上。

准备工作

在本节中,我们将主要使用centos1,但我们将上传我们的配置到centos2

SSH 到你的两台虚拟机:

$ vagrant ssh centos1
$ vagrant ssh centos2

在 centos2 上,从你的主目录创建另一个空的Git 存储库

$ git init --bare ansible-example.git
Initialized empty Git repository in /home/vagrant/ansible-example.git/

操作步骤...

centos1上,切换到你的ansible-example目录:

$ cd ansible-example/

接下来,初始化目录为Git 存储库

$ git init

添加我们已经在目录中的文件,并将它们提交到存储库:

$ git add *
$ git commit -m "Adding my playbook files."

完成后,你的本地存储库就可以了,但我们仍然希望将它们推送到远程目的地。

使用别名origin添加你的远程:

$ git remote add origin vagrant@192.168.33.11:ansible-example.git

最后,我们可以将我们的更改推送远程存储库:

$ git push --set-upstream origin master
vagrant@192.168.33.11's password: 
Counting objects: 4, done.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (4/4), 388 bytes | 0 bytes/s, done.
Total 4 (delta 0), reused 0 (delta 0)
To vagrant@192.168.33.11:ansible-example.git
 * [new branch] master -> master
Branch master set up to track remote branch master from origin.

工作原理...

我们在这里所做的就是将我们在最近几节中所做的结合起来:

  • 我们在 centos2 机器上创建了一个 Git 存储库

  • 我们拿出了我们编写的 Ansible 配置,并在其周围初始化了一个存储库

  • 我们将我们的远程目的地设置为 centos2

  • 我们将我们的配置推送到远程机器

现在,即使 centos1 消失了,你也会在 centos2 上有一份你的配置副本,其他人可以克隆这些信息,进行更改,然后推送回来。

传统上在系统管理领域,你会看到一个叫做“构建服务器”的东西。这是因为你可以将它作为分发配置和管理基础架构的焦点。很容易看出,如果一个团队中的五个人同时在一个项目上工作,每个人都在不同的分支上进行自己的更改,你可能会变得不同步。

探索 IaC 的选项

在本节中,我们将从 Hashicorp 的网站下载一些二进制文件,并使用我们的centos1虚拟机。

完全自动化你的基础架构超出了本书的范围,但我们可以谈谈目前市场上一些最流行的工具。

Terraform 和 Packer 都是由 Hashicorp 制作的,他们以为系统管理员们简化生活而闻名,或者根据你问的人而定,从来没有发布过 1.0.0 版本的产品。

Hashicorp 也是维护 Vagrant 的人,尽管他们曾经试图用一个叫做 Otto 的程序来替换它——我们不谈论 Otto。

Terraform 是一种以声明性代码编写基础架构的方式,有多种提供商可供选择,包括 AWS、Azure、Scaleway、Digital Ocean 等。

另一方面,Packer 是一种构建你选择的提供商的基础镜像的方式,将你认为全局需要的所有软件都嵌入到默认镜像中,然后你可以用 Chef、Ansible 或 Puppet 等程序进行扩展。

如果你曾经通过提供商的在线门户为 VM、对象存储或网络进行配置,你可能已经意识到为什么 Terraform 和 Packer 听起来很棒。

这两个工具都不是第一个做它们打算做的事情的工具,但它们做得很好,并且在撰写本文时,它们在集体意识中占据了很大的份额。

准备工作

SSH 到centos1并下载 Terraform 和 Packer:

$ vagrant ssh centos1 $ curl -O https://releases.hashicorp.com/terraform/0.11.10/terraform_0.11.10_linux_amd64.zip
$ curl -O https://releases.hashicorp.com/packer/1.3.2/packer_1.3.2_linux_amd64.zip

现在,我们需要解压这些二进制文件,首先安装适当的工具。然后,解压我们的两个应用程序并将它们移动到/usr/local/bin/

$ sudo yum install unzip -y
$ unzip packer_1.3.2_linux_amd64.zip 
$ unzip terraform_0.11.10_linux_amd64.zip
$ sudo mv terraform /usr/local/bin/
$ sudo mv packer /usr/local/bin/

Terraform 和 Packer 的发展速度如此之快,以至于实际上很少有存储库会有它们。这些程序还会抱怨如果你使用的版本过时,每次使用它们时都会显示一个大横幅。我遇到的唯一一个似乎一直打包 Terraform 的存储库是 FreeBSD 默认存储库(即使那个也已经过时)。

我们还将在这里使用docker,所以如果你的虚拟机上还没有安装它,运行以下命令(这在本书的前面部分有更详细的介绍):

$ sudo yum install yum-utils -y
$ sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
$ sudo yum install docker-ce -y
$ sudo systemctl enable --now docker

操作步骤...

我们将依次介绍我们的两个程序。

Terraform

从头开始,我们将使用 Terraform 在本地创建一个小的 Docker 部署。这是最容易展示的,因为我们在我们的 VM 上有所有我们需要的工具,这并不意味着我必须告诉您去开始某个云提供商的免费试用。

首先创建一个供我们工作的目录:

$ mkdir example-terraform
$ cd example-terraform

接下来,将以下配置放入main.tf文件中:

$ cat <<HERE > main.tf
provider "docker" {
 host = "unix:///var/run/docker.sock"
}

resource "docker_image" "example-image" {
 name = "nginx"
}

resource "docker_container" "example-container" {
 name = "nginx-example"
 image = "\${docker_image.example-image.latest}"
}
HERE

然后,初始化 Terraform:

$ sudo /usr/local/bin/terraform init

Initializing provider plugins...

The following providers do not have any version constraints in configuration,
so the latest version was installed.

To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.

* provider.docker: version = "~> 1.1"

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

我们在这里使用sudo,因为我们的用户不在可以控制 Docker 的组中,因此当我们调用时,Terraform 也无法与 Docker 通信。

初始化后,现在应该能够应用我们的配置。

首先看一下docker ps

$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

然后,运行terraform apply

$ sudo /usr/local/bin/terraform apply

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
 + create

Terraform will perform the following actions:

 + docker_container.example-container
 id: <computed>
 attach: "false"
 bridge: <computed>
 container_logs: <computed>
 exit_code: <computed>
 gateway: <computed>
 image: "${docker_image.example-image.latest}"
 ip_address: <computed>
 ip_prefix_length: <computed>
 log_driver: "json-file"
 logs: "false"
 must_run: "true"
 name: "nginx-example"
 network_data.#: <computed>
 restart: "no"
 rm: "false"
 start: "true"

 + docker_image.example-image
 id: <computed>
 latest: <computed>
 name: "nginx"

Plan: 2 to add, 0 to change, 0 to destroy.

Do you want to perform these actions?
 Terraform will perform the actions described above.
 Only 'yes' will be accepted to approve.

 Enter a value: 

您应该看到即将执行的操作的详细信息,并提示您输入yes。这样做并按Enter

 Enter a value: yes

docker_image.example-image: Creating...
 latest: "" => "<computed>"
 name: "" => "nginx"
docker_image.example-image: Still creating... (10s elapsed)
docker_image.example-image: Creation complete after 18s (ID: sha256:568c4670fa800978e08e4a51132b995a54f8d5ae83ca133ef5546d092b864acfnginx)
docker_container.example-container: Creating...
 attach: "" => "false"
 bridge: "" => "<computed>"
 container_logs: "" => "<computed>"
 exit_code: "" => "<computed>"
 gateway: "" => "<computed>"
 image: "" => "sha256:568c4670fa800978e08e4a51132b995a54f8d5ae83ca133ef5546d092b864acf"
 ip_address: "" => "<computed>"
 ip_prefix_length: "" => "<computed>"
 log_driver: "" => "json-file"
 logs: "" => "false"
 must_run: "" => "true"
 name: "" => "nginx-example"
 network_data.#: "" => "<computed>"
 restart: "" => "no"
 rm: "" => "false"
 start: "" => "true"
docker_container.example-container: Creation complete after 0s (ID: 4f462e164239c65605c9106378ffb260b8bb7f5d27dc1fe0e008589a1387650e)

Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

太棒了!

再次运行docker ps,找出您刚刚创建的内容:

$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
4f462e164239 568c4670fa80 "nginx -g 'daemon of..." 28 seconds ago Up 26 seconds 80/tcp nginx-example

现在,让我们再次使用 Terraformdestroy我们的容器:

$ sudo /usr/local/bin/terraform destroy
docker_image.example-image: Refreshing state... (ID: sha256:568c4670fa800978e08e4a51132b995a54f8d5ae83ca133ef5546d092b864acfnginx)
docker_container.example-container: Refreshing state... (ID: 4f462e164239c65605c9106378ffb260b8bb7f5d27dc1fe0e008589a1387650e)

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
 - destroy

Terraform will perform the following actions:

 - docker_container.example-container

 - docker_image.example-image

Plan: 0 to add, 0 to change, 2 to destroy.

Do you really want to destroy all resources?
 Terraform will destroy all your managed infrastructure, as shown above.
 There is no undo. Only 'yes' will be accepted to confirm.

 Enter a value: 

再次提示我们在提供的字段中输入yes。这样做并按Enter

 Enter a value: yes

docker_container.example-container: Destroying... (ID: 4f462e164239c65605c9106378ffb260b8bb7f5d27dc1fe0e008589a1387650e)
docker_container.example-container: Destruction complete after 0s
docker_image.example-image: Destroying... (ID: sha256:568c4670fa800978e08e4a51132b995a54f8d5ae83ca133ef5546d092b864acfnginx)
docker_image.example-image: Destruction complete after 0s

Destroy complete! Resources: 2 destroyed.

哇!我们的容器消失了。

Packer

假设我们不只是想使用 Docker Hub 上的容器。假设我们想在使用 Terraform 部署之前稍微调整一下它。

这就是 Packer 的用武之地,它是一个非常多才多艺的工具。Packer 有适用于 AWS、Scaleway、LXC、VirtualBox、QEMU 和其他平台的构建器,但我们感兴趣的是 Docker。

返回到您的主目录并创建一个example-packer目录:

$ cd ~
$ mkdir example-packer
$ cd example-packer

接下来,将以下内容输出到文件中:

$ cat <<HERE > docker.json
{ 

 "builders":[
 {
 "type": "docker",
 "image": "nginx",
 "commit": true,
 "pull": true,
 "changes": [
 "LABEL custom=true",
 "EXPOSE 443"
 ]
 }],

 "provisioners":[
 {
 "type": "shell",
 "inline": ["echo 'Bring back Black Books!'","apt remove nginx -y"]
 }]

} 
HERE

完成后,您应该能够运行packerbuild和调整您的容器:

$ sudo /usr/local/bin/packer build docker.json
docker output will be in this color.

==> docker: Creating a temporary directory for sharing data...
==> docker: Pulling Docker image: nginx
 docker: Using default tag: latest
 docker: latest: Pulling from library/nginx
 docker: Digest: sha256:5d32f60db294b5deb55d078cd4feb410ad88e6fe77500c87d3970eca97f54dba
 docker: Status: Image is up to date for nginx:latest
==> docker: Starting docker container...
 docker: Run command: docker run -v /root/.packer.d/tmp/packer-docker860052889:/packer-files -d -i -t nginx /bin/bash
 docker: Container ID: 2047e58a479904968bff162d279ac42d1e162878fb6b8e2176bb3e4551669c17
==> docker: Using docker communicator to connect: 172.17.0.2
==> docker: Provisioning with shell script: /tmp/packer-shell763812364
 docker:
 docker: WARNING: apt does not have a stable CLI interface. Use with caution in scripts.
 docker: Bring back Black Books!
 docker:
 docker: Reading package lists...
 docker: Building dependency tree...
 docker: Reading state information...
 docker: The following packages were automatically installed and are no longer required:
 docker: fontconfig-config fonts-dejavu-core libbsd0 libedit2 libexpat1
 docker: libfontconfig1 libfreetype6 libgd3 libgeoip1 libicu57 libjbig0
 docker: libjpeg62-turbo libpng16-16 libssl1.1 libtiff5 libwebp6 libx11-6 libx11-data
 docker: libxau6 libxcb1 libxdmcp6 libxml2 libxpm4 libxslt1.1 ucf
 docker: Use 'apt autoremove' to remove them.
 docker: The following packages will be REMOVED:
 docker: nginx nginx-module-geoip nginx-module-image-filter nginx-module-njs
 docker: nginx-module-xslt
 docker: 0 upgraded, 0 newly installed, 5 to remove and 0 not upgraded.
 docker: After this operation, 5336 kB disk space will be freed.
 docker: (Reading database ... 7026 files and directories currently installed.)
 docker: Removing nginx-module-njs (1.15.7.0.2.6-1~stretch) ...
 docker: Removing nginx-module-xslt (1.15.7-1~stretch) ...
 docker: Removing nginx-module-geoip (1.15.7-1~stretch) ...
 docker: Removing nginx-module-image-filter (1.15.7-1~stretch) ...
 docker: Removing nginx (1.15.7-1~stretch) ...
 docker: invoke-rc.d: could not determine current runlevel
 docker: invoke-rc.d: policy-rc.d denied execution of stop.
==> docker: Committing the container
 docker: Image ID: sha256:53a34cbb093015f423de89ad874afebb3b9470f3750e858729f75ed8e3f4bce4
==> docker: Killing the container: 2047e58a479904968bff162d279ac42d1e162878fb6b8e2176bb3e4551669c17
Build 'docker' finished.

==> Builds finished. The artifacts of successful builds are:
--> docker: Imported Docker image: sha256:53a34cbb093015f423de89ad874afebb3b9470f3750e858729f75ed8e3f4bce4

太棒了!我们现在有了一个可以在其他地方使用的镜像:

$ sudo docker image ls
REPOSITORY TAG IMAGE ID CREATED SIZE
<none> <none> 53a34cbb0930 19 seconds ago 110MB
nginx latest 568c4670fa80 19 hours ago 109MB
alpine latest 196d12cf6ab1 2 months ago 4.41MB

工作原理...

让我们来详细了解一下,从 Terraform 开始。

在这一部分,我们通过编写配置到main.tf文件中定义了我们希望我们的基础设施看起来像什么:

provider "docker" {
  host = "unix:///var/run/docker.sock"
}

resource "docker_image" "example-image" {
  name = "nginx"
}

resource "docker_container" "example-container" {
  name = "nginx-example"
  image = "${docker_image.example-image.latest}"
}

具体来说,我们在这里做的是为 Terraform 提供其提供程序和连接所需信息(在本例中是 Unix 套接字):

provider "docker" {
  host = "unix:///var/run/docker.sock"
}

然后,我们通知 Terraform 我们想要用作容器基础的镜像:

resource "docker_image" "example-image" {
  name = "nginx"
}

最后,我们说容器应该被称为nginx-example,并且应该使用我们之前定义的镜像(注意引用变量到前面的块):

resource "docker_container" "example-container" {
  name = "nginx-example"
  image = "${docker_image.example-image.latest}"
}

保存后,我们运行了一个命令来初始化 Terraform 使用的目录:

$ sudo /usr/local/bin/terraform init

这将下载提供程序(docker)并为我们设置目录以供使用。然后我们应用了我们的配置:

$ sudo /usr/local/bin/terraform apply

Terraform 在调用它的目录中寻找任何以.tf结尾的文件。

之后,我们销毁了我们的设置:

$ sudo /usr/local/bin/terraform destroy

您的目录最终会看起来像这样:

$ ls -lha
total 16K
drwxrwxr-x. 3 vagrant vagrant 96 Nov 28 17:05 .
drwx------. 9 vagrant vagrant 4.0K Nov 28 17:07 ..
-rw-rw-r--. 1 vagrant vagrant 251 Nov 28 17:02 main.tf
drwxr-xr-x. 3 root root 21 Nov 28 17:02 .terraform
-rw-r--r--. 1 root root 318 Nov 28 17:05 terraform.tfstate
-rw-r--r--. 1 root root 2.9K Nov 28 17:05 terraform.tfstate.backup

注意state文件。这很重要,因为它包含了您已经配置的基础设施的已知状态。在里面看到一个类似 JSON 的语法,尽管这可能会改变。Terraform 状态文件不应该手动更改。

我有很多关于手动恢复基础设施状态的恐怖故事,我相信其他人也有,相信我,当我告诉你丢失状态文件既不好玩,也不会是一个快速的恢复过程。不用说,保留这个文件的备份是个好主意。

值得注意的是,您也可以将 Terraform 状态文件存储在远程位置。这可以配置为在使用时“锁定”(以避免两个用户尝试同时访问状态文件时发生冲突的更改)。这也意味着状态文件不存储在您的本地计算机或 Jenkins 从属机上,使得分布式构建更容易。

前面的terraform目录包含了我们在初始化存储库时下载的实际插件。

接下来是 Packer!

对于 Packer,情况有些不同。首先,我们设置了这个配置文件:

{ 

  "builders":[
  {
    "type": "docker",
    "image": "nginx",
    "commit": true,
    "pull": true,
    "changes": [
      "LABEL custom=true",
      "EXPOSE 443"
    ]
  }],

  "provisioners":[
  {
    "type": "shell",
    "inline": ["echo 'Bring back Black Books!'","apt remove nginx -y"]
  }]

}

这只是一个非常基本的例子,但它达到了它的目的。

您可能注意到的第一件事是它是 JSON 格式化的,而不是使用 HCL(Terraform 用于其配置)。其次,您可能会注意到,虽然我们配置了构建器(docker),但我们还配置了一个配置程序(shell)。

  "builders":[
  {
    "type": "docker",
    "image": "nginx",
    "commit": true,
    "pull": true,
    "changes": [
      "LABEL custom=true",
      "EXPOSE 443"
    ]
  }],

从构建器开始,您可以看到我们正在使用docker作为类型,以便 Packer 知道这是什么构建器,并且我们还使用了之前使用的nginx图像作为基础。

我们应用了一些元数据更改,以标签的形式暴露了一个不同于图像(80)中默认端口的端口。我们可以在生成的 Docker 图像中看到这些:

$ sudo docker inspect 53a34cbb0930 | grep "custom\|443"
 "443/tcp": {},
 "custom": "true",

接下来,我们继续进行 Packer 工作的核心部分,即配置步骤:

  "provisioners":[
  {
    "type": "shell",
    "inline": ["echo 'Bring back Black Books!'","apt remove nginx -y"]
  }]

这是您可以对容器进行更改的部分,比如在这里,我们使用shell向屏幕echo了一条意味深长的声明,然后激烈地删除了容器的唯一目的以示抗议。

Packer 还有其他配置程序,例如 Ansible,Chef 和 Puppet,但shell是最容易理解和实现的。

最后,我们构建了我们的图像:

$ sudo /usr/local/bin/packer build docker.json

Packer 从 Docker Hub 中提取了指定的图像,然后进行了我们指定的元数据和内容的更改,最后打包图像并将其存储在本地。

我们也可以引入一个后处理步骤,以各种方式标记并上传我们的新图像到某个地方进行安全保存。

还有更多...

这是一个小而有趣的片段。Terraform 有一个内置命令,用于确保所有语法都被良好格式化(这意味着每个人都可以遵循相同的标准,而不会陷入关于间距的愚蠢争论):

$ terraform fmt
main.tf

老实说,我认为这是 Terraform 最伟大的地方之一,因为这意味着没有人可以有自己的意见,如果有一个世界我想生活在其中,那就是这个世界。

另请参阅

Hashicorp 制作了很多东西,虽然重要的是要记住在基础设施即代码方面有几个选择,但不可否认的是,如果您对他们的套件了解得很好,那么您将大大增加获得工作的机会。

Hashicorp 的其他工具包括以下内容:

  • Vagrant,正如我们所知道和喜爱的那样

  • Vault,用于秘密存储

  • Consul,用于服务发现

总结- Git,配置管理和基础设施即代码

这不是一本关于 Ansible 的书,虽然我很想写一本,但已经有相当多的书出版了(尽管实话实说,Ansible 发展非常迅速,最好是边学边用,我通常不提倡这样做)。也就是说,我喜欢 Ansible,以及本章中列出的其他工具(Git,Terraform,Packer),在过去几年中,它们使我的生活变得轻松了许多。

有人说管理员是懒惰的,因为我们真正想做的就是通过自动化来简化工作,消除繁琐的部分。我们曾经开玩笑说,总有一天我们会自动化自己的工作,但我们部落中一些不那么懒惰的人不喜欢这个想法,似乎决定几乎每个月都要开发一个新东西,这样我们就可以拿起这个东西,并决定我们迫切需要在我们的基础设施中使用它。

Git 是神奇的,使我们在源代码控制和分发方面的需求无缝(除非您忘记了如何从您陷入的完全混乱中恢复,并决定更容易地删除目录并重新克隆)。

Ansible 是一个救世主,这意味着我们不再需要在清晨的早些时候打电话给早已退休的老人,只是为了弄清楚他们是如何设法让那台旧的雪花服务器的一半在 RAM 中运行的,而另一半似乎是从网络驱动器中拉取的。

不要假设只因为某个地方使用 Ansible,并声称是完全自动化的,他们就没有一个服务器被搁置在那里,因为没有人想去碰它,因为它做一些愚蠢的事情,比如处理电话服务器的 IVR。在这些情况下,你可以选择两种做法。要么选择 A:提出自动化旧的、陈旧的特殊情况,使其与其他基础设施保持一致;要么选择 B:像其他人一样把头埋在沙子里,希望在你值班时它不会出问题(尽管它会……它会……)。

Terraform 在代码形式上是一个头疼的东西,尽管用户偶尔对它有所抱怨,但在一个以云为导向的世界中,拥有一个可以自动在 Azure 订阅中创建数百个箱子的工具是必不可少的,如果这意味着你不必学习 PowerShell,那就更好了。不过,值得一提的是,我有一个朋友坚持认为 Terraform 还没有准备好,因此,他将 Terraform 文件的生成包装在一个 Ansible 角色中……别问。

Packer 消除了繁琐,并确保你的 Ansible 剧本不会花费太长时间,因为你已经把所有合理的默认值都合并到了一个基础镜像中(暗示,暗示!)。

最后,让我们谈谈云的时代。

我们提到的所有工具在一个短暂的世界中都很棒,在这个世界中,服务器可以在几分钟内启动,完成它们的使命,然后被一个全能的定时任务无情地从存在中移除。然而,它们也有缺点,使用诸如 Terraform 之类的工具来管理一个庄园时要小心是很重要的。

Terraform 在让你删除基础设施之前会提示你“你真的确定吗”,这是有原因的。我总是建议在你要做某事之前,要百分之百地确定自己。你不想成为摧毁生产的责任人(确保你有良好的备份)。

在过去,当我不小心关闭了一个服务器,因为我把它和一个名字相似的服务器搞混了,而且在任何人注意到之前它已经关闭了 12 个小时,后果是最小的。我可以简单地重新打开这个箱子,同时向客户道歉。

现在,如果你不小心对生产环境运行了terraform destroy,并且它也带走了数据,你做的不仅仅是关闭了箱子。它们消失了,完蛋了,被阿瓦达·凯达瓦了,所以要小心。我们有这些工具是为了让我们的生活更轻松,但有时我想知道我们是否也给自己带来了更多意外的破坏的能力。

关于我关闭一个箱子的故事可能是真的,也可能不是……它是真的。

第十一章:Web 服务器、数据库和邮件服务器

在本章中,我们将研究以下主题:

  • 安装和理解 Web 服务器

  • 基本的 Apache 配置

  • 基本的 Nginx 配置

  • SSL、TLS 和 LetsEncrypt

  • 基本的 MySQL 或 MariaDB 安装

  • 基本的 PostgreSQL 安装

  • 本地 MTA 使用和配置(Postfix)

  • 本地 MTA 使用和配置(Exim)

  • NoSQL 文档(MongoDB 示例)

  • NoSQL KV(Redis 示例)

  • 消息代理和队列(RabbitMQ 示例)

介绍

作为系统管理员或开发人员,你很有可能在职业生涯中的某个时候遇到网站。

网站是存在于互联网上的东西,人们可以在那里找到东西(技术描述)。大部分网络都在 Linux 上运行,Windows 等系统有分割和较暗的角落。

许多人开始他们的 Linux 职业生涯时在 ISP 或某种类型的网络主机工作,这意味着许多新手被立即投入到管理非常公开的网站的深水区。这并不是坏事,因为在各种问题环境中你往往会快速学习,当你周围有许多其他人每天都经历同样的挫折时,这会是一个相当有益的学习经验。

这并不意味着每个人都会成为网络主机或 ISP。我在加入一家小型电信公司时开始正式接触 Linux,这意味着我对 Web 技术的接触很少,而对私有分支交换PBX)电话系统的接触要多得多。

毋庸置疑,如果你选择管理网站或为他人维护网站,你会有很多同行。你在各个层面遇到的大多数工程师都曾在一线工作,接听电话,全天候与全球的开发人员打交道。

我不会说和网站一起工作很有趣,但在排除故障时不断刷新一个破损的网页,最终它会像从未离开过一样突然恢复正常,这是一种非常宣泄的经历,即使是在凌晨三点。

网络有许多不同的组件,尽管静态 HTML 网站的鼎盛时期已经过去(尽管最近由于托管在亚马逊 S3 等地方的自动生成的网站的出现而出现了一些复苏),但有许多有趣的技术可以让你深入研究。

从最简单的开始,我们将研究实际的 Web 服务器(提供 Web 内容)、数据库(保存 Web 内容)和 TLS(加密 Web 内容传输)。

我们还将研究一些其他技术,你可能在某个时候会遇到(尤其是如果你在托管提供商工作)。这些是:

  • (电子)邮件传输代理(如 Postfix 和 Exim)

  • NoSQL 数据库(如 MongoDB)

  • 快速键值(KV)存储(如 Redis)

  • 消息代理(如 RabbitMQ)

不要让这些吓到你——它们只是纸上的文字。

技术要求

在本节中,我们将同时使用 CentOS 和 Debian。这是因为虽然 Linux 世界的软件是相当通用的,但一些发行版会为诸如他们的网络和邮件服务器等事物选择特定的默认设置。

在本章中,可以使用以下Vagrantfile

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

  config.vm.define "centos1" do |centos1|
    centos1.vm.box = "centos/7"
    centos1.vm.network "private_network", ip: "192.168.33.10"
    centos1.vm.hostname = "centos1"
    centos1.vm.box_version = "1804.02"
  end

  config.vm.define "debian1" do |debian1|
    debian1.vm.box = "debian/stretch64"
    debian1.vm.network "private_network", ip: "192.168.33.11" 
    debian1.vm.hostname = "debian1"
    debian1.vm.box_version = "9.5.0"
    debian1.vm.provider "virtualbox" do |debian1p|
      debian1p.cpus = 1
    end
  end

end

安装和理解 Web 服务器

当你访问网站时,Web 服务器是你直接交互的组件。它传统上监听端口80(用于超文本传输协议HTTP))或443(用于超文本传输安全协议HTTPS))。

当你在浏览器中输入 URL 时,这些端口通常是隐藏的,除非明确定义;例如,在 Chrome 或 Firefox 中访问duckduckgo.com将加载网站,但它不会告诉你它正在连接到端口443。类似地,如果你去duckduckgo.com:443,同样的页面应该加载。

此外,如果你尝试使用 HTTPS 访问端口80https://duckduckgo.com:80/),通常会收到一个错误,表示该网站无法提供安全连接:

这是因为你试图使用安全协议(HTTPS)访问不安全的端口(80)。

Web 服务器实际上是为网络提供服务的,但它们通常只是其他技术的前端。例如,Wordpress 安装中的博客文章可能在幕后存储在数据库中,而它们通过 Web 服务器前端呈现给最终用户。

Web 服务器的工作是确定如何向请求的客户端显示页面的内容。

不同的客户端发送不同的“用户代理”,告诉 Web 服务器它将能够向你显示什么类型的页面。以桌面和手机互联网浏览器为例:如果网站编码以理解你的设备的不同用户代理,那么完全相同的网站在不同设备上可能看起来完全不同。

准备就绪

我们将使用我们的两个虚拟机,并在每台上设置一个 Web 服务器:

$ vagrant ssh centos1 -- -L 127.0.0.1:8080:127.0.0.1:80
$ vagrant ssh debian1 -- -L 127.0.0.1:8181:127.0.0.1:80

如何做...

从我们的 CentOS 系统开始,我们将安装官方存储库中提供的默认 Web 服务器。

在 CentOS 上安装 httpd(Apache)

正如标题所示,CentOS 将 Apache HTTP 服务器重新标记为httpd,我怀疑是为了便于理解而将产品通用化(尽管我遇到了不少不喜欢这种重新标记的系统管理员,包括我自己)。

像这样安装httpd

$ sudo yum install httpd -y

现在让我们开始吧,因为这是 CentOS:

$ sudo systemctl enable --now httpd

由于我们在登录 Vagrant 虚拟机时转发了端口,我们现在应该能够在本地浏览器中导航到我们转发的地址和端口(http://127.0.0.1:8080):

这是 CentOS 上 Apache 的默认splash页面,开箱即用配置。

在 Debian 上安装 Nginx

现在,在我们的 Debian 系统上,让我们安装 Nginx:

$ sudo apt install nginx -y

一旦安装完成,由于是 Debian,服务可能会自动启动:

$ systemctl status nginx --no-pager
● nginx.service - A high performance web server and a reverse proxy server
 Loaded: loaded (/lib/systemd/system/nginx.service; enabled; vendor preset: enabled)
 Active: active (running) since Sun 2018-12-02 11:54:11 GMT; 21s ago
 Docs: man:nginx(8)
 Process: 1936 ExecStart=/usr/sbin/nginx -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
 Process: 1933 ExecStartPre=/usr/sbin/nginx -t -q -g daemon on; master_process on; (code=exited, status=0/SUCCESS)
 Main PID: 1938 (nginx)
 Tasks: 2 (limit: 4915)
 CGroup: /system.slice/nginx.service
 ├─1938 nginx: master process /usr/sbin/nginx -g daemon on; master_process on;
 └─1939 nginx: worker process

由于我们在登录 Vagrant 虚拟机时转发了不同的端口(http://127.0.0.1:8181)到我们的 Debian 机器,我们也应该能够在浏览器中访问这个地址:

嗯,开箱即用的功能少了很多...

工作原理...

我们在这里做的是安装两个不同的 Web 服务器,尽管它们完成的是同样的工作。

可以说,没有比网络更适合和同时更糟糕的标准合规性的地方了,这意味着无论你选择使用哪个 Web 服务器(Apache,Nginx),你仍然应该能够以一致的方式提供内容。

标准合规性是我们用来指代计算机系统约定的标准的术语。请求评论RFC)可能规定任何内容,从哪些 IP 范围用于私人使用,到两个系统如何在 HTTPS 上进行安全通信。互联网工程任务组IETF)是管理 RFC 的机构之一。

我们安装的第一个服务器是 Apache,多年来一直是“首选”Web 服务器,仍然被许多传统管理员认为是“经过严格考验”的。显然,它是 CentOS 安装的默认选项,这意味着安装基数仍然非常庞大。

在撰写本文时,Apache 仍然被视为更大的主导者,但 Nginx 近年来逐渐崭露头角,并且有望在未来取代 Apache(稍后详细介绍)。

然后我们在 Debian 框上安装了 Nginx(尽管 Apache 也可用)。Debian 之所以声名显赫,是因为它还有一大堆不同的 Web 服务器可供尝试(我只选择了 Apache 和 Nginx 作为最大的两个)。

无论您安装哪一个,这两个系统现在都能够为互联网提供静态 HTTP 内容(或者至少是您网络的一小部分,因为它不是公开可访问的)。

如果我们查看 Debian 框上的ss输出,我们会看到以下内容:

$ ss -tuna
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port 
udp UNCONN 0 0 *:68 *:* 
tcp LISTEN 0 128 *:80 *:* 
tcp LISTEN 0 128 *:22 *:* 
tcp ESTAB 0 0 10.0.2.15:22 10.0.2.2:40136 
tcp ESTAB 0 0 127.0.0.1:56490 127.0.0.1:80 
tcp ESTAB 0 0 127.0.0.1:80 127.0.0.1:56490 
tcp LISTEN 0 128 :::80 :::* 
tcp LISTEN 0 128 :::22 :::* 

我们可以看到端口80,监听所有可用的 IP,并且我们可以看到已建立的通信,实际上来自我们转发的 Web 连接和 Firefox。在 CentOS 框上也是一样的情况。

所有这些都很棒,这意味着当我们的客户端(例如 Firefox)从 Web 服务器(Apache)请求内容时,该服务器能够以客户端能够理解的方式和样式提供所请求的内容。

Firefox 随后可以以用户能够理解的方式向最终用户显示内容,例如猫的图片,或者他们在互联网上搜索的其他信息(尽管应该始终是猫)。

还有更多...

我提到了其他 Web 服务器,事实上有相当多。

在 OpenBSD 领域,您可能会发现自己安装了httpd,这不是在 CentOS 上的重新命名的 Apache(如在 CentOS 上的情况),而是完全不同的软件,只是碰巧有相同的名称,并且执行类似的功能...

或者,您可能会喜欢 Tomcat 的想法,它不太像传统的 Web 服务器,而是作为 Java servlets(通常是某种 Web 应用程序)的前端。

还有lighttpd,它(顾名思义)应该是一个轻量级的 Web 服务器,没有 Nginx 或 Apache 提供的许多功能。

在 Windows 世界中(一个我不喜欢访问的可怕地方),您会得到 IIS,这更像是在 Windows 服务器上提供的一套互联网服务。

基本的 Apache 配置

我们在 CentOS 机器上安装了httpd,这意味着我们在端口80上运行了一个 Web 服务器,并且我们能够从我们主机上的 Firefox 安装中访问它。

在本节中,我们将看看我们的服务器如何知道要显示什么,以及我们可以做些什么来设置自己的站点,以便当人们访问我们的 IP 时不会被默认的 Apache 页面所欢迎。

准备工作

对于本节,我们将使用上一节的Vagrantfile。如果您尚未在 CentOS VM 上安装 Apache,请在此时进行安装。

连接到您的 CentOS 框:

$ vagrant ssh centos1 -- -L 127.0.0.1:8080:127.0.0.1:80

如何做...

首先,我们应该快速查看默认配置加载自何处。在默认页面上,我们可以看到以下部分:

事实证明,我们实际上是管理员。考虑到这一点,让我们看看我们能做些什么。

首先,我们可以ls列出此消息中列出的目录,看看那里已经有什么:

$ ls /var/www/html/
$ 

没有什么...奇怪的。

让我们在这个目录中放一个基本的index.html页面,看看会发生什么:

$ cat <<HERE | sudo tee -a /var/www/html/index.html
WE APOLOGISE FOR THE INCONVENIENCE.
HERE

现在让我们再次访问我们的网站:

它改变了!

好的,显然这个目录正在被用于某些事情,但它没有解释显示配置位于何处。

让我们查看建议的welcome文件:

$ cat /etc/httpd/conf.d/welcome.conf 
# 
# This configuration file enables the default "Welcome" page if there
# is no default index page present for the root URL. To disable the
# Welcome page, comment out all the lines below. 
#
# NOTE: if this file is removed, it will be restored on upgrades.
#
<LocationMatch "^/+$">
 Options -Indexes
 ErrorDocument 403 /.noindex.html
</LocationMatch>

<Directory /usr/share/httpd/noindex>
 AllowOverride None
 Require all granted
</Directory>

Alias /.noindex.html /usr/share/httpd/noindex/index.html
Alias /noindex/css/bootstrap.min.css /usr/share/httpd/noindex/css/bootstrap.min.css
Alias /noindex/css/open-sans.css /usr/share/httpd/noindex/css/open-sans.css
Alias /images/apache_pb.gif /usr/share/httpd/noindex/images/apache_pb.gif
Alias /images/poweredby.png /usr/share/httpd/noindex/images/poweredby.png

重要的要点如下:

“此配置文件在根 URL 没有默认索引页面的情况下启用默认的“欢迎”页面。”

好的,现在让我们不要担心这个,而是专注于我们自己的文件。

首先,因为您可以在一个 Web 服务器上拥有大量不同的网站(虚拟主机),让我们在我们的文件夹结构中创建一些分隔,以便将不同的网站文件分开:

$ sudo mkdir /var/www/god-to-marvin
$ sudo mv /var/www/html/index.html /var/www/god-to-marvin/

此时,我们的 Web 服务器将重新显示默认的 Apache 页面,因为我们已经移动了唯一的index.html文件。

接下来,添加所需的配置,使该目录可以被读取:

$ cat <<HERE | sudo tee -a /etc/httpd/conf.d/god-to-marvin.conf
<VirtualHost 127.0.0.1:80>
 ServerAdmin thebestsysadmin@example.com
 DocumentRoot "/var/www/god-to-marvin/"
 ServerName 127.0.0.1
 ServerAlias 127.0.0.1
</VirtualHost>
HERE

然后,我们需要重新加载配置:

$ sudo systemctl reload httpd

接下来,回到我们的 Firefox 窗口,尝试访问http://127.0.0.1:8080/。你应该再次看到你的消息:

有了这个配置,虽然表面上没有改变,但这意味着你可以在将来添加更多的网站和来自受欢迎的科幻系列的更多引用。

工作原理...

我们能够将文件放入/var/www/html/并在浏览器中查看它,是因为在主 Apache 配置文件中有DocumentRoot设置,可以在这里看到:

$ cat /etc/httpd/conf/httpd.conf | grep ^DocumentRoot
DocumentRoot "/var/www/html"

我们之所以将index.html作为文件名,除了因为它是惯例,还因为以下行:

$ cat /etc/httpd/conf/httpd.conf | grep "^ DirectoryIndex"
 DirectoryIndex index.html

这决定了在请求目录时加载哪个文件。

虽然/etc/httpd/conf/httpd.conf文件是默认的配置文件,但我们也可以在/etc/httpd/conf.d/目录下为网站添加额外的配置,就像我们在这个案例中所做的那样。

我们为自己的配置使用了一个非常特定的段落,如下所示:

<VirtualHost 127.0.0.1:80>
 ServerAdmin thebestsysadmin@example.com
 DocumentRoot "/var/www/god-to-marvin/"
 ServerName 127.0.0.1
 ServerAlias 127.0.0.1
</VirtualHost>

这个段落意味着,虽然我们可以继续托管与以前一样的内容,但我们也能够托管其他内容,使用不同的DocumentRoots

当我们第二次访问我们的站点时,我们不再被指向/var/www/html作为DocumentRoot,而是被指向/var/www/god-to-marvin,因为前面的配置规定了这样做。

我们还有一个ServerName和一个ServerAlias指令,尽管在这种情况下 Alias 没有任何作用。

ServerName是最终用户在浏览器中输入的域名或 IP 地址。别名可以是与该名称相关的其他名称。

例如,你可以有以下内容:

 ServerName example.com
 ServerAlias www.example.com fish.example.com europe.example.com

所有这些都将命中相同的DocumentRoot

还有更多...

虚拟主机只有在多个域名指向一个服务器时才能真正发挥作用。实际上,你可以有数百个不同的域名指向一个服务器,但因为 Apache 知道你用来连接的域名,它只会提供你请求的确切站点。

在多租户情况下,多个客户端共存于一台服务器上并不罕见,只操作和更新他们自己的文件,对与他们与其他公司和用户共享一台服务器毫不知情。

如果一个托管公司每月花费几英镑来设置一个 Web 服务器,并且他们可以向他们的客户收费来托管网站,那么公司可以在很短的时间内收回成本。

另请参阅

在测试环境中,你往往会看到一台服务器上同时运行多个网站,因为它们通常是轻量级的,可以并行运行多个。然而,这给域名解析测试带来了问题,因为使用公共域名服务来测试和临时网站可能会变得昂贵和耗时。

解决这个问题的一个方法是使用/etc/hosts文件(在 Linux 和 Unix 系统上)。

默认的/etc/hosts文件可能如下所示:

$ cat /etc/hosts
127.0.0.1 centos1 centos1
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6

你可以按照以下方式向该文件添加一行:

192.168.33.11 mysupersite.com

现在当你在浏览器中输入mysupersite.com时,名称将被解析为你指定的 IP 地址,而不是向外部 DNS 服务器进行名称解析。

通过这种方式,你可以在 Apache Web 服务器上拥有多个“虚拟主机”,因为你的浏览器请求的是具名站点(即使它们都在同一个 IP 地址上),你将根据你连接的名称获得不同的内容。

人们在他们的/etc/hosts文件中胡乱更改的唯一问题是,当他们忘记改回去并且困惑为什么无法连接到“真正的”站点时。

基本的 Nginx 配置

现在前往我们的 Debian 服务器,我们将查看默认的 Nginx 页面,当我们访问http://127.0.0.1:8181/时可以看到,并且我们将用我们自己的消息替换这个文本。

正如我们之前所述,Nginx 越来越受欢迎。它已经成为首选的 Web 服务器,因为它在需要时易于使用和灵活性——并不是市场营销的宣传;它们都是开源和免费的。

准备工作

对于这一部分,我们将使用第一部分的Vagrantfile。如果您还没有在 Debian VM 上安装 Nginx,请在此时进行安装。

连接到您的 Debian 盒子:

$ vagrant ssh debian1 -- -L 127.0.0.1:8181:127.0.0.1:80

如何做...

我们的默认 Nginx 页面没有任何指针指向配置更改的位置,只是指向官方文档(这是值得一读的)和商业支持提供。

这个默认页面实际上与我们刚刚在 CentOS 上检查的位置非常相似:

$ cat /var/www/html/index.nginx-debian.html 
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
 body {
 width: 35em;
 margin: 0 auto;
 font-family: Tahoma, Verdana, Arial, sans-serif;
 }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

请注意,此文件称为index.nginx-debian.html,并且它是/var/www/html中的唯一文件。

与 Apache 一样,Nginx 有虚拟主机的概念,我们将在/etc/nginx/conf.d/中进行配置。

让我们首先创建一些内容:

$ sudo mkdir /var/www/fenchurch
$ cat <<HERE | sudo tee -a /var/www/fenchurch/index.html
How come I'm in one book, then I just disappear?
HERE

现在我们可以添加到我们选择的虚拟主机目录:

$ cat <<HERE | sudo tee /etc/nginx/conf.d/fenchurch.conf
server {
listen 80;
listen [::]:80;

root /var/www/fenchurch;
index index.html;

server_name 127.0.0.1;

location / {
try_files \$uri \$uri/ =404;
}
}
HERE

然后,我们需要加载 Nginx:

$ sudo systemctl reload nginx

现在,当指向我们设置的转发端口时,我们应该能在浏览器中看到我们的问题:

酷!

它是如何工作的...

我们的默认 Nginx 配置文件位于/etc/nginx/nginx.conf,它设置了诸如进程 ID 位置以及 Nginx 将作为哪个用户运行(这里是www-data)等内容,在这个 Debian 安装中:

$ head /etc/nginx/nginx.conf
user www-data;
worker_processes auto;
pid /run/nginx.pid;
include /etc/nginx/modules-enabled/*.conf;

events {
 worker_connections 768;
 # multi_accept on;
}

在这个文件中,还存在以下配置块:

 ##
 # Virtual Host Configs
 ##

 include /etc/nginx/conf.d/*.conf;
 include /etc/nginx/sites-enabled/*;

请注意,顶级目录是我们选择用于我们的配置的目录。

当我们将fenchurch.conf配置放在/etc/nginx/conf.d/目录中时,我们指示 Nginx 加载此配置,以及它在启动时加载的所有其他内容。

让我们看看我们的配置:

server {
listen 80;
listen [::]:80;

root /var/www/fenchurch;
index index.html;

server_name 127.0.0.1;

location / {
try_files $uri $uri/ =404;
}
}

listen指令非常简单,但如果您在一个盒子上有多个 IP 地址,它们可能会扩展到包括特定条目。

接下来,我们的root条目是网站文件的根位置。在这里,它设置为我们选择为我们的问题创建的位置。

index是 Nginx 进入目录时要加载的文件的名称。这里使用了标准的index.html

而且,就像 Apache 一样,server_name是最终用户希望接收内容的域名或 IP 地址。它可以是一系列名称,就像其他地方看到的那样:

server_name example.com herring.example.com dwarf.example.com;

最后,在location块中的try_files行意味着将搜索给定链接的文件,如果找不到,将返回404

您可以通过尝试在浏览器中访问不存在的文件来测试这一点,例如,http://127.0.0.1:8181/prefect

但是,我们可以将404更改为403并重新加载 Nginx 配置:

$ sudo sed -i 's/404/403/g' /etc/nginx/conf.d/fenchurch.conf 
$ sudo systemctl reload nginx

如果我们这样做,我们会得到403 Forbidden

404可能是 Web 服务器返回的最常见代码,但您应该知道还有更多,并且它们实际上意味着不同的事情(前提是它们已经配置正确返回)。200是 OK,401表示未经授权,等等。

还有更多...

您可能想知道使用systemctl reload的原因,以及为什么我选择使用它而不是restart

当我们cat Nginx 的 systemd 单元文件时,答案应该变得更清晰:

$ systemctl cat nginx | grep Reload
ExecReload=/usr/sbin/nginx -g 'daemon on; master_process on;' -s reload

有一行特定的ExecReload命令,带有-s reload标志。

这会向 Nginx 发送reload信号(SIGHUP);也就是说,它对正在运行的进程的干扰较小。

另请参阅

在 Debian 和类似 Debian 的发行版中,sites-enabledsites-available目录的概念已经变得司空见惯。

从理论上讲,您在服务器上拥有的任何网站都可以放在sites-available目录中,一旦您对它们满意,就可以创建到sites-enabled目录的符号链接。

就个人而言,我发现这种设置有点令人困惑,而且在自动化的世界中有些过时,但如果这是您的菜,那么也许 Debian 的做法适合您。我不会评判。

SSL、TLS 和 LetsEncrypt

我们还没有讨论 HTTP 方程式中的“S”部分。具体来说,S 代表安全,与超人的 S 不同,后者显然不是 S,实际上是埃尔家族的纹章。

与超人不同,Web 服务器可能会对您撒谎。

当您访问一个网站时,您希望知道您正在访问的网站是否真正由您认为的公司拥有和运营。如果您去亚马逊、苹果或 PayPal,您希望在交出您的数字现金之前知道它们是真实的。

但是,您还希望网站能够以安全的方式接收您的信用卡信息,以便这些数字和秘密不以纯文本格式在互联网上传播供任何人阅读。

留意挂锁:

大多数浏览器(如果不是全部)在您访问安全网站时应该显示一个小挂锁,只要网站不是欺诈性的,您就不应该收到有关潜在问题的警告。

HTTPS 绝对不是完美的,正如安全研究人员之前展示的那样。有可能为您设置的公司获得合法证书,这些公司恰好与现有品牌具有相同或相似的名称。务必确保您发送信用卡信息的对象是真正的。

您可以点击这个挂锁并获取有关网站用于与您通信的证书的信息,详细说明证书的所有者、相关网站以及验证它的互联网机构:

在本节中,我们将设置我们的网站以监听 HTTPS 连接,而不是 HTTP,并讨论一个名为Let's Encrypt的机构。

准备就绪

在本节中,我们将使用我们的 Debian 虚拟机。

如果您还没有,最好按照前面的部分设置 Nginx,包括您的虚拟主机条目。

如果您还没有在 Debian 虚拟机上使用 Nginx 设置虚拟主机,请立即设置。

我们在本节中所做的一切在 Apache 中也是可能的,尽管配置不同。

确保您将连接转发到您的虚拟机,这次使用一个新端口:

$ vagrant ssh debian1 -- -L 127.0.0.1:8484:127.0.0.1:443

如何做...

假设您的网站当前正在虚拟机上的端口80上运行,您应该能够对文件进行以下更改以启用 HTTPS 通信:

$ sudo sed -i 's/listen 80/listen 443 ssl/g' /etc/nginx/conf.d/fenchurch.conf
$ sudo sed -i '4iinclude snippets/snakeoil.conf;' /etc/nginx/conf.d/fenchurch.conf

通过使用cat确保您的文件看起来类似于以下内容:

$ cat /etc/nginx/conf.d/fenchurch.conf 
server {
listen 443 ssl;
listen [::]:80;
include snippets/snakeoil.conf;

root /var/www/fenchurch;
index index.html;

server_name 127.0.0.1;

location / {
try_files $uri $uri/ =404;
}
}

现在确保通过安装ssl-cert软件包可用蛇油证书:

$ sudo apt install ssl-cert -y

然后,重新加载我们的配置:

$ sudo systemctl reload nginx

您现在应该能够在浏览器中访问https://127.0.0.1:8484地址,并希望看到以下警告:

这意味着虽然服务器配置为监听 HTTPS 并且您可以连接到它,但浏览器认为证书不合法(因为它确实不合法),因此在您尝试进一步操作之前会警告您:

在这一点上,您可以按“添加异常...”并信任自签名证书,从而使您能够访问该网站。但是,除非您绝对确定应该看到警告,并且您对此没有问题(因为您正在测试),否则这不是一个好习惯。

它是如何工作的...

以下是一个非常重要的警告:

这里使用的证书仅供示例,因此被称为“蛇油”。它们绝对不应该在生产中使用,甚至开玩笑也不行。您应该只为您设置的任何服务器使用合法的证书(除了测试和开发,这些本来就不应该面向公众)。

既然这些都说清楚了,让我们看看我们在这里有什么:

server {
listen 443 ssl;
listen [::]:80;
include snippets/snakeoil.conf;

root /var/www/fenchurch;
index index.html;

server_name 127.0.0.1;

location / {
try_files $uri $uri/ =404;
}
}

我们使用了几个sed命令来更改这个文件,但如果愿意,我们也可以将内容复制到文件中。

我们将listen端口从80更改为443,并添加了一个ssl指令。我们还包括了一个片段示例文件,这里是snakeoil.conf

$ cat /etc/nginx/snippets/snakeoil.conf 
# Self signed certificates generated by the ssl-cert package
# Don't use them in a production server!

ssl_certificate /etc/ssl/certs/ssl-cert-snakeoil.pem;
ssl_certificate_key /etc/ssl/private/ssl-cert-snakeoil.key;

因此,我们的小虚拟主机可以访问 SSL 证书和证书密钥。

重新加载我们的配置意味着这些设置已经生效,然后我们可以在 Firefox 中访问我们的网站443端口。

然后我们收到了关于证书伪造的可怕消息,但我们预料到会发生这种情况。

在这个行业中,我们经常交替使用 SSL 和 TLS 这两个术语(包括我自己),这在技术上是不正确的(最糟糕的错误)。

安全套接字层SSL)是一个长期废弃的协议,已被传输层安全性TLS)取代,这现在是提供数据传输安全性的标准和通常默认的协议。

这两个协议都有几个版本:

  • SSL 1.0:从未发布

  • SSL 2.0:1995

  • SSL 3.0:1996

  • TLS 1.0:1999

  • TLS 1.1:2006

  • TLS 1.2:2008

  • TLS 1.3:2018

实际上,您现在应该只使用 TLS 1.2,当 1.3 变得普遍时,我建议您也转换到那个版本。

有关一些协议可能遭受的各种攻击的整篇文章,我建议您在设置 Web 服务器时,始终阅读当天的建议。

Heartbleed 通常是了解 SSL/TLS 漏洞的好地方。

一些较旧的操作系统不支持较新的协议,这意味着有时您会看到一些网站使用已经过时且不安全的协议,只是为了迎合世界上的 XP 用户。如果您的老板要求您使用 SSL(任何版本)或 TLS 早于 1.2,我建议您坐下来喝杯茶,解释为什么这是一个坏主意。

还有更多...

还有一些其他事情,确保您有一个良好的起点。

Let's Encrypt

如果您不想使用自己的 CA,您可以考虑使用 Let's Encrypt,这是一个提供免费证书的证书颁发机构。

通常,证书是要花钱的——在某些情况下是很多钱(比如扩展验证证书),所以能够使用免费的服务来保护您的网站是件好事。

证书只有效期 90 天,这有点不方便,但可以实现自动更新来解决这个问题。

部署和更新可以通过各种工具来完成,在许多发行版中都有。在 Debian 系统上,您可以考虑安装certbot软件包,并尝试配置自己的 Web 服务器。

工作环境证书

值得注意的是,使证书在传统意义上“受信任”的唯一因素是您知道是谁签发了它。一些浏览器和大多数操作系统都附带了一个“受信任”的证书颁发机构CA)列表,用于验证证书的合法性。

对于工作场所也是如此:您通常会看到(尤其是在大公司中)内部证书颁发机构,其完整性检查证书已安装在公司拥有的每台笔记本电脑和台式电脑上。这样做的结果是公司更容易为内部使用签发证书,但如果从外部访问这些系统,仍会显示警告(因为外部设备没有安装公司 CA)。

您可能会发现您自己的雇主在您的公司笔记本电脑上安装了 CA,与美国、中国和一些政府的大人物一起安装在一起。

另请参阅

谈论安全性是件好事,但事实上,这是一个相当令人困惑的话题,一般公众对此并不是很熟悉。

您可能认为,当终端用户看到一个大而可怕的横幅,告诉他们他们试图访问的网站不是合法的,并且他们会尽快点击离开...但在很多情况下并不是这样。

实际上,有很多终端用户只是因为不得不点击几次警告而感到恼火,然后才能到达他们想要的网站(或者假装是他们想要的网站)。

近年来,针对用户盲目接受假证书的各种尝试都在进行损害限制,例如浏览器使为网站添加例外变得更加烦人,但仍然会发生这种情况。

这突显了您作为系统管理员会一次又一次遇到的问题,即用户教育的问题,以及确保您的用户对网站安全有基本的了解。

基本的 MySQL 或 MariaDB 安装

数据库非常棒——它们让数据库管理员DBA)有了工作,并且提供了一种在不是一个随机大小的平面文件系列的系统上存储数据的便捷方式。

传统上,数据库一直是存储特定类型和大小的有序数据的好地方,这意味着您可以在各种事物中找到支持数据库,从银行交易记录到网站库存数量。

SQL 数据库是人们最熟悉的数据库(稍后会介绍更多关于 NoSQL 数据库),其中最常见的之一是 MariaDB,它是 MySQL 的一个分支。

MariaDB 是在 Oracle 收购 MySQL 后从 MySQL 分叉出来的。这可能是一个可以理解的担忧,因为开源领域的很多人认为 Oracle 是邪恶的,尽管我对此并没有强烈的感觉。

如果您运行一个 Wordpress 网站,您可能已经遇到了 MariaDB 或 MySQL,因为它们是大多数人在设置特定博客平台时的首选数据库。

准备就绪

在本节中,我们将使用我们的 CentOS 虚拟机。

我们现在暂时离开 Web 服务器,这意味着您无需担心必须完成前面的部分才能继续。

让我们跳到我们的 CentOS 虚拟机:

$ vagrant ssh centos1

如何做...

在尝试与其交互之前,安装软件被认为是一个好习惯。记住这一点,install mariadb-server

$ sudo yum install mariadb-server -y

接下来,确保它已启动并配置为在启动时启动(这可能需要几秒钟):

$ sudo systemctl enable --now mariadb
Created symlink from /etc/systemd/system/multi-user.target.wants/mariadb.service to /usr/lib/systemd/system/mariadb.service.

在继续之前,运行mysql_secure_installation脚本:

$ mysql_secure_installation

这将为您提供一系列提示。回答如下:

  • 输入 root 的当前密码(如果没有,请按回车):<BLANK, HIT ENTER>

  • 设置 root 密码? Y

  • 新密码:examplerootpassword

  • 删除匿名用户? Y

  • 禁止远程登录 root? Y

  • 删除测试数据库及其访问权限? Y

  • 现在重新加载权限表? Y

记住:这些只是本书的示例。您可能有一些需要这些设置的原因,在现实世界中,您应该始终设置一个安全的 root 密码。

接下来,登录到您的数据库:

$ mysql -uroot -pexamplerootpassword
Welcome to the MariaDB monitor. Commands end with ; or \g.
Your MariaDB connection id is 10
Server version: 5.5.60-MariaDB MariaDB Server
Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
MariaDB [(none)]> 

我们在这里直接在命令行上给出了密码,仅供显示目的,但是您可以完全省略实际密码,并让 MariaDB 提示您输入密码(这样它就不会出现在您的 Bash 历史记录中)。

列出、创建和选择数据库和表

从您的新提示符内部,您现在可以列出MariaDB中的数据库(我知道这有点混乱,但是数据库服务器(MariaDB)可以有多个它管理的数据库):

MariaDB [(none)]> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
+--------------------+
3 rows in set (0.00 sec)

我们想要查看内置的mysql数据库,所以让我们切换到那里:

MariaDB [(none)]> use mysql;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed

一旦我们使用了这个数据库,我们就可以列出其中的表:

MariaDB [mysql]> show tables;
+---------------------------+
| Tables_in_mysql |
+---------------------------+
| columns_priv |
| db |
| event |
| func |
| general_log |
| help_category |
| help_keyword |
| help_relation |
| help_topic |
| host |
| ndb_binlog_index |
| plugin |
| proc |
| procs_priv |
| proxies_priv |
| servers |
| slow_log |
| tables_priv |
| time_zone |
| time_zone_leap_second |
| time_zone_name |
| time_zone_transition |
| time_zone_transition_type |
| user |
+---------------------------+
24 rows in set (0.00 sec)

现在我们可以获取特定表的信息。在这里,我们从user表中获取HostUserPassword

MariaDB [mysql]> select Host,User,Password from user;
+-----------+------+-------------------------------------------+
| Host | User | Password |
+-----------+------+-------------------------------------------+
| localhost | root | *F61E89B5042AB6D880D5BA79586B46BA93FABF09 |
| 127.0.0.1 | root | *F61E89B5042AB6D880D5BA79586B46BA93FABF09 |
| ::1 | root | *F61E89B5042AB6D880D5BA79586B46BA93FABF09 |
+-----------+------+-------------------------------------------+
3 rows in set (0.00 sec)

除此之外,我们也可以创建自己的数据库和表。

让我们创建exampledb作为一个数据库:

MariaDB [mysql]> create database exampledb;
Query OK, 1 row affected (0.00 sec)

然后我们可以use这个数据库并添加一个表:

MariaDB [mysql]> use exampledb;
Database changed
MariaDB [exampledb]> create table exampletable (food varchar(10), goodbad char(1));
Query OK, 0 rows affected (0.00 sec)

让我们describe一下我们刚刚创建的表,看看我们的字段:

MariaDB [exampledb]> describe exampletable;
+---------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+---------+-------------+------+-----+---------+-------+
| food | varchar(10) | YES | | NULL | |
| goodbad | char(1) | YES | | NULL | |
+---------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)

接下来,用一些数据填充它:

MariaDB [exampledb]> insert into exampletable values ('strawberries','b');
Query OK, 1 row affected, 1 warning (0.00 sec)

现在我们可以通过select来查看我们刚刚放入表中的内容:

MariaDB [exampledb]> select * from exampletable;
+------------+---------+
| food | goodbad |
+------------+---------+
| strawberri | b |
+------------+---------+
1 row in set (0.00 sec)

要退出你的数据库,输入exit(或按下CTRL + D):

MariaDB [exampledb]> exit
Bye
$

它是如何工作的...

我们在系统上安装了 MariaDB 并启动了它。作为结果,我们在/var/lib/mysql目录中创建了几个数据库和相关数据:

$ ls /var/lib/mysql
aria_log.00000001 exampledb ib_logfile0 mysql performance_schema
aria_log_control ibdata1 ib_logfile1 mysql.sock

通过运行secure setup脚本,然后使用刚刚设置的密码进入我们的数据库:

$ mysql -uroot -pexamplerootpassword

这让我们进入了一个完全不同的 shell,一个在MariaDB程序内部的 shell,它使我们能够操纵MariaDB控制的数据库。

我们在四处探索后创建了一个数据库和其中的一个表:

MariaDB [mysql]> create database exampledb;
MariaDB [exampledb]> create table exampletable (food varchar(10), goodbad char(1));

我们特别创建的表名为exampletable。我们给它添加了两个字段:一个food字段和一个goodbad字段。

然后我们向数据库中插入了一些数据。

MariaDB [exampledb]> insert into exampletable values ('strawberries','b');

由于我们将food字段设置为10的 varchar,所以草莓条目太长,导致它被截断:

MariaDB [exampledb]> select * from exampletable;
+------------+---------+
| food | goodbad |
+------------+---------+
| strawberri | b |
+------------+---------+
1 row in set (0.00 sec)

这突显了传统 SQL 数据库的一个好处,你可以非常精细地控制每个字段中存储的数据类型以及你可以在其中存储多少数据。

这个数据库现在存在于我们的文件系统中,就像我们之前列出mysql目录时看到的那样:

$ sudo ls /var/lib/mysql/exampledb/
db.opt exampletable.frm

当你重新启动系统时,你对数据库所做的更改将继续存在。

还有更多...

正如开头所暗示的,MariaDB 是 MySQL 的一个分支,这就是为什么我们在这里安装了MariaDB,但我们也使用了与之交互的mysql系列命令。这是为了确保向后兼容。

另请参阅

还有数据库权限,我们还没有涉及,它们和常规文件系统权限一样重要。你不希望同一主机上的两个 Wordpress 安装能够读取彼此的数据库,所以你会为每个创建一个专用用户,并为他们分配自己的 MariaDB。

基本的 PostgreSQL 安装

还有另一个流行的 SQL 数据库,我不是在说 MSSQL(它还可以,在 Linux 上也可以运行!)。

PostgreSQL(无论你想怎么发音;其他人都这么做)自 1996 年以来一直存在,很多人都发誓它是比 MySQL 或 MariaDB 优秀得多的产品。

就我个人而言,我对数据库并不感到兴奋,所以这些对话通常会让我感到困惑,我会想下一杯咖啡从哪里来。

像 MySQL 和 MariaDB 一样,Postgres 在很多默认仓库中都有,很多流行的软件都会给你选择使用 Postgres 作为后端而不是 MariaDB 的选项。

准备工作

在本节中,我们将再次使用我们的 CentOS 机器。

进入我们的 CentOS 虚拟机(或者如果你已经在那里就留在那里):

$ vagrant ssh centos1

如何做...

就像 Maria 一样,我们实际上必须先安装软件:

$ sudo yum install postgresql-server -y

与 Maria 不同,我们必须在使用 Postgres 之前设置它:

$ sudo postgresql-setup initdb
Initializing database ... OK

不过,一旦完成,你就可以启动并启用服务器了:

$ sudo systemctl enable --now postgresql 

现在,以一种略有不同的方式登录到你的数据库:

$ sudo -u postgres psql 
psql (9.2.24)
Type "help" for help.

postgres=# 

列出、创建和选择数据库和表

有些人发现记住 Postgres 的命令和语法比记住 MariaDB 和 MySQL 更容易。就我个人而言,我总是不得不查找它们,这导致我频繁使用\?,这会让你进入help菜单。

从基础开始,使用\l列出 Postgres 管理的所有数据库:

postgres-# \l
 List of databases
 Name | Owner | Encoding | Collate | Ctype | Access privileges 
-----------+----------+----------+-------------+-------------+-----------------------
 postgres | postgres | UTF8 | en_GB.UTF-8 | en_GB.UTF-8 | 
 template0 | postgres | UTF8 | en_GB.UTF-8 | en_GB.UTF-8 | =c/postgres +
 | | | | | postgres=CTc/postgres
 template1 | postgres | UTF8 | en_GB.UTF-8 | en_GB.UTF-8 | =c/postgres +
 | | | | | postgres=CTc/postgres
(3 rows)

要创建一个数据库,我们将复制我们可以使用的两个模板之一:

postgres=# create database exampledb template template1;
CREATE DATABASE

要切换到我们的新数据库,使用\c

postgres=# \c exampledb
You are now connected to database "exampledb" as user "postgres".

你可能会认为要列出表,我们会使用\t,或者\lt,但你是错误的。

要列出当前数据库中的表,请使用\dt

exampledb=# \dt
No relations found.

在这个数据库中没有,所以让我们创建一个:

exampledb=# create table prime_ministers(firstname varchar(10), lastname varchar(10));
CREATE TABLE

我们还需要填充它:

exampledb=# insert into prime_ministers (firstname, lastname) values ('Lord', 'Rosebury'), ('George', 'Canning'), ('Tony', 'Blair');
INSERT 0 3

然后,我们需要描述它:

exampledb=# \d prime_ministers
 Table "public.prime_ministers"
 Column | Type | Modifiers 
-----------+-----------------------+-----------
 firstname | character varying(10) | 
 lastname | character varying(10) |

最后,我们需要从中进行选择:

exampledb=# select * from prime_ministers;
 firstname | lastname 
-----------+----------
 Lord | Rosebury
 George | Canning
 Tony | Blair
(3 rows)

使用\qCTRL + D退出 Postgres:

exampledb=# \q
$ 

它是如何工作的...

安装 Postgres 后,我们会在/var/lib/pgsql中得到数据,如下所示:

$ sudo ls /var/lib/pgsql/
backups data initdb.log

然后我们使用在安装过程中为我们创建的用户登录到我们的数据库中:

$ sudo -u postgres psql 
$ cat /etc/passwd | grep postgres
postgres:x:26:26:PostgreSQL Server:/var/lib/pgsql:/bin/bash

一旦进入,我们就开始创建我们自己的数据库,使用template1作为模板:

postgres=# create database exampledb template template1;

template1可以进行修改,以便您可以为新系统拥有一个一致的起点,尽管我发现这在当今更容易存储在基础设施即代码存储库中。

我们在我们的数据库中创建了一个表(在切换到它之后):

exampledb=# create table prime_ministers(firstname varchar(10), lastname varchar(10));

再次注意,我们正在定义特定字段,与相关的类型(varchar是一种非常灵活的数据类型,但不建议将其用于所有内容。对于与字段关联的数据类型使用适当的类型对于性能更好)。

我们用内容填充了我们的表,描述了它,并从中进行了选择。

exampledb=# insert into prime_ministers (firstname, lastname) values ('Lord', 'Rosebury'), ('George', 'Canning'), ('Tony', 'Blair');
exampledb=# \d prime_ministers
exampledb=# select * from prime_ministers;

希望您已经注意到,我们在本节中使用的许多语法与 MariaDB 安装中使用的语法完全相同。这是有原因的,因为这两个数据库都带有SQL这个名称。

结构化查询语言是相当一致的,但并不是在一个 SQL 数据库中使用的命令可以直接复制到另一个数据库中。遗憾的是,这在大多数情况下都是一个白日梦。

除了表和数据库操作命令(\l\dt等),您可能会因为混淆 Postgres 和 Maria 语法而受到宽恕,但有时它们之间的差异足够让人讨厌。

我们还没有将 MSSQL 加入其中。

如果你听到有人将 SQL 称为 seeqwel,而不是 S.Q.L.,那么他们很可能是从 Windows 数据库管理员那里学来的,或者是足够老以至于记得 SQL 最初是“结构化英语查询语言”。它的名字不再有“e”,但有些人坚持在发音时加上这个短暂的“e”。

圣战...

本地 MTA 使用和配置(Postfix)

电子邮件仍然存在,虽然这本身就是一种悲剧,但它也为我们提供了机会,让我们看看为什么您可能会在日常工作中与邮件服务器进行交互。

传统上,服务器有时会在每晚或每周进行一系列检查,然后将结果编制成文档并发送给系统管理员,然后管理员可以查看报告并发现异常或意外行为。大多数情况下,这已经是一种被遗忘的艺术,很少有人会费心去配置系统上的默认邮件位置,使其不再是root@localhost

正是因为这个原因,你偶尔会看到you have new mail或类似的通知,当你登录控制台时。那里的邮件通常是你不太在意的东西,来自一个在五天前以不同方式通知你它出了问题的程序。

这并不是说邮件不重要——它仍然被监控系统作为第一种“警报”方法积极使用,而且,尽管听起来令人惊讶,一些人确实仍然在运行他们自己的邮件服务器(尽管如今,您更有可能发现一家公司使用 ProtonMail 等现成解决方案,并配置了他们自己的域记录)。

电子邮件存在问题,即使在监控系统中用作警报方法时也是如此。我已经失去了对我去过的许多地方的数量,他们都设置了 Nagios、Icinga2 或 Zabbix,配置为在出现问题时发送电子邮件,但也在问题消失时发送,或者在可能出现问题时应该查看时发送。警报邮件可能会迅速增加,导致工程师产生警报疲劳,他们只是将他们的电子邮件发送到junk文件夹中,并且从不检查它(除非它变得太满,他们偶尔清空它)。短信是一种更烦人的警报方法-尝试一下。

准备工作

在本节中,我们将使用我们的 CentOS 虚拟机,主要是因为 CentOS 默认安装了 Postfix。

登录到您的 CentOS 服务器,并确保 Postfix 正在运行:

$ vagrant ssh centos1
$ systemctl status postfix
● postfix.service - Postfix Mail Transport Agent
 Loaded: loaded (/usr/lib/systemd/system/postfix.service; enabled; vendor preset: disabled)
 Active: active (running) since Sun 2018-12-02 11:35:12 UTC; 21h ago
 Main PID: 1125 (master)
 CGroup: /system.slice/postfix.service
 ├─ 1125 /usr/libexec/postfix/master -w
 ├─ 1129 qmgr -l -t unix -u
 └─10453 pickup -l -t unix -u

如果它没有安装并且没有运行,您可以简单地安装postfix软件包:

$ sudo yum install postfix -y

如何做...

通常情况下,您会发现 Postfix 已经安装和配置好了,所以让我们浏览一下我们已经准备好的内容,并查看一些常见的实用程序。

首先,让我们使用alternatives命令检查系统配置为使用哪个 MTA:

$ alternatives --list | grep mta
mta auto /usr/sbin/sendmail.postfix

这告诉我们系统正在使用 Postfix 发送邮件。

尝试向示例地址发送一些邮件。为此,我们首先需要安装一个小的命令行,即mailx

$ sudo yum install mailx -y

现在,您可以运行以下邮件命令,将您想要的内容写入新行(并在最后一行以单个.结束邮件):

$ mail -s "Example Subject" packt@example.co
This is an example email.
We end emails written in this way, using the '.' symbol.
That way, the client knows when the email has come to a close.
.
EOT
$ 

如果我们现在再次输入mail,我们将进入邮件 shell:

$ mail 
Heirloom Mail version 12.5 7/5/10\. Type ? for help.
"/var/spool/mail/vagrant": 1 message 1 unread
>U 1 Mail Delivery System Mon Dec 3 09:35 76/2632 "Undelivered Mail Returned to Sender"
&

请注意,我们被告知我们有一条消息,其中有一条未读消息,然后我们看到了加粗的行,主题用引号括起来。

要打开消息,请输入与问题消息对应的数字:

您将进入一个分页器,可以让您滚动查看消息。

在这条消息中,您可以看到一些重要的内容,首先是响应系统是MAILER-DAEMON@centos1.localdomain(邮件传递系统),这表明我们的消息并没有传得很远。

然后,电子邮件建议您可以采取一些措施来帮助缓解您的问题,其中首先是联系邮件管理员,就像同名电影中的页码大师一样,往往具有一定的超凡品质。

在底部,您会看到您的电子邮件的一部分。

这告诉我们什么?首先,它告诉我们您的邮件服务器无法发送您刚刚尝试发送的消息。

接下来,它告诉我们 Postfix 正在运行足够处理邮件,因为退信仍然是邮件。

main.cf

Postfix 使用主配置文件/etc/postfix/main.cf

这个文件包含了很多配置选项,但是默认情况下,它没有配置为执行任何操作(例如,它不会接受来自另一个系统的传入邮件)。

/etc/aliases

这是接收某些邮件的帐户的数据库或映射。

通常,在此文件中进行的唯一配置更改是 root 邮件的目的地。在本地系统上,您可以将此映射到您的用户:

# Person who should get root's mail
root: vagrant

然后,您运行newaliases来应用此更改:

$ sudo newaliases

虽然不经常这样做,但有些人仍然喜欢获取发送给 root 的电子邮件,以确保没有随机程序死掉并在被收割者夺走时尖叫。

工作原理...

要获取有关消息发送失败时发生的情况的更详细输出,通常可以检查/var/log/maillog

$ sudo cat /var/log/maillog 
Dec 2 11:35:12 localhost postfix/postfix-script[1120]: starting the Postfix mail system
Dec 2 11:35:12 localhost postfix/master[1125]: daemon started -- version 2.10.1, configuration /etc/postfix
Dec 3 09:35:43 localhost postfix/pickup[11157]: 8E9A1206B117: uid=1000 from=<vagrant>
Dec 3 09:35:43 localhost postfix/cleanup[11317]: 8E9A1206B117: message-id=<20181203093543.8E9A1206B117@centos1.localdomain>
Dec 3 09:35:43 localhost postfix/qmgr[1129]: 8E9A1206B117: from=<vagrant@centos1.localdomain>, size=601, nrcpt=1 (queue active)
Dec 3 09:35:43 localhost postfix/smtp[11319]: 8E9A1206B117: to=<packt@example.co>, relay=none, delay=0.12, delays=0.04/0.01/0.06/0, dsn=5.4.4, status=bounced (Host or domain name not found. Name service error for name=example.co type=AAAA: Host not found)
Dec 3 09:35:43 localhost postfix/cleanup[11317]: A88F7206B118: message-id=<20181203093543.A88F7206B118@centos1.localdomain>
Dec 3 09:35:43 localhost postfix/qmgr[1129]: A88F7206B118: from=<>, size=2545, nrcpt=1 (queue active)
Dec 3 09:35:43 localhost postfix/bounce[11320]: 8E9A1206B117: sender non-delivery notification: A88F7206B118
Dec 3 09:35:43 localhost postfix/qmgr[1129]: 8E9A1206B117: removed
Dec 3 09:35:43 localhost postfix/local[11321]: A88F7206B118: to=<vagrant@centos1.localdomain>, relay=local, delay=0.02, delays=0/0.02/0/0, dsn=2.0.0, status=sent (delivered to mailbox)
Dec 3 09:35:43 localhost postfix/qmgr[1129]: A88F7206B118: removed

在这里,我们可以看到发生了什么,并且如果我们找到想要的消息,我们可以通过消息的message-id跟踪消息的线程。

首先,我们可以看到 Postfix 正在接收消息:

Dec 3 09:35:43 localhost postfix/pickup[11157]: 8E9A1206B117: uid=1000 from=<vagrant>

接下来,守护进程在将消息传递到邮件队列之前处理消息:

Dec 3 09:35:43 localhost postfix/cleanup[11317]: 8E9A1206B117: message-id=<20181203093543.8E9A1206B117@centos1.localdomain>

我们得知消息在队列中,等待发送:

Dec 3 09:35:43 localhost postfix/qmgr[1129]: 8E9A1206B117: from=<vagrant@centos1.localdomain>, size=601, nrcpt=1 (queue active)

最后(对于这条消息),SMTP 尝试处理邮件:

Dec 3 09:35:43 localhost postfix/smtp[11319]: 8E9A1206B117: to=<packt@example.co>, relay=none, delay=0.12, delays=0.04/0.01/0.06/0, dsn=5.4.4, status=bounced (Host or domain name not found. Name service error for name=example.co type=AAAA: Host not found)

它立即失败,因为域名不真实。

创建了一个退信消息,即A88F7206B118,并进行处理(原始消息8E9A1206B117被删除):

Dec 3 09:35:43 localhost postfix/cleanup[11317]: A88F7206B118: message-id=<20181203093543.A88F7206B118@centos1.localdomain>
Dec 3 09:35:43 localhost postfix/qmgr[1129]: A88F7206B118: from=<>, size=2545, nrcpt=1 (queue active)
Dec 3 09:35:43 localhost postfix/bounce[11320]: 8E9A1206B117: sender non-delivery notification: A88F7206B118
Dec 3 09:35:43 localhost postfix/qmgr[1129]: 8E9A1206B117: removed

在发送给本地用户之前完成此操作,该用户最初发起了邮件尝试:

Dec 3 09:35:43 localhost postfix/local[11321]: A88F7206B118: to=<vagrant@centos1.localdomain>, relay=local, delay=0.02, delays=0/0.02/0/0, dsn=2.0.0, status=sent (delivered to mailbox)
Dec 3 09:35:43 localhost postfix/qmgr[1129]: A88F7206B118: removed

它会落在/var/spool/mail/vagrant,如下友好的消息所示:

You have mail in /var/spool/mail/vagrant

这里的每个步骤都是由 Postfix 完成的,一个主守护进程负责许多小守护进程,每个守护进程都有一个特定的工作。

如果我们运行ps并查找postfix守护程序,你会发现类似这样的东西:

 1125 ? Ss 0:00 /usr/libexec/postfix/master -w
 1129 ? S 0:00 \_ qmgr -l -t unix -u
11157 ? S 0:00 \_ pickup -l -t unix -u

qmgr,顾名思义,是队列管理器,而 pickup 是用于本地邮件接收的。

要查看队列,可以使用postqueue命令:

$ postqueue -p
-Queue ID- --Size-- ----Arrival Time---- -Sender/Recipient-------
D71FD206B117 458 Mon Dec 3 10:04:18 vagrant@centos1.localdomain
 (connect to nasa.com[192.64.147.150]:25: Connection refused)
 contact@nasa.com

-- 0 Kbytes in 1 Request.

在这里,您可以看到我发送给contact@nasa.com的消息。这不起作用,因为我们的连接尝试被拒绝了。

还要注意端口25,这是一个传统的邮件接收端口。

如果您想清空队列,可以使用postsuper,如下所示:

$ sudo postsuper -d D71FD206B117
postsuper: D71FD206B117: removed
postsuper: Deleted: 1 message

您还可以flush邮件队列,尝试重新投递其中的邮件。不过,通常情况下,除非您已解决了导致邮件无法投递的问题,否则您只是导致邮件再次失败。更重要的是,您可能在这个过程中使您的服务器陷入停滞。

还有更多...

Postfix 是一个邮件传输代理(MTA)。

遗憾的是,这不是一本关于配置邮件服务器的书,因为这个主题已经有很多很多书了。你只需要知道如何与默认的 Postfix 安装进行交互,如果你在野外遇到它,那么你就需要知道。

如果您想了解更多关于 Postfix 的信息,或者想运行自己的邮件服务器,我建议您不要这样做。不过,如果您回来说您真的非常想运行自己的邮件服务器,我建议您花一个周末时间研究一下 Postfix,了解最佳实践(例如,不要创建开放中继),并尽量不要立即放弃。祝您好运。

不过,如今大多数公司要么使用某种形式的 Exchange 服务器,要么只是使用 Google、ProtonMail、FastMail 等。

另请参阅

值得阅读电子邮件,因为实际上有很大的机会,你会发现自己有一天要打开一封电子邮件查看标题。了解电子邮件为什么以这种方式工作(即将消息传递给链中的下一个邮件服务器,直到最终到达最终用户)在这种情况下可能非常有价值。

如果自己运行邮件服务器的想法出现了奇迹般的复苏,我可能会在将来扩展这一部分。

本地 MTA 使用和配置(Exim)

与 Postfix 一样,我们还有 Exim,这是另一个 MTA。

Postfix 强大之处在于其专注于安全性,而 Exim 以其极高的可定制性而闻名,其历史可以追溯到 1995 年(比 Postfix 早三年)。多年来,它一直是 Debian 项目的首选,因此很多关于设置邮件服务器的文档都引用了 Exim。

与 Postfix 一样,这不会是一个完全配置 Exim 的长篇章节,因为这需要比一个章节、一本书更长的东西。相反,我们将看一些配置、Exim 日志文件以及在尝试从本地系统发送消息时会发生什么。

做好准备

在本节中,我们将使用我们的 Debian 机器。

要确保 Exim 已安装在您的系统上,请运行以下命令:

$ sudo apt install exim4 -y

Exim 套件中有相当多的软件包,可能需要一些时间来运行它们。

使用systemctl status检查是否正在运行:

$ systemctl status exim4
● exim4.service - LSB: exim Mail Transport Agent
 Loaded: loaded (/etc/init.d/exim4; generated; vendor preset: enabled)
 Active: active (running) since Wed 2018-12-05 17:38:01 GMT; 1min 29s ago
 Docs: man:systemd-sysv-generator(8)
 Process: 5402 ExecStart=/etc/init.d/exim4 start (code=exited, status=0/SUCCESS)
 Tasks: 1 (limit: 4915)
 CGroup: /system.slice/exim4.service
 └─5649 /usr/sbin/exim4 -bd -q30m

如何做到这一点...

安装后,让我们快速测试一下邮件服务器的默认外观。

通过发送一个示例邮件开始:

$ mail -s "Example Subject" packt@example.co
Cc: 
This is another piece of example mail.
In this case we need to end with Ctrl-D.
Like so!
$ 

如果我们现在再次运行mail,我们会看到我们的退信消息:

$ mail
"/var/mail/vagrant": 1 message 1 new
>N 1 Mail Delivery Syst Wed Dec 5 17:46 56/1737 Mail delivery failed: returnin
? 

再次按下1Enter将加载第一条消息:

在这里,我们实际上得到了有用的消息不支持发送到远程域,因为所涉及的服务器无法执行此操作。

使用 Exim,您还可以测试如何将邮件路由到给定地址的地址测试模式。给定前面的地址时,它会打印一个熟悉的消息:

$ sudo exim -bt packt@example.co
R: nonlocal for packt@example.co
packt@example.co is undeliverable: Mailing to remote domains not supported

它甚至告诉你它是非本地的,而如果我们用相同的命令,用本地用户替换假电子邮件,我们会得到以下结果:

$ sudo exim -bt vagrant@localhost
R: system_aliases for vagrant@localhost
R: userforward for vagrant@localhost
R: procmail for vagrant@localhost
R: maildrop for vagrant@localhost
R: lowuid_aliases for vagrant@localhost (UID 1000)
R: local_user for vagrant@localhost
vagrant@localhost
 router = local_user, transport = mail_spool

使用的传输是本地mail_spool,没有关于邮件无法投递的消息。

我们还可以使用ss来确认我们的邮件服务器只在本地(127.0.0.1 和::1)上监听端口25

$ ss -tna '( sport = :smtp )'
State Recv-Q Send-Q Local Address:Port Peer Address:Port 
LISTEN 0 20 127.0.0.1:25 *:* 
LISTEN 0 20 ::1:25 :::* 

它是如何工作的...

在我们的 Debian 主机上,Exim 配置位于/etc/exim4文件夹中。列出此文件的内容如下:

$ ls /etc/exim4/
conf.d exim4.conf.template passwd.client update-exim4.conf.conf

正在积极使用的配置文件是update-exim4.conf.conf文件(是的,.conf出现了两次)。

这个文件的原始内容如下:

# /etc/exim4/update-exim4.conf.conf
#
# Edit this file and /etc/mailname by hand and execute update-exim4.conf
# yourself or use 'dpkg-reconfigure exim4-config'
#
# Please note that this is _not_ a dpkg-conffile and that automatic changes
# to this file might happen. The code handling this will honor your local
# changes, so this is usually fine, but will break local schemes that mess
# around with multiple versions of the file.
#
# update-exim4.conf uses this file to determine variable values to generate
# exim configuration macros for the configuration file.
#
# Most settings found in here do have corresponding questions in the
# Debconf configuration, but not all of them.
#
# This is a Debian specific file

dc_eximconfig_configtype='local'
dc_other_hostnames='debian1'
dc_local_interfaces='127.0.0.1 ; ::1'
dc_readhost=''
dc_relay_domains=''
dc_minimaldns='false'
dc_relay_nets=''
dc_smarthost=''
CFILEMODE='644'
dc_use_split_config='false'
dc_hide_mailname=''
dc_mailname_in_oh='true'
dc_localdelivery='mail_spool'

但是,可以根据文件的建议使用sudo dpkg-reconfigure exim4-config进行修改:

$ sudo dpkg-reconfigure exim4-config

这将使您进入一个 TUI,看起来像这样:

在这里,您可以根据自己的喜好重新配置邮件服务器,包括将其设置为 Internet 邮件服务器。

一旦按照您的喜好完成,您会发现配置文件已经更新以反映这些设置。

如果您想知道某些配置设置是否生效,可以使用exiwhat命令,如下所示:

$ sudo exiwhat
 3280 daemon(4.89): -q30m, listening for SMTP on [127.0.0.1]:25 [::1]:25

Exim,像 Postfix 一样,还提供了一个有用的maillog,尽管在 Exim 的情况下,它被称为mainlog,通常位于特定的exim目录中:

$ sudo ls /var/log/exim4/
mainlog

内容相对熟悉:

$ sudo cat /var/log/exim4/mainlog
<SNIP>
2018-12-05 19:03:15 1gUcS3-0000xe-Ps <= vagrant@debian1 U=vagrant P=local S=466
2018-12-05 19:03:15 1gUcS3-0000xe-Ps ** packt@example.co R=nonlocal: Mailing to remote domains not supported
2018-12-05 19:03:15 1gUcS3-0000xg-SC <= <> R=1gUcS3-0000xe-Ps U=Debian-exim P=local S=1645
2018-12-05 19:03:15 1gUcS3-0000xe-Ps Completed
2018-12-05 19:03:15 1gUcS3-0000xg-SC => vagrant <vagrant@debian1> R=local_user T=mail_spool
2018-12-05 19:03:15 1gUcS3-0000xg-SC Completed

我们还可以在这里看到一些相当明显的行,我们也可以对其进行扩展:

2018-12-05 19:03:15 1gUcS3-0000xe-Ps <= vagrant@debian1 U=vagrant P=local S=466

已经收到了以下消息from vagrant@debian1<=):

2018-12-05 19:03:15 1gUcS3-0000xe-Ps ** packt@example.co R=nonlocal: Mailing to remote domains not supported

邮件传递失败,因为地址被退回(**):

2018-12-05 19:03:15 1gUcS3-0000xg-SC <= <> R=1gUcS3-0000xe-Ps U=Debian-exim P=local S=1645

从 Debian-exim 收到了一条新消息:

2018-12-05 19:03:15 1gUcS3-0000xe-Ps Completed

原始消息的旅程已经结束:

2018-12-05 19:03:15 1gUcS3-0000xg-SC => vagrant <vagrant@debian1> R=local_user T=mail_spool

新消息已发送给 vagrant 用户(=>):

2018-12-05 19:03:15 1gUcS3-0000xg-SC Completed

新消息的旅程现在也已经结束了。

还有更多...

我必须在这里向 Exim 故障排除提供一个不可或缺的网站的赞扬:bradthemad.org/tech/notes/exim_cheatsheet.php

如果您发现自己卡在 Exim 可以执行的某个操作上,或者确定有特定的查询可以获取您要获取的信息,请查看 Brad 的 Exim 备忘单-您不会是第一个。

感谢 Brad,帮助所有管理员。

NoSQL 文档(MongoDB 示例)

我们之前已经看过结构化查询语言SQL)服务,以 MariaDB 和 PostgreSQL 的形式。现在我们将看看以 NoSQL 方式在数据库中存储数据的“新”方式。

与传统数据库的基于表的关系不同,NoSQL 数据库以其他方式存储数据(如键值存储、元组存储或文档存储)。由于市场上突然涌入大量大数据产品,这些数据库在最近几年变得突出,这些产品大多时候依赖这些数据库进行存储。

NoSQL 数据库可以采用几种不同的形式,如先前暗示的,我们将在下一节中看一个示例(Redis)。

在考虑为什么要使用 NoSQL 数据库代替传统数据库时,您可能会考虑可伸缩性、灵活性和速度等方面,所有这些都可以是良好 NoSQL 设置的特点。

术语数据库可能更松散和准确地应用于 NoSQL 设置,这可能是数据的基础,但可能不需要为其包含的数据使用结构化存储。

在本节中,我们将安装 MongoDB,并查看其数据库中存储数据的方式。

大免责声明:

MongoDB 可能因为成为黑客社会不道德成员的目标而臭名昭著。这是因为 MongoDB 可以使用默认设置安装,让它在互联网上监听,开放端口,而没有启用任何形式的访问要求。这是不好的,你应该在做任何事情之前考虑安全问题。与任何应用程序一样,安全性主要是管理员安装和管理的责任。我在本书的某些部分详细讨论了安全性,但强调这一点很重要。我经常看到对部署服务器(各种类型的服务器)采取漫不经心的方法,导致网络中出现一个很大的漏洞。在有自由裁量权的情况下,我看到一些非常聪明的人在不考虑后果的情况下做了一些非常愚蠢的事情(比如在面向公众的构建服务器上保留默认的管理员凭据),汉隆剃刀定律总是适用的:

“永远不要归因于恶意,那可以充分解释为愚蠢。”

准备工作

在本节中,我们将使用我们的 CentOS 机器,通过 SSH 连接到它:

$ vagrant ssh centos1

安装 EPEL 存储库,然后安装 MongoDB:

$ sudo yum install epel-release -y
$ sudo yum install mongodb mongodb-server -y

我们在这里使用 EPEL 存储库是为了方便,但安装 MongoDB 还有更多的方法。具体来说,使用官方存储库是生产系统的推荐方法。

如何做...

安装后,你可以使用mongod守护进程启动和启用 MongoDB:

$ sudo systemctl enable --now mongod

默认情况下,MongoDB 将在本地主机端口 27017 上启动,但使用ss或其他工具进行确认:

$ ss -a '( sport = :27017 )'
State Recv-Q Send-Q Local Address:Port Peer Address:Port 
LISTEN 0 128 127.0.0.1:27017 *:* 

一旦启动,你可以使用mongo命令行工具连接到你的数据库:

$ mongo

上述命令的输出如下:

注意我们默认连接到了test。如果我们错过了通知,我们可以用db命令确认我们连接到了哪个数据库:

> db
test

我们可以通过使用use轻松地(并且以熟悉的方式)切换数据库:

> use local
switched to db local

在我们当前的数据库中,我们可以看到集合。因为我们切换到了local,所以它们看起来是这样的:

> show collections
startup_log
system.indexes

与集合一起工作的语法是db.<collection name>.<blah>,所以如果我们想查询startup_log集合中的所有内容,我们可以这样做:

> db.system.indexes.find()
{ "v" : 1, "key" : { "_id" : 1 }, "name" : "_id_", "ns" : "local.startup_log" }

显然,这有点混乱,所以我们可以使用.pretty()使其更易于阅读:

> db.system.indexes.find().pretty()
{
 "v" : 1,
 "key" : {
 "_id" : 1
 },
 "name" : "_id_",
 "ns" : "local.startup_log"
}

你应该已经看到与 SQL 数据库的相似之处,尽管在许多情况下语法有很大的不同。

让我们回到我们的test数据库,并创建一个我们自己的集合:

> use test
switched to db test
> db.distributions.insert( { name: "Ubuntu", developer: "Canonical Ltd.", initial_year: 2004 } )
WriteResult({ "nInserted" : 1 })

现在,通过对这个集合进行查找,我们应该看到我们刚刚插入的数据:

> db.distributions.find().pretty()
{
 "_id" : ObjectId("5c081ba9832e06b5d1b64d50"),
 "name" : "Ubuntu",
 "developer" : "Canonical Ltd.",
 "initial_year" : 2004
}

让我们再添加一行,这次加上更多的字段:

> db.distributions.insert( { name: "Alpine Linux", developer: "Alpine Linux development team", initial_year: 2010, first_version: 2.0, forked_from: "LEAF Project" } )
WriteResult({ "nInserted" : 1 })

让我们再次执行我们的查找:

> db.distributions.find().pretty()
{
 "_id" : ObjectId("5c081ba9832e06b5d1b64d50"),
 "name" : "Ubuntu",
 "developer" : "Canonical Ltd.",
 "initial_year" : 2004
}
{
 "_id" : ObjectId("5c081c31832e06b5d1b64d51"),
 "name" : "Alpine Linux",
 "developer" : "Alpine Linux development team",
 "initial_year" : 2010,
 "first_version" : 2,
 "forked_from" : "LEAF Project"
}

请注意,虽然我们的插入是正常的,但first_version字段中的2.0已经简化为整数。

如果我们想缩小我们的搜索范围,我们可以专门搜索name为 Ubuntu 的条目:

> db.distributions.find({"name": "Ubuntu"}).pretty()
{
 "_id" : ObjectId("5c081ba9832e06b5d1b64d50"),
 "name" : "Ubuntu",
 "developer" : "Canonical Ltd.",
 "initial_year" : 2004
}

或者,如果我们只想打印特定的值(这里只是名称),我们可以使用以下命令:

> db.distributions.find( {}, {"name": 1, "_id": 0} ).pretty()
{ "name" : "Ubuntu" }
{ "name" : "Alpine Linux" }

注意这个查询的奇怪结构,我们指定了1来包括名称,但指定了0来省略_id,这是默认包含的。

还有很多其他事情可以做,包括按特定查询搜索(在这种情况下,查找所有initial_year大于2004的条目):

> db.distributions.find({"initial_year": { $gt: 2004}}).pretty()
{
 "_id" : ObjectId("5c081c31832e06b5d1b64d51"),
 "name" : "Alpine Linux",
 "developer" : "Alpine Linux development team",
 "initial_year" : 2010,
 "first_version" : 2,
 "forked_from" : "LEAF Project"
}

具体来说,我们对存储数据的这种方法和传统方法之间的区别感兴趣。

它是如何工作的...

由于我们正在处理文档,并且事情是即兴创建的(而不是有一个严格数据集的表),我们之前的命令似乎比传统数据库更加临时。

当我们创建我们的集合时,我们不必定义任何东西。我们只是在我们的测试数据库中写入数据:

> db.distributions.insert( { name: "Ubuntu", developer: "Canonical Ltd.", initial_year: 2004 } )

从那里,我们能够添加更多的数据,然后开始查询我们刚刚写入的数据。

随后添加的数据并不重要,虽然我们写入的文档中存在一些关联,比如名称、开发者和initial_year字段,但也有一些字段是唯一的。数据库并不在乎。

由于这种存储数据的方法,NoSQL 系统在输入方面可以被视为更加灵活。

这些数据现在存储在 MongoDB 中,任何想要查询它的程序都可以快速轻松地访问(通常是诸如 Node.js 应用程序之类的东西)。

我们可以使用另一个查询准确查看dbPath值的位置:

> use local
switched to db local
> db.startup_log.find( {}, {"cmdLine.storage": 1} ).pretty()
{
 "_id" : "centos1-1544033443006",
 "cmdLine" : {
 "storage" : {
 "dbPath" : "/var/lib/mongodb"
 }
 }
}

这意味着我们也可以在我们的主机系统上查看它:

$ sudo ls -l /var/lib/mongodb/
total 163844
drwxr-xr-x. 2 mongodb mongodb 29 Dec 5 18:40 journal
-rw-------. 1 mongodb mongodb 67108864 Dec 5 18:10 local.0
-rw-------. 1 mongodb mongodb 16777216 Dec 5 18:10 local.ns
-rwxr-xr-x. 1 mongodb mongodb 6 Dec 5 18:10 mongod.lock
-rw-------. 1 mongodb mongodb 67108864 Dec 5 18:42 test.0
-rw-------. 1 mongodb mongodb 16777216 Dec 5 18:42 test.ns
drwxr-xr-x. 2 mongodb mongodb 6 Dec 5 18:40 _tmp

还有更多...

我建议使用的 MongoDB 的 EPEL 版本已经过时。在撰写本文时是如此,因此,如果您想要为自己的系统使用 MongoDB,最好从上游存储库中尝试最新版本。

我再次强调,如果您计划在生产中使用它,启用某种安全性也是必须的。

此外,如果您仍在尝试考虑 NoSQL 数据库何时可能比传统的 PostgreSQL 或 MySQL 设置更有用,请考虑诸如日志记录之类的情况。

您希望日志文件是一致的,具有相同的字段和相同类型的数据。但是,日志文件可能会发生变化,它们的顺序可能会不同,类型可能会突然改变,字段的数量可能会增加。

如果您将传统数据库用作这些日志的后端(完全可能),您将不得不在最好的情况下添加新表,或者在最坏的情况下完全使用不同的数据库。

将这些相同的日志导入 NoSQL 系统,一个专门设计用于处理文档的系统,不应该对系统的运行产生影响,因为集合将简单地适应您提供的新信息。

NoSQL KV(Redis 示例)

我们已经看过一种类型的 NoSQL 数据库,即 MongoDB(一个特定于文档的实例)。现在我们将看一看另一种类型的 NoSQL 数据库,具体来说是 Redis,它是一种键-值KV)数据库。

它的独特卖点是它是一个完全的内存数据库(如果需要,定期写入磁盘)。这意味着 Redis 不仅是一个用于存储您正在操作或使用的数据的缓存,而且速度非常快。

由于 Redis 的设计,它常见于 Web 堆栈设置中,速度对于高效处理请求非常重要。

需要注意的是,对于 Redis 和其他 NoSQL 数据库,有一个批评是在特定情况下数据很容易丢失。因为 Redis 主要将数据存储在内存中,偶尔写入磁盘,所以即使只有几秒钟的时间,节点的灾难性故障也可能导致数据丢失。

准备工作

在本节中,我们将再次使用我们的 CentOS 虚拟机。

SSH 到您的 CentOS 机器:

$ vagrant ssh centos1

安装 EPEL 存储库,然后安装 MongoDB:

$ sudo yum install epel-release -y
$ sudo yum install redis -y

同样,出于方便起见,我们使用 EPEL,但是有更更新的选择,而运行 Redis 的最受欢迎的方法之一是在 Docker 容器中。

如何做到这一点...

默认情况下,systemd 将使用/etc/redis.conf配置文件启动 redis-server 二进制文件。现在让我们继续做这个:

$ sudo systemctl enable --now redis
Created symlink from /etc/systemd/system/multi-user.target.wants/redis.service to /usr/lib/systemd/system/redis.service.

默认端口是6379,我们可以再次使用ss来检查:

$ ss -a '( sport = :6379 )'
State Recv-Q Send-Q Local Address:Port Peer Address:Port 
LISTEN 0 128 127.0.0.1:6379 *:* 

在这里,我们监听 localhost,端口6379

与以往一样,同样的警告适用:如果您开始打开任何已安装的服务,请确保它们是安全的,不会让不良行为者窃取您的数据。

通过命令行使用redis-cli连接到实例,简单地调用它应该会让您进入 Redis shell:

$ redis-cli
127.0.0.1:6379> 

Redis 建议使用ping来查看 Redis 是否正常响应:

127.0.0.1:6379> ping
PONG

因为 Redis 是一个 KV 存储,与其交互的语法可能非常简单。以下示例创建了一个具有字符串值的键:

127.0.0.1:6379> set distro centos
OK

为了检索数据,我们使用get

127.0.0.1:6379> get distro
"centos"

如果我们想设置一个整数值,我们只需指定它:

> set number_of_linux_distros 20
OK

每当创建一个新的 Linux 发行版时,我们就可以递增它:

127.0.0.1:6379> incr number_of_linux_distros
(integer) 21
127.0.0.1:6379> incr number_of_linux_distros
(integer) 22
127.0.0.1:6379> incr number_of_linux_distros
(integer) 23
127.0.0.1:6379> incr number_of_linux_distros
(integer) 24
127.0.0.1:6379> incr number_of_linux_distros
(integer) 25
127.0.0.1:6379> get number_of_linux_distros
"25"

您也不仅限于一次获取mget

127.0.0.1:6379> mget distro number_of_linux_distros
1) "centos"
2) "25"

您可能会想知道这有什么用。我的意思是,当然,它很快,但它真的很好吗,以至于成为任何体面的 Web 堆栈的不可或缺的一部分吗?

Redis 还可以做很多其他事情,包括将二进制数据(例如图像)存储为键的值;它可以创建具有短生存期的键,这意味着它可以用于临时缓存;它可以进行集群化。

它的使用通常取决于应用程序的程序员,而不是负责设置它的管理员。但是,这并不意味着如果您的开发人员都坐在一张桌子周围,试图想出一个可以用作网站数据缓存的内存键值存储,您就不能建议它。

它的工作原理...

我们将 Redis 作为服务启动,使用/etc/redis.conf作为配置文件(默认)。

在撰写本文时,EPEL 版本附带的默认设置意味着它在启动时位于本地主机地址(127.0.0.1)和端口6379上:

$ sudo cat /etc/redis.conf | grep "port 6379\|^bind 127.0.0.1"
bind 127.0.0.1
port 6379~

这里的设置还设置了模式(默认受保护),该模式规定了 Redis 在监听其他端口时如何启动:

# By default protected mode is enabled. You should disable it only if
# you are sure you want clients from other hosts to connect to Redis
# even if no authentication is configured, nor a specific set of interfaces
# are explicitly listed using the "bind" directive.
protected-mode yes

明智的选择!

我们知道 Redis 是内存中的,但也有关于它偶尔写入磁盘的事实。

此 DB 转储可以在配置文件的给定目录中查看:

$ sudo ls /var/lib/redis
dump.rdb

除非设置了appendonly值,否则此数据库文件会稍微滞后于运行实例,这意味着如果 Redis 或服务器崩溃,您可能会丢失几秒钟的数据。

您希望数据有多健壮通常取决于应用程序开发人员。如果他们不介意可能会丢失一两秒的数据,这意味着某个值可能不会被缓存或类似的情况,那么您可能不希望承受将所有数据尽快写入磁盘的性能影响。

消息代理和队列(RabbitMQ 示例)

在各种形状和大小的数据库之后,在本节中,我们将看一些非常不同的东西,即一种名为 RabbitMQ 的消息软件。

与 Redis 一样,RabbitMQ 是许多现代 Web 应用程序的重要组成部分,因为它是开源的并且有很好的文档。

但是什么是消息队列?

我知道你会问这个问题,除非你已经知道,如果是这样,那么这一节对你来说可能有点无用。

消息队列通常使用高级消息队列协议AMQP),是消息代理的一部分,用于作为应用程序堆栈的发送和接收消息的软件。

这些消息通常来自可以以不同方式进行交谈和监听的不同组件。消息代理用于促进这些组件之间的对话。

您通常会发现消息代理和队列开始在“解耦”方面进行讨论,这是一种提出应用程序堆栈的不同元素不应该如此依赖彼此的花哨方式。

想象一下,一个 Web 服务器正在与处理数据的应用程序交谈。在旧世界中,Web 服务器会直接与处理应用程序交谈,来回发送消息,同时等待响应。这在扁平设计中可能很好,但您可能会面临诸如前端网站锁定,而后端处理应用程序在处理棘手的任务时被卡住等风险。

而不是直接通信,可以使用消息代理,虽然它依赖于 Web 开发人员编写的代码,不依赖于后端的直接或即时响应,但它有效地解耦了后端应用程序的硬依赖关系(或者根本不需要后端应用程序存在)。

理论上,您可以从队列中删除数据库,替换它,前端网站将一无所知。

您还会经常看到 RabbitMQ(和其他)在分布式和冗余设置的设计中,而不是一个网站与一个处理后端通信,而是几个前端和几个后端只需与队列通信,根据需要接收和处理消息。

准备工作

在本节中,我们将使用我们的 Debian 盒子...只是为了改变一下。

像这样连接到debian1

$ vagrant ssh debian1 -- -L 127.0.0.1:15672:127.0.0.1:15672

注意后续端口以便稍后访问管理界面。

警告:在此设置中,RabbitMQ 默认在所有接口上启动,因此您应确保防火墙使这些端口无法从外部访问。如果您只是在 Vagrant 测试 VM 上工作,那么它应该只设置为本地网络。

从默认存储库安装rabbitmq-server

$ sudo apt install rabbitmq-server -y

如何做...

安装后,Rabbit 应该会自动启动(啊,Debian)。这意味着我们可以立即用ps(以及它作为默认用户运行的rabbitmq)看到正在运行的内容:

$ ps -fu rabbitmq
UID PID PPID C STIME TTY TIME CMD
rabbitmq 5085 1 0 14:25 ? 00:00:00 /bin/sh /usr/sbin/rabbitmq-server
rabbitmq 5094 5085 0 14:25 ? 00:00:00 /bin/sh -e /usr/lib/rabbitmq/bin/rabbitmq-server
rabbitmq 5244 1 0 14:25 ? 00:00:00 /usr/lib/erlang/erts-8.2.1/bin/epmd -daemon
rabbitmq 5307 5094 0 14:25 ? 00:00:02 /usr/lib/erlang/erts-8.2.1/bin/beam -W w -A 64 -P 1048576 -t 5000000 -stbt db -zdbbl 32000 -K true -B i -- -root /usr/lib/erlang
rabbitmq 5417 5307 0 14:25 ? 00:00:00 erl_child_setup 65536
rabbitmq 5442 5417 0 14:25 ? 00:00:00 inet_gethost 4
rabbitmq 5443 5442 0 14:25 ? 00:00:00 inet_gethost 4

从这里,我们可以立即看到 RabbitMQ 使用 Erlang,并且/usr/lib/rabbitmq/bin/rabbitmq-server脚本用于启动服务器。

首先,让我们设置 RabbitMQ 管理界面:

$ sudo rabbitmq-plugins enable rabbitmq_management
The following plugins have been enabled:
 mochiweb
 webmachine
 rabbitmq_web_dispatch
 amqp_client
 rabbitmq_management_agent
 rabbitmq_management

Applying plugin configuration to rabbit@debian1... started 6 plugins.

现在,您应该能够在本地机器上访问此界面。

导航到http://127.0.0.1:15672,您应该看到类似以下的内容:

默认用户名和密码是guest/guest

回到我之前的安全观点,这突显了在安全测试环境中设置和配置软件的必要性,绝对不要在进入实际环境之前使用任何生产数据。

登录后,您应该看到这个:

RabbitMQ 概述页面

通常,我建议从 CLI 开始,但碰巧这是一个很好的可视化 Rabbit 是什么以及它在做什么的方式。

在此屏幕顶部,我们有我们的总数,这是 Rabbit 当前知道的消息的详细信息(现在它是空闲的)。

在节点部分,我们可以立即看到我们有一个名为rabbit@debian1的单个节点,并且我们目前对其使用情况进行了详细说明。

我们还有一个 Rabbit 查找的相关路径列表,包括配置文件(目前不存在)和实际数据库目录。还有有关各种日志文件的信息。

在这个界面上四处点击(尽管您可能会发现它有点稀疏)。

回到命令行,让我们看看如何查询没有启用 Web 界面的远程服务器上的信息。

首先列出队列:

$ sudo rabbitmqctl list_queues
Listing queues ...

这是空的,就像我们在 GUI 上看到的一样(如果您单击了队列选项卡)。

默认情况下,我们有一个vhost,这是对 RabbitMQ 进行分段的一种方法(就像 Apache 和 Nginx 中的 vhost 一样):

$ sudo rabbitmqctl list_vhosts
Listing vhosts ...
/

我们可以查看已配置的用户:

$ sudo rabbitmqctl list_users
Listing users ...
guest [administrator]

我们还可以创建用户:

$ sudo rabbitmqctl add_user exampleUser examplePassword
Creating user "exampleUser" ...

让我们创建一个vhost来配合:

$ sudo rabbitmqctl add_vhost exampleVhost
Creating vhost "exampleVhost" ...

现在让我们让我们的用户访问所述vhost

$ sudo rabbitmqctl set_permissions -p exampleVhost exampleUser ".*" ".*" ".*"
Setting permissions for user "exampleUser" in vhost "exampleVhost" ...

工作原理...

我们所做的大部分内容应该是不言自明的,除了权限行:

$ sudo rabbitmqctl set_permissions -p exampleVhost exampleUser ".*" ".*" ".*"

在这里,我们特别授予configurewriteread权限给我们的用户exampleUser。这意味着在exampleVhost vhost中,我们的用户将完全可以按照自己的意愿进行访问。

这里通过正则表达式授予访问权限,这意味着您可以更精细地控制用户在vhost中可以访问和不能访问的内容。

与 Redis 一样,RabbitMQ 如果没有设置和没有应用程序与其通信,就不会做太多事情。

各种语言都有与 RabbitMQ 进行交互的方式。通常由你公司的开发人员决定他们将如何写入队列,他们将写入哪些队列,一旦完成,他们将如何从这些队列中读取,以及他们将使用什么语言。

遗憾的是,从管理员的角度来看,你很少会涉及到 RabbitMQ 的实际请求和操作。你的大部分工作将围绕着确保安全性(TLS、用户认证等)和确保集群保持一致。

我强烈建议阅读 Rabbit 文档,并尝试一些与 Rabbit 互动的快速入门指南。如果你有开发者的思维,或者你只是喜欢用 Python 脚本进行调试,这可能是深入了解这个消息代理和消息代理的一个好方法。

总结 - 网络服务器、数据库和邮件服务器

在本章中,我故意试图谈论一些目前市场上最流行的技术,包括我知道确实拥有大量用户和市场份额的程序。其中一些软件已经存在了很长时间,接近几十年,但更多的是最近加入的,这在很大程度上归功于 2010 年代后期云计算的爆炸。

当我们谈论网络服务器、邮件服务器和数据库时,它们有各种各样的形状和大小,这意味着很难建议适合所有用例的明智默认值。我唯一能给出的建议,也是普遍适用的,就是不要为了简单而牺牲安全性。(你可能已经读了足够多关于最近几个月和几年的泄露和安全漏洞的报道,你想确保你的名字不会与这样的灾难性事件联系在一起。)

这并不是说这些东西不能有趣。人们用 NoSQL 数据库、消息代理甚至邮件服务器做的奇怪而奇妙的事情对很多人来说都非常有趣,其中可能包括你!

我也不想让你远离自己运行任何这些东西的想法,尤其是现在大部分这些东西都可以从各种云提供商和第三方作为服务提供。这可以是一个很好的学习经验,并且在你自己安装和配置这些东西时,可以教会你某些程序的优点和缺点。

还有一件事要注意,这只是我的观点。

我不喜欢在同一基础设施中混合类似的技术。如果你需要一个 SQL 数据库,并且以前从未在你的基础设施中使用过,那么在决定使用 MariaDB 还是 PostgreSQL 之前,可以考虑一段时间。大多数现代软件只需要某种 SQL 后端,它并不在乎提供它的软件是什么。

你正在安装的 GitLab 服务器?也许你已经决定使用 PostgreSQL。

现在你有一个要设置的 WordPress 实例?再次选择 PostgreSQL。

你需要连接 Icinga2 吗?试试 PostgreSQL。

想象一下,如果你决定在某件事情上使用 MariaDB,另一件事情上使用 MySQL,第三件事情上使用 PostgreSQL,你可能会陷入多大的困境。除非有非常充分的理由选择其中一个,我非常赞成尽可能保持基础设施的简单。这样,当你不得不学习一些 SQL 来正确管理你的数据库时,你只需要担心某个 SQL 提供商的复杂性,而不是三个不同的提供商。

这也是跨领域的,如果你需要为某事设置邮件服务器,我总是更倾向于在 Debian 和 CentOS 机器上都设置 Postfix,这样我就知道在日志和配置指令方面该看哪里,无论我在哪个操作系统上。

事实上,忘掉吧!更进一步,选择一个单一的操作系统进行部署,放弃混合 Debian、CentOS 和 Ubuntu 部署的想法。

从逻辑上讲,这不仅在管理组件时节省了你的理智,而且大大减少了你需要维护的基础设施代码、你需要镜像的存储库,以及你需要订阅的安全邮件列表。

但在你自己的时间?嗯,那取决于你——疯狂地玩,学习。

超级个人偏好时间!

如果你把我绑在椅子上,说我一定要选择我更喜欢的软件,我会说以下的:

  • Postfix > Exim

  • Nginx > Apache

  • MariaDB > PostgreSQL

注意:这是半开玩笑的;不要太认真。

第十二章:故障排除和职场外交

在本章中,我们将研究以下主题:

  • 什么是故障排除?

  • 隔离真正的问题

  • 给出估计并决定下一步

  • 使用ssiftoptcpdump和其他工具解决网络问题

  • 使用curlwgetopenssl解决远程网络问题

  • 使用itoptopvmstat解决本地资源问题

  • 使用pslsofstrace/proc解决服务问题

  • 复制问题以供以后调试

  • 临时解决方案及何时调用它们

  • 处理愤怒的开发人员

  • 处理愤怒的经理

  • 处理愤怒的业主

介绍

在本章中,我们将开始逐渐减少我们所看到的技术方面。我们不会过多地看新的服务和软件,我们所使用的软件大多是我们已经在其他领域涵盖过的工具(只是在这里我们将讨论何时以及如何使用它们)。

本章的主要重点是故障排除,同时也非常强调故障排除的沟通方面。你可能是世界上最优秀的工程师,一个真正的代码耳语者,但如果你不能以其他人能够理解的方式传达你的发现,那么你的能力就毫无意义。

当你排除故障时,不管是在额外的时间还是在火线下,你必须确保你学到的东西得到适当的记录(即使最初只是在一张纸上,以后可以存储在 Confluence 等文档存储中),这样,如果再次发生,不是你的人能够更快地弄清楚发生了什么,并希望更快地解决问题。

在你排除故障的同时,也值得让周围的人知道发生了什么。多年来,我见过各种各样的做法,但如今让人们了解情况的一种更常见的解决方案似乎是通过 Rocket.Chat 或 Slack 频道发布公告和信息。这样,任何人都可以了解情况,即使只是出于一时的兴趣。

是的,有时你在排除故障时,有人希望每五分钟得到一个关于发生了什么的更新。这个人可以是客户、经理、开发人员或业主,但在这种情况下保持冷静,不要感到压力,确保你的回答简洁明了,不要有太多的猜测,这很重要。

技术要求

在本章中,我们所做的一切都可以通过单个节点完成;在这种情况下,我使用的是一个 CentOS 机器。

随时可以使用Vagrantfile来一起玩:

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|

  config.vm.define "centos1" do |centos1|
    centos1.vm.box = "centos/7"
    centos1.vm.network "private_network", ip: "192.168.33.10"
    centos1.vm.hostname = "centos1"
    centos1.vm.box_version = "1804.02"
  end

end

什么是故障排除?

故障排除,或者调试(更常用于特定软件),是试图弄清楚解决方案出了什么问题,或者在事件发生时历史上出了什么问题的行为。那个事件可以是数据中心整个网络崩溃,也可以是弄清楚为什么一个 Docker 容器突然决定像网球一样上下弹跳。

没有完美的软件。如果有的话,我们都会失业,正是因为这个简单的事实陈述,你几乎不可避免地会发现自己在生活中的某个时刻面临一个破损的系统,有人在对你大喊要你修复它。

在这一点之前,你可以尽一切努力,试图确保你永远不会遇到像生产中断这样的事情,包括使用多个环境和通过升级管道测试更改。但我可以保证你永远不可能考虑到一切,正如大多数公司会告诉你的那样,问题也可能发生在生产中。你只能希望当问题发生时,你不会在凌晨两点被叫醒去解决它们,因为你是值班工程师。

如何做…

在排除故障时,有一些重要的第一步要采取:

  1. 喝一杯你喜欢的饮料,或者礼貌地请别人为你倒一杯(如果事件需要立即立即响应)。尽量避免饮酒。我通常选择喝茶。

  2. 确保你知道是什么引起了警报。通常,这将来自监控系统,或者是电话那头慌张的值班工程师。这是下一步的好起点。

  3. 了解问题的影响;例如,这是否意味着网站宕机,没有人能购物,还是系统路由了进来的支持电话宕机?这将帮助你评估问题的重要性。

人们凌晨三点无法打进支持台和一个小城市的交通路由系统宕机是有区别的。令人惊讶的是,有些人会因为问题的规模而感到同样恐慌。

  1. 一旦这些事情确定下来,就开始隔离真正的问题。

隔离真正的问题

当你进行故障排除,或者你面临一个明显的问题,需要你弄清楚发生了什么时,重要的是不要草率下结论。很容易看到一个问题,比如网站宕机,立刻想到,“啊,我以前见过这种情况;那次是数据库行数过多,网站变得缓慢——我会从那里开始查找。”

在计算机领域,闪电确实会击中两次,特别是当根本原因没有得到解决时,但是如果立刻假设你知道问题的原因,这是一种反故障排除的技术,只是基于与过去问题的暂时相似性。

做好准备

在你已经从自己对症状的粗略观察(网站宕机)或者那个支持人员向你烦扰的问题中获得了关于问题的尽可能多的信息后,就是时候开始隔离问题了。

一定要为自己重新创建问题。这是一个重要的步骤,经常被忽视,任何人在调查问题时都应该采取这一步骤。如果你有能力重新创建一个问题,你应该从这里开始,即使只是简单地在浏览器中输入公司的网址并亲自检查网站是否无法加载。这很重要,因为它为你提供了一个起点,你可以在每次进行更改或认为已经解决问题时使用相同的一系列步骤。如果你能确认问题存在,就努力解决这个问题。然后,证明至少对你来说问题已经解决,然后你可以开始让其他人参与这个过程,并要求他们确认相同的问题。

在黑暗中乱刺并不仅会让你有失手的风险,而且也会让你极难确定你所做的改变何时或是否真的产生了影响。

你可能会发现,在为自己重新创建问题时,你会遇到一些尚未与你分享的相关信息。“网站宕机”是一个模糊的说法,但“网站超时”更具信息量,如果你发现网站被某人劫持用来分享猫的照片,那么它可能并没有宕机,但 URL 可能已经被篡改。

操作方法...

一旦问题被重新创建,你就可以开始着手解决问题的核心工作:

  1. 首先,你需要知道哪些设备可能与问题直接或间接相关。例如,如果公司网站宕机,我会立即登录托管网站的系统,同时尝试查找任何为其提供服务的系统,比如消息代理或数据库。

  2. 如果你不知道部署的拓扑结构是什么样子,也找不到内部系统中详细描述布局的文件,最好让其他人参与故障排除过程。

你可能不太愿意在半夜叫醒别人,或者在他们的桌子上打扰他们,但如果你需要花几个小时来逆向工程一个设置,那么绕过这种努力直接去找源头可能是绝对值得考虑的。如果对方是讲道理的,他们可能会提供他们的知识,尽管他们可能会对此感到不悦,你下次见到他们时就得道歉了。

  1. 一旦你确定你已经在脑海中检查了所有可能需要检查的系统,或者更好的是,在记事本上,你应该开始隔离。这取决于你有多幸运,这可能是一个快速或缓慢的过程。

  2. 使用 Web 服务器和网站宕机的例子,你可能会尝试 SSH 到你公司的网站服务器,发现你无法连接。这可能意味着两件事:

  • 有一个妨碍通信的网络问题

  • 服务器宕机

  1. 这些问题可能看起来很广泛,但很容易隔离。下一步是尝试 SSH 到另一个服务器,最好是在同一网络中,如果你可以连接到它,你就缩小了问题可能出现的范围。这表明第一个服务器宕机,或者遇到了个人网络问题。

  2. 假设你无法连接到另一个服务器,你应该尝试另外几个以确保,如果你也无法连接到它们,那么可能是时候让你的网络团队介入了(或者如果你有权利这样做,自己登录交换机或路由器)。

  3. 如果你已经确定是一个或两个服务器出了问题,你可能需要让数据中心的工程师介入(或者如果这是你的工作,自己去),并直接连接到服务器。如果是云解决方案,那么现在是时候打开你可以访问的控制台,以此方式检查明显的问题。

  4. 一旦你进入服务器,最好检查是否可以在服务器本身上重现相同的问题。我们稍后会讨论你可能会这样做的方法。

  5. 如果你按照故障排除步骤工作,并确定本地服务器不是问题所在,那么就是时候向外部工作并测试你尚未检查或可能没有直接控制的解决方案的其他部分了。

人们在故障排除时有不同的技巧,但我喜欢先检查我可以直接访问和控制的东西。对于某些问题,很有可能是由外部因素(比如你的分布式内容交付提供商出现问题)造成的,但在这种情况下,最好尽可能证明外部因素,然后再自己打电话开始责怪别人。

  1. 检查下一个最有可能成为问题源的候选项(可能是 Web 服务器前面的负载均衡器,或者后面的数据库),并继续直到找到一些行为不正常的东西。

给出估计并决定下一步

在你进行故障排除过程中,随时做笔记并记录事件期间的重要时间是个好主意:

  • 事件首次报告时间是什么时候?

  • 你准备开始故障排除的时间是什么时候?

  • 你隔离问题的时间是什么时候?

  • 发现相关信息的时间是什么时候?

  • 等等

另一件需要注意的事情是估计。在很多情况下,高层领导喜欢听到估计,因为这意味着他们可以把估计传达给他们自己的上级或客户。

如果你还不清楚问题的根源,你就不能给出可靠的估计,你应该解释这一点。然而,如果你已经成功隔离了问题,并发现是由于诸如针对数据库运行的存储过程之类的东西引起的,那么在这种情况下可能可以给出一个粗略的估计。由于各种因素,决定下一步可能会很棘手,我们将在这里看到。

做好准备

使用上一个示例,你已经发现你正在查看的网站因为针对数据库运行的存储过程而变慢。你已经确定这个存储过程每周运行一次,并且上个星期日成功运行,花了大约一个小时才完成,然后一切恢复正常,没有触发警报。

然而,现在已经过去一个半小时了,但没有明显迹象表明存储过程已经完成了。你应该记录下你发现的问题,记录下你发现问题的时间,并通知你认为已经找到问题源头的人。

尽量避免使用诸如"肯定是这样"或"我找到问题了"之类的短语,因为这些绝对的说法只会让你看起来愚蠢,并且在它们被证明是误导时可能会让其他人生气。

如何做...

估计可能很容易,也可能非常困难。我不建议凭空捏造数字,因为当你自己设定的截止日期被无视时,这会让你看起来无能。然而,你可以检查历史记录:

  1. 如果你的存储过程正在运行并且需要一段时间,首先要回顾日志,尝试确定最近几次运行花了多长时间。数据越多越好。

如果你有两年的日志,你可以看到存储过程在其生命周期的开始时需要几分钟才能运行,当公司规模较小,数据库规模较小时。然后你可能开始建立一个任务运行时间随着数据库规模增大而变长的情况。你可以通过检查图表中的每次运行来确认这一点,并查看所花时间的增加是否与规模相关。在这一点上,你可能能够得出一个你知道大致会完成任务的数字。

  1. 然而,如果历史日志显示存储过程总是在相同的时间运行相同的时间,那么你可能会开始相信其他原因导致了处理时间的突然增加。你应该注意这一点,并决定下一步该怎么做。

不用说,但在这种情况下,检查特定的过程日志(如果存在)也是必须的,就像阅读和理解脚本或存储过程可能会做的那样。再次强调,如果需要让其他人参与,要做出判断,但不要试图自己承担所有事情,因为这只会导致更大的压力和更多的压力。

在这种情况下,你必须开始考虑影响处理时间的数据库外因素:

  • 最近有没有进行过可能改变存储过程工作方式的工作(检查 Git 日志)?

  • 最近有没有人登录到服务器并进行更改(检查审计日志)?

  • 数据库服务器上是否还有其他正在运行可能影响性能的东西(检查进程信息)?

由你决定这些事情的优先级,并逐个解决。

  1. 回到估计问题:如果你已经确定问题的原因是代码更改,并且需要回滚到早期版本,那么再次提供估计并不是不可能的。你应该知道一个软件包需要多长时间才能构建,以及在服务重新启动之前需要多长时间才能部署到服务器。

  2. 然而,"需要多长时间就需要多长时间"的谚语是真实的,尽管最好不要对那些向你追问答案的人说这句话。让他们更温和地失望,并向他们保证你正在努力解决问题...向他们展示你的笔记。

还有更多...

很多地方都有关于事故期间应该发生什么的时间表,例如:

  • 如果问题在一个小时内仍在进行中,请只通知指定的个人

  • 如果问题持续时间超过一个小时或两个小时,请通知利益相关者

  • 如果问题看起来可能导致系统长时间离线,安排每 30 分钟开一次会议,以便让利益相关者了解最新情况

尽管我在任何地方工作的情况并不一致,但是好的公司都有计划确保每个人都在同一页面,而更好的公司不会邀请故障排除工程师参加这些会议,而是指定一个联络人在工程师工作时获取信息。

使用 ss,iftop,tcpdump 和其他工具解决网络问题

在本文中,我们将列出一些可用于隔离和调试网络问题的工具,其中大多数可以在常见发行版的默认存储库中找到。

这本书中许多(如果不是全部)内容之前已经涵盖过,但是反复使用这些工具是个好主意,因为在故障排除场景中,你会发现自己要做的工作中有 20%是试图记住哪个工具适合解决特定的问题。

准备就绪

在本文中,随时尝试执行列出的一些或所有命令(甚至可以离开脚本并阅读相关的 man 页面)。我们将使用 CentOS 虚拟机。

SSH 到您的 CentOS 虚拟机:

$ vagrant ssh centos1

安装我们将要使用的两个工具:

$ sudo yum install epel-release -y
$ sudo yum install iftop tcpdump -y

如何做...

我们将依次运行我们的工具。

Ping

如果您认为自己遇到了网络问题,ping命令是您可以使用的最古老的工具之一,也是您最好的朋友之一:

  1. 首先确保您的网络正常运行,ping 您的环回地址和您自己的节点 IP。我们将检查本地主机是否正常工作:
$ ping 127.0.0.1
PING 127.0.0.1 (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.044 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.081 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.086 ms
--- 127.0.0.1 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 1999ms
rtt min/avg/max/mdev = 0.044/0.070/0.086/0.019 ms
  1. 然后,检查您的Eth1地址:
$ ping 192.168.33.10
PING 192.168.33.10 (192.168.33.10) 56(84) bytes of data.
64 bytes from 192.168.33.10: icmp_seq=1 ttl=64 time=0.064 ms
64 bytes from 192.168.33.10: icmp_seq=2 ttl=64 time=0.069 ms
64 bytes from 192.168.33.10: icmp_seq=3 ttl=64 time=0.098 ms
--- 192.168.33.10 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2008ms
rtt min/avg/max/mdev = 0.064/0.077/0.098/0.015 ms
  1. 现在您确定您的网络堆栈正常运行,请检查您是否可以与路由器通信。如果您不知道您的路由器 IP,请先获取它:
$ ip route | grep default
default via 10.0.2.2 dev eth0 proto dhcp metric 102
  1. 现在我们将 ping 该 IP:
$ ping 10.0.2.2
PING 10.0.2.2 (10.0.2.2) 56(84) bytes of data.
64 bytes from 10.0.2.2: icmp_seq=1 ttl=64 time=0.473 ms
64 bytes from 10.0.2.2: icmp_seq=2 ttl=64 time=0.861 ms
64 bytes from 10.0.2.2: icmp_seq=3 ttl=64 time=0.451 ms
--- 10.0.2.2 ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2016ms
rtt min/avg/max/mdev = 0.451/0.595/0.861/0.188 ms

太棒了!我们相对确定我们的实际数据包路由是正常的。

  1. 接下来,使用ping命令检查您的 DNS 是否正常工作:
$ ping bbc.co.uk
PING bbc.co.uk (151.101.192.81) 56(84) bytes of data.
64 bytes from 151.101.192.81 (151.101.192.81): icmp_seq=1 ttl=63 time=43.9 ms
64 bytes from 151.101.192.81 (151.101.192.81): icmp_seq=2 ttl=63 time=31.5 ms
64 bytes from 151.101.192.81 (151.101.192.81): icmp_seq=3 ttl=63 time=38.4 ms
--- bbc.co.uk ping statistics ---
3 packets transmitted, 3 received, 0% packet loss, time 2017ms
rtt min/avg/max/mdev = 31.545/37.973/43.910/5.059 ms

很好,所以我们知道我们可以将域名解析为 IP。

如果由于某种原因名称解析无法工作,请尝试检查您的/etc/resolv.conf中是否实际有一个nameserver条目,并尝试 ping 该服务器,以查看您是否实际上可以与其进行通信。 DNS 解析失败可能会产生奇怪的连锁效应(例如在某些默认安装中导致 SSH 连接缓慢)。

Ping 非常好,许多遇到的问题都可以立即追踪到,这要归功于其简单性。

ss

ss非常适合本地端口检查,因为我们迄今为止在本书中多次使用它:

  1. 如果您的计算机上有 Web 服务器,并且您确切知道它应该在端口80上运行,您可能会首先使用fish参数:
$ ss -tuna
Netid State Recv-Q Send-Q Local Address:Port Peer Address:Port 
udp UNCONN 0 0 *:733 *:* 
udp UNCONN 0 0 127.0.0.1:323 *:* 
udp UNCONN 0 0 *:68 *:* 
udp UNCONN 0 0 *:111 *:* 
udp UNCONN 0 0 :::733 :::* 
udp UNCONN 0 0 ::1:323 :::* 
udp UNCONN 0 0 :::111 :::* 
tcp LISTEN 0 128 *:111 *:* 
tcp LISTEN 0 128 *:22 *:* 
tcp LISTEN 0 100 127.0.0.1:25 *:* 
tcp ESTAB 0 0 10.0.2.15:22 10.0.2.2:51224 
tcp LISTEN 0 128 :::111 :::* 
tcp LISTEN 0 128 :::22 :::* 
tcp LISTEN 0 100 ::1:25 :::*

在某些情况下,我们立即执行此操作,只是为了概述主机在 IP 网络方面的活动。

我们可以看到主机上活动和监听的端口,立即排除通常的嫌疑对象(如2225),并且我们可以看到来自远程连接的已连接(ESTAB)会话,例如我的计算机。

如果您想要查看服务名称的最佳猜测,也可以在不带-n 参数的情况下运行ss。我称之为“最佳猜测”,因为ss将只是读取/etc/services文件并将端口与名称匹配,但不能保证 SSH 实际上是在22上运行,而不是在2323或其他端口上运行。

  1. 您还可以使用ss来获取主机在网络方面的快照:
$ ss -s
Total: 194 (kernel 0)
TCP: 7 (estab 1, closed 0, orphaned 0, synrecv 0, timewait 0/0), ports 0

Transport Total IP IPv6
* 0 - - 
RAW 0 0 0 
UDP 7 4 3 
TCP 7 4 3 
INET 14 8 6 
FRAG 0 0 0 

iftop

这是本文中第一个不太可能默认安装的工具;我们有时会在故障排除过程中使用iftop,因为它非常适合可视化。人类倾向于喜欢可视化。我们是简单的生物,有着简单的梦想,而这些梦想通常是色彩鲜艳的,并且包含令人困惑的图表(如果你是我)。

因此,当计算机更喜欢字符串和结构时,我们倾向于喜欢我们正在查看的东西的良好表示,而iftop给了我们这个:

$ sudo iftop

我们在这里生成了一些流量,到三个不同的位置,以展示看到不同来源和通信有多容易:

我们在本书的前面讨论了iftop,但请记住它确实存在!

tcpdump

我们以前使用过的另一个工具是tcpdump;它打印并保存网络流量以供以后调试或即时调试:

$ sudo tcpdump not port 22
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
15:26:48.864239 IP centos1 > server-54-230-129-152.ams50.r.cloudfront.net: ICMP echo request, id 3927, seq 284, length 64
15:26:48.866009 IP centos1.46283 > 10.0.2.3.domain: 32699+ PTR? 152.129.230.54.in-addr.arpa. (45)
15:26:48.899019 IP 10.0.2.3.domain > centos1.46283: 32699 1/0/0 PTR server-54-230-129-152.ams50.r.cloudfront.net. (103)
15:26:48.899678 IP centos1.44944 > 10.0.2.3.domain: 7093+ PTR? 15.2.0.10.in-addr.arpa. (40)
15:26:48.900853 IP 10.0.2.3.domain > centos1.44944: 7093 NXDomain 0/0/0 (40)
15:26:48.903765 IP centos1.37253 > 10.0.2.3.domain: 25988+ PTR? 3.2.0.10.in-addr.arpa. (39)
15:26:48.911352 IP server-54-230-129-152.ams50.r.cloudfront.net > centos1: ICMP echo reply, id 3927, seq 284, length 64
15:26:48.964402 IP 10.0.2.3.domain > centos1.37253: 25988 NXDomain 0/0/0 (39)
15:26:49.869214 IP centos1 > server-54-230-129-152.ams50.r.cloudfront.net: ICMP echo request, id 3927, seq 285, length 64
15:26:49.909387 IP server-54-230-129-152.ams50.r.cloudfront.net > centos1: ICMP echo reply, id 3927, seq 285, length 64
15:26:50.875756 IP centos1 > server-54-230-129-152.ams50.r.cloudfront.net: ICMP echo request, id 3927, seq 286, length 64
15:26:50.913753 IP server-54-230-129-152.ams50.r.cloudfront.net > centos1: ICMP echo reply, id 3927, seq 286, length 64
15:26:51.881191 IP centos1 > server-54-230-129-152.ams50.r.cloudfront.net: ICMP echo request, id 3927, seq 287, length 64
15:26:51.927357 IP server-54-230-129-152.ams50.r.cloudfront.net > centos1: ICMP echo reply, id 3927, seq 287, length 64
^C
14 packets captured
14 packets received by filter
0 packets dropped by kernel

请注意前面的例子,我特意运行了tcpdump,但排除了 SSH 流量:

$ sudo tcpdump not port 22

因此,我得到了关于来自我的第二个会话的 ping 请求的大量信息,但是关键是,我避免了 SSH 流量的噪音。(这将会非常嘈杂,因为每次有东西打印到我的会话时,那就是 SSH 流量,这意味着它会不断增长。)

尽管如果我们确实想要调试 SSH,这是可能的,并且相当容易,因为tcpdump可以让您将输出到文件:

$ sudo tcpdump port 22 -w ssh-traffic.pcap
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
^C3 packets captured
5 packets received by filter
0 packets dropped by kernel

然后可以使用 Wireshark 等工具打开并阅读此文件:www.wireshark.org/

使用 cURL、wget 和 OpenSSL 解决远程网络问题

如果您得出结论,在您的大量故障排除会话期间,您面临的问题是以下之一,那么这些工具中的一些可能适合您:

  • 与远程站点的连接有关

  • 与远程站点相关的证书问题

准备就绪

SSH 到您的 CentOS VM:

$ vagrant ssh centos1

此时,cURL 和 OpenSSL 应该是普遍存在的,您可以期望系统默认安装它们。

Wget 有点难以捉摸,并且很少作为默认安装的一部分,但很容易安装:

$ sudo yum install wget -y

目前,OpenSSL 随处可见,但由于一系列备受关注的漏洞,咳咳心脏出血咳咳,它已经被分叉了几次以产生近似值,尽管希望是有限数量的问题。其中最著名的是 OpenBSD 团队的LibreSSL,虽然它是该特定操作系统的默认选项,但在 CentOS 或 Ubuntu 中不太可能成为默认选项。

在这个示例中,我们还将打破互联网一些关键领域的信任(尽管只是在您的 VM 本地),不用担心。要做到这一点,您可以运行以下命令:

$ sudo mv /etc/pki/ /etc/pki-backup

如何做...

我们将运行这三个程序,包括在我们进行时如何使用它们的示例。

cURL

如果您曾经使用过任何类型的开源操作系统,很有可能 cURL 已经与其捆绑在一起,因此您将在许可协议页面下找到其许可协议(例如游戏机、智能汽车和冰箱通常都包含 cURL 的构建)。但是,它通常总是可以在命令行中使用,因此它是许多管理员的最爱。这在一定程度上是因为 cURL 具有一个广泛(并且高度详细)的退出代码列表:

  1. 例如,对 BBC 进行 cURL 操作的结果如下:
$ curl bbc.co.uk
$ echo $?
0

curl命令成功了,所以我们得到了0退出代码,这几乎普遍意味着 OK。

我们可以尝试curl一个非法的 URL:

$ curl bbc.co.uks
curl: (6) Could not resolve host: bbc.co.uks; Unknown error
$ echo $?
6

在这里,我们得到了一个6退出代码。这在 cURL 手册中定义如下:

 6      Couldn't resolve host. The given remote host was not resolved.
  1. 另一个常用的标志是-I
$ curl -I bbc.co.uk
HTTP/1.1 301 Moved Permanently
Server: Varnish
Retry-After: 0
Content-Length: 0
Accept-Ranges: bytes
Date: Sun, 09 Dec 2018 15:25:09 GMT
Via: 1.1 varnish
Connection: close
X-Served-By: cache-lcy19221-LCY
X-Cache: MISS
X-Cache-Hits: 0
X-Timer: S1544369109.457379,VS0,VE0
Location: http://www.bbc.co.uk/
cache-control: public, max-age=3600

你所看到的是我们试图访问的网站的标头。瞬间,这告诉我们一些事情:

  • 我们实际上得到了301,将我们重定向到不同的地址

  • 我们正在访问一个varnish服务器(一个流行的缓存服务器)

如果我们尝试通过 HTTPS 访问 BBC,并使用www子域,我们会得到以下退出代码:

$ curl -I https://www.bbc.co.uk
curl: (77) Problem with the SSL CA cert (path? access rights?)

这是至关重要的,因为 cURL 没有访问证书颁发机构CA),因为我们已经在本章开头移动了系统上唯一的授权机构。幸运的是,它甚至会给你退出代码,并在输出中详细说明它认为问题所在。

  1. 这带我们来到另一个 cURL 选项,-k
$ curl -k -I https://www.bbc.co.uk
HTTP/1.1 200 OK
Content-Type: text/html; charset=utf-8
ETag: W/"48dfc-dDZDVBqnqFbBCKLot3DWVM1tjvM"
X-Frame-Options: SAMEORIGIN
<SNIP>
X-Cache-Hits: 513
X-Cache-Age: 90
Cache-Control: private, max-age=0, must-revalidate
Vary: Accept-Encoding, X-CDN, X-BBC-Edge-Scheme

在这里请注意,尽管没有访问 CA 来验证网站的合法性,我们得到了200(OK)的响应。-k用于设置不安全标志,请参阅 cURL 文档的以下部分(可在curl.haxx.se/docs/manpage.html找到):

-k,--insecure

(TLS)默认情况下,curl 进行的每个 SSL 连接都经过验证,以确保安全。此选项允许 curl 继续运行,即使服务器连接在其他情况下被认为是不安全的。

显然,使用-k并不是一个明智、理想或实际的日常使用方法。相反,您应该确保您的 CA 是最新的,并且您要连接的站点不是欺诈性的。

  1. 现在可以随意替换您的 CA 证书:
$ sudo mv /etc/pki-backup/ /etc/pki/
  1. 您还可以使用curl通过使用-o选项下载远程文件(在替换了 CA 证书之后,否则会出现证书错误):
$ curl https://www.bbc.co.uk -o index.html
 % Total % Received % Xferd Average Speed Time Time Time Current
 Dload Upload Total Spent Left Speed
100 291k 100 291k 0 0 867k 0 --:--:-- --:--:-- --:--:-- 871k
$ ls -lh
total 292K
-rw-rw-r--. 1 vagrant vagrant 292K Dec 9 15:33 index.html

在这里,我们只是拉取了整个页面并将其保存在本地的index.html中。

cURL 还经常用于故障排除和调试,以与 REST API 进行通信;这是因为,从命令行,工程师或管理员可以轻松地制作自定义请求并查看来自 Web 服务器的输出。

Wget

与 cURL 一样,Wget 是一个与 Web 服务器通信的工具。

默认情况下,输出相当冗长,并会准确告诉您发生了什么:

$ wget bbc.co.uk
--2018-12-09 15:46:15-- http://bbc.co.uk/
Resolving bbc.co.uk (bbc.co.uk)... 151.101.128.81, 151.101.0.81, 151.101.64.81, ...
Connecting to bbc.co.uk (bbc.co.uk)|151.101.128.81|:80... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: http://www.bbc.co.uk/ [following]
--2018-12-09 15:46:15-- http://www.bbc.co.uk/
Resolving www.bbc.co.uk (www.bbc.co.uk)... 212.58.249.215, 212.58.244.27
Connecting to www.bbc.co.uk (www.bbc.co.uk)|212.58.249.215|:80... connected.
HTTP request sent, awaiting response... 301 Moved Permanently
Location: https://www.bbc.co.uk/ [following]
--2018-12-09 15:46:15-- https://www.bbc.co.uk/
Connecting to www.bbc.co.uk (www.bbc.co.uk)|212.58.249.215|:443... connected.
HTTP request sent, awaiting response... 200 OK
Length: 298285 (291K) [text/html]
Saving to: 'index.html.1'

100%[===========================================================>] 298,285 --.-K/s in 0.1s 

2018-12-09 15:46:15 (2.66 MB/s) - 'index.html.1' saved [298285/298285]

在这里,我们可以看到以下内容:

  • wget将网站解析为 IP 地址

  • 连接并获得301告诉它网站已经移动了

  • 跟随网站到http://www.bbc.co.uk

  • 获得另一个301,告诉客户端使用 HTTPS

  • 跟随网站到https://www.bbc.co.uk

  • 最后,连接,获得200,并立即下载 index.html(保存为index.html.1,因为我们已经有一个来自curl的文件)

从中我们可以推断,如果 BBC 只是取消了其中一个重定向(直接指向 HTTPS),它可以为每个请求节省毫秒级的时间,我们还了解到 wget 的默认行为是将内容保存在本地(而不是像 cURL 那样将内容输出到 stdout)。

wget还具有一个“不安全”标志,即--no-check-certificate,尽管我不想在本书中过多地谈论跳过证书检查(因为它们很重要)。

wget被发现非常适合从网站下载多个文件,操纵我们正在下载的内容,并对内容运行校验和检查。就我个人而言,我认为这些标志比 cURL 更直观,而且在故障排除时,下载文件到本地的默认行为对我来说非常有用。

OpenSSL

在进行证书检查时,没有比 OpenSSL 更好的工具(目前)。

用于创建证书和整个 CA,OpenSSL 也可以成为您解决证书问题的出色工具。

例如,如果您想快速检查网站的证书链,可以使用s_client

$ openssl s_client -quiet -connect bbc.co.uk:443 
depth=2 C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA
verify return:1
depth=1 C = BE, O = GlobalSign nv-sa, CN = GlobalSign Organization Validation CA - SHA256 - G2
verify return:1
depth=0 C = GB, ST = London, L = London, O = British Broadcasting Corporation, CN = www.bbc.com
verify return:1

在这里,我们可以看到根 CA(GlobalSign 根 CA),然后是GlobalSign 组织验证 CA,最后是英国广播公司的证书。它还向我们显示了站点的通用名称CN)(www.bbc.com)。

这实际上很有趣,因为我们在这样做时没有跟随重定向,所以我们实际上返回的是bbc.co.uk而不是www.bbc.co.uk使用的证书,看起来是这样的:

$ openssl s_client -quiet -connect www.bbc.co.uk:443 
depth=2 C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA
verify return:1
depth=1 C = BE, O = GlobalSign nv-sa, CN = GlobalSign Organization Validation CA - SHA256 - G2
verify return:1
depth=0 C = GB, ST = London, L = London, O = British Broadcasting Corporation, CN = *.bbc.co.uk
verify return:1

如果您想要证书链中实际的文本表示,可以打印这些:

$ openssl s_client -showcerts -connect www.bbc.co.uk:443 
CONNECTED(00000003)
depth=2 C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA
verify return:1
depth=1 C = BE, O = GlobalSign nv-sa, CN = GlobalSign Organization Validation CA - SHA256 - G2
verify return:1
depth=0 C = GB, ST = London, L = London, O = British Broadcasting Corporation, CN = *.bbc.co.uk
verify return:1
---
Certificate chain
 0 s:/C=GB/ST=London/L=London/O=British Broadcasting Corporation/CN=*.bbc.co.uk
 i:/C=BE/O=GlobalSign nv-sa/CN=GlobalSign Organization Validation CA - SHA256 - G2
-----BEGIN CERTIFICATE-----
MIIHDDCCBfSgAwIBAgIMRXeRavSdIQuVZRucMA0GCSqGSIb3DQEBCwUAMGYxCzAJ
BgNVBAYTAkJFMRkwFwYDVQQKExBHbG9iYWxTaWduIG52LXNhMTwwOgYDVQQDEzNH
<SNIP>
MNHQq0dFAyAa4lcxMjGe/Lfez46BoYQUoQNn8oFv5/xsSI5U3cuxPnKy0ilj1jfc
sDEmTARcxkQrFsFlt7mnmMmCVgEU6ywlqSuR7xg6RLo=
-----END CERTIFICATE-----
 1 s:/C=BE/O=GlobalSign nv-sa/CN=GlobalSign Organization Validation CA - SHA256 - G2
 i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA
-----BEGIN CERTIFICATE-----
MIIEaTCCA1GgAwIBAgILBAAAAAABRE7wQkcwDQYJKoZIhvcNAQELBQAwVzELMAkG
A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
<SNIP>
SOlCdjSXVWkkDoPWoC209fN5ikkodBpBocLTJIg1MGCUF7ThBCIxPTsvFwayuJ2G
K1pp74P1S8SqtCr4fKGxhZSM9AyHDPSsQPhZSZg=
-----END CERTIFICATE-----
---
Server certificate
subject=/C=GB/ST=London/L=London/O=British Broadcasting Corporation/CN=*.bbc.co.uk
issuer=/C=BE/O=GlobalSign nv-sa/CN=GlobalSign Organization Validation CA - SHA256 - G2
---
No client certificate CA names sent
Peer signing digest: SHA256
<SNIP>
---

首先,我们获得 BBC 证书的文本表示,然后是中间 CA 的文本表示。

如果我们想要测试并确保使用了安全协议,我们可以通过简单的标志来做到:

  1. 首先,我们将检查不安全的sslv3是否已启用:
$ openssl s_client -showcerts -connect bbc.co.uk:443 -ssl3
CONNECTED(00000003)
140015333689232:error:14094410:SSL routines:ssl3_read_bytes:sslv3 alert handshake failure:s3_pkt.c:1493:SSL alert number 40
140015333689232:error:1409E0E5:SSL routines:ssl3_write_bytes:ssl handshake failure:s3_pkt.c:659:
---
no peer certificate available
---
No client certificate CA names sent
---
SSL handshake has read 7 bytes and written 0 bytes
---
New, (NONE), Cipher is (NONE)
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
SSL-Session:
 Protocol : SSLv3
 Cipher : 0000
 Session-ID: 
 Session-ID-ctx: 
 Master-Key: 
 Key-Arg : None
 Krb5 Principal: None
 PSK identity: None
 PSK identity hint: None
 Start Time: 1544372823
 Timeout : 7200 (sec)
 Verify return code: 0 (ok)
---

这不起作用(幸好)。

  1. 现在,我们将检查TLS1.2支持:
$ openssl s_client -showcerts -connect bbc.co.uk:443 -tls1_2
CONNECTED(00000003)
depth=2 C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA
verify return:1
depth=1 C = BE, O = GlobalSign nv-sa, CN = GlobalSign Organization Validation CA - SHA256 - G2
verify return:1
depth=0 C = GB, ST = London, L = London, O = British Broadcasting Corporation, CN = www.bbc.com
verify return:1
---
Certificate chain
 0 s:/C=GB/ST=London/L=London/O=British Broadcasting Corporation/CN=www.bbc.com
 i:/C=BE/O=GlobalSign nv-sa/CN=GlobalSign Organization Validation CA - SHA256 - G2
-----BEGIN CERTIFICATE-----
MIIGnDCCBYSgAwIBAgIMIrGYrFe1HwATfmJWMA0GCSqGSIb3DQEBCwUAMGYxCzAJ
Y2FjZXJ0L2dzb3JnYW5pemF0aW9udmFsc2hhMmcycjEuY3J0MD8GCCsGAQUFBzAB
<SNIP>
J+k3TBG221H4c3ahePIfp7IzijJhdb7jZ21HHMSbJu4LN+C7Z3QuCQDnCJIGO3lr
YT7jN6sjN7FQXGEk+P0UNg==
-----END CERTIFICATE-----
 1 s:/C=BE/O=GlobalSign nv-sa/CN=GlobalSign Organization Validation CA - SHA256 - G2
 i:/C=BE/O=GlobalSign nv-sa/OU=Root CA/CN=GlobalSign Root CA
-----BEGIN CERTIFICATE-----
MIIEaTCCA1GgAwIBAgILBAAAAAABRE7wQkcwDQYJKoZIhvcNAQELBQAwVzELMAkG
A1UEBhMCQkUxGTAXBgNVBAoTEEdsb2JhbFNpZ24gbnYtc2ExEDAOBgNVBAsTB1Jv
<SNIP>
SOlCdjSXVWkkDoPWoC209fN5ikkodBpBocLTJIg1MGCUF7ThBCIxPTsvFwayuJ2G
K1pp74P1S8SqtCr4fKGxhZSM9AyHDPSsQPhZSZg=
-----END CERTIFICATE-----
---
Server certificate
subject=/C=GB/ST=London/L=London/O=British Broadcasting Corporation/CN=www.bbc.com
issuer=/C=BE/O=GlobalSign nv-sa/CN=GlobalSign Organization Validation CA - SHA256 - G2
---
No client certificate CA names sent
Peer signing digest: SHA512
Server Temp Key: ECDH, P-256, 256 bits
---
SSL handshake has read 3486 bytes and written 415 bytes
---
New, TLSv1/SSLv3, Cipher is ECDHE-RSA-AES128-GCM-SHA256
Server public key is 2048 bit
Secure Renegotiation IS supported
Compression: NONE
<SNIP>
---

哦哦!所以,我们已经确定我们可以与bbc.co.uk通信,并且我们看到的证书是有效的。但是如果我们想要关于这些证书的信息呢?

我们知道我们正在访问bbc.co.uk,但证书 CN 是www.bbc.com;逻辑上,这应该导致浏览器中的证书错误,那为什么没有呢?

  1. 让我们来检查一下!
$ openssl s_client -connect bbc.co.uk:443 | openssl x509 -text | grep DNS
depth=2 C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA
verify return:1
depth=1 C = BE, O = GlobalSign nv-sa, CN = GlobalSign Organization Validation CA - SHA256 - G2
verify return:1
depth=0 C = GB, ST = London, L = London, O = British Broadcasting Corporation, CN = www.bbc.com
verify return:1
 DNS:www.bbc.com, DNS:fig.bbc.co.uk, DNS:bbc.co.uk, DNS:www.bbc.co.uk, DNS:news.bbc.co.uk, DNS:m.bbc.co.uk, DNS:m.bbc.com, DNS:bbc.com

在这里,我们将我们的s_client命令的输出再次传输到 OpenSSL。然后我们使用x509(证书管理工具)来输出证书的文本信息(从之前看到的那些文本中解码出来),然后我们使用 grep 来查找 DNS。

通常,DNS 位于这个标题下:

            X509v3 Subject Alternative Name: 
                DNS:www.bbc.com, DNS:fig.bbc.co.uk, DNS:bbc.co.uk, DNS:www.bbc.co.uk, DNS:news.bbc.co.uk, DNS:m.bbc.co.uk, DNS:m.bbc.com, DNS:bbc.com

主题备用名称SANs)是此证书可以覆盖的备用名称。其中一个名称是bbc.co.uk,另一个是www.bbc.co.uk。还有一个news和一对移动m)条目(可能是出于历史原因)。

OpenSSL 很酷,不是吗?

...当它没有引起大规模恐慌时。

使用 iotop,top 和 vmstat 解决本地资源问题

同样,这些是我们之前肯定已经涵盖过的工具,但在解决本地问题时可能会非常有用,特别是围绕资源的问题。

做好准备

SSH 到你的 CentOS VM:

$ vagrant ssh centos1

安装适当的软件包:

$ sudo yum install iotop -y

如何做...

我们将简要介绍这些工具,但希望你已经从本书的其他部分中掌握了它们的基本用法。

iotop

使用以下命令调用,iotop显示系统上的 I/O 使用情况,按进程:

$ sudo iotop

在我们的系统上,这意味着我们几乎看不到任何活动(因为默认情况下 VM 并没有做太多事情):

ps一样,括号中的线程(例如"[kthrotld]")表示内核线程。

如果我们想要看到 I/O 的操作,我们可以在另一个会话中执行以下操作:

$ fallocate -l 2G examplefile
$ rsync -z -P --bwlimit=2M examplefile examplefile-copied

我们正在特定地压缩和带宽,限制传输前面的文件。我们还有一个-P 在那里,但那主要是为了让你看到发生的事情。

在我们的第一个会话(iotop)中,我们应该看到 rsync 命令在执行:

请注意,最顶部的一行是我们的命令,并显示我们特定的DISK WRITEs为 95.21 M/s。

top

你可能已经使用了 top 很多次,以至于厌倦了它,你可能已经知道它有多么有用,尽管它的调用很简单。

$ top

在 UNIX 系统中几乎是通用的,top 可以让你近乎实时地了解系统上的进程使用情况:

在故障排除方面,top 通常是不可或缺的。不仅可以轻松查看进程、内存和 CPU 信息,top 的工作占用空间极小,这意味着它可能在看似无限争用的系统上工作。

即使你更喜欢顶级替代方案,比如htop或者 glances,也不能保证这些程序在你接触的每个系统上都可用。因此,熟悉 top 是个好主意,然后在确保你可以正确使用它之后再使用替代方案(或者至少可以勉强使用)。

vmstat

在非常争用的系统上,或者你可能想要输出系统信息以供以后相关和调试时,vmstat 是理想的。它可以给你更全面的系统概览,将周期性的值存储到文件中会很方便,之后你可以随后检查:

  1. 当单独运行时,它也可以定期打印到屏幕上:
$ vmstat -t 3
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- -----timestamp-----
 r b swpd free buff cache si so bi bo in cs us sy id wa st UTC
 2 0 0 15072 164 419900 0 0 1 12 11 11 0 0 100 0 0 2018-12-11 15:40:46
 0 0 0 15084 164 419900 0 0 0 0 15 12 0 0 100 0 0 2018-12-11 15:40:49
 0 0 0 15084 164 419900 0 0 0 0 11 9 0 0 100 0 0 2018-12-11 15:40:52
 0 0 0 15084 164 419900 0 0 0 0 11 10 0 0 100 0 0 2018-12-11 15:40:55
 0 0 0 15084 164 419900 0 0 0 0 10 8 0 0 100 0 0 2018-12-11 15:40:58
 0 0 0 15084 164 419900 0 0 0 0 15 12 0 0 100 0 0 2018-12-11 15:41:01
...
  1. 将输出到文件同样简单;只需重定向stdout
$ vmstat -t 3 > vmstats
^C
$ cat vmstats 
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu----- -----timestamp-----
 r b swpd free buff cache si so bi bo in cs us sy id wa st UTC
 1 0 0 11848 164 423132 0 0 1 12 11 11 0 0 100 0 0 2018-12-11 15:43:35
 0 0 0 11856 164 423136 0 0 0 0 17 12 0 0 100 0 0 2018-12-11 15:43:38
 0 0 0 11856 164 423136 0 0 0 1 15 19 0 0 100 0 0 2018-12-11 15:43:41
 0 0 0 11856 164 423136 0 0 0 0 14 11 0 0 100 0 0 2018-12-11 15:43:44

使用 ps,lsof,Strace 和/proc 来解决服务问题

pslsofstrace/proc在紧急情况下进行故障排除时都是有价值的工具。它们可以显示一些如此明显的东西,以至于你在一百万年内都不会想到去寻找它,而且它们也可以比你自己做得更快地完成某些任务。

strace可能是这些工具中最容易被忽视的,而且很容易学会,但它应该始终在你的脑海中用于程序调试。

就我个人而言,我经常忘记strace,直到有人走过来问我为什么我要以最困难的方式调试某些东西,然后提醒我strace是存在的。

准备工作

连接到你的 CentOS VM:

$ vagrant ssh centos1

为本节安装lsof(其他应该已经安装):

$ sudo yum install lsof -y

如何做到...

依次运行我们的每个命令,让我们快速了解一下ps

ps

ps,用于显示系统上当前进程的快照,是许多管理员的首选,理由充分:

$ ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Dec09 ? 00:00:03 /usr/lib/systemd/systemd --switched-root --system --d
root 2 0 0 Dec09 ? 00:00:00 [kthreadd]
root 3 2 0 Dec09 ? 00:00:01 [ksoftirqd/0]
root 5 2 0 Dec09 ? 00:00:00 [kworker/0:0H]
root 7 2 0 Dec09 ? 00:00:00 [migration/0]
root 8 2 0 Dec09 ? 00:00:00 [rcu_bh]
root 9 2 0 Dec09 ? 00:00:01 [rcu_sched]
<SNIP>
root 2690 2 0 15:28 ? 00:00:01 [kworker/0:1]
postfix 2756 1065 0 15:42 ? 00:00:00 pickup -l -t unix -u
root 2759 2 0 15:43 ? 00:00:00 [kworker/0:2]
root 2783 2 0 15:48 ? 00:00:00 [kworker/0:0]
vagrant 2800 2636 0 15:50 pts/0 00:00:00 ps -ef

如前所述,ps默认还包括内核线程(用方括号括起来的线程)。

经常比精心制作的ps命令更有用的是,我只看到人们通过其他命令将输出通过管道传递来完成他们需要的功能。在野外经常看到这样的命令:

$ ps aux | grep rsysl | grep -v grep | column -t -o, | cut -f1,9,10,11 -d","
root,Dec09,0:22,/usr/sbin/rsyslogd

这不一定是一件坏事,通常只是作为一个快速查询,而不是你真的会放在脚本中的东西。

大提示,没有人(嗯...几乎没有人)会像在教科书中打印的那样编写命令。往往情况下,制作命令的人会使用主要命令得到他们想要的东西的一个近似值,然后通过一系列其他命令来得到他们真正需要的输出。我们都(嗯...几乎都)这样做。

我的意思是,当你在解决问题时无法记住确切的ps语法时,不要担心;尽快做你能做的事情。

lsof

lsof是一个非常令人困惑的工具,帮助部分很混乱。简而言之,它用于列出系统上打开的文件,但是原始运行(不带参数),会得到大量信息。

我经常看到它的最常见用法是这样的:

$ lsof | head -n1; sudo lsof | grep "$(pidof rsyslogd)" | grep -v "socket\|lib64\|dev\|rs:main\|in:imjour" | grep REG
COMMAND PID TID USER FD TYPE DEVICE SIZE/OFF NODE NAME
rsyslogd 861 root txt REG 253,0 663872 34070960 /usr/sbin/rsyslogd
rsyslogd 861 root mem REG 0,19 4194304 7276 /run/log/journal/deb74f074ec9471f91c6c5dd7370484f/system.journal
rsyslogd 861 root 4w REG 253,0 182727 100834552 /var/log/messages
rsyslogd 861 root 5r REG 0,19 4194304 7276 /run/log/journal/deb74f074ec9471f91c6c5dd7370484f/system.journal
rsyslogd 861 root 6w REG 253,0 12423 100834553 /var/log/secure
rsyslogd 861 root 7w REG 253,0 15083 100664480 /var/log/cron
rsyslogd 861 root 9w REG 253,0 392 100834554 /var/log/maillog

这很令人困惑,所以让我们来详细分析一下:

lsof | head -n1;

这是一个愚蠢的技巧,确保你的输出屏幕上有标题行;可以说这浪费了时钟周期,因为我们运行lsof两次(一次只是为了获取第一行),但谁在计数呢?

分号表示命令的结束:

sudo lsof | grep "$(pidof rsyslogd)"

现在,我们再次使用lsof,只是这次我们专门筛选出与rsyslogd的进程 ID 匹配的行。在这里使用$和括号会调用一个子 shell,返回运行命令的值:

$ pidof rsyslogd
861

最后,我们通过grep -v运行输出,返回一个字符串列表。这些通常是由运行命令的人逐渐构建的,当他们找出他们不需要的条目时。最后,我们明确地使用grep来删除我们不感兴趣的其他条目(比如DIR,表示directory):

grep -v "socket\|lib64\|dev\|rs:main\|in:imjour" | grep REG

看起来很愚蠢和冗长,但它有效。

Strace

strace,一个专门用于跟踪系统调用(和信号)的程序,经常被遗忘,但非常强大。如果你想实时了解程序在做什么,你可以使用strace

乍一看,它可能会非常令人困惑:

$ sudo strace -p $(pidof NetworkManager)
strace: Process 550 attached
restart_syscall(<... resuming interrupted poll ...>) = 1
epoll_wait(14, [], 1, 0) = 0
poll([{fd=11, events=POLLIN}], 1, 0) = 1 ([{fd=11, revents=POLLIN}])
read(11, "\2\0\0\0\200\0\0\0G\10\0\0\20\0\0\00054\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 2048) = 128
poll([{fd=11, events=POLLIN}], 1, 0) = 0 (Timeout)
poll([{fd=3, events=POLLIN}, {fd=6, events=POLLIN}, {fd=7, events=POLLIN|POLLPRI}, {fd=11, events=POLLIN}, {fd=12, events=POLLIN}, {fd=14, events=POLLIN}, {fd=15, events=POLLIN}], 7, 41395) = 1 ([{fd=11, revents=POLLIN}])
epoll_wait(14, [], 1, 0) 
...

我们在这里看到的是我连接到一个盒子时触发的事件(NetworkManager是我们正在跟踪的进程)。

我们可以看到有一些系统调用,主要是epoll_waitpoll

这些调用的 man 页面列出了以下内容:

  • epoll_waitepoll_pwait:等待epoll文件描述符上的 I/O 事件

  • pollppoll:等待文件描述符上的某个事件

当你不是 C 或内核黑客时,这可能不会太有帮助,但通过查阅手册页,它们可以指引你找到问题的源头。

通常情况下,当程序被锁定并且您不确定原因时(通常是在检查日志并发现其中没有有用信息后),strace是一个很好的工具。您可以在程序运行时连接到程序,等待(或导致)锁定,然后找到它最后尝试执行的几个操作。

strace的另一个有用用途是查找程序正在打开的文件:

$ strace cat testfile 2>&1 | grep open
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
open("testfile", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 3
open("/usr/share/locale/en_GB.UTF-8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en_GB.utf8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en_GB/LC_MESSAGES/libc.mo", O_RDONLY) = 3
open("/usr/share/locale/en.UTF-8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en.utf8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)
open("/usr/share/locale/en/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (No such file or directory)

在这里,我们运行了cat testfile,但是使用strace作为第一个命令。

然后我们可以看到(通过使用open进行 grep)在读取我们的文件时cat尝试使用的文件,我们还可以看到当它确定文件不存在时的响应。

strace默认会打印到stderr,因此我们在命令中使用2>&1stderr重定向到stdout

/proc

再次,您可能已经厌倦了关于/proc的阅读,但这是一个/proc可以非常有用的现实例子。

VM 对于熵来说并不理想,因此通常最好确定您的系统可以生成多少随机数据。您会发现熵的缺乏会严重阻碍需要执行诸如加密文件之类的操作的进程。

使用/proc,我们可以确定当前可用于我们系统的熵:

$ cat /proc/sys/kernel/random/entropy_avail 
874

这是/proc可以非常有用的一个很好的例子。但它还有什么其他用途呢?

我们可以使用/proc来快速计算出我们的机器有多少个 CPU 核心:

$ cat /proc/cpuinfo | grep "core id"
core id : 0

我们的机器只有一个 CPU 核心。网络统计信息呢?

在这里,我们使用/proc来检查与我们系统相关的 TCP 统计信息,当前时间点上:

$ cat /proc/net/snmp | grep Tcp
Tcp: RtoAlgorithm RtoMin RtoMax MaxConn ActiveOpens PassiveOpens AttemptFails EstabResets CurrEstab InSegs OutSegs RetransSegs InErrs OutRsts InCsumErrors
Tcp: 1 200 120000 -1 117 5 0 5 1 19756 13690 2 0 97 0

是的,通常有更好更快的方法来获取这些信息,但重要的是要意识到,您通常从第一手获得的大部分信息实际上是从"进程信息伪文件系统"中获得的。

复制问题以供以后调试

在本节中,我们将讨论复制文件以供以后调试。经常情况下,我们发现自己在出现故障导致停机的系统上。在这些情况下,工程师们的动力可能是无论问题是什么都要让系统重新运行。

在故障排除步骤中,您可能会发现/var/log已经填满,并且已经占用了剩余的磁盘空间。在这些情况下,很容易简单地删除有问题的日志文件并重新启动损坏的守护程序。

您应该这样做,但不要立即这样做。首先,您需要确保将文件保存以供以后使用,以便问题不再发生。

做好准备

在这个例子中,我们将在/var/log/中玩弄一些日志文件。我们将再次使用我们的 CentOS VM。

如何做到...

快速查看/var/log/中的 messages 文件:

$ ls -lh /var/log/messages
-rw-------. 1 root root 179K Dec 11 16:18 /var/log/messages

它不是很大,但如果您的磁盘非常小,这可能是一个巨大的问题。

假设您在/var/log分区上完全没有空间了,会发生什么?

让我们回顾一下一个示例 VM 的分区布局,注意缺少专用的'/var'分区:

$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/VolGroup00-LogVol00 38G 4.8G 33G 13% /
devtmpfs 235M 0 235M 0% /dev
tmpfs 244M 0 244M 0% /dev/shm
tmpfs 244M 4.5M 240M 2% /run
tmpfs 244M 0 244M 0% /sys/fs/cgroup
/dev/sda2 1014M 63M 952M 7% /boot
tmpfs 49M 0 49M 0% /run/user/1000

前面问题的快速答案如下:

  • 如果/var/log分区与/上的分区相同(就像这里一样),那么您将会遇到问题,甚至可能因为空间不足而无法重新启动系统。/var/log分区可能会占用所有可用的磁盘空间,甚至是关键服务所需的空间,比如 SSH。

  • 如果/var/log分区是独立的分区(它确实应该是这样),那么您将无法再记录任何日志。虽然这可能不会导致系统崩溃,但如果您需要保留日志以进行调试或审计(并且没有将它们传送到其他地方,比如 Elastic Stack 集群),这仍然会导致问题。

在第一个问题的情况下,您基本上必须接受使用几种突然的技巧(或者让数据中心自己来看一下)重新启动该框。

在第二个问题的情况下,您需要做一些事情:

  1. 确定哪个进程正在肆虐,使用我们讨论过的其他各种技术。杀死进程并停止服务,如果不能立即修复。

  2. /var/log/messages的内容复制到系统的另一部分(具有更多空间的分区)或者如果需要的话复制到另一个系统(使用rsyncscp)。

  3. 看看你是否可以手动触发logrotate来旋转和压缩你的消息日志。这可能会失败,取决于实际可用的空间。

如果你无法手动压缩或logrotate一个文件,也许是时候删除原始的消息文件并在其位置创建一个新的文件(具有相同的权限)。

在删除日志之前,你应该先复制日志。否则,你将不知道是什么导致了问题。

临时解决方案及何时调用它们

根据你发现自己所处的情况,临时解决方案可能是很好的,也是必要的。

例如,如果你发现自己在凌晨 5 点被警报唤醒,通知你磁盘只剩下大约 10%的空间,你可能决定调用一个临时解决方案:

  1. 你已经排除故障并确定磁盘以每小时 1GB 的速度填满,这意味着一个半小时后磁盘就会满。

  2. 你还确定了盒子上的 LVM 设置为你提供了 10GB 的可用空间,以扩展你的逻辑卷。

  3. 你检查以确保问题不是由更重大的问题引起的(网站宕机),并意识到这只是因为圣诞销售而产生的嘈杂日志。

  4. 你将 50%的空闲 LVM 空间应用于你的逻辑卷,并回去睡觉。

这是一个很好的临时解决方案,因为这意味着问题可以在白天安全地调查(当其他人可能会在场,你让问题变得更糟的可能性不会太大)。然而,你应该意识到这种方法的缺陷。

临时解决方案在 IT 中,就像在英国政治中一样,往往比你眨眼更快地变成永久性的修复。在我们的例子中,这将不是一个问题,因为我们只有有限的磁盘空间,但我曾见过这种解决方案成为推荐的“修复”并且工程师继续添加虚拟磁盘的时间比他们应该的时间长得多,进一步加剧了问题(还可能掩盖其他问题)。

如何做...

如果临时解决方案为你赢得了时间,或者意味着警报停止响起,网站恢复在线,那么尽管这样做,但也要记下你做了什么,并在你使用的任何工单系统中记录下来。

对你自己的临时解决方案负责。你需要确保你的临时 cron 作业、系统定时器或磁盘空间扩展不会成为问题的永久解决方案。

同样地,如果你编写一个 bash 脚本定期检查一个日志文件,如果超过一定大小就压缩它,确保你也编写检查以确保 bash 脚本正在工作。没有什么比来到一个应用了“修复”但最终由于无法处理超过一定大小的文件而崩溃的系统更令人恼火,而且没有人注意到。

如果你放置的临时“修复”在几个月后导致系统崩溃,你将不会受到感谢。如果你已经离开公司,发现你的名字被拖泥带水地拖着,每当出现类似你造成的问题时,情况只会变得更糟。如果有一件事你应该避免,那就是让你的名字与出现问题相关的事情成为动词。

处理愤怒的开发人员

如果我们从未不得不与其他人打交道,系统管理生活将会容易得多(我想那些其他人也会对我们说同样的话)。

所以,让我们谈谈开发人员。我们的开发人员朋友正在做他们的工作,开发。在计算的本质中,没有软件是完美的,即使是最美丽的代码中也会出现错误;它甚至可能通过了以前的测试,在开发和预生产环境中运行完美,只是在生产环境中出现问题。

你可能会认为自己已经找到了一个完美运行的解决方案,像梦一样运行,像猫一样轻声细语,然后开发人员提出的一个新的“功能”似乎会导致整个解决方案崩溃。在这种情况下,很容易找人来批评。然而,我更希望保持冷静和镇定,因为对某事感到恼火通常只会使情况变得更糟(而且,一半的时间,问题其实是你自己忘记了导致的...)。在噩梦般的情况下,每个人都可能开始互相指责,所以你(和你的同事)需要保持头脑冷静,共同努力找到问题的根源。

如果开发人员来向你报告问题,无论是在实时环境还是非实时环境中,并且假设他们在这样做之前已经通过适当的渠道(提交工单,与团队领导交谈等)进行了沟通,你可能会发现他们已经对正在发生的事情感到沮丧。

如何做到这一点...

很容易因为你正忙于其他事情,或者你认为解决问题不是你的工作,就驳回这种请求。这将是很糟糕的外交手段,通常只会导致双方对彼此感到恼火:

  1. 要感激对方只是在尽力做好自己的工作,无论他们来报告的是什么问题,它可能会妨碍他们继续做自己的工作。问题可能是他们自己设计的,也可能是你在基础设施中忽略的问题,但无论原因如何,都很重要要小心处理这种情况。

  2. 即使你不是问题的联系点,也不知道如何开始调试,也要微笑,礼貌地解释你在这方面是多么笨拙,并引导他们(或更好地带领他们)去找一个可能能够对他们的问题进行分类或帮助的人。

良好的沟通和外交手段会带来更加平静的工作环境,“你帮我,我帮你”的理念也会发挥作用,如果你需要一个友好的开发人员来帮助你调试你认为可能是由应用程序引起的问题。

这也适用于另一种情况;不要因为有人过来对你大喊“你糟糕的基础设施烂透了”而感到压力,最好是首先找一个问题的调解人(通常是经理),然后再去解决问题。在工作场所,任何人都不应该大声喊叫或贬低其他人,记住这一点,如果你发现自己变得焦躁不安,最好是在情况升级之前完全远离这种情况。我见过一些优秀的工程师因为脾气问题而陷入严重麻烦,甚至搭上了工作。

处理愤怒的经理

像开发人员这样的经理只是在做他们的工作。在某些情况下,与他们交谈可能更加困难,因为有时他们只想用简单的英语表达问题和解决方案,即使你还不知道解决方案(或问题)是什么。一些最好的经理明白并不是每个问题都能在十分钟内找到并解决,同样,一些最糟糕的经理期望你在他们从办公桌站起来走到你的位置所需的时间内就能提出完全成熟的解决方案。

如果你发现自己不得不与一个愤怒的经理交谈,也许是因为出现了生产问题,而比他们更高层的人也在追问他们,那么最好不要用他们不太可能理解的行话来激怒他们,或者给他们模糊的套话。

我在这里使用“经理”这个词比较宽泛;同样的逻辑也适用于交付负责人、团队负责人等。

如何做到…

并非所有的经理都具备技术背景,但很多人都具备。最好在早期就确定你的经理的技术水平,这样你就能够评估在问题发生时需要向他们传达多少信息。如果他们的技术水平与你相当或更高,你可能可以把所有信息都告诉他们,让他们向上游传递;如果他们的技术水平较低,最好总结关键点。

记住,优秀的经理要管理和保护工程师或其他人免受工作场所的日常压力,这些压力并非技术性质。当然,你可能会被邀请参加一些会议,解释解决方案,或者为技术决策辩护,但大部分时间,工程师的工作是工程师,行政人员的工作是行政管理,经理的工作是管理(同时也要参加其他人不想参加的所有会议)。

如果一个愤怒的经理开始向你提问,或者在你专心工作时每五分钟要求更新,那么也许是时候把他们引导到你记录日志的工单上,然后每隔几分钟在那里发布一条消息,更新利益相关者的进展。我也提到了 slack 频道或其他沟通媒介,这些都是在让人们了解情况的同时,让你有时间专心工作的好方法。

在以前,我曾被拉到会议电话上,而实际上出现问题时,以下人员都在场:

  • 我的经理

  • 我的经理的经理

  • 我的经理的经理的经理

  • 团队的其他成员

  • 开发团队的成员

  • 销售团队的代表

  • 客户关系团队的代表

现在,我不知道你怎么想,但在出现持续问题时,参与电话会议的人似乎太多了。

在我看来,当你无法立刻参加如此重要的电话会议时,同时提供一下你在故障排除中的进展是可以的。

处理愤怒的企业主

在同事、经理甚至部门领导之上,是企业主。同样,有好的企业主,也有坏的企业主。在大公司里,你可能永远不会见到他们,但似乎有一种普遍规律,你总会在某个时刻与他们交集,有时,这会与实际的故障同时发生,就好像宇宙觉得这很有趣一样。

如果领导层出现了错误,企业主可能一下子就会失去工作和生意。这不是为资本主义辩护,我也不打算建议工人阶级起来夺取生产资料,但值得记住的是,那些因为你没有给出足够完整的答案而生气的人,可能正在失去他们的客户群,进而失去生计。

如何做到…

当你和对话的人有权雇佣或解雇你时,要小心行事,因为你说的话可能会让你失去工作。如果你犯了严重错误(坦白说,那必须是持续且严重的错误),你可能会失去工作,但那只是一份工作,你可能会找到另一份工作,然后把那一段生活放在身后。

希望你不会,但如果你确实发现自己在处理一个极度愤怒的有权力让你下台的人(无论是在悬崖上还是其他地方),那么通常是小心翼翼的时候。向他们保证你正在尽一切努力解决问题,指出已经取得的进展,如果有的话,并向他们保证每个有能力的人都在解决问题。

谁知道——如果你在危机时刻给他们留下深刻印象,保持头脑冷静,而周围的人都在失去理智,你可能会发现自己在不久的将来处于一个受宠的位置。

总结-故障排除和职场外交

这一章有点混杂,因为我试图建议在生产(甚至是小问题)期间可以采取的最佳行动。我包括了我认为在紧要关头对你有用的程序,一些关于你在停机期间应该考虑的模糊指导,然后总结了一些关于在你职业生涯中可能是最紧张的时刻与其他人打交道的建议。

尽管如此,我还有一些要说的话。

不要相信时间

我发现有 70%的时间(随机数字),时间是我问题的根本原因。正因为如此,当我登录到一个系统时,我运行的第一个命令之一就是date

$ date
Tue 11 Dec 17:51:33 UTC 2018

如果时间不对(而不仅仅是因为时区不同),那么你看到的奇怪问题很有可能是两个或更多计算机系统争论它们各自的完全任意的时间表示哪个是正确的直接结果。

先修复时间,然后再修复其他任何东西。

不正确的时间和修复不正确的时间本身就会引起问题,所以要定期检查并备份。

不要忽视简单的问题

除了date之外,我几乎本能地运行的另一个命令是df -h

$ df -h
Filesystem Size Used Avail Use% Mounted on
/dev/mapper/VolGroup00-LogVol00 38G 4.8G 33G 13% /
devtmpfs 235M 0 235M 0% /dev
tmpfs 244M 0 244M 0% /dev/shm
tmpfs 244M 4.5M 240M 2% /run
tmpfs 244M 0 244M 0% /sys/fs/cgroup
/dev/sda2 1014M 63M 952M 7% /boot
tmpfs 49M 0 49M 0% /run/user/1000

就像时间错误一样,当磁盘空间耗尽时,可能会出现宇宙级愚蠢的错误,而实际上并没有任何指示真正的问题是什么。程序很少打印“我无法写入这个文件,所以我出错了”,而通常只是停留在晦涩、令人困惑和完全令人昏昏欲睡的错误上,这在表面上是毫无意义的。

其次检查磁盘空间。

关于“云”部署

又到了发泄的时间!

“云”带来的最让人恼火的事情之一就是引入了“销毁和重新部署”的“解决方案”。

在临时修复的领域,我认为最严重的临时修复是“哦,只需拆除解决方案,让它重新构建;有时会出现这种错误。”

这不是一个“解决方案”,当我听到这样的话时,我感到非常恼火。是的,我们现在可以简单地销毁和重建环境,通常一下子就能搞定,但这并不能原谅最初导致你的系统首次崩溃的错误。如果他们一次这样做了,他们很可能会再次这样做,如果他们在半夜叫醒我或者打断我在埃及度假,那么某人将会收到一封严厉的电子邮件。

“销毁和重建”应该是让解决方案重新上线的手段,但正如我们在本章中所讨论的,你还应该备份可能需要用来解决问题的任何文件,并且在问题停止发生之前,你不应该认为问题已经“解决”。

如果有人告诉你解决方案是销毁和重建,告诉他们去修复他们的代码。

从我的错误中学习

在处理愤怒的人方面,我是一个很好的权威,因为正如我的许多前同事所证明的那样,我通常就是那个愤怒的人。

我花了好几年的时间才意识到,对某种情况或人生气并不能让情况变得更好,或者让我的工作场所更愉快;它只会让我的心情变得更糟。谢天谢地,我从错误中吸取了教训,如今我对问题采取了非常不同的态度,不再立即转向抱怨和指责模式(尽管我总是努力解决问题),而是先退后一步,评估情况,然后带着一杯好茶投入其中。

在危机中保持冷静很难;我不会否认这一点,但头脑冷静的人越多,情况就会越平静。当你情绪激动时,很容易跳到随机的思路上,走进死胡同,变得更烦恼,然后再退回来。

深呼吸。

第十三章:BSD、Solaris、Windows、IaaS 和 PaaS 以及 DevOps

本章将涵盖以下主题:

  • 确定你所在系统的类型

  • 了解 BSD 的不同之处

  • 了解 Solaris 和 illumos 的不同之处

  • 了解 Windows 的不同之处

  • IaaS(基础设施即服务)

  • PaaS(平台即服务)

  • 运维与 DevOps 之争

介绍

当我被要求写这本书时,最初是要求我写 12 章关于 Linux 系统和现代管理的。现在我希望我当初同意了那个提议,但我却大胆地建议增加第十三章。我真是个傻瓜。

所以在这里,这是这篇艰难阅读的最终章节(抱歉,真的很抱歉),它关于计算世界中你需要了解的其他系统,因为不幸的是,现代计算和 IT 基础设施经常是 Windows、Linux 和它们之间的混合体。

我们将简要介绍一下 BSD,因为它们可能是你在当今时代最接近“真正”Unix 的系统,它们也足够接近 Linux,以至于有些 BSD 用户在你使用“它们足够接近 Linux”这样的短语时会感到愤怒。

然后,我们将简要讨论 Solaris,并谈谈它在现代基础设施中的两种形式。

我们将不得不讨论 Windows,尽管我将尝试尽量简短地介绍这一部分。如果你和我一样讨厌“Windows”这个词以及它带来的所有内涵。(微软,你近年来对 Linux 的半嬉皮式态度并不能愚弄我们——我们中有些人喜欢固执地守旧。)

在我们探索其他操作系统之后,我们还将研究基础设施即服务(IaaS)和平台即服务(PaaS),尽管这些缩写有多愚蠢,因为它们是现代 DevOps 和平台创建的重要组成部分。在任何完整的职业生涯中,你都将不得不使用 AWS 和 Azure 等服务,所以最好尽早了解它们的工作方式。

最后,我们将谈一下 DevOps(我会在那一节保留惊喜)。

确定你所在系统的类型

想象一下:你被蒙上眼睛,被塞进车后备箱,然后在长途旅程的另一端被揭开面纱,看到一个闪烁的提示符。你将如何确定你所在的系统类型?

你的第一反应可能是认为你被放在了一个 Linux 盒子前,但这并不是确定的。虽然 Linux 确实主导了服务器领域,但只是因为实例有黑屏、白色文本和登录提示符,并不意味着你被放在了我们友好的企鹅操作系统前。

它可能是 Linux、BSD 系统、Solaris 系统,或者是九十年代的许多 Unix 衍生系统之一。

假设你已经获得了登录凭据,那就登录吧。

如何做…

这是一个简单的起点。

uname

当你成功登录后,确定你正在运行的内核类型:

$ uname
Linux

好吧,这真是令人失望…这只是一个普通的 Linux 系统。

但如果不是呢?想象一下:

$ uname
FreeBSD

这更像是一个 FreeBSD 盒子!

或者是另一种 BSD?

$ uname
OpenBSD

一个 OpenBSD 盒子,很酷!但我们可以更进一步:

$ uname
SunOS

嗯?SunOS到底是什么?

简短的答案是你可以假设你已经登陆到了 Oracle Solaris 或 illumos 发行版,它们都相对罕见,但值得尊重。

文件系统检查

如果你还不确定,你可以快速检查 slash-root(/)所使用的文件系统类型:

$ mount | grep "/ "
/dev/mapper/VolGroup00-LogVol00 on / type xfs (rw,relatime,seclabel,attr2,inode64,noquota)

XFS 通常出现在 Linux 系统上,特别是 RHEL 和 CentOS:

$ mount | grep "/ "
/dev/ada0s1a on / (ufs, local, journaled soft-updates)

Unix 文件系统(UFS)通常出现在 FreeBSD 盒子上(如果你有足够的 RAM,还会有 ZFS):

$ mount | grep "/ "
/dev/wd0a on / type ffs (local)

FFS?你在开玩笑…不,这是快速文件系统(FFS),它通常用于 OpenBSD 盒子:

FFS 和 UFS 有相同的传承,尽管代码库并非完全相同。

$ mount | grep "/ "
/ on rpool/ROOT/openindiana read/write/setuid/devices/dev=4410002 on Thu Jan 1 00:00:00 1970

虽然我们在这里没有得到实际的文件系统类型,但我们可以看到输出中列出了openindiana,我们知道这是一个 illumos 发行版。然后我们可以使用zfs来确定我们的文件系统设计是什么(并确认它是zfs):

$ zfs list
NAME USED AVAIL REFER MOUNTPOINT
rpool 6.82G 41.1G 31.5K /rpool
rpool/ROOT 2.70G 41.1G 23K legacy
rpool/ROOT/openindiana 2.70G 41.1G 2.46G /
rpool/ROOT/openindiana/var 203M 41.1G 202M /var
rpool/dump 2.00G 41.1G 2.00G -
rpool/export 73.5K 41.1G 23K /export
rpool/export/home 50.5K 41.1G 23K /export/home
rpool/export/home/vagrant 27.5K 41.1G 27.5K /export/home/vagrant
rpool/swap 2.13G 43.3G 12K -

它是如何工作的...

尽管提示可能看起来相同(尽管通常,这些发行版不会将 Bash 设置为默认 shell;这是 Linux 的事情),但底层系统可能与您熟悉的 GNU 用户空间大不相同。

当我们运行uname时,正如我们之前讨论过的,我们输出了我们登录到的系统的内核。

希望它是一个 Linux 系统,特别是如果您被迫去修复某些东西,但即使您对 Linux 有一定了解,事情也应该相对熟悉。进程运行,默认工具相同,您可以阅读README文件或 man 页面来了解您尚不了解的内容。

我们进行的文件系统检查并不是最科学的,但通常可以保证像mountdf这样的命令对您是可用的,这要归功于它们的血统。

还有更多...

在前面的部分中有一个显而易见的遗漏,那就是如何确定您是否在 Windows 系统上工作。

我发现检查我是否在 Windows 提示符下的最简单方法是测量我的灵魂从身体中流逝的速度。如果失败,我会寻找文件系统中的“音乐”文件夹,这似乎不可思议地出现在桌面和服务器安装中。

显然,如果前面两种方法对您无效,那么请考虑 Windows 通常具有图形用户界面(除非是现代服务器操作系统与时尚的系统管理员,那么可能只是一个蓝色的 PowerShell 提示符。无论哪种情况,我想您会知道)。

了解 BSD 的不同之处

您可能已经注意到,我在本章中故意将 OpenBSD 和 FreeBSD 分开,但它们只是不同的“BSD”发行版,对吧?

错误。

与 Linux 不同,BSD 的不同“风味”不共享内核,更像是不同的操作系统而不是不同的发行版。

OpenBSD、FreeBSD、NetBSD 和 Dragonfly BSD 都是独特而独立的项目。

NetBSD 甚至有一个 Dreamcast 端口。最后,那个控制台有了用途!

这并不是说各个发行版之间没有代码和修复的共享——只要移植比编写自己的实现更快,而且 BSD 更有可能使用更“自由”的开源许可证,比如 MIT 许可证,而不是“限制性”的开源许可证,比如 GPL(通常出于意识形态原因)。

不同之处

正如我们已经说过的,BSD 是独立的操作系统,其受欢迎程度的估计排名如下:

  • FreeBSD

  • OpenBSD

  • NetBSD

  • 其他 BSD

在这里,我将谈到两个最受欢迎和知名的:FreeBSD 和 OpenBSD。

FreeBSD

作为一个关注多样化的操作系统(服务器、桌面、物联网设备),FreeBSD 是 BSD 衍生版中最受欢迎的。

它以拥有大量可用软件而自豪,既有预先构建的软件包(在撰写时每季度构建一次),也有本地构建的“端口”,通常是该软件的最新版本。

许多开源软件都是专注于 Linux 的。这并不是 BSD 的错——这只是市场份额的问题。由于这种关注,FreeBSD 项目有很多志愿者将他们的时间投入到使软件包在 FreeBSD 上本地运行,有时需要对软件进行小的甚至重大的调整才能使其编译和运行。然后,这些努力大部分都会被推回上游,我还没有看到一个项目不感谢 FreeBSD 支持的。

使 FreeBSD 成为 BSD 思维人士的热门选择的另一个因素是它作为标准附带 ZFS,并提示您使用 ZFS 作为设备的根文件系统以及额外存储的文件系统。

ZFS(特别是 OpenZFS)是一个文件系统、存储管理和全方位的存储解决方案。我听说它被称为“文件系统的最后一句话”,而它的许可证使它在 Linux 平台上变得不常见(FreeBSD 没有这样的顾虑)。

拥抱,或者通用开发和分发许可证(** CDDL **)在开源世界中是一种相当罕见的许可证。由 Sun Microsystems 在其鼎盛时期制作,该许可证曾被称为与 GPL“不兼容”。

虽然 ZFS 确实是存储的一个很好的解决方案,但对于新手来说可能会有点困惑,因为它不遵循传统文件系统的模式(甚至是 UFS——FreeBSD 的更简单的替代方案),并且在文件系统和分区之间模糊了很多界限。它也有一些缺陷。由于其设计,ZFS 需要大量内存,而鉴于近年来内存价格的波动,这对终端用户来说可能是障碍。

FreeBSD 还注重稳定性,这意味着它包括了一些功能,比如能够轻松回滚更改(如果使用 ZFS),并且有一个非常可靠的升级路径(尽管可能有点令人困惑和复杂)。

监狱也值得一提,因为如果我不提到它们,FreeBSD 的粉丝们会感到恼火。在 Docker 之前,有监狱,可以在 FreeBSD 系统上将 OS 的部分分隔开来。它们于 2000 年引入,允许 FreeBSD 用户在主机系统上隔离其软件,甚至在彼此之间安装不同版本的 FreeBSD,以便专门为 FreeBSD 9 编写的软件可以在 FreeBSD 12 上运行。

不公平的是,监狱并没有真正起飞,尽管这在很大程度上归因于 FreeBSD 的市场份额。尽管它们可以说更优越,但它们也比 Docker 和其他容器解决方案更加笨重。我认为 Docker 之所以如此成功,很大程度上归功于像 Docker Hub 这样的地方,而 FreeBSD 则缺乏类似的东西。

总之,我可以这样总结 FreeBSD:

  • 优点

  • 附带 ZFS

  • 提供非常新的软件包

  • 稳定的系统

  • FreeBSD 监狱

  • 第一方软件包和第三方软件包的合理分割(附加软件包通常完全安装在/usr/local/下)

  • 非常好的“FreeBSD 手册”

  • 缺点

  • 安装基数比 Linux 系统小

  • 不太可能有更新的驱动程序(尽管近年来情况有所改善)

  • 通常更倾向于较旧的解决方案(X 优于 Wayland)

  • 升级过程可能会令人困惑

  • 有时会被供应商忽视补丁和安全披露(考虑到英特尔 CPU 的漏洞)

FreeBSD 是一个不错的选择,但我故意不推荐给 Windows 或 macOS 的切换者。相反,我会把他们指向像 Ubuntu 和 Fedora 这样的流行 Linux 发行版。FreeBSD 有它的位置,对于存储服务器来说,你可能会有更糟糕的选择,但它并不是很多人心目中的第一选择。

OpenBSD

OpenBSD 是一个几乎传奇般专注于安全和稳定性的操作系统,它是因为担心保留它的“Linux 兼容层”的安全性而将其删除的软件。

如果你从未听说过或使用过 OpenBSD,我可以保证你至少使用过一个相关项目。一些属于 OpenBSD 领域的项目包括以下内容:

  • LibreSSL

  • OpenSMTPD

  • OpenNTPD

  • OpenSSH

  • httpd(这是 OpenBSD 特定的软件包,而不是重新打包的 Apache)

这意味着虽然你可能从未 SSH 连接到过 OpenBSD 系统本身,但你仍然在主要基于 OpenBSD 系统构建的软件上使用。我知道没有一个 Linux 发行版不使用 OpenSSH 作为默认的 SSH 客户端和服务器(尽管也存在替代方案)。

除了软件,对于那些软件有多好,OpenBSD 还有更多。

作为一个相对较小的项目,再次强调它是开源和捐赠资助的,OpenBSD 的安装基数并不是很大,但在嵌入式系统、防火墙和路由器方面非常受欢迎。这是因为虽然它的多处理器元素可能没有其他一些项目那么强大,但它具有诸如数据包过滤器pf)防火墙之类的软件,并以安全优先而闻名。

OpenBSD 在其网站上的标语是“默认安装中只有两个远程漏洞,而且已经很长时间了!”,这正好说明了他们对安全的承诺。我看到 OpenBSD 积极地删除软件,因为它存在可疑的安全问题。

LibreSSL 诞生于对 OpenSSL 的挫败感,以及开发者认为更容易分叉和修复该项目(而不是争取上游的增强安全性)。很容易看出,支撑互联网安全的软件应该保持安全。

以调整软件以减少漏洞出现的机会而闻名,这有时会对 OpenBSD 产生反作用,因为他们可能会因为害怕立即解决问题(而不是等到“官方”日期才公开漏洞)而被忽视。有关此问题的有趣案例研究,请参见“KRACK Attacks”漏洞,以及 OpenBSD 的响应(www.krackattacks.com/#openbsd)。

因为 OpenBSD 是与 FreeBSD 不同的操作系统,它不包括类似 jails 的功能,也没有 ZFS(而是支持 FFS)。在默认安装中,你几乎不能进行任何调整,有人认为“你不应该这样做”。

它可以用作桌面、服务器(通常是防火墙,据我所见),或嵌入式操作系统。

你可能可以这样总结 OpenBSD:

  • 优点

  • 极其重视安全。

  • 非常重视代码质量

  • 合理的升级路径(尽管耗时)

  • 频繁的发布周期和快速的修补

  • Pf,说实话很棒

  • 缺点

  • 缺少软件包(尽管其端口系统有很多)

  • 稳定和安全可能意味着缺乏功能(这取决于个人)

  • 非常小的安装基数

  • FFS 老化且显示出年龄

  • Theo de Raadt(OpenBSD 的终身慈善独裁者)以直言不讳而闻名(你可以理解为你愿意的)。

我在一台笔记本电脑上使用 OpenBSD,主要是为了体验,但现在我们使用的是 6.4 版本,最初安装的是 6.0 版本(也有一些很棒的发布艺术作品)。笔记本电脑运行良好,尽管大部分时间都在闲置。我也尝试过将 OpenBSD 用作服务器,但出于我的罪恶感,我很快发现当我无法获得我认为非常标准的软件包时,这让我很烦恼。

了解 Solaris 和 illumos 的区别

我有点害怕这一部分,因为很难谈论像 Oracle 这样的公司而不至少有点恼火。

我们将简要介绍 Oracle 及其 Sun 收购的历史,以及 OpenSolaris 运动和由此产生的系统。

在我们开始之前,我应该指出 illumos 和 Solaris 有非常 Unix 的背景,它们可以说是当今“最纯粹”的 Unix 衍生产品。你可能从未使用过它们中的任何一个,但很有可能你使用过一个由这两个操作系统支持的网站或在线门户。

区别

首先,稍微了解一下历史。

Solaris(曾经的 SunOS)是在九十年代初由 Sun Microsystems 发布的,最初设计用于 Sun SPARC 系列处理器,尽管它很快就扩展到支持 x86 处理器。

有一段时间,Sun(以及它的紫色巨兽在数据中心随处可见)备受推崇,在数据中心是一个相当常见的景象。该公司不断发展,开发了诸如 Solaris Zones(就像我们之前讨论的 FreeBSD jails)、ZFS 和 Java 等东西。

并不是一个家庭用户系统,Solaris 在企业环境中与 SPARC 处理器一起很受欢迎,尽管随着 Linux 的发展和市场份额的增加,这个和其他替代操作系统很快就失去了市场。

2005 年,Sun 决定在 CDDL 许可下开源 Solaris,同时创建了“OpenSolaris”项目。这是为了围绕 Solaris 建立一个社区,可能增加使用率和市场份额。

然而,当甲骨文在 2009 年收购 Sun(2010 年完成)时,他们几乎立即停止了向源代码分发公共更新的做法,并有效地将 Solaris 11(因为它是)恢复为一个闭源产品。

他们无法把精灵重新装进瓶子里,而且代码已经被释放了一次,这意味着虽然 OpenSolaris 实际上已经死了,但派生版本仍在继续。

令人困惑的是,许多这些派生版本都属于“illumos”范畴,这可能是 OpenSolaris 项目的主要分支,还有一些相关项目,如 SmartOS(来自 Joyent,现在是三星公司)稍有偏离。

illumos(小写的“i”,出于某种原因)包括一个内核、系统库、系统软件和设备驱动程序。

一般来说,这意味着当人们现在提到“Solaris”时,他们要么是怀念旧时的 Sun,以及可能十年前做过的安装,要么是用它来指代甲骨文仍在发布、支持和据称正在积极开发的 Solaris 11。在撰写本文时,最新版本是 2018 年 8 月的 11.4。

在随意的谈话中,我将 SmartOS、OpenIndiana 和其他系统称为 Solaris,尽管这在技术上是不正确的,可能会导致我有一天收到甲骨文的愤怒信件。

甲骨文 Solaris

正如我们已经说过的,甲骨文仍然积极发布和支持 Solaris,尽管您这些天可能会遇到的很多安装都可能是旧的安装。Solaris 10,首次发布于 2005 年,从技术上讲仍在支持中。

Solaris 10 在 2021 年 1 月正式停止支持,尽管我愿意打赌,支持的持续时间取决于你的财力有多深。

具有 SPARC 处理器支持和倾向于数据库安装,Solaris 可能是您作为工程师在一生中会遇到的东西,如果您决定熟悉它,您很有可能会加入一个知道如何支持它的人群,这意味着您可能会发现自己很受欢迎。

请不要决定在未来的工作中学习甲骨文 Solaris 的所有细节,我不会对你发现自己成为一个死亡领域的专家负责。

ZFS 也是 Solaris 的一个强大特性,虽然 OpenZFS 项目尝试了跨兼容性,但由于代码基础的分歧和甲骨文似乎不愿意保持功能的一致性,这似乎在最近几年已经不再重要了(尽管不要相信我的话,我只是一个谣言的消费者)。

illumos

OpenSolaris 项目的精神延续,illumos 构成了一些不同发行版的基础,这些发行版试图保持基于 CDDL 的 Solaris 的传统。

OpenIndiana 可能是这些发行版中最用户友好的,仍在不断增强。它可以在虚拟机中下载和尝试(我鼓励这样做,只是为了试试水)。

然而,当你运行它时,不要惊讶地发现有关 Solaris 和 SunOS 的引用:

$ uname -a
SunOS openindiana 5.11 illumos-42a3762d01 i86pc i386 i86pc

由于软件包规模较小,你也不太可能找到当今流行的软件,但它将具有你熟悉的编程语言,并且它无疑是稳定的。

甲骨文的 Solaris 和 illumos 在过去曾有一些优秀的头脑为其工作,这意味着它们也具有稳定的内核和合理的开发方法(如果你相信一些曾在其上工作的更有声望的工程师)。

遗憾的是,在 Solaris、BSD 和 Linux 世界中存在一定程度的冲突,每个人都对特定事物的“正确”做法有着自己的看法,尽管所有这些操作系统都可以追溯到一个共同的核心(Unix)。

就个人而言,我喜欢安装操作系统和进行调试——我就是这样奇怪。

了解 Windows 的不同之处

你将不得不使用 Windows——这是一个现实(至少目前是这样)。

如果你不是被迫在工作场所使用 Windows 作为桌面操作系统的选择,那么很可能你至少会有一个 Windows 服务器需要照看或管理。

Windows 仍然常见的安装原因有:

  • 活动目录

  • 使用 Exchange 进行电子邮件

  • 文件服务器

它也被广泛应用于服务器领域,像 IIS 这样的软件在网络中占据了相当大的一部分(尽管远远小于开源软件的份额)。

从一开始,正如我们之前讨论的,Windows 和 Linux 在一些关键方面有所不同。

不同之处

Windows 是有许可的。这可能是开源软件和专有软件之间最明显的区别。如果你想在生产中使用 Windows,你必须确保你有正确的许可证,否则就会面临违规罚款。

有趣的是,这可能是 Linux 获得采用的最大原因。当面对一个可以用来做与 Windows 相同事情的免费软件时,大多数管理员至少会先尝试免费版本。

第二个最明显的区别,尽管这种情况正在慢慢改变,是 Windows 默认安装图形用户界面,而 Linux 更喜欢简单的文本提示符。

CLI 与 GUI 的争论已经持续了多年,我现在不打算继续,但我会说在一个几乎不会被登录的操作系统上耗费额外的资源来支持图形功能,似乎非常愚蠢和浪费。

虽然现在完全有可能安装一个不带图形界面的精简版 Windows(例如 Windows Server 2016 Nano),但在数据中心中仍然不常见,很多功能仍然主要由图形界面驱动(尤其是第三方软件)。

与 Linux 不同,Windows 采用远程桌面协议(RDP)作为其首选的连接方法,将服务器的桌面图形表示传送到本地计算机。

有趣的是,RDP 也开始被远程 PowerShell 连接和甚至 SSH 所取代(微软似乎开始欣赏 SSH 作为一种非常好的轻量级解决方案,尽管采用率仍然未知)。

显然,Windows 也没有采用 Linux 内核,而是使用 NT 内核与硬件和低级工作进行交互。

趣闻时间!

几年前,我和一个朋友在下班后在城市里散步时聊天。我当时年轻而天真,所以我随口提到 Linux 用户更多地使用 Windows 可能是个好主意,因为我们这边的采用只会推动对方的改进。

这导致了一阵喧闹的笑声和嘲笑,我的朋友在我们到达餐馆后向其他同事传达了这个疯狂的建议。大家都对我开了个玩笑,这件事就这样结束了,我们 Linux 人的普遍共识是 Windows 永远都是垃圾,没有什么能拯救它。

快进几年,同样的朋友现在积极倡导 PowerShell,在他的工作机器上安装了 Windows,并谈论使用 Windows 而不是 Linux 进行选择性任务的优点。

我提出这个问题是因为我怀疑他迟早会读到这本书,我只是想提醒他,在很久以前,他只是因为是 Windows 而对 Windows 不屑一顾。

时代确实在变化,尽管微软仍然因其在隐私等方面的立场而受到很多(可以说是合理的)批评,但他们似乎正在努力将他们的操作系统实现为云世界的解决方案。

阅读本文的一些人可能已经记得“拥抱、扩展和扑灭”(EEE),这是微软内部用来谈论其操纵开放和广泛采用的标准的计划的短语,通过说他们的产品可以做到其他产品能做的一切,甚至更多,然后推动开放和免费的替代品退出市场,扩展它们以专有的附加功能。 (想想 AD,基本上就是带有更多花哨功能的 LDAP。)

有一些人认为微软最近的“微软热爱 Linux”的立场只是一个策略,我们即将看到“EEE”方法的复兴。

IaaS(基础设施即服务)

简而言之,IaaS 可以被简单地总结为“云服务器”。

IaaS 是云提供商用来表示你可以将所有那些又旧又吵又昂贵的本地服务器转移到“云”中的术语。

实际上,云只是“各种数据中心中的一堆服务器”的营销术语,这让许多工程师感到恼火,他们不喜欢那些只会混淆和困惑的羊毛术语。

像将基础设施转移到云中这样的做法的好处应该是显而易见的,我们之前已经讨论过“基础设施即代码”(IaC)的概念。

过去,部署开发环境意味着从分销商购买新服务器,架设和布线,确保你有一个体面的方法将你选择的操作系统安装在上面。

现在,从交换机到防火墙和服务器的基础设施可以只需点击几下鼠标,或者更好的是,键盘上输入几个命令就可以部署。

IaaS 意味着你可以在几秒钟内部署成千上万的服务器,并且可以同样快速地将它们拆除。这种可扩展性和部署便利性意味着以前需要为每月运行一次的作业而拥有整个数据中心的公司现在可以通过简单地为一个“十五分钟的作业”启动这些服务器,然后再次将它们删除来节省电力和冷却成本。

然而,使用云解决方案的最大好处可能是你不必担心底层硬件。其他人(提供商)负责确保你正在构建的服务器正常工作,通常你不必担心诸如硬件警报之类的事情。

IaaS 提供商和功能

目前 IaaS 服务的最大提供商是亚马逊,他们提供的 AWS。一个在线零售商也是最大的云解决方案供应商可能看起来有点奇怪,但当你考虑到他们必须为自己的平台设计和构建的基础设施时,他们会想到将其作为服务出售。

AWS 得到了 IaC 工具(如 Terraform 和 Packer)的很好支持,成为一流的公民,它还拥有自己的工具,比如 CloudFormation(类似于 Terraform)。

有趣的是,AWS 出于某种奇怪的原因也混淆了一些名称,导致出现了像www.expeditedssl.com/aws-in-plain-english这样的网站,该网站提供了亚马逊的一些名称以及相应的英文对照。

例如,EC2 基本上是亚马逊对服务器的称呼。

AWS 于 2006 年推出,这意味着他们在 2010 年推出的 Azure 之前就已经领先一步。

微软的 Azure 可能是云解决方案的第二大供应商,他们有一个额外的优势,即如果你的企业签署了使用 Office365 进行电子邮件和文档的协议,微软很有可能会推动你使用 Azure 来进行基础设施。

显然还有其他提供商,不是所有的 IaaS 都必须是远程解决方案。你可以在你的数据中心部署一个 OpenStack 安装,然后与其 API 交互,创建一个 IaaS 平台,用于编程地构建虚拟基础设施。显然,这里的警告是你仍然需要维护基础框、操作系统和 IaaS 软件。

谷歌有一个 IaaS 提供,甲骨文、Rackspace 和 IBM 也有。在较小的一边,你有 DigitalOcean、Scaleway 和 OVH。你可以选择使用哪一个,通常取决于提供的功能。

如果你有特定的要求(比如数据主权),你可能会发现你绝对必须使用国内提供的服务,这意味着你可能会发现一些 IaaS 提供商的竞争对手被立即排除在外,但通常会有一些东西来满足你的需求。

IaaS 为管理员提供了灵活性,这意味着你不再有低估特定软件的风险,因为你可以确定是否需要更多资源,然后简单地销毁这个盒子,然后创建一个更大型的新盒子。

IaaS 意味着你的防火墙和负载均衡器规则不再存储在一个不起眼的盒子的转储配置文件中。相反,你可以配置文本文件,然后在构建你的基础设施的同时读取和应用它们。

IaaS 甚至意味着你可以定期测试你的基础设施,按计划销毁和重建整个集群,只是为了确保操作系统更新或代码更改没有破坏任何东西。

PaaS(平台即服务)

在 IaaS 的另一侧,或者与之并行的是 PaaS 的概念。

平台即服务几乎是基础设施虚拟化的逻辑延续,进一步抽象化,并提出了一个问题:“为什么我们要求用户为 PostgreSQL 启动服务器,而我们可以直接启动一个 PostgreSQL 实例呢?”

是的,总会有一个服务器存在,这是肯定的。这些服务不仅仅是在一个充满雾气的房间里飘忽不定地运行(尽管这是一个很酷的心理形象),但这种理念的关键部分是你不在乎。

实际上,你并不关心你的数据库运行在什么操作系统上,只要它确实在运行,而且不会立即崩溃。然而,作为过去的管理员,你将被要求做到这一点——保持对补丁和重新启动的掌握,以确保数据库本身继续运行。

因此,PaaS 作为一个概念听起来很美好。

为什么你要为托管网站、运行数据库或部署 Redis 而启动多个操作系统实例,当你可以使用现成的提供端点连接,并且不用担心操作系统呢?

在新世界中,你可以将你的网站部署到一个共享的网络段,连接到你指定的数据库,并与一个 Redis 端点进行交互,你根本不知道它运行在什么版本的 Linux 之上(如果它真的是 Linux 的话!)。

从理论上讲,这也意味着开发人员在编写软件时会更容易,因为他们不必关心特定的操作系统怪癖或可能影响他们的代码的微小差异。只要开发人员针对 PaaS 提供的共同平台,他们就不需要知道底层运行的操作系统是什么。

PaaS 提供商和功能

与 IaaS 一样,AWS 和 Azure 都充斥着 PaaS 的提供。

最明显的产品是数据库,AWS 提供 PaaS 关系型数据库、NoSQL 数据库(DynamoDB)和缓存系统,比如 Amazon ElastiCache。

Azure 的数据库产品包括 MSSQL 部署(显然)和最近还包括了 PostgreSQL。

亚马逊提供域名服务,名为 Route 53(哈哈,伙计们,非常聪明,现在别再用愚蠢的命名了)。他们还提供 CDN 解决方案、VPN 设置和 Active Directory 服务。

Azure 还提供 CDN 解决方案,以及 Web 应用(用于托管 Web 应用和网站)、Azure Blob 存储和非关系型数据库产品,比如 Cosmos DB。甚至还有一个专门命名的“Redis 缓存”产品,让你不必自己创建。

这个列表还在继续,这意味着在部署中使用的潜力中,对于绿地项目来说迷失并不罕见。我敢说,对于任何 21 世纪的系统管理员来说,一个很好的经验法则应该是“如果你不必管理它,就不要尝试”。

如果你可以使用 PaaS 选项,那就使用吧,因为从长远来看,它会为你节省大量的麻烦。虽然一开始的学习曲线可能很陡峭,但下次出现重大操作系统漏洞并需要开始一轮关键的补丁时,你会庆幸自己选择了 PaaS 产品。

Azure 和 AWS 是最明显的两个,但与 IaaS 一样,还存在其他提供商。GCP(Google Compute Platform)是最明显的第三个竞争者,但更小的提供商也在市场上迈出了试探性的第一步。

DigitalOcean 已经开始提供像托管的 Kubernetes(这在某种程度上是 PaaS,因为你可以将自己的容器部署到托管平台中)、块存储和负载均衡器等产品。Scaleway 已经开始公测对象存储(在撰写本文时)。

我认为 PaaS 最大的问题在于,为了对最终用户无缝使用,背后需要投入大量工作。

你像消耗无物一样使用数据库产品,创建你的模式在一个透明的结构之上,但在这个链条的某个地方,有人必须设计和维护这些 PaaS 产品所依赖的系统……我希望他们得到了很多钱来做这件事。

显然,有负面影响——当你选择 PaaS 时,经常会出现所见即所得的情况,你使用的现成产品并不总是百分之百适合你的需求。如果它适合百分之九十,你需要判断是否值得那最后的百分之十,或者你宁愿选择一个全功能但可定制的 IaaS 部署。

Ops 与 DevOps 之间的战争

DevOps 作为一个词被误解、滥用和扭曲,到处都有招聘人员这样做。

去 DevOps 的 subreddit(https://old.reddit.com/r/devops)上,你会看到 DevOps 被称为一个“运动”,这正是它最初的意图。

DevOps 是开发和运维的缩写,它应该是一种将软件开发原则与传统 IT 运维相结合的方法论。

然而,我们生活在现实世界中,虽然你可能会觉得在电话那头对招聘人员大喊“DevOps 不是一个职位!”很有趣(不要这样做),但这并不会减弱变革的力量。

我被称为 DevOps 工程师,我也认识很多其他被称为 DevOps 工程师的人。我也申请过专门招聘“一名 DevOps 人员加入并帮助我们发展”的工作。这取决于使用情况,就像语言世界中的很多东西一样,如果招聘人员说“我想要一个 DevOps”,那么招聘人员就会为之做广告。

这个行业本身正在不断壮大,因为将传统的开发方法与基础设施和应用程序管理相结合的明显优势得到了证实,IaaS 和 PaaS 解决方案的崛起只加速了这一趋势。

简而言之,我认为在 DevOps 环境中工作归结为以下几点:

  • 欣赏基础设施即代码

  • 重视代码和实践的可重用性

  • 评估和采用新技术或实践

许多人对“DevOps”的定义有不同的看法,尽管有些人大声喊叫说这是一个黑白分明的定义,但也许最糟糕的是那些固执的系统管理员和运维工程师,他们根本不相信进步。

我遇到过这样的人 - 那些不相信编码的人,他们似乎喜欢独特的服务器,总是自称为管理员,尽管因此而被忽视而无法得到工作。

了解和欣赏传统系统管理的价值是肯定的,但这些技能与现代方法紧密相连。

更像是一场小规模战斗。

过去,每个系统管理员都被期望至少了解一点 SQL,因为他们通常被认为也是基础设施的数据库管理员。现在,人们假定系统管理员至少也精通一些“DevOps”工具。

“DevOps 工具”也有一些模糊的定义,但 Hashicorp 公司的任何东西可能都适用。

事情开始变得棘手的地方在于责任的下放。

有一些公司有以下情况:

  • 一个平台团队

  • 一个运维团队

  • 一个 DevOps 团队

  • 一个拥有 DevOps 工程师的开发团队

根据上述列表,你告诉我,谁负责确保 OS 是最新的。

这是平台团队,对吧?这个平台是他们的,所以他们有责任确保环境中的所有 OS 都是最新版本。

但是 DevOps 团队呢?他们编写了配置盒子的代码,我们知道他们的 Ansible 在一开始就运行了“yum update” - 他们应该再次运行相同的 Ansible 吗?

运维呢?过去,他们的责任可能是确保 OS 更改不会影响软件,所以他们应该是更新 OS 的人吧?

开发人员是想要最新 OS 软件包功能的人,他们要求进行更新 - 因此责任是否应该由他们的“DevOps”团队成员承担?

看到关于责任的争论,甚至是关于个人被雇佣的工作标题的争论,这是令人困惑但并不罕见的。

部分原因是公司没有定义严格的结构,但也是那些自称某种方式然后将自己与他们认为与自己的工作无关的事情隔离的人的错。

如果你发现自己在这样的公司里,有不愿意承担责任的敌对派系,可能最好是与你的上级,或上级的上级,甚至是 CTO,讨论一下结构。

我的一个朋友曾经说过(我在这里是引用),“工作标题可能会改变,但归根结底我们还是运维工程师。”他的意思是,虽然新技术和趋势来来去去(我在看你,Node.js!),但当一切都变得一团糟时,我们仍然需要以我们传统的能力来解决问题。

总结 - BSD,Solaris,Windows,IaaS 和 PaaS,DevOps

我希望这一章节能够涉及我们作为系统管理员存在的各种补充元素。这些概念中的许多概念都可以单独填满一章,但是我已经被一些人告知这本书太长了,而我甚至还没有完成写作!

希望你对世界上存在的其他操作系统有一些了解,如果你之前不知道的话,甚至你可能会有倾向离开这一章并安装 SmartOS 或 OpenBSD,我会极力鼓励。不要把自己局限在一个领域,谁知道呢?在未来的某个时刻,我们所知道的这个怪物 Linux 可能会消亡,然后其他东西可能会从灰烬中崛起来取而代之。你应该做好准备。

就像 Linux 正在消亡一样,传统的系统管理肯定正在改变,如果我在本章中的讽刺语气暗示了什么,那就是你应该准备跟上变化。IaaS 已经很普遍了,尽管在企业世界,它的市场份额正日益流失给 PaaS 解决方案。学会如何在不触及底层操作系统的情况下部署网站,你将会很受欢迎(至少目前是这样)。

最后,还有 DevOps,我特意试图保持简短,因为我已经认为会有人和我争论定义。我的朋友是对的,我们的工作标题似乎每五年就会改变一次,但最终它总是回到同一个问题上——你知道在哪里找到日志文件,你能弄清楚为什么服务崩溃了吗?

如果你从这一部分中得到了什么,那就是你永远不可能知道关于系统管理世界的所有事情,但你知道的越多,你就能赚得越多...或者你可能会对自己的工作感到更自豪——就像那样一些空洞的东西。

posted @ 2024-05-16 19:41  绝不原创的飞龙  阅读(19)  评论(0)    收藏  举报