概要
StatefulSet は Pod 名が常に同じ名前で起動し続ける機能です
これと Headless Service を組み合わせることで Pod の IP アドレスが変わっても名前を使って常に同じ Pod を参照し続けることができます
例えばクラスタ環境を構築する場合などに使います
今回は nginx コンテナを使って StatefulSet と Headless Service の挙動を確認してみました
環境
- macOS 10.14.5
- minikube v0.28.2
StatefulSet の yml ファイル
今回は nginx のコンテナを StatefulSet を使ってデプロイしてみます
vim nginx_sts.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 3
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /tmp
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
StatefulSet で構築した Pods には replicas
で指定した分の Pods が作成されます
また Pods には 0 から順番にインデックス番号が振られ Pods が再作成されても同じ番号が振られます
volumeClaimTemplates
を定義するのもポイントで StatefulSet の場合はデフォルトで PersistentVolumeClaim ありの状態で Pods が上がってくるのが基本です
今回は /tmp
にマウントしています
DocumentRoot が /usr/share/nginx/html
なのでここをマウントしてもいいのですが index.html
が上書きでなくなってしまいます
PersistentVolume に cp してもいいのですが今回はテストなのでマウントポイントを変更することで対応しています
matchLabels
と labels
の app: nginx
は必ず同じラベルを振るようにしてください
Pods に振った app: nginx
というラベルに対して StatefulSet のルールを適用するためです
これで StatefulSet をデプロイしましょう
replicas
で指定した数になるまで Pods が作成されます
kubectl apply -f nginx_sts.yml
Service の yml ファイル
Service もポイントで Headless Service という仕組みを使うことで Pods 同士を名前でアクセスできるようにします
vim nginx_svc.yml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
clusterIP: None
にするのがポイントです
これで Service をデプロイしましょう
kubectl apply -f nginx_svc.yml
今回作成したリソースは以下の通りです
kubectl get sts,pv,pvc,po,svc
NAME DESIRED CURRENT AGE
statefulset.apps/web 3 3 32m
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-160b82d3-81a9-11e9-b396-08002719d78a 1Gi RWO Delete Bound default/www-web-0 standard 32m
persistentvolume/pvc-21951e03-81a9-11e9-b396-08002719d78a 1Gi RWO Delete Bound default/www-web-1 standard 32m
persistentvolume/pvc-28dd3ed2-81a9-11e9-b396-08002719d78a 1Gi RWO Delete Bound default/www-web-2 standard 32m
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/www-web-0 Bound pvc-160b82d3-81a9-11e9-b396-08002719d78a 1Gi RWO standard 32m
persistentvolumeclaim/www-web-1 Bound pvc-21951e03-81a9-11e9-b396-08002719d78a 1Gi RWO standard 32m
persistentvolumeclaim/www-web-2 Bound pvc-28dd3ed2-81a9-11e9-b396-08002719d78a 1Gi RWO standard 32m
NAME READY STATUS RESTARTS AGE
pod/web-0 1/1 Running 0 32m
pod/web-1 1/1 Running 0 32m
pod/web-2 1/1 Running 0 32m
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 120d
service/nginx ClusterIP None <none> 80/TCP 2m
ちなみに PersistentVolume は standard
という StorageClass を使っており standard
は何かというとホストマシンの特定のパスをマウントする StorageClass になります
kubectl describe pv pvc-160b82d3-81a9-11e9-b396-08002719d78a
Source:
Type: HostPath (bare host directory volume)
Path: /tmp/hostpath-provisioner/pvc-160b82d3-81a9-11e9-b396-08002719d78a
でこのホストは何かと言うと minikube で起動している Virtualbox の VM になります
実際に ssh して確認すると該当のパスが存在するのが確認できます
minikube ssh
ls /tmp/hostpath-provisioner/pvc-160b82d3-81a9-11e9-b396-08002719d78a/
Pods を再作成しても同じ状態で上がってくることを確認する
StatefulSet の機能を確認していきます
冒頭でも少し触れましたが StatefulSet 経由で作成した Pod にはインデックス番号が振られます
そして一度振られた番号は何度 Pod を再作成しても同じ状態で起動します
その挙動を確認してみます
まず Headless Service が有効になっているか確認します
各 Pod が DNS を使って名前でアクセスできれば OK です
nslookup
が nginx コンテナでは使えないので確認用の Pod を作成します
kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm
/ # nslookup web-0.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx
Address 1: 172.17.0.7 web-0.nginx.default.svc.cluster.local
/ # nslookup web-1.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-1.nginx
Address 1: 172.17.0.8 web-1.nginx.default.svc.cluster.local
/ # nslookup web-2.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-2.nginx
Address 1: 172.17.0.9 web-2.nginx.default.svc.cluster.local
こんな感じで StatefulSet 経由で作成した各 Pod が Headless Service のおかげで名前でアクセスできるようになっています
ではこの状態で Pod を削除してみましょう
kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted
pod "web-2" deleted
しばらくすると StatefulSet が再度 Pod を作成してくれます
Running になったら再度 nslookup コマンドを実行してみましょう
先ほどと同じ名前で IP が引けることが確認できると思います
Pod を再作成すると IP が変わることがありますが名前が常に同じ状態なので名前さえわかっていれば常に同じ Pod にアクセスすることができるようになります
これが StatefulSet のメイン機能になります
nginx にアクセスするにはどうすればいいか
Pod に直接 exec すればいいのですがそれだと面倒な場合が多いです
一番てっとり早いのはもう一つ公開用の Service をデプロイするのが簡単です
vim nginx_svc_ext.yml
apiVersion: v1
kind: Service
metadata:
name: nginx-ext
labels:
app: nginx
spec:
type: NodePort
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
kubectl apply -f nginx_svc_ext.yml
kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 120d
nginx ClusterIP None <none> 80/TCP 2m
nginx-ext NodePort 10.102.236.113 <none> 80:31481/TCP 1m
ちゃんと各 Pod にアクセスできているか確認するために index.html
を書き換えます
for i in 0 2; do kubectl exec web-$i -- sh -c 'echo $(hostname) > /usr/share/nginx/html/index.html'; done
あとは minikube 経由でアクセスすれば nginx が見えると思います
curl $(minikube ip):31481
ちゃんと Headless Service もあるので Pods 内で名前解決も引き続きできると思います
後処理
kubectl delete sts web
kubectl delete pvc www-web-0 www-web-1 www-web-2
kubectl delete svc nginx nginx-ext
最後に
minikube で StatefulSet を試してみました
ポイントは
- PersistentVolumeClaim と組み合わせて使う
- Headless Service を使うことで Pod を名前で引けるようにする
- 常に同じ Pod 名で起動するので名前で引ければ常に同じ Pod にアクセスすることができる
になるかなと思います
今回のサンプルの場合は PersistentVolumeClaim は必須ではないかなと思います
が、Pod が削除された場合に同じ Pod 名で起動するけどデータがないというのは少しステートフルとは違うかなと思うので基本は PersistentVolumeClaim と組み合わせて使うのが良いと思います
よく Deployment と比較されますが Deployment は Pod 名が固定にはならないので、そこがステートレスと言われているポイントなのかなと思います