一个月的艰辛稳定服务过程

业务大了成本问题更加的凸显,最重要的之一就是带宽,为了更好的优化资源,我们决定将目前 BGP 全线的流量切换一部分到双线上去。说白了很简单,网络方面不需要动什么大手脚,全网 10G 的链路半年之前就全部升级完毕,剩下的就是加前端,10G 服务器,LVS(nat), Keepalived,Nginx。接下来就是小流量的上线测试,从 10M 到 50M 到 100M 再到 300M 前后大概有一周的观察时间,整体还是很稳定的,当然也遇到了一些小问题,比如 10G 网卡不规则的出现了一些 rx_crc_errors/rx_missed_errors,通过优化一些硬件设备基本得到了解决,虽然后期还会时不时的冒出一些,但是从各个服务以及业务的监控方面没有看到潜在的影响,暂时略过。

正所谓稳定压倒一切,从开始小流量切换到正式全量上线的这一个月,为了稳定,充满了坎坷,下面是这一段时间遇到的一些比较有挑战的事件, 「很不幸的是」,全部是人为原因。

4xx 错误飙升
一周之后的某个早上收到报警,4xx, 5xx 的错误上升了大概一倍的样子:

可以确认的是这段时间没有任何的变更,从到后端的请求来看,这段时间的异常并没有对其有很大的影响,但是本着负责任的态度还是把当时的 log 归档拿出来分析了下详情:

可以看到,这段时间的 return code 主要是由 499 以及 500 错误引起,并且 499 的错误占了大部分的,那就从 499 的分析开始。G 了一番,发现这个 code 并非是一个标准的 RFC,唯一可以确认的就是是客户端主动的关闭了链接,至于是什么原因让客户端主动关闭的,原因就太多了。除此之外,还有一部分的稳定的 408 错误,这个比较好理解,对于由于 Nginx 读取客户端发来的请求会造成超时而返回 408。
看了上面的解释,基本得不到很明确的答案。接下来,至少还可以从 debug log 以及 error log 入手,看看能否得到一些信息。
首先看看 error log,关于 Nginx 的 access, error log 不少人都有误解,这里统一说明一下:
1. access log 的非 2xx 错误只有部分的会打到 err log 里面,仅仅是部分,而 access log 是所有的 return code 的集合
2. error log 并非是一些非 2xx code 的集合,里面很多都是开发定制的一些东西,代码都是写死的
3. 所以,任何的返回码(2xx, 4xx, 5xx)的问题都可以从 access log 里面找到问题
看 err log,大部分都是下面两种情形:
1. "no live upstreams while connecting to upstream"
2. "upstream timed out(110: Connection timed out) while reading response reader from upstream"
总结起来就是连不上 upstream 了,为什么会 time out 并不知道。再看看 debug log,这个需要在编译的时候加进 –with-debug,里面能得到的信息同样比较少:

2026/08/02 15:29:14 [debug] 58443#0: *26274947 http upstream recv(): -1 (11: Resource temporarily unavailable)

2026/08/02 15:29:14 [debug] 58443#0: *26274956 recv() not ready (11: Resource temporarily unavailable)

调查到了这里,基本从已有的信息没得到什么特别的结论,考虑到对后端本身并没有影响,这事就这么被放过去了。但是,再怎么逃避也逃避不了墨菲定律。

logrotate(gzip) 的问题
几天之后的某个晚上,突然收到报警,ipvs master 上的 rs 被移除,紧接着 ipvs slave 上的 rs 也被移除,接下来就是疯狂的短信轰炸了,一会儿 add,一会儿 remove。当时我不在现场,让我们同事先临时手动把 vip 从目前的 master 切换到了 slave,但是不见好转,当机立断,直接全部切换到全线,客户端由于会 cache 以及重发机制,所以这 20min 的 outage(发到后端的请求大概跌了 2/3) 对实际的影响并不是很大。回去之后开始重点分析这次事故。
首先是把 dr, rs 包括 traffic, req, lvs conn, Nginx return code/status, 各种 rt/delay 在内的核心监控一步一步分析回溯,理清了从问题开始一直到恢复这中间发生的每一步过程,发现的最大的收获是出问题的这段时间,lvs 到 rs 的延时大大增加:

其次,keepalived 打到 messages 里面的 log 也看到了不少的东西,比如这段时间,会频繁的 add, remove rs:
Sep  6 20:20:10 lvs-1 Keepalived_healthcheckers[107592]: TCP connection to [192.168.90.6]:80 success.
Sep  6 20:20:10 lvs-1 Keepalived_healthcheckers[107592]: Adding service [192.168.90.6]:80 to VS [111.111.111.4]:80
Sep  6 20:20:14 lvs-1 Keepalived_healthcheckers[107592]: TCP connection to [192.168.90.5]:80 failed !!!
Sep  6 20:20:14 lvs-1 Keepalived_healthcheckers[107592]: Removing service [192.168.90.5]:80 from VS [111.111.111.4]:80
Sep  6 20:20:20 lvs-1 Keepalived_healthcheckers[107592]: TCP connection to [192.168.90.5]:80 success.
Sep  6 20:20:20 lvs-1 Keepalived_healthcheckers[107592]: Adding service [192.168.90.5]:80 to VS [111.111.111.4]:80

把 log 的时间扩展到三天前,再仔细的观察观察,可以发现,每一次 add, remove 的操作都是出现在整点的时候:
Sep  6 12:00:15 lvs-1 Keepalived_healthcheckers[107592]: TCP connection to [192.168.90.5]:80 failed !!!
Sep  6 12:00:15 lvs-1 Keepalived_healthcheckers[107592]: Removing service [192.168.90.5]:80 from VS [111.111.111.4]:80

Sep  6 15:00:11 lvs-1 Keepalived_healthcheckers[107592]: TCP connection to [192.168.90.5]:80 failed !!!
Sep  6 15:00:11 lvs-1 Keepalived_healthcheckers[107592]: Removing service [192.168.90.5]:80 from VS [111.111.111.4]:80

联想到整点 ngx 会进行 logrotate 的操作,期间会产生大量的 io,导致系统负载变高,再加上 ngx 本身处理 req 会有一定的负载,导致 lvs 监测到 rs 无法响应的状态。
通过监控可以发现,每个整点的 load 都会变大:

从上图可以发现,整点的这一小段时间,load 至少会超过 1,跟 24 核的机器比,似乎非常的小,但是需要考虑的一点是,我们的采集 load 的监控是 60s 一次,监控实现秒级的成本太大,实际在 60s 内系统的负载会比上图的大得多。
为了验证这个问题,手动的模拟一次 logrotate(gzip) 的操作,平均一小时的 log 量在 5G 左右。下面的是手动执行一次 logroate 操作得到的监控数据,可以看到高峰时段,这块普通的 SAS 盘的 io util 达到了 100%,磁盘写吞吐达到了 245M/s,load 峰值超过 10:
04:06:00 CST       DEV       tps  rd_sec/s  wr_sec/s  avgrq-sz  avgqu-sz     await     svctm     %util
04:06:01 CST    dev8-0    702.06     24.74 711958.76   1014.13    145.91    208.25      1.47    103.20
04:06:02 CST    dev8-0    701.01      8.08 704840.40   1005.48    146.79    208.00      1.44    101.01
04:06:03 CST    dev8-0    677.55      0.00 681755.10   1006.20    144.27    213.12      1.51    102.04
04:06:04 CST    dev8-0    672.45      8.16 681608.16   1013.63    141.47    210.12      1.52    102.04
04:06:05 CST    dev8-0   1364.65      0.00 694884.85    509.21    122.71     91.27      0.74    100.91

这总算是找到 lvs 频发 add, remove 的原因了,接下来改进就相对比较简单了:
1. lvs 对 ngx 不要太敏感,这个在 timeout 的一些设置上适当的加大可以缓解
2. ngx 的 log 处理问题,最初有同学提议只开一台 rs 的log,其他的全部关闭,这个看上去似乎可行,开一台基本可以代表其他 rs 的情况,但是实际情况可能并非我们所能想像,如果没有开启 access 的机器出现了问题而开启的每有任何问题,后续的问题排查将会比较的麻烦。所以,不管如何,保留一分全量的 log 还是非常有必要的。对此,比较通用的做法将 Nginx 通过 syslog 直接传到的远端的机器上,本地保留一分全量的,但是不开启 logrotate 的 gzip 压缩。
3. 除此之外,还可以考虑通过 access_log, error_log 的 ratio 来控制产生 log 的比例

Nginx open file 的问题
这个问题到此结束,这样安稳的过了四天,某天晚上 messages 里面发现 keepalived 频繁的 add, remove rs 了,这次时间不是整点了,虽然只出现了一两次,但是可以暴露出整个集群还是处于一个不稳定的状态,最初我们认为是 TCP_CHECK 的检测机制有问题,太过敏感,于是换成了 HTTP_GET 的方式,但是第二天仍然时不时的报错,并且从时间上来看完全没有规律:
Sep 10 17:15:19 lvs-2-2399 Keepalived_healthcheckers[34943]: MD5 digest error to [192.168.90.6]:80 url[/], MD5SUM [f9943d27e3420479271984e01dfe85dd].
Sep 10 17:15:19 lvs-2-2399 Keepalived_healthcheckers[34943]: Removing service [192.168.90.6]:80 from VS [124.251.33.4]:80
Sep 10 17:15:22 lvs-2-2399 Keepalived_healthcheckers[34943]: MD5 digest success to [192.168.90.6]:80 url(1).
Sep 10 17:15:28 lvs-2-2399 Keepalived_healthcheckers[34943]: Remote Web server [192.168.90.6]:80 succeed on service.
根据 keepalived_healthcheckers 检测的逻辑来看,肯定是发现 http://192.168.90.6/ 返回的不是既定的 md5sum 值,才触发了 remove 操作,很可能 rs 返回的不是 200 的错误才导致的。
抓包,为此蹲守了很长的时间抓住了重现的机会:

果真,确实是 rs 返回了 500 的错误才导致被 remove 的。之前虽然 5xx, 4xx 会增大,但是几乎并不影响 200 的数量,这次却不是这样的情况,500 的增多,200 的却异常下跌。全量分析 err log,惊人的发现,这段时间内的错误条目是平时的 100 倍,并且,出现奇怪的出现了下面的错误:
2014/09/06 20:09:55 [alert] 3164#0: *4082964565 socket() failed (24: Too many open files) while connecting to upstream, client: 122.93.211.105, server: foo.example.com, request: "POST /fk HTTP/1.1", upstream: "http://192.168.90.53:8888/fk", host: "foo.example.com"

一查 /proc 下面的 limits,Ngx 的竟然变成了 1024:

看到 1024 的那一刹那突然想起了我们之前某个产品线同样的出现过一次这个文档,当时尝试了各种办法包括直接修改 limits 文件也无法提高 open file,后来直接 reboot 机器了(有自动的 failover 策略),恢复,这事后来就被慢慢淡忘了,但是真相依然不清楚。结果,这次墨菲又找上门来了。后来我们发现还是有些用户(1, 2)  出现了跟我们类似的情况,但是依然不清楚原因。
为了确认就是 open file 造成了,对比一下 5xx(too many open file) 跟非 200 错误的比例:

完全符合。
真正的原因我也没法说清楚,基本可以肯定的是在 Nginx 运行过程中 open file 被改成了 1024。不过现在应该可以完全避免这个问题了,除了通过 worker_rlimit_nofile 指定之外,我们把这些进程的 limits 文件全部监控起来,包括 fd 的数量,这样即使再次「诡异」的变成了 1024,我们也能及时的发现采取措施。

kernel panic 的一些问题

机器稳定的运行了几天之后,某天早上 master 突然 kernel panic 了。
能得到的信息非常少,接下来继续挖坟似的分析 messages log。5min 打出的几百条 log 一条一条的对着 timestamp 分析,从 log 看,slave 是成功切换了,在 master 起来之后,流量又逐渐的恢复到了 master 上,但是至此之后,rs 上的流量由于 master 出现了问题。开始 master 看上去像是 work 的,inacctive conn 开始恢复,但是 active conn 一直到 5min 之后才开始恢复,这期间发生了什么,lvs 的 log 并没有很详细的说明,看上去像是两台 lvs 都没有工作,slave 变成了 backup 自然不再工作,挂掉的那台 master 虽然起来之后进入了 master 并且也 enable 了两台 rs,但是正常的流量却没有过来。
出现上面这种情况的可能原因是什么?keepalived 的配置出现问题了,果真,没有指明 priority。这个问题复现还算比较容易,直接把 master 的搞 kernel panic 就可以出现跟生产环境一样的现象,严格指定 priority 之后则不会出现这么长时间内无人接收请求的情况。
拿来主义是非常可怕的,当时我没有 review 下 keepalived 的配置就直接开跑了,核心服务还是要亲自验证。
除了上面这个教训之外,既然是 HA,一定要自己做一些破坏性实验,验证各种可能的情况。真正上线之前不验证,及时发现问题,真的全量上了,到时候出现问题的影响更大。我们虽然在小流量切换时,也做了一些破坏性实验验证 HA,但是并没有缜密的把所有情况都列穷举出来。

上面仅仅解决了 keepalived 的一个问题,kernel panic 似乎没有引起我们的注意,之后几天接连的出现 kernel panic,我们才意识到,这是个非常严重的问题,虽然每次 kernel panic 都完全的切换成功了。想到现在 lvs 使用的 kernel 跟官方唯一不一样的地方就是我自己重新编译了 kernel,调大了 IP_VS_TAB_BITS。既然这么一个小的改动都会引发 kernel panic,那索性还是先切回官方的版本,毕竟我们现在线上 90% 的机器都已经成功的跑了很长时间了。没想到的是,即使使用了官方的版本,依然不停的 panic,后来发现是其实是个 bug,在给 ipvs 的连接做 hash&unhashing 的时候缺少对被修改连接的锁,导致内存奔溃。理论上,我们可以对目前的 src 打 patch,但是实际的效果不得而知,加上没有之前的成功案例以及团队缺少精通内核的人,不得放弃这个想法。后来想到了我们兄弟团队,在多方求助之后,终于用上了高大上的集团内核,至此内核运行的很稳定。


4xx 问题的最终调查
还记得最上面第一次出现的 4xx 飙升问题吧,从某日起,4xx 的错误又明显的增大,通过对历史数据的分析发现增多的 4xx 错误主要集中在 499,随着高峰的来临(白天工作时间至凌晨),499 的错误也显著性的增加:

再次回顾下 499, 408 的问题。
499 实际上是客户端先关连接的,原因很多(无法确定,客户端引起),比如:
1. 刷页面的时候连按了 2 次 F5,就会导致之前页面上的请求没有收到完的就 499
2. 网络太慢
3. ngx 刚转发请求往后端,后端还没来得及应答,前端就主动关连接了

408 问题的解释:
1. 408 是客户端连完整的请求也没有传完,从 access log 里面没每发现 ua,正常的上传应该是有 ua 的,所以这个问题很可能是由于网络问题造成的
2. 408 的复现也比较简单,发送 POST /app_log HTTP/1.1 给 ngx,不发数据,过 60s 就会返回 408 的错误

除了 4xx 错误的飙升之外,upstream java 的 fd 也会随之飙升,从上面的一些数据至少可以得到下面的一些结论:
1. 本身移动网络的质量相比会比较差,再加上刚切换完双线,有一定 4xx 的增加还是可以理解的
2. 正常的应用,比较稳定的 4xx 应该是比较正常的
3. 但是,我们的情况是连续数天的 4xx 频繁不稳定,所以,可以确定有问题

想到这段期间唯一的操作就是切换了流量,于是,直接回滚,全部切换到全线 BGP,观察了一天没有任何效果。在跟开发确认一些细节的时候发现,原来在 4xx 飙升的前一天,更新过一次代码,既然切回全线没效果,那继续回滚代码吧,结果大跌眼镜的是问题依然存在。

结合 upstream 发现,有大量的 CLOSE_WAIT  状态,并且 recv-Q 均为非 0:
# cat status
established
syn-sent
syn-recv
fin-wait-1
fin-wait-2
time-wait
closed
close-wait
last-ack
closing
# for i in $(cat status);  do echo -n  "$i: " && ss -o state $i src INTERNAL_IP | wc -l; done
# ss src INTERNAL_IP:PORT  | head


上面的非 0 字段表明,upstream 没能成功的接受发过来的请求将其放到网卡的 buffer 里面,导致其 queue 一直保持在非 0 状态。
问题分析到这里,基本可以确定是用户态的应用没法正确的接收请求才导致此类结果,再结合上面 return code 的分析以及 upstream 的分析:
1. 切换完双线之后,由于本身网络质量会有一定的下降,所有 4xx 的会增多,这个是可以容忍的
2. upstream 代码层面应该还存在一些问题,结合 fd 的监控,网上高峰 fd 可能会飙升到 100+K,是不是有可能 fd 没有及时的回收?(fd 泄漏有没有这种可能)
3. 出问题前一天更新过一次代码,在其他没有变化的情况下,从前端到后端的监控都有不同程度的异常,是不是上线的有问题

上面提到的 fd 飙升问题,既然飙升,那就在出问题的时候上去看看是哪些 fd 飙升,首先需要确认是确实是 java 的进程导致:
for x in `ps -eF| awk '{ print $2 }'`;do echo `ls /proc/$x/fd 2> /dev/null | wc -l` $x `cat /proc/$x/cmdline 2> /dev/null`;done | sort -n -r | head -n 20

在某天出问题的中午 12 点,我登录到出问题的机器上统计了 fd 的情况:
出问题的时间段 fd 在 ~40k,分解下来包含
* tcp estab: ~1.7k,跟正常的比没变化
* tcp close-wait: ~9k,这两者大概 10k
* 剩余的 30k 通过 lsof 观察全部是 can't identify protocol 的状态

而正常情况是:
* tcp estab: ~1.5k
* tcp close-wait: ~2k
* 剩余的一些是 can't xxx

也就是说出问题阶段,close-wait & can't identiy protocol 飙升了原来的数倍:
1. close-wait 的很好理解,跟之前的观点一样,tcp 一直在等待 user space 调用 close(),否则会一直处于 close-wait 状态
2. can't xxx 的暂时没法解释,等到下一次出问题的时候再观察

有这么多的 close wait,很容易让人联想到是 fd 泄漏,想关闭的一方发送 tcp fin,服务端收到之后,如果程序逻辑有问题,忘记调用 close(socketfd),则会造成 fd 泄漏,表象就是有大量的 close wait 状态。不过从监控来看,这部分的飙升的 fd 很快就会直线下降到正常值(后来分析 jvm 的时候发现其实是 mgc, fgc 导致的 fd 回收):

问题分析到这里,那就继续深入下去,从 JVM 层面看看有没有问题。
首先看看 java 的进程,不管是正常状态还是异常状态,某几个 thread 都消耗了大量的 cpu 以及 fd,也就是说目前的 thread 是非常不均匀的,某一个 thread 能消耗 50% 以上的 fd:

接下来,搞个脚本看看最耗资源的那个 thread dump 是什么样的,有了这个就可以很容易的看到某些方法的异常了。这我在前一篇博客已经总结过,这里假设我们已经得到了 TID,先转换成 hex 然后 for 循环就能找到真相了。
for i in {1..100}; do echo "—$(date +'%H:%M:%S')—" >> tiddump.log; LAB=$(date '+%s'); HEX=`echo "obase=16; $TID" |bc`; jstack {PID} >> ./jstack$LAB; grep -iA 20  $HEX  ./jstack$LAB –color >> tiddump.log; sleep5; done

到了这一步,真相大白了,从 log 里面发现某些方法调用常年的 hang 在那儿,源于系统在处理某个大 List,客户端的 SDK 层面逻辑判断有问题,不做检查的把一些巨型字段经过压缩都发到前端这边来,结果到了后端解压缩处理的时候发现处理不了。

如果依然发现不了问题了?其实这时候问题的范围已经被缩的很小了,除了继续通过 mat、btrace、perf 深层次的跟踪服务情况之外,还有个重要的方面遗漏了,确认我们使用的一些软件的版本有无 bug,不要以为那些 release log, changelog, readme 文档是写着玩的,开发没养成看这些细节文档的习惯如果 如果我们一线运维也不看,那最坏的结果就是折腾了十来天最后看到最新的发型版本的 release log 出现了一些特殊的 bugfix 字段以及最后的欲哭无泪。
最初我们怀疑是服务在关闭连接的处理逻辑上有问题,后来发现我们的代码里面并不涉及这方面的逻辑处理,连接的建立关闭全部由 Finagle 框架自己处理了,所以一个重要的疑点是我们使用的该框架的版本是否存在连接方面的 bug,查一下他的下一个以及下几个版本的 release log 很快就能确认。

以上分析的这些案例,虽然影响没有大到会让业务遭受严重的损失,但是不夸张的说,每一个都是一个重量级的定时炸弹,不及时排除,指不定哪天全线就都挂了。这些事件,可能绝大多数的公司都不会遇到,但是思考问题的思路方向比这些案例重要的多,拿这次来说,其实思路在一开始就跑偏了,在确认前端没有问题的情况下还在花了大量的时间而不是一开始就从 upstream 下手。

ps(top) cheatsheet

下面这些操作平时可能不大会用到,但是关键时候却能大显身手,大部分跟 thead 有关。

展开 tid
htop -> F2
ps -p PID -L -o pid,tid,psr,pcpu


特定的 pid, tid 跑在哪些 core 上
1. top -H -p {PID} -> f -> j
2. /proc/{PID}/stat -> 第三列: running/stop,倒数第六列: core


通过 -o 指定需要获取的条目:
ps -eLf/ps aux -L
ps -eLo pid,ppid,lwp,nlwp,osz,rss,ruser,pcpu,stime,etime,args | more
ps -eLo pcpu,args | grep java | sort -k1 -r | awk '{print $1}' | head -n 1
ps ax –no-headings -o user,pid,%cpu,%mem,vsz,sgi_rss
ps axo "user=%u, pid=%p, command line args=%a, elapsed time=%t"
ps axo "Application: %c | CommandlineArgs: %a | PercentageCpu: %C | User: %U | VirtualMemory: %z" –sort vsize  | tail

上面的说了这么多,其实是为了解决下面这个问题,找出 java 中最耗 cpu 的 tid,jstack 追踪。要找出这 tid 方式也有很多种,top,ps 都可以实现。

top -H |head -n 10|grep java|awk '{print $1}'|head -n 1 |sed -r 's/[^0-9].*m//g'
主要注意的是 top -H 直接 grep 出来的有乱码,所以需要通过 sed 处理下,为了这个问题还花了点时间,最后把结果 pipe 到文件,vim 打开才发现了问题。

ps -eLo pcpu,args | grep java | sort -k1 -r | awk '{print $1}' | head -n 1
ps p {PID} -L -o time,pid,tid,pcpu,tname,stat,psr | sort -n -k1 –r
ps 做法会好的多处理起来也更漂亮。

ref:
http://stackoverflow.com/questions/1519196/finding-usage-of-resources-cpu-and-memory-by-threads-of-a-process-in-unix-sol
http://stackoverflow.com/questions/8032372/how-can-i-see-which-cpu-core-a-thread-is-running-in
http://stackoverflow.com/questions/3342889/how-to-measure-separate-cpu-core-usage-for-a-process

http://stackoverflow.com/questions/8032372/how-can-i-see-which-cpu-core-a-thread-is-running-in

换了套新工作环境

一年折腾一次,一次折腾两天。
花了点时间上了套四屏幕的工作环境。之前的工作环境是:
* 一台 mba,基本用来拿在手上到处跑。
* 一台台式机,Arch,还是 3.4 的 kernel,看 /lost+found/ 还是 12 年 2 月分的,工作主力,印象中是自费笔记本给公司打了大半年工之后给配置的。
* 台式机左边是另一台台式机,配置跟上面的类似,主要用来看监控,看图。使用时间跟上面一样。

后来随着要盯的图越来越多,要开的屏幕越来越多,两台 Arch/Awesome 已经无法满足需求了,尽管一台 Arch 默认可以切换 9 个屏幕,但是切来切去依然是是很不方面。
于是搞了个四屏的架子&一个四屏的显卡。架子型号是 BEWISER S4;四屏的显卡是 Nvidia quadro nvs

下图是新的环境。

把四台显示器上架捣鼓捣鼓一个多小时,显卡的 setup 绕了些弯。
最初听说是不支持 Linux,心想既然看看监控也就忍了,真的上上去了之后发现 Win7 太难用了。一块屏幕下面没法继续分屏,即使人肉把两个浏览器堆在一个屏幕上,由于浏览器乱七八糟导航栏之类的存在还是会浪费非常多的空间。即使他们可以手动去除,但是,用过平铺窗口管理器的都知道,他们离心目中想要的样子还是差很远。
于是尝试用目前服役的 Arch 来安装这块多屏显卡,结果手贱在升级之前 Syu 了下,整个系统都玩挂了,不想折腾了,确定 Ubuntu 可以安装 awesome 之后,直接搞了台 12.04 的机器过来。
安装 NVIDIA-Linux-x86_64-340.46.run 这个驱动,安装之前要关闭一切的 X,具体的安装过程看这几篇文档就好了(1, 2)。 

如果最后没成功,按照他的提示基本就是要么显卡没插系统没检测到,要么就是系统有其他不兼容的驱动存在等等之类问题。
nvidia.ko 生成了之后,按照我这边的情况是没发使用的,我的 master screen(四个显卡口的第一口)系统起起来之后一直是 Ubuntu 的紫色屏幕,后来估计是 xorg.conf 的问题,一看果真是。
xorg.conf 的书写还蛮麻烦的,需要先熟悉下语法。另外务必写对 BusID,否则再怎么折腾都是起不来的。
Nvidia 自带的 nvidia-setting 不是很好用,我试了几次没成功就直接改 xorg.conf 文件了,理论上 nvidia-setting 的所有变更都会反映到 xorg.conf 里面。
折腾到这里就差不多了,Ubuntu 该装什么就装什么,改成 awesome 的,外表看上去跟 Arch 差别不大, 目前的是:
* 左边一台继承自之前老的纯 Arch,已经不大用了,准备还给 IT 了。
* 中间四台屏幕由右下角的主机带着,主力工作环境,Ubuntu 12.04+Awesome 管理器,几乎所有的工作都在上面进行。上方的两块用来看我们核心业务的 dashboard,zabbix 的监控会在另外的一个桌面上,左下角的主要是浏览器,firefox 全局 sock proxy,chromium 的不开 proxy,右下角的写代码登录线上机器。OpenVPN,anyconnect,synergys,sock proxy 全部在右下角的另外一个桌面上。
* 右边的 mba 依然是打下手的工作。之前是把 anyconnect 放在这台机器上的,后来发现 Ubuntu 可以通过开启 OpenVPN 以及 anyconnect(anyconnect 真是个恶心的玩意儿,给你强行推送路由表,给你强行修改 iptables。推就推了,还把整个 10/8 的全给推过来了,要是你的内网也是这个段的,那是彻底废了),mac 现在除了开会几乎不用了,不过用 preview 看 pdf 倒是蛮爽了,尤其是高亮操作,Linux 下目前没有一款软件能真正的实现此类功能。
* 全局通过 synergys 以及一个 thinkpad 键盘控制所有的屏幕操作。
新安装的 chromium 好像没法在浏览器里面设置 socks5,不过可以通过命令行直接启动。一套好的做工环境对效率的提升还是非常明显的。
我这次可没黑苹果,别再发生什么口水战了。

家用路由器被入侵了

某天上网浏览网页,半天都返回不了结果,OS X 虽然不怎么样,Firefox/Chrome 浏览个网页应该没什么大问题。
最初以为是浏览器的问题,直接重启,无效;重启 Mac,继续无效。好吧,既然重启都不生效,索性咱们从专业的角度来分析分析问题。
抓包。结果令人惊讶。

我的机器主动给路由器(192.168.2.1)发送了大量的 RST 包,在没有任何主动 SYN 的情况下。第一反应就是受到中间人攻击了。
登到路由器后台一看,果真有几个不认识的客户加到了这个 SSID 里面。
后续的重启改密码就不说了,虽然用的是 WPA2 的加密方式,但是密码太弱智了,花几分钟暴力破解一下应该都能出来,引以为戒。

后续留几个连接大家可以参考参考。
如何通过 Sequence Number Predictionl 的方式来进行 MITM 攻击,手法跟我遇到的类似。

出现 TCP rst 的几种可能:
1.1. Where do resets come from? (No, the stork does not bring them.)
1.2. RST flag at end of TCP transmission 
1.3. What causes a TCP/IP reset (RST) flag to be sent? 

2.1. Why TCP Reset sent after receive [FIN,ACK] Packet? 
2.2. TCP option SO_LINGER (zero) – when it's required

3.1. Windows sends RST after SYN-ACK on a TCP connection
3.2. RST after SYN-ACK
3.3. Why on receiving SYN/ACK, a RST packet is sent with certain sites
3.4. why kernel sent RST to a remote TCP server after the machine receiving a SYN/ACK packet?
3.5. why do Client send a RST packet during TCP handshake? [closed]
3.6. Why would a client send a RST packet as reply to a SYN,ACK? 

4.1. TCP: Server sends [RST, ACK] immediately after receiving [SYN] from Client
4.2. Why do I see a RST, ACK packet instead of a RST packet? 

不要乱用 TCP ENC flag

前段时间处理了一个 case,现象很简单,同网络环境下的机器,绝大多数的机器都无法 curl 访问 example.com,仅有少部分的可以 curl 访问,并且他们的 mtr 的路径一模一样,机器的配置应该也有一样。
对比一下,可以访问的:
$ curl -IL "http://example.com:80/rest"
-v
* About to connect() to example.com port 80 (#0)
*   Trying example.com… connected
* Connected to example.com (example.com) port 80 (#0)
> HEAD /rest HTTP/1.1
> User-Agent: curl/7.19.7 (x86_64-redhat-linux-gnu) libcurl/7.19.7 NSS/3.15.3
> zlib/1.2.3 libidn/1.18 libssh2/1.4.2
> Host: example.com
> Accept: */*
>
< HTTP/1.1 200 OK
HTTP/1.1 200 OK
< Server: nginx
Server: nginx
< Date: Fri, 24 Oct 2014 07:13:23 GMT
Date: Fri, 24 Oct 2014 07:13:23 GMT
< Content-Type: application/json;charset=UTF-8
Content-Type: application/json;charset=UTF-8
< Content-Length: 0
Content-Length: 0
< Connection: keep-alive
Connection: keep-alive
< s-rt: 2
s-rt: 2

<
* Connection #0 to host example.com left intact
* Closing connection #0

访问超时的:
curl -IL "http://example.com:80/rest" -v
* About to connect() to example.com port 80 (#0)
*   Trying example.com… Connection timed out
* couldn't connect to host
* Closing connection #0
curl: (7) couldn't connect to host

最初以为是 example.com(泛指) 的问题,后来联系了下我们的服务方,确认没有问题。当时想到一种可能是否是由于 SNAT 出口的 timestamps, tcp recycle 的时间戳问题引起的,到 nat 机器上看了下,并不存在这个问题。
直接抓包,惊奇的发现,根据经验,第一次的 TCP 连接,9bit 的 Flags 区域只应该有 SYN 标识位是被设置的,但是连不上的机器的 flags 竟然是 SYN, ECN, CWR 三个 bit 都被设置了。

看了下 wiki 对 ECN 的解释,以及 stackexchange 的实践,原因就很明显了,这条经过的 routing 上肯定有不支持 ECN 的 router,收到带有 ECN flag 的包之后就直接丢弃了,
可以访问的那部分机器由于 /proc/sys/net/ipv4/tcp_ecn 的默认值 2,可以接受带有 ECN 的包,但是不会主动发送,其余的机器由于明确的设置为了 1,这些包到从目的地返回源的过程中被中间 router 丢弃了。
2000 的统计显示,全球有 8% 的网络不可达跟 ECN 有关系。十多年过去了,理论上讲,应该有所改善。

ipvs 频繁 add/remove 后端 rs 问题追查

这个问题我们三年前就遇到过,限于当时的资源,没能分析出具体的原因。欠下的债肯定是要还的,最近又遇到了,不过解决起来倒是很快。
看 messages,发现有不少类似的 log:
Mar  6 15:00:11 lvs-1 Keepalived_healthcheckers[107592]: TCP connection to [192.168.90.5]:80 failed !!!
Mar  6 15:00:11 lvs-1 Keepalived_healthcheckers[107592]: Removing service [192.168.90.5]:80 from VS [111.111.111.4]:80
Mar  6 15:00:17 lvs-1 Keepalived_healthcheckers[107592]: TCP connection to [192.168.90.5]:80 success.
Mar  6 15:00:17 lvs-1 Keepalived_healthcheckers[107592]: Adding service [192.168.90.5]:80 to VS [111.111.111.4]:80
Mar  6 20:00:16 lvs-1 Keepalived_healthcheckers[107592]: TCP connection to [192.168.90.5]:80 failed !!!
Mar  6 20:00:16 lvs-1 Keepalived_healthcheckers[107592]: Removing service [192.168.90.5]:80 from VS [111.111.111.4]:80
Mar  6 20:00:22 lvs-1 Keepalived_healthcheckers[107592]: TCP connection to [192.168.90.5]:80 success.
Mar  6 20:00:22 lvs-1 Keepalived_healthcheckers[107592]: Adding service [192.168.90.5]:80 to VS [111.111.111.4]:80

结合监控图,发现出现问题的时候都是在整点,并且正点的时候系统的 load 确实比较大,把历史的 sar 数据调出来发现正点的 IO 利用率都是在 100%,这一联想就只有 logrotate 在正点做 rotate 然后 zip 压缩了。模拟了一下发现可以复现该问题,剩下的就是解决问题了。

除了上面这个,我们还发现在非整点也会出现类似的 log
Mar 10 17:10:23 lvs-2 sshd[54526]: Accepted publickey for jaseywang from 192.168.10.254 port 56532 ssh2
Mar 10 17:15:19 lvs-2 Keepalived_healthcheckers[34943]: MD5 digest error to [192.168.90.5]:80 url[/], MD5SUM [f9943d27e3420479271984e01dfe85dd].
Mar 10 17:15:19 lvs-2 Keepalived_healthcheckers[34943]: Removing service [192.168.90.5]:80 from VS [111.111.111.4]:80
Mar 10 17:15:22 lvs-2 Keepalived_healthcheckers[34943]: MD5 digest success to [192.168.90.5]:80 url(1).
Mar 10 17:15:28 lvs-2 Keepalived_healthcheckers[34943]: Remote Web server [192.168.90.5]:80 succeed on service.


抓包发现 LVS 向 RS 发送 GET / HTTP/1.0 的请求, RS 却返回了 HTTP/1.1 500 Internal Server Error 的非 200 错误码,所以 LVS remove 掉这些 RS 确实是正常的逻辑:

同样从监控中看到,出问题的时间段由于频繁的切换导致了前端几乎不可用,在 5 点多这一时段 error log 的比平常大了 100 多倍,达到了 500M:

从上面的信息,全量分析了出问题阶段所有 err log 的类型,发现了惊人的答案,99% 以上的问题都源于 "too many open file" 的错误,/proc 一看,发现 Nginx 的确实变成了 1024:
# cat /proc/3148/limits
Limit                     Soft Limit           Hard Limit           Units     
Max cpu time              unlimited            unlimited            seconds   
Max file size             unlimited            unlimited            bytes     
Max data size             unlimited            unlimited            bytes     
Max stack size            10485760             unlimited            bytes      
Max core file size        0                    unlimited            bytes     
Max resident set          unlimited            unlimited            bytes     
Max processes             514947               514947               processes
Max open files            1024                 4096                 files     
Max locked memory         65536                65536                bytes     
Max address space         unlimited            unlimited            bytes      
Max file locks            unlimited            unlimited            locks     
Max pending signals       514947               514947               signals   
Max msgqueue size         819200               819200               bytes     
Max nice priority         0                    0                    
Max realtime priority     0                    0                    
Max realtime timeout      unlimited            unlimited            us      

root 用户的没有问题:
# ulimit  -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 514947
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 512000
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 10240
cpu time               (seconds, -t) unlimited
max user processes              (-u) 512000
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

不过为什么突然由原来的系统级别的数字变成了 1024,实在值得奇怪,该问题很早之前在我们一台 Nginx 的机器上同样遇到,记得当时 restart 了 Nginx 依然没有效果,不得已只能 reboot 机器恢复。Nginx limit 变成 1024 的问题,其他用户也遇到类似的问题:
* http://serverfault.com/questions/516802/too-many-open-files-with-nginx-cant-seem-to-raise-limit
* http://anothersysadmin.wordpress.com/2013/08/09/nginx-and-the-too-many-open-files-limit/

目前还无法得知为什么,但是找到了部分原因,要避免此类问题倒是很简单,强行指定 worker_rlimit_nofile 就好了,谨慎起见,把 /proc/xxx/ 下面重要的文件都给监控上。