官术网_书友最值得收藏!

Understanding the playbook framework

A playbook allows you to manage multiple configurations and complex deployments on many machines simply and easily. This is one of the key benefits of using Ansible for the delivery of complex applications. With playbooks, you can organize your tasks in a logical structure as tasks are (generally) executed in the order they are written, allowing you to have a good deal of control over your automation processes. With that said, it is possible to perform tasks asynchronously, so where tasks are not executed in sequence, we will highlight this. Our goal is that once you complete this chapter, you will understand the best practices for writing your own Ansible playbooks.

Although YAML format is easy to read and write, it is very pedantic when it comes to spacing. For example, you cannot use tabs to set indentation even though on the screen a tab and four spaces might look identicalin YAML, they are not. We recommend that you adopt an editor with YAML support to aid you in writing your playbooks if you are doing this for the first time, perhaps Vim, Visual Studio Code, or Eclipse, as these will help you to ensure that your indentation is correct. To test the playbooks we develop in this chapter, we will reuse a variant of an inventory created in Chapter 3, Defining Your Inventory (unless stated otherwise):

[frontends]
frt01.example.com https_port=8443
frt02.example.com http_proxy=proxy.example.com

[frontends:vars]
ntp_server=ntp.frt.example.com
proxy=proxy.frt.example.com

[apps]
app01.example.com
app02.example.com

[webapp:children]
frontends
apps

[webapp:vars]
proxy_server=proxy.webapp.example.com
health_check_retry=3
health_check_interal=60

Let's dive right in and get started writing a playbook. In the section entitled Breaking down the Ansible components in Chapter 2, Understanding the Fundamentals of Ansible, we covered some of the basic aspects of a playbook so we won't repeat these in detail here, but rather build on them to show you what playbook development is all about:

  1. Create a simple playbook to run on the hosts in the frontends host group defined in our inventory file. We can set the user that will access the hosts using the remote_user directive in the playbook as demonstrated in the following (you can also use the --user switch on the command line, but as this chapter is about playbook development, we'll ignore that for now):
---
- hosts: frontends
remote_user: danieloh

tasks:
- name: simple connection test
ping:
remote_user: danieloh
  1. Add another task below the first to run the shell module (that will, in turn, run the ls command on the remote hosts). We'll also add the ignore_errors directive to this task to ensure that our playbook doesn't fail if the ls command fails (for example, if the directory we're trying to list doesn't exist). Be careful with the indentation and ensure it matches that of the first part of the file:
  - name: run a simple command
shell: /bin/ls -al /nonexistent
ignore_errors: True

Let's see how our newly created playbook behaves when we run it:

$ ansible-playbook -i hosts myplaybook.yaml

PLAY [frontends] ***************************************************************

TASK [Gathering Facts] *********************************************************
ok: [frt02.example.com]
ok: [frt01.example.com]

TASK [simple connection test] **************************************************
ok: [frt01.example.com]
ok: [frt02.example.com]

TASK [run a simple command] ****************************************************
fatal: [frt02.example.com]: FAILED! => {"changed": true, "cmd": "/bin/ls -al /nonexistent", "delta": "0:00:00.015687", "end": "2020-04-10 16:37:56.895520", "msg": "non-zero return code", "rc": 2, "start": "2020-04-10 16:37:56.879833", "stderr": "/bin/ls: cannot access /nonexistent: No such file or directory", "stderr_lines": ["/bin/ls: cannot access /nonexistent: No such file or directory"], "stdout": "", "stdout_lines": []}
...ignoring
fatal: [frt01.example.com]: FAILED! => {"changed": true, "cmd": "/bin/ls -al /nonexistent", "delta": "0:00:00.012160", "end": "2020-04-10 16:37:56.930058", "msg": "non-zero return code", "rc": 2, "start": "2020-04-10 16:37:56.917898", "stderr": "/bin/ls: cannot access /nonexistent: No such file or directory", "stderr_lines": ["/bin/ls: cannot access /nonexistent: No such file or directory"], "stdout": "", "stdout_lines": []}
...ignoring

PLAY RECAP *********************************************************************
frt01.example.com : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
frt02.example.com : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1

From the output of the playbook run, you can see that our two tasks were executed in the order in which they were specified. We can see that the ls command failed because we tried to list a directory that did not exist, but the playbook did not register any failed tasks because we set ignore_errors to true for this task (and only this task).

Most Ansible modules (with the exception of those that run user-defined commands such as shell, command, and raw) are coded to be idempotent, that is to say, if you run the same task twice, the results will be the same, and the task will not make the same change twiceif it detects that the action it is being requested to perform has been completed, then it does not perform it a second time. This, of course, is not possible for the aforementioned modules as they could be used to perform just about any conceivable task—hence, how could the module know it was being performed twice?

Every module returns a set of results and among these results is the task status. You can see these summarized at the bottom of the preceding playbook run output, and their meaning is as follows:

  • ok: The task ran successfully and no changes were made.
  • changed: The task ran successfully and a change was made.
  • failed: The task failed to run.
  • unreachable: The host was unreachable to run the task on.
  • skipped: This task was skipped.
  • ignored: This task was ignored (for example, in the case of ignore_errors).
  • rescued: We will see an example of this later when we look at blocks and rescue tasks.

These statuses can be very usefulfor example, if we have a task to deploy a new Apache configuration file from a template, we know we must restart the Apache service for the changes to be picked up. However, we only want to do this if the file was actually changedif no changes were made, we don't want to needlessly restart Apache as it would interrupt people who might be using the service. Hence, we can use the notify action, which tells Ansible to call a handler when (and only when) the result from a task is changed. In brief, a handler is a special type of task that is run as a result of a notify. However, unlike Ansible playbook tasks, which are performed in sequence, handlers are all grouped together and run at the very end of the play. Also, they can be notified more than once but will only be run once regardless, again preventing needless service restarts. Consider the following playbook:

---
- name: Handler demo 1
hosts: frt01.example.com
gather_facts: no
become: yes

tasks:
- name: Update Apache configuration
template:
src: template.j2
dest: /etc/httpd/httpd.conf
notify: Restart Apache

handlers:
- name: Restart Apache
service:
name: httpd
state: restarted

To keep the output concise, I've turned off fact-gathering for this playbook (we won't use them in any of the tasks). I'm also running this on just one host again for conciseness, but you are welcome to expand the demo code as you wish. If we run this task a first time, we will see the following results:

$ ansible-playbook -i hosts handlers1.yml

PLAY [Handler demo 1] **********************************************************

TASK [Update Apache configuration] *********************************************
changed: [frt01.example.com]

RUNNING HANDLER [Restart Apache] ***********************************************
changed: [frt01.example.com]

PLAY RECAP *********************************************************************
frt01.example.com : ok=2 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

Notice how the handler was run at the end, as the configuration file was updated. However, if we run this playbook a second time without making any changes to the template or configuration file, we will see something like this:

$ ansible-playbook -i hosts handlers1.yml

PLAY [Handler demo 1] **********************************************************

TASK [Update Apache configuration] *********************************************
ok: [frt01.example.com]

PLAY RECAP *********************************************************************
frt01.example.com : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

This time, the handler was not called as the result from the configuration task as OK. All handlers should have a globally unique name so that the notify action can call the correct handler. You could also call multiple handlers by setting a common name for using the listen directivethis way, you can call either the handler name or the listen stringas demonstrated in the following example:

---
- name: Handler demo 1
hosts: frt01.example.com
gather_facts: no
become: yes

handlers:
- name: restart chronyd
service:
name: chronyd
state: restarted
listen: "restart all services"
- name: restart apache
service:
name: httpd
state: restarted
listen: "restart all services"

tasks:
- name: restart all services
command: echo "this task will restart all services"
notify: "restart all services"

We only have one task in the playbook, but when we run it, both handlers are called. Also, remember that we said earlier that command was among a set of modules that were a special case because they can't detect whether a change has occurredas a result, they always return the changed value, and so, in this demo playbook, the handlers will always be notified:

$ ansible-playbook -i hosts handlers2.yml

PLAY [Handler demo 1] **********************************************************

TASK [restart all services] ****************************************************
changed: [frt01.example.com]

RUNNING HANDLER [restart chronyd] **********************************************
changed: [frt01.example.com]

RUNNING HANDLER [restart apache] ***********************************************
changed: [frt01.example.com]

PLAY RECAP *********************************************************************
frt01.example.com : ok=3 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0

These are some of the fundamentals that you need to know to start writing your own playbooks. With these under your belt, let's run through a comparison of ad hoc commands and playbooks in the next section.

主站蜘蛛池模板: 柘荣县| 巴塘县| 高要市| 龙陵县| 鹰潭市| 调兵山市| 常山县| 南汇区| 卢湾区| 高雄县| 太保市| 安顺市| 永济市| 卢湾区| 洛南县| 莆田市| 开鲁县| 黄浦区| 呈贡县| 宁陵县| 革吉县| 正宁县| 蚌埠市| 广元市| 石门县| 阳泉市| 竹山县| 灌南县| 常宁市| 蒲城县| 逊克县| 仁寿县| 西和县| 清远市| 贵德县| 西和县| 崇义县| 石嘴山市| 泸定县| 永新县| 永昌县|