mirrors 部分源从磁盘阵列迁移到本地磁盘

由于磁盘阵列负载过高,io util 常年 100%,现将一些大源移到本地磁盘。选取目标磁盘主要有两个原则,一是空余空间足够,二是目前负载不高。

移到 sdd 上的源是 qtproject。sdd 上原有的源主要有 debian 和 gentoo。

移到 sde 上的源是 ubuntu 和 ubuntu-releases。sde 上原有的源主要有 CPAN,CTAN,deepin,opensuse 和 cygwin。

移到 sdf 上的源是 debian-cd。sdf 上原有的源主要有 centos,eclipse 和 archlinux。

To Mirrors Maintainers:移动源的步骤:

  1. 以 mirror 用户登录 sync LXC
  2. 在 screen 或 byobu 中 rsync -av /mnt/original-disk/mirror /mnt/new-disk/
  3. 修改 ~/etc/ 中对应的配置文件
  4. git commit -a -m “commit message” –author=”Your Name <you@gmail.com>”
  5. git push
  6. rsync 结束后,原子地替换掉 HTTP 根目录中的符号链接:ln -s /mnt/new-disk/mirror /tmp/
  7. mv /tmp/mirror /srv/www/
  8. 检查 HTTP 是否可以正常访问。
  9. 如果有提供 FTP 服务,要到主机里修改 /etc/fstab,并 mount -a
  10. 如果有提供 rsync 服务,要到主机里修改 /etc/rsyncd.conf

修复 mirrors 缓存过期问题

mirrors 的源有一些没有更新,原因是使用了过期的 SSD cache。SSD cache 本来会与源目录同步,不幸的是 SSD 满了。撑满 SSD 的原因是同步时没有删除已经不在缓存文件列表中的文件,这又是因为我对

rsync --delete --file-list

的理解有误。我以为这样会删除源目录中不在 file-list 中的文件,事实上不会。

改成了根据缓存文件列表生成符号链接到临时目录,再同步文件到 SSD:

rsync --delete --copy-links $TMP_LINK_ROOT/ $SSD_CACHE_ROOT/

commit: https://gitgeek.net/mirrors/ssd-cache/commit/6602b3b47d55d47d2185a9ed8ad1220a54cd905a

服务器优化 TCP 参数

blog 和 mirrors 服务器调整了 ip_conntrack 配置:

net.ipv4.netfilter.ip_conntrack_max=655360</code>

# should not be less than net.ipv4.tcp_keepalive_time (default 7200)
net.netfilter.nf_conntrack_tcp_timeout_established=14400

blog,mirrors 和 gitlab 调整了 TCP 发送窗口大小限制,以充分利用高延迟和高带宽的线路:

net.core.wmem_max=12582912
net.core.rmem_max=12582912
net.ipv4.tcp_rmem= 10240 87380 12582912
net.ipv4.tcp_wmem= 10240 87380 12582912

blog,mirrors 和 gitlab 在 HTTP 持久连接期间不再慢启动,以加快第二次及以后访问页面:

net.ipv4.tcp_slow_start_after_idle=0

增加 TCP 发送和接收默认窗口大小到 10 个 MSS,以减少小 HTTP 请求和 HTTPS 握手的来回次数。对 2.6.33 以下内核只能调整默认发送窗口大小(以下命令仅为示例):

# example on Debian wheezy
ip route change default via 202.141.176.126 initrwnd 10 initcwnd 10
# example on Debian squeeze
ip route change default via 202.141.176.126 initcwnd 10

Mirrors 扩充磁盘阵列空间

为了给即将入驻的 sourceforge 源腾地方,mirrors 将磁盘阵列的 XFS 扩充到 24T,现有 14T 可用空间。

删除无用内容

首先删除一些未同步成功又占据大量空间的源,释放了约 3T 磁盘空间。

  • android-src
  • android-releases
  • sourceforge
  • google-v8
  • chromiumos

扩容 XFS

XFS 号称能在线扩容,不过那是在 XFS 建立在 LVM 上时才可以。由于害怕 LVM 影响性能,mirrors 的 XFS 是直接建立在磁盘阵列的 GPT 上的。扩容 XFS 分为三步:

  1. 修改分区表
  2. 让内核重新载入分区信息,而这需要卸载文件系统
  3. 在线扩容 XFS

卸载磁盘阵列文件系统

以往卸载磁盘阵列的文件系统是全手工操作,折腾一次至少要十几分钟,其间磁盘阵列上的源都无法访问,整个网络服务(nginx 等)也会有一两分钟中断。这次我们使用了脚本,基本服务(nginx)每次中断时间不超过5秒,磁盘阵列的中断时间不超过2分钟(第一次中断了1分多钟是由于脚本里忘了 mount -a 重新挂载,收到报警短信后赶紧 mount -a)。

  1. 停止 LXC(因为 LXC 的根文件系统在磁盘阵列上)
  2. 杀掉所有服务进程
  3. 卸载已挂载的磁盘阵列文件系统(如果这一步出现问题,需要用 lsof 排查)
  4. 做想做的事
  5. 重新挂载文件系统
  6. 启动服务进程
  7. 启动 LXC

一开始的脚本是这样的:

sudo service rsync stop; sudo service vsftpd stop; sudo service nginx stop; mount | grep /dev/sdh1 | awk '{print $3}' | while read dir; do sudo umount $dir; sudo lsof $dir; done; sudo service nginx start; sudo service vsftpd start; sudo service rsync start

但发现 lsof 里还有很多 rsync 和 vsftpd 占着文件描述符,因此把 vsftpd 和 rsync 改成了杀气腾腾的 pkill:

sudo service nginx stop; sudo pkill vsftpd; sudo pkill rsync

还是有一些 rsync 进程发了 SIGTERM 信号仍无动于衷,因此改成了 SIGKILL(kill -9 所用的不可捕获杀进程信号)。

sudo service nginx stop; sudo pkill vsftpd; sudo pkill -SIGKILL rsync

现在 umount 成功了,但 rsync 启动失败了。这是由于没有删掉 rsyncd 的 pid 文件,强制删除即可。

sudo service nginx stop; sudo pkill vsftpd; sudo pkill -SIGKILL rsync; mount | grep /dev/sdh | awk '{print $3}' | while read dir; do sudo umount $dir; done; sudo service nginx start; sudo service vsftpd start; sudo rm -f /var/run/rsyncd.pid; sudo service rsync start

坑爹的 udev rule

扩容顺利完成了,不过 partprobe 之后,sdh 变成了跟 sdh1 一样的大小,分区表也不见了。重新启动 iscsi,问题依然如故。

$ sudo service nginx stop; sudo pkill vsftpd; sudo pkill -SIGKILL rsync; mount | grep /dev/sdh | awk '{print $3}' | while read dir; do sudo umount $dir; done; sudo /etc/init.d/open-iscsi restart; sudo service nginx start; sudo service vsftpd start; sudo rm -f /var/run/rsyncd.pid; sudo service rsync start
Stopping nginx: nginx.
Unmounting iscsi-backed filesystems: Unmounting all devices marked _netdev.
Disconnecting iSCSI targets:Logging out of session [sid: 1, target: iqn.2002-10.com.infortrend:raid.sn8223150.001, portal: 192.168.10.1,3260]
Logout of [sid: 1, target: iqn.2002-10.com.infortrend:raid.sn8223150.001, portal: 192.168.10.1,3260] successful.
.
Stopping iSCSI initiator service:.
Starting iSCSI initiator service: iscsid.
Setting up iSCSI targets:
Logging in to [iface: default, target: iqn.2002-10.com.infortrend:raid.sn8223150.001, portal: 192.168.10.1,3260] (multiple)
Login to [iface: default, target: iqn.2002-10.com.infortrend:raid.sn8223150.001, portal: 192.168.10.1,3260] successful.
.
Mounting network filesystems:.
Starting nginx: nginx.
Starting FTP server: vsftpd.
Starting rsync daemon: rsync.

$ sudo fdisk -l /dev/sdh

Disk /dev/sdh: 24189.3 GB, 24189254763008 bytes
255 heads, 63 sectors/track, 2940842 cylinders, total 47244638209 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000

Disk /dev/sdh doesn't contain a valid partition table

$ sudo fdisk -l /dev/sdh1

Disk /dev/sdh1: 24189.3 GB, 24189254763008 bytes
255 heads, 63 sectors/track, 2940842 cylinders, total 47244638209 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk identifier: 0x00000000

Disk /dev/sdh1 doesn't contain a valid partition table

线索在 syslog 里:

udevd[13846]: kernel-provided name 'sdh1' and NAME= 'sdh' disagree, please use SYMLINK+= or change the kernel to provide the proper name

原来是下面的 udev rule 捣的鬼,内核把 sdh1 和 sdh 都 probe 出来了,sdh1 占用了 sdh 这个名字(因为它们都符合 udev rules,不信可以自己执行 /lib/udev/scsi_id 验证)……

$ cat /etc/udev/rules.d/80-persistent-iscsi.rules
KERNEL=="sd*", SUBSYSTEM=="block", PROGRAM="/lib/udev/scsi_id --whitelisted --replace-whitespace /dev/$name", RESULT=="3600d0231000da93e75966be33fd9a2b4", NAME="sdh"

修改方法很简单,加个 %n 就行了。

$ cat /etc/udev/rules.d/80-persistent-iscsi.rules
KERNEL=="sd*", SUBSYSTEM=="block", PROGRAM="/lib/udev/scsi_id --whitelisted --replace-whitespace /dev/$name", RESULT=="3600d0231000da93e75966be33fd9a2b4", NAME="sdh%n"

扩容过程演示

下面我们演示将磁盘阵列 XFS 从 22T 扩容到 24T 的完整过程(为了文章清晰,删除了一些输出)。

1. 查看分区表信息。GPT 分区表要用 gdisk,而非 fdisk。

boj@mirrors:~$ sudo gdisk /dev/sdh
GPT fdisk (gdisk) version 0.8.5

Partition table scan:
MBR: protective
BSD: not present
APM: not present
GPT: present

Found valid GPT with protective MBR; using GPT.

Command (? for help): i
Using 1
Partition GUID code: EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 (Microsoft basic data)
Partition unique GUID: 4592A37B-886C-40C9-A2AC-D9145B1D33D1
First sector: 2048 (at 1024.0 KiB)
Last sector: 47244640256 (at 22.0 TiB)
Partition size: 47244638209 sectors (22.0 TiB)
Attribute flags: 0000000000000000
Partition name: 'array'

2. 删除原有分区并新建分区、修改分区名称。注意分区起始扇区、GUID 都必须与原来的相同,否则文件系统无法识别!

Command (? for help): d 1
Using 1

Command (? for help): n
Partition number (1-128, default 1):
First sector (34-54684213214, default = 2048) or {+-}size{KMGTP}:
Last sector (2048-54684213214, default = 54684213214) or {+-}size{KMGTP}: 24T
Current type is 'Linux filesystem'
Hex code or GUID (L to show codes, Enter = 8300): EBD0A0A2-B9E5-4433-87C0-68B6B72699C7
Changed type of partition to 'Microsoft basic data'

Command (? for help): c 1
Using 1
Enter name: array

Command (? for help): i
Using 1
Partition GUID code: EBD0A0A2-B9E5-4433-87C0-68B6B72699C7 (Microsoft basic data)
Partition unique GUID: A5298F7D-A675-41DD-9AA3-BE119CA73DB3
First sector: 2048 (at 1024.0 KiB)
Last sector: 51539607552 (at 24.0 TiB)
Partition size: 51539605505 sectors (24.0 TiB)
Attribute flags: 0000000000000000
Partition name: 'array'

Command (? for help): w

Final checks complete. About to write GPT data. THIS WILL OVERWRITE EXISTING
PARTITIONS!!

Do you want to proceed? (Y/N): Y
OK; writing new GUID partition table (GPT) to /dev/sdh.
Warning: The kernel is still using the old partition table.
The new table will be used at the next reboot.
The operation has completed successfully.

3. 卸载磁盘阵列文件系统以便 partprobe 让内核重新载入分区表(详见上文)

boj@mirrors:~$ sudo lxc-list
RUNNING
  lxr
  pypi
  sync (auto)

boj@mirrors:~$ sudo lxc-stop -n pypi
boj@mirrors:~$ sudo lxc-stop -n sync
boj@mirrors:~$ sudo lxc-stop -n lxr

boj@mirrors:~$ sudo service nginx stop; sudo pkill vsftpd; sudo pkill -SIGKILL rsync; mount | grep /dev/sdh | awk '{print $3}' | while read dir; do sudo umount $dir; done; sudo partprobe; sudo mount -a; sudo service nginx start; sudo service vsftpd start; sudo rm -f /var/run/rsyncd.pid; sudo service rsync start
Stopping nginx: nginx.
Starting nginx: nginx.
Starting FTP server: vsftpd.
Starting rsync daemon: rsync.

boj@mirrors:~$ sudo lxc-start -n pypi -d
boj@mirrors:~$ sudo lxc-start -n sync -d
boj@mirrors:~$ sudo lxc-start -n lxr -d

4. 检查分区信息,使用 xfs_growfs 对 XFS 分区进行扩容。

boj@mirrors:~$ sudo gdisk -l /dev/sdh
GPT fdisk (gdisk) version 0.8.5

Partition table scan:
MBR: protective
BSD: not present
APM: not present
GPT: present

Found valid GPT with protective MBR; using GPT.
Disk /dev/sdh: 54684213248 sectors, 25.5 TiB
Logical sector size: 512 bytes
Disk identifier (GUID): 5E620981-EBFB-4EEA-BCF9-0052707F6859
Partition table holds up to 128 entries
First usable sector is 34, last usable sector is 54684213214
Partitions will be aligned on 2048-sector boundaries
Total free space is 3144607676 sectors (1.5 TiB)

Number Start (sector) End (sector) Size Code Name
1 2048 51539607552 24.0 TiB 0700 array

boj@mirrors:~$ sudo xfs_growfs /dev/sdh1
meta-data=/dev/sdh1 isize=256 agcount=22, agsize=268435455 blks
= sectsz=512 attr=2
data = bsize=4096 blocks=5905579776, imaxpct=5
= sunit=0 swidth=0 blks
naming =version 2 bsize=4096 ascii-ci=0
log =internal bsize=4096 blocks=521728, version=2
= sectsz=512 sunit=0 blks, lazy-count=1
realtime =none extsz=4096 blocks=0, rtextents=0
data blocks changed from 5905579776 to 6442450688

5. 检查扩容是否成功。

boj@mirrors:~$ df -lh /dev/sdh1
Filesystem Size Used Avail Use% Mounted on
/dev/sdh1 24T 11T 14T 43% /srv/ftp/ubuntu-old-releases

boj@mirrors:~$ sudo lxc-list
RUNNING
  lxr
  pypi
  sync (auto)

FROZEN

STOPPED
  mirror-lab
  mirror-lab_
  php-mirror
  root

最后提醒 mirrors 维护者,这类危险操作必须在 screen 或 byobu(screen 的封装,可以有选项卡)中进行,以免你的网络突然中断,脚本被 SIGHUP,将系统留在不可预测的状态。

mirrors 启用 SSD cache

优化之前,mirrors 磁盘阵列(sdh)的 io util 常常在 90% 以上,有些时候甚至“稳定”在 100%。从sda 往 sdh 复制一些小文件,竟然不到 5MB/s。尝试调整了 iscsi 的 MTU、txqueuelen 等网络参数,没有效果,瓶颈可能不在网络上。以一块 7500rpm SATA 磁盘随机读 75~100 iops 计算,RAID6 的 iops 大约是 450~600 iops,与我们的测试结果基本相符,因此磁盘阵列可能本来就这么慢。

SSD(/dev/sdb)被格式化为 ext4 文件系统(UUID=8e19059d-358e-41ca-8896-b9b26a2d4f59),用 tune2fs 去掉了 journal。挂载参数为 nodev,nosuid,noatime,discard,挂载点为 /mnt/ssd。

用 fio 测试结果如下(使用 1G 文件):

  • 连续读 139327KB/s,34831 iops
  • 连续写 1001.1MB/s,256500 iops
  • 同时连续读写,与单独测试相比没有明显变化
  • 随机读 15708KB/s,3926 iops
  • 随机写 762047KB/s,190511 iops
  • 同时随机读写,与单独测试相比没有明显变化

写的性能这么好,估计是由于 buffer。连续读性能不高,是因为 SSD 在 RAID 卡后面,不过对我们来说足够了。我们最关心的随机读性能 3926 iops 达到了预期,目前磁盘阵列的随机读性能不到 500 iops。

SSD cache 的缓存策略基于 HTTP access log。每天凌晨,选出前一周的 HTTP 200 请求,按照 URL 访问频率降序排序,填满 SSD cache 为止。

注册登录 USTC LUG GitLab,即可查看源代码:https://gitgeek.net/mirrors/ssd-cache

cron-cache.sh 每天运行一次,生成缓存:

  1. 读出最近7天的 HTTP access log
  2. 取出 200 请求,统计 URL 访问频率
  3. 筛选至少访问过2次的文件,按频率降序排序,得到 filefreq-20131010.gz 这样的文件列表。
  4. 从上述文件列表中依次取出文件,获取其大小,添加到缓存列表(tocache-20131010)中,直到达到设定的缓存总大小。
  5. 用 rsync 把缓存列表中的文件同步到缓存区。用 rsync 的好处是文件没有被修改时不需要重新复制,由于多数文件的访问频率是相对稳定的,每天复制的数据量并不大。

每个源同步后,要刷新 cache,这是 update-cache.sh 的职责。它同样使用了 rsync。

200G 的缓存共缓存了 3696 个文件(其中 iso 文件占 422 个),根据前7天的 HTTP 200 日志,最多的在7天内被访问了 1036142 次,最少的在7天内被访问了 97 次。

SSD cache 的效果怎么样呢?200G 的 cache 同步完成后,

  • 磁盘阵列(sdh)的负载从一直 90%+,降到了时而 90%+,时而 50%~60%
  • 总流量增加了约 50Mbps(原来是 ~350 Mbps,后来是 ~400 Mbps)
  • load average 似乎没有明显变化
  • 被缓存文件的校内下载速度可达 50~70 MB/s,能够吃满 mirrors 的千兆网卡

希望 status.lug.ustc.edu.cn 和 mirrors 的 collectd-web 尽快恢复,以便对 SSD 的效果做更多更直观的观察和评估。

科大开源软件镜像发生磁盘故障

2013年8月31日 11:06,科大开源软件镜像(mirrors.ustc.edu.cn)发生磁盘故障。磁盘 sda 从系统中突然消失。sda 上有 mirrors 主系统(mirrors-main)的根分区、各 LXC 虚拟机系统的根分区,sda 的消失导致 mirrors 的所有服务立即中断。

问题发生一小时后的 12:08,Stephen Zhang 在邮件列表发帖说 mirrors 挂了。收到邮件后,我们检查了服务监控系统,发现学校短信平台也很不巧地挂掉了,因此没有在第一时间收到报警短信。我们登录到 mirrors-base(mirrors-main 是 Xen 虚拟机,mirrors-base 是 Xen Domain0),发现 dmesg 中有同样的磁盘错误信息,/dev/sda 不见了。据此判断是磁盘故障。

由于此问题无法快速解决,我们采用以往的方法(所谓“启动应急预案”),将 mirrors 的 DNS 设置别名为 backup.mirrors.ustc.edu.cn,它解析到 lug.ustc.edu.cn 服务器,显示一个临时错误页,并把大部分源 HTTP 重定向到国内其他开源软件镜像。

随后,我们把 /etc 备份到其他机器上,挽救了一些尚在缓存内的配置文件。我们重新扫描了 SCSI 总线,未发现新设备。确认主管机房的张运动老师在校后,在学校的 bible 去了网络中心机房。张运动老师还说学校几百台服务器依赖 CentOS 来装软件包,让我们一定把 CentOS 源维护好。据去年统计,校内流量仅占 mirrors 总流量的1%,因此每次 mirrors 挂机,都可能影响数以万计的服务器和 PC,对此我们深感愧疚。

重新拔插硬盘、物理重启 mirrors 数次,包括使用不带 Xen 的内核,仍未在系统里发现丢失的那块磁盘。在 RAID 控制界面,发现有两块磁盘报 PD Missing 错误。我们尚未找到相关技术资料。

2013-08-31

目前 mirrors-base(Xen Domain0)仍然在正常运行,因其根文件系统在 sdc 上。mirrors-main 连同 LXC 虚拟机系统的根分区连同那块硬盘,已经消失了。我们怀疑并不是硬盘损坏,因为两块硬盘是硬 RAID1,同时损坏的概率微乎其微;怀疑是 RAID 卡损坏,或者主板上接线松了。

本次故障由于涉及硬件问题,恢复时间未知。我们对此带来的不便深表歉意。