はじめに
こんにちは!
本記事は「本気で学ぶKubernetes」シリーズの第9回です。このシリーズでは、Kubernetesの基礎から実践まで、段階的に学んでいきます。
このシリーズは、第1回から順に読むことで体系的に学べる構成にしています。
まだご覧になっていない方は、ぜひ最初からご覧ください!
前回は、KubernetesのProbe(ヘルスチェック)について触れました。Readiness Probe、Liveness Probe、Startup Probeを使ってコンテナの健康状態を監視する方法を学びました。
【本気で学ぶKubernetes】コンテナのヘルスチェックを行うProbeに入門
今回は、StatefulSetについて学んでいきたいと思います。
この記事は人間がKubernetesの公式ドキュメントを読み漁りながら、人間の手で書いていますのでご安心ください!
Deploymentの限界について
DeploymentではPod名やIPアドレスがランダムに割り当てられたり、起動順序が保証されていない他、コンテナの再起動時にPodのデータが失われてしまうという特徴があります。
そのためWebサーバーやAPIサーバーといったいわゆる「ステートレス」なアプリケーションを動かすのに適しているリソースとも言えます。
データベースやキューのように「状態を持つ」アプリケーションを動かす場合、つまり「ステートフル」なアプリケーションをKubernetesで動かしたい場合はDeploymentでは対応しきれません
そこで使用されるのが今回のトピックのStatefulSetです。
StatefulSetとは
StatefulSetは、「ステートフル」なアプリケーションを動かすためのKubernetesのリソースです。
Deploymentと比較すると以下のような特徴があります。
- Pod名の固定
- StatefulSetで作成されるPodは、
web-0、web-1、web-2のように、固定の名称を動的につけることができます。 - Podを削除して再作成しても、同じ名前で作り直されます。
- StatefulSetで作成されるPodは、
- 起動・削除の順序を保証
- StatefulSetでは、Podの起動・削除が順序通りに行われます。
- 起動時:0→1→2の順で、1つずつ起動
- 削除時:2→1→0の順で、1つずつ削除
- Pod専用の永続ストレージを利用
- StatefulSetでは、
volumeClaimTemplatesという機能を使って、各Podに専用のPersistentVolumeClaimを自動的に作成できます。
- StatefulSetでは、
出典: Kubernetes公式ドキュメント - StatefulSets
StorageClassとHeadless Serviceについて
StatefulSetを理解するためには、2つの前提知識が必要です。
StorageClass
以前の記事では、PersistentVolume(PV)とPersistentVolumeClaim(PVC)について触れました。
その時はPVを作成して手動でPodにPVCを割り当てるといったことを行いましたが、Podが立ち上がるたびにそれぞれのPodに専用ストレージを自動付与するためにPVを作るのは現実的ではないと思います。
そこで登場するのがStorageClassというリソースを使用します。
StorageClassを使うと、PVCを作成したタイミングで自動的にPVを作ることができます。
minikubeには、デフォルトでstandard StorageClassが用意されていますのでコマンドで確認してみます。
kubectl get storageclass
# NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
# standard (default) k8s.io/minikube-hostpath Delete Immediate false 10d
(default)と表示されているStorageClassが、PVCを作成したときに自動的に使われるようです。
StatefulSetのvolumeClaimTemplatesとこのStorageClassと組み合わせることで、各PodにPVCとPVを自動的に作成することができるようになります。
出典: Kubernetes公式ドキュメント - Storage Classes
Headless Service
こちらも以前の記事では、ServiceのタイプとしてClusterIP、NodePort、LoadBalancer、ExternalNameといったネットワークの概念について触れました
これらのServiceは、複数のPodに対して負荷分散を行ってアクセスを行います。
しかし、実際に運用していくにあたってはデータベースのマスターノードだけに書き込むなど「特定のPod」に直接アクセスしたい場合が考えられます。
Headless Serviceを使うとServiceとしてのロードバランシング自体は無効化して、代わりに各Podに固有に割り当てられるDNS名を付与してアクセスできるようにします。
例えば、nginx-serviceというHeadless Serviceとweb-0というPodがあったとすると、
web-0.nginx-service.default.svc.cluster.localという完全修飾ドメイン名(FQDN)で直接アクセスできるようになります。(以下はフォーマット)
<Pod名>.<Service名>.<Namespace名>.svc.cluster.local
出典: Kubernetes公式ドキュメント - Headless Services
StatefulSetを試してみる
今回も例の如く実際にマニフェストを作成して挙動を確認していこうと思います。
nginxを3台構成で起動するようにマニフェストを作成します。
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
clusterIP: None # Headless Serviceにする
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx-service" # Headless Serviceを指定
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.25
ports:
- containerPort: 80
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates: # Pod専用のPVCを自動作成
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 100Mi
ポイントは2点、もちろん先ほど触れたHeadless ServiceとvolumeClaimTemplatesです。
clusterIP: Noneを設定することで、Headless Serviceになります。
また、volumeClaimTemplatesを指定することで各Podに対してPVCを作成するようにしています。
マニフェストをデプロイしてみます。
kubectl apply -f statefulset-nginx.yaml
# service/nginx-service created
# statefulset.apps/web created
デプロイをしてすぐにPodの状態を確認してみました。
kubectl get pods -w
# NAME READY STATUS RESTARTS AGE
# web-0 0/1 ContainerCreating 0 1s
# web-0 1/1 Running 0 1s
# web-1 0/1 Pending 0 0s
# web-1 0/1 Pending 0 0s
# web-1 0/1 Pending 0 0s
# web-1 0/1 ContainerCreating 0 0s
# web-1 1/1 Running 0 1s
# web-2 0/1 Pending 0 0s
# web-2 0/1 Pending 0 0s
# web-2 0/1 Pending 0 0s
# web-2 0/1 ContainerCreating 0 0s
# web-2 1/1 Running 0 1s
Deploymentでは、3つのPodが同時に起動していましたが、StatefulSetではweb-0から順番にPodが起動していることがわかりますね。
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# web-0 1/1 Running 0 30s
# web-1 1/1 Running 0 25s
# web-2 1/1 Running 0 20s
以前までDeploymentで作成した場合はnginx-deployment-abc123-xyzといったようなランダムな名称が付けられていましたが、StatefulSetの場合はweb-0、web-1、web-2という名称が付いています。
PVCが自動的に作成されているか確認してみます。
kubectl get pvc
# NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
# www-web-0 Bound pvc-12345678-1234-1234-1234-123456789012 100Mi RWO standard 1m
# www-web-1 Bound pvc-23456789-2345-2345-2345-234567890123 100Mi RWO standard 1m
# www-web-2 Bound pvc-34567890-3456-3456-3456-345678901234 100Mi RWO standard 1m
www-web-0、www-web-1、www-web-2という3つのPVCが自動的に作成されていて、volumeClaimTemplatesで指定したwwwという名前と、Pod名を組み合わせた命名規則になっていることがわかるかと思います。
データの永続性を確認してみる
StatefulSetでは、Podを削除しても同じPodが同じストレージを使い続けることができると記載しましたが、その挙動についても確認しておきたいと思います。
web-0のストレージにindex.htmlファイルを配置します
kubectl exec web-0 -- bash -c "echo 'Hello from web-0' > /usr/share/nginx/html/index.html"
# ファイルが作成されたかの確認
kubectl exec web-0 -- cat /usr/share/nginx/html/index.html
# Hello from web-0
web-0を削除してみます。
kubectl delete pod web-0
# pod "web-0" deleted
Podの状態を確認してみると、web-0が再作成されていることがわかります。
kubectl get pods
# NAME READY STATUS RESTARTS AGE
# web-0 0/1 ContainerCreating 0 3s
# web-1 1/1 Running 0 5m
# web-2 1/1 Running 0 5m
再作成されたweb-0にて最初に追加したファイルを確認してみます。
kubectl exec web-0 -- cat /usr/share/nginx/html/index.html
# Hello from web-0
web-0が削除されたとしても、同じ名前のweb-0が作り直されて同じPVCであるwww-web-0をマウントしているためデータが消えずに残っています。
クリーンアップ
挙動確認が終わったので作成したリソースを削除しておきます。
kubectl delete -f statefulset-nginx.yaml
# service "nginx-service" deleted
# statefulset.apps "web" deleted
またStatefulSetを削除しても、PVCは自動的に削除されませんので手動で削除しておきましょう!
kubectl delete pvc www-web-0 www-web-1 www-web-2
# persistentvolumeclaim "www-web-0" deleted
# persistentvolumeclaim "www-web-1" deleted
# persistentvolumeclaim "www-web-2" deleted
StatefulSetを削除してもPVCが残るのは、データの誤削除を防ぐためにこの仕様になっているようです。
まとめと次回予告
今回は、StatefulSetについて触れてきました。
今まで使ってきたDeploymentは「ステートレス」なアプリケーション向けで、今回のStatefulSetは「ステートフル」なアプリケーション向けだということがわかりました。
基本的には、データが消えても問題なくどのPodでも同じ役割ならDeployment、データを永続化したりPod間で役割をかえるならStatefulSetを使うということを押さえておくと良いかと思います。
実際には外部サービスとうまく連携させて使うことが多いと思いますが、ステートフルなアプリケーションを設計していく場合はこちらも念頭においておいた方が良いですね。
次回は、KubernetesにおけるIngressやHTTP(S) Load Balancerについて触れていきたいと思います。
それでは、また明日!