Ansible ( 俺の中で ) 最強の Best Practices

## 前書き

サービスごとに複数のサーバを ansible で管理していく中で、同じような内容で共用したい role が沢山出てきました。それぞれサービス毎に管理していくのは非常に面倒…。困っていたところを下記の記事が救ってくれました。[yteraokaさん](http://qiita.com/yteraoka "yteraoka - Qiita")には本当に感謝しています。

[Ansible オレオレベストプラクティス - Qiita](http://qiita.com/yteraoka/items/5ed2bddefff32e1b9faf "Ansible オレオレベストプラクティス - Qiita")

しかし、group_vars 内のファイルや hosts ファイル等が増えていくにつれ、どのファイルがどんな内容かがディレクトリ構成やファイル名を見ただけでは分かりづらいという点や、環境 ( ステージ ) ごとの管理がしづらいという点などの問題が出てきました。  
そこで、「Ansible オレオレベストプラクティス」を参考にし、更にオレオレにして「 ( 俺の中で ) 最強のベストプラクティス」に辿り着きました。  

本記事は、あくまで **自分の中で最強** ということなので暖かい目で見ていただきたいです。
現在のままでも十分にAnsibleの管理ができている方や、最強でもなんでもないと思う方はスルーしてください。

また、こうすべきやこうした方がいいなどあればコメント下さい。

※ 追記 ( 2016/12/15 )

共用したいロールの管理方法として、 _common-roles_ ではなく Ansible Galaxy を利用する方法を投稿しました。こちらの方がよりスマートかと思います。

[You Don't Need common-roles in Ansible - Qiita](http://qiita.com/kotarella1110/items/212dceee3e56fbffddd1 "You Don't Need common-roles in Ansible - Qiita")


## 筆者が辿り着いたベストプラクティス

```tree
.
├── common_playbook/               # サービス全ての共用の playbook
│   ├── vars/                      # サービス全ての共用の変数
│   │   └── common.yml
│   └── roles/                     # サービス全ての共用のroles
│       ├── common/
│       │   ├── tasks/
│       │   │   └── main.yml
│       │   ├── handlers/
│       │   │   └── main.yml
│       │   ├── templates/
│       │   │   ├── ...
│       │   │   └── ...
│       │   ├── files/
│       │   │   ├── ...
│       │   │   └── ...
│       │   └── defaults/
│       │      └── main.yml
│       ├── role1/
│       │   :
│       ├── role2/
│       │   :
│       └── ...
├── service1/
│   ├── Vagrantfile
│   └── playbook
│       ├── hosts/
│       │   ├── development        # デベロップメント環境用サーバのインベントリファイル
│       │   ├── local              # ローカル環境用サーバ ( vagrant ) のインベントリファイル
│       │   ├── production         # プロダクション環境サーバ用のインベントリファイル
│       │   ├── staging            # ステージング環境サーバ用のインベントリファイル
│       │   └── test               # テスト環境サーバ用のインベントリファイル
│       ├── group_vars/
│       │   ├── all.yml             # service1 に所属するグループ全てへの変数割り当て
│       │   ├── development/       # development に所属するグループをまとめたディレクトリ
│       │   │   ├── all.yml        # development に所属するグループ全てへの変数割り当て
│       │   │   ├── group1.yml     # development に所属する group1 のみへの変数割り当て
│       │   │   ├── group2.yml     # ""
│       │   │   └── ...
│       │   ├── local/             # local に所属するグループをまとめたディレクトリ
│       │   │   :
│       │   ├── production/        # production に所属するグループをまとめたディレクトリ
│       │   │   :
│       │   ├── staging/           # staging に所属するグループをまとめたディレクトリ
│       │   │   :
│       │   └── test/              # test に所属するグループをまとめたディレクトリ
│       ├── host_vars/
│       │  ├── hostname1.yml       # システムの特定の変数が必要な場合
│       │  ├── hostname2.yml
│       │  └── ...
│       ├── site.yml               # マスター playbook
│       ├── webservers.yml         # webserver 層の playbook
│       ├── dbservers.yml          # dbserver 層の playbook
│       ├── ...
│       └── roles/                 # service1 の roles
│           ├── common/
│           │   ├── tasks/
│           │   │   └── main.yml
│           │   ├── handlers/
│           │   │   └── main.yml
│           │   ├── templates/
│           │   │   ├── ...
│           │   │   └── ...
│           │   ├── files/
│           │   │   ├── ...
│           │   │   └── ...
│           │   ├── vars/
│           │   │   └── main.yml
│           │   ├── defaults/
│           │   │   └── main.yml
│           │   └── meta/
│           ├── role1/
│           │   :
│           ├── role2/
│           │   :
│           └── ...
├── service2/
│   :
└── ...
```

※ 自分の使い方に合うように、自由にディレクトリやファイルの変更や整理してください。

## [yteraokaさん](http://qiita.com/yteraoka "yteraoka - Qiita")のベストプラクティス

[Ansible オレオレベストプラクティス - Qiita](http://qiita.com/yteraoka/items/5ed2bddefff32e1b9faf "Ansible オレオレベストプラクティス - Qiita")

## [yteraokaさん](http://qiita.com/yteraoka "yteraoka - Qiita")のベストプラクティスからの変更点

ディレクトリ構成を見れば大体分かると思いますが、変更点を以下にまとめます。
大きく分けると **二つ** のみです！やったことは本当にシンプルです。  
これしか変更してないのに最強ってなんだよって話ですが…。

### ディレクトリ構成を ( 自分的に ) 分かりやすくした

* common_roles, private_vars, varsをひとまとめにした
* 環境ごとのhostsファイルをhosts/ という名でひとまとめにした
* service ごとに Vagrantfile を追加し、playbook を playbook/ という名でひとまとめにした

### 環境ごとの切り替えや変数の管理を楽にした

* group_vars以下に環境ごとのグループ名でディレクトリを用意した


## vars ファイルの変数が割り当てられる範囲

| ファイル                                                    | 割り当てられる範囲                         |
|:-----------------------------------------------------------|:----------------------------------------|
| common_playbook/vars/common.yml                            | サービス全て                              |
| \<service>/playbook/group_vars/all.yml                     | サービスごとに所属するグループ全て           |
| \<service>/playbook/group_vars/\<environment>/all.yml      | 環境ごとに所属するグループ全て               |
| \<service>/playbook/group_vars/\<environment>/\<group>.yml | 環境ごとに所属するグループのみ               |

## 筆者のベストプラクティス構成の設定例
       
会社やサービスごとによって 環境 ( development | local | production | staging | test ) は異なると思いますので、hosts ファイルや group_vars 内のディレクトリは必要に応じて削除してください。

これから local と production 二つの環境で、ディレクトリ構成とファイルの設定例を説明していきます。

### playbook

#### 構成

以下のようにグループごとに playbook を分けます。
今回の例では webservers と dbservers 二つに分けました。

```tree
...
│       ├── site.yml
│       ├── webservers.yml
│       ├── dbservers.yml
...
```

#### 設定例

インフラ全体を定義するplaybookです。
webservers と dbservers の playbook をインクルードしているだけです。

```site.yml
---
# file: site.yml
- include: webservers.yml
- include: dbservers.yml
```

`../../common_playbook/vars/*` で共用の vars ファイルを読み込み、　　
`../../common_playbook/roles/*` で共用の role を読み込みます。

`hosts: webservers` で分かる通り hosts に webservers グループを指定します。

```webservers.yml
---
# file: webservers.yml
- hosts: webservers
  vars_files:
    - ../../common_playbook/vars/common.yml
  roles:
    - ../../common_playbook/roles/common
    - common
    - ../../common_playbook/roles/awscli
    - ../../common_playbook/roles/nginx
    - nginx
```

`hosts: dbservers.yml ` で分かる通り hosts に dbservers.yml グループを指定します。

```dbservers.yml
---
# file: dbservers.yml
- hosts: dbservers
  vars_files:
    - ../../common_playbook/vars/common.yml
  roles:
    - ../../common_playbook/roles/common
    - common
    - ../../common_playbook/roles/awscli
    - ../../common_playbook/roles/mysql
    - mysql
```

### inventory

#### ディレクトリ構成

ホストの目的および地理またはデータセンターの場所に基いて hosts ファイルにグループを定義します。
local ファイルは全てのローカルホストのインベントリを含み、production ファイルはすべての本番ホストのインベントリを含みます。

```tree
...
│       ├── hosts/
│       │   ├── local
│       │   └── production
...
```

#### 設定例

local の hosts ファイル

```hosts/local
# file: hots/local

[webservers]
192.168.33.10

[dbservers]
192.168.33.11

[local:children]
webservers
dbservers
```

production の hosts ファイル

```hosts/production
# file: hots/production

# アジアパシフィック ( 東京 ) リージョンの webservers
[tokyo-webservers]
web1-ap-northeast-1.example.com
web2-ap-northeast-1.example.com

# 米国西部 ( オレゴン )
[oregon-webservers]
web1-us-west-2.example.com
web2-us-west-2.example.com

# アジアパシフィック ( 東京 ) リージョンの dbservers
[tokyo-dbservers]
db1-ap-northeast-1.example.com
db2-ap-northeast-1.example.com

# 米国西部 ( オレゴン ) リージョンの dbservers
[oregon-dbservers]
db1-us-west-2.example.com

# 全てのリージョンの webservers
[webservers:children]
tokyo-webservers
oregon-webservers

# 全てのリージョンの dbserver
[dbservers:children]
tokyo-dbservers
oregon-dbservers

# アジアパシフィック ( 東京 ) リージョンの全てのホスト
[tokyo:children]
tokyo-webservers
tokyo-dbservers

# 米国西部 ( オレゴン ) リージョンの全てのホスト
[oregon:children]
oregon-webservers
oregon-dbservers

# 全てのホスト
[production:children]
webservers
dbservers
tokyo
oregon

```

### group_vars

#### ディレクトリ構成

group_vars 以下は **グループ名でディレクトリを作ることが可能** です。group_vars　以下に環境ごとのグループ名でディレクトリを用意します。そして、そのディレクトリ内に環境ごとに所属するグループファイルを配置します。
そうすることで、group_vars 以下を見たときに、環境ごとに整理されているため編集するファイルに迷うことがなくなり、環境ごとに所属するグループがどんなグループかも分かります。

上記の **vars ファイルの変数が割り当てられる範囲** でも説明しましたが、
  
* `group_vars/all.yml` は、サービスごとに所属するグループ全て (group_vars/ 以下に配置したグループ全て)
* `group_vars/<environment>/all.yml` には、環境ごとに所属するグループ全て ( group_vars/\<environment>/ 以下に配置したグループ全て )
* `group_vars/production/<group>.yml` には、環境ごとに所属するグループのみ ( group_vars/production/<group>.yml のみ )

に変数が割り当てられます。

※ **上記内容におそらく誤りがあります。詳細はコメント欄を参照してください。**

また、環境ごと所属するグループでグループ名が等しくても、ディレクトリごとに分かれているため、hosts ファイルを見て、環境ごと所属するグループをそれぞれ読み込みに行きます。下記のディレクトリ構成を例にすると、 `webservers.yml` と `dbservers.yml` が環境ごとのグループでグループ名が等しくなっています。    
`ansible-playbook --inventory-file ./playbook/hosts/local ./playbook/webservers.yml`  
と実行すると、`group_vars/production/weservers.yml` で定義した変数は適用されずに　`group_vars/all.yml` `group_vars/local/all.yml` `group_vars/local/weservers.yml` の 3 ファイルで定義した変数が適用されます。 


```tree
...
│       ├── group_vars/
│       │   ├── all.yml
│       │   ├── local/
│       │   │   ├── all.yml 
│       │   │   ├── webservers.yml
│       │   │   └── dbservers.yml
│       │   └── production/
│       │       ├── all.yml
│       │       ├── webservers.yml
│       │       ├── dbservers.yml
│       │       ├── tokyo.yml
│       │       └── oregon.yml
...
```

もし、 `group_vars/local/webservers.yml` と `group_vars/production/webservers.yml` の変数の定義が全く一緒の内容だった場合、`group_vars/local/webservers.yml` と `group_vars/production/webservers.yml` のどちらか片方を修正するたびにもう片方も修正しなければいけません。  
そのような、二重管理を防ぐためには下記のようなディレクトリ構成にしておくことが望ましいでしょう。

```tree
...
│       ├── group_vars/
│       │   ├── all.yml
│       │   ├── webservers.yml
│       │   ├── local/
│       │   │   ├── all.yml 
│       │   │   └── dbservers.yml
│       │   └── production/
│       │       ├── all.yml
│       │       ├── dbservers.yml
│       │       ├── tokyo.yml
│       │       └── oregon.yml
...
```

このように `webservers.yml` を外出しし `group_vars/webservers.yml` に配置することで、 local グループ及び production グループの webservers グループが変数を共用できるようになります。

group_vars 以下に、ただ単にグループ名の vars ファイルを配置するのではなくディレクトリを上手く使った構成にすることで管理のしやすさが格段に上がります。

#### 設定例

```group_vars/local/all.yml
---
# file: group_vars/local/all.yml
ansible_ssh_user: vagrant
```

```group_vars/production/all.yml
---
# file: group_vars/production/all.yml
ansible_ssh_user: ubuntu
```

```group_vars/production/tokyo.yml
---
# file: group_vars/production/tokyo.yml
aws_output: json
aws_region: ap-northeast-1
aws_access_key_id: XXXXXXXXXXXX
aws_secret_access_key: XXXXXXXXXXXX
```

```group_vars/production/tokyo.yml
---
# file: group_vars/production/oregon.yml
aws_output: text
aws_region: us-west-2
aws_access_key_id: XXXXXXXXXXXX
aws_secret_access_key: XXXXXXXXXXXX
```

### common_playbook

#### 構成

上記の **playbook** で `- ../../common_playbook/roles/awscli` と awscli の共用 roles を読み込み、 **group_vars** で `aws_region: ap-northeast-1` をはじめとした awscli の変数を定義しています。

今回は変数を上手く使いグループごとの切り分けを行っていますが、 when などをはじめとした様々なグループごとの切り分け方法があります。

```tree
...
│   └── roles/
│       ├── awscli/
│       │   ├── tasks/
│       │   │   └── main.yml
│       │   ├── handlers/
│       │   │   └── main.yml
│       │   ├── templates/
│       │   │   ├── config.j2
│       │   │   └── credentials.j2
...
```

#### 設定例

```common_playbook/roles/aws/tasks/main.yml
---
# file: common_playbook/roles/aws/tasks/main.yml
- name: install awscli
  pip: name=awscli

- name: create directory /home/{{ ansible_ssh_user }}/.aws
  file: path=/home/{{ ansible_ssh_user }}/.aws/ state=directory owner=root group=root mode=0755

- name: template /home/{{ ansible_ssh_user }}/.aws/config
  template: src=config.j2 dest=/home/{{ ansible_ssh_user }}/.aws/config group=root owner=root mode=0644

- name: template /home/{{ ansible_ssh_user }}/.aws/credentials.j2
  template: src=credentials.j2 dest=/home/{{ ansible_ssh_user }}/.aws/credentials group=root owner=root mode=0644
```

```common_playbook/roles/aws/templates/config.j2
# common_playbook/roles/aws/templates/config.j2
[default]
output = {{ aws_output }}
region = {{ aws_region }}
```

```common_playbook/roles/aws/templates/credentials.j2
# file: common_playbook/roles/aws/templates/credentials.j2
[default]
aws_access_key_id = {{ aws_access_key_id }}
aws_secret_access_key = {{ aws_secret_access_key }}
```


## 上記構成の設定例でできること

### Vagrant

#### Vagrantfile を設定

```Vagrantfile
# file: Vagrantfile
Vagrant.configure(2) do |config|
  config.vm.box = "ubuntu/trusty64"
  config.vm.define "webservers" do |webservers|
    webservers.vm.hostname = "webservers"
    webservers.vm.network "private_network", ip: "192.168.33.10"
    webservers.vm.provision "ansible" do |ansible|
      ansible.inventory_path = "./playbook/hosts/local"
      ansible.playbook = "./playbook/webservers.yml"
    end
  end
  config.vm.define "dbservers" do |dbservers|
    dbservers.vm.hostname = "dbservers"
    dbservers.vm.network "private_network", ip: "192.168.33.11"
    dbservers.vm.provision "ansible" do |ansible|
      ansible.inventory_path = "./playbook/hosts/local"
      ansible.playbook = "./playbook/dbservers.yml"
    end
  end
end
```

#### Vagrant コマンド

### インフラ全体を構成

初回の起動時のみプロビジョニングが走ります。

```
$ vagrant up
```

### インフラ全体を再構成

二回目以降の起動時にプロビジョニングを走らせたい場合。

```
$ vagrant up --provision
```

起動中にプロビジョニングを走らせたい場合。

```
$ vagrant provision
```

### webserver だけを再構成

```
$ vagrant provision webservers
```

### ansible-playbook コマンド

### インフラ全体を再構成

```
$ ansible-playbook --inventory-file ./playbook/hosts/production ./playbook/site.yml
```

### webserver だけを再構成

```
$ ansible-playbook --inventory-file ./playbook/hosts/production ./playbook/webservers.yml 
```

### tokyo だけを再構成

```
$ ansible-playbook --inventory-file ./playbook/hosts/production ./playbook/site.yml --limit tokyo
```

### tokyo の webserver だけを再構成

```
$ ansible-playbook --inventory-file ./playbook/hosts/production ./playbook/webservers.yml --limit tokyo
```

## 備考

まとめ方が割と雑ですし追記していきたい部分も沢山あるので、少しずつ更新かけてより詳しく書いていく予定です。 ( 何より説明が分かりづらいww すみません。 )

## 参考 URL

* [Ansible オレオレベストプラクティス - Qiita](http://qiita.com/yteraoka/items/5ed2bddefff32e1b9faf "Ansible オレオレベストプラクティス - Qiita")
* [Best Practices — Ansible Documentation](http://docs.ansible.com/ansible/playbooks_best_practices.html "Best Practices — Ansible Documentation")