Content
本番環境での Kubernetes の使用にあたっては、コンテナ化したアプリケーションをデプロイするためのオプションが多数用意されています。その 1 つが Kubernetes StatefulSet で、アプリケーションコンテナが停止して終了するときにデータを持続させることができます。この対象には、データベースや他のデータストア、およびステートフルアプリケーションが含まれます。
詳しくは、K8s StatefulSet に関するこの概要記事とチュートリアルをお読みください。この記事では、まず StatefulSet の内容と StatefulSet を使用すべき場合について説明します。次に、StatefulSet の作成、更新、削除方法について取り上げます。最後に、Deployment、DaemonSet、および ReplicaSet と StatefulSet を体系的に比較し、それぞれを使用すべき場合について説明します。
また、Kind を使用して、このチュートリアルの目的に沿ったサンプルクラスタを作成しています。
それでは始めましょう。
Kubernetes StatefulSet とは
Kubernetes StatefulSet は、それぞれが一意の状態要件を含む、Pod セットを表現しています。専用のボリューム、一意のホスト名レコード、および特定のデプロイ順序のニーズを規定しています。StatefulSet の主な意図は、アプリケーションが障害により再起動する場合に、ファイルシステム内にデータを保存してそれらに再接続できるアプリケーションを、開発者がデプロイできるようにすることです。MySQL、PostgreSQL、Redis などのデータベース、nginx や Apache などの HTTP サーバー、および Kafka や Zookeeper などの永続ブローカーが該当します。
StatefulSet をデプロイすると、K8s は各レプリカに独自の状態(ボリューム)を割り当て、デプロイと更新の順序を保証します。たとえば、特定の StatefulSet に 3 つのレプリカを指定した場合、StatefulSet はそれらを順番にデプロイし、それぞれに独自の PVC を割り当てます。StatefulSet を削除またはスケールすると、最初にデプロイされたときと同じ順序で削除またはスケールされ、PVC は削除されないため、データの安全性が保証されます。
では、StatefulSet を使用する主な理由について説明しましょう。
StatefulSet を使用すべき場合
StatefulSet は、特定の Pod 要件があるときに使用することをお勧めします。まず、ステートフルアプリケーションとステートレスアプリケーションの違いを認識する必要があります。ステートフルアプリケーションでは、状態がファイルシステム内で保持されます。ステートフルアプリケーションの主な役割は、状態がどのようにアクセスされるを管理することです。ファイルシステムを使って情報を内部に保存するデータベースシステムおよびアプリケーションは、ステートフルアプリケーションの例です。
一方、ステートレスアプリケーションは、将来使用できる、または再起動後に持続できるクライアントデータを保持しません。ステートレスアプリケーションの例は、ソフトウェアエージェント、Web アプリケーション、Lambda 関数です。
どのアプリケーションがステートフルかを識別した後に、StatefulSet 内の各レプリカ用に特定のデプロイ戦略を作成することをお勧めします。各レプリカは 0 ~ N の順に作成され、N ~ 0 の反対の順序で削除されます。これにより、たとえば、最初の Pod をプライマリに、その他をレプリカ Pod に設定できます。プライマリ Pod はクライアントからのリクエストの読み書きを処理し、他の Pod は最初の Pod と同期してデータを複製できます。StatefulSet をスケールアップすることで新しい Pod を導入すると、K8s は新しい PVC をその Pod 用に予約します。
次に、StatefulSet を作成および更新する方法を見てみましょう。
StatefulSet を作成する方法
このデモでは、次の StatefulSet マニフェストを使用します:
stateful-set.yml
---
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-www
spec:
storageClassName: standard
accessModes:
- ReadWriteOnce
capacity:
storage: 1Gi
hostPath:
path: /www/
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
labels:
app: nginx
spec:
serviceName: "nginx"
selector:
matchLabels:
app: nginx
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.21.6
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
storageClassName: standard
Code language: JavaScript (javascript)
いくつか注目すべき点があります:
- Service spec ではヘッドレスサービスを定義します(clusterIP: None)。K8s が IP アドレスを割り当てない、トラフィックを転送しないことを意味します。代わりに、DNS サーバーはサービスの IP ではなく各 Pod の IP を返します(クライアントはこれを使用して Pod に接続できます)。
- volumeClaimTemplates 用に PersistentVolume をプロビジョニングする必要があります。そうしない場合は、Pending 状態でブロックされます。
- StatefulSet spec は、PVC の作成に使用するテンプレートを定義する、特別な volumeClaimTemplates フィールドを使用します。この例の各レプリカには一意の PVC が必要になります。
上記の仕様を適用した後に、状態をチェックできます:
❯ kubectl describe statefulsets
Name: web
Namespace: default
CreationTimestamp: Tue, 08 Mar 2022 13:12:38 +0000
Selector: app=nginx
Labels: app=nginx
Annotations: <none>
Replicas: 3 desired | 3 total
Update Strategy: RollingUpdate
Partition: 0
Pods Status: 3 Running / 0 Waiting / 0 Succeeded / 0 Failed
…
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal SuccessfulCreate 11m statefulset-controller create Pod web-0 in StatefulSet web successful
Normal SuccessfulCreate 11m statefulset-controller create Pod web-1 in StatefulSet web successful
Normal SuccessfulCreate 11m statefulset-controller create Pod web-2 in StatefulSet web successful
Code language: JavaScript (javascript)
StatefulSet を更新するためにさまざまな方法があります。最も簡単な方法は、次のコマンドを使用して、レプリカの数をスケールアップまたはスケールダウンすることです。
❯ kubectl scale statefulset web --replicas 4
statefulset.apps/web scaled
❯ kubectl get pods -l app
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 4h1m
web-1 1/1 Running 0 4h1m
web-2 1/1 Running 0 4h1m
web-3 1/1 Running 0 6s
Code language: JavaScript (javascript)
StatefulSet を 0 にスケールダウンすると、操作の順序を監視できます。最後の Pod、最後から 2 つ目の Pod、とスケールされていきます。
❯ kubectl scale statefulset web --replicas 0
❯ kubectl get pods -l app -w
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 4h4m
web-1 1/1 Running 0 4h4m
web-2 1/1 Running 0 4h4m
web-3 0/1 Terminating 0 3m15s
web-2 1/1 Terminating 0 4h4m
web-2 0/1 Terminating 0 4h4m
web-1 1/1 Terminating 0 4h4m
web-1 0/1 Terminating 0 4h4m
web-0 1/1 Terminating 0 4h4m
web-0 0/1 Terminating 0 4h4m
Code language: JavaScript (javascript)
K8s では、updateStrategy spec フィールドを使用して、更新戦略の動作をカスタマイズできます。persistentVolumeClaimRetentionPolicy spec フィールドを使用して、PVC 保持の動作をカスタマイズできます。
replicas、template、または updateStrategy 以外のフィールドを変更することで既存の StatefulSet spec を更新しようとすると、操作が失敗します:
❯ kubectl apply -f stateful-set.yml
service/nginx unchanged
The StatefulSet "web" is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'template', and 'updateStrategy' are forbidden
Code language: PHP (php)
StatefulSet を削除しても、そこにバインドされていた PVC はデフォルトでは削除されません。これにより、データの安定性が保証されます。それらは次のように個別に再クレームする必要があります:
❯ kubectl get statefulsets
NAME READY AGE
web 3/3 24m
❯ kubectl delete statefulsets web
statefulset.apps "web" deleted
❯ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pv-www 1Gi RWO standard 24m
www-web-1 Bound pvc-35ceb9b1-74e6-42e3-a74b-bd8548249562 1Gi RWO standard 24m
www-web-2 Bound pvc-b2b45bed-d2f2-4293-a419-8616f736b12b 1Gi RWO standard 24m
Code language: JavaScript (javascript)
spec は再適用できます。同じ Pod が順番にされ、対応する PVC が割り当てられます:
❯ kubectl apply -f stateful-set.yml
service/nginx unchanged
persistentvolume/pv-www unchanged
statefulset.apps/web created
Code language: JavaScript (javascript)
次のコマンドを使用して、この StatefulSet が割り当てられた Pod を検査できます:
❯ kubectl get pods -l app
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 3h55m
web-1 1/1 Running 0 3h55m
web-2 1/1 Running 0 3h55m
Code language: JavaScript (javascript)
次に、StatefulSet ではなく Deployment を使用すべき場合について説明しましょう。
StatefulSet と Deployment の比較
StatefulSet を使用する主な理由は、ステートフルアプリケーションを提供することです。そうでない場合は、Deployment を使用することをお勧めします。Deployment を開始して PVC を指定すると、その PVC はすべての Pod レプリカで共有されます(ボリュームが読み取り専用の場合)。前述のとおり、StatefulSet では、各 Pod に独自の PVC が割り当てられます。
Deployment をスケールアップまたはスケールダウンしても、K8s は順序を考慮しません。すべてを一度にトリガーします。しかし、StatefulSet では順序が重要であり、K8s はスケールアップまたはスケールダウンの際に、安定性を保証するためにその順序を保持します。
StatefulSet と DaemonSet の比較
DaemonSet は、K8s がクラスタ内の各ノードの Pod に割り当てる、一意の種類のリソースです。たとえば、3 つのノードがある場合、3 つの DaemonSet を、各ノードに 1 つずつ スケジュールします。StatefulSet では、NodeAffinity spec フィールドを指定しない場合は、デフォルトではこの動作になりません。Pod を処理するのに十分なリソースがある場合は、ノードごとにより多くの Pod をスケジュールできます。
ログまたはアプリのモニターおよびサイドカーなど、パラレルに使用するサービスでは、StatefulSet ではなく DaemonSet を使用することをお勧めします。これらのサービスは通常、イントロスペクションやモニタリングを容易にする長期間の非クリティカルなアプリと見なされています。
StatefulSet と ReplicaSet の比較
ReplicaSet はシンプルな複製された Pod を表現し、Deployment によく似ています。StatefulSet が Pod テンプレートを使用するように、ReplicaSet も Pod テンプレートを使用します。ただし、ReplicaSet では、K8s はローリングアップデートを自動的に処理しません。Pod を新しいバージョンに更新するには、別の ReplicaSet を作成して、1 つずつスケールアップする必要があります。ほとんどのユースケースでは、自動ロールアウトを提供する Deployment を使用することをお勧めします。ReplicaSet には Deployment と多くの共通点がありますが、ステートレスアプリケーションで使用することを検討してください。
次のステップ
この記事では、K8s StatefulSet の詳しいチュートリアルと実際の使用方法をご紹介しました。コンテンツに関心を持たれた方は、Sysdig から今後公開される、K8s、クラウドセキュリティ、オープンソーステクノロジに関連するチュートリアルをブログでチェックしてください。