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。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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 上的代码能给大家一些帮助。

Comments