概要
Topology-Aware Volume Provisioningが、Kubernetes 1.12で追加されました。この機能により、ZoneやRegionを指定し、Podとそれに関連するPersistentVolume(PV)を指定したZoneやRegionにて作成し割り当てることができるようになります。この機能をつかうためには、KubernetesのNodeだけでなくストレージも対応している必要があります。
2019/3時点では以下のストレージが対応しています。
- AWS EBS
- Azure Disk
- GCE PD
- CSI(alpha:https://kubernetes-csi.github.io/docs/topology.html)
Region, Zoneとは
Region, Zoneに馴染みの薄い人もいるかと思うので、Region, Zoneについて簡単に説明します。
Regionは、データセンターの地域のことです。例えば東京リージョン(Region)だと東京にあるデータセンター、大阪リージョンだと大阪にあるデータセンターということになります。Multi Region、つまり複数地域にわかれたデータセンターを構築・運用することで地震などの災害でもサービスを継続できるようにします。
Zoneは、Availability Zone(AZ)とも呼ばれ、データセンター内で起こる電源障害や空調、ラック障害などが発生した場合でもサービスが継続できるように設計された区画のことです。Zoneの切り方は、データセンターの施設設備にも依存するため、データセンターごとに様々です。たとえば、電源が2系統あり、それぞれ分電盤(ブレーカー)が分かれているデータセンターでは、Zoneを2つにわけ設計することもあります。
このようにRegion, Zoneは災害などが発生しても永続的にサービスを継続するためのデータセンターの機器配置の枠組みになります。ただし、データセンターの機器だけがRegion, Zoneを考慮して構築されていても意味がありません。データセンターで動くサービス、ソフトウェアがRegion, Zoneを意識してデプロイし、クラスタリングされていないと意味がありません。
この検証では、複数Zoneを意識してデプロイする方法を検証します。
動作検証
Topology Awareなデプロイでは、StorageClass(SC)のallowedTopologies, Podのspec.affinity.nodeAffinity, spec.podAntiAffinityを使いZoneを意識したデプロイを実現します。本検証では、これらの値の動作検証を行います。
検証環境
本検証では、Topology-Aware VolumeをサポートしているGCE PDが利用できるGKE(1.12.5-gke.5)を使用します。
GKE(Multi Zone)の準備
検証環境のGKEにてKubernetesクラスタをMulti Zone構成で構築します。
まず、GKEのコンソールを開き、Kubernetesクラスタを作成します。
作成では、ロケーションタイプ
のリージョン
を選択します。さらに、ノードプール
のdefault-poolの設定で、各Zoneに何個のNodeを構築するかを指定します。
今回の検証で利用するus-central1
のRegionは、3つのZoneで構成されているため、各Zoneに1個づつNodeを配置します。
以下にMulti Zone構成でKubernetesクラスタを構築の設定例を示します。
上記の設定でKubernetesクラスタを構築します。
次に、CLIでアクセスし確認します。GKEを利用するためのCLIのセットアップについては、GKEのクイックスタートを参考にすると良いでしょう。
$ gcloud config set compute/region us-central1
Updated property [compute/region].
$ gcloud container clusters list
NAME LOCATION MASTER_VERSION MASTER_IP MACHINE_TYPE NODE_VERSION NUM_NODES STATUS
standard-cluster-1 us-central1 1.12.5-gke.5 35.232.92.87 n1-standard-1 1.12.5-gke.5 3 RUNNING
$ gcloud container clusters get-credentials standard-cluster-1 --region us-central1
$ kubectl get node
NAME STATUS ROLES AGE VERSION
gke-standard-cluster-1-default-pool-9e01aaeb-h3xz Ready <none> 5h15m v1.12.5-gke.5
gke-standard-cluster-1-default-pool-a2000bd8-tf05 Ready <none> 5h15m v1.12.5-gke.5
gke-standard-cluster-1-default-pool-b4867b14-d6lr Ready <none> 5h15m v1.12.5-gke.5
$ kubectl get node -o=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.labels.failure\-domain\.beta\.kubernetes\.io/region}{"\t"}{.metadata.labels.failure\-domain\.beta\.kubernetes\.io/zone}{"\n"}{end}'
gke-standard-cluster-1-default-pool-9e01aaeb-h3xz us-central1 us-central1-f
gke-standard-cluster-1-default-pool-a2000bd8-tf05 us-central1 us-central1-b
gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1 us-central1-a
その結果、Regionus-central1
に、Zoneus-central1-a
, us-central1-b
, us-central1-f
にそれぞれ1個づつNodeが構築されているのがわかります。
以降、本Kubernetesクラスタを使い検証を行います。
検証1: Topology指定なし
まず、はじめにTopologyを指定しない場合の動作を確認します。
使用するSCのManifestを以下に示します。
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: topology-aware-standard
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-standard
volumeBindingMode: WaitForFirstConsumer
sc.yaml
をデプロイします。
$ kubectl create -f sc.yaml
storageclass.storage.k8s.io/topology-aware-standard created
次に、デプロイするnginxのManifest(nginx.yaml
)を以下に示します。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
spec:
serviceName: nginx
replicas: 5
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: topology-aware-standard
resources:
requests:
storage: 100Mi
nginx.yaml
をデプロイし、Pod,PVC(PersistentVolumeClaim),PV(PersistentVolume)およびPod, PVのZoneを確認します。
$ kubectl create -f nginx.yaml
statefulset.apps/nginx created
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
nginx-0 1/1 Running 0 118m 10.40.2.26 gke-standard-cluster-1-default-pool-b4867b14-d6lr <none>
nginx-1 1/1 Running 0 118m 10.40.2.27 gke-standard-cluster-1-default-pool-b4867b14-d6lr <none>
nginx-2 1/1 Running 0 117m 10.40.2.28 gke-standard-cluster-1-default-pool-b4867b14-d6lr <none>
nginx-3 1/1 Running 0 117m 10.40.0.10 gke-standard-cluster-1-default-pool-a2000bd8-tf05 <none>
nginx-4 1/1 Running 0 116m 10.40.1.11 gke-standard-cluster-1-default-pool-9e01aaeb-h3xz <none>
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-nginx-0 Bound pvc-867683ec-3f17-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 118m
data-nginx-1 Bound pvc-98b67561-3f17-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 118m
data-nginx-2 Bound pvc-a67a911b-3f17-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 117m
data-nginx-3 Bound pvc-af77e940-3f17-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 117m
data-nginx-4 Bound pvc-c0bfa854-3f17-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 117m
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-867683ec-3f17-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-0 topology-aware-standard 118m
pvc-98b67561-3f17-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-1 topology-aware-standard 118m
pvc-a67a911b-3f17-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-2 topology-aware-standard 117m
pvc-af77e940-3f17-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-3 topology-aware-standard 117m
pvc-c0bfa854-3f17-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-4 topology-aware-standard 117m
$ for i in `kubectl get pod -o wide| awk 'NR>1 {print $1 "," $7}'`; do p=`echo $i |awk -F "," '{print $1}'`; n=`echo $i |awk -F "," '{print $2}'`; z=`kubectl get node -o=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.labels.failure\-domain\.beta\.kubernetes\.io/zone}{"\n"}{end}' | grep $n`; echo $p $z; done
nginx-0 gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1-a
nginx-1 gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1-a
nginx-2 gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1-a
nginx-3 gke-standard-cluster-1-default-pool-a2000bd8-tf05 us-central1-b
nginx-4 gke-standard-cluster-1-default-pool-9e01aaeb-h3xz us-central1-f
$ kubectl get pv -o=jsonpath='{range .items[*]}{.spec.claimRef.name}{"\t"}{.metadata.labels.failure\-domain\.beta\.kubernetes\.io/zone}{"\n"}{end}'
data-nginx-0 us-central1-a
data-nginx-1 us-central1-a
data-nginx-2 us-central1-a
data-nginx-3 us-central1-b
data-nginx-4 us-central1-f
Zoneを指定していないため、5つのPod,PVともバラバラのZoneにデプロイされていいます。
クリーンアップに従い、デプロイしたリソースを削除します。
検証2: SCでZoneを制限する
次に、ZoneをSCで制限します。
allowedTopologiesを指定したSCのManifest(sc2.yaml
)を以下に示します。
kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
name: topology-aware-standard
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-standard
volumeBindingMode: WaitForFirstConsumer
allowedTopologies:
- matchLabelExpressions:
- key: failure-domain.beta.kubernetes.io/zone
values:
- us-central1-a
- us-central1-b
このSCでは、allowedTopologiesにてus-central1-a
, us-central1-b
のZoneのみPV作成を許可します。
sc2.yaml
をデプロイします。
$ kubectl create -f sc2.yaml
storageclass.storage.k8s.io/topology-aware-standard created
検証1で利用したnginx.yaml
をデプロイし、Pod,PVC,PVおよびPVのZoneを確認します。
$ kubectl apply -f nginx.yaml
statefulset.apps/nginx created
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
nginx-0 1/1 Running 0 110s 10.40.2.29 gke-standard-cluster-1-default-pool-b4867b14-d6lr <none>
nginx-1 1/1 Running 0 95s 10.40.2.30 gke-standard-cluster-1-default-pool-b4867b14-d6lr <none>
nginx-2 1/1 Running 0 79s 10.40.0.11 gke-standard-cluster-1-default-pool-a2000bd8-tf05 <none>
nginx-3 1/1 Running 0 56s 10.40.2.31 gke-standard-cluster-1-default-pool-b4867b14-d6lr <none>
nginx-4 1/1 Running 0 32s 10.40.2.32 gke-standard-cluster-1-default-pool-b4867b14-d6lr <none>
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-nginx-0 Bound pvc-a775786d-3f28-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 117s
data-nginx-1 Bound pvc-b0d80943-3f28-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 102s
data-nginx-2 Bound pvc-ba118e49-3f28-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 86s
data-nginx-3 Bound pvc-c7f4149d-3f28-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 63s
data-nginx-4 Bound pvc-d5f866f1-3f28-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 39s
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-a775786d-3f28-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-0 topology-aware-standard 116s
pvc-b0d80943-3f28-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-1 topology-aware-standard 100s
pvc-ba118e49-3f28-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-2 topology-aware-standard 84s
pvc-c7f4149d-3f28-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-3 topology-aware-standard 62s
pvc-d5f866f1-3f28-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-4 topology-aware-standard 38s
$ for i in `kubectl get pod -o wide| awk 'NR>1 {print $1 "," $7}'`; do p=`echo $i |awk -F "," '{print $1}'`; n=`echo $i |awk -F "," '{print $2}'`; z=`kubectl get node -o=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.labels.failure\-domain\.beta\.kubernetes\.io/zone}{"\n"}{end}' | grep $n`; echo $p $z; done
nginx-0 gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1-a
nginx-1 gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1-a
nginx-2 gke-standard-cluster-1-default-pool-a2000bd8-tf05 us-central1-b
nginx-3 gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1-a
nginx-4 gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1-a
$ kubectl get pv -o=jsonpath='{range .items[*]}{.spec.claimRef.name}{"\t"}{.metadata.labels.failure\-domain\.beta\.kubernetes\.io/zone}{"\n"}{end}'
data-nginx-0 us-central1-a
data-nginx-1 us-central1-a
data-nginx-2 us-central1-b
data-nginx-3 us-central1-a
data-nginx-4 us-central1-a
許可されたZoneus-central1-a
, us-central1-b
にのみデプロイされ、許可されていないus-central1-f
にはデプロイされていないことが確認できます。
クリーンアップに従い、デプロイしたリソースを削除します。
検証3: PodのnodeAffinityでZoneを指定する (SCのZone制限なし)
検証1のSC(Zone制限なし)を使用します。
次に、Podのデプロイ先としてaffinity.nodeAffinityを指定したManifest(nginx2.yaml
)を作成します。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
spec:
serviceName: nginx
replicas: 5
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: failure-domain.beta.kubernetes.io/zone
operator: In
values:
- us-central1-a
- us-central1-f
containers:
- name: nginx
image: nginx
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: topology-aware-standard
resources:
requests:
storage: 100Mi
.spec.template.spec.affinity.nodeAffinityにて、Zoneus-central1-a
, us-central1-f
のみにデプロイされるように指定します。
nginx2.yaml
をデプロイし、Pod,PVC,PVおよびPVのZoneを確認します。
$ kubectl create -f sc.yaml
storageclass.storage.k8s.io/topology-aware-standard created
$ kubectl create -f nginx2.yaml
statefulset.apps/nginx created
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
nginx-0 1/1 Running 0 2m4s 10.40.2.33 gke-standard-cluster-1-default-pool-b4867b14-d6lr <none>
nginx-1 1/1 Running 0 100s 10.40.2.34 gke-standard-cluster-1-default-pool-b4867b14-d6lr <none>
nginx-2 1/1 Running 0 85s 10.40.2.35 gke-standard-cluster-1-default-pool-b4867b14-d6lr <none>
nginx-3 1/1 Running 0 70s 10.40.1.12 gke-standard-cluster-1-default-pool-9e01aaeb-h3xz <none>
nginx-4 1/1 Running 0 46s 10.40.2.36 gke-standard-cluster-1-default-pool-b4867b14-d6lr <none>
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-nginx-0 Bound pvc-a9e73085-3f29-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 2m11s
data-nginx-1 Bound pvc-b80b3ae0-3f29-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 107s
data-nginx-2 Bound pvc-c108b79d-3f29-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 92s
data-nginx-3 Bound pvc-ca06f562-3f29-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 77s
data-nginx-4 Bound pvc-d7e80407-3f29-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 53s
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-a9e73085-3f29-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-0 topology-aware-standard 2m12s
pvc-b80b3ae0-3f29-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-1 topology-aware-standard 108s
pvc-c108b79d-3f29-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-2 topology-aware-standard 92s
pvc-ca06f562-3f29-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-3 topology-aware-standard 77s
pvc-d7e80407-3f29-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-4 topology-aware-standard 54s
$ for i in `kubectl get pod -o wide| awk 'NR>1 {print $1 "," $7}'`; do p=`echo $i |awk -F "," '{print $1}'`; n=`echo $i |awk -F "," '{print $2}'`; z=`kubectl get node -o=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.labels.failure\-domain\.beta\.kubernetes\.io/zone}{"\n"}{end}' | grep $n`; echo $p $z; done
nginx-0 gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1-a
nginx-1 gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1-a
nginx-2 gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1-a
nginx-3 gke-standard-cluster-1-default-pool-9e01aaeb-h3xz us-central1-f
nginx-4 gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1-a
$ kubectl get pv -o=jsonpath='{range .items[*]}{.spec.claimRef.name}{"\t"}{.metadata.labels.failure\-domain\.beta\.kubernetes\.io/zone}{"\n"}{end}'
data-nginx-0 us-central1-a
data-nginx-1 us-central1-a
data-nginx-2 us-central1-a
data-nginx-3 us-central1-f
data-nginx-4 us-central1-a
結果、nodeAffinityにて指定したus-central1-a
とus-central1-f
のみにPod, PVが配置されているのが分かります。
クリーンアップに従い、デプロイしたリソースを削除します。
検証4: PodのnodeAffinityでZoneを指定する (SCのZone制限あり)
この検証では、検証2で利用したZoneus-central1-a
, us-central1-b
のみPV作成を許可するSCを利用します。
検証3で利用したZoneus-central1-a
, us-central1-f
へ配置するPodをデプロイします。
Pod,PVC,PVおよびPVのZoneを確認します。
$ kubectl create -f sc2.yaml
storageclass.storage.k8s.io/topology-aware-standard created
$ kubectl create -f nginx2.yaml
statefulset.apps/nginx created
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
nginx-0 1/1 Running 0 2m3s 10.40.2.37 gke-standard-cluster-1-default-pool-b4867b14-d6lr <none>
nginx-1 1/1 Running 0 106s 10.40.2.38 gke-standard-cluster-1-default-pool-b4867b14-d6lr <none>
nginx-2 1/1 Running 0 91s 10.40.2.39 gke-standard-cluster-1-default-pool-b4867b14-d6lr <none>
nginx-3 1/1 Running 0 77s 10.40.2.40 gke-standard-cluster-1-default-pool-b4867b14-d6lr <none>
nginx-4 1/1 Running 0 62s 10.40.2.41 gke-standard-cluster-1-default-pool-b4867b14-d6lr <none>
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-nginx-0 Bound pvc-6dd36b77-3f2a-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 2m6s
data-nginx-1 Bound pvc-77f836e6-3f2a-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 109s
data-nginx-2 Bound pvc-80f9d4e3-3f2a-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 94s
data-nginx-3 Bound pvc-896fd3b8-3f2a-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 80s
data-nginx-4 Bound pvc-9266b045-3f2a-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 65s
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-6dd36b77-3f2a-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-0 topology-aware-standard 2m7s
pvc-77f836e6-3f2a-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-1 topology-aware-standard 110s
pvc-80f9d4e3-3f2a-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-2 topology-aware-standard 95s
pvc-896fd3b8-3f2a-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-3 topology-aware-standard 80s
pvc-9266b045-3f2a-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-4 topology-aware-standard 65s
$ for i in `kubectl get pod -o wide| awk 'NR>1 {print $1 "," $7}'`; do p=`echo $i |awk -F "," '{print $1}'`; n=`echo $i |awk -F "," '{print $2}'`; z=`kubectl get node -o=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.labels.failure\-domain\.beta\.kubernetes\.io/zone}{"\n"}{end}' | grep $n`; echo $p $z; done
nginx-0 gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1-a
nginx-1 gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1-a
nginx-2 gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1-a
nginx-3 gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1-a
nginx-4 gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1-a
$ kubectl get pv -o=jsonpath='{range .items[*]}{.spec.claimRef.name}{"\t"}{.metadata.labels.failure\-domain\.beta\.kubernetes\.io/zone}{"\n"}{end}'
data-nginx-0 us-central1-a
data-nginx-1 us-central1-a
data-nginx-2 us-central1-a
data-nginx-3 us-central1-a
data-nginx-4 us-central1-a
その結果、SCとPodの両方から許可されているus-central1-a
のみにデプロイされているのが分かります。
クリーンアップに従い、デプロイしたリソースを削除します。
検証5: PodのnodeAffinityとpodAntiAffinityでZoneのPod数を制限する (SCのZone制限あり)
次に、podAntiAffinityの検証を行います。
検証2で利用したZoneus-central1-a
, us-central1-b
のみPV作成を許可したSCを利用します。
本検証で利用するpodAntiAffinityを指定したnginxのManifest(nginx3.yaml
)を以下に示します。
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: nginx
spec:
serviceName: nginx
replicas: 5
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: failure-domain.beta.kubernetes.io/zone
operator: In
values:
- us-central1-a
- us-central1-b
podAntiAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
- labelSelector:
matchExpressions:
- key: app
operator: In
values:
- nginx
topologyKey: failure-domain.beta.kubernetes.io/zone
containers:
- name: nginx
image: nginx
volumeMounts:
- name: data
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: topology-aware-standard
resources:
requests:
storage: 100Mi
このManifest(nginx3.yaml
)では、.spec.template.spec.affinity.nodeAffinityにて、Zoneus-central1-a
, us-central1-b
のみにデプロイされるように指定します。
さらに、.spec.template.spec.affinity.podAntiAffinityの、topologykeyにてfailure-domain.beta.kubernetes.io/zone
を指定しています。この指定により同じZoneに複数Podが配置されない、つまり各Zoneに1つのPodが配置されるようになります。
nginx3.yaml
をデプロイし、Pod,PVC,PVおよびPVのZoneを確認します。
$ kubectl create -f sc2.yaml
storageclass.storage.k8s.io/topology-aware-standard created
$ kubectl create -f nginx3.yaml
statefulset.apps/nginx created
$ kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE
nginx-0 1/1 Running 0 2m16s 10.40.2.43 gke-standard-cluster-1-default-pool-b4867b14-d6lr <none>
nginx-1 1/1 Running 0 113s 10.40.0.12 gke-standard-cluster-1-default-pool-a2000bd8-tf05 <none>
nginx-2 0/1 Pending 0 97s <none> <none> <none>
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-nginx-0 Bound pvc-595b8703-3f2b-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 2m20s
data-nginx-1 Bound pvc-66fbea3c-3f2b-11e9-88f3-42010a80006d 1Gi RWO topology-aware-standard 117s
data-nginx-2 Pending topology-aware-standard 101s
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-595b8703-3f2b-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-0 topology-aware-standard 2m24s
pvc-66fbea3c-3f2b-11e9-88f3-42010a80006d 1Gi RWO Delete Bound default/data-nginx-1 topology-aware-standard 2m1s
$ for i in `kubectl get pod -o wide| awk 'NR>1 {print $1 "," $7}'`; do p=`echo $i |awk -F "," '{print $1}'`; n=`echo $i |awk -F "," '{print $2}'`; z=`kubectl get node -o=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.metadata.labels.failure\-domain\.beta\.kubernetes\.io/zone}{"\n"}{end}' | grep $n`; echo $p $z; done
nginx-0 gke-standard-cluster-1-default-pool-b4867b14-d6lr us-central1-a
nginx-1 gke-standard-cluster-1-default-pool-a2000bd8-tf05 us-central1-b
nginx-2
$ kubectl get pv -o=jsonpath='{range .items[*]}{.spec.claimRef.name}{"\t"}{.metadata.labels.failure\-domain\.beta\.kubernetes\.io/zone}{"\n"}{end}'
data-nginx-0 us-central1-a
data-nginx-1 us-central1-b
結果、nginx-0
, nginx-1
とそれに接続されたPVC, PVがそれぞれZoneus-central1-a
, us-central1-b
にデプロイされています。その後、nginx-2
をデプロイしようとしているが、Pending
となっています。
kubectl describeコマンドで詳細を確認します。
$ kubectl describe pod nginx-2
...
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedScheduling 4m4s (x2 over 4m4s) default-scheduler 0/3 nodes are available: 1 node(s) didn't match node selector, 2 node(s) didn't match pod affinity/anti-affinity, 2 node(s) didn't satisfy existing pods anti-affinity rules.
Normal NotTriggerScaleUp 1s (x25 over 4m2s) cluster-autoscaler pod didn't trigger scale-up (it wouldn't fit if a new node is added):
その結果、nginx-2
を配置しようとした際、各ZoneにはnginxのPodが既に配置されており、podAntiAffinityでZoneに1個づつ配置と指定したため、Pending
となっています。
つまり、podAntiAffinityが正しく動作していることが分かります。
クリーンアップに従い、デプロイしたリソースを削除します。
クリーンアップ
StatefulSetを削除する。StatefulSetを削除すると、これに関連するPodも削除されます。
$ kubectl delete sts/nginx
次に、PVCを削除します。PVCを削除すると、これに関連するPVも削除されます。
$ kubectl delete pvc --all
persistentvolumeclaim "data-nginx-0" deleted
persistentvolumeclaim "data-nginx-1" deleted
persistentvolumeclaim "data-nginx-2" deleted
persistentvolumeclaim "data-nginx-3" deleted
persistentvolumeclaim "data-nginx-4" deleted
最後に、SCを削除します。
$ kubectl delete sc topology-aware-standard
感想
今回の検証では、Topology Awareなデプロイとして、Zoneを意識したデプロイを検証しました。コンテナ・Kubernetesによりアプリケーションの構築・運用が劇的に変化しました。さらには、KubernetesのセルフヒーリングによりPodに障害が発生しても自動復旧できるようになりました。しかし、Kubernetesを構築するサーバが搭載されているラックや電源が単一Zoneにのみ存在しているとラック障害や電源障害などが発生した場合、全てのKubernetesのNodeがダウンしてしまうため自動復旧できません。これらラック障害や電源障害でも落ちないサービスを作るためには、ZoneやRegionを考慮したKubernetesクラスタの構築と、それを意識したアプリケーションのデプロイが必要不可欠になります。特にデータを保存するストレージに関しては、格納されたデータを作り直すことは困難なため、デプロイするZoneやRegionを意識することが重要です。たとえば、RDBの場合、Masterのデータを複製するSlaveのデプロイ先を異なるZoneやRegionにデプロイするようにManifestの指定で実現します。
KubernetesがTopology Awareなデプロイをサポートしたことで、コンテナ化されたアプリケーションの耐障害性をレベルアップさせた運用ができるようになりました。まだまだ対応しているストレージが少ない状況ですが、今後対応するストレージ、クラウドサービスが増えることを期待します。