ポッド内のマルチコンテナ
Contents
ポッド内のマルチコンテナ#
ポッドは複数のコンテナを収容できます(ポッド(pod)はお豆が入っている「さや」という意味を持ちます)。 そのため、ポッドは中に複数のコンテナを保有することができます(単純にコンテナと言えないのはこの辺りも含まれています)。
なんのために使うかはいろいろ考えられますが、ここではそのうちのひとつである「コンテナを初期化するコンテナ」を考えてみます。
初期化用コンテナの利用#
コンテナをWebサービスの提供元と考えた場合、コンテンツを含める必要があります。 これを実現することを考えた場合、開発時のように「ホスト側と共有するディレクトリをマウント」はできません。 自ずと「イメージにコンテンツを入れておく」ことになります。 たとえば
FROM nginx
COPY src .
その一方で、コンテンツは(部分的にでも)比較的頻繁に更新されることになります。
とはいえ稼働中にコンテンツを更新するのは整合性に問題が出そうなので、コンテナ起動時に最新コンテンツを取得するあたりで折り合いを付けたとしましょう。
これを実現しようとすると、案外難儀で、データ取得の上で本来のイメージに合ったWebサーバー起動を自前で行う処理をスクリプトにしてENTRYPOINT
に書かないといけません。
そして、イメージに対する信頼性というものを考えた場合、公式イメージをつかうのが一番トラブルが少なそうです。 そこでサイドカーパターンという考え方が登場します。
メインのコンテナは信頼できるイメージを使う
横に補助的にコンテナを起動させ、メインのコンテナにデータを注入する
この構造をマニフェストで記述するとこうなります。
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
形式は、同じポッド上で動くのであれば(同じノード上で動くことが保証されているので)ポッド内で同じボリュームを共有できます。
ポッド内コンテナが落ちて自動再起動があったとしても、内容は維持しています。
そしてポッドが消えるときに内容も消える仕組みになっています。
ボリュームの形式に関しては、ストレージの項目で基本的なものを取りあげる予定です。
ボリュームのマウント#
マニフェスト内で定義したボリュームはコンテナ側で宣言してマウントさせることができます。
volumeMounts:
- mountPath: /usr/share/nginx/html
name: contents
readOnly: true
利用するイメージでコンテナ空間を生成後、mountPath
のディレクトリにname
で指定された(volumes
で定義された)ボリュームをマウントさせます。
ここではさらに、nginx側のトラブルがあったとしても書き換えられないように読み込み専用フラグを立てています。
初期化用コンテナ#
続いて初期化用コンテナを実装します。 本来はコンテンツをどこかから持ってくるのですが、簡素化してコンテナが動いた時間をファイルに書き込むようにしてみます。
初期化コンテナはcontainer
ではなくinitContainers
以下で記述します。
initContainers:
- name: init
image: alpine
volumeMounts:
- mountPath: /contents
name: contents
command:
- sh
- "-c"
- "date > /contents/index.html"
今回はalpineのイメージを用い、contents
ボリュームを/contents
ディレクトリにマウントし、シェルスクリプトで時刻を/contents/index.html
にリダイレクトで書き込んでいます。
共有ボリュームを用いることで片方のコンテナで書き込んだ内容はそのままもう片方で利用可能となっています。
動作チェック#
では実際に動くかをみてみましょう。 今回のマニフェストは、初期化コンテナが動くという仕組みのため、即座にポッドが稼働中状態にはなりません。
共有ボリュームを準備します
初期化コンテナが起動します
初期化コンテナが正常に終了したことを確認してから、メインのコンテナを起動します
この動きを見るために、端末をひとつ用意し、状況を監視していきましょう。
# "-w"付きでget podsして監視モードに入る
PS> minikube kubectl -- get pods -w
そして別の端末でマニフェストを適用させます。
PS> minikube kubectl -- apply -f init-sidecar.yml
pod/initsample created
このとき、ポッド監視をしている端末では動きが出ます。
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で止めてもらってから、次のコマンドを打ち込んでみてください。
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を使うことも多いと思います。
そこで、両方のコンテナを持ってしまう欲張りポッドを考えて見ましょう。コード的にはこうなります。
# 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
に両方を記載することで実現しています。
マニフェスト適用後、
PS> kubectl port-forward pod/mariadb-phpmyadmin 8888:80
で同様にブラウザからアクセスできます。
Hint
もしかすると他のポッドが動いているとリソースが不足してPendingになるかもしれません、その時は適宜他のポッドは止めてあげてください。
Warning
このマニフェストには途中のコメントで入れているように、パスワードが生のまま記載されており、このまま使うことは非常に危険です。
本来ならsecret
リソースで保護するなどの対策を行って使うようにしましょう。