EKSのAmazon Application Recovery Controller (ARC)を試してみる!

こんにちは、タカサオです!

ちょっと前ですが、私の大好きなサービスであるEKSがAmazon Application Recovery Controller (ARC)をサポートする様になりました。

正直あまり派手ではないサービスですが、、EKS上で動くアプリケーションの可用性を高めるためには重要そうです！
ざっと見渡す限りこの機能を扱ったブログも無い様なので今回ちょっと検証してみました！

https://aws.amazon.com/jp/about-aws/whats-new/2024/10/amazon-eks-application-recovery-controller-arc/

# Amazon Application Recovery Controller (ARC) とは?

Amazon Application Recovery Controller (ARC) はAWS上で稼動するアプリケーションの回復力を向上する機能です。
具体的には、障害が発生したリージョン、AZへのサービス通信を遮断し、正常なもののみでサービス継続させる事で障害に対するサービス影響を最小限にする機能です。

ARCにはAZレベルとリージョンレベルで動作する機能がありますが、AZレベルのものの中には以下の2つの機能があります。

* ゾーンシフト
* ゾーンオートシフト

ゾーンシフトは対象のAZをユーザーが手動で切り離す機能です。切り離す時間をユーザが指定でき、その時間が経過すると対象のAZは再びサービスに組込まれます。
ゾーンオートシフトはAZの障害をAWSが検知し、AWSが自動でそのAZを切り離す機能です。

2024/12現在でAPCに対応しているAWSリソースは以下があります。

* Network Load Balancer (NLB)
* Application Load Balancer (ALB)
* Amazon Elastic Kubernetes Service (EKS)

# EKSでのARCの挙動

AWSの公式ドキュメントからの転載ですが、EKSクラスタ内のService間通信に対してARCがどの様に通信を制御するのかを以下で説明します。


https://docs.aws.amazon.com/eks/latest/userguide/zone-shift.html

## 1. 正常時のEKSクラスタ内のService間通信 

```Orders```Serviceから```Producst```Serviceに対して通信を行うケースを考えてみましょう。

1. まずはじめに、```Orders```ServiceのPodは```Producst```Serviceの名前解決をEKSクラスタの内部DNSであるCoreDNSに問合せます。```Producst```ServiceのIPアドレスとして```10.100.78.3```が登録されていましたので、このIPアドレスが```Orders```Podに返却されます
1. Kubernetesの挙動として、Serviceが作成されるとそれに対応するEndpointSliceオブジェクトが自動で作成されます。EndpointSliceオブジェクトには対象のServiceからの振り分け先として設定されているPodのIPアドレス情報が管理されます。ワーカーノード上で稼動するKubernetesのコンポーネントであるkube-proxyはこのEndpointSliceオブジェクトを定期的に監視し、EndpointSliceオブジェクトの内容に変更があれば、その変更内容に沿ってiptablesのパケット転送設定を変更します。```Orders```Podから```Products```Serviceへのパケットの送信先アドレスは、iptablesで```Products```ServiceのIPからそのServiceに関連付けされているPodのうちの1つのIPアドレスに変換されます

1. 2.で送信先アドレスを```Products```PodのIPアドレスに変換されたパケットは、ENIを経由して別ワーカーノード上で稼動するそのIPアドレスを持つ```Products```Podに届きます



![zs-traffic-flow-before-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905292/90ccfd80-fcac-bf8e-31d1-a56ac0892328.png)


![zs-traffic-flow-before-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905292/a1b939bb-2d09-b1b0-acb1-342b7fb4f363.png)


KubernetesのオブジェクトであるEndpointSliceについての詳細は以下のドキュメントを参照頂ければと思います。

https://kubernetes.io/docs/concepts/services-networking/endpoint-slices/


## 2. ARCゾーンシフト発動時のEKSクラスタ内のService間通信 

AZ1〜AZ3と3つあるAZのうち、AZ2で障害が発生したケースを想定して、ARCゾーンシフトが発動する事でどの様にService間通信が制御されるのかを以下で解説します。

1. EKSクラスタ内部には、EndpointSliceオブジェクトを管理するEndpointSlice Controllerが稼動しています。APCゾーンシフトを発動させると、このEndpointSlice Controllerは既に作成されている全てのEndpointSliceオブジェクトを対象に、障害が発生しているAZ(ここではAZ2)上で稼動しているPodの情報が登録されているかをチェックし、もし登録されていればEndpointSliceオブジェクトからそのPod情報を削除します
1. 各ワーカーノード上で稼動しているkube-proxyは変更されたEndpointSliceオブジェクトの情報を元にワーカーノード上のiptablesの設定を更新します。つまり、1.で削除されたPodへのパケット転送設定を削除します
1. Serviceへの通信(ここだと```Products```Service)の際にiptablesの情報を元にServiceのIPアドレスからPodのIPアドレスへとパケットの転送先アドレスが変更されますが、2.で障害AZにあるPodの情報はiptablesから削除されているのでそれらのPodのIPアドレスには変換されません。これにより、障害AZ上にあるPodへの通信のルーティングは行なわれなくなります

![zs-traffic-flow-after-1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905292/acf28111-2b8b-947c-d812-61159fd3e425.png)


![zs-traffic-flow-after-2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905292/ceb76283-8851-ff31-8131-eacf9aeb596e.png)

つまり、EKSクラスタに対するゾーンシフトは、Service毎に自動作成されるEndpointSliceオブジェクトの内容を修正する事で、すばやく障害AZ上にあるPodへの通信ルーティングを遮断する仕組みの様です。

なるほど、、、なんとなく仕組みは理解できました!!

# EKSのARCゾーンシフトを実機で試してみる!

ここまででARCゾーンシフト発動時のEKSクラスタ内部の動作の仕組みについて学びましたので、ここからは実機で検証してみたいと思います!

## 前準備1. ARCゾーンシフトを有効にしたEKSクラスタを用意する

まずはARCゾーンシフトを有効にしたEKSクラスタが必要です。
以下のAWS公式ドキュメントなどを参考にEKSクラスタを構築してください。

https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/create-cluster.html

ARCゾーンシフトの有効化はEKSクラスタ作成時に選択できます。
選択項目は有効化/無効のラジオボタンのみです。


![EKSクラスタ作成時にARC設定可能.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905292/c9130d7c-dac8-8269-95a9-c7bda23b5b57.png)

ARCゾーンシフトの設定はEKSクラスタ作成後でも変更可能できます。

![EKSクラスタ作成後にARC変更可能.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905292/f0c83962-4536-7731-ff7f-878abd1b1e61.png)

今回の検証では2つのAZ(us-east-1a,us-east-1b)にワーカーノードを配置するEKSクラスタを用意しました。サブネットとPodに割り振られる可能性のあるIPアドレスの範囲は以下になります。

| AZ | CIDRレンジ | Podに割り振られる可能性のあるIPアドレスの範囲 |
|:-:|:-:|:-:|
| 1a  | 192.168.128.0/18  | 192.168.128.4 〜 192.168.191.254  |
| 1b  | 192.168.192.0/18  | 192.168.192.4 〜 192.168.255.254  |


もちろん、ARCゾーンシフトは有効にしています!

## 前準備2. ARCゾーンシフトを検証するためのサンプルアプリをデプロイする

次の前準備としてARCゾーンシフトを検証するためのサンプルアプリをデプロイします。
まずは、DeploymentでnginxのPodを作成します。

``` arc-sample-nginx.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    app: arc-sample-nginx
  name: arc-sample-nginx
spec:
  replicas: 2  #Podを2つ作成
  selector:
    matchLabels:
      app: arc-sample-nginx
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        app: arc-sample-nginx
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - podAffinityTerm: #Podをそれぞれ異なるAZのワーカーノードで起動する様にする。
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - arc-sample-nginx
              topologyKey: kubernetes.io/zone
            weight: 100
      containers:
      - image: nginx
        name: nginx
        ports:
        - containerPort: 80
        command: [ "sh", "-c", " 
        hostname -I > /usr/share/nginx/html/index.html;
        nginx -g 'daemon off;';
        "]  #Podに割り当てられたIPアドレスをレスポンスとして返却する様に設定する
```

```replicas: 2``` で同じ内容のPodを2つ起動する様にします。
そして、それらPodを```podAntiAffinity```で異なるAZ上のワーカーノードに対して分散して配置する様に設定します。

作成するPodのコンテナのコマンドには```hostname -I > /usr/share/nginx/html/index.html;```を設定する事で、Podに割り当てられたIPアドレスをHTTPレスポンスとしてクライアントに返却する様に設定します。


```
[cloudshell-user@ip-10-134-48-42 ~]$ kubectl apply -f arc-sample-nginx.yaml 
deployment.apps/arc-sample-nginx created
```

Deploymentの作成が完了しました。
Deploymentの2つのPodも正常稼動(Running)になっています。また、それらのPodは2つのAZそれぞれのサブネット上で起動している事も判ります。

```
[cloudshell-user@ip-10-134-48-42 ~]$ kubectl get pod -o wide
NAME                                READY   STATUS    RESTARTS   AGE   IP               NODE                              NOMINATED NODE   READINESS GATES
arc-sample-nginx-59cb7bff9f-f8nnc   1/1     Running   0          15m   192.168.245.13   ip-192-168-228-161.ec2.internal   <none>           <none>
arc-sample-nginx-59cb7bff9f-jsczh   1/1     Running   0          15m   192.168.191.4    ip-192-168-164-166.ec2.internal   <none>           <none>
```


次に、それら2つのPodへ通信をルーティングするためのServiceを作成します。

``` arc-sample-nginx-svc.yaml
apiVersion: v1
kind: Service
metadata:
  creationTimestamp: null
  labels:
    app: arc-sample-nginx
  name: arc-sample-nginx
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    app: arc-sample-nginx
```

```
[cloudshell-user@ip-10-134-48-42 ~]$ kubectl apply -f arc-sample-nginx-svc.yaml 
service/arc-sample-nginx created
```

これでサンプルアプリの準備は完了です。
このサンプルアプリにcurlを定期的に実行してみましょう。

```
[cloudshell-user@ip-10-134-48-42 ~]$ kubectl run curl-client --image=curlimages/curl -it -- sh
If you don't see a command prompt, try pressing enter.
~ $ while true; do curl http://arc-sample-nginx; sleep 1; done
192.168.191.4 
192.168.245.13 
192.168.191.4 
192.168.191.4 
192.168.245.13 
192.168.245.13 
192.168.191.4 
192.168.191.4 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.191.4 
192.168.191.4 
192.168.191.4 
192.168.245.13 
192.168.245.13 
192.168.191.4 
192.168.191.4 
```
2つのPodのIPアドレスが返却されましたので、それぞれのPodにリクエストがルーティングされている事が判りますね。

また、前述したServiceを作成する事で自動作成されるEndpointSliceオブジェクトも作成されています。2つのPodのIPアドレスである```192.168.191.4```と```192.168.245.13```がEndpointとして登録されている事が判ります。 


```
[cloudshell-user@ip-10-134-48-42 ~]$ kubectl get endpointslices
NAME                     ADDRESSTYPE   PORTS   ENDPOINTS                        AGE
arc-sample-nginx-qxrgc   IPv4          80      192.168.245.13,192.168.191.4     30m
kubernetes               IPv4          443     192.168.174.91,192.168.229.161   8h
```

## ARCゾーンシフトを実行する!

前準備は完了しましたので、いよいよARCゾーンシフトを実行してみましょう!

### 1. ゾーンシフト画面に移動 

まずは、APCサービスのトップページに移動します。
その後、左側ペインの「ゾーンレベルの移行」をクリックします。


![スクリーンショット 2024-12-01 0.41.53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905292/8b7b0225-8395-0d87-f81e-0cd6ae9849c6.png)

画面下分にARC対象のAWSリソースが表示されますので、今回構築したEKSクラスタが表示されている事を確認します。
問題無く表示されていれば画面上部の「ゾーンシフトを開始」ボタンをクリックします。

![スクリーンショット 2024-12-01 0.44.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905292/feaa6b3b-5daf-9b06-a04d-0f792c175c42.png)


### 2. ゾーンシフトを実行!!

ゾーンシフト実行の前にいくつかの設定を入力する必要があります。
まずはじめに遮断するAZを選択します。
続いて、ゾーンシフト対象のリソースとして用意しているEKSクラスタを選択します。


![スクリーンショット 2024-12-01 0.56.42.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905292/89166356-34e4-604d-b2c7-f69d93472da4.png)

ゾーンシフトの有効期限を選択します。
この有効期限が経過するとゾーンシフト解除されて、対象AZに対して再び通信がルーティングされます。
コメント欄にはなにを書いても大丈夫です。
ゾーンシフトで処理に使えるリソース量が減る事について同意のチェックを入れて「開始」ボタンをクリックするとゾーンシフトが開始されます。

![スクリーンショット 2024-12-01 1.01.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905292/4008758c-466a-b42f-2561-fbb84a669a43.png)

 ゾーンシフトのリストに対象のEKSクラスタが表示されました。
 ゾーンシフトが開始された様です!

![スクリーンショット 2024-12-01 1.07.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905292/2d16da1b-5f8b-658c-f7d5-d2af3a42bee9.png)

### 3. 本当にゾーンシフトされているの？？を確認する

本当にゾーンシフトされているのか確認してみましょう。

まずは```kubectl get endpointslices```コマンドでEndpointSliceオブジェクトがどの様に変化しているか見てみましょう。
Endpointから```192.168.191.4```無くなっている事が判ります。
ゾーンシフトが動作している様です!

```
[cloudshell-user@ip-10-134-48-42 ~]$ kubectl get endpointslices 
NAME                     ADDRESSTYPE   PORTS   ENDPOINTS                        AGE
arc-sample-nginx-qxrgc   IPv4          80      192.168.245.13                   47m
kubernetes               IPv4          443     192.168.174.91,192.168.229.161   8h
```

次にサンプルアプリへ1秒毎にcurlを実行してみましたが、```192.168.191.4```にはルーティングされず、全て```192.168.245.13```に向いている事が判ります。
ちゃんと通信遮断できているみたいです、やった!!

```
[cloudshell-user@ip-10-134-48-42 ~]$ kubectl run curl-client --image=curlimages/curl -it -- sh
If you don't see a command prompt, try pressing enter.
~ $ while true; do curl http://arc-sample-nginx; sleep 1; done
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
```

5分立つと、再度```192.168.191.4```にも振り分けられる様になりました。
これも設定通りですねー！

```
~ $ while true; do curl http://arc-sample-nginx; sleep 1; done
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.245.13 
192.168.191.4 
192.168.191.4 
192.168.245.13 
192.168.245.13 
192.168.191.4 
192.168.191.4 
192.168.245.13 
192.168.245.13 
192.168.191.4 
192.168.245.13 
192.168.191.4 
192.168.191.4 
192.168.191.4 
192.168.191.4 
192.168.245.13 
192.168.191.4 
192.168.191.4 
192.168.245.13 
192.168.191.4 
192.168.245.13 
192.168.191.4 
192.168.191.4 
192.168.245.13 
192.168.191.4 
192.168.191.4 
```

念のためEndpointSliceオブジェクトを確認してみましたが、再度```192.168.191.4```が登録される様になりました。

```
[cloudshell-user@ip-10-134-48-42 ~]$ kubectl get endpointslices 
NAME                     ADDRESSTYPE   PORTS   ENDPOINTS                        AGE
arc-sample-nginx-qxrgc   IPv4          80      192.168.245.13,192.168.191.4     55m
kubernetes               IPv4          443     192.168.174.91,192.168.229.161   9h
```
ARCコンソールのゾーンシフトのリストからEKSの行も消えました。

![スクリーンショット 2024-12-01 1.19.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1905292/95ad8584-9623-2c9e-de7b-e869e983438c.png)

# ARCゾーンシフトの料金は?

気になる料金ですが、ゾーンシフトを使う分には追加料金は発生しない様です。
であればこの機能は使うに越した事は無いですねー!

https://docs.aws.amazon.com/r53recovery/latest/dg/introduction-pricing-zonal-shift.html

# 注意点:Karpenter、Cluster AutoscalerはARC非対応！

現状ですがワーカーノードの自動スケールアウトSWであるKarpenter、およびCluster AutoscalerはARCゾーンシフト、ゾーンオートシフトをサポートしていません。
そのため、あるAZを対象にゾーンシフトを発動しても、Karpenter、Cluster AutoscalerはそのAZ上に引続きワーカーノードを起動させる可能性がある点ご注意ください！

特定のAZへのワーカーノードの起動を制限するためには、以下のサイトの記載を参考に個別の再設定が必要になります。

https://aws.github.io/aws-eks-best-practices/karpenter/

https://aws.github.io/aws-eks-best-practices/cluster-autoscaling/

一方、Karpenter、Cluster Autoscalerで起動されたワーカーノードについても、その上で稼動するPodについてはARCゾーンシフト、ゾーンオートシフトの対象になりますので、それだけでも効果はありますね。


# まとめ

今回のEKSクラスタに対してAPCゾーンシフトについて動作の仕組みの概要解説と簡単な実機検証を行ってみました。
ゾーンシフトを実行する事で対象AZ上にあるPodへの通信が遮断される事を確認できました。
追加料金も発生しない様ですので、システムの可用性を上げるためにも有効活用したいですね!!

今後時間があれば、障害AZの検知から通信遮断まで自動で実施するゾーンオートシフトについても検証してみたいと思います。

それでは、また!