ローファイ日記

出てくるコード片、ぼくが書いたものは断りがない場合 MIT License としています http://udzura.mit-license.org/

memory cgroupのevent_controlについて

前提

cgroup.event_control の概要

特定のcgroupに何かが起こった時に、eventfdを経由してユーザランドに通知を送り込む仕組みがある。 cgroup.event_control# an interface for event_fd() とされている。

cgroupを一つ作ると、原則そのファイルも新しく一つできる。あるcgroupに一対一で存在するという認識で良さそう。

root@ubuntu-bionic:~# mkdir /sys/fs/cgroup/memory/udzura-sample
root@ubuntu-bionic:~# ls -l /sys/fs/cgroup/memory/udzura-sample
total 0
-rw-r--r-- 1 root root 0 Jul 23 09:24 cgroup.clone_children
--w--w--w- 1 root root 0 Jul 23 09:24 cgroup.event_control
-rw-r--r-- 1 root root 0 Jul 23 09:24 cgroup.procs
-rw-r--r-- 1 root root 0 Jul 23 09:24 memory.failcnt
--w------- 1 root root 0 Jul 23 09:24 memory.force_empty
-rw-r--r-- 1 root root 0 Jul 23 09:24 memory.kmem.failcnt
-rw-r--r-- 1 root root 0 Jul 23 09:24 memory.kmem.limit_in_bytes
-rw-r--r-- 1 root root 0 Jul 23 09:24 memory.kmem.max_usage_in_bytes
-r--r--r-- 1 root root 0 Jul 23 09:24 memory.kmem.slabinfo
-rw-r--r-- 1 root root 0 Jul 23 09:24 memory.kmem.tcp.failcnt
-rw-r--r-- 1 root root 0 Jul 23 09:24 memory.kmem.tcp.limit_in_bytes
-rw-r--r-- 1 root root 0 Jul 23 09:24 memory.kmem.tcp.max_usage_in_bytes
-r--r--r-- 1 root root 0 Jul 23 09:24 memory.kmem.tcp.usage_in_bytes
-r--r--r-- 1 root root 0 Jul 23 09:24 memory.kmem.usage_in_bytes
-rw-r--r-- 1 root root 0 Jul 23 09:24 memory.limit_in_bytes
-rw-r--r-- 1 root root 0 Jul 23 09:24 memory.max_usage_in_bytes
-rw-r--r-- 1 root root 0 Jul 23 09:24 memory.move_charge_at_immigrate
-r--r--r-- 1 root root 0 Jul 23 09:24 memory.numa_stat
-rw-r--r-- 1 root root 0 Jul 23 09:24 memory.oom_control
---------- 1 root root 0 Jul 23 09:24 memory.pressure_level
-rw-r--r-- 1 root root 0 Jul 23 09:24 memory.soft_limit_in_bytes
-r--r--r-- 1 root root 0 Jul 23 09:24 memory.stat
-rw-r--r-- 1 root root 0 Jul 23 09:24 memory.swappiness
-r--r--r-- 1 root root 0 Jul 23 09:24 memory.usage_in_bytes
-rw-r--r-- 1 root root 0 Jul 23 09:24 memory.use_hierarchy
-rw-r--r-- 1 root root 0 Jul 23 09:24 notify_on_release
-rw-r--r-- 1 root root 0 Jul 23 09:24 tasks

で、今回はmemory controllerでの実装をまとめる(というか、他のcontrollerでの挙動を教えて欲しい...)。

eventfd とは...

何かしらのイベント通知を待ち受けるためのファイル記述子(fd)。普通のfdなので、read(2)などで読み取り可能になるのを待つことができるなど、一般のファイル経由で作ったfdと同様にプログラムの上で扱うことができる。

昔、パイプを使ってプロセス間での通知を試したことがあったが、eventfdの場合はカーネルからのイベントもユーザ空間で待ち受けることができる。

udzura.hatenablog.jp

基本的な使い方

  • eventfdを作る ...(1)
  • memoru cgroupのあるcgroupに紐づく(=そのcgroupのディレクトリ配下にある)、特定のファイルのfdを得る ...(2)。イベントごとに異なる
  • (1)と(2)のfd、プラス場合によってその他のパラメータを同じcgroupに紐づく cgroup.event_control に書き込む
  • 以上の手順により、(1)にイベントが登録される
    • この手順を見て、「fdってプロセスごとにしか番号がユニークじゃないのにどうなってるんだろう?」と思っているんだけれど、カーネルレベルならプロセスIDとfdの番号だけでどういうファイルを掴んでいるかを特定できるという認識なので、頑張っているのだろう。ソース読まないといけない。
    • ということでこの操作は同じプロセス内で行うのが基本っぽい

このように錬成されたeventfdをread()することで、イベントが発生したらデータを読み取ることができる。ブロッキングIOの場合は読み取り可能になるまで待つ。このようにeventfd自体は他のものと同様にファイル記述子なので、ノンブロッキングにしたり、poll(2)とかepoll(7)とかそういうやつで多重に待ち受けたり、色々と扱いやすいし、むやみに新しい概念を覚える必要なく使える。

どのようなイベントが存在するか?

カーネルのドキュメント によると現在の最新のカーネルでは3つ存在する模様。

    1. Memory thresholds より
    2. "<event_fd> <fd of memory.usage_in_bytes> <threshold>" というものを書き込む
      • thresholdはバイト単位
      • グループ内のメモリ利用量がそのスレッショルドに達したら通知される
      • memswの方でも検知できる、同じようなインタフェース。
    1. OOM Control より
    2. <event_fd> <fd of memory.oom_control> を書き込む
      • OOM がグループで怒ったら通知される
      • root cgroup (/) では動かない
    3. memory.oom_control というファイルもある、ここに 1 を書き込むとOOMが無効になる
      • 必要なメモリが不足すると、OOM-waitqueueにタスクが突っ込まれ、停止する
      • 再び動かすには、Limitを大きくするか、メモリ利用状況を(タスクを殺すなどで)改善するしかない
    4. memory.oom_control を読み取ると、以下の二行の情報がわかる
      • oom_kill_disable OOM が無効かどうか。0が有効、1が無効(二重否定感)
      • under_oom 1 であれば、OOMされるべきプロセスがあり、現在停止しているところ
    5. memory.oom_control oom_kill_disable = 1 にしてなおかつeventfdを登録しておけば、OOMでKillせずにcgroup設定値を自動で拡張するなどの対処が取れると思われるのだが、試した方が良さそう。 oom_control oom_kill_disable = 0 だとイベント登録しても殺されてしまう?
    1. Memory Pressure より
    2. "<event_fd> <level[,mode]>"` を書き込む
    3. level は三つある
      • "low": システムが新しいメモリ割り当てのためにReclaimを行った時
      • "medium": システムがスワップを作ったり、ページアウトを行ったりしている
      • "critical": システムがスラッシングしており、OOMが起こりそう、あるいはOOMのトリガーが走ろうとしている
    4. modeも三つある
      • "default": デフォルトの挙動。すなわちA->B->Cとcgroupがあった時、イベントがセットアップされた中で一番階層が低いcgroupにしか通知がされず、イベント登録がある階層までメモリプレッシャーの通知は伝播する
      • "hierarchy": A->B->Cとcgroupがあった時、Cでメモリプレッシャーがあったら必ずB→Aとイベント通知は伝播していく
      • "local": イベントの登録がそのものズバリのcgroupにない限り通知されない。例えば、A->B->Cとcgroupがあった時、CのメモリプレッシャーはCのイベントの有無に関係なくBより上には通知されない、デフォルトでは、Cにイベント登録がない場合、CのメモリプレッシャーはBに通知が行く

明日はこれらのイベントを実際にコードで取り扱ってみる。

参考

d.hatena.ne.jp

tenforward.hatenablog.com

suEXEC セキュリティモデルをベースにしたコンテナ起動時のバリデーションの提案

本稿は、第2回WSA研究会で発表した内容の予稿である。末尾に当日の質疑応答を追記している。

websystemarchitecture.hatenablog.jp

発表スライド

speakerdeck.com

続きを読む

serverengine を使い、Rubyでもサーバーを書こう

Rubyを書いていると、サーバを書きたくなることがあります。皆さんもそうだと思います。

ということで今日はRubyでスッとサーバを書くためのgem、serverengineの簡単な使い方メモ。

github.com

Rubyでサーバを書きたくなった時

そもそも的に、Rubyでただサーバを書くのは非常に簡単である。具体的には Kernel#loop などを回してその中でリクエストを待ったり、何かしら処理を行えば終わり。特別なgemは必要ないし、TCPを扱うクラスなども組み込みで用意されている。

以下のような9行のスクリプトを起動すれば、サーバを書いたと言える。ところで TCPServer#accept_nonblockでないと、acceptでブロックしてしまって終了処理が遅れたりするのでノンブロッキングの方のAPIを好んで使うのがいいだろう。

require 'socket'
server = TCPServer.new(5310)
loop do
  c = server.accept_nonblock(exception: false)
  if c.is_a?(IO)
    c.puts("Hello")
    c.close
  end
end
$ telnet localhost 5310
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
Hello
Connection closed by foreign host.

「ちゃんとした」サーバを書く

とはいえこの簡素なサーバをプロダクションの何かに投入するのは不安があるだろう。具体的には運用面。きょうびのサーバーは、以下のような機能を実装していてほしい。

  • ログを吐いてほしい
  • pid fileを吐いてほしい
  • SIGINT、SIGTERMなどを正しく扱ってほしい。Interrupt例外とか生で出て欲しくない
  • 何ならSIGUSR2あたりを受け取ったら設定のリロードをしてほしい
  • prefork型のワーカをたくさん立ち上げて処理を負荷分散してほしい。Rubyなこともあり、マルチプロセスがいい
  • プロセスが ruby hoge.rb だとカッコ良くないので、かっこいい名前をpsコマンドで出したい(?)

これらをRubyで一から実装していくのはなかなか手間であるが、serverengine gemを使えばこれらの機能はそのままパッケージングされている。

続きを読む

initのお仕事〜tiniのコードを読んでみた

急にinitが何をしているのか、何をすべきなのかが気になったので調べてみた。一緒に600行強のinit実装であるtiniソースコードをざっくり読んだ。この場を借りてメモしていく。

the PID 1 problem

RubyコミュニティなどではPassengerで有名なPhusion社のブログに、Docker and the PID 1 zombie reaping problemという記事が掲載されている。

blog.phusion.nl

  • ゾンビプロセスをreapしてくれないと困る
  • SIGTERMなどでPID=1が先に死んだらその子プロセスを処理してくれないと困る

みたいな内容が書いてある。詳細は読んでみてほしい。

システムコンテナ(参考)と呼ばれる種類のコンテナを作る場合、任意のプログラムをコンテナ内部のPID=1とするのではなく、上記のような振る舞いをする軽量なinitプログラムを経由して呼び出すというプラクティスがある。tiniはそういった軽量initの一つで、dockerの --init オプションで使われるコマンドとしても知られている。

github.com

“A tiny but valid init for containers”と自称している。

tini のコードリーディング

2018/04/16現在、プログラムの主要部分である tini.c は611行しかない。読み切るのもそんなに難しくないので目を通してみる。単にログを出したり便利計算をするだけのマクロやdefineによる分岐はざっくり省略している。

続きを読む