咱们都知道,基于Kubernetes的微服务,大行其道,传统部署模式一直都在跟着变化,但其实,在原有业务向服务化方向过分过程当中,有些场景可能会变得复杂。 node
好比说:将Kubernetes的模式应用到开发环节上,这个环节须要频繁的变动代码,微服务的方式,可能就须要不断的:服务器
改代码->构建镜像->镜像推送->部署->拉去镜像->生成容器
尤为是PHP的业务,不须要构建二进制,仅须要发布代码,所以,若是按照上面的部署方式,就须要频繁改代码,走构建镜像这个流程,最后再作发布,这在开发环节就显得过于麻烦了,换而言之,有没有办法,能让开发直接将代码上传到容器中呢? 网络
实际上是有的,就是设计一个FTP中间件代理,让用户本地改完代码,经过FTP客户端(不少IDE是支持FTP的)直接上传到容器内部,甚至于用户保存一下代码就上传到容器内。 闭包
所以,这就引出了今天的主角,是我基于FTP协议+gRPC协议自研的FTP代理工具。 ssh
这个工具上线后,服务全公司全部研发,通过一段时间运行和修补,相对稳定,也作了一些关于内存方面的优化,直到又一次,在维护这个FTP代理的时候,发现一个奇怪的问题: socket
FTP代理进程,监听的是 192.168.88.32 的 21 端口,因此,这个端口对应了多少链接,就表示有多少个客户端存在,经过:tcp
netstat -apn |grep "192.168.88.32:21"
发现,有将近1000个连接,且都是 ESTABLISHED,ESTABLISHED 状态表示一个链接的状态是“已链接”,但咱们研发团队,并无那么多人,直觉上看,事出反常必有妖。函数
感受可能有一种状况,就是每一个人开了多个FTP客户端,实际场景下,研发同窗组可能会使用3种类型的FTP客户端微服务
PHPStorm:这个客户端(SFTP插件)本身会维护一个FTP长链接。
Sublime + VsCode,这2个客户端不会维护连接,数据交互完成(好比传输任务),就主动发送 QUIT 指令到FTP代理端,而后全部连接关闭。很干净。
另外,使用PHPStorm的话,也存在开多个IDE建立,就使用多个FTP客户端链接的状况。
为了继续排查,我把全部对 192.168.88.32:21 的连接,作了分组统计,看看哪一个IP的链接数最多工具
# 注:61604 是 ftp代理的进程ID netstat -apn|grep "61604/server"|grep '192.168.88.32:21'|awk -F ':' '{print$2}'|awk '{print$2}'|sort|uniq -c |sort
上面的统计,是看哪一个IP,对 192.168.88.32:21 链接数最多(18个)。
统计发现,不少IP,都存在多个连接的状况,难道每一个人都用了多个IDE且可能还多IDE窗口使用吗?因而,挑了一个最多的,找到公司中使用这个IP的人,沟通发现,他确实使用了IDE多窗口,可是远远没有使用18个客户端那么多,仅仅PHPStorm开了3个窗口而已。
初步排查结论:应该是FTP代理所在服务器的问题,和用户开多个客户端没有关系。
此次排查,是怀疑,这将近1000个的 ESTABLISHED 客户端连接中,有大量假的 ESTABLISHED 连接存在,以前的统计发现,实际上,对 192.168.88.32:21 的客户端连接进行筛选,获得的IP,一共才200个客户端IP而已,平均下来,每一个人都有5个FTP客户端连接FTP代理,想象以为不太可能。那么,如何排查 ESTABLISHED 假连接呢?
在 TCP 四次挥手过程当中,首先须要有一端,发起 FIN 包,接收方接受到 FIN 包以后,便开启四次挥手的过程,这也是链接断开的过程。
从以前的排查看,有人的IP,发起了多达18个FTP链接,那么,要排查是否是在 FTP 代理服务器上,存在假的 ESTABLISHED 链接的话,就首先须要去 开发同窗的机器上看,客户端链接的端口,是否是仍在使用。好比:
tcp ESTAB 0 0 192.168.88.32:21 192.168.67.38:58038
这个代表,有一个研发的同窗 IP是 192.168.67.38,使用了端口 58038,链接 192.168.88.32 上的 FTP 代理服务的 21 端口。因此,先要去看,到底研发同窗的电脑上,这个端口存在不存在。
后来通过与研发同窗沟通确认,研发电脑上并无 58038 端口使用,这说明,对FTP代理服务的的客户端连接中显示的端口,也就是实际用户的客户端端口,存在大量不存在的状况。
结论:FTP代理服务器上,存在的近1000个客户端链接中(ESTABLISHED状态),有大量的假链接存在。也就是说,实际上这个链接早就断开不存在了,但服务端却还显示存在。
首先,若是出现假的 ESTABLISHED 链接,表示链接的客户端已经不存在了,客户端一方,要么发起了 TCP FIN 请求服务端没有收到,好比由于网络的各类缘由(好比断网了)以后,FTP客户端没法发送FIN到服务端。要么服务端服务器接受到了 FIN,可是在后续过程当中,丢包了等等。
为了验证上面的问题,我本机进行了一次模拟,链接FTP服务端后,本机直接断网,断网后,杀死FTP客户端进程,等待5分钟(为何等待5分钟后面说)后,从新联网。而后再 FTP 服务端,查看服务器上与 FTP代理进行链接的全部IP,而后发现我本机的IP和端口依然在列,而后再我本机,经过
lsof -i :端口号
却没有任何记录,直接说明:服务端确实保持了假 ESTABLISHED 连接,一直不释放。
上面提到,我等待5分钟,是由于,服务端的 keepalive,是这样的配置:
[root@xx xx]# sysctl -a |grep keepalive net.ipv4.tcp_keepalive_intvl = 75 net.ipv4.tcp_keepalive_probes = 9 net.ipv4.tcp_keepalive_time = 300
服务器默认设置的 tcp keepalive 检测是300秒后进行检测,也就是5分钟,当检测失败后,一共进行9次重试,每次时间间隔是75秒。
那么,问题就来了,服务器设置了 keepalive,若是 300 + 9*75 秒后,依然链接不上,就应该主动关闭假 ESTABLISHED 链接才对。为什么还会积压呢?
为了验证这个问题,咱们就须要具体的看某个链接,何时建立的。因此,我找到其中一个我肯定是假的 ESTABLISHED的连接(那个IP的用户,把全部FTP客户端都关了,进程也杀死了),看此链接的建立时间,过程以下:
先肯定 FTP 代理进程的ID,为 61604
而后,看看这个进程的全部链接,找到某个端口的(55360,就是一个客户端所使用的端口)
[root@xxx xxx]# lsof -p 61604|grep 55360 server 61604 root 6u IPv4 336087732 0t0 TCP node088032:ftp->192.168.70.16:55360 (ESTABLISHED)
咱们看到一个 “6u”,这个就是进程使用的这个链接的socket文件,Linux中,一切皆文件。咱们看看这个文件的建立时间,就是这个链接的建立时间了
ll /proc/61604/fd/6 //输出: lrwx------. 1 root root 64 Nov 1 14:03 /proc/61604/fd/6 -> socket:[336087732]
这个链接是11月1号建立的,如今已经11月8号,这个时间,早已经超出了 keepalive 探测 TCP链接是否存活的时间。这说明2个点:
一、可能 Linux 的 KeepAlive 压根没生效。
二、可能个人 FTP 代理进程,压根没有使用 TCP KeepAlive
要验证这个结论,就得先知道,怎么看一个链接,到底具不具有 KeepAlive 功效?
netstat 命令很差使(也可能我没找到方法),咱们使用 ss 命令,查看 FTP进程下全部链接21端口的连接
ss -aoen|grep 192.168.12.32:21|grep ESTAB
从众多结果中,随便筛选2个结果:
tcp ESTAB 0 0 192.168.12.32:21 192.168.20.63:63677 ino:336879672 sk:65bb <-> tcp ESTAB 0 0 192.168.12.32:21 192.168.49.21:51896 ino:336960511 sk:67f7 <->
咱们再对比一下,全部链接服务器sshd进程的
tcp ESTAB 0 0 192.168.12.32:333 192.168.53.207:63269 timer:(keepalive,59sec,0) ino:336462258 sk:6435 <-> tcp ESTAB 0 0 192.168.12.32:333 192.168.55.185:64892 timer:(keepalive,3min59sec,0) ino:336461969 sk:62d1 <-> tcp ESTAB 0 0 192.168.12.32:333 192.168.53.207:63220 timer:(keepalive,28sec,0) ino:336486442 sk:6329 <-> tcp ESTAB 0 0 192.168.12.32:333 192.168.53.207:63771 timer:(keepalive,12sec,0) ino:336896561 sk:65de <->
对比很容易发现,链接 21端口的全部链接,多没有 timer 项。这说明,FTP代理 进程监听 21 端口时,全部进来的连接,全都没有使用keepalive。
找了一些文章,大多只是说,怎么配置Linux 的 Keep Alive,以及不配置的,会形成 ESTABLISHED 不释放问题,没有说进程须要额外设置啊?难道 Linux KeepAlive 配置,不是对全部链接直接就生效的?
因此,咱们有必要验证 Linux keepalive,必需要进程本身额外开启才能生效
在开始这个验证以前,先摘取一段FTP中间件代理关于监听 21 端口的部分代码:
func (ftpServer *FTPServer) ListenAndServe() error { laddr, err := net.ResolveTCPAddr("tcp4", ftpServer.listenTo) if err != nil { return err } listener, err := net.ListenTCP("tcp4", laddr) if err != nil { return err } for { clientConn, err := listener.AcceptTCP() if err != nil || clientConn == nil { ftpServer.logger.Print("listening error") break } //以闭包的方式整理处理driver和ftpBridge,协程结束总体由GC作资源释放 go func(c *net.TCPConn) { driver, err := ftpServer.driverFactory.NewDriver(ftpServer.FTPDriverType) if err != nil { ftpServer.logger.Print("Error creating driver, aborting client connection:" + err.Error()) } else { ftpBridge := NewftpBridge(c, driver) ftpBridge.Serve() } c = nil }(clientConn) } return nil }
足够明显,整个函数,net.ListenTCP 附近都没有任何设置KeepAlive的相关操做。咱们查看 相关函数,找到了设置 KeepAlive的地方,进行一下设置:
if err != nil || clientConn == nil { ftpServer.logger.Print("listening error") break } // 此处,设置 keepalive clientConn.SetKeepAlive(true)
从新构建部署以后,能够看到,全部对21端口的链接,所有都带了 timer
ss -aoen|grep 192.168.12.32:21|grep ESTAB
输出以下:
tcp ESTAB 0 0 192.168.12.32:21 192.168.70.76:54888 timer:(keepalive,1min19sec,0) ino:397279721 sk:6b49 <-> tcp ESTAB 0 0 192.168.12.32:21 192.168.37.125:49648 timer:(keepalive,1min11sec,0) ino:398533882 sk:6b4a <-> tcp ESTAB 0 0 192.168.12.32:21 192.168.33.196:64471 timer:(keepalive,7.957ms,0) ino:397757143 sk:6b4c <-> tcp ESTAB 0 0 192.168.12.32:21 192.168.21.159:56630 timer:(keepalive,36sec,0) ino:396741646 sk:6b4d <->
能够很明显看到,全部的链接,所有具有了 timer 功效,说明:想要使用 Linux 的 KeepAlive,须要程序单独作设置进行开启才行。
首先,看一下 Linux 中的配置,个人机器以下:
[root@xx xx]# sysctl -a |grep keepalive net.ipv4.tcp_keepalive_intvl = 75 net.ipv4.tcp_keepalive_probes = 9 net.ipv4.tcp_keepalive_time = 300
tcp_keepalive_time:表示多长时间后,开始检测TCP连接是否有效。
tcp_keepalive_probes:表示若是检测失败,会一直探测 9 次。
tcp_keepalive_intvl:承上,探测9次的时间间隔为 75 秒。
而后,咱们看一下 ss 命令的结果:
ss -aoen|grep 192.168.12.32:21|grep ESTAB
tcp ESTAB 0 0 192.168.12.32:21 192.168.70.76:54888 timer:(keepalive,1min19sec,0) ino:397279721 sk:6b49 <->
摘取这部分:timer:(keepalive,1min19sec,0) ,其中:
keepalive:表示此连接具有 keepalive 功效。
1min19sec:表示剩余探测时间,这个时间每次看都会边,是一个递减的值,第一次探测,须要 net.ipv4.tcp_keepalive_time 这个时间倒计时,若是探测失败继续探测,后边会按照 net.ipv4.tcp_keepalive_intvl 这个时间值进行探测。直到探测成功。
0:这个值是探测时,检测到这是一个无效的TCP连接的话已经进行了的探测次数。
欢迎关注“海角之南”公众号获取更新动态