API Gateway経由でのみアクセス可能なAPIをECSで構築する

API Gatewayでのみパブリックアクセス可能なAPIをECSで構築する方法です。構成はこの図のようになります。

Untitled Diagram-5.png

ECSのロードバランサにはApplication Load Balancer(ALB)の方を使用することも多いと思いますが、この図のNetwork Load Balancer(NLB)をALBに変えることはできません。API GatewayからVPC内のプライベートAPIに接続するにはVPC Linkを介する必要があり、VPC LinkはNLBにしか対応していないためです。

NLBだとALBのように細かい制御はできませんが、そこはAPI Gatewayの側で行えば問題ないでしょう。



前提ECSタスク

タスクに登録されているコンテナの定義は以下のようになっており、

http://サーバ名:8080/hello

にGETでアクセスすると Hello3 と返すだけのものです。



NLBの作成

最初にロードバランサーを作成します。


ロードバランサーの設定

「スキーム」に「内部」を指定し、サブネットもプライベートサブネットを指定することで、内部向けNLBとして作成します。

image.png

API GatewayからNLBまでの通信が保護されているという明確な情報が見つけられなかったため、この手順ではリスナーは一応TLSにしておきます。べつに通常のTCPリスナーでも問題ないかもしれません。通常のTCPリスナーなら、ドメイン登録やTLS証明書の取得は不要になります。


セキュリティ設定の構成

TLSの場合、あとでNLBに割り当てるドメイン名に対応する証明書を割り当てておきます。

image.png

この例では、*.app.{秘密}.net のワイルドカード証明書を指定しました。


ルーティングの設定

ルーティングの設定では、ターゲットの種類を IP、プロトコルを TCP、ポートを転送先のポート(この例では 8080)にしておきます。

image.png


ターゲットの登録

ターゲットは何も登録しません。

image.png


作成完了

エラーが出なければOKです。

image.png


NLBをDNSに登録

NLBをDNSに登録します。Route 53 を使う場合は、Create Record Set からAliasとして登録します。

この例では、app-1.app.{秘密}.net ドメインをNLBに割り当てました。



ECSの設定

ECSコンソールから、対象のサービスを作成します。


サービス設定

このあたりは何でもかまいません。例ではFargateを使用しています。

image.png

image.png


ネットワーク構成

サブネットはプライベートを選択します。今回の構成とは関係ありませんが、ECSインスタンスは外部にアクセスできる必要があるため1、パブリックIPをDISABLEDにする場合は、NATゲートウェイなどが別途必要です。

image.png

セキュリティグループの設定では、NLBのターゲットがIPなので、NLBのIPアドレス範囲からアクセス可能なように制限を行います。2

image.png

ELBタイプでNetwork Load Balancerを選択します。

ELB名で作成したNLBを選択し、アクセス振り分け先のコンテナを選択して「ELBへの追加」ボタンを押します。

ターゲットグループ名で、作成済みのターゲットグループを選択します。

他の項目は勝手に設定されます。


作成完了

後の項目は任意で設定し、サービスを登録します。タスクが正常に起動すればOKです。

image.png

プライベートネットワーク内からは、NLBに割り当てたドメインでAPIを呼び出すことができるようになります。



API Gatewayの設定

NLBは外部からアクセスできないため、この時点では外部からAPIにアクセスできません。API Gatewayを経由して外部からアクセスできるように設定を行います。


VPC Linkの作成

API Gatewayのコンソールの「VPC リンク」メニューから、作成したNLBを対象とするVPCリンクを作成します。

ステータスが「利用可能」になるのを待ちます。数分時間がかかります。


APIメソッドの設定

API GatewayのAPIメソッドの設定で、NLB経由でECSタスクにアクセスするよう設定します。

「統合タイプ」で「VPC リンク」を選択し、「VPC リンク」で作成したVPCリンクを選択します。そして、エンドポイントURLに https://{NLBに割り当てたドメイン}/{APIのパス} を指定します。

image.png

設定したら、デプロイして有効化します。

発行された呼び出しURLを確認します。

image.png



呼び出し確認

curlコマンドなどで、発行された呼び出しURLにアクセスしてみます。



どうしてもALBでロードバランスしたい場合

API Gateway経由からしかアクセスさせたくないが、どうしてもNLBではなくALBでロードバランスしたい場合に考えられるやり方です。


内部向けALBの場合

以下で紹介されているように NLB->ALB の構成とし、ALBのIPアドレス変更ごとにNLBの宛先を変更するようにします。

https://qiita.com/naomichi-y/items/9840400e7833175d706b

ただ、AWSのブログには「プロダクションで使う前に必ずテストしてね!」と書いてあるので、本当にちゃんと安定して動くのかはよくわかりません…。


公開ALBの場合

この場合、公開ALBを、API Gatewayのみからアクセス可能にする必要があります。2019年3月時点では、以下の方法が考えられます。


  • API Gateway からバックエンドの公開ALBへのリクエストに、X-API-Key: {ランダムで推測不可能な文字列} のように適当なHTTPヘッダで認証情報をセットします。

  • ALB側でそのHTTPヘッダの値をチェックし、API Gateway側にセットした文字列と同じ文字列が無ければアクセスを拒否するようにします。以下の方法があります。


    • ALBのルール「HTTPヘッダー」で、値がAPI Gateway側にセットした文字列と一致した場合のみECSサービスにリクエストを転送するようにします。例:
      image.png

    • ALBにはAWS WAFが設定できるので、WAFで「ヘッダ X-API-Key がAPI Gateway側にセットした文字列と一致した場合のみアクセスを許可する」ルールを設定します。



認証情報を渡すので、HTTPS前提です。API全体を単一のプレーンテキストで守り、AWSコンソール上から値が丸見えなので、あんまり安全な感じもしません。





  1. https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ECS_instances.html "コンテナインスタンスには、Amazon ECS サービスエンドポイントと通信するために外部ネットワークアクセスが必要なため、コンテナインスタンスにパブリック IP アドレスがない場合は、NAT (ネットワークアドレス変換) を使用してこのアクセスを提供する必要があります。" 



  2. https://docs.aws.amazon.com/ja_jp/elasticloadbalancing/latest/network/load-balancer-target-groups.html#target-type "ターゲットを IP アドレスで指定する場合、送信元 IP アドレスはロードーバランサノードのプライベート IP アドレスとなります。"