Amazon Web Services ブログ

AWS Resilience Hub の 標準作業手順 (SOP) を自動実行する

AWS Resilience Hub は AWS マネジメントコンソール上でアプリケーションの回復力(レジリエンス)を一元的に管理し改善できるツールです。AWS Resilience Hub ではレジリエンスの目標を定義して目標に対する耐障害性体制を評価し、AWS Well-Architected フレームワークに基づいた改善のための推奨事項を実装することができます。

AWS Resilience Hub は耐障害性オペレーションの両方に関する推奨事項を提供します。オペレーションに関する推奨事項には、Amazon CloudWatch AlarmsAWS Systems Manager Documents を利用した標準作業手順 (SOPs)AWS Fault Injection Service (FIS) を使用したカオス実験が含まれます。

SOP(標準作業手順)とはサービスの中断やアラームが発生した際に、アプリケーションを効率的に復旧させるために設計された具体的な手順のことです。AWS Well-Architected Framework の「信頼性の柱」で定義されている一般的なアンチパターンの1つは、アラート通知を受け取ったときにオペレーターが従うべき SOP がないことです。アラームが発生した際の処理の自動化は、自動的な是正措置、定義済みSOPの実行、エラーを起こしやすい手動作業の削減によってシステムのレジリエンスを向上させることができます。AWS Resilience Hub は独自の SOP を定義できるカスタマイズ可能なテンプレートを提供します。

このブログ記事では AWS Resilience Hub のオペレーションに関する推奨事項のテンプレートに基づいてイベントやインシデントに対する SOP の実行を自動化およびテストする方法について説明します。これを CI/CD パイプラインに組み込むことで、障害の検出や復旧が可能かどうかを継続的にテストすることもできます。

SOP の実行を必要とする状況を再現するには、AWS FIS のカオスエンジニアリング手法を使用できます。AWS FIS ではスコープが明確に定義されており、予期しない挙動が発生した場合にはロールバックが可能な安全メカニズムを備えた実験を行うことができます。

前提条件

このブログ記事で使用している例には、いくつかの前提条件があります。

  • AWS Auto Scaling Group の EC2 インスタンスを含むワークロードアーキテクチャ (Figure 1 を参照)
  • AWS Cloud Development Kit (AWS CDK) については、AWS CDK の開始方法 を参照してください
  • AWS Resilience Hub を使用して AWS アカウントにデプロイしたワークロードアーキテクチャを定義し、評価します。AWS Resilience Hub を有効にする方法の詳細については、こちらのブログを参照してください

アーキテクチャ

Figure 1 – このブログで実験対象とするサンプルアーキテクチャ

ワークフロー

Figure 2 – ユーザーが実験を開始してから SOP が自動実行されてアラームが修正されるまでのワークフロー

自動化ソリューション

AWS Resilience Hub は、アラーム、SOP、および FIS 実験に関する推奨事項を提供します。これらオペレーションに関する推奨事項が正常に実装されているかどうかは、お客様の責任においてテストします。AWS Resilience Hub における責任共有モデルの詳細については、ブログ Shared Responsibility with AWS Resilience Hub を参照してください。

重要なリソースの回復を自動化することをお勧めします。このブログでは、特定のアラーム状態に達したときに実装済みの SOP を実行する Amazon EventBridge の自動化について説明します。FIS 実験を使用してこの自動化をテストします。

カオスエンジニアリングはレジリエンス実験の高度なモードで、継続的なレジリエンスパイプラインでの自動実験を含みます。重要な原則は “早く失敗する” ことです。つまりレジリエンスの問題が本番環境で起こる前に、できるだけ早く発見して対処することです。カオス実験を継続的なレジリエンスワークフローに統合することで、レジリエンス実験に対するプロアクティブかつ反復的なアプローチが可能になり、レジリエンスが開発プロセスの不可欠な部分であることを確かにします。

アーキテクチャは Figure 1 に示すように、Relational Database Service (RDS) をバックエンドに持つ Auto Scaling Group (ASG) 内の Amazon Elastic Compute Cloud (Amazon EC2) で実行されるアプリケーションを持ちます。

この例では CPU 使用率が高くなった場合の応答を自動化します。これが役に立つユースケースを考えてみましょう :

e コマース Web アプリケーションは Web サーバーの ASG を min(最小)/desired(希望) = 1、max(最大) = 2 に設定しており、スケーリングポリシーは平均 CPU 使用率によって構成されています。例えばハイシーズンのイベントのようにユーザーからのリクエストが急増した場合、ASG は最大キャパシティである 2 に達しますが、新しいユーザーがまだアプリケーションに接続できないままだとすると、これは十分ではありません。

お客様のオンコールチームが問題を調査し、ASG の最大値を変更するという決定を下すまでには時間的なギャップがあります。この間に新しいユーザーからの接続は途絶え、ビジネスの財務や評判に影響が生じます。SOP を用いてこのメカニズムを自動化することにより、この時間的なギャップを埋めることができます。アラームがトリガーされることでカスタマーチームが追加調査の必要性に気づくことができるからです。

オペレーションに関する推奨事項の実装

AWS Resilience Hub のオペレーションに関する推奨事項の3つの領域 全てについて自動化を実装します。

2 つのアラーム

  • AWSResilienceHub-SyntheticCanaryInRegionAlarm_2021-04-01
  • AWSResilienceHub-AsgHighCpuUtilizationAlarm_2020-07-13

1 つの SOP

  • AWSResilienceHub-ScaleOutAsgSOP_2020-07-01

1 つの FIS 実験

  • AWSResilienceHub-InjectCpuLoadInAsgTest_2021-09-22

オペレーションに関する推奨事項の実装の詳細については、ブログ Measure and Improve Your Application Resilience with AWS Resilience Hub を参照してください。

Amazon EventBridge を使用して自動化を行うために、以下の AWS CloudFormation テンプレートを作成してこのリソースをプロビジョニングしました。この自動化により、複合アラーム ”AsgMaxCapacityReachedAndAsgHighCPUAlarm” がトリガーされて “アラーム中” 状態になったときに、SOP “AWSResilienceHub-ScaleOutAsgSOP_2020-07-01”が開始されます。

AWSTemplateFormatVersion: '2010-09-09'
Description: CloudFormation template for EventBridge rule 'arh-alarm-asg-cpu-triggered'
Parameters:
  AlarmTriggerArn:
    Type: String
    Description: Arn of the Alarm that will trigger this Event
  SSMTemplateAssumeRole:
    Type: String
    Description: An ARN of the role that SSM is going to assume
  SSMTemplateASGName:
    Type: String
    Description: Auto scaling group name (for the SSM Template)

Resources:
  AmazonEventBridgeInvokeStartAutomationExecutionPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      Description: Policy for the Amazon EventBridge Invoke Start Automation Execution
      ManagedPolicyName: !Join ['-', ['AWSResilienceHub-EventBridge_Automation_Policy', !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref "AWS::StackId"]]]]]]
      Path: '/service-role/'
      PolicyDocument:
        !Sub '{ "Version": "2012-10-17", "Statement": [ { "Action": "ssm:StartAutomationExecution", "Effect": "Allow", "Resource": [ "arn:${AWS::Partition}:ssm:${AWS::Region}:*:automation-definition/AWSResilienceHub-ScaleOutAsgSOP_2020-07-01:$DEFAULT" ] }, { "Effect": "Allow", "Action": [ "iam:PassRole" ], "Resource": "${SSMTemplateAssumeRole}", "Condition": { "StringLikeIfExists": { "iam:PassedToService": "ssm.amazonaws.com" } } } ] }'
  AmazonEventBridgeInvokeStartAutomationExecution:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Join ['-', ['AWSResilienceHub-EventBridge_Automation', !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref "AWS::StackId"]]]]]]
      Description: Amazon EventBridge Invoke Start Automation Execution Role
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: events.amazonaws.com
        Version: "2012-10-17"
      MaxSessionDuration: 3600
      Path: '/service-role/'
      ManagedPolicyArns:
        - !Ref AmazonEventBridgeInvokeStartAutomationExecutionPolicy

  EventRuleArhSop:
    Type: AWS::Events::Rule
    Properties:
      EventBusName: default
      EventPattern:
        source:
          - aws.cloudwatch
        detail-type:
          - CloudWatch Alarm State Change
        detail:
          alarmName:
            - !Ref CloudWatchCompositeAlarmAsgMaxCapacityReachedAndAsgHighCPUAlarm
          state:
            value:
              - ALARM
      Name: !Join ['-', ['arh-alarm-asg-cpu-automation', !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref "AWS::StackId"]]]]]]
      State: ENABLED
      Targets:
        - Id: Id5b81de31-a5ef-42e2-90de-1fc8348b3229
          Arn:
            !Sub "arn:${AWS::Partition}:ssm:${AWS::Region}:${AWS::AccountId}:automation-definition/AWSResilienceHub-ScaleOutAsgSOP_2020-07-01"
          RoleArn:
            !GetAtt AmazonEventBridgeInvokeStartAutomationExecution.Arn
          Input:
            !Sub '{"Dryrun":["false"],"AutoScalingGroupName":["${SSMTemplateASGName}"],"AutomationAssumeRole":["${SSMTemplateAssumeRole}"]}'
  CloudWatchAlarmAsgMaxCapacityReached:
    UpdateReplacePolicy: "Retain"
    Type: "AWS::CloudWatch::Alarm"
    Properties:
      ComparisonOperator: "GreaterThanThreshold"
      TreatMissingData: "missing"
      ActionsEnabled: true
      Metrics:
      - Label: "AsgMaxCapacityReached"
        Id: "e1"
        ReturnData: true
        Expression: "IF(m1 >= m2, 1, 0)"
      - ReturnData: false
        MetricStat:
          Period: 120
          Metric:
            MetricName: "GroupInServiceInstances"
            Dimensions:
            - Value: !Ref SSMTemplateASGName
              Name: "AutoScalingGroupName"
            Namespace: "AWS/AutoScaling"
          Stat: "Average"
        Id: "m1"
      - ReturnData: false
        MetricStat:
          Period: 120
          Metric:
            MetricName: "GroupMaxSize"
            Dimensions:
            - Value: !Ref SSMTemplateASGName
              Name: "AutoScalingGroupName"
            Namespace: "AWS/AutoScaling"
          Stat: "Average"
        Id: "m2"
      AlarmName: !Join ['-', ['ARH-AsgMaxCapacityReached', !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref "AWS::StackId"]]]]]]
      EvaluationPeriods: 1
      DatapointsToAlarm: 1
      Threshold: 0
  CloudWatchCompositeAlarmAsgMaxCapacityReachedAndAsgHighCPUAlarm:
    UpdateReplacePolicy: "Retain"
    Type: "AWS::CloudWatch::CompositeAlarm"
    Properties:
      ActionsEnabled: true
      AlarmName: !Join ['-', ['ARH-AsgMaxCapacityReachedAndAsgHighCPUAlarm', !Select [4, !Split ['-', !Select [2, !Split ['/', !Ref "AWS::StackId"]]]]]]
      AlarmRule: !Sub 'ALARM("${CloudWatchAlarmAsgMaxCapacityReached}") AND ALARM("${AlarmTriggerArn}")'

CodeBlock 1 – Amazon EventBridge で自動化をセットアップするための AWS CloudFormation スタック

オペレーションが停止した場合にすぐに復旧できるように、事前に SOP を準備、テスト、評価する必要があります。これには FIS 実験が役立ちます。このシナリオでは AWS Resilience Hub が推奨する SOP を使用しています。また AWS Resilience Hub が推奨する FIS 実験を使用することで、この SOP を実行した場合の仮説検証を行うこともできます。このユースケースでは Auto Scaling キャパシティが最大値に達したときに、Amazon EventBridge rule を通して呼び出される SOP の自動実行もテストします。

仮説

EC2 Auto Scaling と SOP の自動化のおかげで、EC2 インスタンスの CPU 使用率が全体的に高くなった場合でもアプリケーションのパフォーマンスに悪影響をおよぼすことはないと予想します。Web アプリケーションは継続してアクセス可能で、顧客はサービスの利用を中断されることはほとんどありません。

アラーム、SOP、FIS 実験、Amazon EventBridge rule がすべて実装されたら、自動化されていることを実験により確認します。仮説に基づくと、この実験では次のことが確認できるはずです。

  1. FIS 実験では Auto Scaling Group に CPU 負荷を注入します
  2. CloudWatch Alarm “AWSResilienceHub-AsgHighCpuUtilizationAlarm” は “アラーム中” に変わるはずです
  3. Auto Scaling は 負荷を管理するために新しいインスタンスを起動して開始します
  4. FIS 実験は Auto Scaling Group にもう一度 CPU 負荷を注入します
  5. Amazon EventBridge がこのイベントを処理し SOP “AWSResilienceHub-ScaleOutAsgSOP_2020-07-01” を起動します
  6. SOP は Auto Scaling Groupをスケールアウトして EC2 インスタンスを追加します
  7. 実験とSOPの両方が正常に完了します

事前チェック

まず最初に AWS マネジメントコンソールの EC2 セクションで Auto Scaling Group の値とアプリケーションで実行しているインスタンスの数を確認しましょう。

Figure 3 – 元の Auto Scaling Group のキャパシティの値

Figure 4 – 元の 実行中 EC2 インスタンスの数は 1 つ

実験する

ここで上記の仮説をテストするために、AWS Resilienc Hub が推奨する AWS Fault Injection Service (FIS) 実験 “AWSResilienceHub-InjectCpuLoadInAsgTest_2021-09-22” を開始します。

Figure 5 – FIS 実験の実行

CloudWatch コンソールでアラーム “AWSResilienceHub-AsgHighCpuUtilizationAlarm” が “アラーム中” 状態に遷移したことがわかります。これは CPU 使用率が設定されたしきい値を超えたことを示しています。これにより Auto Scaling Group の動的スケーリングがトリガーされ、Auto Scaling Group で 2 つのインスタンスが実行されていることがわかります。

Figure 6 – CloudWatch Alarm の状態が変化

Figure 7 – 2 つの実行中 EC2 インスタンス

Figure 8 – Auto Scaling Group (ASG) の新しい値

実験が終了し、2 つのインスタンスが実行され、アラームが “OK” 状態になりました。

再び同じ実験を実行すると、CloudWatch コンソール画面で CloudWatch Alarm が “アラーム中” 状態に遷移していることがわかります。これは CPU 使用率が設定されたしきい値を超えていることを示しています。さらに、2 番目のアラーム “ARH-AsgMaxCapacityReached” も “アラーム中” 状態になっていることがわかります。これは Auto Scaling Group の最大キャパシティに達したことを示しています。これにより Amazon EventBridge rule が正しく実行されているかどうかを確認できます。このルールは前述のアラームを組み合わせた複合アラームに基づいています(Figure 9 にも表示)。

Figure 9 – CloudWatch Alarm の状態変化 (2 回目の実験)

Figure 10 – Amazon Eventbridge rule が正しくトリガーされている

結果の検証

Amazon EventBridge コンソールのモニタリング タブから、Amazon EventBridge rule がトリガーされ呼び出しが成功していることを確認できます。これにより AWSResilienceHub-ScaleOutAsgSOP_2020-07-01 SOP がターゲットとして自動実行されるはずです。

Systems Manager (SSM) Automations 機能から SOP が正常に完了したことがわかります。Amazon Eventbridge による自動化がなければ、FIS HighCPU 実験からの復旧にはこの SOP を手動で実行することになります。

Figure 11 – 2回目の FIS 実験の後、SOP が正常に実行されている

Auto Scaling Group 自体に新しい値が設定されているか、また現在実行中の EC2 インスタンスの数を確認してみましょう。

Figure 12 – 新しい ASG キャパシティの値

Figure 13 – EC2 インスタンスが追加され合計 3 つになっている

ご覧のとおり、Auto Scaling Group の Desired capacity (希望するキャパシティ) と Maximum capacity (最大キャパシティ) の値が増加しています。これによって期待通り Auto Scaling Group はアプリケーションへインスタンスを追加しました。これは Auto Scaling Group のイベントでも確認できます。Auto Scaling Group アラームと SOP によってそれぞれ追加が行われています。

Figure 14 – Auto Scaling Group イベント

CloudWatch Alarm の履歴を見てどのようなアクションや状態の変化が発生したかを確認することもできます。期待通りに状態が “OK” から “アラーム中” に移行したこと、SOP の実行によりアラームが “OK” に戻ったことを確認することが重要です。

Figure 15 – 実験において CloudWatch Alarm 状態が “OK” から “アラーム中” に変わって “OK” に戻る様子 (左図) と、インスタンスと最大キャパシティの数 (右図)

FIS 実験に戻って、実験が正常に完了したことを確認しましょう。実験を終了し、仮説が完全に立証されたことを確認できます。

Figure 16 – AWS Resilience Hub に完了した実験が表示される

検証

これで当初の仮説と照らし合わせて検証できます。

FIS 実験で ASG に CPU 負荷を注入する

  1. FIS 実験が正常に実行されていることがわかります (Figure 11)。
  2. Amazon CloudWatch Alarm がトリガーされてアラームの状態が変化したことを確認できます (Figure 15)。

CloudWatch Alarm “ASGHighCPUUtilization” が “アラーム中” に変わる

  1. Amazon CloudWatch Alarm がトリガーされてアラーム状態が変化したことを確認できます (Figure 15)。

Amazon EventBridge がこのイベントを処理し、SOP “ScaleOutAsg” を開始する

  1. Amazon EventBridge rule が実行されます (Figure 10)。

最大キャパシティに達した場合に SOP は Auto Scaling Group を スケールアウトしてEC2インスタンスを追加する

Amazon EventBridge rule を実装する AWS CloudFormation スタックを使用して実現した自動化と、必要な変更が SOP で正常に行われたことの両方を仮説に沿って確認します。

  1. SOP 実行の自動化は、手動操作なしで完了した SSM Document で確認できます (Figure 11)。
  2. Auto Scaling Group と EC2 インスタンスの数は期待通りの結果になっています (Figure 12、13、14)。

実験とSOPの両方が正常に完了します

  1. SOP と FIS 実験の完了を確認できます(Figure 16 と Figure 11)。

CI/CD パイプラインでの実行

これを CI/CD パイプラインで実行したい場合は、これらすべてをオーケストレーションする AWS Step Functions を作成できます。ステートマシン図を以下に示します。

Figure 17 – ステートマシン

  1. まず、上記のオートメーションを作成します。
  2. 次に、オートメーションがデプロイされるまで待ちます。
  3. デプロイが成功したら、FIS 実験を開始します。
  4. Amazon EventBridge による自動化によりアラーム発生とAuto Scaling Group の最大キャパシティに基づいて SOP が開始され、問題を軽減します。
  5. エラーが発生すると、Simple Notification Service (SNS) メッセージが送信され、ワークフローは失敗します。
  6. テストがエラーなしで終了すると、成功が報告されます。

この AWS Step Functions を作成する AWS Cloud Development Kit (AWS CDK) のコード。

import * as cdk from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import * as stepfunctions from 'aws-cdk-lib/aws-stepfunctions';

export interface ArhBlogTestImportStackProps extends cdk.StackProps {
}

export class ArhBlogTestImportStack extends cdk.Stack {
  public constructor(scope: cdk.App, id: string, props: ArhBlogTestImportStackProps = {}) {
    super(scope, id, props);

    const iamRoleStepFunctionsRole = new iam.CfnRole(this, 'StepFunctionsRole', {
      path: '/service-role/',
      maxSessionDuration: 3600,
      roleName: 'arh-blog-StepFunctions-role-' + id,
      policies: [
        {
          policyDocument: {
            Version: '2012-10-17',
            Statement: [
              {
                Resource: '*',
                Action: [
                  'cloudformation:CreateStack',
                  'cloudformation:DeleteStack',
                  'cloudformation:DescribeStacks',
                ],
                Effect: 'Allow',
              },
            ],
          },
          policyName: 'cloudformation-permissions',
        },
        {
          policyDocument: {
            Version: '2012-10-17',
            Statement: [
              {
                Resource: '*',
                Action: [
                  'cloudformation:CreateStack',
                  'cloudformation:DeleteStack',
                  'cloudformation:DescribeStacks',
                  "cloudwatch:DescribeAlarms"
                ],
                Effect: 'Allow',
              },
            ],
          },
          policyName: 'cloudwatch-permissions',
        },
        {
          policyDocument: {
            Version: '2012-10-17',
            Statement: [
              {
                Resource: '*',
                Action: [
                  'events:DescribeRule',
                  'events:DeleteRule',
                  'events:PutRule',
                  'events:PutTargets',
                  'events:RemoveTargets',
                ],
                Effect: 'Allow',
              },
            ],
          },
          policyName: 'eventbridge-permissions',
        },
        {
          policyDocument: {
            Version: '2012-10-17',
            Statement: [
              {
                Resource: '*',
                Action: [
                  'fis:StartExperiment',
                  'fis:GetExperiment',
                ],
                Effect: 'Allow'
              },
            ],
          },
          policyName: 'fis-permissions',
        },
        {
          policyDocument: {
            Version: '2012-10-17',
            Statement: [
              {
                Resource: '*',
                Action: [
                  'iam:CreatePolicy',
                  'iam:GetRole',
                  'iam:DetachRolePolicy',
                  'iam:GetPolicy',
                  'iam:CreateRole',
                  'iam:DeleteRole',
                  'iam:AttachRolePolicy',
                  'iam:PutRolePolicy',
                  'iam:PassRole',
                  'iam:ListPolicyVersions',
                  'iam:DeletePolicy',
                ],
                Effect: 'Allow'
              },
            ],
          },
          policyName: 'iam-permissions',
        },
        {
          policyDocument: {
            Version: '2012-10-17',
            Statement: [
              {
                Resource: '*',
                Action: 's3:GetObject',
                Effect: 'Allow',
              },
            ],
          },
          policyName: 's3-permissions',
        },
        {
          policyDocument: {
            Version: '2012-10-17',
            Statement: [
              {
                Resource: '*',
                Action: "sns:Publish",
                Effect: "Allow",
              },
            ],
          },
          policyName: 'sns-permissions',
        },
      ],
      assumeRolePolicyDocument: {
        Version: '2012-10-17',
        Statement: [
          {
            Action: 'sts:AssumeRole',
            Effect: 'Allow',
            Principal: {
              Service: 'states.amazonaws.com',
            },
          },
        ],
      },
    });
    iamRoleStepFunctionsRole.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.RETAIN;

    const stateMachine = new stepfunctions.CfnStateMachine(this, 'StepFunctionsStateMachine', {
      definitionString: '{ \"Comment\": \"A description of my state machine\", \"StartAt\": \"CreateAutomationStack\", \"States\": { \"CreateAutomationStack\": { \"Type\": \"Task\", \"Parameters\": { \"StackName\": \"arh-blog-automation\", \"TemplateURL.$\": \"$.input.S3UrlToCloudformationStack\", \"Capabilities\": [ \"CAPABILITY_NAMED_IAM\", \"CAPABILITY_AUTO_EXPAND\" ], \"Parameters\": [ { \"ParameterKey\": \"AlarmTriggerArn\", \"ParameterValue.$\": \"$.input.AlarmTriggerArn\" }, { \"ParameterKey\": \"SSMTemplateAssumeRole\", \"ParameterValue.$\": \"$.input.SSMTemplateAssumeRole\" }, { \"ParameterKey\": \"SSMTemplateASGName\", \"ParameterValue.$\": \"$.input.SSMTemplateASGName\" } ] }, \"Resource\": \"arn:aws:states:::aws-sdk:cloudformation:createStack\", \"Next\": \"WaitForStackToBeReady\", \"Catch\": [ { \"ErrorEquals\": [ \"States.ALL\" ], \"Next\": \"DeleteAutomationStackOnFail\" } ] }, \"WaitForStackToBeReady\": { \"Type\": \"Wait\", \"Seconds\": 5, \"Next\": \"DescribeStacks\" }, \"DescribeStacks\": { \"Type\": \"Task\", \"Next\": \"StackDeploymentStatus\", \"Parameters\": { \"StackName.$\": \"States.ArrayGetItem(States.StringSplit($.StackId, \'/\'), 1)\" }, \"Resource\": \"arn:aws:states:::aws-sdk:cloudformation:describeStacks\", \"OutputPath\": \"$.Stacks[0]\", \"Catch\": [ { \"ErrorEquals\": [ \"States.ALL\" ], \"Next\": \"DeleteAutomationStackOnFail\" } ] }, \"StackDeploymentStatus\": { \"Type\": \"Choice\", \"Choices\": [ { \"Or\": [ { \"Variable\": \"$.StackStatus\", \"StringEquals\": \"REVIEW_IN_PROGRESS\" }, { \"Variable\": \"$.StackStatus\", \"StringEquals\": \"CREATE_IN_PROGRESS\" } ], \"Next\": \"WaitForStackToBeReady\" }, { \"Variable\": \"$.StackStatus\", \"StringEquals\": \"CREATE_COMPLETE\", \"Next\": \"StartExperiment\" } ], \"Default\": \"DeleteAutomationStackOnFail\" }, \"StartExperiment\": { \"Type\": \"Task\", \"Next\": \"WaitForExperimentToFinish\", \"Parameters\": { \"ClientToken.$\": \"States.UUID()\", \"ExperimentTemplateId.$\": \"$$.Execution.Input.input.ExperimentTemplateId\" }, \"Resource\": \"arn:aws:states:::aws-sdk:fis:startExperiment\", \"ResultPath\": \"$.Result\" }, \"WaitForExperimentToFinish\": { \"Type\": \"Wait\", \"Seconds\": 5, \"Next\": \"GetExperiment\" }, \"GetExperiment\": { \"Type\": \"Task\", \"Next\": \"ExperimentStatus\", \"Parameters\": { \"Id.$\": \"$.Result.Experiment.Id\" }, \"Resource\": \"arn:aws:states:::aws-sdk:fis:getExperiment\", \"ResultPath\": \"$.Result\" }, \"ExperimentStatus\": { \"Type\": \"Choice\", \"Choices\": [ { \"Or\": [ { \"Variable\": \"$.Result.Experiment.State.Status\", \"StringEquals\": \"pending\" }, { \"Variable\": \"$.Result.Experiment.State.Status\", \"StringEquals\": \"initiating\" }, { \"Variable\": \"$.Result.Experiment.State.Status\", \"StringEquals\": \"running\" } ], \"Next\": \"WaitForExperimentToFinish\" }, { \"Variable\": \"$.Result.Experiment.State.Status\", \"StringEquals\": \"completed\", \"Next\": \"Wait\" } ], \"Default\": \"SNSPublishOnError\" }, \"Wait\": { \"Type\": \"Wait\", \"Seconds\": 20, \"Next\": \"StartExperimentAgain\" }, \"StartExperimentAgain\": { \"Type\": \"Task\", \"Next\": \"WaitForExperimentToFinishAgain\", \"Parameters\": { \"ClientToken.$\": \"States.UUID()\", \"ExperimentTemplateId.$\": \"$$.Execution.Input.input.ExperimentTemplateId\" }, \"Resource\": \"arn:aws:states:::aws-sdk:fis:startExperiment\", \"ResultPath\": \"$.Result\" }, \"WaitForExperimentToFinishAgain\": { \"Type\": \"Wait\", \"Seconds\": 5, \"Next\": \"GetExperimentAgain\" }, \"GetExperimentAgain\": { \"Type\": \"Task\", \"Next\": \"ExperimentStatusAgain\", \"Parameters\": { \"Id.$\": \"$.Result.Experiment.Id\" }, \"Resource\": \"arn:aws:states:::aws-sdk:fis:getExperiment\", \"ResultPath\": \"$.Result\" }, \"ExperimentStatusAgain\": { \"Type\": \"Choice\", \"Choices\": [ { \"Or\": [ { \"Variable\": \"$.Result.Experiment.State.Status\", \"StringEquals\": \"pending\" }, { \"Variable\": \"$.Result.Experiment.State.Status\", \"StringEquals\": \"initiating\" }, { \"Variable\": \"$.Result.Experiment.State.Status\", \"StringEquals\": \"running\" } ], \"Next\": \"WaitForExperimentToFinishAgain\" }, { \"Variable\": \"$.Result.Experiment.State.Status\", \"StringEquals\": \"completed\", \"Next\": \"DeleteAutomationStack\" } ], \"Default\": \"SNSPublishOnError\" }, \"SNSPublishOnError\": { \"Type\": \"Task\", \"Resource\": \"arn:aws:states:::sns:publish\", \"Parameters\": { \"TopicArn.$\": \"$$.Execution.Input.input.SnsTopic\", \"Message.$\": \"$\" }, \"Next\": \"DeleteAutomationStackOnFail\" }, \"DeleteAutomationStackOnFail\": { \"Type\": \"Task\", \"Parameters\": { \"StackName\": \"arh-blog-automation\" }, \"Resource\": \"arn:aws:states:::aws-sdk:cloudformation:deleteStack\", \"Next\": \"Fail\" }, \"Fail\": { \"Type\": \"Fail\" }, \"DeleteAutomationStack\": { \"Type\": \"Task\", \"Parameters\": { \"StackName.$\": \"States.ArrayGetItem(States.StringSplit($.StackId, \'/\'), 1)\" }, \"Resource\": \"arn:aws:states:::aws-sdk:cloudformation:deleteStack\", \"Next\": \"Success\" }, \"Success\": { \"Type\": \"Succeed\" } } }',

      loggingConfiguration: {
        includeExecutionData: false,
        level: 'OFF',
      },
      stateMachineName: 'arh-blog-statemachine-' + id,
      roleArn: iamRoleStepFunctionsRole.attrArn,
      tags: [
      ],
      stateMachineType: 'STANDARD',
      tracingConfiguration: {
        enabled: false,
      },
    });
    stateMachine.cfnOptions.deletionPolicy = cdk.CfnDeletionPolicy.RETAIN;
  }
}

CodeBlock 2 – AWS Step Functions を 作成する AWS CDK スタック

上記の AWS CDK コードで作成されるステートマシンを実行するには、いくつかの入力を定義する必要があります。

  • AlarmTriggerArn — Resilience Hub が推奨したアラーム “AsgHighCpuUtilizationAlarm” の ARN
  • SSMTemplateAssumeRole — SOP で作成された “AWSResilienceHubAsgScaleOutAssumeRole” の ARN
  • SSMTemplateASGName — Auto Scaling Group の名前 (ARNではない)
  • ExperimentTemplateId — 実行する FIS 実験の ID (この場合は AsgScaleOut)
  • SnsTopic — 実験が失敗した場合にメッセージを送信する SNS トピック
  • S3UrlToCloudformationStack — Amazon Simple Storage Service (S3) バケット内の CloudFormation ファイルの URL。上記の CodeBlock1 の AWS CloudFormation テンプレートは S3 のフォルダに保存する必要があります

例として、入力は以下のようになります。CDK コードを環境内で正しく機能させるにはこれを更新する必要があります。

{
  "input": {
    "AlarmTriggerArn": "arn:aws:cloudwatch:<region>:<accountid>:alarm:AWSResilienceHub-AsgHighCpuUtilizationAlarm-2020-07-13_arh-demo_arh-lab-workload-AutoScalingGroup-oYSKLDR6Vg21",
    "SSMTemplateAssumeRole": "arn:aws:iam::<accountid>:role/arh-sop-AWSResilienceHubAsgScaleOutAssumeRole-qWqL13hCgexP",
    "SSMTemplateASGName": "arh-lab-workload-AutoScalingGroup-oYSKLDR6Vg21",
    "ExperimentTemplateId": "EXT9Au6P89tSQXa",
    "SnsTopic": "arn of the topics",
    "S3UrlToCloudformationStack": "https://<bucketname>.s3.<region>.amazonaws.com/arh-eventbridge.yml"
  }
}

CodeBlock 3 – AWS CDK 入力(更新が必要)

これで AWS Step Functions が作成されたので、パイプラインに統合できます。こちらのブログ “Continually assessing application resilience with AWS Resilience Hub and AWS CodePipeline” では、AWS Code Pipeline から StepFunctions をトリガーする方法について説明しています。

結論

よく理解され定義されたイベントへの対応を自動化することで、エンジニアはより生産的なタスクに集中できます。これにより例えば平均復旧時間 (MTTR) を改善したり、オンコール対応によるエンジニアリングリソースの疲弊を防ぐことによって、回復力の目標を達成することにもつながります。

リリースの頻度と CI/CD パイプラインのデプロイメント間隔に応じて、カオスパイプラインの範囲と期間を評価する必要があります。一般に Fault Injection Service 実験ではワークロードにおいて十分なインタラクション、データ、および実験条件を確保するために、実行時間を長くする必要があります。開発者の作業が遅くなるのを避けるため、これらの実験は CI/CD パイプラインの後段で、あるいは独自の専用パイプラインで実行する必要があります。通常のデプロイ用 CI/CD パイプラインを使用するか、専用の “カオスパイプライン” を使用するかに関係なく、AWS Resilience Hub の推奨事項は出発点として役立ちます。

本記事は 2024年8月22日に “AWS Cloud Operations & Migrations Blog” で公開された “Automate Standard Operating Procedures (SOPs) execution with AWS Resilience Hub” を翻訳したものです。翻訳はソリューションアーキテクトの三好史隆が担当しました。