AWS CLIでALBをカジュアルに操作するスクリプトを書いた

※この記事は[サーバーワークスAdvent Calendar 2016](http://qiita.com/advent-calendar/2016/serverworks)、12月13日の記事として書いています。

## AWSは全部CLIで操作したい
サーバーワークスといえば**AWS**、AWSといえば**APIが豊富**。APIの操作といえば**CLI**、ですよね？そうですよね。

## ALB最高だけどさ

AWSの[**Application Load Balancing(ALB)**](https://aws.amazon.com/jp/blogs/news/new-aws-application-load-balancer/)について。
パス毎に異なるターゲットにアクセスを振り分けたり非常に便利なALBですが、ひとつだけ気になる点があったんですね。
機能が増えた分、これまでの**Elastic Load Balancing(ELB)**と比較して操作がちょっとめんどくさいなー、と思っていました。

まずELBと根本的に違うのは**ターゲットグループ**の存在です。
バックエンドインスタンスはターゲットグループに所属し、フロントのALBはルールに基づいてターゲットグループへリクエストをルーティングします。
なので、平易に書くと
**ALB <---> ターゲットグループ <---> インスタンス**
となるわけです。

というわけで、バックエンドのEC2インスタンスを取り外したりする際には、ALBではなくターゲットグループを操作する必要があります。処理にはターゲットグループのARNが必要となります。

## 例えば

あるALBに紐付いているターゲットグループのARNを引っ張ってこようと思うと、以下のようなシェルコマンドになります。

```shell-session
$ aws elbv2 describe-load-balancers ¥
  --query 'LoadBalancers[].[LoadBalancerName,LoadBalancerArn]' ¥
  --output text | grep -E "^<ALB名>¥t.*" | cut -f2 | ¥
  xargs -i aws elbv2 describe-listeners ¥
    --load-balancer-arn {} ¥
    --query 'Listeners[].DefaultActions[].TargetGroupArn' --output text
```

長い。ALBと言いつつ、APIのサブコマンドはelbv2ってのもなかなかややこしいっすよね。
こうして取得したARNに対して、さらにインスタンスを追加したり取り外したりといった指示を出すことになります。長い。

# スクリプト作成

なので、このへんを単純化するシェルスクリプトをいくつか書きました。コメント部に引数とか書いといたんで適当な名前で保存して使ってやって下さい。
実行にはAWS CLIのインストールが必要です。インストール方法やら何やらは[このへん](https://blog.serverworks.co.jp/tech/2015/06/11/howto-aws-cli-2/)を参考にどうぞ。今回紹介するスクリプトはいずれもプロファイルの指定が必要ですのでご注意を。

### やりたいこと
- ALBとそこに紐付いたターゲットグループ、そしてその下のインスタンスのステータスをずらっと表示させたい
    - インスタンスIDだけじゃなくてインスタンスName Tagを表示させたい
- インスタンス名を指定してALBターゲットグループへのAttach, Detachをしたい
    - ターゲットグループのARN取得するのがめんどくさいので、ターゲットグループ名だけを指定して済ませたい

## ALBのステータス表示
``` bash:alb_show_health.sh
#/bin/bash
#
# ./alb_show_health.sh Profile ALBName
# 
alb_show_health() {
  local profile=$1;
  local alb=$2

# ALB名からALBのARNを取得
  local alb_arn=`aws elbv2 describe-load-balancers \
    --profile $profile \
    --query 'LoadBalancers[].[LoadBalancerName,LoadBalancerArn]' \
    --output text | grep -E "^${alb}[[:space:]].*" | cut -f2` || return $?

  if [ -z $alb_arn ] ; then
    echo "ALB \"$alb\" is not exist."
    exit 1
  fi

# ALB ARNからTarget Groupを取得
  local target_groups=`aws elbv2 describe-listeners \
    --profile $profile \
    --load-balancer-arn $alb_arn \
    --query 'Listeners[].DefaultActions[].TargetGroupArn' \
    --output text` || return $?

# Target Groupsに紐付いたホストの状態を一覧表示
  for target_group_arn in ${target_groups[@]}
  do
    local target_group_name=`sed 's%^.*:.*/\(.*\)\.*/.*%\1%' <<< $target_group_arn`
    echo "--- Health Status of Target Group \"$target_group_name\" ---"
#次のループのため空白をIFSにしないよう一時的に変更
    IFS_BACKUP=$IFS
    IFS=$'\n'
# 可視性のため、EC2のNameタグを取得するループ
    for instance in `aws elbv2 describe-target-health \
      --target-group-arn $target_group_arn \
      --profile $profile \
      --query 'TargetHealthDescriptions[].[Target.Id,TargetHealth.State]' \
      --output text`
      do
# instance=インスタンスID\tステータス
        local instance_id=`echo $instance | cut -f1`
        local instance_state=`echo $instance | cut -f2`
        local instance_name=`aws ec2 describe-tags \
          --profile $profile \
          --filters "Name=resource-id,Values=$instance_id" "Name=tag-key,Values=Name" \
          --query "Tags[].Value" \
          --output text`
        echo "$instance_name $instance_id $instance_state"
      done
    IFS=$IFS_BACKUP
  done
}

# メイン実行部
if [ $# -ne 2 ] ; then
  echo "Missing Argument"
  echo 'Usage: ./alb_show_health Profile ALBName'
  exit 1
fi

alb_show_health $@
``` 
### 実行結果例
```shell-session
--- Health Status of Target Group "admin" ---
admin-001 i-0123456789abcdef0 healthy
admin-002 i-123456789abcdef01 healthy
--- Health Status of Target Group "web" ---
web-001 i-23456789abcdef012 healthy
web-002 i-3456789abcdef0123 healthy
web-003 i-456789abcdef01234 draining
```
ALBにくっついたターゲットグループごとに、紐付いたインスタンスの情報をだらだらっと出力してくれます。
※めっちゃAPIコールしまくってるんでうまい方法があったら知りたい

上記スクリプトでインスタンス名とインスタンスIDが表示されるので、以下のスクリプトでよしなにALBから追加したり削除してあげることができます。

## ALBターゲットグループにカジュアルにインスタンスを追加する
```bash:alb_register_instance.sh
#/bin/bash
#
# ./alb_register_instance.sh Profile TargetGroupName InstanceName
#
alb_register_instance() {
  local profile=$1;
  local target_group_name=$2
  local target_instance_name=$3

  local target_group_arn=`aws elbv2 describe-target-groups \
    --profile $profile \
    --query 'TargetGroups[].[TargetGroupName,TargetGroupArn]' \
    --output text | grep -E "^$target_group_name[[:space:]]" | cut -f2`

  if [ -z $target_group_arn ] ; then
    echo "Target Group \"$target_group_name\" is not found"
    exit 1
  fi

# インスタンス名の重複対策でID指定を許可
  if [[ "$target_instance_name" =~ ^i-[0-9a-f]{17}$ ]]; then
    target_instance_id=$target_instance_name
  else
    local target_instance_id=`aws ec2 describe-instances \
      --profile $profile \
      --filters "Name=tag-key,Values=Name" "Name=tag-value,Values=$target_instance_name" \
      --query 'Reservations[].Instances[].InstanceId' \
      --output text`
  fi

  if [ -z $target_instance_id ] ; then
    echo "Instance \"$target_instance_name\" is not found"
    exit 1
  fi

# register
  aws elbv2 register-targets \
    --profile $profile \
    --target-group-arn $target_group_arn \
    --targets Id=$target_instance_id

  return $?
}

# メイン実行部
if [ $# -ne 3 ] ; then
  echo "Missing Argument"
  echo 'Usage: ./alb_register_instance.sh Profile TargetGroup InstanceName'
  exit 1
fi

alb_register_instance $@
```
## ALBターゲットグループからカジュアルにインスタンスを削除する
```bash:alb_deregister_instance.sh
#/bin/bash
#
# ./alb_deregister_instance.sh Profile TargetGroupName InstanceName
#
alb_deregister_instance() {
  local profile=$1;
  local target_group_name=$2
  local target_instance_name=$3

  local target_group_arn=`aws elbv2 describe-target-groups \
    --profile $profile \
    --query 'TargetGroups[].[TargetGroupName,TargetGroupArn]' \
    --output text | grep -E "^$target_group_name[[:space:]]" | cut -f2`

  if [ -z $target_group_arn ] ; then
    echo "Target Group \"$target_group_name\" is not found"
    exit 1
  fi

# インスタンス名の重複対策でID指定を許可
  if [[ "$target_instance_name" =~ ^i-[0-9a-f]{17}$ ]]; then
    target_instance_id=$target_instance_name
  else
    local target_instance_id=`aws ec2 describe-instances \
      --profile $profile \
      --filters "Name=tag-key,Values=Name" "Name=tag-value,Values=$target_instance_name" \
      --query 'Reservations[].Instances[].InstanceId' \
      --output text`
  fi

  if [ -z $target_instance_id ] ; then
    echo "Instance \"$target_instance_name\" is not found"
    exit 1
  fi

# deregister
  aws elbv2 deregister-targets \
    --profile $profile \
    --target-group-arn $target_group_arn \
    --targets Id=$target_instance_id

  return $?
}

# メイン実行部
if [ $# -ne 3 ] ; then
  echo "Missing Argument"
  echo 'Usage: ./alb_deregister_instance.sh Profile TargetGroup InstanceName'
  exit 1
fi

alb_deregister_instance $@
``` 
# 〆

以上。
どちらかといえば実用的な方面に寄せ、Advent Calendarに載せるにしては非常に地味な記事となってしまってごめんなさい感溢れてますが、明日はきっと[okochang氏](http://qiita.com/okochang)がかっ飛ばしてくれるのでどうぞご期待ください！