V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Distributions
Ubuntu
Fedora
CentOS
中文资源站
网易开源镜像站
weiweiwitch
V2EX  ›  Linux

绕过了一个 VPS 上恶心问题,但不确定原因。麻烦大佬们提供下排查思路。

  •  
  •   weiweiwitch · 93 天前 · 1196 次点击
    这是一个创建于 93 天前的主题,其中的信息可能已经有所发展或是发生改变。

    环境

    • 机器:阿里云上的 VPS 。
    • 系统:Ubuntu 22.04

    问题现象

    apt更新时会触发/usr/lib/cloud-init/ds-identify的运行。然后这个脚本运行的非常的慢。要 1 分多钟才会运行完。期间 CPU 是满的。而且新的 ssh 连接无法建立。

    排查脚本慢的原因

    这个 ds-identify 是个 shell 脚本。ds-identify开头是#!/bin/sh,用的是系统默认的dash

    我自己按照 top 中显示的命令行信息,增加-x参数,运行/bin/sh -x /usr/lib/cloud-init/ds-identify,也非常慢,但能看到,卡在check_config函数的while read line之后,line=之前。

    for fname in "$@"; do
        [ -f "$fname" ] || continue
        while read line; do
            line=${line%%#*}
            case "$line" in
                $key:\ *|$key:)
                    ret=${line#*:};
                    ret=${ret# };
                    found=$((found+1))
                    found_fn="$fname";;
            esac
        done <"$fname"
    done
    

    这“应该”是在读文件内容。

    根据打印信息,读取的是/etc/cloud/cloud.cfg.d/aliyun_cloud.cfg文件,并且是- echo 开头的很长的一行文本上。

    多次执行,发现只要是在文件中的行内容过长的地方,就会 read 的很慢。

    然后我用bash执行了一遍。发现同样的ds-identify,却执行的非常快。

    初看像是dash的某种性能问题。

    排查 dash 读长文本慢的原因,却发现不是

    我新建一个文本文件test.cfg,将/etc/cloud/cloud.cfg.d/aliyun_cloud.cfg文件的内容复制进去。

    新建一个独立的test.sh脚本文件,将上面那段函数放进去。

    执行这个test.sh脚本来读取test.cfg文件。

    发现用dashbash都执行的非常快。所以不是dash的某种性能问题!

    排查aliyun_cloud.cfg的问题

    通过ls,我观察到/etc/cloud/cloud.cfg.d/aliyun_cloud.cfg文件其实是个链接。

    aliyun_cloud.cfg -> /sys/firmware/qemu_fw_cfg/by_name/etc/cloud-init/vendor-data/raw
    

    我直接cat这个链接文件的内容,显示的很快。不像是某种 IO 问题。

    我修改上面的test.sh脚本,直接读取aliyun_cloud.cfg这个链接文件。

    然后,用bash这个 shell 读取的非常快,但用dash这个 shell 读取的却非常慢!

    暂时绕过

    因为/usr/lib/cloud-init/ds-identify这个脚本是系统在某个时刻调用的。我现在的绕过方式只能是将/bin/sh的默认指向改为bash

    我不确定为什么dash读取阿里云的这个文件很慢,而bash却很快。

    希望有大佬能提供点调试思路。

    5 条回复    2024-01-30 10:58:28 +08:00
    SenLief
        1
    SenLief  
       93 天前
    这机器买过来第一件事就是 dd 了,它自带的还有一个云控,占内存的玩意。
    nevadax
        2
    nevadax  
       93 天前
    把 cloud-init 干掉
    churchmice
        3
    churchmice  
       93 天前 via Android
    不用啊,你这个脚本头上改成
    #!/bin/bash 就好
    weiweiwitch
        4
    weiweiwitch  
    OP
       92 天前
    @churchmice 这个是 cloud-init 里面的脚本。位置是/usr/lib/cloud-init/下。也就是说,直接改,哪天更新时会被覆盖回去。
    不过这些都只是绕过。根本原因还没找到。
    xkwy001
        5
    xkwy001  
       88 天前   ❤️ 1
    使用 strace 命令可以明显看出二者区别。

    根本原因在于 bash 和 dash 对 read 这个 shell 内建命令的具体实现不一样。

    dash 的实现是 read(0, &c, 1)直接使用 read 系统调用,一个字符一个字符地读取;
    https://git.kernel.org/pub/scm/utils/dash/dash.git/tree/src/miscbltin.c#n151

    而 bash 是使用 zreadc(fd, &c)实现的,其内部会有一个 4K 缓冲,每次执行 zreadc 会一次性从文件中读入 4K 内容。
    http://git.savannah.gnu.org/cgit/bash.git/tree/builtins/read.def#n683
    http://git.savannah.gnu.org/cgit/bash.git/tree/lib/sh/zread.c#n132

    对于常规的磁盘文件,使用 read 系统调用也还好,因为内核层面其实也有一层 buffer ,顶多会频繁进/出内核,并不涉及随机读写的磁盘 io 瓶颈。

    猜测/sys/firmware/qemu_fw_cfg/by_name/etc/cloud-init/vendor-data/raw 这个文件不是普通的常规硬盘文件,每一次执行 read 系统调用都会触发异常->qemu 处理->balabala 一顿操作才返回,绕了好大一圈,且相对常规文件也没有内核缓冲,等于是每读一个字符都来一遍
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   1032 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 18:44 · PVG 02:44 · LAX 11:44 · JFK 14:44
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.