はじめに
最近Autoscalingグループで稼働するWebサーバーに対して、アプリケーションの更新を自動化する機会があったので実装した機能を紹介させていただきます。
背景
以下の図のような、よくある構成でWebサーバーをホストする環境を構築しました。
- Application Load Balancer配下に、常時2台稼働するEC2にWebアプリケーションをホストしている。
- アプリケーションの更新用に、ALBには紐づかないEC2が1台存在する。
上記の構成において、これまではアプリケーション更新用のEC2で更新・テストが完了したら以下の手順で、手動でAutoscalingグループのEC2を入れ替えていました。
- アプリケーション更新用EC2を停止する
- このEC2からAMIを作成する
- 作成したAMIを起動テンプレートの新しいバージョンに設定する
- AutoScalingグループでEC2の更新を実行する
しかし、アプリケーションの更新が多く、毎回手動で更新すると時間と手間がかかりすぎるため、今回はLambdaとEventBridgeを使ってAutoscalingグループの更新を自動化しました。
自動化の構成
今回は手動で行っていた手順のうち、以下の3つの手順を自動化しました。
- AMIの作成
- 起動テンプレートの新しいバージョンを設定
- AutoScalingグループのEC2を更新
一連の流れはLambda関数を使用して実行しました。また、EventBridgeを利用してアプリケーション更新用EC2の停止を契機としてLambda関数を呼び出すようにしました。
Lambda関数のコード
自動化に成功したLambda関数のコードを記述します。boto3をインポートしてAWSのリソースを操作できるようにしました。
import boto3
import time
import os
ec2_client = boto3.client('ec2')
autoscaling_client = boto3.client('autoscaling')
def lambda_handler(event, context):
# ① EC2インスタンスAの新しいAMIを作成する
instance_id = os.environ['InstanceId'] # EC2インスタンスのID
ami_name = f"new-ami-{int(time.time())}"
create_image_response = ec2_client.create_image(
InstanceId=instance_id,
Name=ami_name,
NoReboot=True
)
ami_id = create_image_response['ImageId']
print(f"Created AMI ID: {ami_id}")
# AMIが利用可能になるまで待機
waiter = ec2_client.get_waiter('image_available')
waiter.wait(ImageIds=[ami_id])
print(f"AMI {ami_id} is now available.")
# ② 作成したAMIを起動テンプレートの新しいバージョンに設定する
launch_template_id = os.environ['LaunchTemplateId'] # 起動テンプレートのID
create_launch_template_version_response = ec2_client.create_launch_template_version(
LaunchTemplateId=launch_template_id,
VersionDescription='New version with updated AMI',
LaunchTemplateData={
'ImageId': ami_id,
'InstanceType': 't2.micro',
'SecurityGroupIds': ['sg-xxxxx'],
'IamInstanceProfile': {
'Name': 'ec2_instanceprofile'
}
},
)
new_version_number = create_launch_template_version_response['LaunchTemplateVersion']['VersionNumber']
print(f"Created new launch template version: {new_version_number}")
# 起動テンプレートの最新バージョンをデフォルトバージョンとして設定
ec2_client.modify_launch_template(
LaunchTemplateId=launch_template_id,
DefaultVersion="$Latest"
)
# ③ AutoScalingグループで、起動テンプレートに新しく設定したバージョンからEC2インスタンスを新しく起動させる
auto_scaling_group_name = os.environ['AutoScalingGroupName'] # AutoScalingグループの名前
# インスタンスリフレッシュを開始
start_instance_refresh_response = autoscaling_client.start_instance_refresh(
AutoScalingGroupName=auto_scaling_group_name,
Strategy='Rolling'
)
instance_refresh_id = start_instance_refresh_response['InstanceRefreshId']
print(f"Started instance refresh with ID: {instance_refresh_id}")
return {
'statusCode': 200,
'body': f"AMI {ami_id} created, launch template version {new_version_number} created, and instance refresh {instance_refresh_id} started."
}
コードのポイント
Autoscalingグループを更新する運用手順の自動化に加えて、コード内で工夫したポイントを説明します。
-
AMIが利用可能になるまで次の処理を待機させる
→AMIのステータスが更新中の時にAutoscalingグループがEC2を起動するのを防ぐため、AMIが使用可能になるまで待機する処理を加えています。 -
新しく設定した起動テンプレートをデフォルトバージョンとして設定
→デフォルトバージョンは削除できないなどの制約があり、最新バージョンをデフォルトバージョンとしても設定するほうが何かと都合が良いため、このような処理を追加しました。
おわりに
今回はAutoscalingグループで稼働するWebサーバーの更新を自動化するために、LambdaとEventBridgeを利用したシンプルな構成を実装しました。Lambda関数のコードさえかけてしまえば、それ以外の設定はシンプルで簡単に完了できるかと思います。Autoscalingグループの更新を考えている方がいらっしゃれば、本投稿の構成を参考にしていただけますと幸いです。