Ansibleのグローバルスコープの変数をどこに定義するか

# はじめに

Ansible の勉強をする中で、あまりにも変数を定義出来る箇所が多いので驚きました。ざっと数えても 20 パターンあるけど、あまり纏まった情報も見つからない…。

そこで、基盤構築のケースに応じた Ansible 変数定義のベストプラクティスを個人的に纏めていこうと思います。

今回は、**「全ノード、全プレイブック共通のグローバルな変数を定義する箇所」**に焦点を当てて紹介します。


# 前提

## Ansible の利用ケース
個人で利用、というよりは数人以上のチームで利用する場合に焦点を当てています。
なので、個人で利用する場合には「そこまで縛らなくても…」と感じられる内容かもしれません。

## ディレクトリ構成
当記事のAnsibleのディレクトリ構成は、[公式のベストプラクティスのディレクトリレイアウト](http://docs.ansible.com/ansible/latest/playbooks_best_practices.html#directory-layout)に沿う構成を前提に話を進めていきます。


## 実行環境
Windows端末上に **Vagrant**で **CentOS** を立ち上げ、localhost に対して Ansible を実行しています。

|Software|Version|
|:--|:--|
|Windows|10(1709)|
|VirtualBox|5.2.4|
|Vagrant|2.0.1|
|CentOS|[bento/centos-7.2](https://app.vagrantup.com/bento/boxes/centos-7.2)|
|Ansible|2.3.0.1|
|Python|2.7.5|

<br>

# Ansible におけるグローバル変数

## Ansible における変数スコープ
参考までに、Ansible には、3パターンの変数スコープがあります。以下に、[Ansible実践ガイド](https://www.amazon.co.jp/dp/B01NAH7NAA/ref=dp-kindle-redirect?_encoding=UTF8&btkr=1)から引用させて頂きます。

> * __Global :__ 
>    Ansible の実行全体に対して定義。プレイブックのどこからも参照できる。環境変数、エクストラ変数など。
>
> * __Play :__ 
>    個々のプレイ内で定義。プレイ内でしか参照できない。プレイ変数やタスク変数、ロール変数など。
>
> * __Host :__ 
>    各ターゲットノードに紐づく変数。インベントリ変数やファクト変数、レジスタ変数など。


![スコープ図.png](https://qiita-image-store.s3.amazonaws.com/0/147552/7e3b9728-b8a3-3124-df54-0e6ab6b03739.png)


以降、当記事では冒頭に述べたように **Global** なスコープの変数に焦点を絞って説明していきます。


## グローバル変数のパターン
グローバルスコープの変数は、以下の4つのパターンで定義可能です。(参考 : [公式](http://docs.ansible.com/ansible/latest/playbooks_variables.html))

|パターン|定義方法|
|:--|:--|
|ローカル定義したファクト変数 |/etc/ansible/fact.d 配下の .fact 拡張子ファイル内で定義|
|環境変数 |ターゲットノードより取得定義 (※)|
|エクストラ変数|ansible-playbook コマンド実行時に引数(-ｅ)に指定|
|グループ変数の応用|group_vars/all.yml に定義 (※※)|

### **(※) 環境変数の定義**
環境変数は、Ansible 実行時に収集される各ターゲットノードに紐づくファクト情報に含まれています。
(なら、ファクト変数と同様にスコープは **Host** じゃないかと思うのですが…)

一方で、```environment``` ディレクティブを使用して以下の様に独自の環境変数を定義・参照することも出来ます。  
この場合のスコープは **Play** (ディレクティブを定義したプレイまたはタスク単位)になります。

```playbook.yml
- hosts: localhost
  environment:
    http_proxy: http://xxxx.xxx.xxx:8080
  tasks:
    - debug: var=ansible_env.http_proxy
```


このようにスコープ定義が使用方法によって変わるため、以降では**環境変数についてはグローバル変数のパターンからは除き**ます。

### **(※※) group_vars/all.yml に定義**

一方、「group_vars/all.yml 」については、以下の理由からグローバル変数のパターンに追加しました。厳密にはインベントリ変数の扱いなので、上記のスコープに当てはめると **Host** となります。

* ```group_vars```配下の```${ノード毎のグループ名}.yml```は**どのプレイブックからも参照できる**
* 暗黙で全てのノードは「all」グループに属するため、```all.yml```は**全ノード共通定義**を意味する


# グローバル変数のベストプラクティス
## 結論
個人的には、以下の条件下でグローバル変数を定義するのが良いと思っています。

__■ Where : 「group_vars/all.yml」__
__■ When : 定義値にほぼ変更は発生しない見込みがあり、継続的・共通的に使用される時__

* 定義

    ```group_vars/all.yml
    ntp_server: default-time.example.com
    
    ```

* 呼び出し

    ```playbook.yml
    ---
    - hosts: localhost
      tasks:
      - debug: msg="{{ ntp_server }}"

    ## 実行結果 ##
    # TASK [debug] *********************************************************************
    # ok: [localhost] => {
    #     "msg": "default-time.example.com"
    # }
    ```


## Why

以下のような理由から、どちらかと言うと消去法的に選びました。

### **定義の管理のしやすさ**
|パターン|評価|理由|
|:--|:--|:--|
|ローカル定義したファクト変数 |△|Ansible の実行ディレクトリとは別のファイルで管理されている|
|エクストラ変数|✕|Ansible の実行ディレクトリから定義を探せない|
|group_vars/all.yml に定義 |◯|Ansible の実行ディレクトリにあり、かつ1つのファイルに纏っている|

エクストラ変数は、「コマンド引数に何を渡す」って情報を管理しなきゃいけなくなるのでチーム開発では特におすすめしないです。(個人で利用する分には良いと思います)

### **本来の使い方に即しているか**
|パターン|評価|理由|
|:--|:--|:--|
|ローカル定義したファクト変数 |✕|本来はターゲットノード情報のことを指す|
|エクストラ変数|◯|-|
|group_vars/all.yml に定義 |◯|-|


ファクト変数は、先程述べたように本来の役割は**ターゲットノード毎に取得される定義**のため、グローバルではありません。
参考までに以下のように定義してグローバルな変数として呼び出せますが、おすすめしないです。

* 定義

    ```/etc/ansible/facts.d/test.fact
    [default]
    hardware = x86_64
    ```

* 呼び出し

    ```playbook.yml
    
    ---
    - hosts: localhost
      tasks:
        - debug: var=ansible_local.test.default.hardware
    
    ## 実行結果 ##
    # TASK [debug] *********************************************************************
    # ok: [localhost] => {
    #     "ansible_local.test.default.hardware": "x86_64"
    # }
    ```

# おわりに

以上、**「全ノード、全プレイブック共通のグローバルな変数を定義する箇所」** の定義パターンと、(個人的) ベストプラクティスとして ```group_vars/all.yml``` を紹介しました。

個人かチームか、やプレイブックの設計にもよると思いますので、「ここに定義した方が寧ろ良い」という意見があれば是非お願いします。