AWS Application Load Balancer CLIメモ

Application Load Balancer とは
======
AWSのロードバランサとしてはElastic Load Balancer(ELB)が従来から存在しますが、これはEC2インスタンスのみを配下に接続可能で、その接続ポートも全インスタンス共通のL4ロードバランサでした。
今回発表のApplication Load Balancerは、EC2の他、ECSによるコンテナインスタンスでポートが異なるサービスを接続したり、URLパスにより接続するインスタンスを変化させる等、L7ロードバランサとして動作します。

もちろんELB同様、ACM(無料のSSL証明書)を用いたSSLターミネートなども利用可能で、基本的にELBの上位互換として動作します。
みんな大好きAWSCLIで使ってみましょう。

elbv2 と呼びます
========
AWS CLIでの機能名は elbv2 と書かれています。 alb ではありません。
このことからも、AWS内部ではELBバージョン2として作られたことが伺えます。

elbとelbv2の相違点
========
- elbv2では同一VPCで2以上のAZに2以上のサブネットが必要です。elbは単一サブネットでも構築可能ですがこれができなくなりました。ただし接続するインスタンスは1台でも構いません。
- SSLターミネート処理で、elbv2のみ選択可能な最新ポリシーがある。

前提条件
========
- ECSでもEC2でも構いませんが２台のインスタンスでWebサービスが２以上稼働していること。
- ここでは２台のインスタンスにポート80でapache、ポート8080でnginxを稼働させています。
- 同一VPCにサブネットが2つ以上存在すること。
- AWSCLI バージョン 1.10.56 以上がインストールされていること。

実行権限
--------------
- ELBを操作できること。

AWS CLIのバージョン
-------------------
elbv2はバージョン1.10.56で追加された機能です。
これより古いバージョンでは動作しませんから必ず確認しましょう。

```bash:コマンド:
aws --version
```
```text:結果(例):
      aws-cli/1.10.56 Python/2.6.6 Linux/2.6.32-642.3.1.el6.x86_64 botocore/1.4.46
```

今回の目的
--------------
２台のEC2インスタンスでWebサーバが稼働しており、まずは単純に振り分けることを目的とします。
次に、URLパスによるルーティングを追加する試みとして、/cgi-bin/ 以下のURLへアクセスがあった場合 tcp/8080で動作しているwebサーバへ振り分ける機能を追加します。


0. 事前作業
===========

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

Webサービスが動作しているリージョンで実行します。
ここでは ap-northeast-1 (東京)リージョンにて作業します。
自分の環境に合わせて設定してください。

```bash:変数の設定:
export AWS_DEFAULT_REGION='ap-northeast-1'
```

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

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

```bash:コマンド
aws configure list
```
```text:結果(例):
            Name                    Value             Type    Location
            ----                    -----             ----    --------
         profile                <not set>             None    None
      access_key     ****************M73Q shared-credentials-file
      secret_key     ****************YWg/ shared-credentials-file
          region           ap-northeast-1              env    AWS_DEFAULT_REGION
```


1. ELBv2の構築
===========

ELBv2では、最初にサブネットを指定してロードバランサの全体環境を構築して、次にターゲットグループと呼ばれる接続先を定義するグループを構築、そして外部からのアクセスを受けるリスナーを構築しこれにターゲットグループを接続してアクセス可能になる流れです。

1.1. サブネットの確認
---------------------

ELBv2で使用するサブネットを確認してください。ここでは
- subnet-11111111
- subnet-22222222
の2つを使用することにします。自分の環境に合わせて設定してください。

```bash:コマンド:
SUBNET_ID1="subnet-11111111"
SUBNET_ID2="subnet-22222222"
```
```text:結果:
      (返り値なし)
```

1.2. VPCIDの確認
---------------------

上記で使用するサブネットが全て同一のVPCである事を必ず確認してください。
異なるVPCのサブネットは接続出来ません。ここではVPCID
- vpc-33333333
を使用することにします。自分の環境に合わせて設定してください。

```bash:コマンド:
VPC_ID="vpc-33333333"
```
```text:結果:
      (返り値なし)
```

1.3. 登録するインスタンスとポート番号
---------------------

ロードバランサへ登録するインスタンスを確認してください。ここでは
- i-55555555 ポート番号80
- i-66666666 ポート番号80
の2つを使用することにします。自分の環境に合わせて設定してください。

```bash:コマンド:
INSTANCE1_ID="i-55555555"
INSTANCE1_PORT="80"
INSTANCE2_ID="i-66666666"
INSTANCE2_PORT="80"
```
```text:結果:
      (返り値なし)
```

1.4. セキュリティグループの作成
-----------------------

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

```bash:コマンド:
SG_NAME="elbv2-cli-sg"
aws ec2 create-security-group --group-name ${SG_NAME} --description ${SG_NAME} --vpc-id ${VPC_ID}
SG_ID=`aws ec2 describe-security-groups --filter Name=group-name,Values=${SG_NAME} \
 --query 'SecurityGroups[].GroupId' --output text` && echo ${SG_ID}
```
```text:結果(例):
      {
          "GroupId": "sg-99999999"
      }

      sg-99999999
```

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

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

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

それでは作成します。名前を elbv2-cli とします。（任意の名前可）

```bash:コマンド
ELB_NAME="elbv2-cli"
ELB_ARN=`aws elbv2 create-load-balancer --name ${ELB_NAME} \
 --subnets ${SUBNET_ID1} ${SUBNET_ID2} --security-groups ${SG_ID} \
 --query LoadBalancers[].LoadBalancerArn --output text `
DNSName=`aws elbv2 describe-load-balancers --load-balancer-arns ${ELB_ARN} \
 --query LoadBalancers[].DNSName --output text `
aws elbv2 describe-load-balancers --load-balancer-arns ${ELB_ARN}
```
```text:結果(例):
      {
          "LoadBalancers": [
              {
                  "VpcId": "vpc-33333333",
                  "LoadBalancerArn": "arn:aws:elasticloadbalancing:ap-northeast-1:777777777777:loadbalancer/app/elbv2-cli/bd12da1588c83561",
                  "State": {
                      "Code": "provisioning"
                  },
                  "DNSName": "elbv2-cli-1743316501.ap-northeast-1.elb.amazonaws.com",
                  "SecurityGroups": [
                      "sg-99999999"
                  ],
                  "LoadBalancerName": "elbv2-cli",
                  "CreatedTime": "2016-08-13T01:21:05.500Z",
                  "Scheme": "internet-facing",
                  "Type": "application",
                  "CanonicalHostedZoneId": "Z14GRHDCWA56QT",
                  "AvailabilityZones": [
                      {
                          "SubnetId": "subnet-11111111",
                          "ZoneName": "ap-northeast-1a"
                      },
                      {
                          "SubnetId": "subnet-22222222",
                          "ZoneName": "ap-northeast-1c"
                      }
                  ]
              }
          ]
      }
```

1.6. ターゲットグループの作成(通常アクセス用)
-----------------

HTTP(tcp/80)でアクセスを受けるターゲットグループを作成します。

```bash:コマンド:
ELB_TG_NAME="elbv2-cli-tg"
ELB_TG=`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}
```
```text:結果(例):
      {
          "TargetGroups": [
              {
                  "HealthCheckPath": "/",
                  "HealthCheckIntervalSeconds": 30,
                  "VpcId": "vpc-33333333",
                  "Protocol": "HTTP",
                  "HealthCheckTimeoutSeconds": 5,
                  "HealthCheckProtocol": "HTTP",
                  "UnhealthyThresholdCount": 2,
                  "HealthyThresholdCount": 5,
                  "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:777777777777:targetgroup/elbv2-cli-tg/de1a0a8eabbabd2f",
                  "Matcher": {
                      "HttpCode": "200"
                  },
                  "HealthCheckPort": "traffic-port",
                  "Port": 80,
                  "TargetGroupName": "elbv2-cli-tg"
              }
          ]
      }
```

1.7. ターゲットグループへインスタンスを登録
-----------------

稼働しているWebサービスのインスタンスをターゲットグループへ登録します。
このようにインスタンスIDとポート番号の組み合わせで登録するので、コンテナインスタンスで複数のWebサービスコンテナが稼働していてもそれらを登録することができます。

```bash:コマンド:
aws elbv2 register-targets --target-group-arn ${ELB_TG} \
 --targets Id=${INSTANCE1_ID},Port=${INSTANCE1_PORT} Id=${INSTANCE2_ID},Port=${INSTANCE2_PORT}
```
```text:結果:
      返り値なし
```

ポート番号をここでは全て指定していますが、ターゲットグループで設定したポート番号をインスタンス側でも使用する場合は省略可能です。(この例ではtcp/80)
何か表示されたらエラーなので確認しましょう。


1.8. リスナの作成
-----------------

最後に外部からHTTP(tcp/80)でアクセスを受けるリスナを作成して、先に作成したターゲットグループを接続してアクセスできるようにします。

```bash:コマンド:
ELB_LISTENER=`aws elbv2 create-listener --load-balancer-arn ${ELB_ARN} --protocol HTTP --port 80 \
 --default-actions Type=forward,TargetGroupArn=${ELB_TG} \
 --query Listeners[].ListenerArn --output text `
aws elbv2 describe-listeners --listener-arns ${ELB_LISTENER}
```
```text:結果(例):
      {
          "Listeners": [
              {
                  "Protocol": "HTTP",
                  "DefaultActions": [
                      {
                          "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:777777777777:targetgroup/elbv2-cli-tg/de1a0a8eabbabd2f",
                          "Type": "forward"
                      }
                  ],
                  "LoadBalancerArn": "arn:aws:elasticloadbalancing:ap-northeast-1:777777777777:loadbalancer/app/elbv2-cli/bd12da1588c83561",
                  "Port": 80,
                  "ListenerArn": "arn:aws:elasticloadbalancing:ap-northeast-1:777777777777:listener/app/elbv2-cli/bd12da1588c83561/f1edf2404136e22f"
              }
          ]
      }
```


2. アクセスして動作確認
========

実際にDNSNameへアクセスしてWebページが表示されるか確認しましょう。

```bash:コマンド:
curl http://${DNSName}
```
```text:結果(例):
      Webページの内容が表示される
```

普通にWebブラウザでアクセスしてももちろん構いません。


3. Pass-Based ルーティングの追加
========

ここまででロードバランサは動作しました。
次に、特定のURLのみ別のサーバへ振り分けるPass-Based Routing の追加を行います。
ここでは /cgi-bin/ へのアクセスをポート8080で受けるインスタンスへ振り分けることにします。

3.1. 登録するインスタンスとポート番号
---------------------

ここでは
- i-55555555 ポート番号8080
- i-66666666 ポート番号8080
の2つを使用することにします。自分の環境に合わせて設定してください。

```bash:コマンド:
INSTANCE3_ID="i-55555555"
INSTANCE3_PORT="8080"
INSTANCE3_ID="i-66666666"
INSTANCE3_PORT="8080"
```
```text:結果:
      (返り値なし)
```

3.1. ターゲットグループの構築(Pass-Based Routing用)
-----------------

Pass Based Routingはターゲットグループに設定します。
このため、新しくターゲットグループを作成します。

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

3.2. ターゲットグループへインスタンスを登録(Pass-Based Routing用)
-----------------

```bash:コマンド:
aws elbv2 register-targets --target-group-arn ${ELB_TG2} \
 --targets Id=${INSTANCE3_ID},Port=${INSTANCE3_PORT} Id=${INSTANCE4_ID},Port=${INSTANCE4_PORT}
```
```text:結果:
      返り値なし
```

3.3. リスナへルールを追加
-----------------

リスナへPass-Based Routingのルールを追加し、/cgi-bin/ へのアクセスを振り分けるターゲットグループを設定します。

```bash:コマンド:
aws elbv2 create-rule --listener-arn ${ELB_LISTENER} --priority 10 \
--conditions Field=path-pattern,Values='/cgi-bin/*' \
--actions Type=forward,TargetGroupArn=${ELB_TG2}
```
```text:結果(例):
      {
          "Rules": [
              {
                  "Priority": "10",
                  "Conditions": [
                      {
                          "Field": "path-pattern",
                          "Values": [
                              "/cgi-bin/*"
                          ]
                      }
                  ],
                  "RuleArn": "arn:aws:elasticloadbalancing:ap-northeast-1:777777777777:listener-rule/app/elbv2-cli/51d50a8cdff26750/35560600bd1e51e2/89c54c4e952b753a",
                  "IsDefault": false,
                  "Actions": [
                      {
                          "TargetGroupArn": "arn:aws:elasticloadbalancing:ap-northeast-1:777777777777:targetgroup/elbv2-cli-tg2/d83dd8bc4d9e2f86",
                          "Type": "forward"
                      }
                  ]
              }
          ]
      }
```


4. アクセスして動作確認
========

実際にDNSNameへアクセスしてWebページが表示されるか確認しましょう。

```bash:コマンド:
curl http://${DNSName}/
curl http://${DNSName}/cgi-bin/
```
```text:結果(例):
      / へアクセスするとapacheが、
      /cgi-bin/ へアクセスするとnginxが応答を返せば成功です。
```

普通にWebブラウザでアクセスしてももちろん構いません。
セキュリティグループで外部からはポート80しかアクセス許可していないのに、インスタンス上ではポート8080で動作しているnginxが返事をしていることからロードバランサが正しく動作していることがわかるかと思います。



5. まとめ
===========

外部からアクセスを受けるリスナでURLなどの条件から振り分け先のターゲットグループを決め、振り分けられたターゲットグループが配下のインスタンスへトラヒックを流す。
という２段階で動作しているのが従来のELBと異なるので最初はアクセス出来ず戸惑いましたが、わかれば簡単に構築できました。

SSL証明書の登録とターミネートについては追って書きたいと思います。