Linkedin-SRE-中文教程-一-

Linkedin SRE 中文教程(一)

原文:School of SRE

协议:CC BY-NC-SA 4.0

SRE 学校

原文:https://linkedin.github.io/school-of-sre/

站点可靠性工程师(SREs)位于软件工程和系统工程的交叉点。虽然基础架构和软件组件如何组合以实现目标可能有无限的排列和组合,但专注于基础技能使 sre 能够处理复杂的系统和软件,无论这些系统是专有的、第三方的、开放的系统,还是运行在云/本地基础架构上的系统,等等。尤其重要的是深入了解系统和基础设施的这些领域是如何相互联系和相互作用的。软件和系统工程技能的结合是罕见的,通常是随着时间的推移,通过接触各种各样的基础设施、系统和软件而建立起来的。

SREs 引入工程实践来维护网站。每个分布式系统都是许多组件的集合。sre 验证业务需求,将它们转换为构成分布式系统的每个组件的 SLA,监控和测量 SLA 的遵守情况,重新构建或扩展以减轻或避免 SLA 违反,将这些学习作为反馈添加到新系统或项目中,从而减少运营负担。因此,从系统设计的第一天起,SREs 就起着至关重要的作用。

2019 年初,我们开始访问印度各地的校园,招募最优秀和最聪明的头脑,以确保 LinkedIn 以及构成其复杂技术堆栈的所有服务始终可供每个人使用。LinkedIn 的这一关键功能属于网站工程团队和网站可靠性工程师(SREs)的职责范围,他们是软件工程师,专门研究可靠性。

随着我们继续这一旅程,我们开始收到来自这些校园的许多问题,关于站点可靠性工程角色到底需要什么?而且,一个人怎样才能学到成为一名成功的现场可靠性工程师所需的技能和纪律呢?几个月过去了,一些校园学生以实习生或全职工程师的身份加入了 LinkedIn,成为网站工程团队的一员;我们也有一些加入我们组织的横向雇员,他们没有传统的 SRE 背景。就在那时,我们几个人聚在一起,开始思考如何让新毕业的工程师加入现场工程团队。

很少有资源可以指导一个人作为 SRE 初学者必须掌握的基本技能。由于缺乏这些资源,我们认为个人很难获得该行业的空缺职位。我们创建了 SRE 学院,作为任何想成为 SRE 人的人的起点。在本课程中,我们将重点培养强大的基础技能。本课程的结构提供了更多真实的例子,以及学习这些主题如何在 SRE 的日常工作职责中发挥重要作用。目前,我们在 SRE 学院下涵盖以下主题:

我们相信持续学习将有助于获得更深层次的知识和能力,以扩展您的技能组合,每个模块都添加了参考资料,可作为进一步学习的指南。我们希望,通过学习这些模块,我们应该能够掌握现场可靠性工程师所需的基本技能。

在 LinkedIn,我们使用这一课程让我们的非传统雇员和新大学毕业生进入 SRE 的角色。我们与新员工进行了多轮成功的入职培训,该课程帮助他们在很短的时间内提高了工作效率。这促使我们开源内容,以帮助其他组织让新工程师进入角色,并为有抱负的个人进入角色提供指导。我们意识到,我们创建的初始内容只是一个起点,我们希望社区能够在完善和扩展内容的过程中提供帮助。查看投稿指南开始吧。

101 级

基础系列

Linux 基础知识

Linux 基础知识

原文:https://linkedin.github.io/school-of-sre/level101/linux_basics/intro/

介绍

先决条件

  • 应该能够熟练使用任何操作系统,如 Windows、Linux 或 Mac
  • 期望对操作系统有基本的了解

从本课程中可以期待什么

本课程分为三个部分。在第一部分中,我们介绍了 Linux 操作系统的基础知识。我们将讨论 Linux 体系结构、Linux 发行版和 Linux 操作系统的使用。我们还将讨论 GUI 和 CLI 之间的区别。

在第二部分中,我们将介绍 Linux 中使用的一些基本命令。我们将关注用于导航文件系统、查看和操作文件、I/O 重定向等的命令。

在第三部分中,我们将介绍 Linux 系统管理。这包括由 Linux 管理员执行的日常任务,如管理用户/组、管理文件权限、监控系统性能、日志文件等。

在第二和第三部分,我们将举例来理解这些概念。

本课程不包括哪些内容

在本课程中,我们不涉及高级 Linux 命令和 bash 脚本。我们也不会讨论 Linux 的内部机制。

课程内容

本课程涵盖了以下主题:

什么是 Linux 操作系统

我们大多数人都熟悉 75%以上的个人电脑使用的 Windows 操作系统。Windows 操作系统基于 Windows NT 内核。

内核是操作系统中最重要的部分——它执行重要的功能,如进程管理、内存管理、文件系统管理等。

Linux 操作系统基于 Linux 内核。基于 Linux 的操作系统将由 Linux 内核、GUI/CLI、系统库和系统实用程序组成。Linux 内核由 Linus Torvalds 独立开发和发布。Linux 内核是免费和开源的-github.com/torvalds/linux

Linux 是一个内核,而不是一个完整的操作系统。Linux 内核与 GNU 系统结合在一起,构成一个完整的操作系统。因此,基于 linux 的操作系统也被称为 GNU/Linux 系统。GNU 是一个广泛的自由软件集合,如编译器、调试器、C 库等。 Linux 和 GNU 系统

Linux 历史-en.wikipedia.org/wiki/History_of_Linux

什么是流行的 Linux 发行版

Linux 发行版是一个基于 Linux 内核的操作系统和一个软件包管理系统。软件包管理系统由帮助在操作系统上安装、升级、配置和删除软件的工具组成。

软件通常采用发行版,并以发行版特有的格式打包。这些包可以通过发行版特定的库获得。软件包由软件包管理器在操作系统中安装和管理。

流行的 Linux 发行版列表:

  • 一种男式软呢帽

  • 人的本质

  • 一种自由操作系统

  • 摘录

  • 红帽企业版 Linux

  • 苏塞

  • Arch Linux

包系统 分布 包管理器
Debian 风格(。deb) Debian,Ubuntu 恰当的
红帽风格(。rpm) Fedora、CentOS、Red Hat Enterprise Linux 妙的

Linux 架构

  • Linux 内核在本质上是单一的。

  • 系统调用用于与 Linux 内核空间进行交互。

  • 内核代码只能在内核模式下执行。非内核代码在用户模式下执行。

  • 设备驱动程序用于与硬件设备通信。

Linux 操作系统的使用

基于 Linux 内核的操作系统广泛应用于:

  • 个人电脑

  • 服务器

  • 手机——Android 是基于 Linux 操作系统的

  • 嵌入式设备-手表、电视、交通灯等

  • 卫星

  • 网络设备-路由器、交换机等。

图形用户界面(GUI)与命令行界面(CLI)

用户在用户界面的帮助下与计算机交互。用户界面可以是 GUI 或 CLI。

图形用户界面允许用户使用诸如图标和图像的图形与计算机进行交互。当用户点击图标打开计算机上的应用时,他或她实际上是在使用 GUI。使用 GUI 很容易执行任务。

命令行界面允许用户使用命令与计算机进行交互。用户在终端中键入命令,系统帮助执行这些命令。对 GUI 有经验的新用户可能会发现很难与 CLI 交互,因为他/她需要知道执行特定操作的命令。

外壳与终端

Shell 是一个程序,它从用户那里获取命令,并将它们交给操作系统进行处理。Shell 是 CLI(命令行界面)的一个例子。Bash 是 Linux 服务器上最流行的 shell 程序之一。其他流行的 shell 程序有 zsh、ksh 和 tcsh。

终端是一个打开窗口并让你与外壳交互的程序。一些流行的终端例子有 gnome-terminal、xterm、konsole 等。

Linux 用户确实使用外壳、终端、提示符、控制台等术语。可互换。简单来说,这些都是指从用户那里获取命令的方式。

命令行基础

原文:https://linkedin.github.io/school-of-sre/level101/linux_basics/command_line_basics/

实验室环境设置

您可以使用在线 bash 解释器来运行本课程中作为示例提供的所有命令。这也将帮助您获得各种 linux 命令的实践经验。

REPL 是一个流行的运行 linux 命令的在线 bash 解释器。我们将使用它来运行本课程中提到的所有命令。

什么是命令

命令是告诉操作系统执行特定工作的程序。在 linux 中,程序是作为文件存储的。因此,命令也是存储在磁盘某处的文件。

命令也可以接受额外的参数作为用户的输入。这些参数称为命令行参数。知道如何使用命令很重要,在 Linux 中有很多方法可以获得帮助,尤其是命令。几乎每个命令都有某种形式的文档,大多数命令都有一个命令行参数-h 或- help,可以显示合理数量的文档。但是 Linux 中最流行的文档系统叫做 man pages——手册页的缩写。

使用- help 显示 ls 命令的文档。

文件系统组织

linux 文件系统有一个分层(或树状)结构,它的最高层目录称为 root(用/)表示。根目录中的目录存储与系统相关的文件。这些目录又可以存储系统文件、应用文件或用户相关文件。

bin |最常用命令的可执行程序位于 bin 目录中

dev |该目录包含与系统上的设备相关的文件

etc |该目录包含所有系统配置文件

home |此目录包含与用户相关的文件和目录。

lib |该目录包含所有的库文件

mnt |该目录包含与系统上安装的设备相关的文件

proc |该目录包含与系统上正在运行的进程相关的文件

root |此目录包含与 root 用户相关的文件和目录。

sbin |该目录包含用于系统管理的程序。

tmp |该目录用于存储系统中的临时文件

usr |该目录用于存储系统上的应用

用于导航文件系统的命令

有三个常用于导航文件系统的基本命令:

  • 限位开关(Limit Switch)

  • 显示当前工作目录

  • 激光唱片

我们现在将尝试理解每个命令的作用以及如何使用这些命令。您还应该在在线 bash shell 上练习给出的示例。

打印工作目录

在任何给定的时刻,我们都将站在某个目录中。要获得我们所在目录的名称,我们可以在 linux 中使用 pwd 命令。

我们现在将使用 cd 命令移动到不同的目录,然后打印工作目录。

光盘(更改目录)

cd 命令可用于更改工作目录。使用命令,您可以从一个目录移动到另一个目录。

在下面的例子中,我们最初在根目录中。然后,我们使用 cd 命令来更改目录。

ls(列出文件和目录)**

ls 命令用于列出目录的内容。它将列出给定目录中的所有文件和文件夹。

如果我们在 shell 中键入 ls,它将列出当前目录中的所有文件和目录。

我们还可以提供目录名作为 ls 命令的参数。然后,它将列出给定目录中的所有文件和目录。

用于操作文件的命令

有五个常用于操作文件的基本命令:

  • 触控

  • mkdir

  • 丙酸纤维素

  • 平均变化

  • 空间

我们现在将尝试理解每个命令的作用以及如何使用这些命令。您还应该在在线 bash shell 上练习给出的示例。

触摸(创建新文件)

触摸命令可用于创建一个空的新文件。该命令对于许多其他目的非常有用,但是我们将讨论创建新文件的最简单的用例。

使用触摸命令的一般语法

touch <file_name> 

mkdir(创建新目录)

mkdir 命令用于创建目录。您可以使用 ls 命令来验证新目录是否已创建。

使用 mkdir 命令的一般语法

mkdir <directory_name> 

rm(删除文件和目录)

rm 命令可用于删除文件和目录。请务必注意,该命令会永久删除文件和目录。一旦对这些文件和目录成功执行了 rm 命令,就几乎不可能恢复它们。一定要小心运行这个命令。

使用 rm 命令的一般语法:

rm <file_name> 

让我们试着用一个例子来理解 rm 命令。我们将尝试分别删除使用 touch 和 mkdir 命令创建的文件和目录。

复制文件和目录

cp 命令用于将文件和目录从一个位置复制到另一个位置。请注意,cp 命令不会对原始文件或目录做任何更改。成功运行 cp 命令后,原始文件或目录及其副本将共存。

使用 cp 命令的一般语法:

cp <source_path> <destination_path> 

我们目前在“/home/runner”目录中。我们将使用 mkdir 命令创建一个名为“test_directory”的新目录。我们现在将尝试将“_test_runner.py”文件复制到我们刚才创建的目录中。

请注意,原始的“_test_runner.py”文件没有发生任何变化。它仍然在当前目录中。在“test_directory”中创建了一个新的副本。

我们还可以使用 cp 命令将整个目录从一个位置复制到另一个位置。我们试着用一个例子来理解这一点。

我们再次使用 mkdir 命令创建一个名为“another_directory”的新目录。然后,我们使用 cp 命令和一个附加参数'-r '来复制“test_directory”。

mv(移动文件和目录)

mv 命令既可以用于将文件或目录从一个位置移动到另一个位置,也可以用于重命名文件或目录。请注意,移动文件和复制文件是非常不同的。当您移动文件或目录时,原始副本会丢失。

使用 mv 命令的一般语法:

mv <source_path> <destination_path> 

在本例中,我们将使用 mv 命令将“_test_runner.py”文件移动到“test_directory”中。在这种情况下,这个文件已经存在于“test_directory”中。mv 命令将替换它。请注意,mv 命令成功运行后,当前目录中不存在原始文件。

我们还可以使用 mv 命令将目录从一个位置移动到另一个位置。在这种情况下,我们不需要像使用 cp 命令时那样使用'-r '标志。请注意,如果我们使用 mv 命令,原始目录将不存在。

mv 命令的一个重要用途是重命名文件和目录。让我们看看如何使用这个命令进行重命名。

我们首先将我们的位置更改为“test_directory”。然后,我们使用 mv 命令将“_test_runner.py”文件重命名为“test.py”。

查看文件的命令

有五个常用于查看文件的基本命令:

  • 尾巴

  • 更多

  • 较少的

我们现在将尝试理解每个命令的作用以及如何使用这些命令。您还应该在在线 bash shell 上练习给出的示例。

我们将创建一个名为“numbers.txt”的新文件,并在该文件中插入从 1 到 100 的数字。每个数字将在一个单独的行。

现在不要担心上面的命令。这是一个用于生成数字的高级命令。然后,我们使用重定向操作符将这些数字推送到文件中。我们将在后面的章节中讨论 I/O 重定向。

cat 命令最简单的用法是在输出屏幕上打印文件的内容。这个命令非常有用,可以用于许多其他目的。稍后我们将学习其他用例。

你可以试着运行上面的命令,你会在屏幕上看到从 1 到 100 的数字。你需要向上滚动才能看到所有的数字。

默认情况下,head 命令显示文件的前 10 行。我们可以包含额外的参数,从顶部开始显示任意多的行。

在这个例子中,当我们使用 head 命令时,我们只能看到文件的前 10 行。

默认情况下,head 命令将只显示前 10 行。如果我们想要指定我们想要从开始看到的行数,使用'-n '参数来提供输入。

尾巴

默认情况下,tail 命令显示文件的最后 10 行。我们可以包含额外的参数,从文件末尾开始显示任意多的行。

默认情况下,tail 命令将只显示最后 10 行。如果我们想指定我们想从末尾看到的行数,使用'-n '参数来提供输入。

在本例中,当我们使用带有 explicit -n 选项的 tail 命令时,我们只能看到文件的最后 5 行。

更多

More command 显示文件或命令输出的内容,如果文件很大(例如:日志文件),则一次显示一个屏幕。它还允许在文件中向前导航和有限的向后导航。

More command 尽可能多地显示在当前屏幕上,并等待用户输入前进。向前导航可以通过按 Enter 键来完成,这将使输出前进一行,按 Space 键将输出前进一屏。

较少的

Less command 是 more 的改进版本。它显示文件或命令输出的内容,一次显示一页。它允许在文件中向后导航和向前导航,也有搜索选项。我们可以使用箭头键向前或向后移动一行。向前移动一页,按空格键;向后移动一页,按键盘上的 b 键。您可以立即转到文件的开头和结尾。

Linux 中的 Echo 命令

echo 命令是 shell 中使用的最简单的命令之一。这个命令相当于我们在其他编程语言中拥有的

echo 命令在屏幕上打印给定的输入字符串。

文本处理命令

在上一节中,我们学习了如何查看文件的内容。在许多情况下,我们会对执行以下操作感兴趣:

  • 仅打印包含特定单词的行

  • 用文件中的另一个单词替换特定的单词

  • 按特定顺序对行进行排序

有三个常用于处理文本的基本命令:

  • 可做文件内的字符串查找

  • 一项 Linux 指令

  • 分类

我们现在将尝试理解每个命令的作用以及如何使用这些命令。您还应该在在线 bash shell 上练习给出的示例。

我们将创建一个名为“numbers.txt”的新文件,并在该文件中插入从 1 到 10 的数字。每个数字将在一个单独的行。

可做文件内的字符串查找

grep 命令最简单的形式可以用来搜索文本文件中的特定单词。它将显示包含特定输入的文件中的所有行。我们要搜索的单词作为 grep 命令的输入提供。

使用 grep 命令的一般语法:

grep <word_to_search> <file_name> 

在这个例子中,我们试图在这个文件中搜索一个字符串“1”。grep 命令输出找到该字符串的行。

一项 Linux 指令

最简单的 sed 命令可以用来替换文件中的文本。

使用 sed 命令进行替换的一般语法:

sed 's/<text_to_replace>/<replacement_text>/' <file_name> 

让我们尝试使用 sed 命令将文件中出现的“1”替换为“3”。

在上面的例子中,文件的内容不会改变。为此,我们必须使用一个额外的参数'-i ',以便将更改反映到文件中。

分类

sort 命令可用于对作为参数提供给它的输入进行排序。默认情况下,它将按升序排序。

在尝试对文件进行排序之前,让我们先看看文件的内容。

现在,我们将尝试使用 sort 命令对文件进行排序。sort 命令按字典顺序对内容进行排序。

在上面的例子中,文件的内容不会改变。

输入输出重定向

每个打开的文件被分配一个文件描述符。文件描述符是系统中打开文件的唯一标识符。总是有三个默认文件打开,stdin(键盘)、stdout(屏幕)和 stderr(输出到屏幕的错误消息)。这些文件可以被重定向。

Linux-UNIX . stack exchange . com/questions/225537/everything-is-a-file

到目前为止,我们已经在屏幕上显示了所有的输出,这是标准输出。我们可以使用一些特殊的操作符将命令的输出重定向到文件,甚至重定向到其他命令的输入。I/O 重定向是一个非常强大的功能。

在下面的例子中,我们使用了'>'操作符将 ls 命令的输出重定向到 output.txt 文件。

在下面的例子中,我们将 echo 命令的输出重定向到一个文件。

我们还可以将一个命令的输出重定向为另一个命令的输入。借助管道,这是可能的。

在下面的示例中,我们使用管道(|)操作符将 cat 命令的输出作为输入传递给 grep 命令。

在下面的例子中,我们使用管道(|)操作符将 sort 命令的输出作为输入传递给 uniq 命令。uniq 命令只打印输入中的唯一数字。

输入/输出重定向-tldp.org/LDP/abs/html/io-redirection.html

Linux 服务器管理

原文:https://linkedin.github.io/school-of-sre/level101/linux_basics/linux_server_administration/

在本课程中,将尝试涵盖 linux 服务器管理员执行的一些常见任务。我们将首先尝试理解一个特定的命令做什么,然后尝试使用例子来理解这些命令。请记住,自己练习 Linux 命令是非常重要的。

实验室环境设置

  • 我们将在上面的 Docker 容器中运行本模块中使用的大多数命令。

多用户操作系统

如果一个操作系统允许多人/用户使用一台计算机,并且不影响彼此的文件和首选项,则该操作系统被认为是多用户的。基于 Linux 的操作系统本质上是多用户的,因为它允许多个用户同时访问系统。典型的计算机只有一个键盘和显示器,但是如果计算机连接到网络,多个用户可以通过 SSH 登录。我们将在后面介绍更多关于 SSH 的内容。

作为服务器管理员,我们最关心的是离我们很远的 Linux 服务器。我们可以借助 SSH 之类的远程登录方法连接到这些服务器。

由于 Linux 支持多用户,我们需要一种方法来保护用户之间的相互保护。一个用户不应该能够访问和修改其他用户的文件

用户/组管理

  • Linux 中的用户有一个关联的用户 ID,称为 UID。

  • 用户还有一个主目录和一个与之相关联的登录 shell。

  • 组是一个或多个用户的集合。组使得在一组用户之间共享权限变得更加容易。

  • 每个组都有一个与之关联的名为 GID 的组 ID。

id 命令

id命令可用于查找与用户相关联的 uid 和 gid。它还列出了用户所属的组。

与 root 用户关联的 uid 和 gid 为 0。

在 Linux 中找出当前用户的一个好方法是使用 whoami 命令。

“root”用户或超级用户是最有特权的用户,拥有 对系统上所有资源的无限制访问权。它有 UID 0

与用户/组相关的重要文件

/etc/密码 存储用户名、uid、gid、主目录、登录外壳等
/etc/影子 存储与用户相关联的密码
/etc/group 存储系统中不同组的信息

如果您想了解上述输出中讨论的每个字段,可以浏览以下链接:

管理用户的重要命令

下面是一些常用于管理 Linux 上的用户/组的命令:

  • useradd -创建新用户

  • passwd -添加或修改用户密码

  • usermod -修改用户的属性

  • userdel -删除用户

useradd

useradd 命令在 Linux 中添加新用户。

我们将创建一个新用户“shivam”。我们还将通过在/etc/passwd 文件后面添加后缀来验证用户是否已经创建。对于新创建的用户,uid 和 gid 是 1000。分配给用户的主目录是/home/shivam,分配给用户的登录 shell 是/bin/bash。请注意,稍后可以修改用户主目录和登录 shell。

如果我们没有为主目录或登录 shell 之类的属性指定任何值,默认值将被分配给用户。我们也可以在创建新用户时覆盖这些默认值。

密码

passwd 命令用于为用户创建或修改密码。

在上面的例子中,我们在创建用户“shivam”或“amit”时没有为他们分配任何密码。

"!!"在阴影中的帐户条目意味着用户的帐户已经创建,但还没有给出密码。

现在让我们尝试为用户“shivam”创建一个密码。

请记住密码,因为我们将在后面使用有用的示例。

另外,现在让我们更改 root 用户的密码。当我们从普通用户切换到根用户时,它会要求您输入密码。此外,当您使用 root 用户登录时,将会询问密码。

usermod

usermod 命令用于修改用户的属性,如主目录或 shell。

我们试着将用户“amit”的登录 shell 修改为“/bin/bash”。

同样,您也可以修改用户的许多其他属性。请尝试“usermod -h”以获得您可以修改的属性列表。

你是谁

userdel 命令用于删除 Linux 上的用户。一旦我们删除用户,所有与该用户相关的信息都将被删除。

让我们试着删除用户“amit”。删除用户后,您将不会在“/etc/passwd”或“/etc/shadow”文件中找到该用户的条目。

管理组的重要命令

管理组的命令与管理用户的命令非常相似。这里不详细解释每个命令,因为它们非常相似。您可以尝试在您的系统上运行这些命令。

groupadd <group_name> 创建一个新组
groupmod <group_name> 修改组的属性
groupdel <group_name> 删除一个组
gpasswd <group_name> 修改组的密码

我们现在将尝试将用户“shivam”添加到我们上面创建的组中。

成为超级用户

在运行以下命令之前,请确保您已经使用上述 passwd 命令为用户“shivam”和用户“root”设置了密码。

su 命令可用于在 Linux 中切换用户。现在让我们尝试切换到用户“shivam”。

现在让我们尝试打开“/etc/shadow”文件。

操作系统不允许用户“shivam”读取“/etc/shadow”文件的内容。这是 Linux 中的一个重要文件,它存储了用户的密码。该文件只能由 root 用户或拥有超级用户权限的用户访问。

sudo 命令允许 用户以根用户的安全权限运行命令。请记住,root 用户拥有系统的所有权限。我们也可以使用 su 命令切换到 root 用户并打开上面的文件,但是这样做需要 root 用户的密码。另一种在大多数现代操作系统上首选的方法是使用 sudo 命令成为超级用户。使用这种方式,用户必须输入他/她的密码,并且他们需要成为 sudo 组的一部分。

如何向其他用户提供超级权限?

让我们首先使用 su 命令切换到 root 用户。请注意,使用下面的命令需要您输入 root 用户的密码。

如果您忘记了为 root 用户设置密码,请键入“exit ”,您将作为 root 用户返回。现在,使用 passwd 命令设置密码。

文件/etc/sudoers 保存了被允许调用 sudo 的用户的名字。在 redhat 操作系统中,默认情况下不存在该文件。我们需要安装 sudo。

我们将在后面的小节中详细讨论 yum 命令。

尝试打开系统上的“/etc/sudoers”文件。这个文件有很多信息。这个文件存储了用户在运行 sudo 命令时必须遵守的规则。例如,允许 root 从任何地方运行任何命令。

向用户提供 root 访问权限的一个简单方法是将他们添加到有权运行所有命令的组中。“wheel”是 redhat Linux 中拥有此类权限的组。

让我们将用户“shivam”添加到这个组,这样它也拥有 sudo 特权。

现在让我们切换回用户“shivam”并尝试访问“/etc/shadow”文件。

我们需要在运行该命令之前使用 sudo,因为它只能用 sudo 特权来访问。我们已经通过将用户“shivam”添加到组“wheel”中,授予了他 sudo 权限。

文件权限

在 Linux 操作系统上,每个文件和目录都被分配给文件所有者、一组相关用户的成员以及其他所有人的访问权限。这是为了确保不允许一个用户访问另一个用户的文件和资源。

要查看文件的权限,我们可以使用 ls 命令。我们来看看/etc/passwd 文件的权限。

让我们看一下输出中与文件权限相关的一些重要字段。

Chmod 命令

chmod 命令用于修改 Linux 中的文件和目录权限。

chmod 命令接受中的权限作为数字参数。我们可以把权限想象成一系列的位,1 代表真或允许,0 代表假或不允许。

许可 rwx 二进制的 小数
读取、写入和执行 rwx One hundred and eleven seven
直读式记录 rw- One hundred and ten six
阅读并执行 r-x One hundred and one five
只读 r - One hundred four
编写并执行 -wx 011 three
只写 -w- 010 Two
仅执行 [加在以-u 结尾的法语词源的名词之后构成复数] 001 one
没有人 - 000 Zero

我们现在将创建一个新文件,并检查该文件的权限。

群组拥有者没有权限写入这个档案。让我们使用 chmod 命令向组所有者或 root 用户授予写权限。

Chmod 命令也可以用来以类似的方式改变目录的权限。

Chown 命令

chown 命令用于在 Linux 中更改文件或目录的所有者。

命令语法:chown <new_owner> <file_name>

如果我们没有 sudo 权限,我们需要使用 sudo 命令。让我们切换到用户“shivam”并尝试更改所有者。在运行下面的命令之前,我们还将文件的所有者更改为 root。

Chown 命令也可以用来以类似的方式改变目录的所有者。

Chgrp 命令

chgrp 命令可用于在 Linux 中更改文件或目录的组所有权。语法与 chown 命令非常相似。

Chgrp 命令也可以用来以类似的方式更改目录的所有者。

SSH 命令

ssh 命令用于登录远程系统、在系统之间传输文件以及在远程机器上执行命令。SSH 代表 secure shell,用于通过不安全的网络(如互联网)在两台主机之间提供加密的安全连接。

参考:www.ssh.com/ssh/command/

我们现在将讨论无密码认证,这种认证是安全的,并且最常用于 ssh 认证。

使用 SSH 的无密码认证

使用这种方法,我们可以在不输入密码的情况下 ssh 到主机。当我们希望一些脚本执行 ssh 相关的任务时,这种方法也很有用。

无密码身份验证需要使用公钥和私钥对。顾名思义,公钥可以与任何人共享,但私钥应该是私有的。让我们不要进入这个认证如何工作的细节。你可以在这里了解更多信息

使用远程主机设置无密码身份验证的步骤:

  1. 生成公钥-私钥对

    如果我们已经在~/中存储了一个密钥对。ssh 目录中,我们将不再需要生成密钥。

    安装 openssh 包,其中包含所有与 ssh 相关的命令。

    使用 ssh-keygen 命令生成一个密钥对。用户可以选择所有提示的默认值。

    成功运行 ssh-keygen 命令后,我们应该在~/中看到两个密钥。ssh 目录。Id_rsa 是私钥,id_rsa.pub 是公钥。请注意,私钥只能由您读取和修改。

  2. 将公钥传送到远程主机

    有多种方法可以将公钥传输到远程服务器。我们将研究使用 ssh-copy-id 命令来完成这项工作的最常见的方法之一。

    安装 openssh-clients 包以使用 ssh-copy-id 命令。

    使用 ssh-copy-id 命令将您的公钥复制到远程主机。

    现在,ssh 使用密码认证进入远程主机。

    我们的公钥应该在~/中。ssh/authorized_keys now。

    ~/.ssh/authorized_key 包含一个公钥列表。与这些公钥相关联的用户可以通过 ssh 访问远程主机。

如何在远程主机上运行命令?

通用语法:ssh <user>@<hostname> <command>

如何将文件从一台主机传输到另一台主机?

一般语法:scp <source> <destination>

包管理

软件包管理是在系统上安装和管理软件的过程。我们可以从 Linux 包发行商那里安装我们需要的包。不同的分销商使用不同的包系统。

包系统 分布
Debian 风格(。deb) Debian,Ubuntu
红帽风格(。rpm) Fedora、CentOS、Red Hat Enterprise Linux

Linux 中流行的打包系统

命令 描述
yum install <package_name> 在您的系统上安装软件包
yum upudate <package_name> 将软件包更新到最新的可用版本
yum remove <package_name> 从系统中删除软件包
yum search 搜索特定的关键字

DNF 是 YUM 的继承者,现在在 Fedora 中用于安装和管理软件包。DNF 将来可能会在所有基于 RPM 的 Linux 发行版上取代 YUM。

当我们使用 yum search 命令进行搜索时,确实找到了关键字 httpd 的精确匹配。现在让我们安装 httpd 包。

安装 httpd 后,我们将使用 yum remove 命令删除 httpd 包。

进程管理

在这一节中,我们将学习一些有用的命令,它们可以用来监视 Linux 系统上的进程。

ps(流程状态)

ps 命令用于了解进程或进程列表的信息。

如果在运行 ps 命令时出现错误“ps 命令未找到”,请安装 procps 包。

没有任何参数的 ps 用处不大。让我们尝试使用下面的命令列出系统中的所有进程。

参考:UNIX . stack exchange . com/questions/106847/what-does-aux-mean-in-PS-aux

我们可以在 ps 命令中使用一个附加参数来列出带有特定进程 ID 的进程的信息。

我们可以结合使用 grep 和 ps 命令来仅列出特定进程。

顶端

top 命令用于实时显示系统上运行的 Linux 进程的信息。它还显示系统信息的摘要。

对于每个进程,top 列出了进程 ID、所有者、优先级、状态、cpu 利用率、内存利用率和更多信息。它还列出了整个系统的内存利用率和 cpu 利用率,以及系统正常运行时间和 cpu 平均负载。

内存管理

在本节中,我们将学习一些有用的命令,这些命令可用于查看系统内存的信息。

自由的

free 命令用于显示系统的内存使用情况。该命令显示 RAM 中可用的总空闲空间和已用空间,以及缓存/缓冲区占用的空间。

默认情况下,free 命令以千字节为单位显示内存使用情况。我们可以使用一个额外的参数来获取人类可读格式的数据。

vmstat

vmstat 命令可用于显示内存使用情况以及关于 io 和 cpu 使用情况的附加信息。

检查磁盘空间

在这一节中,我们将学习一些有用的命令,它们可以用来在 Linux 上查看磁盘空间。

磁盘空闲

df 命令用于显示每个已装载文件系统的可用空间。

磁盘使用情况(du)

du 命令用于显示系统中文件和目录的磁盘使用情况。

以下命令可用于显示根目录中前 5 个最大的目录。

守护进程

作为后台进程运行的计算机程序称为守护程序。传统上,守护进程的名称以 d - sshd、httpd 等结尾。我们不能与后台进程交互,因为它们在后台运行。

服务和守护进程大部分时间都可以互换使用。

系统

Systemd 是 Linux 操作系统的系统和服务管理器。Systemd 单元是 systemd 的构造块。这些单元由单元配置文件表示。

以下示例显示了/usr/lib/systemd/system 中的单元配置文件,这些文件由安装的 RPM 软件包分发。我们对以 service 结尾的配置文件更感兴趣,因为这些是服务单元。

管理系统服务

服务单位以结尾。服务文件扩展名。Systemctl 命令可用于启动/停止/重新启动由 systemd 管理的服务。

命令 描述
systemctl 启动名称. service 启动服务
系统停止名称.服务 停止服务
systemctl 重新启动名称。服务 重新启动服务
systemctl 状态名称.服务 检查服务的状态
systemctl 重新加载 name.service 重新加载服务的配置

日志

在这一节中,我们将讨论一些重要的文件和目录,它们对于在 Linux 中查看系统日志和应用日志非常有用。当您对系统进行故障诊断时,这些日志非常有用。

总结

原文:https://linkedin.github.io/school-of-sre/level101/linux_basics/conclusion/

我们已经介绍了 linux 操作系统的基础知识和 Linux 中使用的基本命令。我们还讲述了 Linux 服务器管理命令。

我们希望本课程将使您更容易在命令行上操作。

SRE 角色中的应用

  1. 作为 SRE,您需要在这些 Linux 服务器上执行一些常规任务。在对问题进行故障排除时,您也将使用命令行。
  2. 在文件系统中从一个位置移动到另一个位置需要lspwdcd命令的帮助。
  3. 您可能需要在日志文件中搜索一些特定的信息。命令在这里会非常有用。如果您想将输出存储在一个文件中或者将其作为输入传递给另一个命令,I/O 重定向将变得非常方便。
  4. tail命令对于查看日志文件中的最新数据非常有用。
  5. 不同的用户将根据他们的角色拥有不同的权限。出于安全原因,我们也不希望公司的每个人都访问我们的服务器。用户权限可以通过chownchmodchgrp命令进行限制。
  6. ssh是 SRE 最常用的命令之一。只有当我们能够登录到服务器时,才能登录到服务器并进行故障排除以及执行基本的管理任务。
  7. 如果我们想在服务器上运行 apache 服务器或 nginx 怎么办?我们将首先使用软件包管理器安装它。包管理命令在这里变得很重要。
  8. 管理服务器上的服务是 SRE 的另一项重要职责。Systemd 相关命令有助于解决问题。如果服务停止,我们可以使用systemctl start命令启动它。如果不需要某项服务,我们也可以停止它。
  9. 监控是 SRE 的另一个核心职责。内存和 CPU 是应该监控的两个重要的系统级指标。像topfree这样的命令在这里非常有用。
  10. 如果一个服务抛出一个错误,我们如何找出错误的根本原因?我们当然需要检查日志来找出错误的整个堆栈跟踪。日志文件还会告诉我们错误发生的次数以及开始的时间。

有用的课程和教程

Git

Git

原文:https://linkedin.github.io/school-of-sre/level101/git/git-basics/

先决条件

  1. 已经安装了 Gitgit-scm.com/downloads
  2. 参加过 git 高级教程或 LinkedIn 学习课程吗

从本课程中可以期待什么

作为一名计算机科学领域的工程师,拥有版本控制工具的知识几乎成为一项要求。虽然现在有很多版本控制工具,如 SVN、Mercurial 等,但 Git 可能是最常用的工具,本课程我们将使用 Git。虽然本课程不是从 git 101 开始的,并希望将 git 的基础知识作为先决条件,但它将重新介绍您所知道的 Git 概念,并详细介绍在您执行各种 Git 命令时发生的事情。这样下次运行 git 命令时,您就可以更自信地按 enter 键了!

本课程不包括哪些内容

Git 内部实现细节的高级用法和细节。

课程内容

  1. 去基数
  2. 使用分支
  3. 用 Github Git
  4. 挂钩

去吧,普西

尽管你可能已经意识到了,让我们再来看看为什么我们需要一个版本控制系统。随着项目的增长和多个开发人员开始工作,需要一种有效的协作方法。Git 有助于团队轻松协作,并且维护代码库发生变化的历史。

创建 Git Repo

任何文件夹都可以转换成 git 存储库。在执行下面的命令后,我们将在文件夹中看到一个.git文件夹,这使得我们的文件夹成为一个 git 存储库。git 做的所有神奇的事情,.git文件夹也是同样的使能器。

# creating an empty folder and changing current dir to it
$ cd /tmp
$ mkdir school-of-sre
$ cd school-of-sre/

# initialize a git repo
$ git init
Initialized empty Git repository in /private/tmp/school-of-sre/.git/ 

正如输出所说,在我们的文件夹中已经初始化了一个空的 git repo。让我们看看那里有什么。

$ ls .git/
HEAD        config      description hooks       info        objects     refs 

.git文件夹里有一堆文件夹和文件。正如我所说的,所有这些使 git 能够施展它的魔法。我们将查看其中的一些文件夹和文件。但是现在,我们只有一个空的 git 存储库。

跟踪文件

现在,您可能已经知道,让我们在 repo 中创建新文件(我们现在将该文件夹称为 repo 。)并查看 git 状态

$ echo "I am file 1" > file1.txt
$ git status
On branch master

No commits yet

Untracked files:
 (use "git add <file>..." to include in what will be committed)

       file1.txt

nothing added to commit but untracked files present (use "git add" to track) 

当前的 git 状态显示为No commits yet,还有一个未被跟踪的文件。因为我们刚刚创建了这个文件,所以 git 没有跟踪这个文件。我们明确需要让 git 跟踪文件和文件夹。(同时检查 gitignore )我们如何通过上面输出中建议的git add命令来完成。然后,我们继续创建一个提交。

$ git add file1.txt
$ git status
On branch master

No commits yet

Changes to be committed:
 (use "git rm --cached <file>..." to unstage)

       new file:   file1.txt

$ git commit -m "adding file 1"
[master (root-commit) df2fb7a] adding file 1
1 file changed, 1 insertion(+)
create mode 100644 file1.txt 

注意添加文件后,git 状态显示为Changes to be committed:。这意味着无论在那里列出什么,都将包含在下一次提交中。然后,我们继续创建一个 commit,并通过-m附加一条消息。

关于提交的更多信息

提交是回购的快照。每当进行提交时,都会拍摄并保存存储库(文件夹)当前状态的快照。每个提交都有一个唯一的 ID。(df2fb7a对于我们在上一步中所做的提交)。随着我们不断添加/更改越来越多的内容并不断提交,所有这些快照都由 git 存储。同样,所有这些神奇的事情都发生在.git文件夹中。这是所有快照或版本以有效方式存储的地方

添加更多更改

让我们再创建一个文件并提交更改。它看起来和我们之前提交的一样。

$ echo "I am file 2" > file2.txt
$ git add file2.txt
$ git commit -m "adding file 2"
[master 7f3b00e] adding file 2
1 file changed, 1 insertion(+)
create mode 100644 file2.txt 

ID 为7f3b00e的新提交已创建。您可以在任何时候发出git status来查看存储库的状态。

 **IMPORTANT: Note that commit IDs are long string (SHA) but we can refer to a commit by its initial few (8 or more) characters too. We will interchangeably using shorter and longer commit IDs.** 

现在我们有了两个提交,让我们将它们可视化:

$ git log --oneline --graph
* 7f3b00e (HEAD -> master) adding file 2
* df2fb7a adding file 1 

git log,顾名思义,打印所有 git 提交的日志。这里您可以看到两个额外的参数,--oneline打印日志的较短版本,即:只显示提交消息,而不显示提交者和提交时间。--graph以图形格式打印出来。

此时此刻,提交可能看起来每行只有一个,但是所有的提交都被 git 存储为一个树状数据结构。这意味着给定的提交可以有两个或更多的子提交。而不仅仅是一行提交。当我们到达分支部分时,我们将更深入地研究这一部分。现在,这是我们的提交历史:

 df2fb7a ===> 7f3b00e 

提交真的有联系吗?

正如我刚才所说的,我们刚刚做的两个提交是通过树状数据结构连接的,我们看到了它们是如何连接的。不过还是来实际验证一下吧。git 中的一切都是对象。新创建的文件存储为一个对象。对文件的更改存储为对象,甚至提交也是对象。要查看对象的内容,我们可以使用下面的命令和对象的 ID。我们将看看第二次提交的内容

$ git cat-file -p 7f3b00e
tree ebf3af44d253e5328340026e45a9fa9ae3ea1982
parent df2fb7a61f5d40c1191e0fdeb0fc5d6e7969685a
author Sanket Patel <spatel1@linkedin.com> 1603273316 -0700
committer Sanket Patel <spatel1@linkedin.com> 1603273316 -0700

adding file 2 

注意上面输出中的parent属性。它指向我们第一次提交的提交 id。所以这证明他们是有联系的!此外,您可以在该对象中看到第二次提交的消息。正如我所说的,所有这些魔法都是通过.git文件夹实现的,我们正在查看的对象也在那个文件夹中。

$ ls .git/objects/7f/3b00eaa957815884198e2fdfec29361108d6a9
.git/objects/7f/3b00eaa957815884198e2fdfec29361108d6a9 

它存储在.git/objects/文件夹中。所有文件和对它们的更改都存储在这个文件夹中。

Git 的版本控制部分

我们已经可以在 git 日志中看到两个提交(版本)。版本控制工具给你的一个功能是在历史中来回浏览。例如:您的一些用户正在运行旧版本的代码,他们报告了一个问题。为了调试该问题,您需要访问旧代码。您当前回购中的是最新代码。在本例中,您正在进行第二次提交(7f3b00e),有人报告了提交时代码快照的问题(df2fb7a)。这是您在任何较早的提交中访问代码的方式

# Current contents, two files present
$ ls
file1.txt file2.txt

# checking out to (an older) commit
$ git checkout df2fb7a
Note: checking out 'df2fb7a'.

You are in 'detached HEAD' state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.

If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:

 git checkout -b <new-branch-name>

HEAD is now at df2fb7a adding file 1

# checking contents, can verify it has old contents
$ ls
file1.txt 

这就是我们访问旧版本/快照的方式。我们所需要的只是一个对该快照的引用。在执行git checkout ...时,git 为您做的是使用.git文件夹,查看该版本/引用中的事物(文件和文件夹)的状态,并用这些内容替换当前目录的内容。当时存在的内容将不再存在于本地目录(repo)中,但我们可以而且仍然会访问它们,因为它们是通过 git commit 和.git文件夹存储/跟踪的。

参考

我在前面提到我们需要一个引用到这个版本。默认情况下,git repo 由提交树组成。并且每个提交都有一个唯一的 id。但是惟一的 ID 并不是我们唯一可以引用提交的东西。引用提交有多种方式。例如:HEAD是对当前提交的引用。无论你的回购是在哪个提交被签出,HEAD都会指向那个提交。 HEAD~1是对以前提交的引用。因此,在检查上一节中的先前版本时,我们可能已经完成了git checkout HEAD~1

同样,master 也是(对分支的)引用。因为 git 使用树状结构来存储提交,所以当然会有分支。而默认的分支叫做master。主(或任何分支引用)将指向分支中的最新提交。即使我们在我们的回购中已经签出到前一个提交,master仍然指向最近的提交。我们可以通过在master查阅来获得最新版本

$ git checkout master
Previous HEAD position was df2fb7a adding file 1
Switched to branch 'master'

# now we will see latest code, with two files
$ ls
file1.txt file2.txt 

注意,在上面的命令中,我们也可以使用提交的 ID 来代替master

参考文献和魔法

让我们看看事情的状态。两个提交,masterHEAD引用指向最近的提交

$ git log --oneline --graph
* 7f3b00e (HEAD -> master) adding file 2
* df2fb7a adding file 1 

魔法?让我们检查这些文件:

$ cat .git/refs/heads/master
7f3b00eaa957815884198e2fdfec29361108d6a9 

维奥拉。master 指向的位置存储在一个文件中。每当 git 需要知道 master 引用指向哪里,或者 git 需要更新 master 指向哪里,只需要更新上面的文件。因此,当您创建一个新的提交时,会在当前提交的基础上创建一个新的提交,并使用新提交的 ID 更新主文件。

类似地,供HEAD参考:

$ cat .git/HEAD
ref: refs/heads/master 

我们可以看到HEAD指向一个名为refs/heads/master的引用。所以HEAD会指向曾经master指向的地方。

小冒险

我们讨论了 git 如何在我们执行命令时更新文件。但是让我们试着自己动手做,看看会发生什么。

$ git log --oneline --graph
* 7f3b00e (HEAD -> master) adding file 2
* df2fb7a adding file 1 

现在,让我们将 master 更改为指向上一次/第一次提交。

$ echo df2fb7a61f5d40c1191e0fdeb0fc5d6e7969685a > .git/refs/heads/master
$ git log --oneline --graph
* df2fb7a (HEAD -> master) adding file 1

# RESETTING TO ORIGINAL
$ echo 7f3b00eaa957815884198e2fdfec29361108d6a9 > .git/refs/heads/master
$ git log --oneline --graph
* 7f3b00e (HEAD -> master) adding file 2
* df2fb7a adding file 1 

我们刚刚编辑了master引用文件,现在我们只能在 git 日志中看到第一次提交。撤消对文件的更改会将状态恢复到原始状态。没有那么多魔法,是吗?

使用分支

原文:https://linkedin.github.io/school-of-sre/level101/git/branches/

回到本地回购协议,它有两个提交。到目前为止,我们只有一条历史线索。提交链接在一行中。但是有时您可能需要在同一个 repo 中并行处理两个不同的特性。现在,这里的一个选项可能是用相同的代码创建一个新的文件夹/repo,并将其用于另一个功能开发。但是有更好的方法。使用分支。由于 git 遵循树状结构进行提交,我们可以使用分支来处理不同的特性集。通过提交,可以创建两个或更多分支,也可以合并分支。

使用分支,可以存在多行历史,我们可以签出其中的任何一行并对其进行操作。正如我们前面所讨论的,检出仅仅意味着用检出版本的快照替换目录(repo)的内容。

让我们创建一个分支,看看它看起来像什么:

$ git branch b1
$ git log --oneline --graph
* 7f3b00e (HEAD -> master, b1) adding file 2
* df2fb7a adding file 1 

我们创建一个名为b1的分支。Git 日志告诉我们,b1 也指向最后一次提交(7f3b00e ),但是HEAD仍然指向 master。如果你还记得的话,HEAD 会指向提交/引用。所以如果我们结账去b1,头应该指向那个。我们来确认一下:

$ git checkout b1
Switched to branch 'b1'
$ git log --oneline --graph
* 7f3b00e (HEAD -> b1, master) adding file 2
* df2fb7a adding file 1 

b1仍然指向同一个提交,但 HEAD 现在指向b1。因为我们在提交7f3b00e时创建了一个分支,所以将有两行历史记录开始这个提交。根据你在哪个分支上被检查,历史的线将会前进。

此时,我们在分支b1上被签出,因此进行新的提交将使分支引用b1前进到该提交,并且当前的b1提交将成为其父提交。就这么办吧。

# Creating a file and making a commit
$ echo "I am a file in b1 branch" > b1.txt
$ git add b1.txt
$ git commit -m "adding b1 file"
[b1 872a38f] adding b1 file
1 file changed, 1 insertion(+)
create mode 100644 b1.txt

# The new line of history
$ git log --oneline --graph
* 872a38f (HEAD -> b1) adding b1 file
* 7f3b00e (master) adding file 2
* df2fb7a adding file 1
$ 

请注意,master 仍然指向它所指向的旧提交。我们现在可以签出到主分支并在那里提交。这将导致从提交 7f3b00e 开始的另一行历史。

# checkout to master branch
$ git checkout master
Switched to branch 'master'

# Creating a new commit on master branch
$ echo "new file in master branch" > master.txt
$ git add master.txt
$ git commit -m "adding master.txt file"
[master 60dc441] adding master.txt file
1 file changed, 1 insertion(+)
create mode 100644 master.txt

# The history line
$ git log --oneline --graph
* 60dc441 (HEAD -> master) adding master.txt file
* 7f3b00e adding file 2
* df2fb7a adding file 1 

注意分支 b1 在这里是不可见的,因为我们在主节点上。让我们试着将这两者形象化,以了解全貌:

$ git log --oneline --graph --all
* 60dc441 (HEAD -> master) adding master.txt file
| * 872a38f (b1) adding b1 file
|/
* 7f3b00e adding file 2
* df2fb7a adding file 1 

上面的树形结构应该把事情说清楚了。注意在提交 7f3b00e 时有一个清晰的分支/分叉。这就是我们创建分支的方式。现在它们都是两条独立的历史线,特性开发可以独立完成。

再次重申,在内部,git 只是一个提交树。分支名称(人类可读)是指向树中那些提交的指针。我们使用各种 git 命令来处理树结构和引用。Git 相应地修改我们的回购内容。

合并

现在假设您在分支b1上工作的特性已经完成,您需要将它合并到主分支上,所有最终版本的代码都放在那里。所以首先你将签出到 branch master,然后你从上游拉最新的代码(例如:GitHub)。然后你需要将你的代码从b1合并到 master 中。有两种方法可以做到这一点。

以下是当前历史:

$ git log --oneline --graph --all
* 60dc441 (HEAD -> master) adding master.txt file
| * 872a38f (b1) adding b1 file
|/
* 7f3b00e adding file 2
* df2fb7a adding file 1 

方案一:直接合并分行。将分支 b1 合并到主分支将导致新的合并提交。这将合并两个不同历史行的更改,并创建一个新的结果提交。

$ git merge b1
Merge made by the 'recursive' strategy.
b1.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 b1.txt
$ git log --oneline --graph --all
*   8fc28f9 (HEAD -> master) Merge branch 'b1'
|\
| * 872a38f (b1) adding b1 file
* | 60dc441 adding master.txt file
|/
* 7f3b00e adding file 2
* df2fb7a adding file 1 

您可以看到创建了一个新的合并提交(8fc28f9)。系统将提示您提交消息。如果回购中有许多分支,这个结果将以许多合并提交结束。与单一的发展历史相比,这看起来很丑陋。所以让我们来看看另一种方法

首先让我们重置我们的最后一次合并,回到之前的状态。

$ git reset --hard 60dc441
HEAD is now at 60dc441 adding master.txt file
$ git log --oneline --graph --all
* 60dc441 (HEAD -> master) adding master.txt file
| * 872a38f (b1) adding b1 file
|/
* 7f3b00e adding file 2
* df2fb7a adding file 1 

方案二:重定基数。现在,不要合并两个具有相似基础的分支(提交:7f3b00e),让我们将分支 b1 重新基础到当前的主节点上。这意味着取分支b1(从提交 7f3b00e 到提交 872a38f)和 rebase(把它们放在上面)master (60dc441)。

# Switch to b1
$ git checkout b1
Switched to branch 'b1'

# Rebase (b1 which is current branch) on master
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: adding b1 file

# The result
$ git log --oneline --graph --all
* 5372c8f (HEAD -> b1) adding b1 file
* 60dc441 (master) adding master.txt file
* 7f3b00e adding file 2
* df2fb7a adding file 1 

您可以看到有 1 个提交的b1。该提交的父级是7f3b00e。但是既然我们把它重新放在主人(60dc441)身上。它现在成为了父节点。作为一个副作用,你也看到它已经成为历史的一条线。现在,如果我们要将b1合并到master,这仅仅意味着将master改为指向5372c8f,也就是b1。让我们来试试:

# checkout to master since we want to merge code into master
$ git checkout master
Switched to branch 'master'

# the current history, where b1 is based on master
$ git log --oneline --graph --all
* 5372c8f (b1) adding b1 file
* 60dc441 (HEAD -> master) adding master.txt file
* 7f3b00e adding file 2
* df2fb7a adding file 1

# Performing the merge, notice the "fast-forward" message
$ git merge b1
Updating 60dc441..5372c8f
Fast-forward
b1.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 b1.txt

# The Result
$ git log --oneline --graph --all
* 5372c8f (HEAD -> master, b1) adding b1 file
* 60dc441 adding master.txt file
* 7f3b00e adding file 2
* df2fb7a adding file 1 

现在你可以看到b1master都指向同一个提交。您的代码已经合并到主分支,可以推送了。我们也有干净的历史!😄

使用 GitHub 的 Git

原文:https://linkedin.github.io/school-of-sre/level101/git/github-hooks/

到目前为止,我们所做的所有操作都是在本地 repo 中进行的,而 git 也在协作环境中帮助我们。GitHub 是互联网上的一个地方,在这里你可以集中托管你的 git repos 并与其他开发者合作。

大部分工作流程将与我们讨论的一样,只是增加了一些东西:

  1. Pull:从 github(中央)回购中获取最新的变更
  2. 推送:将您的更改推送到 github repo,以便所有人都可以使用

GitHub 已经就此写了很好的指南和教程,你可以在这里参考它们:

钩住

Git 还有另一个很好的特性,叫做 hooks。钩子基本上是当某个事件发生时被调用的脚本。挂钩的位置如下:

$ ls .git/hooks/
applypatch-msg.sample     fsmonitor-watchman.sample pre-applypatch.sample     pre-push.sample           pre-receive.sample        update.sample
commit-msg.sample         post-update.sample        pre-commit.sample         pre-rebase.sample         prepare-commit-msg.sample 

名称是不言自明的。当你想在特定事件发生时做特定的事情时,这些钩子是有用的。如果你想在推送代码之前运行测试,你应该设置pre-push钩子。让我们试着创建一个预提交钩子。

$ echo "echo this is from pre commit hook" > .git/hooks/pre-commit
$ chmod +x .git/hooks/pre-commit 

我们基本上是在 hooks 文件夹中创建一个名为pre-commit的文件,并使其可执行。现在,如果我们提交,我们应该看到消息被打印出来。

$ echo "sample file" > sample.txt
$ git add sample.txt
$ git commit -m "adding sample file"
this is from pre commit hook     # <===== THE MESSAGE FROM HOOK EXECUTION
[master 9894e05] adding sample file
1 file changed, 1 insertion(+)
create mode 100644 sample.txt 

总结

原文:https://linkedin.github.io/school-of-sre/level101/git/conclusion/

接下来会发生什么?

有很多 git 命令和特性我们在这里没有探讨。但是随着基础的建立,一定要探索这样的概念

  • 做出最佳选择
  • 壁球
  • 改进
  • 隐藏物
  • 重置

Linux 网络

Linux 网络基础

原文:https://linkedin.github.io/school-of-sre/level101/linux_networking/intro/

先决条件

从本课程中可以期待什么

在整个课程中,我们将介绍 SRE 如何优化系统以提高其 web 堆栈性能,并在网络堆栈的任何一层出现问题时进行故障排除。本课程试图深入了解传统 TCP/IP 协议栈的每一层,并期望 SRE 能够从鸟瞰的角度了解互联网的功能。

本课程不包括哪些内容

这门课花时间在基础知识上。我们并没有涵盖像 HTTP/2.0QUICTCP 拥塞控制协议AnycastBGPCDN隧道多播这样的概念。我们期望本课程将提供理解这些概念的相关基础知识

球场鸟瞰图

该课程涵盖的问题是“当你在浏览器中打开 linkedin.com 时会发生什么?”本课程遵循 TCP/IP 协议栈的流程。更具体地说,本课程涵盖了应用层协议 DNS 和 HTTP、传输层协议 UDP 和 TCP、网络层协议 IP 和数据链路层协议的主题

课程内容

  1. DNS
  2. UDP
  3. HTTP
  4. TCP
  5. IP 路由

域名服务器(DNS)

原文:https://linkedin.github.io/school-of-sre/level101/linux_networking/dns/

域名是网站的简单易读的名称。互联网只理解 IP 地址,但由于记忆不连贯的数字并不实用,所以改用域名。这些域名被 DNS 基础设施翻译成 IP 地址。当有人试图在浏览器中打开www.linkedin.com时,浏览器试图将www.linkedin.com转换成 IP 地址。这个过程称为 DNS 解析。描述这一过程的简单伪代码如下

ip, err = getIPAddress(domainName)
if err:
  print(“unknown Host Exception while trying to resolve:%s”.format(domainName)) 

现在让我们试着理解 getIPAddress 函数内部发生了什么。浏览器将拥有自己的 DNS 缓存,在其中检查是否有域名到已经可用的 IP 地址的映射,在这种情况下,浏览器使用该 IP 地址。如果不存在这样的映射,浏览器会调用 gethostbyname syscall 来请求操作系统查找给定域名的 IP 地址

def getIPAddress(domainName):
    resp, fail = lookupCache(domainName)
    If not fail:
       return resp
    else:
       resp, err = gethostbyname(domainName)
       if err:
         return null, err
       else:
          return resp 

现在让我们理解当调用 gethostbyname 函数时操作系统内核做什么。Linux 操作系统查看文件 /etc/nsswitch.conf

hosts:      files dns 

这一行意味着操作系统必须首先在文件(/etc/hosts)中查找,然后如果在/etc/hosts 中没有匹配项,就使用 DNS 协议进行解析。

文件/etc/hosts 的格式为

IP 地址 FQDN [FQDN]。*

127.0.0.1 localhost.localdomain localhost
::1 localhost.localdomain localhost 

如果该文件中存在匹配的域,则操作系统会返回该 IP 地址。让我们给这个文件添加一行

127.0.0.1 test.linkedin.com 

然后做平 test.linkedin.com

ping test.linkedin.com -n 
PING test.linkedin.com (127.0.0.1) 56(84) bytes of data.
64 bytes from 127.0.0.1: icmp_seq=1 ttl=64 time=0.047 ms
64 bytes from 127.0.0.1: icmp_seq=2 ttl=64 time=0.036 ms
64 bytes from 127.0.0.1: icmp_seq=3 ttl=64 time=0.037 ms 

如前所述,如果/etc/hosts 中不存在匹配,操作系统会尝试使用 DNS 协议进行 DNS 解析。linux 系统向/etc/resolv.conf 中的第一个 IP 发出 DNS 请求。如果没有响应,请求将被发送到 resolv.conf 中的后续服务器。resolv.conf 中的这些服务器称为 DNS 解析器。DNS 解析器由 DHCP 填充或由管理员静态配置。 Dig 是一个用户空间 DNS 系统,它创建并向 DNS 解析器发送请求,并将收到的响应打印到控制台。

#run this command in one shell to capture all DNS requests
sudo tcpdump -s 0 -A -i any port 53
#make a dig request from another shell
dig linkedin.com 
13:19:54.432507 IP 172.19.209.122.56497 > 172.23.195.101.53: 527+ [1au] A? linkedin.com. (41)
....E..E....@.n....z...e...5.1.:... .........linkedin.com.......)........
13:19:54.485131 IP 172.23.195.101.53 > 172.19.209.122.56497: 527 1/0/1 A 108.174.10.10 (57)
....E..U..@.|.  ....e...z.5...A...............linkedin.com..............3..l.

..)........ 

数据包捕获显示向 linkedin.com 的 172.23.195.101:53(这是/etc/resolv.conf 中的解析器)发出请求,并从 IP 地址为 linkedin.com 108.174.10.10 的 172.23.195.101 收到响应

现在让我们试着理解 DNS 解析器是如何找到 linkedin.com 的 IP 地址的。DNS 解析器首先查看其缓存。由于网络中的许多设备都可以查询域名 linkedin.com,因此域名解析结果可能已经存在于缓存中。如果存在缓存未命中,它将启动 DNS 解析过程。DNS 服务器将“linkedin.com”断开为。,“com。”和“linkedin.com .”并从“.”开始 DNS 解析。的“.”被称为根域,DNS 解析软件知道这些 IP。DNS 解析器查询根域名服务器,以找到能够对“com”的详细信息做出响应的正确域名服务器。“com”的权威名称服务器的地址被退回。现在,DNS 解析服务联系“com”的权威名称服务器获取“linkedin.com”的权威名称服务器。一旦知道了“linkedin.com”的权威名称服务器,解析器就联系 linkedin 的名称服务器以提供“linkedin.com”的 IP 地址。这整个过程可以通过运行

dig +trace linkedin.com 
linkedin.com.       3600    IN  A   108.174.10.10 

这个 DNS 响应有 5 个字段,其中第一个字段是请求,最后一个字段是响应。第二个字段是生存时间,表示 DNS 响应的有效时间,以秒为单位。在这种情况下,linkedin.com 的这种映射在 1 小时内有效。这就是解析器和应用(浏览器)维护其缓存的方式。任何超过 1 小时的 linkedin.com 请求都将被视为缓存未命中,因为映射的 TTL 已经过期,整个过程必须重新进行。第 4 个字段表示 DNS 响应/请求的类型。一些不同的 DNS 查询类型是 A、AAAA、NS、TXT、PTR、MX 和 CNAME。- A 记录返回域名的 IPV4 地址- AAAA 记录返回域名的 IPV6 地址- NS 记录返回域名的权威名称服务器- CNAME 记录是域名的别名。一些域指向其他域名,解析后一个域名会给出一个 IP,该 IP 也用作前一个域名的 IP。示例 www.linkedin.com 的 IP 地址与 2-01-2c3e-005a.cdx.cedexis.net 相同。-为简洁起见,我们不讨论其他 DNS 记录类型,这些记录的 RFC 可在此处获得。

dig A linkedin.com +short
108.174.10.10

dig AAAA linkedin.com +short
2620:109:c002::6cae:a0a

dig NS linkedin.com +short
dns3.p09.nsone.net.
dns4.p09.nsone.net.
dns2.p09.nsone.net.
ns4.p43.dynect.net.
ns1.p43.dynect.net.
ns2.p43.dynect.net.
ns3.p43.dynect.net.
dns1.p09.nsone.net.

dig www.linkedin.com CNAME +short
2-01-2c3e-005a.cdx.cedexis.net. 

有了 DNS 的这些基础知识,让我们看看 SREs 使用 DNS 的用例。

SRE 角色中的应用

本节涵盖了 SRE 可以从 DNS 中获得的一些常见解决方案

  1. 每个公司都必须为内部网站点和内部服务(如数据库和其他内部应用,如 wiki)建立内部 DNS 基础设施。因此,基础设施团队必须为这些域名维护 DNS 基础设施。这种 DNS 基础设施必须进行优化和扩展,以便它不会成为单点故障。内部 DNS 基础设施的故障会导致微服务的 API 调用失败和其他级联效应。
  2. DNS 也可以用于发现服务。例如,主机名 serviceb.internal.example.com 可以列出在 example.com 公司内部运行服务 b 的实例。云提供商提供选项来启用 DNS 发现(示例)
  3. 云提供商和 CDN 提供商使用 DNS 来扩展他们的服务。在 Azure/AWS 中,负载平衡器被赋予一个 CNAME,而不是 IPAddress。它们通过更改别名域名的 IP 地址来更新负载平衡器的 IP 地址。这也是为什么此类别名域名的记录只有 1 分钟那么短的原因之一。
  4. DNS 还可以用来使客户获得离他们位置更近的 IP 地址,这样,如果该公司在地理上是分散的,他们的 HTTP 呼叫可以得到更快的响应。
  5. SRE 还必须明白,由于 DNS 基础设施中没有验证,这些响应可能是欺骗性的。这受到其他协议的保护,如 HTTPS(稍后讨论)。DNSSEC 保护免受伪造或操纵的 DNS 响应。
  6. 过时的 DNS 缓存可能是一个问题。一些应用可能仍在使用过期的 DNS 记录进行 api 调用。这是 SRE 在进行维护时必须警惕的事情。
  7. DNS 负载平衡和服务发现还必须了解 TTL,并且只有在等待 TTL post 对 DNS 记录进行更改后,才能从池中删除服务器。如果不这样做,由于服务器在 TTL 之前被移除,一定部分的流量将会失败。

用户数据报协议(UDP)

原文:https://linkedin.github.io/school-of-sre/level101/linux_networking/udp/

UDP 是传输层协议。DNS 是运行在 UDP 之上的应用层协议(大多数时候)。在进入 UDP 之前,让我们试着理解什么是应用和传输层。DNS 协议由 DNS 客户端(如 dig)和 DNS 服务器(如 named)使用。传输层确保 DNS 请求到达 DNS 服务器进程,同样,响应到达 DNS 客户端进程。多个进程可以在一个系统上运行,它们可以监听任何端口。DNS 服务器通常监听端口号 53。当客户端发出 DNS 请求时,在填充必要的应用负载后,它通过 sendto 系统调用将负载传递给内核。内核随机选取一个端口号( > 1024 )作为源端口号,将 53 作为目的端口号,并将数据包发送到更低层。当服务器端的内核收到数据包时,它检查端口号并将数据包排队到 DNS 服务器进程的应用缓冲区,该进程发出 recvfrom 系统调用并读取数据包。内核的这一过程称为多路复用(将来自多个应用的数据包组合到相同的较低层)和解复用(将来自单个较低层的数据包分离到多个应用)。多路复用和解复用由传输层完成。

UDP 是最简单的传输层协议之一,它只进行复用和解复用。另一个常见的传输层协议 TCP 做许多其他事情,如可靠的通信、流量控制和拥塞控制。UDP 被设计成轻量级的,以很少的开销处理通信。所以除了多路复用和解复用之外,它不做任何事情。如果运行在 UDP 之上的应用需要 TCP 的任何特性,他们必须在他们的应用中实现它

这个来自 python wiki 的示例涵盖了一个示例 UDP 客户端和服务器,其中“Hello World”是一个应用有效负载,它被发送到侦听端口号 5005 的服务器。服务器接收数据包并从客户端打印“Hello World”字符串

SRE 角色中的应用

  1. 如果底层网络速度较慢,UDP 层无法将数据包排入网络层,则来自应用的 sendto syscall 将挂起,直到内核发现其缓冲区被释放。这可能会影响系统的吞吐量。使用 sysctl 变量 net.core.wmem_maxnet.core.wmem_default 增加写内存缓冲值,为来自慢速网络的应用提供一些缓冲
  2. 类似地,如果接收方进程消耗缓冲区的速度很慢,内核必须丢弃由于缓冲区已满而无法排队的数据包。由于 UDP 不保证可靠性,这些丢失的数据包可能会导致数据丢失,除非被应用层跟踪。增加 sysctl 变量 rmem_defaultrmem_max 可以为来自快速发送者的缓慢应用提供一些缓冲。

超文本传输协议(HTTP)

原文:https://linkedin.github.io/school-of-sre/level101/linux_networking/http/

到目前为止,我们只知道 linkedin.com 的 IP 地址。linkedin.com 的 HTML 页面由浏览器渲染的 HTTP 协议提供服务。浏览器向上面确定的服务器 IP 发送 HTTP 请求。Request 有一个动词 GET、PUT、POST,后跟一个路径和查询参数以及键值对行,该键值对提供了有关客户端和客户端功能的信息,如它可以接受的内容和一个主体(通常在 POST 或 PUT 中)

# Eg run the following in your container and have a look at the headers 
curl linkedin.com -v 
* Connected to linkedin.com (108.174.10.10) port 80 (#0)
> GET / HTTP/1.1
> Host: linkedin.com
> User-Agent: curl/7.64.1
> Accept: */*
> 
< HTTP/1.1 301 Moved Permanently
< Date: Mon, 09 Nov 2020 10:39:43 GMT
< X-Li-Pop: prod-esv5
< X-LI-Proto: http/1.1
< Location: https://www.linkedin.com/
< Content-Length: 0
< 
* Connection #0 to host linkedin.com left intact
* Closing connection 0 

这里,第一行 GET 是动词,/是路径,1.1 是 HTTP 协议版本。然后是键值对,它们为服务器提供客户机功能和一些细节。服务器用 HTTP 版本、状态码和状态消息进行响应。状态代码 2xx 表示成功,3xx 表示重定向,4xx 表示客户端错误,5xx 表示服务器端错误。

我们现在就来看看 HTTP/1.0 和 HTTP/1.1 之间的区别。

#On the terminal type
telnet  www.linkedin.com 80
#Copy and paste the following with an empty new line at last in the telnet STDIN
GET / HTTP/1.1
HOST:linkedin.com
USER-AGENT: curl 

这将获得服务器响应,并等待下一个输入,因为到 www.linkedin.com 的底层连接可以被进一步的查询重用。通过 TCP,我们可以了解它的好处。但是在 HTTP/1.0 中,这个连接将在响应之后立即关闭,这意味着必须为每个查询打开新的连接。在一个开放的连接中,HTTP/1.1 只能有一个进行中的请求,但是该连接可以一个接一个地被多个请求重用。HTTP/2.0 优于 HTTP/1.1 的一个好处是,我们可以在同一个连接上有多个正在进行的请求。我们将我们的范围限制在通用 HTTP 上,而不是跳到每个协议版本的复杂性上,但是它们应该很容易理解。

HTTP 被称为无状态协议。这一节我们将试图理解无状态意味着什么。假设我们登录到 linkedin.com,客户端对 linkedin.com 的每个请求都没有用户的上下文,提示用户登录每个页面/资源是没有意义的。HTTP 的这个问题由 COOKIE 解决。当用户登录时,会为用户创建一个会话。该会话标识符通过 SET-COOKIE 头发送给浏览器。浏览器存储 cookie 直到服务器设置的到期时间,并从现在开始为 linkedin.com 发送每个请求的 COOKIE。更多关于 cookies 的细节可以在这里找到。cookie 是像密码一样的重要信息,由于 HTTP 是纯文本协议,任何中间人都可以捕获密码或 cookie,并可能侵犯用户的隐私。类似地,正如在 DNS 中所讨论的,假冒的 linkedin.com IP 可以对用户造成网络钓鱼攻击,用户可以提供 linkedin 的密码来登录恶意站点。为了解决这两个问题,HTTPs 应运而生,而且 HTTPs 必须是强制性的。

HTTPS 必须提供服务器识别和客户端与服务器之间的数据加密。服务器管理员必须生成私有公钥对和证书请求。此证书请求必须由证书颁发机构签名,该机构将证书请求转换为证书。服务器管理员必须更新 web 服务器的证书和私钥。该证书具有关于服务器的细节(如它所服务的域名、到期日)、服务器的公钥。私钥是服务器的秘密,丢失私钥会失去服务器提供的信任。当客户端连接时,客户端发送一个 HELLO。服务器将其证书发送给客户端。客户端通过查看证书是否在其到期时间内、证书是否由可信机构签名以及证书中的主机名是否与服务器相同来检查证书的有效性。这种验证确保服务器是正确的服务器,并且没有网络钓鱼。一旦通过验证,客户端就通过用服务器的公钥加密协商来与服务器协商对称密钥和密码。除了拥有私钥的服务器之外,没有人能够理解这些数据。一旦协商完成,对称密钥和算法被用于进一步加密,该加密只能由客户机和服务器从中解密,因为它们只知道对称密钥和算法。从非对称加密算法切换到对称算法是为了不使客户端设备的资源紧张,因为对称加密通常比非对称加密的资源密集度低。

#Try the following on your terminal to see the cert details like Subject Name(domain name), Issuer details, Expiry date
curl https://www.linkedin.com -v 
* Connected to www.linkedin.com (13.107.42.14) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/cert.pem
  CApath: none
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
} [230 bytes data]
* TLSv1.2 (IN), TLS handshake, Server hello (2):
{ [90 bytes data]
* TLSv1.2 (IN), TLS handshake, Certificate (11):
{ [3171 bytes data]
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
{ [365 bytes data]
* TLSv1.2 (IN), TLS handshake, Server finished (14):
{ [4 bytes data]
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
} [102 bytes data]
* TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):
} [1 bytes data]
* TLSv1.2 (OUT), TLS handshake, Finished (20):
} [16 bytes data]
* TLSv1.2 (IN), TLS change cipher, Change cipher spec (1):
{ [1 bytes data]
* TLSv1.2 (IN), TLS handshake, Finished (20):
{ [16 bytes data]
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: C=US; ST=California; L=Sunnyvale; O=LinkedIn Corporation; CN=www.linkedin.com
*  start date: Oct  2 00:00:00 2020 GMT
*  expire date: Apr  2 12:00:00 2021 GMT
*  subjectAltName: host "www.linkedin.com" matched cert's "www.linkedin.com"
*  issuer: C=US; O=DigiCert Inc; CN=DigiCert SHA2 Secure Server CA
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x7fb055808200)
* Connection state changed (MAX_CONCURRENT_STREAMS == 100)!
  0 82117    0     0    0     0      0      0 --:--:-- --:--:-- --:--:--     0
* Connection #0 to host www.linkedin.com left intact
HTTP/2 200 
cache-control: no-cache, no-store
pragma: no-cache
content-length: 82117
content-type: text/html; charset=utf-8
expires: Thu, 01 Jan 1970 00:00:00 GMT
set-cookie: JSESSIONID=ajax:2747059799136291014; SameSite=None; Path=/; Domain=.www.linkedin.com; Secure
set-cookie: lang=v=2&lang=en-us; SameSite=None; Path=/; Domain=linkedin.com; Secure
set-cookie: bcookie="v=2&70bd59e3-5a51-406c-8e0d-dd70befa8890"; domain=.linkedin.com; Path=/; Secure; Expires=Wed, 09-Nov-2022 22:27:42 GMT; SameSite=None
set-cookie: bscookie="v=1&202011091050107ae9b7ac-fe97-40fc-830d-d7a9ccf80659AQGib5iXwarbY8CCBP94Q39THkgUlx6J"; domain=.www.linkedin.com; Path=/; Secure; Expires=Wed, 09-Nov-2022 22:27:42 GMT; HttpOnly; SameSite=None
set-cookie: lissc=1; domain=.linkedin.com; Path=/; Secure; Expires=Tue, 09-Nov-2021 10:50:10 GMT; SameSite=None
set-cookie: lidc="b=VGST04:s=V:r=V:g=2201:u=1:i=1604919010:t=1605005410:v=1:sig=AQHe-KzU8i_5Iy6MwnFEsgRct3c9Lh5R"; Expires=Tue, 10 Nov 2020 10:50:10 GMT; domain=.linkedin.com; Path=/; SameSite=None; Secure
x-fs-txn-id: 2b8d5409ba70
x-fs-uuid: 61bbf94956d14516302567fc882b0000
expect-ct: max-age=86400, report-uri="https://www.linkedin.com/platform-telemetry/ct"
x-xss-protection: 1; mode=block
content-security-policy-report-only: default-src 'none'; connect-src 'self' www.linkedin.com www.google-analytics.com https://dpm.demdex.net/id lnkd.demdex.net blob: https://linkedin.sc.omtrdc.net/b/ss/ static.licdn.com static-exp1.licdn.com static-exp2.licdn.com static-exp3.licdn.com; script-src 'sha256-THuVhwbXPeTR0HszASqMOnIyxqEgvGyBwSPBKBF/iMc=' 'sha256-PyCXNcEkzRWqbiNr087fizmiBBrq9O6GGD8eV3P09Ik=' 'sha256-2SQ55Erm3CPCb+k03EpNxU9bdV3XL9TnVTriDs7INZ4=' 'sha256-S/KSPe186K/1B0JEjbIXcCdpB97krdzX05S+dHnQjUs=' platform.linkedin.com platform-akam.linkedin.com platform-ecst.linkedin.com platform-azur.linkedin.com static.licdn.com static-exp1.licdn.com static-exp2.licdn.com static-exp3.licdn.com; img-src data: blob: *; font-src data: *; style-src 'self' 'unsafe-inline' static.licdn.com static-exp1.licdn.com static-exp2.licdn.com static-exp3.licdn.com; media-src dms.licdn.com; child-src blob: *; frame-src 'self' lnkd.demdex.net linkedin.cdn.qualaroo.com; manifest-src 'self'; report-uri https://www.linkedin.com/platform-telemetry/csp?f=g
content-security-policy: default-src *; connect-src 'self' https://media-src.linkedin.com/media/ www.linkedin.com s.c.lnkd.licdn.com m.c.lnkd.licdn.com s.c.exp1.licdn.com s.c.exp2.licdn.com m.c.exp1.licdn.com m.c.exp2.licdn.com wss://*.linkedin.com dms.licdn.com https://dpm.demdex.net/id lnkd.demdex.net blob: https://accounts.google.com/gsi/status https://linkedin.sc.omtrdc.net/b/ss/ www.google-analytics.com static.licdn.com static-exp1.licdn.com static-exp2.licdn.com static-exp3.licdn.com media.licdn.com media-exp1.licdn.com media-exp2.licdn.com media-exp3.licdn.com; img-src data: blob: *; font-src data: *; style-src 'unsafe-inline' 'self' static-src.linkedin.com *.licdn.com; script-src 'report-sample' 'unsafe-inline' 'unsafe-eval' 'self' spdy.linkedin.com static-src.linkedin.com *.ads.linkedin.com *.licdn.com static.chartbeat.com www.google-analytics.com ssl.google-analytics.com bcvipva02.rightnowtech.com www.bizographics.com sjs.bizographics.com js.bizographics.com d.la4-c1-was.salesforceliveagent.com slideshare.www.linkedin.com https://snap.licdn.com/li.lms-analytics/ platform.linkedin.com platform-akam.linkedin.com platform-ecst.linkedin.com platform-azur.linkedin.com; object-src 'none'; media-src blob: *; child-src blob: lnkd-communities: voyager: *; frame-ancestors 'self'; report-uri https://www.linkedin.com/platform-telemetry/csp?f=l
x-frame-options: sameorigin
x-content-type-options: nosniff
strict-transport-security: max-age=2592000
x-li-fabric: prod-lva1
x-li-pop: afd-prod-lva1
x-li-proto: http/2
x-li-uuid: Ybv5SVbRRRYwJWf8iCsAAA==
x-msedge-ref: Ref A: CFB9AC1D2B0645DDB161CEE4A4909AEF Ref B: BOM02EDGE0712 Ref C: 2020-11-09T10:50:10Z
date: Mon, 09 Nov 2020 10:50:10 GMT

* Closing connection 0 

这里,我的系统在这个文件/etc/ssl/cert.pem 中有一个它信任的证书颁发机构的列表。它还通过查看过期日期来确保证书没有过期。它还使用/etc/ssl/cert.pem 中颁发者 Digicert 的公钥来验证证书上的签名。完成后,它将使用 www.linkedin.com 的公钥与对称密钥协商密码 TLS _ ECD he _ RSA _ WITH _ AES _ 256 _ GCM _ sha 384。包括第一个 HTTP 请求在内的后续数据传输使用相同的密码和对称密钥。

传输控制协议(TCP)

原文:https://linkedin.github.io/school-of-sre/level101/linux_networking/tcp/

TCP 是像 UDP 一样的传输层协议,但是它保证可靠性、流量控制和拥塞控制。TCP 通过使用序列号来保证可靠的传输。TCP 连接是通过三次握手建立的。在我们的例子中,客户机发送一个 SYN 包和它计划使用的起始序列号,服务器确认这个 SYN 包并发送一个 SYN 和它的序列号。一旦客户端确认了 syn 数据包,连接就建立了。一旦相关方收到对该序列的确认,则从此传输的每个数据都被认为是可靠地传递了

3-way handshake

#To understand handshake run packet capture on one bash session
tcpdump -S -i any port 80
#Run curl on one bash session
curl www.linkedin.com 

tcpdump-3way

在这里,客户端发送一个由[S] flag 表示的 syn 标志,序列号为 1522264672。服务器用 ack 确认收到 SYN。]标志和用于其序列号的 Syn 标志。服务器使用序列号 1063230400,并向客户机确认它期望的序列号 1522264673(客户机序列+1)。客户端向服务器发送一个零长度确认数据包(服务器序列+1 ),连接建立。这被称为三次握手。此后,客户端发送一个 76 字节长的数据包,并将其序列号递增 76。服务器发送一个 170 字节的响应并关闭连接。这就是我们所说的 HTTP/1.1 和 HTTP/1.0 之间的区别。在 HTTP/1.1 中,可以重用相同的连接,这减少了每个 HTTP 请求的 3 次握手的开销。如果客户端和服务器之间的数据包丢失,服务器不会向客户端发送 ack,客户端将重试发送数据包,直到收到 ACK。这保证了可靠性。流量控制由每个段中的 win size 字段建立。win 大小表示内核中可用的 TCP 缓冲区长度,可用于缓冲接收到的数据段。大小为 0 意味着接收方需要从套接字缓冲区捕捉大量的延迟,发送方必须暂停发送数据包,以便接收方能够应付。这种流量控制避免了慢速接收者和快速发送者的问题

TCP 还进行拥塞控制,确定在没有 ack 的情况下可以传输多少数据段。Linux 为我们提供了配置拥塞控制算法的能力,我们不在这里讨论。

关闭连接时,客户机/服务器调用 close syscall。让我们假设客户这样做。客户端的内核将发送一个 FIN 包给服务器。在服务器应用调用 close syscall 之前,服务器内核无法关闭连接。一旦服务器应用调用 close,服务器也发送 FIN 数据包,客户端进入 2 * MSS(120 秒)的时间等待状态,以便该套接字在该时间段内不能重复使用,以防止由于分散的陈旧数据包导致任何 TCP 状态损坏。

Connection tearing

用我们的 TCP 和 HTTP 知识武装起来,让我们看看 sre 在他们的角色中是如何使用它的

SRE 角色中的应用

  1. 使用负载平衡器扩展 HTTP 性能需要对 TCP 和 HTTP 有一致的了解。有种不同的负载平衡,如 L4、L7 负载平衡、直接服务器返回等。根据性能和合规性需求,HTTPs 卸载可以在负载平衡器上完成,也可以直接在服务器上完成。
  2. 像我们对 UDP 那样调整 rmem 和 wmem 的 sysctl 变量可以提高发送方和接收方的吞吐量。
  3. Sysctl 变量 tcp_max_syn_backlog 和 socket 变量 somax_conn 确定在 app 调用 accept syscall 之前内核可以完成 3 次握手的连接数。这在单线程应用中非常有用。一旦积压满了,新的连接就保持在 SYN_RCVD 状态(当您运行 netstat 时),直到应用调用接受 syscall
  4. 如果有太多的短期连接,应用可能会用完文件描述符。深入研究 tcp_reuse 和 tcp_recycle 有助于减少处于时间等待状态的时间(它有自己的风险)。让应用重用连接池而不是创建临时连接也会有所帮助
  5. 通过查看指标了解性能瓶颈,并分类是应用还是网络方面的问题。示例:太多套接字处于 Close_wait 状态是应用的问题,而重新传输可能是网络或操作系统堆栈的问题,而不是应用本身的问题。理解基本原理可以帮助我们缩小瓶颈所在

IP 路由和数据链路层

原文:https://linkedin.github.io/school-of-sre/level101/linux_networking/ipr/

我们将探究离开客户端的数据包如何到达服务器,反之亦然。当数据包到达 IP 层时,传输层填充源端口、目的端口。IP/网络层填充目的地 IP(从 DNS 发现),然后在路由表中查找到目的地 IP 的路由。

#Linux route -n command gives the default routing table
route -n 
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.17.0.1      0.0.0.0         UG    0      0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0 

这里,目的地 IP 与 Genmask 进行逐位 AND 运算,如果答案是表中的目的地部分,则选择该网关和接口进行路由。这里 linkedin.com 的 is 108.174.10.0 与 255.255.255.0 进行 AND 运算,我们得到的答案是 108.174.10.0,它与路由表中的任何目的地都不匹配。然后 Linux 用 0.0.0.0 对目的 IP 进行 AND 运算,我们得到 0.0.0.0。此答案与默认行匹配

路由表按照 genmask 中设置的 1 的多个八位位组的顺序进行处理,如果没有匹配项,genmask 0.0.0.0 是默认路由。在此操作结束时,Linux 发现数据包必须通过 eth0 发送到下一跳 172.17.0.1。数据包的源 IP 将被设置为接口 eth0 的 IP。现在,要将数据包发送到 172.17.0.1,linux 必须计算出 172.17.0.1 的 MAC 地址。MAC 地址是通过查看存储 IP 地址和 MAC 地址之间转换的内部 arp 缓存来计算的。如果存在缓存未命中,Linux 会在内部网络中广播 ARP 请求,询问谁拥有 172.17.0.1。IP 的所有者发送一个 ARP 响应,该响应由内核缓存,内核通过将源 mac 地址设置为 eth0 的 mac 地址和我们刚才获得的目的 mac 地址 172.17.0.1,将数据包发送到网关。在每一跳中都遵循类似的路由查找过程,直到分组到达实际的服务器。传输层及其上层仅在终端服务器上发挥作用。仅在中间跳跃期间,直到涉及 IP/网络层。

Screengrab for above explanation

我们在路由表中看到的一个奇怪的网关是 0.0.0.0。这个网关意味着发送数据包不需要第 3 层(网络层)跳。源和目的都在同一个网络中。内核必须计算出目的地的 mac,并适当地填充源 mac 和目的地 MAC,然后将数据包发送出去,以便它到达目的地时中间没有任何第 3 层跳

正如我们在其他模块中所做的那样,让我们用 SRE 使用案例来结束本次会议

SRE 角色中的应用

  1. 一般来说,路由表是由 DHCP 填充的,随意摆弄并不是一个好习惯。可能有这样的原因,人们不得不绕开路由表,但只有在绝对必要时才选择那条路径
  2. 更好地理解错误消息,如“没有到主机的路由”错误可能意味着找不到目的主机的 mac 地址,也可能意味着目的主机关闭
  3. 在极少数情况下,查看 ARP 表可以帮助我们了解是否存在 IP 冲突,即错误地将相同的 IP 分配给两台主机,从而导致意外行为

总结

原文:https://linkedin.github.io/school-of-sre/level101/linux_networking/conclusion/

至此,我们已经完全遍历了 TCP/IP 协议栈。我们希望当一个人在浏览器中打开任何网站发布课程时,都会有一个不同的视角。

在本课程中,我们还剖析了 SRE 负责的这一管道中的共同任务。

训练后练习

  1. 在开发环境中设置自己的 DNS 解析器,作为 example.com 的权威 DNS 服务器和其他域的转发器。更新 resolv.conf 以使用 localhost 中运行的新 DNS 解析程序
  2. 在 localhost 中设置站点 dummy.example.com,并使用自签名证书运行 web 服务器。更新受信任的 CA 或传递自签名 CA 的公钥作为参数,以便 curl https://dummy.example.com-v 正常工作,没有自签名证书警告
  3. 更新路由表,使用同一网络中的另一台主机(容器/虚拟机)作为 8.8.8.8/32 的网关,并运行 ping 8.8.8.8 命令。在新网关上捕获数据包,查看 L3 跳是否按预期工作(可能需要禁用 icmp_redirect)

Python 和 Web

Python 和 Web

原文:https://linkedin.github.io/school-of-sre/level101/python_web/intro/

先决条件

  • 对 python 语言的基本理解。
  • 基本熟悉 flask 框架。

从本课程中可以期待什么

本课程分为两个高级部分。在第一部分中,假设熟悉 python 语言的基本操作和语法用法,我们将更深入地理解 python 作为一种语言。我们将 python 与其他编程语言进行比较,比如 Java 和 c。我们还将探索 python 对象的概念,并在此基础上探索 Python 的特性,比如 decorators。

在围绕 web 的第二部分中,假设您熟悉 Flask 框架,我们将从 socket 模块开始,处理 HTTP 请求。这将揭开 flask 这样的框架如何在内部工作的神秘面纱。

为了向课程介绍 SRE 风味,我们将设计、开发和部署(理论上)一个 URL 缩短应用。我们将强调整个过程中作为上述应用/服务的 SRE 更重要的部分。

本课程不包括哪些内容

对 python 内部和高级 python 有广泛的了解。

实验室环境设置

安装最新版本的 python

课程内容

  1. Python 语言
    1. 一些 Python 概念
    2. Python 陷阱
  2. Python 和 Web
    1. 插座
    2. 烧瓶
  3. 短网址应用
    1. 设计
    2. 缩放应用
    3. 监控 App

Python 语言

假设你懂一点 C/C++和 Java,让我们试着在这两种语言和 python 的背景下讨论下面的问题。你可能听说过 C/C++是一种编译语言,而 python 是一种解释语言。一般来说,对于编译语言,我们首先编译程序,然后运行可执行文件,而对于 python,我们像python hello_world.py一样直接运行源代码。而 Java 作为一种解释型语言,仍然有一个单独的编译步骤和运行步骤。那么真正的区别是什么呢?

编译与解释

这对你来说可能听起来有点奇怪:python 在某种程度上是一种编译语言!Python 内置了编译器!这在 java 的例子中很明显,因为我们使用一个单独的命令 ie: javac helloWorld.java来编译它,它将产生一个.class文件,我们称之为字节码。python 与此非常相似。这里的一个区别是,运行 python 程序不需要单独的编译命令/二进制文件。

那么,java 和 python 之间有什么区别呢?嗯,Java 的编译器更加严格和复杂。您可能知道 Java 是一种静态类型语言。因此,编译器的编写方式可以在编译时验证与类型相关的错误。虽然 python 是一种动态语言,但在程序运行之前,类型是未知的。所以在某种程度上,python 编译器是哑的(或者说,不那么严格)。但是当 python 程序运行时,确实有一个编译步骤。您可能见过扩展名为.pyc的 python 字节码文件。下面是如何看到给定 python 程序的字节码。

# Create a Hello World
$ echo "print('hello world')" > hello_world.py

# Making sure it runs
$ python3 hello_world.py
hello world

# The bytecode of the given program
$ python -m dis hello_world.py
 1           0 LOAD_NAME                0 (print)
             2 LOAD_CONST               0 ('hello world')
             4 CALL_FUNCTION            1
             6 POP_TOP
             8 LOAD_CONST               1 (None)
            10 RETURN_VALUE 

点击阅读更多关于 dis 模块的信息

现在来看 C/C++,当然有一个编译器。但是输出与 java/python 编译器产生的不同。编译一个 C 程序会产生我们所知的机器码。与字节码相反。

运行程序

我们知道编译涉及到我们正在讨论的所有 3 种语言。只是编译器本质上不同,它们输出不同类型的内容。在 C/C++的情况下,输出是可以被你的操作系统直接读取的机器代码。当你执行这个程序时,你的操作系统会知道如何运行它。但是字节码却不是这样。

那些字节码是特定于语言的。Python 有自己定义的字节码集(更多内容在dis模块中), java 也是如此。所以很自然地,你的操作系统将不知道如何运行它。为了运行这个字节码,我们有一个叫做虚拟机的东西。即:JVM 或 Python VM (CPython,Jython)。这些所谓的虚拟机是能够读取字节码并在给定的操作系统上运行的程序。Python 有多个虚拟机可用。Cpython 是用 C 语言实现的 python VM,类似地,Jython 是 python VM 的 Java 实现。最终,他们应该能够理解 python 语言的语法,能够将其编译成字节码,并能够运行该字节码。你可以用任何语言实现 python 虚拟机!(而人们这么做,只是因为可以做到)

 The Operating System

                                                              +------------------------------------+
                                                              |                                    |
                                                              |                                    |
                                                              |                                    |
hello_world.py                    Python bytecode             |         Python VM Process          |
                                                              |                                    |
+----------------+                +----------------+          |         +----------------+         |
|print(...       |   COMPILE      |LOAD_CONST...   |          |         |Reads bytecode  |         |
|                +--------------->+                +------------------->+line by line    |         |
|                |                |                |          |         |and executes.   |         |
|                |                |                |          |         |                |         |
+----------------+                +----------------+          |         +----------------+         |
                                                              |                                    |
                                                              |                                    |
                                                              |                                    |
hello_world.c                     OS Specific machinecode     |         A New Process              |
                                                              |                                    |
+----------------+                +----------------+          |         +----------------+         |
|void main() {   |   COMPILE      | binary contents|          |         | binary contents|         |
|                +--------------->+                +------------------->+                |         |
|                |                |                |          |         |                |         |
|                |                |                |          |         |                |         |
+----------------+                +----------------+          |         +----------------+         |
                                                              |         (binary contents           |
                                                              |         runs as is)                |
                                                              |                                    |
                                                              |                                    |
                                                              +------------------------------------+ 

上图需要注意两点:

  1. 通常,当我们运行一个 python 程序时,会启动一个 python VM 进程,该进程读取 python 源代码,将其编译成字节码,然后在一个步骤中运行它。编译不是一个单独的步骤。仅出于说明目的示出。
  2. 为类 C 语言生成的二进制文件并不完全按照原样运行。由于有多种类型的二进制文件(例如:ELF),运行二进制文件需要更复杂的步骤,但我们不会深入讨论,因为所有这些都是在操作系统级别完成的。

一些 Python 概念

原文:https://linkedin.github.io/school-of-sre/level101/python_web/python-concepts/

虽然期望您了解 python 及其基本语法,但是让我们讨论一些基本概念,这将帮助您更好地理解 python 语言。

Python 中的一切都是对象。

这包括函数、列表、字典、类、模块、运行函数(函数定义的实例),一切。在 CPython 中,这意味着每个对象都有一个底层结构变量。

在 python 当前的执行上下文中,所有变量都存储在一个 dict 中。这将是一个字符串到对象的映射。如果您在当前上下文中定义了一个函数和一个浮点变量,下面是它在内部的处理方式。

>>> float_number=42.0
>>> def foo_func():
...     pass
...

# NOTICE HOW VARIABLE NAMES ARE STRINGS, stored in a dict
>>> locals()
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'float_number': 42.0, 'foo_func': <function foo_func at 0x1055847a0>} 

Python 函数

因为函数也是对象,我们可以看到函数包含的所有属性如下

>>> def hello(name):
...     print(f"Hello, {name}!")
...
>>> dir(hello)
['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__',
'__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__',
'__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__',
'__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__',
'__subclasshook__'] 

虽然有很多,让我们来看看一些有趣的

globals

顾名思义,这个属性引用了全局变量。如果你需要知道在这个函数的范围内所有的全局变量是什么,这将告诉你。看看函数是如何在全局变量中发现新变量的

>>> hello.__globals__
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'hello': <function hello at 0x7fe4e82554c0>}

# adding new global variable
>>> GLOBAL="g_val"
>>> hello.__globals__
{'__name__': '__main__', '__doc__': None, '__package__': None, '__loader__': <class '_frozen_importlib.BuiltinImporter'>, '__spec__': None, '__annotations__': {}, '__builtins__': <module 'builtins' (built-in)>, 'hello': <function hello at 0x7fe4e82554c0>, 'GLOBAL': 'g_val'} 

代码

这个很有意思!因为 python 中的一切都是对象,这也包括字节码。编译后的 python 字节码是一个 python 代码对象。这可以通过这里的__code__属性访问。一个函数有一个相关的代码对象,它携带一些有趣的信息。

# the file in which function is defined
# stdin here since this is run in an interpreter
>>> hello.__code__.co_filename
'<stdin>'

# number of arguments the function takes
>>> hello.__code__.co_argcount
1

# local variable names
>>> hello.__code__.co_varnames
('name',)

# the function code's compiled bytecode
>>> hello.__code__.co_code
b't\x00d\x01|\x00\x9b\x00d\x02\x9d\x03\x83\x01\x01\x00d\x00S\x00' 

您可以通过>>> dir(hello.__code__)登记更多的代码属性

装修工

与函数相关,python 还有一个特性叫做 decorators。让我们看看这是如何工作的,记住everything is an object

下面是一个装饰示例:

>>> def deco(func):
...     def inner():
...             print("before")
...             func()
...             print("after")
...     return inner
...
>>> @deco
... def hello_world():
...     print("hello world")
...
>>>
>>> hello_world()
before
hello world
after 

这里使用了@deco语法来修饰hello_world函数。本质上和做是一样的

>>> def hello_world():
...     print("hello world")
...
>>> hello_world = deco(hello_world) 

deco函数内部的内容可能看起来很复杂。让我们试着揭开它。

  1. 功能hello_world已创建
  2. 它被传递给deco功能
  3. deco创建新功能
    1. 这个新功能就是调用hello_world功能
    2. 还做了一些其他的事情
  4. deco返回新创建的函数
  5. hello_world替换为上述功能

为了更好的理解,让我们把它形象化

 BEFORE                   function_object (ID: 100)

       "hello_world"            +--------------------+
               +                |print("hello_world")|
               |                |                    |
               +--------------> |                    |
                                |                    |
                                +--------------------+

       WHAT DECORATOR DOES

       creates a new function (ID: 101)
       +---------------------------------+
       |input arg: function with id: 100 |
       |                                 |
       |print("before")                  |
       |call function object with id 100 |
       |print("after")                   |
       |                                 |
       +---------------------------------+
                                   ^
                                   |
       AFTER                       |
                                   |
                                   |
       "hello_world" +-------------+ 

注意hello_world名称是如何指向一个新的函数对象的,但是这个新的函数对象知道原始函数的引用(ID)。

一些问题

  • 虽然用 python 构建原型非常快,并且有大量可用的库,但是随着代码库复杂性的增加,类型错误变得更加常见,并且变得难以处理。(这个问题有解决方案,比如 python 中的类型注释。结帐 mypy 。)
  • 因为 python 是动态类型语言,这意味着所有类型都是在运行时确定的。这使得 python 与其他静态类型语言相比运行速度非常慢。
  • Python 有一种叫做 GIL (全局解释器锁)的东西,这是利用多个 CPU 内核进行并行计算的一个限制因素。
  • python 做的一些奇怪的事情:https://github.com/satwikkansal/wtfpython

Python、Web 和 Flask

原文:https://linkedin.github.io/school-of-sre/level101/python_web/python-web-flask/

过去,网站很简单。它们是简单的静态 html 内容。一个 web 服务器将监听一个定义的端口,根据收到的 HTTP 请求,它将从磁盘读取文件并返回它们作为响应。但从那以后,复杂性发生了变化,网站现在是动态的。根据请求的不同,需要执行多个操作,比如从数据库读取数据或调用其他 API,最后返回一些响应(HTML 数据、JSON 内容等。)

由于服务 web 请求不再是像从磁盘读取文件并返回内容那样简单的任务,我们需要处理每个 http 请求,以编程方式执行一些操作并构造一个响应。

套接字

尽管我们有 flask 这样的框架,HTTP 仍然是一个基于 TCP 协议的协议。因此,让我们设置一个 TCP 服务器,发送一个 HTTP 请求,并检查请求的有效负载。请注意,这不是一个关于套接字编程的教程,但我们在这里所做的是在底层检查 HTTP 协议,看看它的内容是什么样子的。(Ref:real Python 上的 Python 套接字编程(指南)

import socket

HOST = '127.0.0.1'  # Standard loopback interface address (localhost)
PORT = 65432        # Port to listen on (non-privileged ports are > 1023)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
   s.bind((HOST, PORT))
   s.listen()
   conn, addr = s.accept()
   with conn:
       print('Connected by', addr)
       while True:
           data = conn.recv(1024)
           if not data:
               break
           print(data) 

然后,我们在网络浏览器中打开localhost:65432,输出如下:

Connected by ('127.0.0.1', 54719)
b'GET / HTTP/1.1\r\nHost: localhost:65432\r\nConnection: keep-alive\r\nDNT: 1\r\nUpgrade-Insecure-Requests: 1\r\nUser-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.83 Safari/537.36 Edg/85.0.564.44\r\nAccept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\nSec-Fetch-Site: none\r\nSec-Fetch-Mode: navigate\r\nSec-Fetch-User: ?1\r\nSec-Fetch-Dest: document\r\nAccept-Encoding: gzip, deflate, br\r\nAccept-Language: en-US,en;q=0.9\r\n\r\n' 

仔细检查,内容看起来会像 HTTP 协议的格式。即:

HTTP_METHOD URI_PATH HTTP_VERSION
HEADERS_SEPARATED_BY_SEPARATOR 

因此,尽管它是一个字节块,知道 http 协议规范,你可以解析这个字符串(即:被\r\n分割)并从中获得有意义的信息。

Flask 和其他类似的框架做的和我们在上一节讨论的差不多(增加了更多的复杂性)。它们监听 TCP 套接字上的端口,接收 HTTP 请求,根据协议格式解析数据,并以方便的方式提供给你。

ie:你可以通过request.headers访问 flask 中的头,通过/r/n分割上面的有效载荷,就可以得到这个头,正如 http 协议中定义的。

另一个例子:我们通过@app.route("/hello")在 flask 中注册路径。flask 要做的是在内部维护一个注册表,它将把/hello映射到你所修饰的函数。现在,每当一个请求通过/hello路径(第一行的第二个组件,由空格分隔)到来时,flask 调用注册的函数并返回函数返回的任何内容。

其他语言的所有其他 web 框架也是如此。它们都基于相似的原理工作。他们主要做的是理解 HTTP 协议,解析 HTTP 请求数据,给我们程序员一个很好的接口来处理 HTTP 请求。

没有那么多魔法,不是吗?

短网址应用

原文:https://linkedin.github.io/school-of-sre/level101/python_web/url-shorten-app/

让我们使用 flask 构建一个非常简单的 URL 缩短应用,并尝试整合开发过程的所有方面,包括可靠性方面。我们将不会建立用户界面,我们将拿出一个最小的 API 集,这将足以让应用运行良好。

设计

我们不直接跳到编码。我们做的第一件事是收集需求。想出一个办法。让同行评审方法/设计。演进、迭代、记录决策和权衡。然后最终实现。虽然我们不会在这里完成完整的设计文档,但我们会在这里提出一些对设计很重要的问题。

1.高级操作和 API 端点

由于这是一个 URL 缩短应用,我们将需要一个 API 来生成缩短链接给定一个原始链接。以及接受缩短链接并重定向到原始 URL 的 API/端点。我们不包括应用的用户方面,以保持事情最少。这两个 API 应该使任何人都可以使用应用。

2.怎么缩短?

给定一个 url,我们将需要生成它的一个缩短版本。一种方法是为每个链接使用随机字符。另一件可以做的事情是使用某种散列算法。这里的好处是,我们将对相同的链接重用相同的散列。例如:如果很多人都在缩短https://www.linkedin.com,那么他们都将有相同的值,相比之下,如果选择随机字符,数据库中会有多个条目。

哈希冲突怎么办?即使在随机字符方法中,尽管概率较小,哈希冲突也可能发生。我们需要留意它们。在这种情况下,我们可能希望在字符串前/后添加一些随机值以避免冲突。

此外,哈希算法的选择也很重要。我们需要分析算法。它们的 CPU 需求和特性。选一个最适合的。

3.URL 有效吗?

给定一个要缩短的 URL,我们如何验证该 URL 是否有效?我们甚至验证或确认了吗?可以完成的一个基本检查是查看 URL 是否匹配 URL 的正则表达式。为了更进一步,我们可以尝试打开/访问网址。但是这里有一些问题。

  1. 我们需要定义成功的标准。即:HTTP 200 表示有效。
  2. 私有网络中的 URL 是什么?
  3. URL 暂时宕机怎么办?

4.储存;储备

最后是存储。随着时间的推移,我们将在哪里存储我们将生成的数据?有多种数据库解决方案可供选择,我们需要选择最适合该应用的解决方案。像 MySQL 这样的关系数据库将是一个公平的选择,但一定要检查 SRE 学院的 SQL 数据库部分和 NoSQL 数据库部分,以获得更深入的见解,做出更明智的决定。

5.其他的

我们不考虑用户进入我们的应用和其他可能的功能,如速率限制,自定义链接等,但它最终会随着时间的推移。根据需求,它们也可能需要被合并。

下面给出了最少的工作代码供参考,但我鼓励你提出自己的代码。

from flask import Flask, redirect, request

from hashlib import md5

app = Flask("url_shortener")

mapping = {}

@app.route("/shorten", methods=["POST"])
def shorten():
    global mapping
    payload = request.json

    if "url" not in payload:
        return "Missing URL Parameter", 400

    # TODO: check if URL is valid

    hash_ = md5()
    hash_.update(payload["url"].encode())
    digest = hash_.hexdigest()[:5]  # limiting to 5 chars. Less the limit more the chances of collission

    if digest not in mapping:
        mapping[digest] = payload["url"]
        return f"Shortened: r/{digest}\n"
    else:
        # TODO: check for hash collission
        return f"Already exists: r/{digest}\n"

@app.route("/r/<hash_>")
def redirect_(hash_):
    if hash_ not in mapping:
        return "URL Not Found", 404
    return redirect(mapping[hash_])

if __name__ == "__main__":
    app.run(debug=True)

"""
OUTPUT:

===> SHORTENING

$ curl localhost:5000/shorten -H "content-type: application/json" --data '{"url":"https://linkedin.com"}'
Shortened: r/a62a4

===> REDIRECTING, notice the response code 302 and the location header

$ curl localhost:5000/r/a62a4 -v
* Uses proxy env variable NO_PROXY == '127.0.0.1'
*   Trying ::1...
* TCP_NODELAY set
* Connection failed
* connect to ::1 port 5000 failed: Connection refused
*   Trying 127.0.0.1...
* TCP_NODELAY set
* Connected to localhost (127.0.0.1) port 5000 (#0)
> GET /r/a62a4 HTTP/1.1
> Host: localhost:5000
> User-Agent: curl/7.64.1
> Accept: */*
>
* HTTP 1.0, assume close after body
< HTTP/1.0 302 FOUND
< Content-Type: text/html; charset=utf-8
< Content-Length: 247
< Location: https://linkedin.com
< Server: Werkzeug/0.15.4 Python/3.7.7
< Date: Tue, 27 Oct 2020 09:37:12 GMT
<
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2 Final//EN">
<title>Redirecting...</title>
<h1>Redirecting...</h1>
* Closing connection 0
<p>You should be redirected automatically to target URL: <a href="https://linkedin.com">https://linkedin.com</a>.  If not click the link.
""" 

总结

原文:https://linkedin.github.io/school-of-sre/level101/python_web/sre-conclusion/

扩展应用

设计和开发只是旅程的一部分。我们迟早需要建立持续集成和持续交付管道。我们必须在某个地方部署这个应用。

最初,我们可以在任何云提供商的一台虚拟机上部署这款应用。但这是一个Single point of failure我们作为一个 SRE 人(甚至作为一个工程师)绝不允许的事情。因此,这方面的改进可以是在负载平衡器后面部署多个应用实例。这当然可以防止一台机器停机的问题。

这里的扩展意味着在负载均衡器后面添加更多的实例。但是这只能扩展到某一点。之后,系统中的其他瓶颈将开始出现。ie: DB 会成为瓶颈,或者可能是负载均衡器本身。你怎么知道瓶颈是什么?您需要能够观察到应用架构的每个方面。

只有在你有了度量标准之后,你才能知道哪里出了问题。可以测量的,就可以固定!

从 SRE 学院的可扩展性模块中获得关于可扩展性的更深入的见解,并在浏览该模块后,将您的学习和收获应用到该应用中。想想我们将如何使这个应用在地理上分布,高度可用和可扩展。

监控策略

一旦我们部署了应用。它会正常工作的。但不会永远。可靠性是我们工作的主题,我们通过以某种方式进行设计来使系统可靠。但是事情还是会有转机。机器会失灵。磁盘的行为会很奇怪。有问题的代码将被推向生产。所有这些可能的情况都会降低系统的可靠性。那我们该怎么办?我们班长!

我们密切关注系统的健康状况,如果有任何事情不按预期进行,我们希望自己得到提醒。

现在让我们考虑一下给定的 url 缩短应用。我们需要监控它。我们希望在出现问题时得到通知。但是我们首先需要决定什么是我们想要关注的东西。

  1. 因为它是一个服务于 HTTP 请求的 web 应用,所以我们希望关注 HTTP 状态代码和延迟
  2. 请求量也是一个很好的候选,如果应用接收到不寻常的流量,可能是出了什么问题。
  3. 我们还希望密切关注数据库,这取决于所选择的数据库解决方案。查询时间、容量、磁盘使用情况等。
  4. 最后,还需要一些外部监控,从数据中心之外的设备运行定期测试。这模拟了客户,并确保从客户的角度来看,系统按预期工作。

SRE 角色中的应用

在 SRE 的世界里,python 是一种广泛使用的语言。用于为各种目的开发的小脚本和工具。由于 SRE 开发的工具可以处理基础设施的关键部分,并且具有强大的功能(使事情变得简单),所以在使用编程语言及其特性时,知道自己在做什么是很重要的。在调试问题时,了解语言及其特征也同样重要。作为一名对 python 语言有着更深理解的 SRE,它对我调试非常隐蔽的 bug 有很大帮助,并且在做出某些设计决策时通常更加了解和见多识广。

虽然开发工具可能是也可能不是 SRE 工作的一部分,但支持工具或服务更可能是日常职责。构建应用或工具只是生产化的一小部分。虽然应用本身的设计肯定会使其更加健壮,但作为一名 SRE,一旦部署并运行,您就要对其可靠性和稳定性负责。为了确保这一点,您需要首先了解应用,然后想出一个策略来适当地监控它,并为各种故障场景做好准备。

可选练习

  1. 制作一个装饰器,根据输入参数缓存函数返回值。
  2. 在任何云提供商上托管 URL 缩短应用。
  3. 使用许多可用的工具,如 catchpoint、datadog 等,设置监控。
  4. 在 TCP 套接字上创建一个最小的类似烧瓶的框架。

总结

在第一部分,本模块旨在让您更加了解当您选择 python 作为编程语言时会发生什么,以及当您运行 python 程序时会发生什么。有了 python 如何在内部处理对象的知识,python 中许多看似神奇的东西将开始变得更有意义。

第二部分将首先解释像 flask 这样的框架如何使用 TCP 和 HTTP 这样的协议的现有知识来工作。然后,它触及应用开发生命周期的整个生命周期,包括它的 SRE 部分。虽然考虑的设计和建筑领域不会详尽无遗,但它将很好地概述作为 SRE 也很重要的事情以及它们为什么重要。

数据

关系数据库

关系数据库

原文:https://linkedin.github.io/school-of-sre/level101/databases_sql/intro/

先决条件

从本课程中可以期待什么

您将了解什么是关系数据库,它们的优势,以及一些 MySQL 特有的概念。

本课程不包括哪些内容

  • 深入实施细节

  • 标准化、分片等高级主题

  • 管理的特定工具

介绍

数据库系统的主要目的是管理数据。这包括存储、添加新数据、删除未使用的数据、更新现有数据、在合理的响应时间内检索数据、保持系统运行的其他维护任务等。

预读

RDBMS 概念

课程内容

关键概念

原文:https://linkedin.github.io/school-of-sre/level101/databases_sql/concepts/

  • 关系数据库用于数据存储。即使是文件也可以用来存储数据,但是关系数据库的设计有特定的目标:

    • 效率
    • 易于访问和管理
    • 组织
    • 处理数据之间的关系(表示为表格)
  • 事务:一个工作单元,可以包含多个一起执行的语句

  • 酸性

    保证数据库事务数据完整性的一组属性

    • 原子性:每个事务都是原子性的(成功或完全失败)
    • 一致性:事务只产生有效状态(包括规则、约束、触发器等)。)
    • 隔离:在并发系统中,每个事务都独立于其他事务安全地执行
    • 持久性:已完成的事务不会因为任何后来的失败而丢失

    下面举几个例子来说明上述性质。

    • 账户 a 的余额为₹200,账户 b 的余额为₹400.账户 a 将₹100 转移到账户 b。该交易从发送方扣除,并在接收方的余额中增加。如果第一个操作成功通过,而第二个失败,a 的余额将是₹100,而 b 将拥有₹400 而不是₹500.DB 中的原子性确保部分失败的事务被回滚。
    • 如果上面的第二个操作失败,它会使 DB 不一致(操作前后的帐户余额总和不相同)。一致性确保这种情况不会发生。
    • 有三个操作,一个是为 a 的账户计算利息,另一个是将利息加到 a 的账户,然后将₹100 从 b 转移到 a。在没有隔离保证的情况下,同时执行这三个操作每次都可能导致不同的结果。
    • 如果系统在事务写入磁盘之前崩溃,会发生什么情况?耐久性确保在恢复期间正确应用更改。
    • 关系数据
    • 表格代表关系
    • 列(字段)代表属性
    • 行是单独的记录
    • 模式描述了数据库的结构
    • 结构化查询语言

    一种用于交互和管理数据的查询语言。

    CRUD 操作 -创建、读取、更新、删除查询

    管理操作-创建数据库/表/索引等,备份,导入/导出,用户,访问控制

    练习:将以下查询分为四种类型- DDL(定义)、DML(操作)、DCL(控制)和 TCL(事务),并详细解释。

    insert, create, drop, delete, update, commit, rollback, truncate, alter, grant, revoke 
    

    您可以在实验室部分练习这些。

  • 限制

    可以存储的数据的规则。如果违反表中定义的任何一项,查询将失败。

    主键:包含唯一值的一列或多列,不能包含空值。一个表只能有一个主键。默认情况下会创建一个索引。

    外键:将两个表链接在一起。其值与另一个表中的主键匹配\ Not null:不允许空值\ Unique:列的值在所有行中必须是唯一的\ Default:如果在插入期间没有指定任何值,则为列提供默认值

    检查:仅允许特定值(如余额> = 0)

  • 指标

    大多数索引使用 B+树结构。

    为什么使用它们:加速查询(在只获取几行的大型表中,最小/最大查询,通过排除考虑行等)

    索引类型:唯一、主键、全文、次要

    写负载繁重,主要是全表扫描或访问大量行等。不从索引中受益

  • 加入

    允许您从多个表中获取相关数据,用一些公共字段将它们链接在一起。功能强大,但也是资源密集型的,并使数据库难以扩展。这是大规模运行时许多查询执行缓慢的原因,解决方案几乎总是寻找减少连接的方法。

  • 访问控制

    数据库拥有用于管理任务的特权帐户,以及用于客户端的普通帐户。对哪些操作(DDL、DML 等)有细粒度的控制。前面讨论过的)被允许用于这些帐户。

    DB 首先验证用户凭证(身份验证),然后通过在一些内部表中查找这些信息来检查是否允许该用户执行请求(授权)。

    其他控制包括允许检查用户操作历史的活动审计,以及定义查询、连接等数量的资源限制。允许。

流行数据库

商业、封闭源代码——Oracle、Microsoft SQL Server、IBM DB2

带有可选付费支持的开源软件——MySQL、MariaDB、PostgreSQL

由于商业软件的巨大成本,个人和小公司总是更喜欢开源数据库。

最近,即使是大型组织也已经从商业软件转向开源软件,因为它具有灵活性和成本节约。

缺少支持不再是一个问题,因为开发者和第三方可以提供付费支持。

MySQL 是使用最广泛的开源数据库,它得到了主机提供商的广泛支持,任何人都可以轻松使用。它是流行的 Linux-Apache-MySQL-PHP(LAMP)栈的一部分,该栈在 21 世纪开始流行。对于编程语言,我们有更多的选择,但是堆栈的其余部分仍然被广泛使用。

关系型数据库

原文:https://linkedin.github.io/school-of-sre/level101/databases_sql/mysql/

MySQL 架构

alt_text

MySQL 架构使您能够根据自己的需求选择合适的存储引擎,并从最终用户(应用工程师和 DBA)那里抽象出所有的实现细节,他们只需要知道一个一致稳定的 API。

应用层:

  • 连接处理-每个客户端获得其自己的连接,该连接在访问期间被缓存)
  • 认证-服务器检查客户端的信息(用户名、密码、主机)并允许/拒绝连接
  • 安全性:服务器确定客户机是否有权限执行每个查询(用 show privileges 命令检查)

服务器层:

  • 服务和实用程序-备份/恢复、复制、集群等
  • SQL 接口——客户端运行查询进行数据访问和操作
  • SQL 解析器——根据查询创建解析树(词汇/语法/语义分析和代码生成)
  • optimizer——使用各种算法和可用数据(表级统计数据)优化查询,修改查询、扫描顺序、要使用的索引等。(使用解释命令检查)
  • 缓存和缓冲区——缓存存储查询结果,缓冲池(InnoDB)以 LRU 的方式存储表和索引数据

存储引擎选项:

  • InnoDB:使用最广泛,事务支持,符合 ACID,支持行级锁定,崩溃恢复和多版本并发控制。MySQL 5.5+以后默认。
  • MyISAM:速度快,不支持事务,提供表级锁定,非常适合读取繁重的工作负载,主要是在 web 和数据仓库中。默认升级到 MySQL 5.1。
  • 存档:针对高速插入进行了优化,在插入时压缩数据,不支持事务,非常适合存储和检索大量很少引用的历史存档数据
  • 内存:内存中的表。最快的引擎,支持表级锁定,不支持事务,非常适合创建临时表或快速查找,关闭后数据会丢失
  • CSV:将数据存储在 CSV 文件中,非常适合集成到使用这种格式的其他应用中
  • …等等。

可以从一个存储引擎迁移到另一个存储引擎。但是这种迁移锁定了所有操作的表,并且不是在线的,因为它改变了数据的物理布局。耗时较长,一般不推荐。因此,在开始时选择正确的存储引擎非常重要。

一般原则是使用 InnoDB,除非您对其他存储引擎有特殊需求。

运行mysql> SHOW ENGINES;会显示 MySQL 服务器上支持的引擎。

InnoDB

原文:https://linkedin.github.io/school-of-sre/level101/databases_sql/innodb/

为什么要用这个?

通用、行级锁定、ACID 支持、事务、崩溃恢复和多版本并发控制等。

体系结构

alt_text

关键组件:

  • 内存:

    • 缓冲池:直接从内存中处理频繁使用的数据(表和索引)的 LRU 缓存,这加快了处理速度。这对调优性能很重要。

    • Change buffer:当二级索引页不在缓冲池中时,缓存对这些页的更改,并在获取这些页时将其合并。合并可能需要很长时间,并且会影响实时查询。它还会占用部分缓冲池。避免读取辅助索引的额外 I/O。

    • 自适应散列索引:用像缓存一样的快速散列查找表补充 InnoDB 的 B 树索引。未命中的轻微性能损失,也增加了更新它的维护开销。哈希冲突会导致大型数据库的 AHI 重建。

    • 日志缓冲区:在刷新到磁盘之前保存日志数据。

      上述每个内存的大小都是可配置的,并且对性能有很大影响。需要仔细分析工作负载、可用资源、基准测试和优化以获得最佳性能。

  • 磁盘:

    • 表格:在行和列中存储数据。
    • 索引:帮助快速查找具有特定列值的行,避免全表扫描。
    • 重做日志:所有事务都被写入其中,在崩溃后,恢复过程会更正由未完成的事务写入的数据,并重放任何未完成的事务。
    • 撤消日志:与单个事务相关的记录,包含有关如何撤消事务的最新更改的信息。

备份和恢复

原文:https://linkedin.github.io/school-of-sre/level101/databases_sql/backup_recovery/

备份和恢复

备份是任何数据库设置中非常重要的一部分。它们通常是数据的副本,可用于在数据库出现任何重大或轻微危机时重建数据。一般来说,备份有两种类型:-

  • 物理备份 -磁盘上的数据目录
  • 逻辑备份 -表格结构和其中的记录

MySQL 使用不同的工具支持以上两种备份。SRE 的工作是确定什么时候应该使用哪一个。

Mysqldump

MySQL 安装中提供了该实用程序。它有助于获得数据库的逻辑备份。它输出一组 SQL 语句来重建数据。不建议对大型表使用 mysqldump,因为这可能会花费很多时间,并且文件会很大。然而,对于小桌子来说,这是最好和最快的选择。

mysqldump [options] > dump_output.sql

mysqldump 可以使用某些选项来获得适当的数据库转储。

转储所有数据库

mysqldump -u<user> -p<pwd> --all-databases > all_dbs.sql

转储特定数据库

mysqldump -u<user> -p<pwd> --databases db1 db2 db3 > dbs.sql

转储单个数据库mysqldump -u<user> -p<pwd> --databases db1 > db1.sql

运筹学

mysqldump -u<user> -p<pwd> db1 > db1.sql

以上两个命令的区别在于,后一个命令在备份输出中不包含 CREATE DATABASE 命令。

转储数据库中的特定表

mysqldump -u<user> -p<pwd> db1 table1 table2 > db1_tables.sql

只转储表结构而不转储数据

mysqldump -u<user> -p<pwd> --no-data db1 > db1_structure.sql

只转储表数据而不转储 CREATE 语句

mysqldump -u<user> -p<pwd> --no-create-info db1 > db1_data.sql

仅转储表中的特定记录

mysqldump -u<user> -p<pwd> --no-create-info db1 table1 --where=”salary>80000” > db1_table1_80000.sql

Mysqldump 还可以提供 CSV、其他带分隔符的文本或 XML 格式的输出,以支持任何用例。mysqldump 实用程序的备份是离线的,即当备份完成时,它将不会对数据库进行备份时所做的更改。例如,如果备份在下午 3 点开始,在下午 4 点结束,则在下午 3 点到 4 点之间不会对数据库进行更改。

从 mysqldump 恢复可以通过以下两种方式完成:-

来自 shell

mysql -u<user> -p<pwd> < all_dbs.sql

运筹学

如果已经创建了数据库,则从 shell

mysql -u<user> -p<pwd> db1 < db1.sql

从 MySQL shell 内部

mysql> source all_dbs.sql

Percona Xtrabackup

这个工具与 MySQL 服务器分开安装,并且是开源的,由 Percona 提供。它有助于获得数据库的完整或部分物理备份。它提供了数据库的在线备份,也就是说,如前一节末尾所述,在备份过程中会对数据库进行更改。

  • 完整备份 -数据库的完整备份。
  • 部分备份 -增量备份
  • 累积 -一次完整备份后,下一次备份将在完整备份后发生更改。例如,我们在周日进行了一次完整备份,从周一开始,每次备份在周日之后都会有变化;因此,星期二的备份也会有星期一的更改,星期三的备份也会有星期一和星期二的更改,依此类推。
  • 差异 -在一次完整备份之后,下一次备份将在之前的增量备份之后发生更改。例如,我们在周日进行了完整备份,在周日之后对周一进行了更改,在周一之后对周二进行了更改,依此类推。

partial backups - differential and cummulative

Percona xtrabackup 允许我们根据需要进行完整备份和增量备份。但是,增量备份占用的空间比完整备份少(如果每天进行一次),但是增量备份的恢复时间比完整备份长。

创建完整备份

xtrabackup --defaults-file=<location to my.cnf> --user=<mysql user> --password=<mysql password> --backup --target-dir=<location of target directory>

例子

xtrabackup --defaults-file=/etc/my.cnf --user=some_user --password=XXXX --backup --target-dir=/mnt/data/backup/

一些其他选项

  • --stream -可用于将备份文件以指定格式流式输出到标准输出。xbstream 是目前唯一的选择。
  • --tmp-dir -将此设置为备份时用于临时文件的 tmp 目录。
  • --parallel -将此设置为可用于将数据文件并行复制到目标目录的线程数量。
  • --compress -默认情况下-使用 quicklz。设定此项以压缩格式备份。每个文件都是一个. qp 压缩文件,可以由 qpress 文件归档程序提取。
  • --decompress -解压缩所有使用。qp 扩展。它不会删除。解压缩后的 qp 文件。为此,使用--remove-original和这个。请注意,解压缩选项应该与使用压缩选项的 xtrabackup 命令分开运行。

准备备份

使用- backup 选项完成备份后,我们需要准备它以便恢复它。这样做是为了使数据文件与时间点一致。在执行备份时,可能有一些事务正在进行,这些事务已经更改了数据文件。当我们准备备份时,所有这些事务都应用于数据文件。

xtrabackup --prepare --target-dir=<where backup is taken>

例子

xtrabackup --prepare --target-dir=/mnt/data/backup/

不建议暂停正在准备备份的进程,因为这可能会导致数据文件损坏,并且备份无法进一步使用。必须再次进行备份。

恢复完整备份

要恢复通过上述命令创建和准备的备份,只需将所有内容从备份目标目录复制到 mysql 服务器的数据目录,将所有文件的所有权更改为 mysql 用户(MySQL 服务器使用的 linux 用户)并启动 MySQL。

或者也可以使用下面的命令,

xtrabackup --defaults-file=/etc/my.cnf --copy-back --target-dir=/mnt/data/backups/

注意 -为了恢复备份,必须准备好备份。

创建增量备份

Percona Xtrabackup 有助于创建增量备份,即只能备份自上次备份以来的更改。每个 InnoDB 页面都包含一个日志序列号或 LSN,它也是备份和准备命令的最后一行。

xtrabackup: Transaction log of lsn <LSN> to <LSN> was copied. 

运筹学

InnoDB: Shutdown completed; log sequence number <LSN>
<timestamp> completed OK! 

这表明在提到日志序列号之前,已经进行了备份。这是理解增量备份和实现自动化备份的关键信息。增量备份不会比较数据文件的变化,而是浏览 InnoDB 页面,并将它们的 LSN 与上次备份的 LSN 进行比较。因此,如果没有一次完整备份,增量备份将毫无用处。

xtrabackup 命令创建 xtrabackup_checkpoint 文件,该文件包含有关备份 LSN 的信息。以下是该文件的主要内容

backup_type = full-backuped | incremental
from_lsn = 0 (full backup) | to_lsn of last backup <LSN>
to_lsn = <LSN>
last_lsn = <LSN> 

to_lsnlast_lsn 是有区别的。当的 last_lsn 大于 to_lsn 时,这意味着有在我们进行备份时运行的事务尚未应用。这就是- prepare 的用途。

要进行增量备份,首先,我们需要一个完整备份。

xtrabackup --defaults-file=/etc/my.cnf --user=some_user --password=XXXX --backup --target-dir=/mnt/data/backup/full/

让我们假设 xtrabackup_checkpoint 文件的内容如下。

backup_type = full-backuped
from_lsn = 0
to_lsn = 1000
last_lsn = 1000 

现在我们有了一个完整备份,我们可以有一个增量备份来进行更改。我们将采用差异增量备份。

xtrabackup --defaults-file=/etc/my.cnf --user=some_user --password=XXXX --backup --target-dir=/mnt/data/backup/incr1/ --incremental-basedir=/mnt/data/backup/full/

在 incr1 目录中创建了 delta 文件,如, ibdata1.deltadb1/tbl1.ibd.delta 与完整目录的变化。xtrabackup_checkpoint 文件将包含以下内容。

backup_type = incremental
from_lsn = 1000
to_lsn = 1500
last_lsn = 1500 

因此,这里的 from_lsn 等于最后一次备份的 to_lsn 或者为增量备份提供的 basedir。对于下一次增量备份,我们可以将该增量备份用作基本目录。

xtrabackup --defaults-file=/etc/my.cnf --user=some_user --password=XXXX --backup --target-dir=/mnt/data/backup/incr2/ --incremental-basedir=/mnt/data/backup/incr1/

xtrabackup_checkpoint 文件将包含以下内容。

backup_type = incremental
from_lsn = 1500
to_lsn = 2000
last_lsn = 2200 

准备增量备份

准备增量备份不同于准备完整备份。当准备运行时,执行两个操作- 提交的事务应用于数据文件未提交的事务回滚。在准备增量备份时,我们必须跳过未提交事务的回滚,因为它们可能会在下一次增量备份中提交。如果我们回滚未提交的事务,则无法应用进一步的增量备份。

我们使用 - apply-log-only 选项和 - prepare 来避免回滚阶段。

在上一节中,我们有了以下带有完整备份的目录

/mnt/data/backup/full
/mnt/data/backup/incr1
/mnt/data/backup/incr2 

首先,我们准备完整备份,但仅使用- apply-log-only 选项。

xtrabackup --prepare --apply-log-only --target-dir=/mnt/data/backup/full

该命令的输出将在末尾包含以下内容。

InnoDB: Shutdown complete; log sequence number 1000
<timestamp> Completed OK! 

注意:结尾提到的 LSN 与为完整备份创建的 xtrabackup_checkpoint 中的 to_lsn 相同。

接下来,我们将第一次增量备份中的更改应用于完整备份。

xtrabackup --prepare --apply-log-only --target-dir=/mnt/data/backup/full --incremental-dir=/mnt/data/backup/incr1

这会将增量目录中的增量文件应用到完整备份目录。它将完整备份目录中的数据文件前滚到增量备份时,并照常应用重做日志。

最后,我们应用最后一次增量备份,与前一次备份相同,只是做了一点小小的更改。

xtrabackup --prepare --target-dir=/mnt/data/backup/full --incremental-dir=/mnt/data/backup/incr1

我们不必使用 - apply-log-only 选项。它将 incr2 delta 文件应用于完整备份数据文件,将它们向前推进,对它们应用重做日志,最后回滚未提交的事务以产生最终结果。现在,完整备份目录中的数据可用于恢复。

注意 -要创建累积增量备份,增量 basedir 应该始终是每个增量备份的完整备份目录。在准备过程中,我们可以使用- apply-log-only 选项从完整备份开始,并将最后一次增量备份用于最终准备,因为它包含自完整备份以来的所有更改。

恢复增量备份

完成上述所有步骤后,恢复与完整备份相同。

进一步阅读

MySQL 复制

原文:https://linkedin.github.io/school-of-sre/level101/databases_sql/replication/

MySQL 复制

复制使数据能够从一台 MySQL 主机(称为主主机)复制到另一台 MySQL 主机(称为副本主机)。默认情况下,MySQL 复制本质上是异步的,但在某些配置下,可以将其更改为半同步。

MySQL 复制的一些常见应用有:-

  • 读取扩展 -由于多个主机可以从单个主要主机复制数据,我们可以根据需要设置尽可能多的副本,并通过它们扩展读取,即应用写入将进入单个主要主机,读取可以在那里的所有副本之间平衡。这种设置还可以提高写入性能,因为主节点仅用于更新,而不用于读取。
  • 使用副本进行备份 -备份过程有时会有点繁重。但是,如果我们配置了副本,那么我们可以使用其中一个副本来获得备份,而完全不会影响主数据。
  • 灾难恢复 -其他地理区域中的副本为配置灾难恢复铺平了道路。

MySQL 也支持不同类型的同步

  • 异步 -这是默认的同步方法。它是单向的,即一台主机作为主主机,一台或多台主机作为副本主机。我们将在整个复制主题中讨论这种方法。

replication topologies

  • 半同步 -在这种类型的同步中,在主要主机上执行的提交将被阻止,直到至少有一个副本主机对其进行确认。在任何一个复制副本发出确认后,控制权将返回到执行该事务的会话。这确保了很强的一致性,但是复制比异步慢。
  • 延迟——我们可以在典型的 MySQL 复制中故意将副本延迟用例所需的秒数。这种类型的复制可以防止在主服务器上删除或损坏数据的严重人为错误,例如,在上面的延迟复制图表中,如果在主服务器上错误地执行了删除数据库,我们仍然有 30 分钟的时间从 R2 恢复数据,因为该命令尚未在 R2 上复制。

先决条件

在我们开始设置复制之前,我们应该了解二进制日志。二进制日志在 MySQL 复制中起着非常重要的作用。二进制日志,或者通常称为二进制日志包含关于对数据库所做的更改的事件,比如表结构更改、通过 DML 操作的数据更改等。它们不用于记录 SELECT 语句。对于复制,主服务器使用其二进制日志向副本服务器发送有关数据库更改的信息,副本服务器进行相同的数据更改。

关于 MySQL 复制,二进制日志格式可以是两种类型,这两种类型决定了主要的复制类型:?基于语句的复制或 SBR?基于行的复制或 RBR

基于语句的 Binlog 格式

最初,MySQL 中的复制基于从主服务器复制 SQL 语句并在副本服务器上执行。这称为基于语句的日志记录。binlog 包含会话运行的确切 SQL 语句。

SBR update example

因此,如果我们运行上述语句,在一个 update 语句中插入 3 条记录和 update 3,它们将被记录为与我们执行它们时完全相同的日志。

SBR binlog

基于行的 Binlog 格式

在最新的 MySQL 版本中,基于行是默认的。这与语句格式有很大不同,因为这里记录的是行事件,而不是语句。我们的意思是,在上面的例子中,一条 update 语句影响了 3 条记录,但是 binlog 只有一条 update 语句;如果是基于行的格式,binlog 将为每个更新的记录生成一个事件。

RBR update example

RBR binlog

基于语句的 v/s 基于行的二进制日志

让我们看看基于语句和基于行的二进制日志之间的操作差异。

基于语句 基于行
记录执行的 SQL 语句 基于执行的 SQL 语句记录行事件
占用较少的磁盘空间 占用更多磁盘空间
使用二进制日志恢复速度更快 使用二进制日志恢复速度较慢
当用于复制时,如果任何语句有一个预定义的函数,该函数有自己的值,如 sysdate()、uuid()等,则副本上的输出可能会不同,从而导致不一致。 无论执行什么都会变成一个带有值的行事件,所以如果在 SQL 语句中使用这样的函数就不会有问题。
只记录语句,因此不会生成其他行事件。 当使用 INSERT INTO SELECT 将一个表复制到另一个表中时,会生成许多事件。

注意——还有一种叫做混合的 binlog 格式。对于混合日志记录,默认情况下使用基于语句,但在某些情况下会切换到基于行。如果 MySQL 不能保证基于语句的日志记录对于执行的语句是安全的,它会发出警告,并切换到基于行的日志记录。

我们将使用二进制日志格式作为整个复制主题的行。

运动中的复制

replication in motion

上图显示了典型的 MySQL 复制是如何工作的。

  1. Replica_IO_Thread 负责将二进制日志事件从主二进制日志提取到副本
  2. 在副本主机上,创建的中继日志是二进制日志的精确副本。如果主服务器上的二进制日志是行格式,则中继日志将是相同的。
  3. Replica_SQL_Thread 在副本 MySQL 服务器上应用中继日志。
  4. 如果在副本服务器上启用了 log-bin,那么副本服务器也将拥有自己的二进制日志。如果启用了 log-slave-updates,那么它也会将主服务器的更新记录到二进制日志中。

设置复制

在本节中,我们将设置一个简单的异步复制。二进制日志将采用基于行的格式。复制将在两个没有先前数据的新主机上设置。有两种不同的方法可以设置复制。

  • 基于二进制日志的 -每个副本都记录了主二进制日志上的二进制日志坐标和二进制日志中的位置,直到它被读取和处理。因此,在同一时间,不同的副本可能会读取同一个 binlog 的不同部分。
  • 基于 GTID 的 -每个交易都有一个标识符,称为全局交易标识符或 GTID。不需要保留 binlog 坐标的记录,只要副本拥有在主服务器上执行的所有 GTIDs,它就与主服务器保持一致。典型的 GTID 是 server_uuid:#正整数。

我们将在下一节中设置一个基于 GTID 的复制,但也将讨论基于 binlog 的复制设置。

主要主机配置

在设置基于 GTID 的复制时,主要 my.cnf 文件中应该包含以下配置参数。

server-id - a unique ID for the mysql server
log-bin - the binlog location
binlog-format - ROW | STATEMENT (we will use ROW)
gtid-mode - ON
enforce-gtid-consistency - ON (allows execution of only those statements which can be logged using GTIDs) 

副本主机配置

设置复制时,副本 my.cnf 文件中应包含以下配置参数。

server-id - different than the primary host
log-bin - (optional, if you want replica to log its own changes as well)
binlog-format - depends on the above
gtid-mode - ON
enforce-gtid-consistency - ON
log-slave-updates - ON (if binlog is enabled, then we can enable this. This enables the replica to log the changes coming from the primary along with its own changes. Helps in setting up chain replication) 

复制用户

每个副本都使用 mysql 用户连接到主服务器进行复制。因此在主要主机上必须有一个相同的 mysql 用户帐户。只要拥有复制从属权限,任何用户都可以用于此目的。如果唯一的目的是复制,那么我们可以让用户只拥有所需的权限。

在主要主机上

mysql> create user repl_user@<replica_IP> identified by 'xxxxx';

mysql> grant replication slave on *.* to repl_user@'<replica_IP>'; 

从主节点获取起始位置

在主要主机上运行以下命令

mysql> show master status\G
*************************** 1\. row ***************************
             File: mysql-bin.000001
         Position: 73
     Binlog_Do_DB:
 Binlog_Ignore_DB:
Executed_Gtid_Set: e17d0920-d00e-11eb-a3e6-000d3aa00f87:1-3
1 row in set (0.00 sec) 

如果我们使用基于二进制日志的复制,最上面的两行输出是最重要的。它告诉主主机上的当前二进制日志,以及它已经写入的位置。对于新主机,我们知道没有数据写入,因此我们可以使用第一个 binlog 文件和位置 4 直接设置复制。如果我们从备份中设置复制,那么这将改变我们获取起始位置的方式。对于 gtid,executed_gtid_set 是 primary 当前所在的值。同样,对于新的设置,我们不必指定任何关于起始点的内容,它将从事务 id 1 开始,但是当我们从备份设置时,备份将包含 GTID 位置,直到进行备份的位置。

设置副本

复制设置必须知道主要主机、要连接的用户和密码、binlog 坐标(对于基于 binlog 的复制)或 GTID 自动定位参数。以下命令用于设置

change master to
master_host = '<primary host IP>',
master_port = <primary host port - default=3306>,
master_user = 'repl_user',
master_password = 'xxxxx',
master_auto_position = 1; 

注意 -从 Mysql 8.0.23 开始,将主服务器更改为命令已被替换为将复制源更改为,所有主服务器从服务器关键字也被替换为副本

如果是基于二进制日志的复制,那么我们需要指定二进制日志坐标,而不是 master_auto_position。

master_log_file = 'mysql-bin.000001',
master_log_pos = 4 

开始复制并检查状态

现在一切都配置好了,我们只需要通过以下命令在副本上启动复制

start slave;

或者从 MySQL 8.0.23 开始,

start replica;

复制是否成功运行,我们可以通过运行以下命令来确定

show slave status\G

或者从 MySQL 8.0.23 开始,

show replica status\G

mysql> show replica status\G
*************************** 1\. row ***************************
             Replica_IO_State: Waiting for master to send event
                  Source_Host: <primary IP>
                  Source_User: repl_user
                  Source_Port: <primary port>
                Connect_Retry: 60
              Source_Log_File: mysql-bin.000001
          Read_Source_Log_Pos: 852
               Relay_Log_File: mysql-relay-bin.000002
                Relay_Log_Pos: 1067
        Relay_Source_Log_File: mysql-bin.000001
           Replica_IO_Running: Yes
          Replica_SQL_Running: Yes
              Replicate_Do_DB:
          Replicate_Ignore_DB:
           Replicate_Do_Table:
       Replicate_Ignore_Table:
      Replicate_Wild_Do_Table:
  Replicate_Wild_Ignore_Table:
                   Last_Errno: 0
                   Last_Error:
                 Skip_Counter: 0
          Exec_Source_Log_Pos: 852
              Relay_Log_Space: 1283
              Until_Condition: None
               Until_Log_File:
                Until_Log_Pos: 0
           Source_SSL_Allowed: No
           Source_SSL_CA_File:
           Source_SSL_CA_Path:
              Source_SSL_Cert:
            Source_SSL_Cipher:
               Source_SSL_Key:
        Seconds_Behind_Source: 0
Source_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error:
               Last_SQL_Errno: 0
               Last_SQL_Error:
  Replicate_Ignore_Server_Ids:
             Source_Server_Id: 1
                  Source_UUID: e17d0920-d00e-11eb-a3e6-000d3aa00f87
             Source_Info_File: mysql.slave_master_info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
    Replica_SQL_Running_State: Slave has read all relay log; waiting for more updates
           Source_Retry_Count: 86400
                  Source_Bind:
      Last_IO_Error_Timestamp:
     Last_SQL_Error_Timestamp:
               Source_SSL_Crl:
           Source_SSL_Crlpath:
           Retrieved_Gtid_Set: e17d0920-d00e-11eb-a3e6-000d3aa00f87:1-3
            Executed_Gtid_Set: e17d0920-d00e-11eb-a3e6-000d3aa00f87:1-3
                Auto_Position: 1
         Replicate_Rewrite_DB:
                 Channel_Name:
           Source_TLS_Version:
       Source_public_key_path:
        Get_Source_public_key: 0
            Network_Namespace:
1 row in set (0.00 sec) 

一些参数解释如下

  • Relay_Source_Log_File -副本当前正在读取的主服务器的文件
  • Execute_Source_Log_Pos -对于上述文件,副本当前从哪个位置读取。当使用基于 binlog 的复制时,这两个参数至关重要。
  • 副本 IO 运行 -副本 IO 线程是否运行
  • 副本 _SQL_Running -副本的 SQL 线程是否正在运行
  • Seconds_Behind_Source -在主服务器上执行语句和在副本服务器上执行语句的时间差。这表明有多少复制延迟。
  • 源 _UUID -主要主机的 UUID
  • Retrieved_Gtid_Set -要执行的副本从主要主机获取的 Gtid。
  • Executed_Gtid_Set -在副本上执行的 Gtid。如果副本同步,则该设置在整个集群中保持不变。
  • Auto_Position -它指示复制品自动获取下一个 GTID

为已经设置好的集群创建一个副本

上一节中讨论的步骤讨论了在两台新主机上设置复制。当我们必须为已经在为应用提供服务的主机设置复制副本时,将使用主主机的备份,或者为复制副本创建新的备份(仅当它提供的流量较少时才应这样做),或者使用最近创建的备份。

如果 MySQL 主服务器上的数据库很小,建议小于 100G,那么可以使用 mysqldump 和以下选项进行备份。

mysqldump -uroot -p -hhost_ip -P3306 --all-databases --single-transaction --master-data=1 > primary_host.bkp

  • --single-transaction -该选项在进行备份之前启动事务,以确保事务的一致性。由于事务相互隔离,因此不会有其他写入影响备份。
  • --master-data -如果希望设置基于 binlog 的复制,则需要此选项。它包括二进制日志文件和日志文件在备份文件中的位置。

当 GTID 模式被启用并且 mysqldump 被执行时,它包括被执行用于在备份位置之后启动副本的 GTID。mysqldump 输出文件的内容如下

GTID info in mysqldump

建议在恢复之前对这些进行注释,否则它们可能会引发错误。此外,使用 master-data=2 将自动注释 master_log_file 行。

同样,当使用 xtrabackup 对主机进行备份时,文件 xtrabckup_info file 包含 binlog 文件和文件位置的信息,以及 GTID 执行集。

server_version = 8.0.25
start_time = 2021-06-22 03:45:17
end_time = 2021-06-22 03:45:20
lock_time = 0
binlog_pos = filename 'mysql-bin.000007', position '196', GTID of the last change 'e17d0920-d00e-11eb-a3e6-000d3aa00f87:1-5'
innodb_from_lsn = 0
innodb_to_lsn = 18153149
partial = N
incremental = N
format = file
compressed = N
encrypted = N 

现在,在所需的主机上设置 MySQL 服务器后,恢复从上述任何一种方法获得的备份。如果计划的方式是基于二进制日志的复制,则在以下命令中使用二进制日志文件和位置信息

change Replication Source to 
source_host = ‘primary_ip’,
source_port = 3306,
source_user = ‘repl_user’,
source_password = ‘xxxxx’,
source_log_file = ‘mysql-bin.000007’,
source_log_pos = ‘196’; 

如果需要通过 GITDs 设置复制,那么运行下面的命令告诉副本关于已经执行的 GTIDs。在副本主机上,运行以下命令

reset master;

set global gtid_purged = ‘e17d0920-d00e-11eb-a3e6-000d3aa00f87:1-5’

change replication source to
source_host = ‘primary_ip’,
source_port = 3306,
source_user = ‘repl_user’,
source_password = ‘xxxxx’,
source_auto_position = 1 

reset master 命令将二进制日志的位置重置为初始位置。如果主机是新安装的 MySQL,可以跳过它,但我们恢复了一个备份,所以它是必要的。gtid_purged 全局变量让副本知道已经执行的 gtid,以便复制可以在那之后开始。然后在 change source 命令中,我们将 auto-position 设置为 1,这将自动获取下一个 GTID 以继续。

进一步阅读

操作概念

原文:https://linkedin.github.io/school-of-sre/level101/databases_sql/operations/

  • 解释解释+分析

    解释分析来自优化器的查询计划,包括如何连接表,扫描哪些表/行等。

    Explain analyze 显示上述信息和附加信息,如执行成本、返回的行数、花费的时间等。

    这些知识对于调整查询和添加索引非常有用。

    看这个性能调音教程视频

    查看实验室部分,获得关于索引的实际操作。

  • 慢速查询日志

    用于识别慢速查询(可配置阈值),在配置中启用或随查询动态启用

    查看关于识别慢速查询的实验部分

  • 用户管理

    这包括创建和更改用户,如管理权限、更改密码等。

  • 备份和恢复策略,优点和缺点

    使用 mysqldump 的逻辑备份-较慢,但可以在线完成

    物理备份(复制数据目录或使用 xtrabackup) -快速备份/恢复。复制数据目录需要锁定或关闭。xtrabackup 是一个改进,因为它支持不关机的备份(热备份)。

    其他- PITR,快照等。

  • 使用重做日志的故障恢复过程

    崩溃后,当您重新启动服务器时,它会读取重做日志并重放修改以进行恢复

  • 监控 MySQL

    关键 MySQL 指标:读取、写入、查询运行时间、错误、慢速查询、连接、运行线程、InnoDB 指标

    关键操作系统指标:CPU、负载、内存、磁盘 I/O、网络

  • 分身术

    将数据从一个实例复制到一个或多个实例。有助于横向扩展、数据保护、分析和性能。Binlog 主服务器上的转储线程,辅助服务器上的复制 I/O 和 SQL 线程。策略包括标准异步、半异步或组复制。

  • 高可用性

    处理软件、硬件和网络故障的能力。对于需要 99.9%以上正常运行时间的任何人来说都是必不可少的。可以通过 MySQL、Percona、Oracle 等的复制或集群解决方案实施。需要专业知识来设置和维护。故障转移可以是手动的、脚本化的或使用 Orchestrator 等工具。

  • 数据目录

    数据存储在特定的目录中,每个数据库中包含嵌套的数据目录。还有 MySQL 日志文件、InnoDB 日志文件、服务器进程 ID 文件和其他一些配置。数据目录是可配置的。

  • MySQL 配置

    这可以通过在启动时传递参数来完成,或者在文件中完成。MySQL 查找配置文件有几个标准路径/etc/my.cnf是常用路径之一。这些选项被组织在标题下(mysqld 用于服务器,mysql 用于客户端),您可以在接下来的实验中更深入地了解它们。

  • 日志

    MySQL 有各种用途的日志——一般查询日志、错误、二进制日志(用于复制)、慢速查询日志。默认情况下只启用错误日志(以减少 I/O 和存储需求),其他日志可以在需要时启用——通过在启动时指定配置参数或在运行时运行命令。日志目的地也可以通过配置参数进行调整。

选择查询

原文:https://linkedin.github.io/school-of-sre/level101/databases_sql/select_query/

选择查询

使用 MySQL 时最常用的命令是 SELECT。它用于从一个或多个表中获取结果集。典型的选择查询的一般形式如下

SELECT expr
FROM table1
[WHERE condition]
[GROUP BY column_list HAVING condition]
[ORDER BY column_list ASC|DESC]
[LIMIT #] 

上面的一般形式包含了 SELECT 查询的一些常用子句:-

  • expr -逗号分隔的列列表或*(针对所有列)
  • 其中 -提供了一个条件,如果为真,则指示查询只选择那些记录。
  • GROUP BY -根据提供的列列表对整个结果集进行分组。建议在查询的选择表达式中使用聚合函数。 HAVING 通过在所选函数或任何其他集合函数上设置条件来支持分组。
  • ORDER BY -根据列列表以升序或降序对结果集进行排序。
  • 限制 -常用来限制记录的数量。

为了更好地理解上述内容,我们来看一些例子。以下示例中使用的数据集可从这里获得,并可免费使用。

选择所有记录

mysql> select * from employees limit 5;
+--------+------------+------------+-----------+--------+------------+
| emp_no | birth_date | first_name | last_name | gender | hire_date  |
+--------+------------+------------+-----------+--------+------------+
|  10001 | 1953-09-02 | Georgi     | Facello   | M      | 1986-06-26 |
|  10002 | 1964-06-02 | Bezalel    | Simmel    | F      | 1985-11-21 |
|  10003 | 1959-12-03 | Parto      | Bamford   | M      | 1986-08-28 |
|  10004 | 1954-05-01 | Chirstian  | Koblick   | M      | 1986-12-01 |
|  10005 | 1955-01-21 | Kyoichi    | Maliniak  | M      | 1989-09-12 |
+--------+------------+------------+-----------+--------+------------+
5 rows in set (0.00 sec) 

为所有记录选择特定字段

mysql> select first_name, last_name, gender from employees limit 5;
+------------+-----------+--------+
| first_name | last_name | gender |
+------------+-----------+--------+
| Georgi     | Facello   | M      |
| Bezalel    | Simmel    | F      |
| Parto      | Bamford   | M      |
| Chirstian  | Koblick   | M      |
| Kyoichi    | Maliniak  | M      |
+------------+-----------+--------+
5 rows in set (0.00 sec) 

选择所有雇佣日期>= 1990 年 1 月 1 日的记录

mysql> select * from employees where hire_date >= '1990-01-01' limit 5;
+--------+------------+------------+-------------+--------+------------+
| emp_no | birth_date | first_name | last_name   | gender | hire_date  |
+--------+------------+------------+-------------+--------+------------+
|  10008 | 1958-02-19 | Saniya     | Kalloufi    | M      | 1994-09-15 |
|  10011 | 1953-11-07 | Mary       | Sluis       | F      | 1990-01-22 |
|  10012 | 1960-10-04 | Patricio   | Bridgland   | M      | 1992-12-18 |
|  10016 | 1961-05-02 | Kazuhito   | Cappelletti | M      | 1995-01-27 |
|  10017 | 1958-07-06 | Cristinel  | Bouloucos   | F      | 1993-08-03 |
+--------+------------+------------+-------------+--------+------------+
5 rows in set (0.01 sec) 

从出生日期> = 1960 且性别= 'F' 的所有记录中选择名字和姓氏

mysql> select first_name, last_name from employees where year(birth_date) >= 1960 and gender='F' limit 5;
+------------+-----------+
| first_name | last_name |
+------------+-----------+
| Bezalel    | Simmel    |
| Duangkaew  | Piveteau  |
| Divier     | Reistad   |
| Jeong      | Reistad   |
| Mingsen    | Casley    |
+------------+-----------+
5 rows in set (0.00 sec) 

显示记录总数

mysql> select count(*) from employees;
+----------+
| count(*) |
+----------+
|   300024 |
+----------+
1 row in set (0.05 sec) 

显示所有记录的性别计数

mysql> select gender, count(*) from employees group by gender;
+--------+----------+
| gender | count(*) |
+--------+----------+
| M      |   179973 |
| F      |   120051 |
+--------+----------+
2 rows in set (0.14 sec) 

显示雇佣年份 _ 日期和该年雇佣的员工数量,并且只显示雇佣员工超过 2 万人的年份

mysql> select year(hire_date), count(*) from employees group by year(hire_date) having count(*) > 20000;
+-----------------+----------+
| year(hire_date) | count(*) |
+-----------------+----------+
|            1985 |    35316 |
|            1986 |    36150 |
|            1987 |    33501 |
|            1988 |    31436 |
|            1989 |    28394 |
|            1990 |    25610 |
|            1991 |    22568 |
|            1992 |    20402 |
+-----------------+----------+
8 rows in set (0.14 sec) 

按雇佣日期降序显示所有记录。如果雇佣日期相同,则按出生日期升序排列

mysql> select * from employees order by hire_date desc, birth_date asc limit 5;
+--------+------------+------------+-----------+--------+------------+
| emp_no | birth_date | first_name | last_name | gender | hire_date  |
+--------+------------+------------+-----------+--------+------------+
| 463807 | 1964-06-12 | Bikash     | Covnot    | M      | 2000-01-28 |
| 428377 | 1957-05-09 | Yucai      | Gerlach   | M      | 2000-01-23 |
| 499553 | 1954-05-06 | Hideyuki   | Delgrande | F      | 2000-01-22 |
| 222965 | 1959-08-07 | Volkmar    | Perko     | F      | 2000-01-13 |
|  47291 | 1960-09-09 | Ulf        | Flexer    | M      | 2000-01-12 |
+--------+------------+------------+-----------+--------+------------+
5 rows in set (0.12 sec) 

选择-连接

JOIN 语句用于根据某些条件从两个或多个表中产生一个组合结果集。它也可以与 Update 和 Delete 语句一起使用,但是我们将集中讨论 select 查询。以下是连接的基本通用形式

SELECT table1.col1, table2.col1, ... (any combination)
FROM
table1 <join_type> table2
ON (or USING depends on join_type) table1.column_for_joining = table2.column_for_joining
WHERE … 

可以选择任意数量的列,但是建议只选择那些与增加结果集可读性相关的列。所有其他子句,如 where、group by 都不是强制性的。让我们讨论一下 MySQL 语法支持的连接类型。

内部连接

这在一个条件下连接表 A 和表 B。在结果集中,只选择条件为真的记录。

显示员工的一些详细信息以及他们的工资

mysql> select e.emp_no,e.first_name,e.last_name,s.salary from employees e join salaries s on e.emp_no=s.emp_no limit 5;
+--------+------------+-----------+--------+
| emp_no | first_name | last_name | salary |
+--------+------------+-----------+--------+
|  10001 | Georgi     | Facello   |  60117 |
|  10001 | Georgi     | Facello   |  62102 |
|  10001 | Georgi     | Facello   |  66074 |
|  10001 | Georgi     | Facello   |  66596 |
|  10001 | Georgi     | Facello   |  66961 |
+--------+------------+-----------+--------+
5 rows in set (0.00 sec) 

类似结果可以通过以下方式实现

mysql> select e.emp_no,e.first_name,e.last_name,s.salary from employees e join salaries s using (emp_no) limit 5;
+--------+------------+-----------+--------+
| emp_no | first_name | last_name | salary |
+--------+------------+-----------+--------+
|  10001 | Georgi     | Facello   |  60117 |
|  10001 | Georgi     | Facello   |  62102 |
|  10001 | Georgi     | Facello   |  66074 |
|  10001 | Georgi     | Facello   |  66596 |
|  10001 | Georgi     | Facello   |  66961 |
+--------+------------+-----------+--------+
5 rows in set (0.00 sec) 

也是由

mysql> select e.emp_no,e.first_name,e.last_name,s.salary from employees e natural join salaries s limit 5;
+--------+------------+-----------+--------+
| emp_no | first_name | last_name | salary |
+--------+------------+-----------+--------+
|  10001 | Georgi     | Facello   |  60117 |
|  10001 | Georgi     | Facello   |  62102 |
|  10001 | Georgi     | Facello   |  66074 |
|  10001 | Georgi     | Facello   |  66596 |
|  10001 | Georgi     | Facello   |  66961 |
+--------+------------+-----------+--------+
5 rows in set (0.00 sec) 

外部连接

主要有两种类型:- - -在一个条件下连接完整的表 A 和表 B。选择表 A 中的所有记录,但是在表 B 中,只选择条件为真的那些记录。- -与左接合完全相反。

为了更好地理解左连接,让我们假设下表。

mysql> select * from dummy1;
+----------+------------+
| same_col | diff_col_1 |
+----------+------------+
|        1 | A          |
|        2 | B          |
|        3 | C          |
+----------+------------+

mysql> select * from dummy2;
+----------+------------+
| same_col | diff_col_2 |
+----------+------------+
|        1 | X          |
|        3 | Y          |
+----------+------------+ 

一个简单的选择连接如下所示。

mysql> select * from dummy1 d1 left join dummy2 d2 on d1.same_col=d2.same_col;
+----------+------------+----------+------------+
| same_col | diff_col_1 | same_col | diff_col_2 |
+----------+------------+----------+------------+
|        1 | A          |        1 | X          |
|        3 | C          |        3 | Y          |
|        2 | B          |     NULL | NULL       |
+----------+------------+----------+------------+
3 rows in set (0.00 sec) 

也可以写成

mysql> select * from dummy1 d1 left join dummy2 d2 using(same_col);
+----------+------------+------------+
| same_col | diff_col_1 | diff_col_2 |
+----------+------------+------------+
|        1 | A          | X          |
|        3 | C          | Y          |
|        2 | B          | NULL       |
+----------+------------+------------+
3 rows in set (0.00 sec) 

同时也是

mysql> select * from dummy1 d1 natural left join dummy2 d2;
+----------+------------+------------+
| same_col | diff_col_1 | diff_col_2 |
+----------+------------+------------+
|        1 | A          | X          |
|        3 | C          | Y          |
|        2 | B          | NULL       |
+----------+------------+------------+
3 rows in set (0.00 sec) 

交叉连接

这是表 A 和表 B 的叉积,没有任何条件。它在现实世界中没有太多的应用。

一个简单的交叉连接如下所示

mysql> select * from dummy1 cross join dummy2;
+----------+------------+----------+------------+
| same_col | diff_col_1 | same_col | diff_col_2 |
+----------+------------+----------+------------+
|        1 | A          |        3 | Y          |
|        1 | A          |        1 | X          |
|        2 | B          |        3 | Y          |
|        2 | B          |        1 | X          |
|        3 | C          |        3 | Y          |
|        3 | C          |        1 | X          |
+----------+------------+----------+------------+
6 rows in set (0.01 sec) 

一个可以派上用场的用例是当您必须填充一些缺失的条目时。例如,dummy1 中的所有条目必须插入到一个类似的表 dummy3 中,每个记录必须有 3 个状态为 1、5 和 7 的条目。

mysql> desc dummy3;
+----------+----------+------+-----+---------+-------+
| Field    | Type     | Null | Key | Default | Extra |
+----------+----------+------+-----+---------+-------+
| same_col | int      | YES  |     | NULL    |       |
| value    | char(15) | YES  |     | NULL    |       |
| status   | smallint | YES  |     | NULL    |       |
+----------+----------+------+-----+---------+-------+
3 rows in set (0.02 sec) 

要么创建一个与 dummy1 中条目一样多的插入查询脚本,要么使用交叉连接生成所需的结果集。

mysql> select * from dummy1 
cross join 
(select 1 union select 5 union select 7) T2 
order by same_col;
+----------+------------+---+
| same_col | diff_col_1 | 1 |
+----------+------------+---+
|        1 | A          | 1 |
|        1 | A          | 5 |
|        1 | A          | 7 |
|        2 | B          | 1 |
|        2 | B          | 5 |
|        2 | B          | 7 |
|        3 | C          | 1 |
|        3 | C          | 5 |
|        3 | C          | 7 |
+----------+------------+---+
9 rows in set (0.00 sec) 

上述查询中的 T2 部分被称为子查询。我们将在下一节讨论同样的问题。

自然连接

这隐式地从表 A 和表 B 中选择公共列,并执行内部连接。

mysql> select e.emp_no,e.first_name,e.last_name,s.salary from employees e natural join salaries s limit 5;
+--------+------------+-----------+--------+
| emp_no | first_name | last_name | salary |
+--------+------------+-----------+--------+
|  10001 | Georgi     | Facello   |  60117 |
|  10001 | Georgi     | Facello   |  62102 |
|  10001 | Georgi     | Facello   |  66074 |
|  10001 | Georgi     | Facello   |  66596 |
|  10001 | Georgi     | Facello   |  66961 |
+--------+------------+-----------+--------+
5 rows in set (0.00 sec) 

请注意,如果没有为查询显式选择列,自然连接和使用会注意到公共列只显示一次。

更多的例子

显示薪资> 80000 的员工的员工编号、薪资、职称和部门

mysql> select e.emp_no, s.salary, t.title, d.dept_no 
from  
employees e 
join salaries s using (emp_no) 
join titles t using (emp_no) 
join dept_emp d using (emp_no) 
where s.salary > 80000 
limit 5;
+--------+--------+--------------+---------+
| emp_no | salary | title        | dept_no |
+--------+--------+--------------+---------+
|  10017 |  82163 | Senior Staff | d001    |
|  10017 |  86157 | Senior Staff | d001    |
|  10017 |  89619 | Senior Staff | d001    |
|  10017 |  91985 | Senior Staff | d001    |
|  10017 |  96122 | Senior Staff | d001    |
+--------+--------+--------------+---------+
5 rows in set (0.00 sec) 

按部门编号显示每个部门中按职位排序的员工数

mysql> select d.dept_no, t.title, count(*) 
from titles t 
left join dept_emp d using (emp_no) 
group by d.dept_no, t.title 
order by d.dept_no 
limit 10;
+---------+--------------------+----------+
| dept_no | title              | count(*) |
+---------+--------------------+----------+
| d001    | Manager            |        2 |
| d001    | Senior Staff       |    13940 |
| d001    | Staff              |    16196 |
| d002    | Manager            |        2 |
| d002    | Senior Staff       |    12139 |
| d002    | Staff              |    13929 |
| d003    | Manager            |        2 |
| d003    | Senior Staff       |    12274 |
| d003    | Staff              |    14342 |
| d004    | Assistant Engineer |     6445 |
+---------+--------------------+----------+
10 rows in set (1.32 sec) 

选择子查询

子查询通常是一个较小的结果集,可以用多种方式来支持选择查询。它可以用在“where”条件中,可以用来代替 join,大多数情况下,join 可能是多余的。这些子查询也称为派生表。它们在选择查询中必须有一个表别名。

让我们看一些子查询的例子。

这里,我们通过使用 dept_emp 表中的 dept_no 的子查询从 departments 表中获得了部门名称。

mysql> select e.emp_no, 
(select dept_name from departments where dept_no=d.dept_no) dept_name from employees e 
join dept_emp d using (emp_no) 
limit 5;
+--------+-----------------+
| emp_no | dept_name       |
+--------+-----------------+
|  10001 | Development     |
|  10002 | Sales           |
|  10003 | Production      |
|  10004 | Production      |
|  10005 | Human Resources |
+--------+-----------------+
5 rows in set (0.01 sec) 

这里,我们使用上面的‘avg’查询(它获得了 avg salary)作为子查询,列出了最新薪金高于平均值的雇员。

mysql> select avg(salary) from salaries;
+-------------+
| avg(salary) |
+-------------+
|  63810.7448 |
+-------------+
1 row in set (0.80 sec)

mysql> select e.emp_no, max(s.salary) 
from employees e 
natural join salaries s 
group by e.emp_no 
having max(s.salary) > (select avg(salary) from salaries) 
limit 10;
+--------+---------------+
| emp_no | max(s.salary) |
+--------+---------------+
|  10001 |         88958 |
|  10002 |         72527 |
|  10004 |         74057 |
|  10005 |         94692 |
|  10007 |         88070 |
|  10009 |         94443 |
|  10010 |         80324 |
|  10013 |         68901 |
|  10016 |         77935 |
|  10017 |         99651 |
+--------+---------------+
10 rows in set (0.56 sec) 

查询性能

原文:https://linkedin.github.io/school-of-sre/level101/databases_sql/query_performance/

查询性能调优

查询性能是关系数据库的一个非常重要的方面。如果没有正确调优,select 查询对于应用和 MySQL 服务器来说会变得缓慢而痛苦。重要的任务是识别速度慢的查询,并通过重写它们或在相关表上创建适当的索引来提高它们的性能。

慢速查询日志

慢速查询日志包含执行时间比配置参数 long_query_time 中设置的时间长的 SQL 语句。这些查询是优化的候选对象。有一些很好的工具可以总结慢速查询日志,如 mysqldumpslow(由 MySQL 自己提供)、pt-query-digest(由 Percona 提供)等。以下是用于启用和有效捕获慢速查询的配置参数

可变的 说明 示例值
慢速查询日志 启用或禁用慢速查询日志 在…上
慢速查询日志文件 慢速查询日志的位置 /var/lib/mysql/mysql-slow.log
长查询时间 阈值时间。耗时超过此时间的查询会记录在慢速查询日志中 five
日志查询未使用索引 当启用慢速查询日志时,不使用任何索引的查询也会记录在慢速查询日志中,即使它们花费的时间比 long_query_time 少。 在…上

因此,对于这一部分,我们将启用 slow_query_loglong_query_time 将保持为 0.3 (300 ms) ,并且 log_queries_not_using 索引也将启用。

下面是我们将在 employees 数据库上执行的查询。

  1. select * from employees where last _ name = ' Koblick ';
  2. select * from salaries,其中薪金> = 100000;
  3. select * from titles where title = ' Manager ';
  4. select * from employees where year(hire _ date)= 1995;
  5. select year(e.hire_date),max(s . salary from employees e join salary s on e . EMP _ no = s . EMP _ no group by year(e . hire _ date);

现在,查询 134 在 300 毫秒内执行,但是如果我们检查缓慢的查询日志,我们会发现这些查询被记录下来,因为它们没有使用任何索引。查询 25 耗时超过 300 毫秒,并且不使用任何索引。

使用以下命令获取慢速查询日志的摘要

mysqldumpslow /var/lib/mysql/mysql-slow.log

slow query log analysis

除了提到的查询之外,快照中还有其他一些查询。Mysqldumpslow 替换 N(对于数字)和 S(对于字符串)使用的实际值。这可以被-a选项覆盖,但是如果在类似的查询中使用不同的值,这将增加输出行。

解释计划

EXPLAIN 命令用于我们想要分析的任何查询。它描述了查询执行计划,MySQL 如何查看和执行查询。EXPLAIN 使用 Select、Insert、Update 和 Delete 语句。它讲述了查询的不同方面,例如,如何连接表,是否使用索引等。这里重要的是理解查询的基本解释计划输出,以确定其性能。

让我们以下面的查询为例,

mysql> explain select * from salaries where salary = 100000;
+----+-------------+----------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table    | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra       |
+----+-------------+----------+------------+------+---------------+------+---------+------+---------+----------+-------------+
|  1 | SIMPLE      | salaries | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 2838426 |    10.00 | Using where |
+----+-------------+----------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.00 sec) 

以上输出中需要理解的关键方面是:-

  • 分区 -执行查询时考虑的分区数量。只有当表被分区时,它才有效。
  • Possible_keys -创建执行计划时考虑的索引列表。
  • Key -执行查询时将使用的索引。
  • Rows -执行过程中检查的行数。
  • Filtered -被检查的行中被过滤掉的行的百分比。最大和最优化的结果在该字段中将有 100。
  • Extra——这告诉我们一些关于 MySQL 如何评估的额外信息,查询是否只使用 where 子句来匹配目标行、任何索引或临时表等。

因此,对于上面的查询,我们可以确定没有分区,没有要使用的候选索引,因此根本没有使用索引,检查了超过 2M 的行,只有 10%的行包含在结果中,最后,只有 where 子句用于匹配目标行。

创建索引

索引用于加快为给定的列值选择相关行的速度。如果没有索引,MySQL 从第一行开始,遍历整个表来查找匹配的行。如果表中有太多的行,操作会变得很昂贵。有了索引,MySQL 就可以在不读取整个表的情况下确定开始查找数据的位置。

主键也是最快的索引,与表数据一起存储。辅助索引存储在表数据之外,用于进一步提高 SQL 语句的性能。索引大多存储为 B 树,但也有一些例外,比如空间索引使用 R 树,内存表使用散列索引。

创建索引有两种方法:-

  • 当创建一个表时——如果我们预先知道在 select 查询中驱动 where 子句最多的列,那么我们可以在创建一个表时在它们上面放一个索引。
  • 修改表——为了提高一个麻烦的查询的性能,我们使用 ALTER 或 CREATE INDEX 命令在一个已经有数据的表上创建一个索引。此操作不会阻塞表,但可能需要一些时间来完成,具体取决于表的大小。

让我们看一下我们在上一节中讨论的查询。很明显,扫描 2M 记录并不是一个好主意,因为只有 10%的记录在结果集中。

因此,我们在 sales 表的 salary 列上创建了一个索引。

create index idx_salary on salaries(salary)

运筹学

alter table salaries add index idx_salary(salary)

同样的解释计划现在看起来像这样

mysql> explain select * from salaries where salary = 100000;
+----+-------------+----------+------------+------+---------------+------------+---------+-------+------+----------+-------+
| id | select_type | table    | partitions | type | possible_keys | key        | key_len | ref   | rows | filtered | Extra |
+----+-------------+----------+------------+------+---------------+------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | salaries | NULL       | ref  | idx_salary    | idx_salary | 4       | const |   13 |   100.00 | NULL  |
+----+-------------+----------+------------+------+---------------+------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec) 

现在使用的索引是 idx_salary,我们最近创建的那个。该索引实际上只帮助检查了 13 条记录,并且它们都在结果集中。此外,查询执行时间也从 700 毫秒以上减少到几乎可以忽略不计。

让我们看另一个例子。我们在这里搜索名和姓的特定组合。但是,我们也可以只根据姓氏进行搜索。

mysql> explain select * from employees where last_name = 'Dredge' and first_name = 'Yinghua';
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table     | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | employees | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 299468 |     1.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec) 

现在,在近 30 万条记录中,只有 1%是结果集。虽然查询时间特别快,因为我们只有 30 万条记录,但如果记录的数量超过数百万,这将是一个痛苦。在这种情况下,我们在 last_name 和 first_name 上创建一个索引,不是单独创建,而是创建一个包含这两个列的复合索引。

create index idx_last_first on employees(last_name, first_name)

mysql> explain select * from employees where last_name = 'Dredge' and first_name = 'Yinghua';
+----+-------------+-----------+------------+------+----------------+----------------+---------+-------------+------+----------+-------+
| id | select_type | table     | partitions | type | possible_keys  | key            | key_len | ref         | rows | filtered | Extra |
+----+-------------+-----------+------------+------+----------------+----------------+---------+-------------+------+----------+-------+
|  1 | SIMPLE      | employees | NULL       | ref  | idx_last_first | idx_last_first | 124     | const,const |    1 |   100.00 | NULL  |
+----+-------------+-----------+------------+------+----------------+----------------+---------+-------------+------+----------+-------+
1 row in set, 1 warning (0.00 sec) 

我们选择在创建索引时将姓氏放在名字之前,因为优化器在评估查询时从索引最左边的前缀开始。例如,如果我们有一个像 idx(c1,c2,c3)这样的 3 列索引,那么该索引的搜索能力如下- (c1),(c1,c2)或(c1,c2,c3),也就是说,如果 where 子句只有 first_name,则该索引不起作用。

mysql> explain select * from employees where first_name = 'Yinghua';
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
| id | select_type | table     | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra       |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
|  1 | SIMPLE      | employees | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 299468 |    10.00 | Using where |
+----+-------------+-----------+------------+------+---------------+------+---------+------+--------+----------+-------------+
1 row in set, 1 warning (0.00 sec) 

但是,如果 where 子句中只有 last_name,它将按预期工作。

mysql> explain select * from employees where last_name = 'Dredge';
+----+-------------+-----------+------------+------+----------------+----------------+---------+-------+------+----------+-------+
| id | select_type | table     | partitions | type | possible_keys  | key            | key_len | ref   | rows | filtered | Extra |
+----+-------------+-----------+------------+------+----------------+----------------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | employees | NULL       | ref  | idx_last_first | idx_last_first | 66      | const |  200 |   100.00 | NULL  |
+----+-------------+-----------+------------+------+----------------+----------------+---------+-------+------+----------+-------+
1 row in set, 1 warning (0.00 sec) 

再举一个例子,使用以下查询:-

create table employees_2 like employees;
create table salaries_2 like salaries;
alter table salaries_2 drop primary key; 

为了理解 Select with Join 的示例,我们复制了 employees 和 salary 表,但没有 salary 表的主键。

当您有类似下面的查询时,识别查询的难点就变得很棘手。

mysql> select e.first_name, e.last_name, s.salary, e.hire_date from employees_2 e join salaries_2 s on e.emp_no=s.emp_no where e.last_name='Dredge';
1860 rows in set (4.44 sec) 

这个查询大约需要 4.5 秒来完成,结果集中有 1860 行。让我们看看解释计划。解释计划中将有 2 条记录,因为查询中使用了 2 个表。

mysql> explain select e.first_name, e.last_name, s.salary, e.hire_date from employees_2 e join salaries_2 s on e.emp_no=s.emp_no where e.last_name='Dredge';
+----+-------------+-------+------------+--------+------------------------+---------+---------+--------------------+---------+----------+-------------+
| id | select_type | table | partitions | type   | possible_keys          | key     | key_len | ref                | rows    | filtered | Extra       |
+----+-------------+-------+------------+--------+------------------------+---------+---------+--------------------+---------+----------+-------------+
|  1 | SIMPLE      | s     | NULL       | ALL    | NULL                   | NULL    | NULL    | NULL               | 2837194 |   100.00 | NULL        |
|  1 | SIMPLE      | e     | NULL       | eq_ref | PRIMARY,idx_last_first | PRIMARY | 4       | employees.s.emp_no |       1 |     5.00 | Using where |
+----+-------------+-------+------------+--------+------------------------+---------+---------+--------------------+---------+----------+-------------+
2 rows in set, 1 warning (0.00 sec) 

这些是按评估顺序进行的,即首先评估 salaries _ 2,然后将 employees_2 加入其中。看起来,它扫描了 salaries _ 2 表中几乎所有的行,并尝试按照连接条件匹配 employees_2 行。虽然在获取最终结果集时使用了 where 子句,但是对应于 where 子句的索引没有用于 employees_2 表。

如果连接是在两个具有相同数据类型的索引上完成的,那么它总是更快。因此,让我们在 salaries _ 2 表的 emp_no 列上创建一个索引,并再次分析该查询。

create index idx_empno on salaries_2(emp_no);

mysql> explain select e.first_name, e.last_name, s.salary, e.hire_date from employees_2 e join salaries_2 s on e.emp_no=s.emp_no where e.last_name='Dredge';
+----+-------------+-------+------------+------+------------------------+----------------+---------+--------------------+------+----------+-------+
| id | select_type | table | partitions | type | possible_keys          | key            | key_len | ref                | rows | filtered | Extra |
+----+-------------+-------+------------+------+------------------------+----------------+---------+--------------------+------+----------+-------+
|  1 | SIMPLE      | e     | NULL       | ref  | PRIMARY,idx_last_first | idx_last_first | 66      | const              |  200 |   100.00 | NULL  |
|  1 | SIMPLE      | s     | NULL       | ref  | idx_empno              | idx_empno      | 4       | employees.e.emp_no |    9 |   100.00 | NULL  |
+----+-------------+-------+------------+------+------------------------+----------------+---------+--------------------+------+----------+-------+
2 rows in set, 1 warning (0.00 sec) 

现在,索引不仅帮助优化器只检查两个表中的几行,还颠倒了评估中表的顺序。首先计算 employees_2 表,并根据 where 子句的索引选择行。然后根据连接条件使用的索引将记录连接到 salaries _ 2 表。查询的执行时间从 4.5 秒下降到 0.02 秒。

mysql> select e.first_name, e.last_name, s.salary, e.hire_date from employees_2 e join salaries_2 s on e.emp_no=s.emp_no where e.last_name='Dredge'\G
1860 rows in set (0.02 sec) 

并集

原文:https://linkedin.github.io/school-of-sre/level101/databases_sql/lab/

先决条件

安装 Docker

设置

创建一个名为 sos 或类似名称的工作目录,并放入 cd。

在 custom 目录下的 my.cnf 文件中输入以下内容。

sos $ cat custom/my.cnf
[mysqld]
# These settings apply to MySQL server
# You can set port, socket path, buffer size etc.
# Below, we are configuring slow query settings
slow_query_log=1
slow_query_log_file=/var/log/mysqlslow.log
long_query_time=1 

使用以下命令启动容器并启用慢速查询日志:

sos $ docker run --name db -v custom:/etc/mysql/conf.d -e MYSQL_ROOT_PASSWORD=realsecret -d mysql:8
sos $ docker cp custom/my.cnf $(docker ps -qf "name=db"):/etc/mysql/conf.d/custom.cnf
sos $ docker restart $(docker ps -qf "name=db") 

导入示例数据库

sos $ git clone git@github.com:datacharmer/test_db.git
sos $ docker cp test_db $(docker ps -qf "name=db"):/home/test_db/
sos $ docker exec -it $(docker ps -qf "name=db") bash
root@3ab5b18b0c7d:/# cd /home/test_db/
root@3ab5b18b0c7d:/# mysql -uroot -prealsecret mysql < employees.sql
root@3ab5b18b0c7d:/etc# touch /var/log/mysqlslow.log
root@3ab5b18b0c7d:/etc# chown mysql:mysql /var/log/mysqlslow.log 

研讨会 1:运行一些示例查询运行以下内容

$ mysql -uroot -prealsecret mysql
mysql>

# inspect DBs and tables
# the last 4 are MySQL internal DBs

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| employees          |
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+

> use employees;
mysql> show tables;
+----------------------+
| Tables_in_employees  |
+----------------------+
| current_dept_emp     |
| departments          |
| dept_emp             |
| dept_emp_latest_date |
| dept_manager         |
| employees            |
| salaries             |
| titles               |
+----------------------+

# read a few rows
mysql> select * from employees limit 5;

# filter data by conditions
mysql> select count(*) from employees where gender = 'M' limit 5;

# find count of particular data
mysql> select count(*) from employees where first_name = 'Sachin'; 

研讨会 2:使用解释和解释分析来分析查询,确定并添加提高性能所需的索引

# View all indexes on table 
#(\G is to output horizontally, replace it with a ; to get table output)
mysql> show index from employees from employees\G
*************************** 1\. row ***************************
        Table: employees
   Non_unique: 0
     Key_name: PRIMARY
 Seq_in_index: 1
  Column_name: emp_no
    Collation: A
  Cardinality: 299113
     Sub_part: NULL
       Packed: NULL
         Null:
   Index_type: BTREE
      Comment:
Index_comment:
      Visible: YES
   Expression: NULL

# This query uses an index, idenitfied by 'key' field
# By prefixing explain keyword to the command, 
# we get query plan (including key used)
mysql> explain select * from employees where emp_no < 10005\G
*************************** 1\. row ***************************
           id: 1
  select_type: SIMPLE
        table: employees
   partitions: NULL
         type: range
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 4
          ref: NULL
         rows: 4
     filtered: 100.00
        Extra: Using where

# Compare that to the next query which does not utilize any index
mysql> explain select first_name, last_name from employees where first_name = 'Sachin'\G
*************************** 1\. row ***************************
           id: 1
  select_type: SIMPLE
        table: employees
   partitions: NULL
         type: ALL
possible_keys: NULL
          key: NULL
      key_len: NULL
          ref: NULL
         rows: 299113
     filtered: 10.00
        Extra: Using where

# Let's see how much time this query takes
mysql> explain analyze select first_name, last_name from employees where first_name = 'Sachin'\G
*************************** 1\. row ***************************
EXPLAIN: -> Filter: (employees.first_name = 'Sachin')  (cost=30143.55 rows=29911) (actual time=28.284..3952.428 rows=232 loops=1)
    -> Table scan on employees  (cost=30143.55 rows=299113) (actual time=0.095..1996.092 rows=300024 loops=1)

# Cost(estimated by query planner) is 30143.55
# actual time=28.284ms for first row, 3952.428 for all rows
# Now lets try adding an index and running the query again
mysql> create index idx_firstname on employees(first_name);
Query OK, 0 rows affected (1.25 sec)
Records: 0  Duplicates: 0  Warnings: 0

mysql> explain analyze select first_name, last_name from employees where first_name = 'Sachin';
+--------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                    |
+--------------------------------------------------------------------------------------------------------------------------------------------+
| -> Index lookup on employees using idx_firstname (first_name='Sachin')  (cost=81.20 rows=232) (actual time=0.551..2.934 rows=232 loops=1)
 |
+--------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.01 sec)

# Actual time=0.551ms for first row
# 2.934ms for all rows. A huge improvement!
# Also notice that the query involves only an index lookup,
# and no table scan (reading all rows of table)
# ..which vastly reduces load on the DB. 

研讨会 3:识别 MySQL 服务器上的慢速查询

# Run the command below in two terminal tabs to open two shells into the container.
docker exec -it $(docker ps -qf "name=db") bash

# Open a mysql prompt in one of them and execute this command
# We have configured to log queries that take longer than 1s,
# so this sleep(3) will be logged
mysql -uroot -prealsecret mysql
mysql> select sleep(3);

# Now, in the other terminal, tail the slow log to find details about the query
root@62c92c89234d:/etc# tail -f /var/log/mysqlslow.log
/usr/sbin/mysqld, Version: 8.0.21 (MySQL Community Server - GPL). started with:
Tcp port: 3306  Unix socket: /var/run/mysqld/mysqld.sock
Time                 Id Command    Argument
# Time: 2020-11-26T14:53:44.822348Z
# User@Host: root[root] @ localhost []  Id:     9
# Query_time: 5.404938  Lock_time: 0.000000 Rows_sent: 1  Rows_examined: 1
use employees;
# Time: 2020-11-26T14:53:58.015736Z
# User@Host: root[root] @ localhost []  Id:     9
# Query_time: 10.000225  Lock_time: 0.000000 Rows_sent: 1  Rows_examined: 1
SET timestamp=1606402428;
select sleep(3); 

这些是最简单的模拟例子。在现实生活中,查询会更加复杂,解释/分析和慢速查询日志会有更多的细节。

总结

原文:https://linkedin.github.io/school-of-sre/level101/databases_sql/conclusion/

我们已经讲述了 SQL 数据库的基本概念。我们还介绍了 SRE 可能负责的一些任务——还有很多要学和要做的。我们希望本课程给你一个良好的开端,并激励你进一步探索。

进一步阅读

NoSQL

NoSQL 概念

原文:https://linkedin.github.io/school-of-sre/level101/databases_nosql/intro/

先决条件

从本课程中可以期待什么

在培训结束时,您将了解什么是 NoSQL 数据库,它相对于传统 RDBMS 有什么样的优点或缺点,了解不同类型的 NoSQL 数据库,并理解一些基本概念和 w.r.t .到 NoSQL 的权衡。

本课程不包括哪些内容

我们不会深入研究任何特定的 NoSQL 数据库。

课程内容

介绍

当人们使用术语“NoSQL 数据库”时,他们通常用它来指代任何非关系数据库。有人说术语“NoSQL”代表“非 SQL”,有人说它代表“不仅仅是 SQL”不管怎样,大多数人认为 NoSQL 数据库是以关系表以外的格式存储数据的数据库。

一个常见的误解是 NoSQL 数据库或非关系数据库不能很好地存储关系数据。NoSQL 数据库可以存储关系数据,只是存储方式与关系数据库不同。事实上,与 SQL 数据库相比,许多人发现在 NoSQL 数据库中建模关系数据更容易,因为相关数据不必在表之间拆分。

这种数据库自 20 世纪 60 年代末就已经存在,但“NoSQL”这个名字只是在 21 世纪初才被创造出来。美国宇航局使用 NoSQL 数据库跟踪阿波罗任务的库存。随着存储成本的大幅下降,NoSQL 数据库出现在 2000 年代末。仅仅为了减少数据重复而创建复杂、难以管理的数据模型的时代已经一去不复返了。开发人员(而不是存储)正在成为软件开发的主要成本,因此 NoSQL 数据库针对开发人员的工作效率进行了优化。随着敏捷开发方法的兴起,NoSQL 数据库的开发侧重于可伸缩性、快速性能,同时允许频繁的应用更改,并使编程更容易。

NoSQL 数据库的类型:

随着时间的推移,由于这些 NoSQL 数据库是为适应不同公司的需求而开发的,我们最终拥有了相当多类型的数据库。然而,它们可以大致分为 4 种类型。不同类型的数据库之间可能会有一些重叠。他们是

  1. 文档数据库:它们在文档中存储数据,类似于 JSON (JavaScript 对象符号)对象。每个文档都包含成对的字段和值。值通常可以是各种类型,包括字符串、数字、布尔值、数组或对象,它们的结构通常与开发人员在代码中处理的对象一致。优点包括直观的数据模型&灵活的模式。由于其多种多样的字段值类型和强大的查询语言,文档数据库非常适合各种各样的用例,并且可以用作通用数据库。它们可以横向扩展以容纳大量数据。例如:MongoDB、Couchbase
  2. 键值数据库:这是一种比较简单的数据库,其中的每一项都包含键值。值通常只能通过引用其键来检索,因此学习如何查询特定的键-值对通常很简单。键值数据库非常适合需要存储大量数据但不需要执行复杂的查询来检索数据的用例。常见的用例包括存储用户偏好或缓存。Ex: 雷迪斯DynamoDB伏地魔 / 威尼斯 (Linkedin)
  3. 宽列存储:它们在表、行和动态列中存储数据。与关系数据库相比,宽列存储提供了很大的灵活性,因为不要求每行都有相同的列。许多人认为宽列存储是二维键值数据库。当您需要存储大量数据并且可以预测您的查询模式时,宽列存储非常有用。宽列存储通常用于存储物联网数据和用户档案数据。 CassandraHBase 是两家最受欢迎的宽栏商店。
  4. 图数据库:这些数据库在节点和边中存储数据。节点通常存储关于人、地点和事物的信息,而边存储关于节点之间关系的信息。图形数据库的底层存储机制可以变化。一些依赖于关系引擎并将图形数据“存储”在表中(尽管表是逻辑元素,因此这种方法在图形数据库、图形数据库管理系统和实际存储数据的物理设备之间强加了另一个抽象级别)。还有一些使用键值存储或面向文档的数据库进行存储,这使它们具有固有的 NoSQL 结构。图形数据库在需要遍历关系以寻找模式的用例中表现出色,例如社交网络、欺诈检测和推荐引擎。Ex: Neo4j

比较

| | 表演 | 可测量性 | 灵活性 | 复杂性 | 功能 |
| 关键字值 | 高的 | 高的 | 高的 | 没有人 | 可变的 |
| 文档存储 | 高的 | 可变(高) | 高的 | 低的 | 可变(低) |
| 列数据库 | 高的 | 高的 | 温和的 | 低的 | 最小的 |
| 图表 | 可变的 | 可变的 | 高的 | 高的 | 图论 |

SQL 和 NoSQL 的区别

下表总结了 SQL 和 NoSQL 数据库之间的主要差异。

| | SQL 数据库 | NoSQL 数据库 |
| 数据存储模型 | 具有固定行和列的表格 | Document: JSON documents,Key-value: key-value 对,Wide-column:包含行和动态列的表,Graph:节点和边 |
| 主要目的 | 通用 | 文档:通用,键值:具有简单查找查询的大量数据,宽列:具有可预测查询模式的大量数据,图形:分析和遍历连接数据之间的关系 |
| 计划 | 严格的 | 灵活的 |
| 扩缩容 | 垂直(纵向扩展到更大的服务器) | 水平(跨商用服务器横向扩展) |
| 多记录交易 | 支持 | 大多数不支持多记录 ACID 事务。然而,有些——比如 MongoDB——会。 |
| 连接 | 通常需要 | 通常不需要 |
| 数据到对象的映射 | 需要 ORM(对象关系映射) | 许多不需要 ORM。在大多数流行的编程语言中,文档 DB 文档直接映射到数据结构。 |

优势

  • 灵活的数据模型

    大多数 NoSQL 系统都具有灵活的模式。灵活的模式意味着您可以轻松地修改数据库模式来添加或删除字段,以支持不断发展的应用需求。这有助于在没有数据库操作开销的情况下不断开发新功能的应用。

  • 水平缩放

    大多数 NoSQL 系统允许您进行水平扩展,这意味着您可以在任何想要扩展系统的时候添加更便宜的商品硬件。另一方面,SQL 系统通常是垂直扩展的(更强大的服务器)。与传统的 SQL 系统相比,NoSQL 系统还可以托管巨大的数据集。

  • 快速查询

    由于数据非规范化和水平扩展,NoSQL 通常比传统的 SQL 系统快得多。大多数 NoSQL 系统也倾向于将相似的数据存储在一起,以促进更快的查询响应。

  • 开发人员生产力

    NoSQL 系统倾向于基于编程数据结构来映射数据。因此,开发人员需要执行更少的数据转换,从而提高生产率和减少错误。

关键概念

原文:https://linkedin.github.io/school-of-sre/level101/databases_nosql/key_concepts/

当我们谈论 NoSQL 或分布式系统时,让我们看看一些关键概念

CAP 定理

在 2000 年 ACM 的 PODC 研讨会上,Eric Brewer 提出了所谓的 CAP-theorem(CAP-theorem ),这是一个被大型网络公司和 NoSQL 社区广泛采用的主题。CAP 首字母缩写代表 C 一致性, A 可用性&T6】P 分割公差。

  • 一致性

    它指的是系统在执行后的一致性。当一个源所做的写操作对该共享数据的所有读者都可用时,分布式系统被称为一致的。不同的 NoSQL 系统支持不同级别的一致性。

  • 可用性

    它是指系统如何应对由于硬件和软件故障导致的不同系统的功能丧失。高可用性意味着当系统的某个部分由于故障或升级而停机时,系统仍然可以处理操作(读取和写入)。

  • 分区公差

    它是系统在网络分区的情况下继续运行的能力。当一个故障导致两个或多个网络孤岛,系统暂时或永久无法通过孤岛相互通信时,就会发生网络分区。

布鲁尔声称,在共享数据系统中,人们最多只能选择这三个特征中的两个。CAP 定理指出,出于一致性、可用性和分区容差,只能对两个选项进行选择。大规模应用中越来越多的用例倾向于重视可靠性,这意味着可用性和冗余比一致性更有价值。因此,这些系统很难满足酸性。他们通过放松一致性要求(即最终一致性)来实现这一点。

最终一致性意味着随着时间的推移,所有的读者都将看到写入:“在稳定状态下,系统最终将返回最后写入的值”。因此,在更新过程中,客户端可能会面临数据不一致的状态。例如,在复制的数据库中,更新可以进行到一个节点,该节点将最新版本复制到包含修改的数据集的副本的所有其他节点,使得副本节点最终将具有最新版本。

NoSQL 系统支持不同级别的最终一致性模型。例如:

  • 读自己写的一致性

    客户端将在更新完成后立即看到它们。读取操作可能会命中除写入节点之外的节点。但是,他们可能不会立即看到其他客户端的更新。

  • 会话一致性

    客户端将在会话范围内看到对其数据的更新。这通常表示读取和写入发生在同一个服务器上。使用相同节点的其他客户端将收到相同的更新。

  • 偶然的一致性

    如果以下条件成立,则系统提供因果一致性:由潜在因果关系相关的写操作被系统的每个进程按顺序看到。不同的进程可能以不同的顺序观察并发写入

如果对相同数据分区的并发更新不太可能,并且如果客户端不立即依赖于读取由它们自己或由其他客户端发出的更新,则最终一致性是有用的。

根据为系统(或部分系统)选择的一致性模型,确定请求的路由位置,例如复制副本。

瓶盖替代品图解

| 选择 | 特征 | 例子 |
| Consistency + Availability(放弃分区) | 2-phase commits 缓存失效协议 | Single-site databases Cluster databases 轻量级目录访问协议 xFS 文件系统 |
| Consistency + Partition tolerance(丧失可用性) | Pessimistic locking 使少数分区不可用 | 分布式数据库分布式锁定多数协议 |
| 可用性+分区容差(丧失一致性) | expirations/leases 冲突解决乐观 | DNSWeb 缓存 |

分布式系统中数据的版本控制

当数据分布在不同的节点上时,可以同时在不同的节点上对其进行修改(假设实施了严格的一致性)。并发更新的冲突解决出现了问题。一些流行的冲突解决机制有

  • 时间戳

    这是最明显的解决方案。您根据时间顺序对更新进行排序,并选择最新的更新。然而,这依赖于基础设施不同部分之间的时钟同步。当系统的各个部分分布在不同的地理位置时,这变得更加复杂。

  • 乐观锁定

    您将一个唯一的值(如时钟或计数器)与每次数据更新相关联。当客户机想要更新数据时,它必须指定需要更新哪个版本的数据。这意味着您需要跟踪数据版本的历史。

  • 矢量时钟

    向量时钟被定义为来自每个节点的时钟值的元组。在分布式环境中,每个节点维护这样的时钟值的元组,该元组表示节点本身及其对等体/副本的状态。时钟值可以是从本地时钟或版本号导出的真实时间戳

alt_text

矢量时钟插图

与其他冲突解决机制相比,矢量时钟具有以下优势

  1. 不依赖同步时钟
  2. 临时推理不需要修订号的总排序

不需要在不同的节点上存储和维护数据的多个版本。

分割

当数据量超过单个节点的容量时,我们需要考虑拆分数据,为负载平衡和灾难恢复创建副本。根据基础设施的动态性,我们有几种方法可以采用。

  1. 内存缓存

    这些是分区的内存数据库,主要用于临时数据。这些数据库通常被用作传统 RDBMS 的前台。最常用的数据从 rdbms 复制到内存数据库中,以便于快速查询,并减轻后端数据库的负担。一个非常常见的例子是 memcached 或 couchbase。

  2. 聚类

    传统的集群机制从客户端抽象出集群拓扑。客户端不需要知道实际数据驻留在哪里,也不需要知道它正在与哪个节点通信。集群在传统的 RDBMS 中非常常用,它可以在一定程度上帮助扩展持久层。

  3. 将读写分离

    在这种方法中,您将拥有托管相同数据的多个副本。传入的写入通常发送到单个节点(主节点)或多个节点(多主节点),而其余的副本(从节点)处理读取请求。领导者将写入异步复制到所有追随者。然而,写入延迟无法完全避免。有时领导者会在将所有数据复制给追随者之前崩溃。当这种情况发生时,一个拥有最一致数据的追随者可以转变为领导者。正如您现在所意识到的,在这个模型中很难实现完全的一致性。您还需要考虑读写流量的比率。当写操作高于读操作时,这种模式没有意义。复制方法也可能千差万别。一些系统定期进行完整的状态转移,而其他系统使用增量状态转移方法。您也可以通过按顺序转移操作来转移状态。然后,跟随者可以应用与领导者相同的操作来赶上。

  4. 分片

    共享是指以这样一种方式划分数据,即数据均匀地分布在一个节点群集中(就存储和处理能力而言)。它还可能意味着数据局部性,这意味着相似和相关的数据存储在一起,以促进更快的访问。反过来,可以进一步复制一个碎片来满足负载平衡或灾难恢复需求。单个碎片复制副本可以接收所有写入(单个领导者),或者多个复制副本可以接收写入(多个领导者)。读取可以分布在多个副本上。由于数据现在分布在多个节点上,客户端应该能够始终如一地找出数据托管的位置。我们将在下面看看一些常见的技术。分片的缺点是分片之间的连接是不可能的。因此,上游/下游应用必须聚合来自多个碎片的结果。

alt_text

分片示例

散列法

哈希函数是将一段数据(通常描述某种对象,通常为任意大小)映射到另一段数据(通常为整数,称为哈希代码,或简称为哈希的函数。在分区数据库中,将一个键一致地映射到一个服务器/副本是很重要的。

例如:你可以使用一个非常简单的散列作为取模函数。

_p = k mod n_ 

在哪里

p -> partition,

k -> primary key

n -> no of nodes 

这个简单散列的缺点是,每当集群拓扑发生变化时,数据分布也会发生变化。当您处理内存缓存时,将分区分布在各处会很容易。每当一个节点加入/离开拓扑时,分区可以重新排序,缓存未命中可以从后端数据库重新填充。然而,当您查看持久数据时,这是不可能的,因为新节点没有为其提供服务所需的数据。这给我们带来了一致散列。

一致散列法

一致散列是一种分布式散列方案,通过在抽象圆或散列环上给它们分配一个位置,独立于分布式散列表中的服务器或对象的数量进行操作。这允许服务器和对象在不影响整个系统的情况下进行扩展。

假设我们的散列函数 h()生成一个 32 位整数。然后,为了确定我们将向哪个服务器发送密钥 k,我们找到其散列 h(s)是大于 h(k)的最小整数的服务器 s。为了简化这个过程,我们假设这个表是循环的,这意味着如果我们找不到一个散列值大于 h(k)的服务器,我们就绕回并从数组的开头开始查找。

alt_text

一致散列图

在一致散列中,当服务器被移除或添加时,只有来自该服务器的密钥被重定位。例如,如果服务器 S3 被移除,则来自服务器 S3 的所有密钥将被移动到服务器 S4,但是存储在服务器 S4 和 S2 上的密钥不会被重新定位。但是有一个问题,当服务器 S3 被移除时,来自 S3 的密钥不能在剩余的服务器 S4 和 S2 之间平均分配。它们仅被分配给服务器 S4,这增加了服务器 S4 的负载。

为了在添加或删除服务器时在服务器之间平均分配负载,它会为每个服务器创建固定数量的副本(称为虚拟节点),并沿圆圈分布。因此,代替服务器标签 S1、S2 和 S3,我们将有 S10 S11…S19、S20 S21…S29 和 S30 S31…S39。根据具体情况,多个副本的系数也称为重量

映射到复制品 Sij 的所有密钥都存储在服务器 Si 上。要找到一个密钥,我们做同样的事情,找到圆上的密钥的位置,然后向前移动,直到找到一个服务器副本。如果服务器复制品是 Sij,则密钥存储在服务器 Si 中。

假设服务器 S3 被移除,那么所有带有标签 S30 S31 … S39 的 S3 复制品必须被移除。现在,邻近 S3X 标签的对象关键点将自动重新分配给 S1X、S2X 和 S4X。最初分配给 S1、S2 和 S4 的所有密钥都不会被移动。

如果我们添加一个服务器,类似的事情也会发生。假设我们想要添加服务器 S5 作为 S3 的替代,那么我们需要添加标签 S50 S51 … S59。在理想情况下,来自 S1、S2 和 S4 的四分之一的密钥将被重新分配给 S5。

当应用于持久存储时,进一步的问题出现了:如果一个节点已经离开场景,存储在这个节点上的数据变得不可用,除非它之前已经被复制到其他节点;在新节点加入其他节点的相反情况下,相邻节点不再负责它们仍然存储但不再被请求的一些数据,因为相应的对象不再被请求客户端散列到它们。为了解决这个问题,可以引入复制因子(r)。

在分区方案中引入副本,除了可靠性优势之外,还可以将读取请求的工作负载分散到负责所请求数据的任何物理节点。如果客户端必须在数据集的多个版本之间做出决定,则可伸缩性不起作用,因为它们需要从服务器的仲裁中读取,这反过来降低了负载平衡的效率。

法定人数

Quorum 是群集中必须联机并且能够相互通信的最小节点数。如果超过此阈值出现任何额外的节点故障,群集将停止运行。

要获得法定人数,您需要大多数节点。通常是(N/2 + 1),其中 N 是系统中节点的总数。对于 ex 来说,

在 3 节点集群中,大多数情况下需要 2 个节点,

在 5 节点集群中,大多数情况下需要 3 个节点,

在 6 节点群集中,大多数情况下需要 4 个节点。

alt_text

法定人数示例

网络问题会导致群集节点之间的通信失败。一组节点可能能够在网络的功能部分一起通信,但是不能与网络的另一部分中的不同组节点通信。这就是众所周知的在集群或集群分割中的裂脑。

现在,具有仲裁的分区被允许继续运行应用。其他分区将从集群中删除。

例如:在一个 5 节点集群中,考虑如果节点 1、2 和 3 可以相互通信,但不能与节点 4 和 5 通信,会发生什么情况。节点 1、2 和 3 构成了大多数,它们继续作为一个集群运行。作为少数,节点 4 和 5 停止作为集群运行。如果节点 3 失去与其他节点的通信,所有节点将停止作为群集运行。但是,所有运行的节点将继续侦听通信,以便当网络重新开始工作时,群集可以形成并开始运行。

下图演示了在分为两个集的集群上选择仲裁。

alt_text

集群定额示例

总结

原文:https://linkedin.github.io/school-of-sre/level101/databases_nosql/further_reading/

我们已经讲述了 NoSQL 数据库的基本概念。要学的和要做的还有很多。我们希望本课程给你一个良好的开端,并激励你进一步探索。

进一步阅读

NoSQL:

hostingdata.co.uk/nosql-database/

www.mongodb.com/nosql-explained

www.mongodb.com/nosql-explained/nosql-vs-sql

Cap 定理

www.julianbrowne.com/article/brewers-cap-theorem

可测量性

www . slide share . net/JB oner/scalability-avail ability-stability-patterns

最终一致性

www . all things distributed . com/2008/12/finally _ consistent . html

www.toptal.com/big-data/consistent-hashing

web . Stanford . edu/class/cs 244/papers/chord _ TON _ 2003 . pdf

大数据

大数据

原文:https://linkedin.github.io/school-of-sre/level101/big_data/intro/

先决条件

  • Linux 文件系统基础。
  • 对系统设计有基本的了解。

从本课程中可以期待什么

本课程涵盖大数据的基础知识,以及它是如何演变成今天的样子的。我们将看看大数据非常适合的几个现实场景。设计大数据系统的一个有趣任务是理解 Hadoop 的架构和围绕它的工具。

本课程不包括哪些内容

编写程序从数据中提取分析。

课程内容

  1. 大数据概述
  2. 大数据技术的使用
  3. Hadoop 的演变
  4. Hadoop 的架构
    1. HDFS
    2. 故事
  5. MapReduce 框架
  6. 围绕 hadoop 的其他工具
    1. 储备
    2. 火花
    3. 很快
  7. 数据序列化和存储

大数据概述

  1. 大数据是无法使用传统计算技术处理的大型数据集的集合。它不是单一的技术或工具,而是已经成为一门完整的学科,涉及各种工具、技术和框架。
  2. 大数据可能包括
    1. 结构数据
    2. 非结构化数据
    3. 半结构化数据
  3. 大数据的特征:
    1. 多样化
    2. 速度
    3. 变化性
  4. 大数据生成的例子包括证券交易所、社交媒体网站、喷气发动机等。

大数据技术的使用

  1. 以交通灯问题为例。
    1. 截至 2018 年,美国有超过 30 万个交通灯。
    2. 让我们假设我们在每台机器上放置了一个设备来收集指标,并将其发送到一个中央指标收集系统。
    3. 如果每个物联网设备每分钟发送 10 个事件,我们每天有 300000 个 0x10x60x24 = 432x10⁷ 事件。
    4. 你将如何处理这些信息,并告诉我在某一天的上午 10:45 有多少个信号是“绿色”的?
  2. 考虑统一支付接口(UPI)交易的下一个示例:
    1. 2019 年 10 月,我们在印度的 UPI 交易量约为 11.5 亿笔。
    2. 如果我们试着将这个数据推断到一年左右,并试着找出通过某个特定的 UPI ID 发生的一些常见支付,你建议我们怎么做?

Hadoop 的发展

原文:https://linkedin.github.io/school-of-sre/level101/big_data/evolution/

Evolution of hadoop

Hadoop 的架构

  1. HDFS

    1. Hadoop 分布式文件系统(HDFS)是一个分布式文件系统,旨在运行在商用硬件上。它与现有的分布式文件系统有许多相似之处。然而,与其他分布式文件系统的区别是显著的。
    2. HDFS 具有高度容错能力,旨在部署在低成本硬件上。HDFS 提供对应用数据的高吞吐量访问,适用于具有大型数据集的应用。
    3. HDFS 是 Apache Hadoop 核心项目 T1 的一部分。

    HDFS Architecture

    HDFS 的主要成分包括:1 .NameNode:是集群中文件命名空间的仲裁器和中央存储库。NameNode 执行打开、关闭和重命名文件和目录等操作。2.DataNode:管理连接到运行它的节点的存储。它负责处理所有的读写请求。它对 NameNode 上的指令执行操作,例如创建、删除和复制块。3.客户端:负责从 namenode 获取所需的元数据,然后与 datanodes 通信以进行读取和写入。

  2. YARN 代表“又一个资源谈判者”。它是在 Hadoop 2.0 中引入的,以消除 Hadoop 1.0 中存在的作业跟踪器的瓶颈。YARN 在推出时被描述为“重新设计的资源管理器”,但现在它已经发展成为一个用于大数据处理的大规模分布式操作系统。

    YARN Architecture

    纱线结构的主要组成部分包括:1 .客户机:它向资源管理器提交 map-reduce(MR)作业。2.资源管理器:它是 YARN 的主守护进程,负责所有应用之间的资源分配和管理。每当它接收到一个处理请求时,它就将其转发给相应的节点管理器,并相应地分配资源以完成该请求。它有两个主要组成部分:1 .调度程序:它根据分配的应用和可用资源执行调度。它是一个纯粹的调度程序,这意味着它不执行其他任务,如监视或跟踪,也不保证在任务失败时重新启动。YARN scheduler 支持容量调度器和公平调度器等插件来划分集群资源。2.应用管理器:它负责接受应用并协商来自资源管理器的第一个容器。如果任务失败,它还会重新启动应用管理器容器。3.节点管理器:它负责 Hadoop 集群上的各个节点,并管理应用和工作流以及特定的节点。它的主要工作是跟上节点管理器。它监视资源使用情况,执行日志管理,并根据资源管理器的指示终止容器。它还负责创建容器进程,并在应用主机的请求下启动它。4.应用主程序:应用是提交给框架的单个作业。应用管理器负责与资源管理器协商资源,跟踪状态,并监视单个应用的进度。应用主机通过发送容器启动上下文(CLC)向节点管理器请求容器,该上下文包括应用运行所需的一切。一旦应用启动,它会不时地向资源管理器发送健康报告。5.容器:它是一个物理资源的集合,例如单个节点上的 RAM、CPU 内核和磁盘。容器由容器启动上下文(CLC)调用,CLC 是包含诸如环境变量、安全令牌、依赖性等信息的记录。

MapReduce 框架

MapReduce Framework

  1. 术语 MapReduce 代表 Hadoop 程序执行的两个独立且不同的任务——映射作业和简化作业。地图作业将数据集作为输入,并对其进行处理以生成键值对。Reduce job 获取地图作业的输出,即键值对,并聚合它们以生成所需的结果。
  2. Hadoop MapReduce(Hadoop Map/Reduce)是一个在计算集群上分布式处理大型数据集的软件框架。Mapreduce 有助于将输入数据集分割成多个部分,同时并行运行所有数据部分的程序。
  3. 请查找以下字数示例,演示 MapReduce 框架的用法:

Word Count Example

围绕 Hadoop 的其他工具

  1. 鼠标

    1. 使用一种叫做 HQL 的语言,它非常像 SQL。让非程序员能够在 Hadoop 中查询和分析数据。基本上是 map-reduce 之上的一个抽象层。
    2. 《出埃及记》HQL 质问:
      1. SELECT pet.name,评论来自(pet.name = event.name)上的宠物加入活动;
    3. 在 mysql 中:
      1. SELECT pet.name,评论来自 pet,event 其中 pet . name = event . name;
    1. 使用一种称为 Pig Latin 的脚本语言,这种语言更受工作流驱动。不需要成为专业的 Java 程序员,但需要一些编码技能。也是 map-reduce 之上的一个抽象层。
    2. 这里有一个简单的问题:针对下图中左栏中的数据运行右栏中的 pig 查询的输出是什么?

    Pig Example

    输出:7,Komal,Nayak,24,9848022334,trivendram 8,Bharathi,Nambiayar,24,9848022333,Chennai 5,Trupthi,Mohanthy,23,9848022336,Bhuwaneshwar 6,Archana,Mishra,23,9848022335,Chennai

  2. 火花

    1. Spark 为内存集群计算提供了原语,允许用户程序将数据加载到集群的内存中并重复查询,使其非常适合机器学习算法。
  3. 转眼间

    1. Presto 是一个针对大数据的高性能分布式 SQL 查询引擎。
    2. 其架构允许用户查询各种数据源,如 Hadoop、AWS S3、Alluxio、MySQL、Cassandra、Kafka 和 MongoDB。
    3. 示例快速查询:use studentDB; show tables; SELECT roll_no, name FROM studentDB.studentDetails where section=’A’ limit 5;

数据序列化和存储

  1. 为了在网络上传输数据或存储在一些永久存储器上,我们使用将数据结构或对象状态转换成二进制或文本形式的过程。我们称这个过程为序列化..
  2. Avro 数据存储在容器文件(. avro 文件)中,其模式(。avsc 文件)与数据文件一起存储。
  3. Apache Hive 支持将表存储为 Avro,并且还可以查询这种序列化格式的数据。

任务和结论

原文:https://linkedin.github.io/school-of-sre/level101/big_data/tasks/

培训后任务:

  1. 尝试建立自己的 3 节点 Hadoop 集群。
    1. 基于虚拟机的解决方案可在这里找到
  2. 写一份你选择的简单的 spark/MR 工作,了解如何从数据中生成分析。
    1. 样本数据集可以在这里找到

参考资料:

  1. Hadoop 文档
  2. HDFS 建筑
  3. 纱线架构
  4. 谷歌 GFS 论文

系统设计

系统设计

原文:https://linkedin.github.io/school-of-sre/level101/systems_design/intro/

先决条件

常用软件系统组件的基础:

从本课程中可以期待什么

考虑和设计大型软件系统的可伸缩性、可用性和可靠性。

本课程不包括哪些内容

单个软件组件的可伸缩性和可靠性问题,例如数据库,虽然可以应用相同的可伸缩性原则和思想,但这些单个组件在伸缩它们和考虑它们的可靠性时有它们自己的特定细微差别。

我们将更多地关注概念,而不是设置和配置负载平衡器等组件来实现系统的可伸缩性、可用性和可靠性

课程内容

介绍

那么,你如何着手学习设计一个系统呢?

像大多数伟大的问题一样,这个问题表现出惊人的天真。我能给出的唯一简短的答案是,从本质上讲,你通过设计系统并找出什么可行,什么不可行,学会了如何设计一个系统。”Jim Waldo,Sun Microsystems,关于系统设计

由于软件和硬件系统有多个移动部分,我们需要考虑这些部分将如何增长,它们的故障模式,它们的相互依赖性,它将如何影响用户和业务。

学习或做系统设计没有一蹴而就的方法或途径,我们只有通过设计和迭代来学习设计系统。

本课程将是一个开端,使人们在系统设计过程中思考可伸缩性、可用性和容错性。

背景

让我们设计一个简单的内容共享应用,用户可以在我们的应用中共享他们的朋友喜欢的照片和媒体。让我们从应用的简单设计开始,并随着我们学习系统设计概念而发展它

First architecture diagram

可测量性

原文:https://linkedin.github.io/school-of-sre/level101/systems_design/scalability/

可伸缩性对一个系统/服务意味着什么?一个系统由服务/组件组成,每个服务/组件的可伸缩性需要单独处理,而系统的可伸缩性作为一个整体。

如果随着资源被添加到系统中,服务的性能以与添加的资源成比例的方式提高,则称该服务是可伸缩的

如果添加资源以促进冗余不会导致性能损失,则称永远在线服务是可伸缩的

参考

可扩展性- AKF 规模的立方体

Scale Cube 是一个用于细分服务、定义微服务和扩展产品的模型。它还为团队在设计解决方案时讨论与规模相关的选项创造了一种通用语言。下一节根据我们对 AKF 立方体的推断来讨论某些扩展模式

可扩展性-水平扩展

水平伸缩代表应用或服务的克隆,这样工作就可以毫无偏见地轻松分配到不同的实例中。

让我们看看我们的单片应用如何利用这一原则进行改进

Horizontal Scaling

这里,DB 与应用分开缩放。这是为了让您知道每个组件的扩展能力可能是不同的。通常,web 应用可以通过添加资源来扩展,除非应用中存储了状态。但是,通过添加更多的跟随者,数据库只能针对读取进行扩展,但写入必须只针对一个领导者,以确保数据的一致性。有一些数据库支持多领导写,但我们在这一点上把它们排除在范围之外。

应用应该能够区分读取和写入,以选择合适的数据库服务器。负载平衡器可以透明地在相同的服务器之间分割流量。

什么:复制服务或数据库以分散事务负载。

何时使用:读写比率非常高的数据库(5:1 或更高—越高越好)。因为只能缩放数据库的读取副本,而不能缩放主数据库。

使用方法:简单地克隆服务,实现一个负载均衡器。对于数据库,确保访问代码理解读和写之间的区别。

原因:允许以复制数据和功能为代价快速扩展交易。

关键要点:这实现起来很快,从开发人员的角度来看成本很低,并且可以很好地扩展交易量。但是,从数据运营成本的角度来看,它们往往成本较高。这里的成本意味着,如果我们有 3 个追随者和 1 个领导者数据库,相同的数据库将作为 4 个副本存储在 4 个服务器。因此增加了存储成本

参考

可伸缩性模式-负载平衡

改善多种计算资源(如计算机、计算机集群、网络链接、中央处理器或磁盘驱动器)之间的工作负载分配。一种常用的技术是在相同的服务器集群之间负载平衡流量。类似的原理被用于通过 ECMP ,磁盘驱动器通过 RAID 等来平衡网络链路上的流量

旨在优化资源使用,最大化吞吐量,最小化响应时间,并避免任何单个资源过载。使用具有负载平衡的多个组件而不是单个组件可以通过冗余提高可靠性和可用性。在我们更新的架构图中,我们有 4 台服务器来处理应用流量,而不是一台服务器

执行负载平衡的设备或系统称为负载平衡器,缩写为 LB。

参考

可伸缩性模式- LB 任务

LB 是做什么的?

服务发现:

系统中有哪些后端可用?在我们的架构中,有 4 台服务器可用于服务应用流量。LB 充当单个端点,客户端可以透明地使用它来访问 4 个服务器中的一个。

健康检查:

哪些后端目前是健康的,可以接受请求?如果 4 个应用服务器中有一个坏了,LB 会自动缩短路径,这样客户端就不会感觉到任何应用宕机

负载平衡:

应该使用什么算法来平衡健康后端的各个请求?有许多算法可以在四台服务器之间分配流量。基于观察/经验,SRE 可以选择适合他们模式的算法

可伸缩性模式- LB 方法

常见的负载平衡方法

最少联系方法

将流量定向到活动连接最少的服务器。当在服务器之间不均匀分布的流量中有大量持久连接时最有用。如果客户端保持长期连接,则有效

最短响应时间法

将流量定向到具有最少活动连接和最低平均响应时间的服务器。这里,响应时间用于提供服务器健康状况的反馈

循环法

通过将流量定向到第一个可用的服务器来轮换服务器,然后将该服务器移动到队列的底部。当服务器规格相同并且没有很多持久连接时最有用。

IP 哈希

客户端的 IP 地址决定了哪个服务器接收请求。这有时会导致分布偏斜,但如果应用在本地存储一些状态并需要一些粘性,这是有用的

更高级的客户端/服务器端示例技术-https://docs.nginx.com/nginx/admin-guide/load-balancer/-http://cbonte.github.io/haproxy-dconv/2.2/intro.html#3.3.5-https://Twitter . github . io/finagle/guide/clients . html #负载平衡

可扩展性模式-缓存-内容交付网络(CDN)

cdn 被添加到离客户位置更近的地方。如果应用有静态数据,如图像、Javascript、CSS,它们不会经常改变,它们可以被缓存。由于我们的例子是一个内容共享网站,静态内容可以缓存在 cdn 中,并有一个合适的有效期。

CDN block diagram

什么:使用 cdn(内容交付网络)来减少你网站的流量。

何时使用:当速度提高和规模保证额外成本时。

如何使用:大多数 cdn 利用 DNs 为您的网站提供内容。因此,您可能需要对 DNS 进行微小的更改或添加,并将内容从新的子域名转移到其他域名。

media-exp1.licdn.com 是 Linkedin 用来提供静态内容的一个域名

这里,CNAME 将域指向 CDN 提供商的 DNS

挖 media-exp1.licdn.com+做空

2-01-2c3e-005c.cdx .雪松. net。

原因:cdn 有助于减轻流量高峰,通常是扩展网站部分流量的经济方式。它们通常还会大大缩短页面下载时间。

要点:cdn 是一种快速、简单的方法,可以抵消流量峰值和总体流量增长。确保执行成本效益分析并监控 CDN 的使用情况。如果 CDN 有很多缓存未命中,那么我们不会从 CDN 中获得太多,并且仍然使用我们的计算资源来服务请求。

可扩展性-微服务

这种模式代表了应用中服务或功能的工作分离。微服务旨在解决与代码库和数据集中的增长和复杂性相关的问题。目的是创建故障隔离并减少响应时间。

微服务可以扩展事务、数据大小和代码库大小。它们在扩展代码库的规模和复杂性方面最为有效。因为工程团队需要重写服务,或者至少需要将服务从最初的单一应用中分离出来,所以它们的成本往往比水平扩展要高一些。

Microservices block diagram

WHAT: 该规则有时被称为通过服务或资源进行扩展,它侧重于通过沿着动词(服务)或名词(资源)的边界划分数据集、事务和工程团队来进行扩展。

何时使用:不需要数据间关系的超大型数据集。大型复杂系统,其中扩展工程资源需要专业化。

如何使用:用动词拆分动作,用名词拆分资源,或者混合使用。按照动词/名词方法定义的路线来划分服务和数据。

原因:不仅允许高效扩展事务,还允许扩展与这些事务相关的超大型数据集。它还允许团队的有效扩展。

要点:微服务支持高效扩展事务、大型数据集,并有助于故障隔离。它有助于减少团队的通信开销。代码库变得不那么复杂,因为不相交的功能被解耦并作为新的服务旋转,从而让每个服务根据其需求独立扩展。

参考

可伸缩性-分片

该模式表示基于在事务时查找或确定的属性的工作分离。最常见的是,这些被实现为由请求者、顾客或客户进行的分割。

通常,需要为这些类型的拆分编写查找服务或确定性算法。

分片有助于扩展事务增长、扩展指令集和减少处理时间(最后一点是通过限制执行任何事务所需的数据)。这对于扩大顾客或客户的增长更有效。它可以帮助灾难恢复工作,并将事件的影响限制在特定的客户群。

Sharding-block-1

在这里,auth 数据基于用户名进行分片,这样数据库可以更快地响应,因为数据库在查询期间必须处理的数据量已经大大减少。

还可以有其他的分割方式

Sharding-block-2

在这里,整个数据中心被分割和复制,客户端根据其地理位置被定向到数据中心。这有助于提高性能,因为客户端被定向到最近的数据中心,并且随着我们添加更多的数据中心,性能也会提高。这种方法需要注意一些复制和一致性开销。这还通过将测试特性推广到一个站点并在对该地理区域有影响时回滚来提供容错

WHAT: 这通常是根据客户的某些独特方面进行的划分,如客户 ID、姓名、地理位置等。

何时使用:非常大、相似的数据集,例如大型且快速增长的客户群,或者当地理上分散的客户群的响应时间很重要时。

使用方法:识别您所知道的关于客户的一些信息,例如客户 ID、姓氏、地理位置或设备,并根据该属性拆分或划分数据和服务。

原因:客户的快速增长超过了其他形式的数据增长,或者您需要在扩展时在某些客户群之间执行故障隔离。

关键要点:碎片可以有效地帮助您扩展客户群,但也可以应用于无法使用微服务方法分离的其他超大型数据集。

参考

SRE 角色中的应用

  1. SREs 与网络团队合作,研究如何将用户流量映射到特定站点。https://engineering.linkedin.com/blog/2017/05/trafficshift 负荷测试规模
  2. SREs 与开发团队紧密合作,将 monoliths 拆分为多个易于运行和管理的微服务
  3. SREs 致力于提高负载平衡器的可靠性、服务发现和性能
  4. sre 紧密合作,将数据分割成碎片,并管理数据的完整性和一致性。https://engineering . LinkedIn . com/espresso/introducing-espresso-linkedins-hot-new-distributed-document-store
  5. sre 负责设置、配置和提高 CDN 缓存命中率。

高可用性—可用性—常见的“9”

原文:https://linkedin.github.io/school-of-sre/level101/systems_design/availability/

可用性通常用“9”来表示,常见的“9”列举如下。

可用性% 每年停机时间 每月停机时间 每周停机时间 每天停机时间
99%(两个 9) 3.65 天 7.31 小时 1.68 小时 14.40 分钟
99.5%(两个半九) 1.83 天 3.65 小时 50.40 分钟 七点二十分钟
99.9%(三个九) 8.77 小时 43.83 分钟 10.08 分钟 1.44 分钟
99.95%(三个半九) 4.38 小时 21.92 分钟 5.04 分钟 43.20 秒
99.99%(四个九) 52.60 分钟 4.38 分钟 1.01 分钟 8.64 秒
99.995%(四个半九) 26.30 分钟 2.19 分钟 三十点二十四秒 4.32 秒
99.999%(五个九) 5.26 分钟 26.30 秒 6.05 秒 864.0 毫秒

参考

高可用性系列组件

如果一个部件的故障导致组合变得不可操作,则带有部件的系统串联运行。

例如,如果我们架构中的 LB 失败,所有对应用层的访问都将失败。LB 和 app 层是串行连接的。

系统的综合可用性是单个部件可用性的乘积

A = Ax x Ay x …..

参考

高可用性并行组件

如果一个部件的故障导致另一个部件接管故障部件的操作,则具有部件的系统并行操作。

如果我们有一个以上的 LB,并且如果在一个 LB 出现故障时,其余的 LB 可以接管流量,那么 LB 是并行运行的

系统的综合可用性为

A = 1 - ( (1-Ax) x (1-Ax) x …..)

参考

HA -核心原则

消除单点故障(SPOF) 这意味着为系统增加冗余,这样一个组件的故障并不意味着整个系统的故障。

可靠的交叉在冗余系统中,交叉点本身往往会成为单点故障。可靠的系统必须提供可靠的交叉。

故障发生时的检测如果遵守上述两个原则,用户可能永远也看不到故障

参考

哈- SPOF

什么:从不实施,总是消除单点故障。

何时使用:在架构评审和新设计期间。

如何使用:在架构图上识别单个实例。争取主动/主动配置。至少我们应该有一个备用实例在活动实例失败时接管控制权。

为什么:通过多个实例实现可用性最大化。

要点:争取主动/主动解决方案,而不是主动/被动解决方案。使用负载平衡器来平衡服务实例之间的流量。对于需要单一实例的模式,使用带有主动/被动实例的控制服务。

高可用性可靠交叉

内容:确保系统组件故障转移时能够可靠地进行。

何时使用:在架构评审、故障建模和设计期间。

如何使用:确定交叉期间系统的可用性,并确保其在可接受的范围内。

为什么:最大化可用性并确保数据处理语义得到保留。

关键要点:争取主动/主动解决方案,而不是主动/被动解决方案,它们不可靠的风险更小。使用 LB 和正确的负载平衡方法来确保可靠的故障转移。建模并构建您的数据系统,以确保在交叉发生时数据得到正确处理。通常,数据库系统遵循主动/被动的写语义。主设备接受写入,当主设备停机时,从设备被提升为主设备(从被动变为主动)以接受写入。这里我们必须小心,切换永远不会引入一个以上的主设备。这个问题叫做裂脑。

SRE 角色中的应用

  1. SRE 致力于决定一个可接受的 SLA,并确保系统可用于实现 SLA
  2. SRE 从建立数据中心开始就参与架构设计,以确保该站点不受网络交换机、硬件、电源或软件故障的影响
  3. SRE 还进行故障模拟演习,以观察系统在未知领域的表现,并在出现失误时提出改善可用性的计划。https://engineering . LinkedIn . com/blog/2017/11/resilience-engineering-at-LinkedIn-with-project-water bear

贴出我们对 HA 的理解,我们的架构图如下所示HA Block Diagram

容错

原文:https://linkedin.github.io/school-of-sre/level101/systems_design/fault-tolerance/

在任何系统中,故障都是不可避免的,而且会一直发生,因此我们需要构建能够容忍故障或从故障中恢复的系统。

  • 在系统中,失败是常态而不是例外。
  • “任何可能出错的事情都会出错”——墨菲定律
  • “复杂系统包含潜在的不断变化的故障组合”——复杂系统是如何失败的。

容错-故障度量

为任何系统测量和跟踪的常见故障指标。

平均修复时间(MTTR): 修复和恢复故障系统的平均时间。

平均故障间隔时间(MTBF): 一个设备故障或系统故障与下一个设备故障或系统故障之间的平均运行时间。

平均无故障时间(MTTF): 设备或系统在出现故障前预期运行的平均时间。

平均检测时间(MTTD): 从问题出现到组织检测到问题的平均时间。

平均调查时间(MTTI): 从发现事件到组织开始调查其原因和解决方案的平均时间。

恢复服务的平均时间(MTRS): 从检测到事件到受影响的系统或组件再次可供用户使用的平均时间。

系统事件平均间隔时间(MTBSI): 检测到两个连续事件之间的平均经过时间。MTBSI 可以通过将 MTBF 和 MTRS 相加来计算(MTBSI = MTBF + MTRS)。

故障率:另一个可靠性指标,衡量组件或系统发生故障的频率。它表示为单位时间内的失败次数。

参考

容错-故障隔离术语

系统应该有短路。比方说,在我们的内容共享系统中,如果“通知”不起作用,网站应该通过删除功能而不是关闭整个网站来优雅地处理这一故障。

泳道是常用的故障隔离方法之一。Swimlane 为该服务添加了一个来自其他服务的屏障,因此其中任何一个服务的故障都不会影响到另一个服务。假设我们在内容分享应用中推出了一项新功能“广告”。我们可以有两种架构Swimlane

如果在每个新闻订阅源请求期间动态同步生成广告,则广告功能中的错误会传播到新闻订阅源功能。相反,如果我们将“广告生成”服务泳道化,并使用共享存储来填充 Newsfeed 应用,广告故障不会级联到 Newsfeed,最糟糕的情况是,如果广告不符合 SLA,我们可以拥有没有广告的 Newsfeed。

让我们再举一个例子,我们已经为我们的内容分享应用提出了一个新的模型。在这里,我们推出了一个企业内容共享应用,企业为服务付费,内容不应在企业外部共享。

Swimlane-principles

泳道原则

原则 1: 什么都不分享(又称“尽量少分享”)。泳道内共享的越少,泳道的故障隔离性就越强。(如企业用例所示)

原则 2: 没有东西越过泳道边界。同步(通过预期请求而不是传输协议来定义)通信从不跨越泳道边界;如果有,则边界绘制不正确。(如广告特写所示)

泳道进场

方法 1: 泳道赚钱。永远不要让你的收银机受到其他系统的损害。(企业使用案例中的第 1 层与第 2 层)

方法二:泳道事件的最大来源。确定疼痛的重复原因并隔离它们。(如果广告功能处于黄色代码状态,游泳是最佳选择)

方法三:泳道天然屏障。顾客边界是很好的泳道。(公共与企业客户)

参考

SRE 角色中的应用

  1. 与 DC 技术或云团队合作,通过在数据中心内创建故障区域来分布基础架构,使其不受交换机或电源故障的影响 https://docs . Microsoft . com/en-us/azure/virtual-machines/manage-avail ability # use-avail ability-zones-to-protect-from-Data Center-level-failures
  2. 与合作伙伴一起工作,设计服务之间的交互,这样一个服务故障不会以级联方式扩大到所有上游

总结

原文:https://linkedin.github.io/school-of-sre/level101/systems_design/conclusion/

有了这些原则,我们希望这门课能给设计软件系统一个新的视角。在第一天就得到所有这些可能是过度工程化了。但是有些从一开始就非常重要,比如消除单点故障,通过增加副本来提供可扩展的服务。当达到一个瓶颈时,我们可以按服务分割代码,按比例分割数据。随着组织的成熟,引入混沌工程来测量系统如何应对失败将有助于设计健壮的软件系统。

度量和监控

先决条件

原文:https://linkedin.github.io/school-of-sre/level101/metrics_and_monitoring/introduction/

从本课程中可以期待什么

监控是任何系统不可或缺的一部分。作为一名 SRE,您需要对监控服务基础设施有一个基本的了解。本课程结束时,您将对以下主题有更好的理解:

  • 什么是监控?

    • 需要衡量什么

    • 如何使用收集的指标来改进业务决策和整体可靠性

    • 带警报的主动监控

    • 日志处理及其重要性

  • 什么是可观测性?

    • 分布式跟踪

    • 日志

    • 韵律学

本课程没有涵盖哪些内容

  • 监控基础设施设置指南

  • 深入研究不同的监控技术以及任何工具的基准测试或比较

课程内容

结论

介绍

监控是从系统中收集实时性能指标、分析数据以获得有意义的信息,并将数据显示给用户的过程。简单地说,您定期测量各种指标以了解系统的状态,包括但不限于用户请求、延迟和错误率。什么被测量,什么被固定 -如果你能测量什么,你就能推理它,理解它,讨论它,并自信地采取行动。

监控的四个黄金信号

为系统设置监控时,您需要决定测量什么。监控的四个黄金信号提供了对服务性能的良好理解,并为监控系统奠定了基础。这四个黄金信号是

  • 交通

  • 潜伏

  • 错误

  • 浸透

这些指标有助于您了解系统性能和瓶颈,并创建更好的最终用户体验。正如在谷歌 SRE 的书中所讨论的,如果你只能衡量你的服务的四个指标,那么专注于这四个。让我们来看看这四个黄金信号。

  • 交通 - 交通更好地了解服务需求。通常被称为服务 QPS (每秒查询数),流量是服务所服务的请求的度量。该信号帮助您决定何时需要扩大服务规模以应对不断增长的客户需求,何时需要缩小规模以实现成本效益。

  • 延迟 - 延迟是服务处理传入请求和发送响应所花费的时间。测量服务延迟有助于及早发现服务的缓慢下降。区分成功请求的延迟和失败请求的延迟非常重要。例如,由于失去与数据库或其他关键后端的连接而触发的 HTTP 5XX 错误可能会很快得到处理。但是,因为 HTTP 500 错误表示请求失败,所以将 500 秒计入总延迟可能会导致误导性的计算。

  • 错误(速率) - 错误是对失败的客户端请求的度量。根据响应代码( HTTP 5XX 错误)可以很容易地识别这些故障。可能会出现由于错误的结果数据或违反策略而导致响应被视为错误的情况。例如,您可能得到一个 HTTP 200 响应,但是主体具有不完整的数据,或者响应时间超出了商定的 SLA s。因此,您需要有其他机制(代码逻辑或工具)来捕获响应代码之外的错误。

  • 饱和度 - 饱和度是服务对资源利用率的度量。这个信号告诉您服务资源的状态以及它们有多满。这些资源包括内存、计算、网络 I/O 等。甚至在资源利用率达到 100%之前,服务性能就会缓慢下降。因此,有一个利用率目标是很重要的。等待时间的增加是饱和的良好指示;测量潜伏期的第 99 百分位数有助于饱和的早期检测。

根据服务的类型,您可以用不同的方式测量这些信号。例如,您可以测量 web 服务器每秒处理的查询数。相比之下,对于数据库服务器,通过执行的事务和创建的数据库会话,您可以了解数据库服务器处理的流量。借助额外的代码逻辑(监控库和仪器),您可以定期测量这些信号,并存储它们以供将来分析。尽管这些指标让您了解了服务端的性能,但是您还需要确保在客户端提供相同的用户体验。因此,您可能需要从服务基础设施之外监控服务,这将在第三方监控中讨论。

为什么监控很重要?

监控在服务的成功中起着关键的作用。如前所述,监控为理解服务健康状况提供了性能洞察。通过访问随时间收集的历史数据,您可以构建智能应用来满足特定需求。一些关键的使用案例如下:

  • 缩短解决问题的时间 -有了良好的监控基础设施,您可以快速发现问题并解决它们,从而减少问题造成的影响。

  • 业务决策 -在一段时间内收集的数据可以帮助您做出业务决策,例如确定产品发布周期、投资哪些功能以及关注哪些地理区域。基于长期数据的决策可以改善整体产品体验。

  • 资源规划 -通过分析历史数据,您可以预测服务计算资源需求,并合理分配资源。这有助于做出经济高效的决策,而不会影响最终用户体验。

在深入探讨监控之前,让我们先了解一些基本术语。

  • 指标——指标是对特定系统属性的定测量量——例如,内存或 CPU

  • 节点或主机 -运行应用的物理服务器、虚拟机或容器

  • QPS - 每秒查询数,这是一个衡量服务每秒处理的流量的指标

  • 延迟——用户操作和服务器响应之间的时间间隔——例如,从向数据库发送查询到收到第一个响应位所花费的时间

  • 错误 速率 -在特定时间段(通常是一秒钟)内观察到的错误数量

  • 图表 -在监控中,图表代表一段时间内收集的一个或多个指标值

  • 仪表板 -仪表板是一组图表的集合,提供系统健康状况的概述

  • 事件 -事件是指扰乱系统正常运行的事件

  • MTTD - 平均检测时间是服务故障开始和检测到该故障之间的时间间隔

  • MTTR -平均解决时间是修复服务故障并使服务恢复正常状态所花费的时间

在讨论监控应用之前,让我们先来看看监控基础设施。下图是一个基本的监控系统。

Illustration of a monitoring infrastructure

图 1:监控基础设施的图示

图 1 显示了一个监控基础设施机制,用于在系统上聚集指标,并收集和存储数据以供显示。此外,监控基础设施包括警报子系统,用于在任何异常行为期间通知相关方。让我们看一下这些基础架构组件:

  • 主机指标代理-主机指标代理是运行在主机上的一个进程,它收集内存、CPU 和网络等主机子系统的性能统计数据。这些指标会定期传递给指标收集器进行存储和可视化。一些例子是 collectdtelegrafmetricbeat

  • 指标聚合器-指标聚合器是运行在主机上的进程。运行在主机上的应用使用工具收集服务指标。收集的指标将通过 API 发送到聚合器流程或直接发送到指标收集器(如果可用)。收到的指标会定期汇总,并成批转发给指标收集器。一个例子是 StatsD

  • 指标收集器-指标收集器进程从运行在多台主机上的指标聚合器收集所有指标。收集器负责解码并将这些数据存储在数据库中。度量收集和存储可能由一个单独的服务负责,比如我们接下来讨论的 InfluxDB 。一个例子是碳守护进程

  • 存储- 时序数据库存储所有这些指标。例子有 OpenTSDBWhisperInfluxDB

  • 指标服务器-指标服务器可以像以图形方式呈现指标数据的 web 服务器一样简单。此外,度量服务器提供了聚合功能和 API,用于以编程方式获取度量数据。一些例子是 Grafana石墨网

  • 警报管理器-警报管理器定期轮询可用的度量数据,如果检测到任何异常,就会通知您。每个警报都有一套识别此类异常的规则。如今,许多度量服务器,如 Grafana 都支持警报管理。我们将在稍后的中详细讨论报警。例如 GrafanaIcinga

命令行工具

原文:https://linkedin.github.io/school-of-sre/level101/metrics_and_monitoring/command-line_tools/

今天的大多数 Linux 发行版都附带了一套工具来监控系统的性能。这些工具帮助您测量和理解各种子系统统计数据(CPU、内存、网络等)。让我们看看一些主要使用的工具。

  • ps/top -进程状态命令(ps)显示 Linux 系统中当前运行的所有进程的信息。top 命令类似于 ps 命令,但它会定期更新显示的信息,直到程序终止。top 的一个高级版本叫做 htop,有一个更加用户友好的界面和一些额外的功能。这些命令行实用程序带有修改命令操作和输出的选项。以下是 ps 命令支持的一些重要选项。

    • -p <pid1, pid2,...> -显示与指定进程 id 匹配的进程的信息。类似地,您可以使用-u <uid>-g <gid>来显示属于特定用户或组的进程的信息。

    • -a -显示其他用户的进程信息,以及自己的进程信息。

    • -x -当显示与其他选项匹配的进程时,包括没有控制终端的进程。

Results of top command

图 2:最高命令的结果

  • ss-socket statistics 命令(ss)显示系统上网络插座的信息。这个工具是 netstat 的继任者,后者已被弃用。以下是 ss 命令支持的一些命令行选项:

    • -t -显示 TCP 套接字。同样,-u显示 UDP 套接字,-x表示 UNIX 域套接字,以此类推。

    • -l -仅显示监听插座。

    • -n -指示命令不解析服务名。而是显示端口号。

List of listening sockets on a system

图 3:系统上的监听套接字列表

  • free-free 命令显示主机上的内存使用统计信息,如可用内存、已用内存和可用内存。最常见的是,这个命令与-h命令行选项一起使用,它以人类可读的格式显示统计信息。

Memory statistics on a host in human-readable form

图 4:主机上可读形式的内存统计数据

  • df --df 命令显示磁盘空间使用统计数据。-i命令行选项也经常用于显示 inode 的使用统计。-h命令行选项用于以人类可读的格式显示统计数据。

Disk usage statistics on a system in human-readable form

图 5:以人类可读的形式显示系统上的磁盘使用统计信息

  • sar-sar 实用程序实时监控各种子系统,如 CPU 和内存。该数据可以存储在由-o选项指定的文件中。这个工具有助于识别异常情况。

  • 【The interface top 命令(iftop)显示接口上主机的带宽利用率。此命令通常用于识别活动连接的带宽使用情况。-i选项指定监视哪个网络接口。

Network bandwidth usage by  active connection on the host

图 6:主机上活动连接的网络带宽使用情况

  • tcpdump-tcpdump 命令是一个网络监控工具,它捕获流经网络的网络数据包,并显示所捕获数据包的描述。以下是可用的选项:

    • -i <interface> -监听界面

    • host <IP/hostname> -过滤进出指定主机的流量

    • src/dst -显示从源(src)到目的地(dst)的单向流量

    • port <port number> -过滤进出特定端口的流量

tcpdump of packets on an interface

图 7:主机上docker0接口上数据包的 tcpdump

第三方监控

原文:https://linkedin.github.io/school-of-sre/level101/metrics_and_monitoring/third-party_monitoring/

如今,大多数云提供商都提供各种监控解决方案。此外,许多公司,如 Datadog 也提供监控即服务。在本节中,我们不会深入讨论监控即服务。

近年来,越来越多的人可以上网。许多服务都在网上提供,以迎合日益增长的用户群。结果,网页变得越来越大,客户端脚本也越来越多。用户希望这些服务快速无误。从服务的角度来看,在编写响应体时,发送一个 HTTP 200 OK 响应,一切看起来都没问题。但是在传输过程中或者在客户端可能会有错误。如前所述,从服务基础设施内部监控服务可以很好地了解服务的健康状况,但这还不够。您需要监控用户体验,特别是客户端服务的可用性。许多第三方服务,如 CatchpointPingdom 等,都可以用来实现这个目标。

第三方监控服务可以生成模拟来自世界各地的用户请求的合成流量,以确保服务可以在全球范围内访问。针对真实用户监控(RUM)的其他第三方监控解决方案提供不同地理位置的性能统计数据,如服务正常运行时间和响应时间。这允许您从这些位置监视用户体验,这些位置可能具有不同的互联网主干、不同的操作系统以及不同的浏览器和浏览器版本。 Catchpoint 全球监控网络是一个 3 分钟的综合视频,解释了监控客户体验的重要性。

使用警报的主动监控

原文:https://linkedin.github.io/school-of-sre/level101/metrics_and_monitoring/alerts/

前面我们讨论了从服务及其底层基础设施收集关键指标数据点的不同方法。这些数据让我们更好地了解服务的执行情况。监控的主要目标之一是尽早发现任何服务降级(减少平均检测时间)并通知利益相关方,以便避免或尽早解决问题,从而减少平均恢复时间(MTTR)。例如,如果服务的资源使用率超过 90%时通知您,您可以采取预防措施来避免由于资源短缺而导致的任何服务中断。另一方面,当服务由于某个问题而关闭时,及早发现和通知此类事件可以帮助您快速修复问题。

An alert notification received on Slack

图 8:在松弛时间收到的警告通知

如今,大多数可用的监控服务都提供了一种机制,可以根据一个或多个指标设置警报,从而主动监控服务的健康状况。这些警报有一组已定义的规则或条件,当违反规则时,您会收到通知。这些规则可以简单到在指标值超过 n 时发出通知,也可以复杂到对一段时间内的标准偏差进行周与周(WoW)比较。监控工具会向您通知活动警报,其中大多数工具都支持即时消息(IM)平台、SMS、电子邮件或电话。图 8 显示了一个样例警报通知,当内存使用量超过主机上总 RAM 空间的 90%时,会在 Slack 上收到该通知。

监控的最佳实践

原文:https://linkedin.github.io/school-of-sre/level101/metrics_and_monitoring/best_practices/

为服务设置监控时,请记住以下最佳实践。

  • 使用正确的度量类型——目前大多数可用的库都提供了各种度量类型。选择适当的度量类型来监视您的系统。以下是指标的类型及其用途。

    • 量规- 量规是一种常量类型的公制。初始化度量后,度量值不会改变,除非您有意更新它。

    • 计时器- 计时器测量完成一项任务所需的时间。

    • 计数器- 计数器统计特定事件发生的次数。

有关这些度量类型的更多信息,请参见数据类型

  • 避免过度监控 -监控可能是一项重大的工程任务 因此,一定不要在监控服务上花费太多的时间和资源,还要确保所有重要的指标都被捕获。

  • 防止警报疲劳 -为重要且可行的指标设置警报。如果您收到太多非关键警报,随着时间的推移,您可能会开始忽略警报通知。因此,关键警报可能会被忽略。

  • 为警报准备一本操作手册 -对于每一个警报,确保你有一份文件解释当警报触发时需要执行的操作和检查。这使得团队中的任何工程师都能够处理警报并采取必要的措施,而无需他人的任何帮助。

可观测性

原文:https://linkedin.github.io/school-of-sre/level101/metrics_and_monitoring/observability/

当提到构建可靠的系统时,工程师经常使用可观测性。可观测性是一个源自控制理论的术语,它是一个衡量系统的内部状态可以从其外部输出的知识中推断出来的程度的指标。日常使用的服务基础设施变得越来越复杂;仅靠主动监控不足以快速解决导致应用故障的问题。通过监控,您可以防止已知的过去的故障再次发生,但是对于复杂的服务架构,许多未知的因素会导致潜在的问题。为了解决这种情况,您可以使服务变得可观测。一个可观测的系统提供了对隐含故障模式的高度精细的洞察。此外,一个可观测的系统提供了关于其内部工作的丰富背景,这开启了揭示更深层次的系统问题的能力。

监控使故障检测成为可能;可观测性有助于更好地理解系统。在工程师中,有一种普遍的误解,认为监控和可观测性是两回事。实际上,可观测性是监控的超集;也就是说,监控提高了服务的可观测性。可观测性的目标不仅是检测问题,而且是理解问题在哪里,是什么导致了问题。除了度量之外,可观测性还有两个支柱:日志和跟踪,如图 9 所示。尽管这三个组件并不能使系统 100%可观测,但它们是最重要和最强大的组件,可以让我们更好地理解系统。这些支柱中的每一个都有其缺陷,这些缺陷在零答案的三个支柱中有所描述。

Three pillars of observability

图 9:可观测性的三个支柱

因为我们已经讨论了指标,所以让我们看看另外两个支柱(日志和跟踪)。

日志

日志(通常称为事件)是服务在运行时执行的活动的记录,带有相应的时间戳。度量给出了关于系统降级的抽象信息,日志给出了导致这些降级的原因的详细视图。应用和基础架构组件创建的日志通过提供有关应用错误、异常和事件时间表的详细信息,有助于有效了解系统的行为。日志帮助您及时了解导致失败的事件。因此,检查日志对于排除系统故障至关重要。

日志处理包括来自单个应用的不同日志的聚合,以及随后将它们发送到中央存储。将日志移动到中央存储有助于保存日志,以防应用实例不可访问或者应用由于故障而崩溃。在中央位置提供日志后,您可以分析日志以从中获取有用的信息。出于审计和合规性目的,您可以将这些日志在中央存储上存档一段时间。日志分析器从日志行中获取有用的信息,比如请求用户信息、请求 URL(特性)、响应头(比如内容长度)和响应时间。这些信息基于这些属性进行分组,并通过可视化工具提供给您,以便快速理解。

您可能想知道这个日志信息有什么帮助。该信息给出了在所有涉及的实体上执行的活动的整体视图。例如,假设有人正在对 web 应用进行 DoS(拒绝服务)攻击。在日志处理的帮助下,您可以快速查看来自访问日志的顶级客户端 IP,并确定攻击来自何处。

同样,如果应用中的某个特性在使用特定的请求参数值访问时导致高错误率,日志分析的结果可以帮助您快速识别行为不当的参数值,并采取进一步的措施。

Log processing and analysis using ELK stack

图 10:使用 ELK 堆栈的日志处理和分析

图 10 展示了一个使用 ELK (Elasticsearch,Logstash,Kibana)的日志处理平台,它提供了集中式的日志处理。Beats 是一个轻量级数据发送器的集合,可以通过网络发送日志、审计数据、网络数据等等。在这个用例中,我们使用 filebeat 作为日志发送器。Filebeat 监视服务日志文件,并将日志数据发送到 Logstash。Logstash 解析这些日志并转换数据,准备存储在 Elasticsearch 上。转换后的日志数据存储在 Elasticsearch 上,并编制索引以便快速检索。Kibana 搜索并显示存储在 Elasticsearch 上的日志数据。Kibana 还提供了一组可视化工具,用于图形化显示从日志数据中获得的摘要。

存储日志非常昂贵。并且服务器上每个事件的大量日志记录是昂贵的,并且占用更多的存储空间。随着服务数量的增加,这一成本会与服务数量成比例增加。

描摹

到目前为止,我们已经讨论了度量和日志记录的重要性。度量给出了系统的抽象概述,日志给出了所发生事件的记录。想象一个有多个微服务的复杂分布式系统,其中一个用户请求由系统中的多个微服务处理。度量和日志记录为您提供了一些关于系统如何处理这些请求的信息,但它们无法提供所有微服务的详细信息以及它们如何影响特定的客户端请求。如果缓慢的下游微服务导致响应时间增加,您需要详细了解所有涉及的微服务,以识别此类微服务。这种需求的答案是请求跟踪机制。

跟踪是一系列跨度,其中每个跨度是由不同微服务执行以服务于客户端请求的事件的记录。简而言之,跟踪是来自不同物理机器上的各种微服务的客户端请求服务的日志。每个 span 包括 span 元数据,如跟踪 ID 和 span ID,以及上下文,其中包括有关执行的事务的信息。

Trace and spans for a URL shortener request

图 11:URL 缩写请求的跟踪和跨度

图 11 是我们之前在学习 Python 时提到的URL shorter例子中捕获的轨迹的图形表示。

与监视类似,跟踪基础结构包含一些用于收集、存储和访问跟踪的模块。每个微服务运行一个跟踪库,该库在后台收集跟踪,创建内存中的批处理,并提交跟踪后端。跟踪后端对接收到的跟踪数据进行规范化,并将其存储在持久性存储上。追踪数据来自多个不同的微服务;因此,跟踪存储通常被组织为增量存储数据,并通过跟踪标识符进行索引。这种组织有助于跟踪数据的重建和可视化。图 12 展示了分布式系统的结构。

Anatomy of distributed tracing

图 12:分布式跟踪的剖析

今天,有一组工具和框架可用于构建分布式跟踪解决方案。以下是一些流行的工具:

  • OpenTelemetry :云原生软件的可观测性框架

  • Jaeger :开源分布式追踪解决方案

  • Zipkin :开源分布式追踪解决方案

总结

原文:https://linkedin.github.io/school-of-sre/level101/metrics_and_monitoring/conclusion/

强大的监控和警报系统对于系统维护和故障排除是必要的。带有关键指标的仪表板可以让您在一个地方了解服务性能。定义良好的警报(具有现实的阈值和通知)进一步使您能够快速识别服务基础架构和资源饱和中的任何异常。通过采取必要的措施,您可以避免任何服务降级并减少服务故障的 MTTD。

除了内部监控之外,监控真实的用户体验可以帮助您了解用户所感知的服务性能。很多模块都涉及到为用户服务,而且大部分都不在你的控制范围内。因此,您需要对真实用户进行监控。

度量给出了关于服务性能的非常抽象的细节。为了更好地理解系统并在事故期间更快地恢复,您可能希望实现可观测性的另外两个支柱:日志和跟踪。日志和跟踪数据可以帮助您了解导致服务失败或降级的原因。

以下是一些资源,可帮助您了解有关监控和可观测性的更多信息:

参考

安全性

安全性

原文:https://linkedin.github.io/school-of-sre/level101/security/intro/

先决条件

  1. Linux 基础知识

  2. Linux 联网

从本课程中可以期待什么

本课程涵盖了信息安全的基础知识,并触及了系统安全、网络和 web 安全等主题。本课程旨在让您熟悉日常运营中的信息安全基础知识&然后作为一名 SRE,培养在开发解决方案时确保安全的心态。本课程还介绍了常见风险和最佳实践,以及找出易受攻击的系统和漏洞的实用方法,如果不加以保护,这些系统和漏洞可能会受到危害。

本课程不包括哪些内容

该课件不是一个道德黑客研讨会,也不是对问题的基础的深入探究。本课程不涉及黑客攻击或闯入系统,而是一种如何确保您不会陷入这种情况的方法,并让您了解系统可能被入侵的不同方式。

课程内容

  1. 基础知识
  2. 网络安全
  3. 威胁,攻击&防御
  4. 编写安全代码&更多
  5. 结论

第一部分:基础知识

原文:https://linkedin.github.io/school-of-sre/level101/security/fundamentals/

SRE 安全概述介绍

  • 如果你仔细观察,站点可靠性工程和安全工程都与保持系统可用有关。
  • 像不完整的发布、容量短缺和错误配置这样的问题会使系统不可用(至少暂时不可用)。
  • 破坏用户信任的安全或隐私事件也会破坏系统的有用性。
  • 因此,系统安全性应该是 SREs 最关心的问题。

The Wide Area of security

  • SREs 应该参与重要的设计讨论和实际的系统变更。

  • 它们在系统设计中有相当大的作用&因此有时是第一道防线。

  • SRE 帮助防止会影响基础设施整体安全性的不良设计和实施。

  • 成功地设计、实现和维护系统需要对整个系统生命周期的承诺。只有当安全性和可靠性成为系统架构的核心要素时,这种承诺才有可能实现。

  • 信息安全的核心支柱:

  • 保密性–只允许用户访问被允许访问的数据

  • 完整性–确保数据不被未授权用户篡改或更改

  • 可用性–确保授权用户在需要时可以使用系统和数据

  • 像安全工程师一样思考

  • 启动新的应用或重构现有应用时,您应该考虑每个功能特性,并考虑:

    • 围绕此功能的过程是否尽可能安全?换句话说,这是一个有缺陷的过程吗?
    • 如果我是邪恶的,我会如何滥用这个特性?或者更具体地说,未能解决一个特性如何被滥用会导致设计缺陷。
    • 该功能是否需要默认开启?如果是,是否有限制或选项可以帮助降低此功能带来的风险?
  • OWASP(开放网络应用安全项目)的安全原则

  • 最小化攻击表面积:

    • 添加到应用中的每个功能都会给整个应用增加一定的风险。安全开发的目的是通过减少攻击面来降低整体风险。
    • 例如,web 应用通过搜索功能实现在线帮助。搜索功能可能容易受到 SQL 注入攻击。如果帮助功能仅限于授权用户,攻击的可能性就会降低。如果帮助功能的搜索功能是通过集中式数据验证例程实现的,那么执行 SQL 注入的能力就会大大降低。然而,如果帮助功能被重写以消除搜索功能(例如,通过更好的用户界面),这几乎消除了攻击表面区域,即使帮助功能在互联网上普遍可用。
  • 建立安全默认值:

    • 有许多方法可以为用户提供“开箱即用”的体验。然而,默认情况下,体验应该是安全的,如果允许的话,应该由用户决定降低他们的安全性。
    • 例如,默认情况下,应该启用密码老化和复杂性。用户可能被允许关闭这两个功能,以简化他们对应用的使用,并增加他们的风险。
    • 路由器、物联网设备的默认密码应该更改
  • 最小特权原则

    • 最小特权原则建议帐户拥有执行其业务流程所需的最小特权。这包括用户权限、资源权限(如 CPU 限制)、内存、网络和文件系统权限。
    • 例如,如果一个中间件服务器只需要访问网络、读取数据库表以及写入日志的能力,那么这就描述了应该授予的所有权限。在任何情况下都不应该授予中间件管理特权。
  • 纵深防御原则

    • 深度防御原则表明,在一种控制措施合理的情况下,以不同方式应对风险的更多控制措施会更好。当深入使用控制时,可以使严重的漏洞变得非常难以利用,因此不太可能发生。
    • 对于安全编码,这可能采取基于层的验证、集中审计控制以及要求用户登录所有页面的形式。
    • 例如,如果一个有缺陷的管理界面能够正确地控制对生产管理网络的访问、检查管理用户授权并记录所有访问,那么它就不太可能容易受到匿名攻击。
  • 安全失败

    • 由于多种原因,应用经常无法处理事务。它们如何失败可以决定应用是否安全。
    
    is _ admin = true 请尝试{ code _ which _ may _ faile();is _ admin = is _ user _ assigned _ role(" Adminstrator ");} catch(Exception err){ log . error(err . tostring());}
    
    ` ` `-如果 codeWhichMayFail()或 isUserInRole 失败或抛出异常,默认情况下,用户是管理员。这显然是一个安全隐患。
    
    
  • 不要相信服务

    • 许多组织利用第三方合作伙伴的处理能力,这些合作伙伴很可能具有与您不同的安全策略和状态。你不太可能影响或控制任何外部第三方,无论他们是家庭用户还是主要供应商或合作伙伴。
    • 因此,对外部运行系统的隐式信任是没有保证的。所有外部系统都应该被类似地对待。
    • 例如,忠诚计划提供商提供网上银行使用的数据,提供奖励点数和潜在兑换项目的小列表。但是,应该检查数据,以确保向最终用户显示是安全的,并且奖励积分是正数,而不是不可能的大。
  • 职责分离

    • 欺诈控制的关键是职责分离。例如,请求计算机的人不能签收,也不应该直接收到计算机。这防止了用户请求许多计算机并声称它们从未到达。
    • 某些角色具有不同于普通用户的信任级别。特别是管理员不同于普通用户。通常,管理员不应该是应用的用户。
    • 例如,管理员应该能够打开或关闭系统,设置密码策略,但不应该能够作为超级特权用户登录店面,例如能够代表其他用户“购买”商品。
  • 通过模糊来避免安全

    • 通过模糊实现的安全性是一种弱的安全控制,当它是唯一的控制时,几乎总是失败。这并不是说保守秘密是个坏主意,它只是意味着系统的安全性不应该依赖于隐藏细节。
    • 例如,应用的安全性不应该依赖于对保密源代码的了解。安全性应依赖于许多其他因素,包括合理的密码策略、纵深防御、业务交易限制、稳固的网络架构、欺诈和审计控制。
    • 一个实际的例子是 Linux。Linux 的源代码随处可得,但是如果保护得当,Linux 是一个安全且健壮的操作系统。
  • 保持简单的安全性

    • 攻击表面积和简单性是相辅相成的。某些软件工程实践更喜欢过于复杂的方法,而不是相对简单明了的设计。
    • 当更简单的方法会更快更简单时,开发人员应该避免使用双重否定和复杂的架构。
    • 例如,尽管在一个单独的中间件服务器上运行大量的单例实体 beans 可能很流行,但是简单地使用带有适当互斥机制的全局变量来防止竞争情况会更安全、更快。
  • 正确修复安全问题

    • 一旦确定了安全问题,就必须对其进行测试,并了解问题的根本原因。当使用设计模式时,安全问题可能在所有代码库中普遍存在,因此开发正确的修复方法而不引入回归是至关重要的。
    • 例如,一个用户发现他们可以通过调整他们的 cookie 来查看另一个用户的余额。修复看起来相对简单,但是由于 cookie 处理代码是在所有应用之间共享的,所以对一个应用的更改会渗透到所有其他应用。因此,必须在所有受影响的应用上测试该修复程序。
  • 可靠性和安全性

    • 可靠性和安全性都是真正可信的系统的重要组成部分,但是构建既可靠又安全的系统是很困难的。虽然对可靠性和安全性的要求有许多共同的属性,但它们也需要不同的设计考虑。人们很容易忽略可靠性和安全性之间微妙的相互作用,这可能会导致意想不到的结果
    • 例如:密码管理应用故障是由可靠性问题触发的,即负载平衡和减载策略不佳,其恢复后来因多种措施而变得复杂(HSM 机制需要插入服务器机架,作为一种身份验证& HSM 令牌应该锁在箱子里..&问题可以进一步拉长)设计来增加系统的安全性。

身份验证与授权

  • 认证是验证用户就是他们所声称的那个人的行为。密码是最常见的身份验证因素,如果用户输入正确的密码,系统会认为身份有效并授予访问权限。
  • 其他技术,如一次性 pin 码、认证应用,甚至生物识别技术也可以用于身份认证。在某些情况下,系统要求在授予访问权限之前成功验证多个因素。这种多因素身份验证(MFA)要求通常用于提高安全性,而不仅仅是密码。
  • 系统安全中的授权是给予用户访问特定资源或功能的许可的过程。该术语通常与访问控制或客户端权限互换使用。授予某人在服务器上下载特定文件的权限,或者为单个用户提供对应用的管理访问权限都是很好的例子。在安全环境中,授权必须总是在身份验证之后,用户应该首先证明他们的身份是真实的,然后组织的管理员才能授予他们访问所请求的资源的权限。

通用认证流程(本地认证)

  • 用户使用用户名/电子邮件/手机等标识符进行注册
  • 应用将用户凭据存储在数据库中
  • 应用发送验证电子邮件/消息来验证注册
  • 注册成功后,用户输入登录凭证
  • 身份验证成功后,用户就可以访问特定的资源

OpenID/OAuth

OpenID 是一种认证协议,允许我们在不使用本地认证系统的情况下认证用户。在这种情况下,用户必须向 OpenID 提供者注册,并且同一个提供者应该与您的应用的身份验证流集成在一起。为了验证细节,我们必须将身份验证请求转发给提供者。身份验证成功后,我们会收到一条成功消息和/或配置文件详细信息,我们可以使用这些信息执行必要的流程。

OAuth 是一种授权机制,允许你的应用用户访问某个提供商(Gmail/脸书/Instagram/etc)。成功响应后,我们(您的应用)会收到一个令牌,应用可以用它来代表用户访问某些 API。如果您的业务用例需要某些面向用户的 API,比如访问 Google Drive 或代表您发送 tweets,那么 OAuth 非常方便。大多数 OAuth 2.0 提供者可以用于伪认证。话虽如此,如果您在本地身份验证系统之上使用多个 OAuth 提供者来验证用户,事情会变得相当复杂。


密码系统

  • 它是一门隐藏任何文本的科学和研究,只有预定的接收者或授权的人才能阅读它,任何文本甚至可以使用诸如隐形墨水或过去的机械密码术机器之类的东西。

  • 密码术对于保护关键或专有信息是必要的,并且用于通过将一些明文转换成密文来对私人数据消息进行编码。其核心是,有两种方法可以做到这一点,更先进的方法都是建立在。

密码

  • 密码是密码学的基石。密码是对消息执行加密或解密的一组算法。加密算法(E)采用密钥(k)和消息(m)并产生密文(c)。类似地,解密算法(D)采用秘密密钥(K)和先前产生的密文(C)。它们表示如下:
 E(k,m) = c
D(k,c) = m 
```sh

*   这也意味着要成为一个密码,它必须满足如下的一致性等式,这样才有可能解密。

D(k,E(k,m)) = m


流密码:

*   消息被分成字符或比特,并用与明文比特流一样长的密钥或密钥流(应该是随机的并且独立于消息流生成)进行加密。
*   如果密钥流是随机的,这个方案将是不可破解的,除非密钥流被获取,使得它是无条件安全的。密钥流必须以安全的方式提供给双方以防止其泄露。

分组密码:

*   块密码—处理块中的消息,然后对每个块进行加密或解密。
*   分组密码是一种对称密码,其中明文块被视为一个整体,并用于生成密文块。块密码采用 b 位长的块,并将它们加密成 b 位长的块。块大小通常为 64 或 128 位长。

    ![image5](https://github.com/OpenDocCN/geekdoc-linux-zh/raw/master/lkin-sre/img/23e8c25bd836f461b7ef4d0866adc499.png) ![image6](https://github.com/OpenDocCN/geekdoc-linux-zh/raw/master/lkin-sre/img/69e020203dce92b771f8f3370c6302ce.png)

加密

*   **密钥(对称密钥)**:同一密钥用于加密和解密
*   **公钥(非对称密钥)**在非对称中,加密密钥和解密密钥不同但相关。加密密钥称为公钥,解密密钥称为私钥。公钥和私钥被称为密钥对。

对称密钥加密

是吗

*   数据加密标准(DES)长期以来一直是世界范围的加密标准。IBM 在 1975 年开发了 DES,它在多年的密码分析中表现出色。DES 是一种对称加密算法,密钥长度固定为 56 位。算法还是不错的,但是因为密钥长度短,容易受到资源充足的蛮力攻击。

*   DES 通常以块模式工作,以 64 位块加密数据。加密和解密使用相同的算法和密钥。

*   因为 DES 是基于简单的数学函数,所以很容易在硬件中实现和加速。

三重 DES

*   随着计算机处理能力的提高,最初的 56 位 DES 密钥变得太短,以至于无法抵御预算有限的攻击者。增加 DES 的有效密钥长度而不改变经过充分分析的算法本身的一种方法是连续几次使用不同密钥的相同算法。

*   将 DES 连续三次应用到纯文本块的技术称为三重 DES (3DES)。3DES 技术如图所示。今天,对 3DES 的暴力攻击被认为是不可行的。因为基本算法已经在现场测试了超过 25 年,所以被认为比它的前辈更值得信赖。![image7](https://github.com/OpenDocCN/geekdoc-linux-zh/raw/master/lkin-sre/img/961efaf5e4ad56f7010144ecbe654492.png)

俄歇电子能谱

*   2000 年 10 月 2 日,美国国家标准和技术研究所(NIST)宣布选择 Rijndael 密码作为 AES 算法。琼·代蒙和文森特·里门开发的这种密码具有可变的分组长度和密钥长度。该算法目前规定了如何使用长度为 128、192 或 256 位的密钥来加密长度为 128、192 或 256 位的块(密钥长度和块长度的所有九种组合都是可能的)。块和密钥长度都可以轻松扩展到 32 位的倍数。

*   之所以选择 AES 来取代 DES 和 3DES,是因为它们要么太弱(DES,就密钥长度而言),要么太慢(3DES),无法在现代高效的硬件上运行。在相同的硬件上,AES 的效率更高,速度更快,通常是 DES 的 5 倍。AES 也更适合高吞吐量,尤其是在使用纯软件加密的情况下。然而,AES 是一个相对年轻的算法,正如密码学的黄金法则所说,“一个更成熟的算法总是更受信任。”

非对称密钥算法

![image8](https://github.com/OpenDocCN/geekdoc-linux-zh/raw/master/lkin-sre/img/acb447b08812b9977c969b3c4afa632e.png)

*   在对称密钥系统中,Alice 首先将秘密消息放入一个盒子中,然后使用她有钥匙的锁将盒子挂锁。然后她通过普通邮件把盒子寄给鲍勃。当 Bob 收到盒子时,他使用 Alice 的密钥(他先前已经获得)的相同副本来打开盒子并阅读消息。

*   在非对称密钥系统中,Bob 不是在收到盒子时打开它,而是简单地将他自己的个人锁添加到盒子中,并通过公共邮件将盒子返回给 Alice。爱丽丝用她的钥匙打开锁,把盒子还给鲍勃,鲍勃的锁还在。最后,鲍勃用他的钥匙打开了锁,并阅读了爱丽丝发来的信息。

*   非对称系统的关键优势在于 Alice 从不需要向 Bob 发送其密钥的副本。这降低了第三方(例如,不道德的邮局主管)在密钥传输给 Bob 的过程中复制密钥的可能性,从而允许第三方监视 Alice 将来发送的所有消息。此外,如果 Bob 粗心大意,允许其他人复制他的密钥,Alice 给 Bob 的消息就会泄露,但是 Alice 给其他人的消息仍然是保密的

**注**:在 TLS 密钥交换方面,这是常见的做法。

迪菲-赫尔曼

*   该协议有两个系统参数 p 和 g,它们都是公开的,可以被任何人使用。参数 p 是一个素数,参数 g(通常称为生成元)是一个小于 p 的整数,但具有以下性质:对于 1 到 p–1 之间的每个数 n,都有一个 g 的幂 k,使得 n = gk mod p。
*   Diffie Hellman 算法是一种非对称算法,用于为对称密钥算法建立共享机密。现在大多数人使用混合密码系统,即对称和非对称加密的组合。非对称加密被用作密钥交换机制中的一种技术来共享秘密密钥,并且在发送方和接收方之间共享密钥之后,通信将使用对称加密来进行。共享密钥将用于加密通信。
*   参考:[`medium . com/@ akhigbemanuel/what-is-the-diffie-hellman-key-exchange-algorithm-84d 60025 a30d`](https://medium.com/@akhigbemmanuel/what-is-the-diffie-hellman-key-exchange-algorithm-84d60025a30d)

南非共和国(Republic of South Africa)

*   RSA 算法非常灵活,具有可变的密钥长度,如果需要,可以用速度换取算法的安全级别。RSA 密钥通常是 512 到 2048 位长。RSA 经受住了多年的广泛密码分析。虽然那些年既没有证明也没有否定 RSA 的安全性,但它们证明了该算法的可信度。RSA 安全性基于分解非常大的数字的困难性。如果发现了分解这些大数的简单方法,RSA 的有效性将被破坏。
*   参考:[`medium . com/curiositypapers/a-complete-explain-of-RSA-asymmetric-encryption-742 c 5971 e0f`](https://medium.com/curiositypapers/a-complete-explanation-of-rsa-asymmetric-encryption-742c5971e0f)

    **注意** : RSA 密钥可以像 Diffie Hellman 一样用于密钥交换

哈希算法

*   哈希是用于保证数据完整性的机制之一。哈希基于单向数学函数,相对容易计算,但很难逆转。

*   哈希函数,是一种单向函数,用于输入数据以生成输出数据的固定长度摘要(指纹)。摘要在加密上是强的;也就是说,不可能从摘要中恢复输入数据。如果输入数据变化很小,摘要(指纹)就会发生很大的变化,这就是所谓的雪崩效应。

*   更多:

*   [`medium . com/@ Raul Jordan/the-state-of-hashing-algorithms-the-why-the-how-and-the-future-b21 D5 c 0440 de`](https://medium.com/@rauljordan/the-state-of-hashing-algorithms-the-why-the-how-and-the-future-b21d5c0440de)
*   [`medium . com/@ Stevie cellis/the-beautiful-hash-algorithm-f18d 9 D2 b 84 FB`](https://medium.com/@StevieCEllis/the-beautiful-hash-algorithm-f18d9d2b84fb)

讯息摘要 5

*   MD5 是一种单向函数,利用它可以很容易地从给定的输入数据中计算出散列值,但是如果只给定一个散列值,则计算输入数据是不可行的。

SHA-1

*   MD5 被认为不如 SHA-1 安全,因为 MD5 有一些弱点。
*   HA-1 还使用更强的 160 位摘要,这使得 MD5 成为散列方法的第二选择。
*   该算法接收长度小于 264 位的消息,并生成 160 位的消息摘要。该算法比 MD5 稍慢。

**注意** : SHA-1 最近也被证明是被打破的,目前的最低建议是 SHA-256

数字证书

*   数字签名提供了一种对设备和个人用户进行数字身份验证的方法。在诸如 RSA 加密系统之类的公钥加密系统中,每个用户都有一个包含公钥和私钥的密钥对。密钥作为补充,用其中一个密钥加密的任何东西都可以用另一个密钥解密。简单地说,当数据用用户的私钥加密时,就形成了签名。接收者通过用发送者的公钥解密消息来验证签名。

*   密钥管理通常被认为是设计和实现加密系统中最困难的任务。企业可以通过使用公钥基础设施(PKI)来简化安全数据通信中遇到的一些部署和管理问题。因为公司经常通过 Internet 移动安全敏感的通信,所以必须实施有效的机制来保护敏感信息免受 Internet 上的威胁。

*   PKI 为管理数字安全属性提供了一个分层框架。每个 PKI 参与者都持有由 CA(公共或私人)颁发的数字证书。证书包含几个在各方协商安全连接时使用的属性。这些属性必须包括证书有效期、终端主机身份信息、将用于安全通信的加密密钥以及颁发 CA 的签名。根据 PKI 的要求和能力,可以包括可选的属性。

*   CA 可以是受信任的第三方,如 VeriSign 或 Entrust,也可以是您在组织内建立的私有(内部)CA。
*   可以使用发送者的公钥解密消息的事实意味着私钥的持有者创建了消息。这个过程依赖于接收方拥有发送方公钥的副本,并且非常确定地知道它确实属于发送方,而不是某个冒充发送方的人。
*   为了验证 CA 的签名,接收者必须知道 CA 的公钥。通常,这是在带外处理的,或者通过在证书安装期间执行的操作来处理。例如,默认情况下,大多数 web 浏览器都配置了几个 ca 的根证书。

CA 注册过程

1.  终端主机生成一个私钥-公钥对。
2.  终端主机生成一个证书请求,并将其转发给 CA。
3.  需要人工干预来批准注册请求,注册请求由 CA 接收。
4.  CA 操作员批准请求后,CA 用其私钥签署证书请求,并将完成的证书返回给终端主机。
5.  终端主机将证书写入非易失性存储区域(PC 硬盘或 Cisco 路由器上的 NVRAM)。

**参见**:[`www . ssh . com/manuals/server-zos-product/55/ch 06s 03s 01 . html`](https://www.ssh.com/manuals/server-zos-product/55/ch06s03s01.html)

## 登录安全性

### 嘘

*   安全 Shell SSH 是一种流行的、强大的、基于软件的网络安全方法。
*   每当计算机向网络发送数据时,SSH 会自动加密(加密)数据。然后,当数据到达预期的接收者时,SSH 会自动解密(解码)它。
*   结果是透明加密:用户可以正常工作,不知道他们的通信在网络上被安全加密。此外,SSH 可以使用基于其配置方式的现代安全加密算法,并且在大公司的任务关键型应用中非常有效。
*   SSH 有一个客户机/服务器架构
*   SSH 服务器程序通常由系统管理员安装和运行,接受或拒绝到其主机的传入连接。然后,用户通常在其他计算机上运行 SSH 客户端程序,向 SSH 服务器发出请求,例如“请让我登录”、“请给我发送一个文件”或“请执行这个命令”客户端和服务器之间的所有通信都经过安全加密,不会被修改。

![image9](https://github.com/OpenDocCN/geekdoc-linux-zh/raw/master/lkin-sre/img/b576dbf01e7ba9e6468e12d90c0d8e3e.png)

SSH 不是什么:

*   尽管 SSH 代表安全 shell,但它并不是 Unix Bourne shell 和 C shell 意义上的真正 Shell。它不是命令解释器,也不提供通配符扩展、命令历史等等。相反,SSH 创建了一个在远程计算机上运行 shell 的通道,在两个系统之间进行端到端的加密。

SSH 协议的主要特性和保证是:

*   通过高度加密保护您的数据隐私
*   通信的完整性,保证它们没有被改变
*   认证,即发送者和接收者的身份证明
*   授权,即账户的访问控制
*   转发或隧道来加密其他基于 TCP/IP 的会话

### 麻省理工学院开发的安全认证系统

*   根据希腊神话,Kerberos (Cerberus)是一种巨大的三头狗,它守卫着冥界的大门,防止死者离开。
*   因此,当谈到计算机科学时,Kerberos 是一种网络身份验证协议,并且是当前 Microsoft Active Directory 使用的默认身份验证技术,用于在局域网内对用户的服务进行身份验证。

*   Kerberos 使用对称密钥加密,并需要可信的第三方身份验证服务来验证用户身份。因此,他们将 Kerberos 的名称用于他们的计算机网络身份验证协议,因为 Kerberos 的三个头代表:

*   客户端:用户/服务
*   服务器:受 Kerberos 保护的主机驻留在

    ![image10](https://github.com/OpenDocCN/geekdoc-linux-zh/raw/master/lkin-sre/img/0d42df3062b18b5060ddd91aadf61450.png) -密钥分发中心(KDC),充当可信的第三方认证服务。

KDC 包括以下两台服务器:

*   身份验证服务器(AS ),执行初始身份验证并为用户颁发票证授予票证(TGT)。
*   票证授予服务器(TGS),它根据初始票证授予票证(TGT)颁发服务票证。

    ![image11](https://github.com/OpenDocCN/geekdoc-linux-zh/raw/master/lkin-sre/img/2bca4c0d83649a22381b19a2dadd7d4b.png)

### 证书链

OpenSSL 命令输出的第一部分显示了编号为 0、1 和 2(不再是 2)的三个证书。每个证书都有一个主题 s 和一个颁发者 I。第一个证书编号为 0,称为终端实体证书。主题行告诉我们它对 google.com 的任何子域都有效,因为它的主题被设置为*.google.com

`$ openssl s_client -connect www.google.com:443 -CApath /etc/ssl/certs CONNECTED(00000005) depth=2 OU = GlobalSign Root CA - R2, O = GlobalSign, CN = GlobalSign verify return:1 depth=1 C = US, O = Google Trust Services, CN = GTS CA 1O1 verify return:1 depth=0 C = US, ST = California, L = Mountain View, O = Google LLC, CN = www.google.com verify return:1``--- Certificate chain 0 s:/C=US/ST=California/L=Mountain View/O=Google LLC/CN=www.google.com i:/C=US/O=Google Trust Services/CN=GTS CA 1O1 1 s:/C=US/O=Google Trust Services/CN=GTS CA 1O1 i:/OU=GlobalSign Root CA - R2/O=GlobalSign/CN=GlobalSign ---`T2】

*   issuer 行表明它是由 Google Internet Authority G2 发布的,这也是第二个证书(编号 1)的主题
*   OpenSSL 命令行在这里没有显示的是信任存储,它包含运行 OpenSSL 的系统所信任的 CA 证书列表。
*   GlobalSign Authority 的公共证书必须存在于系统的信任存储中,才能关闭验证链。这被称为信任链,下图概括了它的高层次行为。

    ![image122](https://github.com/OpenDocCN/geekdoc-linux-zh/raw/master/lkin-sre/img/fd748c4caa2b918bba4a4e569aa03ec5.png)

*   应用于验证网站真实性的信任链概念的高级视图。Firefox 信任存储中的根 CA 提供初始信任来验证整个链并信任终端实体证书。

### TLS 握手

1.  客户端向服务器发送一条 HELLO 消息,其中包含它支持的协议和算法列表。
2.  服务器回应你好并发送它的证书链。基于客户端的能力,服务器选择一个密码套件。
3.  如果密码套件支持临时密钥交换,如 ECDHE(ECDHE 是一种被称为椭圆曲线 Diffie-Hellman 交换的算法),服务器和客户端将使用 Diffie-Hellman 算法协商预主密钥。预主密钥从不通过网络发送。
4.  客户端和服务器创建一个会话密钥,用于加密通过连接传输的数据。

握手结束时,双方都拥有一个秘密的会话密钥,用于为连接的其余部分加密数据。这就是 OpenSSL 所说的万能钥匙

**注**

*   TLS 有 3 个版本,TLS 1.0、1.1 和 1.2
*   TLS 1.0 发布于 1999 年,是一个有近 20 年历史的协议。众所周知,它容易受到攻击,如 BEAST 和 POODLE,多年来,除了支持薄弱的加密技术,不能保持现代连接足够安全。
*   TLS 1.1 是被遗忘的“老二”。和它的弟弟一样,它也有糟糕的加密技术。在大多数软件中,它被 TLS 1.2 所超越,很少看到 TLS 1.1 被使用。

### “完美”前向保密

*   密钥交换中的术语“短暂的”提供了一个重要的安全特征,它被错误地命名为完美前向保密(PFS)或简称为“前向保密”。
*   在非临时密钥交换中,客户端通过使用服务器的公钥对预主密钥进行加密,将其发送给服务器。然后,服务器用其私钥解密预主密钥。如果在稍后的某个时间点,服务器的私钥被泄露,攻击者可以回到这个握手,解密预主密钥,获得会话密钥,并解密整个流量。非短暂的密钥交换容易受到将来可能发生在记录的流量上的攻击。因为人们很少改变他们的密码,解密过去的数据对攻击者来说仍然是有价值的。
*   像 DHE 或它在椭圆曲线上的变体 ECDHE 这样的短暂密钥交换通过不在网络上传输预主密钥来解决这个问题。相反,预主密钥是由客户端和服务器使用公开交换的非敏感信息单独计算的。因为预主密钥以后不能被攻击者解密,所以会话密钥不会受到将来的攻击:因此,术语“完美前向保密”是安全的。
*   沿着流每隔 X 个块改变一次密钥。这就防止了攻击者简单地嗅探数据流并使用暴力破解整个系统。“前向保密”意味着仅仅因为我可以解密块 M,并不意味着我可以解密块 Q
*   缺点:
*   PFS 的缺点是所有这些额外的计算步骤会导致握手延迟,降低用户速度。为了避免在每次连接时重复这项昂贵的工作,双方都通过一种称为会话恢复的技术缓存会话密钥以备将来使用。这就是 session-ID 和 TLS 票证的用途:它们允许共享一个会话 ID 的客户机和服务器跳过会话密钥的协商,因为它们之前已经同意了一个会话密钥,并直接安全地交换数据。

# 第二部分:网络安全

> 原文:<https://linkedin.github.io/school-of-sre/level101/security/network_security/>

## 介绍

*   TCP/IP 是当今占主导地位的网络技术。它是一个五层架构。这些层从上到下依次是应用层、传输层(TCP)、网络层(IP)、数据链路层和物理层。除了 TCP/IP,还有其他网络技术。为了方便起见,我们使用 OSI 网络模型来表示非 TCP/IP 网络技术。不同的网络通过网关相互连接。网关可以放在任何一层。
*   OSI 模型是一个七层体系结构。OSI 体系结构类似于 TCP/IP 体系结构,只是 OSI 模型在 TCP/IP 体系结构的应用层和传输层之间指定了两个附加层。这两层是表示层和会话层。图 5.1 显示了 TCP/IP 层和 OSI 层之间的关系。TCP/IP 中的应用层对应于 OSI 中的应用层和表示层。TCP/IP 中的传输层对应于 OSI 中的会话层和传输层。TCP/IP 体系结构中的其余三层与 OSI 模型中的其余三层一一对应。

    TCP/IP 体系结构和 OSI 模型各层之间的对应关系。还示出了网络层中加密算法的布局,其中虚线箭头表示加密算法的实际通信

OSI 各层的功能简述如下:

1.  应用层是应用和网络程序之间的接口。它支持应用和最终用户处理。常见的应用层程序包括远程登录、文件传输、电子邮件和 Web 浏览。
2.  表示层负责处理不同形式的数据。该协议层允许位于具有不同平台的通信信道的不同端的应用层程序理解彼此的数据格式,而不管它们是如何呈现的。
3.  会话层负责创建、管理和关闭通信连接。
4.  传输层负责提供可靠的连接,如数据包排序、流量控制和拥塞控制。
5.  网络层负责将与设备无关的数据包从当前跳路由到下一跳。
6.  数据链路层负责将设备无关的数据包封装到设备相关的数据帧中。它有两个子层:逻辑链路控制和媒体访问控制。
7.  物理层负责通过某些物理介质传输设备相关的帧。

8.  从应用层开始,应用生成的数据逐层传递到物理层。来自前一层的数据被包含在当前层的新信封中,其中来自前一层的数据也只是包含来自前一层的数据的信封。这类似于将一个较小的信封装入一个较大的信封。每层添加的信封包含足够的信息来处理数据包。应用层数据被分成足够小的块,以便在下一层封装在信封中。

9.  根据以下基本步骤,应用数据块在 TCP/IP 体系结构中被“修饰”。在发送端,当应用数据块被向下传递到 TCP 层时,它被封装在 TCP 数据包中。换句话说,TCP 数据包由报头和有效载荷组成,其中报头对应于 TCP 信封,有效载荷是应用数据块。同样,当 TCP 数据包被向下传递到 IP 层时,它将被封装在 IP 数据包中。IP 数据包由报头和有效载荷组成,有效载荷是从 TCP 层向下传递的 TCP 数据包。当 IP 分组被向下传递到数据链路层时,它将被封装在设备相关的帧(例如,以太网帧)中。一帧有一个帧头,也可能有一个帧尾。例如,除了报头之外,以太网帧还有 32 位循环冗余校验(CRC)报尾。当帧被传递到物理层时,它将被转换成一系列媒体信号进行传输

    ![image15](https://github.com/OpenDocCN/geekdoc-linux-zh/raw/master/lkin-sre/img/241bbeb68439ce365d32a097c35b826d.png)数据包生成流程图

10.  在目的端,媒体信号由物理层转换成帧,然后向上传递到数据链路层。数据链路层将帧有效负载(即封装在帧中的 IP 数据包)向上传递到 IP 层。IP 层将 IP 有效载荷(即封装在 IP 包中的 TCP 包)向上传递到 TCP 层。TCP 层将 TCP 有效负载(即应用数据块)向上传递到应用层。当数据包到达路由器时,它仅向上到达 IP 层,在那里修改 IP 报头中的某些字段(例如,TTL 的值减 1)。然后,这个修改后的分组被逐层向下传递回物理层,以便进一步传输。

### 公钥基础设施

*   为了在网络应用中部署加密算法,我们需要一种使用开放网络分发密钥的方法。公钥加密是分发这些密钥的最佳方式。要使用公钥加密,我们需要构建一个公钥基础设施(PKI)来支持和管理公钥证书和证书颁发机构(CA)网络。特别是,公钥基础设施被设置为执行以下功能:
*   在向用户颁发公钥证书之前,确定用户的合法性。
*   应用户请求颁发公钥证书。
*   根据用户请求延长公钥证书的有效时间。
*   应用户请求或当相应的私钥泄露时,撤销公钥证书。
*   存储和管理公钥证书。
*   防止数字签名者否认他们的签名。
*   支持 CA 网络以允许不同的 CA 对其他 CA 颁发的公钥证书进行身份验证。
*   x . 509:[`certificatedecoder.dev/?gclid = eaiaiqobchmi 0m 731 o 6g 6 givvsqrch 04 bqaaaeaayaaegkrkpd _ BwE`](https://certificatedecoder.dev/?gclid=EAIaIQobChMI0M731O6G6gIVVSQrCh04bQaAEAAYASAAEgKRkPD_BwE)

### IPsec:网络层的安全协议

*   IPsec 是网络层的主要安全协议
*   IPsec 为构建虚拟专用网络(VPN)提供了一个强大的平台。VPN 是覆盖在公共网络上的私有网络。
*   在网络层部署加密算法的目的是加密或验证 IP 数据包(或者只是有效载荷,或者是整个数据包)。
*   IPsec 还规定了如何交换密钥。因此,IPsec 由认证协议、加密协议和密钥交换协议组成。它们分别被称为认证报头(AH)、封装安全负载(ESP)和互联网密钥交换(IKE)。

### PGP & S/MIME:电子邮件安全性

*   应用层有几种安全协议。这些协议中最常用的是电子邮件安全协议,即 PGP 和 S/MIME。
*   SMTP(“简单邮件传输协议”)用于通过端口 25 从客户端向服务器发送和交付:它是发送服务器。相反,POP(“邮局协议”)允许用户接收邮件并下载到他们的收件箱:它是接收服务器。邮局协议的最新版本被命名为 POP3,从 1996 年开始使用;它使用端口 110

电子邮件加密程序

*   PGP 实现了所有主要的加密算法、ZIP 压缩算法和 Base64 编码算法。
*   它可用于验证消息、加密消息或两者兼而有之。PGP 遵循以下一般过程:身份验证、ZIP 压缩、加密和 Base64 编码。
*   Base64 编码过程使邮件准备好进行 SMTP 传输

GPG (GnuPG)

*   GnuPG 是另一个公司可以使用的基于 OpenPGP 的免费加密标准。
*   GnuPG 是赛门铁克 PGP 的替代品。
*   主要区别在于支持的算法。然而,GnuPG 在设计上与 PGP 配合得很好。因为 GnuPG 是开放的,一些企业更喜欢 Symantec 的 PGP 提供的技术支持和用户界面。
*   需要注意的是,GnuPG 和 PGP 的兼容性之间存在一些细微差别,例如某些算法之间的兼容性,但在大多数应用如电子邮件中,都有变通方法。其中一个算法是 IDEA 模块,由于专利问题,它没有包含在 GnuPG 中。

s/哑剧

*   SMTP 只能处理 7 位 ASCII 文本(您可以使用 UTF-8 扩展来缓解这些限制)消息。虽然 POP 可以处理除 7 位 ASCII 之外的其他内容类型,但是在通用默认设置下,POP 可以将存储在邮件服务器中的所有消息下载到用户的本地计算机。之后,如果 POP 从邮件服务器中删除这些邮件。这使得用户很难从多台计算机上阅读他们的消息。
*   多用途 Internet 邮件扩展协议(MIME)旨在支持发送和接收各种格式的电子邮件,包括由文字处理器生成的非文本文件、图形文件、声音文件和视频剪辑。此外,MIME 允许单个消息以这些格式的任意组合包含混合类型的数据。
*   在 TCP 端口 143 上运行的互联网邮件访问协议(IMAP )(仅用于非加密)在邮件服务器中存储(可在服务器和客户端上配置,就像 PoP 一样)传入的电子邮件消息,直到用户有意删除它们。这允许用户从多台机器访问他们的邮箱,并将消息下载到本地机器,而不用从邮件服务器的邮箱中删除它。

SSL/TLS

*   SSL 通过要求服务器使用由可信 CA 签署的安全证书,使用 PKI 来决定服务器的公钥是否可信。
*   当 Netscape Navigator 1.0 发布时,它信任由 RSA 数据安全公司运营的单个 CA。
*   服务器的公共 RSA 密钥过去被存储在安全证书中,然后可以被浏览器用来建立安全的通信通道。我们今天使用的安全证书仍然依赖于 Netscape Navigator 1.0 当时使用的同一标准(名为 X.509)。
*   Netscape 打算训练用户(尽管后来没有成功)区分安全通信和不安全通信,所以他们在地址栏旁边放了一个锁图标。当锁打开时,通信是不安全的。关闭的锁意味着通信已经通过 SSL 得到了保护,这需要服务器提供一个签名的证书。很明显,你对这个图标很熟悉,因为从那以后它就出现在每个浏览器中。网景公司的工程师们真正创造了安全互联网通信的标准。
*   在发布 SSL 2.0 一年后,Netscape 修复了几个安全问题,并发布了 SSL 3.0,这是一个协议,尽管自 2015 年 6 月以来被正式否决,但在推出 20 多年后,仍在世界上的某些地区使用。为了使 SSL 标准化,因特网工程任务组(IETF)创建了一个稍加修改的 SSL 3.0,并在 1999 年将其公布为传输层安全(TLS) 1.0。SSL 和 TLS 之间的名称变化至今仍在困扰着人们。从官方角度来说,TLS 是新的 SSL,但实际上,人们可以互换使用 SSL 和 TLS 来谈论任何版本的协议。

*   必看:

*   [`tls.ulfheim.net/`](https://tls.ulfheim.net/)
*   [`davidwong.fr/tls13/`](https://davidwong.fr/tls13/)

## 网络边界安全

让我们看看如何检查周边,即边缘,这是第一层保护

### 通用防火墙框架

*   需要防火墙,因为加密算法不能有效地阻止恶意数据包进入边缘网络。
*   这是因为无论 IP 数据包是否加密,它们总是可以被转发到边缘网络。
*   20 世纪 90 年代开发的防火墙是帮助限制网络访问的重要工具。防火墙可以是硬件设备、软件包或两者的组合。
*   从外部流入内部网络的数据包应该在被允许进入之前进行评估。防火墙的一个关键要素是它能够在不影响通信速度的情况下检查数据包,同时为内部网络提供安全保护。
*   防火墙执行的数据包检查可以使用几种不同的方法来完成。根据防火墙使用的特定方法,它可以被描述为包过滤、电路网关、应用网关或动态包过滤。

### 数据包过滤器

*   它检查从外部进入内部网络的入站数据包,并检查从内部网络出去的出站数据包
*   打包过滤只检查 IP 报头和 TCP 报头,而不检查在应用层生成的有效负载
*   包过滤防火墙使用一组规则来确定是允许还是拒绝数据包通过。
*   两种类型:
*   无国籍的

    *   它将每个数据包视为一个独立的对象,并且不跟踪任何以前处理过的数据包。换句话说,无状态过滤会在数据包到达时对其进行检查,并在不留下任何被检查数据包记录的情况下做出决定。
*   宏伟威严的

    *   状态过滤,也称为连接状态过滤,跟踪内部主机和外部主机之间的连接。连接状态(或简称状态)表示是 TCP 连接还是 UDP 连接,以及连接是否建立。

### 电路网关

*   电路网关也称为电路级网关,通常在传输层运行
*   它们评估 TCP(或 UDP)报头中包含的 IP 地址和端口号信息,并使用这些信息来确定是否允许内部主机和外部主机建立连接。
*   通常的做法是将包过滤和电路网关结合起来形成动态包过滤(DPF)。

### 应用网关(ALG)

*   又名代理服务器
*   应用层网关(ALG)充当内部主机的代理,处理来自外部客户端的服务请求。
*   ALG 对每个 IP 数据包(入口或出口)执行深度检查。
*   特别地,ALG 检查包含在分组中的应用格式(例如,MIME 格式或 SQL 格式),并检查其有效载荷是否被允许。
*   因此,ALG 能够检测有效载荷中包含的计算机病毒。由于 ALG 会检查数据包的有效负载,因此除了阻止带有可疑 IP 地址和 TCP 端口的数据包之外,它还能够检测恶意代码并隔离可疑数据包。另一方面,ALG 也会导致大量的计算和空间开销。

### 可信系统和堡垒主机

*   可信操作系统(TOS)是满足一组特定安全要求的操作系统。操作系统是否可信取决于几个因素。例如,对于要被认证为可信的特定计算机上的操作系统,除了别的以外,还需要验证满足以下四个要求:
*   其系统设计没有缺陷;
*   其系统软件没有漏洞;
*   其系统配置正确;和
*   其系统管理得当。

*   堡垒主机

*   堡垒主机是具有强大防御机制的计算机。它们通常用作实现应用网关、电路网关和其他类型防火墙的主机。堡垒主机在可信的操作系统上运行,该操作系统不得包含不必要的功能或程序。这一措施有助于减少错误概率,并使安全检查更容易进行。只有那些必要的网络应用,例如 SSH、DNS、SMTP 和身份验证程序,才安装在堡垒主机上。
*   Bastion 主机也主要用作受控的入口点,以便安全监控可以更精确地关注发生在单个点上的动作。

* * *

## 常用技术&扫描、数据包捕获

### 使用 Nmap 扫描端口

*   Nmap(“网络映射器”)是一个免费的开源(许可)工具,用于网络发现和安全审计。许多系统和网络管理员也发现它对于诸如网络库存、管理服务升级计划以及监控主机或服务正常运行时间等任务非常有用。
*   Nmap 最好的一点是它是免费和开源的,非常灵活和通用
*   Nmap 通常用于确定网络中的活动主机、这些主机上的开放端口、这些开放端口上运行的服务以及该端口上该服务的版本标识。
*   更多在 http://scanme.nmap.org/

```sh
nmap [scan type] [options] [target specification] 

Nmap 使用 6 种不同的端口状态:

  • 开放 —开放端口是主动接受 TCP、UDP 或 SCTP 连接的端口。开放的端口是我们最感兴趣的,因为它们容易受到攻击。开放端口还显示网络上可用的服务。
  • 关闭 —接收和响应 Nmap 探测数据包的端口,但没有应用在该端口上侦听。有助于识别主机是否存在以及操作系统检测。
  • Filtered — Nmap 无法确定端口是否打开,因为包过滤会阻止其探测器到达该端口。过滤可能来自防火墙或路由器规则。在扫描过程中,过滤端口通常很少给出信息,因为过滤器可能会丢弃探测而不作出响应,或者以无用的错误消息作出响应,例如目的地不可达。
  • 未过滤 —端口可访问,但 Nmap 不知道它是打开的还是关闭的。仅在用于映射防火墙规则集的确认扫描中使用。其他扫描类型可用于识别端口是否打开。
  • 打开/过滤 — Nmap 无法确定是打开还是过滤。当打开的端口没有响应时,就会发生这种情况。没有响应可能意味着数据包筛选器丢弃了该探测,或者任何响应都被阻止。
  • 关闭/过滤 — Nmap 无法确定端口是关闭还是过滤。仅用于 IP ID 空闲扫描。

Nmap 扫描的类型:

  1. TCP 连接

  2. TCP 连接扫描完成三次握手。

  3. 如果端口打开,操作系统完成 TCP 三次握手,端口扫描器立即关闭连接以避免 DOS。这是“嘈杂”的,因为服务可以记录发送者的 IP 地址,并可能触发入侵检测系统。

  4. UDP 扫描

  5. 该扫描检查是否有任何 UDP 端口正在侦听。

  6. 由于 UDP 不像 TCP 那样以肯定的确认来响应,而是仅在端口关闭时才响应传入的 UDP 数据包,

  7. 同步扫描

  8. SYN 扫描是 TCP 扫描的另一种形式。

  9. 这种扫描类型也称为“半开扫描”,因为它实际上从不打开完整的 TCP 连接。

  10. 端口扫描器生成一个 SYN 数据包。如果目标端口是开放的,它将使用 SYN-ACK 数据包进行响应。扫描仪主机用 RST 数据包响应,在握手完成前关闭连接。

  11. 如果端口关闭但未过滤,目标将立即用 RST 数据包响应。

  12. SYN 扫描的优点是各个服务实际上从不接收连接。

  13. 手指扫描

  14. 这是一种秘密扫描,类似于 SYN 扫描,但发送的是 TCP FIN 数据包。

  15. 确认扫描

  16. Ack 扫描确定端口是否被过滤。

  17. 零扫描

  18. 另一种非常隐秘的扫描,将所有 TCP 报头标志设置为 off 或 null。

  19. 这通常不是有效的数据包,一些主机不知道如何处理。

  20. 圣诞扫描

  21. 类似于空扫描,只是 TCP 报头中的所有标志都设置为 on

  22. RPC 扫描

  23. 这种特殊类型的扫描寻找对 RPC(远程过程调用)服务的机器应答

  24. 空闲扫描

  25. 这是一种超级隐蔽的方法,扫描数据包从外部主机上反弹回来。

  26. 您不需要控制另一台主机,但它必须设置并满足某些要求。你必须输入我们的“僵尸”主机的 IP 地址和使用的端口号。这是 Nmap 中比较有争议的选项之一,因为它只用于恶意攻击。

扫描技术

一些扫描技术可用于获取有关系统及其端口的更多信息。你可以在medium . com/infosec-adventures/nmap-cheat sheet-a 423 fcd da 0 ca了解更多信息

OpenVAS

  • OpenVAS 是一个全功能的漏洞扫描器。
  • OpenVAS 是一个服务和工具框架,它提供了一个全面而强大的漏洞扫描和管理包
  • OpenVAS 是一个开源程序,最初是一度更受欢迎的扫描程序 Nessus 的一个分支。
  • OpenVAS 由三个主要部分组成。这些是:
  • 网络漏洞测试(NVTs)的定期更新馈送;
  • 运行 NVTs 的扫描仪;和
  • 一个 SQLite 3 数据库,用于存储您的测试配置以及 NVTs 的结果和配置。
  • www.greenbone.net/en/install_use_gce/

WireShark

  • Wireshark 是一个协议分析器。
  • 这意味着 Wireshark 不仅可以解码数据包的位和字节,还可以解码数据包和协议之间的关系。
  • Wireshark 理解协议序列。

Wireshark 的简单演示

  1. 仅捕获 udp 数据包:

  2. 捕获筛选器= "udp "

  3. 仅捕获 tcp 数据包

  4. 捕获筛选器= "tcp "

  5. TCP/IP 三次握手image17

  6. 按 IP 地址过滤:显示来自 IP 的所有流量,无论是源流量还是目的流量

  7. ip.addr == 192.168.1.1

  8. 按源地址过滤:仅显示来自 IP 源的流量

  9. ip.src == 192.168.0.1

  10. 按目标过滤:仅显示来自 IP 目标的流量

  11. ip.dst == 192.168.0.1

  12. 按 IP 子网过滤:显示来自子网的流量,无论是来源还是目的地

  13. ip.addr = 192.168.0.1/24

  14. 按协议过滤:按协议名称过滤流量

  15. 十进位计数制

  16. 超文本传输协议(HTTP)(Hyper Text Transport Protocol 的缩写)

  17. 文件传输协议(File Transfer Protocol 的缩写)

  18. 阿尔普

  19. 远程登录

  20. 网间控制报文协议

  21. 排除 IP 地址:删除进出 IP 地址的流量

  22. !ip.addr ==192.168.0.1

  23. 显示两个特定子网之间流量

*   ip.addr == 192.168.0.1/24,ip.addr == 192.168.1.1/24
  1. 显示两个特定工作站之间的流量
*   ip.addr == 192.168.0.1,ip.addr == 192.168.0.2
  1. 按 MAC 过滤
*   eth.addr = 00:50:7f:c5:b6:78
  1. 过滤 TCP 端口
*   tcp.port == 80
  1. 过滤 TCP 端口源
    * tcp.srcport == 80
  2. 过滤 TCP 端口目标
    * tcp.dstport == 80
  3. 查找用户代理
    * http.user_agent 包含 Firefox
    * !http.user_agent 包含||!http.user_agent 包含 Chrome
  4. 过滤广播流量
    * !(arp 或 icmp 或 dns)
  5. 过滤 IP 地址和端口
*   TCP . port = = 80 & & IP . addr = = 192 . 168 . 0 . 1
  1. 过滤所有 http get 请求
*   请求
  1. 过滤所有 http get 请求和响应
    * http.request 或 http.response
  2. 过滤三次握手
    * tcp.flags.syn1 或(tcp.seq1 且 tcp.ack1 且 tcp.len0 且 tcp.analysis.initial_rtt)
  3. 按类型查找文件
    * 框架包含“(附件|tar|exe|zip|pdf)”
  4. 基于关键字查找流量
    * tcp 包含 facebook
    * 框架包含 facebook
  5. 检测 SYN 泛洪
    * tcp.flags.syn == 1,tcp.flags.ack == 0

Wireshark 混杂模式 -默认情况下,Wireshark 只捕获进出其运行的计算机的数据包。通过在捕获设置中选中在混杂模式下运行 Wireshark 的复选框,您可以捕获 LAN 上的大部分流量。

倾倒帽

  • Dumpcap 是一个网络流量转储工具。它从实时网络中捕获数据包数据,并将数据包写入文件。Dumpcap 的原生捕获文件格式是 pcapng,这也是 Wireshark 使用的格式。
  • 默认情况下,Dumpcap 使用 pcap 库从第一个可用的网络接口捕获流量,并将收到的原始数据包数据以及数据包的时间戳写入 pcapng 文件。捕获过滤器语法遵循 pcap 库的规则。
  • Wireshark 命令行实用程序“dumpcap.exe”可用于捕获一段时间内的 LAN 流量。
  • 也可以使用 Wireshark 本身,但是 dumpcap 在长时间捕获时不会大量使用计算机的内存。

DaemonLogger

  • Daemonlogger 是一个专门设计用于网络和系统管理(NSM)环境的数据包记录应用。
  • Daemonlogger 提供的最大好处是,像 Dumpcap 一样,它很容易用于捕获数据包。为了开始捕获,您只需要调用命令并指定一个接口。
  • daemonlogger–I et h1
  • 默认情况下,该选项将开始捕获数据包并将它们记录到当前工作目录中。
  • 将收集数据包,直到捕获文件大小达到 2 GB,然后将创建一个新文件。这将无限期地继续下去,直到进程停止。

网络嗅探

  • Netsniff-NG 是一个高性能的数据包捕获工具
  • 到目前为止,我们讨论的实用程序依赖 Libpcap 进行捕获,而 Netsniff-NG 利用零拷贝机制来捕获数据包。这样做是为了支持在高吞吐量链路上捕获完整的数据包。
  • 要开始用 Netsniff-NG 捕获包,我们必须指定一个输入和输出。在大多数情况下,输入将是网络接口,输出将是磁盘上的文件或文件夹。

netsniff-ng –i eth1 –o data.pcap

网络流

  • NetFlow 是 Cisco 路由器在 1996 年左右推出的一项功能,能够在 IP 网络流量进出接口时收集流量。通过分析 NetFlow 提供的数据,网络管理员可以确定流量的来源和目的地、服务等级以及拥塞原因等。典型的流监控设置(使用 NetFlow)由三个主要组件组成:[1]

  • 流导出器:将数据包聚合成流,并向一个或多个流收集器导出流记录。

  • 流收集器:负责接收、存储和预处理从流导出器接收的流数据。

  • 分析应用:例如,在入侵检测或流量分析环境中分析接收到的流数据。

    • 支持 NetFlow 的路由器和交换机可以收集启用了 NetFlow 的所有接口上的 IP 流量统计信息,然后将这些统计信息作为 NetFlow 记录导出到至少一个 NetFlow 收集器,通常是进行实际流量分析的服务器。

标识部分(Identification Section)

一种安全解决方案,可检测环境中与安全相关的事件,但不会阻止它们。IDS 传感器可以基于软件和硬件来收集和分析网络流量。这些传感器有两种类型,网络 id 和主机 id。

  • 主机 IDS 是一个特定于服务器的代理,它运行在服务器上,监控操作系统的开销最小。
  • 网络 IDS 可以嵌入网络设备、独立设备或监控网络流量的模块中。

基于签名的 IDS

  • 基于签名的 IDS 监控网络流量或观察系统,并在已知的恶意事件发生时发出警报。

  • 它通过将数据流与已知攻击模式的数据库进行比较来做到这一点

  • 这些签名明确定义了哪些流量或活动应被视为恶意的。

  • 十多年来,基于签名的检测一直是基于网络的防御安全的基础,部分原因是它非常类似于使用防病毒实用程序在主机级别检测恶意活动的方式

  • 公式相当简单:分析师观察恶意活动,从活动中得出指标,并将其发展为签名,然后这些签名将在活动再次发生时发出警报。

  • 例如:SNORT & SURICATA

基于策略的 IDS

  • 基于策略的 IDSs(主要是主机 IDSs)在违反配置的策略时触发警报。
  • 这个配置的策略是或者应该是安全策略的表示。
  • 这种类型的 IDS 非常灵活,可以根据公司的网络需求进行定制,因为它确切地知道什么是允许的,什么是不允许的。
  • 另一方面,基于签名的系统依赖于供应商的细节和默认设置。

基于异常的入侵检测

  • 基于异常的 IDS 寻找偏离正常的流量,但是什么是正常网络流量模式的定义是棘手的部分
  • 基于异常的入侵检测系统有两种类型:统计异常检测和非统计异常检测
  • 统计异常检测以交互方式学习一段时间内的流量模式。
  • 在非统计方法中,IDS 有一个预定义的假定可接受和有效的流量模式配置。

基于主机的 IDS 和基于网络的 IDS

  • 主机 IDS 可以被描述为驻留在需要保护的网络的每个服务器上的分布式代理。这些分布式代理与底层操作系统紧密相关。

  • 另一方面,网络 IDSs 可以被描述为智能嗅探设备。网络 IDS 从网络中捕获数据(原始数据包),而主机 IDS 从安装它们的主机上捕获数据。

蜜罐

  • 使用诱饵机器将入侵者的注意力从受保护的机器上引开是防止入侵攻击的主要技术。任何用作诱饵来引诱攻击者远离重要资产并收集入侵或滥用行为的设备、系统、目录或文件都被称为蜜罐。
  • 蜜罐可以实现为物理设备或仿真系统。其思想是在局域网中设置诱饵机器,或在文件系统中设置诱饵目录/文件,使它们看起来很重要,但有几个可利用的漏洞,引诱攻击者攻击这些机器或目录/文件,以便其他机器、目录和文件可以避开入侵者的注意。诱饵机器可以是主机计算机或服务器计算机。同样,我们也可以建立假路由器,甚至假局域网。

盔甲上的漏洞(TCP/IP 安全问题)

image18

IP 欺骗

  • 在这种类型的攻击中,攻击者用不同的地址替换发件人的 IP 地址,或者在极少数情况下替换目的地的 IP 地址。
  • IP 欺骗通常用于攻击目标主机。在其他情况下,它被用来发起拒绝服务(DoS)攻击。
  • 在 DoS 攻击中,攻击者修改 IP 数据包,误导目标主机接受原始数据包,将其视为来自可信主机的数据包。攻击者必须知道可信主机的 IP 地址,才能修改数据包报头(源 IP 地址),使数据包看起来好像来自该主机。

IP 欺骗检测技术

  • 直接 TTL 探针

  • 在这种技术中,我们向可疑欺骗 IP 主机发送一个数据包,该主机触发回复并将 TTL 与可疑数据包进行比较;如果回复中的 TTL 与被检查的数据包不同;这是一个欺骗数据包。

  • 当攻击者与受害者位于不同的子网时,这种技术就成功了。image19

  • IP 标识号。

  • 向可疑欺骗流量的主机发送一个探测,触发回复并将 IP ID 与可疑流量进行比较。

  • 如果 IP IDs 不在被检查的数据包的邻近值中,则可疑流量是欺骗的

  • TCP 流量控制方法

  • 发送欺骗 TCP 数据包的攻击者不会收到目标的 SYN-ACK 数据包。

  • 因此,攻击者无法对拥塞窗口大小的变化做出响应

  • 当接收器在窗口大小用尽后仍接收到流量时,最有可能的情况是数据包被欺骗。

隐蔽通道

  • 隐蔽或秘密通道可以被最好地描述为两个实体之间的管道或通信通道,它可以被以违反系统安全规范的方式传输信息的进程或应用所利用。

  • 更具体地说,对于 TCP/IP,在某些情况下,建立隐蔽通道,并且数据可以在两个终端系统之间秘密地传递。

  • 例如:ICMP 位于 TCP/IP 协议组的互联网层,在所有 TCP/IP 主机中实现。根据 ICMP 协议的规范,ICMP 回应请求消息应该有一个 8 字节的报头和一个 56 字节的有效载荷。ICMP 回应请求数据包不应在有效负载中携带任何数据。然而,这些数据包经常被用来携带秘密信息。ICMP 数据包稍作改动,以便在有效载荷中携带机密数据。这使得数据包变得更大,但是在协议栈中没有控制来消除这种行为。ICMP 数据包的改变使得入侵者能够对专门的客户机-服务器对进行编程。这些小段代码在不通知网络管理员的情况下导出机密信息。

  • ICMP 不仅可以用于数据过滤。例如,一些 C&C 工具,如 Loki,早在 1996 年就使用 ICMP 信道建立加密的交互式会话。

  • 深度数据包检测已经走过了漫长的道路。许多 IDS/IPS 检测 ICMP 隧道。

    • 检查不包含与请求相同的有效负载的回应响应
    • 检查 ICMP 流量,尤其是超出可接受阈值的流量

IP 碎片攻击

  • TCP/IP 协议族,或者更具体地说是 IP,允许分组的分段。(这是一个特性,而不是一个错误)

  • IP 分段偏移量用于跟踪数据报的不同部分。

  • 该字段中的信息或内容在目的端用于重组数据报

  • 所有这样的片段具有相同的标识字段值,并且分段偏移指示当前片段在原始分组的上下文中的位置。

  • 许多接入路由器和防火墙不执行分组重组。在正常操作中,IP 片段不会重叠,但是攻击者可以创建人为分割的数据包来误导路由器或防火墙。通常,由于数据和计算开销,这些数据包很小,对于终端系统几乎不切实际。

  • IP 碎片攻击的一个很好的例子是 Ping of Death 攻击。死亡 Ping 攻击会发送一些片段,当这些片段在终端站重新组合时,会产生一个比最大允许长度更大的数据包。

TCP 标志

  • 在三次握手完成之前,不会使用 TCP 进行数据交换。这种握手使用不同的标志来影响 TCP 数据段的处理方式。

  • TCP 报头中有 6 位通常称为标志。即:

  • 6 个不同的标志是 TCP 报头的一部分:紧急指针字段(URG)、确认字段(ACK)、推送功能(PSH)、重置连接(RST)、同步序列号(SYN)以及发送方完成该连接(FIN)。image20

  • 攻击者可以滥用这些标志的正常操作或设置来发起 DoS 攻击。这会导致网络服务器或 web 服务器崩溃或挂起。

| SYN  | FIN  | PSH   | RST  | Validity|  
|------|------|-------|------|---------|
| 1    |1     |0      |0     |Illegal Combination
| 1    |1     |1      |0     |Illegal Combination
| 1    |1     |0      |1     |Illegal Combination
| 1    |1     |1      |1     |Illegal Combination 
  • 攻击者的最终目标是编写特殊的程序或代码片段,这些程序或代码片段可以构造这些非法组合,从而导致有效的 DoS 攻击。

合成洪水

  • 攻击者经常使用 3 次握手中的计时器(或缺少某些计时器)来禁用服务,甚至进入系统。
  • 在三次握手的步骤 2 之后,在接收到 SYN 之后的等待时间没有限制。攻击者向 XYZ 公司的 web 服务器发起许多连接请求(几乎可以肯定是用一个假冒的 IP 地址)。
  • web 服务器发送回原始源 IP 地址的 SYN+ACK 数据包(步骤 2)不会得到回复。这使得 TCP 会话在 web 服务器上处于半开状态。多个数据包导致多个 TCP 会话保持打开。
  • 基于服务器的硬件限制,有限数量的 TCP 会话可以保持打开,因此,一旦达到某个限制,web 服务器就拒绝来自任何主机的进一步连接建立尝试。在可以建立新的连接之前,这些半开连接需要被完成或超时。

鳍攻击

  • 在正常操作中,发送方设置 TCP FIN 标志,表示不再传输数据,连接可以关闭。

  • 这是一种四向握手机制,发送方和接收方都需要在收到 FIN 数据包时发送确认。

  • 在试图破坏连接的攻击中,会构造一个伪造的 FIN 数据包。该数据包还具有正确的序列号,因此目标主机会认为该数据包是有效的。这些序列号很容易预测。这一过程被称为 TCP 序列号预测,攻击者要么嗅探连接的当前序列号和确认(SEQ/ACK)号,要么通过算法预测这些号码。

连接劫持

image22

  • 一个授权用户(员工 X)通过与 web 服务器的 TCP 会话发送 HTTP 请求。
  • 只有当数据包具有正确的 SEQ/ACK 号时,web 服务器才会接受来自员工 X 的数据包。如前所述,这些数字对于 web 服务器区分不同的会话并确保它仍在与员工 X 通话非常重要。想象一下,黑客开始使用正确的 seen 确认组合向 web 服务器发送数据包,假冒员工 X 的 IP 地址。web 服务器接受数据包并增加 ACK 号。
  • 与此同时,员工 X 继续发送数据包,但发送的 SEQ/ACK 号不正确。由于发送了不同步的数据包,来自员工 X 的所有数据在被 web 服务器接收时都会被丢弃。攻击者使用正确的数字伪装成员工 X。这最终导致黑客劫持了连接,由此员工 X 完全被弄糊涂了,并且 web 服务器假定黑客正在发送正确的同步数据来回复。

步骤:

  1. 攻击者使用网络监视器检查流量,并注意到从员工 X 到 web 服务器的流量。
  2. web 服务器将数据返回或回显给始发站(员工 X)。
  3. 员工 X 确认了该数据包。
  4. 黑客向服务器发送一个欺骗数据包。
  5. 网络服务器响应黑客。破解者开始验证 SEQ/ACK 号,以再次检查是否成功。此时,破解程序从员工 X 处接管会话,这导致员工 X 的会话挂起。
  6. 黑客可以开始向网络服务器发送流量。
  7. web 服务器返回所请求的数据,用正确的 ACK 号确认交付。
  8. 破解者可以继续发送数据(跟踪正确的 SEQ/ACK 号),直到最终设置 FIN 标志来终止会话。

缓冲区溢出

  • 缓冲区是用于存储程序代码和数据的临时数据存储区域。
  • 当一个程序或进程试图在一个缓冲区中存储比原来预期的更多的数据时,就会发生缓冲区溢出。
  • 缓冲区是内存中的临时存储位置(内存或缓冲区大小通常以字节为单位),可以存储固定数量的字节数据。当检索的数据超过缓冲区位置可以存储的数据时,附加信息必须进入相邻的缓冲区,导致覆盖其中保存的有效数据。

机制:

  • 缓冲区溢出漏洞有不同的类型。但所有缓冲区溢出攻击的总体目标都是控制特权程序,如果可能的话,还包括主机。攻击者有两个任务来实现这个目标。首先,脏代码需要在程序的代码地址空间中可用。第二,特权程序应该跳转到代码的特定部分,这确保了正确的参数被加载到内存中。
  • 第一项任务可以通过两种方式实现:将代码注入正确的地址空间,或者使用现有的代码并稍微修改某些参数。第二个任务稍微复杂一点,因为需要修改程序的控制流,使程序跳转到脏代码。

对策:

  • 最重要的方法是集中精力编写正确的代码。
  • 第二种方法是使程序代码的数据缓冲区(内存位置)地址空间不可执行。这种类型的地址空间使代码无法执行,代码可能会在攻击过程中渗透到程序的缓冲区中。

更多欺骗

地址解析协议欺骗

  • 地址解析协议(ARP)提供了一种将已知 IP 地址解析或映射到 MAC 子层地址的机制。
  • 利用 ARP 欺骗,黑客可以通过欺骗主机 b 的硬件地址来利用这种硬件地址认证机制。基本上,攻击者可以使本地网络上的任何主机或网络设备相信黑客的工作站是可信的主机。这是交换环境中常用的方法。
  • 通过在网络中的所有主机和路由器上实施静态 ARP 表,可以防止 ARP 欺骗。或者,您可以实现一个 ARP 服务器,代表目标主机响应 ARP 请求。

DNS 欺骗

  • DNS 欺骗是黑客使目标机器相信它想要连接的系统就是黑客的机器的方法。
  • 黑客修改了一些记录,使主机的名称条目与攻击者的 IP 地址相对应。存在整个 DNS 服务器被攻击破坏的实例。
  • 为了对抗 DNS 欺骗,反向查找会检测这些攻击。反向查找是一种根据名称验证 IP 地址的机制。IP 地址和名称文件通常保存在不同的服务器上,这使得泄密更加困难

第三部分:威胁、攻击和防御

原文:https://linkedin.github.io/school-of-sre/level101/security/threats_attacks_defences/

DNS 保护

缓存中毒攻击

  • 由于 DNS 响应被缓存,因此可以为重复转换提供快速响应。DNS 否定查询也被缓存,例如拼写错误的单词,并且所有缓存的数据定期超时。缓存中毒是域欺骗中的一个问题。该术语用于描述黑客攻击,通过伪造 DNS 映射将网站流量重定向到虚假网站。在这种情况下,攻击者试图在 DNS 中插入一个伪造的 Internet 域地址记录。如果服务器接受了伪造的记录,缓存就会中毒,随后对域地址的请求会用攻击者控制的服务器地址来回答。只要服务器缓存了虚假条目,浏览器或电子邮件服务器就会自动转到被入侵的 DNS 服务器提供的地址。缓存条目的典型生存时间(TTL)是几个小时,因此许多用户有足够的时间受到攻击的影响。

DNSSEC(安保分机)

  • 这些 DNS 问题的长期解决方案是身份验证。如果解析程序无法区分响应中的有效和无效数据,则添加源身份验证,以验证响应中收到的数据是否等于区域管理员输入的数据
  • DNS 安全扩展(DNSSEC)防止数据欺骗和损坏,并提供验证服务器和请求的机制,以及建立真实性和完整性的机制。
  • 对 DNS 响应进行身份验证时,每个 DNS 区域都使用私钥对其数据进行签名。建议这种签约提前线下完成。对特定记录的查询返回所请求的资源记录集(RRset)和所请求的资源记录集的签名(RRSIG)。然后,解析器使用公钥对响应进行身份验证,该公钥是通过 DNS 层次结构中的一系列密钥记录预先配置或学习的。
  • DNSSEC 的目标是为没有机密性或 DDoS 保护的 DNS 响应提供认证和完整性。

边界网关协议(Border Gateway Protocol)

  • BGP 代表边界网关协议。它是一种在多个自治系统(AS)之间交换路由信息的路由协议
  • 自治系统是具有相同网络策略的路由器或网络的集合,通常受单一管理控制。
  • BGP 告诉路由器使用哪一跳到达目的网络。
  • BGP 用于在一个 AS(内部)的路由器之间和多个 AS(外部)之间传递信息。

image23

BGP 如何工作

  • BGP 负责寻找到目的路由器的路径&它选择的路径应该是最短和最可靠的。
  • 这个决定是通过称为链路状态的协议来完成的。使用链路状态协议,每台路由器都会向网络中的所有其它路由器广播其链路和 IP 子网的状态。然后,每台路由器从其它路由器接收信息,构建整个网络的完整拓扑视图。下一跳路由表基于此拓扑视图。
  • 链路状态协议使用计算机科学领域的一种著名算法,即 Dijkstra 最短路径算法:
  • 我们从路由器开始考虑到所有直接邻居的路径开销。
  • 然后选择最短的路径
  • 然后,我们重新查看我们可以到达的所有邻居,并用开销信息更新我们的链路状态表。然后,我们继续选择最短路径,直到访问完所有路由器。

BGP 漏洞

  • 通过破坏 BGP 路由表,我们能够影响互联网上的流量流向!这种行为被称为 BGP 劫持。

  • 恶意源、意外或路由器将伪造的路由广告信息注入到 BGP 分布式路由数据库中会破坏互联网骨干网的运行。

  • 黑洞流量:

  • 黑洞路由是一种无处可去的网络路由,即路由表条目,匹配路由前缀的数据包会被丢弃或忽略。黑洞路由只能通过监控丢失的流量来检测。

  • 黑洞路由是对许多常见病毒攻击的最佳防御,在这些攻击中,流量从受感染的机器被丢弃到命令和控制主机或从命令和控制主机被丢弃。

  • Youtube 上臭名昭著的 BGP 注入攻击

  • 例句:2008 年,巴基斯坦决定通过创建一条通向黑洞的 BGP 路由来屏蔽 YouTube。相反,这些路由信息被传输到香港的一家 ISP,并从那里意外地传播到世界其他地方,这意味着数百万人被路由到这个黑洞,因此无法访问 YouTube。

  • BGP 最大的潜在风险发生在拒绝服务攻击中,这种攻击使路由器被超过其处理能力的数据包淹没。当网络开始承载过量的 BGP 消息,使路由器控制处理器、存储器、路由表过载,并减少可用于数据流量的带宽时,就会发生网络过载和路由器资源耗尽。

  • 参考:medium . com/bugbountywriteup/BGP-the-weak-link-in-the-internet-what-is-BGP-and-how-do-hackers-exploit-d 899 a 68 ba 5 bb

  • 路由器摆动是另一种类型的攻击。路由波动指的是对 BGP 路由表的重复更改,通常是一分钟几次。高速撤销和重新通告会给路由器带来严重的问题,因为它们会传播路由通告。如果这些路由摆动发生得足够快,例如每秒 30 到 50 次,路由器就会过载,最终会妨碍收敛到有效路由。对互联网用户的潜在影响是消息传递速度变慢,在某些情况下,数据包可能根本无法传递。

BGP 安全性

  • 边界网关协议安全部门建议使用 BGP 对等验证,因为这是防止恶意活动的最强有力的机制之一。
  • 身份验证机制是互联网协议安全(IPsec)或 BGP MD5。
  • 另一种称为前缀限制的方法可以用来避免填充路由表。在这种方法中,路由器应该配置为禁用或终止 BGP 对等会话,并在邻居发送的前缀超过预设数量时向管理员发出警告消息。
  • IETF 目前正在致力于改善这个空间

基于网络的攻击

HTTP 响应分裂攻击

  • 如果服务器脚本将用户数据嵌入 HTTP 响应头中,而没有适当的隔离,则可能会发生 HTTP 响应拆分攻击。
  • 当脚本在重定向响应的重定向 URL(HTTP 状态代码 3xx)中嵌入用户数据时,或者当脚本在响应设置 cookie 时在 cookie 值或名称中嵌入用户数据时,通常会发生这种情况。
  • HTTP 响应拆分攻击可用于执行 web 缓存中毒和跨站点脚本攻击。
  • HTTP 响应分割是攻击者发送单个 HTTP 请求的能力,该请求迫使 web 服务器形成输出流,然后被目标解释为两个 HTTP 响应而不是一个响应。

跨站点请求伪造(CSRF 或 XSRF)

  • 跨站点请求伪造攻击欺骗受害者的浏览器向易受攻击的 web 应用发出命令。
  • 漏洞是由浏览器自动包括用户验证数据、会话 ID、IP 地址、Windows 域凭证等引起的。每一个请求。
  • 攻击者通常使用 CSRF 来启动交易,如转移资金、登录/注销用户、关闭帐户、访问敏感数据和更改帐户详细信息。
  • 该漏洞是由 web 浏览器引起的,这些浏览器会自动在每个请求中包含凭据,即使请求是由另一个站点上的表单、脚本或图像引起的。CSRF 也可以动态构建为跨站点脚本攻击的有效负载的一部分
  • 所有依赖自动凭据的站点都容易受到攻击。流行的浏览器不能防止跨站请求伪造。尽快注销高价值站点可以降低 CSRF 风险。建议高价值网站必须要求客户端在用于执行任何有安全隐患的操作的同一 HTTP 请求中手动提供身份验证数据。限制会话 cookies 的生存期还可以减少被其他恶意站点使用的机会。
  • OWASP 建议网站开发人员在与敏感业务功能相关的 HTTP 请求中包含必需的安全令牌,以减少 CSRF 攻击

跨站点脚本(XSS)攻击

  • 当动态生成的网页显示未经正确验证的用户输入(如登录信息)时,就会出现跨站点脚本,使得攻击者能够在生成的页面中嵌入恶意脚本,然后在查看该站点的任何用户的计算机上执行该脚本。
  • 如果成功,跨站点脚本漏洞可被利用来操纵或窃取 cookies,创建可能被误认为是合法用户的请求,泄露机密信息,或在最终用户系统上执行恶意代码。
  • 跨站点脚本(XSS 或 CSS)攻击包括在受害者的浏览器上执行恶意脚本。受害者只是用户的主机,而不是服务器。XSS 是由于基于 web 的应用无法验证用户输入而导致的。

文档对象模型(DOM) XSS 攻击

  • 基于文档对象模型(DOM)的 XSS 不需要 web 服务器接收 XSS 有效负载就能成功攻击。攻击者通过在客户端嵌入数据来滥用运行时。攻击者可以强制客户端(浏览器)使用攻击者控制的部分 DOM 来呈现页面。
  • 当呈现页面并由页面处理数据时,通常是由客户端 HTML 嵌入脚本(如 JavaScript)处理,页面代码可能会不安全地将数据嵌入页面本身,从而传递跨站点脚本有效负载。有几个 DOM 对象可以作为向受害者浏览器发送恶意脚本的攻击工具。

点击劫持

  • 该技术的工作原理是在合法网站内容的掩护下隐藏恶意链接/脚本。
  • 网站上的按钮实际上包含隐形链接,由攻击者放置在那里。因此,一个人点击一个他们可以看到的对象,实际上是被骗去访问一个恶意页面或执行一个恶意脚本。
  • 当鼠标悬停和点击劫持一起使用时,结果是毁灭性的。脸书用户遭受了点击劫持攻击,这种攻击欺骗人们“喜欢”特定的脸书页面,从而使攻击自 2010 年阵亡将士纪念日以来蔓延开来。
  • 目前还没有针对点击劫持的有效防御措施,禁用 JavaScript 是唯一可行的方法

数据库攻击和防御

SQL 注入袭击

  • 它利用数据库查询中不正确的输入验证。
  • 成功利用此漏洞将允许攻击者访问、修改或删除数据库中的信息。
  • 它允许攻击者窃取存储在受影响网站的后端数据库中的敏感信息,这些信息可能包括用户凭据、电子邮件地址、个人信息和信用卡号等
SELECT USERNAME,PASSWORD from USERS where USERNAME='<username>' AND PASSWORD='<password>';

Here the username & password is the input provided by the user. Suppose an attacker gives the input as " OR '1'='1'" in both fields. Therefore the SQL query will look like:

SELECT USERNAME,PASSWORD from USERS where USERNAME='' OR '1'='1' AND PASSOWRD='' OR '1'='1';

This query results in a true statement & the user gets logged in. This example depicts the bost basic type of SQL injection 

SQL 注入攻击防御

  • 可以通过过滤查询以消除恶意语法来保护 SQL 注入,这涉及使用一些工具以便(a)扫描源代码。
  • 此外,输入字段应限制为绝对最小值,通常为 7-12 个字符,并验证任何数据,例如,如果用户输入年龄,请确保输入的是最多 3 位数的整数。

虚拟专用网络

虚拟专用网络(VPN)是一种通过共享公共基础设施(如互联网)提供安全可靠连接的服务。Cisco 将 VPN 定义为公共网络上专用网络之间的加密连接。迄今为止,有三种类型的 VPN:

  • 远程存取
  • 站点到站点
  • 基于防火墙

安全违规行为

尽管采取了最积极的措施来保护计算机免受攻击,但攻击者有时还是会得逞。任何导致违反机密性、完整性或可用性(CIA)安全原则的事件都是安全违规。

拒绝服务攻击

  • 拒绝服务(DoS)攻击会导致停机或用户无法访问系统。拒绝服务攻击影响了信息系统安全原则的有效性。DoS 攻击是通过占用计算机执行大量不必要的任务来拒绝服务的一种协同尝试。这种过度的活动使系统无法执行合法的操作
  • 两种常见的 DoS 攻击类型如下:
  • 逻辑攻击—逻辑攻击利用软件缺陷使远程服务器崩溃或严重影响其性能。您可以通过安装最新的补丁程序来保持您的软件最新,从而防止其中的许多攻击。
  • 泛洪攻击—泛洪攻击通过向机器发送大量无用的请求来淹没受害计算机的 CPU、内存或网络资源。
  • 大多数 DoS 攻击的目标是整个系统架构中的弱点,而不是软件错误或安全缺陷
  • 发起数据包泛洪的一种流行技术是 SYN 泛洪。
  • 抵御 DoS 攻击的最佳方法之一是使用入侵防御系统(IPS)软件或设备来检测和阻止攻击。

分布式拒绝服务攻击

  • DDoS 攻击在范围上不同于常规的 DoS 攻击。在 DDoS 攻击中,攻击者劫持数百甚至数千台互联网计算机,在这些系统上植入自动攻击代理。然后,攻击者指示特工用伪造的信息轰炸目标网站。这会使网站超载,阻止合法的流量。这里的关键是人数上的优势。攻击者通过将攻击分散到多台计算机来造成更大的损害。

窃听

  • 虽然术语窃听通常与语音电话通信有关,但攻击者也可以使用窃听来拦截数据通信。

  • 攻击者可以窃听电话线和数据通信线路。窃听可以是主动的,攻击者对线路进行修改。它也可以是被动的,即未经授权的用户只是收听传输而不改变内容。被动入侵可能包括复制数据以备后续主动攻击。

  • 主动窃听的两种方法如下:

  • 线间窃听—这种类型的窃听不会改变合法用户发送的消息,但会在合法用户暂停时在通信线路中插入其他消息。

  • 捎带式窃听—这种类型的窃听通过破坏通信线路并将消息路由到充当主机的另一台计算机来截取和修改原始消息。

秘密的

  • 软件开发人员有时会在他们的程序中包含隐藏的访问方法,称为后门。后门让开发人员或支持人员可以轻松访问系统,而不必与安全控制斗争。问题是后门并不总是隐藏的。当攻击者发现后门时,他或她可以利用它绕过现有的安全控制,如密码、加密等。合法用户使用用户 ID 和密码通过前门登录,攻击者使用后门绕过这些正常的访问控制。

恶意攻击

生日袭击

暴力密码攻击

字典密码攻击

  • 字典密码攻击是一种简单的攻击,它依赖于用户错误的密码选择。在字典密码攻击中,一个简单的密码破解程序从字典文件中获取所有单词,并通过输入每个字典条目作为密码来尝试登录。
  • 延伸阅读:https://capec.mitre.org/data/definitions/16.html

重放攻击

  • 重放攻击包括从网络中捕获数据包并重新传输它们以产生未经授权的效果。收到重复的、经过验证的 IP 数据包可能会中断服务或产生一些其他不希望的后果。当攻击者重用旧消息或旧消息的一部分来欺骗系统用户时,可以通过重放攻击来破坏系统。这有助于入侵者获取允许未经授权进入系统的信息。
  • 延伸阅读:study . com/academy/lesson/replay-attack-definition-examples-prevention . html

中间人攻击

  • 中间人攻击利用了许多类型网络使用的多跳过程。在这种类型的攻击中,攻击者在将消息传送到预期的目的地之前拦截双方之间的消息。
  • Web 欺骗是一种中间人攻击,在这种攻击中,用户认为与特定的 web 服务器存在安全会话。实际上,安全连接只存在于攻击者,而不存在于 web 服务器。然后,攻击者与 web 服务器建立一个安全的连接,充当一个看不见的中间人。攻击者在用户和 web 服务器之间传递流量。通过这种方式,攻击者可以欺骗用户提供密码、信用卡信息和其他私人数据。
  • 延伸阅读:
  • owasp . org/www-community/attacks/Man-in-the-middle _ attack

伪装

  • 在伪装攻击中,一个用户或计算机伪装成另一个用户或计算机。伪装攻击通常包括其他形式的主动攻击,如 IP 地址欺骗或重放。攻击者可以捕获身份验证序列,然后重放它们,以便再次登录到应用或操作系统。例如,攻击者可能会监视发送到脆弱的 web 应用的用户名和密码。然后,攻击者可以使用截获的凭据登录到 web 应用,并冒充用户。
  • 延伸阅读:dl.acm.org/doi/book/10.5555/2521792https://ieeexplore.ieee.org/document/1653228T3】

偷听

  • 当主机将其网络接口设置为混杂模式并复制经过的数据包以供以后分析时,就会发生窃听或嗅探。混杂模式使网络设备能够在给定的时间内拦截和读取每个网络数据包(当然,在某些情况下),即使数据包的地址与网络设备不匹配。可以附加硬件和软件来监控和分析该段传输介质上的所有数据包,而不会向任何其他用户发出警报。窃听的候选方法包括卫星、无线、移动和其他传输方法。

社会工程

  • 攻击者经常使用一种叫做社会工程的欺骗技术来获得对 IT 基础设施中资源的访问权。几乎在所有情况下,社会工程都包括欺骗授权用户为未授权用户执行操作。社会工程攻击的成功取决于人们想要提供帮助的基本倾向。

指控制电话系统的过程

  • Phone phreaking,或简称 phreaking,是一个俚语,用来描述研究、试验或探索电话系统、电话公司设备和连接到公共电话网络的系统的亚文化群体的活动。窃听是利用电话系统中存在的漏洞和故障的艺术。

网络钓鱼

  • 网络钓鱼是一种欺诈,攻击者试图诱骗受害者提供个人信息,如信用卡号、密码、出生日期、银行账号、自动柜员机(ATM)pin 和社会安全号码。

域名欺诈

  • 域欺骗是另一种类型的攻击,它试图通过域欺骗来获取个人或私人财务信息。然而,域欺骗攻击不使用消息来欺骗受害者访问看似合法的欺骗性网站。相反,域欺骗在域名服务器(DNS)上“毒害”域名,这一过程称为 DNS 中毒。结果是,当用户在他或她的地址栏中输入中毒服务器的网址时,该用户会导航到攻击者的站点。用户的浏览器仍然显示正确的网站,这使得域欺骗很难被检测到,因此也更严重。网络钓鱼试图通过电子邮件或即时消息一次欺骗一个人,而域欺骗使骗子能够通过域欺骗一次锁定一大群人。

第四部分:编写安全代码及更多

原文:https://linkedin.github.io/school-of-sre/level101/security/writing_secure_code/

减少安全性和可靠性问题的第一步也是最重要的一步是教育开发人员。然而,即使是训练有素的工程师也会犯错误,安全专家可能会编写不安全的代码,sre 可能会忽略可靠性问题。在构建安全可靠的系统时,很难同时考虑和权衡许多因素,尤其是如果您还负责开发软件的话。

在编写代码时使用框架来增强安全性和可靠性

  • 更好的方法是在通用框架、语言和库中处理安全性和可靠性。理想情况下,库只公开一个接口,使得编写具有常见安全漏洞类别的代码变得不可能。
  • 多个应用可以使用每个库或框架。当领域专家修复一个问题时,他们将它从框架支持的所有应用中移除,从而允许这种工程方法更好地扩展。

常见的安全漏洞

  • 在大型代码库中,尽管一直在努力教育开发人员并引入代码审查,但少数类造成了大多数安全漏洞。OWASP 和 San 发布了常见漏洞类别的列表

image26

编写简单的代码

尽量保持你的代码简洁明了。

避免多层嵌套

  • 多级嵌套是一种常见的反模式,会导致简单的错误。如果错误出现在最常见的代码路径中,单元测试很可能会捕捉到它。然而,单元测试并不总是检查多层嵌套代码中的错误处理路径。该错误可能导致可靠性降低(例如,如果服务在错误处理错误时崩溃)或安全漏洞(如错误处理的授权检查错误)。

消除 YAGNI 气味

  • 有时,开发人员通过添加将来可能有用的功能来过度设计解决方案,“以防万一”这违背了 YAGNI(你不会需要它)原则,该原则建议只实现你需要的代码。YAGNI 代码增加了不必要的复杂性,因为它需要被记录、测试和维护。
  • 总而言之,避免 YAGNI 代码可以提高可靠性,更简单的代码可以减少安全漏洞,减少出错的机会,减少开发人员维护未使用代码的时间。

偿还技术债务

  • 对于开发人员来说,用 TODO 或 FIXME 注释来标记需要进一步注意的地方是一种常见的做法。从短期来看,这种习惯可以加快最关键功能的交付速度,并允许团队提前完成期限——但是它也会招致技术债务。尽管如此,只要你有一个清晰的偿还这些债务的过程(并分配时间),这并不一定是一个坏习惯。

重构

  • 重构是保持代码库干净和简单的最有效的方法。即使是健康的代码库偶尔也需要
  • 不管重构背后的原因是什么,你都应该遵循一条黄金法则:永远不要在一次提交代码库中混合重构和功能变更。重构变更通常意义重大,并且可能难以理解。
  • 如果提交还包括功能变更,那么作者或评审者可能会忽略错误的风险就更高了。

单元测试

  • 单元测试可以通过在发布之前查明单个软件组件中的各种错误来提高系统的安全性和可靠性。这种技术包括将软件组件分解成更小的、自包含的、没有外部依赖性的“单元”,然后测试每个单元。

模糊测试

  • 模糊测试是一种补充前面提到的测试技术的技术。模糊化涉及使用模糊化引擎生成大量候选输入,然后通过模糊化驱动程序传递给模糊化目标。然后模糊器分析系统如何处理输入。由各种软件处理的复杂输入是模糊化的常见目标,例如,文件解析器、压缩算法、网络协议实现和音频编解码器。

集成测试

  • 集成测试超越了单个单元和抽象,用真实的实现取代了抽象的虚假或废弃的实现,如数据库或网络服务。因此,集成测试使用了更完整的代码路径。因为您必须初始化和配置这些其他依赖项,所以集成测试可能比单元测试更慢、更繁琐——为了执行测试,这种方法结合了真实世界的变量,如服务端到端通信时的网络延迟。当您从测试单个低级代码单元转移到测试它们在组合在一起时如何交互时,最终结果是对系统按预期运行有了更高的信心。

最后但并非最不重要

  • 代码审查
  • 依靠自动化
  • 不要签入秘密
  • 可验证的构建

总结

原文:https://linkedin.github.io/school-of-sre/level101/security/conclusion/

现在,您已经完成了本安全课程,您已经了解了计算机系统和网络可能面临的安全威胁。不仅如此,您现在还可以更好地保护您的系统,并向他人推荐安全措施。

本课程提供安全领域的基本日常知识,还将帮助您将安全放在第一位。

其他资源

一些书会是很好的资源

培训后提问/进一步阅读

102 级

Linux 中级

Linux 中级

原文:https://linkedin.github.io/school-of-sre/level102/linux_intermediate/introduction/

先决条件

从本课程中可以期待什么

本课程分为两个部分。在第一部分中,我们将讲述在 SRE 学校课程的早期,我们离开 linux 基础知识的地方,我们将深入探究一些更高级的 Linux 命令和概念。

在第二部分中,我们将讨论如何在日常工作中使用 Bash 脚本,通过任何 SRE 的真实例子的帮助,将自动化和辛劳减少作为 SRE。

本课程不包括哪些内容

本课程旨在让你熟悉 Linux 命令、shell 脚本的交集以及 SRE 如何使用它。我们不会讨论 Linux 的内部机制。

实验室环境设置

* 我们将使用 RedHat Enterprise Linux (RHEL) 8。

  • 我们将运行上述 docker 容器中的大多数命令。

课程内容

套餐管理

存储介质

  • 列出已挂载的存储设备

  • 创建文件系统

  • 安装设备

  • 卸载设备

  • 用/etc/fstab 文件变得更简单?T3】

  • 检修 FS

  • 突袭

    * RAID 1(镜像)

    <u>*   [<u>RAID 5(带分布式奇偶校验的条带化)</u>](https://linkedin.github.io/school-of-sre/level102/linux_intermediate/storage_media/#raid-5striping-with-distributed-parity)
    
    *   [<u>RAID 6(双奇偶校验分条)</u>](https://linkedin.github.io/school-of-sre/level102/linux_intermediate/storage_media/#raid-6striping-with-double-parity)
    
    *   [<u>RAID 10(RAID 1+0:镜像和分条)</u>](https://linkedin.github.io/school-of-sre/level102/linux_intermediate/storage_media/#raid-10raid-10-mirroring-and-striping)
    
    *   [<u>命令监视突袭</u>](https://linkedin.github.io/school-of-sre/level102/linux_intermediate/storage_media/#commands-to-monitor-raid)</u></u> 
    

* LVM

归档和备份

Vim 简介

Bash 脚本

结论

包管理

原文:https://linkedin.github.io/school-of-sre/level102/linux_intermediate/package_management/

介绍

任何操作系统的一个主要特征是能够运行其他程序和软件,因此包管理就出现了。软件包管理是一种在任何操作系统上安装和维护软件程序的方法。

包裹

在 Linux 的早期,人们必须下载任何软件的源代码,并编译它来安装和运行软件。随着 Linux 领域变得更加成熟,人们知道软件领域是非常动态的,并且开始以包的形式分发软件。软件包文件是文件的压缩集合,包含软件、其依赖项、安装说明和关于软件包的元数据。

属国

软件包很少是独立的,它依赖于不同的软件、库和模块。这些子程序以共享库的形式存储和提供,共享库可以服务于多个程序。这些共享资源被称为依赖关系。包管理完成了这项艰难的工作,为用户解决依赖关系并随软件一起安装它们。

贮藏室ˌ仓库

存储库是存储所有软件包、更新和依赖项的存储位置。每个存储库可以包含远程服务器上托管的数千个软件包,用于在 linux 系统上安装和更新。我们通常通过运行“ sudo dnf update”来更新包信息(通常称为元数据)。

尝试使用 sudo dnf repolist all 来列出所有的库。

我们通常添加存储库来安装来自第三方供应商的软件包。

dnf 配置-管理器-添加-回购 http://www.example.com/example.repo

高级和低级包管理工具

包管理工具主要有两种类型:

1.低级工具:主要用于安装包文件、移除包文件和升级包文件。

2.高级工具:除了低级工具,高级工具也做元数据搜索和依赖解析。

Linux 发行版 低级工具 高级工具
一种自由操作系统 dpkg 容易得到
软呢帽 未完成(did not finish) 未完成(did not finish)

存储媒体

原文:https://linkedin.github.io/school-of-sre/level102/linux_intermediate/storage_media/

介绍

存储介质是用来存储数据和信息的设备。在处理包括存储设备在内的外部设备时,Linux 有着惊人的能力。存储设备有很多种,物理存储设备如硬盘、虚拟存储设备如 RAID 或 LVM、网络存储等等。

在本节中,我们将学习如何使用任何存储设备,并根据我们的需求对其进行配置。

列出已装载的存储设备:

我们可以使用 mount 命令来列出所有挂载到你电脑上的存储设备。

我们看到的上面输出的格式是:

devicemount_pointfile\_system\_type (options)

例如,在第一行中,虚拟设备 sysfs 安装在 /sys 路径中,并且具有一个 sysfs 文件系统。现在让我们看看什么是文件系统以及如何创建文件系统。

创建文件系统

想象一个磁盘,其中存储在磁盘中的所有数据都是一个大块的形式,没有什么要弄清楚一块数据在哪里开始和结束,哪块数据位于整个数据块的哪个位置,因此文件系统就进入了画面。文件系统(fs)负责任何存储设备上的数据存储、索引和检索。

以下是最常用的文件系统:

f 型 描述
文件分配表(file allocation table) 文件分配表,最初用于 DOS 和 Microsoft Windows,现在广泛用于便携式 USB 存储
Windows NT 文件系统(NT File System) (新技术文件系统)用于微软基于 Windows 的操作系统
外面的(exterior 的简写) 为 Linux 系统设计的扩展文件系统。
ext4 第四个扩展文件系统是日志文件系统,通常由 Linux 内核使用。
超精结构(hyperfine structure) 分层文件系统,在 Mac OS 8.1 上引入 HFS+之前一直使用。
HFS+ 支持文件系统日志记录,支持系统崩溃后的数据恢复。
网络文件系统 最初来自 Sun Microsystems 的网络文件系统是基于 UNIX 的网络中的标准。

我们将尝试使用 mkfs 创建一个 ext4 文件系统,它是 linux 原生 fs。

Discalimer:在空磁盘上运行该命令,因为这将清除现有数据。

在这里,设备 /dev/sdb1 被格式化,其文件系统被更改为 ext4

安装设备:

在 Linux 系统中,所有的文件都是以(/)为根的树形结构排列的。挂载一个文件系统仅仅意味着让 Linux 目录树中的某个点可以访问这个文件系统。

我们需要一个挂载点(位置)来挂载上面格式化的设备。

我们创建了一个挂载点 /mount ,并使用 mount 命令来附加文件系统。这里的 -t 标志指定了什么是 fs 类型,之后是 /dev/sdb1 (设备名)和/mount(我们之前创建的挂载点)。

卸载设备:

现在让我们来看看如何卸载设备,如果我们有可移动存储介质,并希望安装到另一台主机上,这同样重要。我们使用 卸载 来卸载设备。

我们的第一次尝试没有卸载/sdb1,因为我们在存储设备内部,并且它正在被使用。一旦我们伸缩主目录,我们就能够成功地卸载设备。

使用/etc/fstab 文件会更容易吗?

在我们的生产环境中,我们的服务器可能有许多需要装载的存储设备,每次重新启动系统时使用命令装载每个设备是不可行的。为了减轻这一负担,我们可以利用 Linux 系统上的 /etc/fstab 中常见的名为“fstab”的配置表。

在这里的第一行中,我们将 /dev/mapper/rootvg-rootlv(存储设备)挂载在 /(根挂载点)上,它具有 xfs 文件系统类型,后跟选项。

我们可以运行 mount -a 来重新载入修改后的这个文件。

检查和修理 FS

文件系统在任何硬件故障、电源故障的情况下都会遇到问题,有时是由于不正确的关机。Linux 通常在启动时检查并修复损坏的磁盘。我们还可以使用命令fsck手动检查文件系统的损坏。

我们可以使用 fsck -y /dev/sdb1 修复同一个文件系统。

每种文件系统错误都附有错误代码,并返回活动错误的总和。

错误代码 描述
Zero 没有错误
one 文件系统错误已更正
Two 系统应该重新启动
four 未纠正的文件系统错误
eight 操作错误
Sixteen 用法或语法错误
Thirty-two 用户请求取消检查
One hundred and twenty-eight 共享库错误

在上面的 fs 检查中,我们得到了返回代码 12,这是错误代码 8(操作错误)和 4(未纠正的 FS 错误)的总和。

袭击

RAID 或“独立磁盘冗余阵列”是一种跨多个磁盘分布 I/O 以实现更高性能和数据冗余的技术。RAID 能够提高整体磁盘性能,并在磁盘出现故障时仍然存在。软件 RAID 使用计算机的 CPU 来执行 RAID 操作,而硬件 RAID 使用磁盘控制器上的专用处理器来管理磁盘。RAID 的三个基本功能是镜像、条带化和奇偶校验。

RAID 级别

下一节讨论了常用的 RAID 级别。关于所有 RAID 等级的信息,请参考 这里

RAID 0(条带化)

分条是将数据拆分成“块”并写入阵列中所有磁盘的方法。通过将数据分布在多个驱动器上,这意味着多个磁盘可以访问文件,从而提高了读/写速度。阵列中的第一个磁盘不会重复使用,直到等量的数据写入阵列中的其他每个磁盘。

优势

  • 这很容易实现。

  • 避免了由于来自同一磁盘的 I/O 操作而导致的瓶颈,从而提高了此类操作的性能。

不足之处

  • 它不提供任何冗余。如果任何一个磁盘出现故障,整个磁盘的数据都会丢失,并且无法恢复。

用例

RAID 0 可用于需要高速读取非关键数据的系统,例如视频/音频编辑站或游戏环境。

RAID 1(镜像)

镜像将数据副本写入阵列中的每个磁盘。这意味着数据的写入次数与阵列中的磁盘数一样多。它将所有数据的精确副本存储在单独的一个或多个磁盘上。正如预期的那样,与单个磁盘相比,这将导致较慢的写入性能。另一方面,读取操作可以并行进行,从而提高读取性能。

优势

  • RAID 1 提供了比 RAID 0 或单个磁盘更好的读取性能。

  • 它可以承受多个磁盘故障,而不需要特殊的数据恢复算法

不足之处

  • 由于数据复制,有效存储容量只有磁盘数量的一半,因此成本很高。

用例

要求低停机时间但对写入性能有轻微影响的应用。

RAID 4(带专用奇偶校验的条带化)

RAID 4 works 使用块级条带化(根据应用和要存储的数据,可以将数据条带化为各种大小的块)和用于存储奇偶校验信息的专用驱动器。每次将数据写入阵列磁盘时,都会通过算法生成奇偶校验信息。使用奇偶校验位是将校验和添加到数据中的一种方式,可以使目标设备确定数据是否已被正确接收。如果驱动器出现故障,可以反转算法,并根据剩余数据和奇偶校验信息生成缺失数据。

优势

  • RAID 4 阵列中的每个驱动器都独立运行,因此 I/O 请求并行发生,性能比以前的 RAID 级别更快。

  • 它可以承受多个磁盘故障,而不需要特殊的数据恢复算法

不足之处

  • 安装至少需要 3 张磁盘。

  • 它需要硬件支持奇偶校验计算。

  • 写入速度很慢,因为奇偶校验依赖于单个磁盘驱动器,并为每个 I/O 会话修改奇偶校验块。

用例

处理非常大的文件的操作—当使用顺序读写数据过程时

RAID 5(带分布式奇偶校验的条带化)

RAID 5 类似于 RAID 4,只是奇偶校验信息分布在阵列中的所有驱动器上。这有助于减少每次写入操作期间将奇偶校验信息写入单个驱动器时固有的瓶颈。RAID 5 是最常见的安全 RAID 级别。

优势

  • 与由于奇偶校验的计算而有点慢的写数据事务相比,读数据事务很快。

  • 因为存储控制器会在新驱动器上重建数据,所以即使在驱动器出现故障以及更换故障硬盘期间,数据仍可访问。

不足之处

  • RAID 5 最少需要 3 个驱动器,最多可支持 16 个驱动器

  • 它需要硬件支持奇偶校验计算。

  • 两个以上的驱动器故障会导致数据丢失。

用例

文件存储和应用服务器,如电子邮件、通用存储服务器等。

RAID 6(带双奇偶校验的条带化)

RAID 6 类似于 RAID 5,具有双重分布式奇偶校验的额外优势,可提供多达两个故障驱动器的容错能力。

优势

  • 读取数据事务速度很快。

  • 这提供了多达 2 个故障驱动器的容错能力。

  • RAID 6 比 RAID 5 更有弹性。

不足之处

  • 由于双重奇偶校验,写数据事务很慢。

  • 由于结构复杂,重建 RAID 阵列需要较长的时间。

用例

办公自动化、在线客户服务和需要极高可用性的应用。

RAID 10(RAID 1+0:镜像和条带化)

RAID 10 是 RAID 0 和 RAID 1 的组合。这意味着镜像和分条都在一个 RAID 阵列中。

优势

  • 重建 RAID 阵列的速度很快。

  • 读写操作性能良好。

不足之处

  • 就像 RAID 1 一样,只有一半的驱动器容量可用。

  • 实施 RAID 10 的成本可能很高。

用例

具有敏感信息的事务数据库,需要高性能和高数据安全性。

监视 RAID 的命令

命令cat /proc/mdstat将给出软件 RAID 的状态。让我们检查命令的输出:

Personalities : [raid1]

md0 : active raid1 sdb1[2] sda1[0]

10476544 blocks super 1.1 [2/2] [UU]

bitmap: 0/1 pages [0KB], 65536KB chunk

md1 : active raid1 sdb2[2] sda2[0]

10476544 blocks super 1.1 [2/2] [UU]

bitmap: 1/1 pages [4KB], 65536KB chunk

md2 : active raid1 sdb3[2]

41909248 blocks super 1.1 [2/1] [_U]

bitmap: 1/1 pages [4KB], 65536KB chunk 

“个性”为我们提供了 raid 配置的 raid 级别。在上面的例子中,raid 配置为RAID 1\. md0 : active raid1 sdb1[2] sda1[0]告诉我们在 sdb1(即设备 2)和 sda1(即设备 0)之间有一个 raid 1 的活动 RAID。非活动阵列通常意味着其中一个磁盘出现故障。上例中的 Md2 显示我们有41909248 blocks super 1.1 [2/1] [_U],这意味着在这个特定的 raid 中有一个磁盘出现故障。

命令mdadm --detail /dev/<raid-array>给出了关于该特定阵列的详细信息。

sudo mdadm --detail /dev/md0

/dev/md0:

Version : 1.1

Creation Time : Fri Nov 17 11:49:20 2019

Raid Level : raid1

Array Size : 10476544 (9.99 GiB 10.32 GB)

Used Dev Size : 10476544 (9.99 GiB 10.32 GB)

Raid Devices : 2

Total Devices : 2

Persistence : Superblock is persistent

Intent Bitmap : Internal

Update Time : Sun Dec 2 01:00:53 2019

State : clean

Active Devices : 2

Working Devices : 2

Failed Devices : 0

Spare Devices : 0

UUID : xxxxxxx:yyyyyy:zzzzzz:ffffff

Events : 987

Number Major Minor RaidDevice State

0 8 1 0 active sync /dev/sda1

1 8 49 1 active sync /dev/sdb1 

在上述示例中,如果磁盘丢失,raid 的状态将为“脏”,活动设备和工作设备将减少为一个。其中一个条目(/dev/sda1 或/dev/sdb1,具体取决于丢失的磁盘)会将其 RaidDevice 更改为故障。

LVM

LVM 代表逻辑卷管理。在上面的部分中,我们看到了如何以传统方式创建 FS 并根据我们的需求使用单个磁盘,但是使用 LVM,我们可以在存储分配方面实现更大的灵活性,例如,我们可以将三个 2TB 的磁盘拼接成一个 6TB 的单个分区,或者我们可以将另一个 4TB 的物理磁盘连接到服务器,并将该磁盘添加到逻辑卷组,使其总容量达到 10TB。

参考了解更多关于 https://www.redhat.com/sysadmin/lvm-vs-partitioning:

存档和备份

原文:https://linkedin.github.io/school-of-sre/level102/linux_intermediate/archiving_backup/

介绍

SREs 要确保的一件事是服务一直在运行(至少 99.99%的时间),但是运行这些服务的每台服务器生成的数据量是巨大的。这些数据可以是日志、数据库中的用户数据或任何其他类型的元数据。因此,为了数据安全,我们需要及时压缩、归档、轮换和备份数据,并确保我们不会耗尽空间。

归档

我们通常会将不再需要但主要出于合规性目的而保留的数据归档。这有助于将数据存储为压缩格式,节省大量空间。下一节是为了熟悉归档工具和命令。

gzip

gzip 是一个用来 压缩 一个或多个文件的程序,它用原始文件的压缩版本替换原始文件。

在这里,我们可以看到消息日志文件被压缩到原来大小的五分之一,并替换为 messages.gz。我们可以使用 gunzip 命令解压缩这个文件。

水手

tar 程序是一个将文件和目录归档到单个文件中的工具(通常称为 tarball)。该工具通常用于在将文件传输到长期备份服务器之前准备文件存档。 tar 不会替换现有的文件和文件夹,而是创建一个扩展名为的新文件。焦油。它提供了许多标志供选择存档

旗帜 描述
-丙 创建存档
[加在以-u 结尾的法语词源的名词之后构成复数] 提取存档文件
-f 用给定的文件名创建档案
相当于-ED 显示或列出存档文件中的文件
-你 归档并添加到现有的归档文件中
-v 显示详细信息
[构成动植物的古名或拉丁化的现代名] 连接存档的文件
-z 使用 gzip 压缩 tar 文件
-j 使用 bzip2 压缩 tar 文件
-W 验证存档文件
-r 更新或添加已存在的文件或目录。焦油文件

创建包含文件和文件夹存档

标志c用于创建档案,其中f是文件名。

列出归档中的文件

我们可以使用标志t来列出归档文件包含的内容。

从存档中提取文件

我们可以使用标志x来取消归档。

支持

备份是拷贝/复制现有数据的过程,该备份可用于在数据丢失的情况下恢复数据集。当数据在日常工作中并不需要,但可以作为事实的来源和未来的合规性原因时,数据备份也变得至关重要。不同类型的备份包括:

增量备份

增量备份是对自上次备份以来的数据进行备份,这降低了数据冗余和存储效率。

差异备份

有时我们的数据会不断修改/更新。在这种情况下,我们对自上次备份以来发生的更改进行备份,称为差异备份。

网络备份

网络备份是指在客户端-服务器模式下,通过网络将数据从源发送到备份目标。该备份目标可以是集中式的,也可以是分散式的。分散备份对于灾难恢复场景非常有用。

rsync是一个 linux 命令,通过网络将文件从一个服务器同步到目标服务器。

rsync 的语法类似于rsync \[options\] <source> <destination>。我们可以在“目的地”中的:(冒号)后指定的路径上找到文件。如果未指定任何内容,默认路径是用于备份的用户的主目录。/home/azureuser既然如此。您可以使用man rsync命令为 rsync 寻找不同的选项。

云备份

有各种第三方为云提供数据备份。这些云备份比本地机器或任何没有 RAID 配置的服务器上的存储备份可靠得多,因为这些提供商管理数据冗余、数据恢复以及数据安全性。两个最广泛使用的云备份选项是 Azure backup(来自微软)和 Amazon Glacier backup(来自 AWS)。

Vim 简介

原文:https://linkedin.github.io/school-of-sre/level102/linux_intermediate/introvim/

介绍

作为一个 SRE,我们会多次登录到服务器,对配置文件进行修改,编辑和修改脚本,编辑器是 Vim,它很方便,几乎在所有的 linux 发行版中都可以使用。Vim 是一个开源的免费命令行编辑器,被广泛接受和使用。我们将看到如何使用 vim 创建和编辑文件的一些基础知识。这些知识将帮助我们理解下一节,脚本。

打开文件并使用插入模式

我们用 vim filename 命令打开一个 filename 文件。终端会打开一个编辑器,但是一旦你开始写,它就不起作用了。这是因为我们在 vim 中没有处于“插入”模式。

按下 i ,进入插入模式,开始书写。

按下 i 后,你会在左下方看到“插入”。您可以使用* ESC键回到正常模式。

保存文件

在插入模式下插入文本后,按键盘上的 ESC(escape)键退出。按:(冒号 shift+;)并按下 w 并按回车键,您输入的文本将被写入文件。

退出 VIM 编辑器

对于初学者来说,退出 vim 会变得很有挑战性。有多种方法可以退出 Vim,比如不保存工作退出,保存工作退出。

退出插入模式并按下 : (冒号)后,尝试以下命令。

Vim 命令 描述
:q 退出文件,但如果文件有未保存的更改,则不会退出
:wq 写入(保存)并退出文件。
:问! 退出,不保存更改。

这是我们在下一节 bash 脚本中需要的基础。你可以随时访问教程了解更多。要快速练习 vim 命令,请访问:

Bash 脚本

原文:https://linkedin.github.io/school-of-sre/level102/linux_intermediate/bashscripting/

介绍

作为一名 SRE,Linux 系统是我们日常工作的核心,bash 脚本也是如此。它是一种脚本语言,由 Linux Bash 解释器运行。到目前为止,我们已经介绍了许多主要在命令行上的功能,现在我们将使用命令行作为解释器来编写程序,这将简化我们作为 SRE 的日常工作。

编写第一个 bash 脚本:

我们将从一个简单的程序开始,我们将在整个过程中使用 Vim 作为编辑器。

#!/bin/bash

# This if my first bash script
# Line starting with # is commented

echo "Hello world!" 

以“#”开头的脚本的第一行叫做 she-bang。这只是让系统在执行脚本时使用哪个解释器。

任何以“#”开头的行(除了#!)在脚本中被称为注释,在执行脚本时会被解释器忽略。第 6 行显示了我们将要运行的“echo”命令。

我们将这个脚本保存为“firstscript.sh ”,并使用chmod使脚本可执行。

下一件事是用显式路径运行脚本。我们可以看到期望的“Hello World!”作为输出。

接受用户输入并使用变量:

使用read命令获取标准输入,并在 bash 中使用变量。

#!/bin/bash

#We will take standard input
#Will list all files at the path
#We will concate variable and string

echo "Enter the path"
read path

echo "How deep in directory you want to go:"
read depth

echo "All files at path " $path

du -d $depth -all -h $path 

我们正在读取变量" path 和变量" depth 中的 path,以列出该深度的文件和目录。我们用变量连接字符串。我们总是使用$(美元符号)来引用它包含的值。

我们将这些变量传递给du命令,列出该路径中直到所需深度的所有文件和目录。

退出状态:

每个命令和脚本在执行完毕后,都会向系统返回一个 0 到 255 之间的整数,这称为退出状态。“0”表示命令成功,而非零返回代码通常表示各种错误。

我们使用$?特殊的 shell 变量来获取最后执行的脚本或命令的退出状态。

命令行参数和理解 If … else 分支:

向脚本传递一些值的另一种方法是使用命令行参数。通常 bash 中的命令行参数是通过$后跟索引来访问的。第 0 个索引表示文件本身,$1表示第一个参数,依此类推。我们使用$#来检查传递给脚本的参数的数量。

在编程语言中做决定是它不可或缺的一部分,为了处理不同的情况,我们使用 if … else 语句或它的一些嵌套变体。

下面的脚本在一个脚本中使用了多个概念。该脚本的目的是获取文件的一些属性。

第 4 到 7 行是 bash 中“if 语句”的标准示例。语法解释如下:

If [ condition ]; then

If_block_to_execute

else

else_block_to_execute

fi 

fi 将关闭 if … else 块。我们正在比较参数的计数($#)是否等于 1。如果没有,我们只提示一个参数,并以状态代码 1 退出脚本(不成功)。一个或多个 if 语句可以在没有 else 语句的情况下存在,但反之亦然没有任何意义。

运算符-ne 用于比较两个整数,读作“integer1 不等于 integer 2”。其他比较运算符有:

操作 描述
在 1 -eq 中在 2 中 检查第一个数字是否等于第二个数字
在 1-ge # 2 中 检查第一个数字是否大于或等于第二个数字
在 1 -gt num2 中 检查第一个数字是否大于第二个数字
num1 -le num2 检查第一个数字是否小于或等于第二个数字
在 1 -lt num2 中 检查第一个数字是否小于第二个数字
#!/bin/bash
# This script evaluate the status of a file

if [ $# -ne 1 ]; then
    echo "Please pass one file name as argument"
    exit 1
fi

FILE=$1
if [ -e "$FILE" ]; then
    if [ -f "$FILE" ]; then
        echo "$FILE is a regular file."
    fi
    if [ -d "$FILE" ]; then
        echo "$FILE is a directory."
    fi
    if [ -r "$FILE" ]; then
        echo "$FILE is readable."
    fi
    if [ -w "$FILE" ]; then
        echo "$FILE is writable."
    fi
    if [ -x "$FILE" ]; then
        echo "$FILE is executable/searchable."
    fi
else
    echo "$FILE does not exist"
    exit 2
fi

exit 0 

有很多文件表达式来评估文件,比如在 bash 脚本中,如果作为参数传递的文件存在,第 10 行中的"-e "返回 true,否则返回 false。以下是一些广泛使用的文件表达式:

文件操作 描述
-e 文件 文件存在
-d 文件 文件存在并且是目录
-f 文件 文件存在并且是常规文件
-L 文件 文件存在并且是符号链接
-r 文件 文件存在并且具有可读权限
-w 文件 文件存在并且具有可写权限
-x 文件 文件存在并且具有可执行权限
-s 文件 文件存在,并且大小大于零
-S 文件 文件存在,并且是网络套接字。

找不到文件时,退出状态为 2。如果找到该文件,它会打印出它保存的属性,退出状态为 0(成功)。

循环执行重复的任务。

我们通常会遇到重复性很强的任务,循环可以帮助我们以更正式的方式对这些重复性的任务进行编码。我们可以在 bash 中使用不同类型的循环语句:

句法
在…期间 【表情】

【while _ block _ to _ execute】

做了 |
| 为 | 对于 1,2,3 中的变量..n

【for _ block _ to _ execute】

完成 |
| 直到 | 【表情】

【直到 _ 阻止 _ 执行】

做好 |

#!/bin/bash
#Script to monitor the server

hosts=`cat host_list`

while true
do
    for i in $hosts
    do
        h="$i"
        ping -c 1 -q "$h" &>/dev/null
        if [ $? -eq 0 ]
        then
            echo `date` "server $h alive"
        else
            echo `date` "server $h is dead"
        fi
    done
    sleep 60
done 

监控服务器是成为 SRE 的一个重要部分。文件“host_list”包含我们要监控的主机列表。

我们使用了一个无限的“while”循环,每 60 秒休眠一次。对于 host_list 中的每台主机,我们都希望 ping 该主机,并检查 ping 是否成功及其退出状态,如果成功,我们就说服务器是活动的或者它是死的。

脚本的输出显示它每分钟都在运行,时间戳为。

功能

开发人员总是试图以模块化的方式开发他们的应用/程序,这样他们就不必每次在任何地方都编写相同的代码来执行类似的任务。函数帮助我们实现这一点。

我们通常用一些参数来调用函数,并根据这些参数来期望结果。

我们在前面的部分中讨论了备份过程,我们将尝试使用下面的脚本来自动化该过程,并熟悉一些更多的概念,如字符串比较、函数和逻辑 and 和 OR 操作。

在下面的代码中,“log_backup”是一个在被调用之前不会被执行的函数。

首先执行第 37 行,我们将检查传递给脚本的参数数量。

有许多逻辑运算符,如 AND、OR、XOR 等。

逻辑算子 标志
&&
运筹学 |

向脚本“backup.sh”传递错误的参数将提示正确的用法。我们必须传递我们是想要目录的增量备份还是完整备份,以及我们想要备份的目录的路径。如果我们需要增量备份,我们将增加一个参数作为元文件,用于存储以前备份文件的信息。(通常元文件是。snar 扩展)。

#!/bin/bash
#Scripts to take incremental and full backup

backup_dir="/mnt/backup/"
time_stamp="`date +%d-%m-%Y-%Hh-%Mm-%Ss`"

log_backup(){
    if [ $# -lt 2 ]; then
        echo "Usage: ./backup.sh [backup_type] [log_path]"
        exit 1;
    fi
    if [ $1 == "incremental" ]; then
        if [ $# -ne 3 ]; then
            echo "Usage: ./backup.sh [backup_type] [log_path] [meta_file]"
            exit 3;
        fi
        tar --create --listed-incremental=$3 --verbose --verbose --file="${backup_dir}incremental-${time_stamp}.tar" $2
        if [ $? -eq 0 ]; then
            echo "Incremental backup succesful at '${backup_dir}incremental-${time_stamp}.tar'"
        else
            echo "Incremental Backup Failure"
        fi

    elif [ $1 == "full" ];then
        tar cf "${backup_dir}fullbackup-${time_stamp}.tar" $2
        if [ $? -eq 0 ];then
            echo "Full backup successful at '${backup_dir}fullbackup-${time_stamp}.tar'"
        else
            echo "Full Backup Failure"
        fi
    else
        echo "Unknown parameter passed"
        echo "Usage: ./backup.sh [incremental|full] [log_path]"
        exit 2;
    fi
}

if [ $# -lt 2 ] || [ $# -gt 3 ];then
    echo "Usage: ./backup.sh [incremental|full] [log_path]"
    exit 1
elif [ $# -eq 2 ];then
    log_backup $1 $2
elif [ $# -eq 3 ];then
    log_backup $1 $2 $3
fi
exit 0 

传递增量备份的所有 3 个参数将在“/mnt/backup/”处进行增量备份,每个归档都有连接到每个文件的时间戳。

函数内部传递的参数可以通过索引后面的$来访问。第 0 个索引指的是函数本身,

$1到第一个论证,以此类推。我们使用#$来检查传递给函数的参数数量。

一旦我们传递了字符串“incremental”或“full ”,它就会在函数内部进行比较,并执行特定的块。下面是一些可以在字符串上执行的操作。

字符串操作 描述
string1 == string2 如果 string1 等于 string 2,则返回 true,否则返回 false。
string1!= string2 如果字符串不等于字符串 2,则返回 true,否则返回 false。
string1 ~= regex 如果 string1 与扩展正则表达式匹配,则返回 true。
-z 字符串 如果字符串长度为零,则返回 true,否则返回 false。
-n 字符串 如果字符串长度不为零,则返回 true,否则返回 false。

总结

原文:https://linkedin.github.io/school-of-sre/level102/linux_intermediate/conclusion/

理解软件包管理是非常重要的,作为一个 SRE,我们总是希望正确的软件集及其兼容版本能够和谐地工作,以驱动大型基础设施和组织。

我们还了解了如何配置和使用存储驱动器,如何使用 RAID 实现数据冗余以避免数据丢失,如何将数据放置在磁盘上以及如何使用文件系统。

存档和备份也是 SRE 的重要组成部分,我们有责任以更高效的方式保护数据安全。

Bash 对于自动化 SRE 人遇到的日常工作非常有用。上面的 bash 演练给了我们一个开始的想法,但是仅仅通读它不会让您走得更远。我相信“采取行动,实践主题”会给你信心,并帮助你成为一个更好的 SRE。

Linux 高级

容器化和编排

容器和编排

原文:https://linkedin.github.io/school-of-sre/level102/containerization_and_orchestration/intro/

介绍

容器、Docker 和 Kubernetes 是“很酷”的术语,以某种方式参与软件的每个人都在谈论它们。让我们深入研究每一项技术,了解整个交易的内容!

在这个模块中,我们将讨论容器的来龙去脉:容器的内部结构和用法;它们是如何实现的,如何容器化你的应用,最后,如何在不失眠的情况下大规模部署容器化的应用。我们还将通过尝试一些实验室练习来动手实践。

先决条件

  • linux 的基础知识将有助于理解容器的内部结构
  • shell 命令的基本知识(当我们将应用容器化时会派上用场)
  • 运行基本 web 应用的知识。你可以通过我们的 Python 和 Web 模块来熟悉这一点。

从本课程中可以期待什么

该模块分为 3 个子模块。在第一个子模块中,我们将讲述容器化的内部原理以及它们的用途。

第二个子模块介绍了流行的容器引擎 Docker,并包含对基本 webapp 进行 dockerizing 的实验练习。

最后一个模块讲述了 Kubernetes 的容器编排,并通过一些实验练习展示了它如何简化 SREs 的工作。

本课程不包括哪些内容

我们不会讨论高级 docker 和 kubernetes 概念。但是,我们将引导您找到链接和参考资料,您可以根据自己的兴趣选择它们。

课程内容

本课程涵盖了以下主题:

容器介绍

原文:https://linkedin.github.io/school-of-sre/level102/containerization_and_orchestration/intro_to_containers/

什么是容器

下面是一个流行的容器引擎 Docker 对容器的流行定义:

容器是一个标准的软件单元,它将代码及其所有依赖项打包,以便应用能够快速可靠地从一个计算环境运行到另一个计算环境

我们来分析一下。容器是与整个运行时环境捆绑在一起的代码。这包括运行应用所需的系统库、二进制文件和配置文件。

为什么是容器

您可能想知道为什么我们需要将您的应用与其依赖项打包在一起。这就是定义的第二部分,

...因此,应用可以快速可靠地从一个计算环境运行到另一个计算环境。

开发人员通常在他们的开发环境(或本地机器)中编写代码,在将代码投入生产之前,在一个或两个测试环境中进行测试。理想情况下,为了在推向生产之前可靠地测试应用,我们需要所有这些环境都统一到一个 tee(底层操作系统、系统库等)。

当然,这种理想很难实现,尤其是当我们混合使用本地(完全控制)和云基础架构提供商(在硬件控制和安全选项方面更具限制性)时,这种情况在今天更为常见。

这就是为什么我们不仅需要打包代码,还需要打包依赖项;以便您的应用能够可靠地运行,而不管它运行在哪个基础结构或环境上。

我们可以在一台主机上运行几个容器。由于容器的实现方式,每个容器在同一个主机中都有自己的隔离环境。这意味着一个单一的应用可以被分解成微服务并打包到容器中。每个微服务都在隔离环境中的主机上运行。这是使用容器的另一个原因:关注点分离

提供隔离的环境不会让一个容器中的一个应用的故障影响到另一个。这被称为故障隔离。由于容器中进程的有限可见性,隔离还提供了增加安全性的额外好处。

由于大多数容器化解决方案的实施方式,我们还可以选择限制容器内运行的应用消耗的资源量。这叫做资源限制。Will 将在 cgroups 一节中更详细地讨论这个特性。

虚拟机和容器的区别

让我们稍微跑题一下,进入一些历史。在上一节中,我们讨论了容器如何帮助我们实现关注点的分离。在广泛使用容器之前,虚拟化用于在同一主机上的隔离环境中运行应用(在某些情况下,今天仍在使用)。

简单来说,虚拟化就是我们将软件与运行该软件的操作系统副本打包在一起。这个包称为虚拟机(VM)。捆绑在虚拟机中的操作系统映像称为来宾操作系统。一个名为 Hypervisor 的组件位于来宾操作系统和主机操作系统之间,负责促进来宾操作系统对底层操作系统硬件的访问。您可以在此了解有关虚拟机管理程序的更多信息。

Virtual Machine Architecture

与在一台主机上运行多个容器类似,多台虚拟机可以在一台主机上运行,这样,就可以在一台单独的虚拟机上运行应用(或每个微服务),并实现关注点的分离。

这里主要关注虚拟机和容器的大小。虚拟机带有客户操作系统的副本,因此与容器相比重量更大。如果您对虚拟机和容器的比较更感兴趣,您可以查看来自 BackblazeNetApp 的这些文章。

虽然可以使用虚拟机管理程序(例如 CentOS 7 上的 Windows 10 虚拟机)在具有不兼容内核的主机上运行操作系统,但在内核可以共享的情况下(例如 CentOS 7 上的 Ubuntu),由于大小因素,容器优于虚拟机。共享内核,正如您将在后面看到的,也为容器提供了许多优于虚拟机的性能优势,比如更快的启动。让我们看看容器如何工作的图表。

Containers Architecture

比较这两个图,我们注意到两件事:

  • 容器没有单独的(客户)操作系统

  • 容器引擎是容器和主机操作系统之间的中介。它用于促进主机操作系统上容器的生命周期(然而,这不是必需的)。

下一节将详细解释容器如何与主机共享相同的操作系统(准确地说是内核),同时为应用的运行提供隔离的环境。

容器是如何实现的

我们已经讨论了容器如何与虚拟机不同,与主机操作系统共享相同的内核,并为应用运行提供隔离的环境。这是在没有在主机操作系统上运行客户操作系统的开销的情况下实现的,这要归功于 linux 内核的两个特性:cgroups 和内核名称空间。

既然我们已经触及了容器的内部,那么从技术上更准确地描述它们是什么是合适的。容器是一个 linux 进程或一组 linux 进程,它被限制在- 对容器外进程的可见性(使用名称空间实现)——它可以使用的资源数量(使用 cgroups 实现)以及- 可以从容器进行的系统调用。如果有兴趣了解更多信息,请参考 seccomp

这些限制使得容器化的应用与运行在同一主机上的其他进程保持隔离。

现在让我们更详细地讨论一下名称空间和 cgroup。

名称空间

容器内部流程的可见性应该被限制在容器内部。这就是 linux 名称空间的作用。这个想法是命名空间中的进程不能影响那些它不能“看到”的进程。共享单个命名空间的进程具有对它们所在的命名空间唯一的身份、服务和/或接口。以下是 linux 中的名称空间列表:

  • 挂载

共享一个挂载名称空间的进程组共享一组单独的、私有的挂载点和文件系统视图。对这些命名空间挂载点的任何修改在命名空间之外都是不可见的。例如,装载命名空间中的/var 可能与主机中的/var 不同。

  • PID

pid 命名空间中的进程具有仅在命名空间中唯一的进程 id。一个进程可以是它自己的 pid 名称空间中的根进程(pid 1 ),并且在它下面有整个进程树。

  • 网络

每个网络名称空间都有自己的网络设备实例,可以用单独的网络地址进行配置。同一网络命名空间中的进程可以有自己的端口和路由表。

  • 用户

用户名称空间可以有自己的用户和组 id。主机中使用非特权用户的进程可能在用户名称空间中拥有根用户身份。

  • Cgroup

允许创建只能在 cgroup 命名空间中使用的 cgroup。下一节将更详细地介绍 Cgroups。

  • UTS

这个名称空间有自己的主机名和域名 IPC。每个 IPC 名称空间都有自己的 System V 和 POSIX 消息队列。

尽管看起来很复杂,但在 linux 中创建名称空间非常简单。让我们看一个创建 PID 名称空间的快速演示。你需要一个基于 linux 的操作系统,并得到 sudoers 的许可。

演示:名称空间

  • 首先,我们检查哪些进程正在主机系统中运行(输出因系统而异)。注意 pid 1 的过程。

Fig 1

  • 让我们用 unshare 命令创建一个 PID 名称空间,并在名称空间中创建一个 bash 进程

Fig 2

您可以看到ps aux(它本身是在如此创建的 PID 名称空间中启动的进程)只能看到它自己的名称空间中的进程。因此,输出显示只有两个进程在名称空间内运行。还要注意,名称空间中的根进程(pid 1)不是 init,而是我们在创建名称空间时指定的 bash shell。

  • 让我们在相同的名称空间中创建另一个进程,它在后台休眠 1000 秒。在我的例子中,睡眠进程的 pid 是 PID 名称空间中的 44

Fig 3

Fig 4

  • 在单独的终端上,检查从主机看到的睡眠进程的进程 id。

Fig 5

请注意 pid 的差异(主机中为 23844,名称空间中为 44),尽管两者都指同一进程(开始时间和所有其他属性都相同)。

也可以嵌套命名空间,即从另一个 pid 命名空间创建一个 pid 命名空间。尝试使用sudo nsenter -t 23844 --pid -r bash重新输入名称空间,并在其中创建另一个 pid 名称空间。做起来应该很好玩!

Cgroups

可以将 cgroup 定义为一组进程,对这些进程的资源使用进行计量和监控。资源可以是内存页面、磁盘 i/o、CPU 等。事实上,cgroups 是根据对哪个资源施加限制以及违反限制时采取的操作的性质来分类的。

cgroup 中跟踪资源利用并控制 cgroup 中进程行为的组件称为资源子系统或资源控制器。

根据 RHEL 的对 cgroups 的介绍,下面是一组资源控制器及其功能:

  • blkio —该子系统对物理驱动器(磁盘、固态硬盘或 USB)等块设备的输入/输出访问进行限制。
  • cpu —该子系统使用调度程序为 cgroup 进程提供对 cpu 的访问。cpuacct —该子系统生成关于 cgroup 中进程使用的 CPU 资源的自动报告。
  • cpuset —该子系统将单个 CPU(在多核系统上)和内存节点分配给 cgroup 中的进程。
  • 设备 —该子系统允许或拒绝 cgroup 中的进程访问设备。
  • 冻结 —该子系统暂停或恢复 cgroup 中的进程。
  • 内存 —该子系统为 cgroup 中的进程设置内存使用限制,并自动报告这些进程使用的内存资源。

对于每个资源控制器,Cgroups 遵循一个分层的树状结构,即每个控制器都有一个 cgroup。层次结构中的每个 cgroup 从其父 cgroup 继承某些属性(如限制)。

让我们用 memory cgroups 来尝试一个快速演示,让我们的头脑理解上面的想法。您将需要一个基于 linux 的操作系统(这里是 RedHat ),并具有 sudo 权限。

演示:cgroups

  • 让我们从检查您的机器上是否安装了 cgroup 工具开始。执行mount | grep "^cgroup"。如果您安装了这些工具,您将看到如下输出:

Fig 1

如果没有,用sudo yum install libcgroup-tools -y安装工具。

  • 现在,我们创建一个名为 mem_group 的内存 cgroup,用“root”作为 cgroup 的所有者。执行的命令sudo cgcreate -a root -g memory:mem_group。验证是否创建了 cgroup。

Fig 2

/sys/fs/cgroup/<cgroup type>是伪文件系统,其中新创建的 cgroup 被添加为子组。

  • Memory cgroup 对 cgroup 中进程的内存使用进行限制。让我们看看 mem_group 的限制是什么。用于检查内存限制的文件是 memory.limit_in_bytes( 更多信息请点击这里,如果你感兴趣的话)。

Fig 3

  • 请注意,mem_group 继承了其父 cgroup 的限制

Fig 4

  • 现在,为了演示的目的,让我们将内存使用限制减少到 20KB(实际限制四舍五入到最接近的 2 的幂)。

Fig 5

这个限制太低了,因此大多数附加到 mem_group 的进程应该被 OOM 杀死。

  • 创建一个新的 shell 并将其附加到 cgroup。我们需要 sudo 权限。

Fig 6

进程如预期的那样被 OOM 杀死。您可以使用 dmesg 日志(mm_fault_error)来确认这一点。

如果你想在 cgroups 上尝试一个更深入的练习,可以看看 Geeks 为 Geeks 提供的教程。

让我们再次回到容器。容器与底层主机操作系统共享同一个内核,并为其中的应用提供一个隔离的环境。Cgroups 有助于管理容器内进程使用的资源,而 namespaces 有助于将一个容器中的网络堆栈、PID、用户、组 id 和挂载点与同一主机上运行的另一个容器隔离开来。

当然,容器还有更多真正使其功能完整的组件,但这些讨论超出了本模块的范围。

容器发动机

容器引擎简化了在主机上创建和管理容器的过程。怎么会?

  • 容器创建工作流通常从容器图像开始。容器映像是目标应用的打包的、可移植的版本,捆绑了它运行所需的所有依赖项。
  • 这些容器映像要么可以在主机(容器主机)上从以前的构建中获得,要么需要从远程映像存储库中获取。有时,容器引擎可能需要从一组指令中构建容器映像。
  • 最后,一旦获取/构建了容器映像,容器引擎就会解包映像,并根据映像规范为应用创建一个隔离的环境。
  • 容器映像中的文件随后被挂载到隔离环境中,以使应用在容器中启动并运行。

有几种容器引擎可用,如 LXC RKT 的 Docker(首批容器引擎之一),它们需要不同的图像格式(LXD 的 Docker)。OCI (Open Container Initiative)是由 Docker 发起的一个合作项目,旨在跨供应商标准化容器运行时规范和图像格式。如果你对这个项目感兴趣,OCI 的 FAQ 部分是一个很好的起点。我们将在下一节的中重点介绍 Docker。

Docker 容器化

原文:https://linkedin.github.io/school-of-sre/level102/containerization_and_orchestration/containerization_with_docker/

介绍

自 2013 年向公众发布以来,Docker 在其他容器引擎中获得了巨大的人气。以下是 Docker 如此受欢迎的一些原因:

  • 改进的便携性

Docker 容器可以以 Docker 映像的形式跨环境运输和运行,无论是本地机器、本地还是云实例。相比 Docker 容器,LXC 容器有更多的机器规格。- 重量更轻

与虚拟机映像相比,Docker 映像是轻量级的。例如,Ubuntu 18.04 虚拟机的大小约为 3GB,而 docker 映像的大小为 45MB!

  • 容器图像的版本控制

Docker 支持维护映像的多个版本,这使得查找映像的历史甚至回滚变得更加容易。

  • 图像的再利用

由于 Docker 图像是以层的形式出现的,所以一个图像可以用作构建新图像的基础。例如, Alpine 是常用作基础图像的轻量图像(5MB)。Docker 层使用存储驱动进行管理。

  • 社区支持

Docker hub 是一个容器注册中心,任何登录的人都可以上传或下载容器映像。流行的操作系统发行版的 Docker 镜像会在 docker hub 中定期更新,并获得大量的社区支持。

让我们看看在讨论 Docker 时出现的一些术语。

Docker 术语

  • Docker 图像

Docker 映像包含应用的可执行版本,以及应用作为独立容器运行所需的依赖项(配置文件、库、二进制文件)。可以理解为容器的快照。Docker 图像作为基础层之上的层出现。这些图层是版本化的图层。图层的最新版本是在基础图像上使用的版本。

docker image ls列出主机中存在的图像。

  • Docker 容器

Docker 容器是 docker 映像的运行实例。虽然图像是静态的,但是从图像创建的容器可以被执行并与之交互。这实际上是本模块前面部分中的“容器”。

docker run是用于从图像实例化容器的命令。

docker ps列出当前在主机上运行的 docker 容器。

  • Docker 文件

它是一个指令的纯文本文件,docker 引擎(确切地说是守护进程)基于它来组装图像。它包含关于基础图像的信息,以及要注入的环境变量。

docker build用于从 dockerfile 构建图像。

  • 坞站枢纽

这是 Docker 的官方图像容器注册表。任何拥有 docker 登录的用户都可以使用docker push将自定义图像上传到 Docker hub,并使用docker pull获取图像。

了解了基本术语之后,让我们看看 docker 引擎是如何工作的;如何解释 CLI 命令以及如何管理容器生命周期。

Docker 引擎的组件

让我们从 Docker 引擎的示意图开始,以便更好地理解:

Docker Engine Architecture

docker 引擎遵循客户端-服务器架构。它由 3 个部分组成:

  • Docker 客户端

这是用户直接与之交互的组件。当您执行我们之前看到的 docker 命令(push、pull、container ls、image ls)时,我们实际上是在使用 docker 客户端。一个 docker 客户端可以与多个 docker 守护进程通信。

  • REST API

为 docker 客户端和守护进程提供了一个通信接口。

  • 坞站守护程序(服务器)

这是 docker 引擎的主要组件。它从 dockerfile 构建图像,从 docker registry 获取图像,将图像推送到 registry,停止、启动容器等。它还管理容器之间的网络。

实验室

官方 docker github 为学习 docker 提供了几个级别的实验室。我们正在链接一个实验室,我们发现它非常适合从零开始的人。请按照以下顺序完成实验:

  1. 为实验室设置本地环境

  2. docker CLI 使用基础

  3. 创建并封装一个基本的 Flask 应用

这是另一个初级实验室,用于编写 MERN (Mongo + React + Express)应用,很容易上手。

Docker 的高级功能

虽然我们已经介绍了容器化的基础知识以及如何将一个独立的应用 dockerized 化,但是现实世界中的进程需要相互通信。这种需求在遵循微服务架构的应用中尤其普遍。

Docker networks

Docker 网络促进了运行在相同主机甚至不同主机上的容器之间的交互。docker network 命令提供了几个选项,用于指定容器如何与主机和其他容器进行交互。host选项允许与主机共享网络堆栈,bridge允许在同一主机上运行但不在主机外部的容器之间进行通信,overlay促进连接到同一网络的主机之间的容器之间的交互,以及macvlan为传统容器分配单独的 MAC 地址,这些是 Docker 支持的一些重要网络类型。然而,这超出了本模块的范围。在 docker networks 上的官方文档本身就是一个很好的起点。

除了映像、容器和网络,Docker 还提供了在容器中创建和挂载卷的选项。一般来说,docker 容器中的数据是非持久的,也就是说,一旦你取消了容器,数据就会丢失。卷用于在容器中存储持久数据。这个 Docker 实验室是开始玩体积的好地方。

在下一节中,我们将看到 Kubernetes 是如何协调容器部署的。

使用 Kubernetes 的编排

原文:https://linkedin.github.io/school-of-sre/level102/containerization_and_orchestration/orchestration_with_kubernetes/

介绍

现在我们终于到了最期待的部分:大规模运行和管理容器。到目前为止,我们已经看到了 Docker 如何帮助管理容器的生命周期,以及如何提高应用的可移植性。Docker 确实提供了一个解决方案来简化容器的大规模部署(如果感兴趣,您可以查看 Docker Swarm ),它与 Docker 容器集成得很好。然而,Kubernetes 已经成为在大型分布式环境中编排微服务(作为容器)管理的事实上的工具。

让我们来看看 SREs 对使用容器编排工具特别是 Kubernetes 的兴趣点。

使用 Kubernetes 的动机

  • 易用性

虽然 Kubernetes 有一个陡峭的学习曲线,但一旦学会,就可以作为一站式工具来管理您的微服务。只需一个命令,就可以部署成熟的生产就绪环境。应用的期望状态需要记录为 YAML 清单,Kubernetes 为您管理应用。

  • 确保资源的最佳利用

我们可以指定部署中每个容器使用的资源限制。我们还可以指定我们选择的节点,Kubernetes 可以在这些节点上调度要部署的节点(例如,具有高 CPU 消耗的微服务可以被指示部署在高计算节点中)。

  • 容错

自我修复是 Kubernetes 的基本资源类型。这消除了从头开始设计容错应用系统的麻烦。这尤其适用于无状态应用。

  • 与基础设施无关

Kubernetes 没有供应商锁定。它可以设置在多个云环境或本地数据中心。

  • 强大的社区支持和文档

Kubernetes 是开源的,有很多技术,比如运营商、服务网格等。由社区构建,用于更好地管理和监控 Kubernetes 编排的应用。

  • 可扩展和定制

我们可以构建我们的定制资源定义,这些定义符合我们管理应用的用例,并使用 Kubernetes 来管理它们(使用定制控制器)。

如果你对这个话题比较感兴趣,可以看看这篇文章

库伯内特斯的建筑

这里有一个图表(来自 Kubernetes 官方文档)包含了使 Kubernetes 工作的不同组件:

Kubernetes Architecture

Kubernetes 组件可以分为两部分:控制平面组件数据平面组件

Kubernetes 集群由一台或多台主机(称为节点)组成,Kubernetes 管理的容器在这些主机上运行。这构成了数据平面(或节点平面)。

Kuberentes 的大脑对来自节点平面的事件作出响应(例如创建一个 pod,副本不匹配)并进行主要编排,它被称为控制平面。所有控制平面组件通常安装在主节点中。这个主节点不运行任何用户容器。

kubernetes 组件本身作为包在 pod 中的容器运行(这是最基本的 Kubernetes 资源对象)。

  • 控制平面组件:
  • kube-apiserver
  • etcd
  • kube-scheduler
  • kube-控制器-管理器
  • 节点平面组件
  • 库伯莱
  • kube-proxy

此工作流程可能有助于您更好地理解组件的工作:

  • 一个 SRE 在他们的本地机器上安装kubectl。这是与 Kubernetes 控制平面(以及集群)交互的客户端。

  • 他们创建一个名为 manifest 的 YAML 文件,该文件指定了所需的资源状态(例如,名为“frontend”的部署需要 3 个 pod 才能始终运行)

  • 当他们发出命令创建基于 YAML 文件的对象时,kubectl CLI 工具向kube-apiserver发送一个 rest API 请求。

  • 如果清单有效,它将作为键值对存储在控制平面上的etcd服务器中。

  • 选择将容器放在哪个节点上(基本上是调度它们)

  • 有控制器进程(由kube-controller管理器管理)确保集群的当前状态等同于期望的状态(这里,3 个 pod 确实在集群中运行- >一切正常)。

  • 在节点平面侧,kubelet确保 pod 在本地保持运行状态。

实验室

先决条件

开始这项练习的最佳方式是使用 kubernetes lab 的游戏。

环境在 4 小时后被破坏。因此,如果您想恢复这些文件,请务必保存它们。对于持久的 kubernetes 集群,您可以在您的本地(使用 minikube )或者您可以在 Azure 、GCP 或任何其他云提供商创建一个 kubernetes 集群。

了解 YAML 有助于理解清单文件。

亲自动手

实验 1:

我们将创建一个名为 Pod 的对象,它是在 Kubernetes 中运行容器的最基本单元。这里,我们将创建一个名为“nginx-pod”的 pod,它包含一个名为“web”的 nginx 容器。我们还将在容器中公开端口 80,以便我们可以与 nginx 容器交互。将下面的清单保存在一个名为 nginx-pod.yaml 的文件中

apiVersion: v1                  #[1]
kind: Pod                       #[2]
metadata:                       #[3]
 name: nginx-pod                #[4]
 labels:                        #[5]
   app: nginx      
spec:                           #[6]
 containers:                    #[7]
   - name: web                  #[8]
     image: nginx               #[9]
     ports:                     #[10]
       - name: web              #[11]
         containerPort: 80      #[12]
         protocol: TCP          #[13] 

让我们简单了解一下这里有什么:

  • 种类:正在被创建的对象的“种类”。这是一个豆荚
  • #[1]-apiVersion:“Pod”资源的 API version。如果版本不同,yaml 文件中的值或键可能会有微小的变化。
  • #[3] -元数据:给出 pod 标签和名称的文件的元数据部分
  • 规格:这是定义 pod 内部事物的主要部分

这些不是随机的键值对!它们必须能够被 kubeapiserver 解释。您可以使用kubectl explain pod命令检查哪些键值对是可选的/强制的。一定要试一试!

  • 使用命令kubectl apply -f nginx-pod.yaml应用清单。这在 kubernetes 集群中创建了“nginx-pod”pod。

  • 使用kubectl get pod确认 pod 处于运行状态。

说明 nginx-pod 处于运行状态。1/1 表示容器内 1 个容器中有 1 个是健康的。

  • 为了检查在“nginx-pod”中运行的容器是否确实是“web ”,我们执行了kubectl describe pod/nginx-pod命令。这将给出一个冗长的输出,其中详细描述了 pod 以及自 pod 创建以来发生的事件。这个命令对于调试非常有用。我们关心的是:

您可以在 Containers 部分看到“web ”,图像为 nginx。这就是我们正在寻找的。

  • 我们如何访问 nginx“web”容器的欢迎页面?在 describe 命令中,您可以看到 pod 的 IP 地址。每个 pod 在创建时都会分配一个 IP 地址。

这里是 10.244.1.3

  • 从主机发出 curl 请求curl 10.244.1.3:80。您将看到欢迎页面!

  • 假设我们想在同一个 pod 中使用 nginx 的特定标签(比如 1.20.1 ),也就是说,我们想修改 pod 的某些属性。你可以尝试编辑 nginx-pod.yaml(图片:nginx:1.20.1 in #[9])并重新应用(步骤 2。).它将使用新图像在同一个 pod 中创建一个新容器。

在 pod 内创建了一个容器,但 pod 是相同的。您可以通过在 describe 命令中检查 pod 开始时间来验证。它会显示一个更古老的时代。

如果我们想把 1000 个 nginx pods 的图像改成 1.20.1 呢?退一步讲,如果我们想制造 1000 个 nginx pods 会怎么样。当然,我们可以写一个脚本,但是 Kubernetes 已经提供了一个名为“deployment”的资源类型来更好地管理大规模部署。


实验 2:

我们将进一步了解如何同时创建多个 nginx pod 实例。

  • 我们将首先在一个名为 nginx-deploy.yaml 的文件中创建并保存下面的清单
apiVersion: apps/v1
kind: Deployment             #[1]
metadata:
 name: nginx-deployment
 labels:
   app: nginx     
spec:
 replicas: 3                 #[2]
 selector:
   matchLabels:
     app: nginx              #[3]
 template:                   #[4]
   metadata:
     labels:
       app: nginx            #[5]
   spec:
     containers:
     - name: web
       image: nginx
       ports:
       - name: web
         containerPort: 80
         protocol: "TCP" 

您可以看到它类似于一个 pod 定义,直到 spec ( #[1]将 Deployment 作为种类,api 版本也不同)。

另一个有趣的观察是#[4]下的元数据和规范部分几乎与实验 1 中 Pod 定义下的元数据规范部分相同(请继续交叉检查)。这意味着我们正在部署 3 个类似于 Lab1 的 nginx pods。另外,matchLabels 中的标签应该与#[4]下的标签相同。

  • 现在使用kubectl apply -f nginx-deploy.yaml应用清单

验证确实创建了 3 个 pod。

如果你很好奇,检查一下kubectl get deploykubectl describe deploy nginx-deployment的输出。

  • 使用kubectl delete pod <pod name>删除 3 个 pod 中的一个。几秒钟后再次做kubectl get pod

你可以看到一个新的豆荚产生了,以保持豆荚的总数为 3(见年龄 15 与其他 27 分钟前创建的)!这是 Kubernetes 如何实现容错的演示。

这是 Kubernetes 部署对象的一个属性(从 Lab1 中删除 pod,它将不会被重新应用:)

  • 假设我们希望将 pod 的数量增加到 10 个。试用kubectl scale deploy --replicas=10 nginx-deployment

你可以看到 3/10 的豆荚比其余的都要老。这意味着 Kubernetes 增加了 7 个额外的单元,将部署规模扩大到 10 个。这显示了使用 Kubernetes 放大和缩小容器是多么简单。

  • 让我们将所有这些 pod 放在一个 ClusterIP 服务后面。执行kubectl expose deployment nginx-deployment --name=nginx-service

卷曲 10.96.114.184 对应的 IP。这个 curl 请求以循环方式到达部署“nginx-deployment”中的 10 个 pod 之一。当我们执行expose命令时,会发生这样的情况:创建一个集群 IP 类型的 kubernetes Service,以便可以通过一个本地 IP(这里是 10.96.114.184)访问该服务背后的所有 pods。

通过创建类型为 LoadBalancer 的服务,可以拥有一个公共 IP(即一个实际的外部负载平衡器)。请随意使用它!

上面的练习很好地展示了如何使用 Kubernetes 来管理大规模部署。相信我,这个过程与上面操作 1000 个部署和容器的过程非常相似!虽然部署对象对于管理无状态应用已经足够好了,但是 Kuberenetes 还提供了其他资源,如 Job、Daemonset、Cronjob、Statefulset 等。管理特殊用例。

额外实验室:https://kubernetes.courselabs.co/(与 Kubernetes 一起玩的大量免费后续练习)

高级主题

大多数情况下,与 Kubernetes 协调的微服务包含几十个资源实例,如部署、服务和配置。这些应用的清单可以使用舵模板自动生成,并作为舵图表传递。类似于我们如何为 python 包提供 PiPy,还有像 Bitnami 这样的远程存储库,在那里可以下载和使用 Helm charts(例如,只需单击一下就可以设置一个生产就绪的 Prometheus 或 Kafka)。这是一个开始的好地方。

Kuberenetes 提供了创建定制资源的灵活性(类似于我们看到的部署或 Pod)。例如,如果你想创建一个资源的 5 个实例,你可以!唯一的问题是你必须为它编写自定义资源。您还可以为自定义资源构建一个自定义操作符,以便对资源实例执行某些操作。你可以查看这里的了解更多信息。

总结

原文:https://linkedin.github.io/school-of-sre/level102/containerization_and_orchestration/conclusion/

在这个子模块中,我们从为什么使用容器、容器是如何从虚拟机演变而来的(尽管它们绝不是过时的)以及它们与虚拟机的不同之处开始,浏览了容器的世界。然后我们看到了容器是如何实现的,重点是 cgroups 和 namespaces 以及一些实践练习。最后,我们以容器编排结束了我们的旅程,我们通过一些实际例子学习了一些 Kubernetes。

希望本模块能给你足够的知识和兴趣来继续深入学习和应用这些技术!

系统调用和信号

系统调用和信号

原文:https://linkedin.github.io/school-of-sre/level102/system_calls_and_signals/intro/

先决条件

从本课程中可以期待什么

本课程涵盖了对信号和系统调用的基本理解。它揭示了信号和系统调用的知识如何对 SRE 有所帮助。

本课程不包括哪些内容

本课程不讨论除信号之外的任何其他中断或中断处理。本课程不会深入探讨信号处理器和 GNU C 库。

课程内容

信号

原文:https://linkedin.github.io/school-of-sre/level102/system_calls_and_signals/signals/

中断和信号介绍

中断是一种改变程序正常执行流程的事件,可以由硬件设备甚至 CPU 本身产生。当中断发生时,当前的执行流被挂起,中断处理程序运行。在中断处理程序运行后,先前的执行流被恢复。有三种类型的事件会导致 CPU 中断:硬件中断、软件中断和异常。

信号只不过是软件中断,通知一个进程一个事件已经发生。这些事件可能是来自用户的请求,也可能是发生了系统问题(如内存访问错误)的指示。每个信号都有一个信号编号和一个定义的默认动作。流程可以通过以下任何一种方式对它们做出反应:

  • 默认(操作系统提供的)方式
  • 捕捉信号并以程序定义的方式处理它们
  • 完全忽略信号

信号类型

要列出 Linux 系统中可用的信号,可以使用命令kill -l。下表列出了信号 1 至 20。要获得完整的信号列表,您可以参考这里的

信号名称 信号编号 默认操作 意义
西格胡普 one 结束的 在控制终端上检测到挂起或控制进程死亡
信号情报 Two 结束的 键盘中断
西格奎特 three 核心转储 从键盘退出
-是啊 four 核心转储 非法指令
信号陷阱 five 核心转储 用于调试的跟踪/断点陷阱
西格奥特·西格伯特 six 核心转储 异常终止
SIGBUS seven 核心转储 总线错误
西格弗 eight 核心转储 浮点异常
西格基尔 nine 结束的 终止信号(无法捕捉或忽略)
西格 1 号 Ten 结束的 用户定义信号 1
西格尔瑟夫 Eleven 核心转储 无效的内存引用
西格玛瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁瑟鲁 Twelve 结束的 用户定义信号 2
信号管 Thirteen 结束的 管道破裂;写管道没有读者
信号 Fourteen 结束的 来自警报的定时器信号
是 SIGTERM Fifteen 结束的 过程终止
SIGSTKFLT Sixteen 结束的 数学协处理器上的堆栈错误
西格德 Seventeen 忽视 子进程停止或终止
西格孔特 Eighteen 继续 停止后继续
信号停止 Nineteen 停止 停止进程(不能被捕获或忽略)
SIGTSTP Twenty 停止 在 tty 处停止输入

向过程发送信号

有三种不同的方式向进程发送信号:

  • 使用 kill 向进程发送信号

Kill 命令可用于向进程发送信号。默认情况下,会发送一个 SIGTERM 信号,但是通过定义信号编号(或信号名称),可以向进程发送不同类型的信号。例如,命令kill -9 367将 SIGKILL 发送给 PID 为 367 的进程

  • 通过键盘向过程发送信号

通过按下一些特定的键,可以向正在运行的进程发送信号。例如,按住 Ctrl+C 向杀死它的进程发送 SIGINT。

  • 通过另一个进程向进程发送信号

一个进程可以通过 kill()系统调用向另一个进程发送信号。int kill(pid_t pid, int sig)系统调用有两个参数,您希望向其发送信号的进程的 pid 和所需信号的信号号。

处理信号

参考上一节中的信号表,您可以看到当程序启动时,所有信号都有默认的处理程序。当我们调用 signal 来附加我们自己的处理程序时,我们覆盖了程序响应该信号的默认行为。具体来说,如果我们给 SIGINT 附加一个处理程序,当你按下 +C(或者通过其他任何方式给程序发送一个 SIGINT)时,程序就不再终止;相反,将调用指定为处理程序的函数,该函数将定义程序响应该信号的行为。

让我们举一个处理 SIGINT 信号和终止一个程序的例子。我们将使用 Python 的信号库来实现这一点。

当我们按 Ctrl+C 时,SIGINT 信号被发送。从 signals 表中,我们看到 SIGINT 的默认动作是终止进程。为了说明流程如何对默认动作和信号处理程序做出反应,让我们考虑下面的例子。

SIGINT 的默认操作:

让我们首先在 python 环境中运行下面几行代码:

while 1:
        continue 

现在让我们按“Ctrl+C”。

在按下“Ctrl+C”时,SIGINT 中断被发送到进程,根据我们在上一节看到的表,SIGINT 的默认操作是终止进程。我们看到 while 循环被终止,并在控制台上看到以下内容:

^CTraceback (most recent call last):
  File "<stdin>", line 2, in <module>
  KeyboardInterrupt 

当我们按下 Ctrl+C 时,进程收到一个 SIGINT(键盘中断),因此终止(默认操作)。

SIGINT 的信号处理器:

让我们在 Python 环境中运行下面几行代码。

import signal
import sys

#Start of signal_handler function

def signal_handler(signal, frame):
        print ('You pressed Ctrl+C!')

# End of signal_handler function

signal.signal(signal.SIGINT, signal_handler) 

这是一个程序的例子,它为 SIGINT 定义了自己的信号处理程序,覆盖了默认的动作。

现在让我们像以前一样运行 while 和 continue 语句。

while 1:
        continue 

当按下 Ctrl+C 时,我们会看到任何变化吗?程序终止了吗?我们看到下面的输出:

^CYou pressed Ctrl+C! 

每次我们按 Ctrl+C,我们只是看到上面的消息,程序不会终止。为了终止程序,你可以按 Ctrl+Z 来发送 SIGSTOP 信号,它的默认动作是停止进程。

在信号处理程序的例子中,我们定义了一个函数 signal_handler() ,它打印“你按了 Ctrl+C!”并且不终止程序。调用这个处理程序有两个参数,信号号和当前堆栈帧(无或一个帧对象)。 signal.signal() 允许定义接收到信号时要执行的自定义处理程序。它的两个参数是您想要捕获的信号编号(名称)和信号处理程序的名称。

信号在系统调用中的作用,以 wait() 为例

wait() 系统调用等待调用进程的一个子进程终止,并在 statusPtr 指向的缓冲区中返回该子进程的终止状态。

  • 如果父进程调用了 wait() 系统调用,那么父进程的执行将被挂起,直到子进程被终止。
  • 在子进程终止时,会生成一个 SIGCHLD 信号,由内核传递给父进程。SIGCHLD 信号向父母表明,有一些关于孩子的信息需要收集。
  • 收到 SIGCHLD 后,父进程从进程表中获取子进程的状态。即使子进程被终止,进程表中仍有一个条目对应于存储进程条目和 PID 的子进程。
  • 当父收集状态时,该条目被删除。因此,子进程的所有痕迹都将从系统中移除。

如果父进程决定不等待子进程的终止,而是执行其后续任务,或者未能读取子进程的退出状态,则即使在子进程终止后,进程表中仍会保留一个条目。子进程的这种状态称为僵尸状态。为了避免持久的僵尸,我们需要有在子进程创建后调用 wait() 的代码。通常最好为 SIGCHLD 信号创建一个信号处理程序,在一个循环中调用一个 wait-family 函数,直到没有未收集的子数据。

如果子进程的父进程在子进程之前终止,子进程就会成为孤儿。所有进程的祖先 init/systemd(其进程 ID 为 1)收养了这个孤儿。进一步调用以获取该进程的父 pid 将返回 1。

系统调用

原文:https://linkedin.github.io/school-of-sre/level102/system_calls_and_signals/system_calls/

介绍

系统调用是进入内核的受控入口点,允许进程请求内核代表进程执行某些操作。内核通过系统调用应用编程接口(API)使程序可以访问一系列服务。应用开发人员通常不能直接访问系统调用,但是可以通过这个 API 访问它们。例如,这些服务包括创建新进程、执行 I/O 和创建进程间通信管道。系统调用集是固定的。每个系统调用都有一个唯一的编号。不同系统调用的列表可以在这里找到。

系统调用将处理器状态从用户模式更改为内核模式,因此 CPU 可以访问受保护的内核内存。每个系统调用可以具有一组参数,这些参数指定要从用户空间(即,进程的虚拟地址空间)传输到内核空间的信息,反之亦然。从编程的角度来看,调用系统调用看起来很像调用 C 函数。

系统调用的类型

主要有 5 种不同的系统调用。它们是:

  • 流程控制:这些系统调用用于处理与流程相关的任务,如流程创建、终止等。
  • 文件管理:这些系统调用用于文件操作,比如读/写文件。
  • 设备管理:这些系统调用用于处理设备,比如读取/写入设备缓冲区。
  • 信息维护:这些系统调用处理信息及其在操作系统和用户程序之间的传输。
  • 通信:这些系统调用对于进程间通信非常有用。它们还用于创建和删除通信连接。
系统调用的类型 Linux 中的例子
过程控制 fork(),exit(),wait()
文件管理 打开(),读取(),写入()
设备管理 ioctl(),read(),write()
信息维护 getpid(),alarm(),sleep()
沟通 pipe()、shmget()、mmap()

用户模式、内核模式及其转换

现代处理器架构通常允许 CPU 在至少两种不同的模式下运行:用户模式和内核模式。相应地,虚拟内存区域可以被标记为用户空间或内核空间的一部分。当在用户模式下运行时,CPU 只能访问标记为在用户空间中的内存;试图访问内核空间中的内存会导致硬件异常。

在任何给定的时间,进程可能在用户模式或内核模式下执行。可以执行的指令类型取决于模式,这是在硬件级别强制执行的。CPU 模式(也称为处理器模式、CPU 状态、CPU 特权级别)是一些计算机体系结构的中央处理单元的操作模式,其对由 CPU 运行的某些进程可以执行的操作的类型和范围进行限制。内核本身不是进程,而是进程管理器。内核模型假设需要内核服务的进程使用称为系统调用的特定编程结构。

当程序在用户模式下执行时,它不能直接访问内核数据结构或内核程序。然而,当应用在内核模式下执行时,这些限制不再适用。程序通常在用户模式下执行,只有在请求内核提供的服务时才切换到内核模式。如果应用需要访问系统上的硬件资源(如外设、内存、磁盘),它必须发出一个系统调用,这将导致从用户模式到内核模式的上下文切换。当从/向文件等读取/写入时,遵循该过程。在内核模式下运行的只是系统调用本身,而不是应用代码。当系统调用完成时,进程返回到用户模式,并使用反向上下文切换返回返回值。除了系统调用,内核例程也可以通过以下方式激活:

  • 执行该进程的 CPU 发出异常信号,这是一种异常情况,如无效指令。内核代表引发异常的进程处理异常。
  • 外围设备向 CPU 发出一个中断信号,通知它一个事件,如请求注意、状态改变或 I/O 操作完成。每个中断信号都由一个称为中断处理程序的内核程序处理。由于外围设备相对于 CPU 异步运行,中断会在不可预知的时间发生。
  • 执行内核线程。因为它运行在内核模式,相应的程序必须被认为是内核的一部分。

在上图中,用户模式下的进程 1 发出一个系统调用,之后进程切换到内核模式,系统调用得到服务。然后,进程 1 继续在用户模式下执行,直到定时器中断发生,并且调度程序在内核模式下被激活。发生进程切换,进程 2 在用户模式下开始执行,直到硬件设备发出中断。作为中断的结果,进程 2 切换到内核模式并处理该中断。

write() 系统调用的工作方式

系统调用 write() 将数据写入一个打开的文件。

# include <unistd.h>

ssize_t write(int fd, void *buffer, size_t count); 

buffer 是要写入的数据的地址; count 是从缓冲区写入的字节数;并且 fd 是指数据将被写入的文件的文件描述符。

write() 调用从缓冲区向由 fd 引用的打开文件写入多达计数字节。成功时, write() 调用返回实际写入的字节数,可能小于计数,出错时返回-1。当对磁盘文件执行 I/O 时,从 write() 成功返回并不保证数据已经传输到磁盘,因为内核执行磁盘 I/O 缓冲是为了减少磁盘活动并加快 write() 调用。它只是在用户空间缓冲区和内核缓冲区缓存中的缓冲区之间复制数据。稍后,内核会将其缓冲区写入(刷新)到磁盘。

如果在此期间,另一个进程试图读取文件的这些字节,那么内核会自动从缓冲区缓存中提供数据,而不是从文件(过时的内容)中提供数据。这种设计的目的是让 write() 更快,因为它们不需要等待(缓慢的)磁盘操作。这种设计也很有效,因为它减少了内核必须执行的磁盘传输次数。

用 strace 在 Linux 中调试

strace 是一个用来跟踪用户进程和 Linux 内核之间转换的工具。为了使用该工具,我们需要通过运行以下命令来确保它已安装在系统中:

$ rpm -qa | grep -i strace

strace-4.12-9.el7.x86_64 

如果上述命令没有给出任何输出,您可以通过以下方式安装该工具:

$ yum install strace

作为标准 C 库一部分的函数被称为库函数。这些函数的用途非常广泛,包括打开文件、将时间转换为人类可读的格式以及比较两个字符串等任务。一些库函数位于系统调用之上。通常,库函数被设计成提供一个比底层系统调用更友好的接口。例如, printf() 函数提供输出格式化和数据缓冲,而 write() 系统调用只输出一个字节块。Linux 上最常用的标准 C 库实现是 GNU C 库 glibc

C 编程语言提供了 printf() ,让用户以多种不同的格式编写数据。因此,printf()作为一个函数将您的数据转换成格式化的字节序列,并调用 write() 将这些字节写入输出。让我们看看当使用 strace 命令:strace printf %s “Hello world”执行 printf() 语句时会发生什么

 ~]$ strace printf %s "Hello world"
execve("/usr/bin/printf", ["printf", "%s", "Hello world"], [/* 47 vars */]) = 0
brk(NULL)                               = 0x90d000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8fc672f000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=98854, ...}) = 0
mmap(NULL, 98854, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f8fc6716000
close(3)                                = 0
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20&\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2156160, ...}) = 0
mmap(NULL, 3985888, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f8fc6141000
mprotect(0x7f8fc6304000, 2097152, PROT_NONE) = 0
mmap(0x7f8fc6504000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c3000) = 0x7f8fc6504000
mmap(0x7f8fc650a000, 16864, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f8fc650a000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8fc6715000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8fc6713000
arch_prctl(ARCH_SET_FS, 0x7f8fc6713740) = 0
mprotect(0x7f8fc6504000, 16384, PROT_READ) = 0
mprotect(0x60a000, 4096, PROT_READ)     = 0
mprotect(0x7f8fc6730000, 4096, PROT_READ) = 0
munmap(0x7f8fc6716000, 98854)           = 0
brk(NULL)                               = 0x90d000
brk(0x92e000)                           = 0x92e000
brk(NULL)                               = 0x92e000
open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=106075056, ...}) = 0
mmap(NULL, 106075056, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f8fbfc17000
close(3)                                = 0
open("/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=2502, ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8fc672e000
read(3, "# Locale name alias data base.\n#"..., 4096) = 2502
read(3, "", 4096)                       = 0
close(3)                                = 0
munmap(0x7f8fc672e000, 4096)            = 0
open("/usr/lib/locale/UTF-8/LC_CTYPE", O_RDONLY|O_CLOEXEC) = -1 ENOENT (No such file or directory)
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f8fc672e000
write(1, "Hello world", 11Hello world)             = 11
close(1)                                = 0
munmap(0x7f8fc672e000, 4096)            = 0
close(2)                                = 0
exit_group(0)                           = ?
+++ exited with 0 +++ 

execve("/usr/bin/printf ",["printf "," %s "," Hello world"],[/ 47 vars /]) = 0

第一个系统调用是 execve() ,它做了三件事:

  • 操作系统(OS)停止(父进程的)复制进程。
  • OS 加载新程序(本例中: printf() ),并启动新程序。
  • execve() 用从 printf 可执行文件加载的新内容替换当前进程内存堆栈的定义部分。

这一行的第一个单词 execve 是正在执行的系统调用的名称。第一个参数必须是二进制可执行文件或脚本的路径。第二个是传递给新程序的参数字符串数组。按照惯例,这些字符串的第一个应该包含与正在执行的文件相关联的文件名。第三个参数必须是环境变量。等号后面的数字(在本例中是 0)是 execve 系统调用返回的值,表示调用成功。

open("/usr/lib/locale/UTF-8/LC _ CTYPE ",O_RDONLY|O_CLOEXEC) = -1 ENOENT(没有这样的文件或目录)

在这一行,程序试图打开()文件/usr/lib/locale/UTF-8/LC _ CTYPE。然而,系统调用失败(状态为-1 ),并显示描述性错误消息没有这样的文件或目录,因为文件未找到(e not)。

brk(NULL) = 0x90d000

brk(0x92e000) = 0x92e000

brk(NULL) = 0x92e000

系统调用 brk() 用于增加或减少进程的数据段。它返回该进程的数据段将要结束的新地址。

open("/lib64/libc.so.6 ",O_RDONLY|O_CLOEXEC) = 3

阅读(3," \ 177 elf \ 2 \ 1 \ 1 \ 3 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 3 \ 0 > \ 0 \ 1 \ 0 \ 0 \ 0 \ 20 & \ 2 \ 0 \ 0 \ 0 \ 0 "..., 832) = 832

在上面几行控制台输出中,我们看到一个成功的 open() 调用,随后是 read() 系统调用。

open() 中,第一个参数是您想要使用的文件的路径,第二个参数定义了权限。在本例中,O_RDONLY 表示文件是只读的,而 O_CLOEXEC 为打开的文件启用 close-on-exec 标志。这有助于避免多线程程序中的竞争情况,即一个线程与另一个线程同时打开文件描述符。3 表示用于打开文件的文件描述符。因为 fd 0,1,2 已经被 stdin,stdout 和 stderr 占用了。因此,第一个未使用的文件描述符在文件描述符表中是 3。

如果打开()

read() 中,第一个参数是文件描述符,它是 3(文件是由 open() 使用这个文件描述符打开的)。第二个参数是从中读取数据的缓冲区,第三个参数是缓冲区的长度。返回值是 832,这是读取的字节数。

close(3) = 0

内核使用关闭系统调用来关闭文件描述符。对于大多数文件系统,程序使用关闭系统调用来终止对文件系统中文件的访问。=符号后的 0 表示系统调用成功。

写(1,“你好世界”,11 你好世界)= 11

在上一节中,我们描述了 write() 系统调用及其参数。每当我们在视频屏幕上看到任何输出,它都来自名为/dev/tty 的文件,并通过 fd 1 写入屏幕上的 stdout。第一个参数是文件描述符,第二个参数是包含要写入的信息的缓冲区,最后一个参数包含字符数。如果成功,将返回写入的字节数(零表示未写入任何内容),在本例中为 11。

+++用 0 ++退出

这表明程序成功退出,退出代码为 0。在 Linux 程序中,退出代码 0 通常表示成功执行和终止。

你不需要记住所有的系统调用或者它们做了什么,因为你可以在需要的时候参考文档。在运行 man 命令之前,请确保安装了以下软件包:

$ rpm -qa | grep -i man-pages

man-pages-3.53-5.el7.noarch 

使用系统调用名运行下面的man命令,查看该系统调用的文档(例如,execve):

man 2 execve

除了系统调用,strace 还可以用来检测程序正在访问的文件。在上面的跟踪中,我们有一个系统调用 open("/lib64/libc.so.6 ",O_RDONLY|O_CLOEXEC) = 3 ,它打开 libc 共享对象/lib64/libc.so.6,这是各种标准函数的 C 实现。在这个文件中,我们看到了打印 Hello World 所需的 printf() 定义。

Strace 也可以用来检查一个程序是否被挂起或卡住。当我们有了跟踪,我们也可以观察程序在哪个操作上被卡住了。此外,当我们进行跟踪时,我们还可以找到错误(如果有的话)来指出程序挂起/停滞的原因。Strace 可以非常有助于找到程序运行缓慢背后的原因。

尽管 strace 有上述的用途,但是如果你在生产环境中运行跟踪,strace 不是一个好的选择。它引入了大量的开销。根据 Red Hat 的高级软件工程师阿纳尔多·卡瓦略·德·梅洛进行的性能测试,使用 strace 跟踪的过程运行速度慢了 173 倍,这对生产环境来说是灾难性的。

总结

原文:https://linkedin.github.io/school-of-sre/level102/system_calls_and_signals/conclusion/

SRE 的主要目标之一是提高大规模系统的可靠性。为了做到这一点,对系统内部运作的基本理解是必要的。

了解信号是如何工作的非常重要,因为它们在流程的生命周期中扮演着重要的角色。我们看到信号在一系列流程操作中的使用:从创建流程到终止流程。信号知识非常重要,尤其是在程序中处理它们的时候。如果您预期某个事件会引起信号,您可以定义一个处理函数,并告诉操作系统在特定类型的信号到达时运行它。

在调试任何 Linux 进程时,理解系统调用对 SRE 特别有用。系统调用提供了操作系统内部功能的精确知识。它为程序员提供了对 C 库函数的深入理解,这些库函数在较低的层次上实现系统调用。使用 strace 命令,可以轻松调试缓慢或挂起的进程。

进一步阅读

www . oreilly . com/library/view/understanding-the-Linux/0596002130/ch01s 06 . html

jvns . ca/blog/2021/04/03/what-problems-do-people-solve-with-strace/

medium . com/@ akhandmishra/important-system-calls-every-programmer-should-know-8884381 ceadb

www . brendang regg . com/blog/2014-05-11/strace-wow-much-syscall . html

建立网络

先决条件

原文:https://linkedin.github.io/school-of-sre/level102/networking/introduction/

建议具备网络安全、TCP 和数据中心设置以及其中使用的常用术语的基本知识。此外,读者应该浏览学校的 Sre 内容-

从本课程中可以期待什么

这一部分将介绍如何针对不同的应用需求划分数据中心基础架构,以及在决定应用放置位置时需要考虑的事项。这些将主要基于安全性、规模、RTT(延迟)、基础架构功能。

这些主题中的每一个都将被详细讨论,

安全性-将涵盖面向外部/内部客户端的服务所面临的威胁媒介。部署时要考虑的潜在缓解选项。这将涉及外围安全、 DDoS 保护、网络划分和服务器集群的隔离。

规模—部署大规模应用需要更好地了解基础架构功能,包括资源可用性、故障域、使用任播、第 4/7 层负载平衡器、基于 DNS 的负载平衡等扩展选项。

RTT(延迟)-延迟在确定分布式服务/应用的整体性能方面起着关键作用,其中主机之间进行调用以服务于用户。

基础设施特性——需要考虑的一些方面包括,底层数据中心基础设施是否支持 ToR 弹性,例如,链路捆绑(绑定)、BGP(边界网关协议)、支持任播服务、负载平衡器、防火墙、服务质量等特性。

本课程不包括哪些内容

尽管这些参数在设计应用时起着一定的作用,但我们不会深入设计的细节。这些主题中的每一个都很庞大,因此目标是介绍其中参数的术语和相关性,而不是提供每个主题的详细信息。

课程内容

  1. 安全
  2. 刻度
  3. RTT
  4. 基础设施特征
  5. 结论

术语

在讨论每个主题之前,熟悉一些常用术语是很重要的

这是指来自不同提供商的托管解决方案,如 Azure、AWS 和 GCP。其中企业可以为公共或私人用途托管它们的应用。

内部部署

该术语指由企业自己构建和管理的物理数据中心(DC)基础设施。这既可用于私人访问,也可用于公共访问(如用户通过互联网连接)。

叶开关

这是指 DC 中服务器连接到的交换机。它们有许多名称,如接入交换机、架顶交换机、叶交换机。

术语叶交换机来自主干叶架构,其中的接入交换机称为叶交换机。主干叶架构通常用于大型/超大规模数据中心,它为 DC 交换层带来了非常高的可扩展性选项,并且在构建和实施这些交换机时也更加高效。有时这些被称为 Clos 体系结构。

脊柱开关

主干交换机是几个叶子交换机的聚合点,它们提供叶子之间的通信,并且还连接到 DC 基础设施的上层。

DC 织物

随着数据中心的增长,需要将多个 clo 网络互连起来以支持扩展,而光纤交换机有助于实现互连。

内阁

这是指安装服务器和 ToR 的机架。一个机柜指的是整个机架。

边界网关协议(Border Gateway Protocol)

它是边界网关协议,用于在路由器和交换机之间交换路由信息。这是互联网和数据中心常用的协议之一。其他协议也用来代替 BGP,如 OSPF。

虚拟专用网络

虚拟专用网是一种隧道解决方案,其中两个专用网络(如办公室、数据中心等)可以通过公共网络(互联网)互连。作为一种安全措施,这些 VPN 隧道在通过互联网发送流量之前对其进行加密。

国民保险分担

网络接口卡是指服务器中的模块,由以太网端口和与系统总线的互连组成。它用于连接交换机(通常是 ToR 交换机)。

流动

流指的是两个节点(可能是服务器、交换机、路由器等)之间的流量交换,它具有共同的参数,如源/目的 IP 地址、源/目的端口号、IP 协议号。这有助于两个节点之间的特定业务交换会话(如文件复制会话或 HTTP 连接等)的业务。

ECMP

等价多路径意味着交换机/路由器可以在多个送出接口之间将流量分配到目的地。流信息用于构建哈希值,并基于该哈希值选择送出接口。一旦数据流被映射到特定的送出接口,该数据流的所有数据包都只能通过同一接口送出。这有助于防止分组的无序传递。

循环时间(RTT)

这是对数据包从源设备到达目的设备并返回源设备所需时间的度量。这最常用于测量网络性能和故障排除。

TCP 吞吐量

这是衡量两个节点之间数据传输速率的指标。这受到许多参数的影响,如 RTT、数据包大小、窗口大小等。

单播

这是指单个源到单个目的地之间的流量,如 ssh 会话,其中存在一对一的通信。

任播

如上所述,这指的是一对一的流量,但是端点可以是多个(即,单个源可以向该组中的任何一个目的主机发送流量)。这是通过在多个服务器中配置相同的 IP 地址来实现的,并且每个新的流量都被映射到其中一个服务器。

多点传送

这指的是一对多流量(即单个源可以向多个目的地发送流量)。为了使其可行,网络路由器将流量复制到不同的主机(这些主机注册为特定多播组的成员)。

安全性

原文:https://linkedin.github.io/school-of-sre/level102/networking/security/

本节将涵盖面向外部/内部客户的服务所面临的威胁媒介。部署时要考虑的潜在缓解选项。这将涉及周边安全、DDoS 保护、网络划分和运营实践。

安全威胁

在任何基础设施中,安全性都是主要考虑因素之一。存在各种安全威胁,可能导致数据窃取、服务丢失、欺诈活动等。攻击者可以使用网络钓鱼、垃圾邮件、恶意软件、Dos/DDoS、利用漏洞、中间人攻击等技术。在本节中,我们将讨论其中一些威胁和可能的缓解措施。由于攻击和保护基础设施的方法很多,我们将只关注一些最常见的方法。

网络钓鱼大多通过电子邮件(和其他大众传播方式)进行,攻击者提供虚假网站/URL 的链接。在访问该网站时,受害者的敏感信息(如登录凭据或个人数据)会被收集并可能被滥用。

垃圾邮件也类似于网络钓鱼,但攻击者不会从用户那里收集数据,而是试图向特定网站发送垃圾邮件,很可能会淹没他们(导致速度变慢),并利用这个机会危及受攻击网站的安全。

恶意软件就像特洛伊木马,攻击者设法在基础设施中的安全系统上安装一段代码。利用这一点,黑客可以收集敏感数据,并感染目标公司的关键服务。

利用漏洞是攻击者获得系统访问权限的另一种方法。这些可能是 web 服务器、面向互联网的路由器/交换机/防火墙等中的错误或错误配置。

DoS/DDoS 是基于互联网的服务/解决方案中常见的攻击之一,尤其是那些基于眼球流量的业务。在这里,攻击者试图通过向面向外部的服务生成虚假流量来淹没受害者的资源。这样,主要是服务变得缓慢或无响应,在此期间,如果某些安全机制由于过载而无法过滤攻击流量,攻击者可能会试图侵入网络。

保护基础设施

任何基础设施管理的首要方面是识别可能影响在该基础设施上运行的业务的各种安全威胁。一旦知道了不同的威胁,就必须设计和实施安全防御机制。保护基础设施的一些常用方法有

周边安全

这是任何基础设施中的第一道防线,过滤/阻止不需要的/意外的流量流入基础设施。这些可以是边缘路由器中的过滤器,允许预期的服务(如在 HTTPS 上运行的 web 服务的端口 443 流量),或者如果服务不依赖于 UDP,则可以设置该过滤器来阻止不想要的流量,如阻止 UDP 端口。

类似于进入网络的应用流量,可能存在其他流量,如针对互联网对等体的 BGP 消息、VPN 隧道流量,以及其他服务,如电子邮件/DNS 等。有一些方法可以保护其中的每一个,例如对 BGP、VPN 的对等体使用身份验证机制(基于密码或密钥),以及将这些特定对等体列入白名单以进行入站连接(在边界过滤器中)。除此之外,可以将消息/流量的数量限制在已知的规模或预期的负载,这样资源就不会不堪重负。

DDoS 缓解

防范 DDoS 攻击是另一个重要方面。攻击流量看起来类似于真正的用户/客户端请求,但目的是淹没外部暴露的应用,可能是 web 服务器、DNS 等。因此,区分攻击流量和真实流量至关重要,为此,在应用级别有不同的方法,例如在 web 服务上使用 Captcha 来捕获源自僵尸程序的流量。

为了使这些方法有用,节点应该能够处理攻击流量和真实流量。在基于云的基础架构中,可以动态添加更多的虚拟机/资源,以应对流量的突然激增,但在内部,添加额外资源的选项可能具有挑战性。

为了处理大量的攻击流量,有一些可用的解决方案,这些解决方案可以检查数据包/流量流,并识别不像真正连接的异常(即)流量模式,如客户端发起 TCP 连接,但无法完成握手,或具有异常巨大流量流的一组源。一旦识别出这种不需要的流量,这些流量就会被丢弃在网络本身的边缘,从而保护应用节点的资源。可以更详细地讨论这个主题,但这超出了本节的范围。

网络划界

当应用根据其安全需求和易受攻击性进行分组时,网络划分是部署在不同网络中的另一种常见策略。一些常见的划分是,面向外部/互联网的节点被分组到一个单独的区域,而那些具有敏感数据的节点被隔离到一个单独的区域。并且在安全工具的帮助下限制这些区域之间的任何通信,以限制暴露给不需要的主机/端口。这些区间通信过滤器有时被称为隔离。要创建的区域数量因不同的部署而异,例如,可能有一台主机应该能够与外部世界以及内部服务器(如代理、电子邮件)进行通信,在这种情况下,这些可以分组到一个区域中,例如非军事化区域(DMZ)。创建区域的主要优势在于,即使有一台主机遭到破坏,也不会成为基础架构其余部分的后门入口。

节点保护

无论是服务器、路由器、交换机、负载平衡器、防火墙等,这些设备都具有某些保护自身安全的功能,例如支持过滤器(如访问列表、Iptables)来控制要处理的流量和要丢弃的流量,可以在服务器中使用防病毒软件来检查安装在服务器中的软件。

操作实践

基础设施面临众多安全威胁,有不同的解决方案来防御这些威胁。防御的关键部分不仅是确定正确的解决方案和工具,还要确保有可靠的操作程序,以便对任何安全事件做出迅速、果断和清晰的响应。

标准操作程序

SOP 需要明确定义,并作为安全事故期间随叫随到参考。本 SoP 应涵盖以下内容:

  • 当安全事故发生时,如何报警,向谁报警。

  • 确定安全事件的规模和严重性。

  • 谁是上报点以及传达上报点的界限/时间,可能有其他相关团队或管理层,甚至负责安全运营的人员。

  • 使用哪些解决方案(以及其中遵循的程序)来缓解安全事故。

  • 此外,有关安全事故的数据必须进行整理,以供进一步分析。

许多组织都有一个专注于安全的专门团队,他们在攻击期间甚至之前推动大多数活动,以提出最佳实践、指导方针和合规性审计。确保基础架构符合这些建议并修复差距是各个技术团队的责任。

定期审查

在定义 SoP 的同时,必须定期审查基础设施的整体安全性。该审查应包括:

  • 识别可能以基础设施为目标的任何新的/改进的安全威胁。

  • 根据新的安全威胁或程序中的变化,必须定期审查 SoP(以实施解决方案)

  • 确保及时完成软件升级/补丁。

  • 审核基础架构是否存在任何不符合安全标准的情况。

  • 审查最近的安全事件,并找到临时建立防御机制的方法。

规模

原文:https://linkedin.github.io/school-of-sre/level102/networking/scale/

部署大规模应用需要更好地了解基础设施的能力,包括资源可用性、故障域、扩展选项,如使用任播、第 4/7 层负载平衡器、基于 DNS 的负载平衡。

构建大规模的应用是一项复杂的活动,应该涵盖设计、开发和运营的许多方面。本节将讨论部署它们时需要考虑的事项。

故障域

在任何基础架构中,由硬件或软件问题导致的故障都很常见。虽然从服务可用性的角度来看,这可能是一个难题,但是这些故障确实会发生,一个实用的目标是尽量将这些故障降到最低。因此,在部署任何服务时,需要考虑一些节点的故障/不可用性。

服务器故障

由于电源、网卡或软件缺陷,服务器可能会出现故障。有时,它可能不是完全的故障,但可能是网卡中的错误,这会导致一些数据包丢失。这是一个非常常见的场景,对有状态服务的影响更大。在设计此类服务时,对这类故障提供一定程度的容忍度非常重要。

ToR 故障

这是一种常见的情况,连接服务器的边缘交换机发生故障,同时整个机柜也随之关闭。在这种情况下,同一服务可能会有多个服务器停机。它需要计划决定在不使其他服务器过载的情况下可以处理多少服务器丢失。基于此,服务可以分布在许多机柜中。根据 ToR 设计中的弹性,这些计算可能会有所不同,这将在 ToR 连通性一节中介绍。

站点故障

在这里,站点故障是一个通用术语,它可能意味着站点中的特定服务停止运行,这可能是由于新版本的推出,或者防火墙、负载平衡器等设备的故障(如果服务依赖它们),或者与远程站点的连接丢失(这可能具有有限的弹性选项),或者 DNS 等关键服务的问题。虽然这些事件可能并不常见,但它们可能会产生重大影响。

总之,在设计应用本身时,必须考虑如何处理这些故障场景。这将在应用中提供从意外故障中恢复所需的容差。这不仅有助于故障,甚至有助于计划的维护工作,因为这将更容易使部分基础设施停止服务。

资源可用性

大规模部署应用时要考虑的另一个方面是所需基础设施和服务所依赖的功能的可用性。例如,对于机柜的弹性,如果决定将服务分配给 5 个机柜,但服务需要负载平衡器(将传入的连接分配给不同的服务器),如果负载平衡器不是在所有机柜中都受支持,这可能会变得具有挑战性。或者可能存在没有足够的机柜可用(满足要设置的服务的最低要求规格)的情况。在这些情况下,最好的方法是确定需求和差距,然后与基础结构团队合作,以最好的方式解决它们。

缩放选项

将应用分发到不同的机柜时,这些服务的传入流量必须分布在这些服务器上。为了实现这一点,可以考虑以下几点

任播

这是在多个机柜间实现流量分配的最快方式之一。在这种情况下,作为集群一部分的每个服务器(在其中设置服务)向 DC 交换结构通告一个环回地址(/32 IPv4 或/128 IPv6 地址)(通常 BGP 用于此目的)。该服务必须设置为侦听此环回地址。当客户端尝试连接到服务时,解析到这个虚拟地址并转发它们的查询。DC 交换结构将每个流分发到不同的可用下一跳(最终分发到该服务群集中的所有服务器)。

注意:DC 交换机根据 IP 数据包报头计算哈希,这可能包括源地址和目的地址、源端口和目的端口、mac 地址和 IP 协议号的任意组合。基于这个散列值,选择特定的下一跳。由于通信流中的所有数据包都带有相同的报头值,因此该流中的所有数据包都将被映射到相同的路径。

Diagram Description automatically generated with mediumconfidence

图 1:选播设置

为了在这些服务器之间实现均衡的流量分配,保持每个机柜和机架的一致性非常重要。但是请记住,分发仅基于流,如果有任何大的流,一些服务器可能会收到更大的流量。

如果有任何服务器或 ToR 故障,将停止向交换机通告环回地址,从而新的数据包将被转发到剩余的可用服务器。

负载平衡

另一种常见的方法是使用负载平衡器。在负载平衡器中设置一个虚拟 IP,客户端在尝试访问服务时连接到该虚拟 IP。负载平衡器进而将这些连接重定向到运行服务的实际服务器之一。为了验证服务器是否处于可服务状态,负载平衡器会定期进行健康检查,如果检查失败,LB 会停止将连接重定向到这些服务器。

负载平衡器可以部署在单臂模式下,在这种模式下,到 VIP 的流量由 LB 重定向,从服务器到客户端的返回流量直接发送。另一个选项是双臂模式,返回流量也通过 LB。

Graphical user interface, application Description automaticallygenerated

图 2:单臂模式

Graphical user interface, application Description automaticallygenerated

图 3:双臂模式

这种方法的缺点之一是,在更高的规模下,负载平衡器可能会成为瓶颈,以支持更高的流量或每秒并发连接数。

基于 DNS 的负载平衡

这与上述方法类似,唯一的区别是负载平衡是在 DNS 上完成的,而不是在设备上。当客户端查询服务的 DNS 记录时,它们获得不同的 IP 来连接。DNS 服务器必须进行健康检查,以了解哪些服务器处于良好状态。

这种方法缓解了负载平衡器解决方案的瓶颈。但是 DNS 记录需要更短的 TTL,因此有问题的服务器可以快速退出轮换,这意味着将会有更多的 DNS 查询。

循环时间(RTT)

原文:https://linkedin.github.io/school-of-sre/level102/networking/rtt/

延迟在决定分布式服务/应用的整体性能中起着关键作用,其中在主机之间进行调用以服务于用户。

RTT 是时间的度量单位,它是数据包从 A 到达 B 并返回 A 所用的时间,以毫秒为单位。这一措施在确定服务的性能方面发挥了作用。它的影响体现在为用户服务的不同服务器/服务之间的调用,以及可以实现的 TCP 吞吐量。

服务多次调用其集群中的服务器或不同的服务(如身份验证、日志记录、数据库等)来响应每个用户/客户端请求是很常见的。这些服务器可以分布在不同的机柜中,有时甚至分布在同一地区的不同数据中心之间。这种情况在云解决方案中很有可能发生,在云解决方案中,部署分布在一个区域内的不同站点。随着 RTT 的增加,每个呼叫的响应时间变长,从而对发送给用户的最终响应产生级联效应。

RTT 与吞吐量的关系

RTT 与 TCP 吞吐量成反比。随着 RTT 的增加,它会降低 TCP 吞吐量,就像丢包一样。下面是一个基于 TCP mss、RTT 和包丢失来估计 TCP 吞吐量的公式。

Diagram, schematic Description automaticallygenerated

在数据中心内,这些计算对于互联网上的通信也很重要,在互联网上,客户可以通过不同的电信网络连接到 DC 托管的服务,并且由于互联网路由政策的不可预测性,RTT 不是很稳定。

基础设施服务

原文:https://linkedin.github.io/school-of-sre/level102/networking/infrastructure-features/

需要考虑的一些方面是,底层数据中心基础设施是否支持 ToR 弹性,即链路捆绑(绑定)、BGP、选播服务支持、负载平衡器、防火墙、服务质量等功能。

如前所述,要大规模部署应用,需要基础设施支持某些功能。本节将介绍不同的可用选项及其适用性。

ToR 连接

这是最常见的故障点之一(考虑到部署的规模),有不同的选项可以将服务器连接到 ToR。我们将在下面详细介绍它们,

一个

这是所有选项中最简单的。其中服务器的网卡连接到一个 ToR。这种方法的优点是,使用的交换机端口数量最少,允许 DC 结构支持服务器基础架构的快速增长(注意:不仅 ToR 端口得到有效利用,DC 结构中的上层交换层也是如此,端口使用效率也很高)。不利的一面是,如果 ToR、链接或 NIC 出现问题,服务器可能无法访问。这将对有状态应用产生更大的影响,因为现有的连接会突然断开。

Graphical user interface, application Description automaticallygenerated with mediumconfidence

图 4:单 ToR 设计

双重 ToR

在此选项中,每台服务器都连接到同一机柜的两个 ToR。这可以设置为主动/被动模式,从而在 ToR/链路/NIC 故障期间提供弹性。弹性可以在第 2 层或第 3 层实现。

第二层

在这种情况下,两个链路在服务器端作为绑定捆绑在一起(其中一个 NIC 扮演主动角色,另一个扮演被动角色)。在交换机端,这两个链路构成了多机箱延迟的一部分(类似于绑定,但分布在交换机上)。这里的先决条件是,两个 ToR 都应该是同一个第 2 层域的一部分。IP 地址配置在服务器的绑定接口和交换机端的 SVI 上。

Diagram Description automaticallygenerated

注意:在这种情况下,ToR 2 的作用只是提供弹性。

图 5:双 ToR 第 2 层设置

第三层

在这种情况下,两条链路都被配置为独立的第 3 层接口。弹性是通过建立路由协议(如 BGP)来实现的。其中一个链接被给予比另一个更高的优先级。在这种情况下,两个 ToR 可以在第 3 层模式下独立设置。服务器需要一个虚拟地址,服务必须绑定到这个虚拟地址。

Diagram Description automaticallygenerated

注意:在这种情况下,ToR 2 的作用只是提供弹性。

图 6:双 ToR 第 3 层设置

虽然双 ToR 的弹性更好,但缺点是使用的端口数量。随着 ToR 中的接入端口加倍,主干层中所需的端口数量也加倍,并不断级联到更高层。

类型 一个 双 ToR(第 2 层) 双 ToR(第 3 层)
弹性 1 2
端口使用 1:1 1:2 1:2
布线 较少的 更大的 更大的
DC 织物的成本 低的 高的 高的
所需的 ToR 功能 低的 高的 中等

1ToR/Link/NIC 方面的弹性

2 作为替代方案,弹性可以在应用层解决。

除了上面提到的那些,一个应用可能需要基础设施之外的更多功能来大规模部署。有些是,

任播

如前一节所述,在大规模部署中,任播是一种将服务分布在不同机柜中,同时仍有流量流向每台服务器的方法。要做到这一点,需要做两件事

  1. ToR 和服务器之间的路由协议(宣布任播地址)

  2. 支持基础架构中的 ECMP(等价多路径)负载平衡,以在机柜间分配流量。

负载平衡

与任播类似,另一种实现跨服务器负载平衡的方法(托管特定的应用)是使用负载平衡器。这些可以用不同的方式实现

  1. 硬件负载平衡器:LB 设备放置在通信流的内联中,并查看传入数据包中的第 3 层和第 4 层信息。然后确定连接将被重定向到的真实主机集。如 Scale 主题所述,这些负载平衡器可以通过两种方式进行设置,

    • 单臂模式:在这种模式下,负载平衡器只处理传入 VIP 的请求。来自服务器的响应直接发送到客户端。有两种方法可以实现这一点,

      • L2·DSR:负载均衡器和真正的服务器在同一个 VLAN。在收到一个传入的请求时,负载均衡器会识别真正的服务器来重定向该请求,然后修改该以太网帧的目的 mac 地址。在处理这个包时,真正的服务器直接响应客户机。

      • L3 DSR :在这种情况下,负载平衡器和真实服务器不需要在同一个 VLAN 中(消除第 2 层的复杂性,如运行 STP、管理更广泛的广播域等)。在收到请求时,负载平衡器通过修改数据包的目的 IP 地址重定向到真实服务器。与此同时,数据包的 DSCP 值被设置为预定义的值(为该 VIP 映射)。收到此数据包后,实际服务器使用 DSCP 值来确定回送地址(VIP 地址)。响应再次直接到达客户端。

    • 双臂模式:在这种情况下,负载均衡器会处理传入和传出的流量。

  2. 基于 DNS 的负载均衡器:在这里,DNS 服务器检查真实服务器的健康状况,并以这样的方式解析域,使得客户端可以连接到集群中的不同服务器。该部分在规模的部署章节中有详细解释。

  3. 基于 IPVS 的负载平衡:这是另一种方法,IPVS 服务器将自己作为服务端点呈现给客户端。收到请求后,IPVS 会将请求定向到真实的服务器。可以设置 IPVS 为真正的服务器运行状况。

精灵

需要连接到互联网上的目的地,但不想暴露其配置的 NIC 地址的主机将需要网络地址转换(NAT)。在这种情况下,防火墙会将(内部服务器的)地址转换为公共地址。代理服务器、邮件服务器等就是很好的例子。

服务质量

服务质量是一种为少数数据包提供区别对待的手段。这些可以在转发队列或带宽预留中提供优先级。在数据中心场景中,根据带宽预订比率,对 QoS 的需求会有所不同,

  1. 1:1 的带宽订阅比率:在这种情况下,服务器到 ToR 的连接(该机柜中的所有服务器)带宽应等于 ToR 到主干交换机的连接。对于上层也是类似的。在这种设计中,链路上不会发生拥塞,因为总会有足够的带宽可用。在这种情况下,QoS 能够带来的唯一区别是,它为转发队列中的某些数据包提供优先处理。注意:当数据包在不同速度(如 100Gbps、10Gbps)的端口之间移动时,会发生数据包缓冲。

  2. 超额预订网络:在这种情况下,并非所有层都保持带宽预订比率,例如,与 ToR 与服务器带宽相比,ToR 上行链路的带宽可能较低(这有时称为超额预订比率)。在这种情况下,有可能出现拥堵。这里可能需要 QoS,以便为某些类型的业务流提供优先级和带宽预留。

总结

原文:https://linkedin.github.io/school-of-sre/level102/networking/conclusion/

本课程将介绍在数据中心部署服务的一些背景知识,以及要考虑的各种参数和可用的解决方案。必须注意的是,这里讨论的每个解决方案都有不同的优点和缺点,因此对于特定的场景/需求,需要确定和使用其中的正确组合。由于我们在本课程中没有深入探讨各种技术/解决方案,这可能会引起读者对某些主题的好奇。下面是一些参考或网上培训的内容,供进一步学习。

链接工程博客:有关于 Linkedin 数据中心如何建立和一些关键问题如何解决的信息。

IPSpace 博客:有很多关于数据中心网络的文章。

edx 中的网络基础知识课程。

快乐学习!!

系统设计

系统设计

原文:https://linkedin.github.io/school-of-sre/level102/system_design/intro/

先决条件

从本课程中可以期待什么

目的是使读者能够理解设计良好的系统的构建模块,评估现有的系统,了解权衡,提出自己的设计,并探索实现这样一个系统的各种工具。在本模块的第一阶段,我们讨论了系统设计的基础,包括可扩展性、可用性和可靠性等概念。在这一阶段,我们将继续在这些基础上继续努力。

Throughout the course, there are callout sections that appear like this, and talk about things that are closely related to the system design process, but don’t form a part of the system itself. They also have information about some common issues that crop up in system design. Watch out for them.

本课程不包括哪些内容

虽然本课程涵盖了系统设计的许多方面,但并未涵盖最基本的概念。对于这类题目,建议先过一遍前提。

总的来说,本模块不会深入实际实施该架构——我们不会讨论选择托管/云提供商或编排设置或 CI/CD 系统。相反,我们试图把重点放在需要进入系统设计的基本考虑上。

课程内容

介绍

在本课程的前一阶段,我们讨论了如何构建基本的照片共享应用。我们对该应用的基本要求是

  1. 它应该适用于相当多的用户
  2. 避免出现任何问题时出现服务故障/集群崩溃

换句话说,我们想要建立一个可用的、可伸缩的和容错的系统。我们将继续设计该应用,并在设计过程中涵盖其他概念。

照片共享应用是一个 web 应用,它将处理从用户注册、登录、上传、feed 生成、用户交互以及与上传内容的交互的所有事情。还有一个数据库来存储这些信息。在最简单的设计中,web 应用和数据库可以在同一台服务器上运行。回想一下第一阶段的初始设计。

First architecture diagram

在此基础上,我们将讨论系统设计中的性能元素——设置正确的性能测量指标并使用它们来驱动我们的设计决策,使用缓存、内容交付网络(cdn)等来提高性能。我们还将通过一些系统设计模式——适度降级、超时和断路器——来讨论如何进行弹性设计。

费用

System design considerations like availability, scalability cannot exist in isolation. When operating outside the lab, we have other considerations / the existing considerations take on a different hue. One such consideration is cost. Real world systems almost always have budget constraints. System design, implementation and continued operation needs to have predictable costs per unit output. The output is usually the business problem you are trying to solve. Striking a balance between the two is very important.

了解您系统的功能

A well designed system requires understanding the building blocks intimately in terms of their capabilities. Not all components are created equal, and understanding what a single component can do is very important - for e.g., in the photo upload application it is important to know what a single database instance is capable of, in terms of read or write transactions per second and what would be a reasonable expectation be. This helps in building systems that are appropriately weighted - and will eliminate obvious sources of bottlenecks.

On a lower level, even understanding the capabilities of the underlying hardware (or a VM instance if you are on cloud) is important. For eg., all disks don’t perform the same, and all disks don’t perform the same per dollar. If we are planning to have an API that is expected to return a response in 100ms under normal circumstances, then it is important to know how much of it will be spent in which parts of the system. The following link will help in getting a sense of each component’s performance, all the way from the CPU cache to the network link to our end user.

Numbers every programmer should know

大型系统设计

原文:https://linkedin.github.io/school-of-sre/level102/system_design/large-system-design/

设计一个系统通常从抽象开始——我们有需要一起工作的大型功能块,并且被抽象成前端、后端和数据库层。然而,到了实施这一制度的时候,特别是作为一个 SRE 人,我们别无选择,只能具体问题具体考虑。服务器具有固定数量的内存、存储容量和处理能力。因此,我们需要考虑我们系统的现实期望,评估需求,将它们转化为系统每个组件(如网络、存储和计算)的具体需求。几乎所有的大型系统都是这样构建的。谷歌的人已经将这种设计系统的方法正式化为“非抽象大系统设计”(NALSD)。根据谷歌网站可靠性工作手册,

实际上,NALSD 结合了容量规划、组件隔离和适度系统降级等元素,这些元素对于高可用性生产系统至关重要

我们将使用类似的方法来构建我们的系统。

应用要求

让我们用更具体的术语来定义我们的应用需求,即特定的功能:

我们的照片分享应用必须让用户

  • 注册成为会员,并登录到应用

  • 上传照片,并可选地添加描述和标签位置和/或人

  • 关注平台上的其他用户

  • 查看由他们关注的其他用户的照片组成的源

  • 查看他们自己的个人资料页面,并管理他们关注的人

让我们定义对应用性能的期望,以获得更好的用户体验。我们还需要定义系统的健康状况。SLIs 和 SLOs 帮助我们做到了这一点。

SLIs 和 SLOs

谷歌 SRE 的书将服务水平指标(SLI)定义为“对所提供的服务水平的某个方面的一个精心定义的量化测量”对于我们的应用,我们可以定义多个 sli。一个指标可以是加载照片共享应用提要的响应时间。选择正确的 sli 集合非常重要,因为它们本质上帮助我们使用具体的数据来定义系统整体的健康状况。应用的 sli 由服务所有者与 SREs 协商后定义。

服务水平目标(SLO)被定义为“由 SLI 测量的服务水平的目标值或值范围”。SLO 是我们通过定义 SLI 边界来锚定最佳用户体验的一种方式。如果我们的应用需要很长时间来加载提要,用户可能不会经常打开它。因此,SLO 的一个例子是,至少 99%的用户应该在 1 秒钟内看到他们的提要被加载。

既然我们已经定义了 SLIs 和 SLO,那么让我们根据特定的 SLI 和 SLO 级别来定义应用的可伸缩性、可靠性和性能特征。

根据 SLIs 和 SLO 定义应用需求

以下是对我们的应用的一些期望:

  • 一旦用户成功上传图像,它应该可以访问用户和他们的追随者 100%的时间,除非用户选择删除。

  • 至少有 50000 个不同的访问者应该能够在任何给定的时间访问该网站,并查看他们的饲料。

  • 99%的用户应该能够在不到 1 秒的时间内查看他们的提要。

  • 上传一张新图片后,它应该会在 15 分钟内出现在用户关注者的反馈中。

  • 用户应该能够上传潜在的成千上万的图像。(只要他们没有滥用服务)

由于我们的最终目的是学习系统设计,我们将任意限制系统的功能。这将有助于我们看到我们的目标,并保持我们的重点。

定义了我们系统的功能和期望之后,让我们快速勾画出一个初始设计。

Initial Application Sketch

到目前为止,所有的功能都驻留在一台服务器上,该服务器拥有所有这些功能的端点。我们将尝试构建一个满足我们的 SLO 的系统,能够服务 50k 个并发用户和大约一百万个用户。在这个尝试的过程中,我们将讨论一系列的概念,其中一些我们已经在本课程的第一阶段看到过。

警告

Note that the numbers we have picked in the following sections are completely arbitrary. They have been chosen to demonstrate thinking about system design in a non-abstract manner. They have not been benchmarked, and bear no real world resemblance. Do not use them in any real world systems that you may be designing. You should come up with your own numbers, using the guiding principles we have relied upon here.

估计资源需求

单机

如果我们希望在一台服务器上运行应用,我们需要在这台服务器上执行图表中的所有上述功能。让我们进行一些计算,找出我们将需要什么样的资源。

首先,我们需要存储关于用户、他们的上传、追随者信息和任何其他元数据的数据。我们将选择一个关系数据库来存储这些信息,比如 MySQL。请注意,我们在这里也可以选择使用 NOSQL 解决方案。这将需要一个类似的方法来计算需求。让我们像这样表示用户:

UserID(int)
UserName(varchar)
DisplayName(varchar)
YearOfBirth(year)
Email(varchar) 

照片可以这样表示:

PhotoID(int)
PhotoHash(varchar)
Uploadtime(datetime)
Location(varchar)
OptionalFlag(varchar) 

追随者可以这样表示:

Follower(int)
Followee(int) 

让我们快速估算一下总共一亿用户所需的存储。单个用户将需要 4B + 32B + 32B + 4B + 32B = 104 字节。一亿用户将需要 10.4 GB 的存储。一张照片将需要大约 4B + 20B + 4B + 32B + 4B = 64 字节的存储空间来存储与该照片相关的元数据。假设一天上传 100 万张照片,我们每天需要大约 64 MB 的存储空间,仅用于元数据。对于照片存储本身,假设平均照片大小为 300KB,我们每天将需要大约 300GB。

打开我们的应用的单个访问者在登录应用时只需点击我们的/get_feed 端点。让我们快速计算一下满足这一要求所需的资源。假设初始提要加载了 5 个图像(平均大小为 300 KB ),然后进行惰性加载以无限滚动,我们将需要为用户的初始调用发送大约 1.5 兆字节的图像。使用 1000 Mbps网络链接到服务器,在网络链接饱和之前,我们只能同时发送大约(1000/8)/1.5 或大约 83 个用户加载提要。如果我们需要每秒为 50k 个并发用户提供服务,假设我们在一秒钟内发送完所有 5 个图像,则每发送 5 个图像需要 1.550000*8 = 600000 Mbps 的网络吞吐量。如果我们从磁盘中读取所有数据,我们可能会在接近这个流量之前就达到磁盘吞吐量极限。

因此,为了满足我们的应用要求,我们需要一台服务器,它具有大约 310GB 的存储空间来存储一天的数据库和图像,大约 600 Gbps 的链路来同时为 50k 个用户提供服务,以及执行所有这些操作所需的 CPU。显然不是单个服务器的任务。

请注意,我们已经严格限制了存储在数据库中的信息。我们可能需要存储多一个数量级的信息。

虽然我们显然没有任何真实世界的服务器具有我们上面计算的资源,但是这个练习为我们提供了一些关于资源成本的有价值的数据点。有了这些信息,让我们通过系统设计来扩展我们的系统,使我们尽可能接近应用的目标。

*现代服务器甚至有数千兆位的链路,但如此庞大的服务器不太可能单独为我们的应用服务。现代云提供商的虚拟机也拥有数千兆的带宽,但它们通常在达到一定限制后会受到限制。

参考资料:

  1. SQL vs NoSQL 数据库
  2. 引入非抽象大系统设计

扩缩容

原文:https://linkedin.github.io/school-of-sre/level102/system_design/scaling/

在本课程的第一阶段,我们已经看到了 AKF scale cube 以及它如何帮助细分服务、定义微服务和扩展整体应用。我们将使用类似的策略来扩展我们的应用——同时使用上一节中的估计,这样我们就可以有一个数据驱动的设计,而不是任意选择扩展模式。

拆分应用

考虑到我们的应用可能产生的巨大流量,以及内存和 CPU 方面的相关资源需求,让我们将应用分成更小的块。最简单的方法之一就是沿着端点划分应用,并将它们作为独立的实例。实际上,这个决定可能会稍微复杂一点,最终可能会有多个端点从同一个实例运行。

图像可以存储在一个可以独立扩展的对象存储库中,而不是放在应用或数据库所在的服务器上。这将减少服务器的资源需求。

有状态服务与无状态服务

A stateless process or service doesn’t rely on stored data of it’s past invocations. A stateful service on the other hand stores its state in a datastore, and typically uses the state on every call or transaction. In some cases, there are options for us to design services in such a way that certain components can be made stateless and this helps in multiple ways. Applications can be containerized easily if they are stateless. Containerized applications are also easier to scale. Stateful services require you to scale the datastore with the state as well. However, containerizing databases or scaling databases is out of the scope of this module.

经过这样的工作负载分配后,最终的设计可能如下所示。

Microservices diagram

您可能会注意到该图也有多个数据库。我们将在接下来的分片部分看到更多的内容。

既然我们已经将应用分割成了更小的服务,我们需要考虑扩展每个端点的容量。流行的帕累托原理指出“80%的结果来自 20%的原因”。稍微修改一下,我们可以说 80%的流量将用于 20%的图像。上传的图片数量与用户看到的图片数量之间也有类似的偏差。用户更有可能每天查看图片,而不是上传新图片。

在我们的简单设计中,生成包含最初 5 张图片的 feed 页面只需要从该用户关注的其他用户中选择 5 张最近上传的图片。虽然我们可以从数据库中动态获取图像,并在用户登录后即时生成页面,但如果大量用户选择同时登录并加载他们的提要,我们可能很快就会淹没数据库。这里我们可以做两件事,一件是缓存,另一件是提前生成用户提要。

一个拥有 100 万追随者的用户可能会导致数十万次调用 DB,只是为了获取用户上传的最新 photoID。这可能会很快压倒任何数据库,并有可能导致数据库本身崩溃。

分片

解决数据库限制问题的一种方法是扩大数据库读写。分片是扩展数据库写入的一种方式,其中数据库将被分割成多个部分,驻留在运行于不同机器上的不同数据库实例中。正如我们在本模块的第 1 阶段中看到的那样,通过使用读取副本,可以类似地扩展数据库读取。

与普通用户上传的图片数量相比,产生的浏览量将是巨大的。在这种情况下,我们应该缓存用户上传的 photoIDs,在不必执行潜在的昂贵的 DB 调用的情况下返回。

让我们考虑应用中另一个名为/get_user_details的端点。它只是返回一个用户点击另一个用户的名字时看到的页面。该端点将返回用户创建的帖子列表。通常,对该端点的调用将涉及应用与 DB 的对话,获取用户所有帖子的列表并返回结果。如果某人的个人资料被查看了数千次,这意味着有数千次对数据库的调用——这可能会导致热键和热分区之类的问题。与所有其他系统一样,负载的增加可能会导致响应时间恶化,从而导致不一致和潜在的糟糕用户体验。这里一个简单的解决方案是一个缓存层——它可以返回包含文章的用户配置文件,而不必每次都调用数据库。

贮藏

缓存用于临时存储可能会被再次访问(通常是重复访问)的数据。当在高速缓存中找到所请求的数据时,它被称为“高速缓存命中”。“缓存未命中”是明显的补充。定位良好的缓存可以大大减少查询响应时间,并提高系统的可伸缩性。缓存可以放置在用户和应用之间的多个级别。在第 1 阶段,我们看到了如何使用缓存/cdn 为应用的静态资源提供服务,从而加快响应速度,减轻应用服务器的负担。让我们看看缓存可以发挥作用的更多情况。

内存缓存:

在内存缓存中,要缓存的信息保存在服务器的主内存中,检索速度比驻留在磁盘上的数据库快得多。我们缓存任意文本(可以是 HTML 片段,也可以是 JSON 对象)并快速取回。内存中缓存是添加 fast cache 层的最快方式,也可以选择保留到磁盘。

While caching can aid significantly in scaling up and improving performance, there are situations where cache is suddenly not in place. It might be that the cache was accidentally wiped, leading to all the queries falling through to the DB layer, often multiple calls for the same piece of information. It is important to be aware of this potential ‘thundering herd’ problem and design your system accordingly.

缓存代理:

有些情况下,您可能希望缓存整个网页/您需要响应请求的其他上游资源的响应。也有这样的情况,你想让你的上游告诉你缓存什么,缓存多长时间。在这种情况下,拥有一个理解缓存相关 HTTP 头的缓存解决方案可能是一个好主意。我们用例的一个例子是,当用户在我们的应用中搜索一个特定的术语时——如果对一个用户或术语的搜索足够频繁,那么在一段时间内缓存响应可能比每次都重新执行搜索更有效。

让我们回顾一下其中一个目标——至少有 50000 个独立访问者应该能够在任何给定的时间访问网站并查看他们的提要。随着缓存的实现,我们消除了一个潜在的瓶颈——数据库。我们还将整体结构分解成更小的块,提供单独的服务。离我们的目标又近了一步,就是简单地横向扩展订阅源查看所需的服务,并将它们放在负载平衡器之后。请回忆一下本模块第 1 阶段中讨论的扩展概念。

缓存管理

While caching sounds like a simple, easy solution for a hard problem, an even harder problem is to manage the cache efficiently. Like most things in your system, the cache layer is not infinite. Effective cache management means removing things from the cache at the right time, to ensure the cache hit rate remains high. There are many strategies to invalidate cache after a certain time period or below certain usage thresholds. It is important to keep an eye on cache-hit rate and fine tune your caching strategy accordingly.

参考

  1. 有许多可用的对象存储解决方案。 Minio 是一种自托管解决方案。也有供应商特定的云解决方案,如 Azure Blob storage亚马逊 S3
  2. 微服务架构风格- Azure 架构指南
  3. 内存缓存解决方案有很多。一些最受欢迎的包括 redismemcached 。云供应商也有他们的托管缓存解决方案。
  4. 一些最流行的代理包括 squidApache 流量服务器
  5. 雷兽问题——insta gram如何解决

扩展到数据中心之外

原文:https://linkedin.github.io/school-of-sre/level102/system_design/scaling-beyond-the-datacenter/

缓存静态资产

稍微扩展一下现有的缓存解决方案,我们就可以实现内容交付网络(cdn)。cdn 是离用户最近的缓存层。网页中提供的大量资源可能不会每小时甚至每天都发生变化。在这种情况下,我们希望在 CDN 级别缓存这些内容,以减轻我们的负担。cdn 不仅通过消除服务静态/带宽密集型资源的负担来帮助减少我们服务器上的负载,它们还通过存在点(pop)让我们更接近我们的用户。CDNs 还允许我们进行地理负载平衡,以防我们在世界各地有多个数据中心,并希望尽可能从最近的数据中心(DC)提供服务。

更进一步

通过将应用缓存和分发到更简单的服务中,我们解决了扩展到 50000 个用户的问题。但是,我们的用户可能分布在不同的地理位置,并且可能与我们的数据中心或云区域的距离不同。用户体验的一致性很重要,否则我们会将远离我们位置的用户排除在外,潜在地消除了大量的潜在用户。然而,将数据中心分布在世界各地,甚至分布在世界上的几个地方,并不是不切实际的。这就是 cdn 和 pop 出现的原因。

存在点

CDN POPs 是地理上分布的数据中心,旨在靠近用户。pop 通过从离用户最近的位置传送内容来减少往返时间。pop 通常可能没有所有的内容,但是有缓存静态资产的缓存服务器,并从应用实际驻留的源服务器获取剩余的内容。它们的主要功能是通过使内容更接近网站的访问者来减少往返时间。pop 还可以将流量路由到可能的多个来源 DC 之一。这样,可以利用 pop 来增加弹性和负载平衡。

现在,随着我们的图像共享应用日益流行,让我们假设并发用户已经达到 100,000。我们已经建立了另一个数据中心,预计流量将会增加。现在,我们需要能够以可靠的方式将服务路由到这两个数据中心,同时保留在两个数据中心之一出现问题时回退到单个数据中心的能力。这就是粘性路由发挥作用的地方。

粘性路由

当用户发送请求时,如果我们有多个 DC,或者在 DC 中有一个特定的服务器,我们可能希望从 DC 为特定用户的请求提供服务。我们还可能希望由一个数据中心来满足特定 POP 的所有请求。粘性路由帮助我们做到了这一点。它可能只是将所有用户锁定到特定的 DC,或者将特定的用户锁定到特定的服务器。这通常是从 POP 完成的,所以一旦用户进入到达我们的服务器,我们可以将他们路由到最近的 DC。

地理 DNS

当用户打开应用时,用户可以被定向到多个全球分布的 pop 之一。这可以通过使用地理域名来完成,简单来说,根据发出 DNS 请求的用户的位置,给出不同的 IP 地址(按地理位置分布)。GeoDNS 是将用户分配到不同位置的第一步-它不是 100%准确的,并且通常利用 IP 地址分配信息来猜测用户的位置。然而,对于> 90%的用户来说,它已经足够好了。在这之后,我们可以有一个粘性路由服务,它为每个用户分配一个特定的 DC,我们可以用它为这个用户分配一个 DC,并设置一个 cookie。当用户下次访问时,可以在 POP 中读取 cookie,以决定用户的流量必须被定向到哪个数据中心。

拥有多个 DC 并利用粘性路由不仅具有扩展优势,还增加了服务的弹性,尽管代价是增加了复杂性。

让我们考虑另一个用例,用户为自己上传新的个人资料图片。如果我们有多个不实时同步的数据中心或 pop,并非所有的数据中心或 pop 都有更新的图片。在这种情况下,在更新传播到所有地区之前,将该用户绑定到特定的 DC/地区是有意义的。粘性路由将使我们能够做到这一点。

参考

  1. cdn
  2. LinkedIn 的 TrafficShift 博客谈论粘性路由

伸缩

原文:https://linkedin.github.io/school-of-sre/level102/system_design/resiliency/

一个有弹性的系统是一个在逆境中仍能保持运转的系统。对于我们的应用来说,可能会有许多失败作为不利因素。网络级故障可能会导致整个数据中心瘫痪,机架级或服务器级可能会出现问题,或者云提供商可能会出现问题。我们也可能耗尽容量,或者可能有一个错误的代码推破坏了系统。我们将讨论几个这样的问题,并理解我们如何设计一个系统来解决这些问题。在某些情况下,解决方法可能是不可能的。然而,了解系统稳定性的潜在弱点仍然是有价值的。

弹性架构利用系统设计模式,如适度降级、配额、超时和断路器。让我们在这一节看看其中的一些。

定额

一个系统可能有一个组件或一个端点,由多个组件和端点使用。重要的是要有适当的东西来防止一个消费者或客户压倒这样的系统。配额是做到这一点的一种方法——我们简单地为每个组件分配一个特定的配额——通过指定单位时间的请求。违反配额的人要么被警告,要么被除名,这取决于执行情况。这样,我们自己的一个系统行为不当就不会导致对其他系统的拒绝服务。配额还有助于我们防止级联故障。

功能退化

当一个具有多个依赖项的系统在其中一个依赖项中遇到故障时,适度地降级到最小的可行功能要比使整个系统陷入停顿好得多。例如,假设我们的应用中有一个端点(服务或特定功能的 URL ),它的职责是从图像的元数据中解析用户上传的图像中的位置信息,并向用户提供位置标记的建议。与其让整个上传失败,还不如跳过这个功能,给用户一个手动标记位置的选项。优雅地降级总比彻底失败好。

超时设定

我们有时在应用中调用其他服务或资源,如数据库或 API 端点。当从我们的应用中调用这样的资源时,总是有一个合理的超时是很重要的。甚至不一定是资源对所有请求都失败。特定的请求可能属于高尾部延迟类别。合理的超时有助于保持用户体验的一致性——在某些情况下,失败比令人沮丧的长时间延迟要好。

指数后退

当服务端点失败时,重试是查看它是否是暂时失败的一种方式。但是,如果重试也会失败,那么无休止地重试就没有意义了。在足够大的规模下,重试可能会与新请求(很可能会按预期提供服务)竞争,并使系统饱和。为了避免这种情况,我们可以查看重试的指数回退。这实质上降低了客户端重试的速率,当遇到连续的重试失败时。

断路器

虽然指数回退是应对重试风暴的一种方式,但断路器可以是另一种方式。断路器可以防止故障渗透到整个系统。否则,流经系统的未减轻的故障可能导致错误警报,恶化平均检测时间(MTTD)和平均解决时间(MTTR)。例如,如果其中一个内存中的缓存节点出现故障,导致请求在缓存初始超时后到达数据库,可能会导致数据库过载。如果没有在缓存节点故障和数据库节点故障之间建立初始联系,则可能会导致实际原因的 MTTD 增加,从而导致 MTTR 增加。

自我修复系统

当超过阈值的实例停止响应请求时,具有多个实例的传统负载平衡应用可能会失败——要么是因为它们停机,要么是因为突然有大量请求涌入,导致性能下降。在这种情况下,自我修复系统会添加更多实例来替换失败的实例。当查询中突然出现峰值时,像这样的自动缩放也会有所帮助。如果我们的应用运行在公共云上,这可能只是增加更多的虚拟机的问题。如果我们在数据中心外进行内部部署,那么我们将需要更加仔细地考虑容量规划。无论我们如何处理额外容量的增加,简单的增加可能是不够的。我们还应该考虑可能遇到的其他潜在故障模式。例如,负载平衡层本身可能需要扩展,以处理新后端的涌入。

持续部署和集成

一个设计良好的系统还需要考虑尽可能模拟生产环境的适当试运行设置。还应该有一种方法让我们在暂存环境中重放生产流量,以彻底测试对生产的更改。

总结

原文:https://linkedin.github.io/school-of-sre/level102/system_design/conclusion/

我们从头开始设计系统,从一台服务器扩展到多个数据中心和数十万用户。然而,您可能已经(正确地!)认为系统设计比我们到目前为止所介绍的要多得多。本课程应该让你对任何系统设计过程的基本要素有一个大概的了解。实施的具体解决方案、使用的框架和流程编排系统发展迅速。然而,指导原则保持不变。我们希望这门课程能帮助你沿着正确的方向开始,并希望你在设计系统和解决有趣的问题中获得乐趣。

系统故障排除和性能调优

系统故障排除和性能调优

原文:https://linkedin.github.io/school-of-sre/level102/system_troubleshooting_and_performance/introduction/

先决条件

从本课程中可以期待什么

这个简短的课程试图提供一个关于如何解决系统问题的一般性介绍,例如分析 api 故障、资源利用、网络问题、硬件和操作系统问题。本课程还简要介绍了用于测量整体系统性能的分析和基准测试。

本课程不包括哪些内容

本课程不包括以下内容:

  • 系统设计和架构。
  • 编程实践。
  • 度量和监控。
  • 操作系统基础。

课程内容

介绍

故障排除是运营和开发的重要组成部分。它不能通过阅读一篇文章或完成一门在线课程来学习,它是一个持续的学习过程,一个人在以下过程中学习:-

  • 日常运营和开发。
  • 查找和修复应用错误。
  • 发现并修复系统和网络问题。
  • 性能分析和改进。
  • 还有更多。

从 SRE 的角度来看,他们应该预先了解某些主题,以便能够解决单个或分布式系统的问题。

  • 充分了解您的资源,了解主机规格,如 CPU、内存、网络、磁盘等。
  • 理解系统设计和架构。
  • 确保正确收集/呈现重要指标。

惠普创始人有一句名言- “被测量的就被固定”

如果彻底捕获了系统组件和性能指标,那么就很有可能尽早成功地解决问题。

范围

对不同类型的应用或服务没有通用的故障排除方法,故障可能发生在 it 的任何一层。我们将把这项工作的范围保持在 web api 服务类型上。

注意-: Linux 生态系统非常广泛,有数百种工具和实用程序可以帮助进行系统故障排除,每种工具和实用程序都有自己的优点和功能。我们将介绍一些已知的工具,或者是 Linux 中已经有的,或者是开源世界中已经有的。对本文档中提到的工具的详细解释超出了范围,请浏览互联网或手册页以获得更多的示例和相关文档。

故障排除

原文:https://linkedin.github.io/school-of-sre/level102/system_troubleshooting_and_performance/troubleshooting/

排除系统故障有时会很棘手或乏味。在本练习中,我们需要检查服务的端到端流及其所有下游,分析日志、内存泄漏、CPU 使用、磁盘 IO、网络故障、主机问题等。了解某些实践和工具有助于更快地发现和减少故障。以下是高级故障排除流程图:

故障排除流程图

一般惯例

不同的系统需要不同的方法来发现问题。这方面的范围是有限的,并给出了一个问题,可以有更多的点可以研究。以下几点将着眼于查找 webapp 故障和修复故障的一些高级实践。

重现问题

  • 尝试中断的请求以重现问题,例如尝试命中失败的 http/s 请求。
  • 检查请求的端到端流程,并寻找返回代码,主要是 3xx、4xx 或 5xx 。3xx 主要是关于重定向,4xx 是关于未授权、错误请求、禁止等,5xx 主要是关于服务器端问题。根据返回代码,您可以寻找下一步。
  • 客户端问题主要是关于静态内容的缺失或错误,比如 javascript 问题、坏图像、异步调用导致的 json 错误等,这些都会导致浏览器上的页面呈现不正确。

收集信息

  • 在应用日志中查找错误/异常,如“无法分配内存”或“内存不足”错误,或类似“磁盘 I/O 错误”的错误,或 DNS 解析错误。
  • 检查应用和主机指标,查找服务和主机图表中的异常。从 CPU 使用率增加的时候开始,从内存使用率增加的时候开始,从磁盘空间减少或磁盘 I/O 增加的时候开始,从平均负载开始上升的时候开始等等。请阅读 SRE 学院链接,了解有关指标和监控的更多详细信息。
  • 查找最近可能破坏系统的代码或配置更改。

理解问题

  • 尝试将收集的数据与最近的操作相关联,比如在配置/代码部署后日志中显示的异常。
  • 是由于 QPS 的增加吗?是糟糕的 SQL 查询吗?最近的代码更改需要更好还是更多的硬件?

找到解决方案并应用补丁

  • 基于上述发现,如果可能的话,寻找一个快速的解决方案,例如,如果错误/异常相关,就回滚更改。
  • 尝试修补或热修复代码,如果你想向前修复的话,可能是在阶段设置中。
  • 尝试纵向扩展系统,如果高 QPS 是系统故障的原因,则尝试根据需要添加资源(计算、存储、内存等)。
  • 如果需要,优化 SQL 查询。

验证完整的请求流程

  • 再次点击请求并确保返回成功(返回代码 2xx)。
  • 检查日志以确保不再出现之前发现的异常/错误。
  • 确保指标恢复正常。

一般主机问题

要了解主机运行状况是否良好,请查找任何硬件故障或其性能问题,可以尝试以下方法:

  • Dmesg -:显示内核最近抛出的错误/失败。这有助于了解硬件故障(如果有)
  • ls 命令-: lspci,lsblk,lscpu,lsscsi,这些命令列出了 pci,磁盘,cpu 信息。
  • /var/log/messages -:显示与系统应用/服务相关的错误/警告,还显示内核问题。
  • Smartd -:检查磁盘运行状况。

重要工具

原文:https://linkedin.github.io/school-of-sre/level102/system_troubleshooting_and_performance/important-tools/

重要的 linux 命令

了解以下命令将有助于更快地发现问题。详细阐述每个命令超出了范围,请查找手册页或在线获得更多信息和相同的例子。

  • 用于日志解析-: grep、sed、awk、cut、tail、head
  • 用于网络检查-: nc、netstat、traceroute/6、mtr、ping/6、route、tcpdump、ss、ip
  • 对于 DNS -: dig、host、nslookup
  • 用于跟踪系统调用-: strace
  • 对于 ssh 上的并行执行-: gnu parallel,xargs + ssh。
  • 对于 http/s 检查-: curl,wget
  • 对于打开文件的列表-: lsof
  • 用于修改系统内核的属性-: sysctl

在分布式系统的情况下,一些好的第三方工具可以帮助同时在许多主机上执行命令/指令,例如:

  • 基于 SSH 的工具
    • 集群 SSH :集群 SSH 可以帮助您同时在许多主机上并行运行一个命令。
    • Ansible :它允许你编写可以同时在成百上千台主机上运行的 Ansible 剧本。
  • 基于代理的工具
    • Saltstack :是一个配置、状态和远程执行框架,为用户一次在大量主机上执行模块提供了广泛的灵活性。
    • Puppet :是一个自动化的管理引擎,用于您的 Linux、Unix 和 Windows 系统,执行管理任务。

日志分析工具

这些可以帮助编写 SQL 类型的查询来解析、分析日志,并提供一个简单的 UI 界面来创建仪表板,该仪表板可以基于定义的查询呈现各种类型的图表。

  • ELK:elastic search,Logstash 和 Kibana,提供一套工具和服务,可以方便快捷地解析日志、索引日志和分析日志。一旦日志/数据通过 logstash 进行解析/过滤,并在 elasticsearch 中建立索引,人们就可以在几分钟之内在 Kibana 中创建动态仪表板。这提供了对应用错误/异常/警告的简单分析和关联。
  • Azure kusto 是一种基于云的服务,类似于 Elasticsearch 和 Kibana,它允许轻松索引大量日志,提供 SQL 类型的接口来编写查询,以及创建动态仪表板的接口。

性能调优

原文:https://linkedin.github.io/school-of-sre/level102/system_troubleshooting_and_performance/performance-improvements/

性能工具是开发/运营生命周期的重要组成部分,对于理解应用行为非常重要。SRE 通常使用这些工具来评估服务的表现,并相应地做出/建议改进。

性能分析命令

在对系统或服务进行性能分析时,这些命令中的大多数都是必须知道的。

  • top -:显示正在运行的系统、进程、线程等的实时视图。
  • htop -:类似于 top command,但是比它更具交互性。
  • iotop -:一个交互式磁盘 I/O 监控工具。
  • vmstat -:虚拟内存统计浏览器。
  • iostat -:用于设备和分区的输入/输出统计的监控工具。
  • free -:告诉物理内存和交换内存的信息。
  • sar -:系统活动报告,报告不同指标,如 cpu、磁盘、内存、网络等。
  • mpstat -:显示关于 CPU 利用率和性能的信息。
  • lsof -:提供关于打开文件列表的信息,由哪些进程打开。
  • 性能分析工具。

分析工具

分析是服务性能分析的重要组成部分。有各种各样的分析器工具,可以帮助找出最常见的代码路径、调试、内存分析等。这些可以生成热图,以了解负载下的代码性能。

  • Flame graph:Flame graph 是剖析软件的可视化,允许快速准确地识别最频繁的代码路径。
  • Valgrind :这是一个用于内存调试、内存泄漏检测和分析的编程工具。
  • Gprof : GNU profiler 工具混合使用了检测和采样。检测用于收集函数调用信息,采样用于收集运行时分析信息。

要了解 LinkedIn 如何对其服务进行按需分析,请阅读 LinkedIn 博客 ODP:按需服务分析的基础设施

标杆管理

这是一个衡量服务最佳性能的过程。比如 QPS 服务可以处理多少,负载增加时的延迟,主机资源利用率,负载平均值等。在将服务部署到生产环境之前,回归测试(即负载测试)是必须的。

一些已知的工具-:

  • Apache 基准测试工具 ab :,它模拟 webapp 上的高负载并收集数据进行分析
  • Httperf :它以指定的速率向 web 服务器发送请求并收集统计数据。增加,直到找到饱和点。
  • Apache JMeter :这是一个流行的开源工具,用来测量 web 应用的性能。JMeter 是一个基于 java 的应用,不仅仅是一个 web 服务器,你可以用它来对抗 PHP、Java、REST 等等。
  • 这是另一种现代的性能测量工具,它可以给你的网络服务器增加负载,给出延迟、每秒请求数、每秒传输数等等。细节。
  • Locust :易用、可脚本化、可扩展的性能测试工具。

限制-:

上述工具有助于进行综合负载或压力测试,但这并不能衡量实际的最终用户体验,It 无法了解最终用户资源将如何影响应用性能,这是由于内存、CPU 不足或互联网连接不畅造成的。

为了了解 LinkedIn 如何在其车队中进行负载测试。阅读:用全自动负载测试消除辛劳

了解 LinkedIn 如何利用实时监控(RUM)数据来克服负载测试的局限性,并帮助改善最终用户的整体体验。阅读:使用 RUM 数据可视化监控和提高 Web 性能

扩缩容

根据资源的可用性,优化设计的系统只能在一定限度内运行。持续优化总是需要的,以确保在高峰期对资源的最佳利用。随着 QPS 的增加,系统需要扩展。我们既可以纵向扩展,也可以横向扩展。垂直可扩展性有其局限性,因为只能将 cpu、内存、磁盘、GPU 和其他规格增加到一定的限度,而水平可扩展性可以在应用设计和环境属性的限制下轻松无限地增长。

扩展 web 应用需要以下部分或全部条件:

  • 通过添加更多主机来减轻服务器负载。
  • 使用负载平衡器在服务器之间分配流量。
  • 通过数据分片和增加读取副本来纵向扩展数据库。

这里有一篇关于 LinkedIn 如何扩展其应用堆栈的好文章LinkedIn 扩展简史

故障排除示例

原文:https://linkedin.github.io/school-of-sre/level102/system_troubleshooting_and_performance/troubleshooting-example/

在本节中,我们将看到一个问题的示例,并尝试对其进行故障排除,最后分享了几个著名的故障排除故事,这些故事是 LinkedIn 工程师早些时候分享的。

示例-内存泄漏:

通常情况下,内存泄漏问题会被忽视,直到服务在运行一段时间(几天、几周甚至几个月)后变得无响应,直到服务重新启动或修复错误,在这种情况下,服务内存使用量将在指标图中以递增的顺序反映出来,类似于下图。

内存泄漏是应用对内存分配的管理不善,不需要的内存没有被释放,在一段时间内,对象继续堆积在内存中,导致服务崩溃。一般来说,这种未释放的对象会被垃圾收集器自动分类,但有时会因为一个错误而失败。调试有助于判断应用存储内存的使用情况。然后,您开始跟踪并根据使用情况过滤所有内容。如果您发现对象没有被使用,但是被引用了,您可以通过删除它们来消除它们,以避免内存泄漏。在 python 应用的情况下,它带有内置的特性,如 tracemalloc 。这个模块可以帮助确定对象首先被分配到哪里。几乎每种语言都有一套工具/库(内置的或外部的),可以帮助发现内存问题。类似地,对于 Java,有一个著名的内存泄漏检测工具叫做 Java VisualVM

让我们看看一个基于虚拟 flask 的 web 应用如何存在内存泄漏错误,随着每个请求,它的内存使用量不断增加,以及我们如何使用 tracemalloc 来捕获泄漏。

假设-:创建了一个 python 虚拟环境,其中安装了 flask。

一个有 bug 的裸最小烧瓶代码,阅读评论了解更多信息

启动烧瓶 app

启动时,其内存使用量约为 26576 kb,即约 26MB

现在,随着每个后续的 GET 请求,我们可以注意到进程内存的使用继续缓慢增加。

现在让我们尝试 10000 个请求,看看内存使用是否会大幅增加。为了满足大量的请求,我们使用了一个名为“ab”的 Apache 基准测试工具。在 10000 次点击后,我们可以注意到 flask app 的内存使用量跃升了近 15 倍,即从最初的 26576 KB 到 419316 KB,即从大约 26 MB 到 419 MB ,对于这样一个小的 webapp 来说,这是一个巨大的飞跃。

让我们试着使用 python tracemalloc 模块来理解应用的内存分配。 Tracemalloc 在某个特定点拍摄内存快照,并对其执行各种统计。

在我们的 app.py 文件中添加最少的代码,fetchuserdata.py 文件没有变化,它将允许我们在命中/捕获 uri 时捕获 tracemalloc 快照。

在重新启动 app.py (flask run) 之后,我们将-首先点击 http://127.0.0.1:5000/capture -然后点击 http://127.0.0.1:5000/ 10000 次,以便发生内存泄漏。-最后再打 http://127.0.0.1:5000/capture 拍个快照就知道哪一行分配最多了。

在最后的快照中,我们注意到了发生大部分分配的确切模块和行号。即 fetchuserdata.py,第 6 行,在 10000 次点击之后,它拥有 419 MB 的内存。

总结

上面的例子显示了一个 bug 如何导致内存泄漏,以及我们如何使用 tracemalloc 来了解它在哪里。在现实世界中,应用要比上面的虚拟例子复杂得多,您必须明白,由于 tracemalloc 本身的开销,使用 tracemalloc 可能会在一定程度上降低应用的性能。请注意它在生产环境中的使用。

如果你有兴趣深入挖掘 Python 对象内存分配内部机制和调试内存泄漏,可以看看 PyCon India 2019 上 Sanket Patel 的一个有趣的演讲,调试 Python Flask 中的内存泄漏| Python 对象内存分配内部机制

总结

原文:https://linkedin.github.io/school-of-sre/level102/system_troubleshooting_and_performance/conclusion/

复杂系统有许多可能出错的因素。它可能是糟糕的设计和架构、糟糕的代码管理、围绕不同缓存的糟糕策略、糟糕的数据库查询或架构、资源的不当使用或糟糕的操作系统版本、监控不力的系统、数据中心问题、网络故障等等,任何这些都可能出错。

作为一名 SRE,了解重要的工具/命令、最佳实践、分析、基准测试和扩展可以帮助您更快地排除故障并提高整个系统的性能。

进一步阅读

这里有一些 LinkedIn 工程师写的 LinkedIn 工程博客的链接,关于他们做的消防工作,确保网站 24x7x365 不间断运行。

持续集成和持续交付

介绍

原文:https://linkedin.github.io/school-of-sre/level102/continuous_integration_and_continuous_delivery/introduction/

先决条件

  1. 软件开发和维护
  2. Docker 工人

从本课程中可以期待什么?

在本课程中,您将学习 CI/CD 的基础知识,以及它如何帮助推动组织中的 SRE 规则。它还讨论了 CI/CD 实践中的各种 DevOps 工具,以及一个关于基于 Jenkins 的管道的动手实验会议。最后,它将通过解释在不断发展的 SRE 哲学中的作用来结束。

本课程不包括哪些内容?

本课程不全面涵盖 DevOps 元素,如基础架构代码、持续监控应用和基础架构。

目录

什么是 CI/CD?

原文:https://linkedin.github.io/school-of-sre/level102/continuous_integration_and_continuous_delivery/introduction_to_cicd/

持续集成和持续交付,也称为 CI/CD,是一组过程,有助于以可靠的方式更快地集成软件代码变更和部署到最终用户。更频繁的集成和部署有助于缩短软件开发生命周期。CI/CD 中有三种实践:

  • 持续集成
  • 连续交货
  • 持续部署让我们在接下来的小节中详细了解一下其中的每一项。

CI/CD 的优势

  1. 集成问题显著减少。
  2. 团队可以更快地开发内聚的软件。
  3. 开发人员和运营团队之间改进的协作可以减少生产集成问题。
  4. 摩擦更少,新功能交付更快
  5. 更好地调试生产问题,并在下一个版本/补丁中修复它们。

简史

原文:https://linkedin.github.io/school-of-sre/level102/continuous_integration_and_continuous_delivery/cicd_brief_history/

CI/CD 的演变

传统的开发方法已经存在了很长时间。瀑布模型已经在大大小小的项目中广泛使用,并取得了成功。尽管取得了成功,但它也有很多缺点,如周期时间较长或交付时间较长。

当多个团队成员在项目中工作时,代码变更会累积起来,直到计划的构建日期才会被集成。构建通常以约定的周期进行,从一个月到一个季度不等。这导致了几个集成问题和构建失败,因为开发人员是在孤岛中工作的。

对于运营团队/任何人来说,将新的构建/发布部署到生产环境都是一个噩梦般的情况,因为缺乏关于每个变更和配置需求的适当文档。因此,要成功部署,通常需要热修复和即时补丁。

另一个巨大的挑战是协作。开发人员很少见到运营工程师,也不完全了解生产环境。所有这些挑战都增加了代码变更交付的周期时间。

敏捷方法规定了在多次迭代中交付特性的增量交付。因此,开发人员以较小的增量提交他们的代码更改,并更频繁地推出。每一次代码提交都会触发一次新的构建,集成问题会在更早的时候被发现。这改进了构建过程,从而减少了周期时间。这个过程被称为持续集成或 CI

随着组织适应开发运维规程和 SRE 规程的趋势的出现,开发人员和运营团队之间的巨大障碍已经被缩小了。开发人员和运营团队之间的协作得到了改善。此外,两个团队使用相同的工具和过程改善了协调,并避免了对过程的冲突理解。这方面的一个主要驱动因素是连续交付(CD) 过程,它确保较小变更的增量部署。在部署到生产环境之前,有多个生产前环境也称为暂存环境。

CI/CD 和 DevOps

术语 DevOps 代表开发(Dev)和运营(Ops)团队的组合。这将开发者和运营团队聚集在一起,进行更多的合作。开发团队通常希望引入更多的功能和变化,而运营团队则更关注生产中应用的稳定性。变更总是被运营团队视为威胁,因为它会动摇环境的稳定性。DevOps 被称为一种文化,它引入了减少开发人员和操作人员之间障碍的过程。

开发人员和运营人员之间的协作允许更好地跟进端到端生产部署和更频繁的部署。因此,CI/CD 是 DevOps 流程中的一个关键要素。

持续集成

原文:https://linkedin.github.io/school-of-sre/level102/continuous_integration_and_continuous_delivery/continuous_integration_build_pipeline/

CI 是一种软件开发实践,其中团队成员经常集成他们的工作。每个集成都由一个自动化构建(包括测试)来验证,以尽可能快地检测集成错误。

持续集成要求所有的代码变更都保存在一个单一的代码存储库中,所有的成员都可以定期地将变更推送到他们的特性分支。代码变更必须与代码的其余部分快速集成,自动构建应该发生并反馈给成员以尽早解决它们。

应该有一个 CI 服务器,当成员推送代码时,它可以立即触发构建。构建通常包括编译代码并将其转换为可执行文件,如 jar 或 dll 等。叫做包。它还必须用代码覆盖率执行单元测试。可选地,构建过程可以有额外的阶段,例如静态代码分析和漏洞检查等。

詹金斯特拉维斯 CIGitLab蔚蓝 DevOps 等。是少数流行的 CI 工具。这些工具提供了各种插件和集成,如 antmaven 等。用于建筑和包,以及 Junit、selenium 等。用于执行单元测试。sonar cube可用于静态代码分析和代码安全。

图 1:持续集成管道

图 2:持续集成过程

持续交付和部署

原文:https://linkedin.github.io/school-of-sre/level102/continuous_integration_and_continuous_delivery/continuous_delivery_release_pipeline/

持续交付意味着在非生产环境中更频繁地部署应用构建,如 SIT、UAT、INT 并自动执行集成测试和验收测试。

*在 CD 中,测试是在集成应用上执行的,而不是在基于微服务的应用的情况下在单个微服务上执行的。测试必须包括所有可能包含 UI 测试的功能测试和验收测试。构建本质上必须是不可变的,也就是说,必须在包括生产环境在内的所有环境中部署相同的包。

在执行额外的验收测试(如性能测试等)后,通常需要手动部署到生产环境中。因此,全自动部署到生产环境被称为 连续部署 (而CD——连续交付 不会自动部署到生产)。连续部署必须有一个特性切换,这样就可以在不需要重新部署代码的情况下关闭一个特性。

通常,部署涉及多个生产环境,例如在蓝绿色环境中,应用首先部署到蓝色环境,然后部署到绿色环境,因此不需要停机。

图 3:连续输送管道*

CI/CD 流水线:实践

原文:https://linkedin.github.io/school-of-sre/level102/continuous_integration_and_continuous_delivery/jenkins_cicd_pipeline_hands_on_lab/

基于 Jenkins 的 CI/CD 管道

Jenkins 是一个用于编排 CI/CD 管道的开源持续集成服务器。它支持与多个组件、基础设施(如 git、云等)的集成。这有助于完成软件开发生命周期。

在这个动手实验中,让我们:为一个简单的 java 应用创建一个构建管道(CI)。添加测试阶段以构建管道

本次实践基于在本地工作站 docker 上运行的 Jenkins,它是为 Windows 操作系统设计的。对于 Linux 操作系统,请遵循演示

注意:动手实验是由 docker 上的 Jenkins 设计的。但是,这些步骤也适用于在 windows 工作站上直接安装 docker。

安装 Git、Docker 和 Jenkins:

  • 在您的工作站上安装 git 命令行工具。(按照这个在本地安装 Git)
  • Docker Desktop for windows 安装在工作站上。按照说明安装 docker。
  • 确保您的 Docker for Windows 安装配置为运行 Linux 容器而不是 Windows 容器。有关切换到 Linux 容器的说明,请参见 Docker 文档。
  • 参考运行并设置 docker 上的 Jenkins。
  • 通过创建管理员用户等初始步骤配置 Jenkins。遵循安装向导。
  • 如果您已经在本地工作站上安装了 Jenkins,请确保安装了 maven 工具。按照这个来安装 maven。

派生示例 java 应用:

为了实际操作,让我们从 GitHubsimple-java-maven-app派生一个简单的 Java 应用。1.注册 GitHub 账号加入 GitHub GitHub 。注册后,进入登录。2.点击这个链接 3,打开 simple-java-maven-app。在右上角,点击“叉”创建一个项目的副本到你的 GitHub 帐户。(参见叉一个回购 ) 4。一旦分叉,将这个存储库克隆到您的本地工作站。

创建 Jenkins 项目:

  1. 使用之前在 Jenkins 设置期间创建的管理员帐户登录到 Jenkins 门户网站 localhost:8080
  2. 首次登录时,将出现以下屏幕。点击创建一个任务

图 4:詹金斯——创造一份工作

  1. 在下一个屏幕上,在输入项目名称字段中输入 simple-java-pipeline 。从项目列表中选择管道,点击确定

图 5:詹金斯-创建管道

  1. 点击页面顶部的管道选项卡,向下滚动到管道部分。
  2. 定义字段,选择来自 SCM 选项的管道脚本。该选项指示 Jenkins 从源代码控制管理(SCM)获取您的管道,这将是您本地克隆的 Git 存储库。
  3. SCM 字段中选择 Git
  4. 存储库 URL 字段中,从上面的派生示例 Java 应用部分指定本地克隆的存储库的目录路径。

输入详细信息后,屏幕如下所示。

图 6:詹金斯管道配置

使用 Jenkinsfile 创建生成管道:

Jenkinsfile 是一个脚本文件,包含管道配置和阶段以及 Jenkins 从文件创建管道的其他指令。该文件将保存在代码库的根目录下。1.使用您最喜欢的文本编辑器或 IDE,在本地simple-Java-maven-appGit 存储库的根目录下创建并保存一个名为 Jenkinsfile 的新文本文件。2.复制以下声明性管道代码,并将其粘贴到空的 Jenkinsfile 中。

pipeline {
    agent {
        docker {
            image 'maven:3.8.1-adoptopenjdk-11' 
            args '-v /root/.m2:/root/.m2' 
        }
    }
    stages {
        stage('Build') { 
            steps {
                sh 'mvn -B -DskipTests clean package' 
            }
        }
    }
} 

注意:如果您在没有 docker 的本地工作站上运行 Jenkins,请将代理更改为如下所示的 any ,以便它在本地主机上运行。请确保 maven 工具安装在您的本地工作站上。

pipeline {
    agent any

    stages {
        stage('Build') { 
            steps {
                sh 'mvn -B -DskipTests clean package' 
            }
        }
    }
} 

在上面的 Jenkinsfile 中:我们指定了管道应该运行的代理。代理部分中的“docker”指示使用指定的映像运行新的 docker 容器。在“阶段”部分,我们可以将多个步骤定义为不同的阶段。这里,我们有一个称为“构建”的阶段,使用 maven 命令来构建 java 应用。

  1. 保存您的 Jenkinsfile 并提交和推送至您的分叉存储库。从命令提示符运行以下命令。
cd <your local simple-java-maven-app repo cloned folder>
git add .
git commit -m "Add initial Jenkinsfile"
git push origin master 
  1. 在您的浏览器上进入 Jenkins portal,点击仪表板。打开 simple-java-pipeline ,从左侧菜单中点击 Build Now

图 7:詹金斯-建立管道

  1. 注意在 Build History 菜单下运行的构建。点击的建造号,它会显示各个阶段。

图 8: Jenkins -查看运行构建

  1. 我们已经成功地创建了一个包含单个阶段的构建管道并运行了它。我们可以通过点击控制台输出菜单来检查日志。

构建管道中的其他阶段:

在上一节中,我们已经创建了具有单个阶段的管道。通常,您的 CI 管道包含多个阶段,如构建、测试和其他可选阶段,如代码扫描等。在本节中,让我们向构建管道添加一个测试阶段并运行。

  1. 返回到您的文本编辑器/IDE,打开 Jenkinsfile 和下面显示的测试阶段。
stage('Test') {
            steps {
                sh 'mvn test'
            }
            post {
                always {
                    junit 'target/surefire-reports/*.xml'
                }
            }
        } 

添加测试阶段后,Jenkinsfile 如下所示。

pipeline {
    agent {
        docker {
            image 'maven:3.8.1-adoptopenjdk-11' 
            args '-v /root/.m2:/root/.m2' 
        }
    }
    stages {
        stage('Build') { 
            steps {
                sh 'mvn -B -DskipTests clean package' 
            }
        }
        stage('Test') {
            steps {
                sh 'mvn test'
            }
            post {
                always {
                    junit 'target/surefire-reports/*.xml'
                }
            }
        }
    }
} 
  • 这里添加了运行 maven 命令测试的阶段“Test”。
  • 后>总是部分确保该步骤总是在步骤完成后执行。测试报告可通过 Jenkins 的界面获得。

注意:如果您在没有 docker 的本地工作站上运行 Jenkins,请将代理更改为 any ,以便它在本地主机上运行。请确保 maven 工具安装在您的本地工作站上。

pipeline {
    agent any

    stages {…

    }
} 
  1. 保存您的 Jenkinsfile 并提交和推送至您的分叉存储库。从命令提示符运行以下命令。
cd <your local simple-java-maven-app repo cloned folder>
git add .
git commit -m "Test stage is added to Jenkinsfile"
git push origin master 
  1. 在您的浏览器上进入 Jenkins portal,点击仪表板。打开 simple-java-pipeline ,从左侧菜单中点击 Build Now。
  2. 注意构建和测试阶段显示在构建屏幕中。

图 9: Jenkins -查看包含测试阶段的运行构建

我们现在已经成功地创建了包含两个阶段的 CI 管道:构建和测试阶段。

总结

原文:https://linkedin.github.io/school-of-sre/level102/continuous_integration_and_continuous_delivery/conclusion/

SRE 角色中的应用

监控、自动化和消除辛劳是 SRE 纪律的一些核心支柱。作为一个 SRE,你可能需要花费大约 50%的时间来自动化重复的任务,以消除辛劳。CI/CD 管道是 SRE 的重要工具之一。他们有助于用更小的、定期的和更频繁的构建来交付高质量的应用。此外,CI/CD 指标,如部署时间、成功率、周期时间和自动化测试成功率等。是提高产品质量从而提高应用可靠性的关键。

  • 基础设施即代码是 SRE 自动化重复配置任务的标准实践之一。每个配置都作为代码进行维护,因此可以使用 CI/CD 管道进行部署。通过 CI/CD 管道将配置更改交付到生产环境非常重要,这样可以跨环境维护版本控制和更改的一致性,并避免手动错误。
  • 通常,作为一名 SRE,您需要审查应用 CI/CD 管道,并建议额外的阶段,如静态代码分析和代码中的安全性和隐私检查,以提高产品的安全性和可靠性。

总结

在本章中,我们研究了 CI/CD 管道,简要介绍了传统构建实践面临的挑战。我们还研究了 CI/CD 流水线如何增强 SRE 规程。在软件开发生命周期中使用 CI/CD 管道是 SRE 领域的一种现代方法,有助于实现更高的效率。

我们还使用 Jenkins 进行了创建 CI/CD 流水线的动手实验活动。

参考

  1. 持续集成(martinfowler.com)
  2. 微服务 CI/CD-Azure 架构中心|微软文档
  3. 基础蓝图 _ 2(devopsinstitute.com)
  4. 詹金斯用户文档

捐助

原文:https://linkedin.github.io/school-of-sre/CONTRIBUTING/

我们意识到,我们创建的初始内容只是一个起点,我们希望社区能够在完善和扩展内容的过程中提供帮助。

作为投稿人,您声明您提交的内容没有抄袭。通过提交内容,您(以及,如果适用,您的雇主)根据知识共享署名 4.0 国际公共许可证向 LinkedIn 和开源社区许可提交的内容。

资源库网址:【https://github.com/linkedin/school-of-sre T2】

投稿指南

确保您遵守以下准则:

  • 应该是关于可以应用于任何公司或个人项目的原则和概念。不要专注于特定的工具或技术(它们通常会随着时间而变化)。
  • 遵守行为准则。
  • 应该与 SRE 的角色和职责相关。
  • 应进行本地测试(参见测试步骤)并格式化。
  • 在提交拉取请求之前,最好先打开一个问题并讨论您的更改。这样,你甚至可以在开始之前就吸收别人的想法。

本地构建和测试

在打开 PR 之前,运行以下命令在本地构建和查看站点。

python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
mkdocs build
mkdocs serve 

打开一个 PR

遵循 GitHub 公关工作流程投稿。

派生此回购,创建功能分支,提交您的更改并打开此回购的 PR。

行动守则

原文:https://linkedin.github.io/school-of-sre/CODE_OF_CONDUCT/

本行为准则概述了参与 LinkedIn 管理的开源社区的期望,以及报告不可接受行为的步骤。我们致力于为所有人提供一个受欢迎和鼓舞人心的社区。违反这一行为准则的人可能会被禁止进入社区。

我们的开源社区致力于:

  • 友好而耐心:记住,你可能不是在用别人的主要口语或编程语言交流,别人可能没有你的理解水平。
  • 欢迎:我们的社区欢迎并支持各种背景和身份的人。这包括但不限于任何种族、民族、文化、民族血统、肤色、移民身份、社会和经济阶层、教育水平、性别、性取向、性别认同和表达、年龄、大小、家庭状况、政治信仰、宗教以及精神和身体能力。
  • 尊重他人:我们是一个全球性的专业人士社区,我们以专业的方式行事。意见不一致不能成为不良行为和不礼貌的借口。不尊重和不可接受的行为包括但不限于:
    • 暴力威胁或语言。
    • 歧视性或贬损性的笑话和语言。
    • 张贴露骨的或暴力的内容。
    • 张贴或威胁张贴人们的个人身份信息(“doxing”)。
    • 侮辱,尤其是那些使用歧视性词语或诽谤的侮辱。
    • 可能被视为性关注的行为。
    • 提倡或鼓励任何上述行为。
  • 了解分歧:分歧,无论是社会上的还是技术上的,都是有用的学习机会。寻求理解其他观点,建设性地解决分歧。
  • 这段代码并不详尽或完整。它抓住了我们对高效协作环境的共同理解。我们希望该准则在精神上和文字上都得到遵守。

范围

本行为准则适用于 LinkedIn 管理的开源项目的所有回购和社区,无论回购是否明确要求使用本准则。当个人代表一个项目或其社区时,该准则也适用于公共场所。示例包括使用官方项目电子邮件地址、通过官方社交媒体帐户发帖,或作为在线或离线活动的指定代表。项目维护人员可以进一步定义和阐明项目的表示。

注意:一些 LinkedIn 管理的社区在本文档和问题解决流程之前就有行为准则。虽然不要求社区更改他们的代码,但是他们应该使用这里列出的解决流程。审查小组将与相关社区协调解决您的问题。

报告行为准则问题

我们鼓励所有社区尽可能自行解决问题。这建立了更广泛和更深入的理解,并最终建立了更健康的互动。如果问题无法在当地解决,请联系 oss@linkedin.com 报告您的问题。

请在您的报告中包括:

  • 您的联系信息。
  • 任何相关个人的姓名(真实姓名、用户名或假名)。如果有其他证人,也请包括他们。
  • 你对发生的事情的描述,如果你认为事件还在继续。如果有公开的记录(如邮件列表存档或公共聊天日志),请附上链接或附件。
  • 任何可能有帮助的附加信息。

所有报告都将由一个多人组成的团队进行审查,并根据具体情况做出必要且适当的回应。如果需要额外的观点,团队可以从具有相关专业知识或经验的其他人那里寻求见解。将始终为报告事件的人员保密。相关方从来不是审核小组的成员。

任何被要求停止不可接受行为的人都应该立即照办。如果个人参与了不可接受的行为,审查小组可以采取他们认为适当的任何行动,包括从社区中永久禁止。

本行为准则基于微软开源行为准则,该行为准则基于托多组织建立的模板,并被众多其他大型社区使用(例如脸书雅虎TwitterGitHub )以及贡献者契约版本 1.4 的范围部分。

SRE 社区

原文:https://linkedin.github.io/school-of-sre/sre_community/

我们为 SRE 学校建立了一个活跃的 LinkedIn 社区。

请通过:【https://www.linkedin.com/groups/12493545/ T2】加入群

该小组的成员在现场可靠性工程方面具有不同水平的经验。围绕现场可靠性工程的不同技术主题展开了活跃的对话。我们鼓励每个人都加入到对话中来,互相学习,在 SRE 空间中建立成功的职业生涯。

posted @ 2024-11-01 16:31  绝不原创的飞龙  阅读(6)  评论(0编辑  收藏  举报