Kubernetes のデプロイメントを利用した高可用性構成を試す

はじめに

  • 本記事では、KubernetesK8s)においてポッド数の管理をするデプロイメントの具体的動作を確認していきます。
  • デプロイメントを利用した高可用性構成(HA 構成)を Amazon EKS 上で試行出来るようになることを目標とします。

前提条件

  • 以下のものが動作するものとします。
    • kubectl
    • AWS CLI(IAMユーザー作ってprofile設定済み)
    • aws-iam-authenticator
    • eksctl

Deployment の基本事項

  • Deployment はポッドの稼働数を管理します。
    • 要求を満たすポッド数を起動し、障害などでポッド稼働数が減ってしまった場合も、追加起動する制御を行います。
    • アプリケーションのバージョンアップ時には、新バージョンのポッドへ徐々に置き換えていく制御も行います。
  • Deployment は単独では動作せず、ReplicaSet と連動してポッド数の制御を行います。
    • ReplicaSetDeploymentマニフェストreplicas の値を受け取り、ポッド数を制御します。
    • ユーザーが直接 ReplicaSet を操作することはほとんどありません。

f:id:linkode-okazaki:20200520140441p:plain

Deployment の基本的な動きを Amazon EKS で確認する

  • Amazon EKS 上で Deployment の動きを確認します。あらかじめ、EKS 上にクラスターを作成しておきます。
    • この節では、Fargate 上の EKS を利用します。
$ eksctl create cluster \
  --name [クラスター名] \
  --region [リージョン名] \
  --fargate

Deployment の生成

  • Deployment を生成するためのマニフェストファイルは以下のような内容で作成します:

deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: "sample-deployment" # Deployment 名を指定
spec:
  selector:
    matchLabels:
      app: "sample-app" # Deployment と Pod を結びつけるラベル
  replicas: 3 # Pod の稼働数を指定
  template: # 以下、Pod のテンプレートを記述
    metadata:
      labels:
        app: "sample-app" # Deployment と Pod を結びつけるラベル
    spec:
      containers:
      - name: nginx-container
        image: nginx:1.16
        ports:
        - containerPort: 80

  • Deploymentを生成するには、kubectl apply -f [マニフェストファイル名] を実行します。
  • デプロイの一覧を表示するには、kubectl get deploy を実行します。
  • ReplicaSet の一覧を確認するには、kubectl get rs を実行します。
    • Deployment に紐付いたReplicaSet が生成されていることがわかります。
  • Pod の一覧を確認するには、kubectl get po -o wide を実行します。
    • -o wide を付けることで、より詳細な情報を確認できます。
    • Deployment に紐付いた Pod が生成されていることがわかります。

deployment.yml の適用とその後の確認

$ kubectl apply -f deployment.yml
deployment.apps/sample-deployment created
$ kubectl get deploy
NAME                READY   UP-TO-DATE   AVAILABLE   AGE
sample-deployment   3/3     3            3           13s
$ kubectl get rs
NAME                           DESIRED   CURRENT   READY   AGE
sample-deployment-5f4f4775f7   3         3         3       32s
$ kubectl get po -o wide
NAME                                 READY   STATUS    RESTARTS   AGE   IP                NODE                                                    NOMINATED NODE   READINESS GATES
sample-deployment-7b8bb4454b-28q9x   1/1     Running   0          15m   192.168.169.117   fargate-ip-192-168-169-117.us-east-2.compute.internal   <none>           <none>
sample-deployment-7b8bb4454b-hv7dx   1/1     Running   0          14m   192.168.136.239   fargate-ip-192-168-136-239.us-east-2.compute.internal   <none>           <none>
sample-deployment-7b8bb4454b-ng7m5   1/1     Running   0          13m   192.168.147.85    fargate-ip-192-168-147-85.us-east-2.compute.internal    <none>           <none>

  • Fargate の場合、1 Pod あたり 1 ノードが与えられるため、それぞれの Pod が別々のノードに配置されています。

スケール機能を試す

  • 「スケール機能」は replicas の値を変更し、Pod 数を増減することで処理能力を調整する機能です。
    • CPU 使用率と連動して、動的に replicas の値を調整する機能は「オートスケール」と呼ばれます(ここでは扱いません)。
  • Amazon EKS においては、ワーカーノードの種類によって Pod を増やした際の挙動が変わります。
    • ワーカーノードが EC2 上にある場合、K8s クラスタの計算資源が不足すると、ノードの増設がされるまで、ポッドの増設は保留されます。
    • Fargate の場合は、自動でスケールしてくれるため、ノードの増設を手動で行う必要はありません。
  • 先ほどの Deployment のレプリカ数を 3 から 10 に増やして適用します。

変更後の deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: "sample-deployment"
spec:
  selector:
    matchLabels:
      app: "sample-app"
  replicas: 10 # 3 から 10 に変更
  template:
    metadata:
      labels:
        app: "sample-app"
    spec:
      containers:
      - name: nginx-container
        image: nginx:1.16
        ports:
        - containerPort: 80

変更後の Deploymentマニフェスト適用の様子

$ kubectl apply -f deployment.yml
deployment.apps/sample-deployment configured

# 適用直後の様子
# -> 3 個のポッドが稼働中で、新たに 7 個のポッドが生成されていることがわかります。
$ kubectl get po -o wide
NAME                                 READY   STATUS    RESTARTS   AGE    IP                NODE                                                    NOMINATED NODE                                READINESS GATES
sample-deployment-7b8bb4454b-28q9x   1/1     Running   0          131m   192.168.169.117   fargate-ip-192-168-169-117.us-east-2.compute.internal   <none>                                        <none>
sample-deployment-7b8bb4454b-95xxc   0/1     Pending   0          9s     <none>            <none>                                                  e77ba0824e-a8fa0989ba344938a4830bbb7ea23e54   <none>
sample-deployment-7b8bb4454b-9d9kb   0/1     Pending   0          9s     <none>            <none>                                                  e77ba0824e-27fb9bacb66a4adfba8933574db9890e   <none>
sample-deployment-7b8bb4454b-c54p5   0/1     Pending   0          9s     <none>            <none>                                                  047d10071a-648eed996c094ef29a8221d286d30771   <none>
sample-deployment-7b8bb4454b-hv7dx   1/1     Running   0          130m   192.168.136.239   fargate-ip-192-168-136-239.us-east-2.compute.internal   <none>                                        <none>
sample-deployment-7b8bb4454b-ng7m5   1/1     Running   0          130m   192.168.147.85    fargate-ip-192-168-147-85.us-east-2.compute.internal    <none>                                        <none>
sample-deployment-7b8bb4454b-qddmr   0/1     Pending   0          9s     <none>            <none>                                                  09cd2b3a26-bae652eb3ad14375aabc9b4c8dc9af01   <none>
sample-deployment-7b8bb4454b-scdvk   0/1     Pending   0          9s     <none>            <none>                                                  047d10071a-e36b77e7166148b99d86358001742bfa   <none>
sample-deployment-7b8bb4454b-swzkt   0/1     Pending   0          9s     <none>            <none>                                                  09cd2b3a26-a2230349a5d843eab3d4100f29550a85   <none>
sample-deployment-7b8bb4454b-wjjb2   0/1     Pending   0          9s     <none>            <none>                                                  e77ba0824e-499afa9c8f7d40d087e73d5e99896d32   <none>

# 適用後 30 秒ほど経ってからの様子
$ kubectl get po -o wide
NAME                                 READY   STATUS              RESTARTS   AGE    IP                NODE                                                    NOMINATED NODE                                READINESS GATES
sample-deployment-7b8bb4454b-28q9x   1/1     Running             0          132m   192.168.169.117   fargate-ip-192-168-169-117.us-east-2.compute.internal   <none>                                        <none>
sample-deployment-7b8bb4454b-95xxc   0/1     Pending             0          35s    <none>            <none>                                                  e77ba0824e-a8fa0989ba344938a4830bbb7ea23e54   <none>
sample-deployment-7b8bb4454b-9d9kb   0/1     Pending             0          35s    <none>            <none>                                                  e77ba0824e-27fb9bacb66a4adfba8933574db9890e   <none>
sample-deployment-7b8bb4454b-c54p5   0/1     ContainerCreating   0          35s    <none>            fargate-ip-192-168-132-16.us-east-2.compute.internal    <none>                                        <none>
sample-deployment-7b8bb4454b-hv7dx   1/1     Running             0          131m   192.168.136.239   fargate-ip-192-168-136-239.us-east-2.compute.internal   <none>                                        <none>
sample-deployment-7b8bb4454b-ng7m5   1/1     Running             0          130m   192.168.147.85    fargate-ip-192-168-147-85.us-east-2.compute.internal    <none>                                        <none>
sample-deployment-7b8bb4454b-qddmr   0/1     Pending             0          35s    <none>            <none>                                                  09cd2b3a26-bae652eb3ad14375aabc9b4c8dc9af01   <none>
sample-deployment-7b8bb4454b-scdvk   0/1     Pending             0          35s    <none>            <none>                                                  047d10071a-e36b77e7166148b99d86358001742bfa   <none>
sample-deployment-7b8bb4454b-swzkt   0/1     Pending             0          35s    <none>            <none>                                                  09cd2b3a26-a2230349a5d843eab3d4100f29550a85   <none>
sample-deployment-7b8bb4454b-wjjb2   0/1     Pending             0          35s    <none>            <none>                                                  e77ba0824e-499afa9c8f7d40d087e73d5e99896d32   <none>

# 適用後、約 60 秒後の様子
$ kubectl get po -o wide
NAME                                 READY   STATUS              RESTARTS   AGE    IP                NODE                                                    NOMINATED NODE   READINESS GATES
sample-deployment-7b8bb4454b-28q9x   1/1     Running             0          132m   192.168.169.117   fargate-ip-192-168-169-117.us-east-2.compute.internal   <none>           <none>
sample-deployment-7b8bb4454b-95xxc   0/1     ContainerCreating   0          58s    <none>            fargate-ip-192-168-117-130.us-east-2.compute.internal   <none>           <none>
sample-deployment-7b8bb4454b-9d9kb   1/1     Running             0          58s    192.168.126.212   fargate-ip-192-168-126-212.us-east-2.compute.internal   <none>           <none>
sample-deployment-7b8bb4454b-c54p5   1/1     Running             0          58s    192.168.132.16    fargate-ip-192-168-132-16.us-east-2.compute.internal    <none>           <none>
sample-deployment-7b8bb4454b-hv7dx   1/1     Running             0          131m   192.168.136.239   fargate-ip-192-168-136-239.us-east-2.compute.internal   <none>           <none>
sample-deployment-7b8bb4454b-ng7m5   1/1     Running             0          130m   192.168.147.85    fargate-ip-192-168-147-85.us-east-2.compute.internal    <none>           <none>
sample-deployment-7b8bb4454b-qddmr   0/1     ContainerCreating   0          58s    <none>            fargate-ip-192-168-173-77.us-east-2.compute.internal    <none>           <none>
sample-deployment-7b8bb4454b-scdvk   1/1     Running             0          58s    192.168.139.82    fargate-ip-192-168-139-82.us-east-2.compute.internal    <none>           <none>
sample-deployment-7b8bb4454b-swzkt   1/1     Running             0          58s    192.168.180.58    fargate-ip-192-168-180-58.us-east-2.compute.internal    <none>           <none>
sample-deployment-7b8bb4454b-wjjb2   1/1     Running             0          58s    192.168.111.204   fargate-ip-192-168-111-204.us-east-2.compute.internal   <none>           <none>

# 適用完了
$ kubectl get po -o wide
NAME                                 READY   STATUS    RESTARTS   AGE    IP                NODE                                                    NOMINATED NODE   READINESS GATES
sample-deployment-7b8bb4454b-28q9x   1/1     Running   0          132m   192.168.169.117   fargate-ip-192-168-169-117.us-east-2.compute.internal   <none>           <none>
sample-deployment-7b8bb4454b-95xxc   1/1     Running   0          68s    192.168.117.130   fargate-ip-192-168-117-130.us-east-2.compute.internal   <none>           <none>
sample-deployment-7b8bb4454b-9d9kb   1/1     Running   0          68s    192.168.126.212   fargate-ip-192-168-126-212.us-east-2.compute.internal   <none>           <none>
sample-deployment-7b8bb4454b-c54p5   1/1     Running   0          68s    192.168.132.16    fargate-ip-192-168-132-16.us-east-2.compute.internal    <none>           <none>
sample-deployment-7b8bb4454b-hv7dx   1/1     Running   0          131m   192.168.136.239   fargate-ip-192-168-136-239.us-east-2.compute.internal   <none>           <none>
sample-deployment-7b8bb4454b-ng7m5   1/1     Running   0          131m   192.168.147.85    fargate-ip-192-168-147-85.us-east-2.compute.internal    <none>           <none>
sample-deployment-7b8bb4454b-qddmr   1/1     Running   0          68s    192.168.173.77    fargate-ip-192-168-173-77.us-east-2.compute.internal    <none>           <none>
sample-deployment-7b8bb4454b-scdvk   1/1     Running   0          68s    192.168.139.82    fargate-ip-192-168-139-82.us-east-2.compute.internal    <none>           <none>
sample-deployment-7b8bb4454b-swzkt   1/1     Running   0          68s    192.168.180.58    fargate-ip-192-168-180-58.us-east-2.compute.internal    <none>           <none>
sample-deployment-7b8bb4454b-wjjb2   1/1     Running   0          68s    192.168.111.204   fargate-ip-192-168-111-204.us-east-2.compute.internal   <none>           <none>

ロールアウト機能を試す

  • K8s におけるロールアウトとは、アプリケーションコンテナの更新を意味します。
  • ロールアウト進行中は、リクエストの処理負荷に対処できる Pod 数を残して停止できる Pod 数(停止許容数)だけ古い Pod を停止します。
  • 停止した Pod の数に超過許容数を加えた数だけ、かつ、新しい Pod 数は replicas の値を上限として Pod を起動します。
  • 超過許容数は DeploymentRollingUpdateStrategy から計算可能です。
  • 以下、先ほどの Deployment の例で見てみます。
    • Deployment の詳細は kubectl describe deploy [Deployment 名] で確認できます。
    • 下記の例では、RollingUpdateStrategy25% max unavailable, 25% max surge なので、「最大25%の Pod の停止を許容し、最大25%のポッドの稼働超過数を許容する」という意味です。
    • replicas が 10 なので、最小 Pod 数は 10 * (1 - 0.25) = 7.5 の小数点以下を切り上げた 8 となります。また、最大超過数は 10 * ( 1 + 0.25) = 12.5 の小数点以下を切り上げた 13 となります。

Deployment の詳細確認結果

$ kubectl describe deploy sample-deployment
Name:                   sample-deployment
Namespace:              default
CreationTimestamp:      Tue, 05 May 2020 14:24:18 +0900
Labels:                 <none>
Annotations:            deployment.kubernetes.io/revision: 2
Selector:               app=sample-app
Replicas:               10 desired | 10 updated | 10 total | 10 available | 0 unavailable
StrategyType:           RollingUpdate
MinReadySeconds:        0
RollingUpdateStrategy:  25% max unavailable, 25% max surge # 停止数と稼働超過数の割合
Pod Template:
  Labels:  app=sample-app
  Containers:
   nginx-container:
    Image:        nginx:1.16
    Port:         80/TCP
    Host Port:    0/TCP
    Environment:  <none>
    Mounts:       <none>
  Volumes:        <none>
Conditions:
  Type           Status  Reason
  ----           ------  ------
  Progressing    True    NewReplicaSetAvailable
  Available      True    MinimumReplicasAvailable
OldReplicaSets:  <none>
NewReplicaSet:   sample-deployment-7b8bb4454b (10/10 replicas created)
Events:
  Type    Reason             Age   From                   Message
  ----    ------             ----  ----                   -------
  Normal  ScalingReplicaSet  17m   deployment-controller  Scaled up replica set sample-deployment-7b8bb4454b to 10

  • 実際にコンテナアプリケーションの更新を行い、ロールアウトの様子を確認します。
  • Deploymentマニフェストを更新し、containers.imagenginx:1.16 -> nginx:1.17 に変更し、マニフェストを適用します。

変更後の deployment.yml(その2)

apiVersion: apps/v1
kind: Deployment
metadata:
  name: "sample-deployment"
spec:
  selector:
    matchLabels:
      app: "sample-app"
  replicas: 10
  template:
    metadata:
      labels:
        app: "sample-app"
    spec:
      containers:
      - name: nginx-container
        image: nginx:1.17 # バージョンを 1.16 から 1.17 に変更
        ports:
        - containerPort: 80

変更したマニフェストの適用とロールアウト中の様子

$ kubectl apply -f deployment.yml
deployment.apps/sample-deployment configured

# (1) 適用後 20 秒ほど経った後
# -> 更新前のノード(Runnning) は 8 個:最小 Pod 数、更新中のノードは 5 個(Pending)
#    Runnning と Pending は合わせて 13個:最大超過数
$ kubectl get pod -o wide
NAME                                 READY   STATUS    RESTARTS   AGE    IP                NODE                                                    NOMINATED NODE                                READINESS GATES
sample-deployment-7875dd85cc-5xt2h   0/1     Pending   0          24s    <none>            <none>                                                  e77ba0824e-dd4ad95019c04aafb18024de70a39499   <none>
sample-deployment-7875dd85cc-6fxgd   0/1     Pending   0          24s    <none>            <none>                                                  047d10071a-0b5c93d23a6642e58d1abdbd7130801f   <none>
sample-deployment-7875dd85cc-7jzjf   0/1     Pending   0          24s    <none>            <none>                                                  047d10071a-a428a39b62f54d7391c55adedc6a9f52   <none>
sample-deployment-7875dd85cc-g9ztc   0/1     Pending   0          24s    <none>            <none>                                                  e77ba0824e-816c4260aecc4852943ca7c79e15f4ef   <none>
sample-deployment-7875dd85cc-p78fl   0/1     Pending   0          24s    <none>            <none>                                                  e77ba0824e-937779b60c7e4e0c9322cbbe1a4a85cf   <none>
sample-deployment-7b8bb4454b-28q9x   1/1     Running   0          173m   192.168.169.117   fargate-ip-192-168-169-117.us-east-2.compute.internal   <none>                                        <none>
sample-deployment-7b8bb4454b-9d9kb   1/1     Running   0          42m    192.168.126.212   fargate-ip-192-168-126-212.us-east-2.compute.internal   <none>                                        <none>
sample-deployment-7b8bb4454b-c54p5   1/1     Running   0          42m    192.168.132.16    fargate-ip-192-168-132-16.us-east-2.compute.internal    <none>                                        <none>
sample-deployment-7b8bb4454b-hv7dx   1/1     Running   0          172m   192.168.136.239   fargate-ip-192-168-136-239.us-east-2.compute.internal   <none>                                        <none>
sample-deployment-7b8bb4454b-ng7m5   1/1     Running   0          172m   192.168.147.85    fargate-ip-192-168-147-85.us-east-2.compute.internal    <none>                                        <none>
sample-deployment-7b8bb4454b-scdvk   1/1     Running   0          42m    192.168.139.82    fargate-ip-192-168-139-82.us-east-2.compute.internal    <none>                                        <none>
sample-deployment-7b8bb4454b-swzkt   1/1     Running   0          42m    192.168.180.58    fargate-ip-192-168-180-58.us-east-2.compute.internal    <none>                                        <none>
sample-deployment-7b8bb4454b-wjjb2   1/1     Running   0          42m    192.168.111.204   fargate-ip-192-168-111-204.us-east-2.compute.internal   <none>                                        <none>

# (2) 適用後 約 50 秒経過
# -> 古いコンテナが Terminating になり、新しいコンテナが Runnning 状態になる。 
#    Runnning は依然として8個
$ kubectl get pod -o wide
NAME                                 READY   STATUS              RESTARTS   AGE    IP                NODE                                                    NOMINATED NODE                                READINESS GATES
sample-deployment-7875dd85cc-5xt2h   0/1     ContainerCreating   0          52s    <none>            fargate-ip-192-168-109-32.us-east-2.compute.internal    <none>                                        <none>
sample-deployment-7875dd85cc-6fxgd   1/1     Running             0          52s    192.168.134.6     fargate-ip-192-168-134-6.us-east-2.compute.internal     <none>                                        <none>
sample-deployment-7875dd85cc-7jzjf   1/1     Running             0          52s    192.168.134.119   fargate-ip-192-168-134-119.us-east-2.compute.internal   <none>                                        <none>
sample-deployment-7875dd85cc-8m4g2   0/1     Pending             0          19s    <none>            <none>                                                  09cd2b3a26-5eabed2f62b94a20bf35e3093cddaea1   <none>
sample-deployment-7875dd85cc-9vzlc   0/1     Pending             0          10s    <none>            <none>                                                  047d10071a-e04c139321dd4e7ba8fd491d97ade6f3   <none>
sample-deployment-7875dd85cc-g9ztc   1/1     Running             0          52s    192.168.101.63    fargate-ip-192-168-101-63.us-east-2.compute.internal    <none>                                        <none>
sample-deployment-7875dd85cc-p78fl   0/1     ContainerCreating   0          52s    <none>            fargate-ip-192-168-124-111.us-east-2.compute.internal   <none>                                        <none>
sample-deployment-7875dd85cc-vq86b   0/1     Pending             0          1s     <none>            <none>                                                  09cd2b3a26-511ca593cbf048b7a66b5672290be8bd   <none>
sample-deployment-7b8bb4454b-28q9x   1/1     Running             0          174m   192.168.169.117   fargate-ip-192-168-169-117.us-east-2.compute.internal   <none>                                        <none>
sample-deployment-7b8bb4454b-c54p5   1/1     Running             0          42m    192.168.132.16    fargate-ip-192-168-132-16.us-east-2.compute.internal    <none>                                        <none>
sample-deployment-7b8bb4454b-hv7dx   1/1     Running             0          173m   192.168.136.239   fargate-ip-192-168-136-239.us-east-2.compute.internal   <none>                                        <none>
sample-deployment-7b8bb4454b-ng7m5   1/1     Running             0          172m   192.168.147.85    fargate-ip-192-168-147-85.us-east-2.compute.internal    <none>                                        <none>
sample-deployment-7b8bb4454b-scdvk   1/1     Terminating         0          42m    192.168.139.82    fargate-ip-192-168-139-82.us-east-2.compute.internal    <none>                                        <none>
sample-deployment-7b8bb4454b-swzkt   1/1     Running             0          42m    192.168.180.58    fargate-ip-192-168-180-58.us-east-2.compute.internal    <none>                                        <none>

# (3) 適用後 約 75 秒経過
$ kubectl get pod -o wide
NAME                                 READY   STATUS              RESTARTS   AGE    IP                NODE                                                    NOMINATED NODE                                READINESS GATES
sample-deployment-7875dd85cc-5xt2h   1/1     Running             0          74s    192.168.109.32    fargate-ip-192-168-109-32.us-east-2.compute.internal    <none>                                        <none>
sample-deployment-7875dd85cc-6fxgd   1/1     Running             0          74s    192.168.134.6     fargate-ip-192-168-134-6.us-east-2.compute.internal     <none>                                        <none>
sample-deployment-7875dd85cc-7jzjf   1/1     Running             0          74s    192.168.134.119   fargate-ip-192-168-134-119.us-east-2.compute.internal   <none>                                        <none>
sample-deployment-7875dd85cc-8m4g2   0/1     ContainerCreating   0          41s    <none>            fargate-ip-192-168-175-37.us-east-2.compute.internal    <none>                                        <none>
sample-deployment-7875dd85cc-9vzlc   0/1     ContainerCreating   0          32s    <none>            fargate-ip-192-168-149-147.us-east-2.compute.internal   <none>                                        <none>
sample-deployment-7875dd85cc-fcxh4   0/1     Pending             0          20s    <none>            <none>                                                  e77ba0824e-6d6ab522f4314e26acda5bb1e7693d98   <none>
sample-deployment-7875dd85cc-g9ztc   1/1     Running             0          74s    192.168.101.63    fargate-ip-192-168-101-63.us-east-2.compute.internal    <none>                                        <none>
sample-deployment-7875dd85cc-mxvrs   0/1     Pending             0          20s    <none>            <none>                                                  e77ba0824e-6932284805e1460bb2204c4f9030eaa4   <none>
sample-deployment-7875dd85cc-p78fl   1/1     Running             0          74s    192.168.124.111   fargate-ip-192-168-124-111.us-east-2.compute.internal   <none>                                        <none>
sample-deployment-7875dd85cc-vq86b   0/1     Pending             0          23s    <none>            <none>                                                  09cd2b3a26-511ca593cbf048b7a66b5672290be8bd   <none>
sample-deployment-7b8bb4454b-28q9x   1/1     Running             0          174m   192.168.169.117   fargate-ip-192-168-169-117.us-east-2.compute.internal   <none>                                        <none>
sample-deployment-7b8bb4454b-hv7dx   1/1     Running             0          173m   192.168.136.239   fargate-ip-192-168-136-239.us-east-2.compute.internal   <none>                                        <none>
sample-deployment-7b8bb4454b-ng7m5   1/1     Running             0          173m   192.168.147.85    fargate-ip-192-168-147-85.us-east-2.compute.internal    <none>                                        <none>

# (4) 適用後 約 100 秒経過
$ kubectl get pod -o wide
NAME                                 READY   STATUS              RESTARTS   AGE    IP                NODE                                                    NOMINATED NODE   READINESS GATES
sample-deployment-7875dd85cc-5xt2h   1/1     Running             0          97s    192.168.109.32    fargate-ip-192-168-109-32.us-east-2.compute.internal    <none>           <none>
sample-deployment-7875dd85cc-6fxgd   1/1     Running             0          97s    192.168.134.6     fargate-ip-192-168-134-6.us-east-2.compute.internal     <none>           <none>
sample-deployment-7875dd85cc-7jzjf   1/1     Running             0          97s    192.168.134.119   fargate-ip-192-168-134-119.us-east-2.compute.internal   <none>           <none>
sample-deployment-7875dd85cc-8m4g2   1/1     Running             0          64s    192.168.175.37    fargate-ip-192-168-175-37.us-east-2.compute.internal    <none>           <none>
sample-deployment-7875dd85cc-9vzlc   1/1     Running             0          55s    192.168.149.147   fargate-ip-192-168-149-147.us-east-2.compute.internal   <none>           <none>
sample-deployment-7875dd85cc-fcxh4   0/1     ContainerCreating   0          43s    <none>            fargate-ip-192-168-124-138.us-east-2.compute.internal   <none>           <none>
sample-deployment-7875dd85cc-g9ztc   1/1     Running             0          97s    192.168.101.63    fargate-ip-192-168-101-63.us-east-2.compute.internal    <none>           <none>
sample-deployment-7875dd85cc-mxvrs   0/1     ContainerCreating   0          43s    <none>            fargate-ip-192-168-126-24.us-east-2.compute.internal    <none>           <none>
sample-deployment-7875dd85cc-p78fl   1/1     Running             0          97s    192.168.124.111   fargate-ip-192-168-124-111.us-east-2.compute.internal   <none>           <none>
sample-deployment-7875dd85cc-vq86b   0/1     ContainerCreating   0          46s    <none>            fargate-ip-192-168-176-14.us-east-2.compute.internal    <none>           <none>
sample-deployment-7b8bb4454b-28q9x   1/1     Running             0          175m   192.168.169.117   fargate-ip-192-168-169-117.us-east-2.compute.internal   <none>           <none>

# (5) 更新完了
$ kubectl get pod -o wide
NAME                                 READY   STATUS    RESTARTS   AGE    IP                NODE                                                    NOMINATED NODE   READINESS GATES
sample-deployment-7875dd85cc-5xt2h   1/1     Running   0          117s   192.168.109.32    fargate-ip-192-168-109-32.us-east-2.compute.internal    <none>           <none>
sample-deployment-7875dd85cc-6fxgd   1/1     Running   0          117s   192.168.134.6     fargate-ip-192-168-134-6.us-east-2.compute.internal     <none>           <none>
sample-deployment-7875dd85cc-7jzjf   1/1     Running   0          117s   192.168.134.119   fargate-ip-192-168-134-119.us-east-2.compute.internal   <none>           <none>
sample-deployment-7875dd85cc-8m4g2   1/1     Running   0          84s    192.168.175.37    fargate-ip-192-168-175-37.us-east-2.compute.internal    <none>           <none>
sample-deployment-7875dd85cc-9vzlc   1/1     Running   0          75s    192.168.149.147   fargate-ip-192-168-149-147.us-east-2.compute.internal   <none>           <none>
sample-deployment-7875dd85cc-fcxh4   1/1     Running   0          63s    192.168.124.138   fargate-ip-192-168-124-138.us-east-2.compute.internal   <none>           <none>
sample-deployment-7875dd85cc-g9ztc   1/1     Running   0          117s   192.168.101.63    fargate-ip-192-168-101-63.us-east-2.compute.internal    <none>           <none>
sample-deployment-7875dd85cc-mxvrs   1/1     Running   0          63s    192.168.126.24    fargate-ip-192-168-126-24.us-east-2.compute.internal    <none>           <none>
sample-deployment-7875dd85cc-p78fl   1/1     Running   0          117s   192.168.124.111   fargate-ip-192-168-124-111.us-east-2.compute.internal   <none>           <none>
sample-deployment-7875dd85cc-vq86b   1/1     Running   0          66s    192.168.176.14    fargate-ip-192-168-176-14.us-east-2.compute.internal    <none>           <none>

ロールバック機能を試す

  • ロールアウト前の古いコンテナへ戻すためにポッドを入れ替えることをロールバックと言います。
    • アプリケーションのリリース直後に、簡単にリリース前に戻すことが可能です。
    • ただし、不具合の発生中に更新されたデータが一緒にロールバックされることはありません。不具合によるデータ破損には別途データのリカバリー処理を考える必要があります。
  • 以下、ロールバックのためのコマンド kubectl rollout undo deployment [Deployment 名] を実行してから、ロールバックが完了するまでの様子を示します。
    • ロールアウト時と同様に、稼働している Pod の数はよしなに調整されながら進行しています。

ロールバックコマンドの実行とロールバック中の様子

$ kubectl rollout undo deployment sample-deployment
deployment.extensions/sample-deployment rolled back
$ kubectl get po

# ロールバック開始直後
NAME                                 READY   STATUS    RESTARTS   AGE
sample-deployment-5f47777b55-6c77m   0/1     Pending   0          21s
sample-deployment-5f47777b55-6dh55   1/1     Running   0          69s
sample-deployment-5f47777b55-6zf86   0/1     Pending   0          14s
sample-deployment-5f47777b55-b6289   1/1     Running   0          69s
sample-deployment-5f47777b55-c9vfx   0/1     Pending   0          26s
sample-deployment-5f47777b55-dgjnw   1/1     Running   0          69s
sample-deployment-5f47777b55-f9w6r   1/1     Running   0          69s
sample-deployment-5f47777b55-gtrb9   0/1     Pending   0          19s
sample-deployment-5f47777b55-tmqcq   1/1     Running   0          69s
sample-deployment-5f47777b55-xnplh   0/1     Pending   0          8s
sample-deployment-86bd954768-2dqkt   1/1     Running   0          4m40s
sample-deployment-86bd954768-gphkw   1/1     Running   0          4m40s
sample-deployment-86bd954768-wbcds   1/1     Running   0          4m40s

# ロールバック過渡期
NAME                                 READY   STATUS              RESTARTS   AGE
sample-deployment-5f47777b55-6dh55   1/1     Running             0          23m
sample-deployment-5f47777b55-6zf86   0/1     Terminating         0          22m
sample-deployment-5f47777b55-b6289   1/1     Running             0          23m
sample-deployment-5f47777b55-c9vfx   0/1     Terminating         0          23m
sample-deployment-5f47777b55-dgjnw   1/1     Running             0          23m
sample-deployment-5f47777b55-f9w6r   1/1     Running             0          23m
sample-deployment-5f47777b55-tmqcq   1/1     Running             0          23m
sample-deployment-86bd954768-8d8pj   1/1     Running             0          48s
sample-deployment-86bd954768-c5hfc   0/1     Pending             0          3s
sample-deployment-86bd954768-c69rs   0/1     Pending             0          5s
sample-deployment-86bd954768-drjrh   0/1     Pending             0          4s
sample-deployment-86bd954768-dzlsk   0/1     ContainerCreating   0          48s
sample-deployment-86bd954768-jxcvc   1/1     Running             0          48s
sample-deployment-86bd954768-nm5v8   0/1     ContainerCreating   0          48s
sample-deployment-86bd954768-q89zq   1/1     Running             0          48s

# ロールバック完了
NAME                                 READY   STATUS    RESTARTS   AGE
sample-deployment-86bd954768-8d8pj   1/1     Running   0          116s
sample-deployment-86bd954768-c5hfc   1/1     Running   0          71s
sample-deployment-86bd954768-c69rs   1/1     Running   0          73s
sample-deployment-86bd954768-drjrh   1/1     Running   0          72s
sample-deployment-86bd954768-dzlsk   1/1     Running   0          116s
sample-deployment-86bd954768-jxcvc   1/1     Running   0          116s
sample-deployment-86bd954768-kklz6   1/1     Running   0          64s
sample-deployment-86bd954768-nm5v8   1/1     Running   0          116s
sample-deployment-86bd954768-q89zq   1/1     Running   0          116s
sample-deployment-86bd954768-wnpxc   1/1     Running   0          61s

自己回復機能を試す

  • 次に、Deployment自己回復機能を確認します。
    • 単独で起動した PodDeployment から起動したポッドの振る舞いを比較し、自己回復機能を確認します。
    • ポッドは、コンテナレベルの障害に対応する自己回復機能を有していますが、DeploymentPod の障害に対応する自己回復機能を有します。
  • 以下、ノード停止を想定した自己回復機能を確認するため、EKS 上の K8s クラスターを Fargate ではなく EC2 に作成しておきます。
$ eksctl create cluster \
--name [クラスター名] \
--region [リージョン名] \
--nodegroup-name standard-workers \
--node-type t2.micro \
--nodes 3 \
--nodes-min 1 \
--nodes-max 6 \
--ssh-access \
--ssh-public-key my-public-key.pub

マニフェストファイルの作成と適用

  • Pod を単独で生成するためのマニフェストファイルを以下のように作成します。
    • この Pod 自体の振る舞いは、「1 時間経過後に終了するシェルを実行する」ことです。

pod.yml

apiVersion: v1
kind: Pod
metadata:
  name: "sample-pod"
spec:
    containers:
        - name: busybox
          image: busybox:1
          command: ["sh", "-c", "sleep 3600; exit 0"]
    restartPolicy: Always

  • Deployment から Pod を起動するマニフェストは以下のように作成します。

deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: "sample-deployment"
spec:
  selector:
    matchLabels:
      app: "sample-app"
  replicas: 3
  template:
    metadata:
      labels:
        app: "sample-app"
    spec:
      containers:
      - name: nginx-container
        image: nginx:1.17
        ports:
        - containerPort: 80

  • これらをkubectl apply で適用して、生成されたことを確認します。
    • 今回は、単体の PodDeployment によって作られた Pod が同じノード ip-192-168-60-208.us-east-2.compute.internal に作られたことに注意しておきます。
$ kubectl get pod -o wide
NAME                                 READY   STATUS    RESTARTS   AGE    IP               NODE                                           NOMINATED NODE   READINESS GATES
sample-deployment-5f47777b55-6ln9j   1/1     Running   0          8m6s   192.168.53.40    ip-192-168-60-208.us-east-2.compute.internal   <none>           <none>
sample-deployment-5f47777b55-q6z46   1/1     Running   0          8m6s   192.168.76.35    ip-192-168-93-142.us-east-2.compute.internal   <none>           <none>
sample-deployment-5f47777b55-z2ptv   1/1     Running   0          8m6s   192.168.68.255   ip-192-168-93-142.us-east-2.compute.internal   <none>           <none>
sample-pod                           1/1     Running   0          6m7s   192.168.39.68    ip-192-168-60-208.us-east-2.compute.internal   <none>           <none>

ノードを停止させる

  • 稼働中のノードを停止は、Amazon EC2 の管理画面から行います。
  • 管理画面を開き、左メニューの「インスタンス」を選択します。
  • 下図のように、インスタンス一覧が表示されるます。停止対象のインスタンスを探します。
    • 今回の場合、「プライベート DNS」が ip-192-168-60-208.us-east-2.compute.internal であるものが対象です。

f:id:linkode-okazaki:20200520140427p:plain

f:id:linkode-okazaki:20200520140432p:plain

  • しばらくすると、下記のようにノードが停止します。

f:id:linkode-okazaki:20200520140456p:plain

  • その後、ノード数 3 を維持するために、新たなノードが起動します。

f:id:linkode-okazaki:20200520140501p:plain

ノード停止後からのポッドの様子を確認する

  • ノード停止後、kubectl get pod,deploy -o wide のコマンドで PodDeployment の様子を確認します。

ノード停止後の復旧の様子を確認

# ノード停止直前
$ kubectl get pod,deploy -o wide
NAME                                     READY   STATUS    RESTARTS   AGE   IP               NODE                                           NOMINATED NODE   READINESS GATES
pod/sample-deployment-5f47777b55-6ln9j   1/1     Running   0          28m   192.168.53.40    ip-192-168-60-208.us-east-2.compute.internal   <none>           <none>
pod/sample-deployment-5f47777b55-q6z46   1/1     Running   0          28m   192.168.76.35    ip-192-168-93-142.us-east-2.compute.internal   <none>           <none>
pod/sample-deployment-5f47777b55-z2ptv   1/1     Running   0          28m   192.168.68.255   ip-192-168-93-142.us-east-2.compute.internal   <none>           <none>
pod/sample-pod                           1/1     Running   0          26m   192.168.39.68    ip-192-168-60-208.us-east-2.compute.internal   <none>           <none>

NAME                                      READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS        IMAGES       SELECTOR
deployment.extensions/sample-deployment   2/3     3            2           28m   nginx-container   nginx:1.17   app=sample-app

# ノード停止後、100 秒ほど経過した後
# -> Deployment の Pod は 2 個だけが稼働状態となっている。単独で起動した Pod は消滅したまま。
$ kubectl get pod,deploy -o wide
NAME                                     READY   STATUS    RESTARTS   AGE    IP               NODE                                           NOMINATED NODE   READINESS GATES
pod/sample-deployment-5f47777b55-j69vl   0/1     Pending   0          109s   <none>           <none>                                         <none>           <none>
pod/sample-deployment-5f47777b55-q6z46   1/1     Running   0          31m    192.168.76.35    ip-192-168-93-142.us-east-2.compute.internal   <none>           <none>
pod/sample-deployment-5f47777b55-z2ptv   1/1     Running   0          31m    192.168.68.255   ip-192-168-93-142.us-east-2.compute.internal   <none>           <none>

NAME                                      READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS        IMAGES       SELECTOR
deployment.extensions/sample-deployment   2/3     3            2           31m   nginx-container   nginx:1.17   app=sample-app

# ノード停止後、3 分ほど経過した後
# -> Deployment の Pod は再び 3 個が稼働状態となっている。新たなPod は、別のノードで稼働し始める。
#    単独で起動した Pod は復旧せず消滅したまま。
$ kubectl get pod,deploy -o wide
NAME                                     READY   STATUS    RESTARTS   AGE     IP               NODE                                           NOMINATED NODE   READINESS GATES
pod/sample-deployment-5f47777b55-j69vl   1/1     Running   0          3m44s   192.168.44.239   ip-192-168-56-255.us-east-2.compute.internal   <none>           <none>
pod/sample-deployment-5f47777b55-q6z46   1/1     Running   0          32m     192.168.76.35    ip-192-168-93-142.us-east-2.compute.internal   <none>           <none>
pod/sample-deployment-5f47777b55-z2ptv   1/1     Running   0          32m     192.168.68.255   ip-192-168-93-142.us-east-2.compute.internal   <none>           <none>

NAME                                      READY   UP-TO-DATE   AVAILABLE   AGE   CONTAINERS        IMAGES       SELECTOR
deployment.extensions/sample-deployment   3/3     3            3           33m   nginx-container   nginx:1.17   app=sample-app

  • ここで起こったことを順に図にすると以下のような流れになります。まず、Deployment による Pod 3 個と単独で起動したポッド 1 個が稼働している状態です。

f:id:linkode-okazaki:20200520140445p:plain

  • 次に、管理画面からノードを停止させました。この際、ReplicaSet は管理している Pod 1 個が喪失されたことを検知します。

f:id:linkode-okazaki:20200520140452p:plain

  • ReplicaSet の動作により、別のノードに Pod が 1 個生成され、稼働します。
  • ReplicaSet の管理下になかった Pod は別のノードで稼働することはなく、消滅してしまいます。

f:id:linkode-okazaki:20200520142053p:plain

Deployment を利用した高可用性構成

  • 1 つのポッドと永続ボリュームを用いて、下図のような高可用性構成(HA 構成)を作ることができます。
    • ノードが喪失してしまうような障害が発生した場合、DeploymentPod を移動させ、サービスを継続できます。
    • ノードが計画停止する場合でも、事前に Pod を他のノードに移動してサービスを継続できます。

f:id:linkode-okazaki:20200520140448p:plain

  • 以下、ノード障害が発生した場合でも、他のノードでポッドが再スタートし、データも無事に守られている事を確認します。
  • 事前準備として、前回の記事を参考に永続ボリュームを作成しておきます。
    • 今回は、EFS を利用した永続ボリュームを作成しておきます。

blog.linkode.co.jp

高可用性構成のためのマニフェストファイルの準備

strorageclass.yml

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: efs-sc
provisioner: efs.csi.aws.com

claim.yml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: efs-claim
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: efs-sc
  resources:
    requests:
      storage: 5Gi

pv.yml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: efs-pv
spec:
  capacity:
    storage: 5Gi
  volumeMode: Filesystem
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  storageClassName: efs-sc
  csi:
    driver: efs.csi.aws.com
    volumeHandle: [EFS のファイルシステム ID]

deployment.yml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: mysql
  labels:
    app: mysql
spec:
  selector:
    matchLabels:
      app: mysql
  replicas: 1
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
      - name: mysql
        image: mysql:5.7
        ports:
        - containerPort: 3306
        env:
        - name: MYSQL_ROOT_PASSWORD
          value: qwerty
        volumeMounts:
        - mountPath: /var/lib/mysql
          name: pvc
          subPath: mysql-data # EKS や GKE の永続ボリュームを使用する場合は必要
        livenessProbe:
          exec:
            command: ["mysqladmin","-p$MYSQL_ROOT_PASSWORD","ping"]
          initialDelaySeconds: 60
          timeoutSeconds: 10
      volumes:
      - name: pvc
        persistentVolumeClaim:
          claimName: efs-claim

  • EKS の永続ボリュームでは、ファイルシステムのルートに lost+foundディレクトリが存在するため、 MySQL コンテナの初期化処理がエラーとなってしまい、異常終了します。
    • そのため、subPath を設定し、空のディレクトリをマウントするようにします。
    • 詳しくはこちら
    • GKE(Google Kubernetes Engine)を利用する際にも同様の問題が発生します。
  • MySQL の設定でパスワードをベタ書きで設定していますが、実際の本番運用においては、シークレット(Secret)リソースを用いてマニフェストファイルに機密情報をベタ書きしないようにします。

service.yml

apiVersion: v1
kind: Service
metadata:
  name: mysql-dpl
  labels:
    app: mysql
spec:
  type: NodePort
  ports:
  - port: 3306
    nodePort: 30306
  selector:
    app: mysql

マニフェストファイルの適用と中身の確認

  • マニフェストフェイルを kubectl apply -f で適用したら、コンテナの中に入って MySQL の操作を行います。
    • 後に、データが消えていない事を確認するため、データベースとテーブルとレコードを作成しておきます。

MySQL のコンテナに入って、レコードを作成するまでの流れ

$ kubectl exec -it mysql-5f98f6b65b-77m6b -- /bin/bash
root@mysql-5f98f6b65b-77m6b:/# mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 199
Server version: 5.7.30 MySQL Community Server (GPL)

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
+--------------------+
4 rows in set (0.03 sec)

mysql> create database test_db;
Query OK, 1 row affected (0.03 sec)

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| test_db            |
+--------------------+
5 rows in set (0.00 sec)

mysql> use test_db;
Database changed

mysql> show tables;
Empty set (0.01 sec)

mysql> CREATE TABLE `m_users` (
    ->           `id` int NOT NULL AUTO_INCREMENT PRIMARY KEY COMMENT "ID",
    ->           `user_name` VARCHAR(100) NOT NULL COMMENT "",
    ->           `mail_address` VARCHAR(200) NOT NULL COMMENT "",
    ->           `password` VARCHAR(100) NOT NULL COMMENT "",
    ->           `created` datetime DEFAULT NULL COMMENT "",
    ->           `modified` datetime DEFAULT NULL COMMENT ""
    ->         ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Query OK, 0 rows affected (0.14 sec)

mysql> INSERT INTO m_users (user_name, mail_address, password, created, modified)
    ->           VALUES ("Hoge Taro", "hogetaro@hoge.com", "123123", now(), now());
Query OK, 1 row affected (0.03 sec)

mysql> select * from m_users;
+----+-----------+------------------+----------+---------------------+---------------------+
| id | user_name | mail_address     | password | created             | modified            |
+----+-----------+------------------+----------+---------------------+---------------------+
|  1 | Hoge Taro | hogetaro@hoge.com| 123123   | 2020-05-19 02:21:55 | 2020-05-19 02:21:55 |
+----+-----------+------------------+----------+---------------------+---------------------+
1 row in set (0.00 sec)

mysql> \q
Bye
root@mysql-5f98f6b65b-77m6b:/# exit

  • 作成された Pod の情報を確認しておきます。
    • あとで、ノードが変わっていることを確認します。

作成された MySQL の Pod の情報確認

$ kubectl describe pod mysql-5f98f6b65b-77m6b
Name:           mysql-5f98f6b65b-77m6b
Namespace:      default
Priority:       0
Node:           ip-192-168-35-15.us-east-2.compute.internal/192.168.35.15
Start Time:     Tue, 19 May 2020 10:41:06 +0900
Labels:         app=mysql
                pod-template-hash=5f98f6b65b
Annotations:    kubernetes.io/psp: eks.privileged
Status:         Running
IP:             192.168.38.116
IPs:            <none>
Controlled By:  ReplicaSet/mysql-5f98f6b65b
Containers:
  mysql:
    Container ID:   docker://511b1adc2e1fdc0199210dc0697304bbbc1125ac0d709cb072489aec8ef7602c
    Image:          mysql:5.7
    Image ID:       docker-pullable://mysql@sha256:5c9fd7949bc0f076429fa2c40d0e7406e095bdb5216a923257b31972a6f3ae4f
    Port:           3306/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Tue, 19 May 2020 10:41:18 +0900
    Ready:          True
    Restart Count:  0
    Liveness:       exec [mysqladmin -p$MYSQL_ROOT_PASSWORD ping] delay=60s timeout=10s period=10s #success=1 #failure=3
    Environment:
      MYSQL_ROOT_PASSWORD:  qwerty
    Mounts:
      /var/lib/mysql from pvc (rw,path="mysql-data")
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-rsllf (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  pvc:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  efs-claim
    ReadOnly:   false
  default-token-rsllf:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-rsllf
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type     Reason            Age                From                                                  Message
  ----     ------            ----               ----                                                  -------
  Warning  FailedScheduling  50m (x4 over 50m)  default-scheduler                                     pod has unbound immediate PersistentVolumeClaims (repeated 4 times)
  Normal   Scheduled         49m                default-scheduler                                     Successfully assigned default/mysql-5f98f6b65b-77m6b to ip-192-168-35-15.us-east-2.compute.internal
  Normal   Pulling           49m                kubelet, ip-192-168-35-15.us-east-2.compute.internal  Pulling image "mysql:5.7"
  Normal   Pulled            49m                kubelet, ip-192-168-35-15.us-east-2.compute.internal  Successfully pulled image "mysql:5.7"
  Normal   Created           49m                kubelet, ip-192-168-35-15.us-east-2.compute.internal  Created container mysql
  Normal   Started           49m                kubelet, ip-192-168-35-15.us-east-2.compute.internal  Started container mysql

ノードの停止と経過確認、データの確認

  • 先程と同様に、AWS のコンソール画面から MySQLPod が稼働しているノードを停止させます。
  • 停止させたら、while true; do date; kubectl get po -o wide; kubectl get deploy; sleep 10; done のコマンドを実行させ、PodDeployment の情報を様子を確認します。
    • fish-shell を使っている場合は、while true;date; kubectl get po -o wide; kubectl get deploy; sleep 10; end を実行します。

ノード停止後の PodDeployment の様子

$ while true; do date; kubectl get po -o wide; kubectl get deploy; sleep 10; done

# ノード停止直後
2020年 5月19日 火曜日 11時49分08秒 JST
NAME                     READY   STATUS    RESTARTS   AGE   IP               NODE                                          NOMINATED NODE   READINESS GATES
mysql-5f98f6b65b-77m6b   1/1     Running   0          68m   192.168.38.116   ip-192-168-35-15.us-east-2.compute.internal   <none>           <none>
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
mysql   1/1     1            1           68m

# ノード停止後しばらく経つと、Deployment の AVAILABLE がゼロになります。
2020年 5月19日 火曜日 11時50分24秒 JST
NAME                     READY   STATUS    RESTARTS   AGE   IP               NODE                                          NOMINATED NODE   READINESS GATES
mysql-5f98f6b65b-77m6b   1/1     Running   0          69m   192.168.38.116   ip-192-168-35-15.us-east-2.compute.internal   <none>           <none>
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
mysql   0/1     1            0           69m

# 更に時間が経過すると、再び Deployment が利用できるようになります。
# この際、Pod が配置されているノードが変わっていることがわかります。
2020年 5月19日 火曜日 11時51分12秒 JST
NAME                     READY   STATUS    RESTARTS   AGE   IP               NODE                                          NOMINATED NODE   READINESS GATES
mysql-5f98f6b65b-kbbp5   1/1     Running   0          11s   192.168.24.156   ip-192-168-15-93.us-east-2.compute.internal   <none>           <none>
NAME    READY   UP-TO-DATE   AVAILABLE   AGE
mysql   1/1     1            1           70m

サービス復旧後の Pod の情報を確認

$ kubectl describe pod mysql-5f98f6b65b-kbbp5
Name:           mysql-5f98f6b65b-kbbp5
Namespace:      default
Priority:       0
Node:           ip-192-168-15-93.us-east-2.compute.internal/192.168.15.93
Start Time:     Tue, 19 May 2020 11:51:02 +0900
Labels:         app=mysql
                pod-template-hash=5f98f6b65b
Annotations:    kubernetes.io/psp: eks.privileged
Status:         Running
IP:             192.168.24.156
IPs:            <none>
Controlled By:  ReplicaSet/mysql-5f98f6b65b
Containers:
  mysql:
    Container ID:   docker://e9cdc23301058d5c1196f122a18de83e6a07d6e43ad242317d2fae631f8089f4
    Image:          mysql:5.7
    Image ID:       docker-pullable://mysql@sha256:5c9fd7949bc0f076429fa2c40d0e7406e095bdb5216a923257b31972a6f3ae4f
    Port:           3306/TCP
    Host Port:      0/TCP
    State:          Running
      Started:      Tue, 19 May 2020 11:51:12 +0900
    Ready:          True
    Restart Count:  0
    Liveness:       exec [mysqladmin -p$MYSQL_ROOT_PASSWORD ping] delay=60s timeout=10s period=10s #success=1 #failure=3
    Environment:
      MYSQL_ROOT_PASSWORD:  qwerty
    Mounts:
      /var/lib/mysql from pvc (rw,path="mysql-data")
      /var/run/secrets/kubernetes.io/serviceaccount from default-token-rsllf (ro)
Conditions:
  Type              Status
  Initialized       True
  Ready             True
  ContainersReady   True
  PodScheduled      True
Volumes:
  pvc:
    Type:       PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
    ClaimName:  efs-claim
    ReadOnly:   false
  default-token-rsllf:
    Type:        Secret (a volume populated by a Secret)
    SecretName:  default-token-rsllf
    Optional:    false
QoS Class:       BestEffort
Node-Selectors:  <none>
Tolerations:     node.kubernetes.io/not-ready:NoExecute for 300s
                 node.kubernetes.io/unreachable:NoExecute for 300s
Events:
  Type    Reason     Age   From                                                  Message
  ----    ------     ----  ----                                                  -------
  Normal  Scheduled  10m   default-scheduler                                     Successfully assigned default/mysql-5f98f6b65b-kbbp5 to ip-192-168-15-93.us-east-2.compute.internal
  Normal  Pulling    10m   kubelet, ip-192-168-15-93.us-east-2.compute.internal  Pulling image "mysql:5.7"
  Normal  Pulled     10m   kubelet, ip-192-168-15-93.us-east-2.compute.internal  Successfully pulled image "mysql:5.7"
  Normal  Created    10m   kubelet, ip-192-168-15-93.us-east-2.compute.internal  Created container mysql
  Normal  Started    10m   kubelet, ip-192-168-15-93.us-east-2.compute.internal  Started container mysql

  • 再度、MySQL のコンテナに入り、データを確認してみます。

サービス復旧後の MySQL のデータを確認

$ kubectl exec -it mysql-5f98f6b65b-kbbp5 -- /bin/bash

root@mysql-5f98f6b65b-kbbp5:/# mysql -u root -p
Enter password:
Welcome to the MySQL monitor.  Commands end with ; or \g.
Your MySQL connection id is 64
Server version: 5.7.30 MySQL Community Server (GPL)

Copyright (c) 2000, 2020, Oracle and/or its affiliates. All rights reserved.

Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.

Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sys                |
| test_db            |
+--------------------+
5 rows in set (0.05 sec)

mysql> use test_db;
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A

Database changed
mysql> show tables;
+-------------------+
| Tables_in_test_db |
+-------------------+
| m_users           |
+-------------------+
1 row in set (0.01 sec)

mysql> select * from m_users;
+----+-----------+------------------+----------+---------------------+---------------------+
| id | user_name | mail_address     | password | created             | modified            |
+----+-----------+------------------+----------+---------------------+---------------------+
|  1 | Hoge Taro | hogetaro@hoge.com| 123123   | 2020-05-19 02:21:55 | 2020-05-19 02:21:55 |
+----+-----------+------------------+----------+---------------------+---------------------+
1 row in set (0.00 sec)

mysql> \q
Bye
root@mysql-5f98f6b65b-kbbp5:/# exit
exit

  • 上記のように、Pod が別のノードで稼働し始めても、データは喪失せず、無事残っていることが確認できました。

まとめ

  • K8sDeployment を利用することで、Pod の稼働数が適切に管理され、アプリケーションの可用性が担保されることを確認しました。
  • EKS において、 Deployment と永続ボリュームを用いることでノード障害からデータを守る高可用性構成(HA 構成)が実現できることを試行してみました。
    • ここでは試してませんが、メンテナンスのために Pod を別のノードに移した場合でも同様にサービスを継続できます。
  • マニフェストによって宣言的設定をすれば、K8s でよしなにオブジェクトの生成・設定をしてくれることの強力さが感じられたように思います。

参考資料