こんにちは。AWS CLIが好きな福島です。
はじめに
今回は、内部ALBとS3を利用し、プライベートなWebサイトを構築したいと思います。

参考情報
構成図
実際には、Direct ConnectやSite to Site VPNで企業の拠点から内部のALBにプライベート接続することを想定していますが、 今回は検証目的でパブリックサブネットのEC2から内部のALBにアクセスできるかを確認します。

構成のポイント
S3バケット名は、WebサイトのFQDNと同一にする
例えば、WebサイトのFQDNを test.example.com にする場合、S3バケット名も test.example.com にする必要があります。S3のVPCエンドポイント(Interface型)を利用する
ALBのターゲットとして、S3のVPCエンドポイントのIPを登録します。ALBのヘルスチェックの成功コードには200に加えて、307,405を設定する
ALBのターゲットにはS3のVPCエンドポイントをIPで登録するため、 ヘルスチェックのホストヘッダーにはドメイン(例えば、test.example.comなど)が含まれず、 200の成功コードが返ってきません。そのため、307,405の成功コードを追加します。
成功コードにおける補足
以下は、S3のVPCエンドポイントのIPに対して、GETとHEADメソッドでリクエストした際のログです。 GETメソッドの場合は、307、HEADメソッドの場合は、405のステータスコードが返ってきていることが分かります。
[ec2-user@ip-10-88-0-59 ~]$ curl -IX GET http://10.88.10.226 HTTP/1.1 307 Temporary Redirect x-amz-id-2: 9jRaZuSddQUcR6BLnwDu129sv6heJEqy8IWa/stESeLJpN3m57rlmNPjxV/qyNt7aconrwXVDEM= x-amz-request-id: KH5S2EFVFKNY2CX6 Date: Thu, 21 Mar 2024 00:34:04 GMT Location: https://aws.amazon.com/s3/ Server: AmazonS3 Content-Length: 0 [ec2-user@ip-10-88-0-59 ~]$ curl -IX HEAD http://10.88.10.226 HTTP/1.1 405 Method Not Allowed x-amz-request-id: 7V3Y1MW59P0NWMGM x-amz-id-2: ShU6PXWLOrvNOLY+nMG1JgyDQONakfaM2MKBgo7V3IgVvFrQ+qkErZKM7jslQSqbawNEIcsfn84= Allow: GET Content-Type: application/xml Date: Thu, 21 Mar 2024 00:34:05 GMT Server: AmazonS3 [ec2-user@ip-10-88-0-59 ~]$
また、以下はELBのターゲットであるWebサーバ(EC2)のログになりますが、 見る限り、GETメソッドが使われているため、S3へのヘルスチェックは307だけでも良いのかも知れません。 (今回は参考情報に従い、307,405を成功コードに追加します。)
[root@ip-10-88-0-59 httpd]# tail -f access_log 10.88.1.178 - - [21/Mar/2024:09:39:38 +0900] "GET / HTTP/1.1" 200 13 "-" "ELB-HealthChecker/2.0" 10.88.0.219 - - [21/Mar/2024:09:39:49 +0900] "GET / HTTP/1.1" 200 13 "-" "ELB-HealthChecker/2.0"
検証におけるポイント
ALBにアクセスする際のFQDNおよびS3のバケット名は揃える必要があります。
本来は、オンプレミスのDNSサーバーやRoute53にALBのFQDNのCNAMEやエイリアスを設定することになりますが、 準備が大変のため、今回の検証では、ローカル端末のhostsファイルを活用して検証を行います。
動作確認のイメージは以下の通りです。

手順
手順の流れは、以下の通りです。
- ①メインリソースのデプロイ
- ②ALBへのターゲット(S3のVPCエンドポイントのIP)登録 ※1
- ③S3バケットへのHTMLファイルアップロード
- ④ローカル端末のhostsファイル更新
- ⑤EC2を踏み台にALBへアクセスする
- ⑥動作確認
※1 ALBのターゲットには、S3のVPCエンドポイントのIPを登録するのですが、 CloudFormationでは、S3のVPCエンドポイントのIPを取得できないため、手動で実施します。
①メインリソースのデプロイ
- Git から Clone
git clone git@github.com:kazuya9831/blog-sample.git
- ディレクトリの移動
cd internal-alb-and-s3
- 変数の設定
STACK_NAME="private-web"
- スタックのデプロイ
aws cloudformation deploy \
--stack-name ${STACK_NAME} \
--template-file private-web.yml \
--capabilities CAPABILITY_NAMED_IAM
※デプロイには4,5分程度かかります。
②ALBへのターゲット(S3のVPCエンドポイントのIP)登録
- S3のVPCエンドポイントのARNをCloudFormationのOutputから取得
S3_VPC_ENDPOINT_ARN=$(aws cloudformation describe-stacks \
--stack-name "${STACK_NAME}" \
--query "Stacks[].Outputs[?OutputKey=='S3VpcEndpointId'].OutputValue" \
--output text
)
- S3のVPCエンドポイントのIPアドレスを取得
S3_VPC_ENDPOINT_IP=$(aws ec2 describe-network-interfaces \
--network-interface-ids "${S3_VPC_ENDPOINT_ARN}" \
--query "NetworkInterfaces[].PrivateIpAddresses[].PrivateIpAddress" \
--output text)
- ELBのターゲットグループのARNをCloudFormationのOutputから取得
ELB_TARGET_GROUP_ARN=$(aws cloudformation describe-stacks \
--stack-name "${STACK_NAME}" \
--query "Stacks[].Outputs[?OutputKey=='ApplicationLoadBalancerTargetGroupArn'].OutputValue" \
--output text
)
- S3のVPCエンドポイントのIPアドレスをALBのターゲットに登録
aws elbv2 register-targets \
--target-group-arn "${ELB_TARGET_GROUP_ARN}" \
--targets Id="${S3_VPC_ENDPOINT_IP}",Port=443
③S3バケットへのHTMLファイルアップロード
- S3のバケット名をCloudFormationのOutputから取得
S3_BUCKET=$(aws cloudformation describe-stacks \
--stack-name "${STACK_NAME}" \
--query "Stacks[].Outputs[?OutputKey=='S3Bucket'].OutputValue" \
--output text
)
- S3のバケットにHTMLファイルをアップロード
aws s3api put-object \
--bucket ${S3_BUCKET} \
--key index.html \
--body index.html \
--content-type text/html
④ローカル端末のhostsファイル更新
- hostsに登録する情報を出力
echo "127.0.0.1 ${S3_BUCKET}"
- 上記コマンドの実行結果をローカル端末のhostsファイルに追記する
Windows: C:\windows\system32\drivers\etc\hosts Mac: /etc/hosts
⑤EC2を踏み台にALBへアクセスする
- EC2のインスタンスIDをCloudFormationのOutputから取得
EC2_INSTANCE_ID=$(aws cloudformation describe-stacks \
--stack-name "${STACK_NAME}" \
--query "Stacks[].Outputs[?OutputKey=='Ec2InstanceInstanceId'].OutputValue" \
--output text
)
- 内部ALBのDNS名をCloudFormationのOutputから取得
INTERNAL_ALB_DNS_NAME=$(aws cloudformation describe-stacks \
--stack-name "${STACK_NAME}" \
--query "Stacks[].Outputs[?OutputKey=='ApplicationLoadBalancerDNSName'].OutputValue" \
--output text
)
- SSMのリモートホストポートフォワード(AWS-StartPortForwardingSessionToRemoteHost)を利用
aws ssm start-session \
--target ${EC2_INSTANCE_ID} \
--document-name AWS-StartPortForwardingSessionToRemoteHost \
--parameters '{"portNumber":["80"],"localPortNumber":["10080"],"host":["'"${INTERNAL_ALB_DNS_NAME}"'"]}'
⑥動作確認
- HTMLファイルの確認
curl http://${S3_BUCKET}:10080/index.html
- 実行結果例
$ curl http://${S3_BUCKET}:10080/index.html
Internal ALB and S3 test
$
- リダイレクトされるかの確認
curl http://${S3_BUCKET}:10080
- 実行結果例
$ curl -I http://${S3_BUCKET}:10080
HTTP/1.1 301 Moved Permanently
Server: awselb/2.0
Date: Thu, 21 Mar 2024 08:20:31 GMT
Content-Type: text/html
Content-Length: 134
Connection: keep-alive
Location: https://test-XXXXXXXXXXXX.example.com:80/index.html
$
終わりに
今回は、内部ALB+S3でプライベートなWebサイトを構築する方法をご紹介いたしました。 どなたかのお役に立てれば幸いです。