S3の静的WebサイトをEnvoyでホスティング
S3 に静的なコンテンツを配置して公開する場合、よく見られる構成は CloudFront とを組み合わせるパターンです。ほとんどの場合、これで問題ありません。しかし、一部のパターン、例えばエンタープライズで利用されるフロントエンドの場合には、VPN を通じたアクセスのみを許可している場合があります。このような場合、世界中の多数のユーザに低レイテンシーな配信を目的としている CloudFront のような CDN が適切とは言い難いものとなります。
こうした場合、Nginx のような HTTP サーバ製品を利用することもできますが、より軽量な Envoy を利用する例をここでは説明します。
Envoy はとにかく軽量かつ高機能で、そのため Kubernetes 環境ではデータプレーンのサイドカーとして多用されている Proxy です。Envoy 自体は、通常のプロセスとしても、Docker や Pod のようなコンテナとしても実行することができ、そのため AWS App Mesh では、同社の Kubernetes 環境である EKS 以外に EC2 や ECS もサポート対象となっています。
S3 バケットの作成
まず、静的コンテンツを配置するための S3 バケットを作成します。プロダクション環境では、AWS PrivateLink を使って VPC 外から S3 へのアクセスを遮断すべきですが、ここでは簡単に試してみたいため、パブリックアクセスで読み取りを許可するようにします。
例では、バケット名を example-bucket
として、バケットポリシーは下のようになります。
{ "Version": "2012-10-17", "Statement": [ { "Sid": "Statement1", "Effect": "Allow", "Principal": "*", "Action": [ "s3:GetObject", "s3:GetObjectVersion" ], "Resource": "arn:aws:s3:::example-bucket/*" } ] }
作成したバケットに index.html
をアップロードしておいてください。
確認のため、ブラウザで次の URL にアクセスしてみます。
https://example-bucket.s3.amazonaws.com/index.html
index.html
が表示されることを確認したら、次のステップに進みます。
Envoy Proxy の定義ファイルの記述
front-envoy.yaml
というファイルを作成します。
static_resources: listeners: - address: socket_address: address: 0.0.0.0 port_value: 8080 filter_chains: - filters: - name: envoy.filters.network.http_connection_manager typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager codec_type: AUTO stat_prefix: ingress_http route_config: name: s3_route virtual_hosts: - name: static_web domains: - "*" routes: - match: prefix: / route: cluster: s3 auto_host_rewrite: true regex_rewrite: pattern: google_re2: {} regex: '^(.*)\/$' substitution: '\1/index.html' http_filters: - name: envoy.filters.http.router clusters: - name: s3 type: LOGICAL_DNS dns_lookup_family: V4_ONLY load_assignment: cluster_name: s3 endpoints: - lb_endpoints: - endpoint: address: socket_address: address: example-bucket.s3.amazonaws.com port_value: 443 hostname: example-bucket.s3.amazonaws.com transport_socket: name: envoy.transport_sockets.tls admin: address: socket_address: address: 0.0.0.0 port_value: 9901
example-bucket.s3.amazonaws.com
は作成したバケット名に合わせて修正してください。
ここでのポイントは次のようになります。
- S3 のエンドポイントの IP アドレスだけでは、バケット名が何かがわからないため、
auto_host_rewrite: true
を設定して、ホスト名が S3 に渡るようにしています。 - URL のパスが
/
の時に、/index.html
にリライトするためのregex_rewrite
を設定しています。 - S3 の IP アドレスが変化することも想定して、
type: LOGICAL_DNS
に設定しています。 - S3 のエンドポイントは現時点では IPv4 のみと想定されるため、
dns_lookup_family: V4_ONLY
を指定しています。 - 1 と同じ理由で、
hostname
を設定しています。
実行
docker
でも nerdctl
でも実行可能です。ここでは、nerdctl
でのコマンド例ですが、nerdctl
を docker
に置き換えれば Docker でも同様に実行可能です。
nerdctl run -it --rm --name envoy -v `pwd`/front-envoy.yaml:/etc/front-envoy.yaml -p 8080:8080 -p 9901:9901 envoyproxy/envoy:v1.21-latest -c /etc/front-envoy.yaml --service-cluster front-proxy
Envoy Proxy が機能しているかを確認するためには、次の URL でできます。
http://localhost:8080/
Envoy Proxy を利用するメリット
Envoy Proxy を利用すると、ここで紹介した静的 Web サイトのホスティングだけでなく、以下のような機能やその他多くの機能を提供することができます。
- OAuth2 や OpenID Connect 等の JWT を使うユーザ認証
- Open Policy Agent 等を使った RBAC によるユーザ認可
- A/B テスト、カナリア
また、「EnvoyをFront Proxyとして利用する」 に、次のようなメリットも記述されています。
control-planeやファイルベースのdynamic configurationが利用できる
よりセキュアなアクセスの実現 (2022年3月25日追記)
ここまでは、S3 バケットをパブリックアクセスを有効にしていました。S3 バケットのパブリックアクセスを許可することによる事故が絶えないため、公開設定にしていると常に警告が表示されます。パブリックアクセスを無効にしたい場合は、AWS リクエスト署名を行う必要があります。Envoy proxy には、このためのフィルターも用意されています。このフィルターの署名にはリクエスト PATH の値も含みますが、route の regex_rewrite により、"/" を "/index.html" のように書き換える前に署名が行われるため、前述の設定ではうまく機能しません。
署名フィルターの前に、Lua スクリプトを使うフィルターを設定して実現することにしました。設定ファイル全体は、GitHub を参照してください。
http_filters: - name: envoy.filters.http.lua typed_config: '@type': type.googleapis.com/envoy.extensions.filters.http.lua.v3.Lua inline_code: | function envoy_on_request(request_handle) path = request_handle:headers():get(":path"):gsub("/$", "/index.html") request_handle:headers():replace(":path", path) end - name: envoy.filters.http.aws_request_signing typed_config: '@type': type.googleapis.com/envoy.extensions.filters.http.aws_request_signing.v3.AwsRequestSigning service_name: s3 region: ap-northeast-3 use_unsigned_payload: true host_rewrite: ${BUCKET_URL} - name: envoy.filters.http.router
最後に、ここで紹介した設定ファイル等が GitHub リポジトリ にあります。