Installing and configuring software on one machine, for instance your own developer's environment, is an 'easy' task. But how about your team? Or production? Everything is documented and you only probably install some of the components manually, or better, you automate this using a set of shell scripts. The environment is now reproducible. Everything is fine.
Running a set of scripts is not a bad idea, but often what lacks is the maintenance on them or just the magic contained in them. I do not have to make the case for general configuration management, 'configuration as code' or even #DevOps I hope? In this article I will show why I use Ansible for almost all my scripts. Even small one of.
Environment
Although I have a preference for the operating system and distribution I use, I have to deal with many different versions and variants. On my workstation I prefer to use Fedora, but at work we deploy on CentOS. Some of my colleagues tend to prefer Ubuntu (latest), and customers are stuck on LTS releases. A very common situation. In one week, I had to deal with: Alpine, CentOS, Fedora, Ubuntu, (Open)SUSE, Debian, Windows, etc. This is of course an extreme case.
You would prefer to use a single environment for both development and production. When the production environment is fixed, the solution is easier. You will likely use a tool like Vagrant to stand up an environment, or some other virtualization soltuion. But what if you develop for an environment where both Ubuntu and CentOS are an option? In my case this happens a lot because of work for OpenStack, or just general Linux tool development.
Example
Below I will discuss a small example of dealing with package installation. Let's
consider you have to use Ubuntu and Fedora. This means that you already have to
deal with two different package managers, oh wait, three; apt
on Ubuntu and
yum
or dnf
on Fedora.
Bash script
As a crude solution, you could do the following:
#!/bin/sh
APTPKGS="git tmux zsh mc stow python-psutil"
RPMPKGS="git tmux zsh mc stow python-psutil"
# Crude multi-os installation option
if [ -x "/usr/bin/apt-get" ]
then
sudo apt-get install -y $APTPKGS
elif [ -x "/usr/bin/dnf" ]
then
sudo dnf install -y $RPMPKGS
elif [ -x "/usr/bin/yum" ]
then
sudo yum install -y $RPMPKGS
fi
This basically checks if a certain executable is available, and will use that
with a defined list of packages to install. If both yum
and dnf
is
installed, it will in this case use dnf
as it resolves first. This is likely
what you want, but it is hidden logic. But what if you have installed yum
on
Ubuntu? Also, because of this case, apt-get
will resolve first. But again,
this is undocumented in this script. But this knowledge should not be needed.
Note: in this case, the packages are the same for both platforms, but this is not a guarantee. Therefore I use a variable for this.
Ansible playbook
Ansible solves this problem by offering a general package
module. You can use
yum
and apt
specific options, but I prefer to use package
instead. Below
is a playbook that has the same behaviour as the above mentioned script:
tasks:
- name: Install list of required packages
package: name={{ item }} state=installed
become: yes
become_method: sudo
with_items:
- git
- tmux
- zsh
- stow
- python-psutil
- mc
Because of the task name, it becomes clear what the intention is of the commands that follow. In this case, this will use the package manager based on the OS Family that Ansible will find. This means we do not need to have the knowledge of what to pick first. There is however a problem. What if the packages have different names?
Conditionals
For this Ansible offers conditionals which you can use with when
. Below is
an example that will install the development tools on Ubuntu or Fedora, based
on the ansible_distribution
:
#!/usr/bin/env ansible-playbook
---
- hosts: localhost
#remote_user: root
become_method: sudo
tasks:
- name: Install Git
package: name=git state=installed
become: yes
- name: install the 'Development tools' package group
package: name="@Development tools" state=present
when: ansible_distribution == 'CentOS' or
ansible_distribution == 'Red Hat Enterprise Linux' or
ansible_distribution == 'Fedora'
become: yes
- name: Install the 'build-essential' meta package
package: name="build-essential" state=present
when: ansible_distribution == 'Debian' or
ansible_distribution == 'Ubuntu'
become: yes
You can also use ansible_os_family == "RedHat"
to be less specific about the
distribution you are on.
Include
You can combine conditionals with almost anything in Ansible, for instance to include or not another playbook. Below is a simple solution of this when you want to split the install instructions into separate files.
- include: install-centos.yml
when: ansible_distribution == "CentOS"
- include: install-ubuntu.yml
when: ansible_distribution == "Ubuntu"
Conclusion
With Ansible it is possible to create complex instructions that can be more maintainable than when using just scripts. Of course, just using Ansible will not act as a silver bullet. But because a playbook is more readable, the process of refactoring is easier.
In future articles I will talk about other aspects where the move to Ansible helped me. However, the bootstrap process remains... I wish there was a way to Automate everything ;-)
Hope this article has been helpful to you. If so, please consider tweeting about it. Or leave a comment if you have suggestions...
Comments
comments powered by Disqus