ネットワークの閉じた環境で 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 系ブログを書いている人はすごいなぁと思います。