ExecStart はシェルではない:VPN ヘルパーユニットの修正
ExecStart は引数付きでプログラムを実行するもので、シェルのパイプラインではありません。明示的なインタプリタか実際のスクリプトを使い、ユニットを検証して journal を読みます。
動作するコマンドラインを ExecStart にコピーして再読み込みすると、引数がファイル名として扱われ、ユニットは失敗するか無言で終了します。systemd に不具合はありません。ExecStart は引数リストでありシェルではないため、パイプ・リダイレクト・条件分岐はターミナルを離れた瞬間に意味を失います。シェルをどこに置くかを決め、何を実行すべきかを正確に述べるユニットを書きましょう。
ExecStart 内のパイプラインが何もしない理由
ターミナルでコマンドを打つと、まずシェルがそれを解析します。パイプラインを組み、リダイレクト用にファイルを開き、変数を展開してから、はじめてプログラムが起動します。systemd はそのどれも行いません。独自の規則で ExecStart を分割し、得られたプログラムをその引数で実行するだけです。したがって > は文字通りの引数として届き、| は二つのプロセスをつなぐのではなくプログラムに渡されます。ユニットが誤動作しているのではなく、構文が別の意味だったのです。
これは怠慢ではなく意図的な設計です。引数の扱いが予測可能であることが、ユニットを監査可能にします。驚くのは、シェルがあると思い込んでいる人だけです。
何が実行されるかを正確に述べるユニットを書く
journal で目的が見える小さなユニットから始めると、解析の問題とトンネルの問題を切り分けられます。これはバイナリのバージョンを報告するだけです。
[Unit]
Description=Report the installed OpenVPN version
[Service]
Type=oneshot
ExecStart=/usr/sbin/openvpn --version
StandardOutput=journal
StandardError=journalパスはプレースホルダーなので、実際にインストールされているバイナリに置き換えます。Type=oneshot は最後まで実行されるコマンドに適しており、journal の設定によりその出力はターミナルなしで読める場所に残ります。インストール用のセクションは意図的にありません。そのためユニットは起動時に有効化されるのではなく、手動で呼び出します。開始前にファイルを検証してください。ディレクティブ名の打ち間違いは、パスの打ち間違いとは異なる形で失敗します。
systemd-analyze verify /etc/systemd/system/openvpn-version.service
systemctl start openvpn-version.serviceシェルのロジックはスクリプトに置く
ヘルパーに条件分岐が本当に必要なら、そのロジックをスクリプトに置き、明示的に呼び出します。スクリプトにはインタプリタ行、実行権限、サービスが報告できる終了コードが必要です。/bin/sh -c を引用符付きのコマンドとともに起動するのは確かに動きますが、引用規則をユニットの中に隠し、レビューを難しくします。名前付きスクリプトなら手元で試せ、後からユニットを編集せずに差し替えられます。対話ユーザーのシェルプロファイルに頼らないでください。サービスは独自の環境で起動します。
相対パスが重要になる場合はWorkingDirectory を設定し、環境変数はターミナルで export するのではなく、ユニットが対応するディレクティブで渡します。秘密情報は保護されたファイルやクレデンシャルに置くもので、journal に記録されうる引数リストに置くものではありません。
実際に存在するユニットを確認する
drop-in は、編集した覚えのあるファイルに触れずに挙動を変えます。マネージャーが読み込んだ内容と、最後の実行が報告した内容を読みましょう。
systemctl cat vpn-helper.service
journalctl -u vpn-helper.service -n 20 --no-pagerユニットのダンプには drop-in が読み込み順に含まれ、予期しない ExecStart はたいていそこから来ます。変更したらマネージャーを再読み込みし、新しい定義が systemctl cat の出力どおりであることを確認します。journal には正確なコマンドラインと終了ステータスが残ります。これにより、引数の問題を、バイナリの欠落や権限エラーと切り分けられます。次に、ヘルパーを既存の VPN ライフサイクルに属させるか、手動の診断のままにするかを決めます。二つ目のバックグラウンドトンネルプロセスは、競合する所有者を生むからです。
要求に応じて正しい出力を出すユニットは、最初のマイルストーンとして十分です。まだトンネルではありませんし、トンネルのふりをするべきでもありません。