systemdのサービスにCPUを使わせすぎない方法(案)


はじめに

この記事は、gviteという「暗号通貨VITE」のフルノードを強引に稼働させるという話です。
強引にRaspberry Pi3というリソースの程々に厳しい環境で運用し、1日の稼働率を90%以上に保ちたい(ノード運営報酬が出るのは、1日のうち90%以上の稼働時間が求められるのです)という目的のもと、現状を公開していきたいと思ってやってる次第です。

ただし話としてはgvite固有のものではなく、サーバープロセスを走らせる際のリソース制御という話になるので、systemd周りの比較的汎用性のある話になると思います。

もしお役に立てるのであれば、投げ銭してもらえると嬉しいです。

  • VITEウォレットを導入し、紹介コード 18fmACcQ を入れていただく(10VITEもらえます、お得♪)
    • 140VITEまでは1日10VITEずつタップだけでもらえます

Systemdとリソース制御

今どきのSystemdでは、cgroupを使ってのリソース制御が実装されています。詳細はsystemd.resource-control(5)を見てみるといいと思うのですが今回関係したのは以下のものたち。
細かい話は他の文書をあたってください。

cpu.shares

他のプロセスとCPUの奪い合いをするケースが有った場合、どれぐらいの比率で仲良く分割するかというものです。
標準値は1024となっており、1024を持つ他のプロセスとかち合ったときは1:1に分割されます。
もしこの値を512とした場合、512:1024で1:2で分割することになります。仮に1CPUを2つのプロセス競合で分割するのであれば、ざっくり33%と66%という具合になるという仕組みです。

この値は、Systemdのユニットファイル上ではCPUSharesにて設定できます。

CPUShares=512

cpu.cfs_quota_us

cpu.sharesは競合時の分割具合という話になりますが、こちらはクォータという形で利用上限を設定します。
設定した場合、CPU単体での利用率上限ということになるため、コアが複数ある場合は、それぞれで設定されます。
%の形で設定しますが、数値の上では100倍した数値を渡すことになります。
たとえば50000であれば50%ということになります。この値はユニットファイルではCPUQuotaで設定できます。値は%で記述できます。

CPUQuota=10%

他プロセスと競合してCPU使用比率がコレを超えることになっても、強制的に指定した比率以上は使えなくなります。日常的な使用率上昇を防ぐという意味ではこちらのほうが強いでしょう。
逆に普段は使っててもいいけど他のプロセスに対して優先的にCPUを回したい(nice的なやつ)はcpu.sharesをベースに考えたほうがいいでしょう。

これらはCPU利用率ということで電力消費と発熱に影響を及ぼすと考えられます。

memory.limit_in_bytes

この値は、メモリの利用量の設定となります。実際のところは物理メモリとして割り当てられる上限という扱いで、コレを超えた場合はスワップに回されます。メモリとスワップの合計で使い切れるメモリサイズに関しては、memory.memsw.limit_in_bytesというものがあります。

Pi(3)みたいにメモリがほんのりシビアなやつ(Pi4だと4GB選択できるようになるのでかなり楽になる)だと、gviteプロセスが使うメモリサイズが結構しんどい(手元で動かす限り、300MBぐらい物理メモリ常駐の仮想メモリ2.5GBぐらい)ので、他のサービスの生存を考慮して128MB程度に抑え込んでおきたいと思います。
値は本来バイトで渡すのですが、K/M/Gなどで内部変換してくれます。
この値はMemoryLimitで渡せます。

MemoryLimit=128M 

実際に割り当ててるメモリより少なくなってるということは、常時スワップが起きそうな感じもするのですが、一度書き込んでからめったに読まない領域でもあると信じておきます。

ということで現状はこうなってる

これらを考慮したgvite向けのサービスユニット定義は、こんな感じになりました。

[Unit]
Description=gvite node

[Service]
Type=simple
ExecStart=/bin/bash -c "cd /etc/gvite && exec nice -n19 /home/example/bin/gvite"
Restart=on-failure
RestartSec=120s
StandardOutput=null
User=example
CPUShares=128
CPUQuota=10%
MemoryLimit=128M

[Install]
WantedBy=default.target
  • gviteは終了のためにシグナル(SIGTERM)を受けても、キューに残ってる(と思われる)ノード処理が終わるまでは終了しません、そのためしばらく待ちます。RestartSecが長めになってるのはそのためです
  • こんなの管理者権限では知らせるのはアブナイので、ちゃんとユーザーを切って走らせましょう
  • 動作チェック時はともかく、常時稼働であればstdoutに出る格納したノードの情報はログの肥やしになるだけなので、StandardOutputパラメータで闇の彼方へ葬ってます
  • gviteプロセス自体、niceで優先度を下げてます(他プロセスへCPUをゆずりやすくなる)

systemdとは関係ありませんが、cpufreqのondemandガバナーに対しては、/sys/devices/system/cpu/cpufreq/ondemand/ignore_nice_loadを1に設定することで、niceされてるプロセスが元でCPUクロックが上昇しないようにしています。これでgvite(含む低優先度プロセス)により電力消費の上がってしまうクロック上昇を控えるようになります。

他にありませんか?

monitを使って負荷が上昇してるときにSIGSTOP、回復したらSIGCONTを用いることで、過剰な利用をコントロールするというのもあります。
念の為併用してるので、こちらもそのうち書き込んでみるつもり。