systemd時代に困らないためのlimits設定

数年前に、こういう記事「ulimitが効かない不安を無くす設定」を書きました。しかし、ディストリビューションのバージョンが上がり、デーモン管理が systemd に変わったことで、インターネットのゴミとなりつつあります。

そのため今回は、その次世代バージョン的な内容ということで、systemd の場合はこうしておけば見えない敵と闘うこともなくなるはずです、というものになります。例によって、抑えきれていないパターンがあったら御免なさいです、押忍。



limits設定で目指す所

復習になりますが、limits の設定で困るのはだいたいこういうパターンでしょう。

  • 作業中ユーザーのシェルのlimits設定が思い通りにならない
  • コンソール/SSHログインしてデーモンを再起動したら、limits設定が戻っていた
  • su/sudoを使ってデーモンを再起動したら同上
  • デーモンをシステムに自動再起動させたら同上
  • OSを再起動したら同上

  • limits の設定といっても、ulimit -n の値である「Max open files」が主であり、この設定がデフォルトの 1024 のままだったり、引き上げたのが戻ったりすると、高トラフィックを捌いているデーモンが
      Too many open files
    というログを吐き出し、ほぼシステムが半壊する、というのがお決まりです。

    こういったパターンで苦しむことがなくなるよう、またこの先数年は有効であるための設定をまとめていきます。検証環境は CentOS 7.2 になります。

    また、ここでは ulimit -n の設定上限値である 1006500 をつかっていきます。これは、limits設定で引っかかって困ることはあっても、多くしすぎて困ることはほぼないからで、その適正値については考えるのをやめたカーズです。


    ログインシェルで有効にする

    SSHでログインした時に ulimit -n を確認すると、こんな感じです。

    ぶっちゃけ、シェルのファイル数上限なんてどうでもいいんですが……変更するとしたら、こうです。

    ログインし直すと、該当のユーザーのみ変更されているのがわかります。

    一度ログインしたシェルでユーザーを切り替えても、limits値は継承されるようです。これはおそらく、「sshd: root@pts/0」という同一プロセス内での出来事であるからでしょう。

    一般ユーザーから sudo すると、ちゃんと有効になります。これは sshd と違って別プロセスとなるからでしょう。

    コンソールからログインしても、挙動は同じです。
    この辺は、皆大嫌いPAMが関連していて、limits設定が効いてくれているのは、これらの設定のお陰です。本当にあまり好きじゃない部分だし、話が脱線しちゃうので続きはググッてくださいな。


    restart後のデーモンで有効にする

    さて、これで root ユーザーで有効になったからとウキウキウォッチンでデーモンを再起動してみると……

    変わらねぇ(怒)
    皆さんが直感的に嫌なヤツがこれじゃないでしょうか。原因は、PAMの処理を通らないからなんですが、これの解消方法は、systemd の設定に委ねることになります。

    /usr/lib/systemd/system/ の方にも書けるのですが、あちらはパッケージの領域的な感じで込み入っているので、/etc/ でやる方が良いでしょう。
    これで restart すると……

    デーモンプロセス自体は restart できているのですが、怒られて肝心の設定が反映されていません。言われるがままに daemon-reload を実行すると……

    ・・・グレートですよ こいつぁ・・・

    OSを再起動しても・・・ッッ

    ・・・グレートですよ こいつぁ・・・!!

    ただひとつ弱点があるとしたら、デーモン毎に設定を置かないといけないということくらいです。この、restart によるデーモンプロセス作り直しにおける、全デーモンへ有効にする設定置き場や項目はなさそうでした。


    reboot後に全環境で有効にする

    restart の場合は、上記のように各デーモン用設定を置かないといけないのですが、もしほぼ全ての環境に対して同一の値を反映してもよいならば、このように包括的な設定が可能です。

    reboot が前提ではありますが、こうすることで、一般ユーザーのシェルや、自身で明示的に設定していないであろうデーモン全てに有効になります。

    2つ注意点として、1つめは systemd が少し古いと(v208で確認)、 /etc/systemd/system.conf.d の形が有効ではないので、その場合は v219 などに上げるか、/etc/systemd/system.conf に記述することになります。

    もう1つは、こちらの設定だけではデーモン restart 時にデフォに戻ってしまうため、デーモンごとの設定の方が必須であるということです。


    initスクリプトの場合

    CentOS7 でも /etc/init.d を使うことは可能ですが、/etc/initscript が廃止されているので、デーモン起動時に limits を確実に設定するには、起動スクリプトに ulimit -n を仕込む他なさそうです。

    お世話になっているソフトウェアをやり玉的にしてアレなんですが、td-agent(Fluentd) はまさにそれで、/etc/init.d/td-agent に ulimit -n 65536 を直書きしているため、動作的には不安はないものの、やはり systemd との統一感がなくなってしまうため、こういったシステムは systemd への切り替えが望まれるところでしょう。

    また、systemd はデーモンプロセスの死活監視と自動復旧も行ってくれるので、もはや init系 でいくことは時代遅れになりつつあると感じています。


    設定まとめ

    ここで最後に、真に必要であろう設定をまとめてみます。

    私的な一般的要件は、
  • ログインシェルは別にデフォルト値で大丈夫
  • 特定のデーモンは確実にlimitsを設定したい
  • CIテストでのlimits値チェックでは reboot し難く、restartで即反映させたい

  • こんな感じだと思われるので、要は自社で扱っている、アプリケーション用のミドルウェアが全てrestartで設定されればOK!ということで、ミドルウェアのパッケージを入れる前段階に、例えば
  • /etc/systemd/system/nginx.service.d/limits.conf
  • /etc/systemd/system/mysql.service.d/limits.conf
  • を作成してしまうというレシピにしたらよいかと思います。


    それ以外をどこまで設定するかは好みの問題ですが、整理してみると非常に簡潔になったので、ホッと一息というところでしょうか。また数年後に、記事の焼き直しが必要にならないことを願うばかりです;-)