[Docker] Hardcore Container Debugging

原文はこちら。
https://blogs.oracle.com/developers/hardcore-container-debugging

オンラインチュートリアルを読んで、アプリケーションをコンテナ化することができました。外部からアプリケーションにアクセスできるようにポートを公開しましたが、接続すると「データベースに接続できません」というエラーページが表示されます。それではデバッグを始めましょう。以下では、コンテナのデバッグの方法や、Oracleがデバッグを容易にするために開発したcrashcartツールに関する情報を説明します。
CrashCart: sideload binaries into a running container
https://github.com/oracle/crashcart

Related content

Debugging Strategies

コンテナのデバッグは難しい場合があります。特に、コンテナの内容とその動作方法があいまいな場合は特にそうです。小型のVMのようなコンテナを扱う人もいれば、コンテナ内でsshデーモンを実行して、物事がおかしくになったときにログインできるようにする人もいますし、コンテナの中にたくさんの便利なツールを配置しておき、 docker execを使ってコンテナの中のシェルを取得する人もいます。しかし、ふつうのオペレーションをしている人にとって、状況がよくないときはどうすればよいでしょうか。

Debugging from the Host

microontainerを使っている場合、コンテナには一つのアプリケーションおよびその依存性のみが含まれています。
The Microcontainer Manifesto and the Right Tool for the Job
https://blogs.oracle.com/developers/the-microcontainer-manifesto
つまり、デバッグツールやシェルなんかは入っていないので、助けてはくれません。幸運にも、ホストからたくさんデバッグすることができます。あなたの武器のうち最も重要なツールの一つが、nsenterです。Linux Containerは数多くの分離および保護プリミティブの組み合わせですが、その中で理解しておくべきで最も重要なものがnamespace(名前空間)です。名前空間はコンテナ化されたプロセスをシステム上の他のプロセスと分離します。Nsenterを使うと既存の名前空間に入ることができます。
例えば、コンテナ内のネットワークの問題をデバックしたい、としましょう。まず、コンテナのネットワークの名前空間に入ることになるのですが、入るためには、まずコンテナのプロセスのpidを把握する必要があります。
PID=docker inspect -f "{{.State.Pid}}" <container-id>
ネットワークの名前空間のシェルを取得するには、以下のようにpidをnsenterで指定します。
sudo nsenter -n -p$PID
名前空間を示すファイルはprocファイルシステム内にあり、直接その場所を使うことができます。
sudo nsenter -n/proc/$PID/ns/net
Nsenterは非常に強力なツールで、特にネットワークの問題を取り扱う場合には非常に有用です。ホストにインストールしたこのツールを使い、インターフェースをリストアップやトラフィックのダンプが可能です。コンテナの他の名前空間のほとんどにアクセスする場合、同様の方法で入ることができます。
ところが、一つだけデバッグが難しいものがあります。それはコンテナのフィルシステムへのアクセスです。全てのツールが存在するホストのmount名前空間へのアクセスを失わなければ、コンテナのmount名前空間に入ることができません。Dockerのバージョンや利用しているfsドライバによって、コンテナのファイルにアクセスする種々の方法があります。かなり簡単な方法の1つは、/proc/$PID/rootを参照することですが、絶対シンボリックリンクが壊れてしまい、2つのビュー間でファイルの場所を手動で翻訳する必要があります。

Roadblocks to an Ideal Solution

完璧なソリューションは、必要に応じてコンテナ内にデバッグツールをマウントし、終了時にそれらを削除してセキュリティ上の脆弱性を回避する、というものでしょうが、この考えには2点問題があります。
  1. コンテナ内のものにデバッグツールを載せるだけでは不可能で、それではコンテナのmount名前空間に入るという趣旨にあいません。デバッグツールを非標準の場所に置く必要があります。多くのツールは他のディレクトリにあると動作に支障があります。ライブラリの検索パスに問題があったり、/debugのようなstarter以外のデバッグのような新しい場所にツールをロードする問題があったりします。
  2. ホストからコンテナのmount名前空間にディレクトリをマウントだけ実行することはできません。セキュリティ上の理由から、名前空間間のバインドマウントは許可されていません。新しいボリュームマウントでツールを使い、コンテナを確実に再起動することはできますが、これはつまりデバッグセッションの始めと終わりに再起動することであり、あるシナリオでは非常に混乱する可能性があります。

Removing the Roadblocks

まず必要なのは、別の場所にあっても問題ないデバッグツールです。うまく動作するようにするには、binutilsのビルドチェーン全体が非標準の接頭辞でビルドされている必要があります。さらに、ライブラリ依存関係は、コンテナ内のライブラリがデバッグツールと競合せず、問題を引き起こさないよう静的でなければなりません。
nixという、別の場所でビルドされているかなりクールなパッケージシステムがあることがわかりました。
Nix : The Purely Functional Package Manager
https://nixos.org/nix/
nixを使用すると、私たちのツールで/nixディレクトリを読み込むことができ、コンテナ自体がnixで構築されていない限り、競合はありません。nixで構築されたデバッグコンテナをサポートするために、/dev/crashcart のような別のディレクトリを選択することもできます(rootファイルシステム(/)が読み取り専用であったとしても、devはほぼ常にコンテナ内で書き込み可能なtmpfsなので、/devの前に置くと便利です)。
2番目の障害をクリアするには、新しいものをコンテナの名前空間にマウントする方法が必要です。このオプションの一つとして、コンテナ作成時にrslave mountを作成するというものがあります。たとえば、dockerのボリュームコマンドを使い、rslave mountをコンテナの名前空間にロードすることができます。
docker run -v /tmp/mymaster:/dev/crashcart:rslave mycontainer
これにより、コンテナの /dev/crashcartは、ホスト上の /tmp/mymaster のスレーブ・マウントになります。つまり、/tmp/mymasterにディレクトリをマウントすると、そのディレクトリはコンテナ内の/dev/crashcartに伝播されます。 この手法は、必要に応じてツールのマウントをバインドし、後で削除できることを意味します。続いて、nsenterを使用してmount名前空間に入り、ツールを実行できますが、この方法にはまだ1つの欠点があります。この方法を使うには、開始時に実行するすべてのコンテナに対して特別なボリュームマウントを作成する必要があります。rslave mountを使ってコンテナを実行していない場合は、デバッグのために再起動が必要です。ボリュームを持つコンテナを起動せずにすむ方法があれば素晴らしいとは思いませんか?

Enter crashcart

必要に応じてコンテナ内のツールをマウントするために使用できるメソッドがありますが、これにはトリッキーなハックが含まれており、カーネル4.8以降であることが前提で、ユーザー名前空間が使用されていると動作しません。この方法は、コンテナのマウント名前空間内のブロックデバイスをmknodで作成し、mountシステムコールを使ってブロックデバイスをファイルシステムにマウントするというものです。ブロックデバイスを使用するために、バイナリをext3ファイルシステムにパッケージ化し、/dev/loopを使用してループバックブロックデバイスを作成できます。
まだmknodを使ってファイルを作成しておらず、コンテナ内でマウントしていなければ、この方法を手作業で実施することはほぼ不可能ですので、これを実現するため、Rustで作成したユーティリティのcrashcartを開発しました。crashcartはイメージをコンテナの名前空間にマウントし、nsenterのような方法もしくはdocker execを呼び出すかのいずれかを使って/dev/crashcart/bin/bashを実行します。こうすれば、crashcartイメージに格納された任意のツールにアクセスできます。

Why Rust?

以下のエントリでも説明しましたが、Rustを使う理由は全て、crashcartにもあてはまります。
Building a Container Runtime in Rust
https://blogs.oracle.com/developers/building-a-container-runtime-in-rust
https://orablogs-jp.blogspot.jp/2017/07/building-container-runtime-in-rust.html
crashcartは700行未満のコードでできあがっており、Cで書かれていたとしても、潜在的なセキュリティ上の脆弱性を避けるためにメモリ安全であることは良いことです。 Rustは初心者が読むには少し難しいかもしれませんが、Rustの初心者たちがこのプロジェクトに飛び込んで協力くださることを強く願っています。Rustは魅力的な言語であり、非常に有用な特性を有しています。

The Future

crashcartを使うと、今日ユニークな方法でコンテナのデバッグができますが、事態は確実に良くなるでしょう。このツールをコミュニティに提供し、ツールを改善くださることを願っています。テクニックの潜在的な改善に対するいくつかアイデアがあります。
  1. nixを使ってcrashcartイメージをビルドすると、標準のnixパッケージサーバを利用できないため、非常に低速です。新しいロケーションをサポートする代替パッケージサーバが提供される可能性がありますが、別のパッケージャが代替crashcartイメージを作成できるようになることを期待しています。build-image.shスクリプトをプロトタイプと考え、rpmやdebs、その他のディストリビューションからパッケージをインストール可能な代替可能なcrashcartイメージが見られるとすばらしいですね。
  2. このような複雑なシステムコールを必要としないバイナリを簡単にロードするより簡単な方法があるとよいのですが、その方法の一つは、カーネルが名前空間を越えてマウントに対する制限を緩和すること、もう一つは、Dockerがデフォルトでrslave mountを作成することです。これにより、必要に応じてホストからバイナリをより簡単にマウントすることもできるでしょう。

0 件のコメント:

コメントを投稿