AWS S3+CloudFrontで静的HTTPSサイトの構築(サーバレス)

何番煎じか分からないぐらいですが小規模なWebサイトをお手軽（お安く）に構築・運用したいという案件があったので検証と備忘録を兼ねて記事にします。S3で静的ウェブサイト構築を行う記事は色々なサイトで書かれていますが、「独自ドメインで」かつ「HTTPS化」するという内容だと意外と情報が少ないです。

# 案件の背景
小規模な会社HPっぽいサイトをAWS移行したい。ランニング費用を抑えつつ、高可用性を確保し、保守も簡単にできるようにしたい。

お話としてはこんな感じです。よくあるユーザ要望を詰め込んだ内容でした笑。

AWS S3+CloudFrontで構築する場合のメリットですが何といってもお安くできるということです。使用するリソースは以下です。

・S3 Standard
・CloudFront(CDN)
・Route53(DNS)
・ACM(SSL証明書発行)

S3についてはhtmlファイルやCSSファイルを置くだけなので、ほぼ費用はかかりません。次にCloudFrontですがこちらもアクセス数次第では無料枠があるので、小規模サイトであればあまり費用はかかりません。おそらく本構成で一番費用がかかるのはRoute53のホストゾーン(月/$0.5)だと思います。


【CloudFrontの利用料金】

https://aws.amazon.com/jp/cloudfront/pricing/

# 今回のゴール
S3バケットにhtmlファイルとcssファイルを格納して、CloudFrontを経由し、独自ドメイン名かつHTTPS通信でアクセスできるWebサイトを構築する。

構成イメージはこんな感じです。

![全体構成図.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/331ddfb0-8d9d-5712-7fa4-999892dc7681.png)


Webブラザーからアクセスしたときにはこんな感じで表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/667e370f-2881-6725-8cdf-3d55d2b610f0.png)




# 前提(+免責事項)
・独自ドメインを持っている(DNS認証かメール認証できること) 
・独自ドメインのDNSをRoute53で管理するように変更できること
・S3とCloudFrontを利用し、サーバレスで構築する
・SSL証明書はAWS Certificate Manager(ACM)で発効し、利用する
・サイトにはHTTPSでアクセスできるようにする
・S3バケットは公開しない（CloudFront経由でしかアクセスさせない+パブリックアクセスはブロック)

:::note warn
AWSリソース利用に費用が発生します。設定は自己責任お願いします。後片付けも忘れずに！
:::


# 設定の流れ
①S3バケットの作成・ファイルアップロード
②CloudFrontの作成
③ACMでSSL証明書の作成・設定
④Route53の設定(+DNS設定)


# 実際にやってみた

## ①S3バケットの作成・ファイルアップロード
S3バケットを任意のリージョンに作成します。名前は一意でなくてはならないので他のバケットと重複しなさそうな名前をつけましょう。設定はほぼデフォルトのままでよいと思います。注意点ですが「S3バケットの静的ウェブホスティング」設定をする際にはパブリックアクセスのブロック無効化をするので、チェックを外しますが、今回は正確には「S3バケットの静的ウェブホスティング」ではないし、S3バケットに直接アクセスできるのはCloudFrontのみに制限するので、ブロックを有効化したままでOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/766816b9-7b73-19a9-6d5b-e20e7e082af8.png)

また同じ理由でバケットプロパティの「静的ウェブホスティング」機能も無効のままでOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/fea54811-e49c-5504-aaea-1c5d12f9ec90.png)

バケットが出来たら以下内容のhtmlファイルをバケットにアップロードします。

```
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<title></title>
<meta charset="utf-8">
<meta name="description" content="">
<meta name="author" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="style.css" type="text/css" />
<!--[if lt IE 9]>
<script src="//cdn.jsdelivr.net/html5shiv/3.7.2/html5shiv.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/respond.js/1.4.2/respond.min.js"></script>
<![endif]-->
<link rel="shortcut icon" href="">
</head>
<body>
<!-- Place your content here -->
<!-- SCRIPTS -->
<!-- Example: <script src="jquery.min.js"></script> -->
<div id="outer">
<div id="header">
<div class="header-inner">
<h1>あなたのＨＰタイトル</h1>
<p>あなたのＨＰの説明文</p>
</div>
</div>
<div id="content">
<div class="inner">
<h2>記事ページ見出し・大</h2>
<p>文章あれこれ</p>
<img src="photo1.jpg" width="100%">
<h3>記事ページ見出し・中</h3>
<p>文章あれこれ</p>
<h4>記事ページ見出し・小</h4>
<p>文章あれこれ</p>
</div>
</div>
<div id="left">
<div class="left-title">サイドバータイトル</div>
<div class="link">
<ul>
<li>記事ページへのリンク</li>
<li>記事ページへのリンク</li>
</ul>
</div>
</div>
<div id="footer">© 2020 あなたのホームページ</div>
</div>
</body>
</html>

```
ファイル名は[index.html]とします。

同じように今度は以下内容のCSSファイルを同じバケットの同じ階層にアップロードします。

```
body {
background-color:#f8f8f8;
margin:0;
padding:0;
}
#outer {
background-color:#fff;
margin:0 auto;
width:800px;
border-left: 1px solid #e5e5e5;
border-right: 1px solid #e5e5e5;
}
#header {
height:80px;
}
.header-inner {
padding:15px 0 0 20px;
font-size:12px;
}
.header-inner p{
font-size:12px;
}
h1 {
font-weight:bold;
color:#357109;
font-size:16px;
margin:0;
padding:7px 0 0 0;
}
h1 a{
font-weight:bold;
color:#357109;
text-decoration:none;
}
h1 a:hover{
text-decoration:underline;
}
h2 {
font-size:16px;
margin:0;
padding:7px 0 7px 7px;
background-color:#D6E7EE;
}
h2 a{
font-weight:bold;
text-decoration: none;
color:#265699;
}
p {
margin:1em 0;
font-size:13px;
line-height:150%;
}
img {
border:0;
}
a{
color:#094a94;
}
#content {
float:right;
width:580px;
}
.inner {
margin:0 20px 0 0;
padding:0;
}
#left {
color:#676767;
font-size:13px;
width:220px;
float:left;
}
.left-title {
margin:0 15px 0 20px;
padding:7px 20px;
font-weight:bold;
background-color:#E3E3E3;
color:#333;
border-top: 1px solid #c7c7c7;
border-right: 1px solid #c7c7c7;
border-left: 1px solid #c7c7c7;
}
.link {
margin:0 15px 7px 20px;
padding:7px 8px 7px 13px;
line-height:170%;
border: 1px solid #c7c7c7;
}
.link a{
color:#094a94;
text-decoration:none;
}
.link a:hover{
text-decoration:underline;
}
ul {
margin: 0 0 0 1em;
padding: 0;
}
li {
list-style:disc; 
}
#footer {
text-align:center;
color:#666;
font-size:12px;
height:70px;
padding-top:10px;
clear:both;
}
blockquote{
border:1px solid #ccc;
padding:5px;
margin:10px;
}
```
CSSファイルはファイル名を[styl.css]とします。

次にindex.html内で表示させる画像データをアップロードします。
今回はこの[Workshop](https://dcj71ciaiav4i.cloudfront.net/7CB85370-A998-11EB-A09B-259812622FA1/04_contents/01_upload.html)で提供されていた以下の画像データを使いました。

![photo1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/9ba13c2c-14d2-8f7b-b51a-d143267f0f40.jpeg)

これでS3バケットの作成と必要なデータのアップデートが完了しました。

## ②CloudFrontの作成

CloudFrontの管理コンソールから「ディストリビューション」の作成をクリックします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/06f712c4-f5f3-4724-20cb-99b5cad8f098.png)

入力項目は次のように設定します。

・Origin domain →①で作成したS3バケットを選択
・Origin path →なし
・名前　→覚えやすい任意の名前
・オリジンアクセス　→Origin access controle settings(recommended)
・Enable Origin Shield　→いいえ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/e25e3d32-f31b-06e7-0e48-6abfacc109cf.png)

オリジンアクセスで設定したOrigin access controle settingsがOACと言われるオリジン（S3へ格納した元ネタデータ)へのアクセスコントロールになります。本項目を選択してOACがない場合は「Create new OAC」を選択し、新しいOACを作成します。その後上記画面のように「ポリシーをコピー」という項目が出てきますので、クリックし、クリップボードにポリシーをコピーして、「S3バケットアクセス許可に移動」というリンクをクリックします。

①で作成したS3バケットの設定画面に飛ぶので、「アクセス許可」のタブをクリックし、先ほどクリップボードにコピーしたポリシーを張り付けし、保存します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/57e6d2d9-6db9-5d97-76a9-7d1fe9681f06.png)


デフォルトのキャッシュビヘイビアの設定は基本的には変更なしでOKですが今回は以下を設定とします。

・ビュワープロトコルポリシー　→Redirect HTTP to HTTPS

:::note info
WebサーバでいうHSTS機能みたい設定です。
:::


・許可されたHTTPメソッド　→GET,HEAD,OPTION,PUT,POST,PATCH,DELETE

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/2b8f3acb-dd76-7016-bb19-d3755e6957e8.png)

キャッシュポリシーはデフォルトだと選択されていないので、選択肢から「CachingOptimized」を選択します。その他はデフォルトのままでOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/84c0ce1a-f215-6fd2-f320-b1b897ee109e.png)

関数の関連付けはデフォルトのままでOKです。
ウェブアプリケーションファイアウォール(WAF)の設定は「セキュリティ保護を有効にしない」を選択します。

:::note warn
WAFは本番環境の場合は必要に応じて有効化し、チューニングしてください。
:::

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/cc67fd37-b51f-c740-a380-fc0fed731fa4.png)

設定の部分ですが今回は以下設定とします。
・料金クラス　→すべてのエッジロケーションを使用する
・代替ドメイン名　→利用する独自ドメイン

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/1585b819-0afb-57e1-9129-05367c3cb886.png)


## ③ACMでSSL証明書の作成・設定
CloudFront作成の途中ですがここでSSL証明書を選択する項目（Custom SSL Certificate)が出てきました。今回は新規にSSL証明書を発行しますので、「証明書をリクエスト」をクリックします。

こんな画面に飛びますので、「パブリック証明書をリクエスト」を選択し、次へクリックします。この時にリージョンは「バージニア北部」となっていることを確認してください。AWSの仕様でバージニア北部リージョンで証明書発行しないと適用ができない仕様になっています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/c265b003-41e8-9c0e-fdd7-3beee82dc090.png)


設定項目は以下で設定します。

・完全修飾ドメイン名　→利用する独自ドメイン
・検証方法　→DNS認証　※利用したいドメインでメールサーバを契約していて、メール受信できるならEメール検証でもOKです。

:::note info
SSL証明書発行の処理で検証を行うのはSSl証明書を発行しようとしているドメインを所有していることを証明するためです。ドメインを管理しているDNSに任意のコードを登録できる=ドメインの管理・所有者であることの確認というわけです。
:::

:::note warn
DNS認証をクリアするにはドメインを所有しているだけでなくドメインを管理しているDNSの管理サービスにログインできる必要があります。
:::

※利用したいドメインでメールサーバを契約していて、メール受信できるならEメール検証でもOKです。

・キーアルゴリズム　→RSA2048

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/00a63d14-dd04-9069-20ce-599b532bf31f.png)

選択したら「リクエスト」をクリックします。



しばらくすると以下のような画面が表示され、ステータスがドメインの「保留中の検証」となります。
ドメインの検証ですが、CNAME値に記載されている値をドメインを管理しているDNSのCNAMEレコードに登録することで完了しますので、DNSにレコードを追記していきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/125cb33e-d377-322f-6ce8-759f951380cc.png)

私の場合はお名前ドットコムでドメインを取得していたので、管理DNSはお名前ドットコムのDNSになっていました。

こんな感じでお名前ドットコムのDNSにAWS指定CNAME値を追記します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/226a8400-9611-d457-e44c-94e6be694ec5.png)


登録後しばらくすると、こんな感じでステータスが「成功」となっていれば、OKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/511108d8-a694-e561-5482-c95f8488b124.png)


CloudFront作成の画面に戻り、SSL証明書を先ほど作成したドメイン名のものを選択し、「ディストリビューションを作成」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/1585b819-0afb-57e1-9129-05367c3cb886.png)

その他の設定ですが
・デフォルトルートオブジェクトに「index.html」を記載しましょう

:::note info
この設定を入れておくことで「https://{独自ドメイン}」というURLでアクセスしても後ろに/index.htmlを補完してくれます。
:::


・ログは記録しておきたいので、オンにしておきましょう。S3バケットは任意のものを


これでCloudFrontの作成は完了です。

CloudFront自体の作成が完了しているので実はもうS3バケットにCloudFront経由でアクセスはできる状態となっています。

CloudFrontの管理コンソールの「一般」に表示されているディストリビューションドメイン名を使ってS3バケットに格納したindex.htmlファイルが表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/98063228-340a-4ca0-63a7-54b73ddd1f2e.png)

http://{ディストリビューションドメイン名}でWebアクセスできます。

あとは独自ドメインでも同じようにS3バケット内のhtmlファイルにアクセスできる形にするだけです。


## ④Route53の設定(+DNS設定)
今回はDNSをRoute53で管理したいため、Route53以外でドメインを購入している場合DNSをRoute53に切替えする必要があります。

まずRoute53の管理コンソールでホストゾーンを作成していきます。ホストゾーンをクリックし、「ホストゾーンの作成」へ進みます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/dcd477ce-b393-c06b-d0c0-9bbe77d51a8c.png)

ホストゾーンは以下設定とします。

・ドメイン名　→利用する独自ドメイン
・タイプ　→パブリックホストゾーン


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/589a1614-ef7e-bc7d-8db9-8430ebeac382.png)



ホストゾーンが作成されると以下のようにNSレコードが自動で追記されます。このNSレコードを元のDNSに追記することにより、DNSの管理がRoute53側でできるようになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/d0142307-545f-165b-6d18-6e2311f382f1.png)


ちなみにお名前ドットコムだと以下のようにネームサーバを指定する部分があり、ここにRoute53のNSレコードを記入することになります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/fee51ff2-4368-4f4f-cf70-32b270965baa.png)

レコードを登録、反映が完了したら各DNSにレコードが伝播していくまで時間がかかるので20～30分かかるのでいったん待ちです。
しばらくしたら[DNS伝播チェッカー](https://www.nslookuptool.com/ja/)で正しくNSレコードが伝播され、NSレコードにRoute53のコードが表示されるか確認しましょう。

レコードが上手く反映されたらRoute53でAレコードを作成します。レコードの値はCloudFrontのディストリビューションドメイン名を記入します。これで[https://{独自ドメイン}]でWeb検索した際にCloudFrontにアクセスするになります。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/a4816e0e-3ca4-437a-f937-23414f8df7f2.png)



これで[https://{独自ドメイン}]でアクセスした際にS3バケット内のindex.htmlファイルが表示されます。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/667e370f-2881-6725-8cdf-3d55d2b610f0.png)


ちゃんとCSSも適用されているので、想定通りの挙動です。

最後にS3バケットが公開されていないこと、ちゃんと2回目以降のアクセスはキャッシュヒットすることを確認しましょう。

S3バケットの設定は以下のようになっていればOKです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/1105002b-d546-5e4a-9d9e-9b858bf0c7af.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/bcc9f8c0-f6a9-c47c-e505-bfcdcaed540a.png)

またキャッシュヒットはchromeなら以下のサイトを参考に確認してください。

<キャッシュヒット確認方法(他サイト)>
https://www.k-friendly.com/8178
















# 注意点(S3+CloudFront)で出来ないこと
出来ないことは色々ありますがサイト運用にあたり一番影響があるのは<font color="Red"> ***「お問い合わせフォームの実装ができない」***</font> ことだと思います。というのも本構成はS3+CloudFrontのサーバレスなのでJava,SQL,PHPなどミドルウェアで実現している機能は実装できないのです。そのため別の方法でお問い合わせフォーム実装は別方法で検討する必要があります。



# 【おまけ】

## 存在しないページにアクセスしたときはエラーコード404を返したい
一般的なWebサーバの場合、存在しないページにアクセスした場合、エラーコード404(not found)となりますがS3の場合は仕様上以下のようなエラーコード403が返されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/1b7ff5ae-0413-af66-ed72-35b7ca94c944.png)


エラーコードやエラー表示内容を変更したい場合はチューニングが必要となります。
チューニングは以下手順となります。

①index.htmlを格納したのと同じS3バケットにerror.htmlというファイルを格納
②Cloudfrontのディストリビューション管理画面で、エラーページタグから「カスタマーレスポンスを作成」を実行



![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/83f6caf5-09a8-db43-59ac-16930b4c4bbb.png)

レスポンス設定を以下のように行います。

・HTTP error code →403
・Error caching minumum TTL　→10(デフォルト)
・Customize error response　→yesにして、404エラーのときに表示させたいhtmlファイルを指定+HTTP Response codeで404を設定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/0501f413-9e41-ccc8-83e8-23042309981b.png)

こんな感じで403エラー時に指定のhtmlファイルを参照するようにできます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/d18f11a2-b847-5844-864a-52e75ec697dc.png)


## サイトアクセスログ(cloudfrontログ)を確認したい
Webサーバと同じようにアクセスログが確認したい場合、ログ取得設定を有効にして、指定S3バケットにログを吐き出す設定にしましょう。ただS3バケットに蓄積されているログデータはGZ形式で圧縮されており、ぱっとは見づらい形になっています。お手軽に解凍できるソフトがPCにある人はいいのですがソフトがない人は面倒です。そこでAWSCloudshellを使って、AWSコンソール上で解凍、閲覧してしまいましょう。

AWSのコンソールでCloudShellを起動します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/655033/d4d19783-8251-d3d1-a081-95979af3b7ce.png)

```
# S3バケットからgz化されたログファイルをDLする (最後の"."を忘れずに)
aws s3 cp s3://{S3バケット名}/{ファイルパス}.gz　.

成功すると以下のようにDLが始まる
 download:s3 cp s3://{S3バケット名}/{ファイルパス}.gz 

#gzファイルの解凍
gunzip {DLしたファイル.gz} 

#中身の閲覧
cat 解凍したファイル 
```
これでログファイルの中身を閲覧できます。



## キャッシュをクリアしたい
htmlファイルの中身を書き換えてもCloudFrontがコンテンツキャッシュしているので、すぐに変更が反映されません。そんなときはCloudFrontのキャッシュを削除してあげましょう。


# おわりに
S3を使ったWebサイト構築のやり方やCloudFrontの設定方法などは個々の設定方法は結構な数の記事があるのですがHTTPS化する、S3を使う、CloudFrontを使うという複合化すると少なくなります。上手く使えば本当に便利に可用性の高い環境が作れますので脱レンタルサーバをお考えの方々のお役に立てれば幸いです。


## 参照サイト
表示させるhtmlファイルおよびStyle.cssや画像は以下サイトのものを使わせて頂きました。


https://dcj71ciaiav4i.cloudfront.net/7CB85370-A998-11EB-A09B-259812622FA1/04_contents/01_upload.html

その他参考にしたサイト


https://mik2062.jp/s3-cloudfront/

https://dev.classmethod.jp/articles/useful-information-to-build-cloudfront-s3-website/

https://aimstogeek.hatenablog.com/entry/2018/07/23/135032

https://www.k-friendly.com/19302

https://blog.denet.co.jp/adcale2023_s3logkakunin/

https://repost.aws/ja/knowledge-center/cloudfront-serve-static-website