AWS 上でマルチアカウントで複数サービスをホストする場合のアーキテクチャパターン (1)

#はじめに
AWS 上でマルチアカウントで複数サービスをホストする場合のアーキテクチャパターンを紹介します。
書いてみると改めて長くなってしまったので、この記事はシングルアカウントでのパターン紹介です。

#シングルアカウントの場合
まず、マルチアカウントの前に、シングルアカウント(AWSのアカウントIDが1つ。その上に複数サービスをホスト)の場合のパターンを考えてみます。


## ALBをフロントにして、ALBのルーティングでサービスを分けるパターン
構成図だとこんな感じです。
ALBのルーティング機能で、パスごとにリクエストを分けています。
この構成図だとシンプルにパスでルーティングを変えていますが、HTTP Header に応じて、ルーティング先を変えるといったことも可能です。

ルーティング先は、EC2でもコンテナでもLambdaでも、ALBのターゲットグループが対応しているリソースであれば、柔軟に変更可能です。



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107953/86d61539-4c85-9870-4562-2b78e330b556.png)


もしかすると、ALB + Lambda で「あれ？」と思われた方もいるかもしれません。
ALB + Lambda より、API Gateway + Lambda のパターンの方が多いからですかね？ それもやってみましょう。

## API Gateway(HTTP API)をフロントにするパターン
API Gatewayには、HTTP API と REST API の2種類あり、どちらも考えてみます。（正確にいうと、WebSocketにもAPI Gatewayは対応しています）

### パスごとにリソースを作成する方法
HTTP APIは、API Gateway のバックエンドに、内部ALB、CloudMap、内部NLBを指定できるため（REST APIは、NLBのみ）
パスごとに、リソースを分けるパターンです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107953/5ba14364-59bf-7a7d-8afa-843bb3cd3cdf.png)

App2はコンテナで実装しているので、ELBを利用せず Service Discovery(Cloud Map)を利用する図になっていますが、App1と同様にELBでも問題ありません。
ELBは App2専用のELBを作ってもいいですが、そうすると、最初の図のようにELBをApp1とApp2で共有したなくなってくることがあります。それが次の構成図です。


### ALBは共有するパターン
API Gatewayにて、path1とpath2向けのトラフィックは、共有のALBに渡します。
ALBでリクエストの中身を見て、path1ならApp1、path2ならApp2にトラフィックをルーティングするようにします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107953/511b81d8-9614-c6dc-2a06-512b99bd7800.png)


こうすると、最初のALBをフロントに置くパターンと同じでは？API Gatewayというコンポーネントが増えるだけなのではと思われるかも知れませんが、
API Gateway にはスロットリング、キャッシュ、認証機能（Cognito Userpool, OIDC連携、Lambdaを使ったカスタム認証)などALBにはない機能があるので、それらの機能が必要で、App側に極力実装せずに、API Gateway側にオフロードしたい場合には有効なアーキテクチャです。


## API Gateway(REST API)をフロントにするパターン
こちらも少し考えてみます。REST APIは、HTTP APIと違い現時点では、　内部用NLBとしか連携できないという制約があります。

### パスごとにリソースを作成する方法

このようにリソースごとにNLBを作成する必要があります。
(一応、App1とApp2でNLBで待受するportを変えると1つのNLBを共有することは可能ですが、ここでは分かりやすさを優先し、NLBを個別に作成しています）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107953/b0cf5e0d-3dbb-7ace-aee4-e92776f4d2df.png)


これを見ると、HTTP APIのほうが良さそうに見えますが、HTTP API は現時点で、AWS WAF に対応していなかったりなどいくつか制約があります。
REST APIのみで使える機能が必須の場合は、HTTP APIは使えません。


### （余談）REST API でバックエンドに ALB を使いたい場合

イメージとしては、以下のとおりですが、REST API と 内部ALBの連携機能はサポートしていないため、現時点ではできません。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107953/b200290e-821f-2b21-1c1f-028e673fb7a5.png)

じゃあ、どうするかというと、API Gateway --- Public な通信 ---- 外部用ALB な構成にすることで可能です。
こんな感じです。構成図をわかりやすくするために、app1/app2(path1/path2)だけにして、API GatewayもVPC外に出しました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/107953/eadd47cf-6d33-0d34-9fcf-9a52d5d3b56f.png)

こうすることによって、REST API を使って ALBを利用することが可能です。
注意するポイントとしては、前述の通り、 API Gateway -- ALB の通信はパブリック通信になります。
(パブリック接続といっても、ISP網などに出ることはなく、Public IPを使っているだけで AWS内で完結する通信になります)

クライアントがインターネットからAPI Gatewayへアクセスするアプリケーションの場合、
API Gateway -- ALBの通信が Public であること自体を気にすることはそれほど多くないのでは思います。

しかし、API Gatewayを private にしたり、VPC内で全ての通信を閉じるという要件があった場合は、
API Gateway -- ALBの通信が Public であること自体が難しい場合もあります。

### API GatewayとALB間がPublicになることが許容済みであった場合
仮に、REST API でバックエンドに ALB を使いたくて、API GatewayとALB間がPublicになることが許容済みであった場合でも、
次の注意ポイントとしては、ALBへの直接アクセスを禁止にする必要があります。
ALBはPublicにあるので、ALBへ直接アクセスできてしまえば、API Gatewayで認証やスロットリングをオフロードしてもなんの意味もなくなってしまいます。
つまり、なんらかの方法でALBへのアクセスはAPI Gatewayからのみ許可するようにしてあげる必要があります。


じゃあ、どうするか。
### ALBへのアクセス制御の方法
ALBへのアクセス制御の方法はいくつかありますが、以下の3つが多いような気がします

 - Security GroupによるソースIPアドレス制限
 - AWS WAFによる制限、（ソースIPアドレス、HTTP Headerの制限など）
 - ALBのruleによる制限（ソースIPアドレス、HTTP Headerの制限など)


今回のように API Gateway(REST API) -- pubic ALB の場合は、
ALBから見ると送信元IPアドレスは、アクセス元(クライアント)のIPアドレスではなく、API GatewayのIPアドレスになります。
API GatewayのIPアドレス帯は公開されていない（多分。もし、公開されていた場合でも、変更があった場合に追随するのが大変な）ため、ソースIPアドレスでALBへのアクセス制限を行うのは現実的ではないです。つまり、Security Group による制限は適切ではなさそうです。

その代わりに何をすればいいのかというと、
API Gatewayには 指定したHTTP Headerを付けて、バックエンドに渡すという機能があります。
(もっといいドキュメントがあったはずなので、見つかり次第差し替え予定)
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/how-to-method-settings-execution-console.html

これを使うことにで、API Gatewayは 特定の HTTP Header を付けて ALB へリクエストを送ることができるので、
ALBの前段に置いたAWS WAFで HTTP Headerの値をチェックして、問題なければ、ALBに処理させるというのが可能です。

AWS WAF を使わなくても ALB のRuleで、特定のHTTP headerがあるという条件をつけるという方法もあるのですが、

- 「特定のHTTP headerがある + /path1」 なら App1へ
- 「特定のHTTP headerがある + /path2」 なら App2へ

という具合に、それぞれのルーティングに「特定のHTTP headerがある」という条件を1つ１つ記載しないといけないので、
純粋にめんどくさいのと、ついつい付け忘れが発生しがちなのと、後述しますが、万が一、HTTP header自体を変更しないといけない自体が発生した場合は、
全ての Rule を変更しないといけない点がオペレーションの観点から煩わしいかもしれません。

なので、個人的には、AWS WAFの料金が許容できるなら、HTTP Headerのチェックは AWS WAFに任せるのが楽かなと思います。

### HTTP Headerの注意点
API Gateway で予め決めた HTTP Headerを付加して、ALBへのアクセス制限に用いるのは有効です。
（この方法は、CloudFront + ALB の構成で、ALBへの直接アクセスを防ぎたい場合にもよく使われる方法です）
ただ、これも同様に注意点が必要です。HTTP Headerによるアクセス制限は、合言葉と同じなので、なんらかの方法で第三者にバレてしまうと、ALBに直接アクセスされてしまうため、どういうHTTP Headerにどのような値を入れるのかといった管理は厳密に行う必要があります。
そして、万が一バレてしまった場合を想定して、合言葉(HTTP Header)を変更するオペレーションの練習もしておく必要があります。


以上で余談が長くなってしまいましたが、 REST API で バックエンドに ALB を利用する方法です。

# まとめ
（長くなってしまったので、マルチアカウントのパターンは2つ目の記事で書こうと思います）[書きました](https://qiita.com/asahi0301/items/c0035b424c4fe4b8d9a5)
この記事では、シングルアカウントを前提として、複数のサービスをホストするパターンをざっくり書いてみました。