ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

使用 Microsoft.Extensions.Hosting.Systemd 将 .NET 应用部署为 systemd 服务

使用 Microsoft.Extensions.Hosting.Systemd 将 .NET 应用部署为 systemd 服务 使用 Microsoft.Extensions.Hosting.Systemd 将 .NET 应用部署为 systemd 服务【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtimeMicrosoft.Extensions.Hosting.Systemd是 .NET 运行时仓库dotnet/runtime中为 Linux systemd 场景专门提供的托管集成库它让基于IHost的 .NET 应用可以直接作为 systemd 服务运行并与 systemd 进行双向通信一方面通过sd_notify协议向服务管理器上报READY、STOPPING等状态另一方面接管SIGTERM实现优雅停机同时自动切换为 systemd 日志格式。读完本文你将掌握UseSystemd/AddSystemd的完整用法、其上下文感知的激活机制、底层 socket 通信原理以及如何编写配套的 systemd unit 文件。一、库的定位与功能概览该库位于仓库的 src/libraries/Microsoft.Extensions.Hosting.Systemd 目录README 对其定位只有一句话它包含在 systemd 服务中使用托管hosting的实现。结合包内 PACKAGE.md 的说明它的核心能力可以概括为systemd 服务集成让 .NET 应用以 Linux 服务形式运行服务状态上报应用启动完成、开始关闭时主动通知 systemd生命周期管理接管IHostLifetime处理 systemd 发送的终止信号日志与诊断检测到运行在 systemd 下时自动启用 systemd 日志格式ConsoleFormatterNames.Systemd。该库向开发者暴露的主要类型见 ref/Microsoft.Extensions.Hosting.Systemd.cs 中的公开 API 清单包括Microsoft.Extensions.Hosting.SystemdHostBuilderExtensions提供UseSystemd()与AddSystemd()两个入口Microsoft.Extensions.Hosting.Systemd.SystemdLifetimeIHostLifetime实现Microsoft.Extensions.Hosting.Systemd.SystemdNotifier负责向 systemd 发送通知Microsoft.Extensions.Hosting.Systemd.ISystemdNotifier通知器抽象接口Microsoft.Extensions.Hosting.Systemd.ServiceState服务状态数据结构Microsoft.Extensions.Hosting.Systemd.SystemdHelpers运行环境检测辅助类。二、快速上手UseSystemd()与AddSystemd()PACKAGE.md 给出了最经典的使用方式在配置 host 的地方把UseSystemd加入构建链public static IHostBuilder CreateHostBuilder(string[] args) Host.CreateDefaultBuilder(args) .UseSystemd() // 启用 Systemd 服务支持 .ConfigureServices((hostContext, services) { // 其余服务注册 });如果你使用的是HostApplicationBuilder.NET 7 的推荐方式则调用AddSystemdvar builder new HostApplicationBuilder(args); builder.Services.AddSystemd(); var host builder.Build(); await host.RunAsync();从源码看这两个入口实际做了完全相同的事SystemdHostBuilderExtensions.cspublic static IHostBuilder UseSystemd(this IHostBuilder hostBuilder) { ArgumentNullException.ThrowIfNull(hostBuilder); if (SystemdHelpers.IsSystemdLogger()) { hostBuilder.ConfigureServices((hostContext, services) { AddSystemdLogger(services); }); } if (SystemdHelpers.IsSystemdLifetime()) { hostBuilder.ConfigureServices((hostContext, services) { AddSystemdLifetime(services); }); } return hostBuilder; }其中AddSystemdLogger将ConsoleLoggerOptions.FormatterName设置为ConsoleFormatterNames.Systemd见 SystemdHostBuilderExtensions.csAddSystemdLifetime则注册两个单例ISystemdNotifier实现为SystemdNotifier和IHostLifetime实现为SystemdLifetime见 SystemdHostBuilderExtensions.cs。需要特别强调的是这两个方法在非 systemd 环境下是 no-op。PACKAGE.md 明确说明当未作为守护进程运行时UseSystemd方法不会执行任何操作因此无论是本地调试还是部署在 systemd 之外都可以安全地保留这行代码而不影响行为。这一点在 UseSystemdTests.cs 中有对应的测试用例当清空SYSTEMD_EXEC_PID和NOTIFY_SOCKET环境变量后FormatterName不会被改成 systemd 格式IHostLifetime也不会是SystemdLifetime类型。三、上下文感知如何判断正在以 systemd 服务运行上下文感知context aware的实现核心是 SystemdHelpers.cs 中的检测逻辑它决定了日志格式化器与生命周期组件是否被注册。检测分为三套机制按优先级排列SYSTEMD_EXEC_PID精确比对首选systemd ≥ v248读取环境变量SYSTEMD_EXEC_PID并与当前进程 PID 比较。相等即判定为 systemd 服务。注释指出这是最可靠的检测方式因为它不依赖读取/proc即使设置了ProtectProcinvisible也能正常工作。相关常量定义在 SystemdConstants.cs。容器内 systemd 场景PID 1 回退若进程 PID 为 1例如 Podman 的--sdnotifycontainer容器化服务则检查NOTIFY_SOCKET或LISTEN_PID是否设置。传统检测systemd v248如 Ubuntu 20.04、Debian 11通过Interop.libc.GetParentPid()取得父进程 PID再读取/proc/ppid/comm若内容为systemd则判定成立。该路径在无法读取/proc时如ProtectProcinvisible会静默返回false。检测结果的派生规则SystemdHelpers.cs// 是否启用 systemd 日志格式仅当确认是 systemd 服务 internal static bool IsSystemdLogger() IsSystemdService(); // 是否注册 SystemdLifetime / SystemdNotifier是 systemd 服务或设置了 NOTIFY_SOCKET internal static bool IsSystemdLifetime() IsSystemdService() || IsSystemdNotify();也就是说日志格式只有确认在 systemd 服务中才会切换而生命周期与通知器只要检测到NOTIFY_SOCKET无论是否确认服务身份就会注册——因为SystemdNotifier在没有 socket 时本身就是一个 no-op。SystemdHelpersTests.cs中存放了针对这些检测路径的单元测试。四、SystemdLifetime优雅停机与状态通知SystemdLifetime实现了IHostLifetime接口SystemdLifetime.cs并在类型上标注了平台限制[UnsupportedOSPlatform(android)] [UnsupportedOSPlatform(browser)] [UnsupportedOSPlatform(ios)] [UnsupportedOSPlatform(maccatalyst)] [UnsupportedOSPlatform(tvos)] public partial class SystemdLifetime : IHostLifetime, IDisposable其构造依赖四个组件IHostEnvironment、IHostApplicationLifetime、ISystemdNotifier与ILoggerFactory。生命周期的工作流程如下WaitForStartAsync中注册ApplicationStarted与ApplicationStopping两个回调并调用RegisterShutdownHandlers()挂接信号处理应用启动完成OnApplicationStarted时先记录日志Application started. Hosting environment: ...; Content root path: ...随后调用SystemdNotifier.Notify(ServiceState.Ready)通知 systemd 服务已就绪应用开始停止OnApplicationStopping时记录Application is shutting down...并调用SystemdNotifier.Notify(ServiceState.Stopping)StopAsync直接返回Task.CompletedTask因为停止信号由 systemd 通过终止信号驱动无需额外等待Dispose负责注销回调与信号注册。信号处理逻辑按目标框架分为两个部分文件.NET Core 路径SystemdLifetime.netcoreapp.cs使用PosixSignalRegistration.Create(PosixSignal.SIGTERM, ...)只监听SIGTERM——因为 systemd 只会向服务进程发送SIGTERM其他信号如SIGINT/SIGQUIT由 .NET 运行时默认的信号处理器接管不会触发 systemd 服务的优雅关闭。回调中设置context.Cancel true并调用ApplicationLifetime.StopApplication()从而把 systemd 的终止信号转化为托管层的优雅停机流程。.NET Standard 路径SystemdLifetime.netstandard.cs通过AppDomain.CurrentDomain.ProcessExit事件触发StopApplication()并用ManualResetEvent阻塞进程退出直到优雅停机完成最后将Environment.ExitCode置为 0抑制 Linux 上 SIGTERM 默认带来的 143 退出码。五、SystemdNotifier通过 Unix 域套接字上报状态SystemdNotifierSystemdNotifier.cs是通知协议的实际执行者它通过Unix 域数据报套接字Dgram向NOTIFY_SOCKET指向的路径发送消息using (var socket new Socket(AddressFamily.Unix, SocketType.Dgram, ProtocolType.Unspecified)) { var endPoint new UnixDomainSocketEndPoint(_socketPath!); socket.Connect(endPoint); // 这里做非阻塞调用是安全的发送的消息远小于内核缓冲区不会被阻塞 socket.Send(state.GetData()); }值得注意的实现细节NOTIFY_SOCKET的读取与归一化构造函数从环境变量读取 socket 路径若路径以开头Linux 抽象命名空间 socket会将首字符替换为\0字节后再使用SystemdNotifier.csIsEnabled仅当读取到非空 socket 路径时为true否则Notify直接返回环境变量清理AddSystemdLifetime中的 DI 工厂在构造SystemdNotifier后立即执行Environment.SetEnvironmentVariable(SystemdConstants.NotifySocket, null)清除NOTIFY_SOCKET避免子进程继承该变量、误向父服务的 systemd 管理器发送通知SystemdHostBuilderExtensions.cs。这一行为同样有测试覆盖UseSystemdTests.cs 中专门断言了仅设置NOTIFY_SOCKET时host 构建后该环境变量已被清除。ISystemdNotifier接口ISystemdNotifier.cs只有两个成员void Notify(ServiceState state)与bool IsEnabled { get; }它是对通知能力的抽象便于替换与测试。六、ServiceState通知消息的数据载体ServiceStateServiceState.cs是一个struct内部以 UTF-8 字节数组保存要发送的消息并提供两个预置状态public static readonly ServiceState Ready new ServiceState(READY1); public static readonly ServiceState Stopping new ServiceState(STOPPING1);它们对应 systemdsd_notify协议中的两个关键状态READY1表示服务启动完成配合 unit 文件中的Typenotify使用服务管理器会等待该消息STOPPING1表示服务开始关闭。除此之外你还可以通过公开构造函数ServiceState(string state)构造自定义状态消息只要符合sd_notify的KEYVALUE格式即可例如ServiceState(STATUSProcessing data)或ServiceState(WATCHDOG1)用于看门狗心跳需在 unit 中启用WatchdogSec。七、编写 systemd unit 文件Typenotify要让通知机制真正生效unit 文件必须配置为Typenotify。README 中明确要求systemd 服务文件必须配置Typenotify才能启用通知。一个最小可用的 unit 文件示例如下[Unit] DescriptionMy .NET Systemd Service Afternetwork.target [Service] Typenotify ExecStart/usr/bin/dotnet /opt/myapp/MyApp.dll WorkingDirectory/opt/myapp Restarton-failure Userwww-data EnvironmentASPNETCORE_ENVIRONMENTProduction [Install] WantedBymulti-user.target将文件放入/etc/systemd/system/例如命名为myapp.service后执行sudo systemctl daemon-reload sudo systemctl enable myapp.service sudo systemctl start myapp.serviceTypenotify意味着 systemd 会等待应用通过NOTIFY_SOCKET发送READY1后才认为服务启动成功——而这正是SystemdLifetime在ApplicationStarted时自动完成的动作。此时systemd-analyze/systemctl status中也能看到服务状态与日志。需要说明的是从 SystemdHelpers.cs 的检测逻辑和 UseSystemdTests.cs 的注释看Typesimple配合 systemd ≥ v248 的SYSTEMD_EXEC_PID同样能被识别为 systemd 服务并启用日志与生命周期管理只是不会有READY1的启动握手。八、部署方式与平台约束README 的 Deployment 一节说明了该库的分发渠道随 ASP.NET Core 共享框架shared framework内置使用Microsoft.NET.Sdk.Web的项目无需额外引用即可使用作为 out-of-bandOOBNuGet 包独立发布可通过 NuGet 包Microsoft.Extensions.Hosting.Systemd直接引用到任意项目中适用于非 ASP.NET Core 的托管应用如后台 worker。平台支持方面SystemdLifetime明确不支持 android / browser / ios / maccatalyst / tvosSystemdNotifier不支持 browser检测逻辑首先判断Environment.OSVersion.Platform ! PlatformID.Unix直接返回false因此整体仅面向 UnixLinux系环境。库本身的贡献门槛Contribution Bar在 README 中标注为API 与功能已成熟但偶尔会扩展即允许新功能、新 API、缺陷修复与性能改进的合入。九、测试验证行为契约一览仓库在 tests/UseSystemdTests.cs 中提供了覆盖上述所有行为的集成测试核心断言可作为使用时的行为契约参考场景模拟的环境变量预期行为无SYSTEMD_EXEC_PID、无NOTIFY_SOCKET不启用 systemd 日志格式IHostLifetime不是SystemdLifetime设置SYSTEMD_EXEC_PID 当前 PID模拟Typesimple启用 systemd 日志格式注册SystemdLifetime仅设置NOTIFY_SOCKET模拟容器化 / systemd v248注册SystemdLifetime不切换日志格式构建后NOTIFY_SOCKET被清除两者都设置模拟Typenotify systemd ≥ v248日志格式与生命周期同时启用这些测试通过RemoteExecutor在独立进程中注入/清除环境变量来模拟不同的 systemd 运行环境正好印证了上下文感知设计的每种组合行为。总结Microsoft.Extensions.Hosting.Systemd用极简的 API 把 .NET 托管模型与 Linux systemd 生态缝合在一起UseSystemd()一行代码即可获得启动就绪通知、优雅停机、日志格式适配三项能力并且通过精心设计的检测逻辑保证在非 systemd 环境下完全无副作用。其底层实现——从SYSTEMD_EXEC_PID//proc双路径检测、Unix 域套接字发送READY1到SIGTERM到IHostApplicationLifetime.StopApplication()的信号桥接——都在 src/libraries/Microsoft.Extensions.Hosting.Systemd 目录下有清晰、可追踪的源码与测试支撑适合作为理解 .NET 托管生命周期扩展机制的范本。【免费下载链接】runtime.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.项目地址: https://gitcode.com/GitHub_Trending/runtime6/runtime创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表