谷歌-Python-IT-自动化-三四五六课笔记-全-
谷歌 Python IT 自动化 三四五六课笔记(全)
001:Git与版本控制入门 🚀


在本节课中,我们将学习版本控制系统(VCS)的基本概念,特别是Git工具。我们将探讨如何使用Git来跟踪代码和配置文件的变更,以及如何通过GitHub进行协作和建立个人作品集。
大家好,欢迎回来。我们之前讨论了很多关于编程和自动化的内容。本课程将聚焦于一个略有不同的方面:如何使用版本控制系统(VCS)来跟踪代码和配置文件的不同版本。这些工具对IT领域的每个人都有益,即使不专门用于编程或自动化本身。它们能让我们在出错时轻松回滚,并帮助我们与他人协作。你可能已经在管理配置文件或维护程序脚本源代码的背景下听说过版本控制系统。
在本课程中,我们将向你介绍一个流行的VCS工具——Git,并展示一些使用它的方法。我们还将讲解如何注册一个名为GitHub的服务,以便创建你自己的远程仓库来存储代码和配置。课程结束时,你将能够使用Git存储代码历史,并在GitHub上与他人协作,同时开始创建自己的作品集。如今,许多公司在招聘IT职位时都会要求查看你的GitHub作品集。GitHub作品集能让公司了解你参与过的项目和你编写的代码类型。本课程将帮助你建立自己的作品集。
我是Kenny Soiman,我将担任本课程的讲师。我目前担任Android系统健康与验证的技术项目经理。在我的工作中,我与工程团队和领导层合作,确保为用户发布健康稳定的Android设备。我很高兴能担任本课程的讲师。作为技术项目经理,我的主要挑战是确保所有相关人员对共同愿景保持一致。为了确保项目成功,我的团队会制定叙事、找出所有利益相关者,并确保每个人都在同一页面上。之后是最困难的部分:执行项目直至完成。为此,拥有一个版本控制系统至关重要,每位工程师都可以在其中存储和分享他们创建的代码。这让我们能够跟踪不同的修订版本、回滚有问题的变更,并高效地协作。
在开始之前,我想花一点时间分享我为什么如此兴奋能在这里与大家一起参与这个项目。从小时候起,我就一直痴迷于新技术。我曾经以小额费用修理朋友们的旧坏电脑,并花费数小时尝试修改我的旧视频游戏系统,以便从我最喜欢的游戏中获得更多乐趣。所以,当我发现可以通过从事计算机和酷炫小工具的工作来谋生时,我立刻被吸引了。但有一个问题:当我开始我的第一份IT工作时,很少有人看起来像我。随着我职位的提升,这种差距只会越来越大。我很快意识到,这不仅是IT支持的问题,而是整个科技行业都在努力解决的问题。代表性问题是我想有一天解决的问题。我能做到这一点的唯一方法是以身作则、分享我的知识,并帮助尽可能多的人实现他们的目标。谁知道呢?也许有一天,有人会利用在这里学到的信息来改变世界。记住,当你成名时,记得我哦?好了,关于我的话题就到这里,让我们回到课程。
Git和版本控制有很多内容需要学习。我们将逐步分解,以便你完全理解它的工作原理以及为什么它在你的IT角色中如此有用。无论你是IT支持专家、系统管理员,还是希望扩展技能以转向IT其他角色,在本课程中,你将学习Git的核心功能,以便理解它在组织中如何以及为何被使用。我们将探讨基础和更高级的功能,如分支和合并。我们将演示掌握像Git这样的VCS的实用知识如何在紧急情况或调试时成为救命稻草。我们还将探索如何使用VCS通过远程仓库(如GitHub提供的仓库)与他人协作。
为了完成这些内容,并让你能够跟随视频中的练习,你需要在计算机上安装Git。这也将让你能够与GitHub交互,并将代码上传到那里。在本课程的示例中,我们将展示一系列不同的Python脚本。虽然使用Git不需要了解任何Python知识,但我们建议你具备该语言的基础知识,以便理解我们将演示的示例和功能。如果你已经完成了本项目的Python课程,那么你已经具备了所需知识。如果没有,也没关系,但你可能需要复习一些Python技能以跟上我们的部分示例。此外,由于所有脚本都将使用Python 3,你需要在计算机上安装Python 3才能运行它们。
对于我们的示例,我们将使用Linux计算机,并通过最常见的命令行工具与Linux命令行交互。同样,如果你参加了我们的Python课程,你已经熟悉了所有这些概念。如果你是从本课程开始加入这个项目的,复习一些最基本的Linux命令可能会对你有益。请记住,其中一些主题和视频有点复杂,所以第一次可能无法完全理解。这完全正常。请慢慢来,复习任何不完全清楚的内容。你最终会掌握的。另外,别忘了你可以随时使用讨论论坛与其他学习者联系并提出问题。
好了,我们准备好开始学习Git和版本控制了。让我们开始吧!
本节课中,我们一起学习了版本控制系统(VCS)的基本概念,特别是Git工具的重要性。我们了解了Git如何帮助跟踪代码变更、协作开发,以及如何通过GitHub建立个人作品集。接下来,我们将深入探讨Git的具体操作和功能。
002:版本控制介绍 🚀


在本节课中,我们将要学习版本控制的基本概念及其在IT自动化中的重要性。我们将探讨为什么需要版本控制,以及它如何帮助我们管理代码和配置文件的变更。
当你在IT领域工作时,你需要管理许多不同文件中的信息。
你编写的自动化脚本可能会随着时间推移而演变。例如,你可能会为脚本添加新功能,或考虑额外的条件,或修改脚本执行所涉及的系统范围。
你还需要管理与基础设施相关的配置,例如应用程序的默认设置或分配给计算机群的IP地址。
随着安全要求的提高、计算机群的扩大或新软件版本的部署,这些信息会随时间而变化。
在IT中管理变更时,拥有组织配置文件和自动化代码的详细历史信息至关重要。
这使管理员能够查看修改了什么以及何时修改的,这对于故障排除至关重要。它还提供了文档记录,让未来的IT专家了解基础设施为何是当前的状态。

此外,它提供了一种完全撤销变更的机制。这样,我们就不必凭记忆撤销更改,从而减少了人为错误的机会。我们将在讨论回滚时看到这一点。

想象一下这个场景:你的团队为负责检查所有计算机健康状况的脚本添加了一项新功能。这项新检查会验证计算机的固件(也称为UEFI)是否已更新到最新版本。
当你推出此更新时,突然发现一半的计算机现在报告为故障。经过一些调查,你发现检查需要考虑不同的计算机型号。

你可能会想快速修复代码,并立即将其推送到受影响的机器上,尤其是当这看起来是一个简单的修复时。但通常情况下,快速修复本身会包含新的错误,因为我们没有花时间正确测试新代码。因此,在第一次修复之后,你可能最终需要进行第二次甚至第三次紧急推送,直到一切真正正常运行。


为了避免这些麻烦,你可以使用版本控制系统轻松地将代码回滚到之前的版本。


由于你知道在做出更改之前,这个版本是正常工作的,因此回退到该版本是安全的,直到你有时间修复代码、运行一些测试,并确保所有机器型号都能正常工作。
只有在经过适当测试后才发布代码,可以避免一次又一次地推送快速修复。版本控制系统让我们能够做到这一点,甚至更多。它们对于维护各种IT资源的健康代码库以及让多人在同一编码项目上协作至关重要。


现在,我们将迈出学习这个新工具的第一步。它将让我们能够跟踪对脚本、配置文件以及任何其他需要跟踪的文档所做的更改。
我们将首先看看人们在不知道版本控制的情况下通常会做什么,然后查看一些相关工具,如diff和patch。
一旦我们清楚地了解了为什么需要适当的版本控制,我们将开始我们的第一次Git体验。我们将讨论Git是什么以及它是如何工作的。
要跟上课程,你需要在本地计算机上安装Git,并学习如何从命令行使用它。如果这听起来有点吓人,请不要惊慌。我们将指导你,你很快就能上手使用它。
在你的计算机上安装Git后,我们将概述基本的Git工作流程,这将让你开始跟踪你的脚本。
那么,你准备好开始掌控你的代码了吗?我们开始吧。
本节课中,我们一起学习了版本控制的基本概念及其重要性。我们了解到,版本控制可以帮助我们跟踪变更、协作开发,并在出现问题时安全地回滚到之前的稳定状态。在接下来的课程中,我们将深入探讨具体的工具和操作。
003:Git版本控制入门 - 保留历史副本 📜


在本节课中,我们将要学习版本控制的基本概念,特别是如何通过保留历史副本来管理项目的变化。我们将探讨手动版本控制的局限性,并介绍Git如何帮助我们更有效地跟踪变化。

你是否曾参与过一个随时间发展的项目,因此偶尔会创建工作副本,以便在需要时能够回到早期版本?也许你正在一个团队中工作,每天都会将部分工作通过电子邮件发送给团队的其他成员。

然后,你团队中的其他成员会添加他们自己的工作,并将其发送给整个团队。
或者,你可能参与过一个方向不断变化的复杂项目。你感觉某天被删除的一些内容可能以后需要重新添加。因此,每当你要删除一个重要部分时,你都会复制整个项目以防万一。
如果以上任何一点听起来很熟悉,那么你已经接触过最原始的版本控制形式:保留历史副本。这些副本让你可以看到项目之前的样子,并且如果你最终认为最新的更改是错误的,可以回退到那个版本。它们还让你能够看到随时间推移的变化进展,甚至可能帮助你理解为什么进行了某项更改。
我们说这是原始的形式,因为它非常手动且不够详细。首先,你需要记住制作副本。其次,你通常复制整个项目,即使你只更改了一小部分。


第三,即使你将更改通过电子邮件发送给同事,也可能很难弄清楚最终是谁做了什么,更重要的是,他们为什么这样做。

尽管如此,版本控制背后的原理是相同的。它让我们能够跟踪文件中的更改。这些文件可以是代码、图像、配置文件,甚至是视频编辑项目。无论你正在处理什么。在本课程中,我们将看到Git帮助我们跟踪更改的多种方式,以及我们如何使用它与他人协作或回退更改。我们将使用一些在版本控制领域具有特殊含义的术语。

但不要让这些术语吓到你。归根结底,我们所做的只是更好地控制我们的历史副本。
假设你有同一代码在不同时间点制作的两个副本。你如何比较它们?请继续观看下一个视频,你将会找到答案。
本节课中我们一起学习了版本控制的基本目的——通过保留历史副本来跟踪和管理项目的变化。我们认识到手动方法的局限性,并初步了解了Git等现代工具如何提供更高效、更详细的解决方案。在下一节中,我们将深入探讨如何具体比较这些历史副本。
004:比较文件差异 📄

在本节课中,我们将学习如何使用命令行工具自动比较两个文件或目录之间的差异,而无需手动逐行检查。我们将重点介绍 diff 工具及其不同输出格式,并简要提及其他可视化比较工具。
概述
想象一下,你拥有同一代码的两个副本,并且希望查看它们之间的差异。
你会怎么做?你可以在编辑器中并排打开两个文件,先看一个,再看另一个,以找出差异。但这种方法极易出错。我们是人类,仅凭肉眼比较,注定会遗漏一些差异。
幸运的是,存在更好的方法。你可以使用一些精巧的工具来自动完成此任务。
我们可以使用 diff 命令行工具来比较两个文件甚至两个目录,并以几种格式显示它们之间的差异。
使用 diff 命令
让我们通过一个例子来查看。我们有两个文件:rearrange1.py 和 rearrange2.py,它们包含同一函数的两个不同版本。
首先,使用 cat 命令查看它们的内容:
cat rearrange1.py
cat rearrange2.py
你能发现差异吗?也许可以,但这并不十分明显。
让我们使用 diff 命令,这样我们就不必费力地用眼睛去发现差异了。
diff rearrange1.py rearrange2.py
当我们调用 diff 命令时,它只输出两个文件之间不同的行。
当我们只有两行输出时,找到差异就容易多了,对吗?
注意每行开头的符号:
<符号告诉我们该行从第一个文件中被移除了。>符号告诉我们该行在第二个文件中被添加了。
换句话说,旧行被新行替换了。
在这个例子中,我们有一行被新的一行替换。这是在修改代码时常见的变化,但不是唯一可能的情况。
理解 diff 的输出格式
让我们查看另一个例子。这里有更多的变化发生。
我们可以看到 diff 将变化分成两个独立的部分:
- 以
5c5,6开头的部分显示第一个文件中的一行被第二个文件中的两行不同内容替换了。- 开头的数字表示第一个文件和第二个文件中的行号。
- 数字之间的
c表示一行被更改了。
- 以
11a13,15开头的部分显示第二个文件中新增的三行。a代表添加。
但第二个部分看起来有点奇怪,不是吗?看起来我们添加了一个 return 和一个 if 条件,但没有 if 块的主体。这是怎么回事?
为了更好地理解这一点,我们可以使用 -u 标志来告诉 diff 以另一种格式显示差异。
让我们查看一下:
diff -u rearrange1.py rearrange2.py
这种统一格式与我们之前看到的格式有很大不同。
它显示了带有一些上下文的变更行,使用减号 - 标记被移除的行,使用加号 + 标记被添加的行。
额外的上下文让我们能更好地理解我们正在查看的变更。我们可以看到新文件实际上有一个全新的 if 块,它是看起来非常相似的条件链的一部分。这就是为什么我们之前看到的 diff 输出中,哪些行被添加了有点令人困惑。
其他文件比较工具
市面上有很多工具可以比较文件。diff 是最流行的一个,但不是唯一可用的。
例如,wdiff 会高亮显示文件中发生变化的单词,而不是像 diff 那样逐行工作。
为了提供更多帮助,还有许多图形化工具可以并排显示文件,并使用颜色高亮差异。

这类工具的例子包括 MELD、Kdiff3 或 Vimdiff。
我们可以使用这些工具来为我们看到的变更提供更好的上下文。
总结
在本节课中,我们一起学习了如何自动比较文件差异。我们介绍了 diff 命令行工具的基本用法,理解了其默认输出和统一格式(-u)输出的含义。我们还了解到,除了 diff,还有像 wdiff 以及 MELD 等图形化工具可以帮助我们更直观地进行比较。
我们已经讨论了如何查看两个文件之间的差异。那么,如何利用这些差异来应用变更呢?这将在下一个视频中介绍。
005:应用更改 🛠️

在本节课中,我们将学习如何通过生成和应用差异文件(diff文件)来高效地协作修改代码。我们将重点介绍diff和patch这两个命令行工具,它们能帮助我们清晰地展示代码变更,并自动将这些变更应用到原始文件中。
概述
想象一下,一位同事向你发送了一个存在错误的脚本,并请求你帮助修复问题。

当你理解了脚本的问题所在后,你可以向他们描述需要修改的内容。例如,你可以指出:“函数只能返回值,我认为你本意是使用sys.exit。此外,你重复进行了两次千兆字节转换,这会导致脚本始终失败。”
然而,如果代码很复杂,仅靠描述可能仍然难以理解。为了让变更更清晰,你可以向他们发送一个包含变更的差异文件(diff),这样他们就能看到修改后的代码是什么样子。
生成差异文件
为了生成差异文件,我们通常使用如下命令行格式:
diff -U old_file new_file > change.diff
需要提醒的是,大于号 > 将 diff 命令的输出重定向到一个文件。因此,通过这个命令,我们生成了一个名为 change.diff 的文件,其内容就是 diff -U 命令的输出。
使用 -U 标志可以包含更多上下文信息,这有助于阅读文件的人理解变更的来龙去脉。生成的文件通常被称为 差异文件 或 补丁文件。
它包含了旧文件和新文件之间的所有变更,以及理解这些变更并将其应用回原始文件所需的额外上下文信息。
应用差异文件
现在,假设你是接收包含变更的差异文件的一方,并且你想将它应用到你编写的脚本上。
你可以仔细阅读收到的差异文件,然后手动遍历需要更改的文件并应用修改。但这听起来像是大量可以自动化完成的手动工作,不是吗?
确实如此。有一个名为 patch 的命令正是用来做这件事的。patch 命令接收由 diff 生成的文件,并将变更应用到原始文件上。
让我们通过一个例子来查看这个过程。假设我们有一个检查计算机负载是否过高的小脚本,如下所示:
# 示例脚本:检查CPU使用率
import psutil
cpu_usage = psutil.cpu_percent()
if cpu_usage > 75:
print("错误:CPU负载过高!")
else:
print("一切正常。")
这个脚本使用 psutil 模块检查当前正在使用的CPU百分比。当负载高于阈值(本例中为75%)时,它会打印一条错误信息。当负载低于阈值时,它则报告一切正常。
我们将这个脚本分享给了几位同事,其中一位告诉我们脚本运行不正确。即使计算机完全超载,脚本也会报告一切正常。这位同事非常热心,他们给我们发送了一个包含问题修复方案的差异文件。
让我们查看一下这个差异文件。我们可以看到同事做了两处修改:
- 他们为
cpu_percent函数添加了参数1。 - 他们添加了一行调试代码,用于打印函数返回的值。
同事解释说,在不带参数调用 cpu_percent 函数时,我们没有对一段时间进行平均,因此调用总是返回 0。
现在我们有了差异文件,我们想将它应用到我们的脚本上,该怎么做呢?
我们将使用 patch 命令。我们将把想要打补丁的文件名(本例中是 cpu_usage.py)作为命令的第一个参数传递。然后,我们将通过标准输入提供差异文件。还记得怎么做吗?我们将使用小于号 < 将文件内容重定向到标准输入。
让我们实际操作一下:
patch cpu_usage.py < cpu_usage.diff
我们告诉 patch 命令,将来自 cpu_usage.diff 的变更应用到我们的 cpu_usage.py 文件上。我们得到一行输出:“文件已打补丁”,这意味着我们已成功应用了变更。
让我们通过查看脚本内容来验证一下。很好,我们看到文件已按照同事提供的变更进行了修改:cpu_percent 函数现在以参数 1 被调用,并且添加了调试打印行。

当我们对脚本满意后,可以移除调试行,但目前我们先保留它。
为何使用差异与补丁?
你可能会想,为什么要经历所有这些“差异”和“补丁”的麻烦,而不是直接发送整个文件呢?这有几个原因。
主要原因是原始代码可能已经发生了变化。在我们的例子中,同事用来准备修复的代码可能不是最新版本。通过使用差异文件而不是整个文件,我们可以清楚地看到他们修改了什么,无论他们使用的是哪个版本。patch 命令能够检测到文件已做的更改,并会尽力应用差异。虽然它并非总能成功,但在许多情况下是可以的。
另一个原因是项目结构。在这个案例中,我们是为单个小文件打补丁。但有时你可能需要修改一个庞大项目中的一堆大文件。假设你正在修改一个包含100个不同文件、按功能排列在不同目录中的项目树里的4个文件。如果你发送整个文件,你需要指定这些文件应该被放置在哪里。正如我们提到的,我们可以对整个目录结构进行差异比较,在这种情况下,差异文件可以指定每个变更文件应该放置的位置,而无需我们进行任何手动操作。很酷,对吧?
总结
在本节课中,我们一起学习了如何生成差异文件以及如何使用 patch 命令应用其内容。我们了解了通过 diff -U 生成包含上下文的补丁文件,以及使用 patch <file> < diff_file 来应用变更。我们还探讨了在协作中使用差异和补丁方法相对于直接发送整个文件的优势,特别是在处理可能已更改的代码或复杂项目结构时。
在下一个视频中,我们将把所有内容整合起来,看一个在现实世界中如何使用 diff 和 patch 的实际例子。
006:差异和补丁的实际应用 🛠️

在本节课中,我们将学习如何在实际场景中应用 diff 和 patch 命令。我们将通过帮助同事修复一个存在错误的Python脚本来演示整个过程,从识别问题、生成修复补丁,到最终应用补丁使脚本恢复正常。
概述
想象这样一个场景:一位同事请求我们帮助修复一个名为 disk_usage.py 的脚本。该脚本的目标是检查当前已使用的磁盘空间,并在空间不足时打印错误信息。然而,脚本目前因存在几个错误而无法运行。我们将通过修复这些错误来演示如何使用 diff 和 patch 工具。
准备文件副本
在进行任何修改之前,我们首先为脚本创建几个副本。这是为了避免直接修改原始文件,并保留一个干净的版本用于比较。
以下是具体步骤:
- 我们将一个副本命名为
disk_usage_original.py,它将保持未修改状态,用于后续的比较。 - 我们将另一个副本命名为
disk_usage_fixed.py,我们将在这个文件上准备我们的修复。
现在我们已经有了文件的副本,接下来我们将编辑 disk_usage_fixed.py 版本并实际修复其中的错误。
识别并修复错误
这个文件包含一些代码。在我们尝试理解其功能以及问题所在之前,让我们先执行它,看看会得到什么结果。
python3 disk_usage_fixed.py
Python解释器报错了。它提示“在函数外使用了return语句”。查看代码,我们可以清楚地看到有一个 return 语句不在任何函数内部。
你可能记得,在Python中,return 语句只能在函数内部使用。那么如何修复这个问题呢?这里有几个选项:
- 我们可以将当前代码转换成一个函数,然后在脚本的主体部分调用这个函数。
- 我们可以使用
sys.exit()来使return的数字成为脚本的退出代码。退出代码是导致程序以相应值退出的代码。
目前,我们选择第二种方案。我们将 return 语句替换为 sys.exit()。
好的,我们已经做了修改。让我们再次执行这个新版本的脚本。
python3 disk_usage_fixed.py
糟糕,我们修复了语法错误,但现在脚本告诉我们磁盘空间不足。但我们知道实际上我们确实有一些可用空间,这是怎么回事?
如果你仔细观察代码,可能会注意到脚本将字节转换为吉字节(GB)时进行了两次转换。调用 check_disk_usage 函数时传递的参数是 2 * (2**30)。你可能记得,双星号 ** 运算符用于计算幂。在这个例子中,2**30 是1GB对应的字节数。所以,2 * (2**30) 就是2GB。
但这仅当 check_disk_usage 函数期望的参数单位是字节时才成立。如果我们查看该函数的代码,会发现它内部已经将可用字节数除以了 2**30。换句话说,我们进行了两次GB转换:一次在调用函数时,一次在函数内部。我们需要去掉其中一次转换。让我们修改调用函数的方式,直接传递 2(表示2GB)作为参数。
# 修改前
check_disk_usage(“/”, 2*2**30, 2*2**30)
# 修改后
check_disk_usage(“/”, 2, 2)
好的,让我们再试一次。
python3 disk_usage_fixed.py
现在它正常工作了。
生成并应用补丁
现在我们需要将修复发送给同事,以便他们可以修复自己的脚本。为此,我们将使用刚学到的技术生成一个差异(diff)文件,如下所示:
diff -u disk_usage_original.py disk_usage_fixed.py > disk_usage.diff
让我们使用 cat 命令检查 diff 文件的内容。
cat disk_usage.diff
很好,这个文件包含了我们想要的所有更改。这就是我们需要发送给同事,让他们用来修补自己文件的内容。
那么,他们该如何应用这个补丁呢?他们会像下面这样运行 patch 命令:
patch disk_usage.py < disk_usage.diff

通过使用 diff 文件调用 patch 命令,我们已经应用了修复错误所必需的更改。让我们检查一下 disk_usage.py 现在是否可以成功执行。
python3 disk_usage.py
成功!
总结
在本节课中,我们一起学习了如何查看文件之间的差异、生成 diff 文件来汇总我们的更改,然后使用 patch 命令应用这些更改。然而,这仍然是一个非常手动的过程,而版本控制系统在这方面可以提供极大的帮助。

在进入下一节关于版本控制的内容之前,你可以在接下来的速查表中找到我们刚刚介绍的命令总结。请查看速查表,然后完成练习测验,以确保你掌握了所有这些知识。
007:什么是版本控制?📚


在本节课中,我们将要学习版本控制系统(Version Control System, VCS)的基本概念。我们将了解它是什么、为什么它比简单的文件备份更强大,以及它如何帮助我们更高效地管理文件(尤其是代码)的变更历史。
概述
到目前为止,我们已经学习了如何使用现有工具来提取文件不同版本之间的差异,并将这些变更应用到原始文件中。这些工具非常有用,但在大多数情况下,我们不会直接使用它们。相反,我们会通过一个版本控制系统(VCS)来使用它们。

版本控制系统会跟踪我们对文件所做的更改。
版本控制系统的作用
通过使用VCS,我们可以知道更改是何时做出的以及是谁做出的。它还能让我们在发现某个更改不理想时,轻松地回滚该更改。此外,通过允许我们合并来自许多不同来源的更改,它使得协作变得更加容易。
初看起来,版本控制系统可能像是一个复杂甚至令人望而生畏的工具。但如果你仔细观察,你会发现它本质上只是一个存储文件的系统。然而,与只保存文件最新版本的普通文件服务器不同,VCS会跟踪我们在保存更改时创建的所有不同版本。
VCS的种类与核心功能
市面上有许多不同的版本控制系统,每种都有其自身的实现方式以及各自的优缺点。但无论VCS在内部如何实现,它们都始终围绕着访问我们文件的历史记录这一核心功能。
它们让我们能够:
- 检索文件或目录的过去版本。
- 查看谁更改了哪些文件。
- 了解每个文件是如何被更改的。
- 知晓文件是何时被更改的。
在此基础上,我们还可以对多个文件进行编辑,并将这组编辑视为一个单一的变更单元,这通常被称为一次 提交(commit)。


提交信息的重要性
VCS甚至提供了一种机制,允许提交的作者记录为什么要进行此次更改,包括此次更改修复了哪些错误、工单或问题。当试图理解一系列复杂的更改或调试某些难以捉摸的问题时,这些信息可能是救命稻草。因此,请务必在你的提交中记录这些额外信息,以真正致力于编写更好的代码。
VCS的应用场景
在任何生产软件的组织中,VCS都是管理代码的关键部分。文件通常被组织在代码仓库(repositories) 中,这些仓库包含独立的软件项目或只是将所有相关代码分组。
如果有很多人参与软件开发,一些开发人员可能只能访问其中的部分仓库。一个仓库的使用者可以少至一人,也可以多至数千名贡献者。
正如我们之前提到的,版本控制系统不仅可以用于存储代码。我们还可以用它来存储配置文件、文档、数据文件或任何其他我们需要跟踪的内容。
文件类型与VCS的适用性
由于像 diff 和 patch 这样的工具的工作方式,VCS在跟踪文本文件时特别有用,因为文本文件可以用 diff 进行比较,并用 patch 进行修改。
我们也可以在VCS中存储图像、视频或任何其他复杂的文件格式,但在比较这些文件格式时,检查版本之间的差异并不容易,并且可能无法自动合并对文件旧版本所做的更改。
总结
本节课中,我们一起学习了版本控制系统(VCS)的基础知识。你现在对版本控制系统是什么以及它如何工作有了基本的了解。
你可能会问自己:我真的需要这个吗?难道我不能只是偶尔备份一下我的代码吗?我们将在下一个视频中回答这个问题。
008:版本控制与自动化 🛠️


在本节课中,我们将学习版本控制系统(VCS)如何帮助IT专家,即使是在单人团队中,也能提升工作效率和系统可靠性。我们将探讨VCS如何像一台“时间机器”一样工作,并了解它在管理代码和配置文件方面的实际应用。
乍一看,为IT专家设置和学习使用版本控制系统似乎需要大量工作。
如果你所在的IT团队中只有你一个人编写代码,甚至整个团队只有你一个人,使用VCS可能显得小题大做。
那么,即使你不需要与他人共享脚本或协作,VCS也能提供帮助吗?
简短的回答是肯定的。即使在只有一人的IT部门中,VCS也具有不可估量的价值。
VCS不仅存储你的代码和配置,还存储这些代码和配置的历史记录。
版本控制系统可以像一台时间机器一样运作,让你洞察过去的决策。
每当你做出更改后编写提交信息时,就像是当前的你在向未来的你(或未来可能处理相同脚本和配置的其他人)解释你的决策。
这可以帮助你避免在三个月后盯着自己或他人编写的一段代码,却困惑于它的工作原理甚至它为何存在。
使用VCS,你可以查看、跟踪和选择项目历史中的快照,因此你所做的任何工作都不会丢失。
由于我们可以使用VCS来存储代码和配置文件,我们可以使整个IT系统更具可扩展性和可靠性。

例如,假设你将公司的DNS区域文件存储在VCS中。

如果你不记得了,DNS区域文件是一个配置文件,用于指定网络中IP地址和主机名之间的映射关系。
当你更新区域信息时,务必使用清晰易懂的提交信息。
这样,你将能够获取区域文件中新IP地址和主机名的元信息,例如它们何时被添加以及出于何种目的。

如果在添加新条目后出现任何问题,你可以依赖VCS来告诉你更改前文件的样子。

然后,你可以快速恢复到旧版本,以便迅速修复问题,稍后再找出错误所在。
由于VCS提供了审计跟踪,此功能增强了你所操作系统的可靠性。你可以确切地知道要将区域文件回滚到哪个版本,从而减少了修复问题所需的时间。
通常,最好先快速回滚并阻止错误,然后再花时间找出问题所在。你可以在“止血”后再进行修复。
找出错误可能会占用宝贵的时间,更糟糕的是,你的首次解决方案尝试可能本身就有错误。
让我们看另一个不同的例子。
DHCP守护进程的配置可以在两台或多台机器上复制,其中一台作为主服务器,另一台作为备用机器。

当主服务器正常运行时,备用机器不会做太多工作。
但如果主服务器因任何原因宕机,备用机器可以变为主服务器并开始响应DHCP查询。为此,所有机器上的配置文件必须完全相同。
这是因为DHCP协议不像DNS那样,提供一种让备用机器获取配置文件最新版本的方式。
为了解决这个问题,我们可以将DHCP配置的最新版本保存在版本控制系统中。

并让机器从VCS下载配置。
这意味着所有机器都将拥有完全相同的文件。这已经足够方便了,但在使用一段时间后,你肯定会看到其他好处。

假设你在周末收到一个紧急警报,告诉你你的DHCP服务器没有响应任何查询。
你查看更改历史,发现周五晚上添加的一个更改包含了一个重复条目,导致服务器行为异常。
通过使用VCS,你可以轻松回滚更改,并使服务器迅速恢复正常。
当需要更换新服务器时,你可能会遇到第二个意想不到的好处。
通过将所有服务器配置保存在版本控制系统中,自动化部署新服务器的任务会变得容易得多。
你是否开始看到版本控制系统有多么有用了?
你甚至可能想到一些情况,如果当时将文件保存在VCS中,本可以让你省去不少麻烦。
正如我们开始时所说,在本课程中,我们将使用Git,它是当今最流行的版本控制系统之一。
接下来,我们将了解一点Git的历史以及它如此特别的原因。
在本节课中,我们一起学习了版本控制系统(VCS)在IT自动化中的核心价值。我们了解到,即使对于单人团队,VCS也能通过保存完整的历史记录和提供快速回滚能力,显著提升工作的可靠性和效率。通过管理DNS区域文件和DHCP配置等实际例子,我们看到了VCS如何像“时间机器”一样帮助我们追踪决策、快速修复问题并简化系统部署。
009:什么是Git? 🗂️


在本节课中,我们将要学习版本控制系统(VCS)的基本概念,并重点介绍一个名为Git的流行工具。我们将了解Git是什么、它为何被创建、它的核心特点以及它与其他系统的不同之处。
什么是版本控制系统?
上一节我们介绍了版本控制系统(VCS)的概念。本节中我们来看看一个具体的VCS工具——Git。
Git是一个由Linux内核的发起者Linus Torvalds在2005年创建的版本控制系统。它是一个免费的开源系统,可以在Unix、Windows和Mac OS等平台上安装。
Linus最初创建Git是为了帮助管理Linux内核的开发工作。这项工作之所以困难,是因为大量地理上分散的程序员需要协作编写大量代码。当时已有的VCS工具无法满足Linus对系统工作方式和性能的要求,因此他决定自己编写一个。如今,Git已成为最流行的版本控制系统之一,被用于数百万个项目中。
Git的分布式架构


与一些围绕单一服务器进行集中式管理的版本控制系统不同,Git采用的是分布式架构。这意味着每个为代码库做贡献的人,都在自己的开发机器上拥有该代码库的完整副本。

以下是分布式架构带来的几个关键优势:
- 独立协作:协作者可以根据需要,分享和拉取其他人所做的更改。
- 高速操作:由于代码库都存储在本地计算机上,大多数操作都可以非常快速地完成。
- 灵活部署:如果你想与他人协作,通常可以在一台服务器上设置一个代码库,作为大家交互的中心枢纽。
Git的工作模式
Git不依赖任何集中式服务器来控制或组织其工作流程。Git可以作为一个独立的程序、一个服务器或一个客户端来工作。
这意味着你可以在单台机器上使用Git,甚至无需网络连接。或者,你可以将Git作为服务器安装在你想要托管代码库的机器上,然后从另一台(甚至同一台)机器上使用Git作为客户端来访问该代码库。
Git客户端可以通过HTTP、SSH或Git自己的专用协议与Git服务器进行网络通信。如果你想深入了解Git的架构或通信协议,我们会在后续阅读材料中提供更多信息的链接。
Git的适用场景
因此,Git可以在有网络或无网络连接的情况下使用。它既适用于只有一名开发者的小型项目,也适用于拥有数千名贡献者的大型项目。你可以用它来跟踪仅自己可见的私人工作,也可以通过将代码托管在GitHub、GitLab等公共服务器上来与他人分享你的工作。你是否开始体会到Git的强大之处了?
术语说明:VCS vs. SCM
在网上查找信息时,你可能会注意到Git的官方网站叫git-scm.com,并好奇结尾的“SCM”代表什么。这实际上是另一个与VCS类似的缩写,代表“源代码管理”。虽然这两个术语意思相同,但我们通常更倾向于使用VCS,因为正如我们已经指出的,这些系统实际上可以用来存储的远不止源代码。
为什么选择Git?
在本课程中,我们选择Git是因为它的流行度、跨平台支持以及丰富的功能集。不过,与IT世界中的大多数事物一样,还有许多其他工具可以用来完成相同的任务,例如Subversion或Mercurial等其他VCS程序。如果你认为另一个VCS可能更适合你的需求,可以自由尝试其他选择。
但在我们深入探讨如何使用它之前,让我们再做一个快速测验,以确保到目前为止的所有内容都清晰明了。
本节课中我们一起学习了Git的基本概念。我们了解到Git是一个分布式版本控制系统,它允许开发者在本地拥有完整的项目副本,从而实现高效、灵活的协作。我们还探讨了Git的适用场景以及它与其他术语(如SCM)的关系。在接下来的课程中,我们将开始学习如何使用Git进行实际操作。
010:Git入门与安装 🛠️


在本节课中,我们将学习版本控制系统Git的基础知识,并完成在您计算机上的安装步骤。Git是一个强大的工具,能帮助我们高效地管理代码和文件的变更历史。
概述
首先,我们需要在计算机上安装Git。安装过程会根据您使用的操作系统有所不同。我们将分别介绍在Linux、macOS和Windows系统上的安装方法。
检查现有Git版本
在开始安装之前,最好先检查您的计算机上是否已经安装了Git。
以下是检查方法:
- 打开命令行工具。
- 输入命令
git --version并执行。 - 如果返回的版本号高于2.20,则可以直接使用现有版本。
如果系统提示错误信息,或显示的版本号过旧,您就需要安装或更新Git。

通过包管理器安装
如果您使用的是带有包管理器的操作系统,安装Git会非常便捷。

以下是各系统的安装命令:
- Linux (APT):
sudo apt install git - Linux (YUM):
sudo yum install git - Windows (Chocolatey):
choco install git - macOS (Homebrew):
brew install git
手动下载安装
如果您不使用包管理器,可以从Git官方网站下载最新的可执行安装程序。
安装过程如下:
- 访问Git官网,下载对应您操作系统的安装程序。
- 运行下载的安装程序。
- 按照屏幕上的提示完成安装。
在Linux上安装
在Linux系统上,安装和使用Git非常直接。
您可以使用包管理器命令进行安装,例如 apt install git 或 yum install git。安装完成后,Git就可以在命令行中使用了。
在macOS上安装
在macOS上,安装过程同样简单。
您甚至可以在终端中直接运行 git --version 命令。如果Git尚未安装,系统会询问您是否要安装,并自动为您下载和安装。
或者,您也可以从官网下载安装程序,并按照提示完成安装。安装完成后,您可以像使用其他命令行工具一样使用Git。
在Windows上安装
在Windows系统上安装Git需要多一些配置步骤。
下载并运行安装程序后,您需要经过一系列配置选项。这些选项通常有预选的默认值,保持默认设置通常是最佳选择。但请注意“选择默认编辑器”这个选项,您可能需要将其更改为您熟悉的编辑器,例如Notepad++或VS Code。
Windows版Git安装的一个特点是,它预装了一个名为MinGW64的环境。这个环境让我们能够在Windows上使用与Linux相同的命令和工具。因此,您可以在Windows机器上练习一些Linux命令行工具。
在Windows机器上安装Git后,您可以从Linux命令行使用Git。如果您在安装过程中关于PATH环境变量的选项选择了默认设置,那么您也可以从PowerShell命令行运行Git。
如果您想更深入了解Windows安装过程中的每个选项,可以观看可选视频,我们将讨论可用选项以及何时可能需要选择与默认不同的设置。
命令行与图形界面
在本课程中,我们将重点介绍如何使用命令行来操作Git。
一些集成开发环境(IDE)提供了通过图形界面与Git交互的功能,如果您觉得那样更顺手,使用它们完全没有问题。我们专注于命令行,是因为它是标准方式,并且一旦您掌握了命令行的使用,您就一定能够使用任何图形化工具。
总结
本节课我们一起学习了如何在不同操作系统上安装Git。我们介绍了检查现有版本、通过包管理器安装以及手动下载安装的方法。安装完成后,您就可以在命令行中使用Git来管理您的项目了。
在下一个视频中,我们将深入了解在Windows机器上安装Git时可设置的各种选项。如果您已经成功安装了Git,可以直接跳到后续的课程内容。
011:在 Windows 上安装 Git(可选) 🖥️

概述
在本节课中,我们将学习如何在 Windows 操作系统上安装 Git 版本控制系统。我们将详细介绍安装过程中的各个选项及其含义,确保您能顺利完成安装并理解每个配置的作用。

下载 Git 安装程序
首先,我们需要从 Git 官方网站下载适用于 Windows 的最新版本安装程序。
您可以从 gitforwindows.org 网站获取安装包。这个软件包不仅包含 Git 核心程序,还附带了一系列在后续课程中可能会用到的实用工具。
安装过程详解

下载完成后,我们就可以开始安装过程了。以下是安装过程中各个步骤的详细说明。
1. 许可协议
启动安装程序后,首先看到的是软件许可协议窗口。Git 基于 GNU 通用公共许可证第二版(GPL v2)发布,这是一个自由软件许可证。这意味着您可以查看 Git 的源代码以了解其工作原理,甚至可以修改它以实现不同的功能。
我们接受此许可协议并继续安装。
2. 选择安装路径
接下来是选择安装路径。通常,保持默认路径即可。
3. 选择组件
这个窗口允许我们选择要安装的附加组件。默认情况下,Git 会与 Windows 资源管理器集成,允许我们在当前文件夹中运行 Git 命令行或图形界面。
该软件包还包含一个扩展,用于改善大型文件(如音频或视频文件)在版本控制系统中的存储支持。建议保持此选项启用。
安装程序还会将 Git 配置文件注册为应用文本编辑器打开的文件,并将 .sh 文件注册为应用 Bash 执行的文件。这些选项都很合理,建议保持选中。
您还可以根据需要启用其他选项,例如在桌面显示图标、在控制台使用 TrueType 字体,或启用自动更新检查。
4. 选择开始菜单文件夹
安装程序会提示您选择创建快捷方式的开始菜单文件夹名称。使用默认名称即可。
5. 选择默认文本编辑器

这是您很可能需要调整的选项。您需要选择一个您习惯使用的文本编辑器。
安装程序已经列出了一些可选编辑器,如 Notepad++、Visual Studio Code 或 Sublime Text。您甚至可以输入其他编辑器的可执行文件路径来手动选择。
在本课程的所有示例中,我们将使用 Vim。因此,我们在此次安装中选择 Vim。
请注意:该软件包本身不包含任何这些图形化编辑器,您需要单独安装您选择的编辑器。
6. 调整 PATH 环境变量
此选项决定了我们如何从命令行执行 Git。
- 第一个选项:Git 只能通过软件包自带的命令行工具访问。
- 第二个选项(默认选中):允许我们从自带的命令行工具和 Windows 命令提示符中执行 Git。
- 第三个选项:将 Git 附带的类 Unix 工具也添加到 Windows 命令提示符的 PATH 中。选择此选项后,任何与操作系统命令同名的命令都将来自此软件包,而非基本的操作系统命令。
我们保持第二个选项选中,因为它既方便使用,又不会干扰本地工具。
7. 选择 HTTPS 传输后端
此窗口让我们选择如何验证用于 HTTPS 连接的 SSL 证书。
我们可以选择使用 Git 附带的 OpenSSL 库,或使用原生的 Windows 安全通道库。如果您需要与公司内部系统交互,可能需要选择第二个选项。
由于我们只与 GitHub 交互,因此保持默认选项选中。
8. 配置行尾转换
用于指示行尾的字符在 Windows、Linux 和 macOS 之间是不同的。Git 软件包允许我们决定如何处理这些差异。
- 默认选项:在本地文件中存储 Windows 风格的行尾,但在 Git 存储的文件中使用 Unix 风格的行尾。当您使用 Windows 计算机与使用其他操作系统的协作者合作时,此选项效果很好。
- 第二个选项:在本地复制文件时保持行尾不变,并在 Git 存储的文件中使用 Unix 风格的行尾。如果您使用的是类 Unix 操作系统,或者您是唯一在 Windows 上通过类 Unix 编辑器进行编辑的人,此选项效果很好。
- 第三个选项:不进行任何转换。如果您尝试与使用不同操作系统的人合作,此选项效果不佳,因此仅当所有协作者都运行与您相同的操作系统时才推荐使用。
我们保持第一个选项选中并继续。
9. 配置终端模拟器
该软件包自带一个终端模拟器,它具有许多不错的功能,例如更好的 Unicode 支持和可滚动的长命令历史记录。我们保持此选项选中。但如果您更习惯使用 Windows 默认的控制台窗口,也可以选择它。
10. 其他选项
接下来,还有一些额外的选项可供选择启用。我们保持默认设置。这样,我们将获得 Git 文件系统缓存带来的性能改进,并且能够使用 Git 凭据管理器。
我们不需要在仓库中使用符号链接,因此保持该选项禁用。
11. 实验性功能
在安装之前,最后一个提示让我们选择要启用的实验性功能。这里提供的功能会随着时间的推移而变化,一些被采纳,一些被放弃。您可以决定是使用前沿功能,还是选择已经过充分测试的功能。
我们今天不打算冒险,因此不启用实验性功能。我们直接点击“安装”来开始安装过程。
完成安装
现在,Git 正在我们的机器上安装。安装完成后,我们就可以随时使用 Git 的所有功能了。
总结

本节课中,我们一起学习了在 Windows 系统上安装 Git 的完整步骤。我们详细探讨了安装过程中的各个配置选项,包括组件选择、默认编辑器设置、PATH 环境变量调整、行尾转换处理以及终端模拟器的选择。理解这些选项有助于您根据自身需求进行定制化安装,为后续的版本控制操作打下坚实基础。
如果您需要更多帮助,可以在接下来的阅读材料中找到关于 Git 安装的更多信息链接。
012:Git入门 🚀

在本节课中,我们将学习Git版本控制系统的基础概念和基本操作。我们将了解如何配置Git、初始化仓库、跟踪文件更改,并完成第一次提交。
开始使用Git时,我们需要学习一系列概念,以理解文件如何组织以及我们的文件如何被跟踪。
在接下来的视频中,我们将介绍一些主要的Git概念。
如果其中任何概念起初看起来令人困惑,请不要惊慌。
随着我们扩展Git知识,我们将深入探讨所有这些概念。
让我们从设置一些基本配置开始。记得我们说过,版本控制系统会跟踪谁进行了哪些更改。
为此,我们需要告诉Git我们是谁。我们可以使用git config命令,然后将user.email和user.name的值设置为我们的电子邮件和姓名。
像这样:
git config --global user.email "you@example.com"
git config --global user.name "Your Name"
我们使用--global标志来声明我们希望为使用的所有Git仓库设置此值。
我们也可以为不同的仓库设置不同的值。
完成此设置后,有两种方法可以开始使用Git仓库。
我们可以使用git init命令从头创建一个仓库。
或者,我们可以使用git clone命令复制一个已存在于其他地方的仓库。
我们将在课程后面讨论远程仓库。
现在,让我们从创建一个新目录开始,然后在该目录内创建一个Git仓库。
当我们运行git init时,我们在当前目录中初始化一个空的Git仓库。
我们收到的消息提到了一个名为.git的目录。

我们可以使用ls -la命令检查该目录是否存在,该命令会列出以点开头的文件。
我们也可以使用ls -la .git命令查看其内部,并了解它包含的许多不同内容。
这个目录称为Git目录,你可以将其视为Git项目的数据库,用于存储更改和更改历史记录。
我们可以看到它包含许多不同的文件和目录。
我们不会直接接触这些文件中的任何一个。我们将始终通过Git命令与它们交互。
因此,每当你克隆一个仓库时,这个Git目录就会被复制到你的计算机上。
每当你运行git init创建一个新仓库时,就像我们刚才所做的那样,就会初始化一个新的Git目录。
Git目录之外的区域是工作树。

工作树是你的项目的当前版本。
你可以将其视为一个工作台或沙盒,你可以在其中对文件执行所有想要的修改。
这个工作树将包含所有当前被Git跟踪的文件,以及我们尚未添加到跟踪文件列表中的任何新文件。
目前,我们的工作树是空的。让我们通过将之前视频中看到的disk_usage.py文件复制到当前目录来改变这一点。
好的,我们现在在工作树中有了一个文件,但它目前未被Git跟踪。为了让Git跟踪我们的文件,我们将使用git add命令将其添加到项目中,将要添加的文件作为参数传递。
这样,我们就将文件添加到了暂存区。
暂存区,也称为索引,是Git维护的一个文件,其中包含有关哪些文件和更改将进入下一次提交的所有信息。
我们可以使用git status命令获取有关当前工作树和待处理更改的一些信息。
让我们检查一下。


我们看到我们的新文件被标记为待提交。
这意味着我们的更改当前在暂存区中。
要将其提交到Git目录,我们运行git commit命令。现在让我们试试。
当我们运行此命令时,我们告诉Git我们想要保存更改。它会打开一个文本编辑器,我们可以在其中输入提交消息。如果你愿意,可以将使用的编辑器更改为你首选的编辑器。
在我们的例子中,这台计算机将nano配置为默认编辑器。
我们得到的文本告诉我们,我们需要编写提交消息。
并且要提交的更改是我们添加的新文件。
我们稍后将深入探讨提交消息。现在,让我们输入一个简单的描述,说明我们做了什么,即添加了这个文件。
然后退出编辑器,保存我们的提交消息。
这样,我们就创建了我们的第一个Git提交。
接下来,我们将更详细地讨论Git仓库中每个跟踪文件的生命周期。

在本节课中,我们一起学习了Git的基本配置、仓库的初始化、工作树与暂存区的概念,以及如何通过git add和git commit命令来跟踪并提交文件的更改。这是使用Git进行版本控制的第一步。
013:跟踪文件 📁

在本节课中,我们将学习Git如何跟踪文件的变化。我们将详细探讨文件的三种主要状态,并通过一个实际例子演示从修改文件到提交更改的完整工作流程。
在上节课中,我们提到任何Git项目都包含三个部分:Git目录、工作树和暂存区。Git目录存储了所有文件和更改的历史记录。工作树包含了项目的当前状态,包括我们做出的任何更改。暂存区则包含了被标记为要包含在下一次提交中的更改。
为了更清晰地理解,可以将Git视为代表你的项目(即代码和相关文件)以及一系列快照。每次你进行提交时,Git都会记录下那一刻项目状态的新快照。这个快照精确地捕捉了所有文件在某个时间点的样子。这些快照共同构成了你项目的历史,其信息存储在Git目录中。
现在,让我们深入了解如何跟踪文件更改的细节。
文件的跟踪状态与三种状态
当我们使用Git时,文件要么是已跟踪的,要么是未跟踪的。已跟踪的文件是快照的一部分,而未跟踪的文件尚未成为快照的一部分,新文件通常就属于这种情况。
每个已跟踪的文件可以处于以下三种主要状态之一:已修改、已暂存或已提交。
以下是每种状态的含义:
- 已修改:这意味着我们对文件进行了更改,但尚未提交。更改可以是添加、修改或删除文件内容。Git会注意到我们对文件的任何修改,但不会存储这些更改,除非我们将它们添加到暂存区。
- 已暂存:当我们标记这些更改以进行跟踪时,已修改的文件就变成了已暂存的文件。换句话说,对这些文件的更改已准备好提交到项目中。所有已暂存的文件都将成为我们拍摄的下一个快照的一部分。
- 已提交:当文件被提交时,对其所做的更改会安全地存储在Git目录的快照中。
通常,一个被Git跟踪的文件会经历以下流程:首先,当我们以任何方式更改它时,它会变为已修改状态。然后,当我们标记这些更改以进行跟踪时,它会变为已暂存状态。最后,当我们将这些更改存储在版本控制系统(VCS)中时,它会被提交。
实际操作演示
让我们在示例Git仓库中实际操作一下。

首先,使用 ls -la 命令检查当前工作树的内容,然后使用 git status 命令查看文件的当前状态。
ls -la
git status
运行 git status 后,Git会告诉我们很多信息,包括我们当前在 master 分支上(我们将在课程后面学习分支)。目前,请注意它显示“没有要提交的内容”且“工作树是干净的”。
现在,让我们修改一个文件来改变这个状态。例如,我们将在脚本向用户显示的消息末尾添加句号。
修改完成后,再次调用 git status 查看新的输出。Git再次提供了大量信息,包括一些我们可能想使用的命令提示,这些提示在熟悉Git时非常有用。可以看到,我们更改的文件现在被标记为“已修改”,并且“尚未暂存以备提交”。
让我们通过运行 git add 命令来改变这个状态,并将 disk_usage.py 文件作为参数传递。
git add disk_usage.py
当我们调用 git add 时,我们是在告诉Git,我们希望将该文件中的当前更改添加到待提交的更改列表中。这意味着我们的文件现在是暂存区的一部分,一旦我们运行下一个Git命令 git commit,它就会被提交。
这次,我们不打开编辑器,而是使用 -m 标志传递提交消息,说明我们在句子末尾添加了句号。
git commit -m “在句子末尾添加了句号”
现在我们已经提交了暂存的更改,这在Git目录中创建了一个新的快照。该命令向我们显示了所做更改的一些统计信息。
让我们做最后一次状态检查。
git status
我们看到,再次显示没有要提交的更改,因为我们所做的更改已经完成了从已修改到已暂存再到已提交的完整周期。
总结

本节课中,我们一起学习了Git跟踪文件的核心机制。

总结一下工作流程:我们在工作树中处理已修改的文件。当它们准备好后,我们通过将这些文件添加到暂存区来暂存它们。最后,我们提交暂存区中的更改,这会为这些文件拍摄快照,并将它们存储在Git目录的数据库中。
如果其工作原理还不是完全清楚,请不要担心,通过更多的练习会逐渐掌握。在下节课中,我们将把所有内容整合起来,回顾使用Git时的典型工作流程。
014:Git基本工作流 🚀

在本节课中,我们将学习Git在日常工作中的基本使用流程。我们将回顾Git的核心概念,并通过一个具体的例子,演示如何创建文件、跟踪更改、暂存修改以及提交快照。
在之前的视频中,我们讨论了使用Git所涉及的一些基本概念。我们了解到,每个仓库都会有一个Git目录、一个工作树和一个暂存区。并且,文件可以处于三种不同的状态:已修改、已暂存和已提交。
让我们通过观察日常使用Git时的常规工作流,再次回顾这些概念。
初始化仓库与配置
首先,所有我们希望用Git管理的文件都必须是一个Git仓库的一部分。我们可以在任何文件系统目录中运行 git init 命令来初始化一个新的仓库。
例如,让我们使用 mkdir 命令创建一个名为 scripts 的目录,然后进入该目录并初始化一个空的Git仓库。
mkdir scripts
cd scripts
git init
我们闪亮的新Git仓库现在可以用来跟踪其内部文件的更改了。但在开始之前,让我们使用 git config -l 命令查看当前的配置。
配置信息中有很多内容,我们暂时不会全部介绍。请特别注意 user.email 和 user.name 这两行,我们在之前的视频中简要提到过。如果你使用共享仓库,出于隐私考虑,这些信息将出现在公开的提交日志中。在处理私人工作和向公共仓库提交代码时,你可能希望使用不同的身份信息。我们将在下一篇阅读材料中包含更多关于更改此信息的细节。
好的,我们的仓库已准备就绪,但目前是空的。
创建并跟踪新文件
让我们在其中创建一个文件。我们将从一个Python脚本的基本框架开始,这将帮助我们演示Git工作流。
和任何Python脚本一样,我们从 shebang 行开始。目前,我们将添加一个空的 main 函数,稍后再填充内容。最后,我们将调用这个 main 函数。
#!/usr/bin/env python3
def main():
pass
if __name__ == "__main__":
main()
我们已经创建了文件。这是一个我们希望执行的脚本,所以让我们使其可执行。
chmod +x all_checks.py
然后,让我们使用 git status 命令检查仓库的状态。
正如我们之前提到的,当我们在仓库中创建一个新文件时,它最初是未跟踪的。我们可以对文件进行各种更改,但在我们告诉Git跟踪它之前,Git不会对未跟踪的文件做任何处理。
你还记得我们必须使用什么命令来让Git跟踪我们的文件吗?没错,我们需要调用 git add 命令。这个命令会立即将一个新文件从未跟踪状态移动到已暂存状态。正如我们稍后将看到的,它也会将处于已修改状态的文件更改为已暂存状态。
请记住,当文件被暂存时,意味着它已被添加到暂存区,并准备好提交到Git仓库。
git add all_checks.py
提交更改
要提交已暂存的文件,我们使用 git commit 命令。当我们这样做时,它只会提交已添加到暂存区的更改,未跟踪的文件或未暂存的已修改文件将被忽略。
不带参数调用 git commit 将启动一个文本编辑器。这将打开你设置为默认编辑器的程序。如果默认编辑器不是你想要的,有很多方法可以更改它。我们将在下一篇阅读材料中包含更多关于更改默认编辑器的信息。
现在,让我们用 nano(这是当前计算机的默认设置)来编辑我们的提交信息。我们将说明我们的更改是“创建了一个空的 all_checks.py 文件”,然后保存并退出。
git commit
# 在打开的编辑器中输入:Creating an empty all_checks.py file
太好了!我们刚刚记录了项目中代码的一个快照,该快照存储在Git目录中。请记住,每次我们提交更改时,都会拍摄另一个快照,并用我们以后可以查看的提交信息进行注释。
修改现有文件
好的,这就是我们添加新文件的方式,但通常我们会修改现有的文件。所以,让我们在脚本中添加更多内容来看看这个过程。
我们将添加一个名为 check_reboot 的函数,用于检查计算机是否等待重启。为此,我们将检查 /var/run/reboot-required 文件是否存在。这是当某些软件需要重启时,在我们计算机上创建的一个文件。当然,由于我们使用了 os.path.exists,我们需要在脚本中添加 import os。
#!/usr/bin/env python3
import os
def check_reboot():
"""Returns True if the computer has a pending reboot."""
return os.path.exists("/run/reboot-required")
def main():
pass
if __name__ == "__main__":
main()
我们已经向文件中添加了一个函数。让我们再次使用 git status 检查当前状态。

我们的文件显示为已修改,但未暂存。要暂存我们的更改,我们需要再次调用 git add。
git add all_checks.py
好的,我们的更改现在已暂存。
接下来我们需要做什么?你说对了。我们必须调用 git commit 将这些更改存储到Git目录中。这次我们将使用另一种设置提交信息的方式:调用 git commit -m,然后传递我们想要使用的提交信息。
git commit -m "Added the check_reboot function"
总结
至此,我们已经演示了基本的Git工作流:我们对文件进行更改,使用 git add 暂存它们,然后使用 git commit 提交它们。你是否开始对这个过程感到更熟悉,并看到它如何融入你的其他任务中了?
如果还有任何不完全清楚的地方,请记住,熟悉这些概念的唯一方法就是练习。请随时在你的计算机上尝试这些示例,直到你熟悉这些命令。
在本节课中,我们一起学习了Git的基本工作流,包括初始化仓库、创建和跟踪文件、暂存修改以及提交更改。下一节,我们将讨论如何编写有用的提交信息。
015:如何编写有效的Git提交消息 📝

在本节课中,我们将学习如何为Git提交编写清晰、信息丰富的提交消息。这是版本控制中一项至关重要的技能,能帮助你和你的团队在未来更好地理解代码的变更历史。
概述
在之前的课程中,我们学习了如何将变更的快照提交到Git仓库。现在,我们将更深入地探讨什么才是好的提交消息。在使用版本控制系统时,编写清晰、信息丰富的提交消息非常重要。未来的你、其他开发者或IT专家在阅读提交消息时,会非常感激这些上下文信息,因为它们有助于理解代码或配置的某些部分。
什么是好的提交消息?
编写提交消息时,心中想着你的读者会很有帮助。几周或几个月后,阅读消息的人会想知道你做了哪些更改?这些更改中,哪些部分特别重要或难以理解?是否有额外的信息可以帮助读者,例如设计文档的链接或工单系统中的票据链接?
与编写代码的风格指南类似,你的公司可能对编写提交消息有特定的规则。即使没有,遵循一些通用准则也能确保你的提交消息尽可能清晰和有用。
提交消息的结构
一个提交消息通常分为几个部分。
以下是提交消息的标准结构:
- 第一行:提交的简短摘要,通常不超过50个字符。
- 一个空行:用于分隔摘要和详细描述。
- 详细描述:详细说明变更的原因,以及任何特别有趣或难以理解的地方。通常每行不超过72个字符。
当你运行 git commit 命令时,Git会打开你选择的文本编辑器,让你编写提交消息。
一个良好的提交消息示例
一个好的提交消息可能如下所示:
修复用户登录时的空指针异常
- 当用户名为空时,`validate_user` 函数会抛出 `NullPointerException`。
- 在调用 `user.getName()` 之前添加了空值检查。
- 关联的工单编号为 #JIRA-123。
第一行通常保持在50个字符左右。第一行之后是一个空行,其余文本通常保持在72个字符以内。这段文本旨在提供变更的详细解释。它可以引用此变更将修复的错误或问题,也可以在相关时包含更多信息的链接。行数限制可能有些烦人,但它们有助于使提交消息对读者来说更易于消化。
使用 git log 查看提交历史
有一个用于显示这些提交消息的Git命令,叫做 git log。
这个命令不会为我们进行任何换行,这意味着如果我们不遵守推荐的行宽限制,长的提交消息会超出屏幕边缘,难以阅读。
现在,让我们回到之前进行过两次提交的示例 scripts 目录,看看 git log 对那两次提交有什么说法。
看看Git在日志中跟踪了哪些信息。它在短短几行中打包了大量信息。
对于每次提交,列出的第一项是它的标识符,这是一长串字母和数字,用于唯一标识每次提交。列表中的第一次提交还显示 HEAD 指针指向 master 分支。如果这对你来说像天书,别担心。我们将在后面的视频中详细讨论 HEAD 和 master 的含义。
对于每次提交,我们看到提交者的姓名和电子邮件,这被标记为作者。然后我们得到提交的日期和时间。最后,显示提交消息。由于我们刚刚开始处理仓库,我们的提交消息非常简短。
随着我们进行的工作变得更加复杂,我们可能会编写更长的描述,包含更多细节。
避免不良做法
有时,人们可能想写一些简短的内容,比如“更新”、“更改”或“修复”作为提交消息的描述。请不要这样做。
回到仓库的历史记录中,发现没有足够的上下文来理解更改了什么以及为什么更改,这是非常令人沮丧的。多花几秒钟写一个更好的描述是值得的,这在未来会变得非常宝贵。
遵循这些准则可以帮助使你的提交消息真正有用,现在的投入将在以后得到回报。

总结
本节课中,我们一起学习了如何编写有效的Git提交消息。我们了解了提交消息的标准结构,包括简短的摘要和详细的描述部分。我们还探讨了为什么好的提交消息对团队协作和未来维护至关重要,并学习了如何使用 git log 命令查看提交历史。记住,多花一点时间编写清晰的提交消息,是对未来自己和团队成员的宝贵投资。
接下来,我们将为你提供一份速查表,列出到目前为止我们看到的所有Git命令,以及你可能觉得有用的额外信息的链接。之后,请进入下一个练习测验,以确保你完全理解了所有这些新概念。
016:版本控制介绍总结 🎯


在本节课中,我们将对模块一“版本控制介绍”的核心内容进行总结,并介绍接下来的实践评估环节。
恭喜你完成了本模块的学习。这些概念并不简单,你能坚持学到这里非常棒。
在之前的课程中,我们探讨了什么是版本控制以及为什么需要它。我们讨论了版本控制系统如何支持数千人协作,以及它如何成为个人或小型团队工作的强大工具。
接着,我们深入了解了 Git。我们讨论了 Git 的基本概念,并查看了典型的 Git 工作流程。现在,你已经迈出了使用 Git 的第一步。目前所学的流程虽然简单,但对于帮助你跟踪变更已经非常有用。随着你 Git 知识的扩展,你将发现更多可以用它完成的事情。
我们之前说过,并且无疑会再次强调:学习任何有价值知识的最佳途径就是练习、练习、再练习。因此,你将通过完成分级评估,按照 Git 工作流程亲自实践所有新知识。
在本课程的大部分评估中,我们将使用由 Google Cloud 控制台支持的 Quick Lab 在线学习平台。这个平台让你体验真实场景,由你负责完成任务。
要与实验互动,你需要连接到在云端运行的虚拟机。每个实验都会提供一个不同的 Git 活动,你将有机会练习新技能。本次评估需要完成的练习将与我们在此涵盖的示例非常相似。
以下是评估中你将需要完成的步骤:
- 初始化一个仓库。
- 向其中添加内容。
- 提交该内容以便进行跟踪。
这并非魔法。熟练掌握这个过程对后续模块的学习将非常有价值。
所以,现在是时候承诺复习所有示例,并将这些新概念牢记于心了。
我承诺在下一个模块与你相见,我们到时见。
本节课总结:我们一起回顾了版本控制的重要性、Git 的基本工作流程,并介绍了通过 Quick Lab 平台进行实践评估的方法。掌握这些基础是后续深入学习的关键。
017:模块2 - 本地Git使用详解 🚀

在本节课中,我们将深入学习Git的高级功能,包括快捷操作、信息查看、撤销更改以及分支管理。这些技能将帮助你更高效地使用版本控制系统。

上一节我们介绍了Git的基本概念和基础操作。本节中,我们将探索Git的一些强大特性,从提高效率的快捷方式开始。
获取更多信息与快捷操作 🔍
Git提供了多种命令来获取仓库的详细状态和历史信息。同时,掌握一些快捷方式可以显著提升工作效率。
以下是几个常用的信息查看命令:
git status:查看工作目录和暂存区的状态。git log:查看提交历史记录。git diff:显示尚未暂存的改动内容。
撤销更改:版本控制的核心能力 ↩️
能够回退更改是版本控制系统最有用的功能之一。根据不同的情况,我们需要采用不同的回退技术。
接下来,我们将学习几种撤销更改的方法。根据你需要撤销的内容,可以选择不同的技术:
- 丢弃文件的修改:使用
git checkout -- <file>命令可以放弃工作目录中对某个文件的所有修改,将其恢复到最近一次提交的状态。 - 修正错误的提交:如果你提交了错误的内容,可以使用
git commit --amend命令来修改最近一次的提交信息或内容。 - 回滚到历史快照:使用
git revert <commit>或git reset命令可以将项目回退到某个指定的历史版本。
探索分支概念 🌿
最后,我们将了解另一个重要概念:分支。分支允许你在独立的开发线上工作,而不会影响项目的主代码。
分支的用途非常广泛:
- 开发实验性功能。
- 维护无法直接合并的多个软件版本。
- 同时进行多个不同特性的开发。
我们将深入探讨什么是分支、何时以及如何使用它们,并学习如何处理合并时可能出现的冲突。
必须承认,其中一些概念可能比较复杂。因此,我们强烈建议你跟随课程在电脑上实际操作,尝试我们演示的命令,并自行练习,直到熟练掌握这些技巧。
记住,如果对任何内容有不清楚的地方,你可以随时回看之前的视频进行复习。如果在尝试后仍然感到困惑,请务必利用讨论区寻求帮助。
本节课中,我们一起学习了Git的高级功能,包括查看信息的命令、多种撤销更改的方法以及分支管理的基础。通过实践这些技巧,你将能更加自如地运用Git来管理你的项目。
018:跳过暂存区域 🚀

在本节课中,我们将要学习如何使用 Git 的一个快捷方式,跳过“暂存”步骤,直接将工作目录中的修改提交到仓库。这对于处理简单、明确的更改非常有用。
概述
上一节我们介绍了 Git 的基本工作流程:修改文件、暂存更改、提交更改。本节中我们来看看如何通过 git commit -a 命令跳过中间的暂存步骤,实现快速提交。
跳过暂存步骤
当我们介绍基本的 Git 工作流程时,我们指出该过程通常是:进行更改、暂存它们,然后提交它们。暂存和提交之间的独立步骤允许我们在一次提交中暂存多个更改。
但是,如果我们已经知道当前的更改就是我们想要提交的更改,我们可以跳过暂存步骤,直接进行提交。无需彩排。
我们通过使用 git commit 命令的 -a 标志来实现这一点。
这个标志会在执行提交之前,自动暂存所有已被跟踪且被修改的文件,让我们可以跳过 git add 步骤。
起初,你可能会认为 git commit -a 只是 git add 后跟 git commit 的快捷方式。

但这并不完全正确。git commit -a 不适用于新文件,因为那些文件是未被跟踪的。
实际上,git commit -a 是一个快捷方式,用于暂存所有对已跟踪文件的更改,并在一步中提交它们。
如果被修改的文件从未被提交到仓库,我们仍然需要先使用 git add 来跟踪它。
实践操作
让我们修改之前视频中的示例脚本,并尝试这个新标志。
我们将修改我们的 main 函数,使其调用我们之前编写的 check_reboot 函数。
如果存在待处理的重启,我们将打印一条消息,然后以退出状态码 1 退出我们的程序。
由于我们使用了 sys 模块,我们需要导入它。
好的,现在我们已经完成了更改,准备尝试新的 -a 标志。
我们还将使用 -m 标志来直接添加提交消息。这次我们会说“调用 check_reboot 并在错误条件下以状态码 1 退出”。
以下是操作步骤:
- 修改脚本文件。
- 使用
git commit -a -m "调用 check_reboot 并在错误条件下以状态码 1 退出"命令。
成功!这些快捷方式在进行我们知道想要直接提交的小更改时非常有用,无需将它们保留在暂存区,也无需编写冗长复杂的描述。
请记住,当你使用 -m 快捷方式时,你只能写简短的消息,并且无法使用我们之前讨论过的关于提交描述的最佳实践。
因此,它最好只用于那些确实不需要额外上下文或解释的小改动。简短而精悍。
注意:当你使用 -a 快捷方式时,你跳过了暂存区,这意味着在创建提交之前,你无法添加任何其他更改。

所以你需要确保你已经包含了你想在该次提交中包含的所有内容。
提交结果与 HEAD 指针
最终,使用像 -a 这样的快捷方式就像使用常规的提交工作流程一样,提交将连同消息一起出现在日志中,就像往常一样。让我们来检查一下。
看,我们最新的提交被添加到了提交列表的顶部。注意 HEAD 指示器现在已经移动到了最新的提交。

你可能想知道,这个 HEAD 是什么,它指向哪里?我们会经常遇到它,所以让我们来澄清一下。
Git 使用 HEAD 别名来表示你项目中当前已检出的快照。
这让你知道工作目录的内容应该是什么。在这种情况下,当前快照是项目中的最新提交。
我们很快就会学习分支,在那种情况下,HEAD 可以指向项目中不同分支的一个提交。
我们甚至可以使用 Git 回到过去,让 HEAD 代表应用最新更改之前的旧提交。
在所有情况下,HEAD 都用于指示当前已检出的快照是什么。这就是 Git 标记你在项目中位置的方式。可以把它想象成一个书签,你可以用它来跟踪你所在的位置。
即使你有好几本书要读,书签也能让你从上次停下的地方继续阅读。
当你运行像 diff、branch 或 status 这样的 Git 命令时,Git 将使用 HEAD 这个“书签”作为其执行任何操作的基础。
当我们学习如何撤销操作和执行回滚时,我们会看到 HEAD 的使用,我们将在后面的视频中更多地讨论分支。
作为一种快捷方式,通常可以简单地将 HEAD 视为指向当前分支的指针,尽管它的功能可能比这更强大。
总结
本节课中我们一起学习了如何使用 git commit -a 命令跳过暂存步骤,快速提交对已跟踪文件的修改。我们了解了这个快捷方式的适用场景(小改动、已跟踪文件)和注意事项(不适用于新文件、无法添加额外更改)。我们还引入了 HEAD 指针的概念,它像一个书签,标记了当前工作目录所对应的项目快照位置,是 Git 许多操作的基础。接下来,我们将深入探讨如何在提交前后获取更多关于我们更改的信息。
019:获取更多有关更改的信息 📝

在本节课中,我们将学习如何使用Git命令获取比默认信息更详细的提交历史与更改内容。我们将重点介绍 git log 的不同参数、git show 命令以及如何查看未提交的更改。
我们已经了解了 git log 如何显示当前Git仓库中的提交列表。默认情况下,它会打印提交信息、作者和更改日期。这很有用,但如果我们需要在仓库的更改历史中仔细排查,以找出导致最近故障的原因,我们可能还需要查看每次提交中实际更改的代码行。
使用 git log -p 查看补丁详情
为了用 git log 实现这个目的,我们可以使用 -p 标志。这里的 p 代表“补丁”,因为使用此标志会显示每次提交所创建的补丁。
让我们来试试。其输出格式与我们之前视频中看到的 diff -u 输出格式相同。它用加号 + 显示新增的行,用减号 - 显示删除的行。
由于现在显示的文本量超过了屏幕的显示范围,Git会自动使用一个分页工具,允许我们使用 Page Up、Page Down 和方向键进行滚动。提交仍然是一个接一个地显示,但现在每个提交占据的空间大小不同,这取决于该次提交中添加或删除了多少行代码。
使用这个选项,我们可以快速查看仓库中文件的具体更改。这在试图追踪最近导致工具故障的更改时尤其有用。
使用 git show 查看特定提交
如果我们不想一直向下滚动直到找到我们真正感兴趣的提交,另一个选择是使用 git show 命令。该命令以提交ID作为参数,将显示该提交的信息及其关联的补丁。
我们将在后面的视频中更详细地讨论提交ID,但现在请记住,这是我们在 log 输出中“commit”一词旁边看到的标识符。
让我们通过先列出仓库中的当前提交,然后为列表中的第二个提交调用 git show 来查看一下。首先,按 Q 键退出分页视图。
使用 git log --stat 查看更改统计
我们已经展示了如何使用 git log 列出提交,以及使用 git log -p 显示关联的补丁。git log 另一个有趣的标志是 --stat。这将使 git log 显示关于提交中更改的一些统计信息,例如哪些文件被更改,以及添加或删除了多少行。
让我们用我们的仓库试试看。git log 还有很多其他选项,我们无法一一介绍。你可以随时查阅参考文档或手册页以了解更多信息。
正如我们之前提到的,你不需要记住所有这些内容,通过使用,你自然会熟悉不同的命令和标志。重要的是要记住,所有信息都存储在仓库中,当你需要时,它们触手可及。
查看未提交的更改
那么,尚未提交的更改呢?到目前为止,每当我们对文件进行更改时,我们都会使用 git add 将它们添加到暂存区,然后用 git commit 提交,或者直接使用 git commit -a 提交。
这很好用,但这意味着我们必须确切知道我们做了哪些更改。有时,我们可能需要一段时间才能准备好提交。我们称之为“提交困难症”。
开个玩笑。但想象一下,你一直在为一个脚本添加一个复杂的新功能,这需要进行彻底的测试。在提交之前,你需要确保它能正常工作,检查是否覆盖了所有测试用例,等等。在这个过程中,你发现了代码中需要修复的错误。因此,当你进行到提交步骤时,不记得所有更改是很自然的。
为了帮助我们跟踪,Git提供了 git diff 命令。让我们对脚本做一个新的更改,然后试试这个命令。我们将为用户添加另一条消息,说明检查成功时一切正常,然后以状态码0退出,而不是1。
好的,我们已经做了更改,现在保存它,看看 git diff 给我们显示什么。同样,这个格式与我们之前视频中看到的 diff -u 输出格式相同。在这种情况下,我们看到唯一的更改是我们添加的额外行。如果我们的更改更大,涉及多个文件,我们可以传递一个文件名作为参数,以查看与该特定文件相关的差异,而不是同时查看所有文件。
在添加更改前进行审查
在添加更改之前,我们可以做的另一件事是使用带 -p 标志的 git add 命令。当我们使用这个标志时,Git会显示将要添加的更改,并询问我们是否要暂存它。这样,我们可以检测是否有任何我们不想提交的更改。让我们试试这个。
我们已经暂存了更改,现在可以提交了。如果我们再次调用 git diff,它不会显示任何差异,因为默认情况下 git diff 只显示未暂存的更改。相反,我们可以调用 git diff --staged 来查看已暂存但未提交的更改。
使用这个命令,我们可以在调用 git commit 之前查看实际的暂存更改。现在让我们提交这些更改,这样它们就不再是待处理状态了。我们将说明我们添加了一条一切正常时的消息。
很好,通过以上学习,我们掌握了一系列获取更多关于更改信息的不同方法。
总结

本节课中,我们一起学习了如何深入查看Git仓库的更改历史。我们介绍了:
- 使用
git log -p查看每次提交的详细补丁信息。 - 使用
git show <commit-id>快速查看特定提交的详情。 - 使用
git log --stat获取提交的统计概览。 - 使用
git diff查看工作目录中未暂存的更改。 - 使用
git add -p交互式地暂存部分更改。 - 使用
git diff --staged查看已暂存但未提交的更改。
我们涵盖了很多内容,如果这些命令还不完全明白,请花时间复习和练习。接下来,我们将探讨当我们需要删除或重命名仓库中的文件时会发生什么。是时候进行一些“仓库整理”了。
020:Git文件删除、重命名与忽略 🗑️📁

在本节课中,我们将学习如何使用Git管理文件的生命周期,包括删除不再需要的文件、重命名文件以及设置规则忽略特定文件,以保持仓库的整洁。
删除文件
上一节我们介绍了如何添加和提交文件。本节中,我们来看看如何从Git仓库中删除文件。
假设你决定清理一些旧脚本,并希望将它们从仓库中移除。或者,你进行了一些重构,使得某个特定文件变得过时。你可以使用 git rm 命令从仓库中删除文件。该命令会停止Git对该文件的跟踪,并将其从Git目录中移除。
文件夹的删除遵循相同的工作流程,因此你需要编写提交信息,说明删除的原因。
让我们在包含一个我们决定不再需要的文件的checks仓库中尝试此操作。
以下是操作步骤:
- 首先使用
ls查看目录内容。 - 然后使用
git rm删除文件。 - 再次使用
ls检查内容。 - 最后使用
git status检查状态。
ls
git rm old_script.py
ls
git status
我们看到,通过调用 git rm,文件从目录中被删除,并且此更改已暂存,准备在下次提交中提交。
现在,我们通过调用 git commit 并设置一条消息来提交,表明我们删除了不需要的文件。
git commit -m "删除不再需要的旧脚本文件"
与往常一样,提交时会得到一堆统计信息。请注意它报告的所有删除内容。这些都是文件中不再存在的行,并且它声明文件本身已被删除。
重命名文件
接下来,我们探讨如何管理文件名。如果一个文件的名称不准确怎么办?例如,你可能开始编写一个你认为只做一件事的脚本,但后来它扩展到了更多用例。或者相反,如果你命名脚本时认为它会非常通用,但结果却更具体。你可以使用 git mv 命令来重命名仓库中的文件。
让我们将现有的脚本重命名为 check_freespace.py,然后查看 git status 对此有何说明。
git mv check.py check_freespace.py
git status
状态显示文件已被重命名,并清晰地显示了旧名称和新名称。与前面的示例一样,更改已暂存但未提交。让我们再次调用 git commit 来提交它。
git commit -m "将 check.py 重命名为 check_freespace.py"
git mv 命令的工作方式类似于Linux上的 mv 命令,因此既可用于重命名,也可用于移动文件。如果我们的仓库中包含更多目录,我们可以使用相同的 git mv 命令在目录之间移动文件。
使用 .gitignore 忽略文件
从我们的示例中你可能已经看出,git status 的输出是一个非常有用的工具,可以帮助我们了解文件的状态。它显示哪些文件有已跟踪或未跟踪的更改,以及哪些文件被添加、修改、删除或重命名。保持这些命令的输出与我们正在做的事情相关非常重要。
如果我们有一长串未跟踪的文件,可能会在杂乱的输出中遗漏重要的更改。如果有由我们的脚本自动生成的文件,或者操作系统生成了我们不希望放入仓库的产物,我们将希望忽略它们,以免它们给 git status 的输出增加干扰。
为此,我们可以使用 .gitignore 文件。在这个文件中,我们将指定规则来告诉Git为当前仓库跳过哪些文件。
例如,如果我们在OSX计算机上工作,我们可能希望忽略由操作系统自动生成的 .DS_Store 文件。为此,我们将创建一个包含此文件名的 .gitignore 文件。
请记住,在类Unix文件系统中,点号(.)前缀表示文件或目录是隐藏的,执行正常的目录列表时不会显示。这就是为什么我们必须使用
ls -la来查看所有文件。
以下是 .gitignore 文件的内容示例:
.DS_Store
*.log
__pycache__/
我们已经将 .gitignore 文件添加到我们的仓库中,但尚未提交。这个文件需要像仓库中的其他文件一样被跟踪。现在让我们添加并提交它。

git add .gitignore
git commit -m "添加 .gitignore 文件以忽略系统文件和日志"
总结
本节课中我们一起学习了Git中管理文件的几个关键操作。我们掌握了如何使用 git rm 命令删除文件并提交删除操作,如何使用 git mv 命令重命名或移动文件,以及如何创建和配置 .gitignore 文件来忽略不需要跟踪的特定文件或模式,从而保持仓库的清晰和 git status 输出的简洁。这些技能对于维护一个干净、高效的代码仓库至关重要。
021:在提交前撤消更改 🔄

在本节课中,我们将学习如何使用Git来撤消尚未提交的更改。这是版本控制系统最强大的功能之一。我们将探讨几种不同的技术,具体取决于我们需要撤消哪些更改,并了解每种方法的使用场景。
能够回滚更改是版本控制系统提供的最强大功能之一。根据我们需要撤消的更改类型,有多种不同的技术可供使用。在本视频及接下来的几个视频中,我们将讨论Git中回滚更改的最常见方法以及每种方法的适用时机。
例如,您可能会遇到这样的情况:您对一个文件进行了一系列更改,但后来决定不想保留这些更改。您可以使用 git checkout 命令,后跟要还原的文件名,将文件恢复到其先前的提交状态。
让我们使用我们的 scriptris 仓库来尝试一下。
我们将编辑 all_checks.py 脚本并删除 check_reboot 函数,然后保存并返回命令行。很好,我们已经完成了更改。
让我们运行脚本看看会发生什么。糟糕。🤢 删除该函数后,我们实际上破坏了脚本。让我们看看 git status 对此有何说法。
正如预期的那样,我们看到文件已被修改,并且更改尚未暂存。请注意Git如何为我们提供一些关于现在该做什么的有用提示。我们可以运行 git add 来暂存更改,或者运行 git checkout 来丢弃它们。
如果您需要帮助记住这个命令的作用,可以这样想:您正在从最新存储的快照中检出原始文件。现在让我们这样做。
我们将检出原始文件,然后查看 git status 对其有何说法,最后重新运行我们的脚本。
哎呀,看起来我们有一个拼写错误。让我们返回并修复它。完成。
通过这个例子,我们演示了如何在更改被暂存之前,使用 git checkout 来还原对已修改文件的更改。此命令会将文件恢复到最新存储的快照,该快照可以是已提交的或已暂存的。因此,如果您在暂存文件后又对其进行了其他更改,您可以将文件恢复到较早的暂存版本。
如果您需要检出单个更改而不是整个文件,可以使用 -p 标志来实现。这将逐个更改地询问您是否要恢复到先前的快照。
好的,关于撤消未暂存的更改就讲到这里。如果您已经将更改添加到暂存区了怎么办?
别担心,如果我们意识到将一些实际上不想提交的内容添加到了暂存区,我们可以使用 git reset 命令来取消暂存我们的更改。
暂存我们实际上不打算提交的更改是常有的事,特别是当我们使用像 git add * 这样的命令时,其中 * 是bash中使用的文件通配模式,会扩展为所有文件。此命令最终会将工作树中完成的任何更改添加到暂存区。虽然有时这可能是我们想要的,但也可能导致一些意外情况。
让我们用一个例子来试试。首先,我们假装正在尝试调试脚本中的一个问题。为此,我们创建了一个包含脚本输出的临时文件。然后,我们将使用 git add * 添加工作树中所有未暂存的更改,最后使用 git status 检查状态。
我们可以看到,这个本应用于调试的临时输出文件现在已被暂存到我们的仓库中,但我们并不想提交它。方便的是,git status 命令在输出中直接告诉我们如何取消暂存该文件。示例输出中提到了 HEAD 别名。还记得它的含义吗?没错,它就是当前检出的快照。因此,通过运行建议的命令,我们将把更改重置为当前快照中的内容。让我们试试看。
该文件在我们的工作树中再次变为未跟踪状态,并且不再被暂存。
您可以将 reset 视为 add 的对应操作。使用 add,您可以将更改添加到暂存区;使用 reset,您可以从暂存区移除更改。您还可以使用 git reset -p 让Git询问您想要重置哪些特定的更改。
但是,等等,让我们记得提交我们的拼写错误修复。
通过以上内容,我们已经了解了如何还原未暂存和已暂存的更改。但是,如果您已经创建了一个包含想要撤消的更改的提交,该怎么办呢?这是一个很好的问题,我们将在下一个视频中探讨。
本节课总结

在本节课中,我们一起学习了如何在Git中撤消提交前的更改。我们掌握了两个关键命令:
- 使用
git checkout <文件名>来丢弃工作区中对文件的修改,将其恢复到最近一次暂存或提交的状态。 - 使用
git reset HEAD <文件名>来将已暂存但未提交的更改移出暂存区,使其变回未暂存状态。
我们还了解到,可以通过为这些命令添加 -p 标志来进行更精细的、逐个更改的交互式操作。这些技能对于在提交前清理工作区和修正错误至关重要。
022:修改提交 ✏️

在本节课中,我们将学习如何使用Git的 --amend 选项来修改最近一次提交的内容或提交信息。这对于修正遗漏的文件或改进提交描述非常有用。
概述
通常,我们努力确保提交包含所有正确的更改和描述。但人难免犯错,开发者和IT专家时常会发现最近的提交中存在错误。因此,了解如何采取行动并修复这些错误至关重要。
修改提交的场景
假设你刚刚完成了最新一批工作的提交,但忘记添加一个属于同一更改的文件。你希望更新提交以包含该更改。或者,文件是正确的,但你意识到提交信息描述得不够充分,想修复描述以添加指向该提交所解决错误的链接。这时该怎么办?
我们可以使用 git commit --amend 命令来解决这类问题。
使用 git commit --amend
当我们运行 git commit --amend 时,Git会获取当前暂存区中的所有内容,并运行Git提交工作流程来覆盖之前的提交。
让我们通过一个例子来看看。我们将进入脚本目录,使用 touch 命令创建两个新文件,然后使用 ls 列出目录内容,添加我们的Python脚本,并提交它,声称我们添加了两个文件。
cd scripts
touch script1.py script2.py
ls
git add script1.py
git commit -m "Added two files"
如你所见,Git打印的消息显示只添加了一个文件。我们的提交信息说添加了两个文件,但我们忘记添加其中一个。别慌,我们可以修复它。
修正遗漏的文件
我们首先添加遗漏的文件,然后修改我们的提交。
git add script2.py
git commit --amend
我们调用了 git commit --amend,随后一个编辑器打开,显示了我们正在处理的提交信息和提交统计信息。此提交的已添加文件列表现在包含了我们想要添加的两个文件。
改进提交信息

现在文件已添加,我们还可以改进初始的提交信息,它原本有点简短。我们将保留现有的描述作为提交的第一句,然后添加一行描述每个文件的预期用途。
完成这些后,我们的提交就准备好被修改了。像往常一样保存新的描述。
仅修改提交信息
你也可以仅通过运行 git commit --amend 命令来更新前一次提交的信息,而无需更改暂存区的内容。
重要注意事项
虽然 git --amend 适用于修复本地提交,但你不应将其用于公共提交,即那些已推送到公共或共享仓库的提交。这是因为使用 --amend 会重写Git历史,移除之前的提交并用修改后的提交替换它。在与他人合作时,这可能导致一些混乱的情况,应绝对避免。
所以请记住:使用 amend 修复本地提交是很好的,你可以在修复后将其推送到共享仓库,但应避免修改已经公开的提交。如果现在听起来有些困惑,别担心,我们将在讨论通过共享仓库与他人协作时再次提及。
总结
本节课中,我们一起学习了如何修复暂存和非暂存的更改,以及如何修复不完整的提交。我们介绍了 git commit --amend 命令的使用方法和适用场景,并强调了其仅适用于本地提交的重要限制。
接下来,我们将讨论如果遇到需要完全回滚的错误提交时该怎么办。
023:Git回滚操作详解 🔄

在本节课中,我们将学习如何使用Git的revert命令来回滚已提交的更改。我们将了解回滚的原理、具体操作步骤,以及它如何帮助我们在不破坏项目历史记录的情况下修复错误。
在提交前修复工作内容是理想的做法,但如果更改已经被Git快照记录了,该怎么办?
假设你在公司服务器上托管了一个Git仓库,其中包含你和同事使用的各种有用的自动化脚本。
某个早晨,在喝咖啡之前,你对其中一个脚本做了一些修改,并提交了更新后的文件。
几小时后,你开始收到用户提交的工单,指出脚本的某些部分出现了故障。
根据他们描述的错误信息,问题似乎与你最近的更改有关。
你可以查看更新的代码,尝试找出错误所在。
但工单数量不断增加,你需要尽快修复问题。

你决定是时候进行回滚操作了。
回滚的方法与原理
有多种方法可以回滚提交。目前,我们将重点介绍使用git revert命令。
git revert并不仅仅意味着“撤销”。相反,它会创建一个新的提交,其中包含了错误提交中所做所有更改的反向操作,以此来抵消它们。
例如,如果在错误的提交中添加了某一行代码,那么在还原提交中,同一行代码将被删除。
这样,你就能达到撤销更改的效果,同时项目的提交历史记录保持一致,完整地记录了所发生的一切。
因此,git revert会创建一个新的提交,该提交与给定提交中的所有内容相反。
我们可以使用之前提到的HEAD别名来还原最新的提交,因为我们可以将HEAD视为指向你当前提交快照的指针。
当我们向revert命令传递HEAD时,我们是在告诉Git回退那个当前提交。
实践操作:添加并回滚错误提交
为了验证这一点,我们首先在我们的示例仓库中添加一个有错误的提交。
好的,我们已经在脚本中添加了一些代码。让我们保存并提交这个更改。
现在,我们的代码已经提交了。我们甚至没有测试它,这是一个坏主意。如果你在实际操作中这样做,可能已经发现了我们代码中的问题。
此时,我们的用户开始提交工单,说功能损坏了。于是我们运行脚本来看看发生了什么。
糟糕。我们调用了一个忘记定义的函数。
好的,是时候回滚了。让我们通过输入git revert HEAD来清除这段有问题的代码。
执行回滚命令
一旦我们发出git revert命令,就会看到之前见过的文本编辑器提交界面。
在这种情况下,我们可以看到Git已经自动在提交信息中添加了一些文本,表明这是一个回滚操作。第一行提到它正在还原我们刚刚完成的名为“add call to disk Full function”的提交。
额外的描述甚至包括了被还原提交的标识符。
虽然我们可以直接使用这个描述,但通常最好添加一个解释,说明我们为什么要进行回滚。
请记住,这些描述的目的是帮助我们未来的自己理解事情发生的原因。在本例中,我们将解释回滚的原因是代码调用了一个未定义的函数。
输入完描述后,我们可以像往常一样退出并保存。
检查回滚结果
你会注意到,git revert命令的输出看起来与git commit命令的输出相似。
这是因为git revert为我们创建了一个提交。由于我们的还原是一个普通的提交,我们可以在日志中看到原始提交和还原提交。
让我们使用-p和-2作为参数来查看日志中的最后两个条目。
如前所述,-p参数让我们可以看到提交创建的补丁,而-2参数将输出限制在最后两个条目。
在这个日志中,我们可以看到,当我们调用revert时,Git创建了一个新的提交,它是前一个提交的反向操作。这删除了我们在前一个提交中添加的行。
我们可以看到,原始提交通过前面的加号显示了我们添加的行。同样的行在较新的提交信息中显示为减号,表明它们已被删除。
就这样,错误的提交被还原了,错误也随之停止。
总结与展望
在本例中,我们还原了仓库中的最新提交。

但如果我们需要还原更早的提交呢?
准备好你的时间机器,因为在下一个视频中,我们将进行更大幅度的时光倒流。
本节课中,我们一起学习了git revert命令的核心概念和操作流程。我们了解到回滚是通过创建一个反向提交来安全地撤销更改,从而保留了完整且清晰的项目历史。记住,清晰的提交信息对于未来的维护至关重要。
024:Git提交识别与回滚 🎯

在本节课中,我们将学习如何识别Git历史中的特定提交,并使用提交ID来回滚到过去的某个版本。我们将了解提交ID的生成原理及其重要性,并掌握回滚非最近提交的操作方法。

概述
到目前为止,我们使用 HEAD 别名来指定Git历史中最近检出的提交。在我们的错误快照示例中,错误恰好发生在最近创建的提交中。但错误有时可能需要一段时间才能被发现,因此我们可能需要回滚更早的提交。我们可以通过使用提交ID来定位特定的提交。
提交ID与哈希算法

我们已经多次看到提交ID。它们在我们运行 git log 命令时出现,并且我们在上一个例子中也看到了被回滚提交的ID。提交ID是日志消息中“commit”一词后出现的复杂字符串。让我们查看一下我们checks仓库中的最新日志条目。
提交ID是“commit”一词后的40个字符长的字符串。你肯定不会错过它。
这个由字母和数字组成的长字符串实际上是一种称为“哈希”的东西,它是使用名为SHA1的算法计算得出的。本质上,该算法接收一堆数据作为输入,并从这些数据中生成一个40个字符的字符串作为输出。
在Git中,输入是与提交相关的所有信息,而40个字符的字符串就是提交ID。像SHA1这样的加密算法可能非常复杂,因此我们不会深入探讨其含义。如果你感兴趣,可以在接下来的阅读材料中找到更多信息的链接。
然而,你可能会想,为什么使用一长串混乱的字母作为提交的ID,而不是像1、2、3那样递增一个整数?为了回答这个问题,让我们快速看一下Git使用哈希而不是计数器的原因,以及该哈希是如何计算的。
尽管SHA1属于加密哈希函数类别,但Git并不真正将这些哈希用于安全目的,而是用它们来保证我们仓库的一致性。
拥有一致的数据意味着我们得到的结果与预期完全一致。引用Git的创建者Linus Torvalds的话:“你可以验证你取回的数据与你放入的数据完全相同。”这在像Git这样的分布式系统中非常有用,因为每个人都有自己的仓库,并且传输自己的数据片段。
计算哈希可以保持数据的一致性,因为它是根据构成提交的所有信息计算得出的:提交消息、日期、作者以及工作树的快照。两个不同的提交产生相同哈希(通常称为“碰撞”)的几率极小,小到几乎不可能偶然发生。

需要大量的处理能力才能故意使这种情况发生。如果你使用哈希来保证一致性,那么如果不改变SHA1哈希,你就无法更改Git提交中的任何内容。
还记得我们关于使用 git commit --amend 命令修复提交的讨论吗?每次我们修改提交时,提交ID都会改变。这就是为什么在已公开的提交上不使用 --amend 很重要的原因。
提交ID提供的数据完整性意味着,如果坏的磁盘或网络链路损坏了你仓库中的某些数据,或者更糟的是,如果有人故意损坏某些数据,Git可以使用哈希来发现这种损坏。它会说:“你得到的数据不是你期望的数据。出错了。”谢谢你,Git侦探。你又一次拯救了这一天。
好了,背景故事讲得够多了。你如何使用提交ID来指定要处理的特定提交,比如在回滚期间?让我们使用 git log -2 命令查看我们仓库中的最后两个条目。
识别与回滚特定提交
假设我们意识到我们实际上喜欢脚本的旧名称,因此我们想要回滚重命名它的那个提交。首先,让我们使用之前视频中提到的 git show 命令查看那个特定的提交。
我们复制并粘贴了我们想要显示的提交ID,这很有效。或者,我们可以只提供标识提交的前几个字符给命令,Git将足够智能地猜测哪个提交ID以这些字符开头。只要只有一个匹配的可能性,让我们试试这个。两个字符不够,但通常四到八个字符就足够了。
现在我们已经看到了如何识别我们想要回滚的提交,让我们用这个标识符调用 git revert 命令。像往常一样,这将打开一个编辑器,我们应该在其中添加回滚的原因。在这种情况下,我们会说之前的名字实际上更好。为反复无常欢呼吧。
正如我们之前提到的,当我们生成回滚时,Git会自动包含我们正在回滚的提交的ID。这在查看包含大量提交的复杂历史的仓库时非常有用。
现在,一旦我们保存并退出提交消息,它实际上将执行回滚并生成一个具有自己ID的新提交。看到在我们提交的名称之前,revert 命令已经显示了提交ID的前八个字符。让我们使用 git show 来查看它。
总结
在本节课中,我们一起学习了如何通过提交ID来精确定位Git历史中的特定提交。我们了解到提交ID是一个由SHA1算法生成的40位哈希值,它保证了仓库数据的完整性和一致性。我们还实践了使用 git show 和 git revert 命令来查看和回滚非最近的提交。
以下是本课涉及的核心命令回顾:

git log:查看提交历史,获取提交ID。git show <commit_id>:显示特定提交的详细信息。git revert <commit_id>:回滚指定的提交,并创建一个新的反向提交。
我们已经成功回滚了一个并非最近的提交。干得好,时间旅行者们。
在过去的几个视频中,我们涵盖了许多撤销操作和修复错误的方法,无论是取消暂存更改、暂存更改、修改提交还是回滚更改。如果还有什么不清楚的地方,现在是在本地计算机上练习这些命令的好时机。尝试操作,并想出更多你想要测试的用例示例。
接下来,我们准备了一个方便的速查表,总结了我们刚刚涵盖的所有内容。当你准备好时,请继续下一个测验,将所有新知识付诸实践。
我们在过去的几个视频中学到了很多复杂的东西,所以一旦你完成了那个测验,你应该奖励自己休息一下,以度过这些技术细节。这是你应得的。喝杯咖啡、茶或吃点零食,让这些概念沉淀一下。我们下一个视频见。
025:什么是Git分支? 🌿


在本节课中,我们将深入学习Git中一个核心概念——分支。我们将探讨分支是什么、为什么使用分支,以及它如何帮助我们在项目中安全地进行并行开发和实验。
到目前为止,我们只是简要提及过分支。你可能在提交信息中看到过“on branch master”这段文字,或者记得我们在讨论HEAD指针时提到过分支。

分支是Git工作流程的重要组成部分。因此,在接下来的视频中,我们将深入探讨分支。
那么,什么是分支?分支有什么用?在最基本的层面上,Git分支只是一个指向特定提交的指针。

但更重要的是,它代表了项目中的一个独立开发线,而它所指向的提交正是这条开发历史链中的最新一环。
当初始化一个新仓库时,Git为你创建的默认分支叫做 master。
到目前为止,我们所有的示例和开发工作都发生在这个分支上。master分支通常用于代表项目的“已知良好”状态。
当你想在项目中开发一个新功能或尝试一些新东西时,你可以创建一个独立的分支来进行工作,而不用担心破坏当前的工作状态。

如果这听起来令人困惑,也许一个类比会有所帮助。你可以把一个Git项目想象成课堂上老师布置的作业。你在一组笔记本上完成作业,每个笔记本代表一个不同的分支。
你使用一些笔记本来草草记下草稿和实验,但你保持一个笔记本——即master分支——处于整洁状态,并将这些草稿的完善版本复制进去。


请不要在master笔记本上涂鸦。

分支使得在项目中尝试新想法或策略变得非常容易。

当你想添加功能或修复某些东西时,你可以创建一个新分支并在那里进行开发。当你得到满意的结果时,可以将其合并回master分支;如果尝试不成功,也可以丢弃更改而不会产生负面影响。
在Git中,分支作为正常开发工作流程的一部分被频繁使用。


举个例子,回想一下我们在之前视频中修复的那个有问题的提交:我们添加了对disk_full函数的调用,但忘记实际定义这个函数。因此我们不得不回滚它,因为用户看到了错误。
以我们现在所学的知识来看,我们本可以在一个独立的分支上完成那项工作,也许可以命名为类似 add_disc_full 的分支。
那样的话,我们就可以在那个分支上迭代我们的代码,直到它正确工作,而不会影响master分支。只有在代码准备好部署时,我们才会将这些更改合并回master分支。
在接下来的几个视频中,我们将学习如何创建新分支以及如何将其内容合并到master分支。我们还将介绍如果遇到棘手的合并冲突该怎么办。
请注意,接下来的内容会变得相当复杂,因此请确保在你的计算机上跟随我们所有的练习,并持续练习,尝试使用分支和合并的新方法,直到你对我们展示的每个步骤都感到得心应手。
本节课总结
在本节课中,我们一起学习了Git分支的核心概念。我们了解到分支本质上是一个指向提交的指针,它代表了一条独立的开发线。master分支是默认的“稳定”分支,而创建新分支允许我们安全地实验和开发新功能,而不会干扰主线的稳定状态。理解分支是掌握高效、协作式Git工作流的关键一步。
026:Git分支操作入门 🎯

在本节课中,我们将学习Git中一个核心概念——分支。我们将了解如何创建、切换分支,以及如何在分支上进行独立的开发工作。分支是多人协作和功能开发的基础,掌握它是高效使用Git的关键。

分支的重要性与基本操作
上一节我们介绍了Git的基本概念,本节中我们来看看如何实际操作分支。分支在Git的工作流程中至关重要,因此存在多种操作方式。我们可以使用 git branch 命令来列出、创建、删除和管理分支。直接运行 git branch 命令会显示仓库中所有分支的列表。
让我们在 checks 仓库中尝试一下。目前我们的列表看起来相当空,但别担心,创建分支非常简单。我们通过调用 git branch 加上新分支的名称来创建。

以下是创建并列出分支的步骤:
- 创建一个名为
new_feature的新功能分支。 - 再次使用
git branch列出所有分支。
我们的新分支是基于 HEAD 的值创建的。请记住,这不一定是 master 分支。我们得到的列表显示我们仍然在 master 分支上。我们可以分辨出来,因为当前分支在命令输出中用一个星号(通常以不同颜色显示)来指示。
切换分支
我们稍后会探讨 git branch 命令允许我们对分支进行的其他操作,但现在我们想切换到一个新分支。为此,我们需要使用 git checkout 命令。我们之前看到过如何使用 git checkout 将修改的文件恢复到最新提交的状态。

检出分支在原理上是相似的,工作树会被更新以匹配所选分支,包括文件和Git历史记录。如果一开始觉得有点困惑,这很正常。我最初也觉得难以理解。但请放心,在我们使用这些命令一段时间后,它会变得更清晰。记住我们使用 git checkout 来检出文件和分支的最新快照,这可能会有所帮助。
好的,让我们通过调用 git checkout new_feature 切换到我们的新功能分支,然后再次列出我们的分支。之前我们在 master 分支上工作,但现在我们切换到了新分支,星号已经移到了 new_feature。
高效创建并切换分支
创建分支并立即切换到它是一个非常常见的任务,常见到Git为我们提供了一个有用的快捷方式,可以用一个命令创建新分支并切换到它。我们可以使用 git checkout -b <new_branch> 来做到这一点。
请看,消息显示我们已经切换到了一个新分支。我们仅用一个命令就创建了新分支并切换到它,非常高效,对吧?
在新分支上工作

现在我们已经有了闪亮的新分支,让我们在其中创建一个新文件。我们将创建一个新的Python 3文件,它将包含常用的 shebang 行、一个空的 main 函数以及对该函数的调用。
这个文件是空的,因为它只是我们工作的开始。由于它在一个独立的分支中,尚未完成是可以的。现在让我们保存文件并将其提交到当前分支。
好的,我们已经在这个分支中添加了一个提交,情况看起来更好了。让我们检查日志中的最后两个条目。我们看到这个分支中的最后两次提交,请注意在最新提交ID旁边,Git显示这是 HEAD 指向的位置,并且该分支名为 even_better_feature。而在前一次提交旁边,Git显示 master 和 new_feature 分支都指向该项目的快照。通过这种方式,我们可以看到 even_better_feature 分支领先于 master 分支。
总结
本节课中我们一起学习了如何在Git中创建新分支、使用 git checkout 切换分支,以及利用 git checkout -b 快捷方式一步完成创建和切换。我们还实践了在独立分支上进行开发并提交更改。通过这些操作,我们看到了分支如何允许我们在不影响主线代码的情况下并行开发新功能。你的分支知识已经打下了坚实的基础。接下来,我们将学习更多关于操作分支的内容,敬请期待。
027:Git分支管理教程(第3课) 🌿

概述
在本节课中,我们将学习Git分支的基本操作,包括如何切换分支、查看分支状态以及删除分支。通过实际操作,你将理解分支在Git中的工作原理及其重要性。
回顾与当前状态
上一节我们介绍了如何创建新分支并向其添加提交。现在,让我们通过调用git status和ls命令来检查仓库的当前状态。
git status
ls
结果显示,我们当前位于“even_better_feature”分支,工作树是干净的,并且新文件“free_memory.py”存在于工作树中。
切换分支与HEAD指针
现在,让我们使用git checkout master命令切换回主分支,并列出该分支上的最近两次提交。
git checkout master
git log --oneline -2
当我们使用git checkout切换分支时,Git在底层会改变HEAD指针的指向。通过这次切换,HEAD从指向“even_better_feature”分支的最新提交,改为指向主分支的最新提交。
因此,“even_better_feature”分支的提交不会显示在主分支的历史中,最新的快照是我们之前见过的第二个条目。
工作目录的变化
记住,当我们切换分支时,Git还会将工作目录中的文件更改为HEAD当前指向的快照。让我们查看当前目录的内容。
ls
“free_memory.py”文件不见了。这演示了当我们切换分支时,Git会更新工作目录和提交历史,以反映该分支中项目的快照。
当我们在新分支上提交更改时,这些更改会被添加到该分支的历史中。由于“free_memory.py”是在另一个分支上提交的,它不会出现在主分支的历史或工作目录中。
分支的本质
经过这一系列操作后,需要注意的一点是,每个分支只是指向一系列快照中特定提交的指针。创建新分支非常容易,因为不需要复制任何数据。
当我们切换到另一个分支时,我们实际上是在检出不同的提交,并同时更新HEAD和工作目录的内容。HEAD随着我们的移动而浮动,就像一个自由的灵魂。
删除分支
我们已经看到了如何创建和切换分支,那么如果我们想删除一个不再需要的分支呢?我们可以使用git branch -d命令来实现。
以下是删除分支的步骤:
首先,列出当前仓库中的分支。
git branch
然后,通过调用git branch -d new_feature命令删除“new_feature”分支。
git branch -d new_feature
就这样,我们的分支被删除了。我们可以通过再次调用git branch来确认它已不存在。
git branch
如果要删除的分支中有尚未合并回主分支的更改,Git会通过错误提示告知我们。幸运的是,Git还会提供命令,让我们在确定要删除分支时执行,即使它有未合并的更改。
但我们暂时不会这样做。实际上,我们希望将这些更改合并回仓库中。

总结
本节课我们一起学习了Git分支管理的核心操作。我们了解了如何切换分支、查看分支状态,以及删除不需要的分支。通过实际操作,我们看到了分支在Git中的工作原理,以及它们如何帮助我们高效管理项目版本。
在下一节课中,我们将学习如何将分支中的更改合并回主分支,敬请期待!
028:Git分支管理 - 合并操作详解 🧩

在本节课中,我们将学习Git中一个核心操作:合并(Merge)。我们将了解合并的基本概念、两种主要的合并算法(快进合并与三方合并),以及合并过程中可能出现的冲突情况。掌握这些知识,你将能够有效地整合不同分支的代码变更。
概述:什么是合并?

在Git中管理分支的一个典型工作流是:为开发任何新功能或修改创建一个独立的分支。一旦新功能开发完成且状态良好,我们便将这个独立分支合并回代码的主干(如master分支)。
合并是Git用于将分支数据和历史记录组合在一起的术语。
执行合并操作
我们将使用 git merge 命令。这个命令允许我们获取一个Git分支的独立快照和历史记录,并将其整合到另一个分支中。

让我们用上一个视频中的示例分支来尝试一下。
首先,确认我们当前位于master分支:
git checkout master
然后,执行合并命令,将 even-better-feature 分支合并到 master 分支:
git merge even-better-feature
现在,我们已经让master分支同步了最新内容。可以通过查看 git log 来验证这一点。
合并算法:快进合并 vs. 三方合并
Git使用两种不同的算法来执行合并:快进合并(Fast-forward) 和 三方合并(Three-way merge)。
我们刚刚执行的合并就是一个快进合并的例子。当被检出的分支(例如master)的所有提交,也都存在于要被合并的分支(例如even-better-feature)中时,就会发生这种合并。在这种情况下,我们可以说两个分支的提交历史没有分叉。Git只需要将分支的指针更新到同一个提交即可,不需要进行实际的代码合并。
另一方面,当合并分支的历史以某种方式分叉,并且没有一条清晰的线性路径可以通过快进来组合它们时,就会执行三方合并。

理解三方合并与冲突
当两个分支分叉后,在其中一个分支上进行了新的提交,就会发生三方合并。在我们的例子中,如果我们在创建了其他分支之后,又在master分支上进行了提交,就会出现这种情况。
此时,Git会通过创建一个新的合并提交来将分支历史联系在一起。它会将两个分支尖端的快照与最近的共同祖先(即分叉点之前的那个提交)的快照进行合并。
为了成功合并,Git会尝试找出如何组合这两个快照:
- 如果修改发生在不同的文件,或同一文件的不同部分,Git会接受所有更改并将它们整合到结果中。
- 如果修改发生在同一文件的同一部分,Git将不知道如何合并这些更改,尝试合并将导致合并冲突。
这听起来可能有点吓人,但请不要惊慌。Git不会放弃,我们将在下一个视频中学习如何解决这些冲突。
总结
本节课中,我们一起学习了Git的核心合并操作。我们了解了执行合并的基本命令 git merge,并深入探讨了Git使用的两种合并策略:快进合并和三方合并。我们还初步认识了合并过程中可能出现的冲突及其原因。理解这些概念是进行高效团队协作和代码版本管理的基础。在下一课中,我们将具体学习如何解决合并冲突。
029:28_合并冲突 🛠️

在本节课中,我们将学习如何处理Git中的合并冲突。合并冲突发生在两个分支对同一文件的同一部分进行了不同的修改,而Git无法自动决定如何合并时。我们将通过一个具体示例,演示如何识别、解决冲突,并最终完成合并。
概述
有时,我们尝试合并的两个分支可能对同一文件的同一部分进行了编辑。这将导致所谓的“冲突”。通常,Git可以自动为我们合并文件,但当出现合并冲突时,Git需要一些帮助来决定如何处理。
创建冲突场景
上一节我们介绍了分支的基本操作,本节中我们来看看如何模拟一个合并冲突。
首先,我们在master分支上编辑free_memory.py文件,将pass语句替换为一个关于main函数功能的注释。
# 主函数应打印系统内存信息
完成更改后,保存文件并将其提交到master分支。
接下来,我们切换到even-better-feature分支,并在文件的相同位置进行另一处修改。这次,我们将pass调用替换为一个print函数调用。
print("一切正常")
保存此更改并将其提交到even-better-feature分支。
现在,我们的文件已经设置好,即将产生冲突。
尝试合并并发现冲突
让我们再次检出master分支,并尝试将even-better-feature分支合并进来。
git checkout master
git merge even-better-feature
Git会提示它尝试自动合并free_memory.py文件的两个版本,但不知道如何操作。这表明发生了合并冲突。
我们可以使用git status命令来获取更多信息。
git status

git status命令提供了许多额外信息。它告诉我们当前存在未合并的文件,我们需要修复这些冲突,或者如果我们认为合并是个错误,可以中止合并。它还提示我们需要对每个未合并的文件运行git add命令,以标记冲突已解决。
解决冲突
以下是解决冲突的步骤:
-
打开冲突文件:在文本编辑器中打开
free_memory.py文件。Git已在文件中添加了标记,告诉我们代码的哪些部分存在冲突。<<<<<<< HEAD # 主函数应打印系统内存信息 ======= print("一切正常") >>>>>>> even-better-feature<<<<<<< HEAD和=======之间的内容是当前分支(master)的内容。=======和>>>>>>> even-better-feature之间的内容是待合并分支(even-better-feature)的内容。
-
手动编辑文件:我们需要决定保留哪个版本,或者完全更改文件内容。在本例中,我们决定保留两个语句,并删除合并标记。修改后的文件内容如下:
# 主函数应打印系统内存信息 print("一切正常") -
标记冲突已解决:保存文件后,运行
git add命令来标记冲突已解决。git add free_memory.py -
检查合并状态:再次运行
git status,可以看到Git提示所有冲突都已解决,我们只需要运行git commit来完成合并。git status -
完成合并提交:运行
git commit。Git会显示一个与其他提交不同的注释,因为它是一个合并提交。注释会自动包含合并了哪个分支以及哪个文件存在冲突(现已解决)的信息。我们可以在此基础上添加更多描述。git commit在打开的编辑器中,我们可以在自动生成的描述后添加一行,例如“保留了两个分支的代码行”,然后保存并退出。
至此,合并冲突已成功解决。
查看提交历史
要查看解决冲突后的提交历史,我们可以使用git log命令的两个便捷选项:--graph用于以图形方式查看提交,--oneline用于每行只显示一个提交的概要。
git log --graph --oneline

这种格式帮助我们更好地理解提交历史和合并是如何发生的。我们可以看到新添加的合并提交,以及被合并的两个独立提交(一个来自master分支,另一个来自even-better-feature分支)。我们还可以看到master指针指向了合并提交,而even-better-feature指针仍指向其之前的提交。
处理复杂冲突与中止合并
在我们的示例中,解决冲突是直接且简单的。但在实际工作中,情况并非总是如此。合并冲突有时可能很棘手、复杂,并且分布在多个文件中。
如果你想放弃合并并重新开始,可以使用git merge --abort命令作为“逃生舱口”。
git merge --abort
这将停止合并过程,并将工作目录中的文件重置回合并发生之前的上一次提交状态。
总结
本节课中我们一起学习了Git合并冲突的处理。你现在已经知道如何创建、删除和切换分支,并且了解到每个分支都代表指向一个提交的指针和一系列独立的快照。你还学会了如何使用git merge命令将这些提交合并回主干。
出色的工作。这确实不是容易的内容。接下来,你将找到一份总结所有这些分支技术的速查表,随后还有一个测验来巩固这些概念。
030:模块2总结 - 在本地使用Git 🧑💻
在本节课中,我们将总结模块2的核心内容,回顾在本地环境中使用Git进行版本控制的一系列高级操作。我们将涵盖跳过暂存区、查看提交信息、撤销更改以及分支管理等关键技能。

在之前的课程中,我们深入探讨了Git的多个高级功能。现在,让我们系统地回顾这些核心概念,为后续的评估和实践做好准备。

高级Git命令回顾 🔄
上一节我们介绍了Git的基础,本节中我们来看看一些更高效的命令,它们能帮助我们绕过暂存区并获取更详细的提交信息。
跳过暂存区
有时我们希望跳过git add步骤,直接将工作区的更改提交。这可以通过git commit命令的-a选项实现。
公式/代码:
git commit -a -m "提交信息"
此命令会自动暂存所有已跟踪文件的更改并进行提交。
获取提交的详细信息
要查看某次提交的完整信息,包括更改的具体内容,可以使用git show命令。
公式/代码:
git show <commit-hash>
将<commit-hash>替换为具体的提交哈希值。
仓库文件管理
以下是关于在Git仓库中直接管理文件的操作:
- 删除文件:使用
git rm <文件名>命令从工作区和暂存区中删除文件。 - 重命名文件:使用
git mv <旧文件名> <新文件名>命令来重命名文件,Git会自动记录此次更名操作。
撤销与回滚操作 ↩️
版本控制的核心优势之一是能够“后悔”。我们已经学习了多种撤销更改的方法。
撤销工作区更改
要丢弃工作区中某个文件的修改,将其恢复到最近一次提交的状态,可以使用git checkout命令。
公式/代码:
git checkout -- <文件名>
撤销暂存区更改
如果文件已被git add到暂存区,但你想取消暂存,可以使用git reset命令。
公式/代码:
git reset HEAD <文件名>
修正提交
如果刚刚完成的提交有遗漏或信息有误,可以使用--amend选项来修改最后一次提交。
公式/代码:
git commit --amend
此命令会打开编辑器让你修改提交信息,并且如果暂存区有新的更改,也会一并加入这次提交。
执行回滚
以下是两种回滚到历史版本的方法:
- 回滚最新提交:使用
git revert HEAD创建一个新的提交来撤销最近一次提交的更改。这是一种安全的、不破坏历史的撤销方式。 - 回滚到特定旧版本:使用
git revert <commit-hash>可以撤销历史中的某一次特定提交。
分支管理 🌿
分支是Git最强大的功能之一,它允许我们在独立的开发线上工作。
分支基础操作
以下是分支的创建、切换和删除等基本操作:
- 创建分支:
git branch <分支名>创建一个新分支。 - 切换分支:
git checkout <分支名>切换到指定分支。较新版本的Git也支持git switch <分支名>。 - 创建并切换分支:
git checkout -b <分支名>可以一次性完成创建和切换。 - 删除分支:
git branch -d <分支名>在分支合并后安全删除它。使用-D选项可以强制删除未合并的分支。
合并分支与解决冲突
将不同分支的工作成果合并是常见操作。
合并分支:首先切换到要接收更改的分支(如main),然后执行git merge <分支名>将指定分支的修改合并进来。
解决合并冲突:当两个分支对同一文件的同一部分进行了不同修改时,Git无法自动合并,会产生冲突。以下是解决步骤:
- Git会将冲突标记在文件中(
<<<<<<<,=======,>>>>>>>)。 - 手动编辑文件,选择保留的代码,并删除所有冲突标记。
- 将解决后的文件添加到暂存区(
git add)。 - 完成合并提交(
git commit)。
本节课中我们一起学习了在本地使用Git进行高效版本控制的综合技能。我们回顾了跳过暂存区、查看提交、撤销更改和修正提交的方法,并深入掌握了分支的创建、切换、合并以及冲突解决。
请花时间复习这些内容,直到你感到有信心迎接接下来的评估。在下一个视频中,我们将探索如何远程使用和管理Git仓库,并学习在协作环境中应用Git。
031:使用远程仓库介绍 🗂️


在本节课中,我们将学习什么是远程仓库,以及如何使用它们进行协作。到目前为止,我们已掌握了许多在本地使用Git的有趣且复杂的技术。接下来,我们将探索如何将Git的强大功能扩展到团队合作中。
回顾本地Git操作 📝
上一节我们介绍了Git的基本工作流程,包括修改文件、暂存更改和提交更改。我们还学习了其他Git操作,例如删除或重命名文件、获取提交的详细信息,以及完全跳过暂存区。
接着,我们探讨了如何在Git仓库中撤销已暂存、未暂存或已提交的更改。最后,我们学习了如何处理不同的分支,例如如何合并来自不同分支的更改。
这些内容相当复杂,初学者感到畏惧是正常的。但一旦开始理解其运作原理,便会感到非常满足。请记住,这些新技能对推进你的IT职业生涯极具价值。
你可以根据需要多次复习材料,掌握它的最佳方式是持续自行练习。如果仍有不清楚的地方,别忘了使用讨论论坛寻求帮助。
引入远程仓库与协作 🌐
本节中,我们来看看与GitHub和远程仓库相关的一系列新知识。我们将首先讨论GitHub是什么及其重要性,然后深入探讨如何使用GitHub和其他远程仓库。
能够使用远程仓库使我们能够有效地与他人协作。我们的协作者可以与我们坐在同一间办公室,也可以身处不同大陆、在不同时区工作。
使用像Git这样的版本控制系统,让我们能够整合不同人员的工作,无论他们身在何处或何时工作。
我个人曾通过版本控制系统有过许多有趣的协作经历。其中一个突出例子是我和一位朋友在工作时进行的调试会话。当时我正在处理一些棘手的代码,无法找出实现某个函数的最佳方法。我陷入了困境,几乎准备完全放弃。我的同事建议我先处理文件中更靠后的另一部分,而他则为我遇到困难的代码寻找最优解决方案。
这是一次极其顺畅的体验,而这只有在版本控制系统的帮助下才可能实现。当然,也离不开团队合作的超级力量。
到本模块结束时,你将能够像我一样与朋友和同事进行协作。让我们开始吧。
核心概念与操作 🛠️
以下是使用远程仓库涉及的核心步骤:
- 添加远程仓库:使用
git remote add <name> <url>命令将远程仓库链接到你的本地仓库。 - 推送更改:使用
git push <remote-name> <branch-name>命令将本地提交上传到远程仓库。 - 拉取更改:使用
git pull <remote-name> <branch-name>命令从远程仓库下载更改并合并到本地分支。 - 克隆仓库:使用
git clone <url>命令获取远程仓库的完整副本到本地。
总结 📋
本节课中我们一起学习了远程仓库的概念及其在协作中的重要性。我们回顾了之前学到的本地Git操作,并引入了通过GitHub等平台进行远程协作的基本思路。掌握这些技能将为你打开团队合作的大门,无论团队成员分布何处。在接下来的课程中,我们将深入实践这些操作。
032:什么是GitHub?🤔


在本节课中,我们将要学习什么是GitHub,以及它如何作为Git的远程仓库托管服务,帮助我们进行团队协作和代码管理。

在之前的视频中,我们提到Git是一个分布式版本控制系统。
分布式意味着每个开发者本地机器上都拥有整个仓库的一个完整副本。
从本地到远程:协作的桥梁 🌉
上一节我们介绍了Git的分布式特性,本节中我们来看看如何将这些分散的副本连接起来。
每个副本都是彼此对等的,但我们可以将其中一个副本托管在服务器上,并将其作为其他副本的远程仓库。
这让我们能够通过这台服务器在不同副本之间同步工作。
任何人都可以创建这样的Git服务器,许多公司也拥有类似的内部服务。
托管服务:GitHub登场 🚀
如果你不想自己搭建Git服务器并托管仓库,可以使用像GitHub这样的在线服务。
GitHub是一个基于Web的Git仓库托管服务。
除了Git的版本控制功能,GitHub还包含额外的功能,例如:
- 错误跟踪
- Wiki
- 任务管理
GitHub让我们能够在网络上共享和访问仓库,并将其复制或克隆到本地计算机以便进行工作。
GitHub因其强大的功能集而成为流行选择,但它并非唯一选择。
提供类似功能的其他服务包括Bitbucket和GitLab。
在本课程的剩余部分,我们将使用GitHub进行示例演示,但你可以自由选择最适合你需求的工具。
GitHub的账户与仓库 📦
要使用GitHub,首先需要创建一个账户(如果还没有的话)。在线注册是免费且相对简单的。
完成注册后,你可以创建自己的仓库,或为其他项目的仓库做贡献。
GitHub为公共和私有仓库提供免费的Git服务器访问权限。
它对免费私有仓库的贡献者数量有限制,并提供按月付费的无限制私有仓库服务。
我们的示例将使用免费仓库,这对于教育用途、小型个人项目或开源开发来说是合适的。
重要安全提示 ⚠️
以下是关于如何管理这些仓库的一个重要警告。

如果黑客掌握了关于你组织IT基础设施的信息,他们可能会利用这些信息试图入侵你的网络。
因此,请确保将这些信息视为机密。
对于真实的配置和开发工作,你应该使用安全、私有的Git服务器,并限制有权在其上工作的人员。
本节课中我们一起学习了GitHub的核心概念:它是一个基于Web的Git仓库托管平台,为分布式协作提供了中心化的桥梁。我们了解了它的基本功能、账户创建以及使用时的安全注意事项。
如果你还没有GitHub账户,现在是一个创建的好时机。请访问 Github.com 注册他们的服务。完成之后,可以继续观看下一个视频,我们将介绍与GitHub的一些基本交互操作。
033:与GitHub的基本交互 🚀

在本节课中,我们将学习如何与GitHub进行基本交互,包括创建远程仓库、克隆到本地、推送更改以及拉取更新。我们将通过一个简单的“健康检查”脚本仓库示例来演示整个流程。
概述
GitHub是一个在线服务,用于托管Git仓库。要使用它,首先需要创建一个账户。创建账户后,就可以在GitHub上建立新的仓库,并与本地仓库进行同步。本节将引导你完成从创建远程仓库到进行基本版本控制操作的全过程。
创建GitHub仓库
上一节我们介绍了Git的基本概念,本节中我们来看看如何在GitHub上创建一个新的远程仓库。
以下是创建新仓库的步骤:
- 登录GitHub账户后,点击左侧的“Create a repository”链接。
- 进入仓库创建向导。首先,为仓库命名,例如
health-checks。 - 填写仓库描述,例如“用于检查计算机健康状况的脚本”。
- 选择仓库的可见性,可以选择“Public”(公开)或“Private”(私有)。本例中选择“Private”。
- 向导提供初始化选项,如添加README文件、
.gitignore文件或许可证。本例中仅选择“Initialize this repository with a README”。 - 点击“Create repository”按钮完成创建。
完成后,一个全新的远程仓库就准备就绪了。
克隆远程仓库
现在我们已经有了一个远程仓库,下一步是将其复制到本地计算机上,以便进行工作。
我们使用 git clone 命令来实现这一点。该命令的基本格式是:

git clone <repository_url>
操作步骤如下:
- 在GitHub仓库页面,找到并复制仓库的URL。
- 在本地终端中,运行
git clone命令并粘贴复制的URL。 - 首次操作时,GitHub会要求输入用户名和密码进行身份验证。
命令执行后,远程仓库的完整副本就被下载到了本地机器上,并自动创建了一个与仓库同名的目录(例如 health-checks)。
进行本地修改并提交
进入克隆下来的本地仓库目录后,可以看到目前只有一个GitHub自动生成的README.md文件。这个文件使用Markdown格式。
我们可以修改这个文件来添加内容。修改完成后,需要将更改提交到本地仓库。

之前我们学习过几种提交方式,这里使用一个快捷命令来一次性完成暂存和提交:
git commit -a -m "Updated README with project description"
这个命令中的 -a 参数会自动暂存所有已跟踪文件的更改,-m 参数则用于添加提交信息。
推送更改到远程仓库
本地提交完成后,我们需要将这些更改同步到GitHub上的远程仓库。
使用 git push 命令可以将本地提交的“快照”推送到远程仓库。其基本命令是:
git push
运行此命令后,可能需要再次输入密码。推送成功后,在GitHub仓库页面上刷新,就能看到README文件的内容已经更新。
这样,我们就成功地将本地计算机上的更改推送到了GitHub托管的远程仓库中。
配置凭证缓存
你可能注意到,在克隆和推送时都需要输入密码。有几种方法可以避免每次操作都进行验证。
一种方法是配置Git的凭证助手来缓存密码。Git自带了一个凭证缓存助手,只需启用它即可。

启用凭证缓存的命令是:
git config --global credential.helper cache
启用后,首次操作仍需输入凭证,之后在15分钟的有效期内,后续操作将不再需要输入密码。
为了验证缓存是否生效,可以尝试使用 git pull 命令从远程仓库拉取更新(即使没有新内容)。首次执行 git pull 时需要凭证,之后在同一窗口期内则不需要。
git pull

总结
本节课中我们一起学习了与GitHub交互的核心工作流:
- 创建远程仓库:在GitHub上建立新的项目仓库。
- 克隆仓库:使用
git clone将远程仓库复制到本地。 - 本地修改与提交:在本地工作,并使用
git commit记录更改。 - 推送更改:使用
git push将本地提交同步到GitHub。 - 管理凭证:使用
git config credential.helper cache缓存登录信息,简化操作。
我们演示了从零开始创建一个私有仓库,进行简单修改,并完成本地与远程同步的完整过程。在后续课程中,我们将深入探讨这些命令背后的原理以及更高级的协作技巧。
034:什么是远程仓库?🌐


在本节课中,我们将要学习Git中一个核心概念——远程仓库。我们将了解远程仓库是什么,为什么它在协作中至关重要,以及它如何与我们的本地仓库互动。
当我们克隆新创建的GitHub仓库时,我们的本地Git仓库就与一个远程仓库建立了联系。
远程仓库是Git分布式协作特性的重要组成部分。它们允许多个开发者从各自的工作站为同一个项目做出贡献,彼此独立地修改项目的本地副本。当他们需要分享更改时,可以发出Git命令从远程仓库拉取代码,或将代码推送到远程仓库。
上一节我们介绍了本地仓库的基本操作,本节中我们来看看如何通过远程仓库进行团队协作。
远程仓库的托管方式

有多种方式可以托管远程仓库。正如我们所提到的,有许多基于互联网的Git托管服务提供商,例如 GitHub、Bitbucket 或 GitLab,它们提供类似的服务。我们也可以在自有网络上搭建Git服务器来托管私有仓库。
一个本地托管的Git服务器几乎可以在任何平台上运行,包括Linux、Mac OS或Windows。这样做的好处包括更高的隐私性、控制权和可定制性。


理解分布式协作:一个比喻


为了更好地理解远程仓库和分布式特性,想象一下你正和朋友们一起设计一款电脑游戏。
你们每个人负责游戏的不同部分:一个人设计关卡,另一个人设计角色,其他人则编写图形、物理和游戏玩法的代码。


所有这些部分最终都需要整合到一个地方,形成最终产品。虽然你的朋友们可能会独立完成各自的部分,但大家时不时需要发送进度更新,让彼此知道各自的工作内容。
然后,你需要将他们的工作合并到你负责的项目部分中,以确保所有内容兼容。使用Git来管理项目能帮助我们成功协作。

以下是这个协作流程的步骤:
- 每个人在自己的本地仓库中独立开发项目的一部分,甚至可能使用独立的分支。
- 偶尔,他们会将完成的代码推送到一个中央远程仓库。
- 其他人可以从这个远程仓库拉取代码,并将其整合到自己的新开发中。
远程仓库如何工作?
除了像 master 这样的本地开发分支,Git还会保存已提交到远程仓库的提交副本以及远程分支的副本。


如果自你上次同步本地副本以来,有人更新了远程仓库,Git会告诉你该进行更新了。

如果你有自己的本地更改,当你从远程仓库拉取代码时,在推送自己的更改之前,可能需要解决合并冲突。

通过这种方式,Git允许多个人同时处理同一个项目。拉取新代码时,如果可能,它会自动合并更改;如果存在冲突,则会告诉我们手动执行整合。


使用远程仓库的工作流程
因此,当使用远程仓库时,进行更改的工作流程会包含一些额外的步骤。

我们仍然会修改、暂存和提交本地更改。提交之后,我们需要从远程仓库获取任何新的更改,必要时进行手动合并,只有在这之后,才能将我们的更改推送到远程仓库。
以下是标准的工作流程:
git add和git commit:在本地进行修改和提交。git fetch:从远程仓库获取最新更改。git merge(或git rebase):必要时手动合并更改。git push:将本地提交推送到远程仓库。
连接到远程仓库
Git支持多种方式连接到远程仓库。最常见的一些方式是使用HTTP、HTTPS和SSH协议及其对应的URL。
- HTTP:通常用于允许对仓库进行只读访问。换句话说,它允许人们克隆你仓库的内容,但不允许他们推送新内容。
- HTTPS 和 SSH:两者都提供了用户身份验证方法,因此你可以控制谁有权推送代码。
如果你对这些协议的讨论感到困惑,建议你回顾一下我的同事Gion关于这些主题的视频。你可以在接下来的阅读材料中找到链接。
工作的分布式特性意味着可以向仓库推送代码的人数没有限制。控制谁可以向你的仓库推送代码,并确保只授予你信任的人访问权限,这是一个好主意。像GitHub这样的网络服务提供了许多不同的机制来控制对仓库的访问,其中一些对公众开放,而另一些仅对企业用户可用。
本节课中我们一起学习了远程仓库的概念。我们了解了远程仓库是团队协作的中心枢纽,它如何与本地仓库交互,以及使用远程仓库时的基本工作流程和连接方式。掌握远程仓库是进行高效Git协作的关键一步。
接下来,我们将深入探讨一些让我们与远程仓库交互的具体命令。
035:使用远程仓库 🖥️

在本节课中,我们将学习如何与远程仓库进行交互,包括查看远程仓库信息、理解远程分支的概念,以及如何检查本地与远程仓库的同步状态。
概述
当我们使用 git clone 命令获取远程仓库的本地副本时,Git 会默认将该远程仓库命名为 origin。
查看远程仓库配置
我们可以通过运行 git remote -v 命令来查看该远程仓库的配置信息。
以下是远程仓库 origin 的详细信息:
- 获取URL:用于从远程仓库拉取数据。
- 推送URL:用于向远程仓库推送数据。
这两个 URL 通常指向同一个位置。但在某些情况下,获取 URL 可能使用 HTTP 协议(只读访问),而推送 URL 使用 HTTPS 或 SSH 协议(用于访问控制)。只要拉取时读取的仓库内容与推送时写入的内容一致,这种配置就是可行的。
远程仓库与分支
远程仓库会被分配一个名称,默认名称是 origin。这允许我们在同一个 Git 目录中跟踪多个远程仓库。虽然这不是典型用法,但在与不同团队协作处理相关项目时可能很有用。
如果我们想获取关于远程仓库的更多信息,可以运行 git remote show origin 命令。这里会显示大量信息,包括之前看到的获取和推送 URL,以及本地和远程分支的信息。
目前,我们只有一个同时存在于本地和远程的 master 分支,因此信息看起来有些重复。一旦开始拥有更多分支,尤其是在本地和远程仓库中存在不同分支时,这些信息就会变得更加复杂。
理解远程分支
那么,我们所说的远程分支到底是什么呢?在与远程仓库交互时,Git 会使用远程分支来保存远程仓库中数据的副本。
我们可以通过运行 git branch -r 命令来查看当前 Git 仓库正在跟踪的远程分支。这些分支是只读的。我们可以像查看本地分支一样查看它们的提交历史,但不能直接修改其内容。
要修改远程分支的内容,我们必须遵循之前提到的工作流程:首先,将任何新的更改拉取到我们的本地分支;然后,将这些更改与我们本地的修改合并;最后,将我们的更改推送到远程仓库。
检查同步状态
还记得我们如何使用 git status 来检查本地更改的状态吗?我们也可以使用 git status 来检查远程分支的更改状态。
现在,由于我们正在与远程仓库协作,git status 会提供额外的信息。它会告诉我们,我们的分支是否与 origin/master 分支保持同步。这意味着名为 origin 的远程仓库中的 master 分支与我们本地的 master 分支拥有相同的提交记录。
总结

本节课中,我们一起学习了如何配置和查看远程仓库信息,理解了远程分支的只读特性及其作用,并掌握了使用 git status 检查本地与远程仓库同步状态的方法。如果本地分支与远程分支不同步,我们该如何处理呢?这将是下一节视频要讨论的内容。
036:Git 远程协作与同步更新 🚀
概述

在本节课中,我们将学习如何从远程仓库获取最新的更改,并将其同步到本地仓库。我们将重点介绍 git fetch 和 git merge 命令的使用,以及如何查看远程分支的提交历史。
远程仓库状态检查
上一节我们介绍了远程仓库的基本概念,本节中我们来看看如何检查远程仓库的状态。
当我们学习远程仓库时,同事 Blue Kaale 向我们的仓库添加了一些文件。
我们可以使用 GitHub 网站浏览提交的更改。
但我们希望学习如何通过命令行交互来完成此操作。
因为您在工作中可能需要以这种方式操作。
无论使用哪种平台与 Git 交互,操作方式都相同。
首先,让我们查看 git remote show origin 命令的输出。
请注意,它显示本地分支已过时。
当仓库中有尚未在本地反映的提交时,会发生这种情况。
Git 不会自动保持远程和本地分支同步。
它等待我们准备好后执行命令来移动数据。
获取远程更改
为了同步数据,我们使用 git fetch 命令。
该命令将远程仓库中完成的提交复制到远程分支,以便我们可以看到其他人提交的内容。
现在让我们调用它,看看会发生什么。
获取的内容将下载到我们仓库的远程分支。
因此它不会自动镜像到我们的本地分支。
我们可以在这些分支上运行 git checkout 来查看工作树。
我们可以运行 git log 来查看提交历史。
通过运行 git log origin/master,让我们查看远程仓库中的当前提交。
查看此输出,我们可以看到远程 origin 的分支指向最新的提交。
而本地 master 分支指向我们之前所做的上一个提交。
检查本地状态
现在让我们看看如果运行 git status 会发生什么。
git status 会告诉我们,我们的分支中有一个尚未拥有的提交。
它通过告知我们的分支落后于远程 origin/master 分支来实现这一点。
如果我们想将分支集成到我们的 master 分支中,可以执行合并操作。
该操作将 origin/master 分支合并到我们的本地 master 分支中。
为此,我们将调用 git merge origin/master。
很好,我们已经将远程仓库的 master 分支的更改合并到我们的本地分支中。
请注意 Git 如何告诉我们代码是使用快进方式集成的。
它还显示添加了两个文件:all_checks.py 和 disk_usage.py。
如果我们现在查看分支上的日志输出,应该会看到新的提交。

确认同步结果
我们看到现在我们的 master 分支与远程 origin/master 分支保持同步。
通过此操作,我们已将分支更新到最新更改。
我们可以像这样使用 git fetch 来查看远程仓库中发生的更改。
如果我们对它们满意,可以使用 git merge 将它们集成到本地分支中。
从远程仓库获取提交并将其合并到本地仓库是一个非常常见的 Git 操作。
有一个方便的命令可以让我们在一个操作中完成所有这些步骤。
我们将在下一个视频中查看该命令。
总结
本节课中我们一起学习了如何使用 git fetch 获取远程仓库的最新提交,以及如何使用 git merge 将这些更改合并到本地分支。我们还了解了如何检查远程分支的状态和提交历史,确保本地仓库与远程仓库保持同步。
037:Git协作与远程仓库管理 🚀

在本节课中,我们将学习如何与远程Git仓库进行协作,重点掌握如何获取远程更新、合并更改以及处理远程分支。我们将通过具体的命令和示例,帮助你理解如何高效地与团队成员同步代码。
概述
上一节我们介绍了远程仓库的基本概念和git fetch、git merge的基本工作流程。本节中,我们将学习一个更高效的命令git pull,它结合了获取和合并操作。同时,我们还将探讨如何查看和管理远程分支,例如创建一个与远程分支对应的本地分支。这些技能对于团队协作开发至关重要。
使用 git pull 自动获取与合并
由于git fetch(获取)和git merge(合并)这两个操作非常频繁,Git提供了一个便捷的命令git pull来一次性完成这两项工作。
运行git pull命令会获取当前分支的远程副本,并自动尝试将其合并到当前的本地分支中。
以下是检查同事是否对仓库进行了新更改的示例:
git pull
仔细观察命令输出,你会发现它包含了我们之前见过的fetch和merge命令的输出信息。首先,它从远程仓库获取了更新内容(包括一个新分支),然后对本地master分支执行了一次快进合并。
我们可以看到all_checks.py文件也被更新了。我们可以使用git log -p -1来查看具体的更改内容,会发现同事添加了一个check_disk_full函数,其中包含了我们之前看到的disk_usage.py中的代码。查看完毕后,按q键退出日志查看器。
处理远程分支
当我们调用git pull时,注意到还有一个名为experimental的新远程分支。我们的同事Blue告知我们,他正在这个分支上开发一个新功能。
我们可以运行git remote show origin来查看关于这个新分支的信息。输出显示有一个名为experimental的远程分支,而我们本地还没有对应的分支。
为了创建它的本地分支,我们可以运行:
git checkout experimental
当我们检出experimental分支时,Git自动将远程分支的内容复制到了新建的本地分支中,工作目录也随之更新为该分支的内容。现在,我们就可以和同事一起在这个实验性功能上协作开发了。
选择性获取远程更新
在上一个例子中,我们调用git pull时同时获取了master分支和experimental分支的内容,并且如果master有更新,它也会自动合并到本地。
如果我们只想获取所有远程分支的内容,而不希望自动合并任何内容到本地分支,可以使用git remote update命令。

git remote update
这个命令会获取所有远程分支的更新内容,之后我们可以根据需要,再单独执行git checkout或git merge。
命令总结
我们已经学习了多种与远程仓库交互的方式。以下是涉及的核心命令列表:
git pull:获取远程更改并自动合并到当前分支。git remote show origin:查看远程仓库(如origin)的详细信息,包括分支状态。git checkout <branch_name>:切换到指定分支。如果分支不存在但远程存在,则会创建跟踪远程分支的本地分支。git remote update:获取所有远程分支的最新内容,但不进行合并。
总结
本节课中我们一起学习了如何高效地更新本地仓库以同步团队工作。我们掌握了git pull命令来简化获取与合并流程,学会了如何查看远程分支信息并创建对应的本地分支进行协作,也了解了git remote update用于非自动合并的场景。你现在已经具备了基本的远程协作能力。
在接下来的课程中,我们将探讨当更改无法通过快进合并解决时会发生什么,特别是当我们尝试推送更改并产生冲突时的处理方法。但在深入学习之前,请务必通过测验来巩固本节课的实践知识。
038:拉取-合并-推送工作流 🛠️

在本节课中,我们将要学习当远程仓库有新的提交,而你的本地也有未推送的更改时,如何通过“拉取-合并-推送”的工作流来同步代码并解决可能出现的冲突。
概述
上一节我们介绍了如何从远程仓库获取和拉取数据。本节中我们来看看,当我们准备推送本地更改到远程仓库时,却发现远程仓库已经有了新的更改,该如何处理。我们将学习完整的“拉取-合并-推送”流程,并解决合并过程中出现的冲突。
准备推送本地更改
我们首先对 all_checks.py 脚本进行一项改进。回想课程初期修复磁盘空间检查函数中的错误时,我们曾两次进行千兆字节转换。当时代码容易出错的部分原因是我们传递数字时没有明确说明这些数字的用途。
我们可以通过将 min_absolute 参数重命名为 min_gb 来使代码更清晰,这样就能明确函数期望接收的是千兆字节。
# 改进前
def check_disk_usage(disk, min_absolute, min_percent):
...
# 改进后:重命名参数
def check_disk_usage(disk, min_gb, min_percent):
...
另一种让代码更清晰的方法是,在调用函数时使用参数名称。
# 改进前调用
check_disk_usage(“/”, 2*2**30, 10)
# 改进后调用:使用参数名
check_disk_usage(disk=“/”, min_gb=2*2**30, min_percent=10)
通过使用参数名,我们的调用变得清晰,甚至可以改变值的顺序,代码仍然能正常工作。
我们完成了更改,现在像往常一样暂存并提交它。
首先使用 git add -p 查看我们做的更改并接受它们。
然后创建一个提交信息,说明我们将 min_absolute 重命名为 min_gb,并且在调用时使用了参数名。
git add -p
git commit -m “Rename min_absolute to min_gb and use parameter names in invocation”
我们已经完成了更改、暂存和提交。现在应该可以推送到远程仓库了。
遇到推送冲突
但此时,假设有一位协作者也进行了更改。当我们尝试运行 git push 时,操作失败了。
git push
你能分析出这里出了什么问题吗?有几个提示:当我们尝试推送时,Git 拒绝了我们的更改。这是因为远程仓库包含了一些我们本地分支没有的更改,Git 无法进行快进合并。

你可能还记得我们讨论 Git 合并算法时提到过,当分支历史出现分叉时,需要进行三方合并。和往常一样,Git 在错误信息中提供了一些有用的信息,特别是关于使用 git pull 来集成远程更改的部分。
这意味着我们需要在推送之前,先将本地的远程跟踪分支与远程仓库同步。我们之前学过,可以使用 git pull 来完成这个操作。
git pull
Git 尝试自动合并本地和远程对 all_checks.py 的更改,但发现了一个冲突。
分析冲突原因
首先,我们通过 git log --graph --oneline --all 查看所有分支的提交图。

这个图向我们展示了树中不同的提交和位置。我们可以看到 master 分支、origin/master 分支和 experimental 分支。图表表明,我们当前的提交和 origin/master 分支中的提交有一个共同的祖先,但它们并不直接前后相连。这意味着我们需要进行三方合并。
为了解详情,我们运行 git log -p origin/master 来查看该提交中的实际更改。
我们发现,同事决定重新排列函数中的条件子句顺序,以匹配参数传递给函数的顺序。他们恰好更改了我们重命名 min_gb 变量时更改的同一行代码,这导致了 Git 无法自动解决的冲突。
手动解决冲突
我们需要通过编辑文件来移除冲突标记以解决冲突。首先退出日志视图。
我们看到问题发生在条件语句中。第一行显示我们的更改,min_absolute 被重命名为 min_gb。第二行显示旧的变量名,但检查的顺序不同。
我们需要决定如何处理。例如,我们可以保留新的顺序,但使用 min_gb 这个新变量名。
有一点需要注意:Git 会尝试所有可能的自动合并,只有当自动合并失败时,才会留下需要手动解决的冲突。在本例中,我们可以看到我们做的其他更改都成功合并了,无需干预。只有发生在文件同一行的更改需要我们的输入。
我们在这里修复了冲突。由于文件很短,我们可以快速检查是否还有其他冲突。对于更大的文件,在整份文件中搜索冲突标记(>>>>>>)可能更有意义,这可以让我们检查是否还有未解决的冲突。
完成合并与推送
很好,现在我们已经解决了冲突,可以完成合并了。你还记得怎么做吗?

我们需要添加 all_checks.py 文件,然后调用 git commit 来完成合并。
git add all_checks.py
git commit
编辑器显示的信息表明,它正在执行远程分支与本地分支的合并。我们可以为此消息添加额外信息,例如说明我们修复了 check_disk_usage 函数中的条件语句,以使用新的变量名和新的顺序。
我们的合并终于准备好了。可以再次尝试推送到远程仓库。
git push
是的,在解决冲突之后,我们成功地将工作推送到了远程仓库。
现在,通过调用 git log --graph --oneline 查看 master 分支的提交历史。

😊,我们看到最新的提交是合并提交,后面跟着导致合并冲突的两个提交,它们在图中处于分叉的路径上。正如我们之前指出的,当 Git 需要进行三方合并时,我们最终会得到一个单独的提交,用于将分支合并回主树。

总结
本节课中我们一起学习了如何成功完成“拉取-合并-推送”的周期,即使这意味着需要进行一些手动合并。这是一个复杂的练习,如果某些部分看起来仍然有点令人畏惧,这是正常的。我们第一次遇到合并冲突时都会感到恐慌。但别担心,通过练习会变得更容易。
为了练习处理合并冲突,你可以在不同的目录中拥有两个仓库副本,然后尝试编辑相同文件的相同行。你可以按照这里展示的例子操作,或者自己想一些练习。
接下来,我们将讨论在远程仓库中使用分支。
039:Git分支推送与代码重构实践 🚀

在本节课中,我们将学习如何在Git中创建并推送远程分支,同时通过一个实际的Python脚本重构案例,演示如何优化代码结构、减少重复,并确保代码的可维护性。我们将涵盖分支的优势、代码重构的步骤,以及如何将本地分支推送到远程仓库供团队协作。
分支的优势 🌳
上一节我们介绍了Git的基本操作,本节中我们来看看为什么在开发中使用分支是推荐的最佳实践。
使用Git进行新功能开发或大型重构时,建议创建独立的分支。这样做有多重优势。
例如,完成新功能可能需要较长时间。在此期间,主分支中可能出现需要紧急修复的关键错误。
通过使用独立分支,你可以在主分支中修复错误、发布新版本,然后返回继续开发功能,而无需在代码准备就绪前进行集成。
使用独立分支的另一个优势是,你甚至可以从同一代码树中发布两个或更多版本,一个稳定版,一个测试版。这样,任何破坏性更改都可以在全面发布前在少量用户或计算机上进行测试。
创建并切换至新分支 🔄
以下是创建并切换至新分支的步骤:
- 首先,我们可以先创建分支再切换,或者使用一条命令同时完成创建与切换。
- 使用命令
git checkout -b <新分支名>来创建并切换到新分支。
我们已准备好开始重构工作。让我们打开文件查看一下。
识别并开始重构代码 🛠️
我们注意到 all_checks.py 脚本中存在重复的代码模式。
对于每个调用的检查函数,我们都判断其返回 True 或 False。当返回 True 时,我们打印错误并退出。如果添加新的检查,我们将不得不再次重复此模式。此外,如果计算机存在多个问题,只会打印第一个问题的错误信息。
因此,让我们重构代码以避免重复,并打印所有相关错误。我们将逐步进行,使每次提交自成一体。
第一步:创建包装函数
我们要做的第一件事是创建一个无参数的函数来检查磁盘是否已满,以匹配现有模式。这个新的包装函数将为我们传递正确的参数。
def check_disk_full():
"""检查磁盘使用率是否超过80%"""
return check_disk_usage(disk="/", min_gb=2, min_percent=10) # 示例参数
然后,我们将更改代码以调用此函数,并更新错误信息使其更准确。更改函数后,保存并测试代码。


很好,运行正常。让我们提交这个更改。


第二步:使用循环消除重复
我们已准备好重构的下一步以避免代码重复。
我们将创建一个列表,包含要调用的函数名称和检查成功时要打印的消息。
checks_to_perform = [
(check_cpu_usage, "错误:CPU使用率超过80%"),
(check_disk_full, "错误:可用磁盘空间不足20%"),
# 可以在此添加更多检查项
]
之后,添加一个 for 循环来遍历检查列表和消息列表。
for check_func, error_msg in checks_to_perform:
if check_func():
print(error_msg)
sys.exit(1)
然后,调用检查函数。如果返回值为 True,则打印消息并以错误代码1退出。完成此操作后,我们可以删除已替换的旧代码。
完成此更改后,再次保存并测试脚本是否仍能运行。是的,它仍然有效。让我们提交这个新更改。
至此,我们已经重构了代码以避免重复。当前代码的功能与旧代码相同。当我们准备添加新检查时,只需将函数名和错误消息添加到检查列表即可。
第三步:显示多个错误信息
我们想要做的最后一个更改是让脚本在多个检查失败时显示多条消息。
为此,我们在迭代前添加一个名为 everything_ok 的布尔变量。
everything_ok = True
error_messages = []
for check_func, error_msg in checks_to_perform:
if check_func():
error_messages.append(error_msg)
everything_ok = False
if not everything_ok:
for msg in error_messages:
print(msg)
sys.exit(1)
如果某个检查发现问题,将此变量更改为 False,然后在完成所有检查后才以错误代码退出。
好的,最后一次保存并测试,看看是否有效。
现在,让我们为此更改进行最终提交。
推送分支到远程仓库 📤
这样,我们的重构分支中就有了三次提交。在将任何内容合并到主分支之前,我们希望将其推送到远程仓库,以便协作者可以查看代码、测试它,并告知我们是否已准备好合并。
首次将分支推送到远程仓库时,需要在 git push 命令中添加更多参数。

我们需要添加 -u 标志来创建上游分支(这是指远程仓库的另一种说法)。
我们还必须指定要推送到 origin 仓库,并且我们正在推送 refactor 分支。
命令如下:
git push -u origin refactor

Git给出了大量信息。它告诉我们,如果需要,可以创建一个拉取请求。我们将在后面详细讨论拉取请求。目前,我们很高兴看到新的 refactor 分支已在远程仓库中创建,这正是我们想要的。
总结 📝
本节课中我们一起学习了Git分支管理的重要实践。我们通过一个复杂的例子,整合了本课程中学到的许多概念,并实践了一些有趣的Python编程技巧,包括:
- 创建与使用分支:理解了使用独立分支进行开发的优势。
- 代码重构:通过创建函数包装器、使用列表和循环来消除代码重复,并改进错误报告机制。
- 推送远程分支:使用
git push -u origin <分支名>命令首次将本地分支推送到远程仓库,以便团队协作。
如果仍有不清楚的地方,可以随时重看本视频并在计算机上跟随操作,直到熟悉这些步骤。
现在我们的分支已推送到远程仓库,可以由协作者进行审查。假设他们同意,接下来该如何将此分支合并回主分支呢?我们将在下一个视频中讨论。
040:Git变基操作详解 🔄

在本节课中,我们将学习Git中的变基操作。我们将了解变基的含义、它与合并的区别,以及如何通过变基来保持项目历史的线性结构,从而便于代码调试和问题追踪。
概述
上一节我们介绍了分支合并的两种方式。本节中,我们来看看另一种整合分支更改的方法:变基。
在之前的视频中我们提到,当分支经过充分审查和测试后,可以将其合并回主分支。这可以由我们或他人完成。一种选择是使用之前讨论过的git merge命令。另一种选择是使用git rebase命令。变基意味着更改用于我们分支的基准提交。
理解变基的含义
为了理解这意味着什么,让我们快速回顾一下到目前为止学到的关于合并的知识。
正如我们在之前的许多示例中所见,当我们在仓库历史的某个时间点创建分支时,Git知道两个分支上提交的最新提交。如果在我们尝试合并时只有一个分支有新的更改,Git将能够快速前进并应用更改。但是,如果在我们尝试合并时两个分支都有新的更改,Git将创建一个新的合并提交以进行三方合并。
三方合并的问题在于,由于历史记录的分叉,当在我们的代码中发现问题时,我们很难调试,并且需要理解问题是在哪里引入的。

通过更改我们的提交从分支历史中分离出来的基准,我们可以在新的基准之上重新应用新的提交。这允许Git执行快速前进合并,并保持历史记录的线性。
如何进行变基操作
以下是执行变基的步骤:
我们运行命令git rebase,后跟我们想要设置为新基准的分支。当我们这样做时,Git将尝试在该分支的最新提交之后重新应用我们的提交。如果更改是在文件的不同部分进行的,这将自动工作,但如果更改是在其他文件中进行的,则需要手动干预。
让我们通过将我们的重构分支变基到主分支上来检查这个过程。
首先,我们将检出主分支并拉取远程仓库中的最新更改。
Git告诉我们,它已经用我们同事所做的一些更改更新了主分支。

此时,我们在重构分支中的更改不能再通过快速前进合并到主分支中。这是因为现在主分支中有一个额外的提交,而重构分支中没有。
让我们通过要求日志命令显示所有分支的当前图表来查看这种情况。
分析分支历史图表
理解这个图表中发生的一切可能需要一点时间。但对于理解复杂的历史树来说,它可能非常有用。正如你所看到的,重构分支在与主分支头部当前提交的共同祖先之前有三个提交。如果我们现在合并我们的分支,将导致三方合并,但我们希望保持历史记录的线性。我们将通过对主分支进行重构分支的变基来实现这一点。
像往常一样,Git为我们提供了一堆有用的信息。它说它回退了HEAD并在其之上重新应用了我们的工作,幸运的是,一切都成功了。
让我们现在查看我们分支的git log --graph --oneline输出。
现在我们可以看到主分支与我们的提交列表呈线性历史。我们准备将我们的提交合并回仓库的主干,并进行快速前进。
完成合并与清理
为此,我们将检出主分支并合并重构分支。太棒了,我们能够通过快速前进合并我们的分支,并保持历史记录的线性。
我们现在已经完成了重构,可以删除该分支,包括远程和本地分支。
要删除远程分支,我们将调用git push --delete origin refactor。要删除本地分支,我们将调用git branch -D refactor。是的,我们的重构完成了,我们现在可以将更改推回远程仓库。
总结
在本节课中,我们一起学习了Git的变基操作。我们有一个针对主分支旧提交创建的功能分支,因此我们将功能分支变基到主分支的最新提交,然后将功能分支合并回主分支。这是一个复杂的练习。所以如果你仍然对发生了什么感到困惑,请花时间复习,也许可以想出你自己使用变基的例子。接下来,我们将看另一个如何使用变基的例子。
041:另一个变基示例 🔄

概述
在本节课中,我们将学习 Git 变基操作的另一个常见应用场景:当你在主分支上直接进行修改,而同时你的协作者也提交了更改时,如何使用变基来保持项目历史的线性,避免不必要的合并提交。
上一节我们介绍了如何使用变基来整合功能分支。本节中,我们来看看如何在主分支上直接工作时,使用变基来同步他人的更改。
保持历史线性的变基操作
当你在主分支上进行一个足够小的修改,而不需要创建单独的功能分支时,你的协作者可能恰好同时提交了更改。为了保持项目历史的线性,避免出现分叉的合并提交,我们可以使用 git rebase 命令。
以下是操作步骤:
-
首先,进行你的本地修改并提交。
例如,我们为脚本添加一个检查网络连通性的新函数。import socket def check_no_network(): """如果无法解析 Google.com 的 URL,则返回 True""" try: socket.gethostbyname("www.google.com") return False except: return True然后,将这个新检查添加到检查列表中,并提交更改。
git commit -a -m "添加简单的网络连通性检查" -
接着,获取远程仓库的最新更改。
在推送你的提交之前,先使用git fetch命令获取协作者可能已推送的更改。这个命令会将远程分支(如origin/master)的更新下载到本地,但不会自动合并到你的当前分支。git fetch -
然后,执行变基操作。
现在,对你的本地master分支执行变基,将其基于更新后的origin/master。这相当于将你的新提交“重新播放”在协作者的最新提交之上。git rebase origin/master
处理变基冲突
如果协作者的更改与你的修改冲突了,Git 会暂停变基过程并提示你解决冲突。这与处理合并冲突类似。
以下是解决冲突的步骤:

-
Git 会输出冲突信息,并告诉你哪个文件存在冲突。
-
打开冲突文件,你会看到类似下面的冲突标记:
<<<<<<< HEAD # 这是协作者添加的代码(当前`origin/master`的代码) ======= # 这是你添加的代码(你试图应用的提交中的代码) >>>>>>> your-commit-hash -
手动编辑文件,保留你想要的最终代码,并删除所有
<<<<<<<、=======和>>>>>>>冲突标记。 -
保存文件后,使用
git add命令将解决后的文件标记为已解决。 -
最后,使用
git rebase --continue命令继续完成变基过程。git add health_checks.py git rebase --continue如果中途想放弃整个变基操作,可以使用
git rebase --abort。

变基完成与验证


变基成功完成后,你的本地提交历史将变成线性。你可以使用以下命令查看简洁的图形化历史:

git log --graph --oneline
此时,你的更改已经整洁地应用在了项目的最新状态之上,没有产生额外的合并提交。现在,你可以安全地将更改推送到远程仓库:
git push origin master
总结
本节课中我们一起学习了 git rebase 的另一个重要用途:在协作开发中,当你在主分支上直接提交后,通过 获取(fetch)-> 变基(rebase)-> 推送(push) 的工作流,将你的更改基于远程最新代码重新应用,从而保持项目提交历史的线性。
与上一课的变基应用相比,两者的核心操作相同,但目的略有差异:一个用于整合功能分支,另一个用于同步主分支上的并行修改。保持线性历史有助于调试,特别是在需要定位首次引入问题的提交时。
git rebase 是一个非常强大的工具,它还可以用于重排提交顺序或压缩多个提交。你无需记忆所有用法,在实践中遇到相应需求时学习即可。接下来,我们将总结在团队协作中使用 Git 的一些最佳实践。
042:Git协作最佳实践指南 🚀

在本节课中,我们将学习使用Git进行团队协作时的最佳实践。我们将探讨如何高效同步分支、管理代码变更、处理多版本项目,以及安全使用Rebase等高级功能。掌握这些实践能帮助团队减少冲突、提升协作效率。
同步分支的重要性
上一节我们介绍了远程仓库的基本操作,本节中我们来看看协作时的首要原则:保持分支同步。
开始任何工作前,务必先同步你的分支。这样能确保你基于最新版本进行修改,从而最小化冲突风险或减少变基需求。
具体操作如下:
git pull origin <branch_name>
管理代码变更
在同步分支后,我们需要关注如何有效地管理代码变更。
最佳实践是避免单次提交包含大量且互不相关的修改。应尽量保持每次变更小而独立。
以下是具体建议:
- 单一职责提交:例如,若为清晰性重命名变量,不应在同一提交中添加新功能代码。
- 分拆提交:将不同修改拆分为多个提交,便于理解每个提交的意图。
- 频繁推送与拉取:经常推送本地更改,并在开始工作前拉取远程更新,可降低冲突概率。
使用功能分支
当进行大型功能开发时,合理的分支策略至关重要。
在大型变更中,应使用独立的功能分支。这允许你在新分支上开发功能,同时仍能在其他分支(如主分支)上修复错误。
为了使功能分支的最终合并更顺畅,应定期将主分支的更改合并回功能分支。这样在最终合并时,就不会积累大量冲突。
维护多版本项目
在软件开发中,经常需要同时维护项目的多个版本。
常见做法是将项目的最新版本放在主分支(master),而将稳定版本放在单独的分支(例如stable)。每当宣布稳定版本发布时,再将更改合并到这个独立分支。
使用这种双分支策略时,某些针对稳定版本的错误修复,如果与最新版本无关,可以直接在稳定分支上进行。
谨慎使用Rebase
在之前的课程中,我们学习了如何使用Rebase来保持历史线性的方法。Rebase有助于定位错误,但需谨慎使用。
执行Rebase时,我们重写了分支的历史——旧提交被新提交替换。这些新提交基于与之前不同的代码快照,并拥有完全不同的哈希值。这对本地变更可行,但对于已发布并被其他协作者下载的变更,则可能引发严重问题。
因此,通用规则是:不应对已推送到远程仓库的变更执行Rebase。Git服务器通常会拒绝试图重写分支历史的推送。虽然可以强制Git接受更改,但除非你完全清楚后果,否则这不是一个好主意。
在我们之前的功能分支示例中,我们先在本地对分支执行Rebase,然后将其合并到主分支,最后删除旧分支。这样,我们没有将Rebase后的变更推送到功能分支本身,只推送到尚未见过这些变更的主分支。
编写有效的提交信息

在Git学习之旅初期,我们曾提到编写良好的提交信息非常重要。
当你独自工作时这已很重要,因为好的提交信息能帮助未来的你理解当时的情况。而在团队协作中,它更为关键,因为它为你的协作者提供了更多关于你为何进行此更改的上下文,并能在必要时帮助他们解决冲突。
所以,请致力于成为一名优秀的协作者,并牢记添加那些清晰的提交信息。
处理合并冲突
只要我们与他人协作,就难免会遇到合并冲突,这确实令人头疼。在遇到复杂的合并冲突并试图调试结果时,挫折感是难免的。
如果遇到这类合并冲突,我的第一步是向后回溯:撤销我所做的一切,然后检查源代码是否仍能正常工作。接着,再缓慢地逐段添加代码,直到发现问题所在。这个方法通常能帮我度过难关,也确实揭示过一些奇怪的情况。
接下来,我们将有一份阅读材料,汇总所有与解决冲突相关的命令,之后还有一个快速练习测验。
本节课中我们一起学习了Git协作的核心最佳实践,包括同步分支、管理小型提交、使用功能分支、维护多版本项目、安全使用Rebase、编写清晰提交信息以及处理合并冲突的基本思路。遵循这些实践能显著提升团队协作的顺畅度和代码库的可维护性。
043:使用远程仓库 🗂️

在本节课中,我们将总结模块3的核心内容,回顾如何使用远程仓库进行版本控制与协作。
概述
在之前的课程中,我们学习了Git版本控制系统的基础操作。本节将总结远程仓库的使用方法,包括与GitHub的交互、分支管理以及冲突解决。
远程仓库基础
上一节我们介绍了Git的本地操作,本节中我们来看看如何与远程仓库协作。
首先,我们讨论了GitHub是什么,以及与该服务的基本交互方式。
分布式协作
Git的分布式特性允许多个贡献者独立且同时开发项目。
以下是远程协作的核心操作:
- 拉取数据:从远程仓库获取最新更改。
- 推送更改:将本地提交上传到远程仓库。
- 解决冲突:当本地与远程分支不同步时,处理合并冲突。
高级工作流示例
我们通过一个复杂示例收尾,该示例展示了如何使用功能分支进行代码重构,并利用rebase命令保持历史记录的线性。
# 创建并切换到功能分支
git checkout -b feature-refactor
# 在功能分支上完成重构并提交
git add .
git commit -m "完成代码重构"
# 切换回主分支并变基
git checkout main
git pull origin main
git checkout feature-refactor
git rebase main
总结
本节课中我们一起学习了远程仓库的核心概念与操作。虽然掌握Git这样的版本控制系统可能有难度,但其带来的益处值得付出努力。
请记住,版本控制系统让你能够访问任何脚本项目的历史,促进协作与责任追溯,并实现快速回滚。理解版本控制是每位IT专家都应具备的宝贵且多功能的技能,这将使你在人群中脱颖而出。
接下来,分级评估将让你把所有新知识付诸实践。如之前所说,请根据需要随时复习,当你准备好时,前往分级评估部分。你能做到。
044:协作工具介绍 🛠️


在本节课中,我们将学习如何使用Git和GitHub等平台上的协作工具。这些工具能帮助我们向非成员项目提交更改、提升代码质量,并更好地跟踪工作进展。
在之前的模块中,我们已经学习了如何与Git进行交互,包括本地和远程使用、回滚错误更改、解决协作冲突等实用技巧。
本节中,我们将继续探索Git中可用的协作工具,学习如何向非成员项目提交更改、提升代码质量,以及更有效地跟踪工作进展。
其中一些工具是GitHub特有的,但大多数Git仓库托管服务都有类似功能。因此,即使你使用其他平台,这些概念仍然适用。
近年来,GitHub已成为许多项目协作的超级流行平台,尤其在开源项目中广泛使用。开源项目允许任何人使用、复制和修改其代码。
将代码在线发布意味着世界上任何人都可以学习项目的实现方式,甚至协作修复问题和添加新功能。
这也有助于我们相互学习,因为我们可以查看他人如何解决我们正在处理的问题。
我经常使用GitHub查找我感兴趣功能的代码片段。最近,我查找了电影院座位查找器的示例,因为我正在为个人项目开发类似功能。
如果你想学习新技术,通过为使用该技术的项目做贡献来练习技能是一个好方法。
为此,你需要知道如何与项目交互,包括如何提交错误修复、确保修复被应用,甚至确定需要哪些修复。
在本模块中,我们将更详细地介绍这些内容。让我们开始吧。
本节课中,我们一起学习了Git和GitHub协作工具的基本概念,包括如何参与开源项目、提交更改以及通过查看他人代码提升技能。这些工具将帮助你在团队协作和开源贡献中更加高效。
045:在GitHub上进行简单的拉取请求 🛠️


在本节课中,我们将学习如何使用GitHub查看他人的代码并与他们协作。我们将通过一个具体的例子,了解如何通过GitHub的Web界面直接提交一个简单的拉取请求(Pull Request),来为同事的项目修复一个拼写错误。


概述:协作与代码审查
正如我们之前提到的,GitHub是一个用于查看他人代码并与之协作的平台。让我们通过查看同事Blue Kaale的一个项目来了解这一过程。


发现并修复问题


我们的同事创建了一个包含所有验证函数的项目。让我们查看一下 validations.py 文件。
我们看到了一个验证用户名的函数代码。但是,如果你仔细查看函数的文档字符串,可能会注意到一个拼写错误。

我们可以通过修复这个拼写错误来改进同事的代码。让我们开始操作。
创建分支与Fork

我们将点击小铅笔图标,这允许我们直接从Web界面编辑文件。

当我们尝试编辑一个我们没有直接写入权限的项目中的文件时,GitHub会提示我们。它为我们创建了这个项目的一个Fork,我们可以将更改提交到这个Fork。如果我们提交对此文件的更改,它将创建一个新的分支,以便我们可以发送一个拉取请求。
那么,什么是Fork呢?Fork 是创建给定代码库副本的一种方式,使其归属于我们的用户账户。
换句话说,即使我们无法向原始代码库推送更改,我们的用户也能向Fork副本推送更改。在GitHub上协作时,典型的工作流程是首先创建代码库的Fork,然后在本地Fork上工作。
一个Fork的代码库就像一个普通的代码库,区别在于GitHub知道它是从哪个代码库Fork而来的。这样,我们最终可以通过创建拉取请求将我们的更改合并回主代码库。

拉取请求 是你发送给代码库所有者的一系列提交,以便他们将其合并到他们的代码树中。
GitHub的典型工作流程



这是在GitHub上工作的典型方式,因为在大多数项目中,只有少数人对代码库拥有提交权限。但是,任何人都可以通过发送拉取请求来建议补丁、错误修复甚至新功能,然后拥有提交权限的人可以应用这些请求。

通常,代码库的所有者会在合并更改之前对其进行审查,检查它们是否符合项目的开发指南、许可证是否有效等。

实际操作:提交更改
让我们修复文件中的拼写错误,看看拉取请求是什么样子的。
完成对文件的更改后,我们可以通过向下滚动并填写更改描述来提出更改建议。在本例中,我们修复了函数文档中的拼写错误。


点击“提议文件更改”按钮将在我们Fork的代码库中创建一个提交,这样我们就可以将更改发送给我们的同事。现在让我们这样做。
我们已经在Fork的代码库上创建了一个提交。但我们还没有创建将更改发送给原始代码库所有者的拉取请求。

在这个屏幕上,我们可以看到关于我们更改的大量信息。我们可以看到哪些代码库和分支参与了拉取请求的创建。

我们还可以看到GitHub自动为我们创建了一个名为 patch-1 的分支,并且我们的更改可以自动合并。太好了,没有冲突。这个窗口还允许我们在创建实际的拉取请求之前审查更改。当我们准备好后,只需点击“创建拉取请求”按钮。
创建拉取请求


这将打开一个文本框,我们可以在其中输入关于我们更改的评论。在本例中,我们的更改非常简单,只是修复了一个拼写错误,所以没有额外需要添加的内容。

如果我们建议一个更复杂的更改,我们可以使用这个文本框来提供更多背景信息。

底部的复选框允许我们允许维护者进行编辑。这可能很有用,例如,如果项目维护者在合并我们的更改时,已经有了更多提交,我们的更改需要变基。通过允许编辑,维护者可以自己完成,而不必要求我们去做——工作量更少。是的,请允许。好的,我们准备好了。让我们点击“创建拉取请求”。
完成与后续
很好,我们已经用我们的更改创建了一个拉取请求。

我们的同事现在可以查看它,并决定是否要将其合并到项目中。在本视频中,我们探讨了创建拉取请求的最简单方法,即直接通过GitHub界面进行操作。通过使用这个工作流程,你已经可以开始为GitHub上的项目做贡献了。

接下来,我们将了解如何进行更复杂的拉取请求。
总结

本节课中,我们一起学习了GitHub协作的核心概念:Fork 和 拉取请求。我们通过一个修复拼写错误的实际例子,演示了如何查看他人代码、创建Fork、直接在线编辑文件、提交更改并最终发起一个拉取请求的全过程。这是参与开源项目或团队协作的基础且重要的第一步。
046:GitHub 典型拉取请求工作流 🚀

在本节课中,我们将学习如何在GitHub上完成一个典型的拉取请求工作流。我们将从创建代码库的分支开始,在本地进行修改,然后将这些更改推送回GitHub并创建拉取请求。
概述
上一节我们介绍了如何在GitHub网页界面上直接编辑文件来创建简单的拉取请求。本节中,我们将看看如何对更复杂的更改使用更完整的工作流。这包括在本地计算机上处理代码库,并使用我们已学到的所有Git命令。
创建分支
首先,我们需要为想要贡献的代码库创建一个分支。以下是具体步骤:
- 导航到目标代码库页面。
- 点击页面右上角的 Fork 按钮。
- 等待几秒钟,GitHub会为你的账户创建一个该代码库的副本。

这个副本将包含代码库的当前状态,包括所有文件和提交历史。创建完成后,你会进入一个与你账户关联的、同名的新代码库页面。

克隆到本地
现在,我们已经在GitHub上创建了分支。接下来,我们需要在本地计算机上获取一份副本。
- 在你的分支代码库页面上,复制其URL。
- 在终端中,使用
git clone命令和该URL。
git clone <你复制的URL>
执行后,你会获得一个包含代码库内容的新目录。
进行本地更改
拥有了代码库的本地副本后,我们就可以进行所需的更改了。例如,假设这个项目目前缺少一个 README.md 文件。

首先,我们创建一个新的分支来进行这项工作:
git checkout -b add-readme
然后,创建并编辑 README.md 文件。.md 扩展名表示我们使用Markdown,这是一种轻量级标记语言,可以编写格式简单的纯文本文件。
编辑完成后,保存文件并提交更改:
git add README.md
git commit -m "Add README file"
推送更改并创建拉取请求
要将更改推送到我们的分支代码库并创建远程分支,使用以下命令:
git push -u origin add-readme
推送成功后,GitHub通常会提示你可以为此分支创建拉取请求。
在创建拉取请求之前,务必检查代码是否可以成功合并。GitHub会显示你的更改是否可以自动合并。如果不行,你可能需要根据原始代码库的当前分支进行变基操作。
在创建拉取请求的页面,你需要填写评论来解释此次更改的原因。这有助于审核者理解为何应该将你的更改合并到主分支。你可以说明这是在修复错误、添加新功能,还是像本例一样补充缺失的文档。
同时,务必检查页面底部显示的差异对比,确保你发送的更改完全正确。
确认无误后,点击 Create pull request 按钮。创建成功后,你会获得一个用于在GitHub上跟踪此拉取请求的唯一标识号。
后续互动

创建拉取请求后,项目维护者可能会提出疑问、给出评论,甚至要求你修改拉取请求。例如,他们可能指出你的新功能缺少一些文档。在这种情况下,你需要根据反馈进一步更新你的分支。
总结
本节课我们一起学习了GitHub上完整的拉取请求工作流。我们从创建分支开始,将代码库克隆到本地,创建新分支并进行修改,然后推送更改并最终创建拉取请求。记住,清晰的沟通和确保代码可合并是成功提交拉取请求的关键。
047:更新现有的拉取请求 🔄

概述
在本节课中,我们将学习如何在收到项目维护者的反馈后,更新已提交的拉取请求。我们将了解如何根据评论修改代码、提交更改,并确保这些更改自动关联到原有的拉取请求中。
当我们提交拉取请求后,通常会收到项目维护者的一些评论,要求进行改进。
这些改进可能包括添加文档或测试。
也可能需要确保更改适用于所有情况,或符合项目的风格指南。
收到这些评论没有问题。这实际上表明项目维护者对我们的更改感兴趣。
为了让我们的更改获得批准,处理这些评论非常重要。
例如,如果被要求添加文档,我们就去完成它。
回到我们的更改,看起来我们收到了一位同事的评论。
评论说我们的README文件太简短,希望我们添加一个示例。

为了处理这条评论,我们将向文件中添加更多细节。
我们将首先解释该函数将“姓 名”重新排列为“名 姓”。
然后我们将添加一个示例,说明调用rearrange_name函数并传入“Tring Allen”作为参数。
将返回“Alan Tring”。好的,我们稍微充实了README文件。
现在我们可以像往常一样添加更改并将其提交到代码库。
让我们运行git commit并传递一条提交消息,说明我们已向README添加了更多信息。

之后,我们将把更改推送到代码库。
好的,既然我们已经将更改推送回代码库,让我们在GitHub中检查我们的拉取请求。
在“提交”选项卡中,我们可以看到我们的两次提交。我们的提交现在显示为同一拉取请求的一部分。
这里需要注意的是,我们只是将提交推送到之前相同的分支,而GitHub自动将其添加到拉取请求中。
如果我们想创建一个单独的拉取请求,则需要创建一个新分支。
如果我们转到“文件更改”选项卡,可以看到受拉取请求影响的所有文件。
无论它们是在哪次提交中更改的。当我们查看由一次提交或一系列提交生成的差异时。
GitHub将为我们所做的更改显示彩色的差异。
它将使用绿色表示新增的行,红色表示被删除的行。如果只有一行的一部分发生了更改。
它将高亮显示该行的特定部分。在这种情况下,这是一个新文件。
所以所有行都是新增的。请注意,即使有两次单独的提交,我们也只看到一个文件。
我们看到的是我们的代码库与我们为其创建拉取请求的原始代码库之间的差异。
我们还可以单击预览图标,显示渲染后的Markdown内容。
GitHub渲染我们的文件并高亮显示更改。

请记住,GitHub上的每个项目的工作方式可能略有不同。
有些项目可能要求你的拉取请求中只有一个提交。
其他项目可能要求你在拉取请求准备合并回主分支时,基于最新的master分支进行变基。
GitHub允许项目设置其贡献指南。
每当你在项目中创建新的拉取请求或议题时,都会找到指向这些指南的链接。
因此,请确保你已阅读这些指南,并且你的拉取请求符合它们。在本视频中。
我们看到了如何通过在本地Git仓库中进行新的提交并将其推送到远程仓库来更新我们的拉取请求。
我们之前已经看到了如何使用GitWeb界面创建新的拉取请求。
并且我们可以使用此界面来更新拉取请求。
当我们想要进行的更改很小时,这可能很方便。
例如修复拼写错误或在文档中添加额外的句子。接下来。
我们将讨论如果要求你将更改压缩为单个提交时该怎么办。
总结
本节课中,我们一起学习了如何响应代码审查反馈并更新拉取请求。关键步骤包括:根据评论在本地修改代码,提交更改到同一分支,以及推送后GitHub会自动将新提交关联到原有拉取请求。我们还了解了不同项目可能有不同的协作规范,例如要求单一提交或变基,因此务必阅读项目的贡献指南。
048:Git 交互式变基与压缩提交 🛠️

概述
在本节课中,我们将学习如何使用 Git 的交互式变基功能来压缩多个提交。这在你需要整理提交历史、合并多个小改动为一个逻辑清晰的提交时非常有用。我们将重点介绍 git rebase -i 命令,并演示如何安全地重写已推送到远程仓库(如 GitHub 拉取请求)的提交历史。
为何以及何时可以重写历史
上一节我们介绍了变基的基本概念。本节中我们来看看在什么情况下可以安全地重写提交历史。
之前我们强调过,当提交已经发布(推送到公共仓库)后,你不应该重写历史。这是因为其他人可能已经基于这些内容同步了他们的仓库。
然而,在拉取请求的上下文中,这条规则可以放宽。通常只有你自己克隆了你复刻的仓库,因此重写你个人分支上的历史是安全的。
假设项目维护者要求我们将两个更改合并为一个提交,并提供一个比我们最初提交的更详细的描述。我们可以通过使用交互式变基命令来实现这个目标。
使用交互式变基
以下是使用 git rebase -i 进行交互式变基的步骤。
-
启动交互式变基
我们将master分支作为参数传递给命令。git rebase -i master -
理解编辑器中的选项
命令执行后,文本编辑器会打开,按从旧到新的顺序列出所有选中的提交。通过更改每行的第一个单词,我们可以选择要对提交执行的操作。
默认操作是pick,它表示接受提交并将其变基到我们选择的分支上(这与不带-i标志的git rebase行为一致)。但现在我们可以将操作更改为其他命令。文件中的注释说明了我们可以对提交使用的所有不同命令。例如:
reword:保留更改,但修改提交信息。edit:编辑提交,以添加或删除其中的更改。squash和fixup:用于合并提交。两者都将所选提交的内容合并到列表中的前一个提交中。区别在于对提交信息的处理:- 选择
squash时,提交信息会被合并,并且编辑器会打开让我们进行必要的修改。 - 选择
fixup时,该提交的提交信息会被丢弃。
- 选择
-
压缩提交
在我们的例子中,我们希望使用squash来合并两个提交,同时修改提交描述。因此,我们将第二行的pick命令改为squash,使其合并到第一个提交中。 -
编辑合并后的提交信息
保存并退出编辑器后,Git 会提供另一个文件让我们编辑,即合并后的提交信息。Git 在注释中显示了一些有用信息,包括修改了哪些文件以及正在合并哪些提交。
我们可以通过添加关于更改的更多信息来改进描述,例如添加一个示例用例。 -
完成变基
编辑好提交信息后,保存并退出编辑器。变基操作就完成了。我们可以使用git show命令来检查最新的提交及其包含的更改,确认两个更改已成功合并为一个包含完整新文件和正确提交信息的提交。
处理本地与远程分支的差异
在尝试将此更改推送到我们的仓库之前,让我们先检查一下状态。
-
检查状态
运行git status。Git 会告诉我们本地分支有一个提交(即我们刚刚完成的变基),而origin/add-readme远程分支有两个提交(即我们之前已推送到仓库的那两个提交)。 -
查看历史图谱
运行以下命令查看提交历史图谱:git log --graph --oneline --all -4我们可以看到,推送到
origin/add-readme分支的两个提交与当前在我们本地add-readme分支中的提交位于不同的路径上。每当我们执行变基时都会出现这种情况,因为旧的提交在远程仓库中,而我们的本地仓库中有一个不同的新提交。
强制推送更新
那么,当我们运行 git push 时会发生什么?让我们试试看。
正如预期,Git 不喜欢我们尝试推送这个更改,因为它无法快进合并。但在这种情况下,我们不想创建合并提交,而是想用新提交替换旧的提交。
为此,我们需要使用 -f 或 --force 标志进行强制推送:
git push -f
这个命令会强制 Git 按原样将当前快照推送到仓库。
推送成功后,再次运行 git log --graph --oneline --all -4 查看历史图谱。这次,在 master 分支之上只有一个提交,之前的分歧已经消失。

最后,查看拉取请求的内容,确认我们已成功地将两个提交合并为一个。

总结
本节课中我们一起学习了如何使用 Git 的交互式变基 (git rebase -i) 来压缩多个提交。我们了解了在拉取请求的上下文中安全重写历史的场景,掌握了将 pick 改为 squash 来合并提交并编辑信息的具体步骤,并学会了在重写历史后如何使用 git push -f 来强制更新远程分支。这些工具在维护清晰、整洁的提交历史时非常有用。
你已经掌握了在 GitHub 上创建拉取请求、更新拉取请求以及压缩提交的方法。接下来,你将找到我们所使用命令的参考列表以及获取更多信息的链接,之后还有一个快速测验来检查你是否理解了所有内容。
049:什么是代码评审 🔍


在本节课中,我们将学习代码评审(Code Review)的基本概念、目的、流程及其在提升项目质量中的重要性。我们将了解代码评审如何帮助团队协作,确保代码、文档和配置文件的清晰性、一致性和正确性。

GitHub 及其他代码仓库托管服务提供了在其平台上进行代码评审的工具。虽然这被称为“代码评审”,但实际上我们可以使用相同的工具和流程来评审任何文本文件,包括配置文件和文档。
进行代码评审意味着仔细检查他人的代码、文档或配置,确保其逻辑清晰并符合预期的模式。代码评审的目标是通过确保变更具有高质量来改进项目。
上一节我们介绍了代码评审的基本定义和目标,本节中我们来看看代码评审带来的具体好处。


代码评审有助于确保内容易于理解、风格与整体项目保持一致,并且我们没有遗漏任何重要的用例。评审增加了检查代码的“眼睛”数量,这提升了代码质量并减少了错误数量。这并不意味着代码将完全没有错误,但至少最明显的错误会被发现。此外,这有助于知识传播,因为代码编写者和评审者现在都了解代码的功能。


当我们与团队成员在同一办公室工作时,我们可以通过一起查看变更并讨论内容如何配合来进行面对面的评审。

然而,当我们的合作者位于不同的办公室或时区时,使用代码评审工具是更好的选择。
代码评审工具允许我们对他人编写的代码进行评论。这让我们可以留下关于如何改进代码的反馈。
以下是评审中常见的一些代码问题:
- 变量或函数名称不清晰,导致代码难以理解。
- 忘记添加测试。
- 忘记处理特定的条件。
如果我们在编写文档,评审者可以帮助我们捕捉拼写错误和表述不清的地方。
在 GitHub 等平台上,项目通常只要求对没有提交权限的人员进行评审,而项目维护者可以直接提交。但进行代码评审可以提高代码的整体质量。如今,一些开源项目和许多公司要求每个人都进行代码评审。这不是因为他们不信任成员,而是因为他们希望获得最高质量的代码,而代码评审是实现这一目标的方法。
有一点需要始终牢记:代码评审不是评判我们是好程序员还是坏程序员。评审的目的是让我们的代码变得更好,不仅限于某一次评审,而是整体上。通过获得反馈,我们可以持续改进编码技巧;通过评审他人的代码,我们也可以学习实现目标的新方法和不同方法。
和所有人一样,在为一个问题辛苦工作数小时并最终解决后,我只想提交代码并结束工作。但这很少发生。代码评审通常会因为一些小错误和细节问题让我返工。但这是一件好事。这些代码评审指出了我们可能遗漏的地方,并确保我们的代码对他人来说是有意义的。
我想到一个例子,那是我在编写一个 Android 错误报告解析器脚本的时候。在花了几个小时修改代码并编写测试来验证我的工作后,我终于将其提交进行代码评审,并以为大功告成了。结果,我有一堆小的风格指南错误,我的评审者没有放过它们。更重要的是,在修复风格错误时,我注意到我的脚本中遗漏了一个主要用例,这会导致我的代码无法工作。因此,我修复了风格错误和遗漏的用例,并可以微笑着提交我的更改了。
在 Google,我们深信评审我们所做一切事情的价值。即使是这些课程的内容,也经过了大量人员的评审。这些评审确保了内容清晰易懂、技术上正确无重大遗漏,并遵循既定的指导方针。感谢我的同事们让我们保持警惕,并确保这些视频是一流的。
本节课中我们一起学习了代码评审的核心概念。我们了解到,代码评审是一个旨在提升代码、文档和配置质量的协作过程,它通过集体智慧发现错误、确保风格一致并传播知识。评审不是对个人的评判,而是团队共同追求卓越代码质量的必要实践。接下来,我们将讨论典型的评审工作流程以及如何从评审过程中获得最大收益。
050:代码评审工作流 🛠️


在本节课中,我们将学习典型的代码评审工作流。我们将了解从提交代码变更到最终合并的完整过程,包括如何处理评审意见、与评审者互动,以及如何通过评审提升代码质量。




在上一节视频中,我们解释了什么是代码评审以及它如何提升代码质量。本节中,我们将通过一个评审工具来了解典型的代码评审流程。

想象一下,我们刚刚完成了一系列代码变更。现在,我们将请求评审者审查我们的代码。

评审者可能会认为一切正常,并批准我们的变更。但通常,他们会发现一些需要改进的地方。

因此,他们会在我们的变更中添加注释,解释需要修复的内容以及方法。当我们收到评审意见时,我们会通过修复拼写错误、添加缺失的测试等方式来处理这些意见。
处理完一条评论后,我们可以将其标记为“已解决”,以便我们知道该问题已得到处理。如果我们不确定如何操作,或者认为可能有更好的方法,我们可以回复评论并向评审者请求更多信息,而不将评论标记为已解决。
一旦所有评论都已解决,并且评审者对结果感到满意,他们将批准变更,我们便能够合并它。
你可能会好奇,我会收到哪些类型的评论?评审者对你的代码可能有各种各样的意见。
有时,你可能忘记考虑某些重要事项,需要进行大量工作来修复。有时,评审者可能指出一些不重要的小问题,这些评论主要是改进代码的建议。这些评论通常以“nit”(无关紧要的小问题)为前缀。无论评论内容如何,重要的是花时间理解评论的含义以及你需要采取的措施。
例如,如果你编写了一段代码,评审者要求你解释代码为何或如何执行某项操作,你可能倾向于仅在评论中回答问题并将其标记为已解决。但这并不是一个好主意,因为只有评审者能看到你的回答。相反,最好将此视为使代码更清晰的机会。例如,你可以通过使用更好的变量名或将大段代码拆分为更小的函数来实现。此外,你还可以为代码添加注释,并为函数编写文档,以确保清楚地解释“如何”和“为何”。
代码评审中通常包含多条关于代码风格的评论。为了避免反复沟通,最好参考项目的编码风格指南。例如,许多Python项目使用PEP8风格指南。如果你参与贡献的项目没有风格指南,请务必要求提供一份。如果需要灵感,我们将在下一篇阅读材料中提供一些常见风格指南的链接。
市面上有许多代码评审系统,虽然它们都遵循相同的模式,但工作方式并不完全相同。在某些代码评审工具中,你需要项目维护者之一批准你的代码才能提交。在其他工具中,你只需要获得项目贡献者的几个“+1”即可提交。目标是确保你的代码已由熟悉项目的人员评审,以便准备提交。
你能想到过去参与过的、代码评审可能有所帮助的项目吗?也许你曾作为团队一员工作,但难以确保每个人都同意工作方式。或者,你可能正在学习使用新工具,而第二双眼睛的审视会对你的工作有益。无论项目简单还是复杂,良好的代码评审总能带来改进。
接下来,我们将深入探讨GitHub上的代码评审流程是如何工作的。
本节课中,我们一起学习了典型的代码评审工作流。我们了解了从提交变更到处理评审意见、与评审者互动,直至最终合并代码的完整过程。我们还探讨了如何通过评审提升代码质量,以及不同评审工具的特点。掌握这些知识将帮助你在团队协作中更高效地进行代码评审。
051:在GitHub上进行代码评审 📝

在本节课中,我们将学习如何在GitHub平台上进行具体的代码评审操作。我们将跟随一个实际的拉取请求(Pull Request)示例,了解如何查看评审意见、修改代码、提交更改,并最终完成评审流程。
之前我们已经讨论了进行代码评审的一般流程。这个流程适用于任何具备代码评审工具的平台。
现在,让我们具体看看这个过程在GitHub上是如何进行的。还记得吗?在本模块早些时候,我们创建了一个添加README文件的拉取请求。
很巧,我们的同事刚刚回复了一些评审意见。让我们来看一下。
查看评审意见
代码评审包含一条总体评论,以及逐行高亮显示我们需要完成的事项的评论。
我们可以通过点击“查看更改”按钮,来查看针对我们创建的文件所请求的所有更改。

我们的评审员对我们的文件提出了三条意见:
- 第一条是要求我们在句子末尾添加一个句号。
- 第二条要求我们添加一个额外的井号(
#),这会使标题以更小的字体显示。 - 最后一条需要我们做更多工作,因为它要求我们添加几个额外的示例。
修改代码并提交
我们来修复这些问题。我们将在第二句末尾添加一个句号,然后在示例标题前添加第二个井号,最后再添加几个示例。
为此,我们将使用星号字符(*),这是Markdown语言的另一个特性,可以让我们轻松创建项目符号列表。所以,我们将添加几行格式相同的文本,例如“Hopper, Grace M”变成“Grace M Hopper”,以及“Voltaire”保持为“Voltaire”。
好的,我们已经处理了代码评审中的所有意见。
让我们保存文件,然后提交更改。由于我们希望这个更改成为之前提交的一部分,我们将使用 git commit --amend 命令,这会编辑原始的提交。
git commit --amend
完成之后,我们运行 git status 来查看Git对我们仓库状态的说明。
和之前一样,我们看到我们的更改已经与 origin/master 分支产生了分歧。
理解强制推送
你可能记得,git commit --amend 会修改提交,因此对已经推送到远程仓库的提交进行此操作是不安全的。
使用 --amend 与创建一个新提交,然后使用交互式变基(rebase)来修复更改的效果基本相同。所以,原始提交会被一个拥有完全不同提交ID的全新提交所取代。
这意味着要推送它,我们需要再次使用 -f(强制)标志。
git push -f origin branch-name

请记住,对拉取请求分支进行强制推送是可以的,因为理论上没有其他人会克隆它。但这并不是我们希望对公共仓库进行的操作。
完成评审流程
我们已经完成了同事的要求,现在让我们回到拉取请求页面,解决(resolve)这些评论。
你会看到一条评论显示为“已过时”。这是因为我们做出修改后推送了一个新版本。但由于我们已经处理了评审请求,我们可以忽略“已过时”的提示,直接解决(resolve)这个对话。

很好,我们已经处理了所有评论。我们可以在对话中留言,告知评审员我们已经解决了所有意见,并请他们再次查看。
我们的评审员现在可以查看新的更改,如果满意的话,就可以批准(approve)它们。
总结与练习
就像我们讨论过的许多其他主题一样,要充分利用代码评审流程,需要一些实践。掌握一些技巧固然很好,但最终我们需要通过经验来学习。所以,不要害怕去练习、练习、再练习。
接下来,你将找到一些学习更多关于代码评审的资源,以及一个测验来将知识付诸实践。之后,你可以尝试在GitHub上实际进行一次代码评审。
052:协作管理指南 🫂


在本节课中,我们将学习如何在团队协作中有效管理项目,包括代码重构、文档编写、拉取请求处理以及沟通协调等关键实践。
概述
上一节我们介绍了使用GitHub等平台工具进行协作的基本方法。本节中,我们将探讨在这些工具之外,如何通过有效的协调与管理来提升团队协作效率。
代码重构的协调
当项目需要进行中型或大型重构,影响多个文件的多行代码时,提前通知同事非常重要。如果可能,尽量在其他开发者处理项目不同部分时进行重构,这有助于避免大型复杂冲突。
文档的重要性
我们多次强调记录工作内容非常重要。与大型团队协作时,记录所做工作及其原因变得更加重要,否则你将花费大量时间回答其他人的问题。此外,当服务在你休假期间出现问题,或代码开发者在世界另一端正在睡觉时,文档需要足够完善,以帮助他人解决问题。
最基本的文档形式是编写清晰的代码,并为代码中的函数提供良好的注释和文档说明。
项目文档文件
在此基础上,你需要创建文档文件,让他人了解如何与你的项目交互,例如我们在之前视频中创建的README.md文件。
以下是创建有效项目文档的关键步骤:
- 编写清晰的代码注释
- 为函数和方法添加文档字符串
- 创建
README.md文件说明项目概况 - 提供安装和使用指南
- 记录贡献指南和代码规范

拉取请求处理
如果你是项目维护者,及时响应拉取请求非常重要,不要让它们停滞不前。拉取请求等待审核的时间越长,越有可能出现新的提交,导致合并更改时产生冲突。
此外,如果贡献者是试图提供帮助的志愿者,让他们等待反馈时间过长可能会削弱他们为项目工作的动力。
变更审核原则
维护项目时需要记住的另一件事是,理解接受的任何变更非常重要,特别是对于志愿者贡献的开源项目。你永远不知道对方在你合并代码后是否会继续维护,因此最好确保你自己能够做到这一点。
你还应该谨慎决定接受或拒绝哪些补丁。接受所有发送给你的内容可能导致项目过度增长而难以管理,或者考虑太多边缘情况,导致代码复杂难以维护。
相反,如果不接受任何拉取请求,你会打击贡献者的积极性,错失保持项目活跃性和相关性的机会。
代码风格指南
我们已经多次讨论过风格指南。如果你正在为项目做贡献,需要查看风格指南并确保遵循它。如果你拥有一个项目,创建风格指南很有意义,这样其他人就知道你对他们的期望。
在我们的下一篇阅读材料中,将包含一些最常见风格指南的链接,以及如何在自己项目中包含风格指南的指导。


任务协调与问题跟踪
在协调谁做什么以及何时做方面,活跃软件项目的常见策略是使用问题跟踪器。这是一个非常有用的工具,我们将在下一个视频中了解更多相关信息。
团队沟通渠道
此外,当项目足够大时,拥有另一种在贡献者之间沟通和协调的方式非常重要。多年来,大多数项目使用邮件列表和IRC频道进行沟通。最近,新的沟通形式变得流行,如Slack频道或Telegram群组。
如果你管理自己的项目,选择最适合你和贡献者需求的沟通媒介。如果你正在与不属于自己的项目协作,需要找出正在使用哪些渠道进行协作。
总结
本节课中我们一起学习了团队协作中的关键管理实践。你现在对如何通过互联网与他人协作有了大致了解。接下来,我们将讨论两个可以帮助我们更好协作的重要工具:问题跟踪和持续集成。
053:使用问题跟踪器

在本节课中,我们将学习如何在团队协作中使用问题跟踪器来有效管理任务、报告错误并协调工作。这对于确保项目有序进行至关重要。
概述
与他人协作时,决定谁负责什么工作至关重要。缺乏协调会导致多人重复处理项目的同一部分,而其他关键部分却无人问津。想象一下,你和同事决定为网络中的计算机开发自动化更新软件。如果不将任务分解并分配给不同的人,而是随机开始处理基础设施的某些部分,结果很可能是混乱的:软件组件无法协同工作,并且存在许多无人处理的空白。
对于小型团队,通常可以轻松地当面讨论分工。但随着团队规模扩大,讨论职责和后续工作会变得麻烦。这时,问题跟踪器或错误跟踪器这类工具就能帮助我们更好地协调工作。
什么是问题跟踪器?
问题跟踪器向我们展示了需要完成的任务、任务的状态以及负责人。该系统还允许我们为问题添加评论。这些评论非常有用,可以提供问题的更多细节、解释解决方法,或详细说明如何测试问题是否已解决。
问题跟踪器不仅对积极参与项目的人有用,还允许用户遇到错误时进行报告,即使他们不知道如何解决问题。有时用户会遇到我们从未想到的问题,让他们通过错误跟踪器报告这些问题有助于改进我们的项目。此外,跟踪器还能帮助想要为项目做出贡献的志愿者。一个清晰可见的待办工作列表让新贡献者知道如何提供帮助以及从哪里入手。
常见的跟踪工具
有多种不同的解决方案来跟踪错误或问题。有一个流行的错误跟踪器叫Bugzilla,被许多开源项目使用。另一方面,像GitHub这样的平台内置了问题跟踪器。因此,如果你在那里托管项目,使用它来跟踪项目工作(如待解决的问题、要添加的功能和要包含的用例)会非常方便。
实践:在GitHub中创建与处理问题
让我们来实际操作一下。这是我们健康检查项目的问题列表。目前,它只有一个要求我们更新文档的问题。
一位同事建议我们创建一个新的健康检查,用于验证系统日志(如内核日志或系统日志)中是否存在任何关键错误消息。那里出现的错误可能有助于排查一些有趣的问题。因此,这听起来像是一个值得添加的新检查功能。
创建新问题
我们通过点击“New issue”按钮为此功能创建一个问题。对于问题标题,我们将说明我们希望检查系统日志中的关键错误。对于问题描述,我们将说明新检查应遍历 /var/log/kern.log 和 /var/log/syslog,并检查是否存在任何需要关注的关键错误。
在编写问题描述时,最好包含我们掌握的关于问题或缺失功能的所有信息,以及任何解决思路。如果后续出现新信息,可以将其作为同一问题的附加评论添加。
很好,我们现在可以提交这个新问题了。现在,我们的待解决问题列表中有了新条目。列表中的每个问题都有一个唯一的识别编号。
正如我们在之前的视频中提到的,在GitHub中,项目中的每个问题或拉取请求都有一个与之关联的唯一编号。因此,如果存在ID为5的拉取请求,就不会有ID为5的问题。当我们使用 #编号 格式提及时,GitHub会自动引用问题、拉取请求和评论。例如,如果我们在评论中使用 #2,它将自动引用我们刚刚创建的问题。
通过提交自动关闭问题
如果你通过拉取请求修复问题,一旦代码被合并,可以直接自动关闭该问题。为此,你需要在提交信息或拉取请求描述中包含类似 Closes #4 的字符串。一旦代码被合并到主分支,GitHub将自动关闭该问题,并将其链接到新的提交。

让我们通过更新文档(如 #1 号问题所要求的)来尝试这个功能。这个问题似乎很容易修复。我们需要更新Readme文件以使用新的文件名,并进一步解释我们的脚本如何工作。
分配问题
在我们开始处理之前,让我们把这个问题分配给自己。将问题分配给协作者有助于我们跟踪谁在做什么。通过将错误分配给自己,你可以让别人知道你正在处理它,这样他们就不需要重复劳动了。
好的,让我们更新文档。我们还在使用主文件的旧名称 all_checks.py。我们之前已经将该文件重命名为 health_checks.py。让我们更改我们的Readme文件,使用新的文件名(使用反引号表示等宽文本)。然后,我们将添加说明:如果所有检查通过,此脚本将打印“Everything OK”;如果某些检查失败,它将打印相应的错误消息。
我们已经更新了文档。让我们保存文件并提交这个更改。这次,我们调用 git commit -a,以便在文本编辑器中编辑提交信息。我们将说明我们已经更新了Read Me以使用脚本的新名称。在更长的描述中,我们将添加说明,表示我们已经包含了关于脚本工作方式的更多信息。最后,我们将添加字符串 Closes #1 来结束提交信息,以便在本次提交被集成到主分支后,问题会自动关闭。
我们的提交信息看起来不错。让我们保存它并将其推送到代码仓库。

现在让我们回到我们正在处理的问题。我们看到,随着我们推送的提交,我们的问题已自动关闭。我们可以点击提交ID来查看完整的提交。这就是我们创建的带有相关更改的提交信息。看,我们作为提交信息一部分包含的 #1 被自动检测为指向 #1 问题的链接。
总结

本节课中,我们一起学习了问题跟踪器在团队协作中的重要性。我们了解了问题跟踪器的基本功能,包括创建问题、分配任务、添加评论以及如何通过特定的提交信息格式自动关闭问题。我们还以GitHub为例,实践了创建新问题、分配问题、修复问题并提交更改的完整流程。虽然关于问题跟踪还有更多内容可以学习,但这足以让你入门。请自由实验,尝试更多与系统交互的方式。
054:持续集成与持续部署 🚀

在本节课中,我们将学习如何通过自动化流程来确保代码质量与快速部署。我们将探讨持续集成(CI)和持续部署(CD)的核心概念、工作原理以及如何利用相关工具(如Travis CI)为项目配置自动化流程。
概述
在软件开发过程中,我们经常修改文件。有时我们会手动运行测试以验证修改后代码是否仍能正常工作,但有时我们会忘记这样做。无论项目规模大小,这种情况都很常见。人类并不擅长记住所有待办事项,因此我们不能依赖任何人(包括我们自己)去记住测试代码。幸运的是,我们无需如此。我们可以编写自动化测试来为我们检查代码,并利用持续集成系统自动运行这些测试。
什么是持续集成(CI)?
持续集成系统会在每次代码变更时自动构建和测试我们的代码。这意味着每当代码主分支有新的提交,或通过拉取请求引入变更时,系统都会自动运行。换句话说,如果为项目配置了持续集成,我们就可以使用拉取请求中的代码自动运行测试。这样,我们可以验证新变更合并回代码库后测试是否通过。这意味着我们无需指望协作者记住正确测试代码,而是可以依赖自动化测试系统来完成。
从持续集成到持续部署(CD)
一旦代码实现自动构建和测试,下一个自动化步骤就是持续部署,有时也称为持续交付。持续部署意味着新代码会频繁部署。其目标是避免在项目两个版本之间进行包含大量变更的发布,而是每次仅进行少量变更的增量更新。这有助于及早发现并修复错误。典型的配置包括:每当有提交合并到主分支,或每当有分支被标记为发布时,就部署新版本。
CI/CD工具与平台概览
与CI/CD相关的工具和平台众多,整个系统通常被称为CI/CD。一个流行的选择是Jenkins,它可以用于自动化多种不同类型的项目。一些代码库托管服务(如GitLab)提供了自己的持续集成基础设施。GitHub本身不提供集成解决方案,流行的替代方案是使用Travis CI。Travis CI与GitHub通信,可以访问GitHub项目的信息以确定需要运行哪些集成。
无论使用哪种工具,在创建自己的CI/CD流程时都需要处理一些核心概念。
核心概念:流水线与制品
第一个核心概念是流水线。流水线指定了为达到预期结果需要运行的步骤。对于一个简单的Python项目,流水线可能只是运行自动化测试。对于一个用Go编写的Web服务,流水线可能包括:编译程序、运行单元测试和集成测试,最后将代码部署到测试实例。
另一个在CI/CD中出现的概念是制品。这个术语用于描述作为流水线一部分生成的任何文件。这通常包括代码的编译版本,但也可能包括其他生成的文件,如文档的PDF或便于安装的特定操作系统软件包。
此外,您可能希望保留流水线、构建和测试阶段的日志,以便在失败时进行审查。
安全注意事项:管理密钥
在设置CI/CD时,我们必须谨慎管理密钥。如果流水线包括将软件新版本部署到测试服务器,我们需要以某种方式让运行流水线的软件能够访问我们的测试服务器。有多种策略可以实现这一点,例如交换SSH密钥或使用特定于应用程序的API令牌。对于某些流水线,使用这些方法之一可能是不可避免的。但请注意,您正在将测试服务的访问权限授予为您运行流水线的服务所有者。这有点像把家门钥匙交给每年检查一次暖气的人。
因此,需要记住两点:
- 确保测试服务器的授权实体与生产服务器的部署授权实体不同。这样,即使流水线出现任何安全漏洞,您的生产服务器也不会受到影响。
- 始终制定一个计划,以便在流水线遭到破坏时恢复访问权限。
实践:为GitHub项目设置Travis CI

如果您想为GitHub项目设置Travis CI,可以按照以下步骤操作:
- 使用您的GitHub账户登录Travis CI网站(travis-ci.com)。
- 启用您希望进行持续集成的项目。
- 之后,您需要在项目中添加一个用YAML格式编写的配置文件。该文件声明项目使用的语言以及流水线需要执行的步骤。
如果您的项目遵循所用语言的典型配置,这个文件可以非常简单。但如果您想运行一个包含许多阶段和默认步骤之外步骤的复杂流水线,它也可能变得非常复杂。我们在此不深入细节,但接下来的阅读材料中有更多信息。如果您希望对项目进行持续集成和交付,请随时阅读并自行研究。
总结
本节课我们一起学习了持续集成与持续部署的核心概念。我们了解到,CI系统可以自动构建和测试代码变更,确保质量;而CD则在此基础上实现代码的频繁、自动化部署。我们还探讨了流水线、制品等关键概念,以及设置CI/CD(如使用Travis CI)时的基本步骤和安全注意事项。通过自动化这些流程,团队可以减少人为失误,更快地交付更可靠的软件。
055:协作 🎯

在本节课中,我们将回顾模块4的核心内容,总结通过GitHub进行高效协作的关键工具与工作流程。

上一节我们介绍了版本控制的基础操作,本节中我们来看看如何利用这些工具进行团队协作。
我们查看了许多通过GitHub实现更好协作的工具。
我们研究了拉取请求的典型工作流程,例如如何更新与压缩更改。
我们学习了代码评审如何通过帮助我们发现错误、拼写错误及其他问题来提升代码质量。
最后,我们探讨了一些用于协作的高级工具,例如问题追踪器或持续集成服务。
所有这些都建立在本课程中涵盖的其他版本控制工具和技术之上,例如检查变更历史、回滚不良更改以及合并他人的更改。
以下是本模块涵盖的主要协作工具与概念:
- 拉取请求工作流程:包括创建、更新(
git rebase)与压缩提交(git squash)。 - 代码评审:通过同行审查来提升代码质量、发现潜在问题。
- 高级协作工具:如GitHub Issues用于任务追踪,以及Jenkins、Travis CI等持续集成服务用于自动化测试与构建。
在本课程中,你学到了很多知识,你应该为自己取得的成就感到自豪。
我第一次使用版本控制系统是在谷歌的第一个职位上。一旦我理解了它能为我做什么,我主要感受到一种情绪:轻松。
我非常惊讶于它能如此轻松地浏览代码,并准确找出谁为项目做出了贡献以及他们做了什么。
如果我遇到任何问题,我只需联系最后修改代码的人,并与他们坐下来学习。
我希望你也开始体验到掌控代码的力量,并且对在未来的所有项目中使用版本控制系统感到兴奋。
接下来是课程的最后一项评估。你需要与GitHub仓库交互,复刻一个仓库,推送到你自己的仓库,然后根据你的更改创建一个拉取请求。
祝你好运,记住,你能做到。
本节课中我们一起学习了如何利用GitHub进行有效的团队协作,掌握了从拉取请求、代码评审到使用高级工具的全套协作流程。这些技能将帮助你在未来的项目中更好地管理与协作代码。
056:祝贺与总结 🎉

在本节课中,我们将对《用Python进行IT自动化办公》课程中关于Git、调试、云端与实践的核心内容进行总结,并祝贺你完成这一阶段的学习。
恭喜你,你已经完成了本课程的学习。这是一段充满趣味与挑战的旅程。
你完成得非常出色。现在,你几乎已经成为使用Git管理代码的专家。
你学会了如何跟踪代码变更,在变更不合理时进行回滚,以及解决代码冲突——尤其是当你与协作者修改了同一段代码时。你还掌握了一系列有效与他人协作的技巧。
我希望你喜欢这段学习过程。我个人非常享受。对我而言,版本控制中最有用的部分是能够随时查看变更历史。
这在多人协作时尤其方便。我希望你也能使用这一工具,并亲身体验其益处。
好了,是时候结束这一部分了。我非常享受与你共度的这段时光,也很高兴能分享一些我的个人经历以及对IT和版本控制的热爱。
其中一些概念理解起来并不容易,感谢你一直坚持学习。这展现了令人印象深刻的学习承诺。
代表你已见过或将在后续课程中见到的同事们,我们祝你一切顺利。我们期待看到你的代码应用于实际,自动化越来越多的工作。
总结
本节课中我们一起回顾了课程的核心收获:
- Git专家级技能:你已熟练掌握使用Git进行版本控制。
- 核心操作:你学会了跟踪变更、回滚修改以及解决合并冲突。
- 协作能力:你掌握了一系列在团队中高效协作的技术。
- 工具价值:你理解了版本历史记录在多人协作中的巨大实用价值。
旅程暂告一段落,但你的自动化之旅才刚刚开始。祝你未来在IT自动化的道路上继续取得成功!
057:问题排查与调试 🐛

在本节课中,我们将简要回顾已学内容,并预览下一门课程的核心主题:系统化的问题排查与调试技术。
在之前的课程中,我们深入学习了版本控制。我们探索了如何利用它来理解项目的历史记录,以及如何在更新引发问题时回滚到早期版本。
下一站:深入问题根源 🔍
接下来,我们将进入新的学习阶段。我的同事阿曼达将担任你们的讲师,带领大家探索如何定位并修复复杂问题。
以下是阿曼达对下一门课程的介绍:
大家好,肯尼,以及所有的学员们。我对下一门课程感到非常兴奋。我们将学习如何对疑难问题进行故障排除。
我们将掌握一系列实用技能,主要包括以下四个方面:
- 如何找到复现案例:学习定位能稳定触发问题的步骤。
- 如何缩小问题范围:通过隔离变量,将复杂问题简化,使其更易于理解和调试。
- 资源耗尽时的应对方法:当计算机内存或磁盘空间不足时,应该采取哪些措施。
- 处理程序运行缓慢:分析性能瓶颈并优化程序速度。
我们将研究大量真实场景。其中一些场景的排查可能相当棘手,但我们会学习多种不同的技术,帮助你理解现状、找到问题的根本原因,并提出解决方案。
我迫不及待地想与大家分享这些知识。我们下节课再见。
感谢阿曼达,也感谢每一位观看并陪伴我完成这门课程的学员。我们稍后见。
本节课中,我们一起回顾了版本控制的核心价值,并预览了下一门关于系统化调试与问题排查的课程。我们了解到,下一阶段将重点学习复现问题、缩小排查范围、管理资源及优化性能等实用技能,为处理实际工作中的复杂问题做好准备。
058:IT自动化与问题解决 🚀


在本节课中,我们将要学习如何识别和解决IT领域中常见的各类技术问题。无论是程序崩溃、性能瓶颈,还是系统故障,掌握系统化的排查与调试方法都至关重要。我们将从通用策略入手,逐步深入到具体场景的应用,帮助你建立起解决实际问题的能力。
问题类型与挑战 🔍
在IT职业生涯中,你会遇到多种多样的技术问题。
有时你需要找出程序未按预期运行的原因。可能是程序意外崩溃,或在应处理信息时卡住。
其他时候,你需要找到方法让脚本运行得更快、占用更少内存或在网络上传输更少数据。
或者,你可能需要找出整个系统未按预期运行的原因及修复方法,即使问题代码并非由你编写。
课程内容与方法论 🛠️
在本课程中,我们将探讨一系列解决此类问题的不同策略和方法。
我们将学习一些有助于解决几乎所有技术问题的通用思路,然后看看这些思路如何应用于不同的现实场景。
我们选取的示例包括通用系统问题、他人编写的软件问题以及我们自己编写的程序问题。
我们将讨论可能影响任何操作系统的问题,同时也会研究某些平台特有的问题。
对于脚本问题,我们将重点讨论Python程序,但也会探讨其他语言可能出现的常见问题。
讲师介绍与个人分享 👩💻
我叫Themandabels,是谷歌检测与响应运营团队的一名安全系统管理员。在日常工作中,我管理Linux服务器,并特别关注安全领域。我的职责是维护监控内部网络、侦测恶意信号的系统,以确保我们能快速发现恶意行为者。我是团队的技术负责人之一,当我担任这个角色时,我也接手了并非由我自己编写的系统。
这意味着我接管了数千行代码,我必须熟悉它们,以便能够持续添加新功能并维护现有代码。当试图找出问题原因,或更糟的是在处理系统中断时,这可能格外具有挑战性。
课程特色与学习环境 💡
如你所知,本课程由谷歌独家设计和开发,每门课程都在不同的园区地点进行,为你带来额外的谷歌特色体验。这是我们的一处谷歌工作空间,当我们需要一起排查问题时可以在这里深入钻研。
关于故障排除和调试,有很多内容需要学习。在本课程中,我们将为你提供解决IT工作中可能遇到的现实问题的工具。我们将讨论的场景基于实际的IT问题。
我们将邀请你与同学分享你自己解决过的其他示例。在本课程中,我们将使用Quicklas,这是一个允许你在运行Linux的虚拟机上测试代码的环境。这将让你体验真实的Linux场景,你需要运用在本课程中学到的技术来理解和解决一些示例问题。
学习建议与支持 🤝
请记住,其中一些主题和视频内容较为复杂,第一次可能无法100%理解。这完全正常。如果需要,请慢慢来,并多次重看视频,你会掌握所有内容的。
同时请记住,你可以随时使用讨论论坛与同学联系并提出问题。
总结与启程 🎯
本节课中,我们一起了解了IT领域常见的技术问题类型、本课程将采用的方法论、讲师的背景分享,以及课程提供的学习环境和支持。我们明确了学习目标:掌握系统化的排查与调试技能,以应对实际工作中的挑战。
好了,你准备好扩展我们的故障排除和调试能力了吗?太好了,让我们开始吧。
059:故障排查概念介绍 🧩


在本节课中,我们将学习故障排查的基本概念和通用流程。无论面对何种技术问题,掌握一套系统性的方法都至关重要。我们将介绍一个可用于解决任何技术问题的基本流程,并探讨如何理解问题、定位根本原因,以及如何应用这些可复用的技巧。
上一节我们概述了本模块的学习目标,本节中我们来看看故障排查的基本流程。无论遇到应用程序崩溃、硬件故障还是网络中断,作为IT专家,我们都需要确保受影响的用户能尽快恢复工作,并计划如何防止未来再次发生相同问题。
面对这些问题时,我们可以遵循一个通用流程来高效地解决问题。
以下是解决技术问题的基本步骤:
- 识别问题:明确发生了什么错误以及谁受到了影响。
- 重现问题:在可控环境中复现故障,以确认问题存在。
- 定位根源:通过收集信息和测试,找到导致问题的根本原因。
- 制定并实施修复方案:设计解决方案并安全地应用它。
- 验证修复:确认问题已被解决,且未引入新问题。
- 总结经验:记录问题和解决方案,并思考如何预防未来发生。
上一节我们介绍了通用流程,本节中我们来看看如何深入理解问题并定位其根源。我们将学习一种名为“二分查找法”的高效排查技巧。
“二分查找法”是一种通过不断将问题范围对半分割来快速定位根源的策略。其核心思想是,通过测试来排除一半不可能存在问题的部分,从而逐步缩小搜索范围。
以下是应用二分查找法进行故障排查的示例:
- 场景:一个网页应用加载缓慢。
- 步骤1:测试是前端(浏览器)问题还是后端(服务器)问题。
- 步骤2:如果确定是后端问题,则检查是数据库慢还是应用服务器代码慢。
- 步骤3:持续对半分割问题域,直到找到具体的故障模块或代码行。
这种方法可以公式化地理解为,在每次测试后,将待排查的组件数量 n 减少一半,从而将最坏情况下的排查步骤从 O(n) 优化到 O(log n)。
在本课程中,我们将持续应用这些技巧,探索可能以不同方式影响我们或我们所支持用户的各种问题。与你在此课程中学到的其他技能一样,精通某项技能的最佳方式就是实践。
因此,在本模块结束时,你将有机会在一个运行Linux的虚拟机上应用这些技巧,尝试自己解决一个技术问题。虽然找到问题的解决方案有时可能需要很长时间,但这个过程本身充满挑战和乐趣。
处理软件问题就像尝试解决一个巨大的拼图。有时找不到正确的拼图块会令人沮丧,但当一切最终严丝合缝时,又会让人超级兴奋。如果我所做的一切都能立即成功,那反而会失去乐趣。我热爱这种挑战。
本节课中我们一起学习了故障排查的核心概念、通用解决流程以及高效的“二分查找”定位方法。这些可复用的技巧将帮助你解决未来可能遇到的几乎任何类型的技术问题。现在,让我们开始实践吧。😊
060:调试与故障排除 🔍


在本节课中,我们将学习调试与故障排除的核心概念、区别以及在实际工作中如何应用它们。我们将通过定义、工具介绍和一个真实案例,帮助你理解如何系统地分析和解决技术问题。

概述
调试和故障排除是解决技术问题的两个关键过程。虽然它们有时被混用,但各有侧重。故障排除关注于识别、分析和解决系统层面的问题,而调试则专注于定位和修复应用程序代码中的缺陷。掌握这两者,能帮助我们更高效地应对各种技术挑战。
故障排除与调试的定义

故障排除是指识别、分析和解决问题的过程。这个术语可用于解决任何类型的问题。
在本课程中,我们将专注于解决与IT相关的问题。这些问题可能由硬件、操作系统或计算机上运行的应用程序引起。它们也可能由软件的环境和配置、应用程序交互的服务或一系列其他可能的IT原因导致。
另一方面,调试是指识别、分析和移除系统中缺陷(Bug)的过程。
两者的区别与联系
我们有时会互换使用“故障排除”和“调试”这两个词,但通常,当我们在修复运行应用程序的系统问题时,我们称之为故障排除。
而当我们在修复应用程序实际代码中的缺陷时,我们称之为调试。
有许多工具可以帮助我们获取更多关于系统及其程序运行状态的信息。


以下是常用工具分类:
- 网络分析工具:如
tcpdump和Wireshark,可以显示正在进行的网络连接,并帮助我们分析线缆上的流量。 - 系统资源监控工具:如
ps、top或free,可以显示系统中使用的资源数量和类型。 - 程序调用追踪工具:我们可以使用
strace来查看程序进行的系统调用,或使用ltrace来查看软件进行的库调用。
不必担心需要记住它们。我们将在实际示例中详细讨论每一个工具。
在调试程序代码时,我们可以将这些工具与为编写应用程序所使用的编程语言开发的特定调试工具结合起来。
调试器让我们能够逐行跟踪代码、检查变量赋值的变化、在满足特定条件时中断程序执行等等。
此外,如果我们能够修改代码,我们可以更改它以提供更多的日志信息。这可以帮助我们理解幕后发生的事情。
故障排除和调试都带有一点艺术性。在那些幸运的情况下,如果你以前见过这个问题,你可能立即知道解决方案是什么。但通常,找出问题及其解决方案需要一些创造力。我们需要想出可能导致故障的新想法以及检查这些想法的方法。一旦我们知道是什么出了问题,我们就需要设想如何解决它。
更进一步,一旦我们解决了问题,我们就可以开始思考如何防止它再次发生。



一个真实案例:指向生产环境的集成测试
我记得在我的上一个团队中遇到的一次棘手的调试过程。
我们最近在一个流水线中添加了集成测试,以确保在自动构建和部署新版本之前服务能正常工作。

测试顺利运行了大约一个月,然后开始失败。
由于这是一个集成测试,旨在防止有缺陷的版本被发布,我非常惊讶地发现,有缺陷的代码实际上已经运行在生产服务器上了。


我查看了大量日志,并花了很长时间跟踪代码的执行过程。
最后,我注意到问题在于测试是针对生产实例运行的,而不是我们最初预期的测试实例。换句话说,只要生产实例运行正常,测试就会通过;而当生产实例出现问题时,测试就会失败。
这完全不是我们想要的。为了修复这个问题,我必须弄清楚为什么测试代码没有连接到我们在集成测试内部创建的测试实例。

经过更多调查后,我发现测试实例无法启动是因为执行路径不正确。
为了解决这个问题,我最终修改了由另一个团队提供的库,以传递正确的参数。就这样,测试开始针对测试实例中的代码运行,而不再是生产实例。



总结
在故障排除或调试时,我们会遇到各种意外情况。事情没有按预期运行,我们需要理解原因并找出解决方法。
正如我们所指出的,在本课程中,我们将研究一系列不同的技术来理解和解决技术问题。虽然我们有时会关注系统端,有时会关注编码端,但我们所涵盖的大多数技术都可以帮助我们解决任何技术问题。
接下来,我们将讨论解决任何类型技术问题所需采取的步骤。
061:解决问题的步骤 🛠️


在本节课中,我们将学习一套通用的步骤,用于解决IT领域遇到的各种技术问题。这套方法包括信息收集、根因分析和实施修复,能帮助我们系统性地应对挑战。
第一步:获取信息 📋
上一节我们介绍了解决问题的整体框架,本节中我们来看看第一步——获取信息。这意味着我们需要尽可能全面地了解问题的当前状态、具体表现、发生时间和影响范围。
以下是获取信息时可以使用的资源:
- 内部文档、操作手册或知识库。
- 系统自带的帮助页面或手册页(man pages)。
- 互联网上的技术问答和论坛。
- 问题复现步骤:对问题如何及何时出现的清晰描述,是解决问题的关键资源。
第二步:寻找根本原因 🔍


在收集了足够的信息后,下一步是寻找问题的根本原因。这通常是解决问题过程中最具挑战性的一步,本课程后续将深入探讨多种排查方法。
核心在于深入探究问题的本质:是什么触发了问题,以及我们如何改变这一状况。
第三步:执行必要的修复 🛡️

找到原因后,最后一步是执行修复。根据问题的性质,修复可能包括以下两部分:
- 即时修复:使系统快速恢复正常运行。
- 中长期修复:制定方案以防止问题在未来再次发生。
需要指出的是,这三个步骤并非总是严格按顺序进行。在实践中,我们经常需要在步骤间灵活切换。
问题解决的动态过程 🔄
虽然我们列出了三个基本步骤,但它们并不总是线性发生的。
例如,在尝试寻找根本原因时,我们可能发现需要更多关于当前状态的信息,于是返回第一步收集更多数据,直到找到答案。
或者,我们可能对问题有初步了解后,先创建一个临时解决方案让用户快速恢复工作,但仍需要更多时间来找到根本原因并实施永久性修复。
防止问题再次发生有时看似麻烦,但实际上能为用户和我们自己节省大量宝贵时间,避免反复解决同一个问题。
记录的重要性 📝
在整个问题解决过程中,记录我们所做的一切至关重要。
我们应该记下获取的信息、为找出根本原因而尝试的各种测试,以及最终采取的修复步骤。这份文档在下一次类似问题出现时,可能具有不可估量的价值。
实践案例:电脑意外关机 💻
让我们通过一个例子来应用这些步骤。假设有用户求助,称其电脑意外自动关机。
电脑不应自行关机,问题可能源于硬件、软件或配置。因此,首先要做的是获取更多信息。
你需要了解以下情况:
- 关机何时发生?
- 发生时用户正在做什么?
- 问题发生的频率如何?
同时,你还需要检查电脑日志,查看是否有任何异常错误。如果遇到不明确的错误信息,可以在互联网上查询其含义。
在本例中,假设你在日志中发现一行记录,显示“超过温度阈值,因此电脑关机”。这很有用,你知道了关机的原因,但还不知道为何会过热,因此需要继续调查。
在日志中没有发现其他线索后,你决定检查是否是硬件问题。打开电脑机箱后,你发现本该为CPU散热的风扇积满了灰尘,导致无法正常转动。
这就是问题的根本原因。
现在,短期修复是清理风扇,使其恢复转动,电脑便不会过热。
但长期修复是什么?在这个案例中,长期修复包括:
- 在电脑上部署监控,确保在过热时能及早收到通知。
- 检查是否可以减少空气中的灰尘量,以降低此类事件再次发生的概率。
本节课中,我们一起学习了解决问题的三个核心步骤:获取信息、寻找根因和执行修复。我们了解到这些步骤是动态循环的,并强调了记录过程的重要性。最后,通过一个电脑过热的实例,我们看到了如何将这些步骤应用于实际场景。接下来,我们将通过解决一个更实际的例子来测试这些步骤。
062:无声崩溃应用程序的调试 🐛

在本节课中,我们将学习如何诊断一个没有显示任何错误信息就立即退出的“无声崩溃”应用程序。我们将使用系统工具来追踪程序行为,定位问题根源,并实施解决方案。

上一节我们介绍了问题报告的基本处理流程。本节中,我们来看看一个具体的案例:一个在启动时无报错却直接崩溃的应用程序。
一位用户联系我们,告知某个应用程序无法打开。
正如之前强调的,第一步是获取更多关于导致故障的条件信息。
我们需要了解用户遇到了什么错误,然后通过询问这些细节来检查我们是否能复现相同的故障。
经过调查,我们发现该软件最近发布了一个新版本。
当我们升级到这个新版本后,我们可以在自己的电脑上复现这个问题。
我们看到,当我们尝试运行该程序时,它完全没有打印任何错误,只是立即退出。
即使没有错误信息,我们也需要弄清楚发生了什么。

为了更深入地理解系统和应用程序的运行状况,有一系列工具可以帮助我们。
借助这些工具,我们可以扩展对特定问题的认知,从不同角度查看程序的行为,并获取所需信息。
在这些工具中,Strace 让我们能够更深入地查看程序正在做什么。它会追踪程序进行的系统调用,并告诉我们每个调用的结果。
因此,为了弄清楚我们无法打开的程序出了什么问题,我们将对故障应用程序使用 Strace。
以下是使用 Strace 的基本命令格式:
strace <your_command>
哇,输出信息非常多。strace 命令显示了程序进行的所有系统调用。
系统调用是运行在我们计算机上的程序向运行中的内核发出的请求。
存在大量不同的系统调用,根据我们试图调试的内容,我们可能对其中一些更感兴趣。
如果你想了解这些系统调用是什么,可以在相应的手册页中阅读更多关于每个调用的信息。
但在深入研究之前,让我们先让这个输出更易于管理。
我们可以将输出通过管道传递给 less 命令,用它来滚动浏览大量文本。
或者,我们可以使用 strace 命令的 -o 标志将输出存储到文件中,然后浏览该文件的内容。
-o 标志允许我们在需要时稍后查阅该文件,所以让我们选择这个方法。
好的,现在我们可以使用我们偏好的任何程序来读取生成的文件。
让我们用 less 打开它,按 Shift + G 跳到文件末尾,然后向上滚动查看是否能发现任何可疑之处。
在日志接近结尾的地方,我们可以看到应用程序尝试打开一个名为 .config/purple_box 的目录,但这个目录并不存在。
让我们更详细地看一下这一行。
系统调用的名称是 openat,这是用于打开文件或目录的调用之一。
调用的内容显示了传递的参数,包括正在打开的路径和一系列标志。
特别是,O_DIRECTORY 标志告诉我们程序正试图将此路径作为目录打开。
等号后面的数字向我们显示了此调用的返回码。在本例中,它是 -1。
所以程序试图打开这个目录,但结果发现它不存在。
由于这发生在程序结束前不久,它很可能是导致问题的根本原因。

让我们创建这个目录,然后再次尝试启动程序。
mkdir -p ~/.config/purple_box
成功!这次程序正常运行了。
让我们回顾一下我们所做的步骤。
首先,我们从用户那里获得了一些信息,告诉我们新版本中的一个变更导致了问题。
为了调查此事,我们在自己的计算机上复现了这个问题。
然后,我们通过使用 Strace 工具获得了更多关于正在发生什么的信息,该工具让我们可以看到程序进行的系统调用。
我们发现了一个可疑的错误,指出一个目录不存在。
我们创建了这个目录,以检查当目录存在时会发生什么,结果程序正确运行了。
因此,我们确定了问题的根本原因,即缺失的目录。
现在我们可以着手解决问题。
立即的补救措施是告诉用户创建该目录,以便他们能快速恢复工作。
长期的补救措施是联系软件开发者,告知他们如果该目录缺失,程序将无法启动。这让他们对问题有所警觉,以便在下一个版本中修复它。
至于文档,我们应该注明:如果该目录不存在,此版本的软件将无法启动。
这将帮助其他遇到相同问题的人快速找到解决方案。
在这个例子中,我们能够使用 Strace 快速识别问题所在,但这并不总是这么容易。
在本课程中,我们将继续研究更多工具和思路,以帮助我们在问题不那么明显时弄清楚发生了什么。
本节课中我们一起学习了如何调试一个“无声崩溃”的应用程序。我们使用 strace 工具追踪系统调用,发现并修复了因缺失目录导致的启动失败问题。这个过程涵盖了从复现问题、深入分析到实施短期和长期解决方案的完整调试流程。记住,清晰的文档记录对于团队协作和未来问题排查至关重要。
063:问题诊断与解决 🐛


在本节课中,我们将学习如何系统地诊断和解决技术问题。我们将从一个简单的“它不工作”报告开始,逐步深入,直到找到问题的根本原因并实施解决方案。
概述:从“它不工作”开始
正如我们指出的,解决问题的第一步是获取足够的信息,以便理解当前的事态。
为此,我们需要知道我们正在解决的实际问题是什么。
这通常始于我们第一次遇到问题,可能是通过工单系统的报告,也可能是我们在与用户一起工作时自己遇到了问题。
收到仅仅归结为“它不工作”的故障报告是很常见的。
这些报告通常不包含很多有用的信息。
但问题被报告并得到解决仍然很重要。

哪些信息有用可能取决于具体问题。
但对于那些只是报告“某物不工作”的用户,我们可以提出一些常见的问题。
关键提问:获取有效信息
以下是我们可以向用户提出的几个关键问题:
- 用户试图做什么?
- 他们遵循了哪些步骤?
- 预期的结果是什么?
- 实际的结果是什么?
如果贵公司使用的工单系统允许,最好将这些问包含在用户报告问题时必须填写的表格中。
这样,我们可以节省时间,并可以立即开始询问更具体的问题。否则,这些问题几乎总是你首先要问的。


简化原则:从最简单的解释开始
需要记住的另一件事是,在调试问题时,我们希望首先考虑最简单的解释,并避免跳入复杂或耗时的解决方案,除非我们真的必须这样做。
这就是为什么当设备无法开机时,我们首先检查它是否正确插入电源,以及插座是否有电,然后再将其拆开或更换为新设备。
假设你接到一个用户的电话,告诉你销售团队用于跟踪客户互动的内部网站无法工作。
用户非常紧张,因为他们需要在几分钟内举行的会议上访问网站上的信息。

所以你告诉他们你会立即调查这个问题,但你需要更多信息。

- 用户试图做什么? 用户告诉你他们正试图访问网站。
- 他们遵循了哪些步骤? 他们告诉你他们打开了网站URL并输入了凭据。
- 预期的结果是什么? 他们期望看到销售系统的登录页面。
- 实际的结果是什么? 网页只是不停地加载。它永远保持空白。

问题定义:从模糊到具体
现在,你已经从“它不工作”转变为“当我尝试登录时,页面持续加载,从不显示登录页面”。这很好。
既然你对问题有了基本的了解,是时候开始找出根本原因了。
为此,你将应用一个排除过程,从最简单的解释开始,并测试这些解释,直到你能隔离出根本原因。
实践排除:逐步缩小范围
例如,你检查是否可以在自己的计算机上重现该问题。
所以你导航到网站,输入你的凭据,果然,页面只是不停地加载,从不显示登录页面。
这个信息足以让你告诉用户你将着手处理并在自己这边进行调查。通过在你的计算机上重现问题,你采取了一个简单而快速的行动,排除了用户或用户计算机是问题原因的可能性。
这使故障排除过程减少了一半,因为你现在知道服务本身存在问题。
在跳转到托管应用程序的服务器之前,你运行一些快速检查,以验证问题是仅限于该特定网站还是更广泛。
你通过访问一个外部网站来检查你的互联网访问是否正常工作,该网站加载正常。
然后你检查其他内部网站,如库存网站或工单系统,是否工作正常。

这样做,你发现虽然工单系统加载没有问题,但库存网站也从未完成加载。

事实证明,这两个网站都托管在同一台服务器上。
深入调查:检查服务器状态
再次强调,进行这些快速检查以验证互联网工作正常以及哪些站点受问题影响,通过首先查看可能的简单解释,帮助你隔离根本原因,避免浪费时间追逐错误的问题。
此时,你知道运行在特定服务器上的网站无法加载,而其他系统和互联网工作正常。
接下来,你需要检查该服务器上发生了什么。
运行网站的服务器是一台Linux机器,因此你将使用SSH连接到它。
你运行 top 命令,该命令显示计算机状态和占用最多CPU的进程,你看到计算机严重超载。第一行中的平均负载显示为 40。
Linux上的平均负载显示处理器在给定一分钟内的繁忙程度,1 表示它在一整分钟内都处于繁忙状态。

所以通常,这个数字不应高于计算机中的处理器数量。
高于处理器数量的数字意味着计算机超载。

你知道这台计算机有四个核心,所以 40 是一个非常高的数字。

你还看到大部分CPU时间都花在等待上。这意味着进程被卡住,等待操作系统从系统调用返回。
当进程从硬盘驱动器或网络收集数据时被卡住时,通常会发生这种情况。通过查看进程列表,你意识到备份系统当前正在服务器上运行,并且它似乎使用了大量的处理时间。
备份系统上的数据非常重要,但目前整个系统无法使用,所以你决定通过调用 kill -STOP 来停止备份系统。
这将暂停程序的执行,直到你让它继续或决定终止它。
实施解决方案与验证

这样做之后,你再次运行 top,看到负载正在下降,进程也不再因等待IO而卡住。
然后你尝试登录网站,这次登录页面成功加载。

你通知你的用户他们可以再次使用该网站。
此时,你已经应用了即时补救措施。我们将在后面的视频中讨论长期补救措施。
重要教训:避免假设

在进入下一个主题之前,想象一下下周,另一个用户打电话告诉你销售网站无法工作。
想起之前的事件,你告诉他们你会立即修复,你通过SSH登录服务器,试图找到备份进程来停止它。但它没有运行。糟糕,你忘了问用户他们说“它不工作”时具体是什么意思。
当你回电询问他们时,他们告诉你他们正试图生成月度销售报告,并收到一个错误,说产品类别列不存在。
完全不同的问题,需要采取完全不同的行动。
所以请记住,在开始解决问题之前,一定要对问题是什么有一个清晰的了解。
总结
本节课中,我们一起学习了如何从模糊的问题报告入手,通过关键提问(做什么、步骤、预期结果、实际结果)来准确定义问题。我们强调了从最简单的解释开始进行排除的重要性,并通过一个实例演示了如何逐步缩小问题范围,从用户端检查到服务器端,最终使用 top 命令和 kill -STOP 命令定位并暂时解决了因备份进程导致服务器过载的问题。最后,我们认识到避免假设、清晰定义问题是有效诊断的第一步。下一节,我们将讨论什么是复现案例以及如何构建它。
064:创建复现案例 🐛


在本节课中,我们将学习如何为难以调试的问题创建一个清晰的复现案例。这是诊断和解决问题的关键第一步。

什么是复现案例?
当我们处理一个棘手的调试问题时,需要为这个问题准备一个清晰的复现案例。复现案例是一种验证问题是否存在的方法。
我们希望复现案例尽可能简单。这样,我们可以清楚地理解问题何时发生,并且在尝试解决问题时,能非常容易地检查问题是否已被修复。
复现案例的复杂性

有时,复现案例非常明显。在我们之前提到的因缺少目录而导致程序启动失败的例子中,复现案例就是在计算机上打开一个没有该目录的程序。在我们提到的服务器过载的例子中,故障的复现案例就是尝试登录网站并看到加载页面。
但有时,发现复现案例可能复杂得多。想象一下,你正在帮助一个用户解决应用程序无法启动的问题。
处理复杂情况
这一次,当你在自己的计算机上运行相同版本的应用程序时,应用程序启动正常。因此,你怀疑问题与用户的环境或配置有关。
以下是可能导致此问题发生的一系列原因:
- 网络路由问题。
- 旧的配置文件干扰了新版本的程序。
- 权限问题阻止用户访问某些必需的资源。
- 甚至是一些有故障的硬件在作祟。


那么,你如何找出问题的根源呢?
第一步:查阅日志
第一步是查阅你可用的日志。需要查阅哪些日志取决于操作系统和你试图调试的应用程序类型。




以下是不同操作系统的日志位置:
- Linux:你会查阅系统日志,如
/var/log下的日志、syslog,以及用户特定的日志,如位于用户主目录下的.xsession-errors文件。 - Mac OS:除了系统日志,你还需要查阅存储在
~/Library/Logs目录中的日志。 - Windows:你会使用“事件查看器”工具来查阅事件日志。
无论是什么操作系统,请记住,当某些东西行为异常时,要查看日志。很多时候,你会发现一条错误信息,帮助你理解发生了什么,例如“无法连接到服务器”、“无效的文件格式”或“权限被拒绝”。
如果没有明确的错误信息怎么办?
但如果你运气不好,没有错误信息,或者错误信息毫无帮助(例如“内部系统错误”),下一步就是尝试隔离触发问题的条件。
以下是你可以尝试询问或测试的问题:
- 同一办公室的其他用户是否也遇到此问题?
- 如果同一用户登录到另一台计算机,会发生同样的情况吗?
- 如果移动应用程序的配置目录,问题还会发生吗?
构建复现案例

假设是配置目录的问题。你让用户将其移走(而不是删除)。现在应用程序可以正确启动了。于是你让用户将该目录的内容发送给你。你将这些内容复制到自己的计算机上,程序又无法启动了。

太好了! 你得到了你的复现案例:在存在该配置的情况下启动程序。
拥有一个清晰的复现案例可以让你调查问题,并快速看到什么改变了它。例如:
- 如果将应用程序恢复到以前的版本,问题会消失吗?
- 在使用错误配置和不使用错误配置运行应用程序时,
strace日志或ltrace日志中有什么不同吗?
此外,拥有清晰的复现案例,让你在寻求帮助时可以与他人分享。当然,前提是你不分享任何机密信息。你可以用它来向应用程序开发人员报告错误、向同事寻求帮助,甚至可以在关于该应用程序的互联网论坛上(如果它是公开的)寻求帮助。
创建复现案例的原则
因此,在尝试创建复现案例时,我们希望找到能重现问题的操作,并且我们希望这些操作尽可能简单。环境的变化越小,需要遵循的步骤列表越短,就越好。
为了达到这个目的,我们可能需要更深入地挖掘问题,直到我们得到一组足够小的操作指令。一旦你有了复现案例,就可以准备进入下一步:寻找根本原因。我们将在下一个视频中讨论这一点。
总结
本节课中,我们一起学习了如何为调试问题创建复现案例。我们了解到复现案例是验证和隔离问题的关键工具,它应该尽可能简单明了。我们还探讨了如何通过查阅系统日志和逐步隔离环境条件来构建有效的复现案例,为最终找到问题的根本原因打下坚实基础。
065:找到根本原因 🔍


在本节课中,我们将学习如何系统地寻找和确定技术问题的根本原因。我们将探讨从提出假设到验证假设的完整流程,并介绍一些实用的工具和方法来辅助诊断。
初次接触这些概念时,你可能会认为一旦有了问题复现步骤,就已经知道了问题的根本原因。但事实往往并非如此。在我们之前讨论的服务器过载例子中,我们发现了备份系统阻塞了网站运行,并因此采取了缓解措施来解除对用户的阻塞。然而,我们并未深入探究服务器卡住的根本原因。这可能是由于网络带宽饱和、磁盘传输速度过慢、硬盘故障或其他一系列原因造成的。我们也没有采取任何措施来确保未来的备份能够成功运行。理解根本原因对于执行长期修复至关重要。
那么,我们如何着手寻找问题的实际根本原因呢?我们通常遵循一个循环:审视已有的信息,提出一个可以解释问题的假设,然后测试我们的假设。如果假设得到证实,我们就找到了根本原因。如果没有,我们就回到起点,尝试另一种可能性。这正是我们解决问题的创造力发挥作用的地方。我们需要构思一个可能的原因,检查它是否正确,如果不正确,就构思另一个不同的想法,直到找到一个能解释问题的原因为止。
我们的想法并非凭空产生。为了获得灵感,我们需要审视当前已有的信息,并在需要时收集更多信息。在线搜索我们遇到的错误信息,或查看相关应用程序的文档,也能帮助我们想象出可能导致故障的新可能性。
只要有可能,我们应该在测试环境中验证我们的假设,而不是在用户正在使用的生产环境中进行。这样可以避免干扰用户的工作,并且我们可以放心地进行调试,而不用担心破坏重要的东西。

根据你试图修复的内容,这可能意味着我们需要在新安装的机器上测试代码、启动测试服务器、使用测试数据等等。搭建这样的环境可能需要一些时间,但额外的安全性绝对是值得的。即使错误似乎与特定的生产环境有关,在修改生产环境之前,检查是否能在测试环境中复现问题总是一个好主意。
在我们的服务器过载例子中,如果问题出在硬件上,我们将无法在测试服务器上复现它。在这种情况下,我们需要等待服务不被使用的时间段,或者启动一个备用服务器,将服务迁移过去,然后才能检查计算机出了什么问题。
反之,如果问题与Web服务或备份服务的某些配置有关,我们仍然会在测试服务器上看到它。因此,我们总是从搭建服务的测试实例开始,并在接触生产实例之前,检查问题是否在那里复现。


假设我们有一个运行相同网站的测试服务器。当我们开始备份时,我们看到网站停止响应。这很好,因为我们有了复现案例,可以正确地调试它。
我们如何找到根本原因?一个可能的罪魁祸首是过多的磁盘输入/输出。为了获取更多信息,我们可以使用 iotop,这是一个类似于 top 的工具,可以让我们看到哪些进程使用了最多的输入/输出。
以下是其他相关的工具:
iostat:显示输入/输出操作的统计信息。vmstat:显示虚拟内存操作的统计信息。

如果问题是进程产生了过多的输入或输出,我们可以使用像 ionice 这样的命令,让我们的备份系统降低其访问磁盘的优先级,也让Web服务能够使用它。
如果输入/输出不是问题,另一个可能性可能是服务使用了过多的网络带宽,因为它正在将待备份的数据传输到中央服务器,而该传输阻塞了其他所有流量。
我们可以使用 iftop 来检查这一点,这是另一个类似于 top 的工具,用于显示网络接口上的当前流量。


如果备份占用了所有网络带宽,我们可以查看备份软件的文档,检查它是否已经包含了限制带宽的选项。常用于备份数据的 rsync 命令就包含一个 --bwlimit 参数专门用于此目的。如果该选项不可用,我们可以使用像 trickle 这样的程序来限制使用的带宽。
但如果网络也不是问题呢?记住,我们需要发挥调试的创造力,想出其他可能导致失败的原因。
另一个可能性可能是所选的压缩算法过于激进,压缩备份占用了服务器的所有处理能力。我们可以通过降低压缩级别或使用 nice 命令来降低进程访问CPU的优先级来解决这个问题。

如果这仍然不是原因,我们需要继续寻找。检查日志,看看是否能找到之前遗漏的任何信息。或许可以在网上搜索其他人处理过的、关于备份软件与Web服务软件交互的类似问题,并持续这个过程,直到我们找到可能导致问题的原因。

我知道这听起来工作量很大,但通常情况并没有那么糟。总的来说,通过使用我们可用的工具,我们通常只需尝试几次就能收集到足够的信息,从而得出正确的假设。随着经验的积累,我们将更擅长在第一次就选出最有可能的假设。
在本节课中,我们一起学习了寻找问题根本原因的系统方法。我们了解了从复现问题、提出假设到在安全环境中测试验证的完整循环流程,并认识了几款用于诊断磁盘I/O(iotop, iostat)和网络带宽(iftop)的实用工具。掌握这些技能将帮助我们不仅解决表面问题,更能实施长效的修复方案。
066:处理间歇性问题 🔍


在本节课中,我们将学习如何诊断和调试那些时有时无、难以复现的间歇性问题。这类问题在IT运维和软件开发中非常常见,且极具挑战性。
你是否遇到过只在偶尔发生的问题?例如,程序随机崩溃、笔记本电脑有时无法休眠、网络服务意外停止响应,或者文件内容仅在特定情况下损坏。这些时隐时现的缺陷很难复现,调试起来也极其令人烦恼。如果你从事IT工作,很可能在处理这类间歇性问题时感到过挫败。
那么,当你试图调试此类问题时,可以采取哪些措施呢?
第一步:收集更多信息
上一节我们介绍了间歇性问题的普遍性,本节中我们来看看如何开始调查。首要步骤是获取更多关于问题发生时的信息,以便理解问题在何种情况下出现,在何种情况下不出现。
如果你正在处理自己维护的代码中的缺陷,通常可以修改程序,以记录与问题相关的更多信息。由于你无法确切知道缺陷何时会触发,因此需要记录的信息必须详尽。
例如,我最近遇到一个自己维护的服务问题。它偶尔会崩溃,而我当时无法找出原因。查看错误信息后,我知道它与使用特殊字符的字符串有关,但无法确定缺陷的确切位置。因此,我在服务中围绕可疑的输入和函数调用添加了更多日志信息。当程序再次崩溃时,我就能定位到代码中缺少正确编码处理的部分,并修复了问题。
以下是处理此类问题的核心思路:
- 修改代码,添加日志:在怀疑的代码区域(如函数入口、关键操作前后)插入日志语句,记录变量状态、执行路径等信息。
- 启用调试模式:如果无法修改代码,检查程序或服务是否有可更改的日志配置。许多应用程序都包含调试模式,能生成比默认模式多得多的输出。通过预先启用调试信息,你可以在问题再次发生时更清楚地了解情况。
- 监控环境:如果以上都不可行,你需要在问题触发时监控环境。根据问题的性质,你可能需要查看不同的信息源,例如计算机的负载、同时运行的进程、内存使用情况、网络状态等。
对于随机时间发生的缺陷,我们需要让系统在缺陷发生时尽可能提供更多信息。这可能需要多次迭代,直到我们获得足够的信息来理解问题。但请不要失去希望,大多数时候你最终都能找到问题的根源。
海森堡缺陷与资源管理
有时,当我们添加额外的日志信息,或者使用调试器逐步跟踪代码时,缺陷反而消失了。这是一种特别烦人的间歇性问题,昵称为“海森堡缺陷”,以维尔纳·海森堡的名字命名。他是首位描述“观察者效应”的科学家,即观察一个现象本身就会改变该现象。

海森堡缺陷之所以格外难以理解,正是因为当我们试图干预它们时,缺陷就消失了。这些缺陷通常指向不良的资源管理。可能是内存分配错误、网络连接未正确初始化,或者打开的文件未得到妥善处理。在这些情况下,我们通常需要花时间仔细检查受影响的代码,直到最终找出问题所在。
“重启大法”的背后原理
另一种间歇性问题是那种通过关闭再重新打开就能解决的问题。IT界有很多关于“我们解决问题的方法就是重启”的笑话。确实,在许多情况下,重启设备或重新启动程序可以消除我们试图修复的任何问题。
但这是为什么呢?当我们重启计算机或重启程序时,许多东西都发生了变化。回到一个干净的状态意味着:
- 释放所有已分配的内存
- 删除临时文件
- 重置程序的运行状态
- 重新建立网络连接
- 关闭打开的文件
- 以及其他更多操作
如果一个问题通过重启就能解决,那么几乎可以肯定软件中存在缺陷,而且这个缺陷很可能与未能正确管理资源有关。
因此,如果一个问题在重启后消失,最好尝试找出原因,并看看是否有可能以一种无需重启的方式修复它。如果最终无法找到实际原因,在非问题时段安排重启也是一个可选方案。
总结与预告
本节课中我们一起学习了如何定位问题的根本原因,例如隔离原因、理解错误信息、添加日志信息以及为可能的故障生成新的思路。我们还讨论了那些自行消失后又再次出现的问题,并探讨了如何找出它们的根源。
接下来,我们将通过一个实际案例,来具体了解如何理解一个问题并找到其根本原因。
067:调试间歇性失败的脚本 🐛

在本节课中,我们将学习如何诊断和修复一个间歇性失败的脚本。我们将通过一个同事开发的会议提醒应用案例,演示从问题复现到根本原因定位,再到最终修复的完整调试流程。
问题复现
上一节我们介绍了调试的基本思路。本节中,我们来看看如何具体复现一个间歇性问题。
一位同事开发了一个向公司员工发送会议提醒的小应用。上周销售团队首次测试时,应用运行正常。但本周另一名用户尝试发送会议提醒时,程序却不断报错终止。
由于应用开发者在大西洋的另一端,用户请求我们帮助查明原因。首先,我们尝试自己运行程序,看是否能复现问题。
程序窗口允许我们输入会议日期、标题和收件人。用户尝试发送的提醒日期是1月13日,标题是“生产评审”。为了避免测试邮件打扰他人,我们将收件人设为自己。
程序提示“发送邮件失败”,这意味着我们成功复现了问题。
接着,我们尝试发送上周销售团队成功发送的提醒。那次会议的日期是1月7日,标题是“销售全员会议”。同样,我们将其发送给自己以避免打扰。
这次,程序成功发送了提醒。
那么,是哪个参数出了问题,标题还是日期?可能是任何一个。但我猜测是日期。让我们再用1月13日的日期和“销售全员会议”的标题试一次。
再次失败。至此,我们有了一个可复现的案例:尝试发送1月13日的会议提醒会失败,但发送1月7日的相同提醒则正常。
定位根本原因
现在,下一步是找到问题的根本原因。
为什么我们的应用在1月7日工作正常,却在1月13日失败?可能的原因有很多。但通常,当故障与日期相关时,问题往往出在不同国家的日期格式差异上:有些国家先写月份后写日期,而另一些国家则相反。
为了查明情况,我们需要为程序添加更多调试信息。我们打开名为 meeting_reminder.sh 的脚本,这是一个用 Bash 编写的脚本。
我们看到该脚本调用了一个名为 Zenity 的程序。Zenity 是显示窗口以选择日期、标题和邮件的应用程序。Zenity 生成的输出存储在一个名为 meeting_info 的变量中,然后作为参数传递给 send_reminders.py 这个 Python3 脚本,由后者发送邮件。
为了获取更多关于 Zenity 输出的信息,我们希望在调用 Python 脚本之前查看 meeting_info 变量的值。让我们添加一个 echo 语句来查看它。
保存脚本后再次尝试。这次,我们只使用“测试”作为会议标题,因为我们知道问题出在日期上。
我们看到 Zenity 生成的信息由竖线 | 分隔,并且日期的格式是“月-日-年”。这已经是很有价值的信息了。
获取更详细的错误信息
接下来,我们需要获取更具信息量的错误信息。
为此,我们打开发送提醒的 Python 脚本,看看是否能让它打印出更好的错误信息。文件较长,因此从查看包含程序核心功能的 main 函数开始是合理的。
我们看到它将接收到的参数分割成三部分,然后准备要发送的消息,最后发送它。如果一切正常,它会打印“发送成功”的消息。但如果出现任何故障,它会打印我们已经看到的错误信息。
但这个错误信息并不十分有用,因为它隐藏了失败的原因。让我们通过同时打印引发故障的异常来使这个错误更有帮助。
保存并再次尝试。这次我们看到,问题在于我们使用的日期格式将月份放在首位,但程序期望月份在第二位。由于没有第13个月,因此这是一个无效日期。
修复问题
至此,我们找到了问题的根本原因:程序试图按照一种特定的日期格式来转换日期,但我们使用的是另一种格式。
一旦知道了根本原因,下一步就是修复问题。在这种情况下,我们可以做什么来补救?我们可以更改程序以使用我们的日期格式,但这样应用在其他地区运行时就会崩溃。我们需要做的是确保无论在哪里运行脚本,Zenity 生成的日期格式都与 Python 期望的格式匹配。
幸运的是,Zenity 包含一个参数来指定我们想要的任何格式。因此,我们将更改 Shell 脚本,使用 --forms-date-format 参数,并将格式设置为 %Y-%m-%d,这是国际标准日期格式。
这样,Zenity 将以国际格式返回日期。
现在我们需要更改 Python 脚本以使用相同的格式。我们找到指定格式的函数,并将其更改为相同的格式。
现在,Zenity 生成的日期格式应该始终与 Python 读取的格式匹配,这个脚本应该在我们国家和其他任何地方都能正常工作。让我们测试一下,看看是否真的修复了。
太好了,我们成功修复了问题。

总结
本节课中我们一起学习了调试间歇性失败脚本的完整过程。
我们首先自己复现了问题,然后找到了触发问题的输入和未触发问题的输入。接着,我们为脚本添加了更多调试信息,这帮助我们找到了问题的根本原因:Zenity 调用和 Python 脚本使用的日期格式不匹配。最后,我们通过确保两者使用相同的日期格式修复了问题。
接下来,我们将进行另一个测验,以检查我们在最近几个视频中涵盖的概念是否都已理解。
068:🔍 什么是二分查找法


在本节课中,我们将要学习两种在列表中查找元素的基本算法:线性查找和二分查找。我们将重点理解二分查找法的工作原理、其效率优势以及适用的前提条件。
通常,在尝试找出问题的根本原因时,我们常常需要从众多可能性中寻找一个答案。在计算领域,在列表中搜索特定元素是一个常见问题。有多种不同的算法可以帮助我们找到目标元素。
例如,假设你有一个包含公司所有员工数据的列表,你需要找到其中一位特定的员工。
线性查找法
一种可能的方法是:从列表的第一项开始,检查其姓名是否与我们要找的姓名匹配。



如果不匹配,则移动到第二个元素并再次检查。


如此继续,直到找到目标员工,或者查找到列表末尾。这种方法被称为线性查找。
这种查找方法有效,但列表越长,所需时间也可能越长。换句话说,查找所需的时间与列表的长度成正比。
二分查找法
如果列表是已排序的,我们可以使用另一种名为二分查找的搜索算法。因为列表有序,我们可以根据元素在列表中的位置做出决策。


以下是二分查找的核心步骤:

-
首先,将目标元素与列表中间的元素进行比较,判断其是相等、更小还是更大。
![]()
-
如果目标元素更小,我们知道它必定位于列表的前半部分。
![]()

- 反之,如果目标元素更大,则它位于列表的后半部分。
![]()


通过这样一次比较,我们就排除了半数的候选元素。


然后,我们在剩下的半区中重复这个过程:取该半区的中间元素,再次与目标比较,并据此继续缩小搜索范围。




每次我们都处理当前区间的中间元素,直到找到目标元素。


效率对比
使用线性查找,在一个包含1000个元素的列表中查找,最多可能需要1000次比较(如果目标元素是最后一个或根本不存在)。
而使用二分查找处理同样的1000元素列表,最坏情况也只需要大约10次比较。这个数字可以通过计算列表长度的以2为底的对数得到,公式表示为:
比较次数 ≈ log₂(n),其中 n 是列表长度。
列表越长,二分查找的效率优势越显著。对于一个包含100,000个元素的列表,二分查找最多需要约17次比较,而线性查找则可能需要100,000次。
重要前提与权衡
但请记住,二分查找生效的前提是:列表必须已排序。
如果列表未排序,我们需要先对其进行排序,而这本身需要消耗时间。如果我们需要在同一个列表上进行多次查找,先排序再使用二分查找仍然是合算的。但是,如果只是为了查找一个元素而先排序列表,这通常并不划算,因为排序的开销可能超过一次线性查找的成本。在这种情况下,使用更简单的线性查找反而更快。
本节课中,我们一起学习了线性查找和二分查找两种算法。我们了解到二分查找通过每次比较排除一半数据,极大地提升了在已排序列表中的查找效率,其时间复杂度为 O(log n)。同时,我们也明确了选择算法时需要根据数据是否有序以及查找次数进行权衡。在接下来的阅读材料中,你将看到这两种算法在Python中的实现示例。之后,我们将探讨如何将二分查找的原理应用于问题排查。
069:在故障排查中应用二分查找法 🔍


在本节课中,我们将学习如何将二分查找法的核心思想应用于复杂的故障排查场景。通过将问题空间不断对半分割,我们可以高效地定位导致故障的根本原因,无论是错误的配置文件、有问题的代码提交,还是故障的浏览器扩展。
我们之前提到,二分查找算法在有序列表中查找元素时非常高效。在故障排查中,当我们需要测试一长串可能的假设时,就可以应用这一思想。此时,列表中的元素包含了所有可能导致问题的原因。我们通过不断将问题范围减半,直到只剩下一个选项。
这个元素列表可以是文件中的条目、已启用的扩展程序、连接到服务器的设备,甚至是导致故障版本出错的代码行。每一次迭代,问题规模都被削减一半。这种方法有时被称为二分法,即分成两部分。


在之前的视频中,我们举过一个例子:当旧的配置目录存在时,程序的新版本无法启动。
如果该目录中包含许多不同的文件,我们可以通过二分文件列表来找出导致故障的那一个。
假设旧目录包含12个不同的配置文件。我们想找出这12个文件中是哪一个导致了故障。

以下是具体步骤:


- 我们创建目录的一个副本,但只放入12个文件中的6个,然后尝试再次启动程序。
- 如果程序崩溃,则问题文件在这6个之中。如果程序没有崩溃,则问题文件在另外6个之中。
- 在下一步中,我们从有问题的6个文件组中选出3个进行测试。
- 如果程序再次崩溃,则问题文件在这3个之中;如果没有,则在另外3个之中。
对于最后剩下的3个文件,我们可以先同时检查两个,或者逐个检查。无论哪种方式,最多只需要两次检查就能定位到故障文件。
这意味着,总共只需4次尝试,我们就能从12个文件中找出导致问题的那个。由于IT系统中的问题有时复杂且相互关联,在宣布胜利之前,我们还需要进行验证:确认当该单个文件存在时程序会崩溃,而当其不存在时程序运行正常。



一旦确认,我们就将问题的复现条件从一个完整的目录缩小到了单个文件,这大大简化了理解和分析问题的过程。


之后,我们可以对那个单个文件的内容采用同样的方法,反复将其对半分割,直到找到文件中导致问题的具体部分。
同样的流程可以应用于各种各样的问题。例如,这种方法常用于找出导致浏览器崩溃的浏览器扩展:禁用一半扩展,检查浏览器在该子集下是否崩溃,如此反复,直到找到有问题的扩展。
我们也可以使用这种技术来发现桌面环境中哪个插件导致计算机内存耗尽,或者数据库中哪个条目导致程序引发异常。
在尝试查找最近版本中引入的Bug时,我们也可以将此方法应用于代码。如果我们知道从一个版本到下一个版本之间所做的更改列表,我们可以不断将该列表对半分割,直到找到导致故障的那一项。
当使用Git进行版本控制时,我们可以使用一个名为 git bisect 的命令。bisect 接收Git历史中的两个时间点,并反复让我们尝试它们中间点的代码,直到找到导致故障的提交。
这甚至不需要是你自己的Git仓库。如果你使用的开源软件使用Git进行跟踪,你也可以使用 git bisect 命令来找出是哪个提交导致该软件在你的计算机上停止工作。
例如,如果Linux内核的最新版本导致你计算机上的声卡停止工作,你可以使用 git bisect 来找到破坏它的提交,并将其作为需要修复的Bug进行报告。
正如我们在讨论二分查找时指出的,需要检查的项目列表越长,通过每次迭代将问题对半分割所获得的效率提升就越大。如果只有5个选项需要检查,我们可以简单地逐个尝试,这不会有太大区别,而且可能更容易跟踪我们的尝试过程。
但是,如果是100个选项,我们肯定希望使用二分法来定位问题,这样我们可以在大约7步内找到答案,而不是100步。
当我们需要测试一系列不同的选项以找出导致故障的那一个时,我们希望有一种快速简便的方法来进行检查。即使我们通过二分法减少了尝试次数,我们也不希望每次检查都花费很长时间。有时这很直接,程序要么启动要么失败。但其他时候,可能需要一系列手动步骤来检查我们想要检查的内容。
因此,根据我们试图查找的问题类型,花一些时间创建一个用于检查该问题的脚本可能是值得的。接下来,我们将在一个实际例子中看到这一点。
本节课中,我们一起学习了如何将二分查找的逻辑应用于故障排查。核心在于将可能的故障原因列表视为一个有序集合,并通过不断测试中间点来高效缩小范围,最终精确定位问题根源。无论是处理文件、扩展程序、代码提交还是其他复杂系统,掌握这一方法都能显著提升我们解决复杂问题的效率。
070:使用二分法定位无效数据 🔍

在本节课中,我们将学习如何运用二分法,快速定位并修复数据文件中的错误。我们将通过一个具体的例子,演示如何从100行的CSV文件中,高效地找到导致导入失败的那一行无效数据。
概述:二分法排查问题
上一节我们介绍了通过将问题一分为二来快速定位故障原因的思路。本节中,我们来看看如何将这个理论应用于实际的数据处理任务。
我们有一个程序,其功能是读取CSV文件,处理数据,然后将其导入数据库。一位系统用户报告,他尝试导入的文件失败,并返回了一个含义模糊的“导入错误”。用户已将问题文件发送给我们。
第一步:在测试环境中复现问题
在运行任何命令之前,需要牢记:不应在生产环境中进行测试。由于此脚本会尝试向数据库导入数据,我们应该针对测试数据库运行它,而不是生产数据库。
以下是使用测试数据库运行导入命令的方法:
cat context.csv | import.py --server test
我们看到文件导入失败,但错误信息并未明确指出失败原因。
第二步:评估问题规模
我们需要知道这个文件有多大。无需用编辑器打开查看,使用 wc 命令即可统计文件的行数。
wc -l context.csv
输出显示文件有100行。手动逐行检查以找出问题非常耗时,尤其是我们根本不知道问题出在哪里。
第三步:应用二分法定位错误
我们可以尝试只将文件的一半传递给脚本,检查是否成功。如果失败,则问题就在这一半中;如果成功,则问题在另一半。然后对有问题的那一半再次进行二分,如此反复。
我们可以利用 head 和 tail 命令来获取文件的前半部分或后半部分,并通过管道将其传递给导入脚本。
以下是相关命令的用法:
head -n 50 context.csv:输出文件的前50行。tail -n 50 context.csv:输出文件的后50行。
由于我们的导入命令从标准输入读取,因此可以使用管道连接:
head -n 50 context.csv | import.py --server test
测试发现前半部分文件导入失败。接下来,我们对这失败的50行再次进行二分检查。
第四步:逐步缩小范围
通过持续地将失败的数据块一分为二,我们逐步缩小了可疑范围。以下是排查过程的一个示例步骤:
head -n 25 context.csv | import.py --server test
经过几次二分,我们将范围缩小到仅剩6行数据,并且知道其中一行是坏的。再次二分后,我们得到了最后3行需要检查的数据。
第五步:检查并修复数据
现在,让我们查看这最后三行数据:
...
John, A, Smith, 25000
Jane, B, Doe, 30000
Robert, C, Johnson, 42000, 1000
你能发现问题吗?这是一个逗号分隔值文件,这意味着每个逗号都是字段之间的分隔符。如果一个字段内包含逗号,该字段应该用引号括起来。
在第三行中,“42000, 1000”之间有一个逗号,但整个字段没有被引号包围。这导致导入脚本困惑,因为该行的字段数量超出了预期。
找到问题后,我们编辑文件,为包含逗号的字段加上引号(例如,改为 "42000, 1000"),然后重新运行导入命令。这次,导入成功了!

总结与后续措施
本节课中,我们一起学习了如何利用二分法,从100行数据中快速定位到包含损坏数据的那一行,并成功修复了它。
- 短期措施:告知用户我们发现的问题及修复方法,以便他们能将数据成功导入生产数据库。
- 长期措施:调查文件最初为何会生成无效字段,并确保此类问题不再发生。
通过这种方法,我们能够系统、高效地解决复杂数据文件中的错误,而不是进行盲目、耗时的手动检查。
071:故障排查概念总结 🎯


在本节课中,我们将总结故障排查的核心概念。我们将回顾调试与故障处理的基本原则、关键步骤、实用工具与技术,并了解如何将这些知识应用于实际场景。
恭喜你完成本模块的学习。你所学到的知识将为你的IT职业生涯打开许多大门。
在之前的视频中,我们学习了调试与故障处理的一般原则。
我们深入研究了解决技术问题的基本流程,例如收集信息、定位根本原因以及实施修复方案。
我们学习了一系列不同的工具和技术,这些工具和技术可以帮助我们更好地理解系统和程序的运行状况。
以下是本模块涵盖的核心工具与技术:
- 如何创建复现案例。
- 如何定位问题的根本原因。
- 如何处理偶尔出现的问题。
最后,我们学习了二分查找算法,以及如何利用它来分割问题并快速定位技术问题的根本原因。
在整个过程中,我们查看了许多真实世界的示例,并了解了如何将这些方法应用于多种不同类型的问题。
以下是可应用这些方法的场景示例:
- 我们代码中的错误。
- 他人代码中的错误。
- 配置问题。
- 甚至硬件问题。
很高兴能与你分享这些有趣的故事和案例。希望你已经开始享受学习如何理解问题并寻找解决方案的过程。
下次你需要解决技术问题时,尝试使用我们概述的这些步骤和讨论过的思路。
请记住,日志是你最好的朋友,并充分利用所有可用资源。
以下是可用的关键资源:
- 在互联网上查找资料。
- 向同事或朋友寻求帮助。
在本课程的后续部分,我们将继续探索处理特定问题的场景,例如计算机运行缓慢或意外崩溃。
我们将继续应用本模块中讲解的技术来解决这些问题。
因此,你可以期待获得更多的实践机会。
说到实践,接下来是计分评估,它将让你尝试解决一个实际问题。
对于本课程的计分评估,我们将使用由Google云控制台支持的Quicklab在线学习平台。
该平台让你体验真实世界的场景,在这些场景中,你将是负责解决问题的人。
为了与实验环境交互,你将连接到在云端运行的虚拟机。
每个实验都会呈现一个需要解决的不同问题,你将有机会练习新技能并解决问题。很令人兴奋,对吧?
请记住,如果有任何不清楚的地方,你随时可以返回并重新观看视频。
当你准备好时,实验环境正在等待你。你能行的。
总结
本节课中,我们一起学习了故障排查的核心框架。我们回顾了从收集信息到实施修复的完整流程,掌握了创建复现案例、定位根本原因以及使用二分查找算法等关键技能。通过真实案例,我们看到了这些方法在代码错误、配置问题等多种场景下的应用。记住,日志是强大的工具,善用资源和持续练习是提升排查能力的关键。接下来的实践环节将帮助你巩固这些知识。
072:性能瓶颈概述 🐢


在本节课中,我们将要学习如何识别和处理IT系统中常见的性能缓慢问题。我们将探讨导致计算机、脚本或系统运行缓慢的各种原因,并概述后续课程中将深入学习的诊断与解决工具。
欢迎回来,祝贺你成功完成了第一个模块中那个棘手的实验。解决一个实际问题并让计算机重新正常工作,感觉很好。就我个人而言,我热爱工作的原因之一就是能够掌控系统,让它执行我想要的操作。请记住,如果事情没有立即成功,这完全正常。你尝试解决问题的次数越多,就越容易想出可能的解决方案。
在IT工作中,我们经常需要处理的一个问题是系统运行缓慢。这可能涉及我们的计算机、脚本,甚至是复杂的系统。“缓慢”是一个相对的概念。现代计算机比几十年前的计算机快得多,能处理更多任务。然而,我们总是希望它们能更快,在更短的时间内完成更多工作。
对于现代计算机,我们的资源看似是无限的,但如果我们足够努力,仍然可能触及极限。例如,当你浏览互联网并在浏览器中打开一个新标签页时,它似乎不占用任何资源。只需点击一下,就有了一个新标签页。
但是,如果你持续打开标签页,在某个时刻,你的计算机会变得迟缓。根据计算机的硬件配置和正在运行的其他程序,可能需要打开100个或更多标签页才会出现这种情况。但最终,你会耗尽内存,一切都会慢下来。
如果我们的系统太慢,可以采取多种措施。最明显的方法是关闭当前不需要的任何应用程序。这样做之所以有效,是因为它有助于释放计算机中的一些资源,例如CPU时间、RAM或视频内存。
这样,我们希望运行得更快的程序就能获得更多这些资源。在关闭不需要的应用程序时,我们甚至可能需要查看小程序、插件、扩展或其他看似无害的小型程序,因为它们运行也会占用资源。此外,关闭其他占用资源的元素,如浏览器标签页或文档编辑器中的打开文件,也会有所帮助。
但这只能解决部分问题,因为导致我们的设备或程序运行缓慢的原因还有很多。在接下来的视频中,我们将概述导致运行缓慢的各种原因。
我们将探究导致脚本缓慢、计算机缓慢或系统缓慢的根源。我们将为你提供工具,帮助你识别最常见的缓慢原因,并应用解决方案来提升整体性能。我们有很多内容要涵盖,但别担心,我们会引导你完成所有内容,并且我们会循序渐进。
到这些视频结束时,缓慢问题将很少再拖慢你的进度。好了,让我们开始吧。
本节课中,我们一起学习了性能问题的普遍性及其相对性。我们了解到,即使现代计算机资源丰富,不当的使用仍会导致性能瓶颈,例如资源耗尽。我们概述了初步的解决思路,如释放资源,并预告了后续课程将深入分析各类缓慢问题的具体原因与诊断方法。
073:为什么我的计算机运行缓慢?🐢


在本节课中,我们将探讨计算机运行缓慢的常见原因,并学习如何诊断和解决性能瓶颈问题。我们将了解计算机如何同时处理多个任务,以及当资源不足时,系统性能如何受到影响。
我们的计算机每秒执行数十亿条指令。每条指令完成一件小事,例如增加一个值、比较两个值或将一个值从一个地方移动到另一个地方。尽管如此,凭借每秒数十亿条指令,计算机在一秒钟内也能完成大量工作。
这使得我们的计算机似乎能够同时执行许多不同的事情。例如,你可以在浏览网页的同时,运行一个在后台播放你最喜欢的音乐的程序。即使你的计算机只有一个核心来执行这些应用程序,看起来也像是计算机在同时运行这两个程序。


那么,这背后是如何实现的呢?

多任务处理的原理

上一节我们提到了计算机强大的处理能力。本节中我们来看看,当运行多个程序时,计算机内部是如何工作的。


实际上,每个应用程序都获得一小部分CPU时间,然后轮到下一个应用程序。这种快速切换使得所有程序看起来都在同时运行。
大多数情况下,这种机制运行良好。但是,如果我们运行了太多应用程序,或者某个正在运行的应用程序需要的CPU时间超过了它获得的那一小部分,事情就可能变得异常缓慢。
识别性能瓶颈
既然我们了解了多任务处理的原理,那么当计算机变慢时,我们该如何应对呢?以下是解决速度缓慢问题的通用策略。
解决速度缓慢问题的通用策略是,识别导致我们的设备、脚本或系统运行缓慢的瓶颈。这个瓶颈可能是我们刚刚提到的CPU时间,也可能是从磁盘读取数据、等待网络传输数据、将数据从磁盘移动到RAM所花费的时间,或者是其他一些限制整体性能的资源。
通常,我们可以通过关闭同一台计算机上任何其他正在使用资源的程序来加快速度。

- 如果问题是你的程序需要更多CPU时间:你可以关闭当时不需要的其他正在运行的程序。
- 如果问题是磁盘空间不足:你可以卸载不使用的应用程序,或者删除、移动不需要保留在该磁盘上的数据。
- 如果问题是应用程序需要更多网络带宽:你可以尝试停止任何其他也在使用网络的进程,依此类推。
这种方法仅在问题源于太多进程试图使用同一资源时有效。如果我们已经关闭了所有不需要的程序,但计算机仍然很慢,我们就需要寻找其他可能的原因。
硬件限制与监控
如果关闭多余程序后问题依旧,那么问题可能出在哪里呢?本节我们来看看硬件本身是否已成为瓶颈。
如果我们正在使用的硬件根本不足以运行我们试图在其上运行的应用程序,那么在这些情况下,我们将不得不升级底层硬件。但为了真正提升性能,我们需要确保我们实际上是在改进瓶颈,而不是把钱浪费在不会被使用的新硬件上。
那么,我们如何判断需要更换哪一块硬件呢?
我们需要监控资源的使用情况,以了解哪一种资源正在被耗尽。这意味着该资源已被完全使用,程序因无法获得更多资源而被阻塞。是CPU、内存、磁盘I/O、网络连接还是显卡?
为了找出答案,我们将使用操作系统中的可用工具来监控每种资源的使用情况,然后找出哪个资源正在阻止我们的程序运行得更快。
我们已经讨论过在Linux系统上使用 top 工具。这个工具让我们可以看到当前哪些正在运行的进程占用了最多的CPU时间,如果按内存排序,可以看到哪些进程占用了最多的内存。它还显示了许多与计算机当前状态相关的负载信息,例如有多少进程正在运行,以及CPU时间或内存是如何被使用的。
在之前的视频中,我们还提到了其他几个程序,如 iotop 和 iftop,它们可以帮助我们查看当前哪些进程占用了最多的磁盘I/O或网络带宽。
在Mac OS上,系统自带一个名为“活动监视器”的工具,它让我们可以看到是什么占用了最多的CPU、内存、能源、磁盘或网络。

在Windows上,有几个名为“资源监视器”和“性能监视器”的系统工具,它们也让我们可以分析计算机上不同资源(包括CPU、内存、磁盘和网络)的使用情况。
因此,如果你想诊断是什么导致你的计算机运行缓慢,第一步总是打开这些工具之一,查看正在发生的情况,并尝试理解哪个资源是瓶颈以及原因,然后规划如何解决问题。



软件层面的优化
当然,并非所有的性能问题都能通过关闭应用程序或获得更好的硬件来解决。有时,我们需要弄清楚软件做错了什么,以及它把大部分时间花在哪里,以便理解如何让它运行得更快。
我们需要深入研究每个问题,以找到速度缓慢的根本原因。
在本节课中,我们一起学习了计算机运行缓慢的常见原因。我们了解了多任务处理的基本原理,学习了如何通过监控工具(如 top、活动监视器、资源监视器等)来识别CPU、内存、磁盘I/O或网络带宽等资源瓶颈。我们还讨论了通过关闭不必要的程序或升级硬件来解决瓶颈问题,并认识到有时需要深入分析软件本身来优化性能。下一节,我们将讨论一些帮助我们更好地理解底层运行情况的方法。
074:计算机如何使用资源 💻


在本节课中,我们将学习计算机如何管理其核心资源,如CPU、磁盘、内存和网络。我们将探讨这些资源如何相互作用,以及它们的速度差异如何影响系统性能。理解这些概念是优化计算机性能、消除瓶颈的关键。


在上一节视频中,我们指出了计算机如何受到不同资源(如CPU、磁盘、内存或网络)的限制。我们讨论了需要如何消除瓶颈,让计算机更好地利用其资源,从而提升系统性能。要做到这一点,我们需要理解每个组件如何与其他组件交互,以及各自的限制是什么。

具体来说,在考虑如何让事情运行得更快时,理解各个部件的不同速度至关重要。当一个应用程序访问某些数据时,检索这些数据所花费的时间将取决于数据所在的位置。



- 如果数据是当前正在函数中使用的变量,那么它将位于CPU的内部存储器中,我们的程序可以非常快速地检索到它。
- 如果数据与一个正在运行的程序相关,但可能不是当前正在执行的函数,那么它很可能在RAM(内存)中,我们的程序访问它仍然相当快。
- 如果数据在文件中,我们的程序需要从磁盘读取,这比从RAM读取要慢得多。
- 比从磁盘读取更糟的是从网络读取信息。在这种情况下,传输速度较低,并且我们还需要建立到另一端的连接才能使传输成为可能,这增加了获取数据所需的总时间。
因此,如果你有一个需要反复从网络读取数据的进程,你可能需要想办法看是否能只读取一次,将其存储在磁盘上,然后后续从磁盘读取。



类似地,如果你反复从磁盘读取文件,你可以看看是否可以将相同的信息直接放入进程内存中,避免每次都从磁盘加载。换句话说,你需要考虑是否可以创建一个缓存。
缓存以比原始形式更快的访问速度存储数据。在IT领域有大量的缓存示例。网络代理就是一种缓存形式,它存储代理后用户经常访问的网站、图片或视频,这样就不需要每次都从互联网下载。DNS服务通常为其解析的网站实现本地缓存,这样就不需要在每次有人询问其IP地址时都从互联网查询。

操作系统也为我们处理一些缓存工作。它试图在RAM中保留尽可能多的信息,以便我们可以快速访问。这包括经常被访问的文件或库的内容,即使它们当前并未被使用。我们说这些内容被缓存在内存中。
我们提到过,如果数据是当前正在运行的程序的一部分,它将在RAM中。但RAM是有限的。如果你同时运行足够多的程序,就会填满RAM并耗尽空间。
当RAM耗尽时会发生什么?首先,操作系统会从RAM中移除任何被缓存但并非严格必要的内容。如果在此之后仍然没有足够的RAM,操作系统会将当前未使用的内存部分放到硬盘上一个称为交换空间的区域。
从磁盘读写比从RAM读写要慢得多。因此,当应用程序请求被换出的内存时,将其加载回来需要一段时间。不同操作系统的交换实现方式各不相同,但概念始终相同:当前不需要的信息从RAM中移除并放到磁盘上,而当前需要的信息则放入RAM。

这是正常操作,大多数时候我们不会注意到它。但是,如果可用内存显著少于正在运行的应用程序所需,操作系统将不得不持续换出当前未使用的数据,以便将当前正在使用的数据移入RAM。
正如我们指出的,计算机可以在应用程序之间非常快速地切换,这意味着当前正在使用的数据也可能变化得非常快。
计算机将开始花费大量时间向磁盘写入以在RAM中腾出空间,然后又从磁盘读取以将其他内容放入RAM。这可能非常缓慢。


那么,如果你发现你的机器因为花费大量时间进行交换而变慢,你该怎么办?基本上有三个可能的原因,我们已经讨论了其中两个:

- 如果打开的应用程序太多,并且有些可以关闭,请关闭那些不需要的应用程序。
- 如果可用内存对于计算机的使用量来说太小,请为计算机添加更多RAM。
- 第三个原因是,某个正在运行的程序可能存在内存泄漏,导致它占用了所有可用内存。内存泄漏意味着不再需要的内存没有被释放。
我们将在课程后面详细讨论内存泄漏。现在,我们只需说,如果一个程序使用了大量内存,并且在我们重启该程序后这种情况停止,那很可能是因为内存泄漏。
接下来,我们将讨论更多导致计算机运行缓慢的不同原因,以及我们可以采取哪些措施来修复它们。

在本节课中,我们一起学习了计算机资源(CPU、内存、磁盘、网络)的层次结构及其访问速度的差异。我们了解了缓存的概念及其如何通过将数据存储在更快的介质中来提升性能。我们还探讨了当物理内存(RAM)不足时,操作系统如何使用交换空间来管理内存,以及这可能导致性能下降。最后,我们简要介绍了内存泄漏的概念及其对系统的影响。理解这些基本原理是诊断和解决系统性能问题的重要第一步。
075:可能导致计算机运行缓慢的原因 🔍


在本节课中,我们将学习识别和诊断导致计算机运行缓慢的多种常见原因。我们将探讨从启动问题到硬件故障等一系列潜在因素,并学习如何运用排除法来定位根本原因。
我们已经讨论过几种可能导致计算机变慢的情况,但实际原因远不止这些。在本视频中,我们将概述一些在IT工作中最常见的可能原因。
在开始之前,需要快速提醒一下:在尝试诊断计算机为何变慢时,我们应该使用之前提到的排除法。我们首先寻找最简单、最容易检查的解释。在排除一个可能的根本原因后,我们回到问题本身,提出下一个可能的原因进行检查。
因此,当试图找出导致计算机变慢的原因时,第一步是观察计算机在何时变慢。

分析变慢的时机
如果计算机在启动时变慢,这很可能表明有太多应用程序被配置为开机自启动。


在这种情况下,解决问题只需遍历自动启动的程序列表,并禁用任何非必需的程序即可。
如果计算机在正常运行数天后变得迟缓,并且重启后问题消失,这意味着有一个程序在运行时保持某种状态,从而导致计算机变慢。


例如,如果一个程序在内存中存储了一些数据,并且这些数据随时间增长而不删除旧值,就可能发生这种情况。
如果一个这样的程序持续运行多天,数据可能会增长到读取变得缓慢,甚至导致计算机内存耗尽。这几乎可以肯定是程序中的一个bug。此类问题的理想解决方案是修改代码,释放部分已使用的内存。
如果你无法访问代码,另一个选择是安排定期重启,以缓解程序变慢和计算机内存耗尽的问题。
一个类似的问题可能在使用应用程序很长时间后出现,并且重启无法解决,那就是应用程序处理的文件变得过大。因此,当程序需要读取这些文件时,速度会变得非常慢。
同样,这通常指向程序设计方式上的一个bug,因为它没有预料到文件会变得如此之大。这种情况下,最好的解决方案是修复这个bug。


但如果你无法修改程序代码,该怎么办?你可以尝试减小相关文件的大小。如果文件是日志文件,你可以使用像 logrotate 这样的程序来帮你完成。对于其他格式,你可能需要编写自己的工具来轮转内容。
分析影响范围
另一个可用于诊断的数据点是:这种情况是发生在应用程序的所有用户身上,还是仅影响一部分用户。
如果只有部分用户受到影响,我们需要了解这些计算机上是否有不同的配置可能触发了缓慢问题。
例如,许多操作系统包含一个跟踪计算机中文件的功能,以便快速轻松地搜索它们。在计算机上查找某些内容时,这个功能非常有用,但如果我们有大量文件且硬件性能不足,它可能会影响日常使用。
网络与硬件因素
我们之前提到过,从网络读取数据明显比从磁盘读取慢。办公室网络中的计算机通常使用通过网络挂载的文件系统,以便跨计算机共享文件。这通常工作正常,但如果程序在这种网络挂载的文件系统上进行大量读写操作,可能会使某些程序变得非常慢。


要解决此问题,我们需要确保程序用于读写大部分数据的目录是计算机本地的目录。

硬件故障也可能导致计算机变慢。如果你的硬盘出现错误,计算机可能仍能应用纠错来获取所需数据,但这会影响整体性能。一旦硬盘开始出现错误,数据开始丢失就只是时间问题。因此,值得密切关注它们。
为此,我们可以使用一些诊断硬盘或RAM问题的操作系统实用工具,检查是否存在任何可能导致问题的因素。



恶意软件的影响

另一个导致变慢的来源是恶意软件。我们当然希望计算机远离任何恶意软件,但即使恶意软件没有安装,我们也能感受到其影响。
例如,你可能遇到过一些网站,其内容或显示的广告中包含脚本,利用我们的处理器进行加密货币挖矿。恶意的浏览器扩展也属于此类。

如你所见,导致计算机运行缓慢的可能原因有很多。每当我们不得不解决此类问题时,都需要找出瓶颈所在,找出资源被耗尽的根本原因,然后采取适当的措施。
总结
本节课中,我们一起学习了诊断计算机运行缓慢的多种可能原因。我们了解了如何通过分析变慢的时机、影响范围以及检查网络、硬件和软件因素来定位问题。关键在于运用系统性的排除法,从最简单的可能性开始检查,逐步深入,直到找到并解决根本原因。
076:诊断与修复网站服务器运行缓慢问题 🐌

在本节课中,我们将学习如何诊断和修复一个运行缓慢的网站服务器。我们将从一个用户报告出发,使用工具量化问题,分析系统资源,并最终通过调整进程优先级和优化任务执行顺序来解决问题。

概述

用户报告我们公司的一台网站服务器响应缓慢,我们需要找出原因并解决它。

第一步:确认问题
首先,我们导航到网站并加载页面以确认问题。

页面可以加载,但速度似乎有些慢。然而,仅凭主观感受难以准确衡量。
第二步:使用工具量化性能
为了准确测量网站速度,我们使用一个名为 AB(Apache Benchmark)的工具。这个工具可以发送大量请求并汇总结果,帮助我们判断网站性能是否符合预期。
我们将运行以下命令,对网站 example.com 发起500次请求以获取平均响应时间:
ab -n 500 http://example.com/
这个命令会发起500次请求。ab 工具还有很多其他选项,例如可以设置并发请求数或超时时间。我们选择500次请求是为了获得一个可靠的平均时间。
测试完成后,我们可以查看数据来判断服务器是否真的缓慢。
测试结果显示,每个请求的平均时间为155毫秒。虽然这不是一个巨大的数字,但对于一个简单的网站来说,这显然超出了预期。这表明网络服务器确实存在问题,需要进一步调查。
第三步:连接到服务器并检查系统状态
现在,我们连接到网络服务器,查看系统内部情况。
我们首先使用 top 命令查看是否有可疑进程。
top
在 top 的输出中,我们看到许多 FFmpeg 进程正在运行,并且几乎占用了所有可用的CPU资源。系统负载平均值显示为30,这绝对不正常。
在Linux系统中,负载平均值表示在一分钟内处理器繁忙的程度。数值1表示处理器在整个一分钟内都处于繁忙状态。这台计算机有两个处理器,因此任何高于2的数值都意味着系统过载。在每分钟内,等待处理器时间的进程数量超过了处理器能够处理的数量。
FFmpeg 程序用于视频转码,即将文件从一种视频格式转换为另一种。这是一个CPU密集型进程,很可能是导致我们服务器过载的元凶。
第四步:尝试调整进程优先级
既然找到了可能的元凶,我们可以尝试调整这些进程的优先级,让Web服务器获得更高的处理权。
在Linux中,进程优先级数值越低,优先级越高。通常的优先级范围是0到19。默认情况下,进程以优先级0启动,但我们可以使用 nice 和 renice 命令来改变它。nice 用于以不同优先级启动新进程,renice 用于更改已运行进程的优先级。
我们退出 top(按 q 键),然后尝试降低所有 FFmpeg 进程的优先级。
我们可以手动为每个进程ID运行 renice,但这既容易出错又非常枯燥。相反,我们可以使用一行简单的Shell脚本来完成这个任务。
以下是具体步骤:
- 使用
pidof命令获取所有名为ffmpeg的进程ID。 - 使用
for循环遍历这些进程ID。 - 对每个进程ID执行
renice 19命令,将其优先级设置为最低(19)。
对应的Shell命令如下:
for p in $(pidof ffmpeg); do renice 19 $p; done
执行后,我们看到这些进程的优先级已被更新。
第五步:验证调整优先级的效果
我们再次运行基准测试工具,检查调整优先级是否带来了改善。
ab -n 500 http://example.com/
等待500次请求完成,查看新的平均请求时间。
这次的平均时间是153毫秒,与之前的155毫秒相比几乎没有变化。显然,操作系统仍然给了这些 FFmpeg 进程过多的处理器时间,我们的网站依然缓慢。
第六步:深入调查并修改任务执行方式
既然调整优先级没有帮助,我们需要采取其他措施。这些转码进程是CPU密集型的,并行运行它们会使计算机过载。因此,我们可以尝试修改触发这些进程的机制,让它们顺序执行(一个接一个),而不是同时运行。
为此,我们首先需要找出这些进程是如何启动的。
-
使用
ps ax命令查看计算机上所有正在运行的进程,并通过less进行滚动查看。ps ax | less -
在
less中使用/键搜索ffmpeg进程。我们看到有一批ffmpeg进程正在将视频从WebM格式转换为MP4格式。
我们不知道这些视频在硬盘上的具体位置。可以尝试使用 locate 命令查找它们。
- 退出
less(按q键)。 - 运行
locate static/001.webm。我们发现static目录位于/server/deploy/v目录下。
我们切换到该目录并查看其中的文件。
cd /server/deploy/v/static
目录里有很多文件。我们可以逐个检查是否有文件包含调用 ffmpeg 的命令,但这听起来工作量很大。相反,我们使用 grep 来快速检查。
grep -r “ffmpeg” .
我们看到在 deploy.sh 文件中提到了 ffmpeg。让我们查看这个文件。
由于我们是远程连接到服务器,无法使用图形化编辑器。我们需要使用命令行编辑器,这里我们使用 vim。
vim deploy.sh
我们看到,这个脚本使用一个名为 daemonize 的工具并行启动了 ffmpeg 进程,使每个程序都像守护进程一样单独运行。如果只需要转换少量视频,这可能没问题。但为 static 目录中的每个视频都启动一个单独的进程,这导致我们的服务器过载。
因此,我们希望修改脚本,使其一次只运行一个视频转换进程。我们只需删除 daemonize 部分,保留调用 ffmpeg 的核心命令。然后保存并退出 vim。
第七步:停止现有进程并顺序重启
我们已经修改了文件,但这不会影响已经运行的进程。我们希望停止这些进程,但不是完全终止它们,因为这样做会导致当前正在转换的视频不完整。
我们使用 killall 命令配合 --stop 标志,这会发送一个停止信号,但不会完全杀死进程。
killall --stop ffmpeg
现在,我们希望让这些进程一个接一个地运行。如何自动化这个过程呢?
我们可以使用之前用过的 for 循环和 pidof 命令来遍历进程列表。在循环内部,我们想向一个进程发送继续(CONT)信号,然后等待该进程完成。
遗憾的是,没有直接等待进程结束的命令。但我们可以创建一个 while 循环,只要进程存在,就不断向其发送 CONT 信号(这通常会失败,因为进程已停止,但我们可以用其他方式检查)。更简单的方法是,在循环内检查进程是否存在,如果存在则等待一秒再检查。
一个可行的方案是:先向一个进程发送 CONT 信号让其继续,然后使用 wait 命令或循环检查该进程ID是否还存在,直到它完成后再处理下一个进程。但请注意,在交互式Shell中直接等待后台进程需要一些技巧。一种更直接的方法可能是按顺序手动或通过脚本重新启动任务,但原课程中展示的自动化方法较为复杂,其核心思路是控制任务执行流。
假设我们采取更简单的策略:确保修改后的 deploy.sh 脚本不会并行启动新进程,然后逐个恢复被停止的旧进程,并确保它们不会同时运行。
第八步:验证最终效果
在采取了上述措施(修改脚本、停止并行进程、改为顺序处理)之后,我们的服务器现在一次只运行一个 FFmpeg 进程。
让我们再次运行基准测试。
ab -n 500 http://example.com/
现在的平均响应时间是33毫秒,这比之前的155毫秒低得多。我们成功让Web服务器再次能够及时响应请求了。


总结
在本节课中,我们一起学习了诊断和修复网站服务器速度缓慢的完整流程。
- 确认与量化:首先使用
ab工具确认并量化性能问题。 - 系统诊断:使用
top命令发现导致CPU过载的元凶进程(FFmpeg)。 - 初步尝试:尝试使用
renice调整进程优先级,但效果不佳。 - 深入根因:通过
ps、locate、grep和vim找到触发并行任务的脚本文件。 - 根本解决:修改脚本,将并行执行改为顺序执行,并使用
killall --stop和进程管理技巧控制现有任务,最终成功将平均响应时间从155毫秒降低到33毫秒。
我们提到了几种当无法直接修改代码时可以采取的应对方法,例如重新调整进程优先级或让它们顺序执行。在接下来的视频中,我们将讨论如何通过修复代码来进一步提升性能。但在那之前,有一份阅读材料将汇总我们提到的所有资源,随后还有一个快速测验来检查大家是否理解了所有内容。
077:编写高效代码 🚀


在本节课中,我们将学习如何编写高效的代码。作为IT专家或系统管理员,您可能需要编写脚本来自动化任务。一段代码可能最初只是一个执行单一任务的简单脚本,但最终可能发展成一个处理多种不同任务的复杂程序。无论代码的规模和复杂性如何,我们通常都希望其性能良好。在本节及后续视频中,我们将讨论一些使代码更高效以及如何找出导致其运行缓慢的问题的方法。
概述:效率与可读性的平衡 ⚖️
需要牢记的一个重要原则是,我们应始终从编写清晰、功能正确的代码开始。只有在意识到代码速度不够快时,才尝试对其进行优化。


如果花费10分钟编写一个运行时间为5秒的脚本,与花费20分钟编写一个功能相同但只需3秒的脚本,这有区别吗?这完全取决于您运行脚本的频率。如果每天运行一次,那2秒的差异肯定不值得额外10分钟的工作量。但是,如果您要在网络中的500台计算机上运行同一个脚本,这微小的差异意味着整个脚本的运行时间将减少15分钟。因此,总体上您是节省了时间。
当然,很难预先知道脚本的运行速度以及优化它需要多长时间。但作为一般规则,我们的首要目标是编写可读、易于维护和易于理解的代码,因为这有助于我们编写错误更少的代码。如果确实存在运行极其缓慢的部分,那么修复它是有意义的,特别是当脚本执行频率足够高,优化所节省的时间将超过您优化所花费的时间时。但请记住,试图从脚本中榨取每一秒可能并不值得您投入时间。
高效代码的核心原则 🎯
明确了上述原则后,让我们深入探讨如何使代码更高效。第一步是要明白,我们无法真正让计算机运行得更快。如果我们希望代码更快完成,我们需要让计算机做更少的工作。
为了实现这一点,我们必须避免执行不必要的工作。有多种方法可以实现,最常见的包括:
- 缓存计算结果:存储已计算的数据,避免重复计算。
- 选择合适的数据结构:针对问题选择最有效的数据结构。
- 优化代码结构:重组代码,使计算机在等待磁盘或网络等慢速源的信息时能保持忙碌状态。

识别性能瓶颈:使用性能分析器 🔍

为了确定需要解决哪些导致缓慢的根源,我们必须找出代码大部分时间花在哪里。有一类称为“性能分析器”的工具可以帮助我们做到这一点。
性能分析器是一种测量代码资源使用情况的工具,它能让我们更好地理解代码的运行状况。具体来说,它们帮助我们了解内存是如何分配的以及时间是如何消耗的。由于性能分析器的工作原理,它们是针对每种编程语言特定的。例如,我们会使用GProf来分析C程序,但使用cProfile模块来分析Python程序。
使用这类工具,我们可以看到程序调用了哪些函数、每个函数被调用了多少次,以及程序在每个函数上花费了多少时间。通过这种方式,我们可以发现,例如,程序调用某个函数的次数超出了我们的预期,或者我们认为很快的函数实际上很慢。
修复代码:避免“昂贵”操作 💡

为了修复代码,我们可能需要重构它,以避免重复执行“昂贵”的操作。这里的“昂贵”指的是什么?昂贵操作是指那些需要很长时间才能完成的操作。
昂贵的操作包括:
- 解析文件
- 通过网络读取数据
- 遍历整个列表
那么,我们如何修改代码以避免这些昂贵的操作呢?我们将在接下来的视频中讨论几种策略。

总结 📝
本节课中,我们一起学习了编写高效代码的基本理念。我们首先强调了在代码可读性和运行效率之间取得平衡的重要性,优化应建立在代码功能正确且清晰的基础上。接着,我们探讨了高效代码的核心是让计算机做更少的工作,并介绍了缓存、选择合适数据结构和优化代码流程等通用方法。最后,我们了解了如何使用性能分析器来精确识别代码中的性能瓶颈,特别是那些“昂贵”的操作,为后续的具体优化策略奠定了基础。记住,优化是一个有目的的过程,应针对实际瓶颈进行,而非盲目追求极致的速度。
078:使用合适的数据结构 🧱


在本节课中,我们将学习如何通过选择合适的数据结构来编写高效的Python脚本。理解不同数据结构的性能特点,可以帮助我们避免不必要的、代价高昂的操作。
概述
理解我们可用的数据结构有助于避免不必要的昂贵操作,并创建高效的脚本。特别是,我们需要理解这些结构在不同条件下的性能。在Python入门课程中,你已经了解了Python中一系列不同的数据结构,如列表、元组、字典和集合。它们各有其用途、优点和缺点。
列表与字典快速回顾
让我们快速回顾一下列表和字典。


列表

列表是元素的序列。我们可以添加、删除或修改其中的元素,并且可以遍历整个列表来对每个元素进行操作。
my_list = [1, 2, 3]
my_list.append(4) # 在末尾添加元素
不同的编程语言对它们的称呼不同。这种结构在Java中称为ArrayList,在C++中称为Vector,在Ruby中称为Array,在Go中称为Slice。
所有这些名称都指代同一种数据结构:在末尾添加或删除元素很快,但在中间添加或删除元素可能很慢,因为所有后续元素都需要重新定位。访问列表中特定位置的元素很快,但在未知位置查找元素需要遍历整个列表。如果列表很长,这可能非常慢。

字典

字典存储键值对。我们通过将值与键关联来添加数据,然后通过查找特定键来检索值。

my_dict = {'key1': 'value1', 'key2': 'value2'}
value = my_dict['key1'] # 通过键查找值
它们在Java中称为HashMap,在C++中称为unordered_map,在Ruby中称为Hash,在Go中称为Map。
名称中的“Map”部分源于我们如何在键和值之间创建映射关系。“Hash”部分则是因为为了使结构高效,内部使用了哈希函数来决定元素的存储方式。
这种结构的主要特点是查找键的速度非常快。一旦我们将数据存储在字典中,只需一次操作就能找到与键关联的值。如果存储在列表中,则需要遍历列表。

如何选择数据结构

因此,作为一个经验法则:
- 如果需要按位置访问元素,或者总是要遍历所有元素,请使用列表存储它们。
例如,这可以是网络中的所有计算机列表、公司中的所有员工列表或当前销售的所有产品列表。

- 如果需要使用键来查找元素,请使用字典。
例如,这可以是与用户关联的数据(我们使用其用户名查找)、与计算机关联的IP(使用主机名)或与产品关联的数据(使用内部产品代码)。
当我们需要进行大量此类查找操作时,创建字典并使用它来获取数据将比遍历列表以找到所需内容花费的时间少得多。
但是,如果我们只打算在字典中查找一个值,那么创建字典并用数据填充它就没有意义。在这种情况下,我们浪费了创建结构的时间,而本可以直接遍历列表来获取我们寻找的元素。
其他注意事项
我们可能需要三思的另一件事是在内存中创建已有结构的副本。如果这些结构很大,创建这些副本的代价可能相当高。因此,我们应该仔细检查副本是否真的必要。
总结
在本节课中,我们一起学习了如何根据需求选择合适的数据结构。我们回顾了列表和字典的特性、性能差异以及适用场景。记住,选择正确的数据结构是编写高效脚本的关键一步。列表适合顺序访问和遍历,而字典则擅长基于键的快速查找。同时,要避免不必要的大数据结构复制操作。
现在我们对何时使用每种数据结构以及应避免哪些操作有了更好的理解,接下来可以研究如何处理代价高昂的循环。这将在我们的下一个视频中介绍。
079:避免低效循环 🚫


在本节课中,我们将学习如何识别和避免编写低效的循环。循环是编程中强大的工具,但使用不当会显著降低程序性能。我们将通过具体示例,探讨如何优化循环内的操作,使代码运行得更快、更高效。
循环让计算机能够重复执行任务。这是一个非常有用的工具,能帮助我们避免重复性工作。


但我们需要谨慎使用循环。特别是,我们需要仔细考虑在循环内部执行哪些操作,并尽可能避免执行代价高昂的操作。如果在循环内执行代价高昂的操作,那么执行该操作所需的时间将乘以循环重复的次数。
优化循环内的数据访问 📂


上一节我们提到了循环内的高成本操作,本节中我们来看看一个具体的例子。
假设你正在编写一个脚本,向公司所有员工发送电子邮件,要求他们验证其紧急联系人信息是否仍然有效。


为了发送这些邮件,你会使用一个循环,为每位员工发送一封邮件。在邮件正文中,你将包含当前的紧急联系人数据。
关键在于如何在循环内访问这些数据。如果数据存储在文件中,你的脚本需要解析文件来获取数据。
如果脚本为每个用户都读取整个文件,你将不必要地浪费大量时间反复解析文件。相反,你可以在循环外解析文件。


将信息放入字典中,然后在循环内使用该字典来检索数据。
优化循环的通用原则 🔧
因此,每当代码中有循环时,务必检查正在执行的操作,看看是否有操作可以移出循环,只执行一次。
以下是优化循环时可以遵循的几个原则:
- 避免为每个元素都进行网络调用,改为在循环前进行一次调用。
- 避免为每个元素都从磁盘读取,改为在循环前读取全部内容。
- 即使循环内执行的操作成本不高,但如果我们要遍历一个包含1000个元素的列表,而只需要其中的5个,那么我们就在不需要的元素上浪费了时间。因此,请确保你迭代的元素列表仅包含真正需要的部分。
一个具体的优化案例:显示最近登录用户 💻


让我们看另一个例子。假设你正在运行一个内部网站。作为网站显示信息的一部分,它会显示最近登录的五个用户列表。
在代码中,程序会保存一个自上次启动以来所有登录用户的列表。


当程序需要显示最近的五个用户时,它会遍历整个列表,找出其中最近的五个。

这会浪费大量时间。如果服务已经运行了一段时间,遍历整个列表可能需要很长时间。

相反,你可以修改服务,将用户访问信息存储在日志文件中(必要时可读取),并且只在内存中保留最近五次登录。这样,每当有新用户登录时,列表中最旧的条目就会被丢弃,并添加一个新条目。
通过这种方式,脚本在每次需要显示最近五个用户时,就无需遍历整个列表。
及时跳出循环 🛑

关于循环,另一件要记住的事情是:一旦找到你要找的内容,就跳出循环。
在Python中,我们使用关键字 break 来实现这一点。跳出循环意味着一旦找到我们要找的数据,脚本就可以继续执行后续代码。

当然,如果数据在列表末尾,那么我们无论如何都需要遍历整个循环。但当数据在列表开头而非末尾时,让代码提前跳出循环以加快脚本速度是有意义的。
假设你正在编写一个脚本,检查给定的用户名是否在授权实体列表中,如果在,则授予其对特定资源的某些访问权限。你可以使用 for 循环遍历实体列表,当找到用户名时,跳出循环并继续执行脚本的其余部分。
根据问题规模选择方案 ⚖️
最后要记住的一点是,一个问题的正确解决方案可能不适用于另一个问题。
假设你的服务总共有20个用户。在这种情况下,每当你想检查某些内容时,遍历这个列表是可以接受的。它足够短,不需要进行任何特殊的优化。
但如果你的服务有超过1000个用户,你会希望避免遍历该列表,除非绝对必要。
而如果服务有数十万用户,遍历该列表甚至是不可能的。
总结
本节课中,我们一起学习了如何避免编写低效的循环。关键点包括:将高成本操作移出循环、仅遍历必要的元素、在找到目标后及时使用 break 跳出循环,以及根据数据规模选择合适的解决方案。记住这些原则,可以帮助你编写出运行更快、资源利用更高效的Python代码。
080:保持局部结果 🚀
概述
在本节课中,我们将学习如何通过缓存(Cache)技术来优化Python脚本的性能,避免重复执行耗时的操作,从而让程序运行得更快。
上一节我们介绍了如何避免在循环内部执行昂贵操作。本节中,我们来看看当这些操作即使放在循环外部仍然耗时很长时,我们该如何处理。
缓存的基本概念
缓存是一种将数据存储在比原始形式访问速度更快的介质中的方法。如果我们需要解析一个大文件,但只从中提取少量关键信息,就可以创建一个缓存来仅存储这些信息。或者,如果我们通过网络获取信息,可以保留文件的本地副本,以避免反复下载。
创建缓存可以节省时间并加速程序,但有时正确实现缓存可能比较复杂。
缓存更新的策略
我们需要考虑缓存更新的频率,以及缓存数据过时后会发生什么情况。
以下是两种典型的数据场景及其缓存策略:
-
适合长期缓存的场景:对于寻找长期统计数据的情况,我们可以每天生成一次缓存,这通常不会造成问题。例如:
- 过去一个月内整个计算机群组的内存使用情况。
- 公司每个部门的员工数量。
- 上一季度每种产品的销售数量。
-
需要实时或短期缓存的场景:如果我们查看的数据其当前值至关重要,那么要么不能使用缓存,要么缓存的生命周期必须非常短。例如:
- 监控计算机健康状况,以便在指标超过阈值时发出警报。
- 检查库存水平以确定产品是否有足够库存可销售。
- 尝试创建新用户时,检查网络中是否已存在该用户名。
缓存有效性的检查
有时,我们可以添加检查来验证是否需要重新计算缓存。
例如,如果我们的缓存基于一个文件,我们可以在计算缓存时存储该文件的修改日期。然后,只有当文件的修改日期比我们存储的日期更新时,才重新计算缓存。
如果我们没有办法检查缓存是否过时,就需要在程序中添加逻辑来做出合理的决策。为此,我们需要考虑以下因素:
- 数据预期变化的频率。
- 使用最新数据的必要性。
- 我们运行程序的频率。
综合考虑这些因素后,我们可能决定缓存需要每天、每小时甚至每分钟重新创建一次。
是的,即使每分钟一次也可能有意义,如果你的脚本每分钟可能执行多次,并且需要执行一个可以缓存的昂贵操作。这样,每分钟内只有第一次执行会花费时间在这个操作上,其余的调用都会非常快,而且缓存的数据过时时间永远不会超过一分钟。
缓存的简单实现
请记住,缓存并不总是需要复杂的结构来存储大量信息并附带复杂的超时逻辑。
有时,缓存可以简单到使用一个变量来存储临时结果,而不是每次需要时都重新计算这个结果。
例如,假设你正在生成一份报告,打印网络中每个不同组的用户数量。现在,其中一些组可能包含其他组,而有些组甚至可能是多个组的成员。
例如,“Java发布工程师”组可能是“发布工程师”组和“Java开发人员”组的成员。我们如何避免在用户出现在多个组时重复计算他们?
我们可以使用一个字典,以组名作为键,用户数量作为值。这样,我们只需要计算一次组的成员数量,之后就直接使用字典中的值。
# 示例:使用字典缓存组的用户数量
group_user_cache = {}
def get_user_count(group_name):
if group_name not in group_user_cache:
# 这是一个昂贵的操作:计算组的成员数
user_count = calculate_expensive_user_count(group_name)
group_user_cache[group_name] = user_count
return group_user_cache[group_name]

总结
本节课中我们一起学习了性能优化中缓存策略的应用。总而言之,你需要寻找能够避免执行昂贵操作的策略。
首先,检查这些操作是否完全必要。如果必要,看看是否可以存储中间结果,以避免不必要地重复执行这些昂贵操作。

接下来,我们将通过一个实际例子,研究如何处理一些运行速度低于预期的代码。
081:识别并优化低效循环 🐌

在本节课中,我们将学习如何诊断一个运行缓慢的脚本,并使用性能分析工具找出瓶颈。我们将重点关注一个因循环效率低下而导致速度变慢的邮件发送脚本,并学习如何通过重构代码来优化其性能。
还记得那个在处理日期时遇到问题的会议提醒脚本吗?开发人员一直在改进它,现在它可以在问候语中插入收件人姓名,发送个性化的电子邮件。
这很酷,但不幸的是,这似乎让应用程序变得相当慢。开发人员正在请求我们帮助找出如何让程序运行得更快。
所以让我们开始工作。首先,我们需要复现问题,并弄清楚在这种情况下“慢”意味着什么。一位用户告诉我们,当收件人列表很长时,问题就会显现。
为了避免在测试此问题时打扰我们的同事,我们将把提醒邮件发送到我们在邮件服务器中创建的一批测试用户。
你可能还记得,该应用程序有两个部分:一个弹出窗口用于输入提醒数据的Shell脚本,以及一个准备并发送电子邮件的Python脚本。速度慢的部分是发送电子邮件。因此,我们完全不会与弹出窗口交互,而是直接将所需的参数传递给Python脚本。
我们将使用 time 命令来测量脚本速度。首先,我们只用一个测试用户调用它,看看需要多长时间。

当我们调用 time 时,它会运行我们传递给它的命令,并打印执行该命令所花费的时间。


有三个不同的值:real、user 和 sys。
real是执行命令所花费的实际时间。这个值有时被称为“挂钟时间”,因为它是挂在墙上的时钟所测量的时间,无论计算机在做什么。user是在用户空间执行操作所花费的时间。sys是执行系统级操作所花费的时间。

user 和 sys 的值加起来不一定等于 real 的值,因为计算机可能正忙于其他进程。
好的,我们在这里看到了什么?我们的脚本发送电子邮件花了 0.129秒。这不算多,但我们只向一个用户发送了消息。让我们用九个测试用户再试一次。
这次发送电子邮件花了 0.296秒。这仍然不算多,但看起来随着电子邮件列表变长,花费的时间确实更长了。
好的,是时候尝试改进它了。我们如何找出代码的问题所在?

我们总是可以查看代码,看看是否能找到任何可以改进的昂贵操作。但在这种情况下,我们希望使用性能分析器来获取一些关于正在发生什么的数据。
让我们试试看。Python有许多不同的性能分析器,适用于不同的用例。这里我们将使用一个叫做 cProfile 的分析器。
我们将使用 -f 标志告诉它使用 callgrind 文件格式,并使用 -o 标志告诉它将输出存储在 profile.out 文件中。
python -m cProfile -f callgrind -o profile.out ./send_reminders.py


好的,这生成了一个我们可以用任何支持 callgrind 格式的工具打开的文件。我们将使用 KCacheGrind 来查看内容,这是一个用于查看这些文件的图形界面。
这个程序有很多信息,所以如果你需要一段时间才能理解,请不要害怕。就像许多其他事情一样,自己动手练习和摸索将帮助你熟悉这里所有不同事物的含义。


让我们看看现在需要的信息。在右下角,我们看到了一个调用图,它告诉我们 main 函数调用了 send_message 函数一次。这个函数又分别调用了 message_template 函数、get_name 函数和 send_message 函数各9次。该图还告诉我们每次调用花费了多少微秒。
我们可以看到,大部分时间都花在了 get_name 函数上。这很可能就是我们应该优化的那个函数。让我们用编辑器看看这个函数在做什么。

我们看到 get_name 函数打开一个CSV文件,然后遍历整个文件,检查行中的第一个字段是否与电子邮件名称匹配。当匹配时,它设置 name 变量的值。
这个函数有几个问题:
- 首先,一旦它在列表中找到元素,就应该立即跳出循环。目前,即使电子邮件在第一行就被找到,它仍然会遍历整个文件。
- 但即使我们修复了这个问题,它仍然会为每个电子邮件地址打开并读取文件。如果文件有很多行,这会变得非常慢。
那么,我们怎样才能让它变得更好呢?我们可以读取文件一次,将我们关心的值存储在一个字典中,然后使用该字典进行查找。
让我们开始做吧。我们将更改 get_name 函数,将其转换为 read_names 函数,该函数将处理CSV文件并将我们想要的值存储在 names 字典中。
对于每一行,我们将电子邮件存储为键,姓名存储为值。并且,我们不再返回一个姓名,而是返回整个字典。
好的,我们有了一个将所需数据存储在字典中的 read_names 函数。我们现在需要更改在 send_message 函数中调用它的方式。
我们看到 get_name 函数每个电子邮件被调用一次。为了应用我们的更改,我们应该在 for 循环之前调用 read_names 函数,这样我们只做一次。然后,我们不再调用 get_name,而是直接从字典中获取值。


好的,我们已经做了更改。让我们保存文件并再次分析我们的脚本,看看我们是否设法让它变得更快。
😊


现在图表看起来不同了,因为我们已经改变了代码的行为。看看 read_names 函数现在只占用了少得多的时间。另一方面,我们看到 message_template 现在是占用时间最多的部分。所以,如果我们想继续让脚本更快,那就是我们下一步要看的地方。
在本节课中,我们一起学习了如何使用 time 命令来检查程序执行所需的时间。然后,我们看到了如何结合使用性能分析器和分析结果可视化工具来找出代码大部分时间花在哪里。最后,我们通过将信息存储在字典中然后访问字典,而不是反复执行昂贵的循环,来更改了我们的代码。接下来,有一篇阅读材料提供了关于性能分析的更多信息,之后还有一个练习测验来检查你是否理解了所有这些内容。
082:并行操作 🚀


在本节课中,我们将要学习如何通过并行操作来提升脚本的执行效率。我们将探讨操作系统如何处理多进程,以及如何利用多进程、多线程等技术来让计算机在等待慢速I/O操作时执行其他任务,从而避免CPU空闲。
我们已经多次提到,在典型的脚本中,从磁盘读取信息或通过网络传输信息是一种缓慢的操作。在此操作进行期间,不会发生其他任何事情。脚本会被阻塞,等待输入或输出,而CPU则处于空闲状态。

一种改进方法是进行并行操作。这样,当计算机在等待这些缓慢的I/O操作时,其他工作可以同时进行。关键部分在于如何划分任务,以确保最终得到相同的结果。实际上,计算机科学中有一个完整的领域——并发,专门研究如何编写执行并行操作的程序。我们不会深入太多细节,但会简要概述你可以做的事情。


首先,我们需要了解操作系统已经为我们做了什么。操作系统负责处理在我们计算机上运行的众多进程。
如果一台计算机拥有多个核心,操作系统可以决定哪些进程在哪个核心上执行。
无论核心之间如何分配,所有这些进程都将并行执行。每个进程都有自己的内存分配,并进行自己的I/O调用。操作系统将决定每个进程获得多少CPU时间,并根据需要在它们之间切换。



因此,运行并行操作的一个非常简单的方法,就是将任务拆分到不同的进程中,多次调用你的脚本,每次使用不同的输入集,然后让操作系统处理并发。


例如,假设你想要收集网络中所有计算机的当前负载和内存使用情况的统计数据。

你可以编写一个脚本,连接到列表中的每台计算机并获取统计数据。


每次连接都需要一段时间才能完成。


因此,脚本的总运行时间将是所有这些连接所花费时间的总和。

相反,你可以将计算机列表分成更小的组,并利用操作系统多次调用脚本,每组调用一次。
这样,到不同计算机的连接可以并行启动,从而最大限度地减少CPU无所事事的时间。
这非常容易实现,并且对于许多脚本来说,这将是正确的选择。

另一件容易做的事情是,在计算机上运行的不同工作负载之间保持良好的平衡。如果你有一个进程使用大量CPU,而另一个进程使用大量网络I/O,还有一个进程使用大量磁盘I/O,那么它们都可以并行运行而互不干扰。
当使用操作系统将工作拆分为进程时,这些进程不共享任何内存。有时,我们可能需要一些共享数据。在这种情况下,我们会使用线程。线程允许我们在一个进程内运行并行任务。
这使得线程可以与同一进程中的其他线程共享部分内存。由于这不是由操作系统处理的,我们需要修改代码来创建和处理这些线程。我们需要研究我们使用的编程语言如何实现线程。在Python中,我们可以使用 threading 或 asyncio 模块来实现。这些模块让我们可以指定代码的哪些部分要在单独的线程中运行,或作为单独的异步事件运行,以及最终如何合并每个部分的结果。
我们不会在此详细介绍如何操作,但会在下一份阅读材料中链接更多相关信息。需要注意的一点是,根据你所使用语言的实际线程实现,所有线程可能会在同一个CPU处理器上执行。在这种情况下,如果你想使用更多处理器,就需要将代码拆分为完全独立的进程。
如果你的脚本主要只是在等待输入或输出(也称为I/O密集型),那么它是在一个还是八个处理器上执行可能并不重要。但你可能进行并行操作是因为你正在使用所有可用的CPU时间。换句话说,你的脚本是CPU密集型的。在这种情况下,你肯定希望将执行拆分到多个处理器上。
然而,存在一个临界点,增加更多并行进程意味着事情会变得更慢,而不是更快。如果我们试图从磁盘读取大量文件并进行过多的并行操作,磁盘最终可能会花费更多时间在从一个位置移动到另一个位置上,而不是实际检索数据。或者,如果我们进行大量使用大量CPU的操作,操作系统可能会花费更多时间在它们之间切换,而不是在我们试图进行的计算中取得实际进展。
因此,在进行并行操作时,我们需要找到同时操作的正确平衡点,让我们的计算机保持忙碌,同时又不使系统资源匮乏。
我最近感受到了应用并发带来的好处。当时我正在迁移以某种格式存储的数据,需要将其存储为另一种格式。有大量GB级的数据需要迁移。所以,我当然不会手动完成。我脚本的第一个版本平均每迁移1GB数据需要一小时。这比我预期的要慢得多,所以我决定花更多时间调整代码以加快迁移速度。
我重新组织了逻辑,为每个文件使用一个单独的线程,这减少了处理文件的总时间,因为它现在不再是线性过程了。然后,为了让它更快,我将工作拆分到不同的机器上,每台机器运行一堆线程。在所有这些重新安排以利用我拥有的资源之后,我将时间降低到了每GB三分钟。
哇,我们可能学到了很多关于“慢”的知识,但我们确实进展很快。其中一些概念可能感觉有点复杂,这完全正常。慢慢来。每个人学习“慢”的速度都不一样。
接下来,我们将讨论随着系统变得更加复杂,我们可以采取哪些不同的方法来处理它们。
总结
本节课中,我们一起学习了并行操作的基本概念。我们了解到,通过将任务拆分到不同的进程或线程中,可以让计算机在等待慢速I/O操作时执行其他工作,从而提升效率。我们探讨了操作系统如何管理多进程,以及如何利用多核CPU。同时,我们也认识到并行操作需要找到平衡点,过多的并行任务反而可能导致性能下降。最后,通过一个数据迁移的实例,我们看到了合理应用并发技术可以带来的巨大性能提升。
083:系统复杂度增长与架构演进 🚀
在本节课中,我们将探讨一个核心概念:随着系统规模和复杂度的增长,其架构和解决方案也需要相应演进。我们将通过一个“秘密圣诞老人”服务的发展历程,来理解为何适用于小规模问题的方案,在大规模场景下可能不再适用,以及如何逐步调整技术栈以应对挑战。
正如我们在之前的视频中指出,一个对某个问题有效的解决方案,可能并不适合另一个不同的问题。随着系统变得更加复杂且使用量增长,曾经运行良好的方案可能不再适用。
假设你正在编写一个“秘密圣诞老人”脚本,每个人会随机分配给另一人赠送秘密礼物。该脚本随机选择配对,然后向礼物赠送者发送电子邮件,告知他们需要为谁购买礼物。如果仅为同一楼层的同事运行此服务,你可能只需将姓名和电子邮件列表存储在CSV文件中。文件将足够小,解析它所花费的时间可以忽略不计。
然而,如果这个脚本发展成一个处理整个公司员工的大型项目,并且公司持续招聘更多人员,那么在某些时候,解析文件将开始耗费大量时间。此时,你可能需要考虑使用不同的技术。
例如,你可以决定将数据存储在SQLite文件中。这是一个轻量级数据库系统,允许你查询文件中存储的信息,而无需运行数据库服务器。使用SQLite处理数据对于在公司内分配秘密圣诞老人可能完全可行。
但是,想象一下你不断为该服务添加功能:它现在包含了创建愿望清单的方式、一个推荐可能礼物的机器学习算法,以及一个记录每次赠送礼物历史的追踪器。由于公司员工非常喜爱这个程序,你已将其做成一个可供任何人使用的外部服务。
将所有数据保存在一个文件中会变得太慢。因此,你需要转向不同的解决方案。你将不得不使用一个功能齐全的数据库服务器,甚至可能需要在运行秘密圣诞老人服务的机器之外的另一台独立机器上运行。
在此之后,甚至还有更进一步的发展。如果服务变得非常、非常受欢迎,你可能会发现数据库的速度不足以处理所有查询请求。在这种情况下,你可以添加一个缓存服务,如Memcached,它将最常用的结果保存在内存中,以避免不必要的数据库查询。
因此,我们经历了从在CSV文件中托管数据,到存入SQLite文件,然后迁移到数据库服务器,最后在数据库服务器前使用动态缓存以使其运行更快的整个过程。
类似的发展进程也可能发生在同一项目的用户界面侧。最初,我们说该服务只是向列表中的人发送电子邮件。如果是一个小组且由一人负责脚本,这没有问题。
但随着项目变得更加复杂,你会希望为该服务建立一个网站,让人们能够执行诸如查看分配给谁以及创建愿望清单等操作。最初,这可能只是在与数据相同的机器上的Web服务器上运行。
如果网站被大量使用,你可能需要添加像Varnish这样的缓存服务。这将加速动态生成页面的加载。最终,这可能仍然不够,因此你需要将服务分布到许多不同的计算机上,并使用负载均衡器来分发请求。
你可以在公司内部使用托管在公司内的独立计算机来实现这一点,但这意味着随着应用程序不断增长,你需要添加越来越多的服务器。使用在云中运行的虚拟机可能更容易,这些虚拟机可以根据服务承受的负载变化而添加或移除。

这些例子表明,为每个问题找到正确的解决方案是多么重要。当你只有几十个用户时,部署一个用于存储的、带有分布式数据库的多服务器Web服务是毫无意义的。你只需要关注服务是如何增长的,以了解何时需要采取下一步措施,使其在当前用例下发挥最佳性能。
接下来,我们将讨论在处理复杂系统时可能遇到的一些问题。


总结

本节课中,我们一起学习了系统如何随着规模和复杂度的增长而演进。我们通过一个具体的例子,看到了技术栈从简单的CSV文件,到SQLite,再到独立的数据库服务器和缓存层,以及从发送邮件到构建网站、使用缓存和负载均衡的完整发展路径。关键在于理解,没有一劳永逸的解决方案,我们需要根据当前的实际需求和规模,选择并适时调整最合适的技术架构。
084:处理复杂的缓慢系统 🐌


在本节课中,我们将学习如何诊断和优化一个复杂且运行缓慢的系统。我们将探讨如何识别性能瓶颈,并介绍几种常见的解决方案,例如数据库索引、查询缓存和负载均衡。
在上一个视频中,我们讨论了随着使用量增长,系统复杂性也会增加。在大型复杂系统中,会涉及许多不同的计算机,每台计算机负责一部分工作,并通过网络与其他计算机交互。



例如,考虑你公司的一个电子商务网站。Web服务器是直接与外部用户交互的系统部分。另一个组件是数据库服务器,由处理网站生成请求的代码进行访问。根据整个系统的构建方式,可能还涉及许多其他服务,负责处理不同的工作部分。


以下是一个复杂系统可能包含的组件示例:
- 一个在订单下达后生成发票的计费系统。
- 一个供员工为客户准备订单的履约系统。
- 一个每天生成所有销售报告的报告系统,可能还有更多。


除此之外,你可能还需要备份、监控、测试基础设施等等。

调试和理解这样的系统可能很棘手。如果你的复杂系统运行缓慢,通常你需要做的是找到导致基础设施性能不佳的瓶颈。
是Web服务器上动态页面的生成速度慢吗?是数据库查询慢吗?还是履约流程的计算慢?找出原因可能很困难。
因此,一个关键点是拥有一个良好的监控基础设施,它能让你知道系统在哪些环节花费了最多时间。


假设你注意到获取网页的速度相当慢。但当你检查Web服务器时,发现它并没有过载。相反,大部分时间都花在了等待网络调用上。而当查看数据库服务器时,你发现它花费了大量时间在磁盘I/O上。

这表明数据库中的数据访问方式存在问题。需要关注的一点是数据库中是否存在索引。当数据库服务器需要查找数据时,如果你查询的字段上有索引,查找速度会快得多。

另一方面,如果数据库索引过多,添加或修改条目可能会变得非常慢,因为所有索引都需要更新。

因此,我们需要寻找一个良好的平衡点,只为实际会被使用的字段建立索引。如果索引无法解决问题,并且服务器需要处理的查询太多,无法及时响应所有请求,你可能需要研究缓存查询或将数据分布到不同的数据库服务器上。


现在,如果你在尝试找出服务缓慢的原因时,发现Web服务器的CPU使用率已经饱和,该怎么办?



第一步是检查是否可以使用我们之前解释的技术来改进服务代码。如果是一个动态网站,我们可以尝试在其之上添加缓存。
但如果代码本身没问题,并且缓存也无济于事,因为问题仅仅在于单台机器无法处理涌入的大量请求,那么你就需要将负载分布到更多计算机上。
为了实现这一点,你可能需要重组代码,使其能够在分布式系统而非单台计算机上运行。这可能需要一些工作,但一旦完成,你就可以通过向系统添加更多计算机,轻松地将应用程序扩展到处理所需的任意数量的请求。
最后,确保你确实需要执行正在做的所有操作。很多时候,随着项目的发展,我们留下了一个由层层复杂代码构成的可怕怪物。如果我们花几分钟思考一下系统正在做什么,最终可能会发现有一整个部分根本不需要,它一直在让我们的服务器做不必要的工作。
如果所有这些开始听起来太困难和可怕,请不要担心。请记住,如果你需要处理如此复杂的系统,最好的工具之一就是向你的同事寻求帮助。
接下来,我们将尝试动手解决一个现实生活中的复杂问题。
在本节课中,我们一起学习了如何诊断复杂系统的性能瓶颈。我们探讨了通过监控定位问题、优化数据库索引、实施缓存策略以及通过负载均衡扩展系统等方法。处理复杂系统需要耐心和系统性的方法,但掌握这些技能将帮助你有效地维护和优化大型基础设施。
085:使用线程加速 🚀

在本节课中,我们将学习如何通过并行处理来加速一个Python脚本。具体来说,我们将处理一个为大量产品图片生成缩略图的任务,并探索使用线程(Threads)和进程(Processes)来提升执行效率。
我们的公司有一个电子商务网站,网站上包含了许多待售产品的图片。
即将进行一次品牌重塑,这意味着所有这些图片都需要被新图片替换。
这包括全尺寸图片和缩略图。我们有一个脚本,可以根据全尺寸图片生成缩略图。
但是需要处理的文件数量很多,我们的脚本需要很长时间才能完成。
看来是时候提升一个档次,使用更好的方法来进行图片缩放。我们将首先使用一组1000张测试图片来运行当前的脚本。
实际需要转换的图片更多,但使用较小的批次测试脚本速度会更方便。
我们将使用 time 命令来执行程序,以查看其运行时间。
处理1000张图片大约花费了2秒。这看起来不算太慢。
但实际有数万张图片需要转换。
我们希望确保处理过程尽可能快。

让我们尝试通过并行处理图片来加速。我们将从导入 concurrent.futures 模块开始,这为我们提供了一种使用Python线程的简单方法。

import concurrent.futures
为了能够并行运行任务,我们需要创建一个执行器(executor)。

executor = concurrent.futures.ThreadPoolExecutor()
执行器负责在不同的工作单元(workers)之间分配任务。
futures 模块提供了几种不同的执行器,一种用于使用线程,另一种用于使用进程。
# 使用线程池执行器
executor = concurrent.futures.ThreadPoolExecutor()
# 或使用进程池执行器
# executor = concurrent.futures.ProcessPoolExecutor()
我们现在选择使用 ThreadPoolExecutor。现在,在这个循环中完成大部分工作的函数是 process_file。
我们不再在循环中直接调用它,而是向执行器提交一个新任务,指定函数名及其参数。
executor.submit(process_file, image)
我们的 for 循环现在创建了一系列任务,这些任务都被安排在执行器中。
执行器将使用线程并行运行它们。

使用线程时发生的一个有趣现象是,循环在所有任务被安排好后就会立即结束。
但任务完成仍需一段时间。
因此,我们将添加一条消息,说明我们正在等待所有线程完成,然后在执行器上调用 shutdown 函数。
print("等待所有线程完成...")
executor.shutdown()
这个函数会等待池中的所有工作单元完成,然后才关闭执行器。

好的,我们已经完成了修改,让我们保存脚本并测试它。
现在我们的脚本耗时1.2秒。这比之前看到的2秒有了不错的改进。

请注意用户时间(user time)高于实际时间(real time)。通过使用多线程,我们的脚本利用了计算机中可用的不同处理器,这个值显示了所有处理器上使用时间的总和。
你认为如果我们尝试使用进程而不是线程会发生什么?让我们通过更改我们使用的执行器来尝试一下。

executor = concurrent.futures.ProcessPoolExecutor()
通过将执行器更改为 ProcessPoolExecutor,我们告诉 futures 模块,我们希望使用进程而不是线程来进行并行操作。让我们保存并现在尝试这个版本。
哇,现在完成时间不到一秒,并且用户时间进一步上升了。
这是因为通过使用进程,我们更充分地利用了CPU。
这种差异是由Python中线程和进程的工作方式造成的。线程使用一系列安全特性来避免两个线程尝试写入同一个变量。这意味着,在使用线程时,它们可能最终需要等待几毫秒才能轮到它们写入变量,这导致了两种方法之间的微小差异。
在本视频中,我们研究了如何为Python脚本添加线程支持,以更好地利用我们的处理器能力。我们的脚本仍有更多可以改进的地方,例如在转换前检查缩略图是否存在且是最新的,或者在等待任务完成时添加第二个进度条以明确显示脚本正在工作。我们不会在这里深入探讨这些,但如果你感兴趣,可以自行探索这些可能性。
接下来,是另一个包含更多信息指引的阅读材料,随后是本模块的最后一次练习测验。
总结
本节课中,我们一起学习了如何利用Python的 concurrent.futures 模块实现并行处理。我们通过将串行的图片处理任务改造为使用 ThreadPoolExecutor 和 ProcessPoolExecutor,显著提升了脚本的执行速度。关键点在于理解执行器如何管理工作单元,以及线程与进程在利用CPU资源上的不同特性。
086:模块2总结 - 性能瓶颈分析 🐢

在本节课中,我们将总结导致计算机运行缓慢的常见原因,并学习如何系统地识别和解决性能瓶颈问题。

上一节我们介绍了性能问题的多样性,本节中我们来看看如何具体定位和解决这些问题。
在过去的几个视频中,我们学习了导致计算机运行缓慢的多种不同因素。
我们并未涵盖所有可能的原因,但探讨了最常见的情况。
我们谈到,面对运行缓慢的系统时,第一件事是识别瓶颈。要做到这一点,你需要理解每个组件如何与系统交互,以及哪种资源正在被耗尽。
有时根本原因是硬件性能不足。
或者可能只是同时运行的任务过多。
其他时候,问题可能出在代码本身。当尝试修复运行缓慢的程序时,我们应该避免执行开销高昂的操作的代码。
我们回顾了多个最佳实践,以帮助我们编写性能更好的代码。
我们讨论了何时使用正确的数据结构,如何避免开销高昂的循环,以及如何通过创建缓存来保持结果局部化。
例如:
# 使用缓存避免重复计算
cache = {}
def expensive_function(input):
if input not in cache:
cache[input] = perform_expensive_operation(input)
return cache[input]
接着,我们花了一些时间回顾复杂系统,并查看了在此类环境中如何排查运行缓慢问题的一些示例。
以下是排查性能问题的关键步骤:
- 识别瓶颈:确定是CPU、内存、磁盘I/O还是网络导致速度变慢。
- 分析资源消耗:使用监控工具查看哪种资源达到或接近其极限。
- 提出优化方案:根据瓶颈类型,提出针对性的优化想法,如代码优化、硬件升级或负载调整。
下次当你需要调试性能问题时,你将能够思考瓶颈是什么,寻找耗尽该资源的因素,并提出如何加快速度的想法。
我希望你开始认识到,当你在工作中面临一些相同问题时,你正在掌握的这些技能将如何真正帮助你。你不可能解决所有问题,但希望你对你的技能和能力开始感到更加自信。
接下来,我们为你准备了另一个实验来获得一些实践。你将需要调试一个运行缓慢的系统并使其性能更好。
本节课中我们一起学习了如何系统性地分析性能瓶颈,从识别资源耗尽点到应用代码优化策略。掌握这些方法将帮助你有效应对实际工作中的系统性能挑战。
087:崩溃程序介绍 🚨


在本节课中,我们将学习程序崩溃的常见原因、如何定位问题根源,以及应对崩溃的基本策略。程序崩溃是IT工作中常见的问题,理解其背后的原理能帮助我们更有效地进行调试和修复。
概述
上一模块我们探讨了导致计算机、代码或系统运行缓慢的各种原因及其解决方法。你已经顺利完成了课程的前半部分,值得祝贺。
本节我们将转向另一个常耗费大量精力的IT领域:导致程序意外崩溃的各种因素。如果你使用过计算机,很可能遇到过软件崩溃的情况,例如程序意外终止、设备无故重启或操作系统卡死,导致未保存的工作丢失。
程序崩溃的常见原因
在我的工作中,经常需要处理崩溃的应用程序,例如因未捕获异常而终止的程序、升级失败的系统,或是静默退出的作业,这些情况都令人困惑其根本原因。
不久前,我曾调试一个每隔几天就崩溃的程序。该程序负责解析日志,并在发现可疑事件时生成警报。任务崩溃时,所有正在处理的数据都会被丢弃。随后任务重启并重新处理日志文件,因此虽然数据没有丢失,但反复崩溃增加了处理数据的平均时间。
为解决此问题,我首先阅读代码以理解其功能,这引导我发现了问题所在:程序启动了许多线程,但从未关闭它们,最终导致内存耗尽而崩溃。通过确保所有线程在完成任务后都被清理,我修复了这个问题。
通常,崩溃的原因是软件遇到了开发者未预料到的意外情况。由于这些情况难以预测,触发因素可能非常广泛。
以下是可能导致崩溃的一些常见原因:
- 硬件问题:例如损坏的内存条可能导致程序在尝试访问内存时获取无效数据。
- 代码缺陷:代码的某些部分执行了不支持的操作,例如尝试从空列表中读取元素。
- 系统环境问题:例如程序期望某个特定库存在或某个目录存在,但它们并未就绪。
- 用户输入问题:例如我们期望用户输入数字,但他们却输入了字符串。
应对崩溃的策略
可能导致崩溃的原因数不胜数,我们无需全部知晓,但需要学会缩小问题范围,以便找到根本原因。
在接下来的几个视频中,我们将学习一系列不同的技术,用于理解根本原因并修复问题,或在无法修复时至少减轻损害。
我们将首先探讨如何理解问题本身。
接着,我们将检查当无法修改程序代码时我们可以做什么,以及当我们能够访问源代码(即使不是我们自己编写的代码)时可以做什么。
最后,我们还将探讨当问题不仅仅是单台计算机崩溃,而是影响复杂系统的更大规模事件时该如何处理。
我们也将深入探讨如何记录问题及其解决方案,以及如何通过撰写事后分析报告从错误中学习。
与往常一样,我们将通过解决实际问题来应用所有这些知识,并且在本模块结束时,你将有机会尝试修复一个复杂的崩溃问题。
让我们开始吧。😊
总结
本节课我们一起探讨了程序崩溃的常见诱因,包括硬件故障、代码错误、系统环境配置及用户输入异常等。我们了解到,面对崩溃的关键在于系统地缩小问题范围,而非穷尽所有可能原因。后续课程中,我们将学习具体的调试技术、问题记录方法以及如何从事故中学习改进。
088:系统崩溃诊断与处理 🖥️💥
在本节课中,我们将学习如何诊断和处理系统崩溃问题。系统崩溃可能由多种原因引起,我们将通过一个具体的案例,学习如何逐步缩小问题范围,从应用程序层面排查到硬件层面,最终找到并解决问题的根本原因。
概述:从用户报告开始
上一节我们介绍了问题诊断的一般思路。本节中,我们来看看当用户报告一个具体的系统崩溃问题时,我们应该如何着手处理。

假设一位用户向你求助,称其内部计费应用程序在尝试为客户生成发票时崩溃了。这个问题可能由多种原因导致,因此你需要缩小问题范围。请记住,你应该从那些更简单、更快速的检查步骤开始。

第一步:检查日志与重现问题
首先,尝试查看日志,寻找可能指向问题根源的错误信息。但你可能只找到一个写着“应用程序终止”的错误,没有更多有用信息。
因此,你需要检查用户是否能在另一台计算机上通过执行相同操作来重现该问题。你请用户尝试一下,结果发现在另一台机器上,他们可以正常生成发票。这意味着问题很可能只与那台特定计算机的安装或配置有关。
好消息是,你已经将问题范围缩小到了与特定机器相关的问题上。
第二步:确认问题的可靠性与范围
接下来,你可能需要确认这个问题是否稳定出现。例如,是所有发票生成都失败,还是仅限于某个特定产品或客户?
对于本例,假设你请用户尝试生成其他发票,结果一切正常,即使是对同一客户也是如此。那么,你可能会想:问题是否只出现在那台特定计算机上,针对那个特定客户的特定订单?这相当可疑。
但别急,用户告诉你,在生成了当天所有发票后,他们尝试生成报告时,应用程序再次崩溃。但之后又恢复正常了。你与其他用户核实,发现他们使用该应用程序时并未崩溃。
这意味着什么?应用程序似乎在那台计算机上随机崩溃。为了进一步缩小范围,你需要知道是只有那个应用程序有问题,还是整个系统都有问题。
第三步:排查应用程序与系统问题
为了检查这一点,你可以尝试移除程序的本地配置,改用默认配置,或者甚至重新安装应用程序。你也可以询问用户是否在其他应用程序上遇到过崩溃。
对于本例,假设重新安装应用程序并使用默认配置运行后,仍然会出现随机崩溃。当被追问时,用户告诉你,上周他们在使用内部网络邮件时,网页浏览器也崩溃过。
此时,信息指向了整个系统的问题,可能是硬件或操作系统安装的问题。如果你有备用计算机,此时给用户一台可能是个好主意,这样他们可以恢复工作,而你则可以继续寻找问题的根本原因。
第四步:硬件问题诊断
现在,问题很可能是硬件相关的。你可以尝试将硬盘从那台计算机中取出,放入另一台计算机中测试。这种方法在你已经有一台确认工作正常的备用机时效果最好,可以用于此类测试。
这样,你可以快速检查问题是出在硬盘数据上,还是计算机的其他部分。假设将硬盘放入另一台计算机后,应用程序运行正常,没有意外崩溃。这意味着某个硬件组件出现了故障。
下一步是找出是哪个组件。鉴于随机崩溃的特性,需要检查的一个方面是内存(RAM)。内存芯片会随着时间老化,当它们老化时,计算机可能将数据写入内存的某个部分,但在尝试读取时却得到完全不同的值。
为了检查内存的健康状况,我们可以使用 MemTest86 工具来查找错误。我们在启动时运行此工具,而不是正常的操作系统,以便它可以访问所有可用内存,并验证写入内存的数据在尝试读取时是否相同。
如果内存正常,你可以通过查看操作系统提供的传感器数据来检查计算机是否过热。如果也不是这个问题,则检查外部设备(如显卡或声卡)是否存在问题。你可以通过断开连接或更换计算机中存在的设备,并检查崩溃是否仍然发生来进行排查。
第五步:硬盘与操作系统问题
那么,如果将硬盘放入另一台计算机后,仍然出现奇怪的崩溃,该怎么办?这意味着问题出在硬盘本身或操作系统安装上。
和内存一样,我们的硬盘也会老化。在某些时候,计算机读取的数据会与最初存储的数据不匹配。每个操作系统都自带一套硬盘检查工具,你应该熟悉你所使用操作系统的这些工具。
你需要查看检查磁盘坏道的工具输出,并且使用 SMART 工具,这些工具有助于检测错误,甚至可以在问题影响计算机性能之前尝试预测问题。
如果硬盘检查结果正常呢?那么你需要研究可能的操作系统问题。但在这样做之前,问问自己:这值得吗?查找安装过程中的问题可能会花费大量宝贵时间。如果安装过程易于复制,那么重新安装操作系统可能比查找其损坏原因更快、更简单。

总结与下节预告
本节课中,我们一起学习了如何诊断不稳定的、行为异常的系统。我们通过一个案例,从检查日志、重现问题开始,逐步排查应用程序配置、系统范围问题,最终深入到硬件(如内存、硬盘)的诊断,并讨论了在必要时重新安装操作系统的策略。

但通常,你需要处理的是某个特定的行为异常的应用程序。在这种情况下,几乎可以肯定是应用程序代码中存在一个未考虑到某些(虽然罕见但可能发生的)情况的错误。接下来,我们将探讨当这种情况发生时,你可以做些什么。
089:理解崩溃应用程序 🔍


在本节课中,我们将学习当应用程序崩溃时,如何系统地查找原因。我们将探讨如何查看日志、追踪程序行为、分析环境变化,并创建一个可复现的故障案例。
当应用程序崩溃且原因不明时,我们需要查找可能与故障相关的日志。在Linux系统上,我们会查看位于 /var/log/ 的系统日志文件,或用户日志文件。在macOS上,通常使用“控制台”应用来查看日志。在Windows上,则使用“事件查看器”。
那么,在这些日志中应该寻找哪些数据呢?
大多数日志的每一行都包含日期和时间。知道了应用程序崩溃的时间,你就可以查找该时间点附近的日志条目,并尝试找到与崩溃应用程序相关的错误信息。
有时错误信息是自解释的,例如“权限被拒绝”、“没有这样的文件或目录”或“连接被拒绝”。有时它可能是一条晦涩的信息,让人完全不明白其含义。
无论错误信息看起来多么奇怪,我们都可以在线搜索以试图理解其含义。如果幸运的话,我们可能会找到关于该错误的官方文档,说明其含义以及我们可以采取的措施。即使没有官方文档,通常也能找到其他人处理类似错误的帖子,这些额外信息可以帮助我们理解情况。
如果没有错误信息,或者错误信息没有帮助,我们可以尝试通过启用调试日志来获取更多信息。

许多应用程序在启用调试日志时会生成更多的输出。我们可能需要通过应用程序配置文件中的设置,或在手动运行应用程序时传递命令行参数来启用它。

通过启用这些额外的日志信息,我们可以更好地了解导致问题的实际原因。

那么,如果根本没有日志或错误信息,我们该怎么办?在这种情况下,我们需要使用能让我们看到程序内部运行情况的工具。我们之前提到过一些:在Linux上,我们使用 strace 来查看程序正在执行哪些系统调用。
strace <your_program>
在macOS上,等效的工具是 dtruss。在Windows上,“进程监视器”是一个可以窥探进程内部情况的工具。
通过追踪程序执行的系统调用,我们可以看到它试图打开哪些文件和目录,尝试建立哪些网络连接,以及试图读取或写入哪些信息。这可以让我们更好地了解导致实际问题的原因。
我们可能会发现问题是由于程序期望存在的某个资源不存在而引起的,就像我们在早期模块中看到的目录缺失示例。或者,我们可能发现程序试图与图形界面交互,但由于它是在服务器上运行的服务,所以没有任何图形界面。又或者,程序试图打开一个文件,但运行软件的用户没有必要的权限。
如果应用程序以前运行良好,但最近开始崩溃,那么调查这期间发生了什么变化是很有用的。
首先要检查问题是否由应用程序本身的新版本引起。也许新版本中存在导致崩溃的错误,或者我们使用应用程序的方式不再受支持。

但这并不是唯一可能引发崩溃的变化。也可能是应用程序使用的库或服务发生了变化,导致它们不再能很好地协同工作。或者,可能是整体环境发生了配置更改,例如用户不再属于某个特定组,或者应用程序使用的文件位于不同的位置。
在试图找出变化时,日志也是一个有用的信息来源。



在系统日志中,我们可以检查最近更新了哪些程序和库。检查配置更改可能更困难,这取决于你如何管理这些配置。如果设置是通过配置管理系统管理的,并且值存储在版本控制系统中,那么你可能能够查看更改历史记录,并找出是哪一个更改触发了故障。
我们之前已经多次强调,为我们试图解决的问题建立一个可复现的案例是多么重要。当我们试图调试一个崩溃的应用程序时,找到一个可复现的案例可以帮助我们理解导致崩溃的原因,并找出我们可以采取的修复措施。
因此,花一些时间弄清楚触发崩溃的状态是很有价值的。这包括整体系统环境、特定的应用程序配置、应用程序的输入、应用程序生成的输出、它使用的资源以及它与之通信的服务。
在尝试创建复现案例时,从一个干净的状态开始,然后慢慢添加各个部分,直到触发崩溃,这可能是有用的。这可能包括尝试使用默认配置而不是本地配置来运行应用程序,或者在一台新安装的计算机上而不是在它崩溃的计算机上运行。
请记住,我们希望使复现案例尽可能小。这让我们能更好地理解问题,并在我们尝试修复时快速检查问题是否仍然存在。
即使我们最终无法修复问题,拥有一个简单小巧的复现案例对于向程序开发者报告错误也极其有帮助。
总结
本节课中,我们一起学习了如何查找崩溃应用程序的根本原因。我们需要查看所有可用的日志,弄清楚发生了什么变化,追踪程序进行的系统或库调用,并创建尽可能小的复现案例。完成所有这些步骤后,我们应该对问题的根本原因有所了解,甚至可能知道如何修复它。
修复问题的策略将取决于我们是否能修复代码。在下一个视频中,我们将看看当你无法修复程序并需要设法绕过问题时可以做什么。在后面的视频中,我们将深入探讨修复错误代码的策略。
090:谷歌《用Python进行IT自动化办公》第32课 - 无法修复程序时该怎么办 🛠️


概述
在本节课中,我们将学习当遇到无法直接修改源代码的程序崩溃问题时,可以采取哪些策略来解决问题。我们将探讨几种实用的“变通”方法,确保服务或应用的可用性。
无法修改代码时的应对策略
在IT领域工作的一大优势是我们可以指令计算机执行任务。然而,当处理由他人编写的软件出现的意外行为时,我们可能就没那么幸运了。这可能是因为我们面对的是专有软件,根本无法获取源代码;或者我们虽然能访问源代码,但它使用的语言我们并不理解,因此无法修改。
无论原因如何,当你需要修复一个崩溃的应用程序却又无法修改其代码时,你能做什么呢?你需要找到一种方法来规避问题,避免崩溃。具体的解决方案取决于你试图解决的问题。
上一节我们介绍了问题的背景,本节中我们来看看一些可用的选项。
策略一:预处理数据或使用包装器
假设你发现问题是某个特定的数据输入导致应用程序崩溃。崩溃只发生在输入数据不符合代码预期的格式时。
例如,你的某些系统生成XML格式的数据,这在旧版软件中运行良好。但新版软件现在要求所有数据必须是YAML格式。
在这种情况下,你可以编写一个脚本对数据进行预处理,确保其格式符合程序预期。
类似地,如果问题是由应用程序使用的某个不再兼容的外部服务引起的,我们可以编写一个服务作为代理,确保双方都能看到它们预期的请求和响应。
这种兼容性层被称为包装器。


包装器是一个函数或程序,它在两个函数或程序之间提供一个兼容层,使它们能够良好协作。
使用包装器是当预期输出和输入格式不匹配时一种非常常见的技术。
所以,如果你面临某种兼容性问题,不要害怕编写一个包装器来绕过它。
策略二:调整系统环境
另一个可能需要考虑的可能性是,整体系统环境与应用程序不兼容。
在这种情况下,你可能需要检查应用程序开发者推荐的环境,然后修改你的系统以匹配它。这可能意味着运行相同版本的操作系统、使用相同版本的动态库或与相同的后端服务交互。
例如,如果应用程序是在Windows 7上开发和测试的,而你在Windows 10下运行时遇到问题,你可能需要改用Windows 7。或者,如果应用程序是为Ubuntu开发和测试的,而你在Fedora下运行遇到麻烦,你可能需要尝试在Ubuntu上运行。
如果你无法使环境匹配怎么办?例如,这可能发生在另一个应用程序需要同一库的不同版本时,或者你无法更改某个配置设置,因为该设置是访问另一项服务所必需的。
在这种情况下,你可能需要考虑在虚拟机或容器中运行该应用程序。这是两种不同的技术,但我们不在此详述它们的区别。目前你只需要知道,它们都允许你在自己的环境中运行受影响的应用程序,而不会干扰系统的其余部分。当我们需要环境与同一台计算机上其他应用程序使用的环境不同时,这正是我们所需要的。
策略三:部署看门狗进程
有时我们无法找到阻止应用程序崩溃的方法,但我们可以确保如果它崩溃了,能够重新启动。
为此,我们可以部署一个看门狗。这是一个检查程序是否正在运行的进程,当程序未运行时,它会重新启动该程序。
要实现这一点,我们需要编写一个脚本,使其在后台持续运行,并定期检查另一个程序是否在运行。每当检查失败时,看门狗就会触发程序重启。
这样做并不能避免崩溃本身,但至少能确保服务可用。这对于可用性比持续运行更重要的服务来说效果很好。


策略四:报告错误
无论你如何解决问题,请记住始终向应用程序开发者报告错误。
正如我们之前提到的,如果你能为你的问题提供一个良好的复现案例,这将使开发者更容易找出问题所在以及如何修复它。
因此,当你报告错误时,请确保包含尽可能多的信息。提供一个良好的复现案例,并回答我们之前提到的问题:
以下是报告错误时应包含的关键信息:
- 你试图做什么?
- 你遵循了哪些步骤?
- 你期望发生什么?
- 实际结果是什么?
总结

本节课中我们一起学习了当无法直接修改程序代码时,应对软件崩溃的几种策略。我们探讨了通过预处理数据或使用包装器解决兼容性问题,通过调整系统环境或在虚拟机/容器中运行为程序创造合适的环境,以及通过部署看门狗进程来保证服务的可用性。最后,我们强调了向开发者详细报告错误的重要性,这有助于从根本上解决问题。掌握这些“变通”方法,能让你在无法触及源代码时,依然有效地维持IT系统的稳定运行。
接下来,我们将学习如何应用这些技能来对一个崩溃的应用程序进行故障排除。
091:排查内部服务器错误 🔍

在本节课中,我们将学习如何诊断和修复一个Web服务器上的“500内部服务器错误”。我们将从接收错误报告开始,逐步深入系统内部,检查日志、分析网络连接、查看配置文件,最终定位并解决一个由文件权限问题引发的故障。
一位同事提醒我们,公司Web服务器上的一个页面无法正常工作。和以往一样,我们需要弄清楚这具体意味着什么。我们向同事询问了更多细节,他告知我们出错的网页地址是 site.example.com/logs。

让我们检查一下,这个页面是否对我们同样失效。


问题出现了,服务器返回了 500错误。

这个错误通常意味着应用程序的服务器端有东西崩溃了。但我们不知道具体是什么。我们需要进行调查以获取更多信息。
让我们连接到Web服务器,尝试找出问题所在。第一步是查看日志。正如我们之前提到的,在Linux系统上,日志通常位于 /var/log 目录下。
为此,我们先使用 date 命令确认当前日期,然后切换到日志目录,检查是否有关于我们错误的最新日志。
cd /var/log
接着,我们使用 ls -lt 命令,该命令会按最后修改日期排序文件。我们将其输出通过管道连接到 head 命令,以仅查看前10行。
ls -lt | head -10
我们刚刚触发了错误,但日志中似乎没有任何最近的记录。为了以防万一,让我们用 tail 命令查看 syslog 的最后几行。
tail -20 /var/log/syslog
不,这里也没有发现什么有用的信息。
我们需要找到获取更多信息的方法,但我们甚至不知道这台计算机上运行的是哪种Web服务软件。不过,我们知道Web服务器运行在80端口,这是默认的Web服务端口。
我们如何找到哪个软件正在监听80端口呢?我们可以使用 netstat 命令。根据我们传递的标志,这个命令可以提供大量关于网络连接的信息。此命令访问的许多套接字仅限于Linux上的管理员用户 root,因此我们需要使用 sudo 来以root权限运行它。
然后,我们将向 netstat 传递一系列标志:-n 用于打印数字地址而非解析主机名,-l 用于仅查看正在监听连接的套接字,-p 用于打印每个套接字所属的进程ID和名称。由于我们只关心80端口,我们将把输出通过管道连接到 grep 命令来查找 :80。
sudo netstat -nlp | grep :80
很好,我们获得了新信息。我们看到监听80端口的进程叫做 nginx,这是一个流行的Web服务应用程序。
现在,我们想查看我们站点的配置。在Linux上,配置文件通常存储在 /etc 目录中,所以让我们看看 /etc/nginx。
ls /etc/nginx
这里有很多文件,Web服务器中可以设置许多不同的配置选项。我们正在寻找与特定站点相关的配置,所以让我们查看 /etc/nginx/sites-enabled。
ls /etc/nginx/sites-enabled
这里有两个文件,一个是默认站点的,另一个是 site.example.com 站点的。这正是我们需要的。让我们用 vi 编辑器打开它。
sudo vi /etc/nginx/sites-enabled/site.example.com
内容不多,但在底部我们看到一行:uwsgi_pass 后面跟着本地主机地址和一个不同的端口号。看来这个网站并非直接由Nginx提供服务。相反,该软件将连接的控制权传递给了 uwsgi,这是一种常用于将Web服务器连接到生成动态页面的程序的解决方案。
那么,让我们看看是否能找到 uwsgi 的配置。我们用 :q 退出 vi,然后查看 /etc/uwsgi 目录下是否有有趣的内容。
ls /etc/uwsgi
这里只看到两个目录:apps-available 和 apps-enabled。让我们看看 apps-enabled 里有什么。
ls /etc/uwsgi/apps-enabled
很好,我们找到了站点的 uwsgi 配置文件,让我们检查一下。
sudo vi /etc/uwsgi/apps-enabled/site.example.com.ini
这个文件信息丰富得多。我们看到应用程序的主目录是 /srv/site.example.com,应用程序以 www-data 用户和组身份运行,它运行一个名为 prod.py 的Python3脚本,日志存储在 /var/log/site.log,以及其他一些信息。
好的,让我们利用这些额外信息,看看能否找出问题所在。再次用 :q 退出,然后检查那个日志文件。
ls -l /var/log/site.log
奇怪,日志文件的大小是零。这看起来不对。让我们看看能否通过查看 uwsgi 执行的Python脚本来发现其他线索。
sudo vi /srv/site.example.com/prod.py
这个文件配置了几个不同的网页。它使用了 Bottle,这是一个用于生成动态网页的Python模块。在底部,我们看到了当前出错的日志页面的配置。幸运的是,一位同事留下了一条注释,说明我们可以通过取消注释调用 bottle.debug(True) 的那一行来获取调试信息。
这正是我们需要的。要取消注释这一行,我们需要对文件有写权限,而 vi 当前是以只读模式打开的。让我们退出并以 sudo 重新打开以便修改。
sudo vi /srv/site.example.com/prod.py

我们已经做出了更改,按照说明保存它并重新加载 uwsgi。
sudo service uwsgi reload

好的,我们已经添加了调试信息,希望这能告诉我们页面失败的原因。让我们重新加载网站看看会发生什么。

好消息!这次我们看到了错误的回溯信息。我们看到问题是应用程序在尝试打开 /var/log/site.log 时收到了“权限被拒绝”的错误。

还记得我们觉得这个文件为空很奇怪吗?看来它不知怎么损坏了。让我们再仔细看看。这次,检查一下是否有其他以 site.log 开头的文件。
ls -l /var/log/site.log*
有一个 site.log 文件和一个 site.log.1 文件。在使用 logrotate 轮转日志以避免它们变得太大时,这很常见。但这里有些不对劲。注意一个文件属于 root 用户,而另一个属于 www-data 用户。
如果你查看文件的权限,可能会注意到它们被设置为允许所有者写入,所有者和组读取,但其他用户无法访问。我们之前看到应用程序是以 www-data 用户运行的,所以如果 site.log 属于 root 用户,应用程序将无法读取或写入这个日志文件。
叮!叮!看来我们找到了问题的根本原因。让我们更改 site.log 文件的所有者来解决眼前的问题。

sudo chown www-data:www-data /var/log/site.log
现在尝试重新加载我们的页面。


成功了!日志现在是空的,因为应用程序之前无法写入它,但如果我们持续刷新,就会看到它被我们的访问记录填满。
好的,我们已经解决了眼前的问题,我们的网页再次正常工作了。但我们仍然需要处理长期的修复措施。为什么文件的所有权会出错呢?

我们怀疑 logrotate 的配置可能有问题,但我们需要继续调查以找出原因。
在本节课中,我们一起研究了如何排查一个失败的应用程序。我们检查了一系列不同的工具和思路,这些都能帮助我们理解正在发生的情况并获取更多信息,直到找到问题的根本原因。希望你现在开始明白,这些课程提供了诊断和解决工作中肯定会遇到问题的宝贵工具。
接下来,我们将有一篇阅读材料,其中包含一些链接,帮助你了解更多可能导致计算机崩溃的不同情况,然后是一个快速练习测验。
092:访问无效内存 🧠


在本节课中,我们将学习程序崩溃的一个常见原因——访问无效内存。我们将探讨其背后的原理、如何诊断此类问题以及可能的解决方案。
概述:什么是访问无效内存?
在之前的视频中,我们探讨了多种可能导致软件崩溃的原因,以及当无法直接修改代码时我们可以采取的措施。然而,如果我们能让应用程序行为正确,我们就有更多处理崩溃的选项。当然,要应用这些修复,我们需要理解崩溃发生的原因。
程序崩溃的一个常见原因是它试图访问无效内存。为了理解这意味着什么,让我们快速解释一下现代操作系统中内存的使用方式。


内存管理基础

运行在我们计算机上的每个进程都会向操作系统请求一块内存。这块内存用于在程序执行期间存储值和对其进行操作。

操作系统维护一个映射表,记录哪个进程被分配了哪部分内存。
进程不允许在其被分配的内存部分之外进行读取或写入操作。


因此,访问无效内存意味着进程试图访问系统中未分配给它的那部分内存。

为什么会发生访问无效内存?
那么,在正常工作条件下,这种情况是如何发生的呢?应用程序会请求一部分内存,然后使用操作系统分配给它们的空间。

但是,编程错误可能导致进程试图在有效范围之外的内存地址进行读取或写入。
当这种情况发生时,操作系统会引发一个错误,例如分段错误或一般保护错误。

导致无效内存访问的编程错误

这是哪种编程错误呢?它通常发生在像C或C++这样的低级语言中,在这些语言中,程序员需要负责管理程序将要使用的内存,并在不再需要时将其归还。
在这些语言中,存储内存地址的变量被称为指针。
它们就像代码中的任何其他变量一样,可以根据需要进行修改。
因此,如果一个指针被设置为超出该进程有效内存范围的值,它将指向无效内存。如果代码随后尝试访问该指针指向的内存,应用程序就会崩溃。

以下是导致分段错误的常见编程错误:
- 忘记初始化变量:使用未分配初始值的指针。
- 访问列表有效范围之外的元素:例如,尝试读取一个长度为5的列表的第10个元素。
- 在归还内存后仍尝试使用它:这被称为“释放后使用”。
- 尝试写入超过所请求内存部分容量的数据:这被称为“缓冲区溢出”。
如何诊断分段错误?


如果你有一个程序出现了分段错误,该怎么办?理解问题所在的最佳方法是将调试器附加到有问题的程序上。这样,当程序崩溃时,你将获得有关故障发生所在函数的信息。

你会知道函数接收到的参数,并找出无效的内存地址。这可能已经足够理解问题了。也许某个变量初始化得太晚,或者代码试图读取列表中的过多项。
如果这还不够,调试器可以为你提供关于应用程序正在做什么以及为什么内存无效的更多详细信息。为此,我们的程序需要使用调试符号进行编译。
这意味着,除了计算机用于执行程序的信息外,可执行二进制文件还需要包含调试所需的额外信息,例如正在使用的变量和函数的名称。
这些符号通常从我们运行的可执行文件中剥离,以使其更小。因此,我们需要重新编译二进制文件以包含符号,或者从软件提供商处下载调试符号(如果可用)。像Debian或Ubuntu这样的Linux发行版会为发行版中的所有软件包提供包含调试符号的独立软件包。
诊断流程与工具
要调试一个出现分段错误的应用程序,我们下载该应用程序的调试符号,将调试器附加到它,并查看故障发生的位置。这样做时,我们可能会发现崩溃发生在对库函数的调用内部。这与应用程序本身是分开的,因此我们需要为该库安装调试符号。我们可能需要重复这个循环几次,才能识别出有问题的代码部分。
微软的编译器也可以在一个单独的PDB文件中生成调试符号。一些Windows软件提供商允许用户下载与其二进制文件对应的PDB文件,以便他们正确调试故障。

关于无效内存访问最棘手的事情之一是,我们通常处理的是未定义行为。这意味着代码正在做编程语言中无效的事情。
实际结果将取决于所使用的编译器、操作系统如何为进程分配内存,甚至所使用的库的版本。一个在运行Windows的计算机上运行良好的程序,可能在运行Linux的计算机上触发分段错误,反之亦然。
在尝试理解与处理无效内存相关的问题时,Valgrind可以给我们很大帮助。Valgrind是一个非常强大的工具,它可以告诉我们代码是否正在执行任何无效操作,无论它是否崩溃。

Valgrind让我们知道代码是否在初始化变量之前访问它们,代码是否未能释放请求的部分内存,指针是否指向无效的内存地址,以及更多事情。Valgrind在Linux和macOS上可用。Dr. Memory是一个类似的工具,可以在Windows和Linux上使用。
发现原因后怎么办?
那么,说了这么多,当我们最终发现分段错误的原因时,我们该怎么做呢?你会希望要么自己修改代码,要么让开发人员在下一个版本中修复问题。
如果你从未使用过应用程序所用的编程语言,这听起来可能很可怕。但是,当你知道代码有什么问题时,通常不难找出如何修复它。
如果一个变量初始化得太晚,修复问题可能就像将初始化移到代码的正确部分一样简单。或者,如果一个循环正在访问超出列表长度的项,你可以通过检查迭代次数是否超过所需来解决这个问题。
在本课程中,我们一直在教你这些概念,以便你可以将它们应用到任何代码片段中,无论程序使用哪种语言。所以,不要害怕将其付诸实践,你已经具备了相应的技能。
如果程序是开源项目的一部分,你可能会发现其他人已经完成了这项工作,因此你可以应用网上可用的补丁。如果没有补丁,你自己也无法找出错误,你总是可以联系开发人员,询问他们是否可以修复问题并创建必要的补丁。
高级语言中的情况
在像Python这样的高级语言中,解释器几乎肯定会自己捕获这类问题。然后它会抛出一个异常,而不是让无效的内存访问到达操作系统。但是,这些异常仍然可能相当烦人。我们将在下一个视频中讨论这些。
总结
本节课中,我们一起学习了程序崩溃的常见原因——访问无效内存。我们了解了操作系统如何管理内存,以及编程错误(如指针错误、缓冲区溢出等)如何导致分段错误。我们探讨了使用调试器和调试符号来诊断问题的方法,并介绍了Valgrind等辅助工具。最后,我们讨论了发现根本原因后的修复策略,以及在高级语言中此类问题的不同表现形式。掌握这些知识将帮助你更有效地诊断和解决复杂的软件崩溃问题。
093:未处理的错误和异常 🐛
在本节课中,我们将要学习程序运行时可能遇到的未处理错误和异常。我们将了解它们产生的原因,如何通过调试来定位问题,以及最终如何修复这些问题,使程序更加健壮和用户友好。
从内存错误到程序异常
上一节我们介绍了程序访问无效内存可能引发的问题。正确处理内存是一个难题,因此出现了像Python、Java或Ruby这样的编程语言来为我们管理内存。
但这并不意味着用这些语言编写的程序就不会触发奇怪的问题。在这些语言中,当程序遇到代码中未正确处理的意外情况时,就会触发错误或异常。
以下是Python中几种常见的错误类型:
- 索引错误:尝试访问列表末尾之后的元素时触发。
my_list = [1, 2, 3] print(my_list[5]) # 这将引发 IndexError - 类型错误或属性错误:尝试对未正确初始化的变量执行操作时触发。
- 除零错误:尝试进行除以零的操作时触发。
当代码产生这些错误而未妥善处理时,程序通常会意外终止。一般而言,未处理的错误发生是因为代码做出了错误的假设。
理解错误信息与回溯
当这些故障发生时,运行程序的解释器会打印错误类型、导致故障的代码行以及回溯信息。


回溯显示了问题发生时正在执行的不同函数的代码行。
在许多情况下,错误信息和回溯信息已经足以让我们理解发生了什么,从而着手解决问题。但遗憾的是,情况并非总是如此。
一段代码在某个函数中崩溃,并不意味着错误一定就在那个函数里。例如,问题可能是由之前调用的某个函数引起的,它把一个变量设置成了错误的值,而崩溃的函数只是访问了这个变量。
调试代码以定位问题
因此,当错误信息不足以定位问题时,我们需要调试代码来找出问题所在。
对于Python程序,我们可以使用PDB交互式调试器。它允许我们执行所有典型的调试操作,例如逐行执行代码,或者在试图理解一个行为异常的函数时,观察变量值的变化。
除了使用调试器,常见的做法是添加打印与代码执行相关数据的语句。


以下是这类语句可以显示的内容:
- 变量的内容。
- 函数的返回值。
- 元数据,如列表的长度或文件的大小。
这种技术被称为“打印调试”。该名称来源于C编程语言中用于向屏幕打印消息的printf函数。但我们可以在所有语言中使用这种技术,无论我们使用print、puts还是echo来在屏幕上显示文本。
使用日志模块进行更好的调试
让我们更进一步。在修改代码以向屏幕打印消息时,最好的方法是以一种可以轻松启用或禁用的方式添加消息,这取决于我们是否需要调试信息。
在Python中,我们可以使用logging模块来实现这一点。该模块允许我们设置希望代码的详细程度。我们可以选择是包含所有调试消息,还是仅包含信息、警告或错误消息。然后在打印消息时,我们指定正在打印的消息类型。这样,我们就可以通过一个标志或配置设置来更改调试级别。
修复错误与增强程序健壮性
假设你已经找出了抛出意外异常的原因。接下来该怎么做?
解决方案可能是修复编程错误,例如确保变量在使用前被初始化,或者确保代码不会尝试访问列表末尾之后的元素。也可能需要将某些未考虑到的用例添加到代码中。
总的来说,你会希望程序对故障有更强的恢复能力。与其意外崩溃,不如让程序告知用户问题所在,并告诉他们需要做什么。
例如,假设你有一个应用程序因“权限被拒绝”错误而崩溃。与其让程序意外终止,不如修改代码以捕获该错误,并告诉用户权限问题是什么,以便他们可以修复它。例如:“无法在/tmp目录中写入新文件,请确保您的用户对该目录拥有写入权限。”
在某些情况下,如果某些条件不满足,我们的程序甚至没有运行的意义。在这种情况下,程序在触发错误时终止是可以接受的。但同样,它应该以一种告诉用户如何解决问题的方式终止。
例如,如果一个应用程序必须连接到数据库,但数据库服务器没有响应,那么应用程序以错误信息“无法连接到数据库服务器”终止是合理的。同时,包含所有连接尝试的详细信息(如主机、端口或用于连接的用户名)也是有意义的。
课程总结
本节课中我们一起学习了如何处理未处理的错误和异常。我们来总结一下关键步骤:
- 调试定位:如果程序因未处理的错误而崩溃,首先需要进行一些调试以找出问题的原因。
- 修复与捕获:一旦找出原因,你需要确保修复任何编程错误,并捕获任何可能触发错误的条件。
通过这种方式,你可以确保程序不会崩溃,从而避免让用户感到沮丧。
接下来,我们将讨论在尝试修复他人代码时可以做些什么。
094:修复他人的代码 🛠️

在本节课中,我们将学习如何理解和修复他人编写的代码。这是IT工作中常见的任务,无论是处理开源项目还是公司内部其他开发者编写的程序。我们将介绍几种有效的方法来熟悉陌生代码,并逐步掌握修复问题的技巧。
理解代码的起点
上一节我们介绍了修复他人代码的常见场景,本节中我们来看看如何开始理解这些代码。如果代码包含注释且函数文档完善,阅读这些内容是理解代码逻辑的最佳起点。
记得在本课程早期介绍Python时,我们强调了编写代码时养成良好习惯的重要性。编写清晰的注释就是这样一个好习惯,它不仅有助于他人理解你的代码,也能帮助未来的你回顾自己的代码。
然而,许多代码缺乏足够的注释,导致我们难以理解其上下文。在这种情况下,你可以在阅读代码并理解其功能时,主动添加注释。
以下是添加注释的几个好处:
- 帮助你巩固对代码的理解。
- 如果你将这些注释反馈给原始开发者,可以帮助其他试图理解代码的人。

利用测试理解代码
除了注释,阅读与代码相关的测试也能帮助我们理解他人编写的代码。良好的测试可以告诉我们每个函数的预期行为。
查看现有测试可以揭示哪些用例未被考虑。但如果测试不足怎么办?就像添加额外注释一样,编写你自己的测试可以帮助你更好地理解代码的预期行为,并提高代码的整体质量。
这在修改原始代码时尤其有用,可以确保你的更改不会破坏其他功能。
阅读代码的策略
在我的工作中,我经常需要修改他人编写的代码。我肯定会阅读注释,有时也会参考测试。但最终,为了真正理解代码的运行逻辑,我必须亲自阅读代码。
那么,如何开始阅读他人的代码呢?这在一定程度上取决于个人偏好和项目规模。
如果代码只有几百行,通读所有代码是可行的。但当项目有成千上万行代码时,你无法阅读全部内容。你需要专注于与你试图修复的问题相关的函数或模块。
在这种情况下,一种可能的方法是从发生错误的函数开始,然后查看调用它的函数,依此类推,直到你掌握导致问题的上下文。
当然,如果你熟悉代码所用的编程语言,这个过程会容易得多。但你不需要是该语言的专家就能修复程序中的错误。如果你遇到了一个错误,并且通过调试足够理解了问题所在,即使你以前从未见过那种语言,也可能修复它。
通过练习提升技能
这项技能会随着练习而提高。因此,在你需要修复代码问题之前开始练习是有意义的。
选择一个你既使用又能访问其代码的程序,弄清楚它是如何执行某个特定操作的。跟踪代码直到你真正理解其运行逻辑。
例如,你可以查看你正在使用的Web服务器软件,了解它如何解析配置文件。或者查看一个你喜欢的Python模块,比如requests,弄清楚它如何处理接收到的数据。
通过这样做,你可以习惯阅读他人编写的代码并理解其功能。
另一个选择是选择一个你使用的开源项目,查看其公开问题列表,并尝试修复一个简单的问题。为此,你需要熟悉代码结构,理解其功能以及需要更改的内容。
通过练习,你将提高快速理解代码功能和所需更改的能力,同时帮助提高项目的整体质量。
总结
本节课中,我们一起学习了修复他人代码的策略。我们了解到,阅读注释和测试是理解代码的起点,主动添加注释和编写测试可以加深理解并提升代码质量。对于大型项目,需要采取从错误点出发、逐步追踪的策略来聚焦问题。这项技能需要通过实践来提升,例如分析熟悉程序的代码或参与开源项目的issue修复。接下来,我们将通过实践来修复几个导致程序崩溃的问题。
095:调试段错误 🐛

在本节课中,我们将学习如何调试一种称为“段错误”的程序崩溃。我们将通过一个实际的例子,了解段错误的表现形式,并学习如何使用核心文件和调试器来诊断和修复这类问题。

在之前的课程中,我们讨论了几种不同类型的程序崩溃。本节中,我们来看看“段错误”在实际运行中是什么样子的。

我们有一个简单的示例程序,它会因为段错误而崩溃。
当应用程序像这样崩溃时,拥有一个崩溃时的核心文件会很有用。核心文件存储了与崩溃相关的所有信息,以便我们或其他人能够调试问题所在。
这就像在崩溃发生时拍下一张快照,以便稍后进行分析。我们需要告诉操作系统我们希望生成这些核心文件。
我们通过运行 ulimit 命令,然后使用 -c 标志来指定核心文件,并设置为 unlimited 以表示我们希望生成任意大小的核心文件。
ulimit -c unlimited

完成此设置后,我们可以再次尝试执行我们的示例程序。好的,我们的崩溃程序已经生成了一个核心文件。让我们使用 ls -l 命令来查看它。
这个文件包含了程序崩溃时所有正在发生的信息。我们可以通过将其传递给 GDB 调试器来理解程序崩溃的原因。
我们将调用 gdb -c 来指定我们的核心文件,然后指定 example 来告诉调试器崩溃的可执行文件的位置。
gdb -c core example
当 GDB 启动时,它会显示一系列消息,包括其版本、许可证以及如何获取帮助。然后它会告诉我们程序以段错误结束。它显示崩溃发生在 strlen 函数内部,该函数属于系统库的一部分。
我们在这里看到的“No such file or directory”错误意味着我们没有该系统库的调试符号,但这没关系。我们相信 strlen 函数能正常工作,问题出在我们自己的代码有缺陷。
让我们使用 backtrace 命令来查看崩溃的完整回溯信息。
回溯列表中的第一个元素是发生崩溃的函数。第二个元素是调用该函数的函数,依此类推。在本例中,我们看到失败的 strlen 函数是由我们代码中的 copy_parameters 函数调用的,而 copy_parameters 又是由 main 函数调用的。
我们可以使用 up 命令在回溯中移动到调用函数,并查看导致崩溃的 copy_parameters 中的代码行。
我们看到有问题的行正在调用 strlen 函数,但不清楚为什么会失败。我们可以通过调用 list 命令来获取失败代码周围的更多上下文,该命令会显示当前行周围的代码。
这里我们看到一段 C 代码。如果你是第一次看 C 代码,可能会觉得有点困惑。这没关系。它和 Python 有一些相似之处,但也有一些相当不同的地方。我们看到有问题的第 10 行位于一个 for 循环体内。
for 循环用于迭代的变量叫做 i。让我们使用 print 命令查看 i 的值。
GDB 使用美元符号后跟一个数字来为它打印的每个结果提供单独的标识符。在本例中,结果是 1。换句话说,当崩溃发生时,i 的值为 1。
由于这个变量被用来访问一个名为 argv 的数组,让我们打印第一个元素 argv[0] 的内容,然后是第二个元素 argv[1]。
那些以 0x 开头的奇怪数字是什么?那些是十六进制数字,用于显示内存中存储某些数据的地址。在这里,GDB 告诉我们 argv 数组的第一个元素是一个指针,指向 ./example 字符串。第二个元素是一个指向 0 的指针,也称为空指针。0 从来不是一个有效的指针,在 C 语言中它通常表示数据结构的结束。
所以,我们的代码试图访问数组中的第二个元素,但该数组只有一个有效元素。换句话说,for 循环多进行了一次迭代。这被称为“差一错误”,是一种非常常见的错误。
在本例中,修复方法非常简单:我们需要将小于等于号(<=)改为严格的小于号(<),以便在倒数第二个元素处停止迭代。
在本视频中,我们初步了解了如何调试因段错误而崩溃的 C 语言应用程序。接下来,我们将讨论如何调试因异常而崩溃的 Python 应用程序。
本节课中,我们一起学习了段错误的基本概念、如何生成和分析核心文件,以及如何使用 GDB 调试器来定位 C 代码中的“差一错误”。掌握这些技能对于诊断和修复底层程序崩溃至关重要。
096:调试Python程序崩溃 🐛

在本节课中,我们将学习如何调试一个因异常而崩溃的Python程序。我们将通过一个实际的例子,了解如何解读错误信息,并使用Python调试器(PDB)来定位和解决问题。
从段错误到Python异常
上一节我们介绍了处理C或C++等语言编写的应用程序时常见的段错误问题。本节中我们来看看使用Python等语言时,我们通常需要处理的是导致程序崩溃的意外异常。
让我们看一个例子。我们有一个脚本,用于更新公司数据库中某些产品的描述。这是一个相当简单的脚本,它接收一个CSV文件作为参数,该文件包含需要导入的产品代码和描述数据。我们的脚本只是读取文件然后更新数据库。大多数时候,它运行良好。😊
但是,当由某个特定用户生成包含新描述的文件时,程序会因异常而失败。用户给我们发送了一个导致失败的文件,以便我们尝试找出问题所在。
检查文件与重现错误
首先,我们检查一下文件的内容。看起来没什么问题。让我们尝试执行程序。
程序因异常而失败。让我们仔细看看这个回溯信息,以便更好地理解它。
在底部,我们看到异常的名称(本例中是 KeyError)和消息(本例中是 ‘product_code’),这是导致失败的键名。在上面,我们看到一个函数调用列表,每个函数有两行信息:第一行告诉我们包含该函数的Python文件、行号和函数名;第二行显示该行的内容。
这个信息类似于我们在上一个视频中看到的回溯,但函数的顺序是相反的:底部的函数 update_data 是发生异常的地方。在它上面,我们看到 update_data 被 main 调用,而在最上面,我们看到 main 被模块级别的代码行调用。
那么,这里发生了什么?update_data 函数试图访问名为 row 的变量中的 product_code 字段,但由于某种原因,这失败了并引发了 KeyError。
通常,知道异常消息和发生异常的行已经足以理解发生了什么。但在某些情况下,比如这个,这还不够。
使用Python调试器(PDB)
是时候尝试使用Python调试器了。
我们通过运行 pdb3 来启动调试器,然后传递我们想要运行的脚本以及脚本所需的任何参数。
在我们的例子中,我们将调用 pdb3 update_products.py new_products.csv。启动调试器后,它会定位到我们脚本的第一行,并等待我们告诉它该做什么。
我们可以使用 next 命令逐条执行文件中的指令,但这里有很多代码,所以我们需要经过很多行才能到达失败的地方。或者,我们可以告诉调试器继续执行,直到程序完成或崩溃。我们现在就这么做。
程序以我们之前看到的相同方式失败了,但现在我们可以使用调试器来更好地理解为什么会出现这个烦人的 KeyError。
让我们打印变量 row 的内容。哈,这真的很奇怪。‘product_code’ 前面出现的那些字符是什么?
如果我们在网上搜索这个字符序列,我们会发现它代表字节顺序标记(BOM),在UTF-16中用于区分以小端序和大端序存储的文件。我们的文件是UTF-8格式,所以不需要BOM,但有些程序仍然包含它,而这正是导致我们脚本出错的原因。
那么,我们能做什么?幸运的是,其他人已经遇到了同样的问题并找到了解决方案。有一个特殊的编码值叫做 utf-8-sig,我们可以将其设置为 open 函数的 encoding 参数。😊
设置此编码意味着,当文件包含BOM时,Python会将其去除;当文件不包含时,则正常处理。让我们修改脚本代码以使用该编码,而不是默认编码。
我们将找到打开文件的地方,然后添加值为 ‘utf-8-sig’ 的 encoding 参数。
好的,我们已经做了更改。现在它会工作吗?让我们检查一下。耶,我们解决了问题!我们的脚本现在可以处理用户生成的文件,无论它们是否包含字节顺序标记。
调试器的更多功能
在最后两个视频中,我们简要介绍了GDB和PDB。我们仅仅触及了使用调试器可以进行的众多操作的表面。
还有大量更高级的调试功能,例如:
- 设置断点:让代码运行到某一行代码被执行时停止。
- 设置监视点:让代码运行直到某个变量或表达式发生变化。

我们还可以逐条指令地单步执行代码,以检查问题何时发生,以及更多功能。
我们不会在这里深入研究任何这些高级技术,但像往常一样,我们会在接下来的阅读材料中提供更多相关信息,以防你想了解更多。
在那之后,还有另一个练习测验,以确保你理解了所有这些内容。
总结
本节课中我们一起学习了如何调试一个因 KeyError 异常而崩溃的Python脚本。我们通过分析回溯信息定位问题,并使用Python调试器(PDB)检查运行时的变量状态,最终发现问题的根源是CSV文件开头的UTF-8 BOM字符。通过将文件打开编码改为 ‘utf-8-sig’,我们成功解决了问题,使脚本能够兼容处理带或不带BOM的文件。我们还了解到调试器拥有断点、监视点等强大功能,可用于更复杂的调试场景。
097:复杂系统中的故障排查 🕵️


在本节课中,我们将学习如何诊断和修复涉及多个服务和计算机的复杂系统中的故障。我们将探讨如何将单机故障排查的原则应用到更大规模的系统中,并介绍处理此类问题时的关键策略。
到目前为止,我们讨论的都是如何诊断和修复局限于单台计算机的错误。这对于单用户使用的计算机来说是常见情况。

然而,一旦我们开始进入涉及许多不同服务的复杂系统,就需要从更宏观的角度审视问题,并理解不同计算机之间如何交互。
假设你负责公司的电子商务网站。用户最近访问的页面中,约有20%的请求会返回“内部服务器错误”。
如何定位问题?
你需要应用我们在单机故障排查中学到的相同原则,但这次是在更大的规模上。
因此,你需要检查提供服务的服务器中的日志消息,看看是否能找到任何指向问题根源的额外信息。

你需要查找与故障服务相关的特定日志,同时查看一般的系统日志,以判断是否存在影响整个服务器的普遍问题。
对于这个例子,假设你在日志中发现了一系列条目,显示“来自服务器的响应无效”。这并非一个很有用的错误信息。
你不知道请求是什么,也不知道响应是什么。但这至少是一个线索,表明正在发生的问题与整个系统中的其他服务有关。
追溯变化
我们提到这个问题是最近才开始出现的。因此,找出系统正常工作与开始故障之间发生了什么变化是合理的思路。
是否有新版本的系统被部署?是否有与请求相关的任何变更?
假设故障发生在一个周二上午,而该服务的最新版本是在前一周发布的。直到今天为止,一切工作正常。请求看起来也正常,没有异常情况。


所以服务本身可能没问题。但系统中涉及的其他服务呢?
底层系统之一(如数据库、身份验证服务或其他后端服务器,如库存、计费或采购系统)是否有新版本?
查看最近的变更,你发现当天早些时候,前端和后端服务之间使用的负载均衡器进行了一系列更改。
实施回滚与改进日志
由于你唯一的线索是“来自服务的响应无效”,你并不确定这些变更就是罪魁祸首,但它们看起来确实可疑。
只要可能,最佳策略是回滚你怀疑导致问题的变更,即使你并不100%确定这就是真正的原因。
如果你的基础设施允许轻松回滚,在进行任何进一步调查之前,请尝试这样做。因为这样,如果它是原因,你将恢复服务的健康状态;如果回滚没有帮助,你也可以排除这个变更作为可能的原因。

无论你是否进行回滚,当遇到无用的错误消息时,改进它们是一个好主意。

与其让错误只说“响应无效”,不如将其更改为包含请求和响应是什么,以及为什么响应无效。这样,下次你尝试调试类似问题时,就已经有了更多可用的信息。

对于这个例子,如果错误包含了这些信息,你就会看到无效的响应是一个404错误。
这起因于将一台服务器作为库存系统的一部分添加到了资源池中,但这台服务器实际上属于采购系统。
处理后续故障
现在,假设几周后,你发现同一个服务中又出现了一系列内部服务器错误。
你可能会倾向于再次假设是负载均衡器的故障。但到现在,你应该知道首先要查看日志,看看能发现什么。

没有理由认为这次的错误会和上次相同。查看日志时,你可能会注意到,例如,实际上只有一台前端服务器受到了问题的影响。所有其他机器都在成功提供其内容。
在这种情况下,你首先要做的是将这台机器从可以提供此服务的服务器池中移除。这样,在你调查故障机器发生了什么的同时,可以避免用户遇到更多错误。
复杂系统的最佳实践

正如你现在可能已经意识到的,在处理此类复杂系统时,拥有良好的日志对于理解正在发生的事情至关重要。

除此之外,你还需要对服务的运行状态进行良好的监控,并对所有变更使用版本控制,以便快速检查发生了什么变化,并在需要时进行回滚。
能够在必要时非常快速地部署新机器也很重要。这可以通过保留备用服务器以备不时之需来实现。
或者通过拥有一个经过测试的流水线,允许你按需部署新服务器。
如今,许多公司都有自动化流程,可以将服务部署到在云中运行的虚拟机上。这可能需要一些时间来设置,但一旦完成,你就可以非常轻松地增加或减少正在使用的服务器数量。
这在调查和解决问题时会有很大帮助。但需要注意的一点是,当服务器作为虚拟机运行时,尤其是在云中运行时,这些服务可能会受到外部限制。
像可用CPU时间、内存或网络带宽等资源可能会被人为限制。不仅如此,某些外部服务的使用也可能受到限制,例如你可以同时拥有多少个数据库连接,或者可以存储多少数据。如果这些限制导致你的应用程序出现问题,你可能需要重新考虑如何使用资源。
本节课中,我们一起学习了在复杂系统中面对问题时可以使用的一系列技术:查看可用日志、找出系统上次正常工作以来的变化、回滚到先前状态、从资源池中移除故障服务器或按需部署新服务器。
接下来,我们将探讨处理更大规模事件的另一个不同方面:沟通与文档。
098:事件期间的沟通与文档记录 📝


在本节课中,我们将学习在解决IT问题时,如何有效地进行沟通和记录文档。这对于高效协作、避免重复劳动以及为未来提供参考至关重要。
概述
到目前为止,我们已经讨论了如何对存在特定问题的计算机或系统进行故障排除。我们涵盖了如何获取足够信息以确定根本原因,然后应用必要的修复措施。
然而,所有这些工作还有另一个重要方面,即如何处理与受问题影响人员的沟通,以及作为一个团队在解决大型问题时如何分配任务。凭借目前所学和过往经验,你或许能出色地解决问题,但如果在沟通方面做得不好,最终可能会导致大量沮丧的用户致电询问情况。
文档记录的重要性
在解决问题时,将你的操作记录在缺陷报告或工单中始终是一个好主意。
如果公司没有这样的系统,可以使用文档、文本文件、Wiki或任何你可以访问的工具。记录你的操作可以让你追踪已尝试的方法及其结果。
这看似不必要,但在经过一整天的故障排除后,我们很容易忘记尝试过什么,或者某个特定操作的结果是什么。此外,以电子形式保存所有这些信息,可以让你轻松地与其他团队成员共享收集到的数据。
例如,如果你回滚了某个后来发现无关的更改,完整的记录过程可以帮助你记得再次将其向前推进。
与受影响方的沟通
在解决问题期间,与受影响方进行清晰沟通非常重要。他们希望知道你发现了什么问题、有哪些可用的临时解决方案,以及何时能获得下一次更新。
如果你不知道问题是什么,很难给出修复时间的估计,但你仍然可以及时提供关于你正在开展工作的更新。这种定期沟通是有帮助的,无论事件规模大小。
但受影响的人越多,你就越需要提供定期更新,并清晰地说明用户可以做什么以及可以期待什么样的解决方案。这样,他们可以更好地计划和组织自己的时间。
例如,如果网络访问中断,你需要让人们知道修复是需要一两个小时,还是需要一整天。这些信息可能会影响人们是选择当面讨论问题几个小时,还是决定在家工作。
团队协作与角色分配
如果问题足够严重,需要更多人参与寻找解决方案,你们应该商定谁负责哪些任务。
以下是任务分配的一些示例:
- 可以安排一个人负责寻找临时解决方案,而另一个人负责理解问题的根本原因并寻找长期修复措施。
- 如果问题有很多可能的原因,可以将这些原因分配给团队成员,让他们并行处理。
除了寻找根本原因和解决方案的人员外,还需要指定一个人负责与受影响人员沟通。这可以让团队避免忘记更新跟踪问题,或者更糟的是,提供相互矛盾的信息。这位沟通负责人需要了解进展情况,并及时提供关于当前状态以及问题解决所需时间的更新。


他们可以作为用户提问的“屏障”,让团队其他成员专注于实际问题。
同样,应该有一人负责向团队成员分配不同的任务。这个人有时被称为事件指挥官或事件控制员,需要纵观全局,决定如何最佳利用可用资源。
他们可以确保团队成员之间没有重复工作,并且一次只有一个人修改生产系统。多人同时对系统进行重叠的更改可能会导致混乱的结果,使故障时间更长。


当然,这种角色划分在发生大型事件且有大型团队参与寻找解决方案时最有意义。如果只有两三个人处理问题,商定谁负责什么仍然很重要,但可能不需要使用任何特殊的角色名称。
事件解决后的总结
问题解决后,总结有用的信息至关重要。
你需要包含的最重要信息包括:
- 根本原因
- 诊断问题并找到该根本原因的过程
- 为解决问题所采取的措施
- 为防止问题再次发生需要做的事情
根据问题的规模和受影响的人数,这份总结可以只是你用来跟踪工作的缺陷报告或工单的最后一次更新,也可以是一份完整的事后分析报告。


总结
本节课中,我们一起学习了在IT事件处理过程中进行有效沟通和文档记录的关键实践。我们明确了记录操作步骤对于追踪进度和团队协作的重要性,探讨了如何向受影响用户提供清晰、及时的更新。我们还介绍了在团队协作中分配特定角色(如沟通负责人、事件指挥官)以提升效率的方法。最后,我们强调了在问题解决后进行总结,记录根本原因、诊断过程、修复措施和预防方案的必要性,为未来积累经验。
099:编写有效的事后分析报告 📝


在本节课中,我们将要学习如何编写一份有效的事后分析报告。事后分析报告是记录和分析已发生事件的重要文档,其核心目标是总结经验教训,防止问题再次发生,而非追究责任。

事后分析报告的目的与重要性
上一节我们介绍了在故障排除过程中沟通与文档记录的重要性。本节中,我们来看看当问题足够严重时,我们应如何通过撰写事后分析报告来记录事件。
事后分析报告是描述事件细节的文档,旨在帮助我们从中吸取教训。撰写报告的目的不是指责导致事件的人,而是从已发生的事件中学习,以防止相同问题再次发生。
撰写事后分析报告之所以重要,是因为它能帮助我们避免再次处理相同问题,或至少学会如何更好地应对下一次事件。
报告的核心内容
虽然事后分析报告对处理重大事件极为有用,但你无需等到发生大事时才撰写第一份报告。你可以为任何有学习价值的事件练习撰写报告,无论事件多小。这样,当需要在重大事件后撰写报告时,你就知道如何专注于最重要的事情:从问题中学到什么,以及未来如何预防。
那么,事后分析报告应包含哪些内容呢?具体结构可能因个人偏好和处理的事件类型而异。

以下是报告通常应包含的核心部分:


- 问题原因:详细说明导致问题的根本原因。
- 影响范围:描述问题造成的影响。
- 诊断过程:记录如何诊断和定位问题。
- 短期补救措施:说明应用了哪些临时解决方案。
- 长期修复建议:提出为防止问题再次发生而推荐的长期改进方案。

如果文档较长且需要与多人分享,建议包含一个摘要,重点说明根本原因、影响以及为防止问题再次发生需要采取的措施。
记录成功之处与报告的价值
在事后分析报告中记录做得好的方面也很有用。在处理问题时,我们可能会意识到,如果没有某些可用的工具或系统,情况可能会糟糕得多。
例如,我们可以说通过回滚到上一个版本快速解决了问题,或者由于我们拥有良好的监控和告警系统,在用户甚至注意到之前就发现了问题。记录成功之处有助于展示我们系统的有效性,并为维持这些系统的运行提供依据。
撰写事后分析报告有时能帮助你更好地理解你所负责的服务。今年早些时候,我负责的一项服务发生了大规模中断,我需要提供事件相关信息。为此,我需要解析数百GB的归档日志数据,以证明服务从未收到过某些数据。在这个过程中,我意识到需要改进我们工具记录的数据,以提供更好的信息和报告。
在IT领域外练习
你甚至可以在IT领域之外练习撰写事后分析报告。例如,如果你烤的饼干没有达到预期效果,可以记录你做了什么、哪里出了问题、哪些做对了,以及未来如何改进结果。你可以用任何爱好来练习,比如摄影、3D打印或自酿啤酒。
你并不总是需要把整个事情都写下来。有时,记在脑子里就足够了。例如,如果你骑车上班时发现背背包会让肩膀酸痛,可以在心里记下给自行车加个篮子,下次把背包放进去;或者,如果你上次旅行时天气比预期冷,而且忘了带外套,可以在心里记下下次出发前应该查看天气。
再次强调,事后分析报告最重要的部分是我们能为未来学到什么。因此,如果你不是撰写整篇文档,而是创建一段事件摘要,请记住将那段摘要的焦点放在“如何能做得更好”上,而不是放在导致事件的任何错误上。
总结
本节课中,我们一起学习了如何编写有效的事后分析报告。我们明确了报告的核心目的是学习与改进,而非追责。我们了解了报告应包含问题原因、影响、诊断过程、短期和长期措施等核心内容,并认识到记录成功之处同样重要。最后,我们探讨了可以通过日常小事练习撰写报告,并始终将焦点放在未来的改进上。
100:模块 3 总结 - 处理程序崩溃 🧩

在本节课中,我们将总结模块三的核心内容,重点回顾如何处理程序崩溃、错误和各种事故。我们将系统梳理在不同情境下的应对策略,并为你接下来的实践练习做好准备。

恭喜你坚持学习到这里。在过去的视频中,你已经学习了许多关于各种崩溃、错误和事故的知识。
我们探讨了当你无法访问代码时,可以采取哪些变通方法来解决问题。
我们也研究了当你能够访问代码时,解决这些问题的各种方法。
并且,我们还查看了如何处理涉及复杂系统和大型团队的更严重事故。
了解计算机和软件可能以不同方式失效,起初可能令人畏惧。
但请记住,所有这些工具都能帮助我们更好地找到解决方案。
我最喜欢的一点是,完成任何任务都有许多不同的方法。
因此,如果你遇到困难,总可以尝试新的角度。
没有什么比解决一个棘手问题更让人有成就感了。在我们的所有示例中,我们一直在应用本课程开始时学到的相同技术。
我们收集信息,直到理解问题所在。
我们找到根本原因,然后着手制定短期和长期的修复方案。
现在,你对于应用程序意外崩溃时该做什么、如何使用排除法找出问题所在,以及根据故障类型可以应用何种解决方案,都有了更清晰的认识。
接下来,我们为你准备了一些需要解决的问题:一个是程序崩溃但你无法修改代码的情况,另一个则是你可以修改代码的情况。
这听起来可能有些棘手,但请记住,你随时可以复习已有的学习材料来帮助自己理清思路。
祝你好运,你一定能做到。
本节课总结
本节课中,我们一起回顾了处理程序崩溃和系统事故的完整流程。我们学习了在有无代码访问权限下的不同调试方法,并强调了系统化问题解决策略的重要性:收集信息 -> 定位根因 -> 实施修复。记住,面对复杂问题总有多种解决路径,保持耐心并善用所学工具是关键。
101:资源管理介绍 🧠


在本节课中,我们将要学习如何识别和管理计算机系统中的关键资源,包括内存、磁盘空间和网络带宽。我们将探讨如何诊断资源耗尽的问题,并学习如何优化应用程序以更有效地利用这些有限资源。
欢迎回来,我们已接近课程的尾声。祝贺你坚持学习到这里。
希望你已经开始认识到这些课程在真实IT环境中的实用性,并且对自己新掌握的故障排除技能充满信心。
在之前的模块中,你学习了如何对各种情况进行故障排除和调试。
我们看到了如何通过缩小范围和隔离问题,来找到程序运行缓慢或意外崩溃的根本原因。
我们还学习了如何理解不同的错误信息,并使用操作系统中的可用工具来诊断问题所在。
有时,我们面临的问题不是某个东西不工作,而是它没有达到应有的工作效率。
这通常归结于未能充分利用系统中的可用资源。
例如,如果我们的程序使用了太多内存,我们或许可以通过给计算机添加更多内存来解决问题。
但如果程序一开始就不使用那么多内存,岂不是更好?
我们计算机中的所有资源都是有限的,因此我们需要确保运行的应用程序能最有效地利用它们。
我们需要检查运行的软件是否没有为不需要的东西浪费内存。
或者检查磁盘空间是否确实被重要的数据所使用,以及通过网络传输的信息是否确实是我们关心的信息。
总有需要清理的东西。
在接下来的几个视频中,我们将探讨如何弄清楚那些耗尽计算机资源(无论是内存、磁盘还是网络连接)的程序究竟发生了什么。
然后,我们将讨论管理我们最宝贵的资源:时间。
我们将学习如何审视那永无止境的任务清单,并通过确定工作优先级和避免不必要的干扰,来确保我们明智地利用时间。
之后,我们将讨论如何运用所有新学到的知识来尝试避免未来的问题。
积极主动可以帮助我们在事情不按计划发展时缓解问题(提示:事情很少按计划发展),甚至可以通过在测试基础设施中发现问题来完全避免问题。
最后,你将再次有机会尝试解决一个现实世界的挑战,将你的技能付诸实践。
准备好开始了吗?
好的,我们开始吧。
102:内存泄漏及如何防止 🧠💾
概述
在本节课中,我们将要学习什么是内存泄漏,它如何影响计算机系统,以及如何防止和诊断内存泄漏问题。我们将从基本概念开始,逐步深入到在不同编程语言环境下的具体表现和排查工具。
什么是内存泄漏?🤔
大多数应用程序需要将数据存储在内存中才能成功运行。
我们之前提到过,在用C或C++等语言编写程序时,进程如何与操作系统交互以请求内存块,然后在不再需要时释放它们。程序员负责决定请求多少内存以及何时归还。
由于我们是人类,有时可能会忘记释放不再使用的内存。
这就是我们所说的内存泄漏。当一块不再需要的内存没有被释放时,就会发生内存泄漏。
如果内存泄漏很小,我们甚至可能不会注意到它,并且它可能不会引起任何问题。
但是,当泄漏的内存随着时间的推移变得越来越大时,它可能导致整个系统开始运行异常。这可不妙。
内存泄漏的后果 ⚠️
当一个程序使用了大量内存时,其他程序将需要被交换出去,所有程序都会运行缓慢。
如果程序使用了所有可用内存,那么任何进程都将无法请求更多内存,系统会开始以奇怪的方式出错。
当这种情况发生时,操作系统可能会终止进程以释放一些内存,从而导致不相关的程序崩溃。
高级语言中的内存管理 🔄
你可能会想,如果我不打算用C或C++编程,我为什么要关心这个?确实,像Python、Java或Go这样的语言为我们管理内存,但如果我们不能正确使用内存,仍然可能出错。为了理解这是如何运作的,让我们看看这些语言做了什么。
首先,当我们创建变量时,它们会请求必要的内存。然后,它们运行一个名为“垃圾回收器”的工具,负责释放不再使用的内存。
为了检测何时需要释放内存,垃圾回收器会查看正在使用的变量及其分配的内存,然后检查是否有任何内存部分没有被任何变量引用。
垃圾回收器如何工作?🔍

例如,假设你在一个函数内部创建了一个字典。用它来处理文本文件,计算文件中单词的频率,然后返回使用最频繁的单词。
当函数返回时,该字典不再被引用,因此垃圾回收器可以检测到这一点并归还未使用的内存。
但是,如果函数返回了整个字典,那么它仍然在使用中,内存将不会被归还,直到情况不再如此。

当我们的代码持续让变量指向内存中的数据时,比如代码本身的变量,或者列表、字典中的元素,垃圾回收器就不会释放那块内存。
换句话说,即使语言为我们处理了内存的请求和释放,我们仍然可能看到与内存泄漏相同的效果。如果内存持续增长,代码可能导致计算机内存耗尽,就像内存泄漏一样。
内存泄漏的影响范围 📈
操作系统通常会在进程结束后释放分配给该进程的任何内存,因此内存泄漏对于短期运行的程序来说问题不大,但对于在后台持续运行的进程来说,可能变得尤其成问题。
比这些更糟糕的是由设备驱动程序或操作系统本身引起的内存泄漏。在这些情况下,只有完全重启系统才能释放内存。
假设你注意到你的电脑似乎经常内存不足。你观察一段时间内运行的程序,发现有一个进程随着时间的推移使用了越来越多的内存。如果你重启该进程,它开始时占用内存很少,但很快又需要越来越多。如果是这种情况,那么这个程序很可能存在内存泄漏。
如何诊断内存泄漏?🛠️
如果我们怀疑一个程序有内存泄漏,我们能做什么?
我们可以使用内存分析器来弄清楚内存是如何被使用的。与调试器一样,我们需要为应用程序的语言使用正确的分析器。
以下是用于不同语言的分析工具:
- 对于分析C和C++程序,我们将使用Valgrind,我们在之前的视频中提到过它。
- 对于分析Python程序,我们有许多不同的工具可供使用,具体取决于我们想要分析什么。我们可以详细到分析单个函数的内存使用情况,也可以宏观到监控一段时间内的总内存消耗。


使用分析器,我们可以看到在某个时间点哪些数据结构使用了最多的内存,或者在不同时间点拍摄快照并进行比较。
这些工具的目标是帮助我们识别哪些我们保存在内存中的信息实际上并不需要。
优化与总结 📝
在尝试更改任何内容之前,首先测量内存的使用情况非常重要。否则,我们可能优化了错误的代码段。
有时我们需要将数据保存在内存中,这没问题。但要确保只保留实际需要的数据,并释放任何你不会再使用的东西。这样,垃圾回收器就可以将内存归还给操作系统。
当然,如果你检查后发现内存使用正确,但仍然发现可用内存耗尽,那么可能是时候升级硬件了。
你把这些都记在“内存”里了吗?别忘了,关于内存分析,还有很多内容我们来不及涵盖,但我们在接下来的阅读材料中提供了关于其中一些分析工具的更多信息的链接。
接下来,我们将讨论另一种可能需要特别关注的资源:磁盘空间。


总结
本节课中,我们一起学习了内存泄漏的概念、成因及其对系统的影响。我们了解到,即使在有自动垃圾回收的高级语言中,不当的编程实践也可能导致类似内存泄漏的效果。最后,我们介绍了如何使用内存分析工具来诊断问题,并强调了在优化前进行测量以及适时释放无用数据的重要性。
103:管理磁盘空间 💾
在本节课中,我们将学习如何识别和处理计算机磁盘空间不足的问题。磁盘空间是系统运行的关键资源之一,管理不当可能导致性能下降、程序崩溃甚至数据丢失。我们将探讨磁盘空间被占用的常见原因、排查问题的步骤以及一些典型的故障场景。
磁盘空间为何重要?
程序运行需要磁盘空间来存储各种数据,例如安装的库文件、应用程序生成的数据、缓存信息、日志、临时文件或备份。
如果计算机磁盘空间不足,可能是因为存储了过多数据,例如安装了过多应用程序或存有大量大型文件。但也可能是程序行为不当,未能及时清理临时文件或缓存。
随着可用磁盘空间减少,系统整体性能通常会下降。数据在磁盘上变得碎片化,操作变慢。当硬盘已满时,程序在尝试写入磁盘时可能突然崩溃。硬盘满甚至可能导致数据丢失,例如某些程序可能在写入更新版本前截断文件,然后写入失败,从而丢失原有数据。
如果发生这种情况,运行应用程序或查看日志时可能会看到类似“设备上没有剩余空间”的错误。
如何应对磁盘空间不足?
如果是一台用户机器,可以通过卸载不用的应用程序或清理不需要的旧数据来轻松解决。
但如果是一台服务器,则需要更仔细地调查。问题可能在于需要为服务器添加额外硬盘以提供更多空间,也可能是某个应用程序行为异常,用无用数据填满了磁盘。
要查明原因,需要查看空间的使用情况以及哪些目录占用了最多空间。然后深入分析,确定大块空间是被有效信息占用,还是被应清除的文件占用。
例如,在数据库服务器上,大部分磁盘空间预期会被数据库存储的数据占用。在邮件服务器上,则主要是该服务用户的邮箱数据。但如果发现大部分数据存储在日志或临时文件中,那就说明出了问题。
常见的异常行为模式
一种常见的不当行为模式是程序反复向系统日志记录错误信息。这可能有多种原因。
例如,操作系统可能因配置问题而不断尝试启动一个失败的程序。每次重试都会生成新的日志条目,如果每秒重试多次,就会占用大量空间。
或者,服务器活动量很大,日志是真实的,但数量过多。在这种情况下,可能需要调整日志轮转工具的配置,使其更频繁地轮转,以确保只保留必要的内容。
在其他情况下,磁盘可能因程序生成大型临时文件且未能清理而变满。
例如,应用程序在正常关闭时可能会清理临时文件,但如果崩溃,就会留下这些文件。或者,可能只是编程错误,创建了临时文件却从未清理。
在这种情况下,理想的做法是进行一些维护工作来修复程序并正确删除这些文件。如果不可能,则可能需要编写自己的脚本来清除它们。
一种棘手的调试情况:已删除的文件
你可能会疑惑:已删除的文件怎么会占用空间?
答案是:如果一个程序打开了一个文件,操作系统允许该程序读写该文件,无论该文件是否被标记为删除。
因此,许多程序在创建临时文件后立即将其删除,以避免后续清理失败的问题。这样,进程可以在文件打开时对其进行读写。然后,当进程结束时,文件被关闭并实际删除。
这个系统被广泛使用,对大多数进程来说工作正常。但如果由于某种原因,这个临时删除的文件变得非常大,它最终可能占用所有可用磁盘空间。如果发生这种情况,在试图找出大部分数据去向时,我们会感到困惑,因为我们看不到这些已删除的文件。
要检查这种特定情况,我们需要列出当前打开的文件,并筛选出已知已删除的文件。
我们将在下一篇阅读材料中提供相关命令的指引。


当然,磁盘空间过满可能还有各种其他原因。请记住,每当发生这种情况时,你的处理流程应保持一致:需要花时间调查是什么在使用磁盘;检查这是否符合预期或是异常;找出解决方法;最重要的是,如何防止它再次发生。
接下来,我们将讨论另一个可能引发问题的资源:网络。
总结

本节课我们一起学习了磁盘空间管理的重要性。我们了解了磁盘空间不足的常见原因,包括数据过多和程序行为不当。我们探讨了排查问题的步骤:检查使用情况、确定是否异常、寻找解决方案并制定预防措施。我们还分析了几种典型场景,如日志膨胀、临时文件未清理以及已删除文件仍占用空间等棘手问题。掌握这些知识有助于你有效维护系统存储资源,确保其稳定运行。
104:网络饱和与性能分析 🚦


在本节课中,我们将学习网络性能的两个核心概念:延迟和带宽。我们将探讨它们如何影响网络连接的速度,以及在不同场景下如何优化网络性能。
网络性能的核心因素
在IT工作中,您会与遍布互联网的服务进行交互。有时您可能连接到本地网络上的服务,下一刻则使用位于不同大陆数据中心的服务。
如果网络连接良好,您可能无法分辨所浏览网站的托管位置。但如果处理速度不理想的网络服务,您可能需要获取有关所用连接的更多详细信息。
决定通过网络获取数据所需时间的两个最重要因素是连接的延迟和带宽。

延迟

延迟是指从一点发送一个数据位到另一点接收它之间的延迟时间。

这个值直接受两点之间的物理距离以及它们之间中间设备数量的影响。
带宽
带宽是指每秒可以发送或接收的数据量。
这实际上是连接的数据容量。
互联网连接通常根据客户将看到的带宽量来销售。但需要注意的是,与网络服务之间传输数据的可用带宽将由每个端点以及它们之间每个跃点的可用带宽决定。


延迟与带宽的交互作用
为了理解延迟和带宽如何交互,请思考通过互联网访问网站时发生的情况。

如果Web服务器托管在海洋彼岸,延迟可能约为100毫秒。这是您的请求到达服务器所需的时间。


服务器随后会生成响应并将其发送回给您。
响应的第一个数据位将再次花费100毫秒跨越大洋到达您的计算机。
一旦响应开始传输,其余数据到达所需的时间就由带宽决定。


如果两点之间的可用带宽为每秒10兆比特,您将能够每秒接收1.25兆字节。因此,对于一个内容约为1兆字节的网站,由于初始延迟占总下载时间的额外20%,这个较大的初始延迟将很明显。


但如果内容是10兆字节或更多,初始延迟将小于总下载时间的5%,因此影响较小。



优化网络性能的策略
假设您正在尝试找出网络连接速度不如预期的原因。

请记住,如果您传输大量小数据片段,您更关心延迟而非带宽。
在这种情况下,您需要确保服务器尽可能靠近服务用户,目标延迟尽可能低于50毫秒,最坏情况下不超过100毫秒。

另一方面,如果您传输大块数据,您更关心带宽而非延迟。


在这种情况下,您希望拥有尽可能多的可用带宽,无论服务器托管在何处。

可用带宽与连接共享


“可用带宽”是什么意思?计算机可以同时与互联网的许多不同点传输数据。

但所有这些独立的连接共享相同的带宽。

每个连接将获得一部分带宽。

但分配不一定均匀。

如果一个连接正在传输大量数据,可能就没有带宽留给其他连接。

当发生这些流量拥塞时,延迟可能会大幅增加,因为数据包可能会被保留,直到有足够的带宽发送它们。
您可能已经在自己的计算机上经历过这种情况。如果您曾经同时运行多个使用同一网络的应用程序,整体连接速度可能会变慢。
您可以通过运行像iftop这样的程序来检查哪些进程正在使用网络连接。这显示了每个活动连接通过网络发送的数据量。
您可能还注意到,共享同一网络的用户越多,数据传入速度越慢。这对于家庭连接和办公室连接都是如此。
无论您拥有多少带宽,它都是一种有限资源。因此,您需要谨慎地在用户之间分配它。
流量整形与连接管理
如果某些应用程序使用了如此多的带宽,以至于其他应用程序无法传输更多数据,可以通过使用流量整形来限制每个连接占用的带宽。
这是一种通过为通过网络发送的数据包标记不同优先级来避免大块数据占用所有带宽的方法。


通过相应地设置优先级,发送和接收较小数据包的进程可以正常工作,而需要最多带宽的进程可以使用剩余带宽。


连接限制与资源管理

单台计算机上可以建立的网络连接数量也有限制。
这通常不是问题,但软件中可能存在错误,导致其打开过多连接或保持旧连接打开,即使它们不再使用。如果这种情况发生在服务器上,在保持这些连接打开的程序关闭它们之前,新用户将无法连接到它。
总结
在本节课中,我们一起学习了网络性能的两个关键指标:延迟和带宽。我们探讨了它们如何影响不同场景下的网络体验,例如传输小数据时延迟更重要,传输大数据时带宽更关键。我们还了解了可用带宽的共享性质、流量整形的作用以及连接管理的重要性。理解这些概念有助于诊断和优化网络性能问题。
105:处理内存泄漏 🐛💾

在本节课中,我们将学习什么是内存泄漏,如何识别它们,以及如何使用工具来诊断和定位Python程序中的内存问题。

概述


应用程序可能因多种原因请求大量内存。有时这是程序完成任务所必需的,有时则是由软件行为异常引起的。本节我们将通过实例触发异常行为,观察其表现,并学习使用工具来诊断内存问题。
触发异常内存行为
首先,让我们自己触发一个异常行为来观察其表现。我们将使用一个名为UXterm的终端。
我们配置了这个终端,使其拥有一个非常长的滚动缓冲区。滚动缓冲区是一个方便的功能,允许我们向上滚动查看已执行的命令及其输出。

缓冲区的所有内容都保存在内存中。因此,如果我们将其设置得非常长并设法填满它,就会导致我们的计算机在正常使用下耗尽内存。在正常情况下,这可能需要很长时间才会发生。
但如果我们运行一个持续生成大量输出的命令,我们就能相当快地填满那个缓冲区。例如,我们运行一个像 od -cx /dev/urandom 这样的命令。
这个命令将读取由/dev/urandom设备生成的随机数,并以字符和十六进制数的形式显示它们。由于/dev/urandom设备会持续提供越来越多的随机数,这个命令将一直运行下去。
我们的命令正在填满滚动缓冲区,导致你的计算机需要越来越多的内存。在另一个终端中,让我们打开top命令来查看发生了什么。
按下 Shift + M,我们告诉top我们希望按程序使用的内存量对它们进行排序。我们看到Xterm使用的内存百分比正在飞速上升。
让我们通过按下 Control + C 来停止填满缓冲区的进程。这样,我们停止了填满缓冲区的命令,但终端仍然分配了那块内存,用于存储滚动缓冲区中的所有行。
分析 top 命令输出
让我们更详细地看一下top的输出。有一系列不同的列,显示每个进程的数据。
- RES列:标记为特定进程保留的动态内存。
- SHR列:用于跨进程共享的内存。
- VIRT列:列出为每个进程分配的所有虚拟内存。这包括进程特定的内存、共享内存以及存储在磁盘上但映射到进程内存中的其他共享资源。进程在VIRT列中拥有较高的值通常是正常的。
通常指示问题的是RES列。
现在,让我们关闭另一个终端,以便它释放所有保留的内存。在这个例子中,我们看到了一个程序不断请求越来越多内存的样子。这是一个非常极端的例子。大多数内存泄漏不会以这种速度发生。
识别缓慢的内存泄漏
通常需要很长时间我们才会注意到一个程序占用了比它应该占用的更多的内存,并且可能很难区分实际需要的内存和被浪费的内存。
但是,查看top的输出并将其与之前的情况进行比较,通常是任何内存泄漏调查的开始方式。
让我们看另一个例子。我们有一个脚本,用于分析网页中单词的频率。当只有几个网页时,这个脚本运行良好,但如果我们尝试给它所有维基百科的内容,它就会开始耗尽所有内存。
让我们先运行它,看看会发生什么。好的,它正在运行,并且需要很长时间才能完成。毕竟,它正在处理大量的文章。
当这个程序运行时,让我们在另一个终端中查看top的输出,看看我们能发现什么。
我们看到有一堆不同的content_stats进程在运行。这是因为我们的脚本使用了我们在早期视频中看到的多进程技术,以并行化信息处理并尽可能快地获得结果。
看起来这些脚本占用了大量内存,所以让我们排序以查看详细信息。哇,我们看到其中一个进程使用的内存尤其持续增长。
应用程序正在处理大量数据并生成一个字典,因此预计它会使用一些内存,但不会这么多。这看起来像是程序在内存中存储了比它应该存储的更多的东西。
这个程序相当复杂,所以我们可以在这里使用内存分析器的帮助来找出问题所在。让我们现在停止它,并使用分析器来找出我们计算机的内存去了哪里。
使用内存分析器
为此,我们需要使用一个简化版本的代码,因为分析多进程应用程序的内存使用情况特别困难。并且,我们只处理几篇文章,而不是所有文章,以便我们可以快速检查内存消耗。
让我们打开我们简化的脚本看一看。

我们将使用一个名为 memory_profiler 的模块。这是Python可用的众多不同内存分析器之一。

我们在主函数定义之前添加了 @profile 这个标签,告诉分析器我们想要分析它的内存消耗。
@profile
def main():
# ... 函数代码 ...
这种类型的标签在Python中称为装饰器,它用于在不修改代码的情况下为函数添加额外的行为。在本例中,额外的行为是测量内存使用情况。

代码的其余部分基本上与原始代码相同,它只使用单个进程,并且限制为50篇文章,而不是另一个脚本要处理的数千篇文章。
我们启用了内存分析器来运行脚本。这只是读取50篇文章,但由于所有的内存分析,我们的脚本变慢了很多。
程序完成后,内存分析器会提供关于哪些行在增加或减少程序使用的内存中的数据。
第一列显示每行代码执行时所需的内存总量。
第二列显示每行特定代码导致的内存增量。
我们可以看到,在处理了50篇文章之后,程序已经占用了130兆字节的内存。难怪当我们试图处理所有文章时会耗尽内存。
我们可以看到,需要最多内存的变量是article和text,分别约为4兆字节和3兆字节。这些是我们正在处理的文章,当我们在计算文章中的单词时,它们占用空间是正常的,但一旦我们处理完一篇文章,我们就不应该再保留那块内存。
你能发现问题吗?在代码的最后,它存储了文章以保留对它的引用。但它存储的是整篇文章。如果我们想保留包含某个单词的所有文章的引用,我们可以存储标题或索引条目,绝对不是全部内容。
总结
本节课中,我们一起学习了内存泄漏的基本概念。我们通过极端和实际的例子,观察了程序异常占用内存的表现。我们学会了使用 top 命令来监控系统的内存使用情况,并识别出问题的进程。最后,我们引入了 memory_profiler 这个强大的Python工具,通过装饰器来精确分析代码中每一行的内存消耗,从而定位到导致内存泄漏的具体代码行(例如,不必要地存储了大量数据)。关于内存管理和内存分析,还有更多内容,我们将在后续阅读材料中提供更多资源链接。
106:48_专注于重要任务 🎯


在本节课中,我们将学习如何有效管理工作中最宝贵的资源——时间。我们将介绍一种名为“艾森豪威尔决策矩阵”的工具,帮助你区分任务的紧急与重要程度,从而将精力集中在能创造最大价值的工作上。
在之前的课程中,我们讨论了如何更好地利用计算机和系统中的资源,如CPU、内存、磁盘和网络。然而,在我们的日常工作中,还有一种资源更为宝贵,那就是我们作为人类的时间。我们需要确保将时间花在有意义的活动中,例如享受工作本身以及从出色完成的工作中获得满足感。在工作中,我们需要优化时间投入,为公司带来最大价值。找到正确的平衡点很困难,但这正是我们在此要探讨的。



从更新日历到戒除社交媒体,优化时间的方法多种多样。😊 在IT领域工作时,一个极其有效的方法是艾森豪威尔决策矩阵。


使用此方法时,我们将任务分为两个不同的类别:紧急和重要。
以下是任务分类的详细说明:

重要且紧急的任务
这类任务需要立即处理。例如,如果公司的网络连接中断,尽快恢复它既是紧急的也是重要的。


重要但不紧急的任务
这类任务需要在某个时间点完成,即使需要花费较长时间。例如,作为网络中断的后续跟进,确保有备用网络连接非常重要,这样如果现有网络再次中断,公司可以使用备用连接保持在线。
紧急但不重要的任务
许多我们需要处理的干扰都属于此类。回复电子邮件、接听电话、短信或即时消息感觉像是需要立即做的事情,但大多数时候并非最佳利用时间的方式。
既不重要也不紧急的任务
这些是干扰和时间浪费,根本不应该做。这包括讨论无用的会议、没有结果的电子邮件线程、办公室八卦等。任何消耗我们时间却无法带来任何有价值回报的任务都属于此类。
总的来说,为了充分利用时间,我们需要确保将大部分时间花在重要的任务上。当然,我们也希望尽快处理紧急任务,但需要为长期规划和执行预留时间。在长期任务上花费时间可能不会立即见效,但在处理重大事件时可能至关重要。
例如,设置基础设施以便轻松回滚更改或在需要时部署新服务器,需要花费大量时间。但对未来的投资可以在响应问题时,为你节省更多时间并减少用户的困扰。研究新技术是此类中的另一项任务。IT领域不断发展,留出时间保持更新非常重要。例如,判断是否该将Web服务器迁移到不同的软件、将邮件服务器更新到新的操作系统版本,或在整个公司部署IP语音。

另一项可能不紧急但很重要的任务是解决技术债务。在IT领域工作时,你会经常听到这个术语。它是什么意思?技术债务是指当我们选择快速简单的解决方案,而不是采用可持续的长期解决方案时,所积累的待处理工作。
我们已经多次提到,在解决问题时,我们可能会应用短期补救措施来立即修复,然后计划长期解决方案以防止未来再次发生。在我们找到持久的修复方案之前,我们创建的变通方法就是技术债务,因为我们需要花费时间来维持它,即使它不是最佳解决方案。每当我们选择短期解决方案并将长期解决方案留待以后处理时,我们就在制造技术债务。这在当时可能是正确的决定,以使我们摆脱危机并让用户恢复工作。但我们需要随后安排时间应用长期解决方案,这将使我们未来的工作更轻松。
技术债务也可能由外部因素产生。例如,当我们使用的软件发布新版本时,我们需要安排时间进行升级。在我们完成升级之前,这个待定的升级就是技术债务。
好的,我们已经明确需要将工作重点放在重要的任务上。但对于那些紧急但不重要的干扰,我们能做些什么呢?
如果你从事IT支持工作,被打断是不可避免的,这是角色的一部分。因此,你需要计划如何有效地处理这些干扰。如果你在团队中工作,可以轮换处理这些干扰的人员。例如,可能上午由某人负责,下午换另一个人,或者每天轮流。如果你独立工作,可以尝试建立一套用户可以在正常请求时联系你的固定时间,其余时间只处理紧急情况。这里的关键是预留一段不会被中断的时间窗口。那是你可以完成最重要任务的时间,那时你可以完全专注于处理复杂问题和寻找棘手问题的解决方案。
根据你的角色和公司的工作方式,你可能需要在不同的地点完成这项工作,以避免人们走到你的办公桌前,或者主动静音所有通知,以避免被不重要的对话打断和分心。
好的,假设你已经设法留出一些时间来处理这些重要但不紧急的任务。你如何确保自己以正确的优先级处理正确的事情呢?这将在我们的下一个视频中揭晓。
总结
本节课我们一起学习了如何通过艾森豪威尔决策矩阵来区分任务的紧急与重要程度。我们明确了应将主要精力投入重要的任务,并学会为处理重要但不紧急的长期任务(如解决技术债务、研究新技术)预留时间。同时,我们也探讨了如何通过轮岗或设定专注时间段等策略,来有效管理那些紧急但不重要的干扰,从而最大化我们的时间价值。
107:任务优先级管理 🎯


概述
在本节课中,我们将学习如何有效管理IT工作中的多项任务。当所有任务看起来都既重要又紧急时,我们需要一个系统来帮助我们确定处理顺序,从而高效利用有限的时间。
任务管理的核心挑战
上一节我们介绍了为重要但不紧急的任务安排时间。本节中我们来看看当所有任务都显得既重要又紧急时,我们该如何应对。
例如,你可能需要:
- 为明天入职的新同事部署一台新电脑。
- 升级VPN服务到最新版本,因为旧版本存在安全漏洞。
- 修复一个权限问题,该问题导致一组用户无法访问库存数据。
- 检查邮件系统的问题,该问题导致部分邮件被随机拒收。
面对如此多的事项,很容易失去头绪。每个人的工作方式不同,你需要找到最适合自己的系统,但我们可以先了解一个能帮助我们组织和确定任务优先级的基本框架。

第一步:列出所有任务 📝
以下是创建任务清单的步骤:
首先,将所有需要完成的任务列成一个清单。你可以使用:
- 一张纸
- 电脑上的一个文本文件
- 缺陷跟踪系统
- 工单管理系统
关键是将所有任务集中列在一个地方,避免依赖你那并不总是完美的记忆力。

第二步:评估紧急性与重要性 ⚖️
列出清单后,你可以开始检查任务的真实紧急性。
问自己:如果今天不做某项任务,是否会发生不好的事情? 如果答案是肯定的,那么这些任务应该优先处理。
完成最紧急的任务后,你可以查看清单的其余部分,评估每个问题的重要性。
即使所有事情看起来都很重要,你也应该能分辨出某些事情比其他事情更重要。例如:
- 一项能让更多人受益的任务比一项让较少人受益的任务更重要。
- 如果有一堆不同的任务都依赖于你完成其中一项,那么这项“阻塞性”任务就比其他任务更重要。
如果仍然觉得所有事情都“火烧眉毛”,可以尝试将任务分组为:最重要、重要、不太重要,然后在每个组内对任务进行排序。但不要花太多时间在排序上。最终,精确的顺序并不重要,重要的是你将大部分时间花在最重要的任务上。
如果你与团队合作,最好在团队成员之间共享任务清单和优先级标准。这有助于避免重复工作并产生不同的优先级判断。
第三步:估算任务规模 📏
确定需要处理的最重要任务后,你需要对它们所需的工作量有一个大致的了解。
这不是关于精确计时,而是分配大致规模。一种常见的技术是使用小、中、大来标记。当规模范围足够大时,如果需要,可以加入特小或特大。
第四步:执行任务与应对干扰 🚀
一旦识别出最重要的任务及其规模,就可以开始处理它们。
如果可能,尝试从规模最大且最重要的任务开始,以便首先解决它们。但正如我们指出的,当我们的工作涉及IT支持时,我们知道必须处理各种干扰。在被打断的情况下处理复杂任务会非常令人沮丧。
对此,一个有用的策略是:将最复杂的任务留给你最不可能被打扰的时间段。如果你知道自己上午最忙,而下午往往有更安静的时间,那么合理的做法是:在一天早些时候处理简单快速的任务,将最复杂的任务留到后面,那时你将有更多时间集中精力。
但当你的专注时间开始时,你应该确保自己是在处理那些大型复杂任务,而不是简单任务。否则,复杂任务将永远无法完成。这里的关键是始终处理重要任务。如果一个任务不重要,那根本就不应该做。我们在谷歌就遵循这条规则。

然后,根据紧急性和你能投入的时间来选择要处理的任务,从你能在可用时间内完成的最大任务开始。
但请记住,这不应阻止你休息或从事实验性项目。休息很重要,因为它能让我们的创造性思维保持活力;而从事一个有趣的副项目可以帮助我们研究新兴技术并产生新想法。你知道吗?这个证书项目本身最初就是谷歌的一个副项目。
第五步:当任务过多时怎么办? 😅
但是,如果不可思议的事情发生了怎么办?如果在所有这些优先级排序、规模估算和排序之后,仍然有太多工作要做而时间太少,我们该怎么办?
首先要明白:这很正常。大多数IT工作者都有太多事情要做,无法完成所有他们想做的事情。不幸的是,我们人类还不能按需复制自己,而长期加班是不可持续的。
这意味着基本上有两种选择:
- 从其他团队成员那里获得额外帮助。
- 决定某些任务实际上并没有那么重要,因此不会去做。
对于这两种选择,你都需要让其他人(比如你的经理)参与进来,并确保清晰地沟通期望。
区分任务类型:小任务与大项目
有些任务,例如修复目录权限、更换故障键盘或在单台计算机上安装新应用程序,可以是独立的,并且能在短时间内完成。
其他任务,例如将数据库软件升级到新版本、自动化创建用户账户,或编写适配器来协调不兼容的程序,则是更大的项目,可能需要数天甚至数周才能完成。
当遇到后一种情况时,对任务完成所需时间进行粗略估计,并向受影响的人清晰地传达期望,就显得非常重要。我们将在接下来的几个视频中详细讨论这两个方面。
总结
本节课中我们一起学习了任务优先级管理的基本框架。我们了解到,面对众多任务时,应首先列出清单,然后评估其紧急性与重要性,接着估算任务规模,并策略性地安排执行时间以应对干扰。最后,我们认识到当工作量超出负荷时,寻求帮助或重新评估任务重要性是必要的步骤,并且清晰沟通期望至关重要。
108:如何估算任务所需时间 ⏱️


在本节课中,我们将学习如何为一个任务(尤其是自动化任务)估算所需时间。我们将探讨为何估算容易出错,并学习一种基于过往经验、将大任务拆解为小步骤的系统性估算方法。
概述:为何估算时间如此困难?
在决定是否将一个手动任务自动化时,我们通常会考虑两个因素:任务在一定时间内执行的频率和手动执行一次所需的时间。通过这两个因素,我们可以判断手动执行的总时间是否超过了编写自动化脚本所需的时间。
然而,问题在于:在我们实际完成自动化之前,我们无法确切知道编写它需要多久。我们只能进行估算,而人类通常非常不擅长估算任务耗时。
克服过度乐观的倾向
我们往往对编写一段代码或搭建某个基础设施所需的时间过于乐观。通常,我们的第一反应是基于“理想状态”来思考:即我们能全神贯注且完全理解问题时的效率。我们忘记了可能会遇到的许多障碍,例如:
- 遇到不知如何修复的 Bug。
- 被更紧急的问题打断。
- 发现新工具与现有工具不兼容。
因此,在估算项目(无论大小)的完成时间时,你需要保持现实,避免过度乐观。
基于过往经验进行估算
避免过度乐观的最佳方法是:将你当前的任务与你过去完成的类似任务进行比较。
这样,你的估算就不是基于“你希望项目花多久”,而是基于“过去类似项目实际花了多久”。

如果手头的任务很大,可能很难找到足够相似的参照物。这时,你需要将其拆解。
拆分任务:化整为零的估算方法
为了更好地估算一个超乎寻常的大项目,你需要将其切分。
以下是拆分任务的步骤:

- 将任务拆分为更小的步骤。
- 将每个小步骤与你过去完成的类似任务进行比较。
- 基于比较,为每个步骤分配一个预估时间。

如果某个小步骤仍然太大,就继续将其拆分成更小的部分,直到每一部分都能与你过往的经验进行比较。
整合与缓冲:应对未知的挑战
当你获得了所有小步骤的预估时间后,将它们相加,就能得到整个任务的粗略估算。
但即使这个总和也仍然是乐观的,因为将所有部分整合在一起需要额外的时间。
因此,在得到所有步骤的总时间估算后,你需要为“整合”环节预留一些额外时间。这个预留时间也应基于过往经验:回想一下以前整合项目各部分花了多久,就能大致知道该增加多少。
应用“经验乘数”因子
要知道,即使我们基于过往经验进行估算,得出的数字也往往接近“最佳情况”。我们无法预知所有未知的障碍。
因此,请将这个估算值乘以一个系数。同样,这个系数最好基于你过去的经验。
例如,如果你上次做类似估算时,实际花费的时间是计划的三倍,那么这次就将你的估算乘以 3。
这看起来像是在夸大数字,但请记住:我们的目标是获得一个扎实的、接地气的任务完成时间估算。这意味着要将那些你肯定会遇到但尚未知晓的障碍考虑进去。
记录、沟通与迭代
无论我们的估算多么详细,最终结果都很难与实际耗时完全吻合。但它能给我们一个大致的概念:任务是需要几小时、几天、几周还是几个月来完成。
一旦完成估算,请将其记录下来,以便日后检查你的估算与实际结果的差距。你可以根据这个差距来调整未来的估算。
同时,务必与相关方沟通,让他们知道任务预计何时完成。我们将在下一个视频中更详细地讨论沟通技巧。
总结
本节课我们一起学习了如何系统性地估算任务时间。核心方法是:基于过往经验,将大任务拆解为可比较的小步骤,分别估算后求和,并为整合过程与未知风险预留缓冲时间(通常通过乘以一个经验系数来实现)。记住要记录估算并与实际结果对比,以持续改进你的估算能力。
109:沟通预期 - 第51课 📢


在本节课中,我们将学习如何在与用户沟通时有效管理他们的预期。这包括理解用户的隐含期望、主动沟通时间安排、处理优先级冲突,以及通过一些实用技巧提升工作效率。
理解用户期望 🤔
上一节我们介绍了课程概述,本节中我们来看看如何理解用户的期望。
当你处理一个影响一个或多个用户的问题时,你可能会感到压力,需要满足你所帮助的人设定的期望。每个人对于你解决问题所需的时间以及他们何时能期待解决方案都有自己的想法。如果问题是用户把咖啡洒在键盘上需要更换,用户的期望是更换键盘将是一个非常小的任务,几乎不花时间。如果问题是即将有一位新员工入职,需要为他们设置一台新电脑,用户的期望是这将比更换键盘花费更长的时间。
即使我们有一个自动化的流程来设置新电脑,这意味着几乎不需要手动工作,理解这些隐含的期望并告知用户解决问题是否比他们预期的要长,对于成功的互动至关重要。如果问题在用户的期望内得到解决,他们会感到满意;但如果花费的时间比他们想象的要长得多,他们会变得沮丧。
主动沟通与时间管理 ⏰
上一节我们探讨了理解期望的重要性,本节中我们来看看如何主动沟通并管理时间。
只要我们尽早与他们沟通情况,他们就能够理解并根据情况安排自己的时间。假设你需要更换一个键盘,但没有备用件。这意味着你需要购买一个新的,甚至可能需要购买一整批以备下次使用。在这种情况下,提前与用户沟通更换需要更多时间非常重要。然后,评估更换的紧急程度。
以下是评估紧急程度时可以考虑的两种情景:
- 情景一: 用户是一名会计,正在处理需要在一小时内发送给银行的工资存款单,否则没人能按时收到工资。你可能选择把你的键盘先给他用,同时去最近的硬件店购买另一个。
- 情景二: 用户可以先用笔记本电脑工作,直到第二天新一批键盘按计划到货。这样你就不需要特意去提前获取更换件。
处理优先级冲突 🚦
上一节我们讨论了时间管理,本节中我们来看看当多个任务冲突时如何处理。

同样重要的是,让用户知道是否存在任何可能延迟响应他们需求的优先级冲突。假设一个用户打电话请求访问一个共享资源,但你正在处理一个导致公司数据库离线的问题。即使用户的请求解决起来快速简单,修复数据库是至关重要的,并且影响整个公司,因此在这种情况下应该优先处理。确保告诉用户你正在处理一个紧急事件,并将在危机解决后帮助他们处理请求。让他们知道他们的问题预计何时能得到解决,以便他们计划下一步行动。
因此,作为一个通用规则,沟通是关键。尽量清晰、提前地说明你预计问题何时能解决。如果出于任何原因,问题到时没有解决,解释原因以及新的预期时间应该是怎样的。
应对复杂问题与使用工单系统 🐛
不幸的是,当你试图解决的问题涉及故障排除和调试时,通常很难准确估计修复问题需要多长时间。请注意一个主题:估计执行更复杂工作所需的时间是困难的。你的很多时间将花在调查、研究正在发生的事情以及弄清楚应该发生什么上。
在这种情况下,确保让用户知道他们何时可以期待关于他们问题的更新,并在可能时及时提供更新。让用户通过工单跟踪系统提交他们的请求是一个非常好的主意。
使用这样的系统有很多优势。以下是使用工单系统的主要好处:
- 任务组织: 将所有你需要做的工作集中在一处,让你可以按优先级组织任务。
- 时间管理: 通过系统而不是电话或聊天接收问题报告,让你能更好地利用时间。你可以决定何时查看问题列表,而不是在任务中途被打断。
- 更新便捷: 当你对你一直在处理的问题有更新时,你可以轻松地更新工单,而不必追踪用户来告知他们请求的进展。
实用技巧与自动化 🛠️
最后,在与用户打交道时,尝试一些实用的快捷方式。花时间思考你所做的工作,并找出避免中断和节省时间的方法是有意义的。
以下是几个可以节省时间的实用技巧:
- 更换外设: 例如,如果用户告诉你他们的鼠标不工作,你的第一反应可能是亲自去检查,然后在必要时带一个新的过去。但这需要很多来回。相反,你可以要求用户将有问题的鼠标带给你,以便你在自己的电脑上测试。如果坏了,就换一个新的。甚至更好的是,如果你信任你的用户,你甚至可以留出一套鼠标、键盘和其他配件,供用户在正在使用的设备损坏时取用。我们在谷歌就是这样做的,这为我们节省了大量时间和挫败感。
- 备用电脑: 同样地,如果公司的预算允许,你可以准备几台备用电脑随时可用。这样,当一台电脑出现故障时,你可以让用户尽快恢复工作。然后你可以按照自己的节奏调试有故障的电脑。
但并非所有问题都能通过备用设备解决。有时,花时间改进你的基础设施可以帮助你用更少的时间完成更多工作。自动化流程,如安装新电脑、设置新用户账户、部署虚拟机或将更改回滚到先前版本,可以在你响应事件时帮助你节省大量时间。
总结 📝
本节课中我们一起学习了有效沟通预期的重要性。我们探讨了如何理解用户的隐含期望、主动沟通时间安排、处理任务优先级冲突,以及利用工单系统和实用技巧(如准备备用设备、自动化流程)来提升IT支持工作的效率。记住,清晰、主动的沟通是管理用户期望和建立信任的关键。
110:课程 52: 处理困难问题 🧩


在本节课中,我们将探讨为何调试工作有时会异常困难,并学习一些实用的策略来更有效地应对和解决复杂的技术问题。


你可能会疑惑,为什么调试如此困难,以至于我们需要用一整门课程来学习它?布莱恩·柯尼汉,Unix 操作系统的早期贡献者之一,也是著名的《C 程序设计语言》一书的合著者,曾说过一句名言:“众所周知,调试的难度是首次编写程序的两倍。”
所以,如果你在编写程序时已经竭尽所能地展现聪明才智,那么你将来又该如何调试它呢?
这实际上是对编写复杂程序的一种警告。如果代码清晰、简单,那么调试起来会比那些“聪明”但晦涩的代码容易得多。这个道理同样适用于 IT 系统。如果一个系统设计得过于“巧妙”,当它出现故障时,理解其内部状况将变得极其困难。因此,专注于构建简单易懂的系统和应用程序至关重要,这样当问题发生时,我们才能快速找到修复方法。
那么,我们该如何做到这一点呢?
以下是一些实用的建议:
首先,以小块、易于理解的方式开发代码。 我经常停下来测试已经写好的部分。最困难的事情莫过于在完成整个程序后才第一次运行它并进行调试,因为出错的可能性实在太多了。
其次,保持目标清晰。 如果你在编写代码,可以尝试在实际编码之前先为程序编写测试,这有助于你专注于目标。如果你在构建系统或部署应用程序,那么记录下最终目标以及实现步骤的文档会非常有帮助,它既能让你保持正轨,也能帮助发现沿途可能出现的问题。
我们在课程开始时提到,解决技术问题有点像一门艺术,当一切豁然开朗时,它会带来乐趣。然而,故障排除和调试最糟糕的部分,莫过于我们陷入困境的时候 😊。
当我们想不出程序失败的其他原因,或者不知道还能做什么来修复它时,就会感到束手无策。在本课程中,我们为你提供了一系列可以遵循的工具和流程,希望能帮助你避免在许多情况下陷入困境,但我们无法涵盖所有情况。你仍然可能会遇到一个完全不知道该如何处理的问题,这很正常。
如果你身处困境,首要任务是保持冷静。我们需要创造力来解决问题,而创造力的最大敌人就是焦虑。所以,如果你觉得自己已经黔驴技穷,最好暂时把问题放一放。也许去喝杯咖啡,或者到外面散散步。有时,环境的改变正是我们获得新灵感、发现遗漏之处的关键。这在编码和生活中都是如此。
如果你试图解决的问题很复杂,并且影响到很多人,在众人等待的情况下进行彻底调试会带来巨大压力。因此,更好的做法是首先专注于短期解决方案,让受影响的人能够恢复工作,然后再寻找长期的根治方案。
同时,不要害怕寻求帮助。有时,仅仅向他人解释问题本身,就能帮助我们意识到自己遗漏了什么。有一种技巧叫做“橡皮鸭调试法”,就是简单地向一只橡皮鸭解释问题。这听起来有些异想天开,你可能会显得有点古怪,但它确实有效。因为当我们强迫自己解释问题时,我们其实已经开始从不同角度思考这个问题了。
记住,没有人无所不知。有时,学习新技能和技巧的最佳方式就是向他人求助。我们都在共同面对这些挑战。有时候,我知道如果我在一个问题花上足够多的时间,我可能会找到解决方案。但这是对我时间的最佳利用方式吗?通常,更好的答案是向有经验的人请教,以节省时间和减少挫败感,然后把当前问题当作一个持续学习的机会,这样下次我就能独立完成了。
当你向同事寻求帮助以解决一个 bug 时,注意不要直接告诉他们你认为问题的根本原因可能是什么。相反,告诉他们问题的症状,看看他们会提出什么问题,探索哪些可能性。他们可能会提出完全不同的排查路径。
当然,如果我们能完全避免问题,IT 专家的生活将会轻松得多。在接下来的课程中,我们将探讨一些主动的方法,以便在问题影响到任何用户之前就发现它们。
本节课总结:
我们一起学习了调试工作为何具有挑战性,以及应对复杂问题的关键策略:保持代码和系统的简洁性、分块开发与测试、明确目标、在困境中保持冷静并适时休息、优先实施短期解决方案、勇于并善于向他人寻求帮助。掌握这些方法将帮助你在面对技术难题时更加从容和高效。
111:积极主动的实践 🛡️


概述
在本节课中,我们将学习如何在IT自动化工作中采取积极主动的策略,以预防和高效处理软件中的“Bug”。我们将探讨一系列实践方法,包括测试、部署、日志记录和文档编写,旨在帮助我们在问题影响用户之前发现并解决它们,或在问题发生时能更快速地进行故障排除。
IT专家与Bug处理 🐛
IT专家和“Bug处理者”有一个共同点,那就是都需要处理“Bug”。无论是我们自己的软件还是他人的软件,我们都会遇到许多触发程序各种故障的Bug。
我们可以采用一系列策略,通过在问题影响用户之前捕获它们,或通过拥有更好的信息来简化故障排除,从而使我们的工作更轻松。我们之前已经零散地提到过其中一些策略。但现在,是时候深入探讨了。
为了避免在服务中断时手忙脚乱地修复问题,拥有能让我们提前测试变更的基础设施非常有帮助。这样,我们可以在变更到达用户之前,检查一切是否按预期运行。


编写与运行测试 ✅
上一节我们提到了提前测试的重要性。本节中,我们来看看具体如何通过测试来保障代码质量。
如果我们是代码的编写者,可以做的一件事是确保我们的代码具有良好的单元测试和集成测试。如果测试对代码的覆盖度很高,我们就可以依赖它们来捕获各种Bug,尤其是在有变更可能破坏现有功能时。
为了让这些测试真正有意义,我们需要经常运行它们,并确保在它们失败时能第一时间知晓。设置持续集成(CI)可以帮助实现这一点。
以下是实施测试的关键点:
- 编写全面的测试:包括单元测试(测试单个函数或模块)和集成测试(测试模块间的交互)。
- 实现高覆盖率:确保测试覆盖了代码的大部分逻辑分支。
- 集成到CI流程:自动运行测试,并在失败时立即通知团队。
建立测试环境与分阶段部署 🚦
仅仅在本地运行测试还不够。接下来,我们探讨如何在更接近真实场景的环境中验证软件。
朝着这个方向迈出的另一步是拥有一个测试环境,我们可以在将新代码发布给所有用户之前,先部署到这个环境中。这有两个目的。
首先,我们可以对软件进行全面的检查,就像用户看到的那样。根据软件的性质和更新频率,我们可以在这个环境中进行自动化和手动测试。
其次,我们可以使用这个测试环境在问题发生时进行故障排除。我们可以尝试可能的解决方案和新功能,而不会影响生产环境。
更进一步,在管理大量计算机时,另一个推荐的做法是分阶段或“金丝雀”式部署软件。这意味着,与其同时升级所有计算机(并可能同时破坏所有计算机),不如先升级一部分计算机,并检查它们的行为。如果一切顺利,你可以再升级更多计算机,依此类推,直到你有足够信心升级剩余的计算机。
正如谚语所说,“像煤矿里的金丝雀一样”。为了最好地利用这种实践,我们需要能够轻松地回滚到之前的版本。根据软件的不同,这可能或多或少需要一些基础设施,但请相信,花时间设置这些额外的基础设施是值得的。如果你部署了一个有问题的软件版本,突然之间你的一批计算机无法正常工作,你会希望尽快将它们回滚到之前的状态。
记录日志与集中监控 📊
即使采取了所有这些预防措施,Bug仍然会渗透进来,问题仍会发生。本节我们学习如何通过记录和监控来简化故障排除过程。

我们可以通过在代码中加入良好的调试日志记录,使故障排除变得更容易。这样,每当我们必须弄清楚一个问题时,我们可以查看日志,并对正在发生的事情有一个很好的了解。

另一个可以帮助我们的方法是建立集中式日志收集。这意味着有一个特殊的服务器,用于收集网络中所有服务器甚至所有计算机的日志。这样,当我们需要查看这些日志时,我们不需要单独连接到每台机器。我们可以在集中式服务器上一同梳理所有日志。
同样,拥有一个良好的监控系统也非常有帮助。我们可以用它来及早发现问题,以免影响太多用户;并且在调试期间,我们可以查看收集到的数据,试图确定是否有任何异常情况发生。
利用工单系统与编写文档 📝
我们已经多次提到工单系统,因为我们不能过分强调它们的重要性。本节我们看看如何有效利用它们以及文档的力量。
充分利用工单系统可以帮助我们在试图找出问题根源时节省大量时间。如果我们要求用户提前提供所需的信息,我们就不必浪费时间来回沟通。

即使在这里,我们也可以寻找自动化的机会。假设你几乎总是需要用户计算机上的一些特定信息。你可以通过创建一个脚本来自动获取你需要的所有数据,并让用户将其附加到工单中。
最后,请记住花时间编写文档,同样重要的是,将文档存储在众所周知的位置。即使编写文档不是特别有趣,但拥有关于如何解决特定问题的良好说明、知道如何诊断服务器问题或跟踪系统中的已知问题,确实可以节省大量时间。
在谷歌,我们有一系列称为“操作手册”的文档,其中详细说明了值班人员可以采取哪些措施来诊断和缓解大量不同问题。通过保持这些信息的更新,我们确保无论值班人员是谁,每个人都能访问整个团队积累的知识库。
前瞻性规划 🔮
我们的实践并未止步于此。如果我们处理的系统在不断变化和增长,我们可以主动规划未来所需的额外容量。
说到提前规划,你可以在我们的下一个视频中计划听到更多关于这方面的内容。
总结
本节课中,我们一起学习了在IT自动化中采取积极主动实践的多项策略。我们从编写和运行测试开始,以确保代码质量;接着探讨了建立测试环境和实施分阶段部署(金丝雀部署)的重要性,以安全地发布变更。然后,我们了解了如何通过记录调试日志、建立集中式日志收集和监控系统来简化故障排除。最后,我们强调了有效利用工单系统、自动化信息收集以及编写和维护文档对于积累团队知识和快速解决问题至关重要。这些实践共同构成了一个强大的防御和响应体系,能帮助我们在问题影响扩大之前有效地预防、发现和解决它们。
112:规划未来资源使用 📈


在本节课中,我们将学习如何为不断增长的服务规划未来的资源需求。我们将探讨如何预测资源消耗、制定扩容计划,并了解本地部署与云端部署在资源管理上的不同策略。
在之前的课程中,我们讨论了当程序误用内存、磁盘、网络或CPU等资源时可以采取的措施。但有时问题不在于资源被误用,而在于资源本身不足。例如,随着数据量增加,数据库服务器自然会占用更多磁盘空间;或者随着服务日益流行,网络服务器会消耗更多网络带宽。
如果你负责的服务预计会增长,并且未来需要更多资源,那么花时间思考未来的资源需求是很有意义的。提前规划能让你在需要额外资源时有所准备,而不是在最后一刻手忙脚乱。
预测与计算资源需求
假设数据库的预期增长速度为每天1 MB,而你目前有500 MB的可用空间。那么这些存储空间大约可以使用两年。其计算公式为:
可用天数 = 可用空间 / 每日增长量
然而,如果预期增长是每天10 MB,而可用空间仍为500 MB,那么你需要在几个月内空间耗尽前,开始制定一个支持数据库持续增长的计划。
一旦你确定了当前使用量和预期增长率,务必将其记录下来,以便未来参考并检查是否有任何变化。
应对资源短缺的策略
如果你发现空间即将耗尽,下一步的行动取决于系统功能和数据的重要性。以下是几种可能的策略:
- 清理数据:你可能决定不需要存储所有数据,转而清理任何非必要的内容。
- 扩容存储:如果你确实需要更多存储空间,可以选择购买网络附加存储(NAS)为服务器增加磁盘空间。
将服务迁移到不同类型的存储需要时间,并且在压力下操作容易出错。因此,提前进行此类规划至关重要,不要等到磁盘完全写满。
监控与趋势分析
这意味着需要监控计算机的使用增长情况,以发现任何需要关注的趋势。如果使用数据库的服务突然变得非常流行,每日增长量可能会急剧增加,导致我们需要比预期更早地找到更好的解决方案。我们的监控系统应该在这种情况下触发自动警报。
优化资源利用的策略



一个优化资源利用的有趣策略是混合搭配在计算机上运行的进程,以便充分利用所有可用资源。
- 如果你有一个CPU密集型进程,几乎占用了计算机所有可用的CPU,你仍然可以运行I/O密集型进程,这些进程会向硬盘读写大量数据。
- 或者,如果一个服务需要大量内存(RAM),你可以将其与另一个几乎不使用内存、主要通过网络收发数据的服务配对运行。
云端部署:另一种解决方案
处理所有这些资源问题(例如,确定何时购买更多资源以及如何分配)的另一种方案,是将这些系统迁移到云端。
在云端设置服务运行需要一些初始设置时间,并且需要为使用的云资源支付持续的费用。虽然这比在本地运行服务的成本更高,但你基本上将所有容量规划需求委托给了云服务提供商。
这样,如果初始设置空间不足,你可以简单地挂载一个更大的硬盘。或者,如果程序需要更多内存,你只需将服务部署到分配了更多内存的虚拟机上。
如果你认为迁移到云端是公司的正确方向,请记住这也需要规划。将服务完全或部分迁移到云端运行需要你付出努力。因此,你需要决定是否以及何时进行这一跨越,以避免服务因资源耗尽而中断。
总结
本节课中,我们一起学习了如何为服务的未来增长进行资源规划。我们探讨了如何根据预期增长率计算资源耗尽时间,并制定了应对资源短缺的策略,如数据清理和硬件扩容。我们还了解了通过混合不同类型进程来优化资源利用的方法,并对比了本地部署与云端部署在资源弹性管理上的优势。提前规划和持续监控是确保服务长期稳定运行的关键。
你正在提前思考,并学习一些在真实环境中至关重要的技能。这自然引出了我们的最后一个主题:确保问题得到长期妥善的解决。
113:防止未来问题 🛡️

在本节课中,我们将学习如何在解决IT问题后,采取进一步措施防止问题再次发生。我们将探讨监控系统的重要性、如何有效报告错误,以及如何通过编写测试和文档来确保长期稳定性。
概述
在之前的课程中,我们多次提到,面对问题时,通常最好先找到快速解决方案,以便受影响用户尽快恢复工作。例如,数据库服务器因空间不足而崩溃,我们可以通过添加额外硬盘并重启服务来快速解决。
但我们的工作并未就此结束。一旦用户恢复正常工作,我们需要寻找长期解决方案,以防止问题在未来再次发生。在数据库服务的场景中,这意味着在磁盘空间耗尽之前就检测到这一情况。
那么,我们如何在没有“水晶球”的情况下做到这一点呢?一个关键策略是充分利用监控。
利用监控进行预防 📊
上一节我们介绍了快速解决方案的必要性,本节中我们来看看如何通过监控来预防问题。
关于监控有很多内容可讲,甚至足以开设一门完整的课程。简而言之,我们希望所关心的计算机将其数据发送到一个集中位置,该位置会汇总所有信息。
然后,我们希望能够自己查看这些信息,并在数值超出可接受范围时触发警报。
初次设置监控系统时,可能不确定应优先关注哪些信息。因此,应从基础开始。
以下是应优先监控的基本指标列表:
- CPU使用率
- 磁盘使用率
- 内存使用率
- 网络使用率
随着时间的推移,在处理更多事件后,可能会发现其他需要纳入监控系统的指标。例如,如果曾不得不调试与计算机过热相关的问题,就会希望将温度传感器的数据纳入监控系统。
还需要纳入与计算机上运行的特定服务相关的信息。
如果是一台Web服务器,需要知道成功Web响应与错误之间的比率。如果是一台数据库服务器,需要知道随时间处理的查询数量。
每当处理一个未被监控系统捕获的事件时,请记住设置新的监控和警报规则,以便在问题再次发生时通知你。
监控的一个重要功能是包含一段时间内进行的测量。这样,我们可以跟踪资源使用情况,并及早发现趋势变化,以帮助我们进行规划。良好的监控能让我们及早发现故障。
确保修复持久有效 🔧
上一节我们探讨了如何通过监控预警问题,本节中我们来看看如何确保修复措施持久有效。
我们已经提到过这一点,但值得重复:如果你必须解决由他人开发的应用程序中的问题,向相关开发人员报告错误至关重要。这样,负责代码的人员可以将你的情况考虑在内,并在未来使其正常工作。
如果不这样做,你为当前版本找到的解决方案可能不足以应对下一个版本,届时你将不得不寻找全新的解决方案。
向他人报告错误时,请记住我们之前讨论的所有最佳实践。
以下是报告错误时应包含的关键信息:
- 告知他们你试图实现的目标。
- 告知他们你做了什么。
- 告知他们预期结果是什么。
- 告知他们实际结果是什么。
包含你的问题复现步骤和临时解决方案。如果你能访问项目的源代码,提供一个修复问题的补丁会增加代码被修复的机会。
另一方面,如果你必须解决自己拥有的软件中的问题,请确保编写一个能捕获该问题的测试。
# 示例:一个简单的单元测试,用于检查函数是否在磁盘空间不足时抛出正确异常
import unittest
from your_module import your_function
class TestDiskSpace(unittest.TestCase):
def test_low_disk_space_throws_error(self):
# 模拟磁盘空间不足的情况
with self.assertRaises(DiskSpaceError):
your_function(simulate_low_disk=True)
这样,你可以确信不会对代码进行任何会再次触发相同问题的更改。即使你不负责软件的开发,也可以在出现新版本时运行自动测试,以检查其是否仍按预期工作。因此,请确保在应用程序发布新版本时执行这些测试。

记录与总结 📝
最后,无论错误是来自你编写的软件还是他人编写的软件,请确保记录你所做工作的关键部分:如何诊断问题以及如何解决它。
这样,如果问题再次发生,你或其他需要处理它的人将能够快速应用解决方案,而不是花费宝贵的时间进行调查。
以下是应记录的核心信息:
- 问题描述与影响
- 诊断步骤与根本原因
- 应用的解决方案(临时和永久)
- 相关的监控指标或测试用例
总结
本节课中,我们一起学习了在快速解决IT问题后,如何通过建立监控系统来预防问题复发,如何通过有效报告错误和编写测试来确保修复的持久性,以及如何通过详细记录来为未来积累知识。这些实践将帮助你从被动的“救火”转向主动的“防火”,构建更稳定、可维护的IT环境。
114:模块 4 总结 - 资源管理 📊

在本节课中,我们将回顾模块 4 的核心内容,总结在 IT 环境中有效管理各类资源的关键策略与方法。

回顾过去视频所涵盖的内容,我们讨论了许多与处理 IT 问题相关的不同主题。
我们深入探讨了限制可用资源的因素,包括内存、磁盘空间和网络,并研究了如何从硬件和人力两个角度更好地利用这些资源。
上一节我们介绍了资源限制,本节中我们来看看如何优化资源利用。
我们讨论了如何通过使用艾森豪威尔决策矩阵来优先处理任务,从而最有效地利用时间,并涵盖了如何主动工作以预测和防止问题发生。
我们还探讨了估算时间花费的最佳方法,以及如何向用户传达预期。
此外,我们讨论了处理复杂问题的策略。
以下是处理复杂问题的几种关键策略:
- 建立测试环境
- 实施监控
- 使用金丝雀发布
- 准备回滚方案
在短时间内,我们涉及了大量有趣的内容。
在本课程中,你已经学到了许多不同的方法,用以理解和解决大量不同的 IT 相关问题。
现在,再次到了将这些知识付诸实践的时候。接下来是本课程的最后一次分级评估。
在这次评估中,你需要运用迄今为止所学的所有知识。
你需要找出一个未按预期运行的系统的问题所在,并找到修复方法。
本节课中我们一起学习了资源管理的核心概念,包括识别资源限制、优化利用策略、时间管理以及应对复杂问题的系统性方法。你已经掌握了诊断和解决 IT 问题的综合技能。
115:祝贺与回顾 🎉

在本节课中,我们将对《用Python进行IT自动化办公》课程的核心内容进行回顾与总结,祝贺你完成整个学习旅程。
祝贺你解决了那个棘手的实验。完成它绝非易事,同时也祝贺你完成了整个课程。
在我们共同学习的时间里,我们掌握了大量的知识与技能。
我为你能够坚持到这里感到非常自豪。通过本课程学到的知识,你现在已经能更好地应对IT工作中可能遇到的各类问题。
回顾学习旅程 🧭
上一节我们完成了最后的实践挑战,本节中我们来整体回顾一下我们共同走过的学习之路。
你学会了如何处理计算机、代码以及系统中的各种问题。
我们探究了软件可能出错的多种不同方式。
并且,在我们共同的旅程中,我们找到了解决其中许多问题的方法。
实践与应用 💻
我们查看了大量基于现实世界场景的示例,这些场景是你我这样的IT从业者在日常工作中经常会遇到的。
我们为你提供了一份开放的“攻略手册”,其中包含了谷歌员工日常用于解决同类问题的技巧与窍门。
并且,自始至终,你都在通过我们的快速实验,应用所学知识亲自解决现实世界的问题。
像这样的实践,帮助你积累了宝贵的问题解决经验。
成长与核心能力 🚀
以下是本课程旨在帮助你培养的核心IT职业能力:
- 问题识别与解决:对我而言,知道如何识别、解决和预防这类问题是IT工作中最重要的部分之一。
- 时间管理与习惯养成:培养必要的健康习惯,以在处理这些问题时合理安排时间优先级,同样至关重要。
我非常高兴能与你分享这段学习旅程。
祝愿你在接下来的冒险之旅中一切顺利。祝你好运。😊
本节课总结:本节课中,我们一起回顾了整个《用Python进行IT自动化办公》课程的学习成果。我们总结了在故障排查、代码调试、自动化实践以及时间管理等方面获得的关键技能,并祝贺你凭借这些新装备,为未来的IT职业道路做好了更充分的准备。
116:自动化规模化与云端管理 🚀

在本节课中,我们将简要回顾已学内容,并预览下一门课程的核心主题。我们将了解如何将自动化技能扩展到大规模计算机管理,包括配置管理工具和云端虚拟机的自动化部署。
在之前的课程中,我们学习了大量关于诊断、修复和预防IT环境中各类问题的方法。我们讨论了许多内容,特别是如何通过构建正确的基础设施,以及快速部署变更和回滚,来简化故障排除的体验。
接下来,我们将进入新的学习阶段。
下一门课程内容展望 🌟
上一节我们回顾了IT自动化的基础问题解决技能,本节中我们来看看下一门课程将如何深化和扩展这些能力。我的同事Phlin将担任下一门课程的讲师,以下是他的介绍。
“大家好,我是Phlin。在接下来的课程中,我们将学习如何进行规模化自动化。我们将了解通用的配置管理系统,并深入研究一个名为Puppet的特定配置管理工具。我们还将更深入地探索云端技术,学习如何在需要时自动部署新的虚拟机,并集中管理它们的配置。和往常一样,我们会分析大量真实世界的案例,学习这些技术如何应用于不同规模的公司。我期待与大家分享这些知识,我们课堂上见。”
本节课中我们一起回顾了IT自动化问题解决的核心,并预览了下一阶段关于规模化自动化和云端管理的关键学习方向。我们即将探索配置管理工具(如Puppet)和云服务的自动化能力,为管理大规模计算资源做好准备。
117:大规模自动化管理 🚀

在本课程中,我们将学习如何运用自动化技术来管理大规模的计算机集群。我们将探讨自动化部署新计算机、保持机器更新、处理大规模变更等关键技能。课程将涵盖物理机和云虚拟机的管理,并介绍配置管理工具Puppet及云服务的最佳实践。
想象你负责管理一个服务器集群。一切运行顺利,直到某天你发现某个应用程序存在安全漏洞。
现在你需要将所有服务器升级到最新版本。如果集群中有10台服务器,逐一登录并安装新版本可能不算太麻烦。
但如果服务器数量达到100台,这项工作将变得极其枯燥,并且很可能出错,导致部分服务器安装了错误版本。
现在,想象一下需要在1000台服务器上执行此操作。逐一登录升级软件是完全不可行的。
那么,我们还能怎么做呢?在本课程中,我们将探讨如何应用自动化来管理计算机集群。
我们将学习如何自动化部署新计算机、保持这些机器更新、管理大规模变更等更多内容。
我们将讨论如何管理办公室中运行的物理机,以及云中运行的虚拟机。
如果这听起来有些令人望而生畏,请不要担心。我将与你一步步同行。
我是Vdeville,是Google的一名站点可靠性工程师,在支持Gmail的团队工作。
如果你从未听说过站点可靠性工程,让我简要介绍一下我们的工作。
站点可靠性工程师专注于大型系统的可靠性和可维护性,并应用大量自动化技术进行管理。
这使得仅由少数工程师组成的团队也能产生巨大影响,随着服务增长而扩展支持能力。我们规模虽小,但力量强大。
我的工作包含许多不同任务。有时,我与合作团队就酷炫新功能的可靠性方面进行协作,例如Gmail中定时发送邮件的功能。
有些日子,我编写软件,创建工具以自动化服务管理方式。
当我不做这些时,我可能为新项目进行研究或架构设计。
我也是服务值班轮换的一员。如果在我值班时出现问题,我负责修复它们,或者在我无法修复时找到合适的人来修复。
那么,本课程将涵盖哪些内容呢?我们将首先探讨一种称为配置管理的自动化技术,它使我们能够大规模管理计算机的配置。
具体来说,我们将学习如何使用Puppet,这是当前配置管理领域的行业标准。
我们将看一些简单的例子,然后了解如何将相同概念应用于更复杂的场景。
你很快就能成为Puppet大师。之后,我们将通过探索如何利用云来扩展基础设施,从而扩展我们的自动化技能。
我们将了解将服务迁移到云端的优势和挑战。
我们将探讨处理云端数百台虚拟机的一些最佳实践,如何使我们的服务适应云端环境,以及当事情不按计划进行时如何进行故障排除。
提前说明:事情很少完全按计划进行。在我们继续之前,我或许应该简单介绍一下自己。
我在青少年时期发现自己对IT和技术感兴趣。
因此,高中毕业后决定加入海军时,我报名成为一名信息系统技术员。
我在海军服役了四年,支持全球的IT和网络资源。离开海军后,我上了大学,然后加入了Google的IT支持部门。
从军队这样高度结构化的环境过渡到Google这样的地方,起初有点难以适应。
我必须更加自如地处理所面临问题领域中的不确定性,这意味着要学会相信自己的判断力和优先级排序能力。
一路走来,我不断学习新技能,并作为一个人和一名工程师不断成长。
因此,我很高兴能在这里帮助你迈出IT职业生涯的下一步,帮助你通过学习如何使用配置管理来管理计算机集群以及如何与云协作,来持续提升自动化技能。
现代IT正越来越多地转向基于云的解决方案,拥有扎实的云端管理背景对于未来的IT专业人员将变得更加关键。
在本课程中,我们将使用Quicklabs,这是一个允许你在云端虚拟机上测试代码的环境。
这让你能够体验真实场景,在这些场景中,你需要与一个或多个远程系统交互以实现目标。
我们将基于你在整个课程中学到的许多工具进行构建,例如使用Python编写自动化脚本,使用Git存储代码版本或在程序行为不符合预期时找出问题所在。
你将会遇到一些复杂的主题和视频,可能第一次观看时无法完全理解。这完全正常。如果需要,请慢慢来,多次重看视频。你会掌握的。
另外,请记住,你可以随时使用讨论论坛与其他学习者联系并提出问题。
我们即将开始我们的旅程,学习如何大规模应用自动化。
那么,让我们开始吧。
在本节课中,我们一起学习了本课程的核心目标:大规模自动化管理。我们了解了管理服务器集群的挑战,引入了配置管理和云技术作为解决方案,并概述了将使用Puppet等工具以及Quicklabs实践环境来构建这些关键技能。
118:云中自动化介绍 🚀
在本节课中,我们将要学习云计算的基本概念,了解它如何帮助IT自动化,并探索在云中部署和管理服务的方法。

如果你在IT领域工作,你可能经常听到人们谈论云。有时人们谈论云,仿佛它是一种能为我们的服务获取无限资源的神奇方式。事实上,云并没有什么魔法,但它是IT领域中一个能极大提高我们生产力的超级有用的工具。
在接下来的几个视频中,我们将深入探讨不同服务的细节,了解何时使用它们合适,以及如何最大限度地利用我们的云部署。即使你从未直接使用过云,你也肯定与运行在云中的服务进行过交互。
例如,在本课程以及本项目的其他课程中,你一直在使用Quicklabs的在线学习环境来练习解决问题和创建基于真实场景的自动化。Quicklabs是当今众多由云驱动的服务之一。你可能也使用过其他服务,甚至没有意识到它们正在使用云资源。
在深入探讨如何利用云进行大规模自动化之前,我们将快速回顾一些与云相关的概念,以确保我们理解一致。我们将探讨云部署如何帮助我们快速扩展服务,并比较在本地运行IT基础设施与在云中运行的一些不同之处。
之后,我们将研究如何使用各种不同的工具来管理运行在云中的实例。我们将从如何启动单个虚拟机开始,然后探讨管理整个虚拟机群组的多种方法。和往常一样,我们将以一个Quicklabs练习结束。这次,你将使用我们将要介绍的自动化工具,在云中部署一批Web服务器。
这听起来怎么样?非常酷,对吧?让我们开始吧。
核心概念回顾 ☁️
上一节我们提到了云在IT自动化中的重要性,本节中我们来看看一些关键的云相关概念,为后续学习打下基础。
云本质上是通过互联网提供计算服务(如服务器、存储、数据库、网络、软件等)。它并非魔法,其核心优势在于按需获取资源和弹性扩展。
以下是云计算的一些基本特点:
- 按需自助服务:用户可以自行配置计算资源,无需与服务提供商进行人工交互。
- 广泛的网络访问:服务可以通过网络(如互联网)被各种客户端设备(手机、笔记本等)访问。
- 资源池化:提供商的计算资源被集中起来,通过多租户模式服务多个客户。
- 快速弹性:资源可以快速且弹性地供应和释放,以快速扩展或收缩。
- 可计量的服务:云系统通过计量能力自动控制和优化资源使用,提供透明的资源使用报告。
本地部署与云部署 🔄
了解了云的基本概念后,我们来看看在本地运行IT基础设施与在云中运行的主要区别。
在本地(on-premise)环境中,公司需要自己购买、安装和维护所有的硬件和软件。而在云环境中,这些资源由云服务提供商(如Google Cloud, AWS, Azure)在其数据中心维护,用户通过互联网租用。
以下是两者的一些关键对比:
- 资本支出 vs 运营支出:本地部署需要前期大量的硬件投资(资本支出),而云服务通常采用按使用量付费的模式(运营支出)。
- 维护责任:本地部署需要公司自己的IT团队负责全部维护、安全和升级工作。在云中,提供商负责底层基础设施的维护(责任共担模型)。
- 扩展速度:在云中,增加计算资源(如CPU、内存)通常只需点击几下或通过API调用,几分钟内即可完成。本地扩展则需要采购和安装新硬件,过程漫长。
- 高可用与容灾:云提供商通常在全球拥有多个数据中心,可以轻松配置跨区域的高可用和备份方案,成本相对较低。在本地实现同等级别需要巨大投资。
管理云实例 🛠️
上一节我们比较了本地与云的区别,本节中我们来看看如何具体管理云中的计算实例。
我们将从最基本的单元——虚拟机(VM)开始。在云中启动一个虚拟机通常可以通过Web控制台、命令行工具或API完成。例如,使用Google Cloud的gcloud命令行工具启动一个VM的基本命令可能类似于:
gcloud compute instances create my-vm --machine-type=n1-standard-1 --image-family=debian-10
然而,IT自动化的核心是管理成百上千的实例,而非单个。这就需要用到更高级的工具和模式。
以下是管理虚拟机群组(Fleet)的一些常见方法:
- 配置管理工具:如Ansible, Puppet, Chef。它们可以确保所有服务器的配置保持一致和符合预期。
- 模板与镜像:创建包含预装软件和配置的虚拟机镜像或模板,用于快速、一致地部署新实例。
- 编排工具:如Kubernetes(用于容器)或Terraform(用于基础设施即代码)。它们可以声明式地定义和管理整个应用栈或基础设施集群的状态。
- 自动伸缩组:云服务提供的功能,可根据预设的指标(如CPU使用率)自动增加或减少虚拟机实例数量。
实践练习:部署Web服务器集群 🌐
理论需要结合实践。在本节中,你将运用所学的云自动化知识,完成一个实际任务。
在接下来的Quicklabs练习中,你的目标是使用自动化工具在云中部署一批Web服务器。这将涉及使用脚本或配置管理工具,批量创建虚拟机实例,并在其上安装和配置Web服务器软件(如Apache或Nginx),最终确保它们能够正常提供服务。
这个过程将让你亲身体验如何利用云平台的弹性和自动化工具,快速、可靠地搭建一个可扩展的服务基础设施。
本节课中我们一起学习了云计算的基本概念,比较了本地与云部署的差异,并探索了管理云中虚拟机实例的各种自动化工具和方法。最后,我们预告了通过实践练习来部署Web服务器集群,以巩固所学知识。掌握这些内容,是迈向高效IT云自动化的第一步。
119:云服务概览 ☁️

在本节课中,我们将要学习“云服务”的基本概念。我们会探讨云服务的本质、不同类型以及选择云服务时需要考虑的关键因素,例如区域和延迟。
当我们说一项服务运行在“云”中时,我们实际指的是什么?
这与天空中那些白色、蓬松的东西毫无关系。
它仅仅意味着该服务运行在别处,要么在数据中心,要么在我们可以通过互联网访问的其他远程服务器上。
这些数据中心容纳了大量各式各样的机器。
不同类型的机器用于不同的服务。例如,一些机器可能配备本地固态硬盘(SSD)以提升性能,而另一些则可能依赖通过网络挂载的虚拟驱动器以降低成本。
云提供商通常提供一系列不同类型的服务。
用户最常使用的是软件即服务类别。
软件即服务(SaaS)是指云提供商向客户交付一整套应用程序或程序。
如果你选择像Gmail这样的云电子邮件解决方案、像Dropbox这样的云存储解决方案,或者像Microsoft Office 365这样的云生产力套件,你只有少量选项可供选择或定制。
云提供商为你管理与服务相关的一切,包括决定其托管位置、确保服务有足够容量满足你的需求、频繁可靠地执行备份等等。
许多不同的云提供商或其他互联网公司都提供大量软件即服务。
但是,当然,并非我们所有的需求都能通过预打包的软件解决。有时我们需要开发自己的软件。
对于我们软件的某些组件,我们可能会选择使用平台即服务。
平台即服务(PaaS)是指云提供商向客户提供一个预配置的平台。
这里说的“平台”可能有点令人困惑,因为在PaaS模型下存在许多不同的平台。
让我们看一个例子来更好地理解这一点。假设你需要一个SQL数据库来存储应用程序的部分数据。
你可以选择在自己的硬件上托管这个数据库。为此,你需要在那台计算机上安装操作系统,然后在选定的操作系统上安装SQL软件。
这需要对这些不同的部分有基本的了解,仅仅是为了让数据库运行起来。
有很多环节可能出错。即使你最终能解决所有问题,也可能需要一段时间。
相反,你可以决定使用提供SQL数据库即服务的云提供商。这样,你就可以专注于编写SQL查询和使用平台,而让云提供商处理其余的事情。
云提供商提供许多不同的平台即服务。
但是,当然,它们不太可能覆盖你所有的需求。
如果你需要对正在运行的软件及其与系统中其他部分的交互方式有高度的控制权,你可能想选择基础设施即服务。
基础设施即服务(IaaS)是指云提供商仅提供最基础的计算体验。
通常,这意味着一个虚拟机环境以及连接虚拟机所需的任何网络组件。
云提供商不会关心你用虚拟机来做什么。你可以用它们来托管网络服务器、邮件服务器、具有你自己配置的自有SQL数据库,或者更多其他可能性。
在云提供商的IaaS产品上运行你的IT基础设施是一个非常流行的选择。
市面上有许多大大小小的不同提供商,提供可以在他们的云中运行虚拟机的服务。
一些IaaS产品包括亚马逊的EC2、谷歌计算引擎和微软Azure计算服务。
现在,无论使用何种服务模型和提供商,当你设置云资源时,都需要考虑区域。
一个区域是一个包含若干数据中心的地理位置。
区域包含可用区,而可用区可以包含一个或多个物理数据中心。
如果其中一个因某种原因发生故障,其他数据中心仍然可用,服务可以迁移而不会显著影响用户。
大型云提供商通常在全球许多不同的区域提供他们的服务。
通常,你选择的区域和可用区应该最接近你的用户。
你的用户距离物理数据中心越远,他们可能体验到的延迟就越高。
这听起来可能有点奇怪,但想象一下如果你在海外度假,你可能会注意到你的银行网站加载速度稍慢一些。
这就是为什么通常的做法是将数据中心设在用户实际生活、工作和办理银行业务的地方附近。
在选择区域或可用区时,延迟并不是唯一需要考虑的因素。
一些组织出于法律或政策原因,要求其数据存储在特定的城市或国家。
如果你的服务使用其他服务作为依赖项,那么将服务物理上托管在其依赖项附近是一个好主意。
例如,如果邮件服务器需要数据库服务器来发送电子邮件,那么将数据库服务器和邮件服务器托管在同一个可用区是有意义的。
我们之前提到过,Quicklabs是一个使用云基础设施的服务。
那么QuickLabs使用哪种类型的云服务呢?QuickLabs使用基础设施即服务(IaaS)。

虚拟机仅预装了操作系统,然后实验室自动化工具会将任何额外的文件和软件部署到该操作系统中。
接下来,我们将讨论如何利用云提供商提供的服务来帮助我们扩展应用程序。
本节课中,我们一起学习了云服务的核心概念。我们明确了“云”的本质是远程数据中心,并详细介绍了三种主要的服务模型:软件即服务、平台即服务和基础设施即服务。我们还探讨了选择云服务时需要考虑的地理区域、可用区和延迟等因素,为后续学习如何利用云服务扩展应用打下了基础。
120:云端扩展 ☁️

在本节课中,我们将要学习在云端部署解决方案时,如何轻松、快速地扩展我们的服务。我们将探讨容量的概念、扩展的两种主要方式(水平与垂直),以及自动与手动扩展的区别。
将解决方案部署到云端最酷的特性之一,就是我们能够多么轻松、快速地扩展部署。在传统的IT环境中,如果你的团队需要一台额外的服务器来改善服务,你需要购买额外的硬件、安装操作系统和应用软件,然后将新计算机与其余基础设施集成。完成所有这些工作需要时间。因此,如果服务的使用量增加或减少,要快速扩展或缩减规模并不容易。换句话说,修改部署的容量需要花费大量时间。
在此上下文中,容量指的是服务能够提供多少。可用容量与所涉及服务器的数量和规模有关。我们通过添加更多服务器或用更大的服务器替换它们来获得更多容量。
我们衡量系统容量的方式取决于系统在做什么。如果我们在存储数据,我们可能关心总的可用磁盘空间。如果我们有一个响应外部用户查询的Web服务器,我们可能关心每秒可以回答的查询数量,这被称为每秒查询数或QPS。或者也可能关心一小时内服务的总带宽。我们也可以用其他有趣的方式来衡量容量,比如一小时内服务的猫视频数量,或者系统可以计算的圆周率位数。
我们的容量需求会随时间变化。假设你托管一个电子商务网站,需要100台服务器来满足用户需求。随着服务越来越受欢迎,需求可能会增长,你将需要增加可用容量。最终,系统可能需要1000台服务器来满足用户需求。这种容量变化被称为扩展。具体来说,当我们增加容量时,我们称之为向上扩展;当我们减少容量时,称之为向下扩展。例如,如果产品需求下降,或者系统经过改进需要更少的资源,就可能发生向下扩展。
云提供商通常拥有大量可供其客户使用的可用容量。当我们选择在云端托管我们的基础设施时,我们就是在购买和使用提供商的部分容量,以补充或完全取代我们的本地容量。这让我们能够轻松扩展服务以满足需求。
上一节我们介绍了容量的概念,本节中我们来看看扩展服务的两种主要方式:水平扩展和垂直扩展。
以下是两种扩展方式的说明:
- 水平扩展:要水平扩展部署,我们向特定服务所属的池中添加更多节点。假设你的Web服务使用Apache来提供网页。默认情况下,Apache配置为支持150个并发连接。如果你希望能够同时服务1500个连接,你可以部署10个Apache Web服务器,并在它们之间分配负载。这就是水平扩展。你添加更多服务器来增加容量。如果流量上升,你只需添加更多服务器来跟上需求。
- 垂直扩展:如果你垂直扩展部署,意味着你正在使你的节点变得更大。这里所说的“更大”,指的是分配给节点的资源,如内存、CPU和磁盘空间。例如,一个拥有100GB磁盘空间的数据库服务器可以比只有10GB空间的服务器存储更多数据。要扩展此部署,我们可以直接为机器添加更大的磁盘。同样的想法也适用于CPU和内存。假设你有一个缓存服务器,你注意到它正在使用95%的可用内存。你可以通过为节点添加更多内存来处理这个问题。
根据我们的部署和需求,我们可能需要同时进行水平和垂直扩展来提升服务的容量。换句话说,就是向我们的池中添加更多、更大的节点。
这种扩展方法与你在本地运行服务器时需要做的并没有太大不同。区别在于,我们无需派人去更改物理部署(例如,为服务器添加更多物理内存或在服务器机架中添加10台物理机器),而只需通过点击Web用户界面中的一些按钮,或使用配置管理系统来自动化扩展过程。云提供商构建的基础设施将部署我们所需的任何额外资源。
在讨论云端扩展时,我们需要考虑的另一个方面是扩展是自动完成还是手动完成。
以下是两种扩展模式的区别:

- 自动扩展:当我们将服务设置为使用自动扩展时,我们使用的是云提供商提供的一项服务。该服务使用指标来自动增加或减少系统的容量。假设你有一个系统,当前容量为每分钟服务1000个猫视频。如果对这些视频的需求增加到每分钟10000个(并且它会的),负责自动扩展的软件将添加资源并增加整体容量以满足此需求。当用户停止观看猫视频时,自动化系统将移除任何未使用的资源,以保持较低的运营成本。但要确保为你的自动扩展系统设置合理的配额,否则,一个戴着帽子的超可爱猫的病毒视频,可能会给你带来一份来自云提供商的、不那么可爱的大额账单。
- 手动扩展:使用手动扩展意味着更改由人而非软件控制。手动扩展也有其优缺点。当云部署不是很复杂时,对于较小的组织来说,通常更容易使用手动扩展。假设你的公司目前有一台邮件服务器,并且你知道在六个月内会需要另一台。在这种情况下,没有必要用自动扩展器使系统过度复杂化。你可以简单地在这段时间内添加额外的服务器。这里的权衡是,如果没有良好的监控或警报,没有自动扩展技术的系统可能会遭受需求意外增加的影响。如果你对一个变得流行且需求快速增长的服务使用手动扩展,你可能无法足够快地增加容量。这可能会引发许多问题,从性能不佳到实际的服务中断。
在本节课中,我们一起学习了云端托管解决方案的核心概念,如容量和扩展。你可能已经注意到,云技术为IT团队提供了大量好处,但它也可能有点令人望而生畏。
接下来,我们将探讨IT团队可能对迁移到云端犹豫不决的一些原因,以及如何克服这些担忧。
121:评估云计算 ☁️

在本节课中,我们将学习如何评估云计算方案。我们将探讨从传统IT环境迁移到云端的考量因素,包括控制权的变化、安全责任以及如何根据自身需求选择合适的云服务模型。
如果你一直在一个传统的IT环境中工作,公司物理拥有自己的服务器,那么迁移到云端的想法可能会让你感到不安。当你自己管理服务器时,如果出现问题,你可以直接走到服务器前修复它,或者从同一网络内部通过SSH连接它。你可以快速应用修复,让用户很快恢复工作。
作为IT团队的一部分,你拥有硬件、软件、网络连接以及其间的一切,这让你对整个系统的运行拥有很大的控制权。而在云解决方案中,我们需要将部分控制权交给云提供商。
根据我们选择的服务模型——无论是软件即服务(SaaS)、平台即服务(PaaS)还是基础设施即服务(IaaS)——我们拥有不同级别的控制权。
上一节我们提到了控制权的变化,本节中我们来看看不同的云服务模型。
以下是三种主要的云服务模型:
- 软件即服务(SaaS):选择使用SaaS时,我们基本上将应用程序如何运行的控制权完全交给了提供商。我们可以更改的设置有限,但无需担心系统的运行维护。当提供的软件满足我们所有需求,并且我们更愿意专注于使用软件本身时,这是一个很好的选择。但正如我们指出的,以这种预打包方式提供的应用程序数量有限。
- 平台即服务(PaaS):如果我们需要创建自己的应用程序,可以使用PaaS。通过这个选项,我们负责代码,但不控制应用程序的运行。
- 基础设施即服务(IaaS):我们也可以选择IaaS,这样我们仍然可以保持高度的控制权。我们决定在虚拟机上运行的操作系统、安装在其上的应用程序等。不过,部署的其他方面,如网络配置或服务可用性,仍然依赖于供应商。
如果出现问题,你可能需要供应商的支持来解决问题。因此,在选择云提供商时,了解可用的支持类型并选择符合你需求的提供商非常重要。放弃对硬件、网络和整体基础设施的控制权听起来可能很奇怪,但就我个人而言,我发现不必担心维护运行我们服务的机器是件很棒的事。这意味着我们可以将执行工作负载的服务器视为商品,而不是特殊的“雪花”。
一个可能让你对迁移到云端犹豫不决的方面是,你并不确切知道正在实施哪些安全措施。因此,在选择使用哪个提供商时,检查他们如何保护你的实例和数据安全至关重要。你可以寻找一系列认证,如SOC1、ISO 27001和其他行业认可的资质,以验证你的提供商在安全方面进行了投资。

一旦你确信你的提供商采取了正确的安全措施,可能会想把安全问题完全交给专业人士而不再过问。但作为云用户,我们也有责任遵循合理的安全实践。谷歌、亚马逊、微软和其他云提供商在安全研究上投入巨资,但如果你云实例的root密码是“password1”,或者实例没有使用防火墙,这些投入就毫无意义。换句话说,无论部署在本地运行的物理服务器上,还是云中的虚拟机上,我们都应始终运用合理的判断来保护我们部署的机器。
同样重要的是要记住,正确实施安全系统可能成本高昂。一些高度敏感的部署可能需要专门的安全程序,如多因素认证、加密文件系统或公钥加密,但这些流程的实施也可能很昂贵。值得考虑这些技术是否对你的特定用例是必要的。如果你的应用程序存储近期的患者健康记录,这是需要保护的重要数据,你会希望应用最严格的安全实践。但如果你处理的是19世纪的患者健康记录,由于其年代久远,数据敏感性低得多,你需要的安全措施就不必那么全面。
你可能对云提供商抱有疑虑还有其他原因。例如,你可能担心数据将存储在哪里,或者担心提供的支持无法满足你的需求。无论出于何种原因,仔细阅读服务条款以了解条件,并弄清楚所提供的服务是否能满足你的需求,这非常重要。从某种意义上说,云服务有点像真正的云朵。它们有各种形状和大小,有时一片黑暗的暴风雨云会来扰乱你高效的一天。但如果你提前准备好正确的安全措施,也许再加一把“伞”,那么在云端工作将只是一阵清风。
那么,假设你已决定将部分基础设施迁移到云端,接下来该做什么?迁移到云端是一个大话题,我们将在下一个视频中讨论。
本节课中,我们一起学习了评估云计算的关键因素。我们比较了传统IT与云环境的控制差异,详细介绍了SaaS、PaaS和IaaS三种服务模型及其控制级别。我们还强调了安全是共同责任,用户必须与提供商协作,根据数据敏感性实施适当的安全措施。最后,我们指出选择云服务时需要仔细审查服务条款和支持选项。下一课,我们将深入探讨具体的云迁移策略。
122:迁移到云端 ☁️

在本节课中,我们将学习如何将IT基础设施迁移到云端。我们将探讨不同的迁移策略、云服务模型(如IaaS和PaaS)以及各种云部署模式(如公有云、私有云、混合云和多云)。理解这些概念对于在现代IT环境中高效工作至关重要。
云迁移概述
如今,许多公司都在考虑将其至少部分IT基础设施迁移到云端。
迁移的具体细节取决于您当前基础设施的状况以及您希望通过迁移到云服务提供商实现的目标。
总体而言,我们需要在对提供服务的计算机的控制权和维护它们所需的工作量之间进行权衡。
基础设施即服务
我们提到过,当我们使用基础设施即服务时,我们是在云提供商的基础设施上运行的虚拟机中部署我们的服务。
我们对基础设施的设计拥有很大的控制权,这可能非常有用。
例如,我们可以决定使用哪种可用的机器类型,以及为它们附加何种存储。
IaaS对于采用“直接迁移”策略的管理员特别有用。
理解“直接迁移”策略
那么,这是什么意思呢?
假设您在一家正在扩张的小型组织工作。随着公司发展,员工的物理空间、办公桌、乒乓球桌和打印机变得稀缺。
最终,整个办公室可能需要搬到一个更大的空间。
这意味着不仅要移动办公桌和打印机,还要移动任何在本地运行的服务器。
如果需要移动物理服务器,您可能需要从旧办公室取出一台服务器,在维护窗口期间将其关闭,装上卡车,然后实际运送到新地点。
这可能是新办公室,甚至可能是一个小型数据中心。
所以,您实际上是“抬起”服务器并将其移动到新位置。这就是“直接迁移”中“迁移”一词的由来。
当迁移到云端时,过程有些类似。但您不是将物理服务器搬到卡车上,而是将本地运行的物理服务器迁移到在云端运行的虚拟机上。
在这种情况下,您是从一种运行服务器的方式“转换”到另一种方式。
这两种方法的关键在于,服务器的核心配置保持不变。
无论服务器是物理托管在本地还是虚拟托管在云端,需要在机器上安装以提供其功能的软件是相同的。
如果您已经使用配置管理来部署和配置您的物理服务器,那么迁移到云设置可能会非常容易。
您只需将相同的配置应用到在云端运行的虚拟机上,就能复制该设置。
另一方面,使用此策略意味着您仍然需要自己安装和配置应用程序。
您需要确保操作系统和软件保持最新,在更新时功能不会中断,以及其他一系列事情,具体取决于服务器运行的具体应用程序。
平台即服务
在这种情况下,一种替代方案是使用平台即服务。
当您有特定的基础设施需求,但不想参与平台的日常管理时,这非常合适。
在之前的视频中,我们提到了可以这种方式使用的SQL数据库示例。
通过将数据库的管理交给云提供商,您无需担心为计算机连接正确的磁盘、配置数据库或任何其他与机器设置相关的任务。
相反,您可以专注于使用数据库。
平台即服务的另一个例子是托管Web应用程序。
使用此服务时,您只需关心为Web应用程序编写代码,而无需关心运行它的框架。
这可以加速开发,因为开发人员无需花时间管理平台,可以只专注于编写代码。
一些流行的托管Web应用程序平台包括Amazon Elastic Beanstalk、Microsoft App Service和Google App Engine。
虽然这些平台非常相似,但它们并不完全兼容。因此,从本地框架迁移或在供应商之间切换将需要一些代码更改。
容器化应用
您可能听说过的另一个相关概念是容器。
容器是与它们的配置和依赖项打包在一起的应用程序。
这使得应用程序无论在使用何种环境运行时,都能以相同的方式运行。
换句话说,如果您有一个运行应用程序的容器,您可以将其部署到您的本地服务器、云提供商或不同的云提供商。
无论您选择哪一种,它都将始终以相同的方式运行。
这使得从一个平台迁移到另一个平台变得非常容易。
云部署模式
在讨论迁移到云端时,您可能还会听到公有云、私有云、混合云和多云。
让我们看看这些术语各自意味着什么。
我们将第三方提供给您的云服务称为公有云。
之所以称为“公有”,是因为云提供商也向公众提供服务。
私有云是指您的公司拥有服务及其余基础设施的情况,无论是在本地还是在远程数据中心。
它之所以是“私有”的,是因为它只供您的公司使用,就像拥有您自己天空中的云。
混合云是公有云和私有云的混合体。
在这种情况下,一些工作负载在您公司拥有的服务器上运行,而另一些则在第三方拥有的服务器上运行。

充分利用混合云的关键在于确保一切都能平稳集成。
这样,无论数据托管在何处,您都可以无缝地访问、迁移和管理数据。
最后,多云是跨供应商的公有云和/或私有云的混合体。
例如,多云部署可能包括托管在Google、Amazon、Microsoft以及本地的服务器。
混合云只是多云的一种类型,但关键区别在于多云将使用多个供应商,有时还包括本地服务。
使用多个云可能很昂贵,但它为您提供了额外的保护。如果您的某个提供商出现问题,您的服务可以在不同提供商提供的基础设施上继续运行。
总结
在本节课中,我们一起学习了将IT基础设施迁移到云端的关键概念。我们探讨了IaaS和PaaS两种主要服务模型,理解了“直接迁移”策略的含义。我们还介绍了容器技术如何简化跨环境部署。最后,我们区分了公有云、私有云、混合云和多云这几种不同的云部署模式,了解了它们各自的优缺点和适用场景。掌握这些知识将帮助您为组织制定合适的云迁移和部署策略。
123:在云中启动虚拟机 🚀

在本节课中,我们将学习如何在云平台上启动和配置虚拟机。我们将了解创建虚拟机时需要设置的关键参数,并探索如何使用Web界面和命令行界面来管理这些资源。最后,我们将介绍如何使用参考镜像和模板化技术来自动化虚拟机的创建和配置过程。
我们已经讨论了很多关于云的工作原理、其中涉及的不同概念及其含义。
在接下来的几个视频中,我们将向您展示在云上执行的一些常见操作是什么样子,并进行实践。
正如我们指出的,您可以为项目使用许多不同的云服务提供商。每个提供商都有一些特定的优势,具体取决于您想要实现的目标。
虽然某个提供商使用的术语可能与其他提供商不完全相同,但其核心概念是相同的。在这些视频中,我们将使用Google Cloud Platform来演示我们的示例,因为这是我们最熟悉的平台。
所有云提供商都为您提供了一个控制台,用于管理您正在使用的服务。这个控制台包含了提供商提供的许多不同服务的入口。
起初,看到所有可用的选项可能会让人有点眼花缭乱,因此在尝试用它做任何事情之前,最好先熟悉一下平台。例如,可以查看可用的菜单和选项,并找出允许您使用基础设施即服务(IaaS)的部分位于何处。
无论具体的菜单条目是什么,当您想要创建在云中运行的虚拟机时,都需要设置一系列参数。云基础设施使用这些参数来启动具有我们所需设置的机器。
以下是创建虚拟机时需要配置的主要参数:
- 实例名称:您需要首先选择分配给实例的名称。这个名称稍后将帮助您识别实例,以便连接、修改甚至删除它。
- 区域和可用区:您还必须选择实例运行的区域和可用区。正如我们在之前的视频中指出的,通常您会希望选择一个靠近用户的区域,以提供更好的性能。
- 机器类型:另一个需要选择的重要选项是虚拟机的机器类型。云提供商允许用户配置其虚拟机的特性以满足需求。这意味着选择虚拟机将分配多少个处理单元(或虚拟CPU)以及多少内存。您可能会想选择最强大的虚拟机,但当然,虚拟机越强大,运行它的成本就越高。作为系统管理员,您可能需要在成本和处理能力之间做出权衡,以满足组织的需求。在设置此类实例时,最好从小规模开始,然后根据需要扩展。
- 启动磁盘:除了可用的CPU和内存之外,您还需要选择虚拟机将使用的启动磁盘。在云中运行的每个虚拟机都有一个关联的磁盘,其中包含它运行的操作系统和一些额外的磁盘空间。创建虚拟机时,您需要选择要为虚拟磁盘分配多少空间,以及希望机器运行什么操作系统。
为了创建这些资源,我们可以使用Web界面或命令行界面。
Web用户界面对于快速检查我们需要设置的参数非常有用。界面将让我们比较不同的可用选项,甚至显示我们选择的虚拟机每月大概需要多少费用。这对于实验来说很棒,但如果我们需要快速创建一批机器,或者想要自动化创建过程,它的扩展性就不太好。
在这些情况下,我们将使用命令行界面,它允许我们一次性指定所需内容,然后多次使用相同的参数。使用命令行界面,我们可以从脚本中创建、修改甚至删除虚拟机。这是迈向自动化的重要一步,但还不止于此。
我们还可以自动化准备这些虚拟机内容的过程。想象一下,花一个下午的时间安装和配置您的新Web服务器。😊
您可以在一个机器上完成此操作,这个过程相当简单。您安装任何必要的软件,修改任何配置设置,然后确保其正常工作。但是,很难在另一台机器上完全复制这个过程,更不可能在数千台机器上完成。
这就是参考镜像和模板化发挥作用的地方。参考镜像以可重用的格式存储机器的内容,而模板化是捕获所有系统配置的过程,以便我们以可重复的方式创建虚拟机。
参考镜像的确切格式将取决于供应商,但结果通常是一个称为磁盘镜像的文件。磁盘镜像是虚拟机磁盘在给定时间点的快照。

好的模板化软件允许您复制整个虚拟机,并使用该副本来生成新的虚拟机。根据软件的不同,磁盘镜像可能不是原始机器的精确副本,因为某些机器数据(如主机名和IP地址)会发生变化,但它将包含我们需要的数据,使其可在大量虚拟机上重用。如果我们想构建一个拥有10000台机器的集群,并且所有机器都安装了相同的软件,这将非常有帮助。
接下来,我们将对这个过程进行一些演示。我们将向您展示如何在Google Cloud控制台中创建新的虚拟机,如何自定义这些虚拟机,以及如何使用模板化和参考镜像来自动化创建过程。😊
在本节课中,我们一起学习了在云中启动虚拟机的完整流程。我们了解了创建虚拟机时需要配置的核心参数,如名称、区域、机器类型和启动磁盘。我们还比较了使用Web界面和命令行界面进行管理的不同场景,认识到CLI在自动化和批量操作中的优势。最后,我们探讨了通过参考镜像和模板化技术来实现虚拟机配置的标准化和自动化,这是构建可扩展、一致云基础设施的关键一步。
124:使用GCP Web界面创建新虚拟机 🖥️

在本节课中,我们将学习如何在Google Cloud Platform(GCP)项目中,通过Web用户界面(UI)创建并配置一台虚拟机(VM)。我们将从创建项目开始,逐步完成虚拟机的命名、区域选择、机器类型配置、启动磁盘设置,并最终通过SSH连接到新创建的虚拟机。

导航至GCP控制台

首先,我们需要访问GCP的云控制台。请打开浏览器,导航至 console.cloud.google.com。这是管理所有GCP服务和资源的中央界面。
创建新项目
在控制台中,第一步是创建一个新项目。所有虚拟机都需要关联到一个特定的项目,以便进行资源管理和计费。
我们为项目命名,例如“first-cloud-steps”。创建过程需要几秒钟。项目创建完成后,仪表板会显示更多信息。
进入虚拟机实例创建界面
接下来,我们需要找到创建虚拟机的菜单入口。为此,请进入“Compute Engine”菜单,并选择“VM instances”选项。
由于目前没有任何虚拟机,该屏幕显示为空。我们可以通过点击“Create”按钮来开始创建新的虚拟机。
配置虚拟机参数
点击创建后,我们会看到许多可以为此虚拟机设置的选项。以下是需要配置的主要参数:
命名虚拟机
我们将此虚拟机命名为“linux-instance”。
选择区域和可用区
点击区域下拉菜单,可以看到所有当前可用于创建新虚拟机的区域。点击可用区下拉菜单,可以看到该区域内可用于新虚拟机的可用区。
对于本示例,我们保留默认区域。但请注意,如果您部署的是服务,应选择靠近您用户的区域。

选择机器类型
我们需要选择要使用的机器类型。可以在“通用型”和“内存优化型”等系列之间选择。每个系列下又有多种不同的机器类型。
我们可以选择虚拟机所需的CPU数量和内存大小。正确的选择取决于我们计划用这台计算机做什么。对于本示例,我们保留默认机器类型。
配置启动磁盘
选择虚拟机后,需要选择要使用的磁盘。默认磁盘大小为10 GB,并预装了Debian操作系统镜像。
我们可以通过点击“Change”按钮选择不同的大小或不同的操作系统。操作系统列表很长,正确的选择取决于您计划用此实例做什么。

对于本示例,我们选择其中一个Ubuntu版本。我们还可以选择要使用的磁盘类型:标准磁盘(更便宜)或SSD版本(更快)。如果需要为服务器提供额外存储,也可以更改大小。目前,我们保留此处的默认值。
配置访问与防火墙规则
在启动磁盘设置之后,我们会看到用于确定机器访问方式的选项。根据项目的其他部分,这可以非常简单,也可以非常复杂。
默认的访问选项允许您使用SSH远程访问实例,因此我们现在选择它。
最后,创建向导允许我们预配置一些防火墙规则。选择这两个选项之一将允许HTTP或HTTPS流量到达我们的机器。当然,您可能还需要设置更多防火墙规则,这些可以在机器创建后另行设置。
在后续视频中,我们将需要连接到此机器上的Web服务器,因此让我们先启用HTTP。
还有许多其他选项可以设置,它们隐藏在此链接下。由于默认设置对我们的测试机器有意义,我们现在不深入研究这些,但您可以自行查看可以设置的其他参数。
通过命令行了解创建过程
我们基本上已准备好创建虚拟机。但在创建之前,让我们点击“command line”链接。这将向我们展示如何通过命令行创建相同的虚拟机。
这是一个很长的命令行。但请放心,您不需要理解所有参数。这里的要点是,您可以选择所有需要的选项来创建所需的虚拟机,然后复制此命令以创建一批与您选择的虚拟机完全相同的虚拟机。
现在,我们关闭此窗口,然后使用“Create”按钮创建虚拟机。
创建并连接虚拟机
我们的实例正在创建中。这需要一些时间。系统正在为我们的机器分配必要的资源、部署操作系统镜像、连接网络接口等。


一旦设置完成,我们就可以使用SSH连接到它。同样,系统设置我们将用于登录的密钥需要一点时间。但一旦完成,我们就可以远程使用这台机器。
让我们检查一下我们创建的机器是否使用了我们选择的操作系统。至此,我们已经使用Web界面创建了一台虚拟机,并使用SSH连接到它。这非常酷。
一旦登录到机器,您可以像对待任何普通的Linux机器一样对待它,这非常棒。例如,我们可以通过调用curl命令来获取当前位置的天气文本版本,该命令可用于从命令行访问网页,并传入wttr.in作为网站。
看起来云端是多云天气。接下来,我们将通过向虚拟机部署一个简单的Web应用程序来开始进行实验。

总结
在本节课中,我们一起学习了如何在Google Cloud Platform上通过Web界面创建一台完整的虚拟机。我们完成了从创建项目、配置虚拟机参数(包括名称、区域、机器类型和启动磁盘),到设置网络访问和防火墙规则的全过程。最后,我们成功通过SSH连接到新创建的虚拟机,验证了其运行状态。这为后续在云环境中部署和应用服务打下了坚实的基础。
125:在GCP中定制虚拟机 🖥️

在本节课中,我们将学习如何定制一个云虚拟机,将其配置为可自动启动的Web服务器,并最终将其转化为一个可重复部署的参考镜像模板。这是实现大规模云部署的关键一步。
从单机到规模化部署
上一节我们介绍了如何在云中创建单个虚拟机。这很酷,但在云计算的规模下作用有限。请记住,大规模的云部署通常由成百上千台机器组成。因此,创建单台服务器仅仅是开始。
本节中,我们将对这台虚拟机进行一些修改,以便能够进行规模化部署。完成后,我们将把配置好的实例作为创建参考镜像的基础。参考镜像是一个可以反复部署、并能通过自动化工具使用的文件或配置。这非常重要,因为它能让我们快速构建可扩展的服务。
让我们从登录上一节视频中创建的虚拟机开始。

部署应用程序代码
我们将使用Git,它允许我们克隆包含待部署应用程序代码的仓库。
克隆的仓库包含一个用Python编写的非常简单的Web服务应用程序。让我们运行它看看会发生什么。

我们的脚本打印了一行信息,说明它正在监听8000端口上的连接。
在后台,应用程序正在打开一个套接字并在该端口上监听HTTP连接。在本例中,它运行在8000端口。如果我们在本地机器上运行,就可以连接到该端口。
但这运行在云中的虚拟机上,该虚拟机配有防火墙,并且只开放了几个端口。我们有哪些选择?
脚本实际上允许我们将要打开的端口号作为参数传递。我们希望它运行在我们上一节视频中配置的HTTP端口上,即端口80。
因为这是一个系统端口,为了让我们的应用程序使用它,我们需要以管理员权限运行。所以,现在让我们按 Ctrl+C 停止正在运行的进程,然后使用 sudo 并以80作为参数再次运行它。
sudo python3 app.py 80

现在,我们可以访问由我们的虚拟机提供的网站并查看其内容。让我们导航到它。
我们的Web应用程序非常简单。它只是在生成网页时打印“Hello Cloud”。它还打印了机器的主机名和IP地址。这将在我们后续进行大规模部署时有所帮助。
配置为自动启动服务

好了,我们有一个运行在HTTP端口上的Web服务应用程序。这很好,但我们必须手动启动应用程序,所以这无法扩展。
为了让我们的应用程序自动启动,我们需要将其配置为一项服务。幸运的是,我们的仓库已经包含了可以使用的服务定义文件。
让我们查看该文件的内容。这是一个systemd文件,它是大多数现代Linux发行版使用的初始化系统。
如果你不明白这里发生了什么,不用担心。你不需要理解这个文件的细节就知道如何将服务部署到云端。只需注意,配置期望我们要执行的脚本位于 /usr/local/bin 目录下。
我们需要将该文件复制到那里,然后将服务文件复制到 /etc/systemd/system 目录,这是用于配置systemd服务的目录。
最后,我们需要告诉 systemctl 命令,我们希望启用此服务,以便它能自动运行。
以下是需要执行的步骤:
- 将应用程序脚本复制到系统目录。
sudo cp app.py /usr/local/bin/ - 将服务定义文件复制到systemd目录。
sudo cp webapp.service /etc/systemd/system/ - 启用并启动服务。
sudo systemctl enable webapp.service sudo systemctl start webapp.service
现在,我们已经完成了这些步骤。任何时候这台机器启动,它都会启动我们配置的Web应用程序,我们将能够看到之前看到的内容。
让我们通过触发一次重启来测试一下。

验证服务自动启动
我们已经重启了机器。这需要一些时间来完成。它告诉我们连接已断开,我们可以让终端尝试重新连接。这需要一点时间,直到机器完成重启并准备好接收连接。

耐心点,我的朋友。好了,我们的虚拟机已经重启。我们可以使用 ps ax 命令获取运行进程列表,并使用 grep 命令进行过滤,只保留匹配特定模式的进程,以此来检查我们的应用程序是否在运行。在本例中,我们将使用“hello”作为模式。
ps ax | grep hello
太好了,我们的应用程序现在可以在启动时自动运行了。我们几乎准备好将配置好的虚拟机转变为创建更多虚拟机的模板了。
但在这样做之前,我们需要考虑一下,当我们想要对Web应用程序进行更改时,将如何升级它。
集成配置管理
这里有很多不同的选择。一个选择是每次应用程序有新版本时,都创建一个不同的参考镜像。这意味着删除所有旧的虚拟机,并基于新镜像创建新的虚拟机。
另一个选择是向镜像中添加一个配置管理系统,这样我们就可以用它来管理虚拟机创建后的任何更改。我们已经知道如何使用Puppet管理更改。还记得我们之前视频中的Puppet主服务器培训吗?
让我们在这个实例中安装Puppet客户端,以便将来可以使用Puppet。
当我们研究Puppet服务器和客户端设置时,我们看到在客户端需要运行一系列步骤才能准备好应用规则。我们克隆的仓库包含一个可以运行的脚本,它将为我们完成初始配置。它还会将Puppet进程设置为在启动时自动运行。让我们现在运行它。
sudo ./setup_puppet_client.sh
现在,任何时候这台机器启动,它都会提供我们的网站服务。如果我们想更新该网站的内容,可以使用我们的Puppet基础设施来完成。很好。
准备创建模板
我们的虚拟机现在已经准备好作为模板的基础,我们可以用它来创建任意数量的实例。接下来,我们将学习如何创建模板以及如何基于模板创建实例。

本节课中,我们一起学习了如何定制云虚拟机:部署代码、配置自动启动服务、验证其运行,并集成了Puppet以实现未来的配置管理。最终,我们将这台虚拟机准备成了一个可重复部署的参考镜像模板,为大规模、自动化的云部署奠定了基础。
126:模板化定制虚拟机 🖥️

在本节课中,我们将学习如何将一个已配置好的虚拟机(VM)转换为一个可复用的模板,并利用该模板批量创建新的虚拟机实例。我们将涵盖从停止虚拟机、创建磁盘镜像、生成实例模板,到最终通过图形界面和命令行批量部署虚拟机的完整流程。
概述
在前几节课程中,我们创建了一个虚拟机,并确保它已配置好以运行我们的Web应用程序,并能通过Puppet保持更新。现在,我们可以将此虚拟机作为基础,创建一个实例模板,然后使用该模板批量创建基于它的多个虚拟机。


创建磁盘镜像
要创建一个参考镜像,我们需要访问当前在计算机上运行的虚拟磁盘。因此,创建镜像的第一步是停止虚拟机。
虚拟机需要一段时间才能完全关闭。一旦完成,我们可以点击虚拟机的名称以查看其所有详细信息,然后点击其启动磁盘。
这些是附加到虚拟机的磁盘的详细信息。我们可以创建一个快照(即磁盘当前状态的完整副本),或者创建一个镜像(允许我们基于它创建模板)。让我们点击“创建镜像”。
我们将镜像命名为 web-server-image。创建向导显示,我们将基于 linux-instance-disk 创建镜像,这正是我们想要的。对于此示例,我们将保留其余设置为默认值。
现在开始创建我们的镜像。这将创建我们将用于模板的镜像。正如之前提到的,工具会保留镜像的大部分内容,但会删除那些在不同虚拟机之间应该不同的部分。
创建完成后,它会显示我们可以访问的所有镜像列表。这是一个很长的列表,其中包含了许多公共镜像以及我们创建的镜像。其他镜像是我们可以用来部署不同类型虚拟机的公共镜像。
创建实例模板
现在,我们准备好创建实例模板了。为此,我们将转到“实例模板”选项,然后点击“创建新实例模板”。
像往常一样,我们会看到一个包含许多可设置选项的向导。我们将保留大多数默认值,只更改几项内容。
我们将模板命名为 web-server-template。我们将更改启动磁盘,以使用我们创建的镜像。在此屏幕上,我们可以看到所有可用镜像的列表。默认情况下,列表显示平台提供的官方操作系统镜像。对于我们的模板,我们希望使用我们创建的自定义镜像。
最后,我们还需要启用对使用此模板创建的实例的HTTP访问。设置完毕,现在可以创建我们的新模板了。
创建需要一点时间。完成后,我们就可以基于我们的镜像创建实例了。我们将再次通过Web界面操作一次,然后学习如何通过命令行操作。
通过模板创建虚拟机实例

让我们返回“VM实例”条目,然后点击“创建实例”。这次,我们将使用我们准备好的模板来创建实例,而不是从头开始创建。
我们将实例命名为 web-server1,其他所有设置保持不变。请注意,它显示将使用我们选择的基础镜像,并且允许HTTP流量。
我们已经基于模板创建了第二个虚拟机。我们无需更改任何选项,因为所有值都已预先在模板中选定。我们想要的Web应用程序已准备就绪,无需我们进行任何配置。让我们验证一下。
是的,我们的应用程序已在此机器上成功运行。这很棒,但如果我们要创建10个这样的虚拟机,这仍然有点繁琐。对于此类批量操作,使用命令行界面会更好。
使用命令行界面批量创建

接下来,让我们使用命令行来操作。为了与Google Cloud交互,我们将使用 gcloud 命令。我们已经在这台机器上安装了 gcloud 命令。你可以在接下来的阅读材料中找到如何在不同平台上安装 gcloud 的指引。


我们首先运行 gcloud init 命令,该命令会设置此计算机与Google Cloud之间的身份验证机制。我们需要向Google Cloud系统进行身份验证,才能使用 gcloud 命令与其交互。
这会在我们的浏览器中打开一个新标签页,我们可以用它来使用我们的账户进行身份验证。让我们按照此处的流程进行身份验证。
我们现在已登录到我们的云账户。我们可以选择默认项目。让我们在这里选择一个。除了选择默认项目,初始化向导还允许我们选择默认区域和可用区。最好选择这个,因为如果我们不指定其他区域,将来使用的命令将默认使用该区域和可用区。
这是一个很长的列表。有许多不同的可用区可供我们的实例使用。正如之前提到的,在选择运行服务的位置时,应选择离你最近的位置。对于此示例,我们将直接选择 zone1。

完成此身份验证后,我们就可以使用 gcloud 命令来操作我们的云项目了。我们可以修改已创建的虚拟机、创建新的虚拟机、删除一些现有的虚拟机等等。
对于我们的示例,我们将使用它来创建五个额外的虚拟机。命令如下:
gcloud compute instances create --source-instance-template=web-server-template ws1 ws2 ws3 ws4 ws5
首先我们调用 gcloud,然后传递 compute 参数(用于处理与虚拟机相关的一切事务)。接着传递 instances 参数,因为我们将处理虚拟机实例本身。然后传递 create,因为我们想要创建实例。我们指定要使用名为 web-server-template 的源实例模板。最后,我们给出要部署的实例名称。
只需很短的时间,所有实例就创建完成了。这绝对比通过Web界面操作快得多,也更容易通过我们的脚本实现自动化。
总结
在本节课中,我们一起学习了如何创建虚拟机、对其进行定制、从中创建模板,并使用该模板批量创建新的、完全相同的虚拟机。希望你现在开始体会到,这在创建新的IT部署时是多么有用。
接下来,有一篇阅读材料,其中提供了关于我们演示的所有工具的更多信息,然后是一个快速测验,以练习你刚刚学到的所有内容。
127:云端大规模部署 🚀

在本节课中,我们将学习如何在云环境中大规模部署服务。我们将探讨负载均衡、自动扩缩容以及多层缓存架构等核心概念,以构建一个高可用、可扩展的云端应用系统。
概述
在之前的课程中,我们探讨了在云端运行服务时可以使用的一些功能。使用云服务的最大优势在于能够轻松地扩展或缩减服务规模。为了充分利用这一优势,我们需要进行一些准备工作。
我们将设置服务,以便通过向资源池添加更多节点来轻松增加其容量。这些节点可以是虚拟机、容器,甚至是提供特定服务的应用程序。
负载均衡器
当一项服务由多个提供相同功能的不同实例组成时,我们会使用负载均衡器。负载均衡器确保每个节点接收均衡数量的请求。
当一个请求到达时,负载均衡器会选择一个节点来提供响应。
负载均衡器使用多种不同的策略来选择节点。最简单的策略是轮询,即依次给每个节点分配一个请求。更复杂的策略包括:始终为来自同一来源的请求选择同一节点、选择距离请求者最近的节点,或者选择当前负载最轻的节点。
自动扩缩容
正如我们提到的,这类实例组通常被配置为在需求增加时启动更多节点,在需求下降时关闭一些节点。这种能力称为自动扩缩容。
它允许服务根据需要增加或减少容量,而服务所有者只需为任何给定时间正在使用的机器成本付费。
由于需求较低时一些节点会关闭,它们的本地磁盘也会消失,因此应被视为临时或短暂的。如果需要数据持久化,您必须创建单独的存储资源来保存该数据,并将该存储连接到节点。
这就是为什么我们在云端运行的服务通常连接到数据库的原因,该数据库也在云端运行。这个数据库通常也由负载均衡器后面的多个节点提供服务,但这通常由云提供商使用平台即服务模型来管理。
实践示例:多层Web应用架构
为了了解这在实践中如何运作,让我们看一个拥有大量用户的Web应用程序示例。
当您通过互联网连接到网站时,您的网络浏览器首先会检索您要访问的网站的IP地址。这个IP地址标识了一台特定的计算机,即网站的入口点。通常,一个网站会有多个不同的入口点。这允许服务即使在其中某个入口点出现故障时也能保持运行。此外,可以选择一个距离用户更近的入口点以减少延迟。
在一个小规模应用中,这个入口点可能就是提供页面的Web服务器,仅此而已。对于速度和可用性至关重要的大型应用,在入口点和实际Web服务之间会有几层架构。
第一层:Web缓存服务器池
第一层将是一个Web缓存服务器池,并配有一个负载均衡器来在它们之间分配请求。用于此缓存的最流行应用之一是Varnish,当然,它不是唯一的选择。Nginx Web服务软件也包含此缓存功能,并且有许多提供商提供Web缓存即服务,例如Cloudflare和Fastly。
无论使用何种软件,结果基本相同。当发出请求时,缓存服务器首先检查内容是否已存储在其内存中。如果存在,它们就用内容进行响应。如果不存在,它们会向配置的后端请求内容,然后存储起来,以便为未来的请求提供。
第二层:实际Web服务
这个配置的后端是为网站生成网页的实际Web服务,它通常也是一个在负载均衡器下运行的节点池。
为了获取任何必要的数据,此服务将连接到数据库。但是,因为从数据库获取数据可能很慢,所以通常还有一个专门用于数据库内容的额外缓存层。
第三层:数据库缓存

用于此级别缓存的最流行应用是Memcached和Redis。
如您所见,在这个架构中有很多不同的节点。幸运的是,一旦您完成了准备工作并设置好了配置,您就可以依赖云提供商提供的能力,根据需要自动扩展或收缩系统。
基础设施将负责添加和删除实例、分配负载、确保每个地理区域拥有适当的容量以及处理更多事务。
总结
在本节课中,我们一起学习了云端大规模部署的关键组件。我们了解了如何使用负载均衡器在多个服务实例间分配流量,以及如何通过自动扩缩容机制根据需求动态调整资源。我们还探讨了一个典型的高流量Web应用的多层架构,包括Web缓存层、应用服务层和数据库缓存层。理解这些概念有助于我们设计出既高效又经济、能够应对不同负载的云端系统。
128:什么是编排?🎼

在本节课中,我们将要学习编排的概念。编排是自动化领域中协调复杂系统配置的关键环节,尤其在云计算和多服务环境中至关重要。
概述
到目前为止,在整个课程中我们一直在讨论自动化。自动化指的是用自动发生的步骤替代手动操作的过程。
在之前的视频中,我们提到了几种自动化创建云实例的方法。我们可以使用模板创建新的虚拟机,可以运行命令行工具自动创建实例,或者选择启用自动扩缩容,让基础设施工具根据需求来处理。然而,所有这些自动创建新实例的过程都需要进行协调,以确保实例之间能够正确交互。这正是编排发挥作用的地方。
什么是编排?
编排是复杂IT系统和服务的自动化配置与协调。
换句话说,编排意味着自动化许多需要相互通信的不同事物。这通常包含大量不同的自动化任务,并涉及配置一系列不同的系统。
以我们上一个视频中看到的网站基础设施为例。我们已经了解了如何自动化创建系统中的每个实例。现在,假设你想在一个尚未有任何实例的独立数据中心部署该系统的新副本,你还需要自动化整个系统的配置,包括所涉及的不同实例类型、每个实例如何发现其他实例、内部网络的结构等等。
编排如何工作?
关键在于,整个系统的配置需要是可自动重复的。
我们可以使用多种不同的工具来实现这一点。这些工具通常不通过Web界面或命令行与云系统通信,而是使用应用程序编程接口,即API,它允许我们直接从脚本与云基础设施进行交互。
# 示例:通过云服务商API创建实例(概念性代码)
cloud_api.create_instance(image="ubuntu-20.04", instance_type="n1-standard-1")
在云服务商API的情况下,它们通常允许你直接从脚本或程序处理想要设置的配置,而无需调用单独的命令。这将编程能力与所有可用的云资源结合了起来。
云服务商提供的API让我们能够执行之前提到的所有任务,例如创建、修改和删除实例,以及部署这些实例之间如何通信的复杂配置。所有这些操作也可以通过Web界面或命令行完成,但从我们的程序中执行这些操作提供了额外的灵活性,这在自动化复杂设置时至关重要。
编排的应用场景
假设你想部署一个系统,它结合了在云服务商上运行的一些服务和在本地运行的一些服务。这被称为混合云设置,即只有部分服务在云中。这种设置在当今行业中非常普遍。
编排工具可以成为一个非常有用的工具,以确保本地服务和云服务都知道如何相互通信,并使用正确的设置进行配置。
回到我们之前讨论的网站示例,为了确保服务平稳运行,我们应该设置监控和告警。这使我们能够在用户甚至注意到问题之前就检测并纠正服务中的任何问题。这是基础设施的关键部分,但正确设置它可能需要相当长的时间。
通过使用编排工具,我们可以自动化配置需要设置的任何监控规则,例如我们想要查找的指标、希望何时收到告警等,并自动将这些规则应用到我们的完整部署中,无论服务在哪个数据中心运行。

总结
本节课我们一起学习了编排的核心概念。编排是自动化复杂系统配置与协调的过程,它通过API等工具实现,使得部署混合云架构、配置监控告警等任务变得可重复和高效。虽然这看起来是一项超级复杂的任务,但幸运的是,有工具可以让我们的生活更轻松。我们将在下一个视频中讨论这些工具。
129:基础设施即代码 🏗️

在本节课中,我们将学习如何将复杂云基础设施的管理视为代码,并探讨相关的工具与实践。
概述
上一节我们讨论了如何编排复杂的云设置。这包括处理具有不同工作负载的多个节点,管理混合部署的复杂性,或在多个数据中心之间修改部署。
在本节中,我们将深入探讨“基础设施即代码”的概念,了解其优势,并介绍实现这一目标的主流工具。
基础设施即代码的优势
在课程开始时,我们曾提到基础设施即代码。将基础设施以类似代码的格式存储,使我们能够创建可重复的基础设施。使用版本控制系统存储这些配置,意味着我们可以保留所做更改的历史记录,并轻松回滚错误。
这些原则同样适用于云基础设施。根据所使用的工具,存储方式可能略有不同,但我们仍将以类似代码的格式存储此配置,并使用版本控制来跟踪更改。
这使我们能够以小型团队管理大规模解决方案。通过查看配置,我们可以快速了解部署的样貌。我们可以尝试新事物,并在出现问题时回滚。我们可以查看更改历史,以了解为何进行特定更改等等。
云提供商专用工具
大多数云提供商都提供自己的工具来将资源作为代码管理。例如:
- Amazon 有 CloudFormation。
- Google 有 Cloud Deployment Manager。
- Microsoft 有 Azure Resource Manager。
- OpenStack 有 Heat Orchestration Templates。
这些工具特定于云提供商,这意味着迁移到不同提供商或将云部署与本地部署结合可能会变得复杂且繁琐。
通用编排工具:Terraform
编排领域一个日益流行的选项是 Terraform。与 Puppet 类似,Terraform 使用自己的领域特定语言,让我们可以指定我们希望云基础设施呈现的样子。
Terraform 的优点是它知道如何与许多不同的云提供商和自动化供应商进行交互。因此,你可以编写 Terraform 规则在一个云提供商上部署服务,然后使用非常相似的规则将服务部署到另一个云提供商。
Terraform 通过调用每个云提供商的 API 来实现这一点。这使你在迁移到不同云提供商时无需学习新的 API,让你可以专注于基础设施设计。
工作原理类比
在之前的视频中,我们看到 Puppet 规则可以指定计算机应安装特定软件包,然后本地的 Puppet 代理会分析计算机,并根据操作系统、特定的 Linux 发行版等决定使用哪种安装机制。
Terraform 也发生着类似的事情。定义资源(如要使用的虚拟机或容器)的规则将使用与云提供商相关的特定值,例如选择使用哪种机器类型或在哪个区域部署。
但大量的整体配置与提供商无关,如果我们决定将配置迁移到其他提供商,或者想要使用混合设置,这些配置可以被重用。
其他选项与节点管理
当然,Terraform 不是唯一的选择。Puppet 本身也附带了许多插件,可用于与不同的云提供商交互,以创建和修改所需的云基础设施。
最后,让我们花点时间讨论一下由编排工具管理的节点或实例的内容。在处理云中的节点时,基本上有两种选择:
以下是两种节点生命周期的管理方式:
- 长期运行节点:其内容需要定期更新。这类节点通常是预期不会消失的服务器,例如公司的内部邮件服务器或内部文档共享服务器。我们使用像 Puppet 这样的配置管理系统来管理这些实例,它可以在机器运行时部署任何必要的更改,使其保持最新状态。
- 短期运行节点:这类节点来去非常快。对于这些情况,在它们运行时应用更改的意义不大。相反,我们通常在实例启动时应用我们希望它们拥有的配置,并通过用新实例替换旧实例来部署任何未来的更改。
我们仍然可以使用 Puppet 进行初始设置,但不需要定期运行代理,只需在启动时运行即可。
总结

如果这一切听起来非常复杂,没关系。关于云编排有很多需要学习。一旦你尝试过,许多概念会变得更加清晰。
在本节课中,我们一起学习了基础设施即代码的核心思想,它通过将配置代码化、版本化来提升云基础设施管理的可重复性、可追溯性和灵活性。我们介绍了云提供商专用工具的局限性,并重点探讨了跨平台工具 Terraform 的工作原理及其优势。最后,我们还区分了针对长期运行和短期运行节点的不同管理策略。
接下来,我们提供了一些额外的信息供你深入探索,然后是一个小测验来帮助你巩固这些概念。
130:大规模管理云实例 🚀

在本节课中,我们将学习如何大规模地使用和管理云实例。我们将探讨在云环境中管理、扩展和自动化应用程序所面临的挑战,并介绍应对这些挑战的关键技术与策略。
上一节我们介绍了云服务的基础知识。本节中,我们将深入探讨在云中管理和运行应用程序的实际考量。
如果你在一家小型公司从事IT工作,可能主要将云用于在受保护的位置归档和存储记录与财务信息。在这种情况下,你或许可以直接使用云服务提供商提供的预打包应用程序来满足所有需求。
但对于规模更大或正在成长的公司而言,这种浅层次的云应用可能不够。你可能需要开始基于云提供商提供的平台和基础设施模型来开发自己的应用程序。
开发自有应用程序能为你的IT团队带来更多控制权和灵活性,允许你创建和定制应用程序以更好地满足公司的需求和目标,但这同时也带来了新的挑战。你和你的团队将需要:
以下是团队需要应对的几个关键挑战:
- 弄清楚不同的应用程序和组件如何协同工作以及独立工作。
- 确保每个工具和应用程序每次都能可靠运行。
- 知道如何在问题或兼容性问题出现时进行故障排除。
- 根据最新的工作流程,制定扩展或缩减服务的方案。
本课程旨在帮助你理解、规划并应对这些问题。
我们已经了解了可用的各种云服务,如何根据公司需求评估它们,以及在云中构建或适配软件时涉及的迁移和扩展问题。我们还初步了解了云基础设施中的虚拟机、编排和部署。
随着课程的推进,你将有机会学习如何管理和排查你已适配或创建的应用程序的问题,并实现其使用的自动化。
为了进行管理、故障排除和自动化,我们将探索以下核心概念:
以下是本课程将涵盖的几个核心领域:
- 负载均衡:将服务分发到多个实例上。
- 在不破坏应用程序或其相互关系的前提下进行更改、更新和调整,从而提高协作性和可靠性。
- 审视在云中运行软件应用程序时可能遇到的一些限制。

我们还需要管理你的期望并提供必要的背景知识,尤其是在事情未按预期发展时。有时软件甚至系统会出故障,这很正常。此处的关键是找出问题发生的原因,并确定最佳的修复方案。
为此,本课程还将为你提供可用于响应和排查云服务工作中可能出现问题的技巧和流程。之后,你甚至将有机会调试和修复常见问题。这些知识可以在你的整个职业生涯中应用。
最后,我们将通过介绍一些最佳实践来结束课程,例如使用监控系统衡量你的操作,并设置警报以便在计划外情况发生时自动收到通知。
也许你感到兴奋,甚至有点紧张。没有必要紧张,我们将全程与你同行。此外,讨论提示和论坛将允许你在此过程中与同伴分享想法、问题和成功经验。
感觉好点了吗?很好,因为我们还有很多内容要学习。让我们继续深入吧。
本节课中我们一起学习了大规模管理云实例的概述、挑战以及本课程将提供的解决方案。我们明确了开发自有云应用带来的控制力与随之而来的管理复杂性,并预告了后续将深入学习的负载均衡、无缝更新、故障排除和监控警报等关键主题。
131:在云端存储数据 🗄️

在本节课中,我们将学习云环境中不同的数据存储解决方案。我们将探讨块存储、对象存储和数据库服务等核心概念,并了解如何根据性能、成本和访问模式等因素选择合适的存储类型。
几乎所有IT系统都需要存储数据。有时数据量很大,有时则只是零散的信息。
云服务提供商为我们提供了多种存储选项。选择正确的数据存储解决方案取决于您正在构建的服务类型。您需要考虑一系列因素,例如:您希望存储多少数据、数据的类型、使用的地理位置、主要是写入还是读取数据、数据变化的频率以及您的预算。
这听起来需要考虑很多事情,但别担心,情况没那么复杂。我们将查看云提供商提供的一些最常见解决方案,以便让您更好地了解在云端选择存储方案时,何时该选择什么。
在选择云存储解决方案时,您可以选择传统的存储技术,如块存储,也可以选择更新的技术,如对象存储或Blob存储。让我们来看看这些术语分别是什么意思。
块存储 💾
正如我们在之前的视频中所见,当我们在云中创建一台虚拟机时,它会连接一个本地磁盘。这些本地磁盘就是块存储的一个例子。这种类型的存储非常类似于物理机器上使用物理硬盘的存储方式。云中的块存储行为几乎与硬盘完全相同。
虚拟机的操作系统将在块存储之上创建和管理一个文件系统,就像它是一个物理驱动器一样。不过,有一个很酷的区别:这些是虚拟磁盘,因此我们可以轻松地移动数据。例如,我们可以将磁盘上的信息迁移到不同位置,将相同的磁盘映像附加到其他机器,或者创建当前状态的快照。所有这些操作都无需物理设备在不同地点之间运输。
我们的块存储可以是持久性的,也可以是临时性的。持久性存储用于生命周期长、需要在重启和升级后保留数据的实例。相反,临时性存储用于仅临时存在、只需在运行时保留本地数据的实例。临时性存储非常适合服务在运行时需要创建但无需保留的临时文件。这种类型的存储在容器使用时尤其常见,但在处理只需在运行时存储数据的虚拟机时也很有用。
在典型的云设置中,每台虚拟机都连接有一个或多个磁盘。这些磁盘上的数据由操作系统管理,不易与其他虚拟机共享。如果您希望在多个实例之间共享数据,可能需要研究云提供商使用平台即服务模型提供的一些共享文件系统解决方案。使用这些解决方案时,可以通过NFS或CFS等网络文件系统协议访问数据。这允许您将许多不同的实例或容器连接到同一个文件系统,而无需编程。
当您管理需要访问文件的服务器时,块存储和共享文件系统工作得很好。但是,如果您尝试部署需要存储应用程序数据的云应用程序,则可能需要研究其他解决方案,例如对象存储。
对象存储(Blob存储) 📦
对象存储,也称为Blob存储,允许您在存储桶中放置和检索对象。这些对象只是通用文件,如照片或猫咪视频,它们被编码并以二进制数据形式存储在磁盘上。这些文件通常被称为Blob,源自“二进制大对象”。正如我们提到的,这些Blob存储在称为桶的位置。
您放入存储桶的所有内容都有一个唯一的名称。这里没有文件系统。您将一个对象以某个名称放入存储中。如果您想取回该对象,只需通过名称请求它。要与对象存储交互,您需要使用API或可以与您正在使用的特定对象存储交互的特殊工具。
数据库即服务 🗃️
除了上述方案,我们在之前的视频中还提到,大多数云提供商都提供数据库即服务。这些服务基本分为两种类型:SQL和NoSQL。
SQL数据库,也称为关系型数据库,使用传统的数据库格式和查询语言。数据存储在具有行和列的表中,这些表可以被索引,我们通过编写SQL查询来检索数据。许多现有应用程序已经使用这种模型,因此在将现有应用程序迁移到云端时通常会选择它。
NoSQL数据库在扩展性方面提供了许多优势。它们设计为分布在大量机器上,并且在检索结果时速度极快。但是,它们没有统一的查询语言,我们需要使用数据库提供的特定API。这意味着我们可能需要重写应用程序中访问数据库的部分。
选择存储类别与性能指标 ⚙️
在决定如何存储数据时,您还必须选择存储类别。云提供商通常以不同价格提供不同类别的存储。性能、可用性或数据访问频率等变量会影响月度价格。
存储解决方案的性能受多种因素影响,包括吞吐量、IOPS和延迟。让我们看看这些术语的含义:
- 吞吐量:指在给定时间内可以读取和写入的数据量。读取和写入的吞吐量可能大不相同。例如,读取吞吐量可能为每秒1吉字节,而写入吞吐量可能为每秒100兆字节。
- IOPS:即每秒输入/输出操作数。它衡量在一秒钟内可以执行多少次读取或写入操作,无论访问的数据量是多少。每个读取或写入操作都有一些开销,因此在给定秒内可以执行的操作数量是有限的。
- 延迟:指完成一次读取或写入操作所需的时间。这将考虑IOPS、吞吐量以及特定服务细节的影响。读取延迟有时报告为发出读取请求后存储系统开始传送数据所需的时间,也称为“首字节时间”。而写入延迟通常衡量写入操作完成所需的时间。
选择要使用的存储类别时,您可能会遇到热和冷等术语。热数据被频繁访问,存储在热存储中;而冷数据不常被访问,存储在冷存储中。这两种存储类型具有不同的性能特征。例如,热存储后端通常使用固态硬盘构建,其速度通常比传统的旋转硬盘快。

那么,如何在两者之间做出选择呢?假设您希望将服务产生的所有数据保留五年,但您不期望会定期访问超过一年的数据。您可能会选择将最近一年的数据保留在热存储中,以便快速访问。一年后,您可以将数据移动到冷存储中,在那里您仍然可以访问它,但访问速度会变慢,并且成本可能更高。
关于云存储还有很多内容可以探讨,这确实是一个热门话题,但我们不会在此深入更多细节。如果您想了解更多信息,我们将在下一篇阅读材料中提供更多信息的链接。
接下来,我们将探讨另一个我们已经接触过的云规模特性:为同一服务使用多台机器。
132:负载均衡 🚦

在本节课中,我们将要学习负载均衡的概念、常见方法及其在服务部署中的重要性。负载均衡是确保服务高可用性、可扩展性和性能的关键技术。
在之前的视频中,我们看到了多种原因,说明为何需要多台机器或容器来运行我们的服务。例如,我们可能希望水平扩展服务以处理更多工作,将实例地理分布以更接近用户,或者拥有备份实例以确保在部分实例故障时服务仍能运行。无论出于何种原因,我们使用编排工具和技术来确保实例是可重复创建的。
一旦我们设置了复制的机器,就需要将请求分配到各个实例上。我们之前提到,这正是负载均衡发挥作用的地方。
接下来,让我们更详细地看看可以使用的不同负载均衡方法。
常见的负载均衡技术
以下是几种常见的负载均衡方法。
轮询DNS
轮询是一种非常常见的任务分配方法。想象一下你在派对上分发点心。首先,你确保每位朋友都得到一块饼干。然后你给每个人第二份,依此类推,直到所有点心分发完毕或客人们表示已经吃饱了。这就是吃完所有饼干的轮询方法。
现在,如果我们需要将像 Myservice.example.com 这样的URL转换为IP地址,我们会使用DNS协议,即域名系统。在最简单的配置中,URL总是被转换为完全相同的IP地址。但是,当我们配置DNS使用轮询时,它会为每个请求解析的客户端提供一组IP地址,但顺序不同。客户端随后会从列表中选取一个地址来尝试连接服务。如果尝试失败,客户端会跳转到列表中的另一个地址。
这种负载均衡方法设置起来非常简单,你只需要确保DNS服务器中配置了池中所有机器的IP地址。但它也有一些局限性。首先,你无法控制客户端选择哪个地址,即使某台服务器过载,你也不能阻止客户端连接到它。此外,DNS记录会被客户端和其他服务器缓存,因此如果你需要更改实例的地址列表,必须等待所有客户端缓存的DNS记录过期。
专用负载均衡器
肯定有更好的方法,对吧?确实,为了更精确地控制负载分配并实现更快速的变更,我们可以设置一台服务器作为专用负载均衡器。
这是一台在客户端和服务器之间充当代理的机器。它接收请求,并根据我们提供的规则,将它们定向到选定的后端服务器。负载均衡器可以非常简单,也可以非常复杂,这取决于服务的需求。
例如,如果你的服务需要跟踪用户迄今为止的操作,在这种情况下,你会希望负载均衡器使用粘性会话。使用粘性会话意味着来自同一客户端的请求总是被发送到同一个后端服务器。这对于需要此功能的服务非常有用,但在迁移或维护服务时也可能带来麻烦。因此,只有在真正需要时才应使用它,否则你可能会陷入非常棘手的境地。
负载均衡器的另一个很酷的功能是,你可以配置它们来检查后端服务器的健康状况。通常,我们通过向服务器发送一个简单的查询并检查回复是否符合预期回复来实现这一点。如果后端服务器不健康,负载均衡器将停止向其发送新请求,以保持池中只有健康的服务器。
正如我们已经多次提到的,云基础设施的一个很酷的特性是,我们可以多么轻松地从提供服务的服务器池中添加或移除机器。如果我们有一个负载均衡器来控制机器的负载,那么向池中添加新机器就像创建实例然后通知负载均衡器现在可以将流量路由到它一样简单。我们可以手动创建和添加实例,或者当我们的服务负载很重时,我们可以让自动扩展功能为我们完成这项工作。
地理负载均衡与CDN
现在,想象一下你已经用负载均衡器构建了你的服务,并且正在接收来自世界各地的请求。你如何确保客户端连接到离他们最近的服务器呢?
你可以使用GDNS和GEOIP。这些是DNS配置,可以将你的客户端定向到最近的地理负载均衡器。用于路由流量的机制依赖于DNS服务器如何响应请求。例如,对于托管在北美的机器,北美的DNS服务器可能被配置为返回北美地区的IP地址。自行设置这可能很棘手,但大多数云提供商都将其作为其服务的一部分提供,这使得拥有地理分布式服务变得更加容易。
让我们更进一步。有一些提供商专门致力于将你的服务内容尽可能靠近用户。这些就是内容分发网络,即CDN。它们由物理主机组成的网络构成,这些主机地理位置尽可能靠近最终用户。这意味着CDN服务器通常与用户的互联网服务提供商位于同一个数据中心。
CDN的工作原理是将内容缓存到离用户非常近的地方。当用户请求,比如说一个猫咪视频时,它被存储在最近的CDN服务器上。这样,当同一地区的第二个用户请求同一个猫咪视频时,它已经缓存在一个相当近的服务器上,并且可以非常快速地下载。因为没有人应该等待他们的猫咪视频加载。
总结
本节课中,我们一起学习了负载均衡的核心概念。我们探讨了使用多实例的原因,并详细介绍了轮询DNS和专用负载均衡器这两种主要方法。我们还了解了负载均衡器的健康检查、自动扩展集成,以及通过地理DNS和内容分发网络实现地理分布和性能优化的高级策略。负载均衡是构建可靠、可扩展和高性能云服务架构的基石。

接下来,我们将讨论处理云服务的另一个方面:如何安全地向我们的云服务部署变更。
133:变更管理 🛠️

在本节课中,我们将学习如何通过变更管理,在保持云服务稳定运行的同时,安全地进行更新与改进。我们将探讨测试、持续集成与部署、多环境策略以及A/B测试等核心实践。

你已经取得了长足的进步,现在知道了如何让服务在云端运行。接下来,让我们讨论如何保持它的运行。大多数情况下,服务停止工作是因为某些东西发生了变更。
如果我们希望云服务保持稳定,可能会倾向于完全避免变更。但变更是云环境中的常态。为了修复错误和改进服务功能,我们必须进行变更,但我们可以以受控且安全的方式进行。这就是变更管理。它让我们能够在服务持续运行的同时不断创新。
确保变更安全:测试先行 🧪
提高变更安全性的第一步,是确保它们经过充分测试。这意味着运行单元测试和集成测试,并在每次变更时都运行这些测试。
在之前的课程中,我们简要提到了持续集成。这里做一个回顾:持续集成系统会在每次有变更时构建和测试我们的代码。理想情况下,CI系统甚至会对正在审查的变更运行测试。这样,你可以在问题被合并到主分支之前就发现它们。
你可以使用常见的开源CI系统,如 Jenkins。如果你使用Github,可以利用其 Travis CI 集成。许多云提供商也提供持续集成即服务。
一旦变更被提交,CI系统将构建并测试生成的代码。
自动化部署:持续部署 🚀
现在,你可以使用持续部署来自动部署构建结果或构建产物。持续部署允许你通过规则来控制部署。
例如,我们通常配置CD系统,使其仅在所有测试都成功通过时才部署新的构建。此外,我们还可以根据某些规则配置CD,将构建推送到不同的环境。
这是什么意思呢?在之前的视频中我们提到,在推送Puppet变更时,我们应该有一个独立于生产环境的测试环境。将它们分开,可以让我们在变更影响用户之前验证其正确性。
在这里,环境指的是运行服务所需的一切,包括用于运行服务的机器和网络、部署的代码、配置管理、应用程序配置以及客户数据。
- 生产环境,通常简称为 prod,是真实的环境,是用户看到并与之交互的环境。因此,我们必须保护、关爱并培育prod。
- 测试环境需要与prod足够相似,以便我们用它来检查变更是否正常工作。
你可以将CI系统配置为将新变更推送到测试环境。然后,你可以在那里检查服务是否仍能正常工作,再手动告知部署系统将这些相同的变更推送到生产环境。
多环境策略 📊
如果服务很复杂,并且有许多不同的开发人员对其进行更改,你可能会设置额外的环境,让开发人员在发布变更前在不同阶段测试他们的更改。
例如,你可以让CD系统将所有新变更推送到开发环境。然后,设置一个名为预生产环境的独立环境,它只会在批准后接收特定的变更。只有在经过彻底测试后,这些变更才会被推送到prod。
假设你正试图将服务效率提高20%,但不确定所做的更改是否可能导致系统部分崩溃。你会希望先将其部署到某个测试或开发环境中,以确保其正常工作,然后再将其发布到Prod。
请记住,这些环境需要尽可能与prod相似。它们应该以相同的方式构建和部署。虽然我们不希望它们总是出问题,但某些变更导致开发环境甚至预生产环境出故障是正常的。我们庆幸能及早发现问题,从而避免它们破坏prod。
生产环境实验:A/B测试 🔬
有时你可能想试验一项新的服务功能。你已经测试了代码,知道它能工作,但你想知道它是否会对你的用户有效。当你有一些想要在真实客户的生产环境中测试的东西时,你可以使用A/B测试进行实验。
在A/B测试中,一部分请求使用一组代码和配置来服务,另一部分请求则使用另一组不同的代码和配置来服务。这是负载均衡器和实例组可以发挥作用的另一个地方。
你可以部署一个采用A配置的实例组,以及第二个采用B配置的实例组。然后,通过更改负载均衡器的配置,你可以将不同比例的入站请求定向到这两种配置。


如果你的A配置是当前的生产配置,而B配置是实验性的,你可能希望开始时只将1%的请求定向到B。然后,随着你检查B配置是否比A表现更好,可以慢慢提高这个比例。
请注意,确保你拥有基本的监控,以便轻松判断A或B的表现是更好还是更差。如果很难识别负责服务A请求或B请求的后端,那么A/B测试的许多价值就会在A/B调试中丧失。
应对故障:从失败中学习 💡
如果我们采取的所有预防措施都不够,并且我们在生产中破坏了某些东西,该怎么办?记住我们在之前课程中关于事后分析的讨论:我们从失败中学习,并将新知识融入我们的变更管理中。
问问自己:我必须做什么才能发现问题?我能否让我的某个变更管理系统在未来寻找类似的问题?我能否向我的单元测试、CI/CD系统或服务健康检查中添加测试或规则,以防止未来发生此类故障?
所以请记住,如果出现问题,给自己一个喘息的机会。在IT领域,有时这些事情会发生,无论你多么小心。随着你使用和完善你的变更管理系统和技能,你将获得更快、更安全地对服务进行更改的信心。
总结 📝
本节课中,我们一起学习了云服务变更管理的核心实践。我们了解到,通过充分的测试、持续集成与部署的自动化流程、建立多环境策略(如开发、测试、预生产和生产环境)以及利用A/B测试进行生产环境实验,可以在持续创新的同时保障服务的稳定性。最后,我们认识到当故障发生时,应通过事后分析从中学习,并不断完善变更管理流程,从而建立起安全、快速进行变更的信心。
134:了解云端部署的限制 🚧

在本节课中,我们将要学习在云端部署服务时可能遇到的各种限制和挑战。了解这些限制有助于我们设计出更健壮、更具成本效益的云服务。
上一节我们讨论了如何让服务在云端平稳运行。本节中,我们来看看部署过程中可能遇到的具体问题。
保持应用部署方式在心
编写在云端运行的软件时,必须时刻牢记应用程序的部署方式。创建的软件需要具备容错能力,并能处理意外事件。实例池中的机器可能会根据需要被添加或移除。如果单台机器崩溃,服务需要能够平稳过渡,不引发问题。
并非所有问题都会导致崩溃。有时我们会遇到配额或限制。
理解配额与速率限制

以下是几种常见的限制类型:
- 操作频率限制:例如,在使用Blob存储时,可能限制在给定秒数内对同一Blob的写入操作不超过1000次。如果服务常规执行大量此类操作,可能会被这些限制阻止。此时,需要评估是否能改变操作方式,例如将所有调用分组为一个批次,或者切换到不同的服务。
- API调用速率限制:云服务中使用的一些API调用可能成本高昂。因此,大多数云提供商会强制执行速率限制,以防止单个服务使整个系统过载。例如,对于一个昂贵的API调用,速率限制可能为每秒一次。
- 资源利用配额:这类配额限制了你可配置的特定资源总量。
配额的作用与管理
这些配额旨在帮助你避免无意中分配超出预期的资源。
想象一下,你配置了服务使用自动扩展,而它突然收到巨大的流量高峰。这可能意味着要部署大量新实例,从而产生高昂费用。
对于其中一些限制,如果你需要额外容量,可以向云提供商申请增加配额。你也可以设置一个比默认值更小的配额来避免超支。这在预算紧张的情况下运行服务是一个好主意。
如果服务常规执行昂贵的操作,应确保了解所选解决方案的限制。许多平台即服务和基础设施即服务产品的成本与其使用量直接相关,并且也有使用配额。如果构建的服务突然变得非常流行,可能会耗尽配额或预算。
通过对自动扩展系统施加配额,系统将增长以满足用户需求,直到达到配置的限制。此处的诀窍是围绕此类行为建立良好的监控和警报。如果系统配额耗尽,但对“小狗视频”的需求却在增加,系统可能会出现性能下降甚至中断等问题。因此,你希望在此情况发生时立即收到通知,以便决定是否增加配额。
处理服务依赖
最后,我们来谈谈依赖关系。当你的服务依赖于平台即服务产品,如托管数据库或CI/CD系统时,你就将该服务的维护和升级责任移交给了云提供商。这很好,需要担心和维护的事情更少,但也意味着你并不总能选择所使用的软件版本。
你可能会发现自己处于升级周期的任一侧:要么希望停留在对你运行良好的版本,要么希望云提供商加快升级以解决影响你服务的错误。
云提供商有强烈的动机保持其服务软件相对最新。保持软件即服务解决方案处于最新状态,可确保客户不易受到安全漏洞的影响,错误能得到及时修复,并且新功能能尽早发布。同时,云提供商必须谨慎行事并测试变更,以将服务中断降至最低。
他们会主动沟通你所使用服务的变更。在某些情况下,云提供商可能会让你提前访问这些服务的早期版本。例如,你可以为服务设置一个测试环境,该环境使用给定软件即服务解决方案的测试版或预发布版本,让你在其影响生产环境之前进行测试。
总结
本节课中,我们一起学习了云端部署的关键限制,包括操作频率、API速率和资源配额。我们探讨了配额的管理策略,以及如何处理对PaaS产品的依赖和版本控制。希望你现在开始理解,为了从云端部署软件中获得最大收益,需要做出哪些权衡。
135:什么是容器?📦

在本节课中,我们将要学习容器的基本概念。容器是一种将应用程序与其配置和依赖项打包在一起的技术。通过本课,你将理解容器如何工作,以及它们为何在现代软件开发中如此重要。

容器是什么?
在模块1中,我们简要提到了容器,将其定义为将应用程序与其配置和依赖项打包在一起的应用。

那么,这具体意味着什么?假设你正在购买制作世界著名烤土豆的食材。你买了牛奶、酸奶油、辣椒、甜椒、香料和四个大土豆。
但你不会分多次单独将它们搬到车上,对吗?当然不会。
杂货店会将它们放入一个容器中,比如袋子。你的食材被打包在一起,以便你可以将它们运回家中,制作你的美食杰作。
容器的用途
容器不仅仅用于打包。它们还用于共享你正在开发的应用程序、将应用程序发布到服务器进行审查、测试同一应用程序的不同实例,以及分别处理复杂系统架构的关键部分。
让我们进一步扩展我们的例子。如果你想在朋友家或你要去的派对上制作你的烤土豆怎么办?这不是问题,因为你需要的所有东西都在从商店得到的容器里。这个食谱在任何厨房都适用。在Python中也是如此。
容器允许应用程序以相同的方式运行,无论你在什么环境中运行它们。
假设你正在与编程同行分享你的应用程序。你不想让他们下载你用来创建应用程序的所有Python库和依赖项,因此你将应用程序的虚拟环境(VN)打包到一个容器中。然后你将其发送给他们,将其导出为一个requirements.txt文件(这是一个一一列出所有必需库和依赖项的清单)以及一个Dockerfile。我们将在后面的视频中详细讨论Docker。

容器、虚拟机与虚拟环境
容器填补了虚拟机(VMs)和Python虚拟环境之间的空白。可以将容器视为虚拟机和Python虚拟环境之间的用例。
使用容器基本上就像打包你电脑的一部分并交给别人一样。
编写软件时的一个常见问题是,不同的服务器或同行计算机的设置方式可能与你的不完全相同。因此,即使一切在你的电脑上运行完美,但在他们的电脑上可能无法正确运行,甚至根本无法运行。通过容器,你可以虚拟地打包所有使软件在你电脑上运行所需的东西,并给他们提供按预期使用软件所需的一切。
这本质上是允许他们使用你系统的快照来运行应用程序。你也可以在容器内运行一个小型数据库,并用另一个程序连接到该数据库。
容器使程序员能够测试客户端-服务器应用程序,而无需使用另一台计算机或启动昂贵的云服务。
作为程序员,你可以编写自己的容器,或调整现有的容器或容器模板以满足项目需求。
总结
在本节课中,我们一起学习了容器的核心概念。容器允许你组装一个包含应用程序及其配置和依赖项的包,这个包位于一个虚拟环境中,本质上是你用来创建它的计算机的快照(包括使用的Python版本、库、配置等)。因此,它可以运行在任何其他地方,包括互联网上,而不受特定计算机或服务器操作规格的限制。
这就是容器赋予你的能力。当你将它们与Docker结合使用时,这种能力将变得更加强大,我们将在下一课中介绍Docker。
136:Docker Web应用 🐳

在本节课中,我们将学习Docker这一行业标准的容器工具,了解它如何简化将应用程序打包为容器的过程,以及如何创建和共享Docker Web应用。
现在你已经了解了容器是什么以及它们的用途,你可能会想知道接下来该怎么做。接下来,我们将介绍Docker。
Docker是一个行业标准的容器工具。它是一个开源平台,可用于构建、部署、运行、更新和管理容器。换句话说,它简化了将应用程序转换为容器的过程。
回顾一下,要共享或发布打包在容器中的应用程序,你需要发送两个文件:一个requirements.txt文件和一个Docker文件(有时也称为Docker镜像,这两个名称可以互换使用)。Docker本身是一个在命令行上运行的应用程序。它读取Docker文件,然后Docker文件再读取requirements.txt文件。最后,Docker启动Python运行时引擎及其所有依赖项,并独立运行Python脚本。这就像将你计算机的一部分上传到另一台计算机或云端的服务器上。
为了澄清,容器通常是通过互联网共享,而不是在人与人之间传递。这被称为Docker Web应用。对于Docker Web应用,你需要将容器上传到服务器,以便人们可以远程连接到它。在Docker文件中,你需要指定它是一个支持Web的应用程序。一个Docker Web应用的例子是,你在计算机上运行一个像数据库这样的Docker镜像,并且它也允许来自其他计算机的流量。
当你共享一个带有Docker文件的容器时,重要的是要与共享对象(尤其是如果他们不熟悉Docker)设定好预期。第一次运行Docker文件时,它需要下载并安装用于创建该应用程序的Python版本的运行时文件,但这只发生一次。如果你使用相同的参数共享其他容器,它将直接运行Python脚本。
由于Docker是开源的,你可以免费使用它,没有任何许可要求。但Docker也有一个提供企业和订阅计划的付费版本,它可以将你的Docker镜像保存在私有云上,并允许你管理所有的Docker镜像。不过,购买计划只是其中一个选项。Google Cloud平台内置了Docker支持,因此你可以直接将镜像上传到那里。此外,许多网络存储设备和云备份服务器(如Buffalo和Western Digital)也包含对Docker的支持。
尽管Docker是一个相对较新的工具,但它实际上已成为创建容器的代名词。这主要是因为它比其他创建容器的方法(如使用虚拟机或设置像VM或Conda这样的虚拟环境)更简单。
如果你还没有使用Docker,不妨试一试。让我们快速回顾一下,以防你忘记了为什么应该使用它。
以下是Docker的核心优势:

- Docker是一个行业标准工具,用于将应用程序转换为容器。
- 它允许你轻松地与同行和评审者发布和共享你的容器。
- 它独立运行你的Python脚本,这就像将你的计算机上传到另一个位置。
- 别忘了,Docker无需许可费用,并且预装在许多服务器和网络存储设备上。
请始终记住,编程是一项协作活动,而Docker让你更容易进行协作。
本节课只是使用Docker与容器的一个入门介绍。在接下来的几节阅读材料和视频中,你将有机会进行更深入的探索。希望你和我一样感到兴奋。
137:容器与制品注册表 📦

在本节课中,我们将学习容器和制品注册表的基本概念、区别以及它们在软件开发中的作用。我们还将介绍几种主流的云服务提供商提供的注册表服务。
概述
容器技术为程序员在协作、共享乃至获取Python应用反馈方面提供了显著优势。本节我们将探讨如何组织和管理容器,特别是容器注册表与制品注册表。
核心概念解析
上一节我们介绍了容器的优势,本节中我们来看看容器与制品注册表的具体定义与区别。
容器注册表
容器注册表是存储容器镜像的场所,同时包含编程接口路径和访问控制规则(例如针对Docker Web应用)。它们被组织起来以实现高效访问。
容器仓库
容器仓库是一种容器注册表,它不仅存储容器镜像,还管理这些镜像及其关联的制品。
例如,假设你有一台运行Windows 11的新PC。微软的注册表包含了与Windows 11操作系统相关的所有制品。但微软的注册表还负责管理镜像和制品,以便向你和所有其他Windows 11 PC用户推送更新。因此,更准确地说,这个注册表应被称为仓库。
制品
制品是应用程序容器中的依赖项之一。它是软件开发过程中的副产品,即在编程过程中产生的项目,例如Docker镜像或代码的编译版本。制品使你能够分发代码,供他人使用,正如我们提到的Windows 11更新。
容器和制品通常拥有不同的注册表,这是合理的。一个容器可以包含多个制品,但一个制品具有特定的目的或用途。它通常存储在注册表中,以便在需要时被访问和使用。
主流云容器注册表

以下是三种你应该了解的云容器注册表:
- Azure容器注册表 (ACR):一项Microsoft Azure服务,允许你存储、管理和部署容器镜像。
- AWS弹性容器注册表 (ECR):一项Amazon Web Services注册表服务,允许你在AWS上存储、管理和部署容器镜像。
- Google容器注册表 (GCR):一项由Google云平台提供的私有容器注册表服务,允许你使用GCP基础设施存储、管理和部署容器镜像。
这三种服务都提供诸如身份验证、访问控制和镜像地理复制等功能。它们也都提供用于制品存储的注册表。
如何选择注册表
如果你为公司编程,你将使用公司订阅的注册表。如果是个人使用,请花时间研究这些提供商,以确定最适合你的选项。
以下是选择时需要考虑的要点:

- 权衡每种服务的优点和缺点。
- 在做出决定前,仔细研究不同服务的选项,同时关注其功能和安全性协议。
- 进行一些研究,并在有机会时尝试使用它们。


总结


本节课中我们一起学习了以下核心概念:
- 注册表:存储和组织容器或制品的场所。
- 仓库:允许你管理容器或制品的注册表或注册表集合。
- 制品:存储在注册表中、供需要时访问和使用的开发副产品。
最后再次提醒,在决定使用前请仔细考虑注册表服务。注册表在DevOps工作流中管理容器和制品尤为重要,我们将在课程后续部分进一步讨论。届时我们将继续这个话题。
138:Kubernetes 概览 🐳

在本节课中,我们将要学习一个名为 Kubernetes 的强大工具,它用于管理和编排容器。我们将了解它如何解决资源分配不均的问题,以及它与 Docker 的关系。
你是否注意到,有些人就是不喜欢分享?在本节中,我们将探讨如何利用资源管理并进行扩展,从而降低那些不愿分享的人独占所有资源、损害他人利益的可能性。
假设你将一个 Docker 镜像部署在云服务器上,以便与 20 位用户共享你的应用程序。服务器端实际上没有任何机制能防止少数极度热情的用户垄断所有服务器带宽。结果就是,部分用户会得到缓慢、不稳定的服务。
为了预见并解决这个问题,许多程序员会部署同一应用程序的多个 Docker 镜像,以确保每个人都能获得最佳、最快速的服务。但是,当需要更新应用程序、修复漏洞或添加新功能时,你做出的每一项更改也必须同步到这云端的 20 个实例中。
Kubernetes 正是为了解决此类问题而生的工具。Kubernetes,缩写为 K8s,是一个开源平台,它赋予程序员编排或管理容器的能力。
你可以把它想象成一群鱼,或者更贴切地说,一锅鲸鱼。在 K8s 中,你使用和命名的容器在集群中被部署为 Pod。
一个 Pod 是一个逻辑组,包含一个或多个容器,它们被调度并在单个工作节点(即 Kubernetes 集群中的一个主机)上一起运行。同一个 Pod 中的容器共享网络命名空间、相同的 IP 地址和资源,因此它们可以相互通信。这使得部署非常高效。
你可以对 Kubernetes Pod 做很多事情:
以下是 Pod 的核心操作:
- 扩展(Scale):这意味着为你的 Pod 添加更多副本,就像我们在贪婪用户例子中需要的 20 个实例一样。
- 更新(Update):更好的是,你可以通过更新你的 Pod,同时更新所有这些副本。
- 回滚(Rollback):如果你发现了一个漏洞,需要让用户回退到更新前的版本,你可以回滚 Pod 及其副本。这允许你修复漏洞并发布新的更新。
- 监控与检查:Kubernetes 还允许你列出所有 Pod 和副本,并检查描述信息和日志,以寻找趋势或基于使用情况发现信息。
- 对外暴露(Expose):或者,你可以将你的部署暴露给外部世界,这在 Docker 提供的访问权限之上提供了第二层访问控制。
这第二层权限允许你实现自动化的负载均衡。一旦你暴露了你的部署,就可以使用一个特殊的服务器与所有客户端通信,将每个连接实时重定向到 Pod 中最空闲的 Docker 镜像,从而确保每个人都能获得最佳访问。
为了更好地理解 Docker 和 Kubernetes 之间的关系,我们可以打一个比方:
想象 Docker 是一个运输集装箱。它让你将每个应用程序及其依赖项打包在一个独立的容器“板条箱”里,就像运输集装箱让你打包货物以便运输一样。
在这个例子中,Kubernetes 就是港口,它编排着集装箱和包裹的处理方式,并将它们引导到正确的地方。
由此可见,Kubernetes 使你能够在云端自动化容器部署,以最优方式实现你的应用策略目标。
但请注意,强大的能力伴随着一定的代价。它的学习曲线比较陡峭。
因此,除非你需要所有这些强大的功能和效率,否则你可能希望使用一种更简单的部署方法。这是你和你的 IT 部门需要做出的决定。
在本模块中,我们将为你提供大量关于 Kubernetes 的信息,所以在继续深入学习之前,让我们快速回顾一下基础知识。
以下是 Kubernetes 的核心要点:
- Kubernetes 是一个开源平台,赋予程序员管理容器的能力。
- Kubernetes Pod 是一个或多个容器,它们被调度并在单个工作节点或主机上一起运行。
- Pod 可以被扩展、回滚,并可以深入挖掘其日志信息以寻找趋势。
- Kubernetes 在 Docker 提供的功能之上,还提供了访问权限管理。

Kubernetes 对于合适的应用场景来说是一个重要工具。它使得与其他团队和用户共享数据、资源和应用程序变得容易。这有助于提高协作、效率和生产力。下次我们将更详细地讨论这些 Pod。
139:GCP 上的 Kubernetes 🚀

在本节课中,我们将探讨如何在谷歌云平台(GCP)上使用 Kubernetes,特别是通过 Google Kubernetes Engine(GKE)来简化和增强容器管理。
概述
Kubernetes 是一个强大的工具,用于组织、共享和管理容器。它允许程序员进行扩展、复制、推送更新、回滚版本以及处理版本控制等操作。然而,直接在本地或基础云服务上运行 Kubernetes 可能需要大量的手动配置和管理工作。本节我们将了解,通过 GCP 的 GKE 服务,如何让 Kubernetes 的使用变得“更好”。
Kubernetes 的部署选择
上一节我们介绍了 Kubernetes 的基本功能,本节中我们来看看它的不同部署方式。
作为一个开源程序,Kubernetes 非常灵活。程序员可以选择多种方式来运行它:
- 在自己的电脑上运行。
- 直接在 Google Compute Engine(GCE)上运行。
- 在 GCP 上使用 Google Kubernetes Engine(GKE) 运行。GKE 是一项托管服务,专门致力于改善用户使用 Kubernetes 的体验。
你也可以在其他云服务提供商上运行 Kubernetes,它们大多提供类似 GKE 的引擎来增强 Kubernetes 的功能。
GKE 的优势
以下是使用 GKE 在谷歌云上运行 Kubernetes 的主要优势:
- 基于 Web 的界面:GKE 提供了一个仪表盘,让程序员可以即时查看他们的容器、项目及其管理状态。大多数开源工具不提供这样的界面,用户需要自己创建或使用第三方界面。
- 集群管理自动化:GKE 提供自动化的集群配置、扩展和升级功能。
- 简化部署:使用 GKE,你可以简单地复制粘贴 Docker 文件,而无需通过终端上传。
- 自动安全维护:GKE 会自动处理 Kubernetes 软件的安全补丁和更新。如果没有 GKE,这些任务将落在程序员或其 DevOps 团队身上。
这意味着,即使是没有专职 DevOps 团队的系统开发人员,也可以使用 GKE 作为 DevOps 解决方案,通过单一的 Web 门户来管理其基础设施。
GKE 的角色与价值
我们可以将 GKE 视为一个额外的管理层,它让像你这样的 Python 程序员能更容易、更好地利用 Kubernetes 的能力。
随着你在 Python 和其他编程语言上经验日益丰富,GKE 可以通过自动化常见任务和创建高级部署触发器,帮助你更高效地使用 Kubernetes。
选择前的考量
但在做出选择前,请务必进行研究。
比较 GKE 及其他类似引擎的优缺点,仔细评估它们的成本与功能是否符合你或你公司的目标,然后再选择像 GKE 这样的服务。

总结
本节课中我们一起学习了 Kubernetes 在 GCP 上的应用。
请记住,Kubernetes 是一个用于优化 Docker 容器和项目部署管理的工具。😊
在谷歌云上通过 GKE 使用 Kubernetes,为你提供了一个基于 Web 的界面或仪表盘,让你可以更高效地查看和管理容器与项目。
GKE 并没有解锁 Kubernetes 的任何隐藏功能,但它确实让使用变得更简单,并自动处理了安全和更新问题。
回到我们最初的问题:用云还是不用云?这个选择权完全在你手中。
140:使用配置管理实现自动化 🚀

在本节课中,我们将学习如何通过自动化技术高效管理大量计算机。无论团队规模大小,自动化都能显著提升工作效率。我们将介绍规模化管理的核心概念,并初步了解配置管理工具Puppet。
概述
无论团队规模如何,或需要管理的计算机数量有多少,掌握自动化技术都能让你更高效地完成工作。以我所在的Gmail站点可靠性工程团队为例,团队规模虽小,但服务体量巨大。若不借助自动化工具扩展我们的工作能力,将无法帮助Gmail实现其可靠性目标。虽然你可能目前尚未管理如此大规模的服务,但根据需求使用恰当的自动化技术必将使你受益。
能够自动化安装新软件、配置新工作站或设置新服务器,即使你是IT部门的唯一成员,也能带来巨大改变。在接下来的视频中,我们将首先探讨一些重要的自动化概念,例如“规模化”的含义、如何使用配置管理来维护计算机群,以及如何通过“基础设施即代码”使所有人受益。
规模化与自动化
上一节我们概述了自动化的价值,本节中我们来看看规模化管理的核心概念。这些概念是让我们能够管理日益增长的设备数量,而无需相应扩大团队规模的基础。
- 规模化(Scale):指管理不断增长的计算机或服务数量的能力。
- 配置管理(Configuration Management):指使用工具自动维护计算机(如软件安装、系统设置)的一致状态。
- 基础设施即代码(Infrastructure as Code, IaC):指使用代码文件(而非手动操作)来定义和管理基础设施(如服务器、网络),使其可版本化、可重复、可自动化。
初识Puppet
了解了基础概念后,本节我们将初步认识本课程将重点教学的配置管理工具——Puppet。我们将查看多个不同示例,了解Puppet规则的样子,并学习其底层概念,了解如何让它为你完成繁重的工作。
以下是Puppet一个简单的代码示例,用于确保nginx软件包被安装且服务处于运行状态:
package { 'nginx':
ensure => installed,
}
service { 'nginx':
ensure => running,
enable => true,
require => Package['nginx'],
}
模块目标与技能收获
本模块中探讨的概念将帮助你在更大规模自动化方面迈出第一步。掌握如何自动管理设备配置,能让你的团队在人员不变的情况下处理更多工作。同时,由于所有枯燥的任务都能实现自动化,这也腾出了时间去做更有趣的事情。
到本模块结束时,你将掌握修复现有自动化中存在的Bug的技能。这是个好消息,因为你将使用我们提供的代码来实践这一技能。这安排得很巧妙,就像我们计划好的一样。
总结
本节课我们一起学习了规模化IT管理的重要性,认识了配置管理和基础设施即代码的核心概念,并初步接触了自动化工具Puppet。掌握这些知识和技能,是迈向高效、可靠IT运维的关键一步。
141:什么是规模 📈

在本节课中,我们将学习“规模”在IT自动化中的含义,以及如何通过自动化实现高效扩展。
概述
在IT领域,谈论“规模”通常指的是系统或流程处理更大工作量的能力。一个具备良好扩展性的系统,能在工作量增加时,通过提升容量来应对,而无需线性增加人力或资源投入。本节课将探讨规模的概念、其重要性,以及如何通过自动化工具实现高效扩展。
什么是规模?
当我们讨论“规模”时,指的是使我们的工作能够扩展的能力。这意味着我们可以用相同的努力,持续实现更大的影响。
当一个系统具备良好的扩展性时,其需要处理的工作量增加,可以通过增加容量来适应。
例如,如果你公司提供的网络应用是可扩展的,那么它可以通过添加更多服务器来处理用户数量的增长,从而服务更多请求。
简而言之,一个可扩展的系统是灵活的系统。向服务网站的服务器池中添加更多计算机,可能是一个非常简单的操作,也可能非常困难,这取决于你的基础设施是如何设置的。
评估当前设置的扩展性
为了评估你当前设置的扩展性,可以问自己以下问题:
以下是几个关键的自查问题:
- 添加更多服务器是否会增加服务的容量?
- 新服务器是如何准备、安装和配置的?
- 你能多快设置好新计算机并使其投入使用?
- 你能否用现有的IT团队部署100台服务器,还是需要雇佣更多人来更快完成?
- 所有部署的服务器是否都以完全相同的方式配置?
规模不仅限于网站服务
当然,规模不仅仅关乎提供内容的网站。
如果你的公司正在快速招聘大量新员工,你将需要一个能够按需扩展的入职流程。
随着你不断向网络中添加新计算机,你需要确保你的系统管理流程能够扩展到公司日益增长的需求。
这可能包括应用最新的安全策略和补丁等任务,同时确保用户需求仍能得到满足,而且是在越来越多用户加入网络、却没有新的支持人员支持你的情况下完成。
自动化的关键作用
如果现在让这一切发生听起来有点像魔法,请记住,我们在这里与你分享秘密成分:自动化。
自动化是跟上不断增长业务基础设施需求的重要工具。
通过使用正确的自动化工具,我们可以在相同的时间内完成更多工作。

例如,我们可以通过运行一条命令来部署一台全新的服务器,并让自动化处理其余的工作。
我们还可以基于数据库中已存储的数据,创建一批具有所有必要权限的用户账户,从而消除所有人工交互。
自动化正是让我们能够扩展的关键。它允许一个小的IT团队管理数百甚至数千台计算机。
实践中的工具
那么,这在实践中是什么样子呢?我们可以使用多种不同的工具来实现这一点。
接下来,我们将讨论一种称为配置管理的工具类型,它可以帮助我们自动化管理计算机群组的方式。
总结
本节课中,我们一起学习了“规模”在IT环境中的核心含义:即系统或流程高效应对增长的能力。我们探讨了评估系统扩展性的关键问题,并认识到规模挑战不仅限于技术服务,也涉及人力资源和流程管理。最重要的是,我们明确了自动化是实现高效扩展、让小团队管理大规模基础设施的基石。下一节,我们将深入介绍实现自动化的具体工具——配置管理。
142:什么是配置管理?⚙️

在本节课中,我们将要学习配置管理的基本概念。我们将了解什么是配置、为什么需要管理配置,以及配置管理系统如何帮助我们自动化、一致地管理大量设备。
想象一下,你的团队负责设置一台新服务器。这台服务器可以是你附近运行的一台物理计算机,也可以是云中某处运行的虚拟机。
为了让工作启动,团队会安装操作系统、配置一些应用程序和服务、设置网络堆栈。当一切准备就绪后,通过手动部署安装和配置计算机,将服务器投入使用。
当我们在这里谈论配置时,我们指的是从当前操作系统和已安装的应用程序,到任何必要的配置文件或策略,以及服务器执行其工作所需的所有相关设置。当你在IT领域工作时,通常需要负责许多不同设备的配置,而不仅仅是服务器。网络路由器、打印机,甚至智能家居设备,都可能存在我们可以控制的配置。例如,网络交换机可能使用一个配置文件来设置其每个端口。
什么是配置管理?
上一节我们介绍了配置的含义。我们说手动部署服务器意味着配置是未管理的。那么,配置被管理意味着什么呢?
它意味着使用一个配置管理系统来处理你整个设备群(也称为节点)的所有配置。根据所涉及的设备和服务,有许多不同的工具可用。
通常,你会定义一组必须应用于你想要管理的节点的规则,然后通过一个过程确保这些设置在每一个节点上都成立。
为什么需要配置管理?
在小规模情况下,未管理的配置似乎成本不高。如果你只管理少数几台服务器,或许可以在没有自动化帮助的情况下应付。你可以在必要时登录每台设备并手动进行更改。
当你的公司需要一台新的数据库服务器时,你或许可以直接在一台备用计算机上手动安装操作系统和数据库软件。
但是,这种方法并不总能很好地扩展。你需要部署的服务器越多,手动操作所需的时间就越长。而当出现问题时(问题常常会发生),恢复并使服务器重新上线可能需要大量时间。
配置管理系统旨在解决这个扩展性问题。
配置管理系统如何工作?
通过使用这样的系统来管理设备群的配置,大规模部署变得更容易处理,因为无论你管理多少设备,系统都会自动部署配置。
当你使用配置管理并且需要在一台或多台计算机上进行更改时,你无需手动连接到每台计算机来执行操作。相反,你编辑配置管理规则,然后让自动化程序在受影响的机器上应用这些规则。
这样,你对一个系统或一组系统所做的更改是以一种系统化、可重复的方式完成的。可重复性很重要,因为它意味着在所有设备上结果都将相同。
一个配置管理工具可以获取你定义的规则,并将其应用于它所管理的系统,使更改高效且一致。配置管理系统通常还内置某种形式的自动纠错功能,以便它们能够自行从某些类型的错误中恢复。
例如,假设你发现公司广泛使用的某个应用程序配置非常不安全。你可以向配置管理系统添加规则,以改进所有计算机上的设置。这不仅会应用一次更安全的设置,还会持续监控未来的配置。
如果用户更改了他们机器上的设置,配置管理工具将检测到此更改,并重新应用你在代码中定义的设置。
常见的配置管理工具
IT行业中有许多可用的配置管理系统。一些流行的系统包括:

以下是几种主流的配置管理工具:
- Puppet
- Chef
- Ansible
- CFEngine
这些工具可用于管理本地托管的基础设施,例如公司员工使用的笔记本电脑或工作站等物理机或虚拟机。许多工具还具有某种云集成功能,允许它们管理云环境(如Amazon EC2、Microsoft Azure或Google Cloud Platform)中的资源。
这个列表还不止于此。还有一些特定于平台的工具,例如用于Windows的SCCM和组策略。这些工具在某些特定环境中可能非常有用,即使它们不如其他工具灵活。
本课程的选择
对于本课程,我们选择重点介绍Puppet,因为它是当前配置管理的行业标准。但请记住,选择配置管理系统很像决定使用哪种编程语言或版本控制系统。
你应该选择最适合你需求的工具,并在必要时进行相应调整。每种工具都有其自身的优点和缺点,因此事先做一些研究可以帮助你决定哪种系统最适合你的特定基础设施需求。市面上有很多工具,请务必查看它们。
下节预告
接下来,我们将讨论如何利用基础设施即代码范式,最大限度地发挥配置管理系统的作用。
本节课中,我们一起学习了配置管理的核心概念。我们明确了配置的含义,探讨了手动管理配置在规模扩大时面临的挑战,并介绍了配置管理系统如何通过自动化、一致性和可重复性来解决这些问题。我们还列举了Puppet、Ansible等主流工具,并强调了根据实际需求选择合适工具的重要性。
143:什么是基础设施即代码? 🏗️

在本节课中,我们将要学习一个现代IT运维中的核心概念——基础设施即代码。我们将了解它的定义、核心原则、带来的好处以及它如何帮助我们更高效、更可靠地管理计算机系统。
概述
我们已经提到,当我们使用配置管理系统时,我们会编写规则来描述我们整个计算机集群应如何配置。然后,自动化工具会执行这些规则,使计算机达到我们期望的状态。这意味着,我们可以将基础设施的行为建模成能被自动化工具处理的文件。
从配置管理到代码
上一节我们介绍了配置管理系统的基本作用。本节中,我们来看看如何将这种管理方式提升到“代码”的层面。
这些描述配置的规则文件可以被存储在版本控制系统中。记住,版本控制系统帮助我们跟踪文件的所有更改,帮助我们回答“谁、何时、为何”进行了更改。更重要的是,当我们需要回滚更改时,它们非常有用。如果一个更改被证明是有问题的,这一点尤其有帮助。
将受管设备的所有配置存储在版本控制文件中的范式,被称为基础设施即代码,简称 IaC。

换句话说,当部署和管理基础设施中一个节点所需的所有配置都存储在版本控制中时,我们就看到了基础设施即代码的应用。然后,这再与自动化工具相结合,来实际完成节点的配置和管理。
基础设施即代码的核心优势
如果你将基础设施的所有细节都妥善存储在系统中,那么当某个设备出现故障时,你可以非常快速地部署一个新设备。只需获取一台新机器(无论是虚拟的还是物理的),使用自动化工具部署必要的配置,就完成了。
以下是基础设施即代码带来的主要好处:
- 快速恢复与部署:如上所述,可以快速替换故障设备。
- 一致性:应用到设备上的配置不依赖于人工记忆并遵循所有必要步骤。结果是始终如一的,确保了部署的一致性。
- 可追溯与可回滚:由于配置存储在版本控制系统中,我们可以获得更改的审计追踪记录。如果某个更改是错误的,我们可以快速回滚。
- 协作与审查:其他人可以审查我们的“代码”以发现错误并传播知识,从而改善团队协作。
- 自动化测试:将规则存储在文件中意味着我们也可以对它们运行自动化测试。在测试中发现配置文件有拼写错误,远比在复杂或大型环境中从用户那里发现问题要好得多。
“牛 vs. 宠物”的哲学
基础设施即代码的原则通常应用于云计算环境中,在那里机器被视为可互换的资源,而不是独立的计算机。这个原则也被称为将计算机视为牛而不是宠物,因为你将它们作为一个群体来照顾,而不是单独照顾。
(向任何拥有宠物牛的人致歉。)
这个概念不仅适用于管理大型数据中心或全球性基础设施中的计算机。它适用于从服务器到笔记本电脑,甚至是小型IT部门中的工作站等任何设备。即使你的公司只有一台作为邮件服务器的计算机,你仍然可以通过将所有设置它所需的配置存储在配置管理系统中而受益。
基础设施即代码的实践意义
将你的IT基础设施视为代码,可以帮助你部署一个灵活、可扩展的系统。配置管理系统可以通过提供一个平台,以自动化方式维护和配置该基础设施,来帮助你管理这些“代码”。
将你的基础设施存储为代码意味着你可以以极低的开销自动部署你的基础设施。如果你需要将其迁移到不同的位置,只需对代码进行最小的更改,就可以在不同的地点大规模地部署、撤销和重新部署。
总而言之,将你的基础设施作为代码来管理,意味着你的节点集群是一致的、有版本控制的、可靠的和可重复的。机器不再被视为珍贵或独特的个体,而是被视为可以通过自动化按需部署的可替换资源。
面向可扩展性的思考
任何声称可扩展的基础设施都必须能够处理增长带来的容量需求。执行诸如添加更多服务器来处理请求增加这样的操作,可能只是第一步。我们可能还需要考虑其他因素,例如网络可以处理的流量量,或后端服务器(如数据库)的负载。
以这种方式看待你的基础设施,有助于你的IT团队适应变化并保持灵活性。技术行业在不断变化和发展。自动化和配置管理可以帮助你拥抱这种变化,而不是回避它。
总结
本节课中我们一起学习了基础设施即代码的概念。我们了解到,它通过将基础设施配置定义为可版本控制的代码文件,结合自动化工具,实现了部署的一致性、快速恢复、变更可追溯以及团队高效协作。其核心思想是将服务器等资源视为可批量管理和替换的“牛”,而非需要精心呵护的“宠物”,从而构建出灵活、可靠且可扩展的IT系统。
在深入探讨具体的例子之前,本课程的第一次练习测验即将到来。这些测验作为检查点,帮助你确保视频中涵盖的所有概念都得到了理解。
我们测验后再见。
144:什么是Puppet?🤖

概述
在本节课中,我们将要学习Puppet这一配置管理工具的基本概念。Puppet是当前业界管理大批量计算机配置的标准工具,我们将了解其工作原理、基本架构以及它能完成的任务。
什么是Puppet?
正如我们已经多次提到的,在本课程中,我们将学习如何通过使用Puppet来应用基本的配置管理概念。
Puppet是当前业界管理大批量计算机配置的标准工具。Puppet之所以如此流行,部分原因在于它是一个存在已久的跨平台工具。
它是一个在2005年创建的开源项目,已经经历了多个不同的版本。随着其发展,该工具不断吸纳用户的反馈,使其变得越来越有用。本Google课程上线时,最新的可用版本是2018年底发布的Puppet 6。
Puppet的架构
我们通常使用客户端-服务器架构来部署Puppet。客户端被称为Puppet代理,服务器被称为Puppet主控端。
当使用这种模型时,代理会连接到主控端,并向主控端发送一系列描述计算机状态的事实信息。主控端随后处理这些信息,生成需要在设备上应用的规则列表,并将此列表发送回代理。代理则负责在计算机上执行任何必要的更改。

Puppet是一个跨平台的应用程序,适用于所有Linux发行版、Windows和Mac OS。
这意味着你可以使用相同的Puppet规则来管理一系列不同的计算机。
Puppet规则示例
我们一直在谈论的这些规则到底是什么?让我们来看一个非常简单的例子。
package { 'pseudo':
ensure => present,
}

这个代码块表示,pseudo这个软件包应该出现在应用此规则的每台计算机上。如果这个规则应用在100台计算机上,它将在所有计算机上自动安装该软件包。
这是一个小而简单的代码块,但已经可以让我们对Puppet中规则的编写方式有一个基本的印象。现在不必过于担心语法,我们将在未来的视频中探讨每一部分的含义。
跨平台支持
根据操作系统的类型,有多种可用的安装工具。Puppet会确定正在使用的操作系统类型,并选择正确的工具来执行软件包安装。
在Linux发行版上,有几种包管理系统,如apt、yum和dnf。Puppet也会确定应该使用哪个包管理器来安装软件包。
在MacOS上,根据软件包的来源,有几种不同的可用提供程序。Apple提供程序用于操作系统自带的软件包,而Macports提供程序用于来自Macports项目的软件包。
对于Windows,我们需要在规则中添加一个额外的属性,指定安装程序文件在本地磁盘或网络挂载资源上的位置。Puppet随后会执行安装程序并确保其成功完成。如果你使用Chocolatey来管理Windows软件包,你可以为Puppet添加一个额外的chocolatey提供程序来支持它。我们将在下一篇阅读材料中提供更多相关信息的链接。
Puppet的能力范围
使用像这样的规则,我们可以让Puppet为我们做的远不止安装软件包。
以下是Puppet可以执行的一些任务:
- 我们可以添加、删除或修改系统中存储的配置文件,或者更改Windows上的注册表项。
- 我们可以启用、禁用、启动或停止运行在我们计算机上的服务。
- 我们可以配置cron作业或计划任务。
- 我们可以添加、删除或修改用户和组。
- 我们甚至可以在需要时执行外部命令。
总结
关于Puppet有很多内容可以讲。我们不会深入每一个细节,但会在本课程中涵盖最重要的概念。目标是让你对配置管理(特别是Puppet)有一个入门级的了解。我们也会为你提供指引,让你能够自行查找更多信息。
接下来,我们将探讨可以用来定义规则的不同资源。
145:Puppet 资源 🧩

在本节课中,我们将要学习 Puppet 配置管理工具中的核心概念——资源。我们将了解什么是资源,如何声明资源,以及资源如何通过提供者(Provider)在计算机上实现期望的配置状态。


在上一节视频中,我们看到了一个在计算机上安装 pseudo 软件包的例子。为了实现这个目标,我们的示例使用了 package 关键字来声明一个软件包资源。
在 Puppet 中,资源是我们想要管理的配置的基本建模单元。换句话说,每个资源都指定了我们试图管理的一项配置,例如一项服务、一个软件包或一个文件。
让我们来看另一个例子。在这个例子中,我们定义了一个文件资源。这种资源类型用于管理文件和目录。在这个具体规则中,它确保 /etc/sysctl.d 目录存在并且是一个目录。
file { '/etc/sysctl.d':
ensure => directory,
}
让我们简单讨论一下语法。在上一个例子和这个例子中,我们可以看到,在 Puppet 中声明资源时,我们将其写在一个以资源类型(本例中是 file)开头的代码块中。
资源的配置随后写在一对花括号 {} 内。在左花括号之后,首先是资源的标题,后跟一个冒号。冒号之后是我们想要为该资源设置的属性。在这个例子中,我们再次设置了 ensure 属性,其值为 directory,但我们也可以设置其他属性。
让我们查看一个不同的文件资源示例。在这个例子中,我们使用一个文件资源来配置 /etc/timezone 文件的内容,该文件在某些 Linux 发行版中用于确定计算机的时区。
file { '/etc/timezone':
ensure => file,
content => "UTC\n",
replace => true,
}
这个资源有三个属性。首先,我们明确声明这将是一个文件,而不是目录或符号链接。然后,我们将文件内容设置为 UTC 时区。最后,我们将 replace 属性设置为 true,这意味着即使文件已存在,其内容也将被替换。

我们现在已经看到了几个使用文件资源可以做什么的例子。实际上,我们可以设置的属性还有很多,例如文件权限、文件所有者或文件修改时间。我们在下一节的阅读材料中提供了一个官方文档的链接,你可以在那里找到每个资源所有可以设置的属性。
那么,这些规则是如何转化为我们计算机上的变化的呢?当我们在 Puppet 规则中声明一个资源时,我们是在定义该资源在系统中的期望状态。然后,Puppet 代理(agent)会使用提供者(Provider)将期望状态变为现实。
所使用的提供者将取决于定义的资源和代理运行的环境。Puppet 通常会自动检测这一点,无需我们做任何特殊操作。当 Puppet 代理处理一个资源时,它首先决定需要使用哪个提供者,然后将我们在资源中配置的属性传递给该提供者。每个提供者的代码负责使我们的计算机反映出资源中请求的状态。
在这些例子中,我们一次只查看了一个资源。接下来,我们将看到如何将一堆资源组合成更复杂的 Puppet 类。

总结
本节课中我们一起学习了 Puppet 资源的基础知识。我们了解到资源是 Puppet 配置管理的基本单元,用于定义系统组件的期望状态(如文件、软件包)。我们学习了声明资源的语法结构,包括资源类型、标题和属性。最后,我们明白了 Puppet 代理如何通过提供者,将代码中定义的资源状态应用到实际的计算机系统中。
146:Puppet类 🧩

在本节课中,我们将学习Puppet中的“类”概念。我们将了解如何通过类来组织相关的资源,从而更高效地管理配置。
概述
在之前的Puppet代码示例中,我们已经看到了一些声明了单个资源的类。你可能会好奇这些类的作用是什么。实际上,我们使用这些类来将实现某个目标所需的所有资源集中在一个地方。
类的用途
上一节我们介绍了Puppet的基本资源,本节中我们来看看如何通过类来组织这些资源。类的核心目的是将相关的资源组合在一起,以便于管理和理解。
例如,你可以创建一个类,它负责安装一个软件包、设置配置文件的内容,并启动该软件包提供的服务。
示例:NTP配置类
让我们来看一个具体的例子。在这个例子中,我们有一个包含三个资源的类:一个package、一个file和一个service。所有这些资源都与网络时间协议(NTP)相关,这是我们的计算机用来同步时钟的机制。
以下是该类的Puppet代码示例:
class ntp_config {
package { 'ntp':
ensure => latest,
}
file { '/etc/ntp.conf':
source => 'puppet:///modules/ntp/ntp.conf',
ensure => file,
}
service { 'ntp':
ensure => running,
enable => true,
}
}
我们的规则确保NTP软件包始终升级到最新版本。我们使用source属性来设置配置文件的内容,这意味着代理将从指定位置读取所需内容。我们通过声明希望NTP服务被启用并运行。

通过将所有与NTP相关的资源分组到同一个类中,我们只需快速浏览就能理解服务是如何配置的以及它应该如何工作。
类的优势
将所有相关资源放在一起,未来进行更改会变得更加容易。因此,每当我们想要对相关资源进行分组时,使用这种技术都是有意义的。
以下是其他一些可以使用类来分组资源的场景示例:
- 日志文件管理:将所有与日志文件管理相关的资源分组。
- 时区配置:将所有与时区配置相关的资源分组。
- 临时文件处理:将所有与处理目录中临时文件相关的资源分组。
- 软件配置:你还可以创建类来分组与你的Web服务软件、电子邮件基础设施,甚至公司防火墙相关的所有设置。
过渡与后续
我们刚刚开始接触Puppet的基本资源,并了解了如何应用它们。在接下来的视频中,我们将学习更多关于使用配置管理工具时的常见实践。
但在深入探讨之前,我们准备了一份阅读材料,其中包含了关于Puppet语法、资源的更多信息以及官方参考链接。之后还有一个快速测验,以检查你是否理解了所有内容。
总结
本节课中我们一起学习了Puppet中“类”的概念。我们了解到类是将实现特定功能所需资源(如package、file、service)组织在一起的强大工具。通过将相关资源分组,代码变得更容易理解、维护和修改。我们还通过一个配置NTP服务的具体示例,实践了如何定义和使用一个类。
147:什么是领域特定语言? 🤔

在本节课中,我们将要学习Puppet配置管理工具中的领域特定语言。我们将了解它与通用编程语言的区别,并通过一个具体示例来探索其核心语法,包括变量、条件语句和资源定义。
到目前为止,我们已经见过一些非常简单的Puppet规则示例,这些规则只定义了一个或多个资源。这些资源是Puppet规则的基础构建块。然而,我们可以使用Puppet的领域特定语言来执行更复杂的操作。
典型的编程语言,如Python、Ruby、Java或Go,是通用语言,可用于编写具有不同目标和用例的多种应用程序。相反,领域特定语言是一种范围更有限的编程语言。
学习领域特定语言通常比学习通用编程语言更快、更容易,因为它涵盖的内容少得多。你不需要学习太多语法,理解太多关键字,也无需考虑太多通用开销。就Puppet而言,其DSL仅限于与何时以及如何将配置管理规则应用到我们的设备相关的操作。
例如,我们可以使用DSL提供的机制,在笔记本电脑或台式机上设置不同的值,或者在公司网络服务器上安装一些特定的软件包,这些操作都建立在我们已了解的基本资源类型之上。
Puppet的DSL包含变量、条件语句和函数。使用它们,我们可以根据某些条件应用不同的资源或将属性设置为不同的值。
在深入探讨示例之前,我们先简单介绍一下Puppet的事实。事实是代表系统特征的变量。当Puppet代理运行时,它会调用一个名为facter的程序,该程序分析当前系统,并将其收集到的信息存储在这些事实中。完成后,它会将这些事实的值发送给服务器,服务器则利用这些值来计算应应用的规则。

Puppet附带了一系列内置的核心事实,用于存储有关系统的有用信息,例如当前的操作系统是什么、计算机有多少内存、它是否是虚拟机,或者当前的IP地址是什么。如果我们做决策所需的信息无法通过这些事实获得,我们也可以编写一个脚本来检查信息并将其转化为我们自己的自定义事实。
让我们看一个使用内置事实的Puppet代码示例。
以下代码块使用了is_virtual事实和一个条件语句来决定是安装还是清除smartmontools软件包。这个软件包用于通过SMART技术监控硬盘状态,因此在物理机上安装它很有用,但在虚拟机上安装则意义不大。
if $facts['is_virtual'] {
package { 'smartmontools':
ensure => purged,
}
} else {
package { 'smartmontools':
ensure => installed,
}
}
我们可以在这个代码块中看到Puppet领域特定语言的几个特点。让我们花点时间看看这里的所有语法元素。
首先,$facts 是一个变量。在Puppet的DSL中,所有变量名前面都有一个美元符号。具体来说,facts变量在Puppet DSL中被称为哈希,相当于Python中的字典。这意味着我们可以使用键来访问哈希中的不同元素。在本例中,我们正在访问与is_virtual键关联的值。
其次,我们看到了如何使用if编写条件语句,并用花括号将条件的每个代码块括起来。
最后,每个条件块都包含一个包资源。我们之前见过资源,但还没有详细研究其语法,现在让我们来仔细看看。

每个资源都以定义的资源类型开头,在本例中是package,然后资源的内容用花括号括起来。在资源定义内部,第一行包含标题,后跟一个冒号。之后的任何行都是正在设置的属性。我们使用=>为属性赋值,然后每个属性以逗号结尾。
我们现在已经涵盖了Puppet DSL语法的大部分内容。如果你回想一下学习第一门编程语言时的情景,你可能会注意到这里需要学习的语法要少得多。这是配置管理工具使用的领域特定语言的典型特征。虽然每种工具都使用自己的DSL,但它们通常非常简单,可以很快学会。
本节课中,我们一起学习了Puppet的领域特定语言及其与通用编程语言的区别。我们探讨了事实、变量、条件语句和资源定义的核心语法。通过一个具体示例,我们看到了如何利用系统信息来动态决定配置操作。掌握这些基础概念是有效使用Puppet等配置管理工具的关键。接下来,我们将讨论大多数配置管理工具背后的一些其他原则。
148:配置管理的驱动原则 🧭

在本节课中,我们将学习配置管理工具(以Puppet为例)背后的核心驱动原则。我们将探讨声明式语言、幂等性、测试与修复范式以及无状态性等关键概念,理解它们如何帮助我们高效、可靠地管理大量计算机系统。
到目前为止,我们已经看到了几个Puppet规则示例,包括一系列不同的资源,甚至还有一个条件表达式。
你可能已经注意到,在我们查看的所有示例中,我们从未告诉计算机为了达到我们的目的应该遵循哪些具体步骤。相反,我们只是声明了我们想要实现的最终目标。
这就像开车去得来速餐厅点一个汉堡,我们并没有亲手制作它,但它就在那里。我们之前提到的提供者(Providers),如 apt 和 yum,负责将我们的目标转化为任何必要的具体操作。
我们说Puppet使用一种声明式语言,因为我们声明的是希望达到的状态,而不是达到该状态所需的步骤。像Python或C这样的传统语言被称为过程式语言,因为我们需要写出计算机为了达到我们期望目标而需要遵循的过程。
对于来自像Python这样的过程式语言的开发者来说,可能需要一些时间来习惯编写像Puppet所使用的这种声明式代码,这很正常。只需记住,在配置管理中,简单地陈述配置应该是什么,而不是计算机应该做什么来达到这个配置,是合理的。例如,你使用一个资源来声明你想要安装一个软件包,你并不关心计算机必须运行什么命令来安装它,你只关心在配置管理工具运行之后,这个软件包被安装好了。
配置管理的另一个重要方面是操作应该是幂等的。
在此上下文中,一个幂等操作可以被重复执行多次,而不会在第一次执行后改变系统状态,也不会产生意外的副作用。
让我们通过一个文件资源的例子来理解这一点。

file { '/etc/motd':
ensure => file,
content => 'Welcome to our server!',
mode => '0644',
}
这个资源确保 /etc/motd 文件存在,具有一组特定的权限,并且包含特定的内容。满足这个要求就是一个幂等操作。
- 如果文件已经存在并且具有期望的内容,那么Puppet会理解无需采取任何行动。
- 如果文件不存在,那么Puppet会创建它。
- 如果内容或权限不匹配,Puppet会修复它们。
无论代理应用这个规则多少次,最终结果都是这个文件将拥有所要求的内容和权限。
幂等性是任何自动化任务的一个宝贵特性。如果一个脚本是幂等的,意味着它可以在任务执行到一半时失败,然后重新运行,而不会产生问题性的后果。
假设你正在运行配置管理系统来设置一台新服务器。不幸的是,设置失败了,因为你忘记给计算机添加第二块磁盘,而配置需要两块磁盘。如果你的自动化是幂等的,你可以添加缺失的磁盘,然后让系统从它中断的地方继续执行。
大多数Puppet资源都提供幂等操作,我们可以确信,运行同一组规则两次将导致相同的最终结果。
对此的一个例外是 exec 资源,它为我们运行命令。exec 资源采取的操作可能不是幂等的,因为一个命令每次执行时都可能修改系统。为了理解这一点,让我们看看在计算机上执行一个移动文件的命令时会发生什么。
# 第一次执行
mv example.txt ~/Desktop/
# 第二次执行(如果文件已移动)
mv example.txt ~/Desktop/ # 这将导致错误
我们收到一个错误,因为文件已不在原位置。换句话说,这不是一个幂等操作,因为执行相同的操作两次产生了不同的结果,并带来了错误的意外副作用。如果我们在Puppet中运行这个,会导致Puppet运行以错误结束。
因此,如果我们需要使用 exec 资源来运行命令,我们需要小心确保该操作是幂等的。例如,我们可以通过使用 onlyif 属性来实现:
exec { 'move_file':
command => 'mv /path/to/example.txt /path/to/destination/',
onlyif => 'test -f /path/to/example.txt',
}
使用 onlyif 属性,我们指定这个命令只有在我们要移动的文件存在时才应该被执行。这意味着如果文件存在,它将被移动;如果不存在,则什么都不做。通过添加这个条件,我们就把一个非幂等的操作变成了一个幂等的操作。
配置管理运作方式的另一个重要方面是测试与修复范式。
这意味着只有在为实现目标所必需时,才会采取行动。Puppet会首先测试被管理的资源(如文件或软件包)是否真的需要被修改。
- 如果文件已经存在于我们想要的位置,则无需采取行动。
- 如果一个软件包已经安装,则无需再次安装。
这避免了浪费时间去做不需要的操作。

最后,另一个重要特性是Puppet是无状态的。这意味着在代理的两次运行之间不保留任何状态。每次Puppet代理运行都是独立的,不依赖于前一次或下一次运行。
每次Puppet代理运行时,它都会收集当前的事实(facts)。Puppet主服务器仅基于这些事实生成规则。然后代理根据需要应用它们。
我们才刚刚开始了解配置管理是什么,以及在Puppet中它是什么样子,但希望你已经开始理解这些基本概念,以及如何将它们转化为实际规则,来帮助你管理一支计算机“小部队”。
本节课总结
在本节课中,我们一起学习了配置管理的核心驱动原则:
- 声明式语言:我们声明期望的最终状态(“是什么”),而不是实现它的具体步骤(“怎么做”)。
- 幂等性:操作可以安全地重复执行,确保系统始终处于期望状态,这对于自动化的可靠性至关重要。
- 测试与修复范式:系统首先检查当前状态是否符合期望,只在必要时才采取行动,提高了效率。
- 无状态性:每次配置运行都是独立的,基于当前系统事实生成和应用规则,简化了管理和故障排除。
理解这些原则是有效使用任何配置管理工具(包括Puppet)的基础。
149:使用配置管理实现自动化 🛠️

在本节课中,我们将学习如何通过配置管理工具自动化管理大规模计算机集群。我们将探讨配置管理的核心概念、工作原理,并以Puppet为例,展示如何通过声明式语言定义系统状态,实现高效、可靠的自动化运维。
我们首先从一个场景开始:如果你需要为上千台服务器升级一个软件包,会发生什么?
如果你之前从未听说过配置管理,这样的升级任务可能看起来极其漫长且枯燥。
但现在你知道了,有一系列工具可以让这类大规模变更任务变得轻松许多。
我们讨论了以可扩展的方式自动化供应、管理和调整计算机集群的必要性。
我们指出,当今IT世界的一个重要概念是将基础设施视为代码。
这使我们能够以一致、可版本控制、可靠且可重复的方式管理计算机集群。
为了理解如何实现这一目标,我们涵盖了许多与配置管理相关的概念。
例如,这些工具如何使用领域特定语言,帮助我们清晰地描述工具运行后系统应呈现的状态。
我们提到,这种语言是声明式的,因为我们声明目标,而非详细描述实现步骤。
最重要的是,所执行的操作必须是幂等的,即多次运行相同的规则总能得到相同的结果。
在整个过程中,我们以Puppet为例,说明了配置管理工具的工作原理。
我们研究了Puppet DSL的语法,并查看了最常见的资源类型:软件包、文件和服务。
未来的视频将介绍其他资源和高级技术。
但到目前为止,你应该对Puppet规则的形式以及如何将我们讨论的配置管理概念付诸实践有了很好的理解。
结合我们已涵盖的概念,你可能开始意识到,将你的机器集群(无论是虚拟还是物理的)保持在既定配置状态是一种良好实践。
如果某台机器出现故障、宕机,或发生字面或比喻意义上的“着火”,你可以轻松地启动一个替代品。
因为你通过配置确切知道它应该是什么样子,并且可以利用自动化轻松部署它。
在下一个模块中,我们将探讨如何在你的基础设施中部署Puppet,并研究一些更高级的配置管理和变更管理技术。
在此之前,你将有机会尝试修复一个配置管理未按预期工作的系统。
你将看到在实践中运行Puppet代理是什么样子,找出已部署规则的问题所在,然后让自动化行为符合预期。
本节课中,我们一起学习了配置管理如何通过将基础设施视为代码,并利用声明式、幂等的自动化工具(如Puppet),来高效、可靠地管理大规模系统。这为应对复杂的运维挑战提供了坚实的基础。
150:使用配置管理实现自动化 🛠️

在本节课中,我们将学习如何通过配置管理工具自动化管理大规模服务器集群。我们将探讨配置管理的核心概念、工作原理,并通过Puppet工具的具体示例,理解如何声明式地定义系统状态,实现高效、可靠的自动化运维。
我们首先从一个场景开始:如果你需要在由一千台不同服务器组成的集群中升级一个软件包,会发生什么?
如果你之前从未听说过配置管理,这样的升级任务可能看起来非常漫长且枯燥。
但现在你已经知道,有一系列工具可以让这类大规模变更任务变得轻松许多。
我们已经讨论了以可扩展的方式供应、管理和调整计算机集群所必需的自动化类型。
我们指出,当今IT领域的一个重要概念是将我们的基础设施视为代码。
这使我们能够以一致、可版本控制、可靠且可重复的方式管理计算机集群。
为了理解如何实现这一目标,我们涵盖了许多与配置管理相关的概念。
例如,这些工具如何使用领域特定语言,帮助我们清晰地描述工具运行后希望系统呈现的状态。
我们提到这种语言是声明式的,因为我们声明目标,而不是详细描述实现步骤的每一步。
最重要的是,所采取的操作必须是幂等的,这意味着多次运行相同的规则总会得到相同的结果。
在整个过程中,我们一直使用Puppet作为示例,来说明配置管理工具的工作原理。
我们研究了Puppet DSL(领域特定语言)的语法,并查看了最常见的资源类型:包、文件和服务。
在未来的视频中,我们将学习其他资源和更高级的技术。但到目前为止,你应该对Puppet规则的样子以及如何将我们讨论的配置管理概念付诸实践有了很好的理解。
基于我们已涵盖的概念,你可能开始意识到,将你的机器集群(无论是虚拟的还是物理的)置于一个受控的配置管理之下是一种良好的实践。
如果某台机器出现故障、宕机,或发生字面或比喻意义上的“着火”,你可以轻松地启动一个替代品,因为你通过配置确切地知道它应该是什么样子,并且可以使用自动化轻松部署它。
在下一个模块中,我们将探讨如何在你的基础设施中部署Puppet,并研究一些更高级的配置管理和变更管理技术。
在此之前,你将有机会尝试修复一个配置管理未按预期工作的系统。
你将看到在实践中运行Puppet代理是什么样子,找出已部署规则的问题所在,然后让自动化行为符合预期。
核心概念与语法示例
以下是配置管理中的一些核心操作,以Puppet DSL代码示例说明:
1. 管理软件包
确保系统上安装或移除了特定的软件包。
package { 'nginx':
ensure => installed,
}
2. 管理文件
确保文件存在,并具有特定的内容、所有权和权限。
file { '/etc/nginx/nginx.conf':
ensure => file,
content => template('nginx/nginx.conf.erb'),
owner => 'root',
group => 'root',
mode => '0644',
}
3. 管理服务
确保系统服务处于运行状态,并在配置文件变更后自动重启。
service { 'nginx':
ensure => running,
enable => true,
subscribe => File['/etc/nginx/nginx.conf'],
}
总结
本节课中,我们一起学习了配置管理自动化的核心价值与实践方法。
我们理解了将基础设施视为代码的重要性,它带来了可版本化、可靠和可重复的管理方式。
我们探讨了声明式语言与幂等性原则,这是确保自动化结果一致性的关键。
通过Puppet的示例,我们初步掌握了如何使用DSL定义包、文件和服务等资源的状态。
最终,我们认识到,健全的配置管理是应对系统故障、实现快速替换和部署的基石,为后续学习更高级的自动化运维技术打下了坚实基础。
151:部署Puppet 🚀
在本节课中,我们将深入学习如何部署和使用Puppet进行配置管理。我们将从安装Puppet开始,逐步探索如何测试规则、配置客户端-服务器架构,并应用最佳实践来安全地部署变更。

欢迎回来,恭喜你完成了本课程的第一次实验。在上一模块中,我们探讨了一些基本的配置管理概念,以及这些概念在Puppet中的体现。
本节中,我们将更深入地研究如何使用这些工具。我们将从如何在计算机上安装Puppet开始,并介绍如何使用一个简单的测试设置来检查规则是否按预期工作。
这个测试设置将允许你在自己的计算机上尝试这些视频中的示例。
我们还将展示如何配置典型的客户端-服务器设置,让Puppet客户端连接并验证到Puppet服务器,以获取它们应应用的规则。
此外,我们将讨论如何使用测试技术和发布最佳实践,安全地将变更部署到配置管理系统的客户端。
你已经学习了Puppet资源的基础知识。我们介绍了三种最重要的资源:包、文件和服务。
在本模块中,我们将探索更多使用基本资源和Puppet领域特定语言的方法。
我们将研究如何将不同的规则集应用到你的设备群中的不同节点,如何组织规则以便于维护,以及一系列其他Puppet最佳实践。
我们将以另一个实验练习结束。不过这次,你将运用你的Puppet技能来管理部署,并操控系统使其按你的意愿运行。
准备好了吗?让我们开始吧。
安装与测试Puppet 🔧
上一节我们介绍了Puppet的基本概念,本节中我们来看看如何安装Puppet并建立测试环境。
以下是在你的计算机上安装Puppet的步骤:
- 访问Puppet官方网站下载适用于你操作系统的安装包。
- 按照安装向导的指示完成安装过程。
- 打开终端或命令提示符,输入
puppet --version来验证安装是否成功。
为了测试你的Puppet规则,你可以使用一个简单的清单文件。例如,创建一个名为 test.pp 的文件,并写入以下内容:
file { '/tmp/testfile':
ensure => present,
content => "Hello, Puppet!",
}
然后,在终端中运行 puppet apply test.pp 来应用这个规则,并检查 /tmp/testfile 是否被创建。
配置客户端-服务器架构 ⚙️
现在我们已经能够在单机上测试Puppet,接下来让我们看看如何配置一个更接近生产环境的客户端-服务器架构。
在这种架构中,Puppet服务器(称为Puppet master)存储着所有配置规则(清单)。各个客户端节点会定期连接到服务器,获取其对应的配置并应用。
以下是配置此架构的关键步骤:
- 在一台服务器上安装并配置Puppet master组件。
- 在每个客户端节点上安装Puppet代理。
- 在Puppet master上为每个客户端签署证书,以建立安全连接。
- 客户端将定期(默认每30分钟)从master拉取并应用配置。
组织规则与最佳实践 📚
随着规则数量的增长,良好的组织结构变得至关重要。Puppet使用“模块”来将相关的资源、文件和模板分组,这使得代码更易于管理和复用。
例如,你可以创建一个名为 nginx 的模块来管理所有与Nginx Web服务器相关的配置。
Puppet最佳实践还包括:
- 使用版本控制系统(如Git)来管理你的Puppet代码。
- 在将变更部署到生产环境之前,先在测试环境中进行验证。
- 利用Puppet Forge上的社区模块来加速开发,而不是重复造轮子。
实验练习:运用Puppet技能 🧪
在本节最后的实验练习中,你将有机会综合运用所学知识。你的任务是编写Puppet清单,来自动完成以下操作:
- 确保一个特定的软件包(如
vim)被安装。 - 确保一个服务(如
ssh)处于运行状态。 - 管理一个配置文件的内容。
通过完成这个练习,你将巩固对Puppet资源声明和模块化组织的理解。
本节课中我们一起学习了如何部署Puppet,从安装测试到配置客户端-服务器架构,再到组织代码和遵循最佳实践。掌握这些技能将使你能够有效地使用Puppet自动化管理大规模的系统配置。
152:本地应用Puppet规则 🛠️

在本节课中,我们将学习如何将Puppet规则应用于本地计算机。我们将从安装Puppet开始,然后创建并应用一个简单的清单文件,以管理软件包的安装。通过这个过程,您将了解Puppet如何作为独立应用程序运行,并验证规则是否按预期工作。
安装Puppet
上一节我们介绍了Puppet的语法和可用资源。本节中,我们来看看如何在本地计算机上安装Puppet。
Puppet可在多种平台上使用。我们可以通过操作系统的包管理系统安装,或从官方网站下载。两种方式均可,选择取决于具体需求。在本练习中,我们将使用Ubuntu发行版提供的Puppet包。
以下是安装步骤:
sudo apt install puppet-master
安装完成后,我们可以开始尝试编写一些规则。
创建清单文件
现在Puppet已安装,我们将创建一个最简单的Puppet文件。随着部署的改进,我们可以使其更复杂。
在本例中,我们希望使用Puppet确保每台计算机都安装了一些用于调试问题的有用工具。
首先,我们需要创建一个文件来存储要应用的规则。在Puppet术语中,这些文件称为“清单”,必须以.pp扩展名结尾。
我们将创建一个名为tools.pp的新文件,并在其中定义一个包资源。
以下是文件内容:
package { 'htop':
ensure => present,
}
此资源声明我们希望Puppet确保计算机上安装了htop包。htop是一个类似于top的工具,可显示额外信息。
保存文件后,我们可以在应用规则前检查命令是否尚未存在。
应用Puppet规则
在应用规则之前,我们先验证htop是否尚未安装。

确认未安装后,我们使用以下命令运行规则:
sudo puppet apply -v tools.pp
-v标志告诉Puppet我们想要详细输出,这将显示Puppet应用规则时的过程。
Puppet首先加载事实,然后编译目录,接着应用当前配置,安装请求的包,最后通知目录应用完成。
目录是服务器评估所有变量、条件语句和函数后,为特定计算机生成的规则列表。在本例中,目录与代码完全相同,因为代码不包含任何变量、函数或条件语句。
更复杂的规则集可能根据事实值生成不同的目录。

验证规则应用
现在检查规则是否实际生效。尝试再次运行htop命令,因为Puppet已为我们安装。
这次命令应成功运行。如果计算机出现问题,我们现在可以使用此工具更好地了解原因。
退出htop后,我们再次尝试应用Puppet规则。Puppet会检测到包已安装,因此不会尝试重新安装。这意味着目录应用得更快,因为无需更改任何内容。

总结
本节课中,我们一起学习了如何在清单文件中编写Puppet资源,并使用puppet apply将这些规则应用于一台计算机。我们还验证了Puppet能够智能地检测已安装的包,避免重复操作。
下一节,我们将探讨如何管理不同Puppet资源之间的关系,以及应用时的表现。
153:Puppet资源关系管理 🧩

在本节课中,我们将学习如何在Puppet清单中管理多个资源之间的关系。我们将看到如何确保资源按照正确的顺序执行,例如先安装软件包,再配置文件,最后启动服务。
概述
上一节我们编写了一个简单的Puppet清单并在本地应用。这是练习应用Puppet规则的好方法,但内容非常简单。现在,让我们挑战一些更复杂的任务。
我们用于管理计算机群的Puppet清单通常包含多个相互关联的资源。你不可能配置一个未安装的软件包,也不希望在软件包和配置文件就位之前启动服务。Puppet允许我们通过资源关系来控制这一点。
资源关系示例
让我们通过一个例子来了解资源关系。我们有一个名为NTP.pp的文件,其中包含与NTP配置相关的一系列资源,就像我们在之前的视频中看到的那样。
这次,除了声明需要管理的资源外,我们还声明了它们之间的一些关系。
以下是NTP.pp文件内容的示例:
class ntp {
package { 'ntp':
ensure => installed,
}
file { '/etc/ntp.conf':
ensure => file,
content => 'server ntp.org',
require => Package['ntp'],
}
service { 'ntp':
ensure => running,
enable => true,
require => File['/etc/ntp.conf'],
subscribe => File['/etc/ntp.conf'],
}
}
include ntp
我们声明配置文件需要NTP软件包,服务需要配置文件。这样,Puppet就知道在启动服务之前,配置文件需要正确设置;在设置配置文件之前,软件包需要安装。
我们还声明如果配置文件发生变化,NTP服务应该收到通知。这样,如果我们将来对配置文件的内容进行额外更改,服务将重新加载新设置。


如果你仔细观察,可能会注意到资源类型是小写的,但像require或subscribe这样的关系在引用资源时首字母是大写的。这是Puppet语法的一部分。我们在声明资源类型时使用小写,但在从另一个资源的属性中引用它们时将其首字母大写。
如果现在听起来令人困惑,别担心,可能需要一些时间来理解,但最终会明白的。
应用规则
现在,最后一件事,在文件底部,我们有一个include ntp的调用。这就是我们告诉Puppet要应用类中描述的规则的方式。在这个例子中,我们将类的定义和包含类的调用放在同一个文件中。通常,类在一个文件中定义,在另一个文件中包含。我们将在以后的视频中查看这方面的例子。
好的,让我们在本地应用这些规则。
sudo puppet apply NTP.pp --verbose
我们的规则已经运行,在详细输出中,我们可以看到它做了一系列事情。首先,它安装了软件包。然后它检查配置文件是否需要更新,因此更改了其内容。最后,在更改配置内容后,Puppet知道重新启动NTP服务。
我们在这里看到我们的Puppet规则如何转化为几个不同的操作。这很酷,但接下来会更好。
修改配置文件
让我们通过编辑此目录中的NTP.conf文件来更改配置文件。这是NTP服务使用的配置文件。它当前使用来自ntp.org的一堆服务器,但我们希望尝试使用Google提供的NTP服务器。这些服务器称为time1.google.com、time2.google.com、time3.google.com和time4.google.com。
我们已经进行了更改,用:wq保存,然后使用新的配置文件重新运行我们的Puppet规则。

sudo puppet apply NTP.pp --verbose
太棒了,Puppet用新内容更新了配置文件,然后刷新了服务,因此它加载了配置。
总结
在本视频中,我们看到了如何应用包含具有多个资源的类的Puppet清单。我们将与NTP服务相关的所有信息分组到一个特定的清单中,这在处理Puppet规则时是常见做法。我们希望将相关操作保持在一起,并将不相关的事物分开。接下来,我们将研究如何使用Puppet模块来实现这一点。
154:组织Puppet模块 📦

在本节课中,我们将学习如何在Puppet中组织和管理复杂的配置。我们将了解模块的概念、结构,以及如何创建和使用模块来高效地管理大量资源。
在任何配置管理部署中,通常需要管理许多不同的事务。
我们可能需要安装一些软件包、复制一些配置文件、启动一些服务、安排一些周期性任务、确保创建某些用户和组并授予其对特定设备的访问权限,以及执行一些现有资源未提供的命令。
此外,不同的计算机可能需要应用不同的配置。
例如,工作站和笔记本电脑可能包含服务器上不使用的资源,而每种特定类型的服务器都需要其自身的特定设置。
需要管理的事务非常多。我们需要以一种有助于长期维护的方式来组织所有这些资源和信息。
这意味着需要将相关的资源分组,为这些组起恰当的名称,并确保其组织结构对新用户而言是清晰易懂的。
什么是Puppet模块?🧩
上一节我们介绍了配置管理的复杂性,本节中我们来看看Puppet的核心组织单元——模块。
在Puppet中,我们将清单(manifests)组织成模块。一个模块是清单及相关数据的集合。
我们可以将任何想要的资源放入模块中,但为了保持配置管理的条理性,我们会将相关事务按合理的主题分组。
例如,我们可以有一个模块专门管理与计算机健康监控相关的一切,另一个模块用于设置网络栈,还有一个模块用于配置Web服务应用程序。
模块的结构 📁
模块包含清单和关联数据,但具体是如何组织的呢?

所有清单都存储在一个名为 manifests 的目录中。其余数据则根据其功能存储在不同的目录中。
以下是模块中常见的目录及其作用:
files目录:包含无需任何更改即可复制到客户端机器的文件,例如我们上一节视频中看到的ntp.conf文件。templates目录:包含在复制到客户端机器之前需要预处理的文件。这些模板可以包含在计算清单后会被替换的值,或者仅在满足特定条件时才存在的部分。
根据模块的具体功能,还可以包含更多目录,但在创建第一个Puppet模块时,你无需担心这些。
创建你的第一个模块 🛠️
了解了模块的结构后,我们来学习如何创建一个简单的模块。
你可以从一个简单的模块开始,它只在 manifests 目录中有一个清单文件。
这个文件应命名为 init.pp,并且它应该定义一个与所创建模块同名的类。
然后,你的规则所使用的任何文件都需要存储在 files 或 templates 目录中,具体取决于你是直接复制它们还是需要预处理它们。
例如,我们上一节视频中看到的NTP类在转换为模块后是这样的:
- 有一个
init.pp文件,其中包含我们之前看到的NTP类。 - 部署到机器上的
ntp.conf文件现在存储在files目录中。
使用预打包模块 🔌
像这样的模块无论谁使用,看起来都几乎一样。因此,随着时间的推移,使用Puppet的系统管理员会分享他们编写的模块,让其他人也能使用相同的规则。
现在,已经存在大量预打包的、可供直接使用的模块。如果某个模块能满足我们的需求,我们只需将其安装到Puppet服务器上,并在部署中使用它。
让我们安装由Puppet Labs提供的Apache模块,来了解一下这是如何工作的。
# 安装Apache模块的示例命令(具体命令可能因环境而异)
puppet module install puppetlabs-apache
我们已经安装了模块,先快速查看一下它的内容。首先,切换到存储模块文件的目录并列出其内容。
我们看到之前提到的 files、manifests 和 templates 目录。除此之外,还有一个 lib 目录,用于向Puppet已有的功能中添加函数和事实(facts)。metadata.json 文件则包含关于我们刚安装的模块的一些额外数据,例如它与哪些操作系统的哪些版本兼容。
让我们看看 manifests 目录里面。哇,文件真多!就像我们将要管理的不同事务拆分到单独的模块中一样,我们也可以将想要配置的每个独立功能拆分到单独的清单文件中。这有助于我们在修改代码时组织代码。
请注意,这个目录也包含它自己的 init.pp 文件。正如我们指出的,这个清单很特殊,它必须始终存在,因为当包含一个模块时,它是Puppet读取的第一个文件。
如何包含和使用模块 ➕
那么,我们如何包含像这样的一个模块呢?这非常简单。
让我们创建一个清单文件,来包含我们刚刚安装的模块。
# site.pp 或你的主清单文件
include ::apache

在这里,我们告诉Puppet包含Apache模块。模块名前的双冒号 :: 让Puppet知道这是一个全局模块。
现在保存这个文件,并像之前一样使用 puppet apply 来应用它。
我们的清单超级简单,它只是包含了Apache模块。但是,通过包含这个模块,我们让Puppet应用了该模块中默认运行的所有规则。现在,这台机器上已经配置好了一个Apache服务器并准备运行了。
总结 📝
本节课中,我们一起学习了如何将代码组织到模块中,以及如何甚至可以使用其他团队提供的模块,从而避免重复造轮子。
接下来,有一份阅读材料提供了更多信息的指引,之后是一个快速测验。在那之后,请在下一个视频中与我见面,我们将探讨如何将我们的规则部署到更多机器上。
155:Puppet 节点管理 🎯

概述
在本节课中,我们将学习如何使用Puppet管理计算机集群,特别是如何为所有计算机设置通用规则,同时为特定子集系统应用特殊规则。我们将探讨节点定义的概念,并通过示例了解如何配置默认节点和特定节点。
节点定义基础
上一节我们介绍了Puppet的基本概念,本节中我们来看看如何通过节点定义来管理不同系统。
在Puppet术语中,节点指任何可以运行Puppet代理的系统。它可以是物理工作站、服务器、虚拟机,甚至是网络路由器。只要该系统安装了Puppet代理并能应用给定规则,它就是一个节点。
我们可以设置Puppet为所有节点提供一些基本规则,然后为需要不同的节点应用一些特定规则。
默认节点配置
以下是默认节点定义的示例:

node default {
include sudo
class { 'ntp':
servers => ['ntp1.example.com', 'ntp2.example.com']
}
}
在这个例子中,默认节点包含两个类:sudo类和ntp类。对于ntp类,我们设置了一个额外的servers参数,列出了可用于获取网络时间的服务器。
如你所见,在定义节点时,如果没有额外设置,可以直接使用类名包含类;如果需要,可以包含类并设置额外参数。
默认节点将默认应用于集群中的所有计算机。
特定节点配置
如果你希望某些设置仅应用于特定节点,可以通过添加更多节点定义来实现,如下所示:
node 'webserver.example.com' {
include sudo
class { 'ntp':
servers => ['ntp1.example.com', 'ntp2.example.com']
}
include apache
}
在这个例子中,我们为名为webserver.example.com的主机定义了节点。对于这个节点,我们包含了之前相同的sudo和ntp类,并在此基础上添加了Apache类。
集群中的特定节点通过其FQDN(完全限定域名)来标识。
节点匹配机制
需要理解的重要一点是:默认节点定义中包含的类仅应用于没有明确条目的节点。换句话说,当节点请求应应用哪些规则时,Puppet会查看节点定义,找出与节点FQDN匹配的定义,然后仅提供这些规则。
为了避免重复包含所有公共类,我们可以定义一个基础类,负责包含所有节点类型共有的类。
节点定义存储位置

节点定义通常存储在名为site.pp的文件中,该文件不属于任何模块。它仅定义哪些类将包含在哪些节点中。
这是帮助我们以更易于维护的方式组织代码的另一步骤。
总结
本节课中我们一起学习了Puppet节点管理的关键概念。我们了解了如何通过节点定义为计算机集群设置通用和特定规则,探讨了默认节点和特定节点的配置方法,并理解了节点匹配机制和节点定义的存储方式。下一节中,我们将研究Puppet用于验证节点是否确实具有其所声称名称的基础设施。
156:Puppet证书基础设施 🔐

在本节课中,我们将学习Puppet如何通过公钥基础设施(PKI)确保服务器与客户端之间的安全通信,以及如何验证节点的身份。
在典型的Puppet部署中,所有被管理的机器都会连接到一个Puppet服务器。客户端将信息发送给服务器,服务器随后处理清单,生成相应的目录,并将其发送回客户端以在本地应用。在上一节中,我们提到可以根据节点名称对其应用不同的规则。客户端在连接时会向服务器发送其名称。但服务器如何信任客户端确实是其所声称的身份呢?这是一个涉及重要安全概念的复杂主题。
公钥基础设施(PKI)概述 🔑
Puppet使用公钥基础设施(PKI)在服务器与客户端之间建立安全连接。Puppet采用的安全套接层(SSL)技术与HTTPS加密传输所使用的技术相同。客户端使用此基础设施检查服务器身份,服务器则用它检查客户端身份。所有通信都通过使用这些身份的加密通道进行,因此无法被第三方截获。
以下是其工作原理:每台参与的机器都拥有一对相互关联的密钥:一个私钥和一个公钥。私钥是保密的,只有该特定机器知道。公钥则与其他参与的机器共享。机器随后可以使用标准化流程验证任何其他机器的身份。发送方使用其私钥对消息进行签名,接收方则使用相应的公钥验证签名。
证书颁发机构(CA)的作用 🏛️

但机器如何知道该信任哪些公钥呢?这就是证书颁发机构(CA)的作用所在。CA验证机器的身份,然后创建证书,声明该公钥属于该机器。之后,其他机器可以依赖该证书,从而信任该公钥。Puppet自带一个证书颁发机构,可用于为每个客户端创建证书。您可以使用此内置CA,或者如果您的公司已有验证机器身份的CA,可以将其与Puppet集成,以便只进行一次身份验证。
Puppet证书签发流程 🔄
现在,假设您使用内置的证书基础设施,深入了解此流程的工作原理。当节点首次向Puppet主服务器注册时,它会请求证书。Puppet主服务器查看此请求,如果能够验证节点的身份,则为该节点创建证书。系统管理员可以手动检查身份,或使用一个自动流程,利用有关机器的额外信息来验证其身份。当代理节点获取此证书后,它便知道可以信任Puppet主服务器。并且从那时起,节点可以在请求目录时使用该证书来标识自己。
节点身份验证的重要性 ⚠️
您可能会想,为什么我们如此关心节点的身份?原因有很多。首先,Puppet规则有时可能包含机密信息,您不希望这些信息落入错误的人手中。但即使没有任何规则包含机密信息,您也希望确保您设置为Web服务器的机器确实是您的Web服务器,而不是一台仅声称具有相同名称的恶意机器。如果随机计算机以错误设置出现在您的网络中,可能会导致各种问题。如果您正在创建测试部署以尝试Puppet规则的应用方式,并且只管理测试机器,可以配置Puppet自动签署所有请求,但对于真实用户使用的真实计算机,绝不应这样做。请记住,安全总比后悔好。因此,请始终花时间验证您的机器。
手动与自动签名方法 🛠️
刚开始使用Puppet时,通常使用手动签名方法。在这种情况下,当节点连接到主服务器时,它将生成证书请求,该请求将进入Puppet主服务器机器的队列中。然后,您需要验证机器的身份是否正确,内置CA将颁发相应的证书。如果您的机器群规模很大,这种手动方法将无法有效工作。相反,您需要编写一个脚本,自动为您验证机器的身份。一种方法是在机器配置时将唯一信息复制到机器中,然后将此预共享数据用作其证书请求的一部分。这样,您的脚本可以在不涉及任何人工干预的情况下验证机器是否是其声称的身份。
本节课中,我们一起学习了Puppet用于在节点连接到主服务器时识别其身份的基础设施。接下来,我们将看看使用独立Puppet服务器和客户端的典型Puppet设置在实际中是什么样子。
157:设置Puppet客户端与服务器 🚀

在本节课中,我们将学习如何实际部署Puppet,包括设置Puppet主服务器(Master)和客户端(Agent),并实现基本的配置管理自动化。
概述
我们将通过一个测试部署来演示Puppet的工作流程。首先,我们会配置Puppet主服务器,然后在一台名为“web Server”的客户端机器上安装并配置Puppet代理(Agent)。最后,我们将创建一个简单的节点定义清单,让Puppet自动在客户端上安装Apache软件包,并设置代理服务持续运行以保持配置同步。
配置Puppet主服务器
上一节我们介绍了Puppet的基本概念。本节中,我们来看看如何配置Puppet主服务器。
我们已经在这台计算机上安装了puppet-master软件包,因此我们将用它作为主服务器。由于这是一个用于演示的测试部署,我们会将其配置为自动签署所添加节点的证书请求。请注意,如果在真实计算机上部署,则需要手动签署请求或实施适当的验证脚本。
我们通过以下命令进行配置:
puppet config set master autosign true
此命令在[master]配置部分将autosign设置为true。
连接并配置Puppet客户端
配置好主服务器后,我们现在可以连接到需要管理的客户端。
我们将通过SSH连接到一台名为web Server的机器。在这台机器上,我们需要安装Puppet客户端软件包。
以下是安装Puppet代理的步骤:
- 在客户端机器上安装
puppet软件包(该软件包包含代理程序)。 - 安装完成后,需要配置代理以与我们运行在另一台机器上的Puppet服务器通信。
我们使用类似的puppet config命令进行配置,但这次是指定服务器地址:
puppet config set server ubuntu.example.com
此命令将客户端要连接的服务器设置为ubuntu.example.com。
测试连接并申请证书
配置好服务器地址后,我们可以测试与Puppet主服务器的连接。
使用以下命令进行测试运行,-v参数用于获取详细输出,--test参数执行测试运行:
puppet agent -v --test
代理程序会执行一系列操作:
- 首先为机器创建SSL密钥。
- 然后从机器读取一系列信息,并用这些信息创建证书签名请求(CSR)。
- 代理会显示所请求证书的指纹。如果采用手动签署方式,我们可以使用此指纹来验证请求与服务器生成的请求是否匹配。
- 证书随后在我们的Puppet主服务器上生成(此过程在另一台计算机上完成,因此本地看不到记录)。
- 客户端计算机会接收证书并将其存储在本地。
证书交换完成后,代理会检索机器的所有信息并发送给主服务器。作为交换,它会收到一份“目录”(catalog)并应用它。由于我们尚未配置任何要应用于客户端的规则,因此目录几乎立即应用完毕,没有实际变更。
创建节点定义清单
现在,我们应该开始创建一些规则了。让我们回到Puppet主服务器并创建几个节点定义。
如前所述,节点定义存储在一个名为site.pp的清单文件中,该文件位于节点环境的根目录下。我们将在后续视频中详细讨论环境。目前,我们只需知道我们的客户端正在尝试访问production环境。
因此,我们需要创建的文件位于以下路径:
/etc/puppet/code/environments/production/manifests/site.pp
在该文件中,我们将创建几个节点定义。我们希望在我们的Web服务器上安装Apache,因此我们将为Web服务器创建一个节点定义,其中包含Apache类(暂时不设参数)。我们还会添加一个默认的节点定义,暂时保持为空,未来可以添加更多类。
一个基本的节点定义示例如下:
node 'web Server' {
class { 'apache': }
}
node default {
# 可以在此处为其他节点添加默认配置
}
保存此文件后,我们可以在Web服务器机器上再次运行puppet agent。这次,代理连接到Puppet主服务器并获取到一个目录,该目录指示它安装和配置Apache软件包,这包括设置一系列不同的服务。
启用并启动Puppet代理服务
到目前为止,我们一直为了测试目的而手动运行Puppet代理。既然我们知道它工作正常,我们希望让Puppet自动持续运行。这样,如果我们对配置进行更改,客户端将自动应用这些更改,而无需我们执行任何手动步骤。

为此,我们将使用systemctl命令,它允许我们控制机器启动时启用的服务以及当前正在运行的服务。
以下是启用并启动Puppet服务的步骤:
- 首先,告诉systemctl启用puppet服务,以便机器重启时自动启动代理。
systemctl enable puppet - 然后,告诉systemctl启动puppet服务,使其开始运行。
systemctl start puppet - 最后,询问systemctl关于puppet服务的状态,以检查它是否确实在运行。
systemctl status puppet
运行成功后,Puppet代理将持续定期与主服务器检查,询问是否有需要应用到机器上的更改。
总结
本节课中,我们一起学习了Puppet在服务器-客户端模型下的实际应用。我们使用在Puppet主服务器上设置的配置来管理Web服务器中软件的安装和配置。同时,我们在Web服务器中设置Puppet代理持续运行,以保持配置处于最新状态。虽然我们只看到了配置Puppet的最基础知识,但这已经可以让你体会到配置管理功能的强大之处。
接下来,我们将收集更多关于如何进行客户端-服务器设置的信息,之后会有一个快速测验来检查大家是否都理解了所学内容。
158:修改和测试清单 🛠️

在本节课中,我们将学习如何安全地修改Puppet清单,并通过多种方法测试这些修改,以确保它们能正确、安全地应用于整个服务器集群。
概述
当我们修改一个由Puppet管理的配置设置时,Puppet代理会将此更改应用到所有节点上。它执行必要的操作,使节点达到新的期望状态。这意味着,只需对清单做一个小改动,就能修改集群中的所有机器。这功能非常强大,但责任也重大。在接下来的内容中,我们将探讨如何测试我们的更改,以确保它们按预期工作,并能安全地应用到集群中,避免引发问题。
手动测试及其风险
IT专家在配置管理工作中,一个常见的做法是直接在机器上强制应用他们想要测试的清单,以此来测试新规则。我们在之前的示例中,也曾在应用到远程机器之前,先在本地应用过规则。
然而,这种方法可能会适得其反。例如,假设你试图使用Puppet来更改节点上某些文件的权限,锁定一些你认为用户不需要的路径。现在,想象一下你在自己的计算机上测试这些规则,结果发现自己犯了个错误,把自己锁在了系统之外。这就很麻烦了。
更安全的测试策略
那么,我们可以采取哪些替代方案呢?有许多事情需要考虑。一个简单的第一步是使用 puppet parser validate 命令来检查清单的语法是否正确。
代码示例:
puppet parser validate your_manifest.pp
除此之外,我们还可以使用 --noop 参数(意为“无操作”)来运行规则。这会让Puppet模拟它将要执行的操作,而实际上并不执行。你可以查看它计划采取的操作列表,并检查这些操作是否完全符合你的预期。
代码示例:
puppet apply --noop your_manifest.pp
但如果更改很复杂,我们很可能在查看计划操作时遗漏一些重要的细节。
使用测试机器
另一个选择是使用专门用于测试更改的测试机器。你可以在这些机器上应用规则,然后在Puppet运行后,检查一切是否正常工作。但同样,这是一个手动过程,我们可能会忘记验证某些重要的方面。
自动化测试:RSpec
我们如何才能实现自动化呢?就像我们在早期课程中看到的Python自动化测试一样,Puppet也允许我们使用RSpec测试来自动化测试我们的清单。
在这些测试中,我们可以设置相关的事实(Facts)和不同的值,并检查最终生成的目录(Catalog)是否包含我们期望的内容。
让我们看一个例子。在这个测试中,我们将 is_virtual 事实设置为 false,然后要求测试基础设施验证 GKSU 包是否被包含,并且其 ensure 参数被设置为 latest。
代码示例:
# 这是一个RSpec测试示例
describe ‘your_module::your_class’ do
let(:facts) do
{ :is_virtual => false }
end
it { is_expected.to contain_package(‘GKSU’).with_ensure(‘latest’) }
end

像这样的测试是检查我们编写的目录是否正确的一种有效方式。当我们的规则使用大量相互关联的事实时,它们会非常有帮助,我们可以借此检查结果是否符合我们的初衷。
我们可以编写一系列这样的测试,并在规则发生更改时自动运行它们。这样,我们可以确保规则保持有效,并知道新的更改没有破坏旧的规则。
验证实际效果
但这只是检查了目录是否包含我们所说的规则。我们如何验证这些规则是否真的产生了我们想要的效果呢?例如,启用了公司网站或设置了严格的防火墙?
我们需要在节点上应用这些规则,并检查结果是否正确。我们也可以自动化这个过程。为此,我们可以使用一组测试机器,首先在其上应用目录,然后使用脚本来检查机器的行为是否正确。
准备发布更改
现在,假设你所有的测试都成功了,更改已准备好发布。你如何安全地将其推送到整个集群呢?这将是我们下一个视频要讨论的内容。
总结
本节课中,我们一起学习了修改Puppet清单后的关键步骤:测试。我们探讨了手动测试的风险,介绍了使用 puppet parser validate 进行语法检查和 --noop 参数进行模拟运行的更安全方法。我们还了解了使用专用测试机器进行手动验证的局限性,并最终引入了强大的自动化测试工具——RSpec。通过编写RSpec测试,我们可以自动验证目录内容的正确性。最后,我们提到,要验证规则的实际效果,需要在测试机器上应用更改并进行自动化验证。下一节课,我们将学习如何安全地将经过充分测试的更改部署到整个生产环境。
159:安全地推出和验证变更 🚀

在本节课中,我们将学习如何安全地将配置变更部署到生产环境。我们将探讨测试环境的重要性、分批次部署的策略,以及如何通过小范围、自包含的变更来最小化风险。
一旦你准备并测试了想要进行的变更,就是时候将它们推出了。

但别着急,即使你已经在自己的电脑或测试电脑上测试过变更,并且一切正常,这也不意味着该变更在所有运行中的生产机器上都能正确工作。
首先,什么是“生产环境”?在基础设施的语境中,生产环境是指执行服务并将其提供给用户的基础设施部分。
如果你托管一个网站,向用户提供网站内容的服务器就是生产服务器。在你的公司内部,验证用户密码的服务器就是生产认证服务器。你应该明白了。
对生产服务器进行变更可能很棘手,因为如果出现问题,服务可能会中断。那么,我们如何才能安全地推出变更呢?
关键:使用测试环境 🔬
关键是要始终先在测试环境中运行变更。测试环境应该有一台或多台机器,其配置与生产环境完全相同,但这些机器实际上并不为服务的任何用户提供服务。
这样,如果在部署变更时出现问题,你可以在没有任何实际用户看到的情况下修复它。
正如我们在之前的视频中简要提到的,Puppet 内置了环境功能。每个环境都有自己的目录,包含自己的一组清单和模块。Puppet 环境让我们可以根据代理运行的环境,完全隔离它们看到的配置。
这不仅仅是决定哪些节点安装哪些模块,还包括模块的全部内容。例如,我们可以利用这一点,在测试环境的机器上尝试一个全新的 Apache 模块版本,同时在生产环境中仍使用旧版本。
你可以根据需要定义任意多的环境。例如,你可以有一个开发环境,供 IT 专家在变更进入测试环境之前尝试新的 Puppet 规则。或者,假设你正在为系统开发一个非常棘手的新功能,并且不知道它何时能准备好,你可以有一个专门测试该特定功能的环境。
分批次部署变更 📦
现在,假设你有一批准备推出的变更。你通常会先将它们推送到测试环境中的机器,并检查一切是否正常。这可以包括手动验证和自动检查。
好的,假设变更在测试环境中运行良好。你如何将它们部署到机群中的其他机器上呢?
你可能想直接将变更应用到所有机器上,然后完事。但将变更同时推送到每台机器通常不是一个好主意。我们总是有可能在准备变更时遗漏了一些特殊情况,而这些情况并不在我们的测试环境中,结果可能导致我们一半的机群突然离线——糟糕。
因此,我们通常不会将变更推送到所有节点,而是分批进行。
根据你的机群组织方式,有多种方法可以实现这一点。你可以让一些机器带有一个标记它们为“早期采用者”或“金丝雀”的事实。就像煤矿工人用来检测矿井中有毒气体的金丝雀一样,这些节点可以在问题波及到其他计算机之前检测到潜在问题。
所以,你可以在某一天将变更推送到金丝雀节点,检查一切是否正常,然后在第二天将它们部署到机群的其余部分。这样,如果变更中存在测试时未发现的问题,只有一部分用户可能会看到它。一旦你收到问题通知,就可以回滚变更,避免它影响到机群的其他部分。
变更的最佳实践:小而独立 🧩
到目前为止,我们一直在讨论变更,但没有详细说明这些变更具体是什么。一个好的做法是让这些变更小而自包含。这样,如果出现问题,找出问题所在会容易得多。
想象一下,你试图将积累了六个月的变更推送到你的计算机机群。当你将这些变更推送到测试环境的机器时,你发现它们完全停止响应了。现在,你需要梳理所有捆绑在一起的变更,试图找出是哪一个导致了问题。
相反,你可以争取每一到两周推出一次变更。这意味着每当检测到问题时,只需要检查一个简短的变更列表就能找出罪魁祸首。
当然,关于安全测试和发布变更,还有很多可以说的内容。但你不需要一开始就实施所有最佳实践。你可以从小处着手,随着进展不断改进。随着你的清单变得越来越复杂,你会希望改进所有部分的自动化测试。随着你用配置管理系统管理的机群规模增长,你会希望扩大测试环境的规模,将一些节点转为金丝雀节点,等等。
总结 📝
在本节课中,我们一起学习了安全部署变更的核心策略。我们了解到,使用与生产环境一致的测试环境是验证变更的第一步。接着,我们探讨了通过分批次部署(例如使用金丝雀节点)来降低风险。最后,我们强调了保持变更小而独立的重要性,这有助于在出现问题时快速定位和修复。
记住,安全部署是一个迭代过程。从基础开始,随着你的系统和团队经验的增长,逐步引入更复杂的测试和部署实践。
160:入门监控 📊

在本节课中,我们将要学习监控的基本概念、重要性以及如何为云服务设置有效的监控。我们将探讨需要收集哪些指标、如何收集它们,以及如何利用这些数据确保服务稳定运行。
正如我们在之前的视频中提到,一旦我们的服务在云端运行,我们需要确保服务不仅持续运行,而且行为符合预期,能够快速可靠地返回正确结果。确保这一切的关键在于建立良好的监控和告警规则。
在接下来的几节中,我们将概述监控和告警的概念与技术,并进行实际演示。让我们开始吧。
什么是监控?👀
要了解我们的服务表现如何,我们需要对其进行监控。监控让我们能够查看系统的历史和当前状态。
我们如何知道状态是什么?我们将检查一系列不同的指标。这些指标告诉我们服务是否按预期运行。虽然有些指标是通用的,例如实例使用了多少内存,但其他指标则特定于我们想要监控的服务。
关键监控指标 📈
以下是您需要关注的一些关键指标类型:
- 通用指标:例如 CPU 使用率、内存使用量、磁盘 I/O。这些指标反映了基础设施的健康状况。
- 服务特定指标:这些指标根据您的服务类型而有所不同。例如:
- 对于网站,您需要检查 HTTP 响应码。例如,
404表示页面未找到,5xx范围(如501或503)的代码表示服务器端在处理请求时出错。 - 对于电子商务网站,您会关心成功完成的交易数量与失败的交易数量。
- 对于邮件服务器,您需要知道发送了多少封邮件以及有多少封邮件被卡住。
- 对于网站,您需要检查 HTTP 响应码。例如,
您需要根据想要监控的服务,确定所需的指标。
收集与存储指标 💾
上一节我们介绍了需要关注的指标类型,本节中我们来看看如何收集和处理这些指标。
一旦我们决定了要关心哪些指标,我们该如何处理它们?我们通常会将它们存储在监控系统中。
市场上有许多不同的监控系统。一些系统,如 AWS CloudWatch、Google Cloud Monitoring 或 Azure Monitor,由云提供商直接提供。其他系统,如 Prometheus、Datadog 或 Nagios,可以跨供应商使用。
将指标获取到监控系统有两种主要方式:
- 拉取模型:监控基础设施定期查询我们的服务以获取指标。Prometheus 是这种模型的典型代表。
- 推送模型:我们的服务需要定期连接到监控系统以发送指标。许多基于代理的监控工具采用这种方式。
使用仪表板分析数据 📊
无论我们如何将指标输入系统,我们都可以基于收集的数据创建仪表板。
这些仪表板显示指标随时间的变化趋势。我们可以查看单个特定指标的历史记录,将当前状态与上周或上个月进行比较。或者,我们可以同时查看两个或多个指标的进展,以检查一个指标的变化如何影响另一个指标。
例如,假设现在是周一早上,您注意到您的服务接收的流量比平时少得多。您可以查看过去几周的数据,看看周一早上是否总是流量较少,或者是否有某些故障导致您的服务无响应。
又或者,如果您发现过去几天实例使用的内存一直在增加,您可以检查这种增长是否伴随着另一个指标(如接收的请求数量或传输的数据量)的类似增加。这可以帮助您判断是存在需要修复的内存泄漏,还是仅仅是服务受欢迎程度增长的预期结果。
专业提示:您应该只存储您关心的指标,因为在系统中存储所有这些指标会占用空间,而存储空间是需要成本的。
白盒监控 vs. 黑盒监控 ⚫️⚪️
当我们从系统内部收集指标时,例如服务当前使用的存储空间量或处理请求所需的时间,这被称为白盒监控。白盒监控从内部检查系统的行为。我们知道我们想要跟踪的信息,并且我们负责使其可被跟踪。
例如,如果我们想跟踪对数据库进行了多少次查询,我们可能需要添加一个变量来进行计数。
# 示例:简单的白盒监控计数器
query_count = 0
def execute_query(sql):
global query_count
query_count += 1 # 内部计数
# ... 执行数据库查询 ...
相反,黑盒监控从外部检查系统的行为。这通常通过向服务发出请求,然后检查实际响应是否与预期响应匹配来完成。我们可以用它来进行非常简单的检查,以了解服务是否在线,并验证服务是否可以从您的网络外部响应;或者我们可以用它来查看世界不同地区的客户端从系统获得响应需要多长时间。
设置告警规则 🚨

好了,监控真的很酷,但谁愿意整天盯着仪表板,试图弄清楚是否出了问题?幸运的是,我们不必这样做。相反,我们可以设置告警规则,在出现问题时通知我们。这是确保系统可靠性的关键部分,我们将在下一节课中学习如何操作。
本节课总结:在本节课中,我们一起学习了监控的基础知识。我们了解了为什么监控对云服务至关重要,探讨了需要收集的通用和特定服务指标,介绍了指标收集的拉取和推送模型,并学习了如何使用仪表板分析数据趋势。最后,我们区分了从内部检查系统的白盒监控和从外部测试服务的黑盒监控。下一节,我们将深入探讨如何设置告警规则,让系统在出现问题时主动通知我们。
161:谷歌《用Python进行IT自动化办公》第44课 - 出现问题时获取警报 🚨

概述
在本节课中,我们将学习如何在IT服务出现问题时自动获取警报。我们将探讨警报系统的基本原理、如何区分不同紧急程度的警报,以及如何设置可操作的警报,以确保系统问题能被及时发现和处理,而无需管理员时刻监控。
我们对现代IT服务抱有很高的期望。我们期望它们能够24/7不间断运行。我们希望随时随地都能完成工作,因此我们的服务需要不分昼夜、不论工作日或假期都能正常响应。
然而,即使服务是24/7运行的,系统管理员也不可能一直守在系统前。相反,我们会设置服务使其在无人值守的情况下工作,并在问题发生时进行处理。
为了实现这一点,我们需要检测这些问题,以便能尽快处理。如果没有自动化的警报方式,你可能只有在接到沮丧用户的电话,告知你服务已中断时,才会发现问题。
这并不理想。更好的做法是创建自动化程序来检查系统的健康状况,并在情况不符合预期时通知你。这可以让你提前获知问题,有时甚至在用户完全注意到问题之前。
那么,我们该如何做呢?
基础警报方法
最基础的方法是定期运行一个任务来检查系统健康状况,如果系统不健康就发送邮件。在Linux系统上,我们可以使用Cron这个工具来调度周期性任务。我们将其与一个简单的Python脚本配对,该脚本检查服务并发送必要的邮件。
这是一个极度简化的警报系统版本,但它与所有警报系统共享相同的原理,无论它们多么复杂和先进。我们都希望定期检查服务的状态,并在出现问题时发出警报。
上一节我们介绍了基础警报方法,本节中我们来看看如何利用监控系统来提升警报的智能化。
利用监控系统发出警报
当你使用我们上一个视频中描述的那种监控系统时,你收集的指标代表了服务的状态。你无需定期运行一个连接服务并检查其是否响应的脚本,而是可以配置系统定期评估这些指标,并根据某些条件决定是否应触发警报。
触发警报意味着某些东西出了问题,需要人工介入响应。
以下是你可以设置的一些警报条件示例:
- 如果应用程序使用的内存超过 10 GB。
- 如果它响应了过多的 500 错误。
- 如果等待处理的请求队列变得过长。
当然,并非所有警报都具有相同的紧急性。我们通常将有用的警报分为两组:需要立即关注的和在不久的将来需要关注的。如果一个警报不需要关注,那么它根本不应该被发送,它只是噪音。
区分警报的紧急程度
如果您的Web服务对50%的请求都响应错误,您应该立即查看发生了什么。即使这意味着要在半夜醒来处理问题,您也肯定希望尽快修复这种关键问题。
另一方面,如果问题是附加存储已使用80%,您需要决定是增加磁盘容量还是清理一些存储数据。但这并不十分紧急,所以不应让它影响您的睡眠。
由于这两种警报类型不同,我们通常将系统配置为以两种不同的方式触发警报。
上一节我们区分了警报的紧急程度,本节中我们来看看它们的具体实现形式。
警报的实现形式
需要立即关注的警报称为传呼,这个名称来源于一种叫做寻呼机的设备。在手机普及之前,寻呼机是接收紧急消息的首选设备,并且世界上一些地方仍在使用它们。如今,大多数人通过其他形式接收传呼,如短信、自动电话、电子邮件或移动应用程序,但我们仍称之为传呼。
另一方面,非紧急警报通常被配置为创建故障单或工单,供IT专家在工作日处理。它们也可以配置为发送电子邮件到特定的邮件列表,或发送消息到一个聊天频道,供维护服务的人员查看。
需要强调的一点是,所有警报都应该是可操作的。如果您收到一个故障单或传呼,但您无事可做,那么这个警报就是不可操作的,应该被修改或根本不应该存在,否则它只是噪音。
设计可操作的警报
假设您试图检查服务的数据库后端是否响应。如果您通过创建一个查询大表中所有行的请求来实现,您的请求有时可能会超时并触发警报。这将是一个噪音警报,并非真正可操作。您需要调整查询以使检查变得有用。

又比如,您运行一个Cron作业,每10分钟将文件从一个位置复制到另一个位置。您希望检查此作业是否成功运行,因此配置系统在作业失败时向您发出警报。
投入生产后,您发现有一堆不重要的原因可能导致此作业暂时失败。可能是目标存储太忙,因此作业有时会超时。也可能是作业开始时源服务器正在重启,导致作业无法连接。
无论原因如何,每当您去检查导致作业失败的原因时,您都会发现后续的运行已经成功,您无事可做。您需要重新思考这个问题并调整您的警报。
由于任务运行频繁,您不关心它失败一两次。您可以将系统更改为仅当作业连续失败三次时才触发警报。这样,当您收到故障单时,意味着它持续失败,您才真正需要采取行动来修复它。
配置警报的考量
所有这些配置和调整似乎需要大量工作。您需要考虑关心哪些指标,配置监控系统来存储它们,然后配置警报系统在情况不符合预期时触发警报。
另一方面,一旦您设置了系统在需要时触发可操作的警报,您就可以高枕无忧。如果没有警报触发,您就知道服务运行正常。这使您可以专注于其他任务,而无需担心。
为了设置好的警报,我们需要弄清楚哪些情况应该触发传呼,哪些应该创建故障单,以及哪些我们根本不在乎。这些决定并不总是容易的,可能需要与团队其他成员进行一些讨论,但这有助于确保您只将时间花在真正重要的事情上。
总结
本节课中,我们一起学习了IT自动化中警报系统的核心概念。我们了解了从基础脚本到利用监控指标触发警报的演进,学会了根据紧急程度将警报分为传呼和故障单,并强调了设计可操作警报以避免噪音的重要性。通过合理配置,警报系统能让我们在系统出现问题时及时获知,同时在一切正常时安心专注于其他工作。
162:服务级目标 (SLO) 🎯

在本节课中,我们将学习服务级目标(SLO)的概念,了解如何为IT服务设定可衡量的性能目标,并探讨如何利用这些目标来管理服务期望和指导运维工作。
概述
我们都知道,某些IT系统比其他系统更为关键。例如,一款一年未打开的游戏无法运行,其重要性远低于银行网站宕机导致无法转账的情况。有时,部分基础设施出现故障,整个系统仍能以性能降级的状态继续工作。例如,即使加速网络应用的缓存服务器宕机,应用本身仍可运行,只是速度变慢。
没有任何系统能做到100%可用,这并不现实。但根据服务的关键程度,可以为其设定不同的服务级目标(SLO)。SLO是为特定服务预先建立的性能目标。
什么是服务级目标 (SLO)?
设定这些目标有助于管理服务用户的期望,同时也为负责维护服务运行的团队提供了工作指导。SLO必须是可衡量的,这意味着需要有相应的指标来追踪服务性能,并检查其是否达到了目标。
许多SLO以服务按预期运行的时间比例来表示。例如,一项服务可能承诺其可用性达到99%。
理解可用性与“9”的个数
在处理指标和可用性时,我们需要进行一些简单的计算来理解这些数字的实际含义。如果我们的服务SLO是99%的可用性,那就意味着其停机时间最多可达1%。如果以一年为周期衡量,该服务在一年中总共可以停机3.65天,同时仍保持99%的可用性。
这类可用性目标通常以其包含的“9”的个数来命名。我们99%的例子就是一个“2个9”的服务。99.9%的可用性是“3个9”服务,99.999%的可用性则是“5个9”服务。“5个9”的服务承诺一年内的总停机时间不超过五分钟。
“5个9”代表了极高的可用性,通常只保留给最关键的系 统。对于许多IT系统而言,“3个9”的服务,即每年最多停机八小时,就已经足够了。
为何不将所有服务都设为“5个9”?
你可能会想,为什么不把所有服务都做成“5个9”呢?这是一个很好的问题。
答案在于成本和必要性。维持“5个9”的服务非常昂贵,而且通常并非必需。如果你的服务不是超级关键,偶尔短暂停机是可以接受的,那么拥有“2个9”或“3个9”的可用性可能就足够了。一个小团队就可以维持这样的服务运行。而“5个9”的服务通常需要一个庞大得多的工程师团队来维护。
服务级目标 (SLO) 与服务级协议 (SLA)
任何服务都可以有一系列不同的服务级目标,用以告知用户对其的期望。对于一些我们付费使用的服务,它们还会有更严格的承诺,即服务级协议(SLA)。
服务级协议是服务提供商和客户之间的一份承诺。违反这些承诺可能会产生严重后果。而服务级目标则更像是一个软性目标,是维护团队努力的方向,但在某些情况下可能无法达成。
SLO/SLA 的实践应用
正如我们所指出的,拥有明确的SLO或SLA,无论对服务用户还是维护团队都很有用。
以下是其具体应用场景:
- 对于服务使用者:如果你正在使用云服务,可以根据提供商发布的SLA来决定你将多少基础设施托付给它。
- 对于服务维护者:如果你是维护服务团队的一员,则可以利用服务的SLO和SLA来决定创建哪些警报以及警报的紧急程度。
例如,假设你有一项SLO规定:至少90%的请求应在5秒内返回。为了了解服务是否运行正常,你需要测量总请求中有多少是在这5秒内返回的,并且你希望这个数字始终高于90%。

因此,你可能会设置一个非寻呼警报,当在5秒内返回的请求少于95%时通知你;同时设置一个寻呼警报,当该比例低于90%时立即通知你。
如果你负责一个网站,通常会通过测量返回500状态码的响应率来检查服务是否正常。如果你的SLO是99%的成功请求率,那么当错误率超过0.5%时可以设置非寻呼警报,当错误率达到1%时则触发寻呼警报。
变更管理与错误预算
在之前的视频中我们提到,服务中断通常是由于某些变更引起的。在考察服务为何偏离SLO时,情况也往往如此。如果你的服务原本运行良好并满足所有SLO,但之后开始出现问题,这很可能是由最近的变更导致的。
这就是为什么一些团队使用“错误预算”的概念来管理他们的服务。假设你运行一项“3个9”可用性的服务,这意味着该服务每月可以停机43分钟。这就是你的错误预算——你可以容忍最多43分钟的停机时间。
因此,你需要跟踪服务在一个月内的总停机时间。如果开始接近那43分钟,你可能会决定停止推送任何新功能,并专注于解决导致停机的根本问题。
给初学者的建议
关于“几个9”、可用性和停机时间的所有讨论,如果你是第一次接触,可能会感到头晕,这完全正常。如果是首次为你的服务设定目标,请从设定可衡量且可实现的目标开始。
跟踪服务一段时间内的行为表现,观察是什么导致服务偏离目标。一旦你对整个服务的行为有了更清晰的了解,就可以设定更具挑战性的目标。
总结
本节课我们一起学习了服务级目标(SLO)的核心概念。我们了解了SLO是可衡量的性能目标,用于管理期望和指导运维;学会了如何通过“几个9”来理解可用性承诺,并明白了并非所有服务都需要追求最高的“5个9”可用性。我们还区分了SLO(内部目标)和SLA(对外承诺),探讨了如何根据SLO设置监控警报,并引入了“错误预算”这一管理变更和停机风险的工具。
接下来,我们将回到运行在云端的虚拟机,演示如何使用云服务提供商提供的工具来监控它们。
163:GCP基础监控 📊

在本节课中,我们将学习如何在Google Cloud Platform中监控虚拟机实例,并基于收集到的指标创建警报。我们将使用GCP内置的监控工具Stackdriver,了解其核心功能,并实践配置一个CPU使用率警报。
概述
上一节我们介绍了如何在GCP控制台创建虚拟机。本节中,我们来看看如何利用云服务商提供的工具来监控这些持续运行的虚拟机,并基于监控指标创建警报。
我们将使用名为Stackdriver的监控工具,它是GCP整体服务的一部分。首次激活此系统后,需要一段时间才能开始从所有机器收集指标,因此我们已提前将其激活。
访问监控控制台
当我们首次打开监控控制台时,会看到系统的概览。目前,这个面板看起来很空,但我们可以配置此仪表板,以显示我们认为最有用的图表。
让我们进入实例仪表板。
在这里,我们看到实例列表。我们可以点击每个实例,查看Stackdriver收集到的关于它们的监控信息。
查看核心监控指标
监控系统通过三个基本指标,为我们提供了每个实例的简明概览:
- CPU使用率
- 磁盘I/O
- 网络流量
根据我们想在虚拟机上运行的服务,我们可以自定义这些仪表板以显示不同的指标。
如果内置的指标不够用,你还可以创建自己的指标并将其添加到这里。
配置警报策略
现在,我们想了解如何设置警报,以便在系统行为异常时通知我们。
为此,我们将创建一个新的警报策略。
要设置新警报,我们必须配置触发警报的条件。完成此操作后,我们还可以配置希望如何收到问题通知,并添加希望通知中包含的任何文档。
配置触发条件
如前所述,警报条件与特定指标相关。我们希望当某个指标表明实例出现问题时收到通知。
在本示例中,我们将配置一个警报,当某个实例的CPU使用率超过90%时触发。
以下是配置步骤:
- 首先,我们选择要监控GCE VM实例,即我们当前正在运行的实例。
- 然后,选择CPU使用率指标。
选择指标后,我们会看到所有当前运行实例的收集值图表。
我们可以选择为此条件的数据添加额外的过滤器和分组。例如,我们可以选择仅查看某些实例,通过它们的区域、地区或名称进行筛选。
如果你想为生产环境使用的实例和测试或开发环境使用的实例设置单独的警报,这会很有用。
此外,我们还可以为数据选择一个聚合器。当你收集的指标关乎整个系统而不仅仅是单个实例时,这些聚合器非常有用。例如,如果你正在检查系统生成的错误响应数量,你会希望汇总所有实例的错误。
根据我们如何过滤、分组和聚合数据,最终会得到一系列不同的时间序列。我们将使用这些值来决定是否应触发警报。
设置触发阈值
下一步是选择有多少个不同的时间序列需要违反条件才能触发警报。我们可以选择当一个、某些或所有不同的时间序列违反条件时触发警报。
对于此示例,我们将配置警报在任何实例的CPU使用率超过90%时触发,因此我们选择任何时间序列违反。
现在,我们将设置触发条件:如果值在一分钟内持续高于90%,则触发警报。
配置通知方式
好的,我们已经设置了条件。现在,我们可以选择在警报触发时希望如何接收通知。
目前,我们可以使用的唯一通知类型是电子邮件。要使用其他可用的渠道类型,我们需要在个人资料中配置它们。
对于此示例,电子邮件即可。刚开始使用警报时,使用电子邮件可能没问题,但最终你会希望配置其他方法。
我们已经配置了警报以发送电子邮件。
添加警报文档
现在,我们可以为警报添加额外的文档。
此文档旨在帮助响应警报的人员理解他们需要做什么来解决问题。当团队中有许多人一起工作,且并非每个人都了解所有事情时,在此处包含良好的文档可能非常重要。
包含良好文档的警报更容易处理,并有助于更快地使服务恢复健康状态。
对于我们的示例,我们将添加一条消息,说明处理此警报的人员应使用 top 命令检查实例。
保存警报策略
最后,我们需要为警报策略命名,我们将其称为 CPU,然后保存它。
太好了!现在我们已经设置好了警报。我们可以放松一下,知道如果出现问题,我们将是第一个知道的。

演示警报触发

在本演示的最后部分,我们想展示警报触发时会发生什么。
为此,我们将在其中一个实例中启动一个进程,通过创建一个无限循环来占用所有可用的CPU。
我们将回到主控制台,通过SSH连接到名为 linux-instance 的虚拟机,然后创建一个永不结束的 while 循环。
while true; do echo "Looping..."; done
现在,我们的循环正在运行并占用所有可用CPU。我们可以通过运行 top 命令来检查这一点,该命令会显示CPU使用情况。
我们看到有一个 bash 命令几乎占用了100%的可用CPU。我们的实验正在运行。
理解警报延迟
请记住,我们之前说过,我们希望条件持续一分钟才触发警报,所以它不会立即触发。在处理警报时,通常使用1、5甚至10分钟的时间窗口。
我们不希望因为一个只持续几秒钟然后消失的小峰值而收到警报。我们希望在实际需要关注的问题出现时收到警报。
我们选择的时间窗口大小取决于我们正在检查的指标、预期峰值的持续时间以及许多其他因素。在测试警报时,调整我们希望条件持续为真的时间是很正常的。
如果你经常收到关于那些无需你干预即可自行消失的条件的通知,你可能会选择将时间窗口设置得更大。反之,如果你在需要关注的条件出现时收到通知太晚,你可能会选择将时间窗口设置得更小。
查看触发的警报
好的,我们已经让足够的时间过去了。让我们看看我们的警报怎么样了。
我们看到有一个未解决的事件,这是一种对问题进行分组的方式。警报摘要为我们提供了大量关于当前情况的信息。我们可以点击 CPU 链接以获取更多信息。
此页面显示了触发此事件警报的指标。它显示了触发警报的阈值以及指标的当前值。它还显示了我们输入的文档,并允许我们创建注释。我们可以使用这些注释来跟踪在事件期间所做的工作。

解决问题并验证恢复
现在,让我们停止占用实例所有CPU的进程。之前的 top 进程仍在运行。
让我们按 Q 退出。现在,无限循环正在我们控制台的后台运行。
我们可以通过输入 fg 使其在前台运行,然后按 Ctrl+C 取消它。
好的,现在我们已经停止了进程。让我们用 top 检查一下,确认它不再占用所有CPU。很好,bash 进程不再占用所有CPU时间了。
再过一分钟,我们之前看到的警报将停止触发。
总结
本节课中,我们一起学习了如何在云中监控一批运行的实例。我们基于指标创建了一个警报,并验证了警报的触发。当然,使用这些工具我们还可以做更多事情。我们将在接下来的阅读材料中为你提供更多信息的指引。之后,还有一个小测验来检查你是否理解了所有内容。
164:无法亲临现场时该怎么办 🖥️🔧

在本节课中,我们将学习当无法直接接触物理服务器时,如何对运行在云端的虚拟机进行故障排查。我们将探讨云环境故障排查的特殊性、可用的诊断策略,以及如何提前准备以高效应对问题。
习惯了在物理计算机上排查问题后,要适应修复运行在云端的虚拟机问题,可能需要一些思维方式的转变。
有些事情你无法做到,例如无法直接走到服务器前检查问题所在。但另一方面,排查云端虚拟机问题时,有些事情会简单得多,例如增加更多内存或将机器迁移到不同的数据中心。
假设在最近一次升级后,你的一批云虚拟机停止了启动。出现了问题,但你并不确切知道是什么问题。你无法连接到实例或将其启动到救援模式,那么你能做什么呢?
如果你遵循我们讨论过的“基础设施即代码”实践,会有多种选择。
以下是你可以采取的一些步骤:
- 你可以部署使用系统先前版本的新虚拟机。这将帮助我们尽快恢复到健康状态。
- 在此基础上,你还需要理解问题及其修复方法。为此,你可以为其中一台故障虚拟机的磁盘镜像创建快照,然后将该磁盘镜像挂载到一台健康的机器上。


通过这种方式,你可以分析镜像内容,找出导致故障的原因。
要确定系统的哪个部分导致了故障并不总是容易的,尤其是在系统复杂、包含许多相互交互的不同服务时。
如果你试图找出导致你的复杂服务返回大量500错误的原因,你需要逐个检查不同的部分,直到找到罪魁祸首。
以下是一些隔离测试的思路:
- 如果在没有任何负载均衡器或缓存服务器介入的情况下,在测试虚拟机中运行该服务,问题是否会发生?
- 如果在你的工作站本地运行该服务,问题是否会发生?
你越能隔离出故障行为,修复起来就越容易。
你应该记住,问题总会发生。花些时间为此做好准备是明智的。
设置测试环境可能需要时间和精力,因此最好在实际问题发生之前提前完成。这样,当用户抱怨系统宕机时,你就不必在压力下匆忙进行。
假设你正在使用一个只能从你的云网络内部访问的数据库服务。这意味着你无法从外部直接与其交互,只能通过云基础设施内的实例进行。
如果你的服务开始出现问题,你可能希望直接检查数据库的响应,而不是通过任何其他后端服务器。
为此,你需要在网络中有一台调试机器,并且需要使用工具直接与数据库交互。同样,设置机器和学习使用工具需要时间,所以要未雨绸缪,提前完成。
你可能还记得在故障排除和调试课程中,理解日志对于解决IT问题至关重要。
当你在云端运行服务时,你需要了解在哪里可以找到云服务商保存的日志,以及哪些日志中提供了哪些信息。一些云提供商提供集中式日志解决方案,将你所有的日志收集到一个地方。
你可以将所有通知、信息、警告和错误消息发送到日志收集点。然后,当你尝试调试问题时,可以轻松查看错误发生时的一切情况。
接下来,我们将探讨更多可用于找出云端问题根本原因的排查策略。
本节课中,我们一起学习了在无法物理接触服务器时,对云端虚拟机进行故障排查的方法。核心要点包括:利用“基础设施即代码”快速回滚、通过磁盘快照分析故障、通过隔离测试定位问题组件,以及提前准备测试环境和调试工具的重要性。理解并善用云服务商提供的集中日志系统,也是高效解决问题的关键。
165:确定故障来源 🔍

在本节课中,我们将学习如何在云服务环境中确定故障的来源。当服务出现问题时,我们需要判断问题是出在我们自己的代码上,还是云服务提供商的基础设施上。我们将探讨几种实用的排查方法。
当我们在云端托管服务时,我们需要放弃对所用基础设施的部分控制权。
这在尝试找出服务问题的根本原因时可能尤为明显,因为我们不知道故障是由我们自身的错误还是由提供商方面的错误引起的。
那么在这种情况下我们能做什么呢?提供商方面的问题往往局限于特定的地理区域。
如果你看到奇怪的问题,并且完全不知道可能发生了什么,你可以尝试在另一个区域启动你的服务,并检查故障是否也在那里发生。
如果它在其他区域运行正常,那么很可能是云基础设施存在问题,你应该向你的提供商提出这个问题。
如果它在其他区域也失败,那么很可能是你的系统存在问题。
类似地,如果你看到与资源使用相关的问题,你可以尝试在不同的机器类型上运行相同的系统,并检查其在那里的行为表现。
例如,如果你的服务处理传入请求花费的时间太长,通过将你的服务切换到更强大的机器上,你可能会提高整体性能。
我们多次提到的另一个选项是对最近发生变化的组件进行回滚。
将你所有的基础设施作为代码存储在版本控制系统中,将使你能够访问系统中每个组件的变化历史。
在设置服务时,你应该确保能够部署和回滚每个单独的组件。
想象一下,你收到一个警报,说你应用程序中的Web服务器使用的内存比以前多得多。
你不知道原因,但你知道几天前部署了一个新版本。通过回滚到上一个版本,你可以验证该更改是否是问题的根源。
如果回滚后服务器运行正常,你可以调查具体的更改,并尝试找出它们为何使用如此多内存的原因。
如果回滚后服务器仍然使用大量内存,这意味着还有其他问题。

在之前的视频中,我们简要提到了在云端运行服务时一个流行的选项:容器。
容器是打包的应用程序,与其库和依赖项一起交付。
每个应用程序都在一个单独的容器中执行,完全独立于在同一台机器上运行的任何其他应用程序。
容器化应用程序的一个巧妙特性是,你可以将同一个容器部署到本地工作站、本地运行的服务器或不同供应商提供的云基础设施上。
这在试图理解故障是在代码中还是在基础设施中时,会非常有帮助。你只需将容器部署到其他地方,并检查其行为是否相同。
使用容器时,典型的架构是拥有许多处理服务不同部分的小型容器。
这意味着整个系统可能变得非常复杂,当某个部分出现故障时,可能很难确定问题来自哪里。
在容器世界中解决问题的关键是确保你拥有来自系统所有部分的良好日志,并且你可以在必要时为每个应用程序启动测试实例以进行尝试。
接下来,我们将讨论一些可以在云中使用的工具,以从服务中断中恢复。
166:从故障中恢复 🛠️
课程编号:Google IT Automation with Python - P166

在本节课中,我们将要学习如何为复杂的IT系统制定和实施有效的故障恢复策略,确保服务在出现问题时能够快速恢复运行。
当处理一个复杂系统时,它可能以多种方式发生故障。如果我们希望服务可靠,就需要确保在发生问题时能尽快使其恢复运行。这要求我们拥有良好的备份和一份记录详尽的恢复计划。
上一节我们介绍了故障恢复的重要性,本节中我们来看看“备份”具体包含哪些方面。这里的“备份”不仅指数据的副本。
它还包括基础设施各个部分的备份,例如运行服务的实例以及用于连接服务的网络。服务所处理数据的备份极其重要。
如果你运营的服务存储任何类型的数据,实施自动备份并定期通过执行恢复操作来检查这些备份是否正常工作至关重要。这有助于确保你备份了正确的数据,并且拥有在故障时恢复数据的、记录完善的流程。
那么基础设施的其余部分呢?如果你将所有基础设施都存储为代码,那么你已经拥有了基础设施应然状态的备份。但如果你的服务因某些原因宕机,从头开始部署所有基础设施可能需要一段时间。
因此,许多团队会保持其服务的备用或次要实例持续运行。这样,如果主实例出现问题,他们可以简单地将负载均衡器或DNS条目指向次要实例,从而将对服务的中断降至最低。
或者,常见的做法是确保在任何时候都有足够多的服务器在运行,这样即使其中一台宕机,其他服务器仍能处理流量。在更大规模上,可以让服务在全球不同的数据中心运行,这样即使一个数据中心出现问题,服务仍可由其他数据中心运行的实例提供。
如果你在本地运行服务,可能需要两条不同的互联网连接。这样,如果一家互联网服务提供商(ISP)的连接中断,你仍然可以通过另一家连接互联网。当你在云上运行时,通常可以依赖云提供商具备足够的网络冗余。
但如果你真的非常关心服务在任何情况下的持续运行,可能需要在两家不同的云供应商上运行你的服务。这样,如果一家提供商发生大规模中断,你仍然可以依赖另一家。
现在,想象你的服务在一个数据中心运行。不幸的是,该数据中心刚刚遭受自然灾害,你的所有实例都宕机了。你该怎么做?
第一步,深呼吸。不要惊慌。你需要从头开始恢复服务,将其部署到另一个数据中心,并从备份中获取所有数据。只要备份在其他数据中心可用,并且你的基础设施完全存储在版本控制系统中,这应该是可以实现的。
但弄清楚如何从头成功启动整个系统可能需要一段时间。因此,你不希望在灾难发生时仓促行事。相反,你应该拥有一份记录详尽的流程文档,解释所有需要采取的步骤。
并且,由于系统会随时间演进,你需要确保这份文档保持最新。实现这一点的一种方法是,偶尔模拟需要恢复服务的情况,按照文档记录的步骤操作,并检查是否有任何缺失或过时的内容。
系统终将发生故障,100%的可用性根本不是一个可实现的目标。但为故障做好准备,将使你能够快速恢复服务,并让你的用户满意。
接下来,我们收集了一些资源,为你提供更多关于如何调试云上问题以及为故障做好准备的信息。😡 在此之后,本课程的最后一个测验等待着你。
本节课中我们一起学习了如何通过全面的备份策略(包括数据、实例和网络)、保持备用实例运行、跨数据中心或云供应商部署服务来构建弹性,并强调了制定、测试和更新书面恢复计划的重要性,从而为系统故障做好准备并实现快速恢复。
167:什么是 DevOps?🤔

在本节课中,我们将要学习 DevOps 的概念。DevOps 是一种结合了软件开发(Dev)与 IT 运维(Ops)的工作方式,旨在缩短系统开发生命周期,并以高质量的软件实现持续交付。
概述
软件并非凭空产生。开发像 Microsoft Office 这样的应用程序,远不止是编写代码。它涉及一个复杂的协作过程,需要确保软件功能完善、运行稳定,并能与其他系统交互。这个从构思到交付的完整过程,被称为软件开发生命周期。
上一节我们介绍了软件开发的复杂性,本节中我们来看看 DevOps 如何优化这一过程。
什么是 DevOps?
DevOps 是 Development(开发)和 Operations(运维)的组合词。它描述了软件开发生命周期中,除了编写代码之外的各个步骤。
在过去,程序员完成软件开发后,IT 运维团队会创建测试参数、测试应用程序,确保其符合预期。之后,软件会被刻录到 CD、包装并部署到商店供用户购买。更新和新版本也需要经历同样的漫长流程。
然而,时代已经改变。现代的软件开发生命周期速度更快,持续时间更长。DevOps 实践正是为了适应这种变化。
DevOps 的核心方面
DevOps 方法论包含许多环节,其中两个重要方面是:持续集成 和 持续交付。正是这两个方面,使得 DevOps 成为高效 CI/CD 流水线的必要组成部分。
以下是这两个核心概念的详细说明:
持续集成 意味着程序员始终致力于改进应用程序,更新和优化被持续地集成到软件中。其核心思想是频繁地将代码变更合并到共享的主干分支,并通过自动化构建和测试来快速发现集成错误。
持续交付 意味着对软件的任何更改都会经过测试,一旦验证通过,就会立即部署给用户或服务器。这确保了软件可以随时可靠地发布。
DevOps 的价值
尽管 DevOps 实践相对较新,但 IT 部门和软件工程团队已将其视为其 CI/CD 流水线基础设施的必要组成部分。
对于像你这样的程序员而言,DevOps 领域也提供了额外的职业机会,因为许多 DevOps 工作都需要编程技能。
总结
本节课中我们一起学习了 DevOps 的关键要点。
我们首先定义了软件开发生命周期,它是一个包含各类人员、职责和技能的复杂过程。
接着,我们探讨了 DevOps,它描述了编码之外的软件开发生命周期步骤。
DevOps 的两个重要方面是:
- 持续集成:不断向软件添加更新和改进。
- 持续交付:测试软件变更并在验证后立即部署。
DevOps 实践能带来更快的软件交付速度、更好的团队协作以及更高的系统可靠性。所有这些都是在快速演进的软件应用世界中取得成功的关键。😊

接下来,你可以阅读关于持续集成、交付和部署之间关系的更多内容。我们下次再见。
168:从编码到云端 🚀

概述
在本节课中,我们将学习软件从初始编码到部署至云端的完整过程。这是DevOps实践中的第一阶段,我们将了解程序员和DevOps团队在此过程中的职责与协作。
从编码到云端的四个步骤
上一节我们介绍了DevOps的基本概念。本节中,我们来看看软件从编码到部署上云的具体步骤。这个过程通常包含四个关键阶段。
以下是这四个步骤的详细说明:
- 程序员完成代码:程序员在本地机器上完成软件的初始编码工作。
- 容器化代码:通常由程序员负责,将代码及其依赖项打包成容器(如Docker镜像),并移除所有敏感信息。
- DevOps团队接收容器:DevOps团队通过工具(如Docker或Kubernetes)接收准备好的容器及其制品。
- 部署至云端:DevOps团队使用适当的安全凭证,将容器部署到云服务器上。
深入理解每个步骤
第一步:完成编码

程序员在本地开发环境中编写和测试软件代码。这是所有后续工作的起点。
第二步:容器化与清理

此步骤的核心是准备一个干净、可移植的部署单元。关键在于移除所有敏感信息,例如密码、API密钥等。这些信息不应以明文形式存在于源代码中。
云服务商通常提供密钥管理服务,允许应用程序在云端运行时以变量的形式动态调用这些敏感信息。此步骤可由内部程序员完成,或在接收外部程序员代码后由内部工程师处理。
容器化的一个简单示例是创建一个Dockerfile:
FROM python:3.9-slim
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "app.py"]
第三步:交接容器
DevOps团队从开发侧接收已容器化的应用包。这标志着职责从开发转向运维。
第四步:云端部署
DevOps团队利用自动化工具和云平台提供的安全认证,将容器部署到预定的云环境(如测试、生产环境)中。
总结
本节课中,我们一起学习了软件“从编码到云端”的完整流程。我们了解到,这个过程始于程序员完成编码,经过容器化清理后交由DevOps团队,最终安全地部署到云服务器。这个过程是CI/CD(持续集成/持续部署)管道的重要组成部分,尤其随着无服务器技术的发展,它正变得愈发高效。下一节,我们将探讨软件上云后的“从预发布到生产”阶段。
169:谷歌《用Python进行IT自动化办公》第52课 - 自动化 🚀

概述
在本节课中,我们将学习软件开发中自动化的核心概念及其在持续集成(CI)和持续部署/交付(CD)流程中的关键作用。我们将探讨自动化如何通过减少人工干预、提高效率与可靠性来优化现代软件开发生命周期。
在遥远的过去,软件开发生命周期在软件发布并交付给向最终用户销售的商店后就结束了。然后开发者会重新开始。作为最终用户,你不仅需要购买软件,还需要购买下一个版本,以及2.0版和后续版本。
如今,软件开发的速度持续加快。更新和新版本频繁出现,并且同样频繁地交付给那些始终连接到电脑、平板电脑和手机的用户。换句话说,软件开发生命周期并不会因为软件发布而停止。开发会通过持续集成(CI)和持续部署或交付(CD)不断向前推进,迭代进行。
这个CI/CD流水线始终有任务在进行中,每一行需要集成的新代码也必须经过审查、测试和推送。它必须在全球的云服务器上复制和部署。
正如你所知,软件开发生命周期是一个复杂且资源密集的过程。CI/CD只会加剧这一点。每一个独立的任务都需要代码、监控和处理,尤其是对于CI而言。
自动化是一种编程功能,它使得持续且通常是重复的例程能够规模化、捕获错误并减少人工干预的需求。它通过以编程方式专注于并运行每个任务来减少错误,有效地将它们与其他任务以及人为错误隔离开来。
自动化是DevOps的一种迭代方法。要实现自动化,你需要识别流程中的必要步骤,并创建具有正确函数和循环的代码,以便每次都以相同的方式运行它。任何CI自动化设置的核心部分都包括一个版本控制系统、构建服务器和自动化测试框架。
例如,假设你刚刚完成一个更新。你将新版本保存到构建服务器,服务器会识别出新版本。一旦识别,服务器会自动测试新代码的通信能力。
以下是持续集成自动化的七个关键概念。
首先介绍第一个概念。
移除手动步骤。这个概念如其字面意思,用自动化流程取代重复、耗时的人力工作。
接下来是第二个概念。
减少人工干预的需求。一旦设置完成,自动化任务或系统可以独立运行,从而解放IT人员去处理其他工作。
现在来看第三个概念。
一致性与可靠性。自动化消除了潜在的人为错误,因此任务能够一致地运行。
然后是第四个概念。
速度与效率。自动化消除了人类的局限性和干扰,因此任务运行得更快、更顺畅。
接着是第五个概念。
可扩展性。自动化任务更容易扩展,因为它们可以被复制。它们不需要重新配置来处理更大的负载。
第六个概念是集成能力。
自动化任务可以与现有的系统、工具和工作流程集成并连接。

最后是第七个概念。
错误处理。自动化系统可以被设计来处理错误和异常,要么通过启动恢复系统,要么通过警报记录并标记给人工操作员。
总结
本节课中,我们一起学习了自动化在软件开发中的核心作用。自动化是一种编程功能,它使得持续例程能够规模化、捕获错误并减少人工干预的需求。要实现自动化,你需要识别流程中的适当步骤,并创建具有正确函数和循环的代码,以便每次都以相同的方式运行。
自动化重复性任务并移除手动任务是实现持续集成的关键。它能提高代码质量、减少集成问题,并实现更快的发布周期。自动化是构建顺畅CI/CD流水线的必要条件,而CI/CD流水线是软件开发生命周期的重要组成部分。
请记住,自动化使得DevOps和程序员能够更轻松地协作创建软件和更新。在接下来的课程中,我们将继续深入探讨CI/CD。
170:持续交付与持续部署 🚀

在本节课中,我们将学习持续交付(Continuous Delivery)与持续部署(Continuous Deployment)这两个关键的软件开发实践。它们是CI/CD流程中的“CD”部分,旨在帮助团队更快速、更可靠地发布软件。
上一节我们介绍了持续集成(CI),它关注代码的自动构建与测试。本节中,我们来看看如何将经过测试的代码自动交付和部署给用户。
你可以将持续交付和持续部署比作一条汽车装配线。假设你是一家汽车制造商,希望尽可能快地生产新车。采用装配线方法,你可以将制造过程分解为一系列步骤,每个步骤由不同的工人完成,汽车从一个步骤传递到下一个步骤,直到完成。这使你能够快速高效地生产汽车。
类似地,持续交付和持续部署就是软件部署的“装配线”。
- 持续交付:自动化新软件的构建、测试和打包过程。这使得你可以快速高效地向客户发布新功能和错误修复。
- 持续部署:在持续交付的基础上更进一步,自动化将代码部署到生产环境的过程。这意味着新功能和错误修复一旦准备就绪,无需任何人工干预即可自动发布给客户。这同时减少了交付周期——即从开发完成到用户获得更新之间的时间。
总而言之,持续交付和持续部署是两种帮助团队更频繁、更可靠地交付软件的开发实践。
持续交付详解 🔄
持续交付是一种软件开发实践,它自动化软件的构建、测试和部署流程。对代码的每一次更改都会被构建、测试并打包以待部署。这意味着团队可以随时通过“按下一个按钮”来发布新版本的软件。
对于需要频繁且可靠发布软件的团队来说,持续交付是一个很好的选择。它是自动化基础设施和云资源的结合,确保更新能交付到相应用户的计算机和云服务器。
以下是持续交付的工作流程:
- 代码变更:开发人员对代码进行更改。
- 自动构建与测试:代码被自动构建和测试。
- 打包:如果代码通过所有测试,则被打包以备部署。
- 部署到预演环境:代码被部署到预演环境进行额外测试。
- 部署到生产环境:随后,代码被部署到生产环境。
- 用户可用:新版本的软件对用户可用。
这个过程对代码的每一次变更都会重复执行,从而实现“随时可发布”。
持续部署详解 ⚡
持续部署是持续交付的延伸,它在自动化软件构建、测试和打包过程的基础上,进一步自动化将代码部署到生产环境的过程。
这意味着团队可以在新功能或错误修复准备就绪后,立即将其发布给客户。对于在CI/CD方面经验丰富且需要非常频繁发布软件的团队,持续部署是一个理想的选择。
它可以帮助团队自动化整个发布流程,并尽可能快地将新功能和错误修复交付给用户或客户。

核心回顾与总结 📝
我们刚才涵盖了很多内容,让我们快速回顾一遍:
- 持续交付:定义为自动化交付流水线的实践,以确保软件可以随时发布到生产环境。它是每当代码变更时,发布软件更新、补丁和新版本的过程。
- 持续部署:在持续交付的基础上,取消了手动批准步骤。一旦变更通过了所有测试,它就会自动部署到生产环境。
持续交付和持续部署可以帮助软件开发团队更快速、更高效地向客户发布新功能和错误修复。这有助于团队保持领先地位,并更快地满足其用户和客户的需求。
在本节课中,我们一起学习了持续交付与持续部署的概念、工作流程以及它们为软件开发团队带来的价值。理解并实施这些实践,是构建高效、自动化软件交付能力的关键一步。
171:DevSecOps 🛡️

在本节课中,我们将学习什么是DevSecOps,以及它如何融入CI/CD流水线。我们将探讨其核心概念、实践方法以及它对Python程序员的影响。
什么是DevSecOps?

在之前的视频中,我们讨论了开发运维(DevOps),并将其描述为软件开发与IT部门协作的一种方式。
本节中,我们来看看另一个IT职能——DevSecOps,以及它如何融入CI/CD流水线。但首先,让我们定义DevSecOps是什么。
DevSecOps本质上是在软件开发生命周期中添加安全测试与保护。它结合了开发、安全和运维,并将其应用于CI/CD流水线,且从流程早期就开始介入。

为何使用DevSecOps?
简短的回答是:为了速度与效率。
更详细的解释是:CI/CD中的软件开发速度极快,必须依赖自动化来处理某些流程,例如安全测试和持续部署。这可能会引入安全风险。
过去,安全并不像今天这样被优先考虑。通常是先开发软件,之后再考虑安全。你可能已经从新闻中看到足够多的安全故障和漏洞事件,从而意识到——正如软件开发人员所认识到的那样——事后补救安全问题的效果并不理想。
DevSecOps的实践方法
DevSecOps是一种在软件开发流程早期引入安全措施和测试的流程。这通常被称为左移安全。
作为一名Python程序员,DevSecOps将影响你开发和交付代码的方式。除了在打包到Docker文件前清理代码外,DevSecOps还将大部分初始安全责任转移到了开发软件的编程人员身上。
以下是DevSecOps的一些最佳实践:
- 程序员负责安全:你需要负责编写安全的代码。
- 编写小型模块化代码:代码应更小、更模块化,并使用安全扫描工具(如静态应用程序安全测试,即SAST)进行测试。
- 隔离代码:代码本身也更隔离,这通过使用经过定期不安全代码测试的、受信任的、受支持的库,来限制对外部数据库和其他资源的访问。
- 测试应用程序:应用程序组装完成后,会使用动态应用程序安全测试(DAST)等扫描器进行测试。
实际上,DevSecOps从业者是网络安全倡导者,他们与程序员和IT专业人员作为一个大型协作团队合作,共同推动以安全为中心的文化。他们的角色是在流程中任何有需要的地方添加和测试安全性,并验证代码及最终应用程序在整个持续开发过程中是安全的。
核心目标与总结

DevSecOps的关键目的是将安全实践整合到软件开发生命周期的全过程,从协作环境中的代码开发,到自动化及持续的安全测试。它是速度与安全在软件开发生命周期旅程中交汇的地方。
总而言之,DevSecOps的核心是将安全“内建”于整个开发流程的代码中,以确保应用程序在设计上就是安全的。这在版本控制、更新以及整个持续集成和持续交付过程中也是如此。
本节课中,我们一起学习了DevSecOps的概念。记住,DevSecOps是在软件开发生命周期中添加安全测试和保护的过程,并且要从一开始就“内建”安全。
172:从暂存环境到生产环境 🚀

在本节课中,我们将学习软件开发生命周期中,代码在完成“从编码到云端”流程后,如何从暂存环境过渡到生产环境。我们将明确这两个核心环境的概念、区别以及整个部署流程。
概述
上一节我们介绍了“从编码到云端”的流程,它涵盖了软件开发的初始阶段,包括程序员编写代码、移除敏感信息以及为DevOps团队将代码容器化以便交付到云端。
本节中,我们来看看后续的关键步骤:从暂存环境到生产环境。这是确保软件安全、稳定地交付给最终用户的重要环节。
核心概念定义
首先,我们需要明确两个关键术语:暂存和生产。
暂存环境
暂存是为生产环境准备软件的过程。与“从编码到云端”类似,暂存是一种IT战略和DevOps方法,而非纯粹的编程活动。
在暂存环境中,我们指定构建步骤和测试。软件的所有组件在此处被组装和测试,通常还包括一些最终用户的Alpha和Beta测试。当软件处于此环境时,我们通常称其处于开发中。
生产环境
生产意味着软件(无论是应用程序、新版本、补丁还是更新)在真实场景中被使用,这个真实场景就称为生产环境。
生产环境是软件经过暂存阶段的大量测试后,从云服务器实际推送给最终用户的地方。软件开发团队确信软件能在生产环境中安全、稳定地运行,这意味着它不会崩溃或泄露信息。
流程详解:从暂存到生产
我们可以将“暂存到生产”的过程想象成一个履行和仓储配送中心。
以下是该流程的主要步骤:
-
接收与检查:在暂存阶段,软件以独立的容器(如Docker文件)形式从开发者和DevOps团队处接收,并上传至云端进行检查、测试和组装。
-
内部与外部测试:接下来,完整的应用程序会在内部进行测试,并邀请外部Beta测试者参与,以验证其运行是否符合开发者的设计预期。
-
问题反馈与迭代:如果测试未通过,DevOps团队可能需要修改部分代码并重新提交,暂存流程会像新项目一样重新开始。
-
环境一致性:暂存环境的这一部分在测试资源、软件配置和数据监控方面,与真实的生产环境非常相似。测试模拟了最终用户将如何操作软件,确保没有明显的错误或意外。
暂存与生产的关键区别
两者环境设置相似,但有一个重大区别:
在暂存环境中使用的代码和依赖项发布到外部世界之前,必须确保专有代码(如API密钥)已被移除,并且所有调试工具均已关闭。
这样做的目的是避免对性能产生负面影响或带来潜在的安全风险。
流程回顾与总结
现在,让我们简要地回顾一下整个流程。
在“从编码到云端”流程结束时,软件的所有组件都已交付到用于暂存的相应云服务器。
在暂存环境中,所有组件被组装和测试,软件处于持续集成/持续部署的开发流程中。
最后,在生产环境中,软件从云服务器推送给最终用户。

总结
本节课中,我们一起学习了从暂存环境到生产环境的完整流程。
“从暂存到生产”是持续集成/持续部署流水线中,每个应用程序、更新和版本在软件开发生命周期内都必须经历的部分。
这个过程使得应用程序能够更快地被创建和更新,以满足最终用户的需求。随着技术的演进和改进,“从暂存到生产”的流程只会运行得越来越快。
173:祝贺 🎉
在本节课中,我们将一起回顾整个课程的核心收获,并探讨如何将自动化技能应用于更广泛的场景中。


祝贺你解决了那个棘手的实验。它确实不容易。
同时,祝贺你完成了整个课程。
我们一起学习了非常多的内容。
你能一路坚持到这里,给我留下了深刻的印象。在整个课程中,
你深入学习了“基础设施即代码”在实践中的具体含义。
我们覆盖了广阔的知识领域,而所用的时间并不算长。
凭借你在本课程中学到的知识,
你现在能更好地应对大规模自动化任务。
无论公司规模大小,自动化处理繁琐事务总能帮助你更高效地利用时间。
我想与你分享一个小秘密。自动化不仅适用于工作。
它在你的个人生活中也同样有用。从编写脚本在路由器宕机时自动重启,到抓取网站查看演唱会门票是否可用,
自动化在日常生活中的应用非常广泛。我非常高兴能与你共享这段学习旅程。
并祝愿你在接下来的探索中一切顺利。祝你好运。
回顾与总结
上一节我们分享了完成课程的祝贺,本节中我们来总结一下核心要点。
在本课程中,我们一起学习了如何利用Python实现IT自动化,核心在于将基础设施即代码的理念付诸实践。其核心公式可以概括为:
自动化 = 用代码(如Python脚本)定义和管理IT资源与流程
以下是本课程涵盖的主要技能模块:
- 版本控制:使用Git管理代码变更。
- 问题诊断:运用调试技巧定位和修复错误。
- 云端部署:在云平台上实施自动化解决方案。
- 项目实践:通过实验将理论知识应用于真实场景。
自动化应用拓展
掌握了核心概念后,我们来看看如何将这些技能迁移到更多场景中。
自动化思维可以突破工作场景,应用于个人生活以提高效率。例如:
- 家庭网络维护:编写脚本监控并自动重启家庭路由器。
# 示例:使用ping检测网络并重启的伪代码逻辑 if not network_is_ok(): reboot_router() - 信息获取:编写网页抓取脚本,自动查询门票、价格等特定信息。
本节课中我们一起学习了IT自动化的核心价值与实践方法,从工作到个人生活的广泛应用,并完成了整个课程的学习旅程。记住,持续运用“自动化处理繁琐事务”的原则,能让你在任何规模的任务中都游刃有余。祝你未来在自动化道路上继续探索,一切顺利!
174:调试策略
概述

在本节课中,我们将学习如何通过缩小范围和隔离问题来定位导致程序运行缓慢的根本原因。我们将探讨代码审查的价值,并介绍一些实用的调试策略。

缩小范围与隔离问题
上一节我们介绍了调试的基本概念。本节中,我们来看看如何通过缩小问题范围来定位根本原因。
我们看到,通过缩小范围和隔离问题,可以引导我们找到程序运行缓慢的根本原因。
代码审查的价值
在定位问题之后,理解他人对代码的审视也至关重要。以下是代码审查能带来的好处:
- 代码审查能指出我们可能未曾注意到的问题。
利用Python进行调试
当你处理代码时,Python会提供强大的工具来帮助你。虽然教学Python有时会让我开一些愚蠢的玩笑,但工具本身是严肃而高效的。
接下来,我们将探讨更多可用于定位问题根本原因的实用策略。
Python为你提供了强大的调试能力。回顾一下,我们已经涵盖了许多内容。
总结

本节课中,我们一起学习了调试的核心策略:通过缩小范围和隔离问题来定位缺陷。我们还了解了代码审查的重要性,并认识到Python内置工具对调试过程的强大支持。掌握这些方法将帮助你更高效地解决编程中遇到的性能问题。

浙公网安备 33010602011771号