Ansible Playbook development: Step-by-step guide
Tags: ansible, ansibleplaybook, ansibledevelopment
Published: 2023-12-11 12:38:05

Ansible is a powerful automation tool for managing and configuring systems and devices. With Ansible Playbooks, you can automate tasks from simple configuration changes to complex deployments. Simply describe the desired state of your infrastructure and Ansible will make it happen. But writing reliable, maintainable and secure playbooks takes practice.

In this blog post, we’ll explore a good developer flow for writing Ansible Playbooks and we’ll do it **with a practical example: configuring and starting a documentation server.**

Let’s jump right in!

## 1. Start with a plan
Before you dive in to writing, take some time to think about what your playbook needs to do. What tasks do you need to automate? What hosts or groups of hosts do you need to apply the playbook to? Once you have a good understanding of your requirements, you can start to break down the playbook into smaller, more manageable tasks.

## 2. Write a diagram
When you have all your answers, write up a high-level diagram of what your playbook should do. This will help you visualize all the steps involved in the playbook and identify any potential dependencies.

When making a basic diagram of an Ansible Playbook, remember it should:

* Be easy to understand and not cluttered with too much detail.
* Show the overall flow of the playbook, including the steps involved and any dependencies between them.
* Identify any key decision points or branching logic.
* Use clear and concise labels.

In our case, the diagram looks like this:

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sbj3ehm2pe99pxyj02rh.png)

## 3. Find the right collections and approved modules
After you’ve visualized your playbook, it’s time to research available modules, the building blocks of your automation. There are many different modules available, covering a wide range of tasks, and you can find them on Ansible Galaxy. But unfortunately, there’s no way of telling if the examples are actually of high-quality, meaning there’s no guarantee that they will do what they’re supposed to do, or even if they are actually safe to use in your environment.

For better assurance, use the **[Complete Ansible Collections Documentation](https://docs.steampunk.si/plugins/)** generated by XLAB Steampunk team of Ansible experts. This documentation includes examples that have been checked for correctness and can be safely used in your playbooks.

## 4. Write the playbook
Once you have a good understanding of the tasks you need to automate and are sure that the collections and modules you’re about to include in your playbook are high-quality, you can start writing your playbook. Be sure to organize it into distinct tasks, structured plays, and well-defined variables and conditionals to ensure your playbooks are clear, maintainable and efficient.

Check out the **[Ultimate guide for high-quality Ansible Playbooks](https://steampunk.si/blog/ultimate-guide-for-high-quality-ansible-playbooks/)** to get started. Or get some **[help from AI to speed things up](https://steampunk.si/blog/ai-powered-ansible-playbooks/)** even more.

In our case, the playbook looks like this:

```
---
- name: An example playbook
  hosts: localhost
  vars:
    documentation_user:
      username: "documento"
      home_dir: "/home/documento"
      doc_dir: "/home/documento/www-data"
  tasks:
    - name: Install prerequisite packages
      ansible.builtin.package:
        name:
          - nginx
          - python3-pip
          - rssh
        state: present

    - name: Create group
      ansible.builtin.group:
        name: "{{ documentation_user.username }}"
        state: present

    - name: Create a login user
      ansible.builtin.user:
        name: "{{ documentation_user.username }}"
        group: "{{ documentation_user.username }}"
        state: present
        shell: /usr/bin/rssh
        system: "no"
        createhome: true
        home: "{{ documentation_user.home_dir }}"

    - name: Allow deployment pipeline access as the documentation user
      ansible.posix.authorized_key:
        user: "{{ documentation_user.username }}"
        state: present
        key: "{{ lookup('file', 'keys/documentation_ecdsa.pub') }}"

    - name: Create directory for documentation files
      ansible.builtin.file:
        path: "{{ documentation_user.doc_dir }}"
        state: directory
        owner: "{{ documentation_user.username }}"
        group: "{{ documentation_user.username }}"

    - name: Install Ansible PKI prerequisites.
      ansible.builtin.pip: name=pyOpenSSL

    - name: Generate an OpenSSL private key
      openssl_privatekey:
        path: /etc/ssl/private/documentation.key
      register: openssl_private_key_result

    - name: Generate an OpenSSL Certificate Signing Request
      openssl_csr:
        path: /etc/ssl/private/documentation.csr
        privatekey_path: /etc/ssl/private/documentation.key
      register: openssl_csr_result

    - name: Generate a Self Signed OpenSSL certificate
      openssl_certificate:
        path: /etc/ssl/certs/documentation.crt
        privatekey_path: /etc/ssl/private/documentation.key
        csr_path: /etc/ssl/private/documentation.csr
        provider: selfsigned
      register: openssl_crt_result

    - name: Deploy the rssh config file
      ansible.builtin.template:
        src: rssh.conf
        dest: /etc/rssh.conf
        mode: "0644"

    - name: Deploy the nginx config file
      action:
        module: ansible.builtin.template
        src: nginx.conf
        dest: /etc/nginx/nginx.conf
        validate: "nginx -t -c %s"
        mode: "0644"
      register: nginx_conf_result

    - ansible.builtin.service:
        name: nginx
        state: restarted
      when: openssl_private_key_result.changed or openssl_csr_result.changed or openssl_crt_result.changed or nginx_conf_result.changed
```

## 5. Test your playbook
Even if you’ve mapped out your playbook, dove deep into module research, and poured your heart and soul into writing your playbook, does that guarantee it’ll work? Well, sorry to break it to you, but nope! But don’t fret, there are awesome tools out there to whip your playbooks into shape in no time.

### Check it with Ansible Lint
First, always use Ansible Lint. Lint is a tool that will do a basic check of your playbook for syntax errors and general playbook writing best practices. Ansible Lint will make sure your playbook is correctly written, which is the basic requirement for your playbook to work.

Let’s lint the playbook.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vdjlc16bpunrb1oemhu7.png)

As we can see, Lint exited at first error, since it did not recognize the ansible.posix collection, which we have not installed. Which is okay as Lint isn’t meant to dive deep into playbook analysis but only take care of the basics. But just because the syntax is correct doesn’t mean your playbook will actually do what it’s supposed to. So, here’s another tool to the rescue.

### Check it with Steampunk Spotter
Steampunk Spotter performs a thorough in-depth playbook analysis, focusing on security to ensure that the playbooks meet the highest standards and are easy to maintain. It’s like having an Ansible expert by your side, checking your playbooks and guiding you to perfection.

Let’s run a Spotter scan.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ok92yablhb8qpf3nhw2k.png)

As we can see, Spotter gave us valuable information to work with. Most of the issues we are dealing with can be fixed using the rewrite function. Let’s do that using spotter scan `--rewrite playbook.yml command.`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7lyynsqdn0vhe8upq7sn.png)

We fixed most of the issues we were dealing with, and Spotter nicely shows precisely what was done and which checks still remain. We are left with only some hints and warnings which are quite easy to fix manually, and do not take a lot of time, but are very important if we want to follow best practices.

Spotter also generated a requirements file for us, so let’s go ahead and install it.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/58npuuk0lp8owtqwkqnf.png)

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ttyvqwde7piklg8q17ot.png)

Let’s check the playbook with Spotter to see if we cleared up all the issues.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s6gjcdo2gpx8q1v8kb52.png)

Spotter says we’re good to go.

With testing complete, it’s time for the production deployment. Let’s rerun Ansible Lint to see if the playbook is production ready.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g4or0miec0104bbixvtx.png)

And there we go; our playbook is ready for production.

## 6. Deploy playbook to production
Once you have tested your playbook thoroughly in the test environment, you can deploy it to production. Putting your playbooks into action on real systems is a big deal in Ansible Playbook development. It’s crucial to watch closely and double-check to make sure everything is working right, and your system is performing its best.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2jplnetp4apo7ukqeonh.png)

Here is the finished playbook.

```
---
- name: Prepare and deploy documentation server
  hosts: localhost
  vars:
    documentation_user:
      username: "documento"
      home_dir: "/home/documento"
      doc_dir: "/home/documento/www-data"
  tasks:
    - name: Install prerequisite packages
      ansible.builtin.package:
        name:
          - nginx
          - python3-pip
          - rssh
        state: present

    - name: Create group
      ansible.builtin.group:
        name: "{{ documentation_user.username }}"
        state: present

    - name: Create a login user
      ansible.builtin.user:
        name: "{{ documentation_user.username }}"
        group: "{{ documentation_user.username }}"
        state: present
        shell: /usr/bin/rssh
        system: false
        create_home: true
        home: "{{ documentation_user.home_dir }}"

    - name: Allow deployment pipeline access as the documentation user
      ansible.posix.authorized_key:
        user: "{{ documentation_user.username }}"
        state: present
        key: "{{ lookup('file', 'keys/documentation_ecdsa.pub') }}"

    - name: Create directory for documentation files
      ansible.builtin.file:
        path: "{{ documentation_user.doc_dir }}"
        state: directory
        owner: "{{ documentation_user.username }}"
        group: "{{ documentation_user.username }}"
        mode: "0777"

    - name: Install Ansible PKI prerequisites.
      ansible.builtin.pip:
        name: pyOpenSSL

    - name: Generate an OpenSSL private key
      community.crypto.openssl_privatekey:
        path: /etc/ssl/private/documentation.key
      register: openssl_private_key_result

    - name: Generate an OpenSSL Certificate Signing Request
      community.crypto.openssl_csr:
        path: /etc/ssl/private/documentation.csr
        privatekey_path: /etc/ssl/private/documentation.key
      register: openssl_csr_result

    - name: Generate a Self Signed OpenSSL certificate
      community.crypto.x509_certificate:
        path: /etc/ssl/certs/documentation.crt
        privatekey_path: /etc/ssl/private/documentation.key
        csr_path: /etc/ssl/private/documentation.csr
        provider: selfsigned
      register: openssl_crt_result

    - name: Deploy the rssh config file
      ansible.builtin.template:
        src: rssh.conf
        dest: /etc/rssh.conf
        mode: "0644"

    - name: Deploy the nginx config file
      ansible.builtin.template:
        src: nginx.conf
        dest: /etc/nginx/nginx.conf
        validate: "nginx -t -c %s"
        mode: "0644"
      register: nginx_conf_result

    - name: Restart nginx
      ansible.builtin.service:
        name: nginx
        state: restarted
      when: openssl_private_key_result.changed or openssl_csr_result.changed or openssl_crt_result.changed or nginx_conf_result.changed
```

## 7. Document your playbook
Once you have tested and deployed your playbook, take some time to thoroughly document it. The future you will be thankful ;)

Creating clear documentation for your Ansible Playbooks has several advantages. It helps everyone involved to better understand and use them, improving overall collaboration and efficiency, and it also makes playbooks easier to maintain and update, as it provides a clear record of what the playbook does and how it works. Plus, well-documented playbooks can be easily repurposed for different tasks, saving both time and effort. It’s a crucial step worth taking.

## 8. Keep your playbook in check
You’ve crafted a flawless playbook following all the steps, assuring reliable, secure, and efficient playbook execution. But remember, nothing is time-proof, especially playbooks. Regularly check if they still comply with best practices, your policies, and security standards. Using playbook scanning and management platforms like Steampunk Spotter, playbook maintainace becomes just another simple step in your automation workflow. **[Give it a try!](https://spotter.steampunk.si/register/team-plan)**

If you want to dive deeper into tips for **writing high-quality Ansible Playbooks**, give this a glance:

* **[The ultimate guide for writing high-quality Ansible Playbooks]()**

* **[How to create a reliable Ansible Playbook]()**

* **[Future of Ansible Playbook writing: How ChatGPT is changing the game]()**

* **[FREE WEBINAR: AI-Powered Ansible Playbooks: Transforming the future of automation]()**

* **[FREE WEBINAR: Step by step analysis of an Ansible Upgrade]()**

* **[FREE WEBINAR: Avoiding common mistakes in your Ansible Playbooks]()**

>Interested in Spotter, but don’t know how to start? [Explore our Getting started guide.](https://steampunk.si/spotter/getting-started/)