Ansibleで体験した地味なトラップ

# **はじめに**

Ansibleを使うようになってしばらく経ちましたが、たいへん便利なツールだなと思っています。
ベストプラクティスだったり真面目に頭を働かせることもありますが、言われないと気づかないだけの地味なトラップみたいなものもあるのでそちらをまとめてみたいと思います。

# **各種バージョン**

現在 yum の epel-testing リポジトリから入手できる最新の Ansible を使います。

```
[root@controller ~]# cat /etc/centos-release
CentOS release 6.7 (Final)

[root@controller ~]# python --version
Python 2.6.6

[root@controller ~]# ansible --version
ansible 2.0.1.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = Default w/o overrides
```

# **トラップ1 : on, offの暗黙的変換**

## **概要**

Ansible では変数を定義して template モジュールなど多くの場所で変数を展開することができます。
この変数展開に際し、ダブルクオーテーション等で囲んだ文字列でなく、直接 on, off などを記述するとそれが **暗黙的にtrue, falseの真偽値に変換** されてしまいます。
設定ファイルを展開する際にtemplateモジュールを使うことは多いと思いますが、on, off記法の他にtrue, falseでも受け付けているパターンがあるので意外と気づかないこともあるかもしれません。

## **例**

以下のように変数を定義します。

```variable
test_on:
  - on
  - "on"

test_off:
  - off
  - "off"
```

これらの変数を debug モジュールで見てみましょう。

```Playbook
- name: check on value
  debug: msg="{{ item }}"
  with_items: "{{ test_on }}"

- name: check off value
  debug: msg="{{ item }}"
  with_items: "{{ test_off }}"
```

結果がこちらです。

```
[root@controller ansible]# ansible-playbook -i inventory/hosts check.yml

PLAY ***************************************************************************

TASK [setup] *******************************************************************
ok: [192.168.100.20]

TASK [check on value] **********************************************************
ok: [192.168.100.20] => (item=True) => {
    "item": true,
    "msg": true
}
ok: [192.168.100.20] => (item=on) => {
    "item": "on",
    "msg": "on"
}

TASK [check off value] *********************************************************
ok: [192.168.100.20] => (item=False) => {
    "item": false,
    "msg": false
}
ok: [192.168.100.20] => (item=off) => {
    "item": "off",
    "msg": "off"
}

PLAY RECAP *********************************************************************
192.168.100.20             : ok=3    changed=0    unreachable=0    failed=0
```

以上のように、ダブルクオーテーションで囲んでいないものが true, false に変換されていることがわかります。
大小文字によっても少し結果が変わるので、クオートなしとありの出力がどのようになるか表にまとめてみます。

|値|クオートなし|クオートあり|
|:---:|:---:|:---:|
|on|**true**|"on"|
|oN|"oN"|"oN"|
|On|**true**|"On"|
|ON|**true**|"ON"|


|値|クオートなし|クオートあり|
|:---:|:---:|:---:|
|off|**false**|"off"|
|oFf|"oFf"|"oFf"|
|oFF|"oFF"|"oFF"|
|ofF|"ofF"|"ofF"|
|Off|**false**|"Off"|
|OfF|"OfF"|"OfF"|
|OFf|"OFf"|"OFf"|
|OFF|**false**|"OFF"|

結果を見ればわかると思いますが、"oN" や "oFf" などの **キモい組み合わせ** には反応しないようです。


# **トラップ2 : with_dictの出力順**

## **概要**

ループを回す際に `with_items` や `with_dict` を使うかと思いますが、`with_items` は **記載した順にループされる** のに対し、 `with_dict` では **そうなりません** 。

## **例**

以下のように変数を定義します。

```variable
test_items:
  - key: A
    value: hoge
  - key: B
    value: fuga
  - key: C
    value: piyo

test_dict:
  A: hoge
  B: fuga
  C: piyo
```

例によって debug モジュールで確認します。

```Playbook
- name: check items value
  debug: msg="key={{ item.key }}, value={{ item.value }}"
  with_items: "{{ test_items }}"

- name: check dict value
  debug: msg="key={{ item.key }}, value={{ item.value }}"
  with_dict: "{{ test_dict }}"
```

```
[root@controller ansible]# ansible-playbook -i inventory/hosts check.yml

PLAY ***************************************************************************

TASK [setup] *******************************************************************
ok: [192.168.100.20]

TASK [check items value] *******************************************************
ok: [192.168.100.20] => (item={u'value': u'hoge', u'key': u'A'}) => {
    "item": {
        "key": "A",
        "value": "hoge"
    },
    "msg": "key=A, value=hoge"
}
ok: [192.168.100.20] => (item={u'value': u'fuga', u'key': u'B'}) => {
    "item": {
        "key": "B",
        "value": "fuga"
    },
    "msg": "key=B, value=fuga"
}
ok: [192.168.100.20] => (item={u'value': u'piyo', u'key': u'C'}) => {
    "item": {
        "key": "C",
        "value": "piyo"
    },
    "msg": "key=C, value=piyo"
}

TASK [check dict value] ********************************************************
ok: [192.168.100.20] => (item={'value': u'hoge', 'key': u'A'}) => {
    "item": {
        "key": "A",
        "value": "hoge"
    },
    "msg": "key=A, value=hoge"
}
ok: [192.168.100.20] => (item={'value': u'piyo', 'key': u'C'}) => {
    "item": {
        "key": "C",
        "value": "piyo"
    },
    "msg": "key=C, value=piyo"
}
ok: [192.168.100.20] => (item={'value': u'fuga', 'key': u'B'}) => {
    "item": {
        "key": "B",
        "value": "fuga"
    },
    "msg": "key=B, value=fuga"
}

PLAY RECAP *********************************************************************
192.168.100.20             : ok=3    changed=0    unreachable=0    failed=0
```

少し分かりづらいですが、dict側では **A → C → B** の順で出力されていることが確認できるかと思います。
色々なパターンを試していますが key や value の辞書式などで出力されているわけではないようです。
ただし、何度やってもこの順ではあるため、何かしらのロジックに基づいた順序ではあるようです。
(ソースを見ればいいのですが、一旦は後回しに...気が向いたら追記するかもしれません)

# **トラップ3 : serviceモジュールがcheckモードでも変更入れてくる**

## **概要**

check モードといえばいわゆる dry-run ということで、対象に状態の変更を与えずに実行(想定)結果の確認ができる便利なものです。
ところが、 service モジュールでは **check モードで動かしたにも関わらず、対象のサービスがエントリに入ってしまう** ということが発生します。

## **例**

以下の様なタスクを考えます。

```
- name: register hoge service
  service: name=hoge enabled=no
```

これは /etc/init.d 配下にある hoge サービスを off で登録するというものですが、これを check モードで動かすとおかしなことが起きます。
対象ホストの初期状態はこちらです。

```
[root@controller ansible]# ssh root@192.168.100.20 ls -lR /etc/rc* | grep hoge
-rwxr-xr-x  1 root root 11048 Apr  7 16:33 hoge
[root@controller ansible]# ssh root@192.168.100.20 chkconfig --list hoge
service hoge supports chkconfig, but is not referenced in any runlevel (run 'chkconfig --add hoge')
```

/etc/init.d 配下に hoge というサービススクリプトが配置されていますが、 /etc/rc*.d からリンクは張られておらず、 chkconfig には登録されていません。
この状態から、 --check で Ansible を実行してみましょう。

```
[root@controller ansible]# ansible-playbook -i inventory/hosts check.yml --check

PLAY ***************************************************************************

TASK [setup] *******************************************************************
ok: [192.168.100.20]

TASK [register hoge service] ***************************************************
changed: [192.168.100.20]

PLAY RECAP *********************************************************************
192.168.100.20             : ok=2    changed=1    unreachable=0    failed=0

[root@controller ansible]# ssh root@192.168.100.20 ls -lR /etc/rc* | grep hoge
-rwxr-xr-x  1 root root 11048 Apr  7 16:33 hoge
lrwxrwxrwx  1 root root 14 Apr  7 16:47 K92hoge -> ../init.d/hoge
lrwxrwxrwx  1 root root 14 Apr  7 16:47 K92hoge -> ../init.d/hoge
lrwxrwxrwx  1 root root 14 Apr  7 16:47 S08hoge -> ../init.d/hoge
lrwxrwxrwx  1 root root 14 Apr  7 16:47 S08hoge -> ../init.d/hoge
lrwxrwxrwx  1 root root 14 Apr  7 16:47 S08hoge -> ../init.d/hoge
lrwxrwxrwx  1 root root 14 Apr  7 16:47 S08hoge -> ../init.d/hoge
lrwxrwxrwx  1 root root 14 Apr  7 16:47 K92hoge -> ../init.d/hoge
[root@controller ansible]# ssh root@192.168.100.20 chkconfig --list hoge
hoge            0:off   1:off   2:on    3:on    4:on    5:on    6:off
```

なんということでしょう。
check モードで動かしているにも関わらず、 /etc/rc*.d からリンクが張られてしまいました。
また、 service モジュール自体は off で登録するように書いているのですが、 **chkconfig に登録する上、 on の状態** となっています。
そして、 --check を外して実際に動かしてみます。

```
[root@controller ansible]# ansible-playbook -i inventory/hosts check.yml

PLAY ***************************************************************************

TASK [setup] *******************************************************************
ok: [192.168.100.20]

TASK [register hoge service] ***************************************************
changed: [192.168.100.20]

PLAY RECAP *********************************************************************
192.168.100.20             : ok=2    changed=1    unreachable=0    failed=0

[root@controller ansible]# ssh root@192.168.100.20 ls -lR /etc/rc* | grep hoge
-rwxr-xr-x  1 root root 11048 Apr  7 16:33 hoge
lrwxrwxrwx  1 root root 14 Apr  7 16:47 K92hoge -> ../init.d/hoge
lrwxrwxrwx  1 root root 14 Apr  7 16:47 K92hoge -> ../init.d/hoge
lrwxrwxrwx  1 root root 14 Apr  7 16:47 K92hoge -> ../init.d/hoge
lrwxrwxrwx  1 root root 14 Apr  7 16:47 K92hoge -> ../init.d/hoge
lrwxrwxrwx  1 root root 14 Apr  7 16:47 K92hoge -> ../init.d/hoge
lrwxrwxrwx  1 root root 14 Apr  7 16:47 K92hoge -> ../init.d/hoge
lrwxrwxrwx  1 root root 14 Apr  7 16:47 K92hoge -> ../init.d/hoge
[root@controller ansible]# ssh root@192.168.100.20 chkconfig --list hoge
hoge            0:off   1:off   2:off   3:off   4:off   5:off   6:off
```

当然ながら、これ自体は Playbook に書いた通りの状態となっています。
結果的には Playbook に書いた通りの状態ではあるのですが、さすがにこれはいただけないと思います。
今のところ代替として、 command モジュールで `chkconfig --add hoge ; chkconfig hoge on` みたいなものを書いています。
chkconfig コマンド自体は冪等性のある動きをしているし、 command モジュールは --check 時にはスキップされるのでまだマシかなと思っています。

# **続き**

他にもあったら随時追記していきます。