Dockerコンテナ上で実行しているProcess IDをリスト表示するツールを作ってみた

Glia Computing 開発担当 ニシダです。
機械学習系のプログラムを動かす際に、ライブラリの依存関係など実行環境を整備する煩わしさから解放されたくてDockerを利用している方は多いと思います。また、実行環境の共有のためにDockerhubなどもよく利用されています。
本稿では、複数人がDockerを利用しながら1台のサーバを共有する際に少しだけ便利になるツールを作ってPythonパッケージにした話を書きたいと思います。

本稿では以下のような環境で動作確認をしています。

  • Ubuntu 18.04
  • Docker v18.09.0
  • Python 3.7.2

なお、本稿で作成したツールはDockerの構造及びOSへの依存性により、Linux以外のOSでは動作しません。
また、本稿で作成したツールのソースコードは以下のURLで公開してあります。
https://github.com/masatanish/docker-pid

困っていたこと

1台のマシン上で複数ユーザがDockerを使ったときの問題

Glia Computingでは、1台のGPUサーバを複数ユーザで共有することがあります。その際に、Dockerもよく利用されています。そういった利用方法の際にたまに困ることが発生していました。 最近は世の中的にクラウド利用が増えてきているので、複数ユーザで1台のサーバを使うというシーンはあまりなくなっていくのかもしれませんが。

機械学習系のプログラムを動かしていると、意図せずリソース(CPU, メモリなど)を専有してしまうといった事故はよく起こります。(例えば、巨大なデータを何も考えずにメモリ上に展開してしまう場合とか。) 1台のホストを複数人で共有していると人に迷惑をかけることになるので、サーバを共有する場合にはリソースの監視をしながら問題が発生したらそのプロセスを特定して対処が必要なります。
急ぎの仕事で動かしているプロセスを勝手に落としてしまうわけにもいかないため、リソースを専有しているプロセスの実行者と目的の特定は重要になります。

プロセスの特定

単純に1台のホストを複数ユーザが共有している場合には、psコマンドやtopコマンドを利用してリソースを専有しているプロセスとその実行ユーザ(犯人)を特定することが可能です。
一方、同様の環境で各ユーザがDockerコンテナを利用している場合には、先程のコマンドでは当該プロセスを特定することが出来ません。というのもDockerのコンテナ上で実行されるプロセスは(あえて設定をしなければ)ホスト側ではrootユーザの実行プロセスとなるためです。そのためホスト側でps, topコマンドを実行してもDockerコンテナ上で実行されているプロセスを実行した実際のユーザは分かりません。またプロセスとDockerコンテナを結びつける情報も拾うことが出来ません。

そのため、以前は問題発生時の対処フローとして以下のようなことをしていました。

  1. リソースを専有しているプロセスIDの特定
    1. ホスト側でtopコマンド → CPU, メモリ
    2. ホスト側でnvidia-smi → GPUのUsage, メモリ
  2. 対象プロセスIDのユーザがrootだったら、/proc/PID/cgroups を見てdocker コンテナで実行したものかを調べる (参考)
  3. docker ps でコンテナ一覧を表示し、2. で特定したコンテナIDのコンテナを特定
  4. コンテナ名、イメージ名から利用者を推定

リソースの制限をしてコンテナを起動するという運用にすることでこういった事故は防げるのですが、利用者に運用が徹底されていないと事故は防ぎきれないので何らかの方法でDockerプロセスの特定方法が必要になります。

Dockerプロセスの特定

docker topコマンド

Dockerから起動されたプロセスの特定に何かもっと楽な方法がないものだろうかといろいろ調べていたら、dockerのサブコマンドでdocker topなるコマンドを見つけました。
コンテナIDを指定し、当該コンテナが実行しているプロセスのps結果を表示してくれるものです。
psコマンドのオプションも指定できるので、docker top CONTAINERID aux と実行することで、コンテナが実行しているプロセスのCPU利用率、メモリ利用率も取得することが出来ます。

docker top

全Dockerコンテナのdocker top

docker top は便利ですが対象となるコンテナIDを指定する必要があります。これでは、多くのコンテナが動いている場合、リソースを消費しているプロセスを見つけ出すのは面倒です。
そこで、Pythonで全コンテナのdocker topした結果を取得するようなスクリプトを書いてみます。
PythonからDockerの機能を呼び出すためにはDocker SDK for Pythonのパッケージを利用します。

以下のような簡単なコードでホスト内のすべてのコンテナから実行されているプロセスを取得することが出来ます。

import docker

client = docker.from_env()
# コンテナID一覧の取得
containers = client.containers.list()
for c in containers:
    # コンテナ情報の取得
    cid c.attrs['Id']
    cname = c.attrs['Name']
    print("{}:{}".format(cid, cname))
    # `docker top` の実行
    procs = c.top(ps_args="aux")
    # プロセスごとの情報の取得
    for p in procs['Processes']:
        print(p)

コンテナID 取得 → 各コンテナごとにdocker topを実行といった流れになります。これにより、各プロセスの情報を配列で取得できるようになりました。

ツール化

前章までのやり方では、情報を表示するためには毎回Pythonスクリプトを呼び出す必要があります。また表示も若干見づらいです。そこで、表示をきれいに整え、コマンドラインツールとして簡単使えるようにしてみたいと思います。

CLIツール

コマンドラインツールにするために以下のパッケージを利用します。

Package説明
clickPythonでCLIツールを実現するためのコマンドラインパーサ。サブコマンドなども実装できる
tabulateコンソール上にテーブルを出力するためのパッケージ

これらのツールを使って、以下のようにツールを実現します。

  1. 前章の手法でプロセス情報の一覧を取得
  2. 出力したい情報を抽出、型の変換
  3. tabulateでテーブル表示できる形に整形
  4. clickを使ってコンソール上に表示

3.のコンソール上への表示はprint関数でも良いのですが、Python2,3で構文の違いが合ってどちらかでしか使えないというのは嫌だったので、clickのecho関数を使ってコンソールに出力します。(これでPython2と3の違いを吸収してくれると勝手に思っています。) また、今後の機能拡張時にコマンドライン引数などを扱いやすくする目的もありclickを利用しています。
tabulateなどを用いて出力を整形した結果、以下のような表示が出来ました。だいぶ見やすくなったと思います。

Pythonパッケージ化

次にこのスクリプトをPythonのパッケージとして公開します。これによりパッケージがインストールされている環境であれば簡単にこのプログラムを呼び出せるようになります。
単に今までPythonのパッケージを作ったことがなかったので挑戦してみたかったというのも動機としてあります。

パッケージの作成には以下の記事を参考にしました。
PyPI登録手順〜pip installするまで〜 – Qiita

パッケージファイルの作成

まず、setup.pyとsetup.cfgにパッケージの設定を記載します。パッケージは単純にdocker-pid としてみました。
設定の肝はconsole_scriptsとしてエントリポイントを指定することです。この設定により、パッケージをインストールすると、で指定した関数をエントリポイントとしてコマンドラインから呼び出すことが出来るようになります。

# setup.cfg
[metadata]
name = docker-pid
...
[options.entry_points]
console_scripts =
    docker-pid = docker_pid.cli:main

設定を書いたら、wheelを使ってパッケージファイルを作成します。
パッケージ作成時に--universal をつけることでPython2, Python3両方に対応したパッケージが作成できます。

pip install wheel
python setup.py bdist_wheel --universal

PyPIへのアップロード

パッケージを登録するために、下記URLからアカウント登録します
https://pypi.org/account/register/

登録したアカウント情報は、~/.pypirc に記載しておきます。
次に twine パッケージを利用してPyPIにアップロードします。

twine upload --repository pypi dist/*

これでパッケージファイルの登録ができました。
https://pypi.org/project/docker-pid/

まとめ

本稿では、1台のホスト上のDockerコンテナから起動しているプロセスの一覧を表示するコマンドを実装し、Pythonのパッケージとして公開するまでを書いてみました。
実際にプロセスからDockerコンテナを特定できても、コンテナ名やイメージ名が適切に設定されていないと誰が作ったコンテナか分からなかったりします。現状、そこは名前を設定しようねというルールで縛っていたりします。だったら、Dockerコンテナ起動時にメモリ使用量や利用CPUの設定をすればという話もあるのですが。

ちなみに、今回作ったプログラムはできるだけシンプルに作ったので色々と機能がたりてないと感じています。今後折を見て以下のような拡張をしていきたいと考えています。

  • OSがLinuxじゃない場合に本コマンドが対応していない旨を出力して終了するようにする
  • 表示件数の制限(指定)
  • 結果出力時のCPU使用率、メモリの使用率でのソート
  • GPUの使用率、メモリ使用量もnvidia-smiなどから取得して表示できるようにする

最後に、本稿を読んでいただいた方の中で、Dockerプロセスのリソース専有状況の取得がこんな方法でもできるよとご存知の方がいらっしゃいましたら、教えていただけると嬉しいです。

この投稿へのコメント

コメントはありません。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

この投稿へのトラックバック

トラックバックはありません。

トラックバック URL