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を利用
--- # 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での注意点は3つ。
- permanent=True で書き換わるのは、設定ファイルのみ
- 設定ファイルが変化した時だけ、リロードするため、notifyを利用
- リロードでは 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が以下。
--- # 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: