史蒂文斯-CS615-系统管理笔记-全-
史蒂文斯 CS615 系统管理笔记(全)
001:课程介绍与概述 🖥️
在本节课中,我们将学习CS615《计算机系统管理》课程的整体框架、学习目标、评估方式以及所需资源。课程由史蒂文斯理工学院的Jan Sharman教授讲授,旨在介绍系统管理领域的核心概念与实践。

系统管理是一个涉及计算机操作、网络、安全及服务的广泛领域。它不仅是技术操作,更是一门需要深度与广度知识结合的专业。本课程将帮助你理解系统管理在计算机科学中的位置,并为你打下坚实的基础。
系统管理在学术中的定位 🤔
上一节我们介绍了课程的整体情况,本节中我们来看看系统管理如何融入学术课程体系。系统管理常被视为运维工作,但它与计算机科学和编程领域既有交集,又有所不同。

系统管理作为一个专业领域,其职业路径并不固定,甚至没有统一的职位描述。不同组织可能称之为站点可靠性工程、DevOps或全栈运维。学习这门专业通常没有传统的学位路径,大多数人依靠实践经验和特定领域的深入钻研。
该领域要求从业者既要有广泛的基础知识,如操作系统、网络和编程,也要有特定领域的深度专业知识。
以下是系统管理员可能需要掌握的知识领域:
- 广度知识(基础):操作系统概念、TCP/IP网络、编程概念、多种语言实践经验、云计算等。
- 深度知识(专业领域):特定操作系统(如某个Linux发行版)、核心服务(如DNS、SMTP、数据库)、特定厂商的软件产品,或更广泛的领域(如安全、自动化)。
正是这种广度与深度的结合,使得系统管理既充满趣味又富有挑战性。
课程内容概览 📚
鉴于系统管理领域极其广泛,本课程无法覆盖所有内容,而是选择介绍其中一些最重要的方面,旨在激发你的好奇心并为你奠定基础。
以下是本学期计划涵盖的主要主题:
- 第1周:系统管理领域、职业与实践概述;Unix系统历史与基础。
- 第2周:存储模型、设备与技术。
- 第3周:文件系统基础;软件类型区分。
- 第4周:操作系统与软件安装、包管理、多用户基础。
- 第5-6周:TCP/IP网络详解,包括各层协议的抽象概念与实践工具。
- 第7周及以后:关键服务(DNS、HTTP、TLS、SMTP)、系统管理中的软件开发、监控、备份与灾难恢复、配置管理、系统安全、职业道德。
- 期末:夺旗挑战赛,用于复习所学内容。
学习方法:学会如何学习 🧠
在系统管理职业生涯中,持续学习至关重要。本课程将特别强调几种核心的学习方法。
首先,学习如何有效提问以获取所需答案,并知道在哪里提问。其次,培养批判性阅读能力,而非盲目跟随搜索引擎的第一个结果。

更重要的是,要理解“已知”与“未知”的边界。这里涉及一个重要的认知偏差——邓宁-克鲁格效应。其核心观点是:知识少的人容易高估自己的能力,而知识多的人则更能意识到自己的不足。

我们可以用两个集合来粗略表示:
- 集合A:你知道的知识。
- 集合B:你知道自己不知道的知识。

对于新手,A远大于B,因此容易自信。随着学习深入,B的增长速度可能超过A,你会意识到有更多未知领域,从而变得谦逊。本课程希望帮助你扩大A的同时,也理性地认识B。
此外,务必理解你所做操作背后的原理,而不仅仅是复制粘贴命令。最后,积极与同行、社区交流信息,这是学习过程中不可或缺的一环。

课程评分与作业 📝
作为研究生课程,最终会有成绩评估。评分将综合考虑以下几个方面:
以下是评分构成的详细说明:
-
课堂参与(40%):
- 每周问卷:针对当前主题的简短问题,用于引导思考,无标准答案,但需完成。
- 课程笔记:需为每次讲座创建Git仓库和文本文件笔记,记录阅读内容、练习完成情况和问题。
- 社区活动:需参加一次线上技术社区活动(如会议、技术演讲),并提交总结。
- 课堂分享:在同步课程中,轮流分享与主题相关的有趣文章或论文。
-
实践作业(30%):包含一些涉及编码的练习。
-
课程项目(20%):合作开发一个软件项目,评分依据包括项目管理、设计、文档和代码等贡献。
-
期末夺旗赛(10%):用于综合实践所学知识。
关于作业提交的重要说明:请自行管理时间,按时提交。如遇特殊情况可申请延期,但不会单纯因时间管理不佳而批准。允许对未获A的作业进行修改并重新提交以提升成绩。严禁抄袭,违者将不及格。
所需系统与资源 💻
我们将主要使用Unix系统,且全部通过命令行访问。
以下是本课程将使用的系统环境:
- 亚马逊AWS EC2:主要实验环境。作为史蒂文斯学生,可通过AWS教育项目获得积分。请务必在使用后关闭实例以控制费用。
- 史蒂文斯Linux Lab系统:所有学生应已拥有账户。
- 本地虚拟机(可选):例如在VirtualBox中安装NetBSD。可参考CS631课程的安装指南。

请注意,本课程不是Unix使用入门课,假定你已能熟练使用命令行。
核心课程资源与沟通 📧
请务必收藏以下资源,它们是本课程信息的主要来源。
以下是必须关注的核心资源列表:

- 课程网站:所有课程材料的权威来源。史蒂文斯Canvas系统仅提供链接指向该网站。
- 课程邮件列表:主要沟通渠道。所有重要通知和讨论都发生在此。请使用史蒂文斯邮箱订阅。除成绩或个人事务外,所有问题都应发送到邮件列表,以便所有同学受益。
- Slack频道(可选):用于半同步讨论、分享链接和作业思路。非强制加入,重要通知仍通过邮件列表发送。
- 推荐教材:提供免费PDF版本,链接在课程网站上。
课前准备与本周任务 ✅
为了从本课程中获得最大收益,请养成以下学习习惯:
在每次讲座前,复习上周的幻灯片和笔记,完成问卷,观看视频讲座。课后,运行讲义中的命令示例以加深理解,并通过邮件列表跟进未解决的问题。
课程网站还提供了许多非计分的推荐练习,强烈建议你将其作为自学工具。
本周的具体任务是完成所有课程设置:收藏所有资源、初始化课程笔记Git仓库、确保能访问Linux Lab并设置好AWS账户。如果遇到问题,请在邮件列表或Slack中提问。
总结与预告 🎬
本节课我们一起学习了CS615系统管理课程的介绍、学习目标、评分方式以及所需资源。我们探讨了系统管理的学术定位、邓宁-克鲁格效应,并强调了学会学习和有效沟通的重要性。
在接下来的视频中,我们将深入探讨如何具体完成作业、在Linux Lab上使用Git、Unix操作系统的历史,以及系统管理员究竟做些什么——除了胶带、WD-40和扎带,还有更多内容值得探索。


敬请期待下次课程!
002:系统管理员的工作 👨💻
在本节课中,我们将要学习系统管理员这一角色的具体工作内容。我们将探讨其职责的广泛性、所需技能以及他们如何解决各种技术与非技术问题。
在上一节视频中,我们讨论了在线课程的一些行政事务。现在,让我们进入更有趣的部分。
系统管理员的工作是什么?
你们都已注册了这门名为“系统管理”的课程。我假设大家对系统管理员的工作都有一些概念。有趣的是,你们可能都错了。当然,某种程度上你们也都是对的。因为系统管理员的工作并没有非常明确的定义。
当然,你们可能看过像《黑客帝国》里Trinity那样的酷炫角色。等等,那部电影是22年前上映的,你们有些人可能还没出生。看来我得更新我的流行文化引用了。无论如何,我们来谈谈系统管理员的工作。
系统管理员的自我认知与他人看法
作为系统管理员,我们当然有偏见,会把自己视为故事中的英雄,而将技术世界中的其他参与者视为小丑。但同样真实的是,其他人也常常以不那么积极的眼光看待我们。我们将在视频末尾回到这种“部落主义”的话题,但先让我们思考一个系统管理员具体做什么的例子。
一个具体的例子:香蕉Wi-Fi
除了对所有人不屑一顾并单枪匹马拯救人类之外,系统管理员还做什么呢?这里有一个例子。
“想连Wi-Fi?摸一下香蕉。” 我喜欢这个例子。它展示了系统管理员可能遇到的奇怪情况以及他们如何解决问题。为了给人们提供有时限的Wi-Fi访问,前台接待员原本需要管理电子表格并打印出密码。而这位系统管理员,将一个树莓派连接到香蕉上,利用触摸香蕉时检测到的电压降作为信号来生成新密码。这是一个用巧妙且经常出人意料的创意方案来自动化手动任务的例子,非常具有系统管理员风格。
系统管理员的工作范围

但让我们尝试更具体一些。系统管理员到底做什么?当然,系统管理员与各种计算机打交道:工作站、过时的古老硬件、笔记本电脑、像我们刚才看到的树莓派这样的小型计算机。当然,还有更大的计算机。
这里展示的是一个用于数据中心的H刀片系统,它允许在单个机箱内封装多个物理服务器。这些机箱里装满了服务器刀片,它们空间和能源效率高,并通过高速网络和存储网络连接,通常具有热插拔和带外管理功能,可能针对虚拟服务器进行了优化,以实现高计算密度。
当然,你还可以进一步扩大规模。这里看到的一套SGI Altix系统,通常用于构建世界上一些最强大的超级计算机,每个系统最多可包含2048个双核微处理器。是的,如果里面没有装满计算机和线缆,这些机架大到你可以走进去。
容器化与基础设施管理
当然,如今人们不仅可以在单个服务器或CPU上运行任务,还可以在容器中运行。到处都是容器。因此,系统管理员自然需要精通容器,以及如何使用Docker和Kubernetes等工具管理工作负载。网上有很多关于容器和“运输代码”的梗。我们自然要将其发挥到极致,思考如何管理这样的基础设施。也就是说,我们从事的业务是建造“运输船只”,将“集装箱”(容器)运送到我们在云端和数据中心的服务器上。这里面可能还隐藏着某种递归笑话。
数据中心与秩序创造
是的,系统管理员经常在数据中心工作,有些数据中心看起来就像这样。说实话,这不是一个真正的数据中心,而是我以前工作过的一家公司本地机房的景象。你看到的是一个典型的设置:带锁的机柜、沿过道排列的机架、高架地板、成箱成架的设备,以及到处乱跑的线缆,有点乱。
这时系统管理员来了。他们抓起扎带(系统管理员第二喜欢的工具),还有强大的标签打印机,很可能还有不健康剂量的咖啡因或其他完全合法的药物。经过几个不眠之夜,我们清理了混乱。也就是说,系统管理员为混乱带来秩序。我们这个领域从不缺少强迫症,但我们更愿意将这种一丝不苟视为一种美德,一种做正确事情的奉献精神,因为我们知道,这种时间和精力的投入,在未来需要确定哪根网线连接了哪台主机到哪个交换机端口时,会带来数倍的回报。
数据中心设计与冷却技术
有一个专门的子领域致力于整洁数据中心机柜的艺术,通常被称为“线缆艺术”。顺便说一句,并非所有这些线缆都一定是系统管理员手动铺设的。达到一定规模后,你可能可以订购预制的线缆,尽管有些人认为这是作弊。好了,一个整洁的数据中心过道可能看起来更像这样。你可以看出,蓝色闪烁的灯光非常受欢迎,保持事物一致性的趋势也是如此。
随着计算需求越来越大,你最终会填满一排又一排装满服务器的机架,然后你就必须开始考虑所有这些系统的冷却问题。因此,系统管理员可能参与数据中心及其冷却技术的设计。
你在这里看到的不是内布拉斯加州偏远地区的农场,而是内布拉斯加州偏远地区的一个数据中心,准确说是雅虎的一个数据中心。这些建筑形状有点奇怪,因为数据中心使用了一种专利冷却方法——雅虎鸡舍设计。这不是玩笑。雅虎的工程师,包括系统管理员,从鸡舍如何分配气流中获得灵感,设计了他们的数据中心。结果,使用空调或其他电气系统的冷却需求急剧下降,大部分冷却只是通过“开窗”的方式完成。
网络、安全与无线挑战
这些数据中心有各种访问控制,包括安全摄像头和生物识别读卡器。所有这些设备如今都连接到互联网,运行着Web服务器,并且遭受远程代码执行漏洞的困扰,而某个可怜的系统管理员必须尝试修补或以其他方式缓解这些漏洞。为这类设备设计隔离区域的网络也可能属于系统管理员的工作范畴。我们作为这些控制系统的使用者,监控着它们的输出。
当然,要安装这样的设备,每个系统管理员都有一个装满各种随机线缆的大盒子或抽屉。因为不知何故,世界上没有制造商愿意使用与其他制造商相同的线缆。即使在使用像USB这样的行业标准时,为了让你抓狂,它也有30种变体。在互联网历史上,没有一个人第一次尝试就能正确插入USB线缆。所以,连接东西绝对是系统管理员的工作。
这让我想起了史蒂文斯理工学院教室里使用的各种投影仪。这是一个大问题,因为不同的教职员工会使用不同的设备,而且永远没有正确的适配器。所以几个学期前,我很高兴在每个教室都发现了这些装置,在我看来这非常像系统管理员的解决方案:将最常见的适配器串连到投影仪电缆上。

无线化与远程支持
当然,现在通常不再需要线缆了。一切都无线化了,这带来了另一整套问题。因为现在你必须支持人们在你不控制的环境中工作,而这些环境实际上可能只是邀请别人入侵,因为你可以保证,在某个星巴克里,某个聪明的计算机科学专业学生已经设置了一个流氓Wi-Fi接入点,正在嗅探你所有的流量。但如果用户能访问你的网站,那仍然是你的错。另一方面,如果你是星巴克的系统管理员,你面临的问题是为随机人群支持基础设施,或者确保他们不使用你的系统做那些会让你承担责任的事情。真是“美好”的时光。
编程、命令行与日常工作
所以你回到你的办公桌。等等,这是什么?打孔卡。我们以前就是这样编程的。很久以前,你写好代码,按顺序排列好打孔卡,然后走过去交给操作员,你希望他能把卡片喂进计算机。但实际上,他可能绊倒了,把你的卡片全撒了,你现在必须把它们按正确的顺序放回去。好吧,至少我们不再需要那样做了,尽管现在我们得处理“undefined is not a function”之类的废话。
但我们确实写很多代码,系统管理员。总的来说,我们做很多打字工作。所以为了我们的手腕着想,我们应该使用一些符合人体工程学的键盘,比如这里展示的Kinesis键盘。这些键盘很棒。它们提醒你,你可能不擅长盲打。我肯定不擅长。当我在史蒂文斯做系统管理员时,一位计算机科学教授用了这样一个键盘,我很快发现我其实不太记得我的密码,它们都在我的肌肉记忆里。我在他的键盘上登录花了好一会儿。当然,这也没帮上忙,因为他把他的键盘布局改成了Dvorak而不是QWERTY。我很快在他的办公室里放了一个备用键盘,这样如果我必须在他的系统上工作,我就能正常打字了。
总之,既然我们做很多打字工作,当然我们绝大部分工作都是在命令行上完成的。也就是说,我们日复一日地在控制台上操作。系统管理员知道,或者至少应该知道,这里飞过的所有信息是什么意思,因为每一条都不是操作系统为了好玩而生成的,而是为了提供一些有意义的信息。你在这里看到的是一个NetBSD AMD64虚拟机启动,绿色的内核消息,以及init接管后生成的白色消息:获取DHCP租约、启动SSHD、Postfix、Cron等等,最后提供登录提示。大多数时候,我们甚至看不到这些消息,因为我们的大部分工作都是通过互联网远程完成的。
互联网与网络设备
对了,互联网。这当然是系统管理员与之打交道很多的东西。我们使用互联网,我们也构建互联网,一个由网络组成的网络,正如这张局部地图所示。
当然,要做到这一点,我们需要的不仅仅是计算机。所以系统管理员也经常处理网络设备,比如这里展示的以太网交换机。交换机允许在OSI堆栈的第二层进行网络连接,我们对此相当熟悉。但如果你想构建一个更复杂的网络并连接到其他网络,你还需要一些路由器,比如这个。你可以看出,这与我们一分钟前看到的刀片服务器有相似的外形和设计。当然,这并不奇怪。为了便于安装在数据中心内的标准化机架中,这些设备遵循相同的标准,并且通常也可以通过更换插入机箱内背板的单个刀片,以类似的方式进行扩展或升级,这取决于你的路由需求。
典型基础设施架构
我们为什么要处理所有这些网络设备?通常是因为,作为系统管理员,我们负责运行使用HTTP作为通用协议的Web服务器来提供某些服务,当然,我们也希望它使用TLS来保护连接。但这并不是正常基础设施的样子,对吧?首先,我们通常不止有一个Web服务器,我们可能还需要将一些数据存储在冗余的数据库中。然后我们在Web服务器前面有一个负载均衡器,并且可能决定不允许任何流量,也许是通过使用防火墙在Web服务周围创建一个安全边界。当然,随着我们基础设施的增长,我们可能需要添加某种消息队列系统、一个大型存储阵列,也许还需要Zookeeper来协调一些服务。当然,我们需要允许我们的一些工程师和开发人员访问,所以我们让他们的笔记本电脑、工作站和移动设备访问这些内部资源。
但是,该死,人类往往想在某些时候回家。或者可能发生了一场疫情,突然间,人们不再在办公室了。所以我们需要允许外部人员访问你的边界。更重要的是,你可能必须与一堆第三方服务集成,因为现在一切都生活在云端。拜托,如果你不使用GitHub、G Suite和Slack,你真的能称自己为让世界变得更美好的潮人吗?当然不能。
所以你在防火墙上打了一些洞,让这些连接进来。迟早,你的老板会告诉你,他们决定将整个基础设施迁移到云端会好得多。为什么不呢?所以突然间,你在别人的计算机上运行。说实话,云真的就是别人的计算机。

系统管理员的工具箱
是的,所以系统管理员参与构建、支持和维护这个拼图的几乎每一块。我告诉你,没人会注意到。好吧,直到事情出错,而它们迟早会出错。这时,当服务器真的着火时,人们才会突然想起你,打电话叫你来收拾烂摊子。
所以你抓起你信赖的皮制多功能工具开始工作。说真的,每个系统管理员都知道要有一个这样的工具,因为你永远不知道在机房会发现什么,什么东西需要你拧开、撬动或拆卸,然后再组装回去。事实上,系统管理员在工作中最终会使用到的物理工具数量惊人。
比如,圆锯。当你想到系统管理时,这不一定是立刻浮现在脑海中的东西。但在跑了几趟五金店,买了越来越强的钻头,为了把螺栓拧进异常坚硬的水泥地板以固定机架之后,我曾经能够通过切割一些木板来支撑机架里一些非常重、非常快乐的备用电池,用的就是这里展示的这样一把锯。也许这让你对系统管理员职业道路的多样性有了一些了解。不是每个人都必须操作重型机械,但系统连接的方式多种多样,必然会让你走上那条路。

不可避免地,我们要处理需要电力的系统。所以你可能还需要了解一些关于停电时会发生什么,为什么拥有柴油发电机可能是个好主意,并且要记住,为了让发电机工作,你还需要柴油燃料,以及了解你的系统消耗多少电力,哪些系统有更高的优先级需要恢复在线,等等。
软件工具与核心能力
好了,长话短说。每个系统管理员都有自己的工具箱,他们从中取出今天需要的东西来修复任何损坏的东西。很多时候,这涉及到管道胶带和WD-40,或者它们在软件编程中的等价物。你可能听说过人们称Perl为互联网的管道胶带。伙计,人们可不是在开玩笑。如果你看看互联网的内部,你会惊讶于事物是如何被粘合在一起的。在你的系统管理员职业生涯中,你将有机会了解很多关于这里的“胶水层”。
然而,最终,系统管理员工具箱中最重要的工具是你的大脑。你必须为老问题想出新的解决方案,将老方案应用于新问题,或者介于两者之间的一切。无论你最终是支持人们的工作站、在云端运行互联网关键基础设施、设计和运营数据中心,还是管理介于两者之间的一切,这个职业都需要大量的独创性和好奇心。
定义与总结
好了,经过这次对Jan随机图片集的旋风式浏览,我们是否更接近回答之前提出的问题:系统管理员到底做什么?我们几乎已经看到,不可能有一个统一的工作描述,没有统一的职业道路,而且系统管理员通常只被认为是让事情运行起来的人。也就是说,我们经常在幕后工作,很少被看到,除非灾难发生。
因此,系统管理员可能扮演许多角色,有时被称为IT支持、操作员、网络管理员、系统程序员、系统经理、服务工程师、站点可靠性工程师或任何其他变体。近年来,除了系统管理员之外,广泛使用的另外两个主要职位是DevOps和SRE,而这里显示的差异其实并不那么具体。相反,这取决于组织如何定义这个角色。一个组织中的DevOps可能是另一个组织中的系统管理员,而SRE可能执行相同或完全不同的任务,这取决于公司。事实上,使用的头衔通常是组织成熟度和规模的函数。毫不奇怪,“站点可靠性工程”这个术语起源于谷歌,在那里它被描述为“当软件工程师被分配去做过去被称为运维的工作时会发生什么”。
也就是说,在这个领域工作的人可能承担的职责范围可以用下图来说明。在小型环境中,你可能只有少数员工执行这些任务,也许只有一个人,他们负责从日常操作任务到服务规划和基础设施支持的一切。然而,在更大的环境中,这些职责被分配给多个人,甚至在大型组织中分配给多个专家团队。如果你愿意,你可以在你的系统管理职业生涯中回顾这张图,但不幸的是,这仍然没有真正给我们一个系统管理的定义。
所以让我们试试别的。让我们问问字典。什么是“系统”?嗯,这个怎么样?“一组独立但相互关联的元素,构成一个整体。”我喜欢这个。
那“管理员”呢?“一个指导、管理或分配的人。”那么“系统管理员”呢?“负责管理和维护计算机系统或电信系统的人,例如为商业或机构服务。”
所以这个定义对我来说似乎相当不错,因为它触及了几个重要方面。首先,它清楚地暗示了这项工作包含多个方面,计算机和网络系统的管理只是主要的,但不是系统管理员唯一的责任。其次,系统管理员的工作显然是代表他人管理这些资源,这意味着除了个人目标或愿望之外,还有一个更大的组织或系统参与其中。所以恐怕运行你自己的家庭网络并不能使你成为系统管理员。
让我们再考虑一下“系统”这个词的含义。在这门课中,我们主要关注计算机-人类系统。显然,由一堆计算机组成。以及连接这些计算机的网络。但也包括人的组成部分,因为没有用户的系统,从字面上看是毫无用处的。不幸的是,这一点有时很容易被忘记。同样,用户的行为方式可能与组织的目标和政策一致,也可能不一致。
正如刚才提到的,系统管理员的主要工作职能是代表另一个实体(如公司或其他组织)管理这些系统。因此,这强烈影响并塑造了我们管理的系统的几乎所有方面。这又把我们带回了之前展示的内容:办公室政治和人类基于某种“我们 vs 他们”的感觉整齐地创建部落联盟的天性。你猜怎么着?这真的没有任何帮助。相反,重要的是系统管理员要懂得如何与来自不同背景的其他人合作。
因为无论我们多么喜欢专注于工作的技术方面,我们都必须记住,解决技术问题是容易的部分。即使调试你的负载均衡器、尝试解开Git的纠缠,或者追踪那个奇怪的bug可能需要你几个小时,这仍然比与人打交道容易得多。编程以及围绕互联网的一切,以及构成互联网的系统管理,所有这一切实际上都是社会性的,需要理解人类及其动机。计算的核心,是一个人的问题。
因此,考虑到本视频涵盖的所有内容,我开始将系统管理视为一种主要解决人们问题的职业。当然,这通常涉及比他们更努力地思考,并花费大量时间说服计算机去做你希望它们做的事,而不是你告诉它们做的事。但最终目标仍然是解决人的问题,以组织和公共利益为出发点,管理你负责的资源和系统。
而这,就是系统管理员的工作,也是我们这门课将重点关注的。
总结与下节预告

好了,现在对系统管理员是什么有了更多的了解,让我们休息一下。我鼓励你们研究一下关于系统管理员、DevOps和SRE的工作描述或差异,并跟随这些幻灯片中包含的链接。在涵盖了“是什么”之后,我们将在下一个视频片段中更多地讨论系统管理的“如何做”。我们将讨论我称之为卓越系统设计三要素的内容:可扩展性、安全性和简单性。我们还将介绍一些指导原则和几条值得做成梗的系统管理与软件工程定律。最后,我们将谈谈为什么人们认为互联网看起来像这样。但我们,系统管理员,已经稍微窥探了引擎盖下的情况,并且知道,事实上,它看起来更像这样。下次见,感谢观看。
003:系统管理员核心原则与法则 🧠
在本节课中,我们将要学习系统管理员工作的三个核心原则:可扩展性、安全性和简单性。我们还将探讨一系列在系统管理和故障排查中非常有用的经验法则。
上一节我们讨论了系统管理员的角色,本节中我们来看看支撑这一角色的核心设计理念。
核心原则
系统管理员不仅仅是维护计算机和基础设施组件,他们控制着整个系统。随着经验和资历的增长,他们最终将负责构建这些系统,设计基础设施及其组件,因此职位头衔经常演变为系统架构师或网络架构师。
当一切运行良好时,系统管理员就承担起规划、设计和监督构建复杂系统的角色,这些系统既要满足当前的需求,也要预见组织未来的需求。我们成为了超级建造者。
但为了实现这一目标,我们需要将基础设施建立在两个基本原则之上:可扩展性和安全性。这两者都无法在系统构建完成后添加。在系统接口定义好之后再尝试应用安全性,只会带来限制和约束。试图让一个具有固有局限性的系统在其未设计的情况下运行,会产生各种“变通方案”,最终结果往往更像一个脆弱的纸牌屋,而非坚固可靠的结构。
但还有第三个核心原则是必要的,它实际上是实现前两个原则的基石:简单性。
简单性既是显而易见的,又是反直觉的。它构成了可扩展性和安全性的基础,因为降低复杂性意味着接口定义更清晰、通信或数据处理中的歧义最小化,以及灵活性增加。与其他两个核心方面一样,简单性也无法事后添加,它必须是架构中固有的。简单性是实现可扩展性和安全性的关键。
让我们更仔细地看看这三个核心原则。
可扩展性 📈
可扩展性是当今经常被提及的一个流行词,问“它能扩展吗?”是在会议中显得聪明的简单方法。但让我们思考一下可扩展性的真正含义。
假设你有一个网站,它变得非常受欢迎。比如它被发布在 Hacker News 首页或一个热门的 Reddit 板块上。你设计不佳的 Web 服务器无法应对突然增加的流量。换句话说,你的系统过载了。那么现在该怎么办?
每当遇到这样的资源问题时,只有有限的几种选择。一方面,你可以垂直扩展。也就是说,你购买一台更强大的服务器来处理增加的负载。垂直扩展很好,通常很容易做到,因为你不需要做太多改变。你购买一台性能强大的服务器,问题就解决了。在某种程度上,你是在用钱解决问题,如果你有钱,这是解决许多问题的好方法。当然,这也只是为你争取了时间。在某些时候,负载可能大到连这台“怪兽”服务器也无法承受。
那么你的另一个选择是水平扩展。当你进行水平扩展时,你增加更多相同的服务器,并尝试分配负载。水平扩展在这里似乎更好一些,因为它允许你持续承担额外的负载。你只需要不断添加服务器。
但水平扩展并不像垂直扩展那么容易。当你添加更多服务器时,你需要分配负载。因此你需要添加一个负载均衡器,而负载均衡器本身也需要进行健康检查和维护。也就是说,你增加了复杂性。天下没有免费的午餐。
当然,你也可以结合这两种方法:使用更强大的硬件,并在它们之间分配负载。通常,当人们谈论可扩展性时,指的就是这个意思。
但有一个方面经常被忽视:一旦你进行了水平或垂直扩展,或者两者兼有,你现在运行的成本就更高了。为了实现真正的可扩展性,你还需要能够缩减规模。如果你的流量没有保持在同一水平,那么你就是在浪费金钱。如果你只使用了一个 CPU 核心的 1%,却拥有一个满载冗余负载均衡服务器的数据中心,那是相当愚蠢的。
因此,我们对可扩展性的定义将倾向于强调可扩展系统的整体灵活性:在运行时适应不断变化的需求的能力。

在这个背景下,尤其是在谈论云计算时,你会经常听到一个术语:弹性,它或许更能描述这个特性。因此,无论是通过垂直还是水平手段,一个可扩展、灵活、有弹性的系统,就是一个能够轻松适应需求变化的系统。在整个学期中,当我们考虑需求可能激增百倍然后又消失的情况时,我们会努力记住这一点。
安全性 🔒
是的,顺便说一下,这是对许多情况下所谓“安全性”的最准确描述。软件信息技术行业常常将系统安全视为事后考虑,视为在满足所有其他功能需求后可以添加到最终产品上的东西。在用户界面确定之后,在所有代码编写完成之后。
因此,人们常常认为安全性和可用性是直接且成反比的关系,这并不奇怪。

然而,这表明降低风险的唯一方法是拿走有风险的功能。但每当你这样做时,用户就会绕过你的限制,或者完全停止使用你的产品。
相反,安全性需要从设计阶段就内置到系统中。也就是说,我们不应该从一个提供所需功能的解决方案开始,然后试图弄清楚如何使其达到安全状态;而应该从一个安全的、受限制的状态开始,然后在不损害安全性的前提下,慢慢添加功能,直到获得所需的能力。

也就是说,我们需要将安全性视为在设计最小可行产品时就存在的赋能因素。
本课程将经常讨论我们遇到的产品或系统中的安全故障或问题,并且很多时候,我们能够指出导致此类问题的产品设计目标之间的差异。

因此,我们将以边缘方式,几乎贯穿每周,并在学期末以总结的方式,涵盖许多与我们关心的计算机-人类系统直接相关的安全方面。这将包括广泛的密码学领域及其在保密性、完整性和真实性方面的辅助能力,物理安全、服务可用性、服务设计、社会工程学以及通常的通用信任。所有这些都应该帮助我们,从用户的角度(询问并理解用户在使用我们系统时真正想要什么)以及系统或产品中心的角度,来讨论每个领域和主题。很多时候,我们会发现这两个视角的交集,比与对抗性视角的交集更多。
简单性:可扩展性与安全性的基石 🧱
对于可扩展性和安全性这两个核心属性,我们已经注意到两者都无法事后添加。


回到之前的数据中心例子,你无法在这样一个混乱的系统中“添加”可扩展性。可扩展性和安全性都是你必须从一开始就包含在系统设计中的东西。
当做到这一点时,这些原则的实际应用会减少接口、端点、用例和整体差异。此时,也值得注意“复杂”和“繁杂”之间的区别。

如左图所示。一个复杂的系统可能组织良好,展现出清晰的逻辑结构,但需要微妙、错综复杂的连接或组件。而繁杂的系统则是不规则、不可预测或难以遵循的。
可扩展且安全的系统复杂性较低,并且非常、非常不繁杂。
作为系统管理员,我们喜欢 KISS。KISS 乐队非常出色,但这里指的不是他们滑稽的服装、烟火或厚底鞋。事实上,我们指的是一个核心系统原则的缩写:KISS。保持简单,傻瓜。也就是说,我们致力于降低复杂性。复杂性是敌人。我们将抵制构建我们不需要的功能的诱惑,转而构建可以组合的工具。
这样,我们将遵循 Unix 哲学,其中包括一个关键准则:构建简单的工具,做好一件事。也就是说,我们将创建像乐高积木一样可以组合的小组件。遵循相同接口并能很好配合的小工具。有了这样小而简单的构建模块。

你可以构建错综复杂、庞大且确实复杂的系统。乐高星球大战千年隼是他们最大的套装之一。它由超过 7500 个零件组成,因此肯定不简单。但我认为这很好地说明了我们追求的目标:小而简单的构建模块,可以组合起来创建复杂但结构良好的系统。
系统管理的一部分,以及软件工程、编程和各种其他相关且重叠的方面和学科,就在于设计和实现这样的复杂系统。因为我们在构建和使用构建模块时牢记简单性,这些系统将更具容错性、性能更好、更灵活。
因此,简单性作为卓越系统设计的第三个核心属性,确实是实现可扩展性和安全性的关键。这就是为什么我们要 KISS。
系统管理经验法则 ⚖️
现在,简单性的概念甚至可以超越系统设计或实现,延伸到我们解决问题的方法上。解决复杂甚至繁杂问题的一种方法是应用奥卡姆剃刀。
奥卡姆剃刀有许多不同的表述,但最常见的是:对于给定的问题,最简单的解释通常是正确的。当你试图排查复杂系统的故障时,记住这一点会很有用,复杂系统往往以复杂的方式失败,但原因通常是简单的。
需要内化的一个类似黄金法则是热力学第二定律,它规定封闭系统的熵随时间增加。应用到系统管理中,这告诉我们事物会趋向于更大的无序。用户越多、流量越大、系统越多,所有这些都会导致更多带有更多错误的软件连接到更多其他系统,如此循环。
长时间运行的系统最终会耗尽内存或磁盘空间,因为它们可能触发导致内存泄漏的边缘条件。硬件最终会失效。牢记这一点可以让我们预见并准备应对不可避免的情况。
我们系统管理法则系列中的下一个是汉隆剃刀。在某种程度上,汉隆剃刀是奥卡姆剃刀的一个变体。汉隆剃刀指出:能解释为愚蠢的,就不要解释为恶意。人们很容易草率下结论,担心某个国家行为体已经渗透了你的网络并植入了后门,而你不小心发现了这个改变了 /dev/null 权限的后门。或者,用户可能只是不小心做了这件事。哪个解释更简单,因此根据奥卡姆剃刀更有可能?汉隆剃刀值得特别提出,因为尤其是当你专注于安全时(系统管理员作为其工作的一部分必须这样做),很容易觉得到处都是恶意行为者。但值得记住的是,任何时候你防止了意外故障,你也帮助减轻了有人故意利用这种故障模式的风险。所以汉隆剃刀也能帮助你。
接下来是帕累托原则。该原则指出,在大多数情况下,大约 80% 的结果来自 20% 的原因,最初应用于经济学。但事实证明它几乎普遍适用。我相信你已经注意到,当你编写程序时,通用功能、所有主要部分都很容易,你很快就能完成大约 80% 的程序,然后将大部分时间花在最后 20% 上,调试棘手的问题并微调功能。所以,你 80% 的时间花在了 20% 的功能上,反之亦然。这个经验法则在估算软件开发时间、估算客户将如何使用可用磁盘空间、网络过滤器将捕获或阻止什么流量等方面,出人意料地有用。80/20 法则甚至可以递归应用,你可以再次估算需要在关键的少数上花费多少资源,以及在不可避免的长尾上花费多少。
然后是“垃圾”。等等,什么?那甚至不是鲤鱼,那是鲟鱼。好吧,斯特金定律。这是一条以科幻作家西奥多·斯特金命名的格言,他 famously 说过“90% 的一切都是垃圾”。他是在有人指出 90% 的科幻文学都是垃圾后说这句话的,但他观察到“那只是因为 90% 的一切都是垃圾”。也就是说,大多数东西都不出色。系统管理员很快就会痛苦地发现,无论是开源软件还是商业供应商提供的软件,这条定律都异常准确。它实际上并不像听起来那么虚无,而是一个有用的提醒,帮助你设定期望。
同样,我们都相当熟悉墨菲定律,对吧?会出错的事总会出错,或者更准确地说,可能发生的事情就会发生。和斯特金定律一样,它并不完全是悲观的,但最好记住,尤其是在涉及软件时。在那个领域,当我们在互联网规模上谈论软件系统时,我们更不能以“发生这种情况的几率有多大?”或“谁会在这个字段里输入那个?”为借口而不做准备。如果它可能发生,它就会发生。硬盘驱动器会故障。用户会在字段中输入无效数字。连接会中断。你双重冗余的电源最终会同时失效。我们的工作就是为这些可能性做好准备,预见可能发生的情况,并构建在这些情况下仍能运行的健壮系统。
最后,当我们谈论可能发生的事情,然后逻辑上不可能发生的事情不会发生时,我们最终进入了哲学领域,以帮助我们排查系统故障:因果律。对于每一个结果,都必须有一个原因。事情不会无缘无故发生。系统不会无缘无故崩溃。你的软件爆炸、负载均衡器将流量从健康的源服务器移开、你的数据库当前被锁定、你的流量再次激增,这些都有原因。有时这些结果的原因很难找到,但它们就在那里。
我刚才提到的各种规则和定律,希望能帮助你排除不合理的解释,并引导你找到真正的原因。所需要的只是坚持不懈和致力于找到问题的根源。
课程安排与建议 📚
好了,这些只是每个系统管理员最喜欢的一些法则中的一部分。你会发现我们将在整个学期中引用它们,我敢打赌,几年后在你的工作中遇到某些情况时,你也会想起它们。当然,还有许多类似的法律和观察,我们无疑会在每周的课程中穿插一些。但今天,我想我们已经讲得够多了。
那么,让我们看看接下来会发生什么。本周介绍的最后一个主题是 Unix 的历史,因为我们将在本课程中专门使用 Unix 系统,并且由于系统管理在许多方面与 Unix 系统及其发展和演变紧密相连,了解这个操作系统的历史非常重要。为此,你应该观看我上学期为我的另一门课程“CS 631: Unix 环境下的高级编程”录制的视频片段(链接在此)。该视频涵盖了 Unix 历史的所有关键方面,并解释了一些核心功能。虽然面向程序员,但 Unix 系统管理员也能从中受益并转化这些经验。在整个学期中,你会看到我们回到这个视频的某些方面,所以请不要认为这是可选部分,而是系列中的必修视频。
之后,我将解释各种作业以及我们如何设置在本课程中使用 Git。但这基本上就是第一周的内容了。恭喜你。
下周,我们将讨论存储模型和磁盘。因此,请务必阅读课程网站上发布的阅读材料,并在第二次互动课之前提交课程问卷。测验网站还包括每节课的一些不计分的练习,我强烈建议你在准备下一次视频讲座时完成这些练习。
此外,这里有一些方法可以让你复习本周涵盖的主题:
以下是你可以尝试的练习:
- 了解公司基础设施:尝试了解某些公司如何管理其基础设施。你应该会发现许多公司都有公开博客,他们在上面发布大量关于他们使用的基础设施或软件产品的信息。这可能非常有趣,并让你很好地了解他们可能面临的可扩展性挑战。
- 研究相关学位课程:尝试查找有哪些学校授予系统管理学位,以及他们的课程设置是什么样的。将其与我们将在本课程中涵盖的内容进行关联。
- 审视现有工具:当我们准备在 Unix 环境中进行一些实际工作时,也许可以看看你目前如何使用系统。你最常使用哪些工具?在分析它们的复杂性和接口时,它们表现如何?
我想所有这些应该能让你忙上一阵子了。请记住,你能从这门课中获得什么取决于你自己。你投入更多精力去完成这些练习(即使它们不计分),你希望学到的就越多。
总结

本节课中我们一起学习了系统管理的三个核心原则:可扩展性、安全性和简单性。我们了解到,可扩展性和安全性无法事后添加,必须从设计之初就考虑,而简单性是实现这两者的基石。我们还探讨了一系列实用的经验法则,如奥卡姆剃刀、汉隆剃刀、帕累托原则等,这些法则将在未来的系统管理和故障排查中为我们提供指导。最后,我们明确了接下来的学习安排,包括了解 Unix 历史的重要性以及为下周课程所做的准备。
004:第01周 - UNIX历史 📜
概述
在本节课中,我们将回顾UNIX操作系统的历史。我们将了解它的起源、发展过程中的关键事件、许可证战争,以及它如何演变成今天无处不在的操作系统家族。
UNIX的起源
上一节我们介绍了课程大纲,本节中我们来看看UNIX操作系统的历史起点。
UNIX操作系统家族的发展历程漫长,从一个在PDP-7上运行的太空旅行游戏测试平台,演变为如今使用最广泛的服务器操作系统。它的历史始于新泽西州的AT&T贝尔实验室,由丹尼斯·里奇和肯·汤普森,与麦克罗伊博士和乔·奥桑纳一起,共同开发用于替代Multics操作系统。
C编程语言是并行开发的,由丹尼斯·里奇在UNIX上开发,源自肯·汤普森为他们编写的新操作系统开发的B语言。随后,大约在1973年左右,UNIX本身被用C语言重写。
这里有一个问题:既然它是重写的,那么最初是用什么写的呢?答案是,早期UNIX是用针对特定目标平台和硬件的汇编语言编写的。只有当肯·汤普森和丹尼斯·里奇将C语言发展为一种系统编程语言后,他们才决定重写操作系统以利用这种新的高级编程语言。这样一来,操作系统变得可移植,意味着它不再与特定硬件绑定,可以为其他平台重新编译。
BSD的诞生与传播
了解了UNIX的诞生后,我们来看看它的一个重要分支是如何形成的。
1975年,肯·汤普森从贝尔实验室休假,作为访问教授前往加州大学伯克利分校。由于其母公司AT&T被禁止销售该操作系统,实验室将其连同完整的源代码一起授权给学术机构和商业实体。这最终直接导致了开源概念的产生,当时加州大学伯克利分校的计算机系统研究小组(CSRG)通过他们的补丁集扩展了操作系统。
研究生杰克·哈雷和比尔·乔伊(后者于1982年共同创立了Sun Microsystems)添加了新工具和其他软件,并最终开始将其作为伯克利软件发行版(BSD)进行分发。在此期间,发展出了两个主要的UNIX谱系:源自BSD或受其影响的系统,以及源自后来成为System V(最早的商业UNIX版本之一)的系统。
随着不同操作系统的持续开发,伯克利分校的团队在DARPA研究资助下增加了TCP/IP协议栈。这个协议栈至今仍是TCP/IP的默认实现,并可以在许多其他操作系统中找到。
许可证战争与开源发展
随着BSD的流行,商业利益引发了著名的许可证诉讼,这对开源运动产生了深远影响。
与此同时,一家名为BSDI的公司开始销售他们基于伯克利软件发行版的UNIX版本,称之为BSD/OS,并将其品牌定为“UNIX”。他们甚至有一个名为1-800-ITS-UNIX的热线电话。贝尔实验室已将操作系统和源代码授权给伯克利,但BSDI正在销售基于此代码的产品,因此被当时的AT&T贝尔实验室子公司UNIX系统实验室(USL)起诉。
BSDI声称他们不可能有过错,因为他们的代码来自加州大学伯克利分校。于是USL表示,他们也会起诉加州大学伯克利分校。但事情变得有趣起来。BSD补丁一直是在所谓的BSD许可证下授权的,简而言之,该许可证允许你随意使用此代码,包括将其作为闭源销售,但你需要注明出处。听起来很简单,对吧?而且BSD补丁包含了许多很酷的东西,如TCP/IP、NFS、vi编辑器等等。
当时许多商业UNIX提供商已经将这些补丁整合到他们授权和销售的产品中。但USL似乎忘记了在他们的版权声明中包含免责声明,说明代码至少部分源自BSD,而根据许可证条款,他们必须这样做。因此,当面临诉讼威胁时,加州大学伯克利分校(那些精明的加州人)表示,他们要反诉USL。
一段时间后,案件达成和解。加州大学伯克利分校将重写受旧AT&T许可证限制的部分代码,从而形成一个没有任何专有代码的代码库,称为4.4 BSD Lite。此时,在18000个文件中,大约只有6个文件仍包含受限代码。最终,这些代码也被重写,4.4 BSD Lite版本的BSD成为了新的无限制版本。
Linux的崛起与GPL许可证
当BSD陷入法律纠纷时,另一个重要的操作系统内核登上了历史舞台。
与此同时,在芬兰,一位名叫林纳斯·托瓦兹的年轻计算机科学学生一直在研究Minix(由安德鲁·塔能鲍姆创建的类UNIX操作系统),并且刚刚完成了他自己的操作系统内核,名为Linux,于1991年在互联网上发布。
林纳斯为他的内核选择了GNU通用公共许可证(GPL)。该许可证源自GNU项目,这是80年代初由麻省理工学院的理查德·斯托曼发起的一个项目,旨在开发一个完全自由的类UNIX操作系统。GNU项目已经编写了编译器、编辑器(当然是Emacs)、各种实用程序和其他所有东西,但一直缺少一个内核。没有内核的操作系统不是真正的操作系统,就像没有操作系统其余部分的内核也不是操作系统一样。
因此,在GPL下发布的Linux内核的宣布,使得GNU项目最终能够创建一个完整的操作系统:GNU/Linux。但事实上,如今每个人及其兄弟姐妹都将其简称为“Linux”。
与BSD许可证相比,GPL对软件接收者施加了额外的限制。这似乎有些矛盾,因为它是一个自由软件许可证,但它确实增加了一个重要的限制:你可以以任何方式自由使用代码,但如果你对代码进行了任何更改,这些更改必须在相同条款(即GPL)下发布。这与BSD许可证形成对比,BSD许可证仅声明你可以做任何你想做的事情,包括进行修改、保留这些修改,然后销售最终产品,只要你承认原始代码的来源。
这个新操作系统GNU/Linux的诞生,尽管许可证限制更多(通常可能使企业犹豫是否采用),但在USL诉加州大学伯克利分校/BSDI诉讼进行期间,可能直接导致了Linux比BSD变体获得更广泛的采用,并可能造成了Linux如今的市场主导地位。当然,我们无法确定,因为我们无法回到历史中去评估“如果”。
现代UNIX格局
经历了数十年的发展,如今的UNIX世界呈现出怎样的面貌?
无论如何,在整个90年代和21世纪初,许多商业UNIX版本失去了市场份额,但有趣的发展仍在继续。例如,Darwin操作系统源自NeXTSTEP,使用Mach微内核,用户空间代码来自FreeBSD和NetBSD。它大约在2000年诞生。这并不奇怪,因为离开苹果后又在NeXT工作的史蒂夫·乔布斯一直在使用这个内核,并在回归苹果后开始开发这个操作系统,后来发展成为macOS 10。
Solaris(继Sun Microsystems的SunOS之后的操作系统)在合并了许多BSD补丁和一些System V衍生的功能后,开发了许多其他突破性功能,包括ZFS(一个具有新颖想法的高级文件系统)、DTrace和容器(当时容器化尚未广泛使用)。Android(Linux变体)和iOS(实际上是Darwin的版本,因此源自BSD)最终出现在我们的移动设备上。
因此,在过去的50年里,我们看到了数量惊人的UNIX系统。其中一些是“正宗UNIX”系统,直接源自AT&T代码。一些是“商标UNIX”版本,意味着它们经过了认证以满足UNIX规范。这种商标认证非常昂贵,因此没有多少公司会这样做,而且每次进行更改都必须再次经过相同的认证。因此,许多操作系统提供商,尤其是开源项目,不会进行认证,也不会成为商标UNIX,即使它们是所谓的“类UNIX”系统。此外,还有所谓的“类UNIX”操作系统,意味着这些操作系统没有共享任何线性代码,但它们的外观和行为就像UNIX系统一样。
有趣的是,尽管这些不同的UNIX变体在很大程度上行为方式相同,这意味着如果你能使用一个操作系统,你应该能够快速轻松地适应另一个。如果你能为一个操作系统编写代码,你应该能够快速调整你的代码以适应其他系统。这里与Linux的区别在于,所有这些都是独立的操作系统,而我们所看到的所有不同Linux发行版仅仅是Linux发行版,是Linux的打包版本。
但在现实中,在所谓的“神秘现实世界”中,你只会遇到这些不同操作系统中的一小部分。因此,这里有一个列表,主要将它们归类为Linux、BSD和其他,尽管“其他”类别与前两者有重叠。再次强调,Linux是一个类UNIX操作系统,只是碰巧有数量惊人的发行版,不同的项目或公司将不同的软件捆绑在一起。一家公司可能添加Web服务器或数据库服务器,另一家公司可能专注于桌面使用,但仍然挑选和修补相同的部分(内核、其他库和工具),将它们捆绑在一起,或许提供支持,并称之为发行版。这与其他操作系统并无不同,其他操作系统是完整的单元,不能被拆分或重组。我们没有多个NetBSD发行版,只有一个NetBSD。我们没有多个FreeBSD、OpenBSD或DragonFly BSD的变体,它们都是连贯的、统一的整体。
本课程的参考平台:NetBSD
现在,Linux、BSD和移动平台之外的商业UNIX平台的市场份额在过去几年中日益萎缩。因此,你不太可能遇到我在上一张幻灯片中展示的各种操作系统变体。但尽管如此,其中一些仍然存在并努力工作。
我们本课程的参考平台是NetBSD。NetBSD是一个真正的、正宗的类UNIX系统,尽管它不持有UNIX商标。开源NetBSD基金会没有财力在每次发布新版本时都为其产品进行认证。但作为一个完整的操作系统,它不仅提供内核,还提供系统库和用户实用程序,所有这些都是一起开发的,提供了一个连贯的自我和完整的操作系统映像。
作为一个完整的操作系统,它还包含一些附加信息,例如与BSD相关的UNIX历史摘要。你可以在/usr/share/misc目录下找到这段历史,浏览这棵树可能会很有趣。正如你所见,我们这里有一个缩短版的UNIX历史,通过识别不同的谱系并显示不同BSD变体之间的发布日期关联来回顾。
浏览这里,我们可以看到DragonFly BSD如何从FreeBSD分叉出来,OpenBSD如何从NetBSD分叉出来,然后随着不同版本的发布,我们向前推进时间线。在文件的末尾,我们有一个按项目列出的所有版本发布日期列表,包括关于不同分支和操作系统的附加历史信息。浏览一下看看所有这些内容最终在哪里可能会对你有用。
UNIX家族树与Linux发行版图谱
为了真正理解“UNIX”一词的使用有多广泛、含义有多多样,让我们看看完整的家族树。你可以看到大多数UNIX版本的时间线,它们都是从贝尔实验室的原始系统分叉出来的。向右滚动,我们可以看到伯克利软件发行版及其各种版本的诞生,以及几个商业UNIX发行版。
在1984年,我们看到Minix诞生。在80年代中期,你会注意到有很多变化,大量代码在不同的操作系统之间来回流动。值得注意的是,大多数操作系统供应商都在导入BSD补丁集以获得伯克利开发的所有功能。
在1991年,我们看到Linux内核的诞生,从时间线中分叉出来。1993年,你看到NetBSD首次出现,随后不久,同年12月FreeBSD诞生。NetBSD开发团队内部的分歧导致1995年从NetBSD时间线分叉出OpenBSD。
在90年代末,苹果开始开发macOS X(或macOS 10),大约在2001年左右正式发布用于消费产品(macOS Server在此之前发布)。进入21世纪初,我们注意到不同操作系统之间明显的交叉影响减少了,幸存的商业版本也似乎更少了。
2008年,Android出现,这是一个用于移动设备的Linux版本。而苹果的iPhone OS后来变成了众所周知的iOS。我们注意到基于宽松的BSD许可证,部分代码存在共享。例如,我们看到NetBSD代码流入其他一些产品,如2012年左右最近的Minix版本,以及FreeBSD代码反馈到不同产品中,如Junos和PC-BSD等。
当我们回顾2019年和2020年时,只剩下少数流行的UNIX版本。我们在这里看到的最终版本包括NetBSD、FreeBSD、macOS、OpenSolaris、Linux(当然)、Android、HP-UX。但也就这些了,与80年代相比,这是一个稀疏的领域。

现在,以免你认为Linux本身的谱系不那么复杂,这里快速看一下不同的Linux发行版是如何随时间发展的。它看起来和常规的UNIX时间线图一样疯狂,不是吗?我们在这里也识别出几个主要谱系。有Debian,当我们沿着这条棕色的线向右看,它衍生出Ubuntu及其所有变体。我们看到许多短命的项目。然后我们看看Slackware Linux(最古老的发行版之一,与Debian并列),它也衍生或分叉出SuSE变体。在我们图形的底部,我们看到Red Hat Linux,如今在服务器市场上享有很高的普及度。

UNIX无处不在的影响与意义
无论如何,正如我们所看到的,UNIX以及作为类UNIX操作系统一个版本的Linux的历史是多样化的。如今,我们发现UNIX几乎无处不在,这并不奇怪。它运行在你的台式机、笔记本电脑、服务器上;它为亚马逊和谷歌提供的公共云提供动力;它运行在你的电视、手机、手表、音响、汽车导航系统(这意味着有时你可能需要靠边停车来安装软件更新)、恒温器、冰箱、烤面包机等等上。这不仅令人着迷,也带来了一些影响。
一方面,这意味着如果你理解UNIX,你将能更好地理解所有这些事物是如何工作的。这也是为什么这门课程特别相关并希望引起你的兴趣。
另一方面,这也意味着你的冰箱现在可能有一个CVE漏洞,你的恒温器运行着一个可能被黑客攻击的Web服务器。我们在物联网设备上运行通用操作系统,却没有太多考虑如何管理这些设备。但请看看你的设备印刷手册,我敢肯定你会发现许多版权声明,写着“本产品包含源自伯克利分校贡献的软件”或“本产品包含由加州大学董事编写的软件”或类似的内容。这是一个充满UNIX的世界,UNIX无处不在。
总结
本节课中,我们一起学习了UNIX操作系统的简要历史。我们从其在贝尔实验室的起源开始,了解了C语言如何使其可移植。我们探讨了BSD的诞生及其与AT&T的法律纠纷,这影响了开源运动。我们看到了Linux在GPL许可证下的崛起,以及它如何成为主导的类UNIX系统。最后,我们回顾了现代UNIX的格局,包括各种商业版本、BSD变体和Linux发行版,并认识到UNIX原则已渗透到我们数字生活的方方面面。

在下一节中,我们将更仔细地研究系统的特性、C编程语言的特性,并讨论UNIX程序设计和哲学。我们最终也将开始编写一些代码来探索这些功能,所以请确保关注下一个视频讲座。下次见!
005:Git基础配置与使用(无需GitHub)💻
在本节课中,我们将学习如何为CS 615系统管理课程配置和使用Git版本控制系统。我们将从设置SSH连接到Linux实验室开始,然后初始化一个本地的Git仓库,并演示如何在本地和远程系统之间同步代码。整个过程将不依赖GitHub。
概述
Git是一个分布式版本控制系统。GitHub是微软旗下提供Git托管服务的公司。虽然GitHub非常流行,但Git的核心功能本身足以支持我们的课程需求。本节将指导你完成基础配置,确保你能顺利开始使用Git进行作业和项目管理。
SSH配置
首先,我们需要配置SSH以安全、便捷地连接到史蒂文斯理工学院的Linux实验室系统。
配置已知主机指纹
首次连接到一个主机时,系统会询问你是否确认主机密钥的真实性。为了安全地连接,我们需要预先将实验室主机的SSH指纹添加到本地的known_hosts文件中。
实验室主机名指向一个负载均衡器,但我们现在只需要一行配置。你可以从课程网站获取正确的主机密钥指纹,并使用以下命令将其添加:
echo "linuxlab.cs.stevens.edu ssh-rsa <此处应放置实际的密钥指纹>" >> ~/.ssh/known_hosts

创建SSH配置文件
为了简化连接命令,我们可以在~/.ssh/config文件中创建一个快捷方式。这样,你只需输入ssh stevens即可连接。
以下是配置示例:
Host stevens
HostName linuxlab.cs.stevens.edu
User your_stevens_username
IdentityFile ~/.ssh/your_private_key
StrictHostKeyChecking yes
- Host: 你定义的快捷名称。
- HostName: 实际的主机地址。
- User: 你在Linux实验室的用户名。
- IdentityFile: 用于认证的私钥路径。
- StrictHostKeyChecking: 启用严格检查,因为我们已配置正确的指纹。
配置完成后,你可以使用ssh stevens命令登录远程系统。
Git基础配置
成功连接到Linux实验室后,我们可以在远程系统上配置Git。
设置用户信息
首先,设置你的姓名和邮箱地址,这些信息会记录在你的提交中。
git config --global user.email "your_email@example.com"
git config --global user.name "Your Name"
初始化裸仓库
我们将创建一个“裸仓库”(bare repository)作为中央存储库。裸仓库不包含工作目录,只包含Git的版本历史数据。
-
创建一个新目录作为裸仓库:
mkdir ~/coursework.git

-
进入该目录并初始化为裸仓库:
cd ~/coursework.git git init --bare
初始化后,请退出这个目录。这个coursework.git文件夹将作为你的远程仓库。
克隆仓库并开始工作
现在,我们从刚创建的裸仓库克隆一份到你的工作目录。
cd ~
git clone ~/coursework.git coursework
这会创建一个名为coursework的目录,里面包含一个连接到裸仓库的工作副本。
基本的Git工作流程
上一节我们创建了工作副本,本节中我们来看看如何使用Git进行基本的版本控制操作。
以下是使用Git添加文件、提交更改和推送的基本步骤:
-
创建或编辑文件: 在工作目录中,创建一个
README.md文件并添加内容。echo “本仓库包含CS 615系统管理课程的所有作业和笔记。” > README.md -
添加文件到暂存区: 使用
git add命令将文件纳入Git的管理。git add README.md -
提交更改: 使用
git commit命令将暂存区的更改永久记录到本地仓库。git commit -m “添加初始README文件” -
推送更改到上游仓库: 将本地提交推送到我们之前创建的裸仓库(上游)。
git push origin main
你可以重复这个过程来管理所有课程文件和笔记。
从本地计算机同步
配置好远程仓库后,你也可以从自己的笔记本电脑上访问和同步代码。

从本地克隆远程仓库
在你的笔记本电脑上,使用配置好的SSH快捷方式克隆Linux实验室上的裸仓库。
git clone stevens:~/coursework.git local-coursework
这个命令会在你的本地创建一个local-coursework目录,它是远程仓库的完整副本。

在本地工作并推送
现在你可以在本地进行修改:

- 编辑文件,例如
week1/notes.md。 - 提交更改:
git commit -am “更新第一周笔记” - 将更改推送到Linux实验室的远程仓库:
git push origin main
在远程拉取更新

当你回到Linux实验室的终端时,可以进入工作目录并拉取最新的更改。

cd ~/coursework
git pull origin main
使用git log命令可以查看完整的提交历史,确认所有更改都已同步。
总结
本节课中,我们一起学习了如何在不依赖GitHub的情况下,为系统管理课程设置和使用Git。我们从配置SSH连接开始,初始化了一个本地的裸Git仓库,并实践了完整的Git工作流程,包括在远程服务器和本地电脑之间同步代码。请确保按照此流程设置,并开始使用Git来管理你的课程笔记和作业。如果在设置过程中遇到问题,请在课程邮件列表或Slack频道中寻求帮助。

祝你学习顺利,我们下次见!🚀
006:熟悉EC2 🚀
在本节课中,我们将总结第一项作业,目标是帮助你完成AWS EC2的使用准备。你将学习如何设置环境、启动并访问EC2实例,同时运行一些为第二周课程(关于存储模型和文件系统)做准备的命令。

概述
本作业的主要目标是让你在首选环境中完成设置,以便能够顺利使用AWS工具,特别是能够毫无问题地启动和访问EC2实例。其次,你需要运行一些命令,为第二周讨论存储模型和文件系统的课程内容做准备。
我们将通过使用命令行工具来完成这些任务,正如在介绍视频中提到的。
环境设置建议
虽然你可以在自己的笔记本电脑、共享VPS或任何你选择的地方设置AWS命令行工具,但我强烈建议你使用史蒂文斯理工学院提供的She服务,即另一个名为Linux Lab的实验室。
在我们关于Git使用的视频中,已经介绍了如何方便地通过SSH访问这些系统。这些系统已经安装了AWS命令行工具,因此从那里开始会容易得多。
理解命令与输出
作业要求你运行一系列命令,你可能会遵循在线教程或搜索“如何开始使用AWS”时弹出的第一个Stack Overflow答案。重要的是,你需要真正理解你所运行的每一个命令。同样,理解所有命令的输出也至关重要。
因此,作业还要求你在提交的报告中提供额外的注释,并描述你遇到的任何问题。
完整的作业链接可以在课程网站上找到。
启动EC2实例演示
当你完成设置后,应该能够像我即将演示的那样启动EC2实例。
首先,通过SSH连接到Linux Lab。在AWS命令行工具正确设置后,你的配置文件应该类似于这样:
[default]
aws_access_key_id = YOUR_ACCESS_KEY
aws_secret_access_key = YOUR_SECRET_KEY
region = us-east-1

请确保妥善保护你的配置文件,因为它包含私有的访问密钥。同时,请不要向我发送你的私有AWS凭证,它们应该保持私有。
接下来,根据作业要求,我们将使用 aws ec2 run-instances 命令来启动一个新的NePSSD实例。根据你的AWS设置,你可能需要指定安全组或子网,尽管在许多情况下,默认的安全组和子网是可以使用的。
我的账户比较旧,它仍然默认为非VPC环境,这意味着我需要为许多AMI创建非默认的安全组和子网,但请不要因此感到困惑。如果你可以在不指定这些的情况下启动实例,那也没问题。同样,你可能有一个默认的SSH密钥对,使用它完全可以。
我根据启动实例的位置使用不同的密钥,这就是为什么你看到我在这里指定了一个非默认的密钥。
你可以选择你喜欢的实例类型,但需要确保为相关的操作系统选择正确的架构。
因此,你可能会在这里运行一个更简单的AWS命令。但无论如何,最终你将启动一个实例,并得到一堆JSON输出,如下所示。
处理JSON输出
JSON输出非常有用,因为你可以将其通过管道传递给 jq 工具来提取各个字段。
在这种情况下,我们将手动获取实例ID。因为我们很懒,不想一遍又一遍地输入它,所以我们将把它赋值给一个环境变量。
INSTANCE_ID=$(aws ec2 run-instances ... | jq -r '.Instances[0].InstanceId')
然后,我们可以通过使用 aws ec2 describe-instances 命令并将其通过管道传递给 jq 来检查新创建实例的主机名。

PUBLIC_DNS=$(aws ec2 describe-instances --instance-ids $INSTANCE_ID | jq -r '.Reservations[0].Instances[0].PublicDnsName')


好了,我们得到了主机名。让我们看看实例是否已经启动并运行。使用 ping 命令检查。

ping -c 4 $PUBLIC_DNS

不,看起来还没有。让我们检查一下状态。使用 aws ec2 describe-instance-status 命令。
aws ec2 describe-instance-status --instance-ids $INSTANCE_ID


看,jq 不是很棒吗?如果你不熟悉它,你一定要查看并练习使用它,它会很有帮助。无论如何,实例显示正在运行。让我们再ping一次。嗯,仍然不工作。

这是EC2有点烦人的事情之一。你启动了一个实例,但你不知道它什么时候准备好。我们将在批改作业时看到一些确定它何时可用的方法。但也许你可以寻找一些其他可能有帮助的AWS命令。

无论如何,让我们回去再检查一下我的ping。等待一段时间后,它似乎终于有响应了。所以实例现在应该已经启动并运行了。让我们登录。
我们指定要使用的正确SSH密钥以及root用户。
ssh -i /path/to/your-key.pem root@$PUBLIC_DNS
看,我们登录到了我们的NePSD实例。我们可以确认这是一个AM D60实例。我们可以通过 dmesg 命令查看启动消息,它显示了内核在启动时如何初始化虚拟硬件。
现在,在这里你通常会运行作业要求你运行的各种其他命令。但现在,我们只是退出。
终止实例
接下来,由于AWS资源需要花钱,我们要确保记得在使用完毕后终止实例。
因此,我们只需运行 aws ec2 terminate-instances 命令。
aws ec2 terminate-instances --instance-ids $INSTANCE_ID
就是这样。

作业完成状态与求助

当你完成作业一时,你应该能够像我在这里展示的那样启动、访问和终止实例。如果你遇到问题,请确保将你的问题发送到班级邮件列表或在Slack上提问。
同时,请务必写下你可能遇到的任何问题,并回答这里提出的所有问题。这里显示的链接可能帮助你完成设置。

总结
本节课中,我们一起学习了如何为CS615课程的第一项作业做准备,重点是熟悉AWS EC2。我们了解了作业的目标、环境设置的建议、理解命令的重要性,并跟随演示学习了启动、连接和终止EC2实例的完整流程。记住,实践是掌握这些工具的关键,如果在过程中遇到困难,积极利用课程提供的沟通渠道寻求帮助。

祝你作业顺利!
007:热身练习 - 设备空间不足 🚨
在本节课中,我们将通过一个热身练习来探索文件系统空间耗尽时可能出现的各种现象。我们将模拟磁盘空间和inode耗尽的情况,并观察系统、命令和用户会话的异常行为。
概述
上一节我们介绍了课程背景,本节中我们来看看一个具体的实践练习。我们将通过一系列命令操作,逐步填满磁盘空间和inode,并观察由此引发的各种问题,例如登录失败、命令无输出、创建稀疏文件等。这有助于我们理解文件系统资源管理的重要性。
开始练习
首先,我们登录Linux系统并检查本地文件系统的可用空间。
df -h
系统显示有11GB的可用空间。接下来,我们使用dd命令创建一个大型文件来耗尽所有磁盘空间。
dd if=/dev/zero of=/tmp/bigfile bs=1G count=11
这个命令需要几秒钟执行,最终会耗尽空间。创建的文件大小为11GB,因此文件系统被填满,无法再写入数据或创建新文件。
空间耗尽的影响

文件系统完全填满后,可能会产生一些不那么明显的负面影响。我们尝试重新登录系统。
ssh user@hostname

可能会收到一些错误信息,但登录似乎成功了。然而,当我们尝试运行命令时,发现没有任何输出。我们检查最初登录的会话中发生了什么。

首先,查看标准输入、标准输出和标准错误设备。它们都指向/proc伪文件系统中的文件描述符。
ls -l /proc/self/fd/
然后,查看当前运行的进程。
ps aux | grep $$
假设当前shell的进程ID是2115,另一个出现问题的会话进程ID是2157。我们可以查看该进程的文件描述符。
ls -l /proc/2157/fd/
我们发现文件描述符0和2指向伪终端/dev/pts/36,但缺少文件描述符1(标准输出)。这解释了为什么运行的命令没有产生任何输出——标准输出被关闭了。
事实证明,我的登录shell(Korn Shell)在登录时会尝试打开一些文件。如果文件系统已满,它无法完成此操作,从而导致会话进入异常状态。
尝试其他Shell
ssh命令允许在连接时指定要运行的命令。我们尝试运行bash。
ssh user@hostname bash
没有错误。我们登录成功了吗?实际上,是的。当通过SSH直接运行命令时,系统不会分配伪终端,但命令(本例中是bash)仍然连接到SSH命令的标准输入和标准输出,因此我们可以运行命令。现在,我们可以删除之前创建的大文件。
但我们更希望有一个正常的shell环境。我们通过指定-t标志让SSH为我们分配一个伪终端。
ssh -t user@hostname
这样看起来就正常了。显然,bash在登录时对已满的文件系统没有问题。我们现在可以删除导致所有问题的那个大文件。
rm /tmp/bigfile
清理完毕后,我们可以退出当前会话。现在,被文件占用的磁盘空间已重新可用。
我们检查那个损坏的shell连接,它仍然处于损坏状态。因为仅仅删除文件并不会为当前shell神奇地分配一个正确的文件描述符。因此,我们退出该shell并重新连接。现在,一切恢复正常。

如果你尝试重现此场景,请确保删除创建的任何大文件,以免系统因你的练习而受到负面影响。
根据你使用的登录shell是bash还是其他,你可能会看到略有不同的行为。但本练习特别想说明,填满文件系统可能会对看似无关的进程产生影响,并可能导致令人困惑或不一致的结果。一个用户可能抱怨无法登录,而另一个用户可能暂时没有注意到任何异常。
稀疏文件的奥秘
我们已经看到了用一个巨型文件填满所有磁盘空间会发生什么。现在,让我们尝试其他方法。
再次查看可用磁盘空间。
df -h
我们在/tmp目录下创建一个目录,并指定一个用于操作的大文件路径。
mkdir /tmp/test_sparse

从df命令的输出中,我们可以提取出确切的可用磁盘空间大小(通常是第四列)。然后,我们使用truncate命令创建一个特定大小的文件。具体来说,我们将尝试创建一个比可用磁盘空间大很多倍的文件。
truncate -s 10000000000000 /tmp/test_sparse/hugefile
等等,发生了什么?我们成功创建了一个比可用磁盘空间大数千倍的文件。这怎么可能?查看这个文件的大小,这似乎是不可能的。df甚至告诉我们还有大量剩余空间。这说不通。
du命令告诉我们这个文件使用了多少块。
du -h /tmp/test_sparse/hugefile
输出显示该文件使用了0个块。stat命令显示文件大小确实非常大,但仍然使用0个块。
stat /tmp/test_sparse/hugefile
这到底是怎么回事?我们创建的文件显然既具有巨大尺寸,又不占用磁盘空间。这是因为它是通过简单设置文件大小创建的,而没有向其中写入任何数据。这被称为稀疏文件。并非所有文件系统都支持稀疏文件,但这个系统支持。
当我们复制这个文件时会发生什么?
cp /tmp/test_sparse/hugefile /tmp/test_sparse/hugefile2
这似乎可行。现在我们有了两个这样的奇怪文件,都看似巨大且不占用磁盘空间。这是因为cp命令很智能,它能检测到这是一个稀疏文件,然后创建该文件的真实副本。
但是,如果我们尝试使用cat读取文件并将其输出重定向到另一个文件。
cat /tmp/test_sparse/hugefile > /tmp/test_sparse/hugefile3
突然,这个过程需要很长时间。最终,我们收到磁盘空间不足的错误。现在查看这两个文件,第二个文件在磁盘上占用了大量块,多到填满了文件系统。
这是因为当内核尝试读取稀疏文件时,它发现那里没有数据,于是提供空比特(null bits)。读取进程随后会看到这些空比特,并将其写入第二个文件。很奇怪,对吧?如你所见,我们的磁盘现在实际上又满了。只有在删除文件后,我们才能恢复磁盘空间。
这个例子展示了可能依赖于特定文件系统的意外行为。如果你在其他操作系统或使用不同文件系统运行这些命令,可能会得到不同的结果。
耗尽Inode资源
我们已经看到了在真正写入数据时创建大型文件如何耗尽磁盘空间,以及如何创建看似巨大但不占用磁盘空间的文件。但是,如果不创建一个巨型文件,而是创建大量的小文件,会发生什么?让我们试一试。
我们使用df -i命令来检查文件系统的inode使用情况。我们将在未来的视频中详细讨论inode究竟是什么。目前,只需知道每个文件都与一个inode相关联。在这个例子中,我们有923261个可用的空闲inode。
df -i

如果我们创建一个目录,就使用了一个inode。
mkdir /tmp/manyfiles

创建一个文件,又使用了一个inode。
touch /tmp/manyfiles/file1
同样,删除文件会释放inode,删除目录也是如此。这很合理。
现在,让我们看看如果耗尽所有inode会发生什么。为此,我们需要创建923261个文件。如果逐个运行命令会很繁琐,所以我写了一个简单的程序来为我们创建新文件。你可以从课程网站获取它。
程序内容大致如下:我们创建一个目录,然后无限循环地在目录中创建新文件,直到失败为止。

编译并运行这个程序。一段时间后,程序会如预期那样失败。它报告创建了923260个文件,加上那个目录,它创建了文件。注意错误信息:“No space left on device”。听起来像是磁盘满了。

让我们查看目录的大小。

du -sh /tmp/manyfiles

这个目录相当大。查看我们创建的文件。
ls /tmp/manyfiles | head -20
注意,即使运行ls命令也需要很长时间,因为目录太大了。确认目录中有多少文件。

ls -f /tmp/manyfiles | wc -l

是的,923260个文件。现在尝试创建一个新文件。

touch /tmp/manyfiles/newfile

不行,无法创建,“No space left on device”。但是,我可以将文件从该目录移动到另一个目录。
mv /tmp/manyfiles/file1 /tmp/
这是因为移动文件并不会创建新文件(我们将在未来的视频中详述细节)。我们创建的文件大小为0字节,其他923259个文件也是如此。但我们怎么会磁盘空间不足呢?我们不是有11GB的可用空间吗?
实际上,我们并没有耗尽磁盘空间。我们仍然可以向磁盘写入数据,只要写入到现有文件中。
echo "data" >> /tmp/manyfiles/file2
我们耗尽的是inode,意味着我们无法创建新文件,但磁盘空间确实仍然可用,如下所示。
df -h
事实上,我们可以轻松地向现有文件写入1GB的数据。
dd if=/dev/zero of=/tmp/manyfiles/file2 bs=1M count=1024
看,没问题。但让我们清理并删除创建的近百万个文件。
rm -rf /tmp/manyfiles
注意,这需要相当长的时间。我们可以暂停进程一会儿,检查进度。看起来我们已经释放了近20万个inode。然后继续。同时,我们也可以在删除所有文件时查看目录大小。看,目录大小和它包含近百万个文件时一样大。关于这一点,我们稍后再讨论。现在继续。完成了,我们回到了起点。
总结
在本节课中,我们一起学习了当磁盘空间或inode资源耗尽时系统可能表现出的各种行为。
本练习旨在说明的几个关键点:
- 磁盘空间耗尽可能导致奇怪的副作用:我们看到有些用户因为磁盘已满而无法登录系统,但其他用户却没有问题。
- 文件大小并不总是看起来那样:文件大小与文件占用的磁盘块数量之间存在差异。根据文件和文件系统的不同,这种差异可能非常显著。
- 错误信息并不总是表面意思:当我们耗尽inode时,错误信息是“No space left on device”,但这具有误导性。如果你看到此错误信息然后运行
df,它会显示你还有许多GB的可用磁盘空间。在排查系统故障时,了解可能导致此错误信息的不同场景非常重要。 - 所有资源都是有限的:如今我们可能拥有大量磁盘空间,但如果有可能耗尽它,某个进程总会以某种方式做到。一个常见的Unix文件系统看似可以存储近乎无限数量的文件,但文件系统受到可用inode数量的限制,而这些inode也可能被耗尽。
在本学期中,我们还将看到许多其他例子。我们也将在接下来的几个视频中重温很多已涉及的内容。希望这个热身练习能帮助你开始思考文件系统以及我们以这种方式管理的资源。
请务必查看幻灯片中的链接并阅读建议的阅读材料。下次我们将讨论存储模型,希望你在学习时能牢记我们在这里看到的限制。

本节课中我们一起学习了磁盘空间和inode耗尽对系统的影响,包括登录异常、稀疏文件的特性以及错误信息的辨析。理解这些基础概念对于进行有效的系统管理和故障排查至关重要。
008:第2周第1节 - 存储模型与磁盘 💾
在本节课中,我们将要学习数据存储的基础知识。作为系统管理员,我们需要管理各种存储设备,从无本地存储的系统到支持分布式数据复制和归档的大型企业存储阵列。理解不同的存储模型和磁盘概念是构建、维护和扩展可靠IT基础设施的关键。
我们首先讨论这个话题,是因为没有数据存储,我们甚至无法启动操作系统。我们总是在处理存储数据的方式,因此,作为一项核心资产,我们至少需要大致了解其中涉及的内容、可能出现的问题以及如何扩展数据访问。
谈论数据存储的容量总是一项徒劳的努力,因为无论我们提供多少,很快就会被用完。就在几年前,能访问1GB数据都令人难以置信。如今,为视频、照片和音乐准备1TB存储空间似乎已不稀奇。数据总会膨胀以填满任何可用空间。无论分配多少存储,用户总会找到方法将其用尽。因此,我们需要思考如何避免磁盘永远处于满载状态。

为此,我们将讨论以下内容:
- 基本磁盘概念
- 基本文件系统概念
- 通用文件系统,特别是传统的Unix文件系统
在每个主题下,我们都有一些子主题。为了理解基本磁盘概念,我们需要先了解常见的存储模型,这是本视频的主要主题。之后,我们将讨论磁盘接口、磁盘驱动器的物理特性,以及分区。这些知识对于后续理解文件系统概念至关重要。
接下来,在文件系统概念部分,我们将探讨如何使用冗余磁盘阵列(RAID)和逻辑卷管理(LVM)来组合存储设备,以及格式化设备如何影响存储能力。最后,我们将讨论在正确连接存储介质后可以做什么,包括文件系统的类型以及传统Unix文件系统的工作原理。这部分内容将在下周讲解,而本周我们将尝试涵盖所有其他主题。
那么,让我们开始吧。
存储模型 📊
我们根据负责存储比特的设备与上层交互的方式、原始块设备访问的提供位置、创建文件系统以将磁盘空间作为有用单元提供的位置,以及操作系统访问文件系统所使用的协议,来区分不同的存储模型。简化来说,我们识别出以下组件:
- 操作系统
- 存储设备本身(即实际存储比特的物理设备)
- 文件系统,位于存储设备之上,与应用程序软件交互
直连存储 (DAS)


首先,也是最常见、最简单的存储模型是直连存储。顾名思义,存储介质(最常见的是物理硬盘)直接连接到主机。
如果你查看你的笔记本电脑、工作站、台式机或物理服务器,很可能会找到一个物理硬盘。在这个模型中,我们在物理磁盘上看到一个分区(例如 /dev/sda1)挂载在根目录 / 下,以及其他几个分区挂载在文件系统的其他地方。
对于工作站或典型服务器,直连存储设备可能是一个常规的IDE硬盘。存储设备是物理服务器的一部分,由操作系统管理。文件系统在存储设备上创建,并将硬盘提供的块级存储以文件级访问的方式提供给应用程序软件。
直连存储是一种非常简单的架构,具有许多优点。由于操作系统和硬件之间没有网络或其他附加层,因此消除了该层面的故障可能性。同样,由于网络延迟等原因导致的性能损失也不存在。同时,它也有一些缺点。由于存储介质是直接连接的,这意味着它与网络上的其他系统存在一定的隔离。这既是优点也是缺点。一方面,每台服务器都需要某些数据对其操作系统是私有或唯一的。另一方面,一台机器上的数据不能立即提供给其他系统。
网络附加存储 (NAS)

然而,我们经常希望能够从多台服务器访问某些数据。例如,当用户登录到主机A时,她期望找到所有文件,就像登录到主机B时一样。在Stevens的共享Linux系统中,我们通过负载均衡器访问Linux实验室,可能会被分配到几台物理服务器中的一台,但我仍然期望并确实能够访问我的所有文件。
实现这一目标的方法是将数据存储在网络附加存储设备上,该设备位于某个中心位置,并通过例如网络文件系统 进行访问。那么,这是如何工作的呢?
我们看到有一个中央服务器(例如 chronos.sit),它从我的主目录提供数据。在本地主机上,它被挂载在特定的路径下(例如 /home/ds)。文件服务器必须拥有实际的存储设备,并在其上创建了文件系统,然后通过网络使其可用,以便客户端可以访问它。但这些客户端需要支持NFS,而NFS又为应用程序软件提供了文件级访问的标准化抽象。

请注意,文件服务器以直连存储的方式访问存储设备,这意味着它(尽我们所能推测)物理连接到该服务器。这在实践中的样子取决于系统的规模。例如,chronos.sit 上的路径 /xraid01 可能是一个Apple Xraid存储设备。
网络附加存储允许多个客户端通过网络访问同一个文件系统,但这意味着它要求所有客户端都专门使用这个文件系统。NAS文件服务器管理并增强了存储介质上文件系统的创建,并允许共享访问,克服了直连存储的许多限制。
存储区域网络 (SAN)
然而,随着我们对存储大小、数据可用性、数据冗余和性能的要求不断提高,特别是当我们需要扩展时,允许不同客户端在块级别访问大块存储变得非常理想。为了实现这一点,我们构建了专门用于管理数据存储的高性能网络,即存储区域网络。
在这些专用网络中,中央存储介质使用高性能接口和协议(如光纤通道或iSCSI)进行访问,使暴露的设备在客户端上看起来是本地设备,有效地表现为一个块设备,就像直连存储一样。从存储池中划分出的另一个块,可以被一个单独的文件服务器用来构建新的文件系统,并通过NFS导出,如图所示。
存储区域网络也是专门的网络,也就是说,它们可以配置成真正的交换结构,使用看起来很像网络设备的硬件。它可以使用各种协议在现有网络上叠加存储通信,或构建全新的独立网络,通常使用光纤连接。

云存储 ☁️

一旦你理解了SAN可以是一个完全交换的结构,并可用于提供灵活的块级存储和文件级网络存储,那么出现另一层抽象并开始以“云”的形式提供存储即服务也就不足为奇了。
在这里,我们还想做出另一组区分:
- 提供文件级存储和访问的服务,例如文件托管服务,如Dropbox、Google Drive或Apple iCloud。
- 提供对象级访问的服务,向客户端隐藏文件系统实现细节,并提供易于抽象到API中的接口,以Amazon简单存储服务 为典型代表。
- 在块级别向客户端提供访问的服务,允许他们根据需要创建文件系统和分区,例如AWS弹性块存储。

所有这些类别都有一个共同点:为了提供以编程方式访问存储单元的能力,它们提供了一个定义良好的API进行访问,通常是基于HTTP或REST的。在服务提供商端(如图形底部所示),所使用的存储模型对客户来说是一个不透明的系统。他们可能使用存储区域网络来组合大量分布式存储资源,向用户呈现一个大的存储池。作为系统用户,我们并不关心这些,存储就像凭空出现一样神奇。
我们已经并将继续通过使用AWS来了解这是如何工作的,但让我们快速说明对象级和块级之间的区别。

首先,让我们看看S3。我们创建一个新的S3桶,然后递归地将一堆文件复制到其中。就这样,不需要其他任何操作。它真的不能再简单了。就这样,我们将一个充满文件的目录备份到云存储中,能够通过命令行检查内容,而无需担心是否有足够的可用存储空间、文件有多大,或者可以创建多少文件是否有限制。

接下来,看看弹性块存储。顾名思义,这种云存储方法允许在块级别进行访问,这意味着我们获得一个看起来和行为都像真实磁盘的设备。从实例的角度来看,这实际上只是直连存储的一种变体,因为虚拟机不知道也不关心块设备来自哪里。事实上,常规的AWS EC2实例将使用EBS卷作为本地存储。

在这里我们看到,有一个卷作为磁盘 /dev/sda1 被附加。我们可以通过 describe-volumes 命令获取更多信息,了解卷大小和一些属性。使用EBS,我们总是可以神奇地创建新磁盘。例如,创建一个新的4GB大小的磁盘。作为一个块级存储设备,我们不能直接向其中写入文件。我们需要创建一个新的文件系统,我们将在稍后的推荐练习中回到这一点。现在,让这个简短的演示来说明云存储可以是什么样子。
总结与回顾 📝
本节课中我们一起学习了四种主要的存储模型:
- 直连存储:存储设备直接物理连接到主机。
- 网络附加存储:通过文件级协议(如NFS)在网络上共享文件系统。
- 存储区域网络:通过专用高速网络提供块级存储访问。
- 云存储:通过API(文件级、对象级或块级)提供的按需存储服务。

我们注意到这些模型可以组合使用,并提供不同类型的访问,主要区分了块级访问(设备表现为直接连接的物理磁盘)和文件级访问(系统通过文件系统或API与存储介质交互)。

每种模型都有许多含义。首先,重要的是要记住,即使我们处理的是云中凭空出现的“神奇”存储,在某个地方,总有人在管理物理存储介质。其次,随着我们增加抽象层并允许更大的灵活性,我们也在改变安全模型。一个直接连接的物理驱动器只能从该主机或通过物理访问被破坏,但网络文件服务器可能通过网络被破坏。我们还注意到,在组合不同模型时,我们最终可能会使用或组合大量的技术和协议。
我们将在下一个视频中简要介绍其中的一些,届时我们将讨论接口和协议。
推荐练习 💡
在结束之前,我想留给你一些推荐的练习和问题,以帮助你加深对今天所学内容的理解:
- 研究公共云存储服务的细节:尝试研究一些公共云存储服务(如AWS、Google Cloud、Microsoft Azure等)的细节。你能找出它们在后端可能使用什么存储解决方案吗?它们必须考虑哪些可扩展性问题?
- 思考安全影响:思考我们讨论的不同存储模型,并识别特定的安全问题。每种模型都有特定的安全含义,但具体可能是什么?
- 动手实践EBS:这是一个更具体的练习,我建议你创建一个EBS卷,将其附加到一个实例,创建一个文件系统,添加一个文件,然后将该卷移动到另一个实例。这将帮助你更熟悉弹性块存储和文件系统概念。
如果在练习中遇到问题,请记得提问。

下次见,感谢观看。
009:第2周热身练习2 - 跨操作系统迁移EBS卷 🚀
在本节课中,我们将学习如何将亚马逊弹性块存储卷从一个EC2实例迁移到另一个运行不同操作系统的实例。这个练习旨在帮助你熟悉云存储的概念,并理解EBS卷如何像物理硬盘一样在不同系统间移动和使用。
上一节我们介绍了EBS卷的基本概念,本节中我们来看看具体的操作步骤。
概述与目标 🎯
本练习的核心目标是演示EBS卷的独立性和可移植性。我们将创建两个运行不同操作系统的EC2实例,在一个实例上初始化EBS卷并写入数据,然后将其分离并挂载到另一个实例上,最终验证数据是否成功迁移。

操作步骤详解
以下是完成此练习的主要步骤。
1. 创建实例与卷
首先,我们需要创建两个EC2实例。在基础练习中,你可以创建两个相同操作系统的实例。但为了深入理解,本教程将使用两个不同的操作系统:一个NetBSD实例和一个Ubuntu实例。
同时,我们创建一个新的EBS卷。可以使用以下简化的shell函数来创建卷,它接受两个可选参数:卷大小(GB)和可用区。如果未指定,则默认为1GB,位于us-east-1a。
new_volume() {
local size=${1:-1}
local zone=${2:-us-east-1a}
# AWS CLI命令创建卷
aws ec2 create-volume --size $size --availability-zone $zone --volume-type gp2
}
2. 在第一个实例上初始化卷
创建卷后,将其附加到NetBSD实例。通过SSH连接到该实例,使用dmesg命令查看新添加的磁盘(例如/dev/xbd1)。
使用disklabel工具查看分区表,通常新磁盘默认是一个覆盖整个磁盘的单一分区。接着,在该磁盘上创建一个文件系统。我们使用默认的UFS文件系统(版本2)。
# 在NetBSD实例上执行
newfs /dev/xbd1a

创建文件系统后,将其挂载到一个目录下,例如/mnt。
mount /dev/xbd1a /mnt
然后,在文件系统中创建一个测试文件,以验证操作。
echo “Hello from NetBSD” > /mnt/testfile.txt


操作完成后,卸载磁盘并退出SSH连接。

3. 迁移卷到第二个实例
现在,从NetBSD实例分离该EBS卷,并终止该实例。请注意,实例根文件系统上的所有数据都会丢失,但EBS卷上的数据得以保留。
接下来,将同一个EBS卷附加到之前创建的Ubuntu实例。通过SSH连接到Ubuntu实例。


4. 在第二个实例上访问卷
在Ubuntu实例上,使用lsblk命令查看可用磁盘,找到新附加的卷(例如/dev/xvdf)。
由于该磁盘包含的是UFS文件系统(而非Linux常用的ext4),在挂载时需要指定文件系统类型。需要注意的是,Linux内核通常仅支持以只读模式挂载UFS文件系统。

# 在Ubuntu实例上执行
sudo mount -t ufs -o ufstype=ufs2,ro /dev/xvdf /mnt
挂载成功后,检查/mnt目录,应该能看到之前在NetBSD实例上创建的testfile.txt文件。
cat /mnt/testfile.txt
核心要点与扩展练习 💡
通过以上步骤,我们成功地将一个EBS卷从NetBSD实例迁移到了Ubuntu实例,并访问了其中的文件。这证明了EBS卷作为独立、持久化存储设备的特性。
为了巩固理解,建议你进行以下扩展练习:
- 尝试使用其他操作系统组合,例如在Amazon Linux上创建文件系统,然后挂载到FreeBSD上。
- 探索不同文件系统(如ext4, XFS)在跨操作系统挂载时的兼容性和要求。
- 查阅相关文档,理解
mount命令中不同文件系统类型对应的选项。
如果在练习过程中遇到问题,可以随时在课程邮件列表或Slack频道中提问。
总结

本节课中我们一起学习了EBS卷跨操作系统迁移的完整流程。我们实践了创建卷、附加卷、创建文件系统、写入数据、分离卷以及在不同OS实例上重新挂载并读取数据的全过程。关键在于理解EBS卷独立于EC2实例的生命周期,以及不同操作系统对文件系统的支持差异(如Linux对UFS的只读支持)。掌握这些技能对于管理云基础设施至关重要。
010:第10讲 - AWS 别名预热练习 🚀
在本节课中,我们将学习如何通过创建 Shell 别名和函数,来简化与 Amazon Web Services 命令行工具的交互,从而节省大量重复输入命令的时间。
概述
系统管理员通常希望避免重复劳动。在本课程中,我们将全程使用 AWS 命令行界面。因此,本节将演示如何设置一些 Shell 别名和函数,让启动、管理和连接 AWS 实例等常见操作变得更加快捷高效。
创建启动实例的别名
我们经常需要启动新的 AWS 实例。通常,这需要运行 aws ec2 run-instances 命令并指定 AMI ID。此外,为本课程创建的实例都需要使用特定的密钥对 stevens。
每次输入完整的命令非常繁琐。为此,我们可以创建一个 Shell 别名。
代码示例:创建别名
alias instance='aws ec2 run-instances --key-name stevens'
创建此别名后,启动实例只需输入 instance 后跟 AMI ID 即可。
启用双栈网络
我希望所有实例都启用 IPv4 和 IPv6 双栈网络。默认情况下,AWS 不启用此功能,需要正确配置子网和安全组。
我已在本节幻灯片末尾附上博客文章链接。按照该指南操作后,你将获得一个带有 dual-stack 标签的子网和安全组。
因此,我们可以创建一个 Shell 函数来启动具有双栈网络的实例。
代码示例:启动双栈实例的函数
start_instance() {
# 根据标签获取子网和安全组ID
subnet_id=$(aws ec2 describe-subnets --filters "Name=tag:Name,Values=dual-stack" --query "Subnets[0].SubnetId" --output text)
sg_id=$(aws ec2 describe-security-groups --filters "Name=tag:Name,Values=dual-stack" --query "SecurityGroups[0].GroupId" --output text)
# 使用之前定义的别名启动实例
instance --subnet-id $subnet_id --security-group-ids $sg_id --image-id $1
}
这个函数会根据标签获取子网和安全组 ID,然后调用 instance 别名来启动实例。现在,你只需提供 AMI ID 作为参数。
为常用 AMI 创建快捷方式

我经常使用 NetBSD AMI,但不想每次都记住其标识符。为此,可以创建另一个别名。
代码示例:启动 NetBSD 实例的别名
alias startnetbsd='start_instance ami-xxxxxxxx'
运行 startnetbsd 别名即可使用正确的 AMI ID 调用 start_instance 函数。
在展示这些别名和函数时,你会发现其中重复使用了某些构建块。这种模式在本学期后续讨论系统管理中的自动化和编程时还会再次出现。

获取实例信息
启动实例后,我们需要知道其主机名才能登录。通常,我们会运行 aws ec2 describe-instances 命令,但其输出信息繁杂。
因此,可以创建一个函数来根据实例 ID 提取主机名。

代码示例:根据实例ID获取主机名的函数
iname() {
aws ec2 describe-instances --instance-ids $1 --query "Reservations[0].Instances[0].PublicDnsName" --output text
}
现在,使用 iname <实例ID> 命令就能直接获得主机名。
等待实例就绪
启动实例后,系统需要一段时间才能完全启动并接受 SSH 连接。反复尝试 SSH 登录并不高效。

我们可以编写一个函数,使用 AWS CLI 的 wait 命令来等待实例进入运行状态,并额外添加一点延迟以确保系统完全启动。
代码示例:等待实例就绪的函数
e2wait() {
aws ec2 wait instance-running --instance-ids $1
sleep 30 # 额外等待30秒,确保系统服务已启动
}
虽然添加固定延迟并非最优方案,但比反复尝试 SSH 连接要好。你也可以尝试寻找更好的方法。


管理多个实例
当我们运行多个实例时,可能需要快速查看所有实例的列表。
以下是几个有用的别名:
- 列出所有实例ID:
alias instances='aws ec2 describe-instances --query "Reservations[*].Instances[*].InstanceId" --output text' - 列出所有实例主机名:
alias inames='aws ec2 describe-instances --query "Reservations[*].Instances[*].PublicDnsName" --output text' - 同时列出ID和主机名:
alias instances2='aws ec2 describe-instances --query "Reservations[*].Instances[*].[InstanceId, PublicDnsName]" --output table'
终止实例
终止实例同样可以通过别名来简化。
- 终止单个实例:
使用方式:alias terminstance='aws ec2 terminate-instances --instance-ids'terminstance <实例ID>。 - 终止所有正在运行的实例(请谨慎使用):
alias killallinstances='aws ec2 terminate-instances --instance-ids $(instances)'
运行终止命令后,实例会进入“关闭中”状态,但仍会出现在 instances 列表中。若只想查看当前处于“运行中”状态的实例,可以使用更精确的查询。

获取并使用预定义的别名文件
我已经将上述常用的别名和函数整理到一个文件中,你可以从课程网站下载并使用它们。

代码示例:下载并查看别名文件
curl -O https://course-website.example.com/aws-aliases.sh
cat aws-aliases.sh
该文件包含了我们将要使用的各种实例类型对应的快捷命令。
你可以将此文件放在方便的位置,然后从你的 Shell 启动文件(如 ~/.bashrc 或 ~/.zshrc)中加载它。
代码示例:在 Shell 启动文件中加载别名
# 在 ~/.bashrc 或 ~/.zshrc 中添加
source /path/to/your/aws-aliases.sh
这样,每次启动新的 Shell 会话时,所有这些便捷的别名和函数就都可以使用了。
总结
本节课我们一起学习了如何通过自定义 Shell 环境来提升 AWS 命令行工具的使用效率。我们创建了用于启动实例、启用双栈网络、获取实例信息、等待实例就绪以及管理多个实例的别名和函数。掌握这些技巧可以显著减少重复性输入,让你更专注于系统管理的核心任务。

在下一个视频中,我们将进行另一个预热练习,探讨第二周的主题:存储模型与磁盘。谢谢观看,再见!
011:Week 02, Segment 2 - 设备接口 💾
概述
在本节课中,我们将学习存储设备及其接口技术。上一节我们介绍了存储的概念模型,本节中我们来看看具体的物理设备连接方式。我们将从早期的SCSI标准开始,逐步了解ATA、SATA、光纤通道以及固态硬盘等现代技术,并探讨它们如何组合以满足不同的存储需求。
SCSI:小型计算机系统接口
SCSI是一种描述如何将设备或外设连接到计算机并在它们之间传输数据的较旧标准。它已存在超过30年,并有多种令人困惑的实现和变体。
SCSI曾是使用长而笨重的带状电缆和各种连接器连接任何外设的默认方法。


上图是一个SCSI驱动器。不同设备可能使用不同的连接器并需要不同的电缆。


如今,SCSI在很大程度上已被高级技术附件标准所淘汰,但它仍在iSCSI标准中延续。iSCSI规定了在基于IP的网络上使用SCSI命令协议的存储连接,这是存储区域网络中的常见选择。我们稍后将看到的另一个变体是串行连接SCSI或基于光纤通道协议的SCSI。
ATA与IDE接口
另一方面,ATA标准通常等同于集成设备电子接口。
你可能见过使用扁平带状电缆的并行ATA,这种电缆使得在服务器机箱内连接多个驱动器变得困难。幸运的是,对于系统管理员来说,如今串行ATA更为常见。
这些是你的典型硬盘驱动器。它们之所以被称为“集成设备电子”,是因为驱动器包含了控制器,将原本位于主板和独立控制器上的部分复杂性集成到了驱动器内部。
也就是说,驱动器包含一个控制器电路以及一些固件,以方便访问。
安全考量
现在我们讨论的是相当底层的连接,但现在是提醒你安全影响一切的好时机。
几年前,有消息公开称美国国家安全局能够将恶意软件植入硬盘固件中。该恶意软件包含一个API,并能够向磁盘上的隐藏扇区读写任意信息。
这是一种非常难以防范的威胁,也很好地提醒了我们,几乎任何东西都可能被入侵。
具体来说,它帮助我们思考“集成设备电子”这个名字的含义,它意味着那里不仅仅有一些有用的“魔法”。
但不必过分担忧,并非每个硬盘都必然被入侵,你也不一定在NSA的目标名单上。
固态硬盘
然而,出于多种原因,包括安全性和更可能的性能考虑,你可能希望从IDE驱动器转向固态硬盘。
SSD驱动器摒弃了用于存储数据的机械旋转磁性盘片,转而使用集成电路来持久存储数据,例如闪存。
这些驱动器比IDE驱动器更能抵抗物理冲击,更安静,延迟也更低。这就是你的手机很可能使用SSD进行存储的原因。
虽然SSD仍然比传统的机械硬盘更昂贵,但如今即使在服务器市场,你也能发现更多SSD或闪存的使用。
这些驱动器仍会使用SATA标准进行连接,但也可能被组合成更大的存储设备,然后通过例如光纤通道协议在外部或通过存储区域网络连接。
光纤通道与协议栈
光纤通道通常用于交换式网络结构中,这意味着它的外观和行为很像你正常的交换式以太网网络。
它使用右上图所示的光纤电缆,尽管你也可以通过铜线运行它。
如果所有这些不同的技术还不够,你还需要考虑到,在几乎所有非最简单的环境中,当创建存储和网络时,这些技术都会以某种形式组合使用。
也就是说,你可能会发现一个多层协议栈建立在光纤通道协议之上,该协议可能用于纯光纤通道网络、运行在常规TCP/IP之上的以太网网络等等。
类似地,SCSI协议可以用于上述任何一种之上,或者通过像远程直接内存访问这样的技术。
也就是说,我们有各种方法将一种协议承载在另一种协议之上。
例如,ATA over Ethernet允许我们重用现有的以太网网络,并通过将ATA帧封装到以太网帧中,将其转变为存储区域网络。
Fiber Channel over Ethernet同理,但针对的是光纤通道协议。这让你了解了一种趋势,即人们意识到“嘿,我们已经有一个可用的网络了,就让它也承载块级指令吧”。所以你几乎可以在以太网上运行任何东西。
这使得事情变得相当容易,但也显著意味着,以这种方式构建的存储区域网络受限于同一网络层/网段,并且由于在该层运行,它没有固有的安全属性。
因此,你可以尝试通过使用例如iSCSI来将事情推向协议栈的上层,iSCSI包含身份验证,并且可以包装在IPsec中。
当然,我们还可以更进一步,转向我们之前提到的串行连接SCSI。如今,SAS被用于大型存储阵列中,例如下图所示的这种,它使用SCSI命令和协议在现代硬件上提供高效的高速存储访问。
但忠实于其SCSI传统,SAS当然也不乏令人困惑的变体和连接器。

性能与容量演进
正如你所见,在存储技术和协议方面,拓宽你理解或专精的机会是无限的。

快速浏览一下技术如何进步,请注意性能吞吐量如何随时间推移和协议不同而增加。
你在下图中可以看到我们到目前为止提到的所有技术,其比特率随时间增长。

注意光纤通道的引入,速率约为每秒100兆字节。
然后随着各种“over Ethernet”变体向前发展,现在超过了千兆以太网,最终超过了100千兆以太网,这确实非常出色。

这解决了吞吐量的比特率范围。但是,我们能在不同介质上存储多少字节呢?
单个IDE硬盘已经从1980年大约5兆字节、售价1500美元的驱动器走了很长的路,不是吗?我清楚地记得,当我们用500兆字节的驱动器构建服务器时,最终一个10千兆字节的驱动器被认为是巨大的,但当然,如今那不算什么。
事实上,IDE驱动器的存储价格下降得如此之多,你现在只需大约600美元就能买到一个18太字节的驱动器。
18太字节,在单个IDE驱动器中,这真是太神奇了。


所以,在某种程度上,升级你的单个磁盘驱动器是我们上一节讨论的纵向扩展的一个例子。
扩展方案:JBOD与RAID
但是,即使18太字节对你来说还不够,因为正如我们所确定的,使用量会扩张以填满所有可用空间,你可以考虑直接买一大堆驱动器,然后一个一个地连接起来。
这种磁盘配置通常被称为JBOD,即“只是一堆磁盘”,顾名思义。你会买一堆驱动器。

现在你的服务器上会有相当多的开销,因为你有15个独立的磁盘,但这肯定是一种可能代表横向扩展的方法,并带有其所有隐含的缺点。
或者,也许更好的方法是取所有这些大容量驱动器,将它们放入一个RAID控制器中,然后将它们组合成一个单一的卷,实际上结合了纵向和横向扩展的方法。
我们将在下一个视频中更多地讨论RAID。当然,没有什么要求我们必须为此方法使用IDE驱动器,我们可以改用SSD,就像这个一样。这是一个100太字节的固态硬盘。
100太字节,采用小巧的3.5英寸外形规格。唯一的问题是:这将花费你整整40,000美元。
是的,你没听错,100太字节SSD需要40,000美元。SSD确实更贵,但它们也比HDD快得多且可靠,所以这里谈的是纵向扩展。
现在想象一下,将其横向扩展成一个存储设备,该设备结合了SSD或闪存,就像下图所示的NetApp全闪存阵列一样。

总结
本节课中,我们一起学习了各种存储设备接口技术,从SCSI、ATA/IDE到现代的SATA、光纤通道和SSD。我们探讨了这些技术如何通过不同的组合(如各种“over Ethernet”协议和RAID)来满足性能、容量和安全需求。我们了解到,没有单一的简单解决方案,正确的选择取决于具体的需求、性能目标和预算。存储系统的设计需要在纵向扩展、横向扩展以及不同介质和协议之间进行权衡。

课后练习
假设你是史蒂文斯理工学院的一名系统管理员,需要更换当前用于Linux实验室主目录的存储系统。
你会提出什么解决方案?显然,你缺乏做出此决定所需的大量信息,但另一方面,你可以根据观察到的使用情况对存储需求做出有根据的猜测。
尝试规划一个解决方案,然后看看它们会花费你多少成本。接着考虑,作为一个学术机构,你可能受到具有某些限制的预算约束。
最后,考虑你的选择可能对计算机环境的其余部分产生什么影响。
012:存储虚拟化 🗂️
在本节课中,我们将要学习存储虚拟化的核心概念。我们将探讨如何将多个独立的物理磁盘设备组合成更大、更高效、更可靠的逻辑存储单元,并介绍实现这一目标的两种主要方法:基于硬件的RAID和基于主机的逻辑卷管理。

在上一节视频中,我们讨论了不同的磁盘设备及其连接方式。过程中,我们简要提到了几种将独立磁盘组合成更大存储池的配置方案。
本节中,我们将在此基础上,更详细地探讨存储虚拟化这一概念。

当我们听到“虚拟化”这个词时,通常会联想到虚拟机或AWS等云服务。但在基础层面上,存储虚拟化本质上就是将物理存储介质与逻辑存储单元分离开来。这意味着我们可以用多个硬盘填满一个大机箱,然后根据需要将其分割成虚拟磁盘。这种方式在某种程度上与将单个硬盘划分为独立分区的做法相反,我们将在下一节视频中详细讨论分区概念。

这里,我们将快速了解两种不同的存储虚拟化方法:一种是基于硬件的方案,例如RAID设备;另一种是基于主机的方案,即操作系统通过逻辑卷管理来组合物理存储设备。我们还将看到逻辑卷管理的两个实际例子:现代Linux系统中常用的设备映射器,以及包含了存储虚拟化层的ZFS文件系统。
基于硬件的RAID 🖥️
让我们从RAID开始。正如上一节视频提到的,下图展示的是一台现已过时的硬件设备——Apple X RAID设备。我们继续使用这个示例图片,因为它能直观地展示RAID的工作原理。
RAID设备,全称“独立磁盘冗余阵列”或最初所称的“廉价磁盘冗余阵列”,为多个独立硬盘提供机箱、管理固件,并通过例如SCSI等方式连接到您的存储区域网络。但仅仅把硬盘塞进机箱是不够的,这也正是RAID与上节提到的JBOD方式的区别所在。RAID不仅是一堆磁盘,它允许你将所有磁盘组合成一个单一的虚拟磁盘,从而创建一个跨越所有磁盘的大型文件系统。

RAID设备提供的另一个优势是通过在所有驱动器间条带化数据来提高I/O效率,从而最小化硬盘寻道时间。第三,RAID提供了一定程度的数据冗余,它不是在所有驱动器上写入数据,而是在驱动器子集之间镜像数据,从而提供容错能力。这一点很重要,因为我们知道所有设备最终都会故障,如果可能的话,损坏的硬盘不应导致数据丢失。
RAID级别详解 🔢

我们看到有几种方式可以组合和利用RAID阵列中的磁盘,我们使用以下数字级别来描述不同的方法。级别0到6代表了最常见的解决方案,你可以看到数据写入方式的区别,有时我们在物理块级别考虑I/O性能,有时在字节或位级别。别担心,我们将在下一节视频中详细讨论“块”在此上下文中的确切含义。但或许最好先简要总结一下最流行的RAID级别,以说明它们提供的优势。

以下是几种常见的RAID配置方式:
- RAID 0:最简单的多磁盘使用方式是将它们并排放置,然后将数据分布写入两个驱动器。当文件系统发出写操作时,RAID设备会在块级别将其分割,将第一个块写入磁盘1,第二个块写入磁盘2,第三个块再次写入磁盘1,依此类推。这样,I/O性能得到提升,因为读写数据的速度可以翻倍。然而,如果这两个磁盘中的任何一个发生故障,你就会丢失一半的数据,并且很可能无法重建丢失的数据。因此,RAID 0提供了I/O性能优势,但没有容错能力。
- RAID 1:如果你想确保在磁盘故障时不丢失数据,可以使用RAID 1配置。在这种情况下,RAID会复制文件系统要求写入磁盘的每一个数据块,并写入两个副本,每个磁盘一份。这样,如果你丢失了一个磁盘,没有问题,你仍然在另一个磁盘上拥有所有数据。RAID会提醒你现在处于降级模式,你弹出损坏的磁盘,换上一个闪亮的新磁盘,当你插入新磁盘时,RAID会自动将所有数据从完好的磁盘复制到新磁盘上,你就能在不中断服务的情况下恢复运行。这非常棒。当然,在这种配置下,你放弃了I/O性能提升的好处,并且实际上只获得了投入系统磁盘空间的一半。
- RAID 5:你可以选择RAID 5级别,它结合了I/O性能和容错能力。它通过在多块磁盘上条带化数据来实现这一点,每次写入都分布到不同的磁盘上,但同时还会写入奇偶校验位。这些奇偶校验位允许你在磁盘故障时重建数据。此外,RAID 5将奇偶校验位分布到所有磁盘上,这样你可以在任何时候丢失任何一块磁盘而不会丢失任何数据。当然,你需要至少三块磁盘,并且随着磁盘的增加,你获得的磁盘空间不是线性增长的,因为有一部分空间预留给了奇偶校验信息。但总的来说,这是在保持容错能力的同时提高性能的一种流行且高效的解决方案。

当然,你可以看到这其中的可能性,你可以以不同的方式组合这些方法。如果你想要更高的容错能力或更高的性能,你不必再局限于单一选择。也就是说,你可以组合不同的RAID级别来获得镜像的条带阵列、条带化的镜像阵列等等。

固件和控制器处理所有这些卷管理和热交换操作,这就是为什么系统可以在运行时进行所谓的“热插拔”。看到设备重建阵列并保存数据时的闪烁指示灯,会让任何系统管理员感到高兴。这样一个独立磁盘冗余阵列非常有用。
基于主机的逻辑卷管理 💾

但RAID的概念不一定非要通过特殊的硬件设备来实现。一般来说,任何类型的存储虚拟化都可以使用软件来执行,这意味着内核暴露硬件,然后允许底层软件来管理它。这通常被称为逻辑卷管理。笼统地说,这可以分解为对物理存储单元的管理,例如硬盘或通过光纤通道连接的存储设备。这些单元被划分为物理卷,然后可以组合形成所谓的卷组。这些卷组可以跨越多个物理设备,提供一个抽象层,逻辑卷管理器可以在此基础上进一步组合或创建逻辑卷。这样,你就可以以类似JBOD的方式组合独立磁盘来创建更大的卷;通过允许在不中断服务的情况下更换故障磁盘来实现冗余和容错;允许在添加新磁盘时立即自动调整文件系统大小以扩展空间;提供我们已经讨论过的相同RAID功能;或者自动执行文件系统的定期快照,从而提供便捷的实时备份机制。我们将在本学期晚些时候更详细地讨论文件系统快照和备份。



现在,让我们简要演示一下在典型的Linux系统中使用逻辑卷管理器可能是什么样子。

为此,我们登录到Linux实验室。我们观察到挂载了两个磁盘:/dev/sda1挂载在/boot下,以及一个映射设备作为根文件系统。因此,根文件系统并不位于常规磁盘分区上,而是似乎通过设备映射器进行管理,这是Linux中逻辑卷管理器的基础。让我们查看与磁盘相关的dmesg输出。在这里我们看到/dev/sda似乎是一个SCSI磁盘,包含几个分区。lsblk命令向我们展示了关于这个块设备的更多信息。我们有一个20GB的磁盘,第一个分区挂载在/boot下。第二个分区/dev/sda2是一个扩展分区,因此它只包含/dev/sda5的元信息,而/dev/sda5本身又被划分为两个子分区,都由LVM管理:一个用于根目录/,一个用于交换空间。swapon命令确认系统中可用的交换空间是由另一个设备映射器设备/dev/dm-1提供的。我们将在下一节视频中更详细地研究分区的概念,但如图所示,我们可以看到即使对于一个只有单个磁盘的非常简单的系统,也可以使用LVM。




ZFS存储管理示例 🌲
我想在这里演示的另一件事是使用ZFS来管理存储资源。ZFS是一个源自Sun(现在是Oracle)Solaris操作系统的文件系统。它是一个相当不同的系统,因为它包含了逻辑卷和存储池管理的所有组件,我们将会看到。让我们开始一个新的屏幕会话,并启动一个OmniOS实例。OmniOS是Illumos的一个版本,这是一个开源的Unix系统。它基于OpenSolaris,因此是运行Solaris变体的最简单方式。这里的这个AMI镜像让我们可以开始。在我们的自定义EC2等待函数中,当系统启动并运行时,它会通知我们。好的,让我们登录。我们进来了。让我们再次查看dmesg报告的磁盘设备。这里的磁盘叫做xdf。让我们尝试使用format实用程序来查看这个磁盘的分区表。我们找到一个磁盘,使用历史SCSI寻址方案标识为c1t0d0,其各个分区(在Solaris术语中称为“切片”)在此前缀之后被引用。当我们选择这个磁盘时,我们得到一个警告信息:这个磁盘的slice0是一个活跃的ZFS池的一部分。因此,我们不能在这里使用format实用程序。df命令确认我们的根文件系统似乎位于rpool ZFS池上。让我们通过zpool工具查看一下。在这里我们看到,rpool确实由那个磁盘c1t0d0支持,为我们提供了大约7.5GB的磁盘空间。zfs list命令向我们展示了在此池上创建的文件系统,其方式类似于Linux系统上的扩展分区包含根文件系统。
现在,假设我们正在向服务器添加一个新的物理磁盘,并打算从这些磁盘创建第二个存储池。为此,我们在一个单独的屏幕会话中运行AWS EC2创建卷命令。我创建了一个1GB的卷。然后我们将该卷附加到实例。我们的实例ID是什么来着?让我们用我们的一个别名检查一下正在运行的实例。好了,我们抓取这个实例ID并继续。现在,为第二个卷重复同样的操作,这样我们就可以假装刚刚将两个独立的硬盘连接到我们的服务器。好的,回到我们的服务器。再次查看dmesg输出,我们现在看到出现了两个新磁盘。xdf0仍然是根文件系统,但现在我们还有xdf1和xdf2。请注意,当我们使用AWS EC2附加卷命令时,我们指定了一个不同的设备名称,这有点令人困惑和恼人,因为我们选择的设备名称可能不是实例上操作系统使用的名称,但只能如此。还要注意,我们不必重启实例,我们可以简单地将新硬盘“热插拔”到运行中的系统中,这就是我们的磁盘。这很酷。不管怎样,让我们看看disk info告诉我们什么。它们在这里:我们的根磁盘,8GB大小,以及两个新磁盘,c1t5d0和c1t6d0。现在让我们用这两个磁盘创建一个新池,称之为extra。zpool list显示我们的新extra池结合了这两个磁盘的空间,产生大约1.9GB。所以总是涉及一点开销,我们没有得到完整的2GB空间,但我们看到了如何将存储组合到单个池中。zfs list显示我们的文件系统。但我们甚至还没有在extra池上创建一个文件系统。所以让我们快速创建一个。zfs create extra/space。现在告诉系统在哪里挂载这个新文件系统。好了,新的磁盘空间现在在/mount下可用。我们现在可以像预期的那样向这个文件系统写入数据。好的,到目前为止一切顺利。




但现在假设我们又弄到了一块磁盘驱动器,想把它添加到池中。所以我们创建另一个卷。像之前一样附加它。立刻,disk info显示新磁盘存在,c1t7d0。我们挂在/mount上的磁盘大约有1.8GB空间。我们将新附加的磁盘添加到现有的ZFS池中。然后,砰的一下,我们挂载的磁盘现在大小变成了2.7GB。这真的很酷,对吧?我们不必关闭系统来连接磁盘,也不必对磁盘进行分区或重新创建文件系统或做任何事情。只需将磁盘添加到支持文件系统的池中,我们就获得了额外的空间。这让你对ZFS的灵活性和强大功能有了印象,也说明了存储虚拟化的概念,即以非常灵活的方式使用存储单元来组合和创建文件系统。

练习与总结 📝
好了,又到了休息时间。我想确保你跟上了这些例子,所以这里是我推荐给你的另一组练习。创建一个OmniOS实例,并使用ZFS创建不同类型的池。ZFS支持我们讨论的所有概念,你可以用它通过条带化数据来提高性能,通过镜像数据来增加容错能力,或者通过称为RAID-Z的方式组合这些功能。创建这样一个池并挂载文件系统后,通过分离EBS卷来模拟硬盘故障。系统如何处理这种情况?接下来,思考如果我们添加一个磁盘,系统会如何行为。添加磁盘会增加空间,所以扩展文件系统似乎是一件足够容易的事情。但如果你要移除一个磁盘呢?你能以这种方式缩小文件系统吗?只要文件系统上的数据仍然适合新的池,这似乎是可能的,但如果不是这样呢?正如你所见,这里有很多东西可以尝试,我希望你能以这种方式探索本节视频中的概念。如果遇到问题或有疑问,请随时寻求帮助。好的,今天就到这里。下次我们将讨论传统硬盘的物理结构,并继续我们今天已经略有提及的不同分区的讨论。到时见,感谢观看,再见。

本节课中,我们一起学习了存储虚拟化的核心思想。我们了解了如何通过硬件RAID和软件逻辑卷管理将物理磁盘抽象为逻辑存储单元,从而获得性能提升、容量扩展和数据冗余等好处。我们还通过Linux LVM和ZFS的实际操作,直观感受了存储池的动态管理能力。理解这些概念是构建可靠、高效存储系统的基础。
013:物理磁盘结构 💽
在本节课中,我们将学习硬盘驱动器(HDD)的物理结构。了解这些基础知识,不仅能让我们欣赏其精妙的工程设计,将抽象的数据存储概念与具体的机械设备联系起来,还能帮助我们更好地理解分区和文件系统等更广泛的概念。
硬盘的基本组成
上一节我们介绍了存储的基本模型。本节中,我们来看看最常见的存储单元——直接连接存储(DAS)模型中的硬盘驱动器(HDD)。尽管固态硬盘(SSD)具有诸多优势且日益普及,但为了说明问题,我们仍以传统的IDE硬盘为例,因为许多我们关心的考量因素,恰恰源于这类硬盘的物理特性。
以下是硬盘内部的主要组件:
- 盘片:一个或多个高速旋转的磁性盘片。高性能硬盘的转速可超过每分钟7000转,甚至达到15000转。
- 读写磁头:位于磁头臂末端,用于检测和改变盘片上微小区域的磁化方向,从而读写数据(0和1)。
- 磁头臂:带动所有读写磁头在盘片上方径向移动。所有磁头作为一个整体同步移动。
磁盘的逻辑结构
了解了物理组件后,我们来看看数据在盘片上是如何组织的。盘片被逻辑地划分为多个层次结构。
以下是磁盘的逻辑划分层次:

- 磁道:盘片上以主轴为中心的一系列同心圆环。
- 柱面:所有盘片上相同半径的磁道在垂直方向上构成的圆柱面。多个同心柱面组成柱面组。
- 扇区:磁道被进一步划分成更小的弧段,这是硬盘上最小的可寻址单元。传统上,每个扇区存储
512字节,构成标准的磁盘块。
这个 512 字节的块大小是一个硬件限制。尽管现在已有物理块大小为 4096 字节的硬盘,但大量计算机硬件和软件仍假定块大小为512字节,这意味着这些硬盘通常需要模拟一个比实际更小的块大小。

性能影响因素与区域位记录
由于物理限制,硬盘无法一次性读取少于512字节的数据。此外,其性能还受到几个关键物理因素的限制。
以下是影响传统硬盘性能的主要因素:
- 寻道时间:磁头臂将读写磁头移动到指定磁道所需的时间。为了最小化寻道时间,我们希望将相关数据存储在同一柱面内的连续块中。
- 旋转延迟:盘片旋转,使目标扇区移动到磁头下方所需的时间。平均而言,旋转延迟是盘片旋转半周所需的时间。
- 数据传输率:数据从盘片读取的速率,这取决于连续读取的数据块数量。
观察磁盘结构时,你可能注意到:外圈磁道的扇区物理尺寸比内圈磁道的扇区大。如果每个扇区都固定存储512字节,外圈空间就被浪费了。同时,在恒定角速度下,外圈扇区的线速度更快。
因此,人们利用这些特性,发展出了区域位记录技术。在这种技术下,磁盘被分成多个区域,外圈区域放置更多的扇区。在恒定角速度下,这提高了外圈的数据传输速度(因为单位时间经过的扇区更多),并总体上增加了存储容量(因为有了更多的扇区,即更多的512字节物理块)。
容量限制与寻址方案
除了性能,硬盘的容量也是一个关键限制。容量本质上取决于磁盘上能存储多少个独立的块。我们需要一种方法来寻址每一个块。
早期采用的一种方法是柱面-磁头-扇区寻址方案。其逻辑限制来自:
- 柱面(或磁道)数量
- 磁头数量
- 每磁道的扇区数量
早期的ATA规范、BIOS系统各有不同的限制值,导致实际可寻址的最大容量被限制在很低的水平(例如528MB)。随着时间推移,人们通过使用不同的数据类型来存储CHS参数提高了限制,但整个方案最终被逻辑块寻址所取代。LBA简单地按顺序索引每个块。
然而,LBA仍然存在限制,因为存储块总数需要数据类型。例如,ATA1使用28位LBA,最大支持约137GB;ATA6升级到48位LBA,支持约144PB。

容量限制不仅关乎硬盘本身,启动过程涉及的组件也必须能处理磁盘。例如,主引导记录分区表使用32位数据类型寻址,最大支持约2.1TB。幸运的是,GUID分区表使用64位数据类型,将限制提升到了约9.4ZB。
总结与下节预告
本节课我们一起学习了硬盘驱动器的物理结构。关键要点如下:
- 存在一个由驱动器决定的物理块大小,最常见的是
512字节,这将影响文件系统性能。 - 硬盘容量曾受物理因素(如CHS寻址)限制,即使转向逻辑块寻址,仍受所选数据类型的位数限制。这提醒我们资源总是有限的。
- 物理因素(寻道时间、旋转延迟)直接影响传统硬盘性能,在选择存储方案时需加考虑。
- 旋转磁性盘片的物理属性安排,影响了我们如何对磁盘进行分区,即使存储空间可能是虚拟的(如LVM卷)或跨多个物理驱动器抽象的(如RAID),系统底层仍会模拟此类物理驱动器的行为。

下一节,我们将基于对物理结构的理解,深入探讨磁盘分区的概念。
014:第2周第5节 - 磁盘分区 🗂️
在本节课中,我们将要学习磁盘分区和分区表的核心概念。我们将了解物理磁盘如何被逻辑划分,以及不同操作系统如何实现这一过程。
概述
上一节我们介绍了传统硬盘驱动器的物理结构。本节中,我们来看看如何将一个物理或虚拟磁盘划分为多个逻辑部分,即分区。

分区布局:从饼图到柱面组
许多图形化磁盘工具(例如macOS的磁盘工具)将分区显示为整个磁盘的饼图楔形。然而,这种表示方式具有误导性。
假设分区真的像饼图楔形那样布局。那么我们的磁盘驱动器上会有一个这样的分区。但正如上一节所讨论的,同一分区上的数据很可能被顺序访问。这意味着如果我们想从这个分区读取几个文件,磁头将不得不在整个磁盘上寻道。这会导致效率非常低下。
因此,让我们重新思考分区在磁盘上的实际布局方式。我们不使用饼图楔形,而是使用一组连续的柱面来创建一个分区,其布局如下所示:
[柱面组0] [柱面组1] ... [柱面组N]
现在,该分区上的所有数据都存储在这个柱面组环内。访问这些数据的效率会高得多,因为我们减少了寻道时间,并且可以在磁盘旋转时进行读写,而无需磁头大幅移动。
所以,与其使用饼图楔形,我们应该将分区可视化为连续的柱面组块。
典型分区布局示例
以下是一个典型的分区布局示例,它说明了分区如何按柱面组顺序排列。
首先,我们有一个小的引导分区,通常位于磁盘的开头。在上一节中,我们提到磁盘可能比BIOS能够寻址的范围更大。因此,历史上的布局将引导分区放在磁盘起始附近,以便操作系统能够启动,然后可能使用BIOS无法寻址但操作系统可以寻址的分区。这在现代虽然很大程度上已成为历史,因为现代BIOS和引导加载程序可以处理大磁盘,但这解释了为什么有时你仍然会在磁盘起始处看到独立的引导分区。
接下来,我们可能想创建一个交换分区,它从引导分区结束处开始,并延伸到特定柱面(例如柱面228)。
之后,我们创建根分区以及用户分区,填满剩余的柱面组。
实践操作:在不同系统上创建分区
理解了分区按柱面组排列的原理后,我们来看看如何在实践中操作。我们将比较在三种不同操作系统上创建相似分区布局的过程。
在 FreeBSD 上操作
在FreeBSD实例中,我们使用 disklabel 工具来显示和编辑分区表。
以下是使用 disklabel 编辑分区的基本步骤:
- 显示当前磁盘标签:
disklabel da0 - 进入编辑模式:
disklabel -e da0 - 定义分区。例如,创建100MB的引导分区(a分区):
其中a: 63 204800 4.2BSD 0 063是起始扇区,204800是扇区数(100MB)。 - 继续定义交换分区(b分区)、根分区(e分区)和用户分区(f分区)。注意,在BSD系统上,
c分区传统上保留给整个磁盘,d分区保留给操作系统使用。 - 写入更改并退出。
完成分区后,我们可以再次使用 disklabel 命令查看新的分区表,确认布局符合我们的柱面组示意图。
在 OmniOS 上操作
在OmniOS实例中,我们使用 format 工具。OmniOS默认使用ZFS池作为根文件系统,因此我们查看附加的独立磁盘。
以下是使用 format 工具的基本步骤:
- 启动
format工具并选择磁盘(例如c1t5d0)。 - 工具提示未检测到Solaris分区表(即主引导记录),因此我们运行
fdisk创建一个。 - 然后进入
partition子菜单编辑分区。 - 编辑分区0为100MB的可引导、可挂载引导分区。
- 编辑分区1为256MB的可引导但不可挂载交换分区。
- 编辑分区3为256MB的根分区。
- 编辑分区4为用户分区,使用剩余磁盘空间。
- 写入标签并退出。
之后,可以使用 prtvtoc 工具打印卷目录表,显示相同的分区信息。虽然与FreeBSD的示例有些不同,但核心概念相似。
在 Linux 上操作
在Linux(例如Fedora)实例中,有多种工具可用。我们使用 fdisk 或 cfdisk。
以下是使用 fdisk 的基本步骤:
- 使用
fdisk -l查看当前分区表。 - 使用
fdisk /dev/vdb(假设磁盘为vdb)进入交互模式。 - 使用
n命令创建新分区,依次指定引导分区(100MB)、交换分区、根分区(256MB)和用户分区(剩余空间)。 - 使用
w命令将分区表写入磁盘。
我们也可以使用 cfdisk 工具,它提供菜单驱动的界面来验证布局。或者使用 lsblk 命令查看磁盘的分区情况。这些工具虽然语法和界面不同,但实现的目标一致。
为何需要分区?
最后,我们应该快速了解一下为什么我们需要对磁盘进行分区,而不是将所有磁盘空间作为一个巨大的整体来使用。
以下是分区的一些主要原因:
- 分离系统数据与用户数据:例如,防止用户向其家目录写入数据时填满系统分区。
- 分离引导分区:允许引导加载程序启动一个能够寻址对于BIOS来说过大的磁盘的系统。
- 使用不同的文件系统类型:不同的分区可以使用更适合其用途的文件系统(如EXT4、XFS、ZFS等)。
- 应用不同的挂载选项:例如,将系统分区挂载为只读,这样即使系统被入侵,攻击者也无法持久化安装后门;或者将某个分区标记为
no suid和no exec以增强安全性;还可以在文件系统中启用或禁用异步I/O。
总结
本节课中,我们一起学习了磁盘分区的核心概念。我们了解到硬盘驱动器上的分区是柱面组的连续区域,因此不能可视化为饼图楔形,尽管许多工具中常见这种表示方式。
我们看到了不同的分区方案,提到了不同类型的分区(如引导、交换、根、用户分区)。虽然不同的操作系统可能使用不同的工具并存在一些细微差别,但总体概念保持不变:我们利用柱面组、磁道和扇区来寻址块,并通过描述这些分区来定义分区,最后将分区表写入磁盘。
我们还提到了BIOS或MBR分区与操作系统分区的不同,并将在未来的视频中详细探讨。此外,我们看到了分区可能重叠的情况,这取决于分区的目的(例如,描述整个磁盘的分区会包含其他逻辑分区)。

最后,我们讨论了分区磁盘的多种原因,包括数据分离、引导管理、文件系统多样性和安全加固等。随着后续课程深入文件系统、引导加载程序和系统启动过程,我们将重新审视其中的许多主题。
015:启动过程与主引导记录(MBR)🔧
在本节课中,我们将学习计算机系统的启动过程,并深入了解一个关键组件:主引导记录(MBR)。我们将从宏观的启动流程开始,逐步深入到MBR的具体结构和工作原理。
概述
启动过程是计算机从通电到操作系统完全运行所经历的一系列步骤。理解这个过程对于系统管理员至关重要,因为它涉及到硬件初始化、固件交互以及操作系统的加载。本节我们将重点探讨传统的BIOS启动流程及其核心——MBR。

启动过程详解
上一节我们讨论了磁盘分区。本节中,我们来看看系统如何利用这些分区来启动。
当系统启动时,我们可能在控制台上看到类似以下的消息。这是一个NetBSD引导加载程序的显示界面。它是一个BIOS引导加载程序,提供了一个交互式菜单来选择不同的启动方式。如果我们在超时前不做选择,它将开始正常的启动过程,加载BSD内核,并生成屏幕上显示的硬件初始化消息。最终,控制权会交给init进程,我们可以看到init进程继续引导过程,挂载文件系统,启动网络,并启动系统配置的所有守护进程,最后留下一个登录提示。
我们可以将刚刚看到的过程分解为以下几个独立的步骤。
以下是启动过程的各个阶段:
- 通电自检(POST):物理服务器通电后,系统首先执行通电自检。它会检查内存、CPU、存储设备等基本硬件的状态,并确定从哪个设备启动。
- 加载第一阶段引导加载程序:自检完成后,系统会寻找第一阶段引导加载程序。传统上,这意味着在配置的启动磁盘的第一个扇区(启动扇区)中寻找特定的签名和可执行代码。这就是我们所说的主引导记录(MBR)。
- 加载第二阶段引导加载程序:第一阶段引导加载程序可能会将控制权交给第二阶段引导加载程序。这是必要的,因为第一阶段引导加载程序的大小非常有限。如果需要执行比简单启动更复杂的操作,就需要跳转到其他地方的特定代码,然后才能找到并加载内核。
- 加载内核:在第二阶段引导加载程序之后,最终会加载内核。对于虚拟机(如AWS实例),会有一个特殊的内核。例如,如果使用Xen进行虚拟化,启动的将是Dom0管理程序内核。
- 内核初始化:内核启动后,会初始化它发现的硬件。在虚拟机的情况下,这是虚拟硬件。
- 启动用户空间进程:内核将控制权交给
init或systemd等进程,以启动各种服务。 - 运行应用程序:如果启动的系统是Web服务器,应用程序将绑定到正确的网络端口并最终提供内容。
这些步骤涵盖了从通电到提供服务的完整启动过程。但今天,我们只关注前几个步骤,看看我们是如何到达内核的。

固件:BIOS与UEFI
当物理服务器通电时,你首先可能看到的是类似下图的内容。这是一个美国Megatrends BIOS的示例,显示了一次成功的通电自检。通常,此时它会直接跳转到配置的启动磁盘的启动扇区,但也可能允许你配置某些方面。也就是说,尽管这段软件必然很简单,但仍然允许一些灵活性。

由于BIOS通常位于主板上的只读存储器芯片中,不易更改,我们不称它为软件。它更难改变,但也不完全是硬件,所以我们称之为固件。下图展示了一个BIOS配置菜单的示例,允许我们选择设备的启动顺序等。
但BIOS可以追溯到70年代的CP/M操作系统,最初是IBM PC的专有技术,后来被其他人逆向工程,但并未标准化,并且通常针对特定的主板或其他硬件进行专门设计。BIOS也有一些技术限制,我们在上一个视频中看到并讨论了某些BIOS在寻址大磁盘时遇到的问题。

因此,我们现在有了统一可扩展固件接口(UEFI),它提供了一种现代且标准化的方式,用于在操作系统和底层固件之间进行交互。
但正如经常发生的那样,了解历史以及过去是如何做事的非常重要,事实证明,出于向后兼容的原因,今天仍然经常采用旧的方式。这就是为什么我们现在不看UEFI,而是看传统的MBR。
深入主引导记录(MBR)
传统的BIOS期望在启动磁盘的第一个扇区(即前512字节)找到主引导记录。没错,主引导记录只有512字节大小。在这512字节中,我们必须容纳相当多的信息,以及启动系统所需的代码。让我们看看这是什么样子。
在第一个扇区的末尾,我们期望找到“魔法字节”0x55和0xAA。这两个字节的存在向BIOS表明这是一个有效的启动扇区。
紧接着前面的64字节保存着分区表。这个分区表与我们上一个视频中讨论的分区表不同。这是BIOS分区表,它描述了BIOS可以看到哪些分区。
一个BIOS分区表条目是16字节大小,因此我们最多只能有四个这样的分区。也就是说,带有MBR的磁盘最多只能有四个BIOS分区。
但我们知道,如果需要,我们的操作系统可能希望将磁盘划分为多于四个分区,这本身就说明了区分BIOS分区和操作系统分区的必要性。也就是说,BIOS部分实际上只定义了磁盘的哪些部分属于给定的操作系统。操作系统然后用该磁盘切片做什么完全取决于它自己。
请记住,在上一个视频中,我们展示了BSD系统在其磁盘标签中使用d分区来引用整个物理磁盘,使用c分区来引用专用于此操作系统的磁盘部分。这个c分区实际上就是我们在这里定义的内容,然后操作系统可以在其中创建额外的分区。
无论如何,用2个字节存放魔法数字,64个字节存放分区表,我们还剩下446字节。也就是说,启动系统所需的一切都需要装在这446字节里。这就是为什么我们常说这是第一阶段引导加载程序。它只是足够的代码,可以将系统启动到一个点,也许可以访问第一磁道上的其他扇区,然后找到更复杂的代码并将控制权转移给它。那段代码被称为第二阶段引导加载程序。GNU GRUB就是一个可能包含多个阶段的引导加载程序的例子。
回到分区。我们有一个由区区64字节组成的分区表,留给我们更少的16字节来描述磁盘。我们如何组织这16个字节呢?
以下是BIOS分区表条目的16字节结构:
- 活动标志(1字节):指示此分区是否为活动(可启动)分区。
- 起始CHS地址(3字节):使用我们上一个视频讨论的柱面-磁头-扇区寻址方案来寻址磁盘的第一个扇区。
- 第一个字节表示磁头(8位,最多256个磁头)。
- 第二个字节的低6位表示扇区(最大扇区号为64),高2位是柱面地址的高位。
- 第三个字节表示柱面地址的低8位。结合第二个字节的高2位,我们总共有10位用于柱面,最多1024个柱面。
- 分区类型(1字节):标识操作系统的分区类型,例如NetBSD或Linux。
- 结束CHS地址(3字节):与起始CHS地址结构相同,表示分区的最后一个扇区。
- 起始LBA地址(4字节):分区的第一个扇区的逻辑块地址。
- 扇区总数(4字节):分区包含的扇区总数。
由于CHS寻址的限制,我们无法寻址超过一定大小的磁盘,这个限制相当小。因此,我们改变了寻址方案,使用逻辑块寻址。现在我们有两种寻址扇区的方式。如何从一种转换到另一种呢?
我们可以从LBA地址开始,然后使用以下公式确定C、H和S值:
C = LBA / (HPC * SPT)H = (LBA / SPT) % HPCS = (LBA % SPT) + 1
其中,HPC是每柱面的磁头数,SPT是每磁道的扇区数。然后使用这些值填充CHS字段。
动手操作:创建MBR分区
现在我们知道这16个字节是什么样子了,我们应该能够通过将所需的字节写入正确的偏移量,为任何磁盘创建一个带有有效分区表条目的启动扇区。让我们试一试。
我们像往常一样,启动一个新的NetBSD实例,然后创建一个新的卷来操作。我们等待实例启动,并使用另一个shell函数附加卷以节省输入。登录实例后,我们通过dmesg命令检查磁盘。我们看到根磁盘和新附加的磁盘xbd1。
让我们通过fdisk命令查看根磁盘的BIOS分区表。我们看到驱动器的逻辑几何结构以及BIOS几何结构。分区表显示第一个分区类型为NetBSD且处于活动状态,起始于扇区2048,并显示了柱面-磁头-扇区地址。fdisk向我们展示了所有这些信息。但它实际上是什么样子的呢?
让我们使用dd命令检索磁盘的前512个字节,并用hexdump工具将其显示为十六进制。所有这些字节都是实际的引导代码。第一个分区在这里定义。在这16个字节里。在最后这里,我们看到MBR签名55 AA。
让我们单独看一下第一个分区。我们知道它的大小是16字节,位于偏移量0x1BE(十进制446)。所以,这就是它。这16个字节描述了fdisk显示的第一个分区条目。到目前为止,一切顺利。
现在,我们的第二个磁盘是什么样子的?fdisk显示没有定义分区,也没有活动分区。如果我们查看驱动器上的字节,毫不奇怪,它们都是零。
那么,我们如何将这个(全零)变成那个(有效的MBR)呢?让我们从头开始。或者说,从结尾开始。注意fdisk告诉我们这里的分区表无效,因为扇区末尾没有魔法数字。让我们修复它。
我们使用printf将十六进制字节55 AA写入/dev/xbd1的偏移量510处。现在,我们注意到fdisk不再抱怨分区表无效,但它仍然说没有活动分区。所以让我们在这里创建一个NetBSD分区。
为此,我们需要获取NetBSD的分区类型标识符。它是169。169的十六进制是什么?让我们使用bc工具来计算。A9是我们想要的十六进制值。让我们将其写入偏移量0x1C2(十进制450)。然后,通过将0x80写入偏移量0x1BE(十进制446)来将此分区标记为活动。现在看看fdisk怎么说。分区0现在是一个NetBSD分区,并被标记为活动。
但我们仍然缺少此分区的大小和定义。正如我们在这里看到的,柱面-磁头-扇区的定义都是0。所以,让我们定义它们。此分区的起始位置是扇区2048,因为正如我们提到的,整个第一磁道通常保留给额外的引导代码。所以我们可以使用我们的公式,从LBA地址计算CHS地址,这里的LBA地址是2048。
计算过程如下:
- 柱面 = 2048 / (16065) = 0
- 磁头 = (2048 / 63) % 256 = 32 (十六进制
0x20) - 扇区 = (2048 % 63) + 1 = 33
现在我们可以写入这三个字节(磁头、扇区、柱面)到偏移量0x1BF(十进制447)作为起始扇区。检查一下,起始扇区在柱面0,磁头32,扇区33。
现在我们需要最后一个扇区。我们知道总共有这么多扇区,但第一个扇区包含MBR,所以减去它。然后除以每柱面的扇区数得到柱面地址的十进制数,这里是391。余数是10040。现在我们除以BIOS的每磁道扇区数63,得到十进制159。所以我们的磁头字节是0x9F。我们的扇区值是24十进制。但请记住,我们现在需要组合柱面和扇区的位。
我们需要将其转换为二进制。柱面391的二进制是110000111。取其高2位01,加上扇区的6位二进制011000,组合成01011000,即十六进制0x58。柱面的低8位是10000111,即十六进制0x87。所以现在我们有我们的C、H、S字节:0x9F, 0x58, 0x87。让我们将这些写入偏移量0x1C3(十进制451)。fdisk告诉我们什么?到目前为止看起来不错。我们有一个起始CHS地址和一个结束CHS地址。
但我们的MBR也需要LBA地址。让我们继续添加它们。我们知道起始地址的LBA是2048,即十六进制0x800。但LBA字段需要4个字节,并且使用小端字节序。所以,我们把0x800转换成00 08 00 00,并将这些字节写入偏移量0x1C6(十进制454)。最后一个扇区的LBA是总扇区数减去2048,转换为十六进制,再转换为小端序,写入偏移量0x1CA(十进制458)。现在,fdisk向我们显示了一个具有正确大小和地址的适当分区。
当然,有一种更简单的方法来做到这一点。让我们用零覆盖MBR来清除它。然后我们可以使用fdisk来指定我们想要激活分区0,指定类型169用于NetBSD,起始于扇区2048,并具有给定的大小。结果看起来就像我们手动写入磁盘一样。这表明我们用来操作磁盘的工具真的没有什么神奇之处。这完全就是知道将哪些位写入哪个位置的问题。这一点,以及能够使用dd、hexdump和printf来操作和写入单个位和字节,是相当有用的。
总结与练习
让我们回顾一下。我们首先讨论了典型的启动序列。我们说这一切都始于一些基本的固件,可能是BIOS或UEFI等。它可能执行POST检查,并初始化它看到的硬件,然后将执行转移到第一阶段引导加载程序,例如MBR。然后,MBR可能通过将控制权交给第二阶段引导加载程序来继续引导过程,第二阶段引导加载程序然后加载内核,内核最终将控制权交给用户空间进程,如init。这里需要注意的一点是,在虚拟化硬件中,其中一些步骤会重复,一些可能被跳过,一些可能被模拟,因为我们的虚拟主机在启动时会初始化虚拟硬件。但最终,我们到达了一个运行中的系统。
理解了MBR的细节后,让我们考虑一些额外的练习。
以下是推荐的课后练习:
- 比较不同操作系统的启动输出:AWS实例允许你获取发送到虚拟控制台的输出。不同的操作系统显示不同级别的详细信息。当然,每个操作系统的启动过程都不同。一个好的练习是比较不同操作系统的输出。启动几个实例,例如NetBSD、FreeBSD、Ubuntu、Fedora或OmniOS实例,并比较控制台的输出。特别注意文件系统或磁盘特定的消息。确保你理解输出的含义。
- 思考启动过程的安全性:正如我们所看到的,这个过程有好几层,涉及不同的软件:主板ROM中的固件,我们通常不会想到的写入启动扇区的位。我们如何确保没有人篡改过软件或固件?可信计算的概念在这里发挥作用。查找相关术语,例如远程认证或安全启动。
- 预习:你可能已经注意到,我们还没有讲到实际使用文件系统或那可能是什么样子。让我们在下一个视频中解决这个问题。

本节课中,我们一起学习了计算机从通电到操作系统运行的完整启动流程,并深入剖析了传统BIOS启动中的核心——主引导记录的结构与操作。理解这些底层机制是进行系统管理、故障排查和安全加固的基础。
016:第03周第2节 - 文件系统 🗂️
在本节课中,我们将要学习文件系统的基本概念。我们将从一个非常简单的例子开始,探讨文件系统的核心目标、工作原理以及它与数据存储格式的关系。
上一节我们介绍了引导扇区和主引导记录,其中定义了磁盘分区。在本节中,我们来看看文件系统本身。

什么是文件系统?
文件系统的基本目标是什么?它的目的是什么?众所周知,文件系统的主要功能是存储数据,例如猫的图片。那么,我们如何做到这一点呢?
一个非常朴素的想法是,尝试将所有猫的照片一个接一个地存储在磁盘上。这听起来并不复杂。让我们尝试一下。
一个朴素的存储尝试
我们从一个默认的NBSD实例开始,并像之前一样挂载一个卷。我们假设自己是磁盘 xpd1 的文件系统,需要管理1GB的空间。
以下是存储第一个猫图片的步骤:
- 使用
printf和dd命令将字节直接写入磁盘的开头。 - 查看磁盘上的数据,我们看到猫图片由四个字节组成:
F0 9F 98 B8。 - 我们成功存储了第一个文件。要读取它,只需读取前四个字节。
现在存储第二个猫图片。为了不浪费空间,我们将其直接写在第一个图片之后。我们可以读取所有字节来获取两个文件,也可以分别获取它们。
当我们尝试存储第三个猫图片时,问题出现了。为了单独检索第三个文件,我们需要知道它从哪里开始、到哪里结束。仅仅假设每个图片都是4字节是行不通的。
引入“存储桶”概念
这说明了朴素方法的一个问题:为了能够单独检索文件,我们需要知道它们的起始和结束位置。因此,我们可能希望为每个文件分配特定的区域。
以下是改进后的方法:
- 在磁盘上为每个猫图片预留一个区域(“存储桶”)。
- 确保每个存储桶只放入一张猫图片。
- 这样,我们就能确切知道每张图片的起始位置。
让我们演示这种方法。我们清零磁盘并从头开始。第一个猫图片直接写入磁盘开头。第二个猫图片写入偏移量512字节处。我们声明每个存储桶大小为512字节。这样,我们总是知道下一个图片的起始位置,偏移量以512字节递增。
现在,我们可以通过简单地读取完整的512字节并调整偏移量来轻松检索文件。然而,我们浪费了大量空间,因为图片小于512字节,并且我们读取了完整的512字节,即使我们只需要前两个文件的4字节和第三个文件的14字节。
添加元数据
也许我们应该为每个文件添加一些元数据。让我们再次重新开始。这次,我们在文件数据前加上文件编号和文件大小(以字节为单位)。这样,我们以后可以轻松确定需要读取的确切字节数。
现在,磁盘上的每个文件数据前都有一个1字节的字段表示文件编号和一个1字节的字段表示大小。这样,我们就开始将元数据(本例中是文件编号和大小)与文件关联起来。
当然,我们可能希望添加额外的文件属性,即关于我们猫图片的更多信息。因此,我们可以定义一种新格式,指定使用16字节的元数据:
- 一个2字节的标识符。
- 4字节用于权限(如用户、组、其他、读、写、执行)。
- 1字节表示文件的数字所有者。
- 1字节表示组。
- 然后我们为大小保留4字节(4字节比1字节更好,因为1字节最多只能表示256字节的文件)。
分离元数据与文件数据
我们进一步决定,将元数据与文件数据完全分开可能更好,因为这里的16字节对每个文件都是一致的,但文件数据的大小是可变的。因此,我们决定将元数据放在磁盘的开头,而实际的文件数据则写在磁盘的其他地方。
为了能够将元数据映射到文件数据,我们在元数据中包含偏移量。现在,这开始看起来更像一个文件系统了。
让我们看看在我们的模拟中,这个简单系统会是什么样子。我们写入第一个文件的元数据:文件编号 0 1,权限 0 7 4 4,用户ID和组ID都是 0,文件大小 4,以及实际数据的偏移量。然后,我们将实际文件数据写入元数据中指定的偏移量。
第二个文件也类似地处理其元数据和文件数据。现在,查看磁盘上的前32字节,我们应该能找到两个文件的元数据。通过从元数据中确定的文件偏移量,我们可以检索文件内容。
这样,我们就得到了一个非常简化的文件系统,其中元数据与文件数据是分离的。这在某种程度上概念性地说明了文件系统的工作原理。
存档格式与文件系统的相似性
在结束之前,我想再展示一点:存储存档格式实际上与文件系统非常相似。
考虑目录 /tmp。我们创建几个普通文件。运行 ls 命令时,我们可以看到文件的所有元数据,如inode号、权限和所有权等。当然,我们也可以使用 cat 命令显示猫图片的文件内容。
现在,我们可以使用 tar 工具创建这些文件的存档,包括所有元数据。但这次,我们不将存档写入文件,而是直接写入原始磁盘。当我们以十六进制检查字节时,会看到存档的结构。你会发现,这看起来与我们为简单文件系统创建的格式有些相似。

也就是说,在某些偏移量处,我们找到与文件关联的元数据,包括所有权和文件名。然后,在这些元数据之后,我们找到实际的文件数据字节。就像我们可以将数据写入原始磁盘一样,我们也可以从磁盘读取数据并直接通过管道传递给 tar 工具,然后提取文件及其所有元数据。
这说明了,只要以你理解并定义的格式写入数据,你并不一定需要在磁盘设备上有一个文件系统。或者更确切地说,存档文件和文件系统快照之间没有本质的区别。
总结
本节课中我们一起学习了文件系统的基础知识。让我们在休息前回顾一下。在下一个视频中,我们将不再需要假装自己是一个文件系统。
但我们确实了解到,虽然文件系统负责在磁盘上存储数据,但为了读取或写入文件,我们需要知道在磁盘的哪个位置写入实际数据。我们也知道需要一些元数据,并且这些元数据可能与常规文件数据分开存储。
总而言之,在高层面上,文件系统实际上只是描述了一种数据存储格式。当然,还有其他考虑因素,特别是关于效率方面。但大致来说,我们现在知道了文件系统是如何工作的。
在下一个视频中,我们将看看传统的Unix文件系统(UFS),以及该文件系统如何实现我们在本视频中讨论的一些概念。
在继续下一节之前,请务必在你的虚拟磁盘和猫图片上实践一下,并重演 tar 作为文件系统格式的例子,以确保你理解了这些概念。

感谢观看。
017:UNIX文件系统详解 🗂️
在本节课中,我们将深入学习UNIX文件系统(UFS)的结构和核心概念。我们将从磁盘分区开始,逐步深入到文件系统的内部组织,包括超级块、柱面组、inode和数据块。通过理解这些基础组件,你将能够更好地管理文件系统并优化其性能。
概述
上一节我们探讨了文件系统的基本含义。本节中,我们将具体分析一个更复杂的实现——标准的UNIX文件系统(UFS)。UFS是Unix Version 7中实现的历史性文件系统,后来被伯克利快速文件系统(FFS)所取代。虽然如今存在许多不同类型的文件系统(如日志文件系统、ZFS等),实现方式差异很大,但其基本概念是相通的。因此,理解UFS的结构对我们非常有帮助。
磁盘与分区结构
回顾上一节内容,我们认识到文件系统需要为要存储的数据分配存储单元,并且需要将元数据与文件数据分离存储。一个真实文件系统的结构比我们之前构建的简单模型要复杂。
物理或虚拟磁盘可以被划分为逻辑分区,这些分区信息通常记录在主引导记录(MBR)中。


这样的逻辑分区可以进一步划分为文件系统分区。在BSD派生的系统上,这些分区使用所谓的磁盘标签来描述。
我们决定在其上创建文件系统的特定操作系统分区由柱面组组成。这个概念即使在使用SSD等没有物理柱面的存储设备时也同样适用。我们可以将物理磁盘可视化为下图所示的结构。


超级块与柱面组
由于每个分区都包含这些柱面组,我们需要以文件系统特定的方式描述它们,并存储一些关于文件系统本身的元信息。这些数据存储在所谓的超级块中,通常位于分区的起始位置附近。

如果分区是启动分区(如我们的主操作系统分区),那么它也可能在磁盘开头包含启动块。

每个柱面组本身包含实际的数据块(存储构成我们文件的实际字节的部分),以及一个inode列表和为与inode相关的元数据预留的块(Inode块)。由于整个文件系统的结构都写在超级块中,如果丢失这一个块将是灾难性的,因此文件系统会复制超级块和柱面组,从而允许从损坏的超级块中恢复文件系统。


Inode与数据块
最后,当我们谈论文件和目录时讨论的实际数据存储在不同的数据块组中:Inode数据块和文件数据块。如下图所示,文件的元数据(我们在上一节中提出的所有信息)与实际的文件字节是分开存储的。


可以看出,我们严格遵循计算机科学的永恒准则:每深入一层抽象,就会揭示出更多的层次。但至少我们最终得到了一个相对容易理解的图表。
实践:查看文件系统属性

现在,让我们看看是否能将我们从图表中的理解映射回在磁盘上实际观察到的内容。

这里我们有一个通常的NetBSD实例,并附加了额外的卷。要检查文件系统的属性,我们可以使用 dumpfs 工具。让我们先查看一下它的手册页。这个工具允许我们查看关于超级块、柱面组和inode等的详细信息。
以下是使用 dumpfs 命令的示例:
dumpfs /dev/rd0a


运行后发现没有超级块信息。这并不奇怪,因为我们还没有在这个磁盘上创建文件系统。没有文件系统,就没有超级块,也就没有关于文件系统的元信息。


创建文件系统
所以,让我们从创建一个磁盘标签开始。我们接受默认设置,即只使用一个分区覆盖整个磁盘。因为这是我们的第二个磁盘,不需要启动块或交换分区等。

现在,dumpfs 仍然不知道这个磁盘的任何信息。所以让我们在上面创建一个文件系统。


为此,我们使用 newfs 工具。newfs 工具用于创建新的文件系统。从手册页可以看出,它有几个有用的选项。

例如,我们可以指定文件系统的块大小。这是文件的最小存储单元。请注意,这是文件系统特定的属性。存储设备本身,正如我们在上一节讨论的,有自己的物理块大小,通常是512字节。

另一个我们可以在这里指定的选项是创建的inode总数,以及其他几个选项。



以下是创建文件系统的命令示例:
newfs /dev/rd0c
我们创建了一个文件系统,默认块大小为16K,跨越6个柱面组,每个组有21504个inode。超级块存储在分区中的这些位置。
挂载与使用文件系统

现在我们有了一个文件系统,我们可以挂载它,并在上面创建文件。
以下是挂载文件系统并创建文件的命令示例:
mount /dev/rd0c /mnt
echo "Hello" > /mnt/cat1
echo "World" > /mnt/cat2

现在让我们再次查看 dumpfs。创建文件系统后,我们现在在这里看到了大量信息,包括关于柱面组的信息。
dumpfs 的输出也可以显示关于inode及其使用的信息。这里我们看到当前磁盘上文件的信息,包括它们的权限、文件大小、创建时间和所有权等。我们可以与 ls 命令的输出进行比较。

顺便说一下,inode编号2并没有丢失。它是这个磁盘上根目录的inode。其大小、所有者和组信息都显示在 dumpfs 的输出中。
如果我们使用 -v 标志运行 dumpfs,那么我们还可以获得所有inode的信息,包括那些尚未使用的inode。
也就是说,我们可以看到我们的文件系统在创建时确实分配了固定数量的inode,因此我们在创建文件系统的同时就限制了可以在此磁盘上存储的文件数量。在这种情况下,每个柱面组有2153个inode,这意味着这个磁盘最多只能存储大约130万个文件。
核心概念总结
以下是本节介绍的核心概念和可调参数:
- 块大小:文件系统读写数据的基本I/O单元。公式表示为
文件系统块大小。它可以与磁盘的物理块大小不同。 - Inode总数:文件系统可以容纳的文件总数,在创建时固定。这限制了文件系统的最大文件数。
- 柱面组:磁盘的逻辑分区,用于组织inode和数据块。
- 超级块:存储整个文件系统元数据的关键结构,被复制到多个柱面组以实现容错。
性能调优考量
是时候休息一下了。让我们总结一下。我们已经看到,我们的文件系统将磁盘划分为柱面组。关于文件系统和柱面组的信息存储在超级块中。为了避免它成为单点故障,超级块被复制到不同的柱面组中,从而允许即使一个柱面组损坏,也能从备用超级块恢复和重建文件系统。
我们还看到,我们的文件系统有块大小的概念,这是文件系统获取或写入数据的基本最优I/O单元,并且可能与磁盘的物理块大小不同。
接下来,与我们在上一节自己提出的想法一致,我们希望将实际文件数据的写入位置与元数据的写入位置分开。这允许更高效的存储和更少的空间浪费,同时也提高了I/O效率,因为通常我们并不真正需要访问文件的实际数据。想想当你运行 ls -l 时,你是在检索和显示与文件相关的所有元数据,而不是内容。通过将Inode块与数据块分离,我们可以快速获取所有元数据,而无需来回寻道到数据块。
谈到inode,我们观察到inode的总数(即文件系统可以容纳的文件总数)在文件系统创建时是固定的。这与文件系统块大小一样,只是我们在文件系统创建时可以调整的各种参数中的两个,以适应磁盘的预期用途。也就是说,如果我们知道我们正在设置的磁盘主要用于存储相对少量的大文件,那么我们可能会选择与存储大量小文件时不同的inode密度和块大小。在第一种情况下,你会使用更大的块大小来提高I/O效率,并选择更少的inode和片段。在后一种情况下,则使用更小的块大小和更多的inode。
fs 手册页的第5节有更多详细信息,尝试使用不同的参数创建文件系统,并观察基于不同用例的性能差异,这将是一个很好的练习。
总结
本节课中,我们一起学习了UNIX文件系统的基础知识。我们了解了磁盘如何被组织成柱面组,元数据如何通过超级块和inode进行管理,以及数据块如何存储实际的文件内容。我们还通过实践操作,使用 newfs 创建文件系统,并使用 dumpfs 查看其内部结构。尽管其他类型的文件系统使用不同的技术来管理存储空间,但我们在此讨论的很多内容仍然直接适用。
有了这些信息和练习,我们现在对Unix文件系统的基础有了很好的理解。接下来,我们将简要总结我们正在处理的不同文件类型及其一些属性,并讨论我们在哪里挂载文件系统以及文件系统层次结构存在哪些约定。

下次见,感谢观看。
018:第3周热身练习1 - 在NetBSD上调整文件系统大小 💾
在本教程中,我们将学习如何在NetBSD操作系统上非破坏性地调整一个现有文件系统的大小。我们将通过一个完整的实践练习来演示如何扩展和收缩文件系统,同时确保数据安全。

概述
在第二周,我们已经完成了一些与文件系统相关的实践练习。本节我们将继续实践课堂所学的知识。具体来说,我们将使用一个AWS实例上的NetBSD系统,对一个文件系统进行扩容和缩容操作,并验证数据的完整性。
上一节我们讨论了ZFS存储池的扩展和存储虚拟化。本节中我们来看看在不使用ZFS的传统Unix文件系统上,是否也能实现类似的功能。
准备工作

以下是本次练习的步骤概览:
- 启动一个NetBSD实例,并为其附加一个新的存储卷。
- 在该磁盘上创建两个512MB的分区。
- 在每个分区上创建文件系统,挂载它们,并创建一个测试文件以验证操作的非破坏性。
- 将第一个分区扩展回1GB,并验证文件可访问。
- 将该分区收缩至256MB,观察结果。
操作步骤
1. 创建实例与存储卷

首先,我们创建一个新的存储卷,并启动一个NetBSD实例。我们可以立即将卷附加到实例上,无需等待实例完全启动。请确保已加载之前定义的AWS命令行别名。
2. 查看磁盘与准备

实例启动后,我们通过SSH连接。首先,查看系统磁盘情况。

使用 dmesg 命令查找我们附加的第二块磁盘,它被识别为 ld5。

运行 disklabel 命令查看当前分区表。

3. 创建分区


接下来,我们以交互模式编辑分区表,创建两个512MB的分区。


以下是具体操作命令:
- 进入交互模式后,首先显示当前分区表。
- 重新定义分区
a,接受默认文件系统类型,从磁盘起始位置开始,指定大小为512m。 - 创建第二个分区
b,同样使用标准Unix文件系统,从分区a之后开始,延伸到磁盘末尾。 - 最后,将新的分区表写入磁盘。



4. 创建并挂载文件系统

在两个新分区上分别创建文件系统。




使用 newfs 命令:
- 在分区
ld5a上创建:newfs /dev/rld5a - 在分区
ld5b上创建:newfs /dev/rld5b


挂载这两个分区:
- 将
ld5a挂载到/mount - 将
ld5b挂载到/mount2


使用 df -h 命令确认磁盘空间符合预期。

5. 创建测试文件


在第一个分区上创建一个测试文件,用于后续验证。
touch /mount/testfile


6. 扩展文件系统


现在,我们尝试扩展第一个分区。首先,需要卸载该文件系统。


为了给文件系统扩容,我们需要先调整分区表,释放空间。
- 编辑分区表,删除分区
ld5b。 - 将分区
a的大小调整为占用整个磁盘。

然后,运行 resize_ffs 命令来扩展文件系统。指定原始磁盘设备 /dev/rld5a,该命令会自动根据磁盘标签信息扩展到可用空间。
resize_ffs /dev/rld5a

完成后,重新挂载分区,并使用 df -h 验证其大小已变为约1GB。检查测试文件 testfile 依然存在,证明扩展操作是非破坏性的。

7. 收缩文件系统

接下来,我们尝试将1GB的文件系统收缩到256MB。首先,再次卸载它。


收缩操作顺序与扩展相反:
- 首先,使用
resize_ffs指定新的大小(256MB对应的扇区数,例如524288)。resize_ffs -s 524288 /dev/rld5a - 然后,调整磁盘标签,将分区
a的大小设置为对应的扇区数。 - 利用释放出的空间,创建一个新的分区
b,占据磁盘剩余部分。


重新挂载收缩后的分区 a,确认其大小已变为256MB,并且测试文件仍然完好。



此时,新的分区 b 还没有文件系统,因此无法直接挂载。我们需要在其上创建一个新的文件系统。
newfs /dev/rld5b
创建完成后,即可挂载并使用第二个分区。
总结
本节课中我们一起学习了在NetBSD上调整传统Unix文件系统大小的完整流程。
我们了解到,要扩展一个文件系统,必须首先调整分区表,为文件系统提供增长的空间,然后使用 resize_ffs 命令进行扩展。需要注意的是,扩展操作会覆盖目标空间上的原有数据。
反之,要收缩一个文件系统,顺序则相反:先使用 resize_ffs 命令收缩文件系统本身,然后再调整分区表以匹配新的尺寸。释放出的空间可以用于创建新分区,但必须在其上建立新的文件系统才能使用。

在整个过程中,原始文件系统上的数据始终保持不变,这证实了操作的非破坏性。下次课程,我们将在Linux系统上完成相同的任务。
019:在 Debian Linux 上调整文件系统大小 🔧💾
在本节课中,我们将学习如何在 Debian Linux 系统上调整文件系统的大小。我们将通过一个完整的操作流程,演示如何扩展和收缩一个文件系统,并验证数据在调整过程中是否得到保留。
上一节我们介绍了在 NetBSD 上使用 resize_ffs 工具调整文件系统。本节中,我们来看看在 Linux 系统上完成相同任务的过程。尽管工具不同,但只要工具支持,其核心概念和基本步骤在不同操作系统间是相通的。

概述与准备 📋
我们将启动一个 Debian Linux 实例,并执行以下步骤:
- 创建一个新卷并附加到实例。
- 创建两个 512 MB 的分区作为起点。
- 在分区上创建文件系统,挂载并添加文件以证明操作的非破坏性。
- 将第一个分区扩展到 1 GB,并验证文件可访问性。
- 将该分区收缩到 256 MB,并观察结果。
以下是具体操作步骤。
步骤一:启动实例并附加磁盘 🚀

我们使用 AWS 命令行工具启动一个 Debian 实例,创建一个新卷,并将其附加到该实例。
实例启动并运行后,我们通过 SSH 以管理员用户身份登录,准备操作新增的磁盘。

步骤二:分区与创建文件系统 🗂️

首先,我们使用 fdisk 工具对新磁盘进行分区。磁盘设备通常为 /dev/nvme1n1。


以下是使用 fdisk 创建分区的关键命令序列:

# 启动 fdisk 对指定磁盘进行操作
sudo fdisk /dev/nvme1n1

# 在 fdisk 交互界面中:
# 输入 ‘n’ 创建新分区
# 选择 ‘p’ 创建主分区
# 分区号使用默认值
# 起始扇区使用默认值
# 设置大小为 +512M
# 再次输入 ‘n’ 创建第二个分区
# 使用所有剩余空间
# 输入 ‘p’ 打印分区表
# 输入 ‘w’ 将更改写入磁盘


操作完成后,使用 lsblk 命令确认分区创建成功。

接下来,在每个分区上创建 ext2 文件系统:
sudo mkfs.ext2 /dev/nvme1n1p1
sudo mkfs.ext2 /dev/nvme1n1p2
步骤三:挂载分区并创建测试文件 📁
我们至少挂载第一个分区,并在其上创建一个文件,以证明后续调整操作不会破坏数据。

# 创建挂载点并挂载分区
sudo mkdir /mnt/test
sudo mount /dev/nvme1n1p1 /mnt/test

# 创建测试文件
sudo touch /mnt/test/our_test_file.txt
至此,我们已准备好调整分区大小。
步骤四:扩展文件系统 ⬆️

要调整文件系统大小,首先必须卸载它。与在 NetBSD 上类似,我们需要先调整分区大小,然后才能扩展文件系统。
以下是扩展文件系统的步骤:
- 卸载文件系统:
sudo umount /mnt/test - 使用
fdisk删除现有分区,并创建一个占用整个磁盘空间的新分区。fdisk会警告磁盘上已有文件系统签名,选择继续。 - 将新分区表写入磁盘。
- 运行
e2fsck检查并修复文件系统。该工具会检测到分区描述与文件系统不匹配(超级块信息不正确),并自动修复。 - 使用
resize2fs将文件系统扩展到分区的全部容量:sudo resize2fs /dev/nvme1n1p1
操作完成后,重新挂载分区并检查:

sudo mount /dev/nvme1n1p1 /mnt/test
lsblk /dev/nvme1n1p1
ls /mnt/test/

此时应显示文件系统大小约为 1 GB,且之前创建的文件仍然存在。

步骤五:收缩文件系统 ⬇️

现在,我们来收缩文件系统。同样,任何文件系统操作都需要先卸载。
以下是收缩文件系统的步骤:


- 卸载文件系统:
sudo umount /mnt/test - 尝试使用
resize2fs收缩文件系统到 256 MB:sudo resize2fs /dev/nvme1n1p1 256M。工具会提示需要先运行e2fsck。 - 运行文件系统检查:
sudo e2fsck -f /dev/nvme1n1p1 - 再次执行收缩命令,此时应成功。
- 然而,
lsblk可能仍显示分区为 1 GB,因为它读取的是分区表信息。我们需要用fdisk更新分区表以匹配新的文件系统大小。 - 使用
fdisk删除分区,然后创建一个大小为 256 MB 的新分区。接着,可以创建第二个分区使用剩余空间。 - 再次运行
e2fsck。 - 挂载缩小后的分区并验证:
sudo mount /dev/nvme1n1p1 /mnt/test。使用df -h查看大小,并确认测试文件仍在。
总结 📝
本节课中我们一起学习了在 Debian Linux 上调整 ext2 文件系统大小的完整流程。
核心操作可归纳为两个公式:
-
扩展文件系统:
- 使用
fdisk调整分区大小。 - 使用
e2fsck检查文件系统。 - 使用
resize2fs扩展文件系统至分区末尾。
注意:此操作会破坏该磁盘上其他分区(如第二个分区)的任何数据。
- 使用
-
收缩文件系统:
- 使用
e2fsck检查文件系统。 - 使用
resize2fs收缩文件系统。 - 使用
fdisk更新分区表以匹配新大小。 - 若想使用新释放的空间,需在其上创建新的文件系统。
- 使用

可以看到,在掌握了基本概念后,在不同类 Unix 系统上执行此类任务并没有本质区别。请务必亲自运行这些示例,或者尝试用不同的方式来完成它。
020:文件系统类型与挂载点详解 🗂️
在本节课中,我们将结束对分区和文件系统的高层次讨论。我们将深入了解Unix文件系统支持的文件类型及其行为,并探讨不同系统上通常挂载了哪些分区和文件系统。
文件类型详解 🔍
上一节我们介绍了如何挂载文件系统并创建文件。本节中,我们来看看Unix文件系统支持的具体文件类型。
以下是一个空目录,它通常包含两个条目:.(当前目录)和..(父目录)。
我们创建一个常规文件。ls命令通过权限字符串的第一个字符为-来标识它。
接下来,我们创建一个目录。ls命令通过权限字符串的第一个字符为d来标识它。
每个文件都由一个inode引用,该inode包含所有关联的元数据,如权限、所有权、时间戳等。目录中的文件名创建了对inode的引用。
虽然我们通常不这么称呼,但每个文件名到inode的映射都称为硬链接。
我们可以为同一个文件设置多个名称。如果文件已存在,我们可以将另一个目录条目链接到它。使用ln命令即可实现。
现在,这两个文件除了名称外无法区分。更准确地说,它们不是两个文件,而是同一个文件的两个名称。

我们可以通过查看文件的inode编号来验证这一点,ls命令通过-i标志显示此信息。目录有一个inode编号,而两个常规文件共享同一个inode。这意味着,就系统而言,它们是同一个文件。
ls命令也在这里告诉我们,这个文件有两个名称。这个数字是链接计数,即文件系统中该文件存在的名称数量。

但还有另一种类型的链接,称为符号链接,使用ln命令的-s标志创建。现在我们可以看到这个符号链接确实是一个独立的文件。它有自己的inode编号,文件类型显示为l。
符号链接是一种特殊类型的文件,它表示:“嘿,别看我,任何操作都去找那个文件。”ls命令的输出也显示了符号链接的目标。

当我们读取文件时,得到的是它所指向的文件的内容。
其他文件类型 📁
我们已经看到了目录、常规文件(本质上是硬链接的另一种说法)和符号链接,但还有其他类型的文件。
其中之一是FIFO文件。FIFO本质上是管道概念在文件系统中的体现,其行为也类似。写入FIFO的任何内容都可以按顺序从中读取。

因此,ls命令使用p字符来表示FIFO,也称为命名管道。
FIFO的行为在初次使用时可能有些令人惊讶。为了说明,我们在后台启动一个从FIFO读取数据的进程。然后,我们可以将符号链接(重定向到常规文件)的内容输入到FIFO中。
此时,后台的cat进程读取数据并将其打印到标准输出。
当我们再次按回车键时,shell告诉我们后台进程已完成。
这也提醒我们,虽然文件名可以包含非ASCII字符,但这并不总是个好主意。
查看所有文件类型 📋
以下是不同文件类型的汇总。
我们创建一些指向当前目录中现有路径名的硬链接。然后使用file命令报告它们的信息。

现在我们看到了一个FIFO、一个字符特殊设备(如TTY)、一个块特殊设备(如硬盘)、一个目录、包含UTF-8 Unicode的常规文件、一个符号链接以及一个套接字。
套接字是文件系统中的进程间通信会合点,为同一主机上的进程间通信提供套接字API访问。
这里,ls -l显示了更多信息,其中权限字符串的第一个字母再次给出了文件类型。
这些是你在正常Unix系统中可能会遇到的不同文件类型。还有其他一些文件类型,但其中一些是特定操作系统的实现细节。
默认挂载的文件系统 💾
现在,让我们看看在不同Unix版本上默认挂载了哪些文件系统。
首先是FreeBSD。我们看到根文件系统是从da0p2(GPT根文件系统)挂载的,这表明使用了GPT分区表。mount命令显示当前挂载的文件系统,在此情况下与df的输出没有区别。
接下来看OmniOS。运行mount命令,我们看到一个相当不同的视图,显示有几个其他东西被挂载。我们有根文件系统(我们知道它在ZFS池上),以及几个特殊用途的伪文件系统。
查看/etc/mtab手册页。这是将运行系统的信息投射到文件系统中的奇怪情况之一,使其看起来像常规文件。查看/etc/mtab文件,我们发现没有意外,挂载的文件系统与之前显示的一致。
系统如何知道要挂载哪些东西?查看/etc/vfstab手册页。这是一个实际的配置文件,描述了启动时挂载文件系统的默认设置。查看该文件,它描述了要挂载的内容,以及为给定特殊挂载点使用什么类型的伪文件系统。
现在,与Linux(特别是Fedora Linux)进行比较。df命令在这里给出了一些信息,显示了tmpfs等特殊文件系统的使用,以及根文件系统。如果我们运行mount命令,会显示很多在df中没有看到的东西。

Fedora现在使用systemd来引导和管理运行系统,因此我们在这里看到一大堆额外的、奇怪的东西被塞了进来。我们看到tmpfs、devpts、cgroup等等,右边都显示了相应的挂载标志。
查看这个系统上的man fstab。/etc/fstab文件包含关于可以挂载哪些文件系统的信息。查看该文件,我们注意到它只包含一个条目,用于类型为ext4的根文件系统,并映射到具有给定UUID的设备。这与我们当前挂载的内容有很大不同。
那么这些信息在哪里跟踪?/etc/mtab指向/proc/self/mounts,使用proc伪文件系统将运行系统的信息反映到文件系统层次结构中。/proc/self/mounts看起来像常规文件,大小为0字节。但当我们获取它时,它显示了所有内容,就像mount命令所做的那样。
因此,我们再次注意到运行df报告空闲空间时可用文件系统与哪些其他伪文件系统处于活动状态之间存在差异。对于许多伪文件系统,报告文件使用情况没有太大意义,因为根据定义,它们只是将系统属性抽象到文件API中。
实际系统布局示例 🖥️
所有这些都有点奇怪。我们详细讨论了磁盘和分区,但我们在这里看到的大多数东西并不是真正的文件系统,也没有磁盘支持。这说明文件系统API和概念非常成功,以至于我们越来越多地将其用于其他目的。

其次,值得注意的是,我们启动的AWS实例的默认布局不一定代表用于特定目的的实际生产系统的布局。
因此,让我们展示一个不同的例子。这里看到的是托管QSE网站的服务器上的df输出。这是一个NFS虚拟私有服务器,但正如你所见,这里有一个更正常的布局。

我们有一个提供操作系统的根磁盘,它被挂载在/下。但我们还有用于不同目的的额外磁盘。我们看到一个磁盘专门用于系统上所有用户的主目录,另一个磁盘挂载用于杂项数据文件。系统也有devpts、proc和tmpfs挂载,但不像Fedora实例那样疯狂。
挂载可视化与文件系统层次结构标准 📊
让我们可视化如何挂载磁盘。尽管如今我们经常有一个包含所有内容的大型分区,但我们仍然可能看到不同的磁盘,而不是不同的分区。


挂载分区的唯一规定是:分区可以挂载在任何地方,但根分区必须挂载在/下。

在磁盘空间仍然昂贵且稀缺的旧时代,通常有一个磁盘包含启动系统所需的最少文件,该分区将是根分区。而另一个磁盘可能包含所有额外的文件,例如出现在/usr下的文件,而用户的私有文件可能驻留在另一个分区上。顺便说一下,这就是为什么我们有一个/usr目录,而不是将所有文件都放在/下的原因之一。
我们马上会看到,这实际上有一些逻辑。正如我们刚才看到的,我们还可以挂载其他东西,但这些伪文件系统没有在图形中表示,因为我们想区分数据存放在哪里。

为此,我们可能还需要考虑跨不同操作系统的某种标准化,并考虑在哪里安装软件。我们将在下周的视频中更详细地讨论这个话题。
当我们想查找一些信息,比如哪些文件放在哪里,或者文件系统层次结构应该是什么样子时,我们该怎么做?我们不会在Google中输入“哪些文件放在哪里”,然后点击第一个Stack Overflow链接,指向某个随机的个人观点,认为内核应该放在/all/my/kernels下。
相反,我们查阅手册页。操作系统提供的man hier描述了文件系统层次结构。例如,它指出:
- 在
/bin下,我们存放单用户和多用户环境都需要的实用程序。 - 系统配置文件放在
/etc下。 - 它告诉我们用户数据放在哪里。
- 它告诉我们库放在哪里。
- 它指出我们不应依赖
/usr的可用性,因为它可能驻留在启动时尚未可用的单独磁盘上。 - 它告诉我们系统实用程序的位置,从而回答了为什么我们同时有
/bin和/usr/bin的问题。 - 它描述了用户层次结构本身,这在某种程度上镜像了根层次结构。
- 它告诉我们各种其他文件的位置,例如日志文件和PID文件。
因此,所有这些背后都有一些逻辑和原因。定义这个层次结构,然后由软件提供商和系统管理员遵守,使你更容易使用和维护系统。
通过仔细分离这些文件,你可以使用具有不同挂载标志和安全设置的不同分区,或者可能更好地预算磁盘空间,或者避免失控进程在一个分区上耗尽磁盘空间或inode,从而干扰整个系统的运行。
总结与思考练习 💡
本节课中,我们一起学习了Unix文件系统的不同类型(常规文件、目录、硬链接、符号链接、FIFO、设备文件、套接字),探讨了不同操作系统(FreeBSD、OmniOS、Linux)的默认挂载布局,并理解了文件系统层次结构标准(FHS)的逻辑和重要性。
以下是供你思考和实验的一些内容:
- 查看不同挂载点:查看不同操作系统中的不同挂载文件系统,确保理解差异并识别每个的用途。
- 理解挂载选项:查看文件系统的挂载方式。许多文件系统允许基于挂载标志有不同的行为。例如,你可以将磁盘挂载为只读,或启用/禁用某些属性以提高性能。确保理解所有不同标志的含义以及为什么可能使用它们。
- 分析配置差异:我们有时看到
/etc/fstab中规定应挂载的内容与实际挂载的内容之间存在差异。尝试解释在什么情况下会出现这种差异,以及这是否是一个问题。 - 复习文件系统实验:回想本主题开始时,我们运行了一些尝试填满磁盘空间和使用inode的实验。重新运行这些练习,看看现在是否更明白了,你是否获得了更好的理解。
- 探索文件系统限制:
- 思考文件系统的具体限制。例如,单个文件可以有多大?什么限制了它的大小?
- 我们可以在文件名中使用UTF-8字符。你还可以使用哪些其他字符?哪些字符不能在文件名中使用?为什么不能?
- 文件名可以有多长?尝试创建一个具有100、200或2000个字符的文件名。如果有任何限制,是什么?为什么?
- 将这种思考扩展到路径名。如果路径名中的每个组件都是一个由斜杠分隔的文件名,并且文件名长度有限制,那么路径名的限制是什么?
- 如果文件名只是一个硬链接,并且你可以为同一个文件有多个硬链接,你可以有100个、2000个吗?如果有任何限制,是什么?
- 尝试跨磁盘或挂载点创建硬链接。你会发现这失败了。但为什么?
- 理解软件安装:我们还没有完全完成文件系统层次结构的讨论。正如我们在查看
hier手册页时看到的,我们有理由将东西放在一个地方或另一个地方。但文件是如何到达那里的?我们将在接下来的视频中通过软件安装概念来直接和间接地介绍这一点。
如果你能回答所有这些问题,那么你将处于良好状态,并且认为你对迄今为止涵盖的主题有了更好的理解。

感谢观看。😊
021:软件类型 🖥️
在本节课中,我们将学习软件的不同类型。我们将从最接近硬件的固件开始,逐步深入到操作系统内核、核心系统软件,最后是用户应用程序。理解这些类别及其模糊的界限,对于后续学习软件安装和管理至关重要。
上一节我们讨论了文件系统层次结构和启动过程。本节中,我们来看看软件有哪些不同的类型。
从最广义的定义来看,软件就是指示计算机执行特定任务的程序。然而,在实践中,软件有多种不同的类型,我们在讨论系统启动时已经见过其中一些。本视频将尝试对这些类型进行分类,尽管你会很快发现,其区别或区分因素远非泾渭分明。
我们已经识别出一个由软件实现的特定组件:文件系统。直觉上,我们将文件系统归类为比某些应用程序(例如Web服务器)更底层的组件。但文件系统只是操作系统的一个组成部分,而操作系统本身又包含常规应用软件(如文本编辑器或编译器)、被许多应用程序使用的库(如用于将主机名转换为IP地址的解析库)、提供对特定硬件组件访问和控制的设备驱动程序,以及操作系统最核心的组件:内核。
从这个角度看软件,很快就能明白“操作系统”这个术语本身也需要定义,因为不同的操作系统提供商会将不同的组件归入这个总称之下。但在我们尝试解决“究竟什么定义了操作系统”这个问题之前,让我们退一步,尝试根据软件与硬件或最终用户的接近程度来更好地对软件进行分类。
固件 🔌
当我们启动系统时,在操作系统运行之前,许多事情发生在更底层的环节。在计算机世界中,类似的元素可能是固件。即,当我们给系统通电时早期执行的软件片段就属于这一类。
但固件的范围甚至更广。例如,运行在你的Wi-Fi接入点上的软件也属于固件。这说明,要恰当地对软件进行分类是相当困难的。我们知道交换机或路由器运行一个完整的操作系统,尽管是专门化的。许多消费产品也是如此,经常使用定制版本的Linux。但由于我们通常无法更新或管理这些软件,我们认为它不那么“软”。因此,我们也将其归为固件。
其他可能运行某种固件的设备包括遥控器(如今可能复杂得令人惊讶),或者你汽车的信息娱乐导航系统。同样,如今你的汽车更可能运行一个完整的操作系统,但作为用户,它对我们来说在很大程度上是不透明的。

或许再底层一些,但我们之前也提到过,包括IDE驱动器或任何使用USB的设备中嵌入的电子元件。正如我们之前提到的,所有这些设备都运行某种软件(固件)这一事实,意味着它们可以被操纵或入侵。

回到我们的服务器系统,系统BIOS当然是一种固件,它嵌入在只读存储器中,但可以通过特殊工具(例如刷新ROM)进行更新。类似地,不同的持久性内存模块(如NVRAM,这里以一台旧的SPARC工作站为例)也充当第一阶段的引导加载程序。我们可以看到,这里有以交互方式改变系统状态的方法。作为引导加载程序,它最终将控制权交给内核。

内核与操作系统 🏗️
回到我们糟糕的汽车类比,什么代表内核呢?或许我们可以把内核想象成发动机。没有它,你的车就毫无用处。但同样,如果你只有一个内核,那你哪儿也去不了。顺便说一下,这就是为什么,尽管可能很烦人,但从技术上讲,坚持说“这个操作系统叫GNU/Linux”实际上是正确的(最好的那种正确)。但我们不必那么学究。
所以,我们的内核是一段软件。如你所知,有不同类型的操作系统内核:微内核和宏内核。后者是我们在本课程中研究的Unix系统中常见的类型。我们已经看到了一种可视化内核功能的方法:通过查看启动消息,我们看到它管理硬件、管理内存和进程调度,并为常规用户和库提供系统调用来进行交互。
但同样,就像一个孤零零的发动机,一个孤零零的内核本身用途有限。我们需要构建一个更有用的环境来构建我们的服务,这就是核心操作系统,即所有不在内核空间运行,但对于系统变得有用、可用所必需的部分。操作系统需要与内核紧密集成。你不能随便拿一个发动机塞进任何底盘就指望它能工作。你需要兼容的部件和合适的连接器。同样,内核也并非独立于系统的其他部分。
这就是事情变得有点棘手的地方。你会知道存在许多Unix版本,但它们都保持着自己连贯的操作系统。但对于Linux,例如,我们有许多将Linux内核与某些工具结合起来的变体,其中许多工具在不同发行版中是相同或相似的,以构建一个操作系统变体。有些发行版使用init启动系统,有些使用systemd;有些附带库Y的X版本,有些附带X+1版本。所以,你需要核心库来挂钩到内核,而这些库实际上并非完全独立于内核。
系统软件与附加应用 📦

除此之外,你还有一些系统组件,它们专门定义了你的操作系统行为以及用户如何与之交互。你获得了额外的功能,但很多功能与如何操作系统、如何与之交互、如何更改设置密切相关。或许这里的等价物是系统软件、操作系统的核心组件,以及影响其行为的那些部分(配置文件等)。同样,所有这些主要都与系统本身的运行相关。
因此,大多数时候,人们需要扩展系统以添加定制功能,购买一些附加组件。在软件世界中,这些性感、毛茸茸的轮毂盖和炫酷座椅的等价物,我们可能会认为是附加组件,比如Web服务器、数据库、带有其库的额外编程语言解释器、语言平台和虚拟机、邮件系统、编辑器、IDE、厨房水槽,或者一个没人知道怎么用的版本控制系统。
现在,你可能在想,嘿,等等,我的操作系统开箱就带有所有这些,它们不是附加包或定制。但这正是我们将在下一个视频中深入讨论的一个困境:决定哪些软件组件是操作系统的一部分,哪些严格来说是附加组件;什么是核心系统软件,什么是可选的、花哨的装饰。
模糊的界限与新兴类别 🔄
除了我们目前确定的类别(固件、内核、操作系统、系统软件和附加应用程序)之外,我们还越来越多地需要处理介于这些类别之间的系统。因为如今,几乎所有你能插上电源的设备都带有一个功能齐全、配置不安全、运行着你无法关闭的Web服务器的操作系统。尽管我们可能不总是考虑这些设备,而专注于服务器环境,但记住在管理嵌入设备和引入你网络的非计算资源时,同样的原则确实适用,这是有用的。
更重要的是,随着我们进入虚拟化、容器和unikernel领域,所有这些软件类型的分离进一步瓦解。一个静态的、不可变的容器是操作系统的一个实例吗?还是它更类似于固件或封闭系统应用程序?在无服务器环境中,管理程序算是一个合适的操作系统吗?在unikernel上容器内运行的各个应用程序又属于哪里?
恐怕我没有答案给你,除了“视情况而定”。而且事情很快就会变得混乱。但是,嘿,欢迎来到系统管理的奇妙世界。正如我所说,在接下来的视频中,当我们讨论软件捆绑和安装,以及系统软件与操作系统的分离时,我们将尝试为其中一些问题带来更多清晰度。
总结与思考 💡
首先,我希望这一点应该非常清楚:软件真的完全不像汽车。汽车的类比几乎总是很糟糕。其次,尽管这听起来很明显,但我们有时会忘记:我们谈论的所有这些东西确实是软件。它是灵活的,可以被更改、配置、入侵。因此,当我们在接下来的视频中讨论软件包管理、软件更新或安全原则时,许多原则或多或少同样适用于固件、基础操作系统和附加软件。
因此,我们倾向于将软件分为三大类:
- 低级固件:通常指我们一般不更改的、非常接近硬件的软件,它可能是专有的或需要特殊工具来交互。
- 操作系统:尽管我们已经尝到,这并不是一个非常清晰的定义,没有明显的界限来区分附加软件和系统软件。
- 附加软件:但我们确实确定,有些软件我们认为不属于操作系统,需要在操作系统安装后添加。
有了这些类别,我们立刻就会遇到这些问题:主要的区别到底是什么?也许在管理软件的方式上存在区别?还有我们提到的所有这些介于中间的东西怎么办?所有糟糕的“破碎物联网”呢?网络设备、存储设备、专用硬件(如HSM)或第三方设备呢?
也许这个视频确实给我们带来了更多问题而不是答案。在我们进入下一个视频讨论软件包管理之前,让你开始从这些角度思考可能是个好主意。
因此,当你回顾时,以及为下一个主题做准备时,你或许应该回顾一下上周看过的不同启动序列,尝试识别你遇到的不同类型的软件以及它们如何相互作用。然后思考尝试更新其中每一个的影响是什么。这是件简单的事吗?你能在不影响另一个的情况下更改其中一个吗?当你这样做时,必须考虑哪些事情?

其中一些问题的答案将在我们的下一个视频中给出。下次见。感谢观看,再见。
022:操作系统安装
在本节课中,我们将学习如何安装操作系统。我们将重点关注如何以可扩展和自动化的方式进行安装,而不是仅仅在单台机器上手动操作。我们将从规划文件系统布局开始,然后逐步执行手动安装过程,最后讨论如何将这些步骤自动化。
文件系统布局规划
上一节我们介绍了不同类型的软件。本节中,我们来看看如何将操作系统安装到磁盘上。但在开始安装之前,我们需要规划好文件应该存放在哪里。
在之前的课程中,我们提到了文件系统层次结构标准(FHS)。它描述了各种文件应放置的目录层次。例如,我们注意到 /bin 和 /usr/bin 目录之间的区别。/usr 下的内容在系统启动时并非必需,因此可以放在启动过程中挂载的不同分区上。
/var 目录用于存放可变数据。但为什么我们要关心将可变数据放在单独的分区呢?要回答这个问题,我们需要了解不同类型数据的定义。
以下是不同类型数据的定义:
- 可变数据:在常规操作期间预期会被修改的数据。例如日志文件和用户家目录中的文件。
- 静态数据:在正常操作过程中预期不会改变的数据。例如操作系统文件、库和应用程序。
- 可共享数据:在多台主机上保持相同的数据。
- 不可共享数据:每台主机独有的数据。
了解哪些数据会变化、哪些不会变化后,你就可以相应地设置独立的分区并挂载文件系统。可变数据分区可以设置为读写模式,并使用异步I/O以提高性能;静态数据分区可以设置为只读模式。这种分离不仅使攻击者更难破坏系统,还能帮助你跨多台主机组织文件系统。
为了进一步组织,你可能希望将数据分为可共享数据和不可共享数据。如果知道哪些数据集在成百上千台主机上是相同的,就可以考虑如何以集中的方式共享、部署或管理它们。而不可共享的部分则需要逐台主机进行管理。
以下是不同类型数据的示例矩阵:
| 数据类型 | 可共享 | 不可共享 |
|---|---|---|
| 静态 | /usr(库和应用程序)、/opt(附加软件) |
/etc(系统配置文件)、引导块 |
| 可变 | /home(用户家目录)、/data/project(项目数据) |
/var/run(运行时文件)、日志文件(特定主机部分) |
当然,这些区分取决于你的具体设置。但希望你能看到,以这种方式规划数据管理是有用的。更重要的是,在开始操作系统安装之前就决定好这种布局,以便创建正确的分区并适当地安装文件,这将非常有益。
手动安装操作系统
现在,假设我们已经规划好了文件系统布局和分区方案,接下来我们开始安装操作系统。
你可能熟悉类似下图的图形化安装界面,它提供几个按钮让你点击来安装操作系统。


这对于单次安装很有用,但它不利于自动化和扩展。在工业环境中,我们使用部署引擎,通常是自定义构建的,允许基于系统配置文件完全自动化地安装操作系统。
为了能够构建这样的系统,我们需要理解操作系统安装过程中实际发生的步骤。因此,让我们执行一次手动安装,但不是使用图形界面,而是使用图形安装程序实际会调用的命令。
我们启动一个 NetBSD 安装 CD 的虚拟机,它让我们进入一个基于菜单的安装程序。这仍然太交互式,无法显示命令。所以我们决定退出菜单。

这让我们进入从 CD 启动的 RAM 磁盘上的 root shell。现在,我们可以运行安装操作系统所需的所有命令。
以下是安装步骤:
-
识别硬盘:首先,识别可用于安装操作系统的硬盘。例如,我们使用
wd0。dmesg | grep wd0 -
设置分区表:计算从偏移量 63 开始后剩余的扇区总数,然后使用
fdisk命令在偏移量 63 处创建一个类型为 NetBSD 的分区。fdisk -i wd0 -
安装引导代码:将实际的引导代码复制到引导扇区,并将分区标记为活动。
fdisk -b /usr/mdec/mbr wd0 fdisk -a wd0 -
创建 BSD 磁盘标签:使用
disklabel编辑器创建磁盘标签。我们创建一个大的分区供操作系统使用。disklabel -e wd0 -
创建文件系统:在分区上创建文件系统。
newfs /dev/rwd0a -
挂载目标文件系统:将新创建的文件系统挂载到
/mnt。mount -o async /dev/wd0a /mnt -
提取操作系统数据:从安装介质(如 CD)提取操作系统归档文件到挂载点。我们可以选择要安装的组件集。
tar -xzf /path/to/base.tgz -C /mnt tar -xzf /path/to/etc.tgz -C /mnt # ... 提取其他组件 -
安装引导加载程序:将引导加载程序复制到正确位置。
cp /usr/mdec/boot /mnt/boot installboot -v /dev/rwd0a /usr/mdec/bootxx_ffsv1

-
创建设备节点:在目标系统的
/dev目录下创建设备节点。cd /mnt/dev && ./MAKEDEV all -
基本系统配置:通过
chroot进入新安装的系统,进行一些基本配置,如设置主机名、启用 DHCP 和 NTP、配置/etc/fstab等。chroot /mnt /bin/ksh echo "hostname=myserver" >> /etc/rc.conf echo "dhcpcd=YES" >> /etc/rc.conf echo "ntpd=YES" >> /etc/rc.conf echo "/dev/wd0a / ffs rw 1 1" > /etc/fstab exit -
完成安装:卸载磁盘并重启系统。
umount /mnt reboot
系统重启后,将加载我们安装的引导加载程序,并启动新安装的操作系统。
通用安装步骤与自动化
我们为什么要不厌其烦地自己运行所有这些命令,而不是使用操作系统提供的完美安装程序呢?正如前面提到的,那些安装程序不利于自动化。更重要的是,在这门课程中,我们旨在真正理解系统是如何工作的。
根据我们刚才的观察,让我们概括一下安装系统所需的通用步骤。
以下是操作系统安装的通用步骤:
- 系统启动:系统加电启动。
- 从备用介质引导:由于目标磁盘上没有操作系统,需要从备用介质(如 CD、USB 或通过网络 PXE 启动)引导到一个临时环境(如 RAM 磁盘)。
- 识别目标磁盘:在临时环境中,识别要安装操作系统的硬盘。
- 分区和磁盘布局:在目标磁盘上创建分区表(如 MBR/GPT)和分区。
- 创建文件系统:在目标分区上创建文件系统(如 ext4, UFS, ZFS)。
- 安装引导加载程序:将引导代码(如 GRUB, boot0)写入磁盘的引导扇区,并配置引导加载程序。
- 获取操作系统数据:从本地介质(如 CD)或网络源(如 HTTP 服务器)获取操作系统软件包或镜像。
- 部署数据到磁盘:将操作系统文件提取或复制到目标文件系统。
- 安装额外软件:安装任何超出基础操作系统范围的额外所需软件。
- 执行初始配置:进行最基本的系统配置(主机名、网络、
fstab等)。 - 重启系统:卸载磁盘,重启计算机,从新安装的磁盘引导。
这些步骤对于大多数类 Unix 系统来说大同小异,主要区别在于执行它们所使用的具体命令。理解了这些步骤,你就可以自动化整个过程,构建一个部署引擎。
然而,你可能也注意到,这个过程的基础部分并不复杂。更困难的部分发生在其他地方。为了构建一个能在多台系统上无人值守自动化安装操作系统的部署系统,你需要:
- 硬件清单:某种硬件清单系统。
- 配置映射:一种确定哪类镜像应安装到哪台硬件上的方法。
- 软件定义:定义需要安装哪些超出基础操作系统的软件。这可以通过为不同工作负载或镜像定义配置文件或标识来完成。
- 初始配置与注册:执行至少一些初始系统配置,并在清单中注册新安装的系统。
- 基础设施:许多这些步骤需要周围的基础设施和组织支持。
此外,启动新系统和配置运行系统之间的界限并不总是清晰的。这与配置管理以及服务编排等更大的主题有重叠。
最后,我们一直在讨论将操作系统安装到物理或虚拟裸机上。但如今,构建单独的容器或操作系统镜像可能更常见,也更具可扩展性。例如,AWS 机器镜像就是操作系统被安装到一个可被实例化的机器镜像中的例子。创建容器和实例化机器镜像虽有不同,但我们之前讨论的前期规划(决定哪些数据可共享、可变、静态等)以及概述的通用步骤,同样有助于我们更好地理解那个过程。
总结与练习
本节课中我们一起学习了操作系统安装的核心概念和步骤。
首先,尽管第一次操作时可能令人生畏,但安装操作系统所涉及的步骤并不非常复杂。更重要的是,这些步骤在不同系统间是相同或相似的,这就是为什么手动安装系统的练习很有用。
其次,我们看到了规划文件系统布局可以帮助我们简化安装过程,提高其可扩展性。因此,大部分工作不在于执行相同的命令集,而在于识别需要考虑的周边属性、因素和系统方面。所有这些都与系统管理的其他工作领域(如配置管理)有所重叠。
为了加深理解和巩固所学知识,你可以尝试以下练习:

- 重复安装过程:为几种不同的操作系统(如 Linux 发行版、BSD 变体)重复上述过程。下载安装镜像并将其安装到虚拟机中。
- 分析安装程序:跟随它们提供的图形或文本安装程序,注意它们执行了哪些步骤。然后看看你是否能通过手动运行命令来重复这个过程。
- 思考自动化:思考如何自动化安装这些不同操作系统的过程。研究现有的自动化安装工具(如 Kickstart, Preseed, AutoYaST, FAI),了解它们的工作原理。
- 对比镜像创建:思考这个过程与创建机器镜像(而非安装到裸机)有何不同。在这种环境中,你将如何处理主机特定的部分?云平台需要提供哪些基础设施服务来使之成为可能?
正如你所见,仅在这个主题上就可以花费大量时间。系统管理员们发现,供应商或开源项目提供的操作系统与你最终系统上的内容之间存在区别。你将需要添加更多软件,有些是开源的,有些是专有的,有些是内部开发的。我们如何尝试管理所有这些不同的组件,将是我们下一节讨论软件包管理时的主题。
本节课中我们一起学习了:操作系统安装不仅涉及将软件部署到磁盘,更重要的是前期的文件系统布局规划和分区策略。我们通过手动安装 NetBSD 的实例,剖析了从识别磁盘、分区、创建文件系统、部署文件、安装引导程序到基本配置的完整步骤。理解这些通用步骤是构建自动化、可扩展部署系统的基础。真正的挑战在于围绕这些步骤构建必要的基础设施,如硬件清单、配置映射和与配置管理的集成。
023:软件包管理 📦
在本节课中,我们将要学习软件包管理的核心概念。我们将探讨操作系统核心组件与附加软件之间的模糊界限,并深入了解软件包管理器如何通过管理依赖关系、提供系统清单和增强安全性来简化系统管理。
操作系统与附加软件的界限 🔍
上一节我们完成了操作系统的安装,但要让系统真正可用,通常还需要安装额外的软件。这引出了一个根本问题:哪些软件属于操作系统核心,哪些属于第三方附加软件?
为了理清这个概念,我们来看一些软件示例,并尝试将它们归类。以下是几个例子,请思考它们应属于“系统软件”还是“附加软件”:
- 内核模块:扩展内核功能的驱动程序。通常由操作系统提供,但对于某些专用硬件,可能需要由硬件制造商提供,此时应视为第三方附加软件。
- 固件:如BIOS或磁盘控制器上的微码。操作系统可能提供管理工具,但固件本身通常被视为附加组件。
- C库(如glibc):这是与内核及其他应用程序紧密集成的核心库。更新它可能破坏整个系统,因此它无疑是操作系统的一部分。虽然可以安装其他实现,但这很罕见。
- Shell:几乎所有Unix系统都自带一个Shell(如Bash),但用户也可以安装其他Shell(如Zsh)。因此,它可能同时属于两个类别。
- SSH客户端/服务器:情况与Shell类似。大多数系统自带OpenSSH,但也可以安装其他实现。
- 邮件服务器:由于历史原因(Unix是多用户服务器系统),许多系统默认包含邮件服务器。当然,也可以安装其他实现。
- HTTP服务器:传统上不属于操作系统,但因其普遍性,越来越多系统将其纳入基础安装包。
- 数据库:通常不作为操作系统核心部分,主要是独立的附加应用程序。
- Python解释器:这很有趣。它显然是附加软件,可以安装多个版本。但许多操作系统也默认包含它,因为一些系统工具可能依赖Python。
从以上例子可以看出,区分核心与附加软件非常困难,界限取决于操作系统发行版的定义。我们关心这个区分的原因之一,是为了理解组件间的依赖关系。一个将特定核心组件作为一个连贯单元发布的系统,比一堆独立部署的软件包更易于管理和升级。
为了解决这些依赖关系,我们通常使用所谓的软件包管理器。
软件包管理器的职责范围 📊
那么,软件包管理器应该管理哪些组件呢?在系统管理领域,常见的答案是:视情况而定。
这取决于具体的操作系统及其发行版。有些操作系统仅对附加软件使用包管理器,核心系统则不用。而另一些系统则使用同一个包管理器来管理所有组件,包括内核模块和内核本身。
我们可以从另一个视角来看待软件管理。从底层到顶层依次是:
- 硬件
- 固件
- 内核
- 系统软件(如设备驱动、核心库)
- 实用工具和应用程序(如Shell、常用Unix命令)
- 附加软件(如浏览器、Web服务器、数据库、编程语言环境)
在某个(有些随意的)时间点,操作系统发行商会定义哪些组件属于操作系统,哪些不属于。但即使对于不属于操作系统的组件,我们仍然希望用统一的方式来管理它们及其依赖。因此,软件包管理实际上涵盖了所有这些层次。

这意味着,即使操作系统使用包管理器,它也可能用其来管理附加软件。但很快你会发现,总有一些软件无法通过包管理器获得,这时你必须小心管理。此时,区分操作系统软件和附加软件就变得更有意义了。例如,升级操作系统是否会导致与附加软件不兼容?附加软件可能将配置文件放在你认为是静态的位置,或者与系统组件冲突。
软件包管理器的核心功能 ⚙️
既然软件包管理器是系统管理员工具箱中的关键工具,让我们通过实例来看看它提供的一些核心功能。我们将以Debian(使用dpkg)、Fedora(使用rpm)和NetBSD(使用pkgsrc)为例。
1. 软件清单与文件查询

软件包管理器最基本的功能是提供系统上所有已安装软件的清单。
在Debian系统上,你可以运行:
dpkg -l
这将列出所有已安装的软件包及其版本,例如总共1319个包。拥有一个完整的软件清单非常重要。

接下来,对于任何给定的软件包,你可以列出其包含的所有文件。例如,查看tcpdump包的内容:
dpkg -L tcpdump
反之,你也可以查询某个文件属于哪个软件包:
dpkg -S /path/to/file
这提供了正向(包->文件)和反向(文件->包)的便捷查询功能。

请注意:这些功能仅对通过包管理器安装的软件有效。为了保持一致性,你应该确保将所有附加软件都打包。假设你想升级Python,但AWS工具依赖旧版本且会因此损坏。如果AWS工具是在包管理器之外安装的,包管理器就无法知晓这个依赖,从而允许你升级并破坏系统。这非常糟糕。
2. 文件完整性校验与入侵检测

软件包管理器的另一个强大功能是文件完整性校验,这构成了一个隐式的入侵检测系统。
在Fedora系统(使用rpm)上,同样可以列出所有已安装的包(例如344个)。假设我们模拟一次系统入侵,攻击者修改了/etc/pam.d/sudo文件以改变sudo的认证方式。我们更改了文件内容、所有者和组。




如何发现文件被篡改?包管理器在安装时记录了每个文件的所有权、权限、大小甚至内容校验和。因此,我们可以通过包管理器来验证包的完整性。


运行以下命令:
rpm -V sudo
输出会显示文件大小、MD5校验和、所有者、最后修改时间与安装时的记录不符。这非常酷!通过包管理器,我们获得了一个隐式的入侵检测工具。
当然,正常运行时,一些文件(如配置文件、日志文件)的变化是预期的。因此,要有效监控系统,你需要理解上下文并知道什么是正常状态。我们将在后续关于系统监控的课程中再讨论这一点。
3. 安全漏洞审计与自动更新
软件包管理器还能帮助我们自动分析系统上哪些软件包存在已知的安全漏洞,从而指导打补丁。
在NetBSD系统(使用pkgsrc)上,我们先列出已安装的包(例如177个)。为了进行漏洞审计,我们需要获取已知漏洞列表,这通常由发行版的安全团队提供。
通过包管理工具可以获取这个列表。但请注意,这个列表文件通常使用PGP进行加密签名,以确保数据的真实性和未被篡改。你需要导入并信任相应的GPG密钥才能验证签名。
验证签名后,运行包审计命令。结果可能会显示许多漏洞,例如bash存在权限提升漏洞。这正是系统管理的常态——软件总可能存在安全漏洞。
接着,我们可以使用包管理工具来拉取并安装更新版本。工具会自动计算依赖关系,下载新包,移除旧版本并安装新版本。完成更新后,再次运行审计,bash的漏洞就不再显示了。
本节总结 📝

本节课我们一起学习了软件包管理的核心内容。

我们了解到,区分什么是操作系统核心、什么是附加软件并非易事。有些依赖关系(如内核与C库)耦合紧密,更新它们需要协调和兼容性;而其他软件则有多种选择。将哪些软件组合在一起作为操作系统,很大程度上取决于发行版提供商。
无论如何,我们阐明了所有这些软件都可以且应该以某种连贯的方式进行管理。一个好的软件包管理器提供了一系列优秀功能,包括:
- 轻松安装软件并自动解析依赖。
- 提供完整的软件包和文件清单。
- 支持文件和软件包完整性校验,用于入侵检测。
- 提供全面的安全漏洞审计机制。
只要软件被一致地打包,这些功能既适用于操作系统软件,也适用于附加软件。因此,你可能希望确保你的包管理器与操作系统深度集成,使其本身也成为操作系统栈的一部分。

关于软件包管理的话题我们尚未结束。在下节课中,我们将继续深入探讨语言特定的软件包管理,以及该领域中一系列与安全相关的概念。
课后练习 💡
在进入下一节的讨论之前,请思考以下练习以巩固所学知识:

- 练习打包:找一个你喜欢的工具,检查它是否已为你喜欢的Unix发行版打好包。如果没有,尝试为其打包。上游项目很可能会乐于接受你的贡献。
- 比较不同包管理器:类似本节课在不同平台上的示例,找出执行包管理核心任务(安装、查询、更新、删除、验证)的基本命令。整理一个清晰的速查表,这对你未来切换不同Unix系统非常有价值。
- 思考固件管理:研究你首选的操作系统是如何处理固件更新的。
- 研究可重现构建:思考这个概念与我们讨论的内容有何关联。即,在不同的系统上运行相同的包管理器命令,是否总能得到完全相同的结果?可能会产生哪些差异?
- 思考包管理与配置管理的关系:你能否使用包管理器来断言系统达到某个特定配置的期望状态?我们将在学期末回到这个讨论,但现在就开始思考是一个很好的练习。
最后,这里有一个更详细的练习建议:比较手动安装软件和通过包管理器安装软件的过程。这个练习能让你对本章及下一章讨论的内容有更深入的见解。我知道这看起来工作量很大,但正如我一直强调的,你能从这门课中获得多少,取决于你自己。如果你对系统管理感兴趣,这些练习将真正深化你的理解,并在实践中对你有所帮助。
024:第04周,第4节 - 包管理器陷阱 🚧
在本节课中,我们将探讨包管理器在实际使用中可能遇到的各种问题和陷阱。上一节我们高度赞扬了包管理器的各种实用功能,本节中我们来看看其背后的一些常见挑战。

概述
包管理器极大地简化了软件安装和管理,但它们并非完美无缺。本节将讨论依赖关系混乱、软件完整性验证以及信任链等核心问题,这些问题在混合使用不同来源的软件包时尤为突出。
语言特定的包管理器
操作系统自带的包管理器(如RPM、APT)无法涵盖所有软件,尤其是编程语言生态中快速迭代的库和模块。因此,各种编程语言发展出了自己的包管理系统。

以下是几种常见语言的包管理器示例:
- Python: 使用
pip或easy_install。 - Node.js: 使用
npm。 - Perl: 使用
CPAN。 - Ruby: 使用
gems。 - Go: 通常直接从
GitHub获取代码。 - Rust: 使用
cargo管理包并上传到公共的crates.io注册表。

这些语言特定的包管理器与操作系统包管理器是独立并行的,这为后续的依赖管理问题埋下了伏笔。
依赖关系与冲突

当操作系统包管理器和语言包管理器同时管理同一软件的不同版本时,就会产生依赖冲突。
考虑一个场景:系统通过RPM安装了 urllib3 1.10.0,同时通过 pip 安装了 urllib3 1.15.0。此时,一个Python工具运行时应该使用哪个版本?如果1.9版本存在安全漏洞并在1.15版本中修复,该系统是否受影响?如何准确清点主机上安装的所有版本?

更复杂的是,我们甚至可能发现系统中运行的Python解释器本身并非由RPM包提供,例如:
$ which python3
/usr/local/bin/python3 # 此文件未被任何RPM包声明提供
每个包管理器都假设自己全权管理所有软件,无法感知通过其他方式安装的同类软件,因此依赖关系无法被跨管理器地表达、追踪和保证。
完整性与信任
如何确保我们安装的软件不包含后门?传统源码安装方式(./configure && make && make install)允许审查代码,但实际中几乎无人这么做,且不具备可扩展性。

通过包管理器安装时,情况类似。我们下载文件并执行脚本,同样面临信任问题。使用HTTPS而非HTTP可以提供传输过程中的真实性保证,但这只是第一步。


签名验证
一些包管理器支持签名包。例如,RPM包可以包含使用PGP密钥生成的数字签名。安装时,rpm命令会自动验证签名,确保软件包来自可信的发布者(如Fedora EPEL仓库),且未被中间人篡改。
验证过程可以表示为:
- 仓库使用私钥对软件包元数据生成签名。
- 用户系统持有对应的仓库公钥。
- 安装时,使用公钥验证签名,确认软件包完整性。
这提供了强有力的保证,但信任链的建立本身是复杂的。
信任链的挑战

依赖上游仓库意味着你将信任置于他人之手。left-pad事件是一个典型案例:一个仅12行代码的流行Node.js模块被作者从公共仓库中移除,导致无数直接或间接依赖它的项目构建失败。这生动地说明了你的依赖就是你的依赖,它们消失,你就崩溃。

依赖混淆攻击
近期出现了一种名为“依赖混淆”的攻击方式,利用了包管理器的默认行为:
- 侦察:攻击者搜索目标公司内部使用的、未公开发布的私有包名称(例如通过公开的GitHub代码)。
- 投毒:在公共仓库(如npm)上发布同名恶意包,并赋予一个极高的版本号(如99.0.0)。
- 等待:当目标公司的构建系统(默认配置下会同时查询内部和公共仓库)执行安装命令时,包管理器会选择版本号更高的包,即攻击者发布的恶意包。
这种攻击之所以可能,是因为默认情况下,许多包管理器会优先安装版本号最高的包,并且会查询公共源。防御方法包括严格配置只从内部仓库拉取包,但这需要细致的维护。
总结与回顾
本节课我们一起学习了包管理器在实际使用中的主要陷阱:
- 依赖的脆弱性:你依赖于你的依赖。如果它们消失、引入破坏性变更或你无法控制它们,你的系统就会崩溃。
- 镜像的局限性:内部镜像上游仓库可以解决可用性问题,但无法解决上游仓库被投毒或发布恶意更新的问题。镜像只是同步变化,并非隔离。
- 签名的部分解决方案:使用签名包(如RPM)可以验证软件包来自可信源且未被篡改,但这建立在与供应商已建立信任关系的基础上。
- 信任链的传递:所有信任最终都取决于最薄弱的一环。你软件构建的完整性受制于你依赖链中最不可靠的那个环节。
关于信任、完整性和威胁应对的更抽象讨论,我们将在后续课程中继续。现在,建议你思考本节提及的事件,研究类似问题,并审视你所用包管理器的配置:它们从何处拉取包?使用何种传输机制?如何断言完整性和真实性?你如何信任它们?
同时,请思考我们之前提出的核心问题:在使用操作系统原生包管理器的环境中,如何安装和管理语言特定的模块?如何记录和解决由此产生的跨管理器依赖? 正如我们所展示的,这远非一个已解决的问题,但值得你花时间深入探索。
关于包管理器的讨论暂时告一段落。在接下来的视频中,我们将转向多用户基础,探讨系统支持多用户意味着什么,以及这如何影响系统管理员的设计决策,并初步讨论身份验证基础。

下次见。
025:分层模型
概述
在本节课中,我们将开始一个关于网络的长篇讨论。我们将深入探讨Unix系统上的TCP/IP网络细节,并在这个过程中,上下遍历四层TCP/IP栈和七层OSI模型。我们将了解Unix系统如何与其他系统通信,学习互联网的物理结构及其治理的一些方面。
OSI模型与TCP/IP模型
上一节我们介绍了课程概述,本节中我们来看看网络通信中最常用的分层模型。
我相信你们之前都见过这个模型,它通常用来表示通信网络中不同的功能部分。
以下是OSI七层模型:
- 物理层:涉及物理介质、比特率、全双工/半双工等。
- 数据链路层:使用物理介质进行节点到节点的数据传输。
- 网络层:在不同网络之间移动数据包。
- 传输层:TCP的大部分功能位于此层,但TCP也包含OSI模型中属于会话层的功能。
- 会话层:关于会话和表示层具体包含什么内容存在很多混淆。
- 表示层:OSI模型因此受到很多批评。
- 应用层:日常讨论中的大多数区别要么属于第3层及以下,要么属于第7层。
然而,还有两个额外的层很少有人提及:
- 第8层 - 财务层:意味着最终必须有人为你在这里做的所有事情付费,这将影响你的开发方式。
- 第9层 - 政治层:组织的结构影响资金的分配,并为开发、部署和支持的内容设定方向。你几乎总是在这一层上操作。
我们将在未来的视频中讨论政治的影响,但现在让我们暂时放下这个理论布局模型,看看一些实际的网络流量,以更好地理解这个模型想要说明什么。
使用tcpdump捕获和分析数据包
上一节我们介绍了理论模型,本节中我们来看看如何通过实际数据包来理解这些模型。

让我们开始捕获一些网络数据包。我们使用tcpdump工具来实现,在后台启动并运行它,捕获我们在这个例子中感兴趣的80端口流量。
# 在后台启动tcpdump,捕获端口80的流量并保存到文件
sudo tcpdump -i any port 80 -w /tmp/capture.pcap &
在tcpdump运行于后台的情况下,我们向CS Stevens网站发起一个简单的HTTP HEAD请求。

# 发起HTTP HEAD请求
curl -I http://www.cs.stevens.edu
我们并不关心命令的输出,只关心收集到的数据包。然后,我们将tcpdump带回前台并中断它,更改输出文件的所有权以便以普通用户身份操作,最后查看捕获文件中的第一个数据包。
# 将tcpdump带回前台并中断(Ctrl+C)
fg
sudo chown $USER /tmp/capture.pcap
tcpdump -r /tmp/capture.pcap -c 1 -X
我们在这里看到了大量信息。能够理解原始的tcpdump输出是系统管理员的一项关键技能,因为我们经常需要排查网络连接和协议问题。
TCP/IP模型与封装

那么,我们在这里看到的是什么?它如何与我们之前展示的OSI模型联系起来?
首先,让我们回到更简单的TCP/IP栈模型。别担心,我们马上会回到tcpdump的输出。

TCP/IP模型也使用层,但与OSI模型不同,它只使用四层。如下图所示,这些层实际上更像洋葱的层,每一层都包裹着另一层。

我们谈论的是封装,这里很好地展示了这一点:链路层提供头部和尾部,封装了网络层(在本例中使用IP协议),IP协议添加其头部并封装TCP数据包,TCP数据包最终包含应用层数据。
所以,让我们将这些层映射到我们在tcpdump输出中看到的内容。让我们查看细节和十六进制输出。
- 前24个字节包含链路层信息。
- 接下来的2个字节告诉我们封装了什么类型的协议(本例中是IP)。
- 我们在接下来的24个字节中找到IP信息。
- 剩余部分是TCP负载。
这很好地说明了封装模型如何转化为数据包捕获中的实际字节。
解析数据包:链路层与网络层
上一节我们看到了封装的概念,本节中我们来仔细看看这些字节里到底有什么。
前12个字节是链路层目的地址,第二个12字节是源地址。
它们具体是什么?我们的链路层数据链路协议是MAC(媒体访问控制)。我们的网络接口(本例中是eth0)有一个MAC地址,如下所示。由于MAC地址已经是十六进制格式,我们甚至不需要转换任何东西,可以直接在数据包中看到它:e0:76:63:72:39:00。这就是我们的源地址,我们通过它发送数据。
目的地址呢?我们可以查看ARP表,它保存了在其所在的第2层网络上看到的MAC地址到IP地址的映射。
# 查看ARP表
arp -a
我们看到目的MAC地址00:1b:21:73:59:5a映射到IP地址166.84.7.192,这被证实是我们的默认网关。这很合理:无论我们实际想将IP数据包发送到哪里,如果它不在我们的第2层网络段上,我们必须将其交给默认网关,以便它能被正确路由。这就是为什么我们的链路层帧的目的地址是我们的默认网关。然后,我们的默认网关将接收这个数据包,并解开链路层信息以查看内部的IP数据包。
IP数据包看起来像什么?它看起来像RFC 791中定义的IPv4数据包结构。这使我们能够查看在tcpdump数据包中观察到的所有不同字节并理解它们。
- 第一个字节:编码IP版本(本例中为4)以及IP头的总长度(5个32位块,共20字节)。
tcpdump输出中的5也告诉我们,在这种情况下,没有IPv4选项或填充,这使我们能够确定负载(本例中的TCP数据)的开始位置。 - 下一个字节:编码区分服务代码点(DSCP)位(通常用于服务质量保证)以及显式拥塞通知(ECN)字段。在我们的例子中,全是0。
- 接下来两个字节:指定数据包的总长度,包括头部和数据。对我们来说,总共是60字节。这已经告诉我们,减去20字节的IP头后,TCP负载的大小是40字节。
- 然后是两个字节的标识:通常用于标识IP分片以便在传输中需要分片时进行重组。对我们来说,这里全是0。
- 接下来两个字节:再次编码两条信息。标志位(这里设置了“不分片”位)以及剩余的13位,用于在数据包被分片时标识偏移量。由于没有分片,偏移量为0,我们得到这两个字节的十六进制值为
40 00。 - 接下来是1字节的TTL:告诉我们数据包最多可以经过多少跳。我们这里指定了默认值64,它在这里变成了十六进制的
40。 - 之后是1字节:指定封装负载的协议。对于TCP,我们在这里指定
6。 - 然后是两个字节的IP头部校验和:这是一个非常简单的反码校验和,提供最低限度的损坏保护。对我们来说,这里是十六进制的
b9 03。 - 剩余的64位:指定源和目的IPv4地址,我们知道每个地址是32位(4字节)。这里的源地址是十六进制的
a6 54 07 c3,目的地址是十六进制的9b f6 38 0b。
由于这个数据包中没有IP选项,tcpdump输出的剩余字节就是TCP负载。
解析IP地址
但是,IP地址是什么呢?我们知道它们的十六进制值。让我们看看为什么选择这些值。
我们知道这里的四个字节是源地址,这四个字节是目的地址。让我们将源地址的十六进制数转换为十进制。我们可以使用bc工具,将输入基数设置为16,然后简单地输入十六进制数,它会输出十进制值。
# 使用bc将十六进制转换为十进制
echo "ibase=16; A6" | bc # 输出 166
echo "ibase=16; 54" | bc # 输出 84
# ... 以此类推

让我们再次查看我们的网络接口,看看它有什么IP地址。
# 查看网络接口配置(示例命令,实际接口名可能不同)
ip addr show eth0
我们的IP地址确实是166.84.7.199。所以这是我们的源地址。

目的地址呢?由于我经常需要转换这样的地址,我创建了一个shell函数来更轻松地为我进行从十六进制到十进制的转换。如果你感兴趣,幻灯片末尾有一个链接指向一个包含几个此类函数的shell文件。

无论如何,我们的目的地址是十六进制的9b f6 38 0b,即155.246.56.11。这并不奇怪,因为我们尝试向www.cs.stevens.edu发起HTTP请求,它解析为155.246.56.11,即我们在tcpdump输出中观察到的目的地址。
使用Wireshark工具
我们已经成功并完整地剖析了从捕获的单个数据包中提取的链路层和网络层信息。恭喜!
正如你可能知道的,还有其他工具可以检查捕获的数据包。其中之一是流行的Wireshark应用程序。如果我们将包含单个数据包的pcap文件加载到Wireshark中,那么我们可以观察到与我们从原始十六进制转储中提取的所有相同数据。

像之前一样,我们在这里找到链路层源和目的地址,在这里找到IP信息,按版本、长度、DSCP和ECN位、长度、标识、标志、偏移量、TTL、协议、校验和以及最终的源和目的地址进行分解,以及剩余的TCP负载。
正如你所见,Wireshark确实是一个有用的工具,因为它可以轻松地为我们识别所有这些位,但我确实认为系统管理员能够使用tcpdump梳理出所需信息很重要,这就是为什么我在这段视频中逐一展示分解过程。
总结与下节预告
让我们在这里暂停一下,快速回顾一下。
- 我们按要求提到了OSI模型,但承认更简单的四层TCP/IP模型可能更有用一些。
- 我们观察到IPv4 RFC没有欺骗我们,它规定的信息确实就在它所说的位置。这是一个非常有用的经验:如果你能遵循规范,你就能理解任何应用程序或协议,而互联网RFC通常写得非常好且易于理解。
- 我也希望我展示了Wireshark虽然有用,但并非某种不可知的魔法,而只是对协议理解的应用。我认为你现在可以看到,基于你在这里学到的知识,你可以在概念上构建一个像Wireshark这样的工具。
但这只让我们到达了IP层,我们在网络覆盖方面还有很多主题要讨论。所以,让我们看看接下来会发生什么。
在下一个视频中,我们将讨论IPv4子网划分,然后继续讨论IPv6,看看它有什么不同。之后,我们将在这个栈上短暂绕道到第9层——政治层,并讨论互联网是如何在非常高的层面上进行管理和控制的,重点是IP空间分配和管理。但我们也会谈谈互联网的物理结构。

我知道,我知道,它只是一系列管道,或者真的是吗?我们将会发现。下次再见,感谢观看。
026:IPv4 基础与 CIDR 子网划分
概述
在本节课中,我们将学习 IPv4 地址的基本结构,了解如何通过地址判断设备所在的网络位置。我们将回顾互联网早期的网络分类概念,并重点学习目前普遍使用的无类别域间路由(CIDR)子网划分方法。
上一节我们详细分析了 IP 数据包的构成,本节中我们来看看 IPv4 地址本身。
IPv4 地址的表示
在上一节的网络数据包图示中,我们注意到 IP 协议头包含两个 IPv4 地址:源地址和目的地址。在我们的例子中,目的地址是 155.246.56.11,即史蒂文斯理工学院 Web 服务器的地址。当然,在 IP 协议头中,这个地址是以二进制数字表示的。
这引出了第一个基本观察:IPv4 地址是 32 位的数字。这意味着整个 IPv4 地址空间有 2^32 种可能性。关于地址耗尽和 IPv6 的问题,我们将在下一节视频中讨论。
一个 32 位的二进制数字不便于记忆和使用,因此我们通常将这 32 位分成四个 8 位的组,称为“八位组”。每个八位组用一个十进制数值表示,并用点号分隔,这就形成了常见的点分十进制表示法,例如我们例子中的 155.246.56.11。当然,我们也可以使用十六进制来表示这些八位组,在 TCPDump 的十六进制输出中,目的地址就显示为 9B F6 38 0B。
网络部分与主机部分
但是,这个被分成四段的 32 位数字,如何帮助我们理解地址在网络中的位置,或者它能与哪些其他系统通信呢?
要理解这一点,我们需要知道每个 IP 地址都被划分为网络部分和主机部分。例如,我们可以观察到前 16 位代表地址的网络部分,后 16 位代表主机部分。
这有什么意义呢?拥有相同网络部分的主机可以在没有路由器帮助的情况下相互通信。我们称这为处于同一个广播域。这是网络层(第 2 层)和传输层(第 3 层)区别的一个例子。发往同一第 2 层网络内主机的数据包可以由网络交换机直接交付。如果目标主机在不同的网络上,则需要一个具备第 3 层功能的设备,例如路由器。

早期的网络分类
我们如何知道一个 IP 地址的网络部分是哪些位呢?在互联网早期,你只需要查看地址的前三位,它就能告诉你这个地址属于哪一类网络。
当时主要有三类网络:A 类、B 类和 C 类网络。实际上,通过检查前四位,可以识别出五类不同的网络。
以下是各类网络的判断方法:
- A 类网络:如果 32 位 IP 地址以
0开头,那么这就是一个 A 类网络。其前 8 位(第一个八位组)是网络部分,剩下的 24 位是主机部分。这意味着在同一个广播域内,可以寻址 2^24(超过 1600 万)台主机。 - B 类网络:如果前两位是
10,那么这就是一个 B 类网络。其前 16 位(前两个八位组)是网络部分,后 16 位是主机部分。 - C 类网络:如果前三位是
110,那么这就是一个 C 类网络。其前 24 位(前三个八位组)是网络部分,后 8 位是主机部分。 - D 类网络:保留用于多播地址。
- E 类网络:早期保留,以备将来使用。因为 IPv4 最初被设想为一个实验,所以预留了一部分地址。

所以,如果你有一个 B 类 IP 地址,意味着你有 16 位网络部分和 16 位主机部分,即你与大约 64,000 台其他主机处于同一个广播域。这是一个非常大的网络。很可能你并不需要如此大的网络,而更希望拥有一个更小的网络。
子网掩码与 CIDR
因此,我们不再仅仅通过查看 IP 地址的前几位来划分网络和主机部分,而是引入了子网掩码的概念。子网掩码是另一个 32 位的数字,用于“掩盖”原始地址中的某些位。其中所有为 1 的位代表网络部分,所有为 0 的位代表主机部分。
这种方法的优势在于,即使你拥有一个如上所示的 B 类网络 IP 地址,你也可以通过选择不同的子网掩码,将该网络进一步划分为更小的网络和子网。
例如,我们可以使用一个 24 位的子网掩码,留下 8 位作为主机部分。这实际上是在这个 B 类网络中创建了一个 C 类子网。这里的 24 个 1 在网络中可以用所谓的“斜线表示法”表示为 /24。我们也可以用十进制表示法来表示这个子网掩码,即 255.255.255.0。你可能见过这个子网掩码,它非常常见,因为它创建了一个大小合理的网络,同时便于人类快速识别该网络的起始和结束位置(正好在点分十进制的边界上)。
但子网掩码不必与十进制点边界对齐。毕竟,它本质上只是一串比特序列。所以,如果你想创建一个更小的子网,例如,可以使用一个 /26 网络,其十进制表示为 255.255.255.192。如果你需要一个更大的网络,则可能使用 /22 网络。
使用工具进行计算

幸运的是,我们不需要在头脑中执行这些逻辑运算。毕竟,计算机在处理数字,尤其是二进制数字方面比人类强得多。因此,有各种方便的工具可以帮助你确定正确的子网掩码。
例如,我们有 ipcalc 工具,你可以从你喜欢的包管理器中安装它。
如果我们传入我们的 IP 地址和以斜线表示法表示的子网掩码(在这个例子中使用 /16),那么我们会得到类似这样的输出。它向我们显示了 16 位的网络部分和全为 1 的子网掩码。但它也告诉我们,根据前两位的检查,这原本是一个 B 类网络地址。
由于主机部分剩下 16 位,我们现在知道了该子网上可以有多少台主机:接近 64K。但拥有 16 位,我们应该能得到 2^16 台主机,即正好 64K。但我们损失了两个地址,因为网络上的第一个和最后一个地址不能分配给主机。第一个地址是网络地址本身,最后一个地址是所谓的广播地址。发送到此地址的数据包将广播到网络上的所有主机。
如果我们将子网掩码调整为 /24,那么我们会得到调整后的信息,显示有 24 位网络部分和 8 位主机部分,产生 2^8 - 2 个可能的主机地址。

/26 子网掩码看起来像这样:26 位用于网络部分,6 位用于主机部分。
但 ipcalc 能为我们做更多事情。假设你想获取一个网络并创建不同大小的子网。例如,你想将一个现有的 /24 网络划分成三个子网,一个能容纳 24 台主机,一个能容纳 64 台主机,一个能容纳 48 台主机。那么正确的子网掩码是什么?
这就是 ipcalc 的用武之地。它向我们显示,对于第一个网络,我们需要使用 /27,能够容纳 30 台主机。对于第二个子网,我们需要 /25。对于第三个子网,我们需要 /26。ipcalc 还显示我们仍然有一些网络空间剩余,可以用剩余的 IP 地址创建另一个 /27 子网。
CIDR 计算逻辑
ipcalc 向我们展示的是,CIDR 表示法的计算都遵循相同的逻辑。
假设你有一个 IP 地址 A.B.C.D/N,例如 155.246.56.0/27。
N代表子网掩码中全为1的位数,即网络部分的长度(本例中为 27)。- 总 IP 地址长度是 32 位,因此可用于主机部分的剩余位数是
32 - N(本例中为32 - 27 = 5)。 - 此处可用的地址总数是 2 的那个数字次方,即
2^(32-N)(本例中为2^5 = 32)。 - 但我们知道不能使用所有这些地址,我们必须减去 2 个:网络上的第一个地址(网络地址本身)和最后一个地址(广播地址)。
- 最后,子网掩码作为另一个 32 位数字,也可以表示为点分十进制地址。

总结
在本节视频中,我们简要提到了网络分类的历史背景。虽然这已不再使用,但理解它仍然有益。然后,我们讨论了通过使用子网掩码将网络划分为更小子网(子网划分)的概念,并识别了此过程中的不同逻辑步骤。

以上所有内容都适用于 IPv4。那么 IPv6 呢?我们将在下一节视频中讨论。你会发现 IPv6 世界中的情况有很大不同,但并非完全不同。这将非常有趣,请务必观看我们的下一节视频。感谢观看。
027:IPv6 基础 🚀
在本节课中,我们将要学习 IPv6 的基础知识。我们将从回顾 IPv4 数据包结构开始,然后深入探讨 IPv6 数据包的格式、地址表示方法、压缩规则以及地址的不同类型和范围。通过对比 IPv4,我们将理解 IPv6 如何解决地址耗尽问题及其在现代网络中的重要性。
概述
上一节我们分析了 IPv4 数据包和无类别域间路由的概念。本节中,我们来看看 IPv6 如何作为“救星”来解决 IPv4 地址耗尽的问题。
首先,让我们快速回顾一下之前视频中的图示,它展示了 IPv4 数据包的结构。我们曾通过 tcpdump 捕获与史蒂文斯理工学院 Web 服务器的 TCP 数据包,识别了各个字段,并指出了 32 位的源地址和目的地址,以及子网掩码如何将地址划分为网络部分和主机部分。
那么,使用 IPv6 时,情况是怎样的呢?为此,我们希望重复之前的数据包捕获过程,但这次我们将请求一个拥有 IPv6 地址的网站。请注意,你需要在一个启用了 IPv6 的系统上才能重复此练习。你的家庭 ISP 可能不提供 IPv6 地址,你的本地 Wi-Fi 接入点也可能未配置提供 IPv6。不幸的是,在 2021 年,亚马逊仍然默认不启用 IPv6。但至少从 2017 年起,你可以在 AWS 中获得原生的 IPv6 地址,而无需使用隧道代理。
如果你想确保你的新 AWS 实例同时启用 IPv4 和 IPv6(也称为双栈),可以按照我在这篇博客文章中概述的步骤操作。这样,你的实例就应该获得一个可路由的 IPv6 地址,然后我们就可以开始数据包捕获了。
IPv6 数据包结构分析
我们再次从 tcpdump 开始,这次寻找与 www.yahoo.com 通信的数据包。像之前一样,我们发出 curl 请求,然后从文件中读取捕获的第一个数据包。
开头的几个字节是相同的,因为这是在数据链路层,使用了与之前相同的 MAC 地址。但描述 MAC 帧所封装协议类型的下一个字段不再是 IP,而是 IPv6 对应的值。从那里开始的字节,一直到大约这里,是 IPv6 头部,其余部分是 TCP 负载。
让我们查看 eth0 接口上的 IPv6 地址,以及 yahoo.com 解析出的地址,以便你可以将输出与 tcpdump 数据包中的内容进行比较。
现在,让我们与 IPv4 的情况进行对比。以下是 IPv6 头部的结构,它看起来与 IPv4 头部相似,但更简单一些。
以下是各字段的说明:
- 版本:一个 4 位的字段,设置为
6。 - 流量类别:类似于 IPv4,包含 DSCP 和 ECN 位,此处全为零。
- 流标签:一个 20 位的字段。一个“流”只是一组属于一起的数据包,例如一个 TCP 会话。通过这个标识符,我们可以更容易地在网络中追踪完整的请求。这个流标签是随机生成的。
- 有效负载长度:十六进制的
28或十进制的40,因为这里没有额外的 IPv6 扩展头。 - 下一个头部:指定负载的协议,本例中是 TCP。
- 跳数限制:相当于 IPv4 头部的 TTL,默认值也是
64。
剩下的 256 位用于源地址和目的地址,它们在 tcpdump 的十六进制输出中高亮显示。

IPv6 地址表示与压缩
现在,我们需要像处理 IPv4 那样,将这些十六进制字符转换回 IP 地址吗?让我们看看。事实证明不需要。IPv6 地址本身就是十六进制的,所以我们在这里直接看到了实际的 IP 地址,这相当方便。
与 IPv4 相比,IPv4 地址是 32 位数字,而 IPv6 地址则大得多。具体来说,IPv6 地址是 128 位数字。我们为什么最终选择了 128 位,以及 IPv6 旨在解决什么问题,这将在下一个视频中讨论。现在,我们只关注地址的结构。
拥有 128 位会形成一个相当冗长的地址。如果我们使用点分十进制表示法,最终会得到 16 个八位组。因此,我们改用 8 个 16 位的字段,将其转换为十六进制并用冒号分隔。这样,我们冗长的比特串就变成了这个十六进制字符串。
由于这样的字符串仍然可能很长,我们有几种缩短它们的方法。
以下是 IPv6 地址的压缩规则:
- 省略前导零:每个 16 位字段中的前导零可以省略。
- 压缩连续的零字段:如果存在连续的多个全零字段(
0000),可以用一个双冒号::代替。但请注意,这种压缩只能使用一次。这意味着如果你在左边和右边都有连续的零字段,你必须决定压缩哪一边。
例如,对于地址 2001:0db8:0000:0000:0000:ff00:0042:8329:
- 你可以通过压缩左边两个 16 位字段得到:
2001:db8::ff00:42:8329。 - 或者通过压缩右边三个 16 位字段得到:
2001:db8:0:0:0:ff00:42:8329。 - 注意,你不能同时压缩两边,因为这样将无法确定左边或右边应该填充多少个零字段。
- 但请注意,这两种压缩方式(只压缩右边或只压缩左边)都是有效的,并且代表同一个地址,未压缩的版本也是如此。
IPv6 地址的特殊类型与语法
除了字符串压缩,在书写 IPv6 地址时,还需要考虑一些额外的事项。
就像 IPv4 中本地环回地址是 127.0.0.1 一样,IPv6 也有一个环回地址:::1。请注意,这完全不是一个特例,它只是一个前 127 位都是零的地址,转换成了七个全零的字段,然后被压缩了。这里更大的区别在于,IPv4 的环回设备实际上获得了整个 A 类网络,而不仅仅是一个地址,我们将在未来的视频中讨论这一点。
如果你的系统配置为双栈,并且你的套接字允许 IPv4 和 IPv6 地址,你可能会遇到所谓的 IPv4 映射地址。在这种地址中,一个点分十进制格式的 32 位 IPv4 地址被填充到一个 IPv6 地址中,其前缀是特殊的全零加 ffff,例如 ::ffff:192.0.2.1。
接下来,虽然不是 IP 地址的严格组成部分,但我们必须考虑应用程序如何处理在地址之外指定端口。在 IPv4 中,我们使用 地址:端口 的表示法来指定 IP 地址和端口对。但由于 IPv6 地址使用冒号作为分隔符,我们不能这样做。因此,我们使用一种新的语法:用方括号将 IPv6 地址括起来,后面跟着冒号和端口,例如 [2001:db8::1]:80。
最后,我们需要指出,不同的 IPv6 地址具有不同的隐式作用域。
正如你在查看 ifconfig 输出时可能注意到的,每个接口有多个地址。到目前为止,这并不奇怪,因为任何接口也可以有多个 IPv4 地址。但在 IPv6 中,每个接口总是有一个所谓的 链路本地地址。这个链路本地地址由主机自身生成,不需要任何特定的网络知识或 DHCP 服务器。它是一个在 fe80::/10 网络内的地址,低 64 位使用标准化算法生成。这个地址仅在特定链路上使用,任何发送到或来自此地址的数据包都不会被转发到任何其他链路,这意味着数据包保证停留在该接口上。
其次,我们有在 fc00::/7 网络中的 唯一本地地址,用于私有 IPv6 网络,并且不在全球范围内路由。这在概念上类似于你从本地 Wi-Fi 网络、AWS 默认 IPv4 空间等熟悉的 IPv4 RFC 1918 IP 空间。
最后,我们有 全局地址。这些是你将看到和使用的正常地址,例如我们在此示例中使用的地址。它们是全球唯一的,可以在公共互联网上路由。
IPv6 子网划分
从以上要点可以看出,我们经常使用斜线表示法来讨论网络空间,就像我们讨论 IPv4 CIDR 子网划分时一样。这是 IPv6 与 IPv4 并没有太大不同的地方之一。
我们仍然使用一个称为子网前缀的位掩码,斜线表示法仍然适用。唯一的区别是斜线后的数字可以大得多,因为我们现在有 128 位可以操作。
因此,我们使用类似的工具来计算和检查不同的子网前缀也就不足为奇了。遗憾的是,我们在上一个视频中使用的 ipcalc 工具不支持 IPv6,所以我们使用另一个工具 sipcalc。

如果我们指定 /64 的子网长度,那么我们会得到如下输出,说明了扩展和压缩形式,以及子网前缀、地址作用域和网络范围,所有这些看起来都与我们之前的 IPv4 示例非常相似。
如果我们想扩大我们的子网,我们会使用一个更小的网络前缀,比如这里的 /48。

如果我们选择一个 /72 前缀,情况看起来会是这样。然而,请注意,最常遇到的前缀之一将是 /64。为什么是这样,我们将在下一个视频中讨论。
总结与练习
本节课中,我们一起学习了 IPv6 的基础知识。我们分析了 IPv6 数据包的结构,了解了其 128 位地址的表示方法和压缩规则,认识了链路本地地址、唯一本地地址和全局地址等不同类型,并明白了 IPv6 的子网划分与 IPv4 在原理上的一致性。
现在轮到你去探索周围的 IPv6 世界了。检查你是否获得了一个 IPv6 地址,如果是,你的常用网络是否启用了 IPv6,这可以延伸到公共网络或你的家庭网络。如果你的家庭网络目前不支持 IPv6,你能改变这一点吗?我们的 AWS 实例呢?我已经链接了描述如何设置环境以确保实例以双栈模式启动的博客文章。如果你还没有这样做,请现在完成,因为我们将在接下来的视频和练习中使用双栈网络。
做一些研究,找出哪些流行的服务和系统启用了 IPv6。你可能认为所有大公司都会启用,但真的是这样吗?哪些服务没有启用?你认为原因是什么?

在下一个视频中,我们将讨论一些关于互联网治理的内容,探讨 IP 地址的来源、它们是如何分配的,以及我们究竟为什么需要 IPv6。毕竟,32 位对于“互联网”这个小实验来说应该足够了,不是吗?下次见,感谢观看。
028:IP地址分配与IPv4耗尽 🌐
在本节课中,我们将学习IP地址的分配机制,并探讨IPv4地址耗尽的原因。我们将了解IP地址从何而来,以及全球互联网地址空间是如何被管理的。
IP地址从何而来?🤔
上一节我们介绍了IPv4和IPv6的数据包头细节,但回避了一个核心问题:IP地址究竟从何而来?为什么我们同时拥有IPv4和IPv6?另外,IPv5发生了什么?
首先,让我们快速解答最后一个问题。确实存在一个使用协议版本5的IP层协议,即互联网流协议。逻辑上,也存在版本1、2和3的协议,其中版本3是TCP和IP被拆分开的第一个版本。

IPv4地址空间与分配机制 📊
IPv4地址是一个32位的数字,这意味着我们最多可以有 2^32 个地址。2^32 是一个很大的数字,整个IPv4互联网地址空间大约有43亿个IP地址。这至少是理论上的空间。
那么,如何获得其中一个地址呢?许多系统通过DHCP等方式“神奇地”获取IP地址。这些地址来自哪里?以史蒂文斯理工学院为例,为什么我们几乎所有的IP地址都以155.246开头?
要回答这个问题,我们需要回到互联网的早期。当时,IP地址空间管理等事务很大程度上掌握在一个人手中——约翰·波斯泰尔。

约翰·波斯泰尔在互联网发展中极具影响力,最初管理着互联网号码分配机构。这意味着当组织需要分配IP空间时,由他负责处理。他还合著了大量RFC文档,包括那些成为域名系统基础的文档。但显然,让任何一个人负责IP地址空间分配是不可扩展的解决方案。

因此,IANA的管理权移交给了ICANN。这里我们略过了一位最伟大的互联网先驱和架构师的令人印象深刻的历史,强烈建议你自行阅读关于约翰·波斯泰尔的资料。你可能听说过波斯泰尔定律,也称为软件编程中的稳健性原则:“接受时要宽容,发送时要保守。”这也以约翰·波斯泰尔命名。
全球IP地址管理架构 🏛️
现在,我们有了ICANN,这是一个监督全球IP地址和自治系统号码分配、根区域管理等事务的非营利性标准组织。我们今天将重点讨论IP空间的管理。

ICANN控制着IPv4和IPv6空间,意味着它可以取出大的网络块并将其分配给其他实体。需要注意的是,图中显示的IP空间缺少某些部分,这些部分不可由ICANN分配。这是因为对于IPv4和IPv6,都有某些网络块被保留用于特殊用途,例如大家熟悉的RFC 1918私有IP空间及其IPv6等效空间。
在剩余可用的网络块中,ICANN可能会将大块地址分配给区域互联网注册管理机构,例如:
- 非洲网络信息中心
- 亚太网络信息中心
- 美国互联网号码注册机构
- 拉丁美洲和加勒比网络信息中心
- 欧洲网络协调中心
每个RIR管理特定地理区域的分配,这说明了互联网的分布式性质,尽管其起源明显在美国。顺便提一下,XKCD的艺术家兰德尔·门罗对IP空间做了一个简洁的插图,推荐查看。
分配层级与史蒂文斯理工的案例 🏫
RIR们松散地组织为号码资源组织,有助于协调这一层面的内部治理。每个RIR都从ICANN获得了IP空间分配,然后可以进一步划分这些网络块,并将其分配给所谓的本地互联网注册管理机构使用或进一步分配。大多数LIR是互联网服务提供商、学术机构或大型企业。
回到我们最初的问题:史蒂文斯理工学院是如何获得其IP空间的?它是由ARIN从ICANN分配给它的网络块中分配而来的。然而,ARIN由于早期以美国为中心的互联网管理,是一个特例。ARIN确实收到了大量历史性分配,然后它可能会进一步分配给另一个RIR。
我们看到IP空间实际上可能具有地理属性。如果你长期处理这些网络,甚至可能记住一些,并知道连接到系统的流量来自亚洲。但这可能有点误导性,因为IP地址本身并不固有地绑定特定的物理位置,而是一个关于IP空间管理所有权的问题。然而,这反过来可以被全球地理位置数据库所利用。


LIR可以进一步根据需要委托网络块,可能使用我们之前讨论过的CIDR进行划分。例如,我们知道史蒂文斯理工学院被分配了155.246.0.0/16网络块,并将155.246.89.0/24分配给了计算机科学系。我们还可以观察到,威瑞森在几年前收购雅虎后,成为了所示网络的新所有者,并特别将几个IP地址分配给了特定服务。
IPv4地址耗尽危机 ⚠️
现在我们知道IP地址来自哪里了,但为什么我们除了IPv4还需要IPv6呢?回想一下我们刚刚讨论的分配。我们的IPv4空间是32位,因为Vint Cerf等人认为这对于一个看看“互联网这东西”能否成功的小实验来说是一个合理的选择。


结果它成功了。而我们从此就一直困在这个32位的空间里。尽管43亿个地址听起来很多,但实际上并非如此。首先,我们甚至无法获得完整的2^32空间用于互联网。如前所述,有些区块被保留,例如私有IP空间,以及其他几个用于不同目的的网络块,它们总共约占整个IPv4空间的13%。
不仅如此,在互联网早期,分配相当宽松。因此,一些早期参与者确实获得了整个A类网络(超过1600万个地址)分配给了AT&T、苹果、麻省理工学院等。另一个问题是组织自身分配空间效率不高,导致子网碎片化,事后需要额外分配。

当然,我们现在把灯泡、冰箱和牙刷都连上互联网,每个都需要IP地址,这无助于缓解问题。因此,我们最初拥有的2^32个地址很快就开始耗尽。ICANN大约在10年前耗尽了未分配的/8网络池,随后RIR们也迅速跟进:APNIC在2011年4月,RIPE NCC在2012年,LACNIC在2014年,ARIN在2015年,而AfriNIC在去年1月耗尽。这意味着我们在IPv4空间里正式耗尽了IP地址。43亿个地址几乎用完了。这是一个我们早就知道会到来的问题。

这就是为什么IPv6早在1995年就被引入,以及为什么看到在ICANN耗尽可分配的IPv4地址十年后的今天,仍然有组织和公司不支持IPv6,是如此令人失望。
IPv6的广阔空间与分配 🚀
那么,对于IPv6,我们的默认分配是怎样的呢?


RIR通常被分配一个/12,然后它们以低至/32的块委托给LIR,LIR再以/48的块委托给最终用户。而最终用户设备(例如你的Wi-Fi接入点分配给你的地址)大部分是/64。想想看,大多数最终用户将获得一个/64。这听起来非常浪费。当然,你可以把你所有的冰箱、电视、灯泡和牙刷都放在这个/64网络上,但你仍然剩下那么多基本上被浪费的IP。我们不会以这种方式耗尽IPv6地址空间吗?😊
要理解为什么这不是问题,关键在于理解IPv6空间到底有多大。2^32是43亿个地址,我们刚刚意识到这真的不算多。现在你是计算机科学专业的学生,所以希望你能比普通人更好地理解指数运算的工作原理。


但让我们来说明一下IPv6空间中可用的地址数量。2^128是一个像这样的数字:39位十进制数,340 undecillion, 282 decillion, 3 sextillion, 66 quintillion。一个非常大的数字。这个数字有多大?让我们看看。

我们可以给地球上的每个人分配多少个IP地址?好吧,那是每人4.41 x 10^28个IP地址。这很难想象。一个普通人身上有多少个原子?大约7 x 10^27个。所以,我们可以给地球上每个人的每个原子分配IP地址。一个“原子互联网”。
我们还知道什么东西很多?星星。哦,银河系里的星星都不足以在这里真正产生影响。可观测宇宙呢?嘿,我们可以给可观测宇宙中的每颗星星分配340万亿个IP地址。如果可观测宇宙中的每颗星星都有一个像我们这样的星球呢?那么,我们可以给可观测宇宙中每颗星星的“地球”轨道上的每个人分配超过44000个IP地址。
这就是我们拥有的IP地址数量,这就是为什么给每个最终用户分配一个/64不会耗尽IPv6地址池。即使通过分配一个/64(即18 quintillion个地址),我们也是在给每个最终用户43亿个IPv4互联网。我知道指数运算很有趣,对吧?无论如何,我希望这里的这一点能让你对IPv6地址空间的大小有所了解。再多玩玩它,Wolfram Alpha是处理这类事情的一个相当不错的引擎。

总结与思考题 💡
好了,我们在这里暂停一下。我们已经看到了IP地址是如何分配的:ICANN将网络块分配给RIR,RIR进行划分并分配给LIR,LIR然后可以进一步委托或分配它们。
现在,看看周围。你能找出哪些IP块属于哪些公司或组织吗?你如何找出一个IP块分配给了哪个地理区域?

接下来,为了更好地理解IPv4耗尽在实践中意味着什么,并从RIR的角度出发,查看RIPE NCC提供的信息,看看他们还有多少IP地址可以分配。同时,思考为什么AfriNIC是最后一个耗尽的,以及这对全球互联网普及和流量分布说明了什么。
思考一下当一种资源变得稀缺时通常会发生什么。通常,会出现一个新的市场。你能找出组织可以在哪里以及如何交易他们可能不再需要的IP空间吗?所有这些问题都是为了让你从现实世界的角度思考互联网。这意味着它不是抽象的东西,而是具有某些物理特性和影响。

我们将在下一个视频中深入探讨互联网物理结构的一些细节。到时见,感谢观看。再见。

本节课总结:在本节课中,我们一起学习了IP地址的全球分层分配机制,理解了IPv4地址空间有限且已耗尽的原因,并认识了IPv6地址空间的巨大规模及其分配方式。我们了解到互联网地址管理是一个涉及ICANN、RIR和LIR的多层现实世界体系。
029:第5周第5节 - 物理互联网 🌐
概述
在本节课中,我们将要学习互联网的物理层面。我们将从数据包如何通过物理线路传输开始,探讨海底电缆的铺设与风险,并分析物理基础设施如何影响网络连接、审查制度乃至地缘政治。理解这些物理现实,对于系统管理员全面把握网络运行环境至关重要。

从抽象到物理:数据包的旅程
上一节我们介绍了IP地址的分配与地理划分。本节中,我们来看看数据包在物理上是如何从A点流动到B点的。

如果是一条点对点的链路,数据包的路径非常清晰。数据包从一端进入,从另一端出来。你可以沿着物理线缆从一端追踪到另一端。


即使是笔记本电脑与Wi-Fi接入点之间的通信,连接也相对容易理解。数据包到达接入点后,进入线缆,然后从你的互联网服务提供商(ISP)传送到某个机房或数据中心的交换机端口。

此时,数据包进入了“矩阵”。大多数人,甚至计算机专业的学生,对于互联网在这一层面的样貌也只有一个抽象的概念。将其比喻为“一系列管道”其实并不过分,因为本质上就是通过线缆连接,数据包在其中流转、转向和重定向。
全球网络与跨洋连接
互联网是一个全球网络。想象物理线缆跨越国家、在建筑物之间铺设是容易的,数据流也确实与人口密度相关,正如NASA的夜间灯光图所显示的那样。

但问题在于:我们如何将数据包从密集区域传送到其他地方?跨大陆通信并非新问题。自19世纪50年代起,人们就开始在海底铺设超长电缆以实现欧美间的电报通信。令人着迷的是,我们今天依然在做同样的事情。
我们使用装有数千公里网络电缆的特种船只,将长长的电缆铺设到海底,有时还会使用特殊的海底电缆犁将其埋入海床。虽然技术含量高,但原理简单:一根非常长的电缆。这是互联网物理层面需要牢记的一个方面,因为它带来诸多影响。
一方面,物理距离决定了理论上的最低延迟。例如,纽约与巴黎相距约5800公里,光速约为每秒300,000公里,这意味着一个数据包的单向传输不可能快于约19毫秒(计算:5800 km / 300,000 km/s ≈ 0.0193 s),实际中由于各种开销,延迟会更高。
海底电缆:动脉与风险
这些连接国家的电缆被称为海底互联网电缆,由不同的公司运营,包括通信供应商和一些互联网巨头。submarinecablemap.com 这个网站可以让你缩放并查看所有不同的电缆,非常有趣。
但将电缆铺设在海底意味着风险。电缆可能被好奇的鲨鱼咬坏,也可能被在港口外抛锚的船只的锚意外割断,导致严重的网络中断。
以下是部分重要海底电缆的例子:
- AEC-2电缆:连接丹麦、挪威、爱尔兰和美国,长约8000公里,由包括Facebook和Google在内的财团运营,容量为每秒18太比特。其在美国的登陆点位于新泽西州沃尔镇,靠近史蒂文斯理工学院。
- 日本-美国电缆:横跨太平洋,经停夏威夷。
- 东南亚-中东-西欧电缆:从新加坡到法国。2008年,该电缆被意外切断,导致多个国家至少部分失去了互联网连接。
由此可见,这些主要通信线路一旦被破坏,无论是意外还是故意,都可能造成严重影响。
物理控制与网络访问
你可能听过约翰·吉尔摩的名言:“网络将审查视为损坏,并绕开它。”这强调了互联网分布式架构的好处。但不幸的是,这并不完全正确。
如果你拥有足够的权力并愿意承担后果,你确实可以审查或中断大部分公民的互联网接入。独裁者和威权政府越来越多地行使这种权力。当权者非常害怕民众自由沟通的能力,因此互联网必然对他们构成巨大威胁。
每当发生起义或政治动荡时,掌权政府都会尽力压制人民的沟通能力。例如:
- 缅甸:在2021年2月军方接管政权后的抗议活动中,切断了互联网。
- 伊朗:2019年,最高国家安全委员会在抗议期间下令完全断网。
- 印度:这个世界上人口最多的民主国家,却是互联网关闭次数最多的国家。其查谟和克什米尔地区曾经历长达6个月的断网,造成了超过24亿美元的经济损失和50万个工作岗位的流失,这说明了互联网接入在当下已是一项基本人权,而政府却越来越愿意且能够限制它。
如果你控制着负责物理传输数据包的法律实体,你就可以限制人们的访问。但这种权力不仅限于简单的“开”或“关”。

监控与伦理困境
更进一步,你能够访问流经你所控制的物理设备的所有通信。因此,如果有法律权力迫使私营企业监听所有经过的数据包,那么像美国政府这样的政府也会这么做就不足为奇了。

图中是旧金山AT&T大楼内的“641A房间”,一个秘密的网络机房。AT&T员工马克·克莱恩发现这个上锁的房间镜像了所有网络流量,他随后成为举报人,揭露美国政府不仅对全球通信(几乎所有大型情报机构都这么做),还试图对美国公民的通信进行无证监视和数据挖掘。

这一切再次强调了一个观点:无论我们多么专注于比特、字节、软件和系统管理,归根结底,我们都在第9层(政治/社会层) 之上运作。我们的工作本质上是政治性的。互联网工作促进或禁止信息的自由流动;连接性、路由和网络协议设计都必须考虑意外或故意的滥用;我们必须理解强大行为者(包括你自己的政府)的影响和能力。
正如马克·克莱恩所示,有时,我们必须做出艰难的决定,将政治影响置于工作保障之上。因为即使我们自己不在不同司法管辖区之间铺设电缆,互联网的物理层面仍然是我们工作中一个始终存在的因素。
总结
本节课中,我们一起学习了互联网的物理基础设施。我们从数据包的物理路径开始,了解了全球网络如何通过海底电缆连接,并认识到这些物理资产面临的意外风险(如鲨鱼、船锚)和故意破坏风险。更重要的是,我们探讨了物理控制如何直接转化为网络访问控制、审查和监控能力,使得互联网接入成为一个政治议题。作为系统管理员,理解这些物理现实及其政治含义,对于负责任地设计、管理和维护网络系统至关重要。
在接下来的章节中,我们将进一步探讨网络所有权问题,并审视物理互联网所反映的政治结构。在此之前,请务必查看幻灯片中包含的各种链接,阅读有关各种网络中断事件的资料,或许还可以研究一下互联网上其他重大的破坏性事件。我相信你会发现许多以前未曾仔细考虑的、有趣的信息,并开始将互联网视为一个更具体、更实在的物理存在。
感谢观看,下次再见。
030:网络的网络 🌐
概述
在本节课中,我们将学习互联网的本质——它并非一个单一的网络,而是由众多独立网络相互连接构成的“网络的网络”。我们将探讨这些网络如何组织、如何被识别,以及它们之间如何通过“对等互联”建立连接。
网络的物理本质与法律实体
上一节我们探讨了互联网的物理构成及其现实影响。本节中,我们来看看互联网具体是如何由独立网络构成的。
我们已经知道,我们通过例如海底通信光缆等物理设施连接全球互联网,这些光缆将各大洲连接起来。我们直观地理解,这些光缆的端点显然由不同的法律实体控制,因此,由它们实现的连通性也由这些实体管理。
这正是互联网本质的一个直接体现:互联网不是一个单一网络,而是一个网络的网络。这个网络的网络自20世纪60-70年代起步以来,已经取得了巨大的发展。
早在1984年,Sun Microsystems的约翰·盖奇就提出了“网络就是计算机”的说法,意指所有计算机都只是连接到更大网络的接口。将计算资源全部置于网络上的想法并不新鲜。
但这确实带来了一些混淆,因此有必要澄清:网络就是网络,计算机依然是计算机。然而,这个网络本身并不是一个单一网络。尽管我们通常认为通过网络可达的计算资源存在于神奇的“云端”,但系统管理员非常清楚:没有云端,只有别人的计算机。毕竟,总得有人在某个地方维护这些资源。
从局域网到自治系统
现在,让我们更聚焦于网络,或者说构成这个网络的各个网络。
网络有增长的趋势。如果我们连接两个端点,这很简单。但当我们连接多个设备时,正如我们之前讨论过的,我们需要一个网络层。如果设备在同一个广播域内(例如通过交换机连接),那么我们拥有的是一个第二层网络。我们可以使用第三层设备(即路由器)将多个这样的第二层网络连接起来。通过这种方式,我们也讨论过需要补充我们的IP地址块。
当然,我们最终会希望连接多个这样的网络。当我们这样做时,我们开始谈论自治系统——即被分组在一起的网络集合。这样一个自治系统内的各个网络,可能由使用独立IP地址空间的独立网络组成。这些IP地址空间,正如我们在之前的视频中讨论的,是由IANA分配给区域互联网注册管理机构,再分配给本地互联网注册管理机构的。
当我们连接这些自治系统时,我们就在构建互联网。
互联网的可视化与路由
这里展示的是1997年左右互联网路由路径的可视化图,按分配地址空间的RIR进行了颜色编码,并通过连接路径分组。图中的中心星点代表连接到更多其他网络的系统。
到了2021年,这个网络看起来有些不同,显然有了更多的网络、更多的网络间连接和更多的地理区域,但它仍然像以前一样代表路由路径。
这些路由路径主要基于对这些自治系统的了解,因为边界网关协议会共享其路由信息,即不同网络通过其AS编号可达的信息。
如何获取AS编号?
那么,我们从哪里获得AS编号呢?还有谁比分配IP地址空间的同一实体更适合分配AS编号呢?
是的,我们再次回到IANA,它将AS编号块分配给RIRs,然后RIRs再分配具体的AS编号,其管理方式与IP地址块类似。
查询网络信息:whois命令
以下是一种查询给定网络块分配信息的方法:使用whois命令,该命令通过whois协议查询各个网络信息中心。
如你所见,查询通常从IANA开始,然后可能会查询相关的RIR以获取信息。这种方式与DNS协议类似,我们将在未来的视频中详细讨论DNS。
如果我们想查询史蒂文斯理工学院的网络信息,我们可以传入其Web服务器的IP地址。然后我们可以看到,IANA告诉我们它已将155.0.0.0/8地址块分配给了ARIN,而ARIN则告诉我们,它于1991年将155.246.0.0/16地址块分配给了史蒂文斯理工学院。ARIN还提供了一个URL,可提供关于此网络块的额外信息。
从这些信息中,我们可以提取出史蒂文斯理工学院的AS编号是16889。如果我们更详细地查看ARIN的信息,会发现史蒂文斯理工学院被分配了多个IP地址块,而不仅仅是常见的155.246.0.0/16。事实上,看起来史蒂文斯理工学院还被分配了一个IPv6网络,只是似乎使用得不多。
追踪数据包路径:traceroute
现在,我们可以开始可视化我们的网络如何连接到其他网络。假设这里的底部网络代表史蒂文斯理工学院,即AS 16889,以其认为合适的方式使用其网络块。
那么,史蒂文斯理工学院连接到的网络的AS编号是多少?为此,我们只需找出当与互联网上某个Web服务器通信时,我们的数据包所经过的路径。我们将在接下来的一个视频中更详细地研究traceroute工具的工作原理。
当我们追踪到雅虎网站的数据包时,返回的输出会显示沿途的不同跳点。我们看到,数据包从起点跳转到155.246.0.0/16网段内的另一个地址,然后是一个RFC 1918私有网络地址,接着是其他一些155.246.0.0/16地址,之后离开史蒂文斯理工学院网络,最终找到通往雅虎网络的路。

现在,让我们看看这里的第一个非史蒂文斯理工学院地址。这个地址属于新泽西高等教育网络分配的一个网段。我们可以使用一个专门提供此类信息的whois服务器来获取其AS编号。这样很方便,我们识别出了下一个网络的AS编号,在本例中NJH的AS是21976。
我们可以为沿途的每一跳手动执行此操作,但幸运的是,traceroute工具有一个选项可以为我们完成这项工作。于是我们得到:史蒂文斯理工学院 AS 16889 -> NJH AS 21976 -> AS 4637 -> AS 10310(似乎是雅虎的AS编号),此外还有AS 26101,这表明一个组织当然可以拥有多个AS编号。
有了这些信息,我们现在可以填充数据包如何穿越不同网络的图像:史蒂文斯理工学院AS 16889连接到NJH AS 21976,后者连接到AS 4637,再从那里连接到雅虎的AS 10310和AS 26101。
网络互联:对等与交换点
我们看到,网络之间的连接是在特定位置建立的。连接不同网络的这个过程称为对等互联,它以这些实体的系统之间的实际物理连接形式进行,允许它们通过BGP交换路由信息。
这些连接发生在所谓的对等点或互联网交换点,即全球战略位置的数据中心。

关于这一点,就像互联网上的许多事物一样,很棒的一点是它主要是公开信息。也就是说,我们可以在例如PeeringDB网站上查询谁在什么地点与谁对等。
如果我们输入NJH的AS编号21976,我们会看到关于该组织的大量信息,包括它在哪些IXP进行对等。其中之一是纽约国际互联网交换点。我们也可以在此处AS 4637下一跳的DNS名称中看到这一点。AS 4637属于Telstra公司,然后它连接到AS 10310,这是雅虎的AS编号之一。这里的条目显示了雅虎进行对等的所有IXP,结果证明在全球有相当多。
数据包在进入AS 26101之前,在AS 10310内跳转了一会儿。但我们没有在PeeringDB中找到AS 26101,也没有找到史蒂文斯理工学院的AS 16889。这是因为并非每个网络都直接在公共IXP与其他网络对等。除了公共对等位置,当然也存在私有连接,例如史蒂文斯理工学院和NJH之间的连接,后者在此处提供了通往更大互联网的网络连接。
像Telstra的AS 4637这样的大型网络会连接更多其他网络,从而成为我们在本视频开头看到的网络可视化图中的中心点。


互联网交换点的规模与统计
可以想象,这些对等点,即大型运营商之间建立连接的互联网交换点,需要拥有一些“大管道”。由于这是一个重要的卖点,你通常可以在它们的网站上找到一些有趣的统计数据。
阿姆斯特丹互联网交换点是世界上最大的IXP之一,显示日吞吐量达9.4太比特/秒。每日统计数据很好地显示了流量如何在夜间下降,白天上升,晚餐后达到峰值,然后再次下降。月度年度统计数据则显示了随时间推移总吞吐量的增长。
德国的DECIX,另一个世界最大的IXP,也有一些显示类似模式的统计数据。莫斯科交换点也是如此,尽管这里的吞吐量略低于阿姆斯特丹或法兰克福,IPv6的速率甚至更低。纽约、多伦多和瑞典的Netnod交换点的统计数据都呈现相似的模式和风格。
这并不奇怪,因为它们都是使用标准的开源网络流量绘图工具之一MRTG生成的。如果你参与任何网络架构和管理工作,你很可能会非常熟悉看起来都像这样的图表。
总结
本节课中,我们一起学习了互联网作为“网络的网络”的基本结构。我们了解了自治系统的概念、如何通过whois和traceroute工具查询网络和AS信息,以及网络之间如何通过对等互联和互联网交换点连接起来。我们还看到,网络流量可以通过工具进行测量和可视化。
为了确保你内化我们在此说明的经验,建议你尝试使用本视频中展示的工具,识别其他组织和公司的AS编号,并找出它们在何处进行对等。尝试了解当两个组织发生争议,一方想要取消与另一方的对等连接时会发生什么(已有数起竞争公司利用此手段或威胁取消对等来损害竞争对手的案例)。你可以在维基百科上找到最大互联网交换点的列表,并浏览它们的网站以查找我们刚才展示的统计数据。此外,也可以研究一些开放性较低的地点。
如果所有这些让你足够感兴趣,以至于你想成为真正从物理上构建互联网的网络运营商社区的一员,你可以加入北美网络运营商小组。其公共邮件列表是一个非常有趣的地方,可以了解互联网的结构以及大多数高层人士从未考虑过的各个方面。

至此,我们结束了第一周的网络主题学习,但正如我所承诺的,关于这个话题我们还没有结束。在接下来的几个视频中,我们将再次深入到数据包层面,追踪不同应用程序和协议流量,同时也需要看看我们的主机如何知道以及向何处发送数据包。你将非常熟悉tcpdump和各种统计工具。希望你能期待接下来的内容。
031:Week 06, Segment 1 - 网络 II,一个简单的请求 🌐
在本节课中,我们将继续探讨网络这个宏大主题。我们将通过追踪一个非常简单的网络请求,来深入了解系统之间是如何具体通信的,以及数据包在网络中是如何流动的。
上一节我们介绍了互联网的宏观视图、IP地址空间的治理以及物理网络的影响。本节中,我们来看看一个具体的网络请求在本地系统层面是如何运作的。

一个简单的请求示例
一个简单的请求可能是什么样的?超文本传输协议(HTTP)是一个显而易见的选择,因为它无处不在且易于理解。让我们模拟一个最基本的HTTP请求。

我们使用 telnet 命令,从我们的标准AWS EC2双栈FreeBSD实例上,建立一个到 www.yahoo.com 端口80的TCP连接,并发出一个HTTP HEAD请求。
telnet www.yahoo.com 80
HEAD / HTTP/1.0


服务器会回复一些信息,然后连接终止。这就是一个简单的请求。
深入探究:连接是如何建立的?
表面上,这个过程看起来是:本地主机连接到远程主机,发送数据,接收回复,然后完成。但让我们深入挖掘一下,本地主机究竟是如何连接到远程主机的。
为了详细分析,我们重复这个简单请求,但这次会捕获一些信息,以便重建系统上发生的详细过程。

以下是操作步骤:
- 在后台运行
tcpdump以捕获网络数据包。 - 清除ARP缓存,确保从最底层开始。
- 使用
ktrace工具包装telnet命令并执行请求。 - 停止数据包捕获,并分析收集到的信息。


ktrace 是一个用于追踪其他命令的工具,可以让你看到它执行了哪些系统调用和I/O操作。在其他Unix变体上,你可以找到类似的工具,如Linux上的 strace 或OmniOS上的 dtrace。

主机名解析的步骤
通过分析 ktrace 的输出,我们发现,一个简单的“连接到远程主机”的步骤,实际上分解为几个子步骤。
首先,需要将给定的主机名转换为IP地址。这个步骤本身又分解为:
- 应用程序通过查询
/etc/nsswitch.conf文件,来确定如何将主机名转换为IP地址。 - 根据
nsswitch.conf的配置(通常是先查文件,再查DNS),首先在/etc/hosts文件中查找主机名。 - 如果在
/etc/hosts中未找到,则需要查询DNS。为此,系统需要读取/etc/resolv.conf文件来确定使用哪个DNS服务器。 - 然后,应用程序会打开一个UDP套接字连接到DNS服务器,发送查询,并等待回复。
只有在成功获得IP地址后,应用程序才能继续下一步:建立到远程系统的TCP连接并进行通信。
因此,我们最初认为的简单请求,当考虑到本地系统上发生的所有这些步骤时,突然看起来不再那么简单了。
本节总结与下节预告
本节课我们一起学习了如何通过系统调用追踪工具(如 ktrace)来剖析一个简单网络请求在本地系统上的执行过程。我们看到了主机名解析的完整链条,涉及 /etc/nsswitch.conf、/etc/hosts 和 /etc/resolv.conf 等关键配置文件。

记住,我们最初还使用 tcpdump 捕获了网络数据包,但尚未分析它们。在下一个视频中,我们将弥补这一点,详细追踪这些网络数据包的旅程。
在进入下一个视频之前,这里有一些练习供你尝试:

以下是不同系统上的实践练习:
- 在Ubuntu Linux、Alpine Linux和OmniOS实例上重复此练习。在这些系统上,你需要使用不同于
ktrace的工具(ktrace是BSD工具)。在Linux系统上,你应该能找到strace命令;在OmniOS上,是dtrace命令。它们的行为略有不同,但都提供相同的信息。 - 每个操作系统可能使用不同的配置文件,请确保你能追踪主机名解析和连接建立的具体过程。
- 在我们的例子中,看到了连接到IPv4和IPv6系统的连接。比较一下纯IPv6系统的配置会很有趣。配置一个纯IPv6的实例,看看有什么不同。
- 我们提到主机名查找始于
/etc/hosts。请验证你能否通过在该文件中添加地址来绕过DNS查询,并追踪应用程序的行为。 - 最后,如果你查看
ktrace的输出,可能已经注意到我们向DNS服务器建立了两个独立的连接,但我们只解析了一个主机名,不是吗?这是为什么?
032:一个简单请求的深入分析(第二部分)📡
在本节课中,我们将继续追踪一个简单的HTTP HEAD请求从一个系统到另一个系统的完整过程。上一节我们使用ktrace工具分析了可执行文件的行为,并了解了系统如何将主机名解析为IP地址。本节我们将重点分析通过tcpdump捕获的网络数据包,观察请求在网络上传输的每一个细节。

网络数据包初步观察

首先,我们回顾一下启动tcpdump捕获的命令:
tcpdump -i eth0 -w simple.pcap
命令执行后,我们得到了一个名为simple.pcap的数据包捕获文件。打开文件,我们看到大量在不同IP地址间传输的数据包。
为了进行分析,我们首先需要确认本实例的IP地址。使用ifconfig命令可以查看:
ifconfig
输出显示我们的IPv4地址是10.10.0.47,同时还有两个IPv6地址:一个以fe80开头的链路本地地址和一个以2600开头的全局地址。
识别无关流量:端口扫描

在捕获的数据包中,前两个数据包看起来与我们发起的curl命令无关。它们显示一个来自外部IP地址162.142.125.150的TCP SYN包发往我们地址10.10.0.47的一个随机端口。由于我们没有服务监听该端口,系统回复了一个RST(重置)包。

这实际上是互联网上常见的端口扫描行为。外部系统(在此例中,IP反向解析指向censys-scanner.com域名)会持续扫描公网上的主机,试图发现暴露的服务。有些扫描是恶意的,有些则属于互联网整体态势感知的一部分。通过查询该扫描服务(如censys.io),我们可以了解到它通过扫描发现了我们实例上运行着SSH服务(端口22),甚至能识别出操作系统版本。这提醒我们,任何连接到互联网的系统都会很快被扫描。
分析相关流量:ARP与DNS

过滤掉无关的扫描流量后,我们关注与我们的简单请求真正相关的数据包。
接下来我们看到的是第3到第6个以及第8、9个数据包,它们是ARP(地址解析协议)流量。ARP用于在本地网络(第二层)上根据IP地址查找对应的MAC(物理)地址。
以下是相关的ARP请求和响应:
- 我们的默认网关(
10.10.0.1)询问我们的IP地址(10.10.0.47)对应的MAC地址。 - 我们询问默认网关
10.10.0.1的MAC地址。 - 我们询问DNS服务器
10.10.0.2的MAC地址。
双方交换ARP应答后,会更新各自的ARP缓存,从而能够用正确的目标MAC地址封装以太网帧进行通信。
在获得DNS服务器的MAC地址后,我们的系统就可以向其发送DNS查询了。DNS通常使用UDP协议,端口为53。在我们的抓包中,可以看到:
- 系统向DNS服务器
10.10.0.2:53发送一个查询,请求www.yahoo.com的A记录。 - DNS服务器回复一个答案,包含CNAME记录和多个A记录(IPv4地址)。
- 系统随后又发送了一个针对
www.yahoo.com的AAAA记录(IPv6地址)查询。 - DNS服务器同样回复了包含IPv6地址的答案。
建立TCP连接与HTTP通信
获得目标服务器的IP地址(包括IPv6地址)后,由于目标不在本地网络,我们需要通过默认网关10.10.0.1来路由数据包。
接下来是经典的TCP三次握手,用于建立与Yahoo服务器的连接:
- SYN: 我们的系统(使用全局IPv6地址)向目标服务器的80端口发送一个设置了SYN标志的数据包。
- SYN-ACK: 远程服务器确认(ACK)我们的SYN包,并同时发送自己的SYN包。
- ACK: 我们回复一个ACK包,确认服务器的SYN。至此,TCP连接建立。

连接建立后,开始传输HTTP数据:
- 我们的系统发送一个
PUSH标志置位的数据包,内容为ASCII字符串:HEAD / HTTP/1.0\r\n(共17字节)。 - 远程服务器ACK确认收到这17字节(序列号显示为1-18)。
- 我们发送一个空行(
\r\n,2字节)表示HTTP头结束。 - 服务器ACK确认这2字节。
- 服务器发送一个234字节长的HTTP响应,以
HTTP/1.0 200 OK开头。 - 由于我们使用的是HTTP/1.0,服务器在发送完响应后主动关闭连接,发送一个设置了FIN标志的数据包。
- 我们的系统ACK确认服务器的数据和FIN包。
- 我们的系统也发送一个FIN包。
- 服务器ACK确认我们的FIN包。至此,TCP连接完全终止。
请求流程全景图
通过分析tcpdump的数据,我们可以将整个简单请求的流程可视化:
- 本地ARP:为与DNS服务器通信,先通过ARP获取其MAC地址。
- DNS查询:使用UDP向本地DNS服务器查询
www.yahoo.com的IP地址。 - 网关ARP:为与远程Yahoo服务器通信,需通过默认网关,因此通过ARP获取网关的MAC地址。
- TCP/IP通信:将TCP数据包封装在以太网帧中,目标MAC地址设为网关。网关负责将数据包路由至互联网,最终到达Yahoo网络的网关,并转发给正确的Web服务器。
- HTTP交互:服务器处理HEAD请求并返回响应。
- 连接终止:完成HTTP/1.0会话后,关闭TCP连接。
图中,黄色箭头代表ARP流量,蓝色箭头代表DNS流量,黑色箭头代表HTTP/TCP流量。
网络协议栈回顾
这个简单的请求穿越了多个网络协议层:
- 应用层:使用了HTTP(Web通信)和DNS(域名解析)协议。
- 传输层:使用了TCP(为HTTP提供可靠连接)和UDP(为DNS提供快速查询)协议。
- 网络层:使用了IPv4(与DNS服务器通信)和IPv6(与Yahoo服务器通信)协议。
- 链路层:使用了以太网帧和ARP协议进行本地寻址。

思考与延伸
在结束前,请重新审视我们捕获的tcpdump数据,有两个细节值得思考:
- 抓包显示,在向DNS服务器查询之前,就已经出现了与默认网关的ARP请求。但在我们的示例讲解顺序中,ARP请求发生在获得DNS响应之后。为什么会出现这种顺序?
- 仔细观察DNS服务器和默认网关对ARP请求的响应速度,这能告诉你关于这个特定网络段布局的什么信息?
这些思考练习表明,即使对于这样一个简单的请求,通过观察数据包,我们也能提取出大量关于网络配置和行为的信息。

在下一节课中,我们将通过分析更多协议层和数据包捕获文件,来进一步巩固这些知识。

本节课中,我们一起深入分析了一个简单HTTP请求背后的完整网络数据包流程。我们从识别无关的端口扫描开始,逐步剖析了ARP地址解析、DNS域名查询、TCP三次握手、HTTP协议交互以及连接终止的全过程。通过tcpdump这个强大工具,我们得以窥见数据在网络中穿梭时经过的每一层协议封装与处理,深刻理解了“简单”请求背后并不简单的网络通信原理。
033:ARP与NDP协议详解 🖧
在本节课中,我们将学习网络层中两个关键的地址解析协议:用于IPv4的地址解析协议(ARP) 和用于IPv6的邻居发现协议(NDP)。我们将通过实际操作和抓包分析,理解它们如何将IP地址转换为MAC地址,以及两者在设计和行为上的核心差异。
概述
上一节我们介绍了网络基础,本节我们将深入探讨数据链路层与网络层交互的关键环节:地址解析。无论是IPv4还是IPv6网络,设备都需要知道目标IP地址对应的物理(MAC)地址才能进行本地通信。我们将通过分析tcpdump抓取的真实网络数据包,来直观地理解ARP和NDP的工作机制。
ARP:IPv4的地址解析协议
ARP用于在IPv4网络中,根据目标IP地址查询其对应的MAC地址。
ARP缓存表

在分析协议行为前,我们先查看系统的ARP缓存。ARP缓存存储了已知的IP到MAC地址的映射,可以加速通信。
在典型的系统中,使用 arp -a 或 ip neigh show 命令可以查看ARP缓存。初始时,如果网络中没有其他活跃设备,缓存可能为空或条目很少。
为了进行有效分析,我们需要在一个有多台设备的活跃网络中进行操作,并需要超级用户权限来刷新缓存和抓取网络包。
实战分析:捕获ARP流量
以下是通过一系列命令捕获并分析ARP流量的过程:
- 启动抓包:使用
tcpdump捕获所有ARP流量。sudo tcpdump -i eth0 arp -w arp_capture.pcap - 刷新ARP缓存:清空现有的ARP条目,强制系统重新进行地址解析。
sudo ip neigh flush all - 生成网络流量:向一个已知或未知的IPv4地址发送数据包(例如使用
ping),以触发ARP请求。ping -c 1 192.168.1.1 - 查看更新后的ARP缓存:
ip neigh show - 停止抓包并分析:分析捕获的
arp_capture.pcap文件,可以看到大量ARP报文。
解读ARP报文
在抓包结果中,主要看到两种ARP报文:
- “Who has”请求(广播):当主机A需要与主机B(IP地址已知,MAC地址未知)通信时,它会发送一个ARP请求报文。这个报文的目标MAC地址是广播地址
FF:FF:FF:FF:FF:FF,询问“谁有IP地址B?请告诉A”。交换机会将这个广播帧发送给同一广播域内的所有设备。主机A -> 广播域所有主机: “Who has 192.168.1.100? Tell 192.168.1.50” - “Is at”回复(单播):只有拥有该IP地址的主机B会直接向主机A的单播MAC地址回复一个ARP应答,告知自己的MAC地址。
主机B -> 主机A: “192.168.1.100 is at aa:bb:cc:dd:ee:ff”
此外,还可能观察到一种特殊的报文:
- 无故ARP:这是一种未经请求的ARP回复。主机主动广播自己的IP-MAC映射,通常发生在新接口启用或IP地址变更时,用于更新网络中其他设备的ARP缓存或检测IP地址冲突。
主机C -> 广播域所有主机: “192.168.1.200 is at aa:bb:cc:11:22:33” (但没人问过)
ARP过程可视化总结
- 请求阶段(广播):源主机发送
ARP Request到MAC: FF:FF:FF:FF:FF:FF,询问目标IP的MAC地址。 - 响应阶段(单播):目标主机收到请求后,向源主机发送
ARP Reply。 - 特殊公告(广播):主机可主动发送
Gratuitous ARP进行公告或冲突检测。

需要注意的是,在复杂的网络环境中(如一个VLAN内配置了多个子网),你可能会观察到来自你所在子网之外的IP地址的ARP请求,因为它们仍处于同一个广播域中。
从IPv4 ARP过渡到IPv6 NDP

以上我们详细了解了IPv4环境下的ARP协议。那么,在IPv6世界中,地址解析是如何进行的呢?IPv6不再使用ARP,而是通过邻居发现协议(NDP) 来实现类似及更丰富的功能,NDP是ICMPv6协议的一部分。
NDP:IPv6的邻居发现协议
NDP使用ICMPv6报文类型来管理邻居关系,其设计更加高效和安全。
实战分析:捕获NDP流量
让我们用类似的方法分析NDP:
- 启动抓包:捕获ICMPv6流量(NDP使用ICMPv6)。
sudo tcpdump -i eth0 icmp6 -w ndp_capture.pcap - 刷新NDP邻居表:
sudo ip -6 neigh flush all - 生成IPv6流量:触发邻居发现过程。
ping6 -c 1 fe80::1%eth0 # 向链路本地地址发送ping - 查看NDP邻居表:
ip -6 neigh show - 分析抓包文件:观察
ndp_capture.pcap中的ICMPv6报文。
解读NDP报文
IPv6主机通常拥有多个地址:链路本地地址(以fe80::/10开头)和全球单播地址。NDP的核心交互如下:
- 邻居请求(Neighbor Solicitation, NS) - “Who has”:当主机A需要解析主机B的IPv6地址时,它不会广播,而是向一个特定的被请求节点多播地址发送NS报文。这个地址由前缀
FF02::1:FF00:0/104加上目标IPv6地址的低24位组成。只有对应IPv6地址匹配这些低24位的主机才会监听这个多播地址。主机A -> 被请求节点多播地址: “Who has 2001:db8::1?” - 邻居通告(Neighbor Advertisement, NA) - “Is at”:目标主机B收到NS后,会直接向主机A的单播地址回复NA报文,告知自己的MAC地址。
主机B -> 主机A: “2001:db8::1 is at aa:bb:cc:dd:ee:ff”
与ARP的广播相比,NDP的多播机制大幅减少了无关主机的处理开销。
NDP也有类似“广播”所有主机的机制:
- 所有节点多播地址:地址
FF02::1是一个链路本地范围内的所有节点多播地址。向此地址发送报文(如ping6 FF02::1),链路本地广播域内的所有IPv6主机都会收到并可能回复,这类似于IPv4中向广播地址发送数据包。这可以用来快速发现网络中的邻居,并会在本地NDP缓存中生成大量条目。
NDP过程可视化总结

- 请求阶段(多播):源主机向被请求节点多播地址发送
Neighbor Solicitation。 - 响应阶段(单播):目标主机向源主机发送
Neighbor Advertisement。 - 主动通告:主机可以发送未经请求的NA(类似于无故ARP)来宣告自身变化。
- 全局发现:向
FF02::1(所有节点多播地址) 发送报文,可与链路上所有主机交互。
一个关键区别是:NDP运行在网络层,使用ICMPv6报文;而ARP独立于IP,运行在链路层之上。
总结
本节课中,我们一起深入学习了两种地址解析协议:
- 对于IPv4,我们探讨了地址解析协议(ARP,RFC 826)。它使用广播请求(
Who has)和单播回复(Is at)来解析地址,并可通过无故ARP进行主动公告。 - 对于IPv6,我们探讨了邻居发现协议(NDP)。它使用ICMPv6,通过向被请求节点多播地址发送邻居请求(
Neighbor Solicitation),并接收单播的邻居通告(Neighbor Advertisement)来完成解析。这种方式比广播更高效。同时,FF02::1地址提供了链路范围内的“所有主机”通信能力。

理解ARP和NDP对于诊断二层网络连接问题至关重要。请务必在你自己的系统上使用 tcpdump 或 Wireshark 工具进行抓包实践,观察这些协议的真实交互过程。在接下来的课程中,我们将更频繁地使用 tcpdump 来分析其他网络协议。
034:ICMP 🔍
概述
在本节课中,我们将学习互联网控制报文协议。我们将通过分析两个最常用的ICMP应用——Ping和Traceroute——来理解其工作原理,并观察它们在IPv4和IPv6环境下的具体表现。
ICMP简介
上一节我们介绍了网络协议的基础,本节中我们来看看ICMP。ICMP是互联网控制报文协议,用于在IP主机和路由器之间传递控制消息。它对于网络诊断和故障排除至关重要。
Ping:网络连通性测试
Ping是用于测试网络连通性的基本工具。它通过发送ICMP回显请求报文并等待回显应答来工作。
以下是使用tcpdump捕获和分析Ping数据包的基本步骤:
- 在主机上启动
tcpdump,捕获IPv4 ICMP数据包:sudo tcpdump -i any icmp - 使用
ping命令向目标地址(例如www.yahoo.com)发送三个回显请求:ping -c 3 www.yahoo.com - 观察
tcpdump的输出,可以看到主机发送的每个回显请求都触发了来自远程服务器的回显应答。
ICMP回显请求/应答数据包格式相对简单。根据RFC定义,其结构包含:
- 类型:8表示回显请求,0表示回显应答。
- 代码:对于此类消息,通常为0。
- 标识符:用于匹配请求与应答。
- 序列号:用于标识报文序列。
- 数据:可选的数据部分。

数据部分可用于在故障排除时更好地识别和跟踪数据包。例如,可以使用-p标志在ping命令中填充特定数据:
ping -c 1 -p 48656c6c6f www.example.com
使用tcpdump -X查看完整数据包时,可以看到填充的数据。这虽然通常无害,但也展示了协议可能被用于非预期用途(如数据隐蔽传输),因此理解协议细节很重要。

IPv6中的Ping
无论使用IPv4还是IPv6,Ping的工作原理基本相同。在IPv6中,ICMPv6同样用于回显请求和应答。

以下是捕获和分析IPv6 Ping数据包的步骤:
- 捕获ICMPv6回显请求和应答数据包:
sudo tcpdump -i any icmp6 and \"icmp6[0] == 128 or icmp6[0] == 129\" - 运行IPv6 Ping命令:
ping6 -c 3 ipv6-target-host - 观察到的数据包交换与IPv4类似:发送回显请求,目标回复回显应答。
Traceroute:路径追踪
Ping告诉我们目标是否可达,但无法揭示数据包经过的路径。这正是Traceroute的用途。它用于发现数据包从源系统到目标系统所经过的路由路径。
Traceroute巧妙地利用了IP数据包的生存时间字段。TTL决定了数据包在被丢弃前能经过的最大路由器跳数。
以下是Traceroute在IPv4中的工作原理:
- 首先发送一个TTL设置为1的UDP数据包(目标端口通常是一个高端口号,如33435)。
- 第一个路由器将TTL减1,发现TTL变为0,于是丢弃该数据包,并向源主机发送一个ICMP超时消息。这样我们就知道了第一个路由器的IP地址。
- 接着发送一个TTL设置为2的UDP数据包。第一个路由器将TTL减为1并转发,第二个路由器将TTL减为0,丢弃并发送ICMP超时消息。我们从而得知第二个路由器。
- 重复此过程,每次将TTL加1,直到数据包到达目标主机。
- 目标主机收到UDP数据包后,发现没有进程监听该随机端口,于是回复一个ICMP目标不可达(端口不可达)消息。这标志着路径追踪完成。

可以使用tcpdump观察这个过程:
sudo tcpdump -v -i any \"icmp or udp\"
同时,在另一个终端运行Traceroute(限制发送单个探测包以便观察):
traceroute -q 1 -N 1 www.yahoo.com
IPv6中的Traceroute
在IPv6中,TTL被称为跳数限制,但原理完全相同。
以下是IPv6 Traceroute的工作流程:
- 发送H.Limit设置为1的数据包,触发第一跳路由器的“超时”消息。
- 发送H.Limit设置为2的数据包,触发第二跳路由器的“超时”消息。
- 依此类推,直到数据包到达目标,触发“目标不可达”消息。
为了在捕获时过滤掉IPv6中其他的ICMPv6流量(如邻居发现),可以指定只捕获回显请求和应答类型:
sudo tcpdump -i any \"icmp6[0] == 128 or icmp6[0] == 129 or icmp6[0] == 3\"
其他注意事项与探索
Traceroute不仅可以使用UDP,也可以使用ICMP或TCP报文,以防路径中的设备阻塞UDP。
ICMP的用途远不止Ping和Traceroute。例如,路径MTU发现也使用ICMP来确定到达目标而不需要分片的最大数据包大小。其工作原理与Traceroute类似。
在实际互联网中运行Traceroute时,可能会遇到路径中显示星号*或最终超时无法到达目标的情况。思考一下在什么情况下会发生这些现象。
此外,我们可以查找路径中网络节点的自治系统号。当从AWS等云服务商进行追踪时,可能会发现许多节点被标记为属于AS16509(Amazon)。研究该AS的IP地址分配位置,可以解释为何会看到这些路由节点。

总结
本节课中我们一起学习了互联网控制报文协议。我们深入探讨了Ping和Traceroute这两个核心工具,观察了它们如何利用ICMP报文来测试连通性和发现网络路径,并比较了它们在IPv4和IPv6环境下的实现。理解ICMP有助于我们进行有效的网络诊断,并明白为何完全阻塞ICMP通常不是一个好主意。在后续课程中讨论应用层协议时,我们还会再次见到UDP和TCP的身影。
035:域名系统(第一部分)🌐
概述
在本节课中,我们将要学习互联网基础设施的一个关键组成部分——域名系统(DNS)。我们将回顾其历史背景,理解其逻辑结构,并探讨其核心概念,为后续深入学习DNS的工作原理打下基础。
从主机名到IP地址的挑战
上一节我们介绍了TCP/IP协议栈和网络连接的细节。本节中我们来看看,在实际通信中,我们通常如何开始。
在之前的几乎所有例子中,我们都是从一个主机名开始,而不是直接使用IP地址。也就是说,我们所有的通信都始于将主机名转换为IP地址的查询过程。这个过程是通过DNS完成的。顺便提一句,我们说“DNS”,而不是“DNS系统”,因为后者会变成“域名系统系统”,就像说“ATM机”一样。无论如何,DNS是互联网基础设施的关键部分,也是系统管理员需要理解的基本组件之一。这主要是为了让你意识到,在漫长而令人困惑的故障排除之后,当一切都不合理时,问题很可能出在DNS上。
或者,也可能是有人修改了 /etc/hosts 文件,这从来、一次都没有不反过来困扰你。所以不要那样做。但我们有点超前了。
为了更好地理解DNS,让我们倒回并回到互联网的早期。
早期互联网的命名问题
如果你只有两台需要连接的主机。你真正需要的只是每台主机中的网络接口卡。用一根电缆把它们连接起来,你就可以知道如何到达另一台系统,而无需查找地址或任何其他东西。

但是,当然,一旦你添加了其他几台系统,你就遇到了问题。你如何知道如何到达每台系统?我们给它们分配IP地址。
但现在你有了一个问题。你必须记住,左上角那台你想与之通信的系统,其IP地址是 198.51.100.195。而另一台主机B的地址是 192.0.2.80,依此类推。人们非常不擅长记住数字。特别是随着互联网的发展,这成了一个不可扩展的解决方案。
你在这里看到的是早期ARPANET的地图,当时最初的站点被连接起来,网络开始增长。在那个时代,我们如何跟踪哪些主机位于何处?斯坦福研究所的主机如何知道它想与之通信的麻省理工学院系统的地址?
/etc/hosts 文件的起源
让我们回忆一下我们的系统是如何查找这些信息的。在我们之前的视频中,你会记得我们的工具通过 /etc/nsswitch.conf 来了解如何将主机名解析为IP地址。默认情况下,我们首先查看 /etc/hosts 文件。该文件可能包含一组IP地址到主机名的映射。

但等一下。这难道不构成一个先有鸡还是先有蛋的问题吗?我们最初是如何知道要放入该文件的信息的呢?
让我们再看一下手册页。在这个系统上,手册页仍然包含这一段,其中指出这个文件实际上可能源自中央网络信息中心(NIC)。有趣的是,就在上周,这个注释从NetBSD的手册页中被删除了,非常巧合的是,就在我准备这个视频讲座时,恰好提到了主机数据库的话题。
总之,这个手册页告诉我们,/etc/hosts 文件过去来自NIC,意思是,是的,我们确实曾经有一个中央的 hosts 文件,包含了互联网上所有主机的所有IP地址,我们复制这个文件,每当有新主机连接到互联网时就更新它。

主机数据库的演变
这个主机数据库最早的文档之一是RFC 597。它看起来是这样的。在1973年12月,这份文档提供了最新的网络地图。向下滚动,我们会注意到系统的地址是以八进制和十进制列出的,不是IP地址,但包含了关于它们位置的信息,以及它们是什么类型的系统。你会在这里找到一大堆PDP和旧的IBM系统。

过了一段时间,随着网络的发展,很明显这里使用的格式不再合适,新的RFC 810在1982年发布。作者是Elizabeth Feinler(我们稍后会再谈谈她)等人。这份RFC定义了DD主机表,它不仅包含关于主机或系统的信息,还包含关于网络、网关和远程系统操作系统的信息。这份文档包含了关于从哪里可以获取这个主机表的信息。即,斯坦福研究所的网络信息中心(SRI-NIC),通过匿名FTP。
现在的格式看起来是这样的。条目名为 net、gateway 或 host。你仍然可以找到一些历史主机表。我在这个视频片段的幻灯片末尾包含了一个链接。这是1985年的一个副本,向你展示当时的互联网是什么样子。
这里没有包含关于系统的所有详细信息。所以我们在这里看到一大堆网络定义。然后是一些网关系统,指示哪些主机桥接哪些网络。在这里,有主机条目,给你IP地址、操作系统以及主机提供的服务。让我们看看1985年互联网上有多少台主机。看起来当时的互联网由1325台主机组成。
所以,在那个时代,这就是我们解析名字的方式,或多或少。所有地址都是手动分配的。这些信息的管理由斯坦福研究所的网络信息中心负责,由Elizabeth Joscelyn Feinler(我们刚才在之前的RFC中看到了她的名字)领导。Feinler和John Postel(我们在之前的视频中遇到过)基本上在那个时候运行着互联网。如果你想连接一台新主机,你必须打电话给SRI-NIC,要求他们给你分配地址,然后更新主机数据库并发布,所有其他系统都必须获取新副本。

在那段时间,Elizabeth Feinler提出了使用域来根据功能对名称进行分组的概念,例如,使用 .edu 代表教育机构。但是,当然,到处复制文本文件显然不是一个可扩展的解决方案。所以,John Postel、Paul Mockapetris在1983年的RFC 882和883中提出了一个域名系统的提案。
DNS的诞生与结构
加州大学伯克利分校的四名研究生在1984年首次实现了这个提案,作为一个Unix名称服务器,名为伯克利互联网名称域(BIND)。BIND至今仍是互联网上使用最广泛的DNS服务器,目前由互联网系统联盟(ISC)维护。
域名系统基于域名空间的概念,采用树状的层次结构,包含所谓的域名。这棵树植根于一个节点,简单地称为点(.)。并被细分为各个区域。每个区域本身可能由一个或多个域组成,这些域本身可能包含子域,依此类推。
根节点正下方的域被称为顶级域(TLD)。然后它们被进一步划分。成为二级域,二级域又可以划分为三级域,依此类推。
但是谁控制一个给定区域如何划分呢?这种决策权被称为对该区域拥有权威。这种权威不是集中的,但可以在一个区域内委托。现在,由于整个域名树植根于点(.)根区域,这个区域必须将顶级区域的权威委托出去。然后,顶级区域将各个二级域的权威委托给合法的运营商。
请注意,一个区域可以进一步以它认为合适的任何方式委托权威。例如,在 .edu 顶级域下拥有 stevens.edu 区域权威的史蒂文斯理工学院,可以决定将 cs.stevens.edu 子域的权威委托给另一个实体。
接下来,在这棵树中,每个节点必须有一个标签,一个名称,并且每个节点可能有与之关联的附加信息。你可以为给定节点关联许多不同的信息,但它不是完全自由形式的。相反,我们定义了许多所谓的资源记录类型来描述信息。
资源记录类型
最常见的资源记录当然是通过A和AAAA记录关联IP地址,但我相信你们也都熟悉CNAME或规范名称资源记录,它的工作方式有点像文件系统中的符号链接,因为它仅仅指向DNS树中的另一个节点。
但还有其他类型。我们将在未来的视频中再次看到的一个资源记录是MX记录,它定义了负责该域的邮件服务器。但让我们回到DNS,我们有NS记录,它定义了负责给定区域的名称服务器,或者像与DNSSEC相关的记录,DNSSEC是DNS的扩展,用于添加加密认证。

构建域名
当我们构建域名时,我们只需沿着树向上走,用点连接节点的标签。例如,我们得到 www.cs.stevens.edu。为了使这个名称成为一个完全限定域名(FQDN),需要附加最后一个标签,即根的点(.)。这个尾随的点向解析库发出信号,表示不应再尝试向上遍历树。但是,当然,大多数人省略了这个点。而且有相当多的系统在代码实现上存在问题,当你实际输入一个尾随点时,它们会中断或以意想不到的方式运行。
或者,你可能能够通过使用域的FQDN来绕过付费墙。顺便说一句,这只是软件开发人员不理解DNS时可能发生的比较无害的事情之一。
顶级域(TLD)概览
让我们快速看一下我们拥有的顶级域。最初,RFC 920定义了以下内容:
.com用于商业用途。我们使用yahoo.com作为例子,因为雅虎是一个商业实体。.edu用于教育用途。我们使用stevens.edu作为例子,原因很明显。.gov用于政府用途。.mil用于军事用途。.org用于任何其他组织,如今通常用于非营利组织,或者用于区分商业域名。
然后是 .arpa,它本应临时用于ARPANET管理目的。但正如你们已经从关于临时IPv4空间的讨论中知道的那样,“临时”很少是临时的。
但正如你们所知,我们现在还有其他许多TLD。我们有国家代码特定的TLD,例如 .de 代表德国,.fr 代表法国,.ar 代表阿根廷等等。如今还包括一些国际化的国家代码TLD,例如这里显示的埃及、香港和欧盟使用的西里尔字母TLD。


然后我们有许多国际化的通用TLD。所谓的赞助TLD,由一个狭窄的社区支持,但不是例如单个商业实体。这里我们展示了代表加泰罗尼亚语言和文化社区的 .cat TLD,.jobs 用于人力资源经理,或者 .xxx 作为色情网站的自愿选项,尽管当然它们通常也使用 .com 运营。
除此之外,当ICANN宣布任何人都可以提议并成为通用顶级域的赞助商时,我们还获得了一大堆新的通用TLD,这导致了许多词语成为TLD的爆发。
TLD的数量与管理
那么总共有多少个TLD呢?让我们看看。我们可以从 internic.net 获取顶级根区域文件。那个文件看起来是这样的。我们看到各种记录,包括根名称服务器的资源记录。当我们滚动浏览这个文件时,我们找到了所有不同TLD的名称服务器。因为这是这些信息必须保存的地方。
所以让我们提取所有的NS记录条目。现在让我们数一数我们找到了多少个唯一的TLD。答案是,截至2021年3月16日,有1504个TLD。
我们如何管理所有这些域?记住,我们在第9层(应用层)操作,控制DNS似乎有一些相当明显的影响。
DNS的管理与安全考量
首先,如果你关注了我们最近的几个视频,这真的不会让你感到惊讶:我们发现ICANN管理根区域,以及关键的基础设施区域 .arpa 和 .int。所有其他域,即所有TLD的管理,被委托给所谓的域名注册管理机构。对于GTLD,有特定的GTLD注册管理机构,每个国家都有自己的国家代码TLD来管理,所以他们有自己的注册管理机构。
域名注册管理机构然后可以将名称注册外包给域名注册商,他们授权注册商以确保遵守其规则和要求,这意味着注册管理机构本身可以是注册商,也可以委托这个职能。虽然注册管理机构控制分配政策,例如对其TLD内域名的使用施加限制。例如,我们刚才提到的 .cat 域实际上并不是用于互联网上的猫咪图片,而是用于推广加泰罗尼亚语言和社区,所以你不能随机注册 .cat 下的域名,除非你的页面是加泰罗尼亚语的。
这里需要注意的一点是,注册管理机构确实控制其域内的整个命名空间,如果他们不喜欢你的网站,他们有权让它消失。例如,.ly TLD是许多网站的热门选择,但这个域实际上是利比亚的国家代码TLD,而利比亚并不是世界上最进步的国家。几年前有一个案例,成人博客作者Violet Blue注册了域名 vb.ly,但利比亚政府认为其内容不符合伊斯兰教法,因此将其关闭。


所以,当你选择域名时,你可能要确保你不会受到一个与你的原则不符的国家或组织的规则约束。当你想要抓取一个听起来很酷的、以另一个国家TLD结尾的名字时,例如 .ly 或 .io(这是英属印度洋领地的CCTLD),这一点特别容易被遗忘。
这是一个需要记住的好事情,因为DNS命名空间是一棵树。如果你只控制一个分支,你就控制该特定分支下的所有子树和节点。这意味着,如果你控制了 .com 的注册管理机构或注册商,那么你就控制了其下的所有其他节点。因此,确保你的NS记录不被破坏是至关重要的。如果我能诱骗你将它们指向我的名称服务器,那么我就控制了你的整个区域。
总结
好了,我想在这一点上,我们可以休息一下。我们已经了解了DNS的历史和逻辑结构,接下来我们将深入网络数据包,开始追踪DNS请求,以更好地理解我们是如何遍历这棵树的。在那之前,感谢观看,并确保准备好使用你的TCP工具。
036:域名系统(第二部分)🌐
在本节课中,我们将深入学习域名系统(DNS)的实际工作过程。我们将通过分析网络数据包,来理解域名解析的完整步骤,并区分权威名称服务器与缓存解析器之间的不同。
上一节我们介绍了DNS的历史背景和层次结构。本节中,我们来看看域名解析的具体过程。
数据包分析:一次简单的DNS查询

我们首先进行一次简单的DNS查询,并捕获网络数据包来观察其过程。
我们知道DNS默认使用端口53。我们开始捕获数据包,然后执行一个简单的DNS查询。
nslookup www.yahoo.com

查询结果显示,使用的名称服务器是10.10.0.2,这与我们之前在/etc/resolv.conf中配置的一致。同时,结果被标记为非权威。这是因为我们查询的名称服务器并非负责该区域的权威服务器。
现在,让我们查看捕获到的数据包。

过程看起来并不复杂。我们看到一个向名称服务器查询A记录的请求包,以及它返回的响应包。接着是另一个查询AAAA记录的请求和响应。
使用Wireshark进一步分析第一个数据包,它被识别为一个DNS查询包,包含一个针对A记录的提问。响应包则包含了5个答案资源记录,包括www.yahoo.com的规范名称(CNAME)以及该规范名称解析出的不同IP地址。
第二个AAAA记录的查询结果类似。但请注意,我们查询的是scene的AAAA记录,而非.com的。在数据包的标志位中,我们能看到此响应被标记为非权威。
这个简单的例子已经说明了一些重要方面:
- 权威名称服务器与解析器存在区别。权威服务器提供权威答案;解析器通过询问正确的权威服务器来获取答案。
- 解析器通常会缓存结果一段时间,以避免重复查询权威服务器,因此它们常被称为缓存解析器。
- 这个简单的请求涉及多个独立的查询和多种资源记录类型。
深入解析:追踪权威答案
仅仅观察本地解析器的查询就像看到了“魔法”。为了理解背后发生了什么,我们需要追踪到权威服务器。
我们尝试获取www.yahoo.com的权威答案。首先,我们查询负责该域名的名称服务器。
nslookup -type=NS www.yahoo.com
我们得到回复,www.yahoo.com是一个CNAME,指向new-fp-shed.wg1.b.yahoo.com,并且我们获得了可以找到权威答案的服务器信息:yf1.yahoo.com。
接着,我们直接向yf1.yahoo.com发起查询。
nslookup www.yahoo.com yf1.yahoo.com
这次,答案中没有了“非权威”的标记,因为它直接来自权威服务器。
让我们将这个过程可视化:
- 步骤1: 询问本地解析器
www.yahoo.com的NS记录。 - 步骤2: 为了联系上一步得到的权威服务器
yf1.yahoo.com,需要其IP地址,因此再次询问本地解析器。 - 步骤3: 使用获得的IP地址,直接向权威服务器
yf1.yahoo.com查询,获得权威的A/AAAA记录。
然而,步骤1和2仍然依赖于本地解析器这个“黑箱”。如果我们自己作为解析器,该如何找到所有答案?
扮演解析器:完整的迭代查询过程
回忆上一节学习的域名空间树状结构。要找到www.yahoo.com,我们需要自顶向下进行迭代查询。
以下是完整的步骤:
- 查询根服务器: 询问根名称服务器
.com顶级域(TLD)的权威服务器是谁。现代解析器通常使用“DNS查询名称最小化”技术,只发送_ .com这样的查询以保护隐私。根服务器会回复负责.com的TLD服务器列表及其IP地址。 - 查询TLD服务器: 选择其中一个
.comTLD服务器,询问yahoo.com的权威服务器是谁。TLD服务器会回复,例如ns1.yahoo.com等,并同样提供它们的IP地址。 - 查询域权威服务器: 向
ns1.yahoo.com查询www.yahoo.com。它回复这是一个CNAME,指向new-fp-shed.wg1.b.yahoo.com,并且该子域由yf1.yahoo.com负责(这展示了区域委派)。 - 查询子域权威服务器: 最后,向
yf1.yahoo.com查询new-fp-shed.wg1.b.yahoo.com的IP地址,获得最终的权威答案。
缓存解析器在首次查询时会执行上述所有步骤,并将结果缓存。后续对同一域名的查询就可以直接询问缓存或跳转到已缓存的权威服务器,从而大幅提高效率。
实践观察:搭建缓存解析器
为了完整观察上述迭代查询过程,我们需要将我们的实例变成一个缓存名称服务器。
在Ubuntu系统上,这很容易实现:
- 启用
systemd-resolved服务。 - 更新
/etc/resolv.conf,使用127.0.0.1作为解析器,不再使用默认的10.10.0.2。 - 启动
systemd-resolved服务。 - 清空现有缓存(例如使用
systemd-resolve --flush-caches)。 - 开始捕获数据包。
- 执行
nslookup www.yahoo.com。

此时,nslookup会询问本地的缓存解析器。我们捕获到的数据包将完整展示从根服务器到TLD服务器,再到yahoo.com权威服务器的所有迭代查询过程。

在输出中,你可以识别出我们讨论过的各种查询:对根服务器的查询、对TLD服务器的查询以及对Yahoo名称服务器的查询。请注意数据包中混合使用了UDP和TCP协议。虽然DNS主要使用UDP 53端口,但在响应数据过大或进行区域传输等情况下会使用TCP。
此外,我们遗留了一个关键问题:解析器最初是如何知道根名称服务器的地址的?这些至关重要的互联网基础设施是如何被发现和维护的?这将是我们下一个视频要讨论的主题。
本节课中我们一起学习了DNS查询的实际工作流程,区分了权威服务器和缓存解析器,并通过数据包分析和迭代查询步骤拆解了域名解析的“魔法”。我们还实践搭建了一个缓存解析器来观察完整的查询链。理解这个过程是进行高效系统管理和故障排查的基础。
037:域名系统(第三部分)🎯
在本节课中,我们将完成对域名系统(DNS)的讨论。我们将探讨DNS的根服务器、资源记录、反向查询以及DNS安全扩展(DNSSEC)等核心概念。虽然DNS是一个庞大的主题,无法在短时间内详尽覆盖,但本节旨在提供一个足够全面的概述,使你能够基于对底层基础设施的理解,排查常见的DNS相关问题。
根服务器与引导过程🔍
上一节我们详细追踪了一个完整的DNS解析过程。我们提到查询始于根服务器,但我们当时“作弊”了,因为我们没有解释如何知道根服务器的位置。本节中,我们来看看这个初始的“魔法”步骤是如何实现的。
根服务器本质上也是权威名称服务器,只不过它负责的区(zone)是整个域名空间的根。因此,我们可以像查询其他域名一样,通过DNS查询根服务器本身的信息。
以下是使用 dig 工具查询根服务器NS记录的示例:
dig NS .
查询结果会返回13条NS记录,对应从 a.root-servers.net 到 m.root-servers.net 的根服务器。同时,响应中还包含了这些服务器的IP地址(包括IPv4和IPv6)。选择13这个数字,是因为它恰好能容纳在一个512字节的标准UDP数据包内。
这引出了一个“先有鸡还是先有蛋”的问题:为了查询根服务器的信息,我们首先需要知道根服务器是谁。因此,每个新部署的名称服务器都需要一个初始的引导文件来获取根服务器信息。
在BIND(一个常用的DNS软件)系统中,这个文件通常是 namedb.root 或 root.hints。该文件包含了所有根服务器的NS和A记录。服务器启动时会读取此文件,然后立即通过DNS重新查询根服务器信息并缓存,从而可能覆盖文件中的旧信息。这个文件可以从互联网(如InterNIC)获取,其内容更新并不频繁。
根服务器的分布与任播🌐
只有13台服务器作为整个互联网DNS的基石,这听起来似乎很脆弱。但实际上,“13个根服务器”指的是13个根服务器管理机构(从A到M),每个机构都通过任播技术在全球部署了数十甚至数百台服务器实例。
- 管理机构:这13个根服务器由12个独立的国际组织运营,以确保没有任何单一国家能完全控制域名空间。
- 任播技术:任播允许多台服务器使用相同的IP地址。网络路由协议会将查询引导到拓扑结构上“最近”的服务器实例。这既提供了冗余,也降低了查询延迟。
- 全球分布:你可以在
root-servers.org上看到根服务器实例的全球分布图。它们大多部署在网络枢纽和互联网交换中心,以确保良好的连通性。
例如,你可以通过查询特定记录来确定你正在与哪个地理位置的F根服务器实例通信:
dig +short TXT hostname.bind @f.root-servers.net chaos
此查询可能会返回包含机场代码(如DFW)的文本记录,指示该服务器实例的大致地理位置。
DNS资源记录详解📝
DNS是一个分布式数据库,它通过不同类型的资源记录来存储信息。每条记录都有一个生存时间,它规定了缓存服务器可以保存该记录的时间长度。
以下是一些关键的资源记录类型:

- A / AAAA记录:将主机名映射到IPv4或IPv6地址。
- NS记录:指定负责某个区的权威名称服务器。
- SOA记录:起始授权记录。它定义了区的基本信息,包括主名称服务器、管理员邮箱、序列号以及辅助服务器同步参数。
- 公式示例:
example.com. IN SOA ns1.example.com. admin.example.com. ( 2023123001 3600 1800 604800 86400 )
- 公式示例:
- CNAME记录:规范名称记录,用于将一个域名别名指向另一个域名。
- MX记录:邮件交换记录,指定负责接收该域邮件的服务器。
- TXT记录:文本记录,可用于存放任意文本信息,常被用于域名所有权验证、邮件安全策略等。
- CAA记录:证书颁发机构授权记录,指定哪些CA可以为此域名颁发SSL/TLS证书。
- RRSIG记录:资源记录签名,用于DNSSEC,对一组资源记录进行数字签名。
- DS记录:委托签名者记录,用于DNSSEC,在父区中注册子区密钥的哈希值,建立信任链。
值得注意的是,一个域的权威名称服务器不一定位于该域下。例如,DNS服务提供商经常将名称服务器分布在多个顶级域中,以增强冗余性。
反向DNS查询与PTR记录🔄
除了将主机名解析为IP地址,DNS还能进行反向查询。反向查询使用特殊的 in-addr.arpa(IPv4)或 ip6.arpa(IPv6)域,并查询 PTR记录。

其工作原理是将IP地址的字节顺序反转,然后追加到相应的 .arpa 域后进行查询。例如,查询IP地址 155.246.x.x 的反向记录,实际上是查询 x.246.155.in-addr.arpa. 的PTR记录。
反向查询的授权委托与IP地址块的分配保持一致。互联网号码分配机构将 155.0.0.0/8 这个IP块分配给了某个组织(例如ARIN),那么ARIN就负责 155.in-addr.arpa 区。如果ARIN将 155.246.0.0/16 这个子块分配给了史蒂文斯理工学院,那么 246.155.in-addr.arpa 这个区就会被委托给史蒂文斯理工学院管理,由他们自行添加PTR记录。
DNS安全与挑战🔒
我们捕获的DNS流量显示,查询和响应默认是通过不加密的UDP(或TCP)协议传输的。这意味着路径上的攻击者可能篡改或伪造响应。为了提供数据来源验证和完整性保护,DNSSEC 被开发出来。
在启用DNSSEC的查询中,响应不仅包含答案,还包含 RRSIG记录(数字签名)和 DS记录(用于建立信任链)。验证签名需要获取对应的公钥(DNSKEY记录),而该公钥本身又由其父区的密钥签名,如此层层递进,直至根区的密钥(这是一个预置的信任锚)。
尽管DNSSEC至关重要,但其部署并不广泛,部分原因是配置复杂且一旦出错可能导致整个域名无法访问。
此外,DNS还面临其他安全与隐私挑战:
- DNS over TLS / DNS over HTTPS:这些协议旨在加密DNS查询内容,防止窃听和中间人攻击,解决的是与DNSSEC不同的威胁模型(隐私 vs. 完整性)。
- 数据泄露:DNS查询本身可能被用于隐蔽的数据外泄通道。
- 配置风险:DNS配置错误可能导致服务中断,且由于缓存和TTL的存在,排查问题可能比较棘手。正如一个经验法则所说:“当一切都很混乱且毫无头绪时,问题很可能出在DNS上。”
- 控制权:DNS是互联网基础设施的基石。如果攻击者控制了你的DNS,他们几乎可以重定向你的所有流量,危害极大。因此,无论是自行管理还是外包给第三方提供商,都需要极其谨慎地对待DNS服务的安全性和可靠性。
总结📚
本节课中,我们一起深入探讨了域名系统的几个核心部分。

我们首先解决了根服务器的“引导”问题,了解了初始 hints 文件的作用。接着,我们揭示了根服务器并非只有13台物理机器,而是通过任播技术实现的全球分布式网络。然后,我们系统性地回顾了各种DNS资源记录及其用途,特别是SOA记录的构成。我们还剖析了反向DNS查询的原理,理解了其如何通过反转IP地址并与 .arpa 域结合来实现。最后,我们触及了DNS安全的核心——DNSSEC,了解了它如何通过数字签名链来验证响应真实性,同时也认识到其部署的挑战。我们还简要提到了DoT/DoH等加密DNS协议以及其他安全考量。
DNS是一个深邃且关键的系统,支撑着互联网的运转。鼓励你使用 dig、nslookup 等工具,并结合 Wireshark 分析网络抓包,亲自探索不同域名的解析过程,这将极大地加深你的理解。
038:第08周第1节 - 电子邮件,第一部分 📧
在本节课中,我们将要学习电子邮件系统的基础知识。我们将从宏观视角了解电子邮件的工作原理,并通过实际操作观察简单邮件传输协议(SMTP)的通信过程。
概述
电子邮件是系统管理员必须理解的一项关键服务。与DNS类似,电子邮件也使用基于文本的简单协议——SMTP。尽管有观点认为即时通讯工具正在取代电子邮件,但数据显示,全球仍有约55亿个电子邮件账户,每天收发约3000亿封邮件。因此,理解其工作机制至关重要。

电子邮件系统架构
上一节我们介绍了电子邮件的普遍性,本节中我们来看看电子邮件系统的核心组件及其工作流程。
一个简单的认知是:用户撰写邮件并点击发送,邮件就会到达收件人的收件箱。但实际情况要复杂得多。
以下是电子邮件传递过程中涉及的主要组件:
- 邮件用户代理:用户用来阅读和撰写邮件的程序。例如命令行客户端、Outlook、Thunderbird或网页邮箱。
- 邮件传输代理:通常所说的“邮件服务器”,负责接收并转发邮件。常见的MTA软件有Postfix、Sendmail和Qmail。
- 邮件投递代理:负责将已接收的邮件进行最终处理,如分类、复制、转发或过滤。Procmail是一个常见的MDA。
- 邮件访问代理:当用户不直接登录邮件服务器时,通过POP或IMAP等协议远程访问邮件的服务。

邮件发送的基本流程是:用户通过MUA撰写邮件,MUA将邮件交给发送方的MTA。发送方MTA查找收件人域名的邮件服务器(通过DNS MX记录),并通过SMTP协议将邮件转发给接收方MTA。接收方MTA可能进行垃圾邮件评估等处理,然后通过MDA将邮件投递到用户的邮箱。最后,用户通过MUA(或经由访问代理)读取邮件。
观察SMTP通信
了解了系统架构后,我们通过一个实际例子来观察SMTP协议的具体交互。
我们在一个EC2实例上使用mail命令发送一封测试邮件,并捕获网络流量。发送过程涉及以下关键步骤:
- DNS查询:发送方MTA首先查询收件人域名的MX记录,以确定负责接收邮件的服务器。
# 示例:查找域的邮件服务器 dig MX example.com - 建立TCP连接:根据查询到的IP地址,向接收方服务器的25端口发起TCP连接。
- SMTP对话:连接建立后,双方进行明文SMTP对话。主要命令包括:
HELO或EHLO:客户端向服务器打招呼。MAIL FROM::指定发件人地址。RCPT TO::指定收件人地址。DATA:开始传输邮件内容(包括邮件头如From:,To:,Subject:和正文)。- 单独一行的
.:表示邮件内容结束。 QUIT:结束会话。

通过分析邮件服务器日志(如/var/log/mail.log),我们可以追踪邮件的处理状态,包括分配的唯一消息ID、投递尝试(可能因缺乏反向DNS记录而被拒绝)以及最终的成功投递。
手动模拟SMTP会话

为了更深入地理解SMTP的简单性,我们可以使用telnet命令手动完成一次邮件发送。

以下是核心步骤的模拟:



- 查找目标域的MX记录和A记录。
- 使用
telnet连接到该邮件服务器的25端口。 - 遵循SMTP协议进行交互:
telnet mail.example.com 25 220 mail.example.com ESMTP HELO myclient.example.com 250 mail.example.com Hello MAIL FROM:<sender@example.org> 250 2.1.0 Sender OK RCPT TO:<recipient@example.com> 250 2.1.5 Recipient OK DATA 354 End data with <CR><LF>.<CR><LF> From: Sender Name <sender@example.org> To: Recipient <recipient@example.com> Subject: Test Manual SMTP This is the body of the email. . 250 2.0.0 OK: queued as ABC123 QUIT 221 2.0.0 Bye
这个过程清晰地展示了SMTP是一种基于请求-响应的简单文本协议。




本节总结
本节课中我们一起学习了电子邮件系统的基础。我们了解到电子邮件并非由单一服务完成,而是由MUA、MTA、MDA等多个组件协作。核心传输协议SMTP工作于TCP 25端口,其通信是明文的,易于观察和调试,但也带来了安全和隐私挑战。

我们观察到邮件发送始于DNS MX记录查询,并通过一系列简单的SMTP命令完成传输。服务器返回的数值状态码(如250表示成功)指导着客户端下一步操作。

然而,这只是电子邮件主题的入门。在接下来的视频中,我们将更深入地探讨邮件的接收过程、使用TLS加密传输、分析邮件头的解剖结构,并讨论如何防范垃圾邮件等滥用行为。
039:Week 08, Segment 2 - 电子邮件传输安全 🔐
在本节课中,我们将继续探讨邮件系统,重点关注邮件在传输过程中的安全问题。我们将学习如何保护电子邮件,使其在传输过程中不被窃听或篡改。
上一节我们介绍了简单邮件传输协议,并观察了邮件如何从客户端发送到邮件服务器。本节中,我们来看看邮件服务器接收邮件时会发生什么,并讨论保护邮件信息在传输中不被拦截或观察的方法。

观察标准SMTP通信
首先,我们通过一个实验来观察标准的、未加密的SMTP通信过程。
以下是实验设置和观察到的步骤:
- 发送邮件:我们像之前一样,使用
telnet命令手动连接邮件服务器并发送一封测试邮件。 - 服务器日志:在邮件服务器上,我们查看日志,可以看到连接建立、DNS查询(如反向PTR查找、MX记录查询)以及邮件被接收和处理(例如,交给SpamAssassin进行垃圾邮件过滤)的全过程。
- 数据包捕获:通过
tcpdump工具,我们可以清晰地看到所有SMTP命令(如HELO,MAIL FROM,RCPT TO,DATA)和邮件内容都以明文形式在网络中传输。
这个实验清楚地表明,基本的SMTP协议是不加密的,通信内容对路径上的任何观察者都是可见的。
启用传输加密:STARTTLS 🛡️
为了解决明文传输的问题,SMTP协议提供了STARTTLS命令,用于将明文连接升级为加密的TLS连接。
以下是使用STARTTLS建立加密连接的步骤:
- 初始问候:客户端连接服务器并发送
HELO命令。 - 服务器能力通告:服务器回复,并在其支持的特性列表中包含
STARTTLS。 - 启动加密:客户端发送
STARTTLS命令。 - TLS握手:服务器确认后,双方进行TLS握手,建立加密通道。
- 加密通信:此后所有的SMTP命令和邮件内容都在加密通道内传输,数据包捕获工具只能看到加密的数据流,无法解析内容。

我们可以使用openssl s_client命令来模拟支持STARTTLS的客户端,并验证连接是否已加密。
openssl s_client -starttls smtp -connect mail.example.com:25
然而,STARTTLS提供的是一种“机会性加密”。这意味着加密是可选的,而不是强制性的,这带来了安全风险。
机会性加密的风险与MITM攻击 👤

“机会性加密”的主要问题在于,攻击者可能发起中间人攻击。
攻击流程如下:
- 攻击者拦截客户端与服务器之间的通信。
- 当服务器回复支持
STARTTLS时,攻击者可以将其从列表中删除,再转发给客户端。 - 客户端误以为服务器不支持加密,便会继续使用明文SMTP通信。
- 攻击者从而能够持续窃听或篡改邮件内容。
因此,仅依赖STARTTLS不足以保证通信安全,我们需要一种机制来强制要求使用TLS。
强制TLS:MTA-STS 📜
MTA严格传输安全是一种通过DNS声明,强制要求对指定域名的邮件传输必须使用TLS的机制。

其工作原理分为两步:
- 策略发现:发送方MTA查询
_mta-sts.example.com的TXT记录,以确认该域名是否使用了MTA-STS策略。
返回示例:dig TXT _mta-sts.example.comv=STSv1; id=20230830T010101; - 策略获取:如果存在策略记录,发送方MTA通过HTTPS获取
https://mta-sts.example.com/.well-known/mta-sts.txt的策略文件。
策略文件示例:version: STSv1 mode: enforce mx: mail1.example.com mx: mail2.example.com max_age: 604800mode字段可以是testing(仅报告问题)、enforce(强制使用TLS,否则中止发送)或none(禁用)。
当模式为enforce时,如果发送方连接收件方服务器但无法成功建立TLS连接,则应中止邮件发送,从而有效抵御降级攻击。
证书验证:DANE 🔒


即使强制使用了TLS,我们仍需验证服务器证书的真实性,以防攻击者使用伪造的证书。DANE协议提供了一种不依赖传统CA体系的证书验证方法。
DANE利用DNSSEC来保证DNS记录的真实性,并通过TLSA记录来关联域名与证书。
工作流程如下:
- 发送方MTA查询收件方服务器(如
_25._tcp.mail.example.com)的TLSA记录。
返回的TLSA记录包含了服务器证书公钥的哈希值(例如SHA-256)。dig TLSA _25._tcp.mail.example.com - 当建立TLS连接时,发送方将服务器提供的证书与TLSA记录中的哈希值进行比对。
- 由于TLSA记录受DNSSEC保护,发送方可以确信该哈希值是真实有效的。如果匹配,则证书验证通过;否则,连接应被视为不安全。
这确保了即使传统的证书颁发机构被攻破,只要DNSSEC是安全的,邮件传输的认证就是可靠的。
总结与面临的挑战 🤔
本节课我们一起学习了保护电子邮件传输安全的关键机制。
我们首先回顾了SMTP明文协议的风险,然后引入了STARTTLS来实现机会性加密。为了克服其可能被降级攻击的弱点,我们探讨了MTA-STS,它通过DNS策略强制要求使用TLS。最后,为了确保连接服务器的真实性,我们介绍了DANE协议,它利用DNSSEC和TLSA记录来验证TLS证书,无需依赖传统的CA体系。
然而,在实际部署中仍面临挑战:
- 严格性与可用性的权衡:将MTA-STS设置为
enforce模式可能导致因配置错误而无法投递邮件,因此许多提供商仍处于testing模式。 - 部署普及度:DANE的广泛应用依赖于DNSSEC的部署,而这尚未完全普及。

尽管存在挑战,理解这些机制对于构建和维护安全的邮件系统至关重要。在下一节课中,我们将探讨如何验证邮件发送者的身份,以保护邮件服务器不被滥用于发送垃圾邮件。
040:Week 08, Segment 3 - 电子邮件,第三部分 - 垃圾邮件防护机制 🔐

在本节课中,我们将深入学习电子邮件系统中的垃圾邮件问题及其防护机制。我们将探讨为何垃圾邮件如此泛滥,并详细介绍几种关键的防护技术,包括SPF、DKIM和DMARC。通过理解这些机制,你将能够更好地配置和管理邮件服务器,以抵御垃圾邮件的侵扰。
概述:垃圾邮件问题与基础协议
上一节我们介绍了使用TLS为SMTP提供传输层加密。这虽然提供了安全性,但并未解决电子邮件的所有问题。本节中,我们将重点探讨一个突出的问题:垃圾邮件。

正如我们在第一封关于电子邮件的视频中提到的,垃圾邮件占所有发送邮件的70%甚至更多。因此,我们需要了解为何批量发送未经请求的邮件如此容易,以及我们有哪些防御机制。
电子邮件结构剖析

首先,让我们剖析一封电子邮件的基本结构。一封邮件不仅仅包含我们看到的正文内容。
以下是电子邮件的主要组成部分:
- 必需头部: 这些头部是邮件投递所必需的。缺少它们,邮件将无法被投递。这包括
From头部(也称为SMTPMAIL FROM命令)、收件人地址以及Date头部。 - 可选头部: 邮件协议本身不要求这些头部,但正如我们将看到的,缺少它们可能导致其他系统将邮件归类为垃圾邮件。因为来自实际友好协作邮件系统的邮件通常包含这些头部。这些头部包括:
- 显示用的
From头部: 注意,它可以与SMTPMAIL FROM头部不同。 To头部: 它也可以与SMTPRCPT TO命令中的收件人不同。Subject头部等等。
- 显示用的
- 邮件正文: 我们通常认为正文是纯文本,但不幸的是,现在也常是HTML。正文内容独立于SMTP协议,甚至可以包含附件和多部分消息,这些由其他标准定义。
中继限制与开放中继


现在,我们来看看垃圾邮件防护的第一道防线:限制邮件中继。
早期互联网上,邮件服务器数量有限,任何邮件服务器都乐于接受并转发给其他邮件服务器的邮件。但这意味着你可以滥用其他邮件服务器,通过它向其他系统发送邮件。因此,随着时间的推移,邮件服务器开始规定:只允许用户提交发送给其自身用户的邮件。这就是所谓的“不中继邮件”。

如今,互联网上仍然存在一些所谓的“开放中继”,但它们经常被垃圾邮件发送者滥用,以隐藏自己的来源,从而最终被列入各种互联网黑名单。
为了向特定域发送电子邮件,你必须与该域负责的邮件服务器通信,这可以通过查询该域的MX DNS记录来找到。
发送方策略框架

SMTP不提供任何真实性保证。如果你能向我发送电子邮件,你可以假装自己是任何人。SMTP MAIL FROM 命令可以设置为任何内容,甚至不必与邮件中显示的 From 头部一致。

那么,接收方服务器如何知道连接它的系统是否有权代表其声称的域发送邮件呢?这引出了发送方策略框架。
SPF允许域所有者指定哪些系统被允许代表其域发送电子邮件。这是通过在域的DNS中发布一条TXT记录来实现的。


例如,一个SPF记录可能如下所示:
v=spf1 include:_spf.google.com ~all
这条记录表示允许 _spf.google.com 包含的IP地址发送邮件,而其他所有来源则标记为“软失败”。
SPF检查发生在接收邮件服务器接受邮件的早期阶段。如果SPF检查失败(硬失败),服务器可以立即拒绝该邮件。如果是软失败,服务器可能会接受邮件,但会在邮件头部添加警告信息,供后续处理系统参考。

域名密钥识别邮件

SPF解决了IP授权问题,但邮件在传输过程中可能被篡改。为了确保邮件的完整性和真实性,我们引入了域名密钥识别邮件。

DKIM通过为邮件部分内容(如特定头部和正文)添加数字签名来工作。签名信息被添加到一个新的邮件头部 DKIM-Signature 中。


DKIM签名头部包含以下关键信息:
d=: 签名所代表的域名。s=: 选择器,允许使用多个密钥对。h=: 被签名的头部字段列表。bh=: 邮件正文的哈希值。b=: 实际的数字签名。


接收方服务器通过查询DNS来获取验证签名所需的公钥。查询的格式类似于 {selector}._domainkey.{domain} 的TXT记录。
基于域的消息认证、报告和一致性
我们有了SPF和DKIM,但接收方服务器在发现不匹配时应该怎么做呢?过于严格地拒绝邮件可能导致误拒合法邮件,这比误收垃圾邮件更令人不满。

为了解决这个问题,我们引入了基于域的消息认证、报告和一致性。




DMARC结合了SPF和DKIM的使用,并检查SMTP MAIL FROM 与邮件 From 头部是否对齐。更重要的是,它定义了当接收方遇到验证失败时应采取的策略,并通过DNS告知世界。

一个DMARC策略DNS记录示例如下:
v=DMARC1; p=reject; rua=mailto:dmarc-reports@example.com
这条记录表示策略是拒绝(p=reject)未通过验证的邮件,并将聚合报告发送到指定邮箱(rua)。

DMARC策略可以是:
none: 不采取特殊操作,仅用于监测。quarantine: 将邮件标记为可疑并放入隔离区。reject: 拒绝接收该邮件。
总结与扩展思考

本节课我们一起学习了电子邮件垃圾邮件防护的核心机制。
我们了解到,由于SMTP协议简单且缺乏内置认证,伪造邮件很容易。因此,我们需要一系列防护措施:
- 限制中继: 只允许向自己负责的域发送邮件。
- 发送方策略框架: 定义谁可以代表你的域发送邮件。
- 域名密钥识别邮件: 对邮件进行签名,确保其真实性和完整性。
- 基于域的消息认证、报告和一致性: 告知接收方如何处理验证失败的邮件,并接收报告。
这些机制比我们最初谈论简单邮件传输协议时要复杂得多,但这还不是故事的全部。例如,考虑邮件列表处理和重新分发邮件时对DKIM签名的影响。运行大规模电子邮件服务还需要考虑性能、日志记录、隐私(尤其是使用第三方服务时)以及区分垃圾邮件与合法批量邮件(如业务通讯)等复杂问题。

理解这些材料的最佳方式,仍然是亲自尝试视频中的命令和示例。查看你收到邮件的头部,尝试伪造一些邮件并观察哪些防护机制生效,你一定会发现一些有趣的角度。
041:备份(第一部分)💾
在本节课中,我们将要学习系统管理中一个至关重要但常被忽视的主题:数据备份。我们将探讨备份的核心概念、不同策略及其背后的权衡,并理解为什么备份的真正价值在于恢复数据的能力。
概述 📋
备份是确保数据安全、防止意外丢失的关键手段。然而,许多人只关注备份操作本身,而忘记了其最终目的是为了能够恢复数据。本节将介绍备份的基本术语、三种主要策略(完全备份、差异备份、增量备份)以及选择备份存储介质时需要考虑的因素。我们还将讨论恢复点目标(RPO)和恢复时间目标(RTO)等业务概念。
备份的核心概念与术语 🔑
在深入探讨之前,我们需要明确一些基本术语。理解这些概念是制定有效备份策略的基础。
备份类型
以下是三种主要的备份类型:
-
完全备份:创建所有数据的完整副本。这是最直接但最耗资源的备份方式。
- 公式表示:
备份数据量 = 总数据量
- 公式表示:
-
增量备份:仅备份自上一次备份(无论何种类型) 以来发生变化的数据。
- 公式表示:
备份数据量 = 自上次备份以来的数据变化量
- 公式表示:
-
差异备份:备份自上一次完全备份以来发生变化的所有数据。
- 公式表示:
备份数据量 = 自上次完全备份以来的累计数据变化量
- 公式表示:
恢复目标
在业务连续性计划中,有两个关键指标:
- 恢复点目标:指系统能够容忍的数据丢失时间窗口。例如,如果每天午夜备份一次,RPO就是24小时。
- 恢复时间目标:指在发生故障后,恢复系统或数据到可运行状态所需的时间。



三种备份策略详解 📊

上一节我们介绍了备份的基本类型,本节中我们来看看这三种策略在实际操作中的具体表现、数据量以及恢复过程的差异。
策略一:完全备份
这是最简单的策略。我们定期创建所有数据的完整副本。
示例流程:
- 周日:备份全部 7 TB 数据。
- 周一:再次备份全部 7 TB 数据。
- 如此每日重复。

一周后,总备份数据量为 49 TB。

恢复过程:
恢复非常简单,只需获取最近一次的完整备份副本即可。

优缺点:
- 优点:恢复过程最快、最直接。
- 缺点:备份速度慢,存储空间消耗巨大。
策略二:差异备份
此策略在初始完全备份后,后续每次备份都复制自上次完全备份以来所有变更的数据。
示例流程:
- 周日:完全备份 7 TB。
- 周一:备份自周日以来变更的 2 TB 数据(假设)。
- 周二:备份自周日以来累计变更的(2 TB + 新增的 1 TB)= 3 TB 数据。
- 每日持续累积变更数据,直到下一次完全备份。
一周后,总备份数据量可能降至 31 TB。
恢复过程:
恢复需要两个数据集:最近的完全备份 + 最近的差异备份。
优缺点:
- 优点:相比完全备份,节省了备份时间和存储空间。
- 缺点:恢复比完全备份稍慢,因为需要合并两个数据集。
策略三:增量备份
此策略在初始完全备份后,后续每次只备份自上一次备份以来变更的数据。
示例流程:
- 周日:完全备份 7 TB。
- 周一:备份自周日以来变更的 2 TB。
- 周二:仅备份自周一以来新变更的 1 TB(而非累计变更)。
- 周三无变更,则不备份。
- 如此继续,只备份真正新变化的数据块。
一周后,总备份数据量可能仅为 13 TB。
恢复过程:
恢复过程最复杂。需要从初始的完全备份开始,然后按顺序逐一应用所有的增量备份。
优缺点:
- 优点:备份速度最快,存储空间占用最小。
- 缺点:恢复过程最慢、最复杂,且任何一环增量备份损坏都可能导致整个恢复链失败。
备份存储介质的选择 💽
选择备份目的地与选择备份策略同样重要。不同的存储介质具有不同的特性,适用于不同的场景。
以下是常见的备份存储介质及其相关考虑因素:
- 磁带:传统介质,在企业级长期归档中仍占主导地位,通常使用大型自动化磁带库。
- 硬盘:可通过存储区域网络(SAN)等方式用于备份,性能较好。
- 固态硬盘:提供更高的IO性能,但成本也更高。
- 云存储:提供可扩展性、异地性和按需付费模式。
- 一次性写入介质:如DVD-R或企业级WORM存储,可防止数据被篡改,适用于合规性要求高的场景。
选择介质时需考虑的因素:
- I/O性能:备份通常涉及大数据块的顺序写入,而非随机读写。
- 可重用性与寿命:介质需要能长期可靠地保存数据。
- 成本:包括介质本身的成本和长期维护的成本。
- 附加功能:是否支持内置压缩、加密或重复数据删除。
备份的目的与特殊考虑 🎯
备份并非只有一个单一的目的。根据不同的目标,我们需要采取不同的策略。
长期归档 vs. 快速恢复
- 长期归档:为了满足法规遵从或历史记录保存(如政府文件、报纸档案),可能需要将数据保存数十年。这要求使用稳定的介质、完整的备份(而非增量),并妥善保管解密密钥和读取设备(考虑技术过时问题)。
- 快速恢复:针对日常的数据丢失(如误删除、硬盘故障),要求恢复过程迅速。这通常与上述的日常备份策略相关。
针对不同故障场景的恢复
数据丢失的原因不同,恢复策略也应有所侧重:
- 用户误操作/软件Bug:通常需要文件级的细粒度恢复。
- 硬件故障:通常需要整个系统的恢复。
- 安全漏洞:系统可能已被破坏,需要从干净的备份中重建整个系统,且不能使用已被入侵的系统上的工具进行恢复。
- 自然灾害:这超出了本地备份的范畴,进入灾难恢复领域,要求数据在 geographically 分散的地点有副本。
最佳实践与重要原则 🛡️
在制定了备份策略后,还有一些通用的原则和必须执行的步骤来确保其有效性。
3-2-1 备份原则
一个广泛认可的最佳实践是 3-2-1 原则:
- 3:保存3份数据副本。
- 2:使用两种不同的存储介质。
- 1:将其中1份副本存放在异地。
验证备份的完整性
这是最关键却最常被忽略的一步。无法恢复的备份毫无价值。你必须定期:
- 随机抽取备份数据进行恢复测试。
- 验证恢复出的数据是否正确、可用。
- 确保你拥有且熟悉执行恢复所需的工具和流程。
备份安全
备份软件本身需要访问所有数据,因此它也可能成为攻击目标。需注意:
- 备份数据可能包含恶意软件,恢复时可能重新引入系统。
- 从不信任的系统(如已被入侵的系统)上运行的恢复工具本身也可能是不可信的。
总结 📝
本节课中我们一起学习了数据备份的基础知识。我们明确了备份的终极目标是恢复。我们详细分析了完全备份、差异备份和增量备份三种策略的优缺点及适用场景。我们还探讨了如何根据备份目的(长期归档或快速恢复)和故障类型来选择策略和存储介质。最后,我们强调了遵循 3-2-1 原则和定期验证备份的极端重要性。
记住,备份就像保险:你希望永远用不上它,但拥有它让你高枕无忧。现在,是时候去检查一下你自己的备份是否真的可用了。

在下一个视频中,我们将更实际地探讨在文件级别进行备份的常用工具和技术。
042:备份实战 🛡️
在本节课中,我们将通过具体实例学习如何执行系统备份。我们将探讨几种不同的工具和方法,了解它们各自的优缺点,并学习如何从备份中恢复数据。
上一节我们介绍了备份的核心概念,如全量备份与增量备份的区别。本节中,我们来看看如何将这些概念付诸实践,使用具体的工具来创建和管理备份。
备份策略的实践考量
从灾难或系统性故障中恢复数据可能涉及复杂的操作。但即使是恢复单个文件,我们也需要注意一些细节,这些细节将定义我们的整体备份策略。
具体来说,我们需要为用户提供一些保证:被删除的文件应该能在给定的时间窗口内被恢复。
从纯粹的可用性角度来看,用户可能期望能够撤销更改。而像 rm 这样的 Unix 工具,其正常行为就是完全删除文件,而不是将其移动到特殊目录。因此,在文件系统层面没有内置的“撤销”功能。
因此,我们必须与用户协商,确定一个现实的恢复点目标。即,我们定义数据丢失的时间窗口,或者说我们备份的粒度。
例如,我们可以说我们执行夜间备份,这意味着我们平均只能恢复大约12小时前、最坏情况下24小时前的数据。
但数据一旦丢失,即使它在恢复点目标范围内,恢复过程通常也不是即时的。
这就是为什么我们必须定义恢复时间目标,即恢复文件所需的时间。
这包括人员可用性及其开销。例如,如果你需要填写支持工单来请求文件恢复,那么必须有人阅读该工单并有时间采取行动。
此外,如果磁带库当前正忙于将当前数据集备份到磁带,它可能无法立即恢复你的数据。在大型企业中,这通常不是问题,因为可能有多种访问备份的方式。但在较小的环境中,这可能是一个实际问题。
当然,检索数据本身也需要时间。如果你丢失了一个文件,但它在一个20TB的磁带备份上,你可能需要读取数百GB的数据才能找到你的文件。同样,如果你丢失了20TB的数据,恢复这些数据必然需要时间,这受到磁带性能和你恢复数据的目标设备的I/O速度的限制。
所有这些都使得恢复数据成为一个漫长而繁琐的过程。因此,如果能够设置备份系统以允许自助服务恢复,让任何用户都能自行连接并恢复其数据备份,那将更好。但这并不容易实现,尤其是在大规模环境中。不过,我们将在下一个视频中看到一些有助于实现这一目标的方法。
最后,值得一提的是备份也有一个意想不到的副作用:被删除的数据现在仍然在某个地方可用。在某些情况下,这可能是一个问题。有时当你删除数据时,你真的希望确保它已消失且无法恢复。
使用 tar 进行备份 📦

让我们看几个实际例子。我们将使用的工具之一是 tar,你在提交作业时已经经常使用它。
tar 是一个磁带归档器,是 Unix 工具之一,支持相当多的命令行选项,其中一些甚至不需要短横线。这部分原因使得 tar 常被引作 Unix 神秘巫术的例子,但它其实并没有那么复杂。tar 是一个古老的工具,在 Unix 早期,许多工具不需要短横线来指定选项。

tar 的整体使用可以归结为少数几个命令。
tar 是一个用于创建文件系统层次结构归档的工具,通常用于将其写入磁带,默认设备是 /dev/rst0。

让我们看看如何使用 tar 进行备份。
以下是一个简单的双卷实例,连接了第二个卷。我们在其上创建一个新的文件系统,并将其挂载到 /backup 下。
现在,我们创建一个以当前日期时间戳命名的目录。
然后,我们可以使用 tar 备份我们的 /usr/local 目录,创建一个归档并将其写入文件系统。
现在,我们在额外的磁盘上有了副本。这很好。
但我们的备份磁盘仍然位于本地系统上。也就是说,我们只是在同一系统的另一个磁盘上创建了一个副本。
由于 tar 可以将数据写入标准输出,我们可以简单地将其通过管道传递给任何命令。因此,我们可以将数据写入远程系统。
当然,没有什么要求我们必须将数据写回文件系统。请记住,tar 旨在创建归档,即特定格式的文件。
因此,我们可以将数据直接写入块设备,而不是像上一个例子那样将其提取到文件系统中。为此,我们可以使用老朋友 dd 命令。
现在,在远程备份目标上检查,我们可以从块设备读回数据。是的,我们确实将归档写到了这里的磁盘上。
现在,回到我们的原始服务器。如果我们不小心从这个目录删除了数据,我们可以从远程系统上的备份中恢复它。
通过使用 tar 并将数据写入管道,我们还可以获得额外的功能。例如,我们可以在将数据写入远程站点之前压缩它。
我们还可以添加其他数据转换。例如,我们可以在发送数据之前对其进行加密,这里使用 openssl enc 命令进行说明。

现在,要恢复数据,我们只需反转步骤:先解密,然后解压缩,最后写入文件系统。这非常有用,很好地说明了 tar 的灵活性——它不仅仅是一个提交作业的工具。
使用 dump 进行备份 💾
为了创建备份,我们还有其他工具。其中最古老的工具之一是 dump 命令。
dump 命令用于执行文件系统备份,与 tar 不同,它有一些逻辑来确定应该备份哪些内容。

dump 还区分了我们之前讨论过的全量或0级备份和增量备份,这意味着它可以用于仅备份自上次备份以来已更改的数据。
这次,我们将数据写入远程主机文件系统上的一个文件,通过将 dump 命令的输出通过管道传递给 ssh 和 cat。

dump 命令将需要几分钟来确定需要备份哪些文件(在本次迭代中,所有文件都在执行0级或全量备份),然后将数据通过网络写入远程文件。


dump 会通过写入 /etc/dumpdates 来记录它执行了哪个级别的备份,该文件指定了磁盘、备份级别和日期。
在远程系统上,我们找到了完整的0级备份文件,它告诉我们备份的写入时间、上次执行全量备份的时间(这里是纪元时间,因为我们从未执行过备份)以及其他一些信息。

现在我们已经完成了全量备份,让我们看看增量备份是什么样子。
为此,我们通过将我们的组项目 Git 仓库提取到 /usr/local 来创建一些新数据。
现在,再次运行 dump,这次作为增量备份,并将数据写入第二个文件。
请注意,我们的备份完成速度比之前快得多,因为它只需要复制自上次全量备份以来已更改的文件。在远程目标上,备份文件当然比0级 dump 文件小得多。
在我们的服务器上,/etc/dumpdates 已更新以反映增量备份。
现在,让我们通过删除一些文件来模拟数据丢失。这些文件消失了。现在怎么办?让我们从上次备份中恢复数据。我们使用 restore 命令,要求它从上次增量备份中提取 /usr/local 的文件。
命令告诉我们数据来自何时,然后将其写入我们的文件系统。数据回来了。

但我们也删除了 /etc/rc.d 目录。让我们尝试恢复它,但没有成功。/etc/rc.d 在相关备份中未找到,这并不奇怪,因为增量备份只复制了自上次0级备份以来已更改的文件,而 /etc/rc.d 没有更改。所以让我们查看全量备份。

为此,我们将文件复制到这里,以便可以交互式地检查它。
现在,我们可以在交互模式下运行 restore,这会让我们进入一个类似 shell 的提示符。可以像这样显示备份的信息,因为 restore 命令为我们提供了一些简单的命令来与备份交互。

我们可以使用 ls 命令列出备份的内容,进入目录并检查其内容,然后选择要恢复的单个文件或目录。
然后,我们可以提取数据,设置权限。我们的数据已经恢复。
因此,dump 命令原生支持增量备份,而 restore 允许选择要恢复的数据。这与我们之前使用 tar 的例子有些不同。
使用 rsync 进行备份 🔄


但是否有办法以类似的方式使用其他工具呢?让我们再次尝试使用 tar。如前所示,备份目录中的数据很容易。
但是,如果我们在这里创建新数据,然后再次运行备份命令,我们又会复制所有数据。这里没有增量备份。
但是,如果我们不使用 tar,而是使用不同的工具呢?rsync 命令允许我们将目录层次结构同步到另一个位置。

运行它。你会注意到,即使它列出了所有目录,它实际上并没有再次复制所有文件,而是只复制了新修改的文件。因此,我们可以有效地执行增量备份,尽管在这种情况下,增量是直接应用到远程目标的,而不是像 dump 那样作为单独的增量文件保存。

但现在请注意,我们还有另一个用例。我们不仅希望跟踪我们有哪些文件,有时我们还希望确保从文件系统中删除的文件也从另一侧删除。
虽然 dump 在创建增量备份方面很出色,但增量备份实际上只包含已更改文件的数据,而没有关于哪些文件已被删除的记录。因此,当我们在这里删除一些文件,然后运行 rsync 备份时,它的行为就像 dump 一样,本地删除的文件在远程端仍然存在。
但 rsync 有另一个选项可以确保我们也同步文件删除操作:--delete 标志。

使用该标志后,当文件在本地消失时,我们也会删除远程站点上的文件。这很有用,因为它提供了 dump 工具所不具备的功能。
但现在我们有了另一个问题。如果我们以后再次添加这些文件,如何回滚本地所做的更改?我们将无法回到它们最初被删除的状态。
与其只关注文件的添加或删除,拥有一种更好的方式,即能够说“向我展示文件系统在给定时间点的样子”,这不是很有用吗?
我们确实有办法做到这一点,但恐怕你必须等到下一个视频才能发现这些方法是如何工作的。
总结 📝
本节课中我们一起学习了如何使用不同工具进行备份。
我们看到了 tar 如何用于创建文件系统层次结构的归档,以及如何对以这种方式创建的数据进行多种操作,而不仅仅是写入文件。我们可以使用 dd 将其写入原始磁盘设备,通过 ssh 将其复制到远程系统,并在此过程中转换数据,例如压缩和加密。但正如我们在最后一个例子中所示,使用 tar 意味着全量备份,因为 tar 不支持增量备份。
另一方面,dump 确实支持增量备份,并且与系统集成更紧密:/etc/fstab 包含一个字段来帮助 dump 决定需要备份哪些文件系统,而 /etc/dumpdates 会跟踪上次备份的时间等。要恢复以此方式备份的数据,可以使用 restore 命令来恢复所有数据、部分数据,或交互式地浏览备份。

然后,我们研究了 rsync,以展示它如何增量备份数据,并且与 dump 不同,它甚至可以从数据集中删除数据。但我们也提到,我们仍然缺少一些功能,比如能够回滚时间并有效地浏览文件系统在特定时间点的状态。如何做到这一点将是我们下一个视频的主题。
043:时间旅行与快照 📸
在本节课中,我们将学习文件系统快照的概念。快照是一种能够“冻结”文件系统在某一时刻状态的技术,它允许我们快速查看甚至恢复到过去的某个时间点,而无需进行耗时的完整备份。
上一节我们讨论了传统备份与查看历史文件系统状态的区别。本节中,我们来看看如何通过文件系统快照实现“时间旅行”。


创建与使用快照
以下是创建和使用快照的基本步骤。
首先,我们可以使用 fssconfig 工具来创建快照。例如,为根文件系统创建一个快照:
fssconfig fss0 / /backup
这个操作几乎是瞬间完成的,这是快照相较于完整备份的一个主要优势。
创建的快照文件(如 /backup)看起来很大,但它并非普通文件。我们无法直接读取或压缩它,因为它是一个特殊的快照文件。
要使用这个快照,我们需要将其挂载为一个伪设备:
mount -t ffs /dev/fss0 /mnt/snapshot
挂载后,我们可以在 /mnt/snapshot 目录下浏览快照创建时文件系统的完整状态。这个快照是只读的,我们无法修改其中的任何内容,这确保了历史数据的不可变性。
如果发生了数据丢失,我们可以轻松地从快照中恢复文件。使用完毕后,卸载并删除快照:
umount /mnt/snapshot
fssconfig -u fss0
rm /backup
快照的优势与局限
快照机制与传统备份方法有显著不同。

优势:
- 速度快:创建快照是近乎即时的操作。
- 节省空间:快照最初不复制数据块,只记录元数据引用,因此不占用额外存储空间(直到原数据被修改)。
- 使用方便:可以像普通文件系统一样挂载和浏览,便于用户自行恢复文件。
- 数据安全:快照是不可变的,即使是 root 用户也无法修改,防止了意外覆盖。
局限:
- 快照与创建它的文件系统和存储设备绑定。它无法被移动到其他系统,也不能直接备份到单独的磁盘上。因此,它本身不能构成一个完整的灾备策略。
快照的实际应用

快照的概念被应用在许多常见的备份系统中。
MacOS Time Machine:它并非真正的文件系统快照。其原理是先进行一次完整的初始备份,之后每小时创建新的“视图”。对于未更改的文件,它创建硬链接;只复制新增或修改过的文件。这是一种利用硬链接和增量变化的差分备份变体,在速度和空间上取得了平衡。
NetApp WAFL(Write Anywhere File Layout):这种文件系统大量使用快照来保证高可靠性。它采用了一种称为写时重定向的机制。创建快照时,只需复制根 inode,速度极快。当需要修改数据时,新数据写入新的块,并更新当前文件系统的指针,而快照的指针仍指向旧的、未修改的数据块。回滚时,只需将快照的根 inode 复制回来即可,同样非常迅速。
ZFS 文件系统快照示例
ZFS 是另一个原生支持快照的现代文件系统。以下是其基本操作。
首先,查看并创建一个 ZFS 快照:
# 查看当前数据集
zfs list
# 为根数据集创建带时间标签的快照
zfs snapshot rpool/ROOT/omnios@$(date +%Y%m%d)
快照会瞬间创建,并出现在 .zfs/snapshot/ 目录下。
我们可以进入快照目录浏览历史文件,或比较快照与当前文件系统的差异。要恢复单个文件,直接从中复制即可。

如果需要完全回滚到快照时的状态,可以使用回滚命令:
zfs rollback rpool/ROOT/omnios@20231027
这个操作也很快,因为它只恢复数据块的引用关系。之后,快照时间点之后创建的文件和所做的修改都会消失。
不再需要快照时,可以销毁它:
zfs destroy rpool/ROOT/omnios@20231027

总结与最佳实践
本节课中我们一起学习了文件系统快照。
我们了解到,快照是一种快速、节省空间的强大工具,能够为用户提供自助文件恢复能力,极大地降低了恢复时间目标。它的核心原理是通过写时重定向等技术,在几乎不占用额外空间和时间的条件下,记录文件系统在某一时刻的完整视图。
快照技术被应用于 Time Machine、NetApp WAFL 和 ZFS 等系统中。然而,快照本身与原始存储绑定,不能替代离站备份。
无论你采用何种备份方案(快照、dump、rsync等),请务必确保:
- 它是一个自动化的流程。
- 它能够定期且频繁地运行,以满足你的恢复点目标。
- 最重要的是,你需要定期验证备份的有效性,确保在需要时真的可以恢复。

现在,就去检查一下你的备份是否在正常工作吧。
044:配置管理(第一部分)🚀
概述
在本节课中,我们将要学习配置管理。配置管理是系统管理中的一个核心领域,它结合了管理成百上千台系统、服务以及分布式计算的广泛概念。我们将探讨什么是配置管理、为什么需要它,以及系统管理员在实践中如何演进其管理方法。
什么是配置管理?
系统管理的核心目标之一是确保系统能够稳定、可靠地提供服务。然而,系统并非静态的。文件会被创建、修改或删除;用户会登录并运行命令;服务会启动或终止。此外,业务需求和技术演进也要求系统频繁地更新软件、调整配置、管理用户账户和调度任务。
换句话说,我们的系统持续经历着变化。
在单台主机上,这些变化通过修改本地配置文件、执行特定命令以及安装或调整应用程序来完成。而当我们需要将同样的变更应用到大量系统时,问题就变得复杂了。
为什么需要配置管理?
手动管理大量系统的变更是不现实的。例如,为一个安全漏洞打补丁,我们需要:
- 准确知道哪些主机运行了有漏洞的软件版本。
- 在每台主机上执行相同的升级步骤。
我们无法亲自跟踪成千上万个系统上的数百项变更。因此,我们将“应用定义明确的变更集到大量系统”的任务,委托给一类称为软件配置管理系统的软件,简称 CM。
配置管理系统与我们之前讨论的多个主题紧密相关:
- 系统初始化:在操作系统安装后,需要进行最小化的自定义配置。
- 软件管理:CM 需要与系统的包管理器紧密集成。
- 用户管理:用户账户的增删改查是动态的。
- 备份自动化:备份策略需要中心化配置,并根据主机差异进行分发。
配置管理方法的演进

几乎每位系统管理员都经历过管理方法的演进。这个过程通常遵循一个可预测的模式。
以下是系统管理员在尝试规模化管理系统配置时,常见的演进步骤:

- 手动复制:在一台服务器上完成配置,然后手动将必要的文件和配置复制到其他服务器。这种方法效率低下且容易出错。
- 使用
rsync同步:意识到全量复制低效后,转而使用rsync等工具仅同步有差异的数据。但直接同步存在风险,因为不同主机之间存在不可共享的独有数据(如网络配置、主机名)。 - 构建“黄金镜像”:创建一个包含所有可共享数据的基准镜像用于部署,同时单独管理每台主机特定的配置。这种方法适用于少量主机,但按顺序循环处理大量主机时存在可扩展性问题。
- 拉取模式:反转流程,让每台服务器主动从中心服务器“拉取”配置。这缓解了推送模式的中心节点压力,但可能导致所有客户端同时连接中心服务器,造成“惊群”效应。常见的缓解措施是添加随机延迟或分阶段部署。
- 采用成熟的 CM 系统:最终转向使用如 Puppet、Chef、Ansible 等专业的配置管理系统。这些系统提供了声明式的语言来定义系统状态,并能智能地处理依赖关系和变更。
然而,即使使用了 CM 系统,有时仍会遇到其无法处理的边缘情况,管理员可能不得不暂时退回更手动的方法。关键在于找到平衡。

配置数据的分类
为了有效地进行配置管理,我们需要对配置数据进行分类。回顾我们在第 3 周讨论的文件系统层次结构标准,我们可以根据数据的可变性和共享性来区分。
在实践中,系统的配置可以分为两大类:

- 系统特定但可预测的配置:这些配置因主机的位置或角色而异,但其内容是可以通过规则推导的。
- 示例:网络配置(如默认网关)、关键基础设施服务地址(如本地 DNS、NTP、Syslog 服务器)、操作系统最低版本要求、用户与 SSH 配置。
- 特点:通常可以从系统属性(如数据中心区域、主机角色)在运行时派生。

- 服务特定的配置:这些配置由运行的服务本身决定,需要在所有运行该服务的主机上保持一致。
- 示例:Web 服务器软件包、TLS 证书与密钥、数据库连接配置、服务托管的具体内容。
- 特点:与业务逻辑直接相关,跨主机需要高度一致。
假设我们在全球多个数据中心(如美西、美东、欧洲、亚太)部署相同的 HTTP 服务。所有主机都需要相同的 Web 服务器软件,但每台主机的 DNS 解析器地址必须是其本地数据中心的。这就是上述两类配置的典型例子。
配置管理系统的抽象与复用

成熟的配置管理系统允许我们以声明式和抽象化的方式定义配置,并支持代码复用。
不同的 CM 系统使用不同的语法,但核心思想相似:定义资源(如软件包、服务、文件)及其应有的状态。
以下是不同 CM 系统的配置示例,它们都旨在实现相似的目标:
Puppet 示例:使用其领域特定语言定义软件包和服务。
package { 'openssh-server':
ensure => latest,
}
service { 'sshd':
ensure => running,
enable => true,
require => Package['openssh-server'],
}
file { '/etc/ssh/sshd_config':
ensure => file,
owner => 'root',
group => 'root',
mode => '0600',
source => 'puppet:///modules/ssh/sshd_config',
require => Package['openssh-server'],
notify => Service['sshd'],
}

Chef 示例:使用 Ruby 风格的语法定义资源。
package 'rsyslog' do
action :install
end

service 'rsyslog' do
action [:enable, :start]
end

CFEngine 示例:另一种定义承诺的方式。
bundle agent main
{
packages:
“openssh-server” policy => “present”;
services:
“ssh” service_policy => “start”;
}
这些系统的强大之处在于抽象与复用。我们可以将通用功能(如日志轮转、基础用户配置)定义为独立的模块或“类”,然后在不同的服务配置中引用它们。这类似于软件开发中的函数调用,避免了代码重复,使配置更易于维护和更新。
例如,可以为“基础安全设置”创建一个模块,其中包含 SSH 配置和防火墙规则,然后让所有服务器配置都包含这个模块。

总结与下节预告
本节课我们一起学习了配置管理的基础概念。我们了解到,由于系统持续变化且规模庞大,手动管理配置是不可行的。配置管理系统的演进从简单复制发展到声明式、可编程的自动化。我们学会了将配置数据分类为系统特定配置和服务特定配置,并看到了如何使用 CM 系统通过抽象和复用来高效、规模化地定义这些配置。
告别了难以扩展的手工操作,我们进入了更高效、更可靠的自动化管理时代。

在下一个视频中,我们将深入探讨配置管理系统的核心能力,介绍状态声明的概念,并简要提及分布式系统中的 CAP 定理及其对配置管理的影响。
课后练习
为了加深理解,建议你完成以下练习:

- 复习文件系统层次结构:回顾第 3 周的内容,尝试对你系统上的不同目录和文件进行分类(共享/非共享,可变/静态)。这对理解配置数据的处理方式至关重要。
- 尝试定义一项服务:选择一项服务(例如 SMTP 服务器),尝试列出配置它所需的所有不同组件和服务(软件包、配置文件、用户、依赖服务等)。
- 研究不同的 CM 系统:了解 Puppet、Chef、Ansible、SaltStack 等主流配置管理工具的异同。有些更容易设置,不妨尝试搭建一个简单的实验环境。
- 阅读相关概念:提前阅读 基础设施即代码 和 服务编排 的相关资料。这两个领域与配置管理有大量重叠和交叉,但又不完全相同。我们将在后续视频中重新探讨它们之间的关系。
045:配置管理(第二部分)🎯
在本节课中,我们将继续深入探讨配置管理系统的核心概念。我们将了解这些系统如何影响系统运行,以及在安装、运行和管理它们时需要牢记的重要考量。

上一节我们介绍了服务定义抽象化的概念。本节中,我们将进一步探讨这些关键基础设施服务如何影响系统运行,以及配置管理系统的核心任务。
配置管理的核心:断言状态

配置管理系统的主要任务并非执行具体的更改。相反,其核心工作是断言状态。
这两者之间存在显著差异,并深刻影响着系统的工作方式。这听起来可能有些反直觉,但仔细想想,你确实不希望系统执行特定的更改,而是希望确保主机被正确配置,然后仅应用那些必要的更改以使其达到该状态。
我们知道,封闭系统的熵会随时间增加,因此我们的系统会逐渐“恶化”。配置管理系统的职责就是将秩序带回混沌,让系统重回正轨。
以下是系统可能经历的状态:
- 未配置状态:系统不满足我们的要求,未安装正确的软件包或缺少正确的配置文件。这可能是一台仅安装了基本操作系统的全新主机。
- 已配置状态:配置管理系统执行定义的更改后,所有必需的软件包都已正确安装和配置,主机上的所有服务都在运行。系统已准备好投入生产。
- 偏离状态:随着时间的推移,熵或用户操作可能导致系统偏离期望状态,进入偏离状态。例如,有人手动更改了系统或安装了与现有服务冲突的软件包。
- 未知状态:系统处于未明确定义的状态。这可能是因为配置管理代理已停止运行,或正在错误地应用配置。主机可能已关机、网络中断,甚至可能已被入侵。
配置管理系统定期运行,检测到偏离后,会逆转或覆盖这些更改,使系统回到已知的良好状态。一个正常运行的配置管理系统至少具备一定的自愈能力。
此外,系统还可能被标记为以下两种服务状态,这些状态可能由配置管理系统影响,也可能与之无关:
- 服务中:系统已准备好接收生产流量。
- 服务外:系统已从生产流量中移除。

这些状态转换有时由一个独立的监督服务(称为服务编排)来管理,它可以根据外部定义的属性,将单个系统从“已配置状态”置入或移出服务。
分布式系统的挑战:CAP定理
任何单台主机都不是孤立运行的。运行在每台主机上的配置管理系统本身通常由一个中心服务协调,因此它们本身就是分布式系统。

作为分布式系统,配置管理系统也受CAP定理约束。这意味着我们必须考虑三个重要但无法同时完全满足的属性:

- 一致性:确保所有系统彼此一致,每个系统都能一致地接收相同的更新。
- 可用性:系统能够接收更新,并确保其管理的服务在整个操作过程中保持可用。
- 分区容错性:当系统无法接收更新时不会崩溃,并能够优雅地从任何此类网络分区中恢复。
由于系统的分布式特性,我们一次只能保证这三个属性中的两个。
操作的关键属性:幂等性
配置管理系统在断言状态时,其执行的操作必须是幂等的。
幂等性是指一个函数可以重复应用,以其自身的结果作为输入,并产生相同的输出。用数学公式表示即:F(F(x)) = F(x)。

在我们的上下文中,这意味着可以连续多次执行相同的操作,并始终获得相同的结果。这与“做同样的事情”有显著区别。
以下是判断操作是否幂等的例子:
- 删除文件:命令完成后文件不存在。再次运行命令,结果相同(文件仍不存在),尽管命令可能报错。此操作是幂等的。
- 写入配置文件:每次运行都会截断文件并写入相同内容,结果是相同的。此操作是幂等的。
- 向文件追加数据:运行两次命令会得到不同的结果(文件包含两行相同内容)。此操作不是幂等的。
- 更改文件所有者或权限:是幂等操作。
- 安装软件包:通常不是幂等操作。如果指定安装最新版本,每次调用可能安装不同版本。即使指定确切版本,由于软件包安装脚本可能依赖系统参数,也无法绝对保证幂等性。

幂等性是实现某种形式自愈能力的重要要求,因为它允许重复执行相同步骤以达到期望状态,而不会造成损害。但请注意,这并不意味着它一定是高效的。

跨主机的最终一致性
配置管理系统需要确保在执行更改后,能在多台主机上达到期望的最终状态。这就要求跨主机的最终一致性。

例如,假设你想在所有HTTP服务器上推出一个软件包更新。如果10台系统中有1台因故无法应用更新,你该如何处理?是让那台主机保持原样(导致9台一致,1台不一致),还是在9台上回滚更改以确保所有系统一致,或者将第10台标记为偏离状态并将其移出服务?
不同的系统以不同的方式解决这个问题。这通常需要与其他系统进行显著的通信和状态感知,因此我们常与监控系统(如服务编排工具)集成来帮助管理服务状态。
配置管理系统的功能与复杂性

配置管理系统本身也是复杂的系统,可能以复杂的方式失败。它们本质上被高度信任,拥有在主机上进行更改的特权,因此也有可能严重破坏系统,以至于无法自行恢复,需要手动干预。
因此,为任何即将推出的更改制定周密的分阶段推出计划至关重要,应缓慢地、增量式地应用到系统子集,并配合适当的监控、检查以及故障时的自动回滚机制。

一个配置管理系统需要具备以下核心功能:
- 与软件包管理器集成以安装软件。
- 与系统初始化组件(如systemd)集成以确保服务启动、重启和持续运行。
- 应用文件权限和所有权。
- 安装静态文件、向现有文件添加内容,或根据系统/网络位置属性生成主机特定的配置文件。
- 执行超出文件、权限或软件包管理的特定命令。
- 从主机收集数据并报告其状态。
与其他管理领域的重叠

观察上述功能列表,你会发现配置管理系统与你使用的许多其他重要系统和服务存在重叠:
- 软件部署:与部署引擎或持续部署流水线直接重叠。
- 监控解决方案:用于常规报告和临时数据收集。
- 版本控制:一旦配置被充分抽象化,所有更改都成为代码更改,软件工程中的版本控制优势在此变得必要。
- 合规性强制执行:配置管理系统是保证在所有系统上部署法规要求更改的方式。
因此,配置管理通常为许多额外的基础设施任务提供基础,它与资产清单、角色定义、服务编排、部署和监控等领域相互交织。
容器化与配置管理
那么,在使用静态、小型、定义明确的容器时,我们是否以同样的方式使用配置管理?答案是:既是,也不是。这取决于具体情况。

容器一方面是不同的,但另一方面,它们又是相同概念的演进。就像配置管理系统断言系统状态一样,我们的容器在某种程度上也是一种状态断言。我们仍然需要资源、实例和目标的清单。
关键区别在于,我们的容器应该是不可变的,即它们不应在运行时被更改,从而保证正确的状态。我们不是尝试更新运行中系统的配置,而是通过集成代码更改并部署更新的容器镜像来确定状态偏差。
即使在容器化的新世界里,许多繁重的工作仍然由之前的相同工具完成。在成熟的组织中,运行时系统的配置已从单独的手动系统转向通用的、定义明确的、不可变的组件,这些组件可以以自动化方式构建和部署,即基础设施即代码。
总结与应用范围

本节课中,我们一起学习了配置管理系统的核心概念。让我们回顾一下最重要的要点:

首先,我们需要从关注单个系统转向清晰定义我们所部署的服务。这意味着不再费力维护只有少数人记得其确切配置的脆弱个体系统,而是转向可以随时重建和实例化的、完全可替换和可交换的系统。

为实现这一点,我们应聚焦于断言状态,定义结果而非配置系统的方式。鉴于配置系统的分布式特性,我们需要意识到其局限性,并为分布式系统可能失败的各种方式建立防护措施。
我们构建的状态定义,应能通过应用幂等的更改集来达到,并可靠地保证最终收敛于一个已知状态。
所有这些都与系统管理中使用的其他组件、服务和系统存在显著重叠。随着行业从专业化服务转向更描述性的、能够通过集成测试、代码审查和持续集成/部署实现自动化的方式,配置管理已成为大规模系统管理中最重要的领域之一。

最后,请记住,状态断言和配置管理的所有好处不仅适用于服务器,也同样适用于桌面和移动客户端(尽管使用不同的企业工具),以及路由器、交换机、存储设备、负载均衡器等网络设备。我们在此学到的所有知识都可以并应该应用于这些领域。
046:系统安全 I - 风险评估 🔐
在本节课中,我们将开始学习系统安全的核心概念。我们将探讨安全的本质,理解它为何不是一个终极目标,并学习进行风险评估的基本方法。
课程概述
随着学期接近尾声,我们现在回到最初视频中提到的、对系统管理员工作至关重要的一个主题:系统安全。在整个学期中,我们反复提及所涵盖主题的安全方面。当然,不可能在短短几个视频中充分详细地涵盖像安全这样广泛的主题。但正如之前所讨论的,我们的目标是提供足够的信息,让你知道在哪里寻找更多内容,并理解哪些方面最重要。所以,让我们来谈谈系统安全。
首先,我必须让你失望:我无法告诉你如何使你的系统变得安全。根本没有一个简单的公式。天下没有免费的午餐。我希望通过这些视频,你能理解其中的原因。
我也不会告诉你如何入侵其他系统,尽管理解如何入侵通常是理解如何防御系统免受侵害的副产品。当然,你会学到一些东西,让你更容易攻破其他系统,例如系统安全的本质。但无论如何,我显然无法告诉你关于这个主题你需要知道的一切。你可以选修专注于信息安全许多方面的其他课程,你可以获得网络安全学位或专业,你可以在行业工作多年,但仍然无法了解所有知识。因此,在这里,我们仅仅是浅尝辄止。

那么,这些视频对你有何用处呢?以下是我希望你能从中获得的一些东西。正如我所提到的,我希望这些视频能给你足够的信息来开始探索,为你提供关键词和概念,让你了解哪些是至关重要的。我将尝试解决信息安全行业以及我们应用安全概念时存在的一些问题。这一点也很重要。在此过程中,我会尝试加入一些“总是”和“从不”的规则。当然,这些规则总是有例外,因此并非真正的“总是”和“从不”,但作为经验法则,它们对你未来的职业生涯将是有益的。
那么,我们何不从其中一条智慧箴言开始呢?

安全不是终极目标 🎯
这是一个重要的观点。安全本身不是一个终极目标。它不是一个结果。它本身并不是有价值的东西。相反,安全是系统的一种属性,一种可能有助于提高你对特定风险抵御能力的属性。而这种属性是有代价的,所以你总是在权衡利弊。
我们稍后会更多地讨论这个“特定风险”的概念。但也许我们应该首先弄清楚这个神秘的安全属性究竟应用在哪里。
安全贯穿所有层次

安全贯穿技术栈的所有层次。我们很早就提到过,安全不能是事后添加的东西,它需要是系统内在的属性。因此,我们当然需要在每一层都考虑安全。我们这学期确实已经这样做了。让我们回顾一下本学期涵盖的材料。
以下是本学期各主题中涉及的安全方面:
- 第2周:磁盘与存储:我们讨论了不同的存储模型,并注意到从DAS到NAS到SAN再到云存储的抽象层次如何增加了复杂性和数据暴露的风险。
- 本地文件系统:我们注意到可以通过应用不同的挂载选项来增强系统安全性,例如将某些文件系统挂载为只读,或使用
noexec或nosuid等选项。 - 文件系统基础与软件类型:我们指出即使是硬盘上的固件(即IDE驱动器名称来源的集成设备电子部件)也可能被攻破。我们还通过共享文件系统实验,观察了耗尽所有文件空间或inode可能发生的情况。
- Unix文件系统属性:我们讨论了Unix文件系统作为多用户系统的标准属性,因此需要文件所有权和权限,同时也提到了扩展文件系统访问控制列表(ACL)的可用性。
- 软件安装:这是一个充满安全隐患的主题。通过容器和虚拟机实现的权限分离,以及补丁管理和软件包完整性保证,都是一些例子。
- 多用户基础:我们在课堂上花时间讨论了不同的信任模型、身份验证方法,以及用户如何提升权限,所有这些都可能被滥用,并具有内在且明显的安全隐患。
- 网络:我们说明了每一层中元信息的可见性,并且看到仅使用
tcpdump,我们不仅可以查看主机上的所有数据,还可以查看网络上经过的一些数据。这当然意味着,如果你与攻击者在同一网络段,可能会发生什么。 - 互联网的物理性质:我们讨论了互联网的物理性质,以及政治格局如何影响允许通过某些网络的流量,以及流量在传输过程中是否可以被检查。
- DNS:我们很快意识到,如果你能控制一个区域,那么你就能控制流量的几乎所有其他方面,这就是为什么攻击你的域名服务器记录注册地变得如此有趣。我们看到DNS被用于各种侧信道攻击,经常提供带有身份验证上下文的额外信息。我们注意到不幸的是,大多数DNS流量是不受保护和未经身份验证的,但我们也提到了一些补救方法,如DNSSEC、DNS over TLS和DNS over HTTPS。
- HTTP:我们指出HTTP是跨网络的通用入口点。我们讨论了如今HTTP不再只是静态文档,而是在服务器端和客户端动态执行的,这带来了所有安全隐患。我们还讨论了将流量外包给CDN可能对安全和隐私产生的影响。
- HTTPS与TLS:了解到我们可以使用
tcpdump观察纯HTTP文本,从而看到网络上的流量,我们接着讨论了HTTPS和TLS来加密传输中的流量。这引出了通过公钥基础设施(PKI)对服务进行身份验证,而PKI建立在数量惊人的证书颁发机构之上。我们简要提到了TLS协议开发过程中遇到的一些困难。 - SMTP与电子邮件:我们讨论了电子邮件本身作为一种攻击载体,垃圾邮件和网络钓鱼利用了简单邮件传输协议的工作原理,因为它是在开放网络时代开发的。然后我们讨论了使用STARTTLS及其机会主义加密方法,以及各种相关的DNS记录试图提供另一层安全性和真实性,这些并未包含在原始协议中。
- 开发工具与自动化:在课堂上,我们讨论了在评估环境中开发工具、编写代码和编程,我们可以利用自动化作为保护机制,但需要注意选择错误的工具可能导致严重的安全问题。这要求我们理解所使用的语言和框架,并努力通过专注于简单性来减少攻击面,因为我们编写的所有代码都不可避免地包含错误,我们需要小心不要在部署自动化时引入安全漏洞。
- 灾难恢复与监控:与系统安全的联系是显而易见的,因为我们必须准备的灾难中就包括安全漏洞。但更一般地说,任何数据丢失都是安全故障。我们稍微讨论了可能感染备份系统或备份数据本身的恶意软件,并提到了在备份机制中对静态数据进行加密的概念。在监控环境中,我们检测不良事件的能力对系统安全和防御能力至关重要。我们需要意识到所记录数据的敏感性,因为这些数据可能包含密码或其他机密或私人数据,这可能影响你将监控外包给第三方提供商的能力。
- 配置管理系统:我们说明了用户角色和服务定义,这可能允许我们提供更细粒度的访问控制和服务配置文件。但我们也指出了使用配置管理系统的固有权力和风险,其中CA定理可能对我们系统的安全性产生直接影响。
是的,正如学期初所承诺的,我们确实在每一节课中都包含了安全的某些方面。如果你在我们完成这个迷你系列后回顾早期的视频,希望你会发现我们在这里也涵盖了很多领域,并且你现在可能看到需要更深入地研究安全相关方面的更多领域。

我们发现,系统安全确实触及一切,每个主题,我们面临的每个问题,以及我们开发的每个解决方案。
那么,安全究竟是什么?
让我们问问词典。首先,我们发现安全被递归地定义为“处于安全状态”。所以,谢谢《国际英语协作词典》。

但韦氏词典有一个更好的定义:免于风险。这是一个相当不错的初步定义。
然而,在计算环境中,它包含更多内容。具体来说,我们在这个词条中看到了一个很好的细分:机密性、完整性、身份验证、访问控制、不可否认性、可用性、隐私、物理安全、操作安全、人员安全、系统安全和网络安全。它还提到加密是我们可能用来在某些领域实现某种安全的方法之一,这一点我很喜欢,因为重要的是不要将加密等同于安全。当然,我们注意到科罗拉多州有一个叫“Security”的城镇,人口近3万,但这真的不相关。
但好吧,这实际上很有用。在接下来的几个视频中,我们将实际分解其中几个重要方面。但我想回到我们看到的第一个有用的定义:免于风险。这是我们第二次看到对风险的强调。所以也许让我们去看看风险是什么。
理解风险 ⚠️

风险被定义为危险的来源,遭受损失的可能性。这真的很重要。风险不是一场有保证的灾难。它是发生可怕事情的可能性。


但要评估特定风险的影响,我们需要确定具体是什么处于风险之中。确实有许多我们可能面临失去危险的东西,例如对数据的访问(如勒索软件的情况),或者我们可能担心数据的完整性,担心有人可能更改了它。我们可能担心数据的可用性(想想拒绝服务攻击)。任何对我们的数据发生的坏事也可能带来间接后果,例如声誉损失,这意味着你可能会失去用户和客户。当然,金钱使世界运转,我们可能担心由于上述任何原因而损失我们宝贵的金钱。在某些情况下,我们甚至可能担心某些事物的物理价值。在数据驱动的科技世界中,这与数据访问或完整性的价值是完全不同的威胁。

所以你看,显然有不同的东西可能处于风险之中,而每一种都显然以不同的方式面临风险,因此应该以不同的方式加以保护。

因此,为了我们在“保护某物”方面取得任何进展,我们必须——尽管这听起来可能很无聊——进行风险评估。我知道,这听起来像是一个能让你自己睡着的主题,但请稍等片刻。

进行风险评估 📊
我们所说的风险评估是什么意思?首先,我们需要准确确定处于风险中的是什么。正如我们刚才所说,我们需要识别我们想要保护的资产。每项资产面临不同的威胁,所以让我们为每一项识别这些威胁。也就是说,我们需要确定每项资产究竟什么可能构成危险。
每个威胁可能以不同的方式表现出来。我们称这些方式为漏洞,即系统中的弱点、滥用协议、工具甚至人类行为的方式。
但是,利用这些漏洞中的每一个都不是必然的,并且每一个被利用时也不一定会导致完全的系统沦陷。这就是为什么我们需要确定或至少估计损害发生的概率,包括我们可能已经实施的任何可能的缓解措施。例如,如果我们担心数据丢失,但我们有一个每分钟创建快照的文件系统,实际数据丢失的概率就会降低。
另一方面,我们还必须考虑当损害确实发生时我们该怎么做。我们如何恢复?我们必须做什么才能重新启动和运行?我们必须重建系统、更换硬件、开展广告活动以试图向用户保证我们的可信度吗?所有这些都让我们了解到如果发生这样的灾难,情况会有多糟糕。
然后,我们需要反过来问自己,防范这种威胁需要花费多少成本。我们能防范这种情况吗?如果我们拥有无限的资源呢?实施缓解策略的成本是多少?
有了这个成本计算,我们就会得出几个粗略的数字:灾难的成本(即如果发生灾难,我们会损失多少)、灾难发生的概率以及防御该场景的成本。有了这些数字,你就可以做出相当合理的逻辑决策,并确定投资于该场景的防御是否有意义。
换句话说,我们确定风险的方法是评估威胁成功利用漏洞的可能性,以及由此可能产生的短期和长期估计成本或潜在损害。这就是风险评估。这听起来很简单。有时确实如此。但当然,通常需要一些实践才能真正识别所有相关因素并排除不相关因素。
但这里的关键要点是:风险是具体的、可量化的。大多数时候,当我们面对关于抽象意义上的安全的讨论时,很容易陷入只以模糊、理论化的方式谈论非常模糊的问题的陷阱。这种讨论中可能会掺杂很多恐惧、不确定性和怀疑。所以请始终记住,为了能够解决、缓解或最小化风险,它需要被具体界定、明确定义。只有这样,你才能对它采取行动。
如何保护系统?
那么,我们如何保护一个系统呢?答案是:你无法做到。不存在“安全”的系统,因为正如我们开头提到的,安全不是系统的一种属性,我们需要具体说明我们的意思。

因此,与其试图使系统变得安全,我们只能通过各种方式最小化或解决特定的风险。我们可以尝试关闭攻击向量。我们可以尝试消除允许威胁显现的漏洞。即使我们无法消除漏洞或关闭攻击向量,我们也可以尝试减少攻击面。或者,这是另一个经常被忽视的强大机制:我们可以改变攻击者的经济成本。因为请记住,就像我们在评估风险时进行成本效益计算一样,攻击者也会计算尝试利用给定漏洞是否值得他们花费时间。例如,如果我们能提高攻击者的成本,那么他们可能会寻求其他途径,或者被迫完全撤退。

但为了我们能够做到上述任何一点,我们不仅需要了解我们的风险,还需要了解我们的攻击者、他们的动机、他们的目标,以及我们自己的能力。也就是说,我们需要开发并利用一个威胁模型,这是一种正式评估谁可能尝试追求哪些攻击向量以达到何种目的的方法。
但更多关于威胁模型的内容将在下一个视频中讨论。
总结

本节课中,我们一起学习了系统安全的基础。我们明确了安全不是系统的终极目标,而是系统的一种属性,用于抵御特定风险。我们回顾了本学期各技术层次中涉及的安全考量。最重要的是,我们学习了风险评估的核心流程:识别资产、分析威胁与漏洞、评估损害概率与影响、计算防御成本,并据此做出决策。记住,只有具体、可量化的风险,才能被有效地管理和缓解。在下一节中,我们将深入探讨威胁模型,以更好地理解我们的对手。
047:定义威胁模型 🔐
在本节课中,我们将学习如何定义威胁模型。这是系统安全风险评估的关键步骤,帮助我们明确需要保护什么、防范谁,以及如何有效地分配防御资源。
上一节我们介绍了风险评估的概念,并得出结论:与其追求绝对安全,不如进行现实的风险评估,识别并最小化特定风险。为了做到这一点,我们需要一个清晰的威胁模型。本节中,我们将探讨威胁模型的定义、形式化方法,并强调理解攻击者动机和能力的重要性。
识别保护对象与攻击者
与进行风险评估类似,构建威胁模型也需要从识别我们想要保护的具体资产开始。正如上次提到的,不同的资产会引导我们发现不同类型的威胁和漏洞。
然而,我们上次没有深入讨论的是:谁可能试图利用这些漏洞。这一点对于确定缓解策略至关重要。攻击者是具有特定目标的人类。攻击行为通常不是随机的,而是带有明确的意图和期望的结果。
不同的攻击者有不同的动机,更重要的是,他们执行攻击的能力也不同。一个受挫的青少年与一个拥有军事工业复合体支持的、训练有素专家团队的国家级攻击者,他们的目标和伤害能力截然不同。
这同时也意味着,有些威胁是你根本无法防御的,将资源浪费在无能为力的事情上是没有意义的。
威胁模型的核心:关注与能力

在这个背景下,可以参考James Mickens几年前在《Usenix ;login:》杂志上发表的一篇文章,它精炼地总结了威胁模型的概念。
- 如果你的担忧是前任伴侣入侵你的邮箱,那就更改密码。
- 如果你的担忧是犯罪分子试图接管你的账户,那就不要点击邮件中的链接,并在不使用密码管理器或多因素认证的情况下登录网站。
- 但如果你面对的是一个有能力和动机的情报机构,那么你很可能无法保护自己。
当然,这听起来有些悲观。对于系统管理员和信息安全行业从业者来说,我们确实有兴趣防御此类对手。因此,我们需要一个比“密码和魔法护身符”更正式的方法来定义威胁建模过程。
形式化威胁建模:交集与接受风险
让我们来分解这个过程。首先,我们识别已知的可能威胁。然后,我们需要确定我们真正关心哪些威胁。例如,我们可能并不关心硬件的物理价值。如果一个小偷偷走了服务器,我们并不太在意硬件的金钱损失,我们主要关心的是数据。
接下来,有些威胁是我们能够防御的。这部分很关键。我们必须诚实地评估自己的防御能力。例如,当将硬件管理外包给亚马逊EC2时,我们理解一个强大的对手可能通过法律手段迫使亚马逊在我们的虚拟机运行的虚拟机管理程序中安装后门。但鉴于我们当前的能力和基础设施需求,我们默认接受了这个无法防御的威胁。
接受某些威胁超出我们的防御范围,并不意味着举手投降,而是保持现实,决定不将时间和金钱浪费在最终徒劳的努力上。
因此,我们从这些“圆圈”中构建出我们决定防御的威胁交集。这个交集是“所有已知威胁”与“我们关心的威胁”以及“我们能防御的威胁”的交集。
这意味着,会存在一些我们知道但不关心的威胁,或者一些我们不知道但即使知道了也不关心的威胁。在这些情况下,由于我们已将它们置于威胁模型之外,所以不成问题。
那些我们知道、关心但决定不采取任何措施的威胁,通常被称为接受的风险。你理解存在风险,但愿意承担它。有时这个决定是被迫的,因为你无法防御,就像前面提到的EC2虚拟机硬件被入侵的例子。
总的来说,威胁建模练习的目标是尽可能扩大这个交集。你应该小心,不要将时间和精力浪费在你无论如何都无法防御的威胁上,尤其是当你甚至不关心某个特定威胁时。当然,最大的问题是存在一些你不知道的威胁。特别是那些如果你知道本可以防御的威胁,处理起来尤其困难。因此,作为系统管理员,持续关注行业和信息安全领域的发展至关重要。
使用STRIDE模型枚举威胁
但以上内容仍然很抽象。让我们看一个更正式的模型,它可以帮助你识别不同的威胁。其中一个模型是STRIDE,它将特定威胁按类别映射到给定的系统属性。
以下是STRIDE模型的映射关系:
- Spoofing(伪装):针对真实性属性的威胁。
- Tampering(篡改):针对完整性属性的威胁。
- Repudiation(抵赖):针对不可否认性属性的威胁。
- Information Disclosure(信息泄露):针对机密性属性的威胁。
- Denial of Service(拒绝服务):针对可用性属性的威胁。
- Elevation of Privilege(权限提升):针对授权属性的威胁。
对于每个属性,你可以开始枚举漏洞。你可以将数据模型分解为不同的属性,并识别针对机密性属性的威胁可能出现在哪里。每个领域都可以进一步细分,以识别更具体的威胁。可以根据需要在每一层上增加细节,直到绘制出所有可能出错事项的清晰图表。
因此,STRIDE允许我们在不同系统组件之间进行缩放,而不会陷入试图保护所有东西的困境。但在这个阶段,我们只是在枚举威胁。我们如何确定应该关注哪些呢?

使用DREAD方法进行优先级排序
为此,我们可以使用DREAD方法。是的,极客们喜欢首字母缩写词,这并不奇怪。
DREAD允许你通过回答以下问题,为每个威胁分配一个数值:
- Damage(损害):攻击成功会造成多大损害?
- Reproducibility(可复现性):攻击是否容易复现?
- Exploitability(可利用性):发动攻击有多容易?
- Affected users(受影响用户):有多少用户会受到影响?
- Discoverability(可发现性):漏洞有多容易被发现?
有些人还喜欢添加另一个因素:检测给定入侵的难度。然后,你可以根据这些因素以及资产的价值为每个漏洞分配数值。例如,为每个因素分配一个1到10之间的数字,计算DREAD分数的平均值,再乘以资产价值。得到的分数可以让你了解应该优先关注哪些领域。
实践示例:整合资产、威胁与攻击者

以下是一个DREAD电子表格的示例。我们首先在完整性和机密性类别中定义资产,识别不同的信任边界,并为它们分配一个数字重要性。这可以帮助我们确定防御的优先级。
我们还会遍历可能的威胁行为者列表,这有助于澄清对手可能具有的不同目标以及他们常用的方法。每个威胁行为者都有一系列附加属性,帮助我们明确他们可能具备的能力以及他们可能采取的行动。
在完成所有这些定义后,我们使用左侧的DREAD方法来逐项列出单个威胁,然后为损害、可复现性、可利用性等每一项分配数值。将这些数字平均后乘以资产重要性,就得到了最终的数字分数。
这个分数告诉我们,当前最需要关注的关键防御领域是SQL注入、库漏洞和账户泄露。

攻击者的视角:成本与收益
当然,关于威胁模型的讨论离不开引用这张XKCD漫画。它精辟地指出,有些威胁你无法很好地防范。强加密只对加密攻击有效,动机足够强烈的攻击者会找到系统中最薄弱的部分——通常是人为因素——并攻击那里。
顺便说一下,人为因素不仅容易受到实际的身体暴力攻击,提供大笔金钱也容易导致账户泄露。采用哪种方法再次取决于具体的攻击者。这是攻击者会寻找“最低垂果实”的另一个例子。攻击者会继续使用最便宜、最有效的攻击方法,直到它不再有效。
如果攻击者可以通过一些简单的PHP或SQL代码注入来入侵你的基础设施,没有人会去使用一个价值百万美元的零日漏洞。也就是说,你的攻击者也在遵循他们自己的成本效益计算。他们也有损失,并且会在他们的道德和经济框架内理性行事。

只要攻击者从某个攻击角度或向量中获得的收益大于其成本,他们就会继续追求它。他们在漏洞利用上花费的时间和精力越多,他们继续使用它的可能性就越小。
提高防御有效性的策略
那么,一种对你有利的转变方式是提高攻击成本,使他们更难进入系统。例如,多因素认证显著提高了攻击成本。
但还有另一件事可以做,而我们常常忘记:降低资产价值。匿名化用户数据、在一定时间后删除用户的私人数据、使认证令牌或密钥材料过期、缩短密钥的生命周期——所有这些都非常有效。

总结威胁建模流程
总结我们的威胁建模流程:
- 识别资产并分配价值:这些价值在很大程度上可以是任意的,但你应该在组织内部保持一致,并大致按比例分配。在我的例子中,我选择了0到10的数字。如果需要更细的粒度,可以使用更大的范围。
- 努力识别威胁并使用DREAD方法得出威胁分数:如果你在使用这些数字时保持一致,就会得到一个按优先级排序的、简洁的列表,告诉你防御工作的重点。
现在,威胁建模最困难的部分是保持专注。正如我们在示例中看到的,你可以在各个组件之间进行缩放。虽然你经常用抽象术语概述威胁模型,但在将其转化为具体建议时,可能需要深入细节。
当你尝试执行这些评估时,请记住,你无法同时防御所有威胁。要确定优先级并选择你的战场。记住,攻击者总是会寻找“最低垂果实”,不要试图在所有地方都追求完美的安全性,而是先“止血”,专注于重要的事情。
提高攻击成本,而不是消除整个威胁,通常就足够了,因为你的对手也是人。他们在追求目标时会做出理性决策。理解他们的目标和目的,你就能更好地集中你的防御。
好的,我想这大致涵盖了我们对威胁建模概念的快速介绍。现在,在我们的工具箱中有了这个概念,我们将在下一个视频中探讨信息安全的几个核心原则。我们将讨论零信任模型,并看看密码学如何帮助我们提高攻击成本。

下次见,感谢观看。
048:从攻击生命周期到零信任 🔒
在本节课中,我们将学习系统安全的核心概念。我们将探讨攻击者如何遵循一个通用的生命周期来入侵系统,并了解如何基于此构建防御模型,最终引出“零信任”这一现代安全理念。
上一节我们讨论了如何从攻击者的视角建立威胁模型。本节中,我们将看看攻击者为达成目标通常会遵循的通用流程,以便我们能据此构建防御概念模型。
请注意,这不是针对特定攻击向量的讨论。我们不会讨论攻击者是通过网络钓鱼获取员工凭证,还是利用代码注入攻击。这些是既定事实。我们将站在更高层面,审视攻击生命周期的通用阶段,以及这如何引导我们走向防御策略,最终到达信息安全领域近年来的一个热门词汇:零信任。
纵深防御原则 🛡️
系统安全的一个核心原则是纵深防御。这意味着我们不会仅部署单一的防御机制就高枕无忧。相反,我们会在多个不同层面应用各种防护性和检测性控制措施,采用“皮带加背带”的双重保险方法。我们需确保没有任何一层假设其他层会保护它,或认为任何一种机制就足够了。
这与我们的风险评估和威胁模型概念非常契合。在评估中,我们识别了整个环境中可能面临不同威胁、因此需要不同保护的各个组件。此外,我们从不假设部署的保护措施是100%无懈可击的。你应始终假设你的防护机制可能被攻破或绕过,然后思考“接下来怎么办”。纵深防御有助于我们将此类安全事件的影响最小化。这种在多层部署防护的概念,将我们引向稍后将详细讨论的零信任模型。

攻击生命周期 🎯
那么,我们究竟需要在何处实施防御?为了明智地决定防护措施的价值,最好了解对手在发动攻击时可能采取的路径。毕竟,正如上一节所讨论的,攻击者是具有特定目标的、专注的人,因此他们会遵循一条通往最终目标的逻辑路径。这就是攻击生命周期,即我们反复观察到的、任何重大针对性攻击都必然遵循的一系列过程和步骤。


请注意,这里我明确提到了“针对性攻击”,因为一些机会主义攻击者可能会从生命周期的任何阶段介入。但总体而言,攻击者确实遵循我们将要概述的路径。
假设你是一名攻击者,想要获取对特定资产的访问权限。例如,你想获取一家大型电子邮件服务提供商的用户数据。你从哪里开始?
以下是攻击生命周期的各个阶段:
- 初始侦察:攻击者首先需要了解目标。这包括进行网络扫描、识别使用的软件和暴露的服务、探测已知漏洞,以及收集关于系统、公司运营和人员的所有数据。此阶段还包括收集目标组织员工的信息,如职位、访问权限和日常习惯。
- 初始入侵:在此阶段,攻击者识别并利用漏洞,以获取对某些系统的访问权限。这可能表现为利用目标系统已知库中的弱点触发远程代码执行,也可能涉及通过钓鱼手段诱骗员工泄露访问凭证,或通过水坑攻击在员工笔记本电脑上安装恶意软件。
- 建立持久性:一旦找到进入系统的途径,攻击者需要设法保持访问权限,维持系统被入侵的状态。通常,他们会安装一个持久性后门,使他们能够访问系统,而无需依赖最初可能使用的漏洞。
- 权限提升:初始入侵通常不会让攻击者获得他们真正想要的数据,也往往无法提供足够的权限来实现目标,甚至在组织的基础设施内移动。因此,攻击者通常会尝试提升权限。例如,考虑一个PHP漏洞,攻击者以运行HTTP服务器的Unix用户
nobody身份获得了对Web服务器的访问权限。为了访问系统上的私有数据库或获取受Unix权限保护的私钥,攻击者会尝试链接一个额外的本地权限提升漏洞,以成为root用户。 - 内部侦察与横向移动:即使拥有提升后的权限,攻击者也很少能立即攻破最终目标。例如,一个Web应用程序暴露在互联网上的Web服务器可能无法直接访问内部数据存储或最终用户数据库,但该服务器可能有权访问存有通往数据库凭证的服务器。因此,攻击者会进行内部侦察,从最初被入侵的主机(可能已拥有提升的访问权限)扫描网络,识别感兴趣的系统。然后,他们尝试进一步扩大访问范围,从一个系统“跳”到另一个系统,根据需要重复之前的步骤以进一步提升权限。
- 达成目标与数据窃取:攻击者最终到达其任务目标,例如窃取他们觊觎的最终用户数据。

因此,完整的攻击生命周期看起来是这样的。更专业(但远不如用狗狗插图有趣)的图示是:初始侦察 -> 初始入侵 -> 横向移动 -> 达成目标并开始窃取数据。
基于生命周期的防御策略 🛑
理解攻击生命周期的价值在于,它能清晰地分解为各个独立阶段,从而让你能更好地识别具体的防御措施。与其试图“保护一切”,你现在可以考虑如何在每个阶段破坏攻击生命周期。
以下是在各阶段实施防御的示例:
- 破坏初始侦察:尝试减少攻击面,例如限制暴露的系统。
- 破坏初始入侵:强化特定系统和库。
- 破坏横向移动:限制系统间的通信,并使用适当的身份验证和授权控制来保护资产。
- 破坏数据窃取:实施额外的出口流量控制。

请注意,每个阶段都有其独立的防护措施,不依赖于其他机制,也不对环境做出假设。

零信任模型 🚫
这引出了零信任的概念。零信任目前是行业内的一个热门词汇。其核心理念与纵深防御类似,是一种基本假设:假设环境已被入侵或充满敌意。仅此而已。

这听起来可能不多,甚至不新奇,但由此衍生出的实践推翻了几十年的运维惯例。它意味着你可以推动和衡量非常具体的举措。
如果我们假设所有网络都是敌对的,那么:
- 我们必须为所有流量要求传输加密。
- 在敌对环境中运行,要求客户端验证它们所连接的服务,同样,服务也需要验证连接到它们的客户端。双向身份验证成为强制要求。
- 但仅有身份验证还不够。经过身份验证的客户端需要明确的授权才能执行操作,且授权应始终限于所需的最小权限。因此,你需要集成一个细粒度的基于属性的访问控制(ABAC)系统。
- 因为我们假设对手是持久的,可能攻破先前受信任的账户或系统,所以任何已建立的信任都需要定期更新,并且所有操作都需要被记录,以确保完整的审计追踪。
- 系统的访问能力明确源自其身份,因此其访问可以被审计、扩展、限制或撤销,而不是从其在网络中的任何特定物理或逻辑位置隐式继承。
构建公钥基础设施(PKI)、开发合适的基于角色的访问控制(RBAC)、监控、强制执行双向身份验证和加密等,听起来工作量巨大。确实如此,将旧的基础设施迁移到这个新世界需要数年时间。
但这不仅仅是安全上的胜利。零信任支持基于身份的服务部署,具有自动化的访问控制和“诞生即配置”的能力,让你可以抛弃手动配置或高风险的大范围网络操作。并且,你可以逐步实现这些目标。
所有这些规则使你能够具体地破坏攻击生命周期的每个阶段。有些矛盾的是,你甚至可以做出某些反直觉的访问决策,例如,允许从互联网连接到内部服务,因为你将内部网络视为与互联网一样不可信。你可以在部署服务时无需考虑它们必须放入哪个安全区域或网络,同时仍然确信横向移动受到限制,因为所有操作都需要明确的身份验证和授权。
但只有当你不再将其视为一个单一事物、一次性努力、一个产品、一个暂时的行业趋势或一个流行词时,你才能获得这些好处。相反,应将其作为一种思维模式、一个原则、一个核心理念来接受。简而言之:假设环境是敌对的,其余皆由此衍生。
总结 📝

本节课中,我们一起学习了攻击生命周期的各个阶段,从初始侦察到数据窃取。我们了解到,通过在每个阶段部署针对性的防御措施,可以有效地破坏攻击者的进程。这自然引出了“零信任”安全模型,其核心是假设网络内部和外部一样充满威胁,从而要求持续验证、最小权限和严格审计。零信任不是一个可以购买的产品,而是一种需要融入系统设计与运维根本的思维模式和安全原则。

浙公网安备 33010602011771号