[JAWS-UG CLI] ACM入門 #6 CloudFrontでACMを用いたS3コンテンツのHTTP/2配信環境の構築

これは<A HREF="http://qiita.com/zakky/items/29cf8095f6faa24f216b" target=_blank>AWS Certificate Manager (ACM)入門ハンズオン資料</A>の一部です。
最初から続けて行っている場合「0.準備」の各項は作業不要です。<A HREF="#1-cloudfrontの設定">第1項へ進んでください</A>。

前提条件
========

ACMによるSSL証明書が発行済みであること。
S3によるWebコンテンツ配信環境が構築済みであること。
Route53へドメインが登録済みであること。


0. 準備
=======

0.1. ドメイン名の変数への格納
---------------

変数へドメイン名を格納します。
ここでは example.tk とします。自分のドメイン名に読み替えてください。

```bash:コマンド:
DOMAINNAME="example.tk" && echo ${DOMAINNAME}
```

```text:結果(例):
      example.tk
```

0.2. コンテンツ配信用S3バケット名とルートオブジェクトの変数への格納
-----------------

S3バケット名を www.ドメイン名 とします。（例: www.example.tk ）
独自のバケット名を設定した場合は、それを設定してください。

```bash:コマンド:
WEB_S3_BUCKET="www.${DOMAINNAME}" && echo ${WEB_S3_BUCKET}
```
```text:結果:
      www.example.tk
```

ルートオブジェクト（ / でアクセスした場合に表示する内容）を index.html とします。

```bash:コマンド:
ROOT_OBJECT="index.html" && echo ${ROOT_OBJECT}
```
```text:結果:
      index.html
```


0.3. ACM証明書arnの変数への格納
-----------------

証明書のarnを変数 CERT_ARN へ格納します。

```bash:コマンド(例):
CERT_ARN=`aws acm list-certificates --region us-east-1 \
 --query CertificateSummaryList[].CertificateArn \
 --output text` && echo ${CERT_ARN}
```
```text:結果:
      arn:aws:acm:us-east-1:777777777777:certificate/9eb6865a-63a3-48df-92c2-ae113c877b33
```


0.4. Route53 HostedIdの変数への格納
-----------------

Route53のHostedIdを変数 HOSTED_ZONE_ID へ格納します。

```bash:コマンド(例):
HOSTED_ZONE_ID=`aws route53 list-hosted-zones \
 --query HostedZones[].Id --output text` && echo ${HOSTED_ZONE_ID}
```
```text:結果:
      /hostedzone/Z1S92TCRCPLEYH
```



1. CloudFrontの設定
=================

CloudFrontではELB(ALB,CLB),S3バケットなどを配信するコンテンツ元に設定できますが、今回はS3バケットに配信を行うコンテンツを作成します。

1.1. プレビュー機能の有効化
---------------

AWS CLIのCloudFrontは未だに何故かPreviewです。このためそのままでは使用できません。
次のコマンドを実行してプレビュー機能を有効化します。

```bash:コマンド:
aws configure set preview.cloudfront true
```

```text:結果:
      返り値なし
```

1.2. コーラーリファレンスの決定
-----------------

コーラーリファレンスはディストリビューション作成時に他と区別できるようにするため一意性が必要な任意の文字列です。
アクセスする際には何ら影響を及ぼしません。無機質な文字列でも構いません。
ここでは便宜上S3バケット名と同じにしておきます。

```bash:コマンド:
CALLER_REFERENCE="${WEB_S3_BUCKET}" && echo ${CALLER_REFERENCE}
```
```text:結果:
      www.example.tk
```

1.3. ディストリビューション用JSONファイルの作成
-----------------

ディストリビューションの構成情報はJSONファイルに記載します。
・ACM証明書によるTLS(HTTPS)ターミネート
・コンテンツ元はS3バケット
・HTTP/2対応
・利用エッヂロケーションはアジア・欧州・北米 (料金クラス200)

```bash:コマンド:
cat << EOF > cf.json
{
    "CallerReference": "${CALLER_REFERENCE}", 
    "Aliases": {
        "Items": [
            "${DOMAINNAME}"
        ],
        "Quantity": 1
    },
    "DefaultRootObject": "${ROOT_OBJECT}", 
    "Origins": {
        "Quantity": 1, 
        "Items": [
            {
                "Id": "${WEB_S3_BUCKET}", 
                "DomainName": "${WEB_S3_BUCKET}.s3.amazonaws.com", 
                "S3OriginConfig": {
                    "OriginAccessIdentity": ""
                }
            }
        ]
    }, 
    "DefaultCacheBehavior": {
        "TargetOriginId": "${WEB_S3_BUCKET}", 
        "ForwardedValues": {
            "QueryString": false, 
            "Cookies": {
                "Forward": "none"
            } 
        }, 
        "TrustedSigners": {
            "Enabled": false, 
            "Quantity": 0
        }, 
        "ViewerProtocolPolicy": "allow-all", 
        "MinTTL": 0, 
        "AllowedMethods": {
            "Quantity": 2, 
            "Items": [
                "GET",
                "HEAD"
            ]
        }
    }, 
    "CacheBehaviors": {
        "Quantity": 0 
    }, 
    "Comment": "", 
    "Logging": {
        "Enabled": false,
        "IncludeCookies": false,
        "Bucket": "",
        "Prefix": ""
    }, 
    "PriceClass": "PriceClass_200", 
    "Enabled": true, 
    "ViewerCertificate": {
        "ACMCertificateArn": "${CERT_ARN}", 
        "SSLSupportMethod": "sni-only", 
        "MinimumProtocolVersion": "TLSv1", 
        "Certificate": "${CERT_ARN}", 
        "CertificateSource": "acm"
    }, 
    "Restrictions": {
        "GeoRestriction": {
            "RestrictionType": "none", 
            "Quantity": 0
        }
    },
    "HttpVersion": "http2"
}
EOF
```

```text:結果:
      返り値なし
```

JSONファイルを作成したら、フォーマットが壊れてないか必ず確認します。

```bash:コマンド:
jsonlint -q cf.json
```
```text:結果:
      返り値なし
```

1.4. ディストリビューションの作成
-----------------

準備が整ったのでディストリビューションを作成します。

```bash:コマンド:
aws cloudfront create-distribution --distribution-config file://cf.json
```
```text:結果(例):
      {
          "Distribution": {
              "Status": "InProgress",
              "DomainName": "d2k4r1uwgpraj8.cloudfront.net",
              "InProgressInvalidationBatches": 0,
              "DistributionConfig": {
                  "Comment": "",
                  "CacheBehaviors": {
                      "Quantity": 0
                  },
                  "Logging": {
                      "Bucket": "",
                      "Prefix": "",
                      "Enabled": false,
                      "IncludeCookies": false
                  },
                  "WebACLId": "",
                  "Origins": {
                      "Items": [
                          {
                              "S3OriginConfig": {
                                  "OriginAccessIdentity": ""
                              },
                              "OriginPath": "",
                              "CustomHeaders": {
                                  "Quantity": 0
                              },
                              "Id": "www.example.tk",
                              "DomainName": "www.example.tk.s3.amazonaws.com"
                          }
                      ],
                      "Quantity": 1
                  },
                  "DefaultRootObject": "index.html",
                  "PriceClass": "PriceClass_All",
                  "Enabled": true,
                  "DefaultCacheBehavior": {
                      "TrustedSigners": {
                          "Enabled": false,
                          "Quantity": 0
                      },
                      "TargetOriginId": "www.example.tk",
                      "ViewerProtocolPolicy": "allow-all",
                      "ForwardedValues": {
                          "Headers": {
                              "Quantity": 0
                          },
                          "Cookies": {
                              "Forward": "none"
                          },
                          "QueryStringCacheKeys": {
                              "Quantity": 0
                          },
                          "QueryString": false
                      },
                      "MaxTTL": 31536000,
                      "SmoothStreaming": false,
                      "DefaultTTL": 86400,
                      "AllowedMethods": {
                          "Items": [
                              "HEAD",
                              "GET"
                          ],
                          "CachedMethods": {
                              "Items": [
                                  "HEAD",
                                  "GET"
                              ],
                              "Quantity": 2
                          },
                          "Quantity": 2
                      },
                      "MinTTL": 0,
                      "Compress": false
                  },
                  "CallerReference": "www.example.tk",
                  "ViewerCertificate": {
                      "SSLSupportMethod": "sni-only",
                      "ACMCertificateArn": "arn:aws:acm:us-east-1:777777777777:certificate/3429271a-b281-4996-829a-068d938679ea",
                      "MinimumProtocolVersion": "TLSv1",
                      "Certificate": "arn:aws:acm:us-east-1:777777777777:certificate/3429271a-b281-4996-829a-068d938679ea",
                      "CertificateSource": "acm"
                  },
                  "CustomErrorResponses": {
                      "Quantity": 0
                  },
                  "HttpVersion": "http2",
                  "Restrictions": {
                      "GeoRestriction": {
                          "RestrictionType": "none",
                          "Quantity": 0
                      }
                  },
                  "Aliases": {
                      "Items": [
                          "example.tk"
                      ],
                      "Quantity": 1
                  }
              },
              "ActiveTrustedSigners": {
                  "Enabled": false,
                  "Quantity": 0
              },
              "LastModifiedTime": "2016-10-13T15:30:46.198Z",
              "Id": "EQSY786QZ6BPJ",
              "ARN": "arn:aws:cloudfront::571972591141:distribution/E121BR1Z9DCSQB"
          },
          "ETag": "E1EMCKDD6ZMT90",
          "Location": "https://cloudfront.amazonaws.com/2016-09-07/distribution/EQSY786QZ6BPJ"
      }
```

ディストリビューションIDの取得

```bash:コマンド:
CF_DIST_ID=`aws cloudfront list-distributions \
 --query DistributionList.Items[].Id --output text` && echo ${CF_DIST_ID}
```
```text:結果(例):
      EQSY786QZ6BPJ
```

ディストリビューションのFQDNの取得

```bash:コマンド:
CF_DOMAINNAME=`aws cloudfront list-distributions \
 --query DistributionList.Items[].DomainName \
 --output text` && echo ${CF_DOMAINNAME}
```
```text:結果(例):
      d1ah72z09rcb49.cloudfront.net
```

このFQDNでディストリビューションへアクセスできます。
自分のドメイン名のCNAMEレコードにこれを登録することで独自ドメインでもアクセスできるようになります。




2. Route53の設定
=================

example.tk でアクセスできるようにするため、Route53の設定を行います。

2.1. Route53レコードセットの作成
-----------------

CloudFrontやELBはCNAMEでDNSへ通常登録しますが、Zone APEX(www等が付かない example.tk )にはCNAMEレコードを登録できないためこれができません。
そこでRoute53の独自機能であるエイリアスレコードによってこれを実現します。

まずRoute53へレコードを登録するためのJSONファイルを作成します。

```bash:コマンド:
cat << EOF > route53b.json
{
  "Comment": "AliasRecord",
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "AliasTarget": {
          "HostedZoneId": "Z2FDTNDATAQYW2",
          "EvaluateTargetHealth": false,
          "DNSName": "${CF_DOMAINNAME}."
        },
        "Type": "A",
        "Name": "${DOMAINNAME}."
      }
    }
  ]
}
EOF
cat route53b.json
```
```text:結果:
      {
        "Comment": "AliasRecord",
        "Changes": [
          {
            "Action": "CREATE",
            "ResourceRecordSet": {
              "AliasTarget": {
                "HostedZoneId": "Z2FDTNDATAQYW2",
                "EvaluateTargetHealth": false,
                "DNSName": "d1ah72z09rcb49.cloudfront.net."
              },
              "Type": "A",
              "Name": "example.tk."
            }
          }
        ]
      }
```

HostedZoneIdに書かれている文字列は全ユーザ共通の固定値です。
CloudFrontをエイリアスレコードに登録する場合は常にこの値を使用します。

JSONファイルを作成したら、フォーマットが壊れてないか必ず確認します。

```bash:コマンド:
jsonlint -q route53.json
```
```text:結果:
      返り値なし
```


2.2. Route53への登録
-----------------

```bash:コマンド:
aws route53 change-resource-record-sets \
 --hosted-zone-id ${HOSTED_ZONE_ID} --change-batch file://route53b.json
```
```text:結果(例):
      {
          "ChangeInfo": {
              "Status": "PENDING",
              "Comment": "AliasRecord",
              "SubmittedAt": "2016-10-13T08:34:57.794Z",
              "Id": "/change/C3SSDTIES1RAMM"
          }
      }
```



3. 動作確認
=================

3.1. CloudFrontのデプロイ完了を待つ
-----------------

```bash:コマンド:
aws cloudfront wait distribution-deployed --id ${CF_DIST_ID}
```
```text:結果:
      返り値なし
```

プロンプトが帰ってくるまでこれは１５～３０分ぐらいかかります。
waitコマンドには待てる限界が存在し、万一タイムアウトして
Waiter DistributionDeployed failed: Max attempts exceeded
という出力が返って来たらコマンドを再実行してください。




AWSJ亀田さんによる基調講演
====

この待ち時間の間に、本日は亀田さんによる基調講演を行います。
皆様ステージにご注目ください。
基調講演終了後、次へ進みます。




3.2. Webブラウザでアクセス
-----------------

```bash:コマンド:
echo https://${DOMAINNAME}/
```
```text:結果(例):
      https://example.tk/
```

WebブラウザでURLへアクセスしてみます。問題無く表示されたでしょうか。
Route53の機能であるエイリアスレコード機能によって、Zone APEXでも別名が利用できることがわかります。


3.3. HTTP/2対応の確認
-----------------

これは外部のサイトで確認することができます。
https://tools.keycdn.com/http2-test
へアクセスして、URL欄に自分のサイトを入力してみましょう。

```text:結果(例):
      Yeah! example.tk supports HTTP/2.0.
```

緑色でこのような表示がされればHTTP/2対応です。