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.

UI_Icon-Red_Hat-Close-A-Black-RGB

채널별 검색

automation icon

오토메이션

기술, 팀, 인프라를 위한 IT 자동화 최신 동향

AI icon

인공지능

고객이 어디서나 AI 워크로드를 실행할 수 있도록 지원하는 플랫폼 업데이트

open hybrid cloud icon

오픈 하이브리드 클라우드

하이브리드 클라우드로 더욱 유연한 미래를 구축하는 방법을 알아보세요

security icon

보안

환경과 기술 전반에 걸쳐 리스크를 감소하는 방법에 대한 최신 정보

edge icon

엣지 컴퓨팅

엣지에서의 운영을 단순화하는 플랫폼 업데이트

Infrastructure icon

인프라

세계적으로 인정받은 기업용 Linux 플랫폼에 대한 최신 정보

application development icon

애플리케이션

복잡한 애플리케이션에 대한 솔루션 더 보기

Virtualization icon

가상화

온프레미스와 클라우드 환경에서 워크로드를 유연하게 운영하기 위한 엔터프라이즈 가상화의 미래