我们在容器内以 K8s Pod 的形式运行微服务。为了确保我们的应用程序收到发送到容器的任何信号(特别是 pod 驱逐时的 SIGTERM),我们通常exec
在启动脚本的最后使用,以便启动脚本 bash 进程(PID 为 1)有效“变成”我们的应用程序的过程。所以我们的启动脚本通常以
exec <ourCommand>
为了方便起见,我们希望它的日志输出(stdout 和 stderr)在 的输出中可见kubectl logs
,但为了事后分析,我们还希望输出最终出现在一个文件中(是的,该文件最终出现在它存在的地方) Pod 重新启动:-))。为了避免让文件增长太多,应该旋转它。对于日志轮转,我们对 Apache 的rotatelogs
工具有很好的经验。它的“-e”选项将“回显日志到标准输出”——为我们提供文件和标准输出上的记录输出。最明显的方法是通过管道传输脚本输出,如下所示:
exec <ourCommand> | rotatelogs -e -n 10 stdout.log 10M
这似乎工作正常,但我们随后注意到,这有效地阻止了exec
预期的行为,并且启动脚本将仍然是根进程,例如,pstree -p
将显示以下结构:
start.sh(1)-+-<ourCommand>(17)
`-rotatelogs(18)
所以我们的信号处理被破坏了,应用程序不会收到 SIGTERM 并且不会正常退出,相反,pod 将在终止宽限期结束后被杀死。
经过大量的试验和错误,我们使用流程替换得出了这个解决方案:
exec <ourCommand> &> >( rotatelogs -e -n 10 stdout.log 10M)
结果是所需的过程结构和再次工作的信号处理,pstree -p
现在将显示此结构:
<ourCommand>(1)---start.sh(17)---rotatelogs(18)
然而,我们随后反复注意到 Pod 崩溃的情况,但有时没有可见的错误消息kubectl logs
(但它曾是在日志文件中可见,但没有人查看那里,期望它具有与 相同的内容kubectl logs
,所以“何必麻烦”)。有趣的是,这种情况在生产环境(EKS 和 AKS 集群)中发生得更频繁,而在尝试在本地环境(minikube)中重现问题时则不太可靠。
我们最好的猜测是,rotatelogs 并不总是刷新其输出缓冲区,因此特别是脚本输出的最后一行(通常是有用的错误消息......)可能最终会丢失。由于rotatelogs没有提供强制刷新每一行的设置,所以现在的想法是使用这种方法(我们称之为“双进程替换”):
exec <ourCommand> &> >(tee >(rotatelogs -n 10 stdout.log 10M ))
这个想法是让 tee 进行“流分割”,并且由于它不存在旋转日志所-e
具有的刷新问题,因此效果很好 - 崩溃应用程序的“著名的遗言”将可靠地显示在日志文件中,并且在kubectl logs
。
然后有人想到了一个绝妙的主意,通过 ts 过滤来为所有日志行添加时间戳:
exec <ourCommand> &> >(ts | tee >(rotatelogs -n 10 stdout.log 10M ))
这又导致了“著名的遗言”经常不会出现在kubectl logs
。现在的流程结构如下所示:
<ourCommand>(1)---start.sh(17)-+-tee(19)---start.sh(20)---rotatelogs(21)
`-ts(18)
是的,我们确实尝试了“三重进程替换”:-D,但这并没有解决问题。只有删除 ts 才会。
对 bash 有更深入了解的任何人都可以尝试解释这里发生的事情(并且最好能提出解决方案)吗?
谢谢
磷
答案1
ts
期望在添加时间戳并打印之前收到整行。因此,如果您的流程的“遗言”不以 - 结尾\n
,它们可能会丢失。
如何修复它?自己写一个ts
,可能是这样的:
#!/bin/perl
$foundLF = 1;
while($ch=getc) {
if ($foundLF == 1) {
$datestring = gmtime();
print "$datestring ";
$foundLF = 0;
}
print $ch;
$foundLF = 1 if (ord($ch) == 10);
}
已经ts
写好了perl
,大家可以参考一下。
如果您tee
将该rotatelog
功能包含到您自己的ts
.这很容易做到perl
。