はじめに
現場で、オンプレの内部でIPアドレスを変更する仕組みを持つメールサーバをAWSのEC2に移行する際、大きな問題点がありましたので解決策とともに紹介します。
対象読者
- AWSを使用する現場に従事している方
- AWSの資格勉強をしている方
- AWSに興味がある方
1. 問題点
AWSでは内部でのIPアドレス変更に対応できない
内部でプライベートIPアドレスを変更した場合でも、AWS側のネットワークインターフェースのIPアドレスは変わらないのでインスタンス同士が通信できなくなります。
今回の現場では、複数のEC2インスタンスでクラスターを作り、ソフトウェアのIPアドレス変更で障害時に自動でフォールバックするようなシステムにて問題が起きました。
2. 解決策
CloudWatchとLambdaを使用して対応
AWSでは内部でのIPアドレス変更を検知できないので、内部ソフトウェアのトリガーとなるものをAWS側(CloudWatch)で監視し、Lambdaを使ってIPアドレスを付け替える必要がありました。
3. CloudWatch
今回のソフトウェアの場合、プライマリサーバーの停止をトリガーにすればよかったのでプライマリサーバーの状態を監視するEventBridgeルールを作成しました。
{
"source": ["aws.ec2"],
"detail-type": ["EC2 Instance State-change Notification"],
"detail": {
"state": ["stopped", "terminated"],
"instance-id": ["アクティブサーバー用EC2インスタンスのインスタンスID"]
}
}
3-1. sourceフィールド
"source": ["aws.ec2"]
ここでイベントのソースを指定します。この場合、Amazon EC2からのイベントを監視しています。
3-2. detail-type:
"detail-type": ["EC2 Instance State-change Notification"]
ここで特定のイベントタイプを指定します。ここでは、EC2インスタンスの状態変更に関する通知を監視しています。
3-3. detail:
このフィールドはイベントの詳細をフィルタリングするために使用されます。
3-3-1. state:
"state": ["stopped", "terminated"]
監視するインスタンスの状態を指定します。ここでは、インスタンスが「stopped(停止)」または「terminated(終了)」状態になったときルールがトリガーされるようにしています。
3-3-2. instance-id:
"instance-id": ["アクティブサーバー用EC2インスタンスのインスタンスID"]
特定のEC2インスタンスを指定します。このフィールドには監視対象のインスタンスIDが入ります。このインスタンスIDに一致するインスタンスのみが対象となります。
4. Lambda
CloudWatchをトリガーに起動するLambdaを作成しました。
SNSでの通知もLambdaで行います。
import boto3
ec2 = boto3.client('ec2')
sns = boto3.client('sns')
def lambda_handler(event, context):
# アクティブサーバーのセカンダリ IP アドレスを解放
ec2.unassign_private_ip_addresses(
NetworkInterfaceId='アクティブサーバーのインターフェースID',
PrivateIpAddresses=['10.0.0.5'] # セカンダリIPアドレスを設定
)
# セカンダリサーバーにセカンダリ IP アドレスをアタッチ
ec2.assign_private_ip_addresses(
NetworkInterfaceId='セカンダリサーバーのインターフェースID',
PrivateIpAddresses=['10.0.0.5'] # セカンダリIPアドレスを設定
)
# メール本文用の変数を設定
str1 = "アクティブサーバーの停止が検出されました。"
str2 = "セカンダリサーバーにIPアドレスを付け替える処理を実行しました。"
# %sは文字列指定子(str1,2,...と順に改行されて挿入される)
message1 = "%s \n %s " %(str1,str2)
response = sns.publish(
# SNSトピックのARN
TopicArn = '作成したSNSトピックのARNを設定',
# 件名
Subject = 'フェイルオーバーが起動されました。',
# 本文
Message = message1,
)
return response
4-1. インポートとクライアントの設定
import boto3
ec2 = boto3.client('ec2')
sns = boto3.client('sns')
import boto3
でAWS SDK for Pythonをインストールします。
boto3.client('ec2')
でEC2サービスにアクセスするためのクライアントを作成します。
boto3.client('sns')
でSNSサービスにアクセスするためのクライアントを作成します。
4-2. Lambda関数の定義
def lambda_handler(event, context):
lambda_handler
はAWS Lambda関数のエントリーポイントです。イベントが発生したときにこの関数が呼び出されます。
引数のevent
はイベントデータを含む辞書、context
は実行時情報を提供するオブジェクトです。
4-3. アクティブサーバーのセカンダリIPアドレスを解放
ec2.unassign_private_ip_addresses(
NetworkInterfaceId='アクティブサーバーのインターフェースID',
PrivateIpAddresses=['10.0.0.5'] # セカンダリIPアドレスを設定
)
unassign_private_ip_addresses
メソッドは、指定されたネットワークインターフェースからプライベートIPアドレスを解放します。
変数NetworkInterfaceId
には、アクティブサーバーのネットワークインターフェースのIDを設定します。
変数PrivateIpAddresses
は解除するプライベートIPアドレスのリストです。
4-4. セカンダリサーバーにセカンダリIPをアタッチ
ec2.assign_private_ip_addresses(
NetworkInterfaceId='セカンダリサーバーのインターフェースID',
PrivateIpAddresses=['10.0.0.5'] # セカンダリIPアドレスを設定
)
assign_private_ip_addresses
メソッドは、指定されたネットワークインターフェースにプライベートIPアドレスを割り当てます。
NetworkInterfaceId
には、セカンダリサーバーのネットワークインターフェースのIDを設定します。
PrivateIpAddresses
は割り当てるプライベートIPアドレスのリストです。
4-5. SNS通知の設定と送信
str1 = "アクティブサーバーの停止が検出されました。"
str2 = "セカンダリサーバーにIPアドレスを付け替える処理を実行しました。"
message1 = "%s \n %s " %(str1,str2)
response = sns.publish(
TopicArn = '作成したSNSトピックのARNを設定',
Subject = 'フェイルオーバーが起動されました。',
Message = message1,
)
変数str1
とstr2
には通知メッセージの内容を設定しています。
変数message1
にはstr1
とstr2
を組み合わせ、最終的なメッセージを設定します。
sns.publish
メソッドは、SNSトピックにメッセージを送信するメソッドです。
TopicArn
にはSNSトピックのARN(Amazon Resource Name)を設定します。
Subject
には通知の件名を設定します。
Message
は通知の本文です。
4-6. 応答の返却
return response
これでsns.publish
メソッドの応答を返します。ロギングやデバッグで使用します。
4-7. まとめ
以上のようなスクリプトを作成することで、内部IPアドレスを変更する仕組みを持ったソフトウェアもAWSに移行することができます。
現場でも、このような仕組みでシステムを実際にお客様にも使用していただいております。
実際に自分が作ったシステムを、お客様に使用していただいているのを見るととてもうれしく思います。
おわりに
Lambdaは万能
今回は、EC2内部でIPアドレスを変更するソフトウェアの移行を紹介しました。
Lambdaを実務で使ったのは初めてですが、このような特殊なパターンにも対応できる万能なサービスだと感じました。
今後も機会があればLambdaをどんどん使っていきたいと思いました。
基本的にはIPアドレスはAWS側で管理するのが好ましいですが、今回のソフトウェアのような特殊なものを移行する際には、参考にしていただければ幸いです。