为云主机实现网络达量停机

2024-11-11 ⏳3.3分钟(1.3千字) 🕸️

无论是国内的阿里云、腾讯云,还是海外的 Azure/AWS/GCE/Oracle,大多数公有云的云主机都要按网络流量计费。如果被 DDoS 攻击,或者被坏人盯上,很容易出现巨额账单🧾而且各大公有云厂商都不提供达量停机功能,好像是商量好了似的。今天为大家分享一种基于 vnstat 的流量监控和自动停机方案,希望可以保住大家的房产证🏡

其实网上已经有很多 vnstat 相关的内容了。但这些内容要么是不完整,仅仅罗列了 vnstat 的命令参数;要么就是给出了一个神秘且巨长的脚本,不知道里面做了什么。做为一个负责任的系统管理员,我们不但要知其然,更要知其所以然。本文希望为大家提供一个简洁明了的解决方案。

首先需要安装 vnstat。我用的是 ubuntu 系统:

sudo apt-get install vnstat jq

如果用的不是 Debian 系发行版,请自行搜索安装指令。

💡Tip

这里我还额外安装了 jq 工具,后面会用到。如果你不熟悉 jq,可以阅读我的另一篇文章

vnstat 的本质是定时采集网卡流量状态,并根据规则将流量累加起来保存到一个 sqlite 文件。所以系统重启也不会影响流量统计。

vnstat 的配置文件在/etc/vnstat.conf,需要注意的配置项如下:

SaveInterval 1

这个配置项表示流量数据落盘间隔,单位是分钟,最小也是一分钟存一次盘。换句话说流量统计的最小间隔是一分钟。如果在一分钟内产生巨额流量,vnstat 也没有办法。这种情况下可以考虑给网卡限流。

通常 vnstat 会自动选择网卡。如果你的主机网络比较复杂,也可以通过Interface配置项指定网卡。一般此项留空就可以了。

配置好之后启动 vnstat

systemctl start vnstat

这样 vnstat 服务就开始采集流量数据了。

我们可以查看当月消耗的总流量,注意这里的-m参数:

$ vnstat -m

 enp0s6  /  monthly

        month        rx      |     tx      |    total    |   avg. rate
     ------------------------+-------------+-------------+---------------
       2024-11     97.15 MiB |  260.46 MiB |  357.61 MiB |  535.40 kbit/s
     ------------------------+-------------+-------------+---------------
     estimated     28.71 GiB |   76.97 GiB |  105.68 GiB |

vnstat 也支持查看每天和每五分钟消耗的流量,对应的参数分别是-d-5。输出结果我就不贴出来了,请大家自行试验🔬

功能验证没问题后需要设置开机自动启动。一般可以用systemctl enable开启。但 vnstat 服务貌是历史遗留服务,还没迁移到 systemd 上,需要使用单独的命令来设置。systemctl 有提示🔔

$ systemctl enable vnstat
Synchronizing state of vnstat.service with SysV service script with /usr/lib/systemd/systemd-sysv-install.
Executing: /usr/lib/systemd/systemd-sysv-install enable vnstat

运行如下命令设置开机自启:

/usr/lib/systemd/systemd-sysv-install enable vnstat

现在我们来写自动关机脚本。

前面命令输出的内容是纯文本格式,不方便用脚本处理。vnstat 支持输出 json 和 xml 两种机读格式。因为我比较熟悉 json,所以就配置 jq 来处理了。

$ vnstat -m --json|jq -r ".interfaces.[0].traffic.month[0]"
{
  "id": 1,
  "date": {
    "year": 2024,
    "month": 11
  },
  "timestamp": 1730419200,
  "rx": 102250665,
  "tx": 274167696
}

上述代码中使用了--json参数,所以 vnstat 会输出 json 格式结果。然后我们通过 jq 达式 .interfaces.[0].traffic.month[0] 来提取数据。

vnstat 会分网卡汇总数据,所以最外层的 map 中用interfaces键,它的值是一个列表。列表中第一个对应我的公网网卡,所以用.[0]提取。结果也是 map,其中的traffic键对应每个汇总维度流量信息,我们继续用.month提取按月统计的流量数据。最终,该对象的rxtx表示当前的收发总流量。

接下来我们可以使用 shell 脚本提取rxtx的值,把它们相加再跟流量阈值比较。如果超量就执行关机动作。这自 shell 脚本写起来比较繁琐,而且 shell 脚本的语法用过的人都说傻。我们可以直接使用 jq 提供的数学运算和分支比较功能。

这里我用的 jq 表达式为:

"if .rx + .tx > $1 then \"err\" else \"ok\" end"

上面的.rx.tx分别是提取收发流量值,然后相加,如果大于变量$1指定的数值就输出"err",否则输出"ok"。我们在 bash 中可以通过检测 err 或者 ok 来确定是否停机。

最终完整 shell 代码如下:

#!/usr/bin/env bash

vnstat -m --json|jq -r ".interfaces.[0].traffic.month[0]|if .rx+.tx > $1 then \"err\" else \"ok\" end"|grep "ok"

if [ $? -ne 0 ]; then
        shutdown
fi

这里我用grep "ok"检查输出结果。如果 jq 输出 ok,那么对应的$?变量值为0。最后使用shutdown关机📵

将代码保存到磁盘并设置可执行权限。我用的路径是/root/.bin,大家可以按需放置。

该脚本运行时需要提供总流量参数,单位是字节。

一切准备就绪后配置 crontab 每分钟检测一次:

* * * * * /root/.bin/traffic.sh 1099511627776

这样就可以高枕无忧了💤