在 Docker Entry Script 详解中介绍了如何在 shell 脚本中响应 Unix 信号量来实现 Docker 应用优雅的关闭退出, 本文介绍 C# 程序如何在 Docker 中响应 Unix 信号实现优雅的关闭退出。
因为用 Mono 编译出来的程序可以完美的在 Linux/Docker 下运行, 所本文以 Mono 5.4 做为开发环境, 对应的 .Net Framework 版本为 4.6.1 。
假设现在需要运行一个定时任务的程序, 每隔一定时间输出一个 hello, world!
,我们使用 Quartz.Net 来完成这个任务, 代码如下所示:
首先来定义一个 EchoJob
, 向控制台输出 hello, world!
, 代码如下:
public class EchoJob : IJob {
public void Execute(IJobExecutionContext context) {
Console.WriteLine($"{DateTime.Now} Hello, world!");
}
}
接下来使用 Quartz
来配置并启动这个任务, 代码如下:
private static void StartupQuartz() {
Console.WriteLine("Start Quartz");
var factory = new StdSchedulerFactory();
scheduler = factory.GetScheduler();
scheduler.Start();
var job = JobBuilder.Create<EchoJob>()
.WithIdentity("EchoJob", "EchoJob")
.Build();
var trigger = TriggerBuilder.Create()
.WithIdentity("EchoJob-Trigger", "EchoJob")
.WithSimpleSchedule(
x => x.WithInterval(TimeSpan.FromSeconds(5))
.RepeatForever()
)
.StartNow()
.Build();
scheduler.ScheduleJob(job, trigger);
}
当按 Ctrl + C 结束程序或者使用 docker stop
命令停止容器时, 我们希望停止这个任务, 停止任务的代码如下:
private static void ShutdownQuartz() {
Console.WriteLine("Shutdown Quartz");
scheduler.Shutdown();
}
接下来, 问题就来了, 我们的程序如何响应这两个时间呢? 在 Linux 下面, Mono 提供了 Mono.Unix.UnixSignal
来解决这中问题, 我们的程序需要监听两个 Unix 信号, 分别是:
根据 Mono 的文档, 监听 Unix 信号量的代码如下:
private static void WaitForExit() {
var signals = new UnixSignal[] {
new UnixSignal(Signum.SIGINT),
new UnixSignal(Signum.SIGTERM)
};
var index = UnixSignal.WaitAny(signals);
var signal = signals[index].Signum;
Console.WriteLine($"Received Signal: {signal}");
}
现在, 我们的程序看起来是这样子的:
class MainClass {
private static IScheduler scheduler = null;
public static void Main(string[] args) {
StartupQuartz();
WaitForExit();
ShutdownQuartz();
}
private static void StartupQuartz() { ... }
private static void ShutdownQuartz() { ... }
private static void WaitForExit() { ... }
}
程序在前台运行, 用 Ctrl + C
方式来结束程序时, 输出如下:
$ mono bin/QuartzDocker.exe
Start Quartz
12/8/2017 11:34:11 AM Hello, world!
12/8/2017 11:34:16 AM Hello, world!
^CReceived Signal: SIGINT
Shutdown Quartz
部署到 Docker 容器, 用 docker stop
停止容器时, 输出如下:
Sending build context to Docker daemon 4.311MB
Step 1/4 : FROM beginor/mono:5.4.1.6
---> 7c736fa9d337
Step 2/4 : COPY bin /app
---> 80ab98a23b28
Step 3/4 : WORKDIR /app
---> bde64015b8b0
Removing intermediate container 299227729a73
Step 4/4 : ENTRYPOINT mono QuartzDocker.exe
---> Running in 4c23abe8f903
---> b299267db381
Removing intermediate container 4c23abe8f903
Successfully built b299267db381
Successfully tagged quartz-test:latest
Start Quartz
12/08/2017 11:39:00 Hello, world!
12/08/2017 11:39:05 Hello, world!
12/08/2017 11:39:10 Hello, world!
12/08/2017 11:39:15 Hello, world!
12/08/2017 11:39:20 Hello, world!
12/08/2017 11:39:25 Hello, world!
12/08/2017 11:39:30 Hello, world!
Received Signal: SIGTERM
Shutdown Quartz
现在, 我们的目的终于达到了。
通常应用程序都会有自己的状态, 在程序结束时, 保存应用程序的状态是非常重要的, 因此应许能够感知结束, 并保存状态是非常重要的。
对于 Docker 来说, 发送 SIGTERM 之后, 默认最多只等待 10 秒钟, 如果 10 秒钟之后还没有退出, 就会被强制关闭。 如果需要修改这个等待时间的话, 则需要在 docker stop 命令添加 --time
选项, 设置等待时间, 比如:
docker stop --time 30 CONTAINER
如果你的开发环境是 Windows , 只安装了 .Net Framework, 找不到
Mono.Posix
引用怎么办, 不要着急, 可以通过 Nuget 来添加 Mono.Posix 包来解决。