1. Implement handling procedures
1.1 ansible handler
Ansible modules are designed to be idempotent. This means that in a properly written playbook, the playbook and its tasks can run multiple times without changing the managed host unless changes are needed to bring the managed host into the desired state.
However, when the task does change the system, it may be necessary to run further tasks. For example, changing a service profile may require that the service be reloaded for its changed configuration to take effect.
A handler is a task that responds to notifications triggered by other tasks. The task notifies its handler only when it changes something on the managed host. Each handler has a globally unique name and is triggered at the end of the task block in the playbook. If no task notifies the handler by name, the handler will not run. If one or more tasks notify the handler, the handler runs once after all other tasks in play are completed. Because handlers are tasks, you can use the modules they will use for any other task in the handler. Typically, the handler is used to reboot the host and restart the service.
A handler can be considered an inactive task and is triggered only when explicitly invoked with a notify statement. In the following code snippet, the restart httpd handler will restart the Apache server only if the configuration file is updated and the task is notified:
--- - host: 192.168.101.200 tasks: - name: install apache yum: name: httpd state: present - name: service httpd service: name: httpd state: restarted enabled: yes - name: config httpd copy: src: files/httpd.conf dest: /etc/httpd/conf.d/httpd.conf notify: - restart httpd handlers: - name: restart httpd service: name: httpd state: restarted
tasks: - name: config httpd template: src: files/httpd.conf dest: /etc/httpd/conf.d/httpd.conf notify: - restart mysql - restart apache handlers: - name: restart mysql service: name: mariadb state: restarted - name: restart apache service: name: httpd state: restarted
1.2 benefits of using handlers
There are several important things to keep in mind when using handlers:
- Handlers always run in the order specified in the handlers section of play. They do not run in the order listed by the notify statement in the task, or in the order notified to them by the task.
- The handler typically runs after all other tasks in the associated play have been completed. The handler called by a task in the tasks section of the playbook will not run until all tasks under the tasks have been processed.
- The handler name exists in each play namespace. If two handlers are mistakenly given the same name, only one will run.
- Even if there are multiple task notification handlers, the handler runs only once. If there is no task notification handler, it will not run.
- If the task that contains the notify statement does not report the changed result (for example, the package is installed and the task reports ok), the handler is not notified. The handler will be skipped until it is notified by another task. Ansible notifies the handler only if the related task reports the changed status.
- The handler is used to perform additional actions when the task makes changes to the managed host. They should not be used as a substitute for normal tasks.
2. Failed to process task
2.1 manage error tasks in play
Ansible evaluates the return code of the task to determine whether the task succeeded or failed. Generally speaking, when a task fails, ansible will immediately abort the rest of the play on the host and skip all subsequent tasks.
But sometimes, you may want to continue play ing even if the task fails. For example, you might expect a pending task to fail and want to fix it by conditionally running some other task.
Ansible has several functions to manage task errors.
2.2 ignore task failure
By default, play aborts when a task fails. However, you can override this behavior by ignoring failed tasks. Ignore can be used in tasks_ Errors keyword to achieve this.
The following code snippet demonstrates how to use ignore in a task_ Errors to continue to execute playbook on the host if the task fails. For example, if the httpd package does not exist, the yum module will fail, but ignore_ If errors is set to yes, execution will continue.
--- - host: 192.168.101.200 tasks: - name: install apache yum: name: httpd state: present ignore_errors: yes
3.3 enforce handler after task failure
In general, if a task fails and play is aborted on that host, the handler that receives notification of an earlier task in play will not run. If force is set in play_ Handlers: Yes keyword, the notified handler will be called even if play is aborted due to subsequent task failure.
The following code snippet demonstrates how to use force in play_ Handlers keyword. Even if the installation fails because there is no abcdefg such service, the restart task in handlers will not stop:
- host: 192.168.101.200 force_handlers: yes tasks: - name: config httpd copy: src: files/httpd.conf dest: /etc/httpd/conf.d/httpd.conf notify: - restart httpd - name: install abcdefg yum: name: abcdefg state: present handlers: - name: restart httpd service: name: httpd state: restarted
Remember that the handler is notified when the task reports the changed result, but not when the task reports the ok or failed result.
2.4 failed to specify task conditions
Failed can be used in tasks_ When keyword to specify a condition indicating that the task has failed. This is usually used in conjunction with command modules, which may successfully execute a command, but the output of the command may indicate failure.
For example, you can run a script that outputs an error message and use that message to define the failure status of a task. The following code snippet demonstrates how to use failed in a task_ When keyword:
tasks: - name: Run user creation script shell: /usr/local/bin/create_users.sh register: command_result failed_when: "'Password missing' in command_result.stdout"
The fail module can also be used to force a task to fail. The above scenario can also be written as two tasks:
tasks: - name: Run user creation script shell: /usr/local/bin/create_users.sh register: command_result ignore_errors: yes - name: Report script failure fail: msg: "The password is missing in the output" when: "'Password missing' in command_result.stdout"
We can use the fail module to provide a clear failure message for the task. This method also supports deferred failures, allowing intermediate tasks to be run to complete or roll back other changes.
2.5 specify when the task reports "Changed" results
When the task makes changes to the managed host, it reports the changed status and notifies the handler. If the task does not require changes, it reports ok and does not notify the handler.
changed_ The when keyword can be used to control when a task reports that it has changed. For example, the shell module in the next example will be used to obtain Kerberos credentials for subsequent tasks. It usually always reports changed at run time. To resist this change, set changed_when: false so that it only reports ok or failed.
- name: get Kerberos credentials as "admin" shell: echo "{{ krb_admin_pass }}" | kinit -f admin changed_when: false
The following example uses a shell module to report changed based on the output of the module collected through registered variables:
tasks: - shell: cmd: /usr/local/bin/upgrade-database register: command_result changed_when: "'Success' in command_result.stdout" notify: - restart_database handlers: - name: restart_database service: name: mariadb state: restarted
2.6 ansible blocks and error handling
In playbook, blocks are clauses that logically group tasks, and can be used to control how tasks are executed. For example, a task block can contain the when keyword to apply a condition to multiple tasks:
- name: block example hosts: 172.16.103.129 tasks: - name: installing and configuring Yum versionlock plugin block: - name: package needed by yum yum: name: yum-plugin-versionlock state: present - name: lock version of tadata lineinfile: dest: /etc/yum/pluginconf.d/versionlock.list line: tzdata-2020j-1 state: present when: ansible_distribution == "Redhat"
Through the block, you can also combine the rescue and always statements to handle errors. If any task in the block fails, perform the task in its rescue block to recover. After the task in the block clause and the task in the rescue clause (if there is a failure) run, the task in the always clause runs. Summary:
- block: defines the main task to run
- rescue: defines the task to run when the task defined in the block clause fails
- Always: defines tasks that always run independently, regardless of whether the tasks defined in the block and rescue clauses succeed or fail
The following example demonstrates how to implement blocks in playbook. Even if the task defined in the block clause fails, the task defined in the rescue and always clauses will execute.
tasks: - name: Upgrade DB block: - name: upgrade the database shell: cmd: /usr/local/lib/upgrade-database rescue: - name: revert the database upgrade shell: cmd: /usr/local/lib/revert-database always: - name: always restart the database service: name: mariadb state: restarted
when condition in block is also applied to its rescue and always clauses, if any.