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

この記事は リクルートライフスタイル [Advent Calendar 2016](http://qiita.com/advent-calendar/2016/recruitlifestyle) の15日目の記事です。
@MomokoAsai です。社内ではFirebaseの推進を行っております。
それではさっそく本題に。



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

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

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

- Google はChromium Blog 「[Moving Towards a More Secure Web](https://blog.chromium.org/2016/09/moving-towards-more-secure-web.html)」で全 HTTP サイト(非SSL/TLS サイト)に対して警告を表示させることを公言。
- [Googleウェブマスター向け公式ブログ](https://webmaster-ja.googleblog.com/2014/08/https-as-ranking-signal.html)にて、HTTPSをSEOで優遇することを言及。

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

# 全体構成
![全体構成図.png](https://qiita-image-store.s3.amazonaws.com/0/15055/19fb413d-112c-4647-8bb2-e5d04c35e3e7.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月](https://aws.amazon.com/jp/about-aws/whats-new/2016/05/aws-certificate-manager-now-available-in-more-regions/)に東京リージョンがリリース
    - SSL(TLS)証明書を無料で発行
    - さらに、証明書の更新を自動で行ってくるため、メンテナンスフリー

- CloudFront
    - [2016年9月](http://dev.classmethod.jp/cloud/aws/cloudfront-http2/)に東京リージョンでhttp/2プロトコルを使った配信が可能になりました。



## アプリケーション構成
- Ruby 2.3.3<br> 
- static site generatorsの[Middleman](https://middlemanapp.com/jp/)を使用
    - gem [middleman](https://github.com/middleman/middleman) で静的サイトをジェネレート
    - gem [middleman-s3_sync](https://github.com/fredjean/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](http://engineer.recruit-lifestyle.co.jp/techblog/2015-10-05-engineer-portal-site-2/)の作成にも使われております。

# 手順
## 1. Github
今回用意するブランチは以下です。

|ブランチ名|用途|
|:----------|:------------|
|master|本番にReleaseされるブランチ|
|staging|検証環境にReleaseされるブランチ|
|feature/xxx|開発用ブランチ|

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

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

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

``` ruby: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](https://qiita-image-store.s3.amazonaws.com/0/15055/f9d0c510-7019-511e-f753-ff07b256992d.png)


CircleCIの設定詳細については[公式ドキュメント](https://circleci.com/docs/configuration/
)を参照ください


## 3. AWS

### 3-1. S3

❒ Bucket作成
静的Webサイトホスティングを行うBucketを作成します。
作成が完了したら、CircleCIの環境変数に設定することをお忘れなく。


❒ 静的ウェブサイトホスティング機能設定
S3で静的ウェブサイトホスティング機能を有効にします。(本番環境、検証環境)
この時、「インデックスドキュメント」を設定します。「エラードキュメント」は”あると尚良し”という感じです。

![20161214062225.png](https://qiita-image-store.s3.amazonaws.com/0/15055/b8c634dc-c9f6-b9fd-9cfc-c30ed50aa76e.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を指定<br>  <font color='grey'>→ CloudFrontがS3をオリジンサーバーとして認識し、S3を見に行くようになります。</font>
3. **Origin Access Identity** を **Create a New Identity** にし、アクセス制御の設定を新規作成
4. **Grant Read Permissions on Bucket** を **Yes, Update Bucket Policy**に設定<br> <font color='grey'>→ この設定でCloudFrontにS3バケット内のオブジェクト読み取り許可が付与されます。</font>


❒ 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 Certificate** の **Request or Import a Certificate with ACM**を選択し、SSL証明書の発行を行う<font color='grey'>(ACMでのSSL証明書発行方法は後述)</font>。発行が完了したら、**Custom SSL Certificate** にてACMで作成したSSL証明書を選択
2. **Supported HTTP Versions** は、`HTTP/2`を選択
3. **Default Root Object** にはS3Webサイトホスティングで設定した「インデックスドキュメント」と同じファイル名を指定
4. **Create Distribution** で設定完了

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

### 3-3. ACM (AWS Certificate Manager)
❒ SSL証明書の発行

SSL証明書を発行します。
AWS Consoleのガイダンスに従って証明書発行作業を行います。
申請するドメインにACMからドメイン認証メールが届くので、<font color='red'>メール受信設定を事前にしておいて下さい。</font>
※ いずれかの１つで認証できれば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](https://qiita-image-store.s3.amazonaws.com/0/15055/037e54d2-159a-10a9-5392-c09f89689bff.png)

承認が完了したら、発行の完了です。
![20161214042122.png](https://qiita-image-store.s3.amazonaws.com/0/15055/121f2395-afac-fc68-4bf5-84194b9dd543.png)

AWS Management Consoleへアクセスして、発行を確認しましょう。
![20161214042234.png](https://qiita-image-store.s3.amazonaws.com/0/15055/fd36f529-03db-1800-d6d4-dfa1c73e1523.png)

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

### 3-4. 疎通確認
ここで一度、疎通確認をしてみましょう。
CloudFrontの**Distributions**をみると、反映状況が確認できます。
**Status**部分が<font color='green'>Deployed</font> 状態になったら、以下を試して疎通確認をしましょう。

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

```shell-session
$ 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](https://qiita-image-store.s3.amazonaws.com/0/15055/fc96ec57-5fe7-d39f-b58c-d914ab4deeae.png)

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


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

```shell-session
$ 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](https://qiita-image-store.s3.amazonaws.com/0/15055/88e5df0b-dc44-8af0-43a6-78f9ddf7a2d3.png)

このDomain NameをDNSのレコードに追加します。
CNAMEで使いしたいところですが、<font color='red'>問題が発生します。</font>

#### DNSのレコードに登録するときの注意点
CNAMEレコードには特性があり、CNAMEレコードに登録したドメインと<font color='red'>同じドメイン/サブドメインを他レコードに登録できません（RFC規約）</font>

```
例） ↓ができない
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規約によりこの登録は<font color='red'>出来ません。</font>

そこで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](https://qiita-image-store.s3.amazonaws.com/0/15055/1174bce8-aa29-0358-f088-78a92bd1171e.png)

これでRoute５３の設定が出来ました。

## 4. 画面確認

Access!!!

```
$ curl https://example.com
```
Success!!!

```
$ curl https://example.com
<!DOCTYPE html><html><head ....
```



# FIN

構築・運用工数を減らせる部分はどんどん減らしていきましょう。
そしてクリエイティブな作業に工数かけていきましょう。

MAY THE FORCE BE WITH YOU