こんにちは、タカサオです!
ちょっと前ですが、私の大好きなサービスであるEKSがAmazon Application Recovery Controller (ARC)をサポートする様になりました。
正直あまり派手ではないサービスですが、、EKS上で動くアプリケーションの可用性を高めるためには重要そうです!
ざっと見渡す限りこの機能を扱ったブログも無い様なので今回ちょっと検証してみました!
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がどの様に通信を制御するのかを以下で説明します。
1. 正常時のEKSクラスタ内のService間通信
Orders
ServiceからProducst
Serviceに対して通信を行うケースを考えてみましょう。
-
まずはじめに、
Orders
ServiceのPodはProducst
Serviceの名前解決をEKSクラスタの内部DNSであるCoreDNSに問合せます。Producst
ServiceのIPアドレスとして10.100.78.3
が登録されていましたので、このIPアドレスがOrders
Podに返却されます -
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アドレスに変換されます -
2.で送信先アドレスを
Products
PodのIPアドレスに変換されたパケットは、ENIを経由して別ワーカーノード上で稼動するそのIPアドレスを持つProducts
Podに届きます
KubernetesのオブジェクトであるEndpointSliceについての詳細は以下のドキュメントを参照頂ければと思います。
2. ARCゾーンシフト発動時のEKSクラスタ内のService間通信
AZ1〜AZ3と3つあるAZのうち、AZ2で障害が発生したケースを想定して、ARCゾーンシフトが発動する事でどの様にService間通信が制御されるのかを以下で解説します。
- EKSクラスタ内部には、EndpointSliceオブジェクトを管理するEndpointSlice Controllerが稼動しています。APCゾーンシフトを発動させると、このEndpointSlice Controllerは既に作成されている全てのEndpointSliceオブジェクトを対象に、障害が発生しているAZ(ここではAZ2)上で稼動しているPodの情報が登録されているかをチェックし、もし登録されていればEndpointSliceオブジェクトからそのPod情報を削除します
- 各ワーカーノード上で稼動しているkube-proxyは変更されたEndpointSliceオブジェクトの情報を元にワーカーノード上のiptablesの設定を更新します。つまり、1.で削除されたPodへのパケット転送設定を削除します
- Serviceへの通信(ここだと
Products
Service)の際にiptablesの情報を元にServiceのIPアドレスからPodのIPアドレスへとパケットの転送先アドレスが変更されますが、2.で障害AZにあるPodの情報はiptablesから削除されているのでそれらのPodのIPアドレスには変換されません。これにより、障害AZ上にあるPodへの通信のルーティングは行なわれなくなります
つまり、EKSクラスタに対するゾーンシフトは、Service毎に自動作成されるEndpointSliceオブジェクトの内容を修正する事で、すばやく障害AZ上にあるPodへの通信ルーティングを遮断する仕組みの様です。
なるほど、、、なんとなく仕組みは理解できました!!
EKSのARCゾーンシフトを実機で試してみる!
ここまででARCゾーンシフト発動時のEKSクラスタ内部の動作の仕組みについて学びましたので、ここからは実機で検証してみたいと思います!
前準備1. ARCゾーンシフトを有効にしたEKSクラスタを用意する
まずはARCゾーンシフトを有効にしたEKSクラスタが必要です。
以下のAWS公式ドキュメントなどを参考にEKSクラスタを構築してください。
ARCゾーンシフトの有効化はEKSクラスタ作成時に選択できます。
選択項目は有効化/無効のラジオボタンのみです。
ARCゾーンシフトの設定はEKSクラスタ作成後でも変更可能できます。
今回の検証では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を作成します。
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を作成します。
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サービスのトップページに移動します。
その後、左側ペインの「ゾーンレベルの移行」をクリックします。
画面下分にARC対象のAWSリソースが表示されますので、今回構築したEKSクラスタが表示されている事を確認します。
問題無く表示されていれば画面上部の「ゾーンシフトを開始」ボタンをクリックします。
2. ゾーンシフトを実行!!
ゾーンシフト実行の前にいくつかの設定を入力する必要があります。
まずはじめに遮断するAZを選択します。
続いて、ゾーンシフト対象のリソースとして用意しているEKSクラスタを選択します。
ゾーンシフトの有効期限を選択します。
この有効期限が経過するとゾーンシフト解除されて、対象AZに対して再び通信がルーティングされます。
コメント欄にはなにを書いても大丈夫です。
ゾーンシフトで処理に使えるリソース量が減る事について同意のチェックを入れて「開始」ボタンをクリックするとゾーンシフトが開始されます。
ゾーンシフトのリストに対象のEKSクラスタが表示されました。
ゾーンシフトが開始された様です!
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の行も消えました。
ARCゾーンシフトの料金は?
気になる料金ですが、ゾーンシフトを使う分には追加料金は発生しない様です。
であればこの機能は使うに越した事は無いですねー!
注意点:Karpenter、Cluster AutoscalerはARC非対応!
現状ですがワーカーノードの自動スケールアウトSWであるKarpenter、およびCluster AutoscalerはARCゾーンシフト、ゾーンオートシフトをサポートしていません。
そのため、あるAZを対象にゾーンシフトを発動しても、Karpenter、Cluster AutoscalerはそのAZ上に引続きワーカーノードを起動させる可能性がある点ご注意ください!
特定のAZへのワーカーノードの起動を制限するためには、以下のサイトの記載を参考に個別の再設定が必要になります。
一方、Karpenter、Cluster Autoscalerで起動されたワーカーノードについても、その上で稼動するPodについてはARCゾーンシフト、ゾーンオートシフトの対象になりますので、それだけでも効果はありますね。
まとめ
今回のEKSクラスタに対してAPCゾーンシフトについて動作の仕組みの概要解説と簡単な実機検証を行ってみました。
ゾーンシフトを実行する事で対象AZ上にあるPodへの通信が遮断される事を確認できました。
追加料金も発生しない様ですので、システムの可用性を上げるためにも有効活用したいですね!!
今後時間があれば、障害AZの検知から通信遮断まで自動で実施するゾーンオートシフトについても検証してみたいと思います。
それでは、また!