EC2起動タイプのAmazon ECSにElastic IPを付与してIPアドレスを固定する

EC2起動タイプのECSで運用しているアプリケーションを、外部サービスと連携したいとの要望があった。

ただ、連携したいサービスというのが、接続元のIPアドレスを指定して連携するタイプのサービスのため、IPアドレスを固定するのが要件。

EIPをアタッチしたEC2を別に立てて、そのサーバーを経由させるかと考えたが、EC2起動タイプのECSはbridgeネットワークのようなので、「クラスターとして作成されたEC2インスタンスにEIPを付与したらIPアドレス固定できるんじゃないか?」と思い付いた。

試してみたら、うまくいったのでメモ。

環境

Windows 10 Pro 64bit、AWS CLI v1.16.119、ECS CLI v1.12.1で確認。

コマンドは、Git v2.21.0のGit Bashで実行。

現在のIPアドレスの確認には、cman.jpのIPアドレス確認を使わせていただいた。

事前準備

作業ディレクトリに、docker-compose.ymlとindex.htmlを作成しておく。

docker-compose.ymlは以下。

version: "3"

services:
  httpd:
    image: httpd:2.4.38-alpine
    ports:
      - "80:80"
    volumes:
      - /home/ec2-user/index.html:/usr/local/apache2/htdocs/index.html
    command: ["sh", "-c", "wget -q -O - https://www.cman.jp/network/support/go_access.cgi | grep keyHostName > /usr/local/apache2/htdocs/ip_address.js && httpd-foreground"]

index.htmlは以下。

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>ip address check</title>
  <script src="ip_address.js"></script>
  <script>
    document.addEventListener('DOMContentLoaded', function(e) {
      document.getElementById('ip-address').textContent = 'ip address: ' + keyHostName;
    });
  </script>
</head>

<body>
  <p id="ip-address"></p>
</body>

</html>

テスト実行

docker-compose.ymlとindex.htmlを作成したディレクトリをカレントディレクトリとして実行する。

ecs-cli up 直後に aws ecs list-container-instances しても None が返ってくるところでハマった。

結果を取得するには20秒程度待機が必要な模様。ひとまず、結果がNone以外になるまでwhileして待機した。

# 環境変数を設定
KEYPAIR=...
CLUSTER_NAME=...
EC2_SECRET=...

# テストなのでEIPはコマンドで作成
EIP_ID=$(aws ec2 allocate-address --domain vpc --query AllocationId --output text) && echo "eip id > ${EIP_ID}"

# EIPからIPアドレスを取得
EIP_ADDRESS=$(aws ec2 describe-addresses --allocation-ids ${EIP_ID} --query Addresses[0].PublicIp --output text) && echo "ip address > ${EIP_ADDRESS}"

# クラスターの作成
ecs-cli up \
  --capability-iam \
  --keypair ${KEYPAIR} \
  --instance-type t2.micro \
  --size 1 \
  --launch-type EC2 \
  -c ${CLUSTER_NAME}

# クラスターARNの取得、作成直後だとNoneが返るので繰り返す
sleep 10s && CLUSTER_ARN=None && while [ 'None' = "${CLUSTER_ARN}" ]; do
  sleep 5s
  CLUSTER_ARN=$(aws ecs list-container-instances --cluster ${CLUSTER_NAME} --query containerInstanceArns[0] --output text)
done && echo "cluster arn > ${CLUSTER_ARN}"

# クラスターARNの「/」以降がコンテナIDのため切り出し
CONTAINER_ID=${CLUSTER_ARN##*/} && echo "cluster id > ${CONTAINER_ID}"

# EC2のIDを取得
EC2_ID=$(aws ecs describe-container-instances --cluster ${CLUSTER_NAME} --container-instances ${CONTAINER_ID} --query containerInstances[0].ec2InstanceId --output text) && echo "ec2 id > ${EC2_ID}"

# EIPを設定
ASSOCIATE_ID=$(aws ec2 associate-address --allocation-id ${EIP_ID} --instance ${EC2_ID} --output text) && echo "associate id > ${ASSOCIATE_ID}"

# EC2にindex.htmlをアップロード
scp -i "${EC2_SECRET}" -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null ./index.html ec2-user@${EIP_ADDRESS}:~/index.html

# タスク起動
ecs-cli compose up -c ${CLUSTER_NAME}

# ここでhttpにアクセス、EIPのアドレスが表示されていることを確認
read -p "check ip address > [http://${EIP_ADDRESS}]"

# タスク停止
ecs-cli compose down -c ${CLUSTER_NAME}

# EIPを外す
aws ec2 disassociate-address --association-id ${ASSOCIATE_ID}

# クラスターの削除
ecs-cli down -f -c ${CLUSTER_NAME}

# 作成したEIPを削除
aws ec2 release-address --allocation-id ${EIP_ID}

振り返り

オートスケールは不要なアプリケーションのため、エラー処理など詰めていけば何とかなりそう。