コンテナとは何か? 完全な VM なしの分離
コンテナは分離されたプロセスです。アプリと依存関係が、ホストのカーネルを共有しつつ、独自のファイルシステム、ネットワーク、リソース制限を持ちます。
コンテナは、コードと必要なものすべてを 1 つのポータブルな単位にまとめることで、「自分のマシンでは動く」問題を解決します。仮想マシンとは異なり、コンテナはオペレーティングシステム全体を持ち運ぶわけではありません - ホストのカーネルを借り、必要な部分だけを分離します。そのため、コンテナは小さく、起動が速く、CI が依存する使い捨て環境に最適です。
分離が実際に意味するもの
Linux では、コンテナはカーネルがネームスペース(ファイルシステム、プロセスツリー、ネットワーク、ユーザーの独自のビュー)と cgroups(CPU、メモリ、I/O の制限)を使って囲い込んだ、ただの通常のプロセスにすぎません。内部では、そのプロセスはマシンを独占していると思い込んでいますが、外部から見れば数ある中の 1 つのプロセスです。
コンテナ vs 仮想マシン
VM はハードウェアを仮想化し、独自のカーネルを持つ完全なゲスト OS を実行するため、重く、起動には数秒から数分かかります。コンテナはホストのカーネルを共有するため、軽量でミリ秒単位で起動します。トレードオフは次のとおりです。VM はより強い分離を提供し、コンテナは高い集約度と速度を提供します。
コンテナの中身
- アプリケーションのバイナリ、または解釈実行されるコード。
- リンクするランタイムとシステムライブラリ。
- image に焼き込まれた設定ファイルと環境のデフォルト値。
- それ以外は何もなし - カーネルはなく、理想的には不要なシェルやパッケージマネージャーもありません。
なぜ CI はコンテナを好むのか
すべての CI job は、クリーンで同一の環境を求めます。コンテナはまさにそれを提供します。同じ image が、ラップトップでも、runner でも、本番環境でも同じように動作します。build は再現可能になり、ある job が次の job を汚染する状態を残すことはできません。
手短な例
docker run --rm -it python:3.12 python -c "print(1+1)" を実行すると、Python の image を pull し、コンテナを起動し、2 を出力し、コンテナを削除します - すべてホストの Python に触れることなく行われます。--rm フラグはこれを一時的なものにし、まさに CI が望む性質を実現します。
CI におけるコンテナ
CI プロバイダーは各 job を新しいコンテナまたは VM で実行し、その後に破棄します。そのため、pipeline は毎回クリーンな状態で始まります。それらの container image の build はコールドな runner では遅くなることがあります。マネージド runner(Latchkey など)は、温まったレイヤー cache で image の build を高速化し、一時的な registry の不調を自動的にリトライします。
重要なポイント
- コンテナはホストのカーネルを共有する分離されたプロセスであり、ミニ VM ではありません。
- ネームスペースが分離を提供し、cgroups がリソース制限を提供します。
- コンテナは起動が速く再現性があり、それが CI がコンテナに依存する理由です。