LimitNPROC と TasksMax:OpenVPN ユニットが数えるもの
LimitNPROC は一人のユーザーのプロセスをシステム全体で数え、TasksMax は一つのユニットの cgroup を制限します。どちらかを引き上げる前に実効値を読み取ってください。
OpenVPN の起動が失敗すると、多くの場合、リソースに関する苦情と、制限を引き上げるという善意の提案が一緒に届きます。そしてその提案が効くのはおよそ半分のケースです。そうした失敗の背後には、互いに関係のない二つのカウンターがあります。一つは、実在のユーザー ID が所有するタスク数に対するカーネルの制限で、プロセスの実行時に継承されます。もう一つは、サービス管理者がユニットとそこから fork されるすべてに適用する cgroup の予算です。実際に動いているユニットについて両方の実効値を読むほうが、推測より安く済みます。
二つのカウンター、二つの問い
LimitNPROC は systemd の名前で呼ばれる RLIMIT_NPROC で、その計上の単位は実在のユーザー ID です。そのユーザーが所有するすべてのプロセスが同じ合計に数えられ、他のユニット、ログインセッション、スケジューラのジョブが始めた作業も含まれます。したがってこの制限は、サービスユニットの内部に設定されていてもサービスごとの上限ではなく、クライアントごとにヘルパーを fork するサービスは、そのアカウントで動く他のすべてと競合します。同じユーザー配下の多忙な cron ジョブが、VPN ユニットを単独では決して越えなかった線の向こうへ押しやることがあります。
TasksMax の所有者は別です。ユニットの cgroup 上の pids コントローラで、プロセスではなくタスクを数えます。スレッドもタスクなので、単一スレッドに見えてもワーカープールを持つデーモンは、一見してプロセス数よりはるかに大きい予算を消費することがあります。EffectiveTasksMax は、親スライスが意見を述べた後に cgroup 階層が実際に何を許すかを報告します。
root の例外が答えを変える理由
カーネルは CAP_SYS_RESOURCE または CAP_SYS_ADMIN を持つプロセスに RLIMIT_NPROC を適用しないため、特権を手放さずに root で動くユニットはその設定では事実上無制限になります。この一つの例外が、人々が交わす相反する助言を説明します——LimitNPROC を引き上げると特権を手放した配備は直り、特権を保ったままの配備には何の効果もありません。その設定を評価する前に、User= と、デーモンが起動後に身元を変えるかどうかを確認してください。特権を持つユニットが後から非特権アカウントへ切り替わると、切り替わった瞬間に再び制限の下に戻ります。そのため失敗の時系列も読む価値があります。
失敗の段階は範囲をさらに狭めます。実行準備中の拒否、デーモン自身が報告する fork のエラー、cgroup 内でのタスク生成の拒否は、それぞれ異なる journal の行を生み、そのうち一つだけがより大きな予算で解決します。
読むべきは実効値であり、シェルの値ではない
対話シェルの ulimit -u はそのシェルを説明するものです。サービス管理者がデーモンに渡した値は説明せず、cgroup の計上についても何も語りません。代わりにユニットへ問い合わせてください。手引きの例ではなく、ホストに実在するインスタンス名を使います。
systemctl show [email protected] -p User -p LimitNPROC -p TasksCurrent -p TasksMax -p EffectiveTasksMax
cat /proc/$(systemctl show -p MainPID --value [email protected])/limits | grep -i processes
コンテナの中では、さらに二つの読み取りが必要です。infinity という内側の値はゲストの見え方しか説明しないからです。ホストの所有者にコンテナ自身の pids 上限を尋ね、内側のサービス管理者がそのコントローラを委任できるのかを確認してください。入れ子のユニットは、親 cgroup が与えなかった予算を持つことはできません。
修正の規模を見積もり、負荷の下で証明する
数値はエラーの文言ではなく、ワークロードから選んでください。デーモンが接続クライアントごと、またはヘルパー呼び出しごとに起動するプロセスを数え、配備が耐えると想定される同時実行数を掛け、管理用セッションの余裕を加え、その数値を二つの実効値と比べます。ローカルの drop-in 上書きは変更をレビュー可能に保ち、パッケージ更新を生き延びます。一方、同梱のユニットファイルを編集すると、次の更新が置き換えるまで変更が見えなくなる。上書きのそばに計算を書いておいてください。次にその値を上げ下げする人に必要なのは、最終的な数値ではなく前提だからです。
予算の変更は実行時に適用されるため、再読み込みだけではすでに動いているプロセスのサイズは変わりません。通常のライフサイクルでユニットを再起動し、セッションの中断を受け入れ、新しいインスタンスでカウンターを読み直してください。
sudo systemctl daemon-reload
sudo systemctl restart [email protected]
systemctl show [email protected] -p TasksCurrent -p EffectiveTasksMax
sudo journalctl -u [email protected] -b --no-pager | tail -n 20
修正を証明するのは、意図した負荷を負いながらタスク数が両方の上限を下回る再起動済みのユニットと、同じ段階でタスクや fork の枯渇をもう報告しない journal です。変更とともに前後の数値を残してください。次の調査が同じ助言ではなく計測から始まります。それでも数値が上限に達するなら、制限が偶然小さすぎたのではなく計算が誤っていたのであり、その違いは次の再起動の前に知っておく価値があります。