Regular readers of Enable Sysadmin know that most of us are big fans of Ansible. We particularly like using Ansible roles to design reusable code effectively. A playbook follows a specific execution order when it runs, and there are several ways to control the order in which your tasks run. In this article, I'll look at two particularly useful Ansible features, pre_tasks and post_tasks. I'll walk you through some real (and simple) examples of how these features can add additional flexibility to your playbooks by executing tasks at different points during a playbook run.
First, I'll define pre_tasks and post_tasks. You've probably already guessed what they do. Defining pre_tasks in a playbook will cause those tasks to run before all other tasks, including roles. Defining post_tasks is the opposite—these tasks will run after all others, including any handlers defined by other tasks. It's a straightforward concept, and the following examples illustrate how you can put it to work in your environment.
Managing hosts in a load balancer
My favorite aspect of Ansible is its flexibility: You can use Ansible for configuration management and as an orchestration tool to interact with multiple systems during a playbook run. I will begin with a load-balancing example. You always want to minimize disruptions to production systems, and many architectures use load balancing to accomplish this. It's usually a good idea to pull a back-end system out of a load balancer when performing disruptive work and then add it back in once the work is done. Ansible's pre_tasks and post_tasks make this easy.
The following playbook will pull a server out of an HAProxy load balancer, run full patches with a reboot, and then re-add the server after the patch role completes:
$ cat patch-webservers.yml
---
- hosts: webservers
pre_tasks:
- name: Disable web server in load balancer
community.general.haproxy:
state: disabled
host: '{{ inventory_hostname_short }}'
fail_on_not_found: yes
delegate_to: loadbalancer.example.com
roles:
- full_patches
post_tasks:
- name: Enable web server in load balancer
community.general.haproxy:
state: enabled
host: '{{ inventory_hostname_short }}'
fail_on_not_found: yes
delegate_to: loadbalancer.example.com
Performing initial setup tasks
The pre_tasks section can also be useful for setting facts with values obtained at runtime. For example, imagine that some of your roles rely on knowing the latest available version of your software. You could get this information from your artifact server in the pre_tasks section and set a fact that can be accessed by the tasks in your roles. In this example, the latest_app_version fact is set by calling an API endpoint. Since the API call runs only if the latest_app_version isn't defined, it still allows a user to override this fact. The fact is then available to all of the roles in this playbook. It looks like this:
$ cat software-version.yml
---
- hosts: webservers
pre_tasks:
- name: Get latest software version from artifact server
ansible.builtin.uri:
url: http://artifact-server.example.com:8080/software/latest
return_content: yes
delegate_to: localhost
register: uri_output
when: latest_app_version is not defined
- name: Set latest_software_version fact
ansible.builtin.set_fact:
latest_app_version: "{{ uri_output.json.version }}"
when: latest_app_version is not defined
- name: Print latest_app_version
ansible.builtin.debug:
msg: "{{ latest_app_version }}"
roles:
- appserver
- apache
- monitored_host
If this code looks confusing, then check out my article about interacting with web endpoints using Ansible.
Muting a system in monitoring
The final example I will show is very similar to the load balancing example above. It's common to silence a host in a monitoring system prior to beginning any work on it. Once the job is complete, the host's monitoring can be reenabled. Ansible pre_tasks and post_tasks make this easy if your monitoring system supports HTTP requests, which most do:
$ cat patch-databases.yml
---
- hosts: database_servers
pre_tasks:
- name: Silence host in monitoring
ansible.builtin.uri:
url: "http://monitoring-server.example.com:8080/hosts/{{ inventory_hostname }}/schedule_downtime"
method: POST
body_format: json
body:
downtime_duration: 30m
delegate_to: localhost
roles:
- full_patches
post_tasks:
- name: Re-enable host in monitoring
ansible.builtin.uri:
url: "http://monitoring-server.example.com:8080/hosts/{{ inventory_hostname }}/clear_downtime"
method: POST
body_format: json
delegate_to: localhost
A word about includes
The previous examples include tasks defined directly in the playbook. While this might be fine for one or two tasks, it can quickly make your playbooks messy. It's also not very reusable: Different playbooks would need to duplicate these tasks.
[ Need more on Ansible? Take a free technical overview course from Red Hat. Ansible Essentials: Simplicity in Automation Technical Overview. ]
You can use Ansible's built-in include capability to create cleaner, more reusable code. In the example below, I've placed my monitoring tasks into their own directory within a top-level tasks/ directory. This makes my code much cleaner and allows other playbooks to reuse these tasks:
$ cat patch-databases-with-include.yml
---
- hosts: database_servers
pre_tasks:
- name: Silence host in monitoring
ansible.builtin.include: tasks/monitoring/silence-host.yml
roles:
- full_patches
post_tasks:
- name: Enable host in monitoring
ansible.builtin.include: tasks/monitoring/enable-host.yml
Note that this tasks/ directory is in the same directory as my main playbook and not the tasks/ directory under any particular role. To make this point clearer, the directory structure looks like this:
$ tree --noreport
├── ansible.cfg
├── inventory.ini
├── patch-databases-with-include.yml
├── patch-databases.yml
├── patch-webservers.yml
├── roles
│ ├── apache
│ │ └── tasks
│ │ └── main.yml
│ ├── appserver
│ │ └── tasks
│ │ └── main.yml
│ ├── full_patches
│ │ └── tasks
│ │ └── main.yml
│ └── monitored_host
│ └── tasks
│ └── main.yml
├── software-version.yml
├── tasks
│ └── monitoring
│ ├── enable-host.yml
│ └── silence-host.yml
└── templates
└── hosts.j2
Wrapping Up
In this article, I walked you through a few examples of how Ansible's pre_tasks and post_tasks functionality makes it easy to perform actions at the beginning and end of your playbooks. This functionality can be useful for anything from silencing hosts in monitoring to sending alerts to your internal chat tools on successful playbook runs. I'm sure you will find interesting uses for this functionality as you build out more complex playbooks in your own organization.
[ Learn more about server and configuration management by downloading the free eBook Ansible for DevOps. ]
저자 소개
Anthony Critelli is a Linux systems engineer with interests in automation, containerization, tracing, and performance. He started his professional career as a network engineer and eventually made the switch to the Linux systems side of IT. He holds a B.S. and an M.S. from the Rochester Institute of Technology.
유사한 검색 결과
Streamline your work with the new learning drawer in the migration toolkit for virtualization
End of Maintenance Support for Ansible Automation Platform 2.4
Technically Speaking | Taming AI agents with observability
A composable industrial edge platform | Technically Speaking
채널별 검색
오토메이션
기술, 팀, 인프라를 위한 IT 자동화 최신 동향
인공지능
고객이 어디서나 AI 워크로드를 실행할 수 있도록 지원하는 플랫폼 업데이트
오픈 하이브리드 클라우드
하이브리드 클라우드로 더욱 유연한 미래를 구축하는 방법을 알아보세요
보안
환경과 기술 전반에 걸쳐 리스크를 감소하는 방법에 대한 최신 정보
엣지 컴퓨팅
엣지에서의 운영을 단순화하는 플랫폼 업데이트
인프라
세계적으로 인정받은 기업용 Linux 플랫폼에 대한 최신 정보
애플리케이션
복잡한 애플리케이션에 대한 솔루션 더 보기
가상화
온프레미스와 클라우드 환경에서 워크로드를 유연하게 운영하기 위한 엔터프라이즈 가상화의 미래