Julia Evans 发布的文章

我一直在努力学习关于 IPv6 的相关知识。一方面,IPv6 的基础概念是很简单的(没有足够的 IPv4 地址可以满足互联网上的所有设备,所以人们发明了 IPv6!每个人都能有足够的 IPv6 地址!)

但是当我试图进一步理解它时,我遇到了很多问题。其中一个问题是:为什么 twitter.com 不支持 IPv6。假设,网站不支持 IPv6 并不会造成很多困难,那么为什么网站需要支持 IPv6 呢?

我在 Twitter 上询问了很多人 为什么他们的服务器支持 IPv6,我得到了很多很好的答案,我将在这里总结一下。事先说明一下,因为我对 IPv6 基本上毫无经验,所以下面所总结的理由中可能会有写得不准确的地方,请大家多多包涵。

首先,我想解释一下为什么 twitter.com 可以不支持 IPv6,因为这是最先让我困惑的地方。

怎么知道 twitter.com 不支持 IPv6 呢?

你可以使用 dig 命令以 AAAA 的选项查询某一个域名的 IPv6 地址记录,如果没有记录,则表明该域名不支持 IPv6。除了 twitter.com,还有一些大型网站,如 github.comstripe.com 也不支持 IPv6。

$ dig AAAA twitter.com
(empty response)
$ dig AAAA github.com
(empty response)
$ dig AAAA stripe.com
(empty response)

为什么 twitter.com 仍然适用于 IPv6 用户?

我发现这真的很令人困惑。我一直听说因为 IPv4 地址已经用完了,从而很多互联网用户被迫要使用 IPv6 地址。但如果这是真的,twitter.com 怎么能继续为那些没有 IPv6 支持的人提供服务呢?以下内容是我昨天从 Twitter 会话中学习到的。

互联网服务提供商(ISP)有两种:

  1. 能为所有用户拥有足够 IPv4 地址的 ISP
  2. 不能为所有用户拥有足够 IPv4 地址的 ISP

我的互联网服务提供商属于第 1 类,因此我的计算机有自己的 IPv4 地址,实际上我的互联网服务提供商甚至根本不支持 IPv6。

但是很多互联网服务提供商(尤其是北美以外的)都属于第 2 类:他们没有足够的 IPv4 地址供所有用户使用。这些互联网服务提供商通过以下方式处理问题:

  • 为所有用户提供唯一的 IPv6 地址,以便他们可以直接访问 IPv6 网站
  • 让用户 共享 IPv4 地址,这可以使用 CGNAT(“ 运营商级 NAT carrier-grade NAT ”)或者“464XLAT”或其他方式。

所有互联网服务提供商都需要 一些 IPv4 地址,否则他们的用户将无法访问 twitter.com 等只能使用 IPv4 的网站。

为什么网站要支持 IPv6?

现在,我们已经解释了为什么可以 不支持 IPv6。那为什么要支持 IPv6 呢?有下面这些原因。

原因一:CGNAT 是一个性能瓶颈

对我而言,支持 IPv6 最有说服力的论点是:CGNAT 是一个瓶颈,它会导致性能问题,并且随着对 IPv4 地址的访问变得越来越受限,它的性能会变得更糟。

有人也提到:因为 CGNAT 是一个性能瓶颈,因此它成为了一个有吸引力的拒绝服务攻击(DDoS)的目标,因为你可以通过攻击一台服务器,影响其他用户对该服务器的网站的可用性。

支持 IPv6 的服务器减少了对 CGNAT 的需求(IPv6 用户可以直接连接!),这使得互联网对每个人的响应速度都更快了。

我认为这个论点很有趣,因为它需要各方的努力——仅仅你的网站支持 IPv6,并不会让你的网站更好地运行,而更重要的是如果 几乎每个网站 都支持 IPv6,那么它将使每个人的互联网体验更好,尤其对于那些无法轻松访问 IPv4 地址的国家/地区。

实际上,我不知道这在实践中会有多大的关系。

不过,使用 IPv6 还有很多更自私的论点,所以让我们继续探讨吧。

原因二:只能使用 IPv6 的服务器也能够访问你的网站

我之前说过,大多数 IPv6 用户仍然可以通过 NAT 方式访问 IPv4 的网站。但是有些 IPv6 用户是不能访问 IPv4 网站的,因为他们发现他们运行的服务器只有 IPv6 地址,并且不能使用 NAT。因此,这些服务器完全无法访问只能使用 IPv4 的网站。

我想这些服务器并没有连接很多主机,也许它们只需要连接到一些支持 IPv6 的主机。

但对我来说,即使没有 IPv4 地址,一台主机也应该能够访问我的站点。

原因三:更好的性能

对于同时使用 IPv4 和 IPv6(即具有专用 IPv6 地址和共享 IPv4 地址)的用户,IPv6 通常更快,因为它不需要经过额外的 NAT 地址转换。

因此,有时支持 IPv6 的网站可以为用户提供更快的响应。

在实际应用中,客户端使用一种称为“Happy Eyeballs”的算法,该算法能够从 IPv4 和 IPv6 中为用户选择一个最快的链接。

以下是网站支持 IPv6 的一些其他性能优势:

  • 使用 IPv6 可以提高搜索引擎优化(SEO),因为 IPv6 具有更好的性能。
  • 使用 IPv6 可能会使你的数据包通过更好(更快)的网络硬件,因为相较于 IPv4,IPv6 是一个更新的协议。

原因四:能够恢复 IPv4 互联网中断

有人说他碰到过由于意外的 BGP 中毒,而导致仅影响 IPv4 流量的互联网中断问题。

因此,支持 IPv6 的网站意味着在中断期间,网站仍然可以保持部分在线。

原因五:避免家庭服务器的 NAT 问题

将 IPv6 与家庭服务器一起使用,会变得简单很多,因为数据包不必通过路由器进行端口转发,因此只需为每台服务器分配一个唯一的 IPv6 地址,然后直接访问服务器的 IPv6 地址即可。

当然,要实现这一点,客户端需要支持 IPv6,但如今越来越多的客户端也能支持 IPv6 了。

原因六:为了拥有自己的 IP 地址

你也可以自己购买 IPv6 地址,并将它们用于家庭网络的服务器上。如果你更换了互联网服务提供商,可以继续使用相同的 IP 地址。

我不太明白这是如何工作的,是如何让互联网上的计算机将这些 IP 地址路由转发给你的?我猜测你需要运行自己的自治系统(AS)或其他东西。

原因七:为了学习 IPv6

有人说他们在安全领域中工作,为保证信息安全,了解互联网协议的工作原理非常重要(攻击者正在使用互联网协议进行攻击!)。因此,运行 IPv6 服务器有助于他们了解其工作原理。

原因八:为了推进 IPv6

有人说因为 IPv6 是当前的标准,因此他们希望通过支持 IPv6 来为 IPv6 的成功做出贡献。

很多人还说他们的服务器支持 IPv6,是因为他们认为只能使用 IPv4 的网站已经太“落后”了。

原因九:IPv6 很简单

我还得到了一堆“使用 IPv6 很容易,为什么不用呢”的答案。在所有情况下添加 IPv6 支持并不容易,但在某些情况下添加 IPv6 支持会是很容易的,有以下的几个原因:

  • 你可以从托管公司自动地获得 IPv6 地址,因此你只需要做的就是添加指向该地址的 AAAA 记录
  • 你的网站是基于支持 IPv6 的内容分发网络(CDN),因此你无需做任何额外的事情

原因十:为了实施更安全的网络实验

因为 IPv6 的地址空间很大,所以如果你想在网络中尝试某些东西的时候,你可以使用 IPv6 子网进行实验,基本上你之后不会再用到这个子网了。

原因十一:为了运行自己的自治系统(AS)

也有人说他们为了运行自己的自治系统(我在这篇 BGP 帖子 中谈到了什么是 AS),因此在服务器中提供 IPv6。IPv4 地址太贵了,所以他们为运行自治系统而购买了 IPv6 地址。

原因十二:IPv6 更加安全

如果你的服务器 有公共的 IPv6 地址,那么攻击者扫描整个网络,也不能轻易地找出你的服务器地址,这是因为 IPv6 地址空间太大了以至于不能扫描出来!

这显然不能是你仅有的安全策略,但是这是安全上的一个大大的福利。每次我运行 IPv4 服务器时,我都会惊讶于 IPv4 地址一直能够被扫描出来的脆弱性,就像是老版本的 WordPress 博客系统那样。

一个很傻的理由:你可以在你的 IPv6 地址中放个小彩蛋

IPv6 地址中有很多额外的位,你可以用它们做一些不重要的事情。例如,Facebook 的 IPv6 地址之一是“2a03:2880:f10e:83:face:b00c:0:25de”(其中包含 face:b00c)。

理由还有很多

这就是到目前为止我所了解的“为什么支持 IPv6?”的理由。

在我理解这些原因后,相较于以前,我在我的(非常小的)服务器上支持 IPv6 更有动力了。但那是因为我觉得支持 IPv6,对我来说只需要很少的努力。(现在我使用的是支持 IPv6 的 CDN,所以我基本上不用做什么额外的事情)

我仍然对 IPv6 知之甚少,但是在我的印象中,支持 IPv6 并不是不需要花费精力的,实际上可能需要大量工作。例如,我不知道 Twitter 在其边缘服务器上添加 IPv6 支持需要做多少繁杂的工作。

其它关于 IPv6 的问题

这里还有一些关于 IPv6 的问题,也许我之后再会探讨:

  • 支持 IPv6 的缺点是什么?什么会出错呢?
  • 对于拥有了足够 IPv4 地址的 ISP 来说,有什么让他们提供 IPv6 的激励措施?(另一种问法是:我的 ISP 是否有可能在未来几年内转为支持 IPv6?或者他们可能不会支持 IPv6?)
  • Digital Ocean (LCTT 译注:一家建立于美国的云基础架构提供商,面向软件开发人员提供虚拟专用服务器(VPS))只提供 IPv4 的浮动地址,不提供 IPv6 的浮动地址。为什么不提供呢?有更多 IPv6 地址,那提供 IPv6 的浮动地址不是变得更 便捷 吗?
  • 当我尝试 ping IPv6 地址时(例如 example.com 的 IP 地址2606:2800:220:1:248:1893:25c8:1946),我得到一个报错信息 ping: connect: Network is unreachable。这是为什么呢?(回答:因为我的 ISP 不支持 IPv6,所以我的电脑没有公共 IPv6 地址)

这篇 来自 Tailscale 的 IPv4 与 IPv6 文章 非常有意思,并回答了上述的一些问题。


via: https://jvns.ca/blog/2022/01/29/reasons-for-servers-to-support-ipv6/

作者:Julia Evans 选题:lujun9972 译者:chai001125 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

你们好!我一直在编写一本关于调试的杂志(这是 目录的初稿)。

作为其中的一部分,我认为阅读一些关于调试的学术论文可能会很有趣,上周 Greg Wilson 给我发了一些关于调试学术研究的论文。

其中一篇论文(《[建立一个调试教学的框架[付费墙]](https://dl.acm.org/doi/abs/10.1145/3286960.3286970)》)对我们有效调试所需的不同种类的知识/技能进行了分类,我非常喜欢。它来自另一篇关于故障排除的更一般性的论文:《学会排错:一个新的基于理论的设计架构》。

我认为这个分类对于思考如何更好地进行调试是一个非常有用的结构,所以我把论文中的五个类别重新规划为你可以采取的行动,以提高调试的效率。

以下是这些行动:

1、学习代码库

要调试一些代码,你需要了解你正在使用的代码库。

这似乎有点显而易见(当然,不了解代码的工作原理,你就无法调试代码!)

这种学习随着时间的推移会很自然地发生,而且实际上调试也是 学习 一个新的代码库如何工作的最好方法之一—— 看到一些代码是如何崩溃的,有助于你了解它是如何工作的。

该论文将此称为“系统知识”。

2、学习系统

论文中提到,你需要了解编程语言,但我认为不止于此 —— 为了修复 bug,往往你需要学习很多更广泛的环境,而不仅仅是语言。

举个例子,如果你是后端 Web 开发者,你可能需要的一些“系统”知识包括:

  • HTTP 缓存如何工作
  • CORS
  • 数据库事务是如何工作的

我发现我经常需要更有意识地去学习像这样的系统性的东西 —— 我需要真正花时间去查找和阅读它们。

该论文将此称为“领域知识”。

3、学习你的工具

现在有很多工具,例如:

  • 调试器(GDB 等)
  • 浏览器开发工具
  • 剖析器 profiler
  • strace / ltrace
  • tcpdump / wireshark
  • 核心转储
  • 甚至像错误信息这样的基本东西(如何正确阅读它们)

我在这个博客上写了很多关于调试工具的文章,并且肯定学习这些工具给我带来了巨大的变化。

该论文将此称为“处理性知识”。

4、学习策略

这是最模糊的一类,在如何高效调试的过程中,我们都有很多策略和启发式方法。比如说:

  • 写一个单元测试
  • 写一个小的独立程序来重现这个错误
  • 找到一个能工作的版本的代码,看看有什么变化
  • 打印出无数的东西
  • 增加额外的日志记录
  • 休息一下
  • 向朋友解释这个错误,然后在中途发现问题所在
  • 查看 GitHub 上的问题,看看是否有匹配的问题

在写这本杂志的时候,我一直在思考这个类别,但我想让这篇文章简短,所以我不会在这里多说。

该论文将此称为“战略知识”。

5、获得经验

最后一个类别是“经验”。这篇论文对此有一个非常有趣的评论:

他们的研究结果并没有显示出新手和专家所采用的策略有什么明显的区别。专家只是形成了更多正确的假设,并且在寻找故障方面更有效率。作者怀疑这个结果是由于新手和专家之间的编程经验不同造成的。

这真的引起了我的共鸣 —— 我遇到过很多第一次遇到时非常令人沮丧和困难的 bug,而在第五次、第十次或第二十次时就非常简单了。

对我来说,这也是最直接的知识类别之一 —— 你需要做的就是调查一百万个 bug,反正这就是我们作为程序员的全部生活 : ) 。这需要很长的时间,但我觉得它发生得很自然。

本文将此称为“经验知识”。

就这样吧!

我打算把这篇文章写得很短,我只是非常喜欢这个分类,想把它分享出来。


via: https://jvns.ca/blog/2022/08/30/a-way-to-categorize-debugging-skills/

作者:Julia Evans 选题:lkxed 译者:aftermath0703 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

我对 终端 Terminal 是怎么回事困惑了很久。

但在上个星期,我使用 xterm.js 在浏览器中显示了一个交互式终端,我终于想到要问一个相当基本的问题:当你在终端中按下键盘上的一个键(比如 Delete,或 Escape,或 a),发送了哪些字节?

像往常一样,我们将通过做一些实验来回答这个问题,看看会发生什么 : )

远程终端是非常古老的技术

首先,我想说的是,用 xterm.js 在浏览器中显示一个终端可能看起来像一个新事物,但它真的不是。在 70 年代,计算机很昂贵。因此,一个机构的许多员工会共用一台电脑,每个人都可以有自己的 “终端” 来连接该电脑。

例如,这里有一张 70 年代或 80 年代的 VT100 终端的照片。这看起来像是一台计算机(它有点大!),但它不是 —— 它只是显示实际计算机发送的任何信息。

https://creativecommons.org/licenses/by/2.0, via Wikimedia Commons">DEC VT100终端

当然,在 70 年代,他们并没有使用 Websocket 来做这个,但来回发送的信息的方式和当时差不多。

(照片中的终端是来自西雅图的 活电脑博物馆 Living Computer Museum ,我曾经去过那里,并在一个非常老的 Unix 系统上用 ed 编写了 FizzBuzz,所以我有可能真的用过那台机器或它的一个兄弟姐妹!我真的希望活电脑博物馆能再次开放,能玩到老式电脑是非常酷的。)

发送了什么信息?

很明显,如果你想连接到一个远程计算机(用 ssh 或使用 xterm.js 和 Websocket,或其他任何方式),那么需要在客户端和服务器之间发送一些信息。

具体来说:

客户端 需要发送用户输入的键盘信息(如 ls -l)。 服务器 需要告诉客户端在屏幕上显示什么。

让我们看看一个真正的程序,它在浏览器中运行一个远程终端,看看有哪些信息会被来回发送!

我们将使用 goterm 来进行实验

我在 GitHub 上发现了这个叫做 goterm 的小程序,它运行一个 Go 服务器,可以让你在浏览器中使用 xterm.js 与终端进行交互。这个程序非常不安全,但它很简单,很适合学习。

复刻了它,使它能与最新的 xterm.js 一起工作,因为它最后一次更新是在 6 年前。然后,我添加了一些日志语句,以打印出每次通过 WebSocket 发送/接收的字节数。

让我们来看看在几个不同的终端交互过程中的发送和接收情况吧!

示例:ls

首先,让我们运行 ls。下面是我在 xterm.js 终端上看到的情况:

~:/play$ ls
file
~:/play$

以下是发送和接收的内容:(在我的代码中,我记录了每次客户端发送的字节:sent: [bytes],每次它从服务器接收的字节:recv: [bytes]

sent: "l"
recv: "l"
sent: "s"
recv: "s"
sent: "\r"
recv: "
\x1b[?2004l\r"
recv: "file
"
recv: "\x1b[~:/play$ "

我在这个输出中注意到 3 件事:

  1. 回显:客户端发送 l,然后立即收到一个 l 发送回来。我想这里的意思是,客户端真的很笨 —— 它不知道当我输入l 时,我想让 l 被回显到屏幕上。它必须由服务器进程明确地告诉它来显示它。
  2. 换行:当我按下回车键时,它发送了一个 \r'(回车)符号,而不是\n'(换行)。
  3. 转义序列:\x1b 是 ASCII 转义字符,所以 \x1b[?2004h 是告诉终端显示什么或其他东西。我想这是一个颜色序列,但我不确定。我们稍后会详细讨论转义序列。

好了,现在我们来做一些稍微复杂的事情。

示例:Ctrl+C

接下来,让我们看看当我们用 Ctrl+C 中断一个进程时会发生什么。下面是我在终端中看到的情况:

~:/play$ cat
^C
~:/play$

而这里是客户端发送和接收的内容。

sent: "c"
recv: "c"
sent: "a"
recv: "a"
sent: "t"
recv: "t"
sent: "\r"
recv: "
\x1b[?2004l\r"
sent: "\x03"
recv: "^C"
recv: "
"
recv: "\x1b[?2004h"
recv: "~:/play$ "

当我按下 Ctrl+C 时,客户端发送了 \x03。如果我查 ASCII 表,\x03 是 “文本结束”,这似乎很合理。我认为这真的很酷,因为我一直对 Ctrl+C 的工作原理有点困惑 —— 很高兴知道它只是在发送一个 \x03 字符。

我相信当我们按 Ctrl+C 时,cat 被中断的原因是服务器端的 Linux 内核收到这个 \x03 字符,识别出它意味着 “中断”,然后发送一个 SIGINT 到拥有伪终端的进程组。所以它是在内核而不是在用户空间处理的。

示例:Ctrl+D

让我们试试完全相同的事情,只是用 Ctrl+D。下面是我在终端看到的情况:

~:/play$ cat
~:/play$

而这里是发送和接收的内容:

sent: "c"
recv: "c"
sent: "a"
recv: "a"
sent: "t"
recv: "t"
sent: "\r"
recv: "
\x1b[?2004l\r"
sent: "\x04"
recv: "\x1b[?2004h"
recv: "~:/play$ "

它与 Ctrl+C 非常相似,只是发送 \x04 而不是 \x03。很好!\x04 对应于 ASCII “传输结束”。

Ctrl + 其它字母呢?

接下来我开始好奇 —— 如果我发送 Ctrl+e,会发送什么字节?

事实证明,这只是该字母在字母表中的编号,像这样。

  • Ctrl+a => 1
  • Ctrl+b => 2
  • Ctrl+c => 3
  • Ctrl+d => 4
  • ...
  • Ctrl+z => 26

另外,Ctrl+Shift+b 的作用与 Ctrl+b 完全相同(它写的是0x2)。

键盘上的其他键呢?下面是它们的映射情况:

  • Tab -> 0x9(与 Ctrl+I 相同,因为 I 是第 9 个字母)
  • Escape -> \x1b
  • Backspace -> \x7f
  • Home -> \x1b[H
  • End -> \x1b[F
  • Print Screen -> \x1b\x5b\x31\x3b\x35\x41
  • Insert -> \x1b\x5b\x32\x7e
  • Delete -> \x1b\x5b\x33\x7e
  • 我的 Meta 键完全没有作用

Alt 呢?根据我的实验(和一些搜索),似乎 AltEscape 在字面上是一样的,只是按 Alt 本身不会向终端发送任何字符,而按 Escape 本身会。所以:

  • alt + d => \x1bd(其他每个字母都一样)
  • alt + shift + d => \x1bD(其他每个字母都一样)
  • 诸如此类

让我们再看一个例子!

示例:nano

下面是我运行文本编辑器 nano 时发送和接收的内容:

recv: "\r\x1b[~:/play$ "
sent: "n" [[]byte{0x6e}]
recv: "n"
sent: "a" [[]byte{0x61}]
recv: "a"
sent: "n" [[]byte{0x6e}]
recv: "n"
sent: "o" [[]byte{0x6f}]
recv: "o"
sent: "\r" [[]byte{0xd}]
recv: "
\x1b[?2004l\r"
recv: "\x1b[?2004h"
recv: "\x1b[?1049h\x1b[22;0;0t\x1b[1;16r\x1b(B\x1b[m\x1b[4l\x1b[?7h\x1b[39;49m\x1b[?1h\x1b=\x1b[?1h\x1b=\x1b[?25l"
recv: "\x1b[39;49m\x1b(B\x1b[m\x1b[H\x1b[2J"
recv: "\x1b(B\x1b[0;7m  GNU nano 6.2 \x1b[44bNew Buffer \x1b[53b \x1b[1;123H\x1b(B\x1b[m\x1b[14;38H\x1b(B\x1b[0;7m[ Welcome to nano.  For basic help, type Ctrl+G. ]\x1b(B\x1b[m\r\x1b[15d\x1b(B\x1b[0;7m^G\x1b(B\x1b[m Help\x1b[15;16H\x1b(B\x1b[0;7m^O\x1b(B\x1b[m Write Out   \x1b(B\x1b[0;7m^W\x1b(B\x1b[m Where Is    \x1b(B\x1b[0;7m^K\x1b(B\x1b[m Cut\x1b[15;61H"

你可以看到一些来自用户界面的文字,如 “GNU nano 6.2”,而这些 \x1b[27m 的东西是转义序列。让我们来谈谈转义序列吧!

ANSI 转义序列

上面这些 nano 发给客户端的 \x1b[ 东西被称为“转义序列”或 “转义代码”。这是因为它们都是以 “转义”字符 \x1b 开头。它们可以改变光标的位置,使文本变成粗体或下划线,改变颜色,等等。维基百科介绍了一些历史,如果你有兴趣的话可以去看看。

举个简单的例子:如果你在终端运行

echo -e '\e[0;31mhi\e[0m there'

它将打印出 “hi there”,其中 “hi” 是红色的,“there” 是黑色的。本页 有一些关于颜色和格式化的转义代码的例子。

我认为有几个不同的转义代码标准,但我的理解是,人们在 Unix 上使用的最常见的转义代码集来自 VT100(博客文章顶部图片中的那个老终端),在过去的 40 年里没有真正改变。

转义代码是为什么你的终端会被搞乱的原因,如果你 cat 一些二进制数据到你的屏幕上 —— 通常你会不小心打印出一堆随机的转义代码,这将搞乱你的终端 —— 如果你 cat 足够多的二进制数据到你的终端,那里一定会有一个 0x1b 的字节。

可以手动输入转义序列吗?

在前面几节中,我们谈到了 Home 键是如何映射到 \x1b[H 的。这 3 个字节是 Escape + [ + H(因为 Escape\x1b)。

如果我在 xterm.js 终端手动键入 Escape ,然后是 [,然后是 H,我就会出现在行的开头,与我按下 Home 完全一样。

我注意到这在我的电脑上的 Fish shell 中不起作用 —— 如果我键入 Escape,然后输入 [,它只是打印出 [,而不是让我继续转义序列。我问了我的朋友 Jesse,他写过 一堆 Rust 终端代码,Jesse 告诉我,很多程序为转义代码实现了一个 超时 —— 如果你在某个最小的时间内没有按下另一个键,它就会决定它实际上不再是一个转义代码了。

显然,这在 Fish shell 中可以用 fish_escape_delay_ms 来配置,所以我运行了 set fish_escape_delay_ms 1000,然后我就能用手输入转义代码了。工作的很好!

终端编码有点奇怪

我想在这里暂停一下,我觉得你按下的键被映射到字节的方式是非常奇怪的。比如,如果我们今天从头开始设计按键的编码方式,我们可能不会把它设置成这样:

  • Ctrl + aCtrl + Shift + a 做的事情完全一样。
  • AltEscape 是一样的
  • 控制序列(如颜色/移动光标)使用与 Escape 键相同的字节,因此你需要依靠时间来确定它是一个控制序列还是用户只是想按 Escape

但所有这些都是在 70 年代或 80 年代或什么时候设计的,然后需要永远保持不变,以便向后兼容,所以这就是我们得到的东西 :smiley:

改变窗口大小

在终端中,并不是所有你能做的事情都是通过来回发送字节发生的。例如,当终端被调整大小时,我们必须以不同的方式告诉 Linux 窗口大小已经改变。

下面是 goterm 中用来做这件事的 Go 代码的样子:

syscall.Syscall(
    syscall.SYS_IOCTL,
    tty.Fd(),
    syscall.TIOCSWINSZ,
    uintptr(unsafe.Pointer(&resizeMessage)),
)

这是在使用 ioctl 系统调用。我对 ioctl 的理解是,它是一个系统调用,用于处理其他系统调用没有涉及到的一些随机的东西,通常与 IO 有关,我猜。

syscall.TIOCSWINSZ 是一个整数常数,它告诉 ioctl 我们希望它在本例中做哪件事(改变终端的窗口大小)。

这也是 xterm 的工作方式。

在这篇文章中,我们一直在讨论远程终端,即客户端和服务器在不同的计算机上。但实际上,如果你使用像 xterm 这样的终端模拟器,所有这些工作方式都是完全一样的,只是很难注意到,因为这些字节并不是通过网络连接发送的。

文章到此结束啦

关于终端,肯定还有很多东西要了解(我们可以讨论更多关于颜色,或者原始与熟化模式,或者 Unicode 支持,或者 Linux 伪终端界面),但我将在这里停止,因为现在是晚上 10 点,这篇文章有点长,而且我认为我的大脑今天无法处理更多关于终端的新信息。

感谢 Jesse Luehrs 回答了我关于终端的十亿个问题,所有的错误都是我的 :smiley:


via: https://jvns.ca/blog/2022/07/20/pseudoterminals/

作者:Julia Evans 选题:lujun9972 译者:wxy 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

你好! 我最近又开始运行一些服务器(nginx playgroundmess with dnsdns lookup),所以我一直在考虑监控问题。

最初我并不完全清楚如何监控这些网站,所以我想快速写下我是如何做到的。

我根本不打算谈如何监控大型的、严肃的关键任务网站,只谈微型的不重要的网站。

目标:在操作上几乎不花时间

我希望网站大部分时间都能正常工作,但我也希望不用在持续的运营上花费时间。

我最初对运行服务器非常警惕,因为在我的上一份工作中,我是 24/7 轮流值班,负责一些关键的服务,在我的印象中,“负责服务器”意味着“在凌晨 2 点被叫起来修理服务器”和“有很多复杂的仪表盘”。

所以有一段时间我只做静态网站,这样我就不用考虑服务器的问题。

但最终我意识到,我所要写的任何服务器的风险都很低,如果它们偶尔宕机 2 小时也没什么大不了的,我只需设置一些非常简单的监控来帮助它们保持运行。

没有监控很糟糕

起初,我根本没有为我的服务器设置任何监控。这样做的结果是非常可预见的:有时网站坏了,而我却没有发现,直到有人告诉我!

步骤 1:uptime 检查器

第一步是建立一个 uptime 检查器。外面有很多这样的东西,我现在使用的是 updown.iouptime robot。我更喜欢 updown 的用户界面和 定价 结构(它是按请求而不是按月收费),但 uptime 机器人有一个更慷慨的免费套餐。

它们会:

  1. 检查网站是否正常
  2. 如果出现故障,它会给我发电子邮件

我发现电子邮件通知对我来说是一个很好的通知级别,如果网站宕机,我会很快发现,但它不会吵醒我或做其它的什么打扰。

步骤 2:端到端的健康检查

接下来,让我们谈谈“检查网站是否正常”到底是什么意思。

起初,我只是把我的健康检查端点之一变成一个函数,无论如何都会返回 200 OK

这倒是挺有用的 – 它告诉我服务器是启动着的!

但不出所料,我遇到了问题,因为它没有检查 API 是否真的在 工作 – 有时健康检查成功了,尽管服务的其他部分实际上已经进入了一个糟糕的状态。

所以我更新了它,让它真正地发出 API 请求,并确保它成功了。

我所有的服务都只做了很少的事情(nginx playground 只有一个端点),所以设置一个健康检查是非常容易的,它实际上贯穿了服务应该做的大部分动作。

下面是 nginx playground 的端到端健康检查处理程序的样子。它非常基本:它只是发出一个 POST 请求(给自己),并检查该请求是成功还是失败。

    func healthHandler(w http.ResponseWriter, r *http.Request) {
        // make a request to localhost:8080 with `healthcheckJSON` as the body
        // if it works, return 200
        // if it doesn't, return 500
        client := http.Client{}
        resp, err := client.Post("http://localhost:8080/", "application/json", strings.NewReader(healthcheckJSON))
        if err != nil {
            log.Println(err)
            w.WriteHeader(http.StatusInternalServerError)
            return
        }
        if resp.StatusCode != http.StatusOK {
            log.Println(resp.StatusCode)
            w.WriteHeader(http.StatusInternalServerError)
            return
        }
        w.WriteHeader(http.StatusOK)
    }

健康检查频率:每小时一次

现在,我大部分健康检查每小时运行一次,有些每 30 分钟运行一次。

我每小时运行一次,因为 updown.io 的定价是按健康检查次数计算的,我正在监控 18 个不同的 URL,而且我想把我的健康检查预算保持在 5 美元/年的最低水平。

花一个小时来发现这些网站中的一个出现故障,对我来说是可以的 – 如果有问题,我也不能保证能很快修复它。

如果可以更频繁地运行它们,我可能会每 5-10 分钟运行一次。

步骤 3:第三步:如果健康检查失败,自动重新启动

我的一些网站在 fly.io 上,fly 有一个相当标准的功能,我可以为一个服务配置一个 HTTP 健康检查,如果健康检查失败,就重新启动服务。

“经常重启”是一个非常有用的策略来弥补我尚未修复的 bug,有一段时间,nginx playground 有一个进程泄漏,nginx 进程没有被终止,所以服务器的内存一直在耗尽。

通过健康检查,其结果是,每隔一天左右就会发生这样的情况:

  • 服务器的内存用完了
  • 健康检查开始失败
  • 它被重新启动
  • 一切又正常了
  • 几个小时后再次重复整个传奇

最终,我开始实际修复进程泄漏,但很高兴有一个解决方法可以在我拖延修复 bug 时保持运行。

这些用于决定是否重新启动服务的运行状况检查更频繁地运行:每 5 分钟左右。

这不是监控大型服务的最佳方式

这可能很明显,我在一开始就已经说过了,但是“编写一个 HTTP 健康检查”并不是监控大型复杂服务的最佳方法。 但我不会深入讨论,因为这不是这篇文章的主题。

到目前为止一直运行良好!

我最初在 3 个月前的四月写了这篇文章,但我一直等到现在才发布它以确保整个设置正常工作。

这带来了很大的不同 – 在我遇到一些非常愚蠢的停机问题之前,现在在过去的几个月里,网站的运行时间达到了 99.95%!


via: https://jvns.ca/blog/2022/07/09/monitoring-small-web-services/

作者:Julia Evans 选题:lujun9972 译者:geekpi 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

(LCTT 校注:作者原文已经大篇幅进行了修订更新,本文据之前的版本翻译。)

今天我在推特上发布了一些关于 OSI 模型如何与 TCP/IP 工作原理的实际表现不相符的观点,这让我思考——OSI 模型到底是什么?通过阅读推特上的一些回复发现,似乎至少存在三种不同的思考方式:

  1. TCP/IP 工作原理的字面描述
  2. 一个可以用来描述和比较很多不同的网络协议的抽象模型
  3. 对 1980 年代的一些计算机网络协议的字面描述,这些协议如今大多已不再使用

在这篇文章中,我不打算试图争辩以上哪一个才是“真正”的 OSI 模型——似乎不同的人以所有这些方式思考它。这不重要。

OSI 模型有七层

在我们讨论 OSI 模型的含义之前,让我们大致地讨论一下它是什么。它是一个抽象模型,用于描述网络如何在七个编号的层上工作:

  • 第一层:物理层
  • 第二层:数据链路层
  • 第三层:网络层
  • 第四层:传输层
  • 第五层:会话层
  • 第六层:表示层
  • 第七层:应用层

我不会再费时地去解释每一层的含义,网上有上千种解释可供查询。

OSI 模型:TCP/IP 工作原理的字面描述

首先,我想谈谈人们在实践中使用 OSI 模型的一种常见方式:作为对 TCP/IP 工作原理的字面描述。OSI 模型的某些层非常容易映射到 TCP/IP:

  • 第二层对应以太网
  • 第三层对应 IP
  • 第四层对应 TCP 或 UDP(或 ICMP 等)
  • 第七层对应 TCP 或 UDP 包内的任何内容(例如 DNS 查询)

这种映射对第二、三、四层很有意义——TCP 数据包有三个 标头 header 对应于这三个层(以太网标头、IP 标头和 TCP 标头)。

用数字来描述 TCP 数据包中的不同标头非常有用——如果你说“第二层”,很显然它位于第三层“下方”,因为二比三小。

“OSI 模型作为字面描述”的古怪之处在于,第五层和第六层并不真正对应于 TCP/IP 中的任何内容——我听说过很多关于第五层或第六层可能是什么的不同解释(你可以说第五层是 TLS 或其他东西!)但它们没有像第二、三、四层那样“每一层在 TCP 数据包中都有相应的标头”这样的明确对应关系。

此外,TCP/IP 的某些部分即使在第二层到第四层也不能很好地适应 OSI 模型——例如,哪一层是 ARP 数据包?ARP 数据包发送一些带有以太网标头的数据,这是否意味着它们是第三层?或是第二层?列出不同 OSI 层的维基百科文章将其归类为“第 2.5 层”,这并不令人满意。

因为 OSI 模型有时用于教授 TCP/IP,若搞不清楚它的哪些部分可以映射到 TCP/IP,而哪些部分不能,则会令人困惑。这才是真的问题。

OSI 模型:用于比较网络协议的一个抽象

我听说过的另一种关于 OSI 的思考方式是,它是一种抽象,可以用来在许多不同的网络协议之间进行类比。例如,如果你想了解蓝牙协议的工作原理,也许你可以使用 OSI 模型来帮助你——这是我在 这个网页 上找到的一张图表,显示了蓝牙协议如何适配 OSI 模型。

另一个例子是,这篇维基百科文章) 有一个 OSI 层列表,详细划分了哪些特定的网络协议对应于这些 OSI 层。

OSI 模型:一些过时协议的字面描述

维基百科上的一些非常简短的研究表明,除了对这七层的抽象描述之外,OSI 模型还包含了 一组实现这些层的特定协议。显然,这发生在 70 年代和 80 年代的 协议战争 时期,OSI 模型失败了,TCP/IP 则取得了胜利。

这就解释了为什么 OSI 模型无法与 TCP/IP 很好地对应,因为如果当时“获胜”的是 OSI 协议,那么 OSI 模型 完全对应于互联网网络的实际工作方式。

结语

我写这篇文章的初衷是,当我最初学习 OSI 模型时,我发现它非常令人困惑(所有这些层是什么?它们是真实存在的吗?这是网络的实际工作原理吗?发生了什么?)我希望有人告诉我这个只使用 TCP/IP 网络协议的人,只需了解 OSI 模型第二、三、四和七层与 TCP/IP 的关系,然后忽略它的所有其他内容即可。所以我希望这篇文章对某些人能有所帮助!


via: https://jvns.ca/blog/2021/05/11/what-s-the-osi-model-/

作者:Julia Evans 选题:lujun9972 译者:hanszhao80 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

大家好!我又写了一篇关于 我最喜欢的电脑工具 的文章。这一篇讲的是 Docker Compose!

本文主要就是讲一讲我对 Docker Compose 有多么满意啦(不讨论它的缺点)!咳咳,因为它总能够完成它该做的,并且似乎总能有效,更棒的是,它的使用还非常简单。另外,在本文中,我只讨论我是如何用 Docker Compose 来搭建开发环境的,而不涉及它在生产中的使用。

最近,我考虑了很多关于这种个人开发环境的搭建方式,原因是,我现在把所有的计算工作都搬到了一个私有云上,大概 20 美元/月的样子。这样一来,我就不用在工作的时候花时间去思考应该如何管理几千台 AWS 服务器了。

在此之前,我曾花了两天的时间,尝试使用其他的工具来尝试搭建一个开发环境,搭到后面,我实在是心累了。相比起来,Docker Compose 就简单易用多了,我非常满意。于是,我和妹妹分享了我的 docker-compose 使用经历,她略显惊讶:“是吧!你也觉得 Docker Compose 真棒对吧!” 嗯,我觉得我应该写一篇博文把过程记录下来,于是就有了你们看到的这篇文章。

我们的目标是:搭建一个开发环境

目前,我正在编写一个 Ruby on Rails 服务(它是一个计算机“调试”游戏的后端)。在我的生产服务器上,我安装了:

  • 一个 Nginx 服务器
  • 一个 Rails 服务
  • 一个 Go 服务(使用了 gotty 来代理一些 SSH 连接)
  • 一个 Postgres 数据库

在本地搭建 Rails 服务非常简单,用不着容器(我只需要安装 Postgres 和 Ruby 就行了,小菜一碟)。但是,我还想要把匹配 /proxy/* 的请求的发送到 Go 服务,其他所有请求都发送到 Rails 服务,所以需要借助 Nginx。问题来了,在笔记本电脑上安装 Nginx 对我来说太麻烦了。

是时候使用 docker-compose 了!

docker-compose 允许你运行一组 Docker 容器

基本上,Docker Compose 的作用就是允许你运行一组可以互相通信 Docker 容器。

你可以在一个叫做 docker-compose.yml 的文件中,配置你所有的容器。我在下方将贴上我为这个服务编写的 docker-compose.yml 文件(完整内容),因为我觉得它真的很简洁、直接!

version: "3.3"
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: password # yes I set the password to 'password'
  go_server:
    # todo: use a smaller image at some point, we don't need all of ubuntu to run a static go binary
    image: ubuntu
    command: /app/go_proxy/server
    volumes:
      - .:/app
  rails_server:
    build: docker/rails
    command: bash -c "rm -f tmp/pids/server.pid && source secrets.sh && bundle exec rails s -p 3000 -b '0.0.0.0'"
    volumes:
      - .:/app
  web:
    build: docker/nginx
    ports:
      - "8777:80" # this exposes port 8777 on my laptop

这个配置包含了两种容器。对于前面两个容器,我直接使用了现有的镜像(image: postgresimage: ubuntu)。对于后面两个容器,我不得不构建一个自定义容器镜像,其中, build: docker/rails 的作用就是告诉 Docker Compose,它应该使用 docker/rails/Dockerfile 来构建一个自定义容器。

我需要允许我的 Rails 服务访问一些 API 密钥和其他东西,因此,我使用了 source secrets.sh,它的作用就是在环境变量中预设一组密钥。

如何启动所有服务:先 “build” 后 “up”

我一直都是先运行 docker-compose build 来构建容器,然后再运行 docker-compose up 把所有服务启动起来。

你可以在 yaml 文件中设置 depends_on,从而进行更多启动容器的控制。不过,对于我的这些服务而言,启动顺序并不重要,所以我没有设置它。

网络互通也非常简单

容器之间的互通也是一件很重要的事情。Docker Compose 让这件事变得超级简单!假设我有一个 Rails 服务正在名为 rails_server 的容器中运行,端口是 3000,那么我就可以通过 http://rails_server:3000 来访问该服务。就是这么简单!

以下代码片段截取自我的 Nginx 配置文件,它是根据我的使用需求配置的(我删除了许多 proxy_set_headers 行,让它看起来更清楚):

location ~ /proxy.* {
  proxy_pass http://go_server:8080;
}
location @app {
  proxy_pass http://rails_server:3000;
}

或者,你可以参考如下代码片段,它截取自我的 Rails 项目的数据库配置,我在其中使用了数据库容器的名称(db):

development:
  <<: *default
  database: myproject_development
  host: db # <-------- 它会被“神奇地”解析为数据库容器的 IP 地址
  username: postgres
  password: password

至于 rails_server 究竟是如何被解析成一个 IP 地址的,我还真有点儿好奇。貌似是 Docker 在我的计算机上运行了一个 DNS 服务来解析这些名字。下面是一些 DNS 查询记录,我们可以看到,每个容器都有它自己的 IP 地址:

$ dig +short @127.0.0.11 rails_server
172.18.0.2
$ dig +short @127.0.0.11 db
172.18.0.3
$ dig +short @127.0.0.11 web
172.18.0.4
$ dig +short @127.0.0.11 go_server
172.18.0.5

是谁在运行这个 DNS 服务?

我(稍微)研究了一下这个 DNS 服务是怎么搭建起来的。

以下所有命令都是在容器外执行的,因为我没有在容器里安装很多网络工具。

第一步::使用 ps aux | grep puma,获取 Rails 服务的进程 ID。

找到了,它是 1837916!简单~

第二步::找到和 1837916 运行在同一个网络命名空间的 UDP 服务。

我使用了 nsenter 来在 puma 进程的网络命令空间内运行 netstat(理论上,我猜想你也可以使用 netstat -tupn 来只显示 UDP 服务,但此时,我的手指头只习惯于打出 netstat -tulpn)。

$ sudo nsenter -n -t 1837916 netstat -tulpn
Active Internet connections (only servers)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.11:32847        0.0.0.0:*               LISTEN      1333/dockerd
tcp        0      0 0.0.0.0:3000            0.0.0.0:*               LISTEN      1837916/puma 4.3.7
udp        0      0 127.0.0.11:59426        0.0.0.0:*                           1333/dockerd

我们可以看到,此时有一个运行在 59426 端口的 UDP 服务,它是由 dockerd 运行的!或许它就是我们要找的 DNS 服务?

第三步:确定它是不是我们要找的 DNS 服务

我们可以使用 dig 工具来向它发送一个 DNS 查询:

$ sudo nsenter -n -t 1837916 dig +short @127.0.0.11 59426 rails_server
172.18.0.2

奇怪,我们之前运行 dig 的时候,DNS 查询怎么没有发送到 59426 端口,而是发送到了 53 端口呢?这到底是怎么回事呀?

第四步:iptables

对于类似“这个服务似乎正运行在 X 端口上,但我却在 Y 端口上访问到了它,这是什么回事呢?”的问题,我的第一念头都是“一定是 iptables 在作怪”。

于是,我在运行了容器的网络命令空间内执行 iptables-save,果不其然,真相大白:

$ sudo nsenter -n -t 1837916 iptables-save
.... redacted a bunch of output ....
-A DOCKER_POSTROUTING -s 127.0.0.11/32 -p udp -m udp --sport 59426 -j SNAT --to-source :53
COMMIT

在输出中有一条 iptables 规则,它将 53 端口的流量发送到了 59426 上。哈哈,真有意思!

数据库文件储存在一个临时目录中

这样做有一个好处:我可以直接挂载 Postgres 容器的数据目录 ./tmp/db,而无需在我的笔记本电脑上管理 Postgres 环境。

我很喜欢这种方式,因为我真的不想在笔记本电脑上独自管理一个 Postgres 环境(我也真的不知道该如何配置 Postgres)。另外,出于习惯,我更喜欢让开发环境的数据库和代码放在同一个目录下。

仅需一行命令,我就可以访问 Rails 控制台

管理 Ruby 的版本总是有点棘手,并且,即使我暂时搞定了它,我也总是有点担心自己会把 Ruby 环境搞坏,然后就要修它个十年(夸张)。

(使用 Docker Compose)搭建好这个开发环境后,如果我需要访问 Rails 控制台 console (一个交互式环境,加载了所有我的 Rails 代码),我只需要运行一行代码即可:

$ docker-compose exec rails_server rails console
Running via Spring preloader in process 597
Loading development environment (Rails 6.0.3.4)
irb(main):001:0>

好耶!

小问题:Rails 控制台的历史记录丢失了

我碰到了一个问题:Rails 控制台的历史记录丢失了,因为我一直在不断地重启它。

不过,我也找到了一个相当简单的解决方案(嘿嘿):我往容器中添加了一个 /root/.irbrc 文件,它能够把 IRB 历史记录文件的保存位置指向一个不受容器重启影响的地方。只需要一行代码就够啦:

IRB.conf[:HISTORY_FILE] = "/app/tmp/irb_history"

我还是不知道它在生产环境的表现如何

到目前为止,这个项目的生产环境搭建进度,还停留在“我制作了一个 DigitalOcean droplet(LCCT 译注:一种 Linux 虚拟机服务),并手工编辑了很多文件”的阶段。

嗯……我相信以后会在生产环境中使用 docker-compose 来运行一下它的。我猜它能够正常工作,因为这个服务很可能最多只有两个用户在使用,并且,如果我愿意,我可以容忍它在部署过程中有 60 秒的不可用时间。不过话又说回来,出错的往往是我想不到的地方。

推特网友提供了一些在生产中使用 docker-compose 的注意事项:

  • docker-compose up 只会重启那些需要重启的容器,这会让重启速度更快。
  • 有一个 Bash 小脚本 wait-for-it,你可以用它来保持等待一个容器,直到另一个容器的服务可用。
  • 你可以准备两份 docker-compose.yaml 文件:用于开发环境的 docker-compose.yaml 和用于生产环境的 docker-compose-prod.yaml。我想我会在分别为 Nginx 指定不同的端口:开发时使用 8999,生产中使用 80
  • 人们似乎一致认为,如果你的项目是一台计算机上运行的小网站,那么 docker-compose 在生产中不会有问题。
  • 有个人建议说,如果愿意在生产环境搭建复杂那么一丢丢,Docker Swarm 就或许会是更好的选择,不过我还没试过(当然,如果要这么说的话,干嘛不用 Kubernetes 呢?Docker Compose 的意义就是它超级简单,而 Kubernetes 肯定不简单 : ))。

Docker 似乎还有一个特性,它能够 把你用 docker-compose 搭建的环境,自动推送到弹性容器服务(ESC)上,听上去好酷的样子,但是我还没有试过。

docker-compose 会有不适用的场景吗

我听说 docker-compose 在以下场景的表现较差:

  • 当你有很多微服务的时候(还是自己搭建比较好)
  • 当你尝试从一个很大的数据库中导入数据时(就像把几百 G 的数据存到每个人的笔记本电脑里一样)
  • 当你在 Mac 电脑上运行 Docker 时。我听说 Docker 在 macOS 上比在 Linux 上要慢很多(我猜想是因为它需要做额外的虚拟化)。我没有 Mac 电脑,所以我还没有碰到这个问题。

以上就是全部内容啦!

在此之前,我曾花了一整天时间,尝试使用 Puppet 来配置 Vagrant 虚拟机,然后在这个虚拟机里配置开发环境。结果,我发现虚拟机启动起来实在是有点慢啊,还有就是,我也不喜欢编写 Puppet 配置(哈哈,没想到吧)。

幸好,我尝试了 Docker Compose,它真好简单,马上就可以开始工作啦!


via: https://jvns.ca/blog/2021/01/04/docker-compose-is-nice/

作者:Julia Evans 选题:lujun9972 译者:lkxed 校对:turbokernel

本文由 LCTT 原创编译,Linux中国 荣誉推出