Ansible 俺流 Best Practices

# はじめに
この記事は、[Leveragesアドベントカレンダー](https://qiita.com/advent-calendar/2017/engineer)11日目の記事になります。

[10日目](https://qiita.com/winky/items/f74f794649372c0f729d)に続いてAnsible関連の記事をあげていきます。
今回は導入の過程でたどり着いたベストプラクティスを紹介します
[公式ベストプラクティス](http://docs.ansible.com/ansible/latest/playbooks_best_practices.html)では少し都合が悪かったので公式を参考に少しカスタマイズしたものになります。
（自分流なので完全なベストプラクティスでない場合もあります。)

ベストプラクティスにたどり着く過程で下記の記事を参考にさせて頂きました。
[Ansible ( 俺の中で ) 最強の Best Practices](https://qiita.com/kotarella1110/items/79af4485bd7985935d6b#playbook)
[Ansible オレオレベストプラクティス](https://qiita.com/yteraoka/items/5ed2bddefff32e1b9faf)

## 公式では足りなかった理由

- 共有で使いたいRoleがいくつか存在した
- とはいえ社内秘情報もあるのでAnsible Galaxyに載せるのはちょっと…
- プロジェクトが大量に存在するためそれぞれに`production`,`staging`などの設定が存在する
- とはいえ全プロジェクトで`production`,`staging`などの環境で同じ設定のものも存在する

# 出来上がったベストプラクティス
```
playbook/
   ├── ansible.cfg                       # ansibleの設定ファイル
   ├── group_vars/
   │   ├── development
   │   │   └── all.yml                   # 全プロジェクトdevelopment環境用変数
   │   ├── production
   │   │   └── all.yml                   # 全プロジェクトproduction環境用変数
   │   ├── staging
   │   │   └── all.yml                   # 全プロジェクトstaging環境用変数
   │   └── all.yml                       # 全プロジェクト全環境用変数
   ├── host_vars/
   │   ├── host1.yml                     # host1用環境変数
   │   │         ︙
   │   └── host2.yml                     # host2用環境変数
   ├── inventories/
   │   ├── service-A
   │   │   ├── group_vars
   │   │   │   ├── development
   │   │   │   │   └── all.yml           # service-Aのdevelopment環境用変数
   │   │   │   ├── production
   │   │   │   │   └── all.yml           # service-Aのproduction環境用変数
   │   │   │   ├── staging
   │   │   │   │   └── all.yml           # service-Aのstaging環境用変数
   │   │   │   └── all.yml               # service-Aの全環境用変数
   │   │   ├── development.yml           # service-Aのdevelopment環境用インベントリファイル
   │   │   ├── production.yml            # service-Aのproduction環境用インベントリファイル
   │   │   └── staging.yml               # service-Aのstaging環境用インベントリファイル
   │   ├── service-B
   │   │   ├── group_vars
   │   │   │   ├── development
   │   │   │   │   └── all.yml           # service-Bのdevelopment環境用変数
   │   │   │   ├── production
   │   │   │   │   └── all.yml           # service-Bのproduction環境用変数
   │   │   │   ├── staging
   │   │   │   │   └── all.yml           # service-Bのstaging環境用変数
   │   │   │   └── all.yml               # service-Bの全環境用変数
   │   │   ├── production.yml            # service-Bのproduction環境用インベントリファイル
   │   │   ├── local.yml                 # service-Bのlocal環境用インベントリファイル
   │   │   ├── development.yml           # service-Bのdevelopment環境用インベントリファイル
   │   │   └── staging.yml               # service-Bのstaging環境用インベントリファイル
   │   ︙
   │   └── ...
   ├── roles
   │   ├── common
   │   │   ├── defaults
   │   │   │   └── main.yml              # commonロール内で使うデフォルト変数
   │   │   ├── files                     # commonロール内で使用するリソースファイル
   │   │   ├── handlers                  # commonロール内のタスクに依存するハンドラ
   │   │   ├── meta                      # commonロールに関連する依存関係
   │   │   ├── tasks
   │   │   │   └── main.yml              # commonロールで実行するタスク基軸
   │   │   ├── templates                 # commonロール内で呼び出されるテンプレート
   │   │   └── vars                      # commonロール内に関連する変数
   │   ├── nginx
   │   │   ├── defaults
   │   │   │   └── main.yml              # nginxロール内で使うデフォルト変数
   │   │   ├── files                     # nginxロール内で使用するリソースファイル
   │   │   ├── handlers
   │   │   │   └── main.yml              # nginxロール内のタスクに依存するハンドラ
   │   │   ├── meta                      # nginxロールに関連する依存関係
   │   │   ├── tasks
   │   │   │   ├── configure.yml         # nginxロールで実行する設定関連タスク
   │   │   │   ├── install.yml           # nginxロールで実行するインストール関連タスク
   │   │   │   └── main.yml              # nginxロールで実行するタスク基軸
   │   │   ├── templates                 # nginxロール内で呼び出されるテンプレート
   │   │   │   ├── location_root.conf.j2
   │   │   │   ├── nginx.conf.j2
   │   │   │   └── servers.conf.j2
   │   │   └── vars                      # nginxロール内に関連する変数
   │   ︙
   │   └── ...
   └── site.yml                          # 実行するPlaybook

```

# 構成と設定例
## Playbook
### 構成
```
playbook/
   ├── ansible.cfg                       # ansibleの設定ファイル
   ︙
   └── site.yml 
```

- ansible.cfg  
このプレイブック用のansibleの設定ファイルです。
- site.yml  
対象ホストグループとロールの関連設定ファイル

### 設定例
ansibleのオプションを設定します。
オプションの項目と詳細は[Configuration file — Ansible Documentation](http://docs.ansible.com/ansible/latest/intro_configuration.html)を参考にします。

```ansible.cfg
[defaults]
forks          = 10
transport      = ssh
retry_files_enabled = False
```

対象ホストグループとそのグループに対して実行するロールを指定します。


```site.yml
---
- hosts: all
  become: yes
  roles:
    - common

- hosts: web
  become: yes
  roles:
    - php
    - nginx
```

実行するロール内で必要となる他のロールのhandlerは下記のように呼び出します。

```site.yml 
- hosts: db
  become: yes
  roles:
    - postgresql
  handlers:
    - include: roles/php/handlers/main.yml
    - include: roles/nginx/handlers/main.yml
```

## group_vars
### 構成
各環境のディレクトリを作成し、その配下に環境変数格納用のymlを配置します。
ここに格納する環境変数はサービスに関係なく、例えば本番環境であれば全サービスの本番環境で必要な変数になります。

```
   ├── group_vars/
   │   ├── production
   │   │   └── all.yml                   # 全プロジェクトdevelopment環境用変数
```


### 設定例
ansibleを実行する際のsshのポートとユーザー名を指定します。

``` group_vars/production/all.yml
ansible_ssh_port: 22
ansible_ssh_user: root
```

## Inventory
### 構成
inventories以下にサービス毎にディレクトリを作成します。

```
├── inventories/
   │   ├── service-A
   ︙  ︙
   │   ├── service-B
```

サービスのディレクトリ以下は環境毎にymlを作成し環境変数格納用の`group_vars`ディレクトリを配置します。
`group_vars`ディレクトリ下には各環境ごとのディレクトリを作成し、環境変数を格納するymlを作成します。
ここで格納する環境変数はサービスと環境ごとに変わる変数になります。

```
   │   ├── service-A
   │   │   ├── group_vars
   │   │   │   ├── development
   │   │   │   │   └── all.yml           # service-Aのdevelopment環境用変数
   │   │   │   ├── production
   │   │   │   │   └── all.yml           # service-Aのproduction環境用変数
   │   │   │   ├── staging
   │   │   │   │   └── all.yml           # service-Aのstaging環境用変数
   │   │   │   └── all.yml               # service-Aの全環境用変数
   │   │   ├── development.yml           # service-Aのdevelopment環境用インベントリファイル
   │   │   ├── production.yml            # service-Aのproduction環境用インベントリファイル
   │   │   └── staging.yml               # service-Aのstaging環境用インベントリファイル
```

### 設定
production.ymlの設定例

```production.yml
[web]
10.0.1.1

[production:children]
web
```

development.ymlの設定例

```development.yml
[web]
10.0.2.1

[db]
10.0.2.1

[production:children]
web
db
```

group_varsディレクトリ下のall.ymlの設定例
ここではnginxの設定の際に使用するサービスのルートディレクトリなどを設定しています。

```inventories/service-A/group_vars/production/all.yml
projects:
  - location_root: /www/
    host_name: service-a.example.com
    location_names:
      - root
```

## roles
### 構成
`roles`ディレクトリ以下に一般的に全サーバーで必要な`common`ディレクトリとWebサーバーに必要なNginx関連の設定を行う`nginx`ディレクトリを作成します。

```
   ├── roles
   │   ├── common
   │   │   ├── defaults
   │   │   │   └── main.yml              # commonロール内で使うデフォルト変数
   │   │   ├── files                     # commonロール内で使用するリソースファイル
   │   │   ├── handlers                  # commonロール内のタスクに依存するハンドラ
   │   │   ├── meta                      # commonロールに関連する依存関係
   │   │   ├── tasks
   │   │   │   └── main.yml              # commonロールで実行するタスク基軸
   │   │   ├── templates                 # commonロール内で呼び出されるテンプレート
   │   │   └── vars                      # commonロール内に関連する変数
   │   ├── nginx
   │   │   ├── defaults
   │   │   │   └── main.yml              # nginxロール内で使うデフォルト変数
   │   │   ├── files                     # nginxロール内で使用するリソースファイル
   │   │   ├── handlers
   │   │   │   └── main.yml              # nginxロール内のタスクに依存するハンドラ
   │   │   ├── meta                      # nginxロールに関連する依存関係
   │   │   ├── tasks
   │   │   │   ├── configure.yml         # nginxロールで実行する設定関連タスク
   │   │   │   ├── install.yml           # nginxロールで実行するインストール関連タスク
   │   │   │   └── main.yml              # nginxロールで実行するタスク基軸
   │   │   ├── templates                 # nginxロール内で呼び出されるテンプレート
   │   │   │   ├── location_root.conf.j2
   │   │   │   ├── nginx.conf.j2
   │   │   │   └── servers.conf.j2
   │   │   └── vars                      # nginxロール内に関連する変数
```

- defaults  
ロール内で使うデフォルト変数設定ファイルを格納するディレクトリ
- files  
ロール内で使用するリソースファイを格納するディレクトリ
リスースファイルとは`copy`モジュールなどで使われるファイル群
- handlers  
ロール内のタスクに依存するハンドラ(ex. nginxの設定変更後のリスタートタスク)
- meta  
ロールに関連する依存関係の設定ファイルを格納するディレクトリ
- tasks  
ロールで実行するタスクを格納するディレクトリ
- templates  
ロール内で呼び出されるテンプレート(ex. nginxの設定ファイル)
- vars  
ロール内で使用する変数を格納するディレクトリ

### 設定例
`common`ロールではyumでのパッケージインストールなどを行います。

```roles/common/tasks/main.yml
---
- name: Update yum
  yum:
    name: '*'
    state: latest
  tags: yum

- name: Install yum package
  yum:
    name: "{{ item.name }}"
    state: "{{ item.state }}"
  with_items: "{{ yum_install }}"
  tags: yum

- name: Setting timezone
  file:
    src: "{{ zoneinfo_path }}"
    dest: "/etc/localtime"
    state: link
    force: yes
  tags: timezone
```

ここのタスクで呼び出されている`yum_install`などの変数は`defaults`ディレクトリ下にて定義します。

```roles/common/defaults/main.yml
yum_install:
  - {
      name: "epel-release",
      state: "latest"
    }
  - {
      name: "python-pip",
      state: "latest"
    }
  - {
      name: "zip",
      state: "latest"
    }
  - {
      name: "unzip",
      state: "latest"
    }

zoneinfo_path: "/usr/share/zoneinfo/Asia/Tokyo"
```

`nginx`ロールでは`nginx`のインストールと`conf`ファイルの設定などを行います。
インストール関連のタスクをまとめた`install.yml`と設定関連のタスクをまとめた`configure.yml`にわけます。
`main.yml`では`install.yml`と`configure.yml`の読み込むのみにします。
これにより設定のみループ処理したりなどがしやすくなります。

```roles/nginx/tasks/main.yml
---
- include: install.yml
- include: configure.yml
  with_items: "{{ projects }}"
  loop_control:
    loop_var: project
  when: projects is defined
```

```roles/nginx/tasks/install.yml
---
- name: Install nginx
  yum:
    name: nginx
    state: latest
  tags: nginx

- name: Copy nginx configuration
  template:
    src: nginx.conf.j2
    dest: /etc/nginx/nginx.conf
  notify: restart nginx
  tags: nginx
```

```roles/nginx/tasks/configure.yml
- name: Make location root directory
  file:
    path: "{{ project.location_root }}"
    state: directory
    owner: "{{ deploy_user }}"
    group: "{{ deploy_user }}"
    mode: 0775
  tags: nginx

- name: Copy server configuration
  template:
    src: server.conf.j2
    dest: /etc/nginx/sites-available/{{ project.host_name }}/server.conf
  notify: restart nginx
  tags: nginx

- name: Copy location configuration
  template:
    src: "location_{{ item }}.conf.j2"
    dest: "/etc/nginx/sites-available/{{ project.host_name }}/locations/{{ item }}.conf"
  notify: restart nginx
  with_items: "{{ project.location_names }}"
  tags: nginx

- name: Setting php-fpm configuration
  lineinfile:
    dest: /etc/php-fpm.d/www.conf
    state: present
    backrefs: yes
    regexp: "{{ item.regexp }}"
    line: "{{ item.line }}"
  with_items: "{{ php_fpm_setting }}"
  notify: restart php-fpm
  tags: nginx

- name: Nginx is running and enabled
  service:
    name: nginx
    state: started
    enabled: yes
  tags: nginx

- name: php-fpm is running and enabled
  service:
    name: php-fpm
    state: started
    enabled: yes
  tags: nginx
```

ここで使用している変数も`common`の時と同様`defaults`ディレクトリに設定します。

```roles/nginx/defaults/main.yml
php_fpm_setting:
  - {
      regexp: "^user = apache",
      line: "user = nginx"
    }
  - {
      regexp: "^group = apache",
      line: "group = nginx"
    }
  - {
      regexp: "^listen = 127.0.0.1:9000",
      line: "listen = /var/run/php-fpm.sock"
    }
  - {
      regexp: "^;listen.owner = nobody",
      line: "listen.owner = nginx"
    }
  - {
      regexp: "^;listen.group = nobody",
      line: "listen.group = nginx"
    }
  - {
      regexp: "^;listen.mode = 0660",
      line: "listen.mode = 0660"
    }
```

また`projects`変数などは`inventories/service-A/group_vars/production/all.yml`で設定しています。
このようにサービスに依存する変数などはinventories以下に記載することで環境やサービス毎に変更することが可能です。

`configure.yml`で呼び出されている`server.conf.j2`などはテンプレートファイルとよばれ、定義している変数を使用して設定内容を動的に変更できます。
ここでは`nginx`の`server.conf`用のテンプレートを紹介します。

```roles/nginx/templates/server.conf.j2
server {
  listen       80;
  listen       [::]:80;
  server_name  {{ project.host_name }};

  {% if project.use_elb == true %}
  # アクセスが http なら https としてリダイレクト
  if ($http_x_forwarded_proto != https) {
    return 301 https://$host$request_uri;
  }

  set $my_http "http";
  set $my_ssl "off";
  set $my_port "80";
  if ($http_x_forwarded_proto = "https") {
    set $my_http "https";
    set $my_ssl "on";
    set $my_port "443";
  }
  {% endif %}

  charset UTF-8;

  error_page   500 502 503 504  /50x.html;

  access_log /var/log/nginx/{{ project.host_name }}/access.log main;
  error_log /var/log/nginx/{{ project.host_name }}/error.log;

  include /etc/nginx/sites-enabled/{{ project.host_name }}/locations/*.conf;
}
``` 

## まとめ
今回はAnsibleの導入過程でたどり着いたベストプラクティスを紹介しました。
このベストプラクティス自体はAnsibleの公式のベストプラクティスからは少し外れたものになっていますが、
多数のプロジェクトを抱えることの多い場合はある程度使いやすいものになっているのではないかと思います。

## 参考URL
[公式ベストプラクティス](http://docs.ansible.com/ansible/latest/playbooks_best_practices.html)
[Ansible ( 俺の中で ) 最強の Best Practices](https://qiita.com/kotarella1110/items/79af4485bd7985935d6b#playbook)
[Ansible オレオレベストプラクティス](https://qiita.com/yteraoka/items/5ed2bddefff32e1b9faf)