銀の光と碧い空

クラウドなインフラとC#なアプリ開発の狭間にいるエンジニアの日々

.NET Core on Linux で systemd scriptでプロセス管理する (手抜き版)

ASP.NET Core でLinuxでホストする場合、以前の公式ドキュメントではsupervisorを使う方法が紹介されていました。ただ、CentOSやRHELだとEPELリポジトリにあるしなあと思っていたら、いつの間にか公式ドキュメントがsystemdを利用したサンプルに変わっていました。せっかくなので手順を一通りなぞってみます。

Publish to a Linux Production Environment | Microsoft Docs

手抜きポイント

  • リバースプロキシは設定しない (適当なポートでローカルからアクセスして動作確認)
  • firewalldも設定しない (これは独立して設定できる)
  • SELinuxまわりは追加の設定しない (詳細は最後に)

SELinuxは追加の設定をしないというだけで、enforcingの状態です*1

# sestatus 
SELinux status:                 enabled
SELinuxfs mount:                /sys/fs/selinux
SELinux root directory:         /etc/selinux
Loaded policy name:             targeted
Current mode:                   enforcing
Mode from config file:          enforcing
Policy MLS status:              enabled
Policy deny_unknown status:     allowed
Max kernel policy version:      28

実行バイナリとユーザーの用意

systemd のscriptを使うだけならdotnet publishせずにdotnet runでプロジェクトを指定して実行することもできます*2が、せっかくなのでpublishして実行バイナリ一式を適当な場所に配置して、専用のユーザーで実行してみることにします。まずはpublishします。

$ dotnet publish -c Release

bin/Release/netcoreapp1.1/publish/以下に必要なバイナリ一式が生成されるので適当な場所に配置します。/var/www-aspnetを作って配置してみました。

$ sudo mkdir -p /var/www-aspnet/
$ sudo cp -R bin/Release/netcoreapp1.1/publish/* /var/www-aspnet

dotnetプロセスの実行用に専用のユーザーdotnetuserをログインシェルなしで作ってみます。

$ sudo useradd -s /sbin/nologin dotnetuser
$ sudo chown -R dotnetuser /var/www-aspnet

systemd scriptの作成と実行

それではsystemdのscriptを記述します。/etc/systemd/systemaspnet.serviceという名前で以下のように記述します。実行するコマンドのパスはフルパスで、以下の例はRed Hat Software Collections でインストールした時のパスです。

[Unit]
Description = hello aspnet core
Documentation = 

Wants=network.target  
After=network.target  

[Service]
ExecStart = /opt/rh/rh-dotnetcore11/root/usr/bin/dotnet AspNetLabo.dll 
WorkingDirectory = /var/www-aspnet/AspNetLabo/publish/ 
Restart = always
RestartSec = 10
User = dotnetuser
Group=dotnetuser
Environment=ASPNETCORE_ENVIRONMENT=Production

[Install]
WantedBy = multi-user.target

記述したらsystemctlで起動します。起動したかどうかの確認もできます。scriptファイルを修正した時などはdaemon-reloadしておきます。

$ sudo systemctl daemon-reload
$ sudo systemctl start aspnet.service
$  systemctl status aspnet.service 
● aspnet.service - hello aspnet core
   Loaded: loaded (/etc/systemd/system/aspnet.service; disabled; vendor preset: disabled)
   Active: active (running) since Tue 2017-01-10 19:40:46 JST; 7s ago
 Main PID: 6815 (dotnet)
   CGroup: /system.slice/aspnet.service
           └─6815 /opt/rh/rh-dotnetcore11/root/usr/bin/dotnet AspNetLabo.dll

Jan 10 19:40:46 localhost.localdomain systemd[1]: Started hello aspnet core.
Jan 10 19:40:46 localhost.localdomain systemd[1]: Starting hello aspnet core...
Jan 10 19:40:46 localhost.localdomain dotnet[6815]: info: Microsoft.Extensions.DependencyInjection.DataProtectionServices[0]
Jan 10 19:40:46 localhost.localdomain dotnet[6815]: User profile is available. Using '/home/dotnetuser/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest.
Jan 10 19:40:47 localhost.localdomain dotnet[6815]: Hosting environment: Production
Jan 10 19:40:47 localhost.localdomain dotnet[6815]: Content root path: /var/www-aspnet/
Jan 10 19:40:47 localhost.localdomain dotnet[6815]: Now listening on: http://localhost:5000
Jan 10 19:40:47 localhost.localdomain dotnet[6815]: Application started. Press Ctrl+C to shut down.

起動に失敗したときなどにログを見たい場合はjournalctlコマンドを使います。-uオプションでユニットを指定、-fでtailできます。デフォルトのテンプレートで作ったままなので比較的ログが多くでています。

$ $ journalctl -u aspnet.service -f
-- Logs begin at Tue 2017-01-10 18:29:33 JST. --
Jan 10 18:58:19 localhost.localdomain systemd[1]: Stopping hello aspnet core...
Jan 10 18:58:19 localhost.localdomain systemd[1]: Stopped hello aspnet core.
Jan 10 19:40:46 localhost.localdomain systemd[1]: Started hello aspnet core.
Jan 10 19:40:46 localhost.localdomain systemd[1]: Starting hello aspnet core...
Jan 10 19:40:46 localhost.localdomain dotnet[6815]: info: Microsoft.Extensions.DependencyInjection.DataProtectionServices[0]
Jan 10 19:40:46 localhost.localdomain dotnet[6815]: User profile is available. Using '/home/dotnetuser/.aspnet/DataProtection-Keys' as key repository; keys will not be encrypted at rest.
Jan 10 19:40:47 localhost.localdomain dotnet[6815]: Hosting environment: Production
Jan 10 19:40:47 localhost.localdomain dotnet[6815]: Content root path: /var/www-aspnet/
Jan 10 19:40:47 localhost.localdomain dotnet[6815]: Now listening on: http://localhost:5000
Jan 10 19:40:47 localhost.localdomain dotnet[6815]: Application started. Press Ctrl+C to shut down.

これで無事systemdで.NET Coreプロセスを起動できました。常時起動のプロセスである必要もないので、バッチとして.NET Coreプロセスを起動することもできるはずです。

systemd scriptの書き方については下記のブログが参考になると思います。

qiita.com

enakai00.hatenablog.com

SELinuxとの関係

最初にSELinuxはenforcingだが追加の設定はしないといいました。dotnetプロセスのSELinuxでの状態を見てみましょう。

$ ps -eZ | grep dotnet
system_u:system_r:unconfined_service_t:s0 6815 ? 00:00:00 dotnet

unconfined_service_tというタイプでdotnetプロセスが実行されています。これは制限のないプロセスと呼ばれ、SELinuxが有効な状態でも制約なくファイルにアクセスできます*3

3.2. 制限のないプロセス

ですので、SELinuxを使ってdotnetプロセスに制限をかけようとする場合は、dotnetプロセスが特定のSELinuxコンテキストで実行されるようにしないといけません。そもそも、dotnetプロセスがデフォルトのターゲットでは制限のないプロセスとなっているので、手作業で設定する必要が現時点ではあります。単純にsytemdサービスの起動時に特定のコンテキストにするのであれば下記のブログが参考になりそうです。

qiita.com

とはいえやはり手間ですね...ということで、現時点での解としてはdockerコンテナを使って運用することにして、dockerコンテナごと管理する方法が取れると思われます。例えば、dotnet含めてOpenShiftであればdockerコンテナ内で起動するプロセス、実行するdockerコンテナ内のファイル、Volumeとして接続したファイル、がSELinux管理下におけます。こちらも検討するのがよさそうです。

*1:とりあえずSELinux 無効化というのはそろそろなしじゃないかなあというのが正直な気持ちの今日この頃です

*2:その場合プロジェクトをビルドして実行される

*3:PhysicalFileResultなどで任意のファイルを返せる