Linuxコンテナは、プロセスのグループを追跡するだけでなく、CPU、メモリ、およびブロックI / Oの使用状況に関する多くのメトリックを公開する制御グループに依存しています。 これらのメトリックにアクセスする方法と、ネットワーク使用状況メトリックを取得する方法も見ていきます。 これは「純粋な」 LXCコンテナと Docker コンテナに関連しています。
コントロールグループを見つける
制御グループは、疑似ファイルシステムを介して公開されます。 最近のディストリビューションでは、このファイルシステムは./sys/fs/cgroup
そのディレクトリの下には、 、 などと呼ばれる devices
複数のサブディレクトリがあり、freezer
blkio
各サブディレクトリは実際には異なる cgroup 階層に対応しています。
古いシステムでは、制御グループは に /cgroup
マウントされ、明確な階層はない場合があります。 その場合、サブディレクトリを表示する代わりに、そのディレクトリに多数のファイルが表示され、場合によっては既存のコンテナに対応するディレクトリが表示されます。
制御グループがマウントされている場所を把握するには、以下を実行します。
grep cgroup /proc/mounts
コントロールグループの階層
異なるコントロールグループが異なる階層にある可能性があるという事実は、完全に異なるグループ(およびポリシー)を使用できることを意味します。 CPU 割り当てとメモリ割り当て。 完全に想像上の例を作りましょう:Gunicorn、PostgreSQLデータベースでPythonウェブアプリケーションを実行し、SSHログインを受け入れる2CPUシステムがあります。 各Webアプリと各SSHセッションを独自のメモリ制御グループに配置し(単一のアプリまたはユーザーがシステム全体のメモリを使い果たさないようにするため)、同時にWebアプリとデータベースをCPUに固定し、SSHログインを別のCPUに貼り付けることができます。
もちろん、LXC コンテナを実行した場合、各階層はコンテナごとに 1 つのグループを持ち、すべての階層は同じように見えます。
階層のマージまたは分割は、cgroup 擬似ファイルシステムをマウントするときに特別なオプションを使用して実現されます。 これを変更する場合は、分割またはマージする階層内の既存のcgroupをすべて削除する必要があることに注意してください。
cgroup の列挙
/proc/cgroups
調べて、システムに認識されているさまざまな制御グループ・サブシステム、それらが属する階層、およびそれらに含まれるグループの数を確認できます。
また、プロセスがどのコントロールグループに属しているかを確認する /proc/<pid>/cgroup
こともできます。 制御グループは、階層マウントポイントのルートに対する相対パスとして表示されます。例えば。 /
は「このプロセスは特定のグループに割り当てられていません」 /lxc/pumpkin
を意味し、プロセスは という名前の pumpkin
コンテナのメンバーである可能性が高いことを意味します。
特定のコンテナの cgroup を見つける
コンテナごとに、各階層に 1 つの cgroup が作成されます。 古いバージョンの LXC ユーザランドツールを使用している古いシステムでは、cgroup の名前はコンテナの名前になります。 LXC ツールのより新しいバージョンでは、cgroup は lxc/<container_name>
.
Docker ユーザーへの追加の注意: コンテナー名は、コンテナー の完全な ID または 長い ID になります。 コンテナが のように docker ps
表示される ae836c95b4c3
場合、その長い ID は のように表示されます ae836c95b4c3c9e9179e0e91015512da89fdec91612f63cebae57df9a5444c79
。または docker ps -notrunc
で docker inspect
調べることができます。
すべてをまとめると、私のシステムでは、Dockerコンテナのメモリメトリックを確認したい場合は、 /sys/fs/cgroup/memory/lxc/<longid>/
.
メモリ、CPU、ブロック I/O メトリックの収集
サブシステムごとに、使用済みメモリ、累積 CPU サイクル、または完了した I/O 数に関する統計を含む 1 つの疑似ファイル (場合によっては複数) が見つかります。 後で説明するように、これらのファイルは簡単に解析できます。
メモリ メトリック
それらはcgroupにあります memory
(当たり前です! メモリー制御グループは、システムのメモリー使用量を非常にきめ細かくアカウンティングするため、オーバーヘッドが少し増加することに注意してください。 したがって、多くのディストリビューションは、デフォルトで有効に しないことを選択しました 。 一般に、これを有効にするには、カーネルコマンドラインパラメータを追加するだけです。 cgroup_enable=memory swapaccount=1
メトリックは擬似ファイル memory.stat
. これがどのように見えるかです:
キャッシュ11492564992
RSS 1930993664
mapped_file 306728960
PGGIN 406632648
PGGOUT 403355412
スワップ 0
pgfault 728281223
pgmajfault 1724
inactive_anon 46608384
active_anon 1884520448
inactive_file 7003344896
active_file 4489052160
立ち去れない32768
hierarchical_memory_limit 9223372036854775807
hierarchical_memsw_limit 9223372036854775807
total_cache 11492564992
total_rss 1930993664
total_mapped_file 306728960
total_pgpgin 406632648
total_pgpgout 403355412
total_swap 0
total_pgfault 728281223
1724 total_pgmajfault
total_inactive_anon 46608384
total_active_anon 1884520448
total_inactive_file 7003344896
total_active_file 4489052160
total_unevictable 32768
前半 (接頭辞なし total_
) には、サブ cgroup を除く cgroup 内のプロセスに関連する統計が含まれます。 後半 (接頭辞付き total_
) にはサブ cgroup も含まれます。
一部のメトリックは「ゲージ」、つまり増減できる値です(例: swap
は、cgroup のメンバーが使用するスワップ領域の量です。 他のいくつかは「カウンター」、つまり特定のイベントの発生を表すため、上昇することしかできない値です(例: pgfault
これは、cgroup の作成以降に発生したページフォールトの数を示します。この数は決して減少することはできません)。
これらの指標が何を表しているのか見てみましょう。 すべてのメモリ量はバイト単位です (イベント カウンターを除く)。
- キャッシュ は、この制御グループのプロセスによって使用されるメモリの量であり、ブロックデバイス上のブロックに正確に関連付けることができます。 ディスクとの間でファイルを読み書きすると、この量は増加します。 これは、"従来の" I/O (, ,
write
read
syscalls) とマップされたファイル (open
を使用mmap
) を使用する場合に当てはまります。また、マウントで使用されるtmpfs
メモリも考慮されます。 理由は正確にはわかりません。ファイルシステムがページキャッシュと直接連携することが原因tmpfs
である可能性があります。 - RSS は、ディスク上の何にも対応 しない メモリの量です (スタック、ヒープ、匿名メモリ マップ)。
- mapped_file は、制御グループ内のプロセスによってマップされたメモリーの量を示します。 私の謙虚な意見では、使用されているメモリ の量 に関する情報は提供されません。それはむしろそれ がどのように使われるか を教えてくれます。
- PGGGINとPGGGout は少し注意が必要です。
vmstat
に慣れている場合は、cgroup のプロセスによってページの読み取りと書き込みが (それぞれ) 必要になった回数を示し、ファイル I/O とスワップアクティビティの両方を反映する必要があると考えるかもしれません。悪い! 実際、それらは 充電イベントに対応しています。 ページが cgroup に「課金」(=アカウンティングに追加)されるたびに、 pgpginが 増加します。 ページが「課金されていない」(=cgroupに「課金されていない」)場合、 pgpgout は増加します。 - pgfault と pgmajfault は、cgroup のプロセスがそれぞれ "ページ フォールト" と "重大なフォールト" をトリガーした回数を示します。 ページフォールトは、プロセスが存在しないか保護されている仮想メモリ空間の一部にアクセスしたときに発生します。 前者は、プロセスにバグがあり、無効なアドレスにアクセスしようとした場合に発生する可能性があります(その後、シグナルが送信
SIGSEGV
され、通常は有名なSegmentation fault
メッセージで強制終了されます)。 後者は、プロセスがスワップアウトされたメモリゾーン、またはマップされたファイルに対応するメモリゾーンから読み取るときに発生する可能性があります: その場合、カーネルはディスクからページをロードし、CPUにメモリアクセスを完了させます。 また、プロセスがコピーオンライトメモリゾーンに書き込むときにも発生する可能性があります:同様に、カーネルはプロセスをプリエンプトし、メモリページを複製し、プロセス自身のページコピーで書き込み操作を再開します。 「重大な」障害は、カーネルが実際にディスクからデータを読み取らなければならないときに発生します。 既存のページを複製したり、空のページを割り当てる必要がある場合は、通常の(または「マイナー」)障害です。 - swap は (予想通り) この cgroup 内のプロセスによって現在使用されているスワップの量です。
- active_anon および inactive_anon は、カーネルによってそれぞれ アクティブ および 非アクティブ であると識別された 匿名 メモリの量である。「匿名」メモリとは、ディスクページにリンク されていない メモリのことです。 つまり、これは上記の rss カウンターと同等です。 実際、 rss カウンタの定義そのものが active_anon+**inactive_anon**-**tmpfs** です ( tmpfs は、この制御グループによってマウントされたファイルシステムによって
tmpfs
消費されるメモリの量です)。 さて、「アクティブ」と「非アクティブ」の違いは何ですか? ページは最初は「アクティブ」です。そして、一定の間隔で、カーネルはメモリをスイープし、いくつかのページに「非アクティブ」のタグを付けます。 再度アクセスされるたびに、すぐに「アクティブ」に再タグ付けされます。 カーネルがメモリ不足に陥り、ディスクにスワップアウトする時が来ると、カーネルは「非アクティブ」ページをスワップします。 - 同様に、 キャッシュ メモリは active_file と inactive_fileに分割されます。 正確な式は cache=**active_file**+**inactive_file**+**tmpfs** です。 カーネルがアクティブセットと非アクティブセットの間でメモリページを移動するために使用する正確なルールは、匿名メモリに使用されるルールとは異なりますが、一般的な原則は同じです。 カーネルがメモリを再利用する必要がある場合、すぐに再利用できるため、このプールからクリーンな(=変更されていない)ページを再利用する方が安価であることに注意してください(匿名ページとダーティ/変更されたページを最初にディスクに書き込む必要があります)。
- UNEVICABLE は、再利用できないメモリの量です。一般に、で
mlock
「ロック」されたメモリが考慮されます。 これは、秘密鍵やその他の機密資料がディスクにスワップアウトされないようにするために、暗号フレームワークによってよく使用されます。 - 大事なことを言い忘れましたが、 メモリ と memsw の制限は実際にはメトリックではなく、このcgroupに適用される制限を思い出させるものです。 最初のものは、この制御グループのプロセスが使用できる物理メモリーの最大量を示します。2つ目は、RAM +スワップの最大量を示します。
ページ キャッシュ内のメモリのアカウンティングは非常に複雑です。 異なる制御グループ内の 2 つのプロセスが両方とも同じファイルを読み取る場合 (最終的にはディスク上の同じブロックに依存する場合)、対応するメモリー・チャージは制御グループ間で分割されます。 これは素晴らしいことですが、cgroupが終了すると、それらのメモリページのコストを分割しなくなるため、別のcgroupのメモリ使用量が増える可能性があることも意味します。
CPU メトリック
メモリメトリックについて説明したので、他のすべては比較すると非常に単純に見えます。 CPU メトリックはコントローラで cpuacct
検出されます。
コンテナごとに、擬似ファイル cpuacct.stat
、 コンテナのプロセスによって蓄積されたCPU使用率を含み、時間の間に user
system
分類されます。区別に慣れていない場合は、プロセスがCPUを直接制御していた時間です(つまり、 user
プロセスコードの実行)、および system
CPUがそれらのプロセスに代わってシステムコールを実行していた時間です。
これらの時間は、秒の1/100のティックで表されます。 (実際には、「ユーザージフィー」で表現されています。 毎秒 「jiffies」 があり USER_HZ 、x86システムでは
USER_HZ 100です。
これは、毎秒のスケジューラ「ティック」の数に正確にマップするために使用されていました。しかし、より高い頻度のスケジューリングとティックレスカーネルの出現により、 カーネルティックの数はもはや関係がありませんでした。 とにかく、主にレガシーと互換性の理由から立ち往生しています。
ブロック I/O メトリック
ブロック I/O はコントローラで考慮されます blkio
。 さまざまなメトリックがさまざまなファイルに分散しています。 カーネルドキュメントのblkio-controllerファイルで詳細な詳細を見つけることができますが、ここに最も関連性の高いものの短いリストがあります:
- blkio.sectors には、cgroup のプロセスメンバーによってデバイスごとに読み書きされる 512 バイトのセクター数が含まれています。 読み取りと書き込みは 1 つのカウンターにマージされます。
- blkio.io_service_bytes は、cgroup によって読み書きされたバイト数を示します。 デバイスごとに同期 I/O と非同期 I/O、および読み取りと書き込みを区別するため、デバイスごとに 4 つのカウンターがあります。
- blkio.io_serviced 似ていますが、バイト カウンターを表示する代わりに、サイズに関係なく、実行された I/O 操作の数が表示されます。 また、デバイスごとに4つのカウンターがあります。
- blkio.io_queued は、この cgroup に対して現在キューに入っている I/O 操作の数を示します。 つまり、cgroup が I/O を実行していない場合、これはゼロになります。 その逆は当てはまらないことに注意してください。 つまり、キューに入れられた I/O がない場合、cgroup がアイドル状態 (I/O 方向) であることを意味するわけではありません。 静止しているデバイスで純粋に同期読み取りを行っている可能性があるため、キューイングせずにすぐに処理できます。 また、どの cgroup が I/O サブシステムに負荷をかけているかを把握することは役に立ちますが、これは相対的な量であることに注意してください。 プロセス・グループがより多くの入出力を実行しない場合でも、他の装置が原因で装置負荷が増加するという理由だけで、そのキュー・サイズが増加する可能性があります。
各ファイルには、コントロールグループとそのすべてのサブcグループのメトリックを集約するバリアントがあります _recursive
。
また、ほとんどの場合、制御グループのプロセスが特定のブロックデバイスでI / Oを行っていない場合、ブロックデバイスは疑似ファイルに表示されません。 つまり、これらのファイルの 1 つを解析するたびに、前回から新しいエントリが表示される可能性があるため、注意する必要があります。
ネットワーク・メトリックの収集
興味深いことに、ネットワークメトリックはコントロールグループによって直接公開されません。 それについての良い説明があります:ネットワークインターフェイスは ネットワーク名前空間のコンテキスト内に存在するということです。 カーネルはおそらく、プロセスのグループによって送受信されたパケットとバイトに関するメトリックを蓄積できますが、それらのメトリックはあまり役に立ちません。 インターフェイスごとのメトリックが必要です(ローカル lo
インターフェイスで発生するトラフィックは実際にはカウントされないため)。 しかし、単一のcgroup内のプロセスは複数のネットワーク名前空間に属する可能性があるため、これらのメトリックの解釈は難しくなります:複数のネットワーク名前空間は、複数のインターフェイス、場合によっては複数の lo
eth0
インターフェイスなどを意味します。
では、どうすればよいでしょうか。 まあ、私たちは複数の選択肢があります。
イプテーブル
人々が考える iptables
とき、彼らは通常、ファイアウォール、そしておそらくNATシナリオについて考えます。 しかし iptables
(というより、 netfilter
フレームワーク iptables
は単なるインターフェースです)、深刻な会計処理を行うこともできます。
たとえば、Web サーバー上のアウトバウンド HTTP トラフィックを考慮するルールを設定できます。
iptables -I OUTPUT -p tcp --sport 80
または -g
フラグがないため -j
、ルールは一致したパケットをカウントし、次のルールに進みます。
後で、次の方法でカウンタの値を確認できます。
iptables -nxvL 出力
(技術的には必須ではありませんが、 -n
このシナリオではおそらく役に立たないDNS逆引きルックアップをiptablesが妨げます。
カウンタには、パケットとバイトが含まれます。 このようにコンテナー トラフィックのメトリックを設定する場合は、ループを実行して for
、コンテナーの IP アドレスごとに 2 つの iptables
ルール (各方向に 1 つ) を FORWARD
チェーンに追加します。 これにより、NAT 層を通過するトラフィックのみが測定されます。また、ユーザーランドプロキシを通過するトラフィックを追加する必要があります。
次に、これらのカウンターを定期的に確認する必要があります。 collectdを使用する場合は、iptablesカウンターの収集を自動化するための優れたプラグインがあります。
インターフェイス レベルのカウンタ
各コンテナには仮想イーサネット インターフェイスがあるため、このインターフェイスの TX カウンタと RX カウンタを直接確認することもできます。 ただし、これは思ったほど簡単ではありません。 Docker(現在のバージョン0.6以降)または lxc-start
を使用している場合、各コンテナがホスト内の仮想イーサネットインターフェイスに次のような名前 vethKk8Zqi
で関連付けられていることがわかります。 残念ながら、どのインターフェイスがどのコンテナに対応するかを把握することは困難です。 (簡単な方法を知っているなら、私に知らせてください。
長期的には、Dockerがこれらの仮想インターフェイスのセットアップを引き継ぐ可能性があります。 名前を追跡し、コンテナをそれぞれのインターフェイスに簡単に関連付けることができることを確認します。
ただし、今のところ、最善の方法は、 コンテナー内からメトリックを確認することです。 コンテナで特別なエージェントを実行することなどについて話しているのではありません。 実行可能ファイルはホスト環境から実行しますが、コンテナのネットワーク名前空間内で実行します。
IP-Netns マジック
これを行うには、コマンドを使用します ip netns exec
。 このコマンドを使用すると、現在のプロセスから見える任意のネットワーク名前空間内の任意のプログラム(ホストシステムに存在する)を実行できます。 つまり、ホストはコンテナーのネットワーク名前空間に入ることができますが、コンテナーはホストやその兄弟コンテナーにアクセスできません。 ただし、コンテナはサブコンテナを「見て」影響を与えることができます。
コマンドの正確な形式は次のとおりです。
ip netns exec <nsname> <command...>
例えば:
IP netns exec mycontainer netstat -i
命名システムはどのように機能しますか? どのように見つけ mycontainer
ますか ip netns
?回答:名前空間の擬似ファイルを使用します。 各プロセスは、1 つのネットワーク名前空間、1 つの PID 名前空間、1 つの mnt
名前空間などに属し、これらの名前空間は で /proc/<pid>/ns/
具体化されます。 たとえば、PID 42 のネットワーク名前空間は、擬似ファイル /proc/42/ns/net
によって具体化されます。
ip netns exec mycontainer ...
実行すると、これらの /var/run/netns/mycontainer
擬似ファイルの 1 つであることが想定されます。(シンボリックリンクも可)
つまり、コンテナのネットワーク名前空間内でコマンドを実行するには、次のことを行う必要があります。
- 調査したいコンテナ内のプロセスのPIDを見つけます。
- から
/var/run/netns/<somename>
への/proc/<thepid>/ns/net
シンボリックリンクを作成します。 - を実行します
ip netns exec <somename> ...
。
次に、調査するコンテナーで実行されているプロセス (任意のプロセス) の PID を見つける方法を理解する必要があります。 これは実際にはとても簡単です。 コンテナーに対応する制御グループの 1 つを見つける必要があります。 これらのcgroupを見つける方法については、この投稿の冒頭で説明したので、これについては再度説明しません。
私のマシンでは、コントロールグループは通常 /sys/fs/cgroup/devices/lxc/<containerid>
、. そのディレクトリ内に、という擬似ファイル tasks
があります。 これには、コントロールグループ、つまりコンテナ内にあるPIDのリストが含まれています。 私たちはそれらのいずれかを取ることができます。だから最初のものはするでしょう。
すべてをまとめると、コンテナの「短いID」が環境変数 $CID
に保持されている場合、すべてをまとめるための小さなシェルスニペットがあります。
タスク=/sys/fs/cgroup/devices/$CID*/tasks PID=$(ヘッド -n 1 $TASKS) mkdir -p /var/run/netns ln -sf /proc/$PID/ns/net /var/run/netns/$CID IP netns exec $CID netstat -i
Pipework でも同じメカニズムを使用して、コンテナ の外部から コンテナ内にネットワーク インターフェイスを設定します。
高パフォーマンスのメトリック収集のヒント
メトリックを更新するたびに新しいプロセスを実行すると、(比較的) コストがかかることに注意してください。 高解像度で、または多数のコンテナ(単一のホストに1000個のコンテナを考えてください)にわたってメトリックを収集する場合は、毎回新しいプロセスをフォークする必要はありません。
単一のプロセスからメトリックを収集する方法は次のとおりです。 メトリックコレクターはC(または低レベルのシステムコールを実行できる任意の言語)で記述する必要があります。 特別なシステムコールを使用して、 setns()
現在のプロセスが任意の名前空間を入力できるようにする必要があります。 ただし、名前空間の擬似ファイルへのオープンファイル記述子が必要です(これはの /proc/<pid>/ns/net
擬似ファイルです)。
ただし、このファイル記述子を開いたままにしてはならないという落とし穴があります。 そうすると、制御グループの最後のプロセスが終了したときに、名前空間は破棄されず、そのネットワークリソース(コンテナの仮想インターフェイスなど)は永遠に(またはそのファイル記述子を閉じるまで)残ります。
正しいアプローチは、各コンテナの最初のPIDを追跡し、毎回名前空間の擬似ファイルを再度開くことです。
コンテナ終了時のメトリクスの収集
リアルタイムのメトリック収集を気にしない場合もありますが、コンテナが終了したときに、コンテナが使用したCPU、メモリなどの量を知りたい場合があります。
Dockerの現在の実装(0.6現在)は、に依存しているため、これを特に困難にします。 コンテナが停止すると、 lxc-start
lxc-start
その背後で慎重にクリーンアップします。とにかく本当にメトリックを収集したい場合は、次の方法があります。 コンテナーごとに収集プロセスを開始し、その PID tasks
を cgroup のファイルに書き込むことによって、モニターする制御グループに移動します。 収集プロセスでは、 tasks
ファイルを定期的に再読み取りして、それがコントロール グループの最後のプロセスであるかどうかを確認する必要があります。 (前のセクションで説明したようにネットワーク統計も収集する場合は、プロセスを適切なネットワーク名前空間に移動する必要もあります)。
コンテナが終了すると、 lxc-start
コントロールグループを削除しようとします。 コントロールグループがまだ使用されているため、失敗します。しかし、それは問題ありません。 これで、プロセスがグループに残っている唯一のプロセスであることを検出する必要があります。 今こそ、必要なすべての指標を収集する適切な時期です。
最後に、プロセスはルート制御グループに戻り、コンテナー制御グループを削除する必要があります。 制御グループを削除するには、そのディレクトリーのみ rmdir
を削除します。 ディレクトリにはまだファイルが含まれているため、直感に rmdir
反しますが、これは疑似ファイルシステムであるため、通常のルールは適用されないことに注意してください。 クリーンアップが完了すると、収集プロセスは安全に終了できます。
ご覧のとおり、コンテナが終了したときにメトリックを収集するのは難しい場合があります。このため、通常は定期的にメトリックを収集する方が簡単です(例: 毎分、収集されたLXCプラグインを使用して)代わりにそれに依存します。
まとめ
要約すると、次のことをカバーしました。
- コンテナの制御グループを見つける方法。
- コンテナのコンピューティングメトリックの読み取りと解釈。
- コンテナのネットワークメトリックを取得するさまざまな方法。
- コンテナーの終了時に全体的なメトリックを収集する手法。
これまで見てきたように、メトリックの収集はそれほど難しくありませんが、ネットワークサブシステムのような特殊なケースで、多くの複雑な手順が必要です。 Dockerはこれを処理するか、少なくともフックを公開してより簡単にします。 これは、「Dockerはまだ本番環境の準備ができていません」と何度も繰り返す理由の1つです:開発、継続的なテスト、またはステージング環境のメトリックをスキップすることは問題ありませんが、メトリックなしで本番サービスを実行することは間違いなく 問題ありません !
大事なことを言い忘れましたが、そのすべての情報があっても、それらのメトリック用のストレージおよびグラフシステムが必要になることに注意してください。 そのようなシステムはたくさんあります。 自分で展開できるものが必要な場合は、たとえば 収集 または グラファイト。 「サービスとして」のオファリングもあります。 これらのサービスはメトリックを保存し、特定の価格でさまざまな方法でクエリを実行できます。 いくつかの例には、 Librato、 AWS CloudWatch、 New Relic Server Monitoringなどがあります。