Linkedin-SRE-中文教程-四-
Linkedin SRE 中文教程(四)
第三部分:威胁、攻击和防御
原文: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(外部)之间传递信息。

BGP 如何工作
- BGP 负责寻找到目的路由器的路径&它选择的路径应该是最短和最可靠的。
- 这个决定是通过称为链路状态的协议来完成的。使用链路状态协议,每台路由器都会向网络中的所有其它路由器广播其链路和 IP 子网的状态。然后,每台路由器从其它路由器接收信息,构建整个网络的完整拓扑视图。下一跳路由表基于此拓扑视图。
- 链路状态协议使用计算机科学领域的一种著名算法,即 Dijkstra 最短路径算法:
- 我们从路由器开始考虑到所有直接邻居的路径开销。
- 然后选择最短的路径
- 然后,我们重新查看我们可以到达的所有邻居,并用开销信息更新我们的链路状态表。然后,我们继续选择最短路径,直到访问完所有路由器。
BGP 漏洞
-
通过破坏 BGP 路由表,我们能够影响互联网上的流量流向!这种行为被称为 BGP 劫持。
-
恶意源、意外或路由器将伪造的路由广告信息注入到 BGP 分布式路由数据库中会破坏互联网骨干网的运行。
-
黑洞流量:
-
黑洞路由是一种无处可去的网络路由,即路由表条目,匹配路由前缀的数据包会被丢弃或忽略。黑洞路由只能通过监控丢失的流量来检测。
-
黑洞路由是对许多常见病毒攻击的最佳防御,在这些攻击中,流量从受感染的机器被丢弃到命令和控制主机或从命令和控制主机被丢弃。
-
Youtube 上臭名昭著的 BGP 注入攻击
-
例句:2008 年,巴基斯坦决定通过创建一条通向黑洞的 BGP 路由来屏蔽 YouTube。相反,这些路由信息被传输到香港的一家 ISP,并从那里意外地传播到世界其他地方,这意味着数百万人被路由到这个黑洞,因此无法访问 YouTube。
-
BGP 最大的潜在风险发生在拒绝服务攻击中,这种攻击使路由器被超过其处理能力的数据包淹没。当网络开始承载过量的 BGP 消息,使路由器控制处理器、存储器、路由表过载,并减少可用于数据流量的带宽时,就会发生网络过载和路由器资源耗尽。
-
路由器摆动是另一种类型的攻击。路由波动指的是对 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 和密码通过前门登录,攻击者使用后门绕过这些正常的访问控制。
恶意攻击
生日袭击
- 一旦攻击者破坏了哈希密码文件,就会进行生日攻击。生日攻击是一种加密攻击,用于使单向散列的暴力攻击更容易。这是一个基于概率论中生日问题的数学发现。
- 延伸阅读:
www . science direct . com/topics/computer-science/birthday-attackwww.internetsecurity.tips/birthday-attack/
暴力密码攻击
- 在暴力密码攻击中,攻击者在系统上尝试不同的密码,直到其中一个成功。通常,攻击者使用软件程序来尝试可能的密码、用户 ID 或安全代码的所有可能组合,直到找到匹配项。这种情况迅速而有序地发生。这种类型的攻击被称为暴力密码攻击,因为攻击者只是不停地敲打代码。不涉及任何技巧或秘密行动——只是最终破解代码的蛮力。
- 延伸阅读:
owasp.org/www-community/attacks/Brute_force_attackowasp . org/www-community/controls/Blocking _ Brute _ Force _ Attacks
字典密码攻击
- 字典密码攻击是一种简单的攻击,它依赖于用户错误的密码选择。在字典密码攻击中,一个简单的密码破解程序从字典文件中获取所有单词,并通过输入每个字典条目作为密码来尝试登录。
- 延伸阅读: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 发布了常见漏洞类别的列表

编写简单的代码
尽量保持你的代码简洁明了。
避免多层嵌套
- 多级嵌套是一种常见的反模式,会导致简单的错误。如果错误出现在最常见的代码路径中,单元测试很可能会捕捉到它。然而,单元测试并不总是检查多层嵌套代码中的错误处理路径。该错误可能导致可靠性降低(例如,如果服务在错误处理错误时崩溃)或安全漏洞(如错误处理的授权检查错误)。
消除 YAGNI 气味
- 有时,开发人员通过添加将来可能有用的功能来过度设计解决方案,“以防万一”这违背了 YAGNI(你不会需要它)原则,该原则建议只实现你需要的代码。YAGNI 代码增加了不必要的复杂性,因为它需要被记录、测试和维护。
- 总而言之,避免 YAGNI 代码可以提高可靠性,更简单的代码可以减少安全漏洞,减少出错的机会,减少开发人员维护未使用代码的时间。
偿还技术债务
- 对于开发人员来说,用 TODO 或 FIXME 注释来标记需要进一步注意的地方是一种常见的做法。从短期来看,这种习惯可以加快最关键功能的交付速度,并允许团队提前完成期限——但是它也会招致技术债务。尽管如此,只要你有一个清晰的偿还这些债务的过程(并分配时间),这并不一定是一个坏习惯。
重构
- 重构是保持代码库干净和简单的最有效的方法。即使是健康的代码库偶尔也需要
- 不管重构背后的原因是什么,你都应该遵循一条黄金法则:永远不要在一次提交代码库中混合重构和功能变更。重构变更通常意义重大,并且可能难以理解。
- 如果提交还包括功能变更,那么作者或评审者可能会忽略错误的风险就更高了。
单元测试
- 单元测试可以通过在发布之前查明单个软件组件中的各种错误来提高系统的安全性和可靠性。这种技术包括将软件组件分解成更小的、自包含的、没有外部依赖性的“单元”,然后测试每个单元。
模糊测试
- 模糊测试是一种补充前面提到的测试技术的技术。模糊化涉及使用模糊化引擎生成大量候选输入,然后通过模糊化驱动程序传递给模糊化目标。然后模糊器分析系统如何处理输入。由各种软件处理的复杂输入是模糊化的常见目标,例如,文件解析器、压缩算法、网络协议实现和音频编解码器。
集成测试
- 集成测试超越了单个单元和抽象,用真实的实现取代了抽象的虚假或废弃的实现,如数据库或网络服务。因此,集成测试使用了更完整的代码路径。因为您必须初始化和配置这些其他依赖项,所以集成测试可能比单元测试更慢、更繁琐——为了执行测试,这种方法结合了真实世界的变量,如服务端到端通信时的网络延迟。当您从测试单个低级代码单元转移到测试它们在组合在一起时如何交互时,最终结果是对系统按预期运行有了更高的信心。
最后但并非最不重要
- 代码审查
- 依靠自动化
- 不要签入秘密
- 可验证的构建
总结
原文:https://linkedin.github.io/school-of-sre/level101/security/conclusion/
现在,您已经完成了本安全课程,您已经了解了计算机系统和网络可能面临的安全威胁。不仅如此,您现在还可以更好地保护您的系统,并向他人推荐安全措施。
本课程提供安全领域的基本日常知识,还将帮助您将安全放在第一位。
其他资源
一些书会是很好的资源
- Web 开发人员的整体信息-Sec
holisticinfosecforwebdevelopers.com/-免费可下载的图书系列,非常广泛和深入地涵盖了 Web 开发人员和 DevOps 工程师需要了解的内容,以创建健壮、可靠、可维护和安全的软件、网络和其他内容,并持续、按时交付,没有令人讨厌的意外 - Docker Security -快速参考:给 DevOps 工程师
leanpub.com/dockersecurity-quickreference-一本关于理解 Docker 安全缺省、如何改进它们(理论和实践)的书,以及许多工具和技术。 - 如何像传奇人物一样黑客
amzn.to/2uWh1Up——一个黑客闯入一家秘密离岸公司的故事,Sparc Flow,2018 - 如何像摇滚明星一样进行调查
books2read.com/u/4jDWoZ-经历真实的危机以掌握法医分析的秘密,Sparc Flow,2017 - 真实世界密码术
www.manning.com/books/real-world-cryptography-这本早期访问的书教你应用密码技术来理解和应用你的系统和应用的每个级别的安全性。 - AWS 安全公司
www.manning.com/books/aws-security?UTM _ source = github&UTM _ medium = organic&UTM _ campaign = book _ shields _ aws _ 1 _ 31 _ 20-这本书涵盖了常见的 AWS 安全问题以及访问策略、数据保护、审计、持续监控和事件响应的最佳实践。
培训后提问/进一步阅读
- 像:
github.com/apsdehal/awesome-ctfCTF 事件 - 渗透测试:
github.com/enaqx/awesome-pentest - 威胁情报:
github.com/hslatman/awesome-threat-intelligence - 威胁探测与追踪:
github.com/0x4D31/awesome-threat-detection - 网络安全:
github.com/qazbnm456/awesome-web-security - 构建安全可靠的系统:
landing . Google . com/sre/resources/foundationsandprinciples/SRS-book/
102 级
Linux 中级
Linux 中级
原文:https://linkedin.github.io/school-of-sre/level102/linux_intermediate/introduction/
先决条件
- 期望通过了 SRE 学校 Linux 基础 。
从本课程中可以期待什么
本课程分为两个部分。在第一部分中,我们将讲述在 SRE 学校课程的早期,我们离开 linux 基础知识的地方,我们将深入探究一些更高级的 Linux 命令和概念。
在第二部分中,我们将讨论如何在日常工作中使用 Bash 脚本,通过任何 SRE 的真实例子的帮助,将自动化和辛劳减少作为 SRE。
本课程不包括哪些内容
本课程旨在让你熟悉 Linux 命令、shell 脚本的交集以及 SRE 如何使用它。我们不会讨论 Linux 的内部机制。
实验室环境设置
- 在你的系统上安装 docker。https://docs.docker.com/engine/install/
* 我们将使用 RedHat Enterprise Linux (RHEL) 8。

- 我们将运行上述 docker 容器中的大多数命令。
课程内容
-
<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
包管理
原文: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 命令来列出所有挂载到你电脑上的存储设备。

我们看到的上面输出的格式是:
device 上 mount_point 下 file\_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 概念。但是,我们将引导您找到链接和参考资料,您可以根据自己的兴趣选择它们。
课程内容
本课程涵盖了以下主题:
容器介绍
什么是容器
下面是一个流行的容器引擎 Docker 对容器的流行定义:
容器是一个标准的软件单元,它将代码及其所有依赖项打包,以便应用能够快速可靠地从一个计算环境运行到另一个计算环境
我们来分析一下。容器是与整个运行时环境捆绑在一起的代码。这包括运行应用所需的系统库、二进制文件和配置文件。
为什么是容器
您可能想知道为什么我们需要将您的应用与其依赖项打包在一起。这就是定义的第二部分,
...因此,应用可以快速可靠地从一个计算环境运行到另一个计算环境。
开发人员通常在他们的开发环境(或本地机器)中编写代码,在将代码投入生产之前,在一个或两个测试环境中进行测试。理想情况下,为了在推向生产之前可靠地测试应用,我们需要所有这些环境都统一到一个 tee(底层操作系统、系统库等)。
当然,这种理想很难实现,尤其是当我们混合使用本地(完全控制)和云基础架构提供商(在硬件控制和安全选项方面更具限制性)时,这种情况在今天更为常见。
这就是为什么我们不仅需要打包代码,还需要打包依赖项;以便您的应用能够可靠地运行,而不管它运行在哪个基础结构或环境上。
我们可以在一台主机上运行几个容器。由于容器的实现方式,每个容器在同一个主机中都有自己的隔离环境。这意味着一个单一的应用可以被分解成微服务并打包到容器中。每个微服务都在隔离环境中的主机上运行。这是使用容器的另一个原因:关注点分离。
提供隔离的环境不会让一个容器中的一个应用的故障影响到另一个。这被称为故障隔离。由于容器中进程的有限可见性,隔离还提供了增加安全性的额外好处。
由于大多数容器化解决方案的实施方式,我们还可以选择限制容器内运行的应用消耗的资源量。这叫做资源限制。Will 将在 cgroups 一节中更详细地讨论这个特性。
虚拟机和容器的区别
让我们稍微跑题一下,进入一些历史。在上一节中,我们讨论了容器如何帮助我们实现关注点的分离。在广泛使用容器之前,虚拟化用于在同一主机上的隔离环境中运行应用(在某些情况下,今天仍在使用)。
简单来说,虚拟化就是我们将软件与运行该软件的操作系统副本打包在一起。这个包称为虚拟机(VM)。捆绑在虚拟机中的操作系统映像称为来宾操作系统。一个名为 Hypervisor 的组件位于来宾操作系统和主机操作系统之间,负责促进来宾操作系统对底层操作系统硬件的访问。您可以在此了解有关虚拟机管理程序的更多信息。

与在一台主机上运行多个容器类似,多台虚拟机可以在一台主机上运行,这样,就可以在一台单独的虚拟机上运行应用(或每个微服务),并实现关注点的分离。
这里主要关注虚拟机和容器的大小。虚拟机带有客户操作系统的副本,因此与容器相比重量更大。如果您对虚拟机和容器的比较更感兴趣,您可以查看来自 Backblaze 和 NetApp 的这些文章。
虽然可以使用虚拟机管理程序(例如 CentOS 7 上的 Windows 10 虚拟机)在具有不兼容内核的主机上运行操作系统,但在内核可以共享的情况下(例如 CentOS 7 上的 Ubuntu),由于大小因素,容器优于虚拟机。共享内核,正如您将在后面看到的,也为容器提供了许多优于虚拟机的性能优势,比如更快的启动。让我们看看容器如何工作的图表。

比较这两个图,我们注意到两件事:
-
容器没有单独的(客户)操作系统
-
容器引擎是容器和主机操作系统之间的中介。它用于促进主机操作系统上容器的生命周期(然而,这不是必需的)。
下一节将详细解释容器如何与主机共享相同的操作系统(准确地说是内核),同时为应用的运行提供隔离的环境。
容器是如何实现的
我们已经讨论了容器如何与虚拟机不同,与主机操作系统共享相同的内核,并为应用运行提供隔离的环境。这是在没有在主机操作系统上运行客户操作系统的开销的情况下实现的,这要归功于 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 的过程。

- 让我们用 unshare 命令创建一个 PID 名称空间,并在名称空间中创建一个 bash 进程

您可以看到ps aux(它本身是在如此创建的 PID 名称空间中启动的进程)只能看到它自己的名称空间中的进程。因此,输出显示只有两个进程在名称空间内运行。还要注意,名称空间中的根进程(pid 1)不是 init,而是我们在创建名称空间时指定的 bash shell。
- 让我们在相同的名称空间中创建另一个进程,它在后台休眠 1000 秒。在我的例子中,睡眠进程的 pid 是 PID 名称空间中的 44 。


- 在单独的终端上,检查从主机看到的睡眠进程的进程 id。

请注意 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"。如果您安装了这些工具,您将看到如下输出:

如果没有,用sudo yum install libcgroup-tools -y安装工具。
- 现在,我们创建一个名为 mem_group 的内存 cgroup,用“root”作为 cgroup 的所有者。执行的命令
sudo cgcreate -a root -g memory:mem_group。验证是否创建了 cgroup。

/sys/fs/cgroup/<cgroup type>是伪文件系统,其中新创建的 cgroup 被添加为子组。
- Memory cgroup 对 cgroup 中进程的内存使用进行限制。让我们看看 mem_group 的限制是什么。用于检查内存限制的文件是 memory.limit_in_bytes( 更多信息请点击这里,如果你感兴趣的话)。

- 请注意,mem_group 继承了其父 cgroup 的限制

- 现在,为了演示的目的,让我们将内存使用限制减少到 20KB(实际限制四舍五入到最接近的 2 的幂)。

这个限制太低了,因此大多数附加到 mem_group 的进程应该被 OOM 杀死。
- 创建一个新的 shell 并将其附加到 cgroup。我们需要 sudo 权限。

进程如预期的那样被 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 容器化
介绍
自 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 引擎遵循客户端-服务器架构。它由 3 个部分组成:
- Docker 客户端
这是用户直接与之交互的组件。当您执行我们之前看到的 docker 命令(push、pull、container ls、image ls)时,我们实际上是在使用 docker 客户端。一个 docker 客户端可以与多个 docker 守护进程通信。
- REST API
为 docker 客户端和守护进程提供了一个通信接口。
- 坞站守护程序(服务器)
这是 docker 引擎的主要组件。它从 dockerfile 构建图像,从 docker registry 获取图像,将图像推送到 registry,停止、启动容器等。它还管理容器之间的网络。
实验室
官方 docker github 为学习 docker 提供了几个级别的实验室。我们正在链接一个实验室,我们发现它非常适合从零开始的人。请按照以下顺序完成实验:
这是另一个初级实验室,用于编写 MERN (Mongo + React + Express)应用,很容易上手。
Docker 的高级功能
虽然我们已经介绍了容器化的基础知识以及如何将一个独立的应用 dockerized 化,但是现实世界中的进程需要相互通信。这种需求在遵循微服务架构的应用中尤其普遍。
Docker networks
Docker 网络促进了运行在相同主机甚至不同主机上的容器之间的交互。docker network 命令提供了几个选项,用于指定容器如何与主机和其他容器进行交互。host选项允许与主机共享网络堆栈,bridge允许在同一主机上运行但不在主机外部的容器之间进行通信,overlay促进连接到同一网络的主机之间的容器之间的交互,以及macvlan为传统容器分配单独的 MAC 地址,这些是 Docker 支持的一些重要网络类型。然而,这超出了本模块的范围。在 docker networks 上的官方文档本身就是一个很好的起点。
卷
除了映像、容器和网络,Docker 还提供了在容器中创建和挂载卷的选项。一般来说,docker 容器中的数据是非持久的,也就是说,一旦你取消了容器,数据就会丢失。卷用于在容器中存储持久数据。这个 Docker 实验室是开始玩体积的好地方。
在下一节中,我们将看到 Kubernetes 是如何协调容器部署的。
使用 Kubernetes 的编排
介绍
现在我们终于到了最期待的部分:大规模运行和管理容器。到目前为止,我们已经看到了 Docker 如何帮助管理容器的生命周期,以及如何提高应用的可移植性。Docker 确实提供了一个解决方案来简化容器的大规模部署(如果感兴趣,您可以查看 Docker Swarm ),它与 Docker 容器集成得很好。然而,Kubernetes 已经成为在大型分布式环境中编排微服务(作为容器)管理的事实上的工具。
让我们来看看 SREs 对使用容器编排工具特别是 Kubernetes 的兴趣点。
使用 Kubernetes 的动机
- 易用性
虽然 Kubernetes 有一个陡峭的学习曲线,但一旦学会,就可以作为一站式工具来管理您的微服务。只需一个命令,就可以部署成熟的生产就绪环境。应用的期望状态需要记录为 YAML 清单,Kubernetes 为您管理应用。
- 确保资源的最佳利用
我们可以指定部署中每个容器使用的资源限制。我们还可以指定我们选择的节点,Kubernetes 可以在这些节点上调度要部署的节点(例如,具有高 CPU 消耗的微服务可以被指示部署在高计算节点中)。
- 容错
自我修复是 Kubernetes 的基本资源类型。这消除了从头开始设计容错应用系统的麻烦。这尤其适用于无状态应用。
- 与基础设施无关
Kubernetes 没有供应商锁定。它可以设置在多个云环境或本地数据中心。
- 强大的社区支持和文档
Kubernetes 是开源的,有很多技术,比如运营商、服务网格等。由社区构建,用于更好地管理和监控 Kubernetes 编排的应用。
- 可扩展和定制
我们可以构建我们的定制资源定义,这些定义符合我们管理应用的用例,并使用 Kubernetes 来管理它们(使用定制控制器)。
如果你对这个话题比较感兴趣,可以看看这篇文章。
库伯内特斯的建筑
这里有一个图表(来自 Kubernetes 官方文档)包含了使 Kubernetes 工作的不同组件:

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 deploy和kubectl 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 附加一个处理程序,当你按下
让我们举一个处理 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 倍,这对生产环境来说是灾难性的。


浙公网安备 33010602011771号