Go言語でコンテナを作成する
クラウド時代に利用されるコンテナとは何か知り、自分好みのコンテナを作ってみる。
そもそもコンテナってなんやねん
コンテナとは、「docker のことである、runc のことである」などツールやライブラリのことを指してしまっている例が散見される。 しかし、実際にはコンテナは上記のようなツールによって作成されるものだ。では、コンテナとはなにか?
自分の調査の範囲では具体的に「この条件を満たせばコンテナ」というものは見当たらないが、多くの記載で共通して言えることはコンテナが、ホスト(コンテナの作り主)から隔離されているプロセス である点だ。
この 隔離されている というのが重要なのだが、ホストの何から隔離されているのだろう?
具体的には下記が隔離されていることが多い。
- ホストのファイルへのアクセス:コンテナは許可されたパス以外はアクセスができない
- ホストの他のプロセスへのアクセス:コンテナは子プロセス以外のプロセスにはアクセスできない
- ホストのリソースへのアクセス:コンテナは決められた CPU, RAM のリソースしか利用できない
これによって、どんな恩恵が得られるのだろうか?
- コンテナでいくらファイル操作を行っても、ホストのファイルが消されたり、編集されることはない
- ホストの様々なプロセスがコンテナから kill されたりしない
- コンテナで処理を行っても、ホストのリソースが食いつぶされない
つまり、セキュリティ面ではもちろんだが、コンテナで好きな処理を行っても、ホストへの影響を制限できる(全く無いわけではない)。これが 隔離されている の意味。
これにより開発時に好き勝手に試してみることができるし、機能が制限されているからこそ、他の環境でも同じ条件を整えやすい。 これがコンテナがポータブルであるといわれる理由だ。
では、これらの隔離を行うにはどうしたらよいのだろうか?
一般的に行われる方法として、Linux のカーネルの機能を利用する。そのため、多くのコンテナを作成するツールやライブラリは Linux を前提として作られている。
ではカーネルのどのような機能を利用するのだろうか?
- pivot root:ルートファイルシステムを変更することにより、決められたパスの配下以外はアクセスできない
- namespace:プロセス ID やネットワークなどを独立して他のプロセスから分からない状態にする
- cgroups:プロセスを control group に分けて、CPU や RAM などのリソースを割り当てる
まさに Linux カーネルで提供される機能がコンテナを実現している。逆にいえば、他の OS でも上記の機能が提供できればコンテナを作れるということになる。
Go言語でコンテナを作る
コンテナを作るためには、docker や runc を利用すればよい。しかし、それでは内部の動きを理解することは難しい。 そこで、上記の定義に従ってコンテナを作ってみる。
ここで、Go言語で作るのは、Linux の上記の機能を簡単に利用できるからだ。実体としては、system call を呼ぶだけなので、C言語でも他の言語でもコンテナを作成することはできる。 もし興味がある方は、他の言語で syscall 呼び出しで独自のコンテナランタイムを作ってみてもよいだろう。
ちなみに、Go言語でコンテナを作成するのは下記の記事を参考にしている。
- ASCII.jp:Go言語とコンテナ:libcontainer を利用してコンテナを作成している
- Build Your Own Container Using Less than 100 Lines of Go <- この記事の作者は Cloud Foundry のコンテナシステムである Garden を開発している
- lizrice/containers-from-scratch: Writing a container in a few lines of Go code, as seen at DockerCon 2017 and on O’Reilly Safari
作成したリポジトリが、go-container となる。
動作させてみる
手順は、README に記載のとおりだ。注意点は、pivot root, namespace などの分離は Linux のカーネルの機能を利用しているため、ホストのマシンは Linux の OS である必要がある。
行っていることはシンプルで、
- ホストで起動したプロセスから、自分自身を再度呼び出す。このとき、実行オプションとして、新しい UTS, NAMESPACE, PID で起動する
- 起動したプロセスにて、cgroups の設定を自身に実施し、ここでは CPU 使用率の制限を行う
- pivot root を実施して、ホストの root FS とコンテナ用の root FS を交換する
- ホストの root FS を unmount する ことにより、コンテナからホストの root FS が見れないようにする
- コンテナのプロセスを実行する
たったこれだけの処理でも、ホストからの隔離はかなり行える。 docker や runc ではそれをさらに便利にセキュアに行えるようにしている。