Linux TCP keepalive 计时器是错误的?

ss(1) 加上 -o 参数可以打印 tcp 连接的 timer 信息,例如:

[root@VM-0-11-tlinux ~]# ss -ntpo 'sport = 80'
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 172.16.0.11:80 183.14.31.93:25144 users:(("socat",pid=10624,fd=6)) timer:(keepalive,1min32sec,0)

这是一个用 socat 设定了 120s 空闲(keepalive 的 idle)时间的连接,可以看到它正确打印了 timer 信息,执行 ss(1) 时候 timer 超时时间只剩下 92s 了。

问题:我们在连接的一端发送数据后,预期是 timer 应该把超时时间重制为 120s 开始倒数计算,然而实际并不是这样。客户端发送数据后,我们看下 timer:

[root@VM-0-11-tlinux ~]# ss -ntpo 'sport = 80'
State Recv-Q Send-Q Local Address:Port Peer Address:Port
ESTAB 0 0 172.16.0.11:80 183.14.31.93:25144 users:(("socat",pid=10624,fd=6)) timer:(keepalive,1min28sec,0)

ss(1) 仍然在继续倒数连接空闲超时时间。但是当我们实际去抓包的时候,可以发现确实是以接收到数据为起点 120s 之后才会发起探测的,所以 keepalive 这个机制并没有错,错的是这个 timer 展示。应该是为了减少计算量而采取这种“惰性”展示方式,具体可以参考:https://github.com/torvalds/linux/blob/master/net/ipv4/tcp_timer.c#L662

CentOS 的默认最大 pid

最近发现新上架的几个服务器(CentOS 6)上面跑的进程的 pid 有些不一样,它们要比原来一些旧的环境上面的 pid 要大很多。看了下系统最大允许的 pid 值:

[root@whereami ~]# cat /proc/sys/kernel/pid_max
196608

这比旧系统上面的 32768 要大很多。

查了一下原因,是这样的:

The kernel begins with a default pid_max value of 32768, and then will adjust it upward based on the number of possible CPUs the system could have. It will use a value of 1024 * num_possible_cpus if that value is larger than the default. The value the kernel will use for pid_max is capped at approximately 4 million.

https://access.redhat.com/solutions/874023

也就是说,在一个系统上面,pid_max 是根据可能的 CPU 个数来确定的(不是实际存在的个数)。具体规则就是可能 CPU 个数乘以 1024。不过为了防止可能 CPU 个数较小的环境上面 pid 很快被用尽,最小的 pid_max 都是从 32768 开始的。

看了一下新服务器的可能 CPU 个数是 192,乘以 1024,刚好就是 196608 了。

ZSH 也不完美

以前有段时间在命令行上折腾过不少时间,接触过 ZSH, Fish 等不同的 Shell,但是后来不玩了,因为 bash-completion 通常来说已经够用了。但是最近发现已经有人弄了个 Oh My Zsh 这样的项目,一些主题还是很不错的,于是重新设置了默认 shell 为 zsh 了。

但是 zsh 也不完美。

➜  ~ rsync -e ssh -avzP 198.1x.x.x:/home/luo/*.deb .
zsh: no matches found: 198.1x.x.x:/home/luo/*.deb

只能切换回去 bash 执行。

使用 Kapacitor 对 InfluxDB 数据进行统计处理

InfluxDB + Telegraf + Grafana 算是一套不错的数据收集、视觉化工具,相比于更为常见的 ELK 架构,它有着自己的特点。查询功能总得来说虽不如 ES 强大,但是优点是——已经够用了,而且它的性能不错,存储的数据占用空间也相比 ES 小。

简单介绍这三个工具:

  • InfluxDB InfluxData 的一款时序数据库,它是这套工具的核心;
  • Telegraf 则是 InfluxData 自己开发的一个数据收集工具,相比于 Logstash 或者 Mozilla 的 Heka,它有了更多的内置实用 input 插件,例如 sysstat, Mongodb, MySQL, 日志文件 grok 和跟踪 … 使用 Telegraf 可以非常容易的监控起系统和各种常见服务的各种参数,基本无需再安装其它专用工具。
  • Grafana 则是一个数据视觉化工具(“画图”的),支持多种数据后端,包括 InfluxDB,Elasticsearch,Zabbix 等。

实际上,InfluxData 是有一个自己的 TICK 架构的(Telegraf + InfluxDB + Chronograf + Kapacitor),其中 Chronograf  是用来画图的,但是看起来似乎还不成熟,不如使用 Grafana 好。Kapacitor 则是一个专门处理 InfluxDB 里面的数据的工具,用它可以对收集到的数据进行统计、触发报警等。。。

好了,说了这么多,其实是想借一个实例说下怎样用 InfluxData 的 Kapacitor 来对 InfluxDB 的数据进行加工处理。

假设:已经搭建好了 InfluxDB + Telegraf + Grafana 这套工具,web 日志收集存储在了 InfluxDB,我们想查看一些统计数值,比如每日独立访问 IP 这样的有价值的数据,我们现在该怎么做?

一般来说,有以下几个选项:

  1. 在 Grafana 里面,我们新建一个图表,每次查看时实时统计出所有的每日独立 IP。但是这个方法实在不妥,web 日志量很大,如果每次一查看那个图表就把 InfluxDB 里面的数据全部过一遍,还要计算、分组,实在太低效,性能太糟糕。对于每日独立 IP 这种数据来说,我们其实并不需要它是实时的,只要每天统计一次即可。
  2. 我们写个 Shell 或者强大一点,Python 脚本去从 InfluxDB 查询出独立 IP 数,再把结果存回 InfluxDB,再把它放到 crontab 里面每天运行,然后 Grafana 建立的图表可以直接查这个新的 measurement。这种方法远比第一种要好很多,但是仍然是需要写脚本的。
  3. 我们可以利用专门的工具——Kapacitor 来做这件事。下面就详细说下如何取每日独立 IP 数。

按照官方的简介,Kapacitor 是一款“Time-Series Data Processing, Alerting and Anomaly Detection”,也就是说——数据处理、报警、异常探测它都能做。

Kapacitor 的安装、配置都很简单,推荐从官网下载 rpm/deb 包安装,然后修改 /etc/kapacitor/kapacitor.conf 文件,在 InfluxDB 连接配置里面设置好账号、密码,就可以启动 kapacitor 服务了。

Kapacitor 有一个后台服务,用户需要做的事就是编写 TICKscript 脚本,然后把这个脚本加载。

来看这个计算每日独立 IP 的脚本,ip.tick:

batch
    |query('''
        SELECT count(distinct(clientip)) AS uip
        FROM "telegraf"."retentionPolicy"."log_measurement"
    ''')
        .period(1d)
        .cron('0 0 * * *')
    |influxDBOut()
        .database('logstats')
        .measurement('site_uip')
        .tag('kapacitor', 'true')

其中,batch 指令表示这是一个对数据进行一次批量处理的任务; |query() 表示这是数据查询的处理节点,我们在 query() 里面直接写入 InfluxDB 的类SQL 查询语句即可,但并没有加入 WHERE 条件对查询时间进行限定,这是为何?因为后面的 .period() 指定了查询范围为一天;然后再利用 .cron() 这个属性,我们可以告诉 Kapacitor 每天零点都执行一次这个查询。|influxDBOut() 则是将查询出来的数据存入 InfluxDB,它的几个属性也比较好理解,不再详说。

保存文件后,执行以下命令:

$ kapacitor define log_uip -tick ip.tick -type batch \
   -dbrp telegraf.retentionPolicy
$ kapacitor enable log_uip

就定义和启用了这个 Kapacitor 任务。接下来还可以用 kapacitor show log_uip 或者 kapacitor list tasks 等命令查看任务状态。看到了吧,这样比脚本加 crontab 还是要简单不少的。(kapacitor 命令的参数含义可以用 help 子命令或者这个文档链接查看。)

在它运行过后,我们就可以在 Grafana 里面取 logstats 数据库里面 site_uip 的 uip 出来进行画图了。

总结:这篇文章只是用一实例展示了 Kapacitor 的功能之一,实际上 Kapacitor 的功能远不止这些,(报警等功能,因为我的环境早有这种功能,没有使用过。)tick 脚本的语法也比上面展示出来的部分强大很多,其数据处理节点也很丰富。总得来说,InfluxData 的这套 TICK 架构似乎还在快速发展的起步阶段(尤其是除 InfluxDB 外的那三部分),但是其实用性已经有一定吸引力了。

php-fpm chroot 迁移至 CentOS 7.2 版本的 segfault 问题

一直是在用 php-fpm 内置的 chroot 功能在跑手头的几个 php 论坛站点。
但是最近将整个 chroot 环境迁移至新的地方之后,出现问题了:
有一个论坛一直是返回 502 状态码。看了下,发现 dmesg 输出有一些信息:

...
[444346.942769] php-fpm[13096]: segfault at 1fae98fc ip 00007f53655429df sp 00007f5347847d80 error 6 in libresolv-2.17.so[7f5365537000+16000]
[444500.294150] php-fpm[13149]: segfault at 1fae98fc ip 00007f53655429df sp 00007f5347847d80 error 6 in libresolv-2.17.so[7f5365537000+16000]
[444500.407066] php-fpm[13151]: segfault at 1fae98fc ip 00007f53655429df sp 00007f5347c07d80 error 6 in libresolv-2.17.so[7f5365537000+16000]
...

chroot 里面的根目录结构是以前从 CentOS 7.1 拷贝创建的,看来和 7.2 不兼容。libresolv 里面发生了 segfault,说明问题应该是和 DNS 解析有关。
试了几次之后,把 7.2 的 libnss 相关 so 文件拷贝到 chroot 里面后,问题解决了。以防万一,接下来干脆把 chroot 里面的所有 so 更新了一次。

CentOS 7 上面 curl 的响应时间增大问题

前段时间一同事在问为甚么同样是 curl ,在 CentOS 7 上面抓取一个网页的域名解析延迟总是会多 150ms 左右?甚至在抓取 localhost 时候也是一样。具体说来,使用的命令是:

[root@centos7 ~]# curl -o /dev/null -s -w \
'%{time_namelookup}:%{time_connect}:%{time_starttransfer}: \
%{time_total}:%{speed_download}\n' http://localhost
0.150:0.151:0.151:0.151:47442.000

对比了一下 CentOS 6 上面的结果,确实差距很大

[root@centos6 ~]# curl -o /dev/null -s -w \
'%{time_namelookup}:%{time_connect}:%{time_starttransfer}: \
%{time_total}:%{speed_download}\n' http://localhost
0.001:0.001:0.001:0.001:114975.000

同事怀疑是 CentOS 7 的域名解析方式有变化,然而我换做用 wget 去抓发现 CentOS 7 延迟也是很低的,排除了这个可能。原因应该是出在 curl 上面。

排查方式:
用 ltrace 加上 -T 参数打印时间戳跟踪了一下,发现是 curl_easy_perform() 这个函数这里出了问题:

Google 了一番之后,发现这个问题其实已经有了修复,将 50ms, 100ms, 150ms… 这样的延迟增长替换为 4ms, 8ms, 16ms… 这样的了。只不过 CentOS 7 里面的版本较老,还没有加入这个补丁。

今天因为 RHEL 7.2 的发布,看了下 Changelog,也已经合入这个补丁了。
https://bugzilla.redhat.com/show_bug.cgi?id=1130239

另一个越来越不喜欢 Debian 的理由

Debian 现在就完全没有个稍微能快那么一丁点的 repo 了,http.debian.net 更是扯淡,直接给我返回的是奇慢无比的俄国站点。。。
都不知道该怎么说了,反正这点上面 yum 真的是比 apt 要好很多。

CentOS 7 修改 MariaDB 的最大打开文件数

记一下这个坑

最近将一个 php 环境迁移到了 CentOS 7,一开始一切都好,但是网站正式上线后一段时间却老是出现 php-fpm 进程繁忙的情况了。

刚开始以为是 php-fpm 哪里配错了,后来才发现是 MariaDB 的问题,日志里面一直在报达到了最大打开文件数。看了下,发现最大可以打开文件数是 1024,确实小了。

limits.conf 里面是早就把限制调高了的,所以在 /etc/my.cnf 里面加了一行

open-file-limits = 10240

结果重启后却还是 1024。

最终是在 /usr/lib/systemd/system/mariadb.service 里面找到了线索

# It's not recommended to modify this file in-place, because it will be
# overwritten during package upgrades.  If you want to customize, the
# best way is to create a file "/etc/systemd/system/mariadb.service",
# containing
#       .include /lib/systemd/system/mariadb.service
#       ...make your changes here...
# or create a file "/etc/systemd/system/mariadb.service.d/foo.conf",
# which doesn't need to include ".include" call and which will be parsed
# after the file mariadb.service itself is parsed.
#
# For more info about custom unit files, see systemd.unit(5) or
# http://fedoraproject.org/wiki/Systemd#How_do_I_customize_a_unit_file.2F_add_a_custom_unit_file.3F
# For example, if you want to increase mariadb's open-files-limit to 10000,
# you need to increase systemd's LimitNOFILE setting, so create a file named
# "/etc/systemd/system/mariadb.service.d/limits.conf" containing:
#       [Service]
#       LimitNOFILE=10000

需要由 systemd 来提高最大可以打开文件数。

于是按照提示创建了 /etc/systemd/system/mariadb.d/limits.conf 加入了

[Service]
LimitNOFILE=65535

结果发现还是老样子

最终,再把 /etc/my.cnf 里面的 open-file-limits = 10240 删除掉才行。。。

Linux 网络命名空间 (netns),如何进入隐藏的 netns

Linux 的网络命名空间 (netns) 是非常有用和好玩的功能,可以允许进程进入一个隔离的网络空间,在这里面可以安全地进行网络测试、iptables 策略测试等。

通常情况下,一般我们用 ip netns add 添加新的 netns,然后我们可以用 ip netns list 查看所有的 netns。但有的时候进程的 netns 却并没有显式导出,ip netns list 无法列出它,比如 docker 的 container 的网络空间。

也许我们还是需要进入到那个进程的 netns,怎么办呢?答案是做个符号链接就可以了。

ln -sf /proc/<pid>/ns/net /var/run/netns/$ns

简要说明一下这个命令:每个进程的网络命名空间都是通过 proc 文件系统导出来了的,位于 /proc/<pid>/ns/net (这个文件不可读,它只是相当于一个访问点);而 ip netns list 命令是从 /var/run/netns 这个路径读取 netns 列表的,因此直接将进程的命名空间链接到 /var/run/netns 目录下就可以了。

这之后,可以按照以往的 ip netns $ns exec /bin/bash 进去这个命名空间玩耍了。