【AWS】EC2内部でIPアドレスを変更するソフトウェアへの対応方法

# はじめに
　現場で、オンプレの**内部でIPアドレスを変更する仕組みを持つメールサーバ**をAWSのEC2に移行する際、大きな問題点がありましたので解決策とともに紹介します。


# 対象読者

- AWSを使用する現場に従事している方
- AWSの資格勉強をしている方
- AWSに興味がある方


# 1. 問題点

### **AWSでは内部でのIPアドレス変更に対応できない**
内部でプライベートIPアドレスを変更した場合でも、AWS側のネットワークインターフェースのIPアドレスは変わらないのでインスタンス同士が通信できなくなります。

今回の現場では、複数のEC2インスタンスでクラスターを作り、ソフトウェアのIPアドレス変更で障害時に自動でフォールバックするようなシステムにて問題が起きました。


# 2. 解決策

### **CloudWatchとLambdaを使用して対応**
AWSでは内部でのIPアドレス変更を検知できないので、内部ソフトウェアのトリガーとなるものをAWS側(CloudWatch)で監視し、Lambdaを使ってIPアドレスを付け替える必要がありました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3585159/67e87cb6-7a81-6d34-3ac9-b0fcddc093a2.png)


# 3. CloudWatch

今回のソフトウェアの場合、プライマリサーバーの停止をトリガーにすればよかったのでプライマリサーバーの状態を監視するEventBridgeルールを作成しました。

```
{
    "source": ["aws.ec2"],
    "detail-type": ["EC2 Instance State-change Notification"],
    "detail": {
        "state": ["stopped", "terminated"],
        "instance-id": ["アクティブサーバー用EC２インスタンスのインスタンス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": ["アクティブサーバー用EC２インスタンスのインスタンス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側で管理するのが好ましい**ですが、今回のソフトウェアのような特殊なものを移行する際には、参考にしていただければ幸いです。