• 技术流 | 运维平台大型“生产事故”录播和实战重现


    【本文作者:擎创科技 资深专家禹鼎侯】

    本文写于2021年,最近重读觉得特别有现场感。这也是运维人面对生产环境时遇到的各种惊心动魄的事件之一。惊险,但又顺利解决。是最好的结果。


    事情是酱紫的。
    那天上午,轻轻松松完成了一个新功能的开发,原本计划着混到中午吃个饭,美美的午个觉,下午跑跑测试,摸摸鱼,划划水,愉快的一天就过去了。
    正当我在为我的完美计划自豪不已的时候,微信群闪过一条消息,有人艾特我。
    特么的!!!
    这个时候!居然有人艾特我!!!
    当时我就不乐意了。指名道姓的艾特我,指定没好事。

    图片

    划开手机一看,果然,生产出事故了。而且看起来事情还比较紧急严重。

    图片


    我给大家简单描述一下事情的经过。
    我们(咳咳,请把"们"字去掉,说的就是你,别想甩锅)开发了一个可以采集宿主机硬件指标的这么一个功能。其中有一项是获取IPV6的地址,然后这个IPV6地址获取的时候出问题了,本来不应该获取fe80开头的地址(fe80开头的地址类似于IPV4的192.168,是局域网的地址,这个是应该pass掉的),却获取成了一串乱七八糟的数字。

    图片

    当时的情况是这样的。客户提交了升级单,他们也怕出问题,就先升级了两千台设备,并打算当天晚上升级剩余的两万多台。
    然后到了快中午的时候,发现这两千台中,有那么一百台的样子,获取的地址不对。
    如果嫌我说的啰嗦的话,请我师爷来翻译翻译。

    图片


    简单来说,就是客户中午的时候发现了程序有问题,但是由于晚上要升级所有生产环境,所以下午必须给他们搞定。不能耽误他们晚上的升级。此诚危急存亡之秋也。
    什么情况啊?
    劳资特么一脸懵逼啊!
    没办法啊,谁让咱是吃这碗饭的呢?于是乎,简单收拾一下,连午觉都不顾上了,背上我的蓝色小书包,坐上公交车,直接奔赴客户现场。


    当时我心里是一点底都没有,这问题咱也没碰到过啊,于是脑中快速过了一遍当时的代码逻辑,看看能不能找到可以甩锅的地方。嗯,测试!一定是测试没测好!带着问题就上线了!
    于是乎:

    图片


    人家很明确的告诉我,测试没有发现问题。
    这就尴尬了。公司环境没有发现问题,pit环境也没有发现问题(备注:该pit环境是客户提供的一套无限接近生产环境的机器)。也就是说,这个问题似乎是生产环境独有的。
    这特么就扯了。。。
    这都什么鬼啊?
    没办法了,去现场看吧。

    图片


    经过了冗长而复杂的客户园区入场手续后,终于来到了生产机房。客户找出两台有问题的机器,说,你调试吧!然后也不管我,忙他自己的工作去了。
    生产的机器,如果玩过的大家都应该了解,客户不让做的事情千万别做,千万别想着删库跑路,然后坐船去新加坡,越南,柬埔寨。
    特别是我当时拿到的是root权限。客户更是慎之又慎,就连重启程序都得经过他们同意。危险的命令想都不要想。
    那还调试个鸡儿啊。

    图片


    但这个时候我可不能怂啊,老夫一世英名能否保住,就看在此一举了。
    首先我给客户强调,这个问题百年难得一遇,肯定不是那么好解决的,今天下午就算能找到根因,回去还要修改代码,测试,晚上升级估计有点来不及。
    打了这个预防针,客户也知道问题棘手,就有心里准备了。说,你先查原因吧,实在难解决的话今晚就暂缓升级,周五之前能搞定就行。
    这样我就放心大胆的搞了。

    图片


    先说一下我们这个程序获取IPV6的逻辑。
    我们这个程序是用C语言开发的,为了支持跨平台,我们引入了一个叫sigar的第三方库。这个sigar库是专门用来获取机器硬件指标的,而且对不同的平台操作系统都有实现。
    Linux下,获取IPV6的逻辑是读取/proc/net/if_net6的配置文件。这个文件的第一项就是IPV6的地址。
    简单来说,获取ipv6的地址,就是读取文件中指定字符串的内容的实现。听起来剁么简单的事情。就这破逻辑,还能出啥问题?
    sigar库里这段逻辑在 src/os/linux/linux_sigar.c 中的sigar_net_interface_ipv6_config_get函数中。当然我们做了一些调整,最终的样子是这样的:

    1. while (fscanf(fp, "%32s %02x %02x %02x %02x %16s\n",
    2. addr, &idx, &prefix, &scope, &flags, ifname) != EOF)
    3. {
    4. if (strEQ(name, ifname) && addr != strstr(addr, "fe80")) {
    5. status = SIGAR_OK;
    6. break;
    7. }
    8. }

    说一下/proc/net/if_net6这个文件,这个文件里存放的是接口和单播地址。其内部格式如下:
    |addr(32位)|

    addr(32位)if_index(一般2位十六进制数字)prefix(一般2位十六进制数字)scope(一般2位十六进制数字)flags(一般2位十六进制数字)ifname(16位)
    ipv6地址接口ID前缀长度地址适用范围标志位网卡名称

    这样一看,上面的代码解析,似乎没有什么问题嘛。
    但,等等,这都是什么鬼?

    图片


    我嗅到了一丝犯罪的气息,虽然说不上哪里不对,但我感觉这里会有问题。第二位,也就是if_index,为啥出现了这么多三位的?
    一个三位的字符串,却用%02x,这是会出问题的呀。
    可是,为什么会出现3位的呢?

    图片


    咱也不知道啊。没办法,祭出百度吧!
    然鹅,一上百度,是这个样子的:

    图片


    众所周知,大天朝的技术文章,百分之八十被号称吃屎都(D)难(N)的某网站给劫持了,只要一搜索,千篇一律的出来的都是该网站的链接,而且内容重复率之高,含金量之低,令人咋舌。
    既然度娘不助我,我就只能寻求谷嫂的帮助了。
    可是!

    图片


    完犊子了,这可咋办?
    我忽然想到我一个高中同学发小是做海外竞价的,自己创业当了老板,咱要不去试试白嫖一波?

    图片


    嗯。。。弄起来还是挺麻烦的,但好歹是弄到了。
    谷歌一游,大多数也只是告诉你if_net6文件是啥含义的,没有告诉我这个if_index到底应该有几位。
    然后我在if.h头文件里发现了这个if_index的定义:

    1. struct if_nameindex
    2. {
    3. unsigned int if_index; /* 1, 2, ... */
    4. char *if_name; /* null terminated name: "eth0", ... */
    5. };

    int类型,理论上可以有8位啊,汗~
    得,不管了。为了验证我这一猜想是对的,确实是if_index有三位导致的,我让测试的同事在pit环境找if_net6文件中有三位的测试,很快:

    图片

    然后就是愉快的修改代码的过程了。咱也不管有的没的,搞什么位数啊,代码一旦hard code了,下次有变动还是得出问题,不如一把搞定:

    1. while (fscanf(fp, "%s %s %s %s %s %s\n",
    2. addr, idx, prefix, scope, flags, ifname) != EOF)
    3. {
    4. if (strEQ(name, ifname) && addr != strstr(addr, "fe80")) {
    5. status = SIGAR_OK;
    6. break;
    7. }
    8. }

    事实上,fscanf函数处理这种带固定宽度的解析时,如果字符串有三位,但只解析了两位,并不会把第三位丢弃,而是把第三位挤到了下一个域。
    举个例子。

    图片


    上图中第二条记录,if_index321,按照上述代码逻辑处理时,会将32当做if_index,剩下的1当做prefix40当做scope20flags80ifname, veth1ffa1a8本来是网卡名,它会将其认为是第二条的ipv6地址,导致后面的全部错位。
    嗯。。。接下来就是把代码改好,重新发布补丁,让客户更新测试。
    虽然过程是曲折的,但结果是好的。

    图片


    至此,故事划一段落,可惜了了我的午觉啊。嘤嘤嘤~

    ———— THE END ————

  • 相关阅读:
    大龄码农的转型:总结免费升讯威在线客服系统的推广经验与成绩
    OCP Java17 SE Developers 复习题15(完)
    推出一系列GaN功率放大器: QPA2211、QPA2211D、QPA2212、QPA2212D、QPA2212T,支持卫星通信和5G基础设施。
    如何将代码以高亮的方式:插入到 word 文件中
    算法设计 (分治法应用实验报告)基于分治法的合并排序、快速排序、最近对问题
    A - ASCII码排序
    Go&Java算法之同构字符串
    go 并发编程之-工作池
    软件设计模式系列之六——单例模式
    合作对策模型的简单实现
  • 原文地址:https://blog.csdn.net/dazuibar/article/details/140010035