Ansible Playbook Advance Features
Hello Everyone
Welcome to CloudAffaire and this is Debjeet.
In the last blog post, we have discussed roles in Ansible.
https://cloudaffaire.com/roles-in-ansible/
In this blog post, we will discuss some advance features of Ansible Playbook.
Ansible Playbook Advance Features:
Privilege Escalation in playbook:
Ansible allows you to ‘become’ another user, different from the user that logged into the machine (remote user). This is done using existing privilege escalation tools such as sudo, su, pfexec, doas, pbrun, dzdo, ksu, runas, machinectl and others.
Privilege can be escalated in three ways:
- Using command-line options
- Using become directives
- Using connection variables
Using command-line options: You can directly escalate privilage using command line options.
- –ask-become-pass, -K: ask for privilege escalation password; does not imply become will be used.
- –become, -b: run operations with become (no password implied)
- –become-method=BECOME_METHOD: privilege escalation method to use (default=sudo), valid choices: [ sudo | su | pbrun | pfexec | doas | dzdo | ksu | runas | machinectl ]
- –become-user=BECOME_USER: run operations as this user (default=root), does not imply –become/-b
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 |
##---------------------------------------- ## Ansible Playbook: Advance Features ## ##---------------------------------------- ## Systems used for this demo # hostnames ip os role # --------- ------------ -------- ------------ # system1 192.168.0.10 Centos 7 Control Node # system2 192.168.0.20 Centos 7 Managed Node One # system3 192.168.0.30 Centos 7 Managed Node Two ############################################# ## Ansible Playbook : Privilege Escalation ## ############################################# ## Update host file sudo vi /etc/ansible/hosts -------------------------- [localserver] system1 [webserver] system2 -------------------------- :wq ## Create a playbook vi myplaybook.yml -------------------------- --- - hosts: webserver remote_user: debjeet tasks: - name: installs httpd service yum: name: httpd state: latest ... -------------------------- :wq ## Execute the playbook normally ansible-playbook myplaybook.yml ##error: You need to be root to perform this command ## Method 1: Command line options ## ## Execute the playbook with command line option become ansible-playbook myplaybook.yml --become |
Note: become directive will let you execute using root privilege.
Using become directives: You can also escalate privilege using become directive in your playbook.
- become: set to yes to activate privilege escalation.
- become_user: set to user with desired privileges — the user you become, NOT the user you login as. Does NOT imply become: yes, to allow it to be set at host level.
- become_method: (at play or task level) overrides the default method set in ansible.cfg, set to use any of the Become Plugins.
- become_flags: (at play or task level) permit the use of specific flags for the tasks or role.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
## Method 2: become Directives ## ## Edit the playbook vi myplaybook.yml -------------------------- --- - hosts: webserver remote_user: debjeet become: yes tasks: - name: start httpd service service: name: httpd state: started ... -------------------------- :wq ## Execute the playbook ansible-playbook myplaybook.yml |
Using connection variables: You can also escalate privilege using connection variables (host\group variables) in your inventory file.
- ansible_become: equivalent of the become directive, decides if privilege escalation is used or not.
- ansible_become_method: which privilege escalation method should be used
- ansible_become_user: set the user you become through privilege escalation; does not imply ansible_become: yes
- ansible_become_password: set the privilege escalation password.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
## Method 3: Connection variables ## ## Update host file sudo vi /etc/ansible/hosts -------------------------- [localserver] system1 [webserver] system2 ansible_become=yes -------------------------- :wq ## Edit the playbook vi myplaybook.yml -------------------------- --- - hosts: webserver remote_user: debjeet tasks: - name: remove httpd service yum: name: httpd state: absent ... -------------------------- :wq ## Execute the playbook ansible-playbook myplaybook.yml |
Asynchronous Actions and Polling in playbook:
By default tasks in playbooks block, meaning the connections stay open until the task is done on each node. This may not always be desirable, or you may be running operations that take longer than the SSH timeout. To avoid blocking or timeout issues, you can use asynchronous mode to run all of your tasks at once and then poll until they are done. The behavior of asynchronous mode depends on the value of poll.
Avoid connection timeouts: poll > 0
When poll is a positive value, the playbook will still block on the task until it either completes, fails or times out. In this case, however, async explicitly sets the timeout you wish to apply to this task rather than being limited by the connection method timeout.
Concurrent tasks: poll = 0
When poll is 0, Ansible will start the task and immediately move on to the next one without waiting for a result. From the point of view of sequencing this is asynchronous programming: tasks may now run concurrently. The playbook run will end without checking back on async tasks. The async tasks will run until they either complete, fail or timeout according to their async value. If you need a synchronization point with a task, register it to obtain its job ID and use the async_status module to observe it.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
######################################################### ## Ansible Playbook : Asynchronous Actions and Polling ## ######################################################### ## Enable task timer sudo vi /etc/ansible/ansible.cfg --------------------- callback_whitelist = profile_tasks --------------------- :wq ## Edit the ansible host file sudo vi /etc/ansible/hosts -------------------------- [localserver] system1 [webserver] system2 -------------------------- :wq ## Edit the playbook playbook vi myplaybook.yml -------------------------- --- - hosts: all gather_facts: false remote_user: debjeet tasks: - name: Task1 will wait command: /bin/sleep 50 - name: Task2 also waits for Task1 to get completed command: echo cloudaffaire ... -------------------------- :wq ## Execute the playbook ansible-playbook myplaybook.yml ## Edit the playbook vi myplaybook.yml -------------------------- --- - hosts: all gather_facts: false remote_user: debjeet tasks: - name: Task1 will wait command: /bin/sleep 50 async: 60 poll: 0 register: status - name: Task2 does not waits for Task1 to get completed command: echo cloudaffaire - name: Task1 status async_status: jid: "{{ status.ansible_job_id }}" register: job_result until: job_result.finished retries: 10 ... -------------------------- :wq ## Execute the playbook ansible-playbook myplaybook.yml |
Observe: In the second play, Task2 does not wait for Task 1 completion. If you get any error, increase the retries value.
Dryrun (Checks) in playbook:
When ansible-playbook is executed with –check it will not make any changes on remote systems. Instead, any module instrumented to support ‘check mode’ will report what changes they would have made rather than making them. Other modules that do not support check mode will also take no action, but just will not report what changes they might have made. Check mode is just a simulation, and if you have steps that use conditionals that depend on the results of prior commands, it may be less useful for you. However, it is great for one-node-at-time basic configuration management use cases.
The –diff option to ansible-playbook works great with –check (detailed above) but can also be used by itself. When this flag is supplied and the module supports this, Ansible will report back the changes made or, if used with –check, the changes that would have been made. This is mostly used in modules that manipulate files (i.e. template) but other modules might also show ‘before and after’ information (i.e. user). Since the diff feature produces a large amount of output, it is best used when checking a single host at a time.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
############################## ## Ansible Playbook: Dryrun ## ############################## ## Create a file echo mycontent > myfile.txt ## Edit the playbook vi myplaybook.yml -------------------------- --- - hosts: webserver remote_user: debjeet gather_facts: false tasks: - name: copy myfile from system1 to system2 copy: src: /home/debjeet/myfile.txt dest: /home/debjeet/myfile.txt owner: debjeet group: debjeet mode: '0777' ... -------------------------- :wq ## Execute the playbook ansible-playbook myplaybook.yml --check --diff |
Note: The file will not actually get copied, just a check is performed and changes are reported if the file would have been copied.
Debugger in playbook:
Ansible includes a debugger as part of the strategy plugins. This debugger enables you to debug as task. You have access to all of the features of the debugger in the context of the task. You can then, for example, check or set the value of variables, update module arguments, and re-run the task with the new variables and arguments to help resolve the cause of the failure.
Ways to invoke debugger:
- always: Always invoke the debugger, regardless of the outcome
- never: Never invoke the debugger, regardless of the outcome
- on_failed: Only invoke the debugger if a task fails
- on_unreachable: Only invoke the debugger if the a host was unreachable
- on_skipped: Only invoke the debugger if the task is skipped
Available debugging commands:
- p(print) task/task_vars/host/result: Print values used to execute a module
- task.args[key] = value: Update module’s argument
- task_vars[key] = value: Update task_vars
- u(pdate_task): re-creates the task from the original task data structure, and templates with updated task_vars
- r(edo): Run the task again
- c(ontinue): Just continue
- q(uit): Quit from the debugger
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
################################# ## Ansible Playbook : Debugger ## ################################# ## Edit the playbook vi myplaybook.yml -------------------------- --- - hosts: system1 debugger: on_failed gather_facts: false vars: myvar1: myvalue1 tasks: - name: wrong variable myvar2, correct one myvar1 debug: msg: "{{ myvar2 }}" ... -------------------------- :wq ## Execute the playbook ansible-playbook myplaybook.yml ## Debug the play and correct the error ## #[system1] TASK: wrong variable myvar2, correct one myvar1 (debug)> p result._result #[system1] TASK: wrong variable myvar2, correct one myvar1 (debug)> p task.args #[system1] TASK: wrong variable myvar2, correct one myvar1 (debug)> task.args['msg'] = '{{ myvar1 }}' ##[system1] TASK: wrong variable myvar2, correct one myvar1 (debug)> redo |
Rolling update in playbook:
By default, Ansible will try to manage all of the machines referenced in a play in parallel. For a rolling update use case, you can define how many hosts Ansible should manage at a single time by using the serial keyword. The serial keyword can also be specified as a percentage, which will be applied to the total number of hosts in a play, in order to determine the number of hosts per pass.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
####################################### ## Ansible Playbook : Rolling Update ## ####################################### ## Edit the host file sudo vi /etc/ansible/hosts -------------------------- [myserver] system1 system2 system3 -------------------------- :wq ## Edit the playbook vi myplaybook.yml -------------------------- --- - hosts: myserver gather_facts: false remote_user: debjeet serial: 2 tasks: - shell: echo $HOSTNAME register: result - debug: msg: "Task being executed in {{ result.stdout }}" ... -------------------------- :wq ## Execute the playbook ansible-playbook myplaybook.yml |
Error Handling in playbook:
Ansible normally has defaults that make sure to check the return codes of commands and modules and it fails fast – forcing an error to be dealt with unless you decide otherwise. Sometimes a command that returns different than 0 isn’t an error. Sometimes a command might not always need to report that it ‘changed’ the remote system. You can control all this behavior in your playbook using error handling.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
####################################### ## Ansible Playbook : Error Handling ## ####################################### ## Edit the playbook vi myplaybook.yml -------------------------- --- - hosts: localhost gather_facts: false tasks: - name: Error in this task will be ignored command: /bin/false ignore_errors: yes - name: example of single failed_when conditions when error output prints FAILED shell: "./myfile" register: result1 failed_when: "'FAILED' in result1.stderr" - name: example of many failed_when conditions with OR shell: "./myfile" register: result2 failed_when: > ("No such file or directory" in result2.stdout) or (result2.stderr != '') or (result2.rc == 10) ... -------------------------- :wq ## Execute the playbook ansible-playbook myplaybook.yml ## Edit the playbook vi myplaybook.yml -------------------------- --- - hosts: localhost gather_facts: false tasks: - name: This task will report not change command: echo hello world changed_when: False - name: Error Handling using block block: - debug: msg: 'I execute normally' - name: i force a failure command: /bin/false - debug: msg: 'I never execute, due to the above task failing, :-(' rescue: - debug: msg: 'I caught an error, can do stuff here to fix it, :-)' -------------------------- :wq ## Execute the playbook ansible-playbook myplaybook.yml |
Prompt in playbook:
When running a playbook, you may wish to prompt the user for certain input, and can do so with the ‘vars_prompt’ section. A common use for this might be for asking for sensitive data that you do not want to record. This has uses beyond security, for instance, you may use the same playbook for all software releases and would prompt for a particular release version in a push-script.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
################################ ## Ansible Playbook : Prompts ## ################################ ## Edit the playbook vi myplaybook.yml -------------------------- --- - hosts: localhost gather_facts: false vars_prompt: - name: username prompt: "What is your username?" private: no - name: password prompt: "What is your password?" private: yes encrypt: "sha512_crypt" confirm: yes salt_size: 7 tasks: - debug: msg: 'Hello {{ username }}, welcome to cloudaffaire' - debug: msg: 'Your encrypted password: {{ password }}' ... -------------------------- :wq ## Execute the playbook ansible-playbook myplaybook.yml |
Tags in playbook:
If you have a large playbook, it may become useful to be able to run only a specific part of it rather than running everything in the playbook. Ansible supports a “tags:” attribute for this reason.
When you execute a playbook, you can filter tasks based on tags in two ways:
- On the command line, with the –tags or –skip-tags options
- In Ansible configuration settings, with the TAGS_RUN and TAGS_SKIP options
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
############################# ## Ansible Playbook : Tags ## ############################# ## Edit the playbook vi myplaybook.yml -------------------------- --- - hosts: localhost gather_facts: false tasks: - debug: msg: 'Task 1 with tag 1' tags: - tag1 - debug: msg: 'Task 2 with tag 2' tags: - tag2 - debug: msg: 'Task 3 with tag 3' tags: - tag3 - debug: msg: 'Task 4 always execute unless specified --skip-tags always' tags: - always - debug: msg: 'Task 5 never execute unless specifically requested' tags: - never ... -------------------------- :wq ## Execute the playbook with task1, task2 and task4 ansible-playbook myplaybook.yml --tags "tag1,tag2" ## Execute the playbook with task2, task3 and task4 ansible-playbook myplaybook.yml --skip-tags "tag1" ## Execute the playbook with task5 ansible-playbook myplaybook.yml --tags "never" --skip-tags "always" |
Start and Step in playbook:
If you want to start executing your playbook at a particular task, you can do so with the –start-at-task option. Playbooks can also be executed interactively with –step option.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
####################################### ## Ansible Playbook : Start and Step ## ####################################### ## Edit the playbook vi myplaybook.yml -------------------------- --- - hosts: localhost gather_facts: false tasks: - name: step1 debug: msg: 'task at step1' - name: step2 debug: msg: 'task at step2' - name: step3 debug: msg: 'task at step3' ... -------------------------- :wq ## Execute the playbook from task named "step2" ansible-playbook myplaybook.yml --start-at-task="step2" ## Execute the playbook intercatively ansible-playbook myplaybook.yml --step |
Hope you have enjoyed this article. It is not possible to cover all the playbook advance feature in one blog post. May be in future, I will divide this in two-part series and cover all.
To get a complete list of available playbook features, please refer below Ansible documentation
https://docs.ansible.com/ansible/latest/user_guide/playbooks_special_topics.html
https://docs.ansible.com/ansible/latest/reference_appendices/playbooks_keywords.html
To get more details on Ansible, please refer below Ansible documentation.