Reverse ProxyをNLBとECSで構築する
概要
クラウドでサービスを展開していてIP固定化したいことってないですか?
いや、ありますよね!
弊社サービスではCloudfrontを利用しているのですが
Cloudfrontは全世界にエッジロケーションが存在しており、
IPの範囲も膨大であり、利用するIPも不定期に変わります。
なお、変更検知することは可能みたいなので変更があったときのアクションを制御可能であれば
以下方法でもいいかと思います。
構築完了までに色々ハマった箇所があったのでこれから似たような要件を
対応する人の参考になればと思いTIPSを紹介させていただきます。
実際の構築はCDK For Terraform のTypeScriptで実装しましたが、
今回コードの紹介はスコープ外とし、また機会があれば別途記事にしたいと思います。
設計
固定IPを利用するにはAWS EIPを利用する前提になるため、以下構成で考えました。
(IPの固定化はAWSだとGlobalAcceleratorというのもあるのですが要件が合わないため見送りにしています。)
- Network Load Balancer (ALBでは固定IPが設定できないため)
- ECS Fargate (EC2でもいいのですが、プロビジョニングや運用の手間を考えECSを採用しました)
- CodeBuild ECSデプロイに利用 本当はGitHubActionsにしたかったのですがCodebuildを採用した理由は後述します。
- Reverse ProxyのミドルウェアはNginxにしています。
構成図
TIPS
Fargate SPOT はARM対応していない
よくマニュアル読めという話題でもあるのですが、
少しでも安く運用するためFargateSpotを採用しようとしたところ、
2024/03/28時点ではFargateSpotは対応していませんでした。
そのためFargateで固定するようにしました。
ECSへのプロビジョニング時にECRからコンテナイメージがダウンロードできない
ECS Cluster の初期設作成時にPublic IPの自動割り当てを選択できるのですが、
NLBを前段にかまえる想定だったので、不要としていました。
そうするとECRからコンテナイメージがダウンロードできない事象が発生したため
自動割り当ての有効化を行いました。
なお、自動割り当ての変更はCLIから実施する必要があります。
GitHubActionsのランナーがARM対応していない
今後対応する予定らしいですが
2024/03/28時点では対応していませんでした。
そのためGitHubActions上でBuildしてECRにアップロードしても
ECSのタスクがARM指定しているためコンテナが起動しない状態になってました。
そのためGitHubActionsでのデプロイは見送りしARM版のCodeBuildを採用しました。
実装内容もシンプルで以下のようなスクリプトを作ってCodebuildで実行させてあげるだけです。
#!/bin/sh
_AWSACCOUNTID=123456789012345
_ENV="reverseproxy"
aws ecr get-login-password --region ap-northeast-1 | docker login --username AWS --password-stdin ${_AWSACCOUNTID}.dkr.ecr.ap-northeast-1.amazonaws.com
docker build -t ${_ENV}-ecr .
docker tag ${_ENV}-ecr:latest ${_AWSACCOUNTID}.dkr.ecr.ap-northeast-1.amazonaws.com/${_ENV}-ecr:latest
docker push ${_AWSACCOUNTID}.dkr.ecr.ap-northeast-1.amazonaws.com/${_ENV}-ecr:latest
aws ecs update-service --cluster ${_ENV}-ecs-cluster --service ${_ENV}-service --force-new-deployment
ベースイメージのダウンロードエラー
GitHubActionsでもdockerの認証情報を利用することで
ARMでもビルドできるらしいのですが、GitHubActionsの公式対応を待つことにしていたため
DockerHubの認証情報は利用していませんでした。
ただ、そうするとDockerHubのRate Limitにひっかかってしまい、
IPガチャといわれる事象が発生してしまいます。
- IPガチャとは
非認証ユーザ 100 pull / 6 hourまで利用可能なのですが、
Codebuildが外部接続するIPは全世界共有になるため、
制限にかかってしまいやすくなるという事象です。
そのためまだ制限がかかっていないIPでCodebuildが実行されるまで
再実行する。。みたいな対処方法がありそれがIPガチャといわれる所以です。
ただ、IPガチャはECR のPublic Imageを利用することで回避可能です。
以下のようにベースイメージを指定している箇所を変更することで
DockerHubの制限からは解放されます。
- Before
FROM nginx
- After
FROM public.ecr.aws/docker/library/nginx:latest
AWS内からイメージをDLすることでレイテンシーも低下するみたいなのですが、
その部分の効果検証は行なっていません。
Nginxのログ
アクセスログを確認しようとしたところ
アクセス元のIPがNLBのIPになっていることに気づきました。
Nginxの設定をあれこれ弄ってみたものの一向に変わらず、
それもそうでNLBで関連つけているターゲットグループの設定変更が必要でした。
- TargetGroup設定 By CDK For Terraform
preserveClientIp: "true" // Client IPを保持するかどうか ログにClient IPが出力するため
boolenの値なので本来ダブルクォーテーションは不要なはずなのですが何故か囲わないとエラーになりました。
ちなみにReverseProxyの設定は以下のようにしています。
- nginx.conf
example.com
は実際のドメインに置き換えてください。
events {
worker_connections 1024;
}
http {
log_format main '$proxy_protocol_addr:$proxy_protocol_port $remote_addr:$remote_port $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" "$http_user_agent" $server_port';
access_log /var/log/nginx/access.log main;
server {
listen 80;
listen [::]:80;
server_name localhost;
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root /usr/share/nginx/html;
}
location ~ /(.*) {
resolver 8.8.8.8 8.8.4.4;
set $backend_server example.com;
proxy_pass https://$backend_server/$1$is_args$args;
proxy_ssl_protocols TLSv1.2;
proxy_ssl_server_name on;
proxy_ssl_name $backend_server;
proxy_set_header Host $backend_server;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_pass_header Authorization;
}
}
}
IP制限
セキュリティグループで443のみ、かつNLBのリスナーも443ポートのみしか作成していないのですが、
やたら攻撃を受けてました。
- ログ
-:- XX.XX.XX.XX:54780 - [26/Mar/2024:20:30:48 +0000] "GET /v4/ws/usdt HTTP/1.1" 200 2676 "-" "Go-http-client/1.1" 80
ポートスキャンから見つかった感じですかねー。
どうやって見つけているのか気になります。
あとステータスコードが200を返しているのも気にはなります。
WAFで対策してもよかったのですが、不特定多数のアクセスを
許容する必要性もないことからセキュリティグループにて
IP制限(特定のIPからのみアクセス可)することにしました。
まとめ
まだあったような気がするのですが、
それは思い出したら適宜追記していきます。
Discussion