Linux
华为云 ECS EIP 幽灵入网带宽超限告警- 根因分析报告
本文档使用 MrDoc 发布
-
+
首页
华为云 ECS EIP 幽灵入网带宽超限告警- 根因分析报告
| 项目 | 内容 | | ---------- | ----------------------------------------------------------------------- | | 报告日期 | 2026-11-09 | | 主机名 | web-1 | | 公网 IP(EIP) | 123.60.70.80 | | 告警类型 | EIP 带宽超限(入向 peak 233 Mbit/s) | | 原始告警时间 | 2026-10-29 09:46:04(UTC+8) | | 结论 | **`osmt` 组件每小时无条件执行 `dnf clean all`,导致 dnf 元数据缓存被清空并全量重新下载(约 31 MB/次)** | | 置信度 | 确证(源码 + 日志 + 抓包三重印证) | *** ## 一、问题描述 ### 1.1 现象 华为云控制台对 EIP `123.60.70.80` 反复推送**带宽超限告警**,特征如下: * 方向:**入向(inbound)** 为主 * 峰值:**233 Mbit/s**(后续复现中观测到最高 1303 Mbit/s,但其中含人为压测成分) * 持续时间:**极短**,单次约 **2~7 秒**,随后立即回落到基线 * 周期性:**每小时一次** ### 1.2 排查难点 在主机侧使用常规手段(`iftop`、`nethogs`、`vnstat`、`sar -n DEV` 等)**均无法捕获到该流量**。原因: 1. 突发窗口仅数秒,采样型工具(默认 1s/5s/10s 粒度)容易漏采或严重低估; 2. 流量来自 `dnf` 子进程,进程生命周期短,`nethogs` 按进程归集时窗口不足; 3. 告警时间与实际突发时间存在秒级偏移,事后回溯困难。 因此本次排查采用**环形缓冲抓包(netburst)+ dnf 日志 + 源码审计**三路交叉验证的方法。 ### 1.3 排查目标 1. 定位每小时入向突发的**真实来源进程**; 2. 解释为何该流量具备**每小时周期性**且时刻逐小时漂移; 3. 给出可落地的处置方案,并**验证其有效性**(不接受未经实测的推断)。 *** ## 二、环境信息 ### 2.1 操作系统与内核 ``` NAME="Huawei Cloud EulerOS" VERSION="2.0 (x86_64)" ID="hce" VERSION_ID="2.0" PRETTY_NAME="Huawei Cloud EulerOS 2.0 (x86_64)" ``` ``` Linux web-1 5.10.0-60.18.0.50.r1141_59.hce2.x86_64 #1 SMP Fri Feb 23 04:33:05 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux ``` ### 2.2 硬件 / 虚拟机规格 | 项目 | 值 | | ---- | --------------------------------------------------- | | 架构 | x86\_64 | | vCPU | 2(AMD, QEMU pc-i440fx-4.1, KVM full virtualization) | | 内存 | 3.3 GiB(可用 2.5 GiB) | | Swap | 8.0 GiB | | 系统盘 | `/dev/vda1`,ext4,49 G(已用 18 G,38%) | > **说明**:该机型内存仅 3.3 GiB,而 dnf 全量元数据加载(`base.solv` 2.5 MB + `updates.solv` 5.1 MB + > `updates-filenames.solvx` 21 MB)会带来明显的 CPU 与内存瞬时压力,这也是每秒可产生数十 MB > 入向流量的原因之一。 ### 2.3 软件包版本 | 软件包 | 版本 | | --------- | ----------------------------- | | `dnf` | **4.10.0-3.r17.hce2**(noarch) | | `libdnf` | **0.65.0-2.r5.hce2**(x86\_64) | | `librepo` | **1.14.2-2.r2.hce2**(x86\_64) | | `osmt` | **2.0.0-63.hce2**(x86\_64) | ```bash $ dnf --version 4.10.0 Installed: dnf-0:4.10.0-3.r17.hce2.noarch at Mon 25 Mar 2024 07:36:31 AM GMT Built : HCE OS at Wed 09 Feb 2022 09:00:41 AM GMT ``` > **注意**:`osmt-agent` **不是独立 RPM 包**,`rpm -q osmt-agent` 返回 "not installed"。 > 它由 `osmt-2.0.0-63.hce2` 包提供,实际路径为 `/usr/bin/osmt-agent`(bash 脚本) > 与 `/usr/bin/osmt_server`(Python 服务端)。 ### 2.4 相关服务状态 ``` ● osmt-agent.service - osmt-agent - The agent that manages HCE OS. Loaded: loaded (/usr/lib/systemd/system/osmt-agent.service; enabled; vendor preset: disabled) Active: active (running) since Fri 2026-11-06 16:09:35 CST; 3 days ago Main PID: 3033813 (python) Memory: 119.9M CGroup: /system.slice/osmt-agent.service ├─ 3033813 python /usr/bin/osmt_server ├─ 3033816 /bin/bash /usr/bin/osmt-agent └─ 3557264 sleep 3600 ``` > **关键线索**:CGroup 中直接可见 `sleep 3600` 子进程,即为 `osmt-agent` 主循环末尾的 > `sleep $CYCLE_TIME`,直接证明了小时级周期来自脚本内的 sleep,而非 cron / systemd timer。 ### 2.5 关键配置 `/etc/osmt/osmt.conf` ```ini [auto] # the value of cycle_time means the osmt-agent will check upgrade every cycle_time seconds, # default 86400s(1 day) auto_upgrade = False cycle_time = 3600 minimal_interval = 3600 auto_upgrade_window = "22:00-05:00" auto_upgrade_interval = 1 ``` > **出厂默认值为 86400(1 天),本机被设置为 3600(1 小时)**, > 这使每小时一次的全量下载成为常态。 ### 2.6 dnf 相关配置 `/etc/dnf/dnf.conf` ```ini [main] gpgcheck=1 installonly_limit=3 clean_requirements_on_remove=True best=True skip_if_unavailable=False ``` 生效值: ``` metadata_expire = 172800 # 2 天 metadata_timer_sync = 10800 ``` 已启用仓库:`base`、`updates`,均指向 [`http://repo.huaweicloud.com/hce/2.0/...`](http://repo.huaweicloud.com/hce/2.0/...) *** ## 三、根因分析 ### 3.1 第一层:osmt-agent 主循环 `/usr/bin/osmt-agent` 第 122~146 行: ```bash 122 while true 123 do 124 get_config 125 if [ $CYCLE_TIME -lt $MINIMAL_CYCLE_TIME -o $CYCLE_TIME -gt $[ONE_DAY*3] ];then 126 sleep 60 127 exit 1 128 fi 129 130 avail_repo=`refresh_repo` 131 132 dnf makecache --assumeyes 133 if [ ! -z "$avail_repo" ]; then 134 have_update=$(timeout 10s dnf -q info updates $avail_repo) 135 if [[ -n "$have_update" ]];then 136 osmt update --show > /dev/null 2>&1 # ★ 元凶 137 if [ "x$AUTO_UPGRADE" == "xTrue" ]; then 138 dnf upgrade osmt --assumeyes > /dev/null 2>&1 139 fi 140 auto_upgrade 141 fi 142 fi 143 144 sleep $CYCLE_TIME 145 done ``` 脚本变量定义(第 8~13 行): ```bash 8 CYCLE_TIME=3600 9 MINIMAL_CYCLE_TIME=3600 11 AUTO_UPGRADE_WINDOWS="22:00-05:00" 12 AUTO_UPGRADE_INTERVAL=1 13 ONE_DAY=86400 ``` **重要**: * `AUTO_UPGRADE="False"` —— 第 137 行的升级分支**确实被跳过**,本机无任何 RPM 升级发生,配置生效; * 但第 132 行 `dnf makecache` 与第 136 行 `osmt update --show` **不受 `auto_upgrade` 约束**,无条件执行; * 第 125 行的越界校验表明 `CYCLE_TIME` 合法区间为 **\[3600, 259200]**(1 小时 ~ 3 天)。 ### 3.2 第二层:`dnf makecache` 是无害的 实测(缓存有效状态下): | 命令 | 入向流量 | | ---------------------------------------------------- | ------------------- | | `dnf repolist --enabled` | 322 B | | `dnf makecache --assumeyes` | **9,713 B(9.5 KB)** | | `dnf -q info updates --repoid base --repoid updates` | 256 B | | **完整两轮合计** | **≈ 40 KB** | 原因见 `dnf` 源码 `/usr/lib/python3.9/site-packages/dnf/base.py` 第 318~378 行: ```python def update_cache(self, timer=False): ... for r in self.repos.iter_enabled(): (is_cache, expires_in) = r._metadata_expire_in() if expires_in is None: logger.info('%s: will never be expired and will not be refreshed.', r.id) elif not is_cache or expires_in <= 0: logger.debug('%s: has expired and will be refreshed.', r.id) r._repo.expire() else: logger.debug('%s: will expire after %d seconds.', r.id, expires_in) ... self.fill_sack(load_system_repo=False, load_available_repos=True) logger.info(_('Metadata cache created.')) ``` `r._repo.expire()` **仅设置过期标记,并删除本地 12 个缓存文件**,随后 `fill_sack()` 只重新拉取 `repomd.xml`(3.1 KB + 3.6 KB)。若仓库元数据未变更,librepo 判定“未修改”,**不会**下载 primary/filelists。 实测验证:`repomd.xml` 具备 `ETag` 与 `Last-Modified`,且内容稳定: ``` updates: HTTP/1.1 200 OK Content-Length: 3586 Last-Modified: Fri, 09 Oct 2026 06:48:35 GMT ETag: "6ac88e43-e02" base: Content-Length: 3111 Last-Modified: Thu, 14 Dec 2023 14:16:07 GMT ETag: "657b0e27-c27" ``` **结论:`dnf makecache` 每次仅约 9.5 KB,不是 31 MB 的来源。** ### 3.3 第三层:`osmt update --show` 无条件执行 `dnf clean all`(真正元凶) #### 3.3.1 日志铁证 `/var/log/dnf.log` 中每小时出现如下命令序列(以 16:20 为例): ``` 16:20:23 Base command: repolist 16:20:23 Extra commands: ['repolist', '--enabled'] ← agent 第130行 16:20:23 Base command: makecache 16:20:23 Extra commands: ['makecache', '--assumeyes'] ← agent 第132行 16:20:23 DEBUG base: has expired and will be refreshed. 16:20:24 Base command: info 16:20:24 Extra commands: ['-q', 'info', 'updates', '--repoid', 'base', '--repoid', 'updates'] ← agent 第134行 16:20:24 Base command: clean ★★ 元凶 16:20:24 Extra commands: ['clean', 'all', '-q', '--repo', 'base', '--repo', 'updates'] 16:20:24 DDEBUG Removing file /var/cache/dnf/base.solv 16:20:24 DDEBUG Removing file /var/cache/dnf/updates.solv 16:20:24 DDEBUG Removing file .../base-.../repodata/repomd.xml 16:20:24 DDEBUG Removing file .../updates-.../repodata/...-filelists.xml.gz (共 12 个缓存文件被删除) 16:20:24 Base command: info 16:20:24 Extra commands: ['info', 'updates', '-q', '--repo', 'base', '--repo', 'updates'] 16:20:25 DEBUG repo: downloading from remote: base ← 缓存已空 → 全量下载 6 MB 16:20:26 DEBUG repo: downloading from remote: updates ← 缓存已空 → 全量下载 25 MB 16:20:31 INFO Last metadata expiration check: 0:00:01 ago ``` 统计结果(`/var/log/dnf.log` 全量): ``` Removing file 次数/小时 downloading from remote 次数/小时 12 2026-11-09T06:18 2 2026-11-09T06:18 12 2026-11-09T07:19 2 2026-11-09T07:19 12 2026-11-09T08:19 2 2026-11-09T08:19 12 2026-11-09T09:19 2 2026-11-09T09:19 12 2026-11-09T10:19 2 2026-11-09T10:19 12 2026-11-09T11:19 2 2026-11-09T11:19 12 2026-11-09T12:19 2 2026-11-09T12:19 12 2026-11-09T13:19 2 2026-11-09T13:19 12 2026-11-09T14:20 2 2026-11-09T14:20 12 2026-11-09T15:20 2 2026-11-09T15:20 12 2026-11-09T16:20 2 2026-11-09T16:20 ``` **每小时 12 次删除、2 次全量下载,无一例外。** #### 3.3.2 源码调用链 **① 参数定义** —— `/usr/lib64/python3.9/site-packages/osmt/osmt_cmd.py:15` ```python parser_upgrade.add_argument('-s ', '--show', action='store_true', help='show updateinfo') ``` **② 子命令分发** —— `osmt/osmt.py:1435` ```python "update": run_upgrade, ``` **③ 进入升级主流程** —— `osmt/osmt.py:1235, 1246` ```python def run_upgrade(args, direct_run=False): ... args, upgrade_para = prepare_upgrade(args) ``` **④ 准备阶段无条件下发 `clean all`** —— `osmt/osmt.py:1215-1216` ```python def prepare_upgrade(args): __dnf_clean_all() # ← 无任何条件判断 ... ``` **⑤ 具体实现(与日志字串逐字匹配)** —— `osmt/osmt.py:1201-1213` ```python def __dnf_clean_all(): cmd = ['/usr/bin/dnf', 'clean', 'all', '-q'] + osmt_config.repo_cmd proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) try: out, err = proc.communicate(timeout=15) log.info("dnf clean all. out: {}, err: {}".format(str(out), str(err)), log_only=True) except subprocess.TimeoutExpired as e: proc.kill() out, err = proc.communicate() log.error('dnf clean all failed, out: {}, err: {}'.format(str(out), str(err)), log_only=True) log.error('dnf clean all timeout, check if other dnf/yum process working.') sys.exit(1) ``` **⑥ `repo_cmd` 的构造** —— `osmt/osmt_config.py:56-69` ```python def get_available_repo_info(self): global repo_cmd global repo_base_url base = dnf.Base() try: base.read_all_repos() except RepoError as e: log.error("repository error!\nError Message: {}".format(e)) sys.exit(1) for repo_id in base.repos: if repo_id == 'base': repo_cmd += ['--repo', repo_id] if repo_id == 'updates': repo_cmd += ['--repo', repo_id] ``` 拼接结果:`dnf clean all -q --repo base --repo updates` —— **与 `/var/log/dnf.log` 中记录的命令完全一致**(含 `-q` 位置、`--repo` 顺序)。 #### 3.3.3 另一处 `clean all`(非本次问题,但需知晓) `osmt/osmt_check.py:1130-1137` 的 `config_repo()` 中同样存在: ```python def config_repo(self): """config repo""" self.logger.info('Configure repo') retcode, shellresult = self.exe_command('dnf clean all') if retcode != 0 and 'rpmdb' in shellresult: self.logger.error('You need to rebuild rpmdb!') sys.exit(1) ``` 该方法在 `OsmtCheck.__init__`(第 1163 行)中被调用,因此**任何触发 `OsmtCheck` 实例化的路径都会再次清空缓存**。 这是设计层面的系统性问题,不止一处。 ### 3.4 为什么 `metadata_expire` 完全无效 这是本次分析中最关键的**反直觉点**,必须明确记录: | 配置 | 期望作用 | 实际作用 | | -------------------------- | ------------ | ------ | | `metadata_expire = 172800` | 2 天内不重新下载元数据 | **无效** | **原因**:`metadata_expire` 的判定逻辑(`dnf/repo.py:587-604`)依赖**本地缓存的存在**: ```python def _metadata_expire_in(self): """Get the number of seconds after which the cached metadata will expire.""" if not self.metadata: self._repo.loadCache(False) if self.metadata: if self.metadata_expire == -1: return True, None expiration = self._repo.getExpiresIn() if self._repo.isExpired(): expiration = min(0, expiration) return True, expiration return False, 0 # ← 缓存不存在 → 返回 "无缓存" ``` 当 `dnf clean all` 已把缓存目录删除后,`self.metadata` 为空 → 函数返回 `(False, 0)` → 上层直接判定“必须下载”,**`metadata_expire` 的设置值根本未被参与比较**。 > **因此:`metadata_expire` 只能延缓“缓存过期”,无法阻止“缓存被删除”。 > 在 `osmt` 每小时 `clean all` 的前提下,调大 `metadata_expire` 是一个无效方案。** ### 3.5 完整因果链 ``` osmt-agent 第 145 行 sleep $CYCLE_TIME (3600s) ↓ 每小时唤醒 osmt-agent 第 132 行 dnf makecache --assumeyes → 约 9.5 KB(无害) ↓ osmt-agent 第 134 行 dnf -q info updates → 256 B,返回 "291 updates available" 非空 ↓ 进入 if 分支 osmt-agent 第 136 行 osmt update --show → ★ 无条件下发 clean all ↓ osmt/osmt.py:1246 prepare_upgrade(args) ↓ osmt/osmt.py:1216 __dnf_clean_all() ↓ osmt/osmt.py:1202 dnf clean all -q --repo base --repo updates ↓ /var/cache/dnf/ 下 12 个文件被删除(含 repomd.xml、primary、filelists、solv) ↓ osmt 内部再次查询更新(缓存已空) ↓ 全量下载 base + updates 元数据 ≈ 31 MB ↓ EIP 入向突发 → 华为云带宽超限告警 ``` *** ## 四、验证证据 ### 4.1 抓包实测(网络层) 环形缓冲抓包快照 `20261109-162516-735` 中,精确定位 16:20:20~16:20:35 窗口: ``` 时刻 入向字节 MB CDN占比 Mbit/s 16:20:20 102 0.00 0.0% 0.0 16:20:22 152 0.00 0.0% 0.0 16:20:23 19486 0.02 0.0% 0.2 16:20:24 9955 0.01 91.4% 0.1 16:20:25 6438859 6.14 100.0% 51.5 ← base 下载 16:20:26 26689849 25.45 100.0% 213.5 ← updates 下载 16:20:28 19062 0.02 0.3% 0.2 16:20:29 310 0.00 0.0% 0.0 ──────────────────────────────────────────────────── 窗口合计 31.66 MB 其中 repo CDN 31.60 MB (99.8%) ``` 源 IP 明细: ``` 180.163.142.27 25.45 MB <== repo.huaweicloud.com CDN 61.170.56.80 6.14 MB <== repo.huaweicloud.com CDN 61.170.56.81 0.01 MB <== repo.huaweicloud.com CDN 123.144.136.56 0.05 MB ``` ### 4.2 三重交叉印证 | 证据来源 | 数值 | | ---------------- | ------------------------------------------ | | `dnf.log` 自报下载量 | base 6.1 MB + updates 25 MB \= **31.1 MB** | | 抓包实测入向总量 | **31.66 MB** | | 其中来自 repo CDN | **31.60 MB(99.8%)** | | `dnf.log` 下载起始时刻 | `16:20:25 downloading from remote: base` | | netburst 告警时刻 | `16:20:25`(秒级吻合) | | 抓包该秒峰值 | **213.5 Mbit/s** | > 三者高度一致,可确认该次突发**完全由 dnf 元数据下载产生**。 ### 4.3 对照实验:验证 `clean all` 是关键变量 | 场景 | 操作序列 | 实测入向 | 是否删缓存 | | -------------------------- | --------------------------------- | ------------- | ------------ | | 仅 `makecache` | `repolist` → `makecache` → `info` | **40 KB(两轮)** | 否 | | **含 `osmt update --show`** | 上述 + `clean all` | **31 MB** | **是(12 文件)** | **该对照实验直接证明:31 MB 的唯一来源是 `clean all` 之后的缓存重建。** ### 4.4 周期性来源验证(排除 cron) 统计 `/var/log/dnf.log*` 全部历史(含轮转)中 `downloading from remote` 事件,共 **312 次**: ``` 最早: 2026-10-27 18:38:03 最晚: 2026-11-09 16:26:49 正常周期平均值: 3608.2 秒 相对 3600 秒的漂移: +8.2 秒/小时 ``` **+8.2 秒/小时的单调漂移**说明这是典型的 **`sleep`-after-work 循环特征**(每次执行耗时累积), **而非 `cron` / `systemd timer` 的固定时刻调度**(后者不会漂移)。这与 CGroup 中观察到的 `sleep 3600` 子进程完全吻合。 ### 4.5 告警日反推验证 ``` 告警时刻 : 2026-10-29 09:46:04 从最近事件倒推 270 个周期: 2026-10-29 09:49:47 与告警时刻偏差 : 224 秒(3.7 分钟) ``` `dnf.log.4` 最早记录为 **2026-10-27 18:38**,**早于 10/29 告警日**, 说明该机制在告警发生时已持续运行。270 个周期累积 224 秒偏差在合理范围内,**可确认同一来源**。 *** ## 五、排查过程中的修正记录 本报告如实记录分析过程中被推翻的中间结论,以避免后续误用: | 阶段 | 错误结论 | 修正 | | -- | ---------------------------------- | ----------------------------------------------------------------- | | 初判 | `dnf makecache` 每小时下载 33 MB | ❌ 实测仅 9.5 KB;下载发生在 `clean all` 之后 | | 初判 | 抓包快照显示 68 MB / 98.4% CDN 为 osmt 流量 | ❌ 环形缓冲跨度 246 s,污染了同期 40 个并发 curl 测试流量。按时间窗口切分后,该 68 MB **属于人为压测** | | 方案 | 增大 `metadata_expire` 可降至接近 0 | ❌ **完全无效**。`clean all` 删除缓存后,`metadata_expire` 不参与判定 | | 归因 | 16:25:16 告警由手工 `makecache` 引起 | ❌ 手工 makecache 仅 9.5 KB;该告警源于人为并发 curl | > **方法学教训**:环形缓冲抓包的时间跨度可能远大于预期(本次 246~307 s), > 分析时必须**按时间窗口切分**,否则极易把人为测试流量误判为系统行为。 *** ## 六、影响评估 | 维度 | 评估 | | ---- | ---------------------------------------------------------- | | 流量成本 | 每小时 31 MB,**约 744 MB/天,22.3 GB/月**(纯入向,云厂商入向通常不计费,但占用带宽配额) | | 带宽占用 | 单次突发 213 Mbit/s,若 EIP 带宽规格较小(如 100 Mbit/s)必然触发限速与告警 | | 性能影响 | 每小时的 `solv` 重建消耗 CPU(2 vCPU 机型明显)与内存(3.3 GiB 机型紧张) | | 业务风险 | 突发窗口内 EIP 被打满,可能影响同时段业务入向请求 | | 告警噪声 | 24 次/天,掩盖真实攻击流量(如 DDoS、异常扫描) | > **注意**:该机制同样适用于识别**真实攻击**——若出现非整点、非 osmt 周期的突发, > 则应视为异常并单独排查。 *** ## 七、处置方案 ### 7.1 方案比较 | 方案 | 具体操作 | 预期效果 | 风险 | 评价 | | ----------------------- | ------------------------------------ | ---------------------------------- | ----------------------- | -------- | | **A. 调大 `cycle_time`** | `osmt.conf` 中 `cycle_time = 86400` | 突发降至 **1 次/天**,月流量降至 **\~0.93 GB** | 低。仅改配置,不动系统组件 | ⭐ **推荐** | | B. 停用 osmt-agent | `systemctl disable --now osmt-agent` | 完全归零 | 高。失去 HCE 补丁扫描;控制台可能重新拉起 | 不推荐 | | C. 仅调 `metadata_expire` | `dnf.conf` 加 `metadata_expire=7d` | **无效** | — | ❌ 已实测证伪 | | D. 拦截 `clean all` | 用 wrapper 包装 `/usr/bin/dnf` | 归零 | 高。可能破坏 osmt 升级/回滚可靠性 | 不推荐 | | E. 调整告警阈值 | 控制台提高带宽告警阈值 | 不再误报 | 中。不解决根因,但掩盖真实攻击 | 辅助手段 | ### 7.2 推荐方案 A 的可行性验证 `/usr/bin/osmt-agent` 第 125 行: ```bash if [ $CYCLE_TIME -lt $MINIMAL_CYCLE_TIME -o $CYCLE_TIME -gt $[ONE_DAY*3] ];then ``` 即 `CYCLE_TIME` 合法区间为 **\[3600, 259200]**(1 小时 ~ 3 天)。 `osmt.conf` 中 `minimal_interval = 3600` 与之对应。 **因此 `cycle_time = 86400`(1 天)在合法区间内,且正是厂商默认值**,可安全设置。 ### 7.3 实施步骤(方案 A) ```bash # 1. 备份 cp -a /etc/osmt/osmt.conf /etc/osmt/osmt.conf.bak.$(date +%Y%m%d%H%M%S) # 2. 修改(建议同时调整 minimal_interval 以保持一致) sed -i 's/^cycle_time = 3600$/cycle_time = 86400/' /etc/osmt/osmt.conf sed -i 's/^minimal_interval = 3600$/minimal_interval = 86400/' /etc/osmt/osmt.conf # 3. 校验 grep -E '^(cycle_time|minimal_interval|auto_upgrade)' /etc/osmt/osmt.conf # 4. 重启服务使配置生效 systemctl restart osmt-agent systemctl status osmt-agent --no-pager # 5. 确认新周期(应可见 sleep 86400) ps -ef | grep -E 'osmt-agent|sleep' | grep -v grep ``` > **备选**:若希望保留每小时补丁检查能力,可折中设置 `cycle_time = 21600`(6 小时), > 流量降至 1/6(约 124 MB/天),仍在合法区间内。 ### 7.4 验证方法 ```bash # 观测下一次 osmt 周期前后的入向增量 NIC=eth0 awk -v n="$NIC:" '$1==n{print "rx_bytes="$2}' /proc/net/dev # 或持续采样 sar -n DEV 1 60 | grep -E 'IFACE|$NIC' ``` 同时观察服务 CGroup 中的 `sleep` 参数是否已变为目标值。 *** ## 八、结论 1. **根因**:`osmt` 组件(`osmt-2.0.0-63.hce2`)的 `osmt-agent` 脚本第 136 行 `osmt update --show`,会经由 `osmt/osmt.py` 的 `run_upgrade → prepare_upgrade → __dnf_clean_all()` 调用链,**无条件执行 `dnf clean all -q --repo base --repo updates`**, 删除全部 dnf 元数据缓存,随后触发 **约 31 MB** 的全量重新下载。 2. **周期性**:来自 `/usr/bin/osmt-agent` 第 145 行的 `sleep $CYCLE_TIME`, 当前 `cycle_time = 3600`(出厂默认为 86400,本机被改为 1 小时)。 观测到 **+8.2 秒/小时** 的单调漂移,是 `sleep`-after-work 循环的典型特征, 可排除 cron / systemd timer 调度。 3. **告警映射**:31 MB 在 2 秒内完成(16:20:25~16:20:26), 瞬时速率达 **213 Mbit/s**,与 EIP 带宽超限告警完全对应。 4. **`metadata_expire` 无效**:`dnf clean all` 删除缓存后,`_metadata_expire_in()` 因缓存不存在直接返回“无缓存”,`metadata_expire` 设置值不参与判定。 **调大该值无法缓解问题。** 5. **推荐处置**:将 `/etc/osmt/osmt.conf` 的 `cycle_time` 由 `3600` 调整为 `86400` (厂商默认值,在合法区间 \[3600, 259200] 内),可把突发频率从 **24 次/天降至 1 次/天**, 月流量从约 **22.3 GB 降至 0.93 GB**,且不影响 osmt 的补丁扫描与升级能力。 *** ## 附录 A:关键源码位置索引 | 文件 | 行号 | 内容 | | ------------------------------- | ---------- | --------------------------------------------------- | | `/usr/bin/osmt-agent` | 8-9 | `CYCLE_TIME=3600` / `MINIMAL_CYCLE_TIME=3600` | | `/usr/bin/osmt-agent` | 125 | `CYCLE_TIME` 合法区间校验 \[3600, 259200] | | `/usr/bin/osmt-agent` | 132 | `dnf makecache --assumeyes` | | `/usr/bin/osmt-agent` | 134 | `dnf -q info updates`(have\_update 检测) | | **`/usr/bin/osmt-agent`** | **136** | **`osmt update --show`(触发点)** | | `/usr/bin/osmt-agent` | 137 | `if [ "x$AUTO_UPGRADE" == "xTrue" ]`(仅拦截升级) | | `/usr/bin/osmt-agent` | 145 | `sleep $CYCLE_TIME` | | `osmt/osmt_cmd.py` | 15 | `parser_upgrade.add_argument('-s ', '--show', ...)` | | `osmt/osmt.py` | 1201-1213 | `__dnf_clean_all()` 实现 | | `osmt/osmt.py` | 1215-1216 | `prepare_upgrade()` 中无条件调用 `__dnf_clean_all()` | | `osmt/osmt.py` | 1235, 1246 | `run_upgrade()` → `prepare_upgrade()` | | `osmt/osmt.py` | 1435 | `"update": run_upgrade` | | `osmt/osmt_config.py` | 56-69 | `repo_cmd` 构造(`--repo base --repo updates`) | | `osmt/osmt_check.py` | 1130-1137 | `config_repo()` 中另一处 `dnf clean all` | | `osmt/osmt_check.py` | 1163 | `OsmtCheck.__init__` 调用 `config_repo()` | | `dnf/base.py` | 318-378 | `update_cache()` 元数据同步主逻辑 | | `dnf/repo.py` | 587-604 | `_metadata_expire_in()`(缓存不存在即返回 `(False, 0)`) | | `dnf/cli/commands/clean.py` | 111 | `Cache was expired` 日志(`clean expire-cache`) | | `dnf/cli/commands/makecache.py` | 末尾 | `makecache` → `base.update_cache(timer)` | ## 附录 B:关键日志片段 ``` # 每小时固定出现的清理动作(16:20 为例) 2026-11-09T16:20:24+0800 DDEBUG Base command: clean 2026-11-09T16:20:24+0800 DDEBUG Extra commands: ['clean', 'all', '-q', '--repo', 'base', '--repo', 'updates'] 2026-11-09T16:20:24+0800 DDEBUG Removing file /var/cache/dnf/base.solv 2026-11-09T16:20:24+0800 DDEBUG Removing file /var/cache/dnf/updates.solv 2026-11-09T16:20:24+0800 DDEBUG Removing file /var/cache/dnf/base-ee66c3eaaf33edde/repodata/repomd.xml 2026-11-09T16:20:24+0800 DDEBUG Removing file /var/cache/dnf/updates-3f4238b419c0d66a/repodata/repomd.xml ... 2026-11-09T16:20:25+0800 DEBUG repo: downloading from remote: base 2026-11-09T16:20:26+0800 DEBUG repo: downloading from remote: updates 2026-11-09T16:20:31+0800 INFO Last metadata expiration check: 0:00:01 ago on Mon 09 Nov 2026 04:20:26 PM CST. ``` ``` # 312 次下载事件的周期性统计 正常周期平均值: 3608.2 秒 (60 分 8 秒) 相对 3600 秒的漂移: +8.2 秒/小时 最近 12 次事件: 2026-11-09 07:19:05 2026-11-09 08:19:14 2026-11-09 09:19:22 2026-11-09 10:19:31 2026-11-09 11:19:40 2026-11-09 12:19:49 2026-11-09 13:19:58 2026-11-09 14:20:07 2026-11-09 15:20:16 2026-11-09 16:20:25 ``` ## 附录 C:本次分析使用的辅助脚本 | 脚本 | 用途 | | ----------------------- | ------------------------------------------- | | `/root/timeslice.py` | 按时间窗口切分 pcap,隔离人为测试流量与 osmt 原生流量 | | `/root/osmt_window.py` | 精确定位 16:20 窗口的逐秒入向明细与源 IP 归属 | | `/root/backcalc.py` | 基于 312 次事件统计周期漂移,反推历史告警时刻 | | `/root/repomd_probe.py` | 探测仓库 `repomd.xml` 的 revision/timestamp 与稳定性 | | `/root/step_measure.sh` | 分步测量 dnf 各子命令的真实入向流量 | | `/root/osmt_sim.sh` | 模拟 osmt 完整循环,对照验证 `clean all` 的影响 | | `/var/lib/netburst/` | 环形缓冲抓包证据目录(含 `burst.pcap`) | *** *报告生成时间:2026-11-09* *分析主机:web-1 (123.60.70.80) / Huawei Cloud EulerOS 2.0*
admin
2026年11月9日 17:32
转发文档
收藏文档
上一篇
下一篇
手机扫码
复制链接
手机扫一扫转发分享
复制链接
Markdown文件
分享
链接
类型
密码
更新密码