我是一个 Python 程序员,也对 Lua,Go,Rust 感兴趣。目前在蚂蚁金服工作。

我的日常工作离不开 Vim 和 Tmux,当然还有 Git。平时喜欢在看 Github 上有趣的项目,学习新的东西,看书,写博客等。博客的内容一般是我的人生感悟,观后感读后感,以及技术方面的内容。

我的梦想是让网络变得更加开放、自由和快速。

声明:本博客内容仅代表本人观点,和我的雇主无关。本站所有内容未加说明均为原创,一经发布自动进入公共领域,本人放弃所有权利。

Gitops 的一些实践经验

之前看过多很多讲 Gitops 概念的文章,今天终于看到一篇讲实践的(原文见这里),我觉得这篇文章很有参考价值,介绍了一些 gitops 实在会遇到的问题和工具,和大家分享一下。

1.只用一个 git 仓库

建议所有跟基础设施有关的内容都放到一个仓库,包括有的团队、所有的项目。比如 kubernetes 的 template, infra as code 的平台,比如 terraform,比如 ansible playbooks,监控设施比如 grafana dashboards, alerts, 等等。

这样有哪些好处呢?

  1. single source of truth。线上的真实环境,实际在生效的配置,都可以在这一个仓库找到,就避免了去各个平台看现在生效的是什么配置的问题;
  2. 可以将在 CI 中设置 lint、准入检查等。虽然如果分多个仓库,也可以分别设置 CI,但是那样毕竟容易乱。用 CI 我们可以自动的检查某些变更是否符合标准,一开始可能是全部要人工检查(去 Review Merge Request),但是逐渐自动化起来难度也不大;
  3. 灾难恢复更简单。都放在一个仓库里面,可操作性就很强了,不然你要处理多个仓库的先后顺序问题,相互依赖的问题等等。
  4. 加强了管控能力和审计。这个很好理解,毕竟只有一个仓库嘛。但是 git log 一定要写好,审计才方便。

作者推荐了一个目录结构:

思考:

只用一个仓库所带来的透明性,收益是很高的,审批可能都是 merge requests 了。我们也不必各种问”最近有什么变更”了,每个人都可以去看 git log。如果操作都能设计成声明式的,那么回滚也很方便,revert log 就可以了。

恢复整站,或者再搭建一套环境速度也大大提高,waveworks 经历过所有机器都被抹掉的情况,得益于 gitops,集群的重新 provision 只用了 45min.

2.自动化

Automation is key because it speeds you up immense.

作者在这里举了一个例子,如果用 Prometheus 的话,可以将 HTTP 服务的大盘监控抽象成一种通用的模板,新加一种 HTTP 服务的话大盘可以自动生成。(监控规则同理)

思考:

在Web平台上点点点,是比较难自动化和复用的,但是如果是 Code 就不一样了。你可以用你最喜欢的Vim编辑器快速处理大量的配置文本,也可以用脚本批处理,可定制化很高。但是这依赖于 Infra as Code,监控代码化,configuration-as-code、database-as-code、infrastructure-as-code 等。

配监控太痛苦了!我觉得比较理想的监控是:中间件层收集一些通用的 metric,开发同学在代码中(用注释或者继承类的方式?)暴露关键的业务指标,大盘可以根据template自动生成。(要是真这样就好了)

3.能用Operators就用Operators

简而言之,可以减轻手动 Apply 的负担。(既然都 gitops 了,鼠标能少点几次就少点几次吧。)

举几个例子:

  • Atlantis: 基于 PR 的 Terraform 流程,看这里一张图就明白了。
  • flux: waveworks的作品,确保 git 仓库的配置和 k8s 集群中的状态一致。(消除 diff 应用变更,readme 里面也有一张图展示的很明白)

思考:

XX as code, 声明式,diff merge 应该是 gitops 的核心吧?

4.Secrets 能够被自动获取

Secrets are still just parts of the deployment, that is why they are required for full disaster recovery for example.

推荐将 Secrets 存在仓库中(当然了,加密存储),或者在部署后能以某种形式自动地获取。这里的关键和难点是保持 Secrets 以加密的形式存储,严禁明文存入 repo。

Problem: “I can manage all my K8s config in git, except Secrets.”

Solution: Encrypt your Secret into a SealedSecret, which is safe to store – even to a public repository. The SealedSecret can be decrypted only by the controller running in the target cluster and nobody else (not even the original author) is able to obtain the original Secret from the SealedSecret.

— sealed-secrets

推荐的几种形式有:

  • 用类似 sealed-secrests 的 Operator,已加密形式将 Secrets 存到仓库,Secrets 到达集群的时候进行解密。(同类产品还有 Mozilla 的 SOPS)
  • 使用 Hashicorp Vault 类似的集中式 Secrets 管理工具
  • 云服务商提供的同类产品
  • 使用 git-crypt 或 git-secret 进行手动加密

以上,水平有限,如果有疑问可以看下原文确认,如有理解错误欢迎指出。如果原文有错误欢迎讨论。

 

virtualenv的原理

每个语言都是有自己的包管理工具,包管理是一个又复杂又难的话题,我觉得复杂度跟GC这个话题相比都不为过了。有趣的是,每个语言选择的包管理方案、依赖解决方案都多多少少不太一样,或多或少每个语言都做出了一些选择和取舍。比如 Go 是直接依赖 Github 做依赖管理(被大家吐槽很多次),node 是根据 package.json 下载到项目的 node_modules 目录(被吐槽也很多,主要是下载的东西太多了,js 社区的一个风格又是依赖层层嵌套比较重)。Python 的包管理也被吐槽很多,大家吐槽的点主要是太复杂了吧,解决方案又太多,这方面很不Python哦。比如你要去了解 virtualenv,virtualenv wrapper,pyenv, pip, pipenv, 等等……

但是 Python 语言的机制决定了它解决依赖的原理是一样的,这篇文章就来解释 virtualenv 机制的原理。

首先我们来看一下,这些包管理机制要解决的最根本的问题是什么?是多个项目的版本冲突呀!比如A项目依赖了 x package 的版本1,B项目依赖了 y package 的版本2,两个版本又是不兼容的,怎么办?如果都装到系统目录下,那么肯定就有一个项目不可用了。virtualenv 就是来解决这个问题的。

一个实时是,Python 一开始并没有涉及 virtualenv 这样的机制来管理这个语言的包,而是因为 Python 自身寻找包的机制,导致了 virtualenv 这种包管理形式的出现。

从 sys.prefix 说起

当 Python 解释器启动的时候,它会从解释器所在的 Path 开始,加上 /lib/python$VERSION/os.py 来逐层向上查找。因为 os.py 是解释器启动强依赖的包。比如我现在的 Python 启动目录是 /usr/bin/python3.7,那么查找过程就是:

这里贴一下 strace python 的记录,以下是在我电脑上启动时候的真实查找 os.py 的过程:

找到之后会设置 sys.prefix 这个变量,解释器去找包的时候,就去 prefix/lib/PythonX.Y 中去找。virtualenv 的工作原理就是基于这个:如果你需要一个隔离的项目 virtualenv,那我就给你复制一个独立的 Python 解释器可执行文件,然后根据相对目录把你需要的包都放在这个解释器所在的目录下,这样这个解释器启动的时候就可以找到(并且只能找到)这个目录下的包,virtualenv 就实现了独立包依赖的方案。

virtualenv 工作原理

virtualenv 是这样工作的:首先 virtualenv 会复制 Python 解释器的可执行文件到 $VENV_PATH/bin/python,然后创建 $VENV_PATH/lib/python3.7/xx.py 到系统的 os.py 所在的目录的模块的软连接(这样可以节省空间)。根据我们上面说过的解释器的启动原理,启动的时候,根据解释器所在的目录,会找到 VENV_PATH 下面的包,我们安装包的时候,也是安装到这里。这个解释器所使用的包就和其他解释器隔离开了。

为什么解释器的可执行文件需要拷贝一份,而不是也通过软连接的方式呢?因为解释器会解析软连接的目标地址,如果使用软连接的话,包也会使用系统Python的。那硬链接可不可以呢?这个我没研究过,我看 virtualenv 的源代码里面有一个 FIXME 提出了这样的想法,但是没有去实践。

这就是它的基本工作原理了,使用的时候,无非就是将这个 virtualenv 的 bin 目录插入到 $PATH 的最前面。然后我们执行 pip Python 这样的命令,就会执行到 virtualenv 里面的。

Python3

如果使用 Python3,那么在生产环境就不需要安装 virtualenv 来创建虚拟环境了,Python3 内置了 venv 模块。

直接使用 python3 -m venv myenv 创建虚拟环境即可。

这个 venv 的原理,还是和上面我们说过的一样。但是 Python3 有一些提升,它的 Python 可执行文件是一个软连接了,用一个 pyvenv.cfg 来标志出 home 的位置。

它的文件内容如下:

如果 include-system-site-packages 为 true,解释器启动的时候就会将系统的库添加到 sys.path 里面,这样我们在虚拟环境就可以 import 系统中安装的包了。

参考资料:

  1. https://realpython.com/python-virtual-environments-a-primer/#why-the-need-for-virtual-environments
  2. https://rushter.com/blog/python-virtualenv/
 

Kernel space, user space, and syscall

这篇文章介绍什么是 kernel space 和 user space,以及系统调用(System call,以下会用 syscall表示)。

为什么要分成 kernel space 和 user space 呢?

这要对系统对进程的抽象说起,每一个进程看到的内存空间都从固定的位置开始,main() 总是从 0x4005db 开始,stack 也总是从特定的位置开始。那么不同进程使用的内存都是重叠的吗?

当然不是。这就是系统对进程的抽象。每个进程只能看到自己的内存,MMU(内存管理单元)将解决每个进程看到的虚拟内存和实际物理内存的对应(内存管理是一个很大的话题,这里只是简单的描述,有关内存管理有很多资料可以参考,这里提供一个有趣的文章:Virtual Memory With 256 Bytes of RAM – Interactive Demo)。不仅仅是内存,每个进程只能使用有限的资源,而 kernel 可以使用所有的资源。当 user space 需要使用硬件资源时,user space 通过 syscall 告诉 kernel space,kernel space 来完成调用。

这样做的目的是将资源隔离开,确保 user space 崩溃不会影响 kernel space;以及限制 user space 所能做的事情,不能让程序可以随意进入 kernel space 进行任意的操作。怎么做到的呢?当计算机启动的时候,CPU 会进入 ring0,即特权状态,在一个地址设置好 syscall 的对应关系(这个对应关系可以在这里查),比如 1 对应的就是 write() syscall,然后 CPU 退出到 ring3,即 user space。再次进入 ring0 的唯一方法就是通过 syscall。syscall 要求传入一个 int 表示要调用哪一个 syscall,这样,kernel 就可以控制所能执行的动作,即只能执行事先定义好的 syscall mapping。

Syscall 和一般的函数调用是不一样的,这一点读者应该已经明白了。因为在程序内部,可以使用 stack 或内存在调用之间传值,但是 user space 和 kernel space 是隔离的(当然也可以通过共享的内存传值),stack 不是共用的。如果写一个简单的 write() 程序反编译,可以看到 syscall 的汇编代码是 mov exa, 1 syscall ,其中1就是前面我们说的 syscall mapping,即 write(),然后 syscall 是 user space 向 kernel space 发出的软中断,进入 kernel space,kernel 会检查 exa 寄存器的值,找到对应的函数执行。(不同的架构实现会有所不同)

简单来说,kernel space 和 user space 是两个世界,syscall 就是连接这两个世界的桥梁。虽然这个桥梁我们一般不会直接使用,而是通过 glibc,glibc 是 syscall 的一个 wrapper,让我们 call 起来更加简单方便。比如说 printf 函数,其实就是 write() 的一个 wrapper。

通过 man 2 write 可以看到 write() 的原型如下:

所以我们可以这么调用,向屏幕打印字符:

有关 syscall 大体就是这样。以上是我的理解,如果有错误欢迎交流。下面再介绍一些相关的工具。

上面那张截图 radare2 的界面,一个反编译工具。使用方式是 radare2 -d ./a.out ,然后在s sym.main ,设置断点,可以看到 syscall 是如何执行的。

通过 man syscalls 可以查看系统提供的 syscalls. strace 工具可以将一个进程使用的 syscall 输出到 stderr,包括调用的参数,和返回值。比如查看 ls 使用的系统调用:

-c 参数很有用,可以显示每次 call 花费的时间,call 的次数,占用的总时间等,是一个 perf 的好工具。

 

参考资料:

  1. Linux System Programming
  2. Youtube: Syscalls, Kernel vs. User Mode and Linux Kernel Source Code – bin 0x09
  3. jvns 的这篇漫画:https://drawings.jvns.ca/userspace/
 

谈谈预防故障的性价比

说到保障系统的稳定性上,从预防的层面上看,总是有无数的事情可以去做。我觉得人们经常陷入的一个误区是,总是假设系统的某些方面会出问题,然后想办法针对这些特定的问题去做预防,认为预防好了这些问题自己的系统就万无一失了。这就导致很多时间花费在穷举系统可能出现的错误上、针对特定的错误做预防措施上。

最常见的错误就是增加流程。比如上线的流程、修改某个参数的流程。很可能一开始所有的流程都是简洁并快速的,每个人都可以将精力集中在自己的工作上。直到有一天出现了故障,大家在复盘的时候,发现“哦!假如说我们在发布之前增加一个流程,可以确认一下某个东西没有错误,那这个错误就可以避免了!” 这样的故障复盘越来越多,流程也就开始堆积的越来越多了,比如发布之前要求满足多少的覆盖率测试才能发布,某个变更做出之前必须经过某某审批才能执行。

其实添加一个流程是复盘最容易做出的决策——如果什么都不做的话,就等于问题发生了,作为领导或者负责人什么作为都没有,这个事情后面再发生或者再说起来就会很尴尬。事实上,但很多时候这么做其实是没有什么用的,首先,发生的故障一般都是新的故障,对已经发生的故障进行预防性价比太低;然后增加的很多流程其实并没有什么实际作用,最后会变成一种形式主义。比如强制要求测试的覆盖率,会导致程序员去写一些除了增加覆盖率并没有实际测试功能的代码;增加审批流程会徒增同事之间的沟通成本和审批时间,审批人大部分情况下可能也不知道他审批的是什么东西,背后有什么风险,最后也变成了纯粹的“走流程”。

另一种错误是试图穷举出系统中所有可能发生的故障,对这些故障设定自动化的处理方法或做好这个特定故障的应对方案。在一个很复杂的大型的分布式系统中,穷举出所有的故障几乎是不可能的。针对已经发生的故障提供针对的应对策略或者自动化的解决方式也帮助不大,虽然看起来给人一种安全感——我们每次的故障都不会再发生啦!但实际上就算什么都不做,相同的故障再发生一遍的概率又有多少呢?这又犯了形式主义了,我们从错误中学到的不是预防这一个错误,而是想想这个错误为什么会发生,是不是我们现有的机制出了问题(不要盲目修改机制,不然就犯了上面说的“流程”类的错误了。增加一个流程应该是慎重的,要考虑到这对将来每次的流程都会增加成本。)问题发生之后我们用了多长时间恢复,能否提高恢复的速度?还有一个方面,这种强调特定错误的“自愈”一般是和业务强耦合的,业务在发展,“自愈”的测试成本又很高,很难保持这种“自愈”长期有效。

将大部分的成本花在这部分上面,可能看起来让人很有安全感:“看,我们能应对这么多的情况了。”但是这些穷举出的情况并没有多大的意义,故障依旧会发生,并且总是以我们没有想到的方式。

所以我觉得将精力放在补救措施上会更有意义。寻找那些应对场景广泛的补救方法,不去针对特定的场景,而是针对特定的表现。比如部署在多个 Available Zone,如果监控显示一个 AZ 的流量有问题,无脑切换到其他 AZ 就可以;比如对非关键的服务提供降级措施,日常可以快速发布和迭代,如果出现故障立即降级即可。

以前软件行业那种开发与运维职能分开的模式,典型的矛盾就是开发想快速迭代,运维为了稳定性不想做出任何改变。现在流程的 devops/SRE 文化其实并没有从根本的解决这个矛盾。我觉得答案可能就是 Facebook 那句话“Move fast and breaking things.” 错误终究会发生,不要试图完全预防错误,应该尝试提供快速补救、简单可靠的方案(简单很重要,只有简单的东西才是值得依靠的)。并且还有要 blame free 的文化——建立学习和责任的平衡,不带有惩罚性、责备性的报告。不要再使用不专业的(恐吓性质)的故障责任机制。

相关阅读:

  1. 谈谈 Ops(最终篇):工具和实践

 

厦门游记

今年的五一假期有四天,于是就跑去厦门玩了,这几天正好天气也不错,好像早早的迎来了今年的夏天一样。

厦门是一个岛(我竟然出发前才知道,之前一直以为是福建省的一个沿海城市),岛最外圈是一整圈马路,叫环岛路,很漂亮。一圈大约30km,本来想骑一圈的,但是每天都很累,后来选择坐观光巴士看了一圈。

说起来厦门市和我的家乡青岛很像,虽然青岛只是在一个半岛上。但是厦门有沙滩,青岛也有;厦门地势不平,很多坡,很多隧道,青岛也是;厦门吃的基本是就海鲜,青岛也是。但是房价倒是比青岛高的多,我以为厦门也就是个二线城市的样子,没想到房价跟上海差不多。市中心要10万/平左右,最低也要5万。应该是岛上的资源有限吧,就这么大的地方,盖完了就没了。青岛有地方填过海造陆地,厦门也填过,但是已经不能再填了,沿海的地方已经填的很深了。最近几年厦门一直将工厂往岸上迁移,现在岛上的大部分工厂已经迁到对岸了,所以厦门的空气非常好。听一个司机说,岛上的机场也准备迁到另一个岛上去,因为机场在厦门主岛上一是占地方,二是周围的楼层要限高。嘿,这也跟青岛一样,现在我们那边的流亭机场要迁到胶州去。司机还说厦门很排外,不知道真假,但是看起来肯德基,星巴克,麦当劳这种连锁店在厦门很少见到,厦门的连锁店都是我之前没见到的一些牌子(厦门人见了不要伤心,司机说的不一定对)。

厦门的位置基本就在台湾岛的正对面,所以会有很多台湾文化的影响。南边有一个沙滩,就叫“一国两制沙滩”,路上还写着“一国两制,统一中国”。岛上有一个中心干道叫“成功大道”,一开始以为就是一个普通的名字,后来想想应该是纪念郑成功的。鼓浪屿上还有一个皓月园,是专门为纪念郑成功建的,名字取自“思君寝不寐,皓月透素帏”。里面有一个郑成功大雕像,是郑成功的10倍高。

说起吃的,倒觉得厦门没有什么特别好吃的东西。网上搜到的著名小吃沙茶面、花生汤、土笋冻都觉得很一般,去了大名鼎鼎的醉一号吃海鲜,也觉得一般,食材倒是感觉挺新鲜的。中山路步行街、局口街小吃、八市这些地方,跟全国各地的小吃街都差不多,都买油炸香肠、臭豆腐什么的,没有什么特别好吃的。我估计曾厝垵应该也是类似这种东西,所以就直接没去,不太喜欢这种商业化的小吃街。大众点评有一家很有名的惠源面包店,可惜我12:30去的,小哥跟我说现在啥都没有,要15:00才能拷出来。好吧。

倒是现在的榴莲很便宜,买一整个的话是 15/斤,也非常好吃。

鼓浪屿是离厦门主岛很近的一个小岛,挺漂亮的,可惜现在控制上岛人数,每天5w人,船票就很难抢(游客的码头和居民的码头是分开的),我只抢到 16:30 上岛的(本地司机跟我说他小时候都游过去)。上去之后就去了日光岩看日落,非常美。然后天就黑了,在岛上随便溜达了几圈,晚上坐在海边玩了一会就回去了。鼓浪屿上面猫特别多,印象深刻,很多旅馆饭店名字都带“猫”。去的时候又是春天,到了晚上这个猫叫,此起彼伏。

厦门大学旁边有一个西村,简直太大学了,好像所有的大学附近都有这么一个地方:各种小店门头林立,装修散发出廉价又想文艺的气息,价格便宜味道普通,但是都有一个文艺的名字。在这里同样也没找到啥好吃的。

铁路文化公园很漂亮,废弃的铁路改造的狭长的公园。我还是第一次见这么长形状的公园。

植物园、南普陀这些地方还没来得及去。

几点到厦门的旅行建议:

  • 坐飞机非常方便,高崎机场现在离市中心很近(离我们住的酒店就4km),高铁站倒是挺远的,所以建议坐飞机来
  • 提前做好攻略,提前准备,非常重要!下面是一些必须提前预定的东西
    • 鼓浪屿限制上岛人数,五一不提前三五天基本搞不到票,我们只买到下午上岛的了,尽量提前看票;飞猪有旅行社可以帮你抢,他们成功率高一些,但是票贵,2-3倍的价格;
    • 环岛巴士在网上(美团大众点评、飞猪等)订票的话,要付钱之后2小时才能出票,所以不要想着去了再在网上买票,要提前2小时买。实在不行现场买也可以,网上买45/人,现场买60/人。早上8:30 – 下午16:40 不限次数随便上下。
    • 厦门大学控制入校游客数,不要想着混进去,要提前预约。
  • 不要让司机推荐你去哪玩,你问10个司机,9个告诉你去坐帆船。帆船并不好玩,他们拿回扣的。

下面是照片的分割线。


环岛路

观光巴士风景

对面就是鼓浪屿

厦门路边种了很多这样的花,美

环岛路高架

厦门大学附近的“西村”

西村里面的猫文化

铁路公园,晚上拍的,不是很好

沙滩

海边

杂乱的局口小吃街

厦门地铁

中山步行街

八市

第一次体验缆车

车上没有玻璃,门就一个栓子插着,感觉挺危险,挂个风我就掉下去了。

去之前我还专门学习了缆车的结构

好在不是很高,下面都是树木

大名鼎鼎的惠源面包店,可惜我去的时候说还没烤好,要下午三点。隐藏在八市中的看起来非常不起眼的一家小店。

鼓浪屿上面的猫,看起来挺抑郁。猫又出不了岛,应该跟岛上其他的猫都认识吧。

日光岩18:00之后票价半价,现在日落时间是18:35左右,正好上去看日落

俯瞰厦门市

等待日落

鼓浪屿码头,游客专用

福建土楼,田螺坑(四菜一汤)

土楼

土楼2

《大鱼海棠》里面的土楼

土楼3

土楼4

这是我女朋友

土楼的照片是我女朋友用 iPhone Xs Max 拍摄;其余照片是 iPhone 6s 拍摄。图片经过了压缩。