ポッド内のマルチコンテナ#

ポッドは複数のコンテナを収容できます(ポッド(pod)はお豆が入っている「さや」という意味を持ちます)。 そのため、ポッドは中に複数のコンテナを保有することができます(単純にコンテナと言えないのはこの辺りも含まれています)。

なんのために使うかはいろいろ考えられますが、ここではそのうちのひとつである「コンテナを初期化するコンテナ」を考えてみます。

初期化用コンテナの利用#

コンテナをWebサービスの提供元と考えた場合、コンテンツを含める必要があります。 これを実現することを考えた場合、開発時のように「ホスト側と共有するディレクトリをマウント」はできません。 自ずと「イメージにコンテンツを入れておく」ことになります。 たとえば

Listing 15 nginxイメージをベースに、コンテンツ(ホスト側src)の内容をビルド時に封入する例#
FROM nginx
COPY src .

その一方で、コンテンツは(部分的にでも)比較的頻繁に更新されることになります。 とはいえ稼働中にコンテンツを更新するのは整合性に問題が出そうなので、コンテナ起動時に最新コンテンツを取得するあたりで折り合いを付けたとしましょう。 これを実現しようとすると、案外難儀で、データ取得の上で本来のイメージに合ったWebサーバー起動を自前で行う処理をスクリプトにしてENTRYPOINTに書かないといけません。

そして、イメージに対する信頼性というものを考えた場合、公式イメージをつかうのが一番トラブルが少なそうです。 そこでサイドカーパターンという考え方が登場します。

  • メインのコンテナは信頼できるイメージを使う

  • 横に補助的にコンテナを起動させ、メインのコンテナにデータを注入する

この構造をマニフェストで記述するとこうなります。

Listing 16 サイドカーパターンによる初期化#
 1apiVersion: v1
 2kind: Pod
 3metadata:
 4  name: initsample
 5  labels:
 6    name: initsample
 7spec:
 8  volumes:
 9    - name: contents
10      emptyDir: {}
11
12  containers:
13  - name: initsample
14    image: nginx
15    resources:
16      limits:
17        memory: "128Mi"
18        cpu: "500m"
19    ports:
20      - containerPort: 80
21    volumeMounts:
22      - mountPath: /usr/share/nginx/html
23        name: contents
24        readOnly: true
25  initContainers:
26    - name: init
27      image: alpine
28      volumeMounts:
29        - mountPath: /contents
30          name: contents
31      command:
32        - sh
33        - "-c"
34        - "date > /contents/index.html"

ここまでで書いたポッドマニフェストから少し複雑になっています。

共有ボリューム#

片方のコンテナからもう片方のコンテナにデータを送り込むためには、両方のコンテナで共有するディレクトリが必要になります。 そこで8〜10行目で記述されたようにvolumesというキーにてポッド内で使うボリューム(記憶域)の宣言を行っています。 ボリュームの定義は「ボリューム名(name)」と「ボリュームの形式(ここではemptyDir)」になります。

contentsというボリューム名を定義し、実行空間から空のディレクトリを用意してもらうemptyDirという形式を依頼しています。

Hint

emptyDir 形式は、同じポッド上で動くのであれば(同じノード上で動くことが保証されているので)ポッド内で同じボリュームを共有できます。 ポッド内コンテナが落ちて自動再起動があったとしても、内容は維持しています。

そしてポッドが消えるときに内容も消える仕組みになっています。

ボリュームの形式に関しては、ストレージの項目で基本的なものを取りあげる予定です。

ボリュームのマウント#

マニフェスト内で定義したボリュームはコンテナ側で宣言してマウントさせることができます。

Listing 17 コンテナ側のボリュームマウント#
    volumeMounts:
      - mountPath: /usr/share/nginx/html
        name: contents
        readOnly: true

利用するイメージでコンテナ空間を生成後、mountPathのディレクトリにnameで指定された(volumesで定義された)ボリュームをマウントさせます。 ここではさらに、nginx側のトラブルがあったとしても書き換えられないように読み込み専用フラグを立てています。

初期化用コンテナ#

続いて初期化用コンテナを実装します。 本来はコンテンツをどこかから持ってくるのですが、簡素化してコンテナが動いた時間をファイルに書き込むようにしてみます。

初期化コンテナはcontainerではなくinitContainers以下で記述します。

Listing 18 初期化コンテナ部#
  initContainers:
    - name: init
      image: alpine
      volumeMounts:
        - mountPath: /contents
          name: contents
      command:
        - sh
        - "-c"
        - "date > /contents/index.html"

今回はalpineのイメージを用い、contentsボリュームを/contentsディレクトリにマウントし、シェルスクリプトで時刻を/contents/index.htmlにリダイレクトで書き込んでいます。

共有ボリュームを用いることで片方のコンテナで書き込んだ内容はそのままもう片方で利用可能となっています。

動作チェック#

では実際に動くかをみてみましょう。 今回のマニフェストは、初期化コンテナが動くという仕組みのため、即座にポッドが稼働中状態にはなりません。

  1. 共有ボリュームを準備します

  2. 初期化コンテナが起動します

  3. 初期化コンテナが正常に終了したことを確認してから、メインのコンテナを起動します

この動きを見るために、端末をひとつ用意し、状況を監視していきましょう。

Listing 19 Pod状態の確認用コマンド#
# "-w"付きでget podsして監視モードに入る
PS> minikube kubectl -- get pods -w

そして別の端末でマニフェストを適用させます。

Listing 20 初期化コンテナ付きポッドマニフェストの適用#
PS> minikube kubectl -- apply -f init-sidecar.yml
pod/initsample created

このとき、ポッド監視をしている端末では動きが出ます。

Listing 21 マニフェスト適用後の動き#
NAME     READY   STATUS    RESTARTS      AGE
initsample   0/1     Pending   0             0s
initsample   0/1     Pending   0             0s
initsample   0/1     Init:0/1   0             1s
initsample   0/1     PodInitializing   0             4s
initsample   1/1     Running           0             7s
  • Init により初期化コンテナが稼働します、初期化コンテナも複数記述可能なため、コンテナ数と終了数が記述されています

  • PodInitializing にて実際に準備を行っています

  • その後本体のポッドの生成が行われ、稼働状態(Running)と移行します

このとき、初期化コンテナは稼働後に終了して消滅します。

コンテナのプログラム類を書き換えるわけでなければ、このようにベースイメージとして提供されているものをそのまま使うことで、イメージに脆弱性を自分で織り込んでしまう可能性を防ぐことも可能となるわけです。

実際にコンテンツが取れるかですが、今回はサービスリソースを定義していないので、kubectlに少しだけお仕事してもらいます。 get pods -wしてた端末側をCtrl-Cで止めてもらってから、次のコマンドを打ち込んでみてください。

Listing 22 ポートフォワード#
PS> kubectl port-forward pod/initsample 8888:80
Forwarding from 127.0.0.1:8888 -> 80
Forwarding from [::1]:8888 -> 80

これはpod/initsampleがポート公開を宣言している80/tcpに対し、外部から接続できるようにNATを設定する機能です。この機能はCtrl-Cで止めるまで有効なので、ブラウザで確認してみましょう。

Hint

ポートフォワードを使った場合、ロードバランサーを設定したデプロイメントを指したとしても、レプリケーション中の1台を適当に選んでそこにしか行かないという制限もあります。ロードバランサーを活かしたい場合はサービス提供後にminikube tunnelするようにしましょう。

Hint

他のコンテンツが出る場合、ブラウザキャッシュの可能性と、他のプログラムが8888を握っている可能性を考慮してください。 後者であればポートフォワードを一度停止し、8888以外の番号でやり直してみてください。

後始末#

  • ポートフォワードはCtrl-Cで停止できます、停止しておきましょう

  • マニフェストファイルを kubectl delete -f すればポッドと紐付いているコンテナを終了できます

並行動作の例: MariaDBとphpMyAdmin#

MariaDBやMySQLなど、LAMP環境で使うデータベースに関して、開発中であればデータの出し入れや構造の視覚化などでphpMyAdminを使うことも多いと思います。

そこで、両方のコンテナを持ってしまう欲張りポッドを考えて見ましょう。コード的にはこうなります。

Listing 23 MariaDBとphpMyAdminを同時に動かす例#
# MariaDBとphpMyAdminを並行起動する例
apiVersion: v1
kind: Pod
metadata:
  name: mariadb-phpmyadmin
  labels:
    name: mariadb-phpmyadmin
spec:
  containers:
  - name: mariadb
    image: mariadb
    resources:
      limits:
        memory: "256Mi"
        cpu: "750m"
    ports:
      - containerPort: 3306
    env:
      # 注意: ↓ 本来はsecret等で対応するべきです
      - name: MARIADB_ROOT_PASSWORD
        value: dbadmin
  - name: phpmyadmin
    image: phpmyadmin
    resources:
      limits:
        cpu: "500m"
        memory: "256Mi"
    ports:
      - containerPort: 80
    env:
      - name: PMA_HOST
        value: mariadb-phpmyadmin
    

こちらは initContainers を用いずに、そのまま containers に両方を記載することで実現しています。

マニフェスト適用後、

Listing 24 phpMyAdmin側のポートフォワード#
PS> kubectl port-forward pod/mariadb-phpmyadmin 8888:80

で同様にブラウザからアクセスできます。

Hint

もしかすると他のポッドが動いているとリソースが不足してPendingになるかもしれません、その時は適宜他のポッドは止めてあげてください。

Warning

このマニフェストには途中のコメントで入れているように、パスワードが生のまま記載されており、このまま使うことは非常に危険です。 本来ならsecretリソースで保護するなどの対策を行って使うようにしましょう。