如何用一行命令让 mirrors 的负载破百

如何仅用不到 1 MB/s 的带宽把 mirrors.ustc.edu.cn 的负载提高到 100 以上?下面一行命令就能做到(拆开成几行写以便看清楚):

rsync rsync://mirrors.ustc.edu.cn | awk '{print $1}' | while read m; do 
   [ -z "$m" ] && continue
   rsync -r --list-only rsync://mirrors.ustc.edu.cn/$m >$m &
done

这行命令的作用是获取 mirrors 上每个源的文件列表,所有源并行操作。进行真正的 rsync 同步之前,都要执行获取文件列表的操作,因此这个操作是完全合法的,只是我们发起的请求并发数有点高。

执行此命令后,在 mirrors 服务器上看到的负载如下图所示,而发起“攻击”的机器仅用了不到 3 MB/s 的带宽:

┌nmon─13g──────[H for help]───Hostname=mirrors──────Refresh= 2secs ───00:08.45─┐
│ Kernel Stats ────────────────────────────────────────────────────────────────│
│ RunQueue              1   Load Average    CPU use since boot time            │
│ ContextSwitch   11384.5    1 mins 109.04    Uptime Days=106 Hours= 1 Mins=132│
│ Forks               0.0    5 mins 73.83    Idle   Days=1377 Hours=13 Mins=22 │
│ Interrupts      26381.3   15 mins 37.30    Average CPU use=-1198.96%%        │
│ Disk I/O ──/proc/diskstats────mostly in KB/s─────Warning:contains duplicates─│
│DiskName Busy  Read WriteMB|0          |25         |50          |75       100|│
│sda        5%    0.3    0.2|RRW                                    >         |│
│sda1       0%    0.0    0.0|>                                                |│
│sda2       5%    0.3    0.2|RRW                                    >         |│
│sdb        9%   11.5    0.0|RRRRR    >                                       |│
│sdc        0%    0.0    0.0|>                                                |│
│sdc1       0%    0.0    0.0|>                                                |│
│sdd      100%    3.6    0.0|RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR>│
│sde      100%    8.0    0.2|RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR>│
│sdf       94%    6.0    0.0|RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR  >│
│sdg      100%    6.9    0.0|RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR>│
│dm-0       1%    0.0    0.1|R                                      >         |│
│dm-1     100%    7.0    0.0|RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR>│
│dm-2      94%    5.9    0.0|RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR  >│
│dm-3     100%    7.9    0.0|RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR>│
│dm-4     100%    3.4    0.0|RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR>│
│dm-5       3%    0.3    0.0|RR  >                                            |│
│dm-6       0%    0.0    0.1|        >                                        |│
│sdh      100%    5.2    0.0|RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR>│
│sdh1     100%    5.2    0.0|RRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRRR>│
│Totals Read-MB/s=71.7     Writes-MB/s=0.7      Transfers/sec=4584.9           │
│──────────────────────────────────────────────────────────────────────────────│

Mirrors 流量图(下图)中,00:07 左右的低谷就是上述“攻击”所致。

collection.modified (1)

大约 15 分钟后,“攻击”源所用平均带宽下降到 250 KB/s,而 mirrors 服务器的 load average (1 min) 仍然高达 75,这是由于一些“文件数较少”和“被缓存”的文件列表已经发送完毕,剩下的都是“难啃的骨头”需要大量磁盘随机访问了。如果精心选择要同步的源甚至子目录,应该可以用相对少的带宽把 mirrors 的负载升高到很高,进而影响正常用户的服务质量。

Mirrors 遭遇拒绝服务攻击

3月20日 20:00 左右,mirrors.ustc.edu.cn 开始遭遇拒绝服务攻击,攻击强度不断加大,从3月21日凌晨 05:20 开始,造成 30 分钟左右的服务无法访问。攻击来源是一个电信IP,开了 181 个连接访问 rsync(202.141.160.110:873),峰值流量达到 110MB/s,不仅几乎占满了千兆网络,还给磁盘带来很高的负载,SSH 几乎无法登录。

解决方法是添加如下限速规则(来自 http://tldp.org/HOWTO/Adv-Routing-HOWTO/lartc.ratelimit.single.html

$ sudo tc qdisc add dev eth0 root handle 1: cbq avpkt 1000 bandwidth 1000mbit
$ sudo tc class add dev eth0 parent 1: classid 1:1 cbq rate 100mbit allot 1500 prio 5 bounded isolated
$ sudo tc filter add dev eth0 parent 1: protocol ip prio 16 u32 match ip dst 183.60.244.74 flowid 1:1

其中第二行的 100mbit 表示限速 100Mbps,第三行的 IP 为攻击者 IP。

暂时没有找到自动对每个 IP 限速的方法。

mirrors 再次修复缓存不一致问题

前一段时间发现 mirrors 缓存不一致的报警又开始增多了,发现是从一开始就有的一个 bug:更新 incrontab 的脚本没有在缓存文件列表更新后执行,这是因为主控脚本中用了 ./update-incrontab.sh … 的写法,而 crontab 执行时的当前目录(pwd)并非脚本所在目录,因此这些命令从来就没有成功执行过。由于之前测试时都是在脚本所在目录中执行的,测试并未发现 bug。这样,缓存文件列表中新增的文件就不受 incrontab 监控,需要等整个源同步完成后再删除,因此会出现较长时间的缓存不一致。

解决方法:改成

$(dirname $0)/update-incrontab.sh ...

顺便解决了 syslog 一直写入 /var/log/syslog.1 的问题,现在这个文件已经长到 18G 了。做法很简单(但没人管的话永远也不会被解决):

sudo service rsyslogd rotate

mirrors 修复同网段回复包发到默认网关

对于多线接入主机,要让回复的包与进来的包走同一线路,我们一般使用 ip rule 让特定来源 IP 的包走“专用”路由表。例如 mirrors 教育网线路的本机 IP 是 202.38.95.110,默认网关是 202.38.95.126:

ip route add default via 202.38.95.126 table 1000
ip rule add from 202.38.95.110 lookup 1000

但上述配置有个问题:当访问者来自同一子网时,回复包将仍然回复到默认网关。尽管一般来说这仍然能工作,但可能引起网络管理员的怀疑。因此建议让同一子网的包直接发到目标主机。修改方法是增加一条 ip route 到“专用路由表”。例如 mirrors 教育网线路的情况:

ip route add 202.38.95.0/25 dev vlan95
ip route add default via 202.38.95.126 table 1000
ip rule add from 202.38.95.110 lookup 1000

正确配置后,1000 号路由表的内容应该如下:

$ ip route show table 1000
default via 202.38.95.126 dev vlan95
202.38.95.0/25 dev vlan95  scope link

Mirrors 修复索引文件缓存不一致问题

自从去年引入 SSD cache 之后,索引文件总是有缓存不一致问题,因为有一些同步脚本中更新索引文件的操作不是原子操作,这些写操作不会触发清空缓存的动作,导致缓存不一致。现在 incrontab 监控了 IN_MODIFY 动作,一天来没有发现新的缓存不一致事件。之所以早先没监控 IN_MODIFY,是因为 rsync 同步过程中写临时文件,会触发大量的 IN_MODIFY 事件。不过在缓存一致性面前,只好牺牲一些性能了。

mirrors修复BioConductor和Loongson源

这两个源都是因为上游源地址换掉,我们没有及时跟进导致长期同步失败的。

BioConductor此次修复后增加了2.13版本的源,现在共有2.11/2.12/2.13三个版本。

而来自anheng的龙芯补充源则是在联系了其维护者后,重新完成了同步。在此摘录部分邮件内容加以说明:


新的Debian不再支持老的软件源的方式,因此增加了bjlx 目录,用于龙芯补充源,目录说明如下:
 
loongson: 包含一些2e的补充源和一些老的龙芯的东西,install目录是离线安装包发布目录,还在更新。
 
Loongson2f: 2f的补充源,不再更新。
 
loongson3: wheezy源,已经合并到bjlx。
 
bjlx: 统一的补充源,经常更新。

因此我们选择同步loongson/loongson2f/bjlx这三个源,频率是每天一次。

Mirrors 关闭虚拟机导致主机网络中断

今天(2013年12月21日)18:40,我(boj)在 mirrors 的 LXC 虚拟机里 init 0 了一下,本来以为跟 lxc-stop 一样的效果,但主机连不上了。原因是虚拟机没有做网络隔离,虚拟机 init 0 过程中会关闭网卡,然后主机网络就 down 了。

事故发生时虽然网络中心值班老师已经下班,但 jameszhang 老师还在东区,于是帮忙打开了机房门。zguangyu 重启了主机,但重启后屏幕显示 GRUB 四个字母(还没到 GRUB 菜单)就卡住了。截至 20:00 发稿时,zguangyu 仍然在机房修理。

Update (21:00):20:45 mirrors 各项服务已恢复正常。宕机时间2小时5分钟。mirrors 不能自启动的原因另文说明。

由于 lug@ustc.edu.cn 在 DNSPod 的账号被锁定了,无法修改 DNS,从而无法迅速切换到备份站点。非常抱歉此次事故给大家带来的麻烦。

事故教训:

  1. 虚拟机要做网络隔离,避免虚拟机内的错误网络配置影响主机。
  2. 应该尽快把 DNS 收回学校。

mirrors恢复qomo源

qomo 的上游源即官方源把 rsync 的 path 从 qomo/ 调整到了 yum-repo/ ,mirrors 不知为何一直没有跟进。故此次重新修正,并手工同步完成。目前 qomo 源每天 6 点 42 分同步一次。

mirrors修复archlinuxarm源以及status页面的排序bug

在上半年或者更早的时候,我们申请了 archlinuxarm 的官方源,并由主站进行 push。不过自从暑假里 mirrors 服务器重装系统,mirrors-lab 虚拟机停止工作后,这个源就一直没有恢复。目前,我们从亚琛工大的 rsync://ftp.halifax.rwth-aachen.de/archlinux-arm/ 这个源进行同步,每小时一次,基本恢复了 archlinuxarm 源的正常工作。将来有机会的话我们会积极申请恢复官方 push 的。

最近 mirrors 上的 status 页面恢复后,我们用jquery的tablesorter插件来对表格进行排序。可是对于占用空间大小一列,这个插件并不能很好的识别。因此我在 status 页面的生成脚本里,把未经转换的真实大小(bytes)直接以 ‘display:none’ 的样式插入,并写了个函数作为 TextExtraction 让 tablesorter 选取这个 ChildNode 作为排序依据。

但是后面又发现了新的问题,即第一列里因为有锚元素的存在,又导致 TextExtraction 对第一列失效。即对于这样的一个 <td> 元素:

&lt;td&gt;&lt;a href="/anthon/"&gt;anthon&lt;/a&gt;&lt;/td&gt;

如果要取其中的连接部分的话,应该用:

node.childNodes[1].innerHTML;

而非:

node.childNodes[0].innerHTML;

因此调整了一下脚本和页面后即告完工。