题注:这是一篇去年的文章,今早看到 gitlab 运维人员愚蠢地 rm -rf, 心有戚戚焉,故而重发这篇文章,供大家参考。
这两天不是很太平,程序圆媛猿亲们出门前最好拜拜祖师爷 Ada,然后给八阿哥上柱香。
周一早上,我钟爱的一个在线绘图工具 gliffy 挂了。我发现这个问题是因为当天中午我有一个 tech talk,有两张图还没截出来放在 slides 里,结果打开它的时候才发现是这么个状态:
起初我还没在意,结果昨天发现还打不开,知道事情大了。一开始我猜想是黑客攻击,后来看了 hacker news 上的讨论,才知道一个可怜的 sysadmin(好吧,姑且认为他也是个程序员吧,谁叫 dev ops 不分家呢)误操作,删除了 production database,然后整个系统就果断罢工了。gliffy 的技术团队日以继夜地试着恢复他们的数据,可是直到今天,还没有恢复完成。你要是 CEO,你想不想废了那个可恶的家伙?
然后,昨天晚上,朋友圈开始狂转一篇文章:「程序员的bug导致了天大的损失,要枪毙程序员么?」大意是一个坑爹的证券交易员的乌龙导致交易软件里一个「头上有犄角,身后有尾巴」的八阿哥跳出来大唱「我就是不撤单,我就是不撤单」,然后一天之内几百亿美金就被这个八阿哥给吃了。你要是证券公司的老总,你想不想做了那个放出来八阿哥的程序员?
。。。
你看看医生多悲催,别说医疗事故了,没有医活也许本就无医可救的病人,运气好的,被病人家属说些诛心的话;运气不好,被医闹公然折腾,或者在冷僻处被拍板砖。如果程序员每写出一百个 bug 就要被诛心的话,恐怕没有程序员活得过三十。
还好,这个世界是个对程序员友好的世界,程序员不哭。
当 gliffy 事件持续发酵时,hacker news 里满满地都是正能量 -- 大多数人的观点是:作为一个程序员,你如果没有「日了狗了」的高光时刻,你都不好意思给自己挂个资深的抬头。有个哥们这么说:
My very first job - ~25 years ago.
Destroyed the production payroll database for a customer with a bug in a shell .
No problem - they had 3 backup tapes.
First tape - read fails.
Second tape - read fails.
Third tape - worked.... (very nervous at this point).
还有个更绝:
I was testing disaster recovery for the database cluster I was managing. Spun up new instances on AWS, pulled down production data, created various disasters, tested recovery.
Surprisingly it all seemed to work well. These disaster recovery steps weren't heavily tested before. Brilliant! I went to shut down the AWS instances. Kill DB group. Wait. Wait... The DB group? Wasn't it DB-test group...
I'd just killed all the production databases. And the streaming replicas. And... everything... All at the busiest time of day for our site. Panic arose in my chest. Eyes glazed over. It's one thing to test disaster recovery when it doesn't matter, but when it suddenly does matter... I turned to the disaster recovery code I'd just been testing. I was reasonably sure it all worked... Reasonably...
Less than five minutes later, I'd spun up a brand new database cluster. The only loss was a minute or two of user transactions, which for our site wasn't too problematic.
My friends joked later that at least we now knew for sure that disaster recovery worked in production...
Lesson: When testing disaster recovery, ensure you're not actually creating a disaster in production.
还有很多温情的留言,说如果这个可怜的哥们(姐们)被炒鱿鱼了,他们愿意立刻雇佣他(她),理由很简单:唯有经历刻骨铭心,才能换来成熟。
程序君也干过误删数据库的蠢事,作为一个教训,我把它写进了我的『途客圈创业记』里面。
Murphy's law 告诉我们:"Anything that can go wrong, will go wrong." 当你运营着一个系统,服务器会崩溃,数据库会损坏,硬盘会失效,... 不要相信所谓的 MTBF(Mean time between failure),一切一切的小概率事件,只要发生在你身上一次,就是灾难。
作为事后诸葛亮,我们想想,遇到这样的灾难该怎么处理?
首先 我们要有一个详细的灾难处理流程(Disaster Recover Process,以下简称DRP)。把所有可能发生的事情做个攻防演练:如果发生其中的一个或者多个意外情况,你该怎么处理?
比如说:黑客攻击了你的服务器,删除了所有的备份,怎么恢复服务器的运行?
你的 DRP 可能是:多级备份,数据除了本地备份外,还备份到一个权限更高的,远程的,物理上隔离的地方。
如果你使用 AWS,这个翻译过来就是:备份的账号和生产环境的账号分开,生产环境在自己的账号下的 S3(或者其他服务下)备份数据以外,还要在备份账号下的 S3 备份数据。这样,当黑客获取了生产环境的 aws 账号的最高访问权限,即便删除一切,只要备份账号还健在,一切还能救过来。
仅仅有 DRP 是不够的,我们还要确保 DRP 随时可用。这就是第二个要点:像测试你的生产环境一样测试你的灾难处理流程,使其随时可用。这一点是被绝大多数人忽略的。你如果看 gliffy 的更新:
3/22/16 7:24am PST: Unfortunately one of the restore processes failed because it used significantly more disk space than we anticipated. The other restore processes have been configured with more disk space to reduce the chance of this problem happening again.
(作者注:gitlab 也是如此,多级备份没有一个正常工作)
你会发现当无法预料的灾难发生时,他们虽然有详尽的 DRP,但 Murphy's law 不幸应验。很可能,他们从来没有遇到类似的事故,所以也从未试验过他们的 DRP,至少,没有把他们所有三个流程都测试一遍(看起来他们有三级备份,很难得了)。
由于对第一个方案的失败的准备不足,而对第二个方案的时间复杂度估计不足,使得整个服务的恢复过程竟然超过了 48 小时(现在还没完成)。gliffy 的 Eric(Head of Engineer)说 "data transfer is taking longer than expected",可见第二种方案中,他们的备份和生产环境在不同的物理位置,如果是使用 aws,这就好比生产环境在俄勒冈,备份在弗吉尼亚。假设他们使用了 direct connect,理论上可以达到最大 10Gbps 的传输速度,也就是每秒 1.2GB(当然,工程师需要写代码保证并发处理网络读写,使其达到带宽的上限)。在这样的前提下,1PB 的数据需要大概 243 个小时进行传输,而从 gliffy 的日志看,他们花费在数据传输上所花的时间大概 12 - 24 小时,所以,大致猜测 gliffy 要传输的数据在 50 - 100 TB。
注意,在网络上传输的数据很可能是压缩过的数据,所以,实际的数据量可以要比这个大一倍到几倍。
对于 gliffy 这样的工具而言,48 小时还不足以致命,但在线交易,游戏这样的平台,可能就是灾难性的。如果好好地测试 DRP,了解各个方案的不足并且进而想办法加快处理的速度,就不会这么被动。
以上都是纸上谈兵,事后诸葛。
不过你想想看,如果一个程序员经历了这样的磨难,还能挺过来,她的内心该要有多么强大?Randy Paursch 说:
experience is what you get when you didn't get what you wanted.
当然,最最最重要的,就是杜绝类似的事件发生:
首先,automation, automation, automation! 任何 devOps 操作都要自动化,避免手工操作。以上例子中都是手工操作惹的祸,如果将其脚本化,辅以合适的 review,可以把人为错误的发生概率降到最低。(对于 gitlab,sysadmin 再疲惫,也不太可能出错,因为脚本不会出错)
其次,设置合适的权限(least privilege)。在服务器上,代码部署有代码部署的用户,备份有备份的用户,系统维护有系统维护的用户;在 aws 上,用 iam 设置每种角色,每个用户。用户的权限严格定义,只赋予刚刚够用的权限,对于删除操作,权限一定要慎重。比如说 aws 的 RDS,在 console 里不允许删除 db group,同时 db 的用户不允许删库(只有超级用户才可以);对于 unix 的特权用户,不要对 sudo 设置免密码,或者仅仅对特定的命令允许免密码,如 apt-get,等等。合适的权限可以避免意外的事情发生。(对于 gitlab,即便脚本出 bug 了,权限系统也会阻止 rm -rf 的执行)
最后,重要的操作一定要预先触发备份 —— 比如删库,通过脚本,使得这样的操作先进行一次完整备份,然后才真正删除。这样,万一误操作还有挽回的余地。(对于 gitlab,即便权限系统被绕过,在执行包含有 rm -rf 的脚本前,也会先备份,在备份期间,清醒过来的 sysadmin 还可以撤销这个操作,即便没撤销,还有一份最新的磁盘映像可以恢复)
希望大家从 gitlab 和 gliffy 的错误中有所收获!