コンテンツにスキップ

サービスとして動かす

インストーラは systemd ユニットを有効にして起動したままにします。このページは そのユニットを順に読み、操作方法を示し、二つの失敗の形を外から見たままに説明 します。起動しないサーバーと、起動中のサーバーが設定を拒否する場合です。

ターミナルウィンドウ
systemctl cat pingclair

重要なキーは次のとおりです。

[Service]
Type=notify
NotifyAccess=main
User=pingclair
Group=pingclair
AmbientCapabilities=CAP_NET_BIND_SERVICE
CapabilityBoundingSet=CAP_NET_BIND_SERVICE
Environment="RUST_LOG=info"
ExecStart=/usr/local/bin/pingclair run /etc/Pingclair/Pingclairfile
ExecReload=/bin/kill -USR1 $MAINPID
WorkingDirectory=/var/lib/pingclair
Restart=on-failure
RestartPreventExitStatus=1
RestartSec=5s
LimitNOFILE=1048576
LimitNPROC=512
ProtectSystem=full
PrivateTmp=true
NoNewPrivileges=true

順に読みます。

  • Type=notifyNotifyAccess=main: サーバーはリスナーのバインドが終わった 時点で systemd に伝えます。したがって systemctl start はプロセスの存在 ではなく、プロキシが応答できるまで待ちます。
  • User=pingclairAmbientCapabilities=CAP_NET_BIND_SERVICE: サーバーは 非特権で動きながら 80 と 443 にバインドできます。
  • PINGCLAIR_TLS_STORE は意図的に置いていません。サービスアカウントのホームは /var/lib/pingclair なので、証明書は /var/lib/pingclair/.local/share/pingclair にあります——バイナリ自身の既定で、インストーラが作成・移行するディレクトリで、 pingclair environ が印字するパスです。ここでストアを名指しすれば、すでに 一つの答えがある問いに二つ目の答えを与えることになります。
  • ExecStartPrevalidate を走らせることは意図的にしていません。安全な検査 場所に見えますが、それが罠です。systemdRestartPreventExitStatus= を 適用するのは主プロセスであって、失敗した事前コマンドではありません。そのため コンパイラが拒否する設定は、ユニットを failed のまま残す代わりに 5 秒ごとに 再試行されていました。サーバー自身が何かをバインドする前にファイルを コンパイルし、拒否するときは終了コード 1 で終わります。それが上の再起動 ポリシーが書かれた理由の終了コードで、pingclair run はそのプロセスになる ために存在します。
  • ExecReloadSIGUSR1 を送ります。これはサーバーが「ファイルを読み直せ」と 解釈する信号です。SIGHUP は意図的に無視され、それを送っていたユニットは 古い設定が動き続けたまま成功を報告していました (issue #66)。 systemd が観測できるのは kill の終了だけなので、サーバーはファイルをどう 扱ったかをユニットの status line に載せます——Serving (reloaded 1 listener(s) in 323.341µs)、あるいは Reload rejected: …——そして systemctl status がそれを表示します。詳しくは下の 再読み込みの節にあります。
  • Restart=on-failureRestartPreventExitStatus=1RestartSec=5s: 終了 コード 1 は設定もしくは証明書ストアがそもそも使えなかったことを意味するので、 5 秒ごとに再試行せず、ユニットは運用者が見るために failed のまま残ります。 それ以外の失敗は再起動されます。
  • ProtectSystem=fullPrivateTmpNoNewPrivilegesLimitNPROCLimitNOFILE: サーバーは必要なファイルシステムの見え方とプロセス上限だけを 受け取り、それ以上は受け取りません。

どちらの導入経路も同じファイルを書きます。一行インストールは scripts/pingclair.service のバイト単位のコピーを埋め込んでおり——just repo-lint は両者がずれれば失敗します——curl | bash の新規導入とリポジトリ からの導入は同じユニットを生み、 systemd-analyze verify /etc/systemd/system/pingclair.service はどちらの経路でも このユニットについて何も報告しません。

pc service はこのユニットに対する systemctl のラッパーなので、どちらでも 同じです。

目的 pc の場合 systemctl の場合
起動 sudo pc service start sudo systemctl start pingclair
停止 sudo pc service stop sudo systemctl stop pingclair
設定の再読み込み sudo pc service reload sudo systemctl reload pingclair
リスナーやプロセス全体の変更後の再起動 sudo pc service restart sudo systemctl restart pingclair
状態 pc service status systemctl status pingclair
ログを追う journalctl -u pingclair -f

pc service status はサーバーが送った準備完了行を含め、ユニット自身の見方を 表示します。

● pingclair.service - Pingclair High-Performance Web Server
Loaded: loaded (/etc/systemd/system/pingclair.service; enabled; preset: enabled)
Active: active (running)
Docs: https://github.com/dorianverlaine/pingclair
Main PID: 1808 (pingclair)
Status: "Serving (reloaded 1 listener(s) in 323.341µs)"

編集した /etc/Pingclair/Pingclairfile は一つの信号で実行中のサーバーに届き、 二つのコマンドがその信号を送ります。

SIGUSR1 が再読み込みの信号で、追加の設定は不要です。

ターミナルウィンドウ
sudo kill -USR1 "$(systemctl show -p MainPID --value pingclair)"

pc service reload —— 同じ呼び出しである sudo systemctl reload pingclair も —— がその信号を代わりに送ります。ユニットの ExecReload/bin/kill -USR1 $MAINPID で、明らかな命令が動く命令になりました。SIGHUP を 送っていたユニットは成功を報告して何も適用しませんでした。それが issue #66 に記録されて います。

pingclair reload は Admin API 経由で同じコードに到達し、サーバーがファイルを どう見たかを報告します。グローバルオプションの admin が必要です。

✅ Configuration reloaded successfully
Error: ❌ Reload failed (400): HTTP/1.1 400 Bad Request

systemctl reload が報告できるのは一つだけです。kill が信号を届けたこと。 サーバーはそのあとにファイルを読むので、その判断はユニットの status line と ジャーナルに出ます。pc service reload は設定が適用されたと主張する代わりに、 そう伝えます。

$ sudo pc service reload
✅ Reload signal delivered to pingclair.service
ℹ️ The result lands a moment later: `systemctl status pingclair`
or `journalctl -u pingclair -n 20`
$ systemctl status pingclair --no-pager | grep Status
Status: "Serving (reloaded 1 listener(s) in 323.341µs)"

実行中のサーバーがファイルの要求どおりに適用できないときは、以前の設定が サービスのまま残り、status line はどの変更が拒否されたかを示します。サイトを :80 から :8080 へ移すのが典型的な例です。リスナーの構成は起動時にソケットと 一緒に作り直されるためです。

Status: "Reload rejected: listener topology changed (added: ["[::]:8080"], removed: ["[::]:80"]); restart Pingclair to rebuild H1, H2, H3, and TLS together"

どの経路でも、コンパイルできない設定は以前の設定を動かしたままにします。 先に検証します。

ターミナルウィンドウ
sudo pingclair validate /etc/Pingclair/Pingclairfile

例外はプロセス全体に関わるポリシーです。trusted_proxies のような起動時に 確立されるオプションは、再起動後にしか効きません: sudo pc service restart。 リスナーを追加・移動する設定も同じように拒否されます——status line が追加・削除 されたアドレスを示します——再読み込みが適用するのはポリシーであり、新しい 待ち受けソケットではないからです。

ユニットは RUST_LOG=info を設定し、すべてを journal に送ります。

ターミナルウィンドウ
sudo journalctl -u pingclair -f
sudo journalctl -u pingclair --since '10 min ago'

起動、再読み込み、証明書の処理、そしてリクエストごとのアクセス行がそこに 現れます。

INFO pingclair::run: 🚀 Starting Pingclair v0.2.0-rc.3
INFO pingclair::run: 📄 Loaded configuration from: /etc/Pingclair/Pingclairfile
INFO pingclair::run: 🔔 Received SIGUSR1, reloading configuration from: /etc/Pingclair/Pingclairfile
INFO pingclair::run: 📋 Step 1/3: Validating configuration...
INFO pingclair::run: ✅ Configuration reload completed successfully in 323.341µs
INFO pingclair::run: 📊 1 listener(s) updated
INFO pingclair_proxy::server: 📝 Access request_id="65c09fa25d457-6" method="GET" host="localhost" path="/" status=200 bytes=18747 duration_ms=0 remote_ip=::1 user_agent="curl/8.18.0"

サーバーが拒否した再読み込みも同じように、理由と「何も変わっていない」ことと ともに記録されます。

ERROR pingclair::run: ❌ Configuration reload rejected after 414.491µs: listener topology changed (added: ["[::]:8080"], removed: ["[::]:80"]); restart Pingclair to rebuild H1, H2, H3, and TLS together kind=RestartRequired
ERROR pingclair::run: 💡 Previous configuration remains active, unchanged

自前のログをローテーション付きで欲しい場合は log シンクを設定し、 インストーラが作成してサービスユーザーに与える /var/log/pingclair の下に 書き出します。

⚠️ サービスが立ち上がらないとき

Section titled “⚠️ サービスが立ち上がらないとき”
  • is-activeactivating のままで NRestarts が増え続ける。 導入された これは古いユニットで引退した挙動です。RestartPreventExitStatus を伴わない Restart=always を積んでいたため、サーバーが拒否する設定は 5 秒ごとに再試行 され、「一度失敗したユニット」ではなく「いつまでも落ち着かないユニット」に 見えていました。原因はもう一つあります。validateExecStartPre として 走らせていたことで、RestartPreventExitStatus はそれを覆いません。導入される ユニットは Restart=on-failure + RestartPreventExitStatus=1 を積み、 事前コマンドを持ちません。拒否された起動は is-activefailed のまま、 NRestarts をゼロのままにします。古い導入では、デバッグの前にループを 止めます: sudo systemctl stop pingclair、ファイルを直し、 sudo systemctl reset-failed pingclair
  • Job for pingclair.service failed because the control process exited with error code サーバーが何かをバインドする前に設定を拒否しており、 コンパイラの理由が journal にあります。例: Error: ❌ Configuration Error: Compile error: Unsupported feature: `encode br`: Brotli is not implemented for proxied responses; use `encode zstd gzip`
  • TLS store /var/lib/pingclair/.local/share/pingclair is not writable: Permission denied ストアはサービスアカウントのものです。sudo ls -ld /var/lib/pingclair/.local/share/pingclair を確認し、所有者が pingclair であることを確かめます。
  • systemd-analyze verify が導入済みユニットについて Missing '=', ignoring line を出す。 古いワンライナー導入は、コメントが シェルに展開されたユニットを書いていました——--help の出力 25 行で、 systemd はそれを無視します。現在のインストーラで再導入するとユニットは そのまま書かれ、この報告は消えます。
  • ユニットは動いているのに何も応答しない。 リスナーはバインド済みで要求が 届いていません。インストール と同じく、まずプロバイダの ファイアウォール、次にホスト側を確認します。
  • アップグレードと削除: 再実行が何を保つか、そして全部 取り除く方法。
  • HTTPS: 証明書、ストアの場所、そして pingclair trustPINGCLAIR_TLS_STORE が必要な理由。
  • log: このページが journal から読んでいる アクセスログの出力先。