閉じた VPC で API Gateway + ECS Fargate を試してみた

ネットワークの閉じた環境で API Gateway + ECS Fargate の環境を作る機会があったので備忘録として綴ります。

インターネットに出ない閉じた環境の VPC に API Gateway と ECS Fargate を構築し、別の VPC と Peering します。Peering 先の EC2 から API Gateway にアクセスして、ECS から情報を取得します。

ポイントは下記のとおりです。

  • VPC Link で API Gateway と NLB を接続する
  • Peering 先の EC2 が API Gateway の Endpoint の名前解決するために Route 53 リゾルバを用意する

以下、VPC 周りは作成されている前提で構築を進めます。

  • VPC : 10.1.0.0/16
  • PrivateSubnet: 10.1.1.0/24 , 10.1.2.0/24

コンテナイメージを作成する

ローカルの PC で ECR に Push するコンテナイメージを作成します。今回はこちらの記事を参考に、JSON を返すだけのコンテナを作成しました。

https://qiita.com/kahirokunn/items/418f86e4e2ec1746ab60

まずは、Docker Compose を使ってローカル環境で動きを確かめました。JSON を呼び出す側と呼び出される側のコンテナを起動します。docker-compose.yml のサンプルを記載しておきます。

version: '2'
services:
  client:
    image: php:7.4-apache
    ports:
      - 8081:80
    volumes:
      - $PWD/client/html:/var/www/html
  web:
    image: php:7.4-apache
    ports:
      - 8080:80
    volumes:
       - $PWD/web/html:/var/www/html

client と web のコンテナを起動します。それぞれのコンテナはローカル PC の作業ディレクトリ ($PWD) の client と web ディレクトリを volumes で定義します。これでローカル PC のボリュームをコンテナ側がマウント( ? ) してくれます。

docker-compose up -d で起動します。

❯ docker-compose up -d
Starting docker_client_1 ... done
Starting docker_web_1    ... done

JSON を呼び出す側 (client) の Port にアクセスします。client は web から取得した JSON ( {“a”:1,”b”:2,”c”:3,”d”:4,”e”:5} ) をそのまま出力するだけのものです。
① [ ブラウザ ] —————> [ client ] —————> [ web ]
② [ ブラウザ ] <— JSON — [ client ] <— JSON — [ web ]

❯ curl http://localhost:8081/index.php
{"a":1,"b":2,"c":3,"d":4,"e":5}

JSON を出力する側 (web) から JSON が返ってきました。この呼び出された側のコンテナ ( web) をイメージ化して ECR に push します。

ちなみに、client 側の index.php は下記のとおりです。コードはこちらから拝借いたしました。

<?php

$url = "http://web/index.php";

// cURLセッションを初期化
$ch = curl_init();

// オプションを設定
curl_setopt($ch, CURLOPT_URL, $url); // 取得するURLを指定
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // 実行結果を文字列で返す
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // サーバー証明書の検証を行わない

// URLの情報を取得
$response =  curl_exec($ch);

// 取得結果を表示
echo $response;

$result = json_decode($response, true);

// セッションを終了
//curl_close($conn);

?>

イメージを作成するにあたり Dockerfile を記述します。

FROM php:7.4-apache
COPY $PWD/web/html /var/www/htm

Apache + PHP の入ったイメージを Pull して先程作成したコンテンツ ( $PWD/web/html ) を COPY するだけの内容です。

あらかじめ作成した ECR のリポジトリ URL ( ************.dkr.ecr.ap-northeast-1.amazonaws.com/poc_web ) をタグに指定したコンテナイメージを作成します。

docker build -t ************.dkr.ecr.ap-northeast-1.amazonaws.com/poc_web:web .

ECR に push する

今回は ECR リポジトリを poc_web としました。

イメージをリポジトリに push するために ECR トークンを取得します。あらかじめ aws cli を実行できるようにしておいてください。

ECR_TOKEN=`aws ecr get-login --region ap-northeast-1 --no-include-email | awk '{print $6}'`

ECR にアクセスを試みます。Succeeded がでたら成功です。

❯ echo $ECR_TOKEN | docker login -u AWS --password-stdin  https://************.dkr.ecr.ap-northeast-1.amazonaws.com
Login Succeeded

push します。

docker push ************.dkr.ecr.ap-northeast-1.amazonaws.com/poc_web:web

イメージの作成から ECR への登録は AWS 公式ドキュメントがわかりやすいです。

▼ Amazon ECR における Docker の基本
https://docs.aws.amazon.com/ja_jp/AmazonECR/latest/userguide/docker-basics.html

ECR 用の VPC Endpoint を作成する

インターネットに出れない Private Subnet に配置した ECS クラスタが ECR からイメージを pull するために 3 つの VPC Endpoint が必要です。

  • ECR Endpoint
  • S3 Endpoint
  • CloudWatch logs Endpoint

CloudWatch logs Endpoint

サービスに [ com.amazonaws.ap-northeast-1.logs ] を指定してください。

サブネットは ECS を展開する Private Subnet を指定します。Security Group は Private Subnet の CIDR を許可したルールを作成します。

S3 Endpoint

サービスに [ com.amazonaws.ap-northeast-1.s3 ] を指定します。

S3 エンドポイントは Gateway タイプになるので、Private Subnet のルートテーブルで適切にルーティングしておきましょう。

ECR Endpoint

サービスに [ com.amazonaws.ap-northeast-1.ecr.dkr ] を指定します。サービスはそれだけでよいです。

API Gateway バックエンド用の NLB を作成する

API Gateway から VPC に接続するには Private のバックエンド ELB は NLB しか選択できないので、NLB を作成します。以下のとおり画面をポチポチします。

NLB

  • 名前:適当に
  • スキーム:内部
  • リスナー:80
  • VPC:CIDR 10.1.0.0/16 の VPC
  • アベイラビリティゾーン:ap-northeast-1a , ap-notheast-1c
  • サブネット:プライベートの 10.1.1.0/24 と 10.1.2.0/24

ターゲットグループ

  • ターゲットグループ:新しいターゲットグループ
  • 名前:適当に
  • ターゲットの種類:IP
  • プロトコル:TCP
  • ポート:80
  • ヘルスチェック
    • プロトコル:TCP
  • ヘルスチェックの詳細設定:お好みで

ターゲットグループ配下にコンテナ郡がぶら下がります。Fargate ですので、コンテナは ENI に関連付けされます。なので、ターゲットは IP を選択しましょう。

ターゲットの登録

  • IP(許可範囲):ブランク
  • ポート:80

ECS Fargate をデプロイする

続いて、最初に push したコンテナを Fargate にデプロイします。このタイミングで先に作成した NLB のターゲットグループに登録します。

タスクロールを作成する

その前に、ECS のタスクに付与するロールを作成します。以下のポリシーをアタッチしたロールを適当に作成します。

  • AmazonEC2ContainerREgistryPowerUser
  • AmazonECSTaskExecutionRolePolicy

タスク定義を作成する

新しいタスク定義の作成を開き、 FARGATE を選択します。

  • タスク定義名:適当に
  • タスクロール:前項で作成したロールを指定します。
  • ネットワークモード: Fargate を選択してるので awsvpc になります。
  • タスクメモリ:0.5GB ( 最小でよいでしょう
  • タスク CPU :0.25GB
  • コンテナの定義 —> [ コンテナの追加 ] をクリックします。
    • コンテナ名:適当に
    • イメージ:ECR のイメージの URL を指定します。
    • プライベートレジストリの認証:チェックなし
    • メモリ制限:デフォルト
    • ポートマッピング:80
    • 以下、デフォルト
  • 以下、デフォルト

クラスターを作成する

クラスターの作成をクリックします。

  • [ネットワーキングのみ] を選択します。
  • クラスター名:適当に
  • ネットワーキング
    • VPC の作成:チェックなし
  • Tags:適当に
  • CloudWatch Container Insights:コンテナの各種メトリクスを取得する設定です。こちらもお好みでどうぞ。
  • [ 作成 ]

サービスを作成する

作成したクラスタをクリックし、画面を遷移します。[ サービス ] タブを選択し、[ 作成 ]をクリックします。

  • タスク定義: 前項で作成したタスク定義を選択します。
  • リビジョン: 特になければ最新を
  • プラットフォームのバージョン:こちらも最新を
  • クラスター:前項で作成したクラスターを選択
  • サービス名:適当に
  • タスクの数:2
  • 以下、デフォルト
  • VPC とセキュリティグループ
    • クラスターVPC:10.1.0.0/16 の VPC を選択します。
    • サブネット:PrivateSubnet: 10.1.1.0/24 , 10.1.2.0/24 を選択します。
    • セキュリティグループ: 自動で作成されますが、私は編集しました。この場合、VPC の CIDR からアクセス許可をインバウンドルールで作成します。プロトコルとポートはコンテナが使用するものにします。今回は TCP でポート 80 です。
    • パブリック IP の自動割当:プライベートサブネットなので DISABLED
  • ロードバランシング
    • ロードバランサーの種類:Network Load Balancer
    • ロードバランサー名:前項で作成した NLB を選択
    • コンテナの選択:前項で作成したクラスタを選択
    • [ ロードバランサーに追加 ]をクリック
    • プロダクションリスナーポート:80:TCP
    • ターゲットグループ名:前項で作成したターゲットグループを選択
  • サービスの検出
    • 以下、デフォルト
  • Auto Scaling
    • デフォルト
  • [ サービスの作成 ]

サービスの作成後、問題なければコンテンが 2 つ起動します。コンテナの起動に失敗する場合、ECR からイメージを pull できてない場合があります。Security Group まわり、Endpoint まわり、タスクロールのポリシーを確認してみてください。

API Gateway を作成する

NLB + Fargate ができたので、その前段に位置する API Gateway を作成します。

API Gateway 用の Endpoint を作成する

API Gateway を作成する前に API Gateway の Endpoint を作成します。Endpoint を作成する事で VPC 内から API Gateway にアクセスできるようになります。

  • VPC ダッシュボードより、[ エンドポイント ] – [ エンドポイントの作成 ]をクリックします。
  • サービスカテゴリ:AWSサービス
  • サービス名:com.amazonaws.ap-northeast-1.execute-api
  • VPC:10.1.0.0/16 の VPC を選択

API を作成する

Amazon API Gateway メニューを開きます。

  • [ API を作成 ]をクリックします。
  • Choose an API typ : プライベートな API を作成するので [ REST API private ] を選択し、 [ 構築 ]をクリックします。
  • プロトコル:今回は REST を選びます。
  • 名前と説明
    • API 名:適当に
    • エンドポイントタイプ:プライベート
    • VPC エンドポイント ID:前項で作成した API Gateway 用の Endpoint ID を入力します。

リソースポリシー

API Gateway のアクセスポリシーを設定します。今回はホワイトリスト形式で作成しました。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": "*",
            "Action": "execute-api:Invoke",
            "Resource": "arn:aws:execute-api:ap-northeast-1:<AWS ID>:<API ID>/*/*/*",
            "Condition": {
                "StringNotEquals": {
                    "aws:sourceVpc": [
                        "10.0.0.0/8"
                    ]
                }
            }
        }
    ]
}

大雑把に 10.0.0.0/8 と広く開けてますが、実際はアクセス元の IP に合わせて設定してください。

VPC リンクを作成する

メニューバーから[ VPC リンク] – [ + 作成 ] をクリックします。

  • 名前:適当に
  • ターゲットNLB:前項で作成した NLB を選択します。
  • [ 作成 ]

作成してしばらく時間がかかります。

GET メソッドを作成する

メニューバーから [ リソース ] – [ アクション ]をクリックします。アクションメニューから [ メソッドの作成 ]を選択します。

  • プルダウンから [ GET ] を選択し、チェックをクリックします。
  • 総合タイプ:VPCリンク
  • メソッド:GET
  • VPCリンク:前項で作成した VPC リンクを選択
  • エンドポイントURL:GET メソッドを実行する先の URL を記入します。今回は NLB の DNS を入力します。

テストを実行し、JSON がレスポンス本文に表示されれば成功です。これで、API –> NLB –> Fargate の通信が出来上がりました。

Route 53 を設定する

VPC Peering 先から API Gateway のにアクセスするために、API Gateway の DNS の名前を解決できなければなりません。API Gateway の DNS 名でホストゾーンを作成します。

<API GatewayID>.execute-api.ap-northeast-1.amazonaws.com

上記に似た DNS のはずです。

A レコードをエイリアスで作成し、エイリアス先を API Gateway Endpoint DNS にします。

vpce-****************..execute-api.ap-northeast-1.amazonaws.com

リゾルバを設定する

Route53 のインバウンドエンドポイント、アウトバウンドエンドポイントを作成します。インバウンドエンドポイントを作成すると IP が発行されます。

Route 53 の インバウンドエンドポイントを開きます。

  • エンドポイント名:適当に
  • VPC の選択:10.1.0.0/16 の VPC
  • セキュリティグループの選択: Peerig 先からアクセスを許可した Security Group を予め作成し選択します。
  • IP アドレス:AZ と サブネットを選択します。自動か指定かはお好みで

Route 53 の アウトバウンドエンドポイントも同様に作成します。

VPC Peering を設定する

VPC 10.4.0.0/16 の VPC と 10.1.0.0/16の VPC を普通にピアリングします。両 VPC の Subnet のルーティングテーブルを適切に設定します。

Peering 先 EC2 から API Gateway にアクセスする

リゾルバ設定で払い出された IP を EC2 の /etc/resolv.conf に追記します。

$ cat /etc/resolv.conf
nameserver 10.1.1.*
nameserver 10.1.2.*
nameserver 10.4.0.2

Route 53 に登録した A レコードにアクセスします。

$ curl http://<API GatewayID>.execute-api.ap-northeast-1.amazonaws.com
{"a":1,"b":2,"c":3,"d":4,"e":5}

Peering 先の EC2 から API Gateway にアクセスできました。

まとめ

構成の妄想は時間かからなかったのですが、試しに構築して、それを記事にするのにだいぶ時間がかかりました。TEC 系ブログを書いている人はすごいなぁと思います。

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です