# 怎麼在 Playbooks 裡使用 Handlers？

## 15. 怎麼在 Playbooks 使用 Handlers？

[Handlers](https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html#handlers-running-operations-on-change) 是我們在 Ansible Playbooks 裡很常用來重開系統服務 (Service) 的手法，大家可以在不少前人分享的 Playbooks 裡看見它的蹤跡，這裡凍仁將透過安裝 Nginx 的 Playbook 來介紹它。

![automate\_with\_ansible\_practice-20.jpg](/files/-LpvdLmL35eGSKhSZGcY)

### Handlers 是什麼？

Handler 本身是一種非同步的 callback function ；在這裡則是指關連於特定 tasks 的事件 (event) 觸發機制。當這些特定的 tasks 狀態為**被改變 (changed)** 且都已被執行時，才會觸發一次 event。

以上圖為例，要觸發 `restart nginx` 這個 handler，需符合以下條件：

1. `modify index.html` 或 `turn server_tokens off` 兩個 tasks 中，至少有一個狀態為 **changed**。
2. 所有關連到 `restart nginx` handler 的 tasks 都已被執行。

### 怎麼使用 Handlers？

底下凍仁將透過部署 Nginx 的 Playbook 為例。

1. 建立 ansible.cfg。

   ```
   $ vi ansible.cfg
   [defaults]

   inventory = inventory
   remote_user = docker
   private_key_file = ~/.ssh/id_rsa
   host_key_checking = False
   retry_files_save_path = ./ansible-retry
   ```
2. 建立 inventory file。

   ```
   $ vi inventory
   server1  ansible_ssh_host=192.168.1.104  ansible_ssh_port=2221
   ```
3. 建立 setup\_nginx.yml。

   ```
   $ vi setup_nginx.yml
   ---

   - name: setup the nginx
     hosts: all
     become: true
     vars:
       username: "ironman"
       mail: "chusiang (at) drx.tw"
       blog: "http://note.drx.tw"

     tasks:
       # 執行 'apt-get update' 指令。
       - name: update apt repo cache
         apt: name=nginx update_cache=yes

       # 執行 'apt-get install nginx' 指令。
       - name: install nginx with apt
         apt: name=nginx state=present

       # 於網頁根目錄 (DocumentRoot) 編輯 index.html。
       - name: modify index.html
         template: >
           src=templates/index.html.j2
           dest=/usr/share/nginx/html/index.html
           owner=www-data
           group=www-data
           mode="644"
           backup=yes
         notify: restart nginx

       # (security) 關閉 server_tokens：移除 server_tokens 前的 '#' 字元。
       - name: turn server_tokens off
         lineinfile: >
           dest=/etc/nginx/nginx.conf
           regexp="server_tokens off;"
           insertafter="# server_tokens off;"
           line="server_tokens off;"
           state=present
         notify: restart nginx

     # handlers
     #
     # * 當確認事件有被觸發才會動作。
     # * 一個 handler 可被多個 task 通知 (notify)，並於 tasks 跑完才會執行。
     handlers:
       # 執行 'sudo service nginx restart' 指令。
       - name: restart nginx
         service: name=nginx enabled=yes state=restarted

     # post_tasks:
     #
     # 在 tasks 之後執行的 tasks。
     post_tasks:
       # 檢查網頁內容。
       - name: review http state
         command: "curl -s http://localhost"
         register: web_context

       # 印出檢查結果。
       - name: print http state
         debug: msg={{ web_context.stdout_lines }}

   # vim:ft=ansible :

   ```

   > 註：`raw` 和 `endraw` 是為了相容 GitBook 所增加的語法，您可能會在某平台上看到它，請忽略之。

   1. 在第 47 行裡，我們建立了一個 `restart nginx` handler。
   2. 在修改到 Nginx 設定檔的 tasks (`modify index.html`, `turn server_tokens off`) 裡，使用 `notify` 通知 handlers (`restart nginx`) 說這些 Tasks 要進行關連。
   3. 最後在 `post_tasks` 裡建了 2 個 tasks，讓它們可以在一般的 tasks 結束後才執行。
4. 建立 Nginx vhost 的 template：請參考前一篇的「[14. 怎麼使用 Ansible 的 Template 系統？](/14.how-to-use-the-ansible-template-system.md)，凍仁就不在此多加詳述。

   ```
   $ mkdir templates && vi templates/index.html.j2
   <!DOCTYPE html>
   <html>
     <head>
       <meta charset="UTF-8">
       <title>Day15 demo | automate-with-ansible</title>
     </head>
     <style type="text/css" media="all">
       body {
         font-size: x-large;
       }
     </style>
     <body>
       <p>
   <pre>[ {{ username }}@automate-with-ansible ~ ]$ hostname
   automate-with-ansible.drx.tw
   [ {{ username }}@automate-with-ansible ~ ]$ cowsay "This is a ansible-playbook demo for automate-with-ansible at 2016/12/15."
    _____________________________________
   / This is a ansible-playbook demo for \
   \ automate-with-ansible at 2016/12/15./
    -------------------------------------
           \   ^__^
            \  (oo)\_______
               (__)\       )\/\
                   ||----w |
                   ||     ||
   [ {{ username }}@automate-with-ansible ~ ]$
   [ {{ username }}@automate-with-ansible ~ ]$
   [ {{ username }}@automate-with-ansible ~ ]$ cat .profile
   - {{ mail }}
   - {{ blog }}</pre>
       </p>
     </body>
   </html>

   ```

   > 註：`raw` 和 `endraw` 是為了相容 GitBook 所增加的語法，您可能會在某平台上看到它，請忽略之。

   * 在這份 index.html.j2 裡，我們用了 `username`, `mail` 和 `blog` 三個變數，其值會從 `setup_nginx.yml` 中代入。
5. 執行 Playbook。

   ![2016-12-15-ansible-handlers.gif](/files/-LpvdLmRlNbEEh_V3iGh)

   * 試著多跑幾次，就會發現當 `modify index.html` 和 `turn server_tokens off` tasks 的狀態不為 changed 時，該 handler 不會被觸發的差異。
   * 在此例中，我們可以藉由修改 Playbook 裡的變數 (vars) 來重複觸發 handler，例如把 `username` 從 `ironman` 修改成 `root`。

### 後語

雖然我們可以在 tasks 的最後加個 task 來重開 web service，可當與 web service 相關的 tasks 的狀態皆為 ok 時，這種寫法會讓 web service 再次被重開。

透過 Handlers 我們可以**只在需要時重開**一次，進而**減少服務中斷的時間**。

## 相關連結

* [Intro to Playbooks | Ansible Documentation](https://docs.ansible.com/ansible/latest/user_guide/playbooks_intro.html)
* [Playbooks 介紹 | Ansible 中文權威指南](http://ansible-tran.readthedocs.io/en/latest/docs/playbooks_intro.html)
* [響應事件 (Handler) | Ansible入門](https://ansible-book.gitbooks.io/ansible-first-book/content/handler.html)

1. 一般都會用 Tasks 通知 (notify) Handlers 來述敘兩者的關係，但凍仁比較喜歡用 Tasks 關連於 Handlers 的說法。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://ansible.drx.tw/15.how-to-use-handlers-in-playbooks.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
