ALB + EC2環境で、CloudFrontを利用し画像は特定のパスでS3に振り分ける設定をする

株式会社[NoSchool](https://corp.noschool.asia)でCTOをしている@mejilebenです。
今日は画像サーバーとしてS3を利用したときのURLの振り分けと、ALBを初めて使ってみたので全体の流れを記録します。

# CloudFrontを利用し、画像は特定のパスでS3に振り分ける状態でALBを利用してWebサイトを構築する
## やりたいこと
- AWS EC2上にWebサービスをデプロイしたい
- 画像は画像サーバー（S3）に置きたい
- 同ドメインの```/wp-content/uploads/*```のPathではS3を向き、それ以外ではEC2を向くようにしたい（WordPressサイトのAWS移行です）
- EC2は冗長化のため2台構成にしてロードバランサを前段に置きたい
- いずれも独自ドメインを取得の上、動かしたい。HTTPS化もしたい

## 本記事では書かないこと
- EC2より後段のRDS等の話
- IAMロールは関連する権限をすべて持っている前提として書きます

## 全体的な流れ
ひたすら各サービスの知識を繋げあわせて作りました。大まかな流れは下記のとおりです。

1. Route53でドメインを取得する
2. CloudFrontでDistrubutionを作る
3. VPC内にサブネットを2個作り、それぞれ別のアベイラビリティゾーンに設定しておく
4. EC2を単体でIPでアクセスしてページが開けるまでセットアップし、それぞれのサブネットに1台ずつ設置しておく
5. ACMからドメインを指定して証明書を発行する
6. ALBを作成し、その際にドメインは作成したものを、証明書に先程発行した証明書を、ターゲットグループにサブネット2つを指定する
7. Distributionに戻り、Originに対してALBを追加する
8. S3で画像の置き場となるバケットを作成し、```/wp-content/uploads```フォルダを作成する
9. S3もDistributionのOriginにセットする
10. Behaviorを新規追加し、```/wp-content/uploads/*```でS3に向くように設定する
11. Route53でドメインのHosted Zoneを作成し、AレコードにCloudFrontのDistributionをAliasに指定したレコードを加える

## 用語解説
EC2, VPC, S3は割愛。

### Route53
AWS上でドメインを取得し、そのドメインに対して向き先となるIPアドレスの設定などもできるサービス。ちなみに53はDNSのポート番号53にちなんでいるらしい。

### CloudFront
静的コンテンツを配信する時に色々ロジックをかませることができる便利サービス。

その名の通りサービスの前段に噛ませるもので、ALB（ロードバランサ）よりも前段に置くことができる。

WAF（ファイアウォール）を付けたり、Route53を通さなくてもSSL証明書付きのドメインやURLを自動で発行したり、なんだか色々できる。

### ACM
SSL証明書をマネジメントできるサービス。ドメインに対してSSL証明書の発行を申請したり、期日の確認なども一元管理できる。

### ALB
アプリケーションロードバランサの略。ロードバランサの名の通り、バックエンドに複数のEC2をぶらさげてリクエストを振り分けることができる。以前はELBという名前のロードバランサがデファクトだったので、今でもググるときはELBでググったほうがいいときもある。

## 特に注意するところ
### CloudFrontの待ち時間の長さ
CloudFrontは作成したり設定値を編集する時に待ち時間が数分〜数十分の単位で発生します。気長に待ちましょう。

### EC2内のWebサーバーではhttpsアクセスを許可する設定が必要
今回はNginxを利用していたのですが、httpsを許可していないままにしておくと、せっかくACMで証明書を発行したのにCSSやJSがhttpアクセスになって表示崩れが起きるので、下記のような設定をしましょう。

[Laravel 5.5 にてロードバランサ配下のアプリケーションにHTTPS通信を適用させる - Qiita](https://qiita.com/tyamahori/items/dfac2b826cbf2bfa448a)

```nginx
    set $my_ssl "off";
    if ($http_x_forwarded_proto = "https") {
        set $my_ssl "on";
    }
(略)
    location ~ \.php$ {
        fastcgi_pass   unix:/var/run/php-fpm/www.sock;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME $document_root/$fastcgi_script_name;
        include        fastcgi_params;
        fastcgi_param HTTPS $my_ssl;
    }
```

### ALBのバックエンドに指定するサブネットはアベイラビリティゾーンを分ける
勉強不足で理由がわからないのですが、ALBのバックエンドに配置する条件としては、とにかくサブネットを2つ以上、かつアベイラビリティゾーンを分けて作らないといけないです。

サブネットさえ切れば、あとはそのサブネット内にポート番号80番が空いたインスタンスを置くだけでALBが動きます。

### CloudFrontのキャッシュの時間には気をつける
ALB側はアプリケーションなので、変にキャッシュされても困ります。

そのためキャッシュの設定値には気を払いましょう。

このあたりまだ理解が甘いので、色々触ってみて理解を深めていきます。

### CloudFrontでミスしたらInvalidationする
設定を変えたのに反映されていないときは、キャッシュされている可能性があるので、

InvalidationsというタブからCreate Invalidationをクリックします。これでキャッシュを消したいPathを記入して動かすと、キャッシュを消すことができます。

### S3へのパスはそのままS3のディレクトリ構成と一致させなければならない
今回の場合だと```/wp-content/uploads```のURLでS3上の画像を見に行くのですが、S3でも同じフォルダを作成して、同じパスで画像を見れるようにしないといけません。

CloudFrontをリバースプロキシ的に使おうと思った場合、ここが力不足だと感じる点です。

## 検討課題
### S3のアクセスは公開でいいのか
例えばCloudFrontからのリダイレクトのみ許可するようなことができれば、直接S3を覗くルートがなくなると思われます。

### ALB, EC2周りの適切なセキュリティグループ設定
開発用ということでセキュリティ周りの設定を適当に制限した状態で進めていたので、こちらも本番適用にあたっては検討課題です。

### CloudFrontからS3へアクセス時に307リダイレクトが掛かってしまう
[Cloudfront,S3で307リダイレクトに苦しめられた - パパエンジニアのアウトプット帳](http://masaru-tech.hateblo.jp/entry/2018/03/27/111327)にあるように、私の環境でも https://HOGEHOGE/wp-content/uploads/hoge.jpg へアクセスすると、S3のオリジンにリダイレクトされてしまう事象が起きました。

直接的に大きな問題がないとしても、解消したほうがいい問題です。

### CloudFrontのURLを直接叩いてもアクセスができてしまう
本来意図しないドメインで開くことができてしまうので、こちらを閉じることができるなら閉じたいです。

ただ先程開いてみたら502エラーを吐いていたので、もしかするとAliasを貼ってしばらくすると元URLでは何もできなくなるのかもしれません・・・

### EC2を同じもの2台設置する手順が面倒
今のところ僕がやっている方法が、AMIを作ってイメージから起動するというのをやっていまして、masterのほうのインスタンスを更新するたびに同じことをやっているので、ここのフローをどうにかしたいです。

また、デプロイ時のCodeDeployでも複数インスタンスに同時にコードを反映できるのか、は今後調べていこうと思います。

### 証明書の期日の管理、ドメインの期日の管理
ACMやRoute53にアラートを設定できるならしたほうがいいし、それがSlackに流せたらなお良いですね。

## 参考URL
- [CloudFront ウェブディストリビューションを使用して、複数のオリジンからコンテンツを配信](https://aws.amazon.com/jp/premiumsupport/knowledge-center/cloudfront-distribution-serve-content/)
- [CloudFrontでマルチオリジンとCache Behavior設定してみた｜クラスメソッドブログ](https://dev.classmethod.jp/cloud/aws/cloudfront-multioriginbehavior/)
- [CloudFrontを経由してS3の静的ウェブサイトホスティングの機能を使う - Qiita](https://qiita.com/yoheiit/items/6c87610fb9183ecfff9b#%E3%83%90%E3%82%B1%E3%83%83%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90)
- [AWSでWebサイトをHTTPS化 その1：ELB(+ACM発行証明書)→EC2編](https://recipe.kc-cloud.jp/archives/11084)
- [NginxでデフォルトのVirtualHostを設定する – Fish and Tips](https://blog.tmkn.jp/2016/01/10/nginx-default/)
- [Amazon EC2編～SnapshotやAMIを使ったバックアップと運用パート①～](https://recipe.kc-cloud.jp/archives/276)
- [Cloudfront,S3で307リダイレクトに苦しめられた - パパエンジニアのアウトプット帳](http://masaru-tech.hateblo.jp/entry/2018/03/27/111327)
- [Laravel 5.5 にてロードバランサ配下のアプリケーションにHTTPS通信を適用させる - Qiita](https://qiita.com/tyamahori/items/dfac2b826cbf2bfa448a)

# 最後に
僕が働いている[NoSchool](https://portal.noschool.asia/)では仲間を募集中です！
創業期真っ只中、社員は僕と社長の2人きり（+副業メンバー3人）でEdTechを変えに行ってます！

興味あればお茶でもしましょう。Twitterからの連絡でも大歓迎です！
https://www.wantedly.com/companies/noschool