S3で色々お試しして遊んでみる(VPCエンドポイント＆静的Webホスティング性能編)

# はじめに
S3。単なるマネージドでサーバレスなバックエンドストレージなだけでなく(いや、それだけでも充分すごいんだけど)、暗号化できたり、セキュアアクセスができたり、ファイルの中身にクエリ発行できたり、ウェブサイトのホスティングができたり、イベントトリガになったり、なんだか色々できてすごいぞ！

これを使い倒さずに、毎回「とりあえずバケット作っておくか」な使い方をしているだけではもったいない！

ということで今回は、色々ある機能の内、VPCエンドポイントと静的Webホスティングの機能について触れてみる(いずれもさわりだけなので、たいした検証にはなっていないのだが……)。

# VPCエンドポイント
VPCエンドポイントの詳細は[公式](https://docs.aws.amazon.com/ja_jp/vpc/latest/userguide/vpc-endpoints.html)を見てもらうと良い。
要するに、S3のAPIはインターネットに口を向けているので、何も考えずにVPCからaws cliを実行したりすると、インターネットを通ってしまう。内部通信をしたいのに！というときに利用するものである。VPCエンドポイントは色々なAWSサービスに対応しているが、S3とDynamoDBは追加料金不要(ほかのサービスはPrivateLinkを利用するためそこそこのお金がかかる)！これは是非使っておこう。

## Terraformでの設定
マネージメントコンソール画面ではなくていきなりIaCのことを言い出すのは結構アレではあるが、実際やってみると、画面で設定するのも大して変わらないので、さっさと自動化出来てしまった方が手っ取り早い。

予めVPCとルートテーブルは作成済みで、データリソースで参照できるようにしていることが前提だ。

```HCL
resource "aws_vpc_endpoint" "s3" {
  vpc_id       = data.aws_vpc.my.id
  service_name = "com.amazonaws.ap-northeast-1.s3"
}

resource "aws_vpc_endpoint_route_table_association" "s3" {
  route_table_id  = data.aws_route_table.my.id
  vpc_endpoint_id = aws_vpc_endpoint.s3.id
}
```

これだけでOK！簡単！

## 性能差を確認する
さて、インターネット通信を内部に閉じるのだから、おそらくレイテンシも解消されるのであろう。
一石二鳥だ！

と思い、上記のVPCエンドポイント作成後にVPC内のLambdaからboto3でのアクセスとインターネット経由でのアクセスを両方試してみた。boto3でのアクセスは、VPCエンドポイントを作る前はエラーになっていたので、おそらくVPCエンドポイントを通っているのだろう。

余談ではあるが、VPC内のLambdaは起動時にENIを作成するため、以下のページにあるようにIAMポリシにパーミッションを追加する必要があるので要注意だ。

【AWS公式】[VPC 内のリソースにアクセスするための Lambda 関数の設定](https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/configuration-vpc.html)

さて、これでVPCから起動したLambdaで測定ができるぞ。
以下は、VPCエンドポイントへのアクセス後、静的WebホスティングしたS3へアクセスするという順序で20回計測した結果の平均だ。

|VPCエンドポイント|静的Webサイトホスティング|
|:--|:--|
| 329.4msec | 63.8msec |

……あれ？なんかインターネットを経由する静的Webサイトホスティングの方が早いぞ？
もしかして、順序がいけないのだろうか。2回目のアクセスはどこかのキャッシュが機能してしまって早いとかか(そんな話は聞いたことが無いが)？

ということで、VPCエンドポイントのアクセス2回、静的WebホスティングしたS3へアクセス2回を1回のLambda関数内で実行して、それぞれの2回目のアクセスを20回計測して平均を取った。

|VPCエンドポイント|静的Webサイトホスティング|
|:--|:--|
| 154.9msec | 45.0msec |

うーむ、どちらも2回目の方が早くなった。謎……。
一石二鳥にはならないようだ。

# 静的Webサイトホスティング
静的Webサイトホスティングは、名前の通り、S3に静的コンテンツを突っ込んで置いたらWebサーバを立てなくてもHTTPリクエストに応答してコンテンツを返してくれる機能だ。ちょっとしたコンテンツならこれでサーバレスに賄えてしまう。便利！

ここではサクッとホスティング機能を設定することだけ書いているが、商用で運用する場合はアクセスログの記録やアクセス制御をちゃんとやろう。

## マネージメントコンソールでの設定
マネコン画面では以下の画面から設定が可能。簡単！

![キャプチャ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/6462696d-3f04-3019-eb1b-b2a62aee24ed.png)

## Terraformでの設定
以下のように```aws_s3_bucket```に関連したリソースを設定すれば良い。

まずは、ACLの設定を有効にする。
この設定は、以下の他に```BucketOwnerEnforced```と```ObjectWriter```の2種類が使用可能だが、```BucketOwnerEnforced```はACLを無効化してしまって静的Webサイトホスティングの設定時にエラーになってしまうので、```BucketOwnerPreferred```か```ObjectWriter```を設定しよう。

```HCL
resource "aws_s3_bucket_ownership_controls" "example" {
  bucket = aws_s3_bucket.example.id

  rule {
    object_ownership = "BucketOwnerPreferred"
  }
}
```

次に、バケットポリシー側でもACLを有効にしておく。

```HCL
resource "aws_s3_bucket_public_access_block" "example" {
  bucket = aws_s3_bucket.example.id

  block_public_acls       = false
  block_public_policy     = false
  ignore_public_acls      = false
  restrict_public_buckets = false
}
```

とはいえ、上記でACLをブロックしない状態にしたことで、もろもろ素通しにしてしまうため、GetObject以外は行わせないようにする。

```HCL
resource "aws_s3_bucket_policy" "example" {
  depends_on = [ aws_s3_bucket_ownership_controls.example ]

  bucket = aws_s3_bucket.example.id
  policy = data.aws_iam_policy_document.example.json
}

data "aws_iam_policy_document" "example" {
  statement {
    principals {
      type        = "*"
      identifiers = ["*"]
    }
    actions   = ["s3:GetObject"]
    resources = ["${aws_s3_bucket.example.arn}/*"]
  }
}
```

最後に、静的Webサイトホスティングの設定を行う。
難しくはない手順だが、少しクセがあるので、何の設定が作用するのかをしっかり理解しておこう。

```HCL
resource "aws_s3_bucket_website_configuration" "example" {
  bucket = aws_s3_bucket.example.id

  index_document {
    suffix = "index.html"
  }

  error_document {
    key = "error.html"
  }
}
```

:::note info
以前の記事の内容から2023年4月にAmazon S3に行われた仕様変更への対応を行った形で修正した。
詳しくは、以下のクラスメソッド先生の記事を参照。
:::

https://dev.classmethod.jp/articles/s3-acl-error-from-202304/

## 静的Webサイトホスティングの素性能
さて、では静的Webサイトホスティングはどこまで自動でスケールしてくれるのだろうか？
ひとまず、Locustを使って計測してみる。

ちなみに、Locustはt3.xlargeなEC2上でワーカーノードを3つ動かしている。
この状態で、200ユーザを起動し、150ミリ秒のウェイトを挟んでトラフィックを投げ込んでいる。
コンテンツサイズは一律で50KB程度。

![number_of_users_1597459626.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/4f319c49-270c-b0c4-bd59-7dfa3c40e7bf.png)
![total_requests_per_second_1597459626.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/1b1a43bc-33c2-e237-1a93-1786e618f753.png)
![response_times_(ms)_1597459626.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/264b0613-9afb-4f58-cea2-2c3de8e5f17c.png)

ご覧の通り、1100~1200rpsあたりでもまだまだResponse Timesが延びることなく安定して線形にスループットが延びている。ワーカーノードを増やせばまだまだ延ばせそうだ。

ちょっとしたトラフィックを捌くのであれば、CloudFrontを使うまでもなくそこそこに性能が出せるように思える。
S3すごい！