Dockerを0から勉強し直す(1) 「コンテナ技術とは?」

今日から「Dockerを0から勉強し直す」という記事をいくつか書いていきます。これはDockerを構成する技術とか内側の部分を調べてまとめていくものです。自分は今までDockerを使ってCI/CDのパイプラインとか、ちょっとしたWebアプリケーションの環境とかを構築してきましたが、Dockerを構成する部分や、そもそもDocker イメージとはどういうものなのかをちゃんと理解していませんでした。なんというか、このままではまずい気がしたので「0から」学び直したいと思ったわけです。

というわけで第1回は「コンテナ技術とは?」というテーマで書いていきます。(たぶん誤記があると思います)

目次

コンテナ技術の概要

Linuxのユーザーランド上では複数のプロセスを走らせることが可能です。たとえばWebサーバーやデータベースサーバーを一緒に稼働させることができます。ただし、同じLinux上で稼働するプロセスはOSレベルの環境設定については基本的にすべて共通になってしまいます。ディスク(ファイルやディレクトリ)、NICIPアドレス、プロセステーブルなども共通です。この場合、たとえば同じTCP80番ポートを使用するプロセスを複数作れないですし、2つのアプリケーションが必要とするライブラリのバージョンが異なる場合、バージョンによってディレクトリを分けてライブラリのファイルを配置してやる必要があり面倒です。

Linuxコンテナはこのような問題を解決します。Linuxカーネルのnamespace(名前空間)の機能により、Linux上で稼働するプロセスを複数のグループで分割して、それぞれのグループに異なる環境設定を適用できます。複数のユーザーランドを作成し、それぞれに異なるディレクトリの内容を見せたり、異なるネットワークの設定をすることが可能です。このように実行環境が分離されたユーザーランドを「コンテナ」と呼びます。コンテナではプロセステーブルもそれぞれ作られるので、コンテナの外で走っているプロセスを見ることができなくなります。*1

f:id:tckw_aya:20170829024248p:plain

http://www.school.ctc-g.co.jp/columns/nakai/nakai41.html

LXC(Linux Contianer)

コンテナ技術の歴史としてよく聞くのは「chrootFreeBSDのjail → LXC → Docker」という流れで発展していったという話です。Dockerのことを書く前にchrootやLXCとはどういうもなのかということと、LXCを構成するcgroupやnamespaceについて整理して書きます。

chroot

chrootは古くからUNIXのシステムに備わっているファイルシステムの隔離機能です。chrootの特徴は以下の通りです。

  • ディレクトリーツリーの分離を行い、プロセスのルートディレクトリを変更する
  • chrootすると指定したディレクトリ以下に閉じ込めることが出来る。その場合、指定されたディレクトリが/(ルートディレクトリ)として見えるため、それより上のディレクトリにアクセスすることができない
  • chrootはプロセスやネットワークなどの隔離はしないので、プロセスリストが見えるし、ユーザーIDやネットワークインターフェイスも共通になる
  • root権限で使える
  • プロセスのルートを一旦変更すると当該プロセスと(その子孫)は元のルートディレクトリに戻れなくなる

1998年からFreeBSDにはjailという仮想化機構があり、これはchrootの拡張版のようなものでファイルシステムだけでなくプロセス空間の隔離も行います。LXC(Linux Container)はさらなる拡張版とも言え、cgroup、namespace、chrootを使ってコンテナを作成します。

namespace

namespaceはプロセスのグループに対して6種のOSのリソースを分離させて割り当てるというものです。ここで言うリソースには以下のようなものがあります。

名前空間 分離対象
IPC System V IPC, POSIX メッセージキュー
Network ネットワークデバイス、スタック、ポートなど
Mount マウントポイント
PID プロセス ID
User ユーザー ID とグループ ID
UTS ホスト名と NIS ドメイン

namespaceを設定すると別の名前空間のリソースには干渉できなくなります。

cgroup

cgroupはプロセスをグループ化し、グループに対して物理的なリソース(CPU、メモリ、ディスク I/O、ネットワーク等)を制限・隔離・監視するものです。cgroupのリソースを制御するにはサブシステムというものを使います。サブシステムには以下のようなものがあります。

サブシステム 機能
blkio 物理ドライブ (例: ディスク、ソリッドステート、USB) などのブロックデバイスの入出力アクセスの制限を設定する
cpu スケジューラーを使用してcgroupのタスクに割り当てるCPU時間を制御する
freezer cgroupのタスクを一時停止または再開する
memory cgroupのタスクによって使用されるメモリーに対する制限を設定し、それらのタスクによって使用されるメモリーリソースについての自動レポートを生成する

LXC

LXCは環境(プロセスの集合)ごとにリソースを分ける仮想化ソフトウェアで、cgroupやnamespace、chrootなどの技術を使っています。環境毎にプロセスやファイルシステムの隔離だけでなく、CPUやメモリの制限も行えます。LXCの関連技術には、LXDというLXCコンテナを操作するためのAPIを提供するデーモンもあるようです。

Dockerとは

超簡単に言えばDockerはlibcontainerという独自のドライバを利用してcgroupやnamespaceなどの技術を間接的に使い、OSレベルで仮想化を行うソフトウェアです。Dockerはすこし前まではlxcやlibvirtというライブラリを使っていましたが、Docker 0.9からlibcontainerを使用するように変更されたようです。*2

ちなみにsystemd-nspawnはchrootの強化版みたいなものらしいです。

f:id:tckw_aya:20170819171726p:plain

libcontianer

Go言語のネイティブな実装であり、名前空間・cgroup・機能・ファイルシステムへのアクセス管理を持つコンテナを作成します。コンテナを作成後、コンテナに対してライフサイクル上の追加操作を可能にします。

Dockerの特徴

LXCでは実現できていない、Dockerの利点をいくつか書いていきます。

ポータビリティ

Dockerはポータビリティに優れており、簡単に言えば「Dockerさえインストールされていればどこでもアプリケーションコンテナを作成・実行可能」です。ユーザーが注意するべきなのはアプリケーションレベルの依存関係だけです。Dockerイメージにまとまったファイルシステムを使い、Docker上でコンテナを作動させるだけでサービスのデプロイは完了します。コンテナはホストの環境から独立しており共有するのはカーネルだけですが、たとえばWindowsであればDocker for WindowsMacであればDocker for Macがインストールされていれば「カーネルの違い」という問題は解決できます。

Dockerレジストリの存在

これの存在はかなり重要だと思います。たとえば私たちがdocker run centosを実行するとDocker HubというパブリックレジストリからCentOSのDockerイメージをpullし、簡単にコンテナを作成・実行することができます。Dockerレジストリリポジトリを作成し、イメージをpushすればあらゆる人々とイメージの共有ができます。

バージョン管理

DockerはDockerイメージに「タグ」をつけることでバージョンを分けることができます。そもそもDockerイメージはいくつもの層(ファイルやメタ情報)が重なったミルフィーユのような構造をしており、ここでいうバージョンの違いとは「積み重なっている層の違い」を意味します。DockerfileでDockerのイメージをビルドする際にレイヤのキャッシュが起こります。このDockerfileに何か命令を追加して新たにイメージをビルドする際には、このキャッシュを使って差分のレイヤを乗せるだけでできてしまいます。イメージ間で共通する層は共有できるので、ディスク容量の削減が可能です。docker tagによるロールバックも可能です。

Dockerをサポートする技術の多さ

有名なのだとDocker Composeがあります。これは複数のコンテナを使用するアプリケーション環境をdocker-compose.ymlというYAML形式のファイルに記述し、それを読み込むだけで環境のデプロイができるというものです。他にもk8sやSwarmといったオーケストレーションツールがあり、これらを使用することでコンテナを使ったクラスタリングも容易にできます。

ドキュメントの多さ

流行と共にDockerについて書かれたドキュメントがどんどん増えていっています。日本語のドキュメントもいっぱいあるのがいいですよね。

まとめ

コンテナとは「プロセスを実行するために隔離され、リソースが制限されたユーザーランド」です。そして、namespaceやcgroupっていう技術を用いてコンテナ化を行います。

コンテナ技術って聞くと「LXCかDocker」っていうところがあったんですが、FreeBSDのjalisや、OpenVZ、SolarisのZoneなんていうのもコンテナ技術の1種としてあるようです。

課題

ネットで1.5日かけて色々調べてこの記事を書いたんですが、曖昧な部分とか整理しきれてない部分が結構あります。これから勉強会などに出向いて、コンテナ技術や仮想化について詳しい方々に話を聞いて「コンテナ完全に理解した」と言えるようになりたいと思いました。この記事はこれから少しずつ編集・改良を加えていくつもりです。

参考

*1:Docker実践入門――Linuxコンテナ技術の基礎から応用まで」に書いてあることとほぼ同じことを書いてしまいましたがお許しください。この本は「Dockerの勉強をこれから始める」という方にオススメなので是非購入してみてください!

*2:DOCKER 0.9: INTRODUCING EXECUTION DRIVERS AND LIBCONTAINER