Nginx(ngx_lua) 过滤 10w 个 User ID

今天的工作太刺激了,一天下来正好解决了一个有意思的问题。晚上来记录一下。

上次解决了当有很大的 HTTP body,在 ngx_lua 里面读不到的情况后,还留下一个解决性能问题。上次提到,我们对于用户的每一个请求,都要根据一个 json 形式的规则,来判断怎么样路由这个用户。为了让读者更明白这个问题,举几个例子:

  1. 给出一个 10万元素的用户 ID 列表,如果用户的 ID 在这里面,并且请求 URL 是 xx,Cookie 含有 xx,就转发到 Server A
  2. 用户 ID 在列表转发出 10% 的用户到 Server B

此模块我是用 ngx_lua 写的,现在有问题的实现是这样的,将这个规则保存在 ngx.shared_dict 里面。每一个请求过来,我就解析成 lua 的 table,然后判断规则。我的测试环境是一台内网的服务器,单进程开 Nginx,wrk 测试是 1800~1900 request/sec。开启这个模块之后,只有 88 requests/sec,由于每一个请求都要经过这个模块,这样的延迟是无法接受的。

规则是发到每台机器的 Nginx 上,要在 Nginx 所有的 work process 共享一个变量,不知道除了 shared_dict 还有啥方法。其实我想过自己基于 shared_dict 实现保存 table,就是我把 table 打扁平,按照 key-value 放到 shared_dict,但是这项工作想想就挺大的,而且要踩坑才能保证正确性。

今天发了一个邮件到 openresty 社区(这个社区非常活跃和友好!),问了这个问题。mrluanma 和 tokers 回复说可以用 mlcache 。其实我之前也看了一下这个项目,但是没有看完文档,不知道靠不靠谱,既然大家可以说这么用,就去试一试了。

这其实是一个缓存,首先 L1 缓存是每一个 Nginx 进程里面的 Lua vm 会有缓存,如果没有命中,那么第二层缓存就是 ngx.shared_dict ,如果再没有命中,就会调用用户的 callback,也就是所谓的 L3. 由于是一个缓存项目,所以有一些缓存方面的问题,比如 dog pile,此模块都处理好了。我的用法比较特殊,只是拿它来做多个 worker/多个 HTTP requests 的共享数据,所以很多地方没有细看。

新建一个 cache 的 Nginx 代码如下,需要写在 http 里面。

这里要注意 3 个地方:

  1. 因为要调用 set() 方法,我们是主动更新规则的,而不是等他过期。调用 set() 和 update() 要提供 worker 之间通讯的方式。mlcache 实现了通过 shared_dict 来通讯,所以我只要另外申请一个 shared_dict ,然后将这个 shared_dict 设置给 ipc_shm 就好了。
  2. ttl 和 neg_ttl 设置成 0 ,理由同上。
  3. 通过 _G 可以执行全局的变量,这样 lua 就可以直接使用 cache 这个名字了。

然后在设置规则的时候,直接通过 cache 来调用即可:

读取规则也是一样:

今天栽在这里很长时间,文档说第二个参数是 optional 的,我以为就可以不填。然后 set() 就填了两个变量。结果调试半天(Lua 奇葩的变量不够 nil 来补)。后来才明白这个 optional 的意思是你可以填一个 nil 进去,因为未定义的变量就是 nil 啊!难怪呢,我还想 lua 怎么实现的,难不成判断函数调用的参数个数?

另外一个点是 get() 方法要提供一个 callback 函数,L2 没有命中的时候提供就执行这个函数。在我这里,如果 L2 是 nil,那么 L3 也返回 nil 好了。

显示调用 set() 的一个非常重要的点是:一定要通知其他 worker 删除 L1 缓存。不然我们调用 set() 只是更新了一个 worker 的 L1 和 L2。在本文的场景下,worker 的数据不一致导致转发规则不一致是有问题的。这里只要在 set() 的之后调用一下 purge() 之后,通知其他 L1 去删除自己的缓存就可以了。 这里之前写的 purge() 函数的使用是有错误的,purge() 是清除缓存,包括用于 worker 交流的 shm 和 lua-resty-lrucache 的缓存,导致所有 L1 和 L2 miss 然后去 L3 更新,所以开销是比较大的(虽然在我这里,整个缓存=我的一个 table)。

正确的用法是这样的,使用 set() 的话,要多加一步,在 get() 前面调用一下 update() 。从源代码和文档得知,它的工作原理是这样的:set() 内置会广播一条消息,然后更新 L1 缓存(仅自己的 worker)和 L2 缓存。get() 之前 update() 这个调用会队列里面的广播事件,如果有事件的话,就先消费掉事件,没有事件的话,就什么也不做。注意所谓的 update 并不是更新 L2 缓存,而是消费所有的事件的意思。这样就做到一个 worker 更新某个值之后保持和其他 worker 一致了。

下图的第一个 worker 先 set 了一个值,然后通过 shared_dict 广播出去这个值的 name,回调函数是从 L1 删除这个值。其他 worker 蓝色的箭头表示 get() 之前 update() 去检查是否有事件需要处理。

修改之后性能从 88 requests/sec 上升到 300+ requests/sec,所有提升,但还是很慢。平均下来一个请求的延迟增加 3ms 多。

然后我又顺着这 10w userid 进行优化,规则里面这个 uid 是一个很大的 List,所以逻辑上是遍历查找一个用户是不是在列表里面的,O(n) 的效率。主要的耗时点就在这里。我想改成用 Set 结构来存,这样只要 O(1) 复杂度就够了。

问题是,Json 只有 Dict 和 List 两种数据结构,这个回答说的很好:

  1. 编程实现 List 和 Set 互相转换是很简单的
  2. Json 用于数据交换,你不能信任一个数据输入是 Uniq 的

但是我觉得 Json 增加 {"foo", "bar", "banana"} 这种形式好像没有什么不妥。

Anyway,我只能自己实现了,写了一个函数,在 Json 转换成 table 之后,找到我想转换的 Key,将它的 value 从 array 形式的 table(key 是 1 2 3 4 5 …)改成 Set 形式的 table (key 是各个元素,value 为 true),代码如下:

这样,在找的时候,只要看 value 是不是 true 就可以了:

这样改了之后,性能上升到 1800 requests/sec,跟不开启这个模块相比,基本上没有性能损耗了。

 

话说回来,这个问题跟我上一家公司的面试我的时候出的题目很像:给你一个 IP 列表,内存可以随便用,但是查找速度要快,如何看一个 IP 是否在表中?

我当时的方法是,一个 IP 用一个 bit ,bit 位要么是 0 要么是 1,表示此 IP 在或不在。表示世界上所有的 IP(IPv4)需要 2^32 个bit = 536M,将 IP 列表中的 bit 都置为 1,其余为0. 这里的关键是 IP 如何映射成 bit 表,IP 其实是 4 个字节而已,直接用 4 个字节所表示的数字作为 index 就好了。

巧合的是,这个周我正好认识了布隆过滤器。哈,我的想法真先进。

 

使用 ngx_lua (openresty)正确读取 HTTP 请求 body

之前用 ngx_lua(openresty) 写了一个处理 HTTP 请求的程序,今天发现当发送的 HTTP 请求 body 很大的时候,发现老是报错,最后定位到 ngx.req.get_body_data() 这个函数返回 nil ,而不是真正的 body。

于是我去 ngx_lua 的文档看这个函数的文档,发现文档中说有三种情况下,这个函数的返回值会是 nil:

This function returns nil if

  1. the request body has not been read,
  2. the request body has been read into disk temporary files,
  3. or the request body has zero size.

第一条,我是读了文档的,知道要 Nginx 默认是不会读 body 的,要么打开读 body 的开关,要么显示调用一下 read。所以代码中已经调用了 ngx.req.read_body() 了,不会是这个原因。

第三条,也不可能,我通过 curl 发送的,body 肯定是发出去了(可以抓包验证)。

那么基本上就确定是这个 request body 被读到硬盘的临时文件里了。看到这里我猜应该是 Nginx 是将大的 HTTP 请求放到磁盘中而不是放到内存中。搜了一下文档,发现有这个参数:

当请求体的大小大禹 client_body_buffer_size 的时候,Nginx 将会把它存到一个临时文件中,而不是放到内存中。这个值的大小默认是内存页的两倍:32 位系统上是 8k,64位系统上是16k。

OK,问题找到了,现在解决方案有两个:1)调大这个值,我觉得是不合理的,这样会浪费内存。2)可以想办法读到临时文件中的大 body。ngx_lua 提供了配套的方法 ngx_req.get_body_file,注意这只是获得文件,还要在 lua 代码中打开读取文件。

所以最后,处理一个请求且还能正确处理很大 body 的请求的代码是这样的,其中高亮的部分,是核心的逻辑,先尝试从内存中读 body,如果读不到,就去临时文件中读。

我读这个实际的应用场景是,读出 body,按照 json 解析出来当做路由规则(需求是需要动态设置 HTTP 的路由规则,所以我用 ngx_lua 在 nginx 上新开了个端口监控这样的规则)。现在用很大的规则一测试,发现性能下降很厉害。存到文件、再读出文件是一方面。另一方面是 ngx_lua 是为每一个 HTTP 请求开一个 lua 协程处理,不能共享变量,只能通过 lua_shared_dict 来保存持久的变量。但是 lua_shared_dict 的问题是,这个 dict 只支持 “Lua booleans, numbers, strings, or nil”,解析出来的 json 是一个 lua 的 table,不能保存到 lua_shared_dict 里面,我只好保存一个字符串,然后对每一个 HTTP 请求 json decode 这个字符串了。不知道有没有更好的方法。

 

HTTP 长连接

本文介绍 HTTP 长连接的协议、历史,TCP 长连接,和一些客户端、服务器实现。

Disclaimer: 本文所引用的资料都会给出链接,如有疑问应该去原资料验证,如果存在错误请不吝指出。

HTTP/1.0 会为每一个 HTTP 请求都建立 TCP 连接,这样显然是很低效的,所以就有人想在同一个 TCP 连接上发送多个 HTTP 请求,这样就省掉后面 TCP 连接建立的三次握手了。这就是“长连接”,英文里面也有其他的名字:HTTP Persistent Connection,HTTP Keep-Alive,或 HTTP Connection Reuse。

可以用 nc 来测试一下,这里我用 nc 连接到 httpbin.org 的服务器,然后用 tcpdump 抓包看其中的 TCP 连接情况。

用 nc 打开连接后,输入和输出如下。其中高亮的部分是我的输入,其余是服务器的输出:

可以看到我连续发送了两次 HTTP 请求,nc 都没有结束,并且还在等到我的输入,输入 ^c 之后才关闭,断开了连接。

抓包如下:

可以看到两次 HTTP 请求始终在同一个 TCP 连接上,只不过中间有很多保持心跳的 TCP 包。

HTTP/1.0 到 HTTP/1.1

在 HTTP/1.0 中,有人就尝试实现这样的长连接。实现的方式是客户端和服务器协商 Connection: Keep-Alive Header。加入浏览器支持长连接,那么就在 HTTP 请求的 Header 上添加 Connection: Keep-Alive ,如果服务器收到请求,就在 Response 中也加入这个 Header: Connection: Keep-Alive 。这样客户端再发送另一个请求的时候就会使用这个 TCP 连接,而不会新建一个。

然而,HTTP/1.0 的实现是错误的,主要的问题在代理。HTTP/1.0 的 Proxy Server 不懂这个字段,把它原样发给了后面的 Server,导致 Proxy 和 Server 之间建立了长连接,造成了一个 “Hung Connection”。有人提出一种处理的手段(RFC 7230 A.1.2),是想换一个字段来表示 Client 和 Proxy Server 之间的长连接 Proxy-Connection ,但是通常代理并不仅仅是一层,而是层层 HTTP 代理,这样依然就存在前面的问题,所以 Proxy-Connection 这个 Header 在任何情况下都用不到。

所以在 HTTP/1.0 使用长连接要小心,客户端要感知 hung 住的连接,要显示地关闭。因为 HTTP/1.0 的 Proxy 不支持长连接,所以在有 HTTP/1.0 Proxy 的情况下,不能使用长连接

然而,和 Proxy Server 的对话是长连接一个重要的使用场景,所以粗暴地规定不能对 Proxy 使用长连接是不能接受的。所以就需要一种新的机制,要满足以下的需求:1)对于旧版本的 Proxy Server ,忽略了长连接请求不会造成 Hung Connnection. 2)新版本的机制能够让 Real Server 和 Proxy Server 都能正确处理长连接。

HTTP/1.1 提出的方案(RFC2068 19.7.1)是:所有的 HTTP 连接默认都是长连接。当 Header 中添加 Connection: close 的时候,表示不保持长连接。这样可以满足上面说的需求,兼容旧版本的 Proxy Server(不会携带表示长连接的 Header)。

HTTP/1.1 的 Server 也可以跟 HTTP/1.0 的 Client 建立长连接,如果 Client 显式地携带了 Keep-Alive 的话。但是 HTTP/1.0 的客户端不支持 Chunked transfer-coding,所以必须每条信息都带上 Content-Length 表示边界。

另外除了 Connection 这个 Header,还有一个可选的 Header Keep-Alive 可以控制一些有关长连接的参数:

这个字段是可选的,只有带有参数的时候才生效。显而易见,这个 Header 必须和 Connection: Keep-Alive 一起发,毕竟它是控制长连接的,在没有长连接的情况下,这个 Header 就毫无意义。

最重要的一点,通过上面的描述你可能已经意识到了,就是 Connection: Keep-Alive 和下面要将的 Keep-Alive 这两个 Header 都是 hop-by-hop 的,就是说只对通讯的二者起作用,加入第二个人是 Proxy,那么 Proxy 和 Real Server 之间的连接是怎样的,就需要它们二者再自己商量了。

HTTP Keep-Alive Header

Hypertext Transfer Protocol (HTTP) Keep-Alive Header 这个 RFC 定义了 Keep-Alive Header 的一种形式。

为什么需要这些参数呢?

为了节省资源,Host 会选择关闭 idle 的长连接。比如一个连接很长时间没有使用,Host 会选择关闭它(显而易见)。

基于这个原因,很多客户端发送非幂等的请求的时候,会选择不复用现有的 idle 连接。理由如下,假设现在有一个非幂等请求进来,而恰好 Server 认为这个连接空闲了很长时间了,决定要关闭此连接。那么客户端收不到 HTTP 响应(也可能收不到 TCP 的 ACK),客户端就不知道这个非幂等请求到底被处理了没有。可能 Server 收到了这个请求之后又关闭的,也可能请求到达之前 Server 就关闭连接了。

所以很多客户端选择对所有非幂等请求都建立新的连接。但是每次都建立 TCP 连接,是很浪费资源的,也增加了请求的延迟。

假如 Client 知道 Server 的 timeout,客户端就可以在快要 timeout 的时候选择不使用这个已有的连接,或者发送请求来保证不超过 timeout。另外,如果客户端知道 timeout,那么在没有后续请求发送,而 timeout 时间又比较长的时候,客户端可以显示地要求 Server 关闭连接,释放资源。

Keep-Alive Header 的形式定义如下:

‘timeout’ 参数

timeout 参数代表当一个连接至少 idle 多长时间才会被关闭。它的 value 是一个代表秒数的 int 值。通常,连接上没有数据传输就被认为是 idle 的。但是不同的客户端和服务器实现对 idle 的理解不同,还受网络延迟的影响。所以建议客户端评估 idle 的时候算上网络延迟。

‘max’ 参数

max 参数表示在一个连接上客户端可以发送请求数的最大值。客户端在一个连接上请求的次数到了这个值,Server 就会关闭这个连接。

max 的 value 是一个 int 值,表示请求数。

对于客户端来说,收到了带有这个参数的 Response,就可以限制在同一个连接中发送的请求。举例说,假如客户端用一个队列来存放即将发送的请求,那么根据 max 就可以对队列分段。对于服务器来说,当服务器接收的请求达到了计数值之后就可以关闭连接。

Keep-Alive Extensions

除了这两个参数之外,也可以在 keep-alive-extension 字段中添加自定义的字段,如果服务器不理解这些字段,将忽略。

存在中间人的情况

在本文开头已经看到,HTTP 的长连接是基于 TCP 的长连接的。所以情况要复杂的多,实际情况有可能 TCP 保持长连接的时间要比 HTTP 短,HTTP 长连接 timeout 还没达到的时候,像 Net Address Translation (NAT) 这样的设备就将此 TCP 连接断开了,导致此连接不再可用。

HTTP/1.1 的中间人在转发请求的时候会直接丢掉这个 Header,因为它不需要认识这个 Header,它可以在任何时候断开连接

HTTP/1.0 的中间人,我们前面说过了,会导致错误。

此外,如果客户端(或中间人)感知到后面中间人的中间人(或中间 TCP 转发设备)的 timeout 更短的话,可能会修改这个值。Again,这个 Header 是 hop-by-hop 的。

从以下这个例子可以看出,每一端的连接都是独立协商的。

这里 Client 想要建立一个 timeout 为 10 分钟的长连接,但是 Proxy Server 只支持 120 秒,所以 Client 和 Proxy 之间的长连接最终是 120 秒的 timeout。Proxy 想和 Real Server 建立 1200 timeout 的长连接,但是 Real Server 将其降低到 300s。(PS:上图中 120 这个数字在 RFC 中写的是 5000,这样的话跟 RFC 的解释就冲突了,我不是很理解,我觉得 RFC 这里这个数字可能写错了。如果我理解错了,请指出)

如果 Upgraded HTTP Connections 的情况,就更复杂一些。如果 upgraded 的协议没有指明 timeout,那么会继承长连接初始化时候的 timeout,max 这个参数没有意义,因为升级后的 request 和 subrequest 被视为一个 HTTP 请求。客户端、中间人、服务器对 Upgraded 的策略可能不同,但是这个长连接的各个参数不再像上面一样可以分别独立协商,而是从 Client 到 Proxy 到 Real Server 的连接属性是一样的。

由于 keepalive 这个词被用在很多地方,而意义不尽相同,下面介绍一下 TCP 的 keepalive。

TCP Keepalive

对于 TCP 来说,Keepalive 并不是标准 TCP 协议规定的,所以 TCP 本身并不知道这个东西的存在,这只是在 TCP 之上的一个实现。

简单说,TCP Keepalive 就是设置一个 timer,时间一到就发送一个 probe packet,并设置 ACK。如果对方发送回来一个 ACK,那么那就知道这个 TCP 连接依然是可用的。如果对方没有发送 ACK 回来,那么就知道这个连接已经被断开了(实际的实现,一般会在收不到请求 ACK 的情况下重复发送多次 probe packet)。可以这么做是因为 1) TCP 是面向流的连接,而不是面向包的,所以在这个“流”中插入一个长度为 0 的包不会对这个流的内容造成任何影响。2)TCP 对每一个 packet 都会发送一个 ACK 确认。所以就可以用长度为 0 的一段“stream”来当做 probe。

那这个 TCP 的 Keepalive 有什么用呢?

主要有两个,第一是检查连接是否可用。假设 TCP 的另一端断电了,或者中间的某一个转发的设备断电了,那么通过检查连接的可用性,就可以确保不会在一个已经不能用的连接上发消息,不会有 false-positive。

第二是可以起到类似心跳的作用,防火墙或 NAT 设备的内存只能保存有限的连接数,它们普遍采用的策略是保留最近用到的连接,丢弃最旧没有有消息的连接。通过 Keepalive 的机制,我们可以让 NAT 设备保持我们的连接在可用列表中。

对比一下:HTTP 的 Keep-Alive Header 做的是设置长连接的 idle 时间,超过了这个时间就关闭连接,TCP Keepalive 设置的 timer 到了就发送空的 probe packet。

最后再提一个 Nginx 里面的 keepalive 指令,这个指令就更加迷惑了,跟上面介绍的都不搭边。它表示的是:Nginx 与 upstream 之间保持最多多少个 idle 的长连接。这个 idle 很关键,比如 keepalive 100 ,那么收到 300 个请求的时候,Nginx 和 upstream 建立 300 个长连接,这 300 个请求结束后,又来了 50 个请求,那么只有 50 个长连接是工作的,idle 的连接就有 250 个,Nginx 会关闭 150 个。更具体的例子可以看这里

 

参考资料:

 

SRE&Devops 每周分享 Issue #4 AWS Layer

SRE 监控的黄金指标

服务响应延迟、请求总量、错误数量、系统资源使用率是 Google SRE 提出的“四大黄金监控”指标,之后被很多团队实践。本文介绍了为什么这四个指标是了解系统当前状态最合适的四个指标。也提出了一些其他在提高系统可用性方面的实践。

Observability at Scale: Building Uber’s Alerting Ecosystem

Uber 的官方博客,介绍了它们的监控报警系统。Uber 有两个监控系统,一个是 uMonitor,从时序数据库中检测异常,发出报警(业务层面);一个是 Neris,监控基础设施,比如 CPU/Mem 等。文中分别介绍了这两个监控系统,以及如何报警、如果管理报警。

Why Use K-Means for Time Series Data? (Part One)

K-means 算法处理时序数据教程:

  1. Part1:如何使用统计学函数和 K-means 聚类从时序数据中检测异常
  2. Part2:代码实践,如何将 K-means 算法应用到时序数据,以及一些缺点
  3. Part3:如何使用 K-means 和 InfluxDB 在 EKG 数据中检测异常

Get Application Performance Metrics on Python Flask With Elastic APM on Kibana and Elasticsearch

很多 APM 客户端是开源的,Server 不开源。ELK 架构就很良心了。这是使用 Elastic APM 和 ES 的一个手把手教程。

Garbagedog: How eero does continuous monitoring of Java garbage collection

Eero 开发的一个监控 JVM GC 的组件(Python 编写)。Github

Open-Sourcing Our Incident Response Training

PagerDuty 开源了它们的应急反应培训资料。

Kubernetes Security — Are your Container Doors Open?

本文介绍了一些常见的 Kubernetes 误留下的漏洞,以及检查漏洞的方法。

Amazon Lambda 宣布 Layer 功能

Amazon Lambda 问世 4 年了,本周又宣布了重量级功能:Layer。Layer 可以允许不同语言编写的 Function 互相调用。之前,如果有不同的 Funtion 引用了相同的代码,那么就要将代码同引用代码一同打包发布。现在,你可以将被引用部分的代码单独发布,然后在 Function 中引用。Function 可以带有单独的版本控制,这样升级也不会影响所有的代码。

另外公布了 Runtime API,你可以提供一个可执行文件(比如 Python 解释器),然后 Lambda 会处理 Function 的调用和你的解释器执行代码这一层,你可以使用自己定制的 Runtime,也可以使用 AWS 官方制作的 Runtime。Runtime API 将是 AWS Lambda 将来支持新语言的方式。

这里有一个 awesome-layers 整理了一些 Runtime、可以调用的 Layer Function、以及一些监控的 Layer.

工具 twistlock/cloud-discovery

可以列举出你当前使用的所有云服务,原理就是你给它秘钥,它对接了每一个云平台服务商,会给你查询当前运行的各类云服务。举个例子就很明白了:

此工具以 Docker 形式提供,使用方便。主要可以用来审计,如果用了“弹性”的服务,这个工具就很实用了。我觉得可以和监控平台配合起来,每分钟扫描,记录下实时的资源使用。

另外它只使用读的 API,所以可以放心使用(不放心的话还是去看一下源代码)。

它的另一个功能是可以扫描 IP 端和端口,这样可以管理自建的云服务。

工具 dive

可以分析对比每一层 Docker image 都有哪些 diff,再也不用猜着做出更小的镜像了!

The Human Side of Microservices

InfoQ 对一位软件工程师的采访,涉及微服务、软件架构、devops 等话题。

 

SRE&Devops 每周分享 Issue #3

Scaling Spark Streaming for Logging Event Ingestion

在 Airbnb,用户的每一次搜索、预订都会产生 log。这些 log 可以帮助 Airbnb 更懂客户,为顾客和房东提供更好的服务。这些数据也会驱动业务发展和产品迭代——因为他们会反过来影响机器学习的模型和搜索排名。

Airbnb 的日志处理是近乎实时的。客户端和服务器将事件发往 Kafka,Spark Streaming Job 会持续从 Kafka 读出来然后放到 Hbase 中做去重,然后每小时从 Hbase dump 到 Hive 中。

这些日志是很多图表和控制台的元数据,所以保证这些数据的实时性和 SLA 就很重要。

然而,这些日志事件的量无法预估(由于促销、假期或者其他原因),而且事件会有偏斜(大小不一致),而 Kafka 的 partition 又不能自动水平扩展。这样就有很多挑战(具体见文),比如 HBase down 了一段时间,恢复之后无法给 Spark 分更多的资源,就要花很多时间追平。

另一方面,读日志最终还是存到 Hbase 中,所以时序并不重要,据此可以重新设计模型。Airbnb 开发出负载均衡的 Kafka reader,最终解决了文中描述的所有问题,可以水平扩展 reader。

Linux 系统网络管理员最常用的 32 个 Nmap 命令

Nmap 是一个开源的网络安全软件,它使用 IP 包来探测,可以统计网络上的 Hosts,这些 Hosts 提供的服务,运行的操作系统,安全、防火墙规则等。使用在监控方面非常方便。

Nginxconfig.io

一个可以自动根据你的选择生成 Nginx Conf 文件的网站。

Kubernetes Cheat Sheet

Red Hat 的有关 k8s 的介绍,以及如何在本地安装。

Starting and Scaling DevOps in the Enterprise

Gitlab 首页推荐的一本书,Devops 在大型企业的实践。

Cheat sheet: Linux networking

Linux 常用网络命令速查表

Ansible2.7: reboot plugin

Ansible2.1 就有了 win_reboot,因为 windows 重启的需求比 Linux 更频繁。Linux 的重启也可以通过 shell wait_for 来实现。但是有很多坑需要注意。

被折磨很久之后,reboot for linux 终于出现了。本文介绍了 reboot  这个 plugin 的设计、实现和用法。

Java 微服务:容器与框架的介绍 (By RedHat)

Orchestrating Chaos using Grab’s Experimentation Platform

Grab 做 Chaos 的经验分享。

Capacity planning for Etsy’s web and API clusters

Etsy 做容量规划的经验分享。

The Datacenter As A Computer: Designing Warehouse-Scale Machines, Third Edition

又名:Google 是如何建造数据中心的。第三版。读完可能要花一些力气,不过里面的图很酷炫。