[JAWS-UG CLI] ACM入門 #8 ALBでACM証明書を用いたHTTP/2配信環境の構築

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


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

us-east-1 リージョンにACMによるSSL証明書を発行済みであること。
（ALBは他リージョンでも利用可能ですが、今回はCloudFrontとSSL証明書を共有する事を想定しているため us-east-1 リージョンとしています）
us-east-1 リージョンにWebサーバが稼働しているEC2インスタンスが起動していること。

デフォルトVPCとサブネットが存在すること
-----------
us-east-1 リージョンにデフォルトVPCと、デフォルトVPCの各AZにサブネットが1つずつ存在すること。
アカウントを作成後、VPCに変更削除をしていなければ問題ありません。

必要な権限
-----------

トラブル防止の為、全ての操作が可能なクレデンシャル（アカウント）を原則として使用してください。

AWS CLI
-------

以下のバージョンで動作確認済

* AWS CLI 1.10.63

```bash:コマンド:
aws --version
```

```text:結果(例):
      aws-cli/1.10.63 Python/2.7.11 Darwin/15.6.0 botocore/1.4.53
```

注意：elbv2は2016年8月実装の新機能です。バージョン 1.10.57 より古い場合は動作しません。
次のコマンドを実行して最新版に更新しましょう。

```bash:コマンド:
sudo -H pip install -U awscli
```


0. 準備
=======


0.1. リージョンの決定
---------------------

今回はバージニアリージョン(us-east-1)で作業してください。

```bash:変数の設定:
export AWS_DEFAULT_REGION='us-east-1'
```

0.2. 変数の確認
---------------

プロファイルが想定のものになっていることを確認します。

```bash:変数の確認:
aws configure list
```
```text:結果(例):
            Name                    Value             Type    Location
            ----                    -----             ----    --------
         profile                <not set>             None    None
      access_key     ****************2LMA shared-credentials-file
      secret_key     ****************VXyK shared-credentials-file
          region                us-east-1      config-file    ~/.aws/config
```

0.3. VPC IDの取得
---------------

```bash:コマンド:
VPC_ID=` aws ec2 describe-vpcs \
          --filters Name=isDefault,Values=true \
          --query 'Vpcs[].VpcId' \
          --output text ` && echo ${VPC_ID}
```

```text:結果(例):
      vpc-xxxxxxxx
```

0.4. Subnet IDの取得
---------------

```bash:コマンド:
aws ec2 describe-subnets \
 --query "Subnets[?VpcId==\`${VPC_ID}\`].[SubnetId,CidrBlock]" --output text
```

```text:結果(例):
      subnet-77777777 172.31.48.0/20
      subnet-55555555 172.31.32.0/20
      subnet-11111111 172.31.0.0/20
      subnet-33333333 172.31.16.0/20
```

出力結果は左がsubnet id、右がそのサブネットのCIDRブロックです。
172.31.0.0/20のSubnet idを変数 SUBNET_ID1 へ、172.31.16.0/20のものをSUBNET_ID2 へ、コピーペーストして変数へ格納します。

```bash:コマンド:
SUBNET_ID1="subnet-11111111"
SUBNET_ID2="subnet-33333333"
```


0.5. 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.6. EC2インスタンスIDの変数への格納
-----------------

```bash:コマンド(例):
EC2_INSTANCE_ID=`aws ec2 describe-instances \
 --query Reservations[].Instances[].InstanceId \
 --output text` && echo ${EC2_INSTANCE_ID}
```
```text:結果:
      i-88888888888888888
```



1. セキュリティグループの作成
=================

1.1. ALB用セキュリティグループの作成
---------------

セキュリティグループ elbv2-handson-sg を作成します。

```bash:コマンド:
SG_NAME="elbv2-handson-sg"
SG_ID=`aws ec2 create-security-group --group-name ${SG_NAME} \
 --description ${SG_NAME} --output text` && echo ${SG_ID}
```
```text:結果(例):
      sg-59c3e63d
```

外部からのHTTPS接続をセキュリティグループに追加。

```bash:コマンド:
aws ec2 authorize-security-group-ingress --group-id ${SG_ID} --protocol 'tcp' --port 443 --cidr 0.0.0.0/0
```
```text:結果:
      返り値なし
```


2. アプリケーションロードバランサの作成
=================

2.1. ロードバランサの作成
-----------------------

```bash:コマンド
ELB_NAME="elbv2-handson-lb"
ELB_ARN=`aws elbv2 create-load-balancer --name ${ELB_NAME} \
 --subnets ${SUBNET_ID1} ${SUBNET_ID2} --security-groups ${SG_ID} \
 --query LoadBalancers[].LoadBalancerArn --output text `
aws elbv2 describe-load-balancers --load-balancer-arns ${ELB_ARN}
```
```text:結果(例):
      {
          "LoadBalancers": [
              {
                  "VpcId": "vpc-15b0ec70",
                  "LoadBalancerArn": "arn:aws:elasticloadbalancing:ap-northeast-1:571972591141:loadbalancer/app/elbv2-handson-lb/666a7169294ec5bc",
                  "State": {
                      "Code": "provisioning"
                  },
                  "DNSName": "elbv2-handson-lb-1654351783.ap-northeast-1.elb.amazonaws.com",
                  "SecurityGroups": [
                      "sg-c2a4b2a6"
                  ],
                  "LoadBalancerName": "elbv2-handson-lb",
                  "CreatedTime": "2016-09-13T13:05:11.480Z",
                  "Scheme": "internet-facing",
                  "Type": "application",
                  "CanonicalHostedZoneId": "Z14GRHDCWA56QT",
                  "AvailabilityZones": [
                      {
                          "SubnetId": "subnet-67919410",
                          "ZoneName": "ap-northeast-1a"
                      },
                      {
                          "SubnetId": "subnet-692a7730",
                          "ZoneName": "ap-northeast-1c"
                      }
                  ]
              }
          ]
      }
```


2.2. ターゲットグループの作成 (デフォルトアクション用)
-----------------

デフォルトアクション(リスナへ届いた通常のアクセスの転送先)用のターゲットグループを作成します。

```bash:コマンド:
ELB_TG_NAME="elbv2-handson-tg"
ELB_TG_ARN=`aws elbv2 create-target-group --name ${ELB_TG_NAME} \
 --protocol HTTP --port 80 --vpc-id ${VPC_ID} \
 --query TargetGroups[].TargetGroupArn --output text `
aws elbv2 describe-target-groups --target-group-arns ${ELB_TG_ARN}
```
```text:結果(例):
      {
          "TargetGroups": [
              {
                  "HealthCheckPath": "/",
                  "HealthCheckIntervalSeconds": 30,
                  "VpcId": "vpc-15b0ec70",
                  "Protocol": "HTTP",
                  "HealthCheckTimeoutSeconds": 5,
                  "HealthCheckProtocol": "HTTP",
                  "LoadBalancerArns": [],
                  "UnhealthyThresholdCount": 2,
                  "HealthyThresholdCount": 5,
                  "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:571972591141:targetgroup/elbv2-handson-tg/0dc3bae28b0c8b99",
                  "Matcher": {
                      "HttpCode": "200"
                  },
                  "HealthCheckPort": "traffic-port",
                  "Port": 80,
                  "TargetGroupName": "elbv2-handson-tg"
              }
          ]
      }
```


2.3. ターゲットグループへターゲットを登録
-----------------

ターゲット(Webサービスのインスタンス)をターゲットグループへ登録します。
インスタンスIDとポート番号の組み合わせで登録するので、コンテナインスタンスで複数のWebサービスコンテナが稼働していてもそれらを登録することができます。
ポート番号はターゲットグループのデフォルト値である場合、省略可能です。

```bash:コマンド:
aws elbv2 register-targets --target-group-arn ${ELB_TG_ARN} \
 --targets Id=${EC2_INSTANCE_ID}
```
```text:結果:
      返り値なし
```


2.4. HTTPSリスナの作成
-----------------

最後に外部からHTTPS(TCP/443)でアクセスを受け、ACMによるSSL証明書による処理を行い、ターゲットグループのWebサーバへHTTP(TCP/80)で接続するリスナを作成し、Webサーバへアクセスできるようにします。

```bash:コマンド:
ELB_LISTENER_ARN=`aws elbv2 create-listener --load-balancer-arn ${ELB_ARN} \
 --protocol HTTPS --port 443 --certificates CertificateArn=${CERT_ARN} \
 --default-actions Type=forward,TargetGroupArn=${ELB_TG_ARN} \
 --query Listeners[].ListenerArn --output text `
aws elbv2 describe-listeners --listener-arns ${ELB_LISTENER_ARN}
```
```text:結果(例):
      {
          "Listeners": [
              {
                  "Protocol": "HTTPS",
                  "DefaultActions": [
                      {
                          "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:571972591141:targetgroup/hoge/e155d347d35f05f0",
                          "Type": "forward"
                      }
                  ],
                  "SslPolicy": "ELBSecurityPolicy-2015-05",
                  "Certificates": [
                      {
                          "CertificateArn": "arn:aws:acm:ap-northeast-1:571972591141:certificate/07150260-d46a-4a5f-8f20-900a9573d0a6"
                      }
                  ],
                  "LoadBalancerArn": "arn:aws:elasticloadbalancing:ap-northeast-1:571972591141:loadbalancer/app/hoge/b6183b688ccfbdce",
                  "Port": 443,
                  "ListenerArn": "arn:aws:elasticloadbalancing:ap-northeast-1:571972591141:listener/app/hoge/b6183b688ccfbdce/c9dc5e52bde4de8e"
              }
          ]
      }
```

ここで３分ほど待ちます。
ロードバランサとターゲットが接続完了するのに時間がかかるからです。


2.5. ターゲットのヘルスチェック状況確認
-----------------

```bash:コマンド:
aws elbv2 describe-target-health --target-group-arn ${ELB_TG_ARN}
```
```text:結果(例):
      {
          "TargetHealthDescriptions": [
              {
                  "HealthCheckPort": "80",
                  "Target": {
                      "Id": "i-5d3f2bc2",
                      "Port": 80
                  },
                  "TargetHealth": {
                      "State": "healthy"
                  }
              }
          ]
      }
```

State が healthy になっていることを確認します。
initial である場合はヘルスチェックが規定回数まで達していないので少し待って再実行します。
unhealthy など他の内容が表示された場合は、チューターか講師へ声を掛けてください。


2.6. DNSNameの取得
-----------------

ALBはIPアドレスは動的で、DNS名をCNAMEまたはエイリアスレコードによって登録するためこの値を取得します。

```bash:コマンド:
DNSNAME=`aws elbv2 describe-load-balancers --load-balancer-arns ${ELB_ARN} \
 --query LoadBalancers[].DNSName --output text `
echo ${DNSNAME}
```
```text:結果(例):
      elbv2-handson-lb-1654351783.ap-northeast-1.elb.amazonaws.com
```




3. Route53の設定
=================

www.example.tk でアクセスできるようにするため、Route53の設定を行います。
今回はexample.tkでCloudFrontのS3コンテンツが、example.tkでALBによるEC2インスタンスのWebコンテンツが表示されるようにします。

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

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

```bash:コマンド:
cat << EOF > route53c.json
{
  "Comment": "ALB-AliasRecord",
  "Changes": [
    {
      "Action": "CREATE",
      "ResourceRecordSet": {
        "AliasTarget": {
          "HostedZoneId": "Z35SXDOTRQ7X7K",
          "EvaluateTargetHealth": false,
          "DNSName": "${DNSNAME}."
        },
        "Type": "A",
        "Name": "www.${DOMAINNAME}."
      }
    }
  ]
}
EOF
cat route53c.json
```
```text:結果:
      {
        "Comment": "ALB-AliasRecord",
        "Changes": [
          {
            "Action": "CREATE",
            "ResourceRecordSet": {
              "AliasTarget": {
                "HostedZoneId": "Z35SXDOTRQ7X7K",
                "EvaluateTargetHealth": false,
                "DNSName": "elbv2-handson-lb-254473736.us-east-1.elb.amazonaws.com."
              },
              "Type": "A",
              "Name": "www.example.tk."
            }
          }
        ]
      }
```

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

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

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


3.2. Route53への登録
-----------------

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



4. 動作確認
=================

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

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

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


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

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

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

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

おめでとうございます。
ハンズオンの最終目標までこれで達成です。