SRE&Devops 每周分享 Issue #2

Python in RHEL 8

Red Hat 上周发布了 RHEL8,这是官网发布的一篇博客,讲了在 RHEL8 上面使用 Python 的一些注意事项,非常值得一读。

10 年前,Python 社区决定摆脱技术债,发布一个不向后兼容的版本 Python3,但是他们显然低估了这门语言的流行程度和带来的兼容性问题。

在 RHEL8 上面,Python3.6 是默认版本,但是也没有在系统默认安装,需要 yum install python3 。Python2 也是可以通过 yum 安装的: yum install python2 。

但是不带版本的命令 python 会导致 command not found 。因为现在有两拨人,一波认为默认 Python 应该是 Python2,用 Python3 的话应该显式键入 Python3。但是另一拨人应该向前看,默认 Python 应该换成 Python3 了。话说笔者在这周就遇到一个因为 Python 指向 Python37 导致 build 失败的 bug。在这方面,RHEL8 保留观点,默认的 Python 不指向任何命令,用户必须显示的写明版本(这样脚本也可以在任何版本的 Linux 下工作)。

既然 RHEL8 不自带 Python,那 yum 又是 Python 写的,yum 是怎么运行的呢?这个问题就留给读者自己去阅读此文吧。除此之外,这篇文中还写了如何正确使用 pip,如何正确使用 virtualenv,可以说都是一些“最佳实践”,非常值得一读。

Overload Control for Scaling WeChat Microservices

这是 the morning paper 推荐的一篇 Paper,非常值得一读。介绍了微信微服务的流量过载控制系统 DAGOR。此系统已经微信生产环境中服役 5 年,可谓身经百战。过载控制系统可以在当系统过载的时候有效的保护后端的系统,微服务的开发者很难正确估计真正生产流量,所以将过载控制系统从服务逻辑中解耦出来就非常重要。这篇 Paper 可能在这方面给你一些启发。

Analyzing the GitHub outage

非官方的,一篇有关 Github 10月故障的分析。

Some notes on running new software in production

当你想在生产环境使用新的软件时(我觉得新 lib 也差不多),应该花多少时间去了解你要用的这个东西?应该要了解到什么程度?此文是一篇不错的参考。(ps:Jvns 的博客有口皆碑)

工作、生活、side-project 和学习的平衡

是不是感觉很难做着一份工作的同时还保持学习新的技术?没时间去完成自己的 idea?甚至完全没有自己的生活了?

这篇帖子给出了很多有用的建议:

  1. 尽量减少通勤时间
  2. 保证锻炼和休息的时间
  3. 集中精力
  4. 每天进步一点点
  5. 理智的选择社交生活
  6. 接收事情要花很长时间,不要总是想 ALL IN、把其他事情放到一边。慢慢来,即使某件事要花上十年。不要期望某个事情会从一个时间马上就发生改变,接受事情是慢慢做成的。

Travis CI <3 Honeycomb

 本文介绍 Honeycomb 给 Travis 带来的巨大价值。Travis 一周会运行超过 300 万次 build,在这么大的流量中,如果没有足够的数据的话,调试某一次 build 问题,或者接口问题是很困难的。Travis 在没有 Honeycomb 之前是使用几个 Metric(四条黄金监控)来监控线上的服务状态的。但是这样的问题是,没办法知道某一次 outage 影响了多少用户,耗时高的流量是从哪里来的。也就是粒度不够。第二个资源是日志,日志是原始的信息,动辄好几屏,如果没有好的工具,很难使用日志。

Honeycomb 就是这样的工具,以前无法使用的庞大数据,用 Honeycomb,只需要点几下鼠标就可以了。文章举了一个例子,某一个用户(很可能是 bot)持续访问一个非常慢的 API,拖慢了整个接口耗时。有了 Honeycomb,Travis 很快定位出了这个用户,并确定这个是没有对其他用户造成影响的。要是没有 Honeycomb,可能要排查日志、定位、评估很长时间。

在生产环境中部署 Django Channel

本文介绍了在生产环境中部署 ASGI 应用和 WSGI 应用的方法(分别分开部署的)。包括 Nginx、gunicorn、daphne 的部署等。还有 websocket 的压测方法。放在书签里以后可能会有用:)

Introducing PyEnvDiff-lib and a Hub & Spoke Python Environment Diff Tool

本周正好遇到一个 flake8 的结果本地和 CI 不一致的情况,搞了半天发现是本地有一个全局的 config (啥时候写的我自己都忘了)。所以类似的工具可能可以解决这种问题。

The ultimate DevOps hiring guide

《Devops 雇佣指南》给雇主和寻找工作的 devops 的建议。

Highly Available Microservices with Health Checks and Circuit Breakers

介绍了几种负责均衡的方案,和健康检查、熔断的方法。很好的一篇入门文章,后面介绍了 Kong 的解决方案和使用方法。个人很看好这个项目!

 

Nginx if 指令工作原理

Nginx 的 if 指令被认为是“邪恶”的,就和 C 语言的 goto 一样。甚至官方有一篇 If is Evial 来警告你不要使用 if 。但有时候 if 还是非常有用的,如果掌握了它的原理,在合适的地方正确使用 if,会让事情更简单一些。当然前提是你真正知道自己在做什么,就和 goto 一样。 ——这是我的观点。

首先每个接触 Nginx 的人应该意识到的事情是,Nginx 是分 phase(阶段) 的,并不像 C 这种编程语言一样顺序执行。指令执行的顺序和书写的顺序没有太大关系(跟具体模块的实现有关),一个 phase 执行完了就会执行到下一个阶段。

If 是属于 rewrite 模块的,所以对于 if 来讲,会和其他的 rewrite 模块执行全部执行完之后再进行下一阶段。如果 if 指令的结果是 match 的,那么 if 会创建一个内嵌的 location 块,只有这里面的 content 处理指令(NGX_HTTP_CONTENT_PHASE 阶段)会执行。

下面是 agentzh 的四个例子,我这里稍加自己的解释。

实验的机器 IP 是 172.28.128.4 ,结果如下:

首先,对于一个请求 Nginx 会执行 rewrite 阶段,即如下代码。rewrite 阶段的执行顺序和指令的顺序是一样的,这个 rewrite 模块的实现有关。

$a 被设为 32,然后进入 if block,$a 在这里被设为 56,最后 $a 被设为 76. 中间 if block 生效。但是 if block 中没有任何 content 阶段的指令,所以会继承 outer block,即 ngx_proxy 模块的 proxy_pass 设置。这里要注意的是请求在 if block 内完成,if 命中之后就进入了 if block 来处理下一阶段,而不会跳出 if。

第二段示例如下:

结果如下:

Rewrite 阶段的过程和上面一样,不同是这一次 if block 中有了 content 阶段的指令,所以会执行 echo,不会执行到 proxy_pass 。

Rewrite 阶段的 break 可以终止 rewrite 阶段的执行。

以上代码的结果是

在 rewrite 阶段中,执行完 if ($a = 32) 之后执行 set $a 56 ,此时下一行是 break ,然后 rewrite 阶段就停止了,进行下一阶段。set $a 76 并没有被执行到。所以最后 $a 的值是 56。

ngx_proxy 会继承 outter scope,但是很多模块并不会这样,这个地方挺坑人的,我就是在这里被坑到的。

参考这段配置,正常来说,所有的 echo 都会执行,即如果不存在 if 的话,这段配置的结果应该是 hello \n java 。但是这里结果会是:

可以看到 echo 并没有继承 outter 。

顺便说一下我写的那段配置吧。简化之后如下:

我期望如果进 if 和不进 if,都会执行我的 access_by_lua_file ,但事实看来,进入 if 之后并不会再出来,而且 access_by_lua 和 access_by_lua_file 像 echo 一样,if 内并不会继承外面的 access_by_lua_file 。所以如果 if 命中,那么 access_by_lua_file 永远不会执行到。

最后一个例子是会继承 outter 的:

结果如下:

可以看到,这个模块的 more_set_headers 指令是默认继承 outter 的。

所以,官方给出的建议是尽量不要使用 if 指令,比如说有些地方其实可以使用 try_files

如果用,那么尽量只在 if block 内使用 rewrite 模块的指令。因为大家都是在这一个 phase 里面的,不会有 surprise 了。

在某些情况下,这些需要 if 的指令可以用嵌入的第三方模块来完成,比如 ngx_lua perl 等。

实在要用的话,做好充足的测试。

 

SRE&Devops 每周分享 Issue #1 Opening

Hi, 我平时看到一些有趣的文章很喜欢分享给朋友(这也是我写博客的原因吧),现在订阅的内容也越来越多了,有些也实在看不过来(Pocket 都堆满了)。其中订阅了很多 newsletter,newsletter 并不是每一篇都对你的胃口,但是总有几篇不错的,所以我也打算将我的分享整理成这种形式。一方面,我的工作中很多方面还在摸索,另一方面,这样可以将我看过的东西整理下来。最重要是的希望这种方式可以遇到更多同行,大家可以互相交流。

关于形式,暂时我先发布在博客上,后面如果找到不错的 Newsletter 托管(欢迎大家推荐),可能迁移到邮件形式。我每周将想要分享的东西存成草稿,周五中午 1:00 定时发布。当前分那几个板块还没确定,写到哪算哪吧,后面根据需要来调整。

关于投稿,大家发到博客右侧的邮箱即可。

关于内容,暂时会以 URL + 我的推荐语、概括为准。内容我尽量推荐自己看过的,但是有些实在太长,我可能放到 Pocket 慢慢看,还没看完就推荐给大家也说不定。所以大家要带着自己的脑子来读这里推荐的东西,并不一定都是好的。大部分内容都来自 Hacker News,这是我主要的订阅源。

最后,这是一项个人工作,所以无法保证能更新多久也无法保证内容有多少,请见谅。以下是第一期内容。

Honeycomb’s Charity Majors: Go Ahead, Test in Production

“分布式系统天生就是不好克隆、模拟、staged的,所以放弃 Staging 吧。加入线上环境出现问题,有些用户访问图片很慢,有些正常,你能在 Staging 环境发现问题吗?”这是 Honeycomb 在 ChaosConf 2018 的一个演讲,很多人反对在 Production 环境中测试的原因是,他们认为这样会破坏真实用户的体验。但是有像 A/B 测试,金丝雀测试的存在,可以将影响控制在一定范围。

gRPC Load Balancing on Kubernetes without Tears

gRPC 是基于 HTTP/2 的,而 Kubernetes 的负载均衡是基于 TCP 连接的。而 gRPC over HTTP/2 只会建立一个 TCP 连接,所以这样负载均衡就会有问题,所有的请求都发到了一个节点上去。官方推荐使用 Linerd2 来做 gRPC 的负载均衡。

HTTP-over-QUIC to be renamed HTTP/3

HTTP over QUIC(Quick UDP Internet Connections) 被重命名为 HTTP/3,将不再使用 TCP。

Time Series Analysis with LSTM using Python’s Keras Library

这是一篇教程,使用 LSTM 对时间序列数据分析。类似股票走势之类。这对流量监控系统非常有用,普通的规则设定报警很容易出噪音,如果能够基于预测出的流量走势设定监控报警也许更准确一些。

The History of Unix, Rob Pike

上周的一个不错的视频,一位资深程序员 Rob Pike 怀念了 Unix 几十年的历史,和他的故事。

Incident Management in Gitlab

Gitlab 是一家很开放的公司,这是他们的事故管理策略。

Chaos Monkey Guide for Engineers – Tips, Tutorials, and Training

Chaos Monkey 教程,介绍了其理念,实践以及相关的阅读资料(制作很精美)。

How Automatic Root Cause Analysis Works

Instana 如何在复杂的系统中自动定位故障的根因。

Getafix: How Facebook tools learn to fix bugs automatically

我们可以自由的写 Bug 了,毕竟有机器人来修复。

October 21 post-incident analysis

Github 10月32日 故障分析。

 

用 whitenoise 提供静态文件服务(Python)

Whitenoise 这个项目是一个符合 WSGI 标准的静态文件服务器,因为 WSGI 是可以嵌套的,所以 Whitenoise 可以和你原来的 WSGI 应用配合的很好,迁移成本很小,或者像 Django 这种项目有某种 middleware 机制,迁移就更方便了。本文介绍这个库的使用、为什么要用这个库(尽量说服你 ),以及买二赠一的源代码导读。

从文档掏出来的一个 QuickStart 如下:

可以看到,其实就是将你的 WSGI app 外面再包一层 WSGI app,即 WhiteNoise。

Django 可以不通过这种方式,因为 Whitenoise 对 Django 做了一些额外的适配,可以使用 Django 原生的中间件机制。

静态文件服务其实就是对于 HTTP 请求,发送对应的文件给用户。这件事情为什么要用 Whitenoise 来做呢?这个项目存在的意义究竟是什么呢?当你没听说过这个项目之前,一般的做法是用 Python 写的 web 应用来处理动态内容,用 Nginx/Apache2 这种专业的 HTTP 服务器处理静态文件;或者将静态文件都放在 S3 这种对象存储上。

为什么用 Whitenoise 比这两种做法要好,官方的 FAQ 写的很好,我这里捧哏转述一下。

首先使用 S3 这种方案,只是可以 work 的方式但是不是最佳的。第一,对压缩的支持不好。HTTP 的大多数时间都花在了网络上,而现在大多数个人电脑的 CPU 都是闲置的,所以假如服务器要传给客户端文件,那么服务器这边压缩一遍,通过网络传给客户端,客户端解压,虽然多了一次压缩/解压,但是总体上时间还是快的。S3 目前不支持压缩。支持压缩的话就要读 Accept-Encoding 这个 Header 看客户端支持哪种压缩方式(古老的 Gzip 压缩,或者现代的 brotli)。还要设置好 Vary 这些 Header 告诉 CDN 怎么处理缓存(什么是 Vary?)。而 Whitenoise 都帮你处理好了。另一个不便是配置 S3 比较麻烦,要用客户端上传,要有 Key,Secret 这些东西。CDN 的话,CDN 知道你的地址,你知道 CDN 的地址(互相确认过眼神)就好了。

其次,为什么不用 Nginx 呢?用 Nginx 是可以的,Whitenoise 使用的场景是 Heroku 这种 PaaS 平台,这种场景下 Nginx 不太好搞,所以用了 Whitenoise 你可以脱离 Nginx 了,直接将 WSGI app 平滑地部署到 PaaS 上。而且用 Nginx 你得仔细的设置很多东西,比如 CORS Header,cache Header,Nginx 原生不支持 brotli ,你还得去装 module 。

最后的一个问题是,Python 的性能问题是否意味着 Whitenoise 是一个效率很低的静态文件服务器?

如果你关心性能的话一定要使用 CDN,而对于 CDN 后面的真实服务器来说,最重要的事情是正确设置 HTTP 的 Header,最大限度的、正确的使用 CDN。所以这个问题更是一个逻辑是否正确的问题,而不是效率的问题。一个 静态文件 Request 的处理过程,其实主要是根据 PATH 来找到对应的文件并返回而已。对大多输的 WSGI 服务器(比如 Gunicorn)来说,发送文件的部分是对内核的 sendfile 的系统调用,效率是很高的,用 Python 还是 C,区别并不大。

下面是源码导读。

通过本文开头的那个例子可以看出,Whitenoise 实例化出来的对象其实是一个 WSGI app,它的 __call__() 方法如下:

HTTP 请求到了 WSGI 应用的时候会首先进入这个方法,然后尝试根据 PATH 寻找对应的静态文件,如果找到的话,通过 self.serve() 处理返回内容;如果找不到的话,就调用进入 self.application ,就是后端 Python 应用。Whitenoise 只提供了这一种配置方法,不像 uWSGI,有 4 种 serve 静态文件的配置方法。这里我有一个担忧是每一个进来的请求都要经过查找对应静态文件的逻辑,不知道对性能有没有影响。

serve 方法如下:

通过 static_file.get_response 方法拿到 Response,start_response 这一行是 WSGI 标准,返回 200 OK 这样的状态信息以及 Headers,最后以 list 形式返回 body。后面的 FileWrapper 是从 wsgiref.util 导入的,这个 FileWrapper 在 CPython 库里面的代码其实就是实现了迭代器,每次读 8k 的内容返回。如果用了其他的 WSGI 服务器,会拿到对应的 file_wrapper。从这里可以看出,Whitenoise 本身不处理 socket 发送文件的部分,真正负责这个的是 WSGI 服务器,比如 uWSGI 这种。

通过上面的介绍,我们知道这个库最重要的就是正确返回 Header,所以核心的逻辑都在 static_file.get_response 里面。

首先检查 method 必须是 GET 或 HEAD,否则就返回 403.

然后根据 etag 和 HTTP_IF_MODIFIED_SINCE 这个 Header 检查文件是否有修改。

接着解析 path 和 headers,注意这时候还没有打开文件,如果是 HEAD 方法,是不需要打开文件的,直接将 file 变量设置为 None 即可,后面发送的时候会自动不发送 body。

最后看是否是 HTTP_RANGE 请求,如果是,只读相应部分的文件。

主要的逻辑就到这里了,这个库的代码只有几千行,主要的逻辑就是上面介绍的这些。其他的一些包括 compress.py 有压缩相关的代码,middleware.py 和 storage.py 这些处理 Django 相关的一些 migration,scantree.py 处理查找文件的部分等等。

压缩的部分有一个值得注意的,这个变量将不需要压缩的文件的后缀列出来了。因为这些文件格式本身是经过高质量压缩的,如果启用压缩,会徒增计算成本,但是实际并不会减少很多文件体积。

总之,这是一个很小的库,但是对 HTTP 文件服务是一个比较值得参考的实现。Worth to read.

 

5年博客路

写博客 5 年了。

博客已经成为了我的习惯,自己有什么想法的时候,都喜欢将这些思绪理清楚,记录下来。想法只是想想很可能就那么过去了,但是想要整理成一片得体的博客的话,就该考虑周全一些了。第一篇博客写在高中毕业的时候,写下的东西读起来总觉得不完全表达出自己所想的,只能算是表达出六七分吧。写一篇要花我很长时间,最后还总不满意。现在不一样了,打开 WordPress 的编辑器,很快就能把心里想的记下来。我甚至已经对这个编辑器有感情了,一打开它就能找到很多灵感。

最初,总是担心没什么可写的,博客荒废在那里,首页停留着一篇几年前更新的文章,那可太丢人了(对自己的要求,没有觉得那些不更新的博客就不好)。也见过很多优秀的博客许久不更新了,很可惜。

但是从某一个时刻起,这件事默默的发生了质变。我很放心:我的博客永远不会停更。具体的原因我也说不上来,但是总感觉无论再忙,有一些想法不记下来就心里不舒服。那些担心也没有了,不会在意浏览量了,不会在意有人从这些文字中挑刺,虽然它们有时候前后矛盾,也不会在意自己有多久没写了,是不是要找个话题水上一篇来。

我把它当做自己的一个角落,虽然这个角落是完全公开的。

这里记录了我经历的一些事,有些很痛苦,只想写一写,写下来之后我从来没有去看过第二遍。也记录了一些我学到的东西,这是我最开心写的,我比较好为人师,学到一点点东西,就想讲给别人听,希望能帮到一些人吧。有时候自己回来看看,发现自己对这个方面还有过兴趣,也许能重新拾起来研究一下也说不定呢。也记录了一些杂七杂八的想法,比如看完一本书,看完电影,有时候想到了一些自己以前不曾想到的事,也会记下来。这些东西其实不成章法啦,写下来,最希望的是能有人来一起讨论一下,可惜的是,这部分内容实际上收到的有价值的讨论还非常少。还有有对自己做的事情的一些思考,比如做爬虫的时候发现这个工作其实挺有趣的,写了一篇介绍爬虫工作的文章。那时候一直想做一个定时执行任务的平台,只要你给我一个脚本,我可以帮你按照 cron 的方式执行它。在平台上可以清楚地看到执行的日志,像 Github 的 commit 日历一样做一个执行日历,一定很酷。但是一直没做下去(域名都买好了……),只是记录了一些想法

技术方面的文章无关乎深浅,再深奥的东西,其实只是抽象的太高,说不定你最后搞明白了发现是他设计的不好呢!所以并不是你看不懂的博客就是深奥的、写得好的(虽然大多数人都这样想)。有些自己觉得不值得一提的事情,写下来之后发现竟然帮到了很多人(如果你有访客记录的话,浏览量最多的文章一定让你大吃一惊)。所以我基本不会因为一个话题太浅而不写,专门挑显示我的技术高的东西去写。也不会去抄官方文档到博客上来赚取访问量。一个东西写与不写,对我来说就一个标准:我想不想写。毕竟这是我自己的博客,也没有人逼我,更没有人给我发钱。

我发现只有自己感兴趣的东西,写下来才是好的,字里行间都能流露出激情。不感兴趣的东西,肯定也写不好,要么像流水账一样,要么条例不清晰。王垠的这篇《解谜计算机科学1》挺好,但是只有第一篇,后面的太监了。没有了总比勉强写出来要好,我觉得。另外  agentzh 的 Nginx变量漫谈,也非常好,全都带着示例,讲的很明白,如果自己没兴趣,肯定写不出这么一大坨,还带着例子。好的博客是将复杂的问题说明白的,而不是写的别人看不懂的(故作高深)。这里有一篇《Programming for passion — not profit — produce high quality software》,讲的也是这个道理。

出自于兴趣做的事情,一定是比钱、虚荣、利益驱动的做的要好的。这也是为什么那么多人来参与开源,将项目做的那么好的原因。

这篇文章主要想表达一下我对博客的感情,因为我最近发现很多(不写博客的)人对博客有种误解,仿佛这件事情是用浏览量、评论量什么的来量化的。不是这样的,这只是茫茫互联网上的一个个人的角落。我把自己的故事留在这里,把自己读过的、听过的、看过的、想过的留在这里。搜索引擎就像小蜜蜂一样,有时候会将这些东西分享给全世界的人看,有时候这些东西就静静的待在这里。没有人能强迫你写下什么,也没有人能强迫你删除什么。

希望你也有这样一个角落。