📝

ALBのリスナールールを自動で切り替えるLambda関数

2021/06/23に公開

SetRulePriorities APIを使用して、ALBのリスナールールを自動で切り替える関数です。
CloudFormationからSNS経由で通知を受け取り、CloudFormationによる更新中はメンテンナンス画面を表示し、更新が完了したらEC2のサイトを表示するというルールの切り替えを行います。

概要

  • ランタイム:Node 14.x
  • アクセス権限:
    • AWSLambdaBasicExecutionRole
    • ELB v2 SetRulePriorities(カスタムポリシーで選択する)
  • 環境変数
    • LogicalResourceId : 更新するCloudFormationスタック名
    • ForwardRuleArn : ターゲットグループへの転送ルールのARN
    • MaintenanceRuleArn : メンテナンス用の固定レスポンスを返すルールのARN

ソース

const AWS = require('aws-sdk');
AWS.config.update({ region: 'ap-northeast-1' });
AWS.config.apiVersions = {
  elbv2: '2015-12-01',
  // other service API versions
};
const elbv2 = new AWS.ELBv2();

exports.handler = async (event) => {
  console.log(JSON.stringify(event, null, 2));
  const messages = event.Records[0].Sns.Message.split('\n');

  //リソースの論理IDを取得
  const logicalResourceId = messages
    .find((message) => {
      return message.includes('LogicalResourceId');
    })
    .replace(/LogicalResourceId=|\'/g, '');
  console.log(logicalResourceId);

  if (logicalResourceId !== process.env.LogicalResourceId) {
    return;
  }

  //更新状況を取得
  const resourceStatus = messages
    .find((message) => {
      return message.includes('ResourceStatus=');
    })
    .replace(/ResourceStatus=|\'/g, '');
  console.log(resourceStatus);

  let params = '';

  if (resourceStatus === 'UPDATE_IN_PROGRESS') {
    params = {
      RulePriorities: [
        /* required */
        {
          Priority: 1,
          RuleArn: process.env.MaintenanceRuleArn,
        },
        {
          Priority: 2,
          RuleArn: process.env.ForwardRuleArn,
        },
        /* more items */
      ],
    };
  } else if (
    resourceStatus === 'UPDATE_COMPLETE' ||
    resourceStatus === 'UPDATE_ROLLBACK_COMPLETE'
  ) {
    params = {
      RulePriorities: [
        /* required */
        {
          Priority: 1,
          RuleArn: process.env.ForwardRuleArn,
        },
        {
          Priority: 2,
          RuleArn: process.env.MaintenanceRuleArn,
        },
        /* more items */
      ],
    };
  } else {
    return;
  }
  try {
    const result = await elbv2.setRulePriorities(params).promise();
    console.log(JSON.stringify(result, null, 2));
  } catch (err) {
    console.log(err);
  }
};

eventの中身

CloudFormationからSNS経由で飛んでくる情報はeventに入っていますが、実際には以下のような情報が入っています。

{
  "Records": [
    {
      "EventSource": "aws:sns",
      "EventVersion": "1.0",
      "EventSubscriptionArn": "arn:aws:sns:ap-northeast-1:467964259659:Study_Topic_NotifyCloudFormationEvent:986df782-8a51-45e7-b523-9bd396c5ebf3",
      "Sns": {
        "Type": "Notification",
        "MessageId": "d255cf45-5bc0-5286-af38-97b50ae294b0",
        "TopicArn": "arn:aws:sns:ap-northeast-1:467964259659:Study_Topic_NotifyCloudFormationEvent",
        "Subject": "AWS CloudFormation Notification",
        "Message": "StackId='arn:aws:cloudformation:ap-northeast-1:467964259659:stack/StudyRelease/2fc47fe0-8ab6-11eb-b4b5-0e9ed3f1b3df'\nTimestamp='2021-04-14T02:09:38.195Z'\nEventId='77321280-9cc6-11eb-bae0-0a1871fe28bb'\nLogicalResourceId='StudyRelease'\nNamespace='467964259659'\nPhysicalResourceId='arn:aws:cloudformation:ap-northeast-1:467964259659:stack/StudyRelease/2fc47fe0-8ab6-11eb-b4b5-0e9ed3f1b3df'\nPrincipalId='AROAWZ5GX5VF6AF2NRDU6:tnet_admin_119503'\nResourceProperties='null'\nResourceStatus='UPDATE_ROLLBACK_COMPLETE'\nResourceStatusReason=''\nResourceType='AWS::CloudFormation::Stack'\nStackName='StudyRelease'\nClientRequestToken='Console-ExecuteChangeSet-6ed7afc4-c02a-bac3-48e8-dea1b25a96b9'\n",
        "Timestamp": "2021-04-14T02:09:38.250Z",
        "SignatureVersion": "1",
        "Signature": "JrKCtXJEL6aNAZStjXVRfFYHEHYPM63zVPgAf929WKD+2AlB3YUuMc4ZtFg4ly2DDN+ilOzCcCAs5i+fUaGDF8dkSmQS34L7ikCmJPVVXrabam8yZ8CP9lcn2bbTUCYCb75ec9cdDqE3c0Snp6fCKB/ZgkMMCV7w4TTdMzNAHyldjseWu/1goxq91q6X9dOcSYVoV5GuNrI0kQOfRKJQOguWCYV8Ze7uXFJ5frmUrcD/xb3eRNnzCNjAe3pvTqNPVU6RgLO2fIfTq6Qlo+NSweQZ7niJvrEQ+X+uugnWAoFKz7tqO+mehsifFeS2K/oJDvz9XmOC0EAOWF3rDgukuw==",
        "SigningCertUrl": "https://sns.ap-northeast-1.amazonaws.com/SimpleNotificationService-010a507c1833636cd94bdb98bd93083a.pem",
        "UnsubscribeUrl": "https://sns.ap-northeast-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:ap-northeast-1:467964259659:Study_Topic_NotifyCloudFormationEvent:986df782-8a51-45e7-b523-9bd396c5ebf3",
        "MessageAttributes": {}
      }
    }
  ]
}

処理の最初の方で、Messageから必要な情報のみを取得しています。

CloudFormationからの通知内容の判定

CloudFormationからはスタック自体の更新情報の他にもリソースごとの更新情報も飛んできます。
この関数の処理の目的は、
「スタックの更新が開始されたらメンテナンス画面を表示し、スタックの更新が完了したらサイトを表示するようルールの順序を変更する」
ことなので、必要な情報はスタック名とスタックの更新状況の2つです。
まずはスタック名が含まれているかを判定します。

  if (logicalResourceId !== process.env.LogicalResourceId) {
    return;
  }

続いて更新状況の判定です。
更新中(UPDATE_IN_PROGRESS)ならメンテ画面を表示するルールを1に設定します。
更新完了(UPDATE_COMPLETE)またはロールバック完了(UPDATE_ROLLBACK_COMPLETE)ならサイトを表示するルールを1に設定します。

if (resourceStatus === 'UPDATE_IN_PROGRESS') {
    params = {
      RulePriorities: [
        /* required */
        {
          Priority: 1,
          RuleArn: process.env.MaintenanceRuleArn,
        },
        {
          Priority: 2,
          RuleArn: process.env.ForwardRuleArn,
        },
        /* more items */
      ],
    };
  } else if (
    resourceStatus === 'UPDATE_COMPLETE' ||
    resourceStatus === 'UPDATE_ROLLBACK_COMPLETE'
  ) {
    params = {
      RulePriorities: [
        /* required */
        {
          Priority: 1,
          RuleArn: process.env.ForwardRuleArn,
        },
        {
          Priority: 2,
          RuleArn: process.env.MaintenanceRuleArn,
        },
        /* more items */
      ],
    };
  } else {
    return;
  }

ALBへ設定変更リクエスト

リクエストにはSetRulePriorities APIを使用します。
パラメーターの詳細はドキュメントをご覧ください。
この関数では優先順位であるPriorityとALBリスナールールのARNであるRuleArnを設定しています。

まとめ

今回はALBのリスナールールを自動で切り替える関数を紹介しました。
現状はCloudFormationの通知機能から発火させていますが、他にもEventBridgeのスケジュール実行や、フロントエンドからボタンでの切り替えなどもできると思うので、参考にして頂ければ幸いです。

参考資料

SetRulePriorities - Elastic Load Balancing

Discussion