惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

U
Unit 42
K
Kaspersky official blog
L
LangChain Blog
有赞技术团队
有赞技术团队
V
Visual Studio Blog
T
The Blog of Author Tim Ferriss
Microsoft Azure Blog
Microsoft Azure Blog
Stack Overflow Blog
Stack Overflow Blog
美团技术团队
Vercel News
Vercel News
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
G
Google Developers Blog
宝玉的分享
宝玉的分享
月光博客
月光博客
爱范儿
爱范儿
The Register - Security
The Register - Security
S
SegmentFault 最新的问题
M
MIT News - Artificial intelligence
F
Full Disclosure
Engineering at Meta
Engineering at Meta
阮一峰的网络日志
阮一峰的网络日志
Hugging Face - Blog
Hugging Face - Blog
A
About on SuperTechFans
WordPress大学
WordPress大学
D
Docker
Y
Y Combinator Blog
N
News and Events Feed by Topic
P
Proofpoint News Feed
Apple Machine Learning Research
Apple Machine Learning Research
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
Recorded Future
Recorded Future
IT之家
IT之家
C
CXSECURITY Database RSS Feed - CXSecurity.com
Security Latest
Security Latest
Spread Privacy
Spread Privacy
D
Darknet – Hacking Tools, Hacker News & Cyber Security
F
Fortinet All Blogs
AWS News Blog
AWS News Blog
C
Cyber Attacks, Cyber Crime and Cyber Security
P
Proofpoint News Feed
C
CERT Recently Published Vulnerability Notes
D
DataBreaches.Net
Recent Announcements
Recent Announcements
博客园 - Franky
Google DeepMind News
Google DeepMind News
小众软件
小众软件
B
Blog RSS Feed
I
InfoQ
Attack and Defense Labs
Attack and Defense Labs
云风的 BLOG
云风的 BLOG

卡瓦邦噶!

服务器高性能网络调优 | 卡瓦邦噶! 为何写作 | 卡瓦邦噶! 读《金阁寺》 | 卡瓦邦噶! 雨季又来 | 卡瓦邦噶! MTU Probe 引起的初始延迟 | 卡瓦邦噶! 3.5 秒的固定延迟问题 | 卡瓦邦噶! 学习网络的一点经验 | 卡瓦邦噶! ARP 问题诊断 | 卡瓦邦噶! 网络断断续续…… | 卡瓦邦噶! Piccolo P2P 镜像分发 | 卡瓦邦噶! 一起看电影 | 卡瓦邦噶! 《征服C指针》 | 卡瓦邦噶! 我的姥姥 | 卡瓦邦噶! Python的哲学 Python 3.5的新特性 学校不教的计算机课 垃圾回收(GC)的三种基本方式 在编程中体验纯粹的快乐 从《美丽新世界》谈自由 在快钱实习 迷人的嗓音和迷人的故事——《Sleepyhead》 Python 的十个自然语言处理工具 记一个愚蠢的bug 一年炉石传说的游戏体验 《以撒的结合:重生》网页版图鉴 分清 C++的指针、引用和数组 笑话三则 自由比皇帝更伟大——《悲惨世界》笔记 Git 10 周年访谈:Linus 讲述背后故事 用 0x3f3f3f3f 设定最大int值的优点 Joel给计算机系学生的建议 一个词法分析器的简单实现 怎样才算健康的生活方式 MacVim 配置攻略 学习培训课程的视频效果好吗? 语言的控制 可悲的大多数 CSS样式思维导图 2014年终总结 奥巴马成为首位写程序的美国总统 青岛老城区的下水道好在哪里? 做优秀 UI 的七个建议(第二部分) 选择爱情的骑士 Git简明教程 Java集合总览 读 《1984》 java问答:终极父类(六)——等待/唤醒和接口 Java 问答:终极父类(五)——toString() Hyperlapse快速视频背后的技术细节 平庸之恶 Java程序员须知的七个日志管理工具 苏州 逛书摊随想 关于考试作弊 你的工作不仅仅是编程 推荐在线学习Java的英文资源 关注女性命运——《千禧年三部曲》 用好你的幻灯片——《演说之禅》 使用ReentrantLock和Lambda表达式让同步更纯净 新手学编程,从哪里开始? 程序员都是工程师吗? 不要学习代码,要学会思考 Java 问答:终极父类(四)——hashCode() Java 问答:终极父类(三)——finalize()和 getClass() 程序员职业之路的选择 我是一名摄影家 写给何小树的城市指南 为什么一些语言会比别的快? Java的常见误区与细节 跟朋友在一起玩游戏 死神永生——读《三体》 五种类型的程序员 创业圣经——读《黑客与画家》 纪念加西亚·马尔克斯 Junit中处理异常的另一种方式:catch-exception Java8采用Martin Fowler的方法创建内部DSL Linux HotSopt虚拟机GC线程的CPU占用率 J2EE概念介绍 如何成为一名黑客 Java 问答:终极父类(二)——equals()方法 为什么我喜欢Java Java 问答:终极父类(一)——clone()方法 七个改变世界的Java项目 java中默认类型转换的小问题 传统与创新 欢迎来到互联网 莫言和马尔克斯——读《生死疲劳》 位运算的妙用 读《人为什么活着》 写博客教会我的事情 中国特色操作系统 2013年总结 《永不妥协》影评 恨不相逢未嫁时——《廊桥遗梦》影评 如何优雅地使用PPT 给明年依然年轻的我们 天才与柱子 黑客守则和黑客精神 Looking for Freedom——《被解救的姜戈》 简洁之道
SRE 线上操作指南
laixintao · 2023-10-12 · via 卡瓦邦噶!

我们每天要进行大量的线上变更操作。怎么保证这些操作安全,不会导致故障,是我每天都在思考的问题。

这篇文章从工作经历总结一些原则和想法,希望能有帮助。

线上操作有几点基本的要求:

  • 操作需要是可以灰度的 (Canary):即能够在一小部分范围内生效,如果没有问题,可以继续操作更多的部分;
  • 操作必须是可以验证和监控的:要知道自己操作的结果,是否符合预期;
  • 操作必须是可以回滚的:如果发现自己的操作不符合预期,那么有办法能够回到之前的状态;

逻辑很简单:假设我一开始做操作范围很小,可以灰度,做完之后我可以监控是否符合预期,如果不符合预期就回滚,那么,操作就是安全的。

这三步中的每一步看似很简单,但是实际做起来很难。

灰度

发布过程是最简单的一种灰度场景,现在有蓝绿发布模式:

  1. 分成两组,将所有的流量切换到绿组;
  2. 先发布蓝组,此时是没有流量的,发布完成之后,将流量逐渐切换到蓝组;清空绿组;
  3. 然后发布绿组,发布完成之后将流量切换到平均到两组

还有滚动发布,对于每一个实例:让 Load Balancer 不再发给它新的流量,然后升级,然后开始接收流量,如果没有问题,继续以此处理其他的实例。

几乎每个人都可以理解灰度的必要性,但是不是每一种操作都是可以灰度的。

比如说数据库 DDL 的变更,很难灰度,提交到数据库,数据库就开始应用了;还有一些动态配置系统,一些全局配置,如果修改,就对所有的应用同时生效的;一般都是这样一些数据源类型的变更,很容易出现不支持灰度的情况。不可以灰度的情况也是最容易导致问题的。

一个替代方案是,搭建一套一模一样的环境,在这个环境先应用变更,测试一下是否符合预期。但是在今天分布式环境下,很难模拟出来一模一样的环境,可能规模小了,可能测试环境没有一些用户的使用场景,等等。总之,模拟的环境没有问题,不能代表生产的环境就没有问题。

最好的解决办法,还是在软件和架构,从设计上就能支持灰度。

验证与监控

所有的操作,一定要知道自己在做什么,效果是什么。做完之后进行验证。听起来很简单,但是实际上,很多人做事像是闭着眼睛,不知道自己在做什么,做完之后有什么效果也不管。

验证操作的结果

举一个例子,比如目前网关遇到了什么问题,经过查询,发现和 Nginx 的一个参数有关,然后根据网上的内容修改了这个参数,回头去看问题解决了没有。如果没有,继续在网上查资料,看和什么参数有关。

上述操作,一个潜在的问题是,当问题真正修复了之后,我们不知道自己做了啥才修复问题的。也有一些时候,相同的配置变了名字,实际上这个修改这个参数是可以解决问题的,只不过我们用了从网上得到的过时的参数名字,所以不生效。

所以,对于每一个操作,推荐直接去验证目前的操作结果。比如改了一个 log 参数,那么直接去看这个参数是否生效,是否符合预期,然后再去看其他的问题是否得到解决。

做操作要一步一步来,做一步验证一步。

另外,最好去验证操作的副作用,而不是验证操作本身。比如,修改了一个配置,不是去 cat 一下配置文件确认就可以了,而是要去看自己修改的配置是否真的生效了。比如路由器设备,我们执行了一些命令 ip route ... ,验证的方法并不是 show running-config 去看配置是否有这一条,而是要去看 show ip route 确定配置是否生效。比如修改了 postgres 数据库的一个配置,重启数据库,并不一定意味着配置生效了,你可能修改了一个错误位置的配置文件,验证的方法应该是进入到 postgresl 数据库中,然后执行 SHOW ALL 命令,校验配置是否是预期的。

验证核心的业务指标

除了验证操作结果之外,也要关注业务指标是否还正常。

如果业务指标不正常了,而恰好和自己的操作时间吻合,那么就应该立即回滚。

听起来很合理?但是实际上,很多人(我也是)第一反应都会是,我的操作不可能引起这个问题,让我先看看日志,到底发生什么了。

当发生问题的时候,时间很宝贵,正确的做法是第一时间在群组里面宣布自己的操作(事实上,操作之前就宣布了,但是消息太多,没有问题的时候没有人会认真看操作历史),然后开始进行回滚。可惜的是,我发现这么做的人很少,大部分都是想去排查,直到确定是自己的操作导致的,才开始回滚。

回滚

回滚方案至关重要。有了能够工作的回滚方案,在出现未预期的问题的时候,我们可以不需要调查根因就直接触发回滚操作,最大限度减少损失。

但是同上,不是所有的操作都可以回滚的。一些可以补偿的方案有,操作上尽量设计成可以回滚的(有些废话)。比如,DDIA 这本书就介绍了数据上如何做向前兼容和向后兼容的方法。

举个例子,比如软件新版本的一个配置要从名字 A 改成 B,不要直接改,而是添加一个配置 B,代码里面可以读 B,如果没有的话,尝试读 A。等升级完成之后,在下一个新版本中,去掉 A 的逻辑。这样,每两个版本之间都是兼容的。

一次只做一个操作,不要将多个操作合在一起

如果将多个操作合在一起,上面的灰度、监控和回滚都不好做了。不知道问题是哪一个变更造成的。

原则上一次操作只做一个修改。

操作计划和操作记录

一些复杂的操作,比如修改 DNS,配置网关,配置其他东西,可能是联动的。而且现实中也不是所有的东西都适合自动化的。这些复杂的操作,推荐在操作之前就写好操作计划,然后对着一步一步操作,贴上必要的验证结果和操作时间。万一出现什么异常,就可以将异常出现的时间和自己的操作记录对照,很有用的。操作计划也可以相互 review,如果是 gitops 的话,就更好了。

将参数尽量写在操作流程,而不是操作的时候

比如在操作某一个节点的时候,一种方案是,直接在操作的 pipeline 中输入 IP,然后执行操作。但是 IP 很容易输入错误,看到这个 IP,也不会反应过来这到底是哪一台机器。所以,更好地一种方法是,给这个 IP 一个名字,我们使用名字操作,而不是指定 IP。

比如在 Ansible 的 inventory 中,我们可以这样写:

1

webserver_us_1 ansible_host=10.0.0.1

这样,我们在操作的时候,输入的参数就是 webserver_us_1 了,输入一个 IP。(我已经见过很多个因为搞错测试和生产的 IP 而导致的线上事故了)。

另外如果要操作多个机器,可以将 Host 按照区域,灰度步骤等,进行编组。这样在操作的时候,直接指定编组,而不是临时将机器进行分组。

使用像 Jenkins 这种平台的时候,执行 Job 的时候,最好将参数变成选择项,而不是 text 输入,减少错误的可能性。

总结来说,就是尽量将操作的时候需要填写的选项降低到最小。将操作固化下来,review 这些操作,进行测试,测试没问题之后,merge 到主干。这样,对操作步骤就可以更有信心。

操作的每一步都设计成可以失败的

为了进一步防止在操作的时候选错参数的情况,需要在设计操作流程的时候多花点心思才可以。

操作的每一个都应该设计成可以失败的,在大多数的实例是可用的情况下,少数实例的重启甚至宕机应该不引发问题才对。这样,即使在操作的入口选择错了参数,本想执行测试环境却执行了线上环境,那么也不会有什么大问题。

举一个例子,服务升级的步骤,不好的写法是,直接将服务的 binary 下载到执行目录,然后 reload;比较好的写法是,将服务的 binary 下载到一个临时目录,下载完成,校验 checksum,然后设定权限,最后通过 symbolic link 链接到可执行的位置。后者的好处是,无论在哪一步失败,都不会造成问题。

理想的情况下,所有的步骤无论执行或者失败都不应该造成影响。比如,保证所有 merge 到主干的代码都是可以部署的,即使不小心触发部署,也不应该造成很严重的事故。这样,即使「手抖」也不应该造成问题,SRE 的生活将会充满阳光,而不是每天提心吊胆。

Ad-hoc 命令

我们经常需要 ssh 登录机器来运行一个命令做检查。对于敲下的命令,一定要知道这些命令的原理

举几个不当操作的例子:

  • 有同事使用过 Vim 查看日志。这是不正确的,用 Vim 打开日志文件(通常很大)会将文件读入内存,导致机器的内存不足,OOM-killer 开始杀掉业务进程。在机器上处理文本,需要使用 grep, awk, sed 这种「流式风格」的软件,或者使用 less 这种使用文件 offset 风格的软件;
  • tcpdump 运行的繁忙的机器上,对性能是有影响的;
  • 有些命令行看起来是读操作,但是也是会对性能造成影响的,比如 RAID 卡控制器;
  • mount 主机的 /etc/hosts 文件进 docker container,如果使用 Vim 修改 Host 的文件,docker 中的文件不会改变(要验证操作结果而不是验证操作本身,不然,就不会发现问题)。因为 Vim 编辑文件默认会修改 inode(:h backupcopy), 应该用 echo 来修改;

一个小技巧之避免整点操作

如果公司比较大的话,那么不可避免的,很多操作都是在同时进行的。可能有人在进行发布操作,有人在修改配置。如果造成了故障,就很难知道故障是谁导致的。

所以我倾向于避开和其他人同时进行操作——选择一些看起来不是整齐的时间。比如 15:13, 16:27 这样的时间。当发生故障的时候,可以快速判断和自己的操作的相关性。

另外一个原因是,公司的业务可能在整点触发一些发劵,通知,促销等,所以这样做也可以避开业务高峰。

不过,还是最好要选择在工作时间进行线上操作。如果是非工作时间,出了问题,就会加大发现问题的时间(可能造成的问题你发现不了,但是其他的业务方会发现),也会加大找到相关负责人的时间。

效率即是安全

这是 Last but not least! 操作的效率至关重要。

我认为运维平台要设计成简洁,没有歧义,流程清晰的,非必要不审批。这可能跟直觉相反,尤其是领导的直觉。

领导(不知为何)觉得审批流程越多越好,出了事故就开始思考在哪一个阶段可以加上一个审批流程,来避免类似的问题发生。但其实,我觉得流程越多,出问题的概率不减反增。

程序员天生就不喜欢繁重的流程,如果流程太重,就会出现其他的问题,比如,人们会想办法绕过不必要的流程;会想办法“搭车发布”(意思就是将多个操作合并成一个,这也是违反原则的,一次应该只做一个操作);对于明显出现异常苗头的时候,因为不想重新走审批而铤而走险。

但是出现这种情况,领导不会觉得流程有问题,领导会觉得你小子不按照流程办事,开除。

最后导致 SRE 的幸福感很低,事情还是要那么多,完成工作不得不铤而走险,还得责任自负。

事实上,真正能保证安全的是架构设计简单,做事的人知道自己在做什么,操作按照如上灰度、验证,出问题回滚,而不是靠流程。SRE 之间 Review 是有价值的,审批是没有价值的,大部分的审批仅仅是请示一下领导而已,领导可能看不懂操作的后果是什么。

所以,流程是有代价的。