概要
AutoScalingするWebサーバーにCMSサーバーからコンテンツ同期を行っている環境があり、ELBに追加する前にコンテンツ同期が終わっているか確認したかったのでAutoScalingライフサイクルフックでStepFunctionsを呼び出して外部公開前にコンテンツ同期のチェックとライフサイクルアクションを完了させるようにしてみました。
構成図
実装
AutoScaling
コマンドもしくはコンソールからインスタンスの起動 autoscaling:EC2_INSTANCE_LAUNCHING
なライフサイクルフックを追加します。
https://docs.aws.amazon.com/ja_jp/autoscaling/ec2/userguide/lifecycle-hooks.html#adding-lifecycle-hooks
Lambda
コンテンツの同期チェックとライフサイクルフックを完了させるLambdaFunctionを作成します。外部にはELB経由で公開しているので、LambdaはVPC内で起動しインスタンスのプライベートIPに確認しに行きます。
確認するURLはスクリプトを使いまわせるようにLambdaの環境変数に書いています。
# -*- coding: utf-8 -*- import boto3 import hashlib import os import requests ec2 = boto3.resource('ec2') autoscaling = boto3.client('autoscaling') def lambda_handler(event, context): """ AutoScalingのCloudWatch Events[EC2 Instance-launch Lifecycle Action]から起動。 コンテンツの同期を確認、完了していればLifecycle Actionを完了させELBに組み込む。 """ print(event) try: instance_id = event['detail']['EC2InstanceId'] instance = ec2.Instance(instance_id) host = os.environ['Host'] headers = {'Host': host} origin_req = {'timeout': 5} replica_req = {'timeout': 5, 'headers': headers} # TOPページをチェック origin_top = requests.get('https://{}/'.format(host), **origin_req) replica_top = requests.get('http://{}/'.format(instance.private_ip_address), **replica_req) # TOPページのコンテンツを比較 top_result = check_content(origin_top, replica_top) if top_result is True: # Lifecycle Actionを完了 print('Content sync is complete.') response = autoscaling.complete_lifecycle_action( LifecycleHookName=event['detail']['LifecycleHookName'], AutoScalingGroupName=event['detail']['AutoScalingGroupName'], LifecycleActionResult='CONTINUE', InstanceId=instance_id ) print(response) else: class ContentUnsyncException(Exception): """ StepFunctionに認識させるコンテンツが同期が完了していない例外 """ pass raise ContentUnsyncException('top_result:{} pre_result:{}'.format(top_result, pre_result)) except Exception as e: print(e) raise e def check_content(origin, replica): origin_hash = hashlib.sha256(origin.text.encode('utf-8')).hexdigest() replica_hash = hashlib.sha256(replica.text.encode('utf-8')).hexdigest() return origin_hash == replica_hash
IAM Roleは AWSLambdaVPCAccessExecutionRole
AmazonEC2ReadOnlyAccess
とautoscaling:CompleteLifecycleAction
あたりを行えるよう許可します。
StepFunctions
Lambdaを呼び出すステートマシンを作成します。
コンテンツ同期に少し時間がかかるのでリトライを設定しています。
{ "Comment": "Check the sync status of content at instance startup.", "StartAt": "CheckContent", "States": { "CheckContent": { "Type": "Task", "Resource": "arn:aws:lambda:${region}:${account_id}:function:check_content", "Retry": [ { "ErrorEquals": [ "ContentUnsyncException", "States.TaskFailed", "States.Timeout" ], "IntervalSeconds": 30, "MaxAttempts": 10, "BackoffRate": 1.0 } ], "End": true } } }
IAM Roleは lambda:InvokeFunction
を許可します。
CloudWatchイベント
CloudWatchイベントでAutoScalingのライフサイクルアクションからステートマシンを呼び出すよう設定します。
{ "detail": { "AutoScalingGroupName": [ "${autoscalinggroupname}" ] }, "detail-type": [ "EC2 Instance-launch Lifecycle Action" ], "source": [ "aws.autoscaling" ] }
IAM Roleは states:StartExecution
を許可します。
StepFunctionsとCloudWatchはコンソールから作成するとRoleもいい感じのが作成されるかと思います。
実行結果
スケールアウトが発生するとスケールアウトした数だけStepFunctionが動いてコンテンツ同期状況をチェックしてくれます。
参考
- Amazon EC2 Auto Scaling ライフサイクルフック
- Amazon VPC 内のリソースにアクセスできるように Lambda 関数を構成する
- CloudWatch イベント を使用してステートマシンの実行を開始する
- Automating AWS Lambda Function Error Handling with AWS Step Functions