複数のAvailability ZoneにプロビジョニングしたELB(ALB) / AutoScaling Groupから特定Availability Zone上のリソースをパージする

380件のシェア(そこそこ話題の記事)

中山(順)です

先日に東京リージョンで比較的規模の大きな障害が発生いたしました。

東京リージョン (AP-NORTHEAST-1) で発生した Amazon EC2 と Amazon EBS の事象概要

障害の影響は特定のAZに限られていたことは上記の公式メッセージで説明されているとおりです。 また、障害発生の早い段階で単一のAvailability Zoneで問題が発生していることがService Health Dashboardでアナウンスされていました。 しかし、Design for Failureの原則に基づいてリソースを複数のAvailability Zoneで冗長化してるケースでも何らかの影響を受けたというケースもあったようです。(以下、その一例です)

8月23日のAWSの大規模障害でMultiAZでも突然ALB(ELB)が特定条件で500エラーを返しはじめたという話

これは、リソースが「中途半端」に障害の影響を受けたために発生したためではないかと推測します。 こういった現象はシステムの運用を経験したことがある方は「あるある」と思ったのではないかと思います(所謂、半死)。 AWS上で稼働させるサービスの可用性を保つためには、こういった不安定なリソースを切り離すというアプローチも予め考えておく必要があるということを今回の障害で学べたのではないかと思います。

この記事では、複数のAvailability ZoneにプロビジョニングしたELB(ALB) / AutoScaling Groupから特定Availability Zone上のリソースをパージする流れを紹介したいと思います。

テスト環境

作成した環境は以下のような環境です。 このうち1つのAZのリソースをパージしたいと思います。

図では3つのALBを配置しているように書いていますが、論理的には1つのALBをプロビジョニングします(ターゲットが3つのAZに跨がるのでALBの実体も3つのAZに跨がります)。詳細はENIの確認部分で触れます。

VPC

今回、3つのAZに跨がってリソースをプロビジョニングします。 というのも、ALBの場合は1つのAZのみにプロビジョニングするということができません(2AZだと今回紹介する手順を実行できません)。

We recommend that you enable multiple Availability Zones. (Note that with an Application Load Balancer, we require you to enable multiple Availability Zones.) How Elastic Load Balancing Works

今回はDefault VPCを利用しました。

ELB, Target Group

3つのAZを利用するInternet-facingなALBを作成します。

併せて、負荷分散の状況を確認するためにアクセスログの取得を有効化します。

Target Groupはデフォルトのパラメーターで作成しました。

Launch Template

Web Serverが起動するよう、必要最低限度のUserDataのみを設定しました。

#!/bin/sh
yum update -y 
yum install httpd -y
touch /var/www/html/index.html
service httpd start

AutoScaling Group

ここまで作成したリソース (Subnet, Target Group, Launch Template) を利用してAutoScaling Groupを作成します。

正しくプロビジョニングできると、Target Groupのヘルスチェックが成功するかと思います。

ENI (ELBがプロビジョニングされているAZを確認)

ここまででテスト環境の作成は完了です。 ENIのコンソールから、3つのAZに跨がってリソースがプロビジョニングされていることが確認できます。 EC2インスタンスだけでなく、ALB用のENIも確認できます。

ALBのDNS名を名前解決すると、ALB用のENIに割り当てられたPublic IPが返されることが確認できます。

dig (ALB Name)-XXXXXXXXX.ap-northeast-1.elb.amazonaws.com
; <<>> DiG 9.11.3-1ubuntu1.8-Ubuntu <<>> (ALB Name)-XXXXXXXXX.ap-northeast-1.elb.amazonaws.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 8848
;; flags: qr rd ra; QUERY: 1, ANSWER: 3, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;(ALB Name)-XXXXXXXXX.ap-northeast-1.elb.amazonaws.com. IN A

;; ANSWER SECTION:
(ALB Name)-XXXXXXXXX.ap-northeast-1.elb.amazonaws.com. 29 IN A xxx.xxx.xxx.xxx
(ALB Name)-XXXXXXXXX.ap-northeast-1.elb.amazonaws.com. 29 IN A yyy.yyy.yyy.yyy
(ALB Name)-XXXXXXXXX.ap-northeast-1.elb.amazonaws.com. 29 IN A zzz.zzz.zzz.zzz

;; Query time: 8 msec
;; SERVER: 192.168.aaa.aaa#53(192.168.aaa.aaa)
;; WHEN: Sun Aug 25 21:11:04 DST 2019
;; MSG SIZE  rcvd: 133

Athena(アクセスログを確認)

ALBから3つのEC2インスタンスに負荷分散されていることを確認したいと思います。

まず、以下のようにHTTPリクエストを繰り返し行います。

 while true; do sleep 1; curl http://(ALB Name)-XXXXXXXXX.ap-northeast-1.elb.amazonaws.com; echo -e $(date);done

しばらくしてからAthenaでログを確認します。 テーブル定義は以下のドキュメントを参照してください。

Querying Application Load Balancer Logs

実行したクエリと結果は以下の通りです。

SELECT COUNT(target_ip) AS
 count,
 elb_status_code, 
 target_ip
FROM alb_logs
GROUP BY elb_status_code, target_ip
LIMIT 100

このように、綺麗に分散できていることが分かります。

Availability Zoneのパージ

それでは、特定のAZをパージします。 今回は、ap-northeast-1a(apne1-az4)をパージしたいと思います。 なお、AZ名とAZ IDの組み合わせはAWSアカウントによって異なります。

AWSアカウントに因らずアベイラビリティゾーンを識別できるAZ IDを利用しよう #reinvent

以下の通り、ELBのコンソールから作業を実行します。

やることはこれだけです。

結果の確認

この結果、どうなるでしょうか。

まず、ALBのDNS名を名前解決してみました。 その結果、返ってきたAレコードが2つになっていました。

; <<>> DiG 9.11.3-1ubuntu1.8-Ubuntu <<>> (ALB Name)-XXXXXXXXX.ap-northeast-1.elb.amazonaws.com
;; global options: +cmd
;; Got answer:
;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 26882
;; flags: qr rd ra; QUERY: 1, ANSWER: 2, AUTHORITY: 0, ADDITIONAL: 1

;; OPT PSEUDOSECTION:
; EDNS: version: 0, flags:; udp: 512
;; QUESTION SECTION:
;(ALB Name)-XXXXXXXXX.ap-northeast-1.elb.amazonaws.com. IN A

;; ANSWER SECTION:
(ALB Name)-XXXXXXXXX.ap-northeast-1.elb.amazonaws.com. 44 IN A xxx.xxx.xxx.xxx
(ALB Name)-XXXXXXXXX.ap-northeast-1.elb.amazonaws.com. 44 IN A yyy.yyy.yyy.yyy

;; Query time: 8 msec
;; SERVER: 192.168.aaa.aaa#53(192.168.aaa.aaa)
;; WHEN: Sun Aug 25 22:00:39 DST 2019
;; MSG SIZE  rcvd: 117

この結果は(確認した限りでは)1分以内に得ることができました。 なお、ELBのレコードのTTLは60秒です。

As traffic to your application changes over time, Elastic Load Balancing scales your load balancer and updates the DNS entry. Note that the DNS entry also specifies the time-to-live (TTL) as 60 seconds, which ensures that the IP addresses can be remapped quickly in response to changing traffic. How Elastic Load Balancing Works

アクセスログも確認してみましょう。 ap-northeast-1aにあるターゲットへのリクエストが減っていることがリクエストを集計した結果から確認できます。

また、設定を変更したタイミングでパージしたAZへのルーティングが止まることもログから確認できました。

SELECT substr(time,1,16) AS time_every_min,
       count(*) AS count
FROM alb_logs
WHERE time >= '2019-08-25T12:50'
 AND time <  '2019-08-25T13:15'
 AND target_ip = '172.31.20.105'
GROUP BY substr(time,1,16)
ORDER BY time_every_min

なお、設定変更のタイミングが13:00ちょうどくらいとなります。 以下のログはCloudTrailのログです。 "eventTime": "2019-08-25T13:00:02Z" となっていることを確認できます。 ほぼ即時反映と言って良さそうです。

{
    "eventVersion": "1.05",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "XXX:XXX",
        "arn": "arn:aws:sts::XXXXXXXXXXXX:assumed-role/XXX/XXX",
        "accountId": "XXXXXXXXXXXX",
        "accessKeyId": "YYYYYYYYYYYYYYYY",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "ZZZZZZZZZZZZZZZZ",
                "arn": "arn:aws:iam::XXXXXXXXXXXX:role/XXX",
                "accountId": "XXXXXXXXXXXX",
                "userName": "XXX"
            },
            "webIdFederationData": {},
            "attributes": {
                "mfaAuthenticated": "true",
                "creationDate": "2019-08-25T12:10:19Z"
            }
        }
    },
    "eventTime": "2019-08-25T13:00:02Z",
    "eventSource": "elasticloadbalancing.amazonaws.com",
    "eventName": "SetSubnets",
    "awsRegion": "ap-northeast-1",
    "sourceIPAddress": "xxx.xxx.xxx.xxx",
    "userAgent": "console.ec2.amazonaws.com",
    "requestParameters": {
        "subnetMappings": [
            {
                "subnetId": "subnet-2edde176"
            },
            {
                "subnetId": "subnet-71dc8259"
            }
        ],
        "loadBalancerArn": "arn:aws:elasticloadbalancing:ap-northeast-1:XXXXXXXXXXXX:loadbalancer/app/Multi-AZ-Test/aaaaaaaaaaaaaaaaaaaa"
    },
    "responseElements": {
        "ipAddressType": "ipv4",
        "availabilityZones": [
            {
                "subnetId": "subnet-2edde176",
                "zoneName": "ap-northeast-1c"
            },
            {
                "subnetId": "subnet-71dc8259",
                "zoneName": "ap-northeast-1d"
            }
        ]
    },
    "requestID": "3fc546fc-c738-11e9-9630-8db36b4b535e",
    "eventID": "7c6a48c5-f9a1-4bc9-a262-76de6cc7a5e8",
    "eventType": "AwsApiCall",
    "apiVersion": "2015-12-01",
    "recipientAccountId": "XXXXXXXXXXXX"
}

このとき、「ap-northeast-1aにあるターゲットにリクエストがルーティングされたりしないの?」という疑問を持つ方がいるかもしれません。 しかし、ELBで特定のAZを無効にすると、そのAZにあるターゲットへのルーティングは行われなくなります。 先ほどのアクセスログからもそのことは確認できます。

After you disable an Availability Zone, the targets in that Availability Zone remain registered with the load balancer, but the load balancer will not route traffic to them. How Elastic Load Balancing Works

このように、3つのAZを利用できるようなVPC設計をしていれば、これだけで特定AZのリソースを切り離すことができました。

考察

ここまでの動作を確認して、以下のようなことを考えてみました。

  • AZを切り離すかどうかをどのように判断するべきか
  • 切り離した後にやるべきことは何か
  • 継続的に設計を見直すことの重要性

AZを切り離すかどうかをどのように判断するべきか

今回の障害ではService Health DashboardおよびPersonal Health Dashboard経由で障害の概要についての情報提供が実施されていました。 このなかで、障害の影響範囲が単一のAZであることは最初の報告で明記されていました。

インスタンスの接続性について | Instance Availability

[09:18 PM PDT] We are investigating connectivity issues affecting some instances in a single Availability Zone in the AP-NORTHEAST-1 Region.

[09:47 PM PDT] We can confirm that some instances are impaired and some EBS volumes are experiencing degraded performance within a single Availability Zone in the AP-NORTHEAST-1 Region. Some EC2 APIs are also experiencing increased error rates and latencies. We are working to resolve the issue.

(以下、省略)

そのため、「応答が不安定であることを監視システムで検知し、影響範囲が特定AZに偏っている」「AWSから『影響範囲は単一AZ』とアナウンスされる」などの情報が出てくれば、今回紹介したオペレーションの実行を判断できるのではないかと思いました。

しかしながら、「どのAZで障害が発生していたか」をService Health DashboardおよびPersonal Health Dashboard経由で確認することはできませんでした。 今回のような影響範囲が大きい障害ではAWSサポートが非常に混雑することが想定され、「どのAZ(AZ ID)で問題が発生していたのか」を確認できないケースが想定されます(あくまでも想定)。 そのため、Service Health DashboardおよびPersonal Health DashboardでどのIDのAZで問題が発生していたか情報提供してくれると、より正確かつ素早い判断ができたのではないかと思います。 この点は改善を期待したいと思います。

切り離した後にやるべきことは何か

ELBで特定のAZを切り離すことで正常な応答を返せないリソースを切り離すことはできました。 しかし、AZが復旧するまでの間にもサービス提供を継続する必要があります。

リソースが3分の2になってもサービス提供が可能なようなキャパシティプランニングをしていればパージ直後は問題ないと思います。 しかし、スケールアウト・スケールインを繰り返しているような環境(リクエスト数が大きく増減する環境)で、なおかつCPU利用率等に基づくスケーリングポリシーを定義している場合、利用されなくなったインスタンスのCPU利用率が低くなることで適切なスケーリングが行われなくなる可能性があります。 環境によっては、AutoScalingの設定(利用するAZ)も修正する必要があるでしょう。

継続的に設計を見直すことの重要性

今回紹介した作業は3つのAZを利用していることが前提となっています。 東京リージョンでは去年の1月まで「実質的に」利用できるAZが2つでした。 そのため、2つのAZを利用する前提でVPCの設計をしており、今もそのような構成になっている環境は非常に多いと思います。 今回のオペレーションを実行するためには3AZを利用する必要があるため、必要に応じてVPCを再構成するといいでしょう。

東京リージョンの新しいアベイラビリティゾーン「ap-northeast-1d」がリリースされました。

しかし、3AZ化については少し注意が必要です。 以下の例はインスタンスタイプに関してですが、AZによって若干仕様に差があります(以下のコマンドと応答は2019年8月25日に確認した内容です)。

aws ec2 run-instances \
    --image-id ami-0c3fd0f5d33134a76 \
    --count 1 \
    --instance-type m5a.large \
    --security-group-ids sg-7f146119 \
    --subnet-id subnet-2edde176 \
    --dry-run
An error occurred (Unsupported) when calling the RunInstances operation: <strong>Your requested instance type (m5a.large) is not supported in your requested Availability Zone (ap-northeast-1c).</strong> Please retry your request by not specifying an Availability Zone or choosing ap-northeast-1d, ap-northeast-1a.

このようなケースがあるため、構成の変更前に検証環境などで実際に動かして検証することを強くおすすめします。

このような設計の見直しは、Well-Architected Frameworkでも推奨されています。 定期的な情報収集および設計の見直しを行いましょう。

AWS Well-Architected

まとめ

今回の障害では、比較的多くの方が影響を受けたと思います。 ビジネス的な損害を直接的に受けた方も少なからずいるでしょう。

サービスの可用性は、Multi-AZ化/サーバーレス化/マルチリージョン化/マルチクラウド化など設計だけでなく、どのような障害を想定するか/想定した障害の発生時にどのようなオペレーションを行うか/リカバリーを全自動化 or 半自動化するか、などによっても大きく変わります。 「どのような障害を想定するか」というのが難しいですが、できない想定をしても仕方が無いので今回紹介した方法のように問題のある部分を思い切って切り離すという判断も時には有効かもしれません(もちろん、いつも有効とは限りません)。

AWSは新サービスの提供/機能追加/改善を継続的に行っており、最適な設計/運用は更新され続けます。 大変ではありますが、これを今回の障害をきっかけにできることから改善をはじめてはどうでしょうか?

現場からは以上です。