参考にしたのはこちら
ただ、この例はInternal
なALBを用意し、そこから、Private Subnet
→ VPC Endpoint
+ privatelink → S3なので、別にPublic Subnet
に置いたALBでもいけたのが、今回の記事の内容です。
結論
VPC Endpoint(Interface)を使うとうまくいきました。
ALB → Vpc Endpoint
(Interface) → S3
何が嬉しいのか
- ALB → S3 間で認証を完結できる
SSO(シングルサインオン)等で、社内のみで見れるようにしたい社内サイトとか、認証をALBに集約したい時はとても嬉しいと思います。
OIDCを使用して、ALBに認証後、S3にルーティングすると、アプリ側で認証を実装する手間が減ります。
ALB → S3で認証が完結するということは、特にwordpressなどで構築されたサイトだと、プラグインや自作で認証を実装すると思うのですが、それが不要になります。
概要図
下記の図でいう 4 → 2 です
前提
- VPC(
Public Subnet
) - ALB
- S3(ホスト名と同一)
今回は、Public Subnet
のVPC Endpoint
をアタッチする例です。
Private Subnet
の場合は、別途 Privatelink
使用するようです。
手順
前提のALBとVPCがあれば、VPC EndpointのIPアドレスをターゲットグループに設定しましょう。
- VPC Endpointを作成
- VPC Endpoint作成後、IPv4 アドレスをコピー
- ALBのターゲットグループ作成(ターゲットの種類
IP
、 アドレスタイプIPv4
) - ターゲットグループの
IPアドレス
に2
でコピーしたIPアドレス
を設定 - ターゲットグループのヘルスチェックを
307,405
にする - 終了
これで、ALB → S3が設定できました。
ユースケース
- ユーザー数少ないけど、認証が必要なアプリ。
- サーバーサイドで認証実装できない(or 面倒)
パフォーマンス向上
- 規模が大きくなってALBから出ていく通信料が気になるようになったら、ALBの前にCloudFront
- ALB - S3間に FileCache、Storage Gatewayをキャッシュサーバーとして検討(未検証。条件:ALBのターゲットグループ設定、CDK対応)
注意
-
S3のバケット名
手順どおりやっても、S3のバケット名が、ホスト名と同一にならないと見つからないです。
例えば、https://sample-test.co.jp
であれば、バケット名はsample-test.co.jp
です。 -
通信料
もし動画や大きなファイルを扱うのであれば、通信料も大きくなっていくので、ALBの前段にCloudFront
を置くか、ALB - S3
間の間にファイルキャッシュできるようなサービスをおくと通信料もレスポンスタイムも早くなるのでしょう。
CDKのコード
実はVpc Endpoint
から、IPアドレスとるの難しいんだなと。。。
Vpc Endpoint
から簡単にとれるように貢献したいところ。。。
// vpc endpoint 作成
const vpcEndpoint = vpc.addInterfaceEndpoint("InterfaceVpcEndpointForS3", {
service: InterfaceVpcEndpointAwsService.S3,
subnets: { subnetType: SubnetType.PUBLIC },
privateDnsEnabled: false,
});
// vpc endpoint から IP Addressを取得
const getEndpointPrivateIpAddress = (index: number) => {
const privateIpAddressField = `NetworkInterfaces.${index}.PrivateIpAddress`;
const resource = new AwsCustomResource(this, `GetEndpointIp${index}`, {
onUpdate: {
service: "EC2",
action: "describeNetworkInterfaces",
outputPaths: [privateIpAddressField],
parameters: { NetworkInterfaceIds: vpcEndpoint.vpcEndpointNetworkInterfaceIds },
physicalResourceId: PhysicalResourceId.of(privateIpAddressField),
},
policy: AwsCustomResourcePolicy.fromSdkCalls({ resources: AwsCustomResourcePolicy.ANY_RESOURCE }),
});
return resource.getResponseField(privateIpAddressField);
};
// publicSubnets を取得
const { publicSubnets } = vpcEndpoint.node.scope as Vpc;
// publicSubnets の個数 を取得
const ipAddresses = publicSubnets.map((_, index) => Token.asString(getEndpointPrivateIpAddress(index)));
// ALB の ターゲットグループを作成
const vpcEndpointTargetGroup = new ApplicationTargetGroup(this, "VpcEndpointTargetGroup", {
vpc,
targetType: TargetType.IP,
port: 443,
protocol: ApplicationProtocol.HTTPS,
healthCheck: {
healthyHttpCodes: "307,405",
},
});
// vpc endpoint の IPアドレス ALB の ターゲットグループに設定
for (const ipAddress of ipAddresses) {
vpcEndpointTargetGroup.addTarget(new IpTarget(ipAddress));
}
CDKのほうが大変でした。。。
もっと簡単に、作ったVPC endpoint
からIPアドレス取得できる方法あったら教えてくださいmm