こんにちはjunです。会社で静的書き出ししたコンテンツをs3でホストし、cloudfrontを用いてwebサーバーの様にブログ内容をリクエストできる様な構成を作成しました。せっかくなので自分のブログも同じ様にやってみようと思い、復習兼ねて記事にしようと思います。今回は以下のことを説明します。
私のこのブログはnuxt content を用いてnuxt generate をして静的書き出したものをサーバに転送しています。今まではレンタルサーバーの一部ディレクトリを使用していましたが、AWSの勉強ややってみたい機能をつけやすくするためにS3へ引っ越しました。それでは早速やっていきましょう。
CloudFront を使用して、Amazon S3 でホストされた静的ウェブサイトを公開するにはどうすればよいですか?
ドメイン名を使用したトラフィックの Amazon CloudFront ディストリビューションへのルーティング
では最初にまずホスティング元であるバケットを作成します。
バケットの名前は ホストするドメインの名前と同じ になるようにします。私の場合はバケット名が「jun-app.com」です。
設定した後にバケット一覧から選択し、プロパティを選びます。プロパティの一番下までスクロールすると「静的ウェブサイトホスティング」とあるのでここを編集します。
無効から有効に変更し、インデックスドキュメントにindex.html
を入力します。こうするとhttps://jun-app.com/dir/
みたいにパスだけであってもhttps://jun-app.com/dir/index.html
に接続してくれます。また、エラードキュメントを指定しておくことで404リクエストの際のフォールバックとして利用できます。私の場合はルート直下の 404.html
を指定しています。
編集後もういちど静的ウェブサイトホスティングの設定までいくと、一応URLが提供されますがアクセスしても「403 Forbidden」となります。これはバケットに対してパブリックアクセスがないからです。バケット作成時、デフォルトでは全てのパブリックアクセスが制限されていたので、その制限を外してあげます。バケットの画面から「アクセス許可」を選択し、「ブロックパブリックアクセス (バケット設定)」の編集をします。(ここを見ると「パブリックアクセスをすべて ブロック」が有効になっている)
編集の画面で「パブリックアクセスをすべて ブロック」のチェックを外して、全てのブロックを外します。
まだ終わりではありません。先ほどのはバケット単位の設定であり、アップロードされるオブジェクト(静的ファイル)には公開される設定がされません。毎回オブジェクトに対してパブリックアクセスを与えるのは面倒なので、「アクセス許可」の画面にあるバケットポリシーを以下のように編集します。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": [
"s3:GetObject"
],
"Resource": [
"arn:aws:s3:::your-buget-name/*"
]
}
]
}
上記のJSONはPrincipal
はこのポリシーを付与するユーザーです。*
としてゲストを含む全てのユーザーにs3:GetObject
のアクション、つまりファイルの読み取りを"arn:aws:s3:::your-buget-name/*"
のバケットオブジェクトに許可するという意味です。
your-buget-name
にはあなたが設定したバケット名を入力します。そしてそのバケット配下の全オブジェクトに適用するためyour-buget-name/*
とディレクトリのように指定します。"arn:aws:s3:::your-buget-name
という記述(Amazon リソースネーム)はバケットのプロパティで取得できます。
こうすればこのバケットないのファイルは公開されます。試しにindex.html
をアップロードして、提供されたオブジェクト URLにアクセスすると確かに見れます。
これでS3側の設定が完了しました。ただしドメインはS3の初期状態のままです。独自ドメインを割り当てとSSL化を行います。
SSL化した独自ドメインをS3に接続させるためにはcloudfrontの力が必要です。また今回使用するドメイン「jun-app.com」はお名前ドットコムで取得しているので、CNAMEを加えるなどの設定が必要です。クライアントがコンテンツを取得する流れは以下のようになります。
といった流れです。
まずはRoute53でホストゾーンというものを作成して、お名前.comからNSサーバーをRoute53へ委任できるようにします。Route53に移動して「ホストゾーンの作成」をします。
委任したいドメインを入力します。「パブリックホストゾーン」で大丈夫です。タグは管理上のものなので空欄で大丈夫です。問題なければ「ホストゾーンの作成」をクリックします。
作成後にはこのようにNSレコードとSOAレコードが作成されます。ドメイン自体が外部DNS(お名前.com)にある場合この「NSレコード」を指定することでRoute53に委任します。(ぼやけていますが4つ作成されます。)
このNSレコードをお名前.comの場合は「ネームサーバーの設定」を選択します。
そして「2.ネームサーバーの選択」>「その他」>「その他のネームサーバーを使う」にてRoute53で表示された4つのNSレコードをいれて「確認」クリックします。
「確認」の後にはDNSのNSが切り替わり、「jun-app.com」はまずお名前.comへ問い合わせられますが、結局Route53へたらい回しされます。これで「jun-app.com」のAレコードなどをRoute53で制御できるようになりました。
次はS3とcloudfrontと連携させてSSLを利用できるようにします。
まずcloudfrontの設定の前に独自ドメイン(jun-app.com)のSSL証明書をAWSで取得しておきます。AWSではAWS Certificate Manager で取得できます。ただしここで注意点があります。
リージョンを「us-east-1」に変更したのち「証明書をリクエスト」をクリックして作成します。
証明書のタイプは「パブリック証明書をリクエスト」を選択し、ドメイン名などを入力します。そして検証では「DNS検証」を行います。Route53で委任してあれば「DNS検証」ですぐに発行できます。DNS検証はドメインを管理しているDNSにAWS Certificate Managerで発行されたCNAMEを設定することで、ドメインの所有権を確認します。全て入力して「リクエスト」となります。
リクエスト後には一覧に入力したドメインが現れ、対応するCNAMEが現れます。Route53で管理している場合は「Route53でレコードを作成」のボタンを押すだけで作ってくれます。手動で設定する場合は「CNAMEレコード」を作成して、その値を設定するだけです。
このようにDNS検証に必要なCNAMEが追加されています。
数分経つと証明書の検証が完了となりますので、これで「jun-app.com」の証明書の準備ができました。次はcloudfrontとS3を独自ドメインを用いて連携します。
それではcloudfrontとS3を独自ドメインを連携します。cloudfrontのコンソールに移動して「ディスりビーションの作成」をクリックします。
そしてcloudfrontと連携するAWSリソースやオリジンなどを設定します。「オリジンドメイン」でS3リソースを選択するのですがS3をwebサーバーとして利用する場合 サジェストに出てくる 「~~~~.s3.ap-northeast-1.amazonaws.com」 を選択してはいけません。 これは単純に「S3オブジェクトURL」でありwebサーバーのような動きをしません。
今回の構成ではS3の「静的ウェブサイトホスティング」を有効にした際に出現した(下図のボケているとこ)のバケットウェブサイトエンドポイントをいれます。このバケットウェブサイトエンドポイントは「S3をwebサーバーとして使用するときのドメイン」です。ここを忘れて「S3オブジェクトURL」に設定しても一応ルートはs3に接続はできるのですが、「https://jun-app.com/test/」のように接続するとエラーが発生したり、思うように動きません。結構ハマりポイントです。
オリジンの設定をしたら他の項目は基本的にデフォルトのままでOKです。下に進んで独自ドメインと証明書を設定する箇所があります。「代替ドメイン名 (CNAME)」に連携したいドメイン名を入力し、そのドメインに対応する証明書(先ほど取得した証明書)を選択します。
最後にデフォルトルートオブジェクトに「index.html」を設定します。こうすると「https://jun-app.com/test/」とリクエストした際「https://jun-app.com/test/index.html」が自動的に返されます。
これで一通りcloudfrontとS3の連携が完了しました。「ディストリビーションを作成」を行いますと一覧にて ~~~~~~.cloudfront.net
というS3オリジンに対するcloudfrontのドメインが作成されます。このドメインにアクセスしてみますと、SSL付きでS3上にアップロードした index.html
が表示されます。
つぎは cloudfrontのドメインが jun-app.com
に向くようにRoute53を設定します。
ではRoute53に戻ってcloudfrontのドメインが独自ドメインに向くようにします。ドメインのホストゾーンにて「レコードの作成」を選択します。そして以下のような画面が開きます。「レコード名」はサブドメインを作るわけでないので、空欄で大丈夫です。そしてレコードタイプは「A - ipvアドレスと..」を選択します。「トラフィックのルーティング先」のエイリアスをONにします。(トグルになっています)
Aレコードは普通、IPアドレスを入力しますがcloudfrontなどのAWSリソースでしているする場合「エイリアス」を使用します。エイリアスでは「CloudFront ディストリビューションへのエイリアス」を選択します。そしてその下にcloudfrontのディストリビューションの入力欄が現れますので、そこに先ほどのcloudfrontのドメインを入力します。
そして最後に「レコードの作成」をクリックします。すると jun-app.com
に対するAレコードがcloudfrontのディストリビューションに向くようになります。ここまでドメインの流れを追うと以下の通りになります。
jun-app.com
にリクエストしたとき、まずお名前.comに問い合わせるが「Route53」に委任されているのでRoute53へ問い合わせjun-app.com
のAレコードが xxxx.cloudfront.net
に向いているので、xxxx.cloudfront.net
へ接続。xxxx.cloudfront.net
は S3のwebホスティングURLに向いているので、そこに接続となります。(結構ざっくりとしてます) 以上でバックエンド側の準備ができました。すでにドメインを使用している場合はDNSキャッシュが効いていることがあります。別の端末からアクセスしたり、DNSキャッシュをクリアするコマンドを打ってみましょう。
nslookup
やdig
コマンドを使って対象ドメインに紐づけられているサーバーを確かめてみましょう。
現在のところS3にindex.html
が置いてあれば、独自ドメイン+SSLでそのindex.html
が表示されていればOKです。
では最後にブログファイル群をS3にアップロードします。私の場合はNuxt.jsであらかじめ書き出しを行い、dist
ファイル配下を送信します。手動は毎回大変ですのでAWS CLIを用いて自動化と差分アップロード できるようにします。その手順を解説します。最初にAWS CLIをもちいて対象バケットにアクセスできるIAMユーザーを作成します。
AWSの運用ベストプラクティスでは
AWS アカウントのルートユーザー のアクセスキーを使用しないでください。 IAM により、複数のユーザーに AWS リソースへの安全なアクセスを簡単に提供できます。IAM により以下が可能となります。
とある様に基本的にIAMというものを用いてリソースにアクセスする様にします。例えば今回の様にAWS CLIを用いて自分のAWSリソースにアクセスする時にシークレットキーとアクセスキーを使用します。AWSを始めた時に作ったRootアカウントでも可能ですが、rootはCLIを通じで本当になんでもできてしまうので使うべきではありません。であればアクションとアクセスを制限されたユーザー(IAM)を使用すれば、もしキーが漏れたとしても被害は小さめです。なので今回のブログデプロイ用のIAMを作成してしまいます。
AWSにログインしてIAMに移動し、「ユーザーを追加」をクリックします。
ユーザー名を設定し、「プログラムによるアクセス」にチェックをし、次のアクセス権限の設定を行います。
ここでこのユーザーがアクセスできるアクションを設定できます。一応JSONを用いて細かい設定もできますが、今回はチュートリアル的な内容なので「AmazonS3FullAccess」を与えておきます。
次にタグを設定しますが、組織でなければ特に設定しません。最後に全体を確認して「ユーザーを作成」をします。これでS3だけにアクセスできるIAMができます。
このユーザー用のアクセスキーとシークレットキーがダウンロードできますので、控えておきます。まずデプロイ用のIAMユーザーができました。AWS CLIのインストールはここでは割愛しまが、ここで取得したIAMのキーをプロファイルに設定します。
aws configure --profile junapp-s3
プロファイル名は分かりやすい名前にしておくといいです。これでIAMと転送のセットアップは完了です。
私の環境では npm run generate
で dist
ファイルが生成され、必要なHTMLファイルとアセットが出力されます。その dist
ファイル配下を全てs3://jun-app.com/
へ転送します。そのときはAWS CLIで以下のコマンドを使用します。
aws s3 sync ./dist/ s3://jun-app.com/ --delete --profile junapp-s3
s3 sync
は2回目以降、差分をみてあれば同期してくれます。二回目以降は転送量を節約できます。--delete
は同期先(S3)にて同期元(ローカル)にないファイルを消してくれます。例えばnuxt.jsではキャッシュ対策のため、_nuxt
ディレクトリ配下に34234324.js
みたいなハッシュ化したjsファイルが書き出しごとに生成されます。単純に転送していくと__nuxt
配下にjsファイルが溜まっていくので差分を見て古いファイルを削除します。他にも「メニューなどにはないけれど、削除した記事がファイルとしては残ったままになっている」みたいな事故も防げます。
--profile
にて先ほど設定したIAMのプロファイルを指定します。
--dryrun
のオプションをいれてシミュレートしてから本番を実行しましょう。
準備ができたらコマンドを打ちます。終わったらバケットを確認して終了です。
Nuxt.jsでnpm run generate
した際にsync
されるようにpackage.json
を以下のように編集しておきました。
{
"scripts": {
...
"generate-production": "nuxt generate && aws s3 sync ./dist/ s3://jun-app.com/ --delete --profile junapp-s3"
}
}
cloudfrontはCDNであり、キャッシュが強いです。更新したのに本番が変わらないときはキャッシュのせいかもしれません。cloudfrontでは明示的にキャッシュをクリアすることもできますし、キャッシュの期間を変更することもできます。閲覧数などに合わせて設定しましょう。
以上で S3 x SSL x 独自ドメインな静的ブログを構築できました。利用量によっては無料枠でも収まりそうです。しばらく料金の方も見てみてレンサバよりお得かを確認してみます。