Ansibleでfirewalldを設定（ポート開閉）

CentOS7のfirewalldに対して、特定portを開閉する Ansible Playbook を書いた。
若干、素直な動作でなかったので、**注意点**をメモしておく。

★10/17更新：with_itemsを利用した別解を、Playbook2として掲載


# お題

こんな感じのPlaybookを書く。

 - Firewalldが起動中のマシンだけ、設定する
 - portのON/OFFを即座に反映、マシンの再起動は不要
 - 永続的に設定、マシン再起動後も設定を維持
 - 既に設定済みの状況なら、余計なことをしない（changed=0）


# 利用イメージ

普通に実行

    ansible-playbook -i hosts firewalld.yml

ポート状態を引数で指定： port 443をオープンする場合

    ansible-playbook -i hosts firewalld.yml -e "port=443/tcp state=enabled"


# Playbook1｜notifyを利用

```firewalld.yml
--- # file: firewalld.yml

- hosts: all
  remote_user: root
  gather_facts: False

  vars:
    port: 80/tcp
    state: enabled  # enabled: open, disabled: close

  tasks:
    - name: check if firewalld is running
      command: systemctl is-active firewalld
      register: firewalld_result
      changed_when: False
      ignore_errors: True  # rc is 3 when firewalld is stopped

    - name: set the port state
      firewalld: permanent=True port={{ port }} state={{ state }}
      notify: reload firewalld
      when: firewalld_result.stdout == "active"

  handlers:
    - name: reload firewalld
      service: name=firewalld state=restarted
```

## 注意点

上記Playbookでの注意点は３つ。

 1. permanent=True で書き換わるのは、設定ファイルのみ
 2. 設定ファイルが変化した時だけ、リロードするため、notifyを利用
 3. リロードでは restarted を使う： **reloadedではダメだった**

[補足] reloadedでは、portクローズについては即反映されるが、portオープンは反映されない様子。portクローズ＆オープン両方とも即時反映するために、restarted を使っている。

また、少し脇道にそれるけれども。。。

 - 上記PlaybookをRole化して、Tag付けして利用する時は、tasks側とhandlers側の両方のファイルに、同じTagを付ける必要あり

というバグ？に引っかかってしまった。roleとtagの組み合わせは色々と動きがおかしいような。。。相性悪いのかな？


## 実行結果

### firewalld が停止している時

```
$ ansible-playbook -i hosts firewalld.yml

PLAY [all] ********************************************************************

TASK: [check if firewalld is running] *****************************************
failed: [localhost] => {"changed": false, "cmd": ["systemctl", "is-active", "firewalld"], "delta": "0:00:00.003834", "end": "2014-10-16 17:10:22.305641", "rc": 3, "start": "2014-10-16 17:10:22.301807", "stdout_lines": ["inactive"]}
stdout: inactive
...ignoring

TASK: [set the port state] ****************************************************
skipping: [localhost]

PLAY RECAP ********************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=0
```

### firewalld が起動中： disabled -> enabled

```
$ ansible-playbook -i hosts firewalld.yml

PLAY [all] ********************************************************************

TASK: [check if firewalld is running] *****************************************
ok: [localhost]

TASK: [set the port state] ****************************************************
changed: [localhost]

NOTIFIED: [reload firewalld] **************************************************
changed: [localhost]

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


### firewalld が起動中： enabled -> enabled

```
$ ansible-playbook -i hosts firewalld.yml

PLAY [all] ********************************************************************

TASK: [check if firewalld is running] *****************************************
ok: [localhost]

TASK: [set the port state] ****************************************************
ok: [localhost]

PLAY RECAP ********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0
```


# Playbook2｜with_itemsを利用

Playbook1だと、より大きなPlaybookに組み込む場合には、望ましくない性質がある。

Handlers部分は、Playbook実行の最後に行われるので、Tasks部分でfirewalldを設定した後、何らかの後続の処理が失敗、実行が中断すると、Handlers部分のリロードが行われない。以降は、Playbookを再実行しても（既に設定は変更済みなわけだから）notifyもされず、設定ファイルは一生リロードされない。

場合によっては、Notifyを使うより、現在のサービス状態だけを見て、その場でサービス状態を変更するほうが良いかもしれない。

そのように書いたPlaybookが以下。

```firewalld.yml
--- # file: firewalld.yml

- hosts: all
  remote_user: root
  gather_facts: False

  vars:
    port: 80/tcp
    state: enabled  # enabled: open, disabled: close

  tasks:
    - name: check if firewalld is running
      command: systemctl is-active firewalld
      register: firewalld_result
      changed_when: False
      ignore_errors: True  # rc is 3 when firewalld is stopped

    - name: set the port state
      firewalld: permanent={{ item }} port={{ port }} state={{ state }}
      with_items: [ True, False ]
      when: firewalld_result.stdout == "active"
```

with_itemsでループを回している。
ループの1回めは item==True になり、設定ファイルのみ変更。
ループの2回めは、item==False になり、サービス状態のみ変更。
設定ファイルのリロードは無し。

小さくなったし、こっちの方がベターかな。

ただこの場合は、設定ファイルを複数回変更する場合は、まとめて1度だけリロードする、といった芸当はできなくなる。
リロード回数が重要な時は、Playbook1も良いかも。


## 実行結果

### firewalld が停止している時

```
$ ansible-playbook -i hosts firewalld.yml

PLAY [all] ********************************************************************

TASK: [check if firewalld is running] *****************************************
failed: [localhost] => {"changed": false, "cmd": ["systemctl", "is-active", "firewalld"], "delta": "0:00:00.006989", "end": "2014-10-17 07:51:41.657187", "rc": 3, "start": "2014-10-17 07:51:41.650198", "stdout_lines": ["inactive"]}
stdout: inactive
...ignoring

TASK: [set the port state] ****************************************************
skipping: [localhost] => (item=True)
skipping: [localhost]

PLAY RECAP ********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0
```

### firewalld が起動中： disabled -> enabled

with_itemsを使っているので、2つ変更しても changed=1 ですね。

```
$ ansible-playbook -i hosts firewalld.yml

PLAY [all] ********************************************************************

TASK: [check if firewalld is running] *****************************************
ok: [localhost]

TASK: [set the port state] ****************************************************
changed: [localhost] => (item=True)
changed: [localhost]

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


### firewalld が起動中： enabled -> enabled

```
$ ansible-playbook -i hosts firewalld.yml

PLAY [all] ********************************************************************

TASK: [check if firewalld is running] *****************************************
ok: [localhost]

TASK: [set the port state] ****************************************************
ok: [localhost] => (item=True)
ok: [localhost]

PLAY RECAP ********************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0
```

# 参考：firewalldの状態確認

ポートの状態は、以下のコマンドで確認した。

```
# firewall-cmd --list-all
public (default)
  interfaces:
  sources:
  services: dhcpv6-client ssh
  ports: 80/tcp
  masquerade: no
  forward-ports:
  icmp-blocks:
  rich rules:
```