自社クラウドサービスをAnsibleで作った話 recap

# 概要

　先日の[Ansible Night in Tokyo 2019.04](https://ansible-users.connpass.com/event/125609/)にて掲題の内容で発表したのですが、内容の補足を残したいと思います
　下記以外で気になる点や質問があればお気軽にどうぞ！
　
　※発表資料は[こちら](https://www.slideshare.net/comefigo/ansible-141003680)です

　当記事をご覧頂いた方でAnsibleを初めてみようと思ったならば、「[これからAnsibleを勉強する際におさえておきたいこと](https://qiita.com/comefigo/items/a37b47f394f0a9a3e518)」も合わせてどうぞ！

# Salesforceで何を管理しているのか？

　顧客が契約しているサービスプランに応じて、環境が異なるため、そういった情報（インスタンスのスペックやディスク容量など）がSalesforceで管理されています
例えば、トライアル契約、本契約の2つあるのですが、その契約によってデプロイされる環境が異なります
つまり、契約と環境の情報を管理し、契約確定した時点でAWXに環境の作成や更新するような仕組みになっています
その間にシステム担当者は一切関与せずに契約に基づいて環境の変更が行われるので、リードタイムの短縮が図れます

　B2Bのサービスといえどもリードタイムの短縮が問われる（コストダウンの側面もある）ので、営業システムと直結できるようなシステム構成を取ることでビジネススピードが飛躍的に向上すると思います。そういった意味ではSalesforceは強力なプラットフォームだと言えます

## Salesforceのテスト自動化
　Salesforceではカスタムコード（Apex）のテストカバレッジが75%以上でなければ本番環境にデプロイできない仕組みになっています。なので、前述のようにSalesforce（ビジネスロジック）とAnsible（環境構成）で明確に役割を分離させているとシステムのバージョンアップもスムーズになります。つまり、ビジネスロジックのテストは自動化されていて、そのテスト内でREST API呼ぶためのパラメータを確認すればよい
　これもSalesforceの強みだと言えます

# Ansibleを選んだ理由

## 人件費が高い
　イニシャルコストとしてシステム開発費用は発生するが、ランニングコストを含めたトータルコストで見ると自動化しておくことでコスト削減やシステムの安全運用が望める

## 可用性が高い
　Ansibleの特徴でもありますが、Linuxに限らず、Windows、ネットワーク機器、VMware、各種クラウドサービス、APIがあるWebサービスなどに適用できるため、普段の業務効率化につなげることも可能。それらが共通言語としてAnsibleでコード化できるので、管理しやすく、人材育成もしやすい
　現に自社クラウドサービスもSalesforce内の顧客管理部分以外はすべて（Zabbix、AWS、アンチウィルス、Slack）Ansibleで構成管理されている

## 命名規則

補足というよりも反応がよかったので、改めて書きます

### 動的変数名

各タスク内で動的に生成している変数名(`set_fact`、`register`)は`アンダーバー`で始める
静的変数（host_vars、group_vars、vars）との違いを明確にするため
プログラミングでは、関数内の変数なのか、それとも外部の変数なのかをぱっと見区別できるようにするための手法です
意図せず同じ名前つけて変数を上書きしない効果もあります

```yaml:set_fact.yml

# 例1
- set_fact:
    _hoge: "fuga"

# 例2
- command: "env"
  register: _env_result
```

### ファイル名

playbook、role、taskのファイル名は種別（名詞）から始めること
ソースツリーで同じ種別のファイルがまとまって見えるので、可読性が高まる

```txt
# NGパターン
|- create_ec2_instance.yml
|- create_ecr_image.yml
|- delete_ec2_instance.yml
|- delete_ecr_image.yml

# OKパターン
# 同じ種別のファイルがまとまって見えて管理がしやすくなる
|- ec2_instance_create.yml
|- ec2_instance_delete.yml
|- ecr_image_create.yml
|- ecr_image_delete.yml

```

### 自作モジュール名

自作モジュールかどうかをわかりやすくするために、モジュール名はシステム名の略語で開始する

```yaml:hoge_module.yml
# hogeシステムの場合
- name: Create Hoge EC2 Instance
  hoge_ec2_instance:
```

### 変数ファイル

変数ファイルはコメントが記述できるyaml形式を採用
jsonだとコメントを記載できない

### 変数はdict型

一部のサンプルコードで以下のNGパターンのように変数の意味を示すために変数名が長くなり、保守性が低下します
OKパターンのように変数が階層ごとに表現されるので、ぱっと見わかりやすくなります
※好き嫌いはあると思いますので、以下のNGパターンは私のチームでの決め事になります

```yaml:vars.yml

# NG
aws_ec2_tags: my_ec2
aws_ebs_tags: my_ebs

# OK
aws:
  ebs:
    tags: my_ebs
  ec2:
    tags: my_ec2
```

# 苦労したこと

補足というよりも、わかりづらかったので、改めて説明します

## エラーハンドリング

プログラミングでいうtry/catchは`block/resue`で実現できるが、適用範囲はtasks内のみである
下記のようなplaybook内で複数のimport_playbookを包括にblock/resueはない

```yaml:import_playbook.yml

# blck/resueで一括でエラーハンドリングできない

- name: Create APP Instance
  import_playbook: instance_app_create.yml

- name: Create DB Instance
  import_playbook: instance_db_create.yml
```

# 工夫している点

## ビジネスロジックはAnsibleに持ち込まない

前述の契約に応じて環境が作成、更新されるが、ビジネスロジックはAnsibleに持ち込まないようにしています
ビジネスロジックが複数個所に散在すると保守性が低下しますので、
ビジネスロジックはすべてSalesforce上で処理し、その結果としてAnsibleに渡す変数のみをつくるようにしています

## AMIに最新のコンテナイメージを含める

APPとDBはコンテナで動作していますが、最新のコンテナイメージをAMIに含めることで、初回デプロイ時にレジストリからプルすることがなくなりデプロイ時間の短縮が図れます。コンテナイメージがギガ単位になるとプルする時間もそれなりにかかるようになるので、この辺を見直すだけでも時間の短縮になると思います

## Docker in Docker

「Ansibleの実行環境がコンテナ」＋「DockerfileがJinjaテンプレート化」になっている場合は、DockerイメージをビルドするためにDocker in Dockerでビルドしている。つまりAnsible（Docker）内でDockerをビルドする

1. Dockerホストのdocker.sockをAnsibleコンテナにマウントする（ReadOnlyモード）
2. Jinjaテンプレート化されたDockerfileはAnsible内の適当なディレクトリに展開する  
  Ansibleコンテナにマウントしたホストのディレクトリに展開してもよい
3. AnsibleコンテナのDockerクライアントからdockerイメージをビルドする  
  Ansibleイメージにはdockerをインストールしましょう

![docker_in_docker.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/30522/6b90d2e3-38ee-0d95-4733-b8ad61ab1d0b.png)

ちなみにAnsible Containerは使用せずに`docker_container`モジュールでコンテナを操作している


## ドライバ変数

> スタブ（stub）とは、コンピュータプログラムのモジュールをテストする際、そのモジュールが呼び出す下位モジュールの代わりに用いる代用品のこと。下位モジュールが未完成でも代わりにスタブを用いることでテストが可能になる。逆に上位モジュールの代わりに用いる代用品をドライバ（ソフトウェアの場合）
※wiki引用

かみ砕くと上位モジュールから渡ってくる予定の変数をテスト用の変数で代用したもの
本番用のシステム構成はSalesforceからAWXにリクエストするが、開発段階ではそのような構成を取らずにローカルで開発しやすくするためにドライバを用いて開発している

各Playbookの最初に必ず以下のタスクを実行している
`hostvars[inventory_hostname]['awx_job_template_name']`でAWXかどうかを判定している
AWX経由だとhostvarsに`awx_xxx`の変数が含まれるので、それらをAWX判定に使用している

AWXではない場合（ローカル開発）は、var.yml（ドライバ変数）を読み込む
AWXの場合は、Salesforceから渡された外部変数を用いる

```yaml:import_extra_vars
- include_vars:
    file: ../vars/extra/var.yml
  when: hostvars[inventory_hostname]['awx_job_template_name'] is not defined
```

## 自作モジュール

自作モジュールの開発言語を`シェルスクリプト`か`Python`に限定している
好き勝手な言語にすると保守ができなくなる