はじめに
本稿では、2025.10.15 のブログ オンプレの仮想IP冗長構成とAWSの設計思想はなぜ合わないのか?移行時の選択肢を解説 でご紹介した代替策の 1 つ、Amazon VPC で設定する CIDR の範囲外のプライベートIPアドレスをルーティングで活用する方式を取り上げます。
この方式では、特定のプライベートIPアドレス(本稿では 仮想IP とします)宛ての通信を、VPC のルートテーブルで 特定の Amazon EC2 インスタンスへ誘導します。
さらに、状況に応じてそのルートのターゲットを切り替えることで、固定のプライベートIPアドレスで通信し続けながら、高可用性確保 が図れます。
オンプレミスでは VRRP により IP アドレスそのもの を引き継ぐ設計が一般的でした。一方 AWS では、同じ発想をそのまま適用しづらい設計思想になっています。それでも現実には、固定IPアドレス前提のアプリケーションやミドルウェア、外部連携などの既存の接続要件が残ることも少なくありません。
本稿では、まず この方式が提供する価値 を確認し、次に AWS における 仮想IP の実現方式(静的/動的) を整理した上で、静的ルーティング方式で、接続対象の Web サーバの切替を実装しながら仕組みを理解する、という流れで解説します。
1. AWSで固定プライベートIPアドレス接続を実現する価値
2025.10.15の オンプレの仮想IP冗長構成とAWSの設計思想はなぜ合わないのか?移行時の選択肢を解説 で解説の通り、AWS の基本思想は 固定IPアドレスでの接続から脱却し、接続を固定の FQDN で行うことで、冗長性・拡張性を高める方向にあります。
しかし既存システムを移行する局面では、単一で固定のプライベートIPアドレスへの接続を前提としたアプリケーション(レガシーアプリケーション/特定ミドルウェア/外部接続)がボトルネックになり、FQDN 化やマネージドサービスのロードバランサ導入のための改修が現実的でないことがあります。
そこで VPC の CIDR 範囲外の プライベートIPアドレス をルーティングで活用する 方式が、以下の価値を提供し、双方の設計思想のギャップを埋める現実解になります。
- 移行コストの削減
- マルチAZでの高可用性確保
1.1 移行コストの削減
この方式の最大の価値は、既存アプリケーションの改修コストを抑え、AWS への移行を前進させられる点です。
アプリケーションが固定のプライベートIPアドレスに接続する実装を持っている場合でも、その 固定プライベートIPアドレス が指すターゲットを VPC のルーティングで制御すれば、アプリケーションから見ると 常に同じ IPアドレス に接続している ように見せられます。
結果として、既存アプリケーションの変更を最小限に抑えつつ、アプリケーションを AWS の柔軟なインフラ上で稼働させることが可能になります。
1.2 マルチAZでの高可用性確保
AWS では、異なる AZ に跨って 同じプライベートIPアドレスを利用できる構成 は、基本的な VPC の機能範囲だけではできません。
しかし本稿の方式を用いれば、AZ を跨いで 単一の仮想IP を 高可用構成で利用できます。
例えば AZ1 の Web サーバが通信を処理している最中に障害が発生した場合、スタンバイの AZ2 上の Web サーバへ 仮想IP のルーティングのターゲットを切り替えることで、接続元は同じ 仮想IP で接続し続けられます。
これは、オンプレミスで VRRP により実現していた IP アドレス引き継ぎと同等の価値を、AWS のルーティング制御で再現するものです。
2025.10.15のブログ では、この方式について以下のようにご紹介していました。
VPC外のIPアドレスをルーティングテーブルに設定する方式は、VPCのルートテーブルに、VPCのCIDRの範囲外のプライベートIPアドレスを静的に設定し、ルーティング上で特定のEC2インスタンスへトラフィックを誘導する方式です。
(中略)
VPCの範囲外のプライベートIPアドレス192.168.0.10/32へのアクセスを、EC2①の10.0.1.15にルーティングする設定とします。EC2①は、192.168.0.10/32へのアクセスを、自分自身へのアクセスとして処理します。EC2①に障害があった時に、192.168.0.10/32へのアクセスを、EC2②の10.0.2.30にルーティング変更します。
このようなルーティングのアプローチによって、リージョン内で同一のプライベートIPアドレスを使用することが可能になります。
この方式は、EC2でのクラスター構成をAWSで構築するミドルウェアで採用されています。
2. AWSにおける仮想IPアドレスの実現方式
この仮想IPの実現方式ですが、AWS では次の 2 つの実装方式があります。
- 静的ルーティングによる方式
- 動的ルーティング(Amazon VPC Route Server)による方式
まず、静的ルーティングによる方式 について記載します。
2.1 静的ルーティングによる仮想IPアドレス方式
オンプレミスで一般的な VRRP のように IP アドレス自体を引き継ぐ方式 とは異なり、
AWS では、仮想IP 宛てのトラフィックを、ルーティングで任意の EC2 インスタンスに転送 することで、オンプレミスのIPアドレス自体を引き継ぐ方式と同等の振る舞いを実現します。
静的ルーティング方式では、VPC のルートテーブルに 仮想IP(/32)を静的に追加し、
そのターゲットとして EC2 インスタンス(または ENI)を指定します。
障害時には、ルートテーブル上のターゲットを別 EC2 インスタンスに切り替えます。
切り替えは Lambda など、何らかのスクリプトで制御します。
本稿では、3章で以下の動きを確認します。
ルートテーブルで 仮想IP の 192.168.0.10/32 に対するターゲットを書き換えることで、
別の AZ の EC2 インスタンスに接続を切り替える動きを確認します。
この静的ルーティングの方式は 一般的な EC2 のワークロード で親和性が高く、アクティブ/スタンバイ の HAクラスターを構成するミドルウェアでも採用されています。
この方式が有効なケースとしては、以下のようなケースがあります。
-
レガシーアプリケーション
固定プライベートIPアドレス前提で設計され、
FQDN 化や、マネージドサービスのロードバランサー導入が難しいケース -
ミドルウェアを稼働するクラスター構成のシステム
固定プライベートIPアドレスを要求するコンポーネントがあるケース -
特定の外部連携システム
外部連携システムで、固定プライベートIPアドレスでの接続が変えられないケース
3章で、静的ルーティング方式 の仕組みを、実装を通じて確認します。
2.2 動的ルーティング(VPC Route Server)による方式
もう 1 つが、VPC Route Server を利用し、BGP/BFD により経路を動的に制御する方式です。
この方式は IPS/IDS など、BGP/BFD プロトコルを話せる ネットワーク系仮想アプライアンス での利用が主になります。
仮想アプライアンスの障害時は、仮想アプライアンス側が VPC Route Server に対して経路の優先度を下げ(例: MED値を上げる)、それにより自アプライアンスから別アプライアンスへトラフィックを切り替える動きとなります。
例えば 2つのアプライアンスに対して、MED=10 と MED=100 の経路がある場合、通常は MED=10 が選択されます。MED=10 の設定を MED=1000 に変更すると、ルーティングが MED値の小さい方である MED=100 のアプライアンスへ切り替わります。
あるいは BGP セッション断により、VPC Route Server が通信できない仮想アプライアンスの経路を外すことで、通信先が別のアプライアンスに切り替わります。
動的ルーティング方式はネットワーク系仮想アプライアンス向けであるため、本稿の範囲外としますが、接続先を切り替える方式としてこういう方式もあること、ネットワーク系仮想アプライアンスの冗長化で使われることだけでも押さえておくとよいかと思います。
動的ルーティング方式 および VPC Route Serverについては以下をご参照ください。
3. 実装について
本章では、仮想IPでルーティングを行う静的ルーティング方式を段階的に実装していきます。
3.1 実装環境について
3.1.1 本稿の構成図(最終的な構成図)
本稿でご説明する構成図は以下の通りです。
仮想IP (192.168.0.10/32) へのトラフィックを、アクティブなWebサーバへルーティングする仕組みを掘り下げていきます。
上図は、10.0.41.0/24 と 10.0.42.0/24 のいずれかに オートスケーリンググループで Webサーバ が配置され、VPC範囲外のIPアドレスである 192.168.0.10/32 へのトラフィックが、ルートテーブルの設定により AZ1 の Webサーバ i-0b95bbb11bc3f2f41 に向いている状態を示しています。
本稿での確認内容について
本来は、アクティブ/スタンバイの構成で、2 つのインスタンスが起動していて、
インスタンス不具合時にその不具合を検知し、ルーティングを書き換えることで、
正常なインスタンスに接続を切り替える構成が望ましい構成です。
しかしながら本稿では手順を簡略化して、インスタンスが起動した際に、
自インスタンスを仮想IPのターゲットとしてルートテーブルに登録することで、
同じ仮想IP で、ルートテーブルが書き換えられた際に接続されるインスタンスが
切り替わることを確認する流れとします。
3.1.2 事前準備
以下の基本的な部分は構築済とします。
- Amazon VPC
- CIDR:
10.0.0.0/16で作成 - Private Subnet (AZ1):
10.0.41.0/24
※このサブネットのIDは環境変数${WEB_SUBNET1_ID}に格納されていることとします - Private Subnet (AZ2):
10.0.42.0/24
※このサブネットのIDは環境変数${WEB_SUBNET2_ID}に格納されていることとします - Internet Gateway
- NAT Gateway
- リージョナルタイプで作成
- 手動モードで固定パブリックIPアドレスを2つ付与
- Route Table: プライベートサブネットに関連付けるルーティングテーブル
-
0.0.0.0/0を NAT Gatewaynat-1ab774af6a36b31e8へ -
10.0.0.0/16をlocalへ
-
- CIDR:
- セキュリティグループ
| セキュリティ グループ名称 |
用途 | インバウンド | アウトバウンド |
| SG_Web | Web サーバ用 |
セキュリティグループSG_Check が設定されたリソースからの http 接続を許可 |
0.0.0.0/0 に対して https 接続を許可 |
| SG_Check | 動作確認 サーバ用 |
無し | 0.0.0.0/0 に対して http, https 接続を許可 |
- AWS Systems Manager Session Manager で動作確認用仮想サーバにログインするための
AWS Identity and Access Management ロール(以下IAMロール) - 動作確認用仮想サーバ(Amazon EC2インスタンス)
3.1.3 事前準備後の構成図
事前準備後では、以下が作成された状態になります。
3.2 実装の段取り
3.2.1 ユーザーデータの作成
WebサーバのEC2インスタンスが起動時に実行するユーザーデータを作成します。
このユーザーデータには、以下を含めています。
▼ユーザーデータに含めている処理内容
- 事前準備
- IMDSv2 トークン取得(IPアドレス取得、インスタンスID取得のために必要)
- IPアドレス取得
- インスタンスID取得
- インスタンス内の設定
- OSに仮想プライベートIPアドレス設定(セカンダリ IP として追加)
- AWS側設定
- SourceDestCheck を無効化
- ルート設定
- 既存ルートの削除
- 新規ルート追加
#!/bin/bash
# envsubstをインストール
# envsubstで、UserData内の変数を部分的に展開
sudo yum install -y gettext -q
# UserDataスクリプト作成(Base64エンコード)
# envsubstで、UserData内の変数のうち以下を展開
# ${PRIVATE_RT_NAME}:ルートテーブル名
# ${IP_ADDRESS_2}:仮想プライベートIPアドレス
# ※ envsubstで展開しない変数は、インスタンス起動時に値を取得して変数に格納
# ※ UserDataで設定したスクリプトの実行結果は以下で参照可能
# sudo cat /var/log/cloud-init-output.log
# ※ このスクリプトが実行されたサーバーとのやりとりは、以下のコマンドで確認可能
# sudo tcpdump -i ens5 -nn 'tcp port 80' -A
export USER_DATA_FOR_SET_VIP=$(cat <<'EOF' | envsubst '${PRIVATE_RT_NAME} ${IP_ADDRESS_2}' | base64 -w 0
#!/bin/bash
##################################################################################
# 事前準備
##################################################################################
# IMDSv2 トークン取得
TOKEN=$(curl -sX PUT "http://169.254.169.254/latest/api/token" \
-H "X-aws-ec2-metadata-token-ttl-seconds: 21600")
# IPアドレス取得
IP_ADDRESS_1=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/local-ipv4)
# インスタンス ID 取得
INSTANCE_ID=$(curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \
http://169.254.169.254/latest/meta-data/instance-id)
##################################################################################
# インスタンス内設定
# 仮想プライベートIPアドレスでのアクセス受付設定
##################################################################################
# IP アドレス設定
IP_ADDRESS_2="${IP_ADDRESS_2}"
# ネットワークインターフェース取得(ループバック除外)
NET_INTERFACE=$(ip -o -4 addr show | awk '!/127.0.0.1/ && $2 != "lo" {print $2; exit}')
echo "First non-loopback interface: ${NET_INTERFACE}"
# OSに仮想プライベートIPアドレス設定(セカンダリ IP として追加)
sudo ip addr add "${IP_ADDRESS_2}/32" dev "${NET_INTERFACE}" label "${NET_INTERFACE}:vip"
##################################################################################
# AWS側設定
##################################################################################
# インスタンス設定
# AWS CLI で SourceDestCheck を無効化
aws ec2 modify-instance-attribute \
--instance-id "${INSTANCE_ID}" \
--no-source-dest-check
# ルートテーブル設定
# 名称が ${PRIVATE_RT_NAME} のルートテーブル取得
ROUTE_TABLE_IDS=$(aws ec2 describe-route-tables \
--filters "Name=tag:Name,Values=${PRIVATE_RT_NAME}" \
--query "RouteTables[].RouteTableId" --output text)
# 既存ルートの削除と新規ルート追加
for ROUTE_TABLE_ID in ${ROUTE_TABLE_IDS}; do
EXISTING_ROUTE=$(aws ec2 describe-route-tables \
--route-table-ids "${ROUTE_TABLE_ID}" \
--query "RouteTables[0].Routes[?DestinationCidrBlock=='${IP_ADDRESS_2}/32']" \
--output text)
# 既存のルーティング設定があれば削除
if [ -n "${EXISTING_ROUTE}" ]; then
aws ec2 delete-route \
--route-table-id "${ROUTE_TABLE_ID}" \
--destination-cidr-block "${IP_ADDRESS_2}/32"
fi
# 仮想プライベートIPアドレスを、この仮想サーバにルーティングするよう設定を追加
aws ec2 create-route \
--route-table-id "${ROUTE_TABLE_ID}" \
--destination-cidr-block "${IP_ADDRESS_2}/32" \
--instance-id "${INSTANCE_ID}"
done
##################################################################################
# 動作確認用httpd設定
##################################################################################
# httpdのインストール
sudo yum install -y httpd
# index.html作成
hostname > /tmp/index.html
sudo mv /tmp/index.html /var/www/html
# httpdを常時起動に設定
sudo systemctl enable httpd
sudo systemctl start httpd
# End of UserData
EOF
)
このスクリプトを、環境変数 ${USER_DATA_FOR_SET_VIP} に設定しています。
環境変数 ${USER_DATA_FOR_SET_VIP} は、起動テンプレートに設定します。
起動テンプレートに設定されたスクリプトは、EC2インスタンス起動時に実行されます。
このスクリプトの主要な箇所を説明します。
OSに仮想プライベートIPアドレス設定
OS内部で、自分のIPアドレス以外の別のIPアドレスである 仮想IP を受け付けるために、仮想IP をネットワークインターフェースにセカンダリIPアドレスとして登録します。
# OSに仮想プライベートIPアドレス設定(セカンダリ IP として追加)
sudo ip addr add "${IP_ADDRESS_2}/32" dev "${NET_INTERFACE}" label "${NET_INTERFACE}:vip"
SourceDestCheck 無効化
通常、EC2インスタンスは、自分宛て、または自分発の通信しか処理しない設定です。
以下を実行すると、自分のIPアドレスではない、別のIPアドレスである 仮想IP を受け付けることができるようになります。
# AWS CLI で SourceDestCheck を無効化
aws ec2 modify-instance-attribute \
--instance-id "${INSTANCE_ID}" \
--no-source-dest-check
こちらの方式はプライベートIPアドレスを受け付ける目的で行っていますが、
独自のパブリックIPアドレスを BYOIP として AWS に持ち込む方式を解説した
以下のページで、別のIPアドレスをインスタンスに設定する方法と、
SourceDestCheck 無効化について解説されています。
- Amazon VPC ユーザーガイド
上記のページは 独自にAWSに持ち込んだパブリックIPアドレスをインスタンスで
受け付ける 目的ですので、目的は異なりますが、技術的には同じ技術を使います。
ルートテーブルに、仮想IPの宛先を登録
ルートテーブルに、仮想IP の宛先として、自分のインスタンスIDを登録します。
まず、ルートテーブルのIDを取得します。
次に、ルートテーブルに既存のルート設定があれば削除します。
最後に、仮想プライベートIPアドレスを、このインスタンスにルーティングするよう、
ルート設定を追加します。
これにより、仮想IP 宛ての通信が、自インスタンスID にルーティングされます。
# 既存ルートの削除と新規ルート追加
for ROUTE_TABLE_ID in ${ROUTE_TABLE_IDS}; do
EXISTING_ROUTE=$(aws ec2 describe-route-tables \
--route-table-ids "${ROUTE_TABLE_ID}" \
--query "RouteTables[0].Routes[?DestinationCidrBlock=='${IP_ADDRESS_2}/32']" \
--output text)
# 既存のルート設定があれば削除
if [ -n "${EXISTING_ROUTE}" ]; then
aws ec2 delete-route \
--route-table-id "${ROUTE_TABLE_ID}" \
--destination-cidr-block "${IP_ADDRESS_2}/32"
fi
# 仮想プライベートIPアドレスを、このインスタンスにルーティングするようルート設定を追加
aws ec2 create-route \
--route-table-id "${ROUTE_TABLE_ID}" \
--destination-cidr-block "${IP_ADDRESS_2}/32" \
--instance-id "${INSTANCE_ID}"
done
ルートテーブルに、特定のIPアドレスの宛先をインスタンスに登録する方式も、
独自のパブリックIPアドレスを BYOIP として AWS に持ち込む方式を解説した
同じ以下のページで同様に解説されています。
- Amazon VPC ユーザーガイド
Apache Httpd を常時起動に設定
ユーザーデータの最後で、Apache Httpd を常時起動する設定を入れています。
# httpdを常時起動に設定
sudo systemctl enable httpd
sudo systemctl start httpd
3.2.2 起動テンプレート用IAMロール作成
起動テンプレートに設定する IAMロール と インスタンスプロファイル を作成します。
この IAMロール には、SourceDestCheck を無効化する ec2:modify-instance-attribute や、VPCのルートテーブルに対してルートを追加する ec2:CreateRoute や、
ルートを削除する ec2:DeleteRoute などのアクションを許可する必要があります。
以下の作業を行います。
- IAMロール作成用の
信頼ポリシーファイル作成 - 信頼ポリシーファイルを元に
IAMロール作成 - ルーティングを設定する
最低限のポリシーファイル作成 - 最低限のポリシーファイルを元に
カスタムポリシー作成 - IAMロールにカスタムポリシーをアタッチ
- IAMロールをインスタンスに割り当てるための
インスタンスプロファイル作成
IAMロール作成用の信頼ポリシーファイル作成
まず、IAMロールの信頼ポリシーを作成します。
ec2.amazonaws.com に対して、sts:AssumeRole アクションを許可する設定内容です。
これは、EC2 サービスが、この IAM ロールを引き受けて一時的な認証情報を取得してよい という設定になっています。
# EC2 用の信頼ポリシー JSON を作成
cat <<EOF > ${POLICY_FILE_FOR_EC2_NAME_FOR_VIP_TEST}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": "ec2.amazonaws.com"
},
"Action": "sts:AssumeRole"
}
]
}
EOF
IAMロール作成
信頼ポリシーを使って、IAMロールを作成します。
# IAMロール作成
aws iam create-role \
--role-name "${ROLE_FOR_EC2_VIP_TEST}" \
--assume-role-policy-document file://${POLICY_FILE_FOR_EC2_NAME_FOR_VIP_TEST} \
--tags "Key=Name,Value=${ROLE_FOR_EC2_VIP_TEST}"
ルーティングを設定する最低限のポリシーファイル作成
IAMロールに、どういうアクションを許可するか設定するポリシーファイルを作成します。
ここでは、SourceDestCheck を無効化する ec2:modify-instance-attribute や、
VPCのルートテーブルに対してルートを追加する ec2:CreateRoute や、
ルートを削除する ec2:DeleteRoute などのアクションを許可する設定を入れています。
# EC2 の設定を変更する最小限のカスタムポリシーの定義ファイルを作成
cat <<EOF > ${POLICY_FILE_FOR_EC2_NAME_FOR_VIP_TEST_2}
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:ModifyInstanceAttribute"
],
"Resource": "arn:aws:ec2:${REGION}:${AWS_ACCOUNT}:instance/*",
"Condition": {
"StringEquals": {
"aws:PrincipalAccount": "${AWS_ACCOUNT}"
}
}
},
{
"Effect": "Allow",
"Action": [
"ec2:DeleteRoute",
"ec2:CreateRoute"
],
"Resource": "arn:aws:ec2:${REGION}:${AWS_ACCOUNT}:route-table/*",
"Condition": {
"StringEquals": {
"aws:PrincipalAccount": "${AWS_ACCOUNT}"
}
}
},
{
"Effect": "Allow",
"Action": [
"ec2:DescribeRouteTables",
"ec2:DescribeTags"
],
"Resource": "*"
}
]
}
EOF
最低限のポリシーファイルを元にカスタムポリシー作成
作成した最低限のポリシーファイルをもとに、カスタムポリシーを作成します。
# カスタムポリシーを作成
aws iam create-policy \
--policy-name "${ROLE_FOR_EC2_VIP_TEST}-Policy" \
--policy-document file://${POLICY_FILE_FOR_EC2_NAME_FOR_VIP_TEST_2}
IAMロールにカスタムポリシーをアタッチ
作成したカスタムポリシーを、IAMロールにアタッチします。
# ポリシーを作成
aws iam attach-role-policy \
--role-name "${ROLE_FOR_EC2_VIP_TEST}" \
--policy-arn "arn:aws:iam::${AWS_ACCOUNT}:policy/${ROLE_FOR_EC2_VIP_TEST}-Policy"
インスタンスプロファイル作成
IAMロールをインスタンスに割り当てるために、インスタンスプロファイルを作成します。
インスタンスプロファイルをIAMロールと関連付けします。
# インスタンスプロファイル作成
aws iam create-instance-profile \
--instance-profile-name ${ROLE_FOR_EC2_VIP_TEST}
# インスタンスプロファイルをロールと関連付け
aws iam add-role-to-instance-profile \
--instance-profile-name ${ROLE_FOR_EC2_VIP_TEST} \
--role-name ${ROLE_FOR_EC2_VIP_TEST}
3.2.3 起動テンプレート作成
起動テンプレートデータをJSONファイルとして作成
Webサーバを起動するための起動テンプレートを作成します。
ここでは、先ほど作成したユーザーデータと、インスタンスプロファイルを関連付けます。
まず、起動テンプレート作成用のJSONファイルを作成します。
# 起動テンプレートデータをJSONファイルとして作成
cat <<EOF > launch_template_data_for_set_vip.json
{
"ImageId": "${AMI_ID_FOR_SET_VIP}",
"InstanceType": "t3.micro",
"SecurityGroupIds": ["${SG_ID_Web}"],
"IamInstanceProfile": {
"Arn": "arn:aws:iam::${AWS_ACCOUNT}:instance-profile/${ROLE_FOR_EC2_VIP_TEST}"
},
"UserData": "${USER_DATA_FOR_SET_VIP}",
"BlockDeviceMappings": [
{
"DeviceName": "/dev/xvda",
"Ebs": {
"VolumeSize": 8,
"VolumeType": "gp3",
"Iops": 3000,
"Throughput": 125,
"Encrypted": true
}
}
],
"TagSpecifications": [
{
"ResourceType": "instance",
"Tags": [
{
"Key": "Name",
"Value": "${EC2_NAME_FOR_SET_VIP}"
}
]
}
]
}
EOF
主要な箇所を説明します。
起動テンプレートの定義の以下の部分で、インスタンスプロファイルと、仮想プライベートIPアドレス設定などを行うスクリプトを保存した ユーザーデータ を設定しています。
"IamInstanceProfile": {
"Arn": "arn:aws:iam::${AWS_ACCOUNT}:instance-profile/${ROLE_FOR_EC2_VIP_TEST}"
},
"UserData": "${USER_DATA_FOR_SET_VIP}",
起動テンプレートデータを作成
作成した起動テンプレートのJSONファイルを用いて、起動テンプレートを作成します。
# 起動テンプレート作成
aws ec2 create-launch-template \
--region "${REGION}" \
--launch-template-name "${EC2_NAME_FOR_SET_VIP}" \
--version-description "v1" \
--launch-template-data file://launch_template_data_for_set_vip.json \
--tag-specifications "ResourceType=launch-template,Tags=[{Key=Name,Value=${EC2_NAME_FOR_SET_VIP}},{Key=CreateVPCDate,Value=${CREATE_VPC_DATE}}]" \
--query "LaunchTemplate.LaunchTemplateId" \
--output text
3.2.4 動作確認① (仮想IPアドレスが存在しない状態)
仮想IPアドレス 192.168.0.10/32 がルートテーブルにまだ存在しない状態を確認します。
sh-5.2$ aws ec2 describe-route-tables \
--filters "Name=tag:Name,Values=${PRIVATE_RT_NAME}" \
--query "RouteTables[].Routes[]" \
--output json \
| jq -r '.[] | {Destination: .DestinationCidrBlock, Target: (.GatewayId // .InstanceId // .NatGatewayId)} | [.Destination, .Target] | @tsv' \
| column -t
10.0.0.0/16 local
0.0.0.0/0 nat-1ab774af6a36b31e8
sh-5.2$
コマンドを解説します。
aws ec2 describe-route-tables
VPC の ルートテーブルを取得する AWS CLI コマンドです。
--filters "Name=tag:Name,Values=${PRIVATE_RT_NAME}" により、Tag の Key が Name のルートテーブルを、値が ${PRIVATE_RT_NAME} に一致するものだけに絞り込みます。
つまり ${PRIVATE_RT_NAME} という名前(Nameタグ)を付けたルートテーブルを見に行っています。
--query "RouteTables[].Routes[]"
取得したルートテーブルのうち、Routes 配列(ルートの行)だけを抜き出します。
ルートテーブル本体の情報(ID、関連付けサブネットなど)は捨てて、宛先 Destination と転送先 Target だけにしています。
実行結果例
この段階では、VPC内の宛先 10.0.0.0/16 については local に、デフォルトゲートウェイの 0.0.0.0/0 はNAT Gateway nat-1ab774af6a36b31e8 に設定されているだけの状態です。
10.0.0.0/16 local
0.0.0.0/0 nat-1ab774af6a36b31e8
3.2.5 オートスケーリンググループ作成
オートスケーリンググループ作成
起動テンプレートを使用して、起動するインスタンス数を最小1台、最大1台、希望する台数 1台として、Webサーバを起動するオートスケーリンググループを作成します。
ここでは、--vpc-zone-identifier で サブネット ${WEB_SUBNET1_ID} を指定しています。
この変数 ${WEB_SUBNET1_ID} は、AZ1 のサブネット 10.0.41.0/24 を指しています。
この項目については、後工程で、別の AZ にあるサブネットを指定します。
# オートスケーリンググループ作成
aws autoscaling create-auto-scaling-group \
--region "${REGION}" \
--auto-scaling-group-name "${ASG_TEST_VIP_NAME}" \
--launch-template "LaunchTemplateId=${LAUNCH_TEMPLATE_FOR_SET_VIP_ID},Version=1" \
--min-size 1 \
--max-size 1 \
--desired-capacity 1 \
--vpc-zone-identifier "${WEB_SUBNET1_ID}" \
--tag "Key=Name,Value=${ASG_TEST_VIP_NAME},PropagateAtLaunch=true"
3.2.6 動作確認②
EC2インスタンス起動確認
しばらくすると、オートスケーリンググループ作成時に 希望台数 --desired-capacity 1 と設定した通り、AZ1 に EC2インスタンス が 1 台起動します。
以下の aws ec2 describe-instances コマンドでは、オートスケーリンググループ名 に該当する インスタンス を絞り込み、表示項目としてインスタンスが起動した AZ、インスタンスID、
インスタンスに割り当てられた プライベートIPアドレス とインスタンスの 状態 を抽出して 出力しています。
sh-5.2$ aws ec2 describe-instances \
--filters "Name=tag:Name,Values=${ASG_TEST_VIP_NAME}" \
--query "Reservations[].Instances[].{InstanceId:InstanceId,PrivateIp:PrivateIpAddress,AZ:Placement.AvailabilityZone,State:State.Name}" \
--output table
-------------------------------------------------------------------
| DescribeInstances |
+------------+-----------------------+-------------+--------------+
| AZ | InstanceId | PrivateIp | State |
+------------+-----------------------+-------------+--------------+
| us-east-1a| i-0b95bbb11bc3f2f41 | 10.0.41.89 | running |
+------------+-----------------------+-------------+--------------+
sh-5.2$
オートスケーリンググループ作成後の状態
オートスケーリンググループ作成後は、以下の構成図の状態になります。
ルートテーブルにも、宛先 仮想IP に対する転送先 Target として、
AZ1 の EC2インスタンスが設定されているはずです。
ルーティング情報を確認
ルーティング情報を確認します。
EC2インスタンス起動時に、宛先の仮想IP 192.168.0.10/32 の転送先を、自インスタンスに設定するスクリプトが動きます。
ルーティングを確認すると、以下の通り、仮想IP へのルートとして、自インスタンスが設定されたことが確認できます。
sh-5.2$ aws ec2 describe-route-tables \
--filters "Name=tag:Name,Values=${PRIVATE_RT_NAME}" \
--query "RouteTables[].Routes[]" \
--output json \
| jq -r '.[] | {Destination: .DestinationCidrBlock, Target: (.GatewayId // .InstanceId // .NatGatewayId)} | [.Destination, .Target] | @tsv' \
| column -t
192.168.0.10/32 i-0b95bbb11bc3f2f41
10.0.0.0/16 local
0.0.0.0/0 nat-1ab774af6a36b31e8
sh-5.2$
仮想IP へ接続確認
仮想IP 192.168.0.10/32 が AZ1 の Webサーバ にルーティングされる定義がされた状態で、動作確認用仮想サーバから接続を試みます。
以下の構成図で、ピンク色の➡ の動きを確認します。
以下の通り、仮想IPで接続することができました。
sh-5.2$ curl http://192.168.0.10
ip-10-0-41-89.ec2.internal
sh-5.2$
3.2.7 オートスケーリンググループ更新 (フェイルオーバーのシミュレーション)
オートスケーリンググループ更新
アクティブなWebサーバ(AZ1)に障害が発生した状況をシミュレートし、ルートテーブルのターゲットをスタンバイのWebサーバ(AZ2)に切り替えます。
ここでは簡易的に、オートスケーリンググループを更新することで既存 EC2インスタンス を停止して、別の AZ で EC2インスタンスを起動する手順を示します。
EC2インスタンスが起動するサブネットを、--vpc-zone-identifier "${WEB_SUBNET2_ID}" で別の AZ 上のサブネットと指定して、オートスケーリンググループを更新します。
# オートスケーリンググループ更新
aws autoscaling update-auto-scaling-group \
--region "${REGION}" \
--auto-scaling-group-name "${ASG_TEST_VIP_NAME}" \
--min-size 1 \
--max-size 1 \
--desired-capacity 1 \
--vpc-zone-identifier "${WEB_SUBNET2_ID}"
3.2.8 動作確認③
EC2インスタンス起動確認
しばらくすると、AZ1 の Webサーバ が停止し、AZ2 に Webサーバ が 1 台起動します。
以下の aws ec2 describe-instances コマンドでは、オートスケーリンググループ名 に該当する インスタンス を絞り込み、表示項目としてインスタンスが起動した AZ、インスタンスID、
インスタンスに割り当てられた プライベートIPアドレス とインスタンスの 状態 を抽出して 出力しています。
EC2 インスタンスが、別の AZ、別のサブネットで起動したことが確認できます。
sh-5.2$ aws ec2 describe-instances \
--filters "Name=tag:Name,Values=${ASG_TEST_VIP_NAME}" \
--query "Reservations[].Instances[].{InstanceId:InstanceId,PrivateIp:PrivateIpAddress,AZ:Placement.AvailabilityZone,State:State.Name}" \
--output table
----------------------------------------------------------------------
| DescribeInstances |
+------------+-----------------------+-------------+-----------------+
| AZ | InstanceId | PrivateIp | State |
+------------+-----------------------+-------------+-----------------+
| us-east-1a| i-0b95bbb11bc3f2f41 | 10.0.41.89 | shutting-down |
| us-east-1c| i-0f95139028a5f6e17 | 10.0.42.42 | running |
+------------+-----------------------+-------------+-----------------+
sh-5.2$
オートスケーリンググループ更新後の状態
オートスケーリンググループ更新後は、以下の構成図の状態になります。
ルートテーブルにも、宛先 仮想IP に対する転送先 Target として、
AZ2 の EC2インスタンスが設定されているはずです。
ルーティング情報を確認
ルーティング情報を確認します。
EC2インスタンス起動時に、宛先の仮想IP 192.168.0.10/32 の転送先を、自インスタンスに設定するスクリプトが動きます。
ルーティングが設定されたことを確認すると、以下の通り、仮想IPへのルートが変更されたことが確認できます。
sh-5.2$ aws ec2 describe-route-tables \
--filters "Name=tag:Name,Values=${PRIVATE_RT_NAME}" \
--query "RouteTables[].Routes[]" \
--output json \
| jq -r '.[] | {Destination: .DestinationCidrBlock, Target: (.GatewayId // .InstanceId // .NatGatewayId)} | [.Destination, .Target] | @tsv' \
| column -t
192.168.0.10/32 i-0f95139028a5f6e17
10.0.0.0/16 local
0.0.0.0/0 nat-1ab774af6a36b31e8
sh-5.2$
仮想IP へ接続確認
仮想IP 192.168.0.10/32 が AZ2 の Webサーバ にルーティングされる定義がされた状態で、動作確認用仮想サーバから接続を試みます。
以下の構成図で、ピンク色の➡ の動きを確認します。
以下の通り、仮想IPで接続することができました。
sh-5.2$ curl http://192.168.0.10
ip-10-0-42-42.ec2.internal
sh-5.2$
これにより、仮想IP の転送先が書き換えられ、仮想IPへのトラフィックが別の Webサーバ に切り替わったことが確認できました。
まとめ
本稿では、オンプレミス環境で一般的に用いられてきた VRRP による仮想IPアドレス引き継ぎの代替として、AWS において VPC ルートテーブルを操作することで、固定のプライベートIPアドレス接続を実現する方式を解説しました。
AWS の設計思想は、IPアドレスへの依存を減らし、FQDN やマネージドサービスを前提とした疎結合な構成へ移行することにあります。一方で、既存システムの移行フェーズでは、固定IPアドレス前提の設計が大きな制約となり、理想論だけでは移行が進まないケースも少なくありません。
本稿で紹介した方式は、アクティブ/スタンバイ構成を前提としたものであり、AWS が本来志向するスケールアウト型・疎結合型のアーキテクチャとは異なる側面を持ちます。しかし、そうした設計思想の違いを理解した上で、VPC のルーティング機能を活用することで、既存アプリケーションの変更を最小限に抑えながら、マルチAZでの高可用性を確保できる点に、この方式の現実的な価値があると考えます。
本稿では、ルーティングを工夫することで、AWS 上においても単一かつ固定のプライベートIPアドレスでの接続を論理的に実現できることを示しました。ただし、実際のシステムで本方式を適用する際には、EC2 インスタンス自体の可用性を高めるためのクラスター構成や障害検知・切り替えの設計と併せて検討することが重要になります。
この方式は、AWS の設計思想からすると最終形ではありませんが、レガシーな要件を抱えたシステムを AWS に移行し、その後に FQDN 化やマネージドサービスへの置き換えを段階的に進めていくための橋渡しとして、価値を持つ選択肢です。
AWS の設計思想を理解した上で、あえてこのような構成を選択できることもまた、クラウド移行を現実的に前進させるために重要と考えます。
この投稿が皆様のご参考になれば幸いです。














