自動Blue-Greenデプロイをansibleで構築してみた

# 自己紹介
一年目のインフラエンジニア
クラウドなんだからBlue-Greenデプロイをやりたいと言われ、構築して運用してみた。

# Blue-Greenデプロイの構築方法
## Blue-Greenデプロイの概要
AWSで環境を構築するにあたり以下の図のように設計してみました。
この図の通り、ALBにてルーティングしているターゲットグループに紐づいているEC2を切り替えることで、Blue Greenデプロイメントを行っています。
AWSの[ホワイトペーパー](https://d1.awsstatic.com/whitepapers/AWS_Blue_Green_Deployments.pdf)(p. 32)にもあったのですが、DNSのルーティングを行う方法に比べ、Rollbackにおけるリスクを減らすことができます。

<img src=https://qiita-image-store.s3.amazonaws.com/0/153660/459c3301-a375-7eec-1340-e18b2bb4e825.png width=70%>

## デプロイのために作成したplaybook
実際にansibleでplaybookを組んでみました。
なんかrolesがたくさんあるのは、ひとつひとつのrolesをシンプルにしたかったからです。
なぜなら、rolesが複雑になるとあとで管理が面倒になる、と先輩に言われたからだったり、、、
（今はとても実感しています。本当に分けた方がいい。。。）

```yaml

---
- hosts: all
  connection: local
  gather_facts: false
  user: ec2-user
  roles:
    - ec2

- hosts: webserver
- roles:
    - { role: rbenv, become: yes, tags: rbenv}
    - { role: nginx, become: yes, tags: nginx}
    - { role: env, become: yes, tags: env}
    - { role: deploy, become: yes, tags: deploy}
    - { role: config-db, become: yes, tags: 'config-db'}
    - { role: bundle, become: yes, tags: 'bundle'}
    - { role: unicorn, become: yes, tags: 'unicorn'}
    - { role: release-test, become: yes, tags: 'release-test'}
    - { role: migrate-db, tags: migrate-db, when: migrate_db_cli is defined}
    - { role: register-tg, become: yes, tags: 'register-tg', when: project_env != "prd"}
```

## playbookの解説
- EC2立ち上げ

まずはEC2を立ち上げるrolesを作成、ここは後々他のデプロイ機能でも活用できるように切り出してみました。セキュリティ的な意味で、userはrootではなく別にユーザーを作成。

```yaml

provision.yml
- hosts: all
  connection: local
  gather_facts: false
  user: ec2-user
  roles:
    - ec2
```
※EC2を立ち上げてRoute53に登録、SSH接続の確認まで行うrolesを例として載せておきます。変数は別のplaybookで指定しています。（気になるところあればコメントください。）

```yaml

roles/ec2/tasks/main.yml
- name: Provision EC2 Box
  local_action:
    module: ec2
    key_name: "{{ ec2_keypair }}"
    group_id: "{{ ec2_security_group }}"
    instance_type: "{{ ec2_instance_type }}"
    image: "{{ ec2_image }}"
    vpc_subnet_id: "{{ ec2_subnet_ids[(inventory_hostname_short[-1:] | int % 2 )] }}"
    region: "{{ ec2_region }}"
    assign_public_ip: no
    instance_profile_name: "{{ ec2_iam_role | default }}"
    wait: true
    user_data: "{{ lookup('template', 'roles/ec2/templates/user_data_hostname.yml.j2') }}"（※hostnameを動的に付けたかったのでtemplateを作成しusr_dataを使用）
    exact_count: 1
    count_tag:
      Name: "{{ inventory_hostname }}"
    instance_tags:
      Name: "{{ inventory_hostname }}"
      Environment: "{{ ec2_tag_Environment }}"
      Service: "{{ service_name }}"
    volumes:
    - device_name: /dev/xvda
      device_type: gp2
      volume_size: "{{ ec2_volume_size }}"
      delete_on_termination: true
  register: ec2

- debug: var=item
  with_items: '{{ ec2.instances }}'
  when: not ansible_check_mode

- name: Setup for DNS in AWS Route 53
  route53:
    aws_access_key:
    aws_secret_key:
    command: create
    private_zone: True
    zone: bdash.inside
    record: '{{ inventory_hostname }}.bdash.inside'
    type: A
    ttl: 60
    value: '{{ item.private_ip }}'
    overwrite: yes
  with_items: '{{ ec2.instances }}'
  when: not ansible_check_mode

- name: Wait for the instances to boot by checking the ssh port
  wait_for:
    host: '{{ item.private_ip }}'
    port: 22
    delay: 60
    timeout: 320
    state: started
  with_items: '{{ ec2.instances }}'
  when: not ansible_check_mode
```

- ミドルウェアからデプロイまで

webアプリはrailsで組まれているので、よくある組み合わせのnginx、unicornで構築しました。
DBはインフラで管理するので、database.ymlを作成して配置するrolesを別途作成しています。
あとは、デプロイしただけでmigrationまで行ってあげる親切設計。

ちなみにrelease-testのrolesは疎通確認、ヘルスチェックを行うrolesです。
もしうまくデプロイからnginx,unicornの起動までできなかった場合はansibleが落ちます。
これでデプロイしたけど、見てみたら動いていないなんてことはない。

```yaml

- hosts: webserver
- roles:
    - { role: rbenv, become: yes, tags: rbenv}
    - { role: nginx, become: yes, tags: nginx}
    - { role: env, become: yes, tags: env}
    - { role: deploy, become: yes, tags: deploy}
    - { role: config-db, become: yes, tags: 'config-db'}
    - { role: bundle, become: yes, tags: 'bundle'}
    - { role: unicorn, become: yes, tags: 'unicorn'}
    - { role: release-test, become: yes, tags: 'release-test'}
    - { role: migrate-db, tags: migrate-db, when: migrate_db_cli is defined}
```
- デプロイ後

デプロイ後にターゲットグループの切り替えを行います。
ここでBlue-Greenデプロイを実現しています。ターゲットグループに登録して、削除するまで行いました。

ただ、このrolesは本番だけは行わず、手動で切り替えています。なぜなら、削除を行ってしまうと問題が起きた時にロールバックしにくくなる、と言うのが一番の理由です。うまくやればできるとは思うので、残論点です。


開発環境はそんなの関係ないので、利便性を取っています。

```yaml
    - { role: register-tg, become: yes, tags: 'register-tg', when: project_env != "prduction"}
```

# いざ、運用！！
ここまでで無事、Blue-Greenデプロイが実現できました。
当初の予定通り、ターゲットグループが自動で切り替わり、外部からの接続も確認できました。
そして、ansibleで構成管理をしているためサーバーを作っては壊すこともできます。
つまり、Immutable Infrastructureも実現できており、データを作っては壊す開発環境にはもってこいなものができました。

追加で実装に組み込んだテストrolesによって、「ansibleが通ったけれどサービスが動いていない！」なんてぬか喜びもなくすことができました。

まさに、いいことずくめ！！
自動デプロイにひとつ近づいた！

と思っていたのですが、ここで更なる問題が、、、

#### <b>切り替え後にterminate処理をいれたら、アプリのdaemonなどのプロセスが強制KILLされる。</b>

たとえばアプリ側でデータ出力や取込に数時間かかるという話はよくある話です。
そんな時にターゲットグループ切り替えだけならいざしらず、
サーバーを落としてしまったのならプロセスが強制的にKILLされ、結果処理停止となります。

自動デプロイを実装した結果、アプリ側に影響を出してしまいました。
この辺りはBlue-Greenデプロイを行う上で考慮すべき項目でしたね。

このことからプロセス監視を行うことが、今後の課題かなと思っています。
AWSにはスポットインスタンスもあるので、terminateしても動き続けられるようアプリ側にサルベージできる機能か何かも欲しいですね。

#### <b>マイクロサービスだったことを忘れていた
運用に載せてからしばらくたつと、サービスも増え立てるサーバーも増えてきます。
つまり、、、デプロイの回数が単純に増えていきます。
更に自動化を進めないと、工数が増加の一途をたどることに・・・

# 最後に
自動デプロイ、Blue-Greenデプロイをansibleで実装してみました。
「*サービスのリリーススピードはデプロイの手間に依存する*」が私の持論です。
なので、デプロイ作業をいかに自動化しつつ安定化させるかが肝だと思っています。

まだまだ改善できるところは多いので、今週末もansibleと一緒に過ごします～