用 Nginx 在公网上搭建加密数据通道

最近在跨机房做一个部署,因为机房之间暂时没有专线,所以流量需要经过公网。对于经过公网的流量,我们一般需要做以下的安全措施:

  1. 只能允许已知的 IP 来访问;
  2. 流量需要加密;

第一项很简单,一般的防火墙,或者 Iptables 都可以做到。

对于加密的部分,最近做了一些实验和学习,这篇文章总结加密的实现方案,假设读者没有 TLS 方面的背景知识,会简单介绍原理和所有的代码解释。

TLS/SSL 的原理

TLS 是加密传输数据,保证数据在传输的过程中中间的人无法解密,无法修改。(本文中将 TLS 与 SSL 作为同义词。所以提到 SSL 的时候,您可以认为和 TLS 没有区别。)

传输的加密并不是很困难,比如双方用密码加密就可以。但是这样一来,问题就到了该怎么协商这个密码。显然使用固定的密码是不行的,比如每个人都要访问一个网站,如果网站使用固定的密码,那么和没有密码也没有什么区别了,每个人都可以使用这个密码去伪造网站。

TLS 要解决的问题就是,能证明你,是你。现在使用的是非对称加密的技术。非对称加密会有两个秘钥,一个是公钥,一个是私钥。公钥会放在互联网上公开,私钥不公开,只有自己知道。只有你有私钥,我才相信你是你。非对称加密的两个秘钥提供了一下功能(本文不会详细介绍这部分原理,只简单提到理解后续内容需要的知识):

  1. 公钥加密的数据,只有用私钥可以解密;
  2. 私钥可以对数据进行签名,公钥拿到数据之后可以验证数据是否由私钥的所有者签名的。

有了这两点,网站就可以和访问者构建一个加密的数据通道。首选,网站将公钥公开(即我们经常说的“证书”),访客连接到网站的服务器第一件事就是下载网站的证书。因为证书是公开的,每个人都能下载到此网站的证书,那么怎么确定对方就是此证书的所有者呢?客户端会生成一个随机数,并使用公钥进行加密,发送给服务器:请解密这段密文。这就是上文提到的 功能1,即公钥加密的数据,只有私钥才能解密。服务器解密之后发回来(当然,并不是明文发回来的,详细的 TLS 握手过程,见这里,客户端就相信对方的确是这个证书的所有者。后续就可以通过非对称加密协商一个密码,然后使用此密码进行对称加密传输(性能快)。

但是这样就足够验证对方身份了吗?假设这样一种情况,我并不是 google.com 这个域名的所有者,但是我生成了一对证书,然后自己部署,将用户访问 google.com 的流量劫持到自己这里来,是不是也能使用自己的证书和用户进行加密传输呢?

所以就有了另一个问题:访客不仅要验证对方是证书的真实所有者,还要验证对方的证书的合法性。即 google.com 的证书只有 Google 公司可以拥有,我的博客的证书只有我的博客可以拥有。私自签发的证书不合法。

为了解决这个问题,就需要有一个权威的机构,做如下的保证:只有网站的所有者,才能拥有网站的证书。然后访客只要信任这个“权威的机构”就可以了。

CA 扮演的角色

CA 的全称是 Certification Authority, 是一个第三方机构,在上述加密的流程中,扮演的角色同时被访客和网站所信任。

网站需要去 CA 申请证书,而 CA 要对自己颁发(签名)的证书负责,即确保证书颁发给了对方,颁发证书之前要验证你是你。申请证书的时候,CA 一般会要求你完成一个 Challenge 来证明身份,比如,要求你将某个 URL 返回特定内容,或者要求你将 DNS 的某个 text record 返回特定内容来证明你的确拥有此域名(详见 validation standards)。只有你证明了你是你,CA 才会签证书给你。

访客是怎么验证证书的呢?这就用到了上文提到的 功能2:“私钥可以对数据进行签名,公钥拿到数据之后可以验证数据是否由私钥的所有者签名的。” CA 也有自己的一套私钥公钥,CA 使用私钥对网站的证书进行签名(担保),访客拿到网站的证书之后,使用 CA 的公钥校验签名即可验证这个“担保”的有效性。

那么 CA 的公钥是怎么来的呢?答案是直接存储在客户端的。Linux 一般存储在 /etc/ssl/certs。由此可见,CA 列表更新通常意味着要升级系统,一个新的 CA 被广泛接受是一个漫长的过程。新 CA 签发的证书可能有一些老旧的系统依然不信任。比如 letsencrypt 的 CA,之前就是使用交叉签名的方式工作,即已有的 CA 为我做担保,我可以给其他的网站签发证书。这也是中级证书的工作方式。每天有这么多网站要申请证书,CA 怎么签发的过来呢?于是 CA 就给很多中级证书签名,中级证书给网站签名。这就是“信任链”。访客既然信任 CA,也就信任 CA 签发的中级,也就信任中级签发的证书。

被信任很漫长,被不信任很简单。

CA (以及中级证书机构)有着非常大的权利。举例,CA 假如给图谋不轨的人签发了 Google 的证书,那么攻击者就可以冒充 Google。即使 Google 和这个 CA 并没有任何业务往来,但是自己的用户还是被这个 CA 伤害了。所以 CA 必须做好自己的义务:

  1. 保护自己的私钥不被泄漏;
  2. 做好验证证书申请者身份的义务;
  3. 如果 (2) 有了疏忽,对于错误签发的证书要及时吊销

案例:赛门铁克证书占了活跃证书的 30% – 45%(当时),但是被 Google 发现其错误颁发了 3万个证书,发现后却不作为。因此逐步在后续的 Chrome 版本中吊销了赛门铁克的证书

案例2:let’sencrypt 今年1月份发现自己的 TLS-ALPN-01 chanllege 有问题,于是按照规定,在5天后吊销了这期间通过 TLS-ALPN-01 颁发的所有证书

说道这里我想继续跑一个题。我以前给博客部署证书的时候(2017年)就想:CA 给我发一个证书居然要收我的钱?这个不是零成本的东西吗?他们想发多少就发多少。看到现在读者应该明白了,这并不是一个零成本的事情:签发证书的验证服务需要花钱,而 CA Root key 的保护要花更多的钱。整个 CA 公司(组织)的核心资产就是一个 key,如果这个 key 暴露了,后果不堪设想。所以,一个无比重要却要一直使用的 key 在一个上千万人的组织里怎么被使用而不暴露给任何一个人呢?这是要花很多钱的。Root key 的生成会有一个仪式(Key ceremony),全程录像,有 20 多个不同组织的代表会现场参加并监督,会有 3000 多个人观看实时录像,确保 key 的生成是标准流程。在 Root key 的保存和使用上,Root key 只会签中级 CA,以减少使用次数以及 Root key 需要被 revoke(代价太大)的风险。Root Key 保存在一个特殊的硬件中(HSM, Hardware security module),完全离线保存,HSM 也放在特殊的机房中,7×24 有人看守,并离线录像,机房有 Class 5 Alarm System,有多把锁,没有一个人可以单独进入。使用这个 Root Key 必须物理上进入这个机房,使用过程全程录像,并且记录使用过程,如果有问题可以很快地将 Root Key 签的内容 revoke。这里有一个视频介绍 Key Signing Ceremony,非常有趣。所以说 CA 机构并不是一个摇钱树,Let’s Encrypt 这种组织简直就是慈善机构。

以上就是 TLS,证书,CA 大致的工作原理,稍稍有些跑题,有了这些知识我们就可以利用 TLS 来建立一个加密的数据通道了。后续几乎都是实际的操作。笔者对这部分也不是精通,如果有错误,欢迎指出。

对应用透明的加密通道的方案

背景

上文是通过网站部署 HTTPS 来讲的 TLS 的工作原理。其实网站部署 HTTPS 还算是比较简单:你只需要找一个 CA,申请证书,完成 CA 的验证,部署证书,就可以了。

现在要解决的问题更加复杂一些:我们的两个组件之间是通过自己研发的协议通讯(基于 TCP),现在要分别部署在两个机房,通过公网进行通讯。

我们的方案要对通讯的两边做好安全防护:

  1. 数据要进行加密传输;
  2. 要对两边做身份验证,比如 A 向 B 发起连接,A 要验证 B 的身份,B 也要验证 A 的身份;
  3. 最好对于应用来说透明,即应用完全不修改代码,依然按照原来的方式工作,但是我们将中间的流量进行加密;

mTLS

mTLS 的全称是 Mutual TLS. 即双向的 TLS 验证。HTTPS 只是访客验证了网站的身份,网站并没有验证访客的身份。其实要验证也是可以的,网站发送证书之后可以跟访客说:“现在该轮到你出示你的证书了”。如果访客不能提供有效的证书,网站可以拒绝服务。

其实,ssh 方式就是一个双向验证的过程。我们都知道通过 ssh key 登录 server 的时候,需要让 server 信任你的 key(即将你的 pubkey 放到 server 上去)。但是还有一个过程容易被忽略掉,在第一次通过 ssh 连接服务器的时候,ssh 客户端会给你展示 server 的 pubkey,问你是否信任。如果之后这个 key 变了,说明有可能你连接到的并不是目的服务器。

第一次连接到服务器的提示

如果之后这个 key 变了,ssh 客户端就会拒绝连接。

Git 也是通过走 ssh 协议的,所以也是一个双向认证。你在使用 Github 的时候要互相信任对方:

  • Github 信任你的方式是:你将自己的 pubkey 上传到 Github (设置,profile,keys)
  • 你信任 Github 的方式是:Github 将自己的 pubkey 公布在网上

解决方案

为了实现对应用透明的加密通讯,我们在两个机房各搭建一个 Nginx,这里两个 Nginx 之间通过 mTLS 相互认证对方。应用将请求明文发给同机房的 Nginx,然后 Nginx 负责加密发给对方。对于应用来说,对方机房的组件就如同和自己工作在相同机房一样。最终搭建起来如下图所示。

搭建过程

因为用 HTTP 流量来搭建,相关的工具和日志会更友好一些。所以我们会先用 HTTP 将这个通道搭建起来,然后换成 tcp steam。

准备证书

我们一共需要两套证书,一套给 Client,一套给 Server. 因为我们这里主要要解决的问题内部互相信任的问题,不需要开给外面的用户,所以这里我们采用 self signed certificate. 即,我们自己做 CA,给自己签发证书。自签发证书的好处是很灵活,方便,坏处是有一些安全隐患(毕竟不像权威机构那样专业)。所以我把这个过程写在博客上,请大家帮忙看看流程有没有问题。

首先我们创建一个 CA 的 key,即私钥。CA 的 key 最好给一个密码保护,每次使用这个 CA 签发证书的时候,都需要输入密码。

生成 key 的命令:

输出(其中按照提示输入密码):

命令的解释:

  • openssl: cert 和 key 相关的操作我们都用 openssl 来完成;
  • genrsa: 生成 RSA 私钥;
  • -des3: 生成的 key,使用 des3 进行加密,如果不加这个参数,就不会提示让你输入密码;
  • 4096: 生成 key 的长度;

这里我们假设所使用的密码是 hello.

然后我们来生成 CA 的公钥部分,即证书。

这时会询问你一些信息,比如地区,组织名字之类的。其中,Organization Name 和 Common Name 需要留意。CA 的这一步填什么都可以。Common Name 又简称 CN,就是证书签发给哪一个域名(也可以是 IP)的意思。

输出会是如下所示:

命令的解释:

  • req: 创建证书请求;
  • -new: 产生新的证书;
  • -x509: 直接使用 x509 产生新的自签名证书,如果不加这个参数,会产生一个“证书签名请求”而不是一个证书。
  • -days 365: 证书1年之后过期,也可以省略这个参数,设置为永不过期;
  • key: 创建公共证书的私钥,会被提示输入私钥的密码;
  • -out: 生成的证书。

到这里,我们有了一对 CA 证书,ca.keyca.crt 两个文件。接下来申请 server 端的证书。

Server 端证书依然是先生成一个 key,这里就不需要密码保护了:

然后这里下一步不是直接生成证书,而是生成一个证书请求。但是那些问题依然是要回答一遍的。

回答问题的时候要注意两个地方:

  • Organization Name: 不能和 CA 的一样;
  • Common Name: 必须要写一个,可以写一个不存在的域名,比如 proxy.example.com。否则,会有错误:“* SSL: unable to obtain common name from peer certificate”。

否则证书无法使用。

到这里其实也可以看出,CA 的证书和其他的证书没有什么不同,也是一个普通的证书而已。

这个 .csr 文件是 Ceritifcate Signing Request,即请求签名。接下来我们使用我们的 CA 给这个 Server 证书签名(作担保!)。

这个命令需要输入 CA key 的密码,就是刚刚说的 hello

命令的解释:

  • x509: 公有证书的标准格式;
  • -CA: 使用 CA 对其签名;
  • -CAkey: CA key(没有这个岂不是人人可以用 CA 证书签名了?);
  • -set_serial 01: 签发的序列号,如果证书有过期时间的话,过期之后,可以直接用这个 .csr 修改序列号重新签一个,不需要重新生成 .csr 文件;

如此,就得到了 server.crt 文件。

我们可以使用这条命令验证生成的证书是 ok 的:

重复此流程再签发一个 client 端的证书。

结束后,我们有以下内容:

  • ca.key
  • ca.crt
  • CA 的密码,需要保存
  • server.key
  • server.crt
  • server.csr: 部署不需要用到,可以只保存在安全的地方即可;
  • Server 证书签发序列:只保存即可;
  • client.key
  • client.crt
  • client.csr: 部署不需要用到,可以只保存在安全的地方即可;
  • Client 证书签发序列:只保存即可;

然后接下来就可以部署起来了。

搭建远程 Server 端的 Nginx

为了模拟转发到后端应用的场景,这里的 Nginx 不使用静态文件,而是用一个 fastapi 写的样例程序来做后端:

启动的命令是:

程序默认会运行在 8000 端口。

然后修改 Nginx 的配置,nginx.conf 不变,我们只修改 default 的配置,将 default rename 成 remote_server,然后修改成成如下配置:

这就是一个很简单的 Nginx HTTPS 配置,证书配置上了我们刚刚自己签发的证书:

  • ssl_certificate: 告诉 Nginx 使用哪一个公有证书;
  • ssl_certificate_key: 此证书对用的私钥是什么,服务器需要有私钥才能工作。

证书已经配置好了。这时候我们去 cURL 443 端口会出现错误:“curl: (60) SSL: unable to obtain common name from peer certificate”,cURL 不信任这个服务器的证书。这是当然了,因为这个证书是我们自己作为 CA 签的。

要正常访问,必须使用 cURL --ca ./ca.cert 来告诉 cURL 我们信任这个 CA (所签发的所有证书)。

另外还要注意的是,记得我们之前的 Server 证书是签发给 proxy.example.com 的吗?我们这里必须要访问这个域名才行。需要这样使用:

--connect-to 的意思是,所有发往这个域名的请求,都直接发给这个 IP。

Client 对 Server 的验证就配置好了,接下来再配置 Server 对 Client 的验证。

我们只需要将上面的配置文件改成如下即可:

添加的内容的含义:

  • ssl_verify_client: 需要验证客户端的证书;
  • ssl_client_certificate: 我们信任这个 CA 所签发的所有证书。

这里有一个小插曲:Nginx 的文档上说,ssl_trusted_certificate 和 ssl_client_certificate 这两个配置效果都是一样的,唯一的区别是 ssl_client_certificate 会将信任的 CA 列表发送给客户端,但是 ssl_trusted_certificate 不会发。发送是合理的,因为客户端如果有很多证书,让客户端一个一个去尝试哪一个能建连是没有意义并且很浪费的。ssl_trusted_certificate 的作用是验证 OCSP Response。但是我尝试了 ssl_trusted_certificate,Nginx 会直接 fail 掉语法检查:

这里发现一个 ticket 询问和我一样的问题:https://trac.nginx.org/nginx/ticket/1902,不过至今没有回复。我以为是 Nginx 版本的 Bug,然后尝试了最新的版本依然是一样的结果。如果读者知道可以指点一下,谢谢。

这样配置之后 reload Nginx,就开启了对客户端的证书验证了。这时候我们继续使用上面那个 cURL,就无法得到响应。

Nginx 会要求你提供证书。

如下的 cURL,带上证书,就可以正常拿到响应。

这样,远端的 Nginx 就配置好了,它会提供证书证明自己的身份,也会要求客户端提供证书进行验证。

接下来搭建本地的 Nginx,将明文请求加密对接到远端的 Nginx。

搭建本地 Client 端的 Nginx

本地机房开启一个 Nginx,监听 80 端口,转发到远程的 443 端口。

配置如下:

这个配置可以分成两部分看,第一部分,是要验证对方的证书:

  • proxy_ssl_verify: 需要对方提供证书;
  • proxy_ssl_trusted_certificate: 我们只信任这个 CA 签发的所有证书;
  • proxy_ssl_server_name: 不像 cURL 的 --connect-to 选项,这里我们直接指定目标 IP 转发,但是我们使用 SNI 功能来告诉对方我们要连接哪一个 domain,来验证相关 domain 的证书;
  • proxy_ssl_name: 我们需要哪一个 domain 的证书。

然后第二部分是提供自己的证书:

  • proxy_ssl_certificate: 我的证书;
  • proxy_ssl_certificate_key: 我的私钥,不会发送给对方,只是本地 Nginx 自己使用。

然后就可以 cURL 本地的 80 端口了:

可以看到我们从客户端(cURL)发出明文 HTTP 请求,到服务端(fastapi)收到明文 HTTP 请求,两边都不知道中间流量加密过程,但是走公网的部分已经被加密了。就实现了本文开头的需求。

代理 TCP steam

以上是 HTTP 的配置,将其换成 TCP Steam 的代理也很简单,相应的配置修改一下就可以。这里我们以 Redis 服务为例来展示一下配置。

/etc/nginx/nginx.conf

Remote Server 的配置:/etc/nginx/sites-enabled/remote_server

local_client 的配置:/etc/nginx/sites-enabled/client_server

基本上就是把 HTTP 代理换成了 TCP 代理指令。

这样配置好之后,我们就可以用 redis-cli 去连接本地的 80 端口了。

 

一些参考资料:

  1. Nginx 反向代理相关的文档。http://nginx.org/en/docs/http/ngx_http_proxy_module.html
  2. CA 如何保存 key?https://security.stackexchange.com/questions/24896/how-do-certification-authorities-store-their-private-root-keys
  3. 自签名证书的风险:https://www.preveil.com/blog/public-and-private-key/
  4. TLS 建联的展示,每一个字段都有详细的展示,非常好看:https://tls.ulfheim.net/
  5. 什么是 mTLS? https://www.cloudflare.com/zh-cn/learning/access-management/what-is-mutual-tls/
 

2021 年年鉴

今年没有在跨年的时候及时写完年终总结,是因为那天还在西安隔离。2021 年一整年依然是疫情肆虐,下半年在新加坡尤其严重。所以在这一年的最后回想起来,还是比较无聊的。

所幸这一年经过的所有的事情都还算顺利,没有什么大起大落。

就先说说工作吧。其实今年的大部分时间都花在了工作上。从 6 月以来,新加坡的防疫政策从清零变成了与病毒共存,病例数量一直居高不下,一直到年末的时候才开始下降。所以下半年我们几乎都是在家办公的。在家办公就导致了很多问题,比如有同事在 town hall 上提出的 endless working hours,就成了我最大的问题。

以前想,居家办公可以让我们有更多的时间去安排自己的工作。但实际上实行起来,却发现工作时长不可避免的延长了。一方面,大家一起没有了通勤这个工作和生活的分割,所以有时候会在 IM 上在工作时间之外去沟通一些问题。很多时候虽然说不急,可以回头再回复。但是我看到了不回复就会难受,只好去帮人家解决;另一方面,下半年的大促一个接着一个,每个月至少一次。大促之前有事情要忙,大促之后又有因为大促而 block 的事情要忙,也导致工作时间延长。居家办公也导致工作设备的质量下降,没有一把合适的椅子,没有像样的办公的桌子(当初租房子的时候也没想到自己会在家工作这么长时间),导致开始腰疼了。

抱怨就说这么多吧,希望可以尽快恢复办公室办公,也希望这波疫情赶紧过去吧(这篇文章在 2月底捡起来继续写,发现第三波疫情又开始了,真的是烦)。

好在,今年做的工作都比较有意思,所以也没有感觉到多累。

今年工作上做的事情主要有两个,一个是公司的 service mesh 系统的维护。另一个是从零开始一个的 SRE Chaos Engineering 项目,我起的名字的叫做 Chaolab.

Service mesh 方面,其实没有什么特别大的项目或者改动,只不过是在各种部署结构和部署流程上修修补补,没有什么值得特别骄傲的,但是不知不觉,这个项目也越来越稳定了。比如:目前在升级的时候是无法保持住长链接的,通过发布的算法可以分批发布,每次不影响某一个服务 20% 的 instance,降低发布的影响;删除了大量不再使用的资源,归还了很多机器;下线了很多私有部署的 Nginx,迁移到公司的网关上去;将服务部署到了越来越多的机房,等等。

还有一个很有意思的事情,就是完全重构了部署使用的脚本,这个项目以及所有的依赖可以在一台笔记本上跑起来,一共涉及十多个组件,以及 20 多个 docker instance。算是去年一整年的工作成果的总结吧,之前下线了一些不必要的依赖,整理很多次部署结构,才能使它成为可能。

关于 Chaoslab 项目。这个算是比较有成就感的项目。一个人从头写了前端,后端,部署,agent 等组件,算是一个比较完整的项目了。主要实现的功能就是,你可以在页面上去创建一个实验,然后给物理机,或者容器等去实现注入 chaos,然后写 probe 去验证软件是否在注入故障之后依然工作正常。支持 crontab,权限控制,自动 rollback 等功能,大部分会使用到的注入选项都使用到了。

除此之外,就是一些杂七杂八的工作了。比如日常值班,回答别人的问题啥的。今年的感受就是,之前不被承认的工作在 Shopee 都是被认可的。还做了一个平台组件的 status page,这个是之前就想尝试的,一直依赖都有一个观点,就是应该通过公开的信息和用户信任感,用户需要知道你的组件的状态,在什么时间遇到过什么问题。虽然只是一些 CURD 的工作,没有什么技术含量。但是最后做出来非常满足。我一开始的设计有很多问题,但是很多人都给我提了建议,修改之后,就好很多了。

工作方面想到的就这么多吧。在开源方面,其实今年做的不够多,iredis 基本上是修修补补。目前的补全实现是基于每一个命令的,也就是说,redis-server 的改动,我都必须做出相应的适配,否则,一些新的命令或者老的命令修改了语法,都不会有很好的自动补全。然而 redis 6.0 新加入的改动都还没有支持完全。主要是没时间去做。

今年开了一个新坑,lobbyboy. 是一个可以自动帮你开新的虚拟机的 ssh server。一开始是满足自己的需求,后来看到其他人也有需求,也有很多贡献者来提交代码。甚至在这个项目上写的代码比我都多了。后来把这个项目迁移到了一个 org 下面,而不是只有我自己有权限。虽然后面在实现了基本的功能之后也没有花很多时间在上面了。

今年学会了什么?

新学的东西不算多,感觉自己在啃老本。倒是接触了 Prometheus,读了 Up&Running 那本书,感觉对于使用算是比较了解了。

另外看了一些 golang 的教程,目前算是能看懂 golang 的代码,但是写的话还是差点火候。今天还是继续学习一下 golang 这门语言,多看一些代码和书。

另外感觉自己的沟通能力有了很明显的提升。能够在别人描述不清楚自己的问题的时候帮别人解答问题。:)

明年计划学习什么?

除了 Golang 之外,Kubernetes 打算多花点时间学习一下,因为工作中要用的到。

工作和学习的事情就说这么多吧。去年也发生了很多有意义的事情,很多也是一时半会说不完的。年的时候从新加坡回国过年,疫情之下的回国程序真的是复杂呀,交了数不清的文件,终于拿到大使馆发的绿码了。但是偏偏不巧选择的落地地点是西安,落地的第二天西安宣布封城。终于在隔离结束的时候,回到了上海,上海又说你是从西安回来的,作为重点人群需要隔离14天。虽然我极力解释我是在隔离结束之后完全闭环转移到上海的,没有接触过西安的人,也是无济于事。最后还是被拉倒隔离酒店隔离了14天。这28天应该是我在这一年里面最郁闷的。

今年年初的时候和老婆一起去拍了婚纱照,寒冷的冬天在外滩懂的瑟瑟发抖,辛苦老婆了。

今天的首要任务之一就是将婚礼举办好,嘻嘻。

年后经过香港转机返回新加坡,正巧第三波 Omicron 疫情又开始,香港和新加坡又是疫情比较严重的地方(怎么我这段时间去哪哪严重的?!)。经过了道道审核,最后还算是顺利地回到了新加坡。

路上还见了多年未见的老朋友,一起吃了快乐小羊。他吃羊间隙还得拿出来电脑工作,我感叹大家都成社畜了。

回到新加坡之后,很快安顿了下来,换了一个房子,也很快进入了工作状态。

好像也没什么好说的了。2021 年是过的比较平静的一年,2022 年也没有什么特殊的计划。老婆的计划倒是比较重要,今年除了婚礼要办,老婆也在努力朝着一些会改变人生轨迹的大事上面付出努力,我只有默默地支持,提供帮助,希望一切顺利。总之,希望2022年能够多付出一些,过的也更多姿多彩一些吧。


往年的总结:

  1. 2013年
  2. 2014年
  3. 2015年
  4. 2016年
  5. 2017年
  6. 2018年
  7. 2019年
  8. 2020年
 

寻找丢失的信号

记录一个今天遇到的小问题。这是继 Debug 一个在 uWSGI 下使用 subprocess 卡住的问题 之后又一次遇到信号问题。

我写的 chaos engineering 平台支持一个功能:立即中断正在进行的实验,并且执行 rollback 操作复原注入的操作。每一个实验都是由一个进程负责的,终止的方法是向进程发送一个 SIGINT 信号,让进程停止注入并且切换到 rollback 开始清理。

最近的一个改动从 asyncio.create_subprocess_exec 切换到了 asyncio.create_subprocess_shell 导致了一个 bug,现象是线上的执行器根本收不到停止的信号了,刹车失灵,险些酿成悲剧。

经过警方调查发现,asyncio.create_subprocess_shell 其实会开一个新的 shell 来执行命令,默认使用的是 sh,而 sh 默认是不转发它收到的信号的。(这里我是用了 killsnoop 来发现 sh 确实收到了信号,但是执行 chaos 的进程没有收到,然后查阅文档并通过实验复现确认 sh 不会转发信号。)

但是这个问题我在开发环境(Mac)并没有测试出来,因为开发环境工作的好好的。

我写了一个最小的 case 可以复现这个场景:

在 Mac 上的表现是,python 进程的子进程就直接是 sleep 进程,并没有一个中间的 sh 进程。

而在 Linux 上的表现是:python 进程的子进程是 sh 进程,然后 sh 的子进程才是 sleep 进程。

经过 ./grey 指点,发现在 Mac 上 sh -c "sleep 99" 之后,sh 自己也不见了,只有 sleep 99 这个进程,父进程是我自己的 zsh shell.

这里就真相大白了。中间进程的这个 sh 并不会转发 signal,所以在线上的 Linux 系统上收不到信号;在开发电脑上由于没有中间的 sh ,所以直接将 signal 发给了子进程。

那么 sh 在两个系统上到底有怎么样的不同呢?

在我的 Mac 上,man sh 说:

sh is a POSIX-compliant command interpreter (shell). It is implemented by re-execing as either bash(1), dash(1), or zsh(1) as determined by the symbolic link located at /private/var/select/sh. If /private/var/select/sh does not exist or does not point to a valid shell, sh will use one of the supported shells.

经过查看,可以发现其实 sh 在 Mac 上是 bash:

对于 bash -c "sleep 99" 这个命令,bash 有一些优化,为了节省资源,bash 会直接通过 execve() 去执行 sleep,这样在系统上就可以少存在一个 bash 进程。详细解释

而在 ubuntu 上,sh 其实是 dash:

dash (至少我们使用的版本)还没有这个优化,所以在 Python 的 subprocess shell (经过 linw1995 指点)中就会有两层进程,一个是 dash,dash 的子进程才是运行的命令。

在 ubuntu 上 bash -c "sleep 99" 可以看到 bash 本身也是会消失的。说明这个确实是 bash 的行为。

bash 进程消失不太准确,它其实是换了一个形式存在而已。strace可以证明它存在过:

 

反思一下这个问题,有以下几点可以做的更好:

  • 换成 Linux 开发;
  • 写测试用例,CI 完全可以发现这个问题;
  • 还是尽量使用 asyncio.create_subprocess_exec 来执行命令吧!
 

Coredns 源码阅读(导读)

周末看了一下 coredns 的源代码,目前为止只是差不多搞清楚了这个代码库的逻辑。写篇博客记录一下,如果你也想要阅读这个库的源代码的话,这篇博客可以节省你的一些时间。

coredns 介绍

代码库地址:https://github.com/coredns/coredns

coredns 是一个 dns server,简单来说就是这个软件启动之后可以监听一个端口,然后你将 dns 查询请求发给这个端口,它可以告诉你 dns 解析的结果。所以作为任意一种 dns server(DNS Authoritative Nameserver, 或者 Recursive Resolver Server) 工作,我认为都是没有问题的。

这个代码库值得一读的理由有:

  1. coredns 以 plugin 的形式工作,除了 plugin 之外部分的代码很少,而且那部分代码其实不必阅读也可以(在读完本文之后);plugin 作为 first-class citizen 的好处是,职责清晰,一个 plugin 只做一件事情。比如 metrics,trace 这种东西,都是以 plugin 的形式存在的。这样,阅读起来非常方便;
  2. 每个 plugin 完成自己的事情,阅读起来难度低。而且这个库要求的背景知识很少,不需要懂很多网络的协议。DNS 相关的协议部分,看到哪里不懂的时候再查就好了。

不好的地方:

  1. 这个服务是基于一个 coredns 自己 fork 维护的 caddy server 来实现的,就导致可能需要去看 caddy 部分的代码。而且 coredns 自己维护的 caddy 已经脱离最新版了,我发现用的 caddy plugin 已经在官方的代码库中删除了。所以有些地方理解起来可能不简单。

看代码之前的准备工作(推荐)

如果之前没有用过的话,建议看代码之前先:

  1. 看完 manual:https://coredns.io/manual/toc/,可以知道使用方法,和大体的工作原理;
  2. 看下 plugin 的写法:https://coredns.io/2016/12/19/writing-plugins-for-coredns/

编译方法

因为 golang 是编译型的语言,所以无法像 Python 那样动态安装、加载插件。要新增插件,必须重新编译,将 plugin 的代码编译进二进制。

插件的列表在 plugin.cfg 中。如果新安装插件,需要将新插件写入这个列表,然后运行 go generate coredns.go 命令,重新生成这两个文件:

编译的命令是: CGO_ENABLED=0 go build -v -ldflags="-s -w -X github.com/coredns/coredns/coremain.GitCommit=3288b111-dirty" -o coredns. GitCommit 作为变量注入进 binary,运行的时候打印。

程序入口

coredns.go 里面 import 了 plugin,然后调用了 coremain.Run

coremain.Run 里面只是处理了一些和 Version(用于 Print)的信息,和命令和参数。最后启动了 Caddy server。

coredns server 的逻辑在 coredns/core/dnsserver. 但是中间涉及和 caddy v1 的交互,比如 MakeServers() , 是 caddy 里面的接口。应该也不是很重要。

server 里面的入口应该是 Serve()。基本的逻辑是拿到 plugin chain,然后调用第一个 Plugin, 调用 Plugin 的 ServeDNS()

Plugins

Plugin Chain, 顾名思义,是 Chain 在一起的。并不是在 server 里面一个 for 循环调用所有的 Plugins,而是 Server 只会调用第一个 Plugin。

第一个 Plugin 可以处理 DNS 请求,返回结果。如果像是 metrics 这样不负责逻辑的 Plugin,可以在完成自己要做的事情之后,去调用 coredns/plugin/plugin.go 里面的 NextOrFailure 函数,交给下一个 Plugin 去处理。和 ASGI 一样,turtles all the way down.

Plugin 主要做两件事情:

  1. 通过 init() 函数将自己注册进去,也可以做一些初始化的工作。比如支持配置。Corefile 中的配置可以在 init 的时候通过 setup 接口读到;
  2. ServeDNS() 主要的函数入口。处理 DNS 请求。

whoami 这个 Plugin 返回查询者的 IP 端口等信息,逻辑比较简单,建议从这个 plugin 开始看。

 

SRE 的工作介绍

有很多人问过我想了解一下 SRE 这个岗位,这是个很大的话题,在这篇博客中把想到的一些介绍一下吧。

SRE 到底是什么?这是一个最早由 Google 提出的概念,我的理解是,用软件解决运维问题。标准化,自动化,可扩展,高可用是主要的工作内容。这个岗位被提出的时候,想解决的问题是打破开发人员想要快速迭代,与运维人员想要保持稳定,拒绝频繁更新之间的矛盾。

SRE 目前对于招聘来说还是比较困难。一方面,这个岗位需要一定的经验,而应届生一般来说不会有运维复杂软件的经历;另一方面就是很多人依然以为这就是“运维”工程师,认为做的是一些低级重复的工作,对这个工作有排斥。最根本的,其实这个岗位寻找的要么是具有运维经验的开发人员,要么是具有软件开发技能的运维工程师。所以比较难以找到合适的人。

在现实生活中,不同公司的 SRE 岗位大有不同,有一些甚至可能还是传统运维的名字换了一个岗位名称。

比如蚂蚁金服有两种 SRE,一种是负责稳定性的,就是大家所理解的 SRE;另一种叫做资金安全 SRE,并不负责服务正常运行,而是负责金钱数目正确,对账没有错误,工作内容以开发为主,主要是资金核对平台和核对规则(没有做过,只是个人理解)。某种意义上说,已经不算是 SRE 而是专业领域的开发了。

Netflix (2016年)的模式是谁开发,谁维护。SRE 负责提供技术支持,和咨询服务。Netflix 在全球 170 个国家有服务,Core SREs 只有 5 个人。

微软有专门的 Game Streaming SRE,负责 XBox 在线游戏的稳定性。

所以不同公司的 SRE 的内容各有偏重,取决于公司要提供什么样的服务。

我们可以学习网络分层的方式,将 SRE 大致的工作内容从下往上分成 3 个大类:

  1. Infrastructure:主要负责最基础的硬件设施,网络,类似于 IaaS,做的事情可参考 DigitalOcean
  2. Platform:提供中间件技术,开箱即用的一些服务,类似于 PaaS,做的事情可参考 Heroku, GCP, AWS 等
  3. 业务 SRE:维护服务,应用,维护业务的正常运行

Infrastructure

Infrastructure 和 Platform SRE 其实可有可无,这些年商业化的服务其实越来越多了,比如,如果公司选择全部在 AWS 部署自己的服务的话,那么就不需要自己建立 Datacenter,维护网络之类的工作了,只需要几个 AWS 专家即可。

如果有的话,工作内容也可大可小。可以从管理购买的 VPS 开始,也可以从采购硬件服务器开始。

我觉得 Infrastructure SRE 的工作内容可以这样定义:

  1. 负责服务器的采购,预算,CMDB 管理。要知道(能查询到)每一台的负责人是谁,在干什么。这个非常重要,如果做不好,会造成极大的资源浪费。
  2. 提供可靠软件的部署环境,一般是虚拟机,或者 bare mental。
  3. 操作系统的版本统一维护,Linux 发行版的版本,Kernel 的版本等。
  4. 维护机器上的基础软件,比如 NTP,监控代理,其他的一些代理。
  5. 提供机器的登录方式,权限管理,命令审计。
  6. 维护一套可观测性的基础设施,比如监控系统,log 系统,trace 系统。
  7. 维护网络,大公司可能都会自己设计机房内的网络。其中包括:
    1. 网络的连通,这个是必要的。对于上层用户(Platform SRE)来说,交付的服务应该是任意两个 IP 是可以 ping 通的,即管理好 3 层以下的网络。
    2. NAT 服务
    3. DNS 服务
    4. 防火墙
    5. 4 层负载均衡,7层负载均衡
    6. CDN
    7. 证书管理

每一项既可以是一个很大的团队,也可以只有一个人去对商业化的 Infra 服务。可以使用开源的产品,也可以自己研发。

Platform SRE

Infrastructure SRE 维护的是基础设施,Platform SRE 使用他们提供的基础设施建立软件服务,让公司内的开发者可以使用开箱即用的软件服务,比如 Queue,Cache,定时任务,RPC 服务等等。

主要的工作内容有:

  1. RPC 服务:让不同的服务可以互相发现并调用
  2. 私有云服务
  3. 队列服务,比如 Kafka 或者 RabbitMQ
  4. 分布式的 cronjob 服务
  5. Cache
  6. 网关服务:反向代理的配置
  7. 对象存储:s3
  8. 其他一些数据库:ES,mongo 等等。一般来说,关系型数据库会有 DBA 来运维,但是 NoSQL 或者图数据库一般由 SRE 维护。
  9. 内部的开发环境:
    1. SCM 系统,比如自建的 Gitlab
    2. CI/CD 系统
    3. 镜像系统,比如 Harbor
    4. 其他的一些开发工具,比如分布式编译,Sentry 错误管理等等
  10. 一些离线计算环境,大数据的服务

业务 SRE

有了 Platform SRE 的支持,开发人员写代码就基本上不需要关心部署的问题了。可以专注于开发,使用公司开箱即用的服务。这一层的 SRE 更加贴近于业务,知道业务是怎么运行的,请求是怎么处理的,依赖了哪些组件。如果 X 除了问题,可以有哪些降级策略。参与应用的架构设计,提供技术支持。

主要的工作内容有:

  1. 参与系统的设计。比如熔断、降级,扩容等策略。
  2. 做压测,了解系统的容量。
  3. 做容量规划。
  4. 业务侧的 Oncall。

对于一个专业的 SRE 来说,上述技能也不应该有明显的界限,比如说业务 SRE 也需要掌握一些网络技能,Infra SRE 也要写一些代码。很多工具每一个岗位的人都多少用的到,比如 Ansible/Puppet/SaltStack 这种 IT 自动化工具,或者 Grafana/Prometheus 这种监控工具,只有理解才能用的正确。换个角度讲,对于业务 SRE 来说,虽然基本上不会去管理四层以下的网络,但是如果遇到网络问题,能通过已有的工具和权限排查到交换机问题,去找 Infra SRE 帮忙:“请帮我看下 xx IP 到交换机是否有异常,因为 xxx 显示的结果是 xx”,总比 “我怀疑 xx 有网络问题,请帮忙排查下” 要好一些吧?

以上是工作职责的大体划分,这个分层其实没有什么意义,倒是可以让读者了解一下 SRE 都涉及哪一些工作。

下面是一些日常的工作内容。

部署服务

部署分成两种:

  1. Day 1:将服务部署上线的那一天
  2. Day 2+:服务部署之后,还会进行很多更新,升级,配置更改,服务迁移等等

Day2+ 的工作要做很多次,Day 1 做的很少,在不断的迭代升级之后,还能保证有一个可靠的 Day 1 操作是很难的。换句话说,我们在服务部署之后一直改来改去,还要保证这个服务在一个全新的环境能够可靠的部署起来。部署环境的硬编码,奇奇怪怪的 work around,都会破坏 Day 1 的可靠性。之前一家公司,扩容一个新机房的过程简直是噩梦,太多的奇怪配置,hardcode,导致踩过无数个坑才能在一个新的机房部署起来全部的服务。

Day2+ 的操作也不简单,主要要关注稳定性。对于重要的变更操作要设计好变更计划,如何做到灰度测试,如果出了问题应该如何回滚,如何保证回滚可以成功(如何测试回滚)等等。

部署的操作最好都是可以追踪的,因为并不是所有会引起问题的操作都会立即引起问题。比如一个操作当时做完没有什么问题,但是过了 1 个月,偶然的重启或者内存达到了某一个指标触发了问题。如果能记录操作的话,我们可以回溯之前做过的变更,方便定位问题。现在一般都用 git 来追踪部署过程的变更(gitops)。

Oncall

Oncall 简单来说就是要保证线上服务的正常运行。典型的工作流程是:收到告警,检查告警发出的原因,确认线上服务是否有问题,定位到问题,解决问题。

收到告警并不总意味着真正的问题,也有可能告警设置的不合理。告警和监控面板并不是一个静态的配置,它应该是每天都在变化的,时刻在调整的。如果发现没有标志真正线上问题的告警发了出来,就应该修改告警规则。如果发现当前的监控无法快速定位问题,应该调整监控面板,添加或者删除监控指标。业务在发展,请求量在变化,某些阈值也需要不断地调整。

定位问题没有一概而论的方法了,需要根据看到的实时,结合自己的经验,然后做推测,然后使用工具验证自己的推测,然后确定问题的根因。

但是解决问题是可以有方法论的,叫做 SOP,标准操作流程。即:如果出现了这种现象,那么执行那种操作,就可以恢复业务。SOP 文档应该提前制定,并且验证其有效性。

需要注意的是上述定位问题、解决问题并没有顺序关系。一个经常犯的错误是,在出现故障的时候,花了很长时间定位到故障的根因,然后再修复。这样花的时间一般会比较长。正确的做法是先根据现象看现有的 SOP 能否恢复业务。比如说当前错误只发生在某一个节点上,那么就直接下线这个节点,具体的原因后面再排查。恢复当前的故障永远是第一要务。但是恢复操作也要经过测试,比如猜测可以通过重启解决问题的话,可以先重启一台做测试,而不是一次性将所有服务重启。大部分情况是需要临场分析的,是一个紧张又刺激的过程。

故障到底多久恢复算好?出现多少故障是可以容忍的?怎么标志服务的稳定性到底如何?我们使用 SLI/SLO 来衡量这些问题。

制定和交付 SLI/SLO

维护服务等级协议,听起来像是一个非常简单的事情,只要“设定一个可用率”然后去实现它就好了。然而现实的情况并不是。

比如,制定可用率的时候,并不是说我们去“实现4个9”(99.99% 的时间可用)就够了,我们有以下问题要考虑:

  1. 如何定义这个可用率?比如我们以可用率 > 99.9% 为目标,有一个服务部署了 5 个 Zone, 那么有一个 Zone 挂了,其余的 Zone 是可用的,那么可用率被破坏了吗?这个可用率是每一个 Zone 的还是所有的 Zone 一起计算的?
  2. 可用率计算的最小单位是什么?如果 1min 内有 50s 没有达到可用率,那么这一分钟算是 down 还是 up?
  3. 可用率的周期是怎么计算的?按照一个月还是一个周?一个周是最近的 7 天还是计算一个自然周?
  4. 如何对 SLI 和 SLO 做监控?
  5. 如果错误预算即将用完,有什么措施?比如减少发布?如果 SLI 和 SLO 没有达到会怎么样?

等等,如果这些问题不考虑清楚的话,那么 SLI 和 SLO 很可能就是没有意义的。SLI/SLO 也适用于对公司内部用户的承诺,让用户对我们的服务有预期,而不能有盲目的信任。比如 Google 在 SLI/SLO 还有预算的时候,会在满足 SLI/SLO 的时候自行对服务做一些破坏,让用户不要对服务有 100% 可用的错误预期。SLI/SLO 也会让 SRE 自己对当前服务的稳定性有更好的认识,可以根据此调整运维、变更、发布计划。

故障复盘

故障复盘的唯一目的是减少故障的发生。有几个我目前认为不错的做法。

故障复盘需要有文档记录,包括故障发生的过程,时间线的记录,操作的记录,故障恢复的方法,故障根因的分析,为什么故障会发生的分析。文档应该隐去所有当事人的姓名对公司的所有人公开。很多公司对故障文档设置查看权限,我觉得没什么道理。有些公司的故障复盘甚至对外也是公开的

故障在复盘的时候应该将当事人的名字用代码替代,可以营造更好的讨论氛围。

不应该要求所有的故障复盘都产生 Action。之前一家的公司的故障复盘上,因为必须给领导一个“交待”,所以每次都会产生一些措施来预防相同的故障再次发生,比如增加审批流程之类的。这很扯,让级别很高的领导审批他自己也看不懂的操作,只能让领导更痛苦,也让操作流程变得又臭又长,最后所有人都会忘记这里为什么会有一个审批,但是又没有人敢删掉。你删掉,出了事情你负责。

Blame Free 文化?之前我认为是好的。但是后来发现,有些不按照流程操作导致的问题确实多少应该 Blame 一下,比如下线服务的时候没有检查还没有 tcp 连接就直接下线了,或者操作的时候没有做 canary 就全部操作了,这种不理智的行为导致的故障。但是条条框框又不应该太多,不然活都没法干了。

容量规划

容量规划是一个非常复杂的问题,甚至有一些悖论。容量要提前做好规划,但是容量的规划需要知道业务的扩张速度,扩张速度这种事情又不是提前能计划好的。所以我一直觉得这个事情很难做,也一直没有见过做的很好的例子。

但是至少可以对维护的系统建立一个模型,知道多少机器,多少资源,能容纳多少容量。这样遇到大促之类的活动也能及时估算需要的资源数量。

用户支持

用户支持也是日常的一部分。包括技术咨询,以及用户要求的线上问题排查。

这里就需要提到文档的重要性了。如果没有维护好文档,那么用户就会一遍又一遍问相同的问题。写文档也是一个技术活,优秀的需要很长时间的积累。文档也需要经常更新。我一般会这样,保持这样一种状态:用户可以不需要任何人就从文档中找到他需要的所有答案。如果我发现用户的问题无法从文档中找到,或者难以找到在文档中的什么地方,就会更新文档,或者重新组织文档。如果用户的问题已经从文档中找到,那么就直接发文档给他。如果用户的问题显然是文档看都没有看过(有很多人根本不看文档的,只看文档是谁写的然后径直去问这个人),就直接忽略。

优秀的文档应该尽量引入少的专有名词,少使用没有用处的专业词汇描述,只描述具有指导意义的事实,假定用户没有相关的背景知识,列举使用例子,举一些现实会用到的例子而不是强行举例子,明确 Bad Case。等等。这其实是一个很大的话题了,这里就不展开了。

暂时就想到这一些了。下面写一些我经常见到的误解,和经常被别人问的问题。

 

有关做项目没有专业团队得不到训练。

这方面是听到最多的抱怨。虽然说 SRE 在工作上应该是开发时间和运维时间各 50%,但是真实的情况是,即使 SRE 有一些开发工作,也大部分是面向内部用户,面向公司内部的开发者的。大部分项目是一些想法,需要去尝试一下行不行,基本上不会有专业的设计资源,PM 资源。这种项目就需要 SRE 有多方面的技能,包括对产品的理解,清楚地知道它有什么痛点,最好是自己经历过的痛点,然后需要懂设计,管理好开发进度。然而这种人非常少。其实能写中型项目代码的 SRE 就已经非常少了。所以大部分公司内部项目都会做的又难用又复杂。

即使是有专业配套 PM 和设计,甚至前端资源。基本上也是一个灾难。我也经历过这样的团队。这种内部项目对标的不是互联网项目,而更像是 toB 的项目。用户 UI 的设计,交互逻辑,操作流程,交付周期等需要的都是另一个领域的知识。否则的话人越多,也只会徒增沟通成本,拖慢项目进度。

回到经常听到的这个抱怨,说在 SRE 的团队没有像开发团队那样有“正规军”,有设计和 PM,大家各司其职,后端开发只要对齐 API 然后实现就好了。大部分的应届生会有这样的幻想,但实际上不是这样。被搞错的最重要的一点是,学习主要是靠自己的,和别人没有太大的关系。我觉得可能是在一个大团队里面,有很多人一起做一件事情,心里的怀疑和焦虑会少一点,人们会对这样的工作状态感到踏实,误以为是“成长”,自己做所有的工作焦虑更多。

事实是,在大团队工作可能学到更多的沟通技能,比如和不同的人对齐不同的阶段工作目标,要想要学到其他的东西还是要靠自己。比如拿到一个设计,如果照样子去实现了,其实不会学到什么东西。而要去理解为什么这么设计,为什么不那么设计。如果自己去做,思考的过程也基本是这样的,可以怎么设计,选择什么好。都是:思考,选择,尝试,经验,思考……

另一个需要澄清的误区是,模仿并不是学习。在团队中经历了一个设计,如果记住了这个设计,下次碰到类似的问题也用这个设计去解决。这也不能叫做是学习。我见过有在业务部门做过支付的 SRE 写的代码,在内部系统中去实现了订单业务的订单、交易等概念完成一个运维流程,甚至 Model 的名字都没改过。拿着锤子找钉子,会让系统变得更加糟糕和复杂。

总之,工作分的细并不代表工作就会更加专业。一个人身兼数职也可以在每一个方面做得很专业。重要的是不断学习,使用正确的做事方式,向优秀的项目和优秀的开发者学习。

 

有关脏活累活。

每一项工作都会有脏活累活:学不到什么东西,做起来没有意思。可能是整理系统的监控,可能是整理现有的文档,可能清理一些年久的运维脚本,可能是需要和不同的团队做一些沟通工作等。

这是不可避免的,如果可以的话,学会从每一项工作中找一些偷懒的方法吧,比如用脚本处理一些工作,用更聪明的方式工作等等。

但是如果这种工作的比例太高的话,就要思考工作方式的问题了。如果陷入恶性循环,看能不能从工具和工作流程上做一些改变。如果不能的话,考虑换一份工作吧。

 

有关背锅。

互相甩锅的工作环境无疑是非常糟糕的工作环境。如果相同的团队、或者不同的团队之间需要相互勾心斗角的话,如果工作环境不允许大方承认(SRE 无可避免地会犯一些错误)自己的错误,说明公司营造的氛围有问题。比如某些公司规定,发生 P1 级别的错误就必须开除一个 Px 级别的员工,发生 P0 级别的错误就必须开除一个 Py 级别的员工一样。如果是这种情况的话,公司实际上是在用一种懒惰地方法通过提高人的压力来提高系统的稳定性。有没有效果不知道,但是确定的是不会有人在这种情况下工作的开心。建议换一份工作。

 

如何转行?

其实难度没有想象的高,毕竟大学里面没有一个叫做 SRE 的专业。SRE 要求的知识也是编写代码、设计系统、了解操作系统和网络等。所以在大学里面将本科的课程好好学好,尝试做(并维护)一些自己的项目,毕业的时候基本上就满足要求了。非科班的人要转行的话,也可以参考大学的课程内容去补足这方面的知识。

需要注意的是,培训班出来的做开发完成业务可能够,但是做 SRE 远远不够。SRE 不仅需要 make things work,还要知道背后的原理。

 

面试会问什么?

我觉得和后端开发的面试内容基本上差不多。

如果是去应聘的这个岗位所需要的一些技能,比如 K8S,监控系统等,可能也会问一些领域内的知识。虽说这部分工具性的东西都可以学习,但是如果人家要一个经验丰富的、或者入职就能干活的,那么面试成功的机会就会小很多。当然,也不必沮丧,这是市场的供需关系决定的,如果对方执意要找符合特定要求的候选人,那么对方的选择的范围也会小很多,不必因为错失了这种机会而后悔没去学习什么工具。话又说回来,技能越多,选择也会越多。

排查错误可能是转行做 SRE 最大的一个门槛,这个需要一些经验。如果没有经验的话,就补足一些操作系统的知识,这样遇到未知的问题也可以通过已知的知识和工具去排查。

这个仓库是一个不错的面试题集锦:https://github.com/bregman-arie/devops-exercises

 

做 SRE 需要会写代码吗?

会,而且写代码的要求并不会比一个专业的后端开发低。

 

选择大公司还是小公司?

这属于两种截然不同的工作环境。小公司一般都有一个救火英雄式的人物,在公司的时间比较长,知道所有组件的部署结构,什么都懂。跟着这种人学习会成长很快。

大公司细分领域很多。本文前面列出的内容可能每一项在大公司中都是一个团队,对某个领域可以深入研究。

所以还是看想要做什么了。我个人比较喜欢靠谱的小公司,或者大公司中靠谱的小团队。

 

如何判断一家公司是否靠谱?

对于 SRE 这个职位,我总结了一些判断的技巧。比如可以判断一下对方目前的业务和 SRE 员工的数量是否处于一个“正常”的状态,人数是否在随着业务(机器的数量)现象增长?这是一个不好的迹象。是否 SRE 的数量过多?如果 SRE 人太多,有两个可能的原因:1)某个领导为了扩大自己的影响力在为一些“不必要的”岗位招人,这样会导致人多事少,大家开始做一些奇奇怪怪的事情,发明奇奇怪怪的需求,以各种各样的方式浪费自己的时间来领公司的工资;2)这个公司的基础太差,大部分工作都是需要人力运维,导致基本上有多少机器就需要多少人。总之,都不是什么好事情。

一些技术比较好的公司,都没有庞大的 SRE 队伍,比如 Instagram, Netflix(现在可能人数不少了),以及一些创业公司,甚至都可以没有专门的 SRE,优秀的 SRE 首先要是开发者,优秀的开发者也离 SRE 不远了。一些耳熟能详的服务,比如 webarchive 这样的数据量,其实背后也只有几个人在维护。前几年面试了国内的一家公司,在机房遍布全球,业务已经发展的比较庞大(上市了)的时候,SRE 团队也只有 10 个人。

另外我比较喜欢问的一个问题是对方关于 AIOps 怎么看。因为我之前搞了两年这个东西,最后得到的结论是,这基本上是个浪费时间、欺骗上层领导的东西。AI 这东西的不可解释性本质上就和运维操作将就因果相违背的。所以经常喜欢问面试官怎么看这个技术,基本上就可以判断靠不靠谱。当然了,这是我个人的职业阴影导致的后遗症,只能代表个人意见。

 

就说这么多吧,都是一些个人理解,不一定对。写这篇文章感觉自己好像指点江山一样,其实我自己也干了才几年而已,所以本文内容仅供参考。如果有什么问题可以在评论提出,我能回答的话就尽量回答。