我有一个在 Debian 9 上运行的 .NET Core 服务,我们将其称为 MyService。在某些时候,该服务正在update.sh
使用Process.Start()
with运行 bash 脚本ShellExecute=true
。
这个脚本基本上可以运行apt-get update; apt-get upgrade
。
在软件包升级期间,MyService 进程会终止:更新脚本也会终止并被apt-get upgrade
终止,留下必须手动修复的不一致软件包。
我想要的是update.sh
当 MyService 终止时它不会终止。
我尝试将其分成update.sh
两部分,第一部分以不同的方式运行第二部分;我尝试update2.sh
从setsid
and开始nohup
,但总是得到相同的结果。我尝试update2.sh
在新的 bash shell 中执行脚本/bin/bash /c "update2.sh"
,结果相同。
如何运行从二进制文件启动的脚本并完全与二进制进程分离,以便我可以在脚本继续运行时杀死二进制文件?
这是我的环境。 MyService 是作为服务运行的二进制文件。update.sh
由 MyService 启动。
用于启动 shell 脚本的 .NET Core 代码,位于 MyService 二进制文件中:
var process = new Process();
process.EnableRaisingEvents = true; // to avoid [defunct] sh processes
process.StartInfo.FileName = "/opt/myservice/update.sh";
process.StartInfo.Arguments = "";
process.StartInfo.UseShellExecute = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
process.WaitForExit(10000);
if (process.HasExited)
{
Console.WriteLine("Exit code: " + process.ExitCode);
}
else
{
Console.WriteLine("Child process still running after 10 seconds");
}
更新.sh:
nohup /opt/myservice/update2.sh > /opt/myservice/update.log &
systemctl stop MyService
更新2.sh:
apt-get update >> /opt/myservice/update.log
apt-get -y install --only-upgrade myservice-1.0 >> /opt/myservice/update.log
update2.sh
永远不会执行,因为当 MyService 被 终止时它就会终止update.sh
。
update.sh
返回代码143,看来它已经被杀死了。
2018-08-16 14:46:14.5215|Running update script: /opt/myservice/update.sh
2018-08-16 14:46:14.5883|Update script /opt/myservice/update.sh returned: 143
更新
我尝试了以下方法,感谢您的建议:
- 设定值
- 否认
- 诺哈普
- 屏幕
- 多路复用器
- 取消共享
每种方法都有相同的结果,即终止所有生成的进程。我怀疑这是一个 .NET Core“功能”。
更新2
我发现systemctl stop MyService
默认情况下会显式终止服务生成的所有进程。
https://stackoverflow.com/questions/40898077/systemd-systemctl-stop-aggressively-kills-subprocesses
如果我添加KillMode=process
到服务描述符,则服务终止时更新脚本不会终止。
有决不逃离由 启动的服务的 PID 空间systemctl
。使用的每一种技术,包括接受答案中的技术,都不会生成单独的过程。systemctl stop MyService
除非KillMode=process
指定,否则每个生成的进程总是被杀死。
我最终创建了一个单独的服务MyServiceUpdater
:该服务运行简单的更新程序脚本,无需任何分叉。由于 PID 空间不同,一切都按预期进行。那是一段漫长的旅程。
MyServiceUpdater 示例:
[Unit]
Description=Your Service Updater
After=network.target
[Service]
ExecStart=/path/to/update/script/updatescript.sh
ExecStopPost=
TimeoutStopSec=30
StandardOutput=null
WorkingDirectory=/path/to/service/directory/
KillMode=process
[Install]
WantedBy=multi-user.target
答案1
使用 crontab(或 at)(不是 mono/.net)来安排任务。
正常选项;
- nohup my.sh &
- 屏幕-dm -S 我的my.sh
- tmux 新-d -s 我的 my.sh
- 服务我的启动 / systemctl 启动我的
- Ctrl+Z、bg、否认
答案2
在 Centos 7 测试系统上通过
$ sudo rpm -Uvh https://packages.microsoft.com/config/rhel/7/packages-microsoft-prod.rpm
$ sudo yum install dotnet-sdk-2.1
这会导致dotnet-sdk-2.1-2.1.400-1.x86_64
使用测试代码进行安装
using System;
using System.Diagnostics;
using System.ComponentModel;
namespace myApp {
class Program {
static void Main(string[] args) {
var process = new Process();
process.EnableRaisingEvents = true; // to avoid [defunct] sh processes
process.StartInfo.FileName = "/var/tmp/foo";
process.StartInfo.Arguments = "";
process.StartInfo.UseShellExecute = true;
process.StartInfo.CreateNoWindow = true;
process.Start();
process.WaitForExit(10000);
if (process.HasExited) {
Console.WriteLine("Exit code: " + process.ExitCode);
} else {
Console.WriteLine("Child process still running after 10 seconds");
}
}
}
}
和一个 shell 脚本作为/var/tmp/foo
一个strace
停止并显示它在我的系统上/var/tmp/foo
运行xdg-open
...我不知道什么,这似乎是一个不必要的复杂化。
$ strace -o foo -f dotnet run
Child process still running after 10 seconds
^C
$ grep /var/tmp/foo foo
25907 execve("/usr/bin/xdg-open", ["/usr/bin/xdg-open", "/var/tmp/foo"], [/* 37 vars */] <unfinished ...>
...
一个更简单的解决方案是简单地创建exec
一个程序,该程序又可以是执行您想要的操作的 shell 脚本,对于 .NET 来说,这不需要使用 shell:
process.StartInfo.UseShellExecute = false;
通过此设置,strace
显示/var/tmp/foo
正在通过(更简单的)调用运行execve(2)
:
26268 stat("/var/tmp/foo", {st_mode=S_IFREG|0755, st_size=37, ...}) = 0
26268 access("/var/tmp/foo", X_OK) = 0
26275 execve("/var/tmp/foo", ["/var/tmp/foo"], [/* 37 vars */] <unfinished ...>
并且 .NET 拒绝退出:
$ strace -o foo -f dotnet run
Child process still running after 10 seconds
^C^C^C^C^C^C^C^C
因为foo
用忽略大多数信号的东西替换自身(特别是不是USR2
,或者总是有KILL
(但避免使用它!)):
$ cat /var/tmp/foo
#!/bin/sh
exec /var/tmp/stayin-alive
$ cat /var/tmp/stayin-alive
#!/usr/bin/perl
use Sys::Syslog;
for my $s (qw(HUP INT QUIT PIPE ALRM TERM CHLD USR1)) {
$SIG{$s} = \&shandle;
}
openlog( 'stayin-alive', 'ndelay,pid', LOG_USER );
while (1) {
syslog LOG_NOTICE, "oh oh oh oh oh stayin alive";
sleep 7;
}
sub shandle {
syslog LOG_NOTICE, "nice try - @_";
}
守护进程
和一个与父进程解除关联的进程和一个运行一些命令的 shell 脚本(希望与您的预期相同apt-get update; apt-get upgrade
)
$ cat /var/tmp/a-few-things
#!/bin/sh
sleep 17 ; echo a >/var/tmp/output ; echo b >/var/tmp/output
我们可以修改.NET程序来运行/var/tmp/solitary /var/tmp/a-few-things
process.StartInfo.FileName = "/var/tmp/solitary";
process.StartInfo.Arguments = "/var/tmp/a-few-things";
process.StartInfo.UseShellExecute = false;
运行时会导致 .NET 程序相当快地退出
$ dotnet run
Exit code: 0
最终,该/var/tmp/output
文件确实包含由 .NET 程序离开时未被终止的进程写入的两行。
您可能应该将 APT 命令的输出保存在某个地方,并且可能还需要一些东西,以便两个(或更多!)更新不会尝试同时运行,等等。此版本不会因问题而停止并忽略任何TERM
信号(INT
可能也需要被忽略)。
#!/bin/sh
trap '' TERM
set -e
apt-get --yes update
apt-get --yes upgrade
答案3
我自己也一直在与同样的事情作斗争。
我发现的一种解决方案是使用systemd-run
,它本质上创建一个临时的一次性服务来执行任意命令。然而,要使其工作,似乎需要您指定-r
(remain-after-exit) 参数。看https://www.freedesktop.org/software/systemd/man/systemd-run.html获取更多文档。
例如:
var process = new Process()
{
StartInfo = new ProcessStartInfo
{
FileName = "systemd-run",
Arguments = "-r sleep 90",
RedirectStandardOutput = false,
RedirectStandardError = false,
UseShellExecute = false,
CreateNoWindow = true,
},
};
process.EnableRaisingEvents = true;
process.Start();