AutoScalingで生成したEC2に、予め指定した範囲のプライベートIPアドレスを付与する

#はじめに
AutoScalingはインスタンスの負荷やスケジュールに応じて柔軟に台数を増減させてくれるサービスです。しかしその一方で、プライベートIPアドレスはサブネットのCIDR範囲のアドレスから動的に割り当てられるため、インスタンスがスケールイン・スケールアウトする度にIPが毎回変わってしまいます。
今回はこの問題を解決するために、予め指定したアドレス範囲のプライベートIPアドレスを、AutoScalingで生成したEC2インスタンスに付与する方法を実装してみたいと思います。

#構成図
起動したインスタンスのプライマリENIに紐づくIPアドレスは、起動時にサブネットのCIDR範囲のIPアドレスが自動的に割り当てられます。そこで今回は、予め指定したプライベートIPアドレスをセカンダリENIを用意してプールしておき、インスタンスの起動時に自動的にアタッチされる仕組みを実装していきます。
下記図に処理の流れを示します。
![AutoScaling_newinstance.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/23298e45-542a-de87-bdfe-87474fb3115c.png)
①～④でインスタンスを起動し、その際にユーザーデータを実行（⑤）してセカンダリENIのプールの中から、インスタンスにアタッチされてないENIを取得しインスタンスにアタッチします。

#実装
本実装では、以下ネットワーク構成のサブネットを予め用意していることを前提条件としています。
※実際に実装する際はご利用の環境に合わせて読み替えてください

|Availability Zone|IPv4 CIDRブロック   |
|:-------------------|:------------------|
| ap-northeast-1a    |10.0.0.0/24        |
| ap-northeast-1c    |10.0.1.0/24        |

###①セカンダリENIのプールを作成
まずはインスタンスにアタッチするためのセカンダリENIを作成します。
EC2のマネジメントコンソール画面の左ペインから[ネットワークインターフェイス]を選択し、右上の[ネットワークインターフェイスの作成]ボタンをクリックします。
<img width="800" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/8bf1bae0-87fa-bcd1-bf0a-3e19d0893b0e.jpeg">

今回はわかりやすく「AZ-a_ENI1」と名前を付け、サブネットはAZ-aで作成したパブリックサブネットを指定しています。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/b4256324-f1c1-9ca3-117f-9b4c38cc9bd7.jpeg">

続いてプライベートIPアドレスを入力します。
固定のIPアドレスを割り当てるため、[カスタム]のラジオボタンを選択します。
10.0.0.1~10.0.0.3のIPアドレスはAWSで予約されているため、今回は10.0.0.4のプライベートIPアドレスを設定します。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/0efa0115-3dc5-5dc7-b6a4-0da62534560d.jpeg">

セキュリティグループは内部からのインバウンド接続が許可されているものを選択します。
タグは今回、ENI名を指定する[Name]タグと、ENIをアタッチしたインスタンスのホスト名を指定する[HostName]タグを設定します。（HostNameについては、生成したインスタンスにわかりやすい名前を設定するためのものなので、作成しなくても問題ありません）
最後に[ネットインターフェイスの作成]をクリックすれば、ENIの作成が行われます。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/74bf09e4-27f5-2688-00fe-2245ed4ff78f.jpeg">

作成が成功していれば、「正常に作成されました」のメッセージが表示され、作成したENIが一覧に追加されます。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/f9cc5e03-4aa5-8539-1fcf-44ddfa447a99.jpeg">

あとはスケールアウトする最大数分だけENIを作成します。
今回はAZ-aとAZ-cで合わせて最大6台のインスタンスを起動することとし、6個のENIを作成しました。
作成したENIの設定情報を纏めますと以下の通りです。

|ENI名|IPアドレス|Availability Zone|タグ(キー:Name)|タグ(キー:HostName)|
|:----------------|:---------------|:----------------|:---------------|:---------------|
|AZ-a_ENI1|10.0.0.4/24|ap-northeast-1a|AZ-a_ENI1|server-a1|
|AZ-a_ENI2|10.0.0.5/24|ap-northeast-1a|AZ-a_ENI2|server-a2|
|AZ-a_ENI3|10.0.0.6/24|ap-northeast-1a|AZ-a_ENI3|server-a3|
|AZ-c_ENI1|10.0.1.4/24|ap-northeast-1c|AZ-c_ENI1|server-c1|
|AZ-c_ENI2|10.0.1.5/24|ap-northeast-1c|AZ-c_ENI2|server-c2|
|AZ-c_ENI3|10.0.1.6/24|ap-northeast-1c|AZ-c_ENI3|server-c3|

以上にて、セカンダリENIのプールの作成は完了です。

###②EC2にアタッチするIAMロールの作成
ユーザーデータにてAWS CLIを実行し、セカンダリENIをインスタンスにアタッチするのですが、CLIを実行するための権限が無いとそれが行えません。
そのため、ここではAutoScalingで起動したEC2にアタッチするIAMロールを作成します。

まずはIAMのコンソール画面の左ペインから[ロール]を選択し、右上の[ロールを作成]をクリックします。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/699f2e48-fc58-92c4-473b-f76f4adebdb1.jpeg">

ロールの作成画面に遷移したら、ユースケースの選択で[EC2]を選択し、[次のステップ：アクセス権限]をクリックします。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/59c26368-18ae-d0c6-bba2-983a623d324b.jpeg">

続いてロールにアタッチするアクセス権限ポリシーを選択します。
今回はEC2の変更（ENIのアタッチ）を伴うCLI操作を実行する必要があるので、[AmazonEC2FullAccess]を選択し、[次のステップ：タグ]をクリックします。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/1154d64d-1e16-7733-4ed3-bbe32b021c0a.jpeg">

タグの追加画面では特に何も追加せず、[次のステップ：確認]をクリックします。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/5cae205e-2623-b6ab-5765-43383c921897.jpeg">

最後に確認画面へ遷移するので、ロール名（ここでは「EC2-AutoScaling-Role」とします）を入力し、ポリシーに[AmazonEC2FullAccess]が追加されていることを確認したら[ロールの作成]をクリックします。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/01fce934-7a28-48a2-564b-32cfc1a00f3b.jpeg">

IAMのコンソール画面に作成したロールが表示されていれば完了です。
![WS000020.JPG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/8a86f8df-6376-200a-77f6-39048405fff5.jpeg)

###③AutoScalingの起動設定作成
次にAutoScalingでEC2を展開するための起動設定を行います。

EC2のコンソール画面の左ペインから[起動設定]を選択し、右上の[起動設定の作成]をクリックします。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/a30517cb-eb68-6f67-e26e-3a42b137b4af.jpeg">

起動設定の作成画面に遷移したら、起動設定名（ここでは「EC2-Launch-Configuration」とします）を入力し、起動するインスタンスのAMIとインスタンスタイプを選択します。
本実装では、AMIはAWSが提供する[Amazon Linux 2 AM]を選択し、インスタンスタイプは[t2.micro]としました。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/42f0255b-8b32-9562-640e-0474703bf526.jpeg">

画面をスクロールし、追加設定を設定します。
IAMインスタンスプロファイルは②で作成したロールを選択します。そして[高度な詳細]をクリックして展開します。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/c1064adb-8972-aec7-66f2-3e2f84462f9d.jpeg">

高度な詳細画面では、ユーザーデータの入力を行っていきます。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/9a645e0e-4570-cc19-752a-70ca72551158.jpeg">

入力するユーザーデータのテキストは以下の通りです。

```linux:
#!/bin/bash

function eni_attach() {

INSTANCE_ID=`curl http://169.254.169.254/latest/meta-data/instance-id`
REGION=`curl -s http://169.254.169.254/latest/meta-data/placement/region`
AZ=`curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone`
	
ALLOC_ID=$(/usr/bin/aws ec2 describe-network-interfaces --region=$REGION \
--filters "Name=availability-zone, Values=$AZ" "Name=status, Values=available" \
--query "sort_by(NetworkInterfaces, &PrivateIpAddress)[].NetworkInterfaceId | [0]" \
--output text)

/usr/bin/aws ec2 attach-network-interface --region=$REGION --instance-id=$INSTANCE_ID \
--device-index=1 --network-interface-id=$ALLOC_ID

HOST_NAME=$(/usr/bin/aws ec2 describe-network-interfaces --region=$REGION \
--network-interface-ids $ALLOC_ID --query 'NetworkInterfaces[].TagSet[?Key==`HostName`].Value' \
--output text)

sudo hostnamectl set-hostname $HOST_NAME

}

eni_attach || sudo shutdown -h now
```

上記の内容について、少しずつ解説していきます。

関数の中で最初に記述している下記内容は、CLIを実行するために必要な情報をインスタンスのメタデータにアクセスして取得しています。
それぞれインスタンスID、インスタンスが起動しているリージョン、アベイラビリティゾーンを取得し、変数に代入しています。

```linux:
INSTANCE_ID=`curl http://169.254.169.254/latest/meta-data/instance-id`
REGION=`curl -s http://169.254.169.254/latest/meta-data/placement/region`
AZ=`curl -s http://169.254.169.254/latest/meta-data/placement/availability-zone`
```

続いて下記内容は、作成したENIの情報を取得するコマンドです。
「aws ec2 describe-network-interfaces」コマンドにてJson形式でENI情報を取得し、「--fileter」で条件（インスタンスが起動しているAZ、ステータスがAvailableのENI）を指定してENI情報を絞り込みます。
さらに「--query」にて、ENI情報の中からインスタンスへのアタッチに必要な情報（ネットワークインターフェースID（NetworkInterfaceId））のみ取得を行っています。このとき「sortby()」でプライベートIP順に並び変し、かつ[0]で一番最初の項目を出力しているため、ネットワークインターフェースIDの中でもIPアドレスの値が一番小さいものが一つ選択され、変数（ALLOC_ID）に代入されます。

```linux:
ALLOC_ID=$(/usr/bin/aws ec2 describe-network-interfaces --region=$REGION \
--filters "Name=availability-zone, Values=$AZ" "Name=status, Values=available" \
--query "sort_by(NetworkInterfaces, &PrivateIpAddress)[].NetworkInterfaceId | [0]" \
--output text)
```

そして、「attach-network-interface」コマンドでEC2インスタンスにENIをアタッチしています。
「--devide-index」でインデックス番号が1のデバイスを指定し、「--network-interface-id」で直前に取得したネットワークインターフェースID指定し、インスタンスアタッチします。
（なお、インデックス番号が0のデバイスにはデフォルトでプライマリのENIが割り当てられています）

```linux:
/usr/bin/aws ec2 attach-network-interface --region=$REGION --instance-id=$INSTANCE_ID \ 
--device-index=1 --network-interface-id=$ALLOC_ID
```
続いて下記内容では、ホスト名の設定を行います。
「describe-network-interfaces」の「--network-interface-ids」にて今回取得したENIのネットワークインターフェースIDを指定し、そのインターフェースのタグに紐づくホスト名の取得して変数に代入しています。
そして「hostnamectl」コマンドで取得したホスト名をOSへ反映しています。
（こちらの内容はあくまでホスト名をわかりやすくするための操作なので、実装しなくても問題ありません。）

```linux:
HOST_NAME=$(/usr/bin/aws ec2 describe-network-interfaces --region=$REGION \
--network-interface-ids $ALLOC_ID --query 'NetworkInterfaces[].TagSet[?Key==`HostName`].Value' \
--output text)

sudo hostnamectl set-hostname $HOST_NAME
```

以上がインスタンスに実装する内容になります。
なお下記内容を前後に記述することにより、｛｝で囲まれたコマンドの実行が失敗したらシャットダウンされるようにしています。
これにより、AutoScalingで別の新しいインスタンス起動され、別のインスタンスで再度コマンドの実行を試みることが出来ます。

```
function eni_attach() {
・・・
}

eni_attach || sudo shutdown -h now
```
以上の内容を、高度な詳細画面のユーザーデータに入力します。
またENI情報の取得はインターネット経由で行われますので、[すべてのインスタンスにパブリックIPアドレスを割り当てます。]のラジオボタンにチェックを入れます。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/db50d4b8-c922-c8c7-58bb-fcfd985133fc.jpeg">

さらに画面をスクロールし、セキュリティグループとキーペアの設定を行います。
セキュリティグループは内部からアクセス可能なものを選択し、キーペアはインスタンスへのアクセスに利用できるものを選択します。
そして注意事項にチェックを付けた後、[起動設定の作成]をクリックします。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/de3dc812-3dee-2998-e904-4896c57629f8.jpeg">

「正常に作成されました」のメッセージが表示され、作成した起動設定が追加されていれば成功です。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/45026489-a598-e39d-e0c5-07f8b893982f.jpeg">

###④AutoScalingグループの作成
最後にAutoScalingグループの作成を行います。

EC2のコンソール画面の左ペインから[Auto Scaling グループ]を選択し、右上の[Auto Scaling グループの作成]をクリックします。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/977fbbab-c008-c078-ba94-e544ab4fe134.jpeg">

遷移した画面にてAuto Scalingグループ名（ここでは「EC2-Auto-Scaling」とします）を入力します。
さらに起動テンプレートから起動設定に切り替えて、作成した起動設定を選択し、[次へ]をクリックします。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/976ae207-1fd6-242e-2669-a441cd77fed7.jpeg">

次にネットワークの設定を行います。
インスタンスを起動するVPCとアベイラビリティゾーン（セカンダリENIのプールを作成したゾーン）を設定し、[次へ]をクリックします。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/39467810-6b03-4171-dddf-49bfcf8fa5c7.jpeg">

詳細オプションの設定画面に遷移します。
今回ロードバランサーは作成していないためロードバランシングの項目の設定はせず、その他の項目も特に設定はせずに[次へ]をクリックします。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/7cdbd895-73a9-4a4e-cc9d-fd4330c73565.jpeg">

グループサイズの設定を行います。
今回は希望する容量2、最小キャパシティ2、最大キャパシティ6とします。
そしてスケーリングポリシーは今回設定せず、[次へ]をクリックします。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/bb2b05e0-a959-46e5-cc0d-ebd421501cf2.jpeg">

通知の追加画面では特に設定せず、[次へ]をクリックします。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/172f072f-b8f3-1696-1802-6b464936064f.jpeg">

タグを追加画面でも特に設定せず、[次へ]をクリックします。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/0888fd23-977e-5181-87e5-fb7e06650abf.jpeg">

最後に確認画面にて、ここまで設定した通りの内容が設定されていることを確認したら、[Auto Scaling グループを作成]をクリックします。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/346b1d64-c26f-392d-bae7-fd9824a74834.jpeg">

「正常に作成されました」のメッセージが表示され、作成したAutoScalingグループが追加されていれば成功です。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/0b0fb24c-0194-6d89-0786-23c2fdf8530f.jpeg">

しばらくすると、AZ-aとAZ-cに1つずつインスタンスが立ち上がってくることが確認できます。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/cd21a708-e304-4812-1c5d-a95712ddda73.jpeg">

また各インスタンスのネットワーキング情報を確認すると、セカンダリENIのプライベートIPアドレスが作成できていることが確認できます。

【AZ-aのインスタンス】
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/8aeadd4b-98dd-13f8-8a57-672b0dbcb9dc.jpeg">

【AZ-cのインスタンス】
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/37cd6313-4172-52e9-073d-00d078dd3294.jpeg">

そして各インスタンスにセカンダリENIのプライベートIPアドレスを指定してSSH接続を試みると、問題なくログインすることができ、またENIに紐づくホスト名がインスタンスに設定されていることを確認できます。

【AZ-aのインスタンス】
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/c535c0fb-0f3e-d067-5a4b-8aca7d916aff.jpeg">
【AZ-cのインスタンス】
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/5201dab0-c764-d26f-6672-8998ad95d99a.jpeg">
以上にて、全設定は完了となります。

#動作確認
試しにスケールアウトで新たにインスタンスを起動した際に、ENIがちゃんとアタッチされ、ログインができるか確認してみます。

[Auto Scaling グループ]から今回作成したグループを選択し、[編集]をクリックします。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/42b91f03-a620-f4d3-dd5f-fa3b11446979.jpeg">

グループサイズの希望する容量を2→3に変更し、[更新]ボタンをクリックします。
<img width="450" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/aecd960d-efcf-d584-675e-626c3fe2b34f.jpeg">

すると新しいインスタンスが立ち上がってくることが確認できます。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/6e45e897-b080-ac9b-6317-9aac9c7587ea.jpeg">

起動されたインスタンスのネットワーキング情報を確認してみると、ちゃんとセカンダリENIのプライベートIPアドレスがアタッチされていることが確認できました。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/3959b571-d7c5-7454-5acc-dd35688bf8ed.jpeg">

さらにセカンダリENIのプライベートIPアドレスを指定してSSH接続を試みると、問題なくログインすることができ、またENIに紐づくホスト名がインスタンスに設定されていました。
<img width="550" alt="WS0002.JPG" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/689573/1aa471c6-3bdd-e9bd-3991-17ca6af0eb2b.jpeg">

#おわりに
AutoScalingを活用している際に、プライベートIPアドレスが毎回変わり固定でアクセスできず困っていたので、いろいろと調べて検証しているうちに本実装に辿り着きました。
本記事の内容が同じように困っている方の一助となれば幸いです。