leuisken.github.io
leuisken.github.io copied to clipboard
一个 Node.js 在 CentOS 6 上启动卡死问题的分析和解决
昨天晚上接到用户反馈,我们提供的一个脚本执行卡死。这个脚本里面会下载一个我们编译好的 Node.js 12.13.0,并用它来执行一个 js 脚本。经过初步定位,发现是卡死在了 node tool.js 这一句,非常奇怪,于是对这个问题进行了追踪。
遇事不决先升级
首先升级到了 Node.js 的 12.16.1 版本,但还是会卡死,不行。
尝试远程调试
初步的猜想,可能是 tool.js 里面的逻辑哪里有问题,于是让用户执行以下命令:
node --inspect-brk=0.0.0.0:8080 tool.js
打算远程调试,但是发现远程调试无法连接到用户机器上面。同时用户反馈说,执行后控制台没有任何输出,而如果调试能启动,那么至少会有如下的输出。
由于 --inspect-brk
能帮我们排除掉所有 js 逻辑导致的问题,那么说明在执行 js 前,程序就已经卡死了。
分析 strace 日志
strace 是 linux 下的一个命令行工具,我的理解是,它能拦截到程序执行的所有系统调用并记录日志。
在用户机器上面,使用 strace 执行 node,命令为strace ./bin/node -e "console.log(1)" &> a.log
,得到 strace 日志,日志最后两行如下:
open("/dev/random", O_RDONLY) = 17
select(18, [17], NULL, NULL, NULL <unfinished ...>
关注到最后的调用,程序在执行 select 的时候卡住,问题找到了。并且通过前面的分析,select 是由 node 在 js 前的某些逻辑调用的,这个时候就要下载 node 的源码看看了。
我也是第一次去看 node 的代码,先搜 select,没有找到有意义的结果。而 log 前一行打开了 /dev/random
文件,再搜这个关键词,找到了下面这里:
// Ensure that OpenSSL has enough entropy (at least 256 bits) for its PRNG.
// The entropy pool starts out empty and needs to fill up before the PRNG
// can be used securely. Once the pool is filled, it never dries up again;
// its contents is stirred and reused when necessary.
//
// OpenSSL normally fills the pool automatically but not when someone starts
// generating random numbers before the pool is full: in that case OpenSSL
// keeps lowering the entropy estimate to thwart attackers trying to guess
// the initial state of the PRNG.
//
// When that happens, we will have to wait until enough entropy is available.
// That should normally never take longer than a few milliseconds.
//
// OpenSSL draws from /dev/random and /dev/urandom. While /dev/random may
// block pending "true" randomness, /dev/urandom is a CSPRNG that doesn't
// block under normal circumstances.
//
// The only time when /dev/urandom may conceivably block is right after boot,
// when the whole system is still low on entropy. That's not something we can
// do anything about.
inline void CheckEntropy() {
for (;;) {
int status = RAND_status();
CHECK_GE(status, 0); // Cannot fail.
if (status != 0)
break;
// Give up, RAND_poll() not supported.
if (RAND_poll() == 0)
break;
}
}
代码位于:https://github.com/nodejs/node/blob/v12.16.1/src/node_crypto.cc#L426
这就能看出注释写的好的好处了。尽管我是第一次看 Node.js 的代码,但是这里的注释帮助我很快猜测到了可能出现问题的原因。
Ensure that OpenSSL has enough entropy (at least 256 bits) for its PRNG.
大意是保证 OpenSSL 有足够大的熵池用于 PRNG。
这里有遇到了很多新的概念,PRNG 是啥、熵池是啥,不过至少我们有了关键词,即熵池。那么剩下的问题就是,了解新概念的知识背景,如果操作这个新概念相关的东西,进而解决我们的问题。
花了不少时间给自己科普了相关概念,并最终采用这篇文章中( https://blog.csdn.net/fukai8350/article/details/80429978 )提到的方法解决了问题。解决方案总是很简单的。简单说一下我自己的理解,如有不对欢迎拍砖,毕竟就是为了解决问题大概看的,没有深入了解。
熵池是 linux 里面用于生成随机数的一个东西,相当于是随机的源头,这个源头可以是硬件,也可以是软件。可以用如下命令查看熵池大小:
cat /proc/sys/kernel/random/entropy_avail
在用户机器上执行了一下,果然是 0。
那么如何增加的,在用户的机器上,熵池是空的,执行下面的命令就可以填上了。
sudo rngd -r /dev/urandom
执行完再看看熵池的大小。
问题解决。
后记
解决完问题后,才想起去翻翻 Node.js 的 issue,果然还是有一些 close 掉的 issue 说了这个事情。应该早点去翻翻 issue 的。
The entropy pool starts out empty and needs to fill up before the PRNG can be used securely. Once the pool is filled, it never dries up again;
OpenSSL normally fills the pool automatically
从这两句来看的话,OpenSSL应该负责填那个熵池,而且填了之后不会清空。看起来好像它没有干这个事情。 不过这个可能就是更深的问题了。。