CloudFront VPC Originと新しいログ形式への切り替えについて

# はじめに
先日、個人的に大きなCloudFrontのアップデートがあり、これからCloudFrontを使って構築する際のベストプラクティスが変わりそうな変更だったため、以下２点を紹介したいと思います。

- VPC Originの追加
- 新しいログ形式の追加

# VPC Originとログ出力の変更点について
いきなりまとめ。

:::note info
- VPC Originを使用することで、パブリックELBが不要となる
- VPC Originを使用することで、ELB用にSSL/TLSサーバー証明書を付与しなくてもよくなる
- 新しいログ形式に変更するとCloudFrontのログ出力用S3バケットのACLを有効化しなくてすむ
:::

## VPC Originについて
CloudFrontでEC2やECSのコンテンツを公開する場合、今まではインターネット向けのELBを作成し、EC2やECS等に負荷分散を行っていました。

その場合、CloudFrontとELB間はグローバル空間となり、リージョンも異なるため、それぞれのリージョンにSSL/TLSサーバ証明書を準備し、CloudFront、ELBに付与する必要がありました。

今回新しく登場したVPC OriginはCloudFrontとELB、EC2のサブネット間をPrivate Linkで結ぶことでプライベート向けのELBやEC2を直接オリジンとして指定できる機能となります。

また、CloudFrontとELB間は内部通信となるため、SSL/TLSサーバー証明書で通信路の暗号化を行わなくても安全に通信することができるようになったのが大きなポイントです。

![VPCOrigin.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/20ab4550-ef9d-2fda-e1b3-160fc95b88bf.png)

## 新しいログ形式について
CloudFrontのログをS3に出力する場合、今までは指定したプレフィックス（フォルダ）配下にGZ形式のログを出力することしかできなかったため、Athenaでログ検索する際、ログの数が増えるに従い、検索時間も費用もかかってしまっていました。

そのため、Athenaで検索する前提でログ出力する場合、今まではData FirehoseやLambdaを用いて年月日等のパスを付与するパーティショニングを行ったりする必要があり、ハードルが高かったと感じています。

今回実装された新しいログ形式でログ出力する場合、以下のようにData FirehoseやLambdaを使用しなくても直接パーティショニングを行うことができるようになったので、CloudFrontのログ運用のハードルがかなり下がりました。

![パーティショニング.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/e99b52fa-a0aa-2052-082a-14141bc91599.png)

また、CloudFrontのログをS3に出力する場合、AWSのベストプラクティスとしてはS3のACLを無効化することが推奨されておりますが、CloudFrontログを出力するS3バケットだけは例外的にS3バケットのACLを有効化しないとログ出力できませんでした。

しかし今回新しく追加されたS3出力形式に変更することでS3のACLを有効化しなくてもCloudFrontのログをS3バケットに出力できるようになったため、ようやくCloudFrontのログ出力だけ例外とすることなくACLを無効化することができるようになったのも大きなポイントです。

# VPC Originの作成
以下より実際にVPC Originを試すため、各種リソースを作成していきます。

## EC2の作成
CloudFrontのアクセスを受け付けるためのテスト用のEC2を作成します。

`Amazon Linux 2023`でHTTPアクセスを受け付けられるよう、以下のようにユーザーデータを設定して作成します。

```bash
#!/bin/bash
yum -y install httpd
systemctl enable --now httpd
cat << _EOF_ > /var/www/html/healthcheck.html
healthcheck
_EOF_
```

なお、VPC等は事前に作成しておいてください。

## VPC Origin設定用ALBの作成
`VPC Origin`でオリジンとして設定するALBの作成を行います。

- [ターゲットグループの作成](#ターゲットグループの作成)
- [内部ALBの作成](#内部albの作成)

### ターゲットグループの作成
後ほど作成するALBで指定するターゲットグループを以下のように作成します。

|項目|設定|備考|
|:--|:--|:--|
|ターゲットタイプの選択|インスタンス|先ほど作成したEC2を指定|
|ターゲットグループ名|vpc-origin-test-tg|任意の名前を指定|
|プロトコル:ポート|HTTP:80|デフォルトのまま|
|IPアドレスタイプ|IPv4|デフォルトのまま|
|VPC|※作成済みVPC|EC2と同じVPCを指定|
|プロトコルバージョン|HTTP1|デフォルトのまま|
|ヘルスチェックプロトコル|HTTP|デフォルトのまま|
|ヘルスチェックパス|/healthcheck.html|先ほどEC2作成時にユーザーデータで作成したヘルスチェックファイルを指定|
|使用可能なインスタンス|※先ほど作成したEC2|先ほど作成したEC2を指定して「保留中として以下を含める」を選択|




### 内部ALBの作成
VPC Originでのアクセスを受け付けるためのALBを作成します。

今回はインターネット経由でなくても内部でCloudFrontからアクセスできるかを確認するため、内部ALB指定で作成します。

次手順で内部ネットワーク経由でアクセスできるか確認するため、セキュリティグループは内部ネットワークからアクセスできる設定としておきます。

|項目|設定|備考|
|:--|:--|:--|
|ロードバランサータイプ|ALB|今回はALBを指定|
|ロードバランサー名|vpc-origin-test-alb|任意の名前を指定|
|スキーム|内部|VPC Originの動作を確認するため内部で指定|
|ロードバランサーのIPアドレスタイプ|IPv4|デフォルトのまま|
|VPC|※作成済みVPC|EC2と同じVPCを指定|
|アベイラビリティーゾーン|※作成済みAZ|EC2と同じAZを指定|
|セキュリティグループ|※内部ネットワークからのアクセスを受け付けられるSG|内部ネットワークからのアクセスを受け付けられるように設定したSGを指定|
|プロトコル|HTTP|デフォルトのまま|
|ポート|80|デフォルトのまま|
|デフォルトアクション|vpc-origin-test-tg|先ほど作成したターゲットグループを指定|

### ALB経由でのアクセス確認
ALBの設定が問題なければこの時点で状態がアクティブとなると思うので、アクセス確認を行っておきます。

今回は内部ALBでの作成となるため、以下２点を確認しておきます。

- インターネット経由でアクセスできないことを確認
- 内部ネットワーク経由でアクセスできることを確認

先程作成したALBのデフォルトDNS名宛に、インターネット経由でのアクセス確認は自分のPCから確認、内部ネットワーク経由での確認は先程作成したEC2とは別のインスタンスからcurlコマンドで確認します。

```bash:実行したcurlコマンド
curl http://internal-vpc-origin-test-alb-xxxxxxxx.ap-northeast-1.elb.amazonaws.com
```

結果としては想定通り、自分のPCからアクセスした場合はアクセス不可、内部に存在する別EC2からアクセスした際には以下のようにデフォルトの`It works!`のページが帰ってきました。

```bash:内部のEC2でcurlを実行した結果
<html><body><h1>It works!</h1></body></html>
```

なお、自分のPCから`nslookup`コマンドでデフォルトDNS名を引いてみると、プライベートアドレスが結びついていることが確認できます。

```bash:自分の操作PCから実行したnslookup結果
> nslookup internal-vpc-origin-test-alb-xxxxxxxx.ap-northeast-1.elb.amazonaws.com
Server:		192.168.0.1
Address:	192.168.0.1#53

Non-authoritative answer:
Name:	internal-vpc-origin-test-alb-xxxxxxxx.ap-northeast-1.elb.amazonaws.com
Address: 10.1.11.187
Name:	internal-vpc-origin-test-alb-xxxxxxxx.ap-northeast-1.elb.amazonaws.com
Address: 10.1.10.171
```

## CloudFront VPC Originの設定
ここまではVPC Originの設定を行うまでの前準備となりましたが、ここから実際にVPC Originを作成して、設定を行っていきます。

- [VPC Originの作成](#vpc-originの作成)
- [CloudFrontの作成](#cloudfrontの作成)

### VPC Originの作成
CloudFrontで`VPC Origin`によるオリジンを指定するためには、先にCloudFrontのダッシュボードに新しく追加された「**VPCオリジン**」からオリジンの作成を行っておく必要があります。

デフォルトだと「**プロトコル**」は「**マッチビューワー**」で選ばれていますが、今回CloudFront→ALB間は暗号化無しで設定しようと思うので、「**HTTPのみ**」としております。

![Monosnap_20241215_135746.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/bfe2c782-d152-b918-5307-7a1fed324040.png)

### CloudFrontの作成
CloudFrontディストリビューションの作成から今回は以下のように設定しました。

|項目|設定|備考|
|:--|:--|:--|
|Origin domain|※先ほど作成したVPC Origin|以下で説明|
|VPC origin domain|内部ALBのDNS名|ー|
|Origin path|空欄|デフォルトのまま|
|名前|vpc-origin-test-vpc-origin|任意のオリジンの名前を指定|
|Enable Origin Shield|いいえ|デフォルトのまま|
|パスパターン|デフォルト (*)|デフォルトのまま|
|オブジェクトを自動的に圧縮|Yes|デフォルトのまま|
|ビューワープロトコルポリシー|HTTP and HTTPS|デフォルトのまま|
|許可されたHTTPメソッド|GET, HEAD|デフォルトのまま|
|ビューワーのアクセスを制限する|No|デフォルトのまま|
|キャッシュキーとオリジンリクエスト|Cache policy and origin request policy (recommended)|デフォルトのまま|
|レスポンスヘッダーポリシー|空欄|デフォルトのまま|
|ビューワーリクエスト|関連付けなし|デフォルトのまま|
|ビューワーレスポンス|関連付けなし|デフォルトのまま|
|オリジンリクエスト|関連付けなし|デフォルトのまま|
|オリジンレスポンス|関連付けなし|デフォルトのまま|
|ウェブアプリケーションファイアウォール(WAF)|セキュリティ保護を有効にしないでください|今回はテスト用途のため設定しない|
|料金クラス|北米、欧州、アジア、中東、アフリカを使用|テスト目的のため適当に指定|
|Custom SSL certificate|空欄|今回はテスト用途のため設定しない|
|サポートされているHTTPバージョン|HTTP/2|デフォルトのまま|
|IPv6|オフ|今回は使用しないためオフとする|
|ログ配信|オン|後で説明するためオンに設定|
|Cookie logging|オフ|デフォルトのまま|
|Deliver to|Amazon S3（レガシー）|後でレガシーから新しいログ形式に変更するためレガシーに設定|
|送信先S3バケット|※CloudFrontログを出力するS3|
|ログプレフィックス|cloudfront|任意のプレフィックスを指定|

#### Origin domainの指定について
「**Origin domain**」は、先程作成したVPC Originのデプロイが完了していれば、以下のように選択できるため、「**VPC origins**」欄のVPC Origin名を指定します。

「**Elastic Load Balancer**」のALB名を指定するのではないのでご注意ください。

![Monosnap_20241215_142238.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/6a2cc44c-b638-7b46-3102-8f13976f3c0d.png)

#### S3レガシー設定について
ログについては後で説明したいためS3レガシーで設定しておきます。

出力先S3バケットを選択する必要がありますが、最近のS3バケットのデフォルト設定ではACL設定が無効化されており、S3レガシー出力設定ではログ出力できないため、無効化状態の場合は以下のように表示されます。

画面表示に従って「**ACLを有効にする**」を選択してログ出力できるようにしておきます。

![Monosnap_20241215_143853.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/b1acf1f4-4831-65b0-6531-1a55526cd275.png)

#### VPC Originの仕組み

「**VPC Origin**」の設定を行うと、指定のオリジンが存在するサブネットにENI（Elastic Network Interface）と、ENIに付随するセキュリティグループが作成されます。

![Monosnap_20241222_170525.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/922fb4bc-f80d-e06e-6ffc-3f1c64239086.png)

ENIに付随するセキュリティグループは以下のように、インバウンドルールは無し、アウトバウンドルールは全て許可するルールとなっているため、CloudFront→内部ELB間の通信のみ許可する設定となっております。

![Monosnap_20241222_170939.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/00e94fc2-988b-7b90-b56c-5865825a59ad.png)

![Monosnap_20241222_170954.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/a2378ddb-2a79-957a-711c-e7b40815d708.png)

CloudFrontで作成したVPC Originを指定することで、内部のサブネットに作成されたENIを経由して内部用として作成したALB等にアクセスできるようになります。

今までのようにグローバルネットワーク経由とはならず、CloudFrontから直接内部ネットワークに接続されることになるため、証明書を付与しなくても良くなったのが利点となります。

:::note info
ENI経由でもCloudFront→ELB、EC2間の通信路を暗号化したい場合は今までと同様証明書の付与が必要です。
:::

### ALBセキュリティグループの設定
今までの構成ではCloudFrontとELB間はグローバルのネットワーク空間となり、ELBへのアクセスをCloudFrontからのアクセスに限定するには、ELBのセキュリティグループのインバウンドルールでCloudFrontが使用するグローバルIPアドレスが登録されている、「**マネージドプレフィックスリスト**」を送信元として指定して制限したり、特定のカスタムヘッダーからのアクセスに制限するようなやり方を行う必要がありました。

VPC OriginではENI経由で内部ネットワークにアクセスすることから、ENIに付与されているセキュリティグループを送信元として指定できるため、ALB用のセキュリティグループのインバウンドルールにENIに付与されているセキュリティグループ（`CloudFront-VPCOrigins-Service-SG`）を以下のように指定します。

![Monosnap_20241230_190825.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/fd8930d4-5ee1-6db4-ecb6-1af4f65f5937.png)

CloudFrontが使用するIP自体は今までと変わらないため、今まで通り「**マネージドプレフィックスリスト**」を指定したセキュリティグループを設定しても良いですが、マネージドプレフィックスリストの場合、IPの数が多く、すぐにセキュリティグループの登録上限に達してしまうのと、ENIのセキュリティグループを指定したほうがより絞られるので、VPC Originを使用する場合はENIのセキュリティグループを指定したほうが良いと思います。

## VPC Originで指定したオリジンへのアクセス確認
上記までの設定でVPC Originを使用したCloudFrontの設定ができたので、CloudFrontのディストリビューションドメイン名を指定してアクセスしてみます。

![Monosnap_20241230_192415.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/5e41d616-edf7-64a0-6661-13060f1003c2.png)

上記のようにEC2で動かしているWebページが表示されれば完了です。

## 新しいログ形式への変更について
前述の通り新しいログ形式でログをS3に出力すると、今まで必要だったACLの設定が不要となり、さらにログに出力するフィールドを選択したり、パーティショニングを任意で指定したりすることができるため、Athena等でログの検索を行う場合にやりやすくなりました。

![Monosnap_20241222_172642.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/dd3608d6-cc1d-272c-fbf0-9cf9506a1095.png)

ただし、すでに今までの形式（レガシー）でS3にログ出力しているCloudFrontのログを新しいログ形式に変更する場合、今までのレガシー形式の設定画面から新しいログ形式に変更しようとしても、以下のようにグレーアウトされており、レガシー形式から新しいログ形式に直接変更することはできない仕様となっております。

![Monosnap_20241222_173247.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/bcfd48d5-3ced-b52b-1d3c-e506589d2a9f.png)

そのため、レガシー形式から新しいログ形式に変更する場合、次のような対応が必要となります。

## 新しいログ形式のログを出力する場所を新しく準備する場合
ログ設計や運用上、ログ出力先を変更しても問題ないようであれば、新しいログ形式用のS3バケットを用意するか、S3バケットは同じでプレフィックスを分けるのが一番楽だと思います。

CloudFrontのログ出力先は複数設定できるため、今までのログ出力（レガシー）と新しいログ出力、両方設定を行うことで、既存のログを残しつつ、新しいログ形式に変更することができます。

ずっと両方に出力すると二重に費用がかかるため、しばらく両方に出力してみて、問題ないようならレガシーのログ出力設定は削除するようにしましょう。

## ログ出力先は今までと同じでログ形式のみ新しいログ形式に変更する場合
既存の運用に影響を与えずにログ形式だけ変更する場合の例となります。

前述の通り、レガシー形式から新しいログ形式に直接変更することはできないため、今までのログを取りこぼさないようにしつつ、新しいログ形式に変更するためには、以下のようにステップを踏む必要があります。

ステップ数も多くなる分、ヒューマンエラーの可能性も増えるため、できれば別領域に出力するか、多少ログが欠けても了承して頂けるよう顧客調整するかしたほうが良いと思います。

なお、多少ログが欠けても問題ないようであれば、ステップ４→ステップ１→ステップ６の順番で実施すれば変更できます。

### ステップ１：新しいログ形式の出力設定を行う
レガシー形式と新しい形式でそれぞれ別々の場所に出力することができるため、新しいログ形式の出力場所を指定します。

ただし、いきなりレガシー形式のログ出力場所と同じ場所を指定すると重複してしまい、ログ保存できない可能性があるため、一時的な出力場所を指定します。

念の為、それぞれ同じログが出力されているか確認しておきましょう。

### ステップ２：レガシー形式の出力設定を変更する
同じログが出力されていることを確認したらレガシー形式のログ出力場所を変更します。

新しいログ形式の出力場所と被らないように適当な場所を指定します。

### ステップ３：新しいログ形式の出力設定を本来の出力場所に変える
ステップ２までの手順で、本来の出力場所にはログが出力されていない状態となるため、ようやく新しいログ形式の出力場所を本来の出力場所に変更します。

### ステップ４：レガシー形式の出力設定を削除する
ステップ３で本来のログ出力先への変更ができたため、不要となったレガシー形式の設定は削除します。

### ステップ５：一時的な出力場所に出力されていたログを移動する
ログ出力先変更のタイミングで一時的な出力場所にしか出力されていなかったログを本来の出力場所に移動します。

### ステップ６：S3バケットのACL設定を無効化する
レガシー形式のログ設定を削除したことで、ACL設定が不要となったため、S3バケットの設定からACLを無効化します。

無効化するには、まずCloudFrontの「**アクセス許可**」タブの「**アクセスコントロールリスト（ACL）**」から「**他のAWSアカウントのアクセス**」の削除を行う必要があります。

![Monosnap_20241230_193128.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/aeb50533-689d-ddd5-64e9-aab04c06b83b.png)

次に「**オブジェクト所有者**」から所有者を「**ACL無効（推奨）**」に変更することで無効化できます。

![Monosnap_20241230_193207.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/d22e888d-371b-d3fc-7208-76cef9284046.png)

## 新しいログ形式で設定する際に注意が必要なこと

:::note info
2024/12時点での内容となります。
今後変更される可能性もあるため、参考程度でご確認ください。
:::

新しいログ形式でプレフィックスを指定せずにパーティショニング設定をGUIから行った場合、デフォルトのパーティショニングのパス（`AWSLogs/{account-id}/CloudFront`）が頭に付与されてしまうため、プレフィックス設定を行うようにしましょう。

プレフィックス指定せず、パーティショニングのみでパスを指定したい場合、一度設定を行ってから、編集で頭に付与されたデフォルトのパスを削除して更新することで任意のパーティショニングのパスにすることができます。

![Monosnap_20241230_145646.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/569912/6f1f8c36-005d-814a-f386-41da042d6ba9.png)

# おわりに
今回のアップデートでEC2やECSへのアクセス方法が大分変わったため、今まで行ってきたCloudFrontの設計・構成を見直す必要が出てきたと感じました。