zaki work log

作業ログやら生活ログやらなんやら

[Ansible]必要な時だけサービスのrestartを行う (notify / handlers)

ミドルウェアをインストール」「ミドルウェアの設定変更」「ミドルウェアのサービスをスタート」という流れで、サービスのスタートを必要な場合だけに実施するやり方。

以前作成したplaybook

この辺で書いたやつ。

- hosts: www
  gather_facts: false
  become: true
  tasks:
  - yum:
      name: httpd
      state: present

  - name: configure httpd server
    replace:
      path: /etc/httpd/conf/httpd.conf
      regexp: ^#?Listen 80$
      replace: Listen 8080

  - name: enable httpd service
    systemd:
      name: httpd
      state: restarted
      enabled: yes

初回であれば

  1. インストール
  2. 設定変更
  3. サービス起動

を順に全部実行で良いんだけど、同じplaybookを再度Ansible実行すると

  1. インストール (skip!)
  2. 設定変更 (skip!)
  3. サービス起動 (実行!)

となり、サービスの再起動が無駄になります。

(じゃあsystemdrestartedじゃなくてstartedにすると、単純な再実行は冪等になるので処理されないけど、今度は「設定変更」の内容をバージョンアップした場合などで変化があったときに、再起動して欲しいけどされないという状態になる)

こういう時に使用するのがhandlersです。

基本形

まずは単純に「インストール」と「インストールされた場合だけrestart」

- hosts: www
  gather_facts: false
  become: true
  tasks:
  - yum:
      name: httpd
      state: present
    notify:
      - installed_httpd

  handlers:
  - name: installed_httpd
    systemd:
      name: httpd
      state: restarted
      enabled: yes

まずタスク内でnotifyを使い、「そのタスクがchangedだった場合に呼び出すハンドラ名(上の例では- installed_httpd)」を指定します。
これによって、上の例でいえば「yumモジュールでhttpdがインストールされた場合」にinstalled_httpdが呼ばれるようになります。

そして呼ばれる側の定義は通常のタスクを定義するtasksとは別にhandlersセクションで定義します。
handlersに記述したどのハンドラを呼び出すか(上の例は1つしかないけど)については、nameに指定します。ここにinstalled_httpdと書いておくことで、notify: [ installed_httpd]が指定されているタスクがchangedだった場合にこのハンドラが処理されるようになります。

これで「httpdがインストールされたときだけサービスをrestartする」となります。

nameは表示用文字列として使用して、プログラム的な識別子を別途使いたい場合は、handlers側ではlistenを指定することで、nameと別にnotify先を定義できます。(後述)

インストール or 設定変更

冒頭にも挙げた例。

「サービスのrestart」の条件が複数の場合にどうするかと言うと

---
- hosts: www
  gather_facts: false
  become: true
  tasks:
  - yum:
      name: httpd
      state: present
    notify:
      - require_reload_httpd

  - name: configure httpd server
    replace:
      path: /etc/httpd/conf/httpd.conf
      regexp: ^#?Listen 80$
      replace: Listen 8080
    notify:
      - require_reload_httpd

  handlers:
  - name: enable httpd service
    systemd:
      name: httpd
      state: restarted
      enabled: yes
    listen: require_reload_httpd

実は同じハンドラをnotifyで複数指定しても、対象が実行されるのは1回です。

上のplaybookではtasksパートでは「httpdインストール」と「httpdサーバー設定」を順に行い、その両方でnotifyを使って同じrequire_reload_httpdを指定しています。

この時の実行順と回数はtasksに定義されたタスクを順に全て実行し、その際にnotifyで同じハンドラが複数回指定があった年ても、handlersに定義された該当タスクは1回だけ実行、となります。

Handlers: Running Operations On Change

These ‘notify’ actions are triggered at the end of each block of tasks in a play, and will only be triggered once even if notified by multiple different tasks.

お、そういえば日本語ドキュメントも先月公開されたんだった

ハンドラー: 変更時の操作の実行

これらの「通知」アクションは、プレイのタスクの各ブロックの最後にトリガーされ、 複数の異なるタスクから通知された場合でも 1 回だけトリガーされます。

docs.ansible.com

docs.ansible.com

roleにする場合

上の例の「インストール」「設定変更」「必要な時だけrestart」をそのままroleに分割すると

$ tree roles/
roles/
└── install_httpd
    ├── handlers
    │   └── main.yml
    └── tasks
        └── main.yml

install_httpd/tasks/main.yml

---
- yum:
    name: httpd
    state: present
  notify:
    - require_reload_httpd

- name: configure httpd server
  replace:
    path: /etc/httpd/conf/httpd.conf
    regexp: ^#?Listen 80$
    replace: Listen 8080
  notify:
    - require_reload_httpd

install_httpd/handlers/main.yml

---
- name: enable httpd service
  systemd:
    name: httpd
    state: restarted
    enabled: yes
  listen: require_reload_httpd

元のplaybook

---
- hosts: www
  gather_facts: false
  become: true
  roles:
  - install_httpd

です。
まぁここは普通です。


実装例

github.com

github.com

複数のロールがある場合の処理順序はちょっと注意。

zaki-hmkc.hatenablog.com