Linuxコンテナの解析：コンテナのイメージとエンジン

コンテナプラットフォームの専門家であるTang Huamin氏が、**Linux**環境でのコンテナエンジンと名前空間について解説します。

著者：Alibaba Cloudのコンテナプラットフォームテクニカルエキスパート、Tang Huamin (Huamin)氏

[Linux](https://www.alibabacloud.com/products/alinux?spm=a2c65.11461447.0.0.27db30dfCxFVE2)コンテナは軽量な仮想化技術で、名前空間とcgroup技術に基づいて、カーネル共有シナリオでプロセスリソースを分離・制限します。本稿では、Dockerを例に、コンテナイメージと[コンテナエンジン](https://www.alibabacloud.com/product/kubernetes?spm=a2c65.11461447.0.0.27db30dfCxFVE2)の基本的な説明を行います。

#コンテナ
[コンテナ](https://www.alibabacloud.com/product/container-service?spm=a2c65.11461447.0.0.27db30dfCxFVE2)は、軽量な仮想化技術です。仮想マシン（VM）とは対照的に、ハイパーバイザー層を含みません。次の図は、コンテナの起動プロセスを示しています。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/4567f813-0eda-30fd-bc6f-c55651ed15df.png)

最下層では、ディスクにコンテナイメージを格納します。上位層のコンテナエンジンは、Dockerやその他のコンテナエンジンです。コンテナエンジンは、ディスク上のコンテナイメージをホスト上のプロセスとして実行するために、コンテナ作成要求などのリクエストを送信します。

コンテナの場合、プロセスが使用するリソースを分離し、制限する必要があります。これを実現しているのが、Linuxカーネルのcgroupとnamespace技術です。本稿では、Dockerを例に、リソースの隔離とコンテナイメージについて説明します。

#1. リソースの隔離と制限
###名前空間
リソースの分離には名前空間技術が用いられます。Linuxカーネルには7つの名前空間があり、Dockerでは最初の6つの名前空間が使われています。cgroup名前空間はDockerでは使用されていませんが、runCでは実装されています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/448d8cce-2934-ec53-422c-6cafe047171b.png)


以下では、名前空間を順に説明します。

- マウント名前空間は、コンテナから見えるファイルシステムのビューです。これは、コンテナイメージが提供するファイルシステムであり、ホスト上の他のファイルは見えないことを意味します。ホスト上のいくつかのディレクトリやファイルをコンテナから見えるようにするには、`-v parameter bound`を実行する必要があります。
- uts 名前空間は、ホスト名とドメインを分離します。
- pid名前空間は、コンテナのinitプロセスがプロセス1によって開始されることを保証します。
- network名前空間は、コンテナが使用するホストネットワークモード以外のすべてのネットワークモードで使用できます。
- user 名前空間は、コンテナ内およびホスト上のユーザ UID および GID をマッピングします。この名前空間はあまり使用されません。
- IPC名前空間は、セマフォなどのプロセスや通信を制御します。
- cgroup名前空間は、前出の図の右側の部分に示すように、有効または無効にすることができます。cgroup名前空間を使用すると、ホスト上のプロセスと同様に、コンテナのルートとしてcgroupビューが表示されます。また、cgroup名前空間を使用することで、コンテナ内でのcgroupの使用がより安全になります。

以下では、unshareを使用してコンテナ内に名前空間を作成する方法を説明します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/26b97dda-3ba5-a819-2615-84bed2e24c95.png)

図の上段は unshare の使用例で，下段は unshare コマンドで作成された pid 名前空間です。図のようにbashプロセスは新しいpid名前空間になっており、psの結果ではbashプロセスのPIDが1となり、新しいpid名前空間であることを示しています。

#cgroup
###2種類のcgroupドライバー
Cgroup技術はリソース制限に使われます。Dockerコンテナではsystemdドライバとcgroupfsドライバの両方が利用できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/1b103438-b550-946e-8695-e6eba45adb1d.png)

- cgroupfsの方が理解しやすいです。例えば、メモリの制限やCPUのシェアを知りたい場合は、対応するcgroupファイルに直接PIDを書き込み、制限するリソースを対応するメモリcgroupファイルやCPUcgroupファイルに書き込むことができます。
- systemd は cgroup ドライバであり、cgroup を管理することができます。したがって、systemd を cgroup ドライバーとして使用する場合、すべての cgroup 書き込み操作を systemd インターフェイスで完了する必要がありますが、cgroup ファイルを手動で変更することはできません。

###コンテナの共通cgroups
以下では、コンテナ用の共通 cgroup について説明します。Linux カーネルには多くの cgroup が用意されています。Dockerコンテナでは以下の6種類のみを使用します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/401c6a2c-908d-1175-5aba-56970e5b95fa.png)


- CPU cgroupは、CPUシェアやCPUセットを設定することで、CPUの使用率を制御します。
- memory cgroupは、プロセスのメモリ使用量を制御します。
- device cgroupは、コンテナ内で表示されるデバイスを制御します。
- freezer cgroupは、device cgroupと同様にセキュリティを向上させます。コンテナを停止すると、freezer cgroupは、現在のプロセスをすべてcgroupに書き込んでフリーズし、任意のプロセスのフォーク操作を防止します。このようにして、プロセスがホストに逃げるのを防ぎ、セキュリティを確保します。
- blkio cgroupは、コンテナが使用するディスクのIOPS（Input/Output Operations per Second）とBPS（Bytes per Second）を制限します。cgroupがユニークでない場合、blkio cgroupは同期I/Oのみを制限し、Docker I/Oは制限しません。
- pid cgroupは、コンテナ内のプロセスの最大数を制限します。

###コンテナ用の非共通cgroups
いくつかのcgroupはDockerコンテナには使用されません。cgroupはcommon cgroupとuncommon cgroupに分けられます。rdmaを除くすべてのcgroupはrunCでサポートされているため、この区別はDockerにのみ適用されます。しかし、Dockerでは有効になっていません。したがって、Dockerは次の図にあるcgroupをサポートしていません。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/87da7674-8c30-3f40-3ef5-fe573005e244.png)

#2. コンテナイメージ
###Dockerイメージ
ここでは、Dockerイメージを例にコンテナイメージの構造を説明します。

Dockerイメージはユニオンファイルシステムに基づいています。ユニオンファイルシステムでは、ファイルを異なる階層に保存することができます。しかし、これらのファイルはすべて統一されたビューで見ることができます。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/f8d5d06f-d09d-4c24-cb9c-77eea62021d7.png)

前述の図では、右部分がDockerの公式サイトから入手したコンテナストレージの構造です。

この図を見ると、Dockerのストレージはユニオンファイルシステムをベースにした階層構造になっていることがわかります。各層は異なるファイルで構成されており、他のイメージで再利用することができます。イメージがコンテナとして実行されると、最上位の層はコンテナの書き込み可能な層となります。コンテナの書き込み可能なレイヤーは、イメージの新しいレイヤーとしてコミットすることができます。

Dockerイメージのストレージの最下層は、異なるファイルシステムに基づいています。そのため、そのストレージドライバは、AUFS、Btrfs、devicemapper、overlayなどの異なるファイルシステム用にカスタマイズされています。Dockerはこれらのファイルシステムをグラフドライバで駆動し、イメージをディスクに格納します。

#オーバーレイ
###ストレージ処理
このセクションでは、オーバーレイファイルシステムを例に、Dockerイメージがどのようにディスクに保存されるかを説明します。

次の図は、オーバーレイファイルシステムの仕組みを示しています。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/553f6f2b-4630-5854-62f1-4af203115f28.png)


- 下位の層は、読み取り専用のイメージ層。
- 上位層はコンテナの読み書き層で、コピーオンライトの仕組みを採用している。つまり，ファイルを変更する必要がある場合にのみ，下位層からファイルがコピーされ，すべての変更操作は上位層のレプリカに対して行われる。
- workdir層は、中間層として機能しています。上位層で修正されるべきレプリカは、workdir層で修正された後、上位層に移動します。これがオーバーレイファイルシステムの仕組みです。
- mergedir層は、ユニファイドビュー層です。mergedir層では、上位層と下位層のすべてのデータを見ることができます。そして、docker execコマンドを実行すると、コンテナ内のファイルシステムを見ることができますが、これはmergedir層のことです。

###ファイル操作
このセクションでは、オーバーレイ・ストレージをベースにしたコンテナでのファイル操作について説明します。

**ファイル操作**

- 読み込み：上位層にレプリカがない場合、すべてのデータは下位層から読み込まれます。
- 書き込み： コンテナの作成時には、上層部は空の状態です。ファイルの書き込みが必要な場合のみ，下層からコピーされます。
- 削除：削除操作は下位層に影響を与えません。ファイルを削除するということは、実際にはファイルにマークを付けて、そのファイルが表示されないようにすることです。ファイルの削除は、ホワイトアウトや、`xattr trusted.overlay.opaque` = y を設定することで行うことができます。


コンテナが作成された時点では、上層部は空の状態です。この時にデータを読み込もうとすると、すべてのデータが下層から読み込まれます。

前述したように，オーバーレイの上位層はコピーオンライトの仕組みを持っています。いくつかのファイルを変更する必要がある場合、オーバーレイファイルシステムは下位層からファイルをコピーして変更します。

オーバーレイ・ファイルシステムには、本当の意味での削除操作はありません。ファイルを削除するということは、統合ビューレイヤーでそのファイルにマークを付けて、そのファイルが表示されないようにすることです。ファイルの削除には2つの方法があります。

- ホワイトアウト
- ディレクトリの削除：ディレクトリに拡張パーミッションを設定し、拡張パラメータを設定することで行うことができます。

###操作手順
busyboxコンテナを起動するためのdocker runコマンドの実行方法と、オーバーレイのマウントポイントについて説明します。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/c13854a0-be54-e50c-d697-8c071c4dc8eb.png)

2 番目の図は、マウントポイントを表示するために使用した mount コマンドを示しています。コンテナのrootfsマウントポイントはオーバーレイタイプで、上層、下層、workdirの各層が含まれています。

次に、コンテナに新しいファイルを書き込む方法について説明します。docker execコマンドを実行して、ファイルを作成します。前述の図のように、diffは新規ファイルのupperdirとなります。upperdirにあるファイルの内容もdocker execコマンドで書き込まれます。

mergedirディレクトリには、upperdirとlowerdirの内容と、書き込まれたデータが格納されます。

#3. コンテナエンジン
###コンテナのアーキテクチャ
ここでは，CNCF（Cloud Native Computing Foundation）をベースにしたコンテナエンジン上のコンテナの一般的なアーキテクチャについて説明する．下図にコンテナのアーキテクチャを示します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/87cf5209-a8a5-a7e1-88e7-4e46a265ce88.png)

先の図にあるように、コンテナは主に2つの機能を提供します。

1つはランタイムで、コンテナのライフサイクル管理を行います。もう1つはストレージで、イメージの保存管理です。コンテナはイメージを取り出して保存します。

水平方向に見ると、コンテナの構造は次のような層に分かれています。

- コンテナはgRPCサーバを通じて上位層のサービスを提供します。metrics は，いくつかの cgroup メトリクスを提供します．
- 下位層では、左部分がコンテナイメージのストレージです。イメージやコンテナのメタデータで、bootfsを通じてディスクに保存されます。右部のTasksは、コンテナ構造を管理します。イベントは、コンテナに対する特定の操作に対して上位層にイベントを送信し、上位層はそのイベントを購読してコンテナの状態変化を監視することができます。
- 下位の層はRuntimesで、runCやセキュリティコンテナなどのタイプ別に分けられます。

###shim v1/v2
ここでは、Runtimes層におけるコンテナの一般的な構造について説明する。以下の図は、kataの公式サイトから引用したものです。上の部分がソースイメージで、下の部分にはいくつかの拡張例が追加されています。Runtimes層におけるコンテナのアーキテクチャを見てみましょう。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/143526df-3f78-a844-e4d5-7abe51f81a47.png)


上図は、左から順に上位層からRuntime層までの処理を示しています。

一番左には、CRI クライアントが表示されています。通常、Kubelet は CRI リクエストを コンテナ に送信します。リクエストを受け取ったコンテナは、コンテナのライフサイクルを管理するコンテナシムにリクエストを通し、以下の処理を行います。

- I/O を転送
- 信号を送信


図の上側には，プロセスであるセキュリティ・コンテナが示されています。図の下側には，さまざまなシムを示しています．以下に、コンテナ・シムのアーキテクチャについて説明します。

初期状態では、コンテナには青枠で囲われたシムが1つしかありません。kata、runC、gVisorコンテナなど、すべてがコンテナシムです。

コンテナは、shim-v2インターフェースを通じて、異なるタイプのランタイム用に拡張されています。言い換えれば、shim-v2インターフェイスを通じて、異なるランタイム用に異なるシムをカスタマイズすることができます。例えば、runCコンテナではshim-runc、gVisorコンテナではshim-gvisor、kataコンテナではshim-kataという名前のシムを作成することができます。これらのシムは、青枠の コンテナシム を置き換えることができます。

これには多くの利点があります。例えば、shim-v1を使用した場合、kataの制限のために3つのコンポーネントがあります。しかし、shim-v2を使用すると、3つのコンポーネントを1つのshim-kataコンポーネントにすることができます。

###コンテナアーキテクチャの詳細 - コンテナプロセスの例
本節では、2つの例を用いてコンテナプロセスの仕組みを説明します。以下の2つの図は、コンテナアーキテクチャに基づくコンテナのワークフローを示しています。

**開始プロセス**

次の図は、開始プロセスを示しています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/2fb19892-0091-88e4-cb93-9b1cc5a2f13a.png)


プロセスは3つの部分で構成されています。

- コンテナエンジンは、Dockerまたは他のエンジンです。
 -コンテナ と コンテナシム は コンテナアーキテクチャの一部です。
- コンテナはランタイムによって引き出され、あるいはシムがrunCコマンドを実行することでコンテナが作成されます。

図中の数字は、コンテナを作成するプロセスを示しています。

まず、メタデータを作成し、タスクサービスにコンテナ作成のリクエストを送信します。このリクエストは、一連のコンポーネントを介してシムに送られます。コンテナは、gRPCを介してコンテナシムとやりとりします。コンテナ が コンテナシム に作成リクエストを送信した後、コンテナシム はランタイムを呼び出してコンテナを作成します。

###実行プロセス
次の図は，コンテナの実行プロセスを示しています。

execプロセスはstartプロセスと同様です。図中の数字は，コンテナがexecを実行する際の手順を示しています。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/647921/6639e5c8-ec73-74c1-55ea-04131eb2cdfc.png)

前述の図のように、exec操作はコンテナシムにも送られます。コンテナの起動とコンテナの実行に本質的な違いはありません。

唯一の違いは、コンテナ内で実行されるプロセスのために名前空間が作成されるかどうかです。

- 実行時には、既存の名前空間にプロセスを追加する必要があります。
- 起動時には、コンテナプロセスの名前空間を作成する必要があります。

#まとめ
今回の記事で、Linux[コンテナ](https://www.alibabacloud.com/product/kubernetes?spm=a2c65.11461447.0.0.27db30dfCxFVE2)の理解が深まったのではないでしょうか。この記事で学んだことをまとめてみましょう。

1、コンテナのリソース分離に名前空間を、リソース制限にcgroupsを使う方法。
2、オーバーレイファイルシステムをベースにしたコンテナイメージの保存方法。
3、Dockerとコンテナをベースにしたコンテナエンジンの仕組み。


*本ブログは英語版からの翻訳です。オリジナルは[こちら](https://www.alibabacloud.com/blog/getting-started-with-kubernetes-%7C-further-analysis-of-linux-containers_596290)からご確認いただけます。一部機械翻訳を使用しております。翻訳の間違いがありましたら、ご指摘いただけると幸いです。*

*アリババクラウドは日本に２つのデータセンターを有し、世界で60を超えるアベラビリティーゾーンを有するアジア太平洋地域No.1(2019ガートナー)のクラウドインフラ事業者です。
アリババクラウドの詳細は、こちらからご覧ください。
[アリババクラウドジャパン公式ページ](https://www.alibabacloud.com/ja)*