Amazon Web Services ブログ

Amazon EKS を利用した、ステートレスなマルチリージョンアプリケーションの運用

この記事は、Operating a multi-regional stateless application using Amazon EKS を翻訳したものです。

本投稿は、Sr Solutions Architect の Re Alvarez-Parmar と、Technical Account Manager の Avi Harari により寄稿されました。

AWS の上で運用を行う主な利点の一つは、お客様が AWS のグローバルフットプリントを利用して複数のリージョンでワークロードを実行することが、いかに簡単かという点です。ディザスターリカバリーをサポートするため、あるいはエンドユーザーとなるお客様の近くでアプリケーションを稼働させるためにマルチリージョンアーキテクチャが必要な場合、AWS はアプリケーションの可用性や信頼性、そしてレイテンシーを改善するためのビルディングブロックを提供します。本投稿では、Amazon Elastic Kubernetes Service (Amazon EKS) を使用して複数の AWS リージョンでアプリケーションを実行し、AWS Global Accelerator を使用して AWS リージョン間でトラフィックを分散する方法を示します。

本投稿での説明をシンプルにするために、複数の AWS リージョンを跨いで実行されるステートレスアプリケーションにスコープを絞って説明しています。データの永続化が必要なワークロードでは、DynamoDB グローバルテーブルAmazon Aurora グローバルデータベース、そして Amazon S3 クロスリージョンレプリケーションのような機能を利用することができます。本投稿では、ステートレスアプリケーションの基本的なアーキテクチャを提案しています。ステートフルなマルチリージョンアプリケーションのブループリントをビルドするために、データやデータベースレプリケーションサービスでこのアーキテクチャを補完することもできます。

Amazon EKS はグローバル化においてどのように役に立つのか

Kubernetes の宣言的なシステムは、マルチリージョンデプロイメントを運用するための理想的なプラットフォームとなります。宣言的なシステムでは、desired state (望ましい状態) を宣言し、システムが現在の状態と desired state (望ましい状態) を観測し、そして現在の状態から desired state (望ましい状態) へと一致させるために必要なアクションを決定します。Kubernetes の宣言的なシステムを利用すると、アプリケーションのセットアップが容易となり、Kubernetes にシステムの状態を管理させることができます。これにより、single source of truth (信頼できる唯一の情報源) を利用した複数の Kubernetes クラスターを管理できる FluxArgoCD のような GitOps ツールを利用することができます。これらのツールは、構成ドリフトにより、複数環境への同時デプロイメントがほぼ全て脆弱なものになってしまうという問題を最小限に抑えることに役立ちます。

Amazon EKS は、24 の AWS リージョンで利用可能です。一貫性のある Kubernetes-backed なグローバルインフラストラクチャを、GitOps や Infrastructure-as-Code のツールを用いながら運用することが可能です。複数の AWS リージョンの Amazon EKS クラスターにホストされたアプリケーションをインターリージョン VPC ピアリングを使用してプライベートネットワーク上で通信したり、目標復旧時点 (RPO) と目標復旧時間 (RTO) の要件を満たすように AWS のデータベースストレージのサービスを使用してデータレプリケーションの戦略を採用することが可能です。そして、フェイルオーバー (アクティブ/パッシブ)、アクティブ/アクティブ、またはより複雑なシナリオを実装する必要があるかどうかによらず、AWS Global Accelerator のルーティング機能により、トラフィックを複数の AWS リージョンに簡単に転送できます。

AWS Global Accelerator と Amazon Route 53 を使用して、2 つの AWS リージョンで稼働する Kubernetes にホストされたアプリケーションへどのようにトラフィックを分散させるのか確認しましょう。このチュートリアルでは、トラフィックをアクティブに処理するプライマリリージョンと、フェイルオーバーとして機能するセカンダリリージョンがあります。

ソリューション

このチュートリアルでは、2 つの異なる AWS リージョンに存在する 2 つの EKS クラスターにサンプルアプリケーションをデプロイします。それぞれのクラスターに AWS Load Balancer Controller をインストールし、そして Application Load Balancer (ALB) を使用してそれぞれのリージョンでアプリケーションを公開します。その後、ALB をエンドポイントして AWS Global Accelerator の設定を行います。一つをプライマリとして、もう片方をフェイルオーバーとして設定します。

チュートリアルを完了するためには、以下が必要です。

  • AWS CLI version 2
  • eksctl
  • kubectl
  • Helm
  • 異なる AWS リージョンに作成された 2 つの EKS クラスター。EKS クラスターを作成済みではない場合、Amazon EKS クラスターの作成を確認してください。このデモでは、eksctl を使用して 2 つの異なるリージョンに 2 つのクラスターを作成しました。
    eksctl create cluster --name multi-region-blog
  • ドメインおよび Route 53 ホストゾーン

まず、いくつかの環境変数を設定しましょう。

export AWS_REGION_1=<<Primary AWS Region>>
export AWS_REGION_2=<<Secondary AWS Region>>
export EKS_CLUSTER_1=<<EKS cluster name in the primary AWS Region>>
export EKS_CLUSTER_2=<<EKS cluster name in the secondary AWS Region>>
export my_domain=<<example.com Your domain>>
export ACCOUNT_ID=$(aws sts get-caller-identity --query ‘Account’ --output text)

AWS Load Balancer Controller をインストールする

Kubernetes の Ingress に Application Load Balancer を使用するため、AWS Load Balancer Controller をそれぞれの EKS クラスターにインストールする必要があります。コントローラーのインストールには Helm を使用します。すでにクラスターに AWS Load Balancer Controller をインストールしている場合、この手順をスキップすることができます。

AWS ALB Ingress Controller は AWS Load Balancer Controller に名称が変更されました。

クラスター用に OIDC プロバイダーを作成します。

eksctl utils associate-iam-oidc-provider \
  --region $AWS_REGION_1 \
  --cluster $EKS_CLUSTER_1 \
  --approve
  
eksctl utils associate-iam-oidc-provider \
  --region $AWS_REGION_2 \
  --cluster $EKS_CLUSTER_2 \
  --approve

AWS Load Balancer Controller に使用する IAM ポリシードキュメントをダウンロードします。

curl https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/main/docs/install/iam_policy.json > awslb-policy.json

AWS Load Balancer Controller に使用する IAM ポリシーを作成します。

aws iam create-policy \
    --policy-name AWSLoadBalancerControllerIAMPolicy \
    --policy-document file://awslb-policy.json

それぞれのクラスターで、IAM ロールと Kubernetes の Service Account を作成します。

eksctl create iamserviceaccount \
  --cluster $EKS_CLUSTER_1 \
  --namespace kube-system \
  --region $AWS_REGION_1 \
  --name aws-load-balancer-controller \
  --attach-policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/AWSLoadBalancerControllerIAMPolicy \
  --override-existing-serviceaccounts \
  --approve
  
eksctl create iamserviceaccount \
  --cluster $EKS_CLUSTER_2 \
  --namespace kube-system \
  --region $AWS_REGION_2 \
  --name aws-load-balancer-controller \
  --attach-policy-arn arn:aws:iam::${ACCOUNT_ID}:policy/AWSLoadBalancerControllerIAMPolicy \
  --override-existing-serviceaccounts \
  --approve

AWS Load Balancer Controller に使用する Service Account が作成されたので、コントローラーをインストールしましょう。kubectl のコンテキストがプライマリの EKS クラスターに設定されていることを確認してください。

aws eks update-kubeconfig \
  --name $EKS_CLUSTER_1 \
  --region $AWS_REGION_1

コントローラーをインストールします。

helm repo add eks https://aws.github.io/eks-charts
kubectl apply -k “github.com/aws/eks-charts/stable/aws-load-balancer-controller//crds?ref=master”
helm upgrade -i aws-load-balancer-controller \
  eks/aws-load-balancer-controller \
  -n kube-system \
  --set clusterName=$EKS_CLUSTER_1 \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller

セカンダリクラスターに対して、同じ手順を繰り返します。

aws eks update-kubeconfig \
  --name $EKS_CLUSTER_2 \
  --region $AWS_REGION_2 
  
kubectl apply -k “github.com/aws/eks-charts/stable/aws-load-balancer-controller//crds?ref=master”
helm upgrade -i aws-load-balancer-controller \
  eks/aws-load-balancer-controller \
  -n kube-system \
  --set clusterName=$EKS_CLUSTER_2 \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller

Helm チャートが適用されると、次のように表示されます。

AWS Load Balancer controller installed!

サンプルアプリケーションをデプロイする

次に、それぞれのクラスターにサンプルアプリケーションをデプロイする必要があります。kubectl のコンテキストはセカンダリクラスターに切り替えられているため、まずはセカンダリクラスターにサンプルアプリケーションをデプロイしましょう。

cat > sample_application.yaml <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: py-az
  name: py-az
spec:
  replicas: 1
  selector:
    matchLabels:
      app: py-az
  template:
    metadata:
      labels:
        app: py-az
    spec:
      containers:
      - image: public.ecr.aws/b5x3e7x1/eks-py-az
        name: eks-py-az
        ports:
        - containerPort: 5000
          protocol: TCP
---          
apiVersion: v1
kind: Service
metadata:
  annotations:
  labels:
    app: py-az
  name: py-az
spec:
  ports:
  - port: 80
    protocol: TCP
    targetPort: 5000
  type: NodePort
  selector:
    app: py-az
status:
  loadBalancer: {}
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
  name: “py-az-ingress”
  annotations:
    kubernetes.io/ingress.class: alb
    alb.ingress.kubernetes.io/scheme: internet-facing
  labels:
    app: py-az
spec:
  rules:
    - host:
      http:
        paths:
          - path: /*
            backend:
              serviceName: “py-az”
              servicePort: 80
EOF

kubectl apply -f sample_application.yaml

export Ingress_2=$(kubectl get ingress py-az-ingress \
  -o jsonpath=‘{.status.loadBalancer.ingress[0].hostname}’)

プライマリクラスターに対して、手順を繰り返しましょう。

aws eks update-kubeconfig \
  --name $EKS_CLUSTER_1 \
  --region $AWS_REGION_1 

kubectl apply -f sample_application.yaml

export Ingress_1=$(kubectl get ingress py-az-ingress \
  -o jsonpath=‘{.status.loadBalancer.ingress[0].hostname}’)

いずれかの Ingress のアドレスに curl コマンドを実行し、レスポンスを確認することができます。(訳注: プライマリリージョンからレスポンスが返ります)

$ curl $Ingress_1
Welcome to us-east-1c <-- The response should originate from your primary region

Global Accelerator を設定する

サービスが各リージョンで独立して稼働するようになったので、プライマリリージョンにすべてのトラフィックを転送しつつ、プライマリリージョンで問題が発生した場合のみフェイルオーバーリージョンにルーティングする必要があります。トラフィックのルーティングには AWS Global Accelerator を使用します。

AWS Global Accelerator は、ユーザーのトラフィックを AWS のグローバルネットワークインフラストラクチャを介して送信するネットワーキングサービスです。インターネットを介するユーザーのパフォーマンスが 60 % 向上します。また、AWS のグローバルに分散されたエッジロケーションからエニーキャストされた 2 つの静的な IP アドレスを提供することで、マルチリージョンデプロイメントの運用を容易にします。アプリケーションがデプロイされている AWS リージョンの数に関係なく、アプリケーションへの単一のエントリポイントを提供します。トラフィックのルーティングをグローバルレベルでの DNS に依存する代わりに (DNS の結果をキャッシュするクライアントを扱っている場合、しばしばこれが問題になります)、DNS の変更や、DNS の反映またはクライアント側でのキャッシュによる遅延を要求することなく、Global Accelerator はトラフィックのルーティングを切り替えることができます。

アクセラレーターを作成してみましょう。

Global_Accelerator_Arn=$(aws globalaccelerator create-accelerator \
  --name multi-region \
  --query “Accelerator.AcceleratorArn” \
  --output text)

アクセラレーターを作成すると、Global Accelerator は 2 つの静的な IP アドレスをプロビジョニングします。また、アクセラレーターには a1234567890abcdef.awsglobalaccelerator.com のような、2 つの静的な IP アドレスをポイントしているデフォルトの Domain Name System (DNS) 名が割り当てられます。静的な IP アドレスは AWS エッジネットワークから、 Network Load Balancer、Application Load Balancer、EC2 インスタンス、または Elastic IP アドレスのようなエンドポイントに対して、エニーキャストを使用してグローバルにアドバタイズされます。

次に、TCP 80 番ポートでクライアントからのインバウンド接続を処理するリスナーをアクセラレーターに追加します。

Global_Accelerator_Listerner_Arn=$(aws globalaccelerator create-listener \
  --accelerator-arn $Global_Accelerator_Arn \
  --region us-west-2 \
  --protocol TCP \
  --port-ranges FromPort=80,ToPort=80 \
  --query “Listener.ListenerArn” \
  --output text)

続いて、先ほどの手順で作成した 2 つの ALB をエンドポイントとしてリスナーに追加する必要があります。エンドポイントグループは、AWS Global Accelerator に登録されている 1 つ以上のエンドポイントにリクエストをルーティングします。

EndpointGroupArn_1=$(aws globalaccelerator create-endpoint-group \
  --region us-west-2 \
  --listener-arn $Global_Accelerator_Listerner_Arn \
  --endpoint-group-region $AWS_REGION_1 \
  --query “EndpointGroup.EndpointGroupArn” \
  --output text \
  --endpoint-configurations EndpointId=$(aws elbv2 describe-load-balancers \
    --region $AWS_REGION_1 \
    --query “LoadBalancers[?contains(DNSName, ‘$Ingress_1’)].LoadBalancerArn” \
    --output text),Weight=128,ClientIPPreservationEnabled=True)

先ほどの手順を繰り返して、フェイルオーバーリージョンのエンドポイントをエンドポイントグループに追加します。

EndpointGroupArn_2=$(aws globalaccelerator create-endpoint-group \
  --region us-west-2 \
  --traffic-dial-percentage 0 \
  --listener-arn $Global_Accelerator_Listerner_Arn \
  --endpoint-group-region $AWS_REGION_2 \
  --query “EndpointGroup.EndpointGroupArn” \
  --output text \
  --endpoint-configurations EndpointId=$(aws elbv2 describe-load-balancers \
    --region $AWS_REGION_2 \
    --query “LoadBalancers[?contains(DNSName, ‘$Ingress_2’)].LoadBalancerArn” \
    --output text),Weight=128,ClientIPPreservationEnabled=True)

セカンダリリージョンの ALB に対して traffic-dial-percentage パラメータを 0 に設定したので、すべてのトラフィックはプライマリリージョンにルーティングされます。プライマリリージョンのエンドポイント (もしくは、それに関連づけられたバックエンド) がヘルスチェックに失敗した場合、Global Accelerator はフェイルオーバーリージョンの ALB にトラフィックをルーティングします。Web ブラウザまたは curl コマンドでアクセラレーターの DNS 名にアクセスすると、プライマリリージョンからレスポンスが得られます。

GA_DNS=$(aws globalaccelerator describe-accelerator \
  --accelerator-arn $Global_Accelerator_Arn \
  --query “Accelerator.DnsName” \
  --output text)

curl $GA_DNS

Welcome to us-east-1c%  <-- Response from the primary AWS region

Route 53 の設定

Global Accelerator はアクセラレーターに DNS 名を割り当てますが、自身で所有するドメインを使用したい場合もあるでしょう。そのような場合、Amazon Route 53 を使用してアクセラレーターにカスタムドメイン名を用いることができます。

Amazon Route 53 は、高い可用性とスケーラビリティを持つ Domain Name System (DNS) サービスです。Route 53 で新しいドメインを登録することも、その他のレジストラを介して購入したドメインを管理することもできます。Route 53 は、DNS レコードをドメインのルーティングロジック (例えば、example.com) やすべてのサブドメインを含む “ホストゾーン” の中で整理します。ホストゾーンは、対応するドメインと同じ名前です。ドメインが Route 53 で管理されていない場合、この手順に従うことができます。

ドメインのホストゾーン ID を取得します。

Route53_HostedZone=$(aws route53 list-hosted-zones \
  --query “HostedZones[?Name == ‘$my_domain.‘].[Id]” \
  --output text | cut -d’/' -f 3)
  
echo $Route53_HostedZone

Route 53 レコードを作成します。

cat > route53-records.json<<EOF
{
  “Comment”: “Multi region Global Accelerator”,
  “Changes”: [
    {
      “Action”: “CREATE”,
      “ResourceRecordSet”: {
        “Name”: “multi-region.$my_domain.“,
        “Type”: “A”,
        “AliasTarget”: {
          “HostedZoneId”: “Z2BJ6XQ5FK7U4H”,
          “DNSName”: “$GA_DNS”,
          “EvaluateTargetHealth”: false
        }
      }
     }
   ]
}
EOF

aws route53 change-resource-record-sets \
  --hosted-zone-id $Route53_HostedZone \
  --change-batch file://route53-records.json

DNS の変更が反映されるまで数秒待ってから、サービスを確認することができます。

$ curl multi-region.$my_domain
Welcome to us-east-1c <-- Response from the primary AWS Region

プライマリ AWS リージョンからの応答が表示されます。Route 53 はすべてのリクエストを Global Accelerator にルーティングし、Global Accelerator はプライマリリージョンがヘルスチェックに失敗するまで、プライマリ AWS リージョンの EKS クラスターにトラフィックを送信します。プライマリリージョンがヘルスチェックに失敗すると、Global Accelerator はトラフィックをフェイルオーバーリージョンに転送します。

フェイルオーバーをシミュレートする

AWS Global Accelerator はアプリケーションの正常性を自動的にチェックし、ユーザーのトラフィックを正常なアプリケーションのエンドポイントにのみルーティングします。正常性のステータスが変更されるか設定の更新を行なった場合、AWS Global Accelerator は即座に反応し、ユーザーを利用可能なエンドポイントにルーティングします。

プライマリ EKS クラスターで実行されている Pod を終了してみましょう。これにより、サービスはバックエンドが存在しなくなり、ALB は 502 Bad Gateway エラーを返します。はじめに、プライマリリージョンの ALB にリクエストを送り、正常なレスポンスが取得できることを確認します。

curl $Ingress_1
Welcome to us-east-1c% <-- Response from the primary AWS region

次に、プライマリリージョンの Deployment をスケールダウンします。

aws eks update-kubeconfig \
  --name $EKS_CLUSTER_1 \
  --region $AWS_REGION_1 
  
kubectl scale deployment py-az --replicas=0

再度、プライマリの ALB に curl を実行すると、502 エラーが表示されます。

curl $Ingress_1                        
<html>
<head><title>502 Bad Gateway</title></head>
<body>
<center><h1>502 Bad Gateway</h1></center>
</body>
</html>

Global Accelerator のヘルスチェックが失敗するのを待ってから (私たちの検証では、これには 30 秒程度かかります)、サービスの A レコードの名前にリクエストを再送します。

curl multi-region.$my_domain
Welcome to us-east-2b% <-- Response from the failover AWS region

ここでは、フェイルオーバーリージョンからのレスポンスが表示されます。プライマリリージョンの Deployment をスケールアップすると、数分後に Global Accelerator はすべてのリクエストをプライマリリージョンに送ります。

クリーンアップする

本投稿のデモで作成したリソースを削除するために次の手順を使用します。

aws globalaccelerator delete-endpoint-group --endpoint-group-arn $EndpointGroupArn_2 
aws globalaccelerator delete-endpoint-group --endpoint-group-arn $EndpointGroupArn_1
aws globalaccelerator delete-listener --listener-arn $Global_Accelerator_Listerner_Arn 
aws globalaccelerator update-accelerator --accelerator-arn $Global_Accelerator_Arn --no-enabled
# You may have to wait a few seconds until the accelerator is disabled
aws globalaccelerator delete-accelerator --accelerator-arn $Global_Accelerator_Arn 
sed -i ‘s/CREATE/DELETE/g’ route53-records.json
aws route53 change-resource-record-sets --hosted-zone-id $Route53_HostedZone --change-batch file://route53-records.json
aws eks update-kubeconfig --name $EKS_CLUSTER_1 --region $AWS_REGION_1 
helm delete aws-load-balancer-controller -n kube-system
kubectl delete -f sample_application.yaml
aws eks update-kubeconfig --name $EKS_CLUSTER_2 --region $AWS_REGION_2
helm delete aws-load-balancer-controller -n kube-system
kkubectl delete -f sample_application.yaml

まとめ

この記事では、Amazon EKS を使用してマルチリージョンでアプリケーションを実行する方法と、AWS Global Accelerator を使用してトラフィックをルーティングする方法を説明しました。障害を検知するために Global Accelerator のヘルスチェックの設定し、トラフィックをフェイルオーバーリージョンに自動的にルーティングすることで、マルチリージョンアプリケーションの回復性を高めることができます。

このアーキテクチャを使用して主にマルチリージョンデプロイメントにフォーカスしてきましたが、異なるアカウント、Virtual Private Cloud (VPC) 、または同じ AWS リージョンで稼働する複数の EKS クラスターで実行されているワークロードにトラフィックを分散するために、このアーキテクチャを変形することもできます。

– コンテナスペシャリストソリューションアーキテクト Re Alvarez-Parmar

翻訳はソリューションアーキテクト落水 (おちみず) が担当しました。