ELB(CLB)へのEC2インスタンスのアタッチ/デタッチをAnsibleで手軽に


#はじめに
ELB配下のサーバをメンテナンス時にデタッチしてまたアタッチするという運用をされている方は多いと思います。

ELBへのデタッチ/アタッチ操作をメンテナンスのたびに行うこと自体、モダンではないことはわかっていますが、そんな温かみのある運用をすぐに捨てられない以上、操作をAnsbleで自動化することにより、手作業やコマンドのコピペミスによる事故を防ぐ効果はあると思います。

SSL証明書の関係で1つのEC2インスタンスが複数のELBにアタッチされている場合などは特に威力を発揮するかと。
今回はとりあえずCLBです。

#概要
- ELB(CLB)に定義したEC2インスタンスをアタッチ
- 定義したEC2インスタンスと実際にアタッチされているEC2インスタンスの差分を取得
- 上記差分EC2インスタンスをELB(CLB)からデタッチ
- 結果を出力

##ポイント
- varsにはELBにアタッチしたいEC2インスタンスIDを定義しておく
  - デタッチの際は一時的にコメントアウトする
- AnsibleによるEC2インスタンスのアタッチは冪等性が担保されるため、実行することで定義した通りになる
- 定義と実際のELBにアタッチされたEC2インスタンスの差分＝デタッチすべきEC2インスタンスとなる
	
#注意
EC2インスタンスが１つも定義されていない場合は、アタッチ処理でエラーとなります。
エラーハンドリングを実装していないためですが、実際の運用では事故になるため、あえて実装していません。

#前提
- AWS関連のモジュール実行にはbotoが必要です。
- Ansible環境はvirtualenvで用意することとします。
- credential情報は環境変数でセットしてある必要があります。

#sample

##ディレクトリ構成

```bash:ディレクトリ構成
site.yml
inventories/
|--aws
roles/
|--elb/
|  |--tasks/
|  |  |--main.yml
group_vars/
|--group.yml
```

##inventory
インベントリにはターゲットホストを記載しますが、AWSモジュールはlocalhostで実行されるため全て`ansible_host=localhost`となります。
グループ、ホスト名は実行時に読み込まれるgroup_vars,host_vars(今回は未使用)を切り替えるために利用します。
varsに記載したELB全てに対して処理を実行するため、作業単位にグループを分けるといいでしょう。
`ansible_python_interpreter=python`は、virtualenv環境で`pip install`した`boto`モジュールを読み込むために指定しています。

```yaml:inventories/aws
[all:vars]
ansible_connection=local
ansible_python_interpreter=python
ansible_host=localhost

[elb_production]
production

[elb_staging]
staging
```

##vars

こんな感じに変数を定義します。

```yaml:group_vars/elb_production.yml
---
my_vars:
  aws:
    common:
      region: ap-northeast-1
    elb:
      - name: production_elb01
        instances:
          - i-XXXXXXXXXXXXXXXXX #server01
          - i-XXXXXXXXXXXXXXXXX #server02
      - name: production_elb02
        instances:
          - i-XXXXXXXXXXXXXXXXX #server01
          - i-XXXXXXXXXXXXXXXXX #server02
```

##Role
- まず、varsに定義されたEC2インスタンスをそれぞれのELBにアタッチします。すでにアタッチ済みのものはAnsibleによりスキップされます。
- 次に、`ec2_elb_facts`モジュールにより、ELBにアタッチされているEC2インスタンス情報を取得します。
- 取得したEC2インスタンス情報と、varsに定義されたEC2インスタンスとの差分を`difference`フィルタで取得し、`elb_diff_list`を作成します。これらは、ELBにアタッチされているが、varsには定義されていないEC2インスタンスなのでデタッチ対象となります。
- `elb_diff_list`に基づいてデタッチを実行します。
- 再度`ec2_elb_facts`モジュールにより、ELBにアタッチされているEC2インスタンス情報を取得します。
- `debug`モジュールで結果を出力します。

```yaml:roles/elb/tasks/main.yml
---
- name: ELBにインスタンスをアタッチ
  ec2_elb:
    instance_id: "{{ item.1 }}"
    ec2_elbs: "{{ item.0.name }}"
    state: present
  with_subelements:
    - "{{ my_vars.aws.elb }}"
    - instances

- name: Gathering ELB facts
  ec2_elb_facts:
    region: "{{ my_vars.aws.common.region }}"
    names: "{{ item.name }}"
  register: elb_facts
  with_items: "{{ my_vars.aws.elb }}"

- name: ELBアタッチ済みインスタンスとvarsの差分からデタッチ対象インスタンスlist作成
  set_fact:
    elb_diff_list: >-
      {%- set tmplist = [] -%}
      {%- for i in range(elb_facts.results|length) -%}
      {%-   set diffelb = elb_facts.results[i].elbs[0].instances | difference(my_vars.aws.elb[i].instances) -%}
      {%-   set tmpelb = {"name": elb_facts.results[i].elbs[0].name, "instances": diffelb } -%}
      {%-   set _ = tmplist.append(tmpelb) -%}
      {%- endfor -%}
      {{ tmplist }}

- debug: var=elb_diff_list

- name: ELBからインスタンスをデタッチ
  ec2_elb:
    instance_id: "{{ item.1 }}"
    ec2_elbs: "{{ item.0.name }}"
    state: absent
  with_subelements:
    - "{{ elb_diff_list }}"
    - instances

- name: Gathering ELB facts
  ec2_elb_facts:
    region: "{{ my_vars.aws.common.region }}"
    names: "{{ item.name }}"
  register: elb_facts_result
  with_items: "{{ my_vars.aws.elb }}"

- name: 結果出力
  debug:
    msg: "Result: {{ item.elbs[0].name }}: {{ item.elbs[0].instances }}"
  with_items: "{{ elb_facts_result.results }}"
```

## site.yml
```yaml:site.yml
---
- name: elb
  hosts: elb-production
  connection: local
  roles:
    - role: elb
  tags:
    - elb
```

##実行
```bash:実行
$ ansible-playbook -i inventories/aws -l elb_production site.yml
```

##実際の作業イメージ
###server01を2台のELBからデタッチする
デタッチ対象のインスタンスをvars上でコメントアウトする

```yaml:group_vars/elb_production.yml
---
my_vars:
  aws:
    common:
      region: ap-northeast-1
    elb:
      - name: production_elb01
        instances:
#          - i-XXXXXXXXXXXXXXXXX #server01 デタッチするインスタンスをコメントアウト
          - i-XXXXXXXXXXXXXXXXX #server02
      - name: production_elb02
        instances:
#          - i-XXXXXXXXXXXXXXXXX #server01 デタッチするインスタンスをコメントアウト
          - i-XXXXXXXXXXXXXXXXX #server02
```
###　server01デタッチ実行
```bash:server01デタッチ実行
$ ansible-playbook -i inventories/aws -l elb_production site.yml
```
###　server01をメンテナンス
再起動など

###メンテナンス完了したserver01を2台のELBに再びアタッチする

メンテナンス完了したインスタンスをvars上でコメントインする

```yaml:group_vars/elb_production.yml
---
my_vars:
  aws:
    common:
      region: ap-northeast-1
    elb:
      - name: production_elb01
        instances:
          - i-XXXXXXXXXXXXXXXXX #server01 アタッチするインスタンスをコメントイン
          - i-XXXXXXXXXXXXXXXXX #server02
      - name: production_elb02
        instances:
          - i-XXXXXXXXXXXXXXXXX #server01 アタッチするインスタンスをコメントイン
          - i-XXXXXXXXXXXXXXXXX #server02
```
###　server01アタッチ実行
```bash:server01アタッチ実行
$ ansible-playbook -i inventories/aws -l elb_production site.yml
```

### server02についても同様に

#まとめ
いかがでしょうか。あくまでvars上でアタッチ/デタッチ対象を制御するため、シェルスクリプトに引数で対象を渡すより安全に実行できるのではないでしょうか。
普段からAnsibleを利用している環境なら、このようなアドホックな操作をAnsible化するのもありかと思います。
ALB版もそのうち書きたいと思います（ALB移行しなきゃ……）。

#参考
##AnsibleでAWSリソースを管理するシリーズ
- [VPCをAnsibleで作成する](http://qiita.com/isobecky74/items/c245bedfa6966305eb10)
- [IAMユーザをAnsibleで管理する](http://qiita.com/isobecky74/items/5ae6f15f52a2d20e2635)
- [AWSのセキュリティグループをAnsibleで管理する](http://qiita.com/isobecky74/items/7342b8ba874d997c5c6c)
- [EC2をAnsibleで管理する](http://qiita.com/isobecky74/items/163b23d5a0e566aaa159)
- [ec2_group_factsモジュールをAnsible2.2で使えるようにする](http://qiita.com/isobecky74/items/8684b4dc8f99793e5cf8)
- [RDSをAnsibleで管理する](http://qiita.com/isobecky74/items/7ce42217e177f0c6b7ec)
- [AnsibleでRoute53を管理する](http://qiita.com/isobecky74/items/85c5513314a64f2392ba)
- [Ansibleの実行対象AWSアカウントを事前にチェックして事故防止](http://qiita.com/isobecky74/items/f24fbd86eb64a0e9bd4a)
- [ACMを利用した独自取得SSL証明書更新をAnsibleでやってみる](http://qiita.com/isobecky74/items/893e9448b7fd0dacc11e)
- [AnsibleでRoute53のレコード情報をいい感じにエクスポートする(全件対応版)](http://qiita.com/isobecky74/items/cac24af47c85975bb38b)
- [IAMポリシーをAnsibleで管理する](https://qiita.com/isobecky74/items/82b891ae0b093f384d68)