AWS
S3
middleman
CircleCI
CloudFront

Github, CircleCI, S3, CloudFront, Middlemanを活用して、https対応のサーバレスで自動deployなWEBサイト構築

この記事は リクルートライフスタイル Advent Calendar 2016 の15日目の記事です。

@MomokoAsai です。社内ではFirebaseの推進を行っております。

それではさっそく本題に。


実現すること

「Github, CircleCI, S3, CloudFront, Middlemanを活用して、https対応のサーバレスで自動deployなWEBサイト構築」です。

コーポレイトサイトなどの動的要素がないサイトは、簡単実装&低コスト運用が出来るような構成にしてしまいましょう。

そして、https対応をしていると、SEO的な優遇があり、将来的には必須になりそうです

https化も行ってしまいましょう。


全体構成

全体構成図.png

Githubにpushすると、それをフックにCircleCIがBuild & Deployを行います。

Deploy先はS3で、push対象ブランチによって、検証環境・本番環境のどちらかにDeployが行われます。

ACMでSSL(TLS)証明書を発行・自動更新を行い、CloudFrontを通してS3のサイトにアクセスします。


それぞれの役割

サービス
役割


私。生きること

Github
ソースコード管理

CircleCI
Build & Deploy

AWS S3
Webホスティング

AWS CloudFront
キャッシュ & HTTP/2配信

AWS ACM
SSL証明書

AWS Route53
ドメイン管理

AWSサービスについて補足



  • ACM (AWS Certificate Manage)



    • 2016年5月に東京リージョンがリリース

    • SSL(TLS)証明書を無料で発行

    • さらに、証明書の更新を自動で行ってくるため、メンテナンスフリー




  • CloudFront



    • 2016年9月に東京リージョンでhttp/2プロトコルを使った配信が可能になりました。




アプリケーション構成


  • Ruby 2.3.3

  • static site generatorsのMiddlemanを使用


    • gem middleman で静的サイトをジェネレート

    • gem middleman-s3_sync を使ってS3へuploadも簡単に出来るよう設定



$ ruby -version

ruby 2.3.3p222 (2016-11-21 revision 56859) [x86_64-darwin16]
$ bundle exec gem list middleman

*** LOCAL GEMS ***

middleman (4.1.11)
middleman-cli (4.1.11)
middleman-core (4.1.11)
middleman-s3_sync (4.0.3)

Middlemanですが、弊社Tech Blogの作成にも使われております。


手順


1. Github

今回用意するブランチは以下です。

ブランチ名
用途

master
本番にReleaseされるブランチ

staging
検証環境にReleaseされるブランチ

feature/xxx
開発用ブランチ

基本はGithub flowで開発しているのですが、

今回は独自に 「staging」 というブランチを用意し、検証環境用のブランチも作成する方針にしています。


2. CircleCI


2-1. Build & Deploy設定

指定されたブランチへのgit pushをトリガーに、Build & Deploy 処理を実行するよう設定します。


circle.yml

...

deployment:
production:
branch: master
commands:
- bundle exec middleman build
- bundle exec middleman s3_sync -e production
staging:
branch: staging
commands:
- bundle exec middleman build
- bundle exec middleman s3_sync -e staging



2-2. AWSへのAccess設定

次に、CircleCIがAWS S3にアクセスするための Access Key / Secret Key 設定を行います。

「検証環境」と「本番環境」それぞれ作成します。

最終的にはCloudFront経由でのAccessになるため、バケット名をドメインに合わせる必要はありません。

❒ AWS側設定

Access Key / Secret Keyの発行を行います。AWS IAMを使います。

1. 検証環境用のAWSのIAMからユーザーを作成

2. 作成ユーザー(or所属グループ)にAmazonS3FullAccessのポリシーを追加

3. Access Key / Secret Key をユーザーに発行

❒ アプリケーションの設定

gem middleman-s3_syncを使ってS3にuploadが出来るように処理を記載します。
Deployコマンドで指定した「bundle exec middleman s3_sync -e [環境名]」の環境名で出し分け設定が出来ます。
CircleCIの環境変数に登録した値は、RubyのコードからENV['HOGE']で取得することが可能です。(設定方法後述)


config.rb

...

configure :staging do
activate :s3_sync do |s3_sync|
s3_sync.acl = 'private'
s3_sync.bucket = ENV['AWS_S3_BUCKET_NAME_STAGING']
s3_sync.region = ENV['AWS_S3_REGION_STAGING']
s3_sync.aws_access_key_id = ENV['AWS_S3_ACCESS_KEY_STAGING']
s3_sync.aws_secret_access_key = ENV['AWS_S3_SECRET_KEY_STAGING']
end
end

configure :production do
activate :s3_sync do |s3_sync|
s3_sync.acl = 'private'
s3_sync.bucket = ENV['AWS_S3_BUCKET_NAME_PRODUCTION']
s3_sync.region = ENV['AWS_S3_REGION_PRODUCTION']
s3_sync.aws_access_key_id = ENV['AWS_S3_ACCESS_KEY_PRODUCTION']
s3_sync.aws_secret_access_key = ENV['AWS_S3_SECRET_KEY_PRODUCTION']
end
end


❒ CircleCI側設定

AWSで発行した Access Key / Secret Key を。CircleCIの環境変数に登録します。

S3のBucket名とRegionは、S3のBucket作成(後述)してから追加します。

20161214061822.png

CircleCIの設定詳細については公式ドキュメントを参照ください


3. AWS


3-1. S3

❒ Bucket作成

静的Webサイトホスティングを行うBucketを作成します。

作成が完了したら、CircleCIの環境変数に設定することをお忘れなく。

❒ 静的ウェブサイトホスティング機能設定

S3で静的ウェブサイトホスティング機能を有効にします。(本番環境、検証環境)

この時、「インデックスドキュメント」を設定します。「エラードキュメント」は”あると尚良し”という感じです。

20161214062225.png

S3へのAccess制限はCloudFront(後述)の設定で行います。

S3の設定はここまで。


3-2. CloudFront

S3へのアクセスはCloudFront経由のみ許可するよう設定していきます。

CloudFront経由からのアクセスにすることで

・ CDNエッジサーバーにキャッシュされることで高速化

・ S3のみより料金も安くなりやすい(S3へのリクエスト回数が減るため)

という利点があります。

特に、動画や音楽などの大容量配信を行う場合はCloudFront経由だと「ボリューミーディスカウント」が効くことがあり、お得です。

❒ Origin Settings

オリジンの設定を行います。


  1. 静的サイトの配信のため、配信方法は 「Web」を選択


  2. Origin Domain Name にS3バケットの静的ウェブサイトホスティング endpointを指定
    → CloudFrontがS3をオリジンサーバーとして認識し、S3を見に行くようになります。


  3. Origin Access IdentityCreate a New Identity にし、アクセス制御の設定を新規作成


  4. Grant Read Permissions on BucketYes, Update Bucket Policyに設定
    → この設定でCloudFrontにS3バケット内のオブジェクト読み取り許可が付与されます。

❒ Default Cache Behavior Settings

キャッシュ設定、配信プロトコルの設定を行います。


  1. 配信プロトコルを指定

    ★ 「Redirect HTTP to HTTPS」を推奨しますが、サイトに合わせて設定してください。


  2. 許可するHTTP methodを決める

    ★ お問い合わせフォーム等がある場合はPOSTの許可を忘れずに行いましょう。


配信プロトコルについては以下を参考ください

あとは基本デフォルト通りでOKです。キャッシュ時間を変更したい場合はTTLをいじってみて下さい。

❒ Distribution Settings



  1. Alternate Domain Name に、ACMで作成したサーバ証明書に一致するFQDNを設定


  2. SSL CertificateRequest or Import a Certificate with ACMを選択し、SSL証明書の発行を行う(ACMでのSSL証明書発行方法は後述)。発行が完了したら、Custom SSL Certificate にてACMで作成したSSL証明書を選択


  3. Supported HTTP Versions は、HTTP/2を選択


  4. Default Root Object にはS3Webサイトホスティングで設定した「インデックスドキュメント」と同じファイル名を指定


  5. Create Distribution で設定完了

CloudFrontは設定反映に少し時間がかかるため、愛する夫のために料理をしつつ、反映を待ちましょう。


3-3. ACM (AWS Certificate Manager)

❒ SSL証明書の発行

SSL証明書を発行します。

AWS Consoleのガイダンスに従って証明書発行作業を行います。

申請するドメインにACMからドメイン認証メールが届くので、メール受信設定を事前にしておいて下さい。

※ いずれかの1つで認証できればOKです。

例) example.com のドメインで証明書発行する場合の認証メール宛先

 ・ Whoisでドメイン管理者情報が公開されている連絡先メールアドレス
 ・ administrator@example.com
 ・ hostmaster@example.com
 ・ postmaster@example.com
 ・ webmaster@example.com
 ・ admin@example.com

メール届いたら「To approve this request, go to Amazon Certificate Approvals at (リンク)」のリンク部分を押下します。

承認画面に飛ぶので「I Approve」をクリック。

20161214042037.png

承認が完了したら、発行の完了です。

20161214042122.png

AWS Management Consoleへアクセスして、発行を確認しましょう。

20161214042234.png

無事、SSL(TLS)証明書が発行されました。


3-4. 疎通確認

ここで一度、疎通確認をしてみましょう。

CloudFrontのDistributionsをみると、反映状況が確認できます。

Status部分がDeployed 状態になったら、以下を試して疎通確認をしましょう。

❒ s3に直接アクセス: 【正】アクセス不可であること

$ curl http://example.com.s3-website-ap-northeast-1.amazonaws.com

<html>
<head><title>403 Forbidden</title></head>
<body>
<h1>403 Forbidden</h1>
<ul>
<li>Code: AccessDenied</li>
<li>Message: Access Denied</li>
<li>RequestId: xxxxxxxx </li>
<li>HostId: xxxxxxx </li>
</ul>
<hr/>
</body>
</html>

S3のアクセス制限の設定も確認してみましょう

20161214055943.png

CloudFrontからのアクセスのみが許可になっています。

❒ CloudFront経由でアクセス: 【正】アクセス可能であること

$ curl http://xxxxx.cloudfront.net/index.html

<html>
<head><title>301 Moved Permanently</title></head>
<body bgcolor="white">
<center><h1>301 Moved Permanently</h1></center>
<hr><center>CloudFront</center>
</body>
</html>

$ curl -L http://xxxxx.cloudfront.net/index.html
<!DOCTYPE html><html><head ....

$ curl https://xxxxx.cloudfront.net/index.html
<!DOCTYPE html><html><head ....

httpでのアクセスもちゃんとhttpsにリダイレクトされていますね。


3-5. Route53

❒ DNS設定

ドメインの公開をするため、DNSの設定を行います。ドメインに対してレコードを登録していきましょう。

【✕】 S3のendpointに紐付け

【◯】 CloudFront経由でS3紐付け

になるため、CloudFront側のDomain Nameをメモして下さい。

20161214064143.png

このDomain NameをDNSのレコードに追加します。

CNAMEで使いしたいところですが、問題が発生します。


DNSのレコードに登録するときの注意点

CNAMEレコードには特性があり、CNAMEレコードに登録したドメインと同じドメイン/サブドメインを他レコードに登録できません(RFC規約)

例) ↓ができない

example.com IN CNAME www.example.com.
example.com IN CNAME test.example.com.

例) ↓もできない
example.com IN CNAME www.example.com.
example.com IN NS ns4.p16.xxx.net.

例) ↓もできない
example.com IN CNAME www.example.com.
example.com IN A 216.239.xxx.xxx

例) ↓はOK
www.example.com IN CNAME example.com.
example.com IN A 216.239.xxx.xxx

今回でいうと、こうしたいところ

example.com IN CNAME [CloudFront Domain Name] ← ダメ

example.com IN A 98.138.xxx.xxx
example.com IN A 206.190.xxx.xxx
example.com IN A 203.xxx.xxx.xxx

ですが、RFC規約によりこの登録は出来ません。

そこでAmazon Route53は「Alias」という独自機能で ↑の状態を実現出来るようにしました。

DNSサーバーの外部からは A レコードとして見えるようになります。

example.com  Alias  [CloudFront Domain Name] 

 → 外部からは 「example.com IN A [CloudFrontのIP]」で見えている
example.com IN A 98.138.xxx.xxx
example.com IN A 206.190.xxx.xxx
...

このAlias、CloudFrontのIPアドレスが変更されると同時に A レコードが指すIPアドレスも自動的に変更される形になります。

ということで、CloudFrontのDomain Nameを、公開するホスト名のAliasesでレコード追加します。

20161214151637.png

これでRoute53の設定が出来ました。


4. 画面確認

Access!!!

$ curl https://example.com

Success!!!

$ curl https://example.com

<!DOCTYPE html><html><head ....


FIN

構築・運用工数を減らせる部分はどんどん減らしていきましょう。

そしてクリエイティブな作業に工数かけていきましょう。

MAY THE FORCE BE WITH YOU