これまでの章で HTTP サーバを Pod として起動し、それにアクセスする方法を紹介してきました。
本章では同じコンテンツを返す HTTP サーバを複数台起動し、HTTP リクエストをそれらで分散して処理させる方法を紹介します。
Service リソースのマニフェストファイルを書いたときに Pod の指定方法が名前ではなくラベルであったことを覚えているでしょうか。
リソース名は Namespace 内で一意である必要がありますが、ラベルは複数のリソースで同じラベルを指定することができます。
つまり、Service リソースには最初から負荷分散機能が用意されていたのです。
あとは Pod を複数立てればおしまいです。
ここでは複数 Pod を立てるいくつかの方法について紹介します。
Pod
を複数用意する
もちろん、素朴に Pod リソースを複数作成するだけで目的は達成できます。
まずはこの方法から試してみて、Service リソースが本当に負荷分散してくれるのかを見てみましょう。
apiVersion: v1
kind: Pod
metadata:
name: web
labels:
app: web
spec:
containers:
- name: http # コンテナ名
image: nginx # コンテナイメージ名
ports:
- containerPort: 80 # 公開されているコンテナのポート番号
---
apiVersion: v1
kind: Pod
metadata:
name: web2
labels:
app: web
spec:
containers:
- name: http # コンテナ名
image: nginx # コンテナイメージ名
ports:
- containerPort: 80 # 公開されているコンテナのポート番号
マニフェストファイルには ---
で区切ることで複数のマニフェストを記述できます。
web
Pod と同じ設定内容の web2
Pod のマニフェストを追記しました。
このマニフェストファイルを適用することで、nginx コンテナを 2 つ起動できます。
$ kubectl apply -f pod.yaml
$ kubectl get pods
Service リソース経由でリクエストを送信することで、web
Pod と web2
Pod にリクエストが分散されます。
どちらの Pod にアクセスされたかを確認するためにログを出力しておきましょう。
コンテナのログは kubectl logs
コマンドで確認できるのでした。
kubectl logs
の -f
オプションを使うとログの追記を即座に画面に表示してくれます。
今回はこれを利用してみましょう。
$ kubectl logs -f web
実行中はプロンプトが返ってこないので別のターミナルを起動して web2
Pod のログも出力しておきましょう。
$ kubectl logs -f web2
リクエスト方法は前章の内容を思い出しましょう。
ここでは type NodePort である前提で k8s ノードの IP アドレス経由でアクセスします。
$ curl 172.18.0.3:31651
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
何度かアクセスをしてみると、リクエストが分散されていることを確認できるかと思います。
これで負荷分散をしたいという要求は満たせました。
しかし 2 個程度であればコピペしても手間ではありませんが、数が増えたら面倒です。
このように同じ設定内容の Pod を複数用意したいときのために ReplicaSet リソースが存在します。
ReplicaSet リソース経由で Pod を複数起動する
まず pod.yaml
で作成した web
Pod web2
Pod を削除しておきます。
$ kubectl delete -f pod.yaml
そして web
ReplicaSet を作成するためのマニフェストファイルを記述します。
apiVersion: apps/v1
kind: ReplicaSet
metadata:
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web # この ReplicaSet の管理対象となっている Pod が持つラベルを指定
template:
metadata:
labels:
app: web # spec.selector の指定に合うラベルでないと作成時にエラーになる
spec:
containers:
- name: http # コンテナ名
image: nginx # コンテナイメージ名
ports:
- containerPort: 80 # 公開されているコンテナのポート番号
$ kubectl apply -f replicaset.yaml
$ kubectl get replicaset
$ kubectl get pods
web-XXXXX
という名前の Pod が spec.replicas
に記述した数だけ起動していることを確認しましょう。
これで 100 コンテナで負荷分散したくなったとしても spec.replicas
を 100
に変更するだけで実現できるようになりました。
しかし実際の運用では ReplicaSet を直接利用することはありません。
ReplicaSet では何が問題なのでしょうか。
例えば nginx
のバージョンを変更したいと思った場合、どのような手順で更新作業を行うでしょうか。
- 新しいバージョンの Pod を作成してから古いバージョンの Pod を削除する
- 古いバージョンの Pod を削除してから新しいバージョン Pod を作成する
- 古いバージョンの Pod と新しいバージョンの Pod の合計数を保つように一つずつ作成と削除を繰り返す
上記のような更新手順が考えられます。
このような頻出の更新パターンを自動で制御するためのリソースがいくつか存在します。
実際の運用ではこの機能を持つリソースを利用します。
Deployment リソース経由で Pod を複数起動する
Deployment を使うことで以下のようなユースケースを満たせます。
- 新しいバージョンの Pod を作成してから古いバージョンの Pod を削除する
- 古いバージョンの Pod と新しいバージョンの Pod の合計数を保つように一つずつ作成と削除を繰り返す
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: http # コンテナ名
image: nginx # コンテナイメージ名
ports:
- containerPort: 80 # 公開されているコンテナのポート番号
このマニフェストを適用すれば Deployment リソースの作成は完了です。
$ kubectl apply -f deployment.yaml
$ kubectl get pods
Deployment は自動で ReplicaSet を用意して、ReplicaSet が Pod を作成するという流れになっています。
$ kubectl get replicaset
nginx コンテナイメージのバージョンを更新してみましょう。
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: http # コンテナ名
image: nginx:1.16.1 # コンテナイメージ名
ports:
- containerPort: 80 # 公開されているコンテナのポート番号
$ kubectl get pods
$ kubectl get replicaset
この更新時には以下のような動作が行なわれています。
-
nginx:1.16.1
コンテナイメージを指定した ReplicaSet を作成する - 新しい ReplicaSet が起動する Pod 数に応じて
nginx
コンテナイメージを指定している ReplicaSet のレプリカ数を下げる
この仕組みにより、後方互換性を保った更新であれば断時間なしにリリースが行なえることになります。
Deployment は古い ReplicaSet を残します。
これにより変更後の Pod に何か問題があったとしても古い ReplicaSet が残っているのでロールバックを容易に行なえます。
$ kubectl rollout undo deployment web
もう一度同じコマンドを実行することでロールバックを取り消せます。
StatefulSet リソース経由で Pod を複数起動する
Deployment では Pod 名にランダムな文字列を付加することで一意性を担保していました。
HTTP サーバのような役割が同じコンテナで負荷分散するケースではこれで問題にならないでしょう。
一方 RDB のようにコンテナイメージは同一だが Read 用と Write 用で役割が分かれているケースではどうでしょう。
他にもホスト名が重要なケースでは Deployment を使うのは難しくなります。
そのようなケースのために StatefulSet を利用します。
apiVersion: v1
kind: Service
metadata:
name: web
spec:
clusterIP: None
selector:
app: web
ports:
- port: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
replicas: 2
selector:
matchLabels:
app: web
serviceName: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: http # コンテナ名
image: nginx # コンテナイメージ名
ports:
- containerPort: 80 # 公開されているコンテナのポート番号
StatefulSet は特定の名前で Pod が動作していることが重要なケースで利用するので、Service リソース供え付けの負荷分散機能を利用することはありません。
そのため clusterIP: None
という指定をすることで、Service リソースに IP アドレスを付けないようにできます。
$ kubectl apply -f statefulset.yaml