Kubernetesによる同一ポッド内の共有ボリュームを介したコンテナ間通信

#1.はじめに
この記事は、同一ポッド内のコンテナ間通信について3回に分けて説明します。コンテナは、マイクロサービス実現に欠かせない役割を果たします。また、実際の運用には複数のコンテナを必要とします。そこで、KubernetesにおけるPodの定義や複数コンテナから構成されるPodのユースケースの紹介に続き、コンテナ間通信の中でも共有ボリュームを介したコンテナ間通信について、[Mirantis OpenCloud Digest “Multi-container pods and container communication in Kubernetes”](https://www.mirantis.com/blog/multi-container-pods-and-container-communication-in-kubernetes/)に基づき説明します。第1回は、Kubernetes Podとユースケース、そして同一Pod内の複数コンテナ間の通信方法についてです。

#2. Kubernetes Podとは
最初にPodについて説明します。 Podは、Kubernetesで取り扱う基本単位です。 つまり、Kubernetesで単一コンテナを実行する場合でも、そのコンテナ用のPodを作成します。複数のコンテナがある場合には、コンテナ同士はお互い密に連携するため、同一Pod内に複数のコンテナをデプロイします。それでは、同一Pod内に複数のコンテナをデプロイした場合、どのようにコンテナは他のコンテナと連携するのでしょうか？Podを物理サーバに置き換えて考えると、より理解しやすくなります。Pod内のコンテナは、物理サーバ上で実行されるプロセスとみなすことができます。たとえば、同一Pod内のコンテナが他のコンテナに接続する際には、ローカルホスト上のポートを介して接続します。

##2.1. Kubernetesが配備可能な最小ユニットとしてPodを利用する理由
一見するとコンテナ単位でデプロイするほうが簡単に思えますが、Podを抽象化レイヤとして追加することでより一層便利になります。コンテナは既存のエンティティであり、特定の機能を実行します。この特定の機能を実行するのは、Dockerコンテナであり、rktコンテナであり、Virtletによって管理される仮想マシンであったりします。これらのコンテナにはそれぞれ異なる要件があります。
さらに、コンテナを管理するための機能追加が必要となります。例えば、コンテナの終了時に実行すべきアクションを定義する再起動ポリシーや、アプリケーション側からみてコンテナ内のプロセスが稼働しているかどうか状態を監視する機能、HTTPリクエストに応答するWebサーバなどが挙げられます。
コンテナを管理する上で求められるこれらの機能に対して、Kubernetesの設計者は、既存の機能を拡張するよりも、新たな管理単位として「Pod」を追加することとしました。Podは1つ以上のコンテナを管理するための単位です。
 
##2.2. Pod数とコンテナ数の関係
Pod内のコンテナは、ある論理ホスト上で稼働するとみなすことができます。これは、同じネットワークネームスペース、つまり同じIPアドレス、ポート空間、そして同じIPCネームスペースを共有することを意味します。また同じボリュームもコンテナ間で共有します。したがって、同一Pod内のコンテナは効率よく通信し、データを各コンテナに隣接した位置に保管できます。また管理者から見ると、異なるアプリケーションが稼働する複数のコンテナ間が密に連携するPodを、一つの単位として捉えることができます。
それでは、すべてのプロセスを1つのコンテナ上で稼働させる代わりに、複数のコンテナに分割する理由は次の通りです。1つめの理由は、マイクロサービスの重要原則「1プロセス、1コンテナ」に反するためです。単一コンテナ上に複数のプロセスをデプロイすると、管理が複雑になってしまいます。例えば障害が発生した場合、複数のプロセスから生成されたログが混在してしまうため、ログ解析が難しくなります。また、親プロセスが停止した場合のゾンビプロセスの取り扱いなど、各プロセスに対するライフサイクル管理も困難になります。2つめの理由は、ソフトウェアの依存関係に関する点です。複数のプロセスから構成されるアプリケーションがあると仮定します。アプリケーションの各プロセスを複数のコンテナ上に個別のコンテナにデプロイすることによって、より単純に、より透過的に扱うことができるようになります。その結果、ソフトウェアの依存関係から脱却することができます。また、適切な単位で分割されたコンテナは、再利用しやすくなります。

#3. 複数コンテナからなる単一Pod構成のユースケース
単一Pod内に複数コンテナをデプロイする主な目的は、主となるアプリケーションとそれに付随するプロセスを隣接させることによって、より管理しやすくすることです。そのための一般的なパターンがいくつかあります。
 
- 主となるコンテナを支援する目的でデプロイされるコンテナは、サイドカーコンテナと呼ばれます。例えば、ログやデータの更新監視機能や、監視用アダプタなどがサイドカーコンテナに相当します。1つ目の例として、ログ監視機能が挙げられます。他のアプリケーション用に起動したログ監視用コンテナが既にあれば、別のアプリケーションにも利用することができます。2つ目の例としては、主となるアプリケーション用にデータを生成するためのファイルローダやデータローダもサイドカーコンテナに相当します。
 
- プロキシ、ブリッジ、アダプタは、主となるコンテナを外部と接続する機能を提供します。Apache HTTPサーバやNginxがこれらに該当します。一般的には、Apache HTTPサーバやNginxは、主に静的ファイルを対象としたWebサービスを行います。それに加えて、メインコンテナ上で稼働するアプリケーションのリバース・プロキシとして、ログ採取やHTTPリクエスト数の制限を行います。また、リクエストをメインコンテナから外部ネットワークに転送する機能も有しています。この機能によって、サービスディスカバリー機能を利用しなくてもメイン・コンテナを外部データベースに接続することができるようになります。
 
WordPressをはじめ、階層構造となっているアプリケーションでも単一Pod上にデプロイすることは可能です。しかし、各階層に対応したPodを別々にデプロイすることをお勧めします。そうすることによって、階層毎にスケールアップすることができるようになりますし、またクラスタノード全体にPodを分散して配置することも可能です。

#4. 同一Pod内のコンテナ間通信
同一Pod内にデプロイされた複数のコンテナ間の通信は比較的簡単であり、
 - ボリューム共有による通信
 - プロセス間通信
 - ネットワーク通信
など、幾つかの方法があります。今回は、同一ボリューム共有による通信を取り上げます。

##4.1. 同一Pod内のボリューム共有による通信
同一Pod内のコンテナ間でデータを簡単かつ効率よく共有するための方法としてKubernetes共有ボリュームを使用します。多くの場合において、ローカルホスト上の任意のディレクトリーをコンテナ間で共有します。
コンテナの再起動を行っても、Kubernetesボリューム上のデータを失うことはありません。KubernetesボリュームのサイクルはPodと同じであるためです。つまり、ボリュームの割り当てられているPodが存在する限りは、ボリュームとそのボリューム上のデータを保持します。一方で、もしPodが何らかの理由で削除された場合、例えば同じ機能が別のPodに置き換えられた場合、既存のPodに割り当てられた共有ボリュームは削除され、新たに作成されたPod用の共有ボリュームが作成されることとなります。
共有ボリュームの一般的な活用例として、ある1つのコンテナが共有ボリューム上のログファイルあるいは関連ファイルに書き込み、その書き込まれた内容を他のコンテナが該当ファイルから読み込むといった例です。上記の例に沿ったPodを作成するための定義ファイル例を記載します：


```
apiVersion: v1
kind: Pod
metadata:
  name: mc1
spec:
  volumes:
  - name: html
    emptyDir: {}
  containers:
  - name: 1st
    image: nginx
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
  - name: 2nd
    image: debian
    volumeMounts:
    - name: html
      mountPath: /html
    command: ["/bin/sh", "-c"]
    args:
      - while true; do
          date >> /html/index.html;
          sleep 1;
        done
```

この例では、共有ボリューム名：html, タイプ：emptyDirとして定義します。この共有ボリュームは、Podの作成と同時にボリュームも作成され、Podが存在する限りボリュームも存在します。定義通り、作成時点ではボリュームは空の状態です。まずnginx用コンテナを起動し、共有ボリュームをコンテナ上のディレクトリ/usr/share/nginx/htmlにマウントします。続いて、Debianをイメージとした2つ目のコンテナを起動し、共有ボリュームをディレクトリ/htmlにマウントします。そして毎秒毎に日時情報を共有ボリューム上のファイルindex.htmlに追加します。ユーザーがPodに対してHTTPリクエストを送信するたびに、Nginxサーバはこの日時が記録されたファイルindex.htmlを読み込んで、レスポンスとして返信します。

次のように、Nginx用のポートにブラウザからアクセスするか、コンテナ間で共有している共有ディレクトリを直接確認することによって、Podの稼働状況を確認することができます。
![Volume communication](https://cdn.mirantis.com/wp-content/uploads/2017/08/MultiContainerPods.png)

 ```
$ kubectl exec mc1 -c 1st -- /bin/cat　/usr/share/nginx/html/index.html
 ...
 Fri Aug 25 18:36:06 UTC 2017
 
 $ kubectl exec mc1 -c 2nd -- /bin/cat /html/index.html
 ...
 Fri Aug 25 18:36:06 UTC 2017
 Fri Aug 25 18:36:07 UTC 2017
```
#5. 最後に
Kubernetesはコンテナ・オーケストレーションツールとして、Pod内部のコンテナの動作やコンテナ間の通信を最適化します。 Kubernetesによるコンテナ間のオーケストレーションによって、ファイルボリュームを共有できたり、ネットワークやIPCを経由で通信することもできます。
今回は、共有ボリュームを介した同一Pod内コンテナ間通信について記載しました。次回は、プロセス間通信について説明します。
Kubernetetsに実際に触れてみて、Kubernetesの可能性を探ってみたくなった方は、ぜひ[Mirantisのトレーニング](https://www.mirantis.co.jp/training/)にご参加ください。

【参考資料】
Pavel Chekin (2017), “Multi-container pods and container communication in Kubernetes,” Mirantis Open Cloud Digest, https://www.mirantis.com/blog/multi-container-pods-and-container-communication-in-kubernetes/, Mirantis Inc.