出现问题
其他小组在客户现场部署我们的系统时遇到了问题,公司环境测试正常的一个Docker到客户服务器上无法正常使用。Docker核心是运行theia程序,可以在网页里与VSCode一样开发代码。在客户服务器启动后,网页访问正常打开,但在里面开启终端时就卡死,然后theia的后台服务就崩溃断连了。
收集信息
通过沟通了解到,客户现场是离线环境,没办法远程,没办法下载工具调试,传输外部文件也不能轻易传送。简单的让同事做了下测试,并排除了权限问题、操作错误、配置不正确等问题。剩下的很可能就是镜像本身问题了。
把相同镜像拿到客户的其他机器上,发现能正常使用。询问后知晓,不能使用的服务器是 麒麟V10
系统,正常使用的是 CentOS
,以及自己测试服务器的Ubuntu
。麒麟系统我们没考虑过,有些意料之外问题也是能接受的。这种运行终端最核心的功能也没定制过,很大概率是兼容性问题,手头没复现的环境很是头疼。
再次询问后发现一个非常费解的事情,他们组是有部署一台麒麟V10
系统的,这台跑同样的镜像就非常正常,为此还专门过去试了试,确实如此。只能一边让同事对比两台服务器的差异,一边继续收集错误信息。
最后收缩到两个错误上,一个是程序终止后最后会输出终端id是-1与其他环境不一致,另一个是稍早一些有抛出一个错误信息Error: Unexpected SIGPIPE
。
推断排查
翻翻代码,从@theia/terminal
开始,终端id -1是终端初始化值,接着调用 @theia/process
模块,@theia/process
模块创建终端是调用 node-pty
包(版本 0.11.0-beta17)的spawn
方法。node-pty
作用就是创建一个进程,返回可以读写的终端对象,spawn
就是他的创建方法。
抛出的错误信息是在@theia/core
中的 src/node/backend-application.ts
,调用process.on 监听了 "SIGPIPE"事件,处理逻辑就仅仅是抛出错误信息。
在这段代码有附带一个 electron 的 issues,时间很早已经修复关闭了。issues里是electron环境,我们现在是在浏览器环境,并不太一样。并且Node.js中有对SIGPIPE信号做忽略处理,应该不至于进程被终止。继续搜索其他相关讨论也没更多有用的资料了。只能回头,再从提示信息 SIGPIPE
下手了。
信号 signal
SIGPIPE
是什么?这是 linux 进程通信的方式之一,是唯一的异步通信方式。
linux系统定义了64种信号值,其中1-31是不可靠信号,大部分是一些错误信息。
当进程接收到信号,可以有三种处理方式:1. 捕获信号自己处理。2. 忽略信号不做任何处理。3. 缺省处理,使用系统默认的处理方式,有终止进程、忽略信号、挂起等。
除了错误产生的,软硬件也能主动产生信号,像是键盘触发ctrl+c时,就是发送的2信号标识码是 SIGINT。kill命令也能借助系统向其他进程发送信号,以达到杀死进程的效果。
SIGPIPE
的缺省处理就是终止进程,是管道操作产生的错误,发生在管道关闭后,但进程依然在写入,会触发这个信号产生。
所以theia捕获并抛出了错误信息也符合对信号的处理。
管道 pipe
由以上得知,这个标志说明进程执行时遇到了错误,这个错误是在操作"管道"产生的。
什么是"管道"呢?管道与信号一样,也是进程间的通信方式。进程间通信(Inter Process Communication)缩写为IPC,方式一共有四种:管道(pipe)、信号(signal)、共享映射区(mmap)、套接字(socket)。管道还有区分为pipe匿名管道,FIFO为命名管道,一般用在血缘关系的进程之间(父子进程、兄弟的子进程)完成数据传递。
推断
根据以上信息做出一个大致推断,node-pty
在创建终端时要向相终端进程读写数据,但目标进程不明原因未启动或者意外关闭了,导致后续对管道读写数据时出现了错误,系统产生了SIGPIPE
信号。
可能 node-pty
中的错误处理在麒麟下未生效,或是并没对此有做错误处理,导致进程异常连带theia一起被终止。
陷入疑惑
没有复现环境很麻烦,好在最后又整了套麒麟系统,成功了复现了问题。那这样就搞个最小化的验证。
在这期间还发现了,此前旧版的theia在客户服务器上可以正常使用。查询 theia 历史,最开始使用的是 @theia/node-pty
也就是定制的 node-pty
,在 1.22.0版本更换了包,使用了原版的 node-pty
。
搞了两个版本放到测试环境上做验证,但奇怪的是,两个包都成功运行,开启了终端正常读写,并没有中断。
这,难不成是 Docker 环境有什么包导致的?但此前也有打过纯粹的 theia 基础依赖镜像,也是无法运行,与Docker环境中其他依赖没什么关系。最后索性在麒麟系统上直接安装了 theia 测试,发现相同版本确实是可以直接跑起来的。
Docker ?
Docker?剩下的差异只有Docker了。于是在麒麟服务器上将可以运行的项目打包成Docker直接跑,发现打成了Docker就出现了终端打不开的情况。
这就很奇怪了,Docker是一个容器,并不是真正的虚拟机,是利用linux内核提供的Namespace
、CGroup
特性来对程序进行隔离和资源限制。而我们安装的Docker系统镜像只是预装了各种系统的基础初始工具,其内核还是与宿主机使用的是同一套,所以本质上在容器中跑的程序还是在麒麟系统的内核
上跑的。
在此前也有与公司内服务器核对过版本的,大版本一致,只有小版本不同,客户的测试服务器也换过版本,依然无法跑起镜像来。要说区别能跑起来的那台麒麟安装的Docker其中runc版本会高一些。
但即使是Docker问题,真正上生产环境也没办法换Docker,还是只能在自己的程序上找处理方案。
临时补救
目前已知的是降theia版本是可行的,因为降theia版本等于是降node-pty版本。但这个方案问题较多,由于新旧版本差异以及自定的功能模块差异较多,降级的话不容易保证其他功能没有问题,同时打包后的镜像过大,打包以及传输也挺耗时的,对于准备上线项目来说时间太紧张了。但还是没办法,最后依然着手开始搞这个方案,希望尽早弄出新的测试镜像。
之后后端同事发现,既然是node-pty出问题,那么何不试试直接替换镜像中的node-pty版本呢?同事替换的是更高的版本,重启一下镜像中的theia,竟然也能正常工作了。这样的话就不用重新搞大镜像了。
后续我将明确的操作步骤整理了出来,并传好资源,让客户那儿的同事进入theia容器,拿给出的资源文件替换目录下的文件,然后使用 Docker commit 命令生成新的镜像,启动,测试,成功!至此功能使用问题算是解决了。
至于问题定位,后续去翻了翻node-pty
源码搜索与PIPE相关的段落,没找到什么有效线索。至于Docker的调试,则无能为力,相同版本Docker在其他系统正常,在麒麟下不正常,按排除法应该是麒麟系统的差异造成的问题,这块暂时啃不动。最后在现有的theia基础上准备了一份锁死node-pty版本为1.0.0的代码,算是为以后再次碰见类似状况做了准备。