科大开源软件镜像是怎样炼成的

Update (2014-09-29): 由于 mirrors 配置文件里有一些不适合公开的内容,现在配置文件不再公开,本文中的一些链接变成了死链,非常抱歉。

由于科大开源软件镜像(mirrors.ustc.edu.cn)发生磁盘故障,stephen、tux 和我 (boj) 都不在学校,加上7月 mirrors 出故障之后就一直没恢复彻底,是时候推倒重来了。这次 mirrors 重建要完全由在校的同学们完成,这也是锻炼技术的一个机会。这里,我来简要解释开源软件镜像包括哪些部分,应该如何搭建。由于 sourceforge 还在等着我们同步,希望能在三天内恢复基本服务,一周内重建好包括同步在内的整个系统。

WTF?

所谓开源软件镜像,就是从官方站点同步一些 GNU/Linux 发行版和知名开源软件的软件仓库。用户通过修改配置文件,就近使用软件仓库镜像,以加快下载速度,减轻官方站点的负载。

做一个开源软件镜像需要什么呢?

  • 稳定的带宽,一般要 100Mbps 以上
  • 较大的硬盘,一般要 several TB
  • 稳定的服务器,在线时间 99% 以上
    开源软件镜像主要是两个模块:提供下载服务、定期从官方源同步。事实上,科大开源软件镜像目前提供 HTTP、rsync、FTP 服务,可以通过 rsync、ftpsync、ssh-trigger、ftp-push、git 等方式从上游源(upstream)同步。一个实际运行的开源软件镜像还需要服务状态监控。

下文将分几个方面,介绍科大开源软件镜像(下称 mirrors)是如何搭建的:

  1. 网络配置
  2. 存储配置
  3. HTTP、rsync、FTP 服务
  4. 虚拟机
  5. 从上游源定期同步
  6. 服务器监控

网络配置

mirrors 目前有4个 ISP。它们是:

  • 202.141.160.110 China Telecom(电信)
  • 202.141.176.110 China Mobile(移动)
  • 202.38.95.110 CERNET(教育网)
  • 2001:da8:d800:95::110 CERNET2(教育网IPv6)
    mirrors 有两块千兆网卡,eth0 是连接外网的,eth1 是网线直连磁盘阵列的。一根网线,怎么是4个ISP?奥妙在于网络中心交换机上的 VLAN。交换机上运行着 802.11Q 协议,主机需要在网卡上指定 VLAN ID,给数据包打上对应的 tag,才能访问对应的线路。China Telecom、China Mobile、CERNET/CERNET2 的 VLAN ID 分别是 10、400、95,也就是需要在 /etc/network/interfaces 里指定 vlan10、vlan400 和 vlan95 并分别绑定上述IP。
    iface vlan95 inet static
    vlan-raw-device eth0
    address 202.38.95.110
    netmask 255.255.255.128

iface vlan10 inet static
vlan-raw-device eth0
address 202.141.160.110
netmask 255.255.255.128

iface vlan400 inet static
vlan-raw-device eth0
address 202.141.176.110
netmask 255.255.255.128
下面还要配置路由规则(写在 /etc/rc.local 里)。全局的默认是教育网出口。来自教育网的请求要从教育网回复,来自电信的请求要从电信回复,来自移动的请求要从移动回复。关于这些配置的原理,参见多线接入主机的诡异 NAT

ip route add default via 202.38.95.126

ip route add default via 202.38.95.126 table 1000
ip route add default via 202.141.160.126 table 1001
ip route add default via 202.141.176.126 table 1002
ip -6 route add default via 2001:da8:d800:95::1 table 1000

ip rule add from 202.38.95.110 table 1000
ip rule add from 202.141.160.110 table 1001
ip rule add from 202.141.176.110 table 1002
ip -6 rule add from 2001:da8:d800:95::110 table 1000
我们在实际运行时发现,移动出口访问国外网络的网速比较快。有两种方案:

  1. 在同步脚本里 bind 某个特定的 IP 而非 0.0.0.0,rsync、lftp、wget、curl 等均有指定 bind-address 的参数。
  2. 使用 http://gitlab.lug.ustc.edu.cn/boj/smartproxy(需要注册登录)的ISP所在IP表,让教育网走教育网,电信走电信,默认走移动出口。

存储配置

mirrors 的存储资源包括:

  • 10块 SATA 硬盘,采用硬 RAID1,构成 sda、sdb、sdc、sdd、sde,空间分别是1T或2T。现在损坏的是 sda。这些盘上建立了LVM卷组。其中两块1T的盘目前分别用于根文件系统和日志,三块2T的盘用于数据。
  • 26TB 磁盘阵列,通过 iscsi 千兆网直连。目前分了一块 20TB 的 XFS 分区(其余空闲),已占用 13T,其中有 5T 的数据可以删除。需要扩容到至少 22TB 以容纳 预计占用 10T 的 sourceforge 镜像。
  • 256GB SSD,计划用于缓存优化,尚未使用。
    在重新配置硬盘的时候,要注意:
  1. 插拔硬盘时,不要改变硬盘的插槽,否则硬 RAID 会被破坏,可能导致大量数据丢失。
  2. 不要随意拆散 LVM 卷组。熟悉 vgdisplay、pvdisplay 等命令,查看清楚 LVM 信息后再操作。
  3. 配置完毕之后,一定要写个文档写明每个块设备的名称、UUID、LVM 卷组;每个分区或 LVM 卷的名称、挂载点、用途。(血泪教训啊)
    iSCSI 的配置方法(来自 jameszhang):

  4. eth1 接口设置 192.168.10.2/24

  5. sudo apt-get install open-iscsi
  6. 编辑 /etc/iscsi/iscsid.conf,改为 node.startup = automatic
  7. sudo iscsiadm -m discovery -t st -p 192.168.10.1
  8. sudo iscsiadm -m node –login 可以看到磁盘
    配置好之后要使用 udev rules 把块设备名称(sda…)固定下来。不然重启之后硬盘名字变了,看起来很麻烦。fstab 和各种脚本里应尽量使用 UUID,以防万一。

HTTP、rsync、FTP 服务

请参考老 mirrors 的配置:(需要注册登录)

http://gitlab.lug.ustc.edu.cn/mirrors/mirrors-system-conf/tree/master

HTTP - Nginx

Mirrors 使用 Nginx 高性能 Web 服务器提供 HTTP 服务。Nginx 配置文件应当受到版本控制,建立一个独立的 git 仓库(目前是跟其他配置混杂在一起的)。

nginx.conf 是 nginx 配置文件的入口,它 include 其他配置文件,包括 conf.d 中的自定义 log format,还有 sites-enabled 中的“虚拟主机”。

sites-enabled 中的每个文件都是相对路径链接到 sites-available 中。其中 mirrors.ustc.edu.cn-{localhost,vlan10,vlan400,vlan95} 是 mirrors 主站4条线路的配置文件,它们 include 公共的主站配置文件 mirrors.ustc.edu.cn-common。对4条线路分开配置文件,可以方便对不同的线路定制不同的访问控制策略。

mirrors 的各个子站,包括 pypi、archlinuxarm、mirrors lab,都是单独的配置文件。newindex 和 testindex 是我们测试的未来首页,其中 newindex 中有根据 User Agent 判断是否浏览器的示例。

要注意,server 块内的 listen directive 应尽量列出所有监听的IP(三个 IPv4 和一个 IPv6),不要监听 0.0.0.0,因为 mirrors 以后可能添加 IP 专用于某个用途,我们的 “战略IP储备” 有 202.38.95.106 和 202.141.176.111。

Nginx 的一个问题是,当磁盘 I/O 成为瓶颈时,Nginx 响应速度会明显降低。因此应密切关注 mirrors 的 I/O 负载情况。

要注意配置 LogRotate,定时对日志文件进行压缩,并丢弃年代久远的日志文件。 http://gitlab.lug.ustc.edu.cn/mirrors/mirrors-system-conf/blob/master/logrotate.d/nginx

按照目前的 nginx 配置,添加源的时候,只要建立符号链接到 /var/www。

rsync - rsyncd

rsync 协议是大量文件同步的好工具,从上游源同步优先使用 rsync。为方便国内其他开源软件镜像,原则上所有镜像都提供 rsync 服务。

老 mirrors 的 rsyncd 配置文件:

http://gitlab.lug.ustc.edu.cn/mirrors/mirrors-system-conf/blob/master/rsyncd.conf

要写 /etc/rsyncd.motd 作为 “Message Of ToDay” 即 rsync 显示的头部一大堆文字。

rsyncd 会给每个连接 fork 一个进程,会在连接建立的时候读取最新的配置文件,因此修改配置文件不需要重启服务。

根据目前的 rsync 配置,添加源的时候,需要修改 rsyncd.conf 加入对应的配置。

FTP - vsftpd

mirrors 使用 vsftpd 作为 FTP 服务器:http://gitlab.lug.ustc.edu.cn/mirrors/mirrors-system-conf/blob/master/vsftpd.conf

由于每个 FTP 连接需要 vsftpd fork 一个新进程,对服务器的资源消耗较大,因此建议使用 HTTP 访问。基于性能上的考虑,我们之前只对一些必要的源开启了 FTP 访问。不过很多用户有批量下载文件的需要,又不会使用 lftp (http mirror)、rsync 等工具,因此还是有必要提供 FTP 访问的。可以先放开,再观察其访问量和对性能的影响。

根据目前的 FTP 配置,添加源的时候,需要修改 fstab 将数据目录 bind mount 到 /srv/ftp/project-name,并执行 mount -a 以使之生效。需要 bind mount 而不能符号链接,是由于 vsftpd worker 会 chroot,访问的目录不能超出根目录(/srv/ftp)。

虚拟机

我们使用 LXC Linux Container 实现服务的隔离。比如从上游源定期同步、生成 status 页面等操作,与 HTTP、rsync、FTP 等核心服务并没有直接关系,如果把它们放进虚拟机,就可以避免出了 bug 的脚本占用过多的资源影响核心服务,或者操作失误导致服务中断或数据损坏。

mirrors 中建议包括以下几个虚拟机(建议使用 Debian wheezy stable):

  • ftp-push,给上游源开的可写 FTP 同步方式。
  • rsync-push,给上游源开的可写 rsync 同步方式。
  • lab,相对开放的实验虚拟机,初入 mirrors 的童鞋们可以在这里试手。
  • web,生成 status 页面、维护 web 页面(将来 web 页面可能有除 status 页面外的更多定时生成内容)
  • sync,rsync、ftpsync、git 同步脚本在这里运行。
  • npm,Nodejs 源(需要 couchdb 数据库)和同步脚本。
  • rubygems,rubygems 同步脚本。
  • pypi,pypi 同步脚本。
  • sf,即将为 SourceForge Mirror 提供的虚拟机。
    LXC 操作方法和配置文件,可以参考以前的脚本:

http://gitlab.lug.ustc.edu.cn/mirrors/mirrors-main/tree/master

网络隔离

需要注意的是,上述脚本里虚拟机没有隔离网络命名空间。我们希望 LXC 使用隔离的网络命名空间,使用 SNAT 发起出站连接,使用 DNAT 做入站端口映射,不要让 LXC 里的网络访问与主站的核心服务有冲突的可能。

建议的各虚拟机网络配置:

  • ftp-push:DNAT inbound only
  • rsync-push:DNAT inbound only
  • lab:DNAT inbound(如果不再用 Xen 虚拟化的话,也可以绑定 202.38.95.106),SNAT outbound
  • web:不需要网络访问
  • sync:SNAT outbound only
  • npm:nginx 反向代理,SNAT outbound
  • rubygems:SNAT outbound only
  • pypi:SNAT outbound only
  • sf:绑定固定 IP 202.141.176.111

从上游源定期同步

由于历史原因,mirrors 现有两套同步脚本:

要阅读这套脚本,crontab 是入口。从这里可以看到定时调用 ftpsyncustcsync。(小作业:ftpsync 和 ustcsync 有什么区别?)可以看到,2小时、4小时、6小时、24小时为同步周期的都有。注意 crontab 中的分钟数,这些数字是随机的,以尽量分散镜像同步对磁盘和网络带来的压力。

建议在 sync 虚拟机里建立一个 mirror 用户,使用这个 crontab 来进行同步。

crontab 和 bin 目录里有些东西应该移到 web 虚拟机里:

  • 生成首页的 genindex.py
  • 生成 awstats 静态页面的脚本
  • 生成同步状态(status 页面)的脚本
    bin 目录里还有查看服务器状态的一些小工具。

服务器监控

一个稳定的服务需要维护者知晓其状态。这个状态分两方面,一是各种镜像的同步状态,二是服务器自身的运行状态。

status 页面

镜像同步状态,就是大家熟知的 status 页面了。mirrors 的 status 页面是每5分钟定时查询同步日志,以获取同步状态、镜像大小、最后同步时间等。目前的脚本是 stephen 所写:

http://gitlab.lug.ustc.edu.cn/mirrors/mirrors-scripts/blob/master/bin/get-mirror-status.py

正如大家注意到的,我们需要更多有关镜像的信息,例如

  • 上游源类型及 URL
  • 最近一次同步的变化量、用时和平均速度
  • 预计下次同步时间
    这些信息最好能以结构化格式(如 JSON)输出,前端页面再去 parse,这样方便其他程序处理。

我们还需要统计每个镜像的上游源质量,因此有必要在同步结束后把统计信息存入数据库,不然翻 log 太麻烦了,而且过期的 log 还会被删除。

collectd

collectd 是服务器状态监控软件,可以监控 CPU、网络、磁盘、内存等多项指标,存入 rrd 数据库,通过 collectd-web 在网页上浏览。它的结构请自己 Google。由于现在 mirrors 已经挂了,很抱歉不能跟大家分享 collectd 的截图。

下面是 mirrors 上 collectd 的配置文件:

http://gitlab.lug.ustc.edu.cn/mirrors/mirrors-system-conf/tree/master/collectd

一定记得恢复原有日志分区里的 collectd 数据,最好能跟新的拼接起来,这是研究 mirrors 一年来运行状态的宝贵资料。

外部监控

自己监控自己并不是十分可靠,因此 mirrors 还使用了 ganglia 和自编的短信报警。

ganglia 运行在 lug.ustc.edu.cn 服务器上,它监控着 USTC LUG 几乎所有服务器的运行状态。http://status.lug.ustc.edu.cn/

当一台机器挂机的时候,我首先看的就是 status.lug.ustc.edu.cn,看看 CPU、内存、磁盘、网络有什么异常,很容易定位到出事的时间点,猜测大致的原因,再去查 syslog、dmesg 或 nginx 的 access log、error log,就比较有针对性了。

针对 HTTP 服务的基本可用性,我们还有简单的监控脚本,每分钟检测一次,出现问题就发报警短信。

  • 服务器状态列表
  • 故障日志
    当然,目前 mirrors 的报警机制还很缺乏,比如硬盘满了、OOM Kill、nf_conntrack 表满了,我们运维人员都不能在第一时间得知。希望尝试使用 nagios 等报警软件,让 mirrors 的运维更自动化一些。

结语

这篇文章希望让大家了解到,搭建开源软件镜像从概念上并不是一件很复杂的事,主要就是 HTTP、rsync、FTP 服务和自动从上游源同步两部分。但维护好一个有近百个源、数据量超过 10TB、每天近千万次 HTTP 访问、每天流量逾 4TB 的开源软件镜像,还是需要一些更复杂的架构和工具的。就目前而言,我们对 mirrors 的状态监控和日志分析还很不充分,整个系统似乎运行在黑盒子里;mirrors 近期的稳定性也很不令人满意。

现在 mirrors 硬盘坏了,再去机房看看吧。如果不能尽快找出 RAID 卡或者接线方面的问题,就当成那两块硬盘不存在吧,从现在的 1T 日志分区里抠出一部分用于 LXC 虚拟机的根分区(日志可以转移到磁盘阵列)。总之,要尽快恢复服务。

重建后的 mirrors 用什么架构我已经鞭长莫及,也没有时间去折腾了,希望这篇文章和 GitLab 上的代码能给大家一些帮助。