守护进程

守护进程

我有一个在 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.shsetsidand开始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();

相关内容