如何部署一个稳定高效可扩展的前端

一切的工程都要从实用的角度出发,排除 GLSB 在外,目前主流的开放的前端无非就那几种:
1. 最简单的就是 DNS RR,上手很快,缺点也很明显
2. web server + keepalived,相对智能些,依然有缺点,没有 health checker
3. 目前主流的方案 LVS(ipvs) + keepalived + web server(Nginx/Tengine),如果规模比较大,可能还会涉及到 OSPF

前端的地位不言而喻,出问题大家都只能大眼瞪小眼,保证稳定是其首要目标。这里总结一些我们线上的通用问题(前期的 benchmark 就不说了),其基本原理不仅仅适用于前端,中间件、DB 同样适用,举一反三非常重要。

Tengine
在调研比较了 Tengine, Nginx 之后,我们还是决定使用 Tengine,主要原因之一是其添加了一些非常诱人的功能,再加上我们后面有大量的技术支持,所以不是很需要担心 Tengine 本身的问题。后来偶然发现,@chobits 同学在全职开发 Tengine,就更不要担心了,全程也拉他帮我们 debug 了不少问题,提供了不少非常实用的建议,再次感谢。
1. 其支持的 reqstat 模块可以实时的针对不同的 location、server 统计到当前的 return code,这个对于整个 web server 的监控是最重要的一环。
2. 其次,对 log 部分做了改进,支持 syslog/rsyslog,这样可以直接打到后端,方便后续的归档分析。
3. upstream 几个对应的模块可以更好的检测后端机器的健康状况,不过我们没实用这个 module,而是自己编译的 fair module。
4. 另外,还支持一致性 hash、session sticky 以及灰度发布类似 tcpcopy(不推荐使用)/gor(推荐使用) 的 httpcopy 等特性,总的来说,确实比较实用。

LVS/keepalived
这个没什么特别要说的,首先是模式的选择了,目前主流就是 NAT/FNAT,DR,前者更安全,后者在出现某些问题的是时候切换 DNS 比较方便。有点要特别强调的是,keepalived 的 syntax 问题,默认没有任何的检查,我们之前少写了一个配对的括号竟然也能起起来,不禁让人感叹一下,后来找到了一个叫 keepalived-check 的工具,小巧方便,真应该直接编译到 keepalived 里面。除此之外,还有针对 vim  syntax 高亮插件。

debug
该开 debug 的初期最好开起 debug,这样能发现很多的问题。
keepalived,最主要就是看 messages 文件
tegine,把 debug 的 module 编译进去就可以看到更详细的信息了,包括一些非常重要的系统调用,recv() 在哪里出问题等等,我们通过此信息发现了不少的 upstream 问题;其次就是 log format 的定义,尽量定义的详细些,同样的能发现不少问题,多看看 error log 以及 access log,通过实时的 reqstat 就能一步一步的细化。

网络
直接上的万兆,涉及的方面还比较多,可以看我之写的一系列文档,概括一下包含下面的一些内容:
1. 路由的添加,/etc/sysconfig/network-scripts/route-INTERFACE,别在 rc.local 里面加了,太土鳖了
2. 中断的绑定,万兆的都是多队列,这个不做其他的没任何意义
3. ring buffer 调整,GRO 关闭等等
4. kernel 方面的,下面会提到

版本&打包
1. 自己编译,添加一些需要的 module,比如 fair,记得开启 –debug 模式
2. 实用 SysV 来管理进程,统一由 /etc/ini.d/ 下面的脚本来管理

内核
肯定是要重新编译的(CONFIG_IP_VS_TAB_BITS=20),下面包含一些基本的 TCP 优化
1. rmem_max/wmem_max
2. rmem_default/wmem_default
3. tcp_rmem/tcp_wmem/tcp_mem
4. netdev_max_backlpg
5. somaxconn/nginx backlog
6. timestamp/tcp_reuse/tcp_recycle

监控到位
zabbix 的 item,要么通过 trigger 来体现,要么通过 graph 来体现,如果存在于第三者,就没有存在的必要了,很少会到 "Latest data" 里面去挖来挖去,你自己的写的说不定都会忘记。
1. 系统层面的不细说,但是像 DNS resolver, NTP, fork rate, CPU freq(BIOS), 10000M 速率双工, ethtool -S 下面的一些 discard/dropped 很可能会忽略掉,这些 item 其实是非常重要的,关键的时候能派上大用途
2. 基本的 ping、curl 监控,为了确保 LVS 到 RS 的基本连通行,ping、curl 必不可少,这个在出问题的时候能第一时间确定是否是小范围或者大面积的网络故障
3. LVS 相关的,包括进程,链接情况(ipvsadm),rs 的情况等等,这个通过 zbx LLD 还是很方便的
4. Nginx 基本的 req, conn,以及 return code,rt 等等,不少都是直接从 Tengine 获取到的
5. proc 下面的 fd, open file, proc,Nginx 的比较诡异,目前已经遇到至少三次 open file 由系统值变为 1024 的,这方面监控必不可少
6. 全国各地的请求监控,这个一般是第三方的服务来帮助发现问题了

log 处理
1. 目前是本地 logrotate 一份,关闭了 compress,避免短时间内 IO 过高 LVS 以为 RS 出问题了
2. 通过 syslog 远端到另外一台机器上做压缩存档
3. 除此之外,Tengine 可以抽样获取 log,这样对服务器的压力可以大大减小

整个过程就是不断发现问题,解决问题的过程,及时的记录 post-mortem,及时的 review 当前完成的状况,这样整个系统才会朝更好的方向发展。之前发现有同学写完 post-mortem 就扔在一边不管了,虽然写了改进措施,但是由于没有及时的更进,导致的没有真正的完成需要改进的工作,结果是相同的问题继续出现,这就失去了本身的意义了。题目的「稳定高效」全篇都在提。可扩展如果是 10G 或者 20G 以下,可以通过加 RS 的方式完成,当然在这之前需要确定前端网卡能否以 wire rate 抗过 10G 或者 20G 甚至 40G(bonding) 的量,尤其全是小包的极端情况。当然,一般这么大的量已经很少只通过一个出口完成了,一般 OSPF 结合多 uplink 或许能够支撑住这么大的量。
任何一个子系统子模块都可以遵循上面提到的的一些方面以及思路来完善。

读取 /proc/net/tcp 的问题

不愿意看过程的直接跳到本文的最后吧。
虽然很多时候我们知道,出来混要还这句话,但是,不得不承认的是,很多时候都是到病入膏肓的时候才来解决问题。比如下面要说的由 netstat 引起的问题。
如果经常处理一些连接数很多的机器就会发现,使用 netstat 查看当前状态,返回的结果会非常的慢,有时候可能 1m 都返回不了结果。比如下面这个最经典的命令:
$ time sudo netstat  -tunlp
Active Internet connections (only servers)

tcp        0      0 x.x.x.x:80              0.0.0.0:*               LISTEN      1278/nginx      

real    0m12.846s
user    0m0.300s
sys     0m12.490s

上面这个就花了 13s 来返回结果。通过 strace 跟踪发现,netstat 的 input 就是 /proc/net/tcp:
$ sudo strace netstat -tunlp

open("/proc/net/tcp", O_RDONLY)         = 3
read(3, "  sl  local_address rem_address "…, 4096) = 4050
read(3, "  26: A11A91D5:0010 5B248875:7E7"…, 4096) = 4050
read(3, "  53: A11A91D5:0010 EF1C0CAB:C7A"…, 4096) = 4050
read(3, "  80: A11A91D5:0010 4D1E8875:3AD"…, 4096) = 4050
read(3, " 107: A11A91D5:0010 6525CFDD:C1A"…, 4096) = 4050
read(3, " 134: A11A91D5:0010 C2EF53DF:82F"…, 4096) = 4050
read(3, " 161: A11A91D5:0010 62803571:585"…, 4096) = 4050

因此如果该文件的条目非常的多(10w+),经常要等几十秒得到结果也就很正常了。

很不幸的是,zabbix 使用的 net.tcp.listen 这个 item 调用的就是 /prco/net/tcp 这个文件,通过做匹配获取指定的端口,速度可想而知。 造成的结果是经常在 1min 内拿不到数据,造成报警。具体的代码可以到 src/libs/zbxsysinfo/linux/net.c 定义的 NET_TCP_LISTEN 函数去 grep 一下。

而很早之前,我就介绍过一篇使用 ss 来取代 netstat 的博客。跟 netstat 最大的不同就是读取的文件不同,因而速度也快的多。看 ss 的 man 可以发现,ss 调用的是 tcp_diag 模块,这个模块具体怎么操作的不是很清楚,我更关心的是他是如何提升效率的。要实现跟上面一样的效果,像下面这样就可以了:
$ sudo ss -lp | grep mongo
0      128                          *:27700                         *:* users:(("mongod",3187,10))
0      128                          *:27701                         *:* users:(("mongod",2050,13))
0      128                          *:27703                         *:* users:(("mongod",20278,12))
0      128                          *:27704                         *:* users:(("mongod",20379,13))
0      128                          *:27705                         *:* users:(("mongod",2099,12))
0      128                          *:27706                         *:* users:(("mongod",11363,12))
0      128                          *:27707                         *:* users:(("mongod",2293,12))
0      128                          *:28700                         *:* users:(("mongod",3187,11))
0      128                          *:27708                         *:* users:(("mongod",2428,12))
0      128                          *:27709                         *:* users:(("mongod",2442,13))
0      128                          *:28701                         *:* users:(("mongod",2050,14))
0      128                          *:27710                         *:* users:(("mongod",2779,12))
0      128                          *:28703                         *:* users:(("mongod",20278,14))

格式化不是很好,tr 处理一下就好了:
$ sudo ss -lp | tr -s ' ' '\t'

说了这么多,其实应到到实际上就是,zabbix 在监控有大量连接机器的时候,如果使用起原生的 net.tcp.listen 等涉及到调用 netstat 的 item,其实准确的说并没有调用 netstat,而是直接 fopen 的 /proc/net/tcp 文件,就会产生上面的问题。办法就是自己写套 template,换成调用 ss 的方式。除此之外,如果使用 LLD(low level discovery),同样的记得别用 netstat 发现监听的端口,改用 ss 既可,对应的脚本可以在这里找到。

结论:
不管什么时候,请使用 ss 而非 netstat 来查看目前系统的状态。

zabbix 性能方面的优化(一)

下面的操作主要集中在 zabbix server/proxy/agent 本身,涉及到 DB 的操作会在zabbix 性能方面的优化(二)记录。
其所有的来源是《abc of zabbix performace tuning》这个幻灯片。总结的很详细。

我这里就重点总结几个问题。
最终的目的是要使得 zabbix[queue] 足够的低,同时注意看看其他的 internal statisitcs。

最明显的是 zabbix_server.conf 文件里面各个 Start*  指令的调整。这个官方的文档说的也不是很清楚,需要自己慢慢实践调整,太大太小都会造成 queue 的飙升。需要针对目前的 hosts/items 的数量级别进行调整。

开启 DebugLevel 可以在 log 里面看到当前系统的状况。

能用 Zabbix agent (active) 的别用 Zabbix agent。

其他的参见那个幻灯片就好了。

munin 以及 zabbix 的聚合(aggregate)

这类需求应该是评估监控系统是否合适的一个重要因素了。
munin 虽然具备,不过实现起来并不是很智能,需要人肉书写。以 mongo 的为例,我们需要聚合分布在不同机器上的 instance 上的 shard 的状态。
在 munin.conf 里面 include 一个名叫 mongo 的目录专门用来做聚合。先定义涉及到的 host:
[dc_shard;jaseywang-db9]
    address 192.168.1.43
    use_node_name yes

[dc_shard;jaseywang-db10]
        address 192.168.1.83
        use_node_name yes

[dc_shard;DailyCounter]
        update no

接下来就是定义需要聚合的 item 了,这个需要每个 host 都有对应的 plugin,比如这里叫 mongo_ops_xxx,xxx 是起 REST 端口:
dc_shard.graph_title DailyCounter
dc_shard.graph_category DailyCounter
dc_shard.graph_total total
dc_shard.total.graph no
dc_shard.graph_order insert update delete getmore
dc_shard.insert.label insert
dc_shard.insert.sum \
  jaseywang-db9:mongo_ops_28701.insert \
  jaseywang-db10:mongo_ops_28711.insert \
  jaseywang-db10:mongo_ops_28716.insert
dc_shard.update.label update
dc_shard.update.sum \
  jaseywang-db9:mongo_ops_28701.update \
  jaseywang-db10:mongo_ops_28711.update \
  jaseywang-db10:mongo_ops_28716.update

有点需要注意的是,需要严格遵循其语法,该是句号的使用句号,使用冒号的用冒号。

zabbix 的相对比较简单,既然是需要聚合的,划分到同一个 hostgroup,接着就是用其原生支持的 "Zabbix aggragate" 这个 Type 就好了。建立好了 item,trigger、graph 之类的就都有了,还是很简单的。顺便说下 Cacti,只要熟悉 rrdtool,问题也很好解决。
 

zabbix 监控 ipvs(lvs) 运行状态

要监控 ipvs 的状态,无外乎从下面两个地方入手:
1. ipvsadm 命令
2. /proc/net/ip_vs* 下面的文件

其中 1 应该也是从 2 获取到的数据,因此比较直接的方式还是直接从 /proc 下面拿到数据。

虽然通过 low level discovery 可以自动的实现 rs 的增删减操作,但是仔细想想没有必要那么复杂,多定义几个 item 就可以替代上面的操作了。

同一时间只有会有一台 dr 进行工作,因此,完全可以在两台 dr 上部署一模一样的监控,最后通过 aggregate 相加起来。

以 ipvsadm 这个命令为例,要拿到当前 InActConn 总和,直接几个 pipe 就可以搞定:
# ipvsadm -L -n | grep Route | tr -s ' ' '\t'  | awk '{print $6}' | awk 'BEGIN{sum=0}{sum+=$1}END{print sum}'

而如果是通过读取文件来做,需要处理下进至问题,如果不愿意在这里处理,可以直接在新建 item 的时候,以 "hex" 做为 Data Type。以 ip_vs_stats 这个文件为例,主要就是 in/out/total 三个大的参数:
# cat /proc/net/ip_vs_stats
   Total Incoming Outgoing         Incoming         Outgoing
   Conns  Packets  Packets            Bytes            Bytes
     D53    11514        0           886EF0                0

 Conns/s   Pkts/s   Pkts/s          Bytes/s          Bytes/s
       2       24        0             11C7                0

下面这个就能拿到当前的 total/in/out 的 delta(conns, pkts, Bytes/s) 值了:
# tail -1 /proc/net/ip_vs_stats | /usr/bin/awk '{print strtonum("0x"$1), strtonum("0x"$2), strtonum("0x"$3), strtonum("0x"$4), strtonum("0x"$5)}'
0 11 0 1445 0

LVS tcp 的状态还是看 proc 下面的文件:
# grep -i es  /proc/net/ip_vs_conn | cut -d' ' -f8  | grep -v ^$ | uniq -c

至于其他的诸如每个 rs 上的连结数,当前的 rs 的个数,rs 的健康状况,dr 有没有被 keepalived failover 等问题,参照上面的思路,应该都很好解决。不过对于第一个,每个 rs 上的连接数,用 low level discovery 来完成会更优雅些,具体的做法可以参考我之前写的一个 mongo instance,只不过是把 port 变成了 ip:port 而已,其他的都没有大的变化。

这个(1, 2) 脚本也可以做类似的事情,不过需要开 SNMP,多一分服务多一份担心,最终还是没采用,而是自己写简单的脚本实现。

上面的 item 都写好了,定义 trigger、graph 是不是就很简单了,综合起来搞套 template 就具有通用性了。

ref:
http://www.austintek.com/LVS/LVS-HOWTO/HOWTO/LVS-HOWTO.monitoring_lvs.html

LSI 芯片的监控

主流的 x86 服务器基本都是 LSI 的 RAID 芯片,或者像 DELL/HP 那样做贴牌处理,扒了皮还是 LSI。
要监控 RAID 的状态,方法很多,以市场占有率最大的 DELL 为例,可以使用其提供的 openmanager snmp 来监控 controller 以及磁盘,openmanager 应该只是做了一层封装而已,里面整合了大量的底层管理工具,不过适用性有一定的局限,只能用在 DELL 的机器上。要监控 LSI 的产品,当然是直接调用他们自己提供的工具最为方便。对于 nagios 来说,有一套完整的工具来帮助诊断 。zabbix 的也差不多,基本的东西都一样,都是通过 LSI 提供的 megaraid 来获取信息。安装、基本的使用就不说了,前面的都有纪录。

很早的一片博客提到,如果想让 zabbix 用户执行某些只有 root 才能执行的命令,可以有下面两种方式:
1. 增加 AllowRoot=1
2. visudo:  zabbix  ALL=(ALL)      NOPASSWD:NOPASSWD:ALL
现在看来,只要将方案 2 稍加修改就会变的相对完美了,像下面这样,就可以精确的控制 zabbix 用户的权限问题了:
zabbix  ALL=(ALL) NOPASSWD:/opt/MegaRAID/MegaCli/MegaCli64

根据《MegaRAID SAS Software User Guide》这个文档的说明,PD 一共分为 8 个状态,LD 一共分为 5 个状态,这个可以通过 zabbix 的 mapping 表现出来。虽然官方分了这么多的类别,不过我们真正关心的是,最好 pd 的状态是 Online,LD 的状态是 Optimal,其他的所有状态都是我们不希望看到的。因此可以简单的将其分为 ok 跟 problem 两种状态,因为一旦出现了 problem,工程师肯定会登录到机器上进行进一步的情况确认。

下面我们就以简化的方式来进行磁盘的监控,如果希望添加 mapping 的功能,可以参考这篇博客进行。

了解了 LSI 芯片之后,会发现真正需要监控的主要包括 adp、pd、ld 这三个组件的状态:
# MegaCli64 -adpallinfo -aALL
# MegaCli64 -PDList -aALL
# MegaCli64 -ldinfo -Lall -aALL

这个脚本的基础上做了些简单的修改,获取当前磁盘状态的脚本放在了这里

接下来就是定义 UserParameter:
UserParameter=lsi.raid.batterystate,/etc/zabbix/scripts/check_raid.sh batterystate
UserParameter=lsi.raid.isSOHGood,/etc/zabbix/scripts/check_raid.sh isSOHGood
UserParameter=lsi.raid.adapter,/etc/zabbix/scripts/check_raid.sh adapter
UserParameter=lsi.raid.mediaerrors,/etc/zabbix/scripts/check_raid.sh mediaerrors
UserParameter=lsi.raid.otherrrors,/etc/zabbix/scripts/check_raid.sh otherrrors
UserParameter=lsi.raid.raiderrors,/etc/zabbix/scripts/check_raid.sh raiderrors
UserParameter=lsi.raid.alarm,/etc/zabbix/scripts/check_raid.sh alarm

这里还有个简易的 megaraid 的监控程序,对于只有几台机器的公司完全适用。