Ansible - Roles 2/2 (Glances Role)
In the previous part of this article, we had a look at Ansible Roles and how you can use them. Now, let's develop a role on our own. It's really a piece of cake.
In the previous part of this article, we had a look at Ansible Roles and how you can use them. Now, let's develop a role on our own. It's really a piece of cake.
Creating Roles
It is not easy to find an example that spoilers too much or is too boring, but I think, this one may be interesting for you. In my Job, it is very common to automate even trivial things. This time, let's automate the deployment of Glances.
Glances (the project)
You don't know Glances? It's basically "top" on steroids. It is a software, written in Python, that gathers and displays data from a host. In contrast to most other tools, glances aggregates different data from sensors, network, CPU, disk and allows the extension through plugins. The below screenshot will demonstrate this a bit better.
But, there is even more. You can run Glances in a server mode and connect via Browser or Glances from your local machine to a remote machine.
Let's do exactly this via Ansible and create a role for the Glances web server. This might be handy for a home server, too. For now, I will focus on compatibility with Fedora, only.
Directory Layout
The first thing we need to create is a proper directory layout for a new role. You can do this manually or use the ansible-galaxy
command to take care of it.
Let's start with the command-way.
# Init a new role
$ ansible-galaxy role init glances
This will create a directory and some content like this.
This is a perfect example for the start. I have also added some comments to explain which directory holds which kind of data. Anyway, for our use case, we can strip the directory down to the below.
We will have a look at each of the files in the next sections.
Content
Now, we just need to fill the role with our usual content. Everything that was before in the tasks:
section of a playbook will go into the tasks/main.yml
file, everything that can be tuned through the vars:
statement should have a default value in the defaults/main.yml
file.
meta/main.yml
The first file, I am always touching, is this one. It is mostly meant for publishing purposes, but also holds dependencies to other roles. Dependencies will be resolved by Ansible automatically and executed once, before the actual role is applied.
In most cases, we can keep it simple:
If you intend to include the role in a collection, it's even simpler.
We will have a look at Ansible collections in the next Ansible article. For now, you can stick to "just" roles.
defaults/main.yml
The defaults are meant to provide sane default values for variables. I am often thinking about the question "What may a user change?" to identify which variables I want to include.
For example, a user may want to change the package names to avoid adding additional tasks for dependencies. Therefore, I am including parametrization for the same. Parametrizing the state of packages and services allows easy manipulation without changing the role.
For the Glances example, it boils down to this.
This should give us plenty of room for future improvements and configuration options for the user.
tasks/main.yml
I often switch between defaults and tasks to ensure that everything is aligned and working. For this role, it is pretty straight forward. We need to install the packages, provide some configuration, start a service and configure the firewall.
But wait, there are 2 little issues here. First, Glances does not ship a service file and second, we only want to configure the firewall if firewalld is installed.
As a result, we will have a tasks/main.yml
file that looks like this:
As you can see, we have introduced two more topics. We need a handler to restart the glances service" and need a template for the service file.
Furthermore, we are taking care of the firewall situation with the "ansible.builtin.package_facts" module. It is pretty useful for situations like this.
Lastly, we needed to use the systemd module instead of the service module, since we need the daemon_reload
flag. This flag is taking care of telling systemd, that there is a new/changed unit file.
handlers/main.yml
Writing the handler for our role is a piece of cake, now that we are rolling.
This handler is triggered every time the package is touched (for example updates) or the service file is changed (which is created through Ansible).
templates/glances-web.service.j2
The last functional piece we need to tackle is the Service file for systemd. For now, we know the following:
- the command to start the glances web server is
glances --webserver
- we can configure the port with
--port PORT
- the service will require network from the kernel (obviously)
Without going into too many details, we can write a simple systemd unit file, that looks like this.
It's pretty minimal, but it will do the trick.
README.md
Since we want to inform users about our role and document how it works, it is also a good idea to provide a README.md. This file should contain some useful examples and information.
I will not provide any example here, but keep in mind that should always provide the amount of documentation that you expect from any other publisher.
What's Next?
So far, the role is written. If you want to push this example a bit more, here are some ideas what can be done:
- secure the web server with a username and password
- allow passing more configuration
- create a second service that will start the gRPC server and connect with glances directly to a glances server
- change/add/remove plugins
- add a way to remove Glances and all configuration
- add a way to gracefully change the port
- add support for other Linux distributions
For now, I think we should stop writing code, but finally see some results.
Execute the Role
Finally, we can execute the role. Therefore, we need to decide where we want to put our role to make use of it, and we need to write a super minimal playbook, that hooks up the role.
First, we need to make the role available to your playbooks. This can be done by either:
- put it into "/etc/ansible/roles/"
- put it into "~/.ansible/roles/"
- put it in a "roles/" directory, next to your playbook
Next, we need to define our playbook. This was already discussed in the previous part of the article. For a local execution, it boils down to a simple playbook like this.
And lastly, you can run the playbook.
# Run the playbook
$ ansible-playbook playbook.yml
Now, you can easily write new roles, add them to your playbook and re-use them whenever you need.
Publish the Role
I assume, you also want to know, how you can publish a role, don't you? In general, there are two options: "plain code" or "via Ansible Galaxy".
Just the code
The first option is the easiest. Just create a repository and push your code to it. Others can download the code and use it on their own. It's not very elegant, but still works.
Via Ansible Galaxy (and GitHub)
To make your role more discoverable, it may be a good idea to publish it to Ansible Galaxy. This can be done via CI/CD, but also manually.
Ansible Galaxy is somewhat limited, when it comes to the import of roles, and you need to publish your Ansible Role on GitHub first. You will also need a GitHub Account to create an Account on Ansible Galaxy.
Afterwards, you can use the Ansible Galaxy web interface and "Add Content" to your namespace. Just choose the role from your GitHub organization/personal workspace, and you are good to go.
Another method is, to use the ansible-galaxy
command line utility.
To be honest, I am not the biggest fan of the current state of Ansible Galaxy. There is a lot of work done to improve it and provide a better, self-hostable solution, but it seems that still needs some time.
Therefore, I am publishing my roles in an Ansible Collection via GitHub. You can find the Glances role and many others in the while-true-do.io organization in the whiletrueodio.general repository.
Links & Docs
Before closing the article, let me provide some useful documentation for further reading.
Conclusion
That's it for now with Ansible Roles. They are a nice way to make code more reusable and manageable. If done right, you don't need to write the same logic over and over again, but enhance your existing code and all your playbooks can benefit from it.
Do you use Ansible Roles already? Do you craft your own? I would love to hear from your experiences. :)